at 0x7fe07501eb30, file "", line 1>
+ │├┬ F2:
+ ││└ # F2 [19 B]
+ │└ # Co [87 B]
+ ├┬ D1:
+ │└ # D1 [22 B]
+ ├┬ D2:
+ │└ # D2 [2 B]
+ ├┬ D2:
+ │├┬ D2:
+ ││└ # D2 [2 B]
+ │└ # D2 [23 B]
+ └ # F1 [180 B]
+
+With trace, we see how ``dill`` stored the lambda (``F1``) by first storing
+``_create_function``, the underlying code object (``Co``) and ``_create_code``
+(which is used to handle code objects), then we handle the reference to
+the global dict (``D2``) plus other dictionaries (``D1`` and ``D2``) that
+save the lambda object's state. A ``#`` marks when the object is actually stored.
+
+
+More Information
+================
+
+Probably the best way to get started is to look at the documentation at
+http://dill.rtfd.io. Also see ``dill.tests`` for a set of scripts that
+demonstrate how ``dill`` can serialize different Python objects. You can
+run the test suite with ``python -m dill.tests``. The contents of any
+pickle file can be examined with ``undill``. As ``dill`` conforms to
+the ``pickle`` interface, the examples and documentation found at
+http://docs.python.org/library/pickle.html also apply to ``dill``
+if one will ``import dill as pickle``. The source code is also generally
+well documented, so further questions may be resolved by inspecting the
+code itself. Please feel free to submit a ticket on github, or ask a
+question on stackoverflow (**@Mike McKerns**).
+If you would like to share how you use ``dill`` in your work, please send
+an email (to **mmckerns at uqfoundation dot org**).
+
+
+Citation
+========
+
+If you use ``dill`` to do research that leads to publication, we ask that you
+acknowledge use of ``dill`` by citing the following in your publication::
+
+ M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis,
+ "Building a framework for predictive science", Proceedings of
+ the 10th Python in Science Conference, 2011;
+ http://arxiv.org/pdf/1202.1056
+
+ Michael McKerns and Michael Aivazis,
+ "pathos: a framework for heterogeneous computing", 2010- ;
+ https://uqfoundation.github.io/project/pathos
+
+Please see https://uqfoundation.github.io/project/pathos or
+http://arxiv.org/pdf/1202.1056 for further information.
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..2036546857e912725b83a5b63be9dc759f2cafd3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/RECORD
@@ -0,0 +1,56 @@
+../../../bin/get_gprof,sha256=lpXb6xvSUtU9lKE9Dw4Zy35hBLg0RsQ-01l9KNrgDuQ,2491
+../../../bin/get_objgraph,sha256=U9yru_IE03LwChLwS6_nOZovQ29eQEb_4T92bzSYi7c,1685
+../../../bin/undill,sha256=-J0asuqKiqJjosezK0OAgF4PX5gNaUjMfLQvxciY0iA,621
+dill-0.4.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
+dill-0.4.1.dist-info/LICENSE,sha256=9mnWO-v3ZbqqCyDYsMEZMpan4OoMIsUKMIt012qzk5w,1790
+dill-0.4.1.dist-info/METADATA,sha256=BA1shO3fKcjBTcKu8hSWKpGYA_VXJDgk3U-Ko7SeOGg,10171
+dill-0.4.1.dist-info/RECORD,,
+dill-0.4.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+dill-0.4.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
+dill-0.4.1.dist-info/top_level.txt,sha256=HLSIyYIjQzJiBvs3_-16ntezE3j6mWGTW0DT1xDd7X0,5
+dill/__diff.py,sha256=7XCqWIelTWSLjieOIu3Xtr1mUui9YBmltP7-tvtrG-Q,7146
+dill/__info__.py,sha256=EGg2Nh84NhPnYq1J2j6EAPixBb3psVxY-5rAWzr8prk,10756
+dill/__init__.py,sha256=Y8usnzpfsclJxT6TvRYGarEXkWEXprv4lC2ZJ0bGM2Y,3798
+dill/_dill.py,sha256=NdaVyGzOb5S57abYN0eFXcogocHQrTBkUru7cqw2fwM,91730
+dill/_objects.py,sha256=_Xc7OqnZesDmKBmtkbkrBBMDzH3TIFy_vtMRtc7lsSI,20015
+dill/_shims.py,sha256=kkIa7muWHHp9uItpNffqmbhBO4MEKPIdeyHOKxx2VF4,6635
+dill/detect.py,sha256=VbCyNMaIVudiqM1nLmGwS-yXW6M9CkdpbkVarAmnJqU,11207
+dill/logger.py,sha256=Z1wgU8BbSAEWBKoucYRHYrFhVLqqqTTi-BHcfsebi88,11143
+dill/objtypes.py,sha256=3hDEnRS040mVyA3nMJp0seIiAw9SafxbuvG5tFvthfE,736
+dill/pointers.py,sha256=zeX8XqlLBIW9vL-XH_er9HUWmInlbfWjYtva56eTmww,4467
+dill/session.py,sha256=88DJFIQrA8733P-VKkbCq0d1lyJzckQYCrGxcPeSKeE,23541
+dill/settings.py,sha256=DRFp2QC8xspPZ5Sb3eIB6abPp1wNuBNtCEjZ8DLY-x4,630
+dill/source.py,sha256=GsmYf6bEPLM9rivz1Ec7G2E-tEgAoXI95CisqdcQGGA,45710
+dill/temp.py,sha256=eFyMgZIJRUVgdRi2a4I2tZd5g3HSUFnfm5K3LV2Z93k,8027
+dill/tests/__init__.py,sha256=8OmXwJHyWK1axs4XwCD4TimAE1U_oF6S1ZIPJ8tI8Dg,479
+dill/tests/__main__.py,sha256=6jkJVIcrIHqHjyKOoDr95npJz4eASssTGKbuhWEqMow,899
+dill/tests/test_abc.py,sha256=QJ5rW4-HPG0mj-K-96zwASOC-LqGa0XnzDTNW43Cnr4,4227
+dill/tests/test_check.py,sha256=h567c9d3L-O5lc4H8lBsN5St4NkQr5Em9eAU-0l8xYA,1396
+dill/tests/test_classdef.py,sha256=UTctS9spat_KwiubR2-05hVbMvmT5_BqZJWReDC34lM,8600
+dill/tests/test_dataclasses.py,sha256=btxL2-lqjFOLSDf8imLZAAQ4bkm9xQwvS2VlPiIrlQY,890
+dill/tests/test_detect.py,sha256=pm55LV1oi3xr6zmawc2pBlzSMSMkh4TwV7IuoAh3r8M,4329
+dill/tests/test_dictviews.py,sha256=aRqVTK5DOuIoKzxWQcPenSRnvCqLyYeM2jIQsn32Tqo,1337
+dill/tests/test_diff.py,sha256=cvfUtLcJOqlH0VcEmuIHQf69194Jy7vWoEk8pO-GLKM,2667
+dill/tests/test_extendpickle.py,sha256=kYtoA2gr_CLo7NRdyu2fwOdPMt8yp4kVzd9hki7pZBk,1315
+dill/tests/test_fglobals.py,sha256=dSyPOYarpytrnb723m3nAJqGp3zeTFovA7wfoZr6Z_o,1676
+dill/tests/test_file.py,sha256=OsM189KoskuN84bag6GbYJ4OYcTy5WlHSlMWj0j0BhQ,13578
+dill/tests/test_functions.py,sha256=40QuhQIqV-jaDvuR5pq0_Haf7Pmd9M3DYJDs2oSxjiU,4267
+dill/tests/test_functors.py,sha256=iLv95xcaTasuqZh5LQBLa6AWrPeCxFRb-TLzhNzrMB4,930
+dill/tests/test_logger.py,sha256=W0QJMVMQOUOizGb1sNsKInNVS76XqrM7UFyEX-Ei_bs,2385
+dill/tests/test_mixins.py,sha256=tlCp5-NYObO7VIiK0YkciB7yRqepRJPd_RYAc79Zcvw,4007
+dill/tests/test_module.py,sha256=LVdwwdkdbRofoEHhOfszi7oVEUoE__YZyyJzRyza_Ws,1943
+dill/tests/test_moduledict.py,sha256=s5siniJxn78G83lPiQnx4eYV6Z6isCU3m2phgRTtlVA,1182
+dill/tests/test_nested.py,sha256=EuyAWkXGuERTaUaPQDOxhO1cvDhCDWPML3vYOQ0HpFg,3146
+dill/tests/test_objects.py,sha256=6Gy-YuVDuX1efV_pQEbCen7ZpMxTazY9Ihq7y1otesQ,1931
+dill/tests/test_properties.py,sha256=ex9gCfebSawz6n5RGLS4NK5tyC7zjULJBYqPSl33Yqs,1346
+dill/tests/test_pycapsule.py,sha256=EJFsKZ0kTWi17YOQ0wwaoglXP9XqDS83EYIr1-ZsNUk,1417
+dill/tests/test_recursive.py,sha256=5zwJ62LHCtd3lFsFX7__XqflV78QKwuv-hnvBISoiVs,4182
+dill/tests/test_registered.py,sha256=fO2J8t1VlNFd6ecUtd8akgjl4nRy5TldZbnBEzymtWE,1573
+dill/tests/test_restricted.py,sha256=MG6gWcyn1X2kMNL7hX40iTZNbtre8-6QwneTt6EW7eE,783
+dill/tests/test_selected.py,sha256=1lr05NaRqy27XV-xj5A93Mh8vuOEVXbZZ_ELgjyBPl0,3069
+dill/tests/test_session.py,sha256=fILaOGfRHdMd7wED2fqkRHkHlRLY13TaTnvWIcbP8ic,10161
+dill/tests/test_source.py,sha256=3QnPpuQovYz0wAxzH7U4LWaGWI0sejOra8qPRgGj8W0,7311
+dill/tests/test_sources.py,sha256=QWX_Oc3NjpiMZqLLIJsXpK7kGGOYFDv47rCt7HIX_7s,8672
+dill/tests/test_temp.py,sha256=81i8qP5zFbX7bBlXBiYMDlm_p5GTvze6zVtAPifaD_g,2619
+dill/tests/test_threads.py,sha256=a8GRhk81_gE-TsLX21BG8Jb85vRaDd8All2AafqKzHA,1257
+dill/tests/test_weakref.py,sha256=vKT130ADMeqzhD3vx8KZnF5zvvkhTAaq8WD1tyACX2Y,1602
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..ae527e7d64811439e61b93aa375defb30e06edfe
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (75.6.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/top_level.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..85eea7018a40c657c08ef73fcf3a39024b2df2cb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill-0.4.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+dill
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..371af85aa62c3a8a0eb60913ec938b87c645844b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/__init__.py
@@ -0,0 +1,353 @@
+from __future__ import annotations
+
+__docformat__ = "restructuredtext"
+
+# Let users know if they're missing any of our hard dependencies
+# except tzdata (see https://github.com/pandas-dev/pandas/issues/63264)
+_hard_dependencies = ("numpy", "dateutil")
+
+for _dependency in _hard_dependencies:
+ try:
+ __import__(_dependency)
+ except ImportError as _e: # pragma: no cover
+ raise ImportError(
+ f"Unable to import required dependency {_dependency}. "
+ "Please see the traceback for details."
+ ) from _e
+
+del _hard_dependencies, _dependency
+
+try:
+ # numpy compat
+ from pandas.compat import (
+ is_numpy_dev as _is_numpy_dev, # pyright: ignore[reportUnusedImport] # noqa: F401
+ )
+except ImportError as _err: # pragma: no cover
+ _module = _err.name
+ raise ImportError(
+ f"C extension: {_module} not built. If you want to import "
+ "pandas from the source directory, you may need to run "
+ "'python -m pip install -ve . --no-build-isolation -Ceditable-verbose=true' "
+ "to build the C extensions first."
+ ) from _err
+
+from pandas._config import (
+ get_option,
+ set_option,
+ reset_option,
+ describe_option,
+ option_context,
+ options,
+)
+
+# let init-time option registration happen
+import pandas.core.config_init # pyright: ignore[reportUnusedImport] # noqa: F401
+
+from pandas.core.api import (
+ # dtype
+ ArrowDtype,
+ Int8Dtype,
+ Int16Dtype,
+ Int32Dtype,
+ Int64Dtype,
+ UInt8Dtype,
+ UInt16Dtype,
+ UInt32Dtype,
+ UInt64Dtype,
+ Float32Dtype,
+ Float64Dtype,
+ CategoricalDtype,
+ PeriodDtype,
+ IntervalDtype,
+ DatetimeTZDtype,
+ StringDtype,
+ BooleanDtype,
+ # missing
+ NA,
+ isna,
+ isnull,
+ notna,
+ notnull,
+ # indexes
+ Index,
+ CategoricalIndex,
+ RangeIndex,
+ MultiIndex,
+ IntervalIndex,
+ TimedeltaIndex,
+ DatetimeIndex,
+ PeriodIndex,
+ IndexSlice,
+ # tseries
+ NaT,
+ Period,
+ period_range,
+ Timedelta,
+ timedelta_range,
+ Timestamp,
+ date_range,
+ bdate_range,
+ Interval,
+ interval_range,
+ DateOffset,
+ # conversion
+ to_numeric,
+ to_datetime,
+ to_timedelta,
+ # misc
+ Flags,
+ Grouper,
+ factorize,
+ unique,
+ NamedAgg,
+ array,
+ Categorical,
+ set_eng_float_format,
+ Series,
+ DataFrame,
+)
+from pandas.core.col import col
+
+from pandas.core.dtypes.dtypes import SparseDtype
+
+from pandas.tseries.api import infer_freq
+from pandas.tseries import offsets
+
+from pandas.core.computation.api import eval
+
+from pandas.core.reshape.api import (
+ concat,
+ lreshape,
+ melt,
+ wide_to_long,
+ merge,
+ merge_asof,
+ merge_ordered,
+ crosstab,
+ pivot,
+ pivot_table,
+ get_dummies,
+ from_dummies,
+ cut,
+ qcut,
+)
+
+from pandas import api, arrays, errors, io, plotting, tseries
+from pandas import testing
+from pandas.util._print_versions import show_versions
+
+from pandas.io.api import (
+ # excel
+ ExcelFile,
+ ExcelWriter,
+ read_excel,
+ # parsers
+ read_csv,
+ read_fwf,
+ read_table,
+ # pickle
+ read_pickle,
+ to_pickle,
+ # pytables
+ HDFStore,
+ read_hdf,
+ # sql
+ read_sql,
+ read_sql_query,
+ read_sql_table,
+ # misc
+ read_clipboard,
+ read_parquet,
+ read_orc,
+ read_feather,
+ read_html,
+ read_xml,
+ read_json,
+ read_stata,
+ read_sas,
+ read_spss,
+ read_iceberg,
+)
+
+from pandas.io.json._normalize import json_normalize
+
+from pandas.util._tester import test
+
+# use the closest tagged version if possible
+_built_with_meson = False
+try:
+ from pandas._version_meson import ( # pyright: ignore [reportMissingImports]
+ __version__,
+ __git_version__,
+ )
+
+ _built_with_meson = True
+except ImportError:
+ from pandas._version import get_versions
+
+ v = get_versions()
+ __version__ = v.get("closest-tag", v["version"])
+ __git_version__ = v.get("full-revisionid")
+ del get_versions, v
+
+
+# module level doc-string
+__doc__ = """
+pandas - a powerful data analysis and manipulation library for Python
+=====================================================================
+
+**pandas** is a Python package providing fast, flexible, and expressive data
+structures designed to make working with "relational" or "labeled" data both
+easy and intuitive. It aims to be the fundamental high-level building block for
+doing practical, **real world** data analysis in Python. Additionally, it has
+the broader goal of becoming **the most powerful and flexible open source data
+analysis / manipulation tool available in any language**. It is already well on
+its way toward this goal.
+
+Main Features
+-------------
+Here are just a few of the things that pandas does well:
+
+ - Easy handling of missing data in floating point as well as non-floating
+ point data.
+ - Size mutability: columns can be inserted and deleted from DataFrame and
+ higher dimensional objects
+ - Automatic and explicit data alignment: objects can be explicitly aligned
+ to a set of labels, or the user can simply ignore the labels and let
+ `Series`, `DataFrame`, etc. automatically align the data for you in
+ computations.
+ - Powerful, flexible group by functionality to perform split-apply-combine
+ operations on data sets, for both aggregating and transforming data.
+ - Make it easy to convert ragged, differently-indexed data in other Python
+ and NumPy data structures into DataFrame objects.
+ - Intelligent label-based slicing, fancy indexing, and subsetting of large
+ data sets.
+ - Intuitive merging and joining data sets.
+ - Flexible reshaping and pivoting of data sets.
+ - Hierarchical labeling of axes (possible to have multiple labels per tick).
+ - Robust IO tools for loading data from flat files (CSV and delimited),
+ Excel files, databases, and saving/loading data from the ultrafast HDF5
+ format.
+ - Time series-specific functionality: date range generation and frequency
+ conversion, moving window statistics, date shifting and lagging.
+"""
+
+# Use __all__ to let type checkers know what is part of the public API.
+# Pandas is not (yet) a py.typed library: the public API is determined
+# based on the documentation.
+__all__ = [
+ "NA",
+ "ArrowDtype",
+ "BooleanDtype",
+ "Categorical",
+ "CategoricalDtype",
+ "CategoricalIndex",
+ "DataFrame",
+ "DateOffset",
+ "DatetimeIndex",
+ "DatetimeTZDtype",
+ "ExcelFile",
+ "ExcelWriter",
+ "Flags",
+ "Float32Dtype",
+ "Float64Dtype",
+ "Grouper",
+ "HDFStore",
+ "Index",
+ "IndexSlice",
+ "Int8Dtype",
+ "Int16Dtype",
+ "Int32Dtype",
+ "Int64Dtype",
+ "Interval",
+ "IntervalDtype",
+ "IntervalIndex",
+ "MultiIndex",
+ "NaT",
+ "NamedAgg",
+ "Period",
+ "PeriodDtype",
+ "PeriodIndex",
+ "RangeIndex",
+ "Series",
+ "SparseDtype",
+ "StringDtype",
+ "Timedelta",
+ "TimedeltaIndex",
+ "Timestamp",
+ "UInt8Dtype",
+ "UInt16Dtype",
+ "UInt32Dtype",
+ "UInt64Dtype",
+ "api",
+ "array",
+ "arrays",
+ "bdate_range",
+ "col",
+ "concat",
+ "crosstab",
+ "cut",
+ "date_range",
+ "describe_option",
+ "errors",
+ "eval",
+ "factorize",
+ "from_dummies",
+ "get_dummies",
+ "get_option",
+ "infer_freq",
+ "interval_range",
+ "io",
+ "isna",
+ "isnull",
+ "json_normalize",
+ "lreshape",
+ "melt",
+ "merge",
+ "merge_asof",
+ "merge_ordered",
+ "notna",
+ "notnull",
+ "offsets",
+ "option_context",
+ "options",
+ "period_range",
+ "pivot",
+ "pivot_table",
+ "plotting",
+ "qcut",
+ "read_clipboard",
+ "read_csv",
+ "read_excel",
+ "read_feather",
+ "read_fwf",
+ "read_hdf",
+ "read_html",
+ "read_iceberg",
+ "read_json",
+ "read_orc",
+ "read_parquet",
+ "read_pickle",
+ "read_sas",
+ "read_spss",
+ "read_sql",
+ "read_sql_query",
+ "read_sql_table",
+ "read_stata",
+ "read_table",
+ "read_xml",
+ "reset_option",
+ "set_eng_float_format",
+ "set_option",
+ "show_versions",
+ "test",
+ "testing",
+ "timedelta_range",
+ "to_datetime",
+ "to_numeric",
+ "to_pickle",
+ "to_timedelta",
+ "tseries",
+ "unique",
+ "wide_to_long",
+]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/_typing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/_typing.py
new file mode 100644
index 0000000000000000000000000000000000000000..7cf6d58bdf08507244eaca9e9e7c99a8770b6747
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/_typing.py
@@ -0,0 +1,578 @@
+from __future__ import annotations
+
+from builtins import type as type_t # pyright: ignore[reportUnusedImport]
+from collections.abc import (
+ Callable,
+ Hashable,
+ Iterator,
+ Mapping,
+ MutableMapping,
+ Sequence,
+)
+from datetime import (
+ date,
+ datetime,
+ timedelta,
+ tzinfo,
+)
+from os import PathLike
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ ParamSpec,
+ Protocol,
+ SupportsIndex,
+ TypeAlias,
+ TypeVar,
+ Union,
+ overload,
+)
+
+import numpy as np
+import numpy.typing as npt
+
+# To prevent import cycles place any internal imports in the branch below
+# and use a string literal forward reference to it in subsequent types
+# https://mypy.readthedocs.io/en/latest/common_issues.html#import-cycles
+
+# Note that Union is needed when a Union includes a pandas type
+
+if TYPE_CHECKING:
+ from pandas._libs import (
+ NaTType,
+ Period,
+ Timedelta,
+ Timestamp,
+ )
+ from pandas._libs.tslibs import BaseOffset
+
+ from pandas.core.dtypes.dtypes import ExtensionDtype
+
+ from pandas import (
+ DatetimeIndex,
+ Interval,
+ PeriodIndex,
+ TimedeltaIndex,
+ )
+ from pandas.arrays import (
+ DatetimeArray,
+ TimedeltaArray,
+ )
+ from pandas.core.arrays.base import ExtensionArray
+ from pandas.core.frame import DataFrame
+ from pandas.core.generic import NDFrame
+ from pandas.core.groupby.generic import (
+ DataFrameGroupBy,
+ GroupBy,
+ SeriesGroupBy,
+ )
+ from pandas.core.indexes.base import Index
+ from pandas.core.internals import (
+ BlockManager,
+ SingleBlockManager,
+ )
+ from pandas.core.resample import Resampler
+ from pandas.core.series import Series
+ from pandas.core.window.rolling import BaseWindow
+
+ from pandas.io.formats.format import EngFormatter
+ from pandas.tseries.holiday import AbstractHolidayCalendar
+
+ ScalarLike_co: TypeAlias = int | float | complex | str | bytes | np.generic
+
+ # numpy compatible types
+ NumpyValueArrayLike: TypeAlias = ScalarLike_co | npt.ArrayLike
+ NumpySorter: TypeAlias = npt.NDArray[np.integer] | None
+
+
+P = ParamSpec("P")
+
+HashableT = TypeVar("HashableT", bound=Hashable)
+HashableT2 = TypeVar("HashableT2", bound=Hashable)
+MutableMappingT = TypeVar("MutableMappingT", bound=MutableMapping)
+
+# array-like
+
+ArrayLike: TypeAlias = Union["ExtensionArray", np.ndarray]
+ArrayLikeT = TypeVar("ArrayLikeT", "ExtensionArray", np.ndarray)
+AnyArrayLike: TypeAlias = Union[ArrayLike, "Index", "Series"]
+TimeArrayLike: TypeAlias = Union["DatetimeArray", "TimedeltaArray"]
+
+# list-like
+
+# from https://github.com/hauntsaninja/useful_types
+# includes Sequence-like objects but excludes str and bytes
+_T_co = TypeVar("_T_co", covariant=True)
+
+
+class SequenceNotStr(Protocol[_T_co]):
+ __module__: str = "pandas.api.typing.aliases"
+
+ @overload
+ def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...
+
+ @overload
+ def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ...
+
+ def __contains__(self, value: object, /) -> bool: ...
+
+ def __len__(self) -> int: ...
+
+ def __iter__(self) -> Iterator[_T_co]: ...
+
+ def index(self, value: Any, start: int = ..., stop: int = ..., /) -> int: ...
+
+ def count(self, value: Any, /) -> int: ...
+
+ def __reversed__(self) -> Iterator[_T_co]: ...
+
+
+ListLike: TypeAlias = AnyArrayLike | SequenceNotStr | range
+
+# scalars
+
+PythonScalar: TypeAlias = str | float | bool
+DatetimeLikeScalar: TypeAlias = Union["Period", "Timestamp", "Timedelta"]
+
+# aligned with pandas-stubs - typical scalars found in Series. Explicitly leaves
+# out object
+_IndexIterScalar: TypeAlias = Union[
+ str,
+ bytes,
+ date,
+ datetime,
+ timedelta,
+ np.datetime64,
+ np.timedelta64,
+ bool,
+ int,
+ float,
+ "Timestamp",
+ "Timedelta",
+]
+Scalar: TypeAlias = Union[
+ _IndexIterScalar, "Interval", complex, np.integer, np.floating, np.complexfloating
+]
+
+IntStrT = TypeVar("IntStrT", bound=int | str)
+
+# timestamp and timedelta convertible types
+
+TimestampConvertibleTypes: TypeAlias = Union[
+ "Timestamp", date, np.datetime64, np.int64, float, str
+]
+TimestampNonexistent: TypeAlias = (
+ Literal["shift_forward", "shift_backward", "NaT", "raise"] | timedelta
+)
+
+TimedeltaConvertibleTypes: TypeAlias = Union[
+ "Timedelta", timedelta, np.timedelta64, np.int64, float, str
+]
+Timezone: TypeAlias = str | tzinfo
+
+ToTimestampHow: TypeAlias = Literal["s", "e", "start", "end"]
+
+# NDFrameT is stricter and ensures that the same subclass of NDFrame always is
+# used. E.g. `def func(a: NDFrameT) -> NDFrameT: ...` means that if a
+# Series is passed into a function, a Series is always returned and if a DataFrame is
+# passed in, a DataFrame is always returned.
+NDFrameT = TypeVar("NDFrameT", bound="NDFrame")
+
+IndexT = TypeVar("IndexT", bound="Index")
+FreqIndexT = TypeVar("FreqIndexT", "DatetimeIndex", "PeriodIndex", "TimedeltaIndex")
+NumpyIndexT = TypeVar("NumpyIndexT", np.ndarray, "Index")
+
+AxisInt: TypeAlias = int
+Axis: TypeAlias = AxisInt | Literal["index", "columns", "rows"]
+IndexLabel: TypeAlias = Hashable | Sequence[Hashable]
+Level: TypeAlias = Hashable
+Shape: TypeAlias = tuple[int, ...]
+Suffixes: TypeAlias = Sequence[str | None]
+Ordered: TypeAlias = bool | None
+JSONSerializable: TypeAlias = PythonScalar | list | dict | None
+Frequency: TypeAlias = Union[str, "BaseOffset"]
+Axes: TypeAlias = ListLike
+
+RandomState: TypeAlias = (
+ int
+ | np.ndarray
+ | np.random.Generator
+ | np.random.BitGenerator
+ | np.random.RandomState
+)
+
+
+# dtypes
+NpDtype: TypeAlias = str | np.dtype | type[str | complex | bool | object]
+Dtype: TypeAlias = Union["ExtensionDtype", NpDtype]
+AstypeArg: TypeAlias = Union["ExtensionDtype", npt.DTypeLike]
+# DtypeArg specifies all allowable dtypes in a functions its dtype argument
+DtypeArg: TypeAlias = Dtype | Mapping[Hashable, Dtype]
+DtypeObj: TypeAlias = Union[np.dtype, "ExtensionDtype"]
+
+# converters
+ConvertersArg: TypeAlias = dict[Hashable, Callable[[Dtype], Dtype]]
+
+# parse_dates
+ParseDatesArg: TypeAlias = (
+ bool | list[Hashable] | list[list[Hashable]] | dict[Hashable, list[Hashable]]
+)
+
+# For functions like rename that convert one label to another
+Renamer: TypeAlias = Mapping[Any, Hashable] | Callable[[Any], Hashable]
+
+# to maintain type information across generic functions and parametrization
+T = TypeVar("T")
+
+# used in decorators to preserve the signature of the function it decorates
+# see https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
+FuncType: TypeAlias = Callable[..., Any]
+F = TypeVar("F", bound=FuncType)
+TypeT = TypeVar("TypeT", bound=type)
+
+# types of vectorized key functions for DataFrame::sort_values and
+# DataFrame::sort_index, among others
+ValueKeyFunc: TypeAlias = Callable[["Series"], Union["Series", AnyArrayLike]] | None
+IndexKeyFunc: TypeAlias = Callable[["Index"], Union["Index", AnyArrayLike]] | None
+
+# types of `func` kwarg for DataFrame.aggregate and Series.aggregate
+AggFuncTypeBase: TypeAlias = Callable | str
+AggFuncTypeDict: TypeAlias = MutableMapping[
+ Hashable, AggFuncTypeBase | list[AggFuncTypeBase]
+]
+AggFuncType: TypeAlias = AggFuncTypeBase | list[AggFuncTypeBase] | AggFuncTypeDict
+AggObjType: TypeAlias = Union[
+ "Series",
+ "DataFrame",
+ "GroupBy",
+ "SeriesGroupBy",
+ "DataFrameGroupBy",
+ "BaseWindow",
+ "Resampler",
+]
+
+PythonFuncType: TypeAlias = Callable[[Any], Any]
+
+# filenames and file-like-objects
+AnyStr_co = TypeVar("AnyStr_co", str, bytes, covariant=True)
+AnyStr_contra = TypeVar("AnyStr_contra", str, bytes, contravariant=True)
+
+
+class BaseBuffer(Protocol):
+ @property
+ def mode(self) -> str:
+ # for _get_filepath_or_buffer
+ ...
+
+ def seek(self, offset: int, whence: int = ..., /) -> int:
+ # with one argument: gzip.GzipFile, bz2.BZ2File
+ # with two arguments: zip.ZipFile, read_sas
+ ...
+
+ def seekable(self) -> bool:
+ # for bz2.BZ2File
+ ...
+
+ def tell(self) -> int:
+ # for zip.ZipFile, read_stata, to_stata
+ ...
+
+
+class ReadBuffer(BaseBuffer, Protocol[AnyStr_co]):
+ __module__: str = "pandas.api.typing.aliases"
+
+ def read(self, n: int = ..., /) -> AnyStr_co:
+ # for BytesIOWrapper, gzip.GzipFile, bz2.BZ2File
+ ...
+
+
+class WriteBuffer(BaseBuffer, Protocol[AnyStr_contra]):
+ __module__: str = "pandas.api.typing.aliases"
+
+ def write(self, b: AnyStr_contra, /) -> Any:
+ # for gzip.GzipFile, bz2.BZ2File
+ ...
+
+ def flush(self) -> Any:
+ # for gzip.GzipFile, bz2.BZ2File
+ ...
+
+
+class ReadPickleBuffer(ReadBuffer[bytes], Protocol):
+ __module__: str = "pandas.api.typing.aliases"
+
+ def readline(self) -> bytes: ...
+
+
+class WriteExcelBuffer(WriteBuffer[bytes], Protocol):
+ __module__: str = "pandas.api.typing.aliases"
+
+ def truncate(self, size: int | None = ..., /) -> int: ...
+
+
+class ReadCsvBuffer(ReadBuffer[AnyStr_co], Protocol):
+ __module__: str = "pandas.api.typing.aliases"
+
+ def __iter__(self) -> Iterator[AnyStr_co]:
+ # for engine=python
+ ...
+
+ def fileno(self) -> int:
+ # for _MMapWrapper
+ ...
+
+ def readline(self) -> AnyStr_co:
+ # for engine=python
+ ...
+
+ @property
+ def closed(self) -> bool:
+ # for engine=pyarrow
+ ...
+
+
+FilePath: TypeAlias = str | PathLike[str]
+
+# for arbitrary kwargs passed during reading/writing files
+StorageOptions: TypeAlias = dict[str, Any] | None
+
+# compression keywords and compression
+CompressionDict: TypeAlias = dict[str, Any]
+CompressionOptions: TypeAlias = (
+ Literal["infer", "gzip", "bz2", "zip", "xz", "zstd", "tar"] | CompressionDict | None
+)
+ParquetCompressionOptions: TypeAlias = (
+ Literal["snappy", "gzip", "brotli", "lz4", "zstd"] | None
+)
+
+# types in DataFrameFormatter
+FormattersType: TypeAlias = (
+ list[Callable] | tuple[Callable, ...] | Mapping[str | int, Callable]
+)
+ColspaceType: TypeAlias = Mapping[Hashable, str | int]
+FloatFormatType: TypeAlias = Union[str, Callable, "EngFormatter"]
+ColspaceArgType: TypeAlias = (
+ str | int | Sequence[str | int] | Mapping[Hashable, str | int]
+)
+
+# Arguments for fillna()
+FillnaOptions: TypeAlias = Literal["backfill", "bfill", "ffill", "pad"]
+InterpolateOptions: TypeAlias = Literal[
+ "linear",
+ "time",
+ "index",
+ "values",
+ "nearest",
+ "zero",
+ "slinear",
+ "quadratic",
+ "cubic",
+ "barycentric",
+ "polynomial",
+ "krogh",
+ "piecewise_polynomial",
+ "spline",
+ "pchip",
+ "akima",
+ "cubicspline",
+ "from_derivatives",
+]
+
+# internals
+Manager: TypeAlias = Union["BlockManager", "SingleBlockManager"]
+
+# indexing
+# PositionalIndexer -> valid 1D positional indexer, e.g. can pass
+# to ndarray.__getitem__
+# ScalarIndexer is for a single value as the index
+# SequenceIndexer is for list like or slices (but not tuples)
+# PositionalIndexerTuple is extends the PositionalIndexer for 2D arrays
+# These are used in various __getitem__ overloads
+# TODO(typing#684): add Ellipsis, see
+# https://github.com/python/typing/issues/684#issuecomment-548203158
+# https://bugs.python.org/issue41810
+# Using List[int] here rather than Sequence[int] to disallow tuples.
+ScalarIndexer: TypeAlias = int | np.integer
+SequenceIndexer: TypeAlias = slice | list[int] | np.ndarray
+PositionalIndexer: TypeAlias = ScalarIndexer | SequenceIndexer
+PositionalIndexerTuple: TypeAlias = tuple[PositionalIndexer, PositionalIndexer]
+PositionalIndexer2D: TypeAlias = PositionalIndexer | PositionalIndexerTuple
+TakeIndexer: TypeAlias = Sequence[int] | Sequence[np.integer] | npt.NDArray[np.integer]
+
+# Shared by functions such as drop and astype
+IgnoreRaise: TypeAlias = Literal["ignore", "raise"]
+
+# Windowing rank methods
+WindowingRankType: TypeAlias = Literal["average", "min", "max"]
+
+# read_csv engines
+CSVEngine: TypeAlias = Literal["c", "python", "pyarrow", "python-fwf"]
+
+# read_json engines
+JSONEngine: TypeAlias = Literal["ujson", "pyarrow"]
+
+# read_xml parsers
+XMLParsers: TypeAlias = Literal["lxml", "etree"]
+
+# read_html flavors
+HTMLFlavors: TypeAlias = Literal["lxml", "html5lib", "bs4"]
+
+# Interval closed type
+IntervalLeftRight: TypeAlias = Literal["left", "right"]
+IntervalClosedType: TypeAlias = IntervalLeftRight | Literal["both", "neither"]
+
+# datetime and NaTType
+DatetimeNaTType: TypeAlias = Union[datetime, "NaTType"]
+DateTimeErrorChoices: TypeAlias = Literal["raise", "coerce"]
+
+# sort_index
+SortKind: TypeAlias = Literal["quicksort", "mergesort", "heapsort", "stable"]
+NaPosition: TypeAlias = Literal["first", "last"]
+
+# Arguments for nsmallest and nlargest
+NsmallestNlargestKeep: TypeAlias = Literal["first", "last", "all"]
+
+# quantile interpolation
+QuantileInterpolation: TypeAlias = Literal[
+ "linear", "lower", "higher", "midpoint", "nearest"
+]
+
+# plotting
+PlottingOrientation: TypeAlias = Literal["horizontal", "vertical"]
+
+# dropna
+AnyAll: TypeAlias = Literal["any", "all"]
+
+# merge
+MergeHow: TypeAlias = Literal[
+ "left", "right", "inner", "outer", "cross", "left_anti", "right_anti"
+]
+MergeValidate: TypeAlias = Literal[
+ "one_to_one",
+ "1:1",
+ "one_to_many",
+ "1:m",
+ "many_to_one",
+ "m:1",
+ "many_to_many",
+ "m:m",
+]
+
+# join
+JoinHow: TypeAlias = Literal["left", "right", "inner", "outer"]
+JoinValidate: TypeAlias = Literal[
+ "one_to_one",
+ "1:1",
+ "one_to_many",
+ "1:m",
+ "many_to_one",
+ "m:1",
+ "many_to_many",
+ "m:m",
+]
+
+# reindex
+ReindexMethod: TypeAlias = FillnaOptions | Literal["nearest"]
+
+MatplotlibColor: TypeAlias = str | Sequence[float]
+TimeGrouperOrigin: TypeAlias = Union[
+ "Timestamp", Literal["epoch", "start", "start_day", "end", "end_day"]
+]
+TimeAmbiguous: TypeAlias = (
+ Literal["infer", "NaT", "raise"] | bool | npt.NDArray[np.bool_]
+)
+TimeNonexistent: TypeAlias = (
+ Literal["shift_forward", "shift_backward", "NaT", "raise"] | timedelta
+)
+
+DropKeep: TypeAlias = Literal["first", "last", False]
+CorrelationMethod: TypeAlias = (
+ Literal["pearson", "kendall", "spearman"]
+ | Callable[[np.ndarray, np.ndarray], float]
+)
+
+AlignJoin: TypeAlias = Literal["outer", "inner", "left", "right"]
+DtypeBackend: TypeAlias = Literal["pyarrow", "numpy_nullable"]
+
+TimeUnit: TypeAlias = Literal["s", "ms", "us", "ns"]
+OpenFileErrors: TypeAlias = Literal[
+ "strict",
+ "ignore",
+ "replace",
+ "surrogateescape",
+ "xmlcharrefreplace",
+ "backslashreplace",
+ "namereplace",
+]
+
+# update
+UpdateJoin: TypeAlias = Literal["left"]
+
+# applymap
+NaAction: TypeAlias = Literal["ignore"]
+
+# from_dict
+FromDictOrient: TypeAlias = Literal["columns", "index", "tight"]
+
+# to_stata
+ToStataByteorder: TypeAlias = Literal[">", "<", "little", "big"]
+
+# ExcelWriter
+ExcelWriterIfSheetExists: TypeAlias = Literal["error", "new", "replace", "overlay"]
+ExcelWriterMergeCells: TypeAlias = bool | Literal["columns"]
+
+# Offsets
+OffsetCalendar: TypeAlias = Union[np.busdaycalendar, "AbstractHolidayCalendar"]
+
+# read_csv: usecols
+UsecolsArgType: TypeAlias = (
+ SequenceNotStr[Hashable] | range | AnyArrayLike | Callable[[HashableT], bool] | None
+)
+
+# maintain the sub-type of any hashable sequence
+SequenceT = TypeVar("SequenceT", bound=Sequence[Hashable])
+
+SliceType: TypeAlias = Hashable | None
+
+
+# Arrow PyCapsule Interface
+# from https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html#protocol-typehints
+
+
+class ArrowArrayExportable(Protocol):
+ """
+ An object with an ``__arrow_c_array__`` method.
+
+ This method indicates the object is an Arrow-compatible object implementing
+ the `Arrow PyCapsule Protocol`_ (exposing the `Arrow C Data Interface`_ in
+ Python), enabling zero-copy Arrow data interchange across libraries.
+
+ .. _Arrow PyCapsule Protocol: https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html
+ .. _Arrow C Data Interface: https://arrow.apache.org/docs/format/CDataInterface.html
+
+ """
+
+ def __arrow_c_array__(
+ self, requested_schema: object | None = None
+ ) -> tuple[object, object]: ...
+
+
+class ArrowStreamExportable(Protocol):
+ """
+ An object with an ``__arrow_c_stream__`` method.
+
+ This method indicates the object is an Arrow-compatible object implementing
+ the `Arrow PyCapsule Protocol`_ (exposing the `Arrow C Data Interface`_
+ for streams in Python), enabling zero-copy Arrow data interchange across
+ libraries.
+
+ .. _Arrow PyCapsule Protocol: https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html
+ .. _Arrow C Stream Interface: https://arrow.apache.org/docs/format/CStreamInterface.html
+
+ """
+
+ def __arrow_c_stream__(self, requested_schema: object | None = None) -> object: ...
+
+
+__all__ = ["type_t"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/_version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/_version.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7b662af272846b3a5cceae9e69b688ba0ed919b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/_version.py
@@ -0,0 +1,692 @@
+# This file helps to compute a version number in source trees obtained from
+# git-archive tarball (such as those provided by github's download-from-tag
+# feature). Distribution tarballs (built by setup.py sdist) and build
+# directories (produced by setup.py build) will contain a much shorter file
+# that just contains the computed version number.
+
+# This file is released into the public domain.
+# Generated by versioneer-0.28
+# https://github.com/python-versioneer/python-versioneer
+
+"""Git implementation of _version.py."""
+
+from collections.abc import Callable
+import errno
+import functools
+import os
+import re
+import subprocess
+import sys
+
+
+def get_keywords():
+ """Get the keywords needed to look up the version information."""
+ # these strings will be replaced by git during git-archive.
+ # setup.py/versioneer.py will grep for the variable names, so they must
+ # each be defined on a line of their own. _version.py will just call
+ # get_keywords().
+ git_refnames = " (HEAD, tag: v3.0.2, origin/3.0.x)"
+ git_full = "ab90747e3dae0e69b1bdbf083820b8075689b34b"
+ git_date = "2026-03-30 21:34:07 +0200"
+ keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
+ return keywords
+
+
+class VersioneerConfig:
+ """Container for Versioneer configuration parameters."""
+
+
+def get_config():
+ """Create, populate and return the VersioneerConfig() object."""
+ # these strings are filled in when 'setup.py versioneer' creates
+ # _version.py
+ cfg = VersioneerConfig()
+ cfg.VCS = "git"
+ cfg.style = "pep440"
+ cfg.tag_prefix = "v"
+ cfg.parentdir_prefix = "pandas-"
+ cfg.versionfile_source = "pandas/_version.py"
+ cfg.verbose = False
+ return cfg
+
+
+class NotThisMethod(Exception):
+ """Exception raised if a method is not valid for the current scenario."""
+
+
+LONG_VERSION_PY: dict[str, str] = {}
+HANDLERS: dict[str, dict[str, Callable]] = {}
+
+
+def register_vcs_handler(vcs, method): # decorator
+ """Create decorator to mark a method as the handler of a VCS."""
+
+ def decorate(f):
+ """Store f in HANDLERS[vcs][method]."""
+ if vcs not in HANDLERS:
+ HANDLERS[vcs] = {}
+ HANDLERS[vcs][method] = f
+ return f
+
+ return decorate
+
+
+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None):
+ """Call the given command(s)."""
+ assert isinstance(commands, list)
+ process = None
+
+ popen_kwargs = {}
+ if sys.platform == "win32":
+ # This hides the console window if pythonw.exe is used
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ popen_kwargs["startupinfo"] = startupinfo
+
+ for command in commands:
+ dispcmd = str([command, *args])
+ try:
+ # remember shell=False, so use git.cmd on windows, not just git
+ process = subprocess.Popen(
+ [command, *args],
+ cwd=cwd,
+ env=env,
+ stdout=subprocess.PIPE,
+ stderr=(subprocess.PIPE if hide_stderr else None),
+ **popen_kwargs,
+ )
+ break
+ except OSError:
+ e = sys.exc_info()[1]
+ if e.errno == errno.ENOENT:
+ continue
+ if verbose:
+ print(f"unable to run {dispcmd}")
+ print(e)
+ return None, None
+ else:
+ if verbose:
+ print(f"unable to find command, tried {commands}")
+ return None, None
+ stdout = process.communicate()[0].strip().decode()
+ if process.returncode != 0:
+ if verbose:
+ print(f"unable to run {dispcmd} (error)")
+ print(f"stdout was {stdout}")
+ return None, process.returncode
+ return stdout, process.returncode
+
+
+def versions_from_parentdir(parentdir_prefix, root, verbose):
+ """Try to determine the version from the parent directory name.
+
+ Source tarballs conventionally unpack into a directory that includes both
+ the project name and a version string. We will also support searching up
+ two directory levels for an appropriately named parent directory
+ """
+ rootdirs = []
+
+ for _ in range(3):
+ dirname = os.path.basename(root)
+ if dirname.startswith(parentdir_prefix):
+ return {
+ "version": dirname[len(parentdir_prefix) :],
+ "full-revisionid": None,
+ "dirty": False,
+ "error": None,
+ "date": None,
+ }
+ rootdirs.append(root)
+ root = os.path.dirname(root) # up a level
+
+ if verbose:
+ print(
+ f"Tried directories {rootdirs!s} \
+ but none started with prefix {parentdir_prefix}"
+ )
+ raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+
+
+@register_vcs_handler("git", "get_keywords")
+def git_get_keywords(versionfile_abs):
+ """Extract version information from the given file."""
+ # the code embedded in _version.py can just fetch the value of these
+ # keywords. When used from setup.py, we don't want to import _version.py,
+ # so we do it with a regexp instead. This function is not used from
+ # _version.py.
+ keywords = {}
+ try:
+ with open(versionfile_abs, encoding="utf-8") as fobj:
+ for line in fobj:
+ if line.strip().startswith("git_refnames ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["refnames"] = mo.group(1)
+ if line.strip().startswith("git_full ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["full"] = mo.group(1)
+ if line.strip().startswith("git_date ="):
+ mo = re.search(r'=\s*"(.*)"', line)
+ if mo:
+ keywords["date"] = mo.group(1)
+ except OSError:
+ pass
+ return keywords
+
+
+@register_vcs_handler("git", "keywords")
+def git_versions_from_keywords(keywords, tag_prefix, verbose):
+ """Get version information from git keywords."""
+ if "refnames" not in keywords:
+ raise NotThisMethod("Short version file found")
+ date = keywords.get("date")
+ if date is not None:
+ # Use only the last line. Previous lines may contain GPG signature
+ # information.
+ date = date.splitlines()[-1]
+
+ # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
+ # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
+ # -like" string, which we must then edit to make compliant), because
+ # it's been around since git-1.5.3, and it's too difficult to
+ # discover which version we're using, or to work around using an
+ # older one.
+ date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+ refnames = keywords["refnames"].strip()
+ if refnames.startswith("$Format"):
+ if verbose:
+ print("keywords are unexpanded, not using")
+ raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+ refs = {r.strip() for r in refnames.strip("()").split(",")}
+ # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+ # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+ TAG = "tag: "
+ tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
+ if not tags:
+ # Either we're using git < 1.8.3, or there really are no tags. We use
+ # a heuristic: assume all version tags have a digit. The old git %d
+ # expansion behaves like git log --decorate=short and strips out the
+ # refs/heads/ and refs/tags/ prefixes that would let us distinguish
+ # between branches and tags. By ignoring refnames without digits, we
+ # filter out many common branch names like "release" and
+ # "stabilization", as well as "HEAD" and "master".
+ tags = {r for r in refs if re.search(r"\d", r)}
+ if verbose:
+ print(f"discarding '{','.join(refs - tags)}', no digits")
+ if verbose:
+ print(f"likely tags: {','.join(sorted(tags))}")
+ for ref in sorted(tags):
+ # sorting will prefer e.g. "2.0" over "2.0rc1"
+ if ref.startswith(tag_prefix):
+ r = ref[len(tag_prefix) :]
+ # Filter out refs that exactly match prefix or that don't start
+ # with a number once the prefix is stripped (mostly a concern
+ # when prefix is '')
+ if not re.match(r"\d", r):
+ continue
+ if verbose:
+ print(f"picking {r}")
+ return {
+ "version": r,
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False,
+ "error": None,
+ "date": date,
+ }
+ # no suitable tags, so version is "0+unknown", but full hex is still there
+ if verbose:
+ print("no suitable tags, using unknown + full revision id")
+ return {
+ "version": "0+unknown",
+ "full-revisionid": keywords["full"].strip(),
+ "dirty": False,
+ "error": "no suitable tags",
+ "date": None,
+ }
+
+
+@register_vcs_handler("git", "pieces_from_vcs")
+def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
+ """Get version from 'git describe' in the root of the source tree.
+
+ This only gets called if the git-archive 'subst' keywords were *not*
+ expanded, and _version.py hasn't already been rewritten with a short
+ version string, meaning we're inside a checked out source tree.
+ """
+ GITS = ["git"]
+ if sys.platform == "win32":
+ GITS = ["git.cmd", "git.exe"]
+
+ # GIT_DIR can interfere with correct operation of Versioneer.
+ # It may be intended to be passed to the Versioneer-versioned project,
+ # but that should not change where we get our version from.
+ env = os.environ.copy()
+ env.pop("GIT_DIR", None)
+ runner = functools.partial(runner, env=env)
+
+ _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
+ if rc != 0:
+ if verbose:
+ print(f"Directory {root} not under git control")
+ raise NotThisMethod("'git rev-parse --git-dir' returned error")
+
+ # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+ # if there isn't one, this yields HEX[-dirty] (no NUM)
+ describe_out, rc = runner(
+ GITS,
+ [
+ "describe",
+ "--tags",
+ "--dirty",
+ "--always",
+ "--long",
+ "--match",
+ f"{tag_prefix}[[:digit:]]*",
+ ],
+ cwd=root,
+ )
+ # --long was added in git-1.5.5
+ if describe_out is None:
+ raise NotThisMethod("'git describe' failed")
+ describe_out = describe_out.strip()
+ full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+ if full_out is None:
+ raise NotThisMethod("'git rev-parse' failed")
+ full_out = full_out.strip()
+
+ pieces = {}
+ pieces["long"] = full_out
+ pieces["short"] = full_out[:7] # maybe improved later
+ pieces["error"] = None
+
+ branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
+ # --abbrev-ref was added in git-1.6.3
+ if rc != 0 or branch_name is None:
+ raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
+ branch_name = branch_name.strip()
+
+ if branch_name == "HEAD":
+ # If we aren't exactly on a branch, pick a branch which represents
+ # the current commit. If all else fails, we are on a branchless
+ # commit.
+ branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
+ # --contains was added in git-1.5.4
+ if rc != 0 or branches is None:
+ raise NotThisMethod("'git branch --contains' returned error")
+ branches = branches.split("\n")
+
+ # Remove the first line if we're running detached
+ if "(" in branches[0]:
+ branches.pop(0)
+
+ # Strip off the leading "* " from the list of branches.
+ branches = [branch[2:] for branch in branches]
+ if "master" in branches:
+ branch_name = "master"
+ elif not branches:
+ branch_name = None
+ else:
+ # Pick the first branch that is returned. Good or bad.
+ branch_name = branches[0]
+
+ pieces["branch"] = branch_name
+
+ # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+ # TAG might have hyphens.
+ git_describe = describe_out
+
+ # look for -dirty suffix
+ dirty = git_describe.endswith("-dirty")
+ pieces["dirty"] = dirty
+ if dirty:
+ git_describe = git_describe[: git_describe.rindex("-dirty")]
+
+ # now we have TAG-NUM-gHEX or HEX
+
+ if "-" in git_describe:
+ # TAG-NUM-gHEX
+ mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
+ if not mo:
+ # unparsable. Maybe git-describe is misbehaving?
+ pieces["error"] = f"unable to parse git-describe output: '{describe_out}'"
+ return pieces
+
+ # tag
+ full_tag = mo.group(1)
+ if not full_tag.startswith(tag_prefix):
+ if verbose:
+ fmt = "tag '%s' doesn't start with prefix '%s'"
+ print(fmt % (full_tag, tag_prefix))
+ pieces["error"] = (
+ f"tag '{full_tag}' doesn't start with prefix '{tag_prefix}'"
+ )
+ return pieces
+ pieces["closest-tag"] = full_tag[len(tag_prefix) :]
+
+ # distance: number of commits since tag
+ pieces["distance"] = int(mo.group(2))
+
+ # commit: short hex revision ID
+ pieces["short"] = mo.group(3)
+
+ else:
+ # HEX: no tags
+ pieces["closest-tag"] = None
+ out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
+ pieces["distance"] = len(out.split()) # total number of commits
+
+ # commit date: see ISO-8601 comment in git_versions_from_keywords()
+ date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
+ # Use only the last line. Previous lines may contain GPG signature
+ # information.
+ date = date.splitlines()[-1]
+ pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+
+ return pieces
+
+
+def plus_or_dot(pieces) -> str:
+ """Return a + if we don't already have one, else return a ."""
+ if "+" in pieces.get("closest-tag", ""):
+ return "."
+ return "+"
+
+
+def render_pep440(pieces):
+ """Build up version string, with post-release "local version identifier".
+
+ Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+ get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
+
+ Exceptions:
+ 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += plus_or_dot(pieces)
+ rendered += f"{pieces['distance']}.g{pieces['short']}"
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ else:
+ # exception #1
+ rendered = f"0+untagged.{pieces['distance']}.g{pieces['short']}"
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ return rendered
+
+
+def render_pep440_branch(pieces):
+ """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
+
+ The ".dev0" means not master branch. Note that .dev0 sorts backwards
+ (a feature branch will appear "older" than the master branch).
+
+ Exceptions:
+ 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ if pieces["branch"] != "master":
+ rendered += ".dev0"
+ rendered += plus_or_dot(pieces)
+ rendered += f"{pieces['distance']}.g{pieces['short']}"
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ else:
+ # exception #1
+ rendered = "0"
+ if pieces["branch"] != "master":
+ rendered += ".dev0"
+ rendered += f"+untagged.{pieces['distance']}.g{pieces['short']}"
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ return rendered
+
+
+def pep440_split_post(ver):
+ """Split pep440 version string at the post-release segment.
+
+ Returns the release segments before the post-release and the
+ post-release version number (or -1 if no post-release segment is present).
+ """
+ vc = str.split(ver, ".post")
+ return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
+
+
+def render_pep440_pre(pieces):
+ """TAG[.postN.devDISTANCE] -- No -dirty.
+
+ Exceptions:
+ 1: no tags. 0.post0.devDISTANCE
+ """
+ if pieces["closest-tag"]:
+ if pieces["distance"]:
+ # update the post release segment
+ tag_version, post_version = pep440_split_post(pieces["closest-tag"])
+ rendered = tag_version
+ if post_version is not None:
+ rendered += f".post{post_version + 1}.dev{pieces['distance']}"
+ else:
+ rendered += f".post0.dev{pieces['distance']}"
+ else:
+ # no commits, use the tag as the version
+ rendered = pieces["closest-tag"]
+ else:
+ # exception #1
+ rendered = f"0.post0.dev{pieces['distance']}"
+ return rendered
+
+
+def render_pep440_post(pieces):
+ """TAG[.postDISTANCE[.dev0]+gHEX] .
+
+ The ".dev0" means dirty. Note that .dev0 sorts backwards
+ (a dirty tree will appear "older" than the corresponding clean one),
+ but you shouldn't be releasing software with -dirty anyways.
+
+ Exceptions:
+ 1: no tags. 0.postDISTANCE[.dev0]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += f".post{pieces['distance']}"
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ rendered += plus_or_dot(pieces)
+ rendered += f"g{pieces['short']}"
+ else:
+ # exception #1
+ rendered = f"0.post{pieces['distance']}"
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ rendered += f"+g{pieces['short']}"
+ return rendered
+
+
+def render_pep440_post_branch(pieces):
+ """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
+
+ The ".dev0" means not master branch.
+
+ Exceptions:
+ 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += f".post{pieces['distance']}"
+ if pieces["branch"] != "master":
+ rendered += ".dev0"
+ rendered += plus_or_dot(pieces)
+ rendered += f"g{pieces['short']}"
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ else:
+ # exception #1
+ rendered = f"0.post{pieces['distance']}"
+ if pieces["branch"] != "master":
+ rendered += ".dev0"
+ rendered += f"+g{pieces['short']}"
+ if pieces["dirty"]:
+ rendered += ".dirty"
+ return rendered
+
+
+def render_pep440_old(pieces):
+ """TAG[.postDISTANCE[.dev0]] .
+
+ The ".dev0" means dirty.
+
+ Exceptions:
+ 1: no tags. 0.postDISTANCE[.dev0]
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"] or pieces["dirty"]:
+ rendered += f"0.post{pieces['distance']}"
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ else:
+ # exception #1
+ rendered = f"0.post{pieces['distance']}"
+ if pieces["dirty"]:
+ rendered += ".dev0"
+ return rendered
+
+
+def render_git_describe(pieces):
+ """TAG[-DISTANCE-gHEX][-dirty].
+
+ Like 'git describe --tags --dirty --always'.
+
+ Exceptions:
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ if pieces["distance"]:
+ rendered += f"-{pieces['distance']}-g{pieces['short']}"
+ else:
+ # exception #1
+ rendered = pieces["short"]
+ if pieces["dirty"]:
+ rendered += "-dirty"
+ return rendered
+
+
+def render_git_describe_long(pieces):
+ """TAG-DISTANCE-gHEX[-dirty].
+
+ Like 'git describe --tags --dirty --always --long'.
+ The distance/hash is unconditional.
+
+ Exceptions:
+ 1: no tags. HEX[-dirty] (note: no 'g' prefix)
+ """
+ if pieces["closest-tag"]:
+ rendered = pieces["closest-tag"]
+ rendered += f"-{pieces['distance']}-g{pieces['short']}"
+ else:
+ # exception #1
+ rendered = pieces["short"]
+ if pieces["dirty"]:
+ rendered += "-dirty"
+ return rendered
+
+
+def render(pieces, style):
+ """Render the given version pieces into the requested style."""
+ if pieces["error"]:
+ return {
+ "version": "unknown",
+ "full-revisionid": pieces.get("long"),
+ "dirty": None,
+ "error": pieces["error"],
+ "date": None,
+ }
+
+ if not style or style == "default":
+ style = "pep440" # the default
+
+ if style == "pep440":
+ rendered = render_pep440(pieces)
+ elif style == "pep440-branch":
+ rendered = render_pep440_branch(pieces)
+ elif style == "pep440-pre":
+ rendered = render_pep440_pre(pieces)
+ elif style == "pep440-post":
+ rendered = render_pep440_post(pieces)
+ elif style == "pep440-post-branch":
+ rendered = render_pep440_post_branch(pieces)
+ elif style == "pep440-old":
+ rendered = render_pep440_old(pieces)
+ elif style == "git-describe":
+ rendered = render_git_describe(pieces)
+ elif style == "git-describe-long":
+ rendered = render_git_describe_long(pieces)
+ else:
+ raise ValueError(f"unknown style '{style}'")
+
+ return {
+ "version": rendered,
+ "full-revisionid": pieces["long"],
+ "dirty": pieces["dirty"],
+ "error": None,
+ "date": pieces.get("date"),
+ }
+
+
+def get_versions() -> dict:
+ """Get version information or return default if unable to do so."""
+ # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
+ # __file__, we can work backwards from there to the root. Some
+ # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
+ # case we can only use expanded keywords.
+
+ cfg = get_config()
+ verbose = cfg.verbose
+
+ try:
+ return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose)
+ except NotThisMethod:
+ pass
+
+ try:
+ root = os.path.realpath(__file__)
+ # versionfile_source is the relative path from the top of the source
+ # tree (where the .git directory might live) to this file. Invert
+ # this to find the root from __file__.
+ for _ in cfg.versionfile_source.split("/"):
+ root = os.path.dirname(root)
+ except NameError:
+ return {
+ "version": "0+unknown",
+ "full-revisionid": None,
+ "dirty": None,
+ "error": "unable to find root of source tree",
+ "date": None,
+ }
+
+ try:
+ pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
+ return render(pieces, cfg.style)
+ except NotThisMethod:
+ pass
+
+ try:
+ if cfg.parentdir_prefix:
+ return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
+ except NotThisMethod:
+ pass
+
+ return {
+ "version": "0+unknown",
+ "full-revisionid": None,
+ "dirty": None,
+ "error": "unable to compute version",
+ "date": None,
+ }
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/_version_meson.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/_version_meson.py
new file mode 100644
index 0000000000000000000000000000000000000000..949767fe5b3eed262b5081c214563bfb02768b0d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/_version_meson.py
@@ -0,0 +1,2 @@
+__version__="3.0.2"
+__git_version__="ab90747e3dae0e69b1bdbf083820b8075689b34b"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec8c613e741d8a17e57ecb0709fe785f629ce0f9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/conftest.py
@@ -0,0 +1,2159 @@
+"""
+This file is very long and growing, but it was decided to not split it yet, as
+it's still manageable (2020-03-17, ~1.1k LoC). See gh-31989
+
+Instead of splitting it was decided to define sections here:
+- Configuration / Settings
+- Autouse fixtures
+- Common arguments
+- Missing values & co.
+- Classes
+- Indices
+- Series'
+- DataFrames
+- Operators & Operations
+- Data sets/files
+- Time zones
+- Dtypes
+- Misc
+"""
+
+from __future__ import annotations
+
+from collections import abc
+from datetime import (
+ date,
+ datetime,
+ time,
+ timedelta,
+ timezone,
+)
+from decimal import Decimal
+import gc
+import operator
+import os
+from typing import (
+ TYPE_CHECKING,
+ Any,
+)
+import uuid
+
+from dateutil.tz import (
+ tzlocal,
+ tzutc,
+)
+import hypothesis
+from hypothesis import strategies as st
+import numpy as np
+import pytest
+
+from pandas.compat._optional import import_optional_dependency
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.dtypes import (
+ DatetimeTZDtype,
+ IntervalDtype,
+)
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ DataFrame,
+ Interval,
+ IntervalIndex,
+ Period,
+ RangeIndex,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core import ops
+from pandas.core.indexes.api import (
+ Index,
+ MultiIndex,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Callable,
+ Hashable,
+ Iterator,
+ )
+
+try:
+ import pyarrow as pa
+except ImportError:
+ has_pyarrow = False
+else:
+ del pa
+ has_pyarrow = True
+
+pytz = import_optional_dependency("pytz", errors="ignore")
+
+
+# ----------------------------------------------------------------
+# Configuration / Settings
+# ----------------------------------------------------------------
+# pytest
+
+
+def pytest_addoption(parser) -> None:
+ parser.addoption(
+ "--no-strict-data-files",
+ action="store_false",
+ help="Don't fail if a test is skipped for missing data file.",
+ )
+
+
+def pytest_sessionstart(session):
+ import doctest
+ import inspect
+
+ # https://github.com/pandas-dev/pandas/pull/62988
+ # When we modify the __module__ of a class, the __module__ on the methods
+ # of that class do not change. When these two disagree, doctests would not
+ # typically run. We hack `DocTestFinder` to avoid this.
+ orig = doctest.DocTestFinder._from_module # type: ignore[attr-defined]
+
+ def _from_module(self, module, object):
+ # When . is in __qualname__, object is a method of a class.
+ if inspect.isfunction(object) and "." in object.__qualname__:
+ # We only get here when the class that the method is on is from the
+ # appropriate module. So ignore checking the __module__ of the method
+ # itself and run the doctest.
+ return True
+ return orig(self, module, object)
+
+ doctest.DocTestFinder._from_module = _from_module # type: ignore[attr-defined]
+
+
+def ignore_doctest_warning(item: pytest.Item, path: str, message: str) -> None:
+ """Ignore doctest warning.
+
+ Parameters
+ ----------
+ item : pytest.Item
+ pytest test item.
+ path : str
+ Module path to Python object, e.g. "pandas.DataFrame.append". A
+ warning will be filtered when item.name ends with in given path. So it is
+ sufficient to specify e.g. "DataFrame.append".
+ message : str
+ Message to be filtered.
+ """
+ if item.name.endswith(path):
+ item.add_marker(pytest.mark.filterwarnings(f"ignore:{message}"))
+
+
+def pytest_collection_modifyitems(items, config) -> None:
+ is_doctest = config.getoption("--doctest-modules") or config.getoption(
+ "--doctest-cython", default=False
+ )
+
+ # Warnings from doctests that can be ignored; place reason in comment above.
+ # Each entry specifies (path, message) - see the ignore_doctest_warning function
+ ignored_doctest_warnings = [
+ ("api.interchange.from_dataframe", "The DataFrame Interchange Protocol"),
+ ("is_int64_dtype", "is_int64_dtype is deprecated"),
+ ("is_interval_dtype", "is_interval_dtype is deprecated"),
+ ("is_period_dtype", "is_period_dtype is deprecated"),
+ ("is_datetime64tz_dtype", "is_datetime64tz_dtype is deprecated"),
+ ("is_categorical_dtype", "is_categorical_dtype is deprecated"),
+ ("is_sparse", "is_sparse is deprecated"),
+ ("CategoricalDtype._from_values_or_dtype", "Constructing a Categorical"),
+ ("DataFrame.__dataframe__", "The DataFrame Interchange Protocol"),
+ ("DataFrameGroupBy.fillna", "DataFrameGroupBy.fillna is deprecated"),
+ ("DataFrameGroupBy.corrwith", "DataFrameGroupBy.corrwith is deprecated"),
+ ("NDFrame.replace", "Series.replace without 'value'"),
+ ("NDFrame.clip", "Downcasting behavior in Series and DataFrame methods"),
+ ("Series.idxmin", "The behavior of Series.idxmin"),
+ ("Series.idxmax", "The behavior of Series.idxmax"),
+ ("SeriesGroupBy.fillna", "SeriesGroupBy.fillna is deprecated"),
+ ("SeriesGroupBy.idxmin", "The behavior of Series.idxmin"),
+ ("SeriesGroupBy.idxmax", "The behavior of Series.idxmax"),
+ ("to_pytimedelta", "The behavior of TimedeltaProperties.to_pytimedelta"),
+ ("NDFrame.reindex_like", "keyword argument 'method' is deprecated"),
+ # Docstring divides by zero to show behavior difference
+ ("missing.mask_zero_div_zero", "divide by zero encountered"),
+ (
+ "pandas.core.generic.NDFrame.first",
+ "first is deprecated and will be removed in a future version. "
+ "Please create a mask and filter using `.loc` instead",
+ ),
+ (
+ "Resampler.fillna",
+ "DatetimeIndexResampler.fillna is deprecated",
+ ),
+ (
+ "DataFrameGroupBy.fillna",
+ "DataFrameGroupBy.fillna with 'method' is deprecated",
+ ),
+ ("read_parquet", "Passing a BlockManager to DataFrame is deprecated"),
+ ]
+
+ if is_doctest:
+ for item in items:
+ for path, message in ignored_doctest_warnings:
+ ignore_doctest_warning(item, path, message)
+
+
+# Similar to "ci" config in
+# https://hypothesis.readthedocs.io/en/latest/reference/api.html#built-in-profiles
+hypothesis.settings.register_profile(
+ "pandas_ci",
+ database=None,
+ deadline=None,
+ max_examples=15,
+ suppress_health_check=(
+ hypothesis.HealthCheck.too_slow,
+ hypothesis.HealthCheck.differing_executors,
+ ),
+)
+hypothesis.settings.load_profile("pandas_ci")
+
+# Registering these strategies makes them globally available via st.from_type,
+# which is use for offsets in tests/tseries/offsets/test_offsets_properties.py
+for name in "MonthBegin MonthEnd BMonthBegin BMonthEnd".split():
+ cls = getattr(pd.tseries.offsets, name)
+ st.register_type_strategy(
+ cls, st.builds(cls, n=st.integers(-99, 99), normalize=st.booleans())
+ )
+
+for name in "YearBegin YearEnd BYearBegin BYearEnd".split():
+ cls = getattr(pd.tseries.offsets, name)
+ st.register_type_strategy(
+ cls,
+ st.builds(
+ cls,
+ n=st.integers(-5, 5),
+ normalize=st.booleans(),
+ month=st.integers(min_value=1, max_value=12),
+ ),
+ )
+
+for name in "QuarterBegin QuarterEnd BQuarterBegin BQuarterEnd".split():
+ cls = getattr(pd.tseries.offsets, name)
+ st.register_type_strategy(
+ cls,
+ st.builds(
+ cls,
+ n=st.integers(-24, 24),
+ normalize=st.booleans(),
+ startingMonth=st.integers(min_value=1, max_value=12),
+ ),
+ )
+
+
+# ----------------------------------------------------------------
+# Autouse fixtures
+# ----------------------------------------------------------------
+
+
+# https://github.com/pytest-dev/pytest/issues/11873
+# Would like to avoid autouse=True, but cannot as of pytest 8.0.0
+@pytest.fixture(autouse=True)
+def add_doctest_imports(doctest_namespace) -> None:
+ """
+ Make `np` and `pd` names available for doctests.
+ """
+ doctest_namespace["np"] = np
+ doctest_namespace["pd"] = pd
+
+
+@pytest.fixture(autouse=True)
+def configure_tests() -> None:
+ """
+ Configure settings for all tests and test modules.
+ """
+ pd.set_option("chained_assignment", "raise")
+
+
+# ----------------------------------------------------------------
+# Common arguments
+# ----------------------------------------------------------------
+@pytest.fixture(params=[0, 1, "index", "columns"], ids=lambda x: f"axis={x!r}")
+def axis(request):
+ """
+ Fixture for returning the axis numbers of a DataFrame.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def observed(request):
+ """
+ Pass in the observed keyword to groupby for [True, False]
+ This indicates whether categoricals should return values for
+ values which are not in the grouper [False / None], or only values which
+ appear in the grouper [True]. [None] is supported for future compatibility
+ if we decide to change the default (and would need to warn if this
+ parameter is not passed).
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False, None])
+def ordered(request):
+ """
+ Boolean 'ordered' parameter for Categorical.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def dropna(request):
+ """
+ Boolean 'dropna' parameter.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def sort(request):
+ """
+ Boolean 'sort' parameter.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def skipna(request):
+ """
+ Boolean 'skipna' parameter.
+ """
+ return request.param
+
+
+@pytest.fixture(params=["first", "last", False])
+def keep(request):
+ """
+ Valid values for the 'keep' parameter used in
+ .duplicated or .drop_duplicates
+ """
+ return request.param
+
+
+@pytest.fixture(params=["both", "neither", "left", "right"])
+def inclusive_endpoints_fixture(request):
+ """
+ Fixture for trying all interval 'inclusive' parameters.
+ """
+ return request.param
+
+
+@pytest.fixture(params=["left", "right", "both", "neither"])
+def closed(request):
+ """
+ Fixture for trying all interval closed parameters.
+ """
+ return request.param
+
+
+@pytest.fixture(params=["left", "right", "both", "neither"])
+def other_closed(request):
+ """
+ Secondary closed fixture to allow parametrizing over all pairs of closed.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ None,
+ "gzip",
+ "bz2",
+ "zip",
+ "xz",
+ "tar",
+ pytest.param("zstd", marks=td.skip_if_no("zstandard")),
+ ]
+)
+def compression(request):
+ """
+ Fixture for trying common compression types in compression tests.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ "gzip",
+ "bz2",
+ "zip",
+ "xz",
+ "tar",
+ pytest.param("zstd", marks=td.skip_if_no("zstandard")),
+ ]
+)
+def compression_only(request):
+ """
+ Fixture for trying common compression types in compression tests excluding
+ uncompressed case.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def writable(request):
+ """
+ Fixture that an array is writable.
+ """
+ return request.param
+
+
+@pytest.fixture(params=["inner", "outer", "left", "right"])
+def join_type(request):
+ """
+ Fixture for trying all types of join operations.
+ """
+ return request.param
+
+
+@pytest.fixture(params=["nlargest", "nsmallest"])
+def nselect_method(request):
+ """
+ Fixture for trying all nselect methods.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[None, "ignore"])
+def na_action(request):
+ """
+ Fixture for 'na_action' argument in map.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def ascending(request):
+ """
+ Fixture for 'na_action' argument in sort_values/sort_index/rank.
+ """
+ return request.param
+
+
+@pytest.fixture(params=["average", "min", "max", "first", "dense"])
+def rank_method(request):
+ """
+ Fixture for 'rank' argument in rank.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def as_index(request):
+ """
+ Fixture for 'as_index' argument in groupby.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def cache(request):
+ """
+ Fixture for 'cache' argument in to_datetime.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def parallel(request):
+ """
+ Fixture for parallel keyword argument for numba.jit.
+ """
+ return request.param
+
+
+# Can parameterize nogil & nopython over True | False, but limiting per
+# https://github.com/pandas-dev/pandas/pull/41971#issuecomment-860607472
+
+
+@pytest.fixture(params=[False])
+def nogil(request):
+ """
+ Fixture for nogil keyword argument for numba.jit.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True])
+def nopython(request):
+ """
+ Fixture for nopython keyword argument for numba.jit.
+ """
+ return request.param
+
+
+# ----------------------------------------------------------------
+# Missing values & co.
+# ----------------------------------------------------------------
+@pytest.fixture(params=tm.NULL_OBJECTS, ids=lambda x: type(x).__name__)
+def nulls_fixture(request):
+ """
+ Fixture for each null type in pandas.
+ """
+ return request.param
+
+
+nulls_fixture2 = nulls_fixture # Generate cartesian product of nulls_fixture
+
+
+@pytest.fixture(params=[None, np.nan, pd.NaT])
+def unique_nulls_fixture(request):
+ """
+ Fixture for each null type in pandas, each null type exactly once.
+ """
+ return request.param
+
+
+# Generate cartesian product of unique_nulls_fixture:
+unique_nulls_fixture2 = unique_nulls_fixture
+
+
+@pytest.fixture(params=tm.NP_NAT_OBJECTS, ids=lambda x: type(x).__name__)
+def np_nat_fixture(request):
+ """
+ Fixture for each NaT type in numpy.
+ """
+ return request.param
+
+
+# Generate cartesian product of np_nat_fixture:
+np_nat_fixture2 = np_nat_fixture
+
+
+# ----------------------------------------------------------------
+# Classes
+# ----------------------------------------------------------------
+
+
+@pytest.fixture(params=[DataFrame, Series])
+def frame_or_series(request):
+ """
+ Fixture to parametrize over DataFrame and Series.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[Index, Series], ids=["index", "series"])
+def index_or_series(request):
+ """
+ Fixture to parametrize over Index and Series, made necessary by a mypy
+ bug, giving an error:
+
+ List item 0 has incompatible type "Type[Series]"; expected "Type[PandasObject]"
+
+ See GH#29725
+ """
+ return request.param
+
+
+@pytest.fixture(params=[Index, Series, pd.array], ids=["index", "series", "array"])
+def index_or_series_or_array(request):
+ """
+ Fixture to parametrize over Index, Series, and ExtensionArray
+ """
+ return request.param
+
+
+@pytest.fixture(params=[Index, Series, DataFrame, pd.array], ids=lambda x: x.__name__)
+def box_with_array(request):
+ """
+ Fixture to test behavior for Index, Series, DataFrame, and pandas Array
+ classes
+ """
+ return request.param
+
+
+box_with_array2 = box_with_array
+
+
+@pytest.fixture
+def dict_subclass() -> type[dict]:
+ """
+ Fixture for a dictionary subclass.
+ """
+
+ class TestSubDict(dict):
+ def __init__(self, *args, **kwargs) -> None:
+ dict.__init__(self, *args, **kwargs)
+
+ return TestSubDict
+
+
+@pytest.fixture
+def non_dict_mapping_subclass() -> type[abc.Mapping]:
+ """
+ Fixture for a non-mapping dictionary subclass.
+ """
+
+ class TestNonDictMapping(abc.Mapping):
+ def __init__(self, underlying_dict) -> None:
+ self._data = underlying_dict
+
+ def __getitem__(self, key):
+ return self._data.__getitem__(key)
+
+ def __iter__(self) -> Iterator:
+ return self._data.__iter__()
+
+ def __len__(self) -> int:
+ return self._data.__len__()
+
+ return TestNonDictMapping
+
+
+# ----------------------------------------------------------------
+# Indices
+# ----------------------------------------------------------------
+@pytest.fixture
+def multiindex_year_month_day_dataframe_random_data():
+ """
+ DataFrame with 3 level MultiIndex (year, month, day) covering
+ first 100 business days from 2000-01-01 with random data
+ """
+ tdf = DataFrame(
+ np.random.default_rng(2).standard_normal((100, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=100, freq="B"),
+ )
+ ymd = tdf.groupby([lambda x: x.year, lambda x: x.month, lambda x: x.day]).sum()
+ # use int64 Index, to make sure things work
+ ymd.index = ymd.index.set_levels([lev.astype("i8") for lev in ymd.index.levels])
+ ymd.index.set_names(["year", "month", "day"], inplace=True)
+ return ymd
+
+
+@pytest.fixture
+def lexsorted_two_level_string_multiindex() -> MultiIndex:
+ """
+ 2-level MultiIndex, lexsorted, with string names.
+ """
+ return MultiIndex(
+ levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
+ codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
+ names=["first", "second"],
+ )
+
+
+@pytest.fixture
+def multiindex_dataframe_random_data(
+ lexsorted_two_level_string_multiindex,
+) -> DataFrame:
+ """DataFrame with 2 level MultiIndex with random data"""
+ index = lexsorted_two_level_string_multiindex
+ return DataFrame(
+ np.random.default_rng(2).standard_normal((10, 3)),
+ index=index,
+ columns=Index(["A", "B", "C"], name="exp"),
+ )
+
+
+def _create_multiindex():
+ """
+ MultiIndex used to test the general functionality of this object
+ """
+
+ # See Also: tests.multi.conftest.idx
+ major_axis = Index(["foo", "bar", "baz", "qux"])
+ minor_axis = Index(["one", "two"])
+
+ major_codes = np.array([0, 0, 1, 2, 3, 3])
+ minor_codes = np.array([0, 1, 0, 1, 0, 1])
+ index_names = ["first", "second"]
+ return MultiIndex(
+ levels=[major_axis, minor_axis],
+ codes=[major_codes, minor_codes],
+ names=index_names,
+ verify_integrity=False,
+ )
+
+
+def _create_mi_with_dt64tz_level():
+ """
+ MultiIndex with a level that is a tzaware DatetimeIndex.
+ """
+ # GH#8367 round trip with pickle
+ return MultiIndex.from_product(
+ [[1, 2], ["a", "b"], date_range("20130101", periods=3, tz="US/Eastern")],
+ names=["one", "two", "three"],
+ )
+
+
+indices_dict = {
+ "object": Index([f"pandas_{i}" for i in range(10)], dtype=object),
+ "string": Index([f"pandas_{i}" for i in range(10)], dtype="str"),
+ "datetime": date_range("2020-01-01", periods=10),
+ "datetime-tz": date_range("2020-01-01", periods=10, tz="US/Pacific"),
+ "period": period_range("2020-01-01", periods=10, freq="D"),
+ "timedelta": timedelta_range(start="1 day", periods=10, freq="D"),
+ "range": RangeIndex(10),
+ "int8": Index(np.arange(10), dtype="int8"),
+ "int16": Index(np.arange(10), dtype="int16"),
+ "int32": Index(np.arange(10), dtype="int32"),
+ "int64": Index(np.arange(10), dtype="int64"),
+ "uint8": Index(np.arange(10), dtype="uint8"),
+ "uint16": Index(np.arange(10), dtype="uint16"),
+ "uint32": Index(np.arange(10), dtype="uint32"),
+ "uint64": Index(np.arange(10), dtype="uint64"),
+ "float32": Index(np.arange(10), dtype="float32"),
+ "float64": Index(np.arange(10), dtype="float64"),
+ "bool-object": Index([True, False] * 5, dtype=object),
+ "bool-dtype": Index([True, False] * 5, dtype=bool),
+ "complex64": Index(
+ np.arange(10, dtype="complex64") + 1.0j * np.arange(10, dtype="complex64")
+ ),
+ "complex128": Index(
+ np.arange(10, dtype="complex128") + 1.0j * np.arange(10, dtype="complex128")
+ ),
+ "categorical": CategoricalIndex(list("abcd") * 2),
+ "interval": IntervalIndex.from_breaks(np.linspace(0, 100, num=11)),
+ "empty": Index([]),
+ "tuples": MultiIndex.from_tuples(
+ zip(["foo", "bar", "baz"], [1, 2, 3], strict=True)
+ ),
+ "mi-with-dt64tz-level": _create_mi_with_dt64tz_level(),
+ "multi": _create_multiindex(),
+ "repeats": Index([0, 0, 1, 1, 2, 2]),
+ "nullable_int": Index(np.arange(10), dtype="Int64"),
+ "nullable_uint": Index(np.arange(10), dtype="UInt16"),
+ "nullable_float": Index(np.arange(10), dtype="Float32"),
+ "nullable_bool": Index(np.arange(10).astype(bool), dtype="boolean"),
+ "string-python": Index(
+ pd.array([f"pandas_{i}" for i in range(10)], dtype="string[python]")
+ ),
+}
+if has_pyarrow:
+ idx = Index(pd.array([f"pandas_{i}" for i in range(10)], dtype="string[pyarrow]"))
+ indices_dict["string-pyarrow"] = idx
+
+
+@pytest.fixture(params=indices_dict.keys())
+def index(request):
+ """
+ Fixture for many "simple" kinds of indices.
+
+ These indices are unlikely to cover corner cases, e.g.
+ - no names
+ - no NaTs/NaNs
+ - no values near implementation bounds
+ - ...
+ """
+ # copy to avoid mutation, e.g. setting .name
+ return indices_dict[request.param].copy(deep=False)
+
+
+@pytest.fixture(
+ params=[
+ key for key, value in indices_dict.items() if not isinstance(value, MultiIndex)
+ ]
+)
+def index_flat(request):
+ """
+ index fixture, but excluding MultiIndex cases.
+ """
+ key = request.param
+ return indices_dict[key].copy(deep=False)
+
+
+@pytest.fixture(
+ params=[
+ key
+ for key, value in indices_dict.items()
+ if not (
+ key.startswith(("int", "uint", "float"))
+ or key in ["range", "empty", "repeats", "bool-dtype"]
+ )
+ and not isinstance(value, MultiIndex)
+ ]
+)
+def index_with_missing(request):
+ """
+ Fixture for indices with missing values.
+
+ Integer-dtype and empty cases are excluded because they cannot hold missing
+ values.
+
+ MultiIndex is excluded because isna() is not defined for MultiIndex.
+ """
+ ind = indices_dict[request.param]
+ if request.param in ["tuples", "mi-with-dt64tz-level", "multi"]:
+ # For setting missing values in the top level of MultiIndex
+ vals = ind.tolist()
+ vals[0] = (None, *vals[0][1:])
+ vals[-1] = (None, *vals[-1][1:])
+ return MultiIndex.from_tuples(vals)
+ else:
+ vals = ind.values.copy()
+ vals[0] = None
+ vals[-1] = None
+ return type(ind)(vals, copy=False)
+
+
+# ----------------------------------------------------------------
+# Series'
+# ----------------------------------------------------------------
+@pytest.fixture
+def string_series() -> Series:
+ """
+ Fixture for Series of floats with Index of unique strings
+ """
+ return Series(
+ np.arange(30, dtype=np.float64) * 1.1,
+ index=Index([f"i_{i}" for i in range(30)]),
+ name="series",
+ )
+
+
+@pytest.fixture
+def object_series() -> Series:
+ """
+ Fixture for Series of dtype object with Index of unique strings
+ """
+ data = [f"foo_{i}" for i in range(30)]
+ index = Index([f"bar_{i}" for i in range(30)])
+ return Series(data, index=index, name="objects", dtype=object)
+
+
+@pytest.fixture
+def datetime_series() -> Series:
+ """
+ Fixture for Series of floats with DatetimeIndex
+ """
+ return Series(
+ np.random.default_rng(2).standard_normal(30),
+ index=date_range("2000-01-01", periods=30, freq="B"),
+ name="ts",
+ )
+
+
+def _create_series(index):
+ """Helper for the _series dict"""
+ size = len(index)
+ data = np.random.default_rng(2).standard_normal(size)
+ return Series(data, index=index, name="a", copy=False)
+
+
+_series = {
+ f"series-with-{index_id}-index": _create_series(index)
+ for index_id, index in indices_dict.items()
+}
+
+
+@pytest.fixture
+def series_with_simple_index(index) -> Series:
+ """
+ Fixture for tests on series with changing types of indices.
+ """
+ return _create_series(index)
+
+
+_narrow_series = {
+ f"{dtype.__name__}-series": Series(
+ range(30), index=[f"i-{i}" for i in range(30)], name="a", dtype=dtype
+ )
+ for dtype in tm.NARROW_NP_DTYPES
+}
+
+
+_index_or_series_objs = {**indices_dict, **_series, **_narrow_series}
+
+
+@pytest.fixture(params=_index_or_series_objs.keys())
+def index_or_series_obj(request):
+ """
+ Fixture for tests on indexes, series and series with a narrow dtype
+ copy to avoid mutation, e.g. setting .name
+ """
+ return _index_or_series_objs[request.param].copy(deep=False)
+
+
+_typ_objects_series = {
+ f"{dtype.__name__}-series": Series(dtype) for dtype in tm.PYTHON_DATA_TYPES
+}
+
+
+_index_or_series_memory_objs = {
+ **indices_dict,
+ **_series,
+ **_narrow_series,
+ **_typ_objects_series,
+}
+
+
+@pytest.fixture(params=_index_or_series_memory_objs.keys())
+def index_or_series_memory_obj(request):
+ """
+ Fixture for tests on indexes, series, series with a narrow dtype and
+ series with empty objects type
+ copy to avoid mutation, e.g. setting .name
+ """
+ return _index_or_series_memory_objs[request.param].copy(deep=False)
+
+
+# ----------------------------------------------------------------
+# DataFrames
+# ----------------------------------------------------------------
+@pytest.fixture
+def int_frame() -> DataFrame:
+ """
+ Fixture for DataFrame of ints with index of unique strings
+
+ Columns are ['A', 'B', 'C', 'D']
+ """
+ return DataFrame(
+ np.ones((30, 4), dtype=np.int64),
+ index=Index([f"foo_{i}" for i in range(30)]),
+ columns=Index(list("ABCD")),
+ )
+
+
+@pytest.fixture
+def float_frame() -> DataFrame:
+ """
+ Fixture for DataFrame of floats with index of unique strings
+
+ Columns are ['A', 'B', 'C', 'D'].
+ """
+ return DataFrame(
+ np.random.default_rng(2).standard_normal((30, 4)),
+ index=Index([f"foo_{i}" for i in range(30)]),
+ columns=Index(list("ABCD")),
+ )
+
+
+@pytest.fixture
+def rand_series_with_duplicate_datetimeindex() -> Series:
+ """
+ Fixture for Series with a DatetimeIndex that has duplicates.
+ """
+ dates = [
+ datetime(2000, 1, 2),
+ datetime(2000, 1, 2),
+ datetime(2000, 1, 2),
+ datetime(2000, 1, 3),
+ datetime(2000, 1, 3),
+ datetime(2000, 1, 3),
+ datetime(2000, 1, 4),
+ datetime(2000, 1, 4),
+ datetime(2000, 1, 4),
+ datetime(2000, 1, 5),
+ ]
+
+ return Series(np.random.default_rng(2).standard_normal(len(dates)), index=dates)
+
+
+# ----------------------------------------------------------------
+# Scalars
+# ----------------------------------------------------------------
+@pytest.fixture(
+ params=[
+ (Interval(left=0, right=5), IntervalDtype("int64", "right")),
+ (Interval(left=0.1, right=0.5), IntervalDtype("float64", "right")),
+ (Period("2012-01", freq="M"), "period[M]"),
+ (Period("2012-02-01", freq="D"), "period[D]"),
+ (
+ Timestamp("2011-01-01", tz="US/Eastern").as_unit("s"),
+ DatetimeTZDtype(unit="s", tz="US/Eastern"),
+ ),
+ (Timedelta(seconds=500), "timedelta64[us]"),
+ ]
+)
+def ea_scalar_and_dtype(request):
+ """
+ Fixture that tests each scalar and datetime type.
+ """
+ return request.param
+
+
+# ----------------------------------------------------------------
+# Operators & Operations
+# ----------------------------------------------------------------
+
+
+@pytest.fixture(params=tm.arithmetic_dunder_methods)
+def all_arithmetic_operators(request):
+ """
+ Fixture for dunder names for common arithmetic operations.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ operator.add,
+ ops.radd,
+ operator.sub,
+ ops.rsub,
+ operator.mul,
+ ops.rmul,
+ operator.truediv,
+ ops.rtruediv,
+ operator.floordiv,
+ ops.rfloordiv,
+ operator.mod,
+ ops.rmod,
+ operator.pow,
+ ops.rpow,
+ operator.eq,
+ operator.ne,
+ operator.lt,
+ operator.le,
+ operator.gt,
+ operator.ge,
+ operator.and_,
+ ops.rand_,
+ operator.xor,
+ ops.rxor,
+ operator.or_,
+ ops.ror_,
+ ]
+)
+def all_binary_operators(request):
+ """
+ Fixture for operator and roperator arithmetic, comparison, and logical ops.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ operator.add,
+ ops.radd,
+ operator.sub,
+ ops.rsub,
+ operator.mul,
+ ops.rmul,
+ operator.truediv,
+ ops.rtruediv,
+ operator.floordiv,
+ ops.rfloordiv,
+ operator.mod,
+ ops.rmod,
+ operator.pow,
+ ops.rpow,
+ ]
+)
+def all_arithmetic_functions(request):
+ """
+ Fixture for operator and roperator arithmetic functions.
+
+ Notes
+ -----
+ This includes divmod and rdivmod, whereas all_arithmetic_operators
+ does not.
+ """
+ return request.param
+
+
+_all_numeric_reductions = [
+ "count",
+ "sum",
+ "max",
+ "min",
+ "mean",
+ "prod",
+ "std",
+ "var",
+ "median",
+ "kurt",
+ "skew",
+ "sem",
+]
+
+
+@pytest.fixture(params=_all_numeric_reductions)
+def all_numeric_reductions(request):
+ """
+ Fixture for numeric reduction names.
+ """
+ return request.param
+
+
+_all_boolean_reductions = ["all", "any"]
+
+
+@pytest.fixture(params=_all_boolean_reductions)
+def all_boolean_reductions(request):
+ """
+ Fixture for boolean reduction names.
+ """
+ return request.param
+
+
+_all_reductions = _all_numeric_reductions + _all_boolean_reductions
+
+
+@pytest.fixture(params=_all_reductions)
+def all_reductions(request):
+ """
+ Fixture for all (boolean + numeric) reduction names.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ operator.eq,
+ operator.ne,
+ operator.gt,
+ operator.ge,
+ operator.lt,
+ operator.le,
+ ]
+)
+def comparison_op(request):
+ """
+ Fixture for operator module comparison functions.
+ """
+ return request.param
+
+
+@pytest.fixture(params=["__le__", "__lt__", "__ge__", "__gt__"])
+def compare_operators_no_eq_ne(request):
+ """
+ Fixture for dunder names for compare operations except == and !=
+
+ * >=
+ * >
+ * <
+ * <=
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=["__and__", "__rand__", "__or__", "__ror__", "__xor__", "__rxor__"]
+)
+def all_logical_operators(request):
+ """
+ Fixture for dunder names for common logical operations
+
+ * |
+ * &
+ * ^
+ """
+ return request.param
+
+
+_all_numeric_accumulations = ["cumsum", "cumprod", "cummin", "cummax"]
+
+
+@pytest.fixture(params=_all_numeric_accumulations)
+def all_numeric_accumulations(request):
+ """
+ Fixture for numeric accumulation names
+ """
+ return request.param
+
+
+# ----------------------------------------------------------------
+# Data sets/files
+# ----------------------------------------------------------------
+@pytest.fixture
+def strict_data_files(pytestconfig):
+ """
+ Returns the configuration for the test setting `--no-strict-data-files`.
+ """
+ return pytestconfig.getoption("--no-strict-data-files")
+
+
+@pytest.fixture
+def datapath(strict_data_files: str) -> Callable[..., str]:
+ """
+ Get the path to a data file.
+
+ Parameters
+ ----------
+ path : str
+ Path to the file, relative to ``pandas/tests/``
+
+ Returns
+ -------
+ path including ``pandas/tests``.
+
+ Raises
+ ------
+ ValueError
+ If the path doesn't exist and the --no-strict-data-files option is not set.
+ """
+ BASE_PATH = os.path.join(os.path.dirname(__file__), "tests")
+
+ def deco(*args):
+ path = os.path.join(BASE_PATH, *args)
+ if not os.path.exists(path):
+ if strict_data_files:
+ raise ValueError(
+ f"Could not find file {path} and --no-strict-data-files is not set."
+ )
+ pytest.skip(f"Could not find {path}.")
+ return path
+
+ return deco
+
+
+# ----------------------------------------------------------------
+# Time zones
+# ----------------------------------------------------------------
+TIMEZONES = [
+ None,
+ "UTC",
+ "US/Eastern",
+ "Asia/Tokyo",
+ "dateutil/US/Pacific",
+ "dateutil/Asia/Singapore",
+ "+01:15",
+ "-02:15",
+ "UTC+01:15",
+ "UTC-02:15",
+ tzutc(),
+ tzlocal(),
+ timezone.utc,
+ timezone(timedelta(hours=1)),
+ timezone(timedelta(hours=-1), name="foo"),
+]
+if pytz is not None:
+ TIMEZONES.extend(
+ (
+ pytz.FixedOffset(300),
+ pytz.FixedOffset(0),
+ pytz.FixedOffset(-300),
+ pytz.timezone("US/Pacific"),
+ pytz.timezone("UTC"),
+ )
+ )
+TIMEZONE_IDS = [repr(i) for i in TIMEZONES]
+
+
+@td.parametrize_fixture_doc(str(TIMEZONE_IDS))
+@pytest.fixture(params=TIMEZONES, ids=TIMEZONE_IDS)
+def tz_naive_fixture(request):
+ """
+ Fixture for trying timezones including default (None): {0}
+ """
+ return request.param
+
+
+@td.parametrize_fixture_doc(str(TIMEZONE_IDS[1:]))
+@pytest.fixture(params=TIMEZONES[1:], ids=TIMEZONE_IDS[1:])
+def tz_aware_fixture(request):
+ """
+ Fixture for trying explicit timezones: {0}
+ """
+ return request.param
+
+
+_UTCS = ["utc", "dateutil/UTC", tzutc(), timezone.utc]
+
+if pytz is not None:
+ _UTCS.append(pytz.utc)
+
+
+@pytest.fixture(params=_UTCS)
+def utc_fixture(request):
+ """
+ Fixture to provide variants of UTC timezone strings and tzinfo objects.
+ """
+ return request.param
+
+
+utc_fixture2 = utc_fixture
+
+
+@pytest.fixture(params=["s", "ms", "us", "ns"])
+def unit(request):
+ """
+ datetime64 units we support.
+ """
+ return request.param
+
+
+unit2 = unit
+
+
+# ----------------------------------------------------------------
+# Dtypes
+# ----------------------------------------------------------------
+@pytest.fixture(params=tm.STRING_DTYPES)
+def string_dtype(request):
+ """
+ Parametrized fixture for string dtypes.
+
+ * str
+ * 'str'
+ * 'U'
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ ("python", pd.NA),
+ pytest.param(("pyarrow", pd.NA), marks=td.skip_if_no("pyarrow")),
+ pytest.param(("pyarrow", np.nan), marks=td.skip_if_no("pyarrow")),
+ ("python", np.nan),
+ ],
+ ids=[
+ "string=string[python]",
+ "string=string[pyarrow]",
+ "string=str[pyarrow]",
+ "string=str[python]",
+ ],
+)
+def string_dtype_no_object(request):
+ """
+ Parametrized fixture for string dtypes.
+ * 'string[python]' (NA variant)
+ * 'string[pyarrow]' (NA variant)
+ * 'str' (NaN variant, with pyarrow)
+ * 'str' (NaN variant, without pyarrow)
+ """
+ # need to instantiate the StringDtype here instead of in the params
+ # to avoid importing pyarrow during test collection
+ storage, na_value = request.param
+ return pd.StringDtype(storage, na_value)
+
+
+@pytest.fixture(
+ params=[
+ "string[python]",
+ pytest.param("string[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ ]
+)
+def nullable_string_dtype(request):
+ """
+ Parametrized fixture for string dtypes.
+
+ * 'string[python]'
+ * 'string[pyarrow]'
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ pytest.param(("pyarrow", np.nan), marks=td.skip_if_no("pyarrow")),
+ pytest.param(("pyarrow", pd.NA), marks=td.skip_if_no("pyarrow")),
+ ]
+)
+def pyarrow_string_dtype(request):
+ """
+ Parametrized fixture for string dtypes backed by Pyarrow.
+
+ * 'str[pyarrow]'
+ * 'string[pyarrow]'
+ """
+ return pd.StringDtype(*request.param)
+
+
+@pytest.fixture(
+ params=[
+ "python",
+ pytest.param("pyarrow", marks=td.skip_if_no("pyarrow")),
+ ]
+)
+def string_storage(request):
+ """
+ Parametrized fixture for pd.options.mode.string_storage.
+
+ * 'python'
+ * 'pyarrow'
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ ("python", pd.NA),
+ pytest.param(("pyarrow", pd.NA), marks=td.skip_if_no("pyarrow")),
+ pytest.param(("pyarrow", np.nan), marks=td.skip_if_no("pyarrow")),
+ ("python", np.nan),
+ ],
+ ids=[
+ "string=string[python]",
+ "string=string[pyarrow]",
+ "string=str[pyarrow]",
+ "string=str[python]",
+ ],
+)
+def string_dtype_arguments(request):
+ """
+ Parametrized fixture for StringDtype storage and na_value.
+
+ * 'python' + pd.NA
+ * 'pyarrow' + pd.NA
+ * 'pyarrow' + np.nan
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ "numpy_nullable",
+ pytest.param("pyarrow", marks=td.skip_if_no("pyarrow")),
+ ]
+)
+def dtype_backend(request):
+ """
+ Parametrized fixture for pd.options.mode.string_storage.
+
+ * 'python'
+ * 'pyarrow'
+ """
+ return request.param
+
+
+# Alias so we can test with cartesian product of string_storage
+string_storage2 = string_storage
+string_dtype_arguments2 = string_dtype_arguments
+
+
+@pytest.fixture(params=tm.BYTES_DTYPES)
+def bytes_dtype(request):
+ """
+ Parametrized fixture for bytes dtypes.
+
+ * bytes
+ * 'bytes'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.OBJECT_DTYPES)
+def object_dtype(request):
+ """
+ Parametrized fixture for object dtypes.
+
+ * object
+ * 'object'
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ np.dtype("object"),
+ ("python", pd.NA),
+ pytest.param(("pyarrow", pd.NA), marks=td.skip_if_no("pyarrow")),
+ pytest.param(("pyarrow", np.nan), marks=td.skip_if_no("pyarrow")),
+ ("python", np.nan),
+ ],
+ ids=[
+ "string=object",
+ "string=string[python]",
+ "string=string[pyarrow]",
+ "string=str[pyarrow]",
+ "string=str[python]",
+ ],
+)
+def any_string_dtype(request):
+ """
+ Parametrized fixture for string dtypes.
+ * 'object'
+ * 'string[python]' (NA variant)
+ * 'string[pyarrow]' (NA variant)
+ * 'str' (NaN variant, with pyarrow)
+ * 'str' (NaN variant, without pyarrow)
+ """
+ if isinstance(request.param, np.dtype):
+ return request.param
+ else:
+ # need to instantiate the StringDtype here instead of in the params
+ # to avoid importing pyarrow during test collection
+ storage, na_value = request.param
+ return pd.StringDtype(storage, na_value)
+
+
+any_string_dtype2 = any_string_dtype
+
+
+@pytest.fixture(params=tm.DATETIME64_DTYPES)
+def datetime64_dtype(request):
+ """
+ Parametrized fixture for datetime64 dtypes.
+
+ * 'datetime64[ns]'
+ * 'M8[ns]'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.TIMEDELTA64_DTYPES)
+def timedelta64_dtype(request):
+ """
+ Parametrized fixture for timedelta64 dtypes.
+
+ * 'timedelta64[ns]'
+ * 'm8[ns]'
+ """
+ return request.param
+
+
+@pytest.fixture
+def fixed_now_ts() -> Timestamp:
+ """
+ Fixture emits fixed Timestamp.now()
+ """
+ return Timestamp( # pyright: ignore[reportReturnType]
+ year=2021, month=1, day=1, hour=12, minute=4, second=13, microsecond=22
+ )
+
+
+@pytest.fixture(params=tm.FLOAT_NUMPY_DTYPES)
+def float_numpy_dtype(request):
+ """
+ Parameterized fixture for float dtypes.
+
+ * float
+ * 'float32'
+ * 'float64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.FLOAT_EA_DTYPES)
+def float_ea_dtype(request):
+ """
+ Parameterized fixture for float dtypes.
+
+ * 'Float32'
+ * 'Float64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_FLOAT_DTYPES)
+def any_float_dtype(request):
+ """
+ Parameterized fixture for float dtypes.
+
+ * float
+ * 'float32'
+ * 'float64'
+ * 'Float32'
+ * 'Float64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.COMPLEX_DTYPES)
+def complex_dtype(request):
+ """
+ Parameterized fixture for complex dtypes.
+
+ * complex
+ * 'complex64'
+ * 'complex128'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.COMPLEX_FLOAT_DTYPES)
+def complex_or_float_dtype(request):
+ """
+ Parameterized fixture for complex and numpy float dtypes.
+
+ * complex
+ * 'complex64'
+ * 'complex128'
+ * float
+ * 'float32'
+ * 'float64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.SIGNED_INT_NUMPY_DTYPES)
+def any_signed_int_numpy_dtype(request):
+ """
+ Parameterized fixture for signed integer dtypes.
+
+ * int
+ * 'int8'
+ * 'int16'
+ * 'int32'
+ * 'int64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.UNSIGNED_INT_NUMPY_DTYPES)
+def any_unsigned_int_numpy_dtype(request):
+ """
+ Parameterized fixture for unsigned integer dtypes.
+
+ * 'uint8'
+ * 'uint16'
+ * 'uint32'
+ * 'uint64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_INT_NUMPY_DTYPES)
+def any_int_numpy_dtype(request):
+ """
+ Parameterized fixture for any integer dtype.
+
+ * int
+ * 'int8'
+ * 'uint8'
+ * 'int16'
+ * 'uint16'
+ * 'int32'
+ * 'uint32'
+ * 'int64'
+ * 'uint64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_INT_EA_DTYPES)
+def any_int_ea_dtype(request):
+ """
+ Parameterized fixture for any nullable integer dtype.
+
+ * 'UInt8'
+ * 'Int8'
+ * 'UInt16'
+ * 'Int16'
+ * 'UInt32'
+ * 'Int32'
+ * 'UInt64'
+ * 'Int64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_INT_DTYPES)
+def any_int_dtype(request):
+ """
+ Parameterized fixture for any nullable integer dtype.
+
+ * int
+ * 'int8'
+ * 'uint8'
+ * 'int16'
+ * 'uint16'
+ * 'int32'
+ * 'uint32'
+ * 'int64'
+ * 'uint64'
+ * 'UInt8'
+ * 'Int8'
+ * 'UInt16'
+ * 'Int16'
+ * 'UInt32'
+ * 'Int32'
+ * 'UInt64'
+ * 'Int64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_INT_EA_DTYPES + tm.FLOAT_EA_DTYPES)
+def any_numeric_ea_dtype(request):
+ """
+ Parameterized fixture for any nullable integer dtype and
+ any float ea dtypes.
+
+ * 'UInt8'
+ * 'Int8'
+ * 'UInt16'
+ * 'Int16'
+ * 'UInt32'
+ * 'Int32'
+ * 'UInt64'
+ * 'Int64'
+ * 'Float32'
+ * 'Float64'
+ """
+ return request.param
+
+
+# Unsupported operand types for + ("List[Union[str, ExtensionDtype, dtype[Any],
+# Type[object]]]" and "List[str]")
+@pytest.fixture(
+ params=tm.ALL_INT_EA_DTYPES
+ + tm.FLOAT_EA_DTYPES
+ + tm.ALL_INT_PYARROW_DTYPES_STR_REPR
+ + tm.FLOAT_PYARROW_DTYPES_STR_REPR # type: ignore[operator]
+)
+def any_numeric_ea_and_arrow_dtype(request):
+ """
+ Parameterized fixture for any nullable integer dtype and
+ any float ea dtypes.
+
+ * 'UInt8'
+ * 'Int8'
+ * 'UInt16'
+ * 'Int16'
+ * 'UInt32'
+ * 'Int32'
+ * 'UInt64'
+ * 'Int64'
+ * 'Float32'
+ * 'Float64'
+ * 'uint8[pyarrow]'
+ * 'int8[pyarrow]'
+ * 'uint16[pyarrow]'
+ * 'int16[pyarrow]'
+ * 'uint32[pyarrow]'
+ * 'int32[pyarrow]'
+ * 'uint64[pyarrow]'
+ * 'int64[pyarrow]'
+ * 'float32[pyarrow]'
+ * 'float64[pyarrow]'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.SIGNED_INT_EA_DTYPES)
+def any_signed_int_ea_dtype(request):
+ """
+ Parameterized fixture for any signed nullable integer dtype.
+
+ * 'Int8'
+ * 'Int16'
+ * 'Int32'
+ * 'Int64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_REAL_NUMPY_DTYPES)
+def any_real_numpy_dtype(request):
+ """
+ Parameterized fixture for any (purely) real numeric dtype.
+
+ * int
+ * 'int8'
+ * 'uint8'
+ * 'int16'
+ * 'uint16'
+ * 'int32'
+ * 'uint32'
+ * 'int64'
+ * 'uint64'
+ * float
+ * 'float32'
+ * 'float64'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_REAL_DTYPES)
+def any_real_numeric_dtype(request):
+ """
+ Parameterized fixture for any (purely) real numeric dtype.
+
+ * int
+ * 'int8'
+ * 'uint8'
+ * 'int16'
+ * 'uint16'
+ * 'int32'
+ * 'uint32'
+ * 'int64'
+ * 'uint64'
+ * float
+ * 'float32'
+ * 'float64'
+
+ and associated ea dtypes.
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_NUMPY_DTYPES)
+def any_numpy_dtype(request):
+ """
+ Parameterized fixture for all numpy dtypes.
+
+ * bool
+ * 'bool'
+ * int
+ * 'int8'
+ * 'uint8'
+ * 'int16'
+ * 'uint16'
+ * 'int32'
+ * 'uint32'
+ * 'int64'
+ * 'uint64'
+ * float
+ * 'float32'
+ * 'float64'
+ * complex
+ * 'complex64'
+ * 'complex128'
+ * str
+ * 'str'
+ * 'U'
+ * bytes
+ * 'bytes'
+ * 'datetime64[ns]'
+ * 'M8[ns]'
+ * 'timedelta64[ns]'
+ * 'm8[ns]'
+ * object
+ * 'object'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_REAL_NULLABLE_DTYPES)
+def any_real_nullable_dtype(request):
+ """
+ Parameterized fixture for all real dtypes that can hold NA.
+
+ * float
+ * 'float32'
+ * 'float64'
+ * 'Float32'
+ * 'Float64'
+ * 'UInt8'
+ * 'UInt16'
+ * 'UInt32'
+ * 'UInt64'
+ * 'Int8'
+ * 'Int16'
+ * 'Int32'
+ * 'Int64'
+ * 'uint8[pyarrow]'
+ * 'uint16[pyarrow]'
+ * 'uint32[pyarrow]'
+ * 'uint64[pyarrow]'
+ * 'int8[pyarrow]'
+ * 'int16[pyarrow]'
+ * 'int32[pyarrow]'
+ * 'int64[pyarrow]'
+ * 'float[pyarrow]'
+ * 'double[pyarrow]'
+ """
+ return request.param
+
+
+@pytest.fixture(params=tm.ALL_NUMERIC_DTYPES)
+def any_numeric_dtype(request):
+ """
+ Parameterized fixture for all numeric dtypes.
+
+ * int
+ * 'int8'
+ * 'uint8'
+ * 'int16'
+ * 'uint16'
+ * 'int32'
+ * 'uint32'
+ * 'int64'
+ * 'uint64'
+ * float
+ * 'float32'
+ * 'float64'
+ * complex
+ * 'complex64'
+ * 'complex128'
+ * 'UInt8'
+ * 'Int8'
+ * 'UInt16'
+ * 'Int16'
+ * 'UInt32'
+ * 'Int32'
+ * 'UInt64'
+ * 'Int64'
+ * 'Float32'
+ * 'Float64'
+ """
+ return request.param
+
+
+# categoricals are handled separately
+_any_skipna_inferred_dtype = [
+ ("string", ["a", np.nan, "c"]),
+ ("string", ["a", pd.NA, "c"]),
+ ("mixed", ["a", pd.NaT, "c"]), # pd.NaT not considered valid by is_string_array
+ ("bytes", [b"a", np.nan, b"c"]),
+ ("empty", [np.nan, np.nan, np.nan]),
+ ("empty", []),
+ ("mixed-integer", ["a", np.nan, 2]),
+ ("mixed", ["a", np.nan, 2.0]),
+ ("floating", [1.0, np.nan, 2.0]),
+ ("integer", [1, np.nan, 2]),
+ ("mixed-integer-float", [1, np.nan, 2.0]),
+ ("decimal", [Decimal(1), np.nan, Decimal(2)]),
+ ("boolean", [True, np.nan, False]),
+ ("boolean", [True, pd.NA, False]),
+ ("datetime64", [np.datetime64("2013-01-01"), np.nan, np.datetime64("2018-01-01")]),
+ ("datetime", [Timestamp("20130101"), np.nan, Timestamp("20180101")]),
+ ("date", [date(2013, 1, 1), np.nan, date(2018, 1, 1)]),
+ ("complex", [1 + 1j, np.nan, 2 + 2j]),
+ # The following dtype is commented out due to GH 23554
+ # ('timedelta64', [np.timedelta64(1, 'D'),
+ # np.nan, np.timedelta64(2, 'D')]),
+ ("timedelta", [timedelta(1), np.nan, timedelta(2)]),
+ ("time", [time(1), np.nan, time(2)]),
+ ("period", [Period(2013), pd.NaT, Period(2018)]),
+ ("interval", [Interval(0, 1), np.nan, Interval(0, 2)]),
+]
+ids = [
+ pair[0] for pair in _any_skipna_inferred_dtype
+] # use inferred type as fixture-id
+
+
+@pytest.fixture(params=_any_skipna_inferred_dtype, ids=ids)
+def any_skipna_inferred_dtype(request):
+ """
+ Fixture for all inferred dtypes from _libs.lib.infer_dtype
+
+ The covered (inferred) types are:
+ * 'string'
+ * 'empty'
+ * 'bytes'
+ * 'mixed'
+ * 'mixed-integer'
+ * 'mixed-integer-float'
+ * 'floating'
+ * 'integer'
+ * 'decimal'
+ * 'boolean'
+ * 'datetime64'
+ * 'datetime'
+ * 'date'
+ * 'timedelta'
+ * 'time'
+ * 'period'
+ * 'interval'
+
+ Returns
+ -------
+ inferred_dtype : str
+ The string for the inferred dtype from _libs.lib.infer_dtype
+ values : np.ndarray
+ An array of object dtype that will be inferred to have
+ `inferred_dtype`
+
+ Examples
+ --------
+ >>> from pandas._libs import lib
+ >>>
+ >>> def test_something(any_skipna_inferred_dtype):
+ ... inferred_dtype, values = any_skipna_inferred_dtype
+ ... # will pass
+ ... assert lib.infer_dtype(values, skipna=True) == inferred_dtype
+ """
+ inferred_dtype, values = request.param
+ values = np.array(values, dtype=object) # object dtype to avoid casting
+
+ # correctness of inference tested in tests/dtypes/test_inference.py
+ return inferred_dtype, values
+
+
+# ----------------------------------------------------------------
+# Misc
+# ----------------------------------------------------------------
+@pytest.fixture
+def ip():
+ """
+ Get an instance of IPython.InteractiveShell.
+
+ Will raise a skip if IPython is not installed.
+ """
+ pytest.importorskip("IPython", minversion="6.0.0")
+ from IPython.core.interactiveshell import InteractiveShell
+
+ # GH#35711 make sure sqlite history file handle is not leaked
+ from traitlets.config import Config # isort:skip
+
+ c = Config()
+ c.HistoryManager.hist_file = ":memory:"
+
+ return InteractiveShell(config=c)
+
+
+@pytest.fixture
+def mpl_cleanup():
+ """
+ Ensure Matplotlib is cleaned up around a test.
+
+ Before a test is run:
+
+ 1) Set the backend to "template" to avoid requiring a GUI.
+
+ After a test is run:
+
+ 1) Reset units registry
+ 2) Reset rc_context
+ 3) Close all figures
+
+ See matplotlib/testing/decorators.py#L24.
+ """
+ mpl = pytest.importorskip("matplotlib")
+ mpl_units = pytest.importorskip("matplotlib.units")
+ plt = pytest.importorskip("matplotlib.pyplot")
+ orig_units_registry = mpl_units.registry.copy()
+ try:
+ with mpl.rc_context():
+ mpl.use("template")
+ yield
+ finally:
+ mpl_units.registry.clear()
+ mpl_units.registry.update(orig_units_registry)
+ plt.close("all")
+ # https://matplotlib.org/stable/users/prev_whats_new/whats_new_3.6.0.html#garbage-collection-is-no-longer-run-on-figure-close # noqa: E501
+ gc.collect(1)
+
+
+@pytest.fixture(
+ params=[
+ getattr(pd.offsets, o)
+ for o in pd.offsets.__all__
+ if issubclass(getattr(pd.offsets, o), pd.offsets.Tick) and o != "Tick"
+ ]
+)
+def tick_classes(request):
+ """
+ Fixture for Tick based datetime offsets available for a time series.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[None, lambda x: x])
+def sort_by_key(request):
+ """
+ Simple fixture for testing keys in sorting methods.
+ Tests None (no key) and the identity key.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ ("foo", None, None),
+ ("Egon", "Venkman", None),
+ ("NCC1701D", "NCC1701D", "NCC1701D"),
+ # possibly-matching NAs
+ (np.nan, np.nan, np.nan),
+ (np.nan, pd.NaT, None),
+ (np.nan, pd.NA, None),
+ (pd.NA, pd.NA, pd.NA),
+ ]
+)
+def names(request) -> tuple[Hashable, Hashable, Hashable]:
+ """
+ A 3-tuple of names, the first two for operands, the last for a result.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[tm.setitem, tm.loc, tm.iloc])
+def indexer_sli(request):
+ """
+ Parametrize over __setitem__, loc.__setitem__, iloc.__setitem__
+ """
+ return request.param
+
+
+@pytest.fixture(params=[tm.loc, tm.iloc])
+def indexer_li(request):
+ """
+ Parametrize over loc.__getitem__, iloc.__getitem__
+ """
+ return request.param
+
+
+@pytest.fixture(params=[tm.setitem, tm.iloc])
+def indexer_si(request):
+ """
+ Parametrize over __setitem__, iloc.__setitem__
+ """
+ return request.param
+
+
+@pytest.fixture(params=[tm.setitem, tm.loc])
+def indexer_sl(request):
+ """
+ Parametrize over __setitem__, loc.__setitem__
+ """
+ return request.param
+
+
+@pytest.fixture(params=[tm.at, tm.loc])
+def indexer_al(request):
+ """
+ Parametrize over at.__setitem__, loc.__setitem__
+ """
+ return request.param
+
+
+@pytest.fixture(params=[tm.iat, tm.iloc])
+def indexer_ial(request):
+ """
+ Parametrize over iat.__setitem__, iloc.__setitem__
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def performance_warning(request) -> Iterator[bool | type[Warning]]:
+ """
+ Fixture to check if performance warnings are enabled. Either produces
+ ``PerformanceWarning`` if they are enabled, otherwise ``False``.
+ """
+ with pd.option_context("mode.performance_warnings", request.param):
+ yield pd.errors.PerformanceWarning if request.param else False
+
+
+@pytest.fixture
+def using_infer_string() -> bool:
+ """
+ Fixture to check if infer string option is enabled.
+ """
+ return pd.options.future.infer_string is True
+
+
+@pytest.fixture
+def using_python_scalars() -> bool:
+ return pd.options.future.python_scalars is True
+
+
+_warsaws: list[Any] = ["Europe/Warsaw", "dateutil/Europe/Warsaw"]
+if pytz is not None:
+ _warsaws.append(pytz.timezone("Europe/Warsaw"))
+
+
+@pytest.fixture(params=_warsaws)
+def warsaw(request) -> str:
+ """
+ tzinfo for Europe/Warsaw using pytz, dateutil, or zoneinfo.
+ """
+ return request.param
+
+
+@pytest.fixture
+def temp_file(tmp_path):
+ """
+ Generate a unique file for testing use. See link for removal policy.
+ https://docs.pytest.org/en/7.1.x/how-to/tmp_path.html#the-default-base-temporary-directory
+ """
+ file_path = tmp_path / str(uuid.uuid4())
+ file_path.touch()
+ return file_path
+
+
+@pytest.fixture(scope="session")
+def monkeysession():
+ with pytest.MonkeyPatch.context() as mp:
+ yield mp
+
+
+@pytest.fixture(params=[True, False])
+def using_nan_is_na(request):
+ opt = request.param
+ with pd.option_context("future.distinguish_nan_and_na", not opt):
+ yield opt
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/apply.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/apply.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f218b3813149a2c6584e9919d11802dd27b7ce4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/apply.py
@@ -0,0 +1,2132 @@
+from __future__ import annotations
+
+import abc
+from collections import defaultdict
+from collections.abc import Callable
+import functools
+from functools import partial
+import inspect
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ TypeAlias,
+ cast,
+)
+
+import numpy as np
+
+from pandas._libs.internals import BlockValuesRefs
+from pandas._typing import (
+ AggFuncType,
+ AggFuncTypeBase,
+ AggFuncTypeDict,
+ AggObjType,
+ Axis,
+ AxisInt,
+ NDFrameT,
+ npt,
+)
+from pandas.compat._optional import import_optional_dependency
+from pandas.errors import SpecificationError
+from pandas.util._decorators import (
+ cache_readonly,
+ set_module,
+)
+
+from pandas.core.dtypes.cast import is_nested_object
+from pandas.core.dtypes.common import (
+ is_dict_like,
+ is_extension_array_dtype,
+ is_list_like,
+ is_numeric_dtype,
+ is_sequence,
+)
+from pandas.core.dtypes.dtypes import ExtensionDtype
+from pandas.core.dtypes.generic import (
+ ABCDataFrame,
+ ABCNDFrame,
+ ABCSeries,
+)
+
+from pandas.core._numba.executor import generate_apply_looper
+import pandas.core.common as com
+from pandas.core.construction import ensure_wrapped_if_datetimelike
+from pandas.core.util.numba_ import (
+ get_jit_arguments,
+ prepare_function_arguments,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Generator,
+ Hashable,
+ Iterable,
+ MutableMapping,
+ Sequence,
+ )
+
+ from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ )
+ from pandas.core.groupby import GroupBy
+ from pandas.core.resample import Resampler
+ from pandas.core.window.rolling import BaseWindow
+
+ResType: TypeAlias = dict[int, Any]
+
+
+@set_module("pandas.api.executors")
+class BaseExecutionEngine(abc.ABC):
+ """
+ Base class for execution engines for map and apply methods.
+
+ An execution engine receives all the parameters of a call to
+ ``apply`` or ``map``, such as the data container, the function,
+ etc. and takes care of running the execution.
+
+ Supporting different engines allows functions to be JIT compiled,
+ run in parallel, and others. Besides the default executor which
+ simply runs the code with the Python interpreter and pandas.
+ """
+
+ @staticmethod
+ @abc.abstractmethod
+ def map(
+ data: Series | DataFrame | np.ndarray,
+ func: AggFuncType,
+ args: tuple,
+ kwargs: dict[str, Any],
+ decorator: Callable | None,
+ skip_na: bool,
+ ):
+ """
+ Executor method to run functions elementwise.
+
+ In general, pandas uses ``map`` for running functions elementwise,
+ but ``Series.apply`` with the default ``by_row='compat'`` will also
+ call this executor function.
+
+ Parameters
+ ----------
+ data : Series, DataFrame or NumPy ndarray
+ The object to use for the data. Some methods implement a ``raw``
+ parameter which will convert the original pandas object to a
+ NumPy array, which will then be passed here to the executor.
+ func : function or NumPy ufunc
+ The function to execute.
+ args : tuple
+ Positional arguments to be passed to ``func``.
+ kwargs : dict
+ Keyword arguments to be passed to ``func``.
+ decorator : function, optional
+ For JIT compilers and other engines that need to decorate the
+ function ``func``, this is the decorator to use. While the
+ executor may already know which is the decorator to use, this
+ is useful as for a single executor the user can specify for
+ example ``numba.jit`` or ``numba.njit(nogil=True)``, and this
+ decorator parameter will contain the exact decorator from the
+ executor the user wants to use.
+ skip_na : bool
+ Whether the function should be called for missing values or not.
+ This is specified by the pandas user as ``map(na_action=None)``
+ or ``map(na_action='ignore')``.
+ """
+
+ @staticmethod
+ @abc.abstractmethod
+ def apply(
+ data: Series | DataFrame | np.ndarray,
+ func: AggFuncType,
+ args: tuple,
+ kwargs: dict[str, Any],
+ decorator: Callable,
+ axis: Axis,
+ ):
+ """
+ Executor method to run functions by an axis.
+
+ While we can see ``map`` as executing the function for each cell
+ in a ``DataFrame`` (or ``Series``), ``apply`` will execute the
+ function for each column (or row).
+
+ Parameters
+ ----------
+ data : Series, DataFrame or NumPy ndarray
+ The object to use for the data. Some methods implement a ``raw``
+ parameter which will convert the original pandas object to a
+ NumPy array, which will then be passed here to the executor.
+ func : function or NumPy ufunc
+ The function to execute.
+ args : tuple
+ Positional arguments to be passed to ``func``.
+ kwargs : dict
+ Keyword arguments to be passed to ``func``.
+ decorator : function, optional
+ For JIT compilers and other engines that need to decorate the
+ function ``func``, this is the decorator to use. While the
+ executor may already know which is the decorator to use, this
+ is useful as for a single executor the user can specify for
+ example ``numba.jit`` or ``numba.njit(nogil=True)``, and this
+ decorator parameter will contain the exact decorator from the
+ executor the user wants to use.
+ axis : {0 or 'index', 1 or 'columns'}
+ 0 or 'index' should execute the function passing each column as
+ parameter. 1 or 'columns' should execute the function passing
+ each row as parameter. The default executor engine passes rows
+ as pandas ``Series``. Other executor engines should probably
+ expect functions to be implemented this way for compatibility.
+ But passing rows as other data structures is technically possible
+ as far as the function ``func`` is implemented accordingly.
+ """
+
+
+def frame_apply(
+ obj: DataFrame,
+ func: AggFuncType,
+ axis: Axis = 0,
+ raw: bool = False,
+ result_type: str | None = None,
+ by_row: Literal[False, "compat"] = "compat",
+ engine: str = "python",
+ engine_kwargs: dict[str, bool] | None = None,
+ args=None,
+ kwargs=None,
+) -> FrameApply:
+ """construct and return a row or column based frame apply object"""
+ _, func, columns, _ = reconstruct_func(func, **kwargs)
+
+ axis = obj._get_axis_number(axis)
+ klass: type[FrameApply]
+ if axis == 0:
+ klass = FrameRowApply
+ elif axis == 1:
+ if columns:
+ raise NotImplementedError(
+ f"Named aggregation is not supported when {axis=}."
+ )
+ klass = FrameColumnApply
+
+ return klass(
+ obj,
+ func,
+ raw=raw,
+ result_type=result_type,
+ by_row=by_row,
+ engine=engine,
+ engine_kwargs=engine_kwargs,
+ args=args,
+ kwargs=kwargs,
+ )
+
+
+class Apply(metaclass=abc.ABCMeta):
+ axis: AxisInt
+
+ def __init__(
+ self,
+ obj: AggObjType,
+ func: AggFuncType,
+ raw: bool,
+ result_type: str | None,
+ *,
+ by_row: Literal[False, "compat", "_compat"] = "compat",
+ engine: str = "python",
+ engine_kwargs: dict[str, bool] | None = None,
+ args,
+ kwargs,
+ ) -> None:
+ self.obj = obj
+ self.raw = raw
+
+ assert by_row is False or by_row in ["compat", "_compat"]
+ self.by_row = by_row
+
+ self.args = args or ()
+ self.kwargs = kwargs or {}
+
+ self.engine = engine
+ self.engine_kwargs = {} if engine_kwargs is None else engine_kwargs
+
+ if result_type not in [None, "reduce", "broadcast", "expand"]:
+ raise ValueError(
+ "invalid value for result_type, must be one "
+ "of {None, 'reduce', 'broadcast', 'expand'}"
+ )
+
+ self.result_type = result_type
+
+ self.func = func
+
+ @abc.abstractmethod
+ def apply(self) -> DataFrame | Series:
+ pass
+
+ @abc.abstractmethod
+ def agg_or_apply_list_like(
+ self, op_name: Literal["agg", "apply"]
+ ) -> DataFrame | Series:
+ pass
+
+ @abc.abstractmethod
+ def agg_or_apply_dict_like(
+ self, op_name: Literal["agg", "apply"]
+ ) -> DataFrame | Series:
+ pass
+
+ def agg(self) -> DataFrame | Series | None:
+ """
+ Provide an implementation for the aggregators.
+
+ Returns
+ -------
+ Result of aggregation, or None if agg cannot be performed by
+ this method.
+ """
+ func = self.func
+
+ if isinstance(func, str):
+ return self.apply_str()
+
+ if is_dict_like(func):
+ return self.agg_dict_like()
+ elif is_list_like(func):
+ # we require a list, but not a 'str'
+ return self.agg_list_like()
+
+ # caller can react
+ return None
+
+ def transform(self) -> DataFrame | Series:
+ """
+ Transform a DataFrame or Series.
+
+ Returns
+ -------
+ DataFrame or Series
+ Result of applying ``func`` along the given axis of the
+ Series or DataFrame.
+
+ Raises
+ ------
+ ValueError
+ If the transform function fails or does not transform.
+ """
+ obj = self.obj
+ func = self.func
+ axis = self.axis
+ args = self.args
+ kwargs = self.kwargs
+
+ is_series = obj.ndim == 1
+
+ if obj._get_axis_number(axis) == 1:
+ assert not is_series
+ return obj.T.transform(func, 0, *args, **kwargs).T
+
+ if is_list_like(func) and not is_dict_like(func):
+ func = cast(list[AggFuncTypeBase], func)
+ # Convert func equivalent dict
+ if is_series:
+ func = {com.get_callable_name(v) or v: v for v in func}
+ else:
+ func = dict.fromkeys(obj, func)
+
+ if is_dict_like(func):
+ func = cast(AggFuncTypeDict, func)
+ return self.transform_dict_like(func)
+
+ # func is either str or callable
+ func = cast(AggFuncTypeBase, func)
+ try:
+ result = self.transform_str_or_callable(func)
+ except TypeError:
+ raise
+ except Exception as err:
+ raise ValueError("Transform function failed") from err
+
+ # Functions that transform may return empty Series/DataFrame
+ # when the dtype is not appropriate
+ if (
+ isinstance(result, (ABCSeries, ABCDataFrame))
+ and result.empty
+ and not obj.empty
+ ):
+ raise ValueError("Transform function failed")
+ if not isinstance(result, (ABCSeries, ABCDataFrame)) or not result.index.equals(
+ obj.index
+ ):
+ raise ValueError("Function did not transform")
+
+ return result
+
+ def transform_dict_like(self, func) -> DataFrame:
+ """
+ Compute transform in the case of a dict-like func
+ """
+ from pandas.core.reshape.concat import concat
+
+ obj = self.obj
+ args = self.args
+ kwargs = self.kwargs
+
+ # transform is currently only for Series/DataFrame
+ assert isinstance(obj, ABCNDFrame)
+
+ if len(func) == 0:
+ raise ValueError("No transform functions were provided")
+
+ func = self.normalize_dictlike_arg("transform", obj, func)
+
+ results: dict[Hashable, DataFrame | Series] = {}
+ for name, how in func.items():
+ colg = obj._gotitem(name, ndim=1)
+ results[name] = colg.transform(how, 0, *args, **kwargs)
+ return concat(results, axis=1)
+
+ def transform_str_or_callable(self, func) -> DataFrame | Series:
+ """
+ Compute transform in the case of a string or callable func
+ """
+ obj = self.obj
+ args = self.args
+ kwargs = self.kwargs
+
+ if isinstance(func, str):
+ return self._apply_str(obj, func, *args, **kwargs)
+
+ # Two possible ways to use a UDF - apply or call directly
+ try:
+ return obj.apply(func, args=args, **kwargs)
+ except Exception:
+ return func(obj, *args, **kwargs)
+
+ def agg_list_like(self) -> DataFrame | Series:
+ """
+ Compute aggregation in the case of a list-like argument.
+
+ Returns
+ -------
+ Result of aggregation.
+ """
+ return self.agg_or_apply_list_like(op_name="agg")
+
+ def compute_list_like(
+ self,
+ op_name: Literal["agg", "apply"],
+ selected_obj: Series | DataFrame,
+ kwargs: dict[str, Any],
+ ) -> tuple[list[Hashable] | Index, list[Any]]:
+ """
+ Compute agg/apply results for like-like input.
+
+ Parameters
+ ----------
+ op_name : {"agg", "apply"}
+ Operation being performed.
+ selected_obj : Series or DataFrame
+ Data to perform operation on.
+ kwargs : dict
+ Keyword arguments to pass to the functions.
+
+ Returns
+ -------
+ keys : list[Hashable] or Index
+ Index labels for result.
+ results : list
+ Data for result. When aggregating with a Series, this can contain any
+ Python objects.
+ """
+ func = cast(list[AggFuncTypeBase], self.func)
+ obj = self.obj
+
+ results = []
+ keys = []
+
+ # degenerate case
+ if selected_obj.ndim == 1:
+ for a in func:
+ colg = obj._gotitem(selected_obj.name, ndim=1, subset=selected_obj)
+ args = (
+ [self.axis, *self.args]
+ if include_axis(op_name, colg)
+ else self.args
+ )
+ new_res = getattr(colg, op_name)(a, *args, **kwargs)
+ results.append(new_res)
+
+ # make sure we find a good name
+ name = com.get_callable_name(a) or a
+ keys.append(name)
+
+ else:
+ indices = []
+ for index, col in enumerate(selected_obj):
+ colg = obj._gotitem(col, ndim=1, subset=selected_obj.iloc[:, index])
+ args = (
+ [self.axis, *self.args]
+ if include_axis(op_name, colg)
+ else self.args
+ )
+ new_res = getattr(colg, op_name)(func, *args, **kwargs)
+ results.append(new_res)
+ indices.append(index)
+ # error: Incompatible types in assignment (expression has type "Any |
+ # Index", variable has type "list[Any | Callable[..., Any] | str]")
+ keys = selected_obj.columns.take(indices) # type: ignore[assignment]
+
+ return keys, results
+
+ def wrap_results_list_like(
+ self, keys: Iterable[Hashable], results: list[Series | DataFrame]
+ ):
+ from pandas.core.reshape.concat import concat
+
+ obj = self.obj
+
+ try:
+ return concat(results, keys=keys, axis=1, sort=False)
+ except TypeError as err:
+ # we are concatting non-NDFrame objects,
+ # e.g. a list of scalars
+ from pandas import Series
+
+ result = Series(results, index=keys, name=obj.name)
+ if is_nested_object(result):
+ raise ValueError(
+ "cannot combine transform and aggregation operations"
+ ) from err
+ return result
+
+ def agg_dict_like(self) -> DataFrame | Series:
+ """
+ Compute aggregation in the case of a dict-like argument.
+
+ Returns
+ -------
+ Result of aggregation.
+ """
+ return self.agg_or_apply_dict_like(op_name="agg")
+
+ def compute_dict_like(
+ self,
+ op_name: Literal["agg", "apply"],
+ selected_obj: Series | DataFrame,
+ selection: Hashable | Sequence[Hashable],
+ kwargs: dict[str, Any],
+ ) -> tuple[list[Hashable], list[Any]]:
+ """
+ Compute agg/apply results for dict-like input.
+
+ Parameters
+ ----------
+ op_name : {"agg", "apply"}
+ Operation being performed.
+ selected_obj : Series or DataFrame
+ Data to perform operation on.
+ selection : hashable or sequence of hashables
+ Used by GroupBy, Window, and Resample if selection is applied to the object.
+ kwargs : dict
+ Keyword arguments to pass to the functions.
+
+ Returns
+ -------
+ keys : list[hashable]
+ Index labels for result.
+ results : list
+ Data for result. When aggregating with a Series, this can contain any
+ Python object.
+ """
+ from pandas.core.groupby.generic import (
+ DataFrameGroupBy,
+ SeriesGroupBy,
+ )
+
+ obj = self.obj
+ is_groupby = isinstance(obj, (DataFrameGroupBy, SeriesGroupBy))
+ func = cast(AggFuncTypeDict, self.func)
+ func = self.normalize_dictlike_arg(op_name, selected_obj, func)
+
+ is_non_unique_col = (
+ selected_obj.ndim == 2
+ and selected_obj.columns.nunique() < len(selected_obj.columns)
+ )
+
+ if selected_obj.ndim == 1:
+ # key only used for output
+ colg = obj._gotitem(selection, ndim=1)
+ results = [getattr(colg, op_name)(how, **kwargs) for _, how in func.items()]
+ keys = list(func.keys())
+ elif not is_groupby and is_non_unique_col:
+ # key used for column selection and output
+ # GH#51099
+ results = []
+ keys = []
+ for key, how in func.items():
+ indices = selected_obj.columns.get_indexer_for([key])
+ labels = selected_obj.columns.take(indices)
+ label_to_indices = defaultdict(list)
+ for index, label in zip(indices, labels, strict=True):
+ label_to_indices[label].append(index)
+
+ key_data = [
+ getattr(selected_obj._ixs(indice, axis=1), op_name)(how, **kwargs)
+ for label, indices in label_to_indices.items()
+ for indice in indices
+ ]
+
+ keys += [key] * len(key_data)
+ results += key_data
+ elif is_groupby:
+ # key used for column selection and output
+
+ df = selected_obj
+ results, keys = [], []
+ for key, how in func.items():
+ cols = df[key]
+
+ if cols.ndim == 1:
+ series = obj._gotitem(key, ndim=1, subset=cols)
+ results.append(getattr(series, op_name)(how, **kwargs))
+ keys.append(key)
+ else:
+ for _, col in cols.items():
+ series = obj._gotitem(key, ndim=1, subset=col)
+ results.append(getattr(series, op_name)(how, **kwargs))
+ keys.append(key)
+ else:
+ results = [
+ getattr(obj._gotitem(key, ndim=1), op_name)(how, **kwargs)
+ for key, how in func.items()
+ ]
+ keys = list(func.keys())
+
+ return keys, results
+
+ def wrap_results_dict_like(
+ self,
+ selected_obj: Series | DataFrame,
+ result_index: list[Hashable],
+ result_data: list,
+ ):
+ from pandas import Index
+ from pandas.core.reshape.concat import concat
+
+ obj = self.obj
+
+ # Avoid making two isinstance calls in all and any below
+ is_ndframe = [isinstance(r, ABCNDFrame) for r in result_data]
+
+ if all(is_ndframe):
+ results = [result for result in result_data if not result.empty]
+ keys_to_use: Iterable[Hashable]
+ keys_to_use = [
+ k for k, v in zip(result_index, result_data, strict=True) if not v.empty
+ ]
+ # Have to check, if at least one DataFrame is not empty.
+ if keys_to_use == []:
+ keys_to_use = result_index
+ results = result_data
+
+ if selected_obj.ndim == 2:
+ # keys are columns, so we can preserve names
+ ktu = Index(keys_to_use)
+ ktu._set_names(selected_obj.columns.names)
+ keys_to_use = ktu
+
+ axis: AxisInt = 0 if isinstance(obj, ABCSeries) else 1
+ result = concat(
+ results,
+ axis=axis,
+ keys=keys_to_use,
+ sort=False,
+ )
+ elif any(is_ndframe):
+ # There is a mix of NDFrames and scalars
+ raise ValueError(
+ "cannot perform both aggregation "
+ "and transformation operations "
+ "simultaneously"
+ )
+ else:
+ from pandas import Series
+
+ # we have a list of scalars
+ # GH 36212 use name only if obj is a series
+ if obj.ndim == 1:
+ obj = cast("Series", obj)
+ name = obj.name
+ else:
+ name = None
+
+ result = Series(result_data, index=result_index, name=name)
+
+ return result
+
+ def apply_str(self) -> DataFrame | Series:
+ """
+ Compute apply in case of a string.
+
+ Returns
+ -------
+ result: Series or DataFrame
+ """
+ # Caller is responsible for checking isinstance(self.f, str)
+ func = cast(str, self.func)
+
+ obj = self.obj
+
+ from pandas.core.groupby.generic import (
+ DataFrameGroupBy,
+ SeriesGroupBy,
+ )
+
+ # Support for `frame.transform('method')`
+ # Some methods (shift, etc.) require the axis argument, others
+ # don't, so inspect and insert if necessary.
+ method = getattr(obj, func, None)
+ if callable(method):
+ sig = inspect.getfullargspec(method)
+ arg_names = (*sig.args, *sig.kwonlyargs)
+ if self.axis != 0 and (
+ "axis" not in arg_names or func in ("corrwith", "skew")
+ ):
+ raise ValueError(f"Operation {func} does not support axis=1")
+ if "axis" in arg_names and not isinstance(
+ obj, (SeriesGroupBy, DataFrameGroupBy)
+ ):
+ self.kwargs["axis"] = self.axis
+ return self._apply_str(obj, func, *self.args, **self.kwargs)
+
+ def apply_list_or_dict_like(self) -> DataFrame | Series:
+ """
+ Compute apply in case of a list-like or dict-like.
+
+ Returns
+ -------
+ result: Series, DataFrame, or None
+ Result when self.func is a list-like or dict-like, None otherwise.
+ """
+
+ if self.engine == "numba":
+ raise NotImplementedError(
+ "The 'numba' engine doesn't support list-like/"
+ "dict likes of callables yet."
+ )
+
+ if self.axis == 1 and isinstance(self.obj, ABCDataFrame):
+ return self.obj.T.apply(self.func, 0, args=self.args, **self.kwargs).T
+
+ func = self.func
+ kwargs = self.kwargs
+
+ if is_dict_like(func):
+ result = self.agg_or_apply_dict_like(op_name="apply")
+ else:
+ result = self.agg_or_apply_list_like(op_name="apply")
+
+ result = reconstruct_and_relabel_result(result, func, **kwargs)
+
+ return result
+
+ def normalize_dictlike_arg(
+ self, how: str, obj: DataFrame | Series, func: AggFuncTypeDict
+ ) -> AggFuncTypeDict:
+ """
+ Handler for dict-like argument.
+
+ Ensures that necessary columns exist if obj is a DataFrame, and
+ that a nested renamer is not passed. Also normalizes to all lists
+ when values consists of a mix of list and non-lists.
+ """
+ assert how in ("apply", "agg", "transform")
+
+ # Can't use func.values(); wouldn't work for a Series
+ if (
+ how == "agg"
+ and isinstance(obj, ABCSeries)
+ and any(is_list_like(v) for _, v in func.items())
+ ) or (any(is_dict_like(v) for _, v in func.items())):
+ # GH 15931 - deprecation of renaming keys
+ raise SpecificationError("nested renamer is not supported")
+
+ if obj.ndim != 1:
+ # Check for missing columns on a frame
+ from pandas import Index
+
+ cols = Index(list(func.keys())).difference(obj.columns, sort=True)
+ if len(cols) > 0:
+ # GH 58474
+ raise KeyError(f"Label(s) {list(cols)} do not exist")
+
+ aggregator_types = (list, tuple, dict)
+
+ # if we have a dict of any non-scalars
+ # eg. {'A' : ['mean']}, normalize all to
+ # be list-likes
+ # Cannot use func.values() because arg may be a Series
+ if any(isinstance(x, aggregator_types) for _, x in func.items()):
+ new_func: AggFuncTypeDict = {}
+ for k, v in func.items():
+ if not isinstance(v, aggregator_types):
+ new_func[k] = [v]
+ else:
+ new_func[k] = v
+ func = new_func
+ return func
+
+ def _apply_str(self, obj, func: str, *args, **kwargs):
+ """
+ if arg is a string, then try to operate on it:
+ - try to find a function (or attribute) on obj
+ - try to find a numpy function
+ - raise
+ """
+ assert isinstance(func, str)
+
+ if hasattr(obj, func):
+ f = getattr(obj, func)
+ if callable(f):
+ return f(*args, **kwargs)
+
+ # people may aggregate on a non-callable attribute
+ # but don't let them think they can pass args to it
+ assert len(args) == 0
+ assert not any(kwarg == "axis" for kwarg in kwargs)
+ return f
+ elif hasattr(np, func) and hasattr(obj, "__array__"):
+ # in particular exclude Window
+ f = getattr(np, func)
+ return f(obj, *args, **kwargs)
+ else:
+ msg = f"'{func}' is not a valid function for '{type(obj).__name__}' object"
+ raise AttributeError(msg)
+
+
+class NDFrameApply(Apply):
+ """
+ Methods shared by FrameApply and SeriesApply but
+ not GroupByApply or ResamplerWindowApply
+ """
+
+ obj: DataFrame | Series
+
+ @property
+ def index(self) -> Index:
+ return self.obj.index
+
+ @property
+ def agg_axis(self) -> Index:
+ return self.obj._get_agg_axis(self.axis)
+
+ def agg_or_apply_list_like(
+ self, op_name: Literal["agg", "apply"]
+ ) -> DataFrame | Series:
+ obj = self.obj
+ kwargs = self.kwargs
+
+ if op_name == "apply":
+ if isinstance(self, FrameApply):
+ by_row = self.by_row
+
+ elif isinstance(self, SeriesApply):
+ by_row = "_compat" if self.by_row else False
+ else:
+ by_row = False
+ kwargs = {**kwargs, "by_row": by_row}
+
+ if getattr(obj, "axis", 0) == 1:
+ raise NotImplementedError("axis other than 0 is not supported")
+
+ keys, results = self.compute_list_like(op_name, obj, kwargs)
+ result = self.wrap_results_list_like(keys, results)
+ return result
+
+ def agg_or_apply_dict_like(
+ self, op_name: Literal["agg", "apply"]
+ ) -> DataFrame | Series:
+ assert op_name in ["agg", "apply"]
+ obj = self.obj
+
+ kwargs = {}
+ if op_name == "apply":
+ by_row = "_compat" if self.by_row else False
+ kwargs.update({"by_row": by_row})
+
+ if getattr(obj, "axis", 0) == 1:
+ raise NotImplementedError("axis other than 0 is not supported")
+
+ selection = None
+ result_index, result_data = self.compute_dict_like(
+ op_name, obj, selection, kwargs
+ )
+ result = self.wrap_results_dict_like(obj, result_index, result_data)
+ return result
+
+
+class FrameApply(NDFrameApply):
+ obj: DataFrame
+
+ def __init__(
+ self,
+ obj: AggObjType,
+ func: AggFuncType,
+ raw: bool,
+ result_type: str | None,
+ *,
+ by_row: Literal[False, "compat"] = False,
+ engine: str = "python",
+ engine_kwargs: dict[str, bool] | None = None,
+ args,
+ kwargs,
+ ) -> None:
+ if by_row is not False and by_row != "compat":
+ raise ValueError(f"by_row={by_row} not allowed")
+ super().__init__(
+ obj,
+ func,
+ raw,
+ result_type,
+ by_row=by_row,
+ engine=engine,
+ engine_kwargs=engine_kwargs,
+ args=args,
+ kwargs=kwargs,
+ )
+
+ # ---------------------------------------------------------------
+ # Abstract Methods
+
+ @property
+ @abc.abstractmethod
+ def result_index(self) -> Index:
+ pass
+
+ @property
+ @abc.abstractmethod
+ def result_columns(self) -> Index:
+ pass
+
+ @property
+ @abc.abstractmethod
+ def series_generator(self) -> Generator[Series]:
+ pass
+
+ @staticmethod
+ @functools.cache
+ @abc.abstractmethod
+ def generate_numba_apply_func(
+ func, nogil=True, nopython=True, parallel=False
+ ) -> Callable[[npt.NDArray, Index, Index], dict[int, Any]]:
+ pass
+
+ @abc.abstractmethod
+ def apply_with_numba(self):
+ pass
+
+ def validate_values_for_numba(self) -> None:
+ # Validate column dtyps all OK
+ for colname, dtype in self.obj.dtypes.items():
+ if not is_numeric_dtype(dtype):
+ raise ValueError(
+ f"Column {colname} must have a numeric dtype. "
+ f"Found '{dtype}' instead"
+ )
+ if is_extension_array_dtype(dtype):
+ raise ValueError(
+ f"Column {colname} is backed by an extension array, "
+ f"which is not supported by the numba engine."
+ )
+
+ @abc.abstractmethod
+ def wrap_results_for_axis(
+ self, results: ResType, res_index: Index
+ ) -> DataFrame | Series:
+ pass
+
+ # ---------------------------------------------------------------
+
+ @property
+ def res_columns(self) -> Index:
+ return self.result_columns
+
+ @property
+ def columns(self) -> Index:
+ return self.obj.columns
+
+ @cache_readonly
+ def values(self):
+ return self.obj.values
+
+ def apply(self) -> DataFrame | Series:
+ """compute the results"""
+
+ # dispatch to handle list-like or dict-like
+ if is_list_like(self.func):
+ if self.engine == "numba":
+ raise NotImplementedError(
+ "the 'numba' engine doesn't support lists of callables yet"
+ )
+ return self.apply_list_or_dict_like()
+
+ # all empty
+ if len(self.columns) == 0 and len(self.index) == 0:
+ return self.apply_empty_result()
+
+ # string dispatch
+ if isinstance(self.func, str):
+ if self.engine == "numba":
+ raise NotImplementedError(
+ "the 'numba' engine doesn't support using "
+ "a string as the callable function"
+ )
+ return self.apply_str()
+
+ # ufunc
+ elif isinstance(self.func, np.ufunc):
+ if self.engine == "numba":
+ raise NotImplementedError(
+ "the 'numba' engine doesn't support "
+ "using a numpy ufunc as the callable function"
+ )
+ with np.errstate(all="ignore"):
+ results = self.obj._mgr.apply("apply", func=self.func)
+ # _constructor will retain self.index and self.columns
+ return self.obj._constructor_from_mgr(results, axes=results.axes)
+
+ # broadcasting
+ if self.result_type == "broadcast":
+ if self.engine == "numba":
+ raise NotImplementedError(
+ "the 'numba' engine doesn't support result_type='broadcast'"
+ )
+ return self.apply_broadcast(self.obj)
+
+ # one axis empty
+ elif not all(self.obj.shape):
+ return self.apply_empty_result()
+
+ # raw
+ elif self.raw:
+ return self.apply_raw(engine=self.engine, engine_kwargs=self.engine_kwargs)
+
+ return self.apply_standard()
+
+ def agg(self):
+ obj = self.obj
+ axis = self.axis
+
+ # TODO: Avoid having to change state
+ self.obj = self.obj if self.axis == 0 else self.obj.T
+ self.axis = 0
+
+ result = None
+ try:
+ result = super().agg()
+ finally:
+ self.obj = obj
+ self.axis = axis
+
+ if axis == 1:
+ result = result.T if result is not None else result
+
+ if result is None:
+ result = self.obj.apply(self.func, axis, args=self.args, **self.kwargs)
+
+ return result
+
+ def apply_empty_result(self):
+ """
+ we have an empty result; at least 1 axis is 0
+
+ we will try to apply the function to an empty
+ series in order to see if this is a reduction function
+ """
+ assert callable(self.func)
+
+ # we are not asked to reduce or infer reduction
+ # so just return a copy of the existing object
+ if self.result_type not in ["reduce", None]:
+ return self.obj.copy()
+
+ # we may need to infer
+ should_reduce = self.result_type == "reduce"
+
+ from pandas import Series
+
+ if not should_reduce:
+ try:
+ if self.axis == 0:
+ r = self.func(
+ Series([], dtype=np.float64), *self.args, **self.kwargs
+ )
+ else:
+ r = self.func(
+ Series(index=self.columns, dtype=np.float64),
+ *self.args,
+ **self.kwargs,
+ )
+ except Exception:
+ pass
+ else:
+ should_reduce = not isinstance(r, Series)
+
+ if should_reduce:
+ if len(self.agg_axis):
+ r = self.func(Series([], dtype=np.float64), *self.args, **self.kwargs)
+ else:
+ r = np.nan
+
+ return self.obj._constructor_sliced(r, index=self.agg_axis)
+ else:
+ return self.obj.copy()
+
+ def apply_raw(self, engine="python", engine_kwargs=None):
+ """apply to the values as a numpy array"""
+
+ def wrap_function(func):
+ """
+ Wrap user supplied function to work around numpy issue.
+
+ see https://github.com/numpy/numpy/issues/8352
+ """
+
+ def wrapper(*args, **kwargs):
+ result = func(*args, **kwargs)
+ if isinstance(result, str):
+ result = np.array(result, dtype=object)
+ return result
+
+ return wrapper
+
+ if engine == "numba":
+ args, kwargs = prepare_function_arguments(
+ self.func, # type: ignore[arg-type]
+ self.args,
+ self.kwargs,
+ num_required_args=1,
+ )
+ # error: Argument 1 to "__call__" of "_lru_cache_wrapper" has
+ # incompatible type "Callable[..., Any] | str | list[Callable
+ # [..., Any] | str] | dict[Hashable,Callable[..., Any] | str |
+ # list[Callable[..., Any] | str]]"; expected "Hashable"
+ nb_looper = generate_apply_looper(
+ self.func, # type: ignore[arg-type]
+ **get_jit_arguments(engine_kwargs),
+ )
+ result = nb_looper(self.values, self.axis, *args)
+ # If we made the result 2-D, squeeze it back to 1-D
+ result = np.squeeze(result)
+ else:
+ result = np.apply_along_axis(
+ wrap_function(self.func),
+ self.axis,
+ self.values,
+ *self.args,
+ **self.kwargs,
+ )
+
+ # TODO: mixed type case
+ if result.ndim == 2:
+ return self.obj._constructor(result, index=self.index, columns=self.columns)
+ else:
+ return self.obj._constructor_sliced(result, index=self.agg_axis)
+
+ def apply_broadcast(self, target: DataFrame) -> DataFrame:
+ assert callable(self.func)
+
+ result_values = np.empty_like(target.values)
+
+ # axis which we want to compare compliance
+ result_compare = target.shape[0]
+
+ for i, col in enumerate(target.columns):
+ res = self.func(target[col], *self.args, **self.kwargs)
+ ares = np.asarray(res).ndim
+
+ # must be a scalar or 1d
+ if ares > 1:
+ raise ValueError("too many dims to broadcast")
+ if ares == 1:
+ # must match return dim
+ if result_compare != len(res):
+ raise ValueError("cannot broadcast result")
+
+ result_values[:, i] = res
+
+ # we *always* preserve the original index / columns
+ result = self.obj._constructor(
+ result_values, index=target.index, columns=target.columns
+ )
+ return result
+
+ def apply_standard(self):
+ if self.engine == "python":
+ results, res_index = self.apply_series_generator()
+ else:
+ results, res_index = self.apply_series_numba()
+
+ # wrap results
+ return self.wrap_results(results, res_index)
+
+ def apply_series_generator(self) -> tuple[ResType, Index]:
+ assert callable(self.func)
+
+ series_gen = self.series_generator
+ res_index = self.result_index
+
+ results = {}
+
+ for i, v in enumerate(series_gen):
+ results[i] = self.func(v, *self.args, **self.kwargs)
+ if isinstance(results[i], ABCSeries):
+ # If we have a view on v, we need to make a copy because
+ # series_generator will swap out the underlying data
+ results[i] = results[i].copy(deep=False)
+
+ return results, res_index
+
+ def apply_series_numba(self):
+ if self.engine_kwargs.get("parallel", False):
+ raise NotImplementedError(
+ "Parallel apply is not supported when raw=False and engine='numba'"
+ )
+ if not self.obj.index.is_unique or not self.columns.is_unique:
+ raise NotImplementedError(
+ "The index/columns must be unique when raw=False and engine='numba'"
+ )
+ self.validate_values_for_numba()
+ results = self.apply_with_numba()
+ return results, self.result_index
+
+ def wrap_results(self, results: ResType, res_index: Index) -> DataFrame | Series:
+ from pandas import Series
+
+ # see if we can infer the results
+ if len(results) > 0 and 0 in results and is_sequence(results[0]):
+ return self.wrap_results_for_axis(results, res_index)
+
+ # dict of scalars
+
+ # the default dtype of an empty Series is `object`, but this
+ # code can be hit by df.mean() where the result should have dtype
+ # float64 even if it's an empty Series.
+ constructor_sliced = self.obj._constructor_sliced
+ if len(results) == 0 and constructor_sliced is Series:
+ result = constructor_sliced(results, dtype=np.float64)
+ else:
+ result = constructor_sliced(results)
+ result.index = res_index
+
+ return result
+
+ def apply_str(self) -> DataFrame | Series:
+ # Caller is responsible for checking isinstance(self.func, str)
+ # TODO: GH#39993 - Avoid special-casing by replacing with lambda
+ if self.func == "size":
+ # Special-cased because DataFrame.size returns a single scalar
+ obj = self.obj
+ value = obj.shape[self.axis]
+ return obj._constructor_sliced(value, index=self.agg_axis)
+ return super().apply_str()
+
+
+class FrameRowApply(FrameApply):
+ axis: AxisInt = 0
+
+ @property
+ def series_generator(self) -> Generator[Series]:
+ return (self.obj._ixs(i, axis=1) for i in range(len(self.columns)))
+
+ @staticmethod
+ @functools.cache
+ def generate_numba_apply_func(
+ func, nogil=True, nopython=True, parallel=False
+ ) -> Callable[[npt.NDArray, Index, Index], dict[int, Any]]:
+ numba = import_optional_dependency("numba")
+ from pandas import Series
+
+ # Import helper from extensions to cast string object -> np strings
+ # Note: This also has the side effect of loading our numba extensions
+ from pandas.core._numba.extensions import maybe_cast_str
+
+ jitted_udf = numba.extending.register_jitable(func)
+
+ # Currently the parallel argument doesn't get passed through here
+ # (it's disabled) since the dicts in numba aren't thread-safe.
+ @numba.jit(nogil=nogil, nopython=nopython, parallel=parallel)
+ def numba_func(values, col_names, df_index, *args):
+ results = {}
+ for j in range(values.shape[1]):
+ # Create the series
+ ser = Series(
+ values[:, j], index=df_index, name=maybe_cast_str(col_names[j])
+ )
+ results[j] = jitted_udf(ser, *args)
+ return results
+
+ return numba_func
+
+ def apply_with_numba(self) -> dict[int, Any]:
+ func = cast(Callable, self.func)
+ args, kwargs = prepare_function_arguments(
+ func, self.args, self.kwargs, num_required_args=1
+ )
+ nb_func = self.generate_numba_apply_func(
+ func, **get_jit_arguments(self.engine_kwargs)
+ )
+ from pandas.core._numba.extensions import set_numba_data
+
+ index = self.obj.index
+ columns = self.obj.columns
+
+ # Convert from numba dict to regular dict
+ # Our isinstance checks in the df constructor don't pass for numbas typed dict
+ with set_numba_data(index) as index, set_numba_data(columns) as columns:
+ res = dict(nb_func(self.values, columns, index, *args))
+ return res
+
+ @property
+ def result_index(self) -> Index:
+ return self.columns
+
+ @property
+ def result_columns(self) -> Index:
+ return self.index
+
+ def wrap_results_for_axis(
+ self, results: ResType, res_index: Index
+ ) -> DataFrame | Series:
+ """return the results for the rows"""
+
+ if self.result_type == "reduce":
+ # e.g. test_apply_dict GH#8735
+ res = self.obj._constructor_sliced(results)
+ res.index = res_index
+ return res
+
+ elif self.result_type is None and all(
+ isinstance(x, dict) for x in results.values()
+ ):
+ # Our operation was a to_dict op e.g.
+ # test_apply_dict GH#8735, test_apply_reduce_to_dict GH#25196 #37544
+ res = self.obj._constructor_sliced(results)
+ res.index = res_index
+ return res
+
+ try:
+ result = self.obj._constructor(data=results)
+ except ValueError as err:
+ if "All arrays must be of the same length" in str(err):
+ # e.g. result = [[2, 3], [1.5], ['foo', 'bar']]
+ # see test_agg_listlike_result GH#29587
+ res = self.obj._constructor_sliced(results)
+ res.index = res_index
+ return res
+ else:
+ raise
+
+ if not isinstance(results[0], ABCSeries):
+ if len(result.index) == len(self.res_columns):
+ result.index = self.res_columns
+
+ if len(result.columns) == len(res_index):
+ result.columns = res_index
+
+ return result
+
+
+class FrameColumnApply(FrameApply):
+ axis: AxisInt = 1
+
+ def apply_broadcast(self, target: DataFrame) -> DataFrame:
+ result = super().apply_broadcast(target.T)
+ return result.T
+
+ @property
+ def series_generator(self) -> Generator[Series]:
+ values = self.values
+ values = ensure_wrapped_if_datetimelike(values)
+ assert len(values) > 0
+
+ # We create one Series object, and will swap out the data inside
+ # of it. Kids: don't do this at home.
+ ser = self.obj._ixs(0, axis=0)
+ mgr = ser._mgr
+
+ is_view = mgr.blocks[0].refs.has_reference()
+
+ if isinstance(ser.dtype, ExtensionDtype):
+ # values will be incorrect for this block
+ # TODO(EA2D): special case would be unnecessary with 2D EAs
+ obj = self.obj
+ for i in range(len(obj)):
+ yield obj._ixs(i, axis=0)
+
+ else:
+ for arr, name in zip(values, self.index, strict=True):
+ # GH#35462 re-pin mgr in case setitem changed it
+ ser._mgr = mgr
+ mgr.set_values(arr)
+ object.__setattr__(ser, "_name", name)
+ if not is_view:
+ # In apply_series_generator we store the a shallow copy of the
+ # result, which potentially increases the ref count of this reused
+ # `ser` object (depending on the result of the applied function)
+ # -> if that happened and `ser` is already a copy, then we reset
+ # the refs here to avoid triggering a unnecessary CoW inside the
+ # applied function (https://github.com/pandas-dev/pandas/pull/56212)
+ mgr.blocks[0].refs = BlockValuesRefs(mgr.blocks[0])
+ yield ser
+
+ @staticmethod
+ @functools.cache
+ def generate_numba_apply_func(
+ func, nogil=True, nopython=True, parallel=False
+ ) -> Callable[[npt.NDArray, Index, Index], dict[int, Any]]:
+ numba = import_optional_dependency("numba")
+ from pandas import Series
+ from pandas.core._numba.extensions import maybe_cast_str
+
+ jitted_udf = numba.extending.register_jitable(func)
+
+ @numba.jit(nogil=nogil, nopython=nopython, parallel=parallel)
+ def numba_func(values, col_names_index, index, *args):
+ results = {}
+ # Currently the parallel argument doesn't get passed through here
+ # (it's disabled) since the dicts in numba aren't thread-safe.
+ for i in range(values.shape[0]):
+ # Create the series
+ # TODO: values corrupted without the copy
+ ser = Series(
+ values[i].copy(),
+ index=col_names_index,
+ name=maybe_cast_str(index[i]),
+ )
+ results[i] = jitted_udf(ser, *args)
+
+ return results
+
+ return numba_func
+
+ def apply_with_numba(self) -> dict[int, Any]:
+ func = cast(Callable, self.func)
+ args, kwargs = prepare_function_arguments(
+ func, self.args, self.kwargs, num_required_args=1
+ )
+ nb_func = self.generate_numba_apply_func(
+ func, **get_jit_arguments(self.engine_kwargs)
+ )
+
+ from pandas.core._numba.extensions import set_numba_data
+
+ # Convert from numba dict to regular dict
+ # Our isinstance checks in the df constructor don't pass for numbas typed dict
+ with (
+ set_numba_data(self.obj.index) as index,
+ set_numba_data(self.columns) as columns,
+ ):
+ res = dict(nb_func(self.values, columns, index, *args))
+
+ return res
+
+ @property
+ def result_index(self) -> Index:
+ return self.index
+
+ @property
+ def result_columns(self) -> Index:
+ return self.columns
+
+ def wrap_results_for_axis(
+ self, results: ResType, res_index: Index
+ ) -> DataFrame | Series:
+ """return the results for the columns"""
+ result: DataFrame | Series
+
+ # we have requested to expand
+ if self.result_type == "expand":
+ result = self.infer_to_same_shape(results, res_index)
+
+ # we have a non-series and don't want inference
+ elif not isinstance(results[0], ABCSeries):
+ result = self.obj._constructor_sliced(results)
+ result.index = res_index
+
+ # we may want to infer results
+ else:
+ result = self.infer_to_same_shape(results, res_index)
+
+ return result
+
+ def infer_to_same_shape(self, results: ResType, res_index: Index) -> DataFrame:
+ """infer the results to the same shape as the input object"""
+ result = self.obj._constructor(data=results)
+ result = result.T
+
+ # set the index
+ result.index = res_index
+
+ # infer dtypes
+ result = result.infer_objects()
+
+ return result
+
+
+class SeriesApply(NDFrameApply):
+ obj: Series
+ axis: AxisInt = 0
+ by_row: Literal[False, "compat", "_compat"] # only relevant for apply()
+
+ def __init__(
+ self,
+ obj: Series,
+ func: AggFuncType,
+ *,
+ by_row: Literal[False, "compat", "_compat"] = "compat",
+ args,
+ kwargs,
+ ) -> None:
+ super().__init__(
+ obj,
+ func,
+ raw=False,
+ result_type=None,
+ by_row=by_row,
+ args=args,
+ kwargs=kwargs,
+ )
+
+ def apply(self) -> DataFrame | Series:
+ obj = self.obj
+
+ if len(obj) == 0:
+ return self.apply_empty_result()
+
+ # dispatch to handle list-like or dict-like
+ if is_list_like(self.func):
+ return self.apply_list_or_dict_like()
+
+ if isinstance(self.func, str):
+ # if we are a string, try to dispatch
+ return self.apply_str()
+
+ if self.by_row == "_compat":
+ return self.apply_compat()
+
+ # self.func is Callable
+ return self.apply_standard()
+
+ def agg(self):
+ result = super().agg()
+ if result is None:
+ obj = self.obj
+ func = self.func
+ # string, list-like, and dict-like are entirely handled in super
+ assert callable(func)
+ result = func(obj, *self.args, **self.kwargs)
+ return result
+
+ def apply_empty_result(self) -> Series:
+ obj = self.obj
+ return obj._constructor(dtype=obj.dtype, index=obj.index).__finalize__(
+ obj, method="apply"
+ )
+
+ def apply_compat(self):
+ """compat apply method for funcs in listlikes and dictlikes.
+
+ Used for each callable when giving listlikes and dictlikes of callables to
+ apply. Needed for compatibility with Pandas < v2.1.
+
+ .. versionadded:: 2.1.0
+ """
+ obj = self.obj
+ func = self.func
+
+ if callable(func):
+ f = com.get_cython_func(func)
+ if f and not self.args and not self.kwargs:
+ return obj.apply(func, by_row=False)
+
+ try:
+ result = obj.apply(func, by_row="compat")
+ except (ValueError, AttributeError, TypeError):
+ result = obj.apply(func, by_row=False)
+ return result
+
+ def apply_standard(self) -> DataFrame | Series:
+ # caller is responsible for ensuring that f is Callable
+ func = cast(Callable, self.func)
+ obj = self.obj
+
+ if isinstance(func, np.ufunc):
+ with np.errstate(all="ignore"):
+ return func(obj, *self.args, **self.kwargs)
+ elif not self.by_row:
+ return func(obj, *self.args, **self.kwargs)
+
+ if self.args or self.kwargs:
+ # _map_values does not support args/kwargs
+ def curried(x):
+ return func(x, *self.args, **self.kwargs)
+
+ else:
+ curried = func
+ mapped = obj._map_values(mapper=curried)
+
+ if len(mapped) and isinstance(mapped[0], ABCSeries):
+ # GH#43986 Need to do list(mapped) in order to get treated as nested
+ # See also GH#25959 regarding EA support
+ return obj._constructor_expanddim(list(mapped), index=obj.index)
+ else:
+ return obj._constructor(mapped, index=obj.index).__finalize__(
+ obj, method="apply"
+ )
+
+
+class GroupByApply(Apply):
+ obj: GroupBy | Resampler | BaseWindow
+
+ def __init__(
+ self,
+ obj: GroupBy[NDFrameT],
+ func: AggFuncType,
+ *,
+ args,
+ kwargs,
+ ) -> None:
+ kwargs = kwargs.copy()
+ self.axis = obj.obj._get_axis_number(kwargs.get("axis", 0))
+ super().__init__(
+ obj,
+ func,
+ raw=False,
+ result_type=None,
+ args=args,
+ kwargs=kwargs,
+ )
+
+ def apply(self):
+ raise NotImplementedError
+
+ def transform(self):
+ raise NotImplementedError
+
+ def agg_or_apply_list_like(
+ self, op_name: Literal["agg", "apply"]
+ ) -> DataFrame | Series:
+ obj = self.obj
+ kwargs = self.kwargs
+ if op_name == "apply":
+ kwargs = {**kwargs, "by_row": False}
+
+ if getattr(obj, "axis", 0) == 1:
+ raise NotImplementedError("axis other than 0 is not supported")
+
+ if obj._selected_obj.ndim == 1:
+ # For SeriesGroupBy this matches _obj_with_exclusions
+ selected_obj = obj._selected_obj
+ else:
+ selected_obj = obj._obj_with_exclusions
+
+ # Only set as_index=True on groupby objects, not Window or Resample
+ # that inherit from this class.
+ with com.temp_setattr(
+ obj, "as_index", True, condition=hasattr(obj, "as_index")
+ ):
+ keys, results = self.compute_list_like(op_name, selected_obj, kwargs)
+ result = self.wrap_results_list_like(keys, results)
+ return result
+
+ def agg_or_apply_dict_like(
+ self, op_name: Literal["agg", "apply"]
+ ) -> DataFrame | Series:
+ from pandas.core.groupby.generic import (
+ DataFrameGroupBy,
+ SeriesGroupBy,
+ )
+
+ assert op_name in ["agg", "apply"]
+
+ obj = self.obj
+ kwargs: dict[str, Any] = {}
+ if op_name == "apply":
+ by_row = "_compat" if self.by_row else False
+ kwargs.update({"by_row": by_row})
+
+ if getattr(obj, "axis", 0) == 1:
+ raise NotImplementedError("axis other than 0 is not supported")
+
+ selected_obj = obj._selected_obj
+ selection = obj._selection
+
+ is_groupby = isinstance(obj, (DataFrameGroupBy, SeriesGroupBy))
+
+ # Numba Groupby engine/engine-kwargs passthrough
+ if is_groupby:
+ engine = self.kwargs.get("engine", None)
+ engine_kwargs = self.kwargs.get("engine_kwargs", None)
+ kwargs.update({"engine": engine, "engine_kwargs": engine_kwargs})
+
+ with com.temp_setattr(
+ obj, "as_index", True, condition=hasattr(obj, "as_index")
+ ):
+ result_index, result_data = self.compute_dict_like(
+ op_name, selected_obj, selection, kwargs
+ )
+ result = self.wrap_results_dict_like(selected_obj, result_index, result_data)
+ return result
+
+
+class ResamplerWindowApply(GroupByApply):
+ axis: AxisInt = 0
+ obj: Resampler | BaseWindow
+
+ def __init__(
+ self,
+ obj: Resampler | BaseWindow,
+ func: AggFuncType,
+ *,
+ args,
+ kwargs,
+ ) -> None:
+ super(GroupByApply, self).__init__(
+ obj,
+ func,
+ raw=False,
+ result_type=None,
+ args=args,
+ kwargs=kwargs,
+ )
+
+ def apply(self):
+ raise NotImplementedError
+
+ def transform(self):
+ raise NotImplementedError
+
+
+def reconstruct_func(
+ func: AggFuncType | None, **kwargs
+) -> tuple[bool, AggFuncType, tuple[str, ...] | None, npt.NDArray[np.intp] | None]:
+ """
+ This is the internal function to reconstruct func given if there is relabeling
+ or not and also normalize the keyword to get new order of columns.
+
+ If named aggregation is applied, `func` will be None, and kwargs contains the
+ column and aggregation function information to be parsed;
+ If named aggregation is not applied, `func` is either string (e.g. 'min') or
+ Callable, or list of them (e.g. ['min', np.max]), or the dictionary of column name
+ and str/Callable/list of them (e.g. {'A': 'min'}, or {'A': [np.min, lambda x: x]})
+
+ If relabeling is True, will return relabeling, reconstructed func, column
+ names, and the reconstructed order of columns.
+ If relabeling is False, the columns and order will be None.
+
+ Parameters
+ ----------
+ func: agg function (e.g. 'min' or Callable) or list of agg functions
+ (e.g. ['min', np.max]) or dictionary (e.g. {'A': ['min', np.max]}).
+ **kwargs: dict, kwargs used in is_multi_agg_with_relabel and
+ normalize_keyword_aggregation function for relabelling
+
+ Returns
+ -------
+ relabelling: bool, if there is relabelling or not
+ func: normalized and mangled func
+ columns: tuple of column names
+ order: array of columns indices
+
+ Examples
+ --------
+ >>> reconstruct_func(None, **{"foo": ("col", "min")})
+ (True, defaultdict(, {'col': ['min']}), ('foo',), array([0]))
+
+ >>> reconstruct_func("min")
+ (False, 'min', None, None)
+ """
+ from pandas.core.groupby.generic import NamedAgg
+
+ relabeling = func is None and (
+ is_multi_agg_with_relabel(**kwargs)
+ or any(isinstance(v, NamedAgg) for v in kwargs.values())
+ )
+
+ columns: tuple[str, ...] | None = None
+ order: npt.NDArray[np.intp] | None = None
+
+ if not relabeling:
+ if isinstance(func, list) and len(func) > len(set(func)):
+ # GH 28426 will raise error if duplicated function names are used and
+ # there is no reassigned name
+ raise SpecificationError(
+ "Function names must be unique if there is no new column names assigned"
+ )
+ if func is None:
+ # nicer error message
+ raise TypeError("Must provide 'func' or tuples of '(column, aggfunc).")
+
+ if relabeling:
+ # error: Incompatible types in assignment (expression has type
+ # "MutableMapping[Hashable, list[Callable[..., Any] | str]]", variable has type
+ # "Callable[..., Any] | str | list[Callable[..., Any] | str] |
+ # MutableMapping[Hashable, Callable[..., Any] | str | list[Callable[..., Any] |
+ # str]] | None")
+ converted_kwargs = {}
+ for key, val in kwargs.items():
+ if isinstance(val, NamedAgg):
+ aggfunc = val.aggfunc
+ if val.args or val.kwargs:
+ aggfunc = lambda x, func=aggfunc, a=val.args, kw=val.kwargs: func(
+ x, *a, **kw
+ )
+ converted_kwargs[key] = (val.column, aggfunc)
+ else:
+ converted_kwargs[key] = val
+
+ func, columns, order = normalize_keyword_aggregation( # type: ignore[assignment]
+ converted_kwargs
+ )
+
+ assert func is not None
+
+ return relabeling, func, columns, order
+
+
+def is_multi_agg_with_relabel(**kwargs) -> bool:
+ """
+ Check whether kwargs passed to .agg look like multi-agg with relabeling.
+
+ Parameters
+ ----------
+ **kwargs : dict
+
+ Returns
+ -------
+ bool
+
+ Examples
+ --------
+ >>> is_multi_agg_with_relabel(a="max")
+ False
+ >>> is_multi_agg_with_relabel(a_max=("a", "max"), a_min=("a", "min"))
+ True
+ >>> is_multi_agg_with_relabel()
+ False
+ """
+ return all(isinstance(v, tuple) and len(v) == 2 for v in kwargs.values()) and (
+ len(kwargs) > 0
+ )
+
+
+def normalize_keyword_aggregation(
+ kwargs: dict,
+) -> tuple[
+ MutableMapping[Hashable, list[AggFuncTypeBase]],
+ tuple[str, ...],
+ npt.NDArray[np.intp],
+]:
+ """
+ Normalize user-provided "named aggregation" kwargs.
+ Transforms from the new ``Mapping[str, NamedAgg]`` style kwargs
+ to the old Dict[str, List[scalar]]].
+
+ Parameters
+ ----------
+ kwargs : dict
+
+ Returns
+ -------
+ aggspec : dict
+ The transformed kwargs.
+ columns : tuple[str, ...]
+ The user-provided keys.
+ col_idx_order : List[int]
+ List of columns indices.
+
+ Examples
+ --------
+ >>> normalize_keyword_aggregation({"output": ("input", "sum")})
+ (defaultdict(, {'input': ['sum']}), ('output',), array([0]))
+ """
+ from pandas.core.indexes.base import Index
+
+ # Normalize the aggregation functions as Mapping[column, List[func]],
+ # process normally, then fixup the names.
+ # TODO: aggspec type: typing.Dict[str, List[AggScalar]]
+ aggspec = defaultdict(list)
+ order = []
+ columns = tuple(kwargs.keys())
+
+ for column, aggfunc in kwargs.values():
+ aggspec[column].append(aggfunc)
+ order.append((column, com.get_callable_name(aggfunc) or aggfunc))
+
+ # uniquify aggfunc name if duplicated in order list
+ uniquified_order = _make_unique_kwarg_list(order)
+
+ # GH 25719, due to aggspec will change the order of assigned columns in aggregation
+ # uniquified_aggspec will store uniquified order list and will compare it with order
+ # based on index
+ aggspec_order = [
+ (column, com.get_callable_name(aggfunc) or aggfunc)
+ for column, aggfuncs in aggspec.items()
+ for aggfunc in aggfuncs
+ ]
+ uniquified_aggspec = _make_unique_kwarg_list(aggspec_order)
+
+ # get the new index of columns by comparison
+ col_idx_order = Index(uniquified_aggspec).get_indexer(uniquified_order)
+ return aggspec, columns, col_idx_order
+
+
+def _make_unique_kwarg_list(
+ seq: Sequence[tuple[Any, Any]],
+) -> Sequence[tuple[Any, Any]]:
+ """
+ Uniquify aggfunc name of the pairs in the order list
+
+ Examples:
+ --------
+ >>> kwarg_list = [("a", ""), ("a", ""), ("b", "")]
+ >>> _make_unique_kwarg_list(kwarg_list)
+ [('a', '_0'), ('a', '_1'), ('b', '')]
+ """
+ return [
+ (pair[0], f"{pair[1]}_{seq[:i].count(pair)}") if seq.count(pair) > 1 else pair
+ for i, pair in enumerate(seq)
+ ]
+
+
+def relabel_result(
+ result: DataFrame | Series,
+ func: dict[str, list[Callable | str]],
+ columns: Iterable[Hashable],
+ order: Iterable[int],
+) -> dict[Hashable, Series]:
+ """
+ Internal function to reorder result if relabelling is True for
+ dataframe.agg, and return the reordered result in dict.
+
+ Parameters:
+ ----------
+ result: Result from aggregation
+ func: Dict of (column name, funcs)
+ columns: New columns name for relabelling
+ order: New order for relabelling
+
+ Examples
+ --------
+ >>> from pandas.core.apply import relabel_result
+ >>> result = pd.DataFrame(
+ ... {"A": [np.nan, 2, np.nan], "C": [6, np.nan, np.nan], "B": [np.nan, 4, 2.5]},
+ ... index=["max", "mean", "min"],
+ ... )
+ >>> funcs = {"A": ["max"], "C": ["max"], "B": ["mean", "min"]}
+ >>> columns = ("foo", "aab", "bar", "dat")
+ >>> order = [0, 1, 2, 3]
+ >>> result_in_dict = relabel_result(result, funcs, columns, order)
+ >>> pd.DataFrame(result_in_dict, index=columns)
+ A C B
+ foo 2.0 NaN NaN
+ aab NaN 6.0 NaN
+ bar NaN NaN 4.0
+ dat NaN NaN 2.5
+ """
+ from pandas.core.indexes.base import Index
+
+ reordered_indexes = [
+ pair[0] for pair in sorted(zip(columns, order, strict=True), key=lambda t: t[1])
+ ]
+ reordered_result_in_dict: dict[Hashable, Series] = {}
+ idx = 0
+
+ reorder_mask = not isinstance(result, ABCSeries) and len(result.columns) > 1
+ for col, fun in func.items():
+ s = result[col].dropna()
+
+ # In the `_aggregate`, the callable names are obtained and used in `result`, and
+ # these names are ordered alphabetically. e.g.
+ # C2 C1
+ # 1 NaN
+ # amax NaN 4.0
+ # max NaN 4.0
+ # sum 18.0 6.0
+ # Therefore, the order of functions for each column could be shuffled
+ # accordingly so need to get the callable name if it is not parsed names, and
+ # reorder the aggregated result for each column.
+ # e.g. if df.agg(c1=("C2", sum), c2=("C2", lambda x: min(x))), correct order is
+ # [sum, ], but in `result`, it will be [, sum], and we need to
+ # reorder so that aggregated values map to their functions regarding the order.
+
+ # However there is only one column being used for aggregation, not need to
+ # reorder since the index is not sorted, and keep as is in `funcs`, e.g.
+ # A
+ # min 1.0
+ # mean 1.5
+ # mean 1.5
+ if reorder_mask:
+ fun = [
+ com.get_callable_name(f) if not isinstance(f, str) else f for f in fun
+ ]
+ col_idx_order = Index(s.index, copy=False).get_indexer(fun)
+ valid_idx = col_idx_order != -1
+ if valid_idx.any():
+ s = s.iloc[col_idx_order[valid_idx]]
+ # assign the new user-provided "named aggregation" as index names, and reindex
+ # it based on the whole user-provided names.
+ if not s.empty:
+ s.index = reordered_indexes[idx : idx + len(fun)]
+ reordered_result_in_dict[col] = s.reindex(columns)
+ idx = idx + len(fun)
+ return reordered_result_in_dict
+
+
+def reconstruct_and_relabel_result(result, func, **kwargs) -> DataFrame | Series:
+ from pandas import DataFrame
+
+ relabeling, func, columns, order = reconstruct_func(func, **kwargs)
+
+ if relabeling:
+ # This is to keep the order to columns occurrence unchanged, and also
+ # keep the order of new columns occurrence unchanged
+
+ # For the return values of reconstruct_func, if relabeling is
+ # False, columns and order will be None.
+ assert columns is not None
+ assert order is not None
+
+ result_in_dict = relabel_result(result, func, columns, order)
+ result = DataFrame(result_in_dict, index=columns)
+
+ return result
+
+
+# TODO: Can't use, because mypy doesn't like us setting __name__
+# error: "partial[Any]" has no attribute "__name__"
+# the type is:
+# typing.Sequence[Callable[..., ScalarResult]]
+# -> typing.Sequence[Callable[..., ScalarResult]]:
+
+
+def _managle_lambda_list(aggfuncs: Sequence[Any]) -> Sequence[Any]:
+ """
+ Possibly mangle a list of aggfuncs.
+
+ Parameters
+ ----------
+ aggfuncs : Sequence
+
+ Returns
+ -------
+ mangled: list-like
+ A new AggSpec sequence, where lambdas have been converted
+ to have unique names.
+
+ Notes
+ -----
+ If just one aggfunc is passed, the name will not be mangled.
+ """
+ if len(aggfuncs) <= 1:
+ # don't mangle for .agg([lambda x: .])
+ return aggfuncs
+ i = 0
+ mangled_aggfuncs = []
+ for aggfunc in aggfuncs:
+ if com.get_callable_name(aggfunc) == "":
+ aggfunc = partial(aggfunc)
+ # error: "partial[Any]" has no attribute "__name__"; maybe "__new__"?
+ aggfunc.__name__ = f"" # type: ignore[attr-defined]
+ i += 1
+ mangled_aggfuncs.append(aggfunc)
+
+ return mangled_aggfuncs
+
+
+def maybe_mangle_lambdas(agg_spec: Any) -> Any:
+ """
+ Make new lambdas with unique names.
+
+ Parameters
+ ----------
+ agg_spec : Any
+ An argument to GroupBy.agg.
+ Non-dict-like `agg_spec` are pass through as is.
+ For dict-like `agg_spec` a new spec is returned
+ with name-mangled lambdas.
+
+ Returns
+ -------
+ mangled : Any
+ Same type as the input.
+
+ Examples
+ --------
+ >>> maybe_mangle_lambdas("sum")
+ 'sum'
+ >>> maybe_mangle_lambdas([lambda: 1, lambda: 2]) # doctest: +SKIP
+ [,
+ .f(*args, **kwargs)>]
+ """
+ is_dict = is_dict_like(agg_spec)
+ if not (is_dict or is_list_like(agg_spec)):
+ return agg_spec
+ mangled_aggspec = type(agg_spec)() # dict or OrderedDict
+
+ if is_dict:
+ for key, aggfuncs in agg_spec.items():
+ if is_list_like(aggfuncs) and not is_dict_like(aggfuncs):
+ mangled_aggfuncs = _managle_lambda_list(aggfuncs)
+ else:
+ mangled_aggfuncs = aggfuncs
+
+ mangled_aggspec[key] = mangled_aggfuncs
+ else:
+ mangled_aggspec = _managle_lambda_list(agg_spec)
+
+ return mangled_aggspec
+
+
+def validate_func_kwargs(
+ kwargs: dict,
+) -> tuple[list[str], list[str | Callable[..., Any]]]:
+ """
+ Validates types of user-provided "named aggregation" kwargs.
+ `TypeError` is raised if aggfunc is not `str` or callable.
+
+ Parameters
+ ----------
+ kwargs : dict
+
+ Returns
+ -------
+ columns : List[str]
+ List of user-provided keys.
+ func : List[Union[str, callable[...,Any]]]
+ List of user-provided aggfuncs
+
+ Examples
+ --------
+ >>> validate_func_kwargs({"one": "min", "two": "max"})
+ (['one', 'two'], ['min', 'max'])
+ """
+ tuple_given_message = "func is expected but received {} in **kwargs."
+ columns = list(kwargs)
+ func = []
+ for col_func in kwargs.values():
+ if not (isinstance(col_func, str) or callable(col_func)):
+ raise TypeError(tuple_given_message.format(type(col_func).__name__))
+ func.append(col_func)
+ if not columns:
+ no_arg_message = "Must provide 'func' or named aggregation **kwargs."
+ raise TypeError(no_arg_message)
+ return columns, func
+
+
+def include_axis(op_name: Literal["agg", "apply"], colg: Series | DataFrame) -> bool:
+ return isinstance(colg, ABCDataFrame) or (
+ isinstance(colg, ABCSeries) and op_name == "agg"
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7655a013c6cf3fca754086fdeb29b806220d5e4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/__init__.py
@@ -0,0 +1,9 @@
+"""
+core.array_algos is for algorithms that operate on ndarray and ExtensionArray.
+These should:
+
+- Assume that any Index, Series, or DataFrame objects have already been unwrapped.
+- Assume that any list arguments have already been cast to ndarray/EA.
+- Not depend on Index, Series, or DataFrame, nor import any of these.
+- May dispatch to ExtensionArray methods, but should not import from core.arrays.
+"""
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/datetimelike_accumulations.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/datetimelike_accumulations.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc10dbfbec90d2f2a813754f2a09dfa84075ebbb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/datetimelike_accumulations.py
@@ -0,0 +1,73 @@
+"""
+datetimelke_accumulations.py is for accumulations of datetimelike extension arrays
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from pandas._libs import iNaT
+
+from pandas.core.dtypes.missing import isna
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+
+def _cum_func(
+ func: Callable,
+ values: np.ndarray,
+ *,
+ skipna: bool = True,
+) -> np.ndarray:
+ """
+ Accumulations for 1D datetimelike arrays.
+
+ Parameters
+ ----------
+ func : np.cumsum, np.maximum.accumulate, np.minimum.accumulate
+ values : np.ndarray
+ Numpy array with the values (can be of any dtype that support the
+ operation). Values is changed is modified inplace.
+ skipna : bool, default True
+ Whether to skip NA.
+ """
+ try:
+ fill_value = {
+ np.maximum.accumulate: np.iinfo(np.int64).min,
+ np.cumsum: 0,
+ np.minimum.accumulate: np.iinfo(np.int64).max,
+ }[func]
+ except KeyError as err:
+ raise ValueError(
+ f"No accumulation for {func} implemented on BaseMaskedArray"
+ ) from err
+
+ mask = isna(values)
+ y = values.view("i8")
+ y[mask] = fill_value
+
+ if not skipna:
+ mask = np.maximum.accumulate(mask)
+
+ # GH 57956
+ result = func(y, axis=0)
+ result[mask] = iNaT
+
+ if values.dtype.kind in "mM":
+ return result.view(values.dtype.base)
+ return result
+
+
+def cumsum(values: np.ndarray, *, skipna: bool = True) -> np.ndarray:
+ return _cum_func(np.cumsum, values, skipna=skipna)
+
+
+def cummin(values: np.ndarray, *, skipna: bool = True) -> np.ndarray:
+ return _cum_func(np.minimum.accumulate, values, skipna=skipna)
+
+
+def cummax(values: np.ndarray, *, skipna: bool = True) -> np.ndarray:
+ return _cum_func(np.maximum.accumulate, values, skipna=skipna)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/masked_accumulations.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/masked_accumulations.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4e116388b85ecbb809864c419555cd6f1fbe737
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/masked_accumulations.py
@@ -0,0 +1,97 @@
+"""
+masked_accumulations.py is for accumulation algorithms using a mask-based approach
+for missing values.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from pandas._typing import npt
+
+
+def _cum_func(
+ func: Callable,
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
+ """
+ Accumulations for 1D masked array.
+
+ We will modify values in place to replace NAs with the appropriate fill value.
+
+ Parameters
+ ----------
+ func : np.cumsum, np.cumprod, np.maximum.accumulate, np.minimum.accumulate
+ values : np.ndarray
+ Numpy array with the values (can be of any dtype that support the
+ operation).
+ mask : np.ndarray
+ Boolean numpy array (True values indicate missing values).
+ skipna : bool, default True
+ Whether to skip NA.
+ """
+ dtype_info: np.iinfo | np.finfo
+ if values.dtype.kind == "f":
+ dtype_info = np.finfo(values.dtype.type)
+ elif values.dtype.kind in "iu":
+ dtype_info = np.iinfo(values.dtype.type)
+ elif values.dtype.kind == "b":
+ # Max value of bool is 1, but since we are setting into a boolean
+ # array, 255 is fine as well. Min value has to be 0 when setting
+ # into the boolean array.
+ dtype_info = np.iinfo(np.uint8)
+ else:
+ raise NotImplementedError(
+ f"No masked accumulation defined for dtype {values.dtype.type}"
+ )
+ try:
+ fill_value = {
+ np.cumprod: 1,
+ np.maximum.accumulate: dtype_info.min,
+ np.cumsum: 0,
+ np.minimum.accumulate: dtype_info.max,
+ }[func]
+ except KeyError as err:
+ raise NotImplementedError(
+ f"No accumulation for {func} implemented on BaseMaskedArray"
+ ) from err
+
+ values[mask] = fill_value
+
+ if not skipna:
+ mask = np.maximum.accumulate(mask)
+
+ values = func(values)
+ return values, mask
+
+
+def cumsum(
+ values: np.ndarray, mask: npt.NDArray[np.bool_], *, skipna: bool = True
+) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
+ return _cum_func(np.cumsum, values, mask, skipna=skipna)
+
+
+def cumprod(
+ values: np.ndarray, mask: npt.NDArray[np.bool_], *, skipna: bool = True
+) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
+ return _cum_func(np.cumprod, values, mask, skipna=skipna)
+
+
+def cummin(
+ values: np.ndarray, mask: npt.NDArray[np.bool_], *, skipna: bool = True
+) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
+ return _cum_func(np.minimum.accumulate, values, mask, skipna=skipna)
+
+
+def cummax(
+ values: np.ndarray, mask: npt.NDArray[np.bool_], *, skipna: bool = True
+) -> tuple[np.ndarray, npt.NDArray[np.bool_]]:
+ return _cum_func(np.maximum.accumulate, values, mask, skipna=skipna)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/masked_reductions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/masked_reductions.py
new file mode 100644
index 0000000000000000000000000000000000000000..009fd38268c4593306195be2682112bcf595a59b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/masked_reductions.py
@@ -0,0 +1,214 @@
+"""
+masked_reductions.py is for reduction algorithms using a mask-based approach
+for missing values.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+import warnings
+
+import numpy as np
+
+from pandas._libs import (
+ lib,
+ missing as libmissing,
+)
+
+from pandas.core.nanops import check_below_min_count
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from pandas._typing import (
+ AxisInt,
+ npt,
+ )
+
+
+def _reductions(
+ func: Callable,
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+ min_count: int = 0,
+ axis: AxisInt | None = None,
+ initial: object | lib.NoDefault = lib.no_default,
+ **kwargs,
+):
+ """
+ Sum, mean or product for 1D masked array.
+
+ Parameters
+ ----------
+ func : np.sum or np.prod
+ values : np.ndarray
+ Numpy array with the values (can be of any dtype that support the
+ operation).
+ mask : np.ndarray[bool]
+ Boolean numpy array (True values indicate missing values).
+ skipna : bool, default True
+ Whether to skip NA.
+ min_count : int, default 0
+ The required number of valid values to perform the operation. If fewer than
+ ``min_count`` non-NA values are present the result will be NA.
+ axis : int, optional, default None
+ initial : scalar, optional
+ Starting value for the reduction. NumPy has a default value for most
+ data types, but for object-dtype arrays we need to specify it explicitly
+ """
+ if initial is not lib.no_default:
+ kwargs["initial"] = initial
+
+ if not skipna:
+ if mask.any() or check_below_min_count(values.shape, None, min_count):
+ return libmissing.NA
+ else:
+ return func(values, axis=axis, **kwargs)
+ else:
+ if check_below_min_count(values.shape, mask, min_count) and (
+ axis is None or values.ndim == 1
+ ):
+ return libmissing.NA
+
+ return func(values, where=~mask, axis=axis, **kwargs)
+
+
+def sum(
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+ min_count: int = 0,
+ axis: AxisInt | None = None,
+ initial: object | lib.NoDefault = lib.no_default,
+):
+ return _reductions(
+ np.sum,
+ values=values,
+ mask=mask,
+ skipna=skipna,
+ min_count=min_count,
+ axis=axis,
+ initial=initial,
+ )
+
+
+def prod(
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+ min_count: int = 0,
+ axis: AxisInt | None = None,
+):
+ return _reductions(
+ np.prod, values=values, mask=mask, skipna=skipna, min_count=min_count, axis=axis
+ )
+
+
+def _minmax(
+ func: Callable,
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+ axis: AxisInt | None = None,
+):
+ """
+ Reduction for 1D masked array.
+
+ Parameters
+ ----------
+ func : np.min or np.max
+ values : np.ndarray
+ Numpy array with the values (can be of any dtype that support the
+ operation).
+ mask : np.ndarray[bool]
+ Boolean numpy array (True values indicate missing values).
+ skipna : bool, default True
+ Whether to skip NA.
+ axis : int, optional, default None
+ """
+ if not skipna:
+ if mask.any() or not values.size:
+ # min/max with empty array raise in numpy, pandas returns NA
+ return libmissing.NA
+ else:
+ return func(values, axis=axis)
+ else:
+ subset = values[~mask]
+ if subset.size:
+ return func(subset, axis=axis)
+ else:
+ # min/max with empty array raise in numpy, pandas returns NA
+ return libmissing.NA
+
+
+def min(
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+ axis: AxisInt | None = None,
+):
+ return _minmax(np.min, values=values, mask=mask, skipna=skipna, axis=axis)
+
+
+def max(
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+ axis: AxisInt | None = None,
+):
+ return _minmax(np.max, values=values, mask=mask, skipna=skipna, axis=axis)
+
+
+def mean(
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+ axis: AxisInt | None = None,
+):
+ if not values.size or mask.all():
+ return libmissing.NA
+ return _reductions(np.mean, values=values, mask=mask, skipna=skipna, axis=axis)
+
+
+def var(
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+ axis: AxisInt | None = None,
+ ddof: int = 1,
+):
+ if not values.size or mask.all():
+ return libmissing.NA
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", RuntimeWarning)
+ return _reductions(
+ np.var, values=values, mask=mask, skipna=skipna, axis=axis, ddof=ddof
+ )
+
+
+def std(
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ *,
+ skipna: bool = True,
+ axis: AxisInt | None = None,
+ ddof: int = 1,
+):
+ if not values.size or mask.all():
+ return libmissing.NA
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", RuntimeWarning)
+ return _reductions(
+ np.std, values=values, mask=mask, skipna=skipna, axis=axis, ddof=ddof
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/putmask.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/putmask.py
new file mode 100644
index 0000000000000000000000000000000000000000..464a4d552af689d1f7ca68960b402e65ee9a15ed
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/putmask.py
@@ -0,0 +1,150 @@
+"""
+EA-compatible analogue to np.putmask
+"""
+
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ Any,
+)
+
+import numpy as np
+
+from pandas._libs import lib
+
+from pandas.core.dtypes.cast import infer_dtype_from
+from pandas.core.dtypes.common import is_list_like
+
+from pandas.core.arrays import ExtensionArray
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ ArrayLike,
+ npt,
+ )
+
+ from pandas import MultiIndex
+
+
+def putmask_inplace(values: ArrayLike, mask: npt.NDArray[np.bool_], value: Any) -> None:
+ """
+ ExtensionArray-compatible implementation of np.putmask. The main
+ difference is we do not handle repeating or truncating like numpy.
+
+ Parameters
+ ----------
+ values: np.ndarray or ExtensionArray
+ mask : np.ndarray[bool]
+ We assume extract_bool_array has already been called.
+ value : Any
+ """
+
+ if (
+ not isinstance(values, np.ndarray)
+ or (values.dtype == object and not lib.is_scalar(value))
+ # GH#43424: np.putmask raises TypeError if we cannot cast between types with
+ # rule = "safe", a stricter guarantee we may not have here
+ or (
+ isinstance(value, np.ndarray) and not np.can_cast(value.dtype, values.dtype)
+ )
+ ):
+ # GH#19266 using np.putmask gives unexpected results with listlike value
+ # along with object dtype
+ if is_list_like(value) and len(value) == len(values):
+ values[mask] = value[mask]
+ else:
+ values[mask] = value
+ else:
+ # GH#37833 np.putmask is more performant than __setitem__
+ np.putmask(values, mask, value)
+
+
+def putmask_without_repeat(
+ values: np.ndarray, mask: npt.NDArray[np.bool_], new: Any
+) -> None:
+ """
+ np.putmask will truncate or repeat if `new` is a listlike with
+ len(new) != len(values). We require an exact match.
+
+ Parameters
+ ----------
+ values : np.ndarray
+ mask : np.ndarray[bool]
+ new : Any
+ """
+ if getattr(new, "ndim", 0) >= 1:
+ new = new.astype(values.dtype, copy=False)
+
+ # TODO: this prob needs some better checking for 2D cases
+ nlocs = mask.sum()
+ if nlocs > 0 and is_list_like(new) and getattr(new, "ndim", 1) == 1:
+ shape = np.shape(new)
+ # np.shape compat for if setitem_datetimelike_compat
+ # changed arraylike to list e.g. test_where_dt64_2d
+ if nlocs == shape[-1]:
+ # GH#30567
+ # If length of ``new`` is less than the length of ``values``,
+ # `np.putmask` would first repeat the ``new`` array and then
+ # assign the masked values hence produces incorrect result.
+ # `np.place` on the other hand uses the ``new`` values at it is
+ # to place in the masked locations of ``values``
+ np.place(values, mask, new)
+ # i.e. values[mask] = new
+ elif mask.shape[-1] == shape[-1] or shape[-1] == 1:
+ np.putmask(values, mask, new)
+ else:
+ raise ValueError("cannot assign mismatch length to masked array")
+ else:
+ np.putmask(values, mask, new)
+
+
+def validate_putmask(
+ values: ArrayLike | MultiIndex, mask: np.ndarray
+) -> tuple[npt.NDArray[np.bool_], bool]:
+ """
+ Validate mask and check if this putmask operation is a no-op.
+ """
+ mask = extract_bool_array(mask)
+ if mask.shape != values.shape:
+ raise ValueError("putmask: mask and data must be the same size")
+
+ noop = not mask.any()
+ return mask, noop
+
+
+def extract_bool_array(mask: ArrayLike) -> npt.NDArray[np.bool_]:
+ """
+ If we have a SparseArray or BooleanArray, convert it to ndarray[bool].
+ """
+ if isinstance(mask, ExtensionArray):
+ # We could have BooleanArray, Sparse[bool], ...
+ # Except for BooleanArray, this is equivalent to just
+ # np.asarray(mask, dtype=bool)
+ mask = mask.to_numpy(dtype=bool, na_value=False)
+
+ mask = np.asarray(mask, dtype=bool)
+ return mask
+
+
+def setitem_datetimelike_compat(values: np.ndarray, num_set: int, other):
+ """
+ Parameters
+ ----------
+ values : np.ndarray
+ num_set : int
+ For putmask, this is mask.sum()
+ other : Any
+ """
+ if values.dtype == object:
+ dtype, _ = infer_dtype_from(other)
+
+ if lib.is_np_dtype(dtype, "mM"):
+ # https://github.com/numpy/numpy/issues/12550
+ # timedelta64 will incorrectly cast to int
+ if not is_list_like(other):
+ other = [other] * num_set
+ else:
+ other = list(other)
+
+ return other
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/quantile.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/quantile.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f7f356d4820cc79f5801a365362dd8b66f9701c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/quantile.py
@@ -0,0 +1,226 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from pandas.core.dtypes.missing import (
+ isna,
+ na_value_for_dtype,
+)
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ ArrayLike,
+ Scalar,
+ npt,
+ )
+
+
+def quantile_compat(
+ values: ArrayLike, qs: npt.NDArray[np.float64], interpolation: str
+) -> ArrayLike:
+ """
+ Compute the quantiles of the given values for each quantile in `qs`.
+
+ Parameters
+ ----------
+ values : np.ndarray or ExtensionArray
+ qs : np.ndarray[float64]
+ interpolation : str
+
+ Returns
+ -------
+ np.ndarray or ExtensionArray
+ """
+ if isinstance(values, np.ndarray):
+ fill_value = na_value_for_dtype(values.dtype, compat=False)
+ mask = isna(values)
+ return quantile_with_mask(values, mask, fill_value, qs, interpolation)
+ else:
+ return values._quantile(qs, interpolation)
+
+
+def quantile_with_mask(
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ fill_value,
+ qs: npt.NDArray[np.float64],
+ interpolation: str,
+) -> np.ndarray:
+ """
+ Compute the quantiles of the given values for each quantile in `qs`.
+
+ Parameters
+ ----------
+ values : np.ndarray
+ For ExtensionArray, this is _values_for_factorize()[0]
+ mask : np.ndarray[bool]
+ mask = isna(values)
+ For ExtensionArray, this is computed before calling _value_for_factorize
+ fill_value : Scalar
+ The value to interpret fill NA entries with
+ For ExtensionArray, this is _values_for_factorize()[1]
+ qs : np.ndarray[float64]
+ interpolation : str
+ Type of interpolation
+
+ Returns
+ -------
+ np.ndarray
+
+ Notes
+ -----
+ Assumes values is already 2D. For ExtensionArray this means np.atleast_2d
+ has been called on _values_for_factorize()[0]
+
+ Quantile is computed along axis=1.
+ """
+ assert values.shape == mask.shape
+ if values.ndim == 1:
+ # unsqueeze, operate, re-squeeze
+ values = np.atleast_2d(values)
+ mask = np.atleast_2d(mask)
+ res_values = quantile_with_mask(values, mask, fill_value, qs, interpolation)
+ return res_values[0]
+
+ assert values.ndim == 2
+
+ is_empty = values.shape[1] == 0
+
+ if is_empty:
+ # create the array of na_values
+ # 2d len(values) * len(qs)
+ flat = np.full(len(qs), fill_value)
+ result = np.repeat(flat, len(values)).reshape(len(values), len(qs))
+ else:
+ result = _nanquantile(
+ values,
+ qs,
+ na_value=fill_value,
+ mask=mask,
+ interpolation=interpolation,
+ )
+
+ result = np.asarray(result)
+ result = result.T
+
+ return result
+
+
+def _nanquantile_1d(
+ values: np.ndarray,
+ mask: npt.NDArray[np.bool_],
+ qs: npt.NDArray[np.float64],
+ na_value: Scalar,
+ interpolation: str,
+) -> Scalar | np.ndarray:
+ """
+ Wrapper for np.quantile that skips missing values, specialized to
+ 1-dimensional case.
+
+ Parameters
+ ----------
+ values : array over which to find quantiles
+ mask : ndarray[bool]
+ locations in values that should be considered missing
+ qs : np.ndarray[float64] of quantile indices to find
+ na_value : scalar
+ value to return for empty or all-null values
+ interpolation : str
+
+ Returns
+ -------
+ quantiles : scalar or array
+ """
+ # mask is Union[ExtensionArray, ndarray]
+ values = values[~mask]
+
+ if len(values) == 0:
+ # Can't pass dtype=values.dtype here bc we might have na_value=np.nan
+ # with values.dtype=int64 see test_quantile_empty
+ # equiv: 'np.array([na_value] * len(qs))' but much faster
+ return np.full(len(qs), na_value)
+
+ return np.quantile(
+ values,
+ qs,
+ # error: No overload variant of "percentile" matches argument
+ # types "ndarray[Any, Any]", "ndarray[Any, dtype[floating[_64Bit]]]"
+ # , "Dict[str, str]" [call-overload]
+ method=interpolation, # type: ignore[call-overload]
+ )
+
+
+def _nanquantile(
+ values: np.ndarray,
+ qs: npt.NDArray[np.float64],
+ *,
+ na_value,
+ mask: npt.NDArray[np.bool_],
+ interpolation: str,
+):
+ """
+ Wrapper for np.quantile that skips missing values.
+
+ Parameters
+ ----------
+ values : np.ndarray[ndim=2] over which to find quantiles
+ qs : np.ndarray[float64] of quantile indices to find
+ na_value : scalar
+ value to return for empty or all-null values
+ mask : np.ndarray[bool]
+ locations in values that should be considered missing
+ interpolation : str
+
+ Returns
+ -------
+ quantiles : scalar or array
+ """
+
+ if values.dtype.kind in "mM":
+ # need to cast to integer to avoid rounding errors in numpy
+ result = _nanquantile(
+ values.view("i8"),
+ qs=qs,
+ na_value=na_value.view("i8"),
+ mask=mask,
+ interpolation=interpolation,
+ )
+
+ # Note: we have to do `astype` and not view because in general we
+ # have float result at this point, not i8
+ return result.astype(values.dtype)
+
+ if mask.any():
+ # Caller is responsible for ensuring mask shape match
+ assert mask.shape == values.shape
+ result = [
+ _nanquantile_1d(val, m, qs, na_value, interpolation=interpolation)
+ for (val, m) in zip(list(values), list(mask), strict=True)
+ ]
+ if values.dtype.kind == "f":
+ # preserve itemsize
+ result = np.asarray(result, dtype=values.dtype).T
+ else:
+ result = np.asarray(result).T
+ if (
+ result.dtype != values.dtype
+ and not mask.all()
+ and (result == result.astype(values.dtype, copy=False)).all()
+ ):
+ # mask.all() will never get cast back to int
+ # e.g. values id integer dtype and result is floating dtype,
+ # only cast back to integer dtype if result values are all-integer.
+ result = result.astype(values.dtype, copy=False)
+ return result
+ else:
+ return np.quantile(
+ values,
+ qs,
+ axis=1,
+ # error: No overload variant of "percentile" matches argument types
+ # "ndarray[Any, Any]", "ndarray[Any, dtype[floating[_64Bit]]]",
+ # "int", "Dict[str, str]" [call-overload]
+ method=interpolation, # type: ignore[call-overload]
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/replace.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/replace.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c7b9b96740c9664e2d25810ab1c01798325d18c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/replace.py
@@ -0,0 +1,159 @@
+"""
+Methods used by Block.replace and related methods.
+"""
+
+from __future__ import annotations
+
+import operator
+import re
+from re import Pattern
+from typing import (
+ TYPE_CHECKING,
+ Any,
+)
+
+import numpy as np
+
+from pandas.core.dtypes.common import (
+ is_bool,
+ is_re,
+ is_re_compilable,
+)
+from pandas.core.dtypes.missing import isna
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ ArrayLike,
+ Scalar,
+ npt,
+ )
+
+
+def should_use_regex(regex: bool, to_replace: Any) -> bool:
+ """
+ Decide whether to treat `to_replace` as a regular expression.
+ """
+ if is_re(to_replace):
+ regex = True
+
+ regex = regex and is_re_compilable(to_replace)
+
+ # Don't use regex if the pattern is empty.
+ regex = regex and re.compile(to_replace).pattern != ""
+ return regex
+
+
+def compare_or_regex_search(
+ a: ArrayLike, b: Scalar | Pattern, regex: bool, mask: npt.NDArray[np.bool_]
+) -> ArrayLike:
+ """
+ Compare two array-like inputs of the same shape or two scalar values
+
+ Calls operator.eq or re.search, depending on regex argument. If regex is
+ True, perform an element-wise regex matching.
+
+ Parameters
+ ----------
+ a : array-like
+ b : scalar or regex pattern
+ regex : bool
+ mask : np.ndarray[bool]
+
+ Returns
+ -------
+ mask : array-like of bool
+ """
+ if isna(b):
+ return ~mask
+
+ def _check_comparison_types(
+ result: ArrayLike | bool, a: ArrayLike, b: Scalar | Pattern
+ ) -> None:
+ """
+ Raises an error if the two arrays (a,b) cannot be compared.
+ Otherwise, returns the comparison result as expected.
+ """
+ if is_bool(result) and isinstance(a, np.ndarray):
+ type_names = [type(a).__name__, type(b).__name__]
+
+ type_names[0] = f"ndarray(dtype={a.dtype})"
+
+ raise TypeError(
+ f"Cannot compare types {type_names[0]!r} and {type_names[1]!r}"
+ )
+
+ if not regex or not should_use_regex(regex, b):
+ # TODO: should use missing.mask_missing?
+ op = lambda x: operator.eq(x, b)
+ else:
+ op = np.vectorize(
+ lambda x: (
+ bool(re.search(b, x))
+ if isinstance(x, str) and isinstance(b, (str, Pattern))
+ else False
+ ),
+ otypes=[bool],
+ )
+
+ # GH#32621 use mask to avoid comparing to NAs
+ if isinstance(a, np.ndarray) and mask is not None:
+ a = a[mask]
+ result = op(a)
+
+ if isinstance(result, np.ndarray):
+ # The shape of the mask can differ to that of the result
+ # since we may compare only a subset of a's or b's elements
+ tmp = np.zeros(mask.shape, dtype=np.bool_)
+ np.place(tmp, mask, result)
+ result = tmp
+ else:
+ result = op(a)
+
+ _check_comparison_types(result, a, b)
+ return result
+
+
+def replace_regex(
+ values: ArrayLike, rx: re.Pattern, value, mask: npt.NDArray[np.bool_] | None
+) -> None:
+ """
+ Parameters
+ ----------
+ values : ArrayLike
+ Object dtype.
+ rx : re.Pattern
+ value : Any
+ mask : np.ndarray[bool], optional
+
+ Notes
+ -----
+ Alters values in-place.
+ """
+
+ # deal with replacing values with objects (strings) that match but
+ # whose replacement is not a string (numeric, nan, object)
+ if isna(value) or not isinstance(value, str):
+
+ def re_replacer(s):
+ if is_re(rx) and isinstance(s, str):
+ return value if rx.search(s) is not None else s
+ else:
+ return s
+
+ else:
+ # value is guaranteed to be a string here, s can be either a string
+ # or null if it's null it gets returned
+ def re_replacer(s):
+ if is_re(rx) and isinstance(s, str):
+ return rx.sub(value, s)
+ else:
+ return s
+
+ f = np.vectorize(re_replacer, otypes=[np.object_])
+
+ if mask is None:
+ values[:] = f(values)
+ else:
+ if values.ndim != mask.ndim:
+ mask = np.broadcast_to(mask, values.shape)
+ values[mask] = f(values[mask])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/take.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/take.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d33b01f616cc21ec750921801bb0b514c3ea4f8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/take.py
@@ -0,0 +1,546 @@
+from __future__ import annotations
+
+import functools
+from typing import (
+ TYPE_CHECKING,
+ cast,
+ overload,
+)
+
+import numpy as np
+
+from pandas._libs import (
+ algos as libalgos,
+ lib,
+)
+
+from pandas.core.dtypes.cast import maybe_promote
+from pandas.core.dtypes.common import (
+ ensure_platform_int,
+ is_1d_only_ea_dtype,
+)
+from pandas.core.dtypes.missing import na_value_for_dtype
+
+from pandas.core.construction import ensure_wrapped_if_datetimelike
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ ArrayLike,
+ AxisInt,
+ npt,
+ )
+
+ from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
+ from pandas.core.arrays.base import ExtensionArray
+
+
+@overload
+def take_nd(
+ arr: np.ndarray,
+ indexer,
+ axis: AxisInt = ...,
+ fill_value=...,
+ allow_fill: bool = ...,
+) -> np.ndarray: ...
+
+
+@overload
+def take_nd(
+ arr: ExtensionArray,
+ indexer,
+ axis: AxisInt = ...,
+ fill_value=...,
+ allow_fill: bool = ...,
+) -> ArrayLike: ...
+
+
+def take_nd(
+ arr: ArrayLike,
+ indexer,
+ axis: AxisInt = 0,
+ fill_value=lib.no_default,
+ allow_fill: bool = True,
+) -> ArrayLike:
+ """
+ Specialized Cython take which sets NaN values in one pass
+
+ This dispatches to ``take`` defined on ExtensionArrays.
+
+ Note: this function assumes that the indexer is a valid(ated) indexer with
+ no out of bound indices.
+
+ Parameters
+ ----------
+ arr : np.ndarray or ExtensionArray
+ Input array.
+ indexer : ndarray
+ 1-D array of indices to take, subarrays corresponding to -1 value
+ indices are filed with fill_value
+ axis : int, default 0
+ Axis to take from
+ fill_value : any, default np.nan
+ Fill value to replace -1 values with
+ allow_fill : bool, default True
+ If False, indexer is assumed to contain no -1 values so no filling
+ will be done. This short-circuits computation of a mask. Result is
+ undefined if allow_fill == False and -1 is present in indexer.
+
+ Returns
+ -------
+ subarray : np.ndarray or ExtensionArray
+ May be the same type as the input, or cast to an ndarray.
+ """
+ if fill_value is lib.no_default:
+ fill_value = na_value_for_dtype(arr.dtype, compat=False)
+ elif lib.is_np_dtype(arr.dtype, "mM"):
+ dtype, fill_value = maybe_promote(arr.dtype, fill_value)
+ if arr.dtype != dtype:
+ # EA.take is strict about returning a new object of the same type
+ # so for that case cast upfront
+ arr = arr.astype(dtype)
+
+ if not isinstance(arr, np.ndarray):
+ # i.e. ExtensionArray,
+ # includes for EA to catch DatetimeArray, TimedeltaArray
+ if not is_1d_only_ea_dtype(arr.dtype):
+ # i.e. DatetimeArray, TimedeltaArray
+ arr = cast("NDArrayBackedExtensionArray", arr)
+ return arr.take(
+ indexer, fill_value=fill_value, allow_fill=allow_fill, axis=axis
+ )
+
+ return arr.take(indexer, fill_value=fill_value, allow_fill=allow_fill)
+
+ arr = np.asarray(arr)
+ return _take_nd_ndarray(arr, indexer, axis, fill_value, allow_fill)
+
+
+def _take_nd_ndarray(
+ arr: np.ndarray,
+ indexer: npt.NDArray[np.intp] | None,
+ axis: AxisInt,
+ fill_value,
+ allow_fill: bool,
+) -> np.ndarray:
+ if indexer is None:
+ indexer = np.arange(arr.shape[axis], dtype=np.intp)
+ dtype, fill_value = arr.dtype, arr.dtype.type()
+ else:
+ indexer = ensure_platform_int(indexer)
+
+ dtype, fill_value, mask_info = _take_preprocess_indexer_and_fill_value(
+ arr, indexer, fill_value, allow_fill
+ )
+
+ flip_order = False
+ if arr.ndim == 2 and arr.flags.f_contiguous:
+ flip_order = True
+
+ if flip_order:
+ arr = arr.T
+ axis = arr.ndim - axis - 1
+
+ # at this point, it's guaranteed that dtype can hold both the arr values
+ # and the fill_value
+ out_shape_ = list(arr.shape)
+ out_shape_[axis] = len(indexer)
+ out_shape = tuple(out_shape_)
+ if arr.flags.f_contiguous and axis == arr.ndim - 1:
+ # minor tweak that can make an order-of-magnitude difference
+ # for dataframes initialized directly from 2-d ndarrays
+ # (s.t. df.values is c-contiguous and df._mgr.blocks[0] is its
+ # f-contiguous transpose)
+ out = np.empty(out_shape, dtype=dtype, order="F")
+ else:
+ out = np.empty(out_shape, dtype=dtype)
+
+ func = _get_take_nd_function(
+ arr.ndim, arr.dtype, out.dtype, axis=axis, mask_info=mask_info
+ )
+ func(arr, indexer, out, fill_value)
+
+ if flip_order:
+ out = out.T
+ return out
+
+
+def take_2d_multi(
+ arr: np.ndarray,
+ indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
+ fill_value=np.nan,
+) -> np.ndarray:
+ """
+ Specialized Cython take which sets NaN values in one pass.
+ """
+ # This is only called from one place in DataFrame._reindex_multi,
+ # so we know indexer is well-behaved.
+ assert indexer is not None
+ assert indexer[0] is not None
+ assert indexer[1] is not None
+
+ row_idx, col_idx = indexer
+
+ row_idx = ensure_platform_int(row_idx)
+ col_idx = ensure_platform_int(col_idx)
+ indexer = row_idx, col_idx
+ mask_info = None
+
+ # check for promotion based on types only (do this first because
+ # it's faster than computing a mask)
+ dtype, fill_value = maybe_promote(arr.dtype, fill_value)
+ if dtype != arr.dtype:
+ # check if promotion is actually required based on indexer
+ row_mask = row_idx == -1
+ col_mask = col_idx == -1
+ row_needs = row_mask.any()
+ col_needs = col_mask.any()
+ mask_info = (row_mask, col_mask), (row_needs, col_needs)
+
+ if not (row_needs or col_needs):
+ # if not, then depromote, set fill_value to dummy
+ # (it won't be used but we don't want the cython code
+ # to crash when trying to cast it to dtype)
+ dtype, fill_value = arr.dtype, arr.dtype.type()
+
+ # at this point, it's guaranteed that dtype can hold both the arr values
+ # and the fill_value
+ out_shape = len(row_idx), len(col_idx)
+ out = np.empty(out_shape, dtype=dtype)
+
+ func = _take_2d_multi_dict.get((arr.dtype.name, out.dtype.name), None)
+ if func is None and arr.dtype != out.dtype:
+ func = _take_2d_multi_dict.get((out.dtype.name, out.dtype.name), None)
+ if func is not None:
+ func = _convert_wrapper(func, out.dtype)
+
+ if func is not None:
+ func(arr, indexer, out=out, fill_value=fill_value)
+ else:
+ # test_reindex_multi
+ _take_2d_multi_object(
+ arr, indexer, out, fill_value=fill_value, mask_info=mask_info
+ )
+
+ return out
+
+
+@functools.lru_cache
+def _get_take_nd_function_cached(
+ ndim: int, arr_dtype: np.dtype, out_dtype: np.dtype, axis: AxisInt
+):
+ """
+ Part of _get_take_nd_function below that doesn't need `mask_info` and thus
+ can be cached (mask_info potentially contains a numpy ndarray which is not
+ hashable and thus cannot be used as argument for cached function).
+ """
+ tup = (arr_dtype.name, out_dtype.name)
+ if ndim == 1:
+ func = _take_1d_dict.get(tup, None)
+ elif ndim == 2:
+ if axis == 0:
+ func = _take_2d_axis0_dict.get(tup, None)
+ else:
+ func = _take_2d_axis1_dict.get(tup, None)
+ if func is not None:
+ return func
+
+ # We get here with string, uint, float16, and complex dtypes that could
+ # potentially be handled in algos_take_helper.
+ # Also a couple with (M8[ns], object) and (m8[ns], object)
+ tup = (out_dtype.name, out_dtype.name)
+ if ndim == 1:
+ func = _take_1d_dict.get(tup, None)
+ elif ndim == 2:
+ if axis == 0:
+ func = _take_2d_axis0_dict.get(tup, None)
+ else:
+ func = _take_2d_axis1_dict.get(tup, None)
+ if func is not None:
+ func = _convert_wrapper(func, out_dtype)
+ return func
+
+ return None
+
+
+def _get_take_nd_function(
+ ndim: int,
+ arr_dtype: np.dtype,
+ out_dtype: np.dtype,
+ axis: AxisInt = 0,
+ mask_info=None,
+):
+ """
+ Get the appropriate "take" implementation for the given dimension, axis
+ and dtypes.
+ """
+ func = None
+ if ndim <= 2:
+ # for this part we don't need `mask_info` -> use the cached algo lookup
+ func = _get_take_nd_function_cached(ndim, arr_dtype, out_dtype, axis)
+
+ if func is None:
+
+ def func(arr, indexer, out, fill_value=np.nan) -> None:
+ indexer = ensure_platform_int(indexer)
+ _take_nd_object(
+ arr, indexer, out, axis=axis, fill_value=fill_value, mask_info=mask_info
+ )
+
+ return func
+
+
+def _view_wrapper(f, arr_dtype=None, out_dtype=None, fill_wrap=None):
+ def wrapper(
+ arr: np.ndarray, indexer: np.ndarray, out: np.ndarray, fill_value=np.nan
+ ) -> None:
+ if arr_dtype is not None:
+ arr = arr.view(arr_dtype)
+ if out_dtype is not None:
+ out = out.view(out_dtype)
+ if fill_wrap is not None:
+ # FIXME: if we get here with dt64/td64 we need to be sure we have
+ # matching resos
+ if fill_value.dtype.kind == "m":
+ fill_value = fill_value.astype("m8[ns]")
+ else:
+ fill_value = fill_value.astype("M8[ns]")
+ fill_value = fill_wrap(fill_value)
+
+ f(arr, indexer, out, fill_value=fill_value)
+
+ return wrapper
+
+
+def _convert_wrapper(f, conv_dtype):
+ def wrapper(
+ arr: np.ndarray, indexer: np.ndarray, out: np.ndarray, fill_value=np.nan
+ ) -> None:
+ if conv_dtype == object:
+ # GH#39755 avoid casting dt64/td64 to integers
+ arr = ensure_wrapped_if_datetimelike(arr)
+ arr = arr.astype(conv_dtype)
+ f(arr, indexer, out, fill_value=fill_value)
+
+ return wrapper
+
+
+_take_1d_dict = {
+ ("int8", "int8"): libalgos.take_1d_int8_int8,
+ ("int8", "int32"): libalgos.take_1d_int8_int32,
+ ("int8", "int64"): libalgos.take_1d_int8_int64,
+ ("int8", "float64"): libalgos.take_1d_int8_float64,
+ ("int16", "int16"): libalgos.take_1d_int16_int16,
+ ("int16", "int32"): libalgos.take_1d_int16_int32,
+ ("int16", "int64"): libalgos.take_1d_int16_int64,
+ ("int16", "float64"): libalgos.take_1d_int16_float64,
+ ("int32", "int32"): libalgos.take_1d_int32_int32,
+ ("int32", "int64"): libalgos.take_1d_int32_int64,
+ ("int32", "float64"): libalgos.take_1d_int32_float64,
+ ("int64", "int64"): libalgos.take_1d_int64_int64,
+ ("uint8", "uint8"): libalgos.take_1d_bool_bool,
+ ("uint16", "int64"): libalgos.take_1d_uint16_uint16,
+ ("uint32", "int64"): libalgos.take_1d_uint32_uint32,
+ ("uint64", "int64"): libalgos.take_1d_uint64_uint64,
+ ("int64", "float64"): libalgos.take_1d_int64_float64,
+ ("float32", "float32"): libalgos.take_1d_float32_float32,
+ ("float32", "float64"): libalgos.take_1d_float32_float64,
+ ("float64", "float64"): libalgos.take_1d_float64_float64,
+ ("object", "object"): libalgos.take_1d_object_object,
+ ("bool", "bool"): _view_wrapper(libalgos.take_1d_bool_bool, np.uint8, np.uint8),
+ ("bool", "object"): _view_wrapper(libalgos.take_1d_bool_object, np.uint8, None),
+ ("datetime64[ns]", "datetime64[ns]"): _view_wrapper(
+ libalgos.take_1d_int64_int64, np.int64, np.int64, np.int64
+ ),
+ ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper(
+ libalgos.take_1d_int64_int64, np.int64, np.int64, np.int64
+ ),
+}
+
+_take_2d_axis0_dict = {
+ ("int8", "int8"): libalgos.take_2d_axis0_int8_int8,
+ ("int8", "int32"): libalgos.take_2d_axis0_int8_int32,
+ ("int8", "int64"): libalgos.take_2d_axis0_int8_int64,
+ ("int8", "float64"): libalgos.take_2d_axis0_int8_float64,
+ ("int16", "int16"): libalgos.take_2d_axis0_int16_int16,
+ ("int16", "int32"): libalgos.take_2d_axis0_int16_int32,
+ ("int16", "int64"): libalgos.take_2d_axis0_int16_int64,
+ ("int16", "float64"): libalgos.take_2d_axis0_int16_float64,
+ ("int32", "int32"): libalgos.take_2d_axis0_int32_int32,
+ ("int32", "int64"): libalgos.take_2d_axis0_int32_int64,
+ ("int32", "float64"): libalgos.take_2d_axis0_int32_float64,
+ ("int64", "int64"): libalgos.take_2d_axis0_int64_int64,
+ ("int64", "float64"): libalgos.take_2d_axis0_int64_float64,
+ ("uint8", "uint8"): libalgos.take_2d_axis0_bool_bool,
+ ("uint16", "uint16"): libalgos.take_2d_axis0_uint16_uint16,
+ ("uint32", "uint32"): libalgos.take_2d_axis0_uint32_uint32,
+ ("uint64", "uint64"): libalgos.take_2d_axis0_uint64_uint64,
+ ("float32", "float32"): libalgos.take_2d_axis0_float32_float32,
+ ("float32", "float64"): libalgos.take_2d_axis0_float32_float64,
+ ("float64", "float64"): libalgos.take_2d_axis0_float64_float64,
+ ("object", "object"): libalgos.take_2d_axis0_object_object,
+ ("bool", "bool"): _view_wrapper(
+ libalgos.take_2d_axis0_bool_bool, np.uint8, np.uint8
+ ),
+ ("bool", "object"): _view_wrapper(
+ libalgos.take_2d_axis0_bool_object, np.uint8, None
+ ),
+ ("datetime64[ns]", "datetime64[ns]"): _view_wrapper(
+ libalgos.take_2d_axis0_int64_int64, np.int64, np.int64, fill_wrap=np.int64
+ ),
+ ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper(
+ libalgos.take_2d_axis0_int64_int64, np.int64, np.int64, fill_wrap=np.int64
+ ),
+}
+
+_take_2d_axis1_dict = {
+ ("int8", "int8"): libalgos.take_2d_axis1_int8_int8,
+ ("int8", "int32"): libalgos.take_2d_axis1_int8_int32,
+ ("int8", "int64"): libalgos.take_2d_axis1_int8_int64,
+ ("int8", "float64"): libalgos.take_2d_axis1_int8_float64,
+ ("int16", "int16"): libalgos.take_2d_axis1_int16_int16,
+ ("int16", "int32"): libalgos.take_2d_axis1_int16_int32,
+ ("int16", "int64"): libalgos.take_2d_axis1_int16_int64,
+ ("int16", "float64"): libalgos.take_2d_axis1_int16_float64,
+ ("int32", "int32"): libalgos.take_2d_axis1_int32_int32,
+ ("int32", "int64"): libalgos.take_2d_axis1_int32_int64,
+ ("int32", "float64"): libalgos.take_2d_axis1_int32_float64,
+ ("int64", "int64"): libalgos.take_2d_axis1_int64_int64,
+ ("int64", "float64"): libalgos.take_2d_axis1_int64_float64,
+ ("uint8", "uint8"): libalgos.take_2d_axis1_bool_bool,
+ ("uint16", "uint16"): libalgos.take_2d_axis1_uint16_uint16,
+ ("uint32", "uint32"): libalgos.take_2d_axis1_uint32_uint32,
+ ("uint64", "uint64"): libalgos.take_2d_axis1_uint64_uint64,
+ ("float32", "float32"): libalgos.take_2d_axis1_float32_float32,
+ ("float32", "float64"): libalgos.take_2d_axis1_float32_float64,
+ ("float64", "float64"): libalgos.take_2d_axis1_float64_float64,
+ ("object", "object"): libalgos.take_2d_axis1_object_object,
+ ("bool", "bool"): _view_wrapper(
+ libalgos.take_2d_axis1_bool_bool, np.uint8, np.uint8
+ ),
+ ("bool", "object"): _view_wrapper(
+ libalgos.take_2d_axis1_bool_object, np.uint8, None
+ ),
+ ("datetime64[ns]", "datetime64[ns]"): _view_wrapper(
+ libalgos.take_2d_axis1_int64_int64, np.int64, np.int64, fill_wrap=np.int64
+ ),
+ ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper(
+ libalgos.take_2d_axis1_int64_int64, np.int64, np.int64, fill_wrap=np.int64
+ ),
+}
+
+_take_2d_multi_dict = {
+ ("int8", "int8"): libalgos.take_2d_multi_int8_int8,
+ ("int8", "int32"): libalgos.take_2d_multi_int8_int32,
+ ("int8", "int64"): libalgos.take_2d_multi_int8_int64,
+ ("int8", "float64"): libalgos.take_2d_multi_int8_float64,
+ ("int16", "int16"): libalgos.take_2d_multi_int16_int16,
+ ("int16", "int32"): libalgos.take_2d_multi_int16_int32,
+ ("int16", "int64"): libalgos.take_2d_multi_int16_int64,
+ ("int16", "float64"): libalgos.take_2d_multi_int16_float64,
+ ("int32", "int32"): libalgos.take_2d_multi_int32_int32,
+ ("int32", "int64"): libalgos.take_2d_multi_int32_int64,
+ ("int32", "float64"): libalgos.take_2d_multi_int32_float64,
+ ("int64", "int64"): libalgos.take_2d_multi_int64_int64,
+ ("int64", "float64"): libalgos.take_2d_multi_int64_float64,
+ ("float32", "float32"): libalgos.take_2d_multi_float32_float32,
+ ("float32", "float64"): libalgos.take_2d_multi_float32_float64,
+ ("float64", "float64"): libalgos.take_2d_multi_float64_float64,
+ ("object", "object"): libalgos.take_2d_multi_object_object,
+ ("bool", "bool"): _view_wrapper(
+ libalgos.take_2d_multi_bool_bool, np.uint8, np.uint8
+ ),
+ ("bool", "object"): _view_wrapper(
+ libalgos.take_2d_multi_bool_object, np.uint8, None
+ ),
+ ("datetime64[ns]", "datetime64[ns]"): _view_wrapper(
+ libalgos.take_2d_multi_int64_int64, np.int64, np.int64, fill_wrap=np.int64
+ ),
+ ("timedelta64[ns]", "timedelta64[ns]"): _view_wrapper(
+ libalgos.take_2d_multi_int64_int64, np.int64, np.int64, fill_wrap=np.int64
+ ),
+}
+
+
+def _take_nd_object(
+ arr: np.ndarray,
+ indexer: npt.NDArray[np.intp],
+ out: np.ndarray,
+ axis: AxisInt,
+ fill_value,
+ mask_info,
+) -> None:
+ if mask_info is not None:
+ mask, needs_masking = mask_info
+ else:
+ mask = indexer == -1
+ needs_masking = mask.any()
+ if arr.dtype != out.dtype:
+ arr = arr.astype(out.dtype)
+ if arr.shape[axis] > 0:
+ arr.take(indexer, axis=axis, out=out)
+ if needs_masking:
+ outindexer = [slice(None)] * arr.ndim
+ outindexer[axis] = mask
+ out[tuple(outindexer)] = fill_value
+
+
+def _take_2d_multi_object(
+ arr: np.ndarray,
+ indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
+ out: np.ndarray,
+ fill_value,
+ mask_info,
+) -> None:
+ # this is not ideal, performance-wise, but it's better than raising
+ # an exception (best to optimize in Cython to avoid getting here)
+ row_idx, col_idx = indexer # both np.intp
+ if mask_info is not None:
+ (row_mask, col_mask), (row_needs, col_needs) = mask_info
+ else:
+ row_mask = row_idx == -1
+ col_mask = col_idx == -1
+ row_needs = row_mask.any()
+ col_needs = col_mask.any()
+ if fill_value is not None:
+ if row_needs:
+ out[row_mask, :] = fill_value
+ if col_needs:
+ out[:, col_mask] = fill_value
+ for i, u_ in enumerate(row_idx):
+ if u_ != -1:
+ for j, v in enumerate(col_idx):
+ if v != -1:
+ out[i, j] = arr[u_, v]
+
+
+def _take_preprocess_indexer_and_fill_value(
+ arr: np.ndarray,
+ indexer: npt.NDArray[np.intp],
+ fill_value,
+ allow_fill: bool,
+ mask: npt.NDArray[np.bool_] | None = None,
+):
+ mask_info: tuple[np.ndarray | None, bool] | None = None
+
+ if not allow_fill:
+ dtype, fill_value = arr.dtype, arr.dtype.type()
+ mask_info = None, False
+ else:
+ # check for promotion based on types only (do this first because
+ # it's faster than computing a mask)
+ dtype, fill_value = maybe_promote(arr.dtype, fill_value)
+ if dtype != arr.dtype:
+ # check if promotion is actually required based on indexer
+ if mask is not None:
+ needs_masking = True
+ else:
+ mask = indexer == -1
+ needs_masking = bool(mask.any())
+ mask_info = mask, needs_masking
+ if not needs_masking:
+ # if not, then depromote, set fill_value to dummy
+ # (it won't be used but we don't want the cython code
+ # to crash when trying to cast it to dtype)
+ dtype, fill_value = arr.dtype, arr.dtype.type()
+
+ return dtype, fill_value, mask_info
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/transforms.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/transforms.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec67244949e3db92cc811b19cdfcd5d1dd2b4de8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/array_algos/transforms.py
@@ -0,0 +1,50 @@
+"""
+transforms.py is for shape-preserving functions.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ AxisInt,
+ Scalar,
+ )
+
+
+def shift(
+ values: np.ndarray, periods: int, axis: AxisInt, fill_value: Scalar
+) -> np.ndarray:
+ new_values = values
+
+ if periods == 0 or values.size == 0:
+ return new_values.copy()
+
+ # make sure array sent to np.roll is c_contiguous
+ f_ordered = values.flags.f_contiguous
+ if f_ordered:
+ new_values = new_values.T
+ axis = new_values.ndim - axis - 1
+
+ if new_values.size:
+ new_values = np.roll(
+ new_values,
+ np.intp(periods),
+ axis=axis,
+ )
+
+ axis_indexer = [slice(None)] * values.ndim
+ if periods > 0:
+ axis_indexer[axis] = slice(None, periods)
+ else:
+ axis_indexer[axis] = slice(periods, None)
+ new_values[tuple(axis_indexer)] = fill_value
+
+ # restore original order
+ if f_ordered:
+ new_values = new_values.T
+
+ return new_values
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/align.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/align.py
new file mode 100644
index 0000000000000000000000000000000000000000..16a13147cb9c8eaf0d30e682b24a82278cae76b2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/align.py
@@ -0,0 +1,227 @@
+"""
+Core eval alignment algorithms.
+"""
+
+from __future__ import annotations
+
+from functools import (
+ partial,
+ wraps,
+)
+from typing import TYPE_CHECKING
+import warnings
+
+import numpy as np
+
+from pandas._config.config import get_option
+
+from pandas.errors import PerformanceWarning
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core.dtypes.generic import (
+ ABCDataFrame,
+ ABCSeries,
+)
+
+from pandas.core.base import PandasObject
+import pandas.core.common as com
+from pandas.core.computation.common import result_type_many
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Callable,
+ Sequence,
+ )
+
+ from pandas._typing import F
+
+ from pandas.core.generic import NDFrame
+ from pandas.core.indexes.api import Index
+
+
+def _align_core_single_unary_op(
+ term,
+) -> tuple[partial | type[NDFrame], dict[str, Index] | None]:
+ typ: partial | type[NDFrame]
+ axes: dict[str, Index] | None = None
+
+ if isinstance(term.value, np.ndarray):
+ typ = partial(np.asanyarray, dtype=term.value.dtype)
+ else:
+ typ = type(term.value)
+ if hasattr(term.value, "axes"):
+ axes = _zip_axes_from_type(typ, term.value.axes)
+
+ return typ, axes
+
+
+def _zip_axes_from_type(
+ typ: type[NDFrame], new_axes: Sequence[Index]
+) -> dict[str, Index]:
+ return {name: new_axes[i] for i, name in enumerate(typ._AXIS_ORDERS)}
+
+
+def _any_pandas_objects(terms) -> bool:
+ """
+ Check a sequence of terms for instances of PandasObject.
+ """
+ return any(isinstance(term.value, PandasObject) for term in terms)
+
+
+def _filter_special_cases(f) -> Callable[[F], F]:
+ @wraps(f)
+ def wrapper(terms):
+ # single unary operand
+ if len(terms) == 1:
+ return _align_core_single_unary_op(terms[0])
+
+ term_values = (term.value for term in terms)
+
+ # we don't have any pandas objects
+ if not _any_pandas_objects(terms):
+ return result_type_many(*term_values), None
+
+ return f(terms)
+
+ return wrapper
+
+
+@_filter_special_cases
+def _align_core(terms):
+ term_index = [i for i, term in enumerate(terms) if hasattr(term.value, "axes")]
+ term_dims = [terms[i].value.ndim for i in term_index]
+
+ from pandas import Series
+
+ ndims = Series(dict(zip(term_index, term_dims, strict=True)))
+
+ # initial axes are the axes of the largest-axis'd term
+ biggest = terms[ndims.idxmax()].value
+ typ = biggest._constructor
+ axes = biggest.axes
+ naxes = len(axes)
+ gt_than_one_axis = naxes > 1
+
+ for value in (terms[i].value for i in term_index):
+ is_series = isinstance(value, ABCSeries)
+ is_series_and_gt_one_axis = is_series and gt_than_one_axis
+
+ for axis, items in enumerate(value.axes):
+ if is_series_and_gt_one_axis:
+ ax, itm = naxes - 1, value.index
+ else:
+ ax, itm = axis, items
+
+ if not axes[ax].is_(itm):
+ axes[ax] = axes[ax].union(itm)
+
+ for i, ndim in ndims.items():
+ for axis, items in zip(range(ndim), axes, strict=False):
+ ti = terms[i].value
+
+ if hasattr(ti, "reindex"):
+ transpose = isinstance(ti, ABCSeries) and naxes > 1
+ reindexer = axes[naxes - 1] if transpose else items
+
+ term_axis_size = len(ti.axes[axis])
+ reindexer_size = len(reindexer)
+
+ ordm = np.log10(max(1, abs(reindexer_size - term_axis_size)))
+ if (
+ get_option("performance_warnings")
+ and ordm >= 1
+ and reindexer_size >= 10000
+ ):
+ w = (
+ f"Alignment difference on axis {axis} is larger "
+ f"than an order of magnitude on term {terms[i].name!r}, "
+ f"by more than {ordm:.4g}; performance may suffer."
+ )
+ warnings.warn(
+ w, category=PerformanceWarning, stacklevel=find_stack_level()
+ )
+
+ obj = ti.reindex(reindexer, axis=axis)
+ terms[i].update(obj)
+
+ terms[i].update(terms[i].value.values)
+
+ return typ, _zip_axes_from_type(typ, axes)
+
+
+def align_terms(terms):
+ """
+ Align a set of terms.
+ """
+ try:
+ # flatten the parse tree (a nested list, really)
+ terms = list(com.flatten(terms))
+ except TypeError:
+ # can't iterate so it must just be a constant or single variable
+ if isinstance(terms.value, (ABCSeries, ABCDataFrame)):
+ typ = type(terms.value)
+ name = terms.value.name if isinstance(terms.value, ABCSeries) else None
+ return typ, _zip_axes_from_type(typ, terms.value.axes), name
+ return np.result_type(terms.type), None, None
+
+ # if all resolved variables are numeric scalars
+ if all(term.is_scalar for term in terms):
+ return result_type_many(*(term.value for term in terms)).type, None, None
+
+ # if all input series have a common name, propagate it to the returned series
+ names = {term.value.name for term in terms if isinstance(term.value, ABCSeries)}
+ name = names.pop() if len(names) == 1 else None
+
+ # perform the main alignment
+ typ, axes = _align_core(terms)
+ return typ, axes, name
+
+
+def reconstruct_object(typ, obj, axes, dtype, name):
+ """
+ Reconstruct an object given its type, raw value, and possibly empty
+ (None) axes.
+
+ Parameters
+ ----------
+ typ : object
+ A type
+ obj : object
+ The value to use in the type constructor
+ axes : dict
+ The axes to use to construct the resulting pandas object
+
+ Returns
+ -------
+ ret : typ
+ An object of type ``typ`` with the value `obj` and possible axes
+ `axes`.
+ """
+ try:
+ typ = typ.type
+ except AttributeError:
+ pass
+
+ res_t = np.result_type(obj.dtype, dtype)
+
+ if not isinstance(typ, partial) and issubclass(typ, PandasObject):
+ if name is None:
+ return typ(obj, dtype=res_t, **axes)
+ return typ(obj, dtype=res_t, name=name, **axes)
+
+ # special case for pathological things like ~True/~False
+ if hasattr(res_t, "type") and typ == np.bool_ and res_t != np.bool_:
+ ret_value = res_t.type(obj)
+ else:
+ ret_value = res_t.type(obj)
+ # The condition is to distinguish 0-dim array (returned in case of
+ # scalar) and 1 element array
+ # e.g. np.array(0) and np.array([0])
+ if (
+ len(obj.shape) == 1
+ and len(obj) == 1
+ and not isinstance(ret_value, np.ndarray)
+ ):
+ ret_value = np.array([ret_value]).astype(res_t)
+
+ return ret_value
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd3be5b3f8c42267c8a61421b7f0877a01b33d34
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/api.py
@@ -0,0 +1,2 @@
+__all__ = ["eval"]
+from pandas.core.computation.eval import eval
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/check.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/check.py
new file mode 100644
index 0000000000000000000000000000000000000000..caccf34f811112abbe04d965d6f6be1e21527e8b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/check.py
@@ -0,0 +1,8 @@
+from __future__ import annotations
+
+from pandas.compat._optional import import_optional_dependency
+
+ne = import_optional_dependency("numexpr", errors="warn")
+NUMEXPR_INSTALLED = ne is not None
+
+__all__ = ["NUMEXPR_INSTALLED"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2726490319a33798a42e577552056052b0876be
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/common.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from functools import reduce
+
+import numpy as np
+
+from pandas._config import get_option
+
+
+def ensure_decoded(s) -> str:
+ """
+ If we have bytes, decode them to unicode.
+ """
+ if isinstance(s, (np.bytes_, bytes)):
+ s = s.decode(get_option("display.encoding"))
+ return s
+
+
+def result_type_many(*arrays_and_dtypes):
+ """
+ Wrapper around numpy.result_type which overcomes the NPY_MAXARGS (32)
+ argument limit.
+ """
+ try:
+ return np.result_type(*arrays_and_dtypes)
+ except ValueError:
+ # we have > NPY_MAXARGS terms in our expression
+ return reduce(np.result_type, arrays_and_dtypes)
+ except TypeError:
+ from pandas.core.dtypes.cast import find_common_type
+ from pandas.core.dtypes.common import is_extension_array_dtype
+
+ arr_and_dtypes = list(arrays_and_dtypes)
+ ea_dtypes, non_ea_dtypes = [], []
+ for arr_or_dtype in arr_and_dtypes:
+ if is_extension_array_dtype(arr_or_dtype):
+ ea_dtypes.append(arr_or_dtype)
+ else:
+ non_ea_dtypes.append(arr_or_dtype)
+
+ if non_ea_dtypes:
+ try:
+ np_dtype = np.result_type(*non_ea_dtypes)
+ except ValueError:
+ np_dtype = reduce(np.result_type, arrays_and_dtypes)
+ return find_common_type([*ea_dtypes, np_dtype])
+
+ return find_common_type(ea_dtypes)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/engines.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/engines.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2a181cbb3c36df19de37d5dff665ef95af85d0c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/engines.py
@@ -0,0 +1,151 @@
+"""
+Engine classes for :func:`~pandas.eval`
+"""
+
+from __future__ import annotations
+
+import abc
+from typing import TYPE_CHECKING
+
+from pandas.errors import NumExprClobberingError
+
+from pandas.core.computation.align import (
+ align_terms,
+ reconstruct_object,
+)
+from pandas.core.computation.ops import (
+ MATHOPS,
+ REDUCTIONS,
+)
+
+from pandas.io.formats import printing
+
+if TYPE_CHECKING:
+ from pandas.core.computation.expr import Expr
+
+_ne_builtins = frozenset(MATHOPS + REDUCTIONS)
+
+
+def _check_ne_builtin_clash(expr: Expr) -> None:
+ """
+ Attempt to prevent foot-shooting in a helpful way.
+
+ Parameters
+ ----------
+ expr : Expr
+ Terms can contain
+ """
+ names = expr.names
+ overlap = names & _ne_builtins
+
+ if overlap:
+ s = ", ".join([repr(x) for x in overlap])
+ raise NumExprClobberingError(
+ f'Variables in expression "{expr}" overlap with builtins: ({s})'
+ )
+
+
+class AbstractEngine(metaclass=abc.ABCMeta):
+ """Object serving as a base class for all engines."""
+
+ has_neg_frac = False
+
+ def __init__(self, expr) -> None:
+ self.expr = expr
+ self.aligned_axes = None
+ self.result_type = None
+ self.result_name = None
+
+ def convert(self) -> str:
+ """
+ Convert an expression for evaluation.
+
+ Defaults to return the expression as a string.
+ """
+ return printing.pprint_thing(self.expr)
+
+ def evaluate(self) -> object:
+ """
+ Run the engine on the expression.
+
+ This method performs alignment which is necessary no matter what engine
+ is being used, thus its implementation is in the base class.
+
+ Returns
+ -------
+ object
+ The result of the passed expression.
+ """
+ if not self._is_aligned:
+ self.result_type, self.aligned_axes, self.result_name = align_terms(
+ self.expr.terms
+ )
+
+ # make sure no names in resolvers and locals/globals clash
+ res = self._evaluate()
+ return reconstruct_object(
+ self.result_type,
+ res,
+ self.aligned_axes,
+ self.expr.terms.return_type,
+ self.result_name,
+ )
+
+ @property
+ def _is_aligned(self) -> bool:
+ return self.aligned_axes is not None and self.result_type is not None
+
+ @abc.abstractmethod
+ def _evaluate(self):
+ """
+ Return an evaluated expression.
+
+ Parameters
+ ----------
+ env : Scope
+ The local and global environment in which to evaluate an
+ expression.
+
+ Notes
+ -----
+ Must be implemented by subclasses.
+ """
+
+
+class NumExprEngine(AbstractEngine):
+ """NumExpr engine class"""
+
+ has_neg_frac = True
+
+ def _evaluate(self):
+ import numexpr as ne
+
+ # convert the expression to a valid numexpr expression
+ s = self.convert()
+
+ env = self.expr.env
+ scope = env.full_scope
+ _check_ne_builtin_clash(self.expr)
+ return ne.evaluate(s, local_dict=scope)
+
+
+class PythonEngine(AbstractEngine):
+ """
+ Evaluate an expression in Python space.
+
+ Mostly for testing purposes.
+ """
+
+ has_neg_frac = False
+
+ def evaluate(self):
+ return self.expr()
+
+ def _evaluate(self) -> None:
+ pass
+
+
+ENGINES: dict[str, type[AbstractEngine]] = {
+ "numexpr": NumExprEngine,
+ "python": PythonEngine,
+}
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/eval.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/eval.py
new file mode 100644
index 0000000000000000000000000000000000000000..226bea6631a468850b3343b0892accc8e77d93c9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/eval.py
@@ -0,0 +1,454 @@
+"""
+Top level ``eval`` module.
+"""
+
+from __future__ import annotations
+
+import tokenize
+from typing import (
+ TYPE_CHECKING,
+ Any,
+)
+import warnings
+
+from pandas.util._decorators import set_module
+from pandas.util._exceptions import find_stack_level
+from pandas.util._validators import validate_bool_kwarg
+
+from pandas.core.dtypes.common import (
+ is_extension_array_dtype,
+ is_string_dtype,
+)
+
+from pandas.core.computation.engines import ENGINES
+from pandas.core.computation.expr import (
+ PARSERS,
+ Expr,
+)
+from pandas.core.computation.parsing import tokenize_string
+from pandas.core.computation.scope import ensure_scope
+from pandas.core.generic import NDFrame
+
+from pandas.io.formats.printing import pprint_thing
+
+if TYPE_CHECKING:
+ from pandas.core.computation.ops import BinOp
+
+
+def _check_engine(engine: str | None) -> str:
+ """
+ Make sure a valid engine is passed.
+
+ Parameters
+ ----------
+ engine : str
+ String to validate.
+
+ Raises
+ ------
+ KeyError
+ * If an invalid engine is passed.
+ ImportError
+ * If numexpr was requested but doesn't exist.
+
+ Returns
+ -------
+ str
+ Engine name.
+ """
+ from pandas.core.computation.check import NUMEXPR_INSTALLED
+ from pandas.core.computation.expressions import USE_NUMEXPR
+
+ if engine is None:
+ engine = "numexpr" if USE_NUMEXPR else "python"
+
+ if engine not in ENGINES:
+ valid_engines = list(ENGINES.keys())
+ raise KeyError(
+ f"Invalid engine '{engine}' passed, valid engines are {valid_engines}"
+ )
+
+ # TODO: validate this in a more general way (thinking of future engines
+ # that won't necessarily be import-able)
+ # Could potentially be done on engine instantiation
+ if engine == "numexpr" and not NUMEXPR_INSTALLED:
+ raise ImportError(
+ "'numexpr' is not installed or an unsupported version. Cannot use "
+ "engine='numexpr' for query/eval if 'numexpr' is not installed"
+ )
+
+ return engine
+
+
+def _check_parser(parser: str) -> None:
+ """
+ Make sure a valid parser is passed.
+
+ Parameters
+ ----------
+ parser : str
+
+ Raises
+ ------
+ KeyError
+ * If an invalid parser is passed
+ """
+ if parser not in PARSERS:
+ raise KeyError(
+ f"Invalid parser '{parser}' passed, valid parsers are {PARSERS.keys()}"
+ )
+
+
+def _check_resolvers(resolvers) -> None:
+ if resolvers is not None:
+ for resolver in resolvers:
+ if not hasattr(resolver, "__getitem__"):
+ name = type(resolver).__name__
+ raise TypeError(
+ f"Resolver of type '{name}' does not "
+ "implement the __getitem__ method"
+ )
+
+
+def _check_expression(expr) -> None:
+ """
+ Make sure an expression is not an empty string
+
+ Parameters
+ ----------
+ expr : object
+ An object that can be converted to a string
+
+ Raises
+ ------
+ ValueError
+ * If expr is an empty string
+ """
+ if not expr:
+ raise ValueError("expr cannot be an empty string")
+
+
+def _convert_expression(expr) -> str:
+ """
+ Convert an object to an expression.
+
+ This function converts an object to an expression (a unicode string) and
+ checks to make sure it isn't empty after conversion. This is used to
+ convert operators to their string representation for recursive calls to
+ :func:`~pandas.eval`.
+
+ Parameters
+ ----------
+ expr : object
+ The object to be converted to a string.
+
+ Returns
+ -------
+ str
+ The string representation of an object.
+
+ Raises
+ ------
+ ValueError
+ * If the expression is empty.
+ """
+ s = pprint_thing(expr)
+ _check_expression(s)
+ return s
+
+
+def _check_for_locals(expr: str, stack_level: int, parser: str) -> None:
+ at_top_of_stack = stack_level == 0
+ not_pandas_parser = parser != "pandas"
+
+ if not_pandas_parser:
+ msg = "The '@' prefix is only supported by the pandas parser"
+ elif at_top_of_stack:
+ msg = (
+ "The '@' prefix is not allowed in top-level eval calls.\n"
+ "please refer to your variables by name without the '@' prefix."
+ )
+
+ if at_top_of_stack or not_pandas_parser:
+ for toknum, tokval in tokenize_string(expr):
+ if toknum == tokenize.OP and tokval == "@":
+ raise SyntaxError(msg)
+
+
+@set_module("pandas")
+def eval(
+ expr: str | BinOp, # we leave BinOp out of the docstr bc it isn't for users
+ parser: str = "pandas",
+ engine: str | None = None,
+ local_dict=None,
+ global_dict=None,
+ resolvers=(),
+ level: int = 0,
+ target=None,
+ inplace: bool = False,
+) -> Any:
+ """
+ Evaluate a Python expression as a string using various backends.
+
+ .. warning::
+
+ This function can run arbitrary code which can make you vulnerable to code
+ injection if you pass user input to this function.
+
+ Parameters
+ ----------
+ expr : str
+ The expression to evaluate. This string cannot contain any Python
+ `statements
+ `__,
+ only Python `expressions
+ `__.
+
+ By default, with the numexpr engine, the following operations are supported:
+
+ - Arithmetic operations: ``+``, ``-``, ``*``, ``/``, ``**``, ``%``
+ - Boolean operations: ``|`` (or), ``&`` (and), and ``~`` (not)
+ - Comparison operators: ``<``, ``<=``, ``==``, ``!=``, ``>=``, ``>``
+
+ Furthermore, the following mathematical functions are supported:
+
+ - Trigonometric: ``sin``, ``cos``, ``tan``, ``arcsin``, ``arccos``, \
+ ``arctan``, ``arctan2``, ``sinh``, ``cosh``, ``tanh``, ``arcsinh``, \
+ ``arccosh`` and ``arctanh``
+ - Logarithms: ``log`` natural, ``log10`` base 10, ``log1p`` log(1+x)
+ - Absolute Value ``abs``
+ - Square root ``sqrt``
+ - Exponential ``exp`` and Exponential minus one ``expm1``
+
+ See the numexpr engine `documentation
+ `__
+ for further function support details.
+
+ Using the ``'python'`` engine allows the use of native Python operators
+ such as floor division ``//``, in addition to built-in and user-defined
+ Python functions.
+
+ Additionally, the ``'pandas'`` parser allows the use of :keyword:`and`,
+ :keyword:`or`, and :keyword:`not` with the same semantics as the
+ corresponding bitwise operators.
+ parser : {'pandas', 'python'}, default 'pandas'
+ The parser to use to construct the syntax tree from the expression. The
+ default of ``'pandas'`` parses code slightly different than standard
+ Python. Alternatively, you can parse an expression using the
+ ``'python'`` parser to retain strict Python semantics. See the
+ :ref:`enhancing performance ` documentation for
+ more details.
+ engine : {'python', 'numexpr'}, optional, default None
+
+ The engine used to evaluate the expression. Supported engines are
+
+ - None : tries to use ``numexpr``, falls back to ``python``
+ - ``'numexpr'`` : This is the default engine when ``numexpr`` is installed.
+ Evaluates pandas objects using numexpr for large speed ups in complex
+ expressions with large frames.
+ - ``'python'`` : Performs operations as if you had ``eval``'d in top
+ level python. This engine is generally not that useful.
+
+ More backends may be available in the future.
+ local_dict : dict or None, optional
+ A dictionary of local variables, taken from locals() by default.
+ global_dict : dict or None, optional
+ A dictionary of global variables, taken from globals() by default.
+ resolvers : list of dict-like or None, optional
+ A list of objects implementing the ``__getitem__`` special method that
+ you can use to inject an additional collection of namespaces to use for
+ variable lookup. For example, this is used in the
+ :meth:`~DataFrame.query` method to inject the
+ ``DataFrame.index`` and ``DataFrame.columns``
+ variables that refer to their respective :class:`~pandas.DataFrame`
+ instance attributes.
+ level : int, optional
+ The number of prior stack frames to traverse and add to the current
+ scope. Most users will **not** need to change this parameter.
+ target : object, optional, default None
+ This is the target object for assignment. It is used when there is
+ variable assignment in the expression. If so, then `target` must
+ support item assignment with string keys, and if a copy is being
+ returned, it must also support `.copy()`.
+ inplace : bool, default False
+ If `target` is provided, and the expression mutates `target`, whether
+ to modify `target` inplace. Otherwise, return a copy of `target` with
+ the mutation.
+
+ Returns
+ -------
+ ndarray, numeric scalar, DataFrame, Series, or None
+ The completion value of evaluating the given code or None if ``inplace=True``.
+
+ Raises
+ ------
+ ValueError
+ There are many instances where such an error can be raised:
+
+ - `target=None`, but the expression is multiline.
+ - The expression is multiline, but not all them have item assignment.
+ An example of such an arrangement is this:
+
+ a = b + 1
+ a + 2
+
+ Here, there are expressions on different lines, making it multiline,
+ but the last line has no variable assigned to the output of `a + 2`.
+ - `inplace=True`, but the expression is missing item assignment.
+ - Item assignment is provided, but the `target` does not support
+ string item assignment.
+ - Item assignment is provided and `inplace=False`, but the `target`
+ does not support the `.copy()` method
+
+ See Also
+ --------
+ DataFrame.query : Evaluates a boolean expression to query the columns
+ of a frame.
+ DataFrame.eval : Evaluate a string describing operations on
+ DataFrame columns.
+
+ Notes
+ -----
+ The ``dtype`` of any objects involved in an arithmetic ``%`` operation are
+ recursively cast to ``float64``.
+
+ See the :ref:`enhancing performance ` documentation for
+ more details.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame({"animal": ["dog", "pig"], "age": [10, 20]})
+ >>> df
+ animal age
+ 0 dog 10
+ 1 pig 20
+
+ We can add a new column using ``pd.eval``:
+
+ >>> pd.eval("double_age = df.age * 2", target=df)
+ animal age double_age
+ 0 dog 10 20
+ 1 pig 20 40
+ """
+ inplace = validate_bool_kwarg(inplace, "inplace")
+
+ exprs: list[str | BinOp]
+ if isinstance(expr, str):
+ _check_expression(expr)
+ exprs = [e.strip() for e in expr.splitlines() if e.strip() != ""]
+ else:
+ # ops.BinOp; for internal compat, not intended to be passed by users
+ exprs = [expr]
+ multi_line = len(exprs) > 1
+
+ if multi_line and target is None:
+ raise ValueError(
+ "multi-line expressions are only valid in the "
+ "context of data, use DataFrame.eval"
+ )
+ engine = _check_engine(engine)
+ _check_parser(parser)
+ _check_resolvers(resolvers)
+
+ ret = None
+ first_expr = True
+ target_modified = False
+
+ for expr in exprs:
+ expr = _convert_expression(expr)
+ _check_for_locals(expr, level, parser)
+
+ # get our (possibly passed-in) scope
+ env = ensure_scope(
+ level + 1,
+ global_dict=global_dict,
+ local_dict=local_dict,
+ resolvers=resolvers,
+ target=target,
+ )
+
+ parsed_expr = Expr(expr, engine=engine, parser=parser, env=env)
+
+ if engine == "numexpr" and (
+ (
+ is_extension_array_dtype(parsed_expr.terms.return_type)
+ and not is_string_dtype(parsed_expr.terms.return_type)
+ )
+ or (
+ getattr(parsed_expr.terms, "operand_types", None) is not None
+ and any(
+ (is_extension_array_dtype(elem) and not is_string_dtype(elem))
+ for elem in parsed_expr.terms.operand_types
+ )
+ )
+ ):
+ warnings.warn(
+ "Engine has switched to 'python' because numexpr does not support "
+ "extension array dtypes. Please set your engine to python manually.",
+ RuntimeWarning,
+ stacklevel=find_stack_level(),
+ )
+ engine = "python"
+
+ # construct the engine and evaluate the parsed expression
+ eng = ENGINES[engine]
+ eng_inst = eng(parsed_expr)
+ ret = eng_inst.evaluate()
+
+ if parsed_expr.assigner is None:
+ if multi_line:
+ raise ValueError(
+ "Multi-line expressions are only valid "
+ "if all expressions contain an assignment"
+ )
+ if inplace:
+ raise ValueError("Cannot operate inplace if there is no assignment")
+
+ # assign if needed
+ assigner = parsed_expr.assigner
+ if env.target is not None and assigner is not None:
+ target_modified = True
+
+ # if returning a copy, copy only on the first assignment
+ if not inplace and first_expr:
+ try:
+ target = env.target
+ if isinstance(target, NDFrame):
+ target = target.copy(deep=False)
+ else:
+ target = target.copy()
+ except AttributeError as err:
+ raise ValueError("Cannot return a copy of the target") from err
+ else:
+ target = env.target
+
+ # TypeError is most commonly raised (e.g. int, list), but you
+ # get IndexError if you try to do this assignment on np.ndarray.
+ # we will ignore numpy warnings here; e.g. if trying
+ # to use a non-numeric indexer
+ try:
+ if inplace and isinstance(target, NDFrame):
+ target.loc[:, assigner] = ret
+ else:
+ target[assigner] = ret # pyright: ignore[reportIndexIssue]
+ except (TypeError, IndexError) as err:
+ raise ValueError("Cannot assign expression output to target") from err
+
+ if not resolvers:
+ resolvers = ({assigner: ret},)
+ else:
+ # existing resolver needs updated to handle
+ # case of mutating existing column in copy
+ for resolver in resolvers:
+ if assigner in resolver:
+ resolver[assigner] = ret
+ break
+ else:
+ resolvers += ({assigner: ret},)
+
+ ret = None
+ first_expr = False
+
+ # We want to exclude `inplace=None` as being False.
+ if inplace is False:
+ return target if target_modified else ret
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/expr.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/expr.py
new file mode 100644
index 0000000000000000000000000000000000000000..2574c2fcf616bf4a6d7f9a07793d82d9e6fe5f0a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/expr.py
@@ -0,0 +1,850 @@
+"""
+:func:`~pandas.eval` parsers.
+"""
+
+from __future__ import annotations
+
+import ast
+from functools import (
+ partial,
+ reduce,
+)
+from keyword import iskeyword
+import tokenize
+from typing import (
+ TYPE_CHECKING,
+ ClassVar,
+ TypeVar,
+)
+
+import numpy as np
+
+from pandas.errors import UndefinedVariableError
+
+from pandas.core.dtypes.common import is_string_dtype
+
+import pandas.core.common as com
+from pandas.core.computation.ops import (
+ ARITH_OPS_SYMS,
+ BOOL_OPS_SYMS,
+ CMP_OPS_SYMS,
+ LOCAL_TAG,
+ MATHOPS,
+ REDUCTIONS,
+ UNARY_OPS_SYMS,
+ BinOp,
+ Constant,
+ FuncNode,
+ Op,
+ Term,
+ UnaryOp,
+ is_term,
+)
+from pandas.core.computation.parsing import (
+ clean_backtick_quoted_toks,
+ tokenize_string,
+)
+from pandas.core.computation.scope import Scope
+
+from pandas.io.formats import printing
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+
+def _rewrite_assign(tok: tuple[int, str]) -> tuple[int, str]:
+ """
+ Rewrite the assignment operator for PyTables expressions that use ``=``
+ as a substitute for ``==``.
+
+ Parameters
+ ----------
+ tok : tuple of int, str
+ ints correspond to the all caps constants in the tokenize module
+
+ Returns
+ -------
+ tuple of int, str
+ Either the input or token or the replacement values
+ """
+ toknum, tokval = tok
+ return toknum, "==" if tokval == "=" else tokval
+
+
+def _replace_booleans(tok: tuple[int, str]) -> tuple[int, str]:
+ """
+ Replace ``&`` with ``and`` and ``|`` with ``or`` so that bitwise
+ precedence is changed to boolean precedence.
+
+ Parameters
+ ----------
+ tok : tuple of int, str
+ ints correspond to the all caps constants in the tokenize module
+
+ Returns
+ -------
+ tuple of int, str
+ Either the input or token or the replacement values
+ """
+ toknum, tokval = tok
+ if toknum == tokenize.OP:
+ if tokval == "&":
+ return tokenize.NAME, "and"
+ elif tokval == "|":
+ return tokenize.NAME, "or"
+ return toknum, tokval
+ return toknum, tokval
+
+
+def _replace_locals(tok: tuple[int, str]) -> tuple[int, str]:
+ """
+ Replace local variables with a syntactically valid name.
+
+ Parameters
+ ----------
+ tok : tuple of int, str
+ ints correspond to the all caps constants in the tokenize module
+
+ Returns
+ -------
+ tuple of int, str
+ Either the input or token or the replacement values
+
+ Notes
+ -----
+ This is somewhat of a hack in that we rewrite a string such as ``'@a'`` as
+ ``'__pd_eval_local_a'`` by telling the tokenizer that ``__pd_eval_local_``
+ is a ``tokenize.OP`` and to replace the ``'@'`` symbol with it.
+ """
+ toknum, tokval = tok
+ if toknum == tokenize.OP and tokval == "@":
+ return tokenize.OP, LOCAL_TAG
+ return toknum, tokval
+
+
+def _compose2(f, g):
+ """
+ Compose 2 callables.
+ """
+ return lambda *args, **kwargs: f(g(*args, **kwargs))
+
+
+def _compose(*funcs):
+ """
+ Compose 2 or more callables.
+ """
+ assert len(funcs) > 1, "At least 2 callables must be passed to compose"
+ return reduce(_compose2, funcs)
+
+
+def _preparse(
+ source: str,
+ f=_compose(
+ _replace_locals, _replace_booleans, _rewrite_assign, clean_backtick_quoted_toks
+ ),
+) -> str:
+ """
+ Compose a collection of tokenization functions.
+
+ Parameters
+ ----------
+ source : str
+ A Python source code string
+ f : callable
+ This takes a tuple of (toknum, tokval) as its argument and returns a
+ tuple with the same structure but possibly different elements. Defaults
+ to the composition of ``_rewrite_assign``, ``_replace_booleans``, and
+ ``_replace_locals``.
+
+ Returns
+ -------
+ str
+ Valid Python source code
+
+ Notes
+ -----
+ The `f` parameter can be any callable that takes *and* returns input of the
+ form ``(toknum, tokval)``, where ``toknum`` is one of the constants from
+ the ``tokenize`` module and ``tokval`` is a string.
+ """
+ assert callable(f), "f must be callable"
+ return tokenize.untokenize(
+ f(x)
+ for x in tokenize_string(source) # pyright: ignore[reportArgumentType]
+ )
+
+
+def _is_type(t):
+ """
+ Factory for a type checking function of type ``t`` or tuple of types.
+ """
+ return lambda x: isinstance(x.value, t)
+
+
+_is_list = _is_type(list)
+_is_str = _is_type(str)
+
+
+# partition all AST nodes
+_all_nodes = frozenset(
+ node
+ for node in (getattr(ast, name) for name in dir(ast))
+ if isinstance(node, type) and issubclass(node, ast.AST)
+)
+
+
+def _filter_nodes(superclass, all_nodes=_all_nodes):
+ """
+ Filter out AST nodes that are subclasses of ``superclass``.
+ """
+ node_names = (node.__name__ for node in all_nodes if issubclass(node, superclass))
+ return frozenset(node_names)
+
+
+_all_node_names = frozenset(x.__name__ for x in _all_nodes)
+_mod_nodes = _filter_nodes(ast.mod)
+_stmt_nodes = _filter_nodes(ast.stmt)
+_expr_nodes = _filter_nodes(ast.expr)
+_expr_context_nodes = _filter_nodes(ast.expr_context)
+_boolop_nodes = _filter_nodes(ast.boolop)
+_operator_nodes = _filter_nodes(ast.operator)
+_unary_op_nodes = _filter_nodes(ast.unaryop)
+_cmp_op_nodes = _filter_nodes(ast.cmpop)
+_comprehension_nodes = _filter_nodes(ast.comprehension)
+_handler_nodes = _filter_nodes(ast.excepthandler)
+_arguments_nodes = _filter_nodes(ast.arguments)
+_keyword_nodes = _filter_nodes(ast.keyword)
+_alias_nodes = _filter_nodes(ast.alias)
+
+
+# nodes that we don't support directly but are needed for parsing
+_hacked_nodes = frozenset(["Assign", "Module", "Expr"])
+
+
+_unsupported_expr_nodes = frozenset(
+ [
+ "Yield",
+ "GeneratorExp",
+ "IfExp",
+ "DictComp",
+ "SetComp",
+ "Repr",
+ "Lambda",
+ "Set",
+ "AST",
+ "Is",
+ "IsNot",
+ ]
+)
+
+# these nodes are low priority or won't ever be supported (e.g., AST)
+_unsupported_nodes = (
+ _stmt_nodes
+ | _mod_nodes
+ | _handler_nodes
+ | _arguments_nodes
+ | _keyword_nodes
+ | _alias_nodes
+ | _expr_context_nodes
+ | _unsupported_expr_nodes
+) - _hacked_nodes
+
+# we're adding a different assignment in some cases to be equality comparison
+# and we don't want `stmt` and friends in their so get only the class whose
+# names are capitalized
+_base_supported_nodes = (_all_node_names - _unsupported_nodes) | _hacked_nodes
+intersection = _unsupported_nodes & _base_supported_nodes
+_msg = f"cannot both support and not support {intersection}"
+assert not intersection, _msg
+
+
+def _node_not_implemented(node_name: str) -> Callable[..., None]:
+ """
+ Return a function that raises a NotImplementedError with a passed node name.
+ """
+
+ def f(self, *args, **kwargs):
+ raise NotImplementedError(f"'{node_name}' nodes are not implemented")
+
+ return f
+
+
+# should be bound by BaseExprVisitor but that creates a circular dependency:
+# _T is used in disallow, but disallow is used to define BaseExprVisitor
+# https://github.com/microsoft/pyright/issues/2315
+_T = TypeVar("_T")
+
+
+def disallow(nodes: set[str]) -> Callable[[type[_T]], type[_T]]:
+ """
+ Decorator to disallow certain nodes from parsing. Raises a
+ NotImplementedError instead.
+
+ Returns
+ -------
+ callable
+ """
+
+ def disallowed(cls: type[_T]) -> type[_T]:
+ # error: "Type[_T]" has no attribute "unsupported_nodes"
+ cls.unsupported_nodes = () # type: ignore[attr-defined]
+ for node in nodes:
+ new_method = _node_not_implemented(node)
+ name = f"visit_{node}"
+ # error: "Type[_T]" has no attribute "unsupported_nodes"
+ cls.unsupported_nodes += (name,) # type: ignore[attr-defined]
+ setattr(cls, name, new_method)
+ return cls
+
+ return disallowed
+
+
+def _op_maker(op_class, op_symbol):
+ """
+ Return a function to create an op class with its symbol already passed.
+
+ Returns
+ -------
+ callable
+ """
+
+ def f(self, node, *args, **kwargs):
+ """
+ Return a partial function with an Op subclass with an operator already passed.
+
+ Returns
+ -------
+ callable
+ """
+ return partial(op_class, op_symbol, *args, **kwargs)
+
+ return f
+
+
+_op_classes = {"binary": BinOp, "unary": UnaryOp}
+
+
+def add_ops(op_classes):
+ """
+ Decorator to add default implementation of ops.
+ """
+
+ def f(cls):
+ for op_attr_name, op_class in op_classes.items():
+ ops = getattr(cls, f"{op_attr_name}_ops")
+ ops_map = getattr(cls, f"{op_attr_name}_op_nodes_map")
+ for op in ops:
+ op_node = ops_map[op]
+ if op_node is not None:
+ made_op = _op_maker(op_class, op)
+ setattr(cls, f"visit_{op_node}", made_op)
+ return cls
+
+ return f
+
+
+@disallow(_unsupported_nodes)
+@add_ops(_op_classes)
+class BaseExprVisitor(ast.NodeVisitor):
+ """
+ Custom ast walker. Parsers of other engines should subclass this class
+ if necessary.
+
+ Parameters
+ ----------
+ env : Scope
+ engine : str
+ parser : str
+ preparser : callable
+ """
+
+ const_type: ClassVar[type[Term]] = Constant
+ term_type: ClassVar[type[Term]] = Term
+
+ binary_ops = CMP_OPS_SYMS + BOOL_OPS_SYMS + ARITH_OPS_SYMS
+ binary_op_nodes = (
+ "Gt",
+ "Lt",
+ "GtE",
+ "LtE",
+ "Eq",
+ "NotEq",
+ "In",
+ "NotIn",
+ "BitAnd",
+ "BitOr",
+ "And",
+ "Or",
+ "Add",
+ "Sub",
+ "Mult",
+ "Div",
+ "Pow",
+ "FloorDiv",
+ "Mod",
+ )
+ binary_op_nodes_map = dict(zip(binary_ops, binary_op_nodes, strict=True))
+
+ unary_ops = UNARY_OPS_SYMS
+ unary_op_nodes = "UAdd", "USub", "Invert", "Not"
+ unary_op_nodes_map = dict(zip(unary_ops, unary_op_nodes, strict=True))
+
+ rewrite_map = {
+ ast.Eq: ast.In,
+ ast.NotEq: ast.NotIn,
+ ast.In: ast.In,
+ ast.NotIn: ast.NotIn,
+ }
+
+ unsupported_nodes: tuple[str, ...]
+
+ def __init__(self, env, engine, parser, preparser=_preparse) -> None:
+ self.env = env
+ self.engine = engine
+ self.parser = parser
+ self.preparser = preparser
+ self.assigner = None
+
+ def visit(self, node, **kwargs):
+ if isinstance(node, str):
+ clean = self.preparser(node)
+ try:
+ node = ast.fix_missing_locations(ast.parse(clean))
+ except SyntaxError as e:
+ if any(iskeyword(x) for x in clean.split()):
+ e.msg = "Python keyword not valid identifier in numexpr query"
+ raise e
+
+ method = f"visit_{type(node).__name__}"
+ visitor = getattr(self, method)
+ return visitor(node, **kwargs)
+
+ def visit_Module(self, node, **kwargs):
+ if len(node.body) != 1:
+ raise SyntaxError("only a single expression is allowed")
+ expr = node.body[0]
+ return self.visit(expr, **kwargs)
+
+ def visit_Expr(self, node, **kwargs):
+ return self.visit(node.value, **kwargs)
+
+ def _rewrite_membership_op(self, node, left, right):
+ # the kind of the operator (is actually an instance)
+ op_instance = node.op
+ op_type = type(op_instance)
+
+ # must be two terms and the comparison operator must be ==/!=/in/not in
+ if is_term(left) and is_term(right) and op_type in self.rewrite_map:
+ left_list, right_list = map(_is_list, (left, right))
+ left_str, right_str = map(_is_str, (left, right))
+
+ # if there are any strings or lists in the expression
+ if left_list or right_list or left_str or right_str:
+ op_instance = self.rewrite_map[op_type]()
+
+ # pop the string variable out of locals and replace it with a list
+ # of one string, kind of a hack
+ if right_str:
+ name = self.env.add_tmp([right.value])
+ right = self.term_type(name, self.env)
+
+ if left_str:
+ name = self.env.add_tmp([left.value])
+ left = self.term_type(name, self.env)
+
+ op = self.visit(op_instance)
+ return op, op_instance, left, right
+
+ def _maybe_transform_eq_ne(self, node, left=None, right=None):
+ if left is None:
+ left = self.visit(node.left, side="left")
+ if right is None:
+ right = self.visit(node.right, side="right")
+ op, op_class, left, right = self._rewrite_membership_op(node, left, right)
+ return op, op_class, left, right
+
+ def _maybe_downcast_constants(self, left, right):
+ f32 = np.dtype(np.float32)
+ if (
+ left.is_scalar
+ and hasattr(left, "value")
+ and not right.is_scalar
+ and right.return_type == f32
+ ):
+ # right is a float32 array, left is a scalar
+ name = self.env.add_tmp(np.float32(left.value))
+ left = self.term_type(name, self.env)
+ if (
+ right.is_scalar
+ and hasattr(right, "value")
+ and not left.is_scalar
+ and left.return_type == f32
+ ):
+ # left is a float32 array, right is a scalar
+ name = self.env.add_tmp(np.float32(right.value))
+ right = self.term_type(name, self.env)
+
+ return left, right
+
+ def _maybe_eval(self, binop, eval_in_python):
+ # eval `in` and `not in` (for now) in "partial" python space
+ # things that can be evaluated in "eval" space will be turned into
+ # temporary variables. for example,
+ # [1,2] in a + 2 * b
+ # in that case a + 2 * b will be evaluated using numexpr, and the "in"
+ # call will be evaluated using isin (in python space)
+ return binop.evaluate(
+ self.env, self.engine, self.parser, self.term_type, eval_in_python
+ )
+
+ def _maybe_evaluate_binop(
+ self,
+ op,
+ op_class,
+ lhs,
+ rhs,
+ eval_in_python=("in", "not in"),
+ maybe_eval_in_python=("==", "!=", "<", ">", "<=", ">="),
+ ):
+ res = op(lhs, rhs)
+
+ if res.has_invalid_return_type:
+ raise TypeError(
+ f"unsupported operand type(s) for {res.op}: "
+ f"'{lhs.type}' and '{rhs.type}'"
+ )
+
+ if self.engine != "pytables" and (
+ (res.op in CMP_OPS_SYMS and getattr(lhs, "is_datetime", False))
+ or getattr(rhs, "is_datetime", False)
+ ):
+ # all date ops must be done in python bc numexpr doesn't work
+ # well with NaT
+ return self._maybe_eval(res, self.binary_ops)
+
+ if res.op in eval_in_python:
+ # "in"/"not in" ops are always evaluated in python
+ return self._maybe_eval(res, eval_in_python)
+ elif self.engine != "pytables":
+ if (
+ getattr(lhs, "return_type", None) == object
+ or is_string_dtype(getattr(lhs, "return_type", None))
+ or getattr(rhs, "return_type", None) == object
+ or is_string_dtype(getattr(rhs, "return_type", None))
+ ):
+ # evaluate "==" and "!=" in python if either of our operands
+ # has an object or string return type
+ return self._maybe_eval(res, eval_in_python + maybe_eval_in_python)
+ return res
+
+ def visit_BinOp(self, node, **kwargs):
+ op, op_class, left, right = self._maybe_transform_eq_ne(node)
+ left, right = self._maybe_downcast_constants(left, right)
+ return self._maybe_evaluate_binop(op, op_class, left, right)
+
+ def visit_UnaryOp(self, node, **kwargs):
+ op = self.visit(node.op)
+ operand = self.visit(node.operand)
+ return op(operand)
+
+ def visit_Name(self, node, **kwargs) -> Term:
+ return self.term_type(node.id, self.env, **kwargs)
+
+ # TODO(py314): deprecated since Python 3.8. Remove after Python 3.14 is min
+ def visit_NameConstant(self, node, **kwargs) -> Term:
+ return self.const_type(node.value, self.env)
+
+ # TODO(py314): deprecated since Python 3.8. Remove after Python 3.14 is min
+ def visit_Num(self, node, **kwargs) -> Term:
+ return self.const_type(node.value, self.env)
+
+ def visit_Constant(self, node, **kwargs) -> Term:
+ return self.const_type(node.value, self.env)
+
+ # TODO(py314): deprecated since Python 3.8. Remove after Python 3.14 is min
+ def visit_Str(self, node, **kwargs) -> Term:
+ name = self.env.add_tmp(node.s)
+ return self.term_type(name, self.env)
+
+ def visit_List(self, node, **kwargs) -> Term:
+ name = self.env.add_tmp([self.visit(e)(self.env) for e in node.elts])
+ return self.term_type(name, self.env)
+
+ visit_Tuple = visit_List
+
+ def visit_Index(self, node, **kwargs):
+ """df.index[4]"""
+ return self.visit(node.value)
+
+ def visit_Subscript(self, node, **kwargs) -> Term:
+ from pandas import eval as pd_eval
+
+ value = self.visit(node.value)
+ slobj = self.visit(node.slice)
+ result = pd_eval(
+ slobj, local_dict=self.env, engine=self.engine, parser=self.parser
+ )
+ try:
+ # a Term instance
+ v = value.value[result]
+ except AttributeError:
+ # an Op instance
+ lhs = pd_eval(
+ value, local_dict=self.env, engine=self.engine, parser=self.parser
+ )
+ v = lhs[result]
+ name = self.env.add_tmp(v)
+ return self.term_type(name, env=self.env)
+
+ def visit_Slice(self, node, **kwargs) -> slice:
+ """df.index[slice(4,6)]"""
+ lower = node.lower
+ if lower is not None:
+ lower = self.visit(lower).value
+ upper = node.upper
+ if upper is not None:
+ upper = self.visit(upper).value
+ step = node.step
+ if step is not None:
+ step = self.visit(step).value
+
+ return slice(lower, upper, step)
+
+ def visit_Assign(self, node, **kwargs):
+ """
+ support a single assignment node, like
+
+ c = a + b
+
+ set the assigner at the top level, must be a Name node which
+ might or might not exist in the resolvers
+
+ """
+ if len(node.targets) != 1:
+ raise SyntaxError("can only assign a single expression")
+ if not isinstance(node.targets[0], ast.Name):
+ raise SyntaxError("left hand side of an assignment must be a single name")
+ if self.env.target is None:
+ raise ValueError("cannot assign without a target object")
+
+ try:
+ assigner = self.visit(node.targets[0], **kwargs)
+ except UndefinedVariableError:
+ assigner = node.targets[0].id
+
+ self.assigner = getattr(assigner, "name", assigner)
+ if self.assigner is None:
+ raise SyntaxError(
+ "left hand side of an assignment must be a single resolvable name"
+ )
+
+ return self.visit(node.value, **kwargs)
+
+ def visit_Attribute(self, node, **kwargs):
+ attr = node.attr
+ value = node.value
+
+ ctx = node.ctx
+ if isinstance(ctx, ast.Load):
+ # resolve the value
+ visited_value = self.visit(value)
+ if hasattr(visited_value, "value"):
+ resolved = visited_value.value
+ else:
+ resolved = visited_value(self.env)
+ try:
+ v = getattr(resolved, attr)
+ name = self.env.add_tmp(v)
+ return self.term_type(name, self.env)
+ except AttributeError:
+ # something like datetime.datetime where scope is overridden
+ if isinstance(value, ast.Name) and value.id == attr:
+ return resolved
+ raise
+
+ raise ValueError(f"Invalid Attribute context {type(ctx).__name__}")
+
+ def visit_Call(self, node, side=None, **kwargs):
+ if isinstance(node.func, ast.Attribute) and node.func.attr != "__call__":
+ res = self.visit_Attribute(node.func)
+ elif not isinstance(node.func, ast.Name):
+ raise TypeError("Only named functions are supported")
+ else:
+ try:
+ res = self.visit(node.func)
+ except UndefinedVariableError:
+ # Check if this is a supported function name
+ try:
+ res = FuncNode(node.func.id)
+ except ValueError:
+ # Raise original error
+ raise
+
+ if res is None:
+ # error: "expr" has no attribute "id"
+ raise ValueError(
+ f"Invalid function call {node.func.id}" # type: ignore[union-attr]
+ )
+ if hasattr(res, "value"):
+ res = res.value
+
+ if isinstance(res, FuncNode):
+ new_args = [self.visit(arg) for arg in node.args]
+
+ if node.keywords:
+ raise TypeError(
+ f'Function "{res.name}" does not support keyword arguments'
+ )
+
+ return res(*new_args)
+
+ else:
+ new_args = [self.visit(arg)(self.env) for arg in node.args]
+
+ for key in node.keywords:
+ if not isinstance(key, ast.keyword):
+ # error: Item "Attribute" of "Attribute | Name" has no
+ # attribute "id"
+ raise ValueError(
+ f"keyword error in function call '{node.func.id}'" # type: ignore[union-attr]
+ )
+
+ if key.arg:
+ kwargs[key.arg] = self.visit(key.value)(self.env)
+
+ name = self.env.add_tmp(res(*new_args, **kwargs))
+ return self.term_type(name=name, env=self.env)
+
+ def translate_In(self, op):
+ return op
+
+ def visit_Compare(self, node, **kwargs):
+ ops = node.ops
+ comps = node.comparators
+
+ # base case: we have something like a CMP b
+ if len(comps) == 1:
+ op = self.translate_In(ops[0])
+ binop = ast.BinOp(op=op, left=node.left, right=comps[0])
+ return self.visit(binop)
+
+ # recursive case: we have a chained comparison, a CMP b CMP c, etc.
+ left = node.left
+ values = []
+ for op, comp in zip(ops, comps, strict=True):
+ new_node = self.visit(
+ ast.Compare(comparators=[comp], left=left, ops=[self.translate_In(op)])
+ )
+ left = comp
+ values.append(new_node)
+ return self.visit(ast.BoolOp(op=ast.And(), values=values))
+
+ def _try_visit_binop(self, bop):
+ if isinstance(bop, (Op, Term)):
+ return bop
+ return self.visit(bop)
+
+ def visit_BoolOp(self, node, **kwargs):
+ def visitor(x, y):
+ lhs = self._try_visit_binop(x)
+ rhs = self._try_visit_binop(y)
+
+ op, op_class, lhs, rhs = self._maybe_transform_eq_ne(node, lhs, rhs)
+ return self._maybe_evaluate_binop(op, node.op, lhs, rhs)
+
+ operands = node.values
+ return reduce(visitor, operands)
+
+
+_python_not_supported = frozenset(["Dict", "BoolOp", "In", "NotIn"])
+_numexpr_supported_calls = frozenset(REDUCTIONS + MATHOPS)
+
+
+@disallow(
+ (_unsupported_nodes | _python_not_supported)
+ - (_boolop_nodes | frozenset(["BoolOp", "Attribute", "In", "NotIn", "Tuple"]))
+)
+class PandasExprVisitor(BaseExprVisitor):
+ def __init__(
+ self,
+ env,
+ engine,
+ parser,
+ preparser=partial(
+ _preparse,
+ f=_compose(_replace_locals, _replace_booleans, clean_backtick_quoted_toks),
+ ),
+ ) -> None:
+ super().__init__(env, engine, parser, preparser)
+
+
+@disallow(_unsupported_nodes | _python_not_supported | frozenset(["Not"]))
+class PythonExprVisitor(BaseExprVisitor):
+ def __init__(
+ self, env, engine, parser, preparser=lambda source, f=None: source
+ ) -> None:
+ super().__init__(env, engine, parser, preparser=preparser)
+
+
+class Expr:
+ """
+ Object encapsulating an expression.
+
+ Parameters
+ ----------
+ expr : str
+ engine : str, optional, default 'numexpr'
+ parser : str, optional, default 'pandas'
+ env : Scope, optional, default None
+ level : int, optional, default 2
+ """
+
+ env: Scope
+ engine: str
+ parser: str
+
+ def __init__(
+ self,
+ expr,
+ engine: str = "numexpr",
+ parser: str = "pandas",
+ env: Scope | None = None,
+ level: int = 0,
+ ) -> None:
+ self.expr = expr
+ self.env = env or Scope(level=level + 1)
+ self.engine = engine
+ self.parser = parser
+ self._visitor = PARSERS[parser](self.env, self.engine, self.parser)
+ self.terms = self.parse()
+
+ @property
+ def assigner(self):
+ return getattr(self._visitor, "assigner", None)
+
+ def __call__(self):
+ return self.terms(self.env)
+
+ def __repr__(self) -> str:
+ return printing.pprint_thing(self.terms)
+
+ def __len__(self) -> int:
+ return len(self.expr)
+
+ def parse(self):
+ """
+ Parse an expression.
+ """
+ return self._visitor.visit(self.expr)
+
+ @property
+ def names(self):
+ """
+ Get the names in an expression.
+ """
+ if is_term(self.terms):
+ return frozenset([self.terms.name])
+ return frozenset(term.name for term in com.flatten(self.terms))
+
+
+PARSERS = {"python": PythonExprVisitor, "pandas": PandasExprVisitor}
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/expressions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/expressions.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a5fad0d83d7a5a7939d4384498a6a5288bb3543
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/expressions.py
@@ -0,0 +1,290 @@
+"""
+Expressions
+-----------
+
+Offer fast expression evaluation through numexpr
+
+"""
+
+from __future__ import annotations
+
+import operator
+from typing import TYPE_CHECKING
+import warnings
+
+import numpy as np
+
+from pandas._config import get_option
+
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core import roperator
+from pandas.core.computation.check import NUMEXPR_INSTALLED
+
+if NUMEXPR_INSTALLED:
+ import numexpr as ne
+
+if TYPE_CHECKING:
+ from pandas._typing import FuncType
+
+_TEST_MODE: bool | None = None
+_TEST_RESULT: list[bool] = []
+USE_NUMEXPR = NUMEXPR_INSTALLED
+_evaluate: FuncType | None = None
+_where: FuncType | None = None
+
+# the set of dtypes that we will allow pass to numexpr
+_ALLOWED_DTYPES = {
+ "evaluate": {"int64", "int32", "float64", "float32", "bool"},
+ "where": {"int64", "float64", "bool"},
+}
+
+# the minimum prod shape that we will use numexpr
+_MIN_ELEMENTS = 1_000_000
+
+
+def set_use_numexpr(v: bool = True) -> None:
+ # set/unset to use numexpr
+ global USE_NUMEXPR
+ if NUMEXPR_INSTALLED:
+ USE_NUMEXPR = v
+
+ # choose what we are going to do
+ global _evaluate, _where
+
+ _evaluate = _evaluate_numexpr if USE_NUMEXPR else _evaluate_standard
+ _where = _where_numexpr if USE_NUMEXPR else _where_standard
+
+
+def set_numexpr_threads(n=None) -> None:
+ # if we are using numexpr, set the threads to n
+ # otherwise reset
+ if NUMEXPR_INSTALLED and USE_NUMEXPR:
+ if n is None:
+ n = ne.detect_number_of_cores()
+ ne.set_num_threads(n)
+
+
+def _evaluate_standard(op, op_str, left_op, right_op):
+ """
+ Standard evaluation.
+ """
+ if _TEST_MODE:
+ _store_test_result(False)
+ return op(left_op, right_op)
+
+
+def _can_use_numexpr(op, op_str, left_op, right_op, dtype_check) -> bool:
+ """return left_op boolean if we WILL be using numexpr"""
+ if op_str is not None:
+ # required min elements (otherwise we are adding overhead)
+ if left_op.size > _MIN_ELEMENTS:
+ # check for dtype compatibility
+ dtypes: set[str] = set()
+ for o in [left_op, right_op]:
+ # ndarray and Series Case
+ if hasattr(o, "dtype"):
+ dtypes |= {o.dtype.name}
+
+ # allowed are a superset
+ if not len(dtypes) or _ALLOWED_DTYPES[dtype_check] >= dtypes:
+ return True
+
+ return False
+
+
+def _evaluate_numexpr(op, op_str, left_op, right_op):
+ result = None
+
+ if _can_use_numexpr(op, op_str, left_op, right_op, "evaluate"):
+ is_reversed = op.__name__.strip("_").startswith("r")
+ if is_reversed:
+ # we were originally called by a reversed op method
+ left_op, right_op = right_op, left_op
+
+ left_value = left_op
+ right_value = right_op
+
+ try:
+ result = ne.evaluate(
+ f"left_value {op_str} right_value",
+ local_dict={"left_value": left_value, "right_value": right_value},
+ casting="safe",
+ )
+ except TypeError:
+ # numexpr raises eg for array ** array with integers
+ # (https://github.com/pydata/numexpr/issues/379)
+ pass
+ except NotImplementedError:
+ if _bool_arith_fallback(op_str, left_op, right_op):
+ pass
+ else:
+ raise
+
+ if is_reversed:
+ # reverse order to original for fallback
+ left_op, right_op = right_op, left_op
+
+ if _TEST_MODE:
+ _store_test_result(result is not None)
+
+ if result is None:
+ result = _evaluate_standard(op, op_str, left_op, right_op)
+
+ return result
+
+
+_op_str_mapping = {
+ operator.add: "+",
+ roperator.radd: "+",
+ operator.mul: "*",
+ roperator.rmul: "*",
+ operator.sub: "-",
+ roperator.rsub: "-",
+ operator.truediv: "/",
+ roperator.rtruediv: "/",
+ # floordiv not supported by numexpr 2.x
+ operator.floordiv: None,
+ roperator.rfloordiv: None,
+ # we require Python semantics for mod of negative for backwards compatibility
+ # see https://github.com/pydata/numexpr/issues/365
+ # so sticking with unaccelerated for now GH#36552
+ operator.mod: None,
+ roperator.rmod: None,
+ operator.pow: "**",
+ roperator.rpow: "**",
+ operator.eq: "==",
+ operator.ne: "!=",
+ operator.le: "<=",
+ operator.lt: "<",
+ operator.ge: ">=",
+ operator.gt: ">",
+ operator.and_: "&",
+ roperator.rand_: "&",
+ operator.or_: "|",
+ roperator.ror_: "|",
+ operator.xor: "^",
+ roperator.rxor: "^",
+ divmod: None,
+ roperator.rdivmod: None,
+}
+
+
+def _where_standard(cond, left_op, right_op):
+ # Caller is responsible for extracting ndarray if necessary
+ return np.where(cond, left_op, right_op)
+
+
+def _where_numexpr(cond, left_op, right_op):
+ # Caller is responsible for extracting ndarray if necessary
+ result = None
+
+ if _can_use_numexpr(None, "where", left_op, right_op, "where"):
+ result = ne.evaluate(
+ "where(cond_value, a_value, b_value)",
+ local_dict={"cond_value": cond, "a_value": left_op, "b_value": right_op},
+ casting="safe",
+ )
+
+ if result is None:
+ result = _where_standard(cond, left_op, right_op)
+
+ return result
+
+
+# turn myself on
+set_use_numexpr(get_option("compute.use_numexpr"))
+
+
+def _has_bool_dtype(x):
+ try:
+ return x.dtype == bool
+ except AttributeError:
+ return isinstance(x, (bool, np.bool_))
+
+
+_BOOL_OP_UNSUPPORTED = {"+": "|", "*": "&", "-": "^"}
+
+
+def _bool_arith_fallback(op_str, left_op, right_op) -> bool:
+ """
+ Check if we should fallback to the python `_evaluate_standard` in case
+ of an unsupported operation by numexpr, which is the case for some
+ boolean ops.
+ """
+ if _has_bool_dtype(left_op) and _has_bool_dtype(right_op):
+ if op_str in _BOOL_OP_UNSUPPORTED:
+ warnings.warn(
+ f"evaluating in Python space because the {op_str!r} "
+ "operator is not supported by numexpr for the bool dtype, "
+ f"use {_BOOL_OP_UNSUPPORTED[op_str]!r} instead.",
+ stacklevel=find_stack_level(),
+ )
+ return True
+ return False
+
+
+def evaluate(op, left_op, right_op, use_numexpr: bool = True):
+ """
+ Evaluate and return the expression of the op on left_op and right_op.
+
+ Parameters
+ ----------
+ op : the actual operand
+ left_op : left operand
+ right_op : right operand
+ use_numexpr : bool, default True
+ Whether to try to use numexpr.
+ """
+ op_str = _op_str_mapping[op]
+ if op_str is not None:
+ if use_numexpr:
+ # error: "None" not callable
+ return _evaluate(op, op_str, left_op, right_op) # type: ignore[misc]
+ return _evaluate_standard(op, op_str, left_op, right_op)
+
+
+def where(cond, left_op, right_op, use_numexpr: bool = True):
+ """
+ Evaluate the where condition cond on left_op and right_op.
+
+ Parameters
+ ----------
+ cond : np.ndarray[bool]
+ left_op : return if cond is True
+ right_op : return if cond is False
+ use_numexpr : bool, default True
+ Whether to try to use numexpr.
+ """
+ assert _where is not None
+ if use_numexpr:
+ return _where(cond, left_op, right_op)
+ else:
+ return _where_standard(cond, left_op, right_op)
+
+
+def set_test_mode(v: bool = True) -> None:
+ """
+ Keeps track of whether numexpr was used.
+
+ Stores an additional ``True`` for every successful use of evaluate with
+ numexpr since the last ``get_test_result``.
+ """
+ global _TEST_MODE, _TEST_RESULT
+ _TEST_MODE = v
+ _TEST_RESULT = []
+
+
+def _store_test_result(used_numexpr: bool) -> None:
+ if used_numexpr:
+ _TEST_RESULT.append(used_numexpr)
+
+
+def get_test_result() -> list[bool]:
+ """
+ Get test result and reset test_results.
+ """
+ global _TEST_RESULT
+ res = _TEST_RESULT
+ _TEST_RESULT = []
+ return res
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/ops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/ops.py
new file mode 100644
index 0000000000000000000000000000000000000000..49e8f5e93048a99de16cfcf9a79e1b6ae66f7729
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/ops.py
@@ -0,0 +1,561 @@
+"""
+Operator classes for eval.
+"""
+
+from __future__ import annotations
+
+from datetime import datetime
+from functools import partial
+import operator
+from typing import (
+ TYPE_CHECKING,
+ Literal,
+)
+
+import numpy as np
+
+from pandas._libs.tslibs import Timestamp
+
+from pandas.core.dtypes.common import (
+ is_list_like,
+ is_scalar,
+)
+
+import pandas.core.common as com
+from pandas.core.computation.common import (
+ ensure_decoded,
+ result_type_many,
+)
+from pandas.core.computation.scope import DEFAULT_GLOBALS
+
+from pandas.io.formats.printing import (
+ pprint_thing,
+ pprint_thing_encoded,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Callable,
+ Iterable,
+ Iterator,
+ )
+
+REDUCTIONS = ("sum", "prod", "min", "max")
+
+_unary_math_ops = (
+ "sin",
+ "cos",
+ "tan",
+ "exp",
+ "log",
+ "expm1",
+ "log1p",
+ "sqrt",
+ "sinh",
+ "cosh",
+ "tanh",
+ "arcsin",
+ "arccos",
+ "arctan",
+ "arccosh",
+ "arcsinh",
+ "arctanh",
+ "abs",
+ "log10",
+ "floor",
+ "ceil",
+)
+_binary_math_ops = ("arctan2",)
+
+MATHOPS = _unary_math_ops + _binary_math_ops
+
+
+LOCAL_TAG = "__pd_eval_local_"
+
+
+class Term:
+ def __new__(cls, name, env, side=None, encoding=None):
+ klass = Constant if not isinstance(name, str) else cls
+ supr_new = super(Term, klass).__new__
+ return supr_new(klass)
+
+ is_local: bool
+
+ def __init__(self, name, env, side=None, encoding=None) -> None:
+ # name is a str for Term, but may be something else for subclasses
+ self._name = name
+ self.env = env
+ self.side = side
+ tname = str(name)
+ self.is_local = tname.startswith(LOCAL_TAG) or tname in DEFAULT_GLOBALS
+ self._value = self._resolve_name()
+ self.encoding = encoding
+
+ @property
+ def local_name(self) -> str:
+ return self.name.replace(LOCAL_TAG, "")
+
+ def __repr__(self) -> str:
+ return pprint_thing(self.name)
+
+ def __call__(self, *args, **kwargs):
+ return self.value
+
+ def evaluate(self, *args, **kwargs) -> Term:
+ return self
+
+ def _resolve_name(self):
+ local_name = str(self.local_name)
+ is_local = self.is_local
+ if local_name in self.env.scope and isinstance(
+ self.env.scope[local_name], type
+ ):
+ is_local = False
+
+ res = self.env.resolve(local_name, is_local=is_local)
+ self.update(res)
+
+ if hasattr(res, "ndim") and isinstance(res.ndim, int) and res.ndim > 2:
+ raise NotImplementedError(
+ "N-dimensional objects, where N > 2, are not supported with eval"
+ )
+ return res
+
+ def update(self, value) -> None:
+ """
+ search order for local (i.e., @variable) variables:
+
+ scope, key_variable
+ [('locals', 'local_name'),
+ ('globals', 'local_name'),
+ ('locals', 'key'),
+ ('globals', 'key')]
+ """
+ key = self.name
+
+ # if it's a variable name (otherwise a constant)
+ if isinstance(key, str):
+ self.env.swapkey(self.local_name, key, new_value=value)
+
+ self.value = value
+
+ @property
+ def is_scalar(self) -> bool:
+ return is_scalar(self._value)
+
+ @property
+ def type(self):
+ try:
+ # potentially very slow for large, mixed dtype frames
+ return self._value.values.dtype
+ except AttributeError:
+ try:
+ # ndarray
+ return self._value.dtype
+ except AttributeError:
+ # scalar
+ return type(self._value)
+
+ return_type = type
+
+ @property
+ def raw(self) -> str:
+ return f"{type(self).__name__}(name={self.name!r}, type={self.type})"
+
+ @property
+ def is_datetime(self) -> bool:
+ try:
+ t = self.type.type
+ except AttributeError:
+ t = self.type
+
+ return issubclass(t, (datetime, np.datetime64))
+
+ @property
+ def value(self):
+ return self._value
+
+ @value.setter
+ def value(self, new_value) -> None:
+ self._value = new_value
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def ndim(self) -> int:
+ return self._value.ndim
+
+
+class Constant(Term):
+ def _resolve_name(self):
+ return self._name
+
+ @property
+ def name(self):
+ return self.value
+
+ def __repr__(self) -> str:
+ # in python 2 str() of float
+ # can truncate shorter than repr()
+ return repr(self.name)
+
+
+_bool_op_map = {"not": "~", "and": "&", "or": "|"}
+
+
+class Op:
+ """
+ Hold an operator of arbitrary arity.
+ """
+
+ op: str
+
+ def __init__(self, op: str, operands: Iterable[Term | Op], encoding=None) -> None:
+ self.op = _bool_op_map.get(op, op)
+ self.operands = operands
+ self.encoding = encoding
+
+ def __iter__(self) -> Iterator:
+ return iter(self.operands)
+
+ def __repr__(self) -> str:
+ """
+ Print a generic n-ary operator and its operands using infix notation.
+ """
+ # recurse over the operands
+ parened = (f"({pprint_thing(opr)})" for opr in self.operands)
+ return pprint_thing(f" {self.op} ".join(parened))
+
+ @property
+ def return_type(self):
+ # clobber types to bool if the op is a boolean operator
+ if self.op in (CMP_OPS_SYMS + BOOL_OPS_SYMS):
+ return np.bool_
+ return result_type_many(*(term.type for term in com.flatten(self)))
+
+ @property
+ def has_invalid_return_type(self) -> bool:
+ types = self.operand_types
+ obj_dtype_set = frozenset([np.dtype("object")])
+ return self.return_type == object and types - obj_dtype_set
+
+ @property
+ def operand_types(self):
+ return frozenset(term.type for term in com.flatten(self))
+
+ @property
+ def is_scalar(self) -> bool:
+ return all(operand.is_scalar for operand in self.operands)
+
+ @property
+ def is_datetime(self) -> bool:
+ try:
+ t = self.return_type.type
+ except AttributeError:
+ t = self.return_type
+
+ return issubclass(t, (datetime, np.datetime64))
+
+
+def _in(x, y):
+ """
+ Compute the vectorized membership of ``x in y`` if possible, otherwise
+ use Python.
+ """
+ try:
+ return x.isin(y)
+ except AttributeError:
+ if is_list_like(x):
+ try:
+ return y.isin(x)
+ except AttributeError:
+ pass
+ return x in y
+
+
+def _not_in(x, y):
+ """
+ Compute the vectorized membership of ``x not in y`` if possible,
+ otherwise use Python.
+ """
+ try:
+ return ~x.isin(y)
+ except AttributeError:
+ if is_list_like(x):
+ try:
+ return ~y.isin(x)
+ except AttributeError:
+ pass
+ return x not in y
+
+
+CMP_OPS_SYMS = (">", "<", ">=", "<=", "==", "!=", "in", "not in")
+_cmp_ops_funcs = (
+ operator.gt,
+ operator.lt,
+ operator.ge,
+ operator.le,
+ operator.eq,
+ operator.ne,
+ _in,
+ _not_in,
+)
+_cmp_ops_dict = dict(zip(CMP_OPS_SYMS, _cmp_ops_funcs, strict=True))
+
+BOOL_OPS_SYMS = ("&", "|", "and", "or")
+_bool_ops_funcs = (operator.and_, operator.or_, operator.and_, operator.or_)
+_bool_ops_dict = dict(zip(BOOL_OPS_SYMS, _bool_ops_funcs, strict=True))
+
+ARITH_OPS_SYMS = ("+", "-", "*", "/", "**", "//", "%")
+_arith_ops_funcs = (
+ operator.add,
+ operator.sub,
+ operator.mul,
+ operator.truediv,
+ operator.pow,
+ operator.floordiv,
+ operator.mod,
+)
+_arith_ops_dict = dict(zip(ARITH_OPS_SYMS, _arith_ops_funcs, strict=True))
+
+_binary_ops_dict = {}
+
+for d in (_cmp_ops_dict, _bool_ops_dict, _arith_ops_dict):
+ _binary_ops_dict.update(d)
+
+
+def is_term(obj) -> bool:
+ return isinstance(obj, Term)
+
+
+class BinOp(Op):
+ """
+ Hold a binary operator and its operands.
+
+ Parameters
+ ----------
+ op : str
+ lhs : Term or Op
+ rhs : Term or Op
+ """
+
+ def __init__(self, op: str, lhs, rhs) -> None:
+ super().__init__(op, (lhs, rhs))
+ self.lhs = lhs
+ self.rhs = rhs
+
+ self._disallow_scalar_only_bool_ops()
+
+ self.convert_values()
+
+ try:
+ self.func = _binary_ops_dict[op]
+ except KeyError as err:
+ # has to be made a list for python3
+ keys = list(_binary_ops_dict.keys())
+ raise ValueError(
+ f"Invalid binary operator {op!r}, valid operators are {keys}"
+ ) from err
+
+ def __call__(self, env):
+ """
+ Recursively evaluate an expression in Python space.
+
+ Parameters
+ ----------
+ env : Scope
+
+ Returns
+ -------
+ object
+ The result of an evaluated expression.
+ """
+ # recurse over the left/right nodes
+ left = self.lhs(env)
+ right = self.rhs(env)
+
+ return self.func(left, right)
+
+ def evaluate(self, env, engine: str, parser, term_type, eval_in_python):
+ """
+ Evaluate a binary operation *before* being passed to the engine.
+
+ Parameters
+ ----------
+ env : Scope
+ engine : str
+ parser : str
+ term_type : type
+ eval_in_python : list
+
+ Returns
+ -------
+ term_type
+ The "pre-evaluated" expression as an instance of ``term_type``
+ """
+ if engine == "python":
+ res = self(env)
+ else:
+ # recurse over the left/right nodes
+
+ left = self.lhs.evaluate(
+ env,
+ engine=engine,
+ parser=parser,
+ term_type=term_type,
+ eval_in_python=eval_in_python,
+ )
+
+ right = self.rhs.evaluate(
+ env,
+ engine=engine,
+ parser=parser,
+ term_type=term_type,
+ eval_in_python=eval_in_python,
+ )
+
+ # base cases
+ if self.op in eval_in_python:
+ res = self.func(left.value, right.value)
+ else:
+ from pandas.core.computation.eval import eval
+
+ res = eval(self, local_dict=env, engine=engine, parser=parser)
+
+ name = env.add_tmp(res)
+ return term_type(name, env=env)
+
+ def convert_values(self) -> None:
+ """
+ Convert datetimes to a comparable value in an expression.
+ """
+
+ def stringify(value):
+ encoder: Callable
+ if self.encoding is not None:
+ encoder = partial(pprint_thing_encoded, encoding=self.encoding)
+ else:
+ encoder = pprint_thing
+ return encoder(value)
+
+ lhs, rhs = self.lhs, self.rhs
+
+ if is_term(lhs) and lhs.is_datetime and is_term(rhs) and rhs.is_scalar:
+ v = rhs.value
+ if isinstance(v, (int, float)):
+ v = stringify(v)
+ v = Timestamp(ensure_decoded(v))
+ if v.tz is not None:
+ v = v.tz_convert("UTC")
+ self.rhs.update(v)
+
+ if is_term(rhs) and rhs.is_datetime and is_term(lhs) and lhs.is_scalar:
+ v = lhs.value
+ if isinstance(v, (int, float)):
+ v = stringify(v)
+ v = Timestamp(ensure_decoded(v))
+ if v.tz is not None:
+ v = v.tz_convert("UTC")
+ self.lhs.update(v)
+
+ def _disallow_scalar_only_bool_ops(self) -> None:
+ rhs = self.rhs
+ lhs = self.lhs
+
+ # GH#24883 unwrap dtype if necessary to ensure we have a type object
+ rhs_rt = rhs.return_type
+ rhs_rt = getattr(rhs_rt, "type", rhs_rt)
+ lhs_rt = lhs.return_type
+ lhs_rt = getattr(lhs_rt, "type", lhs_rt)
+ if (
+ (lhs.is_scalar or rhs.is_scalar)
+ and self.op in _bool_ops_dict
+ and (
+ not (
+ issubclass(rhs_rt, (bool, np.bool_))
+ and issubclass(lhs_rt, (bool, np.bool_))
+ )
+ )
+ ):
+ raise NotImplementedError("cannot evaluate scalar only bool ops")
+
+
+UNARY_OPS_SYMS = ("+", "-", "~", "not")
+_unary_ops_funcs = (operator.pos, operator.neg, operator.invert, operator.invert)
+_unary_ops_dict = dict(zip(UNARY_OPS_SYMS, _unary_ops_funcs, strict=True))
+
+
+class UnaryOp(Op):
+ """
+ Hold a unary operator and its operands.
+
+ Parameters
+ ----------
+ op : str
+ The token used to represent the operator.
+ operand : Term or Op
+ The Term or Op operand to the operator.
+
+ Raises
+ ------
+ ValueError
+ * If no function associated with the passed operator token is found.
+ """
+
+ def __init__(self, op: Literal["+", "-", "~", "not"], operand) -> None:
+ super().__init__(op, (operand,))
+ self.operand = operand
+
+ try:
+ self.func = _unary_ops_dict[op]
+ except KeyError as err:
+ raise ValueError(
+ f"Invalid unary operator {op!r}, valid operators are {UNARY_OPS_SYMS}"
+ ) from err
+
+ def __call__(self, env) -> MathCall:
+ operand = self.operand(env)
+ # error: Cannot call function of unknown type
+ return self.func(operand) # type: ignore[operator]
+
+ def __repr__(self) -> str:
+ return pprint_thing(f"{self.op}({self.operand})")
+
+ @property
+ def return_type(self) -> np.dtype:
+ operand = self.operand
+ if operand.return_type == np.dtype("bool"):
+ return np.dtype("bool")
+ if isinstance(operand, Op) and (
+ operand.op in _cmp_ops_dict or operand.op in _bool_ops_dict
+ ):
+ return np.dtype("bool")
+ return np.dtype("int")
+
+
+class MathCall(Op):
+ def __init__(self, func, args) -> None:
+ super().__init__(func.name, args)
+ self.func = func
+
+ def __call__(self, env):
+ # error: "Op" not callable
+ operands = [op(env) for op in self.operands] # type: ignore[operator]
+ return self.func.func(*operands)
+
+ def __repr__(self) -> str:
+ operands = map(str, self.operands)
+ return pprint_thing(f"{self.op}({','.join(operands)})")
+
+
+class FuncNode:
+ def __init__(self, name: str) -> None:
+ if name not in MATHOPS:
+ raise ValueError(f'"{name}" is not a supported function')
+ self.name = name
+ self.func = getattr(np, name)
+
+ def __call__(self, *args) -> MathCall:
+ return MathCall(self, args)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/parsing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/parsing.py
new file mode 100644
index 0000000000000000000000000000000000000000..8441941797a6e0edfb76c529cdcab70b1e6e1019
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/parsing.py
@@ -0,0 +1,252 @@
+"""
+:func:`~pandas.eval` source string parsing functions
+"""
+
+from __future__ import annotations
+
+from enum import Enum
+from io import StringIO
+from keyword import iskeyword
+import token
+import tokenize
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Hashable,
+ Iterator,
+ )
+
+# A token value Python's tokenizer probably will never use.
+BACKTICK_QUOTED_STRING = 100
+
+
+def create_valid_python_identifier(name: str) -> str:
+ """
+ Create valid Python identifiers from any string.
+
+ Check if name contains any special characters. If it contains any
+ special characters, the special characters will be replaced by
+ a special string and a prefix is added.
+
+ Raises
+ ------
+ SyntaxError
+ If the returned name is not a Python valid identifier, raise an exception.
+ """
+ if name.isidentifier() and not iskeyword(name):
+ return name
+
+ # Escape characters that fall outside the ASCII range (U+0001..U+007F).
+ # GH 49633
+ gen = (
+ (c, "".join(chr(b) for b in c.encode("ascii", "backslashreplace")))
+ for c in name
+ )
+ name = "".join(
+ c_escaped.replace("\\", "_UNICODE_" if c != c_escaped else "_BACKSLASH_")
+ for c, c_escaped in gen
+ )
+
+ # Create a dict with the special characters and their replacement string.
+ # EXACT_TOKEN_TYPES contains these special characters
+ # token.tok_name contains a readable description of the replacement string.
+ special_characters_replacements = {
+ char: f"_{token.tok_name[tokval]}_"
+ for char, tokval in (tokenize.EXACT_TOKEN_TYPES.items())
+ }
+ special_characters_replacements.update(
+ {
+ " ": "_",
+ "?": "_QUESTIONMARK_",
+ "!": "_EXCLAMATIONMARK_",
+ "$": "_DOLLARSIGN_",
+ "€": "_EUROSIGN_",
+ "°": "_DEGREESIGN_",
+ "'": "_SINGLEQUOTE_",
+ '"': "_DOUBLEQUOTE_",
+ "#": "_HASH_",
+ "`": "_BACKTICK_",
+ }
+ )
+
+ name = "".join([special_characters_replacements.get(char, char) for char in name])
+ name = f"BACKTICK_QUOTED_STRING_{name}"
+
+ if not name.isidentifier():
+ raise SyntaxError(f"Could not convert '{name}' to a valid Python identifier.")
+
+ return name
+
+
+def clean_backtick_quoted_toks(tok: tuple[int, str]) -> tuple[int, str]:
+ """
+ Clean up a column name if surrounded by backticks.
+
+ Backtick quoted string are indicated by a certain tokval value. If a string
+ is a backtick quoted token it will processed by
+ :func:`_create_valid_python_identifier` so that the parser can find this
+ string when the query is executed.
+ In this case the tok will get the NAME tokval.
+
+ Parameters
+ ----------
+ tok : tuple of int, str
+ ints correspond to the all caps constants in the tokenize module
+
+ Returns
+ -------
+ tok : Tuple[int, str]
+ Either the input or token or the replacement values
+ """
+ toknum, tokval = tok
+ if toknum == BACKTICK_QUOTED_STRING:
+ return tokenize.NAME, create_valid_python_identifier(tokval)
+ return toknum, tokval
+
+
+def clean_column_name(name: Hashable) -> Hashable:
+ """
+ Function to emulate the cleaning of a backtick quoted name.
+
+ The purpose for this function is to see what happens to the name of
+ identifier if it goes to the process of being parsed a Python code
+ inside a backtick quoted string and than being cleaned
+ (removed of any special characters).
+
+ Parameters
+ ----------
+ name : hashable
+ Name to be cleaned.
+
+ Returns
+ -------
+ name : hashable
+ Returns the name after tokenizing and cleaning.
+ """
+ try:
+ # Escape backticks
+ name = name.replace("`", "``") if isinstance(name, str) else name
+
+ tokenized = tokenize_string(f"`{name}`")
+ tokval = next(tokenized)[1]
+ return create_valid_python_identifier(tokval)
+ except SyntaxError:
+ return name
+
+
+class ParseState(Enum):
+ DEFAULT = 0
+ IN_BACKTICK = 1
+ IN_SINGLE_QUOTE = 2
+ IN_DOUBLE_QUOTE = 3
+
+
+def _split_by_backtick(s: str) -> list[tuple[bool, str]]:
+ """
+ Splits a str into substrings along backtick characters (`).
+
+ Disregards backticks inside quotes.
+
+ Parameters
+ ----------
+ s : str
+ The Python source code string.
+
+ Returns
+ -------
+ substrings: list[tuple[bool, str]]
+ List of tuples, where each tuple has two elements:
+ The first is a boolean indicating if the substring is backtick-quoted.
+ The second is the actual substring.
+ """
+ substrings = []
+ substr: list[str] = [] # Will join into a string before adding to `substrings`
+ i = 0
+ parse_state = ParseState.DEFAULT
+ while i < len(s):
+ char = s[i]
+
+ match char:
+ case "`":
+ # start of a backtick-quoted string
+ if parse_state == ParseState.DEFAULT:
+ if substr:
+ substrings.append((False, "".join(substr)))
+
+ substr = [char]
+ i += 1
+ parse_state = ParseState.IN_BACKTICK
+ continue
+
+ elif parse_state == ParseState.IN_BACKTICK:
+ # escaped backtick inside a backtick-quoted string
+ next_char = s[i + 1] if (i != len(s) - 1) else None
+ if next_char == "`":
+ substr.append(char)
+ substr.append(next_char)
+ i += 2
+ continue
+
+ # end of the backtick-quoted string
+ else:
+ substr.append(char)
+ substrings.append((True, "".join(substr)))
+
+ substr = []
+ i += 1
+ parse_state = ParseState.DEFAULT
+ continue
+ case "'":
+ # start of a single-quoted string
+ if parse_state == ParseState.DEFAULT:
+ parse_state = ParseState.IN_SINGLE_QUOTE
+ # end of a single-quoted string
+ elif (parse_state == ParseState.IN_SINGLE_QUOTE) and (s[i - 1] != "\\"):
+ parse_state = ParseState.DEFAULT
+ case '"':
+ # start of a double-quoted string
+ if parse_state == ParseState.DEFAULT:
+ parse_state = ParseState.IN_DOUBLE_QUOTE
+ # end of a double-quoted string
+ elif (parse_state == ParseState.IN_DOUBLE_QUOTE) and (s[i - 1] != "\\"):
+ parse_state = ParseState.DEFAULT
+ substr.append(char)
+ i += 1
+
+ if substr:
+ substrings.append((False, "".join(substr)))
+
+ return substrings
+
+
+def tokenize_string(source: str) -> Iterator[tuple[int, str]]:
+ """
+ Tokenize a Python source code string.
+
+ Parameters
+ ----------
+ source : str
+ The Python source code string.
+
+ Returns
+ -------
+ tok_generator : Iterator[Tuple[int, str]]
+ An iterator yielding all tokens with only toknum and tokval (Tuple[ing, str]).
+ """
+ # GH 59285
+ # Escape characters, including backticks
+ source = "".join(
+ (
+ create_valid_python_identifier(substring[1:-1])
+ if is_backtick_quoted
+ else substring
+ )
+ for is_backtick_quoted, substring in _split_by_backtick(source)
+ )
+
+ line_reader = StringIO(source).readline
+ token_generator = tokenize.generate_tokens(line_reader)
+
+ for toknum, tokval, _, _, _ in token_generator:
+ yield toknum, tokval
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/pytables.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/pytables.py
new file mode 100644
index 0000000000000000000000000000000000000000..b094ace9a86853522ad63911aecf81c9af08b9ed
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/pytables.py
@@ -0,0 +1,681 @@
+"""manage PyTables query interface via Expressions"""
+
+from __future__ import annotations
+
+import ast
+from decimal import (
+ Decimal,
+ InvalidOperation,
+)
+from functools import partial
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ ClassVar,
+ Self,
+ cast,
+)
+
+import numpy as np
+
+from pandas._libs import lib
+from pandas._libs.tslibs import (
+ Timedelta,
+ Timestamp,
+)
+from pandas.errors import UndefinedVariableError
+
+from pandas.core.dtypes.common import is_list_like
+
+import pandas.core.common as com
+from pandas.core.computation import (
+ expr,
+ ops,
+ scope as _scope,
+)
+from pandas.core.computation.common import ensure_decoded
+from pandas.core.computation.expr import BaseExprVisitor
+from pandas.core.computation.ops import is_term
+from pandas.core.construction import extract_array
+from pandas.core.indexes.base import Index
+
+from pandas.io.formats.printing import (
+ pprint_thing,
+ pprint_thing_encoded,
+)
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ TimeUnit,
+ npt,
+ )
+
+
+class PyTablesScope(_scope.Scope):
+ __slots__ = ("queryables",)
+
+ queryables: dict[str, Any]
+
+ def __init__(
+ self,
+ level: int,
+ global_dict=None,
+ local_dict=None,
+ queryables: dict[str, Any] | None = None,
+ ) -> None:
+ super().__init__(level + 1, global_dict=global_dict, local_dict=local_dict)
+ self.queryables = queryables or {}
+
+
+class Term(ops.Term):
+ env: PyTablesScope
+
+ def __new__(cls, name, env, side=None, encoding=None):
+ if isinstance(name, str):
+ klass = cls
+ else:
+ klass = Constant
+ return object.__new__(klass)
+
+ def __init__(self, name, env: PyTablesScope, side=None, encoding=None) -> None:
+ super().__init__(name, env, side=side, encoding=encoding)
+
+ def _resolve_name(self):
+ # must be a queryables
+ if self.side == "left":
+ # Note: The behavior of __new__ ensures that self.name is a str here
+ if self.name not in self.env.queryables:
+ raise NameError(f"name {self.name!r} is not defined")
+ return self.name
+
+ # resolve the rhs (and allow it to be None)
+ try:
+ return self.env.resolve(self.name, is_local=False)
+ except UndefinedVariableError:
+ return self.name
+
+ # read-only property overwriting read/write property
+ @property # type: ignore[misc]
+ def value(self):
+ return self._value
+
+
+class Constant(Term):
+ def __init__(self, name, env: PyTablesScope, side=None, encoding=None) -> None:
+ assert isinstance(env, PyTablesScope), type(env)
+ super().__init__(name, env, side=side, encoding=encoding)
+
+ def _resolve_name(self):
+ return self._name
+
+
+class BinOp(ops.BinOp):
+ _max_selectors = 31
+
+ op: str
+ queryables: dict[str, Any]
+ condition: str | None
+
+ def __init__(self, op: str, lhs, rhs, queryables: dict[str, Any], encoding) -> None:
+ super().__init__(op, lhs, rhs)
+ self.queryables = queryables
+ self.encoding = encoding
+ self.condition = None
+
+ def _disallow_scalar_only_bool_ops(self) -> None:
+ pass
+
+ def prune(self, klass):
+ def pr(left, right):
+ """create and return a new specialized BinOp from myself"""
+ if left is None:
+ return right
+ elif right is None:
+ return left
+
+ k = klass
+ if isinstance(left, ConditionBinOp):
+ if isinstance(right, ConditionBinOp):
+ k = JointConditionBinOp
+ elif isinstance(left, k):
+ return left
+ elif isinstance(right, k):
+ return right
+
+ elif isinstance(left, FilterBinOp):
+ if isinstance(right, FilterBinOp):
+ k = JointFilterBinOp
+ elif isinstance(left, k):
+ return left
+ elif isinstance(right, k):
+ return right
+
+ return k(
+ self.op, left, right, queryables=self.queryables, encoding=self.encoding
+ ).evaluate()
+
+ left, right = self.lhs, self.rhs
+
+ if is_term(left) and is_term(right):
+ res = pr(left.value, right.value)
+ elif not is_term(left) and is_term(right):
+ res = pr(left.prune(klass), right.value)
+ elif is_term(left) and not is_term(right):
+ res = pr(left.value, right.prune(klass))
+ elif not (is_term(left) or is_term(right)):
+ res = pr(left.prune(klass), right.prune(klass))
+
+ return res
+
+ def conform(self, rhs):
+ """inplace conform rhs"""
+ if not is_list_like(rhs):
+ rhs = [rhs]
+ if isinstance(rhs, np.ndarray):
+ rhs = rhs.ravel()
+ return rhs
+
+ @property
+ def is_valid(self) -> bool:
+ """return True if this is a valid field"""
+ return self.lhs in self.queryables
+
+ @property
+ def is_in_table(self) -> bool:
+ """
+ return True if this is a valid column name for generation (e.g. an
+ actual column in the table)
+ """
+ return self.queryables.get(self.lhs) is not None
+
+ @property
+ def kind(self):
+ """the kind of my field"""
+ return getattr(self.queryables.get(self.lhs), "kind", None)
+
+ @property
+ def meta(self):
+ """the meta of my field"""
+ return getattr(self.queryables.get(self.lhs), "meta", None)
+
+ @property
+ def metadata(self):
+ """the metadata of my field"""
+ return getattr(self.queryables.get(self.lhs), "metadata", None)
+
+ def generate(self, v) -> str:
+ """create and return the op string for this TermValue"""
+ val = v.tostring(self.encoding)
+ return f"({self.lhs} {self.op} {val})"
+
+ def convert_value(self, conv_val) -> TermValue:
+ """
+ convert the expression that is in the term to something that is
+ accepted by pytables
+ """
+
+ def stringify(value):
+ if self.encoding is not None:
+ return pprint_thing_encoded(value, encoding=self.encoding)
+ return pprint_thing(value)
+
+ kind = ensure_decoded(self.kind)
+ meta = ensure_decoded(self.meta)
+ if kind == "datetime" or (kind and kind.startswith("datetime64")):
+ if isinstance(conv_val, (int, float)):
+ conv_val = stringify(conv_val)
+ conv_val = ensure_decoded(conv_val)
+ unit: TimeUnit = "ns"
+ if "[" in kind:
+ unit = cast("TimeUnit", kind.split("[")[-1][:-1])
+ conv_val = Timestamp(conv_val).as_unit(unit)
+ if conv_val.tz is not None:
+ conv_val = conv_val.tz_convert("UTC")
+ return TermValue(conv_val, conv_val._value, kind)
+ elif kind.startswith("timedelta"):
+ unit = "ns"
+ if "[" in kind:
+ unit = cast("TimeUnit", kind.split("[")[-1][:-1])
+ if isinstance(conv_val, str):
+ conv_val = Timedelta(conv_val)
+ elif lib.is_integer(conv_val) or lib.is_float(conv_val):
+ conv_val = Timedelta(conv_val, unit="s")
+ else:
+ conv_val = Timedelta(conv_val)
+ conv_val = conv_val.as_unit(unit)._value
+ return TermValue(int(conv_val), conv_val, kind)
+
+ elif meta == "category":
+ metadata = extract_array(self.metadata, extract_numpy=True)
+ result: npt.NDArray[np.intp] | np.intp | int
+ if conv_val not in metadata:
+ result = -1
+ else:
+ # Find the index of the first match of conv_val in metadata
+ result = np.flatnonzero(metadata == conv_val)[0]
+ return TermValue(result, result, "integer")
+ elif kind == "integer":
+ try:
+ v_dec = Decimal(conv_val)
+ except InvalidOperation:
+ # GH 54186
+ # convert v to float to raise float's ValueError
+ float(conv_val)
+ else:
+ conv_val = int(v_dec.to_integral_exact(rounding="ROUND_HALF_EVEN"))
+ return TermValue(conv_val, conv_val, kind)
+ elif kind == "float":
+ conv_val = float(conv_val)
+ return TermValue(conv_val, conv_val, kind)
+ elif kind == "bool":
+ if isinstance(conv_val, str):
+ conv_val = conv_val.strip().lower() not in [
+ "false",
+ "f",
+ "no",
+ "n",
+ "none",
+ "0",
+ "[]",
+ "{}",
+ "",
+ ]
+ else:
+ conv_val = bool(conv_val)
+ return TermValue(conv_val, conv_val, kind)
+ elif isinstance(conv_val, str):
+ # string quoting
+ return TermValue(conv_val, stringify(conv_val), "string")
+ else:
+ raise TypeError(
+ f"Cannot compare {conv_val} of type {type(conv_val)} to {kind} column"
+ )
+
+ def convert_values(self) -> None:
+ pass
+
+
+class FilterBinOp(BinOp):
+ filter: tuple[Any, Any, Index] | None = None
+
+ def __repr__(self) -> str:
+ if self.filter is None:
+ return "Filter: Not Initialized"
+ return pprint_thing(f"[Filter : [{self.filter[0]}] -> [{self.filter[1]}]")
+
+ def invert(self) -> Self:
+ """invert the filter"""
+ if self.filter is not None:
+ self.filter = (
+ self.filter[0],
+ self.generate_filter_op(invert=True),
+ self.filter[2],
+ )
+ return self
+
+ def format(self):
+ """return the actual filter format"""
+ return [self.filter]
+
+ # error: Signature of "evaluate" incompatible with supertype "BinOp"
+ def evaluate(self) -> Self | None: # type: ignore[override]
+ if not self.is_valid:
+ raise ValueError(f"query term is not valid [{self}]")
+
+ rhs = self.conform(self.rhs)
+ values = list(rhs)
+
+ if self.is_in_table:
+ # if too many values to create the expression, use a filter instead
+ if self.op in ["==", "!="] and len(values) > self._max_selectors:
+ filter_op = self.generate_filter_op()
+ self.filter = (self.lhs, filter_op, Index(values))
+
+ return self
+ return None
+
+ # equality conditions
+ if self.op in ["==", "!="]:
+ filter_op = self.generate_filter_op()
+ self.filter = (self.lhs, filter_op, Index(values))
+
+ else:
+ raise TypeError(
+ f"passing a filterable condition to a non-table indexer [{self}]"
+ )
+
+ return self
+
+ def generate_filter_op(self, invert: bool = False):
+ if (self.op == "!=" and not invert) or (self.op == "==" and invert):
+ return lambda axis, vals: ~axis.isin(vals)
+ else:
+ return lambda axis, vals: axis.isin(vals)
+
+
+class JointFilterBinOp(FilterBinOp):
+ def format(self):
+ raise NotImplementedError("unable to collapse Joint Filters")
+
+ # error: Signature of "evaluate" incompatible with supertype "BinOp"
+ def evaluate(self) -> Self: # type: ignore[override]
+ return self
+
+
+class ConditionBinOp(BinOp):
+ def __repr__(self) -> str:
+ return pprint_thing(f"[Condition : [{self.condition}]]")
+
+ def invert(self):
+ """invert the condition"""
+ # if self.condition is not None:
+ # self.condition = "~(%s)" % self.condition
+ # return self
+ raise NotImplementedError(
+ "cannot use an invert condition when passing to numexpr"
+ )
+
+ def format(self):
+ """return the actual ne format"""
+ return self.condition
+
+ # error: Signature of "evaluate" incompatible with supertype "BinOp"
+ def evaluate(self) -> Self | None: # type: ignore[override]
+ if not self.is_valid:
+ raise ValueError(f"query term is not valid [{self}]")
+
+ # convert values if we are in the table
+ if not self.is_in_table:
+ return None
+
+ rhs = self.conform(self.rhs)
+ values = [self.convert_value(v) for v in rhs]
+
+ # equality conditions
+ if self.op in ["==", "!="]:
+ # too many values to create the expression?
+ if len(values) <= self._max_selectors:
+ vs = [self.generate(v) for v in values]
+ self.condition = f"({' | '.join(vs)})"
+
+ # use a filter after reading
+ else:
+ return None
+ else:
+ self.condition = self.generate(values[0])
+
+ return self
+
+
+class JointConditionBinOp(ConditionBinOp):
+ # error: Signature of "evaluate" incompatible with supertype "BinOp"
+ def evaluate(self) -> Self: # type: ignore[override]
+ self.condition = f"({self.lhs.condition} {self.op} {self.rhs.condition})"
+ return self
+
+
+class UnaryOp(ops.UnaryOp):
+ def prune(self, klass):
+ if self.op != "~":
+ raise NotImplementedError("UnaryOp only support invert type ops")
+
+ operand = self.operand
+ operand = operand.prune(klass)
+
+ if operand is not None and (
+ (issubclass(klass, ConditionBinOp) and operand.condition is not None)
+ or (
+ not issubclass(klass, ConditionBinOp)
+ and issubclass(klass, FilterBinOp)
+ and operand.filter is not None
+ )
+ ):
+ return operand.invert()
+ return None
+
+
+class PyTablesExprVisitor(BaseExprVisitor):
+ const_type: ClassVar[type[ops.Term]] = Constant
+ term_type: ClassVar[type[Term]] = Term
+
+ def __init__(self, env, engine, parser, **kwargs) -> None:
+ super().__init__(env, engine, parser)
+ for bin_op in self.binary_ops:
+ bin_node = self.binary_op_nodes_map[bin_op]
+ setattr(
+ self,
+ f"visit_{bin_node}",
+ lambda node, bin_op=bin_op: partial(BinOp, bin_op, **kwargs),
+ )
+
+ def visit_UnaryOp(self, node, **kwargs) -> ops.Term | UnaryOp | None:
+ if isinstance(node.op, (ast.Not, ast.Invert)):
+ return UnaryOp("~", self.visit(node.operand))
+ elif isinstance(node.op, ast.USub):
+ return self.const_type(-self.visit(node.operand).value, self.env)
+ elif isinstance(node.op, ast.UAdd):
+ raise NotImplementedError("Unary addition not supported")
+ # TODO: return None might never be reached
+ return None
+
+ def visit_Index(self, node, **kwargs):
+ return self.visit(node.value).value
+
+ def visit_Assign(self, node, **kwargs):
+ cmpr = ast.Compare(
+ ops=[ast.Eq()], left=node.targets[0], comparators=[node.value]
+ )
+ return self.visit(cmpr)
+
+ def visit_Subscript(self, node, **kwargs) -> ops.Term:
+ # only allow simple subscripts
+
+ value = self.visit(node.value)
+ slobj = self.visit(node.slice)
+ try:
+ value = value.value
+ except AttributeError:
+ pass
+
+ if isinstance(slobj, Term):
+ # In py39 np.ndarray lookups with Term containing int raise
+ slobj = slobj.value
+
+ try:
+ return self.const_type(value[slobj], self.env)
+ except TypeError as err:
+ raise ValueError(f"cannot subscript {value!r} with {slobj!r}") from err
+
+ def visit_Attribute(self, node, **kwargs):
+ attr = node.attr
+ value = node.value
+
+ ctx = type(node.ctx)
+ if ctx == ast.Load:
+ # resolve the value
+ resolved = self.visit(value)
+
+ # try to get the value to see if we are another expression
+ try:
+ resolved = resolved.value
+ except AttributeError:
+ pass
+
+ try:
+ return self.term_type(getattr(resolved, attr), self.env)
+ except AttributeError:
+ # something like datetime.datetime where scope is overridden
+ if isinstance(value, ast.Name) and value.id == attr:
+ return resolved
+
+ raise ValueError(f"Invalid Attribute context {ctx.__name__}")
+
+ def translate_In(self, op):
+ return ast.Eq() if isinstance(op, ast.In) else op
+
+ def _rewrite_membership_op(self, node, left, right):
+ return self.visit(node.op), node.op, left, right
+
+
+def _validate_where(w):
+ """
+ Validate that the where statement is of the right type.
+
+ The type may either be String, Expr, or list-like of Exprs.
+
+ Parameters
+ ----------
+ w : String term expression, Expr, or list-like of Exprs.
+
+ Returns
+ -------
+ where : The original where clause if the check was successful.
+
+ Raises
+ ------
+ TypeError : An invalid data type was passed in for w (e.g. dict).
+ """
+ if not (isinstance(w, (PyTablesExpr, str)) or is_list_like(w)):
+ raise TypeError(
+ "where must be passed as a string, PyTablesExpr, "
+ "or list-like of PyTablesExpr"
+ )
+
+ return w
+
+
+class PyTablesExpr(expr.Expr):
+ """
+ Hold a pytables-like expression, comprised of possibly multiple 'terms'.
+
+ Parameters
+ ----------
+ where : string term expression, PyTablesExpr, or list-like of PyTablesExprs
+ queryables : a "kinds" map (dict of column name -> kind), or None if column
+ is non-indexable
+ encoding : an encoding that will encode the query terms
+
+ Returns
+ -------
+ a PyTablesExpr object
+
+ Examples
+ --------
+ 'index>=date'
+ "columns=['A', 'D']"
+ 'columns=A'
+ 'columns==A'
+ "~(columns=['A','B'])"
+ 'index>df.index[3] & string="bar"'
+ '(index>df.index[3] & index<=df.index[6]) | string="bar"'
+ "ts>=Timestamp('2012-02-01')"
+ "major_axis>=20130101"
+ """
+
+ _visitor: PyTablesExprVisitor | None
+ env: PyTablesScope
+ expr: str
+
+ def __init__(
+ self,
+ where,
+ queryables: dict[str, Any] | None = None,
+ encoding=None,
+ scope_level: int = 0,
+ ) -> None:
+ where = _validate_where(where)
+
+ self.encoding = encoding
+ self.condition = None
+ self.filter = None
+ self.terms = None
+ self._visitor = None
+
+ # capture the environment if needed
+ local_dict: _scope.DeepChainMap[Any, Any] | None = None
+
+ if isinstance(where, PyTablesExpr):
+ local_dict = where.env.scope
+ _where = where.expr
+
+ elif is_list_like(where):
+ where = list(where)
+ for idx, w in enumerate(where):
+ if isinstance(w, PyTablesExpr):
+ local_dict = w.env.scope
+ else:
+ where[idx] = _validate_where(w)
+ _where = " & ".join([f"({w})" for w in com.flatten(where)])
+ else:
+ # _validate_where ensures we otherwise have a string
+ _where = where
+
+ self.expr = _where
+ self.env = PyTablesScope(scope_level + 1, local_dict=local_dict)
+
+ if queryables is not None and isinstance(self.expr, str):
+ self.env.queryables.update(queryables)
+ self._visitor = PyTablesExprVisitor(
+ self.env,
+ queryables=queryables,
+ parser="pytables",
+ engine="pytables",
+ encoding=encoding,
+ )
+ self.terms = self.parse()
+
+ def __repr__(self) -> str:
+ if self.terms is not None:
+ return pprint_thing(self.terms)
+ return pprint_thing(self.expr)
+
+ def evaluate(self):
+ """create and return the numexpr condition and filter"""
+ try:
+ self.condition = self.terms.prune(ConditionBinOp)
+ except AttributeError as err:
+ raise ValueError(
+ f"cannot process expression [{self.expr}], [{self}] "
+ "is not a valid condition"
+ ) from err
+ try:
+ self.filter = self.terms.prune(FilterBinOp)
+ except AttributeError as err:
+ raise ValueError(
+ f"cannot process expression [{self.expr}], [{self}] "
+ "is not a valid filter"
+ ) from err
+
+ return self.condition, self.filter
+
+
+class TermValue:
+ """hold a term value the we use to construct a condition/filter"""
+
+ def __init__(self, value, converted, kind: str) -> None:
+ assert isinstance(kind, str), kind
+ self.value = value
+ self.converted = converted
+ self.kind = kind
+
+ def tostring(self, encoding) -> str:
+ """quote the string if not encoded else encode and return"""
+ if self.kind == "string":
+ if encoding is not None:
+ return str(self.converted)
+ return f'"{self.converted}"'
+ elif self.kind == "float":
+ # python 2 str(float) is not always
+ # round-trippable so use repr()
+ return repr(self.converted)
+ return str(self.converted)
+
+
+def maybe_expression(s) -> bool:
+ """loose checking if s is a pytables-acceptable expression"""
+ if not isinstance(s, str):
+ return False
+ operations = PyTablesExprVisitor.binary_ops + PyTablesExprVisitor.unary_ops + ("=",)
+
+ # make sure we have an op at least
+ return any(op in s for op in operations)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/scope.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/scope.py
new file mode 100644
index 0000000000000000000000000000000000000000..7945b72725a14773a4e8fd812efae7b5e96a22dd
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/computation/scope.py
@@ -0,0 +1,356 @@
+"""
+Module for scope operations
+"""
+
+from __future__ import annotations
+
+from collections import ChainMap
+import datetime
+import inspect
+from io import StringIO
+import itertools
+import pprint
+import struct
+import sys
+from typing import TypeVar
+
+import numpy as np
+
+from pandas._libs.tslibs import Timestamp
+from pandas.errors import UndefinedVariableError
+
+_KT = TypeVar("_KT")
+_VT = TypeVar("_VT")
+
+
+# https://docs.python.org/3/library/collections.html#chainmap-examples-and-recipes
+class DeepChainMap(ChainMap[_KT, _VT]):
+ """
+ Variant of ChainMap that allows direct updates to inner scopes.
+
+ Only works when all passed mapping are mutable.
+ """
+
+ def __setitem__(self, key: _KT, value: _VT) -> None:
+ for mapping in self.maps:
+ if key in mapping:
+ mapping[key] = value
+ return
+ self.maps[0][key] = value
+
+ def __delitem__(self, key: _KT) -> None:
+ """
+ Raises
+ ------
+ KeyError
+ If `key` doesn't exist.
+ """
+ for mapping in self.maps:
+ if key in mapping:
+ del mapping[key]
+ return
+ raise KeyError(key)
+
+
+def ensure_scope(
+ level: int, global_dict=None, local_dict=None, resolvers=(), target=None
+) -> Scope:
+ """Ensure that we are grabbing the correct scope."""
+ return Scope(
+ level + 1,
+ global_dict=global_dict,
+ local_dict=local_dict,
+ resolvers=resolvers,
+ target=target,
+ )
+
+
+def _replacer(x) -> str:
+ """
+ Replace a number with its hexadecimal representation. Used to tag
+ temporary variables with their calling scope's id.
+ """
+ # get the hex repr of the binary char and remove 0x and pad by pad_size
+ # zeros
+ try:
+ hexin = ord(x)
+ except TypeError:
+ # bytes literals masquerade as ints when iterating in py3
+ hexin = x
+
+ return hex(hexin)
+
+
+def _raw_hex_id(obj) -> str:
+ """Return the padded hexadecimal id of ``obj``."""
+ # interpret as a pointer since that's what really what id returns
+ packed = struct.pack("@P", id(obj))
+ return "".join([_replacer(x) for x in packed])
+
+
+DEFAULT_GLOBALS = {
+ "Timestamp": Timestamp,
+ "datetime": datetime.datetime,
+ "True": True,
+ "False": False,
+ "list": list,
+ "tuple": tuple,
+ "inf": np.inf,
+ "Inf": np.inf,
+}
+
+
+def _get_pretty_string(obj) -> str:
+ """
+ Return a prettier version of obj.
+
+ Parameters
+ ----------
+ obj : object
+ Object to pretty print
+
+ Returns
+ -------
+ str
+ Pretty print object repr
+ """
+ sio = StringIO()
+ pprint.pprint(obj, stream=sio)
+ return sio.getvalue()
+
+
+class Scope:
+ """
+ Object to hold scope, with a few bells to deal with some custom syntax
+ and contexts added by pandas.
+
+ Parameters
+ ----------
+ level : int
+ global_dict : dict or None, optional, default None
+ local_dict : dict or Scope or None, optional, default None
+ resolvers : list-like or None, optional, default None
+ target : object
+
+ Attributes
+ ----------
+ level : int
+ scope : DeepChainMap
+ target : object
+ temps : dict
+ """
+
+ __slots__ = ["level", "resolvers", "scope", "target", "temps"]
+ level: int
+ scope: DeepChainMap
+ resolvers: DeepChainMap
+ temps: dict
+
+ def __init__(
+ self, level: int, global_dict=None, local_dict=None, resolvers=(), target=None
+ ) -> None:
+ self.level = level + 1
+
+ # shallow copy because we don't want to keep filling this up with what
+ # was there before if there are multiple calls to Scope/_ensure_scope
+ self.scope = DeepChainMap(DEFAULT_GLOBALS.copy())
+ self.target = target
+
+ if isinstance(local_dict, Scope):
+ self.scope.update(local_dict.scope)
+ if local_dict.target is not None:
+ self.target = local_dict.target
+ self._update(local_dict.level)
+
+ frame = sys._getframe(self.level)
+
+ try:
+ # shallow copy here because we don't want to replace what's in
+ # scope when we align terms (alignment accesses the underlying
+ # numpy array of pandas objects)
+ scope_global = self.scope.new_child(
+ (global_dict if global_dict is not None else frame.f_globals).copy()
+ )
+ self.scope = DeepChainMap(scope_global)
+ if not isinstance(local_dict, Scope):
+ scope_local = self.scope.new_child(
+ (local_dict if local_dict is not None else frame.f_locals).copy()
+ )
+ self.scope = DeepChainMap(scope_local)
+ finally:
+ del frame
+
+ # assumes that resolvers are going from outermost scope to inner
+ if isinstance(local_dict, Scope):
+ resolvers += tuple(local_dict.resolvers.maps)
+ self.resolvers = DeepChainMap(*resolvers)
+ self.temps = {}
+
+ def __repr__(self) -> str:
+ scope_keys = _get_pretty_string(list(self.scope.keys()))
+ res_keys = _get_pretty_string(list(self.resolvers.keys()))
+ return f"{type(self).__name__}(scope={scope_keys}, resolvers={res_keys})"
+
+ @property
+ def has_resolvers(self) -> bool:
+ """
+ Return whether we have any extra scope.
+
+ For example, DataFrames pass Their columns as resolvers during calls to
+ ``DataFrame.eval()`` and ``DataFrame.query()``.
+
+ Returns
+ -------
+ hr : bool
+ """
+ return bool(len(self.resolvers))
+
+ def resolve(self, key: str, is_local: bool):
+ """
+ Resolve a variable name in a possibly local context.
+
+ Parameters
+ ----------
+ key : str
+ A variable name
+ is_local : bool
+ Flag indicating whether the variable is local or not (prefixed with
+ the '@' symbol)
+
+ Returns
+ -------
+ value : object
+ The value of a particular variable
+ """
+ try:
+ # only look for locals in outer scope
+ if is_local:
+ return self.scope[key]
+
+ # not a local variable so check in resolvers if we have them
+ if self.has_resolvers:
+ return self.resolvers[key]
+
+ # if we're here that means that we have no locals and we also have
+ # no resolvers
+ assert not is_local and not self.has_resolvers
+ return self.scope[key]
+ except KeyError:
+ try:
+ # last ditch effort we look in temporaries
+ # these are created when parsing indexing expressions
+ # e.g., df[df > 0]
+ return self.temps[key]
+ except KeyError as err:
+ raise UndefinedVariableError(key, is_local) from err
+
+ def swapkey(self, old_key: str, new_key: str, new_value=None) -> None:
+ """
+ Replace a variable name, with a potentially new value.
+
+ Parameters
+ ----------
+ old_key : str
+ Current variable name to replace
+ new_key : str
+ New variable name to replace `old_key` with
+ new_value : object
+ Value to be replaced along with the possible renaming
+ """
+ if self.has_resolvers:
+ maps = self.resolvers.maps + self.scope.maps
+ else:
+ maps = self.scope.maps
+
+ maps.append(self.temps)
+
+ for mapping in maps:
+ if old_key in mapping:
+ mapping[new_key] = new_value
+ return
+
+ def _get_vars(self, stack, scopes: list[str]) -> None:
+ """
+ Get specifically scoped variables from a list of stack frames.
+
+ Parameters
+ ----------
+ stack : list
+ A list of stack frames as returned by ``inspect.stack()``
+ scopes : sequence of strings
+ A sequence containing valid stack frame attribute names that
+ evaluate to a dictionary. For example, ('locals', 'globals')
+ """
+ variables = itertools.product(scopes, stack)
+ for scope, (frame, _, _, _, _, _) in variables:
+ try:
+ d = getattr(frame, f"f_{scope}")
+ self.scope = DeepChainMap(self.scope.new_child(d))
+ finally:
+ # won't remove it, but DECREF it
+ # in Py3 this probably isn't necessary since frame won't be
+ # scope after the loop
+ del frame
+
+ def _update(self, level: int) -> None:
+ """
+ Update the current scope by going back `level` levels.
+
+ Parameters
+ ----------
+ level : int
+ """
+ sl = level + 1
+
+ # add sl frames to the scope starting with the
+ # most distant and overwriting with more current
+ # makes sure that we can capture variable scope
+ stack = inspect.stack()
+
+ try:
+ self._get_vars(stack[:sl], scopes=["locals"])
+ finally:
+ del stack[:], stack
+
+ def add_tmp(self, value) -> str:
+ """
+ Add a temporary variable to the scope.
+
+ Parameters
+ ----------
+ value : object
+ An arbitrary object to be assigned to a temporary variable.
+
+ Returns
+ -------
+ str
+ The name of the temporary variable created.
+ """
+ name = f"{type(value).__name__}_{self.ntemps}_{_raw_hex_id(self)}"
+
+ # add to inner most scope
+ assert name not in self.temps
+ self.temps[name] = value
+ assert name in self.temps
+
+ # only increment if the variable gets put in the scope
+ return name
+
+ @property
+ def ntemps(self) -> int:
+ """The number of temporary variables in this scope"""
+ return len(self.temps)
+
+ @property
+ def full_scope(self) -> DeepChainMap:
+ """
+ Return the full scope for use with passing to engines transparently
+ as a mapping.
+
+ Returns
+ -------
+ vars : DeepChainMap
+ All variables in this scope.
+ """
+ maps = [self.temps, *self.resolvers.maps, *self.scope.maps]
+ return DeepChainMap(*maps)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/groupby/categorical.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/groupby/categorical.py
new file mode 100644
index 0000000000000000000000000000000000000000..0daa6d8f8101b9b1422bc6826746e9f1e1ab6bb8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/groupby/categorical.py
@@ -0,0 +1,83 @@
+from __future__ import annotations
+
+import numpy as np
+
+from pandas.core.algorithms import unique1d
+from pandas.core.arrays.categorical import (
+ Categorical,
+ CategoricalDtype,
+ recode_for_categories,
+)
+
+
+def recode_for_groupby(c: Categorical, sort: bool, observed: bool) -> Categorical:
+ """
+ Code the categories to ensure we can groupby for categoricals.
+
+ If observed=True, we return a new Categorical with the observed
+ categories only.
+
+ If sort=False, return a copy of self, coded with categories as
+ returned by .unique(), followed by any categories not appearing in
+ the data. If sort=True, return self.
+
+ This method is needed solely to ensure the categorical index of the
+ GroupBy result has categories in the order of appearance in the data
+ (GH-8868).
+
+ Parameters
+ ----------
+ c : Categorical
+ sort : bool
+ The value of the sort parameter groupby was called with.
+ observed : bool
+ Account only for the observed values
+
+ Returns
+ -------
+ Categorical
+ If sort=False, the new categories are set to the order of
+ appearance in codes (unless ordered=True, in which case the
+ original order is preserved), followed by any unrepresented
+ categories in the original order.
+ """
+ # we only care about observed values
+ if observed:
+ # In cases with c.ordered, this is equivalent to
+ # return c.remove_unused_categories(), c
+
+ take_codes = unique1d(c.codes[c.codes != -1])
+
+ if sort:
+ take_codes = np.sort(take_codes)
+
+ # we recode according to the uniques
+ categories = c.categories.take(take_codes)
+ codes = recode_for_categories(c.codes, c.categories, categories, copy=False)
+
+ # return a new categorical that maps our new codes
+ # and categories
+ dtype = CategoricalDtype(categories, ordered=c.ordered)
+ return Categorical._simple_new(codes, dtype=dtype)
+
+ # Already sorted according to c.categories; all is fine
+ if sort:
+ return c
+
+ # sort=False should order groups in as-encountered order (GH-8868)
+
+ # GH:46909: Re-ordering codes faster than using (set|add|reorder)_categories
+ # GH 38140: exclude nan from indexer for categories
+ unique_notnan_codes = unique1d(c.codes[c.codes != -1])
+ if sort:
+ unique_notnan_codes = np.sort(unique_notnan_codes)
+ if (num_cat := len(c.categories)) > len(unique_notnan_codes):
+ # GH 13179: All categories need to be present, even if missing from the data
+ missing_codes = np.setdiff1d(
+ np.arange(num_cat), unique_notnan_codes, assume_unique=True
+ )
+ take_codes = np.concatenate((unique_notnan_codes, missing_codes))
+ else:
+ take_codes = unique_notnan_codes
+
+ return Categorical(c, c.categories.take(take_codes))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/groupby/indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/groupby/indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc9cf9bcd1e64ba3550a2c4b74f765c5cc7234d0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/groupby/indexing.py
@@ -0,0 +1,304 @@
+from __future__ import annotations
+
+from collections.abc import Iterable
+from typing import (
+ TYPE_CHECKING,
+ Literal,
+ cast,
+)
+
+import numpy as np
+
+from pandas.util._decorators import (
+ cache_readonly,
+ doc,
+)
+
+from pandas.core.dtypes.common import (
+ is_integer,
+ is_list_like,
+)
+
+if TYPE_CHECKING:
+ from pandas._typing import PositionalIndexer
+
+ from pandas import (
+ DataFrame,
+ Series,
+ )
+ from pandas.core.groupby import groupby
+
+
+class GroupByIndexingMixin:
+ """
+ Mixin for adding ._positional_selector to GroupBy.
+ """
+
+ @cache_readonly
+ def _positional_selector(self) -> GroupByPositionalSelector:
+ """
+ Return positional selection for each group.
+
+ ``groupby._positional_selector[i:j]`` is similar to
+ ``groupby.apply(lambda x: x.iloc[i:j])``
+ but much faster and preserves the original index and order.
+
+ ``_positional_selector[]`` is compatible with and extends :meth:`~GroupBy.head`
+ and :meth:`~GroupBy.tail`. For example:
+
+ - ``head(5)``
+ - ``_positional_selector[5:-5]``
+ - ``tail(5)``
+
+ together return all the rows.
+
+ Allowed inputs for the index are:
+
+ - An integer valued iterable, e.g. ``range(2, 4)``.
+ - A comma separated list of integers and slices, e.g. ``5``, ``2, 4``, ``2:4``.
+
+ The output format is the same as :meth:`~GroupBy.head` and
+ :meth:`~GroupBy.tail`, namely
+ a subset of the ``DataFrame`` or ``Series`` with the index and order preserved.
+
+ Returns
+ -------
+ Series
+ The filtered subset of the original Series.
+ DataFrame
+ The filtered subset of the original DataFrame.
+
+ See Also
+ --------
+ DataFrame.iloc : Purely integer-location based indexing for selection by
+ position.
+ GroupBy.head : Return first n rows of each group.
+ GroupBy.tail : Return last n rows of each group.
+ GroupBy.nth : Take the nth row from each group if n is an int, or a
+ subset of rows, if n is a list of ints.
+
+ Notes
+ -----
+ - The slice step cannot be negative.
+ - If the index specification results in overlaps, the item is not duplicated.
+ - If the index specification changes the order of items, then
+ they are returned in their original order.
+ By contrast, ``DataFrame.iloc`` can change the row order.
+ - ``groupby()`` parameters such as as_index and dropna are ignored.
+
+ The differences between ``_positional_selector[]`` and :meth:`~GroupBy.nth`
+ with ``as_index=False`` are:
+
+ - Input to ``_positional_selector`` can include
+ one or more slices whereas ``nth``
+ just handles an integer or a list of integers.
+ - ``_positional_selector`` can accept a slice relative to the
+ last row of each group.
+ - ``_positional_selector`` does not have an equivalent to the
+ ``nth()`` ``dropna`` parameter.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame(
+ ... [["a", 1], ["a", 2], ["a", 3], ["b", 4], ["b", 5]], columns=["A", "B"]
+ ... )
+ >>> df.groupby("A")._positional_selector[1:2]
+ A B
+ 1 a 2
+ 4 b 5
+
+ >>> df.groupby("A")._positional_selector[1, -1]
+ A B
+ 1 a 2
+ 2 a 3
+ 4 b 5
+ """
+ if TYPE_CHECKING:
+ groupby_self = cast(groupby.GroupBy, self)
+ else:
+ groupby_self = self
+
+ return GroupByPositionalSelector(groupby_self)
+
+ def _make_mask_from_positional_indexer(
+ self,
+ arg: PositionalIndexer | tuple,
+ ) -> np.ndarray:
+ if is_list_like(arg):
+ if all(is_integer(i) for i in cast(Iterable, arg)):
+ mask = self._make_mask_from_list(cast(Iterable[int], arg))
+ else:
+ mask = self._make_mask_from_tuple(cast(tuple, arg))
+
+ elif isinstance(arg, slice):
+ mask = self._make_mask_from_slice(arg)
+ elif is_integer(arg):
+ mask = self._make_mask_from_int(cast(int, arg))
+ else:
+ raise TypeError(
+ f"Invalid index {type(arg)}. "
+ "Must be integer, list-like, slice or a tuple of "
+ "integers and slices"
+ )
+
+ if isinstance(mask, bool):
+ if mask:
+ mask = self._ascending_count >= 0
+ else:
+ mask = self._ascending_count < 0
+
+ return cast(np.ndarray, mask)
+
+ def _make_mask_from_int(self, arg: int) -> np.ndarray:
+ if arg >= 0:
+ return self._ascending_count == arg
+ else:
+ return self._descending_count == (-arg - 1)
+
+ def _make_mask_from_list(self, args: Iterable[int]) -> bool | np.ndarray:
+ positive = [arg for arg in args if arg >= 0]
+ negative = [-arg - 1 for arg in args if arg < 0]
+
+ mask: bool | np.ndarray = False
+
+ if positive:
+ mask |= np.isin(self._ascending_count, positive)
+
+ if negative:
+ mask |= np.isin(self._descending_count, negative)
+
+ return mask
+
+ def _make_mask_from_tuple(self, args: tuple) -> bool | np.ndarray:
+ mask: bool | np.ndarray = False
+
+ for arg in args:
+ if is_integer(arg):
+ mask |= self._make_mask_from_int(cast(int, arg))
+ elif isinstance(arg, slice):
+ mask |= self._make_mask_from_slice(arg)
+ else:
+ raise ValueError(
+ f"Invalid argument {type(arg)}. Should be int or slice."
+ )
+
+ return mask
+
+ def _make_mask_from_slice(self, arg: slice) -> bool | np.ndarray:
+ start = arg.start
+ stop = arg.stop
+ step = arg.step
+
+ if step is not None and step < 0:
+ raise ValueError(f"Invalid step {step}. Must be non-negative")
+
+ mask: bool | np.ndarray = True
+
+ if step is None:
+ step = 1
+
+ if start is None:
+ if step > 1:
+ mask &= self._ascending_count % step == 0
+
+ elif start >= 0:
+ mask &= self._ascending_count >= start
+
+ if step > 1:
+ mask &= (self._ascending_count - start) % step == 0
+
+ else:
+ mask &= self._descending_count < -start
+
+ offset_array = self._descending_count + start + 1
+ limit_array = (
+ self._ascending_count + self._descending_count + (start + 1)
+ ) < 0
+ offset_array = np.where(limit_array, self._ascending_count, offset_array)
+
+ mask &= offset_array % step == 0
+
+ if stop is not None:
+ if stop >= 0:
+ mask &= self._ascending_count < stop
+ else:
+ mask &= self._descending_count >= -stop
+
+ return mask
+
+ @cache_readonly
+ def _ascending_count(self) -> np.ndarray:
+ if TYPE_CHECKING:
+ groupby_self = cast(groupby.GroupBy, self)
+ else:
+ groupby_self = self
+
+ return groupby_self._cumcount_array()
+
+ @cache_readonly
+ def _descending_count(self) -> np.ndarray:
+ if TYPE_CHECKING:
+ groupby_self = cast(groupby.GroupBy, self)
+ else:
+ groupby_self = self
+
+ return groupby_self._cumcount_array(ascending=False)
+
+
+@doc(GroupByIndexingMixin._positional_selector)
+class GroupByPositionalSelector:
+ def __init__(self, groupby_object: groupby.GroupBy) -> None:
+ self.groupby_object = groupby_object
+
+ def __getitem__(self, arg: PositionalIndexer | tuple) -> DataFrame | Series:
+ """
+ Select by positional index per group.
+
+ Implements GroupBy._positional_selector
+
+ Parameters
+ ----------
+ arg : PositionalIndexer | tuple
+ Allowed values are:
+ - int
+ - int valued iterable such as list or range
+ - slice with step either None or positive
+ - tuple of integers and slices
+
+ Returns
+ -------
+ Series
+ The filtered subset of the original groupby Series.
+ DataFrame
+ The filtered subset of the original groupby DataFrame.
+
+ See Also
+ --------
+ DataFrame.iloc : Integer-location based indexing for selection by position.
+ GroupBy.head : Return first n rows of each group.
+ GroupBy.tail : Return last n rows of each group.
+ GroupBy._positional_selector : Return positional selection for each group.
+ GroupBy.nth : Take the nth row from each group if n is an int, or a
+ subset of rows, if n is a list of ints.
+ """
+ mask = self.groupby_object._make_mask_from_positional_indexer(arg)
+ return self.groupby_object._mask_selected_obj(mask)
+
+
+class GroupByNthSelector:
+ """
+ Dynamically substituted for GroupBy.nth to enable both call and index
+ """
+
+ def __init__(self, groupby_object: groupby.GroupBy) -> None:
+ self.groupby_object = groupby_object
+
+ def __call__(
+ self,
+ n: PositionalIndexer | tuple,
+ dropna: Literal["any", "all"] | None = None,
+ ) -> DataFrame | Series:
+ return self.groupby_object._nth(n, dropna)
+
+ def __getitem__(self, n: PositionalIndexer | tuple) -> DataFrame | Series:
+ return self.groupby_object._nth(n)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/groupby/ops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/groupby/ops.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc525156a92e656042720da72a1cbdb36d433aa3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/groupby/ops.py
@@ -0,0 +1,1281 @@
+"""
+Provide classes to perform the groupby aggregate operations.
+
+These are not exposed to the user and provide implementations of the grouping
+operations, primarily in cython. These classes (BaseGrouper and BinGrouper)
+are contained *in* the SeriesGroupBy and DataFrameGroupBy objects.
+"""
+
+from __future__ import annotations
+
+import collections
+import functools
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Generic,
+ final,
+)
+
+import numpy as np
+
+from pandas._libs import (
+ NaT,
+ lib,
+)
+import pandas._libs.groupby as libgroupby
+from pandas._typing import (
+ ArrayLike,
+ AxisInt,
+ NDFrameT,
+ Shape,
+ npt,
+)
+from pandas.errors import AbstractMethodError
+from pandas.util._decorators import cache_readonly
+
+from pandas.core.dtypes.cast import (
+ maybe_downcast_to_dtype,
+)
+from pandas.core.dtypes.common import (
+ ensure_float64,
+ ensure_int64,
+ ensure_platform_int,
+ ensure_uint64,
+ is_1d_only_ea_dtype,
+)
+from pandas.core.dtypes.missing import (
+ isna,
+ maybe_fill,
+)
+
+from pandas.core.arrays import Categorical
+from pandas.core.frame import DataFrame
+from pandas.core.groupby import grouper
+from pandas.core.indexes.api import (
+ CategoricalIndex,
+ Index,
+ MultiIndex,
+ ensure_index,
+)
+from pandas.core.series import Series
+from pandas.core.sorting import (
+ compress_group_index,
+ decons_obs_group_ids,
+ get_group_index,
+ get_group_index_sorter,
+ get_indexer_dict,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Callable,
+ Generator,
+ Hashable,
+ Iterator,
+ )
+
+ from pandas.core.generic import NDFrame
+
+
+def check_result_array(obj, dtype) -> None:
+ # Our operation is supposed to be an aggregation/reduction. If
+ # it returns an ndarray, this likely means an invalid operation has
+ # been passed. See test_apply_without_aggregation, test_agg_must_agg
+ if isinstance(obj, np.ndarray):
+ if dtype != object:
+ # If it is object dtype, the function can be a reduction/aggregation
+ # and still return an ndarray e.g. test_agg_over_numpy_arrays
+ raise ValueError("Must produce aggregated value")
+
+
+def extract_result(res):
+ """
+ Extract the result object, it might be a 0-dim ndarray
+ or a len-1 0-dim, or a scalar
+ """
+ if hasattr(res, "_values"):
+ # Preserve EA
+ res = res._values
+ if res.ndim == 1 and len(res) == 1:
+ # see test_agg_lambda_with_timezone, test_resampler_grouper.py::test_apply
+ res = res[0]
+ return res
+
+
+class WrappedCythonOp:
+ """
+ Dispatch logic for functions defined in _libs.groupby
+
+ Parameters
+ ----------
+ kind: str
+ Whether the operation is an aggregate or transform.
+ how: str
+ Operation name, e.g. "mean".
+ has_dropped_na: bool
+ True precisely when dropna=True and the grouper contains a null value.
+ """
+
+ # Functions for which we do _not_ attempt to cast the cython result
+ # back to the original dtype.
+ cast_blocklist = frozenset(
+ ["any", "all", "rank", "count", "size", "idxmin", "idxmax"]
+ )
+
+ def __init__(self, kind: str, how: str, has_dropped_na: bool) -> None:
+ self.kind = kind
+ self.how = how
+ self.has_dropped_na = has_dropped_na
+
+ _CYTHON_FUNCTIONS: dict[str, dict] = {
+ "aggregate": {
+ "any": functools.partial(libgroupby.group_any_all, val_test="any"),
+ "all": functools.partial(libgroupby.group_any_all, val_test="all"),
+ "sum": "group_sum",
+ "prod": "group_prod",
+ "idxmin": functools.partial(libgroupby.group_idxmin_idxmax, name="idxmin"),
+ "idxmax": functools.partial(libgroupby.group_idxmin_idxmax, name="idxmax"),
+ "min": "group_min",
+ "max": "group_max",
+ "mean": "group_mean",
+ "median": "group_median_float64",
+ "var": "group_var",
+ "std": functools.partial(libgroupby.group_var, name="std"),
+ "sem": functools.partial(libgroupby.group_var, name="sem"),
+ "skew": "group_skew",
+ "kurt": "group_kurt",
+ "first": "group_nth",
+ "last": "group_last",
+ "ohlc": "group_ohlc",
+ },
+ "transform": {
+ "cumprod": "group_cumprod",
+ "cumsum": "group_cumsum",
+ "cummin": "group_cummin",
+ "cummax": "group_cummax",
+ "rank": "group_rank",
+ },
+ }
+
+ _cython_arity = {"ohlc": 4} # OHLC
+
+ @classmethod
+ def get_kind_from_how(cls, how: str) -> str:
+ if how in cls._CYTHON_FUNCTIONS["aggregate"]:
+ return "aggregate"
+ return "transform"
+
+ # Note: we make this a classmethod and pass kind+how so that caching
+ # works at the class level and not the instance level
+ @classmethod
+ @functools.cache
+ def _get_cython_function(
+ cls, kind: str, how: str, dtype: np.dtype, is_numeric: bool
+ ):
+ dtype_str = dtype.name
+ ftype = cls._CYTHON_FUNCTIONS[kind][how]
+
+ # see if there is a fused-type version of function
+ # only valid for numeric
+ if callable(ftype):
+ f = ftype
+ else:
+ f = getattr(libgroupby, ftype)
+ if is_numeric:
+ return f
+ elif dtype == np.dtype(object):
+ if how in ["median", "cumprod"]:
+ # no fused types -> no __signatures__
+ raise NotImplementedError(
+ f"function is not implemented for this dtype: "
+ f"[how->{how},dtype->{dtype_str}]"
+ )
+ elif how in ["std", "sem", "idxmin", "idxmax"]:
+ # We have a partial object that does not have __signatures__
+ return f
+ elif how in ["skew", "kurt"]:
+ # _get_cython_vals will convert to float64
+ pass
+ elif "object" not in f.__signatures__:
+ # raise NotImplementedError here rather than TypeError later
+ raise NotImplementedError(
+ f"function is not implemented for this dtype: "
+ f"[how->{how},dtype->{dtype_str}]"
+ )
+ return f
+ else:
+ raise NotImplementedError(
+ "This should not be reached. Please report a bug at "
+ "github.com/pandas-dev/pandas/",
+ dtype,
+ )
+
+ def _get_cython_vals(self, values: np.ndarray) -> np.ndarray:
+ """
+ Cast numeric dtypes to float64 for functions that only support that.
+
+ Parameters
+ ----------
+ values : np.ndarray
+
+ Returns
+ -------
+ values : np.ndarray
+ """
+ how = self.how
+
+ if how in ["median", "std", "sem", "skew", "kurt"]:
+ # median only has a float64 implementation
+ # We should only get here with is_numeric, as non-numeric cases
+ # should raise in _get_cython_function
+ values = ensure_float64(values)
+
+ elif values.dtype.kind in "iu":
+ if how in ["var", "mean"] or (
+ self.kind == "transform" and self.has_dropped_na
+ ):
+ # has_dropped_na check need for test_null_group_str_transformer
+ # result may still include NaN, so we have to cast
+ values = ensure_float64(values)
+
+ elif how in ["sum", "ohlc", "prod", "cumsum", "cumprod"]:
+ # Avoid overflow during group op
+ if values.dtype.kind == "i":
+ values = ensure_int64(values)
+ else:
+ values = ensure_uint64(values)
+
+ return values
+
+ def _get_output_shape(self, ngroups: int, values: np.ndarray) -> Shape:
+ how = self.how
+ kind = self.kind
+
+ arity = self._cython_arity.get(how, 1)
+
+ out_shape: Shape
+ if how == "ohlc":
+ out_shape = (ngroups, arity)
+ elif arity > 1:
+ raise NotImplementedError(
+ "arity of more than 1 is not supported for the 'how' argument"
+ )
+ elif kind == "transform":
+ out_shape = values.shape
+ else:
+ out_shape = (ngroups, *values.shape[1:])
+ return out_shape
+
+ def _get_out_dtype(self, dtype: np.dtype) -> np.dtype:
+ how = self.how
+
+ if how == "rank":
+ out_dtype = "float64"
+ elif how in ["idxmin", "idxmax"]:
+ # The Cython implementation only produces the row number; we'll take
+ # from the index using this in post processing
+ out_dtype = "intp"
+ elif dtype.kind in "iufcb":
+ out_dtype = f"{dtype.kind}{dtype.itemsize}"
+ else:
+ out_dtype = "object"
+ return np.dtype(out_dtype)
+
+ def _get_result_dtype(self, dtype: np.dtype) -> np.dtype:
+ """
+ Get the desired dtype of a result based on the
+ input dtype and how it was computed.
+
+ Parameters
+ ----------
+ dtype : np.dtype
+
+ Returns
+ -------
+ np.dtype
+ The desired dtype of the result.
+ """
+ how = self.how
+
+ if how in ["sum", "cumsum", "sum", "prod", "cumprod"]:
+ if dtype == np.dtype(bool):
+ return np.dtype(np.int64)
+ elif how in ["mean", "median", "var", "std", "sem"]:
+ if dtype.kind in "fc":
+ return dtype
+ elif dtype.kind in "iub":
+ return np.dtype(np.float64)
+ return dtype
+
+ @final
+ def _cython_op_ndim_compat(
+ self,
+ values: np.ndarray,
+ *,
+ min_count: int,
+ ngroups: int,
+ comp_ids: np.ndarray,
+ mask: npt.NDArray[np.bool_] | None = None,
+ result_mask: npt.NDArray[np.bool_] | None = None,
+ initial: Any = 0,
+ **kwargs,
+ ) -> np.ndarray:
+ if values.ndim == 1:
+ # expand to 2d, dispatch, then squeeze if appropriate
+ values2d = values[None, :]
+ if mask is not None:
+ mask = mask[None, :]
+ if result_mask is not None:
+ result_mask = result_mask[None, :]
+ res = self._call_cython_op(
+ values2d,
+ min_count=min_count,
+ ngroups=ngroups,
+ comp_ids=comp_ids,
+ mask=mask,
+ result_mask=result_mask,
+ initial=initial,
+ **kwargs,
+ )
+ if res.shape[0] == 1:
+ return res[0]
+
+ # otherwise we have OHLC
+ return res.T
+
+ return self._call_cython_op(
+ values,
+ min_count=min_count,
+ ngroups=ngroups,
+ comp_ids=comp_ids,
+ mask=mask,
+ result_mask=result_mask,
+ initial=initial,
+ **kwargs,
+ )
+
+ @final
+ def _call_cython_op(
+ self,
+ values: np.ndarray, # np.ndarray[ndim=2]
+ *,
+ min_count: int,
+ ngroups: int,
+ comp_ids: np.ndarray,
+ mask: npt.NDArray[np.bool_] | None,
+ result_mask: npt.NDArray[np.bool_] | None,
+ initial: Any = 0,
+ **kwargs,
+ ) -> np.ndarray: # np.ndarray[ndim=2]
+ orig_values = values
+
+ dtype = values.dtype
+ is_numeric = dtype.kind in "iufcb"
+
+ is_datetimelike = dtype.kind in "mM"
+
+ if self.how in ["any", "all"]:
+ if mask is None:
+ mask = isna(values)
+
+ if is_datetimelike:
+ values = values.view("int64")
+ is_numeric = True
+ elif dtype.kind == "b":
+ values = values.view("uint8")
+ if values.dtype == "float16":
+ values = values.astype(np.float32)
+
+ if self.how in ["any", "all"]:
+ if dtype == object:
+ if kwargs["skipna"]:
+ # GH#37501: don't raise on pd.NA when skipna=True
+ if mask is not None and mask.any():
+ # mask on original values computed separately
+ values = values.copy()
+ values[mask] = True
+ values = values.astype(bool, copy=False).view(np.int8)
+ is_numeric = True
+
+ values = values.T
+ if mask is not None:
+ mask = mask.T
+ if result_mask is not None:
+ result_mask = result_mask.T
+
+ out_shape = self._get_output_shape(ngroups, values)
+ func = self._get_cython_function(self.kind, self.how, values.dtype, is_numeric)
+ values = self._get_cython_vals(values)
+ out_dtype = self._get_out_dtype(values.dtype)
+
+ result = maybe_fill(np.empty(out_shape, dtype=out_dtype))
+ if self.kind == "aggregate":
+ counts = np.zeros(ngroups, dtype=np.int64)
+ if self.how in [
+ "idxmin",
+ "idxmax",
+ "min",
+ "max",
+ "mean",
+ "last",
+ "first",
+ "sum",
+ "median",
+ ]:
+ if self.how == "sum":
+ # pass in through kwargs only for sum (other functions don't have
+ # the keyword)
+ kwargs["initial"] = initial
+ func(
+ out=result,
+ counts=counts,
+ values=values,
+ labels=comp_ids,
+ min_count=min_count,
+ mask=mask,
+ result_mask=result_mask,
+ is_datetimelike=is_datetimelike,
+ **kwargs,
+ )
+ elif self.how in ["sem", "std", "var", "ohlc", "prod"]:
+ if self.how in ["std", "sem"]:
+ kwargs["is_datetimelike"] = is_datetimelike
+ func(
+ result,
+ counts,
+ values,
+ comp_ids,
+ min_count=min_count,
+ mask=mask,
+ result_mask=result_mask,
+ **kwargs,
+ )
+ elif self.how in ["any", "all"]:
+ func(
+ out=result,
+ values=values,
+ labels=comp_ids,
+ mask=mask,
+ result_mask=result_mask,
+ **kwargs,
+ )
+ result = result.astype(bool, copy=False)
+ elif self.how in ["skew", "kurt"]:
+ func(
+ out=result,
+ counts=counts,
+ values=values,
+ labels=comp_ids,
+ mask=mask,
+ result_mask=result_mask,
+ **kwargs,
+ )
+ if dtype == object:
+ result = result.astype(object)
+
+ else:
+ raise NotImplementedError(f"{self.how} is not implemented")
+ else:
+ # TODO: min_count
+ if self.how != "rank":
+ # TODO: should rank take result_mask?
+ kwargs["result_mask"] = result_mask
+ func(
+ out=result,
+ values=values,
+ labels=comp_ids,
+ ngroups=ngroups,
+ is_datetimelike=is_datetimelike,
+ mask=mask,
+ **kwargs,
+ )
+
+ if self.kind == "aggregate" and self.how not in ["idxmin", "idxmax"]:
+ # i.e. counts is defined. Locations where count None:
+ if values.ndim > 2:
+ raise NotImplementedError("number of dimensions is currently limited to 2")
+ if values.ndim == 2:
+ assert axis == 1, axis
+ elif not is_1d_only_ea_dtype(values.dtype):
+ # Note: it is *not* the case that axis is always 0 for 1-dim values,
+ # as we can have 1D ExtensionArrays that we need to treat as 2D
+ assert axis == 0
+
+ @final
+ def cython_operation(
+ self,
+ *,
+ values: ArrayLike,
+ axis: AxisInt,
+ min_count: int = -1,
+ comp_ids: np.ndarray,
+ ngroups: int,
+ **kwargs,
+ ) -> ArrayLike:
+ """
+ Call our cython function, with appropriate pre- and post- processing.
+ """
+ self._validate_axis(axis, values)
+
+ if not isinstance(values, np.ndarray):
+ # i.e. ExtensionArray
+ return values._groupby_op(
+ how=self.how,
+ has_dropped_na=self.has_dropped_na,
+ min_count=min_count,
+ ngroups=ngroups,
+ ids=comp_ids,
+ **kwargs,
+ )
+
+ return self._cython_op_ndim_compat(
+ values,
+ min_count=min_count,
+ ngroups=ngroups,
+ comp_ids=comp_ids,
+ mask=None,
+ **kwargs,
+ )
+
+
+class BaseGrouper:
+ """
+ This is an internal Grouper class, which actually holds
+ the generated groups
+
+ Parameters
+ ----------
+ axis : Index
+ groupings : Sequence[Grouping]
+ all the grouping instances to handle in this grouper
+ for example for grouper list to groupby, need to pass the list
+ sort : bool, default True
+ whether this grouper will give sorted result or not
+
+ """
+
+ axis: Index
+
+ def __init__(
+ self,
+ axis: Index,
+ groupings: list[grouper.Grouping],
+ sort: bool = True,
+ dropna: bool = True,
+ ) -> None:
+ assert isinstance(axis, Index), axis
+
+ self.axis = axis
+ self._groupings = groupings
+ self._sort = sort
+ self.dropna = dropna
+
+ @property
+ def groupings(self) -> list[grouper.Grouping]:
+ return self._groupings
+
+ def __iter__(self) -> Iterator[Hashable]:
+ return iter(self.indices)
+
+ @property
+ def nkeys(self) -> int:
+ return len(self.groupings)
+
+ def get_iterator(self, data: NDFrameT) -> Iterator[tuple[Hashable, NDFrameT]]:
+ """
+ Groupby iterator
+
+ Returns
+ -------
+ Generator yielding sequence of (name, subsetted object)
+ for each group
+ """
+ splitter = self._get_splitter(data)
+ # TODO: Would be more efficient to skip unobserved for transforms
+ keys = self.result_index
+ yield from zip(keys, splitter, strict=True)
+
+ @final
+ def _get_splitter(self, data: NDFrame) -> DataSplitter:
+ """
+ Returns
+ -------
+ Generator yielding subsetted objects
+ """
+ if isinstance(data, Series):
+ klass: type[DataSplitter] = SeriesSplitter
+ else:
+ # i.e. DataFrame
+ klass = FrameSplitter
+
+ return klass(
+ data,
+ self.ngroups,
+ sorted_ids=self._sorted_ids,
+ sort_idx=self.result_ilocs,
+ )
+
+ @cache_readonly
+ def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]:
+ """dict {group name -> group indices}"""
+ if len(self.groupings) == 1 and isinstance(self.result_index, CategoricalIndex):
+ # This shows unused categories in indices GH#38642
+ result = self.groupings[0].indices
+ else:
+ codes_list = [ping.codes for ping in self.groupings]
+ result = get_indexer_dict(codes_list, self.levels)
+ if not self.dropna:
+ has_mi = isinstance(self.result_index, MultiIndex)
+ if not has_mi and self.result_index.hasnans:
+ result = {
+ np.nan if isna(key) else key: value for key, value in result.items()
+ }
+ elif has_mi:
+ # MultiIndex has no efficient way to tell if there are NAs
+ result = {
+ # error: "Hashable" has no attribute "__iter__" (not iterable)
+ tuple(np.nan if isna(comp) else comp for comp in key): value # type: ignore[attr-defined]
+ for key, value in result.items()
+ }
+
+ return result
+
+ @final
+ @cache_readonly
+ def result_ilocs(self) -> npt.NDArray[np.intp]:
+ """
+ Get the original integer locations of result_index in the input.
+ """
+ # Original indices are where group_index would go via sorting.
+ # But when dropna is true, we need to remove null values while accounting for
+ # any gaps that then occur because of them.
+ ids = self.ids
+
+ if self.has_dropped_na:
+ mask = np.where(ids >= 0)
+ # Count how many gaps are caused by previous null values for each position
+ null_gaps = np.cumsum(ids == -1)[mask]
+ ids = ids[mask]
+
+ result = get_group_index_sorter(ids, self.ngroups)
+
+ if self.has_dropped_na:
+ # Shift by the number of prior null gaps
+ result += np.take(null_gaps, result)
+
+ return result
+
+ @property
+ def codes(self) -> list[npt.NDArray[np.signedinteger]]:
+ return [ping.codes for ping in self.groupings]
+
+ @property
+ def levels(self) -> list[Index]:
+ if len(self.groupings) > 1:
+ # mypy doesn't know result_index must be a MultiIndex
+ return list(self.result_index.levels) # type: ignore[attr-defined]
+ else:
+ return [self.result_index]
+
+ @property
+ def names(self) -> list[Hashable]:
+ return [ping.name for ping in self.groupings]
+
+ @final
+ def size(self) -> Series:
+ """
+ Compute group sizes.
+ """
+ ids = self.ids
+ ngroups = self.ngroups
+ out: np.ndarray | list
+ if ngroups:
+ out = np.bincount(ids[ids != -1], minlength=ngroups)
+ else:
+ out = []
+ return Series(out, index=self.result_index, dtype="int64", copy=False)
+
+ @cache_readonly
+ def groups(self) -> dict[Hashable, Index]:
+ """dict {group name -> group labels}"""
+ if len(self.groupings) == 1:
+ return self.groupings[0].groups
+ result_index, ids = self.result_index_and_ids
+ values = result_index._values
+ categories = Categorical.from_codes(ids, categories=range(len(result_index)))
+ result = {
+ # mypy is not aware that group has to be an integer
+ values[group]: self.axis.take(axis_ilocs) # type: ignore[call-overload]
+ for group, axis_ilocs in categories._reverse_indexer().items()
+ }
+ return result
+
+ @final
+ @cache_readonly
+ def is_monotonic(self) -> bool:
+ # return if my group orderings are monotonic
+ return Index(self.ids, copy=False).is_monotonic_increasing
+
+ @final
+ @cache_readonly
+ def has_dropped_na(self) -> bool:
+ """
+ Whether grouper has null value(s) that are dropped.
+ """
+ return bool((self.ids < 0).any())
+
+ @cache_readonly
+ def codes_info(self) -> npt.NDArray[np.intp]:
+ # return the codes of items in original grouped axis
+ return self.ids
+
+ @final
+ @cache_readonly
+ def ngroups(self) -> int:
+ return len(self.result_index)
+
+ @property
+ def result_index(self) -> Index:
+ return self.result_index_and_ids[0]
+
+ @property
+ def ids(self) -> npt.NDArray[np.intp]:
+ return self.result_index_and_ids[1]
+
+ @cache_readonly
+ def result_index_and_ids(self) -> tuple[Index, npt.NDArray[np.intp]]:
+ levels = [
+ Index._with_infer(ping.uniques, copy=False) for ping in self.groupings
+ ]
+ obs = [
+ ping._observed or not ping._passed_categorical for ping in self.groupings
+ ]
+ sorts = [ping._sort for ping in self.groupings]
+ # When passed a categorical grouping, keep all categories
+ for k, (ping, level) in enumerate(zip(self.groupings, levels, strict=True)):
+ if ping._passed_categorical:
+ levels[k] = level.set_categories(ping._orig_cats)
+
+ if len(self.groupings) == 1:
+ result_index = levels[0]
+ result_index.name = self.names[0]
+ ids = ensure_platform_int(self.codes[0])
+ elif all(obs):
+ result_index, ids = self._ob_index_and_ids(
+ levels, self.codes, self.names, sorts
+ )
+ elif not any(obs):
+ result_index, ids = self._unob_index_and_ids(levels, self.codes, self.names)
+ else:
+ # Combine unobserved and observed parts
+ names = self.names
+ codes = [ping.codes for ping in self.groupings]
+ ob_indices = [idx for idx, ob in enumerate(obs) if ob]
+ unob_indices = [idx for idx, ob in enumerate(obs) if not ob]
+ ob_index, ob_ids = self._ob_index_and_ids(
+ levels=[levels[idx] for idx in ob_indices],
+ codes=[codes[idx] for idx in ob_indices],
+ names=[names[idx] for idx in ob_indices],
+ sorts=[sorts[idx] for idx in ob_indices],
+ )
+ unob_index, unob_ids = self._unob_index_and_ids(
+ levels=[levels[idx] for idx in unob_indices],
+ codes=[codes[idx] for idx in unob_indices],
+ names=[names[idx] for idx in unob_indices],
+ )
+
+ result_index_codes = np.concatenate(
+ [
+ np.tile(unob_index.codes, len(ob_index)),
+ np.repeat(ob_index.codes, len(unob_index), axis=1),
+ ],
+ axis=0,
+ )
+ _, index = np.unique(unob_indices + ob_indices, return_index=True)
+ result_index = MultiIndex(
+ levels=list(unob_index.levels) + list(ob_index.levels),
+ codes=result_index_codes,
+ names=list(unob_index.names) + list(ob_index.names),
+ ).reorder_levels(index)
+
+ # The sum here will get -1 values wrong when dropna=True;
+ # we will fix at the end.
+ ids = len(unob_index) * ob_ids + unob_ids
+
+ if any(sorts):
+ # Sort result_index and recode ids using the new order
+ n_levels = len(sorts)
+ drop_levels = [
+ n_levels - idx
+ for idx, sort in enumerate(reversed(sorts), 1)
+ if not sort
+ ]
+ if len(drop_levels) > 0:
+ sorter = result_index._drop_level_numbers(drop_levels).argsort()
+ else:
+ sorter = result_index.argsort()
+ result_index = result_index.take(sorter)
+ _, index = np.unique(sorter, return_index=True)
+ ids = ensure_platform_int(ids)
+ ids = index.take(ids)
+ else:
+ # Recode ids and reorder result_index with observed groups up front,
+ # unobserved at the end
+ ids, uniques = compress_group_index(ids, sort=False)
+ ids = ensure_platform_int(ids)
+ taker = np.concatenate(
+ [uniques, np.delete(np.arange(len(result_index)), uniques)]
+ )
+ result_index = result_index.take(taker)
+
+ if self.dropna:
+ ids = np.where((ob_ids < 0) | (unob_ids < 0), -1, ids)
+
+ return result_index, ids
+
+ @property
+ def observed_grouper(self) -> BaseGrouper:
+ if all(ping._observed for ping in self.groupings):
+ return self
+
+ return self._observed_grouper
+
+ @cache_readonly
+ def _observed_grouper(self) -> BaseGrouper:
+ groupings = [ping.observed_grouping for ping in self.groupings]
+ grouper = BaseGrouper(self.axis, groupings, sort=self._sort, dropna=self.dropna)
+ return grouper
+
+ def _ob_index_and_ids(
+ self,
+ levels: list[Index],
+ codes: list[npt.NDArray[np.intp]],
+ names: list[Hashable],
+ sorts: list[bool],
+ ) -> tuple[MultiIndex, npt.NDArray[np.intp]]:
+ consistent_sorting = all(sorts[0] == sort for sort in sorts[1:])
+ sort_in_compress = sorts[0] if consistent_sorting else False
+ shape = tuple(len(level) for level in levels)
+ group_index = get_group_index(codes, shape, sort=True, xnull=True)
+ ob_ids, obs_group_ids = compress_group_index(group_index, sort=sort_in_compress)
+ ob_ids = ensure_platform_int(ob_ids)
+ ob_index_codes = decons_obs_group_ids(
+ ob_ids, obs_group_ids, shape, codes, xnull=True
+ )
+ ob_index = MultiIndex(
+ levels=levels,
+ codes=ob_index_codes,
+ names=names,
+ verify_integrity=False,
+ )
+ if not consistent_sorting and len(ob_index) > 0:
+ # Sort by the levels where the corresponding sort argument is True
+ n_levels = len(sorts)
+ drop_levels = [
+ n_levels - idx
+ for idx, sort in enumerate(reversed(sorts), 1)
+ if not sort
+ ]
+ if len(drop_levels) > 0:
+ sorter = ob_index._drop_level_numbers(drop_levels).argsort()
+ else:
+ sorter = ob_index.argsort()
+ ob_index = ob_index.take(sorter)
+ _, index = np.unique(sorter, return_index=True)
+ ob_ids = np.where(ob_ids == -1, -1, index.take(ob_ids))
+ ob_ids = ensure_platform_int(ob_ids)
+ return ob_index, ob_ids
+
+ def _unob_index_and_ids(
+ self,
+ levels: list[Index],
+ codes: list[npt.NDArray[np.intp]],
+ names: list[Hashable],
+ ) -> tuple[MultiIndex, npt.NDArray[np.intp]]:
+ shape = tuple(len(level) for level in levels)
+ unob_ids = get_group_index(codes, shape, sort=True, xnull=True)
+ unob_index = MultiIndex.from_product(levels, names=names)
+ unob_ids = ensure_platform_int(unob_ids)
+ return unob_index, unob_ids
+
+ @final
+ def get_group_levels(self) -> Generator[Index]:
+ # Note: only called from _insert_inaxis_grouper, which
+ # is only called for BaseGrouper, never for BinGrouper
+ result_index = self.result_index
+ if len(self.groupings) == 1:
+ yield result_index
+ else:
+ for level in range(result_index.nlevels - 1, -1, -1):
+ yield result_index.get_level_values(level)
+
+ # ------------------------------------------------------------
+ # Aggregation functions
+
+ @final
+ def _cython_operation(
+ self,
+ kind: str,
+ values,
+ how: str,
+ axis: AxisInt,
+ min_count: int = -1,
+ **kwargs,
+ ) -> ArrayLike:
+ """
+ Returns the values of a cython operation.
+ """
+ assert kind in ["transform", "aggregate"]
+
+ cy_op = WrappedCythonOp(kind=kind, how=how, has_dropped_na=self.has_dropped_na)
+
+ return cy_op.cython_operation(
+ values=values,
+ axis=axis,
+ min_count=min_count,
+ comp_ids=self.ids,
+ ngroups=self.ngroups,
+ **kwargs,
+ )
+
+ @final
+ def agg_series(
+ self, obj: Series, func: Callable, preserve_dtype: bool = False
+ ) -> ArrayLike:
+ """
+ Parameters
+ ----------
+ obj : Series
+ func : function taking a Series and returning a scalar-like
+ preserve_dtype : bool
+ Whether the aggregation is known to be dtype-preserving.
+
+ Returns
+ -------
+ np.ndarray or ExtensionArray
+ """
+ result = self._aggregate_series_pure_python(obj, func)
+ return obj.array._cast_pointwise_result(result)
+
+ @final
+ def _aggregate_series_pure_python(
+ self, obj: Series, func: Callable
+ ) -> npt.NDArray[np.object_]:
+ result = np.empty(self.ngroups, dtype="O")
+ initialized = False
+
+ splitter = self._get_splitter(obj)
+
+ for i, group in enumerate(splitter):
+ res = func(group)
+ res = extract_result(res)
+
+ if not initialized:
+ # We only do this validation on the first iteration
+ check_result_array(res, group.dtype)
+ initialized = True
+
+ result[i] = res
+
+ return result
+
+ @final
+ def apply_groupwise(
+ self, f: Callable, data: DataFrame | Series
+ ) -> tuple[list, bool]:
+ mutated = False
+ splitter = self._get_splitter(data)
+ group_keys = self.result_index
+ result_values = []
+
+ # This calls DataSplitter.__iter__
+ zipped = zip(group_keys, splitter, strict=True)
+
+ for key, group in zipped:
+ # Pinning name is needed for
+ # test_group_apply_once_per_group,
+ # test_inconsistent_return_type, test_set_group_name,
+ # test_group_name_available_in_inference_pass,
+ # test_groupby_multi_timezone
+ object.__setattr__(group, "name", key)
+
+ # group might be modified
+ group_axes = group.axes
+ res = f(group)
+ if not mutated and not _is_indexed_like(res, group_axes):
+ mutated = True
+ result_values.append(res)
+ # getattr pattern for __name__ is needed for functools.partial objects
+ if len(group_keys) == 0 and getattr(f, "__name__", None) in [
+ "skew",
+ "kurt",
+ "sum",
+ "prod",
+ ]:
+ # If group_keys is empty, then no function calls have been made,
+ # so we will not have raised even if this is an invalid dtype.
+ # So do one dummy call here to raise appropriate TypeError.
+ f(data.iloc[:0])
+
+ return result_values, mutated
+
+ # ------------------------------------------------------------
+ # Methods for sorting subsets of our GroupBy's object
+
+ @final
+ @cache_readonly
+ def _sorted_ids(self) -> npt.NDArray[np.intp]:
+ result = self.ids.take(self.result_ilocs)
+ if getattr(self, "dropna", True):
+ # BinGrouper has no dropna
+ result = result[result >= 0]
+ return result
+
+
+class BinGrouper(BaseGrouper):
+ """
+ This is an internal Grouper class
+
+ Parameters
+ ----------
+ bins : the split index of binlabels to group the item of axis
+ binlabels : the label list
+ indexer : np.ndarray[np.intp], optional
+ the indexer created by Grouper
+ some groupers (TimeGrouper) will sort its axis and its
+ group_info is also sorted, so need the indexer to reorder
+
+ Examples
+ --------
+ bins: [2, 4, 6, 8, 10]
+ binlabels: DatetimeIndex(['2005-01-01', '2005-01-03',
+ '2005-01-05', '2005-01-07', '2005-01-09'],
+ dtype='datetime64[ns]', freq='2D')
+
+ the group_info, which contains the label of each item in grouped
+ axis, the index of label in label list, group number, is
+
+ (array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4]), array([0, 1, 2, 3, 4]), 5)
+
+ means that, the grouped axis has 10 items, can be grouped into 5
+ labels, the first and second items belong to the first label, the
+ third and forth items belong to the second label, and so on
+
+ """
+
+ bins: npt.NDArray[np.int64]
+ binlabels: Index
+
+ def __init__(
+ self,
+ bins,
+ binlabels,
+ indexer=None,
+ ) -> None:
+ self.bins = ensure_int64(bins)
+ self.binlabels = ensure_index(binlabels)
+ self.indexer = indexer
+
+ # These lengths must match, otherwise we could call agg_series
+ # with empty self.bins, which would raise later.
+ assert len(self.binlabels) == len(self.bins)
+
+ @cache_readonly
+ def groups(self):
+ """dict {group name -> group labels}"""
+ # this is mainly for compat
+ # GH 3881
+ result = {
+ key: value
+ for key, value in zip(self.binlabels, self.bins, strict=True)
+ if key is not NaT
+ }
+ return result
+
+ @property
+ def nkeys(self) -> int:
+ # still matches len(self.groupings), but we can hard-code
+ return 1
+
+ @cache_readonly
+ def codes_info(self) -> npt.NDArray[np.intp]:
+ # return the codes of items in original grouped axis
+ ids = self.ids
+ if self.indexer is not None:
+ sorter = np.lexsort((ids, self.indexer))
+ ids = ids[sorter]
+ return ids
+
+ def get_iterator(self, data: NDFrame):
+ """
+ Groupby iterator
+
+ Returns
+ -------
+ Generator yielding sequence of (name, subsetted object)
+ for each group
+ """
+ slicer = lambda start, edge: data.iloc[start:edge]
+
+ start: np.int64 | int = 0
+ for edge, label in zip(self.bins, self.binlabels, strict=True):
+ if label is not NaT:
+ yield label, slicer(start, edge)
+ start = edge
+
+ if start < len(data):
+ yield self.binlabels[-1], slicer(start, None)
+
+ @cache_readonly
+ def indices(self):
+ indices = collections.defaultdict(list)
+
+ i: np.int64 | int = 0
+ for label, bin in zip(self.binlabels, self.bins, strict=True):
+ if i < bin:
+ if label is not NaT:
+ indices[label] = list(range(i, bin))
+ i = bin
+ return indices
+
+ @cache_readonly
+ def codes(self) -> list[npt.NDArray[np.intp]]:
+ return [self.ids]
+
+ @cache_readonly
+ def result_index_and_ids(self):
+ result_index = self.binlabels
+ if len(self.binlabels) != 0 and isna(self.binlabels[0]):
+ result_index = result_index[1:]
+
+ ngroups = len(result_index)
+ rep = np.diff(np.r_[0, self.bins])
+
+ rep = ensure_platform_int(rep)
+ if ngroups == len(self.bins):
+ ids = np.repeat(np.arange(ngroups), rep)
+ else:
+ ids = np.repeat(np.r_[-1, np.arange(ngroups)], rep)
+ ids = ensure_platform_int(ids)
+
+ return result_index, ids
+
+ @property
+ def levels(self) -> list[Index]:
+ return [self.binlabels]
+
+ @property
+ def names(self) -> list[Hashable]:
+ return [self.binlabels.name]
+
+ @property
+ def groupings(self) -> list[grouper.Grouping]:
+ lev = self.binlabels
+ codes = self.ids
+ labels = lev.take(codes)
+ ping = grouper.Grouping(
+ labels, labels, in_axis=False, level=None, uniques=lev._values
+ )
+ return [ping]
+
+ @property
+ def observed_grouper(self) -> BinGrouper:
+ return self
+
+
+def _is_indexed_like(obj, axes) -> bool:
+ if isinstance(obj, Series):
+ if len(axes) > 1:
+ return False
+ return obj.index.equals(axes[0])
+ elif isinstance(obj, DataFrame):
+ return obj.index.equals(axes[0])
+
+ return False
+
+
+# ----------------------------------------------------------------------
+# Splitting / application
+
+
+class DataSplitter(Generic[NDFrameT]):
+ def __init__(
+ self,
+ data: NDFrameT,
+ ngroups: int,
+ *,
+ sort_idx: npt.NDArray[np.intp],
+ sorted_ids: npt.NDArray[np.intp],
+ ) -> None:
+ self.data = data
+ self.ngroups = ngroups
+
+ self._slabels = sorted_ids
+ self._sort_idx = sort_idx
+
+ def __iter__(self) -> Iterator:
+ if self.ngroups == 0:
+ # we are inside a generator, rather than raise StopIteration
+ # we merely return signal the end
+ return
+
+ starts, ends = lib.generate_slices(self._slabels, self.ngroups)
+ sdata = self._sorted_data
+ for start, end in zip(starts, ends, strict=True):
+ yield self._chop(sdata, slice(start, end))
+
+ @cache_readonly
+ def _sorted_data(self) -> NDFrameT:
+ return self.data.take(self._sort_idx, axis=0)
+
+ def _chop(self, sdata, slice_obj: slice) -> NDFrame:
+ raise AbstractMethodError(self)
+
+
+class SeriesSplitter(DataSplitter):
+ def _chop(self, sdata: Series, slice_obj: slice) -> Series:
+ # fastpath equivalent to `sdata.iloc[slice_obj]`
+ mgr = sdata._mgr.get_slice(slice_obj)
+ ser = sdata._constructor_from_mgr(mgr, axes=mgr.axes)
+ ser._name = sdata.name
+ return ser.__finalize__(sdata, method="groupby")
+
+
+class FrameSplitter(DataSplitter):
+ def _chop(self, sdata: DataFrame, slice_obj: slice) -> DataFrame:
+ # Fastpath equivalent to:
+ # return sdata.iloc[slice_obj]
+ mgr = sdata._mgr.get_slice(slice_obj, axis=1)
+ df = sdata._constructor_from_mgr(mgr, axes=mgr.axes)
+ return df.__finalize__(sdata, method="groupby")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexers/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..425c515a9e657dd876cf78917f8f7558e5150320
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexers/__init__.py
@@ -0,0 +1,33 @@
+from pandas.core.indexers.utils import (
+ check_array_indexer,
+ check_key_length,
+ check_setitem_lengths,
+ disallow_ndim_indexing,
+ getitem_returns_view,
+ is_empty_indexer,
+ is_list_like_indexer,
+ is_scalar_indexer,
+ is_valid_positional_slice,
+ length_of_indexer,
+ maybe_convert_indices,
+ unpack_1tuple,
+ unpack_tuple_and_ellipses,
+ validate_indices,
+)
+
+__all__ = [
+ "check_array_indexer",
+ "check_key_length",
+ "check_setitem_lengths",
+ "disallow_ndim_indexing",
+ "getitem_returns_view",
+ "is_empty_indexer",
+ "is_list_like_indexer",
+ "is_scalar_indexer",
+ "is_valid_positional_slice",
+ "length_of_indexer",
+ "maybe_convert_indices",
+ "unpack_1tuple",
+ "unpack_tuple_and_ellipses",
+ "validate_indices",
+]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexers/objects.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexers/objects.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b767dbcc0ea123750299704c425bcef17567f2e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexers/objects.py
@@ -0,0 +1,689 @@
+"""Indexer objects for computing start/end window bounds for rolling operations"""
+
+from __future__ import annotations
+
+from datetime import timedelta
+
+import numpy as np
+
+from pandas._libs.tslibs import BaseOffset
+from pandas._libs.window.indexers import calculate_variable_window_bounds
+from pandas.util._decorators import set_module
+
+from pandas.core.dtypes.common import ensure_platform_int
+
+from pandas.core.indexes.datetimes import DatetimeIndex
+
+from pandas.tseries.offsets import Nano
+
+
+@set_module("pandas.api.indexers")
+class BaseIndexer:
+ """
+ Base class for window bounds calculations.
+
+ Parameters
+ ----------
+ index_array : np.ndarray, default None
+ Array-like structure representing the indices for the data points.
+ If None, the default indices are assumed. This can be useful for
+ handling non-uniform indices in data, such as in time series
+ with irregular timestamps.
+ window_size : int, default 0
+ Size of the moving window. This is the number of observations used
+ for calculating the statistic. The default is to consider all
+ observations within the window.
+ **kwargs
+ Additional keyword arguments passed to the subclass's methods.
+
+ See Also
+ --------
+ DataFrame.rolling : Provides rolling window calculations on dataframe.
+ Series.rolling : Provides rolling window calculations on series.
+
+ Examples
+ --------
+ >>> from pandas.api.indexers import BaseIndexer
+ >>> class CustomIndexer(BaseIndexer):
+ ... def get_window_bounds(self, num_values, min_periods, center, closed, step):
+ ... start = np.arange(num_values, dtype=np.int64)
+ ... end = np.arange(num_values, dtype=np.int64) + self.window_size
+ ... return start, end
+ >>> df = pd.DataFrame({"values": range(5)})
+ >>> indexer = CustomIndexer(window_size=2)
+ >>> df.rolling(indexer).sum()
+ values
+ 0 1.0
+ 1 3.0
+ 2 5.0
+ 3 7.0
+ 4 4.0
+ """
+
+ def __init__(
+ self, index_array: np.ndarray | None = None, window_size: int = 0, **kwargs
+ ) -> None:
+ self.index_array = index_array
+ self.window_size = window_size
+ # Set user defined kwargs as attributes that can be used in get_window_bounds
+ for key, value in kwargs.items():
+ setattr(self, key, value)
+
+ def get_window_bounds(
+ self,
+ num_values: int = 0,
+ min_periods: int | None = None,
+ center: bool | None = None,
+ closed: str | None = None,
+ step: int | None = None,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """
+ Computes the bounds of a window.
+
+ Parameters
+ ----------
+ num_values : int, default 0
+ number of values that will be aggregated over
+ window_size : int, default 0
+ the number of rows in a window
+ min_periods : int, default None
+ min_periods passed from the top level rolling API
+ center : bool, default None
+ center passed from the top level rolling API
+ closed : str, default None
+ closed passed from the top level rolling API
+ step : int, default None
+ step passed from the top level rolling API
+ win_type : str, default None
+ win_type passed from the top level rolling API
+
+ Returns
+ -------
+ A tuple of ndarray[int64]s, indicating the boundaries of each
+ window
+ """
+ raise NotImplementedError
+
+
+class FixedWindowIndexer(BaseIndexer):
+ """Creates window boundaries that are of fixed length."""
+
+ def get_window_bounds(
+ self,
+ num_values: int = 0,
+ min_periods: int | None = None,
+ center: bool | None = None,
+ closed: str | None = None,
+ step: int | None = None,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """
+ Computes the bounds of a window.
+
+ Parameters
+ ----------
+ num_values : int, default 0
+ number of values that will be aggregated over
+ window_size : int, default 0
+ the number of rows in a window
+ min_periods : int, default None
+ min_periods passed from the top level rolling API
+ center : bool, default None
+ center passed from the top level rolling API
+ closed : str, default None
+ closed passed from the top level rolling API
+ step : int, default None
+ step passed from the top level rolling API
+ win_type : str, default None
+ win_type passed from the top level rolling API
+
+ Returns
+ -------
+ A tuple of ndarray[int64]s, indicating the boundaries of each
+ window
+ """
+ if center or self.window_size == 0:
+ offset = (self.window_size - 1) // 2
+ else:
+ offset = 0
+
+ end = np.arange(1 + offset, num_values + 1 + offset, step, dtype="int64")
+ start = end - self.window_size
+ if closed in ["left", "both"]:
+ start -= 1
+ if closed in ["left", "neither"]:
+ end -= 1
+
+ end = np.clip(end, 0, num_values)
+ start = np.clip(start, 0, num_values)
+
+ return start, end
+
+
+class VariableWindowIndexer(BaseIndexer):
+ """Creates window boundaries that are of variable length, namely for time series."""
+
+ def get_window_bounds(
+ self,
+ num_values: int = 0,
+ min_periods: int | None = None,
+ center: bool | None = None,
+ closed: str | None = None,
+ step: int | None = None,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """
+ Computes the bounds of a window.
+
+ Parameters
+ ----------
+ num_values : int, default 0
+ number of values that will be aggregated over
+ window_size : int, default 0
+ the number of rows in a window
+ min_periods : int, default None
+ min_periods passed from the top level rolling API
+ center : bool, default None
+ center passed from the top level rolling API
+ closed : str, default None
+ closed passed from the top level rolling API
+ step : int, default None
+ step passed from the top level rolling API
+ win_type : str, default None
+ win_type passed from the top level rolling API
+
+ Returns
+ -------
+ A tuple of ndarray[int64]s, indicating the boundaries of each
+ window
+ """
+ assert self.index_array is not None
+ if (index_length := len(self.index_array)) < num_values:
+ raise ValueError(
+ "Variable rolling window requires the index to be at least as long "
+ f"as the 'other' index. Got {index_length} < {num_values}. "
+ "Please align 'other' to the rolling object's index using "
+ "reindex_like() or similar method."
+ )
+ # error: Argument 4 to "calculate_variable_window_bounds" has incompatible
+ # type "Optional[bool]"; expected "bool"
+ return calculate_variable_window_bounds(
+ num_values,
+ self.window_size,
+ min_periods,
+ center, # type: ignore[arg-type]
+ closed,
+ self.index_array,
+ )
+
+
+@set_module("pandas.api.indexers")
+class VariableOffsetWindowIndexer(BaseIndexer):
+ """
+ Calculate window boundaries based on a non-fixed offset such as a BusinessDay.
+
+ Parameters
+ ----------
+ index_array : np.ndarray, default 0
+ Array-like structure specifying the indices for data points.
+ This parameter is currently not used.
+
+ window_size : int, optional, default 0
+ Specifies the number of data points in each window.
+ This parameter is currently not used.
+
+ index : DatetimeIndex, optional
+ ``DatetimeIndex`` of the labels of each observation.
+
+ offset : BaseOffset, optional
+ ``DateOffset`` representing the size of the window.
+
+ **kwargs
+ Additional keyword arguments passed to the parent class ``BaseIndexer``.
+
+ See Also
+ --------
+ api.indexers.BaseIndexer : Base class for all indexers.
+ DataFrame.rolling : Rolling window calculations on DataFrames.
+ offsets : Module providing various time offset classes.
+
+ Examples
+ --------
+ >>> from pandas.api.indexers import VariableOffsetWindowIndexer
+ >>> df = pd.DataFrame(range(10), index=pd.date_range("2020", periods=10))
+ >>> offset = pd.offsets.BDay(1)
+ >>> indexer = VariableOffsetWindowIndexer(index=df.index, offset=offset)
+ >>> df
+ 0
+ 2020-01-01 0
+ 2020-01-02 1
+ 2020-01-03 2
+ 2020-01-04 3
+ 2020-01-05 4
+ 2020-01-06 5
+ 2020-01-07 6
+ 2020-01-08 7
+ 2020-01-09 8
+ 2020-01-10 9
+ >>> df.rolling(indexer).sum()
+ 0
+ 2020-01-01 0.0
+ 2020-01-02 1.0
+ 2020-01-03 2.0
+ 2020-01-04 3.0
+ 2020-01-05 7.0
+ 2020-01-06 12.0
+ 2020-01-07 6.0
+ 2020-01-08 7.0
+ 2020-01-09 8.0
+ 2020-01-10 9.0
+ """
+
+ def __init__(
+ self,
+ index_array: np.ndarray | None = None,
+ window_size: int = 0,
+ index: DatetimeIndex | None = None,
+ offset: BaseOffset | None = None,
+ **kwargs,
+ ) -> None:
+ super().__init__(index_array, window_size, **kwargs)
+ if not isinstance(index, DatetimeIndex):
+ raise ValueError("index must be a DatetimeIndex.")
+ self.index = index
+ if not isinstance(offset, BaseOffset):
+ raise ValueError("offset must be a DateOffset-like object.")
+ self.offset = offset
+
+ def get_window_bounds(
+ self,
+ num_values: int = 0,
+ min_periods: int | None = None,
+ center: bool | None = None,
+ closed: str | None = None,
+ step: int | None = None,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """
+ Computes the bounds of a window.
+
+ Parameters
+ ----------
+ num_values : int, default 0
+ number of values that will be aggregated over
+ window_size : int, default 0
+ the number of rows in a window
+ min_periods : int, default None
+ min_periods passed from the top level rolling API
+ center : bool, default None
+ center passed from the top level rolling API
+ closed : str, default None
+ closed passed from the top level rolling API
+ step : int, default None
+ step passed from the top level rolling API
+ win_type : str, default None
+ win_type passed from the top level rolling API
+
+ Returns
+ -------
+ A tuple of ndarray[int64]s, indicating the boundaries of each
+ window
+ """
+ if step is not None:
+ raise NotImplementedError("step not implemented for variable offset window")
+ if num_values <= 0:
+ return np.empty(0, dtype="int64"), np.empty(0, dtype="int64")
+
+ # if windows is variable, default is 'right', otherwise default is 'both'
+ if closed is None:
+ closed = "right" if self.index is not None else "both"
+
+ right_closed = closed in ["right", "both"]
+ left_closed = closed in ["left", "both"]
+
+ if self.index[num_values - 1] < self.index[0]:
+ index_growth_sign = -1
+ else:
+ index_growth_sign = 1
+ offset_diff = index_growth_sign * self.offset
+
+ start = np.empty(num_values, dtype="int64")
+ start.fill(-1)
+ end = np.empty(num_values, dtype="int64")
+ end.fill(-1)
+
+ start[0] = 0
+
+ # right endpoint is closed
+ if right_closed:
+ end[0] = 1
+ # right endpoint is open
+ else:
+ end[0] = 0
+
+ zero = timedelta(0)
+ # start is start of slice interval (including)
+ # end is end of slice interval (not including)
+ for i in range(1, num_values):
+ end_bound = self.index[i]
+ start_bound = end_bound - offset_diff
+
+ # left endpoint is closed
+ if left_closed:
+ start_bound -= Nano(1)
+
+ # advance the start bound until we are
+ # within the constraint
+ start[i] = i
+ for j in range(start[i - 1], i):
+ start_diff = (self.index[j] - start_bound) * index_growth_sign
+ if start_diff > zero:
+ start[i] = j
+ break
+
+ # end bound is previous end
+ # or current index
+ end_diff = (self.index[end[i - 1]] - end_bound) * index_growth_sign
+ if end_diff == zero and not right_closed:
+ end[i] = end[i - 1] + 1
+ elif end_diff <= zero:
+ end[i] = i + 1
+ else:
+ end[i] = end[i - 1]
+
+ # right endpoint is open
+ if not right_closed:
+ end[i] -= 1
+
+ return start, end
+
+
+class ExpandingIndexer(BaseIndexer):
+ """Calculate expanding window bounds, mimicking df.expanding()"""
+
+ def get_window_bounds(
+ self,
+ num_values: int = 0,
+ min_periods: int | None = None,
+ center: bool | None = None,
+ closed: str | None = None,
+ step: int | None = None,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """
+ Computes the bounds of a window.
+
+ Parameters
+ ----------
+ num_values : int, default 0
+ number of values that will be aggregated over
+ window_size : int, default 0
+ the number of rows in a window
+ min_periods : int, default None
+ min_periods passed from the top level rolling API
+ center : bool, default None
+ center passed from the top level rolling API
+ closed : str, default None
+ closed passed from the top level rolling API
+ step : int, default None
+ step passed from the top level rolling API
+ win_type : str, default None
+ win_type passed from the top level rolling API
+
+ Returns
+ -------
+ A tuple of ndarray[int64]s, indicating the boundaries of each
+ window
+ """
+ return (
+ np.zeros(num_values, dtype=np.int64),
+ np.arange(1, num_values + 1, dtype=np.int64),
+ )
+
+
+@set_module("pandas.api.indexers")
+class FixedForwardWindowIndexer(BaseIndexer):
+ """
+ Creates window boundaries for fixed-length windows that include the current row.
+
+ Parameters
+ ----------
+ index_array : np.ndarray, default None
+ Array-like structure representing the indices for the data points.
+ If None, the default indices are assumed. This can be useful for
+ handling non-uniform indices in data, such as in time series
+ with irregular timestamps.
+ window_size : int, default 0
+ Size of the moving window. This is the number of observations used
+ for calculating the statistic. The default is to consider all
+ observations within the window.
+ **kwargs
+ Additional keyword arguments passed to the subclass's methods.
+
+ See Also
+ --------
+ DataFrame.rolling : Provides rolling window calculations.
+ api.indexers.VariableWindowIndexer : Calculate window bounds based on
+ variable-sized windows.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})
+ >>> df
+ B
+ 0 0.0
+ 1 1.0
+ 2 2.0
+ 3 NaN
+ 4 4.0
+
+ >>> indexer = pd.api.indexers.FixedForwardWindowIndexer(window_size=2)
+ >>> df.rolling(window=indexer, min_periods=1).sum()
+ B
+ 0 1.0
+ 1 3.0
+ 2 2.0
+ 3 4.0
+ 4 4.0
+ """
+
+ def get_window_bounds(
+ self,
+ num_values: int = 0,
+ min_periods: int | None = None,
+ center: bool | None = None,
+ closed: str | None = None,
+ step: int | None = None,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """
+ Computes the bounds of a window.
+
+ Parameters
+ ----------
+ num_values : int, default 0
+ number of values that will be aggregated over
+ window_size : int, default 0
+ the number of rows in a window
+ min_periods : int, default None
+ min_periods passed from the top level rolling API
+ center : bool, default None
+ center passed from the top level rolling API
+ closed : str, default None
+ closed passed from the top level rolling API
+ step : int, default None
+ step passed from the top level rolling API
+ win_type : str, default None
+ win_type passed from the top level rolling API
+
+ Returns
+ -------
+ A tuple of ndarray[int64]s, indicating the boundaries of each
+ window
+ """
+ if center:
+ raise ValueError("Forward-looking windows can't have center=True")
+ if closed is not None:
+ raise ValueError(
+ "Forward-looking windows don't support setting the closed argument"
+ )
+ if step is None:
+ step = 1
+
+ start = np.arange(0, num_values, step, dtype="int64")
+ end = start + self.window_size
+ if self.window_size:
+ end = np.clip(end, 0, num_values)
+
+ return start, end
+
+
+class GroupbyIndexer(BaseIndexer):
+ """Calculate bounds to compute groupby rolling, mimicking df.groupby().rolling()"""
+
+ def __init__(
+ self,
+ index_array: np.ndarray | None = None,
+ window_size: int | BaseIndexer = 0,
+ groupby_indices: dict | None = None,
+ window_indexer: type[BaseIndexer] = BaseIndexer,
+ indexer_kwargs: dict | None = None,
+ **kwargs,
+ ) -> None:
+ """
+ Parameters
+ ----------
+ index_array : np.ndarray or None
+ np.ndarray of the index of the original object that we are performing
+ a chained groupby operation over. This index has been pre-sorted relative to
+ the groups
+ window_size : int or BaseIndexer
+ window size during the windowing operation
+ groupby_indices : dict or None
+ dict of {group label: [positional index of rows belonging to the group]}
+ window_indexer : BaseIndexer
+ BaseIndexer class determining the start and end bounds of each group
+ indexer_kwargs : dict or None
+ Custom kwargs to be passed to window_indexer
+ **kwargs :
+ keyword arguments that will be available when get_window_bounds is called
+ """
+ self.groupby_indices = groupby_indices or {}
+ self.window_indexer = window_indexer
+ self.indexer_kwargs = indexer_kwargs.copy() if indexer_kwargs else {}
+ super().__init__(
+ index_array=index_array,
+ window_size=self.indexer_kwargs.pop("window_size", window_size),
+ **kwargs,
+ )
+
+ def get_window_bounds(
+ self,
+ num_values: int = 0,
+ min_periods: int | None = None,
+ center: bool | None = None,
+ closed: str | None = None,
+ step: int | None = None,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """
+ Computes the bounds of a window.
+
+ Parameters
+ ----------
+ num_values : int, default 0
+ number of values that will be aggregated over
+ window_size : int, default 0
+ the number of rows in a window
+ min_periods : int, default None
+ min_periods passed from the top level rolling API
+ center : bool, default None
+ center passed from the top level rolling API
+ closed : str, default None
+ closed passed from the top level rolling API
+ step : int, default None
+ step passed from the top level rolling API
+ win_type : str, default None
+ win_type passed from the top level rolling API
+
+ Returns
+ -------
+ A tuple of ndarray[int64]s, indicating the boundaries of each
+ window
+ """
+ # 1) For each group, get the indices that belong to the group
+ # 2) Use the indices to calculate the start & end bounds of the window
+ # 3) Append the window bounds in group order
+ start_arrays = []
+ end_arrays = []
+ window_indices_start = 0
+ for indices in self.groupby_indices.values():
+ index_array: np.ndarray | None
+
+ if self.index_array is not None:
+ index_array = self.index_array.take(ensure_platform_int(indices))
+ else:
+ index_array = self.index_array
+ indexer = self.window_indexer(
+ index_array=index_array,
+ window_size=self.window_size,
+ **self.indexer_kwargs,
+ )
+ start, end = indexer.get_window_bounds(
+ len(indices), min_periods, center, closed, step
+ )
+ start = start.astype(np.int64)
+ end = end.astype(np.int64)
+ assert len(start) == len(end), (
+ "these should be equal in length from get_window_bounds"
+ )
+ # Cannot use groupby_indices as they might not be monotonic with the object
+ # we're rolling over
+ window_indices = np.arange(
+ window_indices_start, window_indices_start + len(indices)
+ )
+ window_indices_start += len(indices)
+ # Extend as we'll be slicing window like [start, end)
+ window_indices = np.append(window_indices, [window_indices[-1] + 1]).astype(
+ np.int64, copy=False
+ )
+ start_arrays.append(window_indices.take(ensure_platform_int(start)))
+ end_arrays.append(window_indices.take(ensure_platform_int(end)))
+ if len(start_arrays) == 0:
+ return np.array([], dtype=np.int64), np.array([], dtype=np.int64)
+ start = np.concatenate(start_arrays)
+ end = np.concatenate(end_arrays)
+ return start, end
+
+
+class ExponentialMovingWindowIndexer(BaseIndexer):
+ """Calculate ewm window bounds (the entire window)"""
+
+ def get_window_bounds(
+ self,
+ num_values: int = 0,
+ min_periods: int | None = None,
+ center: bool | None = None,
+ closed: str | None = None,
+ step: int | None = None,
+ ) -> tuple[np.ndarray, np.ndarray]:
+ """
+ Computes the bounds of a window.
+
+ Parameters
+ ----------
+ num_values : int, default 0
+ number of values that will be aggregated over
+ window_size : int, default 0
+ the number of rows in a window
+ min_periods : int, default None
+ min_periods passed from the top level rolling API
+ center : bool, default None
+ center passed from the top level rolling API
+ closed : str, default None
+ closed passed from the top level rolling API
+ step : int, default None
+ step passed from the top level rolling API
+ win_type : str, default None
+ win_type passed from the top level rolling API
+
+ Returns
+ -------
+ A tuple of ndarray[int64]s, indicating the boundaries of each
+ window
+ """
+ return np.array([0], dtype=np.int64), np.array([num_values], dtype=np.int64)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexers/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexers/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed29e61b0db6800b6ba24cfe26fd7db3c1f7c35b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexers/utils.py
@@ -0,0 +1,584 @@
+"""
+Low-dependency indexing utilities.
+"""
+
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ Any,
+)
+
+import numpy as np
+
+from pandas._libs import lib
+from pandas.util._decorators import set_module
+
+from pandas.core.dtypes.common import (
+ is_array_like,
+ is_bool_dtype,
+ is_integer,
+ is_integer_dtype,
+ is_list_like,
+)
+from pandas.core.dtypes.dtypes import ExtensionDtype
+from pandas.core.dtypes.generic import (
+ ABCIndex,
+ ABCSeries,
+)
+
+if TYPE_CHECKING:
+ from pandas._typing import AnyArrayLike
+
+ from pandas.core.frame import DataFrame
+ from pandas.core.indexes.base import Index
+
+# -----------------------------------------------------------
+# Indexer Identification
+
+
+def is_valid_positional_slice(slc: slice) -> bool:
+ """
+ Check if a slice object can be interpreted as a positional indexer.
+
+ Parameters
+ ----------
+ slc : slice
+
+ Returns
+ -------
+ bool
+
+ Notes
+ -----
+ A valid positional slice may also be interpreted as a label-based slice
+ depending on the index being sliced.
+ """
+ return (
+ lib.is_int_or_none(slc.start)
+ and lib.is_int_or_none(slc.stop)
+ and lib.is_int_or_none(slc.step)
+ )
+
+
+def is_list_like_indexer(key) -> bool:
+ """
+ Check if we have a list-like indexer that is *not* a NamedTuple.
+
+ Parameters
+ ----------
+ key : object
+
+ Returns
+ -------
+ bool
+ """
+ # allow a list_like, but exclude NamedTuples which can be indexers
+ return is_list_like(key) and not (isinstance(key, tuple) and type(key) is not tuple)
+
+
+def is_scalar_indexer(indexer, ndim: int) -> bool:
+ """
+ Return True if we are all scalar indexers.
+
+ Parameters
+ ----------
+ indexer : object
+ ndim : int
+ Number of dimensions in the object being indexed.
+
+ Returns
+ -------
+ bool
+ """
+ if ndim == 1 and is_integer(indexer):
+ # GH37748: allow indexer to be an integer for Series
+ return True
+ if isinstance(indexer, tuple) and len(indexer) == ndim:
+ return all(is_integer(x) for x in indexer)
+ return False
+
+
+def is_empty_indexer(indexer) -> bool:
+ """
+ Check if we have an empty indexer.
+
+ Parameters
+ ----------
+ indexer : object
+
+ Returns
+ -------
+ bool
+ """
+ if is_list_like(indexer) and not len(indexer):
+ return True
+ if not isinstance(indexer, tuple):
+ indexer = (indexer,)
+ return any(isinstance(idx, np.ndarray) and len(idx) == 0 for idx in indexer)
+
+
+# -----------------------------------------------------------
+# Indexer Validation
+
+
+def check_setitem_lengths(indexer, value, values) -> bool:
+ """
+ Validate that value and indexer are the same length.
+
+ A special-case is allowed for when the indexer is a boolean array
+ and the number of true values equals the length of ``value``. In
+ this case, no exception is raised.
+
+ Parameters
+ ----------
+ indexer : sequence
+ Key for the setitem.
+ value : array-like
+ Value for the setitem.
+ values : array-like
+ Values being set into.
+
+ Returns
+ -------
+ bool
+ Whether this is an empty listlike setting which is a no-op.
+
+ Raises
+ ------
+ ValueError
+ When the indexer is an ndarray or list and the lengths don't match.
+ """
+ no_op = False
+
+ if isinstance(indexer, (np.ndarray, list)):
+ # We can ignore other listlikes because they are either
+ # a) not necessarily 1-D indexers, e.g. tuple
+ # b) boolean indexers e.g. BoolArray
+ if is_list_like(value):
+ if len(indexer) != len(value) and values.ndim == 1:
+ # boolean with truth values == len of the value is ok too
+ if isinstance(indexer, list):
+ indexer = np.array(indexer)
+ if not (
+ isinstance(indexer, np.ndarray)
+ and indexer.dtype == np.bool_
+ and indexer.sum() == len(value)
+ ):
+ raise ValueError(
+ "cannot set using a list-like indexer "
+ "with a different length than the value"
+ )
+ if not len(indexer):
+ no_op = True
+
+ elif isinstance(indexer, slice):
+ if is_list_like(value):
+ if len(value) != length_of_indexer(indexer, values) and values.ndim == 1:
+ # In case of two dimensional value is used row-wise and broadcasted
+ raise ValueError(
+ "cannot set using a slice indexer with a "
+ "different length than the value"
+ )
+ if not len(value):
+ no_op = True
+
+ return no_op
+
+
+def validate_indices(indices: np.ndarray, n: int) -> None:
+ """
+ Perform bounds-checking for an indexer.
+
+ -1 is allowed for indicating missing values.
+
+ Parameters
+ ----------
+ indices : ndarray
+ n : int
+ Length of the array being indexed.
+
+ Raises
+ ------
+ ValueError
+
+ Examples
+ --------
+ >>> validate_indices(np.array([1, 2]), 3) # OK
+
+ >>> validate_indices(np.array([1, -2]), 3)
+ Traceback (most recent call last):
+ ...
+ ValueError: negative dimensions are not allowed
+
+ >>> validate_indices(np.array([1, 2, 3]), 3)
+ Traceback (most recent call last):
+ ...
+ IndexError: indices are out-of-bounds
+
+ >>> validate_indices(np.array([-1, -1]), 0) # OK
+
+ >>> validate_indices(np.array([0, 1]), 0)
+ Traceback (most recent call last):
+ ...
+ IndexError: indices are out-of-bounds
+ """
+ if len(indices):
+ min_idx = indices.min()
+ if min_idx < -1:
+ msg = f"'indices' contains values less than allowed ({min_idx} < -1)"
+ raise ValueError(msg)
+
+ max_idx = indices.max()
+ if max_idx >= n:
+ raise IndexError("indices are out-of-bounds")
+
+
+# -----------------------------------------------------------
+# Indexer Conversion
+
+
+def maybe_convert_indices(indices, n: int, verify: bool = True) -> np.ndarray:
+ """
+ Attempt to convert indices into valid, positive indices.
+
+ If we have negative indices, translate to positive here.
+ If we have indices that are out-of-bounds, raise an IndexError.
+
+ Parameters
+ ----------
+ indices : array-like
+ Array of indices that we are to convert.
+ n : int
+ Number of elements in the array that we are indexing.
+ verify : bool, default True
+ Check that all entries are between 0 and n - 1, inclusive.
+
+ Returns
+ -------
+ array-like
+ An array-like of positive indices that correspond to the ones
+ that were passed in initially to this function.
+
+ Raises
+ ------
+ IndexError
+ One of the converted indices either exceeded the number of,
+ elements (specified by `n`), or was still negative.
+ """
+ if isinstance(indices, list):
+ indices = np.array(indices)
+ if len(indices) == 0:
+ # If `indices` is empty, np.array will return a float,
+ # and will cause indexing errors.
+ return np.empty(0, dtype=np.intp)
+
+ mask = indices < 0
+ if mask.any():
+ indices = indices.copy()
+ indices[mask] += n
+
+ if verify:
+ mask = (indices >= n) | (indices < 0)
+ if mask.any():
+ raise IndexError("indices are out-of-bounds")
+ return indices
+
+
+# -----------------------------------------------------------
+# Unsorted
+
+
+def length_of_indexer(indexer, target=None) -> int:
+ """
+ Return the expected length of target[indexer]
+
+ Returns
+ -------
+ int
+ """
+ if target is not None and isinstance(indexer, slice):
+ target_len = len(target)
+ start = indexer.start
+ stop = indexer.stop
+ step = indexer.step
+ if start is None:
+ start = 0
+ elif start < 0:
+ start += target_len
+ if stop is None or stop > target_len:
+ stop = target_len
+ elif stop < 0:
+ stop += target_len
+ if step is None:
+ step = 1
+ elif step < 0:
+ start, stop = stop + 1, start + 1
+ step = -step
+ return (stop - start + step - 1) // step
+ elif isinstance(indexer, (ABCSeries, ABCIndex, np.ndarray, list)):
+ if isinstance(indexer, list):
+ indexer = np.array(indexer)
+
+ if indexer.dtype == bool:
+ # GH#25774
+ return indexer.sum()
+ return len(indexer)
+ elif isinstance(indexer, range):
+ return (indexer.stop - indexer.start) // indexer.step
+ elif not is_list_like_indexer(indexer):
+ return 1
+ raise AssertionError("cannot find the length of the indexer")
+
+
+def disallow_ndim_indexing(result) -> None:
+ """
+ Helper function to disallow multi-dimensional indexing on 1D Series/Index.
+
+ GH#27125 indexer like idx[:, None] expands dim, but we cannot do that
+ and keep an index, so we used to return ndarray, which was deprecated
+ in GH#30588.
+ """
+ if np.ndim(result) > 1:
+ raise ValueError(
+ "Multi-dimensional indexing (e.g. `obj[:, None]`) is no longer "
+ "supported. Convert to a numpy array before indexing instead."
+ )
+
+
+def unpack_1tuple(tup):
+ """
+ If we have a length-1 tuple/list that contains a slice, unpack to just
+ the slice.
+
+ Notes
+ -----
+ The list case is deprecated.
+ """
+ if len(tup) == 1 and isinstance(tup[0], slice):
+ # if we don't have a MultiIndex, we may still be able to handle
+ # a 1-tuple. see test_1tuple_without_multiindex
+
+ if isinstance(tup, list):
+ # GH#31299
+ raise ValueError(
+ "Indexing with a single-item list containing a "
+ "slice is not allowed. Pass a tuple instead.",
+ )
+
+ return tup[0]
+ return tup
+
+
+def check_key_length(columns: Index, key, value: DataFrame) -> None:
+ """
+ Checks if a key used as indexer has the same length as the columns it is
+ associated with.
+
+ Parameters
+ ----------
+ columns : Index The columns of the DataFrame to index.
+ key : A list-like of keys to index with.
+ value : DataFrame The value to set for the keys.
+
+ Raises
+ ------
+ ValueError: If the length of key is not equal to the number of columns in value
+ or if the number of columns referenced by key is not equal to number
+ of columns.
+ """
+ if columns.is_unique:
+ if len(value.columns) != len(key):
+ raise ValueError("Columns must be same length as key")
+ # Missing keys in columns are represented as -1
+ elif len(columns.get_indexer_non_unique(key)[0]) != len(value.columns):
+ raise ValueError("Columns must be same length as key")
+
+
+def unpack_tuple_and_ellipses(item: tuple):
+ """
+ Possibly unpack arr[..., n] to arr[n]
+ """
+ if len(item) > 1:
+ # Note: we are assuming this indexing is being done on a 1D arraylike
+ if item[0] is Ellipsis:
+ item = item[1:]
+ elif item[-1] is Ellipsis:
+ item = item[:-1]
+
+ if len(item) > 1:
+ raise IndexError("too many indices for array.")
+
+ item = item[0]
+ return item
+
+
+def getitem_returns_view(arr, key) -> bool:
+ """
+ Check if an ``arr.__getitem__`` call with given ``key`` would return a view
+ or not.
+ """
+ if not isinstance(key, tuple):
+ key = (key,)
+
+ # filter out Ellipsis and np.newaxis
+ key = tuple(k for k in key if k is not Ellipsis and k is not np.newaxis)
+ if not key:
+ return True
+ # single integer gives view if selecting subset of 2D array
+ if arr.ndim == 2 and lib.is_integer(key[0]):
+ return True
+ # slices always give views
+ if all(isinstance(k, slice) for k in key):
+ return True
+ return False
+
+
+# -----------------------------------------------------------
+# Public indexer validation
+
+
+@set_module("pandas.api.indexers")
+def check_array_indexer(array: AnyArrayLike, indexer: Any) -> Any:
+ """
+ Check if `indexer` is a valid array indexer for `array`.
+
+ For a boolean mask, `array` and `indexer` are checked to have the same
+ length. The dtype is validated, and if it is an integer or boolean
+ ExtensionArray, it is checked if there are missing values present, and
+ it is converted to the appropriate numpy array. Other dtypes will raise
+ an error.
+
+ Non-array indexers (integer, slice, Ellipsis, tuples, ..) are passed
+ through as is.
+
+ Parameters
+ ----------
+ array : array-like
+ The array that is being indexed (only used for the length).
+ indexer : array-like, list-like, int, slice, or other indexer
+ The indexer used for indexing. Array-like and list-like inputs that
+ are not yet a numpy array or an ExtensionArray are converted to one.
+ Non-array indexers (int, slice, Ellipsis, tuples, etc.) are passed
+ through as is.
+
+ Returns
+ -------
+ numpy.ndarray
+ The validated indexer as a numpy array that can be used to index.
+
+ Raises
+ ------
+ IndexError
+ When the lengths don't match.
+ ValueError
+ When `indexer` cannot be converted to a numpy ndarray to index
+ (e.g. presence of missing values).
+
+ See Also
+ --------
+ api.types.is_bool_dtype : Check if `key` is of boolean dtype.
+
+ Examples
+ --------
+ When checking a boolean mask, a boolean ndarray is returned when the
+ arguments are all valid.
+
+ >>> mask = pd.array([True, False])
+ >>> arr = pd.array([1, 2])
+ >>> pd.api.indexers.check_array_indexer(arr, mask)
+ array([ True, False])
+
+ An IndexError is raised when the lengths don't match.
+
+ >>> mask = pd.array([True, False, True])
+ >>> pd.api.indexers.check_array_indexer(arr, mask)
+ Traceback (most recent call last):
+ ...
+ IndexError: Boolean index has wrong length: 3 instead of 2.
+
+ NA values in a boolean array are treated as False.
+
+ >>> mask = pd.array([True, pd.NA])
+ >>> pd.api.indexers.check_array_indexer(arr, mask)
+ array([ True, False])
+
+ A numpy boolean mask will get passed through (if the length is correct):
+
+ >>> mask = np.array([True, False])
+ >>> pd.api.indexers.check_array_indexer(arr, mask)
+ array([ True, False])
+
+ Integer and slice indexers are passed through as is:
+
+ >>> pd.api.indexers.check_array_indexer(arr, 1)
+ 1
+ >>> pd.api.indexers.check_array_indexer(arr, slice(0, 1, 1))
+ slice(0, 1, 1)
+
+ Similarly for integer indexers, an integer ndarray is returned when it is
+ a valid indexer, otherwise an error is (for integer indexers, a matching
+ length is not required):
+
+ >>> indexer = pd.array([0, 2], dtype="Int64")
+ >>> arr = pd.array([1, 2, 3])
+ >>> pd.api.indexers.check_array_indexer(arr, indexer)
+ array([0, 2])
+
+ >>> indexer = pd.array([0, pd.NA], dtype="Int64")
+ >>> pd.api.indexers.check_array_indexer(arr, indexer)
+ Traceback (most recent call last):
+ ...
+ ValueError: Cannot index with an integer indexer containing NA values
+
+ For non-integer/boolean dtypes, an appropriate error is raised:
+
+ >>> indexer = np.array([0.0, 2.0], dtype="float64")
+ >>> pd.api.indexers.check_array_indexer(arr, indexer)
+ Traceback (most recent call last):
+ ...
+ IndexError: arrays used as indices must be of integer or boolean type
+ """
+ from pandas.core.construction import array as pd_array
+
+ # whatever is not an array-like is returned as-is (possible valid array
+ # indexers that are not array-like: integer, slice, Ellipsis, None)
+ # In this context, tuples are not considered as array-like, as they have
+ # a specific meaning in indexing (multi-dimensional indexing)
+ if is_list_like(indexer):
+ if isinstance(indexer, tuple):
+ return indexer
+ else:
+ return indexer
+
+ # convert list-likes to array
+ if not is_array_like(indexer):
+ indexer = pd_array(indexer)
+ if len(indexer) == 0:
+ # empty list is converted to float array by pd.array
+ indexer = np.array([], dtype=np.intp)
+
+ dtype = indexer.dtype
+ if is_bool_dtype(dtype):
+ if isinstance(dtype, ExtensionDtype):
+ indexer = indexer.to_numpy(dtype=bool, na_value=False)
+ else:
+ indexer = np.asarray(indexer, dtype=bool)
+
+ # GH26658
+ if len(indexer) != len(array):
+ raise IndexError(
+ f"Boolean index has wrong length: "
+ f"{len(indexer)} instead of {len(array)}"
+ )
+ elif is_integer_dtype(dtype):
+ try:
+ indexer = np.asarray(indexer, dtype=np.intp)
+ except ValueError as err:
+ raise ValueError(
+ "Cannot index with an integer indexer containing NA values"
+ ) from err
+ else:
+ raise IndexError("arrays used as indices must be of integer or boolean type")
+
+ return indexer
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/accessors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/accessors.py
new file mode 100644
index 0000000000000000000000000000000000000000..a833dee7ef604705a61c78f0d6ce50a87093444b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/accessors.py
@@ -0,0 +1,698 @@
+"""
+datetimelike delegation
+"""
+
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ NoReturn,
+ cast,
+)
+import warnings
+
+import numpy as np
+
+from pandas._libs import lib
+from pandas.errors import Pandas4Warning
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core.dtypes.common import (
+ is_integer_dtype,
+ is_list_like,
+)
+from pandas.core.dtypes.dtypes import (
+ ArrowDtype,
+ CategoricalDtype,
+ DatetimeTZDtype,
+ PeriodDtype,
+)
+from pandas.core.dtypes.generic import ABCSeries
+
+from pandas.core.accessor import (
+ PandasDelegate,
+ delegate_names,
+)
+from pandas.core.arrays import (
+ DatetimeArray,
+ PeriodArray,
+ TimedeltaArray,
+)
+from pandas.core.arrays.arrow.array import ArrowExtensionArray
+from pandas.core.base import (
+ NoNewAttributesMixin,
+ PandasObject,
+)
+from pandas.core.indexes.datetimes import DatetimeIndex
+from pandas.core.indexes.timedeltas import TimedeltaIndex
+
+if TYPE_CHECKING:
+ from pandas import (
+ DataFrame,
+ Series,
+ )
+
+
+class Properties(PandasDelegate, PandasObject, NoNewAttributesMixin):
+ _hidden_attrs = PandasObject._hidden_attrs | {
+ "orig",
+ "name",
+ }
+
+ def __init__(self, data: Series, orig) -> None:
+ if not isinstance(data, ABCSeries):
+ raise TypeError(
+ f"cannot convert an object of type {type(data)} to a datetimelike index"
+ )
+
+ self._parent = data
+ self.orig = orig
+ self.name = getattr(data, "name", None)
+ self._freeze()
+
+ def _get_values(self):
+ data = self._parent
+ if lib.is_np_dtype(data.dtype, "M"):
+ return DatetimeIndex(data, copy=False, name=self.name)
+
+ elif isinstance(data.dtype, DatetimeTZDtype):
+ return DatetimeIndex(data, copy=False, name=self.name)
+
+ elif lib.is_np_dtype(data.dtype, "m"):
+ return TimedeltaIndex(data, copy=False, name=self.name)
+
+ elif isinstance(data.dtype, PeriodDtype):
+ return PeriodArray(data, copy=False)
+
+ raise TypeError(
+ f"cannot convert an object of type {type(data)} to a datetimelike index"
+ )
+
+ def _delegate_property_get(self, name: str):
+ from pandas import Series
+
+ values = self._get_values()
+
+ result = getattr(values, name)
+
+ # maybe need to upcast (ints)
+ if isinstance(result, np.ndarray):
+ if is_integer_dtype(result):
+ result = result.astype("int64")
+ elif not is_list_like(result):
+ return result
+
+ result = np.asarray(result)
+
+ if self.orig is not None:
+ index = self.orig.index
+ else:
+ index = self._parent.index
+ # return the result as a Series
+ return Series(result, index=index, name=self.name).__finalize__(self._parent)
+
+ def _delegate_property_set(self, name: str, value, *args, **kwargs) -> NoReturn:
+ raise ValueError(
+ "modifications to a property of a datetimelike object are not supported. "
+ "Change values on the original."
+ )
+
+ def _delegate_method(self, name: str, *args, **kwargs):
+ from pandas import Series
+
+ values = self._get_values()
+
+ method = getattr(values, name)
+ result = method(*args, **kwargs)
+
+ if not is_list_like(result):
+ return result
+
+ return Series(result, index=self._parent.index, name=self.name).__finalize__(
+ self._parent
+ )
+
+
+@delegate_names(
+ delegate=ArrowExtensionArray,
+ accessors=TimedeltaArray._datetimelike_ops,
+ typ="property",
+ accessor_mapping=lambda x: f"_dt_{x}",
+ raise_on_missing=False,
+)
+@delegate_names(
+ delegate=ArrowExtensionArray,
+ accessors=TimedeltaArray._datetimelike_methods,
+ typ="method",
+ accessor_mapping=lambda x: f"_dt_{x}",
+ raise_on_missing=False,
+)
+@delegate_names(
+ delegate=ArrowExtensionArray,
+ accessors=DatetimeArray._datetimelike_ops,
+ typ="property",
+ accessor_mapping=lambda x: f"_dt_{x}",
+ raise_on_missing=False,
+)
+@delegate_names(
+ delegate=ArrowExtensionArray,
+ accessors=DatetimeArray._datetimelike_methods,
+ typ="method",
+ accessor_mapping=lambda x: f"_dt_{x}",
+ raise_on_missing=False,
+)
+class ArrowTemporalProperties(PandasDelegate, PandasObject, NoNewAttributesMixin):
+ def __init__(self, data: Series, orig) -> None:
+ if not isinstance(data, ABCSeries):
+ raise TypeError(
+ f"cannot convert an object of type {type(data)} to a datetimelike index"
+ )
+
+ self._parent = data
+ self._orig = orig
+ self._freeze()
+
+ def _delegate_property_get(self, name: str):
+ if not hasattr(self._parent.array, f"_dt_{name}"):
+ raise NotImplementedError(
+ f"dt.{name} is not supported for {self._parent.dtype}"
+ )
+ result = getattr(self._parent.array, f"_dt_{name}")
+
+ if not is_list_like(result):
+ return result
+
+ if self._orig is not None:
+ index = self._orig.index
+ else:
+ index = self._parent.index
+ # return the result as a Series, which is by definition a copy
+ result = type(self._parent)(
+ result, index=index, name=self._parent.name
+ ).__finalize__(self._parent)
+
+ return result
+
+ def _delegate_method(self, name: str, *args, **kwargs):
+ if not hasattr(self._parent.array, f"_dt_{name}"):
+ raise NotImplementedError(
+ f"dt.{name} is not supported for {self._parent.dtype}"
+ )
+
+ result = getattr(self._parent.array, f"_dt_{name}")(*args, **kwargs)
+
+ if self._orig is not None:
+ index = self._orig.index
+ else:
+ index = self._parent.index
+ # return the result as a Series, which is by definition a copy
+ result = type(self._parent)(
+ result, index=index, name=self._parent.name
+ ).__finalize__(self._parent)
+
+ return result
+
+ def to_pytimedelta(self):
+ # GH 57463
+ warnings.warn(
+ f"The behavior of {type(self).__name__}.to_pytimedelta is deprecated, "
+ "in a future version this will return a Series containing python "
+ "datetime.timedelta objects instead of an ndarray. To retain the "
+ "old behavior, call `np.array` on the result",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+ return cast(ArrowExtensionArray, self._parent.array)._dt_to_pytimedelta()
+
+ def to_pydatetime(self) -> Series:
+ # GH#20306
+ return cast(ArrowExtensionArray, self._parent.array)._dt_to_pydatetime()
+
+ def isocalendar(self) -> DataFrame:
+ from pandas import DataFrame
+
+ result = (
+ cast(ArrowExtensionArray, self._parent.array)
+ ._dt_isocalendar()
+ ._pa_array.combine_chunks()
+ )
+ iso_calendar_df = DataFrame(
+ {
+ col: type(self._parent.array)(result.field(i)) # type: ignore[call-arg]
+ for i, col in enumerate(["year", "week", "day"])
+ }
+ )
+ return iso_calendar_df
+
+ @property
+ def components(self) -> DataFrame:
+ from pandas import DataFrame
+
+ components_df = DataFrame(
+ {
+ col: getattr(self._parent.array, f"_dt_{col}")
+ for col in [
+ "days",
+ "hours",
+ "minutes",
+ "seconds",
+ "milliseconds",
+ "microseconds",
+ "nanoseconds",
+ ]
+ }
+ )
+ return components_df
+
+
+@delegate_names(
+ delegate=DatetimeArray,
+ accessors=[*DatetimeArray._datetimelike_ops, "unit"],
+ typ="property",
+)
+@delegate_names(
+ delegate=DatetimeArray,
+ accessors=[*DatetimeArray._datetimelike_methods, "as_unit"],
+ typ="method",
+)
+class DatetimeProperties(Properties):
+ """
+ Accessor object for datetimelike properties of the Series values.
+
+ Examples
+ --------
+ >>> seconds_series = pd.Series(pd.date_range("2000-01-01", periods=3, freq="s"))
+ >>> seconds_series
+ 0 2000-01-01 00:00:00
+ 1 2000-01-01 00:00:01
+ 2 2000-01-01 00:00:02
+ dtype: datetime64[us]
+ >>> seconds_series.dt.second
+ 0 0
+ 1 1
+ 2 2
+ dtype: int32
+
+ >>> hours_series = pd.Series(pd.date_range("2000-01-01", periods=3, freq="h"))
+ >>> hours_series
+ 0 2000-01-01 00:00:00
+ 1 2000-01-01 01:00:00
+ 2 2000-01-01 02:00:00
+ dtype: datetime64[us]
+ >>> hours_series.dt.hour
+ 0 0
+ 1 1
+ 2 2
+ dtype: int32
+
+ >>> quarters_series = pd.Series(pd.date_range("2000-01-01", periods=3, freq="QE"))
+ >>> quarters_series
+ 0 2000-03-31
+ 1 2000-06-30
+ 2 2000-09-30
+ dtype: datetime64[us]
+ >>> quarters_series.dt.quarter
+ 0 1
+ 1 2
+ 2 3
+ dtype: int32
+
+ Returns a Series indexed like the original Series.
+ Raises TypeError if the Series does not contain datetimelike values.
+ """
+
+ def to_pydatetime(self) -> Series:
+ """
+ Return the data as a Series of :class:`datetime.datetime` objects.
+
+ Timezone information is retained if present.
+
+ .. warning::
+
+ Python's datetime uses microsecond resolution, which is lower than
+ pandas (nanosecond). The values are truncated.
+
+ Returns
+ -------
+ numpy.ndarray
+ Object dtype array containing native Python datetime objects.
+
+ See Also
+ --------
+ datetime.datetime : Standard library value for a datetime.
+
+ Examples
+ --------
+ >>> s = pd.Series(pd.date_range("20180310", periods=2))
+ >>> s
+ 0 2018-03-10
+ 1 2018-03-11
+ dtype: datetime64[us]
+
+ >>> s.dt.to_pydatetime()
+ 0 2018-03-10 00:00:00
+ 1 2018-03-11 00:00:00
+ dtype: object
+
+ pandas' nanosecond precision is truncated to microseconds.
+
+ >>> s = pd.Series(pd.date_range("20180310", periods=2, freq="ns"))
+ >>> s
+ 0 2018-03-10 00:00:00.000000000
+ 1 2018-03-10 00:00:00.000000001
+ dtype: datetime64[ns]
+
+ >>> s.dt.to_pydatetime()
+ 0 2018-03-10 00:00:00
+ 1 2018-03-10 00:00:00
+ dtype: object
+ """
+ # GH#20306
+ from pandas import Series
+
+ return Series(self._get_values().to_pydatetime(), dtype=object)
+
+ @property
+ def freq(self):
+ """
+ Tries to return a string representing a frequency generated by infer_freq.
+
+ Returns None if it can't autodetect the frequency.
+
+ See Also
+ --------
+ Series.dt.to_period : Cast to PeriodArray/PeriodIndex at a particular
+ frequency.
+
+ Examples
+ --------
+ >>> ser = pd.Series(["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04"])
+ >>> ser = pd.to_datetime(ser)
+ >>> ser.dt.freq
+ 'D'
+
+ >>> ser = pd.Series(["2022-01-01", "2024-01-01", "2026-01-01", "2028-01-01"])
+ >>> ser = pd.to_datetime(ser)
+ >>> ser.dt.freq
+ '2YS-JAN'
+ """
+ return self._get_values().inferred_freq
+
+ def isocalendar(self) -> DataFrame:
+ """
+ Calculate year, week, and day according to the ISO 8601 standard.
+
+ Returns
+ -------
+ DataFrame
+ With columns year, week and day.
+
+ See Also
+ --------
+ Timestamp.isocalendar : Function return a 3-tuple containing ISO year,
+ week number, and weekday for the given Timestamp object.
+ datetime.date.isocalendar : Return a named tuple object with
+ three components: year, week and weekday.
+
+ Examples
+ --------
+ >>> ser = pd.to_datetime(pd.Series(["2010-01-01", pd.NaT]))
+ >>> ser.dt.isocalendar()
+ year week day
+ 0 2009 53 5
+ 1
+ >>> ser.dt.isocalendar().week
+ 0 53
+ 1
+ Name: week, dtype: UInt32
+ """
+ return self._get_values().isocalendar().set_index(self._parent.index)
+
+
+@delegate_names(
+ delegate=TimedeltaArray, accessors=TimedeltaArray._datetimelike_ops, typ="property"
+)
+@delegate_names(
+ delegate=TimedeltaArray,
+ accessors=TimedeltaArray._datetimelike_methods,
+ typ="method",
+)
+class TimedeltaProperties(Properties):
+ """
+ Accessor object for datetimelike properties of the Series values.
+
+ Returns a Series indexed like the original Series.
+ Raises TypeError if the Series does not contain datetimelike values.
+
+ Examples
+ --------
+ >>> seconds_series = pd.Series(
+ ... pd.timedelta_range(start="1 second", periods=3, freq="s")
+ ... )
+ >>> seconds_series
+ 0 0 days 00:00:01
+ 1 0 days 00:00:02
+ 2 0 days 00:00:03
+ dtype: timedelta64[us]
+ >>> seconds_series.dt.seconds
+ 0 1
+ 1 2
+ 2 3
+ dtype: int32
+ """
+
+ def to_pytimedelta(self) -> np.ndarray:
+ """
+ Return an array of native :class:`datetime.timedelta` objects.
+
+ Python's standard `datetime` library uses a different representation
+ timedelta's. This method converts a Series of pandas Timedeltas
+ to `datetime.timedelta` format with the same length as the original
+ Series.
+
+ Returns
+ -------
+ numpy.ndarray
+ Array of 1D containing data with `datetime.timedelta` type.
+
+ See Also
+ --------
+ datetime.timedelta : A duration expressing the difference
+ between two date, time, or datetime.
+
+ Examples
+ --------
+ >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="D"))
+ >>> s
+ 0 0 days
+ 1 1 days
+ 2 2 days
+ 3 3 days
+ 4 4 days
+ dtype: timedelta64[s]
+
+ >>> s.dt.to_pytimedelta()
+ array([datetime.timedelta(0), datetime.timedelta(days=1),
+ datetime.timedelta(days=2), datetime.timedelta(days=3),
+ datetime.timedelta(days=4)], dtype=object)
+ """
+ # GH 57463
+ warnings.warn(
+ f"The behavior of {type(self).__name__}.to_pytimedelta is deprecated, "
+ "in a future version this will return a Series containing python "
+ "datetime.timedelta objects instead of an ndarray. To retain the "
+ "old behavior, call `np.array` on the result",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+ return self._get_values().to_pytimedelta()
+
+ @property
+ def components(self) -> DataFrame:
+ """
+ Return a Dataframe of the components of the Timedeltas.
+
+ Each row of the DataFrame corresponds to a Timedelta in the original
+ Series and contains the individual components (days, hours, minutes,
+ seconds, milliseconds, microseconds, nanoseconds) of the Timedelta.
+
+ Returns
+ -------
+ DataFrame
+
+ See Also
+ --------
+ TimedeltaIndex.components : Return a DataFrame of the individual resolution
+ components of the Timedeltas.
+ Series.dt.total_seconds : Return the total number of seconds in the duration.
+
+ Examples
+ --------
+ >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="s"))
+ >>> s
+ 0 0 days 00:00:00
+ 1 0 days 00:00:01
+ 2 0 days 00:00:02
+ 3 0 days 00:00:03
+ 4 0 days 00:00:04
+ dtype: timedelta64[s]
+ >>> s.dt.components
+ days hours minutes seconds milliseconds microseconds nanoseconds
+ 0 0 0 0 0 0 0 0
+ 1 0 0 0 1 0 0 0
+ 2 0 0 0 2 0 0 0
+ 3 0 0 0 3 0 0 0
+ 4 0 0 0 4 0 0 0
+ """
+ return (
+ self._get_values()
+ .components.set_index(self._parent.index)
+ .__finalize__(self._parent)
+ )
+
+ @property
+ def freq(self):
+ return self._get_values().inferred_freq
+
+
+@delegate_names(
+ delegate=PeriodArray, accessors=PeriodArray._datetimelike_ops, typ="property"
+)
+@delegate_names(
+ delegate=PeriodArray, accessors=PeriodArray._datetimelike_methods, typ="method"
+)
+class PeriodProperties(Properties):
+ """
+ Accessor object for datetimelike properties of the Series values.
+
+ Returns a Series indexed like the original Series.
+ Raises TypeError if the Series does not contain datetimelike values.
+
+ Examples
+ --------
+ >>> seconds_series = pd.Series(
+ ... pd.period_range(
+ ... start="2000-01-01 00:00:00", end="2000-01-01 00:00:03", freq="s"
+ ... )
+ ... )
+ >>> seconds_series
+ 0 2000-01-01 00:00:00
+ 1 2000-01-01 00:00:01
+ 2 2000-01-01 00:00:02
+ 3 2000-01-01 00:00:03
+ dtype: period[s]
+ >>> seconds_series.dt.second
+ 0 0
+ 1 1
+ 2 2
+ 3 3
+ dtype: int64
+
+ >>> hours_series = pd.Series(
+ ... pd.period_range(start="2000-01-01 00:00", end="2000-01-01 03:00", freq="h")
+ ... )
+ >>> hours_series
+ 0 2000-01-01 00:00
+ 1 2000-01-01 01:00
+ 2 2000-01-01 02:00
+ 3 2000-01-01 03:00
+ dtype: period[h]
+ >>> hours_series.dt.hour
+ 0 0
+ 1 1
+ 2 2
+ 3 3
+ dtype: int64
+
+ >>> quarters_series = pd.Series(
+ ... pd.period_range(start="2000-01-01", end="2000-12-31", freq="Q-DEC")
+ ... )
+ >>> quarters_series
+ 0 2000Q1
+ 1 2000Q2
+ 2 2000Q3
+ 3 2000Q4
+ dtype: period[Q-DEC]
+ >>> quarters_series.dt.quarter
+ 0 1
+ 1 2
+ 2 3
+ 3 4
+ dtype: int64
+ """
+
+
+class CombinedDatetimelikeProperties(
+ DatetimeProperties, TimedeltaProperties, PeriodProperties
+):
+ """
+ Accessor object for Series values' datetime-like, timedelta and period properties.
+
+ See Also
+ --------
+ DatetimeIndex : Index of datetime64 data.
+
+ Examples
+ --------
+ >>> dates = pd.Series(
+ ... ["2024-01-01", "2024-01-15", "2024-02-5"], dtype="datetime64[ns]"
+ ... )
+ >>> dates.dt.day
+ 0 1
+ 1 15
+ 2 5
+ dtype: int32
+ >>> dates.dt.month
+ 0 1
+ 1 1
+ 2 2
+ dtype: int32
+
+ >>> dates = pd.Series(
+ ... ["2024-01-01", "2024-01-15", "2024-02-5"], dtype="datetime64[ns, UTC]"
+ ... )
+ >>> dates.dt.day
+ 0 1
+ 1 15
+ 2 5
+ dtype: int32
+ >>> dates.dt.month
+ 0 1
+ 1 1
+ 2 2
+ dtype: int32
+ """
+
+ def __new__(cls, data: Series): # pyright: ignore[reportInconsistentConstructor]
+ # CombinedDatetimelikeProperties isn't really instantiated. Instead
+ # we need to choose which parent (datetime or timedelta) is
+ # appropriate. Since we're checking the dtypes anyway, we'll just
+ # do all the validation here.
+
+ if not isinstance(data, ABCSeries):
+ raise TypeError(
+ f"cannot convert an object of type {type(data)} to a datetimelike index"
+ )
+
+ orig = data if isinstance(data.dtype, CategoricalDtype) else None
+ if orig is not None:
+ data = data._constructor(
+ orig.array,
+ name=orig.name,
+ copy=False,
+ dtype=orig._values.categories.dtype,
+ index=orig.index,
+ )
+
+ if isinstance(data.dtype, ArrowDtype) and data.dtype.kind in "Mm":
+ return ArrowTemporalProperties(data, orig)
+ if lib.is_np_dtype(data.dtype, "M"):
+ return DatetimeProperties(data, orig)
+ elif isinstance(data.dtype, DatetimeTZDtype):
+ return DatetimeProperties(data, orig)
+ elif lib.is_np_dtype(data.dtype, "m"):
+ return TimedeltaProperties(data, orig)
+ elif isinstance(data.dtype, PeriodDtype):
+ return PeriodProperties(data, orig)
+
+ raise AttributeError("Can only use .dt accessor with datetimelike values")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..70e7bd3b385279ea8d309f13f1e0500659033f8f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/api.py
@@ -0,0 +1,342 @@
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ cast,
+)
+
+import numpy as np
+
+from pandas._libs import (
+ NaT,
+ lib,
+)
+from pandas.errors import InvalidIndexError
+
+from pandas.core.dtypes.cast import find_common_type
+
+from pandas.core.algorithms import safe_sort
+from pandas.core.indexes.base import (
+ Index,
+ _new_Index,
+ ensure_index,
+ ensure_index_from_sequences,
+ get_unanimous_names,
+ maybe_sequence_to_range,
+)
+from pandas.core.indexes.category import CategoricalIndex
+from pandas.core.indexes.datetimes import DatetimeIndex
+from pandas.core.indexes.interval import IntervalIndex
+from pandas.core.indexes.multi import MultiIndex
+from pandas.core.indexes.period import PeriodIndex
+from pandas.core.indexes.range import RangeIndex
+from pandas.core.indexes.timedeltas import TimedeltaIndex
+
+if TYPE_CHECKING:
+ from pandas._typing import Axis
+
+
+__all__ = [
+ "CategoricalIndex",
+ "DatetimeIndex",
+ "Index",
+ "IntervalIndex",
+ "InvalidIndexError",
+ "MultiIndex",
+ "NaT",
+ "PeriodIndex",
+ "RangeIndex",
+ "TimedeltaIndex",
+ "_new_Index",
+ "all_indexes_same",
+ "default_index",
+ "ensure_index",
+ "ensure_index_from_sequences",
+ "get_objs_combined_axis",
+ "get_unanimous_names",
+ "maybe_sequence_to_range",
+ "safe_sort_index",
+ "union_indexes",
+]
+
+
+def get_objs_combined_axis(
+ objs,
+ intersect: bool = False,
+ axis: Axis = 0,
+ sort: bool | lib.NoDefault = True,
+) -> Index:
+ """
+ Extract combined index: return intersection or union (depending on the
+ value of "intersect") of indexes on given axis, or None if all objects
+ lack indexes (e.g. they are numpy arrays).
+
+ Parameters
+ ----------
+ objs : list
+ Series or DataFrame objects, may be mix of the two.
+ intersect : bool, default False
+ If True, calculate the intersection between indexes. Otherwise,
+ calculate the union.
+ axis : {0 or 'index', 1 or 'outer'}, default 0
+ The axis to extract indexes from.
+ sort : bool, default True
+ Whether the result index should come out sorted or not. NoDefault
+ use for deprecation in GH#57335.
+
+ Returns
+ -------
+ Index
+ """
+ obs_idxes = [obj._get_axis(axis) for obj in objs]
+ return _get_combined_index(obs_idxes, intersect=intersect, sort=sort)
+
+
+def _get_distinct_objs(objs: list[Index]) -> list[Index]:
+ """
+ Return a list with distinct elements of "objs" (different ids).
+ Preserves order.
+ """
+ ids: set[int] = set()
+ res = []
+ for obj in objs:
+ if id(obj) not in ids:
+ ids.add(id(obj))
+ res.append(obj)
+ return res
+
+
+def _get_combined_index(
+ indexes: list[Index],
+ intersect: bool = False,
+ sort: bool | lib.NoDefault = False,
+) -> Index:
+ """
+ Return the union or intersection of indexes.
+
+ Parameters
+ ----------
+ indexes : list of Index or list objects
+ When intersect=True, do not accept list of lists.
+ intersect : bool, default False
+ If True, calculate the intersection between indexes. Otherwise,
+ calculate the union.
+ sort : bool, default False
+ Whether the result index should come out sorted or not. NoDefault
+ used for deprecation of GH#57335
+
+ Returns
+ -------
+ Index
+ """
+ # TODO: handle index names!
+ indexes = _get_distinct_objs(indexes)
+ if len(indexes) == 0:
+ index: Index = default_index(0)
+ elif len(indexes) == 1:
+ index = indexes[0]
+ elif intersect:
+ index = indexes[0]
+ for other in indexes[1:]:
+ index = index.intersection(other)
+ else:
+ index = union_indexes(indexes, sort=sort if sort is lib.no_default else False)
+ index = ensure_index(index)
+
+ if sort and sort is not lib.no_default:
+ index = safe_sort_index(index)
+ return index
+
+
+def safe_sort_index(index: Index) -> Index:
+ """
+ Returns the sorted index
+
+ We keep the dtypes and the name attributes.
+
+ Parameters
+ ----------
+ index : an Index
+
+ Returns
+ -------
+ Index
+ """
+ if index.is_monotonic_increasing:
+ return index
+
+ try:
+ array_sorted = safe_sort(index)
+ except TypeError:
+ pass
+ else:
+ if isinstance(array_sorted, Index):
+ return array_sorted
+
+ array_sorted = cast(np.ndarray, array_sorted)
+ if isinstance(index, MultiIndex):
+ index = MultiIndex.from_tuples(array_sorted, names=index.names)
+ else:
+ index = Index(array_sorted, name=index.name, dtype=index.dtype)
+
+ return index
+
+
+def union_indexes(indexes, sort: bool | lib.NoDefault = True) -> Index:
+ """
+ Return the union of indexes.
+
+ The behavior of sort and names is not consistent.
+
+ Parameters
+ ----------
+ indexes : list of Index or list objects
+ sort : bool, default True
+ Whether the result index should come out sorted or not. NoDefault
+ used for deprecation of GH#57335.
+
+ Returns
+ -------
+ Index
+ """
+ if len(indexes) == 0:
+ raise AssertionError("Must have at least 1 Index to union")
+ if len(indexes) == 1:
+ result = indexes[0]
+ if isinstance(result, list):
+ if not sort or sort is lib.no_default:
+ result = Index(result)
+ else:
+ result = Index(sorted(result))
+ return result
+
+ indexes, kind = _sanitize_and_check(indexes)
+
+ if kind == "special":
+ result = indexes[0]
+
+ num_dtis = 0
+ num_dti_tzs = 0
+ for idx in indexes:
+ if isinstance(idx, DatetimeIndex):
+ num_dtis += 1
+ if idx.tz is not None:
+ num_dti_tzs += 1
+ if num_dti_tzs not in [0, num_dtis]:
+ # TODO: this behavior is not tested (so may not be desired),
+ # but is kept in order to keep behavior the same when
+ # deprecating union_many
+ # test_frame_from_dict_with_mixed_indexes
+ raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex")
+
+ if num_dtis == len(indexes):
+ if sort is lib.no_default:
+ sort = True
+ result = indexes[0]
+
+ elif num_dtis > 1:
+ # If we have mixed timezones, our casting behavior may depend on
+ # the order of indexes, which we don't want.
+ sort = False
+
+ # TODO: what about Categorical[dt64]?
+ # test_frame_from_dict_with_mixed_indexes
+ indexes = [x.astype(object, copy=False) for x in indexes]
+ result = indexes[0]
+
+ for other in indexes[1:]:
+ result = result.union(other, sort=None if sort else False)
+ return result
+
+ elif kind == "array":
+ if not all_indexes_same(indexes):
+ dtype = find_common_type([idx.dtype for idx in indexes])
+ inds = [ind.astype(dtype, copy=False) for ind in indexes]
+ index = inds[0].unique()
+ other = inds[1].append(inds[2:])
+ diff = other[index.get_indexer_for(other) == -1]
+ if len(diff):
+ index = index.append(diff.unique())
+ if sort:
+ index = index.sort_values()
+ else:
+ index = indexes[0]
+
+ name = get_unanimous_names(*indexes)[0]
+ if name != index.name:
+ index = index.rename(name)
+ return index
+ elif kind == "list":
+ dtypes = [idx.dtype for idx in indexes if isinstance(idx, Index)]
+ if dtypes:
+ dtype = find_common_type(dtypes)
+ else:
+ dtype = None
+ all_lists = (idx.tolist() if isinstance(idx, Index) else idx for idx in indexes)
+ return Index(
+ lib.fast_unique_multiple_list_gen(all_lists, sort=bool(sort)),
+ dtype=dtype,
+ )
+ else:
+ raise ValueError(f"{kind=} must be 'special', 'array' or 'list'.")
+
+
+def _sanitize_and_check(indexes):
+ """
+ Verify the type of indexes and convert lists to Index.
+
+ Cases:
+
+ - [list, list, ...]: Return ([list, list, ...], 'list')
+ - [list, Index, ...]: Return _sanitize_and_check([Index, Index, ...])
+ Lists are sorted and converted to Index.
+ - [Index, Index, ...]: Return ([Index, Index, ...], TYPE)
+ TYPE = 'special' if at least one special type, 'array' otherwise.
+
+ Parameters
+ ----------
+ indexes : list of Index or list objects
+
+ Returns
+ -------
+ sanitized_indexes : list of Index or list objects
+ type : {'list', 'array', 'special'}
+ """
+ kinds = {type(index) for index in indexes}
+
+ if list in kinds:
+ if len(kinds) > 1:
+ indexes = [
+ Index(list(x)) if not isinstance(x, Index) else x for x in indexes
+ ]
+ kinds -= {list}
+ else:
+ return indexes, "list"
+
+ if len(kinds) > 1 or Index not in kinds:
+ return indexes, "special"
+ else:
+ return indexes, "array"
+
+
+def all_indexes_same(indexes) -> bool:
+ """
+ Determine if all indexes contain the same elements.
+
+ Parameters
+ ----------
+ indexes : iterable of Index objects
+
+ Returns
+ -------
+ bool
+ True if all indexes contain the same elements, False otherwise.
+ """
+ itr = iter(indexes)
+ first = next(itr)
+ return all(first.equals(index) for index in itr)
+
+
+def default_index(n: int) -> RangeIndex:
+ rng = range(n)
+ return RangeIndex._simple_new(rng, name=None)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/base.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b06a7f117271b37faf4be39304c99315da5d23b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/base.py
@@ -0,0 +1,8192 @@
+from __future__ import annotations
+
+from collections import abc
+from datetime import datetime
+import functools
+from itertools import zip_longest
+import operator
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ ClassVar,
+ Literal,
+ NoReturn,
+ Self,
+ cast,
+ final,
+ overload,
+)
+import warnings
+
+import numpy as np
+
+from pandas._config import (
+ get_option,
+ is_nan_na,
+ using_string_dtype,
+)
+
+from pandas._libs import (
+ NaT,
+ algos as libalgos,
+ index as libindex,
+ lib,
+ writers,
+)
+from pandas._libs.internals import BlockValuesRefs
+import pandas._libs.join as libjoin
+from pandas._libs.lib import (
+ is_datetime_array,
+ no_default,
+)
+from pandas._libs.tslibs import (
+ OutOfBoundsDatetime,
+ Timestamp,
+ tz_compare,
+)
+from pandas._typing import (
+ AnyAll,
+ ArrayLike,
+ Axes,
+ Axis,
+ AxisInt,
+ DropKeep,
+ Dtype,
+ DtypeObj,
+ F,
+ IgnoreRaise,
+ IndexLabel,
+ IndexT,
+ JoinHow,
+ Level,
+ NaPosition,
+ ReindexMethod,
+ Shape,
+ SliceType,
+ npt,
+)
+from pandas.compat.numpy import function as nv
+from pandas.errors import (
+ DuplicateLabelError,
+ InvalidIndexError,
+ Pandas4Warning,
+)
+from pandas.util._decorators import (
+ cache_readonly,
+ set_module,
+)
+from pandas.util._exceptions import (
+ find_stack_level,
+ rewrite_exception,
+)
+
+from pandas.core.dtypes.astype import (
+ astype_array,
+ astype_is_view,
+)
+from pandas.core.dtypes.cast import (
+ LossySetitemError,
+ can_hold_element,
+ common_dtype_categorical_compat,
+ find_result_type,
+ infer_dtype_from,
+ maybe_unbox_numpy_scalar,
+ np_can_hold_element,
+)
+from pandas.core.dtypes.common import (
+ ensure_int64,
+ ensure_object,
+ ensure_platform_int,
+ is_any_real_numeric_dtype,
+ is_bool_dtype,
+ is_ea_or_datetimelike_dtype,
+ is_float,
+ is_hashable,
+ is_integer,
+ is_iterator,
+ is_list_like,
+ is_numeric_dtype,
+ is_object_dtype,
+ is_scalar,
+ is_signed_integer_dtype,
+ is_string_dtype,
+ needs_i8_conversion,
+ pandas_dtype,
+ validate_all_hashable,
+)
+from pandas.core.dtypes.concat import concat_compat
+from pandas.core.dtypes.dtypes import (
+ ArrowDtype,
+ CategoricalDtype,
+ DatetimeTZDtype,
+ ExtensionDtype,
+ IntervalDtype,
+ PeriodDtype,
+ SparseDtype,
+)
+from pandas.core.dtypes.generic import (
+ ABCCategoricalIndex,
+ ABCDataFrame,
+ ABCDatetimeIndex,
+ ABCIntervalIndex,
+ ABCMultiIndex,
+ ABCPeriodIndex,
+ ABCRangeIndex,
+ ABCSeries,
+ ABCTimedeltaIndex,
+)
+from pandas.core.dtypes.inference import is_dict_like
+from pandas.core.dtypes.missing import (
+ array_equivalent,
+ is_valid_na_for_dtype,
+ isna,
+)
+
+from pandas.core import (
+ arraylike,
+ nanops,
+ ops,
+)
+from pandas.core.accessor import Accessor
+import pandas.core.algorithms as algos
+from pandas.core.array_algos.putmask import (
+ setitem_datetimelike_compat,
+ validate_putmask,
+)
+from pandas.core.arrays import (
+ ArrowExtensionArray,
+ BaseMaskedArray,
+ Categorical,
+ DatetimeArray,
+ ExtensionArray,
+ TimedeltaArray,
+)
+from pandas.core.arrays.floating import FloatingDtype
+from pandas.core.arrays.string_ import (
+ StringArray,
+ StringDtype,
+)
+from pandas.core.base import (
+ IndexOpsMixin,
+ PandasObject,
+)
+import pandas.core.common as com
+from pandas.core.construction import (
+ ensure_wrapped_if_datetimelike,
+ extract_array,
+ sanitize_array,
+)
+from pandas.core.indexers import (
+ disallow_ndim_indexing,
+ is_valid_positional_slice,
+)
+from pandas.core.indexes.frozen import FrozenList
+from pandas.core.missing import clean_reindex_fill_method
+from pandas.core.ops import get_op_result_name
+from pandas.core.sorting import (
+ ensure_key_mapped,
+ get_group_index_sorter,
+ nargsort,
+)
+from pandas.core.strings.accessor import StringMethods
+
+from pandas.io.formats.printing import (
+ PrettyDict,
+ default_pprint,
+ format_object_summary,
+ pprint_thing,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Callable,
+ Hashable,
+ Iterable,
+ Sequence,
+ )
+
+ from pandas import (
+ CategoricalIndex,
+ DataFrame,
+ MultiIndex,
+ Series,
+ )
+ from pandas.core.arrays import (
+ IntervalArray,
+ PeriodArray,
+ )
+
+__all__ = ["Index"]
+
+_unsortable_types = frozenset(("mixed", "mixed-integer"))
+
+_index_doc_kwargs: dict[str, str] = {
+ "klass": "Index",
+ "inplace": "",
+ "target_klass": "Index",
+ "raises_section": "",
+ "unique": "Index",
+ "duplicated": "np.ndarray",
+}
+_index_shared_docs: dict[str, str] = {}
+str_t = str
+
+_dtype_obj = np.dtype("object")
+
+_masked_engines = {
+ "Complex128": libindex.MaskedComplex128Engine,
+ "Complex64": libindex.MaskedComplex64Engine,
+ "Float64": libindex.MaskedFloat64Engine,
+ "Float32": libindex.MaskedFloat32Engine,
+ "UInt64": libindex.MaskedUInt64Engine,
+ "UInt32": libindex.MaskedUInt32Engine,
+ "UInt16": libindex.MaskedUInt16Engine,
+ "UInt8": libindex.MaskedUInt8Engine,
+ "Int64": libindex.MaskedInt64Engine,
+ "Int32": libindex.MaskedInt32Engine,
+ "Int16": libindex.MaskedInt16Engine,
+ "Int8": libindex.MaskedInt8Engine,
+ "boolean": libindex.MaskedBoolEngine,
+ "double[pyarrow]": libindex.MaskedFloat64Engine,
+ "float64[pyarrow]": libindex.MaskedFloat64Engine,
+ "float32[pyarrow]": libindex.MaskedFloat32Engine,
+ "float[pyarrow]": libindex.MaskedFloat32Engine,
+ "uint64[pyarrow]": libindex.MaskedUInt64Engine,
+ "uint32[pyarrow]": libindex.MaskedUInt32Engine,
+ "uint16[pyarrow]": libindex.MaskedUInt16Engine,
+ "uint8[pyarrow]": libindex.MaskedUInt8Engine,
+ "int64[pyarrow]": libindex.MaskedInt64Engine,
+ "int32[pyarrow]": libindex.MaskedInt32Engine,
+ "int16[pyarrow]": libindex.MaskedInt16Engine,
+ "int8[pyarrow]": libindex.MaskedInt8Engine,
+ "bool[pyarrow]": libindex.MaskedBoolEngine,
+}
+
+
+def _maybe_return_indexers(meth: F) -> F:
+ """
+ Decorator to simplify 'return_indexers' checks in Index.join.
+ """
+
+ @functools.wraps(meth)
+ def join(
+ self,
+ other: Index,
+ *,
+ how: JoinHow = "left",
+ level=None,
+ return_indexers: bool = False,
+ sort: bool = False,
+ ):
+ join_index, lidx, ridx = meth(self, other, how=how, level=level, sort=sort)
+ if not return_indexers:
+ return join_index
+
+ if lidx is not None:
+ lidx = ensure_platform_int(lidx)
+ if ridx is not None:
+ ridx = ensure_platform_int(ridx)
+ return join_index, lidx, ridx
+
+ return cast(F, join)
+
+
+def _new_Index(cls, d):
+ """
+ This is called upon unpickling, rather than the default which doesn't
+ have arguments and breaks __new__.
+ """
+ # required for backward compat, because PI can't be instantiated with
+ # ordinals through __new__ GH #13277
+ d["copy"] = False
+ if issubclass(cls, ABCPeriodIndex):
+ from pandas.core.indexes.period import _new_PeriodIndex
+
+ return _new_PeriodIndex(cls, **d)
+
+ if issubclass(cls, ABCMultiIndex):
+ if "labels" in d and "codes" not in d:
+ # GH#23752 "labels" kwarg has been replaced with "codes"
+ d["codes"] = d.pop("labels")
+
+ # Since this was a valid MultiIndex at pickle-time, we don't need to
+ # check validty at un-pickle time.
+ d["verify_integrity"] = False
+
+ elif "dtype" not in d and "data" in d:
+ # Prevent Index.__new__ from conducting inference;
+ # "data" key not in RangeIndex
+ d["dtype"] = d["data"].dtype
+ return cls.__new__(cls, **d)
+
+
+@set_module("pandas")
+class Index(IndexOpsMixin, PandasObject):
+ """
+ Immutable sequence used for indexing and alignment.
+
+ The basic object storing axis labels for all pandas objects.
+
+ .. versionchanged:: 2.0.0
+
+ Index can hold all numpy numeric dtypes (except float16). Previously only
+ int64/uint64/float64 dtypes were accepted.
+
+ Parameters
+ ----------
+ data : array-like (1-dimensional)
+ An array-like structure containing the data for the index. This could be a
+ Python list, a NumPy array, or a pandas Series.
+ dtype : str, numpy.dtype, or ExtensionDtype, optional
+ Data type for the output Index. If not specified, this will be
+ inferred from `data`.
+ See the :ref:`user guide ` for more usages.
+ copy : bool, default None
+ Whether to copy input data, only relevant for array, Series, and Index
+ inputs (for other input, e.g. a list, a new array is created anyway).
+ Defaults to True for array input and False for Index/Series.
+ Set to False to avoid copying array input at your own risk (if you
+ know the input data won't be modified elsewhere).
+ Set to True to force copying Series/Index input up front.
+ name : object
+ Name to be stored in the index.
+ tupleize_cols : bool (default: True)
+ When True, attempt to create a MultiIndex if possible.
+
+ See Also
+ --------
+ RangeIndex : Index implementing a monotonic integer range.
+ CategoricalIndex : Index of :class:`Categorical` s.
+ MultiIndex : A multi-level, or hierarchical Index.
+ IntervalIndex : An Index of :class:`Interval` s.
+ DatetimeIndex : Index of datetime64 data.
+ TimedeltaIndex : Index of timedelta64 data.
+ PeriodIndex : Index of Period data.
+
+ Notes
+ -----
+ An Index instance can **only** contain hashable objects.
+ An Index instance *can not* hold numpy float16 dtype.
+
+ Examples
+ --------
+ >>> pd.Index([1, 2, 3])
+ Index([1, 2, 3], dtype='int64')
+
+ >>> pd.Index(list("abc"))
+ Index(['a', 'b', 'c'], dtype='str')
+
+ >>> pd.Index([1, 2, 3], dtype="uint8")
+ Index([1, 2, 3], dtype='uint8')
+ """
+
+ # similar to __array_priority__, positions Index after Series and DataFrame
+ # but before ExtensionArray. Should NOT be overridden by subclasses.
+ __pandas_priority__ = 2000
+
+ # Cython methods; see github.com/cython/cython/issues/2647
+ # for why we need to wrap these instead of making them class attributes
+ # Moreover, cython will choose the appropriate-dtyped sub-function
+ # given the dtypes of the passed arguments
+
+ @final
+ def _left_indexer_unique(self, other: Self) -> npt.NDArray[np.intp]:
+ # Caller is responsible for ensuring other.dtype == self.dtype
+ sv = self._get_join_target()
+ ov = other._get_join_target()
+ # similar but not identical to ov.searchsorted(sv)
+ return libjoin.left_join_indexer_unique(sv, ov)
+
+ @final
+ def _left_indexer(
+ self, other: Self
+ ) -> tuple[ArrayLike, npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ # Caller is responsible for ensuring other.dtype == self.dtype
+ sv = self._get_join_target()
+ ov = other._get_join_target()
+ joined_ndarray, lidx, ridx = libjoin.left_join_indexer(sv, ov)
+ joined = self._from_join_target(joined_ndarray)
+ return joined, lidx, ridx
+
+ @final
+ def _inner_indexer(
+ self, other: Self
+ ) -> tuple[ArrayLike, npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ # Caller is responsible for ensuring other.dtype == self.dtype
+ sv = self._get_join_target()
+ ov = other._get_join_target()
+ joined_ndarray, lidx, ridx = libjoin.inner_join_indexer(sv, ov)
+ joined = self._from_join_target(joined_ndarray)
+ return joined, lidx, ridx
+
+ @final
+ def _outer_indexer(
+ self, other: Self
+ ) -> tuple[ArrayLike, npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ # Caller is responsible for ensuring other.dtype == self.dtype
+ sv = self._get_join_target()
+ ov = other._get_join_target()
+ joined_ndarray, lidx, ridx = libjoin.outer_join_indexer(sv, ov)
+ joined = self._from_join_target(joined_ndarray)
+ return joined, lidx, ridx
+
+ _typ: str = "index"
+ _data: ExtensionArray | np.ndarray
+ _data_cls: type[ExtensionArray] | tuple[type[np.ndarray], type[ExtensionArray]] = (
+ np.ndarray,
+ ExtensionArray,
+ )
+ _id: object | None = None
+ _name: Hashable = None
+ # MultiIndex.levels previously allowed setting the index name. We
+ # don't allow this anymore, and raise if it happens rather than
+ # failing silently.
+ _no_setting_name: bool = False
+ _comparables: list[str] = ["name"]
+ _attributes: list[str] = ["name"]
+
+ @cache_readonly
+ def _can_hold_strings(self) -> bool:
+ return not is_numeric_dtype(self.dtype)
+
+ _engine_types: dict[np.dtype | ExtensionDtype, type[libindex.IndexEngine]] = {
+ np.dtype(np.int8): libindex.Int8Engine,
+ np.dtype(np.int16): libindex.Int16Engine,
+ np.dtype(np.int32): libindex.Int32Engine,
+ np.dtype(np.int64): libindex.Int64Engine,
+ np.dtype(np.uint8): libindex.UInt8Engine,
+ np.dtype(np.uint16): libindex.UInt16Engine,
+ np.dtype(np.uint32): libindex.UInt32Engine,
+ np.dtype(np.uint64): libindex.UInt64Engine,
+ np.dtype(np.float32): libindex.Float32Engine,
+ np.dtype(np.float64): libindex.Float64Engine,
+ np.dtype(np.complex64): libindex.Complex64Engine,
+ np.dtype(np.complex128): libindex.Complex128Engine,
+ }
+
+ @property
+ def _engine_type(
+ self,
+ ) -> type[libindex.IndexEngine | libindex.ExtensionEngine]:
+ return self._engine_types.get(self.dtype, libindex.ObjectEngine)
+
+ # whether we support partial string indexing. Overridden
+ # in DatetimeIndex and PeriodIndex
+ _supports_partial_string_indexing = False
+
+ _accessors = {"str"}
+
+ str = Accessor("str", StringMethods)
+
+ _references: BlockValuesRefs | None = None
+
+ # --------------------------------------------------------------------
+ # Constructors
+
+ def __new__(
+ cls,
+ data=None,
+ dtype=None,
+ copy: bool | None = None,
+ name=None,
+ tupleize_cols: bool = True,
+ ) -> Self:
+ from pandas.core.indexes.range import RangeIndex
+
+ name = maybe_extract_name(name, data, cls)
+
+ if dtype is not None:
+ dtype = pandas_dtype(dtype)
+
+ data_dtype = getattr(data, "dtype", None)
+
+ refs = None
+ if not copy and isinstance(data, (ABCSeries, Index)):
+ refs = data._references
+
+ # GH 63306, GH 63388
+ data, copy = cls._maybe_copy_array_input(data, copy, dtype)
+
+ # range
+ if isinstance(data, (range, RangeIndex)):
+ result = RangeIndex(start=data, copy=bool(copy), name=name)
+ if dtype is not None:
+ return result.astype(dtype, copy=False)
+ # error: Incompatible return value type (got "MultiIndex",
+ # expected "Self")
+ return result # type: ignore[return-value]
+
+ elif is_ea_or_datetimelike_dtype(dtype):
+ # non-EA dtype indexes have special casting logic, so we punt here
+ if isinstance(data, (set, frozenset)):
+ data = list(data)
+
+ elif is_ea_or_datetimelike_dtype(data_dtype):
+ pass
+
+ elif isinstance(data, (np.ndarray, ABCMultiIndex)):
+ if isinstance(data, ABCMultiIndex):
+ data = data._values
+
+ if data.dtype.kind not in "iufcbmM":
+ # GH#11836 we need to avoid having numpy coerce
+ # things that look like ints/floats to ints unless
+ # they are actually ints, e.g. '0' and 0.0
+ # should not be coerced
+ data = com.asarray_tuplesafe(data, dtype=_dtype_obj)
+ elif isinstance(data, (ABCSeries, Index)):
+ # GH 56244: Avoid potential inference on object types
+ pass
+ elif is_scalar(data):
+ raise cls._raise_scalar_data_error(data)
+ elif hasattr(data, "__array__"):
+ return cls(np.asarray(data), dtype=dtype, copy=copy, name=name)
+ elif not is_list_like(data) and not isinstance(data, memoryview):
+ # 2022-11-16 the memoryview check is only necessary on some CI
+ # builds, not clear why
+ raise cls._raise_scalar_data_error(data)
+
+ else:
+ if tupleize_cols:
+ # GH21470: convert iterable to list before determining if empty
+ if is_iterator(data):
+ data = list(data)
+
+ if data and all(isinstance(e, tuple) for e in data):
+ # we must be all tuples, otherwise don't construct
+ # 10697
+ from pandas.core.indexes.multi import MultiIndex
+
+ # error: Incompatible return value type (got "MultiIndex",
+ # expected "Self")
+ return MultiIndex.from_tuples( # type: ignore[return-value]
+ data, names=name
+ )
+ # other iterable of some kind
+
+ if not isinstance(data, (list, tuple)):
+ # we allow set/frozenset, which Series/sanitize_array does not, so
+ # cast to list here
+ data = list(data)
+ if len(data) == 0:
+ # unlike Series, we default to object dtype:
+ data = np.array(data, dtype=object)
+
+ if len(data) and isinstance(data[0], tuple):
+ # Ensure we get 1-D array of tuples instead of 2D array.
+ data = com.asarray_tuplesafe(data, dtype=_dtype_obj)
+
+ try:
+ arr = sanitize_array(data, None, dtype=dtype, copy=bool(copy))
+ except ValueError as err:
+ if "index must be specified when data is not list-like" in str(err):
+ raise cls._raise_scalar_data_error(data) from err
+ if "Data must be 1-dimensional" in str(err):
+ raise ValueError("Index data must be 1-dimensional") from err
+ raise
+ arr = ensure_wrapped_if_datetimelike(arr)
+
+ klass = cls._dtype_to_subclass(arr.dtype)
+
+ arr = klass._ensure_array(arr, arr.dtype, copy=False)
+ return klass._simple_new(arr, name, refs=refs)
+
+ @classmethod
+ def _ensure_array(cls, data, dtype, copy: bool):
+ """
+ Ensure we have a valid array to pass to _simple_new.
+ """
+ if data.ndim > 1:
+ # GH#13601, GH#20285, GH#27125
+ raise ValueError("Index data must be 1-dimensional")
+ elif dtype == np.float16:
+ # float16 not supported (no indexing engine)
+ raise NotImplementedError("float16 indexes are not supported")
+
+ if copy:
+ # asarray_tuplesafe does not always copy underlying data,
+ # so need to make sure that this happens
+ data = data.copy()
+ return data
+
+ @final
+ @classmethod
+ def _dtype_to_subclass(cls, dtype: DtypeObj):
+ # Delay import for perf. https://github.com/pandas-dev/pandas/pull/31423
+
+ if isinstance(dtype, ExtensionDtype):
+ return dtype.index_class
+
+ if dtype.kind == "M":
+ from pandas import DatetimeIndex
+
+ return DatetimeIndex
+
+ elif dtype.kind == "m":
+ from pandas import TimedeltaIndex
+
+ return TimedeltaIndex
+
+ elif dtype.kind == "O":
+ # NB: assuming away MultiIndex
+ return Index
+
+ elif issubclass(dtype.type, str) or is_numeric_dtype(dtype):
+ return Index
+
+ raise NotImplementedError(dtype)
+
+ # NOTE for new Index creation:
+
+ # - _simple_new: It returns new Index with the same type as the caller.
+ # All metadata (such as name) must be provided by caller's responsibility.
+ # Using _shallow_copy is recommended because it fills these metadata
+ # otherwise specified.
+
+ # - _shallow_copy: It returns new Index with the same type (using
+ # _simple_new), but fills caller's metadata otherwise specified. Passed
+ # kwargs will overwrite corresponding metadata.
+
+ # See each method's docstring.
+
+ @classmethod
+ def _simple_new(
+ cls,
+ values: ArrayLike,
+ name: Hashable | None = None,
+ refs: BlockValuesRefs | None = None,
+ ) -> Self:
+ """
+ We require that we have a dtype compat for the values. If we are passed
+ a non-dtype compat, then coerce using the constructor.
+
+ Must be careful not to recurse.
+ """
+ assert isinstance(values, cls._data_cls), type(values)
+
+ result = object.__new__(cls)
+ result._data = values
+ result._name = name
+ result._cache = {}
+ result._reset_identity()
+ if refs is not None:
+ result._references = refs
+ else:
+ result._references = BlockValuesRefs()
+ result._references.add_index_reference(result)
+
+ return result
+
+ @classmethod
+ def _with_infer(cls, *args, **kwargs):
+ """
+ Constructor that uses the 1.0.x behavior inferring numeric dtypes
+ for ndarray[object] inputs.
+ """
+ result = cls(*args, **kwargs)
+
+ if result.dtype == _dtype_obj and not result._is_multi:
+ # error: Argument 1 to "maybe_convert_objects" has incompatible type
+ # "Union[ExtensionArray, ndarray[Any, Any]]"; expected
+ # "ndarray[Any, Any]"
+ values = lib.maybe_convert_objects(result._values) # type: ignore[arg-type]
+ if values.dtype.kind in "iufb":
+ return Index(values, name=result.name, copy=False)
+
+ return result
+
+ @cache_readonly
+ def _constructor(self) -> type[Self]:
+ return type(self)
+
+ @final
+ def _maybe_check_unique(self) -> None:
+ """
+ Check that an Index has no duplicates.
+
+ This is typically only called via
+ `NDFrame.flags.allows_duplicate_labels.setter` when it's set to
+ True (duplicates aren't allowed).
+
+ Raises
+ ------
+ DuplicateLabelError
+ When the index is not unique.
+ """
+ if not self.is_unique:
+ msg = """Index has duplicates."""
+ duplicates = self._format_duplicate_message()
+ msg += f"\n{duplicates}"
+
+ raise DuplicateLabelError(msg)
+
+ @final
+ def _format_duplicate_message(self) -> DataFrame:
+ """
+ Construct the DataFrame for a DuplicateLabelError.
+
+ This returns a DataFrame indicating the labels and positions
+ of duplicates in an index. This should only be called when it's
+ already known that duplicates are present.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "a"])
+ >>> idx._format_duplicate_message()
+ positions
+ label
+ a [0, 2]
+ """
+ from pandas import Series
+
+ duplicates = self[self.duplicated(keep="first")].unique()
+ assert len(duplicates)
+
+ out = (
+ Series(np.arange(len(self)), copy=False)
+ .groupby(self, observed=False)
+ .agg(list)[duplicates]
+ )
+ if self._is_multi:
+ # test_format_duplicate_labels_message_multi
+ # error: "Type[Index]" has no attribute "from_tuples" [attr-defined]
+ out.index = type(self).from_tuples(out.index) # type: ignore[attr-defined]
+
+ if self.nlevels == 1:
+ out = out.rename_axis("label")
+ return out.to_frame(name="positions")
+
+ # --------------------------------------------------------------------
+ # Index Internals Methods
+
+ def _shallow_copy(self, values, name: Hashable = no_default) -> Self:
+ """
+ Create a new Index with the same class as the caller, don't copy the
+ data, use the same object attributes with passed in attributes taking
+ precedence.
+
+ *this is an internal non-public method*
+
+ Parameters
+ ----------
+ values : the values to create the new Index, optional
+ name : Label, defaults to self.name
+ """
+ name = self._name if name is no_default else name
+
+ return self._simple_new(values, name=name, refs=self._references)
+
+ def _view(self) -> Self:
+ """
+ fastpath to make a shallow copy, i.e. new object with same data.
+ """
+ result = self._simple_new(self._values, name=self._name, refs=self._references)
+
+ result._cache = self._cache
+ return result
+
+ @final
+ def _rename(self, name: Hashable) -> Self:
+ """
+ fastpath for rename if new name is already validated.
+ """
+ result = self._view()
+ result._name = name
+ return result
+
+ @final
+ def is_(self, other) -> bool:
+ """
+ More flexible, faster check like ``is`` but that works through views.
+
+ Note: this is *not* the same as ``Index.identical()``, which checks
+ that metadata is also the same.
+
+ Parameters
+ ----------
+ other : object
+ Other object to compare against.
+
+ Returns
+ -------
+ bool
+ True if both have same underlying data, False otherwise.
+
+ See Also
+ --------
+ Index.identical : Works like ``Index.is_`` but also checks metadata.
+
+ Examples
+ --------
+ >>> idx1 = pd.Index(["1", "2", "3"])
+ >>> idx1.is_(idx1.view())
+ True
+
+ >>> idx1.is_(idx1.copy())
+ False
+ """
+ if self is other:
+ return True
+ elif not hasattr(other, "_id"):
+ return False
+ elif self._id is None or other._id is None:
+ return False
+ else:
+ return self._id is other._id
+
+ @final
+ def _reset_identity(self) -> None:
+ """
+ Initializes or resets ``_id`` attribute with new object.
+ """
+ self._id = object()
+
+ @final
+ def _cleanup(self) -> None:
+ if "_engine" in self._cache:
+ self._engine.clear_mapping()
+
+ @cache_readonly
+ def _engine(
+ self,
+ ) -> libindex.IndexEngine | libindex.ExtensionEngine | libindex.MaskedIndexEngine:
+ # For base class (object dtype) we get ObjectEngine
+ target_values = self._get_engine_target()
+
+ if isinstance(self._values, ArrowExtensionArray) and self.dtype.kind in "Mm":
+ import pyarrow as pa
+
+ pa_type = self._values._pa_array.type
+ if pa.types.is_timestamp(pa_type):
+ target_values = self._values._to_datetimearray()
+ return libindex.DatetimeEngine(target_values._ndarray)
+ elif pa.types.is_duration(pa_type):
+ target_values = self._values._to_timedeltaarray()
+ return libindex.TimedeltaEngine(target_values._ndarray)
+
+ if isinstance(target_values, ExtensionArray):
+ if isinstance(target_values, (BaseMaskedArray, ArrowExtensionArray)):
+ try:
+ return _masked_engines[target_values.dtype.name](target_values)
+ except KeyError:
+ # Not supported yet e.g. decimal
+ pass
+ elif self._engine_type is libindex.ObjectEngine:
+ return libindex.ExtensionEngine(target_values)
+
+ target_values = cast(np.ndarray, target_values)
+ # to avoid a reference cycle, bind `target_values` to a local variable, so
+ # `self` is not passed into the lambda.
+ if target_values.dtype == bool:
+ return libindex.BoolEngine(target_values)
+ elif target_values.dtype == np.complex64:
+ return libindex.Complex64Engine(target_values)
+ elif target_values.dtype == np.complex128:
+ return libindex.Complex128Engine(target_values)
+ elif needs_i8_conversion(self.dtype):
+ # We need to keep M8/m8 dtype when initializing the Engine,
+ # but don't want to change _get_engine_target bc it is used
+ # elsewhere
+ # error: Item "ExtensionArray" of "Union[ExtensionArray,
+ # ndarray[Any, Any]]" has no attribute "_ndarray" [union-attr]
+ target_values = self._data._ndarray # type: ignore[union-attr]
+ elif is_string_dtype(self.dtype) and not is_object_dtype(self.dtype):
+ return libindex.StringObjectEngine(target_values, self.dtype.na_value) # type: ignore[union-attr]
+
+ # error: Argument 1 to "ExtensionEngine" has incompatible type
+ # "ndarray[Any, Any]"; expected "ExtensionArray"
+ return self._engine_type(target_values) # type: ignore[arg-type]
+
+ @final
+ @cache_readonly
+ def _dir_additions_for_owner(self) -> set[str_t]:
+ """
+ Add the string-like labels to the owner dataframe/series dir output.
+
+ If this is a MultiIndex, it's first level values are used.
+ """
+ return {
+ c
+ for c in self.unique(level=0)[: get_option("display.max_dir_items")]
+ if isinstance(c, str) and c.isidentifier()
+ }
+
+ # --------------------------------------------------------------------
+ # Array-Like Methods
+
+ # ndarray compat
+ def __len__(self) -> int:
+ """
+ Return the length of the Index.
+ """
+ return len(self._data)
+
+ def __array__(self, dtype=None, copy=None) -> np.ndarray:
+ """
+ The array interface, return my values.
+ """
+ if copy is None:
+ # Note, that the if branch exists for NumPy 1.x support
+ return np.asarray(self._data, dtype=dtype)
+
+ return np.array(self._data, dtype=dtype, copy=copy)
+
+ def __array_ufunc__(self, ufunc: np.ufunc, method: str_t, *inputs, **kwargs):
+ if any(isinstance(other, (ABCSeries, ABCDataFrame)) for other in inputs):
+ return NotImplemented
+
+ result = arraylike.maybe_dispatch_ufunc_to_dunder_op(
+ self, ufunc, method, *inputs, **kwargs
+ )
+ if result is not NotImplemented:
+ return result
+
+ if "out" in kwargs:
+ # e.g. test_dti_isub_tdi
+ return arraylike.dispatch_ufunc_with_out(
+ self, ufunc, method, *inputs, **kwargs
+ )
+
+ if method == "reduce":
+ result = arraylike.dispatch_reduction_ufunc(
+ self, ufunc, method, *inputs, **kwargs
+ )
+ if result is not NotImplemented:
+ return result
+
+ new_inputs = [x if x is not self else x._values for x in inputs]
+ result = getattr(ufunc, method)(*new_inputs, **kwargs)
+ if ufunc.nout == 2:
+ # i.e. np.divmod, np.modf, np.frexp
+ return tuple(self.__array_wrap__(x) for x in result)
+ elif method == "reduce":
+ result = lib.item_from_zerodim(result)
+ return maybe_unbox_numpy_scalar(result)
+ elif is_scalar(result):
+ # e.g. matmul
+ return maybe_unbox_numpy_scalar(result)
+
+ if result.dtype == np.float16:
+ result = result.astype(np.float32)
+
+ return self.__array_wrap__(result)
+
+ @final
+ def __array_wrap__(self, result, context=None, return_scalar=False):
+ """
+ Gets called after a ufunc and other functions e.g. np.split.
+ """
+ result = lib.item_from_zerodim(result)
+ if np.ndim(result) > 1:
+ # Reached in plotting tests with e.g. np.nonzero(index)
+ return result
+
+ return Index(result, name=self.name)
+
+ @cache_readonly
+ def dtype(self) -> DtypeObj:
+ """
+ Return the dtype object of the underlying data.
+
+ See Also
+ --------
+ Index.inferred_type: Return a string of the type inferred from the values.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx
+ Index([1, 2, 3], dtype='int64')
+ >>> idx.dtype
+ dtype('int64')
+ """
+ return self._data.dtype
+
+ @final
+ def ravel(self, order: str_t = "C") -> Self:
+ """
+ Return a view on self.
+
+ Parameters
+ ----------
+ order : {'K', 'A', 'C', 'F'}, default 'C'
+ Specify the memory layout of the view. This parameter is not
+ implemented currently.
+
+ Returns
+ -------
+ Index
+ A view on self.
+
+ See Also
+ --------
+ numpy.ndarray.ravel : Return a flattened array.
+
+ Examples
+ --------
+ >>> s = pd.Series([1, 2, 3], index=["a", "b", "c"])
+ >>> s.index.ravel()
+ Index(['a', 'b', 'c'], dtype='str')
+ """
+ return self[:]
+
+ def view(self, cls=None):
+ """
+ Return a view of the Index with the specified dtype or a new Index instance.
+
+ This method returns a view of the calling Index object if no arguments are
+ provided. If a dtype is specified through the `cls` argument, it attempts
+ to return a view of the Index with the specified dtype. Note that viewing
+ the Index as a different dtype reinterprets the underlying data, which can
+ lead to unexpected results for non-numeric or incompatible dtype conversions.
+
+ Parameters
+ ----------
+ cls : data-type or ndarray sub-class, optional
+ Data-type descriptor of the returned view, e.g., float32 or int16.
+ Omitting it results in the view having the same data-type as `self`.
+ This argument can also be specified as an ndarray sub-class,
+ e.g., np.int64 or np.float32 which then specifies the type of
+ the returned object.
+
+ Returns
+ -------
+ Index or ndarray
+ A view of the Index. If `cls` is None, the returned object is an Index
+ view with the same dtype as the calling object. If a numeric `cls` is
+ specified an ndarray view with the new dtype is returned.
+
+ Raises
+ ------
+ ValueError
+ If attempting to change to a dtype in a way that is not compatible with
+ the original dtype's memory layout, for example, viewing an 'int64' Index
+ as 'str'.
+
+ See Also
+ --------
+ Index.copy : Returns a copy of the Index.
+ numpy.ndarray.view : Returns a new view of array with the same data.
+
+ Examples
+ --------
+ >>> idx = pd.Index([-1, 0, 1])
+ >>> idx.view()
+ Index([-1, 0, 1], dtype='int64')
+
+ >>> idx.view(np.uint64)
+ array([18446744073709551615, 0, 1],
+ dtype=uint64)
+
+ Viewing as 'int32' or 'float32' reinterprets the memory, which may lead to
+ unexpected behavior:
+
+ >>> idx.view("float32")
+ array([ nan, nan, 0.e+00, 0.e+00, 1.e-45, 0.e+00], dtype=float32)
+ """
+ # we need to see if we are subclassing an
+ # index type here
+ if cls is not None:
+ dtype = cls
+ if isinstance(cls, str):
+ dtype = pandas_dtype(cls)
+
+ if needs_i8_conversion(dtype):
+ idx_cls = self._dtype_to_subclass(dtype)
+ arr = self.array.view(dtype)
+ if isinstance(arr, ExtensionArray):
+ # here we exclude non-supported dt64/td64 dtypes
+ return idx_cls._simple_new(
+ arr, name=self.name, refs=self._references
+ )
+ return arr
+
+ result = self._data.view(cls)
+ else:
+ result = self._view()
+ if isinstance(result, Index):
+ result._id = self._id
+ return result
+
+ def astype(self, dtype: Dtype, copy: bool = True):
+ """
+ Create an Index with values cast to dtypes.
+
+ The class of a new Index is determined by dtype. When conversion is
+ impossible, a TypeError exception is raised.
+
+ Parameters
+ ----------
+ dtype : numpy dtype or pandas type
+ Note that any signed integer `dtype` is treated as ``'int64'``,
+ and any unsigned integer `dtype` is treated as ``'uint64'``,
+ regardless of the size.
+ copy : bool, default True
+ By default, astype always returns a newly allocated object.
+ If copy is set to False and internal requirements on dtype are
+ satisfied, the original data is used to create a new Index
+ or the original Index is returned.
+
+ Returns
+ -------
+ Index
+ Index with values cast to specified dtype.
+
+ See Also
+ --------
+ Index.dtype: Return the dtype object of the underlying data.
+ Index.dtypes: Return the dtype object of the underlying data.
+ Index.convert_dtypes: Convert columns to the best possible dtypes.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx
+ Index([1, 2, 3], dtype='int64')
+ >>> idx.astype("float")
+ Index([1.0, 2.0, 3.0], dtype='float64')
+ """
+ if dtype is not None:
+ dtype = pandas_dtype(dtype)
+
+ if self.dtype == dtype:
+ # Ensure that self.astype(self.dtype) is self
+ return self.copy() if copy else self
+
+ values = self._data
+ if isinstance(values, ExtensionArray):
+ with rewrite_exception(type(values).__name__, type(self).__name__):
+ new_values = values.astype(dtype, copy=copy)
+
+ elif isinstance(dtype, ExtensionDtype):
+ cls = dtype.construct_array_type()
+ # Note: for RangeIndex and CategoricalDtype self vs self._values
+ # behaves differently here.
+ new_values = cls._from_sequence(self, dtype=dtype, copy=copy)
+
+ else:
+ # GH#13149 specifically use astype_array instead of astype
+ new_values = astype_array(values, dtype=dtype, copy=copy)
+
+ # pass copy=False because any copying will be done in the astype above
+ result = Index(new_values, name=self.name, dtype=new_values.dtype, copy=False)
+ if (
+ not copy
+ and self._references is not None
+ and astype_is_view(self.dtype, dtype)
+ ):
+ result._references = self._references
+ result._references.add_index_reference(result)
+ return result
+
+ _index_shared_docs["take"] = """
+ Return a new %(klass)s of the values selected by the indices.
+
+ For internal compatibility with numpy arrays.
+
+ Parameters
+ ----------
+ indices : array-like
+ Indices to be taken.
+ axis : {0 or 'index'}, optional
+ The axis over which to select values, always 0 or 'index'.
+ allow_fill : bool, default True
+ How to handle negative values in `indices`.
+
+ * False: negative values in `indices` indicate positional indices
+ from the right (the default). This is similar to
+ :func:`numpy.take`.
+
+ * True: negative values in `indices` indicate
+ missing values. These values are set to `fill_value`. Any other
+ other negative values raise a ``ValueError``.
+
+ fill_value : scalar, default None
+ If allow_fill=True and fill_value is not None, indices specified by
+ -1 are regarded as NA. If Index doesn't hold NA, raise ValueError.
+ **kwargs
+ Required for compatibility with numpy.
+
+ Returns
+ -------
+ Index
+ An index formed of elements at the given indices. Will be the same
+ type as self, except for RangeIndex.
+
+ See Also
+ --------
+ numpy.ndarray.take: Return an array formed from the
+ elements of a at the given indices.
+
+ Examples
+ --------
+ >>> idx = pd.Index(['a', 'b', 'c'])
+ >>> idx.take([2, 2, 1, 2])
+ Index(['c', 'c', 'b', 'c'], dtype='str')
+ """
+
+ def take(
+ self,
+ indices,
+ axis: Axis = 0,
+ allow_fill: bool = True,
+ fill_value=None,
+ **kwargs,
+ ) -> Self:
+ """
+ Return a new Index of the values selected by the indices.
+
+ For internal compatibility with numpy arrays.
+
+ Parameters
+ ----------
+ indices : array-like
+ Indices to be taken.
+ axis : {0 or 'index'}, optional
+ The axis over which to select values, always 0 or 'index'.
+ allow_fill : bool, default True
+ How to handle negative values in `indices`.
+
+ * False: negative values in `indices` indicate positional indices
+ from the right (the default). This is similar to
+ :func:`numpy.take`.
+
+ * True: negative values in `indices` indicate
+ missing values. These values are set to `fill_value`. Any
+ other negative values raise a ``ValueError``.
+
+ fill_value : scalar, default None
+ If allow_fill=True and fill_value is not None, indices specified by
+ -1 are regarded as NA. If Index doesn't hold NA, raise ValueError.
+ **kwargs
+ Required for compatibility with numpy.
+
+ Returns
+ -------
+ Index
+ An index formed of elements at the given indices. Will be the same
+ type as self, except for RangeIndex.
+
+ See Also
+ --------
+ numpy.ndarray.take: Return an array formed from the
+ elements of a at the given indices.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.take([2, 2, 1, 2])
+ Index(['c', 'c', 'b', 'c'], dtype='str')
+ """
+ if kwargs:
+ nv.validate_take((), kwargs)
+ if is_scalar(indices):
+ raise TypeError("Expected indices to be array-like")
+ indices = ensure_platform_int(indices)
+ allow_fill = self._maybe_disallow_fill(allow_fill, fill_value, indices)
+
+ if indices.ndim == 1 and lib.is_range_indexer(indices, len(self)):
+ return self.copy()
+
+ # Note: we discard fill_value and use self._na_value, only relevant
+ # in the case where allow_fill is True and fill_value is not None
+ values = self._values
+ if isinstance(values, np.ndarray):
+ taken = algos.take(
+ values, indices, allow_fill=allow_fill, fill_value=self._na_value
+ )
+ else:
+ # algos.take passes 'axis' keyword which not all EAs accept
+ taken = values.take(
+ indices, allow_fill=allow_fill, fill_value=self._na_value
+ )
+ return self._constructor._simple_new(taken, name=self.name)
+
+ @final
+ def _maybe_disallow_fill(self, allow_fill: bool, fill_value, indices) -> bool:
+ """
+ We only use pandas-style take when allow_fill is True _and_
+ fill_value is not None.
+ """
+ if allow_fill and fill_value is not None:
+ # only fill if we are passing a non-None fill_value
+ if self._can_hold_na:
+ if (indices < -1).any():
+ raise ValueError(
+ "When allow_fill=True and fill_value is not None, "
+ "all indices must be >= -1"
+ )
+ else:
+ cls_name = type(self).__name__
+ raise ValueError(
+ f"Unable to fill values because {cls_name} cannot contain NA"
+ )
+ else:
+ allow_fill = False
+ return allow_fill
+
+ def repeat(self, repeats, axis: None = None) -> Self:
+ """
+ Repeat elements of an Index.
+
+ Returns a new Index where each element of the current Index
+ is repeated consecutively a given number of times.
+
+ Parameters
+ ----------
+ repeats : int or array of ints
+ The number of repetitions for each element. This should be a
+ non-negative integer. Repeating 0 times will return an empty
+ Index.
+ axis : None
+ Must be ``None``. Has no effect but is accepted for compatibility
+ with numpy.
+
+ Returns
+ -------
+ Index
+ Newly created Index with repeated elements.
+
+ See Also
+ --------
+ Series.repeat : Equivalent function for Series.
+ numpy.repeat : Similar method for :class:`numpy.ndarray`.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx
+ Index(['a', 'b', 'c'], dtype='str')
+ >>> idx.repeat(2)
+ Index(['a', 'a', 'b', 'b', 'c', 'c'], dtype='str')
+ >>> idx.repeat([1, 2, 3])
+ Index(['a', 'b', 'b', 'c', 'c', 'c'], dtype='str')
+ """
+ repeats = ensure_platform_int(repeats)
+ nv.validate_repeat((), {"axis": axis})
+ res_values = self._values.repeat(repeats)
+
+ # _constructor so RangeIndex-> Index with an int64 dtype
+ return self._constructor._simple_new(res_values, name=self.name)
+
+ # --------------------------------------------------------------------
+ # Copying Methods
+
+ def copy(
+ self,
+ name: Hashable | None = None,
+ deep: bool = False,
+ ) -> Self:
+ """
+ Make a copy of this object.
+
+ Name is set on the new object.
+
+ Parameters
+ ----------
+ name : Label, optional
+ Set name for new object.
+ deep : bool, default False
+ If True attempts to make a deep copy of the Index.
+ Else makes a shallow copy.
+
+ Returns
+ -------
+ Index
+ Index refer to new object which is a copy of this object.
+
+ See Also
+ --------
+ Index.delete: Make new Index with passed location(-s) deleted.
+ Index.drop: Make new Index with passed list of labels deleted.
+
+ Notes
+ -----
+ In most cases, there should be no functional difference from using
+ ``deep``, but if ``deep`` is passed it will attempt to deepcopy.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> new_idx = idx.copy()
+ >>> idx is new_idx
+ False
+ """
+
+ name = self._validate_names(name=name, deep=deep)[0]
+ if deep:
+ new_data = self._data.copy()
+ new_index = type(self)._simple_new(new_data, name=name)
+ else:
+ new_index = self._rename(name=name)
+ return new_index
+
+ @final
+ def __copy__(self) -> Self:
+ return self.copy(deep=False)
+
+ @final
+ def __deepcopy__(self, memo=None) -> Self:
+ """
+ Parameters
+ ----------
+ memo, default None
+ Standard signature. Unused
+ """
+ return self.copy(deep=True)
+
+ # --------------------------------------------------------------------
+ # Rendering Methods
+
+ @final
+ def __repr__(self) -> str_t:
+ """
+ Return a string representation for this object.
+ """
+ klass_name = type(self).__name__
+ data = self._format_data()
+ attrs = self._format_attrs()
+ attrs_str = [f"{k}={v}" for k, v in attrs]
+ prepr = ", ".join(attrs_str)
+
+ return f"{klass_name}({data}{prepr})"
+
+ @property
+ def _formatter_func(self):
+ """
+ Return the formatter function.
+ """
+ return default_pprint
+
+ @final
+ def _format_data(self, name=None) -> str_t:
+ """
+ Return the formatted data as a unicode string.
+ """
+ # do we want to justify (only do so for non-objects)
+ is_justify = True
+
+ if self.inferred_type == "string":
+ is_justify = False
+ elif isinstance(self.dtype, CategoricalDtype):
+ self = cast("CategoricalIndex", self)
+ if is_string_dtype(self.categories.dtype):
+ is_justify = False
+ elif isinstance(self, ABCRangeIndex):
+ # We will do the relevant formatting via attrs
+ return ""
+
+ return format_object_summary(
+ self,
+ self._formatter_func,
+ is_justify=is_justify,
+ name=name,
+ line_break_each_value=self._is_multi,
+ )
+
+ def _format_attrs(self) -> list[tuple[str_t, str_t | int | bool | None]]:
+ """
+ Return a list of tuples of the (attr,formatted_value).
+ """
+ attrs: list[tuple[str_t, str_t | int | bool | None]] = []
+
+ if not self._is_multi:
+ attrs.append(("dtype", f"'{self.dtype}'"))
+
+ if self.name is not None:
+ attrs.append(("name", default_pprint(self.name)))
+ elif self._is_multi and any(x is not None for x in self.names):
+ attrs.append(("names", default_pprint(self.names)))
+
+ max_seq_items = get_option("display.max_seq_items") or len(self)
+ if len(self) > max_seq_items:
+ attrs.append(("length", len(self)))
+ return attrs
+
+ @final
+ def _get_level_names(self) -> range | Sequence[Hashable]:
+ """
+ Return a name or list of names with None replaced by the level number.
+ """
+ if self._is_multi:
+ return maybe_sequence_to_range(
+ [
+ level if name is None else name
+ for level, name in enumerate(self.names)
+ ]
+ )
+ else:
+ return range(1) if self.name is None else [self.name]
+
+ @final
+ def _mpl_repr(self) -> np.ndarray:
+ # how to represent ourselves to matplotlib
+ if isinstance(self.dtype, np.dtype) and self.dtype.kind != "M":
+ return cast(np.ndarray, self.values)
+ return self.astype(object, copy=False)._values
+
+ _default_na_rep = "NaN"
+
+ @final
+ def _format_flat(
+ self,
+ *,
+ include_name: bool,
+ formatter: Callable | None = None,
+ ) -> list[str_t]:
+ """
+ Render a string representation of the Index.
+ """
+ header = []
+ if include_name:
+ header.append(
+ pprint_thing(self.name, escape_chars=("\t", "\r", "\n"))
+ if self.name is not None
+ else ""
+ )
+
+ if formatter is not None:
+ return header + list(self.map(formatter))
+
+ return self._format_with_header(header=header, na_rep=self._default_na_rep)
+
+ def _format_with_header(self, *, header: list[str_t], na_rep: str_t) -> list[str_t]:
+ from pandas.io.formats.format import format_array
+
+ values = self._values
+
+ if (
+ is_object_dtype(values.dtype)
+ or is_string_dtype(values.dtype)
+ or isinstance(self.dtype, (IntervalDtype, CategoricalDtype))
+ ):
+ # TODO: why do we need different justify for these cases?
+ justify = "all"
+ else:
+ justify = "left"
+ # passing leading_space=False breaks test_format_missing,
+ # test_index_repr_in_frame_with_nan, but would otherwise make
+ # trim_front unnecessary
+ formatted = format_array(values, None, justify=justify)
+ result = trim_front(formatted)
+ return header + result
+
+ def _get_values_for_csv(
+ self,
+ *,
+ na_rep: str_t = "",
+ decimal: str_t = ".",
+ float_format=None,
+ date_format=None,
+ quoting=None,
+ ) -> npt.NDArray[np.object_]:
+ return get_values_for_csv(
+ self._values,
+ na_rep=na_rep,
+ decimal=decimal,
+ float_format=float_format,
+ date_format=date_format,
+ quoting=quoting,
+ )
+
+ def _summary(self, name=None) -> str_t:
+ """
+ Return a summarized representation.
+
+ Parameters
+ ----------
+ name : str
+ name to use in the summary representation
+
+ Returns
+ -------
+ String with a summarized representation of the index
+ """
+ if len(self) > 0:
+ head = self[0]
+ if hasattr(head, "format") and not isinstance(head, str):
+ head = head.format()
+ elif needs_i8_conversion(self.dtype):
+ # e.g. Timedelta, display as values, not quoted
+ head = self._formatter_func(head).replace("'", "")
+ tail = self[-1]
+ if hasattr(tail, "format") and not isinstance(tail, str):
+ tail = tail.format()
+ elif needs_i8_conversion(self.dtype):
+ # e.g. Timedelta, display as values, not quoted
+ tail = self._formatter_func(tail).replace("'", "")
+
+ index_summary = f", {head} to {tail}"
+ else:
+ index_summary = ""
+
+ if name is None:
+ name = type(self).__name__
+ return f"{name}: {len(self)} entries{index_summary}"
+
+ # --------------------------------------------------------------------
+ # Conversion Methods
+
+ def to_flat_index(self) -> Self:
+ """
+ Identity method.
+
+ This is implemented for compatibility with subclass implementations
+ when chaining.
+
+ Returns
+ -------
+ pd.Index
+ Caller.
+
+ See Also
+ --------
+ MultiIndex.to_flat_index : Subclass implementation.
+ """
+ return self
+
+ @final
+ def to_series(self, index=None, name: Hashable | None = None) -> Series:
+ """
+ Create a Series with both index and values equal to the index keys.
+
+ Useful with map for returning an indexer based on an index.
+
+ Parameters
+ ----------
+ index : Index, optional
+ Index of resulting Series. If None, defaults to original index.
+ name : str, optional
+ Name of resulting Series. If None, defaults to name of original
+ index.
+
+ Returns
+ -------
+ Series
+ The dtype will be based on the type of the Index values.
+
+ See Also
+ --------
+ Index.to_frame : Convert an Index to a DataFrame.
+ Series.to_frame : Convert Series to DataFrame.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["Ant", "Bear", "Cow"], name="animal")
+
+ By default, the original index and original name is reused.
+
+ >>> idx.to_series()
+ animal
+ Ant Ant
+ Bear Bear
+ Cow Cow
+ Name: animal, dtype: str
+
+ To enforce a new index, specify new labels to ``index``:
+
+ >>> idx.to_series(index=[0, 1, 2])
+ 0 Ant
+ 1 Bear
+ 2 Cow
+ Name: animal, dtype: str
+
+ To override the name of the resulting column, specify ``name``:
+
+ >>> idx.to_series(name="zoo")
+ animal
+ Ant Ant
+ Bear Bear
+ Cow Cow
+ Name: zoo, dtype: str
+ """
+ from pandas import Series
+
+ if index is None:
+ index = self._view()
+ if name is None:
+ name = self.name
+
+ return Series(self._values.copy(), index=index, name=name)
+
+ def to_frame(
+ self, index: bool = True, name: Hashable = lib.no_default
+ ) -> DataFrame:
+ """
+ Create a DataFrame with a column containing the Index.
+
+ Parameters
+ ----------
+ index : bool, default True
+ Set the index of the returned DataFrame as the original Index.
+
+ name : object, defaults to index.name
+ The passed name should substitute for the index name (if it has
+ one).
+
+ Returns
+ -------
+ DataFrame
+ DataFrame containing the original Index data.
+
+ See Also
+ --------
+ Index.to_series : Convert an Index to a Series.
+ Series.to_frame : Convert Series to DataFrame.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["Ant", "Bear", "Cow"], name="animal")
+ >>> idx.to_frame()
+ animal
+ animal
+ Ant Ant
+ Bear Bear
+ Cow Cow
+
+ By default, the original Index is reused. To enforce a new Index:
+
+ >>> idx.to_frame(index=False)
+ animal
+ 0 Ant
+ 1 Bear
+ 2 Cow
+
+ To override the name of the resulting column, specify `name`:
+
+ >>> idx.to_frame(index=False, name="zoo")
+ zoo
+ 0 Ant
+ 1 Bear
+ 2 Cow
+ """
+ from pandas import DataFrame
+
+ if name is lib.no_default:
+ result_name = self._get_level_names()
+ else:
+ result_name = Index([name]) # type: ignore[assignment]
+ result = DataFrame(self, copy=False)
+ result.columns = result_name
+
+ if index:
+ result.index = self
+ return result
+
+ # --------------------------------------------------------------------
+ # Name-Centric Methods
+
+ @property
+ def name(self) -> Hashable:
+ """
+ Return Index or MultiIndex name.
+
+ Returns
+ -------
+ label (hashable object)
+ The name of the Index.
+
+ See Also
+ --------
+ Index.set_names: Able to set new names partially and by level.
+ Index.rename: Able to set new names partially and by level.
+ Series.name: Corresponding Series property.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3], name="x")
+ >>> idx
+ Index([1, 2, 3], dtype='int64', name='x')
+ >>> idx.name
+ 'x'
+ """
+ return self._name
+
+ @name.setter
+ def name(self, value: Hashable) -> None:
+ if self._no_setting_name:
+ # Used in MultiIndex.levels to avoid silently ignoring name updates.
+ raise RuntimeError(
+ "Cannot set name on a level of a MultiIndex. Use "
+ "'MultiIndex.set_names' instead."
+ )
+ maybe_extract_name(value, None, type(self))
+ self._name = value
+
+ @final
+ def _validate_names(
+ self, name=None, names=None, deep: bool = False
+ ) -> list[Hashable]:
+ """
+ Handles the quirks of having a singular 'name' parameter for general
+ Index and plural 'names' parameter for MultiIndex.
+ """
+ from copy import deepcopy
+
+ if names is not None and name is not None:
+ raise TypeError("Can only provide one of `names` and `name`")
+ if names is None and name is None:
+ new_names = deepcopy(self.names) if deep else self.names
+ elif names is not None:
+ if not is_list_like(names):
+ raise TypeError("Must pass list-like as `names`.")
+ new_names = names
+ elif not is_list_like(name):
+ new_names = [name]
+ else:
+ new_names = name
+
+ if len(new_names) != len(self.names):
+ raise ValueError(
+ f"Length of new names must be {len(self.names)}, got {len(new_names)}"
+ )
+
+ # All items in 'new_names' need to be hashable
+ validate_all_hashable(*new_names, error_name=f"{type(self).__name__}.name")
+
+ return new_names
+
+ def _get_default_index_names(
+ self, names: Hashable | Sequence[Hashable] | None = None, default=None
+ ) -> list[Hashable]:
+ """
+ Get names of index.
+
+ Parameters
+ ----------
+ names : int, str or 1-dimensional list, default None
+ Index names to set.
+ default : str
+ Default name of index.
+
+ Raises
+ ------
+ TypeError
+ if names not str or list-like
+ """
+ from pandas.core.indexes.multi import MultiIndex
+
+ if names is not None:
+ if isinstance(names, (int, str)):
+ names = [names]
+
+ if not isinstance(names, list) and names is not None:
+ raise ValueError("Index names must be str or 1-dimensional list")
+
+ if not names:
+ if isinstance(self, MultiIndex):
+ names = com.fill_missing_names(self.names)
+ else:
+ names = [default] if self.name is None else [self.name]
+
+ return names
+
+ def _get_names(self) -> FrozenList:
+ """
+ Get names on index.
+
+ This method returns a FrozenList containing the names of the object.
+ It's primarily intended for internal use.
+
+ Returns
+ -------
+ FrozenList
+ A FrozenList containing the object's names, contains None if the object
+ does not have a name.
+
+ See Also
+ --------
+ Index.name : Index name as a string, or None for MultiIndex.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3], name="x")
+ >>> idx.names
+ FrozenList(['x'])
+
+ >>> idx = pd.Index([1, 2, 3], name=("x", "y"))
+ >>> idx.names
+ FrozenList([('x', 'y')])
+
+ If the index does not have a name set:
+
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx.names
+ FrozenList([None])
+ """
+ return FrozenList((self.name,))
+
+ def _set_names(self, values, *, level=None) -> None:
+ """
+ Set new names on index. Each name has to be a hashable type.
+
+ Parameters
+ ----------
+ values : str or sequence
+ name(s) to set
+ level : int, level name, or sequence of int/level names (default None)
+ If the index is a MultiIndex (hierarchical), level(s) to set (None
+ for all levels). Otherwise level must be None
+
+ Raises
+ ------
+ TypeError if each name is not hashable.
+ """
+ if not is_list_like(values):
+ raise ValueError("Names must be a list-like")
+ if len(values) != 1:
+ raise ValueError(f"Length of new names must be 1, got {len(values)}")
+
+ # GH 20527
+ # All items in 'name' need to be hashable:
+ validate_all_hashable(*values, error_name=f"{type(self).__name__}.name")
+
+ self._name = values[0]
+
+ names = property(fset=_set_names, fget=_get_names)
+
+ @overload
+ def set_names(self, names, *, level=..., inplace: Literal[False] = ...) -> Self: ...
+
+ @overload
+ def set_names(self, names, *, level=..., inplace: Literal[True]) -> None: ...
+
+ @overload
+ def set_names(self, names, *, level=..., inplace: bool = ...) -> Self | None: ...
+
+ def set_names(self, names, *, level=None, inplace: bool = False) -> Self | None:
+ """
+ Set Index or MultiIndex name.
+
+ Able to set new names partially and by level.
+
+ Parameters
+ ----------
+ names : Hashable or a sequence of the previous or dict-like for MultiIndex
+ Name(s) to set.
+
+ level : int, Hashable or a sequence of the previous, optional
+ If the index is a MultiIndex and names is not dict-like, level(s) to set
+ (None for all levels). Otherwise level must be None.
+
+ inplace : bool, default False
+ Modifies the object directly, instead of creating a new Index or
+ MultiIndex.
+
+ Returns
+ -------
+ Index or None
+ The same type as the caller or None if ``inplace=True``.
+
+ See Also
+ --------
+ Index.rename : Able to set new names without level.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3, 4])
+ >>> idx
+ Index([1, 2, 3, 4], dtype='int64')
+ >>> idx.set_names("quarter")
+ Index([1, 2, 3, 4], dtype='int64', name='quarter')
+
+ >>> idx = pd.MultiIndex.from_product([["python", "cobra"], [2018, 2019]])
+ >>> idx
+ MultiIndex([('python', 2018),
+ ('python', 2019),
+ ( 'cobra', 2018),
+ ( 'cobra', 2019)],
+ )
+ >>> idx = idx.set_names(["kind", "year"])
+ >>> idx.set_names("species", level=0)
+ MultiIndex([('python', 2018),
+ ('python', 2019),
+ ( 'cobra', 2018),
+ ( 'cobra', 2019)],
+ names=['species', 'year'])
+
+ When renaming levels with a dict, levels can not be passed.
+
+ >>> idx.set_names({"kind": "snake"})
+ MultiIndex([('python', 2018),
+ ('python', 2019),
+ ( 'cobra', 2018),
+ ( 'cobra', 2019)],
+ names=['snake', 'year'])
+ """
+ if level is not None and not isinstance(self, ABCMultiIndex):
+ raise ValueError("Level must be None for non-MultiIndex")
+
+ if level is not None and not is_list_like(level) and is_list_like(names):
+ raise TypeError("Names must be a string when a single level is provided.")
+
+ if not is_list_like(names) and level is None and self.nlevels > 1:
+ raise TypeError("Must pass list-like as `names`.")
+
+ if is_dict_like(names) and not isinstance(self, ABCMultiIndex):
+ raise TypeError("Can only pass dict-like as `names` for MultiIndex.")
+
+ if is_dict_like(names) and level is not None:
+ raise TypeError("Can not pass level for dictlike `names`.")
+
+ if isinstance(self, ABCMultiIndex) and is_dict_like(names) and level is None:
+ # Transform dict to list of new names and corresponding levels
+ level, names_adjusted = [], []
+ for i, name in enumerate(self.names):
+ if name in names.keys():
+ level.append(i)
+ names_adjusted.append(names[name])
+ names = names_adjusted
+
+ if not is_list_like(names):
+ names = [names]
+ if level is not None and not is_list_like(level):
+ level = [level]
+
+ if inplace:
+ idx = self
+ else:
+ idx = self._view()
+
+ idx._set_names(names, level=level)
+ if not inplace:
+ return idx
+ return None
+
+ @overload
+ def rename(self, name, *, inplace: Literal[False] = ...) -> Self: ...
+
+ @overload
+ def rename(self, name, *, inplace: Literal[True]) -> None: ...
+
+ def rename(self, name, *, inplace: bool = False) -> Self | None:
+ """
+ Alter Index or MultiIndex name.
+
+ Able to set new names without level. Defaults to returning new index.
+ Length of names must match number of levels in MultiIndex.
+
+ Parameters
+ ----------
+ name : Hashable or a sequence of the previous
+ Name(s) to set.
+ inplace : bool, default False
+ Modifies the object directly, instead of creating a new Index or
+ MultiIndex.
+
+ Returns
+ -------
+ Index or None
+ The same type as the caller or None if ``inplace=True``.
+
+ See Also
+ --------
+ Index.set_names : Able to set new names partially and by level.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["A", "C", "A", "B"], name="score")
+ >>> idx.rename("grade")
+ Index(['A', 'C', 'A', 'B'], dtype='str', name='grade')
+
+ >>> idx = pd.MultiIndex.from_product(
+ ... [["python", "cobra"], [2018, 2019]], names=["kind", "year"]
+ ... )
+ >>> idx
+ MultiIndex([('python', 2018),
+ ('python', 2019),
+ ( 'cobra', 2018),
+ ( 'cobra', 2019)],
+ names=['kind', 'year'])
+ >>> idx.rename(["species", "year"])
+ MultiIndex([('python', 2018),
+ ('python', 2019),
+ ( 'cobra', 2018),
+ ( 'cobra', 2019)],
+ names=['species', 'year'])
+ >>> idx.rename("species")
+ Traceback (most recent call last):
+ TypeError: Must pass list-like as `names`.
+ """
+ return self.set_names([name], inplace=inplace)
+
+ # --------------------------------------------------------------------
+ # Level-Centric Methods
+
+ @property
+ def nlevels(self) -> int:
+ """
+ Number of levels.
+ """
+ return 1
+
+ def _sort_levels_monotonic(self) -> Self:
+ """
+ Compat with MultiIndex.
+ """
+ return self
+
+ @final
+ def _validate_index_level(self, level) -> None:
+ """
+ Validate index level.
+
+ For single-level Index getting level number is a no-op, but some
+ verification must be done like in MultiIndex.
+
+ """
+ if isinstance(level, int):
+ if level < 0 and level != -1:
+ raise IndexError(
+ "Too many levels: Index has only 1 level, "
+ f"{level} is not a valid level number"
+ )
+ if level > 0:
+ raise IndexError(
+ f"Too many levels: Index has only 1 level, not {level + 1}"
+ )
+ elif level != self.name:
+ raise KeyError(
+ f"Requested level ({level}) does not match index name ({self.name})"
+ )
+
+ def _get_level_number(self, level) -> int:
+ self._validate_index_level(level)
+ return 0
+
+ def sortlevel(
+ self,
+ level=None,
+ ascending: bool | list[bool] = True,
+ sort_remaining=None,
+ na_position: NaPosition = "first",
+ ) -> tuple[Self, np.ndarray]:
+ """
+ For internal compatibility with the Index API.
+
+ Sort the Index. This is for compat with MultiIndex
+
+ Parameters
+ ----------
+ ascending : bool, default True
+ False to sort in descending order
+ na_position : {'first' or 'last'}, default 'first'
+ Argument 'first' puts NaNs at the beginning, 'last' puts NaNs at
+ the end.
+
+ .. versionadded:: 2.1.0
+
+ level, sort_remaining are compat parameters
+
+ Returns
+ -------
+ Index
+ """
+ if not isinstance(ascending, (list, bool)):
+ raise TypeError(
+ "ascending must be a single bool value or"
+ "a list of bool values of length 1"
+ )
+
+ if isinstance(ascending, list):
+ if len(ascending) != 1:
+ raise TypeError("ascending must be a list of bool values of length 1")
+ ascending = ascending[0]
+
+ if not isinstance(ascending, bool):
+ raise TypeError("ascending must be a bool value")
+
+ return self.sort_values(
+ return_indexer=True, ascending=ascending, na_position=na_position
+ )
+
+ def _get_level_values(self, level) -> Index:
+ """
+ Return an Index of values for requested level.
+
+ This is primarily useful to get an individual level of values from a
+ MultiIndex, but is provided on Index as well for compatibility.
+
+ Parameters
+ ----------
+ level : int or str
+ It is either the integer position or the name of the level.
+
+ Returns
+ -------
+ Index
+ Calling object, as there is only one level in the Index.
+
+ See Also
+ --------
+ MultiIndex.get_level_values : Get values for a level of a MultiIndex.
+
+ Notes
+ -----
+ For Index, level should be 0, since there are no multiple levels.
+
+ Examples
+ --------
+ >>> idx = pd.Index(list("abc"))
+ >>> idx
+ Index(['a', 'b', 'c'], dtype='str')
+
+ Get level values by supplying `level` as integer:
+
+ >>> idx.get_level_values(0)
+ Index(['a', 'b', 'c'], dtype='str')
+ """
+ self._validate_index_level(level)
+ return self
+
+ get_level_values = _get_level_values
+
+ @final
+ def droplevel(self, level: IndexLabel = 0):
+ """
+ Return index with requested level(s) removed.
+
+ If resulting index has only 1 level left, the result will be
+ of Index type, not MultiIndex. The original index is not modified inplace.
+
+ Parameters
+ ----------
+ level : int, str, or list-like, default 0
+ If a string is given, must be the name of a level
+ If list-like, elements must be names or indexes of levels.
+
+ Returns
+ -------
+ Index or MultiIndex
+ Returns an Index or MultiIndex object, depending on the resulting index
+ after removing the requested level(s).
+
+ See Also
+ --------
+ Index.dropna : Return Index without NA/NaN values.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays(
+ ... [[1, 2], [3, 4], [5, 6]], names=["x", "y", "z"]
+ ... )
+ >>> mi
+ MultiIndex([(1, 3, 5),
+ (2, 4, 6)],
+ names=['x', 'y', 'z'])
+
+ >>> mi.droplevel()
+ MultiIndex([(3, 5),
+ (4, 6)],
+ names=['y', 'z'])
+
+ >>> mi.droplevel(2)
+ MultiIndex([(1, 3),
+ (2, 4)],
+ names=['x', 'y'])
+
+ >>> mi.droplevel("z")
+ MultiIndex([(1, 3),
+ (2, 4)],
+ names=['x', 'y'])
+
+ >>> mi.droplevel(["x", "y"])
+ Index([5, 6], dtype='int64', name='z')
+ """
+ if not isinstance(level, (tuple, list)):
+ level = [level]
+
+ levnums = sorted((self._get_level_number(lev) for lev in level), reverse=True)
+
+ return self._drop_level_numbers(levnums)
+
+ @final
+ def _drop_level_numbers(self, levnums: list[int]):
+ """
+ Drop MultiIndex levels by level _number_, not name.
+ """
+
+ if not levnums and not isinstance(self, ABCMultiIndex):
+ return self
+ if len(levnums) >= self.nlevels:
+ raise ValueError(
+ f"Cannot remove {len(levnums)} levels from an index with "
+ f"{self.nlevels} levels: at least one level must be left."
+ )
+ # The two checks above guarantee that here self is a MultiIndex
+ self = cast("MultiIndex", self)
+
+ new_levels = list(self.levels)
+ new_codes = list(self.codes)
+ new_names = list(self.names)
+
+ for i in levnums:
+ new_levels.pop(i)
+ new_codes.pop(i)
+ new_names.pop(i)
+
+ if len(new_levels) == 1:
+ lev = new_levels[0]
+
+ if len(lev) == 0:
+ # If lev is empty, lev.take will fail GH#42055
+ if len(new_codes[0]) == 0:
+ # GH#45230 preserve RangeIndex here
+ # see test_reset_index_empty_rangeindex
+ result = lev[:0]
+ else:
+ res_values = algos.take(lev._values, new_codes[0], allow_fill=True)
+ # _constructor instead of type(lev) for RangeIndex compat GH#35230
+ result = lev._constructor._simple_new(res_values, name=new_names[0])
+ else:
+ # set nan if needed
+ mask = new_codes[0] == -1
+ result = new_levels[0].take(new_codes[0])
+ if mask.any():
+ result = result.putmask(mask, np.nan)
+
+ result._name = new_names[0]
+
+ return result
+ else:
+ from pandas.core.indexes.multi import MultiIndex
+
+ return MultiIndex(
+ levels=new_levels,
+ codes=new_codes,
+ names=new_names,
+ verify_integrity=False,
+ )
+
+ # --------------------------------------------------------------------
+ # Introspection Methods
+
+ @cache_readonly
+ @final
+ def _can_hold_na(self) -> bool:
+ if isinstance(self.dtype, ExtensionDtype):
+ return self.dtype._can_hold_na
+ if self.dtype.kind in "iub":
+ return False
+ return True
+
+ @property
+ def is_monotonic_increasing(self) -> bool:
+ """
+ Return a boolean if the values are equal or increasing.
+
+ Returns
+ -------
+ bool
+
+ See Also
+ --------
+ Index.is_monotonic_decreasing : Check if the values are equal or decreasing.
+
+ Examples
+ --------
+ >>> pd.Index([1, 2, 3]).is_monotonic_increasing
+ True
+ >>> pd.Index([1, 2, 2]).is_monotonic_increasing
+ True
+ >>> pd.Index([1, 3, 2]).is_monotonic_increasing
+ False
+ """
+ return self._engine.is_monotonic_increasing
+
+ @property
+ def is_monotonic_decreasing(self) -> bool:
+ """
+ Return a boolean if the values are equal or decreasing.
+
+ Returns
+ -------
+ bool
+
+ See Also
+ --------
+ Index.is_monotonic_increasing : Check if the values are equal or increasing.
+
+ Examples
+ --------
+ >>> pd.Index([3, 2, 1]).is_monotonic_decreasing
+ True
+ >>> pd.Index([3, 2, 2]).is_monotonic_decreasing
+ True
+ >>> pd.Index([3, 1, 2]).is_monotonic_decreasing
+ False
+ """
+ return self._engine.is_monotonic_decreasing
+
+ @final
+ @property
+ def _is_strictly_monotonic_increasing(self) -> bool:
+ """
+ Return if the index is strictly monotonic increasing
+ (only increasing) values.
+
+ Examples
+ --------
+ >>> Index([1, 2, 3])._is_strictly_monotonic_increasing
+ True
+ >>> Index([1, 2, 2])._is_strictly_monotonic_increasing
+ False
+ >>> Index([1, 3, 2])._is_strictly_monotonic_increasing
+ False
+ """
+ return self.is_unique and self.is_monotonic_increasing
+
+ @final
+ @property
+ def _is_strictly_monotonic_decreasing(self) -> bool:
+ """
+ Return if the index is strictly monotonic decreasing
+ (only decreasing) values.
+
+ Examples
+ --------
+ >>> Index([3, 2, 1])._is_strictly_monotonic_decreasing
+ True
+ >>> Index([3, 2, 2])._is_strictly_monotonic_decreasing
+ False
+ >>> Index([3, 1, 2])._is_strictly_monotonic_decreasing
+ False
+ """
+ return self.is_unique and self.is_monotonic_decreasing
+
+ @cache_readonly
+ def is_unique(self) -> bool:
+ """
+ Return if the index has unique values.
+
+ Returns
+ -------
+ bool
+
+ See Also
+ --------
+ Index.has_duplicates : Inverse method that checks if it has duplicate values.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 5, 7, 7])
+ >>> idx.is_unique
+ False
+
+ >>> idx = pd.Index([1, 5, 7])
+ >>> idx.is_unique
+ True
+
+ >>> idx = pd.Index(["Watermelon", "Orange", "Apple", "Watermelon"]).astype(
+ ... "category"
+ ... )
+ >>> idx.is_unique
+ False
+
+ >>> idx = pd.Index(["Orange", "Apple", "Watermelon"]).astype("category")
+ >>> idx.is_unique
+ True
+ """
+ return self._engine.is_unique
+
+ @final
+ @property
+ def has_duplicates(self) -> bool:
+ """
+ Check if the Index has duplicate values.
+
+ Returns
+ -------
+ bool
+ Whether or not the Index has duplicate values.
+
+ See Also
+ --------
+ Index.is_unique : Inverse method that checks if it has unique values.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 5, 7, 7])
+ >>> idx.has_duplicates
+ True
+
+ >>> idx = pd.Index([1, 5, 7])
+ >>> idx.has_duplicates
+ False
+
+ >>> idx = pd.Index(["Watermelon", "Orange", "Apple", "Watermelon"]).astype(
+ ... "category"
+ ... )
+ >>> idx.has_duplicates
+ True
+
+ >>> idx = pd.Index(["Orange", "Apple", "Watermelon"]).astype("category")
+ >>> idx.has_duplicates
+ False
+ """
+ return not self.is_unique
+
+ @cache_readonly
+ def inferred_type(self) -> str_t:
+ """
+ Return a string of the type inferred from the values.
+
+ See Also
+ --------
+ Index.dtype : Return the dtype object of the underlying data.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx
+ Index([1, 2, 3], dtype='int64')
+ >>> idx.inferred_type
+ 'integer'
+ """
+ return lib.infer_dtype(self._values, skipna=False)
+
+ @cache_readonly
+ @final
+ def _is_all_dates(self) -> bool:
+ """
+ Whether or not the index values only consist of dates.
+ """
+ if needs_i8_conversion(self.dtype):
+ return True
+ elif self.dtype != _dtype_obj:
+ # TODO(ExtensionIndex): 3rd party EA might override?
+ # Note: this includes IntervalIndex, even when the left/right
+ # contain datetime-like objects.
+ return False
+ elif self._is_multi:
+ return False
+ return is_datetime_array(ensure_object(self._values))
+
+ @final
+ @cache_readonly
+ def _is_multi(self) -> bool:
+ """
+ Cached check equivalent to isinstance(self, MultiIndex)
+ """
+ return isinstance(self, ABCMultiIndex)
+
+ # --------------------------------------------------------------------
+ # Pickle Methods
+
+ def __reduce__(self):
+ d = {"data": self._data, "name": self.name}
+ return _new_Index, (type(self), d), None
+
+ # --------------------------------------------------------------------
+ # Null Handling Methods
+
+ @cache_readonly
+ def _na_value(self):
+ """The expected NA value to use with this index."""
+ dtype = self.dtype
+ if isinstance(dtype, np.dtype):
+ if dtype.kind in "mM":
+ return NaT
+ return np.nan
+ return dtype.na_value
+
+ @cache_readonly
+ def _isnan(self) -> npt.NDArray[np.bool_]:
+ """
+ Return if each value is NaN.
+ """
+ if self._can_hold_na:
+ return isna(self)
+ else:
+ # shouldn't reach to this condition by checking hasnans beforehand
+ values = np.empty(len(self), dtype=np.bool_)
+ values.fill(False)
+ return values
+
+ @cache_readonly
+ def hasnans(self) -> bool:
+ """
+ Return True if there are any NaNs.
+
+ Enables various performance speedups.
+
+ Returns
+ -------
+ bool
+
+ See Also
+ --------
+ Index.isna : Detect missing values.
+ Index.dropna : Return Index without NA/NaN values.
+ Index.fillna : Fill NA/NaN values with the specified value.
+
+ Examples
+ --------
+ >>> s = pd.Series([1, 2, 3], index=["a", "b", None])
+ >>> s
+ a 1
+ b 2
+ None 3
+ dtype: int64
+ >>> s.index.hasnans
+ True
+ """
+ if self._can_hold_na:
+ return bool(self._isnan.any())
+ else:
+ return False
+
+ @final
+ def isna(self) -> npt.NDArray[np.bool_]:
+ """
+ Detect missing values.
+
+ Return a boolean same-sized object indicating if the values are NA.
+ NA values, such as ``None``, :attr:`numpy.NaN` or :attr:`pd.NaT`, get
+ mapped to ``True`` values.
+ Everything else get mapped to ``False`` values. Characters such as
+ empty strings `''` or :attr:`numpy.inf` are not considered NA values.
+
+ Returns
+ -------
+ numpy.ndarray[bool]
+ A boolean array of whether my values are NA.
+
+ See Also
+ --------
+ Index.notna : Boolean inverse of isna.
+ Index.dropna : Omit entries with missing values.
+ isna : Top-level isna.
+ Series.isna : Detect missing values in Series object.
+
+ Examples
+ --------
+ Show which entries in a pandas.Index are NA. The result is an
+ array.
+
+ >>> idx = pd.Index([5.2, 6.0, np.nan])
+ >>> idx
+ Index([5.2, 6.0, nan], dtype='float64')
+ >>> idx.isna()
+ array([False, False, True])
+
+ Empty strings are not considered NA values. None is considered an NA
+ value.
+
+ >>> idx = pd.Index(["black", "", "red", None])
+ >>> idx
+ Index(['black', '', 'red', nan], dtype='str')
+ >>> idx.isna()
+ array([False, False, False, True])
+
+ For datetimes, `NaT` (Not a Time) is considered as an NA value.
+
+ >>> idx = pd.DatetimeIndex(
+ ... [pd.Timestamp("1940-04-25"), pd.Timestamp(""), None, pd.NaT]
+ ... )
+ >>> idx
+ DatetimeIndex(['1940-04-25', 'NaT', 'NaT', 'NaT'],
+ dtype='datetime64[us]', freq=None)
+ >>> idx.isna()
+ array([False, True, True, True])
+ """
+ return self._isnan
+
+ isnull = isna
+
+ @final
+ def notna(self) -> npt.NDArray[np.bool_]:
+ """
+ Detect existing (non-missing) values.
+
+ Return a boolean same-sized object indicating if the values are not NA.
+ Non-missing values get mapped to ``True``. Characters such as empty
+ strings ``''`` or :attr:`numpy.inf` are not considered NA values.
+ NA values, such as None or :attr:`numpy.NaN`, get mapped to ``False``
+ values.
+
+ Returns
+ -------
+ numpy.ndarray[bool]
+ Boolean array to indicate which entries are not NA.
+
+ See Also
+ --------
+ Index.notnull : Alias of notna.
+ Index.isna: Inverse of notna.
+ notna : Top-level notna.
+
+ Examples
+ --------
+ Show which entries in an Index are not NA. The result is an
+ array.
+
+ >>> idx = pd.Index([5.2, 6.0, np.nan])
+ >>> idx
+ Index([5.2, 6.0, nan], dtype='float64')
+ >>> idx.notna()
+ array([ True, True, False])
+
+ Empty strings are not considered NA values. None is considered a NA
+ value.
+
+ >>> idx = pd.Index(["black", "", "red", None])
+ >>> idx
+ Index(['black', '', 'red', nan], dtype='str')
+ >>> idx.notna()
+ array([ True, True, True, False])
+ """
+ return ~self.isna()
+
+ notnull = notna
+
+ def fillna(self, value):
+ """
+ Fill NA/NaN values with the specified value.
+
+ Parameters
+ ----------
+ value : scalar
+ Scalar value to use to fill holes (e.g. 0).
+ This value cannot be a list-likes.
+
+ Returns
+ -------
+ Index
+ NA/NaN values replaced with `value`.
+
+ See Also
+ --------
+ DataFrame.fillna : Fill NaN values of a DataFrame.
+ Series.fillna : Fill NaN Values of a Series.
+
+ Examples
+ --------
+ >>> idx = pd.Index([np.nan, np.nan, 3])
+ >>> idx.fillna(0)
+ Index([0.0, 0.0, 3.0], dtype='float64')
+ """
+ if not is_scalar(value):
+ raise TypeError(f"'value' must be a scalar, passed: {type(value).__name__}")
+
+ if self.hasnans:
+ result = self.putmask(self._isnan, value)
+ # no need to care metadata other than name
+ # because it can't have freq if it has NaTs
+ # _with_infer needed for test_fillna_categorical
+ return Index._with_infer(result, name=self.name, copy=False)
+ return self._view()
+
+ def dropna(self, how: AnyAll = "any") -> Self:
+ """
+ Return Index without NA/NaN values.
+
+ Parameters
+ ----------
+ how : {'any', 'all'}, default 'any'
+ If the Index is a MultiIndex, drop the value when any or all levels
+ are NaN.
+
+ Returns
+ -------
+ Index
+ Returns an Index object after removing NA/NaN values.
+
+ See Also
+ --------
+ Index.fillna : Fill NA/NaN values with the specified value.
+ Index.isna : Detect missing values.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, np.nan, 3])
+ >>> idx.dropna()
+ Index([1.0, 3.0], dtype='float64')
+ """
+ if how not in ("any", "all"):
+ raise ValueError(f"invalid how option: {how}")
+
+ if self.hasnans:
+ res_values = self._values[~self._isnan]
+ return type(self)._simple_new(res_values, name=self.name)
+ return self._view()
+
+ # --------------------------------------------------------------------
+ # Uniqueness Methods
+
+ def unique(self, level: Hashable | None = None) -> Self:
+ """
+ Return unique values in the index.
+
+ Unique values are returned in order of appearance, this does NOT sort.
+
+ Parameters
+ ----------
+ level : int or hashable, optional
+ Only return values from specified level (for MultiIndex).
+ If int, gets the level by integer position, else by level name.
+
+ Returns
+ -------
+ Index
+ Unique values in the index.
+
+ See Also
+ --------
+ unique : Numpy array of unique values in that column.
+ Series.unique : Return unique values of Series object.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 1, 2, 3, 3])
+ >>> idx.unique()
+ Index([1, 2, 3], dtype='int64')
+ """
+ if level is not None:
+ self._validate_index_level(level)
+
+ if self.is_unique:
+ return self._view()
+
+ result = super().unique()
+ return self._shallow_copy(result)
+
+ def drop_duplicates(self, *, keep: DropKeep = "first") -> Self:
+ """
+ Return Index with duplicate values removed.
+
+ Parameters
+ ----------
+ keep : {'first', 'last', ``False``}, default 'first'
+ - 'first' : Drop duplicates except for the first occurrence.
+ - 'last' : Drop duplicates except for the last occurrence.
+ - ``False`` : Drop all duplicates.
+
+ Returns
+ -------
+ Index
+ A new Index object with the duplicate values removed.
+
+ See Also
+ --------
+ Series.drop_duplicates : Equivalent method on Series.
+ DataFrame.drop_duplicates : Equivalent method on DataFrame.
+ Index.duplicated : Related method on Index, indicating duplicate
+ Index values.
+
+ Examples
+ --------
+ Generate a pandas.Index with duplicate values.
+
+ >>> idx = pd.Index(["llama", "cow", "llama", "beetle", "llama", "hippo"])
+
+ The `keep` parameter controls which duplicate values are removed.
+ The value 'first' keeps the first occurrence for each
+ set of duplicated entries. The default value of keep is 'first'.
+
+ >>> idx.drop_duplicates(keep="first")
+ Index(['llama', 'cow', 'beetle', 'hippo'], dtype='str')
+
+ The value 'last' keeps the last occurrence for each set of duplicated
+ entries.
+
+ >>> idx.drop_duplicates(keep="last")
+ Index(['cow', 'beetle', 'llama', 'hippo'], dtype='str')
+
+ The value ``False`` discards all sets of duplicated entries.
+
+ >>> idx.drop_duplicates(keep=False)
+ Index(['cow', 'beetle', 'hippo'], dtype='str')
+ """
+ if self.is_unique:
+ return self._view()
+
+ return super().drop_duplicates(keep=keep)
+
+ def duplicated(self, keep: DropKeep = "first") -> npt.NDArray[np.bool_]:
+ """
+ Indicate duplicate index values.
+
+ Duplicated values are indicated as ``True`` values in the resulting
+ array. Either all duplicates, all except the first, or all except the
+ last occurrence of duplicates can be indicated.
+
+ Parameters
+ ----------
+ keep : {'first', 'last', False}, default 'first'
+ The value or values in a set of duplicates to mark as missing.
+
+ - 'first' : Mark duplicates as ``True`` except for the first
+ occurrence.
+ - 'last' : Mark duplicates as ``True`` except for the last
+ occurrence.
+ - ``False`` : Mark all duplicates as ``True``.
+
+ Returns
+ -------
+ np.ndarray[bool]
+ A numpy array of boolean values indicating duplicate index values.
+
+ See Also
+ --------
+ Series.duplicated : Equivalent method on pandas.Series.
+ DataFrame.duplicated : Equivalent method on pandas.DataFrame.
+ Index.drop_duplicates : Remove duplicate values from Index.
+
+ Examples
+ --------
+ By default, for each set of duplicated values, the first occurrence is
+ set to False and all others to True:
+
+ >>> idx = pd.Index(["llama", "cow", "llama", "beetle", "llama"])
+ >>> idx.duplicated()
+ array([False, False, True, False, True])
+
+ which is equivalent to
+
+ >>> idx.duplicated(keep="first")
+ array([False, False, True, False, True])
+
+ By using 'last', the last occurrence of each set of duplicated values
+ is set on False and all others on True:
+
+ >>> idx.duplicated(keep="last")
+ array([ True, False, True, False, False])
+
+ By setting keep on ``False``, all duplicates are True:
+
+ >>> idx.duplicated(keep=False)
+ array([ True, False, True, False, True])
+ """
+ if self.is_unique:
+ # fastpath available bc we are immutable
+ return np.zeros(len(self), dtype=bool)
+ return self._duplicated(keep=keep)
+
+ # --------------------------------------------------------------------
+ # Arithmetic & Logical Methods
+
+ def __iadd__(self, other):
+ # alias for __add__
+ return self + other
+
+ @final
+ def __bool__(self) -> NoReturn:
+ raise ValueError(
+ f"The truth value of a {type(self).__name__} is ambiguous. "
+ "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
+ )
+
+ # --------------------------------------------------------------------
+ # Set Operation Methods
+
+ def _get_reconciled_name_object(self, other):
+ """
+ If the result of a set operation will be self,
+ return a shallow copy of self.
+ """
+ name = get_op_result_name(self, other)
+ if self.name is not name:
+ return self.rename(name)
+ return self.copy(deep=False)
+
+ @final
+ def _validate_sort_keyword(self, sort) -> None:
+ if sort not in [None, False, True]:
+ raise ValueError(
+ "The 'sort' keyword only takes the values of "
+ f"None, True, or False; {sort} was passed."
+ )
+
+ @final
+ def _dti_setop_align_tzs(self, other: Index, setop: str_t) -> tuple[Index, Index]:
+ """
+ With mismatched timezones, cast both to UTC.
+ """
+ # Caller is responsible for checking
+ # `self.dtype != other.dtype`
+ if (
+ isinstance(self, ABCDatetimeIndex)
+ and isinstance(other, ABCDatetimeIndex)
+ and self.tz is not None
+ and other.tz is not None
+ ):
+ # GH#39328, GH#45357, GH#60080
+ # If both timezones are the same, no need to convert to UTC
+ if self.tz == other.tz:
+ return self, other
+ else:
+ left = self.tz_convert("UTC")
+ right = other.tz_convert("UTC")
+ return left, right
+ return self, other
+
+ @final
+ def union(self, other, sort: bool | None = None):
+ """
+ Form the union of two Index objects.
+
+ If the Index objects are incompatible, both Index objects will be
+ cast to dtype('object') first.
+
+ Parameters
+ ----------
+ other : Index or array-like
+ Index or an array-like object containing elements to form the union
+ with the original Index.
+ sort : bool or None, default None
+ Whether to sort the resulting Index.
+
+ * None : Sort the result, except when
+
+ 1. `self` and `other` are equal.
+ 2. `self` or `other` has length 0.
+ 3. Some values in `self` or `other` cannot be compared.
+ A RuntimeWarning is issued in this case.
+
+ * False : do not sort the result.
+ * True : Sort the result (which may raise TypeError).
+
+ Returns
+ -------
+ Index
+ Returns a new Index object with all unique elements from both the original
+ Index and the `other` Index.
+
+ See Also
+ --------
+ Index.unique : Return unique values in the index.
+ Index.intersection : Form the intersection of two Index objects.
+ Index.difference : Return a new Index with elements of index not in `other`.
+
+ Examples
+ --------
+ Union matching dtypes
+
+ >>> idx1 = pd.Index([1, 2, 3, 4])
+ >>> idx2 = pd.Index([3, 4, 5, 6])
+ >>> idx1.union(idx2)
+ Index([1, 2, 3, 4, 5, 6], dtype='int64')
+
+ Union mismatched dtypes
+
+ >>> idx1 = pd.Index(["a", "b", "c", "d"])
+ >>> idx2 = pd.Index([1, 2, 3, 4])
+ >>> idx1.union(idx2)
+ Index(['a', 'b', 'c', 'd', 1, 2, 3, 4], dtype='object')
+
+ MultiIndex case
+
+ >>> idx1 = pd.MultiIndex.from_arrays(
+ ... [[1, 1, 2, 2], ["Red", "Blue", "Red", "Blue"]]
+ ... )
+ >>> idx1
+ MultiIndex([(1, 'Red'),
+ (1, 'Blue'),
+ (2, 'Red'),
+ (2, 'Blue')],
+ )
+ >>> idx2 = pd.MultiIndex.from_arrays(
+ ... [[3, 3, 2, 2], ["Red", "Green", "Red", "Green"]]
+ ... )
+ >>> idx2
+ MultiIndex([(3, 'Red'),
+ (3, 'Green'),
+ (2, 'Red'),
+ (2, 'Green')],
+ )
+ >>> idx1.union(idx2)
+ MultiIndex([(1, 'Blue'),
+ (1, 'Red'),
+ (2, 'Blue'),
+ (2, 'Green'),
+ (2, 'Red'),
+ (3, 'Green'),
+ (3, 'Red')],
+ )
+ >>> idx1.union(idx2, sort=False)
+ MultiIndex([(1, 'Red'),
+ (1, 'Blue'),
+ (2, 'Red'),
+ (2, 'Blue'),
+ (3, 'Red'),
+ (3, 'Green'),
+ (2, 'Green')],
+ )
+ """
+ self._validate_sort_keyword(sort)
+ self._assert_can_do_setop(other)
+ other, result_name = self._convert_can_do_setop(other)
+
+ if self.dtype != other.dtype:
+ if (
+ isinstance(self, ABCMultiIndex)
+ and not is_object_dtype(_unpack_nested_dtype(other))
+ and len(other) > 0
+ ):
+ raise NotImplementedError(
+ "Can only union MultiIndex with MultiIndex or Index of tuples, "
+ "try mi.to_flat_index().union(other) instead."
+ )
+ self, other = self._dti_setop_align_tzs(other, "union")
+
+ dtype = self._find_common_type_compat(other)
+ left = self.astype(dtype, copy=False)
+ right = other.astype(dtype, copy=False)
+ return left.union(right, sort=sort)
+
+ elif not len(other) or self.equals(other):
+ # NB: whether this (and the `if not len(self)` check below) come before
+ # or after the dtype equality check above affects the returned dtype
+ result = self._get_reconciled_name_object(other)
+ if sort is True:
+ return result.sort_values()
+ return result
+
+ elif not len(self):
+ result = other._get_reconciled_name_object(self)
+ if sort is True:
+ return result.sort_values()
+ return result
+
+ result = self._union(other, sort=sort)
+
+ return self._wrap_setop_result(other, result)
+
+ def _union(self, other: Index, sort: bool | None):
+ """
+ Specific union logic should go here. In subclasses, union behavior
+ should be overwritten here rather than in `self.union`.
+
+ Parameters
+ ----------
+ other : Index or array-like
+ sort : False or None, default False
+ Whether to sort the resulting index.
+
+ * True : sort the result
+ * False : do not sort the result.
+ * None : sort the result, except when `self` and `other` are equal
+ or when the values cannot be compared.
+
+ Returns
+ -------
+ Index
+ """
+ lvals = self._values
+ rvals = other._values
+
+ if (
+ sort in (None, True)
+ and (self.is_unique or other.is_unique)
+ and self._can_use_libjoin
+ and other._can_use_libjoin
+ ):
+ # Both are monotonic and at least one is unique, so can use outer join
+ # (actually don't need either unique, but without this restriction
+ # test_union_same_value_duplicated_in_both fails)
+ try:
+ return self._outer_indexer(other)[0]
+ except TypeError:
+ # incomparable objects; should only be for object dtype
+ value_list = list(lvals)
+
+ # worth making this faster? a very unusual case
+ value_set = set(lvals)
+ value_list.extend(x for x in rvals if x not in value_set)
+ # If objects are unorderable, we must have object dtype.
+ return np.array(value_list, dtype=object)
+
+ elif not other.is_unique:
+ # other has duplicates
+ result_dups = algos.union_with_duplicates(self, other)
+ return _maybe_try_sort(result_dups, sort)
+
+ # The rest of this method is analogous to Index._intersection_via_get_indexer
+
+ # Self may have duplicates; other already checked as unique
+ # find indexes of things in "other" that are not in "self"
+ if self._index_as_unique:
+ indexer = self.get_indexer(other)
+ missing = (indexer == -1).nonzero()[0]
+ else:
+ missing = algos.unique1d(self.get_indexer_non_unique(other)[1])
+
+ result: Index | MultiIndex | ArrayLike
+ if self._is_multi:
+ # Preserve MultiIndex to avoid losing dtypes
+ result = self.append(other.take(missing))
+
+ elif len(missing) > 0:
+ other_diff = rvals.take(missing)
+ result = concat_compat((lvals, other_diff))
+ else:
+ result = lvals
+
+ if not self.is_monotonic_increasing or not other.is_monotonic_increasing:
+ # if both are monotonic then result should already be sorted
+ result = _maybe_try_sort(result, sort)
+
+ return result
+
+ @final
+ def _wrap_setop_result(self, other: Index, result) -> Index:
+ name = get_op_result_name(self, other)
+ if isinstance(result, Index):
+ if result.name != name:
+ result = result.rename(name)
+ else:
+ result = self._shallow_copy(result, name=name)
+ return result
+
+ @final
+ def intersection(self, other, sort: bool = False):
+ # default sort keyword is different here from other setops intentionally
+ # done in GH#25063
+ """
+ Form the intersection of two Index objects.
+
+ This returns a new Index with elements common to the index and `other`.
+
+ Parameters
+ ----------
+ other : Index or array-like
+ An Index or an array-like object containing elements to form the
+ intersection with the original Index.
+ sort : True, False or None, default False
+ Whether to sort the resulting index.
+
+ * None : sort the result, except when `self` and `other` are equal
+ or when the values cannot be compared.
+ * False : do not sort the result.
+ * True : Sort the result (which may raise TypeError).
+
+ Returns
+ -------
+ Index
+ Returns a new Index object with elements common to both the original Index
+ and the `other` Index.
+
+ See Also
+ --------
+ Index.union : Form the union of two Index objects.
+ Index.difference : Return a new Index with elements of index not in other.
+ Index.isin : Return a boolean array where the index values are in values.
+
+ Examples
+ --------
+ >>> idx1 = pd.Index([1, 2, 3, 4])
+ >>> idx2 = pd.Index([3, 4, 5, 6])
+ >>> idx1.intersection(idx2)
+ Index([3, 4], dtype='int64')
+ """
+ self._validate_sort_keyword(sort)
+ self._assert_can_do_setop(other)
+ other, result_name = self._convert_can_do_setop(other)
+
+ if self.dtype != other.dtype:
+ self, other = self._dti_setop_align_tzs(other, "intersection")
+
+ if self.equals(other):
+ if not self.is_unique:
+ result = self.unique()._get_reconciled_name_object(other)
+ else:
+ result = self._get_reconciled_name_object(other)
+ if sort is True:
+ result = result.sort_values()
+ return result
+
+ if len(self) == 0 or len(other) == 0:
+ # fastpath; we need to be careful about having commutativity
+
+ if self._is_multi or other._is_multi:
+ # _convert_can_do_setop ensures that we have both or neither
+ # We retain self.levels
+ return self[:0].rename(result_name)
+
+ dtype = self._find_common_type_compat(other)
+ if self.dtype == dtype:
+ # Slicing allows us to retain DTI/TDI.freq, RangeIndex
+
+ # Note: self[:0] vs other[:0] affects
+ # 1) which index's `freq` we get in DTI/TDI cases
+ # This may be a historical artifact, i.e. no documented
+ # reason for this choice.
+ # 2) The `step` we get in RangeIndex cases
+ if len(self) == 0:
+ return self[:0].rename(result_name)
+ else:
+ return other[:0].rename(result_name)
+
+ return Index([], dtype=dtype, name=result_name)
+
+ elif not self._should_compare(other):
+ # We can infer that the intersection is empty.
+ if isinstance(self, ABCMultiIndex):
+ return self[:0].rename(result_name)
+ return Index([], name=result_name)
+
+ elif self.dtype != other.dtype:
+ dtype = self._find_common_type_compat(other)
+ this = self.astype(dtype, copy=False)
+ other = other.astype(dtype, copy=False)
+ return this.intersection(other, sort=sort)
+
+ result = self._intersection(other, sort=sort)
+ return self._wrap_intersection_result(other, result)
+
+ def _intersection(self, other: Index, sort: bool = False):
+ """
+ intersection specialized to the case with matching dtypes.
+ """
+ if self._can_use_libjoin and other._can_use_libjoin:
+ try:
+ res_indexer, indexer, _ = self._inner_indexer(other)
+ except TypeError:
+ # non-comparable; should only be for object dtype
+ pass
+ else:
+ # TODO: algos.unique1d should preserve DTA/TDA
+ if is_numeric_dtype(self.dtype):
+ # This is faster, because Index.unique() checks for uniqueness
+ # before calculating the unique values.
+ res = algos.unique1d(res_indexer)
+ else:
+ result = self.take(indexer)
+ res = result.drop_duplicates() # type: ignore[assignment]
+ return ensure_wrapped_if_datetimelike(res)
+
+ res_values = self._intersection_via_get_indexer(other, sort=sort)
+ res_values = _maybe_try_sort(res_values, sort)
+ return res_values
+
+ def _wrap_intersection_result(self, other, result):
+ # We will override for MultiIndex to handle empty results
+ return self._wrap_setop_result(other, result)
+
+ @final
+ def _intersection_via_get_indexer(
+ self, other: Index | MultiIndex, sort
+ ) -> ArrayLike | MultiIndex:
+ """
+ Find the intersection of two Indexes using get_indexer.
+
+ Returns
+ -------
+ np.ndarray or ExtensionArray or MultiIndex
+ The returned array will be unique.
+ """
+ left_unique = self.unique()
+ right_unique = other.unique()
+
+ # even though we are unique, we need get_indexer_for for IntervalIndex
+ indexer = left_unique.get_indexer_for(right_unique)
+
+ mask = indexer != -1
+
+ taker = indexer.take(mask.nonzero()[0])
+ if sort is False:
+ # sort bc we want the elements in the same order they are in self
+ # unnecessary in the case with sort=None bc we will sort later
+ taker = np.sort(taker)
+
+ result: MultiIndex | ExtensionArray | np.ndarray
+ if isinstance(left_unique, ABCMultiIndex):
+ result = left_unique.take(taker)
+ else:
+ result = left_unique.take(taker)._values
+ return result
+
+ @final
+ def difference(self, other, sort: bool | None = None):
+ """
+ Return a new Index with elements of index not in `other`.
+
+ This is the set difference of two Index objects.
+
+ Parameters
+ ----------
+ other : Index or array-like
+ Index object or an array-like object containing elements to be compared
+ with the elements of the original Index.
+ sort : bool or None, default None
+ Whether to sort the resulting index. By default, the
+ values are attempted to be sorted, but any TypeError from
+ incomparable elements is caught by pandas.
+
+ * None : Attempt to sort the result, but catch any TypeErrors
+ from comparing incomparable elements.
+ * False : Do not sort the result.
+ * True : Sort the result (which may raise TypeError).
+
+ Returns
+ -------
+ Index
+ Returns a new Index object containing elements that are in the original
+ Index but not in the `other` Index.
+
+ See Also
+ --------
+ Index.symmetric_difference : Compute the symmetric difference of two Index
+ objects.
+ Index.intersection : Form the intersection of two Index objects.
+
+ Examples
+ --------
+ >>> idx1 = pd.Index([2, 1, 3, 4])
+ >>> idx2 = pd.Index([3, 4, 5, 6])
+ >>> idx1.difference(idx2)
+ Index([1, 2], dtype='int64')
+ >>> idx1.difference(idx2, sort=False)
+ Index([2, 1], dtype='int64')
+ """
+ self._validate_sort_keyword(sort)
+ self._assert_can_do_setop(other)
+ other, result_name = self._convert_can_do_setop(other)
+
+ # Note: we do NOT call _dti_setop_align_tzs here, as there
+ # is no requirement that .difference be commutative, so it does
+ # not cast to object.
+
+ if self.equals(other):
+ # Note: we do not (yet) sort even if sort=None GH#24959
+ return self[:0].rename(result_name)
+
+ if len(other) == 0:
+ # Note: we do not (yet) sort even if sort=None GH#24959
+ result = self.unique().rename(result_name)
+ if sort is True:
+ return result.sort_values()
+ return result
+
+ if not self._should_compare(other):
+ # Nothing matches -> difference is everything
+ result = self.unique().rename(result_name)
+ if sort is True:
+ return result.sort_values()
+ return result
+
+ result = self._difference(other, sort=sort)
+ return self._wrap_difference_result(other, result)
+
+ def _difference(self, other, sort):
+ # overridden by RangeIndex
+ this = self
+ if isinstance(self, ABCCategoricalIndex) and self.hasnans and other.hasnans:
+ this = this.dropna()
+ other = other.unique()
+ the_diff = this[other.get_indexer_for(this) == -1]
+ the_diff = the_diff if this.is_unique else the_diff.unique()
+ the_diff = _maybe_try_sort(the_diff, sort)
+ return the_diff
+
+ def _wrap_difference_result(self, other, result):
+ # We will override for MultiIndex to handle empty results
+ return self._wrap_setop_result(other, result)
+
+ def symmetric_difference(
+ self,
+ other,
+ result_name: abc.Hashable | None = None,
+ sort: bool | None = None,
+ ):
+ """
+ Compute the symmetric difference of two Index objects.
+
+ Parameters
+ ----------
+ other : Index or array-like
+ Index or an array-like object with elements to compute the symmetric
+ difference with the original Index.
+ result_name : str
+ A string representing the name of the resulting Index, if desired.
+ sort : bool or None, default None
+ Whether to sort the resulting index. By default, the
+ values are attempted to be sorted, but any TypeError from
+ incomparable elements is caught by pandas.
+
+ * None : Attempt to sort the result, but catch any TypeErrors
+ from comparing incomparable elements.
+ * False : Do not sort the result.
+ * True : Sort the result (which may raise TypeError).
+
+ Returns
+ -------
+ Index
+ Returns a new Index object containing elements that appear in either the
+ original Index or the `other` Index, but not both.
+
+ See Also
+ --------
+ Index.difference : Return a new Index with elements of index not in other.
+ Index.union : Form the union of two Index objects.
+ Index.intersection : Form the intersection of two Index objects.
+
+ Notes
+ -----
+ ``symmetric_difference`` contains elements that appear in either
+ ``idx1`` or ``idx2`` but not both. Equivalent to the Index created by
+ ``idx1.difference(idx2) | idx2.difference(idx1)`` with duplicates
+ dropped.
+
+ Examples
+ --------
+ >>> idx1 = pd.Index([1, 2, 3, 4])
+ >>> idx2 = pd.Index([2, 3, 4, 5])
+ >>> idx1.symmetric_difference(idx2)
+ Index([1, 5], dtype='int64')
+ """
+ self._validate_sort_keyword(sort)
+ self._assert_can_do_setop(other)
+ other, result_name_update = self._convert_can_do_setop(other)
+ if result_name is None:
+ result_name = result_name_update
+
+ if self.dtype != other.dtype:
+ self, other = self._dti_setop_align_tzs(other, "symmetric_difference")
+
+ if not self._should_compare(other):
+ return self.union(other, sort=sort).rename(result_name)
+
+ elif self.dtype != other.dtype:
+ dtype = self._find_common_type_compat(other)
+ this = self.astype(dtype, copy=False)
+ that = other.astype(dtype, copy=False)
+ return this.symmetric_difference(that, sort=sort).rename(result_name)
+
+ this = self.unique()
+ other = other.unique()
+ indexer = this.get_indexer_for(other)
+
+ # {this} minus {other}
+ common_indexer = indexer.take((indexer != -1).nonzero()[0])
+ left_indexer = np.setdiff1d(
+ np.arange(this.size), common_indexer, assume_unique=True
+ )
+ left_diff = this.take(left_indexer)
+
+ # {other} minus {this}
+ right_indexer = (indexer == -1).nonzero()[0]
+ right_diff = other.take(right_indexer)
+
+ res_values = left_diff.append(right_diff)
+ result = _maybe_try_sort(res_values, sort)
+
+ if not self._is_multi:
+ return Index(result, name=result_name, dtype=res_values.dtype)
+ else:
+ left_diff = cast("MultiIndex", left_diff)
+ if len(result) == 0:
+ # result might be an Index, if other was an Index
+ return left_diff.remove_unused_levels().set_names(result_name)
+ return result.set_names(result_name)
+
+ @final
+ def _assert_can_do_setop(self, other) -> bool:
+ if not is_list_like(other):
+ raise TypeError("Input must be Index or array-like")
+ return True
+
+ def _convert_can_do_setop(self, other) -> tuple[Index, Hashable]:
+ if not isinstance(other, Index):
+ other = Index(other, name=self.name)
+ result_name = self.name
+ else:
+ result_name = get_op_result_name(self, other)
+ return other, result_name
+
+ # --------------------------------------------------------------------
+ # Indexing Methods
+
+ def get_loc(self, key):
+ """
+ Get integer location, slice or boolean mask for requested label.
+
+ Parameters
+ ----------
+ key : label
+ The key to check its location if it is present in the index.
+
+ Returns
+ -------
+ int if unique index, slice if monotonic index, else mask
+ Integer location, slice or boolean mask.
+
+ See Also
+ --------
+ Index.get_slice_bound : Calculate slice bound that corresponds to
+ given label.
+ Index.get_indexer : Computes indexer and mask for new index given
+ the current index.
+ Index.get_non_unique : Returns indexer and masks for new index given
+ the current index.
+ Index.get_indexer_for : Returns an indexer even when non-unique.
+
+ Examples
+ --------
+ >>> unique_index = pd.Index(list("abc"))
+ >>> unique_index.get_loc("b")
+ 1
+
+ >>> monotonic_index = pd.Index(list("abbc"))
+ >>> monotonic_index.get_loc("b")
+ slice(1, 3, None)
+
+ >>> non_monotonic_index = pd.Index(list("abcb"))
+ >>> non_monotonic_index.get_loc("b")
+ array([False, True, False, True])
+ """
+ casted_key = self._maybe_cast_indexer(key)
+ try:
+ return self._engine.get_loc(casted_key)
+ except KeyError as err:
+ if isinstance(casted_key, slice) or (
+ isinstance(casted_key, abc.Iterable)
+ and any(isinstance(x, slice) for x in casted_key)
+ ):
+ raise InvalidIndexError(key) from err
+ raise KeyError(key) from err
+ except TypeError:
+ # If we have a listlike key, _check_indexing_error will raise
+ # InvalidIndexError. Otherwise we fall through and re-raise
+ # the TypeError.
+ self._check_indexing_error(key)
+ raise
+
+ @final
+ def get_indexer(
+ self,
+ target,
+ method: ReindexMethod | None = None,
+ limit: int | None = None,
+ tolerance=None,
+ ) -> npt.NDArray[np.intp]:
+ """
+ Compute indexer and mask for new index given the current index.
+
+ The indexer should be then used as an input to ndarray.take to align the
+ current data to the new index.
+
+ Parameters
+ ----------
+ target : Index
+ An iterable containing the values to be used for computing indexer.
+ method : {None, 'pad'/'ffill', 'backfill'/'bfill', 'nearest'}, optional
+ * default: exact matches only.
+ * pad / ffill: find the PREVIOUS index value if no exact match.
+ * backfill / bfill: use NEXT index value if no exact match
+ * nearest: use the NEAREST index value if no exact match. Tied
+ distances are broken by preferring the larger index value.
+ limit : int, optional
+ Maximum number of consecutive labels in ``target`` to match for
+ inexact matches.
+ tolerance : optional
+ Maximum distance between original and new labels for inexact
+ matches. The values of the index at the matching locations must
+ satisfy the equation ``abs(index[indexer] - target) <= tolerance``.
+
+ Tolerance may be a scalar value, which applies the same tolerance
+ to all values, or list-like, which applies variable tolerance per
+ element. List-like includes list, tuple, array, Series, and must be
+ the same size as the index and its dtype must exactly match the
+ index's type.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ Integers from 0 to n - 1 indicating that the index at these
+ positions matches the corresponding target values. Missing values
+ in the target are marked by -1.
+
+ See Also
+ --------
+ Index.get_indexer_for : Returns an indexer even when non-unique.
+ Index.get_non_unique : Returns indexer and masks for new index given
+ the current index.
+
+ Notes
+ -----
+ Returns -1 for unmatched values, for further explanation see the
+ example below.
+
+ Examples
+ --------
+ >>> index = pd.Index(["c", "a", "b"])
+ >>> index.get_indexer(["a", "b", "x"])
+ array([ 1, 2, -1])
+
+ Notice that the return value is an array of locations in ``index``
+ and ``x`` is marked by -1, as it is not in ``index``.
+ """
+ method = clean_reindex_fill_method(method)
+ orig_target = target
+ target = self._maybe_cast_listlike_indexer(target)
+
+ self._check_indexing_method(method, limit, tolerance)
+
+ if not self._index_as_unique:
+ raise InvalidIndexError(self._requires_unique_msg)
+
+ if len(target) == 0:
+ return np.array([], dtype=np.intp)
+
+ if not self._should_compare(target) and not self._should_partial_index(target):
+ # IntervalIndex get special treatment bc numeric scalars can be
+ # matched to Interval scalars
+ return self._get_indexer_non_comparable(target, method=method, unique=True)
+
+ if isinstance(self.dtype, CategoricalDtype):
+ # _maybe_cast_listlike_indexer ensures target has our dtype
+ # (could improve perf by doing _should_compare check earlier?)
+ assert self.dtype == target.dtype
+
+ indexer = self._engine.get_indexer(target.codes)
+ if self.hasnans and target.hasnans:
+ # After _maybe_cast_listlike_indexer, target elements which do not
+ # belong to some category are changed to NaNs
+ # Mask to track actual NaN values compared to inserted NaN values
+ # GH#45361
+ target_nans = isna(orig_target)
+ loc = self.get_loc(np.nan)
+ mask = target.isna()
+ indexer[target_nans] = loc
+ indexer[mask & ~target_nans] = -1
+ return indexer
+
+ if isinstance(target.dtype, CategoricalDtype):
+ # potential fastpath
+ # get an indexer for unique categories then propagate to codes via take_nd
+ # get_indexer instead of _get_indexer needed for MultiIndex cases
+ # e.g. test_append_different_columns_types
+ categories_indexer = self.get_indexer(target.categories)
+
+ indexer = algos.take_nd(categories_indexer, target.codes, fill_value=-1)
+
+ if (not self._is_multi and self.hasnans) and target.hasnans:
+ # Exclude MultiIndex because hasnans raises NotImplementedError
+ # we should only get here if we are unique, so loc is an integer
+ # GH#41934
+ loc = self.get_loc(np.nan)
+ mask = target.isna()
+ indexer[mask] = loc
+
+ return ensure_platform_int(indexer)
+
+ pself, ptarget = self._maybe_downcast_for_indexing(target)
+ if pself is not self or ptarget is not target:
+ return pself.get_indexer(
+ ptarget, method=method, limit=limit, tolerance=tolerance
+ )
+
+ if self.dtype == target.dtype and self.equals(target):
+ # Only call equals if we have same dtype to avoid inference/casting
+ return np.arange(len(target), dtype=np.intp)
+
+ if self.dtype != target.dtype and not self._should_partial_index(target):
+ # _should_partial_index e.g. IntervalIndex with numeric scalars
+ # that can be matched to Interval scalars.
+ dtype = self._find_common_type_compat(target)
+
+ this = self.astype(dtype, copy=False)
+ target = target.astype(dtype, copy=False)
+ return this._get_indexer(
+ target, method=method, limit=limit, tolerance=tolerance
+ )
+
+ return self._get_indexer(target, method, limit, tolerance)
+
+ def _get_indexer(
+ self,
+ target: Index,
+ method: str_t | None = None,
+ limit: int | None = None,
+ tolerance=None,
+ ) -> npt.NDArray[np.intp]:
+ if tolerance is not None:
+ tolerance = self._convert_tolerance(tolerance, target)
+
+ if method in ["pad", "backfill"]:
+ indexer = self._get_fill_indexer(target, method, limit, tolerance)
+ elif method == "nearest":
+ indexer = self._get_nearest_indexer(target, limit, tolerance)
+ else:
+ if target._is_multi and self._is_multi:
+ engine = self._engine
+ # error: Item "IndexEngine" of "Union[IndexEngine, ExtensionEngine]"
+ # has no attribute "_extract_level_codes"
+ tgt_values = engine._extract_level_codes( # type: ignore[union-attr]
+ target
+ )
+ else:
+ tgt_values = target._get_engine_target()
+
+ indexer = self._engine.get_indexer(tgt_values)
+
+ return ensure_platform_int(indexer)
+
+ @final
+ def _should_partial_index(self, target: Index) -> bool:
+ """
+ Should we attempt partial-matching indexing?
+ """
+ if isinstance(self.dtype, IntervalDtype):
+ if isinstance(target.dtype, IntervalDtype):
+ return False
+ # "Index" has no attribute "left"
+ return self.left._should_compare(target) # type: ignore[attr-defined]
+ return False
+
+ @final
+ def _check_indexing_method(
+ self,
+ method: str_t | None,
+ limit: int | None = None,
+ tolerance=None,
+ ) -> None:
+ """
+ Raise if we have a get_indexer `method` that is not supported or valid.
+ """
+ if method not in [None, "bfill", "backfill", "pad", "ffill", "nearest"]:
+ # in practice the clean_reindex_fill_method call would raise
+ # before we get here
+ raise ValueError("Invalid fill method") # pragma: no cover
+
+ if self._is_multi:
+ if method == "nearest":
+ raise NotImplementedError(
+ "method='nearest' not implemented yet "
+ "for MultiIndex; see GitHub issue 9365"
+ )
+ if method in ("pad", "backfill"):
+ if tolerance is not None:
+ raise NotImplementedError(
+ "tolerance not implemented yet for MultiIndex"
+ )
+
+ if isinstance(self.dtype, (IntervalDtype, CategoricalDtype)):
+ # GH#37871 for now this is only for IntervalIndex and CategoricalIndex
+ if method is not None:
+ raise NotImplementedError(
+ f"method {method} not yet implemented for {type(self).__name__}"
+ )
+
+ if method is None:
+ if tolerance is not None:
+ raise ValueError(
+ "tolerance argument only valid if doing pad, "
+ "backfill or nearest reindexing"
+ )
+ if limit is not None:
+ raise ValueError(
+ "limit argument only valid if doing pad, "
+ "backfill or nearest reindexing"
+ )
+
+ def _convert_tolerance(self, tolerance, target: np.ndarray | Index) -> np.ndarray:
+ # override this method on subclasses
+ tolerance = np.asarray(tolerance)
+ if target.size != tolerance.size and tolerance.size > 1:
+ raise ValueError("list-like tolerance size must match target index size")
+ elif is_numeric_dtype(self) and not np.issubdtype(tolerance.dtype, np.number):
+ if tolerance.ndim > 0:
+ raise ValueError(
+ f"tolerance argument for {type(self).__name__} with dtype "
+ f"{self.dtype} must contain numeric elements if it is list type"
+ )
+
+ raise ValueError(
+ f"tolerance argument for {type(self).__name__} with dtype {self.dtype} "
+ f"must be numeric if it is a scalar: {tolerance!r}"
+ )
+ return tolerance
+
+ @final
+ def _get_fill_indexer(
+ self, target: Index, method: str_t, limit: int | None = None, tolerance=None
+ ) -> npt.NDArray[np.intp]:
+ if self._is_multi:
+ if not (self.is_monotonic_increasing or self.is_monotonic_decreasing):
+ raise ValueError("index must be monotonic increasing or decreasing")
+ encoded = self.append(target)._engine.values # type: ignore[union-attr]
+ self_encoded = Index(encoded[: len(self)], copy=False)
+ target_encoded = Index(encoded[len(self) :], copy=False)
+ return self_encoded._get_fill_indexer(
+ target_encoded, method, limit, tolerance
+ )
+
+ if self.is_monotonic_increasing and target.is_monotonic_increasing:
+ target_values = target._get_engine_target()
+ own_values = self._get_engine_target()
+ if not isinstance(target_values, np.ndarray) or not isinstance(
+ own_values, np.ndarray
+ ):
+ raise NotImplementedError
+
+ if method == "pad":
+ indexer = libalgos.pad(own_values, target_values, limit=limit)
+ else:
+ # i.e. "backfill"
+ indexer = libalgos.backfill(own_values, target_values, limit=limit)
+ else:
+ indexer = self._get_fill_indexer_searchsorted(target, method, limit)
+ if tolerance is not None and len(self):
+ indexer = self._filter_indexer_tolerance(target, indexer, tolerance)
+ return indexer
+
+ @final
+ def _get_fill_indexer_searchsorted(
+ self, target: Index, method: str_t, limit: int | None = None
+ ) -> npt.NDArray[np.intp]:
+ """
+ Fallback pad/backfill get_indexer that works for monotonic decreasing
+ indexes and non-monotonic targets.
+ """
+ if limit is not None:
+ raise ValueError(
+ f"limit argument for {method!r} method only well-defined "
+ "if index and target are monotonic"
+ )
+
+ side: Literal["left", "right"] = "left" if method == "pad" else "right"
+
+ # find exact matches first (this simplifies the algorithm)
+ indexer = self.get_indexer(target)
+ nonexact = indexer == -1
+ indexer[nonexact] = self._searchsorted_monotonic(target[nonexact], side)
+ if side == "left":
+ # searchsorted returns "indices into a sorted array such that,
+ # if the corresponding elements in v were inserted before the
+ # indices, the order of a would be preserved".
+ # Thus, we need to subtract 1 to find values to the left.
+ indexer[nonexact] -= 1
+ # This also mapped not found values (values of 0 from
+ # np.searchsorted) to -1, which conveniently is also our
+ # sentinel for missing values
+ else:
+ # Mark indices to the right of the largest value as not found
+ indexer[indexer == len(self)] = -1
+ return indexer
+
+ @final
+ def _get_nearest_indexer(
+ self, target: Index, limit: int | None, tolerance
+ ) -> npt.NDArray[np.intp]:
+ """
+ Get the indexer for the nearest index labels; requires an index with
+ values that can be subtracted from each other (e.g., not strings or
+ tuples).
+ """
+ if not len(self):
+ return self._get_fill_indexer(target, "pad")
+
+ left_indexer = self.get_indexer(target, "pad", limit=limit)
+ right_indexer = self.get_indexer(target, "backfill", limit=limit)
+
+ left_distances = self._difference_compat(target, left_indexer)
+ right_distances = self._difference_compat(target, right_indexer)
+
+ op = operator.lt if self.is_monotonic_increasing else operator.le
+ indexer = np.where(
+ # error: Argument 1&2 has incompatible type "Union[ExtensionArray,
+ # ndarray[Any, Any]]"; expected "Union[SupportsDunderLE,
+ # SupportsDunderGE, SupportsDunderGT, SupportsDunderLT]"
+ op(left_distances, right_distances) # type: ignore[arg-type]
+ | (right_indexer == -1),
+ left_indexer,
+ right_indexer,
+ )
+ if tolerance is not None:
+ indexer = self._filter_indexer_tolerance(target, indexer, tolerance)
+ return indexer
+
+ @final
+ def _filter_indexer_tolerance(
+ self,
+ target: Index,
+ indexer: npt.NDArray[np.intp],
+ tolerance,
+ ) -> npt.NDArray[np.intp]:
+ distance = self._difference_compat(target, indexer)
+
+ return np.where(distance <= tolerance, indexer, -1)
+
+ @final
+ def _difference_compat(
+ self, target: Index, indexer: npt.NDArray[np.intp]
+ ) -> ArrayLike:
+ # Compatibility for PeriodArray, for which __sub__ returns an ndarray[object]
+ # of DateOffset objects, which do not support __abs__ (and would be slow
+ # if they did)
+
+ if isinstance(self.dtype, PeriodDtype):
+ # Note: we only get here with matching dtypes
+ own_values = cast("PeriodArray", self._data)._ndarray
+ target_values = cast("PeriodArray", target._data)._ndarray
+ diff = own_values[indexer] - target_values
+ else:
+ # error: Unsupported left operand type for - ("ExtensionArray")
+ diff = self._values[indexer] - target._values # type: ignore[operator]
+ return abs(diff)
+
+ # --------------------------------------------------------------------
+ # Indexer Conversion Methods
+
+ @final
+ def _validate_positional_slice(self, key: slice) -> None:
+ """
+ For positional indexing, a slice must have either int or None
+ for each of start, stop, and step.
+ """
+ self._validate_indexer("positional", key.start, "iloc")
+ self._validate_indexer("positional", key.stop, "iloc")
+ self._validate_indexer("positional", key.step, "iloc")
+
+ def _convert_slice_indexer(self, key: slice, kind: Literal["loc", "getitem"]):
+ """
+ Convert a slice indexer.
+
+ By definition, these are labels unless 'iloc' is passed in.
+ Floats are not allowed as the start, step, or stop of the slice.
+
+ Parameters
+ ----------
+ key : label of the slice bound
+ kind : {'loc', 'getitem'}
+ """
+
+ # potentially cast the bounds to integers
+ start, stop, step = key.start, key.stop, key.step
+
+ # figure out if this is a positional indexer
+ is_index_slice = is_valid_positional_slice(key)
+
+ # TODO(GH#50617): once Series.__[gs]etitem__ is removed we should be able
+ # to simplify this.
+ if kind == "getitem":
+ # called from the getitem slicers, validate that we are in fact integers
+ if is_index_slice:
+ # In this case the _validate_indexer checks below are redundant
+ return key
+ elif self.dtype.kind in "iu":
+ # Note: these checks are redundant if we know is_index_slice
+ self._validate_indexer("slice", key.start, "getitem")
+ self._validate_indexer("slice", key.stop, "getitem")
+ self._validate_indexer("slice", key.step, "getitem")
+ return key
+
+ # convert the slice to an indexer here; checking that the user didn't
+ # pass a positional slice to loc
+ is_positional = is_index_slice and self._should_fallback_to_positional
+
+ # if we are mixed and have integers
+ if is_positional:
+ try:
+ # Validate start & stop
+ if start is not None:
+ self.get_loc(start)
+ if stop is not None:
+ self.get_loc(stop)
+ is_positional = False
+ except KeyError:
+ pass
+
+ if com.is_null_slice(key):
+ # It doesn't matter if we are positional or label based
+ indexer = key
+ elif is_positional:
+ if kind == "loc":
+ # GH#16121, GH#24612, GH#31810
+ raise TypeError(
+ "Slicing a positional slice with .loc is not allowed, "
+ "Use .loc with labels or .iloc with positions instead.",
+ )
+ indexer = key
+ else:
+ indexer = self.slice_indexer(start, stop, step)
+
+ return indexer
+
+ @final
+ def _raise_invalid_indexer(
+ self,
+ form: Literal["slice", "positional"],
+ key,
+ reraise: lib.NoDefault | None | Exception = lib.no_default,
+ ) -> None:
+ """
+ Raise consistent invalid indexer message.
+ """
+ msg = (
+ f"cannot do {form} indexing on {type(self).__name__} with these "
+ f"indexers [{key}] of type {type(key).__name__}"
+ )
+ if reraise is not lib.no_default:
+ raise TypeError(msg) from reraise
+ raise TypeError(msg)
+
+ # --------------------------------------------------------------------
+ # Reindex Methods
+
+ @final
+ def _validate_can_reindex(self, indexer: np.ndarray) -> None:
+ """
+ Check if we are allowing reindexing with this particular indexer.
+
+ Parameters
+ ----------
+ indexer : an integer ndarray
+
+ Raises
+ ------
+ ValueError if its a duplicate axis
+ """
+ # trying to reindex on an axis with duplicates
+ if not self._index_as_unique and len(indexer):
+ raise ValueError("cannot reindex on an axis with duplicate labels")
+
+ def reindex(
+ self,
+ target,
+ method: ReindexMethod | None = None,
+ level=None,
+ limit: int | None = None,
+ tolerance: float | None = None,
+ ) -> tuple[Index, npt.NDArray[np.intp] | None]:
+ """
+ Create index with target's values.
+
+ Parameters
+ ----------
+ target : an iterable
+ An iterable containing the values to be used for creating the new index.
+ method : {None, 'pad'/'ffill', 'backfill'/'bfill', 'nearest'}, optional
+ * default: exact matches only.
+ * pad / ffill: find the PREVIOUS index value if no exact match.
+ * backfill / bfill: use NEXT index value if no exact match
+ * nearest: use the NEAREST index value if no exact match. Tied
+ distances are broken by preferring the larger index value.
+ level : int, optional
+ Level of multiindex.
+ limit : int, optional
+ Maximum number of consecutive labels in ``target`` to match for
+ inexact matches.
+ tolerance : int, float, or list-like, optional
+ Maximum distance between original and new labels for inexact
+ matches. The values of the index at the matching locations must
+ satisfy the equation ``abs(index[indexer] - target) <= tolerance``.
+
+ Tolerance may be a scalar value, which applies the same tolerance
+ to all values, or list-like, which applies variable tolerance per
+ element. List-like includes list, tuple, array, Series, and must be
+ the same size as the index and its dtype must exactly match the
+ index's type.
+
+ Returns
+ -------
+ new_index : pd.Index
+ Resulting index.
+ indexer : np.ndarray[np.intp] or None
+ Indices of output values in original index.
+
+ Raises
+ ------
+ TypeError
+ If ``method`` passed along with ``level``.
+ ValueError
+ If non-unique multi-index
+ ValueError
+ If non-unique index and ``method`` or ``limit`` passed.
+
+ See Also
+ --------
+ Series.reindex : Conform Series to new index with optional filling logic.
+ DataFrame.reindex : Conform DataFrame to new index with optional filling logic.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["car", "bike", "train", "tractor"])
+ >>> idx
+ Index(['car', 'bike', 'train', 'tractor'], dtype='str')
+ >>> idx.reindex(["car", "bike"])
+ (Index(['car', 'bike'], dtype='str'), array([0, 1]))
+ """
+ # GH6552: preserve names when reindexing to non-named target
+ # (i.e. neither Index nor Series).
+ preserve_names = not hasattr(target, "name")
+
+ # GH7774: preserve dtype/tz if target is empty and not an Index.
+ if is_iterator(target):
+ target = list(target)
+
+ if not isinstance(target, Index) and len(target) == 0:
+ if level is not None and self._is_multi:
+ # "Index" has no attribute "levels"; maybe "nlevels"?
+ idx = self.levels[level] # type: ignore[attr-defined]
+ else:
+ idx = self
+ target = idx[:0]
+ else:
+ target = ensure_index(target)
+
+ if level is not None and (
+ isinstance(self, ABCMultiIndex) or isinstance(target, ABCMultiIndex)
+ ):
+ if method is not None:
+ raise TypeError("Fill method not supported if level passed")
+
+ # TODO: tests where passing `keep_order=not self._is_multi`
+ # makes a difference for non-MultiIndex case
+ target, indexer, _ = self._join_level(
+ target, level, how="right", keep_order=not self._is_multi
+ )
+
+ elif self.equals(target):
+ indexer = None
+ elif self._index_as_unique:
+ indexer = self.get_indexer(
+ target, method=method, limit=limit, tolerance=tolerance
+ )
+ elif self._is_multi:
+ raise ValueError("cannot handle a non-unique multi-index!")
+ elif not self.is_unique:
+ # GH#42568
+ raise ValueError("cannot reindex on an axis with duplicate labels")
+ else:
+ indexer, _ = self.get_indexer_non_unique(target)
+
+ target = self._wrap_reindex_result(target, indexer, preserve_names)
+ return target, indexer
+
+ def _wrap_reindex_result(self, target, indexer, preserve_names: bool):
+ target = self._maybe_preserve_names(target, preserve_names)
+ return target
+
+ def _maybe_preserve_names(self, target: IndexT, preserve_names: bool) -> IndexT:
+ if preserve_names and target.nlevels == 1 and target.name != self.name:
+ target = target.copy(deep=False)
+ target.name = self.name
+ return target
+
+ @final
+ def _reindex_non_unique(
+ self, target: Index
+ ) -> tuple[Index, npt.NDArray[np.intp], npt.NDArray[np.intp] | None]:
+ """
+ Create a new index with target's values (move/add/delete values as
+ necessary) use with non-unique Index and a possibly non-unique target.
+
+ Parameters
+ ----------
+ target : an iterable
+
+ Returns
+ -------
+ new_index : pd.Index
+ Resulting index.
+ indexer : np.ndarray[np.intp]
+ Indices of output values in original index.
+ new_indexer : np.ndarray[np.intp] or None
+
+ """
+ target = ensure_index(target)
+ if len(target) == 0:
+ # GH#13691
+ return self[:0], np.array([], dtype=np.intp), None
+
+ indexer, missing = self.get_indexer_non_unique(target)
+ check = indexer != -1
+ new_labels: Index | np.ndarray = self.take(indexer[check])
+ new_indexer = None
+
+ if len(missing):
+ length = np.arange(len(indexer), dtype=np.intp)
+
+ missing = ensure_platform_int(missing)
+ missing_labels = target.take(missing)
+ missing_indexer = length[~check]
+ cur_labels = self.take(indexer[check]).values
+ cur_indexer = length[check]
+
+ # Index constructor below will do inference
+ new_labels = np.empty((len(indexer),), dtype=object)
+ new_labels[cur_indexer] = cur_labels
+ new_labels[missing_indexer] = missing_labels
+
+ # GH#38906
+ if not len(self):
+ new_indexer = np.arange(0, dtype=np.intp)
+
+ # a unique indexer
+ elif target.is_unique:
+ # see GH5553, make sure we use the right indexer
+ new_indexer = np.arange(len(indexer), dtype=np.intp)
+ new_indexer[cur_indexer] = np.arange(len(cur_labels))
+ new_indexer[missing_indexer] = -1
+
+ # we have a non_unique selector, need to use the original
+ # indexer here
+ else:
+ # need to retake to have the same size as the indexer
+ indexer[~check] = -1
+
+ # reset the new indexer to account for the new size
+ new_indexer = np.arange(len(self.take(indexer)), dtype=np.intp)
+ new_indexer[~check] = -1
+
+ if not isinstance(self, ABCMultiIndex):
+ new_index = Index(new_labels, name=self.name, copy=False)
+ else:
+ new_index = type(self).from_tuples(new_labels, names=self.names)
+ return new_index, indexer, new_indexer
+
+ # --------------------------------------------------------------------
+ # Join Methods
+
+ @overload
+ def join(
+ self,
+ other: Index,
+ *,
+ how: JoinHow = ...,
+ level: Level = ...,
+ return_indexers: Literal[True],
+ sort: bool = ...,
+ ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: ...
+
+ @overload
+ def join(
+ self,
+ other: Index,
+ *,
+ how: JoinHow = ...,
+ level: Level = ...,
+ return_indexers: Literal[False] = ...,
+ sort: bool = ...,
+ ) -> Index: ...
+
+ @overload
+ def join(
+ self,
+ other: Index,
+ *,
+ how: JoinHow = ...,
+ level: Level = ...,
+ return_indexers: bool = ...,
+ sort: bool = ...,
+ ) -> (
+ Index | tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]
+ ): ...
+
+ @final
+ @_maybe_return_indexers
+ def join(
+ self,
+ other: Index,
+ *,
+ how: JoinHow = "left",
+ level: Level | None = None,
+ return_indexers: bool = False,
+ sort: bool = False,
+ ) -> Index | tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ """
+ Compute join_index and indexers to conform data structures to the new index.
+
+ Parameters
+ ----------
+ other : Index
+ The other index on which join is performed.
+ how : {'left', 'right', 'inner', 'outer'}
+ level : int or level name, default None
+ It is either the integer position or the name of the level.
+ return_indexers : bool, default False
+ Whether to return the indexers or not for both the index objects.
+ sort : bool, default False
+ Sort the join keys lexicographically in the result Index. If False,
+ the order of the join keys depends on the join type (how keyword).
+
+ Returns
+ -------
+ join_index, (left_indexer, right_indexer)
+ The new index.
+
+ See Also
+ --------
+ DataFrame.join : Join columns with `other` DataFrame either on index
+ or on a key.
+ DataFrame.merge : Merge DataFrame or named Series objects with a
+ database-style join.
+
+ Examples
+ --------
+ >>> idx1 = pd.Index([1, 2, 3])
+ >>> idx2 = pd.Index([4, 5, 6])
+ >>> idx1.join(idx2, how="outer")
+ Index([1, 2, 3, 4, 5, 6], dtype='int64')
+ >>> idx1.join(other=idx2, how="outer", return_indexers=True)
+ (Index([1, 2, 3, 4, 5, 6], dtype='int64'),
+ array([ 0, 1, 2, -1, -1, -1]), array([-1, -1, -1, 0, 1, 2]))
+ """
+ if not isinstance(other, Index):
+ warnings.warn(
+ f"Passing {type(other).__name__} to {type(self).__name__}.join "
+ "is deprecated and will raise in a future version. "
+ "Pass an Index instead.",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+
+ other = ensure_index(other)
+ sort = sort or how == "outer"
+
+ if isinstance(self, ABCDatetimeIndex) and isinstance(other, ABCDatetimeIndex):
+ if (self.tz is None) ^ (other.tz is None):
+ # Raise instead of casting to object below.
+ raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex")
+
+ if not self._is_multi and not other._is_multi:
+ # We have specific handling for MultiIndex below
+ pself, pother = self._maybe_downcast_for_indexing(other)
+ if pself is not self or pother is not other:
+ return pself.join(
+ pother, how=how, level=level, return_indexers=True, sort=sort
+ )
+
+ # try to figure out the join level
+ # GH3662
+ if level is None and (self._is_multi or other._is_multi):
+ # have the same levels/names so a simple join
+ if self.names == other.names:
+ pass
+ else:
+ return self._join_multi(other, how=how)
+
+ # join on the level
+ if level is not None and (self._is_multi or other._is_multi):
+ return self._join_level(other, level, how=how)
+
+ if len(self) == 0 or len(other) == 0:
+ try:
+ return self._join_empty(other, how, sort)
+ except TypeError:
+ # object dtype; non-comparable objects
+ pass
+
+ if self.dtype != other.dtype:
+ dtype = self._find_common_type_compat(other)
+ this = self.astype(dtype, copy=False)
+ other = other.astype(dtype, copy=False)
+ return this.join(other, how=how, return_indexers=True)
+ elif (
+ isinstance(self, ABCCategoricalIndex)
+ and isinstance(other, ABCCategoricalIndex)
+ and not self.ordered
+ and not self.categories.equals(other.categories)
+ ):
+ # dtypes are "equal" but categories are in different order
+ other = Index(other._values.reorder_categories(self.categories), copy=False)
+
+ _validate_join_method(how)
+
+ if (
+ self.is_monotonic_increasing
+ and other.is_monotonic_increasing
+ and self._can_use_libjoin
+ and other._can_use_libjoin
+ and (self.is_unique or other.is_unique)
+ ):
+ try:
+ return self._join_monotonic(other, how=how)
+ except TypeError:
+ # object dtype; non-comparable objects
+ pass
+ elif not self.is_unique or not other.is_unique:
+ return self._join_non_unique(other, how=how, sort=sort)
+
+ return self._join_via_get_indexer(other, how, sort)
+
+ def _join_empty(
+ self, other: Index, how: JoinHow, sort: bool
+ ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ assert len(self) == 0 or len(other) == 0
+ _validate_join_method(how)
+
+ lidx: np.ndarray | None
+ ridx: np.ndarray | None
+
+ if len(other):
+ how = cast(JoinHow, {"left": "right", "right": "left"}.get(how, how))
+ join_index, ridx, lidx = other._join_empty(self, how, sort)
+ elif how in ["left", "outer"]:
+ if sort and not self.is_monotonic_increasing:
+ lidx = self.argsort()
+ join_index = self.take(lidx)
+ else:
+ lidx = None
+ join_index = self._view()
+ ridx = np.broadcast_to(np.intp(-1), len(join_index))
+ else:
+ join_index = other._view()
+ lidx = np.array([], dtype=np.intp)
+ ridx = None
+ return join_index, lidx, ridx
+
+ @final
+ def _join_via_get_indexer(
+ self, other: Index, how: JoinHow, sort: bool
+ ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ # Fallback if we do not have any fastpaths available based on
+ # uniqueness/monotonicity
+
+ # Note: at this point we have checked matching dtypes
+ lindexer: npt.NDArray[np.intp] | None
+ rindexer: npt.NDArray[np.intp] | None
+
+ if how == "left":
+ if sort:
+ join_index, lindexer = self.sort_values(return_indexer=True)
+ rindexer = other.get_indexer_for(join_index)
+ return join_index, lindexer, rindexer
+ else:
+ join_index = self
+ elif how == "right":
+ if sort:
+ join_index, rindexer = other.sort_values(return_indexer=True)
+ lindexer = self.get_indexer_for(join_index)
+ return join_index, lindexer, rindexer
+ else:
+ join_index = other
+ elif how == "inner":
+ join_index = self.intersection(other, sort=sort)
+ elif how == "outer":
+ try:
+ join_index = self.union(other, sort=sort)
+ except TypeError:
+ join_index = self.union(other)
+ try:
+ join_index = _maybe_try_sort(join_index, sort)
+ except TypeError:
+ pass
+
+ names = other.names if how == "right" else self.names
+ if join_index.names != names:
+ join_index = join_index.set_names(names)
+
+ if join_index is self:
+ lindexer = None
+ else:
+ lindexer = self.get_indexer_for(join_index)
+ if join_index is other:
+ rindexer = None
+ else:
+ rindexer = other.get_indexer_for(join_index)
+ return join_index, lindexer, rindexer
+
+ @final
+ def _join_multi(self, other: Index, how: JoinHow):
+ from pandas.core.indexes.multi import MultiIndex
+ from pandas.core.reshape.merge import restore_dropped_levels_multijoin
+
+ # figure out join names
+ self_names_list = list(self.names)
+ other_names_list = list(other.names)
+ self_names_order = self_names_list.index
+ other_names_order = other_names_list.index
+ self_names = set(self_names_list)
+ other_names = set(other_names_list)
+ overlap = self_names & other_names
+
+ # need at least 1 in common
+ if not overlap:
+ raise ValueError("cannot join with no overlapping index names")
+
+ if isinstance(self, MultiIndex) and isinstance(other, MultiIndex):
+ # Drop the non-matching levels from left and right respectively
+ ldrop_names = sorted(self_names - overlap, key=self_names_order)
+ rdrop_names = sorted(other_names - overlap, key=other_names_order)
+
+ # if only the order differs
+ if not len(ldrop_names + rdrop_names):
+ self_jnlevels = self
+ other_jnlevels = other.reorder_levels(self.names)
+ else:
+ self_jnlevels = self.droplevel(ldrop_names)
+ other_jnlevels = other.droplevel(rdrop_names)
+
+ # Join left and right
+ # Join on same leveled multi-index frames is supported
+ join_idx, lidx, ridx = self_jnlevels.join(
+ other_jnlevels, how=how, return_indexers=True
+ )
+
+ # Restore the dropped levels
+ # Returned index level order is
+ # common levels, ldrop_names, rdrop_names
+ dropped_names = ldrop_names + rdrop_names
+
+ # error: Argument 5/6 to "restore_dropped_levels_multijoin" has
+ # incompatible type "Optional[ndarray[Any, dtype[signedinteger[Any
+ # ]]]]"; expected "ndarray[Any, dtype[signedinteger[Any]]]"
+ levels, codes, names = restore_dropped_levels_multijoin(
+ self,
+ other,
+ dropped_names,
+ join_idx,
+ lidx, # type: ignore[arg-type]
+ ridx, # type: ignore[arg-type]
+ )
+
+ # Re-create the multi-index
+ multi_join_idx = MultiIndex(
+ levels=levels, codes=codes, names=names, verify_integrity=False
+ )
+
+ multi_join_idx = multi_join_idx.remove_unused_levels()
+
+ # maintain the order of the index levels
+ if how == "right":
+ level_order = other_names_list + ldrop_names
+ else:
+ level_order = self_names_list + rdrop_names
+ multi_join_idx = multi_join_idx.reorder_levels(level_order)
+
+ return multi_join_idx, lidx, ridx
+
+ jl = next(iter(overlap))
+
+ # Case where only one index is multi
+ # make the indices into mi's that match
+ flip_order = False
+ if isinstance(self, MultiIndex):
+ self, other = other, self
+ flip_order = True
+ # flip if join method is right or left
+ flip: dict[JoinHow, JoinHow] = {"right": "left", "left": "right"}
+ how = flip.get(how, how)
+
+ level = other.names.index(jl)
+ result = self._join_level(other, level, how=how)
+
+ if flip_order:
+ return result[0], result[2], result[1]
+ return result
+
+ @final
+ def _join_non_unique(
+ self, other: Index, how: JoinHow = "left", sort: bool = False
+ ) -> tuple[Index, npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ from pandas.core.reshape.merge import get_join_indexers_non_unique
+
+ # We only get here if dtypes match
+ assert self.dtype == other.dtype
+
+ left_idx, right_idx = get_join_indexers_non_unique(
+ self._values, other._values, how=how, sort=sort
+ )
+
+ if how == "right":
+ join_index = other.take(right_idx)
+ else:
+ join_index = self.take(left_idx)
+
+ if how == "outer":
+ mask = left_idx == -1
+ if mask.any():
+ right = other.take(right_idx)
+ join_index = join_index.putmask(mask, right)
+
+ if isinstance(join_index, ABCMultiIndex) and how == "outer":
+ # test_join_index_levels
+ join_index = join_index._sort_levels_monotonic()
+ return join_index, left_idx, right_idx
+
+ @final
+ def _join_level(
+ self, other: Index, level, how: JoinHow = "left", keep_order: bool = True
+ ) -> tuple[MultiIndex, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ """
+ The join method *only* affects the level of the resulting
+ MultiIndex. Otherwise it just exactly aligns the Index data to the
+ labels of the level in the MultiIndex.
+
+ If ```keep_order == True```, the order of the data indexed by the
+ MultiIndex will not be changed; otherwise, it will tie out
+ with `other`.
+ """
+ from pandas.core.indexes.multi import MultiIndex
+
+ def _get_leaf_sorter(labels: list[np.ndarray]) -> npt.NDArray[np.intp]:
+ """
+ Returns sorter for the inner most level while preserving the
+ order of higher levels.
+
+ Parameters
+ ----------
+ labels : list[np.ndarray]
+ Each ndarray has signed integer dtype, not necessarily identical.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ """
+ if labels[0].size == 0:
+ return np.empty(0, dtype=np.intp)
+
+ if len(labels) == 1:
+ return get_group_index_sorter(ensure_platform_int(labels[0]))
+
+ # find indexers of beginning of each set of
+ # same-key labels w.r.t all but last level
+ tic = labels[0][:-1] != labels[0][1:]
+ for lab in labels[1:-1]:
+ tic |= lab[:-1] != lab[1:]
+
+ starts = np.hstack(([True], tic, [True])).nonzero()[0]
+ lab = ensure_int64(labels[-1])
+ return lib.get_level_sorter(lab, ensure_platform_int(starts))
+
+ if isinstance(self, MultiIndex) and isinstance(other, MultiIndex):
+ raise TypeError("Join on level between two MultiIndex objects is ambiguous")
+
+ left, right = self, other
+
+ flip_order = not isinstance(self, MultiIndex)
+ if flip_order:
+ left, right = right, left
+ flip: dict[JoinHow, JoinHow] = {"right": "left", "left": "right"}
+ how = flip.get(how, how)
+
+ assert isinstance(left, MultiIndex)
+
+ level = left._get_level_number(level)
+ old_level = left.levels[level]
+
+ if not right.is_unique:
+ raise NotImplementedError(
+ "Index._join_level on non-unique index is not implemented"
+ )
+
+ new_level, left_lev_indexer, right_lev_indexer = old_level.join(
+ right, how=how, return_indexers=True
+ )
+
+ if left_lev_indexer is None:
+ if keep_order or len(left) == 0:
+ left_indexer = None
+ join_index = left
+ else: # sort the leaves
+ left_indexer = _get_leaf_sorter(left.codes[: level + 1])
+ join_index = left[left_indexer]
+
+ else:
+ left_lev_indexer = ensure_platform_int(left_lev_indexer)
+ rev_indexer = lib.get_reverse_indexer(left_lev_indexer, len(old_level))
+ old_codes = left.codes[level]
+
+ taker = old_codes[old_codes != -1]
+ new_lev_codes = rev_indexer.take(taker)
+
+ new_codes = list(left.codes)
+ new_codes[level] = new_lev_codes
+
+ new_levels = list(left.levels)
+ new_levels[level] = new_level
+
+ if keep_order: # just drop missing values. o.w. keep order
+ left_indexer = np.arange(len(left), dtype=np.intp)
+ left_indexer = cast(np.ndarray, left_indexer)
+ mask = new_lev_codes != -1
+ if not mask.all():
+ new_codes = [lab[mask] for lab in new_codes]
+ left_indexer = left_indexer[mask]
+
+ elif level == 0: # outer most level, take the fast route
+ max_new_lev = 0 if len(new_lev_codes) == 0 else new_lev_codes.max()
+ ngroups = 1 + max_new_lev
+ left_indexer, counts = libalgos.groupsort_indexer(
+ new_lev_codes, ngroups
+ )
+
+ # missing values are placed first; drop them!
+ left_indexer = left_indexer[counts[0] :]
+ new_codes = [lab[left_indexer] for lab in new_codes]
+
+ else: # sort the leaves
+ mask = new_lev_codes != -1
+ mask_all = mask.all()
+ if not mask_all:
+ new_codes = [lab[mask] for lab in new_codes]
+
+ left_indexer = _get_leaf_sorter(new_codes[: level + 1])
+ new_codes = [lab[left_indexer] for lab in new_codes]
+
+ # left_indexers are w.r.t masked frame.
+ # reverse to original frame!
+ if not mask_all:
+ left_indexer = mask.nonzero()[0][left_indexer]
+
+ join_index = MultiIndex(
+ levels=new_levels,
+ codes=new_codes,
+ names=left.names,
+ verify_integrity=False,
+ )
+
+ if right_lev_indexer is not None:
+ right_indexer = right_lev_indexer.take(join_index.codes[level])
+ else:
+ right_indexer = join_index.codes[level]
+
+ if flip_order:
+ left_indexer, right_indexer = right_indexer, left_indexer
+
+ left_indexer = (
+ None if left_indexer is None else ensure_platform_int(left_indexer)
+ )
+ right_indexer = (
+ None if right_indexer is None else ensure_platform_int(right_indexer)
+ )
+ return join_index, left_indexer, right_indexer
+
+ def _join_monotonic(
+ self, other: Index, how: JoinHow = "left"
+ ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ # We only get here with (caller is responsible for ensuring):
+ # 1) matching dtypes
+ # 2) both monotonic increasing
+ # 3) other.is_unique or self.is_unique
+ assert other.dtype == self.dtype
+ assert self._can_use_libjoin and other._can_use_libjoin
+
+ if self.equals(other):
+ # This is a convenient place for this check, but its correctness
+ # does not depend on monotonicity, so it could go earlier
+ # in the calling method.
+ ret_index = other if how == "right" else self
+ return ret_index, None, None
+
+ ridx: npt.NDArray[np.intp] | None
+ lidx: npt.NDArray[np.intp] | None
+
+ if how == "left":
+ if other.is_unique:
+ # We can perform much better than the general case
+ join_index = self
+ lidx = None
+ ridx = self._left_indexer_unique(other)
+ else:
+ join_array, lidx, ridx = self._left_indexer(other)
+ join_index, lidx, ridx = self._wrap_join_result(
+ join_array, other, lidx, ridx, how
+ )
+ elif how == "right":
+ if self.is_unique:
+ # We can perform much better than the general case
+ join_index = other
+ lidx = other._left_indexer_unique(self)
+ ridx = None
+ else:
+ join_array, ridx, lidx = other._left_indexer(self)
+ join_index, lidx, ridx = self._wrap_join_result(
+ join_array, other, lidx, ridx, how
+ )
+ elif how == "inner":
+ join_array, lidx, ridx = self._inner_indexer(other)
+ join_index, lidx, ridx = self._wrap_join_result(
+ join_array, other, lidx, ridx, how
+ )
+ elif how == "outer":
+ join_array, lidx, ridx = self._outer_indexer(other)
+ join_index, lidx, ridx = self._wrap_join_result(
+ join_array, other, lidx, ridx, how
+ )
+
+ lidx = None if lidx is None else ensure_platform_int(lidx)
+ ridx = None if ridx is None else ensure_platform_int(ridx)
+ return join_index, lidx, ridx
+
+ def _wrap_join_result(
+ self,
+ joined: ArrayLike,
+ other: Self,
+ lidx: npt.NDArray[np.intp] | None,
+ ridx: npt.NDArray[np.intp] | None,
+ how: JoinHow,
+ ) -> tuple[Self, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ assert other.dtype == self.dtype
+
+ if lidx is not None and lib.is_range_indexer(lidx, len(self)):
+ lidx = None
+ if ridx is not None and lib.is_range_indexer(ridx, len(other)):
+ ridx = None
+
+ # return self or other if possible to maintain cached attributes
+ if lidx is None:
+ join_index = self
+ elif ridx is None:
+ join_index = other
+ else:
+ join_index = self._constructor._with_infer(
+ joined, dtype=self.dtype, copy=False
+ )
+
+ names = other.names if how == "right" else self.names
+ if join_index.names != names:
+ join_index = join_index.set_names(names)
+
+ return join_index, lidx, ridx
+
+ @final
+ @cache_readonly
+ def _can_use_libjoin(self) -> bool:
+ """
+ Whether we can use the fastpaths implemented in _libs.join.
+
+ This is driven by whether (in monotonic increasing cases that are
+ guaranteed not to have NAs) we can convert to an np.ndarray without
+ making a copy. If we cannot, this negates the performance benefit
+ of using libjoin.
+ """
+ if not self.is_monotonic_increasing:
+ # The libjoin functions all assume monotonicity.
+ return False
+
+ if type(self) is Index:
+ # excludes EAs, but include masks, we get here with monotonic
+ # values only, meaning no NA
+ return (
+ isinstance(self.dtype, np.dtype)
+ or isinstance(self._values, (ArrowExtensionArray, BaseMaskedArray))
+ or (
+ isinstance(self.dtype, StringDtype)
+ and self.dtype.storage == "python"
+ )
+ )
+ # Exclude index types where the conversion to numpy converts to object dtype,
+ # which negates the performance benefit of libjoin
+ # Subclasses should override to return False if _get_join_target is
+ # not zero-copy.
+ # TODO: exclude RangeIndex (which allocates memory)?
+ # Doing so seems to break test_concat_datetime_timezone
+ return not isinstance(self, (ABCIntervalIndex, ABCMultiIndex))
+
+ # --------------------------------------------------------------------
+ # Uncategorized Methods
+
+ @property
+ def values(self) -> ArrayLike:
+ """
+ Return an array representing the data in the Index.
+
+ .. warning::
+
+ We recommend using :attr:`Index.array` or
+ :meth:`Index.to_numpy`, depending on whether you need
+ a reference to the underlying data or a NumPy array.
+
+ .. versionchanged:: 3.0.0
+
+ The returned array is read-only.
+
+ Returns
+ -------
+ array: numpy.ndarray or ExtensionArray
+
+ See Also
+ --------
+ Index.array : Reference to the underlying data.
+ Index.to_numpy : A NumPy array representing the underlying data.
+
+ Examples
+ --------
+ For :class:`pandas.Index`:
+
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx
+ Index([1, 2, 3], dtype='int64')
+ >>> idx.values
+ array([1, 2, 3])
+
+ For :class:`pandas.IntervalIndex`:
+
+ >>> idx = pd.interval_range(start=0, end=5)
+ >>> idx.values
+
+ [(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]]
+ Length: 5, dtype: interval[int64, right]
+ """
+ data = self._data
+ if isinstance(data, np.ndarray):
+ data = data.view()
+ data.flags.writeable = False
+ return data
+
+ @cache_readonly
+ def array(self) -> ExtensionArray:
+ """
+ The ExtensionArray of the data backing this Index.
+
+ This property provides direct access to the underlying array data of
+ an Index without requiring conversion to a NumPy array. It
+ returns an ExtensionArray, which is the native storage format for
+ pandas extension dtypes.
+
+ Returns
+ -------
+ ExtensionArray
+ An ExtensionArray of the values stored within. For extension
+ types, this is the actual array. For NumPy native types, this
+ is a thin (no copy) wrapper around :class:`numpy.ndarray`.
+
+ ``.array`` differs from ``.values``, which may require converting
+ the data to a different form.
+
+ See Also
+ --------
+ Index.to_numpy : Similar method that always returns a NumPy array.
+ Series.to_numpy : Similar method that always returns a NumPy array.
+
+ Notes
+ -----
+ This table lays out the different array types for each extension
+ dtype within pandas.
+
+ ================== =============================
+ dtype array type
+ ================== =============================
+ category Categorical
+ period PeriodArray
+ interval IntervalArray
+ IntegerNA IntegerArray
+ string StringArray
+ boolean BooleanArray
+ datetime64[ns, tz] DatetimeArray
+ ================== =============================
+
+ For any 3rd-party extension types, the array type will be an
+ ExtensionArray.
+
+ For all remaining dtypes ``.array`` will be a
+ :class:`arrays.NumpyExtensionArray` wrapping the actual ndarray
+ stored within. If you absolutely need a NumPy array (possibly with
+ copying / coercing data), then use :meth:`Series.to_numpy` instead.
+
+ Examples
+ --------
+ For regular NumPy types like int, and float, a NumpyExtensionArray
+ is returned.
+
+ >>> pd.Index([1, 2, 3]).array
+
+ [1, 2, 3]
+ Length: 3, dtype: int64
+
+ For extension types, like Categorical, the actual ExtensionArray
+ is returned
+
+ >>> idx = pd.Index(pd.Categorical(["a", "b", "a"]))
+ >>> idx.array
+ ['a', 'b', 'a']
+ Categories (2, str): ['a', 'b']
+ """
+ array = self._data
+ if isinstance(array, np.ndarray):
+ from pandas.core.arrays.numpy_ import NumpyExtensionArray
+
+ array = NumpyExtensionArray(array)
+ # TODO decide on read-only https://github.com/pandas-dev/pandas/issues/63099
+ # array = array.view()
+ # array._readonly = True
+ return array
+
+ @property
+ def _values(self) -> ExtensionArray | np.ndarray:
+ """
+ The best array representation.
+
+ This is an ndarray or ExtensionArray.
+
+ ``_values`` are consistent between ``Series`` and ``Index``.
+
+ It may differ from the public '.values' method.
+
+ index | values | _values |
+ ----------------- | --------------- | ------------- |
+ Index | ndarray | ndarray |
+ CategoricalIndex | Categorical | Categorical |
+ DatetimeIndex | ndarray[M8ns] | DatetimeArray |
+ DatetimeIndex[tz] | ndarray[M8ns] | DatetimeArray |
+ PeriodIndex | ndarray[object] | PeriodArray |
+ IntervalIndex | IntervalArray | IntervalArray |
+
+ See Also
+ --------
+ values : Values
+ """
+ return self._data
+
+ def _get_engine_target(self) -> ArrayLike:
+ """
+ Get the ndarray or ExtensionArray that we can pass to the IndexEngine
+ constructor.
+ """
+ vals = self._values
+ if isinstance(vals, StringArray):
+ # GH#45652 much more performant than ExtensionEngine
+ return vals._ndarray
+ if isinstance(vals, ArrowExtensionArray) and self.dtype.kind in "Mm":
+ import pyarrow as pa
+
+ pa_type = vals._pa_array.type
+ if pa.types.is_timestamp(pa_type):
+ vals = vals._to_datetimearray()
+ return vals._ndarray.view("i8")
+ elif pa.types.is_duration(pa_type):
+ vals = vals._to_timedeltaarray()
+ return vals._ndarray.view("i8")
+ if (
+ type(self) is Index
+ and isinstance(self._values, ExtensionArray)
+ and not isinstance(self._values, BaseMaskedArray)
+ and not (
+ isinstance(self._values, ArrowExtensionArray)
+ and is_numeric_dtype(self.dtype)
+ # Exclude decimal
+ and self.dtype.kind != "O"
+ )
+ ):
+ # TODO(ExtensionIndex): remove special-case, just use self._values
+ return self._values.astype(object)
+ return vals
+
+ @final
+ def _get_join_target(self) -> np.ndarray:
+ """
+ Get the ndarray or ExtensionArray that we can pass to the join
+ functions.
+ """
+ if isinstance(self._values, BaseMaskedArray):
+ # This is only used if our array is monotonic, so no NAs present
+ return self._values._data
+ elif (
+ isinstance(self._values, ArrowExtensionArray)
+ and self.dtype.kind not in "mM"
+ ):
+ # This is only used if our array is monotonic, so no missing values
+ # present
+ # "mM" cases will go through _get_engine_target and cast to i8
+ return self._values.to_numpy()
+
+ # TODO: exclude ABCRangeIndex case here as it copies
+ target = self._get_engine_target()
+ if not isinstance(target, np.ndarray):
+ raise ValueError("_can_use_libjoin should return False.")
+ return target
+
+ def _from_join_target(self, result: np.ndarray) -> ArrayLike:
+ """
+ Cast the ndarray returned from one of the libjoin.foo_indexer functions
+ back to type(self._data).
+ """
+ if isinstance(self.values, BaseMaskedArray):
+ return type(self.values)(result, np.zeros(result.shape, dtype=np.bool_))
+ elif isinstance(self.values, (ArrowExtensionArray, StringArray)):
+ return type(self.values)._from_sequence(result, dtype=self.dtype)
+ return result
+
+ def memory_usage(self, deep: bool = False) -> int:
+ """
+ Memory usage of the values.
+
+ Parameters
+ ----------
+ deep : bool, default False
+ Introspect the data deeply, interrogate
+ `object` dtypes for system-level memory consumption.
+
+ Returns
+ -------
+ bytes used
+ Returns memory usage of the values in the Index in bytes.
+
+ See Also
+ --------
+ numpy.ndarray.nbytes : Total bytes consumed by the elements of the
+ array.
+
+ Notes
+ -----
+ Memory usage does not include memory consumed by elements that
+ are not components of the array if deep=False or if used on PyPy
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx.memory_usage()
+ 24
+ """
+ result = self._memory_usage(deep=deep)
+
+ # include our engine hashtable, only if it's already cached
+ if "_engine" in self._cache:
+ result += self._engine.sizeof(deep=deep)
+ return result
+
+ @final
+ def where(self, cond, other=None) -> Index:
+ """
+ Replace values where the condition is False.
+
+ The replacement is taken from other.
+
+ Parameters
+ ----------
+ cond : bool array-like with the same length as self
+ Condition to select the values on.
+ other : scalar, or array-like, default None
+ Replacement if the condition is False.
+
+ Returns
+ -------
+ pandas.Index
+ A copy of self with values replaced from other
+ where the condition is False.
+
+ See Also
+ --------
+ Series.where : Same method for Series.
+ DataFrame.where : Same method for DataFrame.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["car", "bike", "train", "tractor"])
+ >>> idx
+ Index(['car', 'bike', 'train', 'tractor'], dtype='str')
+ >>> idx.where(idx.isin(["car", "train"]), "other")
+ Index(['car', 'other', 'train', 'other'], dtype='str')
+ """
+ if isinstance(self, ABCMultiIndex):
+ raise NotImplementedError(
+ ".where is not supported for MultiIndex operations"
+ )
+ cond = np.asarray(cond, dtype=bool)
+ return self.putmask(~cond, other)
+
+ # construction helpers
+ @final
+ @classmethod
+ def _raise_scalar_data_error(cls, data):
+ # We return the TypeError so that we can raise it from the constructor
+ # in order to keep mypy happy
+ raise TypeError(
+ f"{cls.__name__}(...) must be called with a collection of some "
+ f"kind, {repr(data) if not isinstance(data, np.generic) else str(data)} "
+ "was passed"
+ )
+
+ @classmethod
+ def _maybe_copy_array_input(
+ cls, data, copy: bool | None, dtype
+ ) -> tuple[Any, bool]:
+ """
+ Ensure that the input data is copied if necessary.
+ GH#63388
+ """
+ if isinstance(data, (ExtensionArray, np.ndarray)):
+ if copy is not False:
+ if dtype is None or astype_is_view(data.dtype, pandas_dtype(dtype)):
+ data = data.copy()
+ copy = False
+ return data, bool(copy)
+
+ def _validate_fill_value(self, value):
+ """
+ Check if the value can be inserted into our array without casting,
+ and convert it to an appropriate native type if necessary.
+
+ Raises
+ ------
+ TypeError
+ If the value cannot be inserted into an array of this dtype.
+ """
+ dtype = self.dtype
+ if isinstance(dtype, np.dtype) and dtype.kind not in "mM":
+ if isinstance(value, tuple) and dtype != object:
+ # GH#54385
+ raise TypeError
+ try:
+ return np_can_hold_element(dtype, value)
+ except LossySetitemError as err:
+ # re-raise as TypeError for consistency
+ raise TypeError from err
+ elif not can_hold_element(self._values, value):
+ raise TypeError
+ return value
+
+ @cache_readonly
+ def _is_memory_usage_qualified(self) -> bool:
+ """
+ Return a boolean if we need a qualified .info display.
+ """
+ return is_object_dtype(self.dtype) or (
+ is_string_dtype(self.dtype) and self.dtype.storage == "python" # type: ignore[union-attr]
+ )
+
+ def __contains__(self, key: Any) -> bool:
+ """
+ Return a boolean indicating whether the provided key is in the index.
+
+ Parameters
+ ----------
+ key : label
+ The key to check if it is present in the index.
+
+ Returns
+ -------
+ bool
+ Whether the key search is in the index.
+
+ Raises
+ ------
+ TypeError
+ If the key is not hashable.
+
+ See Also
+ --------
+ Index.isin : Returns an ndarray of boolean dtype indicating whether the
+ list-like key is in the index.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3, 4])
+ >>> idx
+ Index([1, 2, 3, 4], dtype='int64')
+
+ >>> 2 in idx
+ True
+ >>> 6 in idx
+ False
+ """
+ hash(key)
+ try:
+ return key in self._engine
+ except (OverflowError, TypeError, ValueError):
+ return False
+
+ # https://github.com/python/typeshed/issues/2148#issuecomment-520783318
+ # Incompatible types in assignment (expression has type "None", base class
+ # "object" defined the type as "Callable[[object], int]")
+ __hash__: ClassVar[None] # type: ignore[assignment]
+
+ @final
+ def __setitem__(self, key, value) -> None:
+ raise TypeError("Index does not support mutable operations")
+
+ def __getitem__(self, key):
+ """
+ Override numpy.ndarray's __getitem__ method to work as desired.
+
+ This function adds lists and Series as valid boolean indexers
+ (ndarrays only supports ndarray with dtype=bool).
+
+ If resulting ndim != 1, plain ndarray is returned instead of
+ corresponding `Index` subclass.
+
+ """
+ getitem = self._data.__getitem__
+
+ key = lib.item_from_zerodim(key)
+ if is_integer(key) or is_float(key):
+ # GH#44051 exclude bool, which would return a 2d ndarray
+ key = com.cast_scalar_indexer(key)
+ return getitem(key)
+
+ if isinstance(key, slice):
+ # This case is separated from the conditional above to avoid
+ # pessimization com.is_bool_indexer and ndim checks.
+ return self._getitem_slice(key)
+
+ if com.is_bool_indexer(key):
+ # if we have list[bools, length=1e5] then doing this check+convert
+ # takes 166 µs + 2.1 ms and cuts the ndarray.__getitem__
+ # time below from 3.8 ms to 496 µs
+ # if we already have ndarray[bool], the overhead is 1.4 µs or .25%
+ if isinstance(getattr(key, "dtype", None), ExtensionDtype):
+ key = key.to_numpy(dtype=bool, na_value=False)
+ else:
+ key = np.asarray(key, dtype=bool)
+
+ if not isinstance(self.dtype, ExtensionDtype):
+ if len(key) == 0 and len(key) != len(self):
+ raise ValueError(
+ "The length of the boolean indexer cannot be 0 "
+ "when the Index has length greater than 0."
+ )
+
+ result = getitem(key)
+ # Because we ruled out integer above, we always get an arraylike here
+ if result.ndim > 1:
+ disallow_ndim_indexing(result)
+
+ # NB: Using _constructor._simple_new would break if MultiIndex
+ # didn't override __getitem__
+ return self._constructor._simple_new(result, name=self._name)
+
+ def _getitem_slice(self, slobj: slice) -> Self:
+ """
+ Fastpath for __getitem__ when we know we have a slice.
+ """
+ res = self._data[slobj]
+ result = type(self)._simple_new(res, name=self._name, refs=self._references)
+ if "_engine" in self._cache:
+ reverse = slobj.step is not None and slobj.step < 0
+ result._engine._update_from_sliced(self._engine, reverse=reverse) # type: ignore[union-attr]
+
+ return result
+
+ @final
+ def _can_hold_identifiers_and_holds_name(self, name) -> bool:
+ """
+ Faster check for ``name in self`` when we know `name` is a Python
+ identifier (e.g. in NDFrame.__getattr__, which hits this to support
+ . key lookup). For indexes that can't hold identifiers (everything
+ but object & categorical) we just return False.
+
+ https://github.com/pandas-dev/pandas/issues/19764
+ """
+ if (
+ is_object_dtype(self.dtype)
+ or is_string_dtype(self.dtype)
+ or isinstance(self.dtype, CategoricalDtype)
+ ):
+ return name in self
+ return False
+
+ def append(self, other: Index | Sequence[Index]) -> Index:
+ """
+ Append a collection of Index options together.
+
+ Parameters
+ ----------
+ other : Index or list/tuple of indices
+ Single Index or a collection of indices, which can be either a list or a
+ tuple.
+
+ Returns
+ -------
+ Index
+ Returns a new Index object resulting from appending the provided other
+ indices to the original Index.
+
+ See Also
+ --------
+ Index.insert : Make new Index inserting new item at location.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx.append(pd.Index([4]))
+ Index([1, 2, 3, 4], dtype='int64')
+ """
+ to_concat = [self]
+
+ if isinstance(other, (list, tuple)):
+ to_concat += list(other)
+ else:
+ # error: Argument 1 to "append" of "list" has incompatible type
+ # "Union[Index, Sequence[Index]]"; expected "Index"
+ to_concat.append(other) # type: ignore[arg-type]
+
+ for obj in to_concat:
+ if not isinstance(obj, Index):
+ raise TypeError("all inputs must be Index")
+
+ names = {obj.name for obj in to_concat}
+ name = None if len(names) > 1 else self.name
+
+ return self._concat(to_concat, name)
+
+ def _concat(self, to_concat: list[Index], name: Hashable) -> Index:
+ """
+ Concatenate multiple Index objects.
+ """
+ to_concat_vals = [x._values for x in to_concat]
+
+ result = concat_compat(to_concat_vals)
+
+ return Index._with_infer(result, name=name, copy=False)
+
+ def putmask(self, mask, value) -> Index:
+ """
+ Return a new Index of the values set with the mask.
+
+ Parameters
+ ----------
+ mask : array-like of bool
+ Array of booleans denoting where values should be replaced.
+ value : scalar
+ Scalar value to use to fill holes (e.g. 0).
+ This value cannot be a list-likes.
+
+ Returns
+ -------
+ Index
+ A new Index of the values set with the mask.
+
+ See Also
+ --------
+ numpy.putmask : Changes elements of an array
+ based on conditional and input values.
+
+ Examples
+ --------
+ >>> idx1 = pd.Index([1, 2, 3])
+ >>> idx2 = pd.Index([5, 6, 7])
+ >>> idx1.putmask([True, False, False], idx2)
+ Index([5, 2, 3], dtype='int64')
+ """
+ mask, noop = validate_putmask(self._values, mask)
+ if noop:
+ return self.copy()
+
+ if self.dtype != object and is_valid_na_for_dtype(value, self.dtype):
+ # e.g. None -> np.nan, see also Block._standardize_fill_value
+ value = self._na_value
+
+ try:
+ converted = self._validate_fill_value(value)
+ except (LossySetitemError, ValueError, TypeError) as err:
+ if is_object_dtype(self.dtype): # pragma: no cover
+ raise err
+
+ # See also: Block.coerce_to_target_dtype
+ dtype = self._find_common_type_compat(value)
+ if dtype == self.dtype:
+ # GH#56376 avoid RecursionError
+ raise AssertionError(
+ "Something has gone wrong. Please report a bug at "
+ "github.com/pandas-dev/pandas"
+ ) from err
+ return self.astype(dtype).putmask(mask, value)
+
+ values = self._values.copy()
+
+ if isinstance(values, np.ndarray):
+ converted = setitem_datetimelike_compat(values, mask.sum(), converted)
+ np.putmask(values, mask, converted)
+
+ else:
+ # Note: we use the original value here, not converted, as
+ # _validate_fill_value is not idempotent
+ values._putmask(mask, value)
+
+ return self._shallow_copy(values)
+
+ def equals(self, other: Any) -> bool:
+ """
+ Determine if two Index object are equal.
+
+ The things that are being compared are:
+
+ * The elements inside the Index object.
+ * The order of the elements inside the Index object.
+
+ Parameters
+ ----------
+ other : Any
+ The other object to compare against.
+
+ Returns
+ -------
+ bool
+ True if "other" is an Index and it has the same elements and order
+ as the calling index; False otherwise.
+
+ See Also
+ --------
+ Index.identical: Checks that object attributes and types are also equal.
+ Index.has_duplicates: Check if the Index has duplicate values.
+ Index.is_unique: Return if the index has unique values.
+
+ Examples
+ --------
+ >>> idx1 = pd.Index([1, 2, 3])
+ >>> idx1
+ Index([1, 2, 3], dtype='int64')
+ >>> idx1.equals(pd.Index([1, 2, 3]))
+ True
+
+ The elements inside are compared
+
+ >>> idx2 = pd.Index(["1", "2", "3"])
+ >>> idx2
+ Index(['1', '2', '3'], dtype='str')
+
+ >>> idx1.equals(idx2)
+ False
+
+ The order is compared
+
+ >>> ascending_idx = pd.Index([1, 2, 3])
+ >>> ascending_idx
+ Index([1, 2, 3], dtype='int64')
+ >>> descending_idx = pd.Index([3, 2, 1])
+ >>> descending_idx
+ Index([3, 2, 1], dtype='int64')
+ >>> ascending_idx.equals(descending_idx)
+ False
+
+ The dtype is *not* compared
+
+ >>> int64_idx = pd.Index([1, 2, 3], dtype="int64")
+ >>> int64_idx
+ Index([1, 2, 3], dtype='int64')
+ >>> uint64_idx = pd.Index([1, 2, 3], dtype="uint64")
+ >>> uint64_idx
+ Index([1, 2, 3], dtype='uint64')
+ >>> int64_idx.equals(uint64_idx)
+ True
+ """
+ if self.is_(other):
+ return True
+
+ if not isinstance(other, Index):
+ return False
+
+ if len(self) != len(other):
+ # quickly return if the lengths are different
+ return False
+
+ if isinstance(self.dtype, StringDtype) and other.dtype != self.dtype:
+ # TODO(infer_string) can we avoid this special case?
+ # special case for object behavior
+ return other.equals(self.astype(object))
+
+ if is_object_dtype(self.dtype) and not is_object_dtype(other.dtype):
+ # if other is not object, use other's logic for coercion
+ return other.equals(self)
+
+ if isinstance(other, ABCMultiIndex):
+ # d-level MultiIndex can equal d-tuple Index
+ return other.equals(self)
+
+ if isinstance(self._values, ExtensionArray):
+ # Dispatch to the ExtensionArray's .equals method.
+ if not isinstance(other, type(self)):
+ return False
+
+ earr = cast(ExtensionArray, self._data)
+ return earr.equals(other._data)
+
+ if isinstance(other.dtype, ExtensionDtype):
+ # All EA-backed Index subclasses override equals
+ return other.equals(self)
+
+ return array_equivalent(self._values, other._values)
+
+ @final
+ def identical(self, other) -> bool:
+ """
+ Similar to equals, but checks that object attributes and types are also equal.
+
+ Parameters
+ ----------
+ other : Index
+ The Index object you want to compare with the current Index object.
+
+ Returns
+ -------
+ bool
+ If two Index objects have equal elements and same type True,
+ otherwise False.
+
+ See Also
+ --------
+ Index.equals: Determine if two Index object are equal.
+ Index.has_duplicates: Check if the Index has duplicate values.
+ Index.is_unique: Return if the index has unique values.
+
+ Examples
+ --------
+ >>> idx1 = pd.Index(["1", "2", "3"])
+ >>> idx2 = pd.Index(["1", "2", "3"])
+ >>> idx2.identical(idx1)
+ True
+
+ >>> idx1 = pd.Index(["1", "2", "3"], name="A")
+ >>> idx2 = pd.Index(["1", "2", "3"], name="B")
+ >>> idx2.identical(idx1)
+ False
+ """
+ return (
+ self.equals(other)
+ and all(
+ getattr(self, c, None) == getattr(other, c, None)
+ for c in self._comparables
+ )
+ and type(self) == type(other)
+ and self.dtype == other.dtype
+ )
+
+ @final
+ def asof(self, label):
+ """
+ Return the label from the index, or, if not present, the previous one.
+
+ Assuming that the index is sorted, return the passed index label if it
+ is in the index, or return the previous index label if the passed one
+ is not in the index.
+
+ Parameters
+ ----------
+ label : object
+ The label up to which the method returns the latest index label.
+
+ Returns
+ -------
+ object
+ The passed label if it is in the index. The previous label if the
+ passed label is not in the sorted index or `NaN` if there is no
+ such label.
+
+ See Also
+ --------
+ Series.asof : Return the latest value in a Series up to the
+ passed index.
+ merge_asof : Perform an asof merge (similar to left join but it
+ matches on nearest key rather than equal key).
+ Index.get_loc : An `asof` is a thin wrapper around `get_loc`
+ with method='pad'.
+
+ Examples
+ --------
+ `Index.asof` returns the latest index label up to the passed label.
+
+ >>> idx = pd.Index(["2013-12-31", "2014-01-02", "2014-01-03"])
+ >>> idx.asof("2014-01-01")
+ '2013-12-31'
+
+ If the label is in the index, the method returns the passed label.
+
+ >>> idx.asof("2014-01-02")
+ '2014-01-02'
+
+ If all of the labels in the index are later than the passed label,
+ NaN is returned.
+
+ >>> idx.asof("1999-01-02")
+ nan
+
+ If the index is not sorted, an error is raised.
+
+ >>> idx_not_sorted = pd.Index(["2013-12-31", "2015-01-02", "2014-01-03"])
+ >>> idx_not_sorted.asof("2013-12-31")
+ Traceback (most recent call last):
+ ValueError: index must be monotonic increasing or decreasing
+ """
+ self._searchsorted_monotonic(label) # validate sortedness
+ try:
+ loc = self.get_loc(label)
+ except (KeyError, TypeError) as err:
+ # KeyError -> No exact match, try for padded
+ # TypeError -> passed e.g. non-hashable, fall through to get
+ # the tested exception message
+ indexer = self.get_indexer([label], method="pad")
+ if indexer.ndim > 1 or indexer.size > 1:
+ raise TypeError("asof requires scalar valued input") from err
+ loc = indexer.item()
+ if loc == -1:
+ return self._na_value
+ else:
+ if isinstance(loc, slice):
+ return self[loc][-1]
+
+ return self[loc]
+
+ def asof_locs(
+ self, where: Index, mask: npt.NDArray[np.bool_]
+ ) -> npt.NDArray[np.intp]:
+ """
+ Return the locations (indices) of labels in the index.
+
+ As in the :meth:`pandas.Index.asof`, if the label (a particular entry in
+ ``where``) is not in the index, the latest index label up to the
+ passed label is chosen and its index returned.
+
+ If all of the labels in the index are later than a label in ``where``,
+ -1 is returned.
+
+ ``mask`` is used to ignore ``NA`` values in the index during calculation.
+
+ Parameters
+ ----------
+ where : Index
+ An Index consisting of an array of timestamps.
+ mask : np.ndarray[bool]
+ Array of booleans denoting where values in the original
+ data are not ``NA``.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ An array of locations (indices) of the labels from the index
+ which correspond to the return values of :meth:`pandas.Index.asof`
+ for every element in ``where``.
+
+ See Also
+ --------
+ Index.asof : Return the label from the index, or, if not present, the
+ previous one.
+
+ Examples
+ --------
+ >>> idx = pd.date_range("2023-06-01", periods=3, freq="D")
+ >>> where = pd.DatetimeIndex(
+ ... ["2023-05-30 00:12:00", "2023-06-01 00:00:00", "2023-06-02 23:59:59"]
+ ... )
+ >>> mask = np.ones(3, dtype=bool)
+ >>> idx.asof_locs(where, mask)
+ array([-1, 0, 1])
+
+ We can use ``mask`` to ignore certain values in the index during calculation.
+
+ >>> mask[1] = False
+ >>> idx.asof_locs(where, mask)
+ array([-1, 0, 0])
+ """
+ # error: No overload variant of "searchsorted" of "ndarray" matches argument
+ # types "Union[ExtensionArray, ndarray[Any, Any]]", "str"
+ # TODO: will be fixed when ExtensionArray.searchsorted() is fixed
+ locs = self._values[mask].searchsorted(
+ where._values,
+ side="right", # type: ignore[call-overload]
+ )
+ locs = np.where(locs > 0, locs - 1, 0)
+
+ result = np.arange(len(self), dtype=np.intp)[mask].take(locs)
+
+ first_value = self._values[mask.argmax()]
+ result[(locs == 0) & (where._values < first_value)] = -1
+
+ return result
+
+ @overload
+ def sort_values(
+ self,
+ *,
+ return_indexer: Literal[False] = ...,
+ ascending: bool = ...,
+ na_position: NaPosition = ...,
+ key: Callable | None = ...,
+ ) -> Self: ...
+
+ @overload
+ def sort_values(
+ self,
+ *,
+ return_indexer: Literal[True],
+ ascending: bool = ...,
+ na_position: NaPosition = ...,
+ key: Callable | None = ...,
+ ) -> tuple[Self, np.ndarray]: ...
+
+ @overload
+ def sort_values(
+ self,
+ *,
+ return_indexer: bool = ...,
+ ascending: bool = ...,
+ na_position: NaPosition = ...,
+ key: Callable | None = ...,
+ ) -> Self | tuple[Self, np.ndarray]: ...
+
+ def sort_values(
+ self,
+ *,
+ return_indexer: bool = False,
+ ascending: bool = True,
+ na_position: NaPosition = "last",
+ key: Callable | None = None,
+ ) -> Self | tuple[Self, np.ndarray]:
+ """
+ Return a sorted copy of the index.
+
+ Return a sorted copy of the index, and optionally return the indices
+ that sorted the index itself.
+
+ Parameters
+ ----------
+ return_indexer : bool, default False
+ Should the indices that would sort the index be returned.
+ ascending : bool, default True
+ Should the index values be sorted in an ascending order.
+ na_position : {'first' or 'last'}, default 'last'
+ Argument 'first' puts NaNs at the beginning, 'last' puts NaNs at
+ the end.
+ key : callable, optional
+ If not None, apply the key function to the index values
+ before sorting. This is similar to the `key` argument in the
+ builtin :meth:`sorted` function, with the notable difference that
+ this `key` function should be *vectorized*. It should expect an
+ ``Index`` and return an ``Index`` of the same shape.
+
+ Returns
+ -------
+ sorted_index : pandas.Index
+ Sorted copy of the index.
+ indexer : numpy.ndarray, optional
+ The indices that the index itself was sorted by.
+
+ See Also
+ --------
+ Series.sort_values : Sort values of a Series.
+ DataFrame.sort_values : Sort values in a DataFrame.
+
+ Examples
+ --------
+ >>> idx = pd.Index([10, 100, 1, 1000])
+ >>> idx
+ Index([10, 100, 1, 1000], dtype='int64')
+
+ Sort values in ascending order (default behavior).
+
+ >>> idx.sort_values()
+ Index([1, 10, 100, 1000], dtype='int64')
+
+ Sort values in descending order, and also get the indices `idx` was
+ sorted by.
+
+ >>> idx.sort_values(ascending=False, return_indexer=True)
+ (Index([1000, 100, 10, 1], dtype='int64'), array([3, 1, 0, 2]))
+ """
+ if key is None and (
+ (ascending and self.is_monotonic_increasing)
+ or (not ascending and self.is_monotonic_decreasing)
+ ):
+ if return_indexer:
+ indexer = np.arange(len(self), dtype=np.intp)
+ return self.copy(), indexer
+ else:
+ return self.copy()
+
+ # GH 35584. Sort missing values according to na_position kwarg
+ # ignore na_position for MultiIndex
+ if not isinstance(self, ABCMultiIndex):
+ _as = nargsort(
+ items=self, ascending=ascending, na_position=na_position, key=key
+ )
+ else:
+ idx = cast(Index, ensure_key_mapped(self, key))
+ _as = idx.argsort(na_position=na_position)
+ if not ascending:
+ _as = _as[::-1]
+
+ sorted_index = self.take(_as)
+
+ if return_indexer:
+ return sorted_index, _as
+ else:
+ return sorted_index
+
+ def shift(self, periods: int = 1, freq=None) -> Self:
+ """
+ Shift index by desired number of time frequency increments.
+
+ This method is for shifting the values of datetime-like indexes
+ by a specified time increment a given number of times.
+
+ Parameters
+ ----------
+ periods : int, default 1
+ Number of periods (or increments) to shift by,
+ can be positive or negative.
+ freq : pandas.DateOffset, pandas.Timedelta or str, optional
+ Frequency increment to shift by.
+ If None, the index is shifted by its own `freq` attribute.
+ Offset aliases are valid strings, e.g., 'D', 'W', 'M' etc.
+
+ Returns
+ -------
+ pandas.Index
+ Shifted index.
+
+ See Also
+ --------
+ Series.shift : Shift values of Series.
+
+ Notes
+ -----
+ This method is only implemented for datetime-like index classes,
+ i.e., DatetimeIndex, PeriodIndex and TimedeltaIndex.
+
+ Examples
+ --------
+ Put the first 5 month starts of 2011 into an index.
+
+ >>> month_starts = pd.date_range("1/1/2011", periods=5, freq="MS")
+ >>> month_starts
+ DatetimeIndex(['2011-01-01', '2011-02-01', '2011-03-01', '2011-04-01',
+ '2011-05-01'],
+ dtype='datetime64[us]', freq='MS')
+
+ Shift the index by 10 days.
+
+ >>> month_starts.shift(10, freq="D")
+ DatetimeIndex(['2011-01-11', '2011-02-11', '2011-03-11', '2011-04-11',
+ '2011-05-11'],
+ dtype='datetime64[us]', freq=None)
+
+ The default value of `freq` is the `freq` attribute of the index,
+ which is 'MS' (month start) in this example.
+
+ >>> month_starts.shift(10)
+ DatetimeIndex(['2011-11-01', '2011-12-01', '2012-01-01', '2012-02-01',
+ '2012-03-01'],
+ dtype='datetime64[us]', freq='MS')
+ """
+ raise NotImplementedError(
+ f"This method is only implemented for DatetimeIndex, PeriodIndex and "
+ f"TimedeltaIndex; Got type {type(self).__name__}"
+ )
+
+ def argsort(self, *args, **kwargs) -> npt.NDArray[np.intp]:
+ """
+ Return the integer indices that would sort the index.
+
+ Parameters
+ ----------
+ *args
+ Passed to `numpy.ndarray.argsort`.
+ **kwargs
+ Passed to `numpy.ndarray.argsort`.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ Integer indices that would sort the index if used as
+ an indexer.
+
+ See Also
+ --------
+ numpy.argsort : Similar method for NumPy arrays.
+ Index.sort_values : Return sorted copy of Index.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["b", "a", "d", "c"])
+ >>> idx
+ Index(['b', 'a', 'd', 'c'], dtype='str')
+
+ >>> order = idx.argsort()
+ >>> order
+ array([1, 0, 3, 2])
+
+ >>> idx[order]
+ Index(['a', 'b', 'c', 'd'], dtype='str')
+ """
+ # This works for either ndarray or EA, is overridden
+ # by RangeIndex, MultIIndex
+ return self._data.argsort(*args, **kwargs)
+
+ def _check_indexing_error(self, key) -> None:
+ if not is_scalar(key):
+ # if key is not a scalar, directly raise an error (the code below
+ # would convert to numpy arrays and raise later any way) - GH29926
+ raise InvalidIndexError(key)
+
+ @cache_readonly
+ def _should_fallback_to_positional(self) -> bool:
+ """
+ Should an integer key be treated as positional?
+ """
+ return self.inferred_type not in {
+ "integer",
+ "mixed-integer",
+ "floating",
+ "complex",
+ }
+
+ _index_shared_docs["get_indexer_non_unique"] = """
+ Compute indexer and mask for new index given the current index.
+
+ The indexer should be then used as an input to ndarray.take to align the
+ current data to the new index.
+
+ Parameters
+ ----------
+ target : %(target_klass)s
+ An iterable containing the values to be used for computing indexer.
+
+ Returns
+ -------
+ indexer : np.ndarray[np.intp]
+ Integers from 0 to n - 1 indicating that the index at these
+ positions matches the corresponding target values. Missing values
+ in the target are marked by -1.
+ missing : np.ndarray[np.intp]
+ An indexer into the target of the values not found.
+ These correspond to the -1 in the indexer array.
+
+ See Also
+ --------
+ Index.get_indexer : Computes indexer and mask for new index given
+ the current index.
+ Index.get_indexer_for : Returns an indexer even when non-unique.
+
+ Examples
+ --------
+ >>> index = pd.Index(['c', 'b', 'a', 'b', 'b'])
+ >>> index.get_indexer_non_unique(['b', 'b'])
+ (array([1, 3, 4, 1, 3, 4]), array([], dtype=int64))
+
+ In the example below there are no matched values.
+
+ >>> index = pd.Index(['c', 'b', 'a', 'b', 'b'])
+ >>> index.get_indexer_non_unique(['q', 'r', 't'])
+ (array([-1, -1, -1]), array([0, 1, 2]))
+
+ For this reason, the returned ``indexer`` contains only integers equal to -1.
+ It demonstrates that there's no match between the index and the ``target``
+ values at these positions. The mask [0, 1, 2] in the return value shows that
+ the first, second, and third elements are missing.
+
+ Notice that the return value is a tuple contains two items. In the example
+ below the first item is an array of locations in ``index``. The second
+ item is a mask shows that the first and third elements are missing.
+
+ >>> index = pd.Index(['c', 'b', 'a', 'b', 'b'])
+ >>> index.get_indexer_non_unique(['f', 'b', 's'])
+ (array([-1, 1, 3, 4, -1]), array([0, 2]))
+ """
+
+ def get_indexer_non_unique(
+ self, target
+ ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ """
+ Compute indexer and mask for new index given the current index.
+
+ The indexer should be then used as an input to ndarray.take to align the
+ current data to the new index.
+
+ Parameters
+ ----------
+ target : Index
+ An iterable containing the values to be used for computing indexer.
+
+ Returns
+ -------
+ indexer : np.ndarray[np.intp]
+ Integers from 0 to n - 1 indicating that the index at these
+ positions matches the corresponding target values. Missing values
+ in the target are marked by -1.
+ missing : np.ndarray[np.intp]
+ An indexer into the target of the values not found.
+ These correspond to the -1 in the indexer array.
+
+ See Also
+ --------
+ Index.get_indexer : Computes indexer and mask for new index given
+ the current index.
+ Index.get_indexer_for : Returns an indexer even when non-unique.
+
+ Examples
+ --------
+ >>> index = pd.Index(["c", "b", "a", "b", "b"])
+ >>> index.get_indexer_non_unique(["b", "b"])
+ (array([1, 3, 4, 1, 3, 4]), array([], dtype=int64))
+
+ In the example below there are no matched values.
+
+ >>> index = pd.Index(["c", "b", "a", "b", "b"])
+ >>> index.get_indexer_non_unique(["q", "r", "t"])
+ (array([-1, -1, -1]), array([0, 1, 2]))
+
+ For this reason, the returned ``indexer`` contains only integers equal to -1.
+ It demonstrates that there's no match between the index and the ``target``
+ values at these positions. The mask [0, 1, 2] in the return value shows that
+ the first, second, and third elements are missing.
+
+ Notice that the return value is a tuple contains two items. In the example
+ below the first item is an array of locations in ``index``. The second
+ item is a mask shows that the first and third elements are missing.
+
+ >>> index = pd.Index(["c", "b", "a", "b", "b"])
+ >>> index.get_indexer_non_unique(["f", "b", "s"])
+ (array([-1, 1, 3, 4, -1]), array([0, 2]))
+ """
+ target = self._maybe_cast_listlike_indexer(target)
+
+ if not self._should_compare(target) and not self._should_partial_index(target):
+ # _should_partial_index e.g. IntervalIndex with numeric scalars
+ # that can be matched to Interval scalars.
+ return self._get_indexer_non_comparable(target, method=None, unique=False)
+
+ pself, ptarget = self._maybe_downcast_for_indexing(target)
+ if pself is not self or ptarget is not target:
+ return pself.get_indexer_non_unique(ptarget)
+
+ if self.dtype != target.dtype:
+ # TODO: if object, could use infer_dtype to preempt costly
+ # conversion if still non-comparable?
+ dtype = self._find_common_type_compat(target)
+
+ this = self.astype(dtype, copy=False)
+ that = target.astype(dtype, copy=False)
+ return this.get_indexer_non_unique(that)
+
+ # TODO: get_indexer has fastpaths for both Categorical-self and
+ # Categorical-target. Can we do something similar here?
+
+ # Note: _maybe_downcast_for_indexing ensures we never get here
+ # with MultiIndex self and non-Multi target
+ if self._is_multi and target._is_multi:
+ engine = self._engine
+ # Item "IndexEngine" of "Union[IndexEngine, ExtensionEngine]" has
+ # no attribute "_extract_level_codes"
+ tgt_values = engine._extract_level_codes(target) # type: ignore[union-attr]
+ else:
+ tgt_values = target._get_engine_target()
+
+ indexer, missing = self._engine.get_indexer_non_unique(tgt_values)
+ return ensure_platform_int(indexer), ensure_platform_int(missing)
+
+ @final
+ def get_indexer_for(self, target) -> npt.NDArray[np.intp]:
+ """
+ Guaranteed return of an indexer even when non-unique.
+
+ This dispatches to get_indexer or get_indexer_non_unique
+ as appropriate.
+
+ Parameters
+ ----------
+ target : Index
+ An iterable containing the values to be used for computing indexer.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ List of indices.
+
+ See Also
+ --------
+ Index.get_indexer : Computes indexer and mask for new index given
+ the current index.
+ Index.get_non_unique : Returns indexer and masks for new index given
+ the current index.
+
+ Examples
+ --------
+ >>> idx = pd.Index([np.nan, "var1", np.nan])
+ >>> idx.get_indexer_for([np.nan])
+ array([0, 2])
+ """
+ if self._index_as_unique:
+ return self.get_indexer(target)
+ indexer, _ = self.get_indexer_non_unique(target)
+ return indexer
+
+ def _get_indexer_strict(self, key, axis_name: str_t) -> tuple[Index, np.ndarray]:
+ """
+ Analogue to get_indexer that raises if any elements are missing.
+ """
+ keyarr = key
+ if not isinstance(keyarr, Index):
+ keyarr = com.asarray_tuplesafe(keyarr)
+
+ if self._index_as_unique:
+ indexer = self.get_indexer_for(keyarr)
+ keyarr = self.reindex(keyarr)[0]
+ else:
+ keyarr, indexer, new_indexer = self._reindex_non_unique(keyarr)
+
+ self._raise_if_missing(keyarr, indexer, axis_name)
+
+ keyarr = self.take(indexer)
+ if isinstance(key, Index):
+ # GH 42790 - Preserve name from an Index
+ keyarr.name = key.name
+ if lib.is_np_dtype(keyarr.dtype, "mM") or isinstance(
+ keyarr.dtype, DatetimeTZDtype
+ ):
+ # DTI/TDI.take can infer a freq in some cases when we dont want one
+ if isinstance(key, list) or (
+ isinstance(key, type(self))
+ # "Index" has no attribute "freq"
+ and key.freq is None # type: ignore[attr-defined]
+ ):
+ # error: "Index" has no attribute "_with_freq"; maybe "_with_infer"?
+ keyarr = keyarr._with_freq(None) # type: ignore[attr-defined]
+
+ return keyarr, indexer
+
+ def _raise_if_missing(self, key, indexer, axis_name: str_t) -> None:
+ """
+ Check that indexer can be used to return a result.
+
+ e.g. at least one element was found,
+ unless the list of keys was actually empty.
+
+ Parameters
+ ----------
+ key : list-like
+ Targeted labels (only used to show correct error message).
+ indexer: array-like of booleans
+ Indices corresponding to the key,
+ (with -1 indicating not found).
+ axis_name : str
+
+ Raises
+ ------
+ KeyError
+ If at least one key was requested but none was found.
+ """
+ if len(key) == 0:
+ return
+
+ # Count missing values
+ missing_mask = indexer < 0
+ nmissing = missing_mask.sum()
+
+ if nmissing:
+ if nmissing == len(indexer):
+ raise KeyError(f"None of [{key}] are in the [{axis_name}]")
+
+ not_found = list(ensure_index(key)[missing_mask.nonzero()[0]].unique())
+ raise KeyError(f"{not_found} not in index")
+
+ @overload
+ def _get_indexer_non_comparable(
+ self, target: Index, method, unique: Literal[True] = ...
+ ) -> npt.NDArray[np.intp]: ...
+
+ @overload
+ def _get_indexer_non_comparable(
+ self, target: Index, method, unique: Literal[False]
+ ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
+
+ @overload
+ def _get_indexer_non_comparable(
+ self, target: Index, method, unique: bool = True
+ ) -> npt.NDArray[np.intp] | tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
+
+ @final
+ def _get_indexer_non_comparable(
+ self, target: Index, method, unique: bool = True
+ ) -> npt.NDArray[np.intp] | tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ """
+ Called from get_indexer or get_indexer_non_unique when the target
+ is of a non-comparable dtype.
+
+ For get_indexer lookups with method=None, get_indexer is an _equality_
+ check, so non-comparable dtypes mean we will always have no matches.
+
+ For get_indexer lookups with a method, get_indexer is an _inequality_
+ check, so non-comparable dtypes mean we will always raise TypeError.
+
+ Parameters
+ ----------
+ target : Index
+ method : str or None
+ unique : bool, default True
+ * True if called from get_indexer.
+ * False if called from get_indexer_non_unique.
+
+ Raises
+ ------
+ TypeError
+ If doing an inequality check, i.e. method is not None.
+ """
+ if method is not None:
+ other_dtype = _unpack_nested_dtype(target)
+ raise TypeError(f"Cannot compare dtypes {self.dtype} and {other_dtype}")
+
+ no_matches = -1 * np.ones(target.shape, dtype=np.intp)
+ if unique:
+ # This is for get_indexer
+ return no_matches
+ else:
+ # This is for get_indexer_non_unique
+ missing = np.arange(len(target), dtype=np.intp)
+ return no_matches, missing
+
+ @property
+ def _index_as_unique(self) -> bool:
+ """
+ Whether we should treat this as unique for the sake of
+ get_indexer vs get_indexer_non_unique.
+
+ For IntervalIndex compat.
+ """
+ return self.is_unique
+
+ _requires_unique_msg = "Reindexing only valid with uniquely valued Index objects"
+
+ @final
+ def _maybe_downcast_for_indexing(self, other: Index) -> tuple[Index, Index]:
+ """
+ When dealing with an object-dtype Index and a non-object Index, see
+ if we can upcast the object-dtype one to improve performance.
+ """
+
+ if isinstance(self, ABCDatetimeIndex) and isinstance(other, ABCDatetimeIndex):
+ if (
+ self.tz is not None
+ and other.tz is not None
+ and not tz_compare(self.tz, other.tz)
+ ):
+ # standardize on UTC
+ return self.tz_convert("UTC"), other.tz_convert("UTC")
+
+ elif self.inferred_type == "date" and isinstance(other, ABCDatetimeIndex):
+ try:
+ return type(other)(self), other
+ except OutOfBoundsDatetime:
+ return self, other
+ elif self.inferred_type == "timedelta" and isinstance(other, ABCTimedeltaIndex):
+ # TODO: we dont have tests that get here
+ return type(other)(self), other
+
+ elif self.dtype.kind == "u" and other.dtype.kind == "i":
+ # GH#41873
+ if other.min() >= 0:
+ # lookup min as it may be cached
+ # TODO: may need itemsize check if we have non-64-bit Indexes
+ return self, other.astype(self.dtype)
+
+ elif self._is_multi and not other._is_multi:
+ try:
+ # "Type[Index]" has no attribute "from_tuples"
+ other = type(self).from_tuples(other) # type: ignore[attr-defined]
+ except (TypeError, ValueError):
+ # let's instead try with a straight Index
+ self = Index(self._values, copy=False)
+
+ if not is_object_dtype(self.dtype) and is_object_dtype(other.dtype):
+ # Reverse op so we dont need to re-implement on the subclasses
+ other, self = other._maybe_downcast_for_indexing(self)
+
+ return self, other
+
+ @final
+ def _find_common_type_compat(self, target) -> DtypeObj:
+ """
+ Implementation of find_common_type that adjusts for Index-specific
+ special cases.
+ """
+ target_dtype, _ = infer_dtype_from(target)
+
+ if isinstance(target, tuple):
+ # GH#54385
+ return np.dtype(object)
+
+ if using_string_dtype():
+ # special case: if left or right is a zero-length RangeIndex or
+ # Index[object], those can be created by the default empty constructors
+ # -> for that case ignore this dtype and always return the other
+ # (https://github.com/pandas-dev/pandas/pull/60797)
+ from pandas.core.indexes.range import RangeIndex
+
+ if len(self) == 0 and (
+ isinstance(self, RangeIndex) or self.dtype == np.object_
+ ):
+ return target_dtype
+ if (
+ isinstance(target, Index)
+ and len(target) == 0
+ and (isinstance(target, RangeIndex) or target_dtype == np.object_)
+ ):
+ return self.dtype
+
+ # special case: if one dtype is uint64 and the other a signed int, return object
+ # See https://github.com/pandas-dev/pandas/issues/26778 for discussion
+ # Now it's:
+ # * float | [u]int -> float
+ # * uint64 | signed int -> object
+ # We may change union(float | [u]int) to go to object.
+ if self.dtype == "uint64" or target_dtype == "uint64":
+ if is_signed_integer_dtype(self.dtype) or is_signed_integer_dtype(
+ target_dtype
+ ):
+ return _dtype_obj
+
+ dtype = find_result_type(self.dtype, target)
+ dtype = common_dtype_categorical_compat([self, target], dtype)
+ return dtype
+
+ @final
+ def _should_compare(self, other: Index) -> bool:
+ """
+ Check if `self == other` can ever have non-False entries.
+ """
+
+ # NB: we use inferred_type rather than is_bool_dtype to catch
+ # object_dtype_of_bool and categorical[object_dtype_of_bool] cases
+ if (
+ other.inferred_type == "boolean" and is_any_real_numeric_dtype(self.dtype)
+ ) or (
+ self.inferred_type == "boolean" and is_any_real_numeric_dtype(other.dtype)
+ ):
+ # GH#16877 Treat boolean labels passed to a numeric index as not
+ # found. Without this fix False and True would be treated as 0 and 1
+ # respectively.
+ return False
+
+ dtype = _unpack_nested_dtype(other)
+ return (
+ self._is_comparable_dtype(dtype)
+ or is_object_dtype(dtype)
+ or is_string_dtype(dtype)
+ )
+
+ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
+ """
+ Can we compare values of the given dtype to our own?
+ """
+ if self.dtype.kind == "b":
+ return dtype.kind == "b"
+ elif is_numeric_dtype(self.dtype):
+ return is_numeric_dtype(dtype)
+ # TODO: this was written assuming we only get here with object-dtype,
+ # which is no longer correct. Can we specialize for EA?
+ return True
+
+ @final
+ def groupby(self, values) -> PrettyDict[Hashable, Index]:
+ """
+ Group the index labels by a given array of values.
+
+ Parameters
+ ----------
+ values : array
+ Values used to determine the groups.
+
+ Returns
+ -------
+ dict
+ {group name -> group labels}
+ """
+ # TODO: if we are a MultiIndex, we can do better
+ # that converting to tuples
+ if isinstance(values, ABCMultiIndex):
+ values = values._values
+ values = Categorical(values)
+ result = values._reverse_indexer()
+
+ # map to the label
+ result = {k: self.take(v) for k, v in result.items()}
+
+ return PrettyDict(result)
+
+ def map(self, mapper, na_action: Literal["ignore"] | None = None):
+ """
+ Map values using an input mapping or function.
+
+ Parameters
+ ----------
+ mapper : function, dict, or Series
+ Mapping correspondence.
+ na_action : {None, 'ignore'}
+ If 'ignore', propagate NA values, without passing them to the
+ mapping correspondence.
+
+ Returns
+ -------
+ Union[Index, MultiIndex]
+ The output of the mapping function applied to the index.
+ If the function returns a tuple with more than one element
+ a MultiIndex will be returned.
+
+ See Also
+ --------
+ Index.where : Replace values where the condition is False.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx.map({1: "a", 2: "b", 3: "c"})
+ Index(['a', 'b', 'c'], dtype='str')
+
+ Using `map` with a function:
+
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx.map("I am a {}".format)
+ Index(['I am a 1', 'I am a 2', 'I am a 3'], dtype='str')
+
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.map(lambda x: x.upper())
+ Index(['A', 'B', 'C'], dtype='str')
+ """
+ from pandas.core.indexes.multi import MultiIndex
+
+ new_values = self._map_values(mapper, na_action=na_action)
+
+ # we can return a MultiIndex
+ if new_values.size and isinstance(new_values[0], tuple):
+ if isinstance(self, MultiIndex):
+ names = self.names
+ elif self.name:
+ names = [self.name] * len(new_values[0])
+ else:
+ names = None
+ return MultiIndex.from_tuples(new_values, names=names)
+
+ dtype = None
+ if not new_values.size:
+ # empty
+ dtype = self.dtype
+ elif isinstance(new_values, Categorical):
+ # cast_pointwise_result is unnecessary
+ dtype = new_values.dtype
+ else:
+ if isinstance(self, MultiIndex):
+ arr = self[:0].to_flat_index().array
+ else:
+ arr = self[:0].array
+ # e.g. if we are floating and new_values is all ints, then we
+ # don't want to cast back to floating. But if we are UInt64
+ # and new_values is all ints, we want to try.
+ new_values = arr._cast_pointwise_result(new_values)
+ dtype = new_values.dtype
+ return Index(new_values, dtype=dtype, copy=False, name=self.name)
+
+ # TODO: De-duplicate with map, xref GH#32349
+ @final
+ def _transform_index(self, func, *, level=None) -> Index:
+ """
+ Apply function to all values found in index.
+
+ This includes transforming multiindex entries separately.
+ Only apply function to one level of the MultiIndex if level is specified.
+ """
+ if isinstance(self, ABCMultiIndex):
+ values = [
+ (
+ self.get_level_values(i).map(func)
+ if i == level or level is None
+ else self.get_level_values(i)
+ )
+ for i in range(self.nlevels)
+ ]
+ return type(self).from_arrays(values)
+ else:
+ items = [func(x) for x in self]
+ return Index(items, name=self.name, tupleize_cols=False)
+
+ def isin(self, values, level: str_t | int | None = None) -> npt.NDArray[np.bool_]:
+ """
+ Return a boolean array where the index values are in `values`.
+
+ Compute boolean array of whether each index value is found in the
+ passed set of values. The length of the returned boolean array matches
+ the length of the index.
+
+ Parameters
+ ----------
+ values : set or list-like
+ Sought values.
+ level : str or int, optional
+ Name or position of the index level to use (if the index is a
+ `MultiIndex`).
+
+ Returns
+ -------
+ np.ndarray[bool]
+ NumPy array of boolean values.
+
+ See Also
+ --------
+ Series.isin : Same for Series.
+ DataFrame.isin : Same method for DataFrames.
+
+ Notes
+ -----
+ In the case of `MultiIndex` you must either specify `values` as a
+ list-like object containing tuples that are the same length as the
+ number of levels, or specify `level`. Otherwise it will raise a
+ ``ValueError``.
+
+ If `level` is specified:
+
+ - if it is the name of one *and only one* index level, use that level;
+ - otherwise it should be a number indicating level position.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx
+ Index([1, 2, 3], dtype='int64')
+
+ Check whether each index value in a list of values.
+
+ >>> idx.isin([1, 4])
+ array([ True, False, False])
+
+ >>> midx = pd.MultiIndex.from_arrays(
+ ... [[1, 2, 3], ["red", "blue", "green"]], names=["number", "color"]
+ ... )
+ >>> midx
+ MultiIndex([(1, 'red'),
+ (2, 'blue'),
+ (3, 'green')],
+ names=['number', 'color'])
+
+ Check whether the strings in the 'color' level of the MultiIndex
+ are in a list of colors.
+
+ >>> midx.isin(["red", "orange", "yellow"], level="color")
+ array([ True, False, False])
+
+ To check across the levels of a MultiIndex, pass a list of tuples:
+
+ >>> midx.isin([(1, "red"), (3, "red")])
+ array([ True, False, False])
+ """
+ if level is not None:
+ self._validate_index_level(level)
+ return algos.isin(self._values, values)
+
+ def _get_string_slice(self, key: str_t):
+ # this is for partial string indexing,
+ # overridden in DatetimeIndex, TimedeltaIndex and PeriodIndex
+ raise NotImplementedError
+
+ def slice_indexer(
+ self,
+ start: Hashable | None = None,
+ end: Hashable | None = None,
+ step: int | None = None,
+ ) -> slice:
+ """
+ Compute the slice indexer for input labels and step.
+
+ Index needs to be ordered and unique.
+
+ Parameters
+ ----------
+ start : label, default None
+ If None, defaults to the beginning.
+ end : label, default None
+ If None, defaults to the end.
+ step : int, default None
+ If None, defaults to 1.
+
+ Returns
+ -------
+ slice
+ A slice object.
+
+ Raises
+ ------
+ KeyError : If key does not exist, or key is not unique and index is
+ not ordered.
+
+ See Also
+ --------
+ Index.slice_locs : Computes slice locations for input labels.
+ Index.get_slice_bound : Retrieves slice bound that corresponds to given label.
+
+ Notes
+ -----
+ This function assumes that the data is sorted, so use at your own peril.
+
+ Examples
+ --------
+ This is a method on all index types. For example you can do:
+
+ >>> idx = pd.Index(list("abcd"))
+ >>> idx.slice_indexer(start="b", end="c")
+ slice(1, 3, None)
+
+ >>> idx = pd.MultiIndex.from_arrays([list("abcd"), list("efgh")])
+ >>> idx.slice_indexer(start="b", end=("c", "g"))
+ slice(1, 3, None)
+ """
+ start_slice, end_slice = self.slice_locs(start, end, step=step)
+
+ # return a slice
+ if not is_scalar(start_slice):
+ raise AssertionError("Start slice bound is non-scalar")
+ if not is_scalar(end_slice):
+ raise AssertionError("End slice bound is non-scalar")
+
+ return slice(start_slice, end_slice, step)
+
+ def _maybe_cast_indexer(self, key):
+ """
+ If we have a float key and are not a floating index, then try to cast
+ to an int if equivalent.
+ """
+ if (
+ is_float(key)
+ and np.isnan(key)
+ and isinstance(self.dtype, FloatingDtype)
+ and is_nan_na()
+ ):
+ # TODO: better place to do this?
+ key = self.dtype.na_value
+ return key
+
+ def _maybe_cast_listlike_indexer(self, target) -> Index:
+ """
+ Analogue to maybe_cast_indexer for get_indexer instead of get_loc.
+ """
+ target_index = ensure_index(target)
+ if (
+ not hasattr(target, "dtype")
+ and self.dtype == object
+ and target_index.dtype == "string"
+ ):
+ # If we started with a list-like, avoid inference to string dtype if self
+ # is object dtype (coercing to string dtype will alter the missing values)
+ target_index = Index(target, dtype=self.dtype)
+ elif (
+ not hasattr(target, "dtype")
+ and isinstance(self.dtype, StringDtype)
+ and self.dtype.na_value is np.nan
+ and using_string_dtype()
+ ):
+ # Fill missing values to ensure consistent missing value representation
+ target_index = target_index.fillna(np.nan)
+ return target_index
+
+ @final
+ def _validate_indexer(
+ self,
+ form: Literal["positional", "slice"],
+ key,
+ kind: Literal["getitem", "iloc"],
+ ) -> None:
+ """
+ If we are positional indexer, validate that we have appropriate
+ typed bounds must be an integer.
+ """
+ if not lib.is_int_or_none(key):
+ self._raise_invalid_indexer(form, key)
+
+ def _maybe_cast_slice_bound(self, label, side: str_t):
+ """
+ This function should be overloaded in subclasses that allow non-trivial
+ casting on label-slice bounds, e.g. datetime-like indices allowing
+ strings containing formatted datetimes.
+
+ Parameters
+ ----------
+ label : object
+ side : {'left', 'right'}
+
+ Returns
+ -------
+ label : object
+
+ Notes
+ -----
+ Value of `side` parameter should be validated in caller.
+ """
+
+ # We are a plain index here (sub-class override this method if they
+ # wish to have special treatment for floats/ints, e.g. datetimelike Indexes
+
+ if is_numeric_dtype(self.dtype):
+ return self._maybe_cast_indexer(label)
+
+ # reject them, if index does not contain label
+ if (is_float(label) or is_integer(label)) and label not in self:
+ self._raise_invalid_indexer("slice", label)
+
+ return label
+
+ def _searchsorted_monotonic(self, label, side: Literal["left", "right"] = "left"):
+ if self.is_monotonic_increasing:
+ return self.searchsorted(label, side=side)
+ elif self.is_monotonic_decreasing:
+ # np.searchsorted expects ascending sort order, have to reverse
+ # everything for it to work (element ordering, search side and
+ # resulting value).
+ pos = self[::-1].searchsorted(
+ label, side="right" if side == "left" else "left"
+ )
+ return maybe_unbox_numpy_scalar(len(self) - pos)
+
+ raise ValueError("index must be monotonic increasing or decreasing")
+
+ def get_slice_bound(self, label, side: Literal["left", "right"]) -> int:
+ """
+ Calculate slice bound that corresponds to given label.
+
+ Returns leftmost (one-past-the-rightmost if ``side=='right'``) position
+ of given label.
+
+ Parameters
+ ----------
+ label : object
+ The label for which to calculate the slice bound.
+ side : {'left', 'right'}
+ if 'left' return leftmost position of given label.
+ if 'right' return one-past-the-rightmost position of given label.
+
+ Returns
+ -------
+ int
+ Index of label.
+
+ See Also
+ --------
+ Index.get_loc : Get integer location, slice or boolean mask for requested
+ label.
+
+ Examples
+ --------
+ >>> idx = pd.RangeIndex(5)
+ >>> idx.get_slice_bound(3, "left")
+ 3
+
+ >>> idx.get_slice_bound(3, "right")
+ 4
+
+ If ``label`` is non-unique in the index, an error will be raised.
+
+ >>> idx_duplicate = pd.Index(["a", "b", "a", "c", "d"])
+ >>> idx_duplicate.get_slice_bound("a", "left")
+ Traceback (most recent call last):
+ KeyError: Cannot get left slice bound for non-unique label: 'a'
+ """
+
+ if side not in ("left", "right"):
+ raise ValueError(
+ "Invalid value for side kwarg, must be either "
+ f"'left' or 'right': {side}"
+ )
+
+ original_label = label
+
+ # For datetime indices label may be a string that has to be converted
+ # to datetime boundary according to its resolution.
+ label = self._maybe_cast_slice_bound(label, side)
+
+ # we need to look up the label
+ try:
+ slc = self.get_loc(label)
+ except KeyError:
+ try:
+ return self._searchsorted_monotonic(label, side)
+ except ValueError:
+ raise KeyError(
+ f"Cannot get {side} slice bound for non-monotonic index "
+ f"with a missing label {original_label!r}. "
+ "Either sort the index or specify an existing label."
+ ) from None
+
+ if isinstance(slc, np.ndarray):
+ # get_loc may return a boolean array, which
+ # is OK as long as they are representable by a slice.
+ assert is_bool_dtype(slc.dtype)
+ slc = lib.maybe_booleans_to_slice(slc.view("u1"))
+ if isinstance(slc, np.ndarray):
+ raise KeyError(
+ f"Cannot get {side} slice bound for non-unique "
+ f"label: {original_label!r}"
+ )
+
+ if isinstance(slc, slice):
+ if side == "left":
+ return slc.start
+ else:
+ return slc.stop
+ elif side == "right":
+ return slc + 1
+ else:
+ return slc
+
+ def slice_locs(
+ self,
+ start: SliceType = None,
+ end: SliceType = None,
+ step: int | None = None,
+ ) -> tuple[int, int]:
+ """
+ Compute slice locations for input labels.
+
+ Parameters
+ ----------
+ start : label, default None
+ If None, defaults to the beginning.
+ end : label, default None
+ If None, defaults to the end.
+ step : int, defaults None
+ If None, defaults to 1.
+
+ Returns
+ -------
+ tuple[int, int]
+ Returns a tuple of two integers representing the slice locations for the
+ input labels within the index.
+
+ See Also
+ --------
+ Index.get_loc : Get location for a single label.
+
+ Notes
+ -----
+ This method only works if the index is monotonic or unique.
+
+ Examples
+ --------
+ >>> idx = pd.Index(list("abcd"))
+ >>> idx.slice_locs(start="b", end="c")
+ (1, 3)
+
+ >>> idx = pd.Index(list("bcde"))
+ >>> idx.slice_locs(start="a", end="c")
+ (0, 2)
+ """
+ inc = step is None or step >= 0
+
+ if not inc:
+ # If it's a reverse slice, temporarily swap bounds.
+ start, end = end, start
+
+ # GH 16785: If start and end happen to be date strings with UTC offsets
+ # attempt to parse and check that the offsets are the same
+ if isinstance(start, (str, datetime)) and isinstance(end, (str, datetime)):
+ try:
+ ts_start = Timestamp(start)
+ ts_end = Timestamp(end)
+ except (ValueError, TypeError):
+ pass
+ else:
+ if not tz_compare(ts_start.tzinfo, ts_end.tzinfo):
+ raise ValueError("Both dates must have the same UTC offset")
+
+ start_slice = None
+ if start is not None:
+ start_slice = self.get_slice_bound(start, "left")
+ if start_slice is None:
+ start_slice = 0
+
+ end_slice = None
+ if end is not None:
+ end_slice = self.get_slice_bound(end, "right")
+ if end_slice is None:
+ end_slice = len(self)
+
+ if not inc:
+ # Bounds at this moment are swapped, swap them back and shift by 1.
+ #
+ # slice_locs('B', 'A', step=-1): s='B', e='A'
+ #
+ # s='A' e='B'
+ # AFTER SWAP: | |
+ # v ------------------> V
+ # -----------------------------------
+ # | | |A|A|A|A| | | | | |B|B| | | | |
+ # -----------------------------------
+ # ^ <------------------ ^
+ # SHOULD BE: | |
+ # end=s-1 start=e-1
+ #
+ end_slice, start_slice = start_slice - 1, end_slice - 1
+
+ # i == -1 triggers ``len(self) + i`` selection that points to the
+ # last element, not before-the-first one, subtracting len(self)
+ # compensates that.
+ if end_slice == -1:
+ end_slice -= len(self)
+ if start_slice == -1:
+ start_slice -= len(self)
+
+ start_slice = maybe_unbox_numpy_scalar(start_slice)
+ end_slice = maybe_unbox_numpy_scalar(end_slice)
+ return start_slice, end_slice
+
+ def delete(
+ self, loc: int | np.integer | list[int] | npt.NDArray[np.integer]
+ ) -> Self:
+ """
+ Make new Index with passed location(-s) deleted.
+
+ Parameters
+ ----------
+ loc : int or list of int
+ Location of item(-s) which will be deleted.
+ Use a list of locations to delete more than one value at the same time.
+
+ Returns
+ -------
+ Index
+ Will be same type as self, except for RangeIndex.
+
+ See Also
+ --------
+ numpy.delete : Delete any rows and column from NumPy array (ndarray).
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.delete(1)
+ Index(['a', 'c'], dtype='str')
+
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.delete([0, 2])
+ Index(['b'], dtype='str')
+ """
+ values = self._values
+ res_values: ArrayLike
+ if isinstance(values, np.ndarray):
+ # TODO(__array_function__): special casing will be unnecessary
+ res_values = np.delete(values, loc)
+ else:
+ res_values = values.delete(loc)
+
+ # _constructor so RangeIndex-> Index with an int64 dtype
+ return self._constructor._simple_new(res_values, name=self.name)
+
+ def insert(self, loc: int, item) -> Index:
+ """
+ Make new Index inserting new item at location.
+
+ Follows Python numpy.insert semantics for negative values.
+
+ Parameters
+ ----------
+ loc : int
+ The integer location where the new item will be inserted.
+ item : object
+ The new item to be inserted into the Index.
+
+ Returns
+ -------
+ Index
+ Returns a new Index object resulting from inserting the specified item at
+ the specified location within the original Index.
+
+ See Also
+ --------
+ Index.append : Append a collection of Indexes together.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.insert(1, "x")
+ Index(['a', 'x', 'b', 'c'], dtype='str')
+ """
+ item = lib.item_from_zerodim(item)
+ if is_valid_na_for_dtype(item, self.dtype) and self.dtype != object:
+ item = self._na_value
+
+ arr = self._values
+
+ if using_string_dtype() and len(self) == 0 and self.dtype == np.object_:
+ # special case: if we are an empty object-dtype Index, also
+ # take into account the inserted item for the resulting dtype
+ # (https://github.com/pandas-dev/pandas/pull/60797)
+ dtype = self._find_common_type_compat(item)
+ if dtype != self.dtype:
+ return self.astype(dtype).insert(loc, item)
+
+ try:
+ if isinstance(arr, ExtensionArray):
+ res_values = arr.insert(loc, item)
+ return type(self)._simple_new(res_values, name=self.name)
+ else:
+ item = self._validate_fill_value(item)
+ except (TypeError, ValueError, LossySetitemError):
+ # e.g. trying to insert an integer into a DatetimeIndex
+ # We cannot keep the same dtype, so cast to the (often object)
+ # minimal shared dtype before doing the insert.
+ dtype = self._find_common_type_compat(item)
+ if dtype == self.dtype:
+ # EA's might run into recursion errors if loc is invalid
+ raise
+ return self.astype(dtype).insert(loc, item)
+
+ if arr.dtype != object or not isinstance(
+ item, (tuple, np.datetime64, np.timedelta64)
+ ):
+ # with object-dtype we need to worry about numpy incorrectly casting
+ # dt64/td64 to integer, also about treating tuples as sequences
+ # special-casing dt64/td64 https://github.com/numpy/numpy/issues/12550
+ casted = arr.dtype.type(item)
+ new_values = np.insert(arr, loc, casted)
+
+ else:
+ # error: No overload variant of "insert" matches argument types
+ # "ndarray[Any, Any]", "int", "None"
+ new_values = np.insert(arr, loc, None) # type: ignore[call-overload]
+ loc = loc if loc >= 0 else loc - 1
+ new_values[loc] = item
+
+ # GH#51363 stopped doing dtype inference here
+ out = Index(new_values, dtype=new_values.dtype, name=self.name, copy=False)
+ return out
+
+ def drop(
+ self,
+ labels: Index | np.ndarray | Iterable[Hashable],
+ errors: IgnoreRaise = "raise",
+ ) -> Index:
+ """
+ Make new Index with passed list of labels deleted.
+
+ Parameters
+ ----------
+ labels : array-like or scalar
+ Array-like object or a scalar value, representing the labels to be removed
+ from the Index.
+ errors : {'ignore', 'raise'}, default 'raise'
+ If 'ignore', suppress error and existing labels are dropped.
+
+ Returns
+ -------
+ Index
+ Will be same type as self, except for RangeIndex.
+
+ Raises
+ ------
+ KeyError
+ If not all of the labels are found in the selected axis
+
+ See Also
+ --------
+ Index.dropna : Return Index without NA/NaN values.
+ Index.drop_duplicates : Return Index with duplicate values removed.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.drop(["a"])
+ Index(['b', 'c'], dtype='str')
+ """
+ if not isinstance(labels, Index):
+ # avoid materializing e.g. RangeIndex
+ arr_dtype = "object" if self.dtype == "object" else None
+ labels = com.index_labels_to_array(labels, dtype=arr_dtype)
+
+ indexer = self.get_indexer_for(labels)
+ mask = indexer == -1
+ if mask.any():
+ if errors != "ignore":
+ raise KeyError(f"{labels[mask].tolist()} not found in axis")
+ indexer = indexer[~mask]
+ return self.delete(indexer)
+
+ @final
+ def infer_objects(self, copy: bool = True) -> Index:
+ """
+ If we have an object dtype, try to infer a non-object dtype.
+
+ Parameters
+ ----------
+ copy : bool, default True
+ Whether to make a copy in cases where no inference occurs.
+
+ Returns
+ -------
+ Index
+ An Index with a new dtype if the dtype was inferred
+ or a shallow copy if the dtype could not be inferred.
+
+ See Also
+ --------
+ Index.inferred_type: Return a string of the type inferred from the values.
+
+ Examples
+ --------
+ >>> pd.Index(["a", 1]).infer_objects()
+ Index(['a', 1], dtype='object')
+ >>> pd.Index([1, 2], dtype="object").infer_objects()
+ Index([1, 2], dtype='int64')
+ """
+ if self._is_multi:
+ raise NotImplementedError(
+ "infer_objects is not implemented for MultiIndex. "
+ "Use index.to_frame().infer_objects() instead."
+ )
+ if self.dtype != object:
+ return self.copy() if copy else self
+
+ values = self._values
+ values = cast("npt.NDArray[np.object_]", values)
+ res_values = lib.maybe_convert_objects(
+ values,
+ convert_non_numeric=True,
+ )
+ if copy and res_values is values:
+ return self.copy()
+ result = Index(res_values, name=self.name, copy=False)
+ if not copy and res_values is values and self._references is not None:
+ result._references = self._references
+ result._references.add_index_reference(result)
+ return result
+
+ @final
+ def diff(self, periods: int = 1) -> Index:
+ """
+ Computes the difference between consecutive values in the Index object.
+
+ If periods is greater than 1, computes the difference between values that
+ are `periods` number of positions apart.
+
+ Parameters
+ ----------
+ periods : int, optional
+ The number of positions between the current and previous
+ value to compute the difference with. Default is 1.
+
+ Returns
+ -------
+ Index
+ A new Index object with the computed differences.
+
+ Examples
+ --------
+ >>> import pandas as pd
+ >>> idx = pd.Index([10, 20, 30, 40, 50])
+ >>> idx.diff()
+ Index([nan, 10.0, 10.0, 10.0, 10.0], dtype='float64')
+
+ """
+ return Index(self.to_series().diff(periods))
+
+ def round(self, decimals: int = 0) -> Self:
+ """
+ Round each value in the Index to the given number of decimals.
+
+ Parameters
+ ----------
+ decimals : int, optional
+ Number of decimal places to round to. If decimals is negative,
+ it specifies the number of positions to the left of the decimal point.
+
+ Returns
+ -------
+ Index
+ A new Index with the rounded values.
+
+ Examples
+ --------
+ >>> import pandas as pd
+ >>> idx = pd.Index([10.1234, 20.5678, 30.9123, 40.4567, 50.7890])
+ >>> idx.round(decimals=2)
+ Index([10.12, 20.57, 30.91, 40.46, 50.79], dtype='float64')
+
+ """
+ return self._constructor(self.to_series().round(decimals))
+
+ # --------------------------------------------------------------------
+ # Generated Arithmetic, Comparison, and Unary Methods
+
+ def _cmp_method(self, other, op):
+ """
+ Wrapper used to dispatch comparison operations.
+ """
+ if self.is_(other):
+ # fastpath
+ if op in {operator.eq, operator.le, operator.ge}:
+ arr = np.ones(len(self), dtype=bool)
+ if self._can_hold_na and not isinstance(self, ABCMultiIndex):
+ # TODO: should set MultiIndex._can_hold_na = False?
+ arr[self.isna()] = False
+ return arr
+ elif op is operator.ne:
+ arr = np.zeros(len(self), dtype=bool)
+ if self._can_hold_na and not isinstance(self, ABCMultiIndex):
+ arr[self.isna()] = True
+ return arr
+
+ if isinstance(other, (np.ndarray, Index, ABCSeries, ExtensionArray)) and len(
+ self
+ ) != len(other):
+ raise ValueError("Lengths must match to compare")
+
+ if not isinstance(other, ABCMultiIndex):
+ other = extract_array(other, extract_numpy=True)
+ else:
+ other = np.asarray(other)
+
+ result = ops.comparison_op(self._values, other, op)
+
+ return result
+
+ @final
+ def _logical_method(self, other, op):
+ res_name = ops.get_op_result_name(self, other)
+
+ lvalues = self._values
+ rvalues = extract_array(other, extract_numpy=True, extract_range=True)
+
+ res_values = ops.logical_op(lvalues, rvalues, op)
+ return self._construct_result(res_values, name=res_name, other=other)
+
+ @final
+ def _construct_result(self, result, name, other):
+ if isinstance(result, tuple):
+ return (
+ Index(result[0], name=name, dtype=result[0].dtype, copy=False),
+ Index(result[1], name=name, dtype=result[1].dtype, copy=False),
+ )
+ return Index(result, name=name, dtype=result.dtype, copy=False)
+
+ def _arith_method(self, other, op):
+ if (
+ isinstance(other, Index)
+ and is_object_dtype(other.dtype)
+ and type(other) is not Index
+ ):
+ # We return NotImplemented for object-dtype index *subclasses* so they have
+ # a chance to implement ops before we unwrap them.
+ # See https://github.com/pandas-dev/pandas/issues/31109
+ return NotImplemented
+
+ return super()._arith_method(other, op)
+
+ @final
+ def _unary_method(self, op):
+ result = op(self._values)
+ return Index(result, name=self.name, copy=False)
+
+ def __abs__(self) -> Index:
+ return self._unary_method(operator.abs)
+
+ def __neg__(self) -> Index:
+ return self._unary_method(operator.neg)
+
+ def __pos__(self) -> Index:
+ return self._unary_method(operator.pos)
+
+ def __invert__(self) -> Index:
+ # GH#8875
+ return self._unary_method(operator.inv)
+
+ # --------------------------------------------------------------------
+ # Reductions
+
+ def any(self, *args, **kwargs):
+ """
+ Return whether any element is Truthy.
+
+ Parameters
+ ----------
+ *args
+ Required for compatibility with numpy.
+ **kwargs
+ Required for compatibility with numpy.
+
+ Returns
+ -------
+ bool or array-like (if axis is specified)
+ A single element array-like may be converted to bool.
+
+ See Also
+ --------
+ Index.all : Return whether all elements are True.
+ Series.all : Return whether all elements are True.
+
+ Notes
+ -----
+ Not a Number (NaN), positive infinity and negative infinity
+ evaluate to True because these are not equal to zero.
+
+ Examples
+ --------
+ >>> index = pd.Index([0, 1, 2])
+ >>> index.any()
+ True
+
+ >>> index = pd.Index([0, 0, 0])
+ >>> index.any()
+ False
+ """
+ nv.validate_any(args, kwargs)
+ self._maybe_disable_logical_methods("any")
+ vals = self._values
+ if not isinstance(vals, np.ndarray):
+ # i.e. EA, call _reduce instead of "any" to get TypeError instead
+ # of AttributeError
+ return vals._reduce("any")
+ return maybe_unbox_numpy_scalar(np.any(vals))
+
+ def all(self, *args, **kwargs):
+ """
+ Return whether all elements are Truthy.
+
+ Parameters
+ ----------
+ *args
+ Required for compatibility with numpy.
+ **kwargs
+ Required for compatibility with numpy.
+
+ Returns
+ -------
+ bool or array-like (if axis is specified)
+ A single element array-like may be converted to bool.
+
+ See Also
+ --------
+ Index.any : Return whether any element in an Index is True.
+ Series.any : Return whether any element in a Series is True.
+ Series.all : Return whether all elements in a Series are True.
+
+ Notes
+ -----
+ Not a Number (NaN), positive infinity and negative infinity
+ evaluate to True because these are not equal to zero.
+
+ Examples
+ --------
+ True, because nonzero integers are considered True.
+
+ >>> pd.Index([1, 2, 3]).all()
+ True
+
+ False, because ``0`` is considered False.
+
+ >>> pd.Index([0, 1, 2]).all()
+ False
+ """
+ nv.validate_all(args, kwargs)
+ self._maybe_disable_logical_methods("all")
+ vals = self._values
+ if not isinstance(vals, np.ndarray):
+ # i.e. EA, call _reduce instead of "all" to get TypeError instead
+ # of AttributeError
+ return vals._reduce("all")
+ return maybe_unbox_numpy_scalar(np.all(vals))
+
+ @final
+ def _maybe_disable_logical_methods(self, opname: str_t) -> None:
+ """
+ raise if this Index subclass does not support any or all.
+ """
+ if isinstance(self, ABCMultiIndex):
+ raise TypeError(f"cannot perform {opname} with {type(self).__name__}")
+
+ def argmin(
+ self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs
+ ) -> int:
+ """
+ Return int position of the smallest value in the Index.
+
+ If the minimum is achieved in multiple locations,
+ the first row position is returned.
+
+ Parameters
+ ----------
+ axis : None
+ Unused. Parameter needed for compatibility with DataFrame.
+ skipna : bool, default True
+ Exclude NA/null values. If the entire Series is NA, or if ``skipna=False``
+ and there is an NA value, this method will raise a ``ValueError``.
+ *args, **kwargs
+ Additional arguments and keywords for compatibility with NumPy.
+
+ Returns
+ -------
+ int
+ Row position of the minimum value.
+
+ See Also
+ --------
+ Series.argmin : Return position of the minimum value.
+ Series.argmax : Return position of the maximum value.
+ numpy.ndarray.argmin : Equivalent method for numpy arrays.
+ Series.idxmin : Return index label of the minimum values.
+ Series.idxmax : Return index label of the maximum values.
+
+ Examples
+ --------
+ Consider dataset containing cereal calories
+
+ >>> idx = pd.Index([100.0, 110.0, 120.0, 110.0])
+ >>> idx
+ Index([100.0, 110.0, 120.0, 110.0], dtype='float64')
+
+ >>> idx.argmax()
+ np.int64(2)
+ >>> idx.argmin()
+ np.int64(0)
+
+ The maximum cereal calories is the third element and
+ the minimum cereal calories is the first element,
+ since index is zero-indexed.
+ """
+ nv.validate_argmin(args, kwargs)
+ nv.validate_minmax_axis(axis)
+
+ if not self._is_multi and self.hasnans:
+ if not skipna:
+ raise ValueError("Encountered an NA value with skipna=False")
+ elif self._isnan.all():
+ raise ValueError("Encountered all NA values")
+
+ return super().argmin(skipna=skipna)
+
+ def argmax(
+ self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs
+ ) -> int:
+ """
+ Return int position of the largest value in the Index.
+
+ If the maximum is achieved in multiple locations,
+ the first row position is returned.
+
+ Parameters
+ ----------
+ axis : None
+ Unused. Parameter needed for compatibility with DataFrame.
+ skipna : bool, default True
+ Exclude NA/null values. If the entire Series is NA, or if ``skipna=False``
+ and there is an NA value, this method will raise a ``ValueError``.
+ *args, **kwargs
+ Additional arguments and keywords for compatibility with NumPy.
+
+ Returns
+ -------
+ int
+ Row position of the maximum value.
+
+ See Also
+ --------
+ Series.argmax : Return position of the maximum value.
+ Series.argmin : Return position of the minimum value.
+ numpy.ndarray.argmax : Equivalent method for numpy arrays.
+ Series.idxmax : Return index label of the maximum values.
+ Series.idxmin : Return index label of the minimum values.
+
+ Examples
+ --------
+ Consider dataset containing cereal calories
+
+ >>> idx = pd.Index([100.0, 110.0, 120.0, 110.0])
+ >>> idx
+ Index([100.0, 110.0, 120.0, 110.0], dtype='float64')
+
+ >>> idx.argmax()
+ np.int64(2)
+ >>> idx.argmin()
+ np.int64(0)
+
+ The maximum cereal calories is the third element and
+ the minimum cereal calories is the first element,
+ since index is zero-indexed.
+ """
+ nv.validate_argmax(args, kwargs)
+ nv.validate_minmax_axis(axis)
+
+ if not self._is_multi and self.hasnans:
+ if not skipna:
+ raise ValueError("Encountered an NA value with skipna=False")
+ elif self._isnan.all():
+ raise ValueError("Encountered all NA values")
+ return super().argmax(skipna=skipna)
+
+ def min(self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs):
+ """
+ Return the minimum value of the Index.
+
+ Parameters
+ ----------
+ axis : {None}
+ Dummy argument for consistency with Series.
+ skipna : bool, default True
+ Exclude NA/null values when showing the result.
+ *args, **kwargs
+ Additional arguments and keywords for compatibility with NumPy.
+
+ Returns
+ -------
+ scalar
+ Minimum value.
+
+ See Also
+ --------
+ Index.max : Return the maximum value of the object.
+ Series.min : Return the minimum value in a Series.
+ DataFrame.min : Return the minimum values in a DataFrame.
+
+ Examples
+ --------
+ >>> idx = pd.Index([3, 2, 1])
+ >>> idx.min()
+ 1
+
+ >>> idx = pd.Index(["c", "b", "a"])
+ >>> idx.min()
+ 'a'
+
+ For a MultiIndex, the minimum is determined lexicographically.
+
+ >>> idx = pd.MultiIndex.from_product([("a", "b"), (2, 1)])
+ >>> idx.min()
+ ('a', 1)
+ """
+ nv.validate_min(args, kwargs)
+ nv.validate_minmax_axis(axis)
+
+ if not len(self):
+ return self._na_value
+
+ if len(self) and self.is_monotonic_increasing:
+ # quick check
+ first = self[0]
+ if not isna(first):
+ return maybe_unbox_numpy_scalar(first)
+
+ if not self._is_multi and self.hasnans:
+ # Take advantage of cache
+ mask = self._isnan
+ if not skipna or mask.all():
+ return self._na_value
+
+ if not self._is_multi and not isinstance(self._values, np.ndarray):
+ return self._values._reduce(name="min", skipna=skipna)
+
+ return maybe_unbox_numpy_scalar(nanops.nanmin(self._values, skipna=skipna))
+
+ def max(self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs):
+ """
+ Return the maximum value of the Index.
+
+ Parameters
+ ----------
+ axis : int, optional
+ For compatibility with NumPy. Only 0 or None are allowed.
+ skipna : bool, default True
+ Exclude NA/null values when showing the result.
+ *args, **kwargs
+ Additional arguments and keywords for compatibility with NumPy.
+
+ Returns
+ -------
+ scalar
+ Maximum value.
+
+ See Also
+ --------
+ Index.min : Return the minimum value in an Index.
+ Series.max : Return the maximum value in a Series.
+ DataFrame.max : Return the maximum values in a DataFrame.
+
+ Examples
+ --------
+ >>> idx = pd.Index([3, 2, 1])
+ >>> idx.max()
+ 3
+
+ >>> idx = pd.Index(["c", "b", "a"])
+ >>> idx.max()
+ 'c'
+
+ For a MultiIndex, the maximum is determined lexicographically.
+
+ >>> idx = pd.MultiIndex.from_product([("a", "b"), (2, 1)])
+ >>> idx.max()
+ ('b', 2)
+ """
+
+ nv.validate_max(args, kwargs)
+ nv.validate_minmax_axis(axis)
+
+ if not len(self):
+ return self._na_value
+
+ if len(self) and self.is_monotonic_increasing:
+ # quick check
+ last = self[-1]
+ if not isna(last):
+ return maybe_unbox_numpy_scalar(last)
+
+ if not self._is_multi and self.hasnans:
+ # Take advantage of cache
+ mask = self._isnan
+ if not skipna or mask.all():
+ return maybe_unbox_numpy_scalar(self._na_value)
+
+ if not self._is_multi and not isinstance(self._values, np.ndarray):
+ return self._values._reduce(name="max", skipna=skipna)
+
+ return maybe_unbox_numpy_scalar(nanops.nanmax(self._values, skipna=skipna))
+
+ # --------------------------------------------------------------------
+
+ @final
+ @property
+ def shape(self) -> Shape:
+ """
+ Return a tuple of the shape of the underlying data.
+
+ See Also
+ --------
+ Index.size: Return the number of elements in the underlying data.
+ Index.ndim: Number of dimensions of the underlying data, by definition 1.
+ Index.dtype: Return the dtype object of the underlying data.
+ Index.values: Return an array representing the data in the Index.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx
+ Index([1, 2, 3], dtype='int64')
+ >>> idx.shape
+ (3,)
+ """
+ # See GH#27775, GH#27384 for history/reasoning in how this is defined.
+ return (len(self),)
+
+
+def maybe_sequence_to_range(sequence) -> Any | range:
+ """
+ Convert a 1D, non-pandas sequence to a range if possible.
+
+ Returns the input if not possible.
+
+ Parameters
+ ----------
+ sequence : 1D sequence
+ names : sequence of str
+
+ Returns
+ -------
+ Any : input or range
+ """
+ if isinstance(sequence, (range, ExtensionArray)):
+ return sequence
+ elif len(sequence) == 1 or lib.infer_dtype(sequence, skipna=False) != "integer":
+ return sequence
+ elif isinstance(sequence, (ABCSeries, Index)) and not (
+ isinstance(sequence.dtype, np.dtype) and sequence.dtype.kind == "i"
+ ):
+ return sequence
+ if len(sequence) == 0:
+ return range(0)
+ try:
+ np_sequence = np.asarray(sequence, dtype=np.int64)
+ except OverflowError:
+ return sequence
+ diff = np_sequence[1] - np_sequence[0]
+ if diff == 0:
+ return sequence
+ elif len(sequence) == 2 or lib.is_sequence_range(np_sequence, diff):
+ return range(np_sequence[0], np_sequence[-1] + diff, diff)
+ else:
+ return sequence
+
+
+def ensure_index_from_sequences(sequences, names=None) -> Index:
+ """
+ Construct an index from sequences of data.
+
+ A single sequence returns an Index. Many sequences returns a
+ MultiIndex.
+
+ Parameters
+ ----------
+ sequences : sequence of sequences
+ names : sequence of str
+
+ Returns
+ -------
+ index : Index or MultiIndex
+
+ Examples
+ --------
+ >>> ensure_index_from_sequences([[1, 2, 4]], names=["name"])
+ Index([1, 2, 4], dtype='int64', name='name')
+
+ >>> ensure_index_from_sequences([["a", "a"], ["a", "b"]], names=["L1", "L2"])
+ MultiIndex([('a', 'a'),
+ ('a', 'b')],
+ names=['L1', 'L2'])
+
+ See Also
+ --------
+ ensure_index
+ """
+ from pandas.core.indexes.api import default_index
+ from pandas.core.indexes.multi import MultiIndex
+
+ if len(sequences) == 0:
+ return default_index(0)
+ elif len(sequences) == 1:
+ if names is not None:
+ names = names[0]
+ return Index(maybe_sequence_to_range(sequences[0]), name=names)
+ else:
+ # TODO: Apply maybe_sequence_to_range to sequences?
+ return MultiIndex.from_arrays(sequences, names=names)
+
+
+def ensure_index(index_like: Axes, copy: bool = False) -> Index:
+ """
+ Ensure that we have an index from some index-like object.
+
+ Parameters
+ ----------
+ index_like : sequence
+ An Index or other sequence
+ copy : bool, default False
+
+ Returns
+ -------
+ index : Index or MultiIndex
+
+ See Also
+ --------
+ ensure_index_from_sequences
+
+ Examples
+ --------
+ >>> ensure_index(["a", "b"])
+ Index(['a', 'b'], dtype='str')
+
+ >>> ensure_index([("a", "a"), ("b", "c")])
+ Index([('a', 'a'), ('b', 'c')], dtype='object')
+
+ >>> ensure_index([["a", "a"], ["b", "c"]])
+ MultiIndex([('a', 'b'),
+ ('a', 'c')],
+ )
+ """
+ if isinstance(index_like, Index):
+ if copy:
+ index_like = index_like.copy()
+ return index_like
+
+ if isinstance(index_like, ABCSeries):
+ name = index_like.name
+ return Index(index_like, name=name, copy=copy)
+
+ if is_iterator(index_like):
+ index_like = list(index_like)
+
+ if isinstance(index_like, list):
+ if type(index_like) is not list:
+ # must check for exactly list here because of strict type
+ # check in clean_index_list
+ index_like = list(index_like)
+
+ if index_like and lib.is_all_arraylike(index_like):
+ from pandas.core.indexes.multi import MultiIndex
+
+ return MultiIndex.from_arrays(index_like)
+ else:
+ return Index(index_like, copy=copy, tupleize_cols=False)
+ else:
+ return Index(index_like, copy=copy)
+
+
+def trim_front(strings: list[str]) -> list[str]:
+ """
+ Trims leading spaces evenly among all strings.
+
+ Examples
+ --------
+ >>> trim_front([" a", " b"])
+ ['a', 'b']
+
+ >>> trim_front([" a", " "])
+ ['a', '']
+ """
+ if not strings:
+ return strings
+ smallest_leading_space = min(len(x) - len(x.lstrip()) for x in strings)
+ if smallest_leading_space > 0:
+ strings = [x[smallest_leading_space:] for x in strings]
+ return strings
+
+
+def _validate_join_method(method: str) -> None:
+ if method not in ["left", "right", "inner", "outer"]:
+ raise ValueError(f"do not recognize join method {method}")
+
+
+def maybe_extract_name(name, obj, cls) -> Hashable:
+ """
+ If no name is passed, then extract it from data, validating hashability.
+ """
+ if name is None and isinstance(obj, (Index, ABCSeries)):
+ # Note we don't just check for "name" attribute since that would
+ # pick up e.g. dtype.name
+ name = obj.name
+
+ # GH#29069
+ if not is_hashable(name):
+ raise TypeError(f"{cls.__name__}.name must be a hashable type")
+
+ return name
+
+
+def get_unanimous_names(*indexes: Index) -> tuple[Hashable, ...]:
+ """
+ Return common name if all indices agree, otherwise None (level-by-level).
+
+ Parameters
+ ----------
+ indexes : list of Index objects
+
+ Returns
+ -------
+ list
+ A list representing the unanimous 'names' found.
+ """
+ name_tups = (tuple(i.names) for i in indexes)
+ name_sets = ({*ns} for ns in zip_longest(*name_tups))
+ names = tuple(ns.pop() if len(ns) == 1 else None for ns in name_sets)
+ return names
+
+
+def _unpack_nested_dtype(other: Index) -> DtypeObj:
+ """
+ When checking if our dtype is comparable with another, we need
+ to unpack CategoricalDtype to look at its categories.dtype.
+
+ Parameters
+ ----------
+ other : Index
+
+ Returns
+ -------
+ np.dtype or ExtensionDtype
+ """
+ dtype = other.dtype
+ if isinstance(dtype, CategoricalDtype):
+ # If there is ever a SparseIndex, this could get dispatched
+ # here too.
+ return dtype.categories.dtype
+ elif isinstance(dtype, ArrowDtype):
+ # GH 53617
+ import pyarrow as pa
+
+ if pa.types.is_dictionary(dtype.pyarrow_dtype):
+ other = other[:0].astype(ArrowDtype(dtype.pyarrow_dtype.value_type))
+ return other.dtype
+
+
+def _maybe_try_sort(result: Index | ArrayLike, sort: bool | None):
+ if sort is not False:
+ try:
+ # error: Incompatible types in assignment (expression has type
+ # "Union[ExtensionArray, ndarray[Any, Any], Index, Series,
+ # Tuple[Union[Union[ExtensionArray, ndarray[Any, Any]], Index, Series],
+ # ndarray[Any, Any]]]", variable has type "Union[Index,
+ # Union[ExtensionArray, ndarray[Any, Any]]]")
+ result = algos.safe_sort(result) # type: ignore[assignment]
+ except TypeError as err:
+ if sort is True:
+ raise
+ warnings.warn(
+ f"{err}, sort order is undefined for incomparable objects.",
+ RuntimeWarning,
+ stacklevel=find_stack_level(),
+ )
+ return result
+
+
+def get_values_for_csv(
+ values: ArrayLike,
+ *,
+ date_format,
+ na_rep: str = "nan",
+ quoting=None,
+ float_format=None,
+ decimal: str = ".",
+) -> npt.NDArray[np.object_]:
+ """
+ Convert to types which can be consumed by the standard library's
+ csv.writer.writerows.
+ """
+ if isinstance(values, Categorical) and values.categories.dtype.kind in "Mm":
+ # GH#40754 Convert categorical datetimes to datetime array
+ values = algos.take_nd(
+ values.categories._values,
+ ensure_platform_int(values._codes),
+ fill_value=na_rep,
+ )
+
+ values = ensure_wrapped_if_datetimelike(values)
+
+ if isinstance(values, (DatetimeArray, TimedeltaArray)):
+ if values.ndim == 1:
+ result = values._format_native_types(na_rep=na_rep, date_format=date_format)
+ result = result.astype(object, copy=False)
+ return result
+
+ # GH#21734 Process every column separately, they might have different formats
+ results_converted = []
+ for i in range(len(values)):
+ result = values[i, :]._format_native_types(
+ na_rep=na_rep, date_format=date_format
+ )
+ results_converted.append(result.astype(object, copy=False))
+ return np.vstack(results_converted)
+
+ elif isinstance(values.dtype, PeriodDtype):
+ # TODO: tests that get here in column path
+ values = cast("PeriodArray", values)
+ res = values._format_native_types(na_rep=na_rep, date_format=date_format)
+ return res
+
+ elif isinstance(values.dtype, IntervalDtype):
+ # TODO: tests that get here in column path
+ values = cast("IntervalArray", values)
+ mask = values.isna()
+ if not quoting:
+ result = np.asarray(values).astype(str)
+ else:
+ result = np.array(values, dtype=object, copy=True)
+
+ result[mask] = na_rep
+ return result
+
+ elif values.dtype.kind == "f" and not isinstance(values.dtype, SparseDtype):
+ # see GH#13418: no special formatting is desired at the
+ # output (important for appropriate 'quoting' behaviour),
+ # so do not pass it through the FloatArrayFormatter
+ if float_format is None and decimal == ".":
+ mask = isna(values)
+
+ if not quoting:
+ values = values.astype(str)
+ else:
+ values = np.array(values, dtype="object")
+
+ values[mask] = na_rep
+ values = values.astype(object, copy=False)
+ return values
+
+ from pandas.io.formats.format import FloatArrayFormatter
+
+ formatter = FloatArrayFormatter(
+ values,
+ na_rep=na_rep,
+ float_format=float_format,
+ decimal=decimal,
+ quoting=quoting,
+ fixed_width=False,
+ )
+ res = formatter.get_result_as_array()
+ res = res.astype(object, copy=False)
+ return res
+
+ elif isinstance(values, ExtensionArray):
+ mask = isna(values)
+
+ new_values = np.asarray(values.astype(object))
+ new_values[mask] = na_rep
+ return new_values
+
+ else:
+ mask = isna(values)
+ itemsize = writers.word_len(na_rep)
+
+ if values.dtype != _dtype_obj and not quoting and itemsize:
+ values = values.astype(str)
+ if values.dtype.itemsize / np.dtype("U1").itemsize < itemsize:
+ # enlarge for the na_rep
+ values = values.astype(f"`__
+ for more.
+
+ Examples
+ --------
+ >>> pd.CategoricalIndex(["a", "b", "c", "a", "b", "c"])
+ CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c'],
+ categories=['a', 'b', 'c'], ordered=False, dtype='category')
+
+ ``CategoricalIndex`` can also be instantiated from a ``Categorical``:
+
+ >>> c = pd.Categorical(["a", "b", "c", "a", "b", "c"])
+ >>> pd.CategoricalIndex(c)
+ CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c'],
+ categories=['a', 'b', 'c'], ordered=False, dtype='category')
+
+ Ordered ``CategoricalIndex`` can have a min and max value.
+
+ >>> ci = pd.CategoricalIndex(
+ ... ["a", "b", "c", "a", "b", "c"], ordered=True, categories=["c", "b", "a"]
+ ... )
+ >>> ci
+ CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c'],
+ categories=['c', 'b', 'a'], ordered=True, dtype='category')
+ >>> ci.min()
+ 'c'
+ """
+
+ _typ = "categoricalindex"
+ _data_cls = Categorical
+
+ @property
+ def _can_hold_strings(self):
+ return self.categories._can_hold_strings
+
+ @cache_readonly
+ def _should_fallback_to_positional(self) -> bool:
+ return self.categories._should_fallback_to_positional
+
+ codes: np.ndarray
+ categories: Index
+ ordered: bool | None
+ _data: Categorical
+ _values: Categorical
+
+ @property
+ def _engine_type(self) -> type[libindex.IndexEngine]:
+ # self.codes can have dtype int8, int16, int32 or int64, so we need
+ # to return the corresponding engine type (libindex.Int8Engine, etc.).
+ return {
+ np.int8: libindex.Int8Engine,
+ np.int16: libindex.Int16Engine,
+ np.int32: libindex.Int32Engine,
+ np.int64: libindex.Int64Engine,
+ }[self.codes.dtype.type]
+
+ # --------------------------------------------------------------------
+ # Constructors
+
+ def __new__(
+ cls,
+ data=None,
+ categories=None,
+ ordered=None,
+ dtype: Dtype | None = None,
+ copy: bool = False,
+ name: Hashable | None = None,
+ ) -> Self:
+ name = maybe_extract_name(name, data, cls)
+
+ if is_scalar(data):
+ # GH#38944 include None here, which pre-2.0 subbed in []
+ cls._raise_scalar_data_error(data)
+
+ data = Categorical(
+ data, categories=categories, ordered=ordered, dtype=dtype, copy=copy
+ )
+
+ return cls._simple_new(data, name=name)
+
+ # --------------------------------------------------------------------
+
+ def _is_dtype_compat(self, other: Index) -> Categorical:
+ """
+ *this is an internal non-public method*
+
+ provide a comparison between the dtype of self and other (coercing if
+ needed)
+
+ Parameters
+ ----------
+ other : Index
+
+ Returns
+ -------
+ Categorical
+
+ Raises
+ ------
+ TypeError if the dtypes are not compatible
+ """
+ if isinstance(other.dtype, CategoricalDtype):
+ cat = extract_array(other)
+ cat = cast(Categorical, cat)
+ if not cat._categories_match_up_to_permutation(self._values):
+ raise TypeError(
+ "categories must match existing categories when appending"
+ )
+
+ elif other._is_multi:
+ # preempt raising NotImplementedError in isna call
+ raise TypeError("MultiIndex is not dtype-compatible with CategoricalIndex")
+ else:
+ values = other
+
+ codes = self.categories.get_indexer(values)
+ if ((codes == -1) & ~values.isna()).any():
+ # GH#37667 see test_equals_non_category
+ raise TypeError(
+ "categories must match existing categories when appending"
+ )
+ cat = Categorical(other, dtype=self.dtype)
+ other = CategoricalIndex(cat)
+ if not other.isin(values).all():
+ raise TypeError(
+ "cannot append a non-category item to a CategoricalIndex"
+ )
+ cat = other._values
+
+ return cat
+
+ def equals(self, other: object) -> bool:
+ """
+ Determine if two CategoricalIndex objects contain the same elements.
+
+ The order and orderedness of elements matters. The categories matter,
+ but the order of the categories matters only when ``ordered=True``.
+
+ Parameters
+ ----------
+ other : object
+ The CategoricalIndex object to compare with.
+
+ Returns
+ -------
+ bool
+ ``True`` if two :class:`pandas.CategoricalIndex` objects have equal
+ elements, ``False`` otherwise.
+
+ See Also
+ --------
+ Categorical.equals : Returns True if categorical arrays are equal.
+
+ Examples
+ --------
+ >>> ci = pd.CategoricalIndex(["a", "b", "c", "a", "b", "c"])
+ >>> ci2 = pd.CategoricalIndex(pd.Categorical(["a", "b", "c", "a", "b", "c"]))
+ >>> ci.equals(ci2)
+ True
+
+ The order of elements matters.
+
+ >>> ci3 = pd.CategoricalIndex(["c", "b", "a", "a", "b", "c"])
+ >>> ci.equals(ci3)
+ False
+
+ The orderedness also matters.
+
+ >>> ci4 = ci.as_ordered()
+ >>> ci.equals(ci4)
+ False
+
+ The categories matter, but the order of the categories matters only when
+ ``ordered=True``.
+
+ >>> ci5 = ci.set_categories(["a", "b", "c", "d"])
+ >>> ci.equals(ci5)
+ False
+
+ >>> ci6 = ci.set_categories(["b", "c", "a"])
+ >>> ci.equals(ci6)
+ True
+ >>> ci_ordered = pd.CategoricalIndex(
+ ... ["a", "b", "c", "a", "b", "c"], ordered=True
+ ... )
+ >>> ci2_ordered = ci_ordered.set_categories(["b", "c", "a"])
+ >>> ci_ordered.equals(ci2_ordered)
+ False
+ """
+ if self.is_(other):
+ return True
+
+ if not isinstance(other, Index):
+ return False
+
+ try:
+ other = self._is_dtype_compat(other)
+ except (TypeError, ValueError):
+ return False
+
+ return self._data.equals(other)
+
+ # --------------------------------------------------------------------
+ # Rendering Methods
+
+ @property
+ def _formatter_func(self):
+ return self.categories._formatter_func
+
+ def _format_attrs(self):
+ """
+ Return a list of tuples of the (attr,formatted_value)
+ """
+ attrs: list[tuple[str, str | int | bool | None]]
+
+ attrs = [
+ (
+ "categories",
+ f"[{', '.join(self._data._repr_categories())}]",
+ ),
+ ("ordered", self.ordered),
+ ]
+ extra = super()._format_attrs()
+ return attrs + extra
+
+ # --------------------------------------------------------------------
+
+ @property
+ def inferred_type(self) -> str:
+ return "categorical"
+
+ def __contains__(self, key: Any) -> bool:
+ """
+ Return a boolean indicating whether the provided key is in the index.
+
+ Parameters
+ ----------
+ key : label
+ The key to check if it is present in the index.
+
+ Returns
+ -------
+ bool
+ Whether the key search is in the index.
+
+ Raises
+ ------
+ TypeError
+ If the key is not hashable.
+
+ See Also
+ --------
+ Index.isin : Returns an ndarray of boolean dtype indicating whether the
+ list-like key is in the index.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3, 4])
+ >>> idx
+ Index([1, 2, 3, 4], dtype='int64')
+
+ >>> 2 in idx
+ True
+ >>> 6 in idx
+ False
+ """
+ # if key is a NaN, check if any NaN is in self.
+ if is_valid_na_for_dtype(key, self.categories.dtype):
+ return self.hasnans
+ if self.categories._typ == "rangeindex":
+ container: Index | libindex.IndexEngine | libindex.ExtensionEngine = (
+ self.categories
+ )
+ else:
+ container = self._engine
+ return contains(self, key, container=container)
+
+ def reindex(
+ self, target, method=None, level=None, limit: int | None = None, tolerance=None
+ ) -> tuple[Index, npt.NDArray[np.intp] | None]:
+ """
+ Create index with target's values (move/add/delete values as necessary)
+
+ Returns
+ -------
+ new_index : pd.Index
+ Resulting index
+ indexer : np.ndarray[np.intp] or None
+ Indices of output values in original index
+
+ """
+ if method is not None:
+ raise NotImplementedError(
+ "argument method is not implemented for CategoricalIndex.reindex"
+ )
+ if level is not None:
+ raise NotImplementedError(
+ "argument level is not implemented for CategoricalIndex.reindex"
+ )
+ if limit is not None:
+ raise NotImplementedError(
+ "argument limit is not implemented for CategoricalIndex.reindex"
+ )
+ return super().reindex(target)
+
+ # --------------------------------------------------------------------
+ # Indexing Methods
+
+ def _maybe_cast_indexer(self, key) -> int:
+ # GH#41933: we have to do this instead of self._data._validate_scalar
+ # because this will correctly get partial-indexing on Interval categories
+ try:
+ return self._data._unbox_scalar(key)
+ except KeyError:
+ if is_valid_na_for_dtype(key, self.categories.dtype):
+ return -1
+ raise
+
+ def _maybe_cast_listlike_indexer(self, values) -> CategoricalIndex:
+ if isinstance(values, CategoricalIndex):
+ values = values._data
+ if isinstance(values, Categorical):
+ # Indexing on codes is more efficient if categories are the same,
+ # so we can apply some optimizations based on the degree of
+ # dtype-matching.
+ cat = self._data._encode_with_my_categories(values)
+ codes = cat._codes
+ else:
+ codes = self.categories.get_indexer(values)
+ codes = codes.astype(self.codes.dtype, copy=False)
+ cat = self._data._from_backing_data(codes)
+ return type(self)._simple_new(cat)
+
+ # --------------------------------------------------------------------
+
+ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
+ return self.categories._is_comparable_dtype(dtype)
+
+ def map(self, mapper, na_action: Literal["ignore"] | None = None):
+ """
+ Map values using input an input mapping or function.
+
+ Maps the values (their categories, not the codes) of the index to new
+ categories. If the mapping correspondence is one-to-one the result is a
+ :class:`~pandas.CategoricalIndex` which has the same order property as
+ the original, otherwise an :class:`~pandas.Index` is returned.
+
+ If a `dict` or :class:`~pandas.Series` is used any unmapped category is
+ mapped to `NaN`. Note that if this happens an :class:`~pandas.Index`
+ will be returned.
+
+ Parameters
+ ----------
+ mapper : function, dict, or Series
+ Mapping correspondence.
+ na_action : {None, 'ignore'}, default 'ignore'
+ If 'ignore', propagate NaN values, without passing them to
+ the mapping correspondence.
+
+ Returns
+ -------
+ pandas.CategoricalIndex or pandas.Index
+ Mapped index.
+
+ See Also
+ --------
+ Index.map : Apply a mapping correspondence on an
+ :class:`~pandas.Index`.
+ Series.map : Apply a mapping correspondence on a
+ :class:`~pandas.Series`.
+ Series.apply : Apply more complex functions on a
+ :class:`~pandas.Series`.
+
+ Examples
+ --------
+ >>> idx = pd.CategoricalIndex(["a", "b", "c"])
+ >>> idx
+ CategoricalIndex(['a', 'b', 'c'], categories=['a', 'b', 'c'],
+ ordered=False, dtype='category')
+ >>> idx.map(lambda x: x.upper())
+ CategoricalIndex(['A', 'B', 'C'], categories=['A', 'B', 'C'],
+ ordered=False, dtype='category')
+ >>> idx.map({"a": "first", "b": "second", "c": "third"})
+ CategoricalIndex(['first', 'second', 'third'], categories=['first',
+ 'second', 'third'], ordered=False, dtype='category')
+
+ If the mapping is one-to-one the ordering of the categories is
+ preserved:
+
+ >>> idx = pd.CategoricalIndex(["a", "b", "c"], ordered=True)
+ >>> idx
+ CategoricalIndex(['a', 'b', 'c'], categories=['a', 'b', 'c'],
+ ordered=True, dtype='category')
+ >>> idx.map({"a": 3, "b": 2, "c": 1})
+ CategoricalIndex([3, 2, 1], categories=[3, 2, 1], ordered=True,
+ dtype='category')
+
+ If the mapping is not one-to-one an :class:`~pandas.Index` is returned:
+
+ >>> idx.map({"a": "first", "b": "second", "c": "first"})
+ Index(['first', 'second', 'first'], dtype='str')
+
+ If a `dict` is used, all unmapped categories are mapped to `NaN` and
+ the result is an :class:`~pandas.Index`:
+
+ >>> idx.map({"a": "first", "b": "second"})
+ Index(['first', 'second', nan], dtype='str')
+ """
+ mapped = self._values.map(mapper, na_action=na_action)
+ return Index(mapped, name=self.name, copy=False)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/datetimelike.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/datetimelike.py
new file mode 100644
index 0000000000000000000000000000000000000000..56a4ef7007c7e4b404974bbe16d1e655b068c1b4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/datetimelike.py
@@ -0,0 +1,1146 @@
+"""
+Base and utility classes for tseries type pandas objects.
+"""
+
+from __future__ import annotations
+
+from abc import (
+ ABC,
+ abstractmethod,
+)
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ Self,
+ cast,
+ final,
+)
+
+import numpy as np
+
+from pandas._libs import (
+ NaT,
+ lib,
+)
+from pandas._libs.tslibs import (
+ BaseOffset,
+ Resolution,
+ Tick,
+ Timedelta,
+ Timestamp,
+ parsing,
+ to_offset,
+)
+from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
+from pandas.compat.numpy import function as nv
+from pandas.errors import (
+ InvalidIndexError,
+ NullFrequencyError,
+ OutOfBoundsDatetime,
+ OutOfBoundsTimedelta,
+)
+from pandas.util._decorators import (
+ cache_readonly,
+)
+
+from pandas.core.dtypes.common import (
+ is_integer,
+ is_list_like,
+)
+from pandas.core.dtypes.concat import concat_compat
+from pandas.core.dtypes.dtypes import (
+ CategoricalDtype,
+ PeriodDtype,
+)
+
+from pandas.core.arrays import (
+ DatetimeArray,
+ ExtensionArray,
+ PeriodArray,
+ TimedeltaArray,
+)
+import pandas.core.common as com
+import pandas.core.indexes.base as ibase
+from pandas.core.indexes.base import (
+ Index,
+)
+from pandas.core.indexes.extension import NDArrayBackedExtensionIndex
+from pandas.core.indexes.range import RangeIndex
+from pandas.core.tools.timedeltas import to_timedelta
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+ from datetime import datetime
+
+ from pandas._typing import (
+ Axis,
+ JoinHow,
+ TimeUnit,
+ npt,
+ )
+
+ from pandas import CategoricalIndex
+
+_index_doc_kwargs = dict(ibase._index_doc_kwargs)
+
+
+class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex, ABC):
+ """
+ Common ops mixin to support a unified interface datetimelike Index.
+ """
+
+ _can_hold_strings = False
+ _data: DatetimeArray | TimedeltaArray | PeriodArray
+
+ def mean(self, *, skipna: bool = True, axis: int | None = 0):
+ """
+ Return the mean value of the Array.
+
+ Parameters
+ ----------
+ skipna : bool, default True
+ Whether to ignore any NaT elements.
+ axis : int, optional, default 0
+ Axis for the function to be applied on.
+
+ Returns
+ -------
+ scalar
+ Timestamp or Timedelta.
+
+ See Also
+ --------
+ numpy.ndarray.mean : Returns the average of array elements along a given axis.
+ Series.mean : Return the mean value in a Series.
+
+ Notes
+ -----
+ mean is only defined for Datetime and Timedelta dtypes, not for Period.
+
+ Examples
+ --------
+ For :class:`pandas.DatetimeIndex`:
+
+ >>> idx = pd.date_range("2001-01-01 00:00", periods=3)
+ >>> idx
+ DatetimeIndex(['2001-01-01', '2001-01-02', '2001-01-03'],
+ dtype='datetime64[us]', freq='D')
+ >>> idx.mean()
+ Timestamp('2001-01-02 00:00:00')
+
+ For :class:`pandas.TimedeltaIndex`:
+
+ >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D")
+ >>> tdelta_idx
+ TimedeltaIndex(['1 days', '2 days', '3 days'],
+ dtype='timedelta64[s]', freq=None)
+ >>> tdelta_idx.mean()
+ Timedelta('2 days 00:00:00')
+ """
+ return self._data.mean(skipna=skipna, axis=axis)
+
+ @property
+ def freq(self) -> BaseOffset | None:
+ """
+ Return the frequency object if it is set, otherwise None.
+
+ To learn more about the frequency strings, please see
+ :ref:`this link`.
+
+ See Also
+ --------
+ DatetimeIndex.freq : Return the frequency object if it is set, otherwise None.
+ PeriodIndex.freq : Return the frequency object if it is set, otherwise None.
+
+ Examples
+ --------
+ >>> datetimeindex = pd.date_range(
+ ... "2022-02-22 02:22:22", periods=10, tz="America/Chicago", freq="h"
+ ... )
+ >>> datetimeindex
+ DatetimeIndex(['2022-02-22 02:22:22-06:00', '2022-02-22 03:22:22-06:00',
+ '2022-02-22 04:22:22-06:00', '2022-02-22 05:22:22-06:00',
+ '2022-02-22 06:22:22-06:00', '2022-02-22 07:22:22-06:00',
+ '2022-02-22 08:22:22-06:00', '2022-02-22 09:22:22-06:00',
+ '2022-02-22 10:22:22-06:00', '2022-02-22 11:22:22-06:00'],
+ dtype='datetime64[us, America/Chicago]', freq='h')
+ >>> datetimeindex.freq
+
+ """
+ return self._data.freq
+
+ @freq.setter
+ def freq(self, value) -> None:
+ # error: Property "freq" defined in "PeriodArray" is read-only [misc]
+ self._data.freq = value # type: ignore[misc]
+
+ @property
+ def asi8(self) -> npt.NDArray[np.int64]:
+ return self._data.asi8
+
+ @property
+ def freqstr(self) -> str:
+ """
+ Return the frequency object as a string if it's set, otherwise None.
+
+ See Also
+ --------
+ DatetimeIndex.inferred_freq : Returns a string representing a frequency
+ generated by infer_freq.
+
+ Examples
+ --------
+ For DatetimeIndex:
+
+ >>> idx = pd.DatetimeIndex(["1/1/2020 10:00:00+00:00"], freq="D")
+ >>> idx.freqstr
+ 'D'
+
+ The frequency can be inferred if there are more than 2 points:
+
+ >>> idx = pd.DatetimeIndex(
+ ... ["2018-01-01", "2018-01-03", "2018-01-05"], freq="infer"
+ ... )
+ >>> idx.freqstr
+ '2D'
+
+ For PeriodIndex:
+
+ >>> idx = pd.PeriodIndex(["2023-1", "2023-2", "2023-3"], freq="M")
+ >>> idx.freqstr
+ 'M'
+ """
+ from pandas import PeriodIndex
+
+ if self._data.freqstr is not None and isinstance(
+ self._data, (PeriodArray, PeriodIndex)
+ ):
+ freq = PeriodDtype(self._data.freq)._freqstr
+ return freq
+ else:
+ return self._data.freqstr # type: ignore[return-value]
+
+ @cache_readonly
+ @abstractmethod
+ def _resolution_obj(self) -> Resolution: ...
+
+ @cache_readonly
+ def resolution(self) -> str:
+ """
+ Returns day, hour, minute, second, millisecond or microsecond
+ """
+ return self._data.resolution
+
+ # ------------------------------------------------------------------------
+
+ @cache_readonly
+ def hasnans(self) -> bool:
+ return self._data._hasna
+
+ def equals(self, other: Any) -> bool:
+ """
+ Determines if two Index objects contain the same elements.
+ """
+ if self.is_(other):
+ return True
+
+ if not isinstance(other, Index):
+ return False
+ elif other.dtype.kind in "iufc":
+ return False
+ elif not isinstance(other, type(self)):
+ should_try = False
+ inferable = self._data._infer_matches
+ if other.dtype == object:
+ should_try = other.inferred_type in inferable
+ elif isinstance(other.dtype, CategoricalDtype):
+ other = cast("CategoricalIndex", other)
+ should_try = other.categories.inferred_type in inferable
+
+ if should_try:
+ try:
+ other = type(self)(other)
+ except (ValueError, TypeError, OverflowError):
+ # e.g.
+ # ValueError -> cannot parse str entry, or OutOfBoundsDatetime
+ # TypeError -> trying to convert IntervalIndex to DatetimeIndex
+ # OverflowError -> Index([very_large_timedeltas])
+ return False
+
+ if type(self) != type(other):
+ return False
+ elif self.dtype == other.dtype:
+ return np.array_equal(self.asi8, other.asi8)
+ elif (self.dtype.kind == "M" and self.tz == other.tz) or self.dtype.kind == "m": # type: ignore[attr-defined]
+ # different units, otherwise matching
+ try:
+ # TODO: do this at the EA level?
+ left, right = self._data._ensure_matching_resos(other._data) # type: ignore[union-attr]
+ except (OutOfBoundsDatetime, OutOfBoundsTimedelta):
+ return False
+ else:
+ return np.array_equal(left.view("i8"), right.view("i8"))
+ return False
+
+ def __contains__(self, key: Any) -> bool:
+ """
+ Return a boolean indicating whether the provided key is in the index.
+
+ Parameters
+ ----------
+ key : label
+ The key to check if it is present in the index.
+
+ Returns
+ -------
+ bool
+ Whether the key search is in the index.
+
+ Raises
+ ------
+ TypeError
+ If the key is not hashable.
+
+ See Also
+ --------
+ Index.isin : Returns an ndarray of boolean dtype indicating whether the
+ list-like key is in the index.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3, 4])
+ >>> idx
+ Index([1, 2, 3, 4], dtype='int64')
+ >>> 2 in idx
+ True
+ >>> 6 in idx
+ False
+ """
+ hash(key)
+ try:
+ self.get_loc(key)
+ except (KeyError, TypeError, ValueError, InvalidIndexError):
+ return False
+ return True
+
+ def _convert_tolerance(self, tolerance, target):
+ tolerance = np.asarray(to_timedelta(tolerance).to_numpy())
+ return super()._convert_tolerance(tolerance, target)
+
+ # --------------------------------------------------------------------
+ # Rendering Methods
+ _default_na_rep = "NaT"
+
+ def _format_with_header(
+ self, *, header: list[str], na_rep: str, date_format: str | None = None
+ ) -> list[str]:
+ # TODO: not reached in tests 2023-10-11
+ # matches base class except for whitespace padding and date_format
+ return header + list(
+ self._get_values_for_csv(na_rep=na_rep, date_format=date_format)
+ )
+
+ @property
+ def _formatter_func(self):
+ return self._data._formatter()
+
+ def _format_attrs(self):
+ """
+ Return a list of tuples of the (attr,formatted_value).
+ """
+ attrs = super()._format_attrs()
+ for attrib in self._attributes:
+ # iterating over _attributes prevents us from doing this for PeriodIndex
+ if attrib == "freq":
+ freq = self.freqstr
+ if freq is not None:
+ freq = repr(freq) # e.g. D -> 'D'
+ attrs.append(("freq", freq))
+ return attrs
+
+ def _summary(self, name=None) -> str:
+ """
+ Return a summarized representation.
+
+ Parameters
+ ----------
+ name : str
+ name to use in the summary representation
+
+ Returns
+ -------
+ String with a summarized representation of the index
+ """
+ result = super()._summary(name=name)
+ if self.freq:
+ result += f"\nFreq: {self.freqstr}"
+
+ return result
+
+ # --------------------------------------------------------------------
+ # Indexing Methods
+
+ @final
+ def _can_partial_date_slice(self, reso: Resolution) -> bool:
+ # e.g. test_getitem_setitem_periodindex
+ # History of conversation GH#3452, GH#3931, GH#2369, GH#14826
+ return reso > self._resolution_obj
+ # NB: for DTI/PI, not TDI
+
+ def _parsed_string_to_bounds(self, reso: Resolution, parsed):
+ raise NotImplementedError
+
+ def _parse_with_reso(self, label: str) -> tuple[datetime, Resolution]:
+ # overridden by TimedeltaIndex
+ try:
+ if self.freq is None or hasattr(self.freq, "rule_code"):
+ freq = self.freq
+ except NotImplementedError:
+ freq = getattr(self, "freqstr", getattr(self, "inferred_freq", None))
+
+ freqstr: str | None
+ if freq is not None and not isinstance(freq, str):
+ freqstr = freq.rule_code
+ else:
+ freqstr = freq
+
+ if isinstance(label, np.str_):
+ # GH#45580
+ label = str(label)
+
+ parsed, reso_str = parsing.parse_datetime_string_with_reso(label, freqstr)
+ reso = Resolution.from_attrname(reso_str)
+ return parsed, reso
+
+ def _get_string_slice(self, key: str) -> slice | npt.NDArray[np.intp]:
+ # overridden by TimedeltaIndex
+ parsed, reso = self._parse_with_reso(key)
+ try:
+ return self._partial_date_slice(reso, parsed)
+ except KeyError as err:
+ raise KeyError(key) from err
+
+ @final
+ def _partial_date_slice(
+ self,
+ reso: Resolution,
+ parsed: datetime,
+ ) -> slice | npt.NDArray[np.intp]:
+ """
+ Parameters
+ ----------
+ reso : Resolution
+ parsed : datetime
+
+ Returns
+ -------
+ slice or ndarray[intp]
+ """
+ if not self._can_partial_date_slice(reso):
+ raise ValueError
+
+ t1, t2 = self._parsed_string_to_bounds(reso, parsed)
+ vals = self._data._ndarray
+ unbox = self._data._unbox
+
+ if self.is_monotonic_increasing:
+ if len(self) and (
+ (t1 < self[0] and t2 < self[0]) or (t1 > self[-1] and t2 > self[-1])
+ ):
+ # we are out of range
+ raise KeyError
+
+ # TODO: does this depend on being monotonic _increasing_?
+
+ # a monotonic (sorted) series can be sliced
+ left = vals.searchsorted(unbox(t1), side="left")
+ right = vals.searchsorted(unbox(t2), side="right")
+ return slice(left, right)
+
+ else:
+ lhs_mask = vals >= unbox(t1)
+ rhs_mask = vals <= unbox(t2)
+
+ # try to find the dates
+ return (lhs_mask & rhs_mask).nonzero()[0]
+
+ def _maybe_cast_slice_bound(self, label, side: str):
+ """
+ If label is a string, cast it to scalar type according to resolution.
+
+ Parameters
+ ----------
+ label : object
+ side : {'left', 'right'}
+
+ Returns
+ -------
+ label : object
+
+ Notes
+ -----
+ Value of `side` parameter should be validated in caller.
+ """
+ if isinstance(label, str):
+ try:
+ parsed, reso = self._parse_with_reso(label)
+ except ValueError as err:
+ # DTI -> parsing.DateParseError
+ # TDI -> 'unit abbreviation w/o a number'
+ # PI -> string cannot be parsed as datetime-like
+ self._raise_invalid_indexer("slice", label, err)
+
+ lower, upper = self._parsed_string_to_bounds(reso, parsed)
+ return lower if side == "left" else upper
+ elif not isinstance(label, self._data._recognized_scalars):
+ self._raise_invalid_indexer("slice", label)
+
+ return label
+
+ # --------------------------------------------------------------------
+ # Arithmetic Methods
+
+ def shift(self, periods: int = 1, freq=None) -> Self:
+ """
+ Shift index by desired number of time frequency increments.
+
+ This method is for shifting the values of datetime-like indexes
+ by a specified time increment a given number of times.
+
+ Parameters
+ ----------
+ periods : int, default 1
+ Number of periods (or increments) to shift by,
+ can be positive or negative.
+ freq : pandas.DateOffset, pandas.Timedelta or string, optional
+ Frequency increment to shift by.
+ If None, the index is shifted by its own `freq` attribute.
+ Offset aliases are valid strings, e.g., 'D', 'W', 'M' etc.
+
+ Returns
+ -------
+ pandas.DatetimeIndex
+ Shifted index.
+
+ See Also
+ --------
+ Index.shift : Shift values of Index.
+ PeriodIndex.shift : Shift values of PeriodIndex.
+ """
+ raise NotImplementedError
+
+ # --------------------------------------------------------------------
+
+ def _maybe_cast_listlike_indexer(self, keyarr):
+ """
+ Analogue to maybe_cast_indexer for get_indexer instead of get_loc.
+ """
+ try:
+ res = self._data._validate_listlike(keyarr, allow_object=True)
+ except (ValueError, TypeError):
+ if not isinstance(keyarr, ExtensionArray):
+ # e.g. we don't want to cast DTA to ndarray[object]
+ res = com.asarray_tuplesafe(keyarr)
+ # TODO: com.asarray_tuplesafe shouldn't cast e.g. DatetimeArray
+ else:
+ res = keyarr
+ return Index(res, dtype=res.dtype)
+
+
+class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin, ABC):
+ """
+ Mixin class for methods shared by DatetimeIndex and TimedeltaIndex,
+ but not PeriodIndex
+ """
+
+ _data: DatetimeArray | TimedeltaArray
+ _comparables = ["name", "freq"]
+ _attributes = ["name", "freq"]
+
+ # Compat for frequency inference, see GH#23789
+ _is_monotonic_increasing = Index.is_monotonic_increasing
+ _is_monotonic_decreasing = Index.is_monotonic_decreasing
+ _is_unique = Index.is_unique
+
+ @property
+ def unit(self) -> TimeUnit:
+ return self._data.unit
+
+ def as_unit(self, unit: TimeUnit) -> Self:
+ """
+ Convert to a dtype with the given unit resolution.
+
+ This method is for converting the dtype of a ``DatetimeIndex`` or
+ ``TimedeltaIndex`` to a new dtype with the given unit
+ resolution/precision.
+
+ Parameters
+ ----------
+ unit : {'s', 'ms', 'us', 'ns'}
+
+ Returns
+ -------
+ same type as self
+ Converted to the specified unit.
+
+ See Also
+ --------
+ Timestamp.as_unit : Convert to the given unit.
+ Timedelta.as_unit : Convert to the given unit.
+ DatetimeIndex.as_unit : Convert to the given unit.
+ TimedeltaIndex.as_unit : Convert to the given unit.
+
+ Examples
+ --------
+ For :class:`pandas.DatetimeIndex`:
+
+ >>> idx = pd.DatetimeIndex(["2020-01-02 01:02:03.004005006"])
+ >>> idx
+ DatetimeIndex(['2020-01-02 01:02:03.004005006'],
+ dtype='datetime64[ns]', freq=None)
+ >>> idx.as_unit("s")
+ DatetimeIndex(['2020-01-02 01:02:03'], dtype='datetime64[s]', freq=None)
+
+ For :class:`pandas.TimedeltaIndex`:
+
+ >>> tdelta_idx = pd.to_timedelta(["1 day 3 min 2 us 42 ns"])
+ >>> tdelta_idx
+ TimedeltaIndex(['1 days 00:03:00.000002042'],
+ dtype='timedelta64[ns]', freq=None)
+ >>> tdelta_idx.as_unit("s")
+ TimedeltaIndex(['1 days 00:03:00'], dtype='timedelta64[s]', freq=None)
+ """
+ arr = self._data.as_unit(unit)
+ return type(self)._simple_new(arr, name=self.name)
+
+ def _with_freq(self, freq):
+ arr = self._data._with_freq(freq)
+ return type(self)._simple_new(arr, name=self._name)
+
+ @property
+ def values(self) -> np.ndarray:
+ # NB: For Datetime64TZ this is lossy
+ data = self._data._ndarray
+ data = data.view()
+ data.flags.writeable = False
+ return data
+
+ def shift(self, periods: int = 1, freq=None) -> Self:
+ """
+ Shift index by desired number of time frequency increments.
+ This method is for shifting the values of datetime-like indexes
+ by a specified time increment a given number of times.
+
+ Parameters
+ ----------
+ periods : int, default 1
+ Number of periods (or increments) to shift by,
+ can be positive or negative.
+ freq : pandas.DateOffset, pandas.Timedelta or string, optional
+ Frequency increment to shift by.
+ If None, the index is shifted by its own `freq` attribute.
+ Offset aliases are valid strings, e.g., 'D', 'W', 'M' etc.
+
+ Returns
+ -------
+ pandas.DatetimeIndex
+ Shifted index.
+
+ See Also
+ --------
+ Index.shift : Shift values of Index.
+ PeriodIndex.shift : Shift values of PeriodIndex.
+ """
+ if freq is not None and freq != self.freq:
+ if isinstance(freq, str):
+ freq = to_offset(freq)
+ offset = periods * freq
+ return self + offset
+
+ if periods == 0 or len(self) == 0:
+ # GH#14811 empty case
+ return self.copy()
+
+ if self.freq is None:
+ raise NullFrequencyError("Cannot shift with no freq")
+
+ start = self[0] + periods * self.freq
+ end = self[-1] + periods * self.freq
+
+ # Note: in the DatetimeTZ case, _generate_range will infer the
+ # appropriate timezone from `start` and `end`, so tz does not need
+ # to be passed explicitly.
+ result = self._data._generate_range(
+ start=start, end=end, periods=None, freq=self.freq, unit=self.unit
+ )
+ return type(self)._simple_new(result, name=self.name)
+
+ @cache_readonly
+ def inferred_freq(self) -> str | None:
+ """
+ Return the inferred frequency of the index.
+
+ Returns
+ -------
+ str or None
+ A string representing a frequency generated by ``infer_freq``.
+ Returns ``None`` if the frequency cannot be inferred.
+
+ See Also
+ --------
+ DatetimeIndex.freqstr : Return the frequency object as a string if it's set,
+ otherwise ``None``.
+
+ Examples
+ --------
+ For ``DatetimeIndex``:
+
+ >>> idx = pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
+ >>> idx.inferred_freq
+ '2D'
+
+ For ``TimedeltaIndex``:
+
+ >>> tdelta_idx = pd.to_timedelta(["0 days", "10 days", "20 days"])
+ >>> tdelta_idx
+ TimedeltaIndex(['0 days', '10 days', '20 days'],
+ dtype='timedelta64[us]', freq=None)
+ >>> tdelta_idx.inferred_freq
+ '10D'
+ """
+ return self._data.inferred_freq
+
+ # --------------------------------------------------------------------
+ # Set Operation Methods
+
+ @cache_readonly
+ def _as_range_index(self) -> RangeIndex:
+ # Convert our i8 representations to RangeIndex
+ # Caller is responsible for checking isinstance(self.freq, Tick)
+ freq = cast(Tick, self.freq)
+ tick = Timedelta(freq).as_unit(self.unit)._value
+ rng = range(self[0]._value, self[-1]._value + tick, tick)
+ return RangeIndex(rng)
+
+ def _can_range_setop(self, other) -> bool:
+ return isinstance(self.freq, Tick) and isinstance(other.freq, Tick)
+
+ def _wrap_range_setop(self, other, res_i8) -> Self:
+ new_freq = None
+ if not len(res_i8):
+ # RangeIndex defaults to step=1, which we don't want.
+ new_freq = self.freq
+ elif isinstance(res_i8, RangeIndex):
+ new_freq = to_offset(
+ Timedelta(res_i8.step, unit=self.unit).as_unit(self.unit)
+ )
+
+ # TODO(GH#41493): we cannot just do
+ # type(self._data)(res_i8.values, dtype=self.dtype, freq=new_freq)
+ # because test_setops_preserve_freq fails with _validate_frequency raising.
+ # This raising is incorrect, as 'on_freq' is incorrect. This will
+ # be fixed by GH#41493
+ res_values = res_i8.values.view(self._data._ndarray.dtype)
+ result = type(self._data)._simple_new(
+ # error: Argument "dtype" to "_simple_new" of "DatetimeArray" has
+ # incompatible type "Union[dtype[Any], ExtensionDtype]"; expected
+ # "Union[dtype[datetime64], DatetimeTZDtype]"
+ res_values,
+ dtype=self.dtype, # type: ignore[arg-type]
+ freq=new_freq, # type: ignore[arg-type]
+ )
+ return cast("Self", self._wrap_setop_result(other, result))
+
+ def _range_intersect(self, other, sort) -> Self:
+ # Dispatch to RangeIndex intersection logic.
+ left = self._as_range_index
+ right = other._as_range_index
+ res_i8 = left.intersection(right, sort=sort)
+ return self._wrap_range_setop(other, res_i8)
+
+ def _range_union(self, other, sort) -> Self:
+ # Dispatch to RangeIndex union logic.
+ left = self._as_range_index
+ right = other._as_range_index
+ res_i8 = left.union(right, sort=sort)
+ return self._wrap_range_setop(other, res_i8)
+
+ def _intersection(self, other: Index, sort: bool = False) -> Index:
+ """
+ intersection specialized to the case with matching dtypes and both non-empty.
+ """
+ other = cast("DatetimeTimedeltaMixin", other)
+
+ if self._can_range_setop(other):
+ return self._range_intersect(other, sort=sort)
+
+ if not self._can_fast_intersect(other):
+ result = Index._intersection(self, other, sort=sort)
+ # We need to invalidate the freq because Index._intersection
+ # uses _shallow_copy on a view of self._data, which will preserve
+ # self.freq if we're not careful.
+ # At this point we should have result.dtype == self.dtype
+ # and type(result) is type(self._data)
+ result = self._wrap_setop_result(other, result)
+ # error: "Index" has no attribute "_with_freq"; maybe "_with_infer"?
+ return result._with_freq(None)._with_freq("infer") # type: ignore[attr-defined]
+
+ else:
+ return self._fast_intersect(other, sort)
+
+ def _fast_intersect(self, other, sort):
+ # to make our life easier, "sort" the two ranges
+ if self[0] <= other[0]:
+ left, right = self, other
+ else:
+ left, right = other, self
+
+ # after sorting, the intersection always starts with the right index
+ # and ends with the index of which the last elements is smallest
+ end = min(left[-1], right[-1])
+ start = right[0]
+
+ if end < start:
+ result = self[:0]
+ else:
+ lslice = slice(*left.slice_locs(start, end))
+ result = left._values[lslice]
+
+ return result
+
+ def _can_fast_intersect(self, other: Self) -> bool:
+ # Note: we only get here with len(self) > 0 and len(other) > 0
+ if self.freq is None:
+ return False
+
+ elif other.freq != self.freq:
+ return False
+
+ elif not self.is_monotonic_increasing:
+ # Because freq is not None, we must then be monotonic decreasing
+ return False
+
+ # this along with matching freqs ensure that we "line up",
+ # so intersection will preserve freq
+ # Note we are assuming away Ticks, as those go through _range_intersect
+ # GH#42104
+ return self.freq.n == 1
+
+ def _can_fast_union(self, other: Self) -> bool:
+ # Assumes that type(self) == type(other), as per the annotation
+ # The ability to fast_union also implies that `freq` should be
+ # retained on union.
+ freq = self.freq
+
+ if freq is None or freq != other.freq:
+ return False
+
+ if not self.is_monotonic_increasing:
+ # Because freq is not None, we must then be monotonic decreasing
+ # TODO: do union on the reversed indexes?
+ return False
+
+ if len(self) == 0 or len(other) == 0:
+ # only reached via union_many
+ return True
+
+ # to make our life easier, "sort" the two ranges
+ if self[0] <= other[0]:
+ left, right = self, other
+ else:
+ left, right = other, self
+
+ right_start = right[0]
+ left_end = left[-1]
+
+ # Only need to "adjoin", not overlap
+ return (right_start == left_end + freq) or right_start in left
+
+ def _fast_union(self, other: Self, sort=None) -> Self:
+ # Caller is responsible for ensuring self and other are non-empty
+
+ # to make our life easier, "sort" the two ranges
+ if self[0] <= other[0]:
+ left, right = self, other
+ elif sort is False:
+ # TDIs are not in the "correct" order and we don't want
+ # to sort but want to remove overlaps
+ left, right = self, other
+ left_start = left[0]
+ loc = right.searchsorted(left_start, side="left")
+ right_chunk = right._values[:loc]
+ dates = concat_compat((left._values, right_chunk))
+ result = type(self)._simple_new(dates, name=self.name)
+ return result
+ else:
+ left, right = other, self
+
+ left_end = left[-1]
+ right_end = right[-1]
+
+ # concatenate
+ if left_end < right_end:
+ loc = right.searchsorted(left_end, side="right")
+ right_chunk = right._values[loc:]
+ dates = concat_compat([left._values, right_chunk])
+ # The can_fast_union check ensures that the result.freq
+ # should match self.freq
+ assert isinstance(dates, type(self._data))
+ # error: Item "ExtensionArray" of "ExtensionArray |
+ # ndarray[Any, Any]" has no attribute "_freq"
+ assert dates._freq == self.freq # type: ignore[union-attr]
+ result = type(self)._simple_new(dates)
+ return result
+ else:
+ return left
+
+ def _union(self, other, sort):
+ # We are called by `union`, which is responsible for this validation
+ assert isinstance(other, type(self))
+ assert self.dtype == other.dtype
+
+ if self._can_range_setop(other):
+ return self._range_union(other, sort=sort)
+
+ if self._can_fast_union(other):
+ result = self._fast_union(other, sort=sort)
+ # in the case with sort=None, the _can_fast_union check ensures
+ # that result.freq == self.freq
+ return result
+ else:
+ return super()._union(other, sort)._with_freq("infer")
+
+ # --------------------------------------------------------------------
+ # Join Methods
+
+ def _get_join_freq(self, other):
+ """
+ Get the freq to attach to the result of a join operation.
+ """
+ freq = None
+ if self._can_fast_union(other):
+ freq = self.freq
+ return freq
+
+ def _wrap_join_result(
+ self,
+ joined,
+ other,
+ lidx: npt.NDArray[np.intp] | None,
+ ridx: npt.NDArray[np.intp] | None,
+ how: JoinHow,
+ ) -> tuple[Self, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ assert other.dtype == self.dtype, (other.dtype, self.dtype)
+ join_index, lidx, ridx = super()._wrap_join_result(
+ joined, other, lidx, ridx, how
+ )
+ join_index._data._freq = self._get_join_freq(other)
+ return join_index, lidx, ridx
+
+ def _get_engine_target(self) -> np.ndarray:
+ # engine methods and libjoin methods need dt64/td64 values cast to i8
+ return self._data._ndarray.view("i8")
+
+ def _from_join_target(self, result: np.ndarray):
+ # view e.g. i8 back to M8[ns]
+ result = result.view(self._data._ndarray.dtype)
+ return self._data._from_backing_data(result)
+
+ def _searchsorted_monotonic(self, label, side: Literal["left", "right"] = "left"):
+ if (
+ self.is_monotonic_increasing
+ and isinstance(label, (Timestamp, Timedelta))
+ and abbrev_to_npy_unit(label.unit) > abbrev_to_npy_unit(self.unit)
+ ):
+ # For non-matching units we can safely round down (with side=right)
+ # This is needed for GH#63262
+ if side == "right":
+ label = label.as_unit(self.unit) # this should always be a round-down
+ else:
+ # round up
+ label = label.ceil(self.unit).as_unit(self.unit)
+
+ return super()._searchsorted_monotonic(label, side)
+
+ # --------------------------------------------------------------------
+ # List-like Methods
+
+ def _get_delete_freq(self, loc: int | slice | Sequence[int]):
+ """
+ Find the `freq` for self.delete(loc).
+ """
+ freq = None
+ if self.freq is not None:
+ if is_integer(loc):
+ if loc in (0, -len(self), -1, len(self) - 1):
+ freq = self.freq
+ else:
+ if is_list_like(loc):
+ # error: Incompatible types in assignment (expression has
+ # type "Union[slice, ndarray]", variable has type
+ # "Union[int, slice, Sequence[int]]")
+ loc = lib.maybe_indices_to_slice( # type: ignore[assignment]
+ np.asarray(loc, dtype=np.intp), len(self)
+ )
+ if isinstance(loc, slice) and loc.step in (1, None):
+ if loc.start in (0, None) or loc.stop in (len(self), None):
+ freq = self.freq
+ return freq
+
+ def _get_insert_freq(self, loc: int, item):
+ """
+ Find the `freq` for self.insert(loc, item).
+ """
+ value = self._data._validate_scalar(item)
+ item = self._data._box_func(value)
+
+ freq = None
+ if self.freq is not None:
+ # freq can be preserved on edge cases
+ if self.size:
+ if item is NaT:
+ pass
+ elif loc in (0, -len(self)) and item + self.freq == self[0]:
+ freq = self.freq
+ elif (loc == len(self)) and item - self.freq == self[-1]:
+ freq = self.freq
+ # Adding a single item to an empty index may preserve freq
+ elif isinstance(self.freq, Tick):
+ # all TimedeltaIndex cases go through here; is_on_offset
+ # would raise TypeError
+ freq = self.freq
+ elif self.freq.is_on_offset(item):
+ freq = self.freq
+ return freq
+
+ def delete(self, loc) -> Self:
+ """
+ Make new Index with passed location(-s) deleted.
+
+ Parameters
+ ----------
+ loc : int or list of int
+ Location of item(-s) which will be deleted.
+ Use a list of locations to delete more than one value at the same time.
+
+ Returns
+ -------
+ Index
+ Will be same type as self, except for RangeIndex.
+
+ See Also
+ --------
+ numpy.delete : Delete any rows and column from NumPy array (ndarray).
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.delete(1)
+ Index(['a', 'c'], dtype='str')
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.delete([0, 2])
+ Index(['b'], dtype='str')
+ """
+ result = super().delete(loc)
+ result._data._freq = self._get_delete_freq(loc)
+ return result
+
+ def insert(self, loc: int, item):
+ """
+ Make new Index inserting new item at location.
+ Follows Python numpy.insert semantics for negative values.
+
+ Parameters
+ ----------
+ loc : int
+ The integer location where the new item will be inserted.
+ item : object
+ The new item to be inserted into the Index.
+
+ Returns
+ -------
+ Index
+ Returns a new Index object resulting from inserting the specified item at
+ the specified location within the original Index.
+
+ See Also
+ --------
+ Index.append : Append a collection of Indexes together.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.insert(1, "x")
+ Index(['a', 'x', 'b', 'c'], dtype='str')
+ """
+ result = super().insert(loc, item)
+ if isinstance(result, type(self)):
+ # i.e. parent class method did not cast
+ result._data._freq = self._get_insert_freq(loc, item)
+ return result
+
+ # --------------------------------------------------------------------
+ # NDArray-Like Methods
+
+ def take(
+ self,
+ indices,
+ axis: Axis = 0,
+ allow_fill: bool = True,
+ fill_value=None,
+ **kwargs,
+ ) -> Self:
+ """
+ Return a new Index of the values selected by the indices.
+ For internal compatibility with numpy arrays.
+
+ Parameters
+ ----------
+ indices : array-like
+ Indices to be taken.
+ axis : {0 or 'index'}, optional
+ The axis over which to select values, always 0 or 'index'.
+ allow_fill : bool, default True
+ How to handle negative values in `indices`.
+ * False: negative values in `indices` indicate positional indices
+ from the right (the default). This is similar to
+ :func:`numpy.take`.
+ * True: negative values in `indices` indicate
+ missing values. These values are set to `fill_value`. Any other
+ other negative values raise a ``ValueError``.
+ fill_value : scalar, default None
+ If allow_fill=True and fill_value is not None, indices specified by
+ -1 are regarded as NA. If Index doesn't hold NA, raise ValueError.
+ **kwargs
+ Required for compatibility with numpy.
+
+ Returns
+ -------
+ Index
+ An index formed of elements at the given indices. Will be the same
+ type as self, except for RangeIndex.
+
+ See Also
+ --------
+ numpy.ndarray.take: Return an array formed from the
+ elements of a at the given indices.
+
+ Examples
+ --------
+ >>> idx = pd.Index(["a", "b", "c"])
+ >>> idx.take([2, 2, 1, 2])
+ Index(['c', 'c', 'b', 'c'], dtype='str')
+ """
+ nv.validate_take((), kwargs)
+ indices = np.asarray(indices, dtype=np.intp)
+
+ result = NDArrayBackedExtensionIndex.take(
+ self, indices, axis, allow_fill, fill_value, **kwargs
+ )
+
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(self))
+ if isinstance(maybe_slice, slice):
+ freq = self._data._get_getitem_freq(maybe_slice)
+ result._data._freq = freq
+ return result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/datetimes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/datetimes.py
new file mode 100644
index 0000000000000000000000000000000000000000..70e77f18cb9ef206c779263d7140636f03fff5bf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/datetimes.py
@@ -0,0 +1,1618 @@
+from __future__ import annotations
+
+import datetime as dt
+import operator
+from typing import (
+ TYPE_CHECKING,
+ Self,
+)
+import warnings
+
+import numpy as np
+
+from pandas._libs import (
+ NaT,
+ Period,
+ Timestamp,
+ index as libindex,
+ lib,
+)
+from pandas._libs.tslibs import (
+ Resolution,
+ Tick,
+ Timedelta,
+ periods_per_day,
+ timezones,
+ to_offset,
+)
+from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
+from pandas._libs.tslibs.offsets import (
+ DateOffset,
+ prefix_mapping,
+)
+from pandas.errors import Pandas4Warning
+from pandas.util._decorators import (
+ cache_readonly,
+ set_module,
+)
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core.dtypes.common import is_scalar
+from pandas.core.dtypes.dtypes import (
+ ArrowDtype,
+ DatetimeTZDtype,
+)
+from pandas.core.dtypes.generic import ABCSeries
+from pandas.core.dtypes.missing import is_valid_na_for_dtype
+
+from pandas.core.arrays.datetimes import (
+ DatetimeArray,
+ tz_to_dtype,
+)
+import pandas.core.common as com
+from pandas.core.indexes.base import (
+ Index,
+ maybe_extract_name,
+)
+from pandas.core.indexes.datetimelike import DatetimeTimedeltaMixin
+from pandas.core.indexes.extension import inherit_names
+from pandas.core.tools.times import to_time
+
+if TYPE_CHECKING:
+ from collections.abc import Hashable
+
+ from pandas._typing import (
+ Dtype,
+ DtypeObj,
+ Frequency,
+ IntervalClosedType,
+ TimeAmbiguous,
+ TimeNonexistent,
+ npt,
+ TimeUnit,
+ )
+
+ from pandas.core.api import (
+ DataFrame,
+ PeriodIndex,
+ )
+
+from pandas._libs.tslibs.dtypes import OFFSET_TO_PERIOD_FREQSTR
+
+
+def _new_DatetimeIndex(cls, d):
+ """
+ This is called upon unpickling, rather than the default which doesn't
+ have arguments and breaks __new__
+ """
+ if "data" in d and not isinstance(d["data"], DatetimeIndex):
+ # Avoid need to verify integrity by calling simple_new directly
+ data = d.pop("data")
+ if not isinstance(data, DatetimeArray):
+ # For backward compat with older pickles, we may need to construct
+ # a DatetimeArray to adapt to the newer _simple_new signature
+ tz = d.pop("tz")
+ freq = d.pop("freq")
+ dta = DatetimeArray._simple_new(data, dtype=tz_to_dtype(tz), freq=freq)
+ else:
+ dta = data
+ for key in ["tz", "freq"]:
+ # These are already stored in our DatetimeArray; if they are
+ # also in the pickle and don't match, we have a problem.
+ if key in d:
+ assert d[key] == getattr(dta, key)
+ d.pop(key)
+ result = cls._simple_new(dta, **d)
+ else:
+ with warnings.catch_warnings():
+ # TODO: If we knew what was going in to **d, we might be able to
+ # go through _simple_new instead
+ warnings.simplefilter("ignore")
+ result = cls.__new__(cls, **d)
+
+ return result
+
+
+@inherit_names(
+ DatetimeArray._field_ops
+ + [
+ method
+ for method in DatetimeArray._datetimelike_methods
+ if method not in ("tz_localize", "tz_convert", "strftime")
+ ],
+ DatetimeArray,
+ wrap=True,
+)
+@inherit_names(["is_normalized"], DatetimeArray, cache=True)
+@inherit_names(
+ [
+ "tz",
+ "tzinfo",
+ "dtype",
+ "to_pydatetime",
+ "date",
+ "time",
+ "timetz",
+ "std",
+ *DatetimeArray._bool_ops,
+ ],
+ DatetimeArray,
+)
+@set_module("pandas")
+class DatetimeIndex(DatetimeTimedeltaMixin):
+ """
+ Immutable ndarray-like of datetime64 data.
+
+ Represented internally as int64, and which can be boxed to Timestamp objects
+ that are subclasses of datetime and carry metadata.
+
+ .. versionchanged:: 2.0.0
+ The various numeric date/time attributes (:attr:`~DatetimeIndex.day`,
+ :attr:`~DatetimeIndex.month`, :attr:`~DatetimeIndex.year` etc.) now have dtype
+ ``int32``. Previously they had dtype ``int64``.
+
+ Parameters
+ ----------
+ data : array-like (1-dimensional)
+ Datetime-like data to construct index with.
+ freq : str or pandas offset object, optional
+ One of pandas date offset strings or corresponding objects. The string
+ 'infer' can be passed in order to set the frequency of the index as the
+ inferred frequency upon creation.
+ tz : zoneinfo.ZoneInfo, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or str
+ Set the Timezone of the data.
+ ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
+ When clocks moved backward due to DST, ambiguous times may arise.
+ For example in Central European Time (UTC+01), when going from 03:00
+ DST to 02:00 non-DST, 02:30:00 local time occurs both at 00:30:00 UTC
+ and at 01:30:00 UTC. In such a situation, the `ambiguous` parameter
+ dictates how ambiguous times should be handled.
+
+ - 'infer' will attempt to infer fall dst-transition hours based on
+ order
+ - bool-ndarray where True signifies a DST time, False signifies a
+ non-DST time (note that this flag is only applicable for ambiguous
+ times)
+ - 'NaT' will return NaT where there are ambiguous times
+ - 'raise' will raise a ValueError if there are ambiguous times.
+ dayfirst : bool, default False
+ If True, parse dates in `data` with the day first order.
+ yearfirst : bool, default False
+ If True parse dates in `data` with the year first order.
+ dtype : numpy.dtype or DatetimeTZDtype or str, default None
+ Note that the only NumPy dtype allowed is `datetime64[ns]`.
+ copy : bool, default None
+ Whether to copy input data, only relevant for array, Series, and Index
+ inputs (for other input, e.g. a list, a new array is created anyway).
+ Defaults to True for array input and False for Index/Series.
+ Set to False to avoid copying array input at your own risk (if you
+ know the input data won't be modified elsewhere).
+ Set to True to force copying Series/Index up front.
+ name : label, default None
+ Name to be stored in the index.
+
+ Attributes
+ ----------
+ year
+ month
+ day
+ hour
+ minute
+ second
+ microsecond
+ nanosecond
+ date
+ time
+ timetz
+ dayofyear
+ day_of_year
+ dayofweek
+ day_of_week
+ weekday
+ quarter
+ tz
+ freq
+ freqstr
+ is_month_start
+ is_month_end
+ is_quarter_start
+ is_quarter_end
+ is_year_start
+ is_year_end
+ is_leap_year
+ inferred_freq
+
+ Methods
+ -------
+ normalize
+ strftime
+ snap
+ tz_convert
+ tz_localize
+ round
+ floor
+ ceil
+ to_period
+ to_pydatetime
+ to_series
+ to_frame
+ to_julian_date
+ month_name
+ day_name
+ mean
+ std
+
+ See Also
+ --------
+ Index : The base pandas Index type.
+ TimedeltaIndex : Index of timedelta64 data.
+ PeriodIndex : Index of Period data.
+ to_datetime : Convert argument to datetime.
+ date_range : Create a fixed-frequency DatetimeIndex.
+
+ Notes
+ -----
+ To learn more about the frequency strings, please see
+ :ref:`this link`.
+
+ Examples
+ --------
+ >>> idx = pd.DatetimeIndex(["1/1/2020 10:00:00+00:00", "2/1/2020 11:00:00+00:00"])
+ >>> idx
+ DatetimeIndex(['2020-01-01 10:00:00+00:00', '2020-02-01 11:00:00+00:00'],
+ dtype='datetime64[us, UTC]', freq=None)
+ """
+
+ _typ = "datetimeindex"
+
+ _data_cls = DatetimeArray
+ _supports_partial_string_indexing = True
+
+ @property
+ def _engine_type(self) -> type[libindex.DatetimeEngine]:
+ return libindex.DatetimeEngine
+
+ _data: DatetimeArray
+ _values: DatetimeArray
+ tz: dt.tzinfo | None
+
+ # --------------------------------------------------------------------
+ # methods that dispatch to DatetimeArray and wrap result
+
+ def strftime(self, date_format) -> Index:
+ """
+ Convert to Index using specified date_format.
+
+ Return an Index of formatted strings specified by date_format, which
+ supports the same string format as the python standard library. Details
+ of the string format can be found in `python string format
+ doc `__.
+
+ Formats supported by the C `strftime` API but not by the python string format
+ doc (such as `"%R"`, `"%r"`) are not officially supported and should be
+ preferably replaced with their supported equivalents (such as `"%H:%M"`,
+ `"%I:%M:%S %p"`).
+ Note that `PeriodIndex` support additional directives, detailed in
+ `Period.strftime`.
+
+ Parameters
+ ----------
+ date_format : str
+ Date format string (e.g. "%Y-%m-%d").
+
+ Returns
+ -------
+ ndarray[object]
+ NumPy ndarray of formatted strings.
+
+ See Also
+ --------
+ to_datetime : Convert the given argument to datetime.
+ DatetimeIndex.normalize : Return DatetimeIndex with times to midnight.
+ DatetimeIndex.round : Round the DatetimeIndex to the specified freq.
+ DatetimeIndex.floor : Floor the DatetimeIndex to the specified freq.
+ Timestamp.strftime : Format a single Timestamp.
+ Period.strftime : Format a single Period.
+
+ Examples
+ --------
+ >>> rng = pd.date_range(pd.Timestamp("2018-03-10 09:00"), periods=3, freq="s")
+ >>> rng.strftime("%B %d, %Y, %r")
+ Index(['March 10, 2018, 09:00:00 AM', 'March 10, 2018, 09:00:01 AM',
+ 'March 10, 2018, 09:00:02 AM'],
+ dtype='str')
+ """
+ arr = self._data.strftime(date_format)
+ return Index(arr, name=self.name, dtype=arr.dtype, copy=False)
+
+ def tz_convert(self, tz) -> Self:
+ """
+ Convert tz-aware Datetime Array/Index from one time zone to another.
+
+ Parameters
+ ----------
+ tz : str, zoneinfo.ZoneInfo, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or None
+ Time zone for time. Corresponding timestamps would be converted
+ to this time zone of the Datetime Array/Index. A `tz` of None will
+ convert to UTC and remove the timezone information.
+
+ Returns
+ -------
+ Array or Index
+ Datetme Array/Index with target `tz`.
+
+ Raises
+ ------
+ TypeError
+ If Datetime Array/Index is tz-naive.
+
+ See Also
+ --------
+ DatetimeIndex.tz : A timezone that has a variable offset from UTC.
+ DatetimeIndex.tz_localize : Localize tz-naive DatetimeIndex to a
+ given time zone, or remove timezone from a tz-aware DatetimeIndex.
+
+ Examples
+ --------
+ With the `tz` parameter, we can change the DatetimeIndex
+ to other time zones:
+
+ >>> dti = pd.date_range(
+ ... start="2014-08-01 09:00", freq="h", periods=3, tz="Europe/Berlin"
+ ... )
+
+ >>> dti
+ DatetimeIndex(['2014-08-01 09:00:00+02:00',
+ '2014-08-01 10:00:00+02:00',
+ '2014-08-01 11:00:00+02:00'],
+ dtype='datetime64[us, Europe/Berlin]', freq='h')
+
+ >>> dti.tz_convert("US/Central")
+ DatetimeIndex(['2014-08-01 02:00:00-05:00',
+ '2014-08-01 03:00:00-05:00',
+ '2014-08-01 04:00:00-05:00'],
+ dtype='datetime64[us, US/Central]', freq='h')
+
+ With the ``tz=None``, we can remove the timezone (after converting
+ to UTC if necessary):
+
+ >>> dti = pd.date_range(
+ ... start="2014-08-01 09:00", freq="h", periods=3, tz="Europe/Berlin"
+ ... )
+
+ >>> dti
+ DatetimeIndex(['2014-08-01 09:00:00+02:00',
+ '2014-08-01 10:00:00+02:00',
+ '2014-08-01 11:00:00+02:00'],
+ dtype='datetime64[us, Europe/Berlin]', freq='h')
+
+ >>> dti.tz_convert(None)
+ DatetimeIndex(['2014-08-01 07:00:00',
+ '2014-08-01 08:00:00',
+ '2014-08-01 09:00:00'],
+ dtype='datetime64[us]', freq='h')
+ """ # noqa: E501
+ arr = self._data.tz_convert(tz)
+ return type(self)._simple_new(arr, name=self.name, refs=self._references)
+
+ def tz_localize(
+ self,
+ tz,
+ ambiguous: TimeAmbiguous = "raise",
+ nonexistent: TimeNonexistent = "raise",
+ ) -> Self:
+ """
+ Localize tz-naive Datetime Array/Index to tz-aware Datetime Array/Index.
+
+ This method takes a time zone (tz) naive Datetime Array/Index object
+ and makes this time zone aware. It does not move the time to another
+ time zone.
+
+ This method can also be used to do the inverse -- to create a time
+ zone unaware object from an aware object. To that end, pass `tz=None`.
+
+ Parameters
+ ----------
+ tz : str, zoneinfo.ZoneInfo,, pytz.timezone, dateutil.tz.tzfile, datetime.tzinfo or None
+ Time zone to convert timestamps to. Passing ``None`` will
+ remove the time zone information preserving local time.
+ ambiguous : 'infer', 'NaT', bool array, default 'raise'
+ When clocks moved backward due to DST, ambiguous times may arise.
+ For example in Central European Time (UTC+01), when going from
+ 03:00 DST to 02:00 non-DST, 02:30:00 local time occurs both at
+ 00:30:00 UTC and at 01:30:00 UTC. In such a situation, the
+ `ambiguous` parameter dictates how ambiguous times should be
+ handled.
+
+ - 'infer' will attempt to infer fall dst-transition hours based on
+ order
+ - bool-ndarray where True signifies a DST time, False signifies a
+ non-DST time (note that this flag is only applicable for
+ ambiguous times)
+ - 'NaT' will return NaT where there are ambiguous times
+ - 'raise' will raise a ValueError if there are ambiguous
+ times.
+
+ nonexistent : 'shift_forward', 'shift_backward, 'NaT', timedelta, \
+ default 'raise'
+ A nonexistent time does not exist in a particular timezone
+ where clocks moved forward due to DST.
+
+ - 'shift_forward' will shift the nonexistent time forward to the
+ closest existing time
+ - 'shift_backward' will shift the nonexistent time backward to the
+ closest existing time
+ - 'NaT' will return NaT where there are nonexistent times
+ - timedelta objects will shift nonexistent times by the timedelta
+ - 'raise' will raise a ValueError if there are
+ nonexistent times.
+
+ Returns
+ -------
+ Same type as self
+ Array/Index converted to the specified time zone.
+
+ Raises
+ ------
+ TypeError
+ If the Datetime Array/Index is tz-aware and tz is not None.
+
+ See Also
+ --------
+ DatetimeIndex.tz_convert : Convert tz-aware DatetimeIndex from
+ one time zone to another.
+
+ Examples
+ --------
+ >>> tz_naive = pd.date_range('2018-03-01 09:00', periods=3)
+ >>> tz_naive
+ DatetimeIndex(['2018-03-01 09:00:00', '2018-03-02 09:00:00',
+ '2018-03-03 09:00:00'],
+ dtype='datetime64[us]', freq='D')
+
+ Localize DatetimeIndex in US/Eastern time zone:
+
+ >>> tz_aware = tz_naive.tz_localize(tz='US/Eastern')
+ >>> tz_aware
+ DatetimeIndex(['2018-03-01 09:00:00-05:00',
+ '2018-03-02 09:00:00-05:00',
+ '2018-03-03 09:00:00-05:00'],
+ dtype='datetime64[us, US/Eastern]', freq=None)
+
+ With the ``tz=None``, we can remove the time zone information
+ while keeping the local time (not converted to UTC):
+
+ >>> tz_aware.tz_localize(None)
+ DatetimeIndex(['2018-03-01 09:00:00', '2018-03-02 09:00:00',
+ '2018-03-03 09:00:00'],
+ dtype='datetime64[us]', freq=None)
+
+ Be careful with DST changes. When there is sequential data, pandas can
+ infer the DST time:
+
+ >>> s = pd.to_datetime(pd.Series(['2018-10-28 01:30:00',
+ ... '2018-10-28 02:00:00',
+ ... '2018-10-28 02:30:00',
+ ... '2018-10-28 02:00:00',
+ ... '2018-10-28 02:30:00',
+ ... '2018-10-28 03:00:00',
+ ... '2018-10-28 03:30:00']))
+ >>> s.dt.tz_localize('CET', ambiguous='infer')
+ 0 2018-10-28 01:30:00+02:00
+ 1 2018-10-28 02:00:00+02:00
+ 2 2018-10-28 02:30:00+02:00
+ 3 2018-10-28 02:00:00+01:00
+ 4 2018-10-28 02:30:00+01:00
+ 5 2018-10-28 03:00:00+01:00
+ 6 2018-10-28 03:30:00+01:00
+ dtype: datetime64[us, CET]
+
+ In some cases, inferring the DST is impossible. In such cases, you can
+ pass an ndarray to the ambiguous parameter to set the DST explicitly
+
+ >>> s = pd.to_datetime(pd.Series(['2018-10-28 01:20:00',
+ ... '2018-10-28 02:36:00',
+ ... '2018-10-28 03:46:00']))
+ >>> s.dt.tz_localize('CET', ambiguous=np.array([True, True, False]))
+ 0 2018-10-28 01:20:00+02:00
+ 1 2018-10-28 02:36:00+02:00
+ 2 2018-10-28 03:46:00+01:00
+ dtype: datetime64[us, CET]
+
+ If the DST transition causes nonexistent times, you can shift these
+ dates forward or backwards with a timedelta object or `'shift_forward'`
+ or `'shift_backwards'`.
+
+ >>> s = pd.to_datetime(pd.Series(['2015-03-29 02:30:00',
+ ... '2015-03-29 03:30:00'], dtype="M8[ns]"))
+ >>> s.dt.tz_localize('Europe/Warsaw', nonexistent='shift_forward')
+ 0 2015-03-29 03:00:00+02:00
+ 1 2015-03-29 03:30:00+02:00
+ dtype: datetime64[ns, Europe/Warsaw]
+
+ >>> s.dt.tz_localize('Europe/Warsaw', nonexistent='shift_backward')
+ 0 2015-03-29 01:59:59.999999999+01:00
+ 1 2015-03-29 03:30:00+02:00
+ dtype: datetime64[ns, Europe/Warsaw]
+
+ >>> s.dt.tz_localize('Europe/Warsaw', nonexistent=pd.Timedelta('1h'))
+ 0 2015-03-29 03:30:00+02:00
+ 1 2015-03-29 03:30:00+02:00
+ dtype: datetime64[ns, Europe/Warsaw]
+ """ # noqa: E501
+ arr = self._data.tz_localize(tz, ambiguous, nonexistent)
+ return type(self)._simple_new(arr, name=self.name)
+
+ def to_period(self, freq=None) -> PeriodIndex:
+ """
+ Cast to PeriodArray/PeriodIndex at a particular frequency.
+
+ Converts DatetimeArray/Index to PeriodArray/PeriodIndex.
+
+ Parameters
+ ----------
+ freq : str or Period, optional
+ One of pandas' :ref:`period aliases `
+ or a Period object. Will be inferred by default.
+
+ Returns
+ -------
+ PeriodArray/PeriodIndex
+ Immutable ndarray holding ordinal values at a particular frequency.
+
+ Raises
+ ------
+ ValueError
+ When converting a DatetimeArray/Index with non-regular values,
+ so that a frequency cannot be inferred.
+
+ See Also
+ --------
+ PeriodIndex: Immutable ndarray holding ordinal values.
+ DatetimeIndex.to_pydatetime: Return DatetimeIndex as object.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame(
+ ... {"y": [1, 2, 3]},
+ ... index=pd.to_datetime(
+ ... [
+ ... "2000-03-31 00:00:00",
+ ... "2000-05-31 00:00:00",
+ ... "2000-08-31 00:00:00",
+ ... ]
+ ... ),
+ ... )
+ >>> df.index.to_period("M")
+ PeriodIndex(['2000-03', '2000-05', '2000-08'],
+ dtype='period[M]')
+
+ Infer the daily frequency
+
+ >>> idx = pd.date_range("2017-01-01", periods=2)
+ >>> idx.to_period()
+ PeriodIndex(['2017-01-01', '2017-01-02'],
+ dtype='period[D]')
+ """
+ from pandas.core.indexes.api import PeriodIndex
+
+ arr = self._data.to_period(freq)
+ return PeriodIndex._simple_new(arr, name=self.name)
+
+ def to_julian_date(self) -> Index:
+ """
+ Convert TimeStamp to a Julian Date.
+
+ This method returns the number of days as a float since noon January 1, 4713 BC.
+
+ https://en.wikipedia.org/wiki/Julian_day
+
+ Returns
+ -------
+ ndarray or Index
+ Float values that represent each date in Julian Calendar.
+
+ See Also
+ --------
+ Timestamp.to_julian_date : Equivalent method on ``Timestamp`` objects.
+
+ Examples
+ --------
+ >>> idx = pd.DatetimeIndex(["2028-08-12 00:54", "2028-08-12 02:06"])
+ >>> idx.to_julian_date()
+ Index([2461995.5375, 2461995.5875], dtype='float64')
+ """
+ arr = self._data.to_julian_date()
+ return Index._simple_new(arr, name=self.name)
+
+ def isocalendar(self) -> DataFrame:
+ """
+ Calculate year, week, and day according to the ISO 8601 standard.
+ Returns
+ -------
+ DataFrame
+ With columns year, week and day.
+ See Also
+ --------
+ Timestamp.isocalendar : Function return a 3-tuple containing ISO year,
+ week number, and weekday for the given Timestamp object.
+ datetime.date.isocalendar : Return a named tuple object with
+ three components: year, week and weekday.
+
+ Examples
+ --------
+ >>> idx = pd.date_range(start="2019-12-29", freq="D", periods=4)
+ >>> idx.isocalendar()
+ year week day
+ 2019-12-29 2019 52 7
+ 2019-12-30 2020 1 1
+ 2019-12-31 2020 1 2
+ 2020-01-01 2020 1 3
+ >>> idx.isocalendar().week
+ 2019-12-29 52
+ 2019-12-30 1
+ 2019-12-31 1
+ 2020-01-01 1
+ Freq: D, Name: week, dtype: UInt32
+ """
+ df = self._data.isocalendar()
+ return df.set_index(self)
+
+ @cache_readonly
+ def _resolution_obj(self) -> Resolution:
+ return self._data._resolution_obj
+
+ # --------------------------------------------------------------------
+ # Constructors
+
+ def __new__(
+ cls,
+ data=None,
+ freq: Frequency | lib.NoDefault = lib.no_default,
+ tz=lib.no_default,
+ ambiguous: TimeAmbiguous = "raise",
+ dayfirst: bool = False,
+ yearfirst: bool = False,
+ dtype: Dtype | None = None,
+ copy: bool | None = None,
+ name: Hashable | None = None,
+ ) -> Self:
+ if is_scalar(data):
+ cls._raise_scalar_data_error(data)
+
+ # - Cases checked above all return/raise before reaching here - #
+
+ name = maybe_extract_name(name, data, cls)
+
+ # GH#63388
+ data, copy = cls._maybe_copy_array_input(data, copy, dtype)
+
+ if (
+ isinstance(data, DatetimeArray)
+ and freq is lib.no_default
+ and tz is lib.no_default
+ and dtype is None
+ ):
+ # fastpath, similar logic in TimedeltaIndex.__new__;
+ # Note in this particular case we retain non-nano.
+ if copy:
+ data = data.copy()
+ return cls._simple_new(data, name=name)
+
+ dtarr = DatetimeArray._from_sequence_not_strict(
+ data,
+ dtype=dtype,
+ copy=copy,
+ tz=tz,
+ freq=freq,
+ dayfirst=dayfirst,
+ yearfirst=yearfirst,
+ ambiguous=ambiguous,
+ )
+ refs = None
+ if not copy and isinstance(data, (Index, ABCSeries)):
+ refs = data._references
+
+ subarr = cls._simple_new(dtarr, name=name, refs=refs)
+ return subarr
+
+ # --------------------------------------------------------------------
+
+ @cache_readonly
+ def _is_dates_only(self) -> bool:
+ """
+ Return a boolean if we are only dates (and don't have a timezone)
+
+ Returns
+ -------
+ bool
+ """
+ if isinstance(self.freq, Tick):
+ delta = Timedelta(self.freq)
+
+ if delta % dt.timedelta(days=1) != dt.timedelta(days=0):
+ return False
+
+ return self._values._is_dates_only
+
+ def __reduce__(self):
+ d = {"data": self._data, "name": self.name}
+ return _new_DatetimeIndex, (type(self), d), None
+
+ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
+ """
+ Can we compare values of the given dtype to our own?
+ """
+ if isinstance(dtype, ArrowDtype):
+ # GH#62277
+ if dtype.kind != "M":
+ return False
+
+ pa_dtype = dtype.pyarrow_dtype
+ if (pa_dtype.tz is None) ^ (self.tz is None):
+ return False
+ return True
+
+ if self.tz is not None:
+ # If we have tz, we can compare to tzaware
+ return isinstance(dtype, DatetimeTZDtype)
+ # if we dont have tz, we can only compare to tznaive
+ return lib.is_np_dtype(dtype, "M")
+
+ # --------------------------------------------------------------------
+ # Rendering Methods
+
+ @cache_readonly
+ def _formatter_func(self):
+ # Note this is equivalent to the DatetimeIndexOpsMixin method but
+ # uses the maybe-cached self._is_dates_only instead of re-computing it.
+ from pandas.io.formats.format import get_format_datetime64
+
+ formatter = get_format_datetime64(is_dates_only=self._is_dates_only)
+ return lambda x: f"'{formatter(x)}'"
+
+ # --------------------------------------------------------------------
+ # Set Operation Methods
+
+ def _can_range_setop(self, other) -> bool:
+ # GH 46702: If self or other have non-UTC tzs, DST transitions prevent
+ # range representation due to no singular step
+ if (
+ self.tz is not None
+ and not timezones.is_utc(self.tz)
+ and not timezones.is_fixed_offset(self.tz)
+ ):
+ return False
+ if (
+ other.tz is not None
+ and not timezones.is_utc(other.tz)
+ and not timezones.is_fixed_offset(other.tz)
+ ):
+ return False
+ return super()._can_range_setop(other)
+
+ # --------------------------------------------------------------------
+
+ def _get_time_micros(self) -> npt.NDArray[np.int64]:
+ """
+ Return the number of microseconds since midnight.
+
+ Returns
+ -------
+ ndarray[int64_t]
+ """
+ values = self._data._local_timestamps()
+
+ ppd = periods_per_day(self._data._creso)
+
+ frac = values % ppd
+ if self.unit == "ns":
+ micros = frac // 1000
+ elif self.unit == "us":
+ micros = frac
+ elif self.unit == "ms":
+ micros = frac * 1000
+ elif self.unit == "s":
+ micros = frac * 1_000_000
+ else: # pragma: no cover
+ raise NotImplementedError(self.unit)
+
+ micros[self._isnan] = -1
+ return micros
+
+ def snap(self, freq: Frequency = "S") -> DatetimeIndex:
+ """
+ Snap time stamps to nearest occurring frequency.
+
+ Parameters
+ ----------
+ freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'S'
+ Frequency strings can have multiples, e.g. '5h'. See
+ :ref:`here ` for a list of
+ frequency aliases.
+
+ Returns
+ -------
+ DatetimeIndex
+ Time stamps to nearest occurring `freq`.
+
+ See Also
+ --------
+ DatetimeIndex.round : Perform round operation on the data to the
+ specified `freq`.
+ DatetimeIndex.floor : Perform floor operation on the data to the
+ specified `freq`.
+
+ Examples
+ --------
+ >>> idx = pd.DatetimeIndex(
+ ... ["2023-01-01", "2023-01-02", "2023-02-01", "2023-02-02"],
+ ... dtype="M8[ns]",
+ ... )
+ >>> idx
+ DatetimeIndex(['2023-01-01', '2023-01-02', '2023-02-01', '2023-02-02'],
+ dtype='datetime64[ns]', freq=None)
+ >>> idx.snap("MS")
+ DatetimeIndex(['2023-01-01', '2023-01-01', '2023-02-01', '2023-02-01'],
+ dtype='datetime64[ns]', freq=None)
+ """
+ # Superdumb, punting on any optimizing
+ freq = to_offset(freq)
+
+ dta = self._data.copy()
+
+ for i, v in enumerate(self):
+ s = v
+ if not freq.is_on_offset(s):
+ t0 = freq.rollback(s)
+ t1 = freq.rollforward(s)
+ if abs(s - t0) < abs(t1 - s):
+ s = t0
+ else:
+ s = t1
+ dta[i] = s
+
+ return DatetimeIndex._simple_new(dta, name=self.name)
+
+ # --------------------------------------------------------------------
+ # Indexing Methods
+
+ def _parsed_string_to_bounds(
+ self, reso: Resolution, parsed: dt.datetime
+ ) -> tuple[Timestamp, Timestamp]:
+ """
+ Calculate datetime bounds for parsed time string and its resolution.
+
+ Parameters
+ ----------
+ reso : Resolution
+ Resolution provided by parsed string.
+ parsed : datetime
+ Datetime from parsed string.
+
+ Returns
+ -------
+ lower, upper: pd.Timestamp
+ """
+ freq = OFFSET_TO_PERIOD_FREQSTR.get(reso.attr_abbrev, reso.attr_abbrev)
+ per = Period(parsed, freq=freq)
+ start = per.start_time
+ # Can't use end_time here bc that will subtract a microsecond
+ # instead of a nanosecond
+ end = (per + 1).start_time - np.timedelta64(1, "ns")
+ start = start.as_unit(self.unit)
+ end = end.as_unit(self.unit)
+
+ # GH 24076
+ # If an incoming date string contained a UTC offset, need to localize
+ # the parsed date to this offset first before aligning with the index's
+ # timezone
+ start = start.tz_localize(parsed.tzinfo)
+ end = end.tz_localize(parsed.tzinfo)
+
+ if parsed.tzinfo is not None:
+ if self.tz is None:
+ raise ValueError(
+ "The index must be timezone aware when indexing "
+ "with a date string with a UTC offset"
+ )
+ # The flipped case with parsed.tz is None and self.tz is not None
+ # is ruled out bc parsed and reso are produced by _parse_with_reso,
+ # which localizes parsed.
+ return start, end
+
+ def _parse_with_reso(self, label: str) -> tuple[Timestamp, Resolution]:
+ parsed, reso = super()._parse_with_reso(label)
+
+ parsed = Timestamp(parsed)
+
+ if self.tz is not None and parsed.tzinfo is None:
+ # we special-case timezone-naive strings and timezone-aware
+ # DatetimeIndex
+ # https://github.com/pandas-dev/pandas/pull/36148#issuecomment-687883081
+ parsed = parsed.tz_localize(self.tz)
+
+ return parsed, reso
+
+ def _disallow_mismatched_indexing(self, key) -> None:
+ """
+ Check for mismatched-tzawareness indexing and re-raise as KeyError.
+ """
+ # we get here with isinstance(key, self._data._recognized_scalars)
+ try:
+ # GH#36148
+ self._data._assert_tzawareness_compat(key)
+ except TypeError as err:
+ raise KeyError(key) from err
+
+ def get_loc(self, key):
+ """
+ Get integer location for requested label
+
+ Returns
+ -------
+ loc : int
+ """
+ self._check_indexing_error(key)
+
+ orig_key = key
+ if is_valid_na_for_dtype(key, self.dtype):
+ key = NaT
+
+ if isinstance(key, self._data._recognized_scalars):
+ # needed to localize naive datetimes
+ self._disallow_mismatched_indexing(key)
+ key = Timestamp(key)
+
+ elif isinstance(key, str):
+ try:
+ parsed, reso = self._parse_with_reso(key)
+ except ValueError as err:
+ raise KeyError(key) from err
+ self._disallow_mismatched_indexing(parsed)
+
+ if self._can_partial_date_slice(reso):
+ try:
+ return self._partial_date_slice(reso, parsed)
+ except KeyError as err:
+ raise KeyError(key) from err
+
+ key = parsed
+
+ elif isinstance(key, dt.timedelta):
+ # GH#20464
+ raise TypeError(
+ f"Cannot index {type(self).__name__} with {type(key).__name__}"
+ )
+
+ elif isinstance(key, dt.time):
+ return self.indexer_at_time(key)
+
+ else:
+ # unrecognized type
+ raise KeyError(key)
+
+ try:
+ return Index.get_loc(self, key)
+ except KeyError as err:
+ raise KeyError(orig_key) from err
+
+ def _maybe_cast_slice_bound(self, label, side: str):
+ """
+ This function should be overloaded in subclasses that allow non-trivial
+ casting on label-slice bounds, e.g. datetime-like indices allowing
+ strings containing formatted datetimes.
+
+ Parameters
+ ----------
+ label : object
+ side : {'left', 'right'}
+
+ Returns
+ -------
+ label : object
+
+ Notes
+ -----
+ Value of `side` parameter should be validated in caller.
+ """
+ # GH#42855 handle date here instead of get_slice_bound
+ if isinstance(label, dt.date) and not isinstance(label, dt.datetime):
+ # Pandas supports slicing with dates, treated as datetimes at midnight.
+ # https://github.com/pandas-dev/pandas/issues/31501
+ label = Timestamp(label).to_pydatetime()
+ warnings.warn(
+ # GH#35830 deprecate last remaining inconsistent date treatment
+ "Slicing with a datetime.date object is deprecated. "
+ "Explicitly cast to Timestamp instead.",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+
+ label = super()._maybe_cast_slice_bound(label, side)
+ self._data._assert_tzawareness_compat(label)
+ return Timestamp(label)
+
+ def slice_indexer(self, start=None, end=None, step=None):
+ """
+ Return indexer for specified label slice.
+ Index.slice_indexer, customized to handle time slicing.
+
+ In addition to functionality provided by Index.slice_indexer, does the
+ following:
+
+ - if both `start` and `end` are instances of `datetime.time`, it
+ invokes `indexer_between_time`
+ - if `start` and `end` are both either string or None perform
+ value-based selection in non-monotonic cases.
+
+ """
+ # For historical reasons DatetimeIndex supports slices between two
+ # instances of datetime.time as if it were applying a slice mask to
+ # an array of (self.hour, self.minute, self.seconds, self.microsecond).
+ if isinstance(start, dt.time) and isinstance(end, dt.time):
+ if step is not None and step != 1:
+ raise ValueError("Must have step size of 1 with time slices")
+ return self.indexer_between_time(start, end)
+
+ if isinstance(start, dt.time) or isinstance(end, dt.time):
+ raise KeyError("Cannot mix time and non-time slice keys")
+
+ def check_str_or_none(point) -> bool:
+ return point is not None and not isinstance(point, str)
+
+ # GH#33146 if start and end are combinations of str and None and Index is not
+ # monotonic, we can not use Index.slice_indexer because it does not honor the
+ # actual elements, is only searching for start and end
+ if (
+ check_str_or_none(start)
+ or check_str_or_none(end)
+ or self.is_monotonic_increasing
+ ):
+ return Index.slice_indexer(self, start, end, step)
+
+ mask = np.array(True)
+ in_index = True
+ if start is not None:
+ start_casted = self._maybe_cast_slice_bound(start, "left")
+ mask = start_casted <= self
+ in_index &= (start_casted == self).any()
+
+ if end is not None:
+ end_casted = self._maybe_cast_slice_bound(end, "right")
+ mask = (self <= end_casted) & mask
+ in_index &= (end_casted == self).any()
+
+ if not in_index:
+ raise KeyError(
+ "Value based partial slicing on non-monotonic DatetimeIndexes "
+ "with non-existing keys is not allowed.",
+ )
+ indexer = mask.nonzero()[0][::step]
+ if len(indexer) == len(self):
+ return slice(None)
+ else:
+ return indexer
+
+ # --------------------------------------------------------------------
+
+ @property
+ def inferred_type(self) -> str:
+ # b/c datetime is represented as microseconds since the epoch, make
+ # sure we can't have ambiguous indexing
+ return "datetime64"
+
+ def indexer_at_time(self, time, asof: bool = False) -> npt.NDArray[np.intp]:
+ """
+ Return index locations of values at particular time of day.
+
+ Parameters
+ ----------
+ time : datetime.time or str
+ Time passed in either as object (datetime.time) or as string in
+ appropriate format ("%H:%M", "%H%M", "%I:%M%p", "%I%M%p",
+ "%H:%M:%S", "%H%M%S", "%I:%M:%S%p", "%I%M%S%p").
+ asof : bool, default False
+ This parameter is currently not supported.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ Index locations of values at given `time` of day.
+
+ See Also
+ --------
+ indexer_between_time : Get index locations of values between particular
+ times of day.
+ DataFrame.at_time : Select values at particular time of day.
+
+ Examples
+ --------
+ >>> idx = pd.DatetimeIndex(
+ ... ["1/1/2020 10:00", "2/1/2020 11:00", "3/1/2020 10:00"]
+ ... )
+ >>> idx.indexer_at_time("10:00")
+ array([0, 2])
+ """
+ if asof:
+ raise NotImplementedError("'asof' argument is not supported")
+
+ if isinstance(time, str):
+ from dateutil.parser import parse
+
+ orig = time
+ try:
+ alt = to_time(time)
+ except ValueError:
+ warnings.warn(
+ # GH#50839
+ f"The string '{orig}' cannot be parsed using pd.core.tools.to_time "
+ f"and in a future version will raise. "
+ "Use an unambiguous time string format or explicitly cast to "
+ "`datetime.time` before calling.",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+ time = parse(time).time()
+ else:
+ try:
+ time = parse(time).time()
+ except ValueError:
+ # e.g. '23550' raises dateutil.parser._parser.ParserError
+ time = alt
+ if alt != time:
+ warnings.warn(
+ # GH#50839
+ f"The string '{orig}' is currently parsed as {time} "
+ f"but in a future version will be parsed as {alt}, consistent"
+ "with `between_time` behavior. To avoid this warning, "
+ "use an unambiguous string format or explicitly cast to "
+ "`datetime.time` before calling.",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+
+ if time.tzinfo:
+ if self.tz is None:
+ raise ValueError("Index must be timezone aware.")
+ time_micros = self.tz_convert(time.tzinfo)._get_time_micros()
+ else:
+ time_micros = self._get_time_micros()
+ micros = _time_to_micros(time)
+ return (time_micros == micros).nonzero()[0]
+
+ def indexer_between_time(
+ self, start_time, end_time, include_start: bool = True, include_end: bool = True
+ ) -> npt.NDArray[np.intp]:
+ """
+ Return index locations of values between particular times of day.
+
+ Parameters
+ ----------
+ start_time, end_time : datetime.time, str
+ Time passed either as object (datetime.time) or as string in
+ appropriate format ("%H:%M", "%H%M", "%I:%M%p", "%I%M%p",
+ "%H:%M:%S", "%H%M%S", "%I:%M:%S%p","%I%M%S%p").
+ include_start : bool, default True
+ Include boundaries; whether to set start bound as closed or open.
+ include_end : bool, default True
+ Include boundaries; whether to set end bound as closed or open.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ Index locations of values between particular times of day.
+
+ See Also
+ --------
+ indexer_at_time : Get index locations of values at particular time of day.
+ DataFrame.between_time : Select values between particular times of day.
+
+ Examples
+ --------
+ >>> idx = pd.date_range("2023-01-01", periods=4, freq="h")
+ >>> idx
+ DatetimeIndex(['2023-01-01 00:00:00', '2023-01-01 01:00:00',
+ '2023-01-01 02:00:00', '2023-01-01 03:00:00'],
+ dtype='datetime64[us]', freq='h')
+ >>> idx.indexer_between_time("00:00", "2:00", include_end=False)
+ array([0, 1])
+ """
+ start_time = to_time(start_time)
+ end_time = to_time(end_time)
+ time_micros = self._get_time_micros()
+ start_micros = _time_to_micros(start_time)
+ end_micros = _time_to_micros(end_time)
+
+ if include_start and include_end:
+ lop = rop = operator.le
+ elif include_start:
+ lop = operator.le
+ rop = operator.lt
+ elif include_end:
+ lop = operator.lt
+ rop = operator.le
+ else:
+ lop = rop = operator.lt
+
+ if start_time <= end_time:
+ join_op = operator.and_
+ else:
+ join_op = operator.or_
+
+ mask = join_op(lop(start_micros, time_micros), rop(time_micros, end_micros))
+
+ return mask.nonzero()[0]
+
+
+@set_module("pandas")
+def date_range(
+ start=None,
+ end=None,
+ periods=None,
+ freq=None,
+ tz=None,
+ normalize: bool = False,
+ name: Hashable | None = None,
+ inclusive: IntervalClosedType = "both",
+ *,
+ unit: TimeUnit | None = None,
+ **kwargs,
+) -> DatetimeIndex:
+ """
+ Return a fixed frequency DatetimeIndex.
+
+ Returns the range of equally spaced time points (where the difference between any
+ two adjacent points is specified by the given frequency) such that they fall in the
+ range `[start, end]` , where the first one and the last one are, resp., the first
+ and last time points in that range that fall on the boundary of ``freq`` (if given
+ as a frequency string) or that are valid for ``freq`` (if given as a
+ :class:`pandas.tseries.offsets.DateOffset`). If ``freq`` is positive, the points
+ satisfy `start <[=] x <[=] end`, and if ``freq`` is negative, the points satisfy
+ `end <[=] x <[=] start`. (If exactly one of ``start``, ``end``, or ``freq`` is *not*
+ specified, this missing parameter can be computed given ``periods``, the number of
+ timesteps in the range. See the note below.)
+
+ Parameters
+ ----------
+ start : str or datetime-like, optional
+ Left bound for generating dates.
+ end : str or datetime-like, optional
+ Right bound for generating dates.
+ periods : int, optional
+ Number of periods to generate.
+ freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'D'
+ Frequency strings can have multiples, e.g. '5h'. See
+ :ref:`here ` for a list of
+ frequency aliases.
+ tz : str or tzinfo, optional
+ Time zone name for returning localized DatetimeIndex, for example
+ 'Asia/Hong_Kong'. By default, the resulting DatetimeIndex is
+ timezone-naive unless timezone-aware datetime-likes are passed.
+ normalize : bool, default False
+ Normalize start/end dates to midnight before generating date range.
+ name : Hashable, default None
+ Name of the resulting DatetimeIndex.
+ inclusive : {"both", "neither", "left", "right"}, default "both"
+ Include boundaries; Whether to set each bound as closed or open.
+ unit : {'s', 'ms', 'us', 'ns', None}, default None
+ Specify the desired resolution of the result.
+ If not specified, this is inferred from the 'start', 'end', and 'freq'
+ using the same inference as :class:`Timestamp` taking the highest
+ resolution of the three that are provided.
+
+ .. versionadded:: 2.0.0
+ **kwargs
+ For compatibility. Has no effect on the result.
+
+ Returns
+ -------
+ DatetimeIndex
+ A DatetimeIndex object of the generated dates.
+
+ See Also
+ --------
+ DatetimeIndex : An immutable container for datetimes.
+ timedelta_range : Return a fixed frequency TimedeltaIndex.
+ period_range : Return a fixed frequency PeriodIndex.
+ interval_range : Return a fixed frequency IntervalIndex.
+
+ Notes
+ -----
+ Of the four parameters ``start``, ``end``, ``periods``, and ``freq``,
+ a maximum of three can be specified at once. Of the three parameters
+ ``start``, ``end``, and ``periods``, at least two must be specified.
+ If ``freq`` is omitted, the resulting ``DatetimeIndex`` will have
+ ``periods`` linearly spaced elements between ``start`` and ``end``
+ (closed on both sides).
+
+ To learn more about the frequency strings, please see
+ :ref:`this link`.
+
+ Examples
+ --------
+ **Specifying the values**
+
+ The next four examples generate the same `DatetimeIndex`, but vary
+ the combination of `start`, `end` and `periods`.
+
+ Specify `start` and `end`, with the default daily frequency.
+
+ >>> pd.date_range(start="1/1/2018", end="1/08/2018")
+ DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
+ '2018-01-05', '2018-01-06', '2018-01-07', '2018-01-08'],
+ dtype='datetime64[us]', freq='D')
+
+ Specify timezone-aware `start` and `end`, with the default daily frequency.
+
+ >>> pd.date_range(
+ ... start=pd.to_datetime("1/1/2018").tz_localize("Europe/Berlin"),
+ ... end=pd.to_datetime("1/08/2018").tz_localize("Europe/Berlin"),
+ ... )
+ DatetimeIndex(['2018-01-01 00:00:00+01:00', '2018-01-02 00:00:00+01:00',
+ '2018-01-03 00:00:00+01:00', '2018-01-04 00:00:00+01:00',
+ '2018-01-05 00:00:00+01:00', '2018-01-06 00:00:00+01:00',
+ '2018-01-07 00:00:00+01:00', '2018-01-08 00:00:00+01:00'],
+ dtype='datetime64[us, Europe/Berlin]', freq='D')
+
+ Specify `start` and `periods`, the number of periods (days).
+
+ >>> pd.date_range(start="1/1/2018", periods=8)
+ DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
+ '2018-01-05', '2018-01-06', '2018-01-07', '2018-01-08'],
+ dtype='datetime64[us]', freq='D')
+
+ Specify `end` and `periods`, the number of periods (days).
+
+ >>> pd.date_range(end="1/1/2018", periods=8)
+ DatetimeIndex(['2017-12-25', '2017-12-26', '2017-12-27', '2017-12-28',
+ '2017-12-29', '2017-12-30', '2017-12-31', '2018-01-01'],
+ dtype='datetime64[us]', freq='D')
+
+ Specify `start`, `end`, and `periods`; the frequency is generated
+ automatically (linearly spaced).
+
+ >>> pd.date_range(start="2018-04-24", end="2018-04-27", periods=3)
+ DatetimeIndex(['2018-04-24 00:00:00', '2018-04-25 12:00:00',
+ '2018-04-27 00:00:00'],
+ dtype='datetime64[us]', freq=None)
+
+ **Other Parameters**
+
+ Changed the `freq` (frequency) to ``'ME'`` (month end frequency).
+
+ >>> pd.date_range(start="1/1/2018", periods=5, freq="ME")
+ DatetimeIndex(['2018-01-31', '2018-02-28', '2018-03-31', '2018-04-30',
+ '2018-05-31'],
+ dtype='datetime64[us]', freq='ME')
+
+ Multiples are allowed
+
+ >>> pd.date_range(start="1/1/2018", periods=5, freq="3ME")
+ DatetimeIndex(['2018-01-31', '2018-04-30', '2018-07-31', '2018-10-31',
+ '2019-01-31'],
+ dtype='datetime64[us]', freq='3ME')
+
+ `freq` can also be specified as an Offset object.
+
+ >>> pd.date_range(start="1/1/2018", periods=5, freq=pd.offsets.MonthEnd(3))
+ DatetimeIndex(['2018-01-31', '2018-04-30', '2018-07-31', '2018-10-31',
+ '2019-01-31'],
+ dtype='datetime64[us]', freq='3ME')
+
+ Specify `tz` to set the timezone.
+
+ >>> pd.date_range(start="1/1/2018", periods=5, tz="Asia/Tokyo")
+ DatetimeIndex(['2018-01-01 00:00:00+09:00', '2018-01-02 00:00:00+09:00',
+ '2018-01-03 00:00:00+09:00', '2018-01-04 00:00:00+09:00',
+ '2018-01-05 00:00:00+09:00'],
+ dtype='datetime64[us, Asia/Tokyo]', freq='D')
+
+ `inclusive` controls whether to include `start` and `end` that are on the
+ boundary. The default, "both", includes boundary points on either end.
+
+ >>> pd.date_range(start="2017-01-01", end="2017-01-04", inclusive="both")
+ DatetimeIndex(['2017-01-01', '2017-01-02', '2017-01-03', '2017-01-04'],
+ dtype='datetime64[us]', freq='D')
+
+ Use ``inclusive='left'`` to exclude `end` if it falls on the boundary.
+
+ >>> pd.date_range(start="2017-01-01", end="2017-01-04", inclusive="left")
+ DatetimeIndex(['2017-01-01', '2017-01-02', '2017-01-03'],
+ dtype='datetime64[us]', freq='D')
+
+ Use ``inclusive='right'`` to exclude `start` if it falls on the boundary, and
+ similarly ``inclusive='neither'`` will exclude both `start` and `end`.
+
+ >>> pd.date_range(start="2017-01-01", end="2017-01-04", inclusive="right")
+ DatetimeIndex(['2017-01-02', '2017-01-03', '2017-01-04'],
+ dtype='datetime64[us]', freq='D')
+
+ **Specify a unit**
+
+ >>> pd.date_range(start="2017-01-01", periods=10, freq="100YS", unit="s")
+ DatetimeIndex(['2017-01-01', '2117-01-01', '2217-01-01', '2317-01-01',
+ '2417-01-01', '2517-01-01', '2617-01-01', '2717-01-01',
+ '2817-01-01', '2917-01-01'],
+ dtype='datetime64[s]', freq='100YS-JAN')
+ """
+ if freq is None and com.any_none(periods, start, end):
+ freq = "D"
+ if freq is not None:
+ freq = to_offset(freq)
+
+ if start is NaT or end is NaT:
+ # This check needs to come before the `unit = start.unit` line below
+ raise ValueError("Neither `start` nor `end` can be NaT")
+
+ if unit is None:
+ # Infer the unit based on the inputs
+
+ if start is not None and end is not None:
+ start = Timestamp(start)
+ end = Timestamp(end)
+ if abbrev_to_npy_unit(start.unit) > abbrev_to_npy_unit(end.unit):
+ unit = start.unit
+ else:
+ unit = end.unit
+ elif start is not None:
+ start = Timestamp(start)
+ unit = start.unit
+ elif end is not None:
+ end = Timestamp(end)
+ unit = end.unit
+ else:
+ raise ValueError(
+ "Of the four parameters: start, end, periods, "
+ "and freq, exactly three must be specified"
+ )
+
+ # Last we need to watch out for cases where the 'freq' implies a higher
+ # unit than either start or end
+ if freq is not None:
+ creso = abbrev_to_npy_unit(unit)
+ if isinstance(freq, Tick):
+ if freq._creso > creso:
+ unit = freq.base.freqstr # type: ignore[assignment]
+ elif hasattr(freq, "offset") and freq.offset is not None:
+ # e.g. BDay with an offset
+ td = Timedelta(freq.offset)
+ if abbrev_to_npy_unit(td.unit) > creso:
+ unit = td.unit
+ elif type(freq) is DateOffset:
+ if getattr(freq, "nanoseconds", 0) != 0:
+ # e.g. test_freq_dateoffset_with_relateivedelta_nanos
+ unit = "ns"
+ elif getattr(freq, "microseconds", 0) != 0 and unit != "ns":
+ unit = "us"
+ elif getattr(freq, "milliseconds", 0) != 0 and unit not in ["ns", "us"]:
+ unit = "ms"
+
+ dtarr = DatetimeArray._generate_range(
+ start=start,
+ end=end,
+ periods=periods,
+ freq=freq,
+ tz=tz,
+ normalize=normalize,
+ inclusive=inclusive,
+ unit=unit,
+ **kwargs,
+ )
+ return DatetimeIndex._simple_new(dtarr, name=name)
+
+
+@set_module("pandas")
+def bdate_range(
+ start=None,
+ end=None,
+ periods: int | None = None,
+ freq: Frequency | dt.timedelta = "B",
+ tz=None,
+ normalize: bool = True,
+ name: Hashable | None = None,
+ weekmask=None,
+ holidays=None,
+ inclusive: IntervalClosedType = "both",
+ **kwargs,
+) -> DatetimeIndex:
+ """
+ Return a fixed frequency DatetimeIndex with business day as the default.
+
+ Parameters
+ ----------
+ start : str or datetime-like, default None
+ Left bound for generating dates.
+ end : str or datetime-like, default None
+ Right bound for generating dates.
+ periods : int, default None
+ Number of periods to generate.
+ freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'B'
+ Frequency strings can have multiples, e.g. '5h'. The default is
+ business daily ('B').
+ tz : str or None
+ Time zone name for returning localized DatetimeIndex, for example
+ Asia/Beijing.
+ normalize : bool, default False
+ Normalize start/end dates to midnight before generating date range.
+ name : Hashable, default None
+ Name of the resulting DatetimeIndex.
+ weekmask : str or None, default None
+ Weekmask of valid business days, passed to ``numpy.busdaycalendar``,
+ only used when custom frequency strings are passed. The default
+ value None is equivalent to 'Mon Tue Wed Thu Fri'.
+ holidays : list-like or None, default None
+ Dates to exclude from the set of valid business days, passed to
+ ``numpy.busdaycalendar``, only used when custom frequency strings
+ are passed.
+ inclusive : {"both", "neither", "left", "right"}, default "both"
+ Include boundaries; Whether to set each bound as closed or open.
+ **kwargs
+ For compatibility. Has no effect on the result.
+
+ Returns
+ -------
+ DatetimeIndex
+ Fixed frequency DatetimeIndex.
+
+ See Also
+ --------
+ date_range : Return a fixed frequency DatetimeIndex.
+ period_range : Return a fixed frequency PeriodIndex.
+ timedelta_range : Return a fixed frequency TimedeltaIndex.
+
+ Notes
+ -----
+ Of the four parameters: ``start``, ``end``, ``periods``, and ``freq``,
+ exactly three must be specified. Specifying ``freq`` is a requirement
+ for ``bdate_range``. Use ``date_range`` if specifying ``freq`` is not
+ desired.
+
+ To learn more about the frequency strings, please see
+ :ref:`this link`.
+
+ Examples
+ --------
+ Note how the two weekend days are skipped in the result.
+
+ >>> pd.bdate_range(start="1/1/2018", end="1/08/2018")
+ DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
+ '2018-01-05', '2018-01-08'],
+ dtype='datetime64[us]', freq='B')
+ """
+ if freq is None:
+ msg = "freq must be specified for bdate_range; use date_range instead"
+ raise TypeError(msg)
+
+ if isinstance(freq, str) and freq.upper().startswith("C"):
+ msg = f"invalid custom frequency string: {freq}"
+ if freq == "CBH":
+ raise ValueError(f"{msg}, did you mean cbh?")
+ try:
+ weekmask = weekmask or "Mon Tue Wed Thu Fri"
+ freq = prefix_mapping[freq](holidays=holidays, weekmask=weekmask)
+ except (KeyError, TypeError) as err:
+ raise ValueError(msg) from err
+ elif holidays or weekmask:
+ msg = (
+ "a custom frequency string is required when holidays or "
+ f"weekmask are passed, got frequency {freq}"
+ )
+ raise ValueError(msg)
+
+ return date_range(
+ start=start,
+ end=end,
+ periods=periods,
+ freq=freq,
+ tz=tz,
+ normalize=normalize,
+ name=name,
+ inclusive=inclusive,
+ **kwargs,
+ )
+
+
+def _time_to_micros(time_obj: dt.time) -> int:
+ seconds = time_obj.hour * 60 * 60 + 60 * time_obj.minute + time_obj.second
+ return 1_000_000 * seconds + time_obj.microsecond
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/extension.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/extension.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c37aca23342f57631d01092927e6e1fbf31f291
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/extension.py
@@ -0,0 +1,176 @@
+"""
+Shared methods for Index subclasses backed by ExtensionArray.
+"""
+
+from __future__ import annotations
+
+from inspect import signature
+from typing import (
+ TYPE_CHECKING,
+ TypeVar,
+)
+
+from pandas.util._decorators import cache_readonly
+
+from pandas.core.dtypes.generic import ABCDataFrame
+
+from pandas.core.indexes.base import Index
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ import numpy as np
+
+ from pandas._typing import (
+ ArrayLike,
+ npt,
+ )
+
+ from pandas.core.arrays import IntervalArray
+ from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
+
+_ExtensionIndexT = TypeVar("_ExtensionIndexT", bound="ExtensionIndex")
+
+
+def _inherit_from_data(
+ name: str, delegate: type, cache: bool = False, wrap: bool = False
+):
+ """
+ Make an alias for a method of the underlying ExtensionArray.
+
+ Parameters
+ ----------
+ name : str
+ Name of an attribute the class should inherit from its EA parent.
+ delegate : class
+ cache : bool, default False
+ Whether to convert wrapped properties into cache_readonly
+ wrap : bool, default False
+ Whether to wrap the inherited result in an Index.
+
+ Returns
+ -------
+ attribute, method, property, or cache_readonly
+ """
+ attr = getattr(delegate, name)
+
+ if isinstance(attr, property) or type(attr).__name__ == "getset_descriptor":
+ # getset_descriptor i.e. property defined in cython class
+ if cache:
+
+ def cached(self):
+ return getattr(self._data, name)
+
+ cached.__name__ = name
+ cached.__doc__ = attr.__doc__
+ method = cache_readonly(cached)
+
+ else:
+
+ def fget(self):
+ result = getattr(self._data, name)
+ if wrap:
+ if isinstance(result, type(self._data)):
+ return type(self)._simple_new(result, name=self.name)
+ elif isinstance(result, ABCDataFrame):
+ return result.set_index(self)
+ return Index(result, name=self.name, dtype=result.dtype, copy=False)
+ return result
+
+ def fset(self, value) -> None:
+ setattr(self._data, name, value)
+
+ fget.__name__ = name
+ fget.__doc__ = attr.__doc__
+
+ method = property(fget, fset)
+
+ elif not callable(attr):
+ # just a normal attribute, no wrapping
+ method = attr
+
+ else:
+ # error: Incompatible redefinition (redefinition with type "Callable[[Any,
+ # VarArg(Any), KwArg(Any)], Any]", original type "property")
+ def method(self, *args, **kwargs): # type: ignore[misc]
+ if "inplace" in kwargs:
+ raise ValueError(f"cannot use inplace with {type(self).__name__}")
+ result = attr(self._data, *args, **kwargs)
+ if wrap:
+ if isinstance(result, type(self._data)):
+ return type(self)._simple_new(result, name=self.name)
+ elif isinstance(result, ABCDataFrame):
+ return result.set_index(self)
+ return Index(result, name=self.name, dtype=result.dtype, copy=False)
+ return result
+
+ # error: "property" has no attribute "__name__"
+ method.__name__ = name # type: ignore[attr-defined]
+ method.__doc__ = attr.__doc__
+ method.__signature__ = signature(attr) # type: ignore[attr-defined]
+ return method
+
+
+def inherit_names(
+ names: list[str], delegate: type, cache: bool = False, wrap: bool = False
+) -> Callable[[type[_ExtensionIndexT]], type[_ExtensionIndexT]]:
+ """
+ Class decorator to pin attributes from an ExtensionArray to an Index subclass.
+
+ Parameters
+ ----------
+ names : List[str]
+ delegate : class
+ cache : bool, default False
+ wrap : bool, default False
+ Whether to wrap the inherited result in an Index.
+ """
+
+ def wrapper(cls: type[_ExtensionIndexT]) -> type[_ExtensionIndexT]:
+ for name in names:
+ meth = _inherit_from_data(name, delegate, cache=cache, wrap=wrap)
+ setattr(cls, name, meth)
+
+ return cls
+
+ return wrapper
+
+
+class ExtensionIndex(Index):
+ """
+ Index subclass for indexes backed by ExtensionArray.
+ """
+
+ # The base class already passes through to _data:
+ # size, __len__, dtype
+
+ _data: IntervalArray | NDArrayBackedExtensionArray
+
+ # ---------------------------------------------------------------------
+
+ def _validate_fill_value(self, value):
+ """
+ Convert value to be insertable to underlying array.
+ """
+ return self._data._validate_setitem_value(value)
+
+ @cache_readonly
+ def _isnan(self) -> npt.NDArray[np.bool_]:
+ # error: Incompatible return value type (got "ExtensionArray", expected
+ # "ndarray")
+ return self._data.isna() # type: ignore[return-value]
+
+
+class NDArrayBackedExtensionIndex(ExtensionIndex):
+ """
+ Index subclass for indexes backed by NDArrayBackedExtensionArray.
+ """
+
+ _data: NDArrayBackedExtensionArray
+
+ def _get_engine_target(self) -> np.ndarray:
+ return self._data._ndarray
+
+ def _from_join_target(self, result: np.ndarray) -> ArrayLike:
+ assert result.dtype == self._data._ndarray.dtype
+ return self._data._from_backing_data(result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/frozen.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/frozen.py
new file mode 100644
index 0000000000000000000000000000000000000000..59800c3b9dc5e68a04eac54c9591003a920f4135
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/frozen.py
@@ -0,0 +1,121 @@
+"""
+frozen (immutable) data structures to support MultiIndexing
+
+These are used for:
+
+- .names (FrozenList)
+
+"""
+
+from __future__ import annotations
+
+from typing import (
+ NoReturn,
+ Self,
+)
+
+from pandas.util._decorators import set_module
+
+from pandas.core.base import PandasObject
+
+from pandas.io.formats.printing import pprint_thing
+
+
+@set_module("pandas.api.typing")
+class FrozenList(PandasObject, list):
+ """
+ Container that doesn't allow setting item *but*
+ because it's technically hashable, will be used
+ for lookups, appropriately, etc.
+ """
+
+ # Side note: This has to be of type list. Otherwise,
+ # it messes up PyTables type checks.
+
+ def union(self, other) -> FrozenList:
+ """
+ Returns a FrozenList with other concatenated to the end of self.
+
+ Parameters
+ ----------
+ other : array-like
+ The array-like whose elements we are concatenating.
+
+ Returns
+ -------
+ FrozenList
+ The collection difference between self and other.
+ """
+ if isinstance(other, tuple):
+ other = list(other)
+ return type(self)(super().__add__(other))
+
+ def difference(self, other) -> FrozenList:
+ """
+ Returns a FrozenList with elements from other removed from self.
+
+ Parameters
+ ----------
+ other : array-like
+ The array-like whose elements we are removing self.
+
+ Returns
+ -------
+ FrozenList
+ The collection difference between self and other.
+ """
+ other = set(other)
+ temp = [x for x in self if x not in other]
+ return type(self)(temp)
+
+ # TODO: Consider deprecating these in favor of `union` (xref gh-15506)
+
+ __add__ = __iadd__ = union # pyright: ignore[reportAssignmentType]
+
+ def __getitem__(self, n):
+ if isinstance(n, slice):
+ return type(self)(super().__getitem__(n))
+ return super().__getitem__(n)
+
+ def __radd__(self, other) -> Self:
+ if isinstance(other, tuple):
+ other = list(other)
+ return type(self)(other + list(self))
+
+ def __eq__(self, other: object) -> bool:
+ if isinstance(other, (tuple, FrozenList)):
+ other = list(other)
+ return super().__eq__(other)
+
+ __req__ = __eq__
+
+ def __mul__(self, other) -> Self:
+ return type(self)(super().__mul__(other))
+
+ __imul__ = __mul__
+
+ def __reduce__(self):
+ return type(self), (list(self),)
+
+ # error: Signature of "__hash__" incompatible with supertype "list"
+ def __hash__(self) -> int: # type: ignore[override]
+ return hash(tuple(self))
+
+ def _disabled(self, *args, **kwargs) -> NoReturn:
+ """
+ This method will not function because object is immutable.
+ """
+ raise TypeError(f"'{type(self).__name__}' does not support mutable operations.")
+
+ def __str__(self) -> str:
+ return pprint_thing(
+ self, quote_strings=True, escape_chars=("\t", "\r", "\n", "'")
+ )
+
+ def __repr__(self) -> str:
+ return f"{type(self).__name__}({self!s})"
+
+ __setitem__ = __setslice__ = _disabled
+ __delitem__ = __delslice__ = _disabled
+ pop = append = extend = _disabled
+ remove = sort = insert = _disabled # pyright: ignore[reportAssignmentType]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/interval.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/interval.py
new file mode 100644
index 0000000000000000000000000000000000000000..7dd44b462dd24eb100b140d99f40c9aaf4298d32
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/interval.py
@@ -0,0 +1,1470 @@
+"""define the IntervalIndex"""
+
+from __future__ import annotations
+
+from operator import (
+ le,
+ lt,
+)
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ Self,
+)
+
+import numpy as np
+
+from pandas._libs import lib
+from pandas._libs.interval import (
+ Interval,
+ IntervalMixin,
+ IntervalTree,
+)
+from pandas._libs.tslibs import (
+ BaseOffset,
+ Period,
+ Timedelta,
+ Timestamp,
+ to_offset,
+)
+from pandas.errors import InvalidIndexError
+from pandas.util._decorators import (
+ cache_readonly,
+ set_module,
+)
+from pandas.util._exceptions import rewrite_exception
+
+from pandas.core.dtypes.cast import (
+ find_common_type,
+ infer_dtype_from_scalar,
+ maybe_box_datetimelike,
+ maybe_downcast_numeric,
+ maybe_unbox_numpy_scalar,
+ maybe_upcast_numeric_to_64bit,
+)
+from pandas.core.dtypes.common import (
+ ensure_platform_int,
+ is_float_dtype,
+ is_integer,
+ is_integer_dtype,
+ is_list_like,
+ is_number,
+ is_object_dtype,
+ is_scalar,
+ is_string_dtype,
+ pandas_dtype,
+)
+from pandas.core.dtypes.dtypes import (
+ DatetimeTZDtype,
+ IntervalDtype,
+)
+from pandas.core.dtypes.missing import is_valid_na_for_dtype
+
+from pandas.core.algorithms import unique
+from pandas.core.arrays.datetimelike import validate_periods
+from pandas.core.arrays.interval import (
+ IntervalArray,
+)
+import pandas.core.common as com
+from pandas.core.indexers import is_valid_positional_slice
+from pandas.core.indexes.base import (
+ Index,
+ ensure_index,
+ maybe_extract_name,
+)
+from pandas.core.indexes.datetimes import (
+ DatetimeIndex,
+ date_range,
+)
+from pandas.core.indexes.extension import (
+ ExtensionIndex,
+ inherit_names,
+)
+from pandas.core.indexes.multi import MultiIndex
+from pandas.core.indexes.timedeltas import (
+ TimedeltaIndex,
+ timedelta_range,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import Hashable
+
+ from pandas._typing import (
+ Dtype,
+ DtypeObj,
+ IntervalClosedType,
+ npt,
+ )
+
+
+def _get_next_label(label):
+ # see test_slice_locs_with_ints_and_floats_succeeds
+ dtype = getattr(label, "dtype", type(label))
+ if isinstance(label, (Timestamp, Timedelta)):
+ dtype = "datetime64[ns]"
+ dtype = pandas_dtype(dtype)
+
+ if lib.is_np_dtype(dtype, "mM") or isinstance(dtype, DatetimeTZDtype):
+ return label + np.timedelta64(1, "ns")
+ elif is_integer_dtype(dtype):
+ return label + 1
+ elif is_float_dtype(dtype):
+ return np.nextafter(label, np.inf)
+ else:
+ raise TypeError(f"cannot determine next label for type {type(label)!r}")
+
+
+def _get_prev_label(label):
+ # see test_slice_locs_with_ints_and_floats_succeeds
+ dtype = getattr(label, "dtype", type(label))
+ if isinstance(label, (Timestamp, Timedelta)):
+ dtype = "datetime64[ns]"
+ dtype = pandas_dtype(dtype)
+
+ if lib.is_np_dtype(dtype, "mM") or isinstance(dtype, DatetimeTZDtype):
+ return label - np.timedelta64(1, "ns")
+ elif is_integer_dtype(dtype):
+ return label - 1
+ elif is_float_dtype(dtype):
+ return np.nextafter(label, -np.inf)
+ else:
+ raise TypeError(f"cannot determine next label for type {type(label)!r}")
+
+
+def _new_IntervalIndex(cls, d):
+ """
+ This is called upon unpickling, rather than the default which doesn't have
+ arguments and breaks __new__.
+ """
+ return cls.from_arrays(**d)
+
+
+@inherit_names(["set_closed", "to_tuples"], IntervalArray, wrap=True)
+@inherit_names(
+ [
+ "__array__",
+ "overlaps",
+ "contains",
+ "closed_left",
+ "closed_right",
+ "open_left",
+ "open_right",
+ "is_empty",
+ ],
+ IntervalArray,
+)
+@inherit_names(["is_non_overlapping_monotonic", "closed"], IntervalArray, cache=True)
+@set_module("pandas")
+class IntervalIndex(ExtensionIndex):
+ """
+ Immutable index of intervals that are closed on the same side.
+
+ Parameters
+ ----------
+ data : array-like (1-dimensional)
+ Array-like (ndarray, :class:`DateTimeArray`, :class:`TimeDeltaArray`) containing
+ Interval objects from which to build the IntervalIndex.
+ closed : {'left', 'right', 'both', 'neither'}, default 'right'
+ Whether the intervals are closed on the left-side, right-side, both or
+ neither.
+ dtype : dtype or None, default None
+ If None, dtype will be inferred.
+ copy : bool, default None
+ Whether to copy input data, only relevant for array, Series, and Index
+ inputs (for other input, e.g. a list, a new array is created anyway).
+ Defaults to True for array input and False for Index/Series.
+ Set to False to avoid copying array input at your own risk (if you
+ know the input data won't be modified elsewhere).
+ Set to True to force copying Series/Index input up front.
+ name : object, optional
+ Name to be stored in the index.
+ verify_integrity : bool, default True
+ Verify that the IntervalIndex is valid.
+
+ Attributes
+ ----------
+ left
+ right
+ closed
+ mid
+ length
+ is_empty
+ is_non_overlapping_monotonic
+ is_overlapping
+ values
+
+ Methods
+ -------
+ from_arrays
+ from_tuples
+ from_breaks
+ contains
+ overlaps
+ set_closed
+ to_tuples
+
+ See Also
+ --------
+ Index : The base pandas Index type.
+ Interval : A bounded slice-like interval; the elements of an IntervalIndex.
+ interval_range : Function to create a fixed frequency IntervalIndex.
+ cut : Bin values into discrete Intervals.
+ qcut : Bin values into equal-sized Intervals based on rank or sample quantiles.
+
+ Notes
+ -----
+ See the `user guide
+ `__
+ for more.
+
+ Examples
+ --------
+ A new ``IntervalIndex`` is typically constructed using
+ :func:`interval_range`:
+
+ >>> pd.interval_range(start=0, end=5)
+ IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
+ dtype='interval[int64, right]')
+
+ It may also be constructed using one of the constructor
+ methods: :meth:`IntervalIndex.from_arrays`,
+ :meth:`IntervalIndex.from_breaks`, and :meth:`IntervalIndex.from_tuples`.
+
+ See further examples in the doc strings of ``interval_range`` and the
+ mentioned constructor methods.
+ """
+
+ _typ = "intervalindex"
+
+ # annotate properties pinned via inherit_names
+ closed: IntervalClosedType
+ is_non_overlapping_monotonic: bool
+ closed_left: bool
+ closed_right: bool
+ open_left: bool
+ open_right: bool
+
+ _data: IntervalArray
+ _values: IntervalArray
+ _can_hold_strings = False
+ _data_cls = IntervalArray
+
+ # --------------------------------------------------------------------
+ # Constructors
+
+ def __new__(
+ cls,
+ data,
+ closed: IntervalClosedType | None = None,
+ dtype: Dtype | None = None,
+ copy: bool | None = None,
+ name: Hashable | None = None,
+ verify_integrity: bool = True,
+ ) -> Self:
+ name = maybe_extract_name(name, data, cls)
+
+ # GH#63388
+ data, copy = cls._maybe_copy_array_input(data, copy, dtype)
+
+ with rewrite_exception("IntervalArray", cls.__name__):
+ array = IntervalArray(
+ data,
+ closed=closed,
+ copy=copy,
+ dtype=dtype,
+ verify_integrity=verify_integrity,
+ )
+
+ return cls._simple_new(array, name)
+
+ @classmethod
+ def from_breaks(
+ cls,
+ breaks,
+ closed: IntervalClosedType | None = "right",
+ name: Hashable | None = None,
+ copy: bool = False,
+ dtype: Dtype | None = None,
+ ) -> IntervalIndex:
+ """
+ Construct an IntervalIndex from an array of splits.
+
+ Parameters
+ ----------
+ breaks : array-like (1-dimensional)
+ Left and right bounds for each interval.
+ closed : {'left', 'right', 'both', 'neither'}, default 'right'
+ Whether the intervals are closed on the left-side, right-side, both
+ or neither.
+ name : str, optional
+ Name of the resulting IntervalIndex.
+ copy : bool, default False
+ Copy the data.
+ dtype : dtype or None, default None
+ If None, dtype will be inferred.
+
+ Returns
+ -------
+ IntervalIndex
+
+ See Also
+ --------
+ interval_range : Function to create a fixed frequency IntervalIndex.
+ IntervalIndex.from_arrays : Construct from a left and right array.
+ IntervalIndex.from_tuples : Construct from a sequence of tuples.
+
+ Examples
+ --------
+ >>> pd.IntervalIndex.from_breaks([0, 1, 2, 3])
+ IntervalIndex([(0, 1], (1, 2], (2, 3]],
+ dtype='interval[int64, right]')
+ """
+ with rewrite_exception("IntervalArray", cls.__name__):
+ array = IntervalArray.from_breaks(
+ breaks, closed=closed, copy=copy, dtype=dtype
+ )
+ return cls._simple_new(array, name=name)
+
+ @classmethod
+ def from_arrays(
+ cls,
+ left,
+ right,
+ closed: IntervalClosedType = "right",
+ name: Hashable | None = None,
+ copy: bool = False,
+ dtype: Dtype | None = None,
+ ) -> IntervalIndex:
+ """
+ Construct from two arrays defining the left and right bounds.
+
+ Parameters
+ ----------
+ left : array-like (1-dimensional)
+ Left bounds for each interval.
+ right : array-like (1-dimensional)
+ Right bounds for each interval.
+ closed : {'left', 'right', 'both', 'neither'}, default 'right'
+ Whether the intervals are closed on the left-side, right-side, both
+ or neither.
+ name : str, optional
+ Name of the resulting IntervalIndex.
+ copy : bool, default False
+ Copy the data.
+ dtype : dtype, optional
+ If None, dtype will be inferred.
+
+ Returns
+ -------
+ IntervalIndex
+
+ Raises
+ ------
+ ValueError
+ When a value is missing in only one of `left` or `right`.
+ When a value in `left` is greater than the corresponding value
+ in `right`.
+
+ See Also
+ --------
+ interval_range : Function to create a fixed frequency IntervalIndex.
+ IntervalIndex.from_breaks : Construct an IntervalIndex from an array of
+ splits.
+ IntervalIndex.from_tuples : Construct an IntervalIndex from an
+ array-like of tuples.
+
+ Notes
+ -----
+ Each element of `left` must be less than or equal to the `right`
+ element at the same position. If an element is missing, it must be
+ missing in both `left` and `right`. A TypeError is raised when
+ using an unsupported type for `left` or `right`. At the moment,
+ 'category', 'object', and 'string' subtypes are not supported.
+
+ Examples
+ --------
+ >>> pd.IntervalIndex.from_arrays([0, 1, 2], [1, 2, 3])
+ IntervalIndex([(0, 1], (1, 2], (2, 3]],
+ dtype='interval[int64, right]')
+ """
+ with rewrite_exception("IntervalArray", cls.__name__):
+ array = IntervalArray.from_arrays(
+ left, right, closed, copy=copy, dtype=dtype
+ )
+ return cls._simple_new(array, name=name)
+
+ @classmethod
+ def from_tuples(
+ cls,
+ data,
+ closed: IntervalClosedType = "right",
+ name: Hashable | None = None,
+ copy: bool = False,
+ dtype: Dtype | None = None,
+ ) -> IntervalIndex:
+ """
+ Construct an IntervalIndex from an array-like of tuples.
+
+ Parameters
+ ----------
+ data : array-like (1-dimensional)
+ Array of tuples.
+ closed : {'left', 'right', 'both', 'neither'}, default 'right'
+ Whether the intervals are closed on the left-side, right-side, both
+ or neither.
+ name : str, optional
+ Name of the resulting IntervalIndex.
+ copy : bool, default False
+ By-default copy the data, this is compat only and ignored.
+ dtype : dtype or None, default None
+ If None, dtype will be inferred.
+
+ Returns
+ -------
+ IntervalIndex
+
+ See Also
+ --------
+ interval_range : Function to create a fixed frequency IntervalIndex.
+ IntervalIndex.from_arrays : Construct an IntervalIndex from a left and
+ right array.
+ IntervalIndex.from_breaks : Construct an IntervalIndex from an array of
+ splits.
+
+ Examples
+ --------
+ >>> pd.IntervalIndex.from_tuples([(0, 1), (1, 2)])
+ IntervalIndex([(0, 1], (1, 2]],
+ dtype='interval[int64, right]')
+ """
+ with rewrite_exception("IntervalArray", cls.__name__):
+ arr = IntervalArray.from_tuples(data, closed=closed, copy=copy, dtype=dtype)
+ return cls._simple_new(arr, name=name)
+
+ # --------------------------------------------------------------------
+ # error: Return type "IntervalTree" of "_engine" incompatible with return type
+ # "Union[IndexEngine, ExtensionEngine]" in supertype "Index"
+ @cache_readonly
+ def _engine(self) -> IntervalTree: # type: ignore[override]
+ # IntervalTree does not supports numpy array unless they are 64 bit
+ left = self._maybe_convert_i8(self.left)
+ left = maybe_upcast_numeric_to_64bit(left)
+ right = self._maybe_convert_i8(self.right)
+ right = maybe_upcast_numeric_to_64bit(right)
+ return IntervalTree(left, right, closed=self.closed)
+
+ def __contains__(self, key: Any) -> bool:
+ """
+ return a boolean if this key is IN the index
+ We *only* accept an Interval
+
+ Parameters
+ ----------
+ key : Interval
+
+ Returns
+ -------
+ bool
+ """
+ hash(key)
+ if not isinstance(key, Interval):
+ if is_valid_na_for_dtype(key, self.dtype):
+ return self.hasnans
+ return False
+
+ try:
+ self.get_loc(key)
+ return True
+ except KeyError:
+ return False
+
+ def _getitem_slice(self, slobj: slice) -> IntervalIndex:
+ """
+ Fastpath for __getitem__ when we know we have a slice.
+ """
+ res = self._data[slobj]
+ return type(self)._simple_new(res, name=self._name)
+
+ @cache_readonly
+ def _multiindex(self) -> MultiIndex:
+ return MultiIndex.from_arrays([self.left, self.right], names=["left", "right"])
+
+ def __reduce__(self):
+ d = {
+ "left": self.left,
+ "right": self.right,
+ "closed": self.closed,
+ "name": self.name,
+ }
+ return _new_IntervalIndex, (type(self), d), None
+
+ @property
+ def inferred_type(self) -> str:
+ """Return a string of the type inferred from the values"""
+ return "interval"
+
+ def memory_usage(self, deep: bool = False) -> int:
+ """
+ Memory usage of the values.
+
+ Parameters
+ ----------
+ deep : bool, default False
+ Introspect the data deeply, interrogate
+ `object` dtypes for system-level memory consumption.
+
+ Returns
+ -------
+ bytes used
+ Returns memory usage of the values in the Index in bytes.
+
+ See Also
+ --------
+ numpy.ndarray.nbytes : Total bytes consumed by the elements of the
+ array.
+
+ Notes
+ -----
+ Memory usage does not include memory consumed by elements that
+ are not components of the array if deep=False or if used on PyPy
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx.memory_usage()
+ 24
+ """
+ # we don't use an explicit engine
+ # so return the bytes here
+ return self.left.memory_usage(deep=deep) + self.right.memory_usage(deep=deep)
+
+ # IntervalTree doesn't have a is_monotonic_decreasing, so have to override
+ # the Index implementation
+ @cache_readonly
+ def is_monotonic_decreasing(self) -> bool:
+ """
+ Return True if the IntervalIndex is monotonic decreasing (only equal or
+ decreasing values), else False
+ """
+ return self[::-1].is_monotonic_increasing
+
+ @cache_readonly
+ def is_unique(self) -> bool:
+ """
+ Return True if the IntervalIndex contains unique elements, else False.
+ """
+ left = self.left
+ right = self.right
+
+ if self.isna().sum() > 1:
+ return False
+
+ if left.is_unique or right.is_unique:
+ return True
+
+ seen_pairs = set()
+ check_idx = np.where(left.duplicated(keep=False))[0]
+ for idx in check_idx:
+ pair = (left[idx], right[idx])
+ if pair in seen_pairs:
+ return False
+ seen_pairs.add(pair)
+
+ return True
+
+ @property
+ def is_overlapping(self) -> bool:
+ """
+ Return True if the IntervalIndex has overlapping intervals, else False.
+
+ Two intervals overlap if they share a common point, including closed
+ endpoints. Intervals that only have an open endpoint in common do not
+ overlap.
+
+ Returns
+ -------
+ bool
+ Boolean indicating if the IntervalIndex has overlapping intervals.
+
+ See Also
+ --------
+ Interval.overlaps : Check whether two Interval objects overlap.
+ IntervalIndex.overlaps : Check an IntervalIndex elementwise for
+ overlaps.
+
+ Examples
+ --------
+ >>> index = pd.IntervalIndex.from_tuples([(0, 2), (1, 3), (4, 5)])
+ >>> index
+ IntervalIndex([(0, 2], (1, 3], (4, 5]],
+ dtype='interval[int64, right]')
+ >>> index.is_overlapping
+ True
+
+ Intervals that share closed endpoints overlap:
+
+ >>> index = pd.interval_range(0, 3, closed="both")
+ >>> index
+ IntervalIndex([[0, 1], [1, 2], [2, 3]],
+ dtype='interval[int64, both]')
+ >>> index.is_overlapping
+ True
+
+ Intervals that only have an open endpoint in common do not overlap:
+
+ >>> index = pd.interval_range(0, 3, closed="left")
+ >>> index
+ IntervalIndex([[0, 1), [1, 2), [2, 3)],
+ dtype='interval[int64, left]')
+ >>> index.is_overlapping
+ False
+ """
+ # GH 23309
+ return self._engine.is_overlapping
+
+ def _needs_i8_conversion(self, key) -> bool:
+ """
+ Check if a given key needs i8 conversion. Conversion is necessary for
+ Timestamp, Timedelta, DatetimeIndex, and TimedeltaIndex keys. An
+ Interval-like requires conversion if its endpoints are one of the
+ aforementioned types.
+
+ Assumes that any list-like data has already been cast to an Index.
+
+ Parameters
+ ----------
+ key : scalar or Index-like
+ The key that should be checked for i8 conversion
+
+ Returns
+ -------
+ bool
+ """
+ key_dtype = getattr(key, "dtype", None)
+ if isinstance(key_dtype, IntervalDtype) or isinstance(key, Interval):
+ return self._needs_i8_conversion(key.left)
+
+ i8_types = (Timestamp, Timedelta, DatetimeIndex, TimedeltaIndex)
+ return isinstance(key, i8_types)
+
+ def _maybe_convert_i8(self, key):
+ """
+ Maybe convert a given key to its equivalent i8 value(s). Used as a
+ preprocessing step prior to IntervalTree queries (self._engine), which
+ expects numeric data.
+
+ Parameters
+ ----------
+ key : scalar or list-like
+ The key that should maybe be converted to i8.
+
+ Returns
+ -------
+ scalar or list-like
+ The original key if no conversion occurred, int if converted scalar,
+ Index with an int64 dtype if converted list-like.
+ """
+ if is_list_like(key):
+ key = ensure_index(key)
+ key = maybe_upcast_numeric_to_64bit(key)
+
+ if not self._needs_i8_conversion(key):
+ return key
+
+ scalar = is_scalar(key)
+ key_dtype = getattr(key, "dtype", None)
+ if isinstance(key_dtype, IntervalDtype) or isinstance(key, Interval):
+ # convert left/right and reconstruct
+ left = self._maybe_convert_i8(key.left)
+ right = self._maybe_convert_i8(key.right)
+ constructor = Interval if scalar else IntervalIndex.from_arrays
+ return constructor(left, right, closed=self.closed)
+
+ if scalar:
+ # Timestamp/Timedelta
+ key_dtype, key_i8 = infer_dtype_from_scalar(key)
+ if isinstance(key, Period):
+ key_i8 = key.ordinal
+ elif isinstance(key_i8, Timestamp):
+ key_i8 = key_i8._value
+ elif isinstance(key_i8, (np.datetime64, np.timedelta64)):
+ key_i8 = key_i8.view("i8")
+ else:
+ # DatetimeIndex/TimedeltaIndex
+ key_dtype, key_i8 = key.dtype, Index(key.asi8, copy=False)
+ if key.hasnans:
+ # convert NaT from its i8 value to np.nan so it's not viewed
+ # as a valid value, maybe causing errors (e.g. is_overlapping)
+ key_i8 = key_i8.where(~key._isnan)
+
+ # ensure consistency with IntervalIndex subtype
+ # error: Item "ExtensionDtype"/"dtype[Any]" of "Union[dtype[Any],
+ # ExtensionDtype]" has no attribute "subtype"
+ subtype = self.dtype.subtype # type: ignore[union-attr]
+
+ if subtype != key_dtype:
+ raise ValueError(
+ f"Cannot index an IntervalIndex of subtype {subtype} with "
+ f"values of dtype {key_dtype}"
+ )
+
+ return key_i8
+
+ def _searchsorted_monotonic(self, label, side: Literal["left", "right"] = "left"):
+ if not self.is_non_overlapping_monotonic:
+ raise KeyError(
+ "can only get slices from an IntervalIndex if bounds are "
+ "non-overlapping and all monotonic increasing or decreasing"
+ )
+
+ if isinstance(label, (IntervalMixin, IntervalIndex)):
+ raise NotImplementedError("Interval objects are not currently supported")
+
+ # GH 20921: "not is_monotonic_increasing" for the second condition
+ # instead of "is_monotonic_decreasing" to account for single element
+ # indexes being both increasing and decreasing
+ if (side == "left" and self.left.is_monotonic_increasing) or (
+ side == "right" and not self.left.is_monotonic_increasing
+ ):
+ sub_idx = self.right
+ if self.open_right:
+ label = _get_next_label(label)
+ else:
+ sub_idx = self.left
+ if self.open_left:
+ label = _get_prev_label(label)
+
+ return sub_idx._searchsorted_monotonic(label, side)
+
+ # --------------------------------------------------------------------
+ # Indexing Methods
+
+ def get_loc(self, key) -> int | slice | np.ndarray:
+ """
+ Get integer location, slice or boolean mask for requested label.
+
+ The `get_loc` method is used to retrieve the integer index, a slice for
+ slicing objects, or a boolean mask indicating the presence of the label
+ in the `IntervalIndex`.
+
+ Parameters
+ ----------
+ key : label
+ The value or range to find in the IntervalIndex.
+
+ Returns
+ -------
+ int if unique index, slice if monotonic index, else mask
+ The position or positions found. This could be a single
+ number, a range, or an array of true/false values
+ indicating the position(s) of the label.
+
+ See Also
+ --------
+ IntervalIndex.get_indexer_non_unique : Compute indexer and
+ mask for new index given the current index.
+ Index.get_loc : Similar method in the base Index class.
+
+ Examples
+ --------
+ >>> i1, i2 = pd.Interval(0, 1), pd.Interval(1, 2)
+ >>> index = pd.IntervalIndex([i1, i2])
+ >>> index.get_loc(1)
+ 0
+
+ You can also supply a point inside an interval.
+
+ >>> index.get_loc(1.5)
+ 1
+
+ If a label is in several intervals, you get the locations of all the
+ relevant intervals.
+
+ >>> i3 = pd.Interval(0, 2)
+ >>> overlapping_index = pd.IntervalIndex([i1, i2, i3])
+ >>> overlapping_index.get_loc(0.5)
+ array([ True, False, True])
+
+ Only exact matches will be returned if an interval is provided.
+
+ >>> index.get_loc(pd.Interval(0, 1))
+ 0
+ """
+ self._check_indexing_error(key)
+
+ if isinstance(key, Interval):
+ if self.closed != key.closed:
+ raise KeyError(key)
+ mask = (self.left == key.left) & (self.right == key.right)
+ elif is_valid_na_for_dtype(key, self.dtype):
+ mask = self.isna()
+ else:
+ # assume scalar
+ op_left = le if self.closed_left else lt
+ op_right = le if self.closed_right else lt
+ try:
+ mask = op_left(self.left, key) & op_right(key, self.right)
+ except TypeError as err:
+ # scalar is not comparable to II subtype --> invalid label
+ raise KeyError(key) from err
+
+ matches = mask.sum()
+ if matches == 0:
+ raise KeyError(key)
+ if matches == 1:
+ return maybe_unbox_numpy_scalar(mask.argmax())
+
+ res = lib.maybe_booleans_to_slice(mask.view("u1"))
+ if isinstance(res, slice) and res.stop is None:
+ # TODO: DO this in maybe_booleans_to_slice?
+ res = slice(res.start, len(self), res.step)
+ return res
+
+ def _get_indexer(
+ self,
+ target: Index,
+ method: str | None = None,
+ limit: int | None = None,
+ tolerance: Any | None = None,
+ ) -> npt.NDArray[np.intp]:
+ if isinstance(target, IntervalIndex):
+ # We only get here with not self.is_overlapping
+ # -> at most one match per interval in target
+ # want exact matches -> need both left/right to match, so defer to
+ # left/right get_indexer, compare elementwise, equality -> match
+ if self.left.is_unique and self.right.is_unique:
+ indexer = self._get_indexer_unique_sides(target)
+ else:
+ indexer = self._get_indexer_pointwise(target)[0]
+
+ elif not (is_object_dtype(target.dtype) or is_string_dtype(target.dtype)):
+ # homogeneous scalar index: use IntervalTree
+ # we should always have self._should_partial_index(target) here
+ target = self._maybe_convert_i8(target)
+ indexer = self._engine.get_indexer(target.values)
+ else:
+ # heterogeneous scalar index: defer elementwise to get_loc
+ # we should always have self._should_partial_index(target) here
+ return self._get_indexer_pointwise(target)[0]
+
+ return ensure_platform_int(indexer)
+
+ def get_indexer_non_unique(
+ self, target: Index
+ ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ """
+ Compute indexer and mask for new index given the current index.
+
+ The indexer should be then used as an input to ndarray.take to align the
+ current data to the new index.
+
+ Parameters
+ ----------
+ target : IntervalIndex or list of Intervals
+ An iterable containing the values to be used for computing indexer.
+
+ Returns
+ -------
+ indexer : np.ndarray[np.intp]
+ Integers from 0 to n - 1 indicating that the index at these
+ positions matches the corresponding target values. Missing values
+ in the target are marked by -1.
+ missing : np.ndarray[np.intp]
+ An indexer into the target of the values not found.
+ These correspond to the -1 in the indexer array.
+
+ See Also
+ --------
+ Index.get_indexer : Computes indexer and mask for new index given
+ the current index.
+ Index.get_indexer_for : Returns an indexer even when non-unique.
+
+ Examples
+ --------
+ >>> index = pd.Index(["c", "b", "a", "b", "b"])
+ >>> index.get_indexer_non_unique(["b", "b"])
+ (array([1, 3, 4, 1, 3, 4]), array([], dtype=int64))
+
+ In the example below there are no matched values.
+
+ >>> index = pd.Index(["c", "b", "a", "b", "b"])
+ >>> index.get_indexer_non_unique(["q", "r", "t"])
+ (array([-1, -1, -1]), array([0, 1, 2]))
+
+ For this reason, the returned ``indexer`` contains only integers equal to -1.
+ It demonstrates that there's no match between the index and the ``target``
+ values at these positions. The mask [0, 1, 2] in the return value shows that
+ the first, second, and third elements are missing.
+
+ Notice that the return value is a tuple contains two items. In the example
+ below the first item is an array of locations in ``index``. The second
+ item is a mask shows that the first and third elements are missing.
+
+ >>> index = pd.Index(["c", "b", "a", "b", "b"])
+ >>> index.get_indexer_non_unique(["f", "b", "s"])
+ (array([-1, 1, 3, 4, -1]), array([0, 2]))
+ """
+ target = ensure_index(target)
+
+ if not self._should_compare(target) and not self._should_partial_index(target):
+ # e.g. IntervalIndex with different closed or incompatible subtype
+ # -> no matches
+ return self._get_indexer_non_comparable(target, None, unique=False)
+
+ elif isinstance(target, IntervalIndex):
+ if self.left.is_unique and self.right.is_unique:
+ # fastpath available even if we don't have self._index_as_unique
+ indexer = self._get_indexer_unique_sides(target)
+ missing = (indexer == -1).nonzero()[0]
+ else:
+ return self._get_indexer_pointwise(target)
+
+ elif is_object_dtype(target.dtype) or not self._should_partial_index(target):
+ # target might contain intervals: defer elementwise to get_loc
+ return self._get_indexer_pointwise(target)
+
+ else:
+ # Note: this case behaves differently from other Index subclasses
+ # because IntervalIndex does partial-int indexing
+ target = self._maybe_convert_i8(target)
+ indexer, missing = self._engine.get_indexer_non_unique(target.values)
+
+ return ensure_platform_int(indexer), ensure_platform_int(missing)
+
+ def _get_indexer_unique_sides(self, target: IntervalIndex) -> npt.NDArray[np.intp]:
+ """
+ _get_indexer specialized to the case where both of our sides are unique.
+ """
+ # Caller is responsible for checking
+ # `self.left.is_unique and self.right.is_unique`
+
+ left_indexer = self.left.get_indexer(target.left)
+ right_indexer = self.right.get_indexer(target.right)
+ indexer = np.where(left_indexer == right_indexer, left_indexer, -1)
+ return indexer
+
+ def _get_indexer_pointwise(
+ self, target: Index
+ ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ """
+ pointwise implementation for get_indexer and get_indexer_non_unique.
+ """
+ indexer, missing = [], []
+ for i, key in enumerate(target):
+ try:
+ locs = self.get_loc(key)
+ if isinstance(locs, slice):
+ # Only needed for get_indexer_non_unique
+ locs = np.arange(locs.start, locs.stop, locs.step, dtype="intp")
+ elif lib.is_integer(locs):
+ locs = np.array(locs, ndmin=1)
+ else:
+ # otherwise we have ndarray[bool]
+ locs = np.where(locs)[0]
+ except KeyError:
+ missing.append(i)
+ locs = np.array([-1])
+ except InvalidIndexError:
+ # i.e. non-scalar key e.g. a tuple.
+ # see test_append_different_columns_types_raises
+ missing.append(i)
+ locs = np.array([-1])
+
+ indexer.append(locs)
+
+ concatenated_indexer = np.concatenate(indexer)
+ return ensure_platform_int(concatenated_indexer), ensure_platform_int(missing)
+
+ @cache_readonly
+ def _index_as_unique(self) -> bool:
+ return not self.is_overlapping and self._engine._na_count < 2
+
+ _requires_unique_msg = (
+ "cannot handle overlapping indices; use IntervalIndex.get_indexer_non_unique"
+ )
+
+ def _convert_slice_indexer(self, key: slice, kind: Literal["loc", "getitem"]):
+ if not (key.step is None or key.step == 1):
+ # GH#31658 if label-based, we require step == 1,
+ # if positional, we disallow float start/stop
+ msg = "label-based slicing with step!=1 is not supported for IntervalIndex"
+ if kind == "loc":
+ raise ValueError(msg)
+ if kind == "getitem":
+ if not is_valid_positional_slice(key):
+ # i.e. this cannot be interpreted as a positional slice
+ raise ValueError(msg)
+
+ return super()._convert_slice_indexer(key, kind)
+
+ @cache_readonly
+ def _should_fallback_to_positional(self) -> bool:
+ # integer lookups in Series.__getitem__ are unambiguously
+ # positional in this case
+ # error: Item "ExtensionDtype"/"dtype[Any]" of "Union[dtype[Any],
+ # ExtensionDtype]" has no attribute "subtype"
+ return self.dtype.subtype.kind in "mM" # type: ignore[union-attr]
+
+ def _maybe_cast_slice_bound(self, label, side: str):
+ return getattr(self, side)._maybe_cast_slice_bound(label, side)
+
+ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
+ if not isinstance(dtype, IntervalDtype):
+ return False
+ common_subtype = find_common_type([self.dtype, dtype])
+ return not is_object_dtype(common_subtype)
+
+ # --------------------------------------------------------------------
+
+ @cache_readonly
+ def left(self) -> Index:
+ """
+ Return left bounds of the intervals in the IntervalIndex.
+
+ The left bounds of each interval in the IntervalIndex are
+ returned as an Index. The datatype of the left bounds is the
+ same as the datatype of the endpoints of the intervals.
+
+ Returns
+ -------
+ Index
+ An Index containing the left bounds of the intervals.
+
+ See Also
+ --------
+ IntervalIndex.right : Return the right bounds of the intervals
+ in the IntervalIndex.
+ IntervalIndex.mid : Return the mid-point of the intervals in
+ the IntervalIndex.
+ IntervalIndex.length : Return the length of the intervals in
+ the IntervalIndex.
+
+ Examples
+ --------
+ >>> iv_idx = pd.IntervalIndex.from_arrays([1, 2, 3], [4, 5, 6], closed="right")
+ >>> iv_idx.left
+ Index([1, 2, 3], dtype='int64')
+
+ >>> iv_idx = pd.IntervalIndex.from_tuples(
+ ... [(1, 4), (2, 5), (3, 6)], closed="left"
+ ... )
+ >>> iv_idx.left
+ Index([1, 2, 3], dtype='int64')
+ """
+ return Index(self._data.left, copy=False)
+
+ @cache_readonly
+ def right(self) -> Index:
+ """
+ Return right bounds of the intervals in the IntervalIndex.
+
+ The right bounds of each interval in the IntervalIndex are
+ returned as an Index. The datatype of the right bounds is the
+ same as the datatype of the endpoints of the intervals.
+
+ Returns
+ -------
+ Index
+ An Index containing the right bounds of the intervals.
+
+ See Also
+ --------
+ IntervalIndex.left : Return the left bounds of the intervals
+ in the IntervalIndex.
+ IntervalIndex.mid : Return the mid-point of the intervals in
+ the IntervalIndex.
+ IntervalIndex.length : Return the length of the intervals in
+ the IntervalIndex.
+
+ Examples
+ --------
+ >>> iv_idx = pd.IntervalIndex.from_arrays([1, 2, 3], [4, 5, 6], closed="right")
+ >>> iv_idx.right
+ Index([4, 5, 6], dtype='int64')
+
+ >>> iv_idx = pd.IntervalIndex.from_tuples(
+ ... [(1, 4), (2, 5), (3, 6)], closed="left"
+ ... )
+ >>> iv_idx.right
+ Index([4, 5, 6], dtype='int64')
+ """
+ return Index(self._data.right, copy=False)
+
+ @cache_readonly
+ def mid(self) -> Index:
+ """
+ Return the midpoint of each interval in the IntervalIndex as an Index.
+
+ Each midpoint is calculated as the average of the left and right bounds
+ of each interval. The midpoints are returned as a pandas Index object.
+
+ Returns
+ -------
+ pandas.Index
+ An Index containing the midpoints of each interval.
+
+ See Also
+ --------
+ IntervalIndex.left : Return the left bounds of the intervals
+ in the IntervalIndex.
+ IntervalIndex.right : Return the right bounds of the intervals
+ in the IntervalIndex.
+ IntervalIndex.length : Return the length of the intervals in
+ the IntervalIndex.
+
+ Notes
+ -----
+ The midpoint is the average of the interval bounds, potentially resulting
+ in a floating-point number even if bounds are integers. The returned Index
+ will have a dtype that accurately holds the midpoints. This computation is
+ the same regardless of whether intervals are open or closed.
+
+ Examples
+ --------
+ >>> iv_idx = pd.IntervalIndex.from_arrays([1, 2, 3], [4, 5, 6])
+ >>> iv_idx.mid
+ Index([2.5, 3.5, 4.5], dtype='float64')
+
+ >>> iv_idx = pd.IntervalIndex.from_tuples([(1, 4), (2, 5), (3, 6)])
+ >>> iv_idx.mid
+ Index([2.5, 3.5, 4.5], dtype='float64')
+ """
+ return Index(self._data.mid, copy=False)
+
+ @property
+ def length(self) -> Index:
+ """
+ Calculate the length of each interval in the IntervalIndex.
+
+ This method returns a new Index containing the lengths of each interval
+ in the IntervalIndex. The length of an interval is defined as the difference
+ between its end and its start.
+
+ Returns
+ -------
+ Index
+ An Index containing the lengths of each interval.
+
+ See Also
+ --------
+ Interval.length : Return the length of the Interval.
+
+ Examples
+ --------
+ >>> intervals = pd.IntervalIndex.from_arrays(
+ ... [1, 2, 3], [4, 5, 6], closed="right"
+ ... )
+ >>> intervals.length
+ Index([3, 3, 3], dtype='int64')
+
+ >>> intervals = pd.IntervalIndex.from_tuples([(1, 5), (6, 10), (11, 15)])
+ >>> intervals.length
+ Index([4, 4, 4], dtype='int64')
+ """
+ return Index(self._data.length, copy=False)
+
+ # --------------------------------------------------------------------
+ # Set Operations
+
+ def _intersection(self, other, sort: bool = False):
+ """
+ intersection specialized to the case with matching dtypes.
+ """
+ # For IntervalIndex we also know other.closed == self.closed
+ if self.left.is_unique and self.right.is_unique:
+ taken = self._intersection_unique(other)
+ elif other.left.is_unique and other.right.is_unique and self.isna().sum() <= 1:
+ # Swap other/self if other is unique and self does not have
+ # multiple NaNs
+ taken = other._intersection_unique(self)
+ else:
+ # duplicates
+ taken = self._intersection_non_unique(other)
+
+ if sort:
+ taken = taken.sort_values()
+
+ return taken
+
+ def _intersection_unique(self, other: IntervalIndex) -> IntervalIndex:
+ """
+ Used when the IntervalIndex does not have any common endpoint,
+ no matter left or right.
+ Return the intersection with another IntervalIndex.
+ Parameters
+ ----------
+ other : IntervalIndex
+ Returns
+ -------
+ IntervalIndex
+ """
+ # Note: this is much more performant than super()._intersection(other)
+ lindexer = self.left.get_indexer(other.left)
+ rindexer = self.right.get_indexer(other.right)
+
+ match = (lindexer == rindexer) & (lindexer != -1)
+ indexer = lindexer.take(match.nonzero()[0])
+ indexer = unique(indexer)
+
+ return self.take(indexer)
+
+ def _intersection_non_unique(self, other: IntervalIndex) -> IntervalIndex:
+ """
+ Used when the IntervalIndex does have some common endpoints,
+ on either sides.
+ Return the intersection with another IntervalIndex.
+
+ Parameters
+ ----------
+ other : IntervalIndex
+
+ Returns
+ -------
+ IntervalIndex
+ """
+ # Note: this is about 3.25x faster than super()._intersection(other)
+ # in IntervalIndexMethod.time_intersection_both_duplicate(1000)
+ mask = np.zeros(len(self), dtype=bool)
+
+ if self.hasnans and other.hasnans:
+ first_nan_loc = np.arange(len(self))[self.isna()][0]
+ mask[first_nan_loc] = True
+
+ other_tups = set(zip(other.left, other.right, strict=True))
+ for i, tup in enumerate(zip(self.left, self.right, strict=True)):
+ if tup in other_tups:
+ mask[i] = True
+
+ return self[mask]
+
+ # --------------------------------------------------------------------
+
+ def _get_engine_target(self) -> np.ndarray:
+ # Note: we _could_ use libjoin functions by either casting to object
+ # dtype or constructing tuples (faster than constructing Intervals)
+ # but the libjoin fastpaths are no longer fast in these cases.
+ raise NotImplementedError(
+ "IntervalIndex does not use libjoin fastpaths or pass values to "
+ "IndexEngine objects"
+ )
+
+ def _from_join_target(self, result):
+ raise NotImplementedError("IntervalIndex does not use libjoin fastpaths")
+
+ # TODO: arithmetic operations
+
+
+def _is_valid_endpoint(endpoint) -> bool:
+ """
+ Helper for interval_range to check if start/end are valid types.
+ """
+ return any(
+ [
+ is_number(endpoint),
+ isinstance(endpoint, Timestamp),
+ isinstance(endpoint, Timedelta),
+ endpoint is None,
+ ]
+ )
+
+
+def _is_type_compatible(a, b) -> bool:
+ """
+ Helper for interval_range to check type compat of start/end/freq.
+ """
+ is_ts_compat = lambda x: isinstance(x, (Timestamp, BaseOffset))
+ is_td_compat = lambda x: isinstance(x, (Timedelta, BaseOffset))
+ return (
+ (is_number(a) and is_number(b))
+ or (is_ts_compat(a) and is_ts_compat(b))
+ or (is_td_compat(a) and is_td_compat(b))
+ or com.any_none(a, b)
+ )
+
+
+@set_module("pandas")
+def interval_range(
+ start=None,
+ end=None,
+ periods=None,
+ freq=None,
+ name: Hashable | None = None,
+ closed: IntervalClosedType = "right",
+) -> IntervalIndex:
+ """
+ Return a fixed frequency IntervalIndex.
+
+ Parameters
+ ----------
+ start : numeric or datetime-like, default None
+ Left bound for generating intervals.
+ end : numeric or datetime-like, default None
+ Right bound for generating intervals.
+ periods : int, default None
+ Number of periods to generate.
+ freq : numeric, str, Timedelta, datetime.timedelta, or DateOffset, default None
+ The length of each interval. Must be consistent with the type of start
+ and end, e.g. 2 for numeric, or '5H' for datetime-like. Default is 1
+ for numeric and 'D' for datetime-like.
+ name : str, default None
+ Name of the resulting IntervalIndex.
+ closed : {'left', 'right', 'both', 'neither'}, default 'right'
+ Whether the intervals are closed on the left-side, right-side, both
+ or neither.
+
+ Returns
+ -------
+ IntervalIndex
+ Object with a fixed frequency.
+
+ See Also
+ --------
+ IntervalIndex : An Index of intervals that are all closed on the same side.
+
+ Notes
+ -----
+ Of the four parameters ``start``, ``end``, ``periods``, and ``freq``,
+ exactly three must be specified. If ``freq`` is omitted, the resulting
+ ``IntervalIndex`` will have ``periods`` linearly spaced elements between
+ ``start`` and ``end``, inclusively.
+
+ To learn more about datetime-like frequency strings, please see
+ :ref:`this link`.
+
+ Examples
+ --------
+ Numeric ``start`` and ``end`` is supported.
+
+ >>> pd.interval_range(start=0, end=5)
+ IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
+ dtype='interval[int64, right]')
+
+ Additionally, datetime-like input is also supported.
+
+ >>> pd.interval_range(
+ ... start=pd.Timestamp("2017-01-01"), end=pd.Timestamp("2017-01-04")
+ ... )
+ IntervalIndex([(2017-01-01 00:00:00, 2017-01-02 00:00:00],
+ (2017-01-02 00:00:00, 2017-01-03 00:00:00],
+ (2017-01-03 00:00:00, 2017-01-04 00:00:00]],
+ dtype='interval[datetime64[us], right]')
+
+ The ``freq`` parameter specifies the frequency between the left and right.
+ endpoints of the individual intervals within the ``IntervalIndex``. For
+ numeric ``start`` and ``end``, the frequency must also be numeric.
+
+ >>> pd.interval_range(start=0, periods=4, freq=1.5)
+ IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0]],
+ dtype='interval[float64, right]')
+
+ Similarly, for datetime-like ``start`` and ``end``, the frequency must be
+ convertible to a DateOffset.
+
+ >>> pd.interval_range(start=pd.Timestamp("2017-01-01"), periods=3, freq="MS")
+ IntervalIndex([(2017-01-01 00:00:00, 2017-02-01 00:00:00],
+ (2017-02-01 00:00:00, 2017-03-01 00:00:00],
+ (2017-03-01 00:00:00, 2017-04-01 00:00:00]],
+ dtype='interval[datetime64[us], right]')
+
+ Specify ``start``, ``end``, and ``periods``; the frequency is generated
+ automatically (linearly spaced).
+
+ >>> pd.interval_range(start=0, end=6, periods=4)
+ IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0]],
+ dtype='interval[float64, right]')
+
+ The ``closed`` parameter specifies which endpoints of the individual
+ intervals within the ``IntervalIndex`` are closed.
+
+ >>> pd.interval_range(end=5, periods=4, closed="both")
+ IntervalIndex([[1, 2], [2, 3], [3, 4], [4, 5]],
+ dtype='interval[int64, both]')
+ """
+ start = maybe_box_datetimelike(start)
+ end = maybe_box_datetimelike(end)
+ endpoint = start if start is not None else end
+
+ if freq is None and com.any_none(periods, start, end):
+ freq = 1 if is_number(endpoint) else "D"
+
+ if com.count_not_none(start, end, periods, freq) != 3:
+ raise ValueError(
+ "Of the four parameters: start, end, periods, and "
+ "freq, exactly three must be specified"
+ )
+
+ if not _is_valid_endpoint(start):
+ raise ValueError(f"start must be numeric or datetime-like, got {start}")
+ if not _is_valid_endpoint(end):
+ raise ValueError(f"end must be numeric or datetime-like, got {end}")
+
+ periods = validate_periods(periods)
+
+ if freq is not None and not is_number(freq):
+ try:
+ freq = to_offset(freq)
+ except ValueError as err:
+ raise ValueError(
+ f"freq must be numeric or convertible to DateOffset, got {freq}"
+ ) from err
+
+ # verify type compatibility
+ if not all(
+ [
+ _is_type_compatible(start, end),
+ _is_type_compatible(start, freq),
+ _is_type_compatible(end, freq),
+ ]
+ ):
+ raise TypeError("start, end, freq need to be type compatible")
+
+ # +1 to convert interval count to breaks count (n breaks = n-1 intervals)
+ if periods is not None:
+ periods += 1
+
+ breaks: np.ndarray | TimedeltaIndex | DatetimeIndex
+
+ if is_number(endpoint):
+ dtype: np.dtype = np.dtype("int64")
+ if com.all_not_none(start, end, freq):
+ if (
+ isinstance(start, (np.integer, np.floating))
+ and isinstance(end, (np.integer, np.floating))
+ and start.dtype == end.dtype
+ ):
+ dtype = start.dtype
+ elif (
+ isinstance(start, (float, np.floating))
+ or isinstance(end, (float, np.floating))
+ or isinstance(freq, (float, np.floating))
+ ):
+ dtype = np.dtype("float64")
+ # 0.1 ensures we capture end
+ breaks = np.arange(start, end + (freq * 0.1), freq)
+ breaks = maybe_downcast_numeric(breaks, dtype)
+ else:
+ # compute the period/start/end if unspecified (at most one)
+ if periods is None:
+ periods = int((end - start) // freq) + 1
+ elif start is None:
+ start = end - (periods - 1) * freq
+ elif end is None:
+ end = start + (periods - 1) * freq
+
+ breaks = np.linspace(start, end, periods)
+ if all(is_integer(x) for x in com.not_none(start, end, freq)):
+ # np.linspace always produces float output
+ breaks = maybe_downcast_numeric(breaks, dtype)
+ # delegate to the appropriate range function
+ elif isinstance(endpoint, Timestamp):
+ breaks = date_range(start=start, end=end, periods=periods, freq=freq)
+ else:
+ breaks = timedelta_range(start=start, end=end, periods=periods, freq=freq)
+
+ return IntervalIndex.from_breaks(
+ breaks,
+ name=name,
+ closed=closed,
+ dtype=IntervalDtype(subtype=breaks.dtype, closed=closed),
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/multi.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/multi.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ce0185147d407f6a72992587480aaf3d59fe780
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/multi.py
@@ -0,0 +1,4807 @@
+from __future__ import annotations
+
+from collections.abc import (
+ Callable,
+ Collection,
+ Generator,
+ Hashable,
+ Iterable,
+ Sequence,
+)
+from functools import wraps
+from itertools import zip_longest
+from sys import getsizeof
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ Self,
+ cast,
+)
+import warnings
+
+import numpy as np
+
+from pandas._config import get_option
+
+from pandas._libs import (
+ algos as libalgos,
+ index as libindex,
+ lib,
+)
+from pandas._libs.hashtable import duplicated
+from pandas._typing import (
+ AnyAll,
+ AnyArrayLike,
+ Axis,
+ DropKeep,
+ DtypeObj,
+ F,
+ IgnoreRaise,
+ IndexLabel,
+ IndexT,
+ NaPosition,
+ Scalar,
+ Shape,
+ npt,
+)
+from pandas.compat.numpy import function as nv
+from pandas.errors import (
+ InvalidIndexError,
+ PerformanceWarning,
+ UnsortedIndexError,
+)
+from pandas.util._decorators import (
+ cache_readonly,
+ set_module,
+)
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core.dtypes.cast import (
+ coerce_indexer_dtype,
+ maybe_unbox_numpy_scalar,
+)
+from pandas.core.dtypes.common import (
+ ensure_int64,
+ ensure_platform_int,
+ is_hashable,
+ is_integer,
+ is_iterator,
+ is_list_like,
+ is_object_dtype,
+ is_scalar,
+ is_string_dtype,
+ pandas_dtype,
+)
+from pandas.core.dtypes.dtypes import (
+ CategoricalDtype,
+ ExtensionDtype,
+)
+from pandas.core.dtypes.generic import (
+ ABCDataFrame,
+ ABCSeries,
+)
+from pandas.core.dtypes.inference import is_array_like
+from pandas.core.dtypes.missing import (
+ array_equivalent,
+ isna,
+)
+
+import pandas.core.algorithms as algos
+from pandas.core.array_algos.putmask import validate_putmask
+from pandas.core.arrays import (
+ Categorical,
+ ExtensionArray,
+)
+from pandas.core.arrays.categorical import (
+ factorize_from_iterables,
+ recode_for_categories,
+)
+import pandas.core.common as com
+from pandas.core.construction import sanitize_array
+import pandas.core.indexes.base as ibase
+from pandas.core.indexes.base import (
+ Index,
+ ensure_index,
+ get_unanimous_names,
+)
+from pandas.core.indexes.frozen import FrozenList
+from pandas.core.ops.invalid import make_invalid_op
+from pandas.core.sorting import (
+ get_group_index,
+ lexsort_indexer,
+)
+
+from pandas.io.formats.printing import pprint_thing
+
+if TYPE_CHECKING:
+ from pandas import (
+ CategoricalIndex,
+ DataFrame,
+ Series,
+ )
+
+
+class MultiIndexUInt64Engine(libindex.BaseMultiIndexCodesEngine, libindex.UInt64Engine):
+ """Manages a MultiIndex by mapping label combinations to positive integers.
+
+ The number of possible label combinations must not overflow the 64 bits integers.
+ """
+
+ _base = libindex.UInt64Engine
+ _codes_dtype = "uint64"
+
+
+class MultiIndexUInt32Engine(libindex.BaseMultiIndexCodesEngine, libindex.UInt32Engine):
+ """Manages a MultiIndex by mapping label combinations to positive integers.
+
+ The number of possible label combinations must not overflow the 32 bits integers.
+ """
+
+ _base = libindex.UInt32Engine
+ _codes_dtype = "uint32"
+
+
+class MultiIndexUInt16Engine(libindex.BaseMultiIndexCodesEngine, libindex.UInt16Engine):
+ """Manages a MultiIndex by mapping label combinations to positive integers.
+
+ The number of possible label combinations must not overflow the 16 bits integers.
+ """
+
+ _base = libindex.UInt16Engine
+ _codes_dtype = "uint16"
+
+
+class MultiIndexUInt8Engine(libindex.BaseMultiIndexCodesEngine, libindex.UInt8Engine):
+ """Manages a MultiIndex by mapping label combinations to positive integers.
+
+ The number of possible label combinations must not overflow the 8 bits integers.
+ """
+
+ _base = libindex.UInt8Engine
+ _codes_dtype = "uint8"
+
+
+class MultiIndexPyIntEngine(libindex.BaseMultiIndexCodesEngine, libindex.ObjectEngine):
+ """Manages a MultiIndex by mapping label combinations to positive integers.
+
+ This class manages those (extreme) cases in which the number of possible
+ label combinations overflows the 64 bits integers, and uses an ObjectEngine
+ containing Python integers.
+ """
+
+ _base = libindex.ObjectEngine
+ _codes_dtype = "object"
+
+
+def names_compat(meth: F) -> F:
+ """
+ A decorator to allow either `name` or `names` keyword but not both.
+
+ This makes it easier to share code with base class.
+ """
+
+ @wraps(meth)
+ def new_meth(self_or_cls, *args, **kwargs):
+ if "name" in kwargs and "names" in kwargs:
+ raise TypeError("Can only provide one of `names` and `name`")
+ if "name" in kwargs:
+ kwargs["names"] = kwargs.pop("name")
+
+ return meth(self_or_cls, *args, **kwargs)
+
+ return cast(F, new_meth)
+
+
+@set_module("pandas")
+class MultiIndex(Index):
+ """
+ A multi-level, or hierarchical, index object for pandas objects.
+
+ Parameters
+ ----------
+ levels : sequence of arrays
+ The unique labels for each level.
+ codes : sequence of arrays
+ Integers for each level designating which label at each location.
+ sortorder : optional int
+ Level of sortedness (must be lexicographically sorted by that
+ level).
+ names : optional sequence of objects
+ Names for each of the index levels. (name is accepted for compat).
+ copy : bool, default False
+ Copy the meta-data.
+ name : Label
+ Kept for compatibility with 1-dimensional Index. Should not be used.
+ verify_integrity : bool, default True
+ Check that the levels/codes are consistent and valid.
+
+ Attributes
+ ----------
+ names
+ levels
+ codes
+ nlevels
+ levshape
+ dtypes
+
+ Methods
+ -------
+ from_arrays
+ from_tuples
+ from_product
+ from_frame
+ set_levels
+ set_codes
+ to_frame
+ to_flat_index
+ sortlevel
+ droplevel
+ swaplevel
+ reorder_levels
+ remove_unused_levels
+ get_level_values
+ get_indexer
+ get_loc
+ get_locs
+ get_loc_level
+ drop
+
+ See Also
+ --------
+ MultiIndex.from_arrays : Convert list of arrays to MultiIndex.
+ MultiIndex.from_product : Create a MultiIndex from the cartesian product
+ of iterables.
+ MultiIndex.from_tuples : Convert list of tuples to a MultiIndex.
+ MultiIndex.from_frame : Make a MultiIndex from a DataFrame.
+ Index : The base pandas Index type.
+
+ Notes
+ -----
+ See the `user guide
+ `__
+ for more.
+
+ Examples
+ --------
+ A new ``MultiIndex`` is typically constructed using one of the helper
+ methods :meth:`MultiIndex.from_arrays`, :meth:`MultiIndex.from_product`
+ and :meth:`MultiIndex.from_tuples`. For example (using ``.from_arrays``):
+
+ >>> arrays = [[1, 1, 2, 2], ["red", "blue", "red", "blue"]]
+ >>> pd.MultiIndex.from_arrays(arrays, names=("number", "color"))
+ MultiIndex([(1, 'red'),
+ (1, 'blue'),
+ (2, 'red'),
+ (2, 'blue')],
+ names=['number', 'color'])
+
+ See further examples for how to construct a MultiIndex in the doc strings
+ of the mentioned helper methods.
+ """
+
+ _hidden_attrs = Index._hidden_attrs | frozenset()
+
+ # initialize to zero-length tuples to make everything work
+ _typ = "multiindex"
+ _names: list[Hashable | None] = []
+ _levels = FrozenList()
+ _codes = FrozenList()
+ _comparables = ["names"]
+
+ sortorder: int | None
+
+ # --------------------------------------------------------------------
+ # Constructors
+
+ def __new__(
+ cls,
+ levels=None,
+ codes=None,
+ sortorder=None,
+ names=None,
+ copy: bool = False,
+ name=None,
+ verify_integrity: bool = True,
+ ) -> Self:
+ # compat with Index
+ if name is not None:
+ names = name
+ if levels is None or codes is None:
+ raise TypeError("Must pass both levels and codes")
+ if len(levels) != len(codes):
+ raise ValueError("Length of levels and codes must be the same.")
+ if len(levels) == 0:
+ raise ValueError("Must pass non-zero number of levels/codes")
+
+ result = object.__new__(cls)
+ result._cache = {}
+
+ # we've already validated levels and codes, so shortcut here
+ result._set_levels(levels, copy=copy, validate=False)
+ result._set_codes(codes, copy=copy, validate=False)
+
+ result._names = [None] * len(levels)
+ if names is not None:
+ # handles name validation
+ result._set_names(names)
+
+ if sortorder is not None:
+ result.sortorder = int(sortorder)
+ else:
+ result.sortorder = sortorder
+
+ if verify_integrity:
+ new_codes = result._verify_integrity()
+ result._codes = new_codes
+
+ result._reset_identity()
+ result._references = None
+
+ return result
+
+ def _validate_codes(self, level: Index, code: np.ndarray) -> np.ndarray:
+ """
+ Reassign code values as -1 if their corresponding levels are NaN.
+
+ Parameters
+ ----------
+ code : Index
+ Code to reassign.
+ level : np.ndarray
+ Level to check for missing values (NaN, NaT, None).
+
+ Returns
+ -------
+ new code where code value = -1 if it corresponds
+ to a level with missing values (NaN, NaT, None).
+ """
+ null_mask = isna(level)
+ if np.any(null_mask):
+ code = np.where(null_mask[code], -1, code)
+ return code
+
+ def _verify_integrity(
+ self,
+ codes: list | None = None,
+ levels: list | None = None,
+ levels_to_verify: list[int] | range | None = None,
+ ) -> FrozenList:
+ """
+ Parameters
+ ----------
+ codes : optional list
+ Codes to check for validity. Defaults to current codes.
+ levels : optional list
+ Levels to check for validity. Defaults to current levels.
+ levels_to_validate: optional list
+ Specifies the levels to verify.
+
+ Raises
+ ------
+ ValueError
+ If length of levels and codes don't match, if the codes for any
+ level would exceed level bounds, or there are any duplicate levels.
+
+ Returns
+ -------
+ new codes where code value = -1 if it corresponds to a
+ NaN level.
+ """
+ # NOTE: Currently does not check, among other things, that cached
+ # nlevels matches nor that sortorder matches actually sortorder.
+ codes = codes or self.codes
+ levels = levels or self.levels
+ if levels_to_verify is None:
+ levels_to_verify = range(len(levels))
+
+ if len(levels) != len(codes):
+ raise ValueError(
+ "Length of levels and codes must match. NOTE: "
+ "this index is in an inconsistent state."
+ )
+ codes_length = len(codes[0])
+ for i in levels_to_verify:
+ level = levels[i]
+ level_codes = codes[i]
+
+ if len(level_codes) != codes_length:
+ raise ValueError(
+ f"Unequal code lengths: {[len(code_) for code_ in codes]}"
+ )
+ if len(level_codes) and level_codes.max() >= len(level):
+ raise ValueError(
+ f"On level {i}, code max ({level_codes.max()}) >= length of "
+ f"level ({len(level)}). NOTE: this index is in an "
+ "inconsistent state"
+ )
+ if len(level_codes) and level_codes.min() < -1:
+ raise ValueError(f"On level {i}, code value ({level_codes.min()}) < -1")
+ if not level.is_unique:
+ raise ValueError(
+ f"Level values must be unique: {list(level)} on level {i}"
+ )
+ if self.sortorder is not None:
+ if self.sortorder > _lexsort_depth(self.codes, self.nlevels):
+ raise ValueError(
+ "Value for sortorder must be inferior or equal to actual "
+ f"lexsort_depth: sortorder {self.sortorder} "
+ f"with lexsort_depth {_lexsort_depth(self.codes, self.nlevels)}"
+ )
+
+ result_codes = []
+ for i in range(len(levels)):
+ if i in levels_to_verify:
+ result_codes.append(self._validate_codes(levels[i], codes[i]))
+ else:
+ result_codes.append(codes[i])
+
+ new_codes = FrozenList(result_codes)
+ return new_codes
+
+ @classmethod
+ def from_arrays(
+ cls,
+ arrays,
+ sortorder: int | None = None,
+ names: Sequence[Hashable] | Hashable | lib.NoDefault = lib.no_default,
+ ) -> MultiIndex:
+ """
+ Convert arrays to MultiIndex.
+
+ Parameters
+ ----------
+ arrays : list / sequence of array-likes
+ Each array-like gives one level's value for each data point.
+ len(arrays) is the number of levels.
+ sortorder : int or None
+ Level of sortedness (must be lexicographically sorted by that
+ level).
+ names : list / sequence of str, optional
+ Names for the levels in the index.
+
+ Returns
+ -------
+ MultiIndex
+
+ See Also
+ --------
+ MultiIndex.from_tuples : Convert list of tuples to MultiIndex.
+ MultiIndex.from_product : Make a MultiIndex from cartesian product
+ of iterables.
+ MultiIndex.from_frame : Make a MultiIndex from a DataFrame.
+
+ Examples
+ --------
+ >>> arrays = [[1, 1, 2, 2], ["red", "blue", "red", "blue"]]
+ >>> pd.MultiIndex.from_arrays(arrays, names=("number", "color"))
+ MultiIndex([(1, 'red'),
+ (1, 'blue'),
+ (2, 'red'),
+ (2, 'blue')],
+ names=['number', 'color'])
+ """
+ error_msg = "Input must be a list / sequence of array-likes."
+ if not is_list_like(arrays):
+ raise TypeError(error_msg)
+ if is_iterator(arrays):
+ arrays = list(arrays)
+
+ # Check if elements of array are list-like
+ for array in arrays:
+ if not is_list_like(array):
+ raise TypeError(error_msg)
+
+ # Check if lengths of all arrays are equal or not,
+ # raise ValueError, if not
+ for i in range(1, len(arrays)):
+ if len(arrays[i]) != len(arrays[i - 1]):
+ raise ValueError("all arrays must be same length")
+
+ codes, levels = factorize_from_iterables(arrays)
+ if names is lib.no_default:
+ names = [getattr(arr, "name", None) for arr in arrays]
+
+ return cls(
+ levels=levels,
+ codes=codes,
+ sortorder=sortorder,
+ names=names,
+ verify_integrity=False,
+ )
+
+ @classmethod
+ @names_compat
+ def from_tuples(
+ cls,
+ tuples: Iterable[tuple[Hashable, ...]],
+ sortorder: int | None = None,
+ names: Sequence[Hashable] | Hashable | None = None,
+ ) -> MultiIndex:
+ """
+ Convert list of tuples to MultiIndex.
+
+ Parameters
+ ----------
+ tuples : list / sequence of tuple-likes
+ Each tuple is the index of one row/column.
+ sortorder : int or None
+ Level of sortedness (must be lexicographically sorted by that
+ level).
+ names : list / sequence of str, optional
+ Names for the levels in the index.
+
+ Returns
+ -------
+ MultiIndex
+
+ See Also
+ --------
+ MultiIndex.from_arrays : Convert list of arrays to MultiIndex.
+ MultiIndex.from_product : Make a MultiIndex from cartesian product
+ of iterables.
+ MultiIndex.from_frame : Make a MultiIndex from a DataFrame.
+
+ Examples
+ --------
+ >>> tuples = [(1, "red"), (1, "blue"), (2, "red"), (2, "blue")]
+ >>> pd.MultiIndex.from_tuples(tuples, names=("number", "color"))
+ MultiIndex([(1, 'red'),
+ (1, 'blue'),
+ (2, 'red'),
+ (2, 'blue')],
+ names=['number', 'color'])
+ """
+ if not is_list_like(tuples):
+ raise TypeError("Input must be a list / sequence of tuple-likes.")
+ if is_iterator(tuples):
+ tuples = list(tuples)
+ tuples = cast(Collection[tuple[Hashable, ...]], tuples)
+
+ # handling the empty tuple cases
+ if len(tuples) and all(isinstance(e, tuple) and not e for e in tuples):
+ codes = [np.zeros(len(tuples))]
+ levels = [Index(com.asarray_tuplesafe(tuples, dtype=np.dtype("object")))]
+ return cls(
+ levels=levels,
+ codes=codes,
+ sortorder=sortorder,
+ names=names,
+ verify_integrity=False,
+ )
+
+ arrays: list[Sequence[Hashable]]
+ if len(tuples) == 0:
+ if names is None:
+ raise TypeError("Cannot infer number of levels from empty list")
+ # error: Argument 1 to "len" has incompatible type "Hashable";
+ # expected "Sized"
+ arrays = [[]] * len(names) # type: ignore[arg-type]
+ elif isinstance(tuples, (np.ndarray, Index)):
+ if isinstance(tuples, Index):
+ tuples = np.asarray(tuples._values)
+
+ arrays = list(lib.tuples_to_object_array(tuples).T)
+ elif isinstance(tuples, list):
+ arrays = list(lib.to_object_array_tuples(tuples).T)
+ else:
+ arrs = zip_longest(*tuples, fillvalue=np.nan)
+ arrays = cast(list[Sequence[Hashable]], arrs)
+
+ return cls.from_arrays(arrays, sortorder=sortorder, names=names)
+
+ @classmethod
+ def from_product(
+ cls,
+ iterables: Sequence[Iterable[Hashable]],
+ sortorder: int | None = None,
+ names: Sequence[Hashable] | Hashable | lib.NoDefault = lib.no_default,
+ ) -> MultiIndex:
+ """
+ Make a MultiIndex from the cartesian product of multiple iterables.
+
+ Parameters
+ ----------
+ iterables : list / sequence of iterables
+ Each iterable has unique labels for each level of the index.
+ sortorder : int or None
+ Level of sortedness (must be lexicographically sorted by that
+ level).
+ names : list / sequence of str, optional
+ Names for the levels in the index.
+ If not explicitly provided, names will be inferred from the
+ elements of iterables if an element has a name attribute.
+
+ Returns
+ -------
+ MultiIndex
+
+ See Also
+ --------
+ MultiIndex.from_arrays : Convert list of arrays to MultiIndex.
+ MultiIndex.from_tuples : Convert list of tuples to MultiIndex.
+ MultiIndex.from_frame : Make a MultiIndex from a DataFrame.
+
+ Examples
+ --------
+ >>> numbers = [0, 1, 2]
+ >>> colors = ["green", "purple"]
+ >>> pd.MultiIndex.from_product([numbers, colors], names=["number", "color"])
+ MultiIndex([(0, 'green'),
+ (0, 'purple'),
+ (1, 'green'),
+ (1, 'purple'),
+ (2, 'green'),
+ (2, 'purple')],
+ names=['number', 'color'])
+ """
+
+ if not is_list_like(iterables):
+ raise TypeError("Input must be a list / sequence of iterables.")
+ if is_iterator(iterables):
+ iterables = list(iterables)
+
+ codes, levels = factorize_from_iterables(iterables)
+ if names is lib.no_default:
+ names = [getattr(it, "name", None) for it in iterables]
+
+ # codes are all ndarrays, so cartesian_product is lossless
+ codes = cartesian_product(codes)
+ return cls(levels, codes, sortorder=sortorder, names=names)
+
+ @classmethod
+ def from_frame(
+ cls,
+ df: DataFrame,
+ sortorder: int | None = None,
+ names: Sequence[Hashable] | Hashable | None = None,
+ ) -> MultiIndex:
+ """
+ Make a MultiIndex from a DataFrame.
+
+ Parameters
+ ----------
+ df : DataFrame
+ DataFrame to be converted to MultiIndex.
+ sortorder : int, optional
+ Level of sortedness (must be lexicographically sorted by that
+ level).
+ names : list-like, optional
+ If no names are provided, use the column names, or tuple of column
+ names if the columns is a MultiIndex. If a sequence, overwrite
+ names with the given sequence.
+
+ Returns
+ -------
+ MultiIndex
+ The MultiIndex representation of the given DataFrame.
+
+ See Also
+ --------
+ MultiIndex.from_arrays : Convert list of arrays to MultiIndex.
+ MultiIndex.from_tuples : Convert list of tuples to MultiIndex.
+ MultiIndex.from_product : Make a MultiIndex from cartesian product
+ of iterables.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame(
+ ... [["HI", "Temp"], ["HI", "Precip"], ["NJ", "Temp"], ["NJ", "Precip"]],
+ ... columns=["a", "b"],
+ ... )
+ >>> df
+ a b
+ 0 HI Temp
+ 1 HI Precip
+ 2 NJ Temp
+ 3 NJ Precip
+
+ >>> pd.MultiIndex.from_frame(df)
+ MultiIndex([('HI', 'Temp'),
+ ('HI', 'Precip'),
+ ('NJ', 'Temp'),
+ ('NJ', 'Precip')],
+ names=['a', 'b'])
+
+ Using explicit names, instead of the column names
+
+ >>> pd.MultiIndex.from_frame(df, names=["state", "observation"])
+ MultiIndex([('HI', 'Temp'),
+ ('HI', 'Precip'),
+ ('NJ', 'Temp'),
+ ('NJ', 'Precip')],
+ names=['state', 'observation'])
+ """
+ if not isinstance(df, ABCDataFrame):
+ raise TypeError("Input must be a DataFrame")
+
+ column_names, columns = zip(*df.items(), strict=True)
+ names = column_names if names is None else names
+ return cls.from_arrays(columns, sortorder=sortorder, names=names)
+
+ # --------------------------------------------------------------------
+
+ @cache_readonly
+ def _values(self) -> np.ndarray:
+ # We override here, since our parent uses _data, which we don't use.
+ values = []
+
+ for i in range(self.nlevels):
+ index = self.levels[i]
+ codes = self.codes[i]
+
+ vals = index
+ if isinstance(vals.dtype, CategoricalDtype):
+ vals = cast("CategoricalIndex", vals)
+ vals = vals._data._internal_get_values()
+
+ if isinstance(vals.dtype, ExtensionDtype) or lib.is_np_dtype(
+ vals.dtype, "mM"
+ ):
+ vals = vals.astype(object)
+
+ array_vals = np.asarray(vals)
+ array_vals = algos.take_nd(array_vals, codes, fill_value=index._na_value)
+ values.append(array_vals)
+
+ arr = lib.fast_zip(values)
+ return arr
+
+ @property
+ def values(self) -> np.ndarray:
+ return self._values
+
+ @property
+ def array(self):
+ """
+ Raises a ValueError for `MultiIndex` because there's no single
+ array backing a MultiIndex.
+
+ Raises
+ ------
+ ValueError
+ """
+ raise ValueError(
+ "MultiIndex has no single backing array. Use "
+ "'MultiIndex.to_numpy()' to get a NumPy array of tuples."
+ )
+
+ @cache_readonly
+ def dtypes(self) -> Series:
+ """
+ Return the dtypes as a Series for the underlying MultiIndex.
+
+ See Also
+ --------
+ Index.dtype : Return the dtype object of the underlying data.
+ Series.dtypes : Return the data type of the underlying Series.
+
+ Examples
+ --------
+ >>> idx = pd.MultiIndex.from_product(
+ ... [(0, 1, 2), ("green", "purple")], names=["number", "color"]
+ ... )
+ >>> idx
+ MultiIndex([(0, 'green'),
+ (0, 'purple'),
+ (1, 'green'),
+ (1, 'purple'),
+ (2, 'green'),
+ (2, 'purple')],
+ names=['number', 'color'])
+ >>> idx.dtypes
+ number int64
+ color object
+ dtype: object
+ """
+ from pandas import Series
+
+ names = com.fill_missing_names(self.names)
+ return Series([level.dtype for level in self.levels], index=Index(names))
+
+ def __len__(self) -> int:
+ return len(self.codes[0])
+
+ @property
+ def size(self) -> int:
+ """
+ Return the number of elements in the underlying data.
+ """
+ # override Index.size to avoid materializing _values
+ return len(self)
+
+ # --------------------------------------------------------------------
+ # Levels Methods
+
+ @cache_readonly
+ def levels(self) -> FrozenList:
+ """
+ Levels of the MultiIndex.
+
+ Levels refer to the different hierarchical levels or layers in a MultiIndex.
+ In a MultiIndex, each level represents a distinct dimension or category of
+ the index.
+
+ To access the levels, you can use the levels attribute of the MultiIndex,
+ which returns a tuple of Index objects. Each Index object represents a
+ level in the MultiIndex and contains the unique values found in that
+ specific level.
+
+ If a MultiIndex is created with levels A, B, C, and the DataFrame using
+ it filters out all rows of the level C, MultiIndex.levels will still
+ return A, B, C.
+
+ See Also
+ --------
+ MultiIndex.codes : The codes of the levels in the MultiIndex.
+ MultiIndex.get_level_values : Return vector of label values for requested
+ level.
+
+ Examples
+ --------
+ >>> index = pd.MultiIndex.from_product(
+ ... [["mammal"], ("goat", "human", "cat", "dog")],
+ ... names=["Category", "Animals"],
+ ... )
+ >>> leg_num = pd.DataFrame(data=(4, 2, 4, 4), index=index, columns=["Legs"])
+ >>> leg_num
+ Legs
+ Category Animals
+ mammal goat 4
+ human 2
+ cat 4
+ dog 4
+
+ >>> leg_num.index.levels
+ FrozenList([['mammal'], ['cat', 'dog', 'goat', 'human']])
+
+ MultiIndex levels will not change even if the DataFrame using the MultiIndex
+ does not contain all them anymore.
+ See how "human" is not in the DataFrame, but it is still in levels:
+
+ >>> large_leg_num = leg_num[leg_num.Legs > 2]
+ >>> large_leg_num
+ Legs
+ Category Animals
+ mammal goat 4
+ cat 4
+ dog 4
+
+ >>> large_leg_num.index.levels
+ FrozenList([['mammal'], ['cat', 'dog', 'goat', 'human']])
+ """
+ # Use cache_readonly to ensure that self.get_locs doesn't repeatedly
+ # create new IndexEngine
+ # https://github.com/pandas-dev/pandas/issues/31648
+ result = [
+ x._rename(name=name)
+ for x, name in zip(self._levels, self._names, strict=True)
+ ]
+ for level in result:
+ # disallow midx.levels[0].name = "foo"
+ level._no_setting_name = True
+ return FrozenList(result)
+
+ def _set_levels(
+ self,
+ levels,
+ *,
+ level=None,
+ copy: bool = False,
+ validate: bool = True,
+ verify_integrity: bool = False,
+ ) -> None:
+ # This is NOT part of the levels property because it should be
+ # externally not allowed to set levels. User beware if you change
+ # _levels directly
+ if validate:
+ if len(levels) == 0:
+ raise ValueError("Must set non-zero number of levels.")
+ if level is None and len(levels) != self.nlevels:
+ raise ValueError("Length of levels must match number of levels.")
+ if level is not None and len(levels) != len(level):
+ raise ValueError("Length of levels must match length of level.")
+
+ if level is None:
+ new_levels = FrozenList(
+ ensure_index(lev, copy=copy)._view() for lev in levels
+ )
+ level_numbers: range | list[int] = range(len(new_levels))
+ else:
+ level_numbers = [self._get_level_number(lev) for lev in level]
+ new_levels_list = list(self._levels)
+ for lev_num, lev in zip(level_numbers, levels, strict=True):
+ new_levels_list[lev_num] = ensure_index(lev, copy=copy)._view()
+ new_levels = FrozenList(new_levels_list)
+
+ if verify_integrity:
+ new_codes = self._verify_integrity(
+ levels=new_levels, levels_to_verify=level_numbers
+ )
+ self._codes = new_codes
+
+ names = self.names
+ self._levels = new_levels
+ if any(names):
+ self._set_names(names)
+
+ self._reset_cache()
+
+ def set_levels(
+ self, levels, *, level=None, verify_integrity: bool = True
+ ) -> MultiIndex:
+ """
+ Set new levels on MultiIndex. Defaults to returning new index.
+
+ The `set_levels` method provides a flexible way to change the levels of a
+ `MultiIndex`. This is particularly useful when you need to update the
+ index structure of your DataFrame without altering the data. The method
+ returns a new `MultiIndex` unless the operation is performed in-place,
+ ensuring that the original index remains unchanged unless explicitly
+ modified.
+
+ The method checks the integrity of the new levels against the existing
+ codes by default, but this can be disabled if you are confident that
+ your levels are consistent with the underlying data. This can be useful
+ when you want to perform optimizations or make specific adjustments to
+ the index levels that do not strictly adhere to the original structure.
+
+ Parameters
+ ----------
+ levels : sequence or list of sequence
+ New level(s) to apply.
+ level : int, level name, or sequence of int/level names (default None)
+ Level(s) to set (None for all levels).
+ verify_integrity : bool, default True
+ If True, checks that levels and codes are compatible.
+
+ Returns
+ -------
+ MultiIndex
+ A new `MultiIndex` with the updated levels.
+
+ See Also
+ --------
+ MultiIndex.set_codes : Set new codes on the existing `MultiIndex`.
+ MultiIndex.remove_unused_levels : Create new MultiIndex from current that
+ removes unused levels.
+ Index.set_names : Set Index or MultiIndex name.
+
+ Examples
+ --------
+ >>> idx = pd.MultiIndex.from_tuples(
+ ... [
+ ... (1, "one"),
+ ... (1, "two"),
+ ... (2, "one"),
+ ... (2, "two"),
+ ... (3, "one"),
+ ... (3, "two"),
+ ... ],
+ ... names=["foo", "bar"],
+ ... )
+ >>> idx
+ MultiIndex([(1, 'one'),
+ (1, 'two'),
+ (2, 'one'),
+ (2, 'two'),
+ (3, 'one'),
+ (3, 'two')],
+ names=['foo', 'bar'])
+
+ >>> idx.set_levels([["a", "b", "c"], [1, 2]])
+ MultiIndex([('a', 1),
+ ('a', 2),
+ ('b', 1),
+ ('b', 2),
+ ('c', 1),
+ ('c', 2)],
+ names=['foo', 'bar'])
+ >>> idx.set_levels(["a", "b", "c"], level=0)
+ MultiIndex([('a', 'one'),
+ ('a', 'two'),
+ ('b', 'one'),
+ ('b', 'two'),
+ ('c', 'one'),
+ ('c', 'two')],
+ names=['foo', 'bar'])
+ >>> idx.set_levels(["a", "b"], level="bar")
+ MultiIndex([(1, 'a'),
+ (1, 'b'),
+ (2, 'a'),
+ (2, 'b'),
+ (3, 'a'),
+ (3, 'b')],
+ names=['foo', 'bar'])
+
+ If any of the levels passed to ``set_levels()`` exceeds the
+ existing length, all of the values from that argument will
+ be stored in the MultiIndex levels, though the values will
+ be truncated in the MultiIndex output.
+
+ >>> idx.set_levels([["a", "b", "c"], [1, 2, 3, 4]], level=[0, 1])
+ MultiIndex([('a', 1),
+ ('a', 2),
+ ('b', 1),
+ ('b', 2),
+ ('c', 1),
+ ('c', 2)],
+ names=['foo', 'bar'])
+ >>> idx.set_levels([["a", "b", "c"], [1, 2, 3, 4]], level=[0, 1]).levels
+ FrozenList([['a', 'b', 'c'], [1, 2, 3, 4]])
+ """
+
+ if isinstance(levels, Index):
+ pass
+ elif is_array_like(levels):
+ levels = Index(levels)
+ elif is_list_like(levels):
+ levels = list(levels)
+
+ level, levels = _require_listlike(level, levels, "Levels")
+ idx = self._view()
+ idx._reset_identity()
+ idx._set_levels(
+ levels, level=level, validate=True, verify_integrity=verify_integrity
+ )
+ return idx
+
+ @property
+ def nlevels(self) -> int:
+ """
+ Integer number of levels in this MultiIndex.
+
+ See Also
+ --------
+ MultiIndex.levels : Get the levels of the MultiIndex.
+ MultiIndex.codes : Get the codes of the MultiIndex.
+ MultiIndex.from_arrays : Convert arrays to MultiIndex.
+ MultiIndex.from_tuples : Convert list of tuples to MultiIndex.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([["a"], ["b"], ["c"]])
+ >>> mi
+ MultiIndex([('a', 'b', 'c')],
+ )
+ >>> mi.nlevels
+ 3
+ """
+ return len(self._levels)
+
+ @property
+ def levshape(self) -> Shape:
+ """
+ A tuple representing the length of each level in the MultiIndex.
+
+ In a `MultiIndex`, each level can contain multiple unique values. The
+ `levshape` property provides a quick way to assess the size of each
+ level by returning a tuple where each entry represents the number of
+ unique values in that specific level. This is particularly useful in
+ scenarios where you need to understand the structure and distribution
+ of your index levels, such as when working with multidimensional data.
+
+ See Also
+ --------
+ MultiIndex.shape : Return a tuple of the shape of the MultiIndex.
+ MultiIndex.levels : Returns the levels of the MultiIndex.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([["a"], ["b"], ["c"]])
+ >>> mi
+ MultiIndex([('a', 'b', 'c')],
+ )
+ >>> mi.levshape
+ (1, 1, 1)
+ """
+ return tuple(len(x) for x in self.levels)
+
+ # --------------------------------------------------------------------
+ # Codes Methods
+
+ @property
+ def codes(self) -> FrozenList:
+ """
+ Codes of the MultiIndex.
+
+ Codes are the position of the index value in the list of level values
+ for each level.
+
+ Returns
+ -------
+ tuple of numpy.ndarray
+ The codes of the MultiIndex. Each array in the tuple corresponds
+ to a level in the MultiIndex.
+
+ See Also
+ --------
+ MultiIndex.set_codes : Set new codes on MultiIndex.
+
+ Examples
+ --------
+ >>> arrays = [[1, 1, 2, 2], ["red", "blue", "red", "blue"]]
+ >>> mi = pd.MultiIndex.from_arrays(arrays, names=("number", "color"))
+ >>> mi.codes
+ FrozenList([[0, 0, 1, 1], [1, 0, 1, 0]])
+ """
+ return self._codes
+
+ def _set_codes(
+ self,
+ codes,
+ *,
+ level=None,
+ copy: bool = False,
+ validate: bool = True,
+ verify_integrity: bool = False,
+ ) -> None:
+ if validate:
+ if level is None and len(codes) != self.nlevels:
+ raise ValueError("Length of codes must match number of levels")
+ if level is not None and len(codes) != len(level):
+ raise ValueError("Length of codes must match length of levels.")
+
+ level_numbers: list[int] | range
+ if level is None:
+ new_codes = FrozenList(
+ _coerce_indexer_frozen(level_codes, lev, copy=copy).view()
+ for lev, level_codes in zip(self._levels, codes, strict=True)
+ )
+ level_numbers = range(len(new_codes))
+ else:
+ level_numbers = [self._get_level_number(lev) for lev in level]
+ new_codes_list = list(self._codes)
+ for lev_num, level_codes in zip(level_numbers, codes, strict=True):
+ lev = self.levels[lev_num]
+ new_codes_list[lev_num] = _coerce_indexer_frozen(
+ level_codes, lev, copy=copy
+ )
+ new_codes = FrozenList(new_codes_list)
+
+ if verify_integrity:
+ new_codes = self._verify_integrity(
+ codes=new_codes, levels_to_verify=level_numbers
+ )
+
+ self._codes = new_codes
+
+ self._reset_cache()
+
+ def set_codes(
+ self, codes, *, level=None, verify_integrity: bool = True
+ ) -> MultiIndex:
+ """
+ Set new codes on MultiIndex. Defaults to returning new index.
+
+ Parameters
+ ----------
+ codes : sequence or list of sequence
+ New codes to apply.
+ level : int, level name, or sequence of int/level names (default None)
+ Level(s) to set (None for all levels).
+ verify_integrity : bool, default True
+ If True, checks that levels and codes are compatible.
+
+ Returns
+ -------
+ new index (of same type and class...etc) or None
+ The same type as the caller or None if ``inplace=True``.
+
+ See Also
+ --------
+ MultiIndex.set_levels : Set new levels on MultiIndex.
+ MultiIndex.codes : Get the codes of the levels in the MultiIndex.
+ MultiIndex.levels : Get the levels of the MultiIndex.
+
+ Examples
+ --------
+ >>> idx = pd.MultiIndex.from_tuples(
+ ... [(1, "one"), (1, "two"), (2, "one"), (2, "two")], names=["foo", "bar"]
+ ... )
+ >>> idx
+ MultiIndex([(1, 'one'),
+ (1, 'two'),
+ (2, 'one'),
+ (2, 'two')],
+ names=['foo', 'bar'])
+
+ >>> idx.set_codes([[1, 0, 1, 0], [0, 0, 1, 1]])
+ MultiIndex([(2, 'one'),
+ (1, 'one'),
+ (2, 'two'),
+ (1, 'two')],
+ names=['foo', 'bar'])
+ >>> idx.set_codes([1, 0, 1, 0], level=0)
+ MultiIndex([(2, 'one'),
+ (1, 'two'),
+ (2, 'one'),
+ (1, 'two')],
+ names=['foo', 'bar'])
+ >>> idx.set_codes([0, 0, 1, 1], level="bar")
+ MultiIndex([(1, 'one'),
+ (1, 'one'),
+ (2, 'two'),
+ (2, 'two')],
+ names=['foo', 'bar'])
+ >>> idx.set_codes([[1, 0, 1, 0], [0, 0, 1, 1]], level=[0, 1])
+ MultiIndex([(2, 'one'),
+ (1, 'one'),
+ (2, 'two'),
+ (1, 'two')],
+ names=['foo', 'bar'])
+ """
+
+ level, codes = _require_listlike(level, codes, "Codes")
+ idx = self._view()
+ idx._reset_identity()
+ idx._set_codes(codes, level=level, verify_integrity=verify_integrity)
+ return idx
+
+ # --------------------------------------------------------------------
+ # Index Internals
+
+ @cache_readonly
+ def _engine(self):
+ # Calculate the number of bits needed to represent labels in each
+ # level, as log2 of their sizes:
+ # NaN values are shifted to 1 and missing values in other while
+ # calculating the indexer are shifted to 0
+ sizes = np.ceil(
+ np.log2(
+ [len(level) + libindex.multiindex_nulls_shift for level in self.levels]
+ )
+ )
+
+ # Sum bit counts, starting from the _right_....
+ lev_bits = np.cumsum(sizes[::-1])[::-1]
+
+ # ... in order to obtain offsets such that sorting the combination of
+ # shifted codes (one for each level, resulting in a unique integer) is
+ # equivalent to sorting lexicographically the codes themselves. Notice
+ # that each level needs to be shifted by the number of bits needed to
+ # represent the _previous_ ones:
+ offsets = np.concatenate([lev_bits[1:], [0]])
+ # Downcast the type if possible, to prevent upcasting when shifting codes:
+ offsets = offsets.astype(np.min_scalar_type(int(offsets[0])))
+
+ # Check the total number of bits needed for our representation:
+ if lev_bits[0] > 64:
+ # The levels would overflow a 64 bit uint - use Python integers:
+ return MultiIndexPyIntEngine(self.levels, self.codes, offsets)
+ if lev_bits[0] > 32:
+ # The levels would overflow a 32 bit uint - use uint64
+ return MultiIndexUInt64Engine(self.levels, self.codes, offsets)
+ if lev_bits[0] > 16:
+ # The levels would overflow a 16 bit uint - use uint8
+ return MultiIndexUInt32Engine(self.levels, self.codes, offsets)
+ if lev_bits[0] > 8:
+ # The levels would overflow a 8 bit uint - use uint16
+ return MultiIndexUInt16Engine(self.levels, self.codes, offsets)
+ # The levels fit in an 8 bit uint - use uint8
+ return MultiIndexUInt8Engine(self.levels, self.codes, offsets)
+
+ # Return type "Callable[..., MultiIndex]" of "_constructor" incompatible with return
+ # type "Type[MultiIndex]" in supertype "Index"
+ @property
+ def _constructor(self) -> Callable[..., MultiIndex]: # type: ignore[override]
+ return type(self).from_tuples
+
+ def _shallow_copy(self, values: np.ndarray, name=lib.no_default) -> MultiIndex:
+ """
+ Create a new Index with the same class as the caller, don't copy the
+ data, use the same object attributes with passed in attributes taking
+ precedence.
+
+ *this is an internal non-public method*
+
+ Parameters
+ ----------
+ values : the values to create the new Index, optional
+ name : Label, defaults to self.name
+ """
+ names = name if name is not lib.no_default else self.names
+
+ return type(self).from_tuples(values, sortorder=None, names=names)
+
+ def _view(self) -> MultiIndex:
+ result = type(self)(
+ levels=self.levels,
+ codes=self.codes,
+ sortorder=self.sortorder,
+ names=self.names,
+ verify_integrity=False,
+ )
+ result._cache = self._cache.copy()
+ result._reset_cache("levels") # GH32669
+ return result
+
+ # --------------------------------------------------------------------
+
+ # error: Signature of "copy" incompatible with supertype "Index"
+ def copy( # type: ignore[override]
+ self,
+ names=None,
+ deep: bool = False,
+ name=None,
+ ) -> Self:
+ """
+ Make a copy of this object. Names, dtype, levels and codes can be passed and \
+ will be set on new copy.
+
+ The `copy` method provides a mechanism to create a duplicate of an
+ existing MultiIndex object. This is particularly useful in scenarios where
+ modifications are required on an index, but the original MultiIndex should
+ remain unchanged. By specifying the `deep` parameter, users can control
+ whether the copy should be a deep or shallow copy, providing flexibility
+ depending on the size and complexity of the MultiIndex.
+
+ Parameters
+ ----------
+ names : sequence, optional
+ Names to set on the new MultiIndex object.
+ deep : bool, default False
+ If False, the new object will be a shallow copy. If True, a deep copy
+ will be attempted. Deep copying can be potentially expensive for large
+ MultiIndex objects.
+ name : Label
+ Kept for compatibility with 1-dimensional Index. Should not be used.
+
+ Returns
+ -------
+ MultiIndex
+ A new MultiIndex object with the specified modifications.
+
+ See Also
+ --------
+ MultiIndex.from_arrays : Convert arrays to MultiIndex.
+ MultiIndex.from_tuples : Convert list of tuples to MultiIndex.
+ MultiIndex.from_frame : Convert DataFrame to MultiIndex.
+
+ Notes
+ -----
+ In most cases, there should be no functional difference from using
+ ``deep``, but if ``deep`` is passed it will attempt to deepcopy.
+ This could be potentially expensive on large MultiIndex objects.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([["a"], ["b"], ["c"]])
+ >>> mi
+ MultiIndex([('a', 'b', 'c')],
+ )
+ >>> mi.copy()
+ MultiIndex([('a', 'b', 'c')],
+ )
+ """
+ names = self._validate_names(name=name, names=names, deep=deep)
+ keep_id = not deep
+ levels, codes = None, None
+
+ if deep:
+ from copy import deepcopy
+
+ levels = deepcopy(self.levels)
+ codes = deepcopy(self.codes)
+
+ levels = levels if levels is not None else self.levels
+ codes = codes if codes is not None else self.codes
+
+ new_index = type(self)(
+ levels=levels,
+ codes=codes,
+ sortorder=self.sortorder,
+ names=names,
+ verify_integrity=False,
+ )
+ new_index._cache = self._cache.copy()
+ new_index._reset_cache("levels") # GH32669
+ if keep_id:
+ new_index._id = self._id
+ return new_index
+
+ def __array__(self, dtype=None, copy=None) -> np.ndarray:
+ """the array interface, return my values"""
+ if copy is False:
+ # self.values is always a newly construct array, so raise.
+ raise ValueError(
+ "Unable to avoid copy while creating an array as requested."
+ )
+ if copy is True:
+ # explicit np.array call to ensure a copy is made and unique objects
+ # are returned, because self.values is cached
+ return np.array(self.values, dtype=dtype)
+ return self.values
+
+ def view(self, cls=None) -> Self:
+ """this is defined as a copy with the same identity"""
+ result = self.copy()
+ result._id = self._id
+ return result
+
+ def __contains__(self, key: Any) -> bool:
+ """
+ Return a boolean indicating whether the provided key is in the index.
+
+ Parameters
+ ----------
+ key : label
+ The key to check if it is present in the index.
+
+ Returns
+ -------
+ bool
+ Whether the key search is in the index.
+
+ Raises
+ ------
+ TypeError
+ If the key is not hashable.
+
+ See Also
+ --------
+ Index.isin : Returns an ndarray of boolean dtype indicating whether the
+ list-like key is in the index.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([["a"], ["b"], ["c"]])
+ >>> mi
+ MultiIndex([('a', 'b', 'c')],
+ )
+
+ >>> "a" in mi
+ True
+ >>> "x" in mi
+ False
+ """
+ hash(key)
+ try:
+ self.get_loc(key)
+ return True
+ except (LookupError, TypeError, ValueError):
+ return False
+
+ @cache_readonly
+ def dtype(self) -> np.dtype:
+ return np.dtype("O")
+
+ @cache_readonly
+ def _is_memory_usage_qualified(self) -> bool:
+ """return a boolean if we need a qualified .info display"""
+
+ def f(dtype) -> bool:
+ return is_object_dtype(dtype) or (
+ is_string_dtype(dtype) and dtype.storage == "python"
+ )
+
+ return any(f(level.dtype) for level in self.levels)
+
+ # Cannot determine type of "memory_usage"
+ def memory_usage(self, deep: bool = False) -> int:
+ """
+ Memory usage of the values.
+
+ Parameters
+ ----------
+ deep : bool, default False
+ Introspect the data deeply, interrogate
+ `object` dtypes for system-level memory consumption.
+
+ Returns
+ -------
+ bytes used
+ Returns memory usage of the values in the Index in bytes.
+
+ See Also
+ --------
+ numpy.ndarray.nbytes : Total bytes consumed by the elements of the
+ array.
+
+ Notes
+ -----
+ Memory usage does not include memory consumed by elements that
+ are not components of the array if deep=False or if used on PyPy
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([["a"], ["b"], ["c"]])
+ >>> mi.memory_usage()
+ 81
+ """
+ # we are overwriting our base class to avoid
+ # computing .values here which could materialize
+ # a tuple representation unnecessarily
+ return self._nbytes(deep)
+
+ @cache_readonly
+ def nbytes(self) -> int:
+ """return the number of bytes in the underlying data"""
+ return self._nbytes(False)
+
+ def _nbytes(self, deep: bool = False) -> int:
+ """
+ return the number of bytes in the underlying data
+ deeply introspect the level data if deep=True
+
+ include the engine hashtable
+
+ *this is in internal routine*
+
+ """
+ # for implementations with no useful getsizeof (PyPy)
+ objsize = 24
+
+ level_nbytes = sum(i.memory_usage(deep=deep) for i in self.levels)
+ label_nbytes = sum(i.nbytes for i in self.codes)
+ names_nbytes = sum(getsizeof(i, objsize) for i in self.names)
+ result = level_nbytes + label_nbytes + names_nbytes
+
+ # include our engine hashtable, only if it's already cached
+ if "_engine" in self._cache:
+ result += self._engine.sizeof(deep=deep)
+ return result
+
+ # --------------------------------------------------------------------
+ # Rendering Methods
+
+ def _formatter_func(self, tup):
+ """
+ Formats each item in tup according to its level's formatter function.
+ """
+ formatter_funcs = (level._formatter_func for level in self.levels)
+ return tuple(func(val) for func, val in zip(formatter_funcs, tup, strict=True))
+
+ def _get_values_for_csv(
+ self, *, na_rep: str = "nan", **kwargs
+ ) -> npt.NDArray[np.object_]:
+ new_levels = []
+ new_codes = []
+
+ # go through the levels and format them
+ for level, level_codes in zip(self.levels, self.codes, strict=True):
+ level_strs = level._get_values_for_csv(na_rep=na_rep, **kwargs)
+ # add nan values, if there are any
+ mask = level_codes == -1
+ if mask.any():
+ nan_index = len(level_strs)
+ # numpy 1.21 deprecated implicit string casting
+ level_strs = level_strs.astype(str)
+ level_strs = np.append(level_strs, na_rep)
+ assert not level_codes.flags.writeable # i.e. copy is needed
+ level_codes = level_codes.copy() # make writeable
+ level_codes[mask] = nan_index
+ new_levels.append(level_strs)
+ new_codes.append(level_codes)
+
+ if len(new_levels) == 1:
+ # a single-level multi-index
+ return Index(
+ new_levels[0].take(new_codes[0]), copy=False
+ )._get_values_for_csv()
+ else:
+ # reconstruct the multi-index
+ mi = MultiIndex(
+ levels=new_levels,
+ codes=new_codes,
+ names=self.names,
+ sortorder=self.sortorder,
+ verify_integrity=False,
+ )
+ return mi._values
+
+ def _format_multi(
+ self,
+ *,
+ include_names: bool,
+ sparsify: bool | None | lib.NoDefault,
+ formatter: Callable | None = None,
+ ) -> list:
+ if len(self) == 0:
+ return []
+
+ stringified_levels = []
+ for lev, level_codes in zip(self.levels, self.codes, strict=True):
+ na = _get_na_rep(lev.dtype)
+
+ if len(lev) > 0:
+ taken = formatted = lev.take(level_codes)
+ formatted = taken._format_flat(include_name=False, formatter=formatter)
+
+ # we have some NA
+ mask = level_codes == -1
+ if mask.any():
+ formatted = np.array(formatted, dtype=object)
+ formatted[mask] = na
+ formatted = formatted.tolist()
+
+ else:
+ # weird all NA case
+ formatted = [
+ pprint_thing(na if isna(x) else x, escape_chars=("\t", "\r", "\n"))
+ for x in algos.take_nd(lev._values, level_codes)
+ ]
+ stringified_levels.append(formatted)
+
+ result_levels = []
+ for lev, lev_name in zip(stringified_levels, self.names, strict=True):
+ level = []
+
+ if include_names:
+ level.append(
+ pprint_thing(lev_name, escape_chars=("\t", "\r", "\n"))
+ if lev_name is not None
+ else ""
+ )
+
+ level.extend(np.array(lev, dtype=object))
+ result_levels.append(level)
+
+ if sparsify is None:
+ sparsify = get_option("display.multi_sparse")
+
+ if sparsify:
+ sentinel: Literal[""] | bool | lib.NoDefault = ""
+ # GH3547 use value of sparsify as sentinel if it's "Falsey"
+ assert isinstance(sparsify, bool) or sparsify is lib.no_default
+ if sparsify is lib.no_default:
+ sentinel = sparsify
+ # little bit of a kludge job for #1217
+ result_levels = sparsify_labels(
+ result_levels, start=int(include_names), sentinel=sentinel
+ )
+
+ return result_levels
+
+ # --------------------------------------------------------------------
+ # Names Methods
+
+ def _get_names(self) -> FrozenList:
+ return FrozenList(self._names)
+
+ def _set_names(self, names, *, level=None) -> None:
+ """
+ Set new names on index. Each name has to be a hashable type.
+
+ Parameters
+ ----------
+ values : str or sequence
+ name(s) to set
+ level : int, level name, or sequence of int/level names (default None)
+ If the index is a MultiIndex (hierarchical), level(s) to set (None
+ for all levels). Otherwise level must be None
+
+ Raises
+ ------
+ TypeError if each name is not hashable.
+
+ Notes
+ -----
+ sets names on levels. WARNING: mutates!
+
+ Note that you generally want to set this *after* changing levels, so
+ that it only acts on copies
+ """
+ # GH 15110
+ # Don't allow a single string for names in a MultiIndex
+ if names is not None and not is_list_like(names):
+ raise ValueError("Names should be list-like for a MultiIndex")
+ names = list(names)
+
+ if level is not None and len(names) != len(level):
+ raise ValueError("Length of names must match length of level.")
+ if level is None and len(names) != self.nlevels:
+ raise ValueError(
+ "Length of names must match number of levels in MultiIndex."
+ )
+
+ if level is None:
+ level = range(self.nlevels)
+ else:
+ level = (self._get_level_number(lev) for lev in level)
+
+ # set the name
+ for lev, name in zip(level, names, strict=True):
+ if name is not None:
+ # GH 20527
+ # All items in 'names' need to be hashable:
+ if not is_hashable(name):
+ raise TypeError(
+ f"{type(self).__name__}.name must be a hashable type"
+ )
+ self._names[lev] = name
+
+ # If .levels has been accessed, the .name of each level in our cache
+ # will be stale.
+ self._reset_cache("levels")
+
+ names = property(
+ fset=_set_names,
+ fget=_get_names,
+ doc="""
+ Names of levels in MultiIndex.
+
+ This attribute provides access to the names of the levels in a `MultiIndex`.
+ The names are stored as a `FrozenList`, which is an immutable list-like
+ container. Each name corresponds to a level in the `MultiIndex`, and can be
+ used to identify or manipulate the levels individually.
+
+ See Also
+ --------
+ MultiIndex.set_names : Set Index or MultiIndex name.
+ MultiIndex.rename : Rename specific levels in a MultiIndex.
+ Index.names : Get names on index.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays(
+ ... [[1, 2], [3, 4], [5, 6]], names=['x', 'y', 'z']
+ ... )
+ >>> mi
+ MultiIndex([(1, 3, 5),
+ (2, 4, 6)],
+ names=['x', 'y', 'z'])
+ >>> mi.names
+ FrozenList(['x', 'y', 'z'])
+ """,
+ )
+
+ # --------------------------------------------------------------------
+
+ @cache_readonly
+ def inferred_type(self) -> str:
+ return "mixed"
+
+ def _get_level_number(self, level) -> int:
+ count = self.names.count(level)
+ if (count > 1) and not is_integer(level):
+ raise ValueError(
+ f"The name {level} occurs multiple times, use a level number"
+ )
+ try:
+ level = self.names.index(level)
+ except ValueError as err:
+ if not is_integer(level):
+ raise KeyError(f"Level {level} not found") from err
+ if level < 0:
+ level += self.nlevels
+ if level < 0:
+ orig_level = level - self.nlevels
+ raise IndexError(
+ f"Too many levels: Index has only {self.nlevels} levels, "
+ f"{orig_level} is not a valid level number"
+ ) from err
+ # Note: levels are zero-based
+ elif level >= self.nlevels:
+ raise IndexError(
+ f"Too many levels: Index has only {self.nlevels} levels, "
+ f"not {level + 1}"
+ ) from err
+ return level
+
+ @cache_readonly
+ def is_monotonic_increasing(self) -> bool:
+ """
+ Return a boolean if the values are equal or increasing.
+ """
+ if any(-1 in code for code in self.codes):
+ return False
+
+ if all(level.is_monotonic_increasing for level in self.levels):
+ # If each level is sorted, we can operate on the codes directly. GH27495
+ return libalgos.is_lexsorted(
+ [x.astype("int64", copy=False) for x in self.codes]
+ )
+
+ # reversed() because lexsort() wants the most significant key last.
+ values = [
+ self._get_level_values(i)._values for i in reversed(range(len(self.levels)))
+ ]
+ try:
+ # error: Argument 1 to "lexsort" has incompatible type
+ # "List[Union[ExtensionArray, ndarray[Any, Any]]]";
+ # expected "Union[_SupportsArray[dtype[Any]],
+ # _NestedSequence[_SupportsArray[dtype[Any]]], bool,
+ # int, float, complex, str, bytes, _NestedSequence[Union
+ # [bool, int, float, complex, str, bytes]]]"
+ sort_order = np.lexsort(values) # type: ignore[arg-type]
+ return Index(sort_order, copy=False).is_monotonic_increasing
+ except TypeError:
+ # we have mixed types and np.lexsort is not happy
+ return Index(self._values, copy=False).is_monotonic_increasing
+
+ @cache_readonly
+ def is_monotonic_decreasing(self) -> bool:
+ """
+ Return a boolean if the values are equal or decreasing.
+ """
+ # monotonic decreasing if and only if reverse is monotonic increasing
+ return self[::-1].is_monotonic_increasing
+
+ def duplicated(self, keep: DropKeep = "first") -> npt.NDArray[np.bool_]:
+ """
+ Indicate duplicate index values.
+
+ Duplicated values are indicated as ``True`` values in the resulting
+ array. Either all duplicates, all except the first, or all except the
+ last occurrence of duplicates can be indicated.
+
+ Parameters
+ ----------
+ keep : {'first', 'last', False}, default 'first'
+ The value or values in a set of duplicates to mark as missing.
+
+ - 'first' : Mark duplicates as ``True`` except for the first
+ occurrence.
+ - 'last' : Mark duplicates as ``True`` except for the last
+ occurrence.
+ - ``False`` : Mark all duplicates as ``True``.
+
+ Returns
+ -------
+ np.ndarray[bool]
+ A numpy array of boolean values indicating duplicate index values.
+
+ See Also
+ --------
+ Series.duplicated : Equivalent method on pandas.Series.
+ DataFrame.duplicated : Equivalent method on pandas.DataFrame.
+ Index.drop_duplicates : Remove duplicate values from Index.
+
+ Examples
+ --------
+ By default, for each set of duplicated values, the first occurrence is
+ set to False and all others to True:
+
+ >>> mi = pd.MultiIndex.from_arrays((list("abca"), list("defd")))
+ >>> mi.duplicated()
+ array([False, False, False, True])
+
+ which is equivalent to
+
+ >>> mi.duplicated(keep="first")
+ array([False, False, False, True])
+
+ By using 'last', the last occurrence of each set of duplicated values
+ is set on False and all others on True:
+
+ >>> mi.duplicated(keep="last")
+ array([ True, False, False, False])
+
+ By setting keep on ``False``, all duplicates are True:
+
+ >>> mi.duplicated(keep=False)
+ array([ True, False, False, True])
+ """
+ shape = tuple(len(lev) for lev in self.levels)
+ ids = get_group_index(self.codes, shape, sort=False, xnull=False)
+
+ return duplicated(ids, keep)
+
+ # error: Cannot override final attribute "_duplicated"
+ # (previously declared in base class "IndexOpsMixin")
+ _duplicated = duplicated # type: ignore[misc]
+
+ def fillna(self, value):
+ """
+ fillna is not implemented for MultiIndex
+ """
+ raise NotImplementedError("fillna is not defined for MultiIndex")
+
+ def dropna(self, how: AnyAll = "any") -> MultiIndex:
+ """
+ Return MultiIndex without NA/NaN values.
+
+ Parameters
+ ----------
+ how : {'any', 'all'}, default 'any'
+ Drop the value when any or all levels are NaN.
+
+ Returns
+ -------
+ Index
+ Returns an MultiIndex object after removing NA/NaN values.
+
+ See Also
+ --------
+ Index.fillna : Fill NA/NaN values with the specified value.
+ Index.isna : Detect missing values.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays(([np.nan, np.nan, 2.0], [3.0, np.nan, 4.0]))
+ >>> mi.dropna()
+ MultiIndex([(2.0, 4.0)],
+ )
+ >>> mi.dropna(how="all")
+ MultiIndex([(nan, 3.0),
+ (2.0, 4.0)],
+ )
+ """
+ nans = [level_codes == -1 for level_codes in self.codes]
+ if how == "any":
+ indexer = np.any(nans, axis=0)
+ elif how == "all":
+ indexer = np.all(nans, axis=0)
+ else:
+ raise ValueError(f"invalid how option: {how}")
+
+ new_codes = [level_codes[~indexer] for level_codes in self.codes]
+ return self.set_codes(codes=new_codes)
+
+ def _get_level_values(self, level: int, unique: bool = False) -> Index:
+ """
+ Return vector of label values for requested level,
+ equal to the length of the index
+
+ **this is an internal method**
+
+ Parameters
+ ----------
+ level : int
+ unique : bool, default False
+ if True, drop duplicated values
+
+ Returns
+ -------
+ Index
+ """
+ lev = self.levels[level]
+ level_codes = self.codes[level]
+ name = self._names[level]
+ if unique:
+ level_codes = algos.unique(level_codes)
+ filled = algos.take_nd(lev._values, level_codes, fill_value=lev._na_value)
+ return lev._shallow_copy(filled, name=name)
+
+ def get_level_values(self, level) -> Index:
+ """
+ Return vector of label values for requested level.
+
+ Length of returned vector is equal to the length of the index.
+ The `get_level_values` method is a crucial utility for extracting
+ specific level values from a `MultiIndex`. This function is particularly
+ useful when working with multi-level data, allowing you to isolate
+ and manipulate individual levels without having to deal with the
+ complexity of the entire `MultiIndex` structure. It seamlessly handles
+ both integer and string-based level access, providing flexibility in
+ how you can interact with the data. Additionally, this method ensures
+ that the returned `Index` maintains the integrity of the original data,
+ even when missing values are present, by appropriately casting the
+ result to a suitable data type.
+
+ Parameters
+ ----------
+ level : int or str
+ ``level`` is either the integer position of the level in the
+ MultiIndex, or the name of the level.
+
+ Returns
+ -------
+ Index
+ Values is a level of this MultiIndex converted to
+ a single :class:`Index` (or subclass thereof).
+
+ See Also
+ --------
+ MultiIndex : A multi-level, or hierarchical, index object for pandas objects.
+ Index : Immutable sequence used for indexing and alignment.
+ MultiIndex.remove_unused_levels : Create new MultiIndex from current that
+ removes unused levels.
+
+ Notes
+ -----
+ If the level contains missing values, the result may be casted to
+ ``float`` with missing values specified as ``NaN``. This is because
+ the level is converted to a regular ``Index``.
+
+ Examples
+ --------
+ Create a MultiIndex:
+
+ >>> mi = pd.MultiIndex.from_arrays((list("abc"), list("def")))
+ >>> mi.names = ["level_1", "level_2"]
+
+ Get level values by supplying level as either integer or name:
+
+ >>> mi.get_level_values(0)
+ Index(['a', 'b', 'c'], dtype='str', name='level_1')
+ >>> mi.get_level_values("level_2")
+ Index(['d', 'e', 'f'], dtype='str', name='level_2')
+
+ If a level contains missing values, the return type of the level
+ may be cast to ``float``.
+
+ >>> pd.MultiIndex.from_arrays([[1, None, 2], [3, 4, 5]]).dtypes
+ level_0 int64
+ level_1 int64
+ dtype: object
+ >>> pd.MultiIndex.from_arrays([[1, None, 2], [3, 4, 5]]).get_level_values(0)
+ Index([1.0, nan, 2.0], dtype='float64')
+ """
+ level = self._get_level_number(level)
+ values = self._get_level_values(level)
+ return values
+
+ def unique(self, level=None):
+ """
+ Return unique values in the index.
+
+ Unique values are returned in order of appearance, this does NOT sort.
+
+ Parameters
+ ----------
+ level : int or hashable, optional
+ Only return values from specified level (for MultiIndex).
+ If int, gets the level by integer position, else by level name.
+
+ Returns
+ -------
+ MultiIndex
+ Unique values in the MultiIndex.
+
+ See Also
+ --------
+ unique : Numpy array of unique values in that column.
+ Series.unique : Return unique values of Series object.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays((list("abca"), list("defd")))
+ >>> mi
+ MultiIndex([('a', 'd'),
+ ('b', 'e'),
+ ('c', 'f'),
+ ('a', 'd')],
+ )
+ >>> mi.unique()
+ MultiIndex([('a', 'd'),
+ ('b', 'e'),
+ ('c', 'f')],
+ )
+ """
+ if level is None:
+ return self.drop_duplicates()
+ else:
+ level = self._get_level_number(level)
+ return self._get_level_values(level=level, unique=True)
+
+ def to_frame(
+ self,
+ index: bool = True,
+ name=lib.no_default,
+ allow_duplicates: bool = False,
+ ) -> DataFrame:
+ """
+ Create a DataFrame with the levels of the MultiIndex as columns.
+
+ Column ordering is determined by the DataFrame constructor with data as
+ a dict.
+
+ Parameters
+ ----------
+ index : bool, default True
+ Set the index of the returned DataFrame as the original MultiIndex.
+
+ name : list / sequence of str, optional
+ The passed names should substitute index level names.
+
+ allow_duplicates : bool, optional default False
+ Allow duplicate column labels to be created.
+
+ Returns
+ -------
+ DataFrame
+ DataFrame representation of the MultiIndex, with levels as columns.
+
+ See Also
+ --------
+ DataFrame : Two-dimensional, size-mutable, potentially heterogeneous
+ tabular data.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([["a", "b"], ["c", "d"]])
+ >>> mi
+ MultiIndex([('a', 'c'),
+ ('b', 'd')],
+ )
+
+ >>> df = mi.to_frame()
+ >>> df
+ 0 1
+ a c a c
+ b d b d
+
+ >>> df = mi.to_frame(index=False)
+ >>> df
+ 0 1
+ 0 a c
+ 1 b d
+
+ >>> df = mi.to_frame(name=["x", "y"])
+ >>> df
+ x y
+ a c a c
+ b d b d
+ """
+ from pandas import DataFrame
+
+ if name is not lib.no_default:
+ if not is_list_like(name):
+ raise TypeError("'name' must be a list / sequence of column names.")
+
+ if len(name) != len(self.levels):
+ raise ValueError(
+ "'name' should have same length as number of levels on index."
+ )
+ idx_names = name
+ else:
+ idx_names = self._get_level_names()
+
+ if not allow_duplicates and len(set(idx_names)) != len(idx_names):
+ raise ValueError(
+ "Cannot create duplicate column labels if allow_duplicates is False"
+ )
+
+ # Guarantee resulting column order - PY36+ dict maintains insertion order
+ result = DataFrame(
+ {level: self._get_level_values(level) for level in range(len(self.levels))},
+ copy=False,
+ )
+ result.columns = idx_names
+
+ if index:
+ result.index = self
+ return result
+
+ # error: Return type "Index" of "to_flat_index" incompatible with return type
+ # "MultiIndex" in supertype "Index"
+ def to_flat_index(self) -> Index: # type: ignore[override]
+ """
+ Convert a MultiIndex to an Index of Tuples containing the level values.
+
+ Returns
+ -------
+ pd.Index
+ Index with the MultiIndex data represented in Tuples.
+
+ See Also
+ --------
+ MultiIndex.from_tuples : Convert flat index back to MultiIndex.
+
+ Notes
+ -----
+ This method will simply return the caller if called by anything other
+ than a MultiIndex.
+
+ Examples
+ --------
+ >>> index = pd.MultiIndex.from_product(
+ ... [["foo", "bar"], ["baz", "qux"]], names=["a", "b"]
+ ... )
+ >>> index.to_flat_index()
+ Index([('foo', 'baz'), ('foo', 'qux'),
+ ('bar', 'baz'), ('bar', 'qux')],
+ dtype='object')
+ """
+ return Index(self._values, tupleize_cols=False, copy=False)
+
+ def _is_lexsorted(self) -> bool:
+ """
+ Return True if the codes are lexicographically sorted.
+
+ Returns
+ -------
+ bool
+
+ Examples
+ --------
+ In the below examples, the first level of the MultiIndex is sorted because
+ a>> pd.MultiIndex.from_arrays(
+ ... [["a", "b", "c"], ["d", "e", "f"]]
+ ... )._is_lexsorted()
+ True
+ >>> pd.MultiIndex.from_arrays(
+ ... [["a", "b", "c"], ["d", "f", "e"]]
+ ... )._is_lexsorted()
+ True
+
+ In case there is a tie, the lexicographical sorting looks
+ at the next level of the MultiIndex.
+
+ >>> pd.MultiIndex.from_arrays([[0, 1, 1], ["a", "b", "c"]])._is_lexsorted()
+ True
+ >>> pd.MultiIndex.from_arrays([[0, 1, 1], ["a", "c", "b"]])._is_lexsorted()
+ False
+ >>> pd.MultiIndex.from_arrays(
+ ... [["a", "a", "b", "b"], ["aa", "bb", "aa", "bb"]]
+ ... )._is_lexsorted()
+ True
+ >>> pd.MultiIndex.from_arrays(
+ ... [["a", "a", "b", "b"], ["bb", "aa", "aa", "bb"]]
+ ... )._is_lexsorted()
+ False
+ """
+ return self._lexsort_depth == self.nlevels
+
+ @cache_readonly
+ def _lexsort_depth(self) -> int:
+ """
+ Compute and return the lexsort_depth, the number of levels of the
+ MultiIndex that are sorted lexically
+
+ Returns
+ -------
+ int
+ """
+ if self.sortorder is not None:
+ return self.sortorder
+ return _lexsort_depth(self.codes, self.nlevels)
+
+ def _sort_levels_monotonic(self, raise_if_incomparable: bool = False) -> MultiIndex:
+ """
+ This is an *internal* function.
+
+ Create a new MultiIndex from the current to monotonically sorted
+ items IN the levels. This does not actually make the entire MultiIndex
+ monotonic, JUST the levels.
+
+ The resulting MultiIndex will have the same outward
+ appearance, meaning the same .values and ordering. It will also
+ be .equals() to the original.
+
+ Returns
+ -------
+ MultiIndex
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex(
+ ... levels=[["a", "b"], ["bb", "aa"]], codes=[[0, 0, 1, 1], [0, 1, 0, 1]]
+ ... )
+ >>> mi
+ MultiIndex([('a', 'bb'),
+ ('a', 'aa'),
+ ('b', 'bb'),
+ ('b', 'aa')],
+ )
+
+ >>> mi.sort_values()
+ MultiIndex([('a', 'aa'),
+ ('a', 'bb'),
+ ('b', 'aa'),
+ ('b', 'bb')],
+ )
+ """
+ if self._is_lexsorted() and self.is_monotonic_increasing:
+ return self
+
+ new_levels = []
+ new_codes = []
+
+ for lev, level_codes in zip(self.levels, self.codes, strict=True):
+ if not lev.is_monotonic_increasing:
+ try:
+ # indexer to reorder the levels
+ indexer = lev.argsort()
+ except TypeError:
+ if raise_if_incomparable:
+ raise
+ else:
+ lev = lev.take(indexer)
+
+ # indexer to reorder the level codes
+ indexer = ensure_platform_int(indexer)
+ ri = lib.get_reverse_indexer(indexer, len(indexer))
+ level_codes = algos.take_nd(ri, level_codes, fill_value=-1)
+
+ new_levels.append(lev)
+ new_codes.append(level_codes)
+
+ return MultiIndex(
+ new_levels,
+ new_codes,
+ names=self.names,
+ sortorder=self.sortorder,
+ verify_integrity=False,
+ )
+
+ def remove_unused_levels(self) -> MultiIndex:
+ """
+ Create new MultiIndex from current that removes unused levels.
+
+ Unused level(s) means levels that are not expressed in the
+ labels. The resulting MultiIndex will have the same outward
+ appearance, meaning the same .values and ordering. It will
+ also be .equals() to the original.
+
+ The `remove_unused_levels` method is useful in cases where you have a
+ MultiIndex with hierarchical levels, but some of these levels are no
+ longer needed due to filtering or subsetting operations. By removing
+ the unused levels, the resulting MultiIndex becomes more compact and
+ efficient, which can improve performance in subsequent operations.
+
+ Returns
+ -------
+ MultiIndex
+ A new MultiIndex with unused levels removed.
+
+ See Also
+ --------
+ MultiIndex.droplevel : Remove specified levels from a MultiIndex.
+ MultiIndex.reorder_levels : Rearrange levels of a MultiIndex.
+ MultiIndex.set_levels : Set new levels on a MultiIndex.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_product([range(2), list("ab")])
+ >>> mi
+ MultiIndex([(0, 'a'),
+ (0, 'b'),
+ (1, 'a'),
+ (1, 'b')],
+ )
+
+ >>> mi[2:]
+ MultiIndex([(1, 'a'),
+ (1, 'b')],
+ )
+
+ The 0 from the first level is not represented
+ and can be removed
+
+ >>> mi2 = mi[2:].remove_unused_levels()
+ >>> mi2.levels
+ FrozenList([[1], ['a', 'b']])
+ """
+ new_levels = []
+ new_codes = []
+
+ changed = False
+ for lev, level_codes in zip(self.levels, self.codes, strict=True):
+ # Since few levels are typically unused, bincount() is more
+ # efficient than unique() - however it only accepts positive values
+ # (and drops order):
+ uniques = np.where(np.bincount(level_codes + 1) > 0)[0] - 1
+ has_na = int(len(uniques) and (uniques[0] == -1))
+
+ if len(uniques) != len(lev) + has_na:
+ if lev.isna().any() and len(uniques) == len(lev):
+ break
+ # We have unused levels
+ changed = True
+
+ # Recalculate uniques, now preserving order.
+ # Can easily be cythonized by exploiting the already existing
+ # "uniques" and stop parsing "level_codes" when all items
+ # are found:
+ uniques = algos.unique(level_codes)
+ if has_na:
+ na_idx = np.where(uniques == -1)[0]
+ # Just ensure that -1 is in first position:
+ uniques[[0, na_idx[0]]] = uniques[[na_idx[0], 0]]
+
+ # codes get mapped from uniques to 0:len(uniques)
+ # -1 (if present) is mapped to last position
+ code_mapping = np.zeros(len(lev) + has_na)
+ # ... and reassigned value -1:
+ code_mapping[uniques] = np.arange(len(uniques)) - has_na
+
+ level_codes = code_mapping[level_codes]
+
+ # new levels are simple
+ lev = lev.take(uniques[has_na:])
+
+ new_levels.append(lev)
+ new_codes.append(level_codes)
+
+ result = self.view()
+
+ if changed:
+ result._reset_identity()
+ result._set_levels(new_levels, validate=False)
+ result._set_codes(new_codes, validate=False)
+
+ return result
+
+ # --------------------------------------------------------------------
+ # Pickling Methods
+
+ def __reduce__(self):
+ """Necessary for making this object picklable"""
+ d = {
+ "levels": list(self.levels),
+ "codes": list(self.codes),
+ "sortorder": self.sortorder,
+ "names": list(self.names),
+ }
+ return ibase._new_Index, (type(self), d), None
+
+ # --------------------------------------------------------------------
+
+ def __getitem__(self, key):
+ key = lib.item_from_zerodim(key)
+ if is_scalar(key):
+ key = com.cast_scalar_indexer(key)
+
+ retval = []
+ for lev, level_codes in zip(self.levels, self.codes, strict=True):
+ if level_codes[key] == -1:
+ retval.append(np.nan)
+ else:
+ retval.append(lev[level_codes[key]])
+
+ return tuple(retval)
+ else:
+ # in general cannot be sure whether the result will be sorted
+ sortorder = None
+ if com.is_bool_indexer(key):
+ key = np.asarray(key, dtype=bool)
+ sortorder = self.sortorder
+ elif isinstance(key, slice):
+ if key.step is None or key.step > 0:
+ sortorder = self.sortorder
+ elif isinstance(key, Index):
+ key = np.asarray(key)
+
+ new_codes = [level_codes[key] for level_codes in self.codes]
+
+ return MultiIndex(
+ levels=self.levels,
+ codes=new_codes,
+ names=self.names,
+ sortorder=sortorder,
+ verify_integrity=False,
+ )
+
+ def _getitem_slice(self: MultiIndex, slobj: slice) -> MultiIndex:
+ """
+ Fastpath for __getitem__ when we know we have a slice.
+ """
+ sortorder = None
+ if slobj.step is None or slobj.step > 0:
+ sortorder = self.sortorder
+
+ new_codes = [level_codes[slobj] for level_codes in self.codes]
+
+ return type(self)(
+ levels=self.levels,
+ codes=new_codes,
+ names=self._names,
+ sortorder=sortorder,
+ verify_integrity=False,
+ )
+
+ def take(
+ self: MultiIndex,
+ indices,
+ axis: Axis = 0,
+ allow_fill: bool = True,
+ fill_value=None,
+ **kwargs,
+ ) -> MultiIndex:
+ """
+ Return a new MultiIndex of the values selected by the indices.
+
+ For internal compatibility with numpy arrays.
+
+ Parameters
+ ----------
+ indices : array-like
+ Indices to be taken.
+ axis : {0 or 'index'}, optional
+ The axis over which to select values, always 0 or 'index'.
+ allow_fill : bool, default True
+ How to handle negative values in `indices`.
+
+ * False: negative values in `indices` indicate positional indices
+ from the right (the default). This is similar to
+ :func:`numpy.take`.
+
+ * True: negative values in `indices` indicate
+ missing values. These values are set to `fill_value`. Any other
+ other negative values raise a ``ValueError``.
+
+ fill_value : scalar, default None
+ If allow_fill=True and fill_value is not None, indices specified by
+ -1 are regarded as NA. If Index doesn't hold NA, raise ValueError.
+ **kwargs
+ Required for compatibility with numpy.
+
+ Returns
+ -------
+ Index
+ An index formed of elements at the given indices. Will be the same
+ type as self, except for RangeIndex.
+
+ See Also
+ --------
+ numpy.ndarray.take: Return an array formed from the
+ elements of a at the given indices.
+
+ Examples
+ --------
+ >>> idx = pd.MultiIndex.from_arrays([["a", "b", "c"], [1, 2, 3]])
+ >>> idx
+ MultiIndex([('a', 1),
+ ('b', 2),
+ ('c', 3)],
+ )
+ >>> idx.take([2, 2, 1, 0])
+ MultiIndex([('c', 3),
+ ('c', 3),
+ ('b', 2),
+ ('a', 1)],
+ )
+ """
+ nv.validate_take((), kwargs)
+ indices = ensure_platform_int(indices)
+
+ # only fill if we are passing a non-None fill_value
+ allow_fill = self._maybe_disallow_fill(allow_fill, fill_value, indices)
+
+ if indices.ndim == 1 and lib.is_range_indexer(indices, len(self)):
+ return self.copy()
+
+ na_value = -1
+
+ taken = [lab.take(indices) for lab in self.codes]
+ if allow_fill:
+ mask = indices == -1
+ if mask.any():
+ masked = []
+ for new_label in taken:
+ label_values = new_label
+ label_values[mask] = na_value
+ masked.append(np.asarray(label_values))
+ taken = masked
+
+ return MultiIndex(
+ levels=self.levels, codes=taken, names=self.names, verify_integrity=False
+ )
+
+ def append(self, other):
+ """
+ Append a collection of Index options together.
+
+ The `append` method is used to combine multiple `Index` objects into a single
+ `Index`. This is particularly useful when dealing with multi-level indexing
+ (MultiIndex) where you might need to concatenate different levels of indices.
+ The method handles the alignment of the levels and codes of the indices being
+ appended to ensure consistency in the resulting `MultiIndex`.
+
+ Parameters
+ ----------
+ other : Index or list/tuple of indices
+ Index or list/tuple of Index objects to be appended.
+
+ Returns
+ -------
+ Index
+ The combined index.
+
+ See Also
+ --------
+ MultiIndex: A multi-level, or hierarchical, index object for pandas objects.
+ Index.append : Append a collection of Index options together.
+ concat : Concatenate pandas objects along a particular axis.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([["a"], ["b"]])
+ >>> mi
+ MultiIndex([('a', 'b')],
+ )
+ >>> mi.append(mi)
+ MultiIndex([('a', 'b'), ('a', 'b')],
+ )
+ """
+ if not isinstance(other, (list, tuple)):
+ other = [other]
+
+ if all(
+ (isinstance(o, MultiIndex) and o.nlevels >= self.nlevels) for o in other
+ ):
+ codes = []
+ levels = []
+ names = []
+ for i in range(self.nlevels):
+ level_values = self.levels[i]
+ for mi in other:
+ level_values = level_values.union(mi.levels[i])
+ level_codes = [
+ recode_for_categories(
+ mi.codes[i], mi.levels[i], level_values, copy=False
+ )
+ for mi in ([self, *other])
+ ]
+ level_name = self.names[i]
+ if any(mi.names[i] != level_name for mi in other):
+ level_name = None
+ codes.append(np.concatenate(level_codes))
+ levels.append(level_values)
+ names.append(level_name)
+ return MultiIndex(
+ codes=codes, levels=levels, names=names, verify_integrity=False
+ )
+
+ to_concat = (self._values, *tuple(k._values for k in other))
+ new_tuples = np.concatenate(to_concat)
+
+ # if all(isinstance(x, MultiIndex) for x in other):
+ try:
+ # We only get here if other contains at least one index with tuples,
+ # setting names to None automatically
+ return MultiIndex.from_tuples(new_tuples)
+ except (TypeError, IndexError):
+ return Index(new_tuples, copy=False)
+
+ def argsort(
+ self, *args, na_position: NaPosition = "last", **kwargs
+ ) -> npt.NDArray[np.intp]:
+ """
+ Return the integer indices that would sort the index.
+
+ Parameters
+ ----------
+ *args
+ Passed to `numpy.ndarray.argsort`.
+ na_position : {'first' or 'last'}, default 'last'
+ Argument 'first' puts NaNs at the beginning, 'last' puts NaNs at
+ the end.
+ **kwargs
+ Passed to `numpy.ndarray.argsort`.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ Integer indices that would sort the index if used as
+ an indexer.
+
+ See Also
+ --------
+ numpy.argsort : Similar method for NumPy arrays.
+ Index.argsort : Similar method for Index.
+
+ Examples
+ --------
+ >>> midx = pd.MultiIndex.from_arrays([[3, 2], ["e", "c"]])
+ >>> midx
+ MultiIndex([(3, 'e'),
+ (2, 'c')],
+ )
+
+ >>> order = midx.argsort()
+ >>> order
+ array([1, 0])
+
+ >>> midx[order]
+ MultiIndex([(2, 'c'),
+ (3, 'e')],
+ )
+
+ >>> midx = pd.MultiIndex.from_arrays([[2, 2], [np.nan, 0]])
+ >>> midx.argsort(na_position="first")
+ array([0, 1])
+
+ >>> midx.argsort()
+ array([1, 0])
+ """
+ target = self._sort_levels_monotonic(raise_if_incomparable=True)
+ keys = [lev.codes for lev in target._get_codes_for_sorting()]
+ return lexsort_indexer(keys, na_position=na_position, codes_given=True)
+
+ def repeat(self, repeats: int, axis=None) -> MultiIndex:
+ """
+ Repeat elements of a MultiIndex.
+
+ Returns a new MultiIndex where each element of the current MultiIndex
+ is repeated consecutively a given number of times.
+
+ Parameters
+ ----------
+ repeats : int or array of ints
+ The number of repetitions for each element. This should be a
+ non-negative integer. Repeating 0 times will return an empty
+ MultiIndex.
+ axis : None
+ Must be ``None``. Has no effect but is accepted for compatibility
+ with numpy.
+
+ Returns
+ -------
+ MultiIndex
+ Newly created MultiIndex with repeated elements.
+
+ See Also
+ --------
+ Series.repeat : Equivalent function for Series.
+ numpy.repeat : Similar method for :class:`numpy.ndarray`.
+
+ Examples
+ --------
+ >>> idx = pd.MultiIndex.from_arrays([["a", "b", "c"], [1, 2, 3]])
+ >>> idx
+ MultiIndex([('a', 1),
+ ('b', 2),
+ ('c', 3)],
+ )
+ >>> idx.repeat(2)
+ MultiIndex([('a', 1),
+ ('a', 1),
+ ('b', 2),
+ ('b', 2),
+ ('c', 3),
+ ('c', 3)],
+ )
+ >>> idx.repeat([1, 2, 3])
+ MultiIndex([('a', 1),
+ ('b', 2),
+ ('b', 2),
+ ('c', 3),
+ ('c', 3),
+ ('c', 3)],
+ )
+ """
+ nv.validate_repeat((), {"axis": axis})
+ # error: Incompatible types in assignment (expression has type "ndarray",
+ # variable has type "int")
+ repeats = ensure_platform_int(repeats) # type: ignore[assignment]
+ return MultiIndex(
+ levels=self.levels,
+ codes=[
+ level_codes.view(np.ndarray).astype(np.intp, copy=False).repeat(repeats)
+ for level_codes in self.codes
+ ],
+ names=self.names,
+ sortorder=self.sortorder,
+ verify_integrity=False,
+ )
+
+ # error: Signature of "drop" incompatible with supertype "Index"
+ def drop( # type: ignore[override]
+ self,
+ codes,
+ level: Index | np.ndarray | Iterable[Hashable] | None = None,
+ errors: IgnoreRaise = "raise",
+ ) -> MultiIndex:
+ """
+ Make a new :class:`pandas.MultiIndex` with the passed list of codes deleted.
+
+ This method allows for the removal of specified labels from a MultiIndex.
+ The labels to be removed can be provided as a list of tuples if no level
+ is specified, or as a list of labels from a specific level if the level
+ parameter is provided. This can be useful for refining the structure of a
+ MultiIndex to fit specific requirements.
+
+ Parameters
+ ----------
+ codes : array-like
+ Must be a list of tuples when ``level`` is not specified.
+ level : int or level name, default None
+ Level from which the labels will be dropped.
+ errors : str, default 'raise'
+ If 'ignore', suppress error and existing labels are dropped.
+
+ Returns
+ -------
+ MultiIndex
+ A new MultiIndex with the specified labels removed.
+
+ See Also
+ --------
+ MultiIndex.remove_unused_levels : Create new MultiIndex from current that
+ removes unused levels.
+ MultiIndex.reorder_levels : Rearrange levels using input order.
+ MultiIndex.rename : Rename levels in a MultiIndex.
+
+ Examples
+ --------
+ >>> idx = pd.MultiIndex.from_product(
+ ... [(0, 1, 2), ("green", "purple")], names=["number", "color"]
+ ... )
+ >>> idx
+ MultiIndex([(0, 'green'),
+ (0, 'purple'),
+ (1, 'green'),
+ (1, 'purple'),
+ (2, 'green'),
+ (2, 'purple')],
+ names=['number', 'color'])
+ >>> idx.drop([(1, "green"), (2, "purple")])
+ MultiIndex([(0, 'green'),
+ (0, 'purple'),
+ (1, 'purple'),
+ (2, 'green')],
+ names=['number', 'color'])
+
+ We can also drop from a specific level.
+
+ >>> idx.drop("green", level="color")
+ MultiIndex([(0, 'purple'),
+ (1, 'purple'),
+ (2, 'purple')],
+ names=['number', 'color'])
+
+ >>> idx.drop([1, 2], level=0)
+ MultiIndex([(0, 'green'),
+ (0, 'purple')],
+ names=['number', 'color'])
+ """
+ if level is not None:
+ return self._drop_from_level(codes, level, errors)
+
+ if not isinstance(codes, (np.ndarray, Index)):
+ try:
+ codes = com.index_labels_to_array(codes, dtype=np.dtype("object"))
+ except ValueError:
+ pass
+
+ inds = []
+ for level_codes in codes:
+ try:
+ loc = self.get_loc(level_codes)
+ # get_loc returns either an integer, a slice, or a boolean
+ # mask
+ if isinstance(loc, int):
+ inds.append(loc)
+ elif isinstance(loc, slice):
+ step = loc.step if loc.step is not None else 1
+ inds.extend(range(loc.start, loc.stop, step))
+ elif com.is_bool_indexer(loc):
+ if get_option("performance_warnings") and self._lexsort_depth == 0:
+ warnings.warn(
+ "dropping on a non-lexsorted multi-index "
+ "without a level parameter may impact performance.",
+ PerformanceWarning,
+ stacklevel=find_stack_level(),
+ )
+ loc = loc.nonzero()[0]
+ inds.extend(loc)
+ else:
+ msg = f"unsupported indexer of type {type(loc)}"
+ raise AssertionError(msg)
+ except KeyError:
+ if errors != "ignore":
+ raise
+
+ return self.delete(inds)
+
+ def _drop_from_level(
+ self, codes, level, errors: IgnoreRaise = "raise"
+ ) -> MultiIndex:
+ codes = com.index_labels_to_array(codes)
+ i = self._get_level_number(level)
+ index = self.levels[i]
+ values = index.get_indexer(codes)
+ # If nan should be dropped it will equal -1 here. We have to check which values
+ # are not nan and equal -1, this means they are missing in the index
+ nan_codes = isna(codes)
+ values[(np.equal(nan_codes, False)) & (values == -1)] = -2
+ if index.shape[0] == self.shape[0]:
+ values[np.equal(nan_codes, True)] = -2
+
+ not_found = codes[values == -2]
+ if len(not_found) != 0 and errors != "ignore":
+ raise KeyError(f"labels {not_found} not found in level")
+ mask = ~algos.isin(self.codes[i], values)
+
+ return self[mask]
+
+ def swaplevel(self, i=-2, j=-1) -> MultiIndex:
+ """
+ Swap level i with level j.
+
+ Calling this method does not change the ordering of the values.
+
+ Default is to swap the last two levels of the MultiIndex.
+
+ Parameters
+ ----------
+ i : int, str, default -2
+ First level of index to be swapped. Can pass level name as string.
+ Type of parameters can be mixed. If i is a negative int, the first
+ level is indexed relative to the end of the MultiIndex.
+ j : int, str, default -1
+ Second level of index to be swapped. Can pass level name as string.
+ Type of parameters can be mixed. If j is a negative int, the second
+ level is indexed relative to the end of the MultiIndex.
+
+ Returns
+ -------
+ MultiIndex
+ A new MultiIndex.
+
+ See Also
+ --------
+ Series.swaplevel : Swap levels i and j in a MultiIndex.
+ DataFrame.swaplevel : Swap levels i and j in a MultiIndex on a
+ particular axis.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex(
+ ... levels=[["a", "b"], ["bb", "aa"], ["aaa", "bbb"]],
+ ... codes=[[0, 0, 1, 1], [0, 1, 0, 1], [1, 0, 1, 0]],
+ ... )
+ >>> mi
+ MultiIndex([('a', 'bb', 'bbb'),
+ ('a', 'aa', 'aaa'),
+ ('b', 'bb', 'bbb'),
+ ('b', 'aa', 'aaa')],
+ )
+ >>> mi.swaplevel()
+ MultiIndex([('a', 'bbb', 'bb'),
+ ('a', 'aaa', 'aa'),
+ ('b', 'bbb', 'bb'),
+ ('b', 'aaa', 'aa')],
+ )
+ >>> mi.swaplevel(0)
+ MultiIndex([('bbb', 'bb', 'a'),
+ ('aaa', 'aa', 'a'),
+ ('bbb', 'bb', 'b'),
+ ('aaa', 'aa', 'b')],
+ )
+ >>> mi.swaplevel(0, 1)
+ MultiIndex([('bb', 'a', 'bbb'),
+ ('aa', 'a', 'aaa'),
+ ('bb', 'b', 'bbb'),
+ ('aa', 'b', 'aaa')],
+ )
+ """
+ new_levels = list(self.levels)
+ new_codes = list(self.codes)
+ new_names = list(self.names)
+
+ i = self._get_level_number(i)
+ j = self._get_level_number(j)
+
+ new_levels[i], new_levels[j] = new_levels[j], new_levels[i]
+ new_codes[i], new_codes[j] = new_codes[j], new_codes[i]
+ new_names[i], new_names[j] = new_names[j], new_names[i]
+
+ return MultiIndex(
+ levels=new_levels, codes=new_codes, names=new_names, verify_integrity=False
+ )
+
+ def reorder_levels(self, order) -> MultiIndex:
+ """
+ Rearrange levels using input order. May not drop or duplicate levels.
+
+ `reorder_levels` is useful when you need to change the order of levels in
+ a MultiIndex, such as when reordering levels for hierarchical indexing. It
+ maintains the integrity of the MultiIndex, ensuring that all existing levels
+ are present and no levels are duplicated. This method is helpful for aligning
+ the index structure with other data structures or for optimizing the order
+ for specific data operations.
+
+ Parameters
+ ----------
+ order : list of int or list of str
+ List representing new level order. Reference level by number
+ (position) or by key (label).
+
+ Returns
+ -------
+ MultiIndex
+ A new MultiIndex with levels rearranged according to the specified order.
+
+ See Also
+ --------
+ MultiIndex.swaplevel : Swap two levels of the MultiIndex.
+ MultiIndex.set_names : Set names for the MultiIndex levels.
+ DataFrame.reorder_levels : Reorder levels in a DataFrame with a MultiIndex.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([[1, 2], [3, 4]], names=["x", "y"])
+ >>> mi
+ MultiIndex([(1, 3),
+ (2, 4)],
+ names=['x', 'y'])
+
+ >>> mi.reorder_levels(order=[1, 0])
+ MultiIndex([(3, 1),
+ (4, 2)],
+ names=['y', 'x'])
+
+ >>> mi.reorder_levels(order=["y", "x"])
+ MultiIndex([(3, 1),
+ (4, 2)],
+ names=['y', 'x'])
+ """
+ order = [self._get_level_number(i) for i in order]
+ result = self._reorder_ilevels(order)
+ return result
+
+ def _reorder_ilevels(self, order) -> MultiIndex:
+ if len(order) != self.nlevels:
+ raise AssertionError(
+ f"Length of order must be same as number of levels ({self.nlevels}), "
+ f"got {len(order)}"
+ )
+ new_levels = [self.levels[i] for i in order]
+ new_codes = [self.codes[i] for i in order]
+ new_names = [self.names[i] for i in order]
+
+ return MultiIndex(
+ levels=new_levels, codes=new_codes, names=new_names, verify_integrity=False
+ )
+
+ def _recode_for_new_levels(
+ self, new_levels, *, copy: bool
+ ) -> Generator[np.ndarray]:
+ if len(new_levels) > self.nlevels:
+ raise AssertionError(
+ f"Length of new_levels ({len(new_levels)}) "
+ f"must be <= self.nlevels ({self.nlevels})"
+ )
+ for i in range(len(new_levels)):
+ yield recode_for_categories(
+ self.codes[i], self.levels[i], new_levels[i], copy=copy
+ )
+
+ def _get_codes_for_sorting(self) -> list[Categorical]:
+ """
+ we are categorizing our codes by using the
+ available categories (all, not just observed)
+ excluding any missing ones (-1); this is in preparation
+ for sorting, where we need to disambiguate that -1 is not
+ a valid valid
+ """
+
+ def cats(level_codes: np.ndarray) -> np.ndarray:
+ return np.arange(
+ level_codes.max() + 1 if len(level_codes) else 0,
+ dtype=level_codes.dtype,
+ )
+
+ return [
+ Categorical.from_codes(level_codes, cats(level_codes), True, validate=False)
+ for level_codes in self.codes
+ ]
+
+ def sortlevel(
+ self,
+ level: IndexLabel = 0,
+ ascending: bool | list[bool] = True,
+ sort_remaining: bool = True,
+ na_position: str = "first",
+ ) -> tuple[MultiIndex, npt.NDArray[np.intp]]:
+ """
+ Sort MultiIndex at the requested level.
+
+ This method is useful when dealing with MultiIndex objects, allowing for
+ sorting at a specific level of the index. The function preserves the
+ relative ordering of data within the same level while sorting
+ the overall MultiIndex. The method provides flexibility with the `ascending`
+ parameter to define the sort order and with the `sort_remaining` parameter to
+ control whether the remaining levels should also be sorted. Sorting a
+ MultiIndex can be crucial when performing operations that require ordered
+ indices, such as grouping or merging datasets. The `na_position` argument is
+ important in handling missing values consistently across different levels.
+
+ Parameters
+ ----------
+ level : list-like, int or str, default 0
+ If a string is given, must be a name of the level.
+ If list-like must be names or ints of levels.
+ ascending : bool, default True
+ False to sort in descending order.
+ Can also be a list to specify a directed ordering.
+ sort_remaining : bool, default True
+ If True, sorts by the remaining levels after sorting by the specified
+ `level`.
+ na_position : {'first' or 'last'}, default 'first'
+ Argument 'first' puts NaNs at the beginning, 'last' puts NaNs at
+ the end.
+
+ .. versionadded:: 2.1.0
+
+ Returns
+ -------
+ sorted_index : pd.MultiIndex
+ Resulting index.
+ indexer : np.ndarray[np.intp]
+ Indices of output values in original index.
+
+ See Also
+ --------
+ MultiIndex : A multi-level, or hierarchical, index object for pandas objects.
+ Index.sort_values : Sort Index values.
+ DataFrame.sort_index : Sort DataFrame by the index.
+ Series.sort_index : Sort Series by the index.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([[0, 0], [2, 1]])
+ >>> mi
+ MultiIndex([(0, 2),
+ (0, 1)],
+ )
+
+ >>> mi.sortlevel()
+ (MultiIndex([(0, 1),
+ (0, 2)],
+ ), array([1, 0]))
+
+ >>> mi.sortlevel(sort_remaining=False)
+ (MultiIndex([(0, 2),
+ (0, 1)],
+ ), array([0, 1]))
+
+ >>> mi.sortlevel(1)
+ (MultiIndex([(0, 1),
+ (0, 2)],
+ ), array([1, 0]))
+
+ >>> mi.sortlevel(1, ascending=False)
+ (MultiIndex([(0, 2),
+ (0, 1)],
+ ), array([0, 1]))
+ """
+ if not is_list_like(level):
+ level = [level]
+ # error: Item "Hashable" of "Union[Hashable, Sequence[Hashable]]" has
+ # no attribute "__iter__" (not iterable)
+ level = [
+ self._get_level_number(lev)
+ for lev in level # type: ignore[union-attr]
+ ]
+ sortorder = None
+
+ codes = [self.codes[lev] for lev in level]
+ # we have a directed ordering via ascending
+ if isinstance(ascending, list):
+ if not len(level) == len(ascending):
+ raise ValueError("level must have same length as ascending")
+ elif sort_remaining:
+ codes.extend(
+ [self.codes[lev] for lev in range(len(self.levels)) if lev not in level]
+ )
+ else:
+ sortorder = level[0]
+
+ indexer = lexsort_indexer(
+ codes, orders=ascending, na_position=na_position, codes_given=True
+ )
+
+ indexer = ensure_platform_int(indexer)
+ new_codes = [level_codes.take(indexer) for level_codes in self.codes]
+
+ new_index = MultiIndex(
+ codes=new_codes,
+ levels=self.levels,
+ names=self.names,
+ sortorder=sortorder,
+ verify_integrity=False,
+ )
+
+ return new_index, indexer
+
+ def _wrap_reindex_result(self, target, indexer, preserve_names: bool):
+ if not isinstance(target, MultiIndex):
+ if indexer is None:
+ target = self
+ elif (indexer >= 0).all():
+ target = self.take(indexer)
+ else:
+ try:
+ target = MultiIndex.from_tuples(target)
+ except TypeError:
+ # not all tuples, see test_constructor_dict_multiindex_reindex_flat
+ return target
+
+ target = self._maybe_preserve_names(target, preserve_names)
+ return target
+
+ def _maybe_preserve_names(self, target: IndexT, preserve_names: bool) -> IndexT:
+ if (
+ preserve_names
+ and target.nlevels == self.nlevels
+ and target.names != self.names
+ ):
+ target = target.copy(deep=False)
+ target.names = self.names
+ return target
+
+ # --------------------------------------------------------------------
+ # Indexing Methods
+
+ def _check_indexing_error(self, key) -> None:
+ if not is_hashable(key) or is_iterator(key):
+ # We allow tuples if they are hashable, whereas other Index
+ # subclasses require scalar.
+ # We have to explicitly exclude generators, as these are hashable.
+ raise InvalidIndexError(key)
+
+ @cache_readonly
+ def _should_fallback_to_positional(self) -> bool:
+ """
+ Should integer key(s) be treated as positional?
+ """
+ # GH#33355
+ return self.levels[0]._should_fallback_to_positional
+
+ def _get_indexer_strict(
+ self, key, axis_name: str
+ ) -> tuple[Index, npt.NDArray[np.intp]]:
+ keyarr = key
+ if not isinstance(keyarr, Index):
+ keyarr = com.asarray_tuplesafe(keyarr)
+
+ if len(keyarr) and not isinstance(keyarr[0], tuple):
+ indexer = self._get_indexer_level_0(keyarr)
+
+ self._raise_if_missing(key, indexer, axis_name)
+ return self[indexer], indexer
+
+ return super()._get_indexer_strict(key, axis_name)
+
+ def _raise_if_missing(self, key, indexer, axis_name: str) -> None:
+ keyarr = key
+ if not isinstance(key, Index):
+ keyarr = com.asarray_tuplesafe(key)
+
+ if len(keyarr) and not isinstance(keyarr[0], tuple):
+ # i.e. same condition for special case in MultiIndex._get_indexer_strict
+
+ mask = indexer == -1
+ if mask.any():
+ check = self.levels[0].get_indexer(keyarr)
+ cmask = check == -1
+ if cmask.any():
+ raise KeyError(f"{keyarr[cmask]} not in index")
+ # We get here when levels still contain values which are not
+ # actually in Index anymore
+ raise KeyError(f"{keyarr} not in index")
+ else:
+ return super()._raise_if_missing(key, indexer, axis_name)
+
+ def _get_indexer_level_0(self, target) -> npt.NDArray[np.intp]:
+ """
+ Optimized equivalent to `self.get_level_values(0).get_indexer_for(target)`.
+ """
+ lev = self.levels[0]
+ codes = self._codes[0]
+ cat = Categorical.from_codes(codes=codes, categories=lev, validate=False)
+ ci = Index(cat, copy=False)
+ return ci.get_indexer_for(target)
+
+ def get_slice_bound(
+ self,
+ label: Hashable | Sequence[Hashable],
+ side: Literal["left", "right"],
+ ) -> int:
+ """
+ For an ordered MultiIndex, compute slice bound
+ that corresponds to given label.
+
+ Returns leftmost (one-past-the-rightmost if `side=='right') position
+ of given label.
+
+ Parameters
+ ----------
+ label : object or tuple of objects
+ side : {'left', 'right'}
+
+ Returns
+ -------
+ int
+ Index of label.
+
+ Notes
+ -----
+ This method only works if level 0 index of the MultiIndex is lexsorted.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([list("abbc"), list("gefd")])
+
+ Get the locations from the leftmost 'b' in the first level
+ until the end of the multiindex:
+
+ >>> mi.get_slice_bound("b", side="left")
+ 1
+
+ Like above, but if you get the locations from the rightmost
+ 'b' in the first level and 'f' in the second level:
+
+ >>> mi.get_slice_bound(("b", "f"), side="right")
+ 3
+
+ See Also
+ --------
+ MultiIndex.get_loc : Get location for a label or a tuple of labels.
+ MultiIndex.get_locs : Get location for a label/slice/list/mask or a
+ sequence of such.
+ """
+ if not isinstance(label, tuple):
+ label = (label,)
+ result = self._partial_tup_index(label, side=side)
+ result = maybe_unbox_numpy_scalar(result)
+ return result
+
+ def slice_locs(self, start=None, end=None, step=None) -> tuple[int, int]:
+ """
+ For an ordered MultiIndex, compute the slice locations for input
+ labels.
+
+ The input labels can be tuples representing partial levels, e.g. for a
+ MultiIndex with 3 levels, you can pass a single value (corresponding to
+ the first level), or a 1-, 2-, or 3-tuple.
+
+ Parameters
+ ----------
+ start : label or tuple, default None
+ If None, defaults to the beginning
+ end : label or tuple
+ If None, defaults to the end
+ step : int or None
+ Slice step
+
+ Returns
+ -------
+ (start, end) : (int, int)
+
+ Notes
+ -----
+ This method only works if the MultiIndex is properly lexsorted. So,
+ if only the first 2 levels of a 3-level MultiIndex are lexsorted,
+ you can only pass two levels to ``.slice_locs``.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays(
+ ... [list("abbd"), list("deff")], names=["A", "B"]
+ ... )
+
+ Get the slice locations from the beginning of 'b' in the first level
+ until the end of the multiindex:
+
+ >>> mi.slice_locs(start="b")
+ (1, 4)
+
+ Like above, but stop at the end of 'b' in the first level and 'f' in
+ the second level:
+
+ >>> mi.slice_locs(start="b", end=("b", "f"))
+ (1, 3)
+
+ See Also
+ --------
+ MultiIndex.get_loc : Get location for a label or a tuple of labels.
+ MultiIndex.get_locs : Get location for a label/slice/list/mask or a
+ sequence of such.
+ """
+ # This function adds nothing to its parent implementation (the magic
+ # happens in get_slice_bound method), but it adds meaningful doc.
+ return super().slice_locs(start, end, step)
+
+ def _partial_tup_index(self, tup: tuple, side: Literal["left", "right"] = "left"):
+ if len(tup) > self._lexsort_depth:
+ raise UnsortedIndexError(
+ f"Key length ({len(tup)}) was greater than MultiIndex lexsort depth "
+ f"({self._lexsort_depth})"
+ )
+
+ n = len(tup)
+ start, end = 0, len(self)
+ zipped = zip(tup, self.levels, self.codes, strict=True)
+ for k, (lab, lev, level_codes) in enumerate(zipped):
+ section = level_codes[start:end]
+
+ loc: npt.NDArray[np.intp] | np.intp | int
+ if lab not in lev and not isna(lab):
+ # short circuit
+ try:
+ loc = algos.searchsorted(lev, lab, side=side)
+ except TypeError as err:
+ # non-comparable e.g. test_slice_locs_with_type_mismatch
+ raise TypeError(f"Level type mismatch: {lab}") from err
+ if not is_integer(loc):
+ # non-comparable level, e.g. test_groupby_example
+ raise TypeError(f"Level type mismatch: {lab}")
+ if side == "right" and loc >= 0:
+ loc -= 1
+ return start + algos.searchsorted(section, loc, side=side)
+
+ idx = self._get_loc_single_level_index(lev, lab)
+ if isinstance(idx, slice) and k < n - 1:
+ # Get start and end value from slice, necessary when a non-integer
+ # interval is given as input GH#37707
+ start = idx.start
+ end = idx.stop
+ elif k < n - 1:
+ # error: Incompatible types in assignment (expression has type
+ # "Union[ndarray[Any, dtype[signedinteger[Any]]]
+ end = start + algos.searchsorted( # type: ignore[assignment]
+ section, idx, side="right"
+ )
+ # error: Incompatible types in assignment (expression has type
+ # "Union[ndarray[Any, dtype[signedinteger[Any]]]
+ start = start + algos.searchsorted( # type: ignore[assignment]
+ section, idx, side="left"
+ )
+ elif isinstance(idx, slice):
+ idx = idx.start
+ return start + algos.searchsorted(section, idx, side=side)
+ else:
+ return start + algos.searchsorted(section, idx, side=side)
+
+ def _get_loc_single_level_index(self, level_index: Index, key: Hashable) -> int:
+ """
+ If key is NA value, location of index unify as -1.
+
+ Parameters
+ ----------
+ level_index: Index
+ key : label
+
+ Returns
+ -------
+ loc : int
+ If key is NA value, loc is -1
+ Else, location of key in index.
+
+ See Also
+ --------
+ Index.get_loc : The get_loc method for (single-level) index.
+ """
+ if is_scalar(key) and isna(key):
+ # TODO: need is_valid_na_for_dtype(key, level_index.dtype)
+ return -1
+ else:
+ return level_index.get_loc(key)
+
+ def get_loc(self, key):
+ """
+ Get location for a label or a tuple of labels. The location is returned \
+ as an integer/slice or boolean mask.
+
+ This method returns the integer location, slice object, or boolean mask
+ corresponding to the specified key, which can be a single label or a tuple
+ of labels. The key represents a position in the MultiIndex, and the location
+ indicates where the key is found within the index.
+
+ Parameters
+ ----------
+ key : label or tuple of labels (one for each level)
+ A label or tuple of labels that correspond to the levels of the MultiIndex.
+ The key must match the structure of the MultiIndex.
+
+ Returns
+ -------
+ int, slice object or boolean mask
+ If the key is past the lexsort depth, the return may be a
+ boolean mask array, otherwise it is always a slice or int.
+
+ See Also
+ --------
+ Index.get_loc : The get_loc method for (single-level) index.
+ MultiIndex.slice_locs : Get slice location given start label(s) and
+ end label(s).
+ MultiIndex.get_locs : Get location for a label/slice/list/mask or a
+ sequence of such.
+
+ Notes
+ -----
+ The key cannot be a slice, list of same-level labels, a boolean mask,
+ or a sequence of such. If you want to use those, use
+ :meth:`MultiIndex.get_locs` instead.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([list("abb"), list("def")])
+
+ >>> mi.get_loc("b")
+ slice(1, 3, None)
+
+ >>> mi.get_loc(("b", "e"))
+ 1
+ """
+ self._check_indexing_error(key)
+
+ def _maybe_to_slice(loc):
+ """convert integer indexer to boolean mask or slice if possible"""
+ if not isinstance(loc, np.ndarray) or loc.dtype != np.intp:
+ return loc
+
+ loc = lib.maybe_indices_to_slice(loc, len(self))
+ if isinstance(loc, slice):
+ return loc
+
+ mask = np.empty(len(self), dtype="bool")
+ mask.fill(False)
+ mask[loc] = True
+ return mask
+
+ if not isinstance(key, tuple):
+ loc = self._get_level_indexer(key, level=0)
+ return _maybe_to_slice(loc)
+
+ keylen = len(key)
+ if self.nlevels < keylen:
+ raise KeyError(
+ f"Key length ({keylen}) exceeds index depth ({self.nlevels})"
+ )
+
+ if keylen == self.nlevels and self.is_unique:
+ # TODO: what if we have an IntervalIndex level?
+ # i.e. do we need _index_as_unique on that level?
+ try:
+ return self._engine.get_loc(key)
+ except KeyError as err:
+ raise KeyError(key) from err
+ except TypeError:
+ # e.g. test_partial_slicing_with_multiindex partial string slicing
+ loc, _ = self.get_loc_level(key, range(self.nlevels))
+ return loc
+
+ # -- partial selection or non-unique index
+ # break the key into 2 parts based on the lexsort_depth of the index;
+ # the first part returns a continuous slice of the index; the 2nd part
+ # needs linear search within the slice
+ i = self._lexsort_depth
+ lead_key, follow_key = key[:i], key[i:]
+
+ if not lead_key:
+ start = 0
+ stop = len(self)
+ else:
+ try:
+ start, stop = self.slice_locs(lead_key, lead_key)
+ except TypeError as err:
+ # e.g. test_groupby_example key = ((0, 0, 1, 2), "new_col")
+ # when self has 5 integer levels
+ raise KeyError(key) from err
+
+ if start == stop:
+ raise KeyError(key)
+
+ if not follow_key:
+ return slice(start, stop)
+
+ if get_option("performance_warnings"):
+ warnings.warn(
+ "indexing past lexsort depth may impact performance.",
+ PerformanceWarning,
+ stacklevel=find_stack_level(),
+ )
+
+ loc = np.arange(start, stop, dtype=np.intp)
+
+ for i, k in enumerate(follow_key, len(lead_key)):
+ mask = self.codes[i][loc] == self._get_loc_single_level_index(
+ self.levels[i], k
+ )
+ if not mask.all():
+ loc = loc[mask]
+ if not len(loc):
+ raise KeyError(key)
+
+ return _maybe_to_slice(loc) if len(loc) != stop - start else slice(start, stop)
+
+ def get_loc_level(self, key, level: IndexLabel = 0, drop_level: bool = True):
+ """
+ Get location and sliced index for requested label(s)/level(s).
+
+ The `get_loc_level` method is a more advanced form of `get_loc`, allowing
+ users to specify not just a label or sequence of labels, but also the level(s)
+ in which to search. This method is useful when you need to isolate particular
+ sections of a MultiIndex, either for further analysis or for slicing and
+ dicing the data. The method provides flexibility in terms of maintaining
+ or dropping levels from the resulting index based on the `drop_level`
+ parameter.
+
+ Parameters
+ ----------
+ key : label or sequence of labels
+ The label(s) for which to get the location.
+ level : int/level name or list thereof, optional
+ The level(s) in the MultiIndex to consider. If not provided, defaults
+ to the first level.
+ drop_level : bool, default True
+ If ``False``, the resulting index will not drop any level.
+
+ Returns
+ -------
+ tuple
+ A 2-tuple where the elements :
+
+ Element 0: int, slice object or boolean array.
+
+ Element 1: The resulting sliced multiindex/index. If the key
+ contains all levels, this will be ``None``.
+
+ See Also
+ --------
+ MultiIndex.get_loc : Get location for a label or a tuple of labels.
+ MultiIndex.get_locs : Get location for a label/slice/list/mask or a
+ sequence of such.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([list("abb"), list("def")], names=["A", "B"])
+
+ >>> mi.get_loc_level("b")
+ (slice(1, 3, None), Index(['e', 'f'], dtype='str', name='B'))
+
+ >>> mi.get_loc_level("e", level="B")
+ (array([False, True, False]), Index(['b'], dtype='str', name='A'))
+
+ >>> mi.get_loc_level(["b", "e"])
+ (1, None)
+ """
+ if not isinstance(level, (range, list, tuple)):
+ level = self._get_level_number(level)
+ else:
+ level = [self._get_level_number(lev) for lev in level]
+
+ loc, mi = self._get_loc_level(key, level=level)
+ if not drop_level:
+ if lib.is_integer(loc):
+ # Slice index must be an integer or None
+ mi = self[loc : loc + 1]
+ else:
+ mi = self[loc]
+ return loc, mi
+
+ def _get_loc_level(self, key, level: int | list[int] = 0):
+ """
+ get_loc_level but with `level` known to be positional, not name-based.
+ """
+
+ # different name to distinguish from maybe_droplevels
+ def maybe_mi_droplevels(indexer, levels):
+ """
+ If level does not exist or all levels were dropped, the exception
+ has to be handled outside.
+ """
+ new_index = self[indexer]
+
+ for i in sorted(levels, reverse=True):
+ new_index = new_index._drop_level_numbers([i])
+
+ return new_index
+
+ if isinstance(level, (tuple, list)):
+ if len(key) != len(level):
+ raise AssertionError(
+ "Key for location must have same length as number of levels"
+ )
+ result = None
+ for lev, k in zip(level, key, strict=True):
+ loc, new_index = self._get_loc_level(k, level=lev)
+ if isinstance(loc, slice):
+ mask = np.zeros(len(self), dtype=bool)
+ mask[loc] = True
+ loc = mask
+ result = loc if result is None else result & loc
+
+ try:
+ # FIXME: we should be only dropping levels on which we are
+ # scalar-indexing
+ mi = maybe_mi_droplevels(result, level)
+ except ValueError:
+ # droplevel failed because we tried to drop all levels,
+ # i.e. len(level) == self.nlevels
+ mi = self[result]
+
+ return result, mi
+
+ # kludge for #1796
+ if isinstance(key, list):
+ key = tuple(key)
+
+ if isinstance(key, tuple) and level == 0:
+ try:
+ # Check if this tuple is a single key in our first level
+ if key in self.levels[0]:
+ indexer = self._get_level_indexer(key, level=level)
+ new_index = maybe_mi_droplevels(indexer, [0])
+ return indexer, new_index
+ except (TypeError, InvalidIndexError):
+ pass
+
+ if not any(isinstance(k, slice) for k in key):
+ if len(key) == self.nlevels and self.is_unique:
+ # Complete key in unique index -> standard get_loc
+ try:
+ return (self._engine.get_loc(key), None)
+ except KeyError as err:
+ raise KeyError(key) from err
+ except TypeError:
+ # e.g. partial string indexing
+ # test_partial_string_timestamp_multiindex
+ pass
+
+ # partial selection
+ indexer = self.get_loc(key)
+ ilevels = [i for i in range(len(key)) if key[i] != slice(None, None)]
+ if len(ilevels) == self.nlevels:
+ if is_integer(indexer):
+ # we are dropping all levels
+ return indexer, None
+
+ # TODO: in some cases we still need to drop some levels,
+ # e.g. test_multiindex_perf_warn
+ # test_partial_string_timestamp_multiindex
+ ilevels = [
+ i
+ for i in range(len(key))
+ if (
+ not isinstance(key[i], str)
+ or not self.levels[i]._supports_partial_string_indexing
+ )
+ and key[i] != slice(None, None)
+ ]
+ if len(ilevels) == self.nlevels:
+ # TODO: why?
+ ilevels = []
+ return indexer, maybe_mi_droplevels(indexer, ilevels)
+
+ else:
+ indexer = None
+ for i, k in enumerate(key):
+ if not isinstance(k, slice):
+ loc_level = self._get_level_indexer(k, level=i)
+ if isinstance(loc_level, slice):
+ if com.is_null_slice(loc_level) or com.is_full_slice(
+ loc_level, len(self)
+ ):
+ # everything
+ continue
+
+ # e.g. test_xs_IndexSlice_argument_not_implemented
+ k_index = np.zeros(len(self), dtype=bool)
+ k_index[loc_level] = True
+
+ else:
+ k_index = loc_level
+
+ elif com.is_null_slice(k):
+ # taking everything, does not affect `indexer` below
+ continue
+
+ else:
+ # FIXME: this message can be inaccurate, e.g.
+ # test_series_varied_multiindex_alignment
+ raise TypeError(f"Expected label or tuple of labels, got {key}")
+
+ if indexer is None:
+ indexer = k_index
+ else:
+ indexer &= k_index
+ if indexer is None:
+ indexer = slice(None, None)
+ ilevels = [i for i in range(len(key)) if key[i] != slice(None, None)]
+ return indexer, maybe_mi_droplevels(indexer, ilevels)
+ else:
+ indexer = self._get_level_indexer(key, level=level)
+ if (
+ isinstance(key, str)
+ and self.levels[level]._supports_partial_string_indexing
+ ):
+ # check to see if we did an exact lookup vs sliced
+ check = self.levels[level].get_loc(key)
+ if not is_integer(check):
+ # e.g. test_partial_string_timestamp_multiindex
+ return indexer, self[indexer]
+
+ try:
+ result_index = maybe_mi_droplevels(indexer, [level])
+ except ValueError:
+ result_index = self[indexer]
+
+ return indexer, result_index
+
+ def _get_level_indexer(
+ self, key, level: int = 0, indexer: npt.NDArray[np.bool_] | None = None
+ ):
+ # `level` kwarg is _always_ positional, never name
+ # return a boolean array or slice showing where the key is
+ # in the totality of values
+ # if the indexer is provided, then use this
+
+ level_index = self.levels[level]
+ level_codes = self.codes[level]
+
+ def convert_indexer(start, stop, step, indexer=indexer, codes=level_codes):
+ # Compute a bool indexer to identify the positions to take.
+ # If we have an existing indexer, we only need to examine the
+ # subset of positions where the existing indexer is True.
+ if indexer is not None:
+ # we only need to look at the subset of codes where the
+ # existing indexer equals True
+ codes = codes[indexer]
+
+ if step is None or step == 1:
+ new_indexer = (codes >= start) & (codes < stop)
+ else:
+ r = np.arange(start, stop, step, dtype=codes.dtype)
+ new_indexer = algos.isin(codes, r)
+
+ if indexer is None:
+ return new_indexer
+
+ indexer = indexer.copy()
+ indexer[indexer] = new_indexer
+ return indexer
+
+ if isinstance(key, slice):
+ # handle a slice, returning a slice if we can
+ # otherwise a boolean indexer
+ step = key.step
+ is_negative_step = step is not None and step < 0
+
+ try:
+ if key.start is not None:
+ start = level_index.get_loc(key.start)
+ elif is_negative_step:
+ start = len(level_index) - 1
+ else:
+ start = 0
+
+ if key.stop is not None:
+ stop = level_index.get_loc(key.stop)
+ elif is_negative_step:
+ stop = 0
+ elif isinstance(start, slice):
+ stop = len(level_index)
+ else:
+ stop = len(level_index) - 1
+ except KeyError:
+ # we have a partial slice (like looking up a partial date
+ # string)
+ start = stop = level_index.slice_indexer(key.start, key.stop, key.step)
+ step = start.step
+
+ if isinstance(start, slice) or isinstance(stop, slice):
+ # we have a slice for start and/or stop
+ # a partial date slicer on a DatetimeIndex generates a slice
+ # note that the stop ALREADY includes the stopped point (if
+ # it was a string sliced)
+ start = getattr(start, "start", start)
+ stop = getattr(stop, "stop", stop)
+ return convert_indexer(start, stop, step)
+
+ elif level > 0 or self._lexsort_depth == 0 or step is not None:
+ # need to have like semantics here to right
+ # searching as when we are using a slice
+ # so adjust the stop by 1 (so we include stop)
+ stop = (stop - 1) if is_negative_step else (stop + 1)
+ return convert_indexer(start, stop, step)
+ else:
+ # sorted, so can return slice object -> view
+ i = algos.searchsorted(level_codes, start, side="left")
+ j = algos.searchsorted(level_codes, stop, side="right")
+ return slice(i, j, step)
+
+ else:
+ idx = self._get_loc_single_level_index(level_index, key)
+
+ if level > 0 or self._lexsort_depth == 0:
+ # Desired level is not sorted
+ if isinstance(idx, slice):
+ # test_get_loc_partial_timestamp_multiindex
+ locs = (level_codes >= idx.start) & (level_codes < idx.stop)
+ return locs
+
+ locs = np.asarray(level_codes == idx, dtype=bool)
+
+ if not locs.any():
+ # The label is present in self.levels[level] but unused:
+ raise KeyError(key)
+ return locs
+
+ if isinstance(idx, slice):
+ # e.g. test_partial_string_timestamp_multiindex
+ start = algos.searchsorted(level_codes, idx.start, side="left")
+ # NB: "left" here bc of slice semantics
+ end = algos.searchsorted(level_codes, idx.stop, side="left")
+ else:
+ start = algos.searchsorted(level_codes, idx, side="left")
+ end = algos.searchsorted(level_codes, idx, side="right")
+
+ if start == end:
+ # The label is present in self.levels[level] but unused:
+ raise KeyError(key)
+ return slice(maybe_unbox_numpy_scalar(start), maybe_unbox_numpy_scalar(end))
+
+ def get_locs(self, seq) -> npt.NDArray[np.intp]:
+ """
+ Get location for a sequence of labels.
+
+ Parameters
+ ----------
+ seq : label, slice, list, mask or a sequence of such
+ You should use one of the above for each level.
+ If a level should not be used, set it to ``slice(None)``.
+
+ Returns
+ -------
+ numpy.ndarray
+ NumPy array of integers suitable for passing to iloc.
+
+ See Also
+ --------
+ MultiIndex.get_loc : Get location for a label or a tuple of labels.
+ MultiIndex.slice_locs : Get slice location given start label(s) and
+ end label(s).
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([list("abb"), list("def")])
+
+ >>> mi.get_locs("b") # doctest: +SKIP
+ array([1, 2], dtype=int64)
+
+ >>> mi.get_locs([slice(None), ["e", "f"]]) # doctest: +SKIP
+ array([1, 2], dtype=int64)
+
+ >>> mi.get_locs([[True, False, True], slice("e", "f")]) # doctest: +SKIP
+ array([2], dtype=int64)
+ """
+
+ # must be lexsorted to at least as many levels
+ true_slices = [i for (i, s) in enumerate(com.is_true_slices(seq)) if s]
+ if true_slices and true_slices[-1] >= self._lexsort_depth:
+ raise UnsortedIndexError(
+ "MultiIndex slicing requires the index to be lexsorted: slicing "
+ f"on levels {true_slices}, lexsort depth {self._lexsort_depth}"
+ )
+
+ if any(x is Ellipsis for x in seq):
+ raise NotImplementedError(
+ "MultiIndex does not support indexing with Ellipsis"
+ )
+
+ n = len(self)
+
+ def _to_bool_indexer(indexer) -> npt.NDArray[np.bool_]:
+ if isinstance(indexer, slice):
+ new_indexer = np.zeros(n, dtype=np.bool_)
+ new_indexer[indexer] = True
+ return new_indexer
+ return indexer
+
+ # a bool indexer for the positions we want to take
+ indexer: npt.NDArray[np.bool_] | None = None
+
+ for i, k in enumerate(seq):
+ lvl_indexer: npt.NDArray[np.bool_] | slice | None = None
+
+ if com.is_bool_indexer(k):
+ if len(k) != n:
+ raise ValueError(
+ "cannot index with a boolean indexer that "
+ "is not the same length as the index"
+ )
+ if isinstance(k, (ABCSeries, Index)):
+ k = k._values
+ lvl_indexer = np.asarray(k)
+ if indexer is None:
+ lvl_indexer = lvl_indexer.copy()
+
+ elif is_list_like(k):
+ # a collection of labels to include from this level (these are or'd)
+
+ # GH#27591 check if this is a single tuple key in the level
+ try:
+ lvl_indexer = self._get_level_indexer(k, level=i, indexer=indexer)
+ except (InvalidIndexError, TypeError, KeyError) as err:
+ # InvalidIndexError e.g. non-hashable, fall back to treating
+ # this as a sequence of labels
+ # KeyError it can be ambiguous if this is a label or sequence
+ # of labels
+ # github.com/pandas-dev/pandas/issues/39424#issuecomment-871626708
+ for x in k:
+ if not is_hashable(x):
+ # e.g. slice
+ raise err
+ # GH 39424: Ignore not founds
+ # GH 42351: No longer ignore not founds & enforced in 2.0
+ # TODO: how to handle IntervalIndex level? (no test cases)
+ item_indexer = self._get_level_indexer(
+ x, level=i, indexer=indexer
+ )
+ if lvl_indexer is None:
+ lvl_indexer = _to_bool_indexer(item_indexer)
+ elif isinstance(item_indexer, slice):
+ lvl_indexer[item_indexer] = True # type: ignore[index]
+ else:
+ lvl_indexer |= item_indexer
+
+ if lvl_indexer is None:
+ # no matches we are done
+ # test_loc_getitem_duplicates_multiindex_empty_indexer
+ return np.array([], dtype=np.intp)
+
+ elif com.is_null_slice(k):
+ # empty slice
+ if indexer is None and i == len(seq) - 1:
+ return np.arange(n, dtype=np.intp)
+ continue
+
+ else:
+ # a slice or a single label
+ lvl_indexer = self._get_level_indexer(k, level=i, indexer=indexer)
+
+ # update indexer
+ lvl_indexer = _to_bool_indexer(lvl_indexer)
+ if indexer is None:
+ indexer = lvl_indexer
+ else:
+ indexer &= lvl_indexer
+ if not np.any(indexer) and np.any(lvl_indexer):
+ raise KeyError(seq)
+
+ # empty indexer
+ if indexer is None:
+ return np.array([], dtype=np.intp)
+
+ pos_indexer = indexer.nonzero()[0]
+ return self._reorder_indexer(seq, pos_indexer)
+
+ # --------------------------------------------------------------------
+
+ def _reorder_indexer(
+ self,
+ seq: tuple[Scalar | Iterable | AnyArrayLike, ...],
+ indexer: npt.NDArray[np.intp],
+ ) -> npt.NDArray[np.intp]:
+ """
+ Reorder an indexer of a MultiIndex (self) so that the labels are in the
+ same order as given in seq
+
+ Parameters
+ ----------
+ seq : label/slice/list/mask or a sequence of such
+ indexer: a position indexer of self
+
+ Returns
+ -------
+ indexer : a sorted position indexer of self ordered as seq
+ """
+
+ # check if sorting is necessary
+ need_sort = False
+ for i, k in enumerate(seq):
+ if com.is_null_slice(k) or com.is_bool_indexer(k) or is_scalar(k):
+ pass
+ elif is_list_like(k):
+ if len(k) <= 1: # type: ignore[arg-type]
+ pass
+ elif self._is_lexsorted():
+ # If the index is lexsorted and the list_like label
+ # in seq are sorted then we do not need to sort
+ k_codes = self.levels[i].get_indexer(k)
+ k_codes = k_codes[k_codes >= 0] # Filter absent keys
+ # True if the given codes are not ordered
+ need_sort = (k_codes[:-1] > k_codes[1:]).any()
+ else:
+ need_sort = True
+ elif isinstance(k, slice):
+ if self._is_lexsorted():
+ need_sort = k.step is not None and k.step < 0
+ else:
+ need_sort = True
+ else:
+ need_sort = True
+ if need_sort:
+ break
+ if not need_sort:
+ return indexer
+
+ n = len(self)
+ keys: tuple[np.ndarray, ...] = ()
+ # For each level of the sequence in seq, map the level codes with the
+ # order they appears in a list-like sequence
+ # This mapping is then use to reorder the indexer
+ for i, k in enumerate(seq):
+ if is_scalar(k):
+ # GH#34603 we want to treat a scalar the same as an all equal list
+ k = [k]
+ if com.is_bool_indexer(k):
+ new_order = np.arange(n)[indexer]
+ elif is_list_like(k):
+ # Generate a map with all level codes as sorted initially
+ if not isinstance(k, (np.ndarray, ExtensionArray, Index, ABCSeries)):
+ k = sanitize_array(k, None)
+ k = algos.unique(k)
+ key_order_map = np.ones(len(self.levels[i]), dtype=np.uint64) * len(
+ self.levels[i]
+ )
+ # Set order as given in the indexer list
+ level_indexer = self.levels[i].get_indexer(k)
+ level_indexer = level_indexer[level_indexer >= 0] # Filter absent keys
+ key_order_map[level_indexer] = np.arange(len(level_indexer))
+
+ new_order = key_order_map[self.codes[i][indexer]]
+ elif isinstance(k, slice) and k.step is not None and k.step < 0:
+ # flip order for negative step
+ new_order = np.arange(n - 1, -1, -1)[indexer]
+ elif isinstance(k, slice) and k.start is None and k.stop is None:
+ # slice(None) should not determine order GH#31330
+ new_order = np.ones((n,), dtype=np.intp)[indexer]
+ else:
+ # For all other case, use the same order as the level
+ new_order = np.arange(n)[indexer]
+ keys = (new_order, *keys)
+
+ # Find the reordering using lexsort on the keys mapping
+ ind = np.lexsort(keys)
+ return indexer[ind]
+
+ def truncate(self, before=None, after=None) -> MultiIndex:
+ """
+ Slice index between two labels / tuples, return new MultiIndex.
+
+ Parameters
+ ----------
+ before : label or tuple, can be partial. Default None
+ None defaults to start.
+ after : label or tuple, can be partial. Default None
+ None defaults to end.
+
+ Returns
+ -------
+ MultiIndex
+ The truncated MultiIndex.
+
+ See Also
+ --------
+ DataFrame.truncate : Truncate a DataFrame before and after some index values.
+ Series.truncate : Truncate a Series before and after some index values.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays([["a", "b", "c"], ["x", "y", "z"]])
+ >>> mi
+ MultiIndex([('a', 'x'), ('b', 'y'), ('c', 'z')],
+ )
+ >>> mi.truncate(before="a", after="b")
+ MultiIndex([('a', 'x'), ('b', 'y')],
+ )
+ """
+ if after and before and after < before:
+ raise ValueError("after < before")
+
+ i, j = self.levels[0].slice_locs(before, after)
+ left, right = self.slice_locs(before, after)
+
+ new_levels = list(self.levels)
+ new_levels[0] = new_levels[0][i:j]
+
+ new_codes = [level_codes[left:right] for level_codes in self.codes]
+ new_codes[0] = new_codes[0] - i
+
+ return MultiIndex(
+ levels=new_levels,
+ codes=new_codes,
+ names=self._names,
+ verify_integrity=False,
+ )
+
+ def equals(self, other: object) -> bool:
+ """
+ Determines if two MultiIndex objects have the same labeling information
+ (the levels themselves do not necessarily have to be the same)
+
+ See Also
+ --------
+ equal_levels
+ """
+ if self.is_(other):
+ return True
+
+ if not isinstance(other, Index):
+ return False
+
+ if len(self) != len(other):
+ return False
+
+ if not isinstance(other, MultiIndex):
+ # d-level MultiIndex can equal d-tuple Index
+ if not self._should_compare(other):
+ # object Index or Categorical[object] may contain tuples
+ return False
+ return array_equivalent(self._values, other._values)
+
+ if self.nlevels != other.nlevels:
+ return False
+
+ for i in range(self.nlevels):
+ self_codes = self.codes[i]
+ other_codes = other.codes[i]
+ self_mask = self_codes == -1
+ other_mask = other_codes == -1
+ if not np.array_equal(self_mask, other_mask):
+ return False
+ self_level = self.levels[i]
+ other_level = other.levels[i]
+ new_codes = recode_for_categories(
+ other_codes, other_level, self_level, copy=False
+ )
+ if not np.array_equal(self_codes, new_codes):
+ return False
+ if not self_level[:0].equals(other_level[:0]):
+ # e.g. Int64 != int64
+ return False
+ return True
+
+ def equal_levels(self, other: MultiIndex) -> bool:
+ """
+ Return True if the levels of both MultiIndex objects are the same
+
+ """
+ if self.nlevels != other.nlevels:
+ return False
+
+ for i in range(self.nlevels):
+ if not self.levels[i].equals(other.levels[i]):
+ return False
+ return True
+
+ # --------------------------------------------------------------------
+ # Set Methods
+
+ def _union(self, other, sort) -> MultiIndex:
+ other, result_names = self._convert_can_do_setop(other)
+ if other.has_duplicates:
+ # This is only necessary if other has dupes,
+ # otherwise difference is faster
+ result = super(MultiIndex, self.rename(result_names))._union(
+ other.rename(result_names), sort
+ )
+
+ if isinstance(result, MultiIndex):
+ return result
+ return MultiIndex.from_arrays(
+ zip(*result, strict=True), sortorder=None, names=result_names
+ )
+
+ else:
+ right_missing = other.difference(self, sort=False)
+ if len(right_missing):
+ result = self.append(right_missing)
+ else:
+ result = self._get_reconciled_name_object(other)
+
+ if sort is not False:
+ try:
+ result = result.sort_values()
+ except TypeError:
+ if sort is True:
+ raise
+ warnings.warn(
+ "The values in the array are unorderable. "
+ "Pass `sort=False` to suppress this warning.",
+ RuntimeWarning,
+ stacklevel=find_stack_level(),
+ )
+ return result
+
+ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
+ return is_object_dtype(dtype)
+
+ def _get_reconciled_name_object(self, other) -> MultiIndex:
+ """
+ If the result of a set operation will be self,
+ return a shallow copy of self.
+ """
+ names = self._maybe_match_names(other)
+ if self.names != names:
+ return self.rename(names)
+ return self.copy(deep=False)
+
+ def _maybe_match_names(self, other):
+ """
+ Try to find common names to attach to the result of an operation between
+ a and b. Return a consensus list of names if they match at least partly
+ or list of None if they have completely different names.
+ """
+ if len(self.names) != len(other.names):
+ return [None] * len(self.names)
+ names = []
+ for a_name, b_name in zip(self.names, other.names, strict=True):
+ if a_name == b_name:
+ names.append(a_name)
+ else:
+ # TODO: what if they both have np.nan for their names?
+ names.append(None)
+ return names
+
+ def _wrap_intersection_result(self, other, result) -> MultiIndex:
+ _, result_names = self._convert_can_do_setop(other)
+ return result.set_names(result_names)
+
+ def _wrap_difference_result(self, other, result: MultiIndex) -> MultiIndex:
+ _, result_names = self._convert_can_do_setop(other)
+
+ if len(result) == 0:
+ return result.remove_unused_levels().set_names(result_names)
+ else:
+ return result.set_names(result_names)
+
+ def _convert_can_do_setop(self, other):
+ result_names = self.names
+
+ if not isinstance(other, Index):
+ if len(other) == 0:
+ return self[:0], self.names
+ else:
+ msg = "other must be a MultiIndex or a list of tuples"
+ try:
+ other = MultiIndex.from_tuples(other, names=self.names)
+ except (ValueError, TypeError) as err:
+ # ValueError raised by tuples_to_object_array if we
+ # have non-object dtype
+ raise TypeError(msg) from err
+ else:
+ result_names = get_unanimous_names(self, other)
+
+ return other, result_names
+
+ # --------------------------------------------------------------------
+
+ def astype(self, dtype, copy: bool = True):
+ """
+ Create an MultiIndex with values cast to dtypes.
+
+ The class of a new Index is determined by dtype. When conversion is
+ impossible, a TypeError exception is raised.
+
+ Parameters
+ ----------
+ dtype : numpy dtype or pandas type
+ Note that any signed integer `dtype` is treated as ``'int64'``,
+ and any unsigned integer `dtype` is treated as ``'uint64'``,
+ regardless of the size.
+ copy : bool, default True
+ By default, astype always returns a newly allocated object.
+ If copy is set to False and internal requirements on dtype are
+ satisfied, the original data is used to create a new Index
+ or the original Index is returned.
+
+ Returns
+ -------
+ MultiIndex
+ MultiIndex with values cast to specified dtype.
+
+ See Also
+ --------
+ Index.dtype: Return the dtype object of the underlying data.
+ Index.dtypes: Return the dtype object of the underlying data.
+ Index.convert_dtypes: Convert columns to the best possible dtypes.
+
+ Examples
+ --------
+ >>> mi = pd.MultiIndex.from_arrays(([1, 2, 3], [4, 5, 6]))
+ >>> mi
+ MultiIndex([(1, 4),
+ (2, 5),
+ (3, 6)],
+ )
+ >>> mi.astype("object")
+ MultiIndex([(1, 4),
+ (2, 5),
+ (3, 6)],
+ )
+ """
+ dtype = pandas_dtype(dtype)
+ if isinstance(dtype, CategoricalDtype):
+ msg = "> 1 ndim Categorical are not supported at this time"
+ raise NotImplementedError(msg)
+ if not is_object_dtype(dtype):
+ raise TypeError(
+ "Setting a MultiIndex dtype to anything other than object "
+ "is not supported"
+ )
+ if copy is True:
+ return self._view()
+ return self
+
+ def _validate_fill_value(self, item):
+ if isinstance(item, MultiIndex):
+ # GH#43212
+ if item.nlevels != self.nlevels:
+ raise ValueError("Item must have length equal to number of levels.")
+ return item._values
+ elif not isinstance(item, tuple):
+ # Pad the key with empty strings if lower levels of the key
+ # aren't specified:
+ item = (item,) + ("",) * (self.nlevels - 1)
+ elif len(item) != self.nlevels:
+ raise ValueError("Item must have length equal to number of levels.")
+ return item
+
+ def putmask(self, mask, value: MultiIndex) -> MultiIndex:
+ """
+ Return a new MultiIndex of the values set with the mask.
+
+ Parameters
+ ----------
+ mask : array like
+ value : MultiIndex
+ Must either be the same length as self or length one
+
+ Returns
+ -------
+ MultiIndex
+ """
+ mask, noop = validate_putmask(self, mask)
+ if noop:
+ return self.copy()
+
+ if len(mask) == len(value):
+ subset = value[mask].remove_unused_levels()
+ else:
+ subset = value.remove_unused_levels()
+
+ new_levels = []
+ new_codes = []
+
+ for i, (value_level, level, level_codes) in enumerate(
+ zip(subset.levels, self.levels, self.codes, strict=True)
+ ):
+ new_level = level.union(value_level, sort=False)
+ value_codes = new_level.get_indexer_for(subset.get_level_values(i))
+ new_code = ensure_int64(level_codes)
+ new_code[mask] = value_codes
+ new_levels.append(new_level)
+ new_codes.append(new_code)
+
+ return MultiIndex(
+ levels=new_levels, codes=new_codes, names=self.names, verify_integrity=False
+ )
+
+ def insert(self, loc: int, item) -> MultiIndex:
+ """
+ Make new MultiIndex inserting new item at location
+
+ Parameters
+ ----------
+ loc : int
+ item : tuple
+ Must be same length as number of levels in the MultiIndex
+
+ Returns
+ -------
+ new_index : Index
+ """
+ item = self._validate_fill_value(item)
+
+ new_levels = []
+ new_codes = []
+ for k, level, level_codes in zip(item, self.levels, self.codes, strict=True):
+ if k not in level:
+ # have to insert into level
+ # must insert at end otherwise you have to recompute all the
+ # other codes
+ lev_loc = len(level)
+ level = level.insert(lev_loc, k)
+ if isna(level[lev_loc]): # GH 59003, 60388
+ lev_loc = -1
+ else:
+ lev_loc = level.get_loc(k)
+
+ new_levels.append(level)
+ new_codes.append(np.insert(ensure_int64(level_codes), loc, lev_loc))
+
+ return MultiIndex(
+ levels=new_levels, codes=new_codes, names=self.names, verify_integrity=False
+ )
+
+ def delete(self, loc) -> MultiIndex:
+ """
+ Make new index with passed location deleted
+
+ Returns
+ -------
+ new_index : MultiIndex
+ """
+ new_codes = [np.delete(level_codes, loc) for level_codes in self.codes]
+ return MultiIndex(
+ levels=self.levels,
+ codes=new_codes,
+ names=self.names,
+ verify_integrity=False,
+ )
+
+ def isin(self, values, level=None) -> npt.NDArray[np.bool_]:
+ """
+ Return a boolean array where the index values are in `values`.
+
+ Compute boolean array of whether each index value is found in the
+ passed set of values. The length of the returned boolean array matches
+ the length of the index.
+
+ Parameters
+ ----------
+ values : set or list-like
+ Sought values.
+ level : str or int, optional
+ Name or position of the index level to use (if the index is a
+ `MultiIndex`).
+
+ Returns
+ -------
+ np.ndarray[bool]
+ NumPy array of boolean values.
+
+ See Also
+ --------
+ Series.isin : Same for Series.
+ DataFrame.isin : Same method for DataFrames.
+
+ Notes
+ -----
+ In the case of `MultiIndex` you must either specify `values` as a
+ list-like object containing tuples that are the same length as the
+ number of levels, or specify `level`. Otherwise it will raise a
+ ``ValueError``.
+
+ If `level` is specified:
+
+ - if it is the name of one *and only one* index level, use that level;
+ - otherwise it should be a number indicating level position.
+
+ Examples
+ --------
+ >>> idx = pd.Index([1, 2, 3])
+ >>> idx
+ Index([1, 2, 3], dtype='int64')
+
+ Check whether each index value in a list of values.
+
+ >>> idx.isin([1, 4])
+ array([ True, False, False])
+
+ >>> mi = pd.MultiIndex.from_arrays(
+ ... [[1, 2, 3], ["red", "blue", "green"]], names=["number", "color"]
+ ... )
+ >>> mi
+ MultiIndex([(1, 'red'),
+ (2, 'blue'),
+ (3, 'green')],
+ names=['number', 'color'])
+
+ Check whether the strings in the 'color' level of the MultiIndex
+ are in a list of colors.
+
+ >>> mi.isin(["red", "orange", "yellow"], level="color")
+ array([ True, False, False])
+
+ To check across the levels of a MultiIndex, pass a list of tuples:
+
+ >>> mi.isin([(1, "red"), (3, "red")])
+ array([ True, False, False])
+ """
+ if isinstance(values, Generator):
+ values = list(values)
+
+ if level is None:
+ if len(values) == 0:
+ return np.zeros((len(self),), dtype=np.bool_)
+ if not isinstance(values, MultiIndex):
+ values = MultiIndex.from_tuples(values)
+ return values.unique().get_indexer_for(self) != -1
+ else:
+ num = self._get_level_number(level)
+ levs = self.get_level_values(num)
+
+ if levs.size == 0:
+ return np.zeros(len(levs), dtype=np.bool_)
+ return levs.isin(values)
+
+ # error: Incompatible types in assignment (expression has type overloaded function,
+ # base class "Index" defined the type as "Callable[[Index, Any, bool], Any]")
+ rename = Index.set_names # type: ignore[assignment]
+
+ # ---------------------------------------------------------------
+ # Arithmetic/Numeric Methods - Disabled
+
+ __add__ = make_invalid_op("__add__")
+ __radd__ = make_invalid_op("__radd__")
+ __iadd__ = make_invalid_op("__iadd__")
+ __sub__ = make_invalid_op("__sub__")
+ __rsub__ = make_invalid_op("__rsub__")
+ __isub__ = make_invalid_op("__isub__")
+ __pow__ = make_invalid_op("__pow__")
+ __rpow__ = make_invalid_op("__rpow__")
+ __mul__ = make_invalid_op("__mul__")
+ __rmul__ = make_invalid_op("__rmul__")
+ __floordiv__ = make_invalid_op("__floordiv__")
+ __rfloordiv__ = make_invalid_op("__rfloordiv__")
+ __truediv__ = make_invalid_op("__truediv__")
+ __rtruediv__ = make_invalid_op("__rtruediv__")
+ __mod__ = make_invalid_op("__mod__")
+ __rmod__ = make_invalid_op("__rmod__")
+ __divmod__ = make_invalid_op("__divmod__")
+ __rdivmod__ = make_invalid_op("__rdivmod__")
+ # Unary methods disabled
+ __neg__ = make_invalid_op("__neg__")
+ __pos__ = make_invalid_op("__pos__")
+ __abs__ = make_invalid_op("__abs__")
+ __invert__ = make_invalid_op("__invert__")
+
+
+def _lexsort_depth(codes: list[np.ndarray], nlevels: int) -> int:
+ """Count depth (up to a maximum of `nlevels`) with which codes are lexsorted."""
+ int64_codes = [ensure_int64(level_codes) for level_codes in codes]
+ for k in range(nlevels, 0, -1):
+ if libalgos.is_lexsorted(int64_codes[:k]):
+ return k
+ return 0
+
+
+def sparsify_labels(label_list, start: int = 0, sentinel: object = ""):
+ pivoted = list(zip(*label_list, strict=True))
+ k = len(label_list)
+
+ result = pivoted[: start + 1]
+ prev = pivoted[start]
+
+ for cur in pivoted[start + 1 :]:
+ sparse_cur = []
+
+ for i, (p, t) in enumerate(zip(prev, cur, strict=True)):
+ if i == k - 1:
+ sparse_cur.append(t)
+ result.append(sparse_cur) # type: ignore[arg-type]
+ break
+
+ if p == t:
+ sparse_cur.append(sentinel)
+ else:
+ sparse_cur.extend(cur[i:])
+ result.append(sparse_cur) # type: ignore[arg-type]
+ break
+
+ prev = cur
+
+ return list(zip(*result, strict=True))
+
+
+def _get_na_rep(dtype: DtypeObj) -> str:
+ if isinstance(dtype, ExtensionDtype):
+ return f"{dtype.na_value}"
+ else:
+ dtype_type = dtype.type
+
+ return {np.datetime64: "NaT", np.timedelta64: "NaT"}.get(dtype_type, "NaN")
+
+
+def maybe_droplevels(index: Index, key) -> Index:
+ """
+ Attempt to drop level or levels from the given index.
+
+ Parameters
+ ----------
+ index: Index
+ key : scalar or tuple
+
+ Returns
+ -------
+ Index
+ """
+ # drop levels
+ original_index = index
+ if isinstance(key, tuple):
+ # Caller is responsible for ensuring the key is not an entry in the first
+ # level of the MultiIndex.
+ for _ in key:
+ try:
+ index = index._drop_level_numbers([0])
+ except ValueError:
+ # we have dropped too much, so back out
+ return original_index
+ else:
+ try:
+ index = index._drop_level_numbers([0])
+ except ValueError:
+ pass
+
+ return index
+
+
+def _coerce_indexer_frozen(array_like, categories, copy: bool = False) -> np.ndarray:
+ """
+ Coerce the array-like indexer to the smallest integer dtype that can encode all
+ of the given categories.
+
+ Parameters
+ ----------
+ array_like : array-like
+ categories : array-like
+ copy : bool
+
+ Returns
+ -------
+ np.ndarray
+ Non-writeable.
+ """
+ array_like = coerce_indexer_dtype(array_like, categories)
+ if copy:
+ array_like = array_like.copy()
+ array_like.flags.writeable = False
+ return array_like
+
+
+def _require_listlike(level, arr, arrname: str):
+ """
+ Ensure that level is either None or listlike, and arr is list-of-listlike.
+ """
+ if level is not None and not is_list_like(level):
+ if not is_list_like(arr):
+ raise TypeError(f"{arrname} must be list-like")
+ if len(arr) > 0 and is_list_like(arr[0]):
+ raise TypeError(f"{arrname} must be list-like")
+ level = [level]
+ arr = [arr]
+ elif level is None or is_list_like(level):
+ if not is_list_like(arr) or not is_list_like(arr[0]):
+ raise TypeError(f"{arrname} must be list of lists-like")
+ return level, arr
+
+
+def cartesian_product(X: list[np.ndarray]) -> list[np.ndarray]:
+ """
+ Numpy version of itertools.product.
+ Sometimes faster (for large inputs)...
+
+ Parameters
+ ----------
+ X : list-like of list-likes
+
+ Returns
+ -------
+ product : list of ndarrays
+
+ Examples
+ --------
+ >>> cartesian_product([list("ABC"), [1, 2]])
+ [array(['A', 'A', 'B', 'B', 'C', 'C'], dtype='>> idx = pd.PeriodIndex(data=["2000Q1", "2002Q3"], freq="Q")
+ >>> idx
+ PeriodIndex(['2000Q1', '2002Q3'], dtype='period[Q-DEC]')
+ """
+
+ _typ = "periodindex"
+
+ _data: PeriodArray
+ freq: BaseOffset
+ dtype: PeriodDtype
+
+ _data_cls = PeriodArray
+ _supports_partial_string_indexing = True
+
+ @property
+ def _engine_type(self) -> type[libindex.PeriodEngine]:
+ return libindex.PeriodEngine
+
+ @cache_readonly
+ def _resolution_obj(self) -> Resolution:
+ # for compat with DatetimeIndex
+ return self.dtype._resolution_obj
+
+ # --------------------------------------------------------------------
+ # methods that dispatch to array and wrap result in Index
+ # These are defined here instead of via inherit_names for mypy
+
+ @doc(
+ PeriodArray.asfreq,
+ other="arrays.PeriodArray",
+ other_name="PeriodArray",
+ **_shared_doc_kwargs,
+ )
+ def asfreq(self, freq=None, how: str = "E") -> Self:
+ arr = self._data.asfreq(freq, how)
+ return type(self)._simple_new(arr, name=self.name)
+
+ @doc(PeriodArray.to_timestamp)
+ def to_timestamp(self, freq=None, how: str = "start") -> DatetimeIndex:
+ arr = self._data.to_timestamp(freq, how)
+ return DatetimeIndex._simple_new(arr, name=self.name)
+
+ @property
+ @doc(PeriodArray.hour.fget)
+ def hour(self) -> Index:
+ return Index(self._data.hour, name=self.name, copy=False)
+
+ @property
+ @doc(PeriodArray.minute.fget)
+ def minute(self) -> Index:
+ return Index(self._data.minute, name=self.name, copy=False)
+
+ @property
+ @doc(PeriodArray.second.fget)
+ def second(self) -> Index:
+ return Index(self._data.second, name=self.name, copy=False)
+
+ # ------------------------------------------------------------------------
+ # Index Constructors
+
+ def __new__(
+ cls,
+ data=None,
+ freq=None,
+ dtype: Dtype | None = None,
+ copy: bool | None = None,
+ name: Hashable | None = None,
+ ) -> Self:
+ refs = None
+ if not copy and isinstance(data, (Index, ABCSeries)):
+ refs = data._references
+
+ name = maybe_extract_name(name, data, cls)
+
+ freq = validate_dtype_freq(dtype, freq)
+
+ # GH#63388
+ data, copy = cls._maybe_copy_array_input(data, copy, dtype)
+
+ # PeriodIndex allow PeriodIndex(period_index, freq=different)
+ # Let's not encourage that kind of behavior in PeriodArray.
+
+ if freq and isinstance(data, cls) and data.freq != freq:
+ # TODO: We can do some of these with no-copy / coercion?
+ # e.g. D -> 2D seems to be OK
+ data = data.asfreq(freq)
+
+ # don't pass copy here, since we copy later.
+ data = period_array(data=data, freq=freq)
+
+ if copy:
+ data = data.copy()
+
+ return cls._simple_new(data, name=name, refs=refs)
+
+ @classmethod
+ def from_fields(
+ cls,
+ *,
+ year=None,
+ quarter=None,
+ month=None,
+ day=None,
+ hour=None,
+ minute=None,
+ second=None,
+ freq=None,
+ ) -> Self:
+ """
+ Construct a PeriodIndex from fields (year, month, day, etc.).
+
+ Parameters
+ ----------
+ year : int, array, or Series, default None
+ Year for the PeriodIndex.
+ quarter : int, array, or Series, default None
+ Quarter for the PeriodIndex.
+ month : int, array, or Series, default None
+ Month for the PeriodIndex.
+ day : int, array, or Series, default None
+ Day for the PeriodIndex.
+ hour : int, array, or Series, default None
+ Hour for the PeriodIndex.
+ minute : int, array, or Series, default None
+ Minute for the PeriodIndex.
+ second : int, array, or Series, default None
+ Second for the PeriodIndex.
+ freq : str or period object, optional
+ One of pandas period strings or corresponding objects.
+
+ Returns
+ -------
+ PeriodIndex
+
+ See Also
+ --------
+ PeriodIndex.from_ordinals : Construct a PeriodIndex from ordinals.
+ PeriodIndex.to_timestamp : Cast to DatetimeArray/Index.
+
+ Examples
+ --------
+ >>> idx = pd.PeriodIndex.from_fields(year=[2000, 2002], quarter=[1, 3])
+ >>> idx
+ PeriodIndex(['2000Q1', '2002Q3'], dtype='period[Q-DEC]')
+ """
+ fields = {
+ "year": year,
+ "quarter": quarter,
+ "month": month,
+ "day": day,
+ "hour": hour,
+ "minute": minute,
+ "second": second,
+ }
+ fields = {key: value for key, value in fields.items() if value is not None}
+ arr = PeriodArray._from_fields(fields=fields, freq=freq)
+ return cls._simple_new(arr)
+
+ @classmethod
+ def from_ordinals(cls, ordinals, *, freq, name=None) -> Self:
+ """
+ Construct a PeriodIndex from ordinals.
+
+ Parameters
+ ----------
+ ordinals : array-like of int
+ The period offsets from the proleptic Gregorian epoch.
+ freq : str or period object
+ One of pandas period strings or corresponding objects.
+ name : str, default None
+ Name of the resulting PeriodIndex.
+
+ Returns
+ -------
+ PeriodIndex
+
+ See Also
+ --------
+ PeriodIndex.from_fields : Construct a PeriodIndex from fields
+ (year, month, day, etc.).
+ PeriodIndex.to_timestamp : Cast to DatetimeArray/Index.
+
+ Examples
+ --------
+ >>> idx = pd.PeriodIndex.from_ordinals([-1, 0, 1], freq="Q")
+ >>> idx
+ PeriodIndex(['1969Q4', '1970Q1', '1970Q2'], dtype='period[Q-DEC]')
+ """
+ ordinals = np.asarray(ordinals, dtype=np.int64)
+ dtype = PeriodDtype(freq)
+ data = PeriodArray._simple_new(ordinals, dtype=dtype)
+ return cls._simple_new(data, name=name)
+
+ # ------------------------------------------------------------------------
+ # Data
+
+ @property
+ def values(self) -> npt.NDArray[np.object_]:
+ return np.asarray(self, dtype=object)
+
+ def _maybe_convert_timedelta(self, other) -> int | npt.NDArray[np.int64]:
+ """
+ Convert timedelta-like input to an integer multiple of self.freq
+
+ Parameters
+ ----------
+ other : timedelta, np.timedelta64, DateOffset, int, np.ndarray
+
+ Returns
+ -------
+ converted : int, np.ndarray[int64]
+
+ Raises
+ ------
+ IncompatibleFrequency : if the input cannot be written as a multiple
+ of self.freq. Note IncompatibleFrequency subclasses ValueError.
+ """
+ if isinstance(other, (timedelta, np.timedelta64, Tick, np.ndarray)):
+ if isinstance(self.freq, (Tick, Day)):
+ # _check_timedeltalike_freq_compat will raise if incompatible
+ delta = self._data._check_timedeltalike_freq_compat(other)
+ return delta
+ elif isinstance(other, BaseOffset):
+ if other.base == self.freq.base:
+ return other.n
+
+ raise raise_on_incompatible(self, other)
+ elif is_integer(other):
+ assert isinstance(other, int)
+ return other
+
+ # raise when input doesn't have freq
+ raise raise_on_incompatible(self, None)
+
+ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
+ """
+ Can we compare values of the given dtype to our own?
+ """
+ return self.dtype == dtype
+
+ # ------------------------------------------------------------------------
+ # Index Methods
+
+ def asof_locs(self, where: Index, mask: npt.NDArray[np.bool_]) -> np.ndarray:
+ """
+ where : array of timestamps
+ mask : np.ndarray[bool]
+ Array of booleans where data is not NA.
+ """
+ if isinstance(where, DatetimeIndex):
+ where = PeriodIndex(where._values, freq=self.freq, copy=False)
+ elif not isinstance(where, PeriodIndex):
+ raise TypeError("asof_locs `where` must be DatetimeIndex or PeriodIndex")
+
+ return super().asof_locs(where, mask)
+
+ @property
+ def is_full(self) -> bool:
+ """
+ Returns True if this PeriodIndex is range-like in that all Periods
+ between start and end are present, in order.
+ """
+ if len(self) == 0:
+ return True
+ if not self.is_monotonic_increasing:
+ raise ValueError("Index is not monotonic")
+ values = self.asi8
+ return bool(((values[1:] - values[:-1]) < 2).all())
+
+ @property
+ def inferred_type(self) -> str:
+ # b/c data is represented as ints make sure we can't have ambiguous
+ # indexing
+ return "period"
+
+ # ------------------------------------------------------------------------
+ # Indexing Methods
+
+ def _convert_tolerance(self, tolerance, target):
+ # Returned tolerance must be in dtype/units so that
+ # `|self._get_engine_target() - target._engine_target()| <= tolerance`
+ # is meaningful. Since PeriodIndex returns int64 for engine_target,
+ # we may need to convert timedelta64 tolerance to int64.
+ tolerance = super()._convert_tolerance(tolerance, target)
+
+ if self.dtype == target.dtype:
+ # convert tolerance to i8
+ tolerance = self._maybe_convert_timedelta(tolerance)
+
+ return tolerance
+
+ def get_loc(self, key):
+ """
+ Get integer location for requested label.
+
+ Parameters
+ ----------
+ key : Period, NaT, str, or datetime
+ String or datetime key must be parsable as Period.
+
+ Returns
+ -------
+ loc : int or ndarray[int64]
+
+ Raises
+ ------
+ KeyError
+ Key is not present in the index.
+ TypeError
+ If key is listlike or otherwise not hashable.
+ """
+ orig_key = key
+
+ self._check_indexing_error(key)
+
+ if is_valid_na_for_dtype(key, self.dtype):
+ key = NaT
+
+ elif isinstance(key, str):
+ try:
+ parsed, reso = self._parse_with_reso(key)
+ except ValueError as err:
+ # A string with invalid format
+ raise KeyError(f"Cannot interpret '{key}' as period") from err
+
+ if self._can_partial_date_slice(reso):
+ try:
+ return self._partial_date_slice(reso, parsed)
+ except KeyError as err:
+ raise KeyError(key) from err
+
+ if reso == self._resolution_obj:
+ # the reso < self._resolution_obj case goes
+ # through _get_string_slice
+ key = self._cast_partial_indexing_scalar(parsed)
+ else:
+ raise KeyError(key)
+
+ elif isinstance(key, Period):
+ self._disallow_mismatched_indexing(key)
+
+ elif isinstance(key, datetime):
+ key = self._cast_partial_indexing_scalar(key)
+
+ else:
+ # in particular integer, which Period constructor would cast to string
+ raise KeyError(key)
+
+ try:
+ return Index.get_loc(self, key)
+ except KeyError as err:
+ raise KeyError(orig_key) from err
+
+ def _disallow_mismatched_indexing(self, key: Period) -> None:
+ if key._dtype != self.dtype:
+ raise KeyError(key)
+
+ def _cast_partial_indexing_scalar(self, label: datetime) -> Period:
+ try:
+ period = Period(label, freq=self.freq)
+ except ValueError as err:
+ # we cannot construct the Period
+ raise KeyError(label) from err
+ return period
+
+ @doc(DatetimeIndexOpsMixin._maybe_cast_slice_bound)
+ def _maybe_cast_slice_bound(self, label, side: str):
+ if isinstance(label, datetime):
+ label = self._cast_partial_indexing_scalar(label)
+
+ return super()._maybe_cast_slice_bound(label, side)
+
+ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime):
+ freq = OFFSET_TO_PERIOD_FREQSTR.get(reso.attr_abbrev, reso.attr_abbrev)
+ iv = Period(parsed, freq=freq)
+ return (iv.asfreq(self.freq, how="start"), iv.asfreq(self.freq, how="end"))
+
+ @doc(DatetimeIndexOpsMixin.shift)
+ def shift(self, periods: int = 1, freq=None) -> Self:
+ if freq is not None:
+ raise TypeError(
+ f"`freq` argument is not supported for {type(self).__name__}.shift"
+ )
+ return self + periods
+
+
+@set_module("pandas")
+def period_range(
+ start=None,
+ end=None,
+ periods: int | None = None,
+ freq=None,
+ name: Hashable | None = None,
+) -> PeriodIndex:
+ """
+ Return a fixed frequency PeriodIndex.
+
+ The day (calendar) is the default frequency.
+
+ Parameters
+ ----------
+ start : str, datetime, date, pandas.Timestamp, or period-like, default None
+ Left bound for generating periods.
+ end : str, datetime, date, pandas.Timestamp, or period-like, default None
+ Right bound for generating periods.
+ periods : int, default None
+ Number of periods to generate.
+ freq : str or DateOffset, optional
+ Frequency alias. By default the freq is taken from `start` or `end`
+ if those are Period objects. Otherwise, the default is ``"D"`` for
+ daily frequency.
+ name : str, default None
+ Name of the resulting PeriodIndex.
+
+ Returns
+ -------
+ PeriodIndex
+ A PeriodIndex of fixed frequency periods.
+
+ See Also
+ --------
+ date_range : Returns a fixed frequency DatetimeIndex.
+ Period : Represents a period of time.
+ PeriodIndex : Immutable ndarray holding ordinal values indicating regular periods
+ in time.
+
+ Notes
+ -----
+ Of the three parameters: ``start``, ``end``, and ``periods``, exactly two
+ must be specified.
+
+ To learn more about the frequency strings, please see
+ :ref:`this link`.
+
+ Examples
+ --------
+ >>> pd.period_range(start="2017-01-01", end="2018-01-01", freq="M")
+ PeriodIndex(['2017-01', '2017-02', '2017-03', '2017-04', '2017-05', '2017-06',
+ '2017-07', '2017-08', '2017-09', '2017-10', '2017-11', '2017-12',
+ '2018-01'],
+ dtype='period[M]')
+
+ If ``start`` or ``end`` are ``Period`` objects, they will be used as anchor
+ endpoints for a ``PeriodIndex`` with frequency matching that of the
+ ``period_range`` constructor.
+
+ >>> pd.period_range(
+ ... start=pd.Period("2017Q1", freq="Q"),
+ ... end=pd.Period("2017Q2", freq="Q"),
+ ... freq="M",
+ ... )
+ PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'],
+ dtype='period[M]')
+ """
+ if com.count_not_none(start, end, periods) != 2:
+ raise ValueError(
+ "Of the three parameters: start, end, and periods, "
+ "exactly two must be specified"
+ )
+ if freq is None and (not isinstance(start, Period) and not isinstance(end, Period)):
+ freq = "D"
+
+ data, freq = PeriodArray._generate_range(start, end, periods, freq)
+ dtype = PeriodDtype(freq)
+ data = PeriodArray(data, dtype=dtype)
+ return PeriodIndex(data, name=name, copy=False)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/range.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/range.py
new file mode 100644
index 0000000000000000000000000000000000000000..165edb00f084e04c45b0fa692c03f411b0717625
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/range.py
@@ -0,0 +1,1583 @@
+from __future__ import annotations
+
+from collections.abc import (
+ Callable,
+ Hashable,
+ Iterator,
+)
+from datetime import timedelta
+import operator
+from sys import getsizeof
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ Self,
+ cast,
+ overload,
+)
+
+import numpy as np
+
+from pandas._libs import (
+ index as libindex,
+ lib,
+)
+from pandas._libs.lib import no_default
+from pandas.compat.numpy import function as nv
+from pandas.util._decorators import (
+ cache_readonly,
+ set_module,
+)
+
+from pandas.core.dtypes.base import ExtensionDtype
+from pandas.core.dtypes.common import (
+ ensure_platform_int,
+ ensure_python_int,
+ is_float,
+ is_integer,
+ is_scalar,
+ is_signed_integer_dtype,
+)
+from pandas.core.dtypes.generic import ABCTimedeltaIndex
+
+from pandas.core import ops
+import pandas.core.common as com
+from pandas.core.construction import extract_array
+from pandas.core.indexers import check_array_indexer
+import pandas.core.indexes.base as ibase
+from pandas.core.indexes.base import (
+ Index,
+ maybe_extract_name,
+)
+from pandas.core.ops.common import unpack_zerodim_and_defer
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ Axis,
+ Dtype,
+ JoinHow,
+ NaPosition,
+ NumpySorter,
+ npt,
+ )
+
+ from pandas import Series
+
+_empty_range = range(0)
+_dtype_int64 = np.dtype(np.int64)
+
+
+def min_fitting_element(start: int, step: int, lower_limit: int) -> int:
+ """Returns the smallest element greater than or equal to the limit"""
+ no_steps = -(-(lower_limit - start) // abs(step))
+ return start + abs(step) * no_steps
+
+
+@set_module("pandas")
+class RangeIndex(Index):
+ """
+ Immutable Index implementing a monotonic integer range.
+
+ RangeIndex is a memory-saving special case of an Index limited to representing
+ monotonic ranges with a 64-bit dtype. Using RangeIndex may in some instances
+ improve computing speed.
+
+ This is the default index type used
+ by DataFrame and Series when no explicit index is provided by the user.
+
+ Parameters
+ ----------
+ start : int, range, or other RangeIndex instance, default None
+ If int and "stop" is not given, interpreted as "stop" instead.
+ stop : int, default None
+ The end value of the range (exclusive).
+ step : int, default None
+ The step size of the range.
+ dtype : np.int64, default None
+ Unused, accepted for homogeneity with other index types.
+ copy : bool, default False
+ Unused, accepted for homogeneity with other index types.
+ name : object, optional
+ Name to be stored in the index.
+
+ Attributes
+ ----------
+ start
+ stop
+ step
+
+ Methods
+ -------
+ from_range
+
+ See Also
+ --------
+ Index : The base pandas Index type.
+
+ Examples
+ --------
+ >>> list(pd.RangeIndex(5))
+ [0, 1, 2, 3, 4]
+
+ >>> list(pd.RangeIndex(-2, 4))
+ [-2, -1, 0, 1, 2, 3]
+
+ >>> list(pd.RangeIndex(0, 10, 2))
+ [0, 2, 4, 6, 8]
+
+ >>> list(pd.RangeIndex(2, -10, -3))
+ [2, -1, -4, -7]
+
+ >>> list(pd.RangeIndex(0))
+ []
+
+ >>> list(pd.RangeIndex(1, 0))
+ []
+ """
+
+ _typ = "rangeindex"
+ _dtype_validation_metadata = (is_signed_integer_dtype, "signed integer")
+ _range: range
+ _values: np.ndarray
+
+ @property
+ def _engine_type(self) -> type[libindex.Int64Engine]:
+ return libindex.Int64Engine
+
+ # --------------------------------------------------------------------
+ # Constructors
+
+ def __new__(
+ cls,
+ start=None,
+ stop=None,
+ step=None,
+ dtype: Dtype | None = None,
+ copy: bool = False,
+ name: Hashable | None = None,
+ ) -> Self:
+ cls._validate_dtype(dtype)
+ name = maybe_extract_name(name, start, cls)
+
+ # RangeIndex
+ if isinstance(start, cls):
+ return start.copy(name=name)
+ elif isinstance(start, range):
+ return cls._simple_new(start, name=name)
+
+ # validate the arguments
+ if com.all_none(start, stop, step):
+ raise TypeError("RangeIndex(...) must be called with integers")
+
+ start = ensure_python_int(start) if start is not None else 0
+
+ if stop is None:
+ start, stop = 0, start
+ else:
+ stop = ensure_python_int(stop)
+
+ step = ensure_python_int(step) if step is not None else 1
+ if step == 0:
+ raise ValueError("Step must not be zero")
+
+ rng = range(start, stop, step)
+ return cls._simple_new(rng, name=name)
+
+ @classmethod
+ def from_range(cls, data: range, name=None, dtype: Dtype | None = None) -> Self:
+ """
+ Create :class:`pandas.RangeIndex` from a ``range`` object.
+
+ This method provides a way to create a :class:`pandas.RangeIndex` directly
+ from a Python ``range`` object. The resulting :class:`RangeIndex` will have
+ the same start, stop, and step values as the input ``range`` object.
+ It is particularly useful for constructing indices in an efficient and
+ memory-friendly manner.
+
+ Parameters
+ ----------
+ data : range
+ The range object to be converted into a RangeIndex.
+ name : str, default None
+ Name to be stored in the index.
+ dtype : Dtype or None
+ Data type for the RangeIndex. If None, the default integer type will
+ be used.
+
+ Returns
+ -------
+ RangeIndex
+
+ See Also
+ --------
+ RangeIndex : Immutable Index implementing a monotonic integer range.
+ Index : Immutable sequence used for indexing and alignment.
+
+ Examples
+ --------
+ >>> pd.RangeIndex.from_range(range(5))
+ RangeIndex(start=0, stop=5, step=1)
+
+ >>> pd.RangeIndex.from_range(range(2, -10, -3))
+ RangeIndex(start=2, stop=-10, step=-3)
+ """
+ if not isinstance(data, range):
+ raise TypeError(
+ f"{cls.__name__}(...) must be called with object coercible to a "
+ f"range, {data!r} was passed"
+ )
+ cls._validate_dtype(dtype)
+ return cls._simple_new(data, name=name)
+
+ # error: Argument 1 of "_simple_new" is incompatible with supertype "Index";
+ # supertype defines the argument type as
+ # "Union[ExtensionArray, ndarray[Any, Any]]" [override]
+ @classmethod
+ def _simple_new( # type: ignore[override]
+ cls, values: range, name: Hashable | None = None
+ ) -> Self:
+ result = object.__new__(cls)
+
+ assert isinstance(values, range)
+
+ result._range = values
+ result._name = name
+ result._cache = {}
+ result._reset_identity()
+ result._references = None
+ return result
+
+ @classmethod
+ def _validate_dtype(cls, dtype: Dtype | None) -> None:
+ if dtype is None:
+ return
+
+ validation_func, expected = cls._dtype_validation_metadata
+ if not validation_func(dtype):
+ raise ValueError(
+ f"Incorrect `dtype` passed: expected {expected}, received {dtype}"
+ )
+
+ # --------------------------------------------------------------------
+
+ # error: Return type "Type[Index]" of "_constructor" incompatible with return
+ # type "Type[RangeIndex]" in supertype "Index"
+ @cache_readonly
+ def _constructor(self) -> type[Index]: # type: ignore[override]
+ """return the class to use for construction"""
+ return Index
+
+ # error: Signature of "_data" incompatible with supertype "Index"
+ @cache_readonly
+ def _data(self) -> np.ndarray: # type: ignore[override]
+ """
+ An int array that for performance reasons is created only when needed.
+
+ The constructed array is saved in ``_cache``.
+ """
+ return np.arange(self.start, self.stop, self.step, dtype=np.int64)
+
+ def _get_data_as_items(self) -> list[tuple[str, int]]:
+ """return a list of tuples of start, stop, step"""
+ rng = self._range
+ return [("start", rng.start), ("stop", rng.stop), ("step", rng.step)]
+
+ def __reduce__(self):
+ d = {"name": self._name}
+ d.update(dict(self._get_data_as_items()))
+ return ibase._new_Index, (type(self), d), None
+
+ # --------------------------------------------------------------------
+ # Rendering Methods
+
+ def _format_attrs(self):
+ """
+ Return a list of tuples of the (attr, formatted_value)
+ """
+ attrs = cast("list[tuple[str, str | int]]", self._get_data_as_items())
+ if self._name is not None:
+ attrs.append(("name", ibase.default_pprint(self._name)))
+ return attrs
+
+ def _format_with_header(self, *, header: list[str], na_rep: str) -> list[str]:
+ # Equivalent to Index implementation, but faster
+ if not len(self._range):
+ return header
+ first_val_str = str(self._range[0])
+ last_val_str = str(self._range[-1])
+ max_length = max(len(first_val_str), len(last_val_str))
+
+ return header + [f"{x:<{max_length}}" for x in self._range]
+
+ # --------------------------------------------------------------------
+
+ @property
+ def start(self) -> int:
+ """
+ The value of the `start` parameter (``0`` if this was not supplied).
+
+ This property returns the starting value of the `RangeIndex`. If the `start`
+ value is not explicitly provided during the creation of the `RangeIndex`,
+ it defaults to 0.
+
+ See Also
+ --------
+ RangeIndex : Immutable index implementing a range-based index.
+ RangeIndex.stop : Returns the stop value of the `RangeIndex`.
+ RangeIndex.step : Returns the step value of the `RangeIndex`.
+
+ Examples
+ --------
+ >>> idx = pd.RangeIndex(5)
+ >>> idx.start
+ 0
+
+ >>> idx = pd.RangeIndex(2, -10, -3)
+ >>> idx.start
+ 2
+ """
+ # GH 25710
+ return self._range.start
+
+ @property
+ def stop(self) -> int:
+ """
+ The value of the `stop` parameter.
+
+ This property returns the `stop` value of the RangeIndex, which defines the
+ upper (or lower, in case of negative steps) bound of the index range. The
+ `stop` value is exclusive, meaning the RangeIndex includes values up to but
+ not including this value.
+
+ See Also
+ --------
+ RangeIndex : Immutable index representing a range of integers.
+ RangeIndex.start : The start value of the RangeIndex.
+ RangeIndex.step : The step size between elements in the RangeIndex.
+
+ Examples
+ --------
+ >>> idx = pd.RangeIndex(5)
+ >>> idx.stop
+ 5
+
+ >>> idx = pd.RangeIndex(2, -10, -3)
+ >>> idx.stop
+ -10
+ """
+ return self._range.stop
+
+ @property
+ def step(self) -> int:
+ """
+ The value of the `step` parameter (``1`` if this was not supplied).
+
+ The ``step`` parameter determines the increment (or decrement in the case
+ of negative values) between consecutive elements in the ``RangeIndex``.
+
+ See Also
+ --------
+ RangeIndex : Immutable index implementing a range-based index.
+ RangeIndex.stop : Returns the stop value of the RangeIndex.
+ RangeIndex.start : Returns the start value of the RangeIndex.
+
+ Examples
+ --------
+ >>> idx = pd.RangeIndex(5)
+ >>> idx.step
+ 1
+
+ >>> idx = pd.RangeIndex(2, -10, -3)
+ >>> idx.step
+ -3
+
+ Even if :class:`pandas.RangeIndex` is empty, ``step`` is still ``1`` if
+ not supplied.
+
+ >>> idx = pd.RangeIndex(1, 0)
+ >>> idx.step
+ 1
+ """
+ # GH 25710
+ return self._range.step
+
+ @cache_readonly
+ def nbytes(self) -> int:
+ """
+ Return the number of bytes in the underlying data.
+ """
+ rng = self._range
+ return getsizeof(rng) + sum(
+ getsizeof(getattr(rng, attr_name))
+ for attr_name in ["start", "stop", "step"]
+ )
+
+ def memory_usage(self, deep: bool = False) -> int:
+ """
+ Memory usage of my values
+
+ Parameters
+ ----------
+ deep : bool
+ Introspect the data deeply, interrogate
+ `object` dtypes for system-level memory consumption
+
+ Returns
+ -------
+ bytes used
+
+ Notes
+ -----
+ Memory usage does not include memory consumed by elements that
+ are not components of the array if deep=False
+
+ See Also
+ --------
+ numpy.ndarray.nbytes
+ """
+ return self.nbytes
+
+ @property
+ def dtype(self) -> np.dtype:
+ return _dtype_int64
+
+ @property
+ def is_unique(self) -> bool:
+ """return if the index has unique values"""
+ return True
+
+ @cache_readonly
+ def is_monotonic_increasing(self) -> bool:
+ return self._range.step > 0 or len(self) <= 1
+
+ @cache_readonly
+ def is_monotonic_decreasing(self) -> bool:
+ return self._range.step < 0 or len(self) <= 1
+
+ def __contains__(self, key: Any) -> bool:
+ hash(key)
+ try:
+ key = ensure_python_int(key)
+ except (TypeError, OverflowError):
+ return False
+ return key in self._range
+
+ @property
+ def inferred_type(self) -> str:
+ return "integer"
+
+ # --------------------------------------------------------------------
+ # Indexing Methods
+
+ def get_loc(self, key) -> int:
+ """
+ Get integer location for requested label.
+
+ Parameters
+ ----------
+ key : int or float
+ Label to locate. Integer-like floats (e.g. 3.0) are accepted and
+ treated as the corresponding integer. Non-integer floats and other
+ non-integer labels are not valid and will raise KeyError or
+ InvalidIndexError.
+
+ Returns
+ -------
+ int
+ Integer location of the label within the RangeIndex.
+
+ Raises
+ ------
+ KeyError
+ If the label is not present in the RangeIndex or the label is a
+ non-integer value.
+ InvalidIndexError
+ If the label is of an invalid type for the RangeIndex.
+
+ See Also
+ --------
+ RangeIndex.get_slice_bound : Calculate slice bound that corresponds to
+ given label.
+ RangeIndex.get_indexer : Computes indexer and mask for new index given
+ the current index.
+ RangeIndex.get_non_unique : Returns indexer and masks for new index given
+ the current index.
+ RangeIndex.get_indexer_for : Returns an indexer even when non-unique.
+
+ Examples
+ --------
+ >>> idx = pd.RangeIndex(5)
+ >>> idx.get_loc(3)
+ 3
+
+ >>> idx = pd.RangeIndex(2, 10, 2) # values [2, 4, 6, 8]
+ >>> idx.get_loc(6)
+ 2
+ """
+ if is_integer(key) or (is_float(key) and key.is_integer()):
+ new_key = int(key)
+ try:
+ return self._range.index(new_key)
+ except ValueError as err:
+ raise KeyError(key) from err
+ if isinstance(key, Hashable):
+ raise KeyError(key)
+ self._check_indexing_error(key)
+ raise KeyError(key)
+
+ def _get_indexer(
+ self,
+ target: Index,
+ method: str | None = None,
+ limit: int | None = None,
+ tolerance=None,
+ ) -> npt.NDArray[np.intp]:
+ if com.any_not_none(method, tolerance, limit):
+ return super()._get_indexer(
+ target, method=method, tolerance=tolerance, limit=limit
+ )
+
+ if self.step > 0:
+ start, stop, step = self.start, self.stop, self.step
+ else:
+ # GH 28678: work on reversed range for simplicity
+ reverse = self._range[::-1]
+ start, stop, step = reverse.start, reverse.stop, reverse.step
+
+ target_array = np.asarray(target)
+ locs = target_array - start
+ valid = (locs % step == 0) & (locs >= 0) & (target_array < stop)
+ locs[~valid] = -1
+ locs[valid] = locs[valid] / step
+
+ if step != self.step:
+ # We reversed this range: transform to original locs
+ locs[valid] = len(self) - 1 - locs[valid]
+ return ensure_platform_int(locs)
+
+ @cache_readonly
+ def _should_fallback_to_positional(self) -> bool:
+ """
+ Should an integer key be treated as positional?
+ """
+ return False
+
+ # --------------------------------------------------------------------
+
+ def tolist(self) -> list[int]:
+ return list(self._range)
+
+ def __iter__(self) -> Iterator[int]:
+ """
+ Return an iterator of the values.
+
+ Returns
+ -------
+ iterator
+ An iterator yielding ints from the RangeIndex.
+
+ Examples
+ --------
+ >>> idx = pd.RangeIndex(3)
+ >>> for x in idx:
+ ... print(x)
+ 0
+ 1
+ 2
+ """
+ yield from self._range
+
+ def _shallow_copy(self, values, name: Hashable = no_default):
+ """
+ Create a new RangeIndex with the same class as the caller, don't copy the
+ data, use the same object attributes with passed in attributes taking
+ precedence.
+
+ *this is an internal non-public method*
+
+ Parameters
+ ----------
+ values : the values to create the new RangeIndex, optional
+ name : Label, defaults to self.name
+ """
+ name = self._name if name is no_default else name
+
+ if values.dtype.kind == "f":
+ return Index(values, name=name, dtype=np.float64, copy=False)
+ if values.dtype.kind == "i" and values.ndim == 1:
+ # GH 46675 & 43885: If values is equally spaced, return a
+ # more memory-compact RangeIndex instead of Index with 64-bit dtype
+ if len(values) == 1:
+ start = values[0]
+ new_range = range(start, start + self.step, self.step)
+ return type(self)._simple_new(new_range, name=name)
+ maybe_range = ibase.maybe_sequence_to_range(values)
+ if isinstance(maybe_range, range):
+ return type(self)._simple_new(maybe_range, name=name)
+ return self._constructor._simple_new(values, name=name)
+
+ def _view(self) -> Self:
+ result = type(self)._simple_new(self._range, name=self._name)
+ result._cache = self._cache
+ return result
+
+ def _wrap_reindex_result(self, target, indexer, preserve_names: bool):
+ if not isinstance(target, type(self)) and target.dtype.kind == "i":
+ target = self._shallow_copy(target._values, name=target.name)
+ return super()._wrap_reindex_result(target, indexer, preserve_names)
+
+ def copy(self, name: Hashable | None = None, deep: bool = False) -> Self:
+ """
+ Make a copy of this object.
+
+ Name is set on the new object.
+
+ Parameters
+ ----------
+ name : Label, optional
+ Set name for new object.
+ deep : bool, default False
+ If True attempts to make a deep copy of the RangeIndex.
+ Else makes a shallow copy.
+
+ Returns
+ -------
+ RangeIndex
+ RangeIndex refer to new object which is a copy of this object.
+
+ See Also
+ --------
+ RangeIndex.delete: Make new RangeIndex with passed location(-s) deleted.
+ RangeIndex.drop: Make new RangeIndex with passed list of labels deleted.
+
+ Notes
+ -----
+ In most cases, there should be no functional difference from using
+ ``deep``, but if ``deep`` is passed it will attempt to deepcopy.
+
+ Examples
+ --------
+ >>> idx = pd.RangeIndex(3)
+ >>> new_idx = idx.copy()
+ >>> idx is new_idx
+ False
+ """
+ name = self._validate_names(name=name, deep=deep)[0]
+ new_index = self._rename(name=name)
+ return new_index
+
+ def _minmax(self, meth: Literal["min", "max"]) -> int | float:
+ no_steps = len(self) - 1
+ if no_steps == -1:
+ return np.nan
+ elif (meth == "min" and self.step > 0) or (meth == "max" and self.step < 0):
+ return self.start
+
+ return self.start + self.step * no_steps
+
+ def min(self, axis=None, skipna: bool = True, *args, **kwargs) -> int | float:
+ """The minimum value of the RangeIndex"""
+ nv.validate_minmax_axis(axis)
+ nv.validate_min(args, kwargs)
+ return self._minmax("min")
+
+ def max(self, axis=None, skipna: bool = True, *args, **kwargs) -> int | float:
+ """The maximum value of the RangeIndex"""
+ nv.validate_minmax_axis(axis)
+ nv.validate_max(args, kwargs)
+ return self._minmax("max")
+
+ def _argminmax(
+ self,
+ meth: Literal["min", "max"],
+ axis=None,
+ skipna: bool = True,
+ ) -> int:
+ nv.validate_minmax_axis(axis)
+ if len(self) == 0:
+ return getattr(super(), f"arg{meth}")(
+ axis=axis,
+ skipna=skipna,
+ )
+ elif meth == "min":
+ if self.step > 0:
+ return 0
+ else:
+ return len(self) - 1
+ elif meth == "max":
+ if self.step > 0:
+ return len(self) - 1
+ else:
+ return 0
+ else:
+ raise ValueError(f"{meth=} must be max or min")
+
+ def argmin(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:
+ nv.validate_argmin(args, kwargs)
+ return self._argminmax("min", axis=axis, skipna=skipna)
+
+ def argmax(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:
+ nv.validate_argmax(args, kwargs)
+ return self._argminmax("max", axis=axis, skipna=skipna)
+
+ def argsort(self, *args, **kwargs) -> npt.NDArray[np.intp]:
+ """
+ Returns the indices that would sort the index and its
+ underlying data.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+
+ See Also
+ --------
+ numpy.ndarray.argsort
+ """
+ ascending = kwargs.pop("ascending", True) # EA compat
+ kwargs.pop("kind", None) # e.g. "mergesort" is irrelevant
+ nv.validate_argsort(args, kwargs)
+
+ start, stop, step = None, None, None
+ if self._range.step > 0:
+ if ascending:
+ start = len(self)
+ else:
+ start, stop, step = len(self) - 1, -1, -1
+ elif ascending:
+ start, stop, step = len(self) - 1, -1, -1
+ else:
+ start = len(self)
+
+ return np.arange(start, stop, step, dtype=np.intp)
+
+ def factorize(
+ self,
+ sort: bool = False,
+ use_na_sentinel: bool = True,
+ ) -> tuple[npt.NDArray[np.intp], RangeIndex]:
+ if sort and self.step < 0:
+ codes = np.arange(len(self) - 1, -1, -1, dtype=np.intp)
+ uniques = self[::-1]
+ else:
+ codes = np.arange(len(self), dtype=np.intp)
+ uniques = self
+ return codes, uniques
+
+ def equals(self, other: object) -> bool:
+ """
+ Determines if two Index objects contain the same elements.
+ """
+ if isinstance(other, RangeIndex):
+ return self._range == other._range
+ return super().equals(other)
+
+ @overload
+ def sort_values(
+ self,
+ *,
+ return_indexer: Literal[False] = ...,
+ ascending: bool = ...,
+ na_position: NaPosition = ...,
+ key: Callable | None = ...,
+ ) -> Self: ...
+
+ @overload
+ def sort_values(
+ self,
+ *,
+ return_indexer: Literal[True],
+ ascending: bool = ...,
+ na_position: NaPosition = ...,
+ key: Callable | None = ...,
+ ) -> tuple[Self, np.ndarray]: ...
+
+ @overload
+ def sort_values(
+ self,
+ *,
+ return_indexer: bool = ...,
+ ascending: bool = ...,
+ na_position: NaPosition = ...,
+ key: Callable | None = ...,
+ ) -> Self | tuple[Self, np.ndarray]: ...
+
+ def sort_values(
+ self,
+ *,
+ return_indexer: bool = False,
+ ascending: bool = True,
+ na_position: NaPosition = "last",
+ key: Callable | None = None,
+ ) -> Self | tuple[Self, np.ndarray]:
+ if key is not None:
+ return super().sort_values(
+ return_indexer=return_indexer,
+ ascending=ascending,
+ na_position=na_position,
+ key=key,
+ )
+ else:
+ sorted_index = self
+ inverse_indexer = False
+ if ascending:
+ if self.step < 0:
+ sorted_index = self[::-1]
+ inverse_indexer = True
+ elif self.step > 0:
+ sorted_index = self[::-1]
+ inverse_indexer = True
+
+ if return_indexer:
+ if inverse_indexer:
+ indexer = np.arange(len(self) - 1, -1, -1, dtype=np.intp)
+ else:
+ indexer = np.arange(len(self), dtype=np.intp)
+ return sorted_index, indexer
+ else:
+ return sorted_index
+
+ # --------------------------------------------------------------------
+ # Set Operations
+
+ def _intersection(self, other: Index, sort: bool = False):
+ # caller is responsible for checking self and other are both non-empty
+
+ if not isinstance(other, RangeIndex):
+ return super()._intersection(other, sort=sort)
+
+ first = self._range[::-1] if self.step < 0 else self._range
+ second = other._range[::-1] if other.step < 0 else other._range
+
+ # check whether intervals intersect
+ # deals with in- and decreasing ranges
+ int_low = max(first.start, second.start)
+ int_high = min(first.stop, second.stop)
+ if int_high <= int_low:
+ return self._simple_new(_empty_range)
+
+ # Method hint: linear Diophantine equation
+ # solve intersection problem
+ # performance hint: for identical step sizes, could use
+ # cheaper alternative
+ gcd, s, _ = self._extended_gcd(first.step, second.step)
+
+ # check whether element sets intersect
+ if (first.start - second.start) % gcd:
+ return self._simple_new(_empty_range)
+
+ # calculate parameters for the RangeIndex describing the
+ # intersection disregarding the lower bounds
+ tmp_start = first.start + (second.start - first.start) * first.step // gcd * s
+ new_step = first.step * second.step // gcd
+
+ # adjust index to limiting interval
+ new_start = min_fitting_element(tmp_start, new_step, int_low)
+ new_range = range(new_start, int_high, new_step)
+
+ if (self.step < 0 and other.step < 0) is not (new_range.step < 0):
+ new_range = new_range[::-1]
+
+ return self._simple_new(new_range)
+
+ def _extended_gcd(self, a: int, b: int) -> tuple[int, int, int]:
+ """
+ Extended Euclidean algorithms to solve Bezout's identity:
+ a*x + b*y = gcd(x, y)
+ Finds one particular solution for x, y: s, t
+ Returns: gcd, s, t
+ """
+ s, old_s = 0, 1
+ t, old_t = 1, 0
+ r, old_r = b, a
+ while r:
+ quotient = old_r // r
+ old_r, r = r, old_r - quotient * r
+ old_s, s = s, old_s - quotient * s
+ old_t, t = t, old_t - quotient * t
+ return old_r, old_s, old_t
+
+ def _range_in_self(self, other: range) -> bool:
+ """Check if other range is contained in self"""
+ # https://stackoverflow.com/a/32481015
+ if not other:
+ return True
+ if not self._range:
+ return False
+ if len(other) > 1 and other.step % self._range.step:
+ return False
+ return other.start in self._range and other[-1] in self._range
+
+ def _union(self, other: Index, sort: bool | None):
+ """
+ Form the union of two Index objects and sorts if possible
+
+ Parameters
+ ----------
+ other : Index or array-like
+
+ sort : bool or None, default None
+ Whether to sort (monotonically increasing) the resulting index.
+ ``sort=None|True`` returns a ``RangeIndex`` if possible or a sorted
+ ``Index`` with an int64 dtype if not.
+ ``sort=False`` can return a ``RangeIndex`` if self is monotonically
+ increasing and other is fully contained in self. Otherwise, returns
+ an unsorted ``Index`` with an int64 dtype.
+
+ Returns
+ -------
+ union : Index
+ """
+ if isinstance(other, RangeIndex):
+ if sort in (None, True) or (
+ sort is False and self.step > 0 and self._range_in_self(other._range)
+ ):
+ # GH 47557: Can still return a RangeIndex
+ # if other range in self and sort=False
+ start_s, step_s = self.start, self.step
+ end_s = self.start + self.step * (len(self) - 1)
+ start_o, step_o = other.start, other.step
+ end_o = other.start + other.step * (len(other) - 1)
+ if self.step < 0:
+ start_s, step_s, end_s = end_s, -step_s, start_s
+ if other.step < 0:
+ start_o, step_o, end_o = end_o, -step_o, start_o
+ if len(self) == 1 and len(other) == 1:
+ step_s = step_o = abs(self.start - other.start)
+ elif len(self) == 1:
+ step_s = step_o
+ elif len(other) == 1:
+ step_o = step_s
+ start_r = min(start_s, start_o)
+ end_r = max(end_s, end_o)
+ if step_o == step_s:
+ if (
+ (start_s - start_o) % step_s == 0
+ and (start_s - end_o) <= step_s
+ and (start_o - end_s) <= step_s
+ ):
+ return type(self)(start_r, end_r + step_s, step_s)
+ if (
+ (step_s % 2 == 0)
+ and (abs(start_s - start_o) == step_s / 2)
+ and (abs(end_s - end_o) == step_s / 2)
+ ):
+ # e.g. range(0, 10, 2) and range(1, 11, 2)
+ # but not range(0, 20, 4) and range(1, 21, 4) GH#44019
+ return type(self)(start_r, end_r + step_s / 2, step_s / 2)
+
+ elif step_o % step_s == 0:
+ if (
+ (start_o - start_s) % step_s == 0
+ and (start_o + step_s >= start_s)
+ and (end_o - step_s <= end_s)
+ ):
+ return type(self)(start_r, end_r + step_s, step_s)
+ elif step_s % step_o == 0:
+ if (
+ (start_s - start_o) % step_o == 0
+ and (start_s + step_o >= start_o)
+ and (end_s - step_o <= end_o)
+ ):
+ return type(self)(start_r, end_r + step_o, step_o)
+
+ return super()._union(other, sort=sort)
+
+ def _difference(self, other, sort=None):
+ # optimized set operation if we have another RangeIndex
+ self._validate_sort_keyword(sort)
+ self._assert_can_do_setop(other)
+ other, result_name = self._convert_can_do_setop(other)
+
+ if not isinstance(other, RangeIndex):
+ return super()._difference(other, sort=sort)
+
+ if sort is not False and self.step < 0:
+ return self[::-1]._difference(other)
+
+ res_name = ops.get_op_result_name(self, other)
+
+ first = self._range[::-1] if self.step < 0 else self._range
+ overlap = self.intersection(other)
+ if overlap.step < 0:
+ overlap = overlap[::-1]
+
+ if len(overlap) == 0:
+ return self.rename(name=res_name)
+ if len(overlap) == len(self):
+ return self[:0].rename(res_name)
+
+ # overlap.step will always be a multiple of self.step (see _intersection)
+
+ if len(overlap) == 1:
+ if overlap[0] == self[0]:
+ return self[1:]
+
+ elif overlap[0] == self[-1]:
+ return self[:-1]
+
+ elif len(self) == 3 and overlap[0] == self[1]:
+ return self[::2]
+
+ else:
+ return super()._difference(other, sort=sort)
+
+ elif len(overlap) == 2 and overlap[0] == first[0] and overlap[-1] == first[-1]:
+ # e.g. range(-8, 20, 7) and range(13, -9, -3)
+ return self[1:-1]
+
+ if overlap.step == first.step:
+ if overlap[0] == first.start:
+ # The difference is everything after the intersection
+ new_rng = range(overlap[-1] + first.step, first.stop, first.step)
+ elif overlap[-1] == first[-1]:
+ # The difference is everything before the intersection
+ new_rng = range(first.start, overlap[0], first.step)
+ elif overlap._range == first[1:-1]:
+ # e.g. range(4) and range(1, 3)
+ step = len(first) - 1
+ new_rng = first[::step]
+ else:
+ # The difference is not range-like
+ # e.g. range(1, 10, 1) and range(3, 7, 1)
+ return super()._difference(other, sort=sort)
+
+ else:
+ # We must have len(self) > 1, bc we ruled out above
+ # len(overlap) == 0 and len(overlap) == len(self)
+ assert len(self) > 1
+
+ if overlap.step == first.step * 2:
+ if overlap[0] == first[0] and overlap[-1] in (first[-1], first[-2]):
+ # e.g. range(1, 10, 1) and range(1, 10, 2)
+ new_rng = first[1::2]
+
+ elif overlap[0] == first[1] and overlap[-1] in (first[-1], first[-2]):
+ # e.g. range(1, 10, 1) and range(2, 10, 2)
+ new_rng = first[::2]
+
+ else:
+ # We can get here with e.g. range(20) and range(0, 10, 2)
+ return super()._difference(other, sort=sort)
+
+ else:
+ # e.g. range(10) and range(0, 10, 3)
+ return super()._difference(other, sort=sort)
+
+ if first is not self._range:
+ new_rng = new_rng[::-1]
+ new_index = type(self)._simple_new(new_rng, name=res_name)
+
+ return new_index
+
+ def symmetric_difference(
+ self, other, result_name: Hashable | None = None, sort=None
+ ) -> Index:
+ if not isinstance(other, RangeIndex) or sort is not None:
+ return super().symmetric_difference(other, result_name, sort)
+
+ left = self.difference(other)
+ right = other.difference(self)
+ result = left.union(right)
+
+ if result_name is not None:
+ result = result.rename(result_name)
+ return result
+
+ def _join_empty(
+ self, other: Index, how: JoinHow, sort: bool
+ ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ if not isinstance(other, RangeIndex) and other.dtype.kind == "i":
+ other = self._shallow_copy(other._values, name=other.name)
+ return super()._join_empty(other, how=how, sort=sort)
+
+ def _join_monotonic(
+ self, other: Index, how: JoinHow = "left"
+ ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ # This currently only gets called for the monotonic increasing case
+ if not isinstance(other, type(self)):
+ maybe_ri = self._shallow_copy(other._values, name=other.name)
+ if not isinstance(maybe_ri, type(self)):
+ return super()._join_monotonic(other, how=how)
+ other = maybe_ri
+
+ if self.equals(other):
+ ret_index = other if how == "right" else self
+ return ret_index, None, None
+
+ if how == "left":
+ join_index = self
+ lidx = None
+ ridx = other.get_indexer(join_index)
+ elif how == "right":
+ join_index = other
+ lidx = self.get_indexer(join_index)
+ ridx = None
+ elif how == "inner":
+ join_index = self.intersection(other)
+ lidx = self.get_indexer(join_index)
+ ridx = other.get_indexer(join_index)
+ elif how == "outer":
+ join_index = self.union(other)
+ lidx = self.get_indexer(join_index)
+ ridx = other.get_indexer(join_index)
+
+ lidx = None if lidx is None else ensure_platform_int(lidx)
+ ridx = None if ridx is None else ensure_platform_int(ridx)
+ return join_index, lidx, ridx
+
+ # --------------------------------------------------------------------
+
+ # error: Return type "Index" of "delete" incompatible with return type
+ # "RangeIndex" in supertype "Index"
+ def delete(self, loc) -> Index: # type: ignore[override]
+ # In some cases we can retain RangeIndex, see also
+ # DatetimeTimedeltaMixin._get_delete_Freq
+ if is_integer(loc):
+ if loc in (0, -len(self)):
+ return self[1:]
+ if loc in (-1, len(self) - 1):
+ return self[:-1]
+ if len(self) == 3 and loc in (1, -2):
+ return self[::2]
+
+ elif lib.is_list_like(loc):
+ slc = lib.maybe_indices_to_slice(np.asarray(loc, dtype=np.intp), len(self))
+
+ if isinstance(slc, slice):
+ # defer to RangeIndex._difference, which is optimized to return
+ # a RangeIndex whenever possible
+ other = self[slc]
+ return self.difference(other, sort=False)
+
+ return super().delete(loc)
+
+ def insert(self, loc: int, item) -> Index:
+ if is_integer(item) or is_float(item):
+ # We can retain RangeIndex is inserting at the beginning or end,
+ # or right in the middle.
+ if len(self) == 0 and loc == 0 and is_integer(item):
+ new_rng = range(item, item + self.step, self.step)
+ return type(self)._simple_new(new_rng, name=self._name)
+ elif len(self):
+ rng = self._range
+ if loc == 0 and item == self[0] - self.step:
+ new_rng = range(rng.start - rng.step, rng.stop, rng.step)
+ return type(self)._simple_new(new_rng, name=self._name)
+
+ elif loc == len(self) and item == self[-1] + self.step:
+ new_rng = range(rng.start, rng.stop + rng.step, rng.step)
+ return type(self)._simple_new(new_rng, name=self._name)
+
+ elif len(self) == 2 and item == self[0] + self.step / 2:
+ # e.g. inserting 1 into [0, 2]
+ step = int(self.step / 2)
+ new_rng = range(self.start, self.stop, step)
+ return type(self)._simple_new(new_rng, name=self._name)
+
+ return super().insert(loc, item)
+
+ def _concat(self, indexes: list[Index], name: Hashable) -> Index:
+ """
+ Overriding parent method for the case of all RangeIndex instances.
+
+ When all members of "indexes" are of type RangeIndex: result will be
+ RangeIndex if possible, Index with an int64 dtype otherwise. E.g.:
+ indexes = [RangeIndex(3), RangeIndex(3, 6)] -> RangeIndex(6)
+ indexes = [RangeIndex(3), RangeIndex(4, 6)] -> Index([0,1,2,4,5], dtype='int64')
+ """
+ if not all(isinstance(x, RangeIndex) for x in indexes):
+ result = super()._concat(indexes, name)
+ if result.dtype.kind == "i":
+ return self._shallow_copy(result._values)
+ return result
+
+ elif len(indexes) == 1:
+ return indexes[0]
+
+ rng_indexes = cast(list[RangeIndex], indexes)
+
+ start = step = next_ = None
+
+ # Filter the empty indexes
+ non_empty_indexes = []
+ all_same_index = True
+ prev: RangeIndex | None = None
+ for obj in rng_indexes:
+ if len(obj):
+ non_empty_indexes.append(obj)
+ if all_same_index:
+ if prev is not None:
+ all_same_index = prev.equals(obj)
+ else:
+ prev = obj
+
+ for obj in non_empty_indexes:
+ rng = obj._range
+
+ if start is None:
+ # This is set by the first non-empty index
+ start = rng.start
+ if step is None and len(rng) > 1:
+ step = rng.step
+ elif step is None:
+ # First non-empty index had only one element
+ if rng.start == start:
+ if all_same_index:
+ values = np.tile(
+ non_empty_indexes[0]._values, len(non_empty_indexes)
+ )
+ else:
+ values = np.concatenate([x._values for x in rng_indexes])
+ result = self._constructor(values, copy=False)
+ return result.rename(name)
+
+ step = rng.start - start
+
+ non_consecutive = (step != rng.step and len(rng) > 1) or (
+ next_ is not None and rng.start != next_
+ )
+ if non_consecutive:
+ if all_same_index:
+ values = np.tile(
+ non_empty_indexes[0]._values, len(non_empty_indexes)
+ )
+ else:
+ values = np.concatenate([x._values for x in rng_indexes])
+ result = self._constructor(values, copy=False)
+ return result.rename(name)
+
+ if step is not None:
+ next_ = rng[-1] + step
+
+ if non_empty_indexes:
+ # Get the stop value from "next" or alternatively
+ # from the last non-empty index
+ stop = non_empty_indexes[-1].stop if next_ is None else next_
+ if len(non_empty_indexes) == 1:
+ step = non_empty_indexes[0].step
+ return RangeIndex(start, stop, step, name=name)
+
+ # Here all "indexes" had 0 length, i.e. were empty.
+ # In this case return an empty range index.
+ return RangeIndex(_empty_range, name=name)
+
+ def __len__(self) -> int:
+ """
+ return the length of the RangeIndex
+ """
+ return len(self._range)
+
+ @property
+ def size(self) -> int:
+ return len(self)
+
+ def __getitem__(self, key):
+ """
+ Conserve RangeIndex type for scalar and slice keys.
+ """
+ key = lib.item_from_zerodim(key)
+ if key is Ellipsis:
+ key = slice(None)
+ if isinstance(key, slice):
+ return self._getitem_slice(key)
+ elif is_integer(key):
+ new_key = int(key)
+ try:
+ return self._range[new_key]
+ except IndexError as err:
+ raise IndexError(
+ f"index {key} is out of bounds for axis 0 with size {len(self)}"
+ ) from err
+ elif is_scalar(key):
+ raise IndexError(
+ "only integers, slices (`:`), "
+ "ellipsis (`...`), numpy.newaxis (`None`) "
+ "and integer or boolean "
+ "arrays are valid indices"
+ )
+ elif com.is_bool_indexer(key):
+ if isinstance(getattr(key, "dtype", None), ExtensionDtype):
+ key = key.to_numpy(dtype=bool, na_value=False)
+ else:
+ key = np.asarray(key, dtype=bool)
+ check_array_indexer(self._range, key) # type: ignore[arg-type]
+ key = np.flatnonzero(key)
+ try:
+ return self.take(key)
+ except (TypeError, ValueError):
+ return super().__getitem__(key)
+
+ def _getitem_slice(self, slobj: slice) -> Self:
+ """
+ Fastpath for __getitem__ when we know we have a slice.
+ """
+ res = self._range[slobj]
+ return type(self)._simple_new(res, name=self._name)
+
+ @unpack_zerodim_and_defer("__floordiv__")
+ def __floordiv__(self, other):
+ if is_integer(other) and other != 0:
+ if len(self) == 0 or (self.start % other == 0 and self.step % other == 0):
+ start = self.start // other
+ step = self.step // other
+ stop = start + len(self) * step
+ new_range = range(start, stop, step or 1)
+ return self._simple_new(new_range, name=self._name)
+ if len(self) == 1:
+ start = self.start // other
+ new_range = range(start, start + 1, 1)
+ return self._simple_new(new_range, name=self._name)
+
+ return super().__floordiv__(other)
+
+ # --------------------------------------------------------------------
+ # Reductions
+
+ def all(self, *args, **kwargs) -> bool:
+ return 0 not in self._range
+
+ def any(self, *args, **kwargs) -> bool:
+ return any(self._range)
+
+ # --------------------------------------------------------------------
+
+ # error: Return type "RangeIndex | Index" of "round" incompatible with
+ # return type "RangeIndex" in supertype "Index"
+ def round(self, decimals: int = 0) -> Self | Index: # type: ignore[override]
+ """
+ Round each value in the Index to the given number of decimals.
+
+ Parameters
+ ----------
+ decimals : int, optional
+ Number of decimal places to round to. If decimals is negative,
+ it specifies the number of positions to the left of the decimal point
+ e.g. ``round(11.0, -1) == 10.0``.
+
+ Returns
+ -------
+ Index or RangeIndex
+ A new Index with the rounded values.
+
+ Examples
+ --------
+ >>> import pandas as pd
+ >>> idx = pd.RangeIndex(10, 30, 10)
+ >>> idx.round(decimals=-1)
+ RangeIndex(start=10, stop=30, step=10)
+ >>> idx = pd.RangeIndex(10, 15, 1)
+ >>> idx.round(decimals=-1)
+ Index([10, 10, 10, 10, 10], dtype='int64')
+ """
+ if decimals >= 0:
+ return self.copy()
+ elif self.start % 10**-decimals == 0 and self.step % 10**-decimals == 0:
+ # e.g. RangeIndex(10, 30, 10).round(-1) doesn't need rounding
+ return self.copy()
+ else:
+ return super().round(decimals=decimals)
+
+ def _cmp_method(self, other, op):
+ if isinstance(other, RangeIndex) and self._range == other._range:
+ # Both are immutable so if ._range attr. are equal, shortcut is possible
+ return super()._cmp_method(self, op)
+ return super()._cmp_method(other, op)
+
+ def _arith_method(self, other, op):
+ """
+ Parameters
+ ----------
+ other : Any
+ op : callable that accepts 2 params
+ perform the binary op
+ """
+
+ if isinstance(other, ABCTimedeltaIndex):
+ # Defer to TimedeltaIndex implementation
+ return NotImplemented
+ elif isinstance(other, (timedelta, np.timedelta64)):
+ # GH#19333 is_integer evaluated True on timedelta64,
+ # so we need to catch these explicitly
+ return super()._arith_method(other, op)
+ elif lib.is_np_dtype(getattr(other, "dtype", None), "m"):
+ # Must be an np.ndarray; GH#22390
+ return super()._arith_method(other, op)
+
+ if op in [
+ operator.pow,
+ ops.rpow,
+ operator.mod,
+ ops.rmod,
+ operator.floordiv,
+ ops.rfloordiv,
+ divmod,
+ ops.rdivmod,
+ ]:
+ return super()._arith_method(other, op)
+
+ step: Callable | None = None
+ if op in [operator.mul, ops.rmul, operator.truediv, ops.rtruediv]:
+ step = op
+
+ # TODO: if other is a RangeIndex we may have more efficient options
+ right = extract_array(other, extract_numpy=True, extract_range=True)
+ left = self
+
+ try:
+ # apply if we have an override
+ if step:
+ with np.errstate(all="ignore"):
+ rstep = step(left.step, right)
+
+ # we don't have a representable op
+ # so return a base index
+ if not is_integer(rstep) or not rstep:
+ raise ValueError
+
+ # GH#53255
+ else:
+ rstep = -left.step if op == ops.rsub else left.step
+
+ with np.errstate(all="ignore"):
+ rstart = op(left.start, right)
+ rstop = op(left.stop, right)
+
+ res_name = ops.get_op_result_name(self, other)
+ result = type(self)(rstart, rstop, rstep, name=res_name)
+
+ # for compat with numpy / Index with int64 dtype
+ # even if we can represent as a RangeIndex, return
+ # as a float64 Index if we have float-like descriptors
+ if not all(is_integer(x) for x in [rstart, rstop, rstep]):
+ result = result.astype("float64")
+
+ return result
+
+ except (ValueError, TypeError, ZeroDivisionError):
+ # test_arithmetic_explicit_conversions
+ return super()._arith_method(other, op)
+
+ def __abs__(self) -> Self | Index:
+ if len(self) == 0 or self.min() >= 0:
+ return self.copy()
+ elif self.max() <= 0:
+ return -self
+ else:
+ return super().__abs__()
+
+ def __neg__(self) -> Self:
+ rng = range(-self.start, -self.stop, -self.step)
+ return self._simple_new(rng, name=self.name)
+
+ def __pos__(self) -> Self:
+ return self.copy()
+
+ def __invert__(self) -> Self:
+ if len(self) == 0:
+ return self.copy()
+ rng = range(~self.start, ~self.stop, -self.step)
+ return self._simple_new(rng, name=self.name)
+
+ # error: Return type "Index" of "take" incompatible with return type
+ # "RangeIndex" in supertype "Index"
+ def take( # type: ignore[override]
+ self,
+ indices,
+ axis: Axis = 0,
+ allow_fill: bool = True,
+ fill_value=None,
+ **kwargs,
+ ) -> Self | Index:
+ if kwargs:
+ nv.validate_take((), kwargs)
+ if is_scalar(indices):
+ raise TypeError("Expected indices to be array-like")
+ indices = ensure_platform_int(indices)
+
+ # raise an exception if allow_fill is True and fill_value is not None
+ self._maybe_disallow_fill(allow_fill, fill_value, indices)
+
+ if len(indices) == 0:
+ return type(self)(_empty_range, name=self.name)
+ else:
+ ind_max = indices.max()
+ if ind_max >= len(self):
+ raise IndexError(
+ f"index {ind_max} is out of bounds for axis 0 with size {len(self)}"
+ )
+ ind_min = indices.min()
+ if ind_min < -len(self):
+ raise IndexError(
+ f"index {ind_min} is out of bounds for axis 0 with size {len(self)}"
+ )
+ taken = indices.astype(self.dtype, casting="safe")
+ if ind_min < 0:
+ taken %= len(self)
+ if self.step != 1:
+ taken *= self.step
+ if self.start != 0:
+ taken += self.start
+
+ return self._shallow_copy(taken, name=self.name)
+
+ def value_counts(
+ self,
+ normalize: bool = False,
+ sort: bool = True,
+ ascending: bool = False,
+ bins=None,
+ dropna: bool = True,
+ ) -> Series:
+ from pandas import Series
+
+ if bins is not None:
+ return super().value_counts(
+ normalize=normalize,
+ sort=sort,
+ ascending=ascending,
+ bins=bins,
+ dropna=dropna,
+ )
+ name = "proportion" if normalize else "count"
+ data: npt.NDArray[np.floating] | npt.NDArray[np.signedinteger] = np.ones(
+ len(self), dtype=np.int64
+ )
+ if normalize:
+ data = data / len(self)
+ return Series(data, index=self.copy(), name=name)
+
+ def searchsorted( # type: ignore[override]
+ self,
+ value,
+ side: Literal["left", "right"] = "left",
+ sorter: NumpySorter | None = None,
+ ) -> npt.NDArray[np.intp] | np.intp:
+ if side not in {"left", "right"} or sorter is not None:
+ return super().searchsorted(value=value, side=side, sorter=sorter)
+
+ was_scalar = False
+ if is_scalar(value):
+ was_scalar = True
+ array_value = np.array([value])
+ else:
+ array_value = np.asarray(value)
+ if array_value.dtype.kind not in "iu":
+ return super().searchsorted(value=value, side=side, sorter=sorter)
+
+ if flip := (self.step < 0):
+ rng = self._range[::-1]
+ start = rng.start
+ step = rng.step
+ shift = side == "right"
+ else:
+ start = self.start
+ step = self.step
+ shift = side == "left"
+ result = (array_value - start - int(shift)) // step + 1
+ if flip:
+ result = len(self) - result
+ result = np.maximum(np.minimum(result, len(self)), 0)
+ if was_scalar:
+ return np.intp(result.item())
+ return result.astype(np.intp, copy=False)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/timedeltas.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/timedeltas.py
new file mode 100644
index 0000000000000000000000000000000000000000..da51ec2fde08d5e1dbc3c0acc923cad915496e97
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexes/timedeltas.py
@@ -0,0 +1,417 @@
+"""implement the TimedeltaIndex"""
+
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ cast,
+)
+
+from pandas._libs import (
+ index as libindex,
+ lib,
+)
+from pandas._libs.tslibs import (
+ Resolution,
+ Timedelta,
+ to_offset,
+)
+from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
+from pandas.util._decorators import set_module
+
+from pandas.core.dtypes.common import (
+ is_scalar,
+ pandas_dtype,
+)
+from pandas.core.dtypes.dtypes import ArrowDtype
+from pandas.core.dtypes.generic import ABCSeries
+
+from pandas.core.arrays.timedeltas import TimedeltaArray
+import pandas.core.common as com
+from pandas.core.indexes.base import (
+ Index,
+ maybe_extract_name,
+)
+from pandas.core.indexes.datetimelike import DatetimeTimedeltaMixin
+from pandas.core.indexes.extension import inherit_names
+
+if TYPE_CHECKING:
+ from pandas._libs import NaTType
+ from pandas._libs.tslibs import (
+ Day,
+ Tick,
+ )
+ from pandas._typing import (
+ DtypeObj,
+ TimeUnit,
+ )
+
+
+@inherit_names(
+ [
+ "__neg__",
+ "__pos__",
+ "__abs__",
+ "total_seconds",
+ "round",
+ "floor",
+ "ceil",
+ *TimedeltaArray._field_ops,
+ ],
+ TimedeltaArray,
+ wrap=True,
+)
+@inherit_names(
+ [
+ "components",
+ "to_pytimedelta",
+ "sum",
+ "std",
+ "median",
+ ],
+ TimedeltaArray,
+)
+@set_module("pandas")
+class TimedeltaIndex(DatetimeTimedeltaMixin):
+ """
+ Immutable Index of timedelta64 data.
+
+ Represented internally as int64, and scalars returned Timedelta objects.
+
+ Parameters
+ ----------
+ data : array-like (1-dimensional), optional
+ Optional timedelta-like data to construct index with.
+ freq : str or pandas offset object, optional
+ One of pandas date offset strings or corresponding objects. The string
+ ``'infer'`` can be passed in order to set the frequency of the index as
+ the inferred frequency upon creation.
+ dtype : numpy.dtype or str, default None
+ Valid ``numpy`` dtypes are ``timedelta64[ns]``, ``timedelta64[us]``,
+ ``timedelta64[ms]``, and ``timedelta64[s]``.
+ copy : bool, default None
+ Whether to copy input data, only relevant for array, Series, and Index
+ inputs (for other input, e.g. a list, a new array is created anyway).
+ Defaults to True for array input and False for Index/Series.
+ Set to False to avoid copying array input at your own risk (if you
+ know the input data won't be modified elsewhere).
+ Set to True to force copying Series/Index input up front.
+ name : object
+ Name to be stored in the index.
+
+ Attributes
+ ----------
+ days
+ seconds
+ microseconds
+ nanoseconds
+ components
+ inferred_freq
+
+ Methods
+ -------
+ to_pytimedelta
+ to_series
+ round
+ floor
+ ceil
+ to_frame
+ mean
+
+ See Also
+ --------
+ Index : The base pandas Index type.
+ Timedelta : Represents a duration between two dates or times.
+ DatetimeIndex : Index of datetime64 data.
+ PeriodIndex : Index of Period data.
+ timedelta_range : Create a fixed-frequency TimedeltaIndex.
+
+ Notes
+ -----
+ To learn more about the frequency strings, please see
+ :ref:`this link`.
+
+ Examples
+ --------
+ >>> pd.TimedeltaIndex(["0 days", "1 days", "2 days", "3 days", "4 days"])
+ TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
+ dtype='timedelta64[us]', freq=None)
+
+ We can also let pandas infer the frequency when possible.
+
+ >>> pd.TimedeltaIndex(np.arange(5) * 24 * 3600 * 1e9, freq="infer")
+ TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
+ dtype='timedelta64[ns]', freq='D')
+ """
+
+ _typ = "timedeltaindex"
+
+ _data_cls = TimedeltaArray
+
+ @property
+ def _engine_type(self) -> type[libindex.TimedeltaEngine]:
+ return libindex.TimedeltaEngine
+
+ _data: TimedeltaArray
+
+ # Use base class method instead of DatetimeTimedeltaMixin._get_string_slice
+ _get_string_slice = Index._get_string_slice
+
+ # error: Signature of "_resolution_obj" incompatible with supertype
+ # "DatetimeIndexOpsMixin"
+ @property
+ def _resolution_obj(self) -> Resolution | None: # type: ignore[override]
+ return self._data._resolution_obj
+
+ # -------------------------------------------------------------------
+ # Constructors
+
+ def __new__(
+ cls,
+ data=None,
+ freq=lib.no_default,
+ dtype=None,
+ copy: bool | None = None,
+ name=None,
+ ):
+ name = maybe_extract_name(name, data, cls)
+
+ # GH#63388
+ data, copy = cls._maybe_copy_array_input(data, copy, dtype)
+
+ if is_scalar(data):
+ cls._raise_scalar_data_error(data)
+
+ if dtype is not None:
+ dtype = pandas_dtype(dtype)
+
+ if (
+ isinstance(data, TimedeltaArray)
+ and freq is lib.no_default
+ and (dtype is None or dtype == data.dtype)
+ ):
+ if copy:
+ data = data.copy()
+ return cls._simple_new(data, name=name)
+
+ if (
+ isinstance(data, TimedeltaIndex)
+ and freq is lib.no_default
+ and name is None
+ and (dtype is None or dtype == data.dtype)
+ ):
+ if copy:
+ return data.copy()
+ else:
+ return data._view()
+
+ # - Cases checked above all return/raise before reaching here - #
+
+ tdarr = TimedeltaArray._from_sequence_not_strict(
+ data, freq=freq, unit=None, dtype=dtype, copy=copy
+ )
+ refs = None
+ if not copy and isinstance(data, (ABCSeries, Index)):
+ refs = data._references
+
+ return cls._simple_new(tdarr, name=name, refs=refs)
+
+ # -------------------------------------------------------------------
+
+ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
+ """
+ Can we compare values of the given dtype to our own?
+ """
+ if isinstance(dtype, ArrowDtype):
+ return dtype.kind == "m"
+ return lib.is_np_dtype(dtype, "m") # aka self._data._is_recognized_dtype
+
+ # -------------------------------------------------------------------
+ # Indexing Methods
+
+ def get_loc(self, key):
+ """
+ Get integer location for requested label
+
+ Returns
+ -------
+ loc : int, slice, or ndarray[int]
+ """
+ self._check_indexing_error(key)
+
+ try:
+ key = self._data._validate_scalar(key, unbox=False)
+ except TypeError as err:
+ raise KeyError(key) from err
+
+ return Index.get_loc(self, key)
+
+ # error: Return type "tuple[Timedelta | NaTType, Resolution]" of
+ # "_parse_with_reso" incompatible with return type
+ # "tuple[datetime, Resolution]" in supertype
+ # "pandas.core.indexes.datetimelike.DatetimeIndexOpsMixin"
+ def _parse_with_reso(self, label: str) -> tuple[Timedelta | NaTType, Resolution]: # type: ignore[override]
+ parsed = Timedelta(label)
+ if isinstance(parsed, Timedelta):
+ reso = Resolution.get_reso_from_freqstr(parsed.unit)
+ else:
+ # i.e. pd.NaT
+ reso = Resolution.get_reso_from_freqstr("s")
+ return parsed, reso
+
+ def _parsed_string_to_bounds(self, reso: Resolution, parsed: Timedelta):
+ # reso is unused, included to match signature of DTI/PI
+ lbound = parsed.round(parsed.resolution_string)
+ rbound = (
+ lbound
+ + to_offset(parsed.resolution_string)
+ - Timedelta(1, unit=self.unit).as_unit(self.unit)
+ )
+ return lbound, rbound
+
+ # -------------------------------------------------------------------
+
+ @property
+ def inferred_type(self) -> str:
+ return "timedelta64"
+
+
+@set_module("pandas")
+def timedelta_range(
+ start=None,
+ end=None,
+ periods: int | None = None,
+ freq=None,
+ name=None,
+ closed=None,
+ *,
+ unit: TimeUnit | None = None,
+) -> TimedeltaIndex:
+ """
+ Return a fixed frequency TimedeltaIndex with day as the default.
+
+ Parameters
+ ----------
+ start : str or timedelta-like, default None
+ Left bound for generating timedeltas.
+ end : str or timedelta-like, default None
+ Right bound for generating timedeltas.
+ periods : int, default None
+ Number of periods to generate.
+ freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'D'
+ Frequency strings can have multiples, e.g. '5h'.
+ name : Hashable, default None
+ Name of the resulting TimedeltaIndex.
+ closed : str, default None
+ Make the interval closed with respect to the given frequency to
+ the 'left', 'right', or both sides (None).
+ unit : {'s', 'ms', 'us', 'ns', None}, default None
+ Specify the desired resolution of the result.
+ If not specified, this is inferred from the 'start', 'end', and 'freq'
+ using the same inference as :class:`Timedelta` taking the highest
+ resolution of the three that are provided.
+
+ .. versionadded:: 2.0.0
+
+ Returns
+ -------
+ TimedeltaIndex
+ Fixed frequency, with day as the default.
+
+ See Also
+ --------
+ date_range : Return a fixed frequency DatetimeIndex.
+ period_range : Return a fixed frequency PeriodIndex.
+
+ Notes
+ -----
+ Of the four parameters ``start``, ``end``, ``periods``, and ``freq``,
+ a maximum of three can be specified at once. Of the three parameters
+ ``start``, ``end``, and ``periods``, at least two must be specified.
+ If ``freq`` is omitted, the resulting ``DatetimeIndex`` will have
+ ``periods`` linearly spaced elements between ``start`` and ``end``
+ (closed on both sides).
+
+ To learn more about the frequency strings, please see
+ :ref:`this link`.
+
+ Examples
+ --------
+ >>> pd.timedelta_range(start="1 day", periods=4)
+ TimedeltaIndex(['1 days', '2 days', '3 days', '4 days'],
+ dtype='timedelta64[us]', freq='D')
+
+ The ``closed`` parameter specifies which endpoint is included. The default
+ behavior is to include both endpoints.
+
+ >>> pd.timedelta_range(start="1 day", periods=4, closed="right")
+ TimedeltaIndex(['2 days', '3 days', '4 days'],
+ dtype='timedelta64[us]', freq='D')
+
+ The ``freq`` parameter specifies the frequency of the TimedeltaIndex.
+ Only fixed frequencies can be passed, non-fixed frequencies such as
+ 'M' (month end) will raise.
+
+ >>> pd.timedelta_range(start="1 day", end="2 days", freq="6h")
+ TimedeltaIndex(['1 days 00:00:00', '1 days 06:00:00', '1 days 12:00:00',
+ '1 days 18:00:00', '2 days 00:00:00'],
+ dtype='timedelta64[us]', freq='6h')
+
+ Specify ``start``, ``end``, and ``periods``; the frequency is generated
+ automatically (linearly spaced).
+
+ >>> pd.timedelta_range(start="1 day", end="5 days", periods=4)
+ TimedeltaIndex(['1 days 00:00:00', '2 days 08:00:00', '3 days 16:00:00',
+ '5 days 00:00:00'],
+ dtype='timedelta64[us]', freq=None)
+
+ **Specify a unit**
+
+ >>> pd.timedelta_range("1 Day", periods=3, freq="100000D", unit="s")
+ TimedeltaIndex(['1 days', '100001 days', '200001 days'],
+ dtype='timedelta64[s]', freq='100000D')
+ """
+ if freq is None and com.any_none(periods, start, end):
+ freq = "D"
+ freq = to_offset(freq)
+
+ if com.count_not_none(start, end, periods, freq) != 3:
+ # This check needs to come before the `unit = start.unit` line below
+ raise ValueError(
+ "Of the four parameters: start, end, periods, "
+ "and freq, exactly three must be specified"
+ )
+
+ if unit is None:
+ # Infer the unit based on the inputs
+
+ if start is not None and end is not None:
+ start = Timedelta(start)
+ end = Timedelta(end)
+ start = cast(Timedelta, start)
+ end = cast(Timedelta, end)
+ if abbrev_to_npy_unit(start.unit) > abbrev_to_npy_unit(end.unit):
+ unit = cast("TimeUnit", start.unit)
+ else:
+ unit = cast("TimeUnit", end.unit)
+ elif start is not None:
+ start = Timedelta(start)
+ start = cast(Timedelta, start)
+ unit = cast("TimeUnit", start.unit)
+ else:
+ end = Timedelta(end)
+ end = cast(Timedelta, end)
+ unit = cast("TimeUnit", end.unit)
+
+ # Last we need to watch out for cases where the 'freq' implies a higher
+ # unit than either start or end
+ if freq is not None:
+ freq = cast("Tick | Day", freq)
+ creso = abbrev_to_npy_unit(unit)
+ if freq._creso > creso: # pyright: ignore[reportAttributeAccessIssue]
+ unit = cast("TimeUnit", freq.base.freqstr)
+
+ tdarr = TimedeltaArray._generate_range(
+ start, end, periods, freq, closed=closed, unit=unit
+ )
+ return TimedeltaIndex._simple_new(tdarr, name=name)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3a92cea53615e763c35198e5361bd78ae2f8fb4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/indexing.py
@@ -0,0 +1,2796 @@
+from __future__ import annotations
+
+from contextlib import suppress
+import sys
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Self,
+ cast,
+ final,
+)
+import warnings
+
+import numpy as np
+
+from pandas._libs.indexing import NDFrameIndexerBase
+from pandas._libs.lib import item_from_zerodim
+from pandas.compat import CHAINED_WARNING_DISABLED
+from pandas.compat._constants import REF_COUNT_IDX
+from pandas.errors import (
+ AbstractMethodError,
+ ChainedAssignmentError,
+ IndexingError,
+ InvalidIndexError,
+ LossySetitemError,
+)
+from pandas.errors.cow import _chained_assignment_msg
+from pandas.util._decorators import (
+ doc,
+)
+
+from pandas.core.dtypes.cast import (
+ can_hold_element,
+ maybe_promote,
+)
+from pandas.core.dtypes.common import (
+ is_array_like,
+ is_bool_dtype,
+ is_hashable,
+ is_integer,
+ is_iterator,
+ is_list_like,
+ is_numeric_dtype,
+ is_object_dtype,
+ is_scalar,
+ is_sequence,
+)
+from pandas.core.dtypes.concat import concat_compat
+from pandas.core.dtypes.dtypes import ExtensionDtype
+from pandas.core.dtypes.generic import (
+ ABCDataFrame,
+ ABCSeries,
+)
+from pandas.core.dtypes.missing import (
+ construct_1d_array_from_inferred_fill_value,
+ infer_fill_value,
+ is_valid_na_for_dtype,
+ isna,
+ na_value_for_dtype,
+)
+
+from pandas.core import algorithms as algos
+import pandas.core.common as com
+from pandas.core.construction import (
+ array as pd_array,
+ extract_array,
+)
+from pandas.core.indexers import (
+ check_array_indexer,
+ is_list_like_indexer,
+ is_scalar_indexer,
+ length_of_indexer,
+)
+from pandas.core.indexes.api import (
+ Index,
+ MultiIndex,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Hashable,
+ Sequence,
+ )
+
+ from pandas._typing import (
+ Axis,
+ AxisInt,
+ T,
+ npt,
+ )
+
+ from pandas import (
+ DataFrame,
+ Series,
+ )
+
+# "null slice"
+_NS = slice(None, None)
+_one_ellipsis_message = "indexer may only contain one '...' entry"
+
+
+# the public IndexSlicerMaker
+class _IndexSlice:
+ """
+ Create an object to more easily perform multi-index slicing.
+
+ See Also
+ --------
+ MultiIndex.remove_unused_levels : New MultiIndex with no unused levels.
+
+ Notes
+ -----
+ See :ref:`Defined Levels `
+ for further info on slicing a MultiIndex.
+
+ Examples
+ --------
+ >>> midx = pd.MultiIndex.from_product([["A0", "A1"], ["B0", "B1", "B2", "B3"]])
+ >>> columns = ["foo", "bar"]
+ >>> dfmi = pd.DataFrame(
+ ... np.arange(16).reshape((len(midx), len(columns))),
+ ... index=midx,
+ ... columns=columns,
+ ... )
+
+ Using the default slice command:
+
+ >>> dfmi.loc[(slice(None), slice("B0", "B1")), :]
+ foo bar
+ A0 B0 0 1
+ B1 2 3
+ A1 B0 8 9
+ B1 10 11
+
+ Using the IndexSlice class for a more intuitive command:
+
+ >>> idx = pd.IndexSlice
+ >>> dfmi.loc[idx[:, "B0":"B1"], :]
+ foo bar
+ A0 B0 0 1
+ B1 2 3
+ A1 B0 8 9
+ B1 10 11
+ """
+
+ def __getitem__(self, arg):
+ return arg
+
+
+IndexSlice = _IndexSlice()
+IndexSlice.__module__ = "pandas"
+
+
+class IndexingMixin:
+ """
+ Mixin for adding .loc/.iloc/.at/.iat to Dataframes and Series.
+ """
+
+ @property
+ def iloc(self) -> _iLocIndexer:
+ """
+ Purely integer-location based indexing for selection by position.
+
+ .. versionchanged:: 3.0
+
+ Callables which return a tuple are deprecated as input.
+
+ ``.iloc[]`` is primarily integer position based (from ``0`` to
+ ``length-1`` of the axis), but may also be used with a boolean
+ array.
+
+ Allowed inputs are:
+
+ - An integer, e.g. ``5``.
+ - A list or array of integers, e.g. ``[4, 3, 0]``.
+ - A slice object with ints, e.g. ``1:7``.
+ - A boolean array.
+ - A ``callable`` function with one argument (the calling Series or
+ DataFrame) and that returns valid output for indexing (one of the above).
+ This is useful in method chains, when you don't have a reference to the
+ calling object, but would like to base your selection on
+ some value.
+ - A tuple of row and column indexes. The tuple elements consist of one of the
+ above inputs, e.g. ``(0, 1)``.
+
+ ``.iloc`` will raise ``IndexError`` if a requested indexer is
+ out-of-bounds, except *slice* indexers which allow out-of-bounds
+ indexing (this conforms with python/numpy *slice* semantics).
+
+ See more at :ref:`Selection by Position `.
+
+ See Also
+ --------
+ DataFrame.iat : Fast integer location scalar accessor.
+ DataFrame.loc : Purely label-location based indexer for selection by label.
+ Series.iloc : Purely integer-location based indexing for
+ selection by position.
+
+ Examples
+ --------
+ >>> mydict = [
+ ... {"a": 1, "b": 2, "c": 3, "d": 4},
+ ... {"a": 100, "b": 200, "c": 300, "d": 400},
+ ... {"a": 1000, "b": 2000, "c": 3000, "d": 4000},
+ ... ]
+ >>> df = pd.DataFrame(mydict)
+ >>> df
+ a b c d
+ 0 1 2 3 4
+ 1 100 200 300 400
+ 2 1000 2000 3000 4000
+
+ **Indexing just the rows**
+
+ With a scalar integer.
+
+ >>> type(df.iloc[0])
+
+ >>> df.iloc[0]
+ a 1
+ b 2
+ c 3
+ d 4
+ Name: 0, dtype: int64
+
+ With a list of integers.
+
+ >>> df.iloc[[0]]
+ a b c d
+ 0 1 2 3 4
+ >>> type(df.iloc[[0]])
+
+
+ >>> df.iloc[[0, 1]]
+ a b c d
+ 0 1 2 3 4
+ 1 100 200 300 400
+
+ With a `slice` object.
+
+ >>> df.iloc[:3]
+ a b c d
+ 0 1 2 3 4
+ 1 100 200 300 400
+ 2 1000 2000 3000 4000
+
+ With a boolean mask the same length as the index.
+
+ >>> df.iloc[[True, False, True]]
+ a b c d
+ 0 1 2 3 4
+ 2 1000 2000 3000 4000
+
+ With a callable, useful in method chains. The `x` passed
+ to the ``lambda`` is the DataFrame being sliced. This selects
+ the rows whose index label even.
+
+ >>> df.iloc[lambda x: x.index % 2 == 0]
+ a b c d
+ 0 1 2 3 4
+ 2 1000 2000 3000 4000
+
+ **Indexing both axes**
+
+ You can mix the indexer types for the index and columns. Use ``:`` to
+ select the entire axis.
+
+ With scalar integers.
+
+ >>> df.iloc[0, 1]
+ np.int64(2)
+
+ With lists of integers.
+
+ >>> df.iloc[[0, 2], [1, 3]]
+ b d
+ 0 2 4
+ 2 2000 4000
+
+ With `slice` objects.
+
+ >>> df.iloc[1:3, 0:3]
+ a b c
+ 1 100 200 300
+ 2 1000 2000 3000
+
+ With a boolean array whose length matches the columns.
+
+ >>> df.iloc[:, [True, False, True, False]]
+ a c
+ 0 1 3
+ 1 100 300
+ 2 1000 3000
+
+ With a callable function that expects the Series or DataFrame.
+
+ >>> df.iloc[:, lambda df: [0, 2]]
+ a c
+ 0 1 3
+ 1 100 300
+ 2 1000 3000
+ """
+ return _iLocIndexer("iloc", self)
+
+ @property
+ def loc(self) -> _LocIndexer:
+ """
+ Access a group of rows and columns by label(s) or a boolean array.
+
+ ``.loc[]`` is primarily label based, but may also be used with a
+ boolean array.
+
+ Allowed inputs are:
+
+ - A single label, e.g. ``5`` or ``'a'``, (note that ``5`` is
+ interpreted as a *label* of the index, and **never** as an
+ integer position along the index).
+ - A list or array of labels, e.g. ``['a', 'b', 'c']``.
+ - A slice object with labels, e.g. ``'a':'f'``.
+
+ .. warning:: Note that contrary to usual python slices, **both** the
+ start and the stop are included
+
+ - A boolean array of the same length as the axis being sliced,
+ e.g. ``[True, False, True]``.
+ - An alignable boolean Series. The index of the key will be aligned before
+ masking.
+ - An alignable Index. The Index of the returned selection will be the input.
+ - A ``callable`` function with one argument (the calling Series or
+ DataFrame) and that returns valid output for indexing (one of the above)
+
+ See more at :ref:`Selection by Label `.
+
+ Raises
+ ------
+ KeyError
+ If any items are not found.
+ IndexingError
+ If an indexed key is passed and its index is unalignable to the frame index.
+
+ See Also
+ --------
+ DataFrame.at : Access a single value for a row/column label pair.
+ DataFrame.iloc : Access group of rows and columns by integer position(s).
+ DataFrame.xs : Returns a cross-section (row(s) or column(s)) from the
+ Series/DataFrame.
+ Series.loc : Access group of values using labels.
+
+ Examples
+ --------
+ **Getting values**
+
+ >>> df = pd.DataFrame(
+ ... [[1, 2], [4, 5], [7, 8]],
+ ... index=["cobra", "viper", "sidewinder"],
+ ... columns=["max_speed", "shield"],
+ ... )
+ >>> df
+ max_speed shield
+ cobra 1 2
+ viper 4 5
+ sidewinder 7 8
+
+ Single label. Note this returns the row as a Series.
+
+ >>> df.loc["viper"]
+ max_speed 4
+ shield 5
+ Name: viper, dtype: int64
+
+ List of labels. Note using ``[[]]`` returns a DataFrame.
+
+ >>> df.loc[["viper", "sidewinder"]]
+ max_speed shield
+ viper 4 5
+ sidewinder 7 8
+
+ Single label for row and column
+
+ >>> df.loc["cobra", "shield"]
+ np.int64(2)
+
+ Slice with labels for row and single label for column. As mentioned
+ above, note that both the start and stop of the slice are included.
+
+ >>> df.loc["cobra":"viper", "max_speed"]
+ cobra 1
+ viper 4
+ Name: max_speed, dtype: int64
+
+ Boolean list with the same length as the row axis
+
+ >>> df.loc[[False, False, True]]
+ max_speed shield
+ sidewinder 7 8
+
+ Alignable boolean Series:
+
+ >>> df.loc[
+ ... pd.Series([False, True, False], index=["viper", "sidewinder", "cobra"])
+ ... ]
+ max_speed shield
+ sidewinder 7 8
+
+ Index (same behavior as ``df.reindex``)
+
+ >>> df.loc[pd.Index(["cobra", "viper"], name="foo")]
+ max_speed shield
+ foo
+ cobra 1 2
+ viper 4 5
+
+ Conditional that returns a boolean Series
+
+ >>> df.loc[df["shield"] > 6]
+ max_speed shield
+ sidewinder 7 8
+
+ Conditional that returns a boolean Series with column labels specified
+
+ >>> df.loc[df["shield"] > 6, ["max_speed"]]
+ max_speed
+ sidewinder 7
+
+ Multiple conditional using ``&`` that returns a boolean Series
+
+ >>> df.loc[(df["max_speed"] > 1) & (df["shield"] < 8)]
+ max_speed shield
+ viper 4 5
+
+ Multiple conditional using ``|`` that returns a boolean Series
+
+ >>> df.loc[(df["max_speed"] > 4) | (df["shield"] < 5)]
+ max_speed shield
+ cobra 1 2
+ sidewinder 7 8
+
+ Please ensure that each condition is wrapped in parentheses ``()``.
+ See the :ref:`user guide`
+ for more details and explanations of Boolean indexing.
+
+ .. note::
+ If you find yourself using 3 or more conditionals in ``.loc[]``,
+ consider using :ref:`advanced indexing`.
+
+ See below for using ``.loc[]`` on MultiIndex DataFrames.
+
+ Callable that returns a boolean Series
+
+ >>> df.loc[lambda df: df["shield"] == 8]
+ max_speed shield
+ sidewinder 7 8
+
+ **Setting values**
+
+ Set value for all items matching the list of labels
+
+ >>> df.loc[["viper", "sidewinder"], ["shield"]] = 50
+ >>> df
+ max_speed shield
+ cobra 1 2
+ viper 4 50
+ sidewinder 7 50
+
+ Set value for an entire row
+
+ >>> df.loc["cobra"] = 10
+ >>> df
+ max_speed shield
+ cobra 10 10
+ viper 4 50
+ sidewinder 7 50
+
+ Set value for an entire column
+
+ >>> df.loc[:, "max_speed"] = 30
+ >>> df
+ max_speed shield
+ cobra 30 10
+ viper 30 50
+ sidewinder 30 50
+
+ Set value for rows matching callable condition
+
+ >>> df.loc[df["shield"] > 35] = 0
+ >>> df
+ max_speed shield
+ cobra 30 10
+ viper 0 0
+ sidewinder 0 0
+
+ Add value matching location
+
+ >>> df.loc["viper", "shield"] += 5
+ >>> df
+ max_speed shield
+ cobra 30 10
+ viper 0 5
+ sidewinder 0 0
+
+ Setting using a ``Series`` or a ``DataFrame`` sets the values matching the
+ index labels, not the index positions.
+
+ >>> shuffled_df = df.loc[["viper", "cobra", "sidewinder"]]
+ >>> df.loc[:] += shuffled_df
+ >>> df
+ max_speed shield
+ cobra 60 20
+ viper 0 10
+ sidewinder 0 0
+
+ **Getting values on a DataFrame with an index that has integer labels**
+
+ Another example using integers for the index
+
+ >>> df = pd.DataFrame(
+ ... [[1, 2], [4, 5], [7, 8]],
+ ... index=[7, 8, 9],
+ ... columns=["max_speed", "shield"],
+ ... )
+ >>> df
+ max_speed shield
+ 7 1 2
+ 8 4 5
+ 9 7 8
+
+ Slice with integer labels for rows. As mentioned above, note that both
+ the start and stop of the slice are included.
+
+ >>> df.loc[7:9]
+ max_speed shield
+ 7 1 2
+ 8 4 5
+ 9 7 8
+
+ **Getting values with a MultiIndex**
+
+ A number of examples using a DataFrame with a MultiIndex
+
+ >>> tuples = [
+ ... ("cobra", "mark i"),
+ ... ("cobra", "mark ii"),
+ ... ("sidewinder", "mark i"),
+ ... ("sidewinder", "mark ii"),
+ ... ("viper", "mark ii"),
+ ... ("viper", "mark iii"),
+ ... ]
+ >>> index = pd.MultiIndex.from_tuples(tuples)
+ >>> values = [[12, 2], [0, 4], [10, 20], [1, 4], [7, 1], [16, 36]]
+ >>> df = pd.DataFrame(values, columns=["max_speed", "shield"], index=index)
+ >>> df
+ max_speed shield
+ cobra mark i 12 2
+ mark ii 0 4
+ sidewinder mark i 10 20
+ mark ii 1 4
+ viper mark ii 7 1
+ mark iii 16 36
+
+ Single label. Note this returns a DataFrame with a single index.
+
+ >>> df.loc["cobra"]
+ max_speed shield
+ mark i 12 2
+ mark ii 0 4
+
+ Single index tuple. Note this returns a Series.
+
+ >>> df.loc[("cobra", "mark ii")]
+ max_speed 0
+ shield 4
+ Name: (cobra, mark ii), dtype: int64
+
+ Single label for row and column. Similar to passing in a tuple, this
+ returns a Series.
+
+ >>> df.loc["cobra", "mark i"]
+ max_speed 12
+ shield 2
+ Name: (cobra, mark i), dtype: int64
+
+ Single tuple. Note using ``[[]]`` returns a DataFrame.
+
+ >>> df.loc[[("cobra", "mark ii")]]
+ max_speed shield
+ cobra mark ii 0 4
+
+ Single tuple for the index with a single label for the column
+
+ >>> df.loc[("cobra", "mark i"), "shield"]
+ np.int64(2)
+
+ Slice from index tuple to single label
+
+ >>> df.loc[("cobra", "mark i") : "viper"]
+ max_speed shield
+ cobra mark i 12 2
+ mark ii 0 4
+ sidewinder mark i 10 20
+ mark ii 1 4
+ viper mark ii 7 1
+ mark iii 16 36
+
+ Slice from index tuple to index tuple
+
+ >>> df.loc[("cobra", "mark i") : ("viper", "mark ii")]
+ max_speed shield
+ cobra mark i 12 2
+ mark ii 0 4
+ sidewinder mark i 10 20
+ mark ii 1 4
+ viper mark ii 7 1
+
+ Please see the :ref:`user guide`
+ for more details and explanations of advanced indexing.
+
+ **Assignment with Series**
+
+ When assigning a Series to .loc[row_indexer, col_indexer], pandas aligns
+ the Series by index labels, not by order or position.
+
+ Series assignment with .loc and index alignment:
+
+ >>> df = pd.DataFrame({"A": [1, 2, 3]}, index=[0, 1, 2])
+ >>> s = pd.Series([10, 20], index=[1, 0]) # Note reversed order
+ >>> df.loc[:, "B"] = s # Aligns by index, not order
+ >>> df
+ A B
+ 0 1 20.0
+ 1 2 10.0
+ 2 3 NaN
+ """
+ return _LocIndexer("loc", self)
+
+ @property
+ def at(self) -> _AtIndexer:
+ """
+ Access a single value for a row/column label pair.
+
+ Similar to ``loc``, in that both provide label-based lookups. Use
+ ``at`` if you only need to get or set a single value in a DataFrame
+ or Series.
+
+ Raises
+ ------
+ KeyError
+ If getting a value and 'label' does not exist in a DataFrame or Series.
+
+ ValueError
+ If row/column label pair is not a tuple or if any label
+ from the pair is not a scalar for DataFrame.
+ If label is list-like (*excluding* NamedTuple) for Series.
+
+ See Also
+ --------
+ DataFrame.at : Access a single value for a row/column pair by label.
+ DataFrame.iat : Access a single value for a row/column pair by integer
+ position.
+ DataFrame.loc : Access a group of rows and columns by label(s).
+ DataFrame.iloc : Access a group of rows and columns by integer
+ position(s).
+ Series.at : Access a single value by label.
+ Series.iat : Access a single value by integer position.
+ Series.loc : Access a group of rows by label(s).
+ Series.iloc : Access a group of rows by integer position(s).
+
+ Notes
+ -----
+ See :ref:`Fast scalar value getting and setting `
+ for more details.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame(
+ ... [[0, 2, 3], [0, 4, 1], [10, 20, 30]],
+ ... index=[4, 5, 6],
+ ... columns=["A", "B", "C"],
+ ... )
+ >>> df
+ A B C
+ 4 0 2 3
+ 5 0 4 1
+ 6 10 20 30
+
+ Get value at specified row/column pair
+
+ >>> df.at[4, "B"]
+ np.int64(2)
+
+ Set value at specified row/column pair
+
+ >>> df.at[4, "B"] = 10
+ >>> df.at[4, "B"]
+ np.int64(10)
+
+ Get value within a Series
+
+ >>> df.loc[5].at["B"]
+ np.int64(4)
+ """
+ return _AtIndexer("at", self)
+
+ @property
+ def iat(self) -> _iAtIndexer:
+ """
+ Access a single value for a row/column pair by integer position.
+
+ Similar to ``iloc``, in that both provide integer-based lookups. Use
+ ``iat`` if you only need to get or set a single value in a DataFrame
+ or Series.
+
+ Raises
+ ------
+ IndexError
+ When integer position is out of bounds.
+
+ See Also
+ --------
+ DataFrame.at : Access a single value for a row/column label pair.
+ DataFrame.loc : Access a group of rows and columns by label(s).
+ DataFrame.iloc : Access a group of rows and columns by integer position(s).
+
+ Examples
+ --------
+ >>> df = pd.DataFrame(
+ ... [[0, 2, 3], [0, 4, 1], [10, 20, 30]], columns=["A", "B", "C"]
+ ... )
+ >>> df
+ A B C
+ 0 0 2 3
+ 1 0 4 1
+ 2 10 20 30
+
+ Get value at specified row/column pair
+
+ >>> df.iat[1, 2]
+ np.int64(1)
+
+ Set value at specified row/column pair
+
+ >>> df.iat[1, 2] = 10
+ >>> df.iat[1, 2]
+ np.int64(10)
+
+ Get value within a series
+
+ >>> df.loc[0].iat[1]
+ np.int64(2)
+ """
+ return _iAtIndexer("iat", self)
+
+
+class _LocationIndexer(NDFrameIndexerBase):
+ _valid_types: str
+ axis: AxisInt | None = None
+
+ # sub-classes need to set _takeable
+ _takeable: bool
+
+ @final
+ def __call__(self, axis: Axis | None = None) -> Self:
+ # we need to return a copy of ourselves
+ new_self = type(self)(self.name, self.obj)
+
+ if axis is not None:
+ axis_int_none = self.obj._get_axis_number(axis)
+ else:
+ axis_int_none = axis
+ new_self.axis = axis_int_none
+ return new_self
+
+ def _get_setitem_indexer(self, key):
+ """
+ Convert a potentially-label-based key into a positional indexer.
+ """
+ if self.name == "loc":
+ # always holds here bc iloc overrides _get_setitem_indexer
+ self._ensure_listlike_indexer(key, axis=self.axis)
+
+ if isinstance(key, tuple):
+ for x in key:
+ check_dict_or_set_indexers(x)
+
+ if self.axis is not None:
+ key = _tupleize_axis_indexer(self.ndim, self.axis, key)
+
+ ax = self.obj._get_axis(0)
+
+ if (
+ isinstance(ax, MultiIndex)
+ and self.name != "iloc"
+ and is_hashable(key, allow_slice=False)
+ ):
+ with suppress(KeyError, InvalidIndexError):
+ # TypeError e.g. passed a bool
+ return ax.get_loc(key)
+
+ if isinstance(key, tuple):
+ with suppress(IndexingError):
+ # suppress "Too many indexers"
+ return self._convert_tuple(key)
+
+ if isinstance(key, range):
+ # GH#45479 test_loc_setitem_range_key
+ key = list(key)
+
+ return self._convert_to_indexer(key, axis=0)
+
+ @final
+ def _maybe_mask_setitem_value(self, indexer, value):
+ """
+ If we have obj.iloc[mask] = series_or_frame and series_or_frame has the
+ same length as obj, we treat this as obj.iloc[mask] = series_or_frame[mask],
+ similar to Series.__setitem__.
+
+ Note this is only for loc, not iloc.
+ """
+
+ if (
+ isinstance(indexer, tuple)
+ and len(indexer) == 2
+ and isinstance(value, (ABCSeries, ABCDataFrame))
+ ):
+ pi, icols = indexer
+ ndim = value.ndim
+ if com.is_bool_indexer(pi) and len(value) == len(pi):
+ newkey = pi.nonzero()[0]
+
+ if is_scalar_indexer(icols, self.ndim - 1) and ndim == 1:
+ # e.g. test_loc_setitem_boolean_mask_allfalse
+ if len(newkey) == 0:
+ value = value.iloc[:0]
+ else:
+ # test_loc_setitem_ndframe_values_alignment
+ value = self.obj.iloc._align_series(indexer, value)
+ indexer = (newkey, icols)
+
+ elif (
+ isinstance(icols, np.ndarray)
+ and icols.dtype.kind == "i"
+ and len(icols) == 1
+ ):
+ if ndim == 1:
+ # We implicitly broadcast, though numpy does not, see
+ # github.com/pandas-dev/pandas/pull/45501#discussion_r789071825
+ # test_loc_setitem_ndframe_values_alignment
+ value = self.obj.iloc._align_series(indexer, value)
+ indexer = (newkey, icols)
+
+ elif ndim == 2 and value.shape[1] == 1:
+ if len(newkey) == 0:
+ value = value.iloc[:0]
+ else:
+ # test_loc_setitem_ndframe_values_alignment
+ value = self.obj.iloc._align_frame(indexer, value)
+ indexer = (newkey, icols)
+ elif com.is_bool_indexer(indexer):
+ indexer = indexer.nonzero()[0]
+
+ return indexer, value
+
+ @final
+ def _ensure_listlike_indexer(self, key, axis=None, value=None) -> None:
+ """
+ Ensure that a list-like of column labels are all present by adding them if
+ they do not already exist.
+
+ Parameters
+ ----------
+ key : list-like of column labels
+ Target labels.
+ axis : key axis if known
+ """
+ column_axis = 1
+
+ # column only exists in 2-dimensional DataFrame
+ if self.ndim != 2:
+ return
+
+ if isinstance(key, tuple) and len(key) > 1:
+ # key may be a tuple if we are .loc
+ # if length of key is > 1 set key to column part
+ # unless axis is already specified, then go with that
+ if axis is None:
+ axis = column_axis
+ key = key[axis]
+
+ if (
+ axis == column_axis
+ and not isinstance(self.obj.columns, MultiIndex)
+ and is_list_like_indexer(key)
+ and not com.is_bool_indexer(key)
+ and all(is_hashable(k) for k in key)
+ ):
+ # GH#38148
+ keys = self.obj.columns.union(key, sort=False)
+ diff = Index(key, copy=False).difference(self.obj.columns, sort=False)
+
+ if len(diff):
+ # e.g. if we are doing df.loc[:, ["A", "B"]] = 7 and "B"
+ # is a new column, add the new columns with dtype=np.void
+ # so that later when we go through setitem_single_column
+ # we will use isetitem. Without this, the reindex_axis
+ # below would create float64 columns in this example, which
+ # would successfully hold 7, so we would end up with the wrong
+ # dtype.
+ indexer = np.arange(len(keys), dtype=np.intp)
+ indexer[len(self.obj.columns) :] = -1
+ new_mgr = self.obj._mgr.reindex_indexer(
+ keys, indexer=indexer, axis=0, only_slice=True, use_na_proxy=True
+ )
+ self.obj._mgr = new_mgr
+ return
+
+ self.obj._mgr = self.obj._mgr.reindex_axis(keys, axis=0, only_slice=True)
+
+ @final
+ def __setitem__(self, key, value) -> None:
+ if not CHAINED_WARNING_DISABLED:
+ if sys.getrefcount(self.obj) <= REF_COUNT_IDX:
+ warnings.warn(
+ _chained_assignment_msg, ChainedAssignmentError, stacklevel=2
+ )
+
+ check_dict_or_set_indexers(key)
+ if isinstance(key, tuple):
+ key = (list(x) if is_iterator(x) else x for x in key)
+ key = tuple(com.apply_if_callable(x, self.obj) for x in key)
+ else:
+ maybe_callable = com.apply_if_callable(key, self.obj)
+ key = self._raise_callable_usage(key, maybe_callable)
+ indexer = self._get_setitem_indexer(key)
+ self._has_valid_setitem_indexer(key)
+
+ iloc: _iLocIndexer = (
+ cast("_iLocIndexer", self) if self.name == "iloc" else self.obj.iloc
+ )
+ iloc._setitem_with_indexer(indexer, value, self.name)
+
+ def _validate_key(self, key, axis: AxisInt) -> None:
+ """
+ Ensure that key is valid for current indexer.
+
+ Parameters
+ ----------
+ key : scalar, slice or list-like
+ Key requested.
+ axis : int
+ Dimension on which the indexing is being made.
+
+ Raises
+ ------
+ TypeError
+ If the key (or some element of it) has wrong type.
+ IndexError
+ If the key (or some element of it) is out of bounds.
+ KeyError
+ If the key was not found.
+ """
+ raise AbstractMethodError(self)
+
+ @final
+ def _expand_ellipsis(self, tup: tuple) -> tuple:
+ """
+ If a tuple key includes an Ellipsis, replace it with an appropriate
+ number of null slices.
+ """
+ if any(x is Ellipsis for x in tup):
+ if tup.count(Ellipsis) > 1:
+ raise IndexingError(_one_ellipsis_message)
+
+ if len(tup) == self.ndim:
+ # It is unambiguous what axis this Ellipsis is indexing,
+ # treat as a single null slice.
+ i = tup.index(Ellipsis)
+ # FIXME: this assumes only one Ellipsis
+ new_key = (*tup[:i], _NS, *tup[i + 1 :])
+ return new_key
+
+ # TODO: other cases? only one test gets here, and that is covered
+ # by _validate_key_length
+ return tup
+
+ @final
+ def _validate_tuple_indexer(self, key: tuple) -> tuple:
+ """
+ Check the key for valid keys across my indexer.
+ """
+ key = self._validate_key_length(key)
+ key = self._expand_ellipsis(key)
+ for i, k in enumerate(key):
+ try:
+ self._validate_key(k, i)
+ except ValueError as err:
+ raise ValueError(
+ f"Location based indexing can only have [{self._valid_types}] types"
+ ) from err
+ return key
+
+ @final
+ def _is_nested_tuple_indexer(self, tup: tuple) -> bool:
+ """
+ Returns
+ -------
+ bool
+ """
+ if any(isinstance(ax, MultiIndex) for ax in self.obj.axes):
+ return any(is_nested_tuple(tup, ax) for ax in self.obj.axes)
+ return False
+
+ @final
+ def _convert_tuple(self, key: tuple) -> tuple:
+ # Note: we assume _tupleize_axis_indexer has been called, if necessary.
+ self._validate_key_length(key)
+ keyidx = [self._convert_to_indexer(k, axis=i) for i, k in enumerate(key)]
+ return tuple(keyidx)
+
+ @final
+ def _validate_key_length(self, key: tuple) -> tuple:
+ if len(key) > self.ndim:
+ if key[0] is Ellipsis:
+ # e.g. Series.iloc[..., 3] reduces to just Series.iloc[3]
+ key = key[1:]
+ if Ellipsis in key:
+ raise IndexingError(_one_ellipsis_message)
+ return self._validate_key_length(key)
+ raise IndexingError("Too many indexers")
+ return key
+
+ @final
+ def _getitem_tuple_same_dim(self, tup: tuple):
+ """
+ Index with indexers that should return an object of the same dimension
+ as self.obj.
+
+ This is only called after a failed call to _getitem_lowerdim.
+ """
+ retval = self.obj
+ # Selecting columns before rows is significantly faster
+ start_val = (self.ndim - len(tup)) + 1
+ for i, key in enumerate(reversed(tup)):
+ i = self.ndim - i - start_val
+ if com.is_null_slice(key):
+ continue
+
+ retval = getattr(retval, self.name)._getitem_axis(key, axis=i)
+ # We should never have retval.ndim < self.ndim, as that should
+ # be handled by the _getitem_lowerdim call above.
+ assert retval.ndim == self.ndim
+
+ if retval is self.obj:
+ # if all axes were a null slice (`df.loc[:, :]`), ensure we still
+ # return a new object (https://github.com/pandas-dev/pandas/pull/49469)
+ retval = retval.copy(deep=False)
+
+ return retval
+
+ @final
+ def _getitem_lowerdim(self, tup: tuple):
+ # we can directly get the axis result since the axis is specified
+ if self.axis is not None:
+ axis = self.obj._get_axis_number(self.axis)
+ return self._getitem_axis(tup, axis=axis)
+
+ # we may have a nested tuples indexer here
+ if self._is_nested_tuple_indexer(tup):
+ return self._getitem_nested_tuple(tup)
+
+ # we maybe be using a tuple to represent multiple dimensions here
+ ax0 = self.obj._get_axis(0)
+ # ...but iloc should handle the tuple as simple integer-location
+ # instead of checking it as multiindex representation (GH 13797)
+ if (
+ isinstance(ax0, MultiIndex)
+ and self.name != "iloc"
+ and not any(isinstance(x, slice) for x in tup)
+ ):
+ # Note: in all extant test cases, replacing the slice condition with
+ # `all(is_hashable(x) or com.is_null_slice(x) for x in tup)`
+ # is equivalent.
+ # (see the other place where we call _handle_lowerdim_multi_index_axis0)
+ with suppress(IndexingError):
+ return cast(_LocIndexer, self)._handle_lowerdim_multi_index_axis0(tup)
+
+ tup = self._validate_key_length(tup)
+
+ # Reverse tuple so that we are indexing along columns before rows
+ # and avoid unintended dtype inference. # GH60600
+ for i, key in zip(range(len(tup) - 1, -1, -1), reversed(tup), strict=True):
+ if is_label_like(key) or is_list_like(key):
+ # We don't need to check for tuples here because those are
+ # caught by the _is_nested_tuple_indexer check above.
+ section = self._getitem_axis(key, axis=i)
+
+ # We should never have a scalar section here, because
+ # _getitem_lowerdim is only called after a check for
+ # is_scalar_access, which that would be.
+ if section.ndim == self.ndim:
+ # we're in the middle of slicing through a MultiIndex
+ # revise the key wrt to `section` by inserting an _NS
+ new_key = (*tup[:i], _NS, *tup[i + 1 :])
+
+ else:
+ # Note: the section.ndim == self.ndim check above
+ # rules out having DataFrame here, so we dont need to worry
+ # about transposing.
+ new_key = tup[:i] + tup[i + 1 :]
+
+ if len(new_key) == 1:
+ new_key = new_key[0]
+
+ # Slices should return views, but calling iloc/loc with a null
+ # slice returns a new object.
+ if com.is_null_slice(new_key):
+ return section
+ # This is an elided recursive call to iloc/loc
+ return getattr(section, self.name)[new_key]
+
+ raise IndexingError("not applicable")
+
+ @final
+ def _getitem_nested_tuple(self, tup: tuple):
+ # we have a nested tuple so have at least 1 multi-index level
+ # we should be able to match up the dimensionality here
+
+ for key in tup:
+ check_dict_or_set_indexers(key)
+
+ # we have too many indexers for our dim, but have at least 1
+ # multi-index dimension, try to see if we have something like
+ # a tuple passed to a series with a multi-index
+ if len(tup) > self.ndim:
+ if self.name != "loc":
+ # This should never be reached, but let's be explicit about it
+ raise ValueError("Too many indices") # pragma: no cover
+ if all(
+ is_hashable(x, allow_slice=False) or com.is_null_slice(x) for x in tup
+ ):
+ # GH#10521 Series should reduce MultiIndex dimensions instead of
+ # DataFrame, IndexingError is not raised when slice(None,None,None)
+ # with one row.
+ with suppress(IndexingError):
+ return cast(_LocIndexer, self)._handle_lowerdim_multi_index_axis0(
+ tup
+ )
+ elif isinstance(self.obj, ABCSeries) and any(
+ isinstance(k, tuple) for k in tup
+ ):
+ # GH#35349 Raise if tuple in tuple for series
+ # Do this after the all-hashable-or-null-slice check so that
+ # we are only getting non-hashable tuples, in particular ones
+ # that themselves contain a slice entry
+ # See test_loc_series_getitem_too_many_dimensions
+ raise IndexingError("Too many indexers")
+
+ # this is a series with a multi-index specified a tuple of
+ # selectors
+ axis = self.axis or 0
+ return self._getitem_axis(tup, axis=axis)
+
+ # handle the multi-axis by taking sections and reducing
+ # this is iterative
+ obj = self.obj
+ # GH#41369 Loop in reverse order ensures indexing along columns before rows
+ # which selects only necessary blocks which avoids dtype conversion if possible
+ axis = len(tup) - 1
+ for key in reversed(tup):
+ if com.is_null_slice(key):
+ axis -= 1
+ continue
+
+ obj = getattr(obj, self.name)._getitem_axis(key, axis=axis)
+ axis -= 1
+
+ # if we have a scalar, we are done
+ if is_scalar(obj) or not hasattr(obj, "ndim"):
+ break
+
+ return obj
+
+ def _convert_to_indexer(self, key, axis: AxisInt):
+ raise AbstractMethodError(self)
+
+ def _raise_callable_usage(self, key: Any, maybe_callable: T) -> T:
+ # GH53533
+ if self.name == "iloc" and callable(key) and isinstance(maybe_callable, tuple):
+ raise ValueError(
+ "Returning a tuple from a callable with iloc is not allowed.",
+ )
+ return maybe_callable
+
+ @final
+ def __getitem__(self, key):
+ check_dict_or_set_indexers(key)
+ if type(key) is tuple:
+ key = (list(x) if is_iterator(x) else x for x in key)
+ key = tuple(com.apply_if_callable(x, self.obj) for x in key)
+ if self._is_scalar_access(key):
+ return self.obj._get_value(*key, takeable=self._takeable)
+ return self._getitem_tuple(key)
+ else:
+ # we by definition only have the 0th axis
+ axis = self.axis or 0
+
+ maybe_callable = com.apply_if_callable(key, self.obj)
+ maybe_callable = self._raise_callable_usage(key, maybe_callable)
+ return self._getitem_axis(maybe_callable, axis=axis)
+
+ def _is_scalar_access(self, key: tuple):
+ raise NotImplementedError
+
+ def _getitem_tuple(self, tup: tuple):
+ raise AbstractMethodError(self)
+
+ def _getitem_axis(self, key, axis: AxisInt):
+ raise NotImplementedError
+
+ def _has_valid_setitem_indexer(self, indexer) -> bool:
+ raise AbstractMethodError(self)
+
+ @final
+ def _getbool_axis(self, key, axis: AxisInt):
+ # caller is responsible for ensuring non-None axis
+ labels = self.obj._get_axis(axis)
+ key = check_bool_indexer(labels, key)
+ inds = key.nonzero()[0]
+ return self.obj.take(inds, axis=axis)
+
+
+@doc(IndexingMixin.loc)
+class _LocIndexer(_LocationIndexer):
+ _takeable: bool = False
+ _valid_types = (
+ "labels (MUST BE IN THE INDEX), slices of labels (BOTH "
+ "endpoints included! Can be slices of integers if the "
+ "index is integers), listlike of labels, boolean"
+ )
+
+ # -------------------------------------------------------------------
+ # Key Checks
+
+ @doc(_LocationIndexer._validate_key)
+ def _validate_key(self, key, axis: Axis) -> None:
+ # valid for a collection of labels (we check their presence later)
+ # slice of labels (where start-end in labels)
+ # slice of integers (only if in the labels)
+ # boolean not in slice and with boolean index
+ ax = self.obj._get_axis(axis)
+ if isinstance(key, bool) and not (
+ is_bool_dtype(ax.dtype)
+ or ax.dtype.name == "boolean"
+ or (
+ isinstance(ax, MultiIndex)
+ and is_bool_dtype(ax.get_level_values(0).dtype)
+ )
+ ):
+ raise KeyError(
+ f"{key}: boolean label can not be used without a boolean index"
+ )
+
+ if isinstance(key, slice) and (
+ isinstance(key.start, bool) or isinstance(key.stop, bool)
+ ):
+ raise TypeError(f"{key}: boolean values can not be used in a slice")
+
+ def _has_valid_setitem_indexer(self, indexer) -> bool:
+ return True
+
+ def _is_scalar_access(self, key: tuple) -> bool:
+ """
+ Returns
+ -------
+ bool
+ """
+ # this is a shortcut accessor to both .loc and .iloc
+ # that provide the equivalent access of .at and .iat
+ # a) avoid getting things via sections and (to minimize dtype changes)
+ # b) provide a performant path
+ if len(key) != self.ndim:
+ return False
+
+ for i, k in enumerate(key):
+ if not is_scalar(k):
+ return False
+
+ ax = self.obj.axes[i]
+ if isinstance(ax, MultiIndex):
+ return False
+
+ if isinstance(k, str) and ax._supports_partial_string_indexing:
+ # partial string indexing, df.loc['2000', 'A']
+ # should not be considered scalar
+ return False
+
+ if not ax._index_as_unique:
+ return False
+
+ return True
+
+ # -------------------------------------------------------------------
+ # MultiIndex Handling
+
+ def _multi_take_opportunity(self, tup: tuple) -> bool:
+ """
+ Check whether there is the possibility to use ``_multi_take``.
+
+ Currently the limit is that all axes being indexed, must be indexed with
+ list-likes.
+
+ Parameters
+ ----------
+ tup : tuple
+ Tuple of indexers, one per axis.
+
+ Returns
+ -------
+ bool
+ Whether the current indexing,
+ can be passed through `_multi_take`.
+ """
+ if not all(is_list_like_indexer(x) for x in tup):
+ return False
+
+ # just too complicated
+ return not any(com.is_bool_indexer(x) for x in tup)
+
+ def _multi_take(self, tup: tuple):
+ """
+ Create the indexers for the passed tuple of keys, and
+ executes the take operation. This allows the take operation to be
+ executed all at once, rather than once for each dimension.
+ Improving efficiency.
+
+ Parameters
+ ----------
+ tup : tuple
+ Tuple of indexers, one per axis.
+
+ Returns
+ -------
+ values: same type as the object being indexed
+ """
+ # GH 836
+ d = {
+ axis: self._get_listlike_indexer(key, axis)
+ for (key, axis) in zip(tup, self.obj._AXIS_ORDERS, strict=True)
+ }
+ return self.obj._reindex_with_indexers(d, allow_dups=True)
+
+ # -------------------------------------------------------------------
+
+ def _getitem_iterable(self, key, axis: AxisInt):
+ """
+ Index current object with an iterable collection of keys.
+
+ Parameters
+ ----------
+ key : iterable
+ Targeted labels.
+ axis : int
+ Dimension on which the indexing is being made.
+
+ Raises
+ ------
+ KeyError
+ If no key was found. Will change in the future to raise if not all
+ keys were found.
+
+ Returns
+ -------
+ scalar, DataFrame, or Series: indexed value(s).
+ """
+ # we assume that not com.is_bool_indexer(key), as that is
+ # handled before we get here.
+ self._validate_key(key, axis)
+
+ # A collection of keys
+ keyarr, indexer = self._get_listlike_indexer(key, axis)
+ return self.obj._reindex_with_indexers(
+ {axis: [keyarr, indexer]}, allow_dups=True
+ )
+
+ def _getitem_tuple(self, tup: tuple):
+ with suppress(IndexingError):
+ tup = self._expand_ellipsis(tup)
+ return self._getitem_lowerdim(tup)
+
+ # no multi-index, so validate all of the indexers
+ tup = self._validate_tuple_indexer(tup)
+
+ # ugly hack for GH #836
+ if self._multi_take_opportunity(tup):
+ return self._multi_take(tup)
+
+ return self._getitem_tuple_same_dim(tup)
+
+ def _get_label(self, label, axis: AxisInt):
+ # GH#5567 this will fail if the label is not present in the axis.
+ return self.obj.xs(label, axis=axis)
+
+ def _handle_lowerdim_multi_index_axis0(self, tup: tuple):
+ # we have an axis0 multi-index, handle or raise
+ axis = self.axis or 0
+ try:
+ # fast path for series or for tup devoid of slices
+ return self._get_label(tup, axis=axis)
+
+ except KeyError as ek:
+ # raise KeyError if number of indexers match
+ # else IndexingError will be raised
+ if self.ndim < len(tup) <= self.obj.index.nlevels:
+ raise ek
+ raise IndexingError("No label returned") from ek
+
+ def _getitem_axis(self, key, axis: AxisInt):
+ key = item_from_zerodim(key)
+ if is_iterator(key):
+ key = list(key)
+ if key is Ellipsis:
+ key = slice(None)
+
+ labels = self.obj._get_axis(axis)
+
+ if isinstance(key, tuple) and isinstance(labels, MultiIndex):
+ key = tuple(key)
+
+ if isinstance(key, slice):
+ self._validate_key(key, axis)
+ return self._get_slice_axis(key, axis=axis)
+ elif com.is_bool_indexer(key):
+ return self._getbool_axis(key, axis=axis)
+ elif is_list_like_indexer(key):
+ # an iterable multi-selection
+ if not (isinstance(key, tuple) and isinstance(labels, MultiIndex)):
+ if hasattr(key, "ndim") and key.ndim > 1:
+ raise ValueError("Cannot index with multidimensional key")
+
+ return self._getitem_iterable(key, axis=axis)
+
+ # nested tuple slicing
+ if is_nested_tuple(key, labels):
+ locs = labels.get_locs(key)
+ indexer: list[slice | npt.NDArray[np.intp]] = [slice(None)] * self.ndim
+ indexer[axis] = locs
+ return self.obj.iloc[tuple(indexer)]
+
+ # fall thru to straight lookup
+ self._validate_key(key, axis)
+ return self._get_label(key, axis=axis)
+
+ def _get_slice_axis(self, slice_obj: slice, axis: AxisInt):
+ """
+ This is pretty simple as we just have to deal with labels.
+ """
+ # caller is responsible for ensuring non-None axis
+ obj = self.obj
+ if not need_slice(slice_obj):
+ return obj.copy(deep=False)
+
+ labels = obj._get_axis(axis)
+ indexer = labels.slice_indexer(slice_obj.start, slice_obj.stop, slice_obj.step)
+
+ if isinstance(indexer, slice):
+ return self.obj._slice(indexer, axis=axis)
+ else:
+ # DatetimeIndex overrides Index.slice_indexer and may
+ # return a DatetimeIndex instead of a slice object.
+ return self.obj.take(indexer, axis=axis)
+
+ def _convert_to_indexer(self, key, axis: AxisInt):
+ """
+ Convert indexing key into something we can use to do actual fancy
+ indexing on an ndarray.
+
+ Examples
+ ix[:5] -> slice(0, 5)
+ ix[[1,2,3]] -> [1,2,3]
+ ix[['foo', 'bar', 'baz']] -> [i, j, k] (indices of foo, bar, baz)
+
+ Going by Zen of Python?
+ 'In the face of ambiguity, refuse the temptation to guess.'
+ raise AmbiguousIndexError with integer labels?
+ - No, prefer label-based indexing
+ """
+ labels = self.obj._get_axis(axis)
+
+ if isinstance(key, slice):
+ return labels._convert_slice_indexer(key, kind="loc")
+
+ if (
+ isinstance(key, tuple)
+ and not isinstance(labels, MultiIndex)
+ and self.ndim < 2
+ and len(key) > 1
+ ):
+ raise IndexingError("Too many indexers")
+
+ # Slices are not valid keys passed in by the user,
+ # even though they are hashable in Python 3.12
+ contains_slice = False
+ if isinstance(key, tuple):
+ contains_slice = any(isinstance(v, slice) for v in key)
+
+ if is_scalar(key) or (
+ isinstance(labels, MultiIndex) and is_hashable(key) and not contains_slice
+ ):
+ # Otherwise get_loc will raise InvalidIndexError
+
+ # if we are a label return me
+ try:
+ return labels.get_loc(key)
+ except LookupError:
+ if isinstance(key, tuple) and isinstance(labels, MultiIndex):
+ if len(key) == labels.nlevels:
+ return {"key": key}
+ raise
+ except InvalidIndexError:
+ # GH35015, using datetime as column indices raises exception
+ if not isinstance(labels, MultiIndex):
+ raise
+ except ValueError:
+ if not is_integer(key):
+ raise
+ return {"key": key}
+
+ if is_nested_tuple(key, labels):
+ if self.ndim == 1 and any(isinstance(k, tuple) for k in key):
+ # GH#35349 Raise if tuple in tuple for series
+ raise IndexingError("Too many indexers")
+ return labels.get_locs(key)
+
+ elif is_list_like_indexer(key):
+ if is_iterator(key):
+ key = list(key)
+
+ if com.is_bool_indexer(key):
+ key = check_bool_indexer(labels, key)
+ return key
+ else:
+ return self._get_listlike_indexer(key, axis)[1]
+ else:
+ try:
+ return labels.get_loc(key)
+ except LookupError:
+ # allow a not found key only if we are a setter
+ if not is_list_like_indexer(key):
+ return {"key": key}
+ raise
+
+ def _get_listlike_indexer(self, key, axis: AxisInt):
+ """
+ Transform a list-like of keys into a new index and an indexer.
+
+ Parameters
+ ----------
+ key : list-like
+ Targeted labels.
+ axis: int
+ Dimension on which the indexing is being made.
+
+ Raises
+ ------
+ KeyError
+ If at least one key was requested but none was found.
+
+ Returns
+ -------
+ keyarr: Index
+ New index (coinciding with 'key' if the axis is unique).
+ values : array-like
+ Indexer for the return object, -1 denotes keys not found.
+ """
+ ax = self.obj._get_axis(axis)
+ axis_name = self.obj._get_axis_name(axis)
+
+ keyarr, indexer = ax._get_indexer_strict(key, axis_name)
+
+ return keyarr, indexer
+
+
+@doc(IndexingMixin.iloc)
+class _iLocIndexer(_LocationIndexer):
+ _valid_types = (
+ "integer, integer slice (START point is INCLUDED, END "
+ "point is EXCLUDED), listlike of integers, boolean array"
+ )
+ _takeable = True
+
+ # -------------------------------------------------------------------
+ # Key Checks
+
+ def _validate_key(self, key, axis: AxisInt) -> None:
+ if com.is_bool_indexer(key):
+ if hasattr(key, "index") and isinstance(key.index, Index):
+ if key.index.inferred_type == "integer":
+ return
+ raise ValueError(
+ "iLocation based boolean indexing cannot use an indexable as a mask"
+ )
+ return
+
+ if isinstance(key, slice):
+ return
+ elif is_integer(key):
+ self._validate_integer(key, axis)
+ elif isinstance(key, tuple):
+ # a tuple should already have been caught by this point
+ # so don't treat a tuple as a valid indexer
+ raise IndexingError("Too many indexers")
+ elif is_list_like_indexer(key):
+ if isinstance(key, ABCSeries):
+ arr = key._values
+ elif is_array_like(key):
+ arr = key
+ else:
+ arr = np.array(key)
+ len_axis = len(self.obj._get_axis(axis))
+
+ # check that the key has a numeric dtype
+ if not is_numeric_dtype(arr.dtype):
+ raise IndexError(f".iloc requires numeric indexers, got {arr}")
+
+ if len(arr):
+ if isinstance(arr.dtype, ExtensionDtype):
+ arr_max = arr._reduce("max")
+ arr_min = arr._reduce("min")
+ else:
+ arr_max = np.max(arr)
+ arr_min = np.min(arr)
+
+ # check that the key does not exceed the maximum size
+ if arr_max >= len_axis or arr_min < -len_axis:
+ raise IndexError("positional indexers are out-of-bounds")
+ else:
+ raise ValueError(f"Can only index by location with a [{self._valid_types}]")
+
+ def _has_valid_setitem_indexer(self, indexer) -> bool:
+ """
+ Validate that a positional indexer cannot enlarge its target
+ will raise if needed, does not modify the indexer externally.
+
+ Returns
+ -------
+ bool
+ """
+ if isinstance(indexer, dict):
+ raise IndexError("iloc cannot enlarge its target object")
+
+ if isinstance(indexer, ABCDataFrame):
+ raise TypeError(
+ "DataFrame indexer for .iloc is not supported. "
+ "Consider using .loc with a DataFrame indexer for automatic alignment.",
+ )
+
+ if not isinstance(indexer, tuple):
+ indexer = _tuplify(self.ndim, indexer)
+
+ for ax, i in zip(self.obj.axes, indexer, strict=False):
+ if isinstance(i, slice):
+ # should check the stop slice?
+ pass
+ elif is_list_like_indexer(i):
+ # should check the elements?
+ pass
+ elif is_integer(i):
+ if i >= len(ax):
+ raise IndexError("iloc cannot enlarge its target object")
+ elif isinstance(i, dict):
+ raise IndexError("iloc cannot enlarge its target object")
+
+ return True
+
+ def _is_scalar_access(self, key: tuple) -> bool:
+ """
+ Returns
+ -------
+ bool
+ """
+ # this is a shortcut accessor to both .loc and .iloc
+ # that provide the equivalent access of .at and .iat
+ # a) avoid getting things via sections and (to minimize dtype changes)
+ # b) provide a performant path
+ if len(key) != self.ndim:
+ return False
+
+ return all(is_integer(k) for k in key)
+
+ def _validate_integer(self, key: int | np.integer, axis: AxisInt) -> None:
+ """
+ Check that 'key' is a valid position in the desired axis.
+
+ Parameters
+ ----------
+ key : int
+ Requested position.
+ axis : int
+ Desired axis.
+
+ Raises
+ ------
+ IndexError
+ If 'key' is not a valid position in axis 'axis'.
+ """
+ len_axis = len(self.obj._get_axis(axis))
+ if key >= len_axis or key < -len_axis:
+ raise IndexError("single positional indexer is out-of-bounds")
+
+ # -------------------------------------------------------------------
+
+ def _getitem_tuple(self, tup: tuple):
+ tup = self._validate_tuple_indexer(tup)
+ with suppress(IndexingError):
+ return self._getitem_lowerdim(tup)
+
+ return self._getitem_tuple_same_dim(tup)
+
+ def _get_list_axis(self, key, axis: AxisInt):
+ """
+ Return Series values by list or array of integers.
+
+ Parameters
+ ----------
+ key : list-like positional indexer
+ axis : int
+
+ Returns
+ -------
+ Series object
+
+ Notes
+ -----
+ `axis` can only be zero.
+ """
+ try:
+ return self.obj.take(key, axis=axis)
+ except IndexError as err:
+ # re-raise with different error message, e.g. test_getitem_ndarray_3d
+ raise IndexError("positional indexers are out-of-bounds") from err
+
+ def _getitem_axis(self, key, axis: AxisInt):
+ if key is Ellipsis:
+ key = slice(None)
+ elif isinstance(key, ABCDataFrame):
+ raise IndexError(
+ "DataFrame indexer is not allowed for .iloc\n"
+ "Consider using .loc for automatic alignment."
+ )
+
+ if isinstance(key, slice):
+ return self._get_slice_axis(key, axis=axis)
+
+ if is_iterator(key):
+ key = list(key)
+
+ if isinstance(key, list):
+ key = np.asarray(key)
+
+ if com.is_bool_indexer(key):
+ self._validate_key(key, axis)
+ return self._getbool_axis(key, axis=axis)
+
+ # a list of integers
+ elif is_list_like_indexer(key):
+ return self._get_list_axis(key, axis=axis)
+
+ # a single integer
+ else:
+ key = item_from_zerodim(key)
+ if not is_integer(key):
+ raise TypeError("Cannot index by location index with a non-integer key")
+
+ # validate the location
+ self._validate_integer(key, axis)
+
+ return self.obj._ixs(key, axis=axis)
+
+ def _get_slice_axis(self, slice_obj: slice, axis: AxisInt):
+ # caller is responsible for ensuring non-None axis
+ obj = self.obj
+
+ if not need_slice(slice_obj):
+ return obj.copy(deep=False)
+
+ labels = obj._get_axis(axis)
+ labels._validate_positional_slice(slice_obj)
+ return self.obj._slice(slice_obj, axis=axis)
+
+ def _convert_to_indexer(self, key: T, axis: AxisInt) -> T:
+ """
+ Much simpler as we only have to deal with our valid types.
+ """
+ return key
+
+ def _get_setitem_indexer(self, key):
+ # GH#32257 Fall through to let numpy do validation
+ if is_iterator(key):
+ key = list(key)
+
+ if self.axis is not None:
+ key = _tupleize_axis_indexer(self.ndim, self.axis, key)
+
+ return key
+
+ # -------------------------------------------------------------------
+
+ def _decide_split_path(self, indexer, value) -> bool:
+ """
+ Decide whether we will take a block-by-block path.
+ """
+ take_split_path = not self.obj._mgr.is_single_block
+
+ if not take_split_path and isinstance(value, ABCDataFrame):
+ # Avoid cast of values
+ take_split_path = not value._mgr.is_single_block
+
+ # if there is only one block/type, still have to take split path
+ # unless the block is one-dimensional or it can hold the value
+ if not take_split_path and len(self.obj._mgr.blocks) and self.ndim > 1:
+ # in case of dict, keys are indices
+ val = list(value.values()) if isinstance(value, dict) else value
+ arr = self.obj._mgr.blocks[0].values
+ take_split_path = not can_hold_element(
+ arr, extract_array(val, extract_numpy=True)
+ )
+
+ # if we have any multi-indexes that have non-trivial slices
+ # (not null slices) then we must take the split path, xref
+ # GH 10360, GH 27841
+ if isinstance(indexer, tuple) and len(indexer) == len(self.obj.axes):
+ for i, ax in zip(indexer, self.obj.axes, strict=True):
+ if isinstance(ax, MultiIndex) and not (
+ is_integer(i) or com.is_null_slice(i)
+ ):
+ take_split_path = True
+ break
+
+ return take_split_path
+
+ def _setitem_new_column(self, indexer, key, value, name: str) -> None:
+ """
+ _setitem_with_indexer cases that can go through DataFrame.__setitem__.
+ """
+ # add the new item, and set the value
+ # must have all defined axes if we have a scalar
+ # or a list-like on the non-info axes if we have a
+ # list-like
+ if not len(self.obj):
+ if not is_list_like_indexer(value):
+ raise ValueError(
+ "cannot set a frame with no defined index and a scalar"
+ )
+ self.obj[key] = value
+ return
+
+ # add a new item with the dtype setup
+ if com.is_null_slice(indexer[0]):
+ # We are setting an entire column
+ self.obj[key] = value
+ return
+ elif is_array_like(value):
+ # GH#42099
+ arr = extract_array(value, extract_numpy=True)
+ taker = -1 * np.ones(len(self.obj), dtype=np.intp)
+ empty_value = algos.take_nd(arr, taker)
+ if not isinstance(value, ABCSeries):
+ # if not Series (in which case we need to align),
+ # we can short-circuit
+ if isinstance(arr, np.ndarray) and arr.ndim == 1 and len(arr) == 1:
+ # NumPy 1.25 deprecation: https://github.com/numpy/numpy/pull/10615
+ arr = arr[0, ...]
+ empty_value[indexer[0]] = arr
+ self.obj[key] = empty_value
+ return
+
+ self.obj[key] = empty_value
+ elif not is_list_like(value):
+ self.obj[key] = construct_1d_array_from_inferred_fill_value(
+ value, len(self.obj)
+ )
+ else:
+ # FIXME: GH#42099#issuecomment-864326014
+ self.obj[key] = infer_fill_value(value)
+
+ new_indexer = convert_from_missing_indexer_tuple(indexer, self.obj.axes)
+ self._setitem_with_indexer(new_indexer, value, name)
+
+ return
+
+ def _setitem_with_indexer(self, indexer, value, name: str = "iloc") -> None:
+ """
+ _setitem_with_indexer is for setting values on a Series/DataFrame
+ using positional indexers.
+
+ If the relevant keys are not present, the Series/DataFrame may be
+ expanded.
+ """
+ info_axis = self.obj._info_axis_number
+ take_split_path = self._decide_split_path(indexer, value)
+
+ if isinstance(indexer, tuple):
+ nindexer = []
+ for i, idx in enumerate(indexer):
+ idx, missing = convert_missing_indexer(idx)
+ if missing:
+ # reindex the axis to the new value
+ # and set inplace
+ key = idx
+
+ # if this is the items axes, then take the main missing
+ # path first
+ # this correctly sets the dtype
+ # essentially this separates out the block that is needed
+ # to possibly be modified
+ if self.ndim > 1 and i == info_axis:
+ self._setitem_new_column(indexer, key, value, name=name)
+ return
+
+ # reindex the axis
+ index = self.obj._get_axis(i)
+ labels = index.insert(len(index), key)
+
+ # We are expanding the Series/DataFrame values to match
+ # the length of the new index `labels`. GH#40096 ensure
+ # this is valid even if the index has duplicates.
+ taker = np.arange(len(index) + 1, dtype=np.intp)
+ taker[-1] = -1
+ reindexers = {i: (labels, taker)}
+ new_obj = self.obj._reindex_with_indexers(
+ reindexers, allow_dups=True
+ )
+ self.obj._mgr = new_obj._mgr
+
+ nindexer.append(labels.get_loc(key))
+
+ else:
+ nindexer.append(idx)
+
+ indexer = tuple(nindexer)
+ else:
+ indexer, missing = convert_missing_indexer(indexer)
+
+ if missing:
+ self._setitem_with_indexer_missing(indexer, value)
+ return
+
+ if name == "loc":
+ # must come after setting of missing
+ indexer, value = self._maybe_mask_setitem_value(indexer, value)
+
+ # align and set the values
+ if take_split_path:
+ # We have to operate column-wise
+ self._setitem_with_indexer_split_path(indexer, value, name)
+ else:
+ self._setitem_single_block(indexer, value, name)
+
+ def _setitem_with_indexer_split_path(self, indexer, value, name: str):
+ """
+ Setitem column-wise.
+ """
+ # Above we only set take_split_path to True for 2D cases
+ assert self.ndim == 2
+
+ if not isinstance(indexer, tuple):
+ indexer = _tuplify(self.ndim, indexer)
+ if len(indexer) > self.ndim:
+ raise IndexError("too many indices for array")
+ if isinstance(indexer[0], np.ndarray) and indexer[0].ndim > 2:
+ raise ValueError(r"Cannot set values with ndim > 2")
+
+ if (isinstance(value, ABCSeries) and name != "iloc") or isinstance(value, dict):
+ from pandas import Series
+
+ value = self._align_series(indexer, Series(value))
+
+ # Ensure we have something we can iterate over
+ info_axis = indexer[1]
+ ilocs = self._ensure_iterable_column_indexer(info_axis)
+
+ pi = indexer[0]
+ lplane_indexer = length_of_indexer(pi, self.obj.index)
+ # lplane_indexer gives the expected length of obj[indexer[0]]
+
+ # we need an iterable, with an ndim of at least 1
+ # eg. don't pass through np.array(0)
+ if is_list_like_indexer(value) and getattr(value, "ndim", 1) > 0:
+ if isinstance(value, ABCDataFrame):
+ self._setitem_with_indexer_frame_value(indexer, value, name)
+
+ elif np.ndim(value) == 2:
+ # TODO: avoid np.ndim call in case it isn't an ndarray, since
+ # that will construct an ndarray, which will be wasteful
+ self._setitem_with_indexer_2d_value(indexer, value)
+
+ elif len(ilocs) == 1 and lplane_indexer == len(value) and not is_scalar(pi):
+ # We are setting multiple rows in a single column.
+ self._setitem_single_column(ilocs[0], value, pi)
+
+ elif len(ilocs) == 1 and 0 != lplane_indexer != len(value):
+ # We are trying to set N values into M entries of a single
+ # column, which is invalid for N != M
+ # Exclude zero-len for e.g. boolean masking that is all-false
+
+ if len(value) == 1 and not is_integer(info_axis):
+ # This is a case like df.iloc[:3, [1]] = [0]
+ # where we treat as df.iloc[:3, 1] = 0
+ return self._setitem_with_indexer((pi, info_axis[0]), value[0])
+
+ raise ValueError(
+ "Must have equal len keys and value when setting with an iterable"
+ )
+
+ elif lplane_indexer == 0 and len(value) == len(self.obj.index):
+ # We get here in one case via .loc with an all-False mask
+ pass
+
+ elif self._is_scalar_access(indexer) and is_object_dtype(
+ self.obj.dtypes._values[ilocs[0]]
+ ):
+ # We are setting nested data, only possible for object dtype data
+ self._setitem_single_column(indexer[1], value, pi)
+
+ elif len(ilocs) == len(value):
+ # We are setting multiple columns in a single row.
+ for loc, v in zip(ilocs, value, strict=True):
+ self._setitem_single_column(loc, v, pi)
+
+ elif len(ilocs) == 1 and com.is_null_slice(pi) and len(self.obj) == 0:
+ # This is a setitem-with-expansion, see
+ # test_loc_setitem_empty_append_expands_rows_mixed_dtype
+ # e.g. df = DataFrame(columns=["x", "y"])
+ # df["x"] = df["x"].astype(np.int64)
+ # df.loc[:, "x"] = [1, 2, 3]
+ self._setitem_single_column(ilocs[0], value, pi)
+
+ else:
+ raise ValueError(
+ "Must have equal len keys and value when setting with an iterable"
+ )
+
+ else:
+ # scalar value
+ for loc in ilocs:
+ self._setitem_single_column(loc, value, pi)
+
+ def _setitem_with_indexer_2d_value(self, indexer, value) -> None:
+ # We get here with np.ndim(value) == 2, excluding DataFrame,
+ # which goes through _setitem_with_indexer_frame_value
+ pi = indexer[0]
+
+ ilocs = self._ensure_iterable_column_indexer(indexer[1])
+
+ if not is_array_like(value):
+ # cast lists to array
+ value = np.array(value, dtype=object)
+ if len(ilocs) != value.shape[1]:
+ raise ValueError(
+ "Must have equal len keys and value when setting with an ndarray"
+ )
+
+ for i, loc in enumerate(ilocs):
+ value_col = value[:, i]
+ if is_object_dtype(value_col.dtype):
+ # casting to list so that we do type inference in setitem_single_column
+ value_col = value_col.tolist()
+ self._setitem_single_column(loc, value_col, pi)
+
+ def _setitem_with_indexer_frame_value(
+ self, indexer, value: DataFrame, name: str
+ ) -> None:
+ ilocs = self._ensure_iterable_column_indexer(indexer[1])
+
+ sub_indexer = list(indexer)
+ pi = indexer[0]
+
+ multiindex_indexer = isinstance(self.obj.columns, MultiIndex)
+
+ unique_cols = value.columns.is_unique
+
+ # We do not want to align the value in case of iloc GH#37728
+ if name == "iloc":
+ for i, loc in enumerate(ilocs):
+ val = value.iloc[:, i]
+ self._setitem_single_column(loc, val, pi)
+
+ elif not unique_cols and value.columns.equals(self.obj.columns):
+ # We assume we are already aligned, see
+ # test_iloc_setitem_frame_duplicate_columns_multiple_blocks
+ for loc in ilocs:
+ item = self.obj.columns[loc]
+ if item in value:
+ sub_indexer[1] = item
+ val = self._align_series(
+ tuple(sub_indexer),
+ value.iloc[:, loc],
+ multiindex_indexer,
+ )
+ else:
+ val = np.nan
+
+ self._setitem_single_column(loc, val, pi)
+
+ elif not unique_cols:
+ raise ValueError("Setting with non-unique columns is not allowed.")
+
+ else:
+ for loc in ilocs:
+ item = self.obj.columns[loc]
+ if item in value:
+ sub_indexer[1] = item
+ val = self._align_series(
+ tuple(sub_indexer),
+ value[item],
+ multiindex_indexer,
+ using_cow=True,
+ )
+ else:
+ val = np.nan
+
+ self._setitem_single_column(loc, val, pi)
+
+ def _setitem_single_column(self, loc: int, value, plane_indexer) -> None:
+ """
+
+ Parameters
+ ----------
+ loc : int
+ Indexer for column position
+ plane_indexer : int, slice, listlike[int]
+ The indexer we use for setitem along axis=0.
+ """
+ pi = plane_indexer
+
+ is_full_setter = com.is_null_slice(pi) or com.is_full_slice(pi, len(self.obj))
+
+ is_null_setter = com.is_empty_slice(pi) or (is_array_like(pi) and len(pi) == 0)
+
+ if is_null_setter:
+ # no-op, don't cast dtype later
+ return
+
+ elif is_full_setter:
+ try:
+ self.obj._mgr.column_setitem(
+ loc, plane_indexer, value, inplace_only=True
+ )
+ except (ValueError, TypeError, LossySetitemError) as exc:
+ # If we're setting an entire column and we can't do it inplace,
+ # then we can use value's dtype (or inferred dtype)
+ # instead of object
+ dtype = self.obj.dtypes.iloc[loc]
+ if dtype not in (np.void, object) and not self.obj.empty:
+ # - Exclude np.void, as that is a special case for expansion.
+ # We want to raise for
+ # df = pd.DataFrame({'a': [1, 2]})
+ # df.loc[:, 'a'] = .3
+ # but not for
+ # df = pd.DataFrame({'a': [1, 2]})
+ # df.loc[:, 'b'] = .3
+ # - Exclude `object`, as then no upcasting happens.
+ # - Exclude empty initial object with enlargement,
+ # as then there's nothing to be inconsistent with.
+ raise TypeError(
+ f"Invalid value '{value}' for dtype '{dtype}'"
+ ) from exc
+ self.obj.isetitem(loc, value)
+ else:
+ # set value into the column (first attempting to operate inplace, then
+ # falling back to casting if necessary)
+ dtype = self.obj.dtypes.iloc[loc]
+ if dtype == np.void:
+ # This means we're expanding, with multiple columns, e.g.
+ # df = pd.DataFrame({'A': [1,2,3], 'B': [4,5,6]})
+ # df.loc[df.index <= 2, ['F', 'G']] = (1, 'abc')
+ # Columns F and G will initially be set to np.void.
+ # Here, we replace those temporary `np.void` columns with
+ # columns of the appropriate dtype, based on `value`.
+ self.obj.iloc[:, loc] = construct_1d_array_from_inferred_fill_value(
+ value, len(self.obj)
+ )
+ self.obj._mgr.column_setitem(loc, plane_indexer, value)
+
+ def _setitem_single_block(self, indexer, value, name: str) -> None:
+ """
+ _setitem_with_indexer for the case when we have a single Block.
+ """
+ from pandas import Series
+
+ if (isinstance(value, ABCSeries) and name != "iloc") or isinstance(value, dict):
+ # TODO(EA): ExtensionBlock.setitem this causes issues with
+ # setting for extensionarrays that store dicts. Need to decide
+ # if it's worth supporting that.
+ value = self._align_series(indexer, Series(value))
+
+ info_axis = self.obj._info_axis_number
+ item_labels = self.obj._get_axis(info_axis)
+ if isinstance(indexer, tuple):
+ # if we are setting on the info axis ONLY
+ # set using those methods to avoid block-splitting
+ # logic here
+ if (
+ self.ndim == len(indexer) == 2
+ and is_integer(indexer[1])
+ and com.is_null_slice(indexer[0])
+ ):
+ col = item_labels[indexer[info_axis]]
+ if len(item_labels.get_indexer_for([col])) == 1:
+ # e.g. test_loc_setitem_empty_append_expands_rows
+ loc = item_labels.get_loc(col)
+ self._setitem_single_column(loc, value, indexer[0])
+ return
+
+ indexer = maybe_convert_ix(*indexer) # e.g. test_setitem_frame_align
+
+ if isinstance(value, ABCDataFrame) and name != "iloc":
+ value = self._align_frame(indexer, value)._values
+
+ # actually do the set
+ self.obj._mgr = self.obj._mgr.setitem(indexer=indexer, value=value)
+
+ def _setitem_with_indexer_missing(self, indexer, value):
+ """
+ Insert new row(s) or column(s) into the Series or DataFrame.
+ """
+ from pandas import Series
+
+ # reindex the axis to the new value
+ # and set inplace
+ if self.ndim == 1:
+ index = self.obj.index
+ new_index = index.insert(len(index), indexer)
+
+ # we have a coerced indexer, e.g. a float
+ # that matches in an int64 Index, so
+ # we will not create a duplicate index, rather
+ # index to that element
+ # e.g. 0.0 -> 0
+ # GH#12246
+ if index.is_unique:
+ # pass new_index[-1:] instead if [new_index[-1]]
+ # so that we retain dtype
+ new_indexer = index.get_indexer(new_index[-1:])
+ if (new_indexer != -1).any():
+ # We get only here with loc, so can hard code
+ return self._setitem_with_indexer(new_indexer, value, "loc")
+
+ # this preserves dtype of the value and of the object
+ if not is_scalar(value):
+ new_dtype = None
+
+ elif is_valid_na_for_dtype(value, self.obj.dtype):
+ if not is_object_dtype(self.obj.dtype):
+ # Every NA value is suitable for object, no conversion needed
+ value = na_value_for_dtype(self.obj.dtype, compat=False)
+
+ new_dtype = maybe_promote(self.obj.dtype, value)[0]
+
+ elif isna(value):
+ new_dtype = None
+ elif not self.obj.empty and not is_object_dtype(self.obj.dtype):
+ # We should not cast, if we have object dtype because we can
+ # set timedeltas into object series
+ curr_dtype = self.obj.dtype
+ curr_dtype = getattr(curr_dtype, "numpy_dtype", curr_dtype)
+ new_dtype = maybe_promote(curr_dtype, value)[0]
+ else:
+ new_dtype = None
+
+ new_values = Series([value], dtype=new_dtype)._values
+
+ if len(self.obj._values):
+ # GH#22717 handle casting compatibility that np.concatenate
+ # does incorrectly
+ new_values = concat_compat([self.obj._values, new_values])
+ self.obj._mgr = self.obj._constructor(
+ new_values, index=new_index, name=self.obj.name
+ )._mgr
+
+ elif self.ndim == 2:
+ if not len(self.obj.columns):
+ # no columns and scalar
+ raise ValueError("cannot set a frame with no defined columns")
+
+ has_dtype = hasattr(value, "dtype")
+ if isinstance(value, ABCSeries):
+ # append a Series
+ value = value.reindex(index=self.obj.columns)
+ value.name = indexer
+ elif isinstance(value, dict):
+ value = Series(
+ value, index=self.obj.columns, name=indexer, dtype=object
+ )
+ else:
+ # a list-list
+ if is_list_like_indexer(value):
+ # must have conforming columns
+ if len(value) != len(self.obj.columns):
+ raise ValueError("cannot set a row with mismatched columns")
+
+ value = Series(value, index=self.obj.columns, name=indexer)
+
+ if not len(self.obj):
+ # We will ignore the existing dtypes instead of using
+ # internals.concat logic
+ df = value.to_frame().T
+
+ idx = self.obj.index
+ if isinstance(idx, MultiIndex):
+ name = idx.names
+ else:
+ name = idx.name
+
+ df.index = Index([indexer], name=name)
+ if not has_dtype:
+ # i.e. if we already had a Series or ndarray, keep that
+ # dtype. But if we had a list or dict, then do inference
+ df = df.infer_objects()
+ self.obj._mgr = df._mgr
+ else:
+ self.obj._mgr = self.obj._append_internal(value)._mgr
+
+ def _ensure_iterable_column_indexer(self, column_indexer):
+ """
+ Ensure that our column indexer is something that can be iterated over.
+ """
+ ilocs: Sequence[int | np.integer] | np.ndarray | range
+ if is_integer(column_indexer):
+ ilocs = [column_indexer]
+ elif isinstance(column_indexer, slice):
+ ilocs = range(len(self.obj.columns))[column_indexer]
+ elif (
+ isinstance(column_indexer, np.ndarray) and column_indexer.dtype.kind == "b"
+ ):
+ ilocs = np.arange(len(column_indexer))[column_indexer]
+ else:
+ ilocs = column_indexer
+ return ilocs
+
+ def _align_series(
+ self,
+ indexer,
+ ser: Series,
+ multiindex_indexer: bool = False,
+ using_cow: bool = False,
+ ):
+ """
+ Parameters
+ ----------
+ indexer : tuple, slice, scalar
+ Indexer used to get the locations that will be set to `ser`.
+ ser : pd.Series
+ Values to assign to the locations specified by `indexer`.
+ multiindex_indexer : bool, optional
+ Defaults to False. Should be set to True if `indexer` was from
+ a `pd.MultiIndex`, to avoid unnecessary broadcasting.
+
+ Returns
+ -------
+ `np.array` of `ser` broadcast to the appropriate shape for assignment
+ to the locations selected by `indexer`
+ """
+ if isinstance(indexer, (slice, np.ndarray, list, Index)):
+ indexer = (indexer,)
+
+ if isinstance(indexer, tuple):
+ # flatten np.ndarray indexers
+ if (
+ len(indexer) == 2
+ and isinstance(indexer[1], np.ndarray)
+ and indexer[1].dtype == np.bool_
+ ):
+ indexer = (indexer[0], np.where(indexer[1])[0])
+
+ def ravel(i):
+ return i.ravel() if isinstance(i, np.ndarray) else i
+
+ indexer = tuple(map(ravel, indexer))
+ aligners = [not com.is_null_slice(idx) for idx in indexer]
+ sum_aligners = sum(aligners)
+ single_aligner = sum_aligners == 1
+ is_frame = self.ndim == 2
+ obj = self.obj
+
+ # are we a single alignable value on a non-primary
+ # dim (e.g. panel: 1,2, or frame: 0) ?
+ # hence need to align to a single axis dimension
+ # rather that find all valid dims
+
+ # frame
+ if is_frame:
+ single_aligner = single_aligner and aligners[0]
+
+ # we have a frame, with multiple indexers on both axes; and a
+ # series, so need to broadcast (see GH5206)
+ if all(is_sequence(_) or isinstance(_, slice) for _ in indexer):
+ ser_values = ser.reindex(obj.axes[0][indexer[0]])._values
+
+ # single indexer
+ if len(indexer) > 1 and not multiindex_indexer:
+ if isinstance(indexer[1], slice):
+ len_indexer = len(obj.axes[1][indexer[1]])
+ else:
+ len_indexer = len(indexer[1])
+ ser_values = (
+ np.tile(ser_values, len_indexer).reshape(len_indexer, -1).T
+ )
+
+ return ser_values
+
+ for i, idx in enumerate(indexer):
+ ax = obj.axes[i]
+
+ # multiple aligners (or null slices)
+ if is_sequence(idx) or isinstance(idx, slice):
+ if single_aligner and com.is_null_slice(idx):
+ continue
+ new_ix = ax[idx]
+ if not is_list_like_indexer(new_ix):
+ new_ix = Index([new_ix])
+ else:
+ new_ix = Index(new_ix)
+ if not len(new_ix) or ser.index.equals(new_ix):
+ if using_cow:
+ return ser
+ return ser._values.copy()
+
+ return ser.reindex(new_ix)._values
+
+ # 2 dims
+ elif single_aligner:
+ # reindex along index
+ ax = self.obj.axes[1]
+ if ser.index.equals(ax) or not len(ax):
+ return ser._values.copy()
+ return ser.reindex(ax)._values
+
+ elif is_integer(indexer) and self.ndim == 1:
+ if is_object_dtype(self.obj.dtype):
+ return ser
+ ax = self.obj._get_axis(0)
+
+ if ser.index.equals(ax):
+ return ser._values.copy()
+
+ return ser.reindex(ax)._values[indexer]
+
+ elif is_integer(indexer):
+ ax = self.obj._get_axis(1)
+
+ if ser.index.equals(ax):
+ return ser._values.copy()
+
+ return ser.reindex(ax)._values
+
+ raise ValueError("Incompatible indexer with Series")
+
+ def _align_frame(self, indexer, df: DataFrame) -> DataFrame:
+ is_frame = self.ndim == 2
+
+ if isinstance(indexer, tuple):
+ idx, cols = None, None
+ sindexers = []
+ for i, ix in enumerate(indexer):
+ ax = self.obj.axes[i]
+ if is_sequence(ix) or isinstance(ix, slice):
+ if isinstance(ix, np.ndarray):
+ ix = ix.reshape(-1)
+ if idx is None:
+ idx = ax[ix]
+ elif cols is None:
+ cols = ax[ix]
+ else:
+ break
+ else:
+ sindexers.append(i)
+
+ if idx is not None and cols is not None:
+ if df.index.equals(idx) and df.columns.equals(cols):
+ val = df.copy()
+ else:
+ val = df.reindex(idx, columns=cols)
+ return val
+
+ elif (isinstance(indexer, slice) or is_list_like_indexer(indexer)) and is_frame:
+ ax = self.obj.index[indexer]
+ if df.index.equals(ax):
+ val = df.copy()
+ else:
+ # we have a multi-index and are trying to align
+ # with a particular, level GH3738
+ if (
+ isinstance(ax, MultiIndex)
+ and isinstance(df.index, MultiIndex)
+ and ax.nlevels != df.index.nlevels
+ ):
+ raise TypeError(
+ "cannot align on a multi-index with out "
+ "specifying the join levels"
+ )
+
+ val = df.reindex(index=ax)
+ return val
+
+ raise ValueError("Incompatible indexer with DataFrame")
+
+
+class _ScalarAccessIndexer(NDFrameIndexerBase):
+ """
+ Access scalars quickly.
+ """
+
+ # sub-classes need to set _takeable
+ _takeable: bool
+
+ def _convert_key(self, key):
+ raise AbstractMethodError(self)
+
+ def __getitem__(self, key):
+ if not isinstance(key, tuple):
+ # we could have a convertible item here (e.g. Timestamp)
+ if not is_list_like_indexer(key):
+ key = (key,)
+ else:
+ raise ValueError("Invalid call for scalar access (getting)!")
+
+ key = self._convert_key(key)
+ return self.obj._get_value(*key, takeable=self._takeable)
+
+ def __setitem__(self, key, value) -> None:
+ if isinstance(key, tuple):
+ key = tuple(com.apply_if_callable(x, self.obj) for x in key)
+ else:
+ # scalar callable may return tuple
+ key = com.apply_if_callable(key, self.obj)
+
+ if not isinstance(key, tuple):
+ key = _tuplify(self.ndim, key)
+ key = list(self._convert_key(key))
+ if len(key) != self.ndim:
+ raise ValueError("Not enough indexers for scalar access (setting)!")
+
+ self.obj._set_value(*key, value=value, takeable=self._takeable)
+
+
+@doc(IndexingMixin.at)
+class _AtIndexer(_ScalarAccessIndexer):
+ _takeable = False
+
+ def _convert_key(self, key):
+ """
+ Require they keys to be the same type as the index. (so we don't
+ fallback)
+ """
+ # GH 26989
+ # For series, unpacking key needs to result in the label.
+ # This is already the case for len(key) == 1; e.g. (1,)
+ if self.ndim == 1 and len(key) > 1:
+ key = (key,)
+
+ return key
+
+ @property
+ def _axes_are_unique(self) -> bool:
+ # Only relevant for self.ndim == 2
+ assert self.ndim == 2
+ return self.obj.index.is_unique and self.obj.columns.is_unique
+
+ def __getitem__(self, key):
+ if self.ndim == 2 and not self._axes_are_unique:
+ # GH#33041 fall back to .loc
+ if not isinstance(key, tuple) or not all(is_scalar(x) for x in key):
+ raise ValueError("Invalid call for scalar access (getting)!")
+ return self.obj.loc[key]
+
+ return super().__getitem__(key)
+
+ def __setitem__(self, key, value) -> None:
+ if not CHAINED_WARNING_DISABLED:
+ if sys.getrefcount(self.obj) <= REF_COUNT_IDX:
+ warnings.warn(
+ _chained_assignment_msg, ChainedAssignmentError, stacklevel=2
+ )
+
+ if self.ndim == 2 and not self._axes_are_unique:
+ # GH#33041 fall back to .loc
+ if not isinstance(key, tuple) or not all(is_scalar(x) for x in key):
+ raise ValueError("Invalid call for scalar access (setting)!")
+
+ self.obj.loc[key] = value
+ return
+
+ return super().__setitem__(key, value)
+
+
+@doc(IndexingMixin.iat)
+class _iAtIndexer(_ScalarAccessIndexer):
+ _takeable = True
+
+ def _convert_key(self, key):
+ """
+ Require integer args. (and convert to label arguments)
+ """
+ for i in key:
+ if not is_integer(i):
+ raise ValueError("iAt based indexing can only have integer indexers")
+ return key
+
+ def __setitem__(self, key, value) -> None:
+ if not CHAINED_WARNING_DISABLED:
+ if sys.getrefcount(self.obj) <= REF_COUNT_IDX:
+ warnings.warn(
+ _chained_assignment_msg, ChainedAssignmentError, stacklevel=2
+ )
+
+ return super().__setitem__(key, value)
+
+
+def _tuplify(ndim: int, loc: Hashable) -> tuple[Hashable | slice, ...]:
+ """
+ Given an indexer for the first dimension, create an equivalent tuple
+ for indexing over all dimensions.
+
+ Parameters
+ ----------
+ ndim : int
+ loc : object
+
+ Returns
+ -------
+ tuple
+ """
+ _tup: list[Hashable | slice]
+ _tup = [slice(None, None) for _ in range(ndim)]
+ _tup[0] = loc
+ return tuple(_tup)
+
+
+def _tupleize_axis_indexer(ndim: int, axis: AxisInt, key) -> tuple:
+ """
+ If we have an axis, adapt the given key to be axis-independent.
+ """
+ new_key = [slice(None)] * ndim
+ new_key[axis] = key
+ return tuple(new_key)
+
+
+def check_bool_indexer(index: Index, key) -> np.ndarray:
+ """
+ Check if key is a valid boolean indexer for an object with such index and
+ perform reindexing or conversion if needed.
+
+ This function assumes that is_bool_indexer(key) == True.
+
+ Parameters
+ ----------
+ index : Index
+ Index of the object on which the indexing is done.
+ key : list-like
+ Boolean indexer to check.
+
+ Returns
+ -------
+ np.array
+ Resulting key.
+
+ Raises
+ ------
+ IndexError
+ If the key does not have the same length as index.
+ IndexingError
+ If the index of the key is unalignable to index.
+ """
+ result = key
+ if isinstance(key, ABCSeries) and not key.index.equals(index):
+ indexer = result.index.get_indexer_for(index)
+ if -1 in indexer:
+ raise IndexingError(
+ "Unalignable boolean Series provided as "
+ "indexer (index of the boolean Series and of "
+ "the indexed object do not match)."
+ )
+
+ result = result.take(indexer)
+
+ # fall through for boolean
+ if not isinstance(result.dtype, ExtensionDtype):
+ return result.astype(bool)._values
+
+ if is_object_dtype(key):
+ # key might be object-dtype bool, check_array_indexer needs bool array
+ result = np.asarray(result, dtype=bool)
+ elif not is_array_like(result):
+ # GH 33924
+ # key may contain nan elements, check_array_indexer needs bool array
+ result = pd_array(result, dtype=bool)
+ return check_array_indexer(index, result)
+
+
+def convert_missing_indexer(indexer):
+ """
+ Reverse convert a missing indexer, which is a dict
+ return the scalar indexer and a boolean indicating if we converted
+ """
+ if isinstance(indexer, dict):
+ # a missing key (but not a tuple indexer)
+ indexer = indexer["key"]
+
+ if isinstance(indexer, bool):
+ raise KeyError("cannot use a single bool to index into setitem")
+ return indexer, True
+
+ return indexer, False
+
+
+def convert_from_missing_indexer_tuple(indexer: tuple, axes: list[Index]) -> tuple:
+ """
+ Create a filtered indexer that doesn't have any missing indexers.
+ """
+
+ def get_indexer(_i, _idx):
+ return axes[_i].get_loc(_idx["key"]) if isinstance(_idx, dict) else _idx
+
+ return tuple(get_indexer(_i, _idx) for _i, _idx in enumerate(indexer))
+
+
+def maybe_convert_ix(*args):
+ """
+ We likely want to take the cross-product.
+ """
+ for arg in args:
+ if not isinstance(arg, (np.ndarray, list, ABCSeries, Index)):
+ return args
+ return np.ix_(*args)
+
+
+def is_nested_tuple(tup, labels) -> bool:
+ """
+ Returns
+ -------
+ bool
+ """
+ # check for a compatible nested tuple and multiindexes among the axes
+ if not isinstance(tup, tuple):
+ return False
+
+ for k in tup:
+ if is_list_like(k) or isinstance(k, slice):
+ return isinstance(labels, MultiIndex)
+
+ return False
+
+
+def is_label_like(key) -> bool:
+ """
+ Returns
+ -------
+ bool
+ """
+ # select a label or row
+ return (
+ not isinstance(key, slice)
+ and not is_list_like_indexer(key)
+ and key is not Ellipsis
+ )
+
+
+def need_slice(obj: slice) -> bool:
+ """
+ Returns
+ -------
+ bool
+ """
+ return (
+ obj.start is not None
+ or obj.stop is not None
+ or (obj.step is not None and obj.step != 1)
+ )
+
+
+def check_dict_or_set_indexers(key) -> None:
+ """
+ Check if the indexer is or contains a dict or set, which is no longer allowed.
+ """
+ if isinstance(key, set) or (
+ isinstance(key, tuple) and any(isinstance(x, set) for x in key)
+ ):
+ raise TypeError(
+ "Passing a set as an indexer is not supported. Use a list instead."
+ )
+
+ if isinstance(key, dict) or (
+ isinstance(key, tuple) and any(isinstance(x, dict) for x in key)
+ ):
+ raise TypeError(
+ "Passing a dict as an indexer is not supported. Use a list instead."
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/buffer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/buffer.py
new file mode 100644
index 0000000000000000000000000000000000000000..8953360a91c8efa2b8571290513b1ebb48b00e9d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/buffer.py
@@ -0,0 +1,135 @@
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ Any,
+)
+
+from pandas.core.interchange.dataframe_protocol import (
+ Buffer,
+ DlpackDeviceType,
+)
+
+if TYPE_CHECKING:
+ import numpy as np
+ import pyarrow as pa
+
+
+class PandasBuffer(Buffer):
+ """
+ Data in the buffer is guaranteed to be contiguous in memory.
+ """
+
+ def __init__(self, x: np.ndarray, allow_copy: bool = True) -> None:
+ """
+ Handle only regular columns (= numpy arrays) for now.
+ """
+ if x.strides[0] and not x.strides == (x.dtype.itemsize,):
+ # The protocol does not support strided buffers, so a copy is
+ # necessary. If that's not allowed, we need to raise an exception.
+ if allow_copy:
+ x = x.copy()
+ else:
+ raise RuntimeError(
+ "Exports cannot be zero-copy in the case of a non-contiguous buffer"
+ )
+
+ # Store the numpy array in which the data resides as a private
+ # attribute, so we can use it to retrieve the public attributes
+ self._x = x
+
+ @property
+ def bufsize(self) -> int:
+ """
+ Buffer size in bytes.
+ """
+ return self._x.size * self._x.dtype.itemsize
+
+ @property
+ def ptr(self) -> int:
+ """
+ Pointer to start of the buffer as an integer.
+ """
+ return self._x.__array_interface__["data"][0]
+
+ def __dlpack__(self) -> Any:
+ """
+ Represent this structure as DLPack interface.
+ """
+ return self._x.__dlpack__()
+
+ def __dlpack_device__(self) -> tuple[DlpackDeviceType, int | None]:
+ """
+ Device type and device ID for where the data in the buffer resides.
+ """
+ return (DlpackDeviceType.CPU, None)
+
+ def __repr__(self) -> str:
+ return (
+ "PandasBuffer("
+ + str(
+ {
+ "bufsize": self.bufsize,
+ "ptr": self.ptr,
+ "device": self.__dlpack_device__()[0].name,
+ }
+ )
+ + ")"
+ )
+
+
+class PandasBufferPyarrow(Buffer):
+ """
+ Data in the buffer is guaranteed to be contiguous in memory.
+ """
+
+ def __init__(
+ self,
+ buffer: pa.Buffer,
+ *,
+ length: int,
+ ) -> None:
+ """
+ Handle pyarrow chunked arrays.
+ """
+ self._buffer = buffer
+ self._length = length
+
+ @property
+ def bufsize(self) -> int:
+ """
+ Buffer size in bytes.
+ """
+ return self._buffer.size
+
+ @property
+ def ptr(self) -> int:
+ """
+ Pointer to start of the buffer as an integer.
+ """
+ return self._buffer.address
+
+ def __dlpack__(self) -> Any:
+ """
+ Represent this structure as DLPack interface.
+ """
+ raise NotImplementedError
+
+ def __dlpack_device__(self) -> tuple[DlpackDeviceType, int | None]:
+ """
+ Device type and device ID for where the data in the buffer resides.
+ """
+ return (DlpackDeviceType.CPU, None)
+
+ def __repr__(self) -> str:
+ return (
+ "PandasBuffer[pyarrow]("
+ + str(
+ {
+ "bufsize": self.bufsize,
+ "ptr": self.ptr,
+ "device": "CPU",
+ }
+ )
+ + ")"
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/column.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/column.py
new file mode 100644
index 0000000000000000000000000000000000000000..00a8e742c6fa948806cada50aef8bfa9bf036034
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/column.py
@@ -0,0 +1,479 @@
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ Any,
+)
+
+import numpy as np
+
+from pandas._config import using_python_scalars
+
+from pandas._libs.lib import infer_dtype
+from pandas._libs.tslibs import iNaT
+from pandas.errors import NoBufferPresent
+from pandas.util._decorators import cache_readonly
+
+from pandas.core.dtypes.dtypes import BaseMaskedDtype
+
+import pandas as pd
+from pandas import (
+ ArrowDtype,
+ DatetimeTZDtype,
+)
+from pandas.api.types import is_string_dtype
+from pandas.core.interchange.buffer import (
+ PandasBuffer,
+ PandasBufferPyarrow,
+)
+from pandas.core.interchange.dataframe_protocol import (
+ Column,
+ ColumnBuffers,
+ ColumnNullType,
+ DtypeKind,
+)
+from pandas.core.interchange.utils import (
+ ArrowCTypes,
+ Endianness,
+ dtype_to_arrow_c_fmt,
+)
+
+if TYPE_CHECKING:
+ from pandas.core.interchange.dataframe_protocol import Buffer
+
+_NP_KINDS = {
+ "i": DtypeKind.INT,
+ "u": DtypeKind.UINT,
+ "f": DtypeKind.FLOAT,
+ "b": DtypeKind.BOOL,
+ "U": DtypeKind.STRING,
+ "M": DtypeKind.DATETIME,
+ "m": DtypeKind.DATETIME,
+}
+
+_NULL_DESCRIPTION = {
+ DtypeKind.FLOAT: (ColumnNullType.USE_NAN, None),
+ DtypeKind.DATETIME: (ColumnNullType.USE_SENTINEL, iNaT),
+ DtypeKind.INT: (ColumnNullType.NON_NULLABLE, None),
+ DtypeKind.UINT: (ColumnNullType.NON_NULLABLE, None),
+ DtypeKind.BOOL: (ColumnNullType.NON_NULLABLE, None),
+ # Null values for categoricals are stored as `-1` sentinel values
+ # in the category date (e.g., `col.values.codes` is int8 np.ndarray)
+ DtypeKind.CATEGORICAL: (ColumnNullType.USE_SENTINEL, -1),
+ # follow Arrow in using 1 as valid value and 0 for missing/null value
+ DtypeKind.STRING: (ColumnNullType.USE_BYTEMASK, 0),
+}
+
+_NO_VALIDITY_BUFFER = {
+ ColumnNullType.NON_NULLABLE: "This column is non-nullable",
+ ColumnNullType.USE_NAN: "This column uses NaN as null",
+ ColumnNullType.USE_SENTINEL: "This column uses a sentinel value",
+}
+
+
+class PandasColumn(Column):
+ """
+ A column object, with only the methods and properties required by the
+ interchange protocol defined.
+ A column can contain one or more chunks. Each chunk can contain up to three
+ buffers - a data buffer, a mask buffer (depending on null representation),
+ and an offsets buffer (if variable-size binary; e.g., variable-length
+ strings).
+ Note: this Column object can only be produced by ``__dataframe__``, so
+ doesn't need its own version or ``__column__`` protocol.
+ """
+
+ def __init__(self, column: pd.Series, allow_copy: bool = True) -> None:
+ """
+ Note: doesn't deal with extension arrays yet, just assume a regular
+ Series/ndarray for now.
+ """
+ if isinstance(column, pd.DataFrame):
+ raise TypeError(
+ "Expected a Series, got a DataFrame. This likely happened "
+ "because you called __dataframe__ on a DataFrame which, "
+ "after converting column names to string, resulted in duplicated "
+ f"names: {column.columns}. Please rename these columns before "
+ "using the interchange protocol."
+ )
+ if not isinstance(column, pd.Series):
+ raise NotImplementedError(f"Columns of type {type(column)} not handled yet")
+
+ # Store the column as a private attribute
+ self._col = column
+ self._allow_copy = allow_copy
+
+ def size(self) -> int:
+ """
+ Size of the column, in elements.
+ """
+ return self._col.size
+
+ @property
+ def offset(self) -> int:
+ """
+ Offset of first element. Always zero.
+ """
+ # TODO: chunks are implemented now, probably this should return something
+ return 0
+
+ @cache_readonly
+ def dtype(self) -> tuple[DtypeKind, int, str, str]:
+ dtype = self._col.dtype
+
+ if isinstance(dtype, pd.CategoricalDtype):
+ codes = self._col.values.codes
+ (
+ _,
+ bitwidth,
+ c_arrow_dtype_f_str,
+ _,
+ ) = self._dtype_from_pandasdtype(codes.dtype)
+ return (
+ DtypeKind.CATEGORICAL,
+ bitwidth,
+ c_arrow_dtype_f_str,
+ Endianness.NATIVE,
+ )
+ elif is_string_dtype(dtype):
+ if infer_dtype(self._col) in ("string", "empty"):
+ return (
+ DtypeKind.STRING,
+ 8,
+ dtype_to_arrow_c_fmt(dtype),
+ Endianness.NATIVE,
+ )
+ raise NotImplementedError("Non-string object dtypes are not supported yet")
+ else:
+ return self._dtype_from_pandasdtype(dtype)
+
+ def _dtype_from_pandasdtype(self, dtype) -> tuple[DtypeKind, int, str, str]:
+ """
+ See `self.dtype` for details.
+ """
+ # Note: 'c' (complex) not handled yet (not in array spec v1).
+ # 'b', 'B' (bytes), 'S', 'a', (old-style string) 'V' (void) not handled
+ # datetime and timedelta both map to datetime (is timedelta handled?)
+
+ kind = _NP_KINDS.get(dtype.kind, None)
+ if kind is None:
+ # Not a NumPy dtype. Check if it's a categorical maybe
+ raise ValueError(f"Data type {dtype} not supported by interchange protocol")
+ if isinstance(dtype, ArrowDtype):
+ byteorder = dtype.numpy_dtype.byteorder
+ elif isinstance(dtype, DatetimeTZDtype):
+ byteorder = dtype.base.byteorder # type: ignore[union-attr]
+ elif isinstance(dtype, BaseMaskedDtype):
+ byteorder = dtype.numpy_dtype.byteorder
+ else:
+ byteorder = dtype.byteorder
+
+ if dtype == "bool[pyarrow]":
+ # return early to avoid the `* 8` below, as this is a bitmask
+ # rather than a bytemask
+ return (
+ kind,
+ dtype.itemsize, # pyright: ignore[reportAttributeAccessIssue]
+ ArrowCTypes.BOOL,
+ byteorder,
+ )
+
+ return kind, dtype.itemsize * 8, dtype_to_arrow_c_fmt(dtype), byteorder
+
+ @property
+ def describe_categorical(self):
+ """
+ If the dtype is categorical, there are two options:
+ - There are only values in the data buffer.
+ - There is a separate non-categorical Column encoding for categorical values.
+
+ Raises TypeError if the dtype is not categorical
+
+ Content of returned dict:
+ - "is_ordered" : bool, whether the ordering of dictionary indices is
+ semantically meaningful.
+ - "is_dictionary" : bool, whether a dictionary-style mapping of
+ categorical values to other objects exists
+ - "categories" : Column representing the (implicit) mapping of indices to
+ category values (e.g. an array of cat1, cat2, ...).
+ None if not a dictionary-style categorical.
+ """
+ if not self.dtype[0] == DtypeKind.CATEGORICAL:
+ raise TypeError(
+ "describe_categorical only works on a column with categorical dtype!"
+ )
+
+ return {
+ "is_ordered": self._col.cat.ordered,
+ "is_dictionary": True,
+ "categories": PandasColumn(pd.Series(self._col.cat.categories)),
+ }
+
+ @property
+ def describe_null(self):
+ if isinstance(self._col.dtype, BaseMaskedDtype):
+ column_null_dtype = ColumnNullType.USE_BYTEMASK
+ null_value = 1
+ return column_null_dtype, null_value
+ if isinstance(self._col.dtype, ArrowDtype):
+ # We already rechunk (if necessary / allowed) upon initialization, so this
+ # is already single-chunk by the time we get here.
+ if self._col.array._pa_array.chunks[0].buffers()[0] is None: # type: ignore[attr-defined]
+ return ColumnNullType.NON_NULLABLE, None
+ return ColumnNullType.USE_BITMASK, 0
+ kind = self.dtype[0]
+ try:
+ null, value = _NULL_DESCRIPTION[kind]
+ except KeyError as err:
+ raise NotImplementedError(f"Data type {kind} not yet supported") from err
+
+ return null, value
+
+ @cache_readonly
+ def null_count(self) -> int:
+ """
+ Number of null elements. Should always be known.
+ """
+ result = self._col.isna().sum()
+ if not using_python_scalars():
+ result = result.item()
+ return result
+
+ @property
+ def metadata(self) -> dict[str, pd.Index]:
+ """
+ Store specific metadata of the column.
+ """
+ return {"pandas.index": self._col.index}
+
+ def num_chunks(self) -> int:
+ """
+ Return the number of chunks the column consists of.
+ """
+ return 1
+
+ def get_chunks(self, n_chunks: int | None = None):
+ """
+ Return an iterator yielding the chunks.
+ See `DataFrame.get_chunks` for details on ``n_chunks``.
+ """
+ if n_chunks and n_chunks > 1:
+ size = len(self._col)
+ step = size // n_chunks
+ if size % n_chunks != 0:
+ step += 1
+ for start in range(0, step * n_chunks, step):
+ yield PandasColumn(
+ self._col.iloc[start : start + step], self._allow_copy
+ )
+ else:
+ yield self
+
+ def get_buffers(self) -> ColumnBuffers:
+ """
+ Return a dictionary containing the underlying buffers.
+ The returned dictionary has the following contents:
+ - "data": a two-element tuple whose first element is a buffer
+ containing the data and whose second element is the data
+ buffer's associated dtype.
+ - "validity": a two-element tuple whose first element is a buffer
+ containing mask values indicating missing data and
+ whose second element is the mask value buffer's
+ associated dtype. None if the null representation is
+ not a bit or byte mask.
+ - "offsets": a two-element tuple whose first element is a buffer
+ containing the offset values for variable-size binary
+ data (e.g., variable-length strings) and whose second
+ element is the offsets buffer's associated dtype. None
+ if the data buffer does not have an associated offsets
+ buffer.
+ """
+ buffers: ColumnBuffers = {
+ "data": self._get_data_buffer(),
+ "validity": None,
+ "offsets": None,
+ }
+
+ try:
+ buffers["validity"] = self._get_validity_buffer()
+ except NoBufferPresent:
+ pass
+
+ try:
+ buffers["offsets"] = self._get_offsets_buffer()
+ except NoBufferPresent:
+ pass
+
+ return buffers
+
+ def _get_data_buffer(
+ self,
+ ) -> tuple[Buffer, tuple[DtypeKind, int, str, str]]:
+ """
+ Return the buffer containing the data and the buffer's associated dtype.
+ """
+ buffer: Buffer
+ if self.dtype[0] == DtypeKind.DATETIME:
+ # self.dtype[2] is an ArrowCTypes.TIMESTAMP where the tz will make
+ # it longer than 4 characters
+ if len(self.dtype[2]) > 4:
+ np_arr = self._col.dt.tz_convert(None).to_numpy()
+ else:
+ np_arr = self._col.to_numpy()
+ buffer = PandasBuffer(np_arr, allow_copy=self._allow_copy)
+ dtype = (
+ DtypeKind.INT,
+ 64,
+ ArrowCTypes.INT64,
+ Endianness.NATIVE,
+ )
+ elif self.dtype[0] in (
+ DtypeKind.INT,
+ DtypeKind.UINT,
+ DtypeKind.FLOAT,
+ DtypeKind.BOOL,
+ ):
+ dtype = self.dtype
+ arr = self._col.array
+ if isinstance(self._col.dtype, ArrowDtype):
+ # We already rechunk (if necessary / allowed) upon initialization, so
+ # this is already single-chunk by the time we get here.
+ arr = arr._pa_array.chunks[0] # type: ignore[attr-defined]
+ buffer = PandasBufferPyarrow(
+ arr.buffers()[1],
+ length=len(arr),
+ )
+ return buffer, dtype
+ if isinstance(self._col.dtype, BaseMaskedDtype):
+ np_arr = arr._data # type: ignore[attr-defined]
+ else:
+ np_arr = arr._ndarray # type: ignore[attr-defined]
+ buffer = PandasBuffer(np_arr, allow_copy=self._allow_copy)
+ elif self.dtype[0] == DtypeKind.CATEGORICAL:
+ codes = self._col.values._codes
+ buffer = PandasBuffer(codes, allow_copy=self._allow_copy)
+ dtype = self._dtype_from_pandasdtype(codes.dtype)
+ elif self.dtype[0] == DtypeKind.STRING:
+ # Marshal the strings from a NumPy object array into a byte array
+ buf = self._col.to_numpy()
+ b = bytearray()
+
+ # TODO: this for-loop is slow; can be implemented in Cython/C/C++ later
+ for obj in buf:
+ if isinstance(obj, str):
+ b.extend(obj.encode(encoding="utf-8"))
+
+ # Convert the byte array to a Pandas "buffer" using
+ # a NumPy array as the backing store
+ buffer = PandasBuffer(np.frombuffer(b, dtype="uint8"))
+
+ # Define the dtype for the returned buffer
+ # TODO: this will need correcting
+ # https://github.com/pandas-dev/pandas/issues/54781
+ dtype = (
+ DtypeKind.UINT,
+ 8,
+ ArrowCTypes.UINT8,
+ Endianness.NATIVE,
+ ) # note: currently only support native endianness
+ else:
+ raise NotImplementedError(f"Data type {self._col.dtype} not handled yet")
+
+ return buffer, dtype
+
+ def _get_validity_buffer(self) -> tuple[Buffer, Any] | None:
+ """
+ Return the buffer containing the mask values indicating missing data and
+ the buffer's associated dtype.
+ Raises NoBufferPresent if null representation is not a bit or byte mask.
+ """
+ null, invalid = self.describe_null
+ buffer: Buffer
+ if isinstance(self._col.dtype, ArrowDtype):
+ # We already rechunk (if necessary / allowed) upon initialization, so this
+ # is already single-chunk by the time we get here.
+ arr = self._col.array._pa_array.chunks[0] # type: ignore[attr-defined]
+ dtype = (DtypeKind.BOOL, 1, ArrowCTypes.BOOL, Endianness.NATIVE)
+ if arr.buffers()[0] is None:
+ return None
+ buffer = PandasBufferPyarrow(
+ arr.buffers()[0],
+ length=len(arr),
+ )
+ return buffer, dtype
+
+ if isinstance(self._col.dtype, BaseMaskedDtype):
+ mask = self._col.array._mask # type: ignore[attr-defined]
+ buffer = PandasBuffer(mask)
+ dtype = (DtypeKind.BOOL, 8, ArrowCTypes.BOOL, Endianness.NATIVE)
+ return buffer, dtype
+
+ if self.dtype[0] == DtypeKind.STRING:
+ # For now, use byte array as the mask.
+ # TODO: maybe store as bit array to save space?..
+ buf = self._col.to_numpy()
+
+ # Determine the encoding for valid values
+ valid = invalid == 0
+ invalid = not valid
+
+ mask = np.zeros(shape=(len(buf),), dtype=np.bool_)
+ for i, obj in enumerate(buf):
+ mask[i] = valid if isinstance(obj, str) else invalid
+
+ # Convert the mask array to a Pandas "buffer" using
+ # a NumPy array as the backing store
+ buffer = PandasBuffer(mask)
+
+ # Define the dtype of the returned buffer
+ dtype = (DtypeKind.BOOL, 8, ArrowCTypes.BOOL, Endianness.NATIVE)
+
+ return buffer, dtype
+
+ try:
+ msg = f"{_NO_VALIDITY_BUFFER[null]} so does not have a separate mask"
+ except KeyError as err:
+ # TODO: implement for other bit/byte masks?
+ raise NotImplementedError("See self.describe_null") from err
+
+ raise NoBufferPresent(msg)
+
+ def _get_offsets_buffer(self) -> tuple[PandasBuffer, Any]:
+ """
+ Return the buffer containing the offset values for variable-size binary
+ data (e.g., variable-length strings) and the buffer's associated dtype.
+ Raises NoBufferPresent if the data buffer does not have an associated
+ offsets buffer.
+ """
+ if self.dtype[0] == DtypeKind.STRING:
+ # For each string, we need to manually determine the next offset
+ values = self._col.to_numpy()
+ ptr = 0
+ offsets = np.zeros(shape=(len(values) + 1,), dtype=np.int64)
+ for i, v in enumerate(values):
+ # For missing values (in this case, `np.nan` values)
+ # we don't increment the pointer
+ if isinstance(v, str):
+ b = v.encode(encoding="utf-8")
+ ptr += len(b)
+
+ offsets[i + 1] = ptr
+
+ # Convert the offsets to a Pandas "buffer" using
+ # the NumPy array as the backing store
+ buffer = PandasBuffer(offsets)
+
+ # Assemble the buffer dtype info
+ dtype = (
+ DtypeKind.INT,
+ 64,
+ ArrowCTypes.INT64,
+ Endianness.NATIVE,
+ ) # note: currently only support native endianness
+ else:
+ raise NoBufferPresent(
+ "This column has a fixed-length dtype so "
+ "it does not have an offsets buffer"
+ )
+
+ return buffer, dtype
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/dataframe.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/dataframe.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a116af567e599e16d38c62559539e5031da83b5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/dataframe.py
@@ -0,0 +1,113 @@
+from __future__ import annotations
+
+from collections import abc
+from typing import TYPE_CHECKING
+
+from pandas.core.interchange.column import PandasColumn
+from pandas.core.interchange.dataframe_protocol import DataFrame as DataFrameXchg
+from pandas.core.interchange.utils import maybe_rechunk
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Iterable,
+ Sequence,
+ )
+
+ from pandas import (
+ DataFrame,
+ Index,
+ )
+
+
+class PandasDataFrameXchg(DataFrameXchg):
+ """
+ A data frame class, with only the methods required by the interchange
+ protocol defined.
+ Instances of this (private) class are returned from
+ ``pd.DataFrame.__dataframe__`` as objects with the methods and
+ attributes defined on this class.
+ """
+
+ def __init__(self, df: DataFrame, allow_copy: bool = True) -> None:
+ """
+ Constructor - an instance of this (private) class is returned from
+ `pd.DataFrame.__dataframe__`.
+ """
+ self._df = df.rename(columns=str)
+ self._allow_copy = allow_copy
+ for i, _col in enumerate(self._df.columns):
+ rechunked = maybe_rechunk(self._df.iloc[:, i], allow_copy=allow_copy)
+ if rechunked is not None:
+ self._df.isetitem(i, rechunked)
+
+ def __dataframe__(
+ self, nan_as_null: bool = False, allow_copy: bool = True
+ ) -> PandasDataFrameXchg:
+ # `nan_as_null` can be removed here once it's removed from
+ # Dataframe.__dataframe__
+ return PandasDataFrameXchg(self._df, allow_copy)
+
+ @property
+ def metadata(self) -> dict[str, Index]:
+ # `index` isn't a regular column, and the protocol doesn't support row
+ # labels - so we export it as Pandas-specific metadata here.
+ return {"pandas.index": self._df.index}
+
+ def num_columns(self) -> int:
+ return len(self._df.columns)
+
+ def num_rows(self) -> int:
+ return len(self._df)
+
+ def num_chunks(self) -> int:
+ return 1
+
+ def column_names(self) -> Index:
+ return self._df.columns
+
+ def get_column(self, i: int) -> PandasColumn:
+ return PandasColumn(self._df.iloc[:, i], allow_copy=self._allow_copy)
+
+ def get_column_by_name(self, name: str) -> PandasColumn:
+ return PandasColumn(self._df[name], allow_copy=self._allow_copy)
+
+ def get_columns(self) -> list[PandasColumn]:
+ return [
+ PandasColumn(self._df[name], allow_copy=self._allow_copy)
+ for name in self._df.columns
+ ]
+
+ def select_columns(self, indices: Sequence[int]) -> PandasDataFrameXchg:
+ if not isinstance(indices, abc.Sequence):
+ raise ValueError("`indices` is not a sequence")
+ if not isinstance(indices, list):
+ indices = list(indices)
+
+ return PandasDataFrameXchg(
+ self._df.iloc[:, indices], allow_copy=self._allow_copy
+ )
+
+ def select_columns_by_name(self, names: list[str]) -> PandasDataFrameXchg: # type: ignore[override]
+ if not isinstance(names, abc.Sequence):
+ raise ValueError("`names` is not a sequence")
+ if not isinstance(names, list):
+ names = list(names)
+
+ return PandasDataFrameXchg(self._df.loc[:, names], allow_copy=self._allow_copy)
+
+ def get_chunks(self, n_chunks: int | None = None) -> Iterable[PandasDataFrameXchg]:
+ """
+ Return an iterator yielding the chunks.
+ """
+ if n_chunks and n_chunks > 1:
+ size = len(self._df)
+ step = size // n_chunks
+ if size % n_chunks != 0:
+ step += 1
+ for start in range(0, step * n_chunks, step):
+ yield PandasDataFrameXchg(
+ self._df.iloc[start : start + step, :],
+ allow_copy=self._allow_copy,
+ )
+ else:
+ yield self
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/dataframe_protocol.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/dataframe_protocol.py
new file mode 100644
index 0000000000000000000000000000000000000000..28aa13202a7125f8f2d6f7a2797488bc8ef357b8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/dataframe_protocol.py
@@ -0,0 +1,468 @@
+"""
+A verbatim copy (vendored) of the spec from https://github.com/data-apis/dataframe-api
+"""
+
+from __future__ import annotations
+
+from abc import (
+ ABC,
+ abstractmethod,
+)
+import enum
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ TypedDict,
+)
+
+from pandas.util._decorators import set_module
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Iterable,
+ Sequence,
+ )
+
+
+class DlpackDeviceType(enum.IntEnum):
+ """Integer enum for device type codes matching DLPack."""
+
+ CPU = 1
+ CUDA = 2
+ CPU_PINNED = 3
+ OPENCL = 4
+ VULKAN = 7
+ METAL = 8
+ VPI = 9
+ ROCM = 10
+
+
+class DtypeKind(enum.IntEnum):
+ """
+ Integer enum for data types.
+
+ Attributes
+ ----------
+ INT : int
+ Matches to signed integer data type.
+ UINT : int
+ Matches to unsigned integer data type.
+ FLOAT : int
+ Matches to floating point data type.
+ BOOL : int
+ Matches to boolean data type.
+ STRING : int
+ Matches to string data type (UTF-8 encoded).
+ DATETIME : int
+ Matches to datetime data type.
+ CATEGORICAL : int
+ Matches to categorical data type.
+ """
+
+ INT = 0
+ UINT = 1
+ FLOAT = 2
+ BOOL = 20
+ STRING = 21 # UTF-8
+ DATETIME = 22
+ CATEGORICAL = 23
+
+
+class ColumnNullType(enum.IntEnum):
+ """
+ Integer enum for null type representation.
+
+ Attributes
+ ----------
+ NON_NULLABLE : int
+ Non-nullable column.
+ USE_NAN : int
+ Use explicit float NaN value.
+ USE_SENTINEL : int
+ Sentinel value besides NaN/NaT.
+ USE_BITMASK : int
+ The bit is set/unset representing a null on a certain position.
+ USE_BYTEMASK : int
+ The byte is set/unset representing a null on a certain position.
+ """
+
+ NON_NULLABLE = 0
+ USE_NAN = 1
+ USE_SENTINEL = 2
+ USE_BITMASK = 3
+ USE_BYTEMASK = 4
+
+
+class ColumnBuffers(TypedDict):
+ # first element is a buffer containing the column data;
+ # second element is the data buffer's associated dtype
+ data: tuple[Buffer, Any]
+
+ # first element is a buffer containing mask values indicating missing data;
+ # second element is the mask value buffer's associated dtype.
+ # None if the null representation is not a bit or byte mask
+ validity: tuple[Buffer, Any] | None
+
+ # first element is a buffer containing the offset values for
+ # variable-size binary data (e.g., variable-length strings);
+ # second element is the offsets buffer's associated dtype.
+ # None if the data buffer does not have an associated offsets buffer
+ offsets: tuple[Buffer, Any] | None
+
+
+class CategoricalDescription(TypedDict):
+ # whether the ordering of dictionary indices is semantically meaningful
+ is_ordered: bool
+ # whether a dictionary-style mapping of categorical values to other objects exists
+ is_dictionary: bool
+ # Python-level only (e.g. ``{int: str}``).
+ # None if not a dictionary-style categorical.
+ categories: Column | None
+
+
+class Buffer(ABC):
+ """
+ Data in the buffer is guaranteed to be contiguous in memory.
+
+ Note that there is no dtype attribute present, a buffer can be thought of
+ as simply a block of memory. However, if the column that the buffer is
+ attached to has a dtype that's supported by DLPack and ``__dlpack__`` is
+ implemented, then that dtype information will be contained in the return
+ value from ``__dlpack__``.
+
+ This distinction is useful to support both data exchange via DLPack on a
+ buffer and (b) dtypes like variable-length strings which do not have a
+ fixed number of bytes per element.
+ """
+
+ @property
+ @abstractmethod
+ def bufsize(self) -> int:
+ """
+ Buffer size in bytes.
+ """
+
+ @property
+ @abstractmethod
+ def ptr(self) -> int:
+ """
+ Pointer to start of the buffer as an integer.
+ """
+
+ @abstractmethod
+ def __dlpack__(self):
+ """
+ Produce DLPack capsule (see array API standard).
+
+ Raises:
+
+ - TypeError : if the buffer contains unsupported dtypes.
+ - NotImplementedError : if DLPack support is not implemented
+
+ Useful to have to connect to array libraries. Support optional because
+ it's not completely trivial to implement for a Python-only library.
+ """
+ raise NotImplementedError("__dlpack__")
+
+ @abstractmethod
+ def __dlpack_device__(self) -> tuple[DlpackDeviceType, int | None]:
+ """
+ Device type and device ID for where the data in the buffer resides.
+ Uses device type codes matching DLPack.
+ Note: must be implemented even if ``__dlpack__`` is not.
+ """
+
+
+class Column(ABC):
+ """
+ A column object, with only the methods and properties required by the
+ interchange protocol defined.
+
+ A column can contain one or more chunks. Each chunk can contain up to three
+ buffers - a data buffer, a mask buffer (depending on null representation),
+ and an offsets buffer (if variable-size binary; e.g., variable-length
+ strings).
+
+ TBD: Arrow has a separate "null" dtype, and has no separate mask concept.
+ Instead, it seems to use "children" for both columns with a bit mask,
+ and for nested dtypes. Unclear whether this is elegant or confusing.
+ This design requires checking the null representation explicitly.
+
+ The Arrow design requires checking:
+ 1. the ARROW_FLAG_NULLABLE (for sentinel values)
+ 2. if a column has two children, combined with one of those children
+ having a null dtype.
+
+ Making the mask concept explicit seems useful. One null dtype would
+ not be enough to cover both bit and byte masks, so that would mean
+ even more checking if we did it the Arrow way.
+
+ TBD: there's also the "chunk" concept here, which is implicit in Arrow as
+ multiple buffers per array (= column here). Semantically it may make
+ sense to have both: chunks were meant for example for lazy evaluation
+ of data which doesn't fit in memory, while multiple buffers per column
+ could also come from doing a selection operation on a single
+ contiguous buffer.
+
+ Given these concepts, one would expect chunks to be all of the same
+ size (say a 10,000 row dataframe could have 10 chunks of 1,000 rows),
+ while multiple buffers could have data-dependent lengths. Not an issue
+ in pandas if one column is backed by a single NumPy array, but in
+ Arrow it seems possible.
+ Are multiple chunks *and* multiple buffers per column necessary for
+ the purposes of this interchange protocol, or must producers either
+ reuse the chunk concept for this or copy the data?
+
+ Note: this Column object can only be produced by ``__dataframe__``, so
+ doesn't need its own version or ``__column__`` protocol.
+ """
+
+ @abstractmethod
+ def size(self) -> int:
+ """
+ Size of the column, in elements.
+
+ Corresponds to DataFrame.num_rows() if column is a single chunk;
+ equal to size of this current chunk otherwise.
+ """
+
+ @property
+ @abstractmethod
+ def offset(self) -> int:
+ """
+ Offset of first element.
+
+ May be > 0 if using chunks; for example for a column with N chunks of
+ equal size M (only the last chunk may be shorter),
+ ``offset = n * M``, ``n = 0 .. N-1``.
+ """
+
+ @property
+ @abstractmethod
+ def dtype(self) -> tuple[DtypeKind, int, str, str]:
+ """
+ Dtype description as a tuple ``(kind, bit-width, format string, endianness)``.
+
+ Bit-width : the number of bits as an integer
+ Format string : data type description format string in Apache Arrow C
+ Data Interface format.
+ Endianness : current only native endianness (``=``) is supported
+
+ Notes:
+ - Kind specifiers are aligned with DLPack where possible (hence the
+ jump to 20, leave enough room for future extension)
+ - Masks must be specified as boolean with either bit width 1 (for bit
+ masks) or 8 (for byte masks).
+ - Dtype width in bits was preferred over bytes
+ - Endianness isn't too useful, but included now in case in the future
+ we need to support non-native endianness
+ - Went with Apache Arrow format strings over NumPy format strings
+ because they're more complete from a dataframe perspective
+ - Format strings are mostly useful for datetime specification, and
+ for categoricals.
+ - For categoricals, the format string describes the type of the
+ categorical in the data buffer. In case of a separate encoding of
+ the categorical (e.g. an integer to string mapping), this can
+ be derived from ``self.describe_categorical``.
+ - Data types not included: complex, Arrow-style null, binary, decimal,
+ and nested (list, struct, map, union) dtypes.
+ """
+
+ @property
+ @abstractmethod
+ def describe_categorical(self) -> CategoricalDescription:
+ """
+ If the dtype is categorical, there are two options:
+ - There are only values in the data buffer.
+ - There is a separate non-categorical Column encoding for categorical values.
+
+ Raises TypeError if the dtype is not categorical
+
+ Returns the dictionary with description on how to interpret the data buffer:
+ - "is_ordered" : bool, whether the ordering of dictionary indices is
+ semantically meaningful.
+ - "is_dictionary" : bool, whether a mapping of
+ categorical values to other objects exists
+ - "categories" : Column representing the (implicit) mapping of indices to
+ category values (e.g. an array of cat1, cat2, ...).
+ None if not a dictionary-style categorical.
+
+ TBD: are there any other in-memory representations that are needed?
+ """
+
+ @property
+ @abstractmethod
+ def describe_null(self) -> tuple[ColumnNullType, Any]:
+ """
+ Return the missing value (or "null") representation the column dtype
+ uses, as a tuple ``(kind, value)``.
+
+ Value : if kind is "sentinel value", the actual value. If kind is a bit
+ mask or a byte mask, the value (0 or 1) indicating a missing value. None
+ otherwise.
+ """
+
+ @property
+ @abstractmethod
+ def null_count(self) -> int | None:
+ """
+ Number of null elements, if known.
+
+ Note: Arrow uses -1 to indicate "unknown", but None seems cleaner.
+ """
+
+ @property
+ @abstractmethod
+ def metadata(self) -> dict[str, Any]:
+ """
+ The metadata for the column. See `DataFrame.metadata` for more details.
+ """
+
+ @abstractmethod
+ def num_chunks(self) -> int:
+ """
+ Return the number of chunks the column consists of.
+ """
+
+ @abstractmethod
+ def get_chunks(self, n_chunks: int | None = None) -> Iterable[Column]:
+ """
+ Return an iterator yielding the chunks.
+
+ See `DataFrame.get_chunks` for details on ``n_chunks``.
+ """
+
+ @abstractmethod
+ def get_buffers(self) -> ColumnBuffers:
+ """
+ Return a dictionary containing the underlying buffers.
+
+ The returned dictionary has the following contents:
+
+ - "data": a two-element tuple whose first element is a buffer
+ containing the data and whose second element is the data
+ buffer's associated dtype.
+ - "validity": a two-element tuple whose first element is a buffer
+ containing mask values indicating missing data and
+ whose second element is the mask value buffer's
+ associated dtype. None if the null representation is
+ not a bit or byte mask.
+ - "offsets": a two-element tuple whose first element is a buffer
+ containing the offset values for variable-size binary
+ data (e.g., variable-length strings) and whose second
+ element is the offsets buffer's associated dtype. None
+ if the data buffer does not have an associated offsets
+ buffer.
+ """
+
+
+# def get_children(self) -> Iterable[Column]:
+# """
+# Children columns underneath the column, each object in this iterator
+# must adhere to the column specification.
+# """
+# pass
+
+
+@set_module("pandas.api.interchange")
+class DataFrame(ABC):
+ """
+ A data frame class, with only the methods required by the interchange
+ protocol defined.
+
+ A "data frame" represents an ordered collection of named columns.
+ A column's "name" must be a unique string.
+ Columns may be accessed by name or by position.
+
+ This could be a public data frame class, or an object with the methods and
+ attributes defined on this DataFrame class could be returned from the
+ ``__dataframe__`` method of a public data frame class in a library adhering
+ to the dataframe interchange protocol specification.
+ """
+
+ version = 0 # version of the protocol
+
+ @abstractmethod
+ def __dataframe__(self, nan_as_null: bool = False, allow_copy: bool = True):
+ """Construct a new interchange object, potentially changing the parameters."""
+
+ @property
+ @abstractmethod
+ def metadata(self) -> dict[str, Any]:
+ """
+ The metadata for the data frame, as a dictionary with string keys. The
+ contents of `metadata` may be anything, they are meant for a library
+ to store information that it needs to, e.g., roundtrip losslessly or
+ for two implementations to share data that is not (yet) part of the
+ interchange protocol specification. For avoiding collisions with other
+ entries, please add name the keys with the name of the library
+ followed by a period and the desired name, e.g, ``pandas.indexcol``.
+ """
+
+ @abstractmethod
+ def num_columns(self) -> int:
+ """
+ Return the number of columns in the DataFrame.
+ """
+
+ @abstractmethod
+ def num_rows(self) -> int | None:
+ # TODO: not happy with Optional, but need to flag it may be expensive
+ # why include it if it may be None - what do we expect consumers
+ # to do here?
+ """
+ Return the number of rows in the DataFrame, if available.
+ """
+
+ @abstractmethod
+ def num_chunks(self) -> int:
+ """
+ Return the number of chunks the DataFrame consists of.
+ """
+
+ @abstractmethod
+ def column_names(self) -> Iterable[str]:
+ """
+ Return an iterator yielding the column names.
+ """
+
+ @abstractmethod
+ def get_column(self, i: int) -> Column:
+ """
+ Return the column at the indicated position.
+ """
+
+ @abstractmethod
+ def get_column_by_name(self, name: str) -> Column:
+ """
+ Return the column whose name is the indicated name.
+ """
+
+ @abstractmethod
+ def get_columns(self) -> Iterable[Column]:
+ """
+ Return an iterator yielding the columns.
+ """
+
+ @abstractmethod
+ def select_columns(self, indices: Sequence[int]) -> DataFrame:
+ """
+ Create a new DataFrame by selecting a subset of columns by index.
+ """
+
+ @abstractmethod
+ def select_columns_by_name(self, names: Sequence[str]) -> DataFrame:
+ """
+ Create a new DataFrame by selecting a subset of columns by name.
+ """
+
+ @abstractmethod
+ def get_chunks(self, n_chunks: int | None = None) -> Iterable[DataFrame]:
+ """
+ Return an iterator yielding the chunks.
+
+ By default (None), yields the chunks that the data is stored as by the
+ producer. If given, ``n_chunks`` must be a multiple of
+ ``self.num_chunks()``, meaning the producer must subdivide each chunk
+ before yielding it.
+ """
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/from_dataframe.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/from_dataframe.py
new file mode 100644
index 0000000000000000000000000000000000000000..04278c0e7856ded5f55fe393d596c5a0728b6ced
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/from_dataframe.py
@@ -0,0 +1,613 @@
+from __future__ import annotations
+
+import ctypes
+import re
+from typing import (
+ Any,
+ overload,
+)
+import warnings
+
+import numpy as np
+
+from pandas._config import using_string_dtype
+
+from pandas.compat._optional import import_optional_dependency
+from pandas.errors import Pandas4Warning
+from pandas.util._decorators import set_module
+from pandas.util._exceptions import find_stack_level
+
+import pandas as pd
+from pandas.core.interchange.dataframe_protocol import (
+ Buffer,
+ Column,
+ ColumnNullType,
+ DataFrame as DataFrameXchg,
+ DtypeKind,
+)
+from pandas.core.interchange.utils import (
+ ArrowCTypes,
+ Endianness,
+)
+
+_NP_DTYPES: dict[DtypeKind, dict[int, Any]] = {
+ DtypeKind.INT: {8: np.int8, 16: np.int16, 32: np.int32, 64: np.int64},
+ DtypeKind.UINT: {8: np.uint8, 16: np.uint16, 32: np.uint32, 64: np.uint64},
+ DtypeKind.FLOAT: {32: np.float32, 64: np.float64},
+ DtypeKind.BOOL: {1: bool, 8: bool},
+}
+
+
+@set_module("pandas.api.interchange")
+def from_dataframe(df, allow_copy: bool = True) -> pd.DataFrame:
+ """
+ Build a ``pd.DataFrame`` from any DataFrame supporting the interchange protocol.
+
+ .. note::
+
+ For new development, we highly recommend using the Arrow C Data Interface
+ alongside the Arrow PyCapsule Interface instead of the interchange protocol.
+ From pandas 3.0 onwards, `from_dataframe` uses the PyCapsule Interface,
+ only falling back to the interchange protocol if that fails.
+
+ From pandas 4.0 onwards, that fallback will no longer be available and only
+ the PyCapsule Interface will be used.
+
+ .. warning::
+
+ Due to severe implementation issues, we recommend only considering using the
+ interchange protocol in the following cases:
+
+ - converting to pandas: for pandas >= 2.0.3
+ - converting from pandas: for pandas >= 3.0.0
+
+ Parameters
+ ----------
+ df : DataFrameXchg
+ Object supporting the interchange protocol, i.e. `__dataframe__` method.
+ allow_copy : bool, default: True
+ Whether to allow copying the memory to perform the conversion
+ (if false then zero-copy approach is requested).
+
+ Returns
+ -------
+ pd.DataFrame
+ A pandas DataFrame built from the provided interchange
+ protocol object.
+
+ See Also
+ --------
+ pd.DataFrame : DataFrame class which can be created from various input data
+ formats, including objects that support the interchange protocol.
+
+ Examples
+ --------
+ >>> df_not_necessarily_pandas = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
+ >>> interchange_object = df_not_necessarily_pandas.__dataframe__()
+ >>> interchange_object.column_names()
+ Index(['A', 'B'], dtype='str')
+ >>> df_pandas = pd.api.interchange.from_dataframe(
+ ... interchange_object.select_columns_by_name(["A"])
+ ... )
+ >>> df_pandas
+ A
+ 0 1
+ 1 2
+
+ These methods (``column_names``, ``select_columns_by_name``) should work
+ for any dataframe library which implements the interchange protocol.
+ """
+ if isinstance(df, pd.DataFrame):
+ return df
+
+ if hasattr(df, "__arrow_c_stream__"):
+ try:
+ pa = import_optional_dependency("pyarrow", min_version="14.0.0")
+ except ImportError:
+ # fallback to _from_dataframe
+ warnings.warn(
+ "Conversion using Arrow PyCapsule Interface failed due to "
+ "missing PyArrow>=14 dependency, falling back to (deprecated) "
+ "interchange protocol. We recommend that you install "
+ "PyArrow>=14.0.0.",
+ UserWarning,
+ stacklevel=find_stack_level(),
+ )
+ else:
+ try:
+ return pa.table(df).to_pandas(zero_copy_only=not allow_copy)
+ except pa.ArrowInvalid as e:
+ raise RuntimeError(e) from e
+
+ if not hasattr(df, "__dataframe__"):
+ raise ValueError("`df` does not support __dataframe__")
+
+ warnings.warn(
+ "The Dataframe Interchange Protocol is deprecated.\n"
+ "For dataframe-agnostic code, you may want to look into:\n"
+ "- Arrow PyCapsule Interface: https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html\n"
+ "- Narwhals: https://github.com/narwhals-dev/narwhals\n",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+
+ return _from_dataframe(
+ df.__dataframe__(allow_copy=allow_copy), allow_copy=allow_copy
+ )
+
+
+def _from_dataframe(df: DataFrameXchg, allow_copy: bool = True) -> pd.DataFrame:
+ """
+ Build a ``pd.DataFrame`` from the DataFrame interchange object.
+
+ Parameters
+ ----------
+ df : DataFrameXchg
+ Object supporting the interchange protocol, i.e. `__dataframe__` method.
+ allow_copy : bool, default: True
+ Whether to allow copying the memory to perform the conversion
+ (if false then zero-copy approach is requested).
+
+ Returns
+ -------
+ pd.DataFrame
+ """
+ pandas_dfs = []
+ for chunk in df.get_chunks():
+ pandas_df = protocol_df_chunk_to_pandas(chunk)
+ pandas_dfs.append(pandas_df)
+
+ if not allow_copy and len(pandas_dfs) > 1:
+ raise RuntimeError(
+ "To join chunks a copy is required which is forbidden by allow_copy=False"
+ )
+ if not pandas_dfs:
+ pandas_df = protocol_df_chunk_to_pandas(df)
+ elif len(pandas_dfs) == 1:
+ pandas_df = pandas_dfs[0]
+ else:
+ pandas_df = pd.concat(pandas_dfs, axis=0, ignore_index=True, copy=False)
+
+ index_obj = df.metadata.get("pandas.index", None)
+ if index_obj is not None:
+ pandas_df.index = index_obj
+
+ return pandas_df
+
+
+def protocol_df_chunk_to_pandas(df: DataFrameXchg) -> pd.DataFrame:
+ """
+ Convert interchange protocol chunk to ``pd.DataFrame``.
+
+ Parameters
+ ----------
+ df : DataFrameXchg
+
+ Returns
+ -------
+ pd.DataFrame
+ """
+ columns: dict[str, Any] = {}
+ buffers = [] # hold on to buffers, keeps memory alive
+ for name in df.column_names():
+ if not isinstance(name, str):
+ raise ValueError(f"Column {name} is not a string")
+ if name in columns:
+ raise ValueError(f"Column {name} is not unique")
+ col = df.get_column_by_name(name)
+ dtype = col.dtype[0]
+ if dtype in (
+ DtypeKind.INT,
+ DtypeKind.UINT,
+ DtypeKind.FLOAT,
+ DtypeKind.BOOL,
+ ):
+ columns[name], buf = primitive_column_to_ndarray(col)
+ elif dtype == DtypeKind.CATEGORICAL:
+ columns[name], buf = categorical_column_to_series(col)
+ elif dtype == DtypeKind.STRING:
+ columns[name], buf = string_column_to_ndarray(col)
+ elif dtype == DtypeKind.DATETIME:
+ columns[name], buf = datetime_column_to_ndarray(col)
+ else:
+ raise NotImplementedError(f"Data type {dtype} not handled yet")
+
+ buffers.append(buf)
+
+ pandas_df = pd.DataFrame(columns)
+ pandas_df.attrs["_INTERCHANGE_PROTOCOL_BUFFERS"] = buffers
+ return pandas_df
+
+
+def primitive_column_to_ndarray(col: Column) -> tuple[np.ndarray, Any]:
+ """
+ Convert a column holding one of the primitive dtypes to a NumPy array.
+
+ A primitive type is one of: int, uint, float, bool.
+
+ Parameters
+ ----------
+ col : Column
+
+ Returns
+ -------
+ tuple
+ Tuple of np.ndarray holding the data and the memory owner object
+ that keeps the memory alive.
+ """
+ buffers = col.get_buffers()
+
+ data_buff, data_dtype = buffers["data"]
+ data = buffer_to_ndarray(
+ data_buff, data_dtype, offset=col.offset, length=col.size()
+ )
+
+ data = set_nulls(data, col, buffers["validity"])
+ return data, buffers
+
+
+def categorical_column_to_series(col: Column) -> tuple[pd.Series, Any]:
+ """
+ Convert a column holding categorical data to a pandas Series.
+
+ Parameters
+ ----------
+ col : Column
+
+ Returns
+ -------
+ tuple
+ Tuple of pd.Series holding the data and the memory owner object
+ that keeps the memory alive.
+ """
+ categorical = col.describe_categorical
+
+ if not categorical["is_dictionary"]:
+ raise NotImplementedError("Non-dictionary categoricals not supported yet")
+
+ cat_column = categorical["categories"]
+ if hasattr(cat_column, "_col"):
+ # Item "Column" of "Optional[Column]" has no attribute "_col"
+ # Item "None" of "Optional[Column]" has no attribute "_col"
+ categories = np.array(cat_column._col) # type: ignore[union-attr]
+ else:
+ raise NotImplementedError(
+ "Interchanging categorical columns isn't supported yet, and our "
+ "fallback of using the `col._col` attribute (a ndarray) failed."
+ )
+ buffers = col.get_buffers()
+
+ codes_buff, codes_dtype = buffers["data"]
+ codes = buffer_to_ndarray(
+ codes_buff, codes_dtype, offset=col.offset, length=col.size()
+ )
+
+ # Doing module in order to not get ``IndexError`` for
+ # out-of-bounds sentinel values in `codes`
+ if len(categories) > 0:
+ values = categories[codes % len(categories)]
+ else:
+ values = codes
+
+ cat = pd.Categorical(
+ values, categories=categories, ordered=categorical["is_ordered"]
+ )
+ data = pd.Series(cat)
+
+ data = set_nulls(data, col, buffers["validity"])
+ return data, buffers
+
+
+def string_column_to_ndarray(col: Column) -> tuple[np.ndarray, Any]:
+ """
+ Convert a column holding string data to a NumPy array.
+
+ Parameters
+ ----------
+ col : Column
+
+ Returns
+ -------
+ tuple
+ Tuple of np.ndarray holding the data and the memory owner object
+ that keeps the memory alive.
+ """
+ null_kind, sentinel_val = col.describe_null
+
+ if null_kind not in (
+ ColumnNullType.NON_NULLABLE,
+ ColumnNullType.USE_BITMASK,
+ ColumnNullType.USE_BYTEMASK,
+ ):
+ raise NotImplementedError(
+ f"{null_kind} null kind is not yet supported for string columns."
+ )
+
+ buffers = col.get_buffers()
+
+ assert buffers["offsets"], "String buffers must contain offsets"
+ # Retrieve the data buffer containing the UTF-8 code units
+ data_buff, _ = buffers["data"]
+ # We're going to reinterpret the buffer as uint8, so make sure we can do it safely
+ assert col.dtype[2] in (
+ ArrowCTypes.STRING,
+ ArrowCTypes.LARGE_STRING,
+ ) # format_str == utf-8
+ # Convert the buffers to NumPy arrays. In order to go from STRING to
+ # an equivalent ndarray, we claim that the buffer is uint8 (i.e., a byte array)
+ data_dtype = (
+ DtypeKind.UINT,
+ 8,
+ ArrowCTypes.UINT8,
+ Endianness.NATIVE,
+ )
+ # Specify zero offset as we don't want to chunk the string data
+ data = buffer_to_ndarray(data_buff, data_dtype, offset=0, length=data_buff.bufsize)
+
+ # Retrieve the offsets buffer containing the index offsets demarcating
+ # the beginning and the ending of each string
+ offset_buff, offset_dtype = buffers["offsets"]
+ # Offsets buffer contains start-stop positions of strings in the data buffer,
+ # meaning that it has more elements than in the data buffer, do `col.size() + 1`
+ # here to pass a proper offsets buffer size
+ offsets = buffer_to_ndarray(
+ offset_buff, offset_dtype, offset=col.offset, length=col.size() + 1
+ )
+
+ null_pos = None
+ if null_kind in (ColumnNullType.USE_BITMASK, ColumnNullType.USE_BYTEMASK):
+ validity = buffers["validity"]
+ if validity is not None:
+ valid_buff, valid_dtype = validity
+ null_pos = buffer_to_ndarray(
+ valid_buff, valid_dtype, offset=col.offset, length=col.size()
+ )
+ if sentinel_val == 0:
+ null_pos = ~null_pos
+
+ # Assemble the strings from the code units
+ str_list: list[None | float | str] = [None] * col.size()
+ for i in range(col.size()):
+ # Check for missing values
+ if null_pos is not None and null_pos[i]:
+ str_list[i] = np.nan
+ continue
+
+ # Extract a range of code units
+ units = data[offsets[i] : offsets[i + 1]]
+
+ # Convert the list of code units to bytes
+ str_bytes = bytes(units)
+
+ # Create the string
+ string = str_bytes.decode(encoding="utf-8")
+
+ # Add to our list of strings
+ str_list[i] = string
+
+ if using_string_dtype():
+ res = pd.Series(str_list, dtype="str")
+ else:
+ res = np.asarray(str_list, dtype="object") # type: ignore[assignment]
+
+ return res, buffers # type: ignore[return-value]
+
+
+def parse_datetime_format_str(format_str, data) -> pd.Series | np.ndarray:
+ """Parse datetime `format_str` to interpret the `data`."""
+ # timestamp 'ts{unit}:tz'
+ timestamp_meta = re.match(r"ts([smun]):(.*)", format_str)
+ if timestamp_meta:
+ unit, tz = timestamp_meta.group(1), timestamp_meta.group(2)
+ if unit != "s":
+ # the format string describes only a first letter of the unit, so
+ # add one extra letter to convert the unit to numpy-style:
+ # 'm' -> 'ms', 'u' -> 'us', 'n' -> 'ns'
+ unit += "s"
+ data = data.astype(f"datetime64[{unit}]")
+ if tz != "":
+ data = pd.Series(data).dt.tz_localize("UTC").dt.tz_convert(tz)
+ return data
+
+ # date 'td{Days/Ms}'
+ date_meta = re.match(r"td([Dm])", format_str)
+ if date_meta:
+ unit = date_meta.group(1)
+ if unit == "D":
+ # NumPy doesn't support DAY unit, so converting days to seconds
+ # (converting to uint64 to avoid overflow)
+ data = (data.astype(np.uint64) * (24 * 60 * 60)).astype("datetime64[s]")
+ elif unit == "m":
+ data = data.astype("datetime64[ms]")
+ else:
+ raise NotImplementedError(f"Date unit is not supported: {unit}")
+ return data
+
+ raise NotImplementedError(f"DateTime kind is not supported: {format_str}")
+
+
+def datetime_column_to_ndarray(col: Column) -> tuple[np.ndarray | pd.Series, Any]:
+ """
+ Convert a column holding DateTime data to a NumPy array.
+
+ Parameters
+ ----------
+ col : Column
+
+ Returns
+ -------
+ tuple
+ Tuple of np.ndarray holding the data and the memory owner object
+ that keeps the memory alive.
+ """
+ buffers = col.get_buffers()
+
+ _, col_bit_width, format_str, _ = col.dtype
+ dbuf, _ = buffers["data"]
+ # Consider dtype being `uint` to get number of units passed since the 01.01.1970
+
+ data = buffer_to_ndarray(
+ dbuf,
+ (
+ DtypeKind.INT,
+ col_bit_width,
+ getattr(ArrowCTypes, f"INT{col_bit_width}"),
+ Endianness.NATIVE,
+ ),
+ offset=col.offset,
+ length=col.size(),
+ )
+
+ data = parse_datetime_format_str(format_str, data) # type: ignore[assignment]
+ data = set_nulls(data, col, buffers["validity"])
+ return data, buffers
+
+
+def buffer_to_ndarray(
+ buffer: Buffer,
+ dtype: tuple[DtypeKind, int, str, str],
+ *,
+ length: int,
+ offset: int = 0,
+) -> np.ndarray:
+ """
+ Build a NumPy array from the passed buffer.
+
+ Parameters
+ ----------
+ buffer : Buffer
+ Buffer to build a NumPy array from.
+ dtype : tuple
+ Data type of the buffer conforming protocol dtypes format.
+ offset : int, default: 0
+ Number of elements to offset from the start of the buffer.
+ length : int, optional
+ If the buffer is a bit-mask, specifies a number of bits to read
+ from the buffer. Has no effect otherwise.
+
+ Returns
+ -------
+ np.ndarray
+
+ Notes
+ -----
+ The returned array doesn't own the memory. The caller of this function is
+ responsible for keeping the memory owner object alive as long as
+ the returned NumPy array is being used.
+ """
+ kind, bit_width, _, _ = dtype
+
+ column_dtype = _NP_DTYPES.get(kind, {}).get(bit_width, None)
+ if column_dtype is None:
+ raise NotImplementedError(f"Conversion for {dtype} is not yet supported.")
+
+ # TODO: No DLPack yet, so need to construct a new ndarray from the data pointer
+ # and size in the buffer plus the dtype on the column. Use DLPack as NumPy supports
+ # it since https://github.com/numpy/numpy/pull/19083
+ ctypes_type = np.ctypeslib.as_ctypes_type(column_dtype)
+
+ if bit_width == 1:
+ assert length is not None, "`length` must be specified for a bit-mask buffer."
+ pa = import_optional_dependency("pyarrow")
+ arr = pa.BooleanArray.from_buffers(
+ pa.bool_(),
+ length,
+ [None, pa.foreign_buffer(buffer.ptr, length)],
+ offset=offset,
+ )
+ return np.asarray(arr)
+ else:
+ data_pointer = ctypes.cast(
+ buffer.ptr + (offset * bit_width // 8), ctypes.POINTER(ctypes_type)
+ )
+ if length > 0:
+ return np.ctypeslib.as_array(data_pointer, shape=(length,))
+ return np.array([], dtype=ctypes_type)
+
+
+@overload
+def set_nulls(
+ data: np.ndarray,
+ col: Column,
+ validity: tuple[Buffer, tuple[DtypeKind, int, str, str]] | None,
+ allow_modify_inplace: bool = ...,
+) -> np.ndarray: ...
+
+
+@overload
+def set_nulls(
+ data: pd.Series,
+ col: Column,
+ validity: tuple[Buffer, tuple[DtypeKind, int, str, str]] | None,
+ allow_modify_inplace: bool = ...,
+) -> pd.Series: ...
+
+
+@overload
+def set_nulls(
+ data: np.ndarray | pd.Series,
+ col: Column,
+ validity: tuple[Buffer, tuple[DtypeKind, int, str, str]] | None,
+ allow_modify_inplace: bool = ...,
+) -> np.ndarray | pd.Series: ...
+
+
+def set_nulls(
+ data: np.ndarray | pd.Series,
+ col: Column,
+ validity: tuple[Buffer, tuple[DtypeKind, int, str, str]] | None,
+ allow_modify_inplace: bool = True,
+) -> np.ndarray | pd.Series:
+ """
+ Set null values for the data according to the column null kind.
+
+ Parameters
+ ----------
+ data : np.ndarray or pd.Series
+ Data to set nulls in.
+ col : Column
+ Column object that describes the `data`.
+ validity : tuple(Buffer, dtype) or None
+ The return value of ``col.buffers()``. We do not access the ``col.buffers()``
+ here to not take the ownership of the memory of buffer objects.
+ allow_modify_inplace : bool, default: True
+ Whether to modify the `data` inplace when zero-copy is possible (True) or always
+ modify a copy of the `data` (False).
+
+ Returns
+ -------
+ np.ndarray or pd.Series
+ Data with the nulls being set.
+ """
+ if validity is None:
+ return data
+ null_kind, sentinel_val = col.describe_null
+ null_pos = None
+
+ if null_kind == ColumnNullType.USE_SENTINEL:
+ null_pos = pd.Series(data) == sentinel_val
+ elif null_kind in (ColumnNullType.USE_BITMASK, ColumnNullType.USE_BYTEMASK):
+ valid_buff, valid_dtype = validity
+ null_pos = buffer_to_ndarray(
+ valid_buff, valid_dtype, offset=col.offset, length=col.size()
+ )
+ if sentinel_val == 0:
+ null_pos = ~null_pos
+ elif null_kind in (ColumnNullType.NON_NULLABLE, ColumnNullType.USE_NAN):
+ pass
+ else:
+ raise NotImplementedError(f"Null kind {null_kind} is not yet supported.")
+
+ if null_pos is not None and np.any(null_pos):
+ if not allow_modify_inplace:
+ data = data.copy()
+ try:
+ data[null_pos] = None
+ except TypeError:
+ # TypeError happens if the `data` dtype appears to be non-nullable
+ # in numpy notation (bool, int, uint). If this happens,
+ # cast the `data` to nullable float dtype.
+ data = data.astype(float)
+ data[null_pos] = None
+
+ return data
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..035a1f8abdbc5d07ccec000d38c3fc159ccf94b6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/interchange/utils.py
@@ -0,0 +1,183 @@
+"""
+Utility functions and objects for implementing the interchange API.
+"""
+
+from __future__ import annotations
+
+import typing
+
+import numpy as np
+
+from pandas._libs import lib
+
+from pandas.core.dtypes.dtypes import (
+ ArrowDtype,
+ CategoricalDtype,
+ DatetimeTZDtype,
+)
+
+import pandas as pd
+
+if typing.TYPE_CHECKING:
+ from pandas._typing import DtypeObj
+
+
+# Maps str(pyarrow.DataType) = C type format string
+# Currently, no pyarrow API for this
+PYARROW_CTYPES = {
+ "null": "n",
+ "bool": "b",
+ "uint8": "C",
+ "uint16": "S",
+ "uint32": "I",
+ "uint64": "L",
+ "int8": "c",
+ "int16": "S",
+ "int32": "i",
+ "int64": "l",
+ "halffloat": "e", # float16
+ "float": "f", # float32
+ "double": "g", # float64
+ "string": "u",
+ "large_string": "U",
+ "binary": "z",
+ "time32[s]": "tts",
+ "time32[ms]": "ttm",
+ "time64[us]": "ttu",
+ "time64[ns]": "ttn",
+ "date32[day]": "tdD",
+ "date64[ms]": "tdm",
+ "timestamp[s]": "tss:",
+ "timestamp[ms]": "tsm:",
+ "timestamp[us]": "tsu:",
+ "timestamp[ns]": "tsn:",
+ "duration[s]": "tDs",
+ "duration[ms]": "tDm",
+ "duration[us]": "tDu",
+ "duration[ns]": "tDn",
+}
+
+
+class ArrowCTypes:
+ """
+ Enum for Apache Arrow C type format strings.
+
+ The Arrow C data interface:
+ https://arrow.apache.org/docs/format/CDataInterface.html#data-type-description-format-strings
+ """
+
+ NULL = "n"
+ BOOL = "b"
+ INT8 = "c"
+ UINT8 = "C"
+ INT16 = "s"
+ UINT16 = "S"
+ INT32 = "i"
+ UINT32 = "I"
+ INT64 = "l"
+ UINT64 = "L"
+ FLOAT16 = "e"
+ FLOAT32 = "f"
+ FLOAT64 = "g"
+ STRING = "u" # utf-8
+ LARGE_STRING = "U" # utf-8
+ DATE32 = "tdD"
+ DATE64 = "tdm"
+ # Resoulution:
+ # - seconds -> 's'
+ # - milliseconds -> 'm'
+ # - microseconds -> 'u'
+ # - nanoseconds -> 'n'
+ TIMESTAMP = "ts{resolution}:{tz}"
+ TIME = "tt{resolution}"
+
+
+class Endianness:
+ """Enum indicating the byte-order of a data-type."""
+
+ LITTLE = "<"
+ BIG = ">"
+ NATIVE = "="
+ NA = "|"
+
+
+def dtype_to_arrow_c_fmt(dtype: DtypeObj) -> str:
+ """
+ Represent pandas `dtype` as a format string in Apache Arrow C notation.
+
+ Parameters
+ ----------
+ dtype : np.dtype
+ Datatype of pandas DataFrame to represent.
+
+ Returns
+ -------
+ str
+ Format string in Apache Arrow C notation of the given `dtype`.
+ """
+ if isinstance(dtype, CategoricalDtype):
+ return ArrowCTypes.INT64
+ elif dtype == np.dtype("O"):
+ return ArrowCTypes.STRING
+ elif isinstance(dtype, ArrowDtype):
+ import pyarrow as pa
+
+ pa_type = dtype.pyarrow_dtype
+ if pa.types.is_decimal(pa_type):
+ return f"d:{pa_type.precision},{pa_type.scale}"
+ elif pa.types.is_timestamp(pa_type) and pa_type.tz is not None:
+ return f"ts{pa_type.unit[0]}:{pa_type.tz}"
+ format_str = PYARROW_CTYPES.get(str(pa_type), None)
+ if format_str is not None:
+ return format_str
+
+ format_str = getattr(ArrowCTypes, dtype.name.upper(), None)
+ if format_str is not None:
+ return format_str
+
+ if isinstance(dtype, pd.StringDtype):
+ # TODO(infer_string) this should be LARGE_STRING for pyarrow storage,
+ # but current tests don't cover this distinction
+ return ArrowCTypes.STRING
+
+ elif lib.is_np_dtype(dtype, "M"):
+ # Selecting the first char of resolution string:
+ # dtype.str -> ' 'n'
+ resolution = np.datetime_data(dtype)[0][0]
+ return ArrowCTypes.TIMESTAMP.format(resolution=resolution, tz="")
+
+ elif isinstance(dtype, DatetimeTZDtype):
+ return ArrowCTypes.TIMESTAMP.format(resolution=dtype.unit[0], tz=dtype.tz)
+
+ elif isinstance(dtype, pd.BooleanDtype):
+ return ArrowCTypes.BOOL
+
+ raise NotImplementedError(
+ f"Conversion of {dtype} to Arrow C format string is not implemented."
+ )
+
+
+def maybe_rechunk(series: pd.Series, *, allow_copy: bool) -> pd.Series | None:
+ """
+ Rechunk a multi-chunk pyarrow array into a single-chunk array, if necessary.
+
+ - Returns `None` if the input series is not backed by a multi-chunk pyarrow array
+ (and so doesn't need rechunking)
+ - Returns a single-chunk-backed-Series if the input is backed by a multi-chunk
+ pyarrow array and `allow_copy` is `True`.
+ - Raises a `RuntimeError` if `allow_copy` is `False` and input is a
+ based by a multi-chunk pyarrow array.
+ """
+ if not isinstance(series.dtype, pd.ArrowDtype):
+ return None
+ chunked_array = series.array._pa_array # type: ignore[attr-defined]
+ if len(chunked_array.chunks) == 1:
+ return None
+ if not allow_copy:
+ raise RuntimeError(
+ "Found multi-chunk pyarrow array, but `allow_copy` is False. "
+ "Please rechunk the array before calling this function, or set "
+ "`allow_copy=True`."
+ )
+ arr = chunked_array.combine_chunks()
+ return pd.Series(arr, dtype=series.dtype, name=series.name, index=series.index)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/internals/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/internals/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ff3933a5c2552e61852164bcb7b02639fee29e7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/internals/__init__.py
@@ -0,0 +1,65 @@
+from pandas.core.internals.api import make_block # 2023-09-18 pyarrow uses this
+from pandas.core.internals.concat import concatenate_managers
+from pandas.core.internals.managers import (
+ BlockManager,
+ SingleBlockManager,
+)
+
+__all__ = [
+ "Block", # pyright:ignore[reportUnsupportedDunderAll)]
+ "BlockManager",
+ "DatetimeTZBlock", # pyright:ignore[reportUnsupportedDunderAll)]
+ "ExtensionBlock", # pyright:ignore[reportUnsupportedDunderAll)]
+ "SingleBlockManager",
+ "concatenate_managers",
+ "make_block",
+]
+
+
+def __getattr__(name: str):
+ # GH#55139
+ import warnings
+
+ from pandas.errors import Pandas4Warning
+
+ if name == "create_block_manager_from_blocks":
+ # GH#33892, GH#58715
+ warnings.warn(
+ f"{name} is deprecated and will be removed in a future version. "
+ "Use public APIs instead.",
+ Pandas4Warning,
+ # https://github.com/pandas-dev/pandas/pull/55139#pullrequestreview-1720690758
+ # on hard-coding stacklevel
+ stacklevel=2,
+ )
+ from pandas.core.internals.managers import create_block_manager_from_blocks
+
+ return create_block_manager_from_blocks
+
+ if name in [
+ "Block",
+ "ExtensionBlock",
+ "DatetimeTZBlock",
+ ]:
+ warnings.warn(
+ f"{name} is deprecated and will be removed in a future version. "
+ "Use public APIs instead.",
+ Pandas4Warning,
+ # https://github.com/pandas-dev/pandas/pull/55139#pullrequestreview-1720690758
+ # on hard-coding stacklevel
+ stacklevel=2,
+ )
+ if name == "DatetimeTZBlock":
+ from pandas.core.internals.api import _DatetimeTZBlock as DatetimeTZBlock
+
+ return DatetimeTZBlock
+ if name == "ExtensionBlock":
+ from pandas.core.internals.blocks import ExtensionBlock
+
+ return ExtensionBlock
+ else:
+ from pandas.core.internals.blocks import Block
+
+ return Block
+
+ raise AttributeError(f"module 'pandas.core.internals' has no attribute '{name}'")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/internals/api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/internals/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..6031d2f61ef42686ce850fb7f4ddfb7a959b2c96
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/internals/api.py
@@ -0,0 +1,177 @@
+"""
+This is a pseudo-public API for downstream libraries. We ask that downstream
+authors
+
+1) Try to avoid using internals directly altogether, and failing that,
+2) Use only functions exposed here (or in core.internals)
+
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+import warnings
+
+import numpy as np
+
+from pandas._libs.internals import BlockPlacement
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.common import pandas_dtype
+from pandas.core.dtypes.dtypes import (
+ DatetimeTZDtype,
+ ExtensionDtype,
+ PeriodDtype,
+)
+
+from pandas.core.arrays import (
+ DatetimeArray,
+ TimedeltaArray,
+)
+from pandas.core.construction import extract_array
+from pandas.core.internals.blocks import (
+ DatetimeLikeBlock,
+ check_ndim,
+ ensure_block_shape,
+ extract_pandas_array,
+ get_block_type,
+ maybe_coerce_values,
+)
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ ArrayLike,
+ Dtype,
+ )
+
+ from pandas.core.internals.blocks import Block
+
+
+def _make_block(values: ArrayLike, placement: np.ndarray) -> Block:
+ """
+ This is an analogue to blocks.new_block(_2d) that ensures:
+ 1) correct dimension for EAs that support 2D (`ensure_block_shape`), and
+ 2) correct EA class for datetime64/timedelta64 (`maybe_coerce_values`).
+
+ The input `values` is assumed to be either numpy array or ExtensionArray:
+ - In case of a numpy array, it is assumed to already be in the expected
+ shape for Blocks (2D, (cols, rows)).
+ - In case of an ExtensionArray the input can be 1D, also for EAs that are
+ internally stored as 2D.
+
+ For the rest no preprocessing or validation is done, except for those dtypes
+ that are internally stored as EAs but have an exact numpy equivalent (and at
+ the moment use that numpy dtype), i.e. datetime64/timedelta64.
+ """
+ dtype = values.dtype
+ klass = get_block_type(dtype)
+ placement_obj = BlockPlacement(placement)
+
+ if (isinstance(dtype, ExtensionDtype) and dtype._supports_2d) or isinstance(
+ values, (DatetimeArray, TimedeltaArray)
+ ):
+ values = ensure_block_shape(values, ndim=2)
+
+ values = maybe_coerce_values(values)
+ return klass(values, ndim=2, placement=placement_obj)
+
+
+class _DatetimeTZBlock(DatetimeLikeBlock):
+ """implement a datetime64 block with a tz attribute"""
+
+ values: DatetimeArray
+
+ __slots__ = ()
+
+
+def make_block(
+ values, placement, klass=None, ndim=None, dtype: Dtype | None = None
+) -> Block:
+ """
+ This is a pseudo-public analogue to blocks.new_block.
+
+ We ask that downstream libraries use this rather than any fully-internal
+ APIs, including but not limited to:
+
+ - core.internals.blocks.make_block
+ - Block.make_block
+ - Block.make_block_same_class
+ - Block.__init__
+ """
+ warnings.warn(
+ # GH#56815
+ "make_block is deprecated and will be removed in a future version. "
+ "Use pd.api.internals.create_dataframe_from_blocks or "
+ "(recommended) higher-level public APIs instead.",
+ Pandas4Warning,
+ stacklevel=2,
+ )
+
+ if dtype is not None:
+ dtype = pandas_dtype(dtype)
+
+ values, dtype = extract_pandas_array(values, dtype, ndim)
+
+ from pandas.core.internals.blocks import ExtensionBlock
+
+ if klass is ExtensionBlock and isinstance(values.dtype, PeriodDtype):
+ # GH-44681 changed PeriodArray to be stored in the 2D
+ # NDArrayBackedExtensionBlock instead of ExtensionBlock
+ # -> still allow ExtensionBlock to be passed in this case for back compat
+ klass = None
+
+ if klass is None:
+ dtype = dtype or values.dtype
+ klass = get_block_type(dtype)
+
+ elif klass is _DatetimeTZBlock and not isinstance(values.dtype, DatetimeTZDtype):
+ # pyarrow calls get here (pyarrow<15)
+ values = DatetimeArray._simple_new(
+ # error: Argument "dtype" to "_simple_new" of "DatetimeArray" has
+ # incompatible type "Union[ExtensionDtype, dtype[Any], None]";
+ # expected "Union[dtype[datetime64], DatetimeTZDtype]"
+ values,
+ dtype=dtype, # type: ignore[arg-type]
+ )
+
+ if not isinstance(placement, BlockPlacement):
+ placement = BlockPlacement(placement)
+
+ ndim = _maybe_infer_ndim(values, placement, ndim)
+ if isinstance(values.dtype, (PeriodDtype, DatetimeTZDtype)):
+ # GH#41168 ensure we can pass 1D dt64tz values
+ # More generally, any EA dtype that isn't is_1d_only_ea_dtype
+ values = extract_array(values, extract_numpy=True)
+ values = ensure_block_shape(values, ndim)
+
+ check_ndim(values, placement, ndim)
+ values = maybe_coerce_values(values)
+ return klass(values, ndim=ndim, placement=placement)
+
+
+def _maybe_infer_ndim(values, placement: BlockPlacement, ndim: int | None) -> int:
+ """
+ If `ndim` is not provided, infer it from placement and values.
+ """
+ if ndim is None:
+ # GH#38134 Block constructor now assumes ndim is not None
+ if not isinstance(values.dtype, np.dtype):
+ if len(placement) != 1:
+ ndim = 1
+ else:
+ ndim = 2
+ else:
+ ndim = values.ndim
+ return ndim
+
+
+def maybe_infer_ndim(values, placement: BlockPlacement, ndim: int | None) -> int:
+ """
+ If `ndim` is not provided, infer it from placement and values.
+ """
+ warnings.warn(
+ "maybe_infer_ndim is deprecated and will be removed in a future version.",
+ Pandas4Warning,
+ stacklevel=2,
+ )
+ return _maybe_infer_ndim(values, placement, ndim)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/methods/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/methods/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/methods/to_dict.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/methods/to_dict.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c9ca5f4d0d3c54205a5c9d5541e7f0abb57a748
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/methods/to_dict.py
@@ -0,0 +1,289 @@
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ Literal,
+ overload,
+)
+import warnings
+
+import numpy as np
+
+from pandas._libs import (
+ lib,
+ missing as libmissing,
+)
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core.dtypes.cast import maybe_box_native
+from pandas.core.dtypes.dtypes import (
+ BaseMaskedDtype,
+ ExtensionDtype,
+)
+
+from pandas.core import common as com
+
+if TYPE_CHECKING:
+ from collections.abc import Generator
+
+ from pandas._typing import MutableMappingT
+
+ from pandas import DataFrame
+
+
+def create_data_for_split(
+ df: DataFrame, are_all_object_dtype_cols: bool, object_dtype_indices: list[int]
+) -> Generator[list]:
+ """
+ Simple helper method to create data for to ``to_dict(orient="split")``
+ to create the main output data
+ """
+ if are_all_object_dtype_cols:
+ for tup in df.itertuples(index=False, name=None):
+ yield list(map(maybe_box_native, tup))
+ else:
+ for tup in df.itertuples(index=False, name=None):
+ data = list(tup)
+ if object_dtype_indices:
+ # If we have object_dtype_cols, apply maybe_box_naive after
+ # for perf
+ for i in object_dtype_indices:
+ data[i] = maybe_box_native(data[i])
+ yield data
+
+
+@overload
+def to_dict(
+ df: DataFrame,
+ orient: Literal["dict", "list", "series", "split", "tight", "index"] = ...,
+ *,
+ into: type[MutableMappingT] | MutableMappingT,
+ index: bool = ...,
+) -> MutableMappingT: ...
+
+
+@overload
+def to_dict(
+ df: DataFrame,
+ orient: Literal["records"],
+ *,
+ into: type[MutableMappingT] | MutableMappingT,
+ index: bool = ...,
+) -> list[MutableMappingT]: ...
+
+
+@overload
+def to_dict(
+ df: DataFrame,
+ orient: Literal["dict", "list", "series", "split", "tight", "index"] = ...,
+ *,
+ into: type[dict] = ...,
+ index: bool = ...,
+) -> dict: ...
+
+
+@overload
+def to_dict(
+ df: DataFrame,
+ orient: Literal["records"],
+ *,
+ into: type[dict] = ...,
+ index: bool = ...,
+) -> list[dict]: ...
+
+
+# error: Incompatible default for argument "into" (default has type "type[dict
+# [Any, Any]]", argument has type "type[MutableMappingT] | MutableMappingT")
+def to_dict(
+ df: DataFrame,
+ orient: Literal[
+ "dict", "list", "series", "split", "tight", "records", "index"
+ ] = "dict",
+ *,
+ into: type[MutableMappingT] | MutableMappingT = dict, # type: ignore[assignment]
+ index: bool = True,
+) -> MutableMappingT | list[MutableMappingT]:
+ """
+ Convert the DataFrame to a dictionary.
+
+ The type of the key-value pairs can be customized with the parameters
+ (see below).
+
+ Parameters
+ ----------
+ orient : str {'dict', 'list', 'series', 'split', 'tight', 'records', 'index'}
+ Determines the type of the values of the dictionary.
+
+ - 'dict' (default) : dict like {column -> {index -> value}}
+ - 'list' : dict like {column -> [values]}
+ - 'series' : dict like {column -> Series(values)}
+ - 'split' : dict like
+ {'index' -> [index], 'columns' -> [columns], 'data' -> [values]}
+ - 'tight' : dict like
+ {'index' -> [index], 'columns' -> [columns], 'data' -> [values],
+ 'index_names' -> [index.names], 'column_names' -> [column.names]}
+ - 'records' : list like
+ [{column -> value}, ... , {column -> value}]
+ - 'index' : dict like {index -> {column -> value}}
+
+ into : class, default dict
+ The collections.abc.MutableMapping subclass used for all Mappings
+ in the return value. Can be the actual class or an empty
+ instance of the mapping type you want. If you want a
+ collections.defaultdict, you must pass it initialized.
+
+ index : bool, default True
+ Whether to include the index item (and index_names item if `orient`
+ is 'tight') in the returned dictionary. Can only be ``False``
+ when `orient` is 'split' or 'tight'.
+
+ .. versionadded:: 2.0.0
+
+ Returns
+ -------
+ dict, list or collections.abc.Mapping
+ Return a collections.abc.MutableMapping object representing the
+ DataFrame. The resulting transformation depends on the `orient` parameter.
+ """
+ if orient != "tight" and not df.columns.is_unique:
+ warnings.warn(
+ "DataFrame columns are not unique, some columns will be omitted.",
+ UserWarning,
+ stacklevel=find_stack_level(),
+ )
+ # GH16122
+ # error: Call to untyped function "standardize_mapping" in typed context
+ into_c = com.standardize_mapping(into) # type: ignore[no-untyped-call]
+
+ # error: Incompatible types in assignment (expression has type "str",
+ # variable has type "Literal['dict', 'list', 'series', 'split', 'tight',
+ # 'records', 'index']")
+ orient = orient.lower() # type: ignore[assignment]
+
+ if not index and orient not in ["split", "tight"]:
+ raise ValueError(
+ "'index=False' is only valid when 'orient' is 'split' or 'tight'"
+ )
+
+ if orient == "series":
+ # GH46470 Return quickly if orient series to avoid creating dtype objects
+ return into_c((k, v) for k, v in df.items())
+
+ if orient == "dict":
+ return into_c((k, v.to_dict(into=into)) for k, v in df.items())
+
+ box_native_indices = [
+ i
+ for i, col_dtype in enumerate(df.dtypes.values)
+ if col_dtype == np.dtype(object) or isinstance(col_dtype, ExtensionDtype)
+ ]
+
+ are_all_object_dtype_cols = len(box_native_indices) == len(df.dtypes)
+
+ if orient == "list":
+ object_dtype_indices_as_set: set[int] = set(box_native_indices)
+ box_na_values = (
+ lib.no_default
+ if not isinstance(col_dtype, BaseMaskedDtype)
+ else libmissing.NA
+ for col_dtype in df.dtypes.values
+ )
+ return into_c(
+ (
+ k,
+ list(map(maybe_box_native, v.to_numpy(na_value=box_na_value)))
+ if i in object_dtype_indices_as_set
+ else list(map(maybe_box_native, v.to_numpy())),
+ )
+ for i, (box_na_value, (k, v)) in enumerate(
+ zip(box_na_values, df.items(), strict=True)
+ )
+ )
+
+ elif orient == "split":
+ data = list(
+ create_data_for_split(df, are_all_object_dtype_cols, box_native_indices)
+ )
+
+ return into_c(
+ ((("index", df.index.tolist()),) if index else ())
+ + (
+ ("columns", df.columns.tolist()),
+ ("data", data),
+ )
+ )
+
+ elif orient == "tight":
+ return into_c(
+ ((("index", df.index.tolist()),) if index else ())
+ + (
+ ("columns", df.columns.tolist()),
+ (
+ "data",
+ [
+ list(map(maybe_box_native, t))
+ for t in df.itertuples(index=False, name=None)
+ ],
+ ),
+ )
+ + ((("index_names", list(df.index.names)),) if index else ())
+ + (("column_names", list(df.columns.names)),)
+ )
+
+ elif orient == "records":
+ columns = df.columns.tolist()
+ if are_all_object_dtype_cols:
+ return [
+ into_c(zip(columns, map(maybe_box_native, row), strict=True))
+ for row in df.itertuples(index=False, name=None)
+ ]
+ else:
+ data = [
+ into_c(zip(columns, t, strict=True))
+ for t in df.itertuples(index=False, name=None)
+ ]
+ if box_native_indices:
+ object_dtype_indices_as_set = set(box_native_indices)
+ object_dtype_cols = {
+ col
+ for i, col in enumerate(df.columns)
+ if i in object_dtype_indices_as_set
+ }
+ for row in data:
+ for col in object_dtype_cols:
+ row[col] = maybe_box_native(row[col])
+ return data # type: ignore[return-value]
+
+ elif orient == "index":
+ if not df.index.is_unique:
+ raise ValueError("DataFrame index must be unique for orient='index'.")
+ columns = df.columns.tolist()
+ if are_all_object_dtype_cols:
+ return into_c(
+ (t[0], dict(zip(df.columns, map(maybe_box_native, t[1:]), strict=True)))
+ for t in df.itertuples(name=None)
+ )
+ elif box_native_indices:
+ object_dtype_indices_as_set = set(box_native_indices)
+ return into_c(
+ (
+ t[0],
+ {
+ column: maybe_box_native(v)
+ if i in object_dtype_indices_as_set
+ else v
+ for i, (column, v) in enumerate(
+ zip(columns, t[1:], strict=True)
+ )
+ },
+ )
+ for t in df.itertuples(name=None)
+ )
+ else:
+ return into_c(
+ (t[0], dict(zip(columns, t[1:], strict=True)))
+ for t in df.itertuples(name=None)
+ )
+
+ else:
+ raise ValueError(f"orient '{orient}' not understood")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/ops/array_ops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/ops/array_ops.py
new file mode 100644
index 0000000000000000000000000000000000000000..c362520c04dd1394b9eb7b6d3ccd7e8d2b8f38a8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/ops/array_ops.py
@@ -0,0 +1,620 @@
+"""
+Functions for arithmetic and comparison operations on NumPy arrays and
+ExtensionArrays.
+"""
+
+from __future__ import annotations
+
+import datetime
+from functools import partial
+import operator
+from typing import (
+ TYPE_CHECKING,
+ Any,
+)
+
+import numpy as np
+
+from pandas._libs import (
+ NaT,
+ Timedelta,
+ Timestamp,
+ lib,
+ ops as libops,
+)
+from pandas._libs.tslibs import (
+ BaseOffset,
+ get_supported_dtype,
+ is_supported_dtype,
+ is_unitless,
+)
+
+from pandas.core.dtypes.cast import (
+ construct_1d_object_array_from_listlike,
+ find_common_type,
+)
+from pandas.core.dtypes.common import (
+ ensure_object,
+ is_bool_dtype,
+ is_list_like,
+ is_numeric_v_string_like,
+ is_object_dtype,
+ is_scalar,
+)
+from pandas.core.dtypes.generic import (
+ ABCExtensionArray,
+ ABCIndex,
+ ABCSeries,
+)
+from pandas.core.dtypes.missing import (
+ isna,
+ notna,
+)
+
+from pandas.core import roperator
+from pandas.core.computation import expressions
+from pandas.core.construction import (
+ ensure_wrapped_if_datetimelike,
+ sanitize_array,
+)
+from pandas.core.ops import missing
+from pandas.core.ops.dispatch import should_extension_dispatch
+from pandas.core.ops.invalid import invalid_comparison
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ ArrayLike,
+ Shape,
+ )
+
+# -----------------------------------------------------------------------------
+# Masking NA values and fallbacks for operations numpy does not support
+
+
+def fill_binop(left, right, fill_value):
+ """
+ If a non-None fill_value is given, replace null entries in left and right
+ with this value, but only in positions where _one_ of left/right is null,
+ not both.
+
+ Parameters
+ ----------
+ left : array-like
+ right : array-like
+ fill_value : object
+
+ Returns
+ -------
+ left : array-like
+ right : array-like
+
+ Notes
+ -----
+ Makes copies if fill_value is not None and NAs are present.
+ """
+ if fill_value is not None:
+ left_mask = isna(left)
+ right_mask = isna(right)
+
+ # one but not both
+ mask = left_mask ^ right_mask
+
+ if left_mask.any():
+ # Avoid making a copy if we can
+ left = left.copy()
+ left[left_mask & mask] = fill_value
+
+ if right_mask.any():
+ # Avoid making a copy if we can
+ right = right.copy()
+ right[right_mask & mask] = fill_value
+
+ return left, right
+
+
+def comp_method_OBJECT_ARRAY(op, x, y):
+ if isinstance(y, list):
+ # e.g. test_tuple_categories
+ y = construct_1d_object_array_from_listlike(y)
+
+ if isinstance(y, (np.ndarray, ABCSeries, ABCIndex)):
+ if not is_object_dtype(y.dtype):
+ y = y.astype(np.object_)
+
+ if isinstance(y, (ABCSeries, ABCIndex)):
+ y = y._values
+
+ if x.shape != y.shape:
+ raise ValueError("Shapes must match", x.shape, y.shape)
+ result = libops.vec_compare(x.ravel(), y.ravel(), op)
+ else:
+ result = libops.scalar_compare(x.ravel(), y, op)
+ return result.reshape(x.shape)
+
+
+def _masked_arith_op(x: np.ndarray, y, op) -> np.ndarray:
+ """
+ If the given arithmetic operation fails, attempt it again on
+ only the non-null elements of the input array(s).
+
+ Parameters
+ ----------
+ x : np.ndarray
+ y : np.ndarray, Series, Index
+ op : binary operator
+ """
+ # For Series `x` is 1D so ravel() is a no-op; calling it anyway makes
+ # the logic valid for both Series and DataFrame ops.
+ xrav = x.ravel()
+
+ if isinstance(y, np.ndarray):
+ dtype = find_common_type([x.dtype, y.dtype])
+ result = np.empty(x.size, dtype=dtype)
+
+ if len(x) != len(y):
+ raise ValueError(x.shape, y.shape)
+ ymask = notna(y)
+
+ # NB: ravel() is only safe since y is ndarray; for e.g. PeriodIndex
+ # we would get int64 dtype, see GH#19956
+ yrav = y.ravel()
+ mask = notna(xrav) & ymask.ravel()
+
+ # See GH#5284, GH#5035, GH#19448 for historical reference
+ if mask.any():
+ result[mask] = op(xrav[mask], yrav[mask])
+
+ else:
+ if not is_scalar(y):
+ raise TypeError(
+ f"Cannot broadcast np.ndarray with operand of type {type(y)}"
+ )
+
+ # mask is only meaningful for x
+ result = np.empty(x.size, dtype=x.dtype)
+ mask = notna(xrav)
+
+ # 1 ** np.nan is 1. So we have to unmask those.
+ if op is pow:
+ mask = np.where(x == 1, False, mask)
+ elif op is roperator.rpow:
+ mask = np.where(y == 1, False, mask)
+
+ if mask.any():
+ result[mask] = op(xrav[mask], y)
+
+ np.putmask(result, ~mask, np.nan)
+ result = result.reshape(x.shape) # 2D compat
+ return result
+
+
+def _na_arithmetic_op(left: np.ndarray, right, op, is_cmp: bool = False):
+ """
+ Return the result of evaluating op on the passed in values.
+
+ If native types are not compatible, try coercion to object dtype.
+
+ Parameters
+ ----------
+ left : np.ndarray
+ right : np.ndarray or scalar
+ Excludes DataFrame, Series, Index, ExtensionArray.
+ is_cmp : bool, default False
+ If this a comparison operation.
+
+ Returns
+ -------
+ array-like
+
+ Raises
+ ------
+ TypeError : invalid operation
+ """
+ if isinstance(right, str):
+ # can never use numexpr
+ func = op
+ else:
+ func = partial(expressions.evaluate, op)
+
+ try:
+ result = func(left, right)
+ except TypeError:
+ if not is_cmp and (
+ left.dtype == object or getattr(right, "dtype", None) == object
+ ):
+ # For object dtype, fallback to a masked operation (only operating
+ # on the non-missing values)
+ # Don't do this for comparisons, as that will handle complex numbers
+ # incorrectly, see GH#32047
+ result = _masked_arith_op(left, right, op)
+ else:
+ raise
+
+ if is_cmp and (is_scalar(result) or result is NotImplemented):
+ # numpy returned a scalar instead of operating element-wise
+ # e.g. numeric array vs str
+ # TODO: can remove this after dropping some future numpy version?
+ return invalid_comparison(left, right, op)
+
+ return missing.dispatch_fill_zeros(op, left, right, result)
+
+
+def arithmetic_op(left: ArrayLike, right: Any, op):
+ """
+ Evaluate an arithmetic operation `+`, `-`, `*`, `/`, `//`, `%`, `**`, ...
+
+ Note: the caller is responsible for ensuring that numpy warnings are
+ suppressed (with np.errstate(all="ignore")) if needed.
+
+ Parameters
+ ----------
+ left : np.ndarray or ExtensionArray
+ right : object
+ Cannot be a DataFrame or Index. Series is *not* excluded.
+ op : {operator.add, operator.sub, ...}
+ Or one of the reversed variants from roperator.
+
+ Returns
+ -------
+ ndarray or ExtensionArray
+ Or a 2-tuple of these in the case of divmod or rdivmod.
+ """
+ # NB: We assume that extract_array and ensure_wrapped_if_datetimelike
+ # have already been called on `left` and `right`,
+ # and `maybe_prepare_scalar_for_op` has already been called on `right`
+ # We need to special-case datetime64/timedelta64 dtypes (e.g. because numpy
+ # casts integer dtypes to timedelta64 when operating with timedelta64 - GH#22390)
+ if isinstance(right, list):
+ # GH#62423
+ right = sanitize_array(right, None)
+ right = ensure_wrapped_if_datetimelike(right)
+
+ if (
+ should_extension_dispatch(left, right)
+ or isinstance(right, (Timedelta, BaseOffset, Timestamp))
+ or right is NaT
+ ):
+ # Timedelta/Timestamp and other custom scalars are included in the check
+ # because numexpr will fail on it, see GH#31457
+ res_values = op(left, right)
+ else:
+ # TODO we should handle EAs consistently and move this check before the if/else
+ # (https://github.com/pandas-dev/pandas/issues/41165)
+ # error: Argument 2 to "_bool_arith_check" has incompatible type
+ # "Union[ExtensionArray, ndarray[Any, Any]]"; expected "ndarray[Any, Any]"
+ _bool_arith_check(op, left, right) # type: ignore[arg-type]
+
+ # error: Argument 1 to "_na_arithmetic_op" has incompatible type
+ # "Union[ExtensionArray, ndarray[Any, Any]]"; expected "ndarray[Any, Any]"
+ res_values = _na_arithmetic_op(left, right, op) # type: ignore[arg-type]
+
+ return res_values
+
+
+def comparison_op(left: ArrayLike, right: Any, op) -> ArrayLike:
+ """
+ Evaluate a comparison operation `=`, `!=`, `>=`, `>`, `<=`, or `<`.
+
+ Note: the caller is responsible for ensuring that numpy warnings are
+ suppressed (with np.errstate(all="ignore")) if needed.
+
+ Parameters
+ ----------
+ left : np.ndarray or ExtensionArray
+ right : object
+ Cannot be a DataFrame, Series, or Index.
+ op : {operator.eq, operator.ne, operator.gt, operator.ge, operator.lt, operator.le}
+
+ Returns
+ -------
+ ndarray or ExtensionArray
+ """
+ # NB: We assume extract_array has already been called on left and right
+ lvalues = ensure_wrapped_if_datetimelike(left)
+ rvalues = ensure_wrapped_if_datetimelike(right)
+
+ rvalues = lib.item_from_zerodim(rvalues)
+
+ # Special handling needed if rvalues is a zerodim np.ndarray subclass, see GH#63205
+ rvalues_is_zerodim: bool = getattr(rvalues, "ndim", None) == 0
+
+ if isinstance(rvalues, list):
+ # We don't catch tuple here bc we may be comparing e.g. MultiIndex
+ # to a tuple that represents a single entry, see test_compare_tuple_strs
+ rvalues = sanitize_array(rvalues, None)
+ rvalues = ensure_wrapped_if_datetimelike(rvalues)
+
+ if isinstance(rvalues, (np.ndarray, ABCExtensionArray)) and not rvalues_is_zerodim:
+ # TODO: make this treatment consistent across ops and classes.
+ # We are not catching all listlikes here (e.g. frozenset, tuple)
+ # The ambiguous case is object-dtype. See GH#27803
+ if len(lvalues) != len(rvalues):
+ raise ValueError(
+ "Lengths must match to compare", lvalues.shape, rvalues.shape
+ )
+
+ if should_extension_dispatch(lvalues, rvalues) or (
+ (isinstance(rvalues, (Timedelta, BaseOffset, Timestamp)) or right is NaT)
+ and lvalues.dtype != object
+ ):
+ # Call the method on lvalues
+ res_values = op(lvalues, rvalues)
+
+ # TODO: but not pd.NA?
+ elif (is_scalar(rvalues) or rvalues_is_zerodim) and isna(rvalues):
+ # numpy does not like comparisons vs None
+ if op is operator.ne:
+ res_values = np.ones(lvalues.shape, dtype=bool)
+ else:
+ res_values = np.zeros(lvalues.shape, dtype=bool)
+
+ elif is_numeric_v_string_like(lvalues, rvalues):
+ # GH#36377 going through the numexpr path would incorrectly raise
+ return invalid_comparison(lvalues, rvalues, op)
+
+ elif lvalues.dtype == object or isinstance(rvalues, str):
+ res_values = comp_method_OBJECT_ARRAY(op, lvalues, rvalues)
+
+ else:
+ res_values = _na_arithmetic_op(lvalues, rvalues, op, is_cmp=True)
+
+ return res_values
+
+
+def na_logical_op(x: np.ndarray, y, op):
+ try:
+ # For exposition, write:
+ # yarr = isinstance(y, np.ndarray)
+ # yint = is_integer(y) or (yarr and y.dtype.kind == "i")
+ # ybool = is_bool(y) or (yarr and y.dtype.kind == "b")
+ # xint = x.dtype.kind == "i"
+ # xbool = x.dtype.kind == "b"
+ # Then Cases where this goes through without raising include:
+ # (xint or xbool) and (yint or bool)
+ result = op(x, y)
+ except TypeError:
+ if isinstance(y, np.ndarray):
+ # bool-bool dtype operations should be OK, should not get here
+ assert not (x.dtype.kind == "b" and y.dtype.kind == "b")
+ x = ensure_object(x)
+ y = ensure_object(y)
+ result = libops.vec_binop(x.ravel(), y.ravel(), op)
+ else:
+ # let null fall thru
+ assert lib.is_scalar(y)
+ if not isna(y):
+ y = bool(y)
+ try:
+ result = libops.scalar_binop(x, y, op)
+ except (
+ TypeError,
+ ValueError,
+ AttributeError,
+ OverflowError,
+ NotImplementedError,
+ ) as err:
+ typ = type(y).__name__
+ raise TypeError(
+ f"Cannot perform '{op.__name__}' with a dtyped [{x.dtype}] array "
+ f"and scalar of type [{typ}]"
+ ) from err
+
+ return result.reshape(x.shape)
+
+
+def logical_op(left: ArrayLike, right: Any, op) -> ArrayLike:
+ """
+ Evaluate a logical operation `|`, `&`, or `^`.
+
+ Parameters
+ ----------
+ left : np.ndarray or ExtensionArray
+ right : object
+ Cannot be a DataFrame, Series, or Index.
+ op : {operator.and_, operator.or_, operator.xor}
+ Or one of the reversed variants from roperator.
+
+ Returns
+ -------
+ ndarray or ExtensionArray
+ """
+
+ def fill_bool(x, left=None):
+ # if `left` is specifically not-boolean, we do not cast to bool
+ if x.dtype.kind in "cfO":
+ # dtypes that can hold NA
+ mask = isna(x)
+ if mask.any():
+ x = x.astype(object)
+ x[mask] = False
+
+ if left is None or left.dtype.kind == "b":
+ x = x.astype(bool)
+ return x
+
+ right = lib.item_from_zerodim(right)
+ if is_list_like(right) and not hasattr(right, "dtype"):
+ # e.g. list, tuple
+ raise TypeError(
+ # GH#52264
+ "Logical ops (and, or, xor) between Pandas objects and dtype-less "
+ "sequences (e.g. list, tuple) are no longer supported. "
+ "Wrap the object in a Series, Index, or np.array "
+ "before operating instead.",
+ )
+
+ # NB: We assume extract_array has already been called on left and right
+ lvalues = ensure_wrapped_if_datetimelike(left)
+ rvalues = right
+
+ if should_extension_dispatch(lvalues, rvalues):
+ # Call the method on lvalues
+ res_values = op(lvalues, rvalues)
+
+ else:
+ if isinstance(rvalues, np.ndarray):
+ is_other_int_dtype = rvalues.dtype.kind in "iu"
+ if not is_other_int_dtype:
+ rvalues = fill_bool(rvalues, lvalues)
+
+ else:
+ # i.e. scalar
+ is_other_int_dtype = lib.is_integer(rvalues)
+
+ res_values = na_logical_op(lvalues, rvalues, op)
+
+ # For int vs int `^`, `|`, `&` are bitwise operators and return
+ # integer dtypes. Otherwise these are boolean ops
+ if not (left.dtype.kind in "iu" and is_other_int_dtype):
+ res_values = fill_bool(res_values)
+
+ return res_values
+
+
+def get_array_op(op):
+ """
+ Return a binary array operation corresponding to the given operator op.
+
+ Parameters
+ ----------
+ op : function
+ Binary operator from operator or roperator module.
+
+ Returns
+ -------
+ functools.partial
+ """
+ if isinstance(op, partial):
+ # We get here via dispatch_to_series in DataFrame case
+ # e.g. test_rolling_consistency_var_debiasing_factors
+ return op
+
+ op_name = op.__name__.strip("_").lstrip("r")
+ if op_name == "arith_op":
+ # Reached via DataFrame._combine_frame i.e. flex methods
+ # e.g. test_df_add_flex_filled_mixed_dtypes
+ return op
+
+ if op_name in {"eq", "ne", "lt", "le", "gt", "ge"}:
+ return partial(comparison_op, op=op)
+ elif op_name in {"and", "or", "xor", "rand", "ror", "rxor"}:
+ return partial(logical_op, op=op)
+ elif op_name in {
+ "add",
+ "sub",
+ "mul",
+ "truediv",
+ "floordiv",
+ "mod",
+ "divmod",
+ "pow",
+ }:
+ return partial(arithmetic_op, op=op)
+ else:
+ raise NotImplementedError(op_name)
+
+
+def maybe_prepare_scalar_for_op(obj, shape: Shape):
+ """
+ Cast non-pandas objects to pandas types to unify behavior of arithmetic
+ and comparison operations.
+
+ Parameters
+ ----------
+ obj: object
+ shape : tuple[int]
+
+ Returns
+ -------
+ out : object
+
+ Notes
+ -----
+ Be careful to call this *after* determining the `name` attribute to be
+ attached to the result of the arithmetic operation.
+ """
+ if type(obj) is datetime.timedelta:
+ # GH#22390 cast up to Timedelta to rely on Timedelta
+ # implementation; otherwise operation against numeric-dtype
+ # raises TypeError
+ return Timedelta(obj)
+ elif type(obj) is datetime.datetime:
+ # cast up to Timestamp to rely on Timestamp implementation, see Timedelta above
+ return Timestamp(obj)
+ elif isinstance(obj, np.datetime64):
+ # GH#28080 numpy casts integer-dtype to datetime64 when doing
+ # array[int] + datetime64, which we do not allow
+ if isna(obj):
+ from pandas.core.arrays import DatetimeArray
+
+ # Avoid possible ambiguities with pd.NaT
+ # GH 52295
+ if is_unitless(obj.dtype):
+ # Use second resolution to ensure that the result of e.g.
+ # `left - np.datetime64("NaT")` retains the unit of left.unit
+ obj = obj.astype("datetime64[s]")
+ elif not is_supported_dtype(obj.dtype):
+ new_dtype = get_supported_dtype(obj.dtype)
+ obj = obj.astype(new_dtype)
+ right = np.broadcast_to(obj, shape)
+ return DatetimeArray._simple_new(right, dtype=right.dtype)
+
+ return Timestamp(obj)
+
+ elif isinstance(obj, np.timedelta64):
+ if isna(obj):
+ from pandas.core.arrays import TimedeltaArray
+
+ # wrapping timedelta64("NaT") in Timedelta returns NaT,
+ # which would incorrectly be treated as a datetime-NaT, so
+ # we broadcast and wrap in a TimedeltaArray
+ # GH 52295
+ if is_unitless(obj.dtype):
+ # Use second resolution to ensure that the result of e.g.
+ # `left + np.timedelta64("NaT")` retains the unit of left.unit
+ obj = obj.astype("timedelta64[s]")
+ elif not is_supported_dtype(obj.dtype):
+ new_dtype = get_supported_dtype(obj.dtype)
+ obj = obj.astype(new_dtype)
+ right = np.broadcast_to(obj, shape)
+ return TimedeltaArray._simple_new(right, dtype=right.dtype)
+
+ # In particular non-nanosecond timedelta64 needs to be cast to
+ # nanoseconds, or else we get undesired behavior like
+ # np.timedelta64(3, 'D') / 2 == np.timedelta64(1, 'D')
+ return Timedelta(obj)
+
+ # We want NumPy numeric scalars to behave like Python scalars
+ # post NEP 50
+ elif isinstance(obj, np.integer):
+ return int(obj)
+
+ elif isinstance(obj, np.floating):
+ return float(obj)
+
+ return obj
+
+
+_BOOL_OP_NOT_ALLOWED = {
+ operator.truediv,
+ roperator.rtruediv,
+ operator.floordiv,
+ roperator.rfloordiv,
+ operator.pow,
+ roperator.rpow,
+ divmod,
+ roperator.rdivmod,
+}
+
+
+def _bool_arith_check(op, a: np.ndarray, b) -> None:
+ """
+ In contrast to numpy, pandas raises an error for certain operations
+ with booleans.
+ """
+ if op in _BOOL_OP_NOT_ALLOWED:
+ if a.dtype.kind == "b" and (is_bool_dtype(b) or lib.is_bool(b)):
+ op_name = op.__name__.strip("_").lstrip("r")
+ raise NotImplementedError(
+ f"operator '{op_name}' not implemented for bool dtypes"
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/ops/common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/ops/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..96f6a23578bf0810571b01a2dbfb9baa7685dab5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/ops/common.py
@@ -0,0 +1,157 @@
+"""
+Boilerplate functions used in defining binary operations.
+"""
+
+from __future__ import annotations
+
+from functools import wraps
+from typing import TYPE_CHECKING
+
+from pandas._libs.lib import item_from_zerodim
+from pandas._libs.missing import is_matching_na
+
+from pandas.core.dtypes.generic import (
+ ABCExtensionArray,
+ ABCIndex,
+ ABCSeries,
+)
+
+from pandas.core.construction import (
+ ensure_wrapped_if_datetimelike,
+ sanitize_array,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from pandas._typing import F
+
+
+def unpack_zerodim_and_defer(name: str) -> Callable[[F], F]:
+ """
+ Boilerplate for pandas conventions in arithmetic and comparison methods.
+
+ Parameters
+ ----------
+ name : str
+
+ Returns
+ -------
+ decorator
+ """
+
+ def wrapper(method: F) -> F:
+ return _unpack_zerodim_and_defer(method, name)
+
+ return wrapper
+
+
+def _unpack_zerodim_and_defer(method: F, name: str) -> F:
+ """
+ Boilerplate for pandas conventions in arithmetic and comparison methods.
+
+ Ensure method returns NotImplemented when operating against "senior"
+ classes. Ensure zero-dimensional ndarrays are always unpacked.
+
+ Parameters
+ ----------
+ method : binary method
+ name : str
+
+ Returns
+ -------
+ method
+ """
+ is_logical = name.strip("_") in ["or", "xor", "and", "ror", "rxor", "rand"]
+
+ @wraps(method)
+ def new_method(self, other):
+ prio = getattr(other, "__pandas_priority__", None)
+ if prio is not None:
+ if prio > self.__pandas_priority__:
+ # e.g. other is DataFrame while self is Index/Series/EA
+ return NotImplemented
+
+ other = item_from_zerodim(other)
+ if (
+ isinstance(self, ABCExtensionArray)
+ and isinstance(other, list)
+ and not is_logical
+ ):
+ # See GH#62423
+ other = sanitize_array(other, None)
+ other = ensure_wrapped_if_datetimelike(other)
+
+ return method(self, other)
+
+ # error: Incompatible return value type (got "Callable[[Any, Any], Any]",
+ # expected "F")
+ return new_method # type: ignore[return-value]
+
+
+def get_op_result_name(left, right):
+ """
+ Find the appropriate name to pin to an operation result. This result
+ should always be either an Index or a Series.
+
+ Parameters
+ ----------
+ left : {Series, Index}
+ right : object
+
+ Returns
+ -------
+ name : object
+ Usually a string
+ """
+ if isinstance(right, (ABCSeries, ABCIndex)):
+ name = _maybe_match_name(left, right)
+ else:
+ name = left.name
+ return name
+
+
+def _maybe_match_name(a, b):
+ """
+ Try to find a name to attach to the result of an operation between
+ a and b. If only one of these has a `name` attribute, return that
+ name. Otherwise return a consensus name if they match or None if
+ they have different names.
+
+ Parameters
+ ----------
+ a : object
+ b : object
+
+ Returns
+ -------
+ name : str or None
+
+ See Also
+ --------
+ pandas.core.common.consensus_name_attr
+ """
+ a_has = hasattr(a, "name")
+ b_has = hasattr(b, "name")
+ if a_has and b_has:
+ try:
+ if a.name == b.name:
+ return a.name
+ elif is_matching_na(a.name, b.name):
+ # e.g. both are np.nan
+ return a.name
+ else:
+ return None
+ except TypeError:
+ # pd.NA
+ if is_matching_na(a.name, b.name):
+ return a.name
+ return None
+ except ValueError:
+ # e.g. np.int64(1) vs (np.int64(1), np.int64(2))
+ return None
+ elif a_has:
+ return a.name
+ elif b_has:
+ return b.name
+ return None
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..b1884c497f0ad7000351e131ef11dadab4a7c700
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/api.py
@@ -0,0 +1,41 @@
+from pandas.core.reshape.concat import concat
+from pandas.core.reshape.encoding import (
+ from_dummies,
+ get_dummies,
+)
+from pandas.core.reshape.melt import (
+ lreshape,
+ melt,
+ wide_to_long,
+)
+from pandas.core.reshape.merge import (
+ merge,
+ merge_asof,
+ merge_ordered,
+)
+from pandas.core.reshape.pivot import (
+ crosstab,
+ pivot,
+ pivot_table,
+)
+from pandas.core.reshape.tile import (
+ cut,
+ qcut,
+)
+
+__all__ = [
+ "concat",
+ "crosstab",
+ "cut",
+ "from_dummies",
+ "get_dummies",
+ "lreshape",
+ "melt",
+ "merge",
+ "merge_asof",
+ "merge_ordered",
+ "pivot",
+ "pivot_table",
+ "qcut",
+ "wide_to_long",
+]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/concat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/concat.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fffddf4195ba84d7c6b0dbb614730a1885d8c35
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/concat.py
@@ -0,0 +1,998 @@
+"""
+Concat routines.
+"""
+
+from __future__ import annotations
+
+from collections import abc
+from itertools import pairwise
+import types
+from typing import (
+ TYPE_CHECKING,
+ Literal,
+ cast,
+ overload,
+)
+import warnings
+
+import numpy as np
+
+from pandas._libs import lib
+from pandas.errors import Pandas4Warning
+from pandas.util._decorators import set_module
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core.dtypes.common import (
+ is_bool,
+ is_scalar,
+)
+from pandas.core.dtypes.concat import concat_compat
+from pandas.core.dtypes.generic import (
+ ABCDataFrame,
+ ABCSeries,
+)
+from pandas.core.dtypes.missing import isna
+
+from pandas.core.arrays.categorical import (
+ factorize_from_iterable,
+ factorize_from_iterables,
+)
+import pandas.core.common as com
+from pandas.core.indexes.api import (
+ Index,
+ MultiIndex,
+ all_indexes_same,
+ default_index,
+ ensure_index,
+ get_objs_combined_axis,
+ get_unanimous_names,
+ union_indexes,
+)
+from pandas.core.indexes.datetimes import DatetimeIndex
+from pandas.core.internals import concatenate_managers
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Callable,
+ Hashable,
+ Iterable,
+ Mapping,
+ )
+
+ from pandas._typing import (
+ Axis,
+ AxisInt,
+ HashableT,
+ )
+
+ from pandas import (
+ DataFrame,
+ Series,
+ )
+
+# ---------------------------------------------------------------------
+# Concatenate DataFrame objects
+
+
+@overload
+def concat(
+ objs: Iterable[DataFrame] | Mapping[HashableT, DataFrame],
+ *,
+ axis: Literal[0, "index"] = ...,
+ join: str = ...,
+ ignore_index: bool = ...,
+ keys: Iterable[Hashable] | None = ...,
+ levels=...,
+ names: list[HashableT] | None = ...,
+ verify_integrity: bool = ...,
+ sort: bool = ...,
+ copy: bool | lib.NoDefault = ...,
+) -> DataFrame: ...
+
+
+@overload
+def concat(
+ objs: Iterable[Series] | Mapping[HashableT, Series],
+ *,
+ axis: Literal[0, "index"] = ...,
+ join: str = ...,
+ ignore_index: bool = ...,
+ keys: Iterable[Hashable] | None = ...,
+ levels=...,
+ names: list[HashableT] | None = ...,
+ verify_integrity: bool = ...,
+ sort: bool = ...,
+ copy: bool | lib.NoDefault = ...,
+) -> Series: ...
+
+
+@overload
+def concat(
+ objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
+ *,
+ axis: Literal[0, "index"] = ...,
+ join: str = ...,
+ ignore_index: bool = ...,
+ keys: Iterable[Hashable] | None = ...,
+ levels=...,
+ names: list[HashableT] | None = ...,
+ verify_integrity: bool = ...,
+ sort: bool = ...,
+ copy: bool | lib.NoDefault = ...,
+) -> DataFrame | Series: ...
+
+
+@overload
+def concat(
+ objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
+ *,
+ axis: Literal[1, "columns"],
+ join: str = ...,
+ ignore_index: bool = ...,
+ keys: Iterable[Hashable] | None = ...,
+ levels=...,
+ names: list[HashableT] | None = ...,
+ verify_integrity: bool = ...,
+ sort: bool = ...,
+ copy: bool | lib.NoDefault = ...,
+) -> DataFrame: ...
+
+
+@overload
+def concat(
+ objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
+ *,
+ axis: Axis = ...,
+ join: str = ...,
+ ignore_index: bool = ...,
+ keys: Iterable[Hashable] | None = ...,
+ levels=...,
+ names: list[HashableT] | None = ...,
+ verify_integrity: bool = ...,
+ sort: bool = ...,
+ copy: bool | lib.NoDefault = ...,
+) -> DataFrame | Series: ...
+
+
+@set_module("pandas")
+def concat(
+ objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
+ *,
+ axis: Axis = 0,
+ join: str = "outer",
+ ignore_index: bool = False,
+ keys: Iterable[Hashable] | None = None,
+ levels=None,
+ names: list[HashableT] | None = None,
+ verify_integrity: bool = False,
+ sort: bool | lib.NoDefault = lib.no_default,
+ copy: bool | lib.NoDefault = lib.no_default,
+) -> DataFrame | Series:
+ """
+ Concatenate pandas objects along a particular axis.
+
+ Allows optional set logic along the other axes.
+
+ Can also add a layer of hierarchical indexing on the concatenation axis,
+ which may be useful if the labels are the same (or overlapping) on
+ the passed axis number.
+
+ Parameters
+ ----------
+ objs : an iterable or mapping of Series or DataFrame objects
+ If a mapping is passed, the keys will be used as the `keys`
+ argument, unless it is passed, in which case the values will be
+ selected (see below). Any None objects will be dropped silently unless
+ they are all None in which case a ValueError will be raised.
+ axis : {0/'index', 1/'columns'}, default 0
+ The axis to concatenate along.
+ join : {'inner', 'outer'}, default 'outer'
+ How to handle indexes on other axis (or axes).
+ ignore_index : bool, default False
+ If True, do not use the index values along the concatenation axis. The
+ resulting axis will be labeled 0, ..., n - 1. This is useful if you are
+ concatenating objects where the concatenation axis does not have
+ meaningful indexing information. Note the index values on the other
+ axes are still respected in the join.
+ keys : sequence, default None
+ If multiple levels passed, should contain tuples. Construct
+ hierarchical index using the passed keys as the outermost level.
+ levels : list of sequences, default None
+ Specific levels (unique values) to use for constructing a
+ MultiIndex. Otherwise they will be inferred from the keys.
+ names : list, default None
+ Names for the levels in the resulting hierarchical index.
+ verify_integrity : bool, default False
+ Check whether the new concatenated axis contains duplicates. This can
+ be very expensive relative to the actual data concatenation.
+ sort : bool, default False
+ Sort non-concatenation axis. One exception to this is when the
+ non-concatenation axis is a DatetimeIndex and join='outer' and the axis is
+ not already aligned. In that case, the non-concatenation axis is always
+ sorted lexicographically.
+ copy : bool, default False
+ This keyword is now ignored; changing its value will have no
+ impact on the method.
+
+ .. deprecated:: 3.0.0
+
+ This keyword is ignored and will be removed in pandas 4.0. Since
+ pandas 3.0, this method always returns a new object using a lazy
+ copy mechanism that defers copies until necessary
+ (Copy-on-Write). See the `user guide on Copy-on-Write
+ `__
+ for more details.
+
+ Returns
+ -------
+ object, type of objs
+ When concatenating all ``Series`` along the index (axis=0), a
+ ``Series`` is returned. When ``objs`` contains at least one
+ ``DataFrame``, a ``DataFrame`` is returned. When concatenating along
+ the columns (axis=1), a ``DataFrame`` is returned.
+
+ See Also
+ --------
+ DataFrame.join : Join DataFrames using indexes.
+ DataFrame.merge : Merge DataFrames by indexes or columns.
+
+ Notes
+ -----
+ The keys, levels, and names arguments are all optional.
+
+ A walkthrough of how this method fits in with other tools for combining
+ pandas objects can be found `here
+ `__.
+
+ It is not recommended to build DataFrames by adding single rows in a
+ for loop. Build a list of rows and make a DataFrame in a single concat.
+
+ Examples
+ --------
+ Combine two ``Series``.
+
+ >>> s1 = pd.Series(["a", "b"])
+ >>> s2 = pd.Series(["c", "d"])
+ >>> pd.concat([s1, s2])
+ 0 a
+ 1 b
+ 0 c
+ 1 d
+ dtype: str
+
+ Clear the existing index and reset it in the result
+ by setting the ``ignore_index`` option to ``True``.
+
+ >>> pd.concat([s1, s2], ignore_index=True)
+ 0 a
+ 1 b
+ 2 c
+ 3 d
+ dtype: str
+
+ Add a hierarchical index at the outermost level of
+ the data with the ``keys`` option.
+
+ >>> pd.concat([s1, s2], keys=["s1", "s2"])
+ s1 0 a
+ 1 b
+ s2 0 c
+ 1 d
+ dtype: str
+
+ Label the index keys you create with the ``names`` option.
+
+ >>> pd.concat([s1, s2], keys=["s1", "s2"], names=["Series name", "Row ID"])
+ Series name Row ID
+ s1 0 a
+ 1 b
+ s2 0 c
+ 1 d
+ dtype: str
+
+ Combine two ``DataFrame`` objects with identical columns.
+
+ >>> df1 = pd.DataFrame([["a", 1], ["b", 2]], columns=["letter", "number"])
+ >>> df1
+ letter number
+ 0 a 1
+ 1 b 2
+ >>> df2 = pd.DataFrame([["c", 3], ["d", 4]], columns=["letter", "number"])
+ >>> df2
+ letter number
+ 0 c 3
+ 1 d 4
+ >>> pd.concat([df1, df2])
+ letter number
+ 0 a 1
+ 1 b 2
+ 0 c 3
+ 1 d 4
+
+ Combine ``DataFrame`` objects with overlapping columns
+ and return everything. Columns outside the intersection will
+ be filled with ``NaN`` values.
+
+ >>> df3 = pd.DataFrame(
+ ... [["c", 3, "cat"], ["d", 4, "dog"]], columns=["letter", "number", "animal"]
+ ... )
+ >>> df3
+ letter number animal
+ 0 c 3 cat
+ 1 d 4 dog
+ >>> pd.concat([df1, df3], sort=False)
+ letter number animal
+ 0 a 1 NaN
+ 1 b 2 NaN
+ 0 c 3 cat
+ 1 d 4 dog
+
+ Combine ``DataFrame`` objects with overlapping columns
+ and return only those that are shared by passing ``inner`` to
+ the ``join`` keyword argument.
+
+ >>> pd.concat([df1, df3], join="inner")
+ letter number
+ 0 a 1
+ 1 b 2
+ 0 c 3
+ 1 d 4
+
+ Combine ``DataFrame`` objects horizontally along the x axis by
+ passing in ``axis=1``.
+
+ >>> df4 = pd.DataFrame(
+ ... [["bird", "polly"], ["monkey", "george"]], columns=["animal", "name"]
+ ... )
+ >>> pd.concat([df1, df4], axis=1)
+ letter number animal name
+ 0 a 1 bird polly
+ 1 b 2 monkey george
+
+ Prevent the result from including duplicate index values with the
+ ``verify_integrity`` option.
+
+ >>> df5 = pd.DataFrame([1], index=["a"])
+ >>> df5
+ 0
+ a 1
+ >>> df6 = pd.DataFrame([2], index=["a"])
+ >>> df6
+ 0
+ a 2
+ >>> pd.concat([df5, df6], verify_integrity=True)
+ Traceback (most recent call last):
+ ...
+ ValueError: Indexes have overlapping values: ['a']
+
+ Append a single row to the end of a ``DataFrame`` object.
+
+ >>> df7 = pd.DataFrame({"a": 1, "b": 2}, index=[0])
+ >>> df7
+ a b
+ 0 1 2
+ >>> new_row = pd.Series({"a": 3, "b": 4})
+ >>> new_row
+ a 3
+ b 4
+ dtype: int64
+ >>> pd.concat([df7, new_row.to_frame().T], ignore_index=True)
+ a b
+ 0 1 2
+ 1 3 4
+ """
+ if ignore_index and keys is not None:
+ raise ValueError(
+ f"Cannot set {ignore_index=} and specify keys. Either should be used."
+ )
+
+ if copy is not lib.no_default:
+ warnings.warn(
+ "The copy keyword is deprecated and will be removed in a future "
+ "version. Copy-on-Write is active in pandas since 3.0 which utilizes "
+ "a lazy copy mechanism that defers copies until necessary. Use "
+ ".copy() to make an eager copy if necessary.",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+ if join == "outer":
+ intersect = False
+ elif join == "inner":
+ intersect = True
+ else: # pragma: no cover
+ raise ValueError(
+ "Only can inner (intersect) or outer (union) join the other axis"
+ )
+
+ objs, keys, ndims = _clean_keys_and_objs(objs, keys)
+
+ if sort is lib.no_default:
+ if axis == 0:
+ non_concat_axis = [
+ obj.columns if isinstance(obj, ABCDataFrame) else Index([obj.name])
+ for obj in objs
+ ]
+ else:
+ non_concat_axis = [obj.index for obj in objs]
+
+ if (
+ intersect
+ or any(not isinstance(index, DatetimeIndex) for index in non_concat_axis)
+ or all(prev is curr for prev, curr in pairwise(non_concat_axis))
+ or (
+ all(
+ prev[-1] <= curr[0] and prev.is_monotonic_increasing
+ for prev, curr in pairwise(non_concat_axis)
+ if not prev.empty and not curr.empty
+ )
+ and non_concat_axis[-1].is_monotonic_increasing
+ )
+ ):
+ # Sorting or not will not impact the result.
+ sort = False
+ elif not is_bool(sort):
+ raise ValueError(
+ f"The 'sort' keyword only accepts boolean values; {sort} was passed."
+ )
+ else:
+ sort = bool(sort)
+
+ # select an object to be our result reference
+ sample, objs = _get_sample_object(objs, ndims, keys, names, levels, intersect)
+
+ # Standardize axis parameter to int
+ if sample.ndim == 1:
+ from pandas import DataFrame
+
+ bm_axis = DataFrame._get_axis_number(axis)
+ is_frame = False
+ is_series = True
+ else:
+ bm_axis = sample._get_axis_number(axis)
+ is_frame = True
+ is_series = False
+
+ # Need to flip BlockManager axis in the DataFrame special case
+ bm_axis = sample._get_block_manager_axis(bm_axis)
+
+ # if we have mixed ndims, then convert to highest ndim
+ # creating column numbers as needed
+ if len(ndims) > 1:
+ objs = _sanitize_mixed_ndim(objs, sample, ignore_index, bm_axis)
+
+ orig_axis = axis
+ axis = 1 - bm_axis if is_frame else 0
+ names = names or getattr(keys, "names", None)
+ result = _get_result(
+ objs,
+ is_series,
+ bm_axis,
+ ignore_index,
+ intersect,
+ sort,
+ keys,
+ levels,
+ verify_integrity,
+ names,
+ axis,
+ )
+
+ if sort is lib.no_default:
+ if orig_axis == 0:
+ non_concat_axis = [
+ obj.columns if isinstance(obj, ABCDataFrame) else Index([obj.name])
+ for obj in objs
+ ]
+ else:
+ non_concat_axis = [obj.index for obj in objs]
+ no_sort_result_index = union_indexes(non_concat_axis, sort=False)
+ orig = result.index if orig_axis == 1 else result.columns
+ if not no_sort_result_index.equals(orig):
+ msg = (
+ "Sorting by default when concatenating all DatetimeIndex is "
+ "deprecated. In the future, pandas will respect the default "
+ "of `sort=False`. Specify `sort=True` or `sort=False` to "
+ "silence this message. If you see this warnings when not "
+ "directly calling concat, report a bug to pandas."
+ )
+ warnings.warn(msg, Pandas4Warning, stacklevel=find_stack_level())
+
+ return result
+
+
+def _sanitize_mixed_ndim(
+ objs: list[Series | DataFrame],
+ sample: Series | DataFrame,
+ ignore_index: bool,
+ axis: AxisInt,
+) -> list[Series | DataFrame]:
+ # if we have mixed ndims, then convert to highest ndim
+ # creating column numbers as needed
+
+ new_objs = []
+
+ current_column = 0
+ max_ndim = sample.ndim
+ for obj in objs:
+ ndim = obj.ndim
+ if ndim == max_ndim:
+ pass
+
+ elif ndim != max_ndim - 1:
+ raise ValueError(
+ "cannot concatenate unaligned mixed dimensional NDFrame objects"
+ )
+
+ else:
+ name = getattr(obj, "name", None)
+ rename_columns = False
+ if ignore_index or name is None:
+ if axis == 1:
+ # doing a row-wise concatenation so need everything
+ # to line up
+ if name is None:
+ name = 0
+ rename_columns = True
+ # doing a column-wise concatenation so need series
+ # to have unique names
+ elif name is None:
+ rename_columns = True
+ name = current_column
+ current_column += 1
+ obj = sample._constructor(obj, copy=False)
+ if isinstance(obj, ABCDataFrame) and rename_columns:
+ obj.columns = range(name, name + 1, 1)
+ else:
+ obj = sample._constructor({name: obj}, copy=False)
+
+ new_objs.append(obj)
+
+ return new_objs
+
+
+def _get_result(
+ objs: list[Series | DataFrame],
+ is_series: bool,
+ bm_axis: AxisInt,
+ ignore_index: bool,
+ intersect: bool,
+ sort: bool | lib.NoDefault,
+ keys: Iterable[Hashable] | None,
+ levels,
+ verify_integrity: bool,
+ names: list[HashableT] | None,
+ axis: AxisInt,
+):
+ cons: Callable[..., DataFrame | Series]
+ sample: DataFrame | Series
+
+ # series only
+ if is_series:
+ sample = cast("Series", objs[0])
+
+ # stack blocks
+ if bm_axis == 0:
+ name = com.consensus_name_attr(objs)
+ cons = sample._constructor
+
+ arrs = [ser._values for ser in objs]
+
+ res = concat_compat(arrs, axis=0)
+
+ if ignore_index:
+ new_index: Index = default_index(len(res))
+ else:
+ new_index = _get_concat_axis_series(
+ objs,
+ ignore_index,
+ bm_axis,
+ keys,
+ levels,
+ verify_integrity,
+ names,
+ )
+
+ mgr = type(sample._mgr).from_array(res, index=new_index)
+
+ result = sample._constructor_from_mgr(mgr, axes=mgr.axes)
+ result._name = name
+ return result.__finalize__(
+ types.SimpleNamespace(input_objs=objs, objs=objs), method="concat"
+ )
+
+ # combine as columns in a frame
+ else:
+ data = dict(enumerate(objs))
+
+ # GH28330 Preserves subclassed objects through concat
+ cons = sample._constructor_expanddim
+
+ index = get_objs_combined_axis(
+ objs,
+ axis=objs[0]._get_block_manager_axis(0),
+ intersect=intersect,
+ sort=sort,
+ )
+ columns = _get_concat_axis_series(
+ objs, ignore_index, bm_axis, keys, levels, verify_integrity, names
+ )
+ df = cons(data, index=index, copy=False)
+ df.columns = columns
+ return df.__finalize__(
+ types.SimpleNamespace(input_objs=objs, objs=objs), method="concat"
+ )
+
+ # combine block managers
+ else:
+ sample = cast("DataFrame", objs[0])
+
+ mgrs_indexers = []
+ result_axes = new_axes(
+ objs,
+ bm_axis,
+ intersect,
+ sort,
+ keys,
+ names,
+ axis,
+ levels,
+ verify_integrity,
+ ignore_index,
+ )
+ for obj in objs:
+ indexers = {}
+ for ax, new_labels in enumerate(result_axes):
+ # ::-1 to convert BlockManager ax to DataFrame ax
+ if ax == bm_axis:
+ # Suppress reindexing on concat axis
+ continue
+
+ # 1-ax to convert BlockManager axis to DataFrame axis
+ obj_labels = obj.axes[1 - ax]
+ if not new_labels.equals(obj_labels):
+ indexers[ax] = obj_labels.get_indexer(new_labels)
+
+ mgrs_indexers.append((obj._mgr, indexers))
+
+ new_data = concatenate_managers(
+ mgrs_indexers, result_axes, concat_axis=bm_axis, copy=False
+ )
+
+ out = sample._constructor_from_mgr(new_data, axes=new_data.axes)
+ return out.__finalize__(
+ types.SimpleNamespace(input_objs=objs, objs=objs), method="concat"
+ )
+
+
+def new_axes(
+ objs: list[Series | DataFrame],
+ bm_axis: AxisInt,
+ intersect: bool,
+ sort: bool | lib.NoDefault,
+ keys: Iterable[Hashable] | None,
+ names: list[HashableT] | None,
+ axis: AxisInt,
+ levels,
+ verify_integrity: bool,
+ ignore_index: bool,
+) -> list[Index]:
+ """Return the new [index, column] result for concat."""
+ return [
+ _get_concat_axis_dataframe(
+ objs,
+ axis,
+ ignore_index,
+ keys,
+ names,
+ levels,
+ verify_integrity,
+ )
+ if i == bm_axis
+ else get_objs_combined_axis(
+ objs,
+ axis=objs[0]._get_block_manager_axis(i),
+ intersect=intersect,
+ sort=sort,
+ )
+ for i in range(2)
+ ]
+
+
+def _get_concat_axis_series(
+ objs: list[Series | DataFrame],
+ ignore_index: bool,
+ bm_axis: AxisInt,
+ keys: Iterable[Hashable] | None,
+ levels,
+ verify_integrity: bool,
+ names: list[HashableT] | None,
+) -> Index:
+ """Return result concat axis when concatenating Series objects."""
+ if ignore_index:
+ return default_index(len(objs))
+ elif bm_axis == 0:
+ indexes = [x.index for x in objs]
+ if keys is None:
+ if levels is not None:
+ raise ValueError("levels supported only when keys is not None")
+ concat_axis = _concat_indexes(indexes)
+ else:
+ concat_axis = _make_concat_multiindex(indexes, keys, levels, names)
+ if verify_integrity and not concat_axis.is_unique:
+ overlap = concat_axis[concat_axis.duplicated()].unique()
+ raise ValueError(f"Indexes have overlapping values: {overlap}")
+ return concat_axis
+ elif keys is None:
+ result_names: list[Hashable] = [None] * len(objs)
+ num = 0
+ has_names = False
+ for i, x in enumerate(objs):
+ if x.ndim != 1:
+ raise TypeError(
+ f"Cannot concatenate type 'Series' with "
+ f"object of type '{type(x).__name__}'"
+ )
+ if x.name is not None:
+ result_names[i] = x.name
+ has_names = True
+ else:
+ result_names[i] = num
+ num += 1
+ if has_names:
+ return Index(result_names)
+ else:
+ return default_index(len(objs))
+ else:
+ return ensure_index(keys).set_names(names) # type: ignore[arg-type]
+
+
+def _get_concat_axis_dataframe(
+ objs: list[Series | DataFrame],
+ axis: AxisInt,
+ ignore_index: bool,
+ keys: Iterable[Hashable] | None,
+ names: list[HashableT] | None,
+ levels,
+ verify_integrity: bool,
+) -> Index:
+ """Return result concat axis when concatenating DataFrame objects."""
+ indexes_gen = (x.axes[axis] for x in objs)
+
+ if ignore_index:
+ return default_index(sum(len(i) for i in indexes_gen))
+ else:
+ indexes = list(indexes_gen)
+
+ if keys is None:
+ if levels is not None:
+ raise ValueError("levels supported only when keys is not None")
+ concat_axis = _concat_indexes(indexes)
+ else:
+ concat_axis = _make_concat_multiindex(indexes, keys, levels, names)
+
+ if verify_integrity and not concat_axis.is_unique:
+ overlap = concat_axis[concat_axis.duplicated()].unique()
+ raise ValueError(f"Indexes have overlapping values: {overlap}")
+
+ return concat_axis
+
+
+def _clean_keys_and_objs(
+ objs: Iterable[Series | DataFrame] | Mapping[HashableT, Series | DataFrame],
+ keys,
+) -> tuple[list[Series | DataFrame], Index | None, set[int]]:
+ """
+ Returns
+ -------
+ clean_objs : list[Series | DataFrame]
+ List of DataFrame and Series with Nones removed.
+ keys : Index | None
+ None if keys was None
+ Index if objs was a Mapping or keys was not None. Filtered where objs was None.
+ ndim : set[int]
+ Unique .ndim attribute of obj encountered.
+ """
+ if isinstance(objs, abc.Mapping):
+ if keys is None:
+ keys = objs.keys()
+ objs = [objs[k] for k in keys]
+ elif isinstance(objs, (ABCSeries, ABCDataFrame)) or is_scalar(objs):
+ raise TypeError(
+ "first argument must be an iterable of pandas "
+ f'objects, you passed an object of type "{type(objs).__name__}"'
+ )
+ elif not isinstance(objs, abc.Sized):
+ objs = list(objs)
+
+ if len(objs) == 0:
+ raise ValueError("No objects to concatenate")
+
+ if keys is not None:
+ if not isinstance(keys, Index):
+ keys = Index(keys)
+ if len(keys) != len(objs):
+ # GH#43485
+ raise ValueError(
+ f"The length of the keys ({len(keys)}) must match "
+ f"the length of the objects to concatenate ({len(objs)})"
+ )
+
+ # GH#1649
+ key_indices = []
+ clean_objs = []
+ ndims = set()
+ for i, obj in enumerate(objs):
+ if obj is None:
+ continue
+ elif isinstance(obj, (ABCSeries, ABCDataFrame)):
+ key_indices.append(i)
+ clean_objs.append(obj)
+ ndims.add(obj.ndim)
+ else:
+ msg = (
+ f"cannot concatenate object of type '{type(obj)}'; "
+ "only Series and DataFrame objs are valid"
+ )
+ raise TypeError(msg)
+
+ if keys is not None and len(key_indices) < len(keys):
+ keys = keys.take(key_indices)
+
+ if len(clean_objs) == 0:
+ raise ValueError("All objects passed were None")
+
+ return clean_objs, keys, ndims
+
+
+def _get_sample_object(
+ objs: list[Series | DataFrame],
+ ndims: set[int],
+ keys,
+ names,
+ levels,
+ intersect: bool,
+) -> tuple[Series | DataFrame, list[Series | DataFrame]]:
+ # get the sample
+ # want the highest ndim that we have, and must be non-empty
+ # unless all objs are empty
+ if len(ndims) > 1:
+ max_ndim = max(ndims)
+ for obj in objs:
+ if obj.ndim == max_ndim and sum(obj.shape): # type: ignore[arg-type]
+ return obj, objs
+ elif keys is None and names is None and levels is None and not intersect:
+ # filter out the empties if we have not multi-index possibilities
+ # note to keep empty Series as it affect to result columns / name
+ if ndims.pop() == 2:
+ non_empties = [obj for obj in objs if sum(obj.shape)]
+ else:
+ non_empties = objs
+
+ if len(non_empties):
+ return non_empties[0], non_empties
+
+ return objs[0], objs
+
+
+def _concat_indexes(indexes) -> Index:
+ return indexes[0].append(indexes[1:])
+
+
+def validate_unique_levels(levels: list[Index]) -> None:
+ for level in levels:
+ if not level.is_unique:
+ raise ValueError(f"Level values not unique: {level.tolist()}")
+
+
+def _make_concat_multiindex(indexes, keys, levels=None, names=None) -> MultiIndex:
+ if (levels is None and isinstance(keys[0], tuple)) or (
+ levels is not None and len(levels) > 1
+ ):
+ zipped = list(zip(*keys, strict=True))
+ if names is None:
+ names = [None] * len(zipped)
+
+ if levels is None:
+ _, levels = factorize_from_iterables(zipped)
+ else:
+ levels = [ensure_index(x) for x in levels]
+ validate_unique_levels(levels)
+ else:
+ zipped = [keys]
+ if names is None:
+ names = [None]
+
+ if levels is None:
+ levels = [ensure_index(keys).unique()]
+ else:
+ levels = [ensure_index(x) for x in levels]
+ validate_unique_levels(levels)
+
+ if not all_indexes_same(indexes):
+ codes_list = []
+
+ # things are potentially different sizes, so compute the exact codes
+ # for each level and pass those to MultiIndex.from_arrays
+
+ for hlevel, level in zip(zipped, levels, strict=True):
+ to_concat = []
+ if isinstance(hlevel, Index) and hlevel.equals(level):
+ lens = [len(idx) for idx in indexes]
+ codes_list.append(np.repeat(np.arange(len(hlevel)), lens))
+ else:
+ for key, index in zip(hlevel, indexes, strict=True):
+ # Find matching codes, include matching nan values as equal.
+ mask = (isna(level) & isna(key)) | (level == key)
+ if not mask.any():
+ raise ValueError(f"Key {key} not in level {level}")
+ i = np.nonzero(mask)[0][0]
+
+ to_concat.append(np.repeat(i, len(index)))
+ codes_list.append(np.concatenate(to_concat))
+
+ concat_index = _concat_indexes(indexes)
+
+ # these go at the end
+ if isinstance(concat_index, MultiIndex):
+ levels.extend(concat_index.levels)
+ codes_list.extend(concat_index.codes)
+ else:
+ codes, categories = factorize_from_iterable(concat_index)
+ levels.append(categories)
+ codes_list.append(codes)
+
+ if len(names) == len(levels):
+ names = list(names)
+ else:
+ # make sure that all of the passed indices have the same nlevels
+ if not len({idx.nlevels for idx in indexes}) == 1:
+ raise AssertionError(
+ "Cannot concat indices that do not have the same number of levels"
+ )
+
+ # also copies
+ names = list(names) + list(get_unanimous_names(*indexes))
+
+ return MultiIndex(
+ levels=levels, codes=codes_list, names=names, verify_integrity=False
+ )
+
+ new_index = indexes[0]
+ n = len(new_index)
+ kpieces = len(indexes)
+
+ # also copies
+ new_names = list(names)
+ new_levels = list(levels)
+
+ # construct codes
+ new_codes = []
+
+ # do something a bit more speedy
+
+ for hlevel, level in zip(zipped, levels, strict=True):
+ hlevel_index = ensure_index(hlevel)
+ mapped = level.get_indexer(hlevel_index)
+
+ mask = mapped == -1
+ if mask.any():
+ raise ValueError(
+ f"Values not found in passed level: {hlevel_index[mask]!s}"
+ )
+
+ new_codes.append(np.repeat(mapped, n))
+
+ if isinstance(new_index, MultiIndex):
+ new_levels.extend(new_index.levels)
+ new_codes.extend(np.tile(lab, kpieces) for lab in new_index.codes)
+ else:
+ new_levels.append(new_index.unique())
+ single_codes = new_index.unique().get_indexer(new_index)
+ new_codes.append(np.tile(single_codes, kpieces))
+
+ if len(new_names) < len(new_levels):
+ new_names.extend(new_index.names)
+
+ return MultiIndex(
+ levels=new_levels, codes=new_codes, names=new_names, verify_integrity=False
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/encoding.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/encoding.py
new file mode 100644
index 0000000000000000000000000000000000000000..35fed5a24fb30208a8f2c06100550de88935b3e4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/encoding.py
@@ -0,0 +1,589 @@
+from __future__ import annotations
+
+from collections import defaultdict
+from collections.abc import (
+ Hashable,
+ Iterable,
+)
+import itertools
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from pandas._libs import missing as libmissing
+from pandas._libs.sparse import IntIndex
+from pandas.util._decorators import set_module
+
+from pandas.core.dtypes.common import (
+ is_integer_dtype,
+ is_list_like,
+ is_object_dtype,
+ pandas_dtype,
+)
+from pandas.core.dtypes.dtypes import (
+ ArrowDtype,
+ CategoricalDtype,
+)
+
+from pandas.core.arrays import SparseArray
+from pandas.core.arrays.categorical import factorize_from_iterable
+from pandas.core.arrays.string_ import StringDtype
+from pandas.core.frame import DataFrame
+from pandas.core.indexes.api import (
+ Index,
+ default_index,
+)
+from pandas.core.series import Series
+
+if TYPE_CHECKING:
+ from pandas._typing import NpDtype
+
+
+@set_module("pandas")
+def get_dummies(
+ data,
+ prefix=None,
+ prefix_sep: str | Iterable[str] | dict[str, str] = "_",
+ dummy_na: bool = False,
+ columns=None,
+ sparse: bool = False,
+ drop_first: bool = False,
+ dtype: NpDtype | None = None,
+) -> DataFrame:
+ """
+ Convert categorical variable into dummy/indicator variables.
+
+ Each variable is converted in as many 0/1 variables as there are different
+ values. Columns in the output are each named after a value; if the input is
+ a DataFrame, the name of the original variable is prepended to the value.
+
+ Parameters
+ ----------
+ data : array-like, Series, or DataFrame
+ Data of which to get dummy indicators.
+ prefix : str, list of str, or dict of str, default None
+ A string to be prepended to DataFrame column names.
+ Pass a list with length equal to the number of columns
+ when calling get_dummies on a DataFrame. Alternatively, `prefix`
+ can be a dictionary mapping column names to prefixes.
+ prefix_sep : str, list of str, or dict of str, default '_'
+ Should you choose to prepend DataFrame column names with a prefix, this
+ is the separator/delimiter to use between the two. Alternatively,
+ `prefix_sep` can be a list with length equal to the number of columns,
+ or a dictionary mapping column names to separators.
+ dummy_na : bool, default False
+ If True, a NaN indicator column will be added even if no NaN values are present.
+ If False, NA values are encoded as all zero.
+ columns : list-like, default None
+ Column names in the DataFrame to be encoded.
+ If `columns` is None then all the columns with
+ `object`, `string`, or `category` dtype will be converted.
+ sparse : bool, default False
+ Whether the dummy-encoded columns should be backed by
+ a :class:`SparseArray` (True) or a regular NumPy array (False).
+ drop_first : bool, default False
+ Whether to get k-1 dummies out of k categorical levels by removing the
+ first level.
+ dtype : dtype, default bool
+ Data type for new columns. Only a single dtype is allowed.
+
+ Returns
+ -------
+ DataFrame
+ Dummy-coded data. If `data` contains other columns than the
+ dummy-coded one(s), these will be prepended, unaltered, to the result.
+
+ See Also
+ --------
+ Series.str.get_dummies : Convert Series of strings to dummy codes.
+ :func:`~pandas.from_dummies` : Convert dummy codes to categorical ``DataFrame``.
+
+ Notes
+ -----
+ Reference :ref:`the user guide ` for more examples.
+
+ Examples
+ --------
+ >>> s = pd.Series(list("abca"))
+
+ >>> pd.get_dummies(s)
+ a b c
+ 0 True False False
+ 1 False True False
+ 2 False False True
+ 3 True False False
+
+ >>> s1 = ["a", "b", np.nan]
+
+ >>> pd.get_dummies(s1)
+ a b
+ 0 True False
+ 1 False True
+ 2 False False
+
+ >>> pd.get_dummies(s1, dummy_na=True)
+ a b NaN
+ 0 True False False
+ 1 False True False
+ 2 False False True
+
+ >>> df = pd.DataFrame({"A": ["a", "b", "a"], "B": ["b", "a", "c"], "C": [1, 2, 3]})
+
+ >>> pd.get_dummies(df, prefix=["col1", "col2"])
+ C col1_a col1_b col2_a col2_b col2_c
+ 0 1 True False False True False
+ 1 2 False True True False False
+ 2 3 True False False False True
+
+ >>> pd.get_dummies(pd.Series(list("abcaa")))
+ a b c
+ 0 True False False
+ 1 False True False
+ 2 False False True
+ 3 True False False
+ 4 True False False
+
+ >>> pd.get_dummies(pd.Series(list("abcaa")), drop_first=True)
+ b c
+ 0 False False
+ 1 True False
+ 2 False True
+ 3 False False
+ 4 False False
+
+ >>> pd.get_dummies(pd.Series(list("abc")), dtype=float)
+ a b c
+ 0 1.0 0.0 0.0
+ 1 0.0 1.0 0.0
+ 2 0.0 0.0 1.0
+ """
+ from pandas.core.reshape.concat import concat
+
+ dtypes_to_encode = ["object", "string", "category"]
+
+ if isinstance(data, DataFrame):
+ # determine columns being encoded
+ if columns is None:
+ data_to_encode = data.select_dtypes(include=dtypes_to_encode)
+ elif not is_list_like(columns):
+ raise TypeError("Input must be a list-like for parameter `columns`")
+ else:
+ data_to_encode = data[columns]
+
+ # validate prefixes and separator to avoid silently dropping cols
+ def check_len(item, name: str) -> None:
+ if is_list_like(item):
+ if not len(item) == data_to_encode.shape[1]:
+ len_msg = (
+ f"Length of '{name}' ({len(item)}) did not match the "
+ "length of the columns being encoded "
+ f"({data_to_encode.shape[1]})."
+ )
+ raise ValueError(len_msg)
+
+ check_len(prefix, "prefix")
+ check_len(prefix_sep, "prefix_sep")
+
+ if isinstance(prefix, str):
+ prefix = itertools.repeat(prefix, len(data_to_encode.columns))
+ if isinstance(prefix, dict):
+ prefix = [prefix[col] for col in data_to_encode.columns]
+
+ if prefix is None:
+ prefix = data_to_encode.columns
+
+ # validate separators
+ if isinstance(prefix_sep, str):
+ prefix_sep = itertools.repeat(prefix_sep, len(data_to_encode.columns))
+ elif isinstance(prefix_sep, dict):
+ prefix_sep = [prefix_sep[col] for col in data_to_encode.columns]
+
+ with_dummies: list[DataFrame]
+ if data_to_encode.shape == data.shape:
+ # Encoding the entire df, do not prepend any dropped columns
+ with_dummies = []
+ elif columns is not None:
+ # Encoding only cols specified in columns. Get all cols not in
+ # columns to prepend to result.
+ with_dummies = [data.drop(columns, axis=1)]
+ else:
+ # Encoding only object and category dtype columns. Get remaining
+ # columns to prepend to result.
+ with_dummies = [data.select_dtypes(exclude=dtypes_to_encode)]
+
+ for col, pre, sep in zip(
+ data_to_encode.items(), prefix, prefix_sep, strict=True
+ ):
+ # col is (column_name, column), use just column data here
+ dummy = _get_dummies_1d(
+ col[1],
+ prefix=pre,
+ prefix_sep=sep,
+ dummy_na=dummy_na,
+ sparse=sparse,
+ drop_first=drop_first,
+ dtype=dtype,
+ )
+ with_dummies.append(dummy)
+ result = concat(with_dummies, axis=1)
+ else:
+ result = _get_dummies_1d(
+ data,
+ prefix,
+ prefix_sep,
+ dummy_na,
+ sparse=sparse,
+ drop_first=drop_first,
+ dtype=dtype,
+ )
+ return result
+
+
+def _get_dummies_1d(
+ data,
+ prefix,
+ prefix_sep: str | Iterable[str] | dict[str, str] = "_",
+ dummy_na: bool = False,
+ sparse: bool = False,
+ drop_first: bool = False,
+ dtype: NpDtype | None = None,
+) -> DataFrame:
+ from pandas.core.reshape.concat import concat
+
+ # Series avoids inconsistent NaN handling
+ codes, levels = factorize_from_iterable(Series(data, copy=False))
+
+ if dtype is None and hasattr(data, "dtype"):
+ input_dtype = data.dtype
+ if isinstance(input_dtype, CategoricalDtype):
+ input_dtype = input_dtype.categories.dtype
+
+ if isinstance(input_dtype, ArrowDtype):
+ import pyarrow as pa
+
+ dtype = ArrowDtype(pa.bool_()) # type: ignore[assignment]
+ elif (
+ isinstance(input_dtype, StringDtype)
+ and input_dtype.na_value is libmissing.NA
+ ):
+ dtype = pandas_dtype("boolean") # type: ignore[assignment]
+ else:
+ dtype = np.dtype(bool)
+ elif dtype is None:
+ dtype = np.dtype(bool)
+
+ _dtype = pandas_dtype(dtype)
+
+ if is_object_dtype(_dtype):
+ raise ValueError("dtype=object is not a valid dtype for get_dummies")
+
+ def get_empty_frame(data) -> DataFrame:
+ index: Index | np.ndarray
+ if isinstance(data, Series):
+ index = data.index
+ else:
+ index = default_index(len(data))
+ return DataFrame(index=index)
+
+ # if all NaN
+ if not dummy_na and len(levels) == 0:
+ return get_empty_frame(data)
+
+ codes = codes.copy()
+ if dummy_na:
+ codes[codes == -1] = len(levels)
+ levels = levels.insert(len(levels), np.nan)
+
+ # if dummy_na, we just fake a nan level. drop_first will drop it again
+ if drop_first and len(levels) == 1:
+ return get_empty_frame(data)
+
+ number_of_cols = len(levels)
+
+ if prefix is None:
+ dummy_cols = levels
+ else:
+ dummy_cols = Index([f"{prefix}{prefix_sep}{level}" for level in levels])
+
+ index: Index | None
+ if isinstance(data, Series):
+ index = data.index
+ else:
+ index = None
+
+ if sparse:
+ fill_value: bool | float
+ if is_integer_dtype(dtype):
+ fill_value = 0
+ elif dtype == np.dtype(bool):
+ fill_value = False
+ else:
+ fill_value = 0.0
+
+ sparse_series = []
+ N = len(data)
+ sp_indices: list[list] = [[] for _ in range(len(dummy_cols))]
+ mask = codes != -1
+ codes = codes[mask]
+ n_idx = np.arange(N)[mask]
+
+ for ndx, code in zip(n_idx, codes, strict=True):
+ sp_indices[code].append(ndx)
+
+ if drop_first:
+ # remove first categorical level to avoid perfect collinearity
+ # GH12042
+ sp_indices = sp_indices[1:]
+ dummy_cols = dummy_cols[1:]
+ for col, ixs in zip(dummy_cols, sp_indices, strict=True):
+ sarr = SparseArray(
+ np.ones(len(ixs), dtype=dtype),
+ sparse_index=IntIndex(N, ixs),
+ fill_value=fill_value,
+ dtype=dtype,
+ )
+ sparse_series.append(Series(data=sarr, index=index, name=col, copy=False))
+
+ return concat(sparse_series, axis=1)
+
+ else:
+ # ensure ndarray layout is column-major
+ shape = len(codes), number_of_cols
+ dummy_dtype: NpDtype
+ if isinstance(_dtype, np.dtype):
+ dummy_dtype = _dtype
+ else:
+ dummy_dtype = np.bool_
+ dummy_mat = np.zeros(shape=shape, dtype=dummy_dtype, order="F")
+ dummy_mat[np.arange(len(codes)), codes] = 1
+
+ if not dummy_na:
+ # reset NaN GH4446
+ dummy_mat[codes == -1] = 0
+
+ if drop_first:
+ # remove first GH12042
+ dummy_mat = dummy_mat[:, 1:]
+ dummy_cols = dummy_cols[1:]
+ return DataFrame(dummy_mat, index=index, columns=dummy_cols, dtype=_dtype)
+
+
+@set_module("pandas")
+def from_dummies(
+ data: DataFrame,
+ sep: None | str = None,
+ default_category: None | Hashable | dict[str, Hashable] = None,
+) -> DataFrame:
+ """
+ Create a categorical ``DataFrame`` from a ``DataFrame`` of dummy variables.
+
+ Inverts the operation performed by :func:`~pandas.get_dummies`.
+
+ Parameters
+ ----------
+ data : DataFrame
+ Data which contains dummy-coded variables in form of integer columns of
+ 1's and 0's.
+ sep : str, default None
+ Separator used in the column names of the dummy categories they are
+ character indicating the separation of the categorical names from the prefixes.
+ For example, if your column names are 'prefix_A' and 'prefix_B',
+ you can strip the underscore by specifying sep='_'.
+ default_category : None, Hashable or dict of Hashables, default None
+ The default category is the implied category when a value has none of the
+ listed categories specified with a one, i.e. if all dummies in a row are
+ zero. Can be a single value for all variables or a dict directly mapping
+ the default categories to a prefix of a variable. The default category
+ will be coerced to the dtype of ``data.columns`` if such coercion is
+ lossless, and will raise otherwise.
+
+ Returns
+ -------
+ DataFrame
+ Categorical data decoded from the dummy input-data.
+
+ Raises
+ ------
+ ValueError
+ * When the input ``DataFrame`` ``data`` contains NA values.
+ * When the input ``DataFrame`` ``data`` contains column names with separators
+ that do not match the separator specified with ``sep``.
+ * When a ``dict`` passed to ``default_category`` does not include an implied
+ category for each prefix.
+ * When a value in ``data`` has more than one category assigned to it.
+ * When ``default_category=None`` and a value in ``data`` has no category
+ assigned to it.
+ TypeError
+ * When the input ``data`` is not of type ``DataFrame``.
+ * When the input ``DataFrame`` ``data`` contains non-dummy data.
+ * When the passed ``sep`` is of a wrong data type.
+ * When the passed ``default_category`` is of a wrong data type.
+
+ See Also
+ --------
+ :func:`~pandas.get_dummies` : Convert ``Series`` or ``DataFrame`` to dummy codes.
+ :class:`~pandas.Categorical` : Represent a categorical variable in classic.
+
+ Notes
+ -----
+ The columns of the passed dummy data should only include 1's and 0's,
+ or boolean values.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame({"a": [1, 0, 0, 1], "b": [0, 1, 0, 0], "c": [0, 0, 1, 0]})
+
+ >>> df
+ a b c
+ 0 1 0 0
+ 1 0 1 0
+ 2 0 0 1
+ 3 1 0 0
+
+ >>> pd.from_dummies(df)
+ 0 a
+ 1 b
+ 2 c
+ 3 a
+
+ >>> df = pd.DataFrame(
+ ... {
+ ... "col1_a": [1, 0, 1],
+ ... "col1_b": [0, 1, 0],
+ ... "col2_a": [0, 1, 0],
+ ... "col2_b": [1, 0, 0],
+ ... "col2_c": [0, 0, 1],
+ ... }
+ ... )
+
+ >>> df
+ col1_a col1_b col2_a col2_b col2_c
+ 0 1 0 0 1 0
+ 1 0 1 1 0 0
+ 2 1 0 0 0 1
+
+ >>> pd.from_dummies(df, sep="_")
+ col1 col2
+ 0 a b
+ 1 b a
+ 2 a c
+
+ >>> df = pd.DataFrame(
+ ... {
+ ... "col1_a": [1, 0, 0],
+ ... "col1_b": [0, 1, 0],
+ ... "col2_a": [0, 1, 0],
+ ... "col2_b": [1, 0, 0],
+ ... "col2_c": [0, 0, 0],
+ ... }
+ ... )
+
+ >>> df
+ col1_a col1_b col2_a col2_b col2_c
+ 0 1 0 0 1 0
+ 1 0 1 1 0 0
+ 2 0 0 0 0 0
+
+ >>> pd.from_dummies(df, sep="_", default_category={"col1": "d", "col2": "e"})
+ col1 col2
+ 0 a b
+ 1 b a
+ 2 d e
+ """
+ from pandas.core.reshape.concat import concat
+
+ if not isinstance(data, DataFrame):
+ raise TypeError(
+ "Expected 'data' to be a 'DataFrame'; "
+ f"Received 'data' of type: {type(data).__name__}"
+ )
+
+ col_isna_mask = data.isna().any()
+
+ if col_isna_mask.any():
+ raise ValueError(
+ f"Dummy DataFrame contains NA value in column: '{col_isna_mask.idxmax()}'"
+ )
+
+ # index data with a list of all columns that are dummies
+ try:
+ data_to_decode = data.astype("boolean")
+ except TypeError as err:
+ raise TypeError("Passed DataFrame contains non-dummy data") from err
+
+ # collect prefixes and get lists to slice data for each prefix
+ variables_slice = defaultdict(list)
+ if sep is None:
+ variables_slice[""] = list(data.columns)
+ elif isinstance(sep, str):
+ for col in data_to_decode.columns:
+ prefix = col.split(sep)[0]
+ if len(prefix) == len(col):
+ raise ValueError(f"Separator not specified for column: {col}")
+ variables_slice[prefix].append(col)
+ else:
+ raise TypeError(
+ "Expected 'sep' to be of type 'str' or 'None'; "
+ f"Received 'sep' of type: {type(sep).__name__}"
+ )
+
+ if default_category is not None:
+ if isinstance(default_category, dict):
+ if not len(default_category) == len(variables_slice):
+ len_msg = (
+ f"Length of 'default_category' ({len(default_category)}) "
+ f"did not match the length of the columns being encoded "
+ f"({len(variables_slice)})"
+ )
+ raise ValueError(len_msg)
+ elif isinstance(default_category, Hashable):
+ default_category = dict(
+ zip(
+ variables_slice,
+ [default_category] * len(variables_slice),
+ strict=True,
+ )
+ )
+ else:
+ raise TypeError(
+ "Expected 'default_category' to be of type "
+ "'None', 'Hashable', or 'dict'; "
+ "Received 'default_category' of type: "
+ f"{type(default_category).__name__}"
+ )
+
+ cat_data = {}
+ for prefix, prefix_slice in variables_slice.items():
+ if sep is None:
+ cats = prefix_slice.copy()
+ else:
+ cats = [col[len(prefix + sep) :] for col in prefix_slice]
+ assigned = data_to_decode.loc[:, prefix_slice].sum(axis=1)
+ if any(assigned > 1):
+ raise ValueError(
+ "Dummy DataFrame contains multi-assignment(s); "
+ f"First instance in row: {assigned.idxmax()}"
+ )
+ if any(assigned == 0):
+ if isinstance(default_category, dict):
+ cats.append(default_category[prefix])
+ else:
+ raise ValueError(
+ "Dummy DataFrame contains unassigned value(s); "
+ f"First instance in row: {assigned.idxmin()}"
+ )
+ data_slice = concat(
+ (data_to_decode.loc[:, prefix_slice], assigned == 0), axis=1
+ )
+ else:
+ data_slice = data_to_decode.loc[:, prefix_slice]
+ cats_array = data._constructor_sliced(cats, dtype=data.columns.dtype)
+ # get indices of True entries along axis=1
+ true_values = data_slice.idxmax(axis=1)
+ indexer = data_slice.columns.get_indexer_for(true_values)
+ cat_data[prefix] = cats_array.take(indexer).set_axis(data.index)
+
+ result = DataFrame(cat_data)
+ if sep is not None:
+ result.columns = result.columns.astype(data.columns.dtype)
+ return result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/melt.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/melt.py
new file mode 100644
index 0000000000000000000000000000000000000000..8476f4a4b47d181fe922606b579e8db0c89b52f6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/melt.py
@@ -0,0 +1,676 @@
+from __future__ import annotations
+
+import re
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from pandas.util._decorators import set_module
+
+from pandas.core.dtypes.common import (
+ is_iterator,
+ is_list_like,
+)
+from pandas.core.dtypes.concat import concat_compat
+from pandas.core.dtypes.missing import notna
+
+import pandas.core.algorithms as algos
+from pandas.core.indexes.api import MultiIndex
+from pandas.core.reshape.concat import concat
+from pandas.core.tools.numeric import to_numeric
+
+if TYPE_CHECKING:
+ from collections.abc import Hashable
+
+ from pandas._typing import AnyArrayLike
+
+ from pandas import DataFrame
+
+
+def ensure_list_vars(arg_vars, variable: str, columns) -> list:
+ if arg_vars is not None:
+ if not is_list_like(arg_vars):
+ return [arg_vars]
+ elif isinstance(columns, MultiIndex) and not isinstance(arg_vars, list):
+ raise ValueError(
+ f"{variable} must be a list of tuples when columns are a MultiIndex"
+ )
+ else:
+ return list(arg_vars)
+ else:
+ return []
+
+
+@set_module("pandas")
+def melt(
+ frame: DataFrame,
+ id_vars=None,
+ value_vars=None,
+ var_name=None,
+ value_name: Hashable = "value",
+ col_level=None,
+ ignore_index: bool = True,
+) -> DataFrame:
+ """
+ Unpivot a DataFrame from wide to long format, optionally leaving identifiers set.
+
+ This function is useful to reshape a DataFrame into a format where one
+ or more columns are identifier variables (`id_vars`), while all other
+ columns are considered measured variables (`value_vars`), and are "unpivoted" to
+ the row axis, leaving just two non-identifier columns, 'variable' and
+ 'value'.
+
+ Parameters
+ ----------
+ frame : DataFrame
+ The DataFrame to unpivot.
+ id_vars : scalar, tuple, list, or ndarray, optional
+ Column(s) to use as identifier variables.
+ value_vars : scalar, tuple, list, or ndarray, optional
+ Column(s) to unpivot. If not specified, uses all columns that
+ are not set as `id_vars`.
+ var_name : scalar, tuple, list, or ndarray, optional
+ Name to use for the 'variable' column. If None it uses
+ ``frame.columns.name`` or 'variable'. Must be a scalar if columns are a
+ MultiIndex.
+ value_name : scalar, default 'value'
+ Name to use for the 'value' column, can't be an existing column label.
+ col_level : scalar, optional
+ If columns are a MultiIndex then use this level to melt.
+ ignore_index : bool, default True
+ If True, original index is ignored. If False, the original index is retained.
+ Index labels will be repeated as necessary.
+
+ Returns
+ -------
+ DataFrame
+ Unpivoted DataFrame.
+
+ See Also
+ --------
+ DataFrame.melt : Identical method.
+ pivot_table : Create a spreadsheet-style pivot table as a DataFrame.
+ DataFrame.pivot : Return reshaped DataFrame organized
+ by given index / column values.
+ DataFrame.explode : Explode a DataFrame from list-like
+ columns to long format.
+
+ Notes
+ -----
+ Reference :ref:`the user guide ` for more examples.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame(
+ ... {
+ ... "A": {0: "a", 1: "b", 2: "c"},
+ ... "B": {0: 1, 1: 3, 2: 5},
+ ... "C": {0: 2, 1: 4, 2: 6},
+ ... }
+ ... )
+ >>> df
+ A B C
+ 0 a 1 2
+ 1 b 3 4
+ 2 c 5 6
+
+ >>> pd.melt(df, id_vars=["A"], value_vars=["B"])
+ A variable value
+ 0 a B 1
+ 1 b B 3
+ 2 c B 5
+
+ >>> pd.melt(df, id_vars=["A"], value_vars=["B", "C"])
+ A variable value
+ 0 a B 1
+ 1 b B 3
+ 2 c B 5
+ 3 a C 2
+ 4 b C 4
+ 5 c C 6
+
+ The names of 'variable' and 'value' columns can be customized:
+
+ >>> pd.melt(
+ ... df,
+ ... id_vars=["A"],
+ ... value_vars=["B"],
+ ... var_name="myVarname",
+ ... value_name="myValname",
+ ... )
+ A myVarname myValname
+ 0 a B 1
+ 1 b B 3
+ 2 c B 5
+
+ Original index values can be kept around:
+
+ >>> pd.melt(df, id_vars=["A"], value_vars=["B", "C"], ignore_index=False)
+ A variable value
+ 0 a B 1
+ 1 b B 3
+ 2 c B 5
+ 0 a C 2
+ 1 b C 4
+ 2 c C 6
+
+ If you have multi-index columns:
+
+ >>> df.columns = [list("ABC"), list("DEF")]
+ >>> df
+ A B C
+ D E F
+ 0 a 1 2
+ 1 b 3 4
+ 2 c 5 6
+
+ >>> pd.melt(df, col_level=0, id_vars=["A"], value_vars=["B"])
+ A variable value
+ 0 a B 1
+ 1 b B 3
+ 2 c B 5
+
+ >>> pd.melt(df, id_vars=[("A", "D")], value_vars=[("B", "E")])
+ (A, D) variable_0 variable_1 value
+ 0 a B E 1
+ 1 b B E 3
+ 2 c B E 5
+ """
+ if value_name in frame.columns:
+ raise ValueError(
+ f"value_name ({value_name}) cannot match an element in "
+ "the DataFrame columns."
+ )
+ id_vars = ensure_list_vars(id_vars, "id_vars", frame.columns)
+ value_vars_was_not_none = value_vars is not None
+ value_vars = ensure_list_vars(value_vars, "value_vars", frame.columns)
+
+ # GH61475 - prevent AttributeError when duplicate column in id_vars
+ if len(frame.columns.get_indexer_for(id_vars)) > len(id_vars):
+ raise ValueError("id_vars cannot contain duplicate columns.")
+
+ if id_vars or value_vars:
+ if col_level is not None:
+ level = frame.columns.get_level_values(col_level)
+ else:
+ level = frame.columns
+ labels = id_vars + value_vars
+ idx = level.get_indexer_for(labels)
+ missing = idx == -1
+ if missing.any():
+ missing_labels = [
+ lab for lab, not_found in zip(labels, missing, strict=True) if not_found
+ ]
+ raise KeyError(
+ "The following id_vars or value_vars are not present in "
+ f"the DataFrame: {missing_labels}"
+ )
+ if value_vars_was_not_none:
+ frame = frame.iloc[:, algos.unique(idx)]
+ else:
+ frame = frame.copy(deep=False)
+ else:
+ frame = frame.copy(deep=False)
+
+ if col_level is not None: # allow list or other?
+ # frame is a copy
+ frame.columns = frame.columns.get_level_values(col_level)
+
+ if var_name is None:
+ if isinstance(frame.columns, MultiIndex):
+ if len(frame.columns.names) == len(set(frame.columns.names)):
+ var_name = frame.columns.names
+ else:
+ var_name = [f"variable_{i}" for i in range(len(frame.columns.names))]
+ else:
+ var_name = [
+ frame.columns.name if frame.columns.name is not None else "variable"
+ ]
+ elif is_list_like(var_name):
+ if isinstance(frame.columns, MultiIndex):
+ if is_iterator(var_name):
+ var_name = list(var_name)
+ if len(var_name) > len(frame.columns):
+ raise ValueError(
+ f"{var_name=} has {len(var_name)} items, "
+ f"but the dataframe columns only have {len(frame.columns)} levels."
+ )
+ else:
+ raise ValueError(f"{var_name=} must be a scalar.")
+ else:
+ var_name = [var_name]
+
+ num_rows, K = frame.shape
+ num_cols_adjusted = K - len(id_vars)
+
+ mdata: dict[Hashable, AnyArrayLike] = {}
+ for col in id_vars:
+ id_data = frame.pop(col)
+ if not isinstance(id_data.dtype, np.dtype):
+ # i.e. ExtensionDtype
+ if num_cols_adjusted > 0:
+ mdata[col] = concat([id_data] * num_cols_adjusted, ignore_index=True)
+ else:
+ # We can't concat empty list. (GH 46044)
+ mdata[col] = type(id_data)([], name=id_data.name, dtype=id_data.dtype)
+ else:
+ mdata[col] = np.tile(id_data._values, num_cols_adjusted)
+
+ mcolumns = id_vars + var_name + [value_name]
+
+ if frame.shape[1] > 0 and not any(
+ not isinstance(dt, np.dtype) and dt._supports_2d for dt in frame.dtypes
+ ):
+ mdata[value_name] = concat(
+ [frame.iloc[:, i] for i in range(frame.shape[1])], ignore_index=True
+ ).values
+ else:
+ mdata[value_name] = frame._values.ravel("F")
+ for i, col in enumerate(var_name):
+ mdata[col] = frame.columns._get_level_values(i).repeat(num_rows)
+
+ result = frame._constructor(mdata, columns=mcolumns)
+
+ if not ignore_index:
+ taker = np.tile(np.arange(len(frame)), num_cols_adjusted)
+ result.index = frame.index.take(taker)
+
+ return result
+
+
+@set_module("pandas")
+def lreshape(data: DataFrame, groups: dict, dropna: bool = True) -> DataFrame:
+ """
+ Reshape wide-format data to long. Generalized inverse of DataFrame.pivot.
+
+ Accepts a dictionary, ``groups``, in which each key is a new column name
+ and each value is a list of old column names that will be "melted" under
+ the new column name as part of the reshape.
+
+ Parameters
+ ----------
+ data : DataFrame
+ The wide-format DataFrame.
+ groups : dict
+ {new_name : list_of_columns}.
+ dropna : bool, default True
+ Do not include columns whose entries are all NaN.
+
+ Returns
+ -------
+ DataFrame
+ Reshaped DataFrame.
+
+ See Also
+ --------
+ melt : Unpivot a DataFrame from wide to long format, optionally leaving
+ identifiers set.
+ pivot : Create a spreadsheet-style pivot table as a DataFrame.
+ DataFrame.pivot : Pivot without aggregation that can handle
+ non-numeric data.
+ DataFrame.pivot_table : Generalization of pivot that can handle
+ duplicate values for one index/column pair.
+ DataFrame.unstack : Pivot based on the index values instead of a
+ column.
+ wide_to_long : Wide panel to long format. Less flexible but more
+ user-friendly than melt.
+
+ Examples
+ --------
+ >>> data = pd.DataFrame(
+ ... {
+ ... "hr1": [514, 573],
+ ... "hr2": [545, 526],
+ ... "team": ["Red Sox", "Yankees"],
+ ... "year1": [2007, 2007],
+ ... "year2": [2008, 2008],
+ ... }
+ ... )
+ >>> data
+ hr1 hr2 team year1 year2
+ 0 514 545 Red Sox 2007 2008
+ 1 573 526 Yankees 2007 2008
+
+ >>> pd.lreshape(data, {"year": ["year1", "year2"], "hr": ["hr1", "hr2"]})
+ team year hr
+ 0 Red Sox 2007 514
+ 1 Yankees 2007 573
+ 2 Red Sox 2008 545
+ 3 Yankees 2008 526
+ """
+ mdata = {}
+ pivot_cols = []
+ all_cols: set[Hashable] = set()
+ K = len(next(iter(groups.values())))
+ for target, names in groups.items():
+ if len(names) != K:
+ raise ValueError("All column lists must be same length")
+ to_concat = [data[col]._values for col in names]
+
+ mdata[target] = concat_compat(to_concat)
+ pivot_cols.append(target)
+ all_cols = all_cols.union(names)
+
+ id_cols = list(data.columns.difference(all_cols))
+ for col in id_cols:
+ mdata[col] = np.tile(data[col]._values, K)
+
+ if dropna:
+ mask = np.ones(len(mdata[pivot_cols[0]]), dtype=bool)
+ for c in pivot_cols:
+ mask &= notna(mdata[c])
+ if not mask.all():
+ mdata = {k: v[mask] for k, v in mdata.items()}
+
+ return data._constructor(mdata, columns=id_cols + pivot_cols)
+
+
+@set_module("pandas")
+def wide_to_long(
+ df: DataFrame, stubnames, i, j, sep: str = "", suffix: str = r"\d+"
+) -> DataFrame:
+ r"""
+ Unpivot a DataFrame from wide to long format.
+
+ Less flexible but more user-friendly than melt.
+
+ With stubnames ['A', 'B'], this function expects to find one or more
+ group of columns with format
+ A-suffix1, A-suffix2,..., B-suffix1, B-suffix2,...
+ You specify what you want to call this suffix in the resulting long format
+ with `j` (for example `j='year'`)
+
+ Each row of these wide variables are assumed to be uniquely identified by
+ `i` (can be a single column name or a list of column names)
+
+ All remaining variables in the data frame are left intact.
+
+ Parameters
+ ----------
+ df : DataFrame
+ The wide-format DataFrame.
+ stubnames : str or list-like
+ The stub name(s). The wide format variables are assumed to
+ start with the stub names.
+ i : str or list-like
+ Column(s) to use as id variable(s).
+ j : str
+ The name of the sub-observation variable. What you wish to name your
+ suffix in the long format.
+ sep : str, default ""
+ A character indicating the separation of the variable names
+ in the wide format, to be stripped from the names in the long format.
+ For example, if your column names are A-suffix1, A-suffix2, you
+ can strip the hyphen by specifying `sep='-'`.
+ suffix : str, default '\\d+'
+ A regular expression capturing the wanted suffixes. '\\d+' captures
+ numeric suffixes. Suffixes with no numbers could be specified with the
+ negated character class '\\D+'. You can also further disambiguate
+ suffixes, for example, if your wide variables are of the form A-one,
+ B-two,.., and you have an unrelated column A-rating, you can ignore the
+ last one by specifying `suffix='(!?one|two)'`. When all suffixes are
+ numeric, they are cast to int64/float64.
+
+ Returns
+ -------
+ DataFrame
+ A DataFrame that contains each stub name as a variable, with new index
+ (i, j).
+
+ See Also
+ --------
+ melt : Unpivot a DataFrame from wide to long format, optionally leaving
+ identifiers set.
+ pivot : Create a spreadsheet-style pivot table as a DataFrame.
+ DataFrame.pivot : Pivot without aggregation that can handle
+ non-numeric data.
+ DataFrame.pivot_table : Generalization of pivot that can handle
+ duplicate values for one index/column pair.
+ DataFrame.unstack : Pivot based on the index values instead of a
+ column.
+
+ Notes
+ -----
+ All extra variables are left untouched. This simply uses
+ `pandas.melt` under the hood, but is hard-coded to "do the right thing"
+ in a typical case.
+
+ Examples
+ --------
+ >>> np.random.seed(123)
+ >>> df = pd.DataFrame(
+ ... {
+ ... "A1970": {0: "a", 1: "b", 2: "c"},
+ ... "A1980": {0: "d", 1: "e", 2: "f"},
+ ... "B1970": {0: 2.5, 1: 1.2, 2: 0.7},
+ ... "B1980": {0: 3.2, 1: 1.3, 2: 0.1},
+ ... "X": dict(zip(range(3), np.random.randn(3), strict=True)),
+ ... }
+ ... )
+ >>> df["id"] = df.index
+ >>> df
+ A1970 A1980 B1970 B1980 X id
+ 0 a d 2.5 3.2 -1.085631 0
+ 1 b e 1.2 1.3 0.997345 1
+ 2 c f 0.7 0.1 0.282978 2
+ >>> pd.wide_to_long(df, ["A", "B"], i="id", j="year")
+ ... # doctest: +NORMALIZE_WHITESPACE
+ X A B
+ id year
+ 0 1970 -1.085631 a 2.5
+ 1 1970 0.997345 b 1.2
+ 2 1970 0.282978 c 0.7
+ 0 1980 -1.085631 d 3.2
+ 1 1980 0.997345 e 1.3
+ 2 1980 0.282978 f 0.1
+
+ With multiple id columns
+
+ >>> df = pd.DataFrame(
+ ... {
+ ... "famid": [1, 1, 1, 2, 2, 2, 3, 3, 3],
+ ... "birth": [1, 2, 3, 1, 2, 3, 1, 2, 3],
+ ... "ht1": [2.8, 2.9, 2.2, 2, 1.8, 1.9, 2.2, 2.3, 2.1],
+ ... "ht2": [3.4, 3.8, 2.9, 3.2, 2.8, 2.4, 3.3, 3.4, 2.9],
+ ... }
+ ... )
+ >>> df
+ famid birth ht1 ht2
+ 0 1 1 2.8 3.4
+ 1 1 2 2.9 3.8
+ 2 1 3 2.2 2.9
+ 3 2 1 2.0 3.2
+ 4 2 2 1.8 2.8
+ 5 2 3 1.9 2.4
+ 6 3 1 2.2 3.3
+ 7 3 2 2.3 3.4
+ 8 3 3 2.1 2.9
+ >>> long_format = pd.wide_to_long(df, stubnames="ht", i=["famid", "birth"], j="age")
+ >>> long_format
+ ... # doctest: +NORMALIZE_WHITESPACE
+ ht
+ famid birth age
+ 1 1 1 2.8
+ 2 3.4
+ 2 1 2.9
+ 2 3.8
+ 3 1 2.2
+ 2 2.9
+ 2 1 1 2.0
+ 2 3.2
+ 2 1 1.8
+ 2 2.8
+ 3 1 1.9
+ 2 2.4
+ 3 1 1 2.2
+ 2 3.3
+ 2 1 2.3
+ 2 3.4
+ 3 1 2.1
+ 2 2.9
+
+ Going from long back to wide just takes some creative use of `unstack`
+
+ >>> wide_format = long_format.unstack()
+ >>> wide_format.columns = wide_format.columns.map("{0[0]}{0[1]}".format)
+ >>> wide_format.reset_index()
+ famid birth ht1 ht2
+ 0 1 1 2.8 3.4
+ 1 1 2 2.9 3.8
+ 2 1 3 2.2 2.9
+ 3 2 1 2.0 3.2
+ 4 2 2 1.8 2.8
+ 5 2 3 1.9 2.4
+ 6 3 1 2.2 3.3
+ 7 3 2 2.3 3.4
+ 8 3 3 2.1 2.9
+
+ Less wieldy column names are also handled
+
+ >>> np.random.seed(0)
+ >>> df = pd.DataFrame(
+ ... {
+ ... "A(weekly)-2010": np.random.rand(3),
+ ... "A(weekly)-2011": np.random.rand(3),
+ ... "B(weekly)-2010": np.random.rand(3),
+ ... "B(weekly)-2011": np.random.rand(3),
+ ... "X": np.random.randint(3, size=3),
+ ... }
+ ... )
+ >>> df["id"] = df.index
+ >>> df # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS
+ A(weekly)-2010 A(weekly)-2011 B(weekly)-2010 B(weekly)-2011 X id
+ 0 0.548814 0.544883 0.437587 0.383442 0 0
+ 1 0.715189 0.423655 0.891773 0.791725 1 1
+ 2 0.602763 0.645894 0.963663 0.528895 1 2
+
+ >>> pd.wide_to_long(df, ["A(weekly)", "B(weekly)"], i="id", j="year", sep="-")
+ ... # doctest: +NORMALIZE_WHITESPACE
+ X A(weekly) B(weekly)
+ id year
+ 0 2010 0 0.548814 0.437587
+ 1 2010 1 0.715189 0.891773
+ 2 2010 1 0.602763 0.963663
+ 0 2011 0 0.544883 0.383442
+ 1 2011 1 0.423655 0.791725
+ 2 2011 1 0.645894 0.528895
+
+ If we have many columns, we could also use a regex to find our
+ stubnames and pass that list on to wide_to_long
+
+ >>> stubnames = sorted(
+ ... set(
+ ... [
+ ... match[0]
+ ... for match in df.columns.str.findall(r"[A-B]\(.*\)").values
+ ... if match != []
+ ... ]
+ ... )
+ ... )
+ >>> list(stubnames)
+ ['A(weekly)', 'B(weekly)']
+
+ All of the above examples have integers as suffixes. It is possible to
+ have non-integers as suffixes.
+
+ >>> df = pd.DataFrame(
+ ... {
+ ... "famid": [1, 1, 1, 2, 2, 2, 3, 3, 3],
+ ... "birth": [1, 2, 3, 1, 2, 3, 1, 2, 3],
+ ... "ht_one": [2.8, 2.9, 2.2, 2, 1.8, 1.9, 2.2, 2.3, 2.1],
+ ... "ht_two": [3.4, 3.8, 2.9, 3.2, 2.8, 2.4, 3.3, 3.4, 2.9],
+ ... }
+ ... )
+ >>> df
+ famid birth ht_one ht_two
+ 0 1 1 2.8 3.4
+ 1 1 2 2.9 3.8
+ 2 1 3 2.2 2.9
+ 3 2 1 2.0 3.2
+ 4 2 2 1.8 2.8
+ 5 2 3 1.9 2.4
+ 6 3 1 2.2 3.3
+ 7 3 2 2.3 3.4
+ 8 3 3 2.1 2.9
+
+ >>> long_format = pd.wide_to_long(
+ ... df, stubnames="ht", i=["famid", "birth"], j="age", sep="_", suffix=r"\w+"
+ ... )
+ >>> long_format
+ ... # doctest: +NORMALIZE_WHITESPACE
+ ht
+ famid birth age
+ 1 1 one 2.8
+ two 3.4
+ 2 one 2.9
+ two 3.8
+ 3 one 2.2
+ two 2.9
+ 2 1 one 2.0
+ two 3.2
+ 2 one 1.8
+ two 2.8
+ 3 one 1.9
+ two 2.4
+ 3 1 one 2.2
+ two 3.3
+ 2 one 2.3
+ two 3.4
+ 3 one 2.1
+ two 2.9
+ """
+
+ def get_var_names(df, stub: str, sep: str, suffix: str):
+ regex = rf"^{re.escape(stub)}{re.escape(sep)}{suffix}$"
+ return df.columns[df.columns.str.match(regex)]
+
+ def melt_stub(df, stub: str, i, j, value_vars, sep: str):
+ newdf = melt(
+ df,
+ id_vars=i,
+ value_vars=value_vars,
+ value_name=stub.rstrip(sep),
+ var_name=j,
+ )
+ newdf[j] = newdf[j].str.replace(re.escape(stub + sep), "", regex=True)
+
+ # GH17627 Cast numerics suffixes to int/float
+ try:
+ newdf[j] = to_numeric(newdf[j])
+ except (TypeError, ValueError, OverflowError):
+ # TODO: anything else to catch?
+ pass
+
+ return newdf.set_index([*i, j])
+
+ if not is_list_like(stubnames):
+ stubnames = [stubnames]
+ else:
+ stubnames = list(stubnames)
+
+ if df.columns.isin(stubnames).any():
+ raise ValueError("stubname can't be identical to a column name")
+
+ if not is_list_like(i):
+ i = [i]
+ else:
+ i = list(i)
+
+ if df[i].duplicated().any():
+ raise ValueError("the id variables need to uniquely identify each row")
+
+ _melted = []
+ value_vars_flattened = []
+ for stub in stubnames:
+ value_var = get_var_names(df, stub, sep, suffix)
+ value_vars_flattened.extend(value_var)
+ _melted.append(melt_stub(df, stub, i, j, value_var, sep))
+
+ melted = concat(_melted, axis=1)
+ id_vars = df.columns.difference(value_vars_flattened)
+ new = df[id_vars]
+
+ if len(i) == 1:
+ return new.set_index(i).join(melted)
+ else:
+ return new.merge(melted.reset_index(), on=i).set_index([*i, j])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/merge.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/merge.py
new file mode 100644
index 0000000000000000000000000000000000000000..712caf9755a6812120c162cc9e17f6895de97df7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/merge.py
@@ -0,0 +1,3136 @@
+"""
+SQL-style merge routines
+"""
+
+from __future__ import annotations
+
+from collections.abc import (
+ Hashable,
+ Sequence,
+)
+import datetime
+from functools import partial
+import types
+from typing import (
+ TYPE_CHECKING,
+ Literal,
+ cast,
+ final,
+)
+import uuid
+import warnings
+
+import numpy as np
+
+from pandas._libs import (
+ Timedelta,
+ hashtable as libhashtable,
+ join as libjoin,
+ lib,
+)
+from pandas._libs.lib import is_range_indexer
+from pandas._typing import (
+ AnyArrayLike,
+ ArrayLike,
+ IndexLabel,
+ JoinHow,
+ MergeHow,
+ Shape,
+ Suffixes,
+ npt,
+)
+from pandas.errors import MergeError
+from pandas.util._decorators import (
+ cache_readonly,
+ set_module,
+)
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core.dtypes.base import ExtensionDtype
+from pandas.core.dtypes.cast import find_common_type
+from pandas.core.dtypes.common import (
+ ensure_int64,
+ ensure_object,
+ is_bool,
+ is_bool_dtype,
+ is_float_dtype,
+ is_integer,
+ is_integer_dtype,
+ is_list_like,
+ is_number,
+ is_numeric_dtype,
+ is_object_dtype,
+ is_string_dtype,
+ needs_i8_conversion,
+)
+from pandas.core.dtypes.dtypes import (
+ CategoricalDtype,
+ DatetimeTZDtype,
+)
+from pandas.core.dtypes.generic import (
+ ABCDataFrame,
+ ABCSeries,
+)
+from pandas.core.dtypes.missing import (
+ isna,
+ na_value_for_dtype,
+)
+
+from pandas import (
+ ArrowDtype,
+ Categorical,
+ Index,
+ MultiIndex,
+ Series,
+)
+import pandas.core.algorithms as algos
+from pandas.core.arrays import (
+ ArrowExtensionArray,
+ BaseMaskedArray,
+ ExtensionArray,
+)
+from pandas.core.arrays.string_ import StringDtype
+import pandas.core.common as com
+from pandas.core.construction import (
+ ensure_wrapped_if_datetimelike,
+ extract_array,
+)
+from pandas.core.indexes.api import default_index
+from pandas.core.sorting import (
+ get_group_index,
+ is_int64_overflow_possible,
+)
+
+if TYPE_CHECKING:
+ from pandas import DataFrame
+ from pandas.core import groupby
+ from pandas.core.arrays import DatetimeArray
+ from pandas.core.indexes.frozen import FrozenList
+
+_factorizers = {
+ np.int64: libhashtable.Int64Factorizer,
+ np.longlong: libhashtable.Int64Factorizer,
+ np.int32: libhashtable.Int32Factorizer,
+ np.int16: libhashtable.Int16Factorizer,
+ np.int8: libhashtable.Int8Factorizer,
+ np.uint64: libhashtable.UInt64Factorizer,
+ np.uint32: libhashtable.UInt32Factorizer,
+ np.uint16: libhashtable.UInt16Factorizer,
+ np.uint8: libhashtable.UInt8Factorizer,
+ np.bool_: libhashtable.UInt8Factorizer,
+ np.float64: libhashtable.Float64Factorizer,
+ np.float32: libhashtable.Float32Factorizer,
+ np.complex64: libhashtable.Complex64Factorizer,
+ np.complex128: libhashtable.Complex128Factorizer,
+ np.object_: libhashtable.ObjectFactorizer,
+}
+
+# See https://github.com/pandas-dev/pandas/issues/52451
+if np.intc is not np.int32:
+ if np.dtype(np.intc).itemsize == 4:
+ _factorizers[np.intc] = libhashtable.Int32Factorizer
+ else:
+ _factorizers[np.intc] = libhashtable.Int64Factorizer
+
+if np.uintc is not np.uint32:
+ if np.dtype(np.uintc).itemsize == 4:
+ _factorizers[np.uintc] = libhashtable.UInt32Factorizer
+ else:
+ _factorizers[np.uintc] = libhashtable.UInt64Factorizer
+
+
+_known = (np.ndarray, ExtensionArray, Index, ABCSeries)
+
+
+@set_module("pandas")
+def merge(
+ left: DataFrame | Series,
+ right: DataFrame | Series,
+ how: MergeHow = "inner",
+ on: IndexLabel | AnyArrayLike | None = None,
+ left_on: IndexLabel | AnyArrayLike | None = None,
+ right_on: IndexLabel | AnyArrayLike | None = None,
+ left_index: bool = False,
+ right_index: bool = False,
+ sort: bool = False,
+ suffixes: Suffixes = ("_x", "_y"),
+ copy: bool | lib.NoDefault = lib.no_default,
+ indicator: str | bool = False,
+ validate: str | None = None,
+) -> DataFrame:
+ """
+ Merge DataFrame or named Series objects with a database-style join.
+
+ A named Series object is treated as a DataFrame with a single named column.
+
+ The join is done on columns or indexes. If joining columns on
+ columns, the DataFrame indexes *will be ignored*. Otherwise if joining indexes
+ on indexes or indexes on a column or columns, the index will be passed on.
+ When performing a cross merge, no column specifications to merge on are
+ allowed.
+
+ .. warning::
+
+ If both key columns contain rows where the key is a null value, those
+ rows will be matched against each other. This is different from usual SQL
+ join behaviour and can lead to unexpected results.
+
+ Parameters
+ ----------
+ left : DataFrame or named Series
+ First pandas object to merge.
+ right : DataFrame or named Series
+ Second pandas object to merge.
+ how : {'left', 'right', 'outer', 'inner', 'cross', 'left_anti', 'right_anti},
+ default 'inner'
+ Type of merge to be performed.
+
+ * left: use only keys from left frame, similar to a SQL left outer join;
+ preserve key order.
+ * right: use only keys from right frame, similar to a SQL right outer join;
+ preserve key order.
+ * outer: use union of keys from both frames, similar to a SQL full outer
+ join; sort keys lexicographically.
+ * inner: use intersection of keys from both frames, similar to a SQL inner
+ join; preserve the order of the left keys.
+ * cross: creates the cartesian product from both frames, preserves the order
+ of the left keys.
+ * left_anti: use only keys from left frame that are not in right frame, similar
+ to SQL left anti join; preserve key order.
+ * right_anti: use only keys from right frame that are not in left frame, similar
+ to SQL right anti join; preserve key order.
+ on : Hashable or a sequence of the previous
+ Column or index level names to join on. These must be found in both
+ DataFrames. If `on` is None and not merging on indexes then this defaults
+ to the intersection of the columns in both DataFrames.
+ left_on : Hashable or a sequence of the previous, or array-like
+ Column or index level names to join on in the left DataFrame. Can also
+ be an array or list of arrays of the length of the left DataFrame.
+ These arrays are treated as if they are columns.
+ right_on : Hashable or a sequence of the previous, or array-like
+ Column or index level names to join on in the right DataFrame. Can also
+ be an array or list of arrays of the length of the right DataFrame.
+ These arrays are treated as if they are columns.
+ left_index : bool, default False
+ Use the index from the left DataFrame as the join key(s). If it is a
+ MultiIndex, the number of keys in the other DataFrame (either the index
+ or a number of columns) must match the number of levels.
+ right_index : bool, default False
+ Use the index from the right DataFrame as the join key. Same caveats as
+ left_index.
+ sort : bool, default False
+ Sort the join keys lexicographically in the result DataFrame. If False,
+ the order of the join keys depends on the join type (how keyword).
+ suffixes : list-like, default is ("_x", "_y")
+ A length-2 sequence where each element is optionally a string
+ indicating the suffix to add to overlapping column names in
+ `left` and `right` respectively. Pass a value of `None` instead
+ of a string to indicate that the column name from `left` or
+ `right` should be left as-is, with no suffix. At least one of the
+ values must not be None.
+ copy : bool, default False
+ This keyword is now ignored; changing its value will have no
+ impact on the method.
+
+ .. deprecated:: 3.0.0
+
+ This keyword is ignored and will be removed in pandas 4.0. Since
+ pandas 3.0, this method always returns a new object using a lazy
+ copy mechanism that defers copies until necessary
+ (Copy-on-Write). See the `user guide on Copy-on-Write
+ `__
+ for more details.
+
+ indicator : bool or str, default False
+ If True, adds a column to the output DataFrame called "_merge" with
+ information on the source of each row. The column can be given a different
+ name by providing a string argument. The column will have a Categorical
+ type with the value of "left_only" for observations whose merge key only
+ appears in the left DataFrame, "right_only" for observations
+ whose merge key only appears in the right DataFrame, and "both"
+ if the observation's merge key is found in both DataFrames.
+
+ validate : str, optional
+ If specified, checks if merge is of specified type.
+
+ * "one_to_one" or "1:1": check if merge keys are unique in both
+ left and right datasets.
+ * "one_to_many" or "1:m": check if merge keys are unique in left
+ dataset.
+ * "many_to_one" or "m:1": check if merge keys are unique in right
+ dataset.
+ * "many_to_many" or "m:m": allowed, but does not result in checks.
+
+ Returns
+ -------
+ DataFrame
+ A DataFrame of the two merged objects.
+
+ See Also
+ --------
+ merge_ordered : Merge with optional filling/interpolation.
+ merge_asof : Merge on nearest keys.
+ DataFrame.join : Similar method using indices.
+
+ Examples
+ --------
+ >>> df1 = pd.DataFrame(
+ ... {"lkey": ["foo", "bar", "baz", "foo"], "value": [1, 2, 3, 5]}
+ ... )
+ >>> df2 = pd.DataFrame(
+ ... {"rkey": ["foo", "bar", "baz", "foo"], "value": [5, 6, 7, 8]}
+ ... )
+ >>> df1
+ lkey value
+ 0 foo 1
+ 1 bar 2
+ 2 baz 3
+ 3 foo 5
+ >>> df2
+ rkey value
+ 0 foo 5
+ 1 bar 6
+ 2 baz 7
+ 3 foo 8
+
+ Merge df1 and df2 on the lkey and rkey columns. The value columns have
+ the default suffixes, _x and _y, appended.
+
+ >>> df1.merge(df2, left_on="lkey", right_on="rkey")
+ lkey value_x rkey value_y
+ 0 foo 1 foo 5
+ 1 foo 1 foo 8
+ 2 bar 2 bar 6
+ 3 baz 3 baz 7
+ 4 foo 5 foo 5
+ 5 foo 5 foo 8
+
+ Merge DataFrames df1 and df2 with specified left and right suffixes
+ appended to any overlapping columns.
+
+ >>> df1.merge(df2, left_on="lkey", right_on="rkey", suffixes=("_left", "_right"))
+ lkey value_left rkey value_right
+ 0 foo 1 foo 5
+ 1 foo 1 foo 8
+ 2 bar 2 bar 6
+ 3 baz 3 baz 7
+ 4 foo 5 foo 5
+ 5 foo 5 foo 8
+
+ Merge DataFrames df1 and df2, but raise an exception if the DataFrames have
+ any overlapping columns.
+
+ >>> df1.merge(df2, left_on="lkey", right_on="rkey", suffixes=(False, False))
+ Traceback (most recent call last):
+ ...
+ ValueError: columns overlap but no suffix specified:
+ Index(['value'], dtype='str')
+
+ >>> df1 = pd.DataFrame({"a": ["foo", "bar"], "b": [1, 2]})
+ >>> df2 = pd.DataFrame({"a": ["foo", "baz"], "c": [3, 4]})
+ >>> df1
+ a b
+ 0 foo 1
+ 1 bar 2
+ >>> df2
+ a c
+ 0 foo 3
+ 1 baz 4
+
+ >>> df1.merge(df2, how="inner", on="a")
+ a b c
+ 0 foo 1 3
+
+ >>> df1.merge(df2, how="left", on="a")
+ a b c
+ 0 foo 1 3.0
+ 1 bar 2 NaN
+
+ >>> df1 = pd.DataFrame({"left": ["foo", "bar"]})
+ >>> df2 = pd.DataFrame({"right": [7, 8]})
+ >>> df1
+ left
+ 0 foo
+ 1 bar
+ >>> df2
+ right
+ 0 7
+ 1 8
+
+ >>> df1.merge(df2, how="cross")
+ left right
+ 0 foo 7
+ 1 foo 8
+ 2 bar 7
+ 3 bar 8
+ """
+ left_df = _validate_operand(left)
+ left._check_copy_deprecation(copy)
+ right_df = _validate_operand(right)
+ if how == "cross":
+ return _cross_merge(
+ left_df,
+ right_df,
+ on=on,
+ left_on=left_on,
+ right_on=right_on,
+ left_index=left_index,
+ right_index=right_index,
+ sort=sort,
+ suffixes=suffixes,
+ indicator=indicator,
+ validate=validate,
+ )
+ else:
+ op = _MergeOperation(
+ left_df,
+ right_df,
+ how=how,
+ on=on,
+ left_on=left_on,
+ right_on=right_on,
+ left_index=left_index,
+ right_index=right_index,
+ sort=sort,
+ suffixes=suffixes,
+ indicator=indicator,
+ validate=validate,
+ )
+ return op.get_result()
+
+
+def _cross_merge(
+ left: DataFrame,
+ right: DataFrame,
+ on: IndexLabel | AnyArrayLike | None = None,
+ left_on: IndexLabel | AnyArrayLike | None = None,
+ right_on: IndexLabel | AnyArrayLike | None = None,
+ left_index: bool = False,
+ right_index: bool = False,
+ sort: bool = False,
+ suffixes: Suffixes = ("_x", "_y"),
+ indicator: str | bool = False,
+ validate: str | None = None,
+) -> DataFrame:
+ """
+ See merge.__doc__ with how='cross'
+ """
+
+ if (
+ left_index
+ or right_index
+ or right_on is not None
+ or left_on is not None
+ or on is not None
+ ):
+ raise MergeError(
+ "Can not pass on, right_on, left_on or set right_index=True or "
+ "left_index=True"
+ )
+
+ cross_col = f"_cross_{uuid.uuid4()}"
+ left = left.assign(**{cross_col: 1})
+ right = right.assign(**{cross_col: 1})
+
+ left_on = right_on = [cross_col]
+
+ res = merge(
+ left,
+ right,
+ how="inner",
+ on=on,
+ left_on=left_on,
+ right_on=right_on,
+ left_index=left_index,
+ right_index=right_index,
+ sort=sort,
+ suffixes=suffixes,
+ indicator=indicator,
+ validate=validate,
+ )
+ del res[cross_col]
+ return res
+
+
+def _groupby_and_merge(
+ by, left: DataFrame | Series, right: DataFrame | Series, merge_pieces
+):
+ """
+ groupby & merge; we are always performing a left-by type operation
+
+ Parameters
+ ----------
+ by: field to group
+ left: DataFrame
+ right: DataFrame
+ merge_pieces: function for merging
+ """
+ pieces = []
+ if not isinstance(by, (list, tuple)):
+ by = [by]
+
+ lby = left.groupby(by, sort=False)
+ rby: groupby.DataFrameGroupBy | groupby.SeriesGroupBy | None = None
+
+ # if we can groupby the rhs
+ # then we can get vastly better perf
+ if all(item in right.columns for item in by):
+ rby = right.groupby(by, sort=False)
+
+ for key, lhs in lby._grouper.get_iterator(lby._selected_obj):
+ if rby is None:
+ rhs = right
+ else:
+ try:
+ rhs = right.take(rby.indices[key])
+ except KeyError:
+ # key doesn't exist in left
+ lcols = lhs.columns.tolist()
+ cols = lcols + [r for r in right.columns if r not in set(lcols)]
+ merged = lhs.reindex(columns=cols)
+ merged.index = range(len(merged))
+ pieces.append(merged)
+ continue
+
+ merged = merge_pieces(lhs, rhs)
+
+ # make sure join keys are in the merged
+ # TODO, should merge_pieces do this?
+ merged[by] = key
+
+ pieces.append(merged)
+
+ # preserve the original order
+ # if we have a missing piece this can be reset
+ from pandas.core.reshape.concat import concat
+
+ result = concat(pieces, ignore_index=True)
+ result = result.reindex(columns=pieces[0].columns)
+ return result, lby
+
+
+@set_module("pandas")
+def merge_ordered(
+ left: DataFrame | Series,
+ right: DataFrame | Series,
+ on: IndexLabel | None = None,
+ left_on: IndexLabel | None = None,
+ right_on: IndexLabel | None = None,
+ left_by=None,
+ right_by=None,
+ fill_method: str | None = None,
+ suffixes: Suffixes = ("_x", "_y"),
+ how: JoinHow = "outer",
+) -> DataFrame:
+ """
+ Perform a merge for ordered data with optional filling/interpolation.
+
+ Designed for ordered data like time series data. Optionally
+ perform group-wise merge (see examples).
+
+ Parameters
+ ----------
+ left : DataFrame or named Series
+ First pandas object to merge.
+ right : DataFrame or named Series
+ Second pandas object to merge.
+ on : Hashable or a sequence of the previous
+ Field names to join on. Must be found in both DataFrames.
+ left_on : Hashable or a sequence of the previous, or array-like
+ Field names to join on in left DataFrame. Can be a vector or list of
+ vectors of the length of the DataFrame to use a particular vector as
+ the join key instead of columns.
+ right_on : Hashable or a sequence of the previous, or array-like
+ Field names to join on in right DataFrame or vector/list of vectors per
+ left_on docs.
+ left_by : column name or list of column names
+ Group left DataFrame by group columns and merge piece by piece with
+ right DataFrame. Must be None if either left or right are a Series.
+ right_by : column name or list of column names
+ Group right DataFrame by group columns and merge piece by piece with
+ left DataFrame. Must be None if either left or right are a Series.
+ fill_method : {'ffill', None}, default None
+ Interpolation method for data.
+ suffixes : list-like, default is ("_x", "_y")
+ A length-2 sequence where each element is optionally a string
+ indicating the suffix to add to overlapping column names in
+ `left` and `right` respectively. Pass a value of `None` instead
+ of a string to indicate that the column name from `left` or
+ `right` should be left as-is, with no suffix. At least one of the
+ values must not be None.
+
+ how : {'left', 'right', 'outer', 'inner'}, default 'outer'
+ * left: use only keys from left frame (SQL: left outer join)
+ * right: use only keys from right frame (SQL: right outer join)
+ * outer: use union of keys from both frames (SQL: full outer join)
+ * inner: use intersection of keys from both frames (SQL: inner join).
+
+ Returns
+ -------
+ DataFrame
+ The merged DataFrame output type will be the same as
+ 'left', if it is a subclass of DataFrame.
+
+ See Also
+ --------
+ merge : Merge with a database-style join.
+ merge_asof : Merge on nearest keys.
+
+ Examples
+ --------
+ >>> from pandas import merge_ordered
+ >>> df1 = pd.DataFrame(
+ ... {
+ ... "key": ["a", "c", "e", "a", "c", "e"],
+ ... "lvalue": [1, 2, 3, 1, 2, 3],
+ ... "group": ["a", "a", "a", "b", "b", "b"],
+ ... }
+ ... )
+ >>> df1
+ key lvalue group
+ 0 a 1 a
+ 1 c 2 a
+ 2 e 3 a
+ 3 a 1 b
+ 4 c 2 b
+ 5 e 3 b
+
+ >>> df2 = pd.DataFrame({"key": ["b", "c", "d"], "rvalue": [1, 2, 3]})
+ >>> df2
+ key rvalue
+ 0 b 1
+ 1 c 2
+ 2 d 3
+
+ >>> merge_ordered(df1, df2, fill_method="ffill", left_by="group")
+ key lvalue group rvalue
+ 0 a 1 a NaN
+ 1 b 1 a 1.0
+ 2 c 2 a 2.0
+ 3 d 2 a 3.0
+ 4 e 3 a 3.0
+ 5 a 1 b NaN
+ 6 b 1 b 1.0
+ 7 c 2 b 2.0
+ 8 d 2 b 3.0
+ 9 e 3 b 3.0
+ """
+
+ def _merger(x, y) -> DataFrame:
+ # perform the ordered merge operation
+ op = _OrderedMerge(
+ x,
+ y,
+ on=on,
+ left_on=left_on,
+ right_on=right_on,
+ suffixes=suffixes,
+ fill_method=fill_method,
+ how=how,
+ )
+ return op.get_result()
+
+ if left_by is not None and right_by is not None:
+ raise ValueError("Can only group either left or right frames")
+ if left_by is not None:
+ if isinstance(left_by, str):
+ left_by = [left_by]
+ check = set(left_by).difference(left.columns)
+ if len(check) != 0:
+ raise KeyError(f"{check} not found in left columns")
+ result, _ = _groupby_and_merge(left_by, left, right, lambda x, y: _merger(x, y))
+ elif right_by is not None:
+ if isinstance(right_by, str):
+ right_by = [right_by]
+ check = set(right_by).difference(right.columns)
+ if len(check) != 0:
+ raise KeyError(f"{check} not found in right columns")
+ result, _ = _groupby_and_merge(
+ right_by, right, left, lambda x, y: _merger(y, x)
+ )
+ else:
+ result = _merger(left, right)
+ return result
+
+
+@set_module("pandas")
+def merge_asof(
+ left: DataFrame | Series,
+ right: DataFrame | Series,
+ on: IndexLabel | None = None,
+ left_on: IndexLabel | None = None,
+ right_on: IndexLabel | None = None,
+ left_index: bool = False,
+ right_index: bool = False,
+ by=None,
+ left_by=None,
+ right_by=None,
+ suffixes: Suffixes = ("_x", "_y"),
+ tolerance: int | datetime.timedelta | None = None,
+ allow_exact_matches: bool = True,
+ direction: str = "backward",
+) -> DataFrame:
+ """
+ Perform a merge by key distance.
+
+ This is similar to a left-join except that we match on nearest
+ key rather than equal keys. Both DataFrames must be first sorted by
+ the merge key in ascending order before calling this function.
+ Sorting by any additional 'by' grouping columns is not required.
+
+ For each row in the left DataFrame:
+
+ - A "backward" search selects the last row in the right DataFrame whose
+ 'on' key is less than or equal to the left's key.
+
+ - A "forward" search selects the first row in the right DataFrame whose
+ 'on' key is greater than or equal to the left's key.
+
+ - A "nearest" search selects the row in the right DataFrame whose 'on'
+ key is closest in absolute distance to the left's key.
+
+ Optionally match on equivalent keys with 'by' before searching with 'on'.
+
+ Parameters
+ ----------
+ left : DataFrame or named Series
+ First pandas object to merge.
+ right : DataFrame or named Series
+ Second pandas object to merge.
+ on : label
+ Field name to join on. Must be found in both DataFrames.
+ The data MUST be in ascending order. Furthermore this must be
+ a numeric column, such as datetimelike, integer, or float. ``on``
+ or ``left_on`` / ``right_on`` must be given.
+ left_on : label
+ Field name to join on in left DataFrame. If specified, sort the left
+ DataFrame by this column in ascending order before merging.
+ right_on : label
+ Field name to join on in right DataFrame. If specified, sort the right
+ DataFrame by this column in ascending order before merging.
+ left_index : bool
+ Use the index of the left DataFrame as the join key.
+ right_index : bool
+ Use the index of the right DataFrame as the join key.
+ by : column name or list of column names
+ Match on these columns before performing merge operation. It is not required
+ to sort by these columns.
+ left_by : column name
+ Field names to match on in the left DataFrame.
+ right_by : column name
+ Field names to match on in the right DataFrame.
+ suffixes : 2-length sequence (tuple, list, ...)
+ Suffix to apply to overlapping column names in the left and right
+ side, respectively.
+ tolerance : int or timedelta, optional, default None
+ Select asof tolerance within this range; must be compatible
+ with the merge index.
+ allow_exact_matches : bool, default True
+
+ - If True, allow matching with the same 'on' value
+ (i.e. less-than-or-equal-to / greater-than-or-equal-to)
+ - If False, don't match the same 'on' value
+ (i.e., strictly less-than / strictly greater-than).
+
+ direction : 'backward' (default), 'forward', or 'nearest'
+ Whether to search for prior, subsequent, or closest matches.
+
+ Returns
+ -------
+ DataFrame
+ A DataFrame of the two merged objects, containing all rows from the
+ left DataFrame and the nearest matches from the right DataFrame.
+
+ See Also
+ --------
+ merge : Merge with a database-style join.
+ merge_ordered : Merge with optional filling/interpolation.
+
+ Examples
+ --------
+ >>> left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ >>> left
+ a left_val
+ 0 1 a
+ 1 5 b
+ 2 10 c
+
+ >>> right = pd.DataFrame({"a": [1, 2, 3, 6, 7], "right_val": [1, 2, 3, 6, 7]})
+ >>> right
+ a right_val
+ 0 1 1
+ 1 2 2
+ 2 3 3
+ 3 6 6
+ 4 7 7
+
+ >>> pd.merge_asof(left, right, on="a")
+ a left_val right_val
+ 0 1 a 1
+ 1 5 b 3
+ 2 10 c 7
+
+ >>> pd.merge_asof(left, right, on="a", allow_exact_matches=False)
+ a left_val right_val
+ 0 1 a NaN
+ 1 5 b 3.0
+ 2 10 c 7.0
+
+ >>> pd.merge_asof(left, right, on="a", direction="forward")
+ a left_val right_val
+ 0 1 a 1.0
+ 1 5 b 6.0
+ 2 10 c NaN
+
+ >>> pd.merge_asof(left, right, on="a", direction="nearest")
+ a left_val right_val
+ 0 1 a 1
+ 1 5 b 6
+ 2 10 c 7
+
+ We can use indexed DataFrames as well.
+
+ >>> left = pd.DataFrame({"left_val": ["a", "b", "c"]}, index=[1, 5, 10])
+ >>> left
+ left_val
+ 1 a
+ 5 b
+ 10 c
+
+ >>> right = pd.DataFrame({"right_val": [1, 2, 3, 6, 7]}, index=[1, 2, 3, 6, 7])
+ >>> right
+ right_val
+ 1 1
+ 2 2
+ 3 3
+ 6 6
+ 7 7
+
+ >>> pd.merge_asof(left, right, left_index=True, right_index=True)
+ left_val right_val
+ 1 a 1
+ 5 b 3
+ 10 c 7
+
+ Here is a real-world times-series example
+
+ >>> quotes = pd.DataFrame(
+ ... {
+ ... "time": [
+ ... pd.Timestamp("2016-05-25 13:30:00.023"),
+ ... pd.Timestamp("2016-05-25 13:30:00.023"),
+ ... pd.Timestamp("2016-05-25 13:30:00.030"),
+ ... pd.Timestamp("2016-05-25 13:30:00.041"),
+ ... pd.Timestamp("2016-05-25 13:30:00.048"),
+ ... pd.Timestamp("2016-05-25 13:30:00.049"),
+ ... pd.Timestamp("2016-05-25 13:30:00.072"),
+ ... pd.Timestamp("2016-05-25 13:30:00.075"),
+ ... ],
+ ... "ticker": [
+ ... "GOOG",
+ ... "MSFT",
+ ... "MSFT",
+ ... "MSFT",
+ ... "GOOG",
+ ... "AAPL",
+ ... "GOOG",
+ ... "MSFT",
+ ... ],
+ ... "bid": [720.50, 51.95, 51.97, 51.99, 720.50, 97.99, 720.50, 52.01],
+ ... "ask": [720.93, 51.96, 51.98, 52.00, 720.93, 98.01, 720.88, 52.03],
+ ... }
+ ... )
+ >>> quotes
+ time ticker bid ask
+ 0 2016-05-25 13:30:00.023 GOOG 720.50 720.93
+ 1 2016-05-25 13:30:00.023 MSFT 51.95 51.96
+ 2 2016-05-25 13:30:00.030 MSFT 51.97 51.98
+ 3 2016-05-25 13:30:00.041 MSFT 51.99 52.00
+ 4 2016-05-25 13:30:00.048 GOOG 720.50 720.93
+ 5 2016-05-25 13:30:00.049 AAPL 97.99 98.01
+ 6 2016-05-25 13:30:00.072 GOOG 720.50 720.88
+ 7 2016-05-25 13:30:00.075 MSFT 52.01 52.03
+
+ >>> trades = pd.DataFrame(
+ ... {
+ ... "time": [
+ ... pd.Timestamp("2016-05-25 13:30:00.023"),
+ ... pd.Timestamp("2016-05-25 13:30:00.038"),
+ ... pd.Timestamp("2016-05-25 13:30:00.048"),
+ ... pd.Timestamp("2016-05-25 13:30:00.048"),
+ ... pd.Timestamp("2016-05-25 13:30:00.048"),
+ ... ],
+ ... "ticker": ["MSFT", "MSFT", "GOOG", "GOOG", "AAPL"],
+ ... "price": [51.95, 51.95, 720.77, 720.92, 98.0],
+ ... "quantity": [75, 155, 100, 100, 100],
+ ... }
+ ... )
+ >>> trades
+ time ticker price quantity
+ 0 2016-05-25 13:30:00.023 MSFT 51.95 75
+ 1 2016-05-25 13:30:00.038 MSFT 51.95 155
+ 2 2016-05-25 13:30:00.048 GOOG 720.77 100
+ 3 2016-05-25 13:30:00.048 GOOG 720.92 100
+ 4 2016-05-25 13:30:00.048 AAPL 98.00 100
+
+ By default we are taking the asof of the quotes
+
+ >>> pd.merge_asof(trades, quotes, on="time", by="ticker")
+ time ticker price quantity bid ask
+ 0 2016-05-25 13:30:00.023 MSFT 51.95 75 51.95 51.96
+ 1 2016-05-25 13:30:00.038 MSFT 51.95 155 51.97 51.98
+ 2 2016-05-25 13:30:00.048 GOOG 720.77 100 720.50 720.93
+ 3 2016-05-25 13:30:00.048 GOOG 720.92 100 720.50 720.93
+ 4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
+
+ We only asof within 2ms between the quote time and the trade time
+
+ >>> pd.merge_asof(
+ ... trades, quotes, on="time", by="ticker", tolerance=pd.Timedelta("2ms")
+ ... )
+ time ticker price quantity bid ask
+ 0 2016-05-25 13:30:00.023 MSFT 51.95 75 51.95 51.96
+ 1 2016-05-25 13:30:00.038 MSFT 51.95 155 NaN NaN
+ 2 2016-05-25 13:30:00.048 GOOG 720.77 100 720.50 720.93
+ 3 2016-05-25 13:30:00.048 GOOG 720.92 100 720.50 720.93
+ 4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
+
+ We only asof within 10ms between the quote time and the trade time
+ and we exclude exact matches on time. However *prior* data will
+ propagate forward
+
+ >>> pd.merge_asof(
+ ... trades,
+ ... quotes,
+ ... on="time",
+ ... by="ticker",
+ ... tolerance=pd.Timedelta("10ms"),
+ ... allow_exact_matches=False,
+ ... )
+ time ticker price quantity bid ask
+ 0 2016-05-25 13:30:00.023 MSFT 51.95 75 NaN NaN
+ 1 2016-05-25 13:30:00.038 MSFT 51.95 155 51.97 51.98
+ 2 2016-05-25 13:30:00.048 GOOG 720.77 100 NaN NaN
+ 3 2016-05-25 13:30:00.048 GOOG 720.92 100 NaN NaN
+ 4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
+ """
+ op = _AsOfMerge(
+ left,
+ right,
+ on=on,
+ left_on=left_on,
+ right_on=right_on,
+ left_index=left_index,
+ right_index=right_index,
+ by=by,
+ left_by=left_by,
+ right_by=right_by,
+ suffixes=suffixes,
+ how="asof",
+ tolerance=tolerance,
+ allow_exact_matches=allow_exact_matches,
+ direction=direction,
+ )
+ return op.get_result()
+
+
+# TODO: transformations??
+class _MergeOperation:
+ """
+ Perform a database (SQL) merge operation between two DataFrame or Series
+ objects using either columns as keys or their row indexes
+ """
+
+ _merge_type = "merge"
+ how: JoinHow | Literal["asof"]
+ on: IndexLabel | None
+ # left_on/right_on may be None when passed, but in validate_specification
+ # get replaced with non-None.
+ left_on: Sequence[Hashable | AnyArrayLike]
+ right_on: Sequence[Hashable | AnyArrayLike]
+ left_index: bool
+ right_index: bool
+ sort: bool
+ suffixes: Suffixes
+ indicator: str | bool
+ validate: str | None
+ join_names: list[Hashable]
+ right_join_keys: list[ArrayLike]
+ left_join_keys: list[ArrayLike]
+
+ def __init__(
+ self,
+ left: DataFrame | Series,
+ right: DataFrame | Series,
+ how: JoinHow | Literal["left_anti", "right_anti", "asof"] = "inner",
+ on: IndexLabel | AnyArrayLike | None = None,
+ left_on: IndexLabel | AnyArrayLike | None = None,
+ right_on: IndexLabel | AnyArrayLike | None = None,
+ left_index: bool = False,
+ right_index: bool = False,
+ sort: bool = True,
+ suffixes: Suffixes = ("_x", "_y"),
+ indicator: str | bool = False,
+ validate: str | None = None,
+ ) -> None:
+ _left = _validate_operand(left)
+ _right = _validate_operand(right)
+ self.left = self.orig_left = _left
+ self.right = self.orig_right = _right
+ self.how, self.anti_join = self._validate_how(how)
+
+ self.on = com.maybe_make_list(on)
+
+ self.suffixes = suffixes
+ self.sort = sort or how == "outer"
+
+ self.left_index = left_index
+ self.right_index = right_index
+
+ self.indicator = indicator
+
+ if not is_bool(left_index):
+ raise ValueError(
+ f"left_index parameter must be of type bool, not {type(left_index)}"
+ )
+ if not is_bool(right_index):
+ raise ValueError(
+ f"right_index parameter must be of type bool, not {type(right_index)}"
+ )
+
+ # GH 40993: raise when merging between different levels; enforced in 2.0
+ if _left.columns.nlevels != _right.columns.nlevels:
+ msg = (
+ "Not allowed to merge between different levels. "
+ f"({_left.columns.nlevels} levels on the left, "
+ f"{_right.columns.nlevels} on the right)"
+ )
+ raise MergeError(msg)
+
+ self.left_on, self.right_on = self._validate_left_right_on(left_on, right_on)
+
+ (
+ self.left_join_keys,
+ self.right_join_keys,
+ self.join_names,
+ left_drop,
+ right_drop,
+ ) = self._get_merge_keys()
+
+ if left_drop:
+ self.left = self.left._drop_labels_or_levels(left_drop)
+
+ if right_drop:
+ self.right = self.right._drop_labels_or_levels(right_drop)
+
+ self._maybe_require_matching_dtypes(self.left_join_keys, self.right_join_keys)
+ self._validate_tolerance(self.left_join_keys)
+
+ # validate the merge keys dtypes. We may need to coerce
+ # to avoid incompatible dtypes
+ self._maybe_coerce_merge_keys()
+
+ # If argument passed to validate,
+ # check if columns specified as unique
+ # are in fact unique.
+ if validate is not None:
+ self._validate_validate_kwd(validate)
+
+ @final
+ def _validate_how(
+ self, how: JoinHow | Literal["left_anti", "right_anti", "asof"]
+ ) -> tuple[JoinHow | Literal["asof"], bool]:
+ """
+ Validate the 'how' parameter and return the actual join type and whether
+ this is an anti join.
+ """
+ # GH 59435: raise when "how" is not a valid Merge type
+ merge_type = {
+ "left",
+ "right",
+ "inner",
+ "outer",
+ "left_anti",
+ "right_anti",
+ "cross",
+ "asof",
+ }
+ if how not in merge_type:
+ raise ValueError(
+ f"'{how}' is not a valid Merge type: "
+ f"left, right, inner, outer, left_anti, right_anti, cross, asof"
+ )
+ anti_join = False
+ if how in {"left_anti", "right_anti"}:
+ how = how.split("_")[0] # type: ignore[assignment]
+ anti_join = True
+ how = cast(JoinHow | Literal["asof"], how)
+ return how, anti_join
+
+ def _maybe_require_matching_dtypes(
+ self, left_join_keys: list[ArrayLike], right_join_keys: list[ArrayLike]
+ ) -> None:
+ # Overridden by AsOfMerge
+ pass
+
+ def _validate_tolerance(self, left_join_keys: list[ArrayLike]) -> None:
+ # Overridden by AsOfMerge
+ pass
+
+ @final
+ def _reindex_and_concat(
+ self,
+ join_index: Index,
+ left_indexer: npt.NDArray[np.intp] | None,
+ right_indexer: npt.NDArray[np.intp] | None,
+ ) -> DataFrame:
+ """
+ reindex along index and concat along columns.
+ """
+ # Take views so we do not alter the originals
+ left = self.left[:]
+ right = self.right[:]
+
+ llabels, rlabels = _items_overlap_with_suffix(
+ self.left._info_axis, self.right._info_axis, self.suffixes
+ )
+
+ if left_indexer is not None and not is_range_indexer(left_indexer, len(left)):
+ # Pinning the index here (and in the right code just below) is not
+ # necessary, but makes the `.take` more performant if we have e.g.
+ # a MultiIndex for left.index.
+ lmgr = left._mgr.reindex_indexer(
+ join_index,
+ left_indexer,
+ axis=1,
+ only_slice=True,
+ allow_dups=True,
+ use_na_proxy=True,
+ )
+ left = left._constructor_from_mgr(lmgr, axes=lmgr.axes)
+ left.index = join_index
+
+ if right_indexer is not None and not is_range_indexer(
+ right_indexer, len(right)
+ ):
+ rmgr = right._mgr.reindex_indexer(
+ join_index,
+ right_indexer,
+ axis=1,
+ only_slice=True,
+ allow_dups=True,
+ use_na_proxy=True,
+ )
+ right = right._constructor_from_mgr(rmgr, axes=rmgr.axes)
+ right.index = join_index
+
+ from pandas import concat
+
+ left.columns = llabels
+ right.columns = rlabels
+ result = concat([left, right], axis=1)
+ return result
+
+ def get_result(self) -> DataFrame:
+ """
+ Execute the merge.
+ """
+ if self.indicator:
+ self.left, self.right = self._indicator_pre_merge(self.left, self.right)
+
+ join_index, left_indexer, right_indexer = self._get_join_info()
+
+ result = self._reindex_and_concat(join_index, left_indexer, right_indexer)
+
+ if self.indicator:
+ result = self._indicator_post_merge(result)
+
+ self._maybe_add_join_keys(result, left_indexer, right_indexer)
+
+ self._maybe_restore_index_levels(result)
+
+ return result.__finalize__(
+ types.SimpleNamespace(
+ input_objs=[self.left, self.right], left=self.left, right=self.right
+ ),
+ method="merge",
+ )
+
+ @final
+ @cache_readonly
+ def _indicator_name(self) -> str | None:
+ if isinstance(self.indicator, str):
+ return self.indicator
+ elif isinstance(self.indicator, bool):
+ return "_merge" if self.indicator else None
+ else:
+ raise ValueError(
+ "indicator option can only accept boolean or string arguments"
+ )
+
+ @final
+ def _indicator_pre_merge(
+ self, left: DataFrame, right: DataFrame
+ ) -> tuple[DataFrame, DataFrame]:
+ """
+ Add one indicator column to each of the left and right inputs.
+
+ These columns are used to produce another column in the output of the
+ merge, indicating for each row of the output whether it was produced
+ using the left, right or both inputs.
+ """
+ columns = left.columns.union(right.columns)
+
+ for i in ["_left_indicator", "_right_indicator"]:
+ if i in columns:
+ raise ValueError(
+ "Cannot use `indicator=True` option when "
+ f"data contains a column named {i}"
+ )
+ if self._indicator_name in columns:
+ raise ValueError(
+ "Cannot use name of an existing column for indicator column"
+ )
+
+ left = left.copy(deep=False)
+ right = right.copy(deep=False)
+
+ left["_left_indicator"] = 1
+ left["_left_indicator"] = left["_left_indicator"].astype("int8")
+
+ right["_right_indicator"] = 2
+ right["_right_indicator"] = right["_right_indicator"].astype("int8")
+
+ return left, right
+
+ @final
+ def _indicator_post_merge(self, result: DataFrame) -> DataFrame:
+ """
+ Add an indicator column to the merge result.
+
+ This column indicates for each row of the output whether it was produced using
+ the left, right or both inputs.
+ """
+ result["_left_indicator"] = result["_left_indicator"].fillna(0)
+ result["_right_indicator"] = result["_right_indicator"].fillna(0)
+
+ result[self._indicator_name] = Categorical(
+ (result["_left_indicator"] + result["_right_indicator"]),
+ categories=[1, 2, 3],
+ )
+ result[self._indicator_name] = result[
+ self._indicator_name
+ ].cat.rename_categories(["left_only", "right_only", "both"])
+
+ result = result.drop(labels=["_left_indicator", "_right_indicator"], axis=1)
+ return result
+
+ @final
+ def _maybe_restore_index_levels(self, result: DataFrame) -> None:
+ """
+ Restore index levels specified as `on` parameters
+
+ Here we check for cases where `self.left_on` and `self.right_on` pairs
+ each reference an index level in their respective DataFrames. The
+ joined columns corresponding to these pairs are then restored to the
+ index of `result`.
+
+ **Note:** This method has side effects. It modifies `result` in-place
+
+ Parameters
+ ----------
+ result: DataFrame
+ merge result
+
+ Returns
+ -------
+ None
+ """
+ names_to_restore = []
+ for name, left_key, right_key in zip(
+ self.join_names, self.left_on, self.right_on, strict=True
+ ):
+ if (
+ # Argument 1 to "_is_level_reference" of "NDFrame" has incompatible
+ # type "Union[Hashable, ExtensionArray, Index, Series]"; expected
+ # "Hashable"
+ self.orig_left._is_level_reference(left_key) # type: ignore[arg-type]
+ # Argument 1 to "_is_level_reference" of "NDFrame" has incompatible
+ # type "Union[Hashable, ExtensionArray, Index, Series]"; expected
+ # "Hashable"
+ and self.orig_right._is_level_reference(
+ right_key # type: ignore[arg-type]
+ )
+ and left_key == right_key
+ and name not in result.index.names
+ ):
+ names_to_restore.append(name)
+
+ if names_to_restore:
+ result.set_index(names_to_restore, inplace=True)
+
+ @final
+ def _maybe_add_join_keys(
+ self,
+ result: DataFrame,
+ left_indexer: npt.NDArray[np.intp] | None,
+ right_indexer: npt.NDArray[np.intp] | None,
+ ) -> None:
+ left_has_missing = None
+ right_has_missing = None
+
+ assert all(isinstance(x, _known) for x in self.left_join_keys)
+
+ keys = zip(self.join_names, self.left_on, self.right_on, strict=True)
+ for i, (name, lname, rname) in enumerate(keys):
+ if not _should_fill(lname, rname):
+ continue
+
+ take_left, take_right = None, None
+
+ if name in result:
+ if left_indexer is not None or right_indexer is not None:
+ if name in self.left:
+ if left_has_missing is None:
+ left_has_missing = (
+ False
+ if left_indexer is None
+ else (left_indexer == -1).any()
+ )
+
+ if left_has_missing:
+ take_right = self.right_join_keys[i]
+
+ if result[name].dtype != self.left[name].dtype:
+ take_left = self.left[name]._values
+
+ elif name in self.right:
+ if right_has_missing is None:
+ right_has_missing = (
+ False
+ if right_indexer is None
+ else (right_indexer == -1).any()
+ )
+
+ if right_has_missing:
+ take_left = self.left_join_keys[i]
+
+ if result[name].dtype != self.right[name].dtype:
+ take_right = self.right[name]._values
+
+ else:
+ take_left = self.left_join_keys[i]
+ take_right = self.right_join_keys[i]
+
+ if take_left is not None or take_right is not None:
+ if take_left is None:
+ lvals = result[name]._values
+ elif left_indexer is None:
+ lvals = take_left
+ else:
+ # TODO: can we pin down take_left's type earlier?
+ take_left = extract_array(take_left, extract_numpy=True)
+ lfill = na_value_for_dtype(take_left.dtype)
+ lvals = algos.take_nd(take_left, left_indexer, fill_value=lfill)
+
+ if take_right is None:
+ rvals = result[name]._values
+ elif right_indexer is None:
+ rvals = take_right
+ else:
+ # TODO: can we pin down take_right's type earlier?
+ taker = extract_array(take_right, extract_numpy=True)
+ rfill = na_value_for_dtype(taker.dtype)
+ rvals = algos.take_nd(taker, right_indexer, fill_value=rfill)
+
+ # if we have an all missing left_indexer
+ # make sure to just use the right values or vice-versa
+ if left_indexer is not None and (left_indexer == -1).all():
+ key_col = Index(rvals, dtype=rvals.dtype, copy=False)
+ result_dtype = rvals.dtype
+ elif right_indexer is not None and (right_indexer == -1).all():
+ key_col = Index(lvals, dtype=lvals.dtype, copy=False)
+ result_dtype = lvals.dtype
+ else:
+ key_col = Index(lvals, dtype=lvals.dtype, copy=False)
+ if left_indexer is not None:
+ mask_left = left_indexer == -1
+ key_col = key_col.where(~mask_left, rvals)
+ result_dtype = find_common_type([lvals.dtype, rvals.dtype])
+ if (
+ lvals.dtype.kind == "M"
+ and rvals.dtype.kind == "M"
+ and result_dtype.kind == "O"
+ ):
+ # TODO(non-nano) Workaround for common_type not dealing
+ # with different resolutions
+ result_dtype = key_col.dtype
+
+ if result._is_label_reference(name):
+ result[name] = result._constructor_sliced(
+ key_col, dtype=result_dtype, index=result.index
+ )
+ elif result._is_level_reference(name):
+ if isinstance(result.index, MultiIndex):
+ key_col.name = name
+ idx_list = [
+ (
+ result.index.get_level_values(level_name)
+ if level_name != name
+ else key_col
+ )
+ for level_name in result.index.names
+ ]
+
+ result.set_index(idx_list, inplace=True)
+ else:
+ result.index = Index(key_col, name=name)
+ else:
+ result.insert(i, name or f"key_{i}", key_col)
+
+ def _get_join_indexers(
+ self,
+ ) -> tuple[npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ """return the join indexers"""
+ # make mypy happy
+ assert self.how != "asof"
+ return get_join_indexers(
+ self.left_join_keys, self.right_join_keys, sort=self.sort, how=self.how
+ )
+
+ @final
+ def _get_join_info(
+ self,
+ ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ left_ax = self.left.index
+ right_ax = self.right.index
+
+ if self.left_index and self.right_index and self.how != "asof":
+ join_index, left_indexer, right_indexer = left_ax.join(
+ right_ax, how=self.how, return_indexers=True, sort=self.sort
+ )
+
+ elif self.right_index and self.how == "left":
+ join_index, left_indexer, right_indexer = _left_join_on_index(
+ left_ax, right_ax, self.left_join_keys, sort=self.sort
+ )
+
+ elif self.left_index and self.how == "right":
+ join_index, right_indexer, left_indexer = _left_join_on_index(
+ right_ax, left_ax, self.right_join_keys, sort=self.sort
+ )
+ else:
+ (left_indexer, right_indexer) = self._get_join_indexers()
+
+ if self.right_index:
+ if len(self.left) > 0:
+ join_index = self._create_join_index(
+ left_ax,
+ right_ax,
+ left_indexer,
+ how="right",
+ )
+ elif right_indexer is None:
+ join_index = right_ax.copy()
+ else:
+ join_index = right_ax.take(right_indexer)
+ elif self.left_index:
+ if self.how == "asof":
+ # GH#33463 asof should always behave like a left merge
+ join_index = self._create_join_index(
+ left_ax,
+ right_ax,
+ left_indexer,
+ how="left",
+ )
+
+ elif len(self.right) > 0:
+ join_index = self._create_join_index(
+ right_ax,
+ left_ax,
+ right_indexer,
+ how="left",
+ )
+ elif left_indexer is None:
+ join_index = left_ax.copy()
+ else:
+ join_index = left_ax.take(left_indexer)
+ else:
+ n = len(left_ax) if left_indexer is None else len(left_indexer)
+ join_index = default_index(n)
+
+ if self.anti_join:
+ join_index, left_indexer, right_indexer = self._handle_anti_join(
+ join_index, left_indexer, right_indexer
+ )
+
+ return join_index, left_indexer, right_indexer
+
+ @final
+ def _create_join_index(
+ self,
+ index: Index,
+ other_index: Index,
+ indexer: npt.NDArray[np.intp] | None,
+ how: JoinHow = "left",
+ ) -> Index:
+ """
+ Create a join index by rearranging one index to match another
+
+ Parameters
+ ----------
+ index : Index
+ index being rearranged
+ other_index : Index
+ used to supply values not found in index
+ indexer : np.ndarray[np.intp] or None
+ how to rearrange index
+ how : str
+ Replacement is only necessary if indexer based on other_index.
+
+ Returns
+ -------
+ Index
+ """
+ if self.how in (how, "outer") and not isinstance(other_index, MultiIndex):
+ # if final index requires values in other_index but not target
+ # index, indexer may hold missing (-1) values, causing Index.take
+ # to take the final value in target index. So, we set the last
+ # element to be the desired fill value. We do not use allow_fill
+ # and fill_value because it throws a ValueError on integer indices
+ mask = indexer == -1
+ if np.any(mask):
+ fill_value = na_value_for_dtype(index.dtype, compat=False)
+ if not index._can_hold_na:
+ new_index = Index([fill_value])
+ else:
+ new_index = Index([fill_value], dtype=index.dtype)
+ index = index.append(new_index)
+ if indexer is None:
+ return index.copy()
+ return index.take(indexer)
+
+ @final
+ def _handle_anti_join(
+ self,
+ join_index: Index,
+ left_indexer: npt.NDArray[np.intp] | None,
+ right_indexer: npt.NDArray[np.intp] | None,
+ ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ """
+ Handle anti join by returning the correct join index and indexers
+
+ Parameters
+ ----------
+ join_index : Index
+ join index
+ left_indexer : np.ndarray[np.intp] or None
+ left indexer
+ right_indexer : np.ndarray[np.intp] or None
+ right indexer
+
+ Returns
+ -------
+ Index, np.ndarray[np.intp] or None, np.ndarray[np.intp] or None
+ """
+ # Make sure indexers are not None
+ if left_indexer is None:
+ left_indexer = np.arange(len(self.left))
+ if right_indexer is None:
+ right_indexer = np.arange(len(self.right))
+
+ assert self.how in {"left", "right"}
+ if self.how == "left":
+ # Filter to rows where left keys are not in right keys
+ filt = right_indexer == -1
+ else:
+ # Filter to rows where right keys are not in left keys
+ filt = left_indexer == -1
+ join_index = join_index[filt]
+ left_indexer = left_indexer[filt]
+ right_indexer = right_indexer[filt]
+
+ return join_index, left_indexer, right_indexer
+
+ @final
+ def _get_merge_keys(
+ self,
+ ) -> tuple[
+ list[ArrayLike],
+ list[ArrayLike],
+ list[Hashable],
+ list[Hashable],
+ list[Hashable],
+ ]:
+ """
+ Returns
+ -------
+ left_keys, right_keys, join_names, left_drop, right_drop
+ """
+ left_keys: list[ArrayLike] = []
+ right_keys: list[ArrayLike] = []
+ join_names: list[Hashable] = []
+ right_drop: list[Hashable] = []
+ left_drop: list[Hashable] = []
+
+ left, right = self.left, self.right
+
+ is_lkey = lambda x: isinstance(x, _known) and len(x) == len(left)
+ is_rkey = lambda x: isinstance(x, _known) and len(x) == len(right)
+
+ # Note that pd.merge_asof() has separate 'on' and 'by' parameters. A
+ # user could, for example, request 'left_index' and 'left_by'. In a
+ # regular pd.merge(), users cannot specify both 'left_index' and
+ # 'left_on'. (Instead, users have a MultiIndex). That means the
+ # self.left_on in this function is always empty in a pd.merge(), but
+ # a pd.merge_asof(left_index=True, left_by=...) will result in a
+ # self.left_on array with a None in the middle of it. This requires
+ # a work-around as designated in the code below.
+ # See _validate_left_right_on() for where this happens.
+
+ # ugh, spaghetti re #733
+ if _any(self.left_on) and _any(self.right_on):
+ for lk, rk in zip(self.left_on, self.right_on, strict=True):
+ lk = extract_array(lk, extract_numpy=True)
+ rk = extract_array(rk, extract_numpy=True)
+ if is_lkey(lk):
+ lk = cast(ArrayLike, lk)
+ left_keys.append(lk)
+ if is_rkey(rk):
+ rk = cast(ArrayLike, rk)
+ right_keys.append(rk)
+ join_names.append(None) # what to do?
+ else:
+ # Then we're either Hashable or a wrong-length arraylike,
+ # the latter of which will raise
+ rk = cast(Hashable, rk)
+ if rk is not None:
+ right_keys.append(right._get_label_or_level_values(rk))
+ join_names.append(rk)
+ else:
+ # work-around for merge_asof(right_index=True)
+ right_keys.append(right.index._values)
+ join_names.append(right.index.name)
+ else:
+ if not is_rkey(rk):
+ # Then we're either Hashable or a wrong-length arraylike,
+ # the latter of which will raise
+ rk = cast(Hashable, rk)
+ if rk is not None:
+ right_keys.append(right._get_label_or_level_values(rk))
+ else:
+ # work-around for merge_asof(right_index=True)
+ right_keys.append(right.index._values)
+ if lk is not None and lk == rk: # FIXME: what about other NAs?
+ right_drop.append(rk)
+ else:
+ rk = cast(ArrayLike, rk)
+ right_keys.append(rk)
+ if lk is not None:
+ # Then we're either Hashable or a wrong-length arraylike,
+ # the latter of which will raise
+ lk = cast(Hashable, lk)
+ left_keys.append(left._get_label_or_level_values(lk))
+ join_names.append(lk)
+ else:
+ # work-around for merge_asof(left_index=True)
+ left_keys.append(left.index._values)
+ join_names.append(left.index.name)
+ elif _any(self.left_on):
+ for k in self.left_on:
+ if is_lkey(k):
+ k = extract_array(k, extract_numpy=True)
+ k = cast(ArrayLike, k)
+ left_keys.append(k)
+ join_names.append(None)
+ else:
+ # Then we're either Hashable or a wrong-length arraylike,
+ # the latter of which will raise
+ k = cast(Hashable, k)
+ left_keys.append(left._get_label_or_level_values(k))
+ join_names.append(k)
+ if isinstance(self.right.index, MultiIndex):
+ right_keys = [
+ lev._values.take(lev_codes)
+ for lev, lev_codes in zip(
+ self.right.index.levels, self.right.index.codes, strict=True
+ )
+ ]
+ else:
+ right_keys = [self.right.index._values]
+ elif _any(self.right_on):
+ for k in self.right_on:
+ k = extract_array(k, extract_numpy=True)
+ if is_rkey(k):
+ k = cast(ArrayLike, k)
+ right_keys.append(k)
+ join_names.append(None)
+ else:
+ # Then we're either Hashable or a wrong-length arraylike,
+ # the latter of which will raise
+ k = cast(Hashable, k)
+ right_keys.append(right._get_label_or_level_values(k))
+ join_names.append(k)
+ if isinstance(self.left.index, MultiIndex):
+ left_keys = [
+ lev._values.take(lev_codes)
+ for lev, lev_codes in zip(
+ self.left.index.levels, self.left.index.codes, strict=True
+ )
+ ]
+ else:
+ left_keys = [self.left.index._values]
+
+ return left_keys, right_keys, join_names, left_drop, right_drop
+
+ @final
+ def _maybe_coerce_merge_keys(self) -> None:
+ # we have valid merges but we may have to further
+ # coerce these if they are originally incompatible types
+ #
+ # for example if these are categorical, but are not dtype_equal
+ # or if we have object and integer dtypes
+
+ for lk, rk, name in zip(
+ self.left_join_keys, self.right_join_keys, self.join_names, strict=True
+ ):
+ if (len(lk) and not len(rk)) or (not len(lk) and len(rk)):
+ continue
+
+ lk = extract_array(lk, extract_numpy=True)
+ rk = extract_array(rk, extract_numpy=True)
+
+ lk_is_cat = isinstance(lk.dtype, CategoricalDtype)
+ rk_is_cat = isinstance(rk.dtype, CategoricalDtype)
+ lk_is_object_or_string = is_object_dtype(lk.dtype) or is_string_dtype(
+ lk.dtype
+ )
+ rk_is_object_or_string = is_object_dtype(rk.dtype) or is_string_dtype(
+ rk.dtype
+ )
+
+ # if either left or right is a categorical
+ # then the must match exactly in categories & ordered
+ if lk_is_cat and rk_is_cat:
+ lk = cast(Categorical, lk)
+ rk = cast(Categorical, rk)
+ if lk._categories_match_up_to_permutation(rk):
+ continue
+
+ elif lk_is_cat or rk_is_cat:
+ pass
+
+ elif lk.dtype == rk.dtype:
+ continue
+
+ msg = (
+ f"You are trying to merge on {lk.dtype} and {rk.dtype} columns "
+ f"for key '{name}'. If you wish to proceed you should use pd.concat"
+ )
+
+ # if we are numeric, then allow differing
+ # kinds to proceed, eg. int64 and int8, int and float
+ # further if we are object, but we infer to
+ # the same, then proceed
+ if is_numeric_dtype(lk.dtype) and is_numeric_dtype(rk.dtype):
+ if lk.dtype.kind == rk.dtype.kind:
+ continue
+
+ if isinstance(lk.dtype, ExtensionDtype) and not isinstance(
+ rk.dtype, ExtensionDtype
+ ):
+ ct = find_common_type([lk.dtype, rk.dtype])
+ if isinstance(ct, ExtensionDtype):
+ com_cls = ct.construct_array_type()
+ rk = com_cls._from_sequence(rk, dtype=ct, copy=False)
+ else:
+ rk = rk.astype(ct)
+ elif isinstance(rk.dtype, ExtensionDtype):
+ ct = find_common_type([lk.dtype, rk.dtype])
+ if isinstance(ct, ExtensionDtype):
+ com_cls = ct.construct_array_type()
+ lk = com_cls._from_sequence(lk, dtype=ct, copy=False)
+ else:
+ lk = lk.astype(ct)
+
+ # check whether ints and floats
+ if is_integer_dtype(rk.dtype) and is_float_dtype(lk.dtype):
+ # GH 47391 numpy > 1.24 will raise a RuntimeError for nan -> int
+ with np.errstate(invalid="ignore"):
+ # error: Argument 1 to "astype" of "ndarray" has incompatible
+ # type "Union[ExtensionDtype, Any, dtype[Any]]"; expected
+ # "Union[dtype[Any], Type[Any], _SupportsDType[dtype[Any]]]"
+ casted = lk.astype(rk.dtype) # type: ignore[arg-type]
+
+ mask = ~np.isnan(lk)
+ match = lk == casted
+ # error: Item "ExtensionArray" of
+ # "ExtensionArray | Any" has no attribute "all"
+ if not match[mask].all(): # type: ignore[union-attr]
+ warnings.warn(
+ "You are merging on int and float "
+ "columns where the float values "
+ "are not equal to their int representation.",
+ UserWarning,
+ stacklevel=find_stack_level(),
+ )
+ continue
+
+ if is_float_dtype(rk.dtype) and is_integer_dtype(lk.dtype):
+ # GH 47391 numpy > 1.24 will raise a RuntimeError for nan -> int
+ with np.errstate(invalid="ignore"):
+ # error: Argument 1 to "astype" of "ndarray" has incompatible
+ # type "Union[ExtensionDtype, Any, dtype[Any]]"; expected
+ # "Union[dtype[Any], Type[Any], _SupportsDType[dtype[Any]]]"
+ casted = rk.astype(lk.dtype) # type: ignore[arg-type]
+
+ mask = ~np.isnan(rk)
+ match = rk == casted
+ # error: Item "ExtensionArray" of
+ # "ExtensionArray | Any" has no attribute "all"
+ if not match[mask].all(): # type: ignore[union-attr]
+ warnings.warn(
+ "You are merging on int and float "
+ "columns where the float values "
+ "are not equal to their int representation.",
+ UserWarning,
+ stacklevel=find_stack_level(),
+ )
+ continue
+
+ # let's infer and see if we are ok
+ if lib.infer_dtype(lk, skipna=False) == lib.infer_dtype(
+ rk, skipna=False
+ ):
+ continue
+
+ # Check if we are trying to merge on obviously
+ # incompatible dtypes GH 9780, GH 15800
+
+ # bool values are coerced to object
+ elif (lk_is_object_or_string and is_bool_dtype(rk.dtype)) or (
+ is_bool_dtype(lk.dtype) and rk_is_object_or_string
+ ):
+ pass
+
+ # object values are allowed to be merged
+ elif (lk_is_object_or_string and is_numeric_dtype(rk.dtype)) or (
+ is_numeric_dtype(lk.dtype) and rk_is_object_or_string
+ ):
+ inferred_left = lib.infer_dtype(lk, skipna=False)
+ inferred_right = lib.infer_dtype(rk, skipna=False)
+ bool_types = ["integer", "mixed-integer", "boolean", "empty"]
+ string_types = ["string", "unicode", "mixed", "bytes", "empty"]
+
+ # inferred bool
+ if inferred_left in bool_types and inferred_right in bool_types:
+ pass
+
+ # unless we are merging non-string-like with string-like
+ elif (
+ inferred_left in string_types and inferred_right not in string_types
+ ) or (
+ inferred_right in string_types and inferred_left not in string_types
+ ):
+ raise ValueError(msg)
+
+ # datetimelikes must match exactly
+ elif needs_i8_conversion(lk.dtype) and not needs_i8_conversion(rk.dtype):
+ raise ValueError(msg)
+ elif not needs_i8_conversion(lk.dtype) and needs_i8_conversion(rk.dtype):
+ raise ValueError(msg)
+ elif isinstance(lk.dtype, DatetimeTZDtype) and not isinstance(
+ rk.dtype, DatetimeTZDtype
+ ):
+ raise ValueError(msg)
+ elif not isinstance(lk.dtype, DatetimeTZDtype) and isinstance(
+ rk.dtype, DatetimeTZDtype
+ ):
+ raise ValueError(msg)
+ elif (
+ isinstance(lk.dtype, DatetimeTZDtype)
+ and isinstance(rk.dtype, DatetimeTZDtype)
+ ) or (lk.dtype.kind == "M" and rk.dtype.kind == "M"):
+ # allows datetime with different resolutions
+ continue
+ # datetime and timedelta not allowed
+ elif lk.dtype.kind == "M" and rk.dtype.kind == "m":
+ raise ValueError(msg)
+ elif lk.dtype.kind == "m" and rk.dtype.kind == "M":
+ raise ValueError(msg)
+
+ elif is_object_dtype(lk.dtype) and is_object_dtype(rk.dtype):
+ continue
+
+ # Houston, we have a problem!
+ # let's coerce to object if the dtypes aren't
+ # categorical, otherwise coerce to the category
+ # dtype. If we coerced categories to object,
+ # then we would lose type information on some
+ # columns, and end up trying to merge
+ # incompatible dtypes. See GH 16900.
+ if name in self.left.columns:
+ typ = cast(Categorical, lk).categories.dtype if lk_is_cat else object
+ self.left = self.left.copy(deep=False)
+ self.left[name] = self.left[name].astype(typ)
+ if name in self.right.columns:
+ typ = cast(Categorical, rk).categories.dtype if rk_is_cat else object
+ self.right = self.right.copy(deep=False)
+ self.right[name] = self.right[name].astype(typ)
+
+ def _validate_left_right_on(self, left_on, right_on):
+ left_on = com.maybe_make_list(left_on)
+ right_on = com.maybe_make_list(right_on)
+
+ # Hm, any way to make this logic less complicated??
+ if self.on is None and left_on is None and right_on is None:
+ if self.left_index and self.right_index:
+ left_on, right_on = (), ()
+ elif self.left_index:
+ raise MergeError("Must pass right_on or right_index=True")
+ elif self.right_index:
+ raise MergeError("Must pass left_on or left_index=True")
+ else:
+ # use the common columns
+ left_cols = self.left.columns
+ right_cols = self.right.columns
+ common_cols = left_cols.intersection(right_cols)
+ if len(common_cols) == 0:
+ raise MergeError(
+ "No common columns to perform merge on. "
+ f"Merge options: left_on={left_on}, "
+ f"right_on={right_on}, "
+ f"left_index={self.left_index}, "
+ f"right_index={self.right_index}"
+ )
+ if (
+ not left_cols.join(common_cols, how="inner").is_unique
+ or not right_cols.join(common_cols, how="inner").is_unique
+ ):
+ raise MergeError(f"Data columns not unique: {common_cols!r}")
+ left_on = right_on = common_cols
+ elif self.on is not None:
+ if left_on is not None or right_on is not None:
+ raise MergeError(
+ 'Can only pass argument "on" OR "left_on" '
+ 'and "right_on", not a combination of both.'
+ )
+ if self.left_index or self.right_index:
+ raise MergeError(
+ 'Can only pass argument "on" OR "left_index" '
+ 'and "right_index", not a combination of both.'
+ )
+ left_on = right_on = self.on
+ elif left_on is not None:
+ if self.left_index:
+ raise MergeError(
+ 'Can only pass argument "left_on" OR "left_index" not both.'
+ )
+ if not self.right_index and right_on is None:
+ raise MergeError('Must pass "right_on" OR "right_index".')
+ if self.right_index and right_on is not None:
+ raise MergeError(
+ 'Can only pass argument "right_on" OR "right_index" not both.'
+ )
+ n = len(left_on)
+ if self.right_index:
+ if len(left_on) != self.right.index.nlevels:
+ raise ValueError(
+ "len(left_on) must equal the number "
+ 'of levels in the index of "right"'
+ )
+ right_on = [None] * n
+ elif right_on is not None:
+ if self.right_index:
+ raise MergeError(
+ 'Can only pass argument "right_on" OR "right_index" not both.'
+ )
+ if not self.left_index and left_on is None:
+ raise MergeError('Must pass "left_on" OR "left_index".')
+ n = len(right_on)
+ if self.left_index:
+ if len(right_on) != self.left.index.nlevels:
+ raise ValueError(
+ "len(right_on) must equal the number "
+ 'of levels in the index of "left"'
+ )
+ left_on = [None] * n
+ if len(right_on) != len(left_on):
+ raise ValueError("len(right_on) must equal len(left_on)")
+
+ return left_on, right_on
+
+ @final
+ def _validate_validate_kwd(self, validate: str) -> None:
+ # Check uniqueness of each
+ if self.left_index:
+ left_join_index = self.orig_left.index
+ left_unique = left_join_index.is_unique
+ else:
+ left_join_index = MultiIndex.from_arrays(self.left_join_keys)
+ left_unique = left_join_index.is_unique
+
+ if self.right_index:
+ right_join_index = self.orig_right.index
+ right_unique = self.orig_right.index.is_unique
+ else:
+ right_join_index = MultiIndex.from_arrays(self.right_join_keys)
+ right_unique = right_join_index.is_unique
+
+ def left_error_msg(x: Index) -> str:
+ name = self.left_on if not self.left_index else lib.no_default
+ msg = x[x.duplicated()][:5].to_frame(name=name).to_string(index=False)
+ return f"\nDuplicates in left:\n {msg} ..."
+
+ def right_error_msg(x: Index) -> str:
+ name = self.right_on if not self.right_index else lib.no_default
+ msg = x[x.duplicated()][:5].to_frame(name=name).to_string(index=False)
+ return f"\nDuplicates in right:\n {msg} ..."
+
+ # Check data integrity
+ if validate in ["one_to_one", "1:1"]:
+ if not left_unique and not right_unique:
+ raise MergeError(
+ "Merge keys are not unique in either left "
+ "or right dataset; not a one-to-one merge."
+ f"{left_error_msg(left_join_index)}"
+ f"{right_error_msg(right_join_index)}"
+ )
+ if not left_unique:
+ raise MergeError(
+ "Merge keys are not unique in left dataset; not a one-to-one merge"
+ f"{left_error_msg(left_join_index)}"
+ )
+ if not right_unique:
+ raise MergeError(
+ "Merge keys are not unique in right dataset; not a one-to-one merge"
+ f"{right_error_msg(right_join_index)}"
+ )
+
+ elif validate in ["one_to_many", "1:m"]:
+ if not left_unique:
+ raise MergeError(
+ "Merge keys are not unique in left dataset; not a one-to-many merge"
+ f"{left_error_msg(left_join_index)}"
+ )
+
+ elif validate in ["many_to_one", "m:1"]:
+ if not right_unique:
+ raise MergeError(
+ "Merge keys are not unique in right dataset; "
+ "not a many-to-one merge\n"
+ f"{right_error_msg(right_join_index)}"
+ )
+
+ elif validate in ["many_to_many", "m:m"]:
+ pass
+
+ else:
+ raise ValueError(
+ f'"{validate}" is not a valid argument. '
+ "Valid arguments are:\n"
+ '- "1:1"\n'
+ '- "1:m"\n'
+ '- "m:1"\n'
+ '- "m:m"\n'
+ '- "one_to_one"\n'
+ '- "one_to_many"\n'
+ '- "many_to_one"\n'
+ '- "many_to_many"'
+ )
+
+
+def get_join_indexers(
+ left_keys: list[ArrayLike],
+ right_keys: list[ArrayLike],
+ sort: bool = False,
+ how: JoinHow = "inner",
+) -> tuple[npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
+ """
+
+ Parameters
+ ----------
+ left_keys : list[ndarray, ExtensionArray, Index, Series]
+ right_keys : list[ndarray, ExtensionArray, Index, Series]
+ sort : bool, default False
+ how : {'inner', 'outer', 'left', 'right'}, default 'inner'
+
+ Returns
+ -------
+ np.ndarray[np.intp] or None
+ Indexer into the left_keys.
+ np.ndarray[np.intp] or None
+ Indexer into the right_keys.
+ """
+ assert len(left_keys) == len(right_keys), (
+ "left_keys and right_keys must be the same length"
+ )
+
+ # fast-path for empty left/right
+ left_n = len(left_keys[0])
+ right_n = len(right_keys[0])
+ if left_n == 0:
+ if how in ["left", "inner"]:
+ return _get_empty_indexer()
+ elif not sort and how in ["right", "outer"]:
+ return _get_no_sort_one_missing_indexer(right_n, True)
+ elif right_n == 0:
+ if how in ["right", "inner"]:
+ return _get_empty_indexer()
+ elif not sort and how in ["left", "outer"]:
+ return _get_no_sort_one_missing_indexer(left_n, False)
+
+ lkey: ArrayLike
+ rkey: ArrayLike
+ if len(left_keys) > 1:
+ # get left & right join labels and num. of levels at each location
+ mapped = (
+ _factorize_keys(left_keys[n], right_keys[n], sort=sort)
+ for n in range(len(left_keys))
+ )
+ zipped = zip(*mapped, strict=True)
+ llab, rlab, shape = (list(x) for x in zipped)
+
+ # get flat i8 keys from label lists
+ lkey, rkey = _get_join_keys(llab, rlab, tuple(shape), sort)
+ else:
+ lkey = left_keys[0]
+ rkey = right_keys[0]
+
+ left = Index(lkey, copy=False)
+ right = Index(rkey, copy=False)
+
+ if (
+ left.is_monotonic_increasing
+ and right.is_monotonic_increasing
+ and (left.is_unique or right.is_unique)
+ ):
+ _, lidx, ridx = left.join(right, how=how, return_indexers=True, sort=sort)
+ else:
+ lidx, ridx = get_join_indexers_non_unique(
+ left._values, right._values, sort, how
+ )
+
+ if lidx is not None and is_range_indexer(lidx, len(left)):
+ lidx = None
+ if ridx is not None and is_range_indexer(ridx, len(right)):
+ ridx = None
+ return lidx, ridx
+
+
+def get_join_indexers_non_unique(
+ left: ArrayLike,
+ right: ArrayLike,
+ sort: bool = False,
+ how: JoinHow = "inner",
+) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ """
+ Get join indexers for left and right.
+
+ Parameters
+ ----------
+ left : ArrayLike
+ right : ArrayLike
+ sort : bool, default False
+ how : {'inner', 'outer', 'left', 'right'}, default 'inner'
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ Indexer into left.
+ np.ndarray[np.intp]
+ Indexer into right.
+ """
+ lkey, rkey, count = _factorize_keys(left, right, sort=sort, how=how)
+ if count == -1:
+ # hash join
+ return lkey, rkey
+ if how == "left":
+ lidx, ridx = libjoin.left_outer_join(lkey, rkey, count, sort=sort)
+ elif how == "right":
+ ridx, lidx = libjoin.left_outer_join(rkey, lkey, count, sort=sort)
+ elif how == "inner":
+ lidx, ridx = libjoin.inner_join(lkey, rkey, count, sort=sort)
+ elif how == "outer":
+ lidx, ridx = libjoin.full_outer_join(lkey, rkey, count)
+ return lidx, ridx
+
+
+def restore_dropped_levels_multijoin(
+ left: MultiIndex,
+ right: MultiIndex,
+ dropped_level_names,
+ join_index: Index,
+ lindexer: npt.NDArray[np.intp],
+ rindexer: npt.NDArray[np.intp],
+) -> tuple[FrozenList, FrozenList, FrozenList]:
+ """
+ *this is an internal non-public method*
+
+ Returns the levels, labels and names of a multi-index to multi-index join.
+ Depending on the type of join, this method restores the appropriate
+ dropped levels of the joined multi-index.
+ The method relies on lindexer, rindexer which hold the index positions of
+ left and right, where a join was feasible
+
+ Parameters
+ ----------
+ left : MultiIndex
+ left index
+ right : MultiIndex
+ right index
+ dropped_level_names : str array
+ list of non-common level names
+ join_index : Index
+ the index of the join between the
+ common levels of left and right
+ lindexer : np.ndarray[np.intp]
+ left indexer
+ rindexer : np.ndarray[np.intp]
+ right indexer
+
+ Returns
+ -------
+ levels : list of Index
+ levels of combined multiindexes
+ labels : np.ndarray[np.intp]
+ labels of combined multiindexes
+ names : List[Hashable]
+ names of combined multiindex levels
+
+ """
+
+ def _convert_to_multiindex(index: Index) -> MultiIndex:
+ if isinstance(index, MultiIndex):
+ return index
+ else:
+ return MultiIndex.from_arrays([index._values], names=[index.name])
+
+ # For multi-multi joins with one overlapping level,
+ # the returned index if of type Index
+ # Assure that join_index is of type MultiIndex
+ # so that dropped levels can be appended
+ join_index = _convert_to_multiindex(join_index)
+
+ join_levels = join_index.levels
+ join_codes = join_index.codes
+ join_names = join_index.names
+
+ # Iterate through the levels that must be restored
+ for dropped_level_name in dropped_level_names:
+ if dropped_level_name in left.names:
+ idx = left
+ indexer = lindexer
+ else:
+ idx = right
+ indexer = rindexer
+
+ # The index of the level name to be restored
+ name_idx = idx.names.index(dropped_level_name)
+
+ restore_levels = idx.levels[name_idx]
+ # Inject -1 in the codes list where a join was not possible
+ # IOW indexer[i]=-1
+ codes = idx.codes[name_idx]
+ if indexer is None:
+ restore_codes = codes
+ else:
+ restore_codes = algos.take_nd(codes, indexer, fill_value=-1)
+
+ # Use + operator: FrozenList.__add__ returns FrozenList, unpacking returns list
+ join_levels = join_levels + [restore_levels] # noqa: RUF005
+ join_codes = join_codes + [restore_codes] # noqa: RUF005
+ join_names = join_names + [dropped_level_name] # noqa: RUF005
+
+ return join_levels, join_codes, join_names
+
+
+class _OrderedMerge(_MergeOperation):
+ _merge_type = "ordered_merge"
+
+ def __init__(
+ self,
+ left: DataFrame | Series,
+ right: DataFrame | Series,
+ on: IndexLabel | None = None,
+ left_on: IndexLabel | None = None,
+ right_on: IndexLabel | None = None,
+ left_index: bool = False,
+ right_index: bool = False,
+ suffixes: Suffixes = ("_x", "_y"),
+ fill_method: str | None = None,
+ how: JoinHow | Literal["asof"] = "outer",
+ ) -> None:
+ self.fill_method = fill_method
+ _MergeOperation.__init__(
+ self,
+ left,
+ right,
+ on=on,
+ left_on=left_on,
+ left_index=left_index,
+ right_index=right_index,
+ right_on=right_on,
+ how=how,
+ suffixes=suffixes,
+ sort=True, # factorize sorts
+ )
+
+ def get_result(self) -> DataFrame:
+ join_index, left_indexer, right_indexer = self._get_join_info()
+
+ left_join_indexer: npt.NDArray[np.intp] | None
+ right_join_indexer: npt.NDArray[np.intp] | None
+
+ if self.fill_method == "ffill":
+ if left_indexer is None:
+ left_join_indexer = None
+ else:
+ left_join_indexer = libjoin.ffill_indexer(left_indexer)
+ if right_indexer is None:
+ right_join_indexer = None
+ else:
+ right_join_indexer = libjoin.ffill_indexer(right_indexer)
+ elif self.fill_method is None:
+ left_join_indexer = left_indexer
+ right_join_indexer = right_indexer
+ else:
+ raise ValueError("fill_method must be 'ffill' or None")
+
+ result = self._reindex_and_concat(
+ join_index, left_join_indexer, right_join_indexer
+ )
+ self._maybe_add_join_keys(result, left_indexer, right_indexer)
+
+ return result
+
+
+def _asof_by_function(direction: str):
+ name = f"asof_join_{direction}_on_X_by_Y"
+ return getattr(libjoin, name, None)
+
+
+class _AsOfMerge(_OrderedMerge):
+ _merge_type = "asof_merge"
+
+ def __init__(
+ self,
+ left: DataFrame | Series,
+ right: DataFrame | Series,
+ on: IndexLabel | None = None,
+ left_on: IndexLabel | None = None,
+ right_on: IndexLabel | None = None,
+ left_index: bool = False,
+ right_index: bool = False,
+ by=None,
+ left_by=None,
+ right_by=None,
+ suffixes: Suffixes = ("_x", "_y"),
+ how: Literal["asof"] = "asof",
+ tolerance=None,
+ allow_exact_matches: bool = True,
+ direction: str = "backward",
+ ) -> None:
+ self.by = by
+ self.left_by = left_by
+ self.right_by = right_by
+ self.tolerance = tolerance
+ self.allow_exact_matches = allow_exact_matches
+ self.direction = direction
+
+ # check 'direction' is valid
+ if self.direction not in ["backward", "forward", "nearest"]:
+ raise MergeError(f"direction invalid: {self.direction}")
+
+ # validate allow_exact_matches
+ if not is_bool(self.allow_exact_matches):
+ msg = (
+ "allow_exact_matches must be boolean, "
+ f"passed {self.allow_exact_matches}"
+ )
+ raise MergeError(msg)
+
+ _OrderedMerge.__init__(
+ self,
+ left,
+ right,
+ on=on,
+ left_on=left_on,
+ right_on=right_on,
+ left_index=left_index,
+ right_index=right_index,
+ how=how,
+ suffixes=suffixes,
+ fill_method=None,
+ )
+
+ def _validate_left_right_on(self, left_on, right_on):
+ left_on, right_on = super()._validate_left_right_on(left_on, right_on)
+
+ # we only allow on to be a single item for on
+ if len(left_on) != 1 and not self.left_index:
+ raise MergeError("can only asof on a key for left")
+
+ if len(right_on) != 1 and not self.right_index:
+ raise MergeError("can only asof on a key for right")
+
+ if self.left_index and isinstance(self.left.index, MultiIndex):
+ raise MergeError("left can only have one index")
+
+ if self.right_index and isinstance(self.right.index, MultiIndex):
+ raise MergeError("right can only have one index")
+
+ # set 'by' columns
+ if self.by is not None:
+ if self.left_by is not None or self.right_by is not None:
+ raise MergeError("Can only pass by OR left_by and right_by")
+ self.left_by = self.right_by = self.by
+ if self.left_by is None and self.right_by is not None:
+ raise MergeError("missing left_by")
+ if self.left_by is not None and self.right_by is None:
+ raise MergeError("missing right_by")
+
+ # GH#29130 Check that merge keys do not have dtype object
+ if not self.left_index:
+ left_on_0 = left_on[0]
+ if isinstance(left_on_0, _known):
+ lo_dtype = left_on_0.dtype
+ else:
+ lo_dtype = (
+ self.left._get_label_or_level_values(left_on_0).dtype
+ if left_on_0 in self.left.columns
+ else self.left.index.get_level_values(left_on_0)
+ )
+ else:
+ lo_dtype = self.left.index.dtype
+
+ if not self.right_index:
+ right_on_0 = right_on[0]
+ if isinstance(right_on_0, _known):
+ ro_dtype = right_on_0.dtype
+ else:
+ ro_dtype = (
+ self.right._get_label_or_level_values(right_on_0).dtype
+ if right_on_0 in self.right.columns
+ else self.right.index.get_level_values(right_on_0)
+ )
+ else:
+ ro_dtype = self.right.index.dtype
+
+ if (
+ is_object_dtype(lo_dtype)
+ or is_object_dtype(ro_dtype)
+ or is_string_dtype(lo_dtype)
+ or is_string_dtype(ro_dtype)
+ ):
+ raise MergeError(
+ f"Incompatible merge dtype, {lo_dtype!r} and "
+ f"{ro_dtype!r}, both sides must have numeric dtype"
+ )
+
+ # add 'by' to our key-list so we can have it in the
+ # output as a key
+ if self.left_by is not None:
+ if not is_list_like(self.left_by):
+ self.left_by = [self.left_by]
+ if not is_list_like(self.right_by):
+ self.right_by = [self.right_by]
+
+ if len(self.left_by) != len(self.right_by):
+ raise MergeError("left_by and right_by must be the same length")
+
+ left_on = self.left_by + list(left_on)
+ right_on = self.right_by + list(right_on)
+
+ return left_on, right_on
+
+ def _maybe_require_matching_dtypes(
+ self, left_join_keys: list[ArrayLike], right_join_keys: list[ArrayLike]
+ ) -> None:
+ # TODO: why do we do this for AsOfMerge but not the others?
+
+ def _check_dtype_match(left: ArrayLike, right: ArrayLike, i: int) -> None:
+ if left.dtype != right.dtype:
+ if isinstance(left.dtype, CategoricalDtype) and isinstance(
+ right.dtype, CategoricalDtype
+ ):
+ # The generic error message is confusing for categoricals.
+ #
+ # In this function, the join keys include both the original
+ # ones of the merge_asof() call, and also the keys passed
+ # to its by= argument. Unordered but equal categories
+ # are not supported for the former, but will fail
+ # later with a ValueError, so we don't *need* to check
+ # for them here.
+ msg = (
+ f"incompatible merge keys [{i}] {left.dtype!r} and "
+ f"{right.dtype!r}, both sides category, but not equal ones"
+ )
+ else:
+ msg = (
+ f"incompatible merge keys [{i}] {left.dtype!r} and "
+ f"{right.dtype!r}, must be the same type"
+ )
+ raise MergeError(msg)
+
+ # validate index types are the same
+ for i, (lk, rk) in enumerate(zip(left_join_keys, right_join_keys, strict=True)):
+ _check_dtype_match(lk, rk, i)
+
+ if self.left_index:
+ lt = self.left.index._values
+ else:
+ lt = left_join_keys[-1]
+
+ if self.right_index:
+ rt = self.right.index._values
+ else:
+ rt = right_join_keys[-1]
+
+ _check_dtype_match(lt, rt, 0)
+
+ def _validate_tolerance(self, left_join_keys: list[ArrayLike]) -> None:
+ # validate tolerance; datetime.timedelta or Timedelta if we have a DTI
+ if self.tolerance is not None:
+ if self.left_index:
+ lt = self.left.index._values
+ else:
+ lt = left_join_keys[-1]
+
+ msg = (
+ f"incompatible tolerance {self.tolerance}, must be compat "
+ f"with type {lt.dtype!r}"
+ )
+
+ if needs_i8_conversion(lt.dtype) or (
+ isinstance(lt, ArrowExtensionArray) and lt.dtype.kind in "mM"
+ ):
+ if not isinstance(self.tolerance, datetime.timedelta):
+ raise MergeError(msg)
+ if self.tolerance < Timedelta(0):
+ raise MergeError("tolerance must be positive")
+
+ elif is_integer_dtype(lt.dtype):
+ if not is_integer(self.tolerance):
+ raise MergeError(msg)
+ if self.tolerance < 0:
+ raise MergeError("tolerance must be positive")
+
+ elif is_float_dtype(lt.dtype):
+ if not is_number(self.tolerance):
+ raise MergeError(msg)
+ # error: Unsupported operand types for > ("int" and "Number")
+ if self.tolerance < 0: # type: ignore[operator]
+ raise MergeError("tolerance must be positive")
+
+ else:
+ raise MergeError("key must be integer, timestamp or float")
+
+ def _convert_values_for_libjoin(
+ self, values: AnyArrayLike, side: str
+ ) -> np.ndarray:
+ # we require sortedness and non-null values in the join keys
+ if not Index(values, copy=False).is_monotonic_increasing:
+ if isna(values).any():
+ raise ValueError(f"Merge keys contain null values on {side} side")
+ raise ValueError(f"{side} keys must be sorted")
+
+ if isinstance(values, ArrowExtensionArray):
+ values = values._maybe_convert_datelike_array()
+
+ if needs_i8_conversion(values.dtype):
+ values = values.view("i8")
+
+ elif isinstance(values, BaseMaskedArray):
+ # we've verified above that no nulls exist
+ values = values._data
+ elif isinstance(values, ExtensionArray):
+ values = values.to_numpy()
+
+ # error: Incompatible return value type (got "Union[ExtensionArray,
+ # Any, ndarray[Any, Any], ndarray[Any, dtype[Any]], Index, Series]",
+ # expected "ndarray[Any, Any]")
+ return values # type: ignore[return-value]
+
+ def _get_join_indexers(self) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ """return the join indexers"""
+
+ # values to compare
+ left_values = (
+ self.left.index._values if self.left_index else self.left_join_keys[-1]
+ )
+ right_values = (
+ self.right.index._values if self.right_index else self.right_join_keys[-1]
+ )
+
+ # _maybe_require_matching_dtypes already checked for dtype matching
+ assert left_values.dtype == right_values.dtype
+
+ tolerance = self.tolerance
+ if tolerance is not None:
+ # TODO: can we reuse a tolerance-conversion function from
+ # e.g. TimedeltaIndex?
+ if needs_i8_conversion(left_values.dtype) or (
+ isinstance(left_values, ArrowExtensionArray)
+ and left_values.dtype.kind in "mM"
+ ):
+ tolerance = Timedelta(tolerance)
+ # TODO: we have no test cases with PeriodDtype here; probably
+ # need to adjust tolerance for that case.
+ if left_values.dtype.kind in "mM":
+ # Make sure the i8 representation for tolerance
+ # matches that for left_values/right_values.
+ if isinstance(left_values, ArrowExtensionArray):
+ unit = left_values.dtype.pyarrow_dtype.unit
+ else:
+ unit = ensure_wrapped_if_datetimelike(left_values).unit
+ tolerance = tolerance.as_unit(unit)
+
+ tolerance = tolerance._value
+
+ # initial type conversion as needed
+ left_values = self._convert_values_for_libjoin(left_values, "left")
+ right_values = self._convert_values_for_libjoin(right_values, "right")
+
+ # a "by" parameter requires special handling
+ if self.left_by is not None:
+ # remove 'on' parameter from values if one existed
+ if self.left_index and self.right_index:
+ left_join_keys = self.left_join_keys
+ right_join_keys = self.right_join_keys
+ else:
+ left_join_keys = self.left_join_keys[0:-1]
+ right_join_keys = self.right_join_keys[0:-1]
+
+ mapped = [
+ _factorize_keys(
+ left_join_keys[n],
+ right_join_keys[n],
+ sort=False,
+ )
+ for n in range(len(left_join_keys))
+ ]
+
+ if len(left_join_keys) == 1:
+ left_by_values = mapped[0][0]
+ right_by_values = mapped[0][1]
+ else:
+ arrs = [np.concatenate(m[:2]) for m in mapped]
+ shape = tuple(m[2] for m in mapped)
+ group_index = get_group_index(
+ arrs, shape=shape, sort=False, xnull=False
+ )
+ left_len = len(left_join_keys[0])
+ left_by_values = group_index[:left_len]
+ right_by_values = group_index[left_len:]
+
+ left_by_values = ensure_int64(left_by_values)
+ right_by_values = ensure_int64(right_by_values)
+
+ # choose appropriate function by type
+ func = _asof_by_function(self.direction)
+ return func(
+ left_values,
+ right_values,
+ left_by_values,
+ right_by_values,
+ self.allow_exact_matches,
+ tolerance,
+ )
+ else:
+ # choose appropriate function by type
+ func = _asof_by_function(self.direction)
+ return func(
+ left_values,
+ right_values,
+ None,
+ None,
+ self.allow_exact_matches,
+ tolerance,
+ False,
+ )
+
+
+def _get_multiindex_indexer(
+ join_keys: list[ArrayLike], index: MultiIndex, sort: bool
+) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ # left & right join labels and num. of levels at each location
+ mapped = (
+ _factorize_keys(index.levels[n]._values, join_keys[n], sort=sort)
+ for n in range(index.nlevels)
+ )
+ zipped = zip(*mapped, strict=True)
+ rcodes, lcodes, shape = (list(x) for x in zipped)
+ if sort:
+ rcodes = list(map(np.take, rcodes, index.codes))
+ else:
+ i8copy = lambda a: a.astype("i8", subok=False)
+ rcodes = list(map(i8copy, index.codes))
+
+ # fix right labels if there were any nulls
+ for i, join_key in enumerate(join_keys):
+ mask = index.codes[i] == -1
+ if mask.any():
+ # check if there already was any nulls at this location
+ # if there was, it is factorized to `shape[i] - 1`
+ a = join_key[lcodes[i] == shape[i] - 1]
+ if a.size == 0 or not a[0] != a[0]:
+ shape[i] += 1
+
+ rcodes[i][mask] = shape[i] - 1
+
+ # get flat i8 join keys
+ lkey, rkey = _get_join_keys(lcodes, rcodes, tuple(shape), sort)
+ return lkey, rkey
+
+
+def _get_empty_indexer() -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ """Return empty join indexers."""
+ return (
+ np.array([], dtype=np.intp),
+ np.array([], dtype=np.intp),
+ )
+
+
+def _get_no_sort_one_missing_indexer(
+ n: int, left_missing: bool
+) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ """
+ Return join indexers where all of one side is selected without sorting
+ and none of the other side is selected.
+
+ Parameters
+ ----------
+ n : int
+ Length of indexers to create.
+ left_missing : bool
+ If True, the left indexer will contain only -1's.
+ If False, the right indexer will contain only -1's.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ Left indexer
+ np.ndarray[np.intp]
+ Right indexer
+ """
+ idx = np.arange(n, dtype=np.intp)
+ idx_missing = np.full(shape=n, fill_value=-1, dtype=np.intp)
+ if left_missing:
+ return idx_missing, idx
+ return idx, idx_missing
+
+
+def _left_join_on_index(
+ left_ax: Index, right_ax: Index, join_keys: list[ArrayLike], sort: bool = False
+) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp]]:
+ if isinstance(right_ax, MultiIndex):
+ lkey, rkey = _get_multiindex_indexer(join_keys, right_ax, sort=sort)
+ else:
+ # error: Incompatible types in assignment (expression has type
+ # "Union[Union[ExtensionArray, ndarray[Any, Any]], Index, Series]",
+ # variable has type "ndarray[Any, dtype[signedinteger[Any]]]")
+ lkey = join_keys[0] # type: ignore[assignment]
+ # error: Incompatible types in assignment (expression has type "Index",
+ # variable has type "ndarray[Any, dtype[signedinteger[Any]]]")
+ rkey = right_ax._values # type: ignore[assignment]
+
+ left_key, right_key, count = _factorize_keys(lkey, rkey, sort=sort)
+ left_indexer, right_indexer = libjoin.left_outer_join(
+ left_key, right_key, count, sort=sort
+ )
+
+ if sort or len(left_ax) != len(left_indexer):
+ # if asked to sort or there are 1-to-many matches
+ join_index = left_ax.take(left_indexer)
+ return join_index, left_indexer, right_indexer
+
+ # left frame preserves order & length of its index
+ return left_ax, None, right_indexer
+
+
+def _factorize_keys(
+ lk: ArrayLike,
+ rk: ArrayLike,
+ sort: bool = True,
+ how: str | None = None,
+) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp], int]:
+ """
+ Encode left and right keys as enumerated types.
+
+ This is used to get the join indexers to be used when merging DataFrames.
+
+ Parameters
+ ----------
+ lk : ndarray, ExtensionArray
+ Left key.
+ rk : ndarray, ExtensionArray
+ Right key.
+ sort : bool, defaults to True
+ If True, the encoding is done such that the unique elements in the
+ keys are sorted.
+ how: str, optional
+ Used to determine if we can use hash-join. If not given, then just factorize
+ keys.
+
+ Returns
+ -------
+ np.ndarray[np.intp]
+ Left (resp. right if called with `key='right'`) labels, as enumerated type.
+ np.ndarray[np.intp]
+ Right (resp. left if called with `key='right'`) labels, as enumerated type.
+ int
+ Number of unique elements in union of left and right labels. -1 if we used
+ a hash-join.
+
+ See Also
+ --------
+ merge : Merge DataFrame or named Series objects
+ with a database-style join.
+ algorithms.factorize : Encode the object as an enumerated type
+ or categorical variable.
+
+ Examples
+ --------
+ >>> lk = np.array(["a", "c", "b"])
+ >>> rk = np.array(["a", "c"])
+
+ Here, the unique values are `'a', 'b', 'c'`. With the default
+ `sort=True`, the encoding will be `{0: 'a', 1: 'b', 2: 'c'}`:
+
+ >>> pd.core.reshape.merge._factorize_keys(lk, rk)
+ (array([0, 2, 1]), array([0, 2]), 3)
+
+ With the `sort=False`, the encoding will correspond to the order
+ in which the unique elements first appear: `{0: 'a', 1: 'c', 2: 'b'}`:
+
+ >>> pd.core.reshape.merge._factorize_keys(lk, rk, sort=False)
+ (array([0, 1, 2]), array([0, 1]), 3)
+ """
+ # TODO: if either is a RangeIndex, we can likely factorize more efficiently?
+
+ if (
+ isinstance(lk.dtype, DatetimeTZDtype) and isinstance(rk.dtype, DatetimeTZDtype)
+ ) or (lib.is_np_dtype(lk.dtype, "M") and lib.is_np_dtype(rk.dtype, "M")):
+ # Extract the ndarray (UTC-localized) values
+ # Note: we dont need the dtypes to match, as these can still be compared
+ lk, rk = cast("DatetimeArray", lk)._ensure_matching_resos(rk)
+ lk = cast("DatetimeArray", lk)._ndarray
+ rk = cast("DatetimeArray", rk)._ndarray
+
+ elif (
+ isinstance(lk.dtype, CategoricalDtype)
+ and isinstance(rk.dtype, CategoricalDtype)
+ and lk.dtype == rk.dtype
+ ):
+ assert isinstance(lk, Categorical)
+ assert isinstance(rk, Categorical)
+ # Cast rk to encoding so we can compare codes with lk
+
+ rk = lk._encode_with_my_categories(rk)
+
+ lk = ensure_int64(lk.codes)
+ rk = ensure_int64(rk.codes)
+
+ elif isinstance(lk, ExtensionArray) and lk.dtype == rk.dtype:
+ if isinstance(lk.dtype, ArrowDtype) or (
+ isinstance(lk.dtype, StringDtype) and lk.dtype.storage == "pyarrow"
+ ):
+ import pyarrow as pa
+
+ from pandas.compat.pyarrow import _safe_fill_null
+
+ len_lk = len(lk)
+ lk = lk._pa_array # type: ignore[attr-defined]
+ rk = rk._pa_array # type: ignore[union-attr]
+ dc = (
+ pa.chunked_array(lk.chunks + rk.chunks) # type: ignore[union-attr]
+ .combine_chunks()
+ .dictionary_encode()
+ )
+
+ llab, rlab, count = (
+ _safe_fill_null(dc.indices[slice(len_lk)], -1)
+ .to_numpy()
+ .astype(np.intp, copy=False),
+ _safe_fill_null(dc.indices[slice(len_lk, None)], -1)
+ .to_numpy()
+ .astype(np.intp, copy=False),
+ len(dc.dictionary),
+ )
+
+ if sort:
+ uniques = dc.dictionary.to_numpy(zero_copy_only=False)
+ llab, rlab = _sort_labels(uniques, llab, rlab)
+
+ if dc.null_count > 0:
+ lmask = llab == -1
+ lany = lmask.any()
+ rmask = rlab == -1
+ rany = rmask.any()
+ if lany:
+ np.putmask(llab, lmask, count)
+ if rany:
+ np.putmask(rlab, rmask, count)
+ count += 1
+ return llab, rlab, count
+
+ if not isinstance(lk, BaseMaskedArray) and not (
+ # exclude arrow dtypes that would get cast to object
+ isinstance(lk.dtype, ArrowDtype)
+ and (
+ is_numeric_dtype(lk.dtype.numpy_dtype)
+ or (is_string_dtype(lk.dtype) and not sort)
+ )
+ ):
+ lk, _ = lk._values_for_factorize()
+
+ # error: Item "ndarray" of "Union[Any, ndarray]" has no attribute
+ # "_values_for_factorize"
+ rk, _ = rk._values_for_factorize() # type: ignore[union-attr]
+
+ if needs_i8_conversion(lk.dtype) and lk.dtype == rk.dtype:
+ # GH#23917 TODO: Needs tests for non-matching dtypes
+ # GH#23917 TODO: needs tests for case where lk is integer-dtype
+ # and rk is datetime-dtype
+ lk = np.asarray(lk, dtype=np.int64)
+ rk = np.asarray(rk, dtype=np.int64)
+
+ klass, lk, rk = _convert_arrays_and_get_rizer_klass(lk, rk)
+
+ rizer = klass(
+ max(len(lk), len(rk)),
+ uses_mask=isinstance(rk, (BaseMaskedArray, ArrowExtensionArray)),
+ )
+
+ if isinstance(lk, BaseMaskedArray):
+ assert isinstance(rk, BaseMaskedArray)
+ lk_data, lk_mask = lk._data, lk._mask
+ rk_data, rk_mask = rk._data, rk._mask
+ elif isinstance(lk, ArrowExtensionArray):
+ assert isinstance(rk, ArrowExtensionArray)
+ # we can only get here with numeric dtypes
+ # TODO: Remove when we have a Factorizer for Arrow
+ lk_data = lk.to_numpy(na_value=1, dtype=lk.dtype.numpy_dtype)
+ rk_data = rk.to_numpy(na_value=1, dtype=lk.dtype.numpy_dtype)
+ lk_mask, rk_mask = lk.isna(), rk.isna()
+ else:
+ # Argument 1 to "factorize" of "ObjectFactorizer" has incompatible type
+ # "Union[ndarray[Any, dtype[signedinteger[_64Bit]]],
+ # ndarray[Any, dtype[object_]]]"; expected "ndarray[Any, dtype[object_]]"
+ lk_data, rk_data = lk, rk # type: ignore[assignment]
+ lk_mask, rk_mask = None, None
+
+ hash_join_available = how == "inner" and not sort and lk.dtype.kind in "iufb"
+ if hash_join_available:
+ rlab = rizer.factorize(rk_data, mask=rk_mask)
+ if rizer.get_count() == len(rlab):
+ ridx, lidx = rizer.hash_inner_join(lk_data, lk_mask)
+ return lidx, ridx, -1
+ else:
+ llab = rizer.factorize(lk_data, mask=lk_mask)
+ else:
+ llab = rizer.factorize(lk_data, mask=lk_mask)
+ rlab = rizer.factorize(rk_data, mask=rk_mask)
+
+ assert llab.dtype == np.dtype(np.intp), llab.dtype
+ assert rlab.dtype == np.dtype(np.intp), rlab.dtype
+
+ count = rizer.get_count()
+
+ if sort:
+ uniques = rizer.uniques.to_array()
+ llab, rlab = _sort_labels(uniques, llab, rlab)
+
+ # NA group
+ lmask = llab == -1
+ lany = lmask.any()
+ rmask = rlab == -1
+ rany = rmask.any()
+
+ if lany or rany:
+ if lany:
+ np.putmask(llab, lmask, count)
+ if rany:
+ np.putmask(rlab, rmask, count)
+ count += 1
+
+ return llab, rlab, count
+
+
+def _convert_arrays_and_get_rizer_klass(
+ lk: ArrayLike, rk: ArrayLike
+) -> tuple[type[libhashtable.Factorizer], ArrayLike, ArrayLike]:
+ klass: type[libhashtable.Factorizer]
+ if is_numeric_dtype(lk.dtype):
+ if lk.dtype != rk.dtype:
+ dtype = find_common_type([lk.dtype, rk.dtype])
+ if isinstance(dtype, ExtensionDtype):
+ cls = dtype.construct_array_type()
+ if not isinstance(lk, ExtensionArray):
+ lk = cls._from_sequence(lk, dtype=dtype, copy=False)
+ else:
+ lk = lk.astype(dtype, copy=False)
+
+ if not isinstance(rk, ExtensionArray):
+ rk = cls._from_sequence(rk, dtype=dtype, copy=False)
+ else:
+ rk = rk.astype(dtype, copy=False)
+ else:
+ lk = lk.astype(dtype, copy=False)
+ rk = rk.astype(dtype, copy=False)
+ if isinstance(lk, BaseMaskedArray):
+ klass = _factorizers[lk.dtype.type]
+ elif isinstance(lk.dtype, ArrowDtype):
+ klass = _factorizers[lk.dtype.numpy_dtype.type]
+ else:
+ klass = _factorizers[lk.dtype.type]
+
+ else:
+ klass = libhashtable.ObjectFactorizer
+ lk = ensure_object(lk)
+ rk = ensure_object(rk)
+ return klass, lk, rk
+
+
+def _sort_labels(
+ uniques: np.ndarray, left: npt.NDArray[np.intp], right: npt.NDArray[np.intp]
+) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
+ llength = len(left)
+ labels = np.concatenate([left, right])
+
+ _, new_labels = algos.safe_sort(uniques, labels, use_na_sentinel=True)
+ new_left, new_right = new_labels[:llength], new_labels[llength:]
+
+ return new_left, new_right
+
+
+def _get_join_keys(
+ llab: list[npt.NDArray[np.int64 | np.intp]],
+ rlab: list[npt.NDArray[np.int64 | np.intp]],
+ shape: Shape,
+ sort: bool,
+) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.int64]]:
+ # how many levels can be done without overflow
+ nlev = next(
+ lev
+ for lev in range(len(shape), 0, -1)
+ if not is_int64_overflow_possible(shape[:lev])
+ )
+
+ # get keys for the first `nlev` levels
+ stride = np.prod(shape[1:nlev], dtype="i8")
+ lkey = stride * llab[0].astype("i8", subok=False, copy=False)
+ rkey = stride * rlab[0].astype("i8", subok=False, copy=False)
+
+ for i in range(1, nlev):
+ with np.errstate(divide="ignore"):
+ stride //= shape[i]
+ lkey += llab[i] * stride
+ rkey += rlab[i] * stride
+
+ if nlev == len(shape): # all done!
+ return lkey, rkey
+
+ # densify current keys to avoid overflow
+ lkey, rkey, count = _factorize_keys(lkey, rkey, sort=sort)
+
+ llab = [lkey, *llab[nlev:]]
+ rlab = [rkey, *rlab[nlev:]]
+ shape = (count, *shape[nlev:])
+
+ return _get_join_keys(llab, rlab, shape, sort)
+
+
+def _should_fill(lname, rname) -> bool:
+ if not isinstance(lname, str) or not isinstance(rname, str):
+ return True
+ return lname == rname
+
+
+def _any(x) -> bool:
+ return x is not None and com.any_not_none(*x)
+
+
+def _validate_operand(obj: DataFrame | Series) -> DataFrame:
+ if isinstance(obj, ABCDataFrame):
+ return obj
+ elif isinstance(obj, ABCSeries):
+ if obj.name is None:
+ raise ValueError("Cannot merge a Series without a name")
+ return obj.to_frame()
+ else:
+ raise TypeError(
+ f"Can only merge Series or DataFrame objects, a {type(obj)} was passed"
+ )
+
+
+def _items_overlap_with_suffix(
+ left: Index, right: Index, suffixes: Suffixes
+) -> tuple[Index, Index]:
+ """
+ Suffixes type validation.
+
+ If two indices overlap, add suffixes to overlapping entries.
+
+ If corresponding suffix is empty, the entry is simply converted to string.
+
+ """
+ if not is_list_like(suffixes, allow_sets=False) or isinstance(suffixes, dict):
+ raise TypeError(
+ f"Passing 'suffixes' as a {type(suffixes)}, is not supported. "
+ "Provide 'suffixes' as a tuple instead."
+ )
+
+ to_rename = left.intersection(right)
+ if len(to_rename) == 0:
+ return left, right
+
+ lsuffix, rsuffix = suffixes
+
+ if not lsuffix and not rsuffix:
+ raise ValueError(f"columns overlap but no suffix specified: {to_rename}")
+
+ def renamer(x, suffix: str | None):
+ """
+ Rename the left and right indices.
+
+ If there is overlap, and suffix is not None, add
+ suffix, otherwise, leave it as-is.
+
+ Parameters
+ ----------
+ x : original column name
+ suffix : str or None
+
+ Returns
+ -------
+ x : renamed column name
+ """
+ if x in to_rename and suffix is not None:
+ return f"{x}{suffix}"
+ return x
+
+ lrenamer = partial(renamer, suffix=lsuffix)
+ rrenamer = partial(renamer, suffix=rsuffix)
+
+ llabels = left._transform_index(lrenamer)
+ rlabels = right._transform_index(rrenamer)
+
+ dups = []
+ if not llabels.is_unique:
+ # Only warn when duplicates are caused because of suffixes, already duplicated
+ # columns in origin should not warn
+ dups.extend(llabels[(llabels.duplicated()) & (~left.duplicated())].tolist())
+ if not rlabels.is_unique:
+ dups.extend(rlabels[(rlabels.duplicated()) & (~right.duplicated())].tolist())
+ # Suffix addition creates duplicate to pre-existing column name
+ dups.extend(llabels.intersection(right.difference(to_rename)).tolist())
+ dups.extend(rlabels.intersection(left.difference(to_rename)).tolist())
+ if dups:
+ raise MergeError(
+ f"Passing 'suffixes' which cause duplicate columns {set(dups)} is "
+ "not allowed.",
+ )
+
+ return llabels, rlabels
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/pivot.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/pivot.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7fdb732103c6dd0d323eb18e41968963a85a3d7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/pivot.py
@@ -0,0 +1,1275 @@
+from __future__ import annotations
+
+import itertools
+from typing import (
+ TYPE_CHECKING,
+ Literal,
+ cast,
+)
+
+import numpy as np
+
+from pandas._libs import lib
+from pandas.util._decorators import set_module
+
+from pandas.core.dtypes.cast import maybe_downcast_to_dtype
+from pandas.core.dtypes.common import (
+ is_list_like,
+ is_nested_list_like,
+ is_scalar,
+)
+from pandas.core.dtypes.dtypes import ExtensionDtype
+from pandas.core.dtypes.generic import (
+ ABCDataFrame,
+ ABCSeries,
+)
+
+import pandas.core.common as com
+from pandas.core.groupby import Grouper
+from pandas.core.indexes.api import (
+ Index,
+ MultiIndex,
+ get_objs_combined_axis,
+)
+from pandas.core.reshape.concat import concat
+from pandas.core.series import Series
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Callable,
+ Hashable,
+ )
+
+ from pandas._typing import (
+ AggFuncType,
+ AggFuncTypeBase,
+ AggFuncTypeDict,
+ IndexLabel,
+ SequenceNotStr,
+ )
+
+ from pandas import DataFrame
+
+
+@set_module("pandas")
+def pivot_table(
+ data: DataFrame,
+ values=None,
+ index=None,
+ columns=None,
+ aggfunc: AggFuncType = "mean",
+ fill_value=None,
+ margins: bool = False,
+ dropna: bool = True,
+ margins_name: Hashable = "All",
+ observed: bool = True,
+ sort: bool = True,
+ **kwargs,
+) -> DataFrame:
+ """
+ Create a spreadsheet-style pivot table as a DataFrame.
+
+ The levels in the pivot table will be stored in MultiIndex objects
+ (hierarchical indexes) on the index and columns of the result DataFrame.
+
+ Parameters
+ ----------
+ data : DataFrame
+ Input pandas DataFrame object.
+ values : list-like or scalar, optional
+ Column or columns to aggregate.
+ index : column, Grouper, array, or sequence of the previous
+ Keys to group by on the pivot table index. If a list is passed,
+ it can contain any of the other types (except list). If an array is
+ passed, it must be the same length as the data and will be used in
+ the same manner as column values.
+ columns : column, Grouper, array, or sequence of the previous
+ Keys to group by on the pivot table column. If a list is passed,
+ it can contain any of the other types (except list). If an array is
+ passed, it must be the same length as the data and will be used in
+ the same manner as column values.
+ aggfunc : function, list of functions, dict, default "mean"
+ If a list of functions is passed, the resulting pivot table will have
+ hierarchical columns whose top level are the function names
+ (inferred from the function objects themselves).
+ If a dict is passed, the key is column to aggregate and the value is
+ function or list of functions. If ``margins=True``, aggfunc will be
+ used to calculate the partial aggregates.
+ fill_value : scalar, default None
+ Value to replace missing values with (in the resulting pivot table,
+ after aggregation).
+ margins : bool, default False
+ If ``margins=True``, special ``All`` columns and rows
+ will be added with partial group aggregates across the categories
+ on the rows and columns.
+ dropna : bool, default True
+ Do not include columns whose entries are all NaN. If True,
+
+ * rows with an NA value in any column will be omitted before computing margins,
+ * index/column keys containing NA values will be dropped (see ``dropna``
+ parameter in :meth:``DataFrame.groupby``).
+
+ margins_name : str, default 'All'
+ Name of the row / column that will contain the totals
+ when margins is True.
+ observed : bool, default False
+ This only applies if any of the groupers are Categoricals.
+ If True: only show observed values for categorical groupers.
+ If False: show all values for categorical groupers.
+
+ .. versionchanged:: 3.0.0
+
+ The default value is now ``True``.
+
+ sort : bool, default True
+ Specifies if the result should be sorted.
+
+ **kwargs : dict
+ Optional keyword arguments to pass to ``aggfunc``.
+
+ .. versionadded:: 3.0.0
+
+ Returns
+ -------
+ DataFrame
+ An Excel style pivot table.
+
+ See Also
+ --------
+ DataFrame.pivot : Pivot without aggregation that can handle
+ non-numeric data.
+ DataFrame.melt: Unpivot a DataFrame from wide to long format,
+ optionally leaving identifiers set.
+ wide_to_long : Wide panel to long format. Less flexible but more
+ user-friendly than melt.
+
+ Notes
+ -----
+ Reference :ref:`the user guide ` for more examples.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame(
+ ... {
+ ... "A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
+ ... "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"],
+ ... "C": [
+ ... "small",
+ ... "large",
+ ... "large",
+ ... "small",
+ ... "small",
+ ... "large",
+ ... "small",
+ ... "small",
+ ... "large",
+ ... ],
+ ... "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
+ ... "E": [2, 4, 5, 5, 6, 6, 8, 9, 9],
+ ... }
+ ... )
+ >>> df
+ A B C D E
+ 0 foo one small 1 2
+ 1 foo one large 2 4
+ 2 foo one large 2 5
+ 3 foo two small 3 5
+ 4 foo two small 3 6
+ 5 bar one large 4 6
+ 6 bar one small 5 8
+ 7 bar two small 6 9
+ 8 bar two large 7 9
+
+ This first example aggregates values by taking the sum.
+
+ >>> table = pd.pivot_table(
+ ... df, values="D", index=["A", "B"], columns=["C"], aggfunc="sum"
+ ... )
+ >>> table
+ C large small
+ A B
+ bar one 4.0 5.0
+ two 7.0 6.0
+ foo one 4.0 1.0
+ two NaN 6.0
+
+ We can also fill missing values using the `fill_value` parameter.
+
+ >>> table = pd.pivot_table(
+ ... df, values="D", index=["A", "B"], columns=["C"], aggfunc="sum", fill_value=0
+ ... )
+ >>> table
+ C large small
+ A B
+ bar one 4 5
+ two 7 6
+ foo one 4 1
+ two 0 6
+
+ The next example aggregates by taking the mean across multiple columns.
+
+ >>> table = pd.pivot_table(
+ ... df, values=["D", "E"], index=["A", "C"], aggfunc={"D": "mean", "E": "mean"}
+ ... )
+ >>> table
+ D E
+ A C
+ bar large 5.500000 7.500000
+ small 5.500000 8.500000
+ foo large 2.000000 4.500000
+ small 2.333333 4.333333
+
+ We can also calculate multiple types of aggregations for any given
+ value column.
+
+ >>> table = pd.pivot_table(
+ ... df,
+ ... values=["D", "E"],
+ ... index=["A", "C"],
+ ... aggfunc={"D": "mean", "E": ["min", "max", "mean"]},
+ ... )
+ >>> table
+ D E
+ mean max mean min
+ A C
+ bar large 5.500000 9 7.500000 6
+ small 5.500000 9 8.500000 8
+ foo large 2.000000 5 4.500000 4
+ small 2.333333 6 4.333333 2
+ """
+ index = _convert_by(index)
+ columns = _convert_by(columns)
+
+ if isinstance(aggfunc, list):
+ pieces: list[DataFrame] = []
+ keys = []
+ for func in aggfunc:
+ _table = __internal_pivot_table(
+ data,
+ values=values,
+ index=index,
+ columns=columns,
+ fill_value=fill_value,
+ aggfunc=func,
+ margins=margins,
+ dropna=dropna,
+ margins_name=margins_name,
+ observed=observed,
+ sort=sort,
+ kwargs=kwargs,
+ )
+ pieces.append(_table)
+ keys.append(getattr(func, "__name__", func))
+
+ table = concat(pieces, keys=keys, axis=1)
+ return table.__finalize__(data, method="pivot_table")
+
+ table = __internal_pivot_table(
+ data,
+ values,
+ index,
+ columns,
+ aggfunc,
+ fill_value,
+ margins,
+ dropna,
+ margins_name,
+ observed,
+ sort,
+ kwargs,
+ )
+ return table.__finalize__(data, method="pivot_table")
+
+
+def __internal_pivot_table(
+ data: DataFrame,
+ values,
+ index,
+ columns,
+ aggfunc: AggFuncTypeBase | AggFuncTypeDict,
+ fill_value,
+ margins: bool,
+ dropna: bool,
+ margins_name: Hashable,
+ observed: bool,
+ sort: bool,
+ kwargs,
+) -> DataFrame:
+ """
+ Helper of :func:`pandas.pivot_table` for any non-list ``aggfunc``.
+ """
+ keys = index + columns
+
+ values_passed = values is not None
+ if values_passed:
+ if is_list_like(values):
+ values_multi = True
+ values = list(values)
+ else:
+ values_multi = False
+ values = [values]
+
+ # GH14938 Make sure value labels are in data
+ for i in values:
+ if i not in data:
+ raise KeyError(i)
+
+ to_filter = []
+ for x in keys + values:
+ if isinstance(x, Grouper):
+ x = x.key
+ try:
+ if x in data:
+ to_filter.append(x)
+ except TypeError:
+ pass
+ if len(to_filter) < len(data.columns):
+ data = data[to_filter]
+
+ else:
+ values = data.columns
+ for key in keys:
+ try:
+ values = values.drop(key)
+ except (TypeError, ValueError, KeyError):
+ pass
+ values = list(values)
+
+ grouped = data.groupby(keys, observed=observed, sort=sort, dropna=dropna)
+ if values_passed:
+ # GH#57876 and GH#61292
+ # mypy is not aware `grouped[values]` will always be a DataFrameGroupBy
+ grouped = grouped[values] # type: ignore[assignment]
+
+ agged = grouped.agg(aggfunc, **kwargs)
+
+ if dropna and isinstance(agged, ABCDataFrame) and len(agged.columns):
+ agged = agged.dropna(how="all")
+
+ table = agged
+
+ # GH17038, this check should only happen if index is defined (not None)
+ if table.index.nlevels > 1 and index:
+ # Related GH #17123
+ # If index_names are integers, determine whether the integers refer
+ # to the level position or name.
+ index_names = agged.index.names[: len(index)]
+ to_unstack = []
+ for i in range(len(index), len(keys)):
+ name = agged.index.names[i]
+ if name is None or name in index_names:
+ to_unstack.append(i)
+ else:
+ to_unstack.append(name)
+ table = agged.unstack(to_unstack, fill_value=fill_value)
+
+ if not dropna:
+ if isinstance(table.index, MultiIndex):
+ m = MultiIndex.from_product(table.index.levels, names=table.index.names)
+ table = table.reindex(m, axis=0, fill_value=fill_value)
+
+ if isinstance(table.columns, MultiIndex):
+ m = MultiIndex.from_product(table.columns.levels, names=table.columns.names)
+ table = table.reindex(m, axis=1, fill_value=fill_value)
+
+ if sort is True and isinstance(table, ABCDataFrame):
+ table = table.sort_index(axis=1)
+
+ if fill_value is not None:
+ table = table.fillna(fill_value)
+ if aggfunc is len and not observed and lib.is_integer(fill_value):
+ # TODO: can we avoid this? this used to be handled by
+ # downcast="infer" in fillna
+ table = table.astype(np.int64)
+
+ if margins:
+ if dropna:
+ data = data[data.notna().all(axis=1)]
+ table = _add_margins(
+ table,
+ data,
+ values,
+ rows=index,
+ cols=columns,
+ aggfunc=aggfunc,
+ kwargs=kwargs,
+ observed=dropna,
+ margins_name=margins_name,
+ fill_value=fill_value,
+ dropna=dropna,
+ )
+
+ # discard the top level
+ if values_passed and not values_multi and table.columns.nlevels > 1:
+ table.columns = table.columns.droplevel(0)
+ if len(index) == 0 and len(columns) > 0:
+ table = table.T
+
+ # GH 15193 Make sure empty columns are removed if dropna=True
+ if isinstance(table, ABCDataFrame) and dropna:
+ table = table.dropna(how="all", axis=1)
+
+ return table
+
+
+def _add_margins(
+ table: DataFrame | Series,
+ data: DataFrame,
+ values,
+ rows,
+ cols,
+ aggfunc,
+ kwargs,
+ observed: bool,
+ margins_name: Hashable = "All",
+ fill_value=None,
+ dropna: bool = True,
+):
+ if not isinstance(margins_name, str):
+ raise ValueError("margins_name argument must be a string")
+
+ msg = f'Conflicting name "{margins_name}" in margins'
+ for level in table.index.names:
+ if margins_name in table.index.get_level_values(level):
+ raise ValueError(msg)
+
+ grand_margin = _compute_grand_margin(data, values, aggfunc, kwargs, margins_name)
+
+ if table.ndim == 2:
+ # i.e. DataFrame
+ for level in table.columns.names[1:]:
+ if margins_name in table.columns.get_level_values(level):
+ raise ValueError(msg)
+
+ key: str | tuple[str, ...]
+ if len(rows) > 1:
+ key = (margins_name,) + ("",) * (len(rows) - 1)
+ else:
+ key = margins_name
+
+ if not values and isinstance(table, ABCSeries):
+ # If there are no values and the table is a series, then there is only
+ # one column in the data. Compute grand margin and return it.
+ return table._append_internal(
+ table._constructor({key: grand_margin[margins_name]})
+ )
+
+ elif values:
+ marginal_result_set = _generate_marginal_results(
+ table,
+ data,
+ values,
+ rows,
+ cols,
+ aggfunc,
+ kwargs,
+ observed,
+ margins_name,
+ dropna,
+ )
+ if not isinstance(marginal_result_set, tuple):
+ return marginal_result_set
+ result, margin_keys, row_margin = marginal_result_set
+ else:
+ # no values, and table is a DataFrame
+ assert isinstance(table, ABCDataFrame)
+ marginal_result_set = _generate_marginal_results_without_values(
+ table, data, rows, cols, aggfunc, kwargs, observed, margins_name, dropna
+ )
+ if not isinstance(marginal_result_set, tuple):
+ return marginal_result_set
+ result, margin_keys, row_margin = marginal_result_set
+
+ row_margin = row_margin.reindex(result.columns, fill_value=fill_value)
+ # populate grand margin
+ for k in margin_keys:
+ if isinstance(k, str):
+ row_margin[k] = grand_margin[k]
+ else:
+ row_margin[k] = grand_margin[k[0]]
+
+ from pandas import DataFrame
+
+ margin_dummy = DataFrame(row_margin, columns=Index([key])).T
+
+ row_names = result.index.names
+ # check the result column and leave floats
+
+ for dtype in set(result.dtypes):
+ if isinstance(dtype, ExtensionDtype):
+ # Can hold NA already
+ continue
+
+ cols = result.select_dtypes([dtype]).columns
+ margin_dummy[cols] = margin_dummy[cols].apply(
+ maybe_downcast_to_dtype, args=(dtype,)
+ )
+ result = concat([result, margin_dummy])
+ result.index.names = row_names
+
+ return result
+
+
+def _compute_grand_margin(
+ data: DataFrame, values, aggfunc, kwargs, margins_name: Hashable = "All"
+):
+ if values:
+ grand_margin = {}
+ for k, v in data[values].items():
+ try:
+ if isinstance(aggfunc, str):
+ grand_margin[k] = getattr(v, aggfunc)(**kwargs)
+ elif isinstance(aggfunc, dict):
+ if isinstance(aggfunc[k], str):
+ grand_margin[k] = getattr(v, aggfunc[k])(**kwargs)
+ else:
+ grand_margin[k] = aggfunc[k](v, **kwargs)
+ else:
+ grand_margin[k] = aggfunc(v, **kwargs)
+ except TypeError:
+ pass
+ return grand_margin
+ else:
+ return {margins_name: aggfunc(data.index, **kwargs)}
+
+
+def _generate_marginal_results(
+ table,
+ data: DataFrame,
+ values,
+ rows,
+ cols,
+ aggfunc,
+ kwargs,
+ observed: bool,
+ margins_name: Hashable = "All",
+ dropna: bool = True,
+):
+ margin_keys: list | Index
+ if len(cols) > 0:
+ # need to "interleave" the margins
+ table_pieces = []
+ margin_keys = []
+
+ def _all_key(key):
+ return (key, margins_name) + ("",) * (len(cols) - 1)
+
+ if len(rows) > 0:
+ margin = (
+ data[rows + values]
+ .groupby(rows, observed=observed, dropna=dropna)
+ .agg(aggfunc, **kwargs)
+ )
+ cat_axis = 1
+
+ for key, piece in table.T.groupby(level=0, observed=observed):
+ piece = piece.T
+ all_key = _all_key(key)
+
+ piece[all_key] = margin[key]
+
+ table_pieces.append(piece)
+ margin_keys.append(all_key)
+ else:
+ margin = (
+ data[cols[:1] + values]
+ .groupby(cols[:1], observed=observed, dropna=dropna)
+ .agg(aggfunc, **kwargs)
+ .T
+ )
+
+ cat_axis = 0
+ for key, piece in table.groupby(level=0, observed=observed):
+ if len(cols) > 1:
+ all_key = _all_key(key)
+ else:
+ all_key = margins_name
+ table_pieces.append(piece)
+ transformed_piece = margin[key].to_frame().T
+ if isinstance(piece.index, MultiIndex):
+ # We are adding an empty level
+ transformed_piece.index = MultiIndex.from_tuples(
+ [all_key],
+ names=[*piece.index.names, None],
+ )
+ else:
+ transformed_piece.index = Index([all_key], name=piece.index.name)
+
+ # append piece for margin into table_piece
+ table_pieces.append(transformed_piece)
+ margin_keys.append(all_key)
+
+ if not table_pieces:
+ # GH 49240
+ return table
+ else:
+ result = concat(table_pieces, axis=cat_axis)
+
+ if len(rows) == 0:
+ return result
+ else:
+ result = table
+ margin_keys = table.columns
+
+ if len(cols) > 0:
+ row_margin = (
+ data[cols + values]
+ .groupby(cols, observed=observed, dropna=dropna)
+ .agg(aggfunc, **kwargs)
+ )
+ row_margin = row_margin.stack()
+
+ # GH#26568. Use names instead of indices in case of numeric names
+ new_order_indices = itertools.chain([len(cols)], range(len(cols)))
+ new_order_names = [row_margin.index.names[i] for i in new_order_indices]
+ row_margin.index = row_margin.index.reorder_levels(new_order_names)
+ else:
+ row_margin = data._constructor_sliced(np.nan, index=result.columns)
+
+ return result, margin_keys, row_margin
+
+
+def _generate_marginal_results_without_values(
+ table: DataFrame,
+ data: DataFrame,
+ rows,
+ cols,
+ aggfunc,
+ kwargs,
+ observed: bool,
+ margins_name: Hashable = "All",
+ dropna: bool = True,
+):
+ margin_keys: list | Index
+ if len(cols) > 0:
+ # need to "interleave" the margins
+ margin_keys = []
+
+ def _all_key():
+ if len(cols) == 1:
+ return margins_name
+ return (margins_name,) + ("",) * (len(cols) - 1)
+
+ if len(rows) > 0:
+ margin = data.groupby(rows, observed=observed, dropna=dropna)[rows].apply(
+ aggfunc, **kwargs
+ )
+ all_key = _all_key()
+ table[all_key] = margin
+ result = table
+ margin_keys.append(all_key)
+
+ else:
+ margin = data.groupby(level=0, observed=observed, dropna=dropna).apply(
+ aggfunc, **kwargs
+ )
+ all_key = _all_key()
+ table[all_key] = margin
+ result = table
+ margin_keys.append(all_key)
+ return result
+ else:
+ result = table
+ margin_keys = table.columns
+
+ if len(cols):
+ row_margin = data.groupby(cols, observed=observed, dropna=dropna)[cols].apply(
+ aggfunc, **kwargs
+ )
+ else:
+ row_margin = Series(np.nan, index=result.columns)
+
+ return result, margin_keys, row_margin
+
+
+def _convert_by(by):
+ if by is None:
+ by = []
+ elif (
+ is_scalar(by)
+ or isinstance(by, (np.ndarray, Index, ABCSeries, Grouper))
+ or callable(by)
+ ):
+ by = [by]
+ else:
+ by = list(by)
+ return by
+
+
+@set_module("pandas")
+def pivot(
+ data: DataFrame,
+ *,
+ columns: IndexLabel,
+ index: IndexLabel | lib.NoDefault = lib.no_default,
+ values: IndexLabel | lib.NoDefault = lib.no_default,
+) -> DataFrame:
+ """
+ Return reshaped DataFrame organized by given index / column values.
+
+ Reshape data (produce a "pivot" table) based on column values. Uses
+ unique values from specified `index` / `columns` to form axes of the
+ resulting DataFrame. This function does not support data
+ aggregation, multiple values will result in a MultiIndex in the
+ columns. See the :ref:`User Guide ` for more on reshaping.
+
+ Parameters
+ ----------
+ data : DataFrame
+ Input pandas DataFrame object.
+ columns : Hashable or a sequence of the previous
+ Column to use to make new frame's columns.
+ index : Hashable or a sequence of the previous, optional
+ Column to use to make new frame's index. If not given, uses existing index.
+ values : Hashable or a sequence of the previous, optional
+ Column(s) to use for populating new frame's values. If not
+ specified, all remaining columns will be used and the result will
+ have hierarchically indexed columns.
+
+ Returns
+ -------
+ DataFrame
+ Returns reshaped DataFrame.
+
+ Raises
+ ------
+ ValueError:
+ When there are any `index`, `columns` combinations with multiple
+ values. `DataFrame.pivot_table` when you need to aggregate.
+
+ See Also
+ --------
+ DataFrame.pivot_table : Generalization of pivot that can handle
+ duplicate values for one index/column pair.
+ DataFrame.unstack : Pivot based on the index values instead of a
+ column.
+ wide_to_long : Wide panel to long format. Less flexible but more
+ user-friendly than melt.
+
+ Notes
+ -----
+ For finer-tuned control, see hierarchical indexing documentation along
+ with the related stack/unstack methods.
+
+ Reference :ref:`the user guide ` for more examples.
+
+ Examples
+ --------
+ >>> df = pd.DataFrame(
+ ... {
+ ... "foo": ["one", "one", "one", "two", "two", "two"],
+ ... "bar": ["A", "B", "C", "A", "B", "C"],
+ ... "baz": [1, 2, 3, 4, 5, 6],
+ ... "zoo": ["x", "y", "z", "q", "w", "t"],
+ ... }
+ ... )
+ >>> df
+ foo bar baz zoo
+ 0 one A 1 x
+ 1 one B 2 y
+ 2 one C 3 z
+ 3 two A 4 q
+ 4 two B 5 w
+ 5 two C 6 t
+
+ >>> df.pivot(index="foo", columns="bar", values="baz")
+ bar A B C
+ foo
+ one 1 2 3
+ two 4 5 6
+
+ >>> df.pivot(index="foo", columns="bar")["baz"]
+ bar A B C
+ foo
+ one 1 2 3
+ two 4 5 6
+
+ >>> df.pivot(index="foo", columns="bar", values=["baz", "zoo"])
+ baz zoo
+ bar A B C A B C
+ foo
+ one 1 2 3 x y z
+ two 4 5 6 q w t
+
+ You could also assign a list of column names or a list of index names.
+
+ >>> df = pd.DataFrame(
+ ... {
+ ... "lev1": [1, 1, 1, 2, 2, 2],
+ ... "lev2": [1, 1, 2, 1, 1, 2],
+ ... "lev3": [1, 2, 1, 2, 1, 2],
+ ... "lev4": [1, 2, 3, 4, 5, 6],
+ ... "values": [0, 1, 2, 3, 4, 5],
+ ... }
+ ... )
+ >>> df
+ lev1 lev2 lev3 lev4 values
+ 0 1 1 1 1 0
+ 1 1 1 2 2 1
+ 2 1 2 1 3 2
+ 3 2 1 2 4 3
+ 4 2 1 1 5 4
+ 5 2 2 2 6 5
+
+ >>> df.pivot(index="lev1", columns=["lev2", "lev3"], values="values")
+ lev2 1 2
+ lev3 1 2 1 2
+ lev1
+ 1 0.0 1.0 2.0 NaN
+ 2 4.0 3.0 NaN 5.0
+
+ >>> df.pivot(index=["lev1", "lev2"], columns=["lev3"], values="values")
+ lev3 1 2
+ lev1 lev2
+ 1 1 0.0 1.0
+ 2 2.0 NaN
+ 2 1 4.0 3.0
+ 2 NaN 5.0
+
+ A ValueError is raised if there are any duplicates.
+
+ >>> df = pd.DataFrame(
+ ... {
+ ... "foo": ["one", "one", "two", "two"],
+ ... "bar": ["A", "A", "B", "C"],
+ ... "baz": [1, 2, 3, 4],
+ ... }
+ ... )
+ >>> df
+ foo bar baz
+ 0 one A 1
+ 1 one A 2
+ 2 two B 3
+ 3 two C 4
+
+ Notice that the first two rows are the same for our `index`
+ and `columns` arguments.
+
+ >>> df.pivot(index="foo", columns="bar", values="baz")
+ Traceback (most recent call last):
+ ...
+ ValueError: Index contains duplicate entries, cannot reshape
+ """
+ columns_listlike = com.convert_to_list_like(columns)
+
+ # If columns is None we will create a MultiIndex level with None as name
+ # which might cause duplicated names because None is the default for
+ # level names
+ if any(name is None for name in data.index.names):
+ data = data.copy(deep=False)
+ data.index.names = [
+ name if name is not None else lib.no_default for name in data.index.names
+ ]
+
+ indexed: DataFrame | Series
+ if values is lib.no_default:
+ if index is not lib.no_default:
+ cols = com.convert_to_list_like(index)
+ else:
+ cols = []
+
+ append = index is lib.no_default
+ # error: Unsupported operand types for + ("List[Any]" and "ExtensionArray")
+ # error: Unsupported left operand type for + ("ExtensionArray")
+ indexed = data.set_index(
+ cols + columns_listlike, # type: ignore[operator]
+ append=append,
+ )
+ else:
+ index_list: list[Index] | list[Series]
+ if index is lib.no_default:
+ if isinstance(data.index, MultiIndex):
+ # GH 23955
+ index_list = [
+ data.index.get_level_values(i) for i in range(data.index.nlevels)
+ ]
+ else:
+ index_list = [
+ data._constructor_sliced(data.index, name=data.index.name)
+ ]
+ else:
+ index_list = [data[idx] for idx in com.convert_to_list_like(index)]
+
+ data_columns = [data[col] for col in columns_listlike]
+ index_list.extend(data_columns)
+ multiindex = MultiIndex.from_arrays(index_list)
+
+ if is_list_like(values) and not isinstance(values, tuple):
+ # Exclude tuple because it is seen as a single column name
+ indexed = data._constructor(
+ data[values]._values,
+ index=multiindex,
+ columns=cast("SequenceNotStr", values),
+ )
+ else:
+ indexed = data._constructor_sliced(data[values]._values, index=multiindex)
+ # error: Argument 1 to "unstack" of "DataFrame" has incompatible type "Union
+ # [List[Any], ExtensionArray, ndarray[Any, Any], Index, Series]"; expected
+ # "Hashable"
+ # unstack with a MultiIndex returns a DataFrame
+ result = cast("DataFrame", indexed.unstack(columns_listlike)) # type: ignore[arg-type]
+ result.index.names = [
+ name if name is not lib.no_default else None for name in result.index.names
+ ]
+
+ return result
+
+
+@set_module("pandas")
+def crosstab(
+ index,
+ columns,
+ values=None,
+ rownames=None,
+ colnames=None,
+ aggfunc=None,
+ margins: bool = False,
+ margins_name: Hashable = "All",
+ dropna: bool = True,
+ normalize: bool | Literal[0, 1, "all", "index", "columns"] = False,
+) -> DataFrame:
+ """
+ Compute a simple cross tabulation of two (or more) factors.
+
+ By default, computes a frequency table of the factors unless an
+ array of values and an aggregation function are passed.
+
+ Parameters
+ ----------
+ index : array-like, Series, or list of arrays/Series
+ Values to group by in the rows.
+ columns : array-like, Series, or list of arrays/Series
+ Values to group by in the columns.
+ values : array-like, optional
+ Array of values to aggregate according to the factors.
+ Requires `aggfunc` be specified.
+ rownames : sequence, default None
+ If passed, must match number of row arrays passed.
+ colnames : sequence, default None
+ If passed, must match number of column arrays passed.
+ aggfunc : function, optional
+ If specified, requires `values` be specified as well.
+ margins : bool, default False
+ Add row/column margins (subtotals).
+ margins_name : str, default 'All'
+ Name of the row/column that will contain the totals
+ when margins is True.
+ dropna : bool, default True
+ Do not include columns whose entries are all NaN.
+ normalize : bool, {'all', 'index', 'columns'}, or {0,1}, default False
+ Normalize by dividing all values by the sum of values.
+
+ - If passed 'all' or `True`, will normalize over all values.
+ - If passed 'index' will normalize over each row.
+ - If passed 'columns' will normalize over each column.
+ - If margins is `True`, will also normalize margin values.
+
+ Returns
+ -------
+ DataFrame
+ Cross tabulation of the data.
+
+ See Also
+ --------
+ DataFrame.pivot : Reshape data based on column values.
+ pivot_table : Create a pivot table as a DataFrame.
+
+ Notes
+ -----
+ Any Series passed will have their name attributes used unless row or column
+ names for the cross-tabulation are specified.
+
+ Any input passed containing Categorical data will have **all** of its
+ categories included in the cross-tabulation, even if the actual data does
+ not contain any instances of a particular category.
+
+ In the event that there aren't overlapping indexes an empty DataFrame will
+ be returned.
+
+ Reference :ref:`the user guide ` for more examples.
+
+ Examples
+ --------
+ >>> a = np.array(
+ ... [
+ ... "foo",
+ ... "foo",
+ ... "foo",
+ ... "foo",
+ ... "bar",
+ ... "bar",
+ ... "bar",
+ ... "bar",
+ ... "foo",
+ ... "foo",
+ ... "foo",
+ ... ],
+ ... dtype=object,
+ ... )
+ >>> b = np.array(
+ ... [
+ ... "one",
+ ... "one",
+ ... "one",
+ ... "two",
+ ... "one",
+ ... "one",
+ ... "one",
+ ... "two",
+ ... "two",
+ ... "two",
+ ... "one",
+ ... ],
+ ... dtype=object,
+ ... )
+ >>> c = np.array(
+ ... [
+ ... "dull",
+ ... "dull",
+ ... "shiny",
+ ... "dull",
+ ... "dull",
+ ... "shiny",
+ ... "shiny",
+ ... "dull",
+ ... "shiny",
+ ... "shiny",
+ ... "shiny",
+ ... ],
+ ... dtype=object,
+ ... )
+ >>> pd.crosstab(a, [b, c], rownames=["a"], colnames=["b", "c"])
+ b one two
+ c dull shiny dull shiny
+ a
+ bar 1 2 1 0
+ foo 2 2 1 2
+
+ Here 'c' and 'f' are not represented in the data and will not be
+ shown in the output because dropna is True by default. Set
+ dropna=False to preserve categories with no data.
+
+ >>> foo = pd.Categorical(["a", "b"], categories=["a", "b", "c"])
+ >>> bar = pd.Categorical(["d", "e"], categories=["d", "e", "f"])
+ >>> pd.crosstab(foo, bar)
+ col_0 d e
+ row_0
+ a 1 0
+ b 0 1
+ >>> pd.crosstab(foo, bar, dropna=False)
+ col_0 d e f
+ row_0
+ a 1 0 0
+ b 0 1 0
+ c 0 0 0
+ """
+ if values is None and aggfunc is not None:
+ raise ValueError("aggfunc cannot be used without values.")
+
+ if values is not None and aggfunc is None:
+ raise ValueError("values cannot be used without an aggfunc.")
+
+ if not is_nested_list_like(index):
+ index = [index]
+ if not is_nested_list_like(columns):
+ columns = [columns]
+
+ common_idx = None
+ pass_objs = [x for x in index + columns if isinstance(x, (ABCSeries, ABCDataFrame))]
+ if pass_objs:
+ common_idx = get_objs_combined_axis(pass_objs, intersect=True, sort=False)
+
+ rownames = _get_names(index, rownames, prefix="row")
+ colnames = _get_names(columns, colnames, prefix="col")
+
+ # duplicate names mapped to unique names for pivot op
+ (
+ rownames_mapper,
+ unique_rownames,
+ colnames_mapper,
+ unique_colnames,
+ ) = _build_names_mapper(rownames, colnames)
+
+ from pandas import DataFrame
+
+ data = {
+ **dict(zip(unique_rownames, index, strict=True)),
+ **dict(zip(unique_colnames, columns, strict=True)),
+ }
+ df = DataFrame(data, index=common_idx)
+
+ if values is None:
+ df["__dummy__"] = 0
+ kwargs = {"aggfunc": len, "fill_value": 0}
+ else:
+ df["__dummy__"] = values
+ kwargs = {"aggfunc": aggfunc}
+
+ # error: Argument 7 to "pivot_table" of "DataFrame" has incompatible type
+ # "**Dict[str, object]"; expected "Union[...]"
+ table = df.pivot_table(
+ "__dummy__",
+ index=unique_rownames,
+ columns=unique_colnames,
+ margins=margins,
+ margins_name=margins_name,
+ dropna=dropna,
+ observed=dropna,
+ **kwargs, # type: ignore[arg-type]
+ )
+
+ # Post-process
+ if normalize is not False:
+ table = _normalize(
+ table, normalize=normalize, margins=margins, margins_name=margins_name
+ )
+
+ table = table.rename_axis(index=rownames_mapper, axis=0)
+ table = table.rename_axis(columns=colnames_mapper, axis=1)
+
+ return table
+
+
+def _normalize(
+ table: DataFrame, normalize, margins: bool, margins_name: Hashable = "All"
+) -> DataFrame:
+ if not isinstance(normalize, (bool, str)):
+ axis_subs = {0: "index", 1: "columns"}
+ try:
+ normalize = axis_subs[normalize]
+ except KeyError as err:
+ raise ValueError("Not a valid normalize argument") from err
+
+ if margins is False:
+ # Actual Normalizations
+ normalizers: dict[bool | str, Callable] = {
+ "all": lambda x: x / x.sum(axis=1).sum(axis=0),
+ "columns": lambda x: x / x.sum(),
+ "index": lambda x: x.div(x.sum(axis=1), axis=0),
+ }
+
+ normalizers[True] = normalizers["all"]
+
+ try:
+ f = normalizers[normalize]
+ except KeyError as err:
+ raise ValueError("Not a valid normalize argument") from err
+
+ table = f(table)
+ table = table.fillna(0)
+
+ elif margins is True:
+ # keep index and column of pivoted table
+ table_index = table.index
+ table_columns = table.columns
+ last_ind_or_col = table.iloc[-1, :].name
+
+ # check if margin name is not in (for MI cases) and not equal to last
+ # index/column and save the column and index margin
+ if (margins_name not in last_ind_or_col) & (margins_name != last_ind_or_col):
+ raise ValueError(f"{margins_name} not in pivoted DataFrame")
+ column_margin = table.iloc[:-1, -1]
+ index_margin = table.iloc[-1, :-1]
+
+ # keep the core table
+ table = table.iloc[:-1, :-1]
+
+ # Normalize core
+ table = _normalize(table, normalize=normalize, margins=False)
+
+ # Fix Margins
+ if normalize == "columns":
+ column_margin = column_margin / column_margin.sum()
+ table = concat([table, column_margin], axis=1)
+ table = table.fillna(0)
+ table.columns = table_columns
+
+ elif normalize == "index":
+ index_margin = index_margin / index_margin.sum()
+ table = table._append_internal(index_margin, ignore_index=True)
+ table = table.fillna(0)
+ table.index = table_index
+
+ elif normalize == "all" or normalize is True:
+ column_margin = column_margin / column_margin.sum()
+ index_margin = index_margin / index_margin.sum()
+ index_margin.loc[margins_name] = 1
+ table = concat([table, column_margin], axis=1)
+ table = table._append_internal(index_margin, ignore_index=True)
+
+ table = table.fillna(0)
+ table.index = table_index
+ table.columns = table_columns
+
+ else:
+ raise ValueError("Not a valid normalize argument")
+
+ else:
+ raise ValueError("Not a valid margins argument")
+
+ return table
+
+
+def _get_names(arrs, names, prefix: str = "row") -> list:
+ if names is None:
+ names = []
+ for i, arr in enumerate(arrs):
+ if isinstance(arr, ABCSeries) and arr.name is not None:
+ names.append(arr.name)
+ else:
+ names.append(f"{prefix}_{i}")
+ else:
+ if len(names) != len(arrs):
+ raise AssertionError("arrays and names must have the same length")
+ if not isinstance(names, list):
+ names = list(names)
+
+ return names
+
+
+def _build_names_mapper(
+ rownames: list[str], colnames: list[str]
+) -> tuple[dict[str, str], list[str], dict[str, str], list[str]]:
+ """
+ Given the names of a DataFrame's rows and columns, returns a set of unique row
+ and column names and mappers that convert to original names.
+
+ A row or column name is replaced if it is duplicate among the rows of the inputs,
+ among the columns of the inputs or between the rows and the columns.
+
+ Parameters
+ ----------
+ rownames: list[str]
+ colnames: list[str]
+
+ Returns
+ -------
+ Tuple(Dict[str, str], List[str], Dict[str, str], List[str])
+
+ rownames_mapper: dict[str, str]
+ a dictionary with new row names as keys and original rownames as values
+ unique_rownames: list[str]
+ a list of rownames with duplicate names replaced by dummy names
+ colnames_mapper: dict[str, str]
+ a dictionary with new column names as keys and original column names as values
+ unique_colnames: list[str]
+ a list of column names with duplicate names replaced by dummy names
+
+ """
+ dup_names = set(rownames) | set(colnames)
+
+ rownames_mapper = {
+ f"row_{i}": name for i, name in enumerate(rownames) if name in dup_names
+ }
+ unique_rownames = [
+ f"row_{i}" if name in dup_names else name for i, name in enumerate(rownames)
+ ]
+
+ colnames_mapper = {
+ f"col_{i}": name for i, name in enumerate(colnames) if name in dup_names
+ }
+ unique_colnames = [
+ f"col_{i}" if name in dup_names else name for i, name in enumerate(colnames)
+ ]
+
+ return rownames_mapper, unique_rownames, colnames_mapper, unique_colnames
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/reshape.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/reshape.py
new file mode 100644
index 0000000000000000000000000000000000000000..46a102cf233f98093e11f8c1500dae4875f9efd5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/reshape.py
@@ -0,0 +1,1124 @@
+from __future__ import annotations
+
+import itertools
+from typing import (
+ TYPE_CHECKING,
+ cast,
+ overload,
+)
+import warnings
+
+import numpy as np
+
+from pandas._config.config import get_option
+
+import pandas._libs.reshape as libreshape
+from pandas.errors import (
+ Pandas4Warning,
+ PerformanceWarning,
+)
+from pandas.util._decorators import cache_readonly
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core.dtypes.cast import (
+ find_common_type,
+ maybe_promote,
+)
+from pandas.core.dtypes.common import (
+ ensure_platform_int,
+ is_1d_only_ea_dtype,
+ is_integer,
+ needs_i8_conversion,
+)
+from pandas.core.dtypes.dtypes import ExtensionDtype
+from pandas.core.dtypes.missing import (
+ isna,
+ notna,
+)
+
+import pandas.core.algorithms as algos
+from pandas.core.algorithms import (
+ factorize,
+ unique,
+)
+from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
+from pandas.core.arrays.categorical import factorize_from_iterable
+from pandas.core.construction import ensure_wrapped_if_datetimelike
+from pandas.core.frame import DataFrame
+from pandas.core.indexes.api import (
+ Index,
+ MultiIndex,
+ default_index,
+)
+from pandas.core.reshape.concat import concat
+from pandas.core.series import Series
+from pandas.core.sorting import (
+ compress_group_index,
+ decons_obs_group_ids,
+ get_compressed_ids,
+ get_group_index,
+ get_group_index_sorter,
+)
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ ArrayLike,
+ Level,
+ npt,
+ )
+
+ from pandas.core.arrays import ExtensionArray
+ from pandas.core.indexes.frozen import FrozenList
+
+
+class _Unstacker:
+ """
+ Helper class to unstack data / pivot with multi-level index
+
+ Parameters
+ ----------
+ index : MultiIndex
+ level : int or str, default last level
+ Level to "unstack". Accepts a name for the level.
+ fill_value : scalar, optional
+ Default value to fill in missing values if subgroups do not have the
+ same set of labels. By default, missing values will be replaced with
+ the default fill value for that data type, NaN for float, NaT for
+ datetimelike, etc. For integer types, by default data will converted to
+ float and missing values will be set to NaN.
+ constructor : object
+ Pandas ``DataFrame`` or subclass used to create unstacked
+ response. If None, DataFrame will be used.
+
+ Examples
+ --------
+ >>> index = pd.MultiIndex.from_tuples(
+ ... [("one", "a"), ("one", "b"), ("two", "a"), ("two", "b")]
+ ... )
+ >>> s = pd.Series(np.arange(1, 5, dtype=np.int64), index=index)
+ >>> s
+ one a 1
+ b 2
+ two a 3
+ b 4
+ dtype: int64
+
+ >>> s.unstack(level=-1)
+ a b
+ one 1 2
+ two 3 4
+
+ >>> s.unstack(level=0)
+ one two
+ a 1 3
+ b 2 4
+
+ Returns
+ -------
+ unstacked : DataFrame
+ """
+
+ def __init__(
+ self, index: MultiIndex, level: Level, constructor, sort: bool = True
+ ) -> None:
+ self.constructor = constructor
+ self.sort = sort
+
+ self.index = index.remove_unused_levels()
+
+ self.level = self.index._get_level_number(level)
+
+ # `nan` values have code `-1`, when sorting, we lift to assign them
+ # at index 0
+ self.has_nan = -1 in self.index.codes[self.level]
+ should_lift = self.has_nan and self.sort
+ self.lift = 1 if should_lift else 0
+
+ # Note: the "pop" below alters these in-place.
+ self.new_index_levels = list(self.index.levels)
+ self.new_index_names = list(self.index.names)
+
+ self.removed_name = self.new_index_names.pop(self.level)
+ self.removed_level = self.new_index_levels.pop(self.level)
+ self.removed_level_full = index.levels[self.level]
+ self.unique_nan_index: int = -1
+ if not self.sort:
+ unique_codes: np.ndarray = unique(self.index.codes[self.level])
+ if self.has_nan:
+ # drop nan codes, because they are not represented in level
+ nan_mask = unique_codes == -1
+
+ unique_codes = unique_codes[~nan_mask]
+ self.unique_nan_index = np.flatnonzero(nan_mask)[0]
+
+ self.removed_level = self.removed_level.take(unique_codes)
+ self.removed_level_full = self.removed_level_full.take(unique_codes)
+
+ if get_option("performance_warnings"):
+ # Bug fix GH 20601
+ # If the data frame is too big, the number of unique index combination
+ # will cause int32 overflow on windows environments.
+ # We want to check and raise a warning before this happens
+ num_rows = max(index_level.size for index_level in self.new_index_levels)
+ num_columns = self.removed_level.size
+
+ # GH20601: This forces an overflow if the number of cells is too high.
+ # GH 26314: Previous ValueError raised was too restrictive for many users.
+ num_cells = num_rows * num_columns
+ if num_cells > np.iinfo(np.int32).max:
+ warnings.warn(
+ f"The following operation may generate {num_cells} cells "
+ f"in the resulting pandas object.",
+ PerformanceWarning,
+ stacklevel=find_stack_level(),
+ )
+
+ self._make_selectors()
+
+ @cache_readonly
+ def _indexer_and_to_sort(
+ self,
+ ) -> tuple[
+ npt.NDArray[np.intp],
+ list[np.ndarray], # each has _some_ signed integer dtype
+ ]:
+ v = self.level
+
+ codes = list(self.index.codes)
+ if not self.sort:
+ # Create new codes considering that labels are already sorted
+ codes = [factorize(code)[0] for code in codes]
+ levs = list(self.index.levels)
+ to_sort = codes[:v] + codes[v + 1 :] + [codes[v]]
+ sizes = tuple(len(x) for x in levs[:v] + levs[v + 1 :] + [levs[v]])
+
+ comp_index, obs_ids = get_compressed_ids(to_sort, sizes)
+ ngroups = len(obs_ids)
+
+ indexer = get_group_index_sorter(comp_index, ngroups)
+ return indexer, to_sort
+
+ @cache_readonly
+ def sorted_labels(self) -> list[np.ndarray]:
+ indexer, to_sort = self._indexer_and_to_sort
+ if self.sort:
+ return [line.take(indexer) for line in to_sort]
+ return to_sort
+
+ def _make_sorted_values(self, values: np.ndarray) -> np.ndarray:
+ indexer, _ = self._indexer_and_to_sort
+ sorted_values = algos.take_nd(values, indexer, axis=0)
+ return sorted_values
+
+ def _make_selectors(self) -> None:
+ new_levels = self.new_index_levels
+
+ # make the mask
+ remaining_labels = self.sorted_labels[:-1]
+ level_sizes = tuple(len(x) for x in new_levels)
+
+ comp_index, obs_ids = get_compressed_ids(remaining_labels, level_sizes)
+ ngroups = len(obs_ids)
+
+ comp_index = ensure_platform_int(comp_index)
+ stride = self.index.levshape[self.level] + self.has_nan
+ self.full_shape = ngroups, stride
+
+ selector = self.sorted_labels[-1] + stride * comp_index + self.lift
+ mask = np.zeros(np.prod(self.full_shape), dtype=bool)
+ mask.put(selector, True)
+
+ if mask.sum() < len(self.index):
+ raise ValueError("Index contains duplicate entries, cannot reshape")
+
+ self.group_index = comp_index
+ self.mask = mask
+ if self.sort:
+ self.compressor = comp_index.searchsorted(np.arange(ngroups))
+ else:
+ self.compressor = np.sort(np.unique(comp_index, return_index=True)[1])
+
+ @cache_readonly
+ def mask_all(self) -> bool:
+ return bool(self.mask.all())
+
+ @cache_readonly
+ def arange_result(self) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.bool_]]:
+ # We cache this for reuse in ExtensionBlock._unstack
+ dummy_arr = np.arange(len(self.index), dtype=np.intp)
+ new_values, mask = self.get_new_values(dummy_arr, fill_value=-1)
+ return new_values, mask.any(0)
+ # TODO: in all tests we have mask.any(0).all(); can we rely on that?
+
+ def get_result(self, obj, value_columns, fill_value) -> DataFrame:
+ values = obj._values
+ if values.ndim == 1:
+ values = values[:, np.newaxis]
+
+ if value_columns is None and values.shape[1] != 1: # pragma: no cover
+ raise ValueError("must pass column labels for multi-column data")
+
+ new_values, _ = self.get_new_values(values, fill_value)
+ columns = self.get_new_columns(value_columns)
+ index = self.new_index
+
+ result = self.constructor(
+ new_values, index=index, columns=columns, dtype=new_values.dtype, copy=False
+ )
+ if isinstance(values, np.ndarray):
+ base, new_base = values.base, new_values.base
+ elif isinstance(values, NDArrayBackedExtensionArray):
+ base, new_base = values._ndarray.base, new_values._ndarray.base
+ else:
+ base, new_base = 1, 2 # type: ignore[assignment]
+ if base is new_base:
+ # We can only get here if one of the dimensions is size 1
+ result._mgr.add_references(obj._mgr)
+ return result
+
+ def get_new_values(self, values, fill_value=None):
+ if values.ndim == 1:
+ values = values[:, np.newaxis]
+
+ sorted_values = self._make_sorted_values(values)
+
+ # place the values
+ length, width = self.full_shape
+ stride = values.shape[1]
+ result_width = width * stride
+ result_shape = (length, result_width)
+ mask = self.mask
+ mask_all = self.mask_all
+
+ # we can simply reshape if we don't have a mask
+ if mask_all and len(values):
+ # TODO: Under what circumstances can we rely on sorted_values
+ # matching values? When that holds, we can slice instead
+ # of take (in particular for EAs)
+ new_values = (
+ sorted_values.reshape(length, width, stride)
+ .swapaxes(1, 2)
+ .reshape(result_shape)
+ )
+ new_mask = np.ones(result_shape, dtype=bool)
+ return new_values, new_mask
+
+ dtype = values.dtype
+
+ if isinstance(dtype, ExtensionDtype):
+ # GH#41875
+ # We are assuming that fill_value can be held by this dtype,
+ # unlike the non-EA case that promotes.
+ cls = dtype.construct_array_type()
+ new_values = cls._empty(result_shape, dtype=dtype)
+ if not mask_all:
+ new_values[:] = fill_value
+ else:
+ if not mask_all:
+ old_dtype = dtype
+ dtype, fill_value = maybe_promote(dtype, fill_value)
+ if old_dtype != dtype:
+ if old_dtype.kind not in "iub":
+ warnings.warn(
+ # GH#12189, GH#53868
+ "Using a fill_value that cannot be held in the existing "
+ "dtype is deprecated and will raise in a future version.",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+ elif not isna(fill_value):
+ warnings.warn(
+ # GH#12189, GH#53868
+ "Using a fill_value that cannot be held in the existing "
+ "dtype is deprecated and will raise in a future version.",
+ Pandas4Warning,
+ stacklevel=find_stack_level(),
+ )
+ new_values = np.empty(result_shape, dtype=dtype)
+ if not mask_all:
+ new_values.fill(fill_value)
+
+ name = dtype.name
+ new_mask = np.zeros(result_shape, dtype=bool)
+
+ # we need to convert to a basic dtype
+ # and possibly coerce an input to our output dtype
+ # e.g. ints -> floats
+ if needs_i8_conversion(values.dtype):
+ sorted_values = sorted_values.view("i8")
+ new_values = new_values.view("i8")
+ else:
+ sorted_values = sorted_values.astype(name, copy=False)
+
+ # fill in our values & mask
+ libreshape.unstack(
+ sorted_values,
+ mask.view("u1"),
+ stride,
+ length,
+ width,
+ new_values,
+ new_mask.view("u1"),
+ )
+
+ # reconstruct dtype if needed
+ if needs_i8_conversion(values.dtype):
+ # view as datetime64 so we can wrap in DatetimeArray and use
+ # DTA's view method
+ new_values = new_values.view("M8[ns]")
+ new_values = ensure_wrapped_if_datetimelike(new_values)
+ new_values = new_values.view(values.dtype)
+
+ return new_values, new_mask
+
+ def get_new_columns(self, value_columns: Index | None):
+ if value_columns is None:
+ if not self.has_nan:
+ return self.removed_level._rename(name=self.removed_name)
+
+ lev = self.removed_level.insert(0, item=self.removed_level._na_value)
+ return lev.rename(self.removed_name)
+
+ stride = len(self.removed_level) + self.has_nan
+ width = len(value_columns)
+ propagator = np.repeat(np.arange(width), stride)
+
+ new_levels: FrozenList | list[Index]
+
+ if isinstance(value_columns, MultiIndex):
+ new_levels = value_columns.levels + (self.removed_level_full,) # pyright: ignore[reportOperatorIssue] # noqa: RUF005
+ new_names = value_columns.names + (self.removed_name,) # noqa: RUF005
+
+ new_codes = [lab.take(propagator) for lab in value_columns.codes]
+ else:
+ new_levels = [
+ value_columns,
+ self.removed_level_full,
+ ]
+ new_names = [value_columns.name, self.removed_name]
+ new_codes = [propagator]
+
+ repeater = self._repeater
+
+ # The entire level is then just a repetition of the single chunk:
+ new_codes.append(np.tile(repeater, width))
+ return MultiIndex(
+ levels=new_levels, codes=new_codes, names=new_names, verify_integrity=False
+ )
+
+ @cache_readonly
+ def _repeater(self) -> np.ndarray:
+ # The two indices differ only if the unstacked level had unused items:
+ if len(self.removed_level_full) != len(self.removed_level):
+ # In this case, we remap the new codes to the original level:
+ repeater = self.removed_level_full.get_indexer(self.removed_level)
+ if self.has_nan:
+ # insert nan index at first position
+ repeater = np.insert(repeater, 0, -1)
+ else:
+ # Otherwise, we just use each level item exactly once:
+ stride = len(self.removed_level) + self.has_nan
+ repeater = np.arange(stride) - self.lift
+ if self.has_nan and not self.sort:
+ assert self.unique_nan_index > -1, (
+ "`unique_nan_index` not properly initialized"
+ )
+ # assign -1 where should be nan according to the unique values.
+ repeater[self.unique_nan_index] = -1
+ # compensate for the removed index level
+ repeater[self.unique_nan_index + 1 :] -= 1
+
+ return repeater
+
+ @cache_readonly
+ def new_index(self) -> MultiIndex | Index:
+ # Does not depend on values or value_columns
+ if self.sort:
+ labels = self.sorted_labels[:-1]
+ else:
+ v = self.level
+ codes = list(self.index.codes)
+ labels = codes[:v] + codes[v + 1 :]
+ result_codes = [lab.take(self.compressor) for lab in labels]
+
+ # construct the new index
+ if len(self.new_index_levels) == 1:
+ level, level_codes = self.new_index_levels[0], result_codes[0]
+ if (level_codes == -1).any():
+ level = level.insert(len(level), level._na_value)
+ return level.take(level_codes).rename(self.new_index_names[0])
+
+ return MultiIndex(
+ levels=self.new_index_levels,
+ codes=result_codes,
+ names=self.new_index_names,
+ verify_integrity=False,
+ )
+
+
+def _unstack_multiple(
+ data: Series | DataFrame, clocs, fill_value=None, sort: bool = True
+):
+ if len(clocs) == 0:
+ return data
+
+ # NOTE: This doesn't deal with hierarchical columns yet
+
+ index = data.index
+ index = cast(MultiIndex, index) # caller is responsible for checking
+
+ # GH 19966 Make sure if MultiIndexed index has tuple name, they will be
+ # recognised as a whole
+ if clocs in index.names:
+ clocs = [clocs]
+ clocs = [index._get_level_number(i) for i in clocs]
+
+ rlocs = [i for i in range(index.nlevels) if i not in clocs]
+
+ clevels = [index.levels[i] for i in clocs]
+ ccodes = [index.codes[i] for i in clocs]
+ cnames = [index.names[i] for i in clocs]
+ rlevels = [index.levels[i] for i in rlocs]
+ rcodes = [index.codes[i] for i in rlocs]
+ rnames = [index.names[i] for i in rlocs]
+
+ shape = tuple(len(x) for x in clevels)
+ group_index = get_group_index(ccodes, shape, sort=False, xnull=False)
+
+ comp_ids, obs_ids = compress_group_index(group_index, sort=False)
+ recons_codes = decons_obs_group_ids(comp_ids, obs_ids, shape, ccodes, xnull=False)
+
+ if not rlocs:
+ # Everything is in clocs, so the dummy df has a regular index
+ dummy_index = Index(obs_ids, name="__placeholder__", copy=False)
+ else:
+ dummy_index = MultiIndex(
+ levels=[*rlevels, obs_ids],
+ codes=[*rcodes, comp_ids],
+ names=[*rnames, "__placeholder__"],
+ verify_integrity=False,
+ )
+
+ if isinstance(data, Series):
+ dummy = data.copy(deep=False)
+ dummy.index = dummy_index
+
+ unstacked = dummy.unstack("__placeholder__", fill_value=fill_value, sort=sort)
+ new_levels = clevels
+ new_names = cnames
+ new_codes = recons_codes
+ else:
+ if isinstance(data.columns, MultiIndex):
+ result = data
+ while clocs:
+ val = clocs.pop(0)
+ # error: Incompatible types in assignment (expression has type
+ # "DataFrame | Series", variable has type "DataFrame")
+ result = result.unstack( # type: ignore[assignment]
+ val, fill_value=fill_value, sort=sort
+ )
+ clocs = [v if v < val else v - 1 for v in clocs]
+
+ return result
+
+ # GH#42579 deep=False to avoid consolidating
+ dummy_df = data.copy(deep=False)
+ dummy_df.index = dummy_index
+
+ # error: Incompatible types in assignment (expression has type "DataFrame |
+ # Series", variable has type "DataFrame")
+ unstacked = dummy_df.unstack( # type: ignore[assignment]
+ "__placeholder__", fill_value=fill_value, sort=sort
+ )
+ if isinstance(unstacked, Series):
+ unstcols = unstacked.index
+ else:
+ unstcols = unstacked.columns
+ assert isinstance(unstcols, MultiIndex) # for mypy
+ new_levels = [unstcols.levels[0], *clevels]
+ new_names = [data.columns.name, *cnames]
+
+ new_codes = [unstcols.codes[0]]
+ new_codes.extend(rec.take(unstcols.codes[-1]) for rec in recons_codes)
+
+ new_columns = MultiIndex(
+ levels=new_levels, codes=new_codes, names=new_names, verify_integrity=False
+ )
+
+ if isinstance(unstacked, Series):
+ unstacked.index = new_columns
+ else:
+ unstacked.columns = new_columns
+
+ return unstacked
+
+
+@overload
+def unstack(obj: Series, level, fill_value=..., sort: bool = ...) -> DataFrame: ...
+
+
+@overload
+def unstack(
+ obj: Series | DataFrame, level, fill_value=..., sort: bool = ...
+) -> Series | DataFrame: ...
+
+
+def unstack(
+ obj: Series | DataFrame, level, fill_value=None, sort: bool = True
+) -> Series | DataFrame:
+ if isinstance(level, (tuple, list)):
+ if len(level) != 1:
+ # _unstack_multiple only handles MultiIndexes,
+ # and isn't needed for a single level
+ return _unstack_multiple(obj, level, fill_value=fill_value, sort=sort)
+ else:
+ level = level[0]
+
+ if not is_integer(level) and not level == "__placeholder__":
+ # check if level is valid in case of regular index
+ obj.index._get_level_number(level)
+
+ if isinstance(obj, DataFrame):
+ if isinstance(obj.index, MultiIndex):
+ return _unstack_frame(obj, level, fill_value=fill_value, sort=sort)
+ else:
+ return obj.T.stack()
+ elif not isinstance(obj.index, MultiIndex):
+ # GH 36113
+ # Give nicer error messages when unstack a Series whose
+ # Index is not a MultiIndex.
+ raise ValueError(
+ f"index must be a MultiIndex to unstack, {type(obj.index)} was passed"
+ )
+ else:
+ if is_1d_only_ea_dtype(obj.dtype):
+ return _unstack_extension_series(obj, level, fill_value, sort=sort)
+ unstacker = _Unstacker(
+ obj.index, level=level, constructor=obj._constructor_expanddim, sort=sort
+ )
+ return unstacker.get_result(obj, value_columns=None, fill_value=fill_value)
+
+
+def _unstack_frame(
+ obj: DataFrame, level, fill_value=None, sort: bool = True
+) -> DataFrame:
+ assert isinstance(obj.index, MultiIndex) # checked by caller
+ unstacker = _Unstacker(
+ obj.index, level=level, constructor=obj._constructor, sort=sort
+ )
+
+ if not obj._can_fast_transpose:
+ mgr = obj._mgr.unstack(unstacker, fill_value=fill_value)
+ return obj._constructor_from_mgr(mgr, axes=mgr.axes)
+ else:
+ return unstacker.get_result(
+ obj, value_columns=obj.columns, fill_value=fill_value
+ )
+
+
+def _unstack_extension_series(
+ series: Series, level, fill_value, sort: bool
+) -> DataFrame:
+ """
+ Unstack an ExtensionArray-backed Series.
+
+ The ExtensionDtype is preserved.
+
+ Parameters
+ ----------
+ series : Series
+ A Series with an ExtensionArray for values
+ level : Any
+ The level name or number.
+ fill_value : Any
+ The user-level (not physical storage) fill value to use for
+ missing values introduced by the reshape. Passed to
+ ``series.values.take``.
+ sort : bool
+ Whether to sort the resulting MuliIndex levels
+
+ Returns
+ -------
+ DataFrame
+ Each column of the DataFrame will have the same dtype as
+ the input Series.
+ """
+ # Defer to the logic in ExtensionBlock._unstack
+ df = series.to_frame()
+ result = df.unstack(level=level, fill_value=fill_value, sort=sort)
+
+ # equiv: result.droplevel(level=0, axis=1)
+ # but this avoids an extra copy
+ result.columns = result.columns._drop_level_numbers([0])
+ # error: Incompatible return value type (got "DataFrame | Series", expected
+ # "DataFrame")
+ return result # type: ignore[return-value]
+
+
+def stack(
+ frame: DataFrame, level=-1, dropna: bool = True, sort: bool = True
+) -> Series | DataFrame:
+ """
+ Convert DataFrame to Series with multi-level Index. Columns become the
+ second level of the resulting hierarchical index
+
+ Returns
+ -------
+ stacked : Series or DataFrame
+ """
+
+ def stack_factorize(index):
+ if index.is_unique:
+ return index, np.arange(len(index))
+ codes, categories = factorize_from_iterable(index)
+ return categories, codes
+
+ N, K = frame.shape
+
+ # Will also convert negative level numbers and check if out of bounds.
+ level_num = frame.columns._get_level_number(level)
+
+ if isinstance(frame.columns, MultiIndex):
+ return _stack_multi_columns(
+ frame, level_num=level_num, dropna=dropna, sort=sort
+ )
+ elif isinstance(frame.index, MultiIndex):
+ new_levels = list(frame.index.levels)
+ new_codes = [lab.repeat(K) for lab in frame.index.codes]
+
+ clev, clab = stack_factorize(frame.columns)
+ new_levels.append(clev)
+ new_codes.append(np.tile(clab, N).ravel())
+
+ new_names = list(frame.index.names)
+ new_names.append(frame.columns.name)
+ new_index = MultiIndex(
+ levels=new_levels, codes=new_codes, names=new_names, verify_integrity=False
+ )
+ else:
+ levels, (ilab, clab) = zip(
+ *map(stack_factorize, (frame.index, frame.columns)), strict=True
+ )
+ codes = ilab.repeat(K), np.tile(clab, N).ravel()
+ new_index = MultiIndex(
+ levels=levels,
+ codes=codes,
+ names=[frame.index.name, frame.columns.name],
+ verify_integrity=False,
+ )
+
+ new_values: ArrayLike
+ if not frame.empty and frame._is_homogeneous_type:
+ # For homogeneous EAs, frame._values will coerce to object. So
+ # we concatenate instead.
+ dtypes = list(frame.dtypes._values)
+ dtype = dtypes[0]
+
+ if isinstance(dtype, ExtensionDtype):
+ arr = dtype.construct_array_type()
+ new_values = arr._concat_same_type(
+ [col._values for _, col in frame.items()]
+ )
+ new_values = _reorder_for_extension_array_stack(new_values, N, K)
+ else:
+ # homogeneous, non-EA
+ new_values = frame._values.ravel()
+
+ else:
+ # non-homogeneous
+ new_values = frame._values.ravel()
+
+ if dropna:
+ mask = notna(new_values)
+ new_values = new_values[mask]
+ new_index = new_index[mask]
+
+ return frame._constructor_sliced(new_values, index=new_index)
+
+
+def stack_multiple(frame: DataFrame, level, dropna: bool = True, sort: bool = True):
+ # If all passed levels match up to column names, no
+ # ambiguity about what to do
+ if all(lev in frame.columns.names for lev in level):
+ result = frame
+ for lev in level:
+ # error: Incompatible types in assignment (expression has type
+ # "Series | DataFrame", variable has type "DataFrame")
+ result = stack(result, lev, dropna=dropna, sort=sort) # type: ignore[assignment]
+
+ # Otherwise, level numbers may change as each successive level is stacked
+ elif all(isinstance(lev, int) for lev in level):
+ # As each stack is done, the level numbers decrease, so we need
+ # to account for that when level is a sequence of ints
+ result = frame
+ # _get_level_number() checks level numbers are in range and converts
+ # negative numbers to positive
+ level = [frame.columns._get_level_number(lev) for lev in level]
+
+ while level:
+ lev = level.pop(0)
+ # error: Incompatible types in assignment (expression has type
+ # "Series | DataFrame", variable has type "DataFrame")
+ result = stack(result, lev, dropna=dropna, sort=sort) # type: ignore[assignment]
+ # Decrement all level numbers greater than current, as these
+ # have now shifted down by one
+ level = [v if v <= lev else v - 1 for v in level]
+
+ else:
+ raise ValueError(
+ "level should contain all level names or all level "
+ "numbers, not a mixture of the two."
+ )
+
+ return result
+
+
+def _stack_multi_column_index(columns: MultiIndex) -> MultiIndex | Index:
+ """Creates a MultiIndex from the first N-1 levels of this MultiIndex."""
+ if len(columns.levels) <= 2:
+ return columns.levels[0]._rename(name=columns.names[0])
+
+ levs = (
+ [lev[c] if c >= 0 else None for c in codes]
+ for lev, codes in zip(columns.levels[:-1], columns.codes[:-1], strict=True)
+ )
+
+ # Remove duplicate tuples in the MultiIndex.
+ tuples = zip(*levs, strict=True)
+ unique_tuples = (key for key, _ in itertools.groupby(tuples))
+ new_levs = zip(*unique_tuples, strict=True)
+
+ # The dtype of each level must be explicitly set to avoid inferring the wrong type.
+ # See GH-36991.
+ return MultiIndex.from_arrays(
+ [
+ # Not all indices can accept None values.
+ Index(new_lev, dtype=lev.dtype) if None not in new_lev else new_lev
+ for new_lev, lev in zip(new_levs, columns.levels[:-1], strict=True)
+ ],
+ names=columns.names[:-1],
+ )
+
+
+def _stack_multi_columns(
+ frame: DataFrame, level_num: int = -1, dropna: bool = True, sort: bool = True
+) -> DataFrame:
+ def _convert_level_number(level_num: int, columns: Index):
+ """
+ Logic for converting the level number to something we can safely pass
+ to swaplevel.
+
+ If `level_num` matches a column name return the name from
+ position `level_num`, otherwise return `level_num`.
+ """
+ if level_num in columns.names:
+ return columns.names[level_num]
+
+ return level_num
+
+ this = frame.copy(deep=False)
+ mi_cols = this.columns # cast(MultiIndex, this.columns)
+ assert isinstance(mi_cols, MultiIndex) # caller is responsible
+
+ # this makes life much simpler
+ if level_num != mi_cols.nlevels - 1:
+ # roll levels to put selected level at end
+ roll_columns = mi_cols
+ for i in range(level_num, mi_cols.nlevels - 1):
+ # Need to check if the ints conflict with level names
+ lev1 = _convert_level_number(i, roll_columns)
+ lev2 = _convert_level_number(i + 1, roll_columns)
+ roll_columns = roll_columns.swaplevel(lev1, lev2)
+ this.columns = mi_cols = roll_columns
+
+ if not mi_cols._is_lexsorted() and sort:
+ # Workaround the edge case where 0 is one of the column names,
+ # which interferes with trying to sort based on the first
+ # level
+ level_to_sort = _convert_level_number(0, mi_cols)
+ this = this.sort_index(level=level_to_sort, axis=1)
+ mi_cols = this.columns
+
+ mi_cols = cast(MultiIndex, mi_cols)
+ new_columns = _stack_multi_column_index(mi_cols)
+
+ # time to ravel the values
+ new_data = {}
+ level_vals = mi_cols.levels[-1]
+ level_codes = unique(mi_cols.codes[-1])
+ if sort:
+ level_codes = np.sort(level_codes)
+ level_vals_nan = level_vals.insert(len(level_vals), None)
+
+ level_vals_used = np.take(level_vals_nan, level_codes)
+ levsize = len(level_codes)
+ drop_cols = []
+ for key in new_columns:
+ try:
+ loc = this.columns.get_loc(key)
+ except KeyError:
+ drop_cols.append(key)
+ continue
+
+ # can make more efficient?
+ # we almost always return a slice
+ # but if unsorted can get a boolean
+ # indexer
+ if not isinstance(loc, slice):
+ slice_len = len(loc)
+ else:
+ slice_len = loc.stop - loc.start
+
+ if slice_len != levsize:
+ chunk = this.loc[:, this.columns[loc]]
+ chunk.columns = level_vals_nan.take(chunk.columns.codes[-1])
+ value_slice = chunk.reindex(columns=level_vals_used).values
+ else:
+ subset = this.iloc[:, loc]
+ dtype = find_common_type(subset.dtypes.tolist())
+ if isinstance(dtype, ExtensionDtype):
+ # TODO(EA2D): won't need special case, can go through .values
+ # paths below (might change to ._values)
+ value_slice = dtype.construct_array_type()._concat_same_type(
+ [x._values.astype(dtype, copy=False) for _, x in subset.items()]
+ )
+ N, K = subset.shape
+ idx = np.arange(N * K).reshape(K, N).T.reshape(-1)
+ value_slice = value_slice.take(idx)
+ else:
+ value_slice = subset.values
+
+ if value_slice.ndim > 1:
+ # i.e. not extension
+ value_slice = value_slice.ravel()
+
+ new_data[key] = value_slice
+
+ if len(drop_cols) > 0:
+ new_columns = new_columns.difference(drop_cols)
+
+ N = len(this)
+
+ if isinstance(this.index, MultiIndex):
+ new_levels = list(this.index.levels)
+ new_names = list(this.index.names)
+ new_codes = [lab.repeat(levsize) for lab in this.index.codes]
+ else:
+ old_codes, old_levels = factorize_from_iterable(this.index)
+ new_levels = [old_levels]
+ new_codes = [old_codes.repeat(levsize)]
+ new_names = [this.index.name] # something better?
+
+ new_levels.append(level_vals)
+ new_codes.append(np.tile(level_codes, N))
+ new_names.append(frame.columns.names[level_num])
+
+ new_index = MultiIndex(
+ levels=new_levels, codes=new_codes, names=new_names, verify_integrity=False
+ )
+
+ result = frame._constructor(new_data, index=new_index, columns=new_columns)
+
+ if frame.columns.nlevels > 1:
+ desired_columns = frame.columns._drop_level_numbers([level_num]).unique()
+ if not result.columns.equals(desired_columns):
+ result = result[desired_columns]
+
+ # more efficient way to go about this? can do the whole masking biz but
+ # will only save a small amount of time...
+ if dropna:
+ result = result.dropna(axis=0, how="all")
+
+ return result
+
+
+def _reorder_for_extension_array_stack(
+ arr: ExtensionArray, n_rows: int, n_columns: int
+) -> ExtensionArray:
+ """
+ Re-orders the values when stacking multiple extension-arrays.
+
+ The indirect stacking method used for EAs requires a followup
+ take to get the order correct.
+
+ Parameters
+ ----------
+ arr : ExtensionArray
+ n_rows, n_columns : int
+ The number of rows and columns in the original DataFrame.
+
+ Returns
+ -------
+ taken : ExtensionArray
+ The original `arr` with elements re-ordered appropriately
+
+ Examples
+ --------
+ >>> arr = np.array(["a", "b", "c", "d", "e", "f"])
+ >>> _reorder_for_extension_array_stack(arr, 2, 3)
+ array(['a', 'c', 'e', 'b', 'd', 'f'], dtype='>> _reorder_for_extension_array_stack(arr, 3, 2)
+ array(['a', 'd', 'b', 'e', 'c', 'f'], dtype=' Series | DataFrame:
+ if frame.columns.nunique() != len(frame.columns):
+ raise ValueError("Columns with duplicate values are not supported in stack")
+ if not len(level):
+ return frame
+ set_levels = set(level)
+ stack_cols = frame.columns._drop_level_numbers(
+ [k for k in range(frame.columns.nlevels - 1, -1, -1) if k not in set_levels]
+ )
+
+ result: Series | DataFrame
+ if not isinstance(frame.columns, MultiIndex):
+ # GH#58817 Fast path when we're stacking the columns of a non-MultiIndex.
+ # When columns are homogeneous EAs, we pass through object
+ # dtype but this is still slightly faster than the normal path.
+ if len(frame.columns) > 0 and frame._is_homogeneous_type:
+ dtype = frame._mgr.blocks[0].dtype
+ else:
+ dtype = None
+ result = frame._constructor_sliced(
+ frame._values.reshape(-1, order="F"), dtype=dtype
+ )
+ else:
+ result = stack_reshape(frame, level, set_levels, stack_cols)
+
+ # Construct the correct MultiIndex by combining the frame's index and
+ # stacked columns.
+ ratio = 0 if frame.empty else len(result) // len(frame)
+
+ index_levels: list | FrozenList
+ if isinstance(frame.index, MultiIndex):
+ index_levels = frame.index.levels
+ index_codes = list(np.tile(frame.index.codes, (1, ratio)))
+ else:
+ codes, uniques = factorize(frame.index, use_na_sentinel=False)
+ index_levels = [uniques]
+ index_codes = list(np.tile(codes, (1, ratio)))
+
+ if len(level) > 1:
+ # Arrange columns in the order we want to take them, e.g. level=[2, 0, 1]
+ sorter = np.argsort(level)
+ assert isinstance(stack_cols, MultiIndex)
+ ordered_stack_cols = stack_cols._reorder_ilevels(sorter)
+ else:
+ ordered_stack_cols = stack_cols
+ ordered_stack_cols_unique = ordered_stack_cols.unique()
+ if isinstance(ordered_stack_cols, MultiIndex):
+ column_levels = ordered_stack_cols.levels
+ column_codes = ordered_stack_cols.drop_duplicates().codes
+ else:
+ column_levels = [ordered_stack_cols_unique]
+ column_codes = [factorize(ordered_stack_cols_unique, use_na_sentinel=False)[0]]
+
+ # error: Incompatible types in assignment (expression has type "list[ndarray[Any,
+ # dtype[Any]]]", variable has type "FrozenList")
+ column_codes = [np.repeat(codes, len(frame)) for codes in column_codes] # type: ignore[assignment]
+ result.index = MultiIndex(
+ levels=index_levels + column_levels,
+ codes=index_codes + column_codes,
+ names=frame.index.names + list(ordered_stack_cols.names),
+ verify_integrity=False,
+ )
+
+ # sort result, but faster than calling sort_index since we know the order we need
+ len_df = len(frame)
+ n_uniques = len(ordered_stack_cols_unique)
+ indexer = np.arange(n_uniques)
+ idxs = np.tile(len_df * indexer, len_df) + np.repeat(np.arange(len_df), n_uniques)
+ result = result.take(idxs)
+
+ # Reshape/rename if needed and dropna
+ if result.ndim == 2 and frame.columns.nlevels == len(level):
+ if len(result.columns) == 0:
+ result = Series(index=result.index)
+ else:
+ result = result.iloc[:, 0]
+ if result.ndim == 1:
+ result.name = None
+
+ return result
+
+
+def stack_reshape(
+ frame: DataFrame, level: list[int], set_levels: set[int], stack_cols: Index
+) -> Series | DataFrame:
+ """Reshape the data of a frame for stack.
+
+ This function takes care of most of the work that stack needs to do. Caller
+ will sort the result once the appropriate index is set.
+
+ Parameters
+ ----------
+ frame: DataFrame
+ DataFrame that is to be stacked.
+ level: list of ints.
+ Levels of the columns to stack.
+ set_levels: set of ints.
+ Same as level, but as a set.
+ stack_cols: Index.
+ Columns of the result when the DataFrame is stacked.
+
+ Returns
+ -------
+ The data of behind the stacked DataFrame.
+ """
+ # non-MultIndex takes a fast path.
+ assert isinstance(frame.columns, MultiIndex)
+ # If we need to drop `level` from columns, it needs to be in descending order
+ drop_levnums = sorted(level, reverse=True)
+
+ # Grab data for each unique index to be stacked
+ buf = []
+ for idx in stack_cols.unique():
+ if len(frame.columns) == 1:
+ data = frame.copy(deep=False)
+ else:
+ # Take the data from frame corresponding to this idx value
+ if len(level) == 1:
+ idx = (idx,)
+ gen = iter(idx)
+ column_indexer = tuple(
+ next(gen) if k in set_levels else slice(None)
+ for k in range(frame.columns.nlevels)
+ )
+ data = frame.loc[:, column_indexer]
+
+ if len(level) < frame.columns.nlevels:
+ data.columns = data.columns._drop_level_numbers(drop_levnums)
+ elif stack_cols.nlevels == 1:
+ if data.ndim == 1:
+ data.name = 0
+ else:
+ data.columns = default_index(len(data.columns))
+ buf.append(data)
+
+ if len(buf) > 0 and not frame.empty:
+ result = concat(buf, ignore_index=True)
+ else:
+ # input is empty
+ if len(level) < frame.columns.nlevels:
+ # concat column order may be different from dropping the levels
+ new_columns = frame.columns._drop_level_numbers(drop_levnums).unique()
+ else:
+ new_columns = [0]
+ result = DataFrame(columns=new_columns, dtype=frame._values.dtype)
+
+ if len(level) < frame.columns.nlevels:
+ # concat column order may be different from dropping the levels
+ desired_columns = frame.columns._drop_level_numbers(drop_levnums).unique()
+ if not result.columns.equals(desired_columns):
+ result = result[desired_columns]
+
+ return result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/tile.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/tile.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f65edb42724586a29ca3cc6fc5ebffe4d8d24c3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/reshape/tile.py
@@ -0,0 +1,682 @@
+"""
+Quantilization functions and related stuff
+"""
+
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ cast,
+)
+
+import numpy as np
+
+from pandas._libs import (
+ Timedelta,
+ Timestamp,
+ lib,
+)
+from pandas.util._decorators import set_module
+
+from pandas.core.dtypes.common import (
+ ensure_platform_int,
+ is_bool_dtype,
+ is_integer,
+ is_list_like,
+ is_numeric_dtype,
+ is_scalar,
+)
+from pandas.core.dtypes.dtypes import (
+ CategoricalDtype,
+ DatetimeTZDtype,
+ ExtensionDtype,
+)
+from pandas.core.dtypes.generic import ABCSeries
+from pandas.core.dtypes.missing import isna
+
+from pandas import (
+ Categorical,
+ Index,
+ IntervalIndex,
+)
+import pandas.core.algorithms as algos
+from pandas.core.arrays.datetimelike import dtype_to_unit
+from pandas.core.col import Expression
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
+ from pandas._typing import (
+ DtypeObj,
+ IntervalLeftRight,
+ TimeUnit,
+ )
+
+
+@set_module("pandas")
+def cut(
+ x,
+ bins,
+ right: bool = True,
+ labels=None,
+ retbins: bool = False,
+ precision: int = 3,
+ include_lowest: bool = False,
+ duplicates: str = "raise",
+ ordered: bool = True,
+):
+ """
+ Bin values into discrete intervals.
+
+ Use `cut` when you need to segment and sort data values into bins. This
+ function is also useful for going from a continuous variable to a
+ categorical variable. For example, `cut` could convert ages to groups of
+ age ranges. Supports binning into an equal number of bins, or a
+ pre-specified array of bins.
+
+ Parameters
+ ----------
+ x : 1d ndarray or Series
+ The input array to be binned. Must be 1-dimensional.
+ bins : int, sequence of scalars, or IntervalIndex
+ The criteria to bin by.
+
+ * int : Defines the number of equal-width bins in the range of `x`. The
+ range of `x` is extended by .1% on each side to include the minimum
+ and maximum values of `x`.
+ * sequence of scalars : Defines the bin edges allowing for non-uniform
+ width. No extension of the range of `x` is done.
+ * IntervalIndex : Defines the exact bins to be used. Note that
+ IntervalIndex for `bins` must be non-overlapping.
+
+ right : bool, default True
+ Indicates whether `bins` includes the rightmost edge or not. If
+ ``right == True`` (the default), then the `bins` ``[1, 2, 3, 4]``
+ indicate (1,2], (2,3], (3,4]. This argument is ignored when
+ `bins` is an IntervalIndex.
+ labels : array or False, default None
+ Specifies the labels for the returned bins. Must be the same length as
+ the resulting bins. If False, returns only integer indicators of the
+ bins. This affects the type of the output container (see below).
+ This argument is ignored when `bins` is an IntervalIndex. If True,
+ raises an error. When `ordered=False`, labels must be provided.
+ retbins : bool, default False
+ Whether to return the bins or not. Useful when bins is provided
+ as a scalar.
+ precision : int, default 3
+ The precision at which to store and display the bins labels.
+ include_lowest : bool, default False
+ Whether the first interval should be left-inclusive or not.
+ duplicates : {'raise', 'drop'}, default 'raise'
+ If bin edges are not unique, raise ValueError or drop non-uniques.
+ ordered : bool, default True
+ Whether the labels are ordered or not. Applies to returned types
+ Categorical and Series (with Categorical dtype). If True,
+ the resulting categorical will be ordered. If False, the resulting
+ categorical will be unordered (labels must be provided).
+
+ Returns
+ -------
+ out : Categorical, Series, or ndarray
+ An array-like object representing the respective bin for each value
+ of `x`. The type depends on the value of `labels`.
+
+ * None (default) : returns a Series for Series `x` or a
+ Categorical for all other inputs. The values stored within
+ are Interval dtype.
+
+ * sequence of scalars : returns a Series for Series `x` or a
+ Categorical for all other inputs. The values stored within
+ are whatever the type in the sequence is.
+
+ * False : returns a 1d ndarray or Series of integers.
+
+ bins : numpy.ndarray or IntervalIndex.
+ The computed or specified bins. Only returned when `retbins=True`.
+ For scalar or sequence `bins`, this is an ndarray with the computed
+ bins. If set `duplicates=drop`, `bins` will drop non-unique bin. For
+ an IntervalIndex `bins`, this is equal to `bins`.
+
+ See Also
+ --------
+ qcut : Discretize variable into equal-sized buckets based on rank
+ or based on sample quantiles.
+ Categorical : Array type for storing data that come from a
+ fixed set of values.
+ Series : One-dimensional array with axis labels (including time series).
+ IntervalIndex : Immutable Index implementing an ordered, sliceable set.
+ numpy.histogram_bin_edges: Function to calculate only the edges of the bins
+ used by the histogram function.
+
+ Notes
+ -----
+ Any NA values will be NA in the result. Out of bounds values will be NA in
+ the resulting Series or Categorical object.
+
+ ``numpy.histogram_bin_edges`` can be used along with cut to calculate bins according
+ to some predefined methods.
+
+ Reference :ref:`the user guide ` for more examples.
+
+ Examples
+ --------
+ Discretize into three equal-sized bins.
+
+ >>> pd.cut(np.array([1, 7, 5, 4, 6, 3]), 3)
+ ... # doctest: +ELLIPSIS
+ [(0.994, 3.0], (5.0, 7.0], (3.0, 5.0], (3.0, 5.0], (5.0, 7.0], ...
+ Categories (3, interval[float64, right]): [(0.994, 3.0] < (3.0, 5.0] ...
+
+ >>> pd.cut(np.array([1, 7, 5, 4, 6, 3]), 3, retbins=True)
+ ... # doctest: +ELLIPSIS
+ ([(0.994, 3.0], (5.0, 7.0], (3.0, 5.0], (3.0, 5.0], (5.0, 7.0], ...
+ Categories (3, interval[float64, right]): [(0.994, 3.0] < (3.0, 5.0] ...
+ array([0.994, 3. , 5. , 7. ]))
+
+ Discovers the same bins, but assign them specific labels. Notice that
+ the returned Categorical's categories are `labels` and is ordered.
+
+ >>> pd.cut(np.array([1, 7, 5, 4, 6, 3]), 3, labels=["bad", "medium", "good"])
+ ['bad', 'good', 'medium', 'medium', 'good', 'bad']
+ Categories (3, str): ['bad' < 'medium' < 'good']
+
+ ``ordered=False`` will result in unordered categories when labels are passed.
+ This parameter can be used to allow non-unique labels:
+
+ >>> pd.cut(np.array([1, 7, 5, 4, 6, 3]), 3, labels=["B", "A", "B"], ordered=False)
+ ['B', 'B', 'A', 'A', 'B', 'B']
+ Categories (2, str): ['A', 'B']
+
+ ``labels=False`` implies you just want the bins back.
+
+ >>> pd.cut([0, 1, 1, 2], bins=4, labels=False)
+ array([0, 1, 1, 3])
+
+ Passing a Series as an input returns a Series with categorical dtype:
+
+ >>> s = pd.Series(np.array([2, 4, 6, 8, 10]), index=["a", "b", "c", "d", "e"])
+ >>> pd.cut(s, 3)
+ ... # doctest: +ELLIPSIS
+ a (1.992, 4.667]
+ b (1.992, 4.667]
+ c (4.667, 7.333]
+ d (7.333, 10.0]
+ e (7.333, 10.0]
+ dtype: category
+ Categories (3, interval[float64, right]): [(1.992, 4.667] < (4.667, ...
+
+ Passing a Series as an input returns a Series with mapping value.
+ It is used to map numerically to intervals based on bins.
+
+ >>> s = pd.Series(np.array([2, 4, 6, 8, 10]), index=["a", "b", "c", "d", "e"])
+ >>> pd.cut(s, [0, 2, 4, 6, 8, 10], labels=False, retbins=True, right=False)
+ ... # doctest: +ELLIPSIS
+ (a 1.0
+ b 2.0
+ c 3.0
+ d 4.0
+ e NaN
+ dtype: float64,
+ array([ 0, 2, 4, 6, 8, 10]))
+
+ Use `drop` optional when bins is not unique
+
+ >>> pd.cut(
+ ... s,
+ ... [0, 2, 4, 6, 10, 10],
+ ... labels=False,
+ ... retbins=True,
+ ... right=False,
+ ... duplicates="drop",
+ ... )
+ ... # doctest: +ELLIPSIS
+ (a 1.0
+ b 2.0
+ c 3.0
+ d 3.0
+ e NaN
+ dtype: float64,
+ array([ 0, 2, 4, 6, 10]))
+
+ Passing an IntervalIndex for `bins` results in those categories exactly.
+ Notice that values not covered by the IntervalIndex are set to NaN. 0
+ is to the left of the first bin (which is closed on the right), and 1.5
+ falls between two bins.
+
+ >>> bins = pd.IntervalIndex.from_tuples([(0, 1), (2, 3), (4, 5)])
+ >>> pd.cut([0, 0.5, 1.5, 2.5, 4.5], bins)
+ [NaN, (0.0, 1.0], NaN, (2.0, 3.0], (4.0, 5.0]]
+ Categories (3, interval[int64, right]): [(0, 1] < (2, 3] < (4, 5]]
+
+ Using np.histogram_bin_edges with cut
+
+ >>> pd.cut(
+ ... np.array([1, 7, 5, 4]),
+ ... bins=np.histogram_bin_edges(np.array([1, 7, 5, 4]), bins="auto"),
+ ... )
+ ... # doctest: +ELLIPSIS
+ [NaN, (5.0, 7.0], (3.0, 5.0], (3.0, 5.0]]
+ Categories (3, interval[float64, right]): [(1.0, 3.0] < (3.0, 5.0] < (5.0, 7.0]]
+ """
+ # NOTE: this binning code is changed a bit from histogram for var(x) == 0
+
+ original = x
+ x_idx = _preprocess_for_cut(x)
+ x_idx, _ = _coerce_to_type(x_idx)
+
+ if not np.iterable(bins):
+ bins = _nbins_to_bins(x_idx, bins, right)
+
+ elif isinstance(bins, IntervalIndex):
+ if bins.is_overlapping:
+ raise ValueError("Overlapping IntervalIndex is not accepted.")
+
+ else:
+ bins = Index(bins)
+ if not bins.is_monotonic_increasing:
+ raise ValueError("bins must increase monotonically.")
+
+ fac, bins = _bins_to_cuts(
+ x_idx,
+ bins,
+ right=right,
+ labels=labels,
+ precision=precision,
+ include_lowest=include_lowest,
+ duplicates=duplicates,
+ ordered=ordered,
+ )
+
+ return _postprocess_for_cut(fac, bins, retbins, original)
+
+
+@set_module("pandas")
+def qcut(
+ x,
+ q,
+ labels=None,
+ retbins: bool = False,
+ precision: int = 3,
+ duplicates: str = "raise",
+):
+ """
+ Quantile-based discretization function.
+
+ Discretize variable into equal-sized buckets based on rank or based
+ on sample quantiles. For example 1000 values for 10 quantiles would
+ produce a Categorical object indicating quantile membership for each data point.
+
+ Parameters
+ ----------
+ x : 1d ndarray or Series
+ Input Numpy array or pandas Series object to be discretized.
+ q : int or list-like of float
+ Number of quantiles. 10 for deciles, 4 for quartiles, etc. Alternately
+ array of quantiles, e.g. [0, .25, .5, .75, 1.] for quartiles.
+ labels : array or False, default None
+ Used as labels for the resulting bins. Must be of the same length as
+ the resulting bins. If False, return only integer indicators of the
+ bins. If True, raises an error.
+ retbins : bool, optional
+ Whether to return the (bins, labels) or not. Can be useful if bins
+ is given as a scalar.
+ precision : int, optional
+ The precision at which to store and display the bins labels.
+ duplicates : {default 'raise', 'drop'}, optional
+ If bin edges are not unique, raise ValueError or drop non-uniques.
+
+ Returns
+ -------
+ out : Categorical or Series or array of integers if labels is False
+ The return type (Categorical or Series) depends on the input: a Series
+ of type category if input is a Series else Categorical. Bins are
+ represented as categories when categorical data is returned.
+ bins : ndarray of floats
+ Returned only if `retbins` is True.
+
+ See Also
+ --------
+ cut : Bin values into discrete intervals.
+ Series.quantile : Return value at the given quantile.
+
+ Notes
+ -----
+ Out of bounds values will be NA in the resulting Categorical object
+
+ Examples
+ --------
+ >>> pd.qcut(range(5), 4)
+ ... # doctest: +ELLIPSIS
+ [(-0.001, 1.0], (-0.001, 1.0], (1.0, 2.0], (2.0, 3.0], (3.0, 4.0]]
+ Categories (4, interval[float64, right]): [(-0.001, 1.0] < (1.0, 2.0] ...
+
+ >>> pd.qcut(range(5), 3, labels=["good", "medium", "bad"])
+ ... # doctest: +SKIP
+ [good, good, medium, bad, bad]
+ Categories (3, str): [good < medium < bad]
+
+ >>> pd.qcut(range(5), 4, labels=False)
+ array([0, 0, 1, 2, 3])
+ """
+ if isinstance(x, Expression):
+ return x._call_with_func(
+ qcut, x=x, q=q, labels=labels, retbins=retbins, precision=precision
+ )
+ original = x
+ x_idx = _preprocess_for_cut(x)
+ x_idx, _ = _coerce_to_type(x_idx)
+
+ if is_integer(q):
+ quantiles = np.linspace(0, 1, q + 1)
+ # Round up rather than to nearest if not representable in base 2
+ np.putmask(
+ quantiles,
+ q * quantiles != np.arange(q + 1),
+ np.nextafter(quantiles, 1),
+ )
+ else:
+ quantiles = q
+
+ bins = x_idx.to_series().dropna().quantile(quantiles)
+
+ fac, bins = _bins_to_cuts(
+ x_idx,
+ Index(bins),
+ labels=labels,
+ precision=precision,
+ include_lowest=True,
+ duplicates=duplicates,
+ )
+
+ return _postprocess_for_cut(fac, bins, retbins, original)
+
+
+def _nbins_to_bins(x_idx: Index, nbins: int, right: bool) -> Index:
+ """
+ If a user passed an integer N for bins, convert this to a sequence of N
+ equal(ish)-sized bins.
+ """
+ if is_scalar(nbins) and nbins < 1:
+ raise ValueError("`bins` should be a positive integer.")
+
+ if x_idx.size == 0:
+ raise ValueError("Cannot cut empty array")
+
+ rng = (x_idx.min(), x_idx.max())
+ mn, mx = rng
+
+ if is_numeric_dtype(x_idx.dtype) and (np.isinf(mn) or np.isinf(mx)):
+ # GH#24314
+ raise ValueError(
+ "cannot specify integer `bins` when input data contains infinity"
+ )
+
+ if mn == mx: # adjust end points before binning
+ if _is_dt_or_td(x_idx.dtype):
+ # using seconds=1 is pretty arbitrary here
+ # error: Argument 1 to "dtype_to_unit" has incompatible type
+ # "dtype[Any] | ExtensionDtype"; expected "DatetimeTZDtype | dtype[Any]"
+ unit = dtype_to_unit(x_idx.dtype) # type: ignore[arg-type]
+ td = Timedelta(seconds=1).as_unit(cast("TimeUnit", unit))
+ # Use DatetimeArray/TimedeltaArray method instead of linspace
+ # error: Item "ExtensionArray" of "ExtensionArray | ndarray[Any, Any]"
+ # has no attribute "_generate_range"
+ bins = x_idx._values._generate_range( # type: ignore[union-attr]
+ start=mn - td, end=mx + td, periods=nbins + 1, freq=None, unit=unit
+ )
+ else:
+ mn -= 0.001 * abs(mn) if mn != 0 else 0.001
+ mx += 0.001 * abs(mx) if mx != 0 else 0.001
+
+ bins = np.linspace(mn, mx, nbins + 1, endpoint=True)
+ else: # adjust end points after binning
+ if _is_dt_or_td(x_idx.dtype):
+ # Use DatetimeArray/TimedeltaArray method instead of linspace
+
+ # error: Argument 1 to "dtype_to_unit" has incompatible type
+ # "dtype[Any] | ExtensionDtype"; expected "DatetimeTZDtype | dtype[Any]"
+ unit = dtype_to_unit(x_idx.dtype) # type: ignore[arg-type]
+ # error: Item "ExtensionArray" of "ExtensionArray | ndarray[Any, Any]"
+ # has no attribute "_generate_range"
+ bins = x_idx._values._generate_range( # type: ignore[union-attr]
+ start=mn, end=mx, periods=nbins + 1, freq=None, unit=unit
+ )
+ else:
+ bins = np.linspace(mn, mx, nbins + 1, endpoint=True)
+ adj = (mx - mn) * 0.001 # 0.1% of the range
+ if right:
+ bins[0] -= adj
+ else:
+ bins[-1] += adj
+
+ return Index(bins, copy=False)
+
+
+def _bins_to_cuts(
+ x_idx: Index,
+ bins: Index,
+ right: bool = True,
+ labels=None,
+ precision: int = 3,
+ include_lowest: bool = False,
+ duplicates: str = "raise",
+ ordered: bool = True,
+):
+ if not ordered and labels is None:
+ raise ValueError("'labels' must be provided if 'ordered = False'")
+
+ if duplicates not in ["raise", "drop"]:
+ raise ValueError(
+ "invalid value for 'duplicates' parameter, valid options are: raise, drop"
+ )
+
+ result: Categorical | np.ndarray
+
+ if isinstance(bins, IntervalIndex):
+ # we have a fast-path here
+ ids = bins.get_indexer(x_idx)
+ cat_dtype = CategoricalDtype(bins, ordered=True)
+ result = Categorical.from_codes(ids, dtype=cat_dtype, validate=False)
+ return result, bins
+
+ unique_bins = algos.unique(bins)
+ if len(unique_bins) < len(bins) and len(bins) != 2:
+ if duplicates == "raise":
+ raise ValueError(
+ f"Bin edges must be unique: {bins!r}.\n"
+ f"You can drop duplicate edges by setting the 'duplicates' kwarg"
+ )
+ bins = unique_bins
+
+ side: Literal["left", "right"] = "left" if right else "right"
+
+ try:
+ ids = bins.searchsorted(x_idx, side=side)
+ except TypeError as err:
+ # e.g. test_datetime_nan_error if bins are DatetimeArray and x_idx
+ # is integers
+ if x_idx.dtype.kind == "m":
+ raise ValueError("bins must be of timedelta64 dtype") from err
+ elif x_idx.dtype.kind == bins.dtype.kind == "M":
+ raise ValueError(
+ "Cannot use timezone-naive bins with timezone-aware values, "
+ "or vice-versa"
+ ) from err
+ elif x_idx.dtype.kind == "M":
+ raise ValueError("bins must be of datetime64 dtype") from err
+ else:
+ raise
+ ids = ensure_platform_int(ids)
+
+ if include_lowest:
+ ids[x_idx == bins[0]] = 1
+
+ na_mask = isna(x_idx) | (ids == len(bins)) | (ids == 0)
+ has_nas = na_mask.any()
+
+ if labels is not False:
+ if not (labels is None or is_list_like(labels)):
+ raise ValueError(
+ "Bin labels must either be False, None or passed in as a "
+ "list-like argument"
+ )
+
+ if labels is None:
+ labels = _format_labels(
+ bins, precision, right=right, include_lowest=include_lowest
+ )
+ elif ordered and len(set(labels)) != len(labels):
+ raise ValueError(
+ "labels must be unique if ordered=True; pass ordered=False "
+ "for duplicate labels"
+ )
+ elif len(labels) != len(bins) - 1:
+ raise ValueError(
+ "Bin labels must be one fewer than the number of bin edges"
+ )
+
+ if not isinstance(getattr(labels, "dtype", None), CategoricalDtype):
+ labels = Categorical(
+ labels,
+ categories=labels if len(set(labels)) == len(labels) else None,
+ ordered=ordered,
+ )
+ # TODO: handle mismatch between categorical label order and pandas.cut order.
+ np.putmask(ids, na_mask, 0)
+ result = algos.take_nd(labels, ids - 1)
+
+ else:
+ result = ids - 1
+ if has_nas:
+ result = result.astype(np.float64)
+ np.putmask(result, na_mask, np.nan)
+
+ return result, bins
+
+
+def _coerce_to_type(x: Index) -> tuple[Index, DtypeObj | None]:
+ """
+ if the passed data is of datetime/timedelta, bool or nullable int type,
+ this method converts it to numeric so that cut or qcut method can
+ handle it
+ """
+ dtype: DtypeObj | None = None
+
+ if _is_dt_or_td(x.dtype):
+ dtype = x.dtype
+ elif is_bool_dtype(x.dtype):
+ # GH 20303
+ x = x.astype(np.int64)
+ # To support cut and qcut for IntegerArray we convert to float dtype.
+ # Will properly support in the future.
+ # https://github.com/pandas-dev/pandas/pull/31290
+ # https://github.com/pandas-dev/pandas/issues/31389
+ elif isinstance(x.dtype, ExtensionDtype) and is_numeric_dtype(x.dtype):
+ x_arr = x.to_numpy(dtype=np.float64, na_value=np.nan)
+ x = Index(x_arr, copy=False)
+
+ return Index(x), dtype
+
+
+def _is_dt_or_td(dtype: DtypeObj) -> bool:
+ # Note: the dtype here comes from an Index.dtype, so we know that that any
+ # dt64/td64 dtype is of a supported unit.
+ return isinstance(dtype, DatetimeTZDtype) or lib.is_np_dtype(dtype, "mM")
+
+
+def _format_labels(
+ bins: Index,
+ precision: int,
+ right: bool = True,
+ include_lowest: bool = False,
+) -> IntervalIndex:
+ """based on the dtype, return our labels"""
+ closed: IntervalLeftRight = "right" if right else "left"
+
+ formatter: Callable[[Any], Timestamp] | Callable[[Any], Timedelta]
+
+ if _is_dt_or_td(bins.dtype):
+ # error: Argument 1 to "dtype_to_unit" has incompatible type
+ # "dtype[Any] | ExtensionDtype"; expected "DatetimeTZDtype | dtype[Any]"
+ unit = dtype_to_unit(bins.dtype) # type: ignore[arg-type]
+ unit = cast("TimeUnit", unit)
+ formatter = lambda x: x
+ adjust = lambda x: x - Timedelta(1, unit=unit).as_unit(unit)
+ else:
+ precision = _infer_precision(precision, bins)
+ formatter = lambda x: _round_frac(x, precision)
+ adjust = lambda x: x - 10 ** (-precision)
+
+ breaks = [formatter(b) for b in bins]
+ if right and include_lowest:
+ # adjust lhs of first interval by precision to account for being right closed
+ breaks[0] = adjust(breaks[0])
+
+ if _is_dt_or_td(bins.dtype):
+ # error: "Index" has no attribute "as_unit"
+ breaks = type(bins)(breaks).as_unit(unit) # type: ignore[attr-defined]
+
+ return IntervalIndex.from_breaks(breaks, closed=closed)
+
+
+def _preprocess_for_cut(x) -> Index:
+ """
+ handles preprocessing for cut where we convert passed
+ input to array, strip the index information and store it
+ separately
+ """
+ # Check that the passed array is a Pandas or Numpy object
+ # We don't want to strip away a Pandas data-type here (e.g. datetimetz)
+ ndim = getattr(x, "ndim", None)
+ if ndim is None:
+ x = np.asarray(x)
+ if x.ndim != 1:
+ raise ValueError("Input array must be 1 dimensional")
+
+ return Index(x, copy=False)
+
+
+def _postprocess_for_cut(fac, bins, retbins: bool, original):
+ """
+ handles post processing for the cut method where
+ we combine the index information if the originally passed
+ datatype was a series
+ """
+ if isinstance(original, ABCSeries):
+ fac = original._constructor(fac, index=original.index, name=original.name)
+
+ if not retbins:
+ return fac
+
+ if isinstance(bins, Index) and is_numeric_dtype(bins.dtype):
+ bins = bins._values
+
+ return fac, bins
+
+
+def _round_frac(x, precision: int):
+ """
+ Round the fractional part of the given number
+ """
+ if not np.isfinite(x) or x == 0:
+ return x
+ else:
+ frac, whole = np.modf(x)
+ if whole == 0:
+ digits = -int(np.floor(np.log10(abs(frac)))) - 1 + precision
+ else:
+ digits = precision
+ return np.around(x, digits)
+
+
+def _infer_precision(base_precision: int, bins: Index) -> int:
+ """
+ Infer an appropriate precision for _round_frac
+ """
+ for precision in range(base_precision, 20):
+ levels = np.asarray([_round_frac(b, precision) for b in bins])
+ if algos.unique(levels).size == bins.size:
+ return precision
+ return base_precision # default
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/shared_docs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/shared_docs.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f91443b8cda655d07f9cf2dc1aedb8bcce873da
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/shared_docs.py
@@ -0,0 +1,639 @@
+from __future__ import annotations
+
+_shared_docs: dict[str, str] = {}
+
+_shared_docs["aggregate"] = """
+Aggregate using one or more operations over the specified axis.
+
+Parameters
+----------
+func : function, str, list or dict
+ Function to use for aggregating the data. If a function, must either
+ work when passed a {klass} or when passed to {klass}.apply.
+
+ Accepted combinations are:
+
+ - function
+ - string function name
+ - list of functions and/or function names, e.g. ``[np.sum, 'mean']``
+ - dict of axis labels -> functions, function names or list of such.
+{axis}
+*args
+ Positional arguments to pass to `func`.
+**kwargs
+ Keyword arguments to pass to `func`.
+
+Returns
+-------
+scalar, Series or DataFrame
+
+ The return can be:
+
+ * scalar : when Series.agg is called with single function
+ * Series : when DataFrame.agg is called with a single function
+ * DataFrame : when DataFrame.agg is called with several functions
+{see_also}
+Notes
+-----
+The aggregation operations are always performed over an axis, either the
+index (default) or the column axis. This behavior is different from
+`numpy` aggregation functions (`mean`, `median`, `prod`, `sum`, `std`,
+`var`), where the default is to compute the aggregation of the flattened
+array, e.g., ``numpy.mean(arr_2d)`` as opposed to
+``numpy.mean(arr_2d, axis=0)``.
+
+`agg` is an alias for `aggregate`. Use the alias.
+
+Functions that mutate the passed object can produce unexpected
+behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
+for more details.
+
+A passed user-defined-function will be passed a Series for evaluation.
+
+If ``func`` defines an index relabeling, ``axis`` must be ``0`` or ``index``.
+{examples}"""
+
+_shared_docs["compare"] = """
+Compare to another {klass} and show the differences.
+
+Parameters
+----------
+other : {klass}
+ Object to compare with.
+
+align_axis : {{0 or 'index', 1 or 'columns'}}, default 1
+ Determine which axis to align the comparison on.
+
+ * 0, or 'index' : Resulting differences are stacked vertically
+ with rows drawn alternately from self and other.
+ * 1, or 'columns' : Resulting differences are aligned horizontally
+ with columns drawn alternately from self and other.
+
+keep_shape : bool, default False
+ If true, all rows and columns are kept.
+ Otherwise, only the ones with different values are kept.
+
+keep_equal : bool, default False
+ If true, the result keeps values that are equal.
+ Otherwise, equal values are shown as NaNs.
+
+result_names : tuple, default ('self', 'other')
+ Set the dataframes names in the comparison.
+"""
+
+_shared_docs["groupby"] = """
+Group %(klass)s using a mapper or by a Series of columns.
+
+A groupby operation involves some combination of splitting the
+object, applying a function, and combining the results. This can be
+used to group large amounts of data and compute operations on these
+groups.
+
+Parameters
+----------
+by : mapping, function, label, pd.Grouper or list of such
+ Used to determine the groups for the groupby.
+ If ``by`` is a function, it's called on each value of the object's
+ index. If a dict or Series is passed, the Series or dict VALUES
+ will be used to determine the groups (the Series' values are first
+ aligned; see ``.align()`` method). If a list or ndarray of length
+ equal to the selected axis is passed (see the `groupby user guide
+ `_),
+ the values are used as-is to determine the groups. A label or list
+ of labels may be passed to group by the columns in ``self``.
+ Notice that a tuple is interpreted as a (single) key.
+level : int, level name, or sequence of such, default None
+ If the axis is a MultiIndex (hierarchical), group by a particular
+ level or levels. Do not specify both ``by`` and ``level``.
+as_index : bool, default True
+ Return object with group labels as the
+ index. Only relevant for DataFrame input. as_index=False is
+ effectively "SQL-style" grouped output. This argument has no effect
+ on filtrations (see the `filtrations in the user guide
+ `_),
+ such as ``head()``, ``tail()``, ``nth()`` and in transformations
+ (see the `transformations in the user guide
+ `_).
+sort : bool, default True
+ Sort group keys. Get better performance by turning this off.
+ Note this does not influence the order of observations within each
+ group. Groupby preserves the order of rows within each group. If False,
+ the groups will appear in the same order as they did in the original DataFrame.
+ This argument has no effect on filtrations (see the `filtrations in the user guide
+ `_),
+ such as ``head()``, ``tail()``, ``nth()`` and in transformations
+ (see the `transformations in the user guide
+ `_).
+
+ .. versionchanged:: 2.0.0
+
+ Specifying ``sort=False`` with an ordered categorical grouper will no
+ longer sort the values.
+
+group_keys : bool, default True
+ When calling apply and the ``by`` argument produces a like-indexed
+ (i.e. :ref:`a transform `) result, add group keys to
+ index to identify pieces. By default group keys are not included
+ when the result's index (and column) labels match the inputs, and
+ are included otherwise.
+
+ .. versionchanged:: 2.0.0
+
+ ``group_keys`` now defaults to ``True``.
+
+observed : bool, default True
+ This only applies if any of the groupers are Categoricals.
+ If True: only show observed values for categorical groupers.
+ If False: show all values for categorical groupers.
+
+ .. versionchanged:: 3.0.0
+
+ The default value is now ``True``.
+
+dropna : bool, default True
+ If True, and if group keys contain NA values, NA values together
+ with row/column will be dropped.
+ If False, NA values will also be treated as the key in groups.
+
+Returns
+-------
+pandas.api.typing.%(klass)sGroupBy
+ Returns a groupby object that contains information about the groups.
+
+See Also
+--------
+resample : Convenience method for frequency conversion and resampling
+ of time series.
+
+Notes
+-----
+See the `user guide
+`__ for more
+detailed usage and examples, including splitting an object into groups,
+iterating through groups, selecting a group, aggregation, and more.
+
+The implementation of groupby is hash-based, meaning in particular that
+objects that compare as equal will be considered to be in the same group.
+An exception to this is that pandas has special handling of NA values:
+any NA values will be collapsed to a single group, regardless of how
+they compare. See the user guide linked above for more details.
+"""
+
+_shared_docs["transform"] = """
+Call ``func`` on self producing a {klass} with the same axis shape as self.
+
+Parameters
+----------
+func : function, str, list-like or dict-like
+ Function to use for transforming the data. If a function, must either
+ work when passed a {klass} or when passed to {klass}.apply. If func
+ is both list-like and dict-like, dict-like behavior takes precedence.
+
+ Accepted combinations are:
+
+ - function
+ - string function name
+ - list-like of functions and/or function names, e.g. ``[np.exp, 'sqrt']``
+ - dict-like of axis labels -> functions, function names or list-like of such.
+{axis}
+*args
+ Positional arguments to pass to `func`.
+**kwargs
+ Keyword arguments to pass to `func`.
+
+Returns
+-------
+{klass}
+ A {klass} that must have the same length as self.
+
+Raises
+------
+ValueError : If the returned {klass} has a different length than self.
+
+See Also
+--------
+{klass}.agg : Only perform aggregating type operations.
+{klass}.apply : Invoke function on a {klass}.
+
+Notes
+-----
+Functions that mutate the passed object can produce unexpected
+behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
+for more details.
+
+Examples
+--------
+>>> df = pd.DataFrame({{'A': range(3), 'B': range(1, 4)}})
+>>> df
+ A B
+0 0 1
+1 1 2
+2 2 3
+>>> df.transform(lambda x: x + 1)
+ A B
+0 1 2
+1 2 3
+2 3 4
+
+Even though the resulting {klass} must have the same length as the
+input {klass}, it is possible to provide several input functions:
+
+>>> s = pd.Series(range(3))
+>>> s
+0 0
+1 1
+2 2
+dtype: int64
+>>> s.transform([np.sqrt, np.exp])
+ sqrt exp
+0 0.000000 1.000000
+1 1.000000 2.718282
+2 1.414214 7.389056
+
+You can call transform on a GroupBy object:
+
+>>> df = pd.DataFrame({{
+... "Date": [
+... "2015-05-08", "2015-05-07", "2015-05-06", "2015-05-05",
+... "2015-05-08", "2015-05-07", "2015-05-06", "2015-05-05"],
+... "Data": [5, 8, 6, 1, 50, 100, 60, 120],
+... }})
+>>> df
+ Date Data
+0 2015-05-08 5
+1 2015-05-07 8
+2 2015-05-06 6
+3 2015-05-05 1
+4 2015-05-08 50
+5 2015-05-07 100
+6 2015-05-06 60
+7 2015-05-05 120
+>>> df.groupby('Date')['Data'].transform('sum')
+0 55
+1 108
+2 66
+3 121
+4 55
+5 108
+6 66
+7 121
+Name: Data, dtype: int64
+
+>>> df = pd.DataFrame({{
+... "c": [1, 1, 1, 2, 2, 2, 2],
+... "type": ["m", "n", "o", "m", "m", "n", "n"]
+... }})
+>>> df
+ c type
+0 1 m
+1 1 n
+2 1 o
+3 2 m
+4 2 m
+5 2 n
+6 2 n
+>>> df['size'] = df.groupby('c')['type'].transform(len)
+>>> df
+ c type size
+0 1 m 3
+1 1 n 3
+2 1 o 3
+3 2 m 4
+4 2 m 4
+5 2 n 4
+6 2 n 4
+"""
+
+_shared_docs["storage_options"] = """storage_options : dict, optional
+ Extra options that make sense for a particular storage connection, e.g.
+ host, port, username, password, etc. For HTTP(S) URLs the key-value pairs
+ are forwarded to ``urllib.request.Request`` as header options. For other
+ URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are
+ forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more
+ details, and for more examples on storage options refer `here
+ `_."""
+
+_shared_docs["compression_options"] = """compression : str or dict, default 'infer'
+ For on-the-fly compression of the output data. If 'infer' and '%s' is
+ path-like, then detect compression from the following extensions: '.gz',
+ '.bz2', '.zip', '.xz', '.zst', '.tar', '.tar.gz', '.tar.xz' or '.tar.bz2'
+ (otherwise no compression).
+ Set to ``None`` for no compression.
+ Can also be a dict with key ``'method'`` set
+ to one of {``'zip'``, ``'gzip'``, ``'bz2'``, ``'zstd'``, ``'xz'``, ``'tar'``} and
+ other key-value pairs are forwarded to
+ ``zipfile.ZipFile``, ``gzip.GzipFile``,
+ ``bz2.BZ2File``, ``zstandard.ZstdCompressor``, ``lzma.LZMAFile`` or
+ ``tarfile.TarFile``, respectively.
+ As an example, the following could be passed for faster compression and to create
+ a reproducible gzip archive:
+ ``compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}``.
+
+ .. versionadded:: 1.5.0
+ Added support for `.tar` files."""
+
+_shared_docs["decompression_options"] = """compression : str or dict, default 'infer'
+ For on-the-fly decompression of on-disk data. If 'infer' and '%s' is
+ path-like, then detect compression from the following extensions: '.gz',
+ '.bz2', '.zip', '.xz', '.zst', '.tar', '.tar.gz', '.tar.xz' or '.tar.bz2'
+ (otherwise no compression).
+ If using 'zip' or 'tar', the ZIP file must contain only one data file to be read in.
+ Set to ``None`` for no decompression.
+ Can also be a dict with key ``'method'`` set
+ to one of {``'zip'``, ``'gzip'``, ``'bz2'``, ``'zstd'``, ``'xz'``, ``'tar'``} and
+ other key-value pairs are forwarded to
+ ``zipfile.ZipFile``, ``gzip.GzipFile``,
+ ``bz2.BZ2File``, ``zstandard.ZstdDecompressor``, ``lzma.LZMAFile`` or
+ ``tarfile.TarFile``, respectively.
+ As an example, the following could be passed for Zstandard decompression using a
+ custom compression dictionary:
+ ``compression={'method': 'zstd', 'dict_data': my_compression_dict}``.
+
+ .. versionadded:: 1.5.0
+ Added support for `.tar` files."""
+
+_shared_docs["replace"] = """
+ Replace values given in `to_replace` with `value`.
+
+ Values of the {klass} are replaced with other values dynamically.
+ This differs from updating with ``.loc`` or ``.iloc``, which require
+ you to specify a location to update with some value.
+
+ Parameters
+ ----------
+ to_replace : str, regex, list, dict, Series, int, float, or None
+ How to find the values that will be replaced.
+
+ * numeric, str or regex:
+
+ - numeric: numeric values equal to `to_replace` will be
+ replaced with `value`
+ - str: string exactly matching `to_replace` will be replaced
+ with `value`
+ - regex: regexes matching `to_replace` will be replaced with
+ `value`
+
+ * list of str, regex, or numeric:
+
+ - First, if `to_replace` and `value` are both lists, they
+ **must** be the same length.
+ - Second, if ``regex=True`` then all of the strings in **both**
+ lists will be interpreted as regexes otherwise they will match
+ directly. This doesn't matter much for `value` since there
+ are only a few possible substitution regexes you can use.
+ - str, regex and numeric rules apply as above.
+
+ * dict:
+
+ - Dicts can be used to specify different replacement values
+ for different existing values. For example,
+ ``{{'a': 'b', 'y': 'z'}}`` replaces the value 'a' with 'b' and
+ 'y' with 'z'. To use a dict in this way, the optional `value`
+ parameter should not be given.
+ - For a DataFrame a dict can specify that different values
+ should be replaced in different columns. For example,
+ ``{{'a': 1, 'b': 'z'}}`` looks for the value 1 in column 'a'
+ and the value 'z' in column 'b' and replaces these values
+ with whatever is specified in `value`. The `value` parameter
+ should not be ``None`` in this case. You can treat this as a
+ special case of passing two lists except that you are
+ specifying the column to search in.
+ - For a DataFrame nested dictionaries, e.g.,
+ ``{{'a': {{'b': np.nan}}}}``, are read as follows: look in column
+ 'a' for the value 'b' and replace it with NaN. The optional `value`
+ parameter should not be specified to use a nested dict in this
+ way. You can nest regular expressions as well. Note that
+ column names (the top-level dictionary keys in a nested
+ dictionary) **cannot** be regular expressions.
+
+ * None:
+
+ - This means that the `regex` argument must be a string,
+ compiled regular expression, or list, dict, ndarray or
+ Series of such elements. If `value` is also ``None`` then
+ this **must** be a nested dictionary or Series.
+
+ See the examples section for examples of each of these.
+ value : scalar, dict, list, str, regex, default None
+ Value to replace any values matching `to_replace` with.
+ For a DataFrame a dict of values can be used to specify which
+ value to use for each column (columns not in the dict will not be
+ filled). Regular expressions, strings and lists or dicts of such
+ objects are also allowed.
+ {inplace}
+ regex : bool or same types as `to_replace`, default False
+ Whether to interpret `to_replace` and/or `value` as regular
+ expressions. Alternatively, this could be a regular expression or a
+ list, dict, or array of regular expressions in which case
+ `to_replace` must be ``None``.
+
+ Returns
+ -------
+ {klass}
+ Object after replacement.
+
+ Raises
+ ------
+ AssertionError
+ * If `regex` is not a ``bool`` and `to_replace` is not
+ ``None``.
+
+ TypeError
+ * If `to_replace` is not a scalar, array-like, ``dict``, or ``None``
+ * If `to_replace` is a ``dict`` and `value` is not a ``list``,
+ ``dict``, ``ndarray``, or ``Series``
+ * If `to_replace` is ``None`` and `regex` is not compilable
+ into a regular expression or is a list, dict, ndarray, or
+ Series.
+ * When replacing multiple ``bool`` or ``datetime64`` objects and
+ the arguments to `to_replace` does not match the type of the
+ value being replaced
+
+ ValueError
+ * If a ``list`` or an ``ndarray`` is passed to `to_replace` and
+ `value` but they are not the same length.
+
+ See Also
+ --------
+ Series.fillna : Fill NA values.
+ DataFrame.fillna : Fill NA values.
+ Series.where : Replace values based on boolean condition.
+ DataFrame.where : Replace values based on boolean condition.
+ DataFrame.map: Apply a function to a Dataframe elementwise.
+ Series.map: Map values of Series according to an input mapping or function.
+ Series.str.replace : Simple string replacement.
+
+ Notes
+ -----
+ * Regex substitution is performed under the hood with ``re.sub``. The
+ rules for substitution for ``re.sub`` are the same.
+ * Regular expressions will only substitute on strings, meaning you
+ cannot provide, for example, a regular expression matching floating
+ point numbers and expect the columns in your frame that have a
+ numeric dtype to be matched. However, if those floating point
+ numbers *are* strings, then you can do this.
+ * This method has *a lot* of options. You are encouraged to experiment
+ and play with this method to gain intuition about how it works.
+ * When dict is used as the `to_replace` value, it is like
+ key(s) in the dict are the to_replace part and
+ value(s) in the dict are the value parameter.
+
+ Examples
+ --------
+
+ **Scalar `to_replace` and `value`**
+
+ >>> s = pd.Series([1, 2, 3, 4, 5])
+ >>> s.replace(1, 5)
+ 0 5
+ 1 2
+ 2 3
+ 3 4
+ 4 5
+ dtype: int64
+
+ >>> df = pd.DataFrame({{'A': [0, 1, 2, 3, 4],
+ ... 'B': [5, 6, 7, 8, 9],
+ ... 'C': ['a', 'b', 'c', 'd', 'e']}})
+ >>> df.replace(0, 5)
+ A B C
+ 0 5 5 a
+ 1 1 6 b
+ 2 2 7 c
+ 3 3 8 d
+ 4 4 9 e
+
+ **List-like `to_replace`**
+
+ >>> df.replace([0, 1, 2, 3], 4)
+ A B C
+ 0 4 5 a
+ 1 4 6 b
+ 2 4 7 c
+ 3 4 8 d
+ 4 4 9 e
+
+ >>> df.replace([0, 1, 2, 3], [4, 3, 2, 1])
+ A B C
+ 0 4 5 a
+ 1 3 6 b
+ 2 2 7 c
+ 3 1 8 d
+ 4 4 9 e
+
+ **dict-like `to_replace`**
+
+ >>> df.replace({{0: 10, 1: 100}})
+ A B C
+ 0 10 5 a
+ 1 100 6 b
+ 2 2 7 c
+ 3 3 8 d
+ 4 4 9 e
+
+ >>> df.replace({{'A': 0, 'B': 5}}, 100)
+ A B C
+ 0 100 100 a
+ 1 1 6 b
+ 2 2 7 c
+ 3 3 8 d
+ 4 4 9 e
+
+ >>> df.replace({{'A': {{0: 100, 4: 400}}}})
+ A B C
+ 0 100 5 a
+ 1 1 6 b
+ 2 2 7 c
+ 3 3 8 d
+ 4 400 9 e
+
+ **Regular expression `to_replace`**
+
+ >>> df = pd.DataFrame({{'A': ['bat', 'foo', 'bait'],
+ ... 'B': ['abc', 'bar', 'xyz']}})
+ >>> df.replace(to_replace=r'^ba.$', value='new', regex=True)
+ A B
+ 0 new abc
+ 1 foo new
+ 2 bait xyz
+
+ >>> df.replace({{'A': r'^ba.$'}}, {{'A': 'new'}}, regex=True)
+ A B
+ 0 new abc
+ 1 foo bar
+ 2 bait xyz
+
+ >>> df.replace(regex=r'^ba.$', value='new')
+ A B
+ 0 new abc
+ 1 foo new
+ 2 bait xyz
+
+ >>> df.replace(regex={{r'^ba.$': 'new', 'foo': 'xyz'}})
+ A B
+ 0 new abc
+ 1 xyz new
+ 2 bait xyz
+
+ >>> df.replace(regex=[r'^ba.$', 'foo'], value='new')
+ A B
+ 0 new abc
+ 1 new new
+ 2 bait xyz
+
+ Compare the behavior of ``s.replace({{'a': None}})`` and
+ ``s.replace('a', None)`` to understand the peculiarities
+ of the `to_replace` parameter:
+
+ >>> s = pd.Series([10, 'a', 'a', 'b', 'a'])
+
+ When one uses a dict as the `to_replace` value, it is like the
+ value(s) in the dict are equal to the `value` parameter.
+ ``s.replace({{'a': None}})`` is equivalent to
+ ``s.replace(to_replace={{'a': None}}, value=None)``:
+
+ >>> s.replace({{'a': None}})
+ 0 10
+ 1 None
+ 2 None
+ 3 b
+ 4 None
+ dtype: object
+
+ If ``None`` is explicitly passed for ``value``, it will be respected:
+
+ >>> s.replace('a', None)
+ 0 10
+ 1 None
+ 2 None
+ 3 b
+ 4 None
+ dtype: object
+
+ When ``regex=True``, ``value`` is not ``None`` and `to_replace` is a string,
+ the replacement will be applied in all columns of the DataFrame.
+
+ >>> df = pd.DataFrame({{'A': [0, 1, 2, 3, 4],
+ ... 'B': ['a', 'b', 'c', 'd', 'e'],
+ ... 'C': ['f', 'g', 'h', 'i', 'j']}})
+
+ >>> df.replace(to_replace='^[a-g]', value='e', regex=True)
+ A B C
+ 0 0 e e
+ 1 1 e e
+ 2 2 e h
+ 3 3 e i
+ 4 4 e j
+
+ If ``value`` is not ``None`` and `to_replace` is a dictionary, the dictionary
+ keys will be the DataFrame columns that the replacement will be applied.
+
+ >>> df.replace(to_replace={{'B': '^[a-c]', 'C': '^[h-j]'}}, value='e', regex=True)
+ A B C
+ 0 0 e f
+ 1 1 e g
+ 2 2 e e
+ 3 3 d e
+ 4 4 e e
+"""
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/tools/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/tools/datetimes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/tools/datetimes.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c96ccc175a8f152bb3286c80428bcd540f7fb64
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/tools/datetimes.py
@@ -0,0 +1,1213 @@
+from __future__ import annotations
+
+from collections import abc
+from datetime import date
+from functools import partial
+from itertools import islice
+from typing import (
+ TYPE_CHECKING,
+ TypeAlias,
+ TypedDict,
+ Union,
+ cast,
+ overload,
+)
+import warnings
+
+import numpy as np
+
+from pandas._libs import (
+ lib,
+ tslib,
+)
+from pandas._libs.tslibs import (
+ NaT,
+ OutOfBoundsDatetime,
+ Timedelta,
+ Timestamp,
+ astype_overflowsafe,
+ get_supported_dtype,
+ is_supported_dtype,
+ timezones as libtimezones,
+)
+from pandas._libs.tslibs.conversion import cast_from_unit_vectorized
+from pandas._libs.tslibs.parsing import (
+ DateParseError,
+ guess_datetime_format,
+)
+from pandas._libs.tslibs.strptime import array_strptime
+from pandas._typing import (
+ AnyArrayLike,
+ ArrayLike,
+ DateTimeErrorChoices,
+)
+from pandas.util._decorators import set_module
+from pandas.util._exceptions import find_stack_level
+
+from pandas.core.dtypes.common import (
+ ensure_object,
+ is_float,
+ is_float_dtype,
+ is_integer,
+ is_integer_dtype,
+ is_list_like,
+ is_numeric_dtype,
+)
+from pandas.core.dtypes.dtypes import (
+ ArrowDtype,
+ DatetimeTZDtype,
+)
+from pandas.core.dtypes.generic import (
+ ABCDataFrame,
+ ABCSeries,
+)
+
+from pandas.arrays import (
+ DatetimeArray,
+ IntegerArray,
+ NumpyExtensionArray,
+)
+from pandas.core.algorithms import unique
+from pandas.core.arrays import ArrowExtensionArray
+from pandas.core.arrays.base import ExtensionArray
+from pandas.core.arrays.datetimes import (
+ maybe_convert_dtype,
+ objects_to_datetime64,
+ tz_to_dtype,
+)
+from pandas.core.construction import extract_array
+from pandas.core.indexes.base import Index
+from pandas.core.indexes.datetimes import DatetimeIndex
+
+if TYPE_CHECKING:
+ from collections.abc import (
+ Callable,
+ Hashable,
+ )
+
+ from pandas._libs.tslibs.nattype import NaTType
+ from pandas._libs.tslibs.timedeltas import UnitChoices
+ from pandas._typing import TimeUnit
+
+ from pandas import (
+ DataFrame,
+ Series,
+ )
+
+# ---------------------------------------------------------------------
+# types used in annotations
+
+ArrayConvertible: TypeAlias = list | tuple | AnyArrayLike
+Scalar: TypeAlias = float | str
+DatetimeScalar: TypeAlias = Scalar | date | np.datetime64
+
+DatetimeScalarOrArrayConvertible: TypeAlias = DatetimeScalar | ArrayConvertible
+DatetimeDictArg: TypeAlias = list[Scalar] | tuple[Scalar, ...] | AnyArrayLike
+
+
+class YearMonthDayDict(TypedDict, total=True):
+ year: DatetimeDictArg
+ month: DatetimeDictArg
+ day: DatetimeDictArg
+
+
+class FulldatetimeDict(YearMonthDayDict, total=False):
+ hour: DatetimeDictArg
+ hours: DatetimeDictArg
+ minute: DatetimeDictArg
+ minutes: DatetimeDictArg
+ second: DatetimeDictArg
+ seconds: DatetimeDictArg
+ ms: DatetimeDictArg
+ us: DatetimeDictArg
+ ns: DatetimeDictArg
+
+
+DictConvertible = Union[FulldatetimeDict, "DataFrame"]
+start_caching_at = 50
+
+
+# ---------------------------------------------------------------------
+
+
+def _guess_datetime_format_for_array(arr, dayfirst: bool | None = False) -> str | None:
+ # Try to guess the format based on the first non-NaN element, return None if can't
+ if (first_non_null := tslib.first_non_null(arr)) != -1:
+ if type(first_non_nan_element := arr[first_non_null]) is str:
+ # GH#32264 np.str_ object
+ guessed_format = guess_datetime_format(
+ first_non_nan_element, dayfirst=dayfirst
+ )
+ if guessed_format is not None:
+ return guessed_format
+ # If there are multiple non-null elements, warn about
+ # how parsing might not be consistent
+ if tslib.first_non_null(arr[first_non_null + 1 :]) != -1:
+ warnings.warn(
+ "Could not infer format, so each element will be parsed "
+ "individually, falling back to `dateutil`. To ensure parsing is "
+ "consistent and as-expected, please specify a format.",
+ UserWarning,
+ stacklevel=find_stack_level(),
+ )
+ return None
+
+
+def should_cache(
+ arg: ArrayConvertible, unique_share: float = 0.7, check_count: int | None = None
+) -> bool:
+ """
+ Decides whether to do caching.
+
+ If the percent of unique elements among `check_count` elements less
+ than `unique_share * 100` then we can do caching.
+
+ Parameters
+ ----------
+ arg: listlike, tuple, 1-d array, Series
+ unique_share: float, default=0.7, optional
+ 0 < unique_share < 1
+ check_count: int, optional
+ 0 <= check_count <= len(arg)
+
+ Returns
+ -------
+ do_caching: bool
+
+ Notes
+ -----
+ By default for a sequence of less than 50 items in size, we don't do
+ caching; for the number of elements less than 5000, we take ten percent of
+ all elements to check for a uniqueness share; if the sequence size is more
+ than 5000, then we check only the first 500 elements.
+ All constants were chosen empirically by.
+ """
+ do_caching = True
+
+ # default realization
+ if check_count is None:
+ # in this case, the gain from caching is negligible
+ if len(arg) <= start_caching_at:
+ return False
+
+ if len(arg) <= 5000:
+ check_count = len(arg) // 10
+ else:
+ check_count = 500
+ else:
+ assert 0 <= check_count <= len(arg), (
+ "check_count must be in next bounds: [0; len(arg)]"
+ )
+ if check_count == 0:
+ return False
+
+ assert 0 < unique_share < 1, "unique_share must be in next bounds: (0; 1)"
+
+ try:
+ # We can't cache if the items are not hashable.
+ unique_elements = set(islice(arg, check_count))
+ except TypeError:
+ return False
+ if len(unique_elements) > check_count * unique_share:
+ do_caching = False
+ return do_caching
+
+
+def _maybe_cache(
+ arg: ArrayConvertible,
+ format: str | None,
+ cache: bool,
+ convert_listlike: Callable,
+) -> Series:
+ """
+ Create a cache of unique dates from an array of dates
+
+ Parameters
+ ----------
+ arg : listlike, tuple, 1-d array, Series
+ format : string
+ Strftime format to parse time
+ cache : bool
+ True attempts to create a cache of converted values
+ convert_listlike : function
+ Conversion function to apply on dates
+
+ Returns
+ -------
+ cache_array : Series
+ Cache of converted, unique dates. Can be empty
+ """
+ from pandas import Series
+
+ cache_array = Series(dtype=object)
+
+ if cache:
+ # Perform a quicker unique check
+ if not should_cache(arg):
+ return cache_array
+
+ if not isinstance(arg, (np.ndarray, ExtensionArray, Index, ABCSeries)):
+ arg = np.array(arg)
+
+ unique_dates = unique(arg)
+ if len(unique_dates) < len(arg):
+ cache_dates = convert_listlike(unique_dates, format)
+ # GH#45319
+ try:
+ cache_array = Series(cache_dates, index=unique_dates, copy=False)
+ except OutOfBoundsDatetime:
+ return cache_array
+ # GH#39882 and GH#35888 in case of None and NaT we get duplicates
+ if not cache_array.index.is_unique:
+ cache_array = cache_array[~cache_array.index.duplicated()]
+ return cache_array
+
+
+def _box_as_indexlike(
+ dt_array: ArrayLike, utc: bool = False, name: Hashable | None = None
+) -> Index:
+ """
+ Properly boxes the ndarray of datetimes to DatetimeIndex
+ if it is possible or to generic Index instead
+
+ Parameters
+ ----------
+ dt_array: 1-d array
+ Array of datetimes to be wrapped in an Index.
+ utc : bool
+ Whether to convert/localize timestamps to UTC.
+ name : string, default None
+ Name for a resulting index
+
+ Returns
+ -------
+ result : datetime of converted dates
+ - DatetimeIndex if convertible to sole datetime64 type
+ - general Index otherwise
+ """
+
+ if lib.is_np_dtype(dt_array.dtype, "M"):
+ tz = "utc" if utc else None
+ return DatetimeIndex(dt_array, tz=tz, name=name)
+ return Index(dt_array, name=name, dtype=dt_array.dtype)
+
+
+def _convert_and_box_cache(
+ arg: DatetimeScalarOrArrayConvertible,
+ cache_array: Series,
+ name: Hashable | None = None,
+) -> Index:
+ """
+ Convert array of dates with a cache and wrap the result in an Index.
+
+ Parameters
+ ----------
+ arg : integer, float, string, datetime, list, tuple, 1-d array, Series
+ cache_array : Series
+ Cache of converted, unique dates
+ name : string, default None
+ Name for a DatetimeIndex
+
+ Returns
+ -------
+ result : Index-like of converted dates
+ """
+ from pandas import Series
+
+ result = Series(arg, dtype=cache_array.index.dtype).map(cache_array)
+ return _box_as_indexlike(result._values, utc=False, name=name)
+
+
+def _convert_listlike_datetimes(
+ arg,
+ format: str | None,
+ name: Hashable | None = None,
+ utc: bool = False,
+ unit: str | None = None,
+ errors: DateTimeErrorChoices = "raise",
+ dayfirst: bool | None = None,
+ yearfirst: bool | None = None,
+ exact: bool = True,
+):
+ """
+ Helper function for to_datetime. Performs the conversions of 1D listlike
+ of dates
+
+ Parameters
+ ----------
+ arg : list, tuple, ndarray, Series, Index
+ date to be parsed
+ name : object
+ None or string for the Index name
+ utc : bool
+ Whether to convert/localize timestamps to UTC.
+ unit : str
+ None or string of the frequency of the passed data
+ errors : str
+ error handing behaviors from to_datetime, 'raise', 'coerce'
+ dayfirst : bool
+ dayfirst parsing behavior from to_datetime
+ yearfirst : bool
+ yearfirst parsing behavior from to_datetime
+ exact : bool, default True
+ exact format matching behavior from to_datetime
+
+ Returns
+ -------
+ Index-like of parsed dates
+ """
+ if isinstance(arg, (list, tuple)):
+ arg = np.array(arg, dtype="O")
+ elif isinstance(arg, NumpyExtensionArray):
+ arg = np.array(arg)
+
+ arg_dtype = getattr(arg, "dtype", None)
+ # these are shortcutable
+ tz = "utc" if utc else None
+ if isinstance(arg_dtype, DatetimeTZDtype):
+ if not isinstance(arg, (DatetimeArray, DatetimeIndex)):
+ return DatetimeIndex(arg, tz=tz, name=name)
+ if utc:
+ arg = arg.tz_convert(None).tz_localize("utc")
+ return arg
+
+ elif isinstance(arg_dtype, ArrowDtype) and arg_dtype.type is Timestamp:
+ # TODO: Combine with above if DTI/DTA supports Arrow timestamps
+ if utc:
+ # pyarrow uses UTC, not lowercase utc
+ if isinstance(arg, Index):
+ arg_array = cast(ArrowExtensionArray, arg.array)
+ if arg_dtype.pyarrow_dtype.tz is not None:
+ arg_array = arg_array._dt_tz_convert("UTC")
+ else:
+ arg_array = arg_array._dt_tz_localize("UTC")
+ arg = Index(arg_array, copy=False)
+ # ArrowExtensionArray
+ elif arg_dtype.pyarrow_dtype.tz is not None:
+ arg = arg._dt_tz_convert("UTC")
+ else:
+ arg = arg._dt_tz_localize("UTC")
+ return arg
+
+ elif lib.is_np_dtype(arg_dtype, "M"):
+ if not is_supported_dtype(arg_dtype):
+ # We go to closest supported reso, i.e. "s"
+ arg = astype_overflowsafe(
+ np.asarray(arg),
+ np.dtype("M8[s]"),
+ is_coerce=errors == "coerce",
+ )
+
+ if not isinstance(arg, (DatetimeArray, DatetimeIndex)):
+ return DatetimeIndex(arg, tz=tz, name=name)
+ elif utc:
+ # DatetimeArray, DatetimeIndex
+ return arg.tz_localize("utc")
+
+ return arg
+
+ elif unit is not None:
+ if format is not None:
+ raise ValueError("cannot specify both format and unit")
+ return _to_datetime_with_unit(arg, unit, name, utc, errors)
+ elif getattr(arg, "ndim", 1) > 1:
+ raise TypeError(
+ "arg must be a string, datetime, list, tuple, 1-d array, or Series"
+ )
+
+ # warn if passing timedelta64, raise for PeriodDtype
+ # NB: this must come after unit transformation
+ try:
+ arg, _ = maybe_convert_dtype(arg, copy=False, tz=libtimezones.maybe_get_tz(tz))
+ except TypeError:
+ if errors == "coerce":
+ npvalues = np.full(len(arg), np.datetime64("NaT", "ns"))
+ return DatetimeIndex(npvalues, name=name)
+ raise
+
+ arg = ensure_object(arg)
+
+ if format is None:
+ format = _guess_datetime_format_for_array(arg, dayfirst=dayfirst)
+
+ # `format` could be inferred, or user didn't ask for mixed-format parsing.
+ if format is not None and format != "mixed":
+ return _array_strptime_with_fallback(arg, name, utc, format, exact, errors)
+
+ result, tz_parsed = objects_to_datetime64(
+ arg,
+ dayfirst=dayfirst,
+ yearfirst=yearfirst,
+ utc=utc,
+ errors=errors,
+ allow_object=True,
+ )
+
+ if tz_parsed is not None:
+ # We can take a shortcut since the datetime64 numpy array
+ # is in UTC
+ out_unit = np.datetime_data(result.dtype)[0]
+ out_unit = cast("TimeUnit", out_unit)
+ dtype = tz_to_dtype(tz_parsed, out_unit)
+ dt64_values = result.view(f"M8[{dtype.unit}]")
+ dta = DatetimeArray._simple_new(dt64_values, dtype=dtype)
+ return DatetimeIndex._simple_new(dta, name=name)
+
+ return _box_as_indexlike(result, utc=utc, name=name)
+
+
+def _array_strptime_with_fallback(
+ arg,
+ name,
+ utc: bool,
+ fmt: str,
+ exact: bool,
+ errors: str,
+) -> Index:
+ """
+ Call array_strptime, with fallback behavior depending on 'errors'.
+ """
+ result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc)
+ if tz_out is not None:
+ unit = np.datetime_data(result.dtype)[0]
+ unit = cast("TimeUnit", unit)
+ dtype = DatetimeTZDtype(tz=tz_out, unit=unit)
+ dta = DatetimeArray._simple_new(result, dtype=dtype)
+ if utc:
+ dta = dta.tz_convert("UTC")
+ return Index(dta, name=name, copy=False)
+ elif result.dtype != object and utc:
+ unit = np.datetime_data(result.dtype)[0]
+ unit = cast("TimeUnit", unit)
+ res = Index(result, dtype=f"M8[{unit}, UTC]", name=name, copy=False)
+ return res
+ return Index(result, dtype=result.dtype, name=name, copy=False)
+
+
+def _to_datetime_with_unit(arg, unit, name, utc: bool, errors: str) -> Index:
+ """
+ to_datetime specalized to the case where a 'unit' is passed.
+ """
+ arg = extract_array(arg, extract_numpy=True)
+
+ # GH#30050 pass an ndarray to tslib.array_to_datetime
+ # because it expects an ndarray argument
+ if isinstance(arg, IntegerArray):
+ arr = arg.astype(f"datetime64[{unit}]")
+ tz_parsed = None
+ else:
+ arg = np.asarray(arg)
+
+ if arg.dtype.kind in "iu":
+ # Note we can't do "f" here because that could induce unwanted
+ # rounding GH#14156, GH#20445
+ arr = arg.astype(f"datetime64[{unit}]", copy=False)
+ dtype = get_supported_dtype(arr.dtype)
+ try:
+ arr = astype_overflowsafe(arr, dtype, copy=False)
+ except OutOfBoundsDatetime:
+ if errors == "raise":
+ raise
+ arg = arg.astype(object)
+ return _to_datetime_with_unit(arg, unit, name, utc, errors)
+ tz_parsed = None
+
+ elif arg.dtype.kind == "f":
+ with np.errstate(invalid="ignore"):
+ int_values = arg.astype(np.int64)
+ mask = np.isnan(arg)
+ if (mask | (arg == int_values)).all():
+ # With all-round-or-NaN entries, we give the requested unit
+ # back like with integers
+ result = _to_datetime_with_unit(
+ int_values, unit=unit, name=name, utc=utc, errors=errors
+ )
+ result._data[mask] = NaT
+ return result
+ with np.errstate(over="raise"):
+ try:
+ arr = cast_from_unit_vectorized(arg, unit=unit)
+ except OutOfBoundsDatetime as err:
+ if errors != "raise":
+ return _to_datetime_with_unit(
+ arg.astype(object), unit, name, utc, errors
+ )
+ raise OutOfBoundsDatetime(
+ f"cannot convert input with unit '{unit}'"
+ ) from err
+
+ arr = arr.view("M8[ns]")
+ tz_parsed = None
+ else:
+ arg = arg.astype(object, copy=False)
+ arr, tz_parsed = tslib.array_to_datetime(
+ arg,
+ utc=utc,
+ errors=errors,
+ unit_for_numerics=unit,
+ )
+
+ result = DatetimeIndex(arr, name=name)
+ if not isinstance(result, DatetimeIndex):
+ return result
+
+ # GH#23758: We may still need to localize the result with tz
+ # GH#25546: Apply tz_parsed first (from arg), then tz (from caller)
+ # result will be naive but in UTC
+ result = result.tz_localize("UTC").tz_convert(tz_parsed)
+
+ if utc:
+ if result.tz is None:
+ result = result.tz_localize("utc")
+ else:
+ result = result.tz_convert("utc")
+ return result
+
+
+def _adjust_to_origin(arg, origin, unit):
+ """
+ Helper function for to_datetime.
+ Adjust input argument to the specified origin
+
+ Parameters
+ ----------
+ arg : list, tuple, ndarray, Series, Index
+ date to be adjusted
+ origin : 'julian' or Timestamp
+ origin offset for the arg
+ unit : str
+ passed unit from to_datetime, must be 'D'
+
+ Returns
+ -------
+ ndarray or scalar of adjusted date(s)
+ """
+ if origin == "julian":
+ original = arg
+ j0 = Timestamp(0).to_julian_date()
+ if unit != "D":
+ raise ValueError("unit must be 'D' for origin='julian'")
+ try:
+ arg = arg - j0
+ except TypeError as err:
+ raise ValueError(
+ "incompatible 'arg' type for given 'origin'='julian'"
+ ) from err
+
+ # preemptively check this for a nice range
+ j_max = Timestamp.max.to_julian_date() - j0
+ j_min = Timestamp.min.to_julian_date() - j0
+ if np.any(arg > j_max) or np.any(arg < j_min):
+ raise OutOfBoundsDatetime(
+ f"{original} is Out of Bounds for origin='julian'"
+ )
+ else:
+ # arg must be numeric
+ if not (
+ (is_integer(arg) or is_float(arg)) or is_numeric_dtype(np.asarray(arg))
+ ):
+ raise ValueError(
+ f"'{arg}' is not compatible with origin='{origin}'; "
+ "it must be numeric with a unit specified"
+ )
+
+ # we are going to offset back to unix / epoch time
+ try:
+ if lib.is_integer(origin) or lib.is_float(origin):
+ offset = Timestamp(origin, unit=unit)
+ else:
+ offset = Timestamp(origin)
+ except OutOfBoundsDatetime as err:
+ raise OutOfBoundsDatetime(f"origin {origin} is Out of Bounds") from err
+ except ValueError as err:
+ raise ValueError(
+ f"origin {origin} cannot be converted to a Timestamp"
+ ) from err
+
+ if offset.tz is not None:
+ raise ValueError(f"origin offset {offset} must be tz-naive")
+ td_offset = offset - Timestamp(0)
+
+ # convert the offset to the unit of the arg
+ # this should be lossless in terms of precision
+ ioffset = td_offset // Timedelta(1, unit=unit)
+
+ # scalars & ndarray-like can handle the addition
+ if is_list_like(arg) and not isinstance(arg, (ABCSeries, Index, np.ndarray)):
+ arg = np.asarray(arg)
+ arg = arg + ioffset
+ return arg
+
+
+@overload
+def to_datetime(
+ arg: DatetimeScalar,
+ errors: DateTimeErrorChoices = ...,
+ dayfirst: bool = ...,
+ yearfirst: bool = ...,
+ utc: bool = ...,
+ format: str | None = ...,
+ exact: bool = ...,
+ unit: str | None = ...,
+ origin=...,
+ cache: bool = ...,
+) -> Timestamp: ...
+
+
+@overload
+def to_datetime(
+ arg: Series | DictConvertible,
+ errors: DateTimeErrorChoices = ...,
+ dayfirst: bool = ...,
+ yearfirst: bool = ...,
+ utc: bool = ...,
+ format: str | None = ...,
+ exact: bool = ...,
+ unit: str | None = ...,
+ origin=...,
+ cache: bool = ...,
+) -> Series: ...
+
+
+@overload
+def to_datetime(
+ arg: list | tuple | Index | ArrayLike,
+ errors: DateTimeErrorChoices = ...,
+ dayfirst: bool = ...,
+ yearfirst: bool = ...,
+ utc: bool = ...,
+ format: str | None = ...,
+ exact: bool = ...,
+ unit: str | None = ...,
+ origin=...,
+ cache: bool = ...,
+) -> DatetimeIndex: ...
+
+
+@set_module("pandas")
+def to_datetime(
+ arg: DatetimeScalarOrArrayConvertible | DictConvertible,
+ errors: DateTimeErrorChoices = "raise",
+ dayfirst: bool = False,
+ yearfirst: bool = False,
+ utc: bool = False,
+ format: str | None = None,
+ exact: bool | lib.NoDefault = lib.no_default,
+ unit: str | None = None,
+ origin: str = "unix",
+ cache: bool = True,
+) -> DatetimeIndex | Series | DatetimeScalar | NaTType:
+ """
+ Convert argument to datetime.
+
+ This function converts a scalar, array-like, :class:`Series` or
+ :class:`DataFrame`/dict-like to a pandas datetime object.
+
+ Parameters
+ ----------
+ arg : int, float, str, datetime, list, tuple, 1-d array, Series, DataFrame/dict-like
+ The object to convert to a datetime. If a :class:`DataFrame` is provided, the
+ method expects minimally the following columns: :const:`"year"`,
+ :const:`"month"`, :const:`"day"`. The column "year"
+ must be specified in 4-digit format.
+ errors : {'raise', 'coerce'}, default 'raise'
+ - If :const:`'raise'`, then invalid parsing will raise an exception.
+ - If :const:`'coerce'`, then invalid parsing will be set as :const:`NaT`.
+ dayfirst : bool, default False
+ Specify a date parse order if `arg` is str or is list-like.
+ If :const:`True`, parses dates with the day first, e.g. :const:`"10/11/12"`
+ is parsed as :const:`2012-11-10`.
+
+ .. warning::
+
+ ``dayfirst=True`` is not strict, but will prefer to parse
+ with day first.
+
+ yearfirst : bool, default False
+ Specify a date parse order if `arg` is str or is list-like.
+
+ - If :const:`True` parses dates with the year first, e.g.
+ :const:`"10/11/12"` is parsed as :const:`2010-11-12`.
+ - If both `dayfirst` and `yearfirst` are :const:`True`, `yearfirst` is
+ preceded (same as :mod:`dateutil`).
+
+ .. warning::
+
+ ``yearfirst=True`` is not strict, but will prefer to parse
+ with year first.
+
+ utc : bool, default False
+ Control timezone-related parsing, localization and conversion.
+
+ - If :const:`True`, the function *always* returns a timezone-aware
+ UTC-localized :class:`Timestamp`, :class:`Series` or
+ :class:`DatetimeIndex`. To do this, timezone-naive inputs are
+ *localized* as UTC, while timezone-aware inputs are *converted* to UTC.
+
+ - If :const:`False` (default), inputs will not be coerced to UTC.
+ Timezone-naive inputs will remain naive, while timezone-aware ones
+ will keep their time offsets. Limitations exist for mixed
+ offsets (typically, daylight savings), see :ref:`Examples
+ ` section for details.
+
+ See also: pandas general documentation about `timezone conversion and
+ localization
+ `_.
+
+ format : str, default None
+ The strftime to parse time, e.g. :const:`"%d/%m/%Y"`. See
+ `strftime documentation
+ `_ for more information on choices, though
+ note that :const:`"%f"` will parse all the way up to nanoseconds.
+ You can also pass:
+
+ - "ISO8601", to parse any `ISO8601 `_
+ time string (not necessarily in exactly the same format);
+ - "mixed", to infer the format for each element individually. This is risky,
+ and you should probably use it along with `dayfirst`.
+
+ .. note::
+
+ If a :class:`DataFrame` is passed, then `format` has no effect.
+
+ exact : bool, default True
+ Control how `format` is used:
+
+ - If :const:`True`, require an exact `format` match.
+ - If :const:`False`, allow the `format` to match anywhere in the target
+ string.
+
+ Cannot be used alongside ``format='ISO8601'`` or ``format='mixed'``.
+ unit : str, default 'ns'
+ The unit of the arg (D,s,ms,us,ns) denote the unit, which is an
+ integer or float number. This will be based off the origin.
+ Example, with ``unit='ms'`` and ``origin='unix'``, this would calculate
+ the number of milliseconds to the unix epoch start.
+ origin : scalar, default 'unix'
+ Define the reference date. The numeric values would be parsed as number
+ of units (defined by `unit`) since this reference date.
+
+ - If :const:`'unix'` (or POSIX) time; origin is set to 1970-01-01.
+ - If :const:`'julian'`, unit must be :const:`'D'`, and origin is set to
+ beginning of Julian Calendar. Julian day number :const:`0` is assigned
+ to the day starting at noon on January 1, 4713 BC.
+ - If Timestamp convertible (Timestamp, dt.datetime, np.datetimt64 or date
+ string), origin is set to Timestamp identified by origin.
+ - If a float or integer, origin is the difference
+ (in units determined by the ``unit`` argument) relative to 1970-01-01.
+ cache : bool, default True
+ If :const:`True`, use a cache of unique, converted dates to apply the
+ datetime conversion. May produce significant speed-up when parsing
+ duplicate date strings, especially ones with timezone offsets. The cache
+ is only used when there are at least 50 values. The presence of
+ out-of-bounds values will render the cache unusable and may slow down
+ parsing.
+
+ Returns
+ -------
+ datetime
+ If parsing succeeded.
+ Return type depends on input (types in parenthesis correspond to
+ fallback in case of unsuccessful timezone or out-of-range timestamp
+ parsing):
+
+ - scalar: :class:`Timestamp` (or :class:`datetime.datetime`)
+ - array-like: :class:`DatetimeIndex` (or :class:`Series` with
+ :class:`object` dtype containing :class:`datetime.datetime`)
+ - Series: :class:`Series` of :class:`datetime64` dtype (or
+ :class:`Series` of :class:`object` dtype containing
+ :class:`datetime.datetime`)
+ - DataFrame: :class:`Series` of :class:`datetime64` dtype (or
+ :class:`Series` of :class:`object` dtype containing
+ :class:`datetime.datetime`)
+
+ Raises
+ ------
+ ParserError
+ When parsing a date from string fails.
+ ValueError
+ When another datetime conversion error happens. For example when one
+ of 'year', 'month', day' columns is missing in a :class:`DataFrame`, or
+ when a Timezone-aware :class:`datetime.datetime` is found in an array-like
+ of mixed time offsets, and ``utc=False``, or when parsing datetimes
+ with mixed time zones unless ``utc=True``. If parsing datetimes with mixed
+ time zones, please specify ``utc=True``.
+
+ See Also
+ --------
+ DataFrame.astype : Cast argument to a specified dtype.
+ to_timedelta : Convert argument to timedelta.
+ convert_dtypes : Convert dtypes.
+
+ Notes
+ -----
+
+ Many input types are supported, and lead to different output types:
+
+ - **scalars** can be int, float, str, datetime object (from stdlib :mod:`datetime`
+ module or :mod:`numpy`). They are converted to :class:`Timestamp` when
+ possible, otherwise they are converted to :class:`datetime.datetime`.
+ None/NaN/null scalars are converted to :const:`NaT`.
+
+ - **array-like** can contain int, float, str, datetime objects. They are
+ converted to :class:`DatetimeIndex` when possible, otherwise they are
+ converted to :class:`Index` with :class:`object` dtype, containing
+ :class:`datetime.datetime`. None/NaN/null entries are converted to
+ :const:`NaT` in both cases.
+
+ - **Series** are converted to :class:`Series` with :class:`datetime64`
+ dtype when possible, otherwise they are converted to :class:`Series` with
+ :class:`object` dtype, containing :class:`datetime.datetime`. None/NaN/null
+ entries are converted to :const:`NaT` in both cases.
+
+ - **DataFrame/dict-like** are converted to :class:`Series` with
+ :class:`datetime64` dtype. For each row a datetime is created from assembling
+ the various dataframe columns. Column keys can be common abbreviations
+ like ['year', 'month', 'day', 'minute', 'second', 'ms', 'us', 'ns']) or
+ plurals of the same.
+
+ The following causes are responsible for :class:`datetime.datetime` objects
+ being returned (possibly inside an :class:`Index` or a :class:`Series` with
+ :class:`object` dtype) instead of a proper pandas designated type
+ (:class:`Timestamp`, :class:`DatetimeIndex` or :class:`Series`
+ with :class:`datetime64` dtype):
+
+ - when any input element is before :const:`Timestamp.min` or after
+ :const:`Timestamp.max`, see `timestamp limitations
+ `_.
+
+ - when ``utc=False`` (default) and the input is an array-like or
+ :class:`Series` containing mixed naive/aware datetime, or aware with mixed
+ time offsets. Note that this happens in the (quite frequent) situation when
+ the timezone has a daylight savings policy. In that case you may wish to
+ use ``utc=True``.
+
+ Examples
+ --------
+
+ **Handling various input formats**
+
+ Assembling a datetime from multiple columns of a :class:`DataFrame`. The keys
+ can be common abbreviations like ['year', 'month', 'day', 'minute', 'second',
+ 'ms', 'us', 'ns']) or plurals of the same
+
+ >>> df = pd.DataFrame({"year": [2015, 2016], "month": [2, 3], "day": [4, 5]})
+ >>> pd.to_datetime(df)
+ 0 2015-02-04
+ 1 2016-03-05
+ dtype: datetime64[us]
+
+ Using a unix epoch time
+
+ >>> pd.to_datetime(1490195805, unit="s")
+ Timestamp('2017-03-22 15:16:45')
+ >>> pd.to_datetime(1490195805433502912, unit="ns")
+ Timestamp('2017-03-22 15:16:45.433502912')
+
+ .. warning:: For float arg, precision rounding might happen. To prevent
+ unexpected behavior use a fixed-width exact type.
+
+ Using a non-unix epoch origin
+
+ >>> pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
+ DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'],
+ dtype='datetime64[s]', freq=None)
+
+ **Differences with strptime behavior**
+
+ :const:`"%f"` will parse all the way up to nanoseconds.
+
+ >>> pd.to_datetime("2018-10-26 12:00:00.0000000011", format="%Y-%m-%d %H:%M:%S.%f")
+ Timestamp('2018-10-26 12:00:00.000000001')
+
+ **Non-convertible date/times**
+
+ Passing ``errors='coerce'`` will force an out-of-bounds date to :const:`NaT`,
+ in addition to forcing non-dates (or non-parseable dates) to :const:`NaT`.
+
+ >>> pd.to_datetime("invalid for Ymd", format="%Y%m%d", errors="coerce")
+ NaT
+
+ .. _to_datetime_tz_examples:
+
+ **Timezones and time offsets**
+
+ The default behaviour (``utc=False``) is as follows:
+
+ - Timezone-naive inputs are converted to timezone-naive :class:`DatetimeIndex`:
+
+ >>> pd.to_datetime(["2018-10-26 12:00:00", "2018-10-26 13:00:15"])
+ DatetimeIndex(['2018-10-26 12:00:00', '2018-10-26 13:00:15'],
+ dtype='datetime64[us]', freq=None)
+
+ - Timezone-aware inputs *with constant time offset* are converted to
+ timezone-aware :class:`DatetimeIndex`:
+
+ >>> pd.to_datetime(["2018-10-26 12:00 -0500", "2018-10-26 13:00 -0500"])
+ DatetimeIndex(['2018-10-26 12:00:00-05:00', '2018-10-26 13:00:00-05:00'],
+ dtype='datetime64[us, UTC-05:00]', freq=None)
+
+ - However, timezone-aware inputs *with mixed time offsets* (for example
+ issued from a timezone with daylight savings, such as Europe/Paris)
+ are **not successfully converted** to a :class:`DatetimeIndex`.
+ Parsing datetimes with mixed time zones will raise a ValueError unless
+ ``utc=True``:
+
+ >>> pd.to_datetime(
+ ... ["2020-10-25 02:00 +0200", "2020-10-25 04:00 +0100"]
+ ... ) # doctest: +SKIP
+ ValueError: Mixed timezones detected. Pass utc=True in to_datetime
+ or tz='UTC' in DatetimeIndex to convert to a common timezone.
+
+ - To create a :class:`Series` with mixed offsets and ``object`` dtype, please use
+ :meth:`Series.apply` and :func:`datetime.datetime.strptime`:
+
+ >>> import datetime as dt
+ >>> ser = pd.Series(["2020-10-25 02:00 +0200", "2020-10-25 04:00 +0100"])
+ >>> ser.apply(lambda x: dt.datetime.strptime(x, "%Y-%m-%d %H:%M %z"))
+ 0 2020-10-25 02:00:00+02:00
+ 1 2020-10-25 04:00:00+01:00
+ dtype: object
+
+ - A mix of timezone-aware and timezone-naive inputs will also raise a ValueError
+ unless ``utc=True``:
+
+ >>> from datetime import datetime
+ >>> pd.to_datetime(
+ ... ["2020-01-01 01:00:00-01:00", datetime(2020, 1, 1, 3, 0)]
+ ... ) # doctest: +SKIP
+ ValueError: Mixed timezones detected. Pass utc=True in to_datetime
+ or tz='UTC' in DatetimeIndex to convert to a common timezone.
+
+ |
+
+ Setting ``utc=True`` solves most of the above issues:
+
+ - Timezone-naive inputs are *localized* as UTC
+
+ >>> pd.to_datetime(["2018-10-26 12:00", "2018-10-26 13:00"], utc=True)
+ DatetimeIndex(['2018-10-26 12:00:00+00:00', '2018-10-26 13:00:00+00:00'],
+ dtype='datetime64[us, UTC]', freq=None)
+
+ - Timezone-aware inputs are *converted* to UTC (the output represents the
+ exact same datetime, but viewed from the UTC time offset `+00:00`).
+
+ >>> pd.to_datetime(["2018-10-26 12:00 -0530", "2018-10-26 12:00 -0500"], utc=True)
+ DatetimeIndex(['2018-10-26 17:30:00+00:00', '2018-10-26 17:00:00+00:00'],
+ dtype='datetime64[us, UTC]', freq=None)
+
+ - Inputs can contain both string or datetime, the above
+ rules still apply
+
+ >>> pd.to_datetime(["2018-10-26 12:00", datetime(2020, 1, 1, 18)], utc=True)
+ DatetimeIndex(['2018-10-26 12:00:00+00:00', '2020-01-01 18:00:00+00:00'],
+ dtype='datetime64[us, UTC]', freq=None)
+ """
+ if exact is not lib.no_default and format in {"mixed", "ISO8601"}:
+ raise ValueError("Cannot use 'exact' when 'format' is 'mixed' or 'ISO8601'")
+ if arg is None:
+ return NaT
+
+ if origin != "unix":
+ arg = _adjust_to_origin(arg, origin, unit)
+
+ convert_listlike = partial(
+ _convert_listlike_datetimes,
+ utc=utc,
+ unit=unit,
+ dayfirst=dayfirst,
+ yearfirst=yearfirst,
+ errors=errors,
+ exact=exact, # type: ignore[arg-type]
+ )
+ result: Timestamp | NaTType | Series | Index
+
+ if isinstance(arg, Timestamp):
+ result = arg
+ if utc:
+ if arg.tz is not None:
+ result = arg.tz_convert("utc")
+ else:
+ result = arg.tz_localize("utc")
+ elif isinstance(arg, ABCSeries):
+ cache_array = _maybe_cache(arg, format, cache, convert_listlike)
+ if not cache_array.empty:
+ result = arg.map(cache_array)
+ else:
+ values = convert_listlike(arg._values, format)
+ result = arg._constructor(values, index=arg.index, name=arg.name)
+ elif isinstance(arg, (ABCDataFrame, abc.MutableMapping)):
+ result = _assemble_from_unit_mappings(arg, errors, utc)
+ elif isinstance(arg, Index):
+ cache_array = _maybe_cache(arg, format, cache, convert_listlike)
+ if not cache_array.empty:
+ result = _convert_and_box_cache(arg, cache_array, name=arg.name)
+ else:
+ result = convert_listlike(arg, format, name=arg.name)
+ elif is_list_like(arg):
+ try:
+ # error: Argument 1 to "_maybe_cache" has incompatible type
+ # "Union[float, str, datetime, List[Any], Tuple[Any, ...], ExtensionArray,
+ # ndarray[Any, Any], Series]"; expected "Union[List[Any], Tuple[Any, ...],
+ # Union[Union[ExtensionArray, ndarray[Any, Any]], Index, Series], Series]"
+ argc = cast(
+ Union[list, tuple, ExtensionArray, np.ndarray, "Series", Index], arg
+ )
+ cache_array = _maybe_cache(argc, format, cache, convert_listlike)
+ except OutOfBoundsDatetime:
+ # caching attempts to create a DatetimeIndex, which may raise
+ # an OOB. If that's the desired behavior, then just reraise...
+ if errors == "raise":
+ raise
+ # ... otherwise, continue without the cache.
+ from pandas import Series
+
+ cache_array = Series([], dtype=object) # just an empty array
+ if not cache_array.empty:
+ result = _convert_and_box_cache(argc, cache_array)
+ else:
+ result = convert_listlike(argc, format)
+ else:
+ result = convert_listlike(np.array([arg]), format)[0]
+ if isinstance(arg, bool) and isinstance(result, np.bool_):
+ result = bool(result) # TODO: avoid this kludge.
+
+ # error: Incompatible return value type (got "Union[Timestamp, NaTType,
+ # Series, Index]", expected "Union[DatetimeIndex, Series, float, str,
+ # NaTType, None]")
+ return result # type: ignore[return-value]
+
+
+# mappings for assembling units
+_unit_map = {
+ "year": "year",
+ "years": "year",
+ "month": "month",
+ "months": "month",
+ "day": "day",
+ "days": "day",
+ "hour": "h",
+ "hours": "h",
+ "minute": "m",
+ "minutes": "m",
+ "second": "s",
+ "seconds": "s",
+ "ms": "ms",
+ "millisecond": "ms",
+ "milliseconds": "ms",
+ "us": "us",
+ "microsecond": "us",
+ "microseconds": "us",
+ "ns": "ns",
+ "nanosecond": "ns",
+ "nanoseconds": "ns",
+}
+
+
+def _assemble_from_unit_mappings(
+ arg, errors: DateTimeErrorChoices, utc: bool
+) -> Series:
+ """
+ assemble the unit specified fields from the arg (DataFrame)
+ Return a Series for actual parsing
+
+ Parameters
+ ----------
+ arg : DataFrame
+ errors : {'raise', 'coerce'}, default 'raise'
+
+ - If :const:`'raise'`, then invalid parsing will raise an exception
+ - If :const:`'coerce'`, then invalid parsing will be set as :const:`NaT`
+ utc : bool
+ Whether to convert/localize timestamps to UTC.
+
+ Returns
+ -------
+ Series
+ """
+ from pandas import (
+ DataFrame,
+ to_numeric,
+ to_timedelta,
+ )
+
+ arg = DataFrame(arg)
+ if not arg.columns.is_unique:
+ raise ValueError("cannot assemble with duplicate keys")
+
+ # replace passed unit with _unit_map
+ def f(value):
+ if value in _unit_map:
+ return _unit_map[value]
+
+ # m is case significant
+ if value.lower() in _unit_map:
+ return _unit_map[value.lower()]
+
+ return value
+
+ unit = {k: f(k) for k in arg.keys()}
+ unit_rev = {v: k for k, v in unit.items()}
+
+ # we require at least Ymd
+ required = ["year", "month", "day"]
+ req = set(required) - set(unit_rev.keys())
+ if len(req):
+ _required = ",".join(sorted(req))
+ raise ValueError(
+ "to assemble mappings requires at least that "
+ f"[year, month, day] be specified: [{_required}] is missing"
+ )
+
+ # keys we don't recognize
+ excess = set(unit_rev.keys()) - set(_unit_map.values())
+ if len(excess):
+ _excess = ",".join(sorted(excess))
+ raise ValueError(
+ f"extra keys have been passed to the datetime assemblage: [{_excess}]"
+ )
+
+ def coerce(values):
+ # we allow coercion to if errors allows
+ values = to_numeric(values, errors=errors)
+
+ # prevent prevision issues in case of float32 # GH#60506
+ if is_float_dtype(values.dtype):
+ values = values.astype("float64")
+
+ # prevent overflow in case of int8 or int16
+ if is_integer_dtype(values.dtype):
+ values = values.astype("int64")
+ return values
+
+ values = (
+ coerce(arg[unit_rev["year"]]) * 10000
+ + coerce(arg[unit_rev["month"]]) * 100
+ + coerce(arg[unit_rev["day"]])
+ )
+ try:
+ values = to_datetime(values, format="%Y%m%d", errors=errors, utc=utc)
+ except (TypeError, ValueError) as err:
+ raise ValueError(f"cannot assemble the datetimes: {err}") from err
+
+ units: list[UnitChoices] = ["h", "m", "s", "ms", "us", "ns"]
+ for u in units:
+ value = unit_rev.get(u)
+ if value is not None and value in arg:
+ try:
+ values += to_timedelta(coerce(arg[value]), unit=u, errors=errors)
+ except (TypeError, ValueError) as err:
+ raise ValueError(
+ f"cannot assemble the datetimes [{value}]: {err}"
+ ) from err
+ return values
+
+
+__all__ = [
+ "DateParseError",
+ "should_cache",
+ "to_datetime",
+]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/tools/numeric.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/tools/numeric.py
new file mode 100644
index 0000000000000000000000000000000000000000..14921457194ca98dd3f92d1e2b4b5534c573f2b9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/core/tools/numeric.py
@@ -0,0 +1,327 @@
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ Literal,
+)
+
+import numpy as np
+
+from pandas._libs import (
+ lib,
+ missing as libmissing,
+)
+from pandas._libs.tslibs import (
+ Timedelta,
+ Timestamp,
+)
+from pandas.util._decorators import set_module
+from pandas.util._validators import check_dtype_backend
+
+from pandas.core.dtypes.cast import maybe_downcast_numeric
+from pandas.core.dtypes.common import (
+ ensure_object,
+ is_bool_dtype,
+ is_decimal,
+ is_integer_dtype,
+ is_number,
+ is_numeric_dtype,
+ is_scalar,
+ is_string_dtype,
+ needs_i8_conversion,
+)
+from pandas.core.dtypes.dtypes import ArrowDtype
+from pandas.core.dtypes.generic import (
+ ABCIndex,
+ ABCSeries,
+)
+
+from pandas.core.arrays import BaseMaskedArray
+from pandas.core.arrays.string_ import StringDtype
+
+if TYPE_CHECKING:
+ from pandas._typing import (
+ DateTimeErrorChoices,
+ DtypeBackend,
+ npt,
+ )
+
+
+@set_module("pandas")
+def to_numeric(
+ arg,
+ errors: DateTimeErrorChoices = "raise",
+ downcast: Literal["integer", "signed", "unsigned", "float"] | None = None,
+ dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default,
+):
+ """
+ Convert argument to a numeric type.
+
+ If the input is already of a numeric dtype, the dtype will be preserved.
+ For non-numeric inputs, the default return dtype is `float64` or `int64`
+ depending on the data supplied. Use the `downcast` parameter
+ to obtain other dtypes.
+
+ Please note that precision loss may occur if really large numbers
+ are passed in. Due to the internal limitations of `ndarray`, if
+ numbers smaller than `-9223372036854775808` (np.iinfo(np.int64).min)
+ or larger than `18446744073709551615` (np.iinfo(np.uint64).max) are
+ passed in, it is very likely they will be converted to float so that
+ they can be stored in an `ndarray`. These warnings apply similarly to
+ `Series` since it internally leverages `ndarray`.
+
+ Parameters
+ ----------
+ arg : scalar, list, tuple, 1-d array, or Series
+ Argument to be converted.
+
+ errors : {'raise', 'coerce'}, default 'raise'
+ - If 'raise', then invalid parsing will raise an exception.
+ - If 'coerce', then invalid parsing will be set as NaN.
+
+ downcast : str, default None
+ Can be 'integer', 'signed', 'unsigned', or 'float'.
+ If not None, and if the data has been successfully cast to a
+ numerical dtype (or if the data was numeric to begin with),
+ downcast that resulting data to the smallest numerical dtype
+ possible according to the following rules:
+
+ - 'integer' or 'signed': smallest signed int dtype (min.: np.int8)
+ - 'unsigned': smallest unsigned int dtype (min.: np.uint8)
+ - 'float': smallest float dtype (min.: np.float32)
+
+ As this behaviour is separate from the core conversion to
+ numeric values, any errors raised during the downcasting
+ will be surfaced regardless of the value of the 'errors' input.
+
+ In addition, downcasting will only occur if the size
+ of the resulting data's dtype is strictly larger than
+ the dtype it is to be cast to, so if none of the dtypes
+ checked satisfy that specification, no downcasting will be
+ performed on the data.
+
+ dtype_backend : {'numpy_nullable', 'pyarrow'}
+ Back-end data type applied to the resultant :class:`DataFrame`
+ (still experimental). If not specified, the default behavior
+ is to not use nullable data types. If specified, the behavior
+ is as follows:
+
+ * ``"numpy_nullable"``: returns nullable-dtype-backed object
+ * ``"pyarrow"``: returns with pyarrow-backed nullable object
+
+ .. versionadded:: 2.0
+
+ Returns
+ -------
+ ret
+ Numeric if parsing succeeded.
+ Return type depends on input. Series if Series, otherwise ndarray.
+
+ Raises
+ ------
+ ValueError
+ If the input contains non-numeric values and `errors='raise'`.
+ TypeError
+ If the input is not list-like, 1D, or scalar convertible to numeric,
+ such as nested lists or unsupported input types (e.g., dict).
+
+ See Also
+ --------
+ DataFrame.astype : Cast argument to a specified dtype.
+ to_datetime : Convert argument to datetime.
+ to_timedelta : Convert argument to timedelta.
+ numpy.ndarray.astype : Cast a numpy array to a specified type.
+ DataFrame.convert_dtypes : Convert dtypes.
+
+ Examples
+ --------
+ Take separate series and convert to numeric, coercing when told to
+
+ >>> s = pd.Series(["1.0", "2", -3])
+ >>> pd.to_numeric(s)
+ 0 1.0
+ 1 2.0
+ 2 -3.0
+ dtype: float64
+ >>> pd.to_numeric(s, downcast="float")
+ 0 1.0
+ 1 2.0
+ 2 -3.0
+ dtype: float32
+ >>> pd.to_numeric(s, downcast="signed")
+ 0 1
+ 1 2
+ 2 -3
+ dtype: int8
+ >>> s = pd.Series(["apple", "1.0", "2", -3])
+ >>> pd.to_numeric(s, errors="coerce")
+ 0 NaN
+ 1 1.0
+ 2 2.0
+ 3 -3.0
+ dtype: float64
+
+ Downcasting of nullable integer and floating dtypes is supported:
+
+ >>> s = pd.Series([1, 2, 3], dtype="Int64")
+ >>> pd.to_numeric(s, downcast="integer")
+ 0 1
+ 1 2
+ 2 3
+ dtype: Int8
+ >>> s = pd.Series([1.0, 2.1, 3.0], dtype="Float64")
+ >>> pd.to_numeric(s, downcast="float")
+ 0 1.0
+ 1 2.1
+ 2 3.0
+ dtype: Float32
+ """
+ if downcast not in (None, "integer", "signed", "unsigned", "float"):
+ raise ValueError("invalid downcasting method provided")
+
+ if errors not in ("raise", "coerce"):
+ raise ValueError("invalid error value specified")
+
+ check_dtype_backend(dtype_backend)
+
+ is_series = False
+ is_index = False
+ is_scalars = False
+
+ if isinstance(arg, ABCSeries):
+ is_series = True
+ values = arg.values
+ elif isinstance(arg, ABCIndex):
+ is_index = True
+ if needs_i8_conversion(arg.dtype):
+ values = arg.view("i8")
+ else:
+ values = arg.values
+ elif isinstance(arg, (list, tuple)):
+ values = np.array(arg, dtype="O")
+ elif is_scalar(arg):
+ if is_decimal(arg):
+ return float(arg)
+ if is_number(arg):
+ return arg
+ if isinstance(arg, (Timedelta, Timestamp)):
+ return arg._value
+ is_scalars = True
+ values = np.array([arg], dtype="O")
+ elif getattr(arg, "ndim", 1) > 1:
+ raise TypeError("arg must be a list, tuple, 1-d array, or Series")
+ else:
+ values = arg
+
+ # GH33013: for IntegerArray & FloatingArray extract non-null values for casting
+ # save mask to reconstruct the full array after casting
+ mask: npt.NDArray[np.bool_] | None = None
+ if isinstance(values, BaseMaskedArray):
+ mask = values._mask
+ values = values._data[~mask]
+
+ values_dtype = getattr(values, "dtype", None)
+ if isinstance(values_dtype, ArrowDtype):
+ mask = values.isna()
+ values = values.dropna().to_numpy()
+ new_mask: np.ndarray | None = None
+ if is_numeric_dtype(values_dtype):
+ pass
+ elif lib.is_np_dtype(values_dtype, "mM"):
+ values = values.view(np.int64)
+ else:
+ values = ensure_object(values)
+ coerce_numeric = errors != "raise"
+ values, new_mask = lib.maybe_convert_numeric( # type: ignore[call-overload]
+ values,
+ set(),
+ coerce_numeric=coerce_numeric,
+ convert_to_masked_nullable=dtype_backend is not lib.no_default
+ or (
+ isinstance(values_dtype, StringDtype)
+ and values_dtype.na_value is libmissing.NA
+ ),
+ )
+
+ if new_mask is not None:
+ # Remove unnecessary values, is expected later anyway and enables
+ # downcasting
+ values = values[~new_mask]
+ elif (dtype_backend is not lib.no_default and new_mask is None) or (
+ isinstance(values_dtype, StringDtype) and values_dtype.na_value is libmissing.NA
+ ):
+ new_mask = np.zeros(values.shape, dtype=np.bool_)
+
+ # attempt downcast only if the data has been successfully converted
+ # to a numerical dtype and if a downcast method has been specified
+ if downcast is not None and is_numeric_dtype(values.dtype):
+ typecodes: str | None = None
+
+ if downcast in ("integer", "signed"):
+ typecodes = np.typecodes["Integer"]
+ elif downcast == "unsigned" and (not len(values) or np.min(values) >= 0):
+ typecodes = np.typecodes["UnsignedInteger"]
+ elif downcast == "float":
+ typecodes = np.typecodes["Float"]
+
+ # pandas support goes only to np.float32,
+ # as float dtypes smaller than that are
+ # extremely rare and not well supported
+ float_32_char = np.dtype(np.float32).char
+ float_32_ind = typecodes.index(float_32_char)
+ typecodes = typecodes[float_32_ind:]
+
+ if typecodes is not None:
+ # from smallest to largest
+ for typecode in typecodes:
+ dtype = np.dtype(typecode)
+ if dtype.itemsize <= values.dtype.itemsize:
+ values = maybe_downcast_numeric(values, dtype)
+
+ # successful conversion
+ if values.dtype == dtype:
+ break
+
+ # GH33013: for IntegerArray, BooleanArray & FloatingArray need to reconstruct
+ # masked array
+ if (mask is not None or new_mask is not None) and not is_string_dtype(values.dtype):
+ if mask is None or (new_mask is not None and new_mask.shape == mask.shape):
+ # GH 52588
+ mask = new_mask
+ else:
+ mask = mask.copy()
+ assert isinstance(mask, np.ndarray)
+ data = np.zeros(mask.shape, dtype=values.dtype)
+ data[~mask] = values
+
+ from pandas.core.arrays import (
+ ArrowExtensionArray,
+ BooleanArray,
+ FloatingArray,
+ IntegerArray,
+ )
+
+ klass: type[IntegerArray | BooleanArray | FloatingArray]
+ if is_integer_dtype(data.dtype):
+ klass = IntegerArray
+ elif is_bool_dtype(data.dtype):
+ klass = BooleanArray
+ else:
+ klass = FloatingArray
+ values = klass(data, mask)
+
+ if dtype_backend == "pyarrow" or isinstance(values_dtype, ArrowDtype):
+ values = ArrowExtensionArray(values.__arrow_array__())
+
+ if is_series:
+ return arg._constructor(values, index=arg.index, name=arg.name)
+ elif is_index:
+ # because we want to coerce to numeric if possible,
+ # do not use _shallow_copy
+ from pandas import Index
+
+ return Index(values, name=arg.name)
+ elif is_scalars:
+ return values[0]
+ else:
+ return values
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/pyproject.toml b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..e6684b895f99153a2da09fdd5382ef37eb6dbffa
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/pyproject.toml
@@ -0,0 +1,835 @@
+[build-system]
+# Minimum requirements for the build system to execute.
+# See https://github.com/scipy/scipy/pull/12940 for the AIX issue.
+requires = [
+ "meson-python>=0.17.1,<1",
+ "meson>=1.2.1,<2",
+ "wheel",
+ "Cython>3.1.0,<4.0.0a0", # Note: sync with setup.py, environment.yml and asv.conf.json
+ # Force numpy higher than 2.0, so that built wheels are compatible
+ # with both numpy 1 and 2
+ "numpy>=2.0.0",
+ "versioneer[toml]"
+]
+
+build-backend = "mesonpy"
+
+[project]
+name = 'pandas'
+dynamic = [
+ 'version'
+]
+description = 'Powerful data structures for data analysis, time series, and statistics'
+readme = 'README.md'
+authors = [
+ { name = 'The Pandas Development Team', email='pandas-dev@python.org' },
+]
+license = {file = 'LICENSE'}
+requires-python = '>=3.11'
+dependencies = [
+ "numpy>=1.26.0; python_version < '3.14'",
+ # Earlier versions of NumPy are fundamentally broken on 3.14
+ "numpy>=2.3.3; python_version >= '3.14'",
+ "python-dateutil>=2.8.2",
+ "tzdata; sys_platform == 'win32'",
+ # Emscripten is the platform system for Pyodide.
+ "tzdata; sys_platform == 'emscripten'",
+]
+classifiers = [
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Console',
+ 'Intended Audience :: Science/Research',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Cython',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3 :: Only',
+ 'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
+ 'Programming Language :: Python :: 3.13',
+ 'Programming Language :: Python :: 3.14',
+ 'Topic :: Scientific/Engineering'
+]
+
+[project.urls]
+homepage = 'https://pandas.pydata.org'
+documentation = 'https://pandas.pydata.org/docs/'
+repository = 'https://github.com/pandas-dev/pandas'
+
+[project.entry-points."pandas_plotting_backends"]
+matplotlib = "pandas:plotting._matplotlib"
+
+[project.optional-dependencies]
+test = ['hypothesis>=6.116.0', 'pytest>=8.3.4', 'pytest-xdist>=3.6.1']
+pyarrow = ['pyarrow>=13.0.0']
+performance = ['bottleneck>=1.4.2', 'numba>=0.60.0', 'numexpr>=2.10.2']
+computation = ['scipy>=1.14.1', 'xarray>=2024.10.0']
+fss = ['fsspec>=2024.10.0']
+aws = ['s3fs>=2024.10.0']
+gcp = ['gcsfs>=2024.10.0']
+excel = ['odfpy>=1.4.1', 'openpyxl>=3.1.5', 'python-calamine>=0.3.0', 'pyxlsb>=1.0.10', 'xlrd>=2.0.1', 'xlsxwriter>=3.2.0']
+parquet = ['pyarrow>=13.0.0']
+feather = ['pyarrow>=13.0.0']
+iceberg = ['pyiceberg>=0.8.1']
+hdf5 = ['tables>=3.10.1']
+spss = ['pyreadstat>=1.2.8']
+postgresql = ['SQLAlchemy>=2.0.36', 'psycopg2>=2.9.10', 'adbc-driver-postgresql>=1.2.0']
+mysql = ['SQLAlchemy>=2.0.36', 'pymysql>=1.1.1']
+sql-other = ['SQLAlchemy>=2.0.36', 'adbc-driver-postgresql>=1.2.0', 'adbc-driver-sqlite>=1.2.0']
+html = ['beautifulsoup4>=4.12.3', 'html5lib>=1.1', 'lxml>=5.3.0']
+xml = ['lxml>=5.3.0']
+plot = ['matplotlib>=3.9.3']
+output-formatting = ['jinja2>=3.1.5', 'tabulate>=0.9.0']
+clipboard = ['PyQt5>=5.15.9', 'qtpy>=2.4.2']
+compression = ['zstandard>=0.23.0']
+timezone = ['pytz>=2024.2']
+all = ['adbc-driver-postgresql>=1.2.0',
+ 'adbc-driver-sqlite>=1.2.0',
+ 'beautifulsoup4>=4.12.3',
+ 'bottleneck>=1.4.2',
+ 'fastparquet>=2024.11.0',
+ 'fsspec>=2024.10.0',
+ 'gcsfs>=2024.10.0',
+ 'html5lib>=1.1',
+ 'hypothesis>=6.116.0',
+ 'jinja2>=3.1.5',
+ 'lxml>=5.3.0',
+ 'matplotlib>=3.9.3',
+ 'numba>=0.60.0',
+ 'numexpr>=2.10.2',
+ 'odfpy>=1.4.1',
+ 'openpyxl>=3.1.5',
+ 'psycopg2>=2.9.10',
+ 'pyarrow>=13.0.0',
+ 'pyiceberg>=0.8.1',
+ 'pymysql>=1.1.1',
+ 'PyQt5>=5.15.9',
+ 'pyreadstat>=1.2.8',
+ 'pytest>=8.3.4',
+ 'pytest-xdist>=3.6.1',
+ 'python-calamine>=0.3.0',
+ 'pytz>=2024.2',
+ 'pyxlsb>=1.0.10',
+ 'qtpy>=2.4.2',
+ 'scipy>=1.14.1',
+ 's3fs>=2024.10.0',
+ 'SQLAlchemy>=2.0.36',
+ 'tables>=3.10.1',
+ 'tabulate>=0.9.0',
+ 'xarray>=2024.10.0',
+ 'xlrd>=2.0.1',
+ 'xlsxwriter>=3.2.0',
+ 'zstandard>=0.23.0']
+
+# TODO: Remove after setuptools support is dropped.
+[tool.setuptools]
+include-package-data = true
+
+[tool.setuptools.packages.find]
+include = ["pandas", "pandas.*"]
+namespaces = false
+
+[tool.setuptools.exclude-package-data]
+"*" = ["*.c", "*.h"]
+
+# See the docstring in versioneer.py for instructions. Note that you must
+# re-run 'versioneer.py setup' after changing this section, and commit the
+# resulting files.
+[tool.versioneer]
+VCS = "git"
+style = "pep440"
+versionfile_source = "pandas/_version.py"
+versionfile_build = "pandas/_version.py"
+tag_prefix = "v"
+parentdir_prefix = "pandas-"
+
+[tool.meson-python.args]
+setup = ['--vsenv'] # For Windows
+
+[tool.cibuildwheel]
+skip = ["*_i686", "*_ppc64le", "*_s390x"]
+build-verbosity = 3
+environment = {LDFLAGS="-Wl,--strip-all"}
+test-extras = "test"
+test-command = """
+ PANDAS_CI='1' python -c 'import pandas as pd; \
+ 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"]); \
+ pd.test(extra_args=["-m not clipboard and single_cpu and not slow and not network and not db", "--no-strict-data-files"]);' \
+ """
+enable = ["cpython-freethreading"]
+before-build = "PACKAGE_DIR={package} bash {package}/scripts/cibw_before_build.sh"
+
+[tool.cibuildwheel.windows]
+environment = {}
+before-build = "pip install delvewheel"
+test-command = """
+ set PANDAS_CI='1' && \
+ python -c "import pandas as pd; \
+ pd.test(extra_args=['--no-strict-data-files', '-m not clipboard and not single_cpu and not slow and not network and not db']);" \
+ """
+repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}"
+
+[[tool.cibuildwheel.overrides]]
+select = "*-manylinux_aarch64*"
+test-command = """
+ PANDAS_CI='1' python -c 'import pandas as pd; \
+ 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"]); \
+ pd.test(extra_args=["-m not clipboard and single_cpu and not slow and not network and not db", "--no-strict-data-files"]);' \
+ """
+
+[[tool.cibuildwheel.overrides]]
+select = "*-musllinux*"
+before-test = "apk update && apk add musl-locales"
+
+[[tool.cibuildwheel.overrides]]
+# Don't strip wheels on macOS.
+# macOS doesn't support stripping wheels with linker
+# https://github.com/MacPython/numpy-wheels/pull/87#issuecomment-624878264
+select = "*-macosx*"
+environment = {CFLAGS="-g0"}
+
+[[tool.cibuildwheel.overrides]]
+select = "*pyodide*"
+test-requires = "pytest>=8.3.4 hypothesis>=6.116.0"
+# Pyodide repairs wheels on its own, using auditwheel-emscripten
+repair-wheel-command = ""
+# https://github.com/pyodide/pyodide/issues/5805
+build-verbosity = 1
+test-command = """
+ PANDAS_CI='1' python -c 'import pandas as pd; \
+ pd.test(extra_args=["-m not clipboard and not single_cpu and not slow and not network and not db", "--no-strict-data-files"]);' \
+ """
+
+[tool.ruff]
+line-length = 88
+target-version = "py311"
+fix = true
+
+[tool.ruff.lint]
+unfixable = []
+typing-modules = ["pandas._typing"]
+
+select = [
+ # pyflakes
+ "F",
+ # pycodestyle
+ "E", "W",
+ # flake8-2020
+ "YTT",
+ # flake8-bugbear
+ "B",
+ # flake8-quotes
+ "Q",
+ # flake8-debugger
+ "T10",
+ # flake8-gettext
+ "INT",
+ # pylint
+ "PL",
+ # flake8-pytest-style
+ "PT",
+ # misc lints
+ "PIE",
+ # flake8-pyi
+ "PYI",
+ # tidy imports
+ "TID",
+ # implicit string concatenation
+ "ISC",
+ # flake8-type-checking
+ "TC",
+ # comprehensions
+ "C4",
+ # pygrep-hooks
+ "PGH",
+ # Ruff-specific rules
+ "RUF",
+ # flake8-bandit: exec-builtin
+ "S102",
+ # numpy-legacy-random
+ "NPY002",
+ # Perflint
+ "PERF",
+ # flynt
+ "FLY",
+ # flake8-logging-format
+ "G",
+ # flake8-future-annotations
+ "FA",
+ # unconventional-import-alias
+ "ICN001",
+ # flake8-slots
+ "SLOT",
+ # flake8-raise
+ "RSE"
+]
+
+ignore = [
+ ### Intentionally disabled
+ # module level import not at top of file
+ "E402",
+ # do not assign a lambda expression, use a def
+ "E731",
+ # controversial
+ "B007",
+ # controversial
+ "B008",
+ # getattr is used to side-step mypy
+ "B010",
+ # tests use comparisons but not their returned value
+ "B015",
+ # Function definition does not bind loop variable
+ "B023",
+ # Too many arguments to function call
+ "PLR0913",
+ # Too many returns
+ "PLR0911",
+ # Too many branches
+ "PLR0912",
+ # Too many statements
+ "PLR0915",
+ # Redefined loop name
+ "PLW2901",
+ # Global statements are discouraged
+ "PLW0603",
+ # Use `typing.NamedTuple` instead of `collections.namedtuple`
+ "PYI024",
+ # Use of possibly insecure function; consider using ast.literal_eval
+ "S307",
+ # while int | float can be shortened to float, the former is more explicit
+ "PYI041",
+ # incorrect-dict-iterator, flags valid Series.items usage
+ "PERF102",
+ # pytest-parametrize-names-wrong-type
+ "PT006",
+ # pytest-parametrize-values-wrong-type
+ "PT007",
+ # pytest-patch-with-lambda
+ "PT008",
+ # pytest-raises-with-multiple-statements
+ "PT012",
+ # pytest-assert-in-except
+ "PT017",
+ # pytest-composite-assertion
+ "PT018",
+ # pytest-fixture-param-without-value
+ "PT019",
+ # The following rules may cause conflicts when used with the formatter:
+ "ISC001",
+ # if-stmt-min-max
+ "PLR1730",
+ # nan-comparison
+ "PLW0177",
+
+ ### TODO: Enable gradually
+ # Useless statement
+ "B018",
+ # Magic number
+ "PLR2004",
+ # comparison-with-itself
+ "PLR0124",
+ # pairwise-over-zipped
+ "RUF007",
+ # mutable-class-default
+ "RUF012",
+ # type-comparison
+ "E721",
+ # self-or-cls-assignment
+ "PLW0642",
+ # literal-membership
+ "PLR6201", # 847 errors
+ # Method could be a function, class method, or static method
+ "PLR6301", # 11411 errors
+ # Private name import
+ "PLC2701", # 27 errors
+ # Too many positional arguments (6/5)
+ "PLR0917", # 470 errors
+ # compare-to-empty-string
+ "PLC1901",
+ # `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument
+ "PLW1514", # 1 error
+ # Object does not implement `__hash__` method
+ "PLW1641", # 16 errors
+ # Bad or misspelled dunder method name
+ "PLW3201", # 69 errors, seems to be all false positive
+ # Unpacking a dictionary in iteration without calling `.items()`
+ "PLE1141", # autofixable
+ # import-outside-toplevel
+ "PLC0415",
+ # unnecessary-dunder-call
+ "PLC2801",
+ # comparison-with-itself
+ "PLR0124",
+ # too-many-public-methods
+ "PLR0904",
+ # too-many-return-statements
+ "PLR0911",
+ # too-many-branches
+ "PLR0912",
+ # too-many-arguments
+ "PLR0913",
+ # too-many-locals
+ "PLR0914",
+ # too-many-statements
+ "PLR0915",
+ # too-many-boolean-expressions
+ "PLR0916",
+ # too-many-nested-blocks
+ "PLR1702",
+ # redefined-argument-from-local
+ "PLR1704",
+ # unnecessary-lambda
+ "PLW0108",
+ # global-statement
+ "PLW0603",
+ # runtime-cast-value
+ "TC006",
+ # unused-unpacked-variable
+ "RUF059",
+ # pytest-raises-ambiguous-pattern
+ "RUF043",
+]
+
+exclude = [
+ "doc/sphinxext/*.py",
+ "doc/build/*.py",
+ "doc/temp/*.py",
+ ".eggs/*.py",
+ # vendored files
+ "pandas/util/version/*",
+ "pandas/io/clipboard/__init__.py",
+ # exclude asv benchmark environments from linting
+ "env",
+]
+
+[tool.ruff.lint.flake8-tidy-imports.banned-api]
+"urllib.request.urlopen".msg = "Use pandas.io.common.urlopen instead of urllib.request.urlopen"
+# numpy.random is banned but np.random is not. Is this intentional?
+# "numpy.random".msg = "Do not use numpy.random"
+"pytest.warns".msg = "Use tm.assert_produces_warning instead of pytest.warns"
+"pytest.xfail".msg = "Use pytest.mark.xfail instead of pytest.xfail"
+"conftest".msg = "No direct imports from conftest"
+"numpy.testing".msg = "Do not use numpy.testing"
+# "numpy.array_equal".msg = "Do not use numpy.array_equal" # Used in pandas/core
+"unittest.mock".msg = "use pytest builtin monkeypatch fixture instead"
+"os.remove".msg = "Do not use os.remove"
+
+
+
+[tool.ruff.lint.flake8-import-conventions.aliases]
+"pandas.core.construction.array" = "pd_array"
+
+[tool.ruff.lint.per-file-ignores]
+# relative imports allowed for asv_bench
+"asv_bench/*" = ["TID", "NPY002"]
+# to be enabled gradually
+"pandas/tests/*" = ["B028", "FLY"]
+# Keep this one enabled
+"pandas/_typing.py" = ["TC"]
+
+# TODO: Fix B905 (zip-without-explicit-strict) - Remove files below as they're fixed
+# For contributors working on this issue:
+# After adding strict={True,False} to zip() calls in a file,
+# remove its line from this section
+"pandas/tests/frame/conftest.py" = ["B905"]
+"pandas/tests/frame/constructors/test_from_dict.py" = ["B905"]
+"pandas/tests/frame/indexing/test_delitem.py" = ["B905"]
+"pandas/tests/frame/indexing/test_getitem.py" = ["B905"]
+"pandas/tests/frame/indexing/test_indexing.py" = ["B905"]
+"pandas/tests/frame/indexing/test_setitem.py" = ["B905"]
+"pandas/tests/frame/indexing/test_xs.py" = ["B905"]
+"pandas/tests/frame/methods/test_drop.py" = ["B905"]
+"pandas/tests/frame/methods/test_isin.py" = ["B905"]
+"pandas/tests/frame/methods/test_pop.py" = ["B905"]
+"pandas/tests/frame/methods/test_replace.py" = ["B905"]
+"pandas/tests/frame/methods/test_reset_index.py" = ["B905"]
+"pandas/tests/frame/methods/test_to_dict.py" = ["B905"]
+"pandas/tests/frame/test_api.py" = ["B905"]
+"pandas/tests/frame/test_constructors.py" = ["B905"]
+"pandas/tests/frame/test_iteration.py" = ["B905"]
+"pandas/tests/frame/test_query_eval.py" = ["B905"]
+"pandas/tests/frame/test_stack_unstack.py" = ["B905"]
+"pandas/tests/frame/test_subclass.py" = ["B905"]
+"pandas/tests/frame/test_ufunc.py" = ["B905"]
+"pandas/tests/groupby/test_groupby_dropna.py" = ["B905"]
+"pandas/tests/groupby/test_groupby.py" = ["B905"]
+"pandas/tests/groupby/test_grouping.py" = ["B905"]
+"pandas/tests/groupby/test_raises.py" = ["B905"]
+"pandas/tests/groupby/test_timegrouper.py" = ["B905"]
+"pandas/tests/groupby/transform/test_transform.py" = ["B905"]
+"pandas/tests/indexes/categorical/test_map.py" = ["B905"]
+"pandas/tests/indexes/datetimes/methods/test_astype.py" = ["B905"]
+"pandas/tests/indexes/datetimes/test_formats.py" = ["B905"]
+"pandas/tests/indexes/datetimes/test_partial_slicing.py" = ["B905"]
+"pandas/tests/indexes/datetimes/test_scalar_compat.py" = ["B905"]
+"pandas/tests/indexes/datetimes/test_timezones.py" = ["B905"]
+"pandas/tests/indexes/interval/test_constructors.py" = ["B905"]
+"pandas/tests/indexes/interval/test_formats.py" = ["B905"]
+"pandas/tests/indexes/interval/test_interval.py" = ["B905"]
+"pandas/tests/indexes/period/methods/test_asfreq.py" = ["B905"]
+"pandas/tests/indexes/period/test_constructors.py" = ["B905"]
+"pandas/tests/indexes/period/test_formats.py" = ["B905"]
+"pandas/tests/indexes/period/test_period.py" = ["B905"]
+"pandas/tests/indexes/test_base.py" = ["B905"]
+"pandas/tests/indexes/test_datetimelike.py" = ["B905"]
+"pandas/tests/indexes/test_old_base.py" = ["B905"]
+"pandas/tests/indexes/test_setops.py" = ["B905"]
+"pandas/tests/indexes/timedeltas/test_formats.py" = ["B905"]
+"pandas/tests/indexing/interval/test_interval.py" = ["B905"]
+"pandas/tests/indexing/multiindex/test_getitem.py" = ["B905"]
+"pandas/tests/indexing/multiindex/test_iloc.py" = ["B905"]
+"pandas/tests/indexing/multiindex/test_indexing_slow.py" = ["B905"]
+"pandas/tests/indexing/multiindex/test_loc.py" = ["B905"]
+"pandas/tests/indexing/multiindex/test_setitem.py" = ["B905"]
+"pandas/tests/indexing/multiindex/test_sorted.py" = ["B905"]
+"pandas/tests/indexing/test_coercion.py" = ["B905"]
+"pandas/tests/indexing/test_loc.py" = ["B905"]
+"pandas/tests/internals/test_internals.py" = ["B905"]
+"pandas/tests/io/excel/test_writers.py" = ["B905"]
+"pandas/tests/io/formats/style/test_style.py" = ["B905"]
+"pandas/tests/io/formats/test_format.py" = ["B905"]
+"pandas/tests/io/formats/test_ipython_compat.py" = ["B905"]
+"pandas/tests/io/formats/test_to_string.py" = ["B905"]
+"pandas/tests/io/generate_legacy_storage_files.py" = ["B905"]
+"pandas/tests/io/parser/conftest.py" = ["B905"]
+"pandas/tests/io/parser/dtypes/test_categorical.py" = ["B905"]
+"pandas/tests/io/parser/test_header.py" = ["B905"]
+"pandas/tests/io/parser/test_python_parser_only.py" = ["B905"]
+"pandas/tests/io/pytables/test_complex.py" = ["B905"]
+"pandas/tests/io/pytables/test_select.py" = ["B905"]
+"pandas/tests/io/sas/test_sas7bdat.py" = ["B905"]
+"pandas/tests/io/test_gcs.py" = ["B905"]
+"pandas/tests/io/test_html.py" = ["B905"]
+"pandas/tests/io/test_sql.py" = ["B905"]
+"pandas/tests/io/test_stata.py" = ["B905"]
+"pandas/tests/plotting/common.py" = ["B905"]
+"pandas/tests/plotting/frame/test_frame_color.py" = ["B905"]
+"pandas/tests/resample/test_base.py" = ["B905"]
+"pandas/tests/reshape/concat/test_concat.py" = ["B905"]
+"pandas/tests/reshape/concat/test_index.py" = ["B905"]
+"pandas/tests/reshape/test_melt.py" = ["B905"]
+"pandas/tests/reshape/test_qcut.py" = ["B905"]
+"pandas/tests/scalar/period/test_asfreq.py" = ["B905"]
+"pandas/tests/series/accessors/test_dt_accessor.py" = ["B905"]
+"pandas/tests/series/indexing/test_get.py" = ["B905"]
+"pandas/tests/series/test_api.py" = ["B905"]
+"pandas/tests/series/test_constructors.py" = ["B905"]
+"pandas/tests/strings/conftest.py" = ["B905"]
+
+[tool.ruff.lint.flake8-pytest-style]
+fixture-parentheses = false
+mark-parentheses = false
+
+[tool.ruff.format]
+docstring-code-format = true
+
+[tool.pytest.ini_options]
+# sync minversion with pyproject.toml & install.rst
+minversion = "8.3.4"
+addopts = "--strict-markers --strict-config --capture=no --durations=30 --junitxml=test-data.xml"
+empty_parameter_set_mark = "fail_at_collect"
+xfail_strict = true
+testpaths = "pandas"
+doctest_optionflags = [
+ "NORMALIZE_WHITESPACE",
+ "IGNORE_EXCEPTION_DETAIL",
+ "ELLIPSIS",
+]
+filterwarnings = [
+ "error:::pandas",
+ "error::ResourceWarning",
+ "error::pytest.PytestUnraisableExceptionWarning",
+ "error::pytest.PytestWarning",
+ # e.g. Module already imported so cannot be rewritten; _hypothesis_globals
+ "ignore::pytest.PytestAssertRewriteWarning",
+ "ignore::pytest.PytestCacheWarning",
+ # TODO(PY311-minimum): Specify EncodingWarning
+ # Ignore 3rd party EncodingWarning but raise on pandas'
+ "ignore:.*encoding.* argument not specified",
+ "error:.*encoding.* argument not specified::pandas",
+ "ignore:.*ssl.SSLSocket:pytest.PytestUnraisableExceptionWarning",
+ "ignore:.*ssl.SSLSocket:ResourceWarning",
+ # GH 44844: Can remove once minimum matplotlib version >= 3.7
+ "ignore:.*FileIO:pytest.PytestUnraisableExceptionWarning",
+ "ignore:.*BufferedRandom:ResourceWarning",
+ "ignore::ResourceWarning:asyncio",
+ # From plotting doctests
+ "ignore:More than 20 figures have been opened:RuntimeWarning",
+ "ignore:.*urllib3:DeprecationWarning:botocore",
+ "ignore:Setuptools is replacing distutils.:UserWarning:_distutils_hack",
+ # https://github.com/PyTables/PyTables/issues/822
+ "ignore:a closed node found in the registry:UserWarning:tables",
+]
+junit_family = "xunit2"
+markers = [
+ "single_cpu: tests that should run on a single cpu only",
+ "slow: mark a test as slow",
+ "network: mark a test as network",
+ "db: tests requiring a database (mysql or postgres)",
+ "clipboard: mark a pd.read_clipboard test",
+ "arm_slow: mark a test as slow for arm64 architecture",
+ # TODO: someone should investigate this ...
+ # these tests only fail in the wheel builder and don't fail in regular
+ # ARM CI
+ "fails_arm_wheels: Tests that fail in the ARM wheel build only",
+]
+
+[tool.mypy]
+# Import discovery
+mypy_path = "typings"
+files = ["pandas", "typings"]
+namespace_packages = false
+explicit_package_bases = false
+ignore_missing_imports = true
+follow_imports = "normal"
+follow_imports_for_stubs = false
+no_site_packages = false
+no_silence_site_packages = false
+# Platform configuration
+python_version = "3.11"
+platform = "linux-64"
+# Disallow dynamic typing
+disallow_any_unimported = false # TODO
+disallow_any_expr = false # TODO
+disallow_any_decorated = false # TODO
+disallow_any_explicit = false # TODO
+disallow_any_generics = false # TODO
+disallow_subclassing_any = false # TODO
+# Untyped definitions and calls
+disallow_untyped_calls = true
+disallow_untyped_defs = true
+disallow_incomplete_defs = true
+check_untyped_defs = true
+disallow_untyped_decorators = true
+# None and Optional handling
+no_implicit_optional = true
+strict_optional = true
+# Configuring warnings
+warn_redundant_casts = true
+warn_unused_ignores = true
+warn_no_return = true
+warn_return_any = false # TODO
+warn_unreachable = false # GH#27396
+# Suppressing errors
+ignore_errors = false
+enable_error_code = "ignore-without-code"
+# Miscellaneous strictness flags
+allow_untyped_globals = false
+allow_redefinition = false
+local_partial_types = true
+implicit_reexport = true
+strict_equality = true
+# Configuring error messages
+show_error_context = false
+show_column_numbers = false
+show_error_codes = true
+
+[[tool.mypy.overrides]]
+module = [
+ "pandas._config.config", # TODO
+ "pandas._libs.*",
+ "pandas._testing.*", # TODO
+ "pandas.compat.numpy.function", # TODO
+ "pandas.core._numba.executor", # TODO
+ "pandas.core.array_algos.masked_reductions", # TODO
+ "pandas.core.array_algos.putmask", # TODO
+ "pandas.core.array_algos.quantile", # TODO
+ "pandas.core.array_algos.replace", # TODO
+ "pandas.core.array_algos.take", # TODO
+ "pandas.core.arrays.*", # TODO
+ "pandas.core.computation.*", # TODO
+ "pandas.core.dtypes.astype", # TODO
+ "pandas.core.dtypes.cast", # TODO
+ "pandas.core.dtypes.common", # TODO
+ "pandas.core.dtypes.concat", # TODO
+ "pandas.core.dtypes.dtypes", # TODO
+ "pandas.core.dtypes.generic", # TODO
+ "pandas.core.dtypes.missing", # TODO
+ "pandas.core.groupby.generic", # TODO
+ "pandas.core.groupby.grouper", # TODO
+ "pandas.core.groupby.groupby", # TODO
+ "pandas.core.groupby.ops", # TODO
+ "pandas.core.indexers.*", # TODO
+ "pandas.core.indexes.*", # TODO
+ "pandas.core.interchange.column", # TODO
+ "pandas.core.interchange.dataframe_protocol", # TODO
+ "pandas.core.interchange.from_dataframe", # TODO
+ "pandas.core.internals.*", # TODO
+ "pandas.core.ops.array_ops", # TODO
+ "pandas.core.ops.common", # TODO
+ "pandas.core.ops.missing", # TODO
+ "pandas.core.reshape.*", # TODO
+ "pandas.core.strings.*", # TODO
+ "pandas.core.tools.*", # TODO
+ "pandas.core.window.common", # TODO
+ "pandas.core.window.ewm", # TODO
+ "pandas.core.window.expanding", # TODO
+ "pandas.core.window.numba_", # TODO
+ "pandas.core.window.online", # TODO
+ "pandas.core.window.rolling", # TODO
+ "pandas.core.accessor", # TODO
+ "pandas.core.algorithms", # TODO
+ "pandas.core.apply", # TODO
+ "pandas.core.arraylike", # TODO
+ "pandas.core.base", # TODO
+ "pandas.core.common", # TODO
+ "pandas.core.construction", # TODO
+ "pandas.core.flags", # TODO
+ "pandas.core.frame", # TODO
+ "pandas.core.generic", # TODO
+ "pandas.core.indexing", # TODO
+ "pandas.core.missing", # TODO
+ "pandas.core.nanops", # TODO
+ "pandas.core.resample", # TODO
+ "pandas.core.roperator", # TODO
+ "pandas.core.sample", # TODO
+ "pandas.core.series", # TODO
+ "pandas.core.sorting", # TODO
+ "pandas.errors", # TODO
+ "pandas.io.clipboard", # TODO
+ "pandas.io.excel._base", # TODO
+ "pandas.io.excel._odfreader", # TODO
+ "pandas.io.excel._openpyxl", # TODO
+ "pandas.io.excel._pyxlsb", # TODO
+ "pandas.io.excel._xlrd", # TODO
+ "pandas.io.excel._xlsxwriter", # TODO
+ "pandas.io.formats.excel", # TODO
+ "pandas.io.formats.format", # TODO
+ "pandas.io.formats.style", # TODO
+ "pandas.io.formats.style_render", # TODO
+ "pandas.io.formats.xml", # TODO
+ "pandas.io.json.*", # TODO
+ "pandas.io.parsers.*", # TODO
+ "pandas.io.sas.sas_xport", # TODO
+ "pandas.io.sas.sas7bdat", # TODO
+ "pandas.io.clipboards", # TODO
+ "pandas.io.html", # TODO
+ "pandas.io.parquet", # TODO
+ "pandas.io.pytables", # TODO
+ "pandas.io.sql", # TODO
+ "pandas.io.xml", # TODO
+ "pandas.plotting.*", # TODO
+ "pandas.tests.*",
+ "pandas.tseries.frequencies", # TODO
+ "pandas.tseries.holiday", # TODO
+ "pandas.util._decorators", # TODO
+ "pandas.util._doctools", # TODO
+ "pandas.util._test_decorators", # TODO
+ "pandas.util._validators", # TODO
+ "pandas.util", # TODO
+ "pandas._version",
+ "pandas.conftest",
+ "pandas"
+]
+disallow_untyped_calls = false
+disallow_untyped_defs = false
+disallow_incomplete_defs = false
+
+[[tool.mypy.overrides]]
+module = [
+ "pandas.tests.*",
+ "pandas._version",
+ "pandas.io.clipboard",
+]
+check_untyped_defs = false
+
+[[tool.mypy.overrides]]
+module = [
+ "pandas.tests.apply.test_series_apply",
+ "pandas.tests.arithmetic.conftest",
+ "pandas.tests.arrays.sparse.test_combine_concat",
+ "pandas.tests.dtypes.test_common",
+ "pandas.tests.frame.methods.test_to_records",
+ "pandas.tests.groupby.test_rank",
+ "pandas.tests.groupby.transform.test_transform",
+ "pandas.tests.indexes.interval.test_interval",
+ "pandas.tests.indexing.test_categorical",
+ "pandas.tests.io.excel.test_writers",
+ "pandas.tests.reductions.test_reductions",
+ "pandas.tests.test_expressions",
+]
+ignore_errors = true
+
+# To be kept consistent with "Import Formatting" section in contributing.rst
+[tool.isort]
+known_pre_libs = "pandas._config"
+known_pre_core = ["pandas._libs", "pandas._typing", "pandas.util._*", "pandas.compat", "pandas.errors"]
+known_dtypes = "pandas.core.dtypes"
+known_post_core = ["pandas.tseries", "pandas.io", "pandas.plotting"]
+sections = ["FUTURE", "STDLIB", "THIRDPARTY" ,"PRE_LIBS" , "PRE_CORE", "DTYPES", "FIRSTPARTY", "POST_CORE", "LOCALFOLDER"]
+profile = "black"
+combine_as_imports = true
+force_grid_wrap = 2
+force_sort_within_sections = true
+skip_glob = "env"
+skip = "pandas/__init__.py"
+
+[tool.pyright]
+pythonVersion = "3.11"
+typeCheckingMode = "basic"
+useLibraryCodeForTypes = false
+include = ["pandas", "typings"]
+exclude = ["pandas/tests", "pandas/io/clipboard", "pandas/util/version", "pandas/core/_numba/extensions.py"]
+# enable subset of "strict"
+reportDuplicateImport = true
+reportInconsistentConstructor = true
+reportInvalidStubStatement = true
+reportOverlappingOverload = true
+reportPropertyTypeMismatch = true
+reportUntypedClassDecorator = true
+reportUntypedFunctionDecorator = true
+reportUntypedNamedTuple = true
+reportUnusedImport = true
+disableBytesTypePromotions = true
+# disable subset of "basic"
+reportArgumentType = false
+reportAssignmentType = false
+reportAttributeAccessIssue = false
+reportCallIssue = false
+reportGeneralTypeIssues = false
+reportIndexIssue = false
+reportMissingModuleSource = false
+reportOperatorIssue = false
+reportOptionalCall = false
+reportOptionalIterable = false
+reportOptionalMemberAccess = false
+reportOptionalOperand = false
+reportOptionalSubscript = false
+reportPrivateImportUsage = false
+reportRedeclaration = false
+reportReturnType = false
+reportUnboundVariable = false
+
+[tool.coverage.run]
+branch = true
+omit = ["pandas/_typing.py", "pandas/_version.py"]
+plugins = ["Cython.Coverage"]
+source = ["pandas"]
+
+[tool.coverage.report]
+ignore_errors = false
+show_missing = true
+omit = ["pandas/_version.py"]
+exclude_lines = [
+ # Have to re-enable the standard pragma
+ "pragma: no cover",
+ # Don't complain about missing debug-only code:s
+ "def __repr__",
+ "if self.debug",
+ # Don't complain if tests don't hit defensive assertion code:
+ "raise AssertionError",
+ "raise NotImplementedError",
+ "AbstractMethodError",
+ # Don't complain if non-runnable code isn't run:
+ "if 0:",
+ "if __name__ == .__main__.:",
+ "if TYPE_CHECKING:",
+]
+
+[tool.coverage.html]
+directory = "coverage_html_report"
+
+[tool.codespell]
+ignore-words-list = "blocs, coo, hist, nd, sav, ser, recuse, nin, timere, expec, expecs, indext, SME, NotIn, tructures, tru, indx, abd, ABD"
+ignore-regex = 'https://([\w/\.])+'
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/testing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/testing.py
new file mode 100644
index 0000000000000000000000000000000000000000..433b22bf1107e1d199c3070fa5c7aef45df6758c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/testing.py
@@ -0,0 +1,17 @@
+"""
+Public testing utility functions.
+"""
+
+from pandas._testing import (
+ assert_extension_array_equal,
+ assert_frame_equal,
+ assert_index_equal,
+ assert_series_equal,
+)
+
+__all__ = [
+ "assert_extension_array_equal",
+ "assert_frame_equal",
+ "assert_index_equal",
+ "assert_series_equal",
+]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/api/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/api/test_api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/api/test_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..4eefb1163352c16e4d4a263a8af1a457ad510599
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/api/test_api.py
@@ -0,0 +1,577 @@
+from __future__ import annotations
+
+import importlib
+import inspect
+import pathlib
+import pkgutil
+
+import pytest
+
+import pandas as pd
+from pandas import api
+import pandas._testing as tm
+from pandas.api import (
+ executors as api_executors,
+ extensions as api_extensions,
+ indexers as api_indexers,
+ interchange as api_interchange,
+ types as api_types,
+ typing as api_typing,
+)
+from pandas.api.typing import aliases as api_aliases
+
+
+class Base:
+ def check(self, namespace, expected, ignored=None):
+ # see which names are in the namespace, minus optional
+ # ignored ones
+ # compare vs the expected
+
+ result = sorted(
+ f for f in dir(namespace) if not f.startswith("__") and f != "annotations"
+ )
+ if ignored is not None:
+ result = sorted(set(result) - set(ignored))
+
+ expected = sorted(expected)
+ tm.assert_almost_equal(result, expected)
+
+
+class TestPDApi(Base):
+ # these are optionally imported based on testing
+ # & need to be ignored
+ ignored = ["tests", "locale", "conftest", "_version_meson"]
+
+ # top-level sub-packages
+ public_lib = [
+ "api",
+ "arrays",
+ "options",
+ "test",
+ "testing",
+ "errors",
+ "plotting",
+ "io",
+ "tseries",
+ ]
+ private_lib = ["compat", "core", "pandas", "util", "_built_with_meson"]
+
+ # misc
+ misc = ["IndexSlice", "NaT", "NA"]
+
+ # top-level classes
+ classes = [
+ "ArrowDtype",
+ "Categorical",
+ "CategoricalIndex",
+ "DataFrame",
+ "DateOffset",
+ "DatetimeIndex",
+ "ExcelFile",
+ "ExcelWriter",
+ "Flags",
+ "Grouper",
+ "HDFStore",
+ "Index",
+ "MultiIndex",
+ "Period",
+ "PeriodIndex",
+ "RangeIndex",
+ "Series",
+ "SparseDtype",
+ "StringDtype",
+ "Timedelta",
+ "TimedeltaIndex",
+ "Timestamp",
+ "Interval",
+ "IntervalIndex",
+ "CategoricalDtype",
+ "PeriodDtype",
+ "IntervalDtype",
+ "DatetimeTZDtype",
+ "BooleanDtype",
+ "Int8Dtype",
+ "Int16Dtype",
+ "Int32Dtype",
+ "Int64Dtype",
+ "UInt8Dtype",
+ "UInt16Dtype",
+ "UInt32Dtype",
+ "UInt64Dtype",
+ "Float32Dtype",
+ "Float64Dtype",
+ "NamedAgg",
+ ]
+
+ # these are already deprecated; awaiting removal
+ deprecated_classes: list[str] = []
+
+ # external modules exposed in pandas namespace
+ modules: list[str] = []
+
+ # top-level functions
+ funcs = [
+ "array",
+ "bdate_range",
+ "col",
+ "concat",
+ "crosstab",
+ "cut",
+ "date_range",
+ "interval_range",
+ "eval",
+ "factorize",
+ "get_dummies",
+ "from_dummies",
+ "infer_freq",
+ "isna",
+ "isnull",
+ "lreshape",
+ "melt",
+ "notna",
+ "notnull",
+ "offsets",
+ "merge",
+ "merge_ordered",
+ "merge_asof",
+ "period_range",
+ "pivot",
+ "pivot_table",
+ "qcut",
+ "show_versions",
+ "timedelta_range",
+ "unique",
+ "wide_to_long",
+ ]
+
+ # top-level option funcs
+ funcs_option = [
+ "reset_option",
+ "describe_option",
+ "get_option",
+ "option_context",
+ "set_option",
+ "set_eng_float_format",
+ ]
+
+ # top-level read_* funcs
+ funcs_read = [
+ "read_clipboard",
+ "read_csv",
+ "read_excel",
+ "read_fwf",
+ "read_hdf",
+ "read_html",
+ "read_xml",
+ "read_json",
+ "read_pickle",
+ "read_sas",
+ "read_sql",
+ "read_sql_query",
+ "read_sql_table",
+ "read_stata",
+ "read_table",
+ "read_feather",
+ "read_parquet",
+ "read_orc",
+ "read_spss",
+ "read_iceberg",
+ ]
+
+ # top-level json funcs
+ funcs_json = ["json_normalize"]
+
+ # top-level to_* funcs
+ funcs_to = ["to_datetime", "to_numeric", "to_pickle", "to_timedelta"]
+
+ # top-level to deprecate in the future
+ deprecated_funcs_in_future: list[str] = []
+
+ # these are already deprecated; awaiting removal
+ deprecated_funcs: list[str] = []
+
+ # private modules in pandas namespace
+ private_modules = [
+ "_config",
+ "_libs",
+ "_is_numpy_dev",
+ "_pandas_datetime_CAPI",
+ "_pandas_parser_CAPI",
+ "_testing",
+ "_typing",
+ ]
+ if not pd._built_with_meson:
+ private_modules.append("_version")
+
+ def test_api(self):
+ checkthese = (
+ self.public_lib
+ + self.private_lib
+ + self.misc
+ + self.modules
+ + self.classes
+ + self.funcs
+ + self.funcs_option
+ + self.funcs_read
+ + self.funcs_json
+ + self.funcs_to
+ + self.private_modules
+ )
+ self.check(namespace=pd, expected=checkthese, ignored=self.ignored)
+
+ def test_api_all(self):
+ expected = set(
+ self.public_lib
+ + self.misc
+ + self.modules
+ + self.classes
+ + self.funcs
+ + self.funcs_option
+ + self.funcs_read
+ + self.funcs_json
+ + self.funcs_to
+ ) - set(self.deprecated_classes)
+ actual = set(pd.__all__)
+
+ extraneous = actual - expected
+ assert not extraneous
+
+ missing = expected - actual
+ assert not missing
+
+ def test_depr(self):
+ deprecated_list = (
+ self.deprecated_classes
+ + self.deprecated_funcs
+ + self.deprecated_funcs_in_future
+ )
+ for depr in deprecated_list:
+ with tm.assert_produces_warning(FutureWarning):
+ _ = getattr(pd, depr)
+
+
+class TestApi(Base):
+ allowed_api_dirs = [
+ "executors",
+ "types",
+ "extensions",
+ "indexers",
+ "interchange",
+ "typing",
+ "internals",
+ ]
+ allowed_typing = [
+ "DataFrameGroupBy",
+ "DatetimeIndexResamplerGroupby",
+ "Expanding",
+ "ExpandingGroupby",
+ "ExponentialMovingWindow",
+ "ExponentialMovingWindowGroupby",
+ "Expression",
+ "FrozenList",
+ "JsonReader",
+ "NaTType",
+ "NAType",
+ "NoDefault",
+ "PeriodIndexResamplerGroupby",
+ "Resampler",
+ "Rolling",
+ "RollingGroupby",
+ "SeriesGroupBy",
+ "StataReader",
+ "SASReader",
+ "TimedeltaIndexResamplerGroupby",
+ "TimeGrouper",
+ "Window",
+ "aliases",
+ ]
+ allowed_api_types = [
+ "is_any_real_numeric_dtype",
+ "is_array_like",
+ "is_bool",
+ "is_bool_dtype",
+ "is_categorical_dtype",
+ "is_complex",
+ "is_complex_dtype",
+ "is_datetime64_any_dtype",
+ "is_datetime64_dtype",
+ "is_datetime64_ns_dtype",
+ "is_datetime64tz_dtype",
+ "is_dict_like",
+ "is_dtype_equal",
+ "is_extension_array_dtype",
+ "is_file_like",
+ "is_float",
+ "is_float_dtype",
+ "is_hashable",
+ "is_int64_dtype",
+ "is_integer",
+ "is_integer_dtype",
+ "is_interval_dtype",
+ "is_iterator",
+ "is_list_like",
+ "is_named_tuple",
+ "is_number",
+ "is_numeric_dtype",
+ "is_object_dtype",
+ "is_period_dtype",
+ "is_re",
+ "is_re_compilable",
+ "is_scalar",
+ "is_signed_integer_dtype",
+ "is_sparse",
+ "is_string_dtype",
+ "is_timedelta64_dtype",
+ "is_timedelta64_ns_dtype",
+ "is_unsigned_integer_dtype",
+ "pandas_dtype",
+ "infer_dtype",
+ "union_categoricals",
+ "CategoricalDtype",
+ "DatetimeTZDtype",
+ "IntervalDtype",
+ "PeriodDtype",
+ ]
+ allowed_api_interchange = ["from_dataframe", "DataFrame"]
+ allowed_api_indexers = [
+ "check_array_indexer",
+ "BaseIndexer",
+ "FixedForwardWindowIndexer",
+ "VariableOffsetWindowIndexer",
+ ]
+ allowed_api_extensions = [
+ "no_default",
+ "ExtensionDtype",
+ "register_extension_dtype",
+ "register_dataframe_accessor",
+ "register_index_accessor",
+ "register_series_accessor",
+ "take",
+ "ExtensionArray",
+ "ExtensionScalarOpsMixin",
+ ]
+ allowed_api_executors = ["BaseExecutionEngine"]
+ allowed_api_aliases = [
+ "AggFuncType",
+ "AlignJoin",
+ "AnyAll",
+ "AnyArrayLike",
+ "ArrayLike",
+ "AstypeArg",
+ "Axes",
+ "Axis",
+ "CSVEngine",
+ "ColspaceArgType",
+ "CompressionOptions",
+ "CorrelationMethod",
+ "DropKeep",
+ "Dtype",
+ "DtypeArg",
+ "DtypeBackend",
+ "DtypeObj",
+ "ExcelWriterIfSheetExists",
+ "ExcelWriterMergeCells",
+ "FilePath",
+ "FillnaOptions",
+ "FloatFormatType",
+ "FormattersType",
+ "FromDictOrient",
+ "HTMLFlavors",
+ "IgnoreRaise",
+ "IndexLabel",
+ "InterpolateOptions",
+ "IntervalClosedType",
+ "IntervalLeftRight",
+ "JSONEngine",
+ "JSONSerializable",
+ "JoinHow",
+ "JoinValidate",
+ "ListLike",
+ "MergeHow",
+ "MergeValidate",
+ "NaPosition",
+ "NsmallestNlargestKeep",
+ "OpenFileErrors",
+ "Ordered",
+ "ParquetCompressionOptions",
+ "QuantileInterpolation",
+ "ReadBuffer",
+ "ReadCsvBuffer",
+ "ReadPickleBuffer",
+ "ReindexMethod",
+ "Scalar",
+ "ScalarIndexer",
+ "SequenceIndexer",
+ "SequenceNotStr",
+ "SliceType",
+ "SortKind",
+ "StorageOptions",
+ "Suffixes",
+ "TakeIndexer",
+ "TimeAmbiguous",
+ "TimeGrouperOrigin",
+ "TimeNonexistent",
+ "TimeUnit",
+ "TimedeltaConvertibleTypes",
+ "TimestampConvertibleTypes",
+ "ToStataByteorder",
+ "ToTimestampHow",
+ "UpdateJoin",
+ "UsecolsArgType",
+ "WindowingRankType",
+ "WriteBuffer",
+ "WriteExcelBuffer",
+ "XMLParsers",
+ ]
+
+ def test_api(self):
+ self.check(api, self.allowed_api_dirs)
+
+ def test_api_typing(self):
+ self.check(api_typing, self.allowed_typing)
+
+ def test_api_types(self):
+ self.check(api_types, self.allowed_api_types)
+
+ def test_api_interchange(self):
+ self.check(api_interchange, self.allowed_api_interchange)
+
+ def test_api_indexers(self):
+ self.check(api_indexers, self.allowed_api_indexers)
+
+ def test_api_extensions(self):
+ self.check(api_extensions, self.allowed_api_extensions)
+
+ def test_api_executors(self):
+ self.check(api_executors, self.allowed_api_executors)
+
+ def test_api_typing_aliases(self):
+ self.check(api_aliases, self.allowed_api_aliases)
+
+
+class TestErrors(Base):
+ def test_errors(self):
+ ignored = ["_CurrentDeprecationWarning", "abc", "ctypes", "cow"]
+ self.check(pd.errors, pd.errors.__all__, ignored=ignored)
+
+
+class TestUtil(Base):
+ def test_util(self):
+ self.check(
+ pd.util,
+ ["hash_array", "hash_pandas_object"],
+ ignored=[
+ "_decorators",
+ "_test_decorators",
+ "_exceptions",
+ "_validators",
+ "capitalize_first_letter",
+ "version",
+ "_print_versions",
+ "_tester",
+ ],
+ )
+
+
+class TestTesting(Base):
+ funcs = [
+ "assert_frame_equal",
+ "assert_series_equal",
+ "assert_index_equal",
+ "assert_extension_array_equal",
+ ]
+
+ def test_testing(self):
+ from pandas import testing
+
+ self.check(testing, self.funcs)
+
+ def test_util_in_top_level(self):
+ with pytest.raises(AttributeError, match="foo"):
+ pd.util.foo
+
+
+def get_pandas_objects(
+ module_name: str, recurse: bool
+) -> list[tuple[str, str, object]]:
+ """
+ Get all pandas objects within a module.
+
+ An object is determined to be part of pandas if it has a string
+ __module__ attribute that starts with ``"pandas"``.
+
+ Parameters
+ ----------
+ module_name : str
+ Name of the module to search.
+ recurse : bool
+ Whether to search submodules.
+
+ Returns
+ -------
+ List of all objects that are determined to be a part of pandas.
+ """
+ module = importlib.import_module(module_name)
+ objs = []
+
+ for name, obj in inspect.getmembers(module):
+ module_dunder = getattr(obj, "__module__", None)
+ if isinstance(module_dunder, str) and module_dunder.startswith("pandas"):
+ objs.append((module_name, name, obj))
+
+ if not recurse:
+ return objs
+
+ # __file__ can, but shouldn't, be None
+ assert isinstance(module.__file__, str)
+ paths = [pathlib.Path(module.__file__).parent]
+ for module_info in pkgutil.walk_packages(paths):
+ name = module_info.name
+ if name.startswith("_") or name == "internals":
+ continue
+ objs.extend(
+ get_pandas_objects(f"{module.__name__}.{name}", recurse=module_info.ispkg)
+ )
+ return objs
+
+
+@pytest.mark.slow
+@pytest.mark.parametrize(
+ "module_name",
+ [
+ "pandas",
+ "pandas.api",
+ "pandas.arrays",
+ "pandas.errors",
+ pytest.param("pandas.io", marks=pytest.mark.xfail(reason="Private imports")),
+ "pandas.plotting",
+ "pandas.testing",
+ ],
+)
+def test_attributes_module(module_name):
+ """
+ Ensures that all public objects have their __module__ set to the public import path.
+ """
+ recurse = module_name not in ["pandas", "pandas.testing"]
+ objs = get_pandas_objects(module_name, recurse=recurse)
+ failures = [
+ (module_name, name, type(obj), obj.__module__)
+ for module_name, name, obj in objs
+ if not (
+ obj.__module__ == module_name
+ # Explicit exceptions
+ or ("Dtype" in name and obj.__module__ == "pandas")
+ or (name == "Categorical" and obj.__module__ == "pandas")
+ )
+ ]
+ assert len(failures) == 0, "\n".join(str(e) for e in failures)
+
+ # Check that all objects can indeed be imported from their __module__
+ failures = []
+ for module_name, name, obj in objs:
+ module = importlib.import_module(obj.__module__)
+ try:
+ getattr(module, name)
+ except Exception:
+ failures.append((module_name, name, type(obj), obj.__module__))
+ assert len(failures) == 0, "\n".join(str(e) for e in failures)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/api/test_types.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/api/test_types.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf39370c49d76762760a98e820ad2985a8a81222
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/api/test_types.py
@@ -0,0 +1,61 @@
+from __future__ import annotations
+
+import pandas._testing as tm
+from pandas.api import types
+from pandas.tests.api.test_api import Base
+
+
+class TestTypes(Base):
+ allowed = [
+ "is_any_real_numeric_dtype",
+ "is_bool",
+ "is_bool_dtype",
+ "is_categorical_dtype",
+ "is_complex",
+ "is_complex_dtype",
+ "is_datetime64_any_dtype",
+ "is_datetime64_dtype",
+ "is_datetime64_ns_dtype",
+ "is_datetime64tz_dtype",
+ "is_dtype_equal",
+ "is_float",
+ "is_float_dtype",
+ "is_int64_dtype",
+ "is_integer",
+ "is_integer_dtype",
+ "is_number",
+ "is_numeric_dtype",
+ "is_object_dtype",
+ "is_scalar",
+ "is_sparse",
+ "is_string_dtype",
+ "is_signed_integer_dtype",
+ "is_timedelta64_dtype",
+ "is_timedelta64_ns_dtype",
+ "is_unsigned_integer_dtype",
+ "is_period_dtype",
+ "is_interval_dtype",
+ "is_re",
+ "is_re_compilable",
+ "is_dict_like",
+ "is_iterator",
+ "is_file_like",
+ "is_list_like",
+ "is_hashable",
+ "is_array_like",
+ "is_named_tuple",
+ "pandas_dtype",
+ "union_categoricals",
+ "infer_dtype",
+ "is_extension_array_dtype",
+ ]
+ deprecated: list[str] = []
+ dtypes = ["CategoricalDtype", "DatetimeTZDtype", "PeriodDtype", "IntervalDtype"]
+
+ def test_types(self):
+ self.check(types, self.allowed + self.dtypes + self.deprecated)
+
+ def test_deprecated_from_api_types(self):
+ for t in self.deprecated:
+ with tm.assert_produces_warning(FutureWarning):
+ getattr(types, t)(1)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4d153df54059ca2a82f336e19afb4297eb218a2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/common.py
@@ -0,0 +1,7 @@
+from pandas.core.groupby.base import transformation_kernels
+
+# There is no Series.cumcount or DataFrame.cumcount
+series_transform_kernels = [
+ x for x in sorted(transformation_kernels) if x != "cumcount"
+]
+frame_transform_kernels = [x for x in sorted(transformation_kernels) if x != "cumcount"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..aecf82f5a941948da66c9dda09ec9a826a2706ca
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/conftest.py
@@ -0,0 +1,63 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+)
+from pandas.api.executors import BaseExecutionEngine
+
+
+class MockExecutionEngine(BaseExecutionEngine):
+ """
+ Execution Engine to test if the execution engine interface receives and
+ uses all parameters provided by the user.
+
+ Making this engine work as the default Python engine by calling it, no extra
+ functionality is implemented here.
+
+ When testing, this will be called when this engine is provided, and then the
+ same pandas.map and pandas.apply function will be called, but without engine,
+ executing the default behavior from the python engine.
+ """
+
+ def map(data, func, args, kwargs, decorator, skip_na):
+ kwargs_to_pass = kwargs if isinstance(data, DataFrame) else {}
+ return data.map(func, na_action="ignore" if skip_na else None, **kwargs_to_pass)
+
+ def apply(data, func, args, kwargs, decorator, axis):
+ if isinstance(data, Series):
+ return data.apply(func, convert_dtype=True, args=args, by_row=False)
+ elif isinstance(data, DataFrame):
+ return data.apply(
+ func,
+ axis=axis,
+ raw=False,
+ result_type=None,
+ args=args,
+ by_row="compat",
+ **kwargs,
+ )
+ else:
+ assert isinstance(data, np.ndarray)
+
+ def wrap_function(func):
+ # https://github.com/numpy/numpy/issues/8352
+ def wrapper(*args, **kwargs):
+ result = func(*args, **kwargs)
+ if isinstance(result, str):
+ result = np.array(result, dtype=object)
+ return result
+
+ return wrapper
+
+ return np.apply_along_axis(wrap_function(func), axis, data, *args, **kwargs)
+
+
+class MockEngineDecorator:
+ __pandas_udf__ = MockExecutionEngine
+
+
+@pytest.fixture(params=[None, MockEngineDecorator])
+def engine(request):
+ return request.param
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_frame_apply.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_frame_apply.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c16425ac2ac73f2ea96173fe6ec97c4b8ef0cb9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_frame_apply.py
@@ -0,0 +1,1875 @@
+from datetime import datetime
+import warnings
+
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_arm
+
+from pandas.core.dtypes.dtypes import CategoricalDtype
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.tests.apply.conftest import MockEngineDecorator
+from pandas.tests.frame.common import zip_frames
+from pandas.util.version import Version
+
+
+@pytest.fixture
+def int_frame_const_col():
+ """
+ Fixture for DataFrame of ints which are constant per column
+
+ Columns are ['A', 'B', 'C'], with values (per column): [1, 2, 3]
+ """
+ df = DataFrame(
+ np.tile(np.arange(3, dtype="int64"), 6).reshape(6, -1) + 1,
+ columns=["A", "B", "C"],
+ )
+ return df
+
+
+@pytest.fixture(
+ params=[
+ "python",
+ pytest.param("numba", marks=pytest.mark.single_cpu),
+ MockEngineDecorator,
+ ]
+)
+def engine(request):
+ if request.param == "numba":
+ pytest.importorskip("numba")
+ return request.param
+
+
+def test_apply(float_frame, engine, request):
+ if engine == "numba":
+ mark = pytest.mark.xfail(reason="numba engine not supporting numpy ufunc yet")
+ request.node.add_marker(mark)
+ with np.errstate(all="ignore"):
+ # ufunc
+ result = np.sqrt(float_frame["A"])
+ expected = float_frame.apply(np.sqrt, engine=engine)["A"]
+ tm.assert_series_equal(result, expected)
+
+ # aggregator
+ result = float_frame.apply(np.mean, engine=engine)["A"]
+ expected = np.mean(float_frame["A"])
+ assert result == expected
+
+ d = float_frame.index[0]
+ result = float_frame.apply(np.mean, axis=1, engine=engine)
+ expected = np.mean(float_frame.xs(d))
+ assert result[d] == expected
+ assert result.index is float_frame.index
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+@pytest.mark.parametrize("raw", [True, False])
+@pytest.mark.parametrize("nopython", [True, False])
+def test_apply_args(float_frame, axis, raw, engine, nopython):
+ numba = pytest.importorskip("numba")
+ if (
+ engine == "numba"
+ and Version(numba.__version__) == Version("0.61")
+ and is_platform_arm()
+ ):
+ pytest.skip(f"Segfaults on ARM platforms with numba {numba.__version__}")
+ engine_kwargs = {"nopython": nopython}
+ result = float_frame.apply(
+ lambda x, y: x + y,
+ axis,
+ args=(1,),
+ raw=raw,
+ engine=engine,
+ engine_kwargs=engine_kwargs,
+ )
+ expected = float_frame + 1
+ tm.assert_frame_equal(result, expected)
+
+ # GH:58712
+ result = float_frame.apply(
+ lambda x, a, b: x + a + b,
+ args=(1,),
+ b=2,
+ raw=raw,
+ engine=engine,
+ engine_kwargs=engine_kwargs,
+ )
+ expected = float_frame + 3
+ tm.assert_frame_equal(result, expected)
+
+ if engine == "numba":
+ # py signature binding
+ with pytest.raises(TypeError, match="missing a required argument: 'a'"):
+ float_frame.apply(
+ lambda x, a: x + a,
+ b=2,
+ raw=raw,
+ engine=engine,
+ engine_kwargs=engine_kwargs,
+ )
+
+ # keyword-only arguments are not supported in numba
+ with pytest.raises(
+ pd.errors.NumbaUtilError,
+ match="numba does not support keyword-only arguments",
+ ):
+ float_frame.apply(
+ lambda x, a, *, b: x + a + b,
+ args=(1,),
+ b=2,
+ raw=raw,
+ engine=engine,
+ engine_kwargs=engine_kwargs,
+ )
+
+ with pytest.raises(
+ pd.errors.NumbaUtilError,
+ match="numba does not support keyword-only arguments",
+ ):
+ float_frame.apply(
+ lambda *x, b: x[0] + x[1] + b,
+ args=(1,),
+ b=2,
+ raw=raw,
+ engine=engine,
+ engine_kwargs=engine_kwargs,
+ )
+
+
+def test_apply_categorical_func():
+ # GH 9573
+ df = DataFrame({"c0": ["A", "A", "B", "B"], "c1": ["C", "C", "D", "D"]})
+ result = df.apply(lambda ts: ts.astype("category"))
+
+ assert result.shape == (4, 2)
+ assert isinstance(result["c0"].dtype, CategoricalDtype)
+ assert isinstance(result["c1"].dtype, CategoricalDtype)
+
+
+def test_apply_axis1_with_ea():
+ # GH#36785
+ expected = DataFrame({"A": [Timestamp("2013-01-01", tz="UTC")]})
+ result = expected.apply(lambda x: x, axis=1)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data, dtype",
+ [(1, None), (1, CategoricalDtype([1])), (Timestamp("2013-01-01", tz="UTC"), None)],
+)
+def test_agg_axis1_duplicate_index(data, dtype):
+ # GH 42380
+ expected = DataFrame([[data], [data]], index=["a", "a"], dtype=dtype)
+ result = expected.agg(lambda x: x, axis=1)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_mixed_datetimelike():
+ # mixed datetimelike
+ # GH 7778
+ expected = DataFrame(
+ {
+ "A": date_range("20130101", periods=3),
+ "B": pd.to_timedelta(np.arange(3), unit="s"),
+ }
+ )
+ result = expected.apply(lambda x: x, axis=1)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", [np.sqrt, np.mean])
+def test_apply_empty(func, engine):
+ # empty
+ empty_frame = DataFrame()
+
+ result = empty_frame.apply(func, engine=engine)
+ assert result.empty
+
+
+def test_apply_float_frame(float_frame, engine):
+ no_rows = float_frame[:0]
+ result = no_rows.apply(lambda x: x.mean(), engine=engine)
+ expected = Series(np.nan, index=float_frame.columns)
+ tm.assert_series_equal(result, expected)
+
+ no_cols = float_frame.loc[:, []]
+ result = no_cols.apply(lambda x: x.mean(), axis=1, engine=engine)
+ expected = Series(np.nan, index=float_frame.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_empty_except_index(engine):
+ # GH 2476
+ expected = DataFrame(index=["a"])
+ result = expected.apply(lambda x: x["a"], axis=1, engine=engine)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_with_reduce_empty():
+ # reduce with an empty DataFrame
+ empty_frame = DataFrame()
+
+ x = []
+ result = empty_frame.apply(x.append, axis=1, result_type="expand")
+ tm.assert_frame_equal(result, empty_frame)
+ result = empty_frame.apply(x.append, axis=1, result_type="reduce")
+ expected = Series([], dtype=np.float64)
+ tm.assert_series_equal(result, expected)
+
+ empty_with_cols = DataFrame(columns=["a", "b", "c"])
+ result = empty_with_cols.apply(x.append, axis=1, result_type="expand")
+ tm.assert_frame_equal(result, empty_with_cols)
+ result = empty_with_cols.apply(x.append, axis=1, result_type="reduce")
+ expected = Series([], dtype=np.float64)
+ tm.assert_series_equal(result, expected)
+
+ # Ensure that x.append hasn't been called
+ assert x == []
+
+
+@pytest.mark.parametrize("func", ["sum", "prod", "any", "all"])
+def test_apply_funcs_over_empty(func):
+ # GH 28213
+ df = DataFrame(columns=["a", "b", "c"])
+
+ result = df.apply(getattr(np, func))
+ expected = getattr(df, func)()
+ if func in ("sum", "prod"):
+ expected = expected.astype(float)
+ tm.assert_series_equal(result, expected)
+
+
+def test_nunique_empty():
+ # GH 28213
+ df = DataFrame(columns=["a", "b", "c"])
+
+ result = df.nunique()
+ expected = Series(0, index=df.columns)
+ tm.assert_series_equal(result, expected)
+
+ result = df.T.nunique()
+ expected = Series([], dtype=np.float64)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_standard_nonunique():
+ df = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=["a", "a", "c"])
+
+ result = df.apply(lambda s: s[0], axis=1)
+ expected = Series([1, 4, 7], ["a", "a", "c"])
+ tm.assert_series_equal(result, expected)
+
+ result = df.T.apply(lambda s: s[0], axis=0)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_broadcast_scalars(float_frame):
+ # scalars
+ result = float_frame.apply(np.mean, result_type="broadcast")
+ expected = DataFrame([float_frame.mean()], index=float_frame.index)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_broadcast_scalars_axis1(float_frame):
+ result = float_frame.apply(np.mean, axis=1, result_type="broadcast")
+ m = float_frame.mean(axis=1)
+ expected = DataFrame(dict.fromkeys(float_frame.columns, m))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_broadcast_lists_columns(float_frame):
+ # lists
+ result = float_frame.apply(
+ lambda x: list(range(len(float_frame.columns))),
+ axis=1,
+ result_type="broadcast",
+ )
+ m = list(range(len(float_frame.columns)))
+ expected = DataFrame(
+ [m] * len(float_frame.index),
+ dtype="float64",
+ index=float_frame.index,
+ columns=float_frame.columns,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_broadcast_lists_index(float_frame):
+ result = float_frame.apply(
+ lambda x: list(range(len(float_frame.index))), result_type="broadcast"
+ )
+ m = list(range(len(float_frame.index)))
+ expected = DataFrame(
+ dict.fromkeys(float_frame.columns, m),
+ dtype="float64",
+ index=float_frame.index,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_broadcast_list_lambda_func(int_frame_const_col):
+ # preserve columns
+ df = int_frame_const_col
+ result = df.apply(lambda x: [1, 2, 3], axis=1, result_type="broadcast")
+ tm.assert_frame_equal(result, df)
+
+
+def test_apply_broadcast_series_lambda_func(int_frame_const_col):
+ df = int_frame_const_col
+ result = df.apply(
+ lambda x: Series([1, 2, 3], index=list("abc")),
+ axis=1,
+ result_type="broadcast",
+ )
+ expected = df.copy()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+def test_apply_raw_float_frame(float_frame, axis, engine):
+ if engine == "numba":
+ pytest.skip("numba can't handle when UDF returns None.")
+
+ def _assert_raw(x):
+ assert isinstance(x, np.ndarray)
+ assert x.ndim == 1
+
+ float_frame.apply(_assert_raw, axis=axis, engine=engine, raw=True)
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+def test_apply_raw_float_frame_lambda(float_frame, axis, engine):
+ result = float_frame.apply(np.mean, axis=axis, engine=engine, raw=True)
+ expected = float_frame.apply(lambda x: x.values.mean(), axis=axis)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_raw_float_frame_no_reduction(float_frame, engine):
+ # no reduction
+ result = float_frame.apply(lambda x: x * 2, engine=engine, raw=True)
+ expected = float_frame * 2
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+def test_apply_raw_mixed_type_frame(axis, engine):
+ if engine == "numba":
+ pytest.skip("isinstance check doesn't work with numba")
+
+ def _assert_raw(x):
+ assert isinstance(x, np.ndarray)
+ assert x.ndim == 1
+
+ # Mixed dtype (GH-32423)
+ df = DataFrame(
+ {
+ "a": 1.0,
+ "b": 2,
+ "c": "foo",
+ "float32": np.array([1.0] * 10, dtype="float32"),
+ "int32": np.array([1] * 10, dtype="int32"),
+ },
+ index=np.arange(10),
+ )
+ df.apply(_assert_raw, axis=axis, engine=engine, raw=True)
+
+
+def test_apply_axis1(float_frame):
+ d = float_frame.index[0]
+ result = float_frame.apply(np.mean, axis=1)[d]
+ expected = np.mean(float_frame.xs(d))
+ assert result == expected
+
+
+def test_apply_mixed_dtype_corner():
+ df = DataFrame({"A": ["foo"], "B": [1.0]})
+ result = df[:0].apply(np.mean, axis=1)
+ # the result here is actually kind of ambiguous, should it be a Series
+ # or a DataFrame?
+ expected = Series(dtype=np.float64)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_mixed_dtype_corner_indexing():
+ df = DataFrame({"A": ["foo"], "B": [1.0]})
+ result = df.apply(lambda x: x["A"], axis=1)
+ expected = Series(["foo"], index=range(1))
+ tm.assert_series_equal(result, expected)
+
+ result = df.apply(lambda x: x["B"], axis=1)
+ expected = Series([1.0], index=range(1))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.filterwarnings("ignore::RuntimeWarning")
+@pytest.mark.parametrize("ax", ["index", "columns"])
+@pytest.mark.parametrize(
+ "func", [lambda x: x, lambda x: x.mean()], ids=["identity", "mean"]
+)
+@pytest.mark.parametrize("raw", [True, False])
+@pytest.mark.parametrize("axis", [0, 1])
+def test_apply_empty_infer_type(ax, func, raw, axis, engine, request):
+ df = DataFrame(**{ax: ["a", "b", "c"]})
+
+ with np.errstate(all="ignore"):
+ test_res = func(np.array([], dtype="f8"))
+ is_reduction = not isinstance(test_res, np.ndarray)
+
+ result = df.apply(func, axis=axis, engine=engine, raw=raw)
+ if is_reduction:
+ agg_axis = df._get_agg_axis(axis)
+ assert isinstance(result, Series)
+ assert result.index is agg_axis
+ else:
+ assert isinstance(result, DataFrame)
+
+
+def test_apply_empty_infer_type_broadcast():
+ no_cols = DataFrame(index=["a", "b", "c"])
+ result = no_cols.apply(lambda x: x.mean(), result_type="broadcast")
+ assert isinstance(result, DataFrame)
+
+
+def test_apply_with_args_kwds_add_some(float_frame):
+ def add_some(x, howmuch=0):
+ return x + howmuch
+
+ result = float_frame.apply(add_some, howmuch=2)
+ expected = float_frame.apply(lambda x: x + 2)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_with_args_kwds_agg_and_add(float_frame):
+ def agg_and_add(x, howmuch=0):
+ return x.mean() + howmuch
+
+ result = float_frame.apply(agg_and_add, howmuch=2)
+ expected = float_frame.apply(lambda x: x.mean() + 2)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_with_args_kwds_subtract_and_divide(float_frame):
+ def subtract_and_divide(x, sub, divide=1):
+ return (x - sub) / divide
+
+ result = float_frame.apply(subtract_and_divide, args=(2,), divide=2)
+ expected = float_frame.apply(lambda x: (x - 2.0) / 2.0)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_yield_list(float_frame):
+ result = float_frame.apply(list)
+ tm.assert_frame_equal(result, float_frame)
+
+
+def test_apply_reduce_Series(float_frame):
+ float_frame.iloc[::2, float_frame.columns.get_loc("A")] = np.nan
+ expected = float_frame.mean(axis=1)
+ result = float_frame.apply(np.mean, axis=1)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_reduce_to_dict():
+ # GH 25196 37544
+ data = DataFrame([[1, 2], [3, 4]], columns=["c0", "c1"], index=["i0", "i1"])
+
+ result = data.apply(dict, axis=0)
+ expected = Series([{"i0": 1, "i1": 3}, {"i0": 2, "i1": 4}], index=data.columns)
+ tm.assert_series_equal(result, expected)
+
+ result = data.apply(dict, axis=1)
+ expected = Series([{"c0": 1, "c1": 2}, {"c0": 3, "c1": 4}], index=data.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_differently_indexed():
+ df = DataFrame(np.random.default_rng(2).standard_normal((20, 10)))
+
+ result = df.apply(Series.describe, axis=0)
+ expected = DataFrame({i: v.describe() for i, v in df.items()}, columns=df.columns)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.apply(Series.describe, axis=1)
+ expected = DataFrame({i: v.describe() for i, v in df.T.items()}, columns=df.index).T
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_bug():
+ # GH 6125
+ positions = DataFrame(
+ [
+ [1, "ABC0", 50],
+ [1, "YUM0", 20],
+ [1, "DEF0", 20],
+ [2, "ABC1", 50],
+ [2, "YUM1", 20],
+ [2, "DEF1", 20],
+ ],
+ columns=["a", "market", "position"],
+ )
+
+ def f(r):
+ return r["market"]
+
+ expected = positions.apply(f, axis=1)
+
+ positions = DataFrame(
+ [
+ [datetime(2013, 1, 1), "ABC0", 50],
+ [datetime(2013, 1, 2), "YUM0", 20],
+ [datetime(2013, 1, 3), "DEF0", 20],
+ [datetime(2013, 1, 4), "ABC1", 50],
+ [datetime(2013, 1, 5), "YUM1", 20],
+ [datetime(2013, 1, 6), "DEF1", 20],
+ ],
+ columns=["a", "market", "position"],
+ )
+ result = positions.apply(f, axis=1)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_convert_objects():
+ expected = DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "C": [
+ "dull",
+ "dull",
+ "shiny",
+ "dull",
+ "dull",
+ "shiny",
+ "shiny",
+ "dull",
+ "shiny",
+ "shiny",
+ "shiny",
+ ],
+ "D": np.random.default_rng(2).standard_normal(11),
+ "E": np.random.default_rng(2).standard_normal(11),
+ "F": np.random.default_rng(2).standard_normal(11),
+ }
+ )
+
+ result = expected.apply(lambda x: x, axis=1)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_attach_name(float_frame):
+ result = float_frame.apply(lambda x: x.name)
+ expected = Series(float_frame.columns, index=float_frame.columns)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_attach_name_axis1(float_frame):
+ result = float_frame.apply(lambda x: x.name, axis=1)
+ expected = Series(float_frame.index, index=float_frame.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_attach_name_non_reduction(float_frame):
+ # non-reductions
+ result = float_frame.apply(lambda x: np.repeat(x.name, len(x)))
+ expected = DataFrame(
+ np.tile(float_frame.columns, (len(float_frame.index), 1)),
+ index=float_frame.index,
+ columns=float_frame.columns,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_attach_name_non_reduction_axis1(float_frame):
+ result = float_frame.apply(lambda x: np.repeat(x.name, len(x)), axis=1)
+ expected = Series(
+ np.repeat(t[0], len(float_frame.columns)) for t in float_frame.itertuples()
+ )
+ expected.index = float_frame.index
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_multi_index():
+ index = MultiIndex.from_arrays([["a", "a", "b"], ["c", "d", "d"]])
+ s = DataFrame([[1, 2], [3, 4], [5, 6]], index=index, columns=["col1", "col2"])
+ result = s.apply(lambda x: Series({"min": min(x), "max": max(x)}), 1)
+ expected = DataFrame([[1, 2], [3, 4], [5, 6]], index=index, columns=["min", "max"])
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+
+@pytest.mark.parametrize(
+ "df, dicts",
+ [
+ [
+ DataFrame([["foo", "bar"], ["spam", "eggs"]]),
+ Series([{0: "foo", 1: "spam"}, {0: "bar", 1: "eggs"}]),
+ ],
+ [DataFrame([[0, 1], [2, 3]]), Series([{0: 0, 1: 2}, {0: 1, 1: 3}])],
+ ],
+)
+def test_apply_dict(df, dicts):
+ # GH 8735
+ fn = lambda x: x.to_dict()
+ reduce_true = df.apply(fn, result_type="reduce")
+ reduce_false = df.apply(fn, result_type="expand")
+ reduce_none = df.apply(fn)
+
+ tm.assert_series_equal(reduce_true, dicts)
+ tm.assert_frame_equal(reduce_false, df)
+ tm.assert_series_equal(reduce_none, dicts)
+
+
+def test_apply_non_numpy_dtype():
+ # GH 12244
+ df = DataFrame({"dt": date_range("2015-01-01", periods=3, tz="Europe/Brussels")})
+ result = df.apply(lambda x: x)
+ tm.assert_frame_equal(result, df)
+
+ result = df.apply(lambda x: x + pd.Timedelta("1day"))
+ expected = DataFrame(
+ {"dt": date_range("2015-01-02", periods=3, tz="Europe/Brussels")}
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_non_numpy_dtype_category():
+ df = DataFrame({"dt": ["a", "b", "c", "a"]}, dtype="category")
+ result = df.apply(lambda x: x)
+ tm.assert_frame_equal(result, df)
+
+
+def test_apply_dup_names_multi_agg():
+ # GH 21063
+ df = DataFrame([[0, 1], [2, 3]], columns=["a", "a"])
+ expected = DataFrame([[0, 1]], columns=["a", "a"], index=["min"])
+ result = df.agg(["min"])
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("op", ["apply", "agg"])
+def test_apply_nested_result_axis_1(op):
+ # GH 13820
+ def apply_list(row):
+ return [2 * row["A"], 2 * row["C"], 2 * row["B"]]
+
+ df = DataFrame(np.zeros((4, 4)), columns=list("ABCD"))
+ result = getattr(df, op)(apply_list, axis=1)
+ expected = Series(
+ [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_noreduction_tzaware_object():
+ # https://github.com/pandas-dev/pandas/issues/31505
+ expected = DataFrame(
+ {"foo": [Timestamp("2020", tz="UTC")]}, dtype="datetime64[ns, UTC]"
+ )
+ result = expected.apply(lambda x: x)
+ tm.assert_frame_equal(result, expected)
+ result = expected.apply(lambda x: x.copy())
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_function_runs_once():
+ # https://github.com/pandas-dev/pandas/issues/30815
+
+ df = DataFrame({"a": [1, 2, 3]})
+ names = [] # Save row names function is applied to
+
+ def reducing_function(row):
+ names.append(row.name)
+
+ def non_reducing_function(row):
+ names.append(row.name)
+ return row
+
+ for func in [reducing_function, non_reducing_function]:
+ del names[:]
+
+ df.apply(func, axis=1)
+ assert names == list(df.index)
+
+
+def test_apply_raw_function_runs_once(engine):
+ # https://github.com/pandas-dev/pandas/issues/34506
+ if engine == "numba":
+ pytest.skip("appending to list outside of numba func is not supported")
+
+ df = DataFrame({"a": [1, 2, 3]})
+ values = [] # Save row values function is applied to
+
+ def reducing_function(row):
+ values.extend(row)
+
+ def non_reducing_function(row):
+ values.extend(row)
+ return row
+
+ for func in [reducing_function, non_reducing_function]:
+ del values[:]
+
+ df.apply(func, engine=engine, raw=True, axis=1)
+ assert values == list(df.a.to_list())
+
+
+def test_apply_with_byte_string():
+ # GH 34529
+ df = DataFrame(np.array([b"abcd", b"efgh"]), columns=["col"])
+ expected = DataFrame(np.array([b"abcd", b"efgh"]), columns=["col"], dtype=object)
+ # After we make the apply we expect a dataframe just
+ # like the original but with the object datatype
+ result = df.apply(lambda x: x.astype("object"))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("val", ["asd", 12, None, np.nan])
+def test_apply_category_equalness(val):
+ # Check if categorical comparisons on apply, GH 21239
+ df_values = ["asd", None, 12, "asd", "cde", np.nan]
+ df = DataFrame({"a": df_values}, dtype="category")
+
+ result = df.a.apply(lambda x: x == val)
+ expected = Series(
+ [False if pd.isnull(x) else x == val for x in df_values], name="a"
+ )
+ # False since behavior of NaN for categorical dtype has been changed (GH 59966)
+ tm.assert_series_equal(result, expected)
+
+
+# the user has supplied an opaque UDF where
+# they are transforming the input that requires
+# us to infer the output
+
+
+def test_infer_row_shape():
+ # GH 17437
+ # if row shape is changing, infer it
+ df = DataFrame(np.random.default_rng(2).random((10, 2)))
+ result = df.apply(np.fft.fft, axis=0).shape
+ assert result == (10, 2)
+
+ result = df.apply(np.fft.rfft, axis=0).shape
+ assert result == (6, 2)
+
+
+@pytest.mark.parametrize(
+ "ops, by_row, expected",
+ [
+ ({"a": lambda x: x + 1}, "compat", DataFrame({"a": [2, 3]})),
+ ({"a": lambda x: x + 1}, False, DataFrame({"a": [2, 3]})),
+ ({"a": lambda x: x.sum()}, "compat", Series({"a": 3})),
+ ({"a": lambda x: x.sum()}, False, Series({"a": 3})),
+ (
+ {"a": ["sum", np.sum, lambda x: x.sum()]},
+ "compat",
+ DataFrame({"a": [3, 3, 3]}, index=["sum", "sum", ""]),
+ ),
+ (
+ {"a": ["sum", np.sum, lambda x: x.sum()]},
+ False,
+ DataFrame({"a": [3, 3, 3]}, index=["sum", "sum", ""]),
+ ),
+ ({"a": lambda x: 1}, "compat", DataFrame({"a": [1, 1]})),
+ ({"a": lambda x: 1}, False, Series({"a": 1})),
+ ],
+)
+def test_dictlike_lambda(ops, by_row, expected):
+ # GH53601
+ df = DataFrame({"a": [1, 2]})
+ result = df.apply(ops, by_row=by_row)
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops",
+ [
+ {"a": lambda x: x + 1},
+ {"a": lambda x: x.sum()},
+ {"a": ["sum", np.sum, lambda x: x.sum()]},
+ {"a": lambda x: 1},
+ ],
+)
+def test_dictlike_lambda_raises(ops):
+ # GH53601
+ df = DataFrame({"a": [1, 2]})
+ with pytest.raises(ValueError, match="by_row=True not allowed"):
+ df.apply(ops, by_row=True)
+
+
+def test_with_dictlike_columns():
+ # GH 17602
+ df = DataFrame([[1, 2], [1, 2]], columns=["a", "b"])
+ result = df.apply(lambda x: {"s": x["a"] + x["b"]}, axis=1)
+ expected = Series([{"s": 3} for t in df.itertuples()])
+ tm.assert_series_equal(result, expected)
+
+ df["tm"] = [
+ Timestamp("2017-05-01 00:00:00"),
+ Timestamp("2017-05-02 00:00:00"),
+ ]
+ result = df.apply(lambda x: {"s": x["a"] + x["b"]}, axis=1)
+ tm.assert_series_equal(result, expected)
+
+ # compose a series
+ result = (df["a"] + df["b"]).apply(lambda x: {"s": x})
+ expected = Series([{"s": 3}, {"s": 3}])
+ tm.assert_series_equal(result, expected)
+
+
+def test_with_dictlike_columns_with_datetime():
+ # GH 18775
+ df = DataFrame()
+ df["author"] = ["X", "Y", "Z"]
+ df["publisher"] = ["BBC", "NBC", "N24"]
+ df["date"] = pd.to_datetime(
+ ["17-10-2010 07:15:30", "13-05-2011 08:20:35", "15-01-2013 09:09:09"],
+ dayfirst=True,
+ )
+ result = df.apply(lambda x: {}, axis=1)
+ expected = Series([{}, {}, {}])
+ tm.assert_series_equal(result, expected)
+
+
+def test_with_dictlike_columns_with_infer():
+ # GH 17602
+ df = DataFrame([[1, 2], [1, 2]], columns=["a", "b"])
+ result = df.apply(lambda x: {"s": x["a"] + x["b"]}, axis=1, result_type="expand")
+ expected = DataFrame({"s": [3, 3]})
+ tm.assert_frame_equal(result, expected)
+
+ df["tm"] = [
+ Timestamp("2017-05-01 00:00:00"),
+ Timestamp("2017-05-02 00:00:00"),
+ ]
+ result = df.apply(lambda x: {"s": x["a"] + x["b"]}, axis=1, result_type="expand")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops, by_row, expected",
+ [
+ ([lambda x: x + 1], "compat", DataFrame({("a", ""): [2, 3]})),
+ ([lambda x: x + 1], False, DataFrame({("a", ""): [2, 3]})),
+ ([lambda x: x.sum()], "compat", DataFrame({"a": [3]}, index=[""])),
+ ([lambda x: x.sum()], False, DataFrame({"a": [3]}, index=[""])),
+ (
+ ["sum", np.sum, lambda x: x.sum()],
+ "compat",
+ DataFrame({"a": [3, 3, 3]}, index=["sum", "sum", ""]),
+ ),
+ (
+ ["sum", np.sum, lambda x: x.sum()],
+ False,
+ DataFrame({"a": [3, 3, 3]}, index=["sum", "sum", ""]),
+ ),
+ (
+ [lambda x: x + 1, lambda x: 3],
+ "compat",
+ DataFrame([[2, 3], [3, 3]], columns=[["a", "a"], ["", ""]]),
+ ),
+ (
+ [lambda x: 2, lambda x: 3],
+ False,
+ DataFrame({"a": [2, 3]}, ["", ""]),
+ ),
+ ],
+)
+def test_listlike_lambda(ops, by_row, expected):
+ # GH53601
+ df = DataFrame({"a": [1, 2]})
+ result = df.apply(ops, by_row=by_row)
+ tm.assert_equal(result, expected)
+
+
+def test_listlike_datetime_index_unsorted():
+ # https://github.com/pandas-dev/pandas/pull/62843
+ values = [datetime(2024, 1, 1), datetime(2024, 1, 2), datetime(2024, 1, 3)]
+ df = DataFrame({"a": [1, 2]}, index=[values[1], values[0]])
+ result = df.apply([lambda x: x, lambda x: x.shift(freq="D")], by_row=False)
+ expected = DataFrame(
+ [[1.0, 2.0], [2.0, np.nan], [np.nan, 1.0]],
+ index=[values[1], values[0], values[2]],
+ columns=MultiIndex([["a"], [""]], codes=[[0, 0], [0, 0]]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dictlike_datetime_index_unsorted():
+ # https://github.com/pandas-dev/pandas/pull/62843
+ values = [datetime(2024, 1, 1), datetime(2024, 1, 2), datetime(2024, 1, 3)]
+ df = DataFrame({"a": [1, 2], "b": [3, 4]}, index=[values[1], values[0]])
+ result = df.apply(
+ {"a": lambda x: x, "b": lambda x: x.shift(freq="D")}, by_row=False
+ )
+ expected = DataFrame(
+ {
+ "a": [1.0, 2.0, np.nan],
+ "b": [4.0, np.nan, 3.0],
+ },
+ index=[values[1], values[0], values[2]],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops",
+ [
+ [lambda x: x + 1],
+ [lambda x: x.sum()],
+ ["sum", np.sum, lambda x: x.sum()],
+ [lambda x: x + 1, lambda x: 3],
+ ],
+)
+def test_listlike_lambda_raises(ops):
+ # GH53601
+ df = DataFrame({"a": [1, 2]})
+ with pytest.raises(ValueError, match="by_row=True not allowed"):
+ df.apply(ops, by_row=True)
+
+
+def test_with_listlike_columns():
+ # GH 17348
+ df = DataFrame(
+ {
+ "a": Series(np.random.default_rng(2).standard_normal(4)),
+ "b": ["a", "list", "of", "words"],
+ "ts": date_range("2016-10-01", periods=4, freq="h"),
+ }
+ )
+
+ result = df[["a", "b"]].apply(tuple, axis=1)
+ expected = Series([t[1:] for t in df[["a", "b"]].itertuples()])
+ tm.assert_series_equal(result, expected)
+
+ result = df[["a", "ts"]].apply(tuple, axis=1)
+ expected = Series([t[1:] for t in df[["a", "ts"]].itertuples()])
+ tm.assert_series_equal(result, expected)
+
+
+def test_with_listlike_columns_returning_list():
+ # GH 18919
+ df = DataFrame({"x": Series([["a", "b"], ["q"]]), "y": Series([["z"], ["q", "t"]])})
+ df.index = MultiIndex.from_tuples([("i0", "j0"), ("i1", "j1")])
+
+ result = df.apply(lambda row: [el for el in row["x"] if el in row["y"]], axis=1)
+ expected = Series([[], ["q"]], index=df.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_infer_output_shape_columns():
+ # GH 18573
+
+ df = DataFrame(
+ {
+ "number": [1.0, 2.0],
+ "string": ["foo", "bar"],
+ "datetime": [
+ Timestamp("2017-11-29 03:30:00"),
+ Timestamp("2017-11-29 03:45:00"),
+ ],
+ }
+ )
+ result = df.apply(lambda row: (row.number, row.string), axis=1)
+ expected = Series([(t.number, t.string) for t in df.itertuples()])
+ tm.assert_series_equal(result, expected)
+
+
+def test_infer_output_shape_listlike_columns():
+ # GH 16353
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 3)), columns=["A", "B", "C"]
+ )
+
+ result = df.apply(lambda x: [1, 2, 3], axis=1)
+ expected = Series([[1, 2, 3] for t in df.itertuples()])
+ tm.assert_series_equal(result, expected)
+
+ result = df.apply(lambda x: [1, 2], axis=1)
+ expected = Series([[1, 2] for t in df.itertuples()])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("val", [1, 2])
+def test_infer_output_shape_listlike_columns_np_func(val):
+ # GH 17970
+ df = DataFrame({"a": [1, 2, 3]}, index=list("abc"))
+
+ result = df.apply(lambda row: np.ones(val), axis=1)
+ expected = Series([np.ones(val) for t in df.itertuples()], index=df.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_infer_output_shape_listlike_columns_with_timestamp():
+ # GH 17892
+ df = DataFrame(
+ {
+ "a": [
+ Timestamp("2010-02-01"),
+ Timestamp("2010-02-04"),
+ Timestamp("2010-02-05"),
+ Timestamp("2010-02-06"),
+ ],
+ "b": [9, 5, 4, 3],
+ "c": [5, 3, 4, 2],
+ "d": [1, 2, 3, 4],
+ }
+ )
+
+ def fun(x):
+ return (1, 2)
+
+ result = df.apply(fun, axis=1)
+ expected = Series([(1, 2) for t in df.itertuples()])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("lst", [[1, 2, 3], [1, 2]])
+def test_consistent_coerce_for_shapes(lst):
+ # we want column names to NOT be propagated
+ # just because the shape matches the input shape
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 3)), columns=["A", "B", "C"]
+ )
+
+ result = df.apply(lambda x: lst, axis=1)
+ expected = Series([lst for t in df.itertuples()])
+ tm.assert_series_equal(result, expected)
+
+
+def test_consistent_names(int_frame_const_col):
+ # if a Series is returned, we should use the resulting index names
+ df = int_frame_const_col
+
+ result = df.apply(
+ lambda x: Series([1, 2, 3], index=["test", "other", "cols"]), axis=1
+ )
+ expected = int_frame_const_col.rename(
+ columns={"A": "test", "B": "other", "C": "cols"}
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.apply(lambda x: Series([1, 2], index=["test", "other"]), axis=1)
+ expected = expected[["test", "other"]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_result_type(int_frame_const_col):
+ # result_type should be consistent no matter which
+ # path we take in the code
+ df = int_frame_const_col
+
+ result = df.apply(lambda x: [1, 2, 3], axis=1, result_type="expand")
+ expected = df.copy()
+ expected.columns = range(3)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_result_type_shorter_list(int_frame_const_col):
+ # result_type should be consistent no matter which
+ # path we take in the code
+ df = int_frame_const_col
+ result = df.apply(lambda x: [1, 2], axis=1, result_type="expand")
+ expected = df[["A", "B"]].copy()
+ expected.columns = range(2)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_result_type_broadcast(int_frame_const_col, request, engine):
+ # result_type should be consistent no matter which
+ # path we take in the code
+ if engine == "numba":
+ mark = pytest.mark.xfail(reason="numba engine doesn't support list return")
+ request.node.add_marker(mark)
+ df = int_frame_const_col
+ if engine is MockEngineDecorator:
+ with pytest.raises(
+ NotImplementedError,
+ match="result_type='broadcast' only implemented for the default engine",
+ ):
+ df.apply(
+ lambda x: [1, 2, 3], axis=1, result_type="broadcast", engine=engine
+ )
+ else:
+ # broadcast result
+ result = df.apply(
+ lambda x: [1, 2, 3], axis=1, result_type="broadcast", engine=engine
+ )
+ expected = df.copy()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_result_type_broadcast_series_func(int_frame_const_col, engine, request):
+ # result_type should be consistent no matter which
+ # path we take in the code
+ if engine == "numba":
+ mark = pytest.mark.xfail(
+ reason="numba Series constructor only support ndarrays not list data"
+ )
+ request.node.add_marker(mark)
+ df = int_frame_const_col
+ columns = ["other", "col", "names"]
+
+ if engine is MockEngineDecorator:
+ with pytest.raises(
+ NotImplementedError,
+ match="result_type='broadcast' only implemented for the default engine",
+ ):
+ df.apply(
+ lambda x: Series([1, 2, 3], index=columns),
+ axis=1,
+ result_type="broadcast",
+ engine=engine,
+ )
+ else:
+ result = df.apply(
+ lambda x: Series([1, 2, 3], index=columns),
+ axis=1,
+ result_type="broadcast",
+ engine=engine,
+ )
+ expected = df.copy()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_result_type_series_result(int_frame_const_col, engine, request):
+ # result_type should be consistent no matter which
+ # path we take in the code
+ if engine == "numba":
+ mark = pytest.mark.xfail(
+ reason="numba Series constructor only support ndarrays not list data"
+ )
+ request.node.add_marker(mark)
+ df = int_frame_const_col
+ # series result
+ result = df.apply(lambda x: Series([1, 2, 3], index=x.index), axis=1, engine=engine)
+ expected = df.copy()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_result_type_series_result_other_index(int_frame_const_col, engine, request):
+ # result_type should be consistent no matter which
+ # path we take in the code
+
+ if engine == "numba":
+ mark = pytest.mark.xfail(
+ reason="no support in numba Series constructor for list of columns"
+ )
+ request.node.add_marker(mark)
+ df = int_frame_const_col
+ # series result with other index
+ columns = ["other", "col", "names"]
+ result = df.apply(lambda x: Series([1, 2, 3], index=columns), axis=1, engine=engine)
+ expected = df.copy()
+ expected.columns = columns
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "box",
+ [lambda x: list(x), lambda x: tuple(x), lambda x: np.array(x, dtype="int64")],
+ ids=["list", "tuple", "array"],
+)
+def test_consistency_for_boxed(box, int_frame_const_col):
+ # passing an array or list should not affect the output shape
+ df = int_frame_const_col
+
+ result = df.apply(lambda x: box([1, 2]), axis=1)
+ expected = Series([box([1, 2]) for t in df.itertuples()])
+ tm.assert_series_equal(result, expected)
+
+ result = df.apply(lambda x: box([1, 2]), axis=1, result_type="expand")
+ expected = int_frame_const_col[["A", "B"]].rename(columns={"A": 0, "B": 1})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_transform(axis, float_frame):
+ other_axis = 1 if axis in {0, "index"} else 0
+
+ with np.errstate(all="ignore"):
+ f_abs = np.abs(float_frame)
+ f_sqrt = np.sqrt(float_frame)
+
+ # ufunc
+ expected = f_sqrt.copy()
+ result = float_frame.apply(np.sqrt, axis=axis)
+ tm.assert_frame_equal(result, expected)
+
+ # list-like
+ result = float_frame.apply([np.sqrt], axis=axis)
+ expected = f_sqrt.copy()
+ if axis in {0, "index"}:
+ expected.columns = MultiIndex.from_product([float_frame.columns, ["sqrt"]])
+ else:
+ expected.index = MultiIndex.from_product([float_frame.index, ["sqrt"]])
+ tm.assert_frame_equal(result, expected)
+
+ # multiple items in list
+ # these are in the order as if we are applying both
+ # functions per series and then concatting
+ result = float_frame.apply([np.abs, np.sqrt], axis=axis)
+ expected = zip_frames([f_abs, f_sqrt], axis=other_axis)
+ if axis in {0, "index"}:
+ expected.columns = MultiIndex.from_product(
+ [float_frame.columns, ["absolute", "sqrt"]]
+ )
+ else:
+ expected.index = MultiIndex.from_product(
+ [float_frame.index, ["absolute", "sqrt"]]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_demo():
+ # demonstration tests
+ df = DataFrame({"A": range(5), "B": 5})
+
+ result = df.agg(["min", "max"])
+ expected = DataFrame(
+ {"A": [0, 4], "B": [5, 5]}, columns=["A", "B"], index=["min", "max"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_demo_dict_agg():
+ # demonstration tests
+ df = DataFrame({"A": range(5), "B": 5})
+ result = df.agg({"A": ["min", "max"], "B": ["sum", "max"]})
+ expected = DataFrame(
+ {"A": [4.0, 0.0, np.nan], "B": [5.0, np.nan, 25.0]},
+ columns=["A", "B"],
+ index=["max", "min", "sum"],
+ )
+ tm.assert_frame_equal(result.reindex_like(expected), expected)
+
+
+def test_agg_with_name_as_column_name():
+ # GH 36212 - Column name is "name"
+ data = {"name": ["foo", "bar"]}
+ df = DataFrame(data)
+
+ # result's name should be None
+ result = df.agg({"name": "count"})
+ expected = Series({"name": 2})
+ tm.assert_series_equal(result, expected)
+
+ # Check if name is still preserved when aggregating series instead
+ result = df["name"].agg({"name": "count"})
+ expected = Series({"name": 2}, name="name")
+ tm.assert_series_equal(result, expected)
+
+
+def test_agg_multiple_mixed():
+ # GH 20909
+ mdf = DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [1.0, 2.0, 3.0],
+ "C": ["foo", "bar", "baz"],
+ }
+ )
+ expected = DataFrame(
+ {
+ "A": [1, 6],
+ "B": [1.0, 6.0],
+ "C": ["bar", "foobarbaz"],
+ },
+ index=["min", "sum"],
+ )
+ # sorted index
+ result = mdf.agg(["min", "sum"])
+ tm.assert_frame_equal(result, expected)
+
+ result = mdf[["C", "B", "A"]].agg(["sum", "min"])
+ # GH40420: the result of .agg should have an index that is sorted
+ # according to the arguments provided to agg.
+ expected = expected[["C", "B", "A"]].reindex(["sum", "min"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_multiple_mixed_raises():
+ # GH 20909
+ mdf = DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [1.0, 2.0, 3.0],
+ "C": ["foo", "bar", "baz"],
+ "D": date_range("20130101", periods=3),
+ }
+ )
+
+ # sorted index
+ msg = "does not support operation"
+ with pytest.raises(TypeError, match=msg):
+ mdf.agg(["min", "sum"])
+
+ with pytest.raises(TypeError, match=msg):
+ mdf[["D", "C", "B", "A"]].agg(["sum", "min"])
+
+
+def test_agg_reduce(axis, float_frame):
+ other_axis = 1 if axis in {0, "index"} else 0
+ name1, name2 = float_frame.axes[other_axis].unique()[:2].sort_values()
+
+ # all reducers
+ expected = pd.concat(
+ [
+ float_frame.mean(axis=axis),
+ float_frame.max(axis=axis),
+ float_frame.sum(axis=axis),
+ ],
+ axis=1,
+ )
+ expected.columns = ["mean", "max", "sum"]
+ expected = expected.T if axis in {0, "index"} else expected
+
+ result = float_frame.agg(["mean", "max", "sum"], axis=axis)
+ tm.assert_frame_equal(result, expected)
+
+ # dict input with scalars
+ func = {name1: "mean", name2: "sum"}
+ result = float_frame.agg(func, axis=axis)
+ expected = Series(
+ [
+ float_frame.loc(other_axis)[name1].mean(),
+ float_frame.loc(other_axis)[name2].sum(),
+ ],
+ index=[name1, name2],
+ )
+ tm.assert_series_equal(result, expected)
+
+ # dict input with lists
+ func = {name1: ["mean"], name2: ["sum"]}
+ result = float_frame.agg(func, axis=axis)
+ expected = DataFrame(
+ {
+ name1: Series([float_frame.loc(other_axis)[name1].mean()], index=["mean"]),
+ name2: Series([float_frame.loc(other_axis)[name2].sum()], index=["sum"]),
+ }
+ )
+ expected = expected.T if axis in {1, "columns"} else expected
+ tm.assert_frame_equal(result, expected)
+
+ # dict input with lists with multiple
+ func = {name1: ["mean", "sum"], name2: ["sum", "max"]}
+ result = float_frame.agg(func, axis=axis)
+ expected = pd.concat(
+ {
+ name1: Series(
+ [
+ float_frame.loc(other_axis)[name1].mean(),
+ float_frame.loc(other_axis)[name1].sum(),
+ ],
+ index=["mean", "sum"],
+ ),
+ name2: Series(
+ [
+ float_frame.loc(other_axis)[name2].sum(),
+ float_frame.loc(other_axis)[name2].max(),
+ ],
+ index=["sum", "max"],
+ ),
+ },
+ axis=1,
+ )
+ expected = expected.T if axis in {1, "columns"} else expected
+ tm.assert_frame_equal(result, expected)
+
+
+def test_named_agg_reduce_axis1_raises(float_frame):
+ name1, name2 = float_frame.axes[0].unique()[:2].sort_values()
+ msg = "Named aggregation is not supported when axis=1."
+ for axis in [1, "columns"]:
+ with pytest.raises(NotImplementedError, match=msg):
+ float_frame.agg(row1=(name1, "sum"), row2=(name2, "max"), axis=axis)
+
+
+def test_nuiscance_columns():
+ # GH 15015
+ df = DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [1.0, 2.0, 3.0],
+ "C": ["foo", "bar", "baz"],
+ "D": date_range("20130101", periods=3),
+ }
+ )
+
+ result = df.agg("min")
+ expected = Series([1, 1.0, "bar", Timestamp("20130101")], index=df.columns)
+ tm.assert_series_equal(result, expected)
+
+ result = df.agg(["min"])
+ expected = DataFrame(
+ [[1, 1.0, "bar", Timestamp("20130101")]],
+ index=["min"],
+ columns=df.columns,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ msg = "does not support operation"
+ with pytest.raises(TypeError, match=msg):
+ df.agg("sum")
+
+ result = df[["A", "B", "C"]].agg("sum")
+ expected = Series([6, 6.0, "foobarbaz"], index=["A", "B", "C"])
+ tm.assert_series_equal(result, expected)
+
+ msg = "does not support operation"
+ with pytest.raises(TypeError, match=msg):
+ df.agg(["sum"])
+
+
+@pytest.mark.parametrize("how", ["agg", "apply"])
+def test_non_callable_aggregates(how):
+ # GH 16405
+ # 'size' is a property of frame/series
+ # validate that this is working
+ # GH 39116 - expand to apply
+ df = DataFrame(
+ {"A": [None, 2, 3], "B": [1.0, np.nan, 3.0], "C": ["foo", None, "bar"]}
+ )
+
+ # Function aggregate
+ result = getattr(df, how)({"A": "count"})
+ expected = Series({"A": 2})
+
+ tm.assert_series_equal(result, expected)
+
+ # Non-function aggregate
+ result = getattr(df, how)({"A": "size"})
+ expected = Series({"A": 3})
+
+ tm.assert_series_equal(result, expected)
+
+ # Mix function and non-function aggs
+ result1 = getattr(df, how)(["count", "size"])
+ result2 = getattr(df, how)(
+ {"A": ["count", "size"], "B": ["count", "size"], "C": ["count", "size"]}
+ )
+ expected = DataFrame(
+ {
+ "A": {"count": 2, "size": 3},
+ "B": {"count": 2, "size": 3},
+ "C": {"count": 2, "size": 3},
+ }
+ )
+
+ tm.assert_frame_equal(result1, result2, check_like=True)
+ tm.assert_frame_equal(result2, expected, check_like=True)
+
+ # Just functional string arg is same as calling df.arg()
+ result = getattr(df, how)("count")
+ expected = df.count()
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("how", ["agg", "apply"])
+def test_size_as_str(how, axis):
+ # GH 39934
+ df = DataFrame(
+ {"A": [None, 2, 3], "B": [1.0, np.nan, 3.0], "C": ["foo", None, "bar"]}
+ )
+ # Just a string attribute arg same as calling df.arg
+ # on the columns
+ result = getattr(df, how)("size", axis=axis)
+ if axis in (0, "index"):
+ expected = Series(df.shape[0], index=df.columns)
+ else:
+ expected = Series(df.shape[1], index=df.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_agg_listlike_result():
+ # GH-29587 user defined function returning list-likes
+ df = DataFrame({"A": [2, 2, 3], "B": [1.5, np.nan, 1.5], "C": ["foo", None, "bar"]})
+
+ def func(group_col):
+ return list(group_col.dropna().unique())
+
+ result = df.agg(func)
+ expected = Series([[2, 3], [1.5], ["foo", "bar"]], index=["A", "B", "C"])
+ tm.assert_series_equal(result, expected)
+
+ result = df.agg([func])
+ expected = expected.to_frame("func").T
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+@pytest.mark.parametrize(
+ "args, kwargs",
+ [
+ ((1, 2, 3), {}),
+ ((8, 7, 15), {}),
+ ((1, 2), {}),
+ ((1,), {"b": 2}),
+ ((), {"a": 1, "b": 2}),
+ ((), {"a": 2, "b": 1}),
+ ((), {"a": 1, "b": 2, "c": 3}),
+ ],
+)
+def test_agg_args_kwargs(axis, args, kwargs):
+ def f(x, a, b, c=3):
+ return x.sum() + (a + b) / c
+
+ df = DataFrame([[1, 2], [3, 4]])
+
+ if axis == 0:
+ expected = Series([5.0, 7.0])
+ else:
+ expected = Series([4.0, 8.0])
+
+ result = df.agg(f, axis, *args, **kwargs)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("num_cols", [2, 3, 5])
+def test_frequency_is_original(num_cols, engine, request):
+ # GH 22150
+ if engine == "numba":
+ mark = pytest.mark.xfail(reason="numba engine only supports numeric indices")
+ request.node.add_marker(mark)
+ index = pd.DatetimeIndex(["1950-06-30", "1952-10-24", "1953-05-29"])
+ original = index.copy()
+ df = DataFrame(1, index=index, columns=range(num_cols))
+ df.apply(lambda x: x, engine=engine)
+ assert index.freq == original.freq
+
+
+def test_apply_datetime_tz_issue(engine, request):
+ # GH 29052
+
+ if engine == "numba":
+ mark = pytest.mark.xfail(
+ reason="numba engine doesn't support non-numeric indexes"
+ )
+ request.node.add_marker(mark)
+
+ timestamps = [
+ Timestamp("2019-03-15 12:34:31.909000+0000", tz="UTC"),
+ Timestamp("2019-03-15 12:34:34.359000+0000", tz="UTC"),
+ Timestamp("2019-03-15 12:34:34.660000+0000", tz="UTC"),
+ ]
+ df = DataFrame(data=[0, 1, 2], index=timestamps)
+ result = df.apply(lambda x: x.name, axis=1, engine=engine)
+ expected = Series(index=timestamps, data=timestamps)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("df", [DataFrame({"A": ["a", None], "B": ["c", "d"]})])
+@pytest.mark.parametrize("method", ["min", "max", "sum"])
+def test_mixed_column_raises(df, method, using_infer_string):
+ # GH 16832
+ if method == "sum":
+ msg = r'can only concatenate str \(not "int"\) to str|does not support'
+ else:
+ msg = "not supported between instances of 'str' and 'float'"
+ if not using_infer_string:
+ with pytest.raises(TypeError, match=msg):
+ getattr(df, method)()
+ else:
+ getattr(df, method)()
+
+
+@pytest.mark.parametrize("col", [1, 1.0, True, "a", np.nan])
+def test_apply_dtype(col):
+ # GH 31466
+ df = DataFrame([[1.0, col]], columns=["a", "b"])
+ result = df.apply(lambda x: x.dtype)
+ expected = df.dtypes
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_mutating():
+ # GH#35462 case where applied func pins a new BlockManager to a row
+ df = DataFrame({"a": range(10), "b": range(10, 20)})
+ df_orig = df.copy()
+
+ def func(row):
+ mgr = row._mgr
+ row.loc["a"] += 1
+ assert row._mgr is not mgr
+ return row
+
+ expected = df.copy()
+ expected["a"] += 1
+
+ result = df.apply(func, axis=1)
+
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_apply_empty_list_reduce():
+ # GH#35683 get columns correct
+ df = DataFrame([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]], columns=["a", "b"])
+
+ result = df.apply(lambda x: [], result_type="reduce")
+ expected = Series({"a": [], "b": []}, dtype=object)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_no_suffix_index(engine, request):
+ # GH36189
+ if engine == "numba":
+ mark = pytest.mark.xfail(
+ reason="numba engine doesn't support list-likes/dict-like callables"
+ )
+ request.node.add_marker(mark)
+ pdf = DataFrame([[4, 9]] * 3, columns=["A", "B"])
+ result = pdf.apply(["sum", lambda x: x.sum(), lambda x: x.sum()], engine=engine)
+ expected = DataFrame(
+ {"A": [12, 12, 12], "B": [27, 27, 27]}, index=["sum", "", ""]
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_raw_returns_string(engine):
+ # https://github.com/pandas-dev/pandas/issues/35940
+ if engine == "numba":
+ pytest.skip("No object dtype support in numba")
+ df = DataFrame({"A": ["aa", "bbb"]})
+ result = df.apply(lambda x: x[0], engine=engine, axis=1, raw=True)
+ expected = Series(["aa", "bbb"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_aggregation_func_column_order():
+ # GH40420: the result of .agg should have an index that is sorted
+ # according to the arguments provided to agg.
+ df = DataFrame(
+ [
+ (1, 0, 0),
+ (2, 0, 0),
+ (3, 0, 0),
+ (4, 5, 4),
+ (5, 6, 6),
+ (6, 7, 7),
+ ],
+ columns=("att1", "att2", "att3"),
+ )
+
+ def sum_div2(s):
+ return s.sum() / 2
+
+ aggs = ["sum", sum_div2, "count", "min"]
+ result = df.agg(aggs)
+ expected = DataFrame(
+ {
+ "att1": [21.0, 10.5, 6.0, 1.0],
+ "att2": [18.0, 9.0, 6.0, 0.0],
+ "att3": [17.0, 8.5, 6.0, 0.0],
+ },
+ index=["sum", "sum_div2", "count", "min"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_getitem_axis_1(engine, request):
+ # GH 13427
+ if engine == "numba":
+ mark = pytest.mark.xfail(
+ reason="numba engine not supporting duplicate index values"
+ )
+ request.node.add_marker(mark)
+ df = DataFrame({"a": [0, 1, 2], "b": [1, 2, 3]})
+ result = df[["a", "a"]].apply(
+ lambda x: x.iloc[0] + x.iloc[1], axis=1, engine=engine
+ )
+ expected = Series([0, 2, 4])
+ tm.assert_series_equal(result, expected)
+
+
+def test_nuisance_depr_passes_through_warnings():
+ # GH 43740
+ # DataFrame.agg with list-likes may emit warnings for both individual
+ # args and for entire columns, but we only want to emit once. We
+ # catch and suppress the warnings for individual args, but need to make
+ # sure if some other warnings were raised, they get passed through to
+ # the user.
+
+ def expected_warning(x):
+ warnings.warn("Hello, World!")
+ return x.sum()
+
+ df = DataFrame({"a": [1, 2, 3]})
+ with tm.assert_produces_warning(UserWarning, match="Hello, World!"):
+ df.agg([expected_warning])
+
+
+def test_apply_type():
+ # GH 46719
+ df = DataFrame(
+ {"col1": [3, "string", float], "col2": [0.25, datetime(2020, 1, 1), np.nan]},
+ index=["a", "b", "c"],
+ )
+
+ # axis=0
+ result = df.apply(type, axis=0)
+ expected = Series({"col1": Series, "col2": Series})
+ tm.assert_series_equal(result, expected)
+
+ # axis=1
+ result = df.apply(type, axis=1)
+ expected = Series({"a": Series, "b": Series, "c": Series})
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_on_empty_dataframe(engine):
+ # GH 39111
+ df = DataFrame({"a": [1, 2], "b": [3, 0]})
+ result = df.head(0).apply(lambda x: max(x["a"], x["b"]), axis=1, engine=engine)
+ expected = Series([], dtype=np.float64)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_return_list():
+ df = DataFrame({"a": [1, 2], "b": [2, 3]})
+ result = df.apply(lambda x: [x.values])
+ expected = DataFrame({"a": [[1, 2]], "b": [[2, 3]]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "test, constant",
+ [
+ ({"a": [1, 2, 3], "b": [1, 1, 1]}, {"a": [1, 2, 3], "b": [1]}),
+ ({"a": [2, 2, 2], "b": [1, 1, 1]}, {"a": [2], "b": [1]}),
+ ],
+)
+def test_unique_agg_type_is_series(test, constant):
+ # GH#22558
+ df1 = DataFrame(test)
+ expected = Series(data=constant, index=["a", "b"], dtype="object")
+ aggregation = {"a": "unique", "b": "unique"}
+
+ result = df1.agg(aggregation)
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_any_apply_keyword_non_zero_axis_regression():
+ # https://github.com/pandas-dev/pandas/issues/48656
+ df = DataFrame({"A": [1, 2, 0], "B": [0, 2, 0], "C": [0, 0, 0]})
+ expected = Series([True, True, False])
+ tm.assert_series_equal(df.any(axis=1), expected)
+
+ result = df.apply("any", axis=1)
+ tm.assert_series_equal(result, expected)
+
+ result = df.apply("any", 1)
+ tm.assert_series_equal(result, expected)
+
+
+def test_agg_mapping_func_deprecated():
+ # GH 53325
+ df = DataFrame({"x": [1, 2, 3]})
+
+ def foo1(x, a=1, c=0):
+ return x + a + c
+
+ def foo2(x, b=2, c=0):
+ return x + b + c
+
+ # single func already takes the vectorized path
+ result = df.agg(foo1, 0, 3, c=4)
+ expected = df + 7
+ tm.assert_frame_equal(result, expected)
+
+ result = df.agg([foo1, foo2], 0, 3, c=4)
+ expected = DataFrame(
+ [[8, 8], [9, 9], [10, 10]], columns=[["x", "x"], ["foo1", "foo2"]]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # TODO: the result below is wrong, should be fixed (GH53325)
+ result = df.agg({"x": foo1}, 0, 3, c=4)
+ expected = DataFrame([2, 3, 4], columns=["x"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_std():
+ df = DataFrame(np.arange(6).reshape(3, 2), columns=["A", "B"])
+
+ result = df.agg(np.std, ddof=1)
+ expected = Series({"A": 2.0, "B": 2.0}, dtype=float)
+ tm.assert_series_equal(result, expected)
+
+ result = df.agg([np.std], ddof=1)
+ expected = DataFrame({"A": 2.0, "B": 2.0}, index=["std"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_np_size():
+ # GH#42203, GH#48328
+ df = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=["A", "B", "C"])
+
+ result = df.agg({"A": [np.size]})
+ expected = DataFrame({"A": [3]}, index=["size"])
+ tm.assert_frame_equal(result, expected)
+
+ result = df.agg({"A": np.size})
+ expected = Series({"A": 3})
+ tm.assert_series_equal(result, expected)
+
+ result = df.agg({"A": [np.mean, np.size]})
+ expected = DataFrame({"A": [4.0, 3.0]}, index=["mean", "size"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_dist_like_and_nonunique_columns():
+ # GH#51099
+ df = DataFrame(
+ {"A": [None, 2, 3], "B": [1.0, np.nan, 3.0], "C": ["foo", None, "bar"]}
+ )
+ df.columns = ["A", "A", "C"]
+
+ result = df.agg({"A": "count"})
+ expected = df["A"].count()
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("engine_name", ["unknown", 25])
+def test_wrong_engine(engine_name):
+ with pytest.raises(ValueError, match="Unknown engine "):
+ DataFrame().apply(lambda x: x, engine=engine_name)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_frame_apply_relabeling.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_frame_apply_relabeling.py
new file mode 100644
index 0000000000000000000000000000000000000000..86918ec09aa97d7db9af0a8655e3273a53b7aad0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_frame_apply_relabeling.py
@@ -0,0 +1,105 @@
+import numpy as np
+
+import pandas as pd
+import pandas._testing as tm
+
+
+def test_agg_relabel():
+ # GH 26513
+ df = pd.DataFrame({"A": [1, 2, 1, 2], "B": [1, 2, 3, 4], "C": [3, 4, 5, 6]})
+
+ # simplest case with one column, one func
+ result = df.agg(foo=("B", "sum"))
+ expected = pd.DataFrame({"B": [10]}, index=pd.Index(["foo"]))
+ tm.assert_frame_equal(result, expected)
+
+ # test on same column with different methods
+ result = df.agg(foo=("B", "sum"), bar=("B", "min"))
+ expected = pd.DataFrame({"B": [10, 1]}, index=pd.Index(["foo", "bar"]))
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_relabel_multi_columns_multi_methods():
+ # GH 26513, test on multiple columns with multiple methods
+ df = pd.DataFrame({"A": [1, 2, 1, 2], "B": [1, 2, 3, 4], "C": [3, 4, 5, 6]})
+ result = df.agg(
+ foo=("A", "sum"),
+ bar=("B", "mean"),
+ cat=("A", "min"),
+ dat=("B", "max"),
+ f=("A", "max"),
+ g=("C", "min"),
+ )
+ expected = pd.DataFrame(
+ {
+ "A": [6.0, np.nan, 1.0, np.nan, 2.0, np.nan],
+ "B": [np.nan, 2.5, np.nan, 4.0, np.nan, np.nan],
+ "C": [np.nan, np.nan, np.nan, np.nan, np.nan, 3.0],
+ },
+ index=pd.Index(["foo", "bar", "cat", "dat", "f", "g"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_relabel_partial_functions():
+ # GH 26513, test on partial, functools or more complex cases
+ df = pd.DataFrame({"A": [1, 2, 1, 2], "B": [1, 2, 3, 4], "C": [3, 4, 5, 6]})
+ result = df.agg(foo=("A", np.mean), bar=("A", "mean"), cat=("A", min))
+ expected = pd.DataFrame(
+ {"A": [1.5, 1.5, 1.0]}, index=pd.Index(["foo", "bar", "cat"])
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.agg(
+ foo=("A", min),
+ bar=("B", np.min),
+ cat=("B", max),
+ dat=("C", "min"),
+ f=("B", np.sum),
+ kk=("B", lambda x: min(x)),
+ )
+ expected = pd.DataFrame(
+ {
+ "A": [1.0, np.nan, np.nan, np.nan, np.nan, np.nan],
+ "B": [np.nan, 1.0, 4.0, np.nan, 10.0, 1.0],
+ "C": [np.nan, np.nan, np.nan, 3.0, np.nan, np.nan],
+ },
+ index=pd.Index(["foo", "bar", "cat", "dat", "f", "kk"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_namedtuple():
+ # GH 26513
+ df = pd.DataFrame({"A": [0, 1], "B": [1, 2]})
+ result = df.agg(
+ foo=pd.NamedAgg("B", "sum"),
+ bar=pd.NamedAgg("B", "min"),
+ cat=pd.NamedAgg(column="B", aggfunc="count"),
+ fft=pd.NamedAgg("B", aggfunc="max"),
+ )
+
+ expected = pd.DataFrame(
+ {"B": [3, 1, 2, 2]}, index=pd.Index(["foo", "bar", "cat", "fft"])
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.agg(
+ foo=pd.NamedAgg("A", "min"),
+ bar=pd.NamedAgg(column="B", aggfunc="max"),
+ cat=pd.NamedAgg(column="A", aggfunc="max"),
+ )
+ expected = pd.DataFrame(
+ {"A": [0.0, np.nan, 1.0], "B": [np.nan, 2.0, np.nan]},
+ index=pd.Index(["foo", "bar", "cat"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_reconstruct_func():
+ # GH 28472, test to ensure reconstruct_func isn't moved;
+ # This method is used by other libraries (e.g. dask)
+ result = pd.core.apply.reconstruct_func("min")
+ expected = (False, "min", None, None)
+ tm.assert_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_frame_transform.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_frame_transform.py
new file mode 100644
index 0000000000000000000000000000000000000000..558d76ae8fdc4b95d46bbe94e15822779bd7c53f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_frame_transform.py
@@ -0,0 +1,264 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+from pandas.tests.apply.common import frame_transform_kernels
+from pandas.tests.frame.common import zip_frames
+
+
+def unpack_obj(obj, klass, axis):
+ """
+ Helper to ensure we have the right type of object for a test parametrized
+ over frame_or_series.
+ """
+ if klass is not DataFrame:
+ obj = obj["A"]
+ if axis != 0:
+ pytest.skip(f"Test is only for DataFrame with axis={axis}")
+ return obj
+
+
+def test_transform_ufunc(axis, float_frame, frame_or_series):
+ # GH 35964
+ obj = unpack_obj(float_frame, frame_or_series, axis)
+
+ with np.errstate(all="ignore"):
+ f_sqrt = np.sqrt(obj)
+
+ # ufunc
+ result = obj.transform(np.sqrt, axis=axis)
+ expected = f_sqrt
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops, names",
+ [
+ ([np.sqrt], ["sqrt"]),
+ ([np.abs, np.sqrt], ["absolute", "sqrt"]),
+ (np.array([np.sqrt]), ["sqrt"]),
+ (np.array([np.abs, np.sqrt]), ["absolute", "sqrt"]),
+ ],
+)
+def test_transform_listlike(axis, float_frame, ops, names):
+ # GH 35964
+ other_axis = 1 if axis in {0, "index"} else 0
+ with np.errstate(all="ignore"):
+ expected = zip_frames([op(float_frame) for op in ops], axis=other_axis)
+ if axis in {0, "index"}:
+ expected.columns = MultiIndex.from_product([float_frame.columns, names])
+ else:
+ expected.index = MultiIndex.from_product([float_frame.index, names])
+ result = float_frame.transform(ops, axis=axis)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("ops", [[], np.array([])])
+def test_transform_empty_listlike(float_frame, ops, frame_or_series):
+ obj = unpack_obj(float_frame, frame_or_series, 0)
+
+ with pytest.raises(ValueError, match="No transform functions were provided"):
+ obj.transform(ops)
+
+
+def test_transform_listlike_func_with_args():
+ # GH 50624
+ df = DataFrame({"x": [1, 2, 3]})
+
+ def foo1(x, a=1, c=0):
+ return x + a + c
+
+ def foo2(x, b=2, c=0):
+ return x + b + c
+
+ msg = r"foo1\(\) got an unexpected keyword argument 'b'"
+ with pytest.raises(TypeError, match=msg):
+ df.transform([foo1, foo2], 0, 3, b=3, c=4)
+
+ result = df.transform([foo1, foo2], 0, 3, c=4)
+ expected = DataFrame(
+ [[8, 8], [9, 9], [10, 10]],
+ columns=MultiIndex.from_tuples([("x", "foo1"), ("x", "foo2")]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("box", [dict, Series])
+def test_transform_dictlike(axis, float_frame, box):
+ # GH 35964
+ if axis in (0, "index"):
+ e = float_frame.columns[0]
+ expected = float_frame[[e]].transform(np.abs)
+ else:
+ e = float_frame.index[0]
+ expected = float_frame.iloc[[0]].transform(np.abs)
+ result = float_frame.transform(box({e: np.abs}), axis=axis)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_dictlike_mixed():
+ # GH 40018 - mix of lists and non-lists in values of a dictionary
+ df = DataFrame({"a": [1, 2], "b": [1, 4], "c": [1, 4]})
+ result = df.transform({"b": ["sqrt", "abs"], "c": "sqrt"})
+ expected = DataFrame(
+ [[1.0, 1, 1.0], [2.0, 4, 2.0]],
+ columns=MultiIndex([("b", "c"), ("sqrt", "abs")], [(0, 0, 1), (0, 1, 0)]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops",
+ [
+ {},
+ {"A": []},
+ {"A": [], "B": "cumsum"},
+ {"A": "cumsum", "B": []},
+ {"A": [], "B": ["cumsum"]},
+ {"A": ["cumsum"], "B": []},
+ ],
+)
+def test_transform_empty_dictlike(float_frame, ops, frame_or_series):
+ obj = unpack_obj(float_frame, frame_or_series, 0)
+
+ with pytest.raises(ValueError, match="No transform functions were provided"):
+ obj.transform(ops)
+
+
+@pytest.mark.parametrize("use_apply", [True, False])
+def test_transform_udf(axis, float_frame, use_apply, frame_or_series):
+ # GH 35964
+ obj = unpack_obj(float_frame, frame_or_series, axis)
+
+ # transform uses UDF either via apply or passing the entire DataFrame
+ def func(x):
+ # transform is using apply iff x is not a DataFrame
+ if use_apply == isinstance(x, frame_or_series):
+ # Force transform to fallback
+ raise ValueError
+ return x + 1
+
+ result = obj.transform(func, axis=axis)
+ expected = obj + 1
+ tm.assert_equal(result, expected)
+
+
+wont_fail = ["ffill", "bfill", "fillna", "pad", "backfill", "shift"]
+frame_kernels_raise = [x for x in frame_transform_kernels if x not in wont_fail]
+
+
+@pytest.mark.parametrize("op", [*frame_kernels_raise, lambda x: x + 1])
+def test_transform_bad_dtype(op, frame_or_series, request):
+ # GH 35964
+ if op == "ngroup":
+ request.applymarker(
+ pytest.mark.xfail(raises=ValueError, reason="ngroup not valid for NDFrame")
+ )
+
+ obj = DataFrame({"A": 3 * [object]}) # DataFrame that will fail on most transforms
+ obj = tm.get_obj(obj, frame_or_series)
+ error = TypeError
+ msg = "|".join(
+ [
+ "not supported between instances of 'type' and 'type'",
+ "unsupported operand type",
+ ]
+ )
+
+ with pytest.raises(error, match=msg):
+ obj.transform(op)
+ with pytest.raises(error, match=msg):
+ obj.transform([op])
+ with pytest.raises(error, match=msg):
+ obj.transform({"A": op})
+ with pytest.raises(error, match=msg):
+ obj.transform({"A": [op]})
+
+
+@pytest.mark.parametrize("op", frame_kernels_raise)
+def test_transform_failure_typeerror(request, op):
+ # GH 35964
+
+ if op == "ngroup":
+ request.applymarker(
+ pytest.mark.xfail(raises=ValueError, reason="ngroup not valid for NDFrame")
+ )
+
+ # Using object makes most transform kernels fail
+ df = DataFrame({"A": 3 * [object], "B": [1, 2, 3]})
+ error = TypeError
+ msg = "|".join(
+ [
+ "not supported between instances of 'type' and 'type'",
+ "unsupported operand type",
+ ]
+ )
+
+ with pytest.raises(error, match=msg):
+ df.transform([op])
+
+ with pytest.raises(error, match=msg):
+ df.transform({"A": op, "B": op})
+
+ with pytest.raises(error, match=msg):
+ df.transform({"A": [op], "B": [op]})
+
+ with pytest.raises(error, match=msg):
+ df.transform({"A": [op, "shift"], "B": [op]})
+
+
+def test_transform_failure_valueerror():
+ # GH 40211
+ def op(x):
+ if np.sum(np.sum(x)) < 10:
+ raise ValueError
+ return x
+
+ df = DataFrame({"A": [1, 2, 3], "B": [400, 500, 600]})
+ msg = "Transform function failed"
+
+ with pytest.raises(ValueError, match=msg):
+ df.transform([op])
+
+ with pytest.raises(ValueError, match=msg):
+ df.transform({"A": op, "B": op})
+
+ with pytest.raises(ValueError, match=msg):
+ df.transform({"A": [op], "B": [op]})
+
+ with pytest.raises(ValueError, match=msg):
+ df.transform({"A": [op, "shift"], "B": [op]})
+
+
+@pytest.mark.parametrize("use_apply", [True, False])
+def test_transform_passes_args(use_apply, frame_or_series):
+ # GH 35964
+ # transform uses UDF either via apply or passing the entire DataFrame
+ expected_args = [1, 2]
+ expected_kwargs = {"c": 3}
+
+ def f(x, a, b, c):
+ # transform is using apply iff x is not a DataFrame
+ if use_apply == isinstance(x, frame_or_series):
+ # Force transform to fallback
+ raise ValueError
+ assert [a, b] == expected_args
+ assert c == expected_kwargs["c"]
+ return x
+
+ frame_or_series([1]).transform(f, 0, *expected_args, **expected_kwargs)
+
+
+def test_transform_empty_dataframe():
+ # https://github.com/pandas-dev/pandas/issues/39636
+ df = DataFrame([], columns=["col1", "col2"])
+ result = df.transform(lambda x: x + 10)
+ tm.assert_frame_equal(result, df)
+
+ result = df["col1"].transform(lambda x: x + 10)
+ tm.assert_series_equal(result, df["col1"])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_invalid_arg.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_invalid_arg.py
new file mode 100644
index 0000000000000000000000000000000000000000..0503bf9166ec7b6c06edf95293cc286140787d60
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_invalid_arg.py
@@ -0,0 +1,375 @@
+# Tests specifically aimed at detecting bad arguments.
+# This file is organized by reason for exception.
+# 1. always invalid argument values
+# 2. missing column(s)
+# 3. incompatible ops/dtype/args/kwargs
+# 4. invalid result shape/type
+# If your test does not fit into one of these categories, add to this list.
+
+from itertools import chain
+import re
+
+import numpy as np
+import pytest
+
+from pandas.errors import SpecificationError
+
+from pandas import (
+ DataFrame,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("result_type", ["foo", 1])
+def test_result_type_error(result_type):
+ # allowed result_type
+ df = DataFrame(
+ np.tile(np.arange(3, dtype="int64"), 6).reshape(6, -1) + 1,
+ columns=["A", "B", "C"],
+ )
+
+ msg = (
+ "invalid value for result_type, must be one of "
+ "{None, 'reduce', 'broadcast', 'expand'}"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.apply(lambda x: [1, 2, 3], axis=1, result_type=result_type)
+
+
+def test_apply_invalid_axis_value():
+ df = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=["a", "a", "c"])
+ msg = "No axis named 2 for object type DataFrame"
+ with pytest.raises(ValueError, match=msg):
+ df.apply(lambda x: x, 2)
+
+
+def test_agg_raises():
+ # GH 26513
+ df = DataFrame({"A": [0, 1], "B": [1, 2]})
+ msg = "Must provide"
+
+ with pytest.raises(TypeError, match=msg):
+ df.agg()
+
+
+def test_map_with_invalid_na_action_raises():
+ # https://github.com/pandas-dev/pandas/issues/32815
+ s = Series([1, 2, 3])
+ msg = "na_action must either be 'ignore' or None"
+ with pytest.raises(ValueError, match=msg):
+ s.map(lambda x: x, na_action="____")
+
+
+@pytest.mark.parametrize("input_na_action", ["____", True])
+def test_map_arg_is_dict_with_invalid_na_action_raises(input_na_action):
+ # https://github.com/pandas-dev/pandas/issues/46588
+ s = Series([1, 2, 3])
+ msg = f"na_action must either be 'ignore' or None, {input_na_action} was passed"
+ with pytest.raises(ValueError, match=msg):
+ s.map({1: 2}, na_action=input_na_action)
+
+
+@pytest.mark.parametrize("method", ["apply", "agg", "transform"])
+@pytest.mark.parametrize("func", [{"A": {"B": "sum"}}, {"A": {"B": ["sum"]}}])
+def test_nested_renamer(frame_or_series, method, func):
+ # GH 35964
+ obj = frame_or_series({"A": [1]})
+ match = "nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=match):
+ getattr(obj, method)(func)
+
+
+@pytest.mark.parametrize(
+ "renamer",
+ [{"foo": ["min", "max"]}, {"foo": ["min", "max"], "bar": ["sum", "mean"]}],
+)
+def test_series_nested_renamer(renamer):
+ s = Series(range(6), dtype="int64", name="series")
+ msg = "nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ s.agg(renamer)
+
+
+def test_apply_dict_depr():
+ tsdf = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 3)),
+ columns=["A", "B", "C"],
+ index=date_range("1/1/2000", periods=10),
+ )
+ msg = "nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ tsdf.A.agg({"foo": ["sum", "mean"]})
+
+
+@pytest.mark.parametrize("method", ["agg", "transform"])
+def test_dict_nested_renaming_depr(method):
+ df = DataFrame({"A": range(5), "B": 5})
+
+ # nested renaming
+ msg = r"nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ getattr(df, method)({"A": {"foo": "min"}, "B": {"bar": "max"}})
+
+
+@pytest.mark.parametrize("method", ["apply", "agg", "transform"])
+@pytest.mark.parametrize("func", [{"B": "sum"}, {"B": ["sum"]}])
+def test_missing_column(method, func):
+ # GH 40004
+ obj = DataFrame({"A": [1]})
+ msg = r"Label\(s\) \['B'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ getattr(obj, method)(func)
+
+
+def test_transform_mixed_column_name_dtypes():
+ # GH39025
+ df = DataFrame({"a": ["1"]})
+ msg = r"Label\(s\) \[1, 'b'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ df.transform({"a": int, 1: str, "b": int})
+
+
+@pytest.mark.parametrize(
+ "how, args", [("pct_change", ()), ("nsmallest", (1, ["a", "b"])), ("tail", 1)]
+)
+def test_apply_str_axis_1_raises(how, args):
+ # GH 39211 - some ops don't support axis=1
+ df = DataFrame({"a": [1, 2], "b": [3, 4]})
+ msg = f"Operation {how} does not support axis=1"
+ with pytest.raises(ValueError, match=msg):
+ df.apply(how, axis=1, args=args)
+
+
+def test_transform_axis_1_raises():
+ # GH 35964
+ msg = "No axis named 1 for object type Series"
+ with pytest.raises(ValueError, match=msg):
+ Series([1]).transform("sum", axis=1)
+
+
+def test_apply_modify_traceback():
+ data = DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "C": [
+ "dull",
+ "dull",
+ "shiny",
+ "dull",
+ "dull",
+ "shiny",
+ "shiny",
+ "dull",
+ "shiny",
+ "shiny",
+ "shiny",
+ ],
+ "D": np.random.default_rng(2).standard_normal(11),
+ "E": np.random.default_rng(2).standard_normal(11),
+ "F": np.random.default_rng(2).standard_normal(11),
+ }
+ )
+
+ data.loc[4, "C"] = np.nan
+
+ def transform(row):
+ if row["C"].startswith("shin") and row["A"] == "foo":
+ row["D"] = 7
+ return row
+
+ msg = "'float' object has no attribute 'startswith'"
+ with pytest.raises(AttributeError, match=msg):
+ data.apply(transform, axis=1)
+
+
+@pytest.mark.parametrize(
+ "df, func, expected",
+ tm.get_cython_table_params(
+ DataFrame([["a", "b"], ["b", "a"]]), [["cumprod", TypeError]]
+ ),
+)
+def test_agg_cython_table_raises_frame(df, func, expected, axis, using_infer_string):
+ # GH 21224
+ if using_infer_string:
+ expected = (expected, NotImplementedError)
+
+ msg = (
+ "can't multiply sequence by non-int of type 'str'"
+ "|cannot perform cumprod with type str" # NotImplementedError python backend
+ "|operation 'cumprod' not supported for dtype 'str'" # TypeError pyarrow
+ )
+ warn = None if isinstance(func, str) else FutureWarning
+ with pytest.raises(expected, match=msg):
+ with tm.assert_produces_warning(warn, match="using DataFrame.cumprod"):
+ df.agg(func, axis=axis)
+
+
+@pytest.mark.parametrize(
+ "series, func, expected",
+ chain(
+ tm.get_cython_table_params(
+ Series("a b c".split()),
+ [
+ ("mean", TypeError), # mean raises TypeError
+ ("prod", TypeError),
+ ("std", TypeError),
+ ("var", TypeError),
+ ("median", TypeError),
+ ("cumprod", TypeError),
+ ],
+ )
+ ),
+)
+def test_agg_cython_table_raises_series(series, func, expected, using_infer_string):
+ # GH21224
+ msg = r"[Cc]ould not convert|can't multiply sequence by non-int of type"
+ if func == "median" or func is np.nanmedian or func is np.median:
+ msg = r"Cannot convert \['a' 'b' 'c'\] to numeric"
+
+ if using_infer_string and func == "cumprod":
+ expected = (expected, NotImplementedError)
+
+ msg = (
+ msg + "|does not support|has no kernel|Cannot perform|cannot perform|operation"
+ )
+ warn = None if isinstance(func, str) else FutureWarning
+
+ with pytest.raises(expected, match=msg):
+ # e.g. Series('a b'.split()).cumprod() will raise
+ with tm.assert_produces_warning(warn, match="is currently using Series.*"):
+ series.agg(func)
+
+
+def test_agg_none_to_type():
+ # GH 40543
+ df = DataFrame({"a": [None]})
+ msg = re.escape("int() argument must be a string")
+ with pytest.raises(TypeError, match=msg):
+ df.agg({"a": lambda x: int(x.iloc[0])})
+
+
+def test_transform_none_to_type():
+ # GH#34377
+ df = DataFrame({"a": [None]})
+ msg = "argument must be a"
+ with pytest.raises(TypeError, match=msg):
+ df.transform({"a": lambda x: int(x.iloc[0])})
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda x: np.array([1, 2]).reshape(-1, 2),
+ lambda x: [1, 2],
+ lambda x: Series([1, 2]),
+ ],
+)
+def test_apply_broadcast_error(func):
+ df = DataFrame(
+ np.tile(np.arange(3, dtype="int64"), 6).reshape(6, -1) + 1,
+ columns=["A", "B", "C"],
+ )
+
+ # > 1 ndim
+ msg = "too many dims to broadcast|cannot broadcast result"
+ with pytest.raises(ValueError, match=msg):
+ df.apply(func, axis=1, result_type="broadcast")
+
+
+def test_transform_and_agg_err_agg(axis, float_frame):
+ # cannot both transform and agg
+ msg = "cannot combine transform and aggregation operations"
+ with pytest.raises(ValueError, match=msg):
+ with np.errstate(all="ignore"):
+ float_frame.agg(["max", "sqrt"], axis=axis)
+
+
+@pytest.mark.filterwarnings("ignore::FutureWarning") # GH53325
+@pytest.mark.parametrize(
+ "func, msg",
+ [
+ (["sqrt", "max"], "cannot combine transform and aggregation"),
+ (
+ {"foo": np.sqrt, "bar": "sum"},
+ "cannot perform both aggregation and transformation",
+ ),
+ ],
+)
+def test_transform_and_agg_err_series(string_series, func, msg):
+ # we are trying to transform with an aggregator
+ with pytest.raises(ValueError, match=msg):
+ with np.errstate(all="ignore"):
+ string_series.agg(func)
+
+
+@pytest.mark.parametrize("func", [["max", "min"], ["max", "sqrt"]])
+def test_transform_wont_agg_frame(axis, float_frame, func):
+ # GH 35964
+ # cannot both transform and agg
+ msg = "Function did not transform"
+ with pytest.raises(ValueError, match=msg):
+ float_frame.transform(func, axis=axis)
+
+
+@pytest.mark.parametrize("func", [["min", "max"], ["sqrt", "max"]])
+def test_transform_wont_agg_series(string_series, func):
+ # GH 35964
+ # we are trying to transform with an aggregator
+ msg = "Function did not transform"
+
+ with pytest.raises(ValueError, match=msg):
+ string_series.transform(func)
+
+
+@pytest.mark.parametrize(
+ "op_wrapper", [lambda x: x, lambda x: [x], lambda x: {"A": x}, lambda x: {"A": [x]}]
+)
+def test_transform_reducer_raises(all_reductions, frame_or_series, op_wrapper):
+ # GH 35964
+ op = op_wrapper(all_reductions)
+
+ obj = DataFrame({"A": [1, 2, 3]})
+ obj = tm.get_obj(obj, frame_or_series)
+
+ msg = "Function did not transform"
+ with pytest.raises(ValueError, match=msg):
+ obj.transform(op)
+
+
+def test_transform_missing_labels_raises():
+ # GH 58474
+ df = DataFrame({"foo": [2, 4, 6], "bar": [1, 2, 3]}, index=["A", "B", "C"])
+ msg = r"Label\(s\) \['A', 'B'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ df.transform({"A": lambda x: x + 2, "B": lambda x: x * 2}, axis=0)
+
+ msg = r"Label\(s\) \['bar', 'foo'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ df.transform({"foo": lambda x: x + 2, "bar": lambda x: x * 2}, axis=1)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_numba.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_numba.py
new file mode 100644
index 0000000000000000000000000000000000000000..75bc3f5b74b9deff5587a6c0b0a3c25a266f9a1e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_numba.py
@@ -0,0 +1,129 @@
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_arm
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+from pandas.util.version import Version
+
+pytestmark = [td.skip_if_no("numba"), pytest.mark.single_cpu, pytest.mark.skipif()]
+
+numba = pytest.importorskip("numba")
+pytestmark.append(
+ pytest.mark.skipif(
+ Version(numba.__version__) == Version("0.61") and is_platform_arm(),
+ reason=f"Segfaults on ARM platforms with numba {numba.__version__}",
+ )
+)
+
+
+@pytest.fixture(params=[0, 1])
+def apply_axis(request):
+ return request.param
+
+
+def test_numba_vs_python_noop(float_frame, apply_axis):
+ func = lambda x: x
+ result = float_frame.apply(func, engine="numba", axis=apply_axis)
+ expected = float_frame.apply(func, engine="python", axis=apply_axis)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_numba_vs_python_string_index():
+ # GH#56189
+ df = DataFrame(
+ 1,
+ index=Index(["a", "b"], dtype=pd.StringDtype(na_value=np.nan)),
+ columns=Index(["x", "y"], dtype=pd.StringDtype(na_value=np.nan)),
+ )
+ func = lambda x: x
+ result = df.apply(func, engine="numba", axis=0)
+ expected = df.apply(func, engine="python", axis=0)
+ tm.assert_frame_equal(
+ result, expected, check_column_type=False, check_index_type=False
+ )
+
+
+def test_numba_vs_python_indexing():
+ frame = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": [7.0, 8.0, 9.0]},
+ index=Index(["A", "B", "C"]),
+ )
+ row_func = lambda x: x["c"]
+ result = frame.apply(row_func, engine="numba", axis=1)
+ expected = frame.apply(row_func, engine="python", axis=1)
+ tm.assert_series_equal(result, expected)
+
+ col_func = lambda x: x["A"]
+ result = frame.apply(col_func, engine="numba", axis=0)
+ expected = frame.apply(col_func, engine="python", axis=0)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "reduction",
+ [lambda x: x.mean(), lambda x: x.min(), lambda x: x.max(), lambda x: x.sum()],
+)
+def test_numba_vs_python_reductions(reduction, apply_axis):
+ df = DataFrame(np.ones((4, 4), dtype=np.float64))
+ result = df.apply(reduction, engine="numba", axis=apply_axis)
+ expected = df.apply(reduction, engine="python", axis=apply_axis)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("colnames", [[1, 2, 3], [1.0, 2.0, 3.0]])
+def test_numba_numeric_colnames(colnames):
+ # Check that numeric column names lower properly and can be indexed on
+ df = DataFrame(
+ np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.int64), columns=colnames
+ )
+ first_col = colnames[0]
+ f = lambda x: x[first_col] # Get the first column
+ result = df.apply(f, engine="numba", axis=1)
+ expected = df.apply(f, engine="python", axis=1)
+ tm.assert_series_equal(result, expected)
+
+
+def test_numba_parallel_unsupported(float_frame):
+ f = lambda x: x
+ with pytest.raises(
+ NotImplementedError,
+ match="Parallel apply is not supported when raw=False and engine='numba'",
+ ):
+ float_frame.apply(f, engine="numba", engine_kwargs={"parallel": True})
+
+
+def test_numba_nonunique_unsupported(apply_axis):
+ f = lambda x: x
+ df = DataFrame({"a": [1, 2]}, index=Index(["a", "a"]))
+ with pytest.raises(
+ NotImplementedError,
+ match="The index/columns must be unique when raw=False and engine='numba'",
+ ):
+ df.apply(f, engine="numba", axis=apply_axis)
+
+
+def test_numba_unsupported_dtypes(apply_axis):
+ pytest.importorskip("pyarrow")
+ f = lambda x: x
+ df = DataFrame({"a": [1, 2], "b": ["a", "b"], "c": [4, 5]})
+ df["c"] = df["c"].astype("double[pyarrow]")
+
+ with pytest.raises(
+ ValueError,
+ match="Column b must have a numeric dtype. Found 'object|str' instead",
+ ):
+ df.apply(f, engine="numba", axis=apply_axis)
+
+ with pytest.raises(
+ ValueError,
+ match="Column c is backed by an extension array, "
+ "which is not supported by the numba engine.",
+ ):
+ df["c"].to_frame().apply(f, engine="numba", axis=apply_axis)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_series_apply.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_series_apply.py
new file mode 100644
index 0000000000000000000000000000000000000000..cea6fb793c0c7b4687bbacc6b57b5e13dd7a2aee
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_series_apply.py
@@ -0,0 +1,669 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ concat,
+ date_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.tests.apply.common import series_transform_kernels
+
+
+@pytest.fixture(params=[False, "compat"])
+def by_row(request):
+ return request.param
+
+
+def test_series_map_box_timedelta(by_row):
+ # GH#11349
+ ser = Series(timedelta_range("1 day 1 s", periods=3, freq="h"))
+
+ def f(x):
+ return x.total_seconds() if by_row else x.dt.total_seconds()
+
+ result = ser.apply(f, by_row=by_row)
+
+ expected = ser.map(lambda x: x.total_seconds())
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([86401.0, 90001.0, 93601.0])
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply(datetime_series, by_row):
+ result = datetime_series.apply(np.sqrt, by_row=by_row)
+ with np.errstate(all="ignore"):
+ expected = np.sqrt(datetime_series)
+ tm.assert_series_equal(result, expected)
+
+ # element-wise apply (ufunc)
+ result = datetime_series.apply(np.exp, by_row=by_row)
+ expected = np.exp(datetime_series)
+ tm.assert_series_equal(result, expected)
+
+ # empty series
+ s = Series(dtype=object, name="foo", index=Index([], name="bar"))
+ rs = s.apply(lambda x: x, by_row=by_row)
+ tm.assert_series_equal(s, rs)
+
+ # check all metadata (GH 9322)
+ assert s is not rs
+ assert s.index is rs.index
+ assert s.dtype == rs.dtype
+ assert s.name == rs.name
+
+ # index but no data
+ s = Series(index=[1, 2, 3], dtype=np.float64)
+ rs = s.apply(lambda x: x, by_row=by_row)
+ tm.assert_series_equal(s, rs)
+
+
+def test_apply_map_same_length_inference_bug():
+ s = Series([1, 2])
+
+ def f(x):
+ return (x, x + 1)
+
+ result = s.apply(f, by_row="compat")
+ expected = s.map(f)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_args():
+ s = Series(["foo,bar"])
+
+ result = s.apply(str.split, args=(",",))
+ assert result[0] == ["foo", "bar"]
+ assert isinstance(result[0], list)
+
+
+@pytest.mark.parametrize(
+ "args, kwargs, increment",
+ [((), {}, 0), ((), {"a": 1}, 1), ((2, 3), {}, 32), ((1,), {"c": 2}, 201)],
+)
+def test_agg_args(args, kwargs, increment):
+ # GH 43357
+ def f(x, a=0, b=0, c=0):
+ return x + a + 10 * b + 100 * c
+
+ s = Series([1, 2])
+ result = s.agg(f, 0, *args, **kwargs)
+ expected = s + increment
+ tm.assert_series_equal(result, expected)
+
+
+def test_agg_mapping_func_deprecated():
+ # GH 53325
+ s = Series([1, 2, 3])
+
+ def foo1(x, a=1, c=0):
+ return x + a + c
+
+ def foo2(x, b=2, c=0):
+ return x + b + c
+
+ s.agg(foo1, 0, 3, c=4)
+ s.agg([foo1, foo2], 0, 3, c=4)
+ s.agg({"a": foo1, "b": foo2}, 0, 3, c=4)
+
+
+def test_series_apply_map_box_timestamps(by_row):
+ # GH#2689, GH#2627
+ ser = Series(date_range("1/1/2000", periods=10))
+
+ def func(x):
+ return (x.hour, x.day, x.month)
+
+ if not by_row:
+ msg = "Series' object has no attribute 'hour'"
+ with pytest.raises(AttributeError, match=msg):
+ ser.apply(func, by_row=by_row)
+ return
+
+ result = ser.apply(func, by_row=by_row)
+ expected = ser.map(func)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_box_dt64():
+ # ufunc will not be boxed. Same test cases as the test_map_box
+ vals = [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")]
+ ser = Series(vals, dtype="M8[ns]")
+ assert ser.dtype == "datetime64[ns]"
+ # boxed value must be Timestamp instance
+ res = ser.apply(lambda x: f"{type(x).__name__}_{x.day}_{x.tz}", by_row="compat")
+ exp = Series(["Timestamp_1_None", "Timestamp_2_None"])
+ tm.assert_series_equal(res, exp)
+
+
+def test_apply_box_dt64tz():
+ vals = [
+ pd.Timestamp("2011-01-01", tz="US/Eastern"),
+ pd.Timestamp("2011-01-02", tz="US/Eastern"),
+ ]
+ ser = Series(vals, dtype="M8[ns, US/Eastern]")
+ assert ser.dtype == "datetime64[ns, US/Eastern]"
+ res = ser.apply(lambda x: f"{type(x).__name__}_{x.day}_{x.tz}", by_row="compat")
+ exp = Series(["Timestamp_1_US/Eastern", "Timestamp_2_US/Eastern"])
+ tm.assert_series_equal(res, exp)
+
+
+def test_apply_box_td64():
+ # timedelta
+ vals = [pd.Timedelta("1 days"), pd.Timedelta("2 days")]
+ ser = Series(vals)
+ assert ser.dtype == "timedelta64[us]"
+ res = ser.apply(lambda x: f"{type(x).__name__}_{x.days}", by_row="compat")
+ exp = Series(["Timedelta_1", "Timedelta_2"])
+ tm.assert_series_equal(res, exp)
+
+
+def test_apply_box_period():
+ # period
+ vals = [pd.Period("2011-01-01", freq="M"), pd.Period("2011-01-02", freq="M")]
+ ser = Series(vals)
+ assert ser.dtype == "Period[M]"
+ res = ser.apply(lambda x: f"{type(x).__name__}_{x.freqstr}", by_row="compat")
+ exp = Series(["Period_M", "Period_M"])
+ tm.assert_series_equal(res, exp)
+
+
+def test_apply_datetimetz(by_row):
+ values = date_range("2011-01-01", "2011-01-02", freq="h").tz_localize("Asia/Tokyo")
+ s = Series(values, name="XX")
+
+ result = s.apply(lambda x: x + pd.offsets.Day(), by_row=by_row)
+ exp_values = date_range("2011-01-02", "2011-01-03", freq="h").tz_localize(
+ "Asia/Tokyo"
+ )
+ exp = Series(exp_values, name="XX")
+ tm.assert_series_equal(result, exp)
+
+ result = s.apply(lambda x: x.hour if by_row else x.dt.hour, by_row=by_row)
+ exp = Series([*list(range(24)), 0], name="XX", dtype="int64" if by_row else "int32")
+ tm.assert_series_equal(result, exp)
+
+ # not vectorized
+ def f(x):
+ return str(x.tz) if by_row else str(x.dt.tz)
+
+ result = s.apply(f, by_row=by_row)
+ if by_row:
+ exp = Series(["Asia/Tokyo"] * 25, name="XX")
+ tm.assert_series_equal(result, exp)
+ else:
+ assert result == "Asia/Tokyo"
+
+
+def test_apply_categorical(by_row, using_infer_string):
+ values = pd.Categorical(list("ABBABCD"), categories=list("DCBA"), ordered=True)
+ ser = Series(values, name="XX", index=list("abcdefg"))
+
+ if not by_row:
+ msg = "Series' object has no attribute 'lower"
+ with pytest.raises(AttributeError, match=msg):
+ ser.apply(lambda x: x.lower(), by_row=by_row)
+ assert ser.apply(lambda x: "A", by_row=by_row) == "A"
+ return
+
+ result = ser.apply(lambda x: x.lower(), by_row=by_row)
+
+ # should be categorical dtype when the number of categories are
+ # the same
+ values = pd.Categorical(list("abbabcd"), categories=list("dcba"), ordered=True)
+ exp = Series(values, name="XX", index=list("abcdefg"))
+ tm.assert_series_equal(result, exp)
+ tm.assert_categorical_equal(result.values, exp.values)
+
+ result = ser.apply(lambda x: "A")
+ exp = Series(["A"] * 7, name="XX", index=list("abcdefg"))
+ tm.assert_series_equal(result, exp)
+ assert result.dtype == object if not using_infer_string else "str"
+
+
+@pytest.mark.parametrize("series", [["1-1", "1-1", np.nan], ["1-1", "1-2", np.nan]])
+def test_apply_categorical_with_nan_values(series, by_row):
+ # GH 20714 bug fixed in: GH 24275
+ s = Series(series, dtype="category")
+ if not by_row:
+ msg = "'Series' object has no attribute 'split'"
+ with pytest.raises(AttributeError, match=msg):
+ s.apply(lambda x: x.split("-")[0], by_row=by_row)
+ return
+ # NaN for cat dtype fixed in (GH 59966)
+ result = s.apply(lambda x: x.split("-")[0] if pd.notna(x) else False, by_row=by_row)
+ result = result.astype(object)
+ expected = Series(["1", "1", False], dtype="category")
+ expected = expected.astype(object)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_empty_integer_series_with_datetime_index(by_row):
+ # GH 21245
+ s = Series([], index=date_range(start="2018-01-01", periods=0), dtype=int)
+ result = s.apply(lambda x: x, by_row=by_row)
+ tm.assert_series_equal(result, s)
+
+
+def test_apply_dataframe_iloc():
+ uintDF = DataFrame(np.uint64([1, 2, 3, 4, 5]), columns=["Numbers"])
+ indexDF = DataFrame([2, 3, 2, 1, 2], columns=["Indices"])
+
+ def retrieve(targetRow, targetDF):
+ val = targetDF["Numbers"].iloc[targetRow]
+ return val
+
+ result = indexDF["Indices"].apply(retrieve, args=(uintDF,))
+ expected = Series([3, 4, 3, 2, 3], name="Indices", dtype="uint64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_transform(string_series, by_row):
+ # transforming functions
+
+ with np.errstate(all="ignore"):
+ f_sqrt = np.sqrt(string_series)
+ f_abs = np.abs(string_series)
+
+ # ufunc
+ result = string_series.apply(np.sqrt, by_row=by_row)
+ expected = f_sqrt.copy()
+ tm.assert_series_equal(result, expected)
+
+ # list-like
+ result = string_series.apply([np.sqrt], by_row=by_row)
+ expected = f_sqrt.to_frame().copy()
+ expected.columns = ["sqrt"]
+ tm.assert_frame_equal(result, expected)
+
+ result = string_series.apply(["sqrt"], by_row=by_row)
+ tm.assert_frame_equal(result, expected)
+
+ # multiple items in list
+ # these are in the order as if we are applying both functions per
+ # series and then concatting
+ expected = concat([f_sqrt, f_abs], axis=1)
+ expected.columns = ["sqrt", "absolute"]
+ result = string_series.apply([np.sqrt, np.abs], by_row=by_row)
+ tm.assert_frame_equal(result, expected)
+
+ # dict, provide renaming
+ expected = concat([f_sqrt, f_abs], axis=1)
+ expected.columns = ["foo", "bar"]
+ expected = expected.unstack().rename("series")
+
+ result = string_series.apply({"foo": np.sqrt, "bar": np.abs}, by_row=by_row)
+ tm.assert_series_equal(result.reindex_like(expected), expected)
+
+
+@pytest.mark.parametrize("op", series_transform_kernels)
+def test_transform_partial_failure(op, request):
+ # GH 35964
+ if op in ("ffill", "bfill", "shift"):
+ request.applymarker(
+ pytest.mark.xfail(reason=f"{op} is successful on any dtype")
+ )
+
+ # Using object makes most transform kernels fail
+ ser = Series(3 * [object])
+
+ if op in ("fillna", "ngroup"):
+ error = ValueError
+ msg = "Transform function failed"
+ else:
+ error = TypeError
+ msg = "|".join(
+ [
+ "not supported between instances of 'type' and 'type'",
+ "unsupported operand type",
+ ]
+ )
+
+ with pytest.raises(error, match=msg):
+ ser.transform([op, "shift"])
+
+ with pytest.raises(error, match=msg):
+ ser.transform({"A": op, "B": "shift"})
+
+ with pytest.raises(error, match=msg):
+ ser.transform({"A": [op], "B": ["shift"]})
+
+ with pytest.raises(error, match=msg):
+ ser.transform({"A": [op, "shift"], "B": [op]})
+
+
+def test_transform_partial_failure_valueerror():
+ # GH 40211
+ def noop(x):
+ return x
+
+ def raising_op(_):
+ raise ValueError
+
+ ser = Series(3 * [object])
+ msg = "Transform function failed"
+
+ with pytest.raises(ValueError, match=msg):
+ ser.transform([noop, raising_op])
+
+ with pytest.raises(ValueError, match=msg):
+ ser.transform({"A": raising_op, "B": noop})
+
+ with pytest.raises(ValueError, match=msg):
+ ser.transform({"A": [raising_op], "B": [noop]})
+
+ with pytest.raises(ValueError, match=msg):
+ ser.transform({"A": [noop, raising_op], "B": [noop]})
+
+
+def test_demo():
+ # demonstration tests
+ s = Series(range(6), dtype="int64", name="series")
+
+ result = s.agg(["min", "max"])
+ expected = Series([0, 5], index=["min", "max"], name="series")
+ tm.assert_series_equal(result, expected)
+
+ result = s.agg({"foo": "min"})
+ expected = Series([0], index=["foo"], name="series")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", [str, lambda x: str(x)])
+def test_apply_map_evaluate_lambdas_the_same(string_series, func, by_row, engine):
+ # test that we are evaluating row-by-row first if by_row="compat"
+ # else vectorized evaluation
+ result = string_series.apply(func, by_row=by_row)
+
+ if by_row:
+ expected = string_series.map(func, engine=engine)
+ tm.assert_series_equal(result, expected)
+ else:
+ assert result == str(string_series)
+
+
+def test_agg_evaluate_lambdas(string_series):
+ # GH53325
+ result = string_series.agg(lambda x: type(x))
+ assert result is Series
+
+ result = string_series.agg(type)
+ assert result is Series
+
+
+@pytest.mark.parametrize("op_name", ["agg", "apply"])
+def test_with_nested_series(datetime_series, op_name):
+ # GH 2316 & GH52123
+ # .agg with a reducer and a transform, what to do
+ result = getattr(datetime_series, op_name)(
+ lambda x: Series([x, x**2], index=["x", "x^2"])
+ )
+ if op_name == "apply":
+ expected = DataFrame({"x": datetime_series, "x^2": datetime_series**2})
+ tm.assert_frame_equal(result, expected)
+ else:
+ expected = Series([datetime_series, datetime_series**2], index=["x", "x^2"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_replicate_describe(string_series):
+ # this also tests a result set that is all scalars
+ expected = string_series.describe()
+ result = string_series.apply(
+ {
+ "count": "count",
+ "mean": "mean",
+ "std": "std",
+ "min": "min",
+ "25%": lambda x: x.quantile(0.25),
+ "50%": "median",
+ "75%": lambda x: x.quantile(0.75),
+ "max": "max",
+ },
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_reduce(string_series):
+ # reductions with named functions
+ result = string_series.agg(["sum", "mean"])
+ expected = Series(
+ [string_series.sum(), string_series.mean()],
+ ["sum", "mean"],
+ name=string_series.name,
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "how, kwds",
+ [("agg", {}), ("apply", {"by_row": "compat"}), ("apply", {"by_row": False})],
+)
+def test_non_callable_aggregates(how, kwds):
+ # test agg using non-callable series attributes
+ # GH 39116 - expand to apply
+ s = Series([1, 2, None])
+
+ # Calling agg w/ just a string arg same as calling s.arg
+ result = getattr(s, how)("size", **kwds)
+ expected = s.size
+ assert result == expected
+
+ # test when mixed w/ callable reducers
+ result = getattr(s, how)(["size", "count", "mean"], **kwds)
+ expected = Series({"size": 3.0, "count": 2.0, "mean": 1.5})
+ tm.assert_series_equal(result, expected)
+
+ result = getattr(s, how)({"size": "size", "count": "count", "mean": "mean"}, **kwds)
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_apply_no_suffix_index(by_row):
+ # GH36189
+ s = Series([4] * 3)
+ result = s.apply(["sum", lambda x: x.sum(), lambda x: x.sum()], by_row=by_row)
+ expected = Series([12, 12, 12], index=["sum", "", ""])
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dti,exp",
+ [
+ (
+ Series([1, 2], index=pd.DatetimeIndex([0, 31536000000])),
+ DataFrame(np.repeat([[1, 2]], 2, axis=0), dtype="int64"),
+ ),
+ (
+ Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ ),
+ DataFrame(np.repeat([[1, 2]], 10, axis=0), dtype="int64"),
+ ),
+ ],
+)
+@pytest.mark.parametrize("aware", [True, False])
+def test_apply_series_on_date_time_index_aware_series(dti, exp, aware):
+ # GH 25959
+ # Calling apply on a localized time series should not cause an error
+ if aware:
+ index = dti.tz_localize("UTC").index
+ else:
+ index = dti.index
+ result = Series(index).apply(lambda x: Series([1, 2]))
+ tm.assert_frame_equal(result, exp)
+
+
+@pytest.mark.parametrize(
+ "by_row, expected", [("compat", Series(np.ones(10), dtype="int64")), (False, 1)]
+)
+def test_apply_scalar_on_date_time_index_aware_series(by_row, expected):
+ # GH 25959
+ # Calling apply on a localized time series should not cause an error
+ series = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10, tz="UTC"),
+ )
+ result = Series(series.index).apply(lambda x: 1, by_row=by_row)
+ tm.assert_equal(result, expected)
+
+
+def test_apply_to_timedelta(by_row):
+ list_of_valid_strings = ["00:00:01", "00:00:02"]
+ a = pd.to_timedelta(list_of_valid_strings)
+ b = Series(list_of_valid_strings).apply(pd.to_timedelta, by_row=by_row)
+ tm.assert_series_equal(Series(a), b)
+
+ list_of_strings = ["00:00:01", np.nan, pd.NaT, pd.NaT]
+
+ a = pd.to_timedelta(list_of_strings)
+ ser = Series(list_of_strings)
+ b = ser.apply(pd.to_timedelta, by_row=by_row)
+ tm.assert_series_equal(Series(a), b)
+
+
+@pytest.mark.parametrize(
+ "ops, names",
+ [
+ ([np.sum], ["sum"]),
+ ([np.sum, np.mean], ["sum", "mean"]),
+ (np.array([np.sum]), ["sum"]),
+ (np.array([np.sum, np.mean]), ["sum", "mean"]),
+ ],
+)
+@pytest.mark.parametrize(
+ "how, kwargs",
+ [["agg", {}], ["apply", {"by_row": "compat"}], ["apply", {"by_row": False}]],
+)
+def test_apply_listlike_reducer(string_series, ops, names, how, kwargs):
+ # GH 39140
+ expected = Series(
+ {name: op(string_series) for name, op in zip(names, ops, strict=True)}
+ )
+ expected.name = "series"
+ result = getattr(string_series, how)(ops, **kwargs)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops",
+ [
+ {"A": np.sum},
+ {"A": np.sum, "B": np.mean},
+ Series({"A": np.sum}),
+ Series({"A": np.sum, "B": np.mean}),
+ ],
+)
+@pytest.mark.parametrize(
+ "how, kwargs",
+ [["agg", {}], ["apply", {"by_row": "compat"}], ["apply", {"by_row": False}]],
+)
+def test_apply_dictlike_reducer(string_series, ops, how, kwargs, by_row):
+ # GH 39140
+ expected = Series({name: op(string_series) for name, op in ops.items()})
+ expected.name = string_series.name
+ result = getattr(string_series, how)(ops, **kwargs)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops, names",
+ [
+ ([np.sqrt], ["sqrt"]),
+ ([np.abs, np.sqrt], ["absolute", "sqrt"]),
+ (np.array([np.sqrt]), ["sqrt"]),
+ (np.array([np.abs, np.sqrt]), ["absolute", "sqrt"]),
+ ],
+)
+def test_apply_listlike_transformer(string_series, ops, names, by_row):
+ # GH 39140
+ with np.errstate(all="ignore"):
+ expected = concat([op(string_series) for op in ops], axis=1)
+ expected.columns = names
+ result = string_series.apply(ops, by_row=by_row)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops, expected",
+ [
+ ([lambda x: x], DataFrame({"": [1, 2, 3]})),
+ ([lambda x: x.sum()], Series([6], index=[""])),
+ ],
+)
+def test_apply_listlike_lambda(ops, expected, by_row):
+ # GH53400
+ ser = Series([1, 2, 3])
+ result = ser.apply(ops, by_row=by_row)
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops",
+ [
+ {"A": np.sqrt},
+ {"A": np.sqrt, "B": np.exp},
+ Series({"A": np.sqrt}),
+ Series({"A": np.sqrt, "B": np.exp}),
+ ],
+)
+def test_apply_dictlike_transformer(string_series, ops, by_row):
+ # GH 39140
+ with np.errstate(all="ignore"):
+ expected = concat({name: op(string_series) for name, op in ops.items()})
+ expected.name = string_series.name
+ result = string_series.apply(ops, by_row=by_row)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops, expected",
+ [
+ (
+ {"a": lambda x: x},
+ Series([1, 2, 3], index=MultiIndex.from_arrays([["a"] * 3, range(3)])),
+ ),
+ ({"a": lambda x: x.sum()}, Series([6], index=["a"])),
+ ],
+)
+def test_apply_dictlike_lambda(ops, by_row, expected):
+ # GH53400
+ ser = Series([1, 2, 3])
+ result = ser.apply(ops, by_row=by_row)
+ tm.assert_equal(result, expected)
+
+
+def test_apply_retains_column_name(by_row):
+ # GH 16380
+ df = DataFrame({"x": range(3)}, Index(range(3), name="x"))
+ result = df.x.apply(lambda x: Series(range(x + 1), Index(range(x + 1), name="y")))
+ expected = DataFrame(
+ [[0.0, np.nan, np.nan], [0.0, 1.0, np.nan], [0.0, 1.0, 2.0]],
+ columns=Index(range(3), name="y"),
+ index=Index(range(3), name="x"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_type():
+ # GH 46719
+ s = Series([3, "string", float], index=["a", "b", "c"])
+ result = s.apply(type)
+ expected = Series([int, str, type], index=["a", "b", "c"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_apply_unpack_nested_data():
+ # GH#55189
+ ser = Series([[1, 2, 3], [4, 5, 6, 7]])
+ result = ser.apply(lambda x: Series(x))
+ expected = DataFrame({0: [1.0, 4.0], 1: [2.0, 5.0], 2: [3.0, 6.0], 3: [np.nan, 7]})
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_series_apply_relabeling.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_series_apply_relabeling.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0a285e6eb38cc26da155755108ef2c814229384
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_series_apply_relabeling.py
@@ -0,0 +1,33 @@
+import pandas as pd
+import pandas._testing as tm
+
+
+def test_relabel_no_duplicated_method():
+ # this is to test there is no duplicated method used in agg
+ df = pd.DataFrame({"A": [1, 2, 1, 2], "B": [1, 2, 3, 4]})
+
+ result = df["A"].agg(foo="sum")
+ expected = df["A"].agg({"foo": "sum"})
+ tm.assert_series_equal(result, expected)
+
+ result = df["B"].agg(foo="min", bar="max")
+ expected = df["B"].agg({"foo": "min", "bar": "max"})
+ tm.assert_series_equal(result, expected)
+
+ result = df["B"].agg(foo=sum, bar=min, cat="max")
+ expected = df["B"].agg({"foo": sum, "bar": min, "cat": "max"})
+ tm.assert_series_equal(result, expected)
+
+
+def test_relabel_duplicated_method():
+ # this is to test with nested renaming, duplicated method can be used
+ # if they are assigned with different new names
+ df = pd.DataFrame({"A": [1, 2, 1, 2], "B": [1, 2, 3, 4]})
+
+ result = df["A"].agg(foo="sum", bar="sum")
+ expected = pd.Series([6, 6], index=["foo", "bar"], name="A")
+ tm.assert_series_equal(result, expected)
+
+ result = df["B"].agg(foo=min, bar="min")
+ expected = pd.Series([1, 1], index=["foo", "bar"], name="B")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_series_transform.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_series_transform.py
new file mode 100644
index 0000000000000000000000000000000000000000..82592c4711ece5a7f4b6d421d743e1adbd78c345
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_series_transform.py
@@ -0,0 +1,84 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Series,
+ concat,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "args, kwargs, increment",
+ [((), {}, 0), ((), {"a": 1}, 1), ((2, 3), {}, 32), ((1,), {"c": 2}, 201)],
+)
+def test_agg_args(args, kwargs, increment):
+ # GH 43357
+ def f(x, a=0, b=0, c=0):
+ return x + a + 10 * b + 100 * c
+
+ s = Series([1, 2])
+ result = s.transform(f, 0, *args, **kwargs)
+ expected = s + increment
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ops, names",
+ [
+ ([np.sqrt], ["sqrt"]),
+ ([np.abs, np.sqrt], ["absolute", "sqrt"]),
+ (np.array([np.sqrt]), ["sqrt"]),
+ (np.array([np.abs, np.sqrt]), ["absolute", "sqrt"]),
+ ],
+)
+def test_transform_listlike(string_series, ops, names):
+ # GH 35964
+ with np.errstate(all="ignore"):
+ expected = concat([op(string_series) for op in ops], axis=1)
+ expected.columns = names
+ result = string_series.transform(ops)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_listlike_func_with_args():
+ # GH 50624
+
+ s = Series([1, 2, 3])
+
+ def foo1(x, a=1, c=0):
+ return x + a + c
+
+ def foo2(x, b=2, c=0):
+ return x + b + c
+
+ msg = r"foo1\(\) got an unexpected keyword argument 'b'"
+ with pytest.raises(TypeError, match=msg):
+ s.transform([foo1, foo2], 0, 3, b=3, c=4)
+
+ result = s.transform([foo1, foo2], 0, 3, c=4)
+ expected = DataFrame({"foo1": [8, 9, 10], "foo2": [8, 9, 10]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("box", [dict, Series])
+def test_transform_dictlike(string_series, box):
+ # GH 35964
+ with np.errstate(all="ignore"):
+ expected = concat([np.sqrt(string_series), np.abs(string_series)], axis=1)
+ expected.columns = ["foo", "bar"]
+ result = string_series.transform(box({"foo": np.sqrt, "bar": np.abs}))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_dictlike_mixed():
+ # GH 40018 - mix of lists and non-lists in values of a dictionary
+ df = Series([1, 4])
+ result = df.transform({"b": ["sqrt", "abs"], "c": "sqrt"})
+ expected = DataFrame(
+ [[1.0, 1, 1.0], [2.0, 4, 2.0]],
+ columns=MultiIndex([("b", "c"), ("sqrt", "abs")], [(0, 0, 1), (0, 1, 0)]),
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_str.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_str.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5a9492630b13a8ac03e976a699f8e58752887f2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/apply/test_str.py
@@ -0,0 +1,307 @@
+from itertools import chain
+import operator
+
+import numpy as np
+import pytest
+
+from pandas.compat import (
+ WASM,
+)
+
+from pandas.core.dtypes.common import is_number
+
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+from pandas.tests.apply.common import (
+ frame_transform_kernels,
+ series_transform_kernels,
+)
+
+
+@pytest.mark.parametrize("func", ["sum", "mean", "min", "max", "std"])
+@pytest.mark.parametrize(
+ "kwds",
+ [
+ pytest.param({}, id="no_kwds"),
+ pytest.param({"axis": 1}, id="on_axis"),
+ pytest.param({"numeric_only": True}, id="func_kwds"),
+ pytest.param({"axis": 1, "numeric_only": True}, id="axis_and_func_kwds"),
+ ],
+)
+@pytest.mark.parametrize("how", ["agg", "apply"])
+def test_apply_with_string_funcs(float_frame, func, kwds, how):
+ result = getattr(float_frame, how)(func, **kwds)
+ expected = getattr(float_frame, func)(**kwds)
+ tm.assert_series_equal(result, expected)
+
+
+def test_with_string_args(datetime_series, all_numeric_reductions):
+ result = datetime_series.apply(all_numeric_reductions)
+ expected = getattr(datetime_series, all_numeric_reductions)()
+ assert result == expected
+
+
+@pytest.mark.parametrize("op", ["mean", "median", "std", "var"])
+@pytest.mark.parametrize("how", ["agg", "apply"])
+def test_apply_np_reducer(op, how):
+ # GH 39116
+ float_frame = DataFrame({"a": [1, 2], "b": [3, 4]})
+ result = getattr(float_frame, how)(op)
+ # pandas ddof defaults to 1, numpy to 0
+ kwargs = {"ddof": 1} if op in ("std", "var") else {}
+ expected = Series(
+ getattr(np, op)(float_frame, axis=0, **kwargs), index=float_frame.columns
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.skipif(WASM, reason="No fp exception support in wasm")
+@pytest.mark.parametrize(
+ "op", ["abs", "ceil", "cos", "cumsum", "exp", "log", "sqrt", "square"]
+)
+@pytest.mark.parametrize("how", ["transform", "apply"])
+def test_apply_np_transformer(float_frame, op, how):
+ # GH 39116
+
+ # float_frame will _usually_ have negative values, which will
+ # trigger the warning here, but let's put one in just to be sure
+ float_frame.iloc[0, 0] = -1.0
+ warn = None
+ if op in ["log", "sqrt"]:
+ warn = RuntimeWarning
+
+ with tm.assert_produces_warning(warn, check_stacklevel=False):
+ # float_frame fixture is defined in conftest.py, so we don't check the
+ # stacklevel as otherwise the test would fail.
+ result = getattr(float_frame, how)(op)
+ expected = getattr(np, op)(float_frame)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "series, func, expected",
+ chain(
+ tm.get_cython_table_params(
+ Series(dtype=np.float64),
+ [
+ ("sum", 0),
+ ("max", np.nan),
+ ("min", np.nan),
+ ("all", True),
+ ("any", False),
+ ("mean", np.nan),
+ ("prod", 1),
+ ("std", np.nan),
+ ("var", np.nan),
+ ("median", np.nan),
+ ],
+ ),
+ tm.get_cython_table_params(
+ Series([np.nan, 1, 2, 3]),
+ [
+ ("sum", 6),
+ ("max", 3),
+ ("min", 1),
+ ("all", True),
+ ("any", True),
+ ("mean", 2),
+ ("prod", 6),
+ ("std", 1),
+ ("var", 1),
+ ("median", 2),
+ ],
+ ),
+ tm.get_cython_table_params(
+ Series("a b c".split()),
+ [
+ ("sum", "abc"),
+ ("max", "c"),
+ ("min", "a"),
+ ("all", True),
+ ("any", True),
+ ],
+ ),
+ ),
+)
+def test_agg_cython_table_series(series, func, expected):
+ # GH21224
+ # test reducing functions in
+ # pandas.core.base.SelectionMixin._cython_table
+ warn = None if isinstance(func, str) else FutureWarning
+ with tm.assert_produces_warning(warn, match="is currently using Series.*"):
+ result = series.agg(func)
+ if is_number(expected):
+ assert np.isclose(result, expected, equal_nan=True)
+ else:
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "series, func, expected",
+ chain(
+ tm.get_cython_table_params(
+ Series(dtype=np.float64),
+ [
+ ("cumprod", Series([], dtype=np.float64)),
+ ("cumsum", Series([], dtype=np.float64)),
+ ],
+ ),
+ tm.get_cython_table_params(
+ Series([np.nan, 1, 2, 3]),
+ [
+ ("cumprod", Series([np.nan, 1, 2, 6])),
+ ("cumsum", Series([np.nan, 1, 3, 6])),
+ ],
+ ),
+ tm.get_cython_table_params(
+ Series("a b c".split()), [("cumsum", Series(["a", "ab", "abc"]))]
+ ),
+ ),
+)
+def test_agg_cython_table_transform_series(series, func, expected):
+ # GH21224
+ # test transforming functions in
+ # pandas.core.base.SelectionMixin._cython_table (cumprod, cumsum)
+ warn = None if isinstance(func, str) else FutureWarning
+ with tm.assert_produces_warning(warn, match="is currently using Series.*"):
+ result = series.agg(func)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "df, func, expected",
+ chain(
+ tm.get_cython_table_params(
+ DataFrame(),
+ [
+ ("sum", Series(dtype="float64")),
+ ("max", Series(dtype="float64")),
+ ("min", Series(dtype="float64")),
+ ("all", Series(dtype=bool)),
+ ("any", Series(dtype=bool)),
+ ("mean", Series(dtype="float64")),
+ ("prod", Series(dtype="float64")),
+ ("std", Series(dtype="float64")),
+ ("var", Series(dtype="float64")),
+ ("median", Series(dtype="float64")),
+ ],
+ ),
+ tm.get_cython_table_params(
+ DataFrame([[np.nan, 1], [1, 2]]),
+ [
+ ("sum", Series([1.0, 3])),
+ ("max", Series([1.0, 2])),
+ ("min", Series([1.0, 1])),
+ ("all", Series([True, True])),
+ ("any", Series([True, True])),
+ ("mean", Series([1, 1.5])),
+ ("prod", Series([1.0, 2])),
+ ("std", Series([np.nan, 0.707107])),
+ ("var", Series([np.nan, 0.5])),
+ ("median", Series([1, 1.5])),
+ ],
+ ),
+ ),
+)
+def test_agg_cython_table_frame(df, func, expected, axis):
+ # GH 21224
+ # test reducing functions in
+ # pandas.core.base.SelectionMixin._cython_table
+ warn = None if isinstance(func, str) else FutureWarning
+ with tm.assert_produces_warning(warn, match="is currently using DataFrame.*"):
+ # GH#53425
+ result = df.agg(func, axis=axis)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "df, func, expected",
+ chain(
+ tm.get_cython_table_params(
+ DataFrame(), [("cumprod", DataFrame()), ("cumsum", DataFrame())]
+ ),
+ tm.get_cython_table_params(
+ DataFrame([[np.nan, 1], [1, 2]]),
+ [
+ ("cumprod", DataFrame([[np.nan, 1], [1, 2]])),
+ ("cumsum", DataFrame([[np.nan, 1], [1, 3]])),
+ ],
+ ),
+ ),
+)
+def test_agg_cython_table_transform_frame(df, func, expected, axis):
+ # GH 21224
+ # test transforming functions in
+ # pandas.core.base.SelectionMixin._cython_table (cumprod, cumsum)
+ if axis in ("columns", 1):
+ # operating blockwise doesn't let us preserve dtypes
+ expected = expected.astype("float64")
+
+ warn = None if isinstance(func, str) else FutureWarning
+ with tm.assert_produces_warning(warn, match="is currently using DataFrame.*"):
+ # GH#53425
+ result = df.agg(func, axis=axis)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("op", series_transform_kernels)
+def test_transform_groupby_kernel_series(request, string_series, op):
+ # GH 35964
+ if op == "ngroup":
+ request.applymarker(
+ pytest.mark.xfail(raises=ValueError, reason="ngroup not valid for NDFrame")
+ )
+ args = [0.0] if op == "fillna" else []
+ ones = np.ones(string_series.shape[0])
+
+ warn = FutureWarning if op == "fillna" else None
+ msg = "SeriesGroupBy.fillna is deprecated"
+ with tm.assert_produces_warning(warn, match=msg):
+ expected = string_series.groupby(ones).transform(op, *args)
+ result = string_series.transform(op, 0, *args)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("op", frame_transform_kernels)
+def test_transform_groupby_kernel_frame(request, float_frame, op):
+ if op == "ngroup":
+ request.applymarker(
+ pytest.mark.xfail(raises=ValueError, reason="ngroup not valid for NDFrame")
+ )
+
+ # GH 35964
+
+ args = [0.0] if op == "fillna" else []
+ ones = np.ones(float_frame.shape[0])
+ gb = float_frame.groupby(ones)
+
+ warn = FutureWarning if op == "fillna" else None
+ op_msg = "DataFrameGroupBy.fillna is deprecated"
+ with tm.assert_produces_warning(warn, match=op_msg):
+ expected = gb.transform(op, *args)
+
+ result = float_frame.transform(op, 0, *args)
+ tm.assert_frame_equal(result, expected)
+
+ # same thing, but ensuring we have multiple blocks
+ assert "E" not in float_frame.columns
+ float_frame["E"] = float_frame["A"].copy()
+ assert len(float_frame._mgr.blocks) > 1
+
+ ones = np.ones(float_frame.shape[0])
+ gb2 = float_frame.groupby(ones)
+ expected2 = gb2.transform(op, *args)
+ result2 = float_frame.transform(op, 0, *args)
+ tm.assert_frame_equal(result2, expected2)
+
+
+@pytest.mark.parametrize("method", ["abs", "shift", "pct_change", "cumsum", "rank"])
+def test_transform_method_name(method):
+ # GH 19760
+ df = DataFrame({"A": [-1, 2]})
+ result = df.transform(method)
+ expected = operator.methodcaller(method)(df)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ea9d2b0ee23ad14168a4366e332e1d49d3c0c85
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/common.py
@@ -0,0 +1,158 @@
+"""
+Assertion helpers for arithmetic tests.
+"""
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ array,
+)
+import pandas._testing as tm
+from pandas.core.arrays import (
+ BooleanArray,
+ NumpyExtensionArray,
+)
+
+
+def assert_cannot_add(left, right, msg="cannot add"):
+ """
+ Helper function to assert that two objects cannot be added.
+
+ Parameters
+ ----------
+ left : object
+ The first operand.
+ right : object
+ The second operand.
+ msg : str, default "cannot add"
+ The error message expected in the TypeError.
+ """
+ with pytest.raises(TypeError, match=msg):
+ left + right
+ with pytest.raises(TypeError, match=msg):
+ right + left
+
+
+def assert_invalid_addsub_type(left, right, msg=None):
+ """
+ Helper function to assert that two objects can
+ neither be added nor subtracted.
+
+ Parameters
+ ----------
+ left : object
+ The first operand.
+ right : object
+ The second operand.
+ msg : str or None, default None
+ The error message expected in the TypeError.
+ """
+ with pytest.raises(TypeError, match=msg):
+ left + right
+ with pytest.raises(TypeError, match=msg):
+ right + left
+ with pytest.raises(TypeError, match=msg):
+ left - right
+ with pytest.raises(TypeError, match=msg):
+ right - left
+
+
+def get_upcast_box(left, right, is_cmp: bool = False):
+ """
+ Get the box to use for 'expected' in an arithmetic or comparison operation.
+
+ Parameters
+ left : Any
+ right : Any
+ is_cmp : bool, default False
+ Whether the operation is a comparison method.
+ """
+
+ if isinstance(left, DataFrame) or isinstance(right, DataFrame):
+ return DataFrame
+ if isinstance(left, Series) or isinstance(right, Series):
+ if is_cmp and isinstance(left, Index):
+ # Index does not defer for comparisons
+ return np.array
+ return Series
+ if isinstance(left, Index) or isinstance(right, Index):
+ if is_cmp:
+ return np.array
+ return Index
+ return tm.to_array
+
+
+def assert_invalid_comparison(left, right, box):
+ """
+ Assert that comparison operations with mismatched types behave correctly.
+
+ Parameters
+ ----------
+ left : np.ndarray, ExtensionArray, Index, or Series
+ right : object
+ box : {pd.DataFrame, pd.Series, pd.Index, pd.array, tm.to_array}
+ """
+ # Not for tznaive-tzaware comparison
+
+ # Note: not quite the same as how we do this for tm.box_expected
+ xbox = box if box not in [Index, array] else np.array
+
+ def xbox2(x):
+ # Eventually we'd like this to be tighter, but for now we'll
+ # just exclude NumpyExtensionArray[bool]
+ if isinstance(x, NumpyExtensionArray):
+ return x._ndarray
+ if isinstance(x, BooleanArray):
+ # NB: we are assuming no pd.NAs for now
+ return x.astype(bool)
+ return x
+
+ result = xbox2(left == right)
+ expected = xbox(np.zeros(result.shape, dtype=np.bool_))
+
+ tm.assert_equal(result, expected)
+
+ result = xbox2(right == left)
+ tm.assert_equal(result, xbox(expected))
+
+ result = xbox2(left != right)
+ tm.assert_equal(result, ~expected)
+
+ result = xbox2(right != left)
+ tm.assert_equal(result, xbox(~expected))
+
+ msg = "|".join(
+ [
+ "Invalid comparison between",
+ "Cannot compare type",
+ "not supported between",
+ "invalid type promotion",
+ (
+ # GH#36706 npdev 1.20.0 2020-09-28
+ r"The DTypes and "
+ r" do not have a common DType. "
+ "For example they cannot be stored in a single array unless the "
+ "dtype is `object`."
+ ),
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ left < right
+ with pytest.raises(TypeError, match=msg):
+ left <= right
+ with pytest.raises(TypeError, match=msg):
+ left > right
+ with pytest.raises(TypeError, match=msg):
+ left >= right
+ with pytest.raises(TypeError, match=msg):
+ right < left
+ with pytest.raises(TypeError, match=msg):
+ right <= left
+ with pytest.raises(TypeError, match=msg):
+ right > left
+ with pytest.raises(TypeError, match=msg):
+ right >= left
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..44838394f2183dd7f18c4a960ff60a9f5e3f29cc
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/conftest.py
@@ -0,0 +1,139 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import Index
+
+
+@pytest.fixture(params=[1, np.array(1, dtype=np.int64)])
+def one(request):
+ """
+ Several variants of integer value 1. The zero-dim integer array
+ behaves like an integer.
+
+ This fixture can be used to check that datetimelike indexes handle
+ addition and subtraction of integers and zero-dimensional arrays
+ of integers.
+
+ Examples
+ --------
+ dti = pd.date_range('2016-01-01', periods=2, freq='h')
+ dti
+ DatetimeIndex(['2016-01-01 00:00:00', '2016-01-01 01:00:00'],
+ dtype='datetime64[ns]', freq='h')
+ dti + one
+ DatetimeIndex(['2016-01-01 01:00:00', '2016-01-01 02:00:00'],
+ dtype='datetime64[ns]', freq='h')
+ """
+ return request.param
+
+
+zeros = [
+ box_cls([0] * 5, dtype=dtype)
+ for box_cls in [Index, np.array, pd.array]
+ for dtype in [np.int64, np.uint64, np.float64]
+]
+zeros.extend([box_cls([-0.0] * 5, dtype=np.float64) for box_cls in [Index, np.array]])
+zeros.extend([np.array(0, dtype=dtype) for dtype in [np.int64, np.uint64, np.float64]])
+zeros.extend([np.array(-0.0, dtype=np.float64)])
+zeros.extend([0, 0.0, -0.0])
+
+
+@pytest.fixture(params=zeros)
+def zero(request):
+ """
+ Several types of scalar zeros and length 5 vectors of zeros.
+
+ This fixture can be used to check that numeric-dtype indexes handle
+ division by any zero numeric-dtype.
+
+ Uses vector of length 5 for broadcasting with `numeric_idx` fixture,
+ which creates numeric-dtype vectors also of length 5.
+
+ Examples
+ --------
+ arr = RangeIndex(5)
+ arr / zeros
+ Index([nan, inf, inf, inf, inf], dtype='float64')
+ """
+ return request.param
+
+
+# ------------------------------------------------------------------
+# Scalar Fixtures
+
+
+@pytest.fixture(
+ params=[
+ pd.Timedelta("10m7s").to_pytimedelta(),
+ pd.Timedelta("10m7s"),
+ pd.Timedelta("10m7s").to_timedelta64(),
+ ],
+ ids=lambda x: type(x).__name__,
+)
+def scalar_td(request):
+ """
+ Several variants of Timedelta scalars representing 10 minutes and 7 seconds.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ pd.offsets.Day(3),
+ pd.offsets.Hour(72),
+ pd.Timedelta(days=3).to_pytimedelta(),
+ pd.Timedelta("72:00:00"),
+ np.timedelta64(3, "D"),
+ np.timedelta64(72, "h"),
+ ],
+ ids=lambda x: type(x).__name__,
+)
+def three_days(request):
+ """
+ Several timedelta-like and DateOffset objects that each represent
+ a 3-day timedelta
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ pd.offsets.Hour(2),
+ pd.offsets.Minute(120),
+ pd.Timedelta(hours=2).to_pytimedelta(),
+ pd.Timedelta(seconds=2 * 3600),
+ np.timedelta64(2, "h"),
+ np.timedelta64(120, "m"),
+ ],
+ ids=lambda x: type(x).__name__,
+)
+def two_hours(request):
+ """
+ Several timedelta-like and DateOffset objects that each represent
+ a 2-hour timedelta
+ """
+ return request.param
+
+
+_common_mismatch = [
+ pd.offsets.YearBegin(2),
+ pd.offsets.MonthBegin(1),
+ pd.offsets.Minute(),
+]
+
+
+@pytest.fixture(
+ params=[
+ np.timedelta64(4, "h"),
+ pd.Timedelta(hours=23).to_pytimedelta(),
+ pd.Timedelta("23:00:00"),
+ *_common_mismatch,
+ ]
+)
+def not_daily(request):
+ """
+ Several timedelta-like and DateOffset instances that are _not_
+ compatible with Daily frequencies.
+ """
+ return request.param
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_array_ops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_array_ops.py
new file mode 100644
index 0000000000000000000000000000000000000000..f026092e0d1133ea861f9cdb2699924e3618298e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_array_ops.py
@@ -0,0 +1,78 @@
+import operator
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.missing import isna
+
+import pandas._testing as tm
+from pandas.core.ops.array_ops import (
+ comparison_op,
+ na_logical_op,
+)
+
+
+def test_na_logical_op_2d():
+ left = np.arange(8).reshape(4, 2)
+ right = left.astype(object)
+ right[0, 0] = np.nan
+
+ # Check that we fall back to the vec_binop branch
+ with pytest.raises(TypeError, match="unsupported operand type"):
+ operator.or_(left, right)
+
+ result = na_logical_op(left, right, operator.or_)
+ expected = right
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_object_comparison_2d():
+ left = np.arange(9).reshape(3, 3).astype(object)
+ right = left.T
+
+ result = comparison_op(left, right, operator.eq)
+ expected = np.eye(3).astype(bool)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # Ensure that cython doesn't raise on non-writeable arg, which
+ # we can get from np.broadcast_to
+ right.flags.writeable = False
+ result = comparison_op(left, right, operator.ne)
+ tm.assert_numpy_array_equal(result, ~expected)
+
+
+@pytest.mark.parametrize("rvalues", [1, [1, 1, 1], np.nan, None])
+@pytest.mark.parametrize(
+ "op", [operator.eq, operator.ne, operator.lt, operator.le, operator.gt, operator.ge]
+)
+def test_comparison_for_subclasses(rvalues, op):
+ # GH#63205 Ensure subclasses of ndarray are correctly handled in comparison_op
+ # Define a custom ndarray subclass
+ class TestArray(np.ndarray):
+ def __new__(cls, input_array):
+ return np.asarray(input_array).view(cls)
+
+ def __array_finalize__(self, obj) -> None:
+ self._is_test_array = True
+
+ def expected_with_na_handling(lvalues, rvalues, op):
+ # Similar to comparison_op, handle zerodim arrays with na value separately
+ if (rvalues.ndim == 0) and isna(rvalues.item()):
+ # numpy does not like comparisons vs None
+ if op is operator.ne:
+ return np.ones(lvalues.shape, dtype=bool)
+ else:
+ return np.zeros(lvalues.shape, dtype=bool)
+ return op(lvalues, rvalues)
+
+ # Define test data
+ lvalues = [1, 2, 3]
+
+ # Test with both ndarray and TestArray
+ result = comparison_op(np.array(lvalues), np.array(rvalues), op)
+ expected = expected_with_na_handling(np.array(lvalues), np.array(rvalues), op)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = comparison_op(TestArray(lvalues), TestArray(rvalues), op)
+ expected = expected_with_na_handling(TestArray(lvalues), TestArray(rvalues), op)
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_bool.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_bool.py
new file mode 100644
index 0000000000000000000000000000000000000000..3723b7042a3ce77bcf21d34c77fff01ed31eceb4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_bool.py
@@ -0,0 +1,28 @@
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+
+def test_divmod_bool_raises(box_with_array):
+ # GH#46043 // raises, so divmod should too
+ ser = Series([True, False])
+ obj = tm.box_expected(ser, box_with_array)
+
+ msg = "operator 'floordiv' not implemented for bool dtypes"
+ with pytest.raises(NotImplementedError, match=msg):
+ obj // obj
+
+ if box_with_array is DataFrame:
+ msg = "operator 'floordiv' not implemented for bool dtypes"
+ else:
+ msg = "operator 'divmod' not implemented for bool dtypes"
+ with pytest.raises(NotImplementedError, match=msg):
+ divmod(obj, obj)
+
+ # go through __rdivmod__
+ with pytest.raises(NotImplementedError, match=msg):
+ divmod(True, obj)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_categorical.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_categorical.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6f3a13ce670596a12ca10b9e8d02d69d63c96fb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_categorical.py
@@ -0,0 +1,25 @@
+import numpy as np
+
+from pandas import (
+ Categorical,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestCategoricalComparisons:
+ def test_categorical_nan_equality(self):
+ cat = Series(Categorical(["a", "b", "c", np.nan]))
+ expected = Series([True, True, True, False])
+ result = cat == cat
+ tm.assert_series_equal(result, expected)
+
+ def test_categorical_tuple_equality(self):
+ # GH 18050
+ ser = Series([(0, 0), (0, 1), (0, 0), (1, 0), (1, 1)])
+ expected = Series([True, False, True, False, False])
+ result = ser == (0, 0)
+ tm.assert_series_equal(result, expected)
+
+ result = ser.astype("category") == (0, 0)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_datetime64.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_datetime64.py
new file mode 100644
index 0000000000000000000000000000000000000000..05d0a9c0626af83b3eb43a683a94a0229376ff88
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_datetime64.py
@@ -0,0 +1,2500 @@
+# Arithmetic tests for DataFrame/Series/Index/Array classes that should
+# behave identically.
+# Specifically for datetime64 and datetime64tz dtypes
+from datetime import (
+ datetime,
+ time,
+ timedelta,
+ timezone,
+)
+from itertools import (
+ product,
+)
+import operator
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs.conversion import localize_pydatetime
+from pandas._libs.tslibs.offsets import shift_months
+
+import pandas as pd
+from pandas import (
+ DateOffset,
+ DatetimeIndex,
+ NaT,
+ Period,
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.core import roperator
+from pandas.tests.arithmetic.common import (
+ assert_cannot_add,
+ assert_invalid_addsub_type,
+ assert_invalid_comparison,
+ get_upcast_box,
+)
+
+# ------------------------------------------------------------------
+# Comparisons
+
+
+class TestDatetime64ArrayLikeComparisons:
+ # Comparison tests for datetime64 vectors fully parametrized over
+ # DataFrame/Series/DatetimeIndex/DatetimeArray. Ideally all comparison
+ # tests will eventually end up here.
+
+ def test_compare_zerodim(self, tz_naive_fixture, box_with_array):
+ # Test comparison with zero-dimensional array is unboxed
+ tz = tz_naive_fixture
+ box = box_with_array
+ dti = date_range("20130101", periods=3, tz=tz)
+
+ other = np.array(dti.to_numpy()[0])
+
+ dtarr = tm.box_expected(dti, box)
+ xbox = get_upcast_box(dtarr, other, True)
+ result = dtarr <= other
+ expected = np.array([True, False, False])
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ "foo",
+ -1,
+ 99,
+ 4.0,
+ object(),
+ timedelta(days=2),
+ # GH#19800, GH#19301 datetime.date comparison raises to
+ # match DatetimeIndex/Timestamp. This also matches the behavior
+ # of stdlib datetime.datetime
+ datetime(2001, 1, 1).date(),
+ # GH#19301 None and NaN are *not* cast to NaT for comparisons
+ None,
+ np.nan,
+ ],
+ )
+ def test_dt64arr_cmp_scalar_invalid(self, other, tz_naive_fixture, box_with_array):
+ # GH#22074, GH#15966
+ tz = tz_naive_fixture
+
+ rng = date_range("1/1/2000", periods=10, tz=tz)
+ dtarr = tm.box_expected(rng, box_with_array)
+ assert_invalid_comparison(dtarr, other, box_with_array)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ # GH#4968 invalid date/int comparisons
+ list(range(10)),
+ np.arange(10),
+ np.arange(10).astype(np.float32),
+ np.arange(10).astype(object),
+ pd.timedelta_range("1ns", periods=10).array,
+ np.array(pd.timedelta_range("1ns", periods=10)),
+ list(pd.timedelta_range("1ns", periods=10)),
+ pd.timedelta_range("1 Day", periods=10).astype(object),
+ pd.period_range("1971-01-01", freq="D", periods=10).array,
+ pd.period_range("1971-01-01", freq="D", periods=10).astype(object),
+ ],
+ )
+ def test_dt64arr_cmp_arraylike_invalid(
+ self, other, tz_naive_fixture, box_with_array
+ ):
+ tz = tz_naive_fixture
+
+ dta = date_range("1970-01-01", freq="ns", periods=10, tz=tz)._data
+ obj = tm.box_expected(dta, box_with_array)
+ assert_invalid_comparison(obj, other, box_with_array)
+
+ def test_dt64arr_cmp_mixed_invalid(self, tz_naive_fixture):
+ tz = tz_naive_fixture
+
+ dta = date_range("1970-01-01", freq="h", periods=5, tz=tz)._data
+
+ other = np.array([0, 1, 2, dta[3], Timedelta(days=1)])
+ result = dta == other
+ expected = np.array([False, False, False, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = dta != other
+ tm.assert_numpy_array_equal(result, ~expected)
+
+ msg = "Invalid comparison between|Cannot compare type|not supported between"
+ with pytest.raises(TypeError, match=msg):
+ dta < other
+ with pytest.raises(TypeError, match=msg):
+ dta > other
+ with pytest.raises(TypeError, match=msg):
+ dta <= other
+ with pytest.raises(TypeError, match=msg):
+ dta >= other
+
+ def test_dt64arr_nat_comparison(self, tz_naive_fixture, box_with_array):
+ # GH#22242, GH#22163 DataFrame considered NaT == ts incorrectly
+ tz = tz_naive_fixture
+ box = box_with_array
+
+ ts = Timestamp("2021-01-01", tz=tz)
+ ser = Series([ts, NaT])
+
+ obj = tm.box_expected(ser, box)
+ xbox = get_upcast_box(obj, ts, True)
+
+ expected = Series([True, False], dtype=np.bool_)
+ expected = tm.box_expected(expected, xbox)
+
+ result = obj == ts
+ tm.assert_equal(result, expected)
+
+
+class TestDatetime64SeriesComparison:
+ # TODO: moved from tests.series.test_operators; needs cleanup
+
+ @pytest.mark.parametrize(
+ "pair",
+ [
+ (
+ [Timestamp("2011-01-01"), NaT, Timestamp("2011-01-03")],
+ [NaT, NaT, Timestamp("2011-01-03")],
+ ),
+ (
+ [Timedelta("1 days"), NaT, Timedelta("3 days")],
+ [NaT, NaT, Timedelta("3 days")],
+ ),
+ (
+ [Period("2011-01", freq="M"), NaT, Period("2011-03", freq="M")],
+ [NaT, NaT, Period("2011-03", freq="M")],
+ ),
+ ],
+ )
+ @pytest.mark.parametrize("reverse", [True, False])
+ @pytest.mark.parametrize("dtype", [None, object])
+ @pytest.mark.parametrize(
+ "op, expected",
+ [
+ (operator.eq, [False, False, True]),
+ (operator.ne, [True, True, False]),
+ (operator.lt, [False, False, False]),
+ (operator.gt, [False, False, False]),
+ (operator.ge, [False, False, True]),
+ (operator.le, [False, False, True]),
+ ],
+ )
+ def test_nat_comparisons(
+ self,
+ dtype,
+ index_or_series,
+ reverse,
+ pair,
+ op,
+ expected,
+ ):
+ box = index_or_series
+ lhs, rhs = pair
+ if reverse:
+ # add lhs / rhs switched data
+ lhs, rhs = rhs, lhs
+
+ left = Series(lhs, dtype=dtype)
+ right = box(rhs, dtype=dtype)
+
+ result = op(left, right)
+
+ tm.assert_series_equal(result, Series(expected))
+
+ @pytest.mark.parametrize(
+ "data",
+ [
+ [Timestamp("2011-01-01"), NaT, Timestamp("2011-01-03")],
+ [Timedelta("1 days"), NaT, Timedelta("3 days")],
+ [Period("2011-01", freq="M"), NaT, Period("2011-03", freq="M")],
+ ],
+ )
+ @pytest.mark.parametrize("dtype", [None, object])
+ def test_nat_comparisons_scalar(self, dtype, data, box_with_array):
+ box = box_with_array
+
+ left = Series(data, dtype=dtype)
+ left = tm.box_expected(left, box)
+ xbox = get_upcast_box(left, NaT, True)
+
+ expected = [False, False, False]
+ expected = tm.box_expected(expected, xbox)
+ if box is pd.array and dtype is object:
+ expected = pd.array(expected, dtype="bool")
+
+ tm.assert_equal(left == NaT, expected)
+ tm.assert_equal(NaT == left, expected)
+
+ expected = [True, True, True]
+ expected = tm.box_expected(expected, xbox)
+ if box is pd.array and dtype is object:
+ expected = pd.array(expected, dtype="bool")
+ tm.assert_equal(left != NaT, expected)
+ tm.assert_equal(NaT != left, expected)
+
+ expected = [False, False, False]
+ expected = tm.box_expected(expected, xbox)
+ if box is pd.array and dtype is object:
+ expected = pd.array(expected, dtype="bool")
+ tm.assert_equal(left < NaT, expected)
+ tm.assert_equal(NaT > left, expected)
+ tm.assert_equal(left <= NaT, expected)
+ tm.assert_equal(NaT >= left, expected)
+
+ tm.assert_equal(left > NaT, expected)
+ tm.assert_equal(NaT < left, expected)
+ tm.assert_equal(left >= NaT, expected)
+ tm.assert_equal(NaT <= left, expected)
+
+ @pytest.mark.parametrize("val", [datetime(2000, 1, 4), datetime(2000, 1, 5)])
+ def test_series_comparison_scalars(self, val):
+ series = Series(date_range("1/1/2000", periods=10))
+
+ result = series > val
+ expected = Series([x > val for x in series])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "left,right", [("lt", "gt"), ("le", "ge"), ("eq", "eq"), ("ne", "ne")]
+ )
+ def test_timestamp_compare_series(self, left, right):
+ # see gh-4982
+ # Make sure we can compare Timestamps on the right AND left hand side.
+ ser = Series(date_range("20010101", periods=10), name="dates")
+ s_nat = ser.copy(deep=True)
+
+ ser[0] = Timestamp("nat")
+ ser[3] = Timestamp("nat")
+
+ left_f = getattr(operator, left)
+ right_f = getattr(operator, right)
+
+ # No NaT
+ expected = left_f(ser, Timestamp("20010109"))
+ result = right_f(Timestamp("20010109"), ser)
+ tm.assert_series_equal(result, expected)
+
+ # NaT
+ expected = left_f(ser, Timestamp("nat"))
+ result = right_f(Timestamp("nat"), ser)
+ tm.assert_series_equal(result, expected)
+
+ # Compare to Timestamp with series containing NaT
+ expected = left_f(s_nat, Timestamp("20010109"))
+ result = right_f(Timestamp("20010109"), s_nat)
+ tm.assert_series_equal(result, expected)
+
+ # Compare to NaT with series containing NaT
+ expected = left_f(s_nat, NaT)
+ result = right_f(NaT, s_nat)
+ tm.assert_series_equal(result, expected)
+
+ def test_dt64arr_timestamp_equality(self, box_with_array):
+ # GH#11034
+ box = box_with_array
+
+ ser = Series([Timestamp("2000-01-29 01:59:00"), Timestamp("2000-01-30"), NaT])
+ ser = tm.box_expected(ser, box)
+ xbox = get_upcast_box(ser, ser, True)
+
+ result = ser != ser
+ expected = tm.box_expected([False, False, True], xbox)
+ tm.assert_equal(result, expected)
+
+ if box is pd.DataFrame:
+ # alignment for frame vs series comparisons deprecated
+ # in GH#46795 enforced 2.0
+ with pytest.raises(ValueError, match="not aligned"):
+ ser != ser[0]
+
+ else:
+ result = ser != ser[0]
+ expected = tm.box_expected([False, True, True], xbox)
+ tm.assert_equal(result, expected)
+
+ if box is pd.DataFrame:
+ # alignment for frame vs series comparisons deprecated
+ # in GH#46795 enforced 2.0
+ with pytest.raises(ValueError, match="not aligned"):
+ ser != ser[2]
+ else:
+ result = ser != ser[2]
+ expected = tm.box_expected([True, True, True], xbox)
+ tm.assert_equal(result, expected)
+
+ result = ser == ser
+ expected = tm.box_expected([True, True, False], xbox)
+ tm.assert_equal(result, expected)
+
+ if box is pd.DataFrame:
+ # alignment for frame vs series comparisons deprecated
+ # in GH#46795 enforced 2.0
+ with pytest.raises(ValueError, match="not aligned"):
+ ser == ser[0]
+ else:
+ result = ser == ser[0]
+ expected = tm.box_expected([True, False, False], xbox)
+ tm.assert_equal(result, expected)
+
+ if box is pd.DataFrame:
+ # alignment for frame vs series comparisons deprecated
+ # in GH#46795 enforced 2.0
+ with pytest.raises(ValueError, match="not aligned"):
+ ser == ser[2]
+ else:
+ result = ser == ser[2]
+ expected = tm.box_expected([False, False, False], xbox)
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "datetimelike",
+ [
+ Timestamp("20130101"),
+ datetime(2013, 1, 1),
+ np.datetime64("2013-01-01T00:00", "ns"),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "op,expected",
+ [
+ (operator.lt, [True, False, False, False]),
+ (operator.le, [True, True, False, False]),
+ (operator.eq, [False, True, False, False]),
+ (operator.gt, [False, False, False, True]),
+ ],
+ )
+ def test_dt64_compare_datetime_scalar(self, datetimelike, op, expected):
+ # GH#17965, test for ability to compare datetime64[ns] columns
+ # to datetimelike
+ ser = Series(
+ [
+ Timestamp("20120101"),
+ Timestamp("20130101"),
+ np.nan,
+ Timestamp("20130103"),
+ ],
+ name="A",
+ )
+ result = op(ser, datetimelike)
+ expected = Series(expected, name="A")
+ tm.assert_series_equal(result, expected)
+
+ def test_ts_series_numpy_maximum(self):
+ # GH#50864, test numpy.maximum does not fail
+ # given a TimeStamp and Series(with dtype datetime64) comparison
+ ts = Timestamp("2024-07-01")
+ ts_series = Series(
+ ["2024-06-01", "2024-07-01", "2024-08-01"],
+ dtype="datetime64[us]",
+ )
+
+ expected = Series(
+ ["2024-07-01", "2024-07-01", "2024-08-01"],
+ dtype="datetime64[us]",
+ )
+
+ tm.assert_series_equal(expected, np.maximum(ts, ts_series))
+
+
+class TestDatetimeIndexComparisons:
+ # TODO: moved from tests.indexes.test_base; parametrize and de-duplicate
+ def test_comparators(self, comparison_op):
+ index = date_range("2020-01-01", periods=10)
+ element = index[len(index) // 2]
+ element = Timestamp(element).to_datetime64()
+
+ arr = np.array(index)
+ arr_result = comparison_op(arr, element)
+ index_result = comparison_op(index, element)
+
+ assert isinstance(index_result, np.ndarray)
+ tm.assert_numpy_array_equal(arr_result, index_result)
+
+ @pytest.mark.parametrize(
+ "other",
+ [datetime(2016, 1, 1), Timestamp("2016-01-01"), np.datetime64("2016-01-01")],
+ )
+ def test_dti_cmp_datetimelike(self, other, tz_naive_fixture):
+ tz = tz_naive_fixture
+ dti = date_range("2016-01-01", periods=2, tz=tz)
+ if tz is not None:
+ if isinstance(other, np.datetime64):
+ pytest.skip(f"{type(other).__name__} is not tz aware")
+ other = localize_pydatetime(other, dti.tzinfo)
+
+ result = dti == other
+ expected = np.array([True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = dti > other
+ expected = np.array([False, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = dti >= other
+ expected = np.array([True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = dti < other
+ expected = np.array([False, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = dti <= other
+ expected = np.array([True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", [None, object])
+ def test_dti_cmp_nat(self, dtype, box_with_array):
+ left = DatetimeIndex([Timestamp("2011-01-01"), NaT, Timestamp("2011-01-03")])
+ right = DatetimeIndex([NaT, NaT, Timestamp("2011-01-03")])
+
+ left = tm.box_expected(left, box_with_array)
+ right = tm.box_expected(right, box_with_array)
+ xbox = get_upcast_box(left, right, True)
+
+ lhs, rhs = left, right
+ if dtype is object:
+ lhs, rhs = left.astype(object), right.astype(object)
+
+ result = rhs == lhs
+ expected = np.array([False, False, True])
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(result, expected)
+
+ result = lhs != rhs
+ expected = np.array([True, True, False])
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(result, expected)
+
+ expected = np.array([False, False, False])
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(lhs == NaT, expected)
+ tm.assert_equal(NaT == rhs, expected)
+
+ expected = np.array([True, True, True])
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(lhs != NaT, expected)
+ tm.assert_equal(NaT != lhs, expected)
+
+ expected = np.array([False, False, False])
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(lhs < NaT, expected)
+ tm.assert_equal(NaT > lhs, expected)
+
+ def test_dti_cmp_nat_behaves_like_float_cmp_nan(self):
+ fidx1 = pd.Index([1.0, np.nan, 3.0, np.nan, 5.0, 7.0])
+ fidx2 = pd.Index([2.0, 3.0, np.nan, np.nan, 6.0, 7.0])
+
+ didx1 = DatetimeIndex(
+ ["2014-01-01", NaT, "2014-03-01", NaT, "2014-05-01", "2014-07-01"]
+ )
+ didx2 = DatetimeIndex(
+ ["2014-02-01", "2014-03-01", NaT, NaT, "2014-06-01", "2014-07-01"]
+ )
+ darr = np.array(
+ [
+ np.datetime64("2014-02-01 00:00"),
+ np.datetime64("2014-03-01 00:00"),
+ np.datetime64("nat"),
+ np.datetime64("nat"),
+ np.datetime64("2014-06-01 00:00"),
+ np.datetime64("2014-07-01 00:00"),
+ ]
+ )
+
+ cases = [(fidx1, fidx2), (didx1, didx2), (didx1, darr)]
+
+ # Check pd.NaT is handles as the same as np.nan
+ with tm.assert_produces_warning(None):
+ for idx1, idx2 in cases:
+ result = idx1 < idx2
+ expected = np.array([True, False, False, False, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx2 > idx1
+ expected = np.array([True, False, False, False, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 <= idx2
+ expected = np.array([True, False, False, False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx2 >= idx1
+ expected = np.array([True, False, False, False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 == idx2
+ expected = np.array([False, False, False, False, False, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 != idx2
+ expected = np.array([True, True, True, True, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ with tm.assert_produces_warning(None):
+ for idx1, val in [(fidx1, np.nan), (didx1, NaT)]:
+ result = idx1 < val
+ expected = np.array([False, False, False, False, False, False])
+ tm.assert_numpy_array_equal(result, expected)
+ result = idx1 > val
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 <= val
+ tm.assert_numpy_array_equal(result, expected)
+ result = idx1 >= val
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 == val
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 != val
+ expected = np.array([True, True, True, True, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ # Check pd.NaT is handles as the same as np.nan
+ with tm.assert_produces_warning(None):
+ for idx1, val in [(fidx1, 3), (didx1, datetime(2014, 3, 1))]:
+ result = idx1 < val
+ expected = np.array([True, False, False, False, False, False])
+ tm.assert_numpy_array_equal(result, expected)
+ result = idx1 > val
+ expected = np.array([False, False, False, False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 <= val
+ expected = np.array([True, False, True, False, False, False])
+ tm.assert_numpy_array_equal(result, expected)
+ result = idx1 >= val
+ expected = np.array([False, False, True, False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 == val
+ expected = np.array([False, False, True, False, False, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 != val
+ expected = np.array([True, True, False, True, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_comparison_tzawareness_compat(self, comparison_op, box_with_array):
+ # GH#18162
+ op = comparison_op
+ box = box_with_array
+
+ dr = date_range("2016-01-01", periods=6, unit="ns")
+ dz = dr.tz_localize("US/Pacific")
+
+ dr = tm.box_expected(dr, box)
+ dz = tm.box_expected(dz, box)
+
+ if box is pd.DataFrame:
+ tolist = lambda x: x.astype(object).values.tolist()[0]
+ else:
+ tolist = list
+
+ if op not in [operator.eq, operator.ne]:
+ msg = (
+ r"Invalid comparison between dtype=datetime64\[ns.*\] "
+ "and (Timestamp|DatetimeArray|list|ndarray)"
+ )
+ with pytest.raises(TypeError, match=msg):
+ op(dr, dz)
+
+ with pytest.raises(TypeError, match=msg):
+ op(dr, tolist(dz))
+ with pytest.raises(TypeError, match=msg):
+ op(dr, np.array(tolist(dz), dtype=object))
+ with pytest.raises(TypeError, match=msg):
+ op(dz, dr)
+
+ with pytest.raises(TypeError, match=msg):
+ op(dz, tolist(dr))
+ with pytest.raises(TypeError, match=msg):
+ op(dz, np.array(tolist(dr), dtype=object))
+
+ # The aware==aware and naive==naive comparisons should *not* raise
+ assert np.all(dr == dr)
+ assert np.all(dr == tolist(dr))
+ assert np.all(tolist(dr) == dr)
+ assert np.all(np.array(tolist(dr), dtype=object) == dr)
+ assert np.all(dr == np.array(tolist(dr), dtype=object))
+
+ assert np.all(dz == dz)
+ assert np.all(dz == tolist(dz))
+ assert np.all(tolist(dz) == dz)
+ assert np.all(np.array(tolist(dz), dtype=object) == dz)
+ assert np.all(dz == np.array(tolist(dz), dtype=object))
+
+ def test_comparison_tzawareness_compat_scalars(self, comparison_op, box_with_array):
+ # GH#18162
+ op = comparison_op
+
+ dr = date_range("2016-01-01", periods=6, unit="ns")
+ dz = dr.tz_localize("US/Pacific")
+
+ dr = tm.box_expected(dr, box_with_array)
+ dz = tm.box_expected(dz, box_with_array)
+
+ # Check comparisons against scalar Timestamps
+ ts = Timestamp("2000-03-14 01:59")
+ ts_tz = Timestamp("2000-03-14 01:59", tz="Europe/Amsterdam")
+
+ assert np.all(dr > ts)
+ msg = r"Invalid comparison between dtype=datetime64\[ns.*\] and Timestamp"
+ if op not in [operator.eq, operator.ne]:
+ with pytest.raises(TypeError, match=msg):
+ op(dr, ts_tz)
+
+ assert np.all(dz > ts_tz)
+ if op not in [operator.eq, operator.ne]:
+ with pytest.raises(TypeError, match=msg):
+ op(dz, ts)
+
+ if op not in [operator.eq, operator.ne]:
+ # GH#12601: Check comparison against Timestamps and DatetimeIndex
+ with pytest.raises(TypeError, match=msg):
+ op(ts, dz)
+
+ @pytest.mark.parametrize(
+ "other",
+ [datetime(2016, 1, 1), Timestamp("2016-01-01"), np.datetime64("2016-01-01")],
+ )
+ # Bug in NumPy? https://github.com/numpy/numpy/issues/13841
+ # Raising in __eq__ will fallback to NumPy, which warns, fails,
+ # then re-raises the original exception. So we just need to ignore.
+ @pytest.mark.filterwarnings("ignore:elementwise comp:DeprecationWarning")
+ def test_scalar_comparison_tzawareness(
+ self, comparison_op, other, tz_aware_fixture, box_with_array
+ ):
+ op = comparison_op
+ tz = tz_aware_fixture
+ dti = date_range("2016-01-01", periods=2, tz=tz, unit="ns")
+
+ dtarr = tm.box_expected(dti, box_with_array)
+ xbox = get_upcast_box(dtarr, other, True)
+ if op in [operator.eq, operator.ne]:
+ exbool = op is operator.ne
+ expected = np.array([exbool, exbool], dtype=bool)
+ expected = tm.box_expected(expected, xbox)
+
+ result = op(dtarr, other)
+ tm.assert_equal(result, expected)
+
+ result = op(other, dtarr)
+ tm.assert_equal(result, expected)
+ else:
+ msg = (
+ r"Invalid comparison between dtype=datetime64\[ns, .*\] "
+ f"and {type(other).__name__}"
+ )
+ with pytest.raises(TypeError, match=msg):
+ op(dtarr, other)
+ with pytest.raises(TypeError, match=msg):
+ op(other, dtarr)
+
+ def test_nat_comparison_tzawareness(self, comparison_op):
+ # GH#19276
+ # tzaware DatetimeIndex should not raise when compared to NaT
+ op = comparison_op
+
+ dti = DatetimeIndex(
+ ["2014-01-01", NaT, "2014-03-01", NaT, "2014-05-01", "2014-07-01"]
+ )
+ expected = np.array([op == operator.ne] * len(dti))
+ result = op(dti, NaT)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = op(dti.tz_localize("US/Pacific"), NaT)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_dti_cmp_str(self, tz_naive_fixture):
+ # GH#22074
+ # regardless of tz, we expect these comparisons are valid
+ tz = tz_naive_fixture
+ rng = date_range("1/1/2000", periods=10, tz=tz)
+ other = "1/1/2000"
+
+ result = rng == other
+ expected = np.array([True] + [False] * 9)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = rng != other
+ expected = np.array([False] + [True] * 9)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = rng < other
+ expected = np.array([False] * 10)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = rng <= other
+ expected = np.array([True] + [False] * 9)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = rng > other
+ expected = np.array([False] + [True] * 9)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = rng >= other
+ expected = np.array([True] * 10)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_dti_cmp_list(self):
+ rng = date_range("1/1/2000", periods=10)
+
+ result = rng == list(rng)
+ expected = rng == rng
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ pd.timedelta_range("1D", periods=10),
+ pd.timedelta_range("1D", periods=10).to_series(),
+ pd.timedelta_range("1D", periods=10).asi8.view("m8[ns]"),
+ ],
+ ids=lambda x: type(x).__name__,
+ )
+ def test_dti_cmp_tdi_tzawareness(self, other):
+ # GH#22074
+ # reversion test that we _don't_ call _assert_tzawareness_compat
+ # when comparing against TimedeltaIndex
+ dti = date_range("2000-01-01", periods=10, tz="Asia/Tokyo")
+
+ result = dti == other
+ expected = np.array([False] * 10)
+ if isinstance(other, Series):
+ tm.assert_series_equal(result, Series(expected, index=other.index))
+ else:
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = dti != other
+ expected = np.array([True] * 10)
+ if isinstance(other, Series):
+ tm.assert_series_equal(result, Series(expected, index=other.index))
+ else:
+ tm.assert_numpy_array_equal(result, expected)
+
+ msg = "Invalid comparison between"
+ with pytest.raises(TypeError, match=msg):
+ dti < other
+ with pytest.raises(TypeError, match=msg):
+ dti <= other
+ with pytest.raises(TypeError, match=msg):
+ dti > other
+ with pytest.raises(TypeError, match=msg):
+ dti >= other
+
+ def test_dti_cmp_object_dtype(self):
+ # GH#22074
+ dti = date_range("2000-01-01", periods=10, tz="Asia/Tokyo")
+
+ other = dti.astype("O")
+
+ result = dti == other
+ expected = np.array([True] * 10)
+ tm.assert_numpy_array_equal(result, expected)
+
+ other = dti.tz_localize(None)
+ result = dti != other
+ tm.assert_numpy_array_equal(result, expected)
+
+ other = np.array(list(dti[:5]) + [Timedelta(days=1)] * 5)
+ result = dti == other
+ expected = np.array([True] * 5 + [False] * 5)
+ tm.assert_numpy_array_equal(result, expected)
+ msg = ">=' not supported between instances of 'Timestamp' and 'Timedelta'"
+ with pytest.raises(TypeError, match=msg):
+ dti >= other
+
+
+# ------------------------------------------------------------------
+# Arithmetic
+
+
+class TestDatetime64Arithmetic:
+ # This class is intended for "finished" tests that are fully parametrized
+ # over DataFrame/Series/Index/DatetimeArray
+
+ # -------------------------------------------------------------
+ # Addition/Subtraction of timedelta-like
+
+ @pytest.mark.arm_slow
+ def test_dt64arr_add_timedeltalike_scalar(
+ self, tz_naive_fixture, two_hours, box_with_array
+ ):
+ # GH#22005, GH#22163 check DataFrame doesn't raise TypeError
+ tz = tz_naive_fixture
+
+ rng = date_range("2000-01-01", "2000-02-01", tz=tz, unit="ns")
+ expected = date_range("2000-01-01 02:00", "2000-02-01 02:00", tz=tz, unit="ns")
+ if tz is not None:
+ expected = expected._with_freq(None)
+
+ rng = tm.box_expected(rng, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = rng + two_hours
+ tm.assert_equal(result, expected)
+
+ result = two_hours + rng
+ tm.assert_equal(result, expected)
+
+ rng += two_hours
+ tm.assert_equal(rng, expected)
+
+ def test_dt64arr_sub_timedeltalike_scalar(
+ self, tz_naive_fixture, two_hours, box_with_array
+ ):
+ tz = tz_naive_fixture
+
+ rng = date_range("2000-01-01", "2000-02-01", tz=tz, unit="ns")
+ expected = date_range("1999-12-31 22:00", "2000-01-31 22:00", tz=tz, unit="ns")
+ if tz is not None:
+ expected = expected._with_freq(None)
+
+ rng = tm.box_expected(rng, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = rng - two_hours
+ tm.assert_equal(result, expected)
+
+ rng -= two_hours
+ tm.assert_equal(rng, expected)
+
+ def test_dt64_array_sub_dt_with_different_timezone(self, box_with_array):
+ t1 = date_range("20130101", periods=3).tz_localize("US/Eastern")
+ t1 = tm.box_expected(t1, box_with_array)
+ t2 = Timestamp("20130101").tz_localize("CET")
+ tnaive = Timestamp(20130101)
+
+ result = t1 - t2
+ expected = TimedeltaIndex(
+ ["0 days 06:00:00", "1 days 06:00:00", "2 days 06:00:00"]
+ )
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ result = t2 - t1
+ expected = TimedeltaIndex(
+ ["-1 days +18:00:00", "-2 days +18:00:00", "-3 days +18:00:00"]
+ )
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ msg = "Cannot subtract tz-naive and tz-aware datetime-like objects"
+ with pytest.raises(TypeError, match=msg):
+ t1 - tnaive
+
+ with pytest.raises(TypeError, match=msg):
+ tnaive - t1
+
+ def test_dt64_array_sub_dt64_array_with_different_timezone(self, box_with_array):
+ t1 = date_range("20130101", periods=3).tz_localize("US/Eastern")
+ t1 = tm.box_expected(t1, box_with_array)
+ t2 = date_range("20130101", periods=3).tz_localize("CET")
+ t2 = tm.box_expected(t2, box_with_array)
+ tnaive = date_range("20130101", periods=3)
+
+ result = t1 - t2
+ expected = TimedeltaIndex(
+ ["0 days 06:00:00", "0 days 06:00:00", "0 days 06:00:00"]
+ )
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ result = t2 - t1
+ expected = TimedeltaIndex(
+ ["-1 days +18:00:00", "-1 days +18:00:00", "-1 days +18:00:00"]
+ )
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ msg = "Cannot subtract tz-naive and tz-aware datetime-like objects"
+ with pytest.raises(TypeError, match=msg):
+ t1 - tnaive
+
+ with pytest.raises(TypeError, match=msg):
+ tnaive - t1
+
+ def test_dt64arr_add_sub_td64_nat(self, box_with_array, tz_naive_fixture):
+ # GH#23320 special handling for timedelta64("NaT")
+ tz = tz_naive_fixture
+
+ dti = date_range("1994-04-01", periods=9, tz=tz, freq="QS", unit="ns")
+ other = np.timedelta64("NaT")
+ expected = DatetimeIndex(["NaT"] * 9, tz=tz).as_unit("ns")
+
+ obj = tm.box_expected(dti, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = obj + other
+ tm.assert_equal(result, expected)
+ result = other + obj
+ tm.assert_equal(result, expected)
+ result = obj - other
+ tm.assert_equal(result, expected)
+ msg = "cannot subtract"
+ with pytest.raises(TypeError, match=msg):
+ other - obj
+
+ def test_dt64arr_add_sub_td64ndarray(self, tz_naive_fixture, box_with_array):
+ tz = tz_naive_fixture
+ dti = date_range("2016-01-01", periods=3, tz=tz)
+ tdi = TimedeltaIndex(["-1 Day", "-1 Day", "-1 Day"])
+ tdarr = tdi.values
+
+ expected = date_range("2015-12-31", "2016-01-02", periods=3, tz=tz)
+
+ dtarr = tm.box_expected(dti, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = dtarr + tdarr
+ tm.assert_equal(result, expected)
+ result = tdarr + dtarr
+ tm.assert_equal(result, expected)
+
+ expected = date_range("2016-01-02", "2016-01-04", periods=3, tz=tz)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = dtarr - tdarr
+ tm.assert_equal(result, expected)
+ msg = "|".join(
+ [
+ "cannot subtract DatetimeArray from ndarray",
+ "cannot subtract a datelike from a TimedeltaArray",
+ "cannot subtract DatetimeArray from Timedelta",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ tdarr - dtarr
+
+ # -----------------------------------------------------------------
+ # Subtraction of datetime-like scalars
+
+ @pytest.mark.parametrize(
+ "ts",
+ [
+ Timestamp("2013-01-01"),
+ Timestamp("2013-01-01").to_pydatetime(),
+ Timestamp("2013-01-01").to_datetime64(),
+ # GH#7996, GH#22163 ensure non-nano datetime64 is converted to nano
+ # for DataFrame operation
+ np.datetime64("2013-01-01", "D"),
+ ],
+ )
+ def test_dt64arr_sub_dtscalar(self, box_with_array, ts):
+ # GH#8554, GH#22163 DataFrame op should _not_ return dt64 dtype
+ idx = date_range("2013-01-01", periods=3)._with_freq(None)
+ idx = tm.box_expected(idx, box_with_array)
+
+ expected = TimedeltaIndex(["0 Days", "1 Day", "2 Days"])
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = idx - ts
+ tm.assert_equal(result, expected)
+
+ result = ts - idx
+ tm.assert_equal(result, -expected)
+ tm.assert_equal(result, -expected)
+
+ def test_dt64arr_sub_timestamp_tzaware(self, box_with_array):
+ ser = date_range("2014-03-17", periods=2, freq="D", tz="US/Eastern", unit="ns")
+ ser = ser._with_freq(None)
+ ts = ser[0]
+
+ ser = tm.box_expected(ser, box_with_array)
+
+ delta_series = Series(
+ [np.timedelta64(0, "D"), np.timedelta64(1, "D")], dtype="m8[ns]"
+ )
+ expected = tm.box_expected(delta_series, box_with_array)
+
+ tm.assert_equal(ser - ts, expected)
+ tm.assert_equal(ts - ser, -expected)
+
+ def test_dt64arr_sub_NaT(self, box_with_array, unit):
+ # GH#18808
+ dti = DatetimeIndex([NaT, Timestamp("19900315")]).as_unit(unit)
+ ser = tm.box_expected(dti, box_with_array)
+
+ result = ser - NaT
+ expected = Series([NaT, NaT], dtype=f"timedelta64[{unit}]")
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ dti_tz = dti.tz_localize("Asia/Tokyo")
+ ser_tz = tm.box_expected(dti_tz, box_with_array)
+
+ result = ser_tz - NaT
+ expected = Series([NaT, NaT], dtype=f"timedelta64[{unit}]")
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ # -------------------------------------------------------------
+ # Subtraction of datetime-like array-like
+
+ def test_dt64arr_sub_dt64object_array(
+ self, performance_warning, box_with_array, tz_naive_fixture
+ ):
+ dti = date_range("2016-01-01", periods=3, tz=tz_naive_fixture)
+ expected = dti - dti
+
+ obj = tm.box_expected(dti, box_with_array)
+ expected = tm.box_expected(expected, box_with_array).astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ result = obj - obj.astype(object)
+ tm.assert_equal(result, expected)
+
+ def test_dt64arr_naive_sub_dt64ndarray(self, box_with_array):
+ dti = date_range("2016-01-01", periods=3, tz=None)
+ dt64vals = dti.values
+
+ dtarr = tm.box_expected(dti, box_with_array)
+
+ expected = dtarr - dtarr
+ result = dtarr - dt64vals
+ tm.assert_equal(result, expected)
+ result = dt64vals - dtarr
+ tm.assert_equal(result, expected)
+
+ def test_dt64arr_aware_sub_dt64ndarray_raises(
+ self, tz_aware_fixture, box_with_array
+ ):
+ tz = tz_aware_fixture
+ dti = date_range("2016-01-01", periods=3, tz=tz)
+ dt64vals = dti.values
+
+ dtarr = tm.box_expected(dti, box_with_array)
+ msg = "Cannot subtract tz-naive and tz-aware datetime"
+ with pytest.raises(TypeError, match=msg):
+ dtarr - dt64vals
+ with pytest.raises(TypeError, match=msg):
+ dt64vals - dtarr
+
+ # -------------------------------------------------------------
+ # Addition of datetime-like others (invalid)
+
+ def test_dt64arr_add_dtlike_raises(self, tz_naive_fixture, box_with_array):
+ # GH#22163 ensure DataFrame doesn't cast Timestamp to i8
+ # GH#9631
+ tz = tz_naive_fixture
+
+ dti = date_range("2016-01-01", periods=3, tz=tz)
+ if tz is None:
+ dti2 = dti.tz_localize("US/Eastern")
+ else:
+ dti2 = dti.tz_localize(None)
+ dtarr = tm.box_expected(dti, box_with_array)
+
+ assert_cannot_add(dtarr, dti.values)
+ assert_cannot_add(dtarr, dti)
+ assert_cannot_add(dtarr, dtarr)
+ assert_cannot_add(dtarr, dti[0])
+ assert_cannot_add(dtarr, dti[0].to_pydatetime())
+ assert_cannot_add(dtarr, dti[0].to_datetime64())
+ assert_cannot_add(dtarr, dti2[0])
+ assert_cannot_add(dtarr, dti2[0].to_pydatetime())
+ assert_cannot_add(dtarr, np.datetime64("2011-01-01", "D"))
+
+ # -------------------------------------------------------------
+ # Other Invalid Addition/Subtraction
+
+ # Note: freq here includes both Tick and non-Tick offsets; this is
+ # relevant because historically integer-addition was allowed if we had
+ # a freq.
+ @pytest.mark.parametrize("freq", ["h", "D", "W", "2ME", "MS", "QE", "B", None])
+ @pytest.mark.parametrize("dtype", [None, "uint8"])
+ def test_dt64arr_addsub_intlike(
+ self, dtype, index_or_series_or_array, freq, tz_naive_fixture
+ ):
+ # GH#19959, GH#19123, GH#19012
+ # GH#55860 use index_or_series_or_array instead of box_with_array
+ # bc DataFrame alignment makes it inapplicable
+ tz = tz_naive_fixture
+
+ if freq is None:
+ dti = DatetimeIndex(["NaT", "2017-04-05 06:07:08"], tz=tz)
+ else:
+ dti = date_range("2016-01-01", periods=2, freq=freq, tz=tz)
+
+ obj = index_or_series_or_array(dti)
+ other = np.array([4, -1])
+ if dtype is not None:
+ other = other.astype(dtype)
+
+ msg = "|".join(
+ [
+ "Addition/subtraction of integers",
+ "cannot subtract DatetimeArray from",
+ # IntegerArray
+ "can only perform ops with numeric values",
+ "unsupported operand type.*Categorical",
+ r"unsupported operand type\(s\) for -: 'int' and 'Timestamp'",
+ ]
+ )
+ assert_invalid_addsub_type(obj, 1, msg)
+ assert_invalid_addsub_type(obj, np.int64(2), msg)
+ assert_invalid_addsub_type(obj, np.array(3, dtype=np.int64), msg)
+ assert_invalid_addsub_type(obj, other, msg)
+ assert_invalid_addsub_type(obj, np.array(other), msg)
+ assert_invalid_addsub_type(obj, pd.array(other), msg)
+ assert_invalid_addsub_type(obj, pd.Categorical(other), msg)
+ assert_invalid_addsub_type(obj, pd.Index(other), msg)
+ assert_invalid_addsub_type(obj, Series(other), msg)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ 3.14,
+ np.array([2.0, 3.0]),
+ # GH#13078 datetime +/- Period is invalid
+ Period("2011-01-01", freq="D"),
+ # https://github.com/pandas-dev/pandas/issues/10329
+ time(1, 2, 3),
+ ],
+ )
+ @pytest.mark.parametrize("dti_freq", [None, "D"])
+ def test_dt64arr_add_sub_invalid(self, dti_freq, other, box_with_array):
+ dti = DatetimeIndex(["2011-01-01", "2011-01-02"], freq=dti_freq)
+ dtarr = tm.box_expected(dti, box_with_array)
+ msg = "|".join(
+ [
+ "unsupported operand type",
+ "cannot (add|subtract)",
+ "cannot use operands with types",
+ "ufunc '?(add|subtract)'? cannot use operands with types",
+ "Concatenation operation is not implemented for NumPy arrays",
+ ]
+ )
+ assert_invalid_addsub_type(dtarr, other, msg)
+
+ @pytest.mark.parametrize("pi_freq", ["D", "W", "Q", "h"])
+ @pytest.mark.parametrize("dti_freq", [None, "D"])
+ def test_dt64arr_add_sub_parr(
+ self, dti_freq, pi_freq, box_with_array, box_with_array2
+ ):
+ # GH#20049 subtracting PeriodIndex should raise TypeError
+ dti = DatetimeIndex(["2011-01-01", "2011-01-02"], freq=dti_freq)
+ pi = dti.to_period(pi_freq)
+
+ dtarr = tm.box_expected(dti, box_with_array)
+ parr = tm.box_expected(pi, box_with_array2)
+ msg = "|".join(
+ [
+ "cannot (add|subtract)",
+ "unsupported operand",
+ "descriptor.*requires",
+ "ufunc.*cannot use operands",
+ ]
+ )
+ assert_invalid_addsub_type(dtarr, parr, msg)
+
+ @pytest.mark.filterwarnings("ignore::pandas.errors.PerformanceWarning")
+ def test_dt64arr_addsub_time_objects_raises(self, box_with_array, tz_naive_fixture):
+ # https://github.com/pandas-dev/pandas/issues/10329
+
+ tz = tz_naive_fixture
+
+ obj1 = date_range("2012-01-01", periods=3, tz=tz)
+ obj2 = [time(i, i, i) for i in range(3)]
+
+ obj1 = tm.box_expected(obj1, box_with_array)
+ obj2 = tm.box_expected(obj2, box_with_array)
+
+ msg = "|".join(
+ [
+ "unsupported operand",
+ "cannot subtract DatetimeArray from ndarray",
+ ]
+ )
+ # pandas.errors.PerformanceWarning: Non-vectorized DateOffset being
+ # applied to Series or DatetimeIndex
+ # we aren't testing that here, so ignore.
+ assert_invalid_addsub_type(obj1, obj2, msg=msg)
+
+ # -------------------------------------------------------------
+ # Other invalid operations
+
+ @pytest.mark.parametrize(
+ "dt64_series",
+ [
+ Series([Timestamp("19900315"), Timestamp("19900315")]),
+ Series([NaT, Timestamp("19900315")]),
+ Series([NaT, NaT], dtype="datetime64[ns]"),
+ ],
+ )
+ @pytest.mark.parametrize("one", [1, 1.0, np.array(1)])
+ def test_dt64_mul_div_numeric_invalid(self, one, dt64_series, box_with_array):
+ obj = tm.box_expected(dt64_series, box_with_array)
+
+ msg = "cannot perform .* with this index type"
+
+ # multiplication
+ with pytest.raises(TypeError, match=msg):
+ obj * one
+ with pytest.raises(TypeError, match=msg):
+ one * obj
+
+ # division
+ with pytest.raises(TypeError, match=msg):
+ obj / one
+ with pytest.raises(TypeError, match=msg):
+ one / obj
+
+
+class TestDatetime64DateOffsetArithmetic:
+ # -------------------------------------------------------------
+ # Tick DateOffsets
+
+ # TODO: parametrize over timezone?
+ def test_dt64arr_series_add_tick_DateOffset(self, box_with_array, unit):
+ # GH#4532
+ # operate with pd.offsets
+ ser = Series(
+ [Timestamp("20130101 9:01"), Timestamp("20130101 9:02")]
+ ).dt.as_unit(unit)
+ expected = Series(
+ [Timestamp("20130101 9:01:05"), Timestamp("20130101 9:02:05")]
+ ).dt.as_unit(unit)
+
+ ser = tm.box_expected(ser, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = ser + pd.offsets.Second(5)
+ tm.assert_equal(result, expected)
+
+ result2 = pd.offsets.Second(5) + ser
+ tm.assert_equal(result2, expected)
+
+ def test_dt64arr_series_sub_tick_DateOffset(self, box_with_array):
+ # GH#4532
+ # operate with pd.offsets
+ ser = Series([Timestamp("20130101 9:01"), Timestamp("20130101 9:02")])
+ expected = Series(
+ [Timestamp("20130101 9:00:55"), Timestamp("20130101 9:01:55")]
+ )
+
+ ser = tm.box_expected(ser, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = ser - pd.offsets.Second(5)
+ tm.assert_equal(result, expected)
+
+ result2 = -pd.offsets.Second(5) + ser
+ tm.assert_equal(result2, expected)
+ msg = "cannot subtract DatetimeArray from Second"
+ with pytest.raises(TypeError, match=msg):
+ pd.offsets.Second(5) - ser
+
+ @pytest.mark.parametrize(
+ "cls_name", ["Day", "Hour", "Minute", "Second", "Milli", "Micro", "Nano"]
+ )
+ def test_dt64arr_add_sub_tick_DateOffset_smoke(self, cls_name, box_with_array):
+ # GH#4532
+ # smoke tests for valid DateOffsets
+ ser = Series([Timestamp("20130101 9:01"), Timestamp("20130101 9:02")])
+ ser = tm.box_expected(ser, box_with_array)
+
+ offset_cls = getattr(pd.offsets, cls_name)
+ ser + offset_cls(5)
+ offset_cls(5) + ser
+ ser - offset_cls(5)
+
+ def test_dti_add_tick_tzaware(self, tz_aware_fixture, box_with_array):
+ # GH#21610, GH#22163 ensure DataFrame doesn't return object-dtype
+ tz = tz_aware_fixture
+ if tz == "US/Pacific":
+ dates = date_range("2012-11-01", periods=3, tz=tz, unit="ns")
+ offset = dates + pd.offsets.Hour(5)
+ assert dates[0] + pd.offsets.Hour(5) == offset[0]
+
+ dates = date_range("2010-11-01 00:00", periods=3, tz=tz, freq="h", unit="ns")
+ expected = DatetimeIndex(
+ ["2010-11-01 05:00", "2010-11-01 06:00", "2010-11-01 07:00"],
+ freq="h",
+ tz=tz,
+ ).as_unit("ns")
+
+ dates = tm.box_expected(dates, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ for scalar in [pd.offsets.Hour(5), np.timedelta64(5, "h"), timedelta(hours=5)]:
+ offset = dates + scalar
+ tm.assert_equal(offset, expected)
+ offset = scalar + dates
+ tm.assert_equal(offset, expected)
+
+ roundtrip = offset - scalar
+ tm.assert_equal(roundtrip, dates)
+
+ msg = "cannot subtract DatetimeArray from"
+ with pytest.raises(TypeError, match=msg):
+ scalar - dates
+
+ # -------------------------------------------------------------
+ # RelativeDelta DateOffsets
+
+ def test_dt64arr_add_sub_relativedelta_offsets(self, box_with_array, unit):
+ # GH#10699
+ vec = DatetimeIndex(
+ [
+ Timestamp("2000-01-05 00:15:00"),
+ Timestamp("2000-01-31 00:23:00"),
+ Timestamp("2000-01-01"),
+ Timestamp("2000-03-31"),
+ Timestamp("2000-02-29"),
+ Timestamp("2000-12-31"),
+ Timestamp("2000-05-15"),
+ Timestamp("2001-06-15"),
+ ]
+ ).as_unit(unit)
+ vec = tm.box_expected(vec, box_with_array)
+ vec_items = vec.iloc[0] if box_with_array is pd.DataFrame else vec
+
+ # DateOffset relativedelta fastpath
+ relative_kwargs = [
+ ("years", 2),
+ ("months", 5),
+ ("days", 3),
+ ("hours", 5),
+ ("minutes", 10),
+ ("seconds", 2),
+ ("microseconds", 5),
+ ]
+ for i, (offset_unit, value) in enumerate(relative_kwargs):
+ off = DateOffset(**{offset_unit: value})
+
+ exp_unit = unit
+ if offset_unit == "microseconds" and unit != "ns":
+ exp_unit = "us"
+
+ # TODO(GH#55564): as_unit will be unnecessary
+ expected = DatetimeIndex([x + off for x in vec_items]).as_unit(exp_unit)
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(expected, vec + off)
+
+ expected = DatetimeIndex([x - off for x in vec_items]).as_unit(exp_unit)
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(expected, vec - off)
+
+ off = DateOffset(**dict(relative_kwargs[: i + 1]))
+
+ expected = DatetimeIndex([x + off for x in vec_items]).as_unit(exp_unit)
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(expected, vec + off)
+
+ expected = DatetimeIndex([x - off for x in vec_items]).as_unit(exp_unit)
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(expected, vec - off)
+ msg = "cannot subtract DatetimeArray from"
+ with pytest.raises(TypeError, match=msg):
+ off - vec
+
+ # -------------------------------------------------------------
+ # Non-Tick, Non-RelativeDelta DateOffsets
+
+ # TODO: redundant with test_dt64arr_add_sub_DateOffset? that includes
+ # tz-aware cases which this does not
+ @pytest.mark.filterwarnings("ignore::pandas.errors.PerformanceWarning")
+ @pytest.mark.parametrize(
+ "cls_and_kwargs",
+ [
+ "YearBegin",
+ ("YearBegin", {"month": 5}),
+ "YearEnd",
+ ("YearEnd", {"month": 5}),
+ "MonthBegin",
+ "MonthEnd",
+ "SemiMonthEnd",
+ "SemiMonthBegin",
+ "Week",
+ ("Week", {"weekday": 3}),
+ ("Week", {"weekday": 6}),
+ "BusinessDay",
+ "BDay",
+ "QuarterEnd",
+ "QuarterBegin",
+ "CustomBusinessDay",
+ "CDay",
+ "CBMonthEnd",
+ "CBMonthBegin",
+ "BMonthBegin",
+ "BMonthEnd",
+ "BusinessHour",
+ "BYearBegin",
+ "BYearEnd",
+ "BQuarterBegin",
+ ("LastWeekOfMonth", {"weekday": 2}),
+ (
+ "FY5253Quarter",
+ {
+ "qtr_with_extra_week": 1,
+ "startingMonth": 1,
+ "weekday": 2,
+ "variation": "nearest",
+ },
+ ),
+ ("FY5253", {"weekday": 0, "startingMonth": 2, "variation": "nearest"}),
+ ("WeekOfMonth", {"weekday": 2, "week": 2}),
+ "Easter",
+ ("DateOffset", {"day": 4}),
+ ("DateOffset", {"month": 5}),
+ ],
+ )
+ @pytest.mark.parametrize("normalize", [True, False])
+ @pytest.mark.parametrize("n", [0, 5])
+ @pytest.mark.parametrize("tz", [None, "US/Central"])
+ def test_dt64arr_add_sub_DateOffsets(
+ self, box_with_array, n, normalize, cls_and_kwargs, unit, tz
+ ):
+ # GH#10699
+ # assert vectorized operation matches pointwise operations
+
+ if isinstance(cls_and_kwargs, tuple):
+ # If cls_name param is a tuple, then 2nd entry is kwargs for
+ # the offset constructor
+ cls_name, kwargs = cls_and_kwargs
+ else:
+ cls_name = cls_and_kwargs
+ kwargs = {}
+
+ if n == 0 and cls_name in [
+ "WeekOfMonth",
+ "LastWeekOfMonth",
+ "FY5253Quarter",
+ "FY5253",
+ ]:
+ # passing n = 0 is invalid for these offset classes
+ return
+
+ vec = (
+ DatetimeIndex(
+ [
+ Timestamp("2000-01-05 00:15:00"),
+ Timestamp("2000-01-31 00:23:00"),
+ Timestamp("2000-01-01"),
+ Timestamp("2000-03-31"),
+ Timestamp("2000-02-29"),
+ Timestamp("2000-12-31"),
+ Timestamp("2000-05-15"),
+ Timestamp("2001-06-15"),
+ ]
+ )
+ .as_unit(unit)
+ .tz_localize(tz)
+ )
+ vec = tm.box_expected(vec, box_with_array)
+ vec_items = vec.iloc[0] if box_with_array is pd.DataFrame else vec
+
+ offset_cls = getattr(pd.offsets, cls_name)
+ offset = offset_cls(n, normalize=normalize, **kwargs)
+
+ # TODO(GH#55564): as_unit will be unnecessary
+ expected = DatetimeIndex([x + offset for x in vec_items]).as_unit(unit)
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(expected, vec + offset)
+ tm.assert_equal(expected, offset + vec)
+
+ expected = DatetimeIndex([x - offset for x in vec_items]).as_unit(unit)
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(expected, vec - offset)
+
+ expected = DatetimeIndex([offset + x for x in vec_items]).as_unit(unit)
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(expected, offset + vec)
+ msg = "cannot subtract DatetimeArray from"
+ with pytest.raises(TypeError, match=msg):
+ offset - vec
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ [pd.offsets.MonthEnd(), pd.offsets.Day(n=2)],
+ [pd.offsets.DateOffset(years=1), pd.offsets.MonthEnd()],
+ # matching offsets
+ [pd.offsets.DateOffset(years=1), pd.offsets.DateOffset(years=1)],
+ ],
+ )
+ @pytest.mark.parametrize("op", [operator.add, roperator.radd, operator.sub])
+ def test_dt64arr_add_sub_offset_array(
+ self, performance_warning, tz_naive_fixture, box_with_array, op, other
+ ):
+ # GH#18849
+ # GH#10699 array of offsets
+
+ tz = tz_naive_fixture
+ dti = date_range("2017-01-01", periods=2, tz=tz)
+ dtarr = tm.box_expected(dti, box_with_array)
+ other = np.array(other)
+ expected = DatetimeIndex([op(dti[n], other[n]) for n in range(len(dti))])
+ expected = tm.box_expected(expected, box_with_array).astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ res = op(dtarr, other)
+ tm.assert_equal(res, expected)
+
+ # Same thing but boxing other
+ other = tm.box_expected(other, box_with_array)
+ if box_with_array is pd.array and op is roperator.radd:
+ # We expect a NumpyExtensionArray, not ndarray[object] here
+ expected = pd.array(expected, dtype=object)
+ with tm.assert_produces_warning(performance_warning):
+ res = op(dtarr, other)
+ tm.assert_equal(res, expected)
+
+ @pytest.mark.parametrize(
+ "op, offset, exp, exp_freq",
+ [
+ (
+ "__add__",
+ DateOffset(months=3, days=10),
+ [
+ Timestamp("2014-04-11"),
+ Timestamp("2015-04-11"),
+ Timestamp("2016-04-11"),
+ Timestamp("2017-04-11"),
+ ],
+ None,
+ ),
+ (
+ "__add__",
+ DateOffset(months=3),
+ [
+ Timestamp("2014-04-01"),
+ Timestamp("2015-04-01"),
+ Timestamp("2016-04-01"),
+ Timestamp("2017-04-01"),
+ ],
+ "YS-APR",
+ ),
+ (
+ "__sub__",
+ DateOffset(months=3, days=10),
+ [
+ Timestamp("2013-09-21"),
+ Timestamp("2014-09-21"),
+ Timestamp("2015-09-21"),
+ Timestamp("2016-09-21"),
+ ],
+ None,
+ ),
+ (
+ "__sub__",
+ DateOffset(months=3),
+ [
+ Timestamp("2013-10-01"),
+ Timestamp("2014-10-01"),
+ Timestamp("2015-10-01"),
+ Timestamp("2016-10-01"),
+ ],
+ "YS-OCT",
+ ),
+ ],
+ )
+ def test_dti_add_sub_nonzero_mth_offset(
+ self, op, offset, exp, exp_freq, tz_aware_fixture, box_with_array
+ ):
+ # GH 26258
+ tz = tz_aware_fixture
+ date = date_range(
+ start="01 Jan 2014", end="01 Jan 2017", freq="YS", tz=tz, unit="ns"
+ )
+ date = tm.box_expected(date, box_with_array, False)
+ mth = getattr(date, op)
+ result = mth(offset)
+
+ expected = DatetimeIndex(exp, tz=tz).as_unit("ns")
+ expected = tm.box_expected(expected, box_with_array, False)
+ tm.assert_equal(result, expected)
+
+ def test_dt64arr_series_add_DateOffset_with_milli(self):
+ # GH 57529
+ dti = DatetimeIndex(
+ [
+ "2000-01-01 00:00:00.012345678",
+ "2000-01-31 00:00:00.012345678",
+ "2000-02-29 00:00:00.012345678",
+ ],
+ dtype="datetime64[ns]",
+ )
+ result = dti + DateOffset(milliseconds=4)
+ expected = DatetimeIndex(
+ [
+ "2000-01-01 00:00:00.016345678",
+ "2000-01-31 00:00:00.016345678",
+ "2000-02-29 00:00:00.016345678",
+ ],
+ dtype="datetime64[ns]",
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = dti + DateOffset(days=1, milliseconds=4)
+ expected = DatetimeIndex(
+ [
+ "2000-01-02 00:00:00.016345678",
+ "2000-02-01 00:00:00.016345678",
+ "2000-03-01 00:00:00.016345678",
+ ],
+ dtype="datetime64[ns]",
+ )
+ tm.assert_index_equal(result, expected)
+
+
+class TestDatetime64OverflowHandling:
+ # TODO: box + de-duplicate
+
+ def test_dt64_overflow_masking(self, box_with_array):
+ # GH#25317
+ left = Series([Timestamp("1969-12-31")], dtype="M8[ns]")
+ right = Series([NaT])
+
+ left = tm.box_expected(left, box_with_array)
+ right = tm.box_expected(right, box_with_array)
+
+ expected = TimedeltaIndex([NaT], dtype="m8[ns]")
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = left - right
+ tm.assert_equal(result, expected)
+
+ def test_dt64_series_arith_overflow(self):
+ # GH#12534, fixed by GH#19024
+ dt = Timestamp("1700-01-31")
+ td = Timedelta("20000 Days")
+ dti = date_range("1949-09-30", freq="100YE", periods=4, unit="ns")
+ ser = Series(dti)
+ msg = "Overflow in int64 addition"
+ with pytest.raises(OverflowError, match=msg):
+ ser - dt
+ with pytest.raises(OverflowError, match=msg):
+ dt - ser
+ with pytest.raises(OverflowError, match=msg):
+ ser + td
+ with pytest.raises(OverflowError, match=msg):
+ td + ser
+
+ ser.iloc[-1] = NaT
+ expected = Series(
+ ["2004-10-03", "2104-10-04", "2204-10-04", "NaT"], dtype="datetime64[ns]"
+ )
+ res = ser + td
+ tm.assert_series_equal(res, expected)
+ res = td + ser
+ tm.assert_series_equal(res, expected)
+
+ ser.iloc[1:] = NaT
+ expected = Series(["91279 Days", "NaT", "NaT", "NaT"], dtype="timedelta64[ns]")
+ res = ser - dt
+ tm.assert_series_equal(res, expected)
+ res = dt - ser
+ tm.assert_series_equal(res, -expected)
+
+ def test_datetimeindex_sub_timestamp_overflow(self):
+ dtimax = pd.to_datetime(["2021-12-28 17:19", Timestamp.max]).as_unit("ns")
+ dtimin = pd.to_datetime(["2021-12-28 17:19", Timestamp.min]).as_unit("ns")
+
+ tsneg = Timestamp("1950-01-01").as_unit("ns")
+ ts_neg_variants = [
+ tsneg,
+ tsneg.to_pydatetime(),
+ tsneg.to_datetime64().astype("datetime64[ns]"),
+ tsneg.to_datetime64().astype("datetime64[D]"),
+ ]
+
+ tspos = Timestamp("1980-01-01").as_unit("ns")
+ ts_pos_variants = [
+ tspos,
+ tspos.to_pydatetime(),
+ tspos.to_datetime64().astype("datetime64[ns]"),
+ tspos.to_datetime64().astype("datetime64[D]"),
+ ]
+ msg = "Overflow in int64 addition"
+ for variant in ts_neg_variants:
+ with pytest.raises(OverflowError, match=msg):
+ dtimax - variant
+
+ expected = Timestamp.max._value - tspos._value
+ for variant in ts_pos_variants:
+ res = dtimax - variant
+ assert res[1]._value == expected
+
+ expected = Timestamp.min._value - tsneg._value
+ for variant in ts_neg_variants:
+ res = dtimin - variant
+ assert res[1]._value == expected
+
+ for variant in ts_pos_variants:
+ with pytest.raises(OverflowError, match=msg):
+ dtimin - variant
+
+ def test_datetimeindex_sub_datetimeindex_overflow(self):
+ # GH#22492, GH#22508
+ dtimax = pd.to_datetime(["2021-12-28 17:19", Timestamp.max]).as_unit("ns")
+ dtimin = pd.to_datetime(["2021-12-28 17:19", Timestamp.min]).as_unit("ns")
+
+ ts_neg = pd.to_datetime(["1950-01-01", "1950-01-01"]).as_unit("ns")
+ ts_pos = pd.to_datetime(["1980-01-01", "1980-01-01"]).as_unit("ns")
+
+ # General tests
+ expected = Timestamp.max._value - ts_pos[1]._value
+ result = dtimax - ts_pos
+ assert result[1]._value == expected
+
+ expected = Timestamp.min._value - ts_neg[1]._value
+ result = dtimin - ts_neg
+ assert result[1]._value == expected
+ msg = "Overflow in int64 addition"
+ with pytest.raises(OverflowError, match=msg):
+ dtimax - ts_neg
+
+ with pytest.raises(OverflowError, match=msg):
+ dtimin - ts_pos
+
+ # Edge cases
+ tmin = pd.to_datetime([Timestamp.min])
+ t1 = tmin + Timedelta.max + Timedelta("1us")
+ with pytest.raises(OverflowError, match=msg):
+ t1 - tmin
+
+ tmax = pd.to_datetime([Timestamp.max])
+ t2 = tmax + Timedelta.min - Timedelta("1us")
+ with pytest.raises(OverflowError, match=msg):
+ tmax - t2
+
+
+class TestTimestampSeriesArithmetic:
+ def test_empty_series_add_sub(self, box_with_array):
+ # GH#13844
+ a = Series(dtype="M8[ns]")
+ b = Series(dtype="m8[ns]")
+ a = box_with_array(a)
+ b = box_with_array(b)
+ tm.assert_equal(a, a + b)
+ tm.assert_equal(a, a - b)
+ tm.assert_equal(a, b + a)
+ msg = "cannot subtract"
+ with pytest.raises(TypeError, match=msg):
+ b - a
+
+ def test_operators_datetimelike(self):
+ # ## timedelta64 ###
+ td1 = Series([timedelta(minutes=5, seconds=3)] * 3)
+ td1.iloc[2] = np.nan
+
+ # ## datetime64 ###
+ dt1 = Series(
+ [
+ Timestamp("20111230"),
+ Timestamp("20120101"),
+ Timestamp("20120103"),
+ ]
+ )
+ dt1.iloc[2] = np.nan
+ dt2 = Series(
+ [
+ Timestamp("20111231"),
+ Timestamp("20120102"),
+ Timestamp("20120104"),
+ ]
+ )
+ dt1 - dt2
+ dt2 - dt1
+
+ # datetime64 with timetimedelta
+ dt1 + td1
+ td1 + dt1
+ dt1 - td1
+
+ # timetimedelta with datetime64
+ td1 + dt1
+ dt1 + td1
+
+ def test_dt64ser_sub_datetime_dtype(self, unit):
+ ts = Timestamp(datetime(1993, 1, 7, 13, 30, 00))
+ dt = datetime(1993, 6, 22, 13, 30)
+ ser = Series([ts], dtype=f"M8[{unit}]")
+ result = ser - dt
+
+ # the expected unit is the max of `unit` and the unit imputed to `dt`,
+ # which is "us"
+ exp_unit = tm.get_finest_unit(unit, "us")
+ assert result.dtype == f"timedelta64[{exp_unit}]"
+
+ # -------------------------------------------------------------
+ # TODO: This next block of tests came from tests.series.test_operators,
+ # needs to be de-duplicated and parametrized over `box` classes
+
+ @pytest.mark.parametrize(
+ "left, right, op_fail",
+ [
+ [
+ [Timestamp("20111230"), Timestamp("20120101"), NaT],
+ [Timestamp("20111231"), Timestamp("20120102"), Timestamp("20120104")],
+ ["__sub__", "__rsub__"],
+ ],
+ [
+ [Timestamp("20111230"), Timestamp("20120101"), NaT],
+ [timedelta(minutes=5, seconds=3), timedelta(minutes=5, seconds=3), NaT],
+ ["__add__", "__radd__", "__sub__"],
+ ],
+ [
+ [
+ Timestamp("20111230", tz="US/Eastern"),
+ Timestamp("20111230", tz="US/Eastern"),
+ NaT,
+ ],
+ [timedelta(minutes=5, seconds=3), NaT, timedelta(minutes=5, seconds=3)],
+ ["__add__", "__radd__", "__sub__"],
+ ],
+ ],
+ )
+ def test_operators_datetimelike_invalid(
+ self, left, right, op_fail, all_arithmetic_operators
+ ):
+ # these are all TypeError ops
+ op_str = all_arithmetic_operators
+ arg1 = Series(left)
+ arg2 = Series(right)
+ # check that we are getting a TypeError
+ # with 'operate' (from core/ops.py) for the ops that are not
+ # defined
+ op = getattr(arg1, op_str, None)
+ # Previously, _validate_for_numeric_binop in core/indexes/base.py
+ # did this for us.
+ if op_str not in op_fail:
+ with pytest.raises(
+ TypeError, match="operate|[cC]annot|unsupported operand"
+ ):
+ op(arg2)
+ else:
+ # Smoke test
+ op(arg2)
+
+ def test_sub_single_tz(self, unit):
+ # GH#12290
+ s1 = Series([Timestamp("2016-02-10", tz="America/Sao_Paulo")]).dt.as_unit(unit)
+ s2 = Series([Timestamp("2016-02-08", tz="America/Sao_Paulo")]).dt.as_unit(unit)
+ result = s1 - s2
+ expected = Series([Timedelta("2days")]).dt.as_unit(unit)
+ tm.assert_series_equal(result, expected)
+ result = s2 - s1
+ expected = Series([Timedelta("-2days")]).dt.as_unit(unit)
+ tm.assert_series_equal(result, expected)
+
+ def test_dt64tz_series_sub_dtitz(self):
+ # GH#19071 subtracting tzaware DatetimeIndex from tzaware Series
+ # (with same tz) raises, fixed by #19024
+ dti = date_range("1999-09-30", periods=10, tz="US/Pacific")
+ ser = Series(dti)
+ expected = Series(TimedeltaIndex(["0days"] * 10))
+
+ res = dti - ser
+ tm.assert_series_equal(res, expected)
+ res = ser - dti
+ tm.assert_series_equal(res, expected)
+
+ def test_sub_datetime_compat(self, unit):
+ # see GH#14088
+ ser = Series([datetime(2016, 8, 23, 12, tzinfo=timezone.utc), NaT]).dt.as_unit(
+ unit
+ )
+ dt = datetime(2016, 8, 22, 12, tzinfo=timezone.utc)
+ # The datetime object has "us" so we upcast lower units
+ exp_unit = tm.get_finest_unit(unit, "us")
+ exp = Series([Timedelta("1 days"), NaT]).dt.as_unit(exp_unit)
+ result = ser - dt
+ tm.assert_series_equal(result, exp)
+ result2 = ser - Timestamp(dt)
+ tm.assert_series_equal(result2, exp)
+
+ def test_dt64_series_add_mixed_tick_DateOffset(self):
+ # GH#4532
+ # operate with pd.offsets
+ s = Series([Timestamp("20130101 9:01"), Timestamp("20130101 9:02")])
+
+ result = s + pd.offsets.Milli(5)
+ result2 = pd.offsets.Milli(5) + s
+ expected = Series(
+ [Timestamp("20130101 9:01:00.005"), Timestamp("20130101 9:02:00.005")]
+ )
+ tm.assert_series_equal(result, expected)
+ tm.assert_series_equal(result2, expected)
+
+ result = s + pd.offsets.Minute(5) + pd.offsets.Milli(5)
+ expected = Series(
+ [Timestamp("20130101 9:06:00.005"), Timestamp("20130101 9:07:00.005")]
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_datetime64_ops_nat(self, unit):
+ # GH#11349
+ datetime_series = Series([NaT, Timestamp("19900315")]).dt.as_unit(unit)
+ nat_series_dtype_timestamp = Series([NaT, NaT], dtype=f"datetime64[{unit}]")
+ single_nat_dtype_datetime = Series([NaT], dtype=f"datetime64[{unit}]")
+
+ # subtraction
+ tm.assert_series_equal(-NaT + datetime_series, nat_series_dtype_timestamp)
+ msg = "bad operand type for unary -: 'DatetimeArray'"
+ with pytest.raises(TypeError, match=msg):
+ -single_nat_dtype_datetime + datetime_series
+
+ tm.assert_series_equal(
+ -NaT + nat_series_dtype_timestamp, nat_series_dtype_timestamp
+ )
+ with pytest.raises(TypeError, match=msg):
+ -single_nat_dtype_datetime + nat_series_dtype_timestamp
+
+ # addition
+ tm.assert_series_equal(
+ nat_series_dtype_timestamp + NaT, nat_series_dtype_timestamp
+ )
+ tm.assert_series_equal(
+ NaT + nat_series_dtype_timestamp, nat_series_dtype_timestamp
+ )
+
+ tm.assert_series_equal(
+ nat_series_dtype_timestamp + NaT, nat_series_dtype_timestamp
+ )
+ tm.assert_series_equal(
+ NaT + nat_series_dtype_timestamp, nat_series_dtype_timestamp
+ )
+
+ # -------------------------------------------------------------
+ # Timezone-Centric Tests
+
+ def test_operators_datetimelike_with_timezones(self):
+ tz = "US/Eastern"
+ dt1 = Series(date_range("2000-01-01 09:00:00", periods=5, tz=tz), name="foo")
+ dt2 = dt1.copy()
+ dt2.iloc[2] = np.nan
+
+ td1 = Series(pd.timedelta_range("1 days 1 min", periods=5, freq="h"))
+ td2 = td1.copy()
+ td2.iloc[1] = np.nan
+ assert td2._values.freq is None
+
+ result = dt1 + td1[0]
+ exp = (dt1.dt.tz_localize(None) + td1[0]).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+
+ result = dt2 + td2[0]
+ exp = (dt2.dt.tz_localize(None) + td2[0]).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+
+ # odd numpy behavior with scalar timedeltas
+ result = td1[0] + dt1
+ exp = (dt1.dt.tz_localize(None) + td1[0]).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+
+ result = td2[0] + dt2
+ exp = (dt2.dt.tz_localize(None) + td2[0]).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+
+ result = dt1 - td1[0]
+ exp = (dt1.dt.tz_localize(None) - td1[0]).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+ msg = "cannot subtract DatetimeArray from"
+ with pytest.raises(TypeError, match=msg):
+ td1[0] - dt1
+
+ result = dt2 - td2[0]
+ exp = (dt2.dt.tz_localize(None) - td2[0]).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+ with pytest.raises(TypeError, match=msg):
+ td2[0] - dt2
+
+ result = dt1 + td1
+ exp = (dt1.dt.tz_localize(None) + td1).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+
+ result = dt2 + td2
+ exp = (dt2.dt.tz_localize(None) + td2).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+
+ result = dt1 - td1
+ exp = (dt1.dt.tz_localize(None) - td1).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+
+ result = dt2 - td2
+ exp = (dt2.dt.tz_localize(None) - td2).dt.tz_localize(tz)
+ tm.assert_series_equal(result, exp)
+ msg = "cannot (add|subtract)"
+ with pytest.raises(TypeError, match=msg):
+ td1 - dt1
+ with pytest.raises(TypeError, match=msg):
+ td2 - dt2
+
+
+class TestDatetimeIndexArithmetic:
+ # -------------------------------------------------------------
+ # Binary operations DatetimeIndex and TimedeltaIndex/array
+
+ def test_dti_add_tdi(self, tz_naive_fixture):
+ # GH#17558
+ tz = tz_naive_fixture
+ dti = DatetimeIndex([Timestamp("2017-01-01", tz=tz)] * 10)
+ tdi = pd.timedelta_range("0 days", periods=10)
+ expected = date_range("2017-01-01", periods=10, tz=tz)
+ expected = expected._with_freq(None)
+
+ # add with TimedeltaIndex
+ result = dti + tdi
+ tm.assert_index_equal(result, expected)
+
+ result = tdi + dti
+ tm.assert_index_equal(result, expected)
+
+ # add with timedelta64 array
+ result = dti + tdi.values
+ tm.assert_index_equal(result, expected)
+
+ result = tdi.values + dti
+ tm.assert_index_equal(result, expected)
+
+ def test_dti_iadd_tdi(self, tz_naive_fixture):
+ # GH#17558
+ tz = tz_naive_fixture
+ dti = DatetimeIndex([Timestamp("2017-01-01", tz=tz)] * 10)
+ tdi = pd.timedelta_range("0 days", periods=10)
+ expected = date_range("2017-01-01", periods=10, tz=tz)
+ expected = expected._with_freq(None)
+
+ # iadd with TimedeltaIndex
+ result = DatetimeIndex([Timestamp("2017-01-01", tz=tz)] * 10)
+ result += tdi
+ tm.assert_index_equal(result, expected)
+
+ result = pd.timedelta_range("0 days", periods=10)
+ result += dti
+ tm.assert_index_equal(result, expected)
+
+ # iadd with timedelta64 array
+ result = DatetimeIndex([Timestamp("2017-01-01", tz=tz)] * 10)
+ result += tdi.values
+ tm.assert_index_equal(result, expected)
+
+ result = pd.timedelta_range("0 days", periods=10)
+ result += dti
+ tm.assert_index_equal(result, expected)
+
+ def test_dti_sub_tdi(self, tz_naive_fixture):
+ # GH#17558
+ tz = tz_naive_fixture
+ dti = DatetimeIndex([Timestamp("2017-01-01", tz=tz)] * 10)
+ tdi = pd.timedelta_range("0 days", periods=10)
+ expected = date_range("2017-01-01", periods=10, tz=tz, freq="-1D")
+ expected = expected._with_freq(None)
+
+ # sub with TimedeltaIndex
+ result = dti - tdi
+ tm.assert_index_equal(result, expected)
+
+ msg = "cannot subtract .*TimedeltaArray"
+ with pytest.raises(TypeError, match=msg):
+ tdi - dti
+
+ # sub with timedelta64 array
+ result = dti - tdi.values
+ tm.assert_index_equal(result, expected)
+
+ msg = "cannot subtract a datelike from a TimedeltaArray"
+ with pytest.raises(TypeError, match=msg):
+ tdi.values - dti
+
+ def test_dti_isub_tdi(self, tz_naive_fixture, unit):
+ # GH#17558
+ tz = tz_naive_fixture
+ dti = DatetimeIndex([Timestamp("2017-01-01", tz=tz)] * 10).as_unit(unit)
+ tdi = pd.timedelta_range("0 days", periods=10, unit=unit)
+ expected = date_range("2017-01-01", periods=10, tz=tz, freq="-1D", unit=unit)
+ expected = expected._with_freq(None)
+
+ # isub with TimedeltaIndex
+ result = DatetimeIndex([Timestamp("2017-01-01", tz=tz)] * 10).as_unit(unit)
+ result -= tdi
+ tm.assert_index_equal(result, expected)
+
+ # DTA.__isub__ GH#43904
+ dta = dti._data.copy()
+ dta -= tdi
+ tm.assert_datetime_array_equal(dta, expected._data)
+
+ out = dti._data.copy()
+ np.subtract(out, tdi, out=out)
+ tm.assert_datetime_array_equal(out, expected._data)
+
+ msg = "cannot subtract a datelike from a TimedeltaArray"
+ with pytest.raises(TypeError, match=msg):
+ tdi -= dti
+
+ # isub with timedelta64 array
+ result = DatetimeIndex([Timestamp("2017-01-01", tz=tz)] * 10).as_unit(unit)
+ result -= tdi.values
+ tm.assert_index_equal(result, expected)
+
+ with pytest.raises(TypeError, match=msg):
+ tdi.values -= dti
+
+ with pytest.raises(TypeError, match=msg):
+ tdi._values -= dti
+
+ # -------------------------------------------------------------
+ # Binary Operations DatetimeIndex and datetime-like
+ # TODO: A couple other tests belong in this section. Move them in
+ # A PR where there isn't already a giant diff.
+
+ # -------------------------------------------------------------
+
+ def test_dta_add_sub_index(self, tz_naive_fixture):
+ # Check that DatetimeArray defers to Index classes
+ dti = date_range("20130101", periods=3, tz=tz_naive_fixture)
+ dta = dti.array
+ result = dta - dti
+ expected = dti - dti
+ tm.assert_index_equal(result, expected)
+
+ tdi = result
+ result = dta + tdi
+ expected = dti + tdi
+ tm.assert_index_equal(result, expected)
+
+ result = dta - tdi
+ expected = dti - tdi
+ tm.assert_index_equal(result, expected)
+
+ def test_sub_dti_dti(self, unit):
+ # previously performed setop (deprecated in 0.16.0), now changed to
+ # return subtraction -> TimeDeltaIndex (GH ...)
+
+ dti = date_range("20130101", periods=3, unit=unit)
+ dti_tz = date_range("20130101", periods=3, unit=unit).tz_localize("US/Eastern")
+ expected = TimedeltaIndex([0, 0, 0]).as_unit(unit)
+
+ result = dti - dti
+ tm.assert_index_equal(result, expected)
+
+ result = dti_tz - dti_tz
+ tm.assert_index_equal(result, expected)
+ msg = "Cannot subtract tz-naive and tz-aware datetime-like objects"
+ with pytest.raises(TypeError, match=msg):
+ dti_tz - dti
+
+ with pytest.raises(TypeError, match=msg):
+ dti - dti_tz
+
+ # isub
+ dti -= dti
+ tm.assert_index_equal(dti, expected)
+
+ # different length raises ValueError
+ dti1 = date_range("20130101", periods=3, unit=unit)
+ dti2 = date_range("20130101", periods=4, unit=unit)
+ msg = "cannot add indices of unequal length"
+ with pytest.raises(ValueError, match=msg):
+ dti1 - dti2
+
+ # NaN propagation
+ dti1 = DatetimeIndex(["2012-01-01", np.nan, "2012-01-03"]).as_unit(unit)
+ dti2 = DatetimeIndex(["2012-01-02", "2012-01-03", np.nan]).as_unit(unit)
+ expected = TimedeltaIndex(["1 days", np.nan, np.nan]).as_unit(unit)
+ result = dti2 - dti1
+ tm.assert_index_equal(result, expected)
+
+ # -------------------------------------------------------------------
+ # TODO: Most of this block is moved from series or frame tests, needs
+ # cleanup, box-parametrization, and de-duplication
+
+ @pytest.mark.parametrize("op", [operator.add, operator.sub])
+ def test_timedelta64_equal_timedelta_supported_ops(self, op, box_with_array):
+ ser = Series(
+ [
+ Timestamp("20130301"),
+ Timestamp("20130228 23:00:00"),
+ Timestamp("20130228 22:00:00"),
+ Timestamp("20130228 21:00:00"),
+ ]
+ )
+ obj = box_with_array(ser)
+
+ intervals = ["D", "h", "m", "s", "us"]
+
+ def timedelta64(*args):
+ # see casting notes in NumPy gh-12927
+ return np.sum(list(map(np.timedelta64, args, intervals)))
+
+ for d, h, m, s, us in product(*([range(2)] * 5)):
+ nptd = timedelta64(d, h, m, s, us)
+ pytd = timedelta(days=d, hours=h, minutes=m, seconds=s, microseconds=us)
+ lhs = op(obj, nptd)
+ rhs = op(obj, pytd)
+
+ tm.assert_equal(lhs, rhs)
+
+ def test_ops_nat_mixed_datetime64_timedelta64(self):
+ # GH#11349
+ timedelta_series = Series([NaT, Timedelta("1s")])
+ datetime_series = Series([NaT, Timestamp("19900315")])
+ nat_series_dtype_timedelta = Series([NaT, NaT], dtype="timedelta64[ns]")
+ nat_series_dtype_timestamp = Series([NaT, NaT], dtype="datetime64[ns]")
+ single_nat_dtype_datetime = Series([NaT], dtype="datetime64[ns]")
+ single_nat_dtype_timedelta = Series([NaT], dtype="timedelta64[ns]")
+
+ # subtraction
+ tm.assert_series_equal(
+ datetime_series - single_nat_dtype_datetime, nat_series_dtype_timedelta
+ )
+
+ tm.assert_series_equal(
+ datetime_series - single_nat_dtype_timedelta, nat_series_dtype_timestamp
+ )
+ tm.assert_series_equal(
+ -single_nat_dtype_timedelta + datetime_series, nat_series_dtype_timestamp
+ )
+
+ # without a Series wrapping the NaT, it is ambiguous
+ # whether it is a datetime64 or timedelta64
+ # defaults to interpreting it as timedelta64
+ tm.assert_series_equal(
+ nat_series_dtype_timestamp - single_nat_dtype_datetime,
+ nat_series_dtype_timedelta,
+ )
+
+ tm.assert_series_equal(
+ nat_series_dtype_timestamp - single_nat_dtype_timedelta,
+ nat_series_dtype_timestamp,
+ )
+ tm.assert_series_equal(
+ -single_nat_dtype_timedelta + nat_series_dtype_timestamp,
+ nat_series_dtype_timestamp,
+ )
+ msg = "cannot subtract a datelike"
+ with pytest.raises(TypeError, match=msg):
+ timedelta_series - single_nat_dtype_datetime
+
+ # addition
+ tm.assert_series_equal(
+ nat_series_dtype_timestamp + single_nat_dtype_timedelta,
+ nat_series_dtype_timestamp,
+ )
+ tm.assert_series_equal(
+ single_nat_dtype_timedelta + nat_series_dtype_timestamp,
+ nat_series_dtype_timestamp,
+ )
+
+ tm.assert_series_equal(
+ nat_series_dtype_timestamp + single_nat_dtype_timedelta,
+ nat_series_dtype_timestamp,
+ )
+ tm.assert_series_equal(
+ single_nat_dtype_timedelta + nat_series_dtype_timestamp,
+ nat_series_dtype_timestamp,
+ )
+
+ tm.assert_series_equal(
+ nat_series_dtype_timedelta + single_nat_dtype_datetime,
+ nat_series_dtype_timestamp,
+ )
+ tm.assert_series_equal(
+ single_nat_dtype_datetime + nat_series_dtype_timedelta,
+ nat_series_dtype_timestamp,
+ )
+
+ def test_ufunc_coercions(self, unit):
+ idx = date_range("2011-01-01", periods=3, freq="2D", name="x", unit=unit)
+
+ delta = np.timedelta64(1, "D")
+ exp = date_range("2011-01-02", periods=3, freq="2D", name="x", unit=unit)
+ for result in [idx + delta, np.add(idx, delta)]:
+ assert isinstance(result, DatetimeIndex)
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "2D"
+
+ exp = date_range("2010-12-31", periods=3, freq="2D", name="x", unit=unit)
+
+ for result in [idx - delta, np.subtract(idx, delta)]:
+ assert isinstance(result, DatetimeIndex)
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "2D"
+
+ # When adding/subtracting an ndarray (which has no .freq), the result
+ # does not infer freq
+ idx = idx._with_freq(None)
+ delta = np.array(
+ [np.timedelta64(1, "D"), np.timedelta64(2, "D"), np.timedelta64(3, "D")]
+ )
+ exp = DatetimeIndex(
+ ["2011-01-02", "2011-01-05", "2011-01-08"], name="x"
+ ).as_unit(unit)
+
+ for result in [idx + delta, np.add(idx, delta)]:
+ tm.assert_index_equal(result, exp)
+ assert result.freq == exp.freq
+
+ exp = DatetimeIndex(
+ ["2010-12-31", "2011-01-01", "2011-01-02"], name="x"
+ ).as_unit(unit)
+ for result in [idx - delta, np.subtract(idx, delta)]:
+ assert isinstance(result, DatetimeIndex)
+ tm.assert_index_equal(result, exp)
+ assert result.freq == exp.freq
+
+ def test_dti_add_series(self, tz_naive_fixture, names):
+ # GH#13905
+ tz = tz_naive_fixture
+ index = DatetimeIndex(
+ ["2016-06-28 05:30", "2016-06-28 05:31"], tz=tz, name=names[0]
+ ).as_unit("ns")
+ ser = Series([Timedelta(seconds=5)] * 2, index=index, name=names[1])
+ expected = Series(index + Timedelta(seconds=5), index=index, name=names[2])
+
+ # passing name arg isn't enough when names[2] is None
+ expected.name = names[2]
+ assert expected.dtype == index.dtype
+ result = ser + index
+ tm.assert_series_equal(result, expected)
+ result2 = index + ser
+ tm.assert_series_equal(result2, expected)
+
+ expected = index + Timedelta(seconds=5)
+ result3 = ser.values + index
+ tm.assert_index_equal(result3, expected)
+ result4 = index + ser.values
+ tm.assert_index_equal(result4, expected)
+
+ @pytest.mark.parametrize("op", [operator.add, roperator.radd, operator.sub])
+ def test_dti_addsub_offset_arraylike(
+ self, performance_warning, tz_naive_fixture, names, op, index_or_series
+ ):
+ # GH#18849, GH#19744
+ other_box = index_or_series
+
+ tz = tz_naive_fixture
+ dti = date_range("2017-01-01", periods=2, tz=tz, name=names[0])
+ other = other_box([pd.offsets.MonthEnd(), pd.offsets.Day(n=2)], name=names[1])
+
+ xbox = get_upcast_box(dti, other)
+
+ with tm.assert_produces_warning(performance_warning):
+ res = op(dti, other)
+
+ expected = DatetimeIndex(
+ [op(dti[n], other[n]) for n in range(len(dti))], name=names[2], freq="infer"
+ )
+ expected = tm.box_expected(expected, xbox).astype(object)
+ tm.assert_equal(res, expected)
+
+ @pytest.mark.parametrize("other_box", [pd.Index, np.array])
+ def test_dti_addsub_object_arraylike(
+ self, performance_warning, tz_naive_fixture, box_with_array, other_box
+ ):
+ tz = tz_naive_fixture
+
+ dti = date_range("2017-01-01", periods=2, tz=tz)
+ dtarr = tm.box_expected(dti, box_with_array)
+ other = other_box([pd.offsets.MonthEnd(), Timedelta(days=4)])
+ xbox = get_upcast_box(dtarr, other)
+
+ expected = DatetimeIndex(["2017-01-31", "2017-01-06"], tz=tz_naive_fixture)
+ expected = tm.box_expected(expected, xbox).astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ result = dtarr + other
+ tm.assert_equal(result, expected)
+
+ expected = DatetimeIndex(["2016-12-31", "2016-12-29"], tz=tz_naive_fixture)
+ expected = tm.box_expected(expected, xbox).astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ result = dtarr - other
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize("years", [-1, 0, 1])
+@pytest.mark.parametrize("months", [-2, 0, 2])
+def test_shift_months(years, months, unit):
+ dti = DatetimeIndex(
+ [
+ Timestamp("2000-01-05 00:15:00"),
+ Timestamp("2000-01-31 00:23:00"),
+ Timestamp("2000-01-01"),
+ Timestamp("2000-02-29"),
+ Timestamp("2000-12-31"),
+ ]
+ ).as_unit(unit)
+ shifted = shift_months(dti.asi8, years * 12 + months, reso=dti._data._creso)
+ shifted_dt64 = shifted.view(f"M8[{dti.unit}]")
+ actual = DatetimeIndex(shifted_dt64)
+
+ raw = [x + pd.offsets.DateOffset(years=years, months=months) for x in dti]
+ expected = DatetimeIndex(raw).as_unit(dti.unit)
+ tm.assert_index_equal(actual, expected)
+
+
+def test_dt64arr_addsub_object_dtype_2d(performance_warning):
+ # block-wise DataFrame operations will require operating on 2D
+ # DatetimeArray/TimedeltaArray, so check that specifically.
+ dti = date_range("1994-02-13", freq="2W", periods=4)
+ dta = dti._data.reshape((4, 1))
+
+ other = np.array([[pd.offsets.Day(n)] for n in range(4)])
+ assert other.shape == dta.shape
+
+ with tm.assert_produces_warning(performance_warning):
+ result = dta + other
+ with tm.assert_produces_warning(performance_warning):
+ expected = (dta[:, 0] + other[:, 0]).reshape(-1, 1)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ with tm.assert_produces_warning(performance_warning):
+ # Case where we expect to get a TimedeltaArray back
+ result2 = dta - dta.astype(object)
+
+ assert result2.shape == (4, 1)
+ assert all(td._value == 0 for td in result2.ravel())
+
+
+def test_non_nano_dt64_addsub_np_nat_scalars():
+ # GH 52295
+ ser = Series([1233242342344, 232432434324, 332434242344], dtype="datetime64[ms]")
+ result = ser - np.datetime64("nat", "ms")
+ expected = Series([NaT] * 3, dtype="timedelta64[ms]")
+ tm.assert_series_equal(result, expected)
+
+ result = ser + np.timedelta64("nat", "ms")
+ expected = Series([NaT] * 3, dtype="datetime64[ms]")
+ tm.assert_series_equal(result, expected)
+
+
+def test_non_nano_dt64_addsub_np_nat_scalars_unitless():
+ # GH 52295
+ # TODO: Can we default to the ser unit?
+ ser = Series([1233242342344, 232432434324, 332434242344], dtype="datetime64[ms]")
+ result = ser - np.datetime64("nat")
+ expected = Series([NaT] * 3, dtype="timedelta64[ms]")
+ tm.assert_series_equal(result, expected)
+
+ result = ser + np.timedelta64("nat")
+ expected = Series([NaT] * 3, dtype="datetime64[ms]")
+ tm.assert_series_equal(result, expected)
+
+
+def test_non_nano_dt64_addsub_np_nat_scalars_unsupported_unit():
+ # GH 52295
+ ser = Series([12332, 23243, 33243], dtype="datetime64[s]")
+ result = ser - np.datetime64("nat", "D")
+ expected = Series([NaT] * 3, dtype="timedelta64[s]")
+ tm.assert_series_equal(result, expected)
+
+ result = ser + np.timedelta64("nat", "D")
+ expected = Series([NaT] * 3, dtype="datetime64[s]")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_interval.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_interval.py
new file mode 100644
index 0000000000000000000000000000000000000000..e2353da0dad568875ba150570b52262aa81d0ea4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_interval.py
@@ -0,0 +1,308 @@
+import operator
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import is_list_like
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ Index,
+ Interval,
+ IntervalIndex,
+ Period,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import (
+ BooleanArray,
+ IntervalArray,
+)
+from pandas.tests.arithmetic.common import get_upcast_box
+
+
+@pytest.fixture(
+ params=[
+ (Index([0, 2, 4, 4]), Index([1, 3, 5, 8])),
+ (Index([0.0, 1.0, 2.0, np.nan]), Index([1.0, 2.0, 3.0, np.nan])),
+ (
+ timedelta_range("0 days", periods=3).insert(3, pd.NaT),
+ timedelta_range("1 day", periods=3).insert(3, pd.NaT),
+ ),
+ (
+ date_range("20170101", periods=3).insert(3, pd.NaT),
+ date_range("20170102", periods=3).insert(3, pd.NaT),
+ ),
+ (
+ date_range("20170101", periods=3, tz="US/Eastern").insert(3, pd.NaT),
+ date_range("20170102", periods=3, tz="US/Eastern").insert(3, pd.NaT),
+ ),
+ ],
+ ids=lambda x: str(x[0].dtype),
+)
+def left_right_dtypes(request):
+ """
+ Fixture for building an IntervalArray from various dtypes
+ """
+ return request.param
+
+
+@pytest.fixture
+def interval_array(left_right_dtypes):
+ """
+ Fixture to generate an IntervalArray of various dtypes containing NA if possible
+ """
+ left, right = left_right_dtypes
+ return IntervalArray.from_arrays(left, right)
+
+
+def create_categorical_intervals(left, right, closed="right"):
+ return Categorical(IntervalIndex.from_arrays(left, right, closed))
+
+
+def create_series_intervals(left, right, closed="right"):
+ return Series(IntervalArray.from_arrays(left, right, closed))
+
+
+def create_series_categorical_intervals(left, right, closed="right"):
+ return Series(Categorical(IntervalIndex.from_arrays(left, right, closed)))
+
+
+class TestComparison:
+ @pytest.fixture(params=[operator.eq, operator.ne])
+ def op(self, request):
+ return request.param
+
+ @pytest.fixture(
+ params=[
+ IntervalArray.from_arrays,
+ IntervalIndex.from_arrays,
+ create_categorical_intervals,
+ create_series_intervals,
+ create_series_categorical_intervals,
+ ],
+ ids=[
+ "IntervalArray",
+ "IntervalIndex",
+ "Categorical[Interval]",
+ "Series[Interval]",
+ "Series[Categorical[Interval]]",
+ ],
+ )
+ def interval_constructor(self, request):
+ """
+ Fixture for all pandas native interval constructors.
+ To be used as the LHS of IntervalArray comparisons.
+ """
+ return request.param
+
+ def elementwise_comparison(self, op, interval_array, other):
+ """
+ Helper that performs elementwise comparisons between `array` and `other`
+ """
+ other = other if is_list_like(other) else [other] * len(interval_array)
+ expected = np.array(
+ [op(x, y) for x, y in zip(interval_array, other, strict=True)]
+ )
+ if isinstance(other, Series):
+ return Series(expected, index=other.index)
+ return expected
+
+ def test_compare_scalar_interval(self, op, interval_array):
+ # matches first interval
+ other = interval_array[0]
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # matches on a single endpoint but not both
+ other = Interval(interval_array.left[0], interval_array.right[1])
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_compare_scalar_interval_mixed_closed(self, op, closed, other_closed):
+ interval_array = IntervalArray.from_arrays(range(2), range(1, 3), closed=closed)
+ other = Interval(0, 1, closed=other_closed)
+
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_compare_scalar_na(self, op, interval_array, nulls_fixture, box_with_array):
+ box = box_with_array
+ obj = tm.box_expected(interval_array, box)
+ result = op(obj, nulls_fixture)
+
+ if nulls_fixture is pd.NA:
+ # GH#31882
+ exp = np.ones(interval_array.shape, dtype=bool)
+ expected = BooleanArray(exp, exp)
+ else:
+ expected = self.elementwise_comparison(op, interval_array, nulls_fixture)
+
+ if not (box is Index and nulls_fixture is pd.NA):
+ # don't cast expected from BooleanArray to ndarray[object]
+ xbox = get_upcast_box(obj, nulls_fixture, True)
+ expected = tm.box_expected(expected, xbox)
+
+ tm.assert_equal(result, expected)
+
+ rev = op(nulls_fixture, obj)
+ tm.assert_equal(rev, expected)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ 0,
+ 1.0,
+ True,
+ "foo",
+ Timestamp("2017-01-01"),
+ Timestamp("2017-01-01", tz="US/Eastern"),
+ Timedelta("0 days"),
+ Period("2017-01-01", "D"),
+ ],
+ )
+ def test_compare_scalar_other(self, op, interval_array, other):
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_compare_list_like_interval(self, op, interval_array, interval_constructor):
+ # same endpoints
+ other = interval_constructor(interval_array.left, interval_array.right)
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_equal(result, expected)
+
+ # different endpoints
+ other = interval_constructor(
+ interval_array.left[::-1], interval_array.right[::-1]
+ )
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_equal(result, expected)
+
+ # all nan endpoints
+ other = interval_constructor([np.nan] * 4, [np.nan] * 4)
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_equal(result, expected)
+
+ def test_compare_list_like_interval_mixed_closed(
+ self, op, interval_constructor, closed, other_closed
+ ):
+ interval_array = IntervalArray.from_arrays(range(2), range(1, 3), closed=closed)
+ other = interval_constructor(range(2), range(1, 3), closed=other_closed)
+
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ (
+ Interval(0, 1),
+ Interval(Timedelta("1 day"), Timedelta("2 days")),
+ Interval(4, 5, "both"),
+ Interval(10, 20, "neither"),
+ ),
+ (0, 1.5, Timestamp("20170103"), np.nan),
+ (
+ Timestamp("20170102", tz="US/Eastern"),
+ Timedelta("2 days"),
+ "baz",
+ pd.NaT,
+ ),
+ ],
+ )
+ def test_compare_list_like_object(self, op, interval_array, other):
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_compare_list_like_nan(self, op, interval_array, nulls_fixture):
+ other = [nulls_fixture] * 4
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ np.arange(4, dtype="int64"),
+ np.arange(4, dtype="float64"),
+ date_range("2017-01-01", periods=4),
+ date_range("2017-01-01", periods=4, tz="US/Eastern"),
+ timedelta_range("0 days", periods=4),
+ period_range("2017-01-01", periods=4, freq="D"),
+ Categorical(list("abab")),
+ Categorical(date_range("2017-01-01", periods=4)),
+ pd.array(list("abcd")),
+ pd.array(["foo", 3.14, None, object()], dtype=object),
+ ],
+ ids=lambda x: str(x.dtype),
+ )
+ def test_compare_list_like_other(self, op, interval_array, other):
+ result = op(interval_array, other)
+ expected = self.elementwise_comparison(op, interval_array, other)
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("length", [1, 3, 5])
+ @pytest.mark.parametrize("other_constructor", [IntervalArray, list])
+ def test_compare_length_mismatch_errors(self, op, other_constructor, length):
+ interval_array = IntervalArray.from_arrays(range(4), range(1, 5))
+ other = other_constructor([Interval(0, 1)] * length)
+ with pytest.raises(ValueError, match="Lengths must match to compare"):
+ op(interval_array, other)
+
+ @pytest.mark.parametrize(
+ "constructor, expected_type, assert_func",
+ [
+ (IntervalIndex, np.array, tm.assert_numpy_array_equal),
+ (Series, Series, tm.assert_series_equal),
+ ],
+ )
+ def test_index_series_compat(self, op, constructor, expected_type, assert_func):
+ # IntervalIndex/Series that rely on IntervalArray for comparisons
+ breaks = range(4)
+ index = constructor(IntervalIndex.from_breaks(breaks))
+
+ # scalar comparisons
+ other = index[0]
+ result = op(index, other)
+ expected = expected_type(self.elementwise_comparison(op, index, other))
+ assert_func(result, expected)
+
+ other = breaks[0]
+ result = op(index, other)
+ expected = expected_type(self.elementwise_comparison(op, index, other))
+ assert_func(result, expected)
+
+ # list-like comparisons
+ other = IntervalArray.from_breaks(breaks)
+ result = op(index, other)
+ expected = expected_type(self.elementwise_comparison(op, index, other))
+ assert_func(result, expected)
+
+ other = [index[0], breaks[0], "foo"]
+ result = op(index, other)
+ expected = expected_type(self.elementwise_comparison(op, index, other))
+ assert_func(result, expected)
+
+ @pytest.mark.parametrize("scalars", ["a", False, 1, 1.0, None])
+ def test_comparison_operations(self, scalars):
+ # GH #28981
+ expected = Series([False, False])
+ s = Series([Interval(0, 1), Interval(1, 2)], dtype="interval")
+ result = s == scalars
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_numeric.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_numeric.py
new file mode 100644
index 0000000000000000000000000000000000000000..5878246126d617e96dbb11b2d437e78fe87a3aec
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_numeric.py
@@ -0,0 +1,1585 @@
+# Arithmetic tests for DataFrame/Series/Index/Array classes that should
+# behave identically.
+# Specifically for numeric dtypes
+from __future__ import annotations
+
+from collections import abc
+from datetime import timedelta
+from decimal import Decimal
+import operator
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ RangeIndex,
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ array,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.core import ops
+from pandas.core.computation import expressions as expr
+from pandas.tests.arithmetic.common import (
+ assert_invalid_addsub_type,
+ assert_invalid_comparison,
+)
+
+
+@pytest.fixture(autouse=True, params=[0, 1000000], ids=["numexpr", "python"])
+def switch_numexpr_min_elements(request, monkeypatch):
+ with monkeypatch.context() as m:
+ m.setattr(expr, "_MIN_ELEMENTS", request.param)
+ yield request.param
+
+
+@pytest.fixture(
+ params=[
+ # TODO: add more dtypes here
+ Index(np.arange(5, dtype="float64")),
+ Index(np.arange(5, dtype="int64")),
+ Index(np.arange(5, dtype="uint64")),
+ RangeIndex(5),
+ ],
+ ids=lambda x: type(x).__name__,
+)
+def numeric_idx(request):
+ """
+ Several types of numeric-dtypes Index objects
+ """
+ return request.param
+
+
+def adjust_negative_zero(zero, expected):
+ """
+ Helper to adjust the expected result if we are dividing by -0.0
+ as opposed to 0.0
+ """
+ if np.signbit(np.array(zero)).any():
+ # All entries in the `zero` fixture should be either
+ # all-negative or no-negative.
+ assert np.signbit(np.array(zero)).all()
+
+ expected *= -1
+
+ return expected
+
+
+def compare_op(series, other, op):
+ left = np.abs(series) if op in (ops.rpow, operator.pow) else series
+ right = np.abs(other) if op in (ops.rpow, operator.pow) else other
+
+ cython_or_numpy = op(left, right)
+ python = left.combine(right, op)
+ if isinstance(other, Series) and not other.index.equals(series.index):
+ python.index = python.index._with_freq(None)
+ tm.assert_series_equal(cython_or_numpy, python)
+
+
+# TODO: remove this kludge once mypy stops giving false positives here
+# List comprehension has incompatible type List[PandasObject]; expected List[RangeIndex]
+# See GH#29725
+_ldtypes = ["i1", "i2", "i4", "i8", "u1", "u2", "u4", "u8", "f2", "f4", "f8"]
+lefts: list[Index | Series] = [RangeIndex(10, 40, 10)]
+lefts.extend([Series([10, 20, 30], dtype=dtype) for dtype in _ldtypes])
+lefts.extend([Index([10, 20, 30], dtype=dtype) for dtype in _ldtypes if dtype != "f2"])
+
+# ------------------------------------------------------------------
+# Comparisons
+
+
+class TestNumericComparisons:
+ def test_operator_series_comparison_zerorank(self):
+ # GH#13006
+ result = np.float64(0) > Series([1, 2, 3])
+ expected = 0.0 > Series([1, 2, 3])
+ tm.assert_series_equal(result, expected)
+ result = Series([1, 2, 3]) < np.float64(0)
+ expected = Series([1, 2, 3]) < 0.0
+ tm.assert_series_equal(result, expected)
+ result = np.array([0, 1, 2])[0] > Series([0, 1, 2])
+ expected = 0.0 > Series([1, 2, 3])
+ tm.assert_series_equal(result, expected)
+
+ def test_df_numeric_cmp_dt64_raises(self, box_with_array, fixed_now_ts):
+ # GH#8932, GH#22163
+ ts = fixed_now_ts
+ obj = np.array(range(5))
+ obj = tm.box_expected(obj, box_with_array)
+
+ assert_invalid_comparison(obj, ts, box_with_array)
+
+ def test_compare_invalid(self):
+ # GH#8058
+ # ops testing
+ a = Series(np.random.default_rng(2).standard_normal(5), name=0)
+ b = Series(np.random.default_rng(2).standard_normal(5))
+ b.name = pd.Timestamp("2000-01-01")
+ tm.assert_series_equal(a / b, 1 / (b / a))
+
+ def test_numeric_cmp_string_numexpr_path(self, box_with_array, monkeypatch):
+ # GH#36377, GH#35700
+ box = box_with_array
+ xbox = box if box is not Index else np.ndarray
+
+ obj = Series(np.random.default_rng(2).standard_normal(51))
+ obj = tm.box_expected(obj, box, transpose=False)
+ with monkeypatch.context() as m:
+ m.setattr(expr, "_MIN_ELEMENTS", 50)
+ result = obj == "a"
+
+ expected = Series(np.zeros(51, dtype=bool))
+ expected = tm.box_expected(expected, xbox, transpose=False)
+ tm.assert_equal(result, expected)
+
+ with monkeypatch.context() as m:
+ m.setattr(expr, "_MIN_ELEMENTS", 50)
+ result = obj != "a"
+ tm.assert_equal(result, ~expected)
+
+ msg = "Invalid comparison between dtype=float64 and str"
+ with pytest.raises(TypeError, match=msg):
+ obj < "a"
+
+
+# ------------------------------------------------------------------
+# Numeric dtypes Arithmetic with Datetime/Timedelta Scalar
+
+
+class TestNumericArraylikeArithmeticWithDatetimeLike:
+ def test_mul_timedelta_list(self, box_with_array):
+ # GH#62524
+ box = box_with_array
+ left = np.array([3, 4])
+ left = tm.box_expected(left, box)
+
+ right = [Timedelta(days=1), Timedelta(days=2)]
+
+ result = left * right
+
+ expected = TimedeltaIndex([Timedelta(days=3), Timedelta(days=8)])
+ expected = tm.box_expected(expected, box)
+ tm.assert_equal(result, expected)
+
+ result2 = right * left
+ tm.assert_equal(result2, expected)
+
+ @pytest.mark.parametrize("box_cls", [np.array, Index, Series])
+ @pytest.mark.parametrize(
+ "left", lefts, ids=lambda x: type(x).__name__ + str(x.dtype)
+ )
+ def test_mul_td64arr(self, left, box_cls):
+ # GH#22390
+ right = np.array([1, 2, 3], dtype="m8[s]")
+ right = box_cls(right)
+
+ expected = TimedeltaIndex(["10s", "40s", "90s"], dtype=right.dtype)
+
+ if isinstance(left, Series) or box_cls is Series:
+ expected = Series(expected)
+ assert expected.dtype == right.dtype
+
+ result = left * right
+ tm.assert_equal(result, expected)
+
+ result = right * left
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("box_cls", [np.array, Index, Series])
+ @pytest.mark.parametrize(
+ "left", lefts, ids=lambda x: type(x).__name__ + str(x.dtype)
+ )
+ def test_div_td64arr(self, left, box_cls):
+ # GH#22390
+ right = np.array([10, 40, 90], dtype="m8[s]")
+ right = box_cls(right)
+
+ expected = TimedeltaIndex(["1s", "2s", "3s"], dtype=right.dtype)
+ if isinstance(left, Series) or box_cls is Series:
+ expected = Series(expected)
+ assert expected.dtype == right.dtype
+
+ result = right / left
+ tm.assert_equal(result, expected)
+
+ result = right // left
+ tm.assert_equal(result, expected)
+
+ # (true_) needed for min-versions build 2022-12-26
+ msg = "ufunc '(true_)?divide' cannot use operands with types"
+ with pytest.raises(TypeError, match=msg):
+ left / right
+
+ msg = "ufunc 'floor_divide' cannot use operands with types"
+ with pytest.raises(TypeError, match=msg):
+ left // right
+
+ # TODO: also test Tick objects;
+ # see test_numeric_arr_rdiv_tdscalar for note on these failing
+ @pytest.mark.parametrize(
+ "scalar_td",
+ [
+ Timedelta(days=1).as_unit("ns"),
+ Timedelta(days=1).as_unit("ns").to_timedelta64(),
+ Timedelta(days=1).to_pytimedelta(),
+ Timedelta(days=1).to_timedelta64().astype("timedelta64[s]"),
+ Timedelta(days=1).to_timedelta64().astype("timedelta64[ms]"),
+ ],
+ ids=lambda x: type(x).__name__,
+ )
+ def test_numeric_arr_mul_tdscalar(self, scalar_td, numeric_idx, box_with_array):
+ # GH#19333
+ box = box_with_array
+ index = numeric_idx
+ expected = TimedeltaIndex(
+ [Timedelta(days=n) for n in range(len(index))], dtype="m8[ns]"
+ )
+ if isinstance(scalar_td, np.timedelta64):
+ dtype = scalar_td.dtype
+ expected = expected.astype(dtype)
+ elif type(scalar_td) is timedelta:
+ expected = expected.astype("m8[us]")
+
+ index = tm.box_expected(index, box)
+ expected = tm.box_expected(expected, box)
+
+ result = index * scalar_td
+ tm.assert_equal(result, expected)
+
+ commute = scalar_td * index
+ tm.assert_equal(commute, expected)
+
+ @pytest.mark.parametrize(
+ "scalar_td",
+ [
+ Timedelta(days=1).as_unit("ns"),
+ Timedelta(days=1).as_unit("ns").to_timedelta64(),
+ Timedelta(days=1).as_unit("ns").to_pytimedelta(),
+ ],
+ ids=lambda x: type(x).__name__,
+ )
+ @pytest.mark.parametrize("dtype", [np.int64, np.float64])
+ def test_numeric_arr_mul_tdscalar_numexpr_path(
+ self, dtype, scalar_td, box_with_array
+ ):
+ # GH#44772 for the float64 case
+ box = box_with_array
+
+ arr_i8 = np.arange(2 * 10**4).astype(np.int64, copy=False)
+ arr = arr_i8.astype(dtype, copy=False)
+ obj = tm.box_expected(arr, box, transpose=False)
+
+ expected = arr_i8.view("timedelta64[D]").astype("timedelta64[ns]")
+ if type(scalar_td) is timedelta:
+ expected = expected.astype("timedelta64[us]")
+
+ expected = tm.box_expected(expected, box, transpose=False)
+
+ result = obj * scalar_td
+ tm.assert_equal(result, expected)
+
+ result = scalar_td * obj
+ tm.assert_equal(result, expected)
+
+ def test_numeric_arr_rdiv_tdscalar(self, three_days, numeric_idx, box_with_array):
+ box = box_with_array
+
+ index = numeric_idx[1:3]
+
+ expected = TimedeltaIndex(["3 Days", "36 Hours"])
+ if isinstance(three_days, np.timedelta64):
+ dtype = three_days.dtype
+ if dtype < np.dtype("m8[s]"):
+ # i.e. resolution is lower -> use lowest supported resolution
+ dtype = np.dtype("m8[s]")
+ expected = expected.astype(dtype)
+ elif type(three_days) is timedelta or (
+ isinstance(three_days, Timedelta) and three_days.unit == "us"
+ ):
+ expected = expected.astype("m8[us]")
+ elif isinstance(
+ three_days,
+ (pd.offsets.Day, pd.offsets.Hour, pd.offsets.Minute, pd.offsets.Second),
+ ):
+ # closest reso is Second
+ expected = expected.astype("m8[s]")
+
+ index = tm.box_expected(index, box)
+ expected = tm.box_expected(expected, box)
+
+ if isinstance(three_days, pd.offsets.Day):
+ # GH#41943 Day is no longer timedelta-like
+ msg = "unsupported operand type"
+ with pytest.raises(TypeError, match=msg):
+ three_days / index
+ else:
+ result = three_days / index
+ tm.assert_equal(result, expected)
+ msg = "cannot use operands with types dtype"
+
+ with pytest.raises(TypeError, match=msg):
+ index / three_days
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ Timedelta(hours=31),
+ Timedelta(hours=31).to_pytimedelta(),
+ Timedelta(hours=31).to_timedelta64(),
+ Timedelta(hours=31).to_timedelta64().astype("m8[h]"),
+ np.timedelta64("NaT"),
+ np.timedelta64("NaT", "D"),
+ pd.offsets.Minute(3),
+ pd.offsets.Second(0),
+ # GH#28080 numeric+datetimelike should raise; Timestamp used
+ # to raise NullFrequencyError but that behavior was removed in 1.0
+ pd.Timestamp("2021-01-01", tz="Asia/Tokyo"),
+ pd.Timestamp("2021-01-01"),
+ pd.Timestamp("2021-01-01").to_pydatetime(),
+ pd.Timestamp("2021-01-01", tz="UTC").to_pydatetime(),
+ pd.Timestamp("2021-01-01").to_datetime64(),
+ np.datetime64("NaT", "ns"),
+ pd.NaT,
+ ],
+ ids=repr,
+ )
+ def test_add_sub_datetimedeltalike_invalid(
+ self, numeric_idx, other, box_with_array
+ ):
+ box = box_with_array
+
+ left = tm.box_expected(numeric_idx, box)
+ msg = "|".join(
+ [
+ "unsupported operand type",
+ "Addition/subtraction of integers and integer-arrays",
+ "Instead of adding/subtracting",
+ "cannot use operands with types dtype",
+ "Concatenation operation is not implemented for NumPy arrays",
+ "Cannot (add|subtract) NaT (to|from) ndarray",
+ # pd.array vs np.datetime64 case
+ r"operand type\(s\) all returned NotImplemented from __array_ufunc__",
+ "can only perform ops with numeric values",
+ "cannot subtract DatetimeArray from ndarray",
+ # pd.Timedelta(1) + Index([0, 1, 2])
+ "Cannot add or subtract Timedelta from integers",
+ ]
+ )
+ assert_invalid_addsub_type(left, other, msg)
+
+
+# ------------------------------------------------------------------
+# Arithmetic
+
+
+class TestDivisionByZero:
+ def test_div_zero(self, zero, numeric_idx):
+ idx = numeric_idx
+
+ expected = Index([np.nan, np.inf, np.inf, np.inf, np.inf], dtype=np.float64)
+ # We only adjust for Index, because Series does not yet apply
+ # the adjustment correctly.
+ expected2 = adjust_negative_zero(zero, expected)
+
+ result = idx / zero
+ tm.assert_index_equal(result, expected2)
+ ser_compat = Series(idx).astype("i8") / np.array(zero).astype("i8")
+ tm.assert_series_equal(ser_compat, Series(expected))
+
+ def test_floordiv_zero(self, zero, numeric_idx):
+ idx = numeric_idx
+
+ expected = Index([np.nan, np.inf, np.inf, np.inf, np.inf], dtype=np.float64)
+ # We only adjust for Index, because Series does not yet apply
+ # the adjustment correctly.
+ expected2 = adjust_negative_zero(zero, expected)
+
+ result = idx // zero
+ tm.assert_index_equal(result, expected2)
+ ser_compat = Series(idx).astype("i8") // np.array(zero).astype("i8")
+ tm.assert_series_equal(ser_compat, Series(expected))
+
+ def test_mod_zero(self, zero, numeric_idx):
+ idx = numeric_idx
+
+ expected = Index([np.nan, np.nan, np.nan, np.nan, np.nan], dtype=np.float64)
+ result = idx % zero
+ tm.assert_index_equal(result, expected)
+ ser_compat = Series(idx).astype("i8") % np.array(zero).astype("i8")
+ tm.assert_series_equal(ser_compat, Series(result))
+
+ def test_divmod_zero(self, zero, numeric_idx):
+ idx = numeric_idx
+
+ exleft = Index([np.nan, np.inf, np.inf, np.inf, np.inf], dtype=np.float64)
+ exright = Index([np.nan, np.nan, np.nan, np.nan, np.nan], dtype=np.float64)
+ exleft = adjust_negative_zero(zero, exleft)
+
+ result = divmod(idx, zero)
+ tm.assert_index_equal(result[0], exleft)
+ tm.assert_index_equal(result[1], exright)
+
+ @pytest.mark.parametrize("op", [operator.truediv, operator.floordiv])
+ def test_div_negative_zero(self, zero, numeric_idx, op):
+ # Check that -1 / -0.0 returns np.inf, not -np.inf
+ if numeric_idx.dtype == np.uint64:
+ pytest.skip(f"Div by negative 0 not relevant for {numeric_idx.dtype}")
+ idx = numeric_idx - 3
+
+ expected = Index([-np.inf, -np.inf, -np.inf, np.nan, np.inf], dtype=np.float64)
+ expected = adjust_negative_zero(zero, expected)
+
+ result = op(idx, zero)
+ tm.assert_index_equal(result, expected)
+
+ # ------------------------------------------------------------------
+
+ @pytest.mark.parametrize("dtype1", [np.int64, np.float64, np.uint64])
+ def test_ser_div_ser(
+ self,
+ switch_numexpr_min_elements,
+ dtype1,
+ any_real_numpy_dtype,
+ ):
+ # no longer do integer div for any ops, but deal with the 0's
+ dtype2 = any_real_numpy_dtype
+
+ first = Series([3, 4, 5, 8], name="first").astype(dtype1)
+ second = Series([0, 0, 0, 3], name="second").astype(dtype2)
+
+ with np.errstate(all="ignore"):
+ expected = Series(
+ first.values.astype(np.float64) / second.values,
+ dtype="float64",
+ name=None,
+ )
+ expected.iloc[0:3] = np.inf
+ if first.dtype == "int64" and second.dtype == "float32":
+ # when using numexpr, the casting rules are slightly different
+ # and int64/float32 combo results in float32 instead of float64
+ if expr.USE_NUMEXPR and switch_numexpr_min_elements == 0:
+ expected = expected.astype("float32")
+
+ result = first / second
+ tm.assert_series_equal(result, expected)
+ assert not result.equals(second / first)
+
+ @pytest.mark.parametrize("dtype1", [np.int64, np.float64, np.uint64])
+ def test_ser_divmod_zero(self, dtype1, any_real_numpy_dtype):
+ # GH#26987
+ dtype2 = any_real_numpy_dtype
+ left = Series([1, 1]).astype(dtype1)
+ right = Series([0, 2]).astype(dtype2)
+
+ # GH#27321 pandas convention is to set 1 // 0 to np.inf, as opposed
+ # to numpy which sets to np.nan; patch `expected[0]` below
+ expected = left // right, left % right
+ expected = list(expected)
+ expected[0] = expected[0].astype(np.float64)
+ expected[0][0] = np.inf
+ result = divmod(left, right)
+
+ tm.assert_series_equal(result[0], expected[0])
+ tm.assert_series_equal(result[1], expected[1])
+
+ # rdivmod case
+ result = divmod(left.values, right)
+ tm.assert_series_equal(result[0], expected[0])
+ tm.assert_series_equal(result[1], expected[1])
+
+ def test_ser_divmod_inf(self):
+ left = Series([np.inf, 1.0])
+ right = Series([np.inf, 2.0])
+
+ expected = left // right, left % right
+ result = divmod(left, right)
+
+ tm.assert_series_equal(result[0], expected[0])
+ tm.assert_series_equal(result[1], expected[1])
+
+ # rdivmod case
+ result = divmod(left.values, right)
+ tm.assert_series_equal(result[0], expected[0])
+ tm.assert_series_equal(result[1], expected[1])
+
+ def test_rdiv_zero_compat(self):
+ # GH#8674
+ zero_array = np.array([0] * 5)
+ data = np.random.default_rng(2).standard_normal(5)
+ expected = Series([0.0] * 5)
+
+ result = zero_array / Series(data)
+ tm.assert_series_equal(result, expected)
+
+ result = Series(zero_array) / data
+ tm.assert_series_equal(result, expected)
+
+ result = Series(zero_array) / Series(data)
+ tm.assert_series_equal(result, expected)
+
+ def test_div_zero_inf_signs(self):
+ # GH#9144, inf signing
+ ser = Series([-1, 0, 1], name="first")
+ expected = Series([-np.inf, np.nan, np.inf], name="first")
+
+ result = ser / 0
+ tm.assert_series_equal(result, expected)
+
+ def test_rdiv_zero(self):
+ # GH#9144
+ ser = Series([-1, 0, 1], name="first")
+ expected = Series([0.0, np.nan, 0.0], name="first")
+
+ result = 0 / ser
+ tm.assert_series_equal(result, expected)
+
+ def test_floordiv_div(self):
+ # GH#9144
+ ser = Series([-1, 0, 1], name="first")
+
+ result = ser // 0
+ expected = Series([-np.inf, np.nan, np.inf], name="first")
+ tm.assert_series_equal(result, expected)
+
+ def test_df_div_zero_df(self):
+ # integer div, but deal with the 0's (GH#9144)
+ df = pd.DataFrame({"first": [3, 4, 5, 8], "second": [0, 0, 0, 3]})
+ result = df / df
+
+ first = Series([1.0, 1.0, 1.0, 1.0])
+ second = Series([np.nan, np.nan, np.nan, 1])
+ expected = pd.DataFrame({"first": first, "second": second})
+ tm.assert_frame_equal(result, expected)
+
+ def test_df_div_zero_array(self):
+ # integer div, but deal with the 0's (GH#9144)
+ df = pd.DataFrame({"first": [3, 4, 5, 8], "second": [0, 0, 0, 3]})
+
+ first = Series([1.0, 1.0, 1.0, 1.0])
+ second = Series([np.nan, np.nan, np.nan, 1])
+ expected = pd.DataFrame({"first": first, "second": second})
+
+ with np.errstate(all="ignore"):
+ arr = df.values.astype("float") / df.values
+ result = pd.DataFrame(arr, index=df.index, columns=df.columns)
+ tm.assert_frame_equal(result, expected)
+
+ def test_df_div_zero_int(self):
+ # integer div, but deal with the 0's (GH#9144)
+ df = pd.DataFrame({"first": [3, 4, 5, 8], "second": [0, 0, 0, 3]})
+
+ result = df / 0
+ expected = pd.DataFrame(np.inf, index=df.index, columns=df.columns)
+ expected.iloc[0:3, 1] = np.nan
+ tm.assert_frame_equal(result, expected)
+
+ # numpy has a slightly different (wrong) treatment
+ with np.errstate(all="ignore"):
+ arr = df.values.astype("float64") / 0
+ result2 = pd.DataFrame(arr, index=df.index, columns=df.columns)
+ tm.assert_frame_equal(result2, expected)
+
+ def test_df_div_zero_series_does_not_commute(self):
+ # integer div, but deal with the 0's (GH#9144)
+ df = pd.DataFrame(np.random.default_rng(2).standard_normal((10, 5)))
+ ser = df[0]
+ res = ser / df
+ res2 = df / ser
+ assert not res.fillna(0).equals(res2.fillna(0))
+
+ # ------------------------------------------------------------------
+ # Mod By Zero
+
+ def test_df_mod_zero_df(self):
+ # GH#3590, modulo as ints
+ df = pd.DataFrame({"first": [3, 4, 5, 8], "second": [0, 0, 0, 3]})
+ # this is technically wrong, as the integer portion is coerced to float
+ first = Series([0, 0, 0, 0])
+ first = first.astype("float64")
+ second = Series([np.nan, np.nan, np.nan, 0])
+ expected = pd.DataFrame({"first": first, "second": second})
+ result = df % df
+ tm.assert_frame_equal(result, expected)
+
+ # GH#38939 If we dont pass copy=False, df is consolidated and
+ # result["first"] is float64 instead of int64
+ df = pd.DataFrame({"first": [3, 4, 5, 8], "second": [0, 0, 0, 3]}, copy=False)
+ first = Series([0, 0, 0, 0], dtype="int64")
+ second = Series([np.nan, np.nan, np.nan, 0])
+ expected = pd.DataFrame({"first": first, "second": second})
+ result = df % df
+ tm.assert_frame_equal(result, expected)
+
+ def test_df_mod_zero_array(self):
+ # GH#3590, modulo as ints
+ df = pd.DataFrame({"first": [3, 4, 5, 8], "second": [0, 0, 0, 3]})
+
+ # this is technically wrong, as the integer portion is coerced to float
+ # ###
+ first = Series([0, 0, 0, 0], dtype="float64")
+ second = Series([np.nan, np.nan, np.nan, 0])
+ expected = pd.DataFrame({"first": first, "second": second})
+
+ # numpy has a slightly different (wrong) treatment
+ with np.errstate(all="ignore"):
+ arr = df.values % df.values
+ result2 = pd.DataFrame(arr, index=df.index, columns=df.columns, dtype="float64")
+ result2.iloc[0:3, 1] = np.nan
+ tm.assert_frame_equal(result2, expected)
+
+ def test_df_mod_zero_int(self):
+ # GH#3590, modulo as ints
+ df = pd.DataFrame({"first": [3, 4, 5, 8], "second": [0, 0, 0, 3]})
+
+ result = df % 0
+ expected = pd.DataFrame(np.nan, index=df.index, columns=df.columns)
+ tm.assert_frame_equal(result, expected)
+
+ # numpy has a slightly different (wrong) treatment
+ with np.errstate(all="ignore"):
+ arr = df.values.astype("float64") % 0
+ result2 = pd.DataFrame(arr, index=df.index, columns=df.columns)
+ tm.assert_frame_equal(result2, expected)
+
+ def test_df_mod_zero_series_does_not_commute(self):
+ # GH#3590, modulo as ints
+ # not commutative with series
+ df = pd.DataFrame(np.random.default_rng(2).standard_normal((10, 5)))
+ ser = df[0]
+ res = ser % df
+ res2 = df % ser
+ assert not res.fillna(0).equals(res2.fillna(0))
+
+
+class TestMultiplicationDivision:
+ # __mul__, __rmul__, __div__, __rdiv__, __floordiv__, __rfloordiv__
+ # for non-timestamp/timedelta/period dtypes
+
+ def test_divide_decimal(self, box_with_array):
+ # resolves issue GH#9787
+ box = box_with_array
+ ser = Series([Decimal(10)])
+ expected = Series([Decimal(5)])
+
+ ser = tm.box_expected(ser, box)
+ expected = tm.box_expected(expected, box)
+
+ result = ser / Decimal(2)
+
+ tm.assert_equal(result, expected)
+
+ result = ser // Decimal(2)
+ tm.assert_equal(result, expected)
+
+ def test_div_equiv_binop(self):
+ # Test Series.div as well as Series.__div__
+ # float/integer issue
+ # GH#7785
+ first = Series([1, 0], name="first")
+ second = Series([-0.01, -0.02], name="second")
+ expected = Series([-0.01, -np.inf])
+
+ result = second.div(first)
+ tm.assert_series_equal(result, expected, check_names=False)
+
+ result = second / first
+ tm.assert_series_equal(result, expected)
+
+ def test_div_int(self, numeric_idx):
+ idx = numeric_idx
+ result = idx / 1
+ expected = idx.astype("float64")
+ tm.assert_index_equal(result, expected)
+
+ result = idx / 2
+ expected = Index(idx.values / 2)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("op", [operator.mul, ops.rmul, operator.floordiv])
+ def test_mul_int_identity(self, op, numeric_idx, box_with_array):
+ idx = numeric_idx
+ idx = tm.box_expected(idx, box_with_array)
+
+ result = op(idx, 1)
+ tm.assert_equal(result, idx)
+
+ def test_mul_int_array(self, numeric_idx):
+ idx = numeric_idx
+ didx = idx * idx
+
+ result = idx * np.array(5, dtype="int64")
+ tm.assert_index_equal(result, idx * 5)
+
+ arr_dtype = "uint64" if idx.dtype == np.uint64 else "int64"
+ result = idx * np.arange(5, dtype=arr_dtype)
+ tm.assert_index_equal(result, didx)
+
+ def test_mul_int_series(self, numeric_idx):
+ idx = numeric_idx
+ didx = idx * idx
+
+ arr_dtype = "uint64" if idx.dtype == np.uint64 else "int64"
+ result = idx * Series(np.arange(5, dtype=arr_dtype))
+ tm.assert_series_equal(result, Series(didx))
+
+ def test_mul_float_series(self, numeric_idx):
+ idx = numeric_idx
+ rng5 = np.arange(5, dtype="float64")
+
+ result = idx * Series(rng5 + 0.1)
+ expected = Series(rng5 * (rng5 + 0.1))
+ tm.assert_series_equal(result, expected)
+
+ def test_mul_index(self, numeric_idx):
+ idx = numeric_idx
+
+ result = idx * idx
+ tm.assert_index_equal(result, idx**2)
+
+ def test_mul_datelike_raises(self, numeric_idx):
+ idx = numeric_idx
+ msg = "cannot perform __rmul__ with this index type"
+ with pytest.raises(TypeError, match=msg):
+ idx * date_range("20130101", periods=5)
+
+ def test_mul_size_mismatch_raises(self, numeric_idx):
+ idx = numeric_idx
+ msg = "operands could not be broadcast together"
+ with pytest.raises(ValueError, match=msg):
+ idx * idx[0:3]
+ with pytest.raises(ValueError, match=msg):
+ idx * np.array([1, 2])
+
+ @pytest.mark.parametrize("op", [operator.pow, ops.rpow])
+ def test_pow_float(self, op, numeric_idx, box_with_array):
+ # test power calculations both ways, GH#14973
+ box = box_with_array
+ idx = numeric_idx
+ expected = Index(op(idx.values, 2.0))
+
+ idx = tm.box_expected(idx, box)
+ expected = tm.box_expected(expected, box)
+
+ result = op(idx, 2.0)
+ tm.assert_equal(result, expected)
+
+ def test_modulo(self, numeric_idx, box_with_array):
+ # GH#9244
+ box = box_with_array
+ idx = numeric_idx
+ expected = Index(idx.values % 2)
+
+ idx = tm.box_expected(idx, box)
+ expected = tm.box_expected(expected, box)
+
+ result = idx % 2
+ tm.assert_equal(result, expected)
+
+ def test_divmod_scalar(self, numeric_idx):
+ idx = numeric_idx
+
+ result = divmod(idx, 2)
+ with np.errstate(all="ignore"):
+ div, mod = divmod(idx.values, 2)
+
+ expected = Index(div), Index(mod)
+ for r, e in zip(result, expected, strict=True):
+ tm.assert_index_equal(r, e)
+
+ def test_divmod_ndarray(self, numeric_idx):
+ idx = numeric_idx
+ other = np.ones(idx.values.shape, dtype=idx.values.dtype) * 2
+
+ result = divmod(idx, other)
+ with np.errstate(all="ignore"):
+ div, mod = divmod(idx.values, other)
+
+ expected = Index(div), Index(mod)
+ for r, e in zip(result, expected, strict=True):
+ tm.assert_index_equal(r, e)
+
+ def test_divmod_series(self, numeric_idx):
+ idx = numeric_idx
+ other = np.ones(idx.values.shape, dtype=idx.values.dtype) * 2
+
+ result = divmod(idx, Series(other))
+ with np.errstate(all="ignore"):
+ div, mod = divmod(idx.values, other)
+
+ expected = Series(div), Series(mod)
+ for r, e in zip(result, expected, strict=True):
+ tm.assert_series_equal(r, e)
+
+ @pytest.mark.parametrize("other", [np.nan, 7, -23, 2.718, -3.14, np.inf])
+ def test_ops_np_scalar(self, other):
+ vals = np.random.default_rng(2).standard_normal((5, 3))
+ f = lambda x: pd.DataFrame(
+ x, index=list("ABCDE"), columns=["jim", "joe", "jolie"]
+ )
+
+ df = f(vals)
+
+ tm.assert_frame_equal(df / np.array(other), f(vals / other))
+ tm.assert_frame_equal(np.array(other) * df, f(vals * other))
+ tm.assert_frame_equal(df + np.array(other), f(vals + other))
+ tm.assert_frame_equal(np.array(other) - df, f(other - vals))
+
+ # TODO: This came from series.test.test_operators, needs cleanup
+ def test_operators_frame(self):
+ # rpow does not work with DataFrame
+ ts = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ ts.name = "ts"
+
+ df = pd.DataFrame({"A": ts})
+
+ tm.assert_series_equal(ts + ts, ts + df["A"], check_names=False)
+ tm.assert_series_equal(ts**ts, ts ** df["A"], check_names=False)
+ tm.assert_series_equal(ts < ts, ts < df["A"], check_names=False)
+ tm.assert_series_equal(ts / ts, ts / df["A"], check_names=False)
+
+ # TODO: this came from tests.series.test_analytics, needs cleanup and
+ # de-duplication with test_modulo above
+ def test_modulo2(self):
+ with np.errstate(all="ignore"):
+ # GH#3590, modulo as ints
+ p = pd.DataFrame({"first": [3, 4, 5, 8], "second": [0, 0, 0, 3]})
+ result = p["first"] % p["second"]
+ expected = Series(p["first"].values % p["second"].values, dtype="float64")
+ expected.iloc[0:3] = np.nan
+ tm.assert_series_equal(result, expected)
+
+ result = p["first"] % 0
+ expected = Series(np.nan, index=p.index, name="first")
+ tm.assert_series_equal(result, expected)
+
+ p = p.astype("float64")
+ result = p["first"] % p["second"]
+ expected = Series(p["first"].values % p["second"].values)
+ tm.assert_series_equal(result, expected)
+
+ p = p.astype("float64")
+ result = p["first"] % p["second"]
+ result2 = p["second"] % p["first"]
+ assert not result.equals(result2)
+
+ def test_modulo_zero_int(self):
+ # GH#9144
+ with np.errstate(all="ignore"):
+ s = Series([0, 1])
+
+ result = s % 0
+ expected = Series([np.nan, np.nan])
+ tm.assert_series_equal(result, expected)
+
+ result = 0 % s
+ expected = Series([np.nan, 0.0])
+ tm.assert_series_equal(result, expected)
+
+ def test_non_1d_ea_raises_notimplementederror(self):
+ # GH#61866
+ ea_array = array([1, 2, 3, 4, 5], dtype="Int64").reshape(5, 1)
+ np_array = np.array([1, 2, 3, 4, 5], dtype=np.int64).reshape(5, 1)
+
+ msg = "can only perform ops with 1-d structures"
+
+ with pytest.raises(NotImplementedError, match=msg):
+ ea_array * np_array
+
+ with pytest.raises(NotImplementedError, match=msg):
+ np_array * ea_array
+
+
+class TestAdditionSubtraction:
+ # __add__, __sub__, __radd__, __rsub__, __iadd__, __isub__
+ # for non-timestamp/timedelta/period dtypes
+
+ @pytest.mark.parametrize(
+ "first, second, expected",
+ [
+ (
+ Series([1, 2, 3], index=list("ABC"), name="x"),
+ Series([2, 2, 2], index=list("ABD"), name="x"),
+ Series([3.0, 4.0, np.nan, np.nan], index=list("ABCD"), name="x"),
+ ),
+ (
+ Series([1, 2, 3], index=list("ABC"), name="x"),
+ Series([2, 2, 2, 2], index=list("ABCD"), name="x"),
+ Series([3, 4, 5, np.nan], index=list("ABCD"), name="x"),
+ ),
+ ],
+ )
+ def test_add_series(self, first, second, expected):
+ # GH#1134
+ tm.assert_series_equal(first + second, expected)
+ tm.assert_series_equal(second + first, expected)
+
+ @pytest.mark.parametrize(
+ "first, second, expected",
+ [
+ (
+ pd.DataFrame({"x": [1, 2, 3]}, index=list("ABC")),
+ pd.DataFrame({"x": [2, 2, 2]}, index=list("ABD")),
+ pd.DataFrame({"x": [3.0, 4.0, np.nan, np.nan]}, index=list("ABCD")),
+ ),
+ (
+ pd.DataFrame({"x": [1, 2, 3]}, index=list("ABC")),
+ pd.DataFrame({"x": [2, 2, 2, 2]}, index=list("ABCD")),
+ pd.DataFrame({"x": [3, 4, 5, np.nan]}, index=list("ABCD")),
+ ),
+ ],
+ )
+ def test_add_frames(self, first, second, expected):
+ # GH#1134
+ tm.assert_frame_equal(first + second, expected)
+ tm.assert_frame_equal(second + first, expected)
+
+ # TODO: This came from series.test.test_operators, needs cleanup
+ def test_series_frame_radd_bug(self, fixed_now_ts):
+ # GH#353
+ vals = Series([str(i) for i in range(5)])
+ result = "foo_" + vals
+ expected = vals.map(lambda x: "foo_" + x)
+ tm.assert_series_equal(result, expected)
+
+ frame = pd.DataFrame({"vals": vals})
+ result = "foo_" + frame
+ expected = pd.DataFrame({"vals": vals.map(lambda x: "foo_" + x)})
+ tm.assert_frame_equal(result, expected)
+
+ ts = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+
+ # really raise this time
+ fix_now = fixed_now_ts.to_pydatetime()
+ msg = "|".join(
+ [
+ "unsupported operand type",
+ # wrong error message, see https://github.com/numpy/numpy/issues/18832
+ "Concatenation operation",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ fix_now + ts
+
+ with pytest.raises(TypeError, match=msg):
+ ts + fix_now
+
+ # TODO: This came from series.test.test_operators, needs cleanup
+ def test_datetime64_with_index(self):
+ # arithmetic integer ops with an index
+ ser = Series(np.random.default_rng(2).standard_normal(5))
+ expected = ser - ser.index.to_series()
+ result = ser - ser.index
+ tm.assert_series_equal(result, expected)
+
+ # GH#4629
+ # arithmetic datetime64 ops with an index
+ ser = Series(
+ date_range("20130101", periods=5),
+ index=date_range("20130101", periods=5),
+ )
+ expected = ser - ser.index.to_series()
+ result = ser - ser.index
+ tm.assert_series_equal(result, expected)
+
+ msg = "cannot subtract PeriodArray from DatetimeArray"
+ with pytest.raises(TypeError, match=msg):
+ # GH#18850
+ result = ser - ser.index.to_period()
+
+ df = pd.DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)),
+ index=date_range("20130101", periods=5),
+ )
+ df["date"] = pd.Timestamp("20130102")
+ df["expected"] = df["date"] - df.index.to_series()
+ df["result"] = df["date"] - df.index
+ tm.assert_series_equal(df["result"], df["expected"], check_names=False)
+
+ # TODO: taken from tests.frame.test_operators, needs cleanup
+ def test_frame_operators(self, float_frame):
+ frame = float_frame
+
+ garbage = np.random.default_rng(2).random(4)
+ colSeries = Series(garbage, index=np.array(frame.columns))
+
+ idSum = frame + frame
+ seriesSum = frame + colSeries
+
+ for col, series in idSum.items():
+ for idx, val in series.items():
+ origVal = frame[col][idx] * 2
+ if not np.isnan(val):
+ assert val == origVal
+ else:
+ assert np.isnan(origVal)
+
+ for col, series in seriesSum.items():
+ for idx, val in series.items():
+ origVal = frame[col][idx] + colSeries[col]
+ if not np.isnan(val):
+ assert val == origVal
+ else:
+ assert np.isnan(origVal)
+
+ def test_frame_operators_col_align(self, float_frame):
+ frame2 = pd.DataFrame(float_frame, columns=["D", "C", "B", "A"])
+ added = frame2 + frame2
+ expected = frame2 * 2
+ tm.assert_frame_equal(added, expected)
+
+ def test_frame_operators_none_to_nan(self):
+ df = pd.DataFrame({"a": ["a", None, "b"]})
+ tm.assert_frame_equal(df + df, pd.DataFrame({"a": ["aa", np.nan, "bb"]}))
+
+ @pytest.mark.parametrize("dtype", ("float", "int64"))
+ def test_frame_operators_empty_like(self, dtype):
+ # Test for issue #10181
+ frames = [
+ pd.DataFrame(dtype=dtype),
+ pd.DataFrame(columns=["A"], dtype=dtype),
+ pd.DataFrame(index=[0], dtype=dtype),
+ ]
+ for df in frames:
+ assert (df + df).equals(df)
+ tm.assert_frame_equal(df + df, df)
+
+ @pytest.mark.parametrize(
+ "func",
+ [lambda x: x * 2, lambda x: x[::2], lambda x: 5],
+ ids=["multiply", "slice", "constant"],
+ )
+ def test_series_operators_arithmetic(self, all_arithmetic_functions, func):
+ op = all_arithmetic_functions
+ series = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ other = func(series)
+ compare_op(series, other, op)
+
+ @pytest.mark.parametrize(
+ "func", [lambda x: x + 1, lambda x: 5], ids=["add", "constant"]
+ )
+ def test_series_operators_compare(self, comparison_op, func):
+ op = comparison_op
+ series = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ other = func(series)
+ compare_op(series, other, op)
+
+ @pytest.mark.parametrize(
+ "func",
+ [lambda x: x * 2, lambda x: x[::2], lambda x: 5],
+ ids=["multiply", "slice", "constant"],
+ )
+ def test_divmod(self, func):
+ series = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ other = func(series)
+ results = divmod(series, other)
+ if isinstance(other, abc.Iterable) and len(series) != len(other):
+ # if the lengths don't match, this is the test where we use
+ # `tser[::2]`. Pad every other value in `other_np` with nan.
+ other_np = []
+ for n in other:
+ other_np.append(n)
+ other_np.append(np.nan)
+ else:
+ other_np = other
+ other_np = np.asarray(other_np)
+ with np.errstate(all="ignore"):
+ expecteds = divmod(series.values, np.asarray(other_np))
+
+ for result, expected in zip(results, expecteds, strict=True):
+ # check the values, name, and index separately
+ tm.assert_almost_equal(np.asarray(result), expected)
+
+ assert result.name == series.name
+ tm.assert_index_equal(result.index, series.index._with_freq(None))
+
+ def test_series_divmod_zero(self):
+ # Check that divmod uses pandas convention for division by zero,
+ # which does not match numpy.
+ # pandas convention has
+ # 1/0 == np.inf
+ # -1/0 == -np.inf
+ # 1/-0.0 == -np.inf
+ # -1/-0.0 == np.inf
+ tser = Series(
+ np.arange(1, 11, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ other = tser * 0
+
+ result = divmod(tser, other)
+ exp1 = Series([np.inf] * len(tser), index=tser.index, name="ts")
+ exp2 = Series([np.nan] * len(tser), index=tser.index, name="ts")
+ tm.assert_series_equal(result[0], exp1)
+ tm.assert_series_equal(result[1], exp2)
+
+
+class TestUFuncCompat:
+ # TODO: add more dtypes
+ @pytest.mark.parametrize("holder", [Index, RangeIndex, Series])
+ @pytest.mark.parametrize("dtype", [np.int64, np.uint64, np.float64])
+ def test_ufunc_compat(self, holder, dtype):
+ box = Series if holder is Series else Index
+
+ if holder is RangeIndex:
+ if dtype != np.int64:
+ pytest.skip(f"dtype {dtype} not relevant for RangeIndex")
+ idx = RangeIndex(0, 5, name="foo")
+ else:
+ idx = holder(np.arange(5, dtype=dtype), name="foo")
+ result = np.sin(idx)
+ expected = box(np.sin(np.arange(5, dtype=dtype)), name="foo")
+ tm.assert_equal(result, expected)
+
+ # TODO: add more dtypes
+ @pytest.mark.parametrize("dtype", [np.int64, np.uint64, np.float64])
+ def test_ufunc_coercions(self, index_or_series, dtype):
+ idx = index_or_series([1, 2, 3, 4, 5], dtype=dtype, name="x")
+ box = index_or_series
+
+ result = np.sqrt(idx)
+ assert result.dtype == "f8" and isinstance(result, box)
+ exp = Index(np.sqrt(np.array([1, 2, 3, 4, 5], dtype=np.float64)), name="x")
+ exp = tm.box_expected(exp, box)
+ tm.assert_equal(result, exp)
+
+ result = np.divide(idx, 2.0)
+ assert result.dtype == "f8" and isinstance(result, box)
+ exp = Index([0.5, 1.0, 1.5, 2.0, 2.5], dtype=np.float64, name="x")
+ exp = tm.box_expected(exp, box)
+ tm.assert_equal(result, exp)
+
+ # _evaluate_numeric_binop
+ result = idx + 2.0
+ assert result.dtype == "f8" and isinstance(result, box)
+ exp = Index([3.0, 4.0, 5.0, 6.0, 7.0], dtype=np.float64, name="x")
+ exp = tm.box_expected(exp, box)
+ tm.assert_equal(result, exp)
+
+ result = idx - 2.0
+ assert result.dtype == "f8" and isinstance(result, box)
+ exp = Index([-1.0, 0.0, 1.0, 2.0, 3.0], dtype=np.float64, name="x")
+ exp = tm.box_expected(exp, box)
+ tm.assert_equal(result, exp)
+
+ result = idx * 1.0
+ assert result.dtype == "f8" and isinstance(result, box)
+ exp = Index([1.0, 2.0, 3.0, 4.0, 5.0], dtype=np.float64, name="x")
+ exp = tm.box_expected(exp, box)
+ tm.assert_equal(result, exp)
+
+ result = idx / 2.0
+ assert result.dtype == "f8" and isinstance(result, box)
+ exp = Index([0.5, 1.0, 1.5, 2.0, 2.5], dtype=np.float64, name="x")
+ exp = tm.box_expected(exp, box)
+ tm.assert_equal(result, exp)
+
+ # TODO: add more dtypes
+ @pytest.mark.parametrize("holder", [Index, Series])
+ @pytest.mark.parametrize("dtype", [np.int64, np.uint64, np.float64])
+ def test_ufunc_multiple_return_values(self, holder, dtype):
+ obj = holder([1, 2, 3], dtype=dtype, name="x")
+ box = Series if holder is Series else Index
+
+ result = np.modf(obj)
+ assert isinstance(result, tuple)
+ exp1 = Index([0.0, 0.0, 0.0], dtype=np.float64, name="x")
+ exp2 = Index([1.0, 2.0, 3.0], dtype=np.float64, name="x")
+ tm.assert_equal(result[0], tm.box_expected(exp1, box))
+ tm.assert_equal(result[1], tm.box_expected(exp2, box))
+
+ def test_ufunc_at(self):
+ s = Series([0, 1, 2], index=[1, 2, 3], name="x")
+ np.add.at(s, [0, 2], 10)
+ expected = Series([10, 1, 12], index=[1, 2, 3], name="x")
+ tm.assert_series_equal(s, expected)
+
+
+class TestObjectDtypeEquivalence:
+ # Tests that arithmetic operations match operations executed elementwise
+
+ @pytest.mark.parametrize("dtype", [None, object])
+ def test_numarr_with_dtype_add_nan(self, dtype, box_with_array):
+ box = box_with_array
+ ser = Series([1, 2, 3], dtype=dtype)
+ expected = Series([np.nan, np.nan, np.nan], dtype=dtype)
+
+ ser = tm.box_expected(ser, box)
+ expected = tm.box_expected(expected, box)
+
+ result = np.nan + ser
+ tm.assert_equal(result, expected)
+
+ result = ser + np.nan
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", [None, object])
+ def test_numarr_with_dtype_add_int(self, dtype, box_with_array):
+ box = box_with_array
+ ser = Series([1, 2, 3], dtype=dtype)
+ expected = Series([2, 3, 4], dtype=dtype)
+
+ ser = tm.box_expected(ser, box)
+ expected = tm.box_expected(expected, box)
+
+ result = 1 + ser
+ tm.assert_equal(result, expected)
+
+ result = ser + 1
+ tm.assert_equal(result, expected)
+
+ # TODO: moved from tests.series.test_operators; needs cleanup
+ @pytest.mark.parametrize(
+ "op",
+ [operator.add, operator.sub, operator.mul, operator.truediv, operator.floordiv],
+ )
+ def test_operators_reverse_object(self, op):
+ # GH#56
+ arr = Series(
+ np.random.default_rng(2).standard_normal(10),
+ index=np.arange(10),
+ dtype=object,
+ )
+
+ result = op(1.0, arr)
+ expected = op(1.0, arr.astype(float))
+ tm.assert_series_equal(result.astype(float), expected)
+
+
+class TestNumericArithmeticUnsorted:
+ # Tests in this class have been moved from type-specific test modules
+ # but not yet sorted, parametrized, and de-duplicated
+ @pytest.mark.parametrize(
+ "op",
+ [
+ operator.add,
+ operator.sub,
+ operator.mul,
+ operator.floordiv,
+ operator.truediv,
+ ],
+ )
+ @pytest.mark.parametrize(
+ "idx1",
+ [
+ RangeIndex(0, 10, 1),
+ RangeIndex(0, 20, 2),
+ RangeIndex(-10, 10, 2),
+ RangeIndex(5, -5, -1),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "idx2",
+ [
+ RangeIndex(0, 10, 1),
+ RangeIndex(0, 20, 2),
+ RangeIndex(-10, 10, 2),
+ RangeIndex(5, -5, -1),
+ ],
+ )
+ def test_binops_index(self, op, idx1, idx2):
+ idx1 = idx1._rename("foo")
+ idx2 = idx2._rename("bar")
+ result = op(idx1, idx2)
+ expected = op(Index(idx1.to_numpy()), Index(idx2.to_numpy()))
+ tm.assert_index_equal(result, expected, exact="equiv")
+
+ @pytest.mark.parametrize(
+ "op",
+ [
+ operator.add,
+ operator.sub,
+ operator.mul,
+ operator.floordiv,
+ operator.truediv,
+ ],
+ )
+ @pytest.mark.parametrize(
+ "idx",
+ [
+ RangeIndex(0, 10, 1),
+ RangeIndex(0, 20, 2),
+ RangeIndex(-10, 10, 2),
+ RangeIndex(5, -5, -1),
+ ],
+ )
+ @pytest.mark.parametrize("scalar", [-1, 1, 2])
+ def test_binops_index_scalar(self, op, idx, scalar):
+ result = op(idx, scalar)
+ expected = op(Index(idx.to_numpy()), scalar)
+ tm.assert_index_equal(result, expected, exact="equiv")
+
+ @pytest.mark.parametrize("idx1", [RangeIndex(0, 10, 1), RangeIndex(0, 20, 2)])
+ @pytest.mark.parametrize("idx2", [RangeIndex(0, 10, 1), RangeIndex(0, 20, 2)])
+ def test_binops_index_pow(self, idx1, idx2):
+ # numpy does not allow powers of negative integers so test separately
+ # https://github.com/numpy/numpy/pull/8127
+ idx1 = idx1._rename("foo")
+ idx2 = idx2._rename("bar")
+ result = pow(idx1, idx2)
+ expected = pow(Index(idx1.to_numpy()), Index(idx2.to_numpy()))
+ tm.assert_index_equal(result, expected, exact="equiv")
+
+ @pytest.mark.parametrize("idx", [RangeIndex(0, 10, 1), RangeIndex(0, 20, 2)])
+ @pytest.mark.parametrize("scalar", [1, 2])
+ def test_binops_index_scalar_pow(self, idx, scalar):
+ # numpy does not allow powers of negative integers so test separately
+ # https://github.com/numpy/numpy/pull/8127
+ result = pow(idx, scalar)
+ expected = pow(Index(idx.to_numpy()), scalar)
+ tm.assert_index_equal(result, expected, exact="equiv")
+
+ # TODO: divmod?
+ @pytest.mark.parametrize(
+ "op",
+ [
+ operator.add,
+ operator.sub,
+ operator.mul,
+ operator.floordiv,
+ operator.truediv,
+ operator.pow,
+ operator.mod,
+ ],
+ )
+ def test_arithmetic_with_frame_or_series(self, op):
+ # check that we return NotImplemented when operating with Series
+ # or DataFrame
+ index = RangeIndex(5)
+ other = Series(np.random.default_rng(2).standard_normal(5))
+
+ expected = op(Series(index), other)
+ result = op(index, other)
+ tm.assert_series_equal(result, expected)
+
+ other = pd.DataFrame(np.random.default_rng(2).standard_normal((2, 5)))
+ expected = op(pd.DataFrame([index, index]), other)
+ result = op(index, other)
+ tm.assert_frame_equal(result, expected)
+
+ def test_numeric_compat2(self):
+ # validate that we are handling the RangeIndex overrides to numeric ops
+ # and returning RangeIndex where possible
+
+ idx = RangeIndex(0, 10, 2)
+
+ result = idx * 2
+ expected = RangeIndex(0, 20, 4)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx + 2
+ expected = RangeIndex(2, 12, 2)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx - 2
+ expected = RangeIndex(-2, 8, 2)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx / 2
+ expected = RangeIndex(0, 5, 1).astype("float64")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx / 4
+ expected = RangeIndex(0, 10, 2) / 4
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx // 1
+ expected = idx
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # __mul__
+ result = idx * idx
+ expected = Index(idx.values * idx.values)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # __pow__
+ idx = RangeIndex(0, 1000, 2)
+ result = idx**2
+ expected = Index(idx._values) ** 2
+ tm.assert_index_equal(Index(result.values), expected, exact=True)
+
+ @pytest.mark.parametrize(
+ "idx, div, expected",
+ [
+ # TODO: add more dtypes
+ (RangeIndex(0, 1000, 2), 2, RangeIndex(0, 500, 1)),
+ (RangeIndex(-99, -201, -3), -3, RangeIndex(33, 67, 1)),
+ (
+ RangeIndex(0, 1000, 1),
+ 2,
+ Index(RangeIndex(0, 1000, 1)._values) // 2,
+ ),
+ (
+ RangeIndex(0, 100, 1),
+ 2.0,
+ Index(RangeIndex(0, 100, 1)._values) // 2.0,
+ ),
+ (RangeIndex(0), 50, RangeIndex(0)),
+ (RangeIndex(2, 4, 2), 3, RangeIndex(0, 1, 1)),
+ (RangeIndex(-5, -10, -6), 4, RangeIndex(-2, -1, 1)),
+ (RangeIndex(-100, -200, 3), 2, RangeIndex(0)),
+ ],
+ )
+ def test_numeric_compat2_floordiv(self, idx, div, expected):
+ # __floordiv__
+ tm.assert_index_equal(idx // div, expected, exact=True)
+
+ @pytest.mark.parametrize("dtype", [np.int64, np.float64])
+ @pytest.mark.parametrize("delta", [1, 0, -1])
+ def test_addsub_arithmetic(self, dtype, delta):
+ # GH#8142
+ delta = dtype(delta)
+ index = Index([10, 11, 12], dtype=dtype)
+ result = index + delta
+ expected = Index(index.values + delta, dtype=dtype)
+ tm.assert_index_equal(result, expected)
+
+ # this subtraction used to fail
+ result = index - delta
+ expected = Index(index.values - delta, dtype=dtype)
+ tm.assert_index_equal(result, expected)
+
+ tm.assert_index_equal(index + index, 2 * index)
+ tm.assert_index_equal(index - index, 0 * index)
+ assert not (index - index).empty
+
+ def test_pow_nan_with_zero(self, box_with_array):
+ left = Index([np.nan, np.nan, np.nan])
+ right = Index([0, 0, 0])
+ expected = Index([1.0, 1.0, 1.0])
+
+ left = tm.box_expected(left, box_with_array)
+ right = tm.box_expected(right, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = left**right
+ tm.assert_equal(result, expected)
+
+
+def test_fill_value_inf_masking():
+ # GH #27464 make sure we mask 0/1 with Inf and not NaN
+ df = pd.DataFrame({"A": [0, 1, 2], "B": [1.1, None, 1.1]})
+
+ other = pd.DataFrame({"A": [1.1, 1.2, 1.3]}, index=[0, 2, 3])
+
+ result = df.rfloordiv(other, fill_value=1)
+
+ expected = pd.DataFrame(
+ {"A": [np.inf, 1.0, 0.0, 1.0], "B": [0.0, np.nan, 0.0, np.nan]}
+ )
+ tm.assert_frame_equal(result, expected, check_index_type=False)
+
+
+def test_dataframe_div_silenced():
+ # GH#26793
+ pdf1 = pd.DataFrame(
+ {
+ "A": np.arange(10),
+ "B": [np.nan, 1, 2, 3, 4] * 2,
+ "C": [np.nan] * 10,
+ "D": np.arange(10),
+ },
+ index=list("abcdefghij"),
+ columns=list("ABCD"),
+ )
+ pdf2 = pd.DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ index=list("abcdefghjk"),
+ columns=list("ABCX"),
+ )
+ with tm.assert_produces_warning(None):
+ pdf1.div(pdf2, fill_value=0)
+
+
+@pytest.mark.parametrize(
+ "data, expected_data",
+ [([0, 1, 2], [0, 2, 4])],
+)
+@pytest.mark.parametrize("box_pandas_1d_array", [Index, Series, tm.to_array])
+@pytest.mark.parametrize("box_1d_array", [Index, Series, tm.to_array, np.array, list])
+def test_integer_array_add_list_like(
+ box_pandas_1d_array, box_1d_array, data, expected_data
+):
+ # GH22606 Verify operators with IntegerArray and list-likes
+ arr = array(data, dtype="Int64")
+ container = box_pandas_1d_array(arr)
+ left = container + box_1d_array(data)
+ right = box_1d_array(data) + container
+
+ if Series in [box_1d_array, box_pandas_1d_array]:
+ cls = Series
+ elif Index in [box_1d_array, box_pandas_1d_array]:
+ cls = Index
+ else:
+ cls = array
+
+ expected = cls(expected_data, dtype="Int64")
+
+ tm.assert_equal(left, expected)
+ tm.assert_equal(right, expected)
+
+
+def test_sub_multiindex_swapped_levels():
+ # GH 9952
+ df = pd.DataFrame(
+ {"a": np.random.default_rng(2).standard_normal(6)},
+ index=pd.MultiIndex.from_product(
+ [["a", "b"], [0, 1, 2]], names=["levA", "levB"]
+ ),
+ )
+ df2 = df.copy()
+ df2.index = df2.index.swaplevel(0, 1)
+ result = df - df2
+ expected = pd.DataFrame([0.0] * 6, columns=["a"], index=df.index)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("power", [1, 2, 5])
+@pytest.mark.parametrize("string_size", [0, 1, 2, 5])
+def test_empty_str_comparison(power, string_size):
+ # GH 37348
+ a = np.array(range(10**power))
+ right = pd.DataFrame(a, dtype=np.int64)
+ left = " " * string_size
+
+ result = right == left
+ expected = pd.DataFrame(np.zeros(right.shape, dtype=bool))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_series_add_sub_with_UInt64():
+ # GH 22023
+ series1 = Series([1, 2, 3])
+ series2 = Series([2, 1, 3], dtype="UInt64")
+
+ result = series1 + series2
+ expected = Series([3, 3, 6], dtype="Float64")
+ tm.assert_series_equal(result, expected)
+
+ result = series1 - series2
+ expected = Series([-1, 1, 0], dtype="Float64")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_object.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_object.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc0f78d3aa01af5353bc9385848fb49a7784020d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_object.py
@@ -0,0 +1,410 @@
+# Arithmetic tests for DataFrame/Series/Index/Array classes that should
+# behave identically.
+# Specifically for object dtype
+import datetime
+from decimal import Decimal
+import operator
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ Series,
+ Timestamp,
+ option_context,
+)
+import pandas._testing as tm
+from pandas.core import ops
+
+# ------------------------------------------------------------------
+# Comparisons
+
+
+class TestObjectComparisons:
+ def test_comparison_object_numeric_nas(self, comparison_op):
+ ser = Series(np.random.default_rng(2).standard_normal(10), dtype=object)
+ shifted = ser.shift(2)
+
+ func = comparison_op
+
+ result = func(ser, shifted)
+ expected = func(ser.astype(float), shifted.astype(float))
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))]
+ )
+ def test_object_comparisons(self, infer_string):
+ with option_context("future.infer_string", infer_string):
+ ser = Series(["a", "b", np.nan, "c", "a"])
+
+ result = ser == "a"
+ expected = Series([True, False, False, False, True])
+ tm.assert_series_equal(result, expected)
+
+ result = ser < "a"
+ expected = Series([False, False, False, False, False])
+ tm.assert_series_equal(result, expected)
+
+ result = ser != "a"
+ expected = -(ser == "a")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", [None, object])
+ def test_more_na_comparisons(self, dtype):
+ left = Series(["a", np.nan, "c"], dtype=dtype)
+ right = Series(["a", np.nan, "d"], dtype=dtype)
+
+ result = left == right
+ expected = Series([True, False, False])
+ tm.assert_series_equal(result, expected)
+
+ result = left != right
+ expected = Series([False, True, True])
+ tm.assert_series_equal(result, expected)
+
+ result = left == np.nan
+ expected = Series([False, False, False])
+ tm.assert_series_equal(result, expected)
+
+ result = left != np.nan
+ expected = Series([True, True, True])
+ tm.assert_series_equal(result, expected)
+
+
+# ------------------------------------------------------------------
+# Arithmetic
+
+
+class TestArithmetic:
+ def test_add_period_to_array_of_offset(self):
+ # GH#50162
+ per = pd.Period("2012-1-1", freq="D")
+ pi = pd.period_range("2012-1-1", periods=10, freq="D")
+ idx = per - pi
+
+ expected = pd.Index([x + per for x in idx], dtype=object)
+ result = idx + per
+ tm.assert_index_equal(result, expected)
+
+ result = per + idx
+ tm.assert_index_equal(result, expected)
+
+ # TODO: parametrize
+ def test_pow_ops_object(self):
+ # GH#22922
+ # pow is weird with masking & 1, so testing here
+ a = Series([1, np.nan, 1, np.nan], dtype=object)
+ b = Series([1, np.nan, np.nan, 1], dtype=object)
+ result = a**b
+ expected = Series(a.values**b.values, dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ result = b**a
+ expected = Series(b.values**a.values, dtype=object)
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("op", [operator.add, ops.radd])
+ @pytest.mark.parametrize("other", ["category", "Int64"])
+ def test_add_extension_scalar(self, other, box_with_array, op):
+ # GH#22378
+ # Check that scalars satisfying is_extension_array_dtype(obj)
+ # do not incorrectly try to dispatch to an ExtensionArray operation
+
+ arr = Series(["a", "b", "c"])
+ expected = Series([op(x, other) for x in arr])
+
+ arr = tm.box_expected(arr, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = op(arr, other)
+ tm.assert_equal(result, expected)
+
+ def test_objarr_add_str(self, box_with_array):
+ ser = Series(["x", np.nan, "x"])
+ expected = Series(["xa", np.nan, "xa"])
+
+ ser = tm.box_expected(ser, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = ser + "a"
+ tm.assert_equal(result, expected)
+
+ def test_objarr_radd_str(self, box_with_array):
+ ser = Series(["x", np.nan, "x"])
+ expected = Series(["ax", np.nan, "ax"])
+
+ ser = tm.box_expected(ser, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = "a" + ser
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "data",
+ [
+ [1, 2, 3],
+ [1.1, 2.2, 3.3],
+ [Timestamp("2011-01-01"), Timestamp("2011-01-02"), pd.NaT],
+ ["x", "y", 1],
+ ],
+ )
+ @pytest.mark.parametrize("dtype", [None, object])
+ def test_objarr_radd_str_invalid(self, dtype, data, box_with_array):
+ ser = Series(data, dtype=dtype)
+
+ ser = tm.box_expected(ser, box_with_array)
+ msg = "|".join(
+ [
+ "can only concatenate str",
+ "did not contain a loop with signature matching types",
+ "unsupported operand type",
+ "must be str",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ "foo_" + ser
+
+ @pytest.mark.parametrize("op", [operator.add, ops.radd, operator.sub, ops.rsub])
+ def test_objarr_add_invalid(self, op, box_with_array):
+ # invalid ops
+ box = box_with_array
+
+ obj_ser = Series(list("abc"), dtype=object, name="objects")
+
+ obj_ser = tm.box_expected(obj_ser, box)
+ msg = "|".join(
+ [
+ "can only concatenate str",
+ "unsupported operand type",
+ "must be str",
+ "has no kernel",
+ ]
+ )
+ with pytest.raises(Exception, match=msg):
+ op(obj_ser, 1)
+ with pytest.raises(Exception, match=msg):
+ op(obj_ser, np.array(1, dtype=np.int64))
+
+ # TODO: Moved from tests.series.test_operators; needs cleanup
+ def test_operators_na_handling(self):
+ ser = Series(["foo", "bar", "baz", np.nan])
+ result = "prefix_" + ser
+ expected = Series(["prefix_foo", "prefix_bar", "prefix_baz", np.nan])
+ tm.assert_series_equal(result, expected)
+
+ result = ser + "_suffix"
+ expected = Series(["foo_suffix", "bar_suffix", "baz_suffix", np.nan])
+ tm.assert_series_equal(result, expected)
+
+ # TODO: parametrize over box
+ @pytest.mark.parametrize("dtype", [None, object])
+ def test_series_with_dtype_radd_timedelta(self, dtype):
+ # note this test is _not_ aimed at timedelta64-dtyped Series
+ # as of 2.0 we retain object dtype when ser.dtype == object
+ ser = Series(
+ [pd.Timedelta("1 days"), pd.Timedelta("2 days"), pd.Timedelta("3 days")],
+ dtype=dtype,
+ )
+ expected = Series(
+ [pd.Timedelta("4 days"), pd.Timedelta("5 days"), pd.Timedelta("6 days")],
+ dtype=dtype,
+ )
+
+ result = pd.Timedelta("3 days") + ser
+ tm.assert_series_equal(result, expected)
+
+ result = ser + pd.Timedelta("3 days")
+ tm.assert_series_equal(result, expected)
+
+ # TODO: cleanup & parametrize over box
+ def test_mixed_timezone_series_ops_object(self):
+ # GH#13043
+ ser = Series(
+ [
+ Timestamp("2015-01-01", tz="US/Eastern"),
+ Timestamp("2015-01-01", tz="Asia/Tokyo"),
+ ],
+ name="xxx",
+ )
+ assert ser.dtype == object
+
+ exp = Series(
+ [
+ Timestamp("2015-01-02", tz="US/Eastern"),
+ Timestamp("2015-01-02", tz="Asia/Tokyo"),
+ ],
+ name="xxx",
+ )
+ tm.assert_series_equal(ser + pd.Timedelta("1 days"), exp)
+ tm.assert_series_equal(pd.Timedelta("1 days") + ser, exp)
+
+ # object series & object series
+ ser2 = Series(
+ [
+ Timestamp("2015-01-03", tz="US/Eastern"),
+ Timestamp("2015-01-05", tz="Asia/Tokyo"),
+ ],
+ name="xxx",
+ )
+ assert ser2.dtype == object
+ exp = Series(
+ [pd.Timedelta("2 days"), pd.Timedelta("4 days")], name="xxx", dtype=object
+ )
+ tm.assert_series_equal(ser2 - ser, exp)
+ tm.assert_series_equal(ser - ser2, -exp)
+
+ ser = Series(
+ [pd.Timedelta("01:00:00"), pd.Timedelta("02:00:00")],
+ name="xxx",
+ dtype=object,
+ )
+ assert ser.dtype == object
+
+ exp = Series(
+ [pd.Timedelta("01:30:00"), pd.Timedelta("02:30:00")],
+ name="xxx",
+ dtype=object,
+ )
+ tm.assert_series_equal(ser + pd.Timedelta("00:30:00"), exp)
+ tm.assert_series_equal(pd.Timedelta("00:30:00") + ser, exp)
+
+ # TODO: cleanup & parametrize over box
+ def test_iadd_preserves_name(self):
+ # GH#17067, GH#19723 __iadd__ and __isub__ should preserve index name
+ ser = Series([1, 2, 3])
+ ser.index.name = "foo"
+
+ ser.index += 1
+ assert ser.index.name == "foo"
+
+ ser.index -= 1
+ assert ser.index.name == "foo"
+
+ def test_add_string(self):
+ # from bug report
+ index = pd.Index(["a", "b", "c"])
+ index2 = index + "foo"
+
+ assert "a" not in index2
+ assert "afoo" in index2
+
+ def test_iadd_string(self):
+ index = pd.Index(["a", "b", "c"])
+ # doesn't fail test unless there is a check before `+=`
+ assert "a" in index
+
+ index += "_x"
+ assert "a_x" in index
+
+ def test_add(self):
+ index = pd.Index([str(i) for i in range(10)])
+ expected = pd.Index(index.values * 2)
+ tm.assert_index_equal(index + index, expected)
+ tm.assert_index_equal(index + index.tolist(), expected)
+ tm.assert_index_equal(index.tolist() + index, expected)
+
+ # test add and radd
+ index = pd.Index(list("abc"))
+ expected = pd.Index(["a1", "b1", "c1"])
+ tm.assert_index_equal(index + "1", expected)
+ expected = pd.Index(["1a", "1b", "1c"])
+ tm.assert_index_equal("1" + index, expected)
+
+ def test_sub_fail(self):
+ index = pd.Index([str(i) for i in range(10)])
+
+ msg = "unsupported operand type|Cannot broadcast|sub' not supported"
+ with pytest.raises(TypeError, match=msg):
+ index - "a"
+ with pytest.raises(TypeError, match=msg):
+ index - index
+ with pytest.raises(TypeError, match=msg):
+ index - index.tolist()
+ with pytest.raises(TypeError, match=msg):
+ index.tolist() - index
+
+ def test_sub_object(self):
+ # GH#19369
+ index = pd.Index([Decimal(1), Decimal(2)])
+ expected = pd.Index([Decimal(0), Decimal(1)])
+
+ result = index - Decimal(1)
+ tm.assert_index_equal(result, expected)
+
+ result = index - pd.Index([Decimal(1), Decimal(1)])
+ tm.assert_index_equal(result, expected)
+
+ msg = "unsupported operand type"
+ with pytest.raises(TypeError, match=msg):
+ index - "foo"
+
+ with pytest.raises(TypeError, match=msg):
+ index - np.array([2, "foo"], dtype=object)
+
+ def test_rsub_object(self, fixed_now_ts):
+ # GH#19369
+ index = pd.Index([Decimal(1), Decimal(2)])
+ expected = pd.Index([Decimal(1), Decimal(0)])
+
+ result = Decimal(2) - index
+ tm.assert_index_equal(result, expected)
+
+ result = np.array([Decimal(2), Decimal(2)]) - index
+ tm.assert_index_equal(result, expected)
+
+ msg = "unsupported operand type"
+ with pytest.raises(TypeError, match=msg):
+ "foo" - index
+
+ with pytest.raises(TypeError, match=msg):
+ np.array([True, fixed_now_ts]) - index
+
+
+class MyIndex(pd.Index):
+ # Simple index subclass that tracks ops calls.
+
+ _calls: int
+
+ @classmethod
+ def _simple_new(cls, values, name=None, dtype=None):
+ result = object.__new__(cls)
+ result._data = values
+ result._name = name
+ result._calls = 0
+ result._reset_identity()
+
+ return result
+
+ def __add__(self, other):
+ self._calls += 1
+ return self._simple_new(self._data)
+
+ def __radd__(self, other):
+ return self.__add__(other)
+
+
+@pytest.mark.parametrize(
+ "other",
+ [
+ [datetime.timedelta(1), datetime.timedelta(2)],
+ [datetime.datetime(2000, 1, 1), datetime.datetime(2000, 1, 2)],
+ [pd.Period("2000"), pd.Period("2001")],
+ ["a", "b"],
+ ],
+ ids=["timedelta", "datetime", "period", "object"],
+)
+def test_index_ops_defer_to_unknown_subclasses(other):
+ # https://github.com/pandas-dev/pandas/issues/31109
+ values = np.array(
+ [datetime.date(2000, 1, 1), datetime.date(2000, 1, 2)], dtype=object
+ )
+ a = MyIndex._simple_new(values)
+ other = pd.Index(other)
+ result = other + a
+ assert isinstance(result, MyIndex)
+ assert a._calls == 1
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_period.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_period.py
new file mode 100644
index 0000000000000000000000000000000000000000..24733f3b3e5634e96742e8035d00bcb3edf7ccd7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_period.py
@@ -0,0 +1,1679 @@
+# Arithmetic tests for DataFrame/Series/Index/Array classes that should
+# behave identically.
+# Specifically for Period dtype
+import operator
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import (
+ IncompatibleFrequency,
+ Period,
+ Timestamp,
+ to_offset,
+)
+
+import pandas as pd
+from pandas import (
+ PeriodIndex,
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ period_range,
+)
+import pandas._testing as tm
+from pandas.core import ops
+from pandas.core.arrays import TimedeltaArray
+from pandas.tests.arithmetic.common import (
+ assert_invalid_addsub_type,
+ assert_invalid_comparison,
+ get_upcast_box,
+)
+
+_common_mismatch = [
+ pd.offsets.YearBegin(2),
+ pd.offsets.MonthBegin(1),
+ pd.offsets.Minute(),
+]
+
+
+@pytest.fixture(
+ params=[
+ Timedelta(minutes=30).to_pytimedelta(),
+ np.timedelta64(30, "s"),
+ Timedelta(seconds=30),
+ *_common_mismatch,
+ ]
+)
+def not_hourly(request):
+ """
+ Several timedelta-like and DateOffset instances that are _not_
+ compatible with Hourly frequencies.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ np.timedelta64(365, "D"),
+ Timedelta(days=365).to_pytimedelta(),
+ Timedelta(days=365),
+ *_common_mismatch,
+ ]
+)
+def mismatched_freq(request):
+ """
+ Several timedelta-like and DateOffset instances that are _not_
+ compatible with Monthly or Annual frequencies.
+ """
+ return request.param
+
+
+# ------------------------------------------------------------------
+# Comparisons
+
+
+class TestPeriodArrayLikeComparisons:
+ # Comparison tests for PeriodDtype vectors fully parametrized over
+ # DataFrame/Series/PeriodIndex/PeriodArray. Ideally all comparison
+ # tests will eventually end up here.
+
+ @pytest.mark.parametrize("other", ["2017", Period("2017", freq="D")])
+ def test_eq_scalar(self, other, box_with_array):
+ idx = PeriodIndex(["2017", "2017", "2018"], freq="D")
+ idx = tm.box_expected(idx, box_with_array)
+ xbox = get_upcast_box(idx, other, True)
+
+ expected = np.array([True, True, False])
+ expected = tm.box_expected(expected, xbox)
+
+ result = idx == other
+
+ tm.assert_equal(result, expected)
+
+ def test_compare_zerodim(self, box_with_array):
+ # GH#26689 make sure we unbox zero-dimensional arrays
+
+ pi = period_range("2000", periods=4)
+ other = np.array(pi.to_numpy()[0])
+
+ pi = tm.box_expected(pi, box_with_array)
+ xbox = get_upcast_box(pi, other, True)
+
+ result = pi <= other
+ expected = np.array([True, False, False, False])
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "scalar",
+ [
+ "foo",
+ Timestamp("2021-01-01"),
+ Timedelta(days=4),
+ 9,
+ 9.5,
+ 2000, # specifically don't consider 2000 to match Period("2000", "D")
+ False,
+ None,
+ ],
+ )
+ def test_compare_invalid_scalar(self, box_with_array, scalar):
+ # GH#28980
+ # comparison with scalar that cannot be interpreted as a Period
+ pi = period_range("2000", periods=4)
+ parr = tm.box_expected(pi, box_with_array)
+ assert_invalid_comparison(parr, scalar, box_with_array)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ pd.date_range("2000", periods=4).array,
+ pd.timedelta_range("1D", periods=4).array,
+ np.arange(4),
+ np.arange(4).astype(np.float64),
+ list(range(4)),
+ # match Period semantics by not treating integers as Periods
+ [2000, 2001, 2002, 2003],
+ np.arange(2000, 2004),
+ np.arange(2000, 2004).astype(object),
+ pd.Index([2000, 2001, 2002, 2003]),
+ ],
+ )
+ def test_compare_invalid_listlike(self, box_with_array, other):
+ pi = period_range("2000", periods=4)
+ parr = tm.box_expected(pi, box_with_array)
+ assert_invalid_comparison(parr, other, box_with_array)
+
+ @pytest.mark.parametrize("other_box", [list, np.array, lambda x: x.astype(object)])
+ def test_compare_object_dtype(self, box_with_array, other_box):
+ pi = period_range("2000", periods=5)
+ parr = tm.box_expected(pi, box_with_array)
+
+ other = other_box(pi)
+ xbox = get_upcast_box(parr, other, True)
+
+ expected = np.array([True, True, True, True, True])
+ expected = tm.box_expected(expected, xbox)
+
+ result = parr == other
+ tm.assert_equal(result, expected)
+ result = parr <= other
+ tm.assert_equal(result, expected)
+ result = parr >= other
+ tm.assert_equal(result, expected)
+
+ result = parr != other
+ tm.assert_equal(result, ~expected)
+ result = parr < other
+ tm.assert_equal(result, ~expected)
+ result = parr > other
+ tm.assert_equal(result, ~expected)
+
+ other = other_box(pi[::-1])
+
+ expected = np.array([False, False, True, False, False])
+ expected = tm.box_expected(expected, xbox)
+ result = parr == other
+ tm.assert_equal(result, expected)
+
+ expected = np.array([True, True, True, False, False])
+ expected = tm.box_expected(expected, xbox)
+ result = parr <= other
+ tm.assert_equal(result, expected)
+
+ expected = np.array([False, False, True, True, True])
+ expected = tm.box_expected(expected, xbox)
+ result = parr >= other
+ tm.assert_equal(result, expected)
+
+ expected = np.array([True, True, False, True, True])
+ expected = tm.box_expected(expected, xbox)
+ result = parr != other
+ tm.assert_equal(result, expected)
+
+ expected = np.array([True, True, False, False, False])
+ expected = tm.box_expected(expected, xbox)
+ result = parr < other
+ tm.assert_equal(result, expected)
+
+ expected = np.array([False, False, False, True, True])
+ expected = tm.box_expected(expected, xbox)
+ result = parr > other
+ tm.assert_equal(result, expected)
+
+
+class TestPeriodIndexComparisons:
+ # TODO: parameterize over boxes
+
+ def test_pi_cmp_period(self):
+ idx = period_range("2007-01", periods=20, freq="M")
+ per = idx[10]
+
+ result = idx < per
+ exp = idx.values < idx.values[10]
+ tm.assert_numpy_array_equal(result, exp)
+
+ # Tests Period.__richcmp__ against ndarray[object, ndim=2]
+ result = idx.values.reshape(10, 2) < per
+ tm.assert_numpy_array_equal(result, exp.reshape(10, 2))
+
+ # Tests Period.__richcmp__ against ndarray[object, ndim=0]
+ result = idx < np.array(per)
+ tm.assert_numpy_array_equal(result, exp)
+
+ # TODO: moved from test_datetime64; de-duplicate with version below
+ def test_parr_cmp_period_scalar2(self, box_with_array):
+ pi = period_range("2000-01-01", periods=10, freq="D")
+
+ val = pi[3]
+ expected = [x > val for x in pi]
+
+ ser = tm.box_expected(pi, box_with_array)
+ xbox = get_upcast_box(ser, val, True)
+
+ expected = tm.box_expected(expected, xbox)
+ result = ser > val
+ tm.assert_equal(result, expected)
+
+ val = pi[5]
+ result = ser > val
+ expected = [x > val for x in pi]
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("freq", ["M", "2M", "3M"])
+ def test_parr_cmp_period_scalar(self, freq, box_with_array):
+ # GH#13200
+ base = PeriodIndex(["2011-01", "2011-02", "2011-03", "2011-04"], freq=freq)
+ base = tm.box_expected(base, box_with_array)
+ per = Period("2011-02", freq=freq)
+ xbox = get_upcast_box(base, per, True)
+
+ exp = np.array([False, True, False, False])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base == per, exp)
+ tm.assert_equal(per == base, exp)
+
+ exp = np.array([True, False, True, True])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base != per, exp)
+ tm.assert_equal(per != base, exp)
+
+ exp = np.array([False, False, True, True])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base > per, exp)
+ tm.assert_equal(per < base, exp)
+
+ exp = np.array([True, False, False, False])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base < per, exp)
+ tm.assert_equal(per > base, exp)
+
+ exp = np.array([False, True, True, True])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base >= per, exp)
+ tm.assert_equal(per <= base, exp)
+
+ exp = np.array([True, True, False, False])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base <= per, exp)
+ tm.assert_equal(per >= base, exp)
+
+ @pytest.mark.parametrize("freq", ["M", "2M", "3M"])
+ def test_parr_cmp_pi(self, freq, box_with_array):
+ # GH#13200
+ base = PeriodIndex(["2011-01", "2011-02", "2011-03", "2011-04"], freq=freq)
+ base = tm.box_expected(base, box_with_array)
+
+ # TODO: could also box idx?
+ idx = PeriodIndex(["2011-02", "2011-01", "2011-03", "2011-05"], freq=freq)
+
+ xbox = get_upcast_box(base, idx, True)
+
+ exp = np.array([False, False, True, False])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base == idx, exp)
+
+ exp = np.array([True, True, False, True])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base != idx, exp)
+
+ exp = np.array([False, True, False, False])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base > idx, exp)
+
+ exp = np.array([True, False, False, True])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base < idx, exp)
+
+ exp = np.array([False, True, True, False])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base >= idx, exp)
+
+ exp = np.array([True, False, True, True])
+ exp = tm.box_expected(exp, xbox)
+ tm.assert_equal(base <= idx, exp)
+
+ @pytest.mark.parametrize("freq", ["M", "2M", "3M"])
+ def test_parr_cmp_pi_mismatched_freq(self, freq, box_with_array):
+ # GH#13200
+ # different base freq
+ base = PeriodIndex(["2011-01", "2011-02", "2011-03", "2011-04"], freq=freq)
+ base = tm.box_expected(base, box_with_array)
+
+ msg = rf"Invalid comparison between dtype=period\[{freq}\] and Period"
+ with pytest.raises(TypeError, match=msg):
+ base <= Period("2011", freq="Y")
+
+ with pytest.raises(TypeError, match=msg):
+ Period("2011", freq="Y") >= base
+
+ # TODO: Could parametrize over boxes for idx?
+ idx = PeriodIndex(["2011", "2012", "2013", "2014"], freq="Y")
+ rev_msg = r"Invalid comparison between dtype=period\[Y-DEC\] and PeriodArray"
+ idx_msg = rev_msg if box_with_array in [tm.to_array, pd.array] else msg
+ with pytest.raises(TypeError, match=idx_msg):
+ base <= idx
+
+ # Different frequency
+ msg = rf"Invalid comparison between dtype=period\[{freq}\] and Period"
+ with pytest.raises(TypeError, match=msg):
+ base <= Period("2011", freq="4M")
+
+ with pytest.raises(TypeError, match=msg):
+ Period("2011", freq="4M") >= base
+
+ idx = PeriodIndex(["2011", "2012", "2013", "2014"], freq="4M")
+ rev_msg = r"Invalid comparison between dtype=period\[4M\] and PeriodArray"
+ idx_msg = rev_msg if box_with_array in [tm.to_array, pd.array] else msg
+ with pytest.raises(TypeError, match=idx_msg):
+ base <= idx
+
+ @pytest.mark.parametrize("freq", ["M", "2M", "3M"])
+ def test_pi_cmp_nat(self, freq):
+ idx1 = PeriodIndex(["2011-01", "2011-02", "NaT", "2011-05"], freq=freq)
+ per = idx1[1]
+
+ result = idx1 > per
+ exp = np.array([False, False, False, True])
+ tm.assert_numpy_array_equal(result, exp)
+ result = per < idx1
+ tm.assert_numpy_array_equal(result, exp)
+
+ result = idx1 == pd.NaT
+ exp = np.array([False, False, False, False])
+ tm.assert_numpy_array_equal(result, exp)
+ result = pd.NaT == idx1
+ tm.assert_numpy_array_equal(result, exp)
+
+ result = idx1 != pd.NaT
+ exp = np.array([True, True, True, True])
+ tm.assert_numpy_array_equal(result, exp)
+ result = pd.NaT != idx1
+ tm.assert_numpy_array_equal(result, exp)
+
+ idx2 = PeriodIndex(["2011-02", "2011-01", "2011-04", "NaT"], freq=freq)
+ result = idx1 < idx2
+ exp = np.array([True, False, False, False])
+ tm.assert_numpy_array_equal(result, exp)
+
+ result = idx1 == idx2
+ exp = np.array([False, False, False, False])
+ tm.assert_numpy_array_equal(result, exp)
+
+ result = idx1 != idx2
+ exp = np.array([True, True, True, True])
+ tm.assert_numpy_array_equal(result, exp)
+
+ result = idx1 == idx1
+ exp = np.array([True, True, False, True])
+ tm.assert_numpy_array_equal(result, exp)
+
+ result = idx1 != idx1
+ exp = np.array([False, False, True, False])
+ tm.assert_numpy_array_equal(result, exp)
+
+ @pytest.mark.parametrize("freq", ["M", "2M", "3M"])
+ def test_pi_cmp_nat_mismatched_freq_raises(self, freq):
+ idx1 = PeriodIndex(["2011-01", "2011-02", "NaT", "2011-05"], freq=freq)
+
+ diff = PeriodIndex(["2011-02", "2011-01", "2011-04", "NaT"], freq="4M")
+ msg = rf"Invalid comparison between dtype=period\[{freq}\] and PeriodArray"
+ with pytest.raises(TypeError, match=msg):
+ idx1 > diff
+
+ result = idx1 == diff
+ expected = np.array([False, False, False, False], dtype=bool)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # TODO: De-duplicate with test_pi_cmp_nat
+ @pytest.mark.parametrize("dtype", [object, None])
+ def test_comp_nat(self, dtype):
+ left = PeriodIndex([Period("2011-01-01"), pd.NaT, Period("2011-01-03")])
+ right = PeriodIndex([pd.NaT, pd.NaT, Period("2011-01-03")])
+
+ if dtype is not None:
+ left = left.astype(dtype)
+ right = right.astype(dtype)
+
+ result = left == right
+ expected = np.array([False, False, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = left != right
+ expected = np.array([True, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ expected = np.array([False, False, False])
+ tm.assert_numpy_array_equal(left == pd.NaT, expected)
+ tm.assert_numpy_array_equal(pd.NaT == right, expected)
+
+ expected = np.array([True, True, True])
+ tm.assert_numpy_array_equal(left != pd.NaT, expected)
+ tm.assert_numpy_array_equal(pd.NaT != left, expected)
+
+ expected = np.array([False, False, False])
+ tm.assert_numpy_array_equal(left < pd.NaT, expected)
+ tm.assert_numpy_array_equal(pd.NaT > left, expected)
+
+
+class TestPeriodSeriesComparisons:
+ def test_cmp_series_period_series_mixed_freq(self):
+ # GH#13200
+ base = Series(
+ [
+ Period("2011", freq="Y"),
+ Period("2011-02", freq="M"),
+ Period("2013", freq="Y"),
+ Period("2011-04", freq="M"),
+ ]
+ )
+
+ ser = Series(
+ [
+ Period("2012", freq="Y"),
+ Period("2011-01", freq="M"),
+ Period("2013", freq="Y"),
+ Period("2011-05", freq="M"),
+ ]
+ )
+
+ exp = Series([False, False, True, False])
+ tm.assert_series_equal(base == ser, exp)
+
+ exp = Series([True, True, False, True])
+ tm.assert_series_equal(base != ser, exp)
+
+ exp = Series([False, True, False, False])
+ tm.assert_series_equal(base > ser, exp)
+
+ exp = Series([True, False, False, True])
+ tm.assert_series_equal(base < ser, exp)
+
+ exp = Series([False, True, True, False])
+ tm.assert_series_equal(base >= ser, exp)
+
+ exp = Series([True, False, True, True])
+ tm.assert_series_equal(base <= ser, exp)
+
+
+class TestPeriodIndexSeriesComparisonConsistency:
+ """Test PeriodIndex and Period Series Ops consistency"""
+
+ # TODO: needs parametrization+de-duplication
+
+ def _check(self, values, func, expected):
+ # Test PeriodIndex and Period Series Ops consistency
+
+ idx = PeriodIndex(values)
+ result = func(idx)
+
+ # check that we don't pass an unwanted type to tm.assert_equal
+ assert isinstance(expected, (pd.Index, np.ndarray))
+ tm.assert_equal(result, expected)
+
+ s = Series(values)
+ result = func(s)
+
+ exp = Series(expected, name=values.name)
+ tm.assert_series_equal(result, exp)
+
+ def test_pi_comp_period(self):
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "2011-03", "2011-04"], freq="M", name="idx"
+ )
+ per = idx[2]
+
+ f = lambda x: x == per
+ exp = np.array([False, False, True, False], dtype=np.bool_)
+ self._check(idx, f, exp)
+ f = lambda x: per == x
+ self._check(idx, f, exp)
+
+ f = lambda x: x != per
+ exp = np.array([True, True, False, True], dtype=np.bool_)
+ self._check(idx, f, exp)
+ f = lambda x: per != x
+ self._check(idx, f, exp)
+
+ f = lambda x: per >= x
+ exp = np.array([True, True, True, False], dtype=np.bool_)
+ self._check(idx, f, exp)
+
+ f = lambda x: x > per
+ exp = np.array([False, False, False, True], dtype=np.bool_)
+ self._check(idx, f, exp)
+
+ f = lambda x: per >= x
+ exp = np.array([True, True, True, False], dtype=np.bool_)
+ self._check(idx, f, exp)
+
+ def test_pi_comp_period_nat(self):
+ idx = PeriodIndex(
+ ["2011-01", "NaT", "2011-03", "2011-04"], freq="M", name="idx"
+ )
+ per = idx[2]
+
+ f = lambda x: x == per
+ exp = np.array([False, False, True, False], dtype=np.bool_)
+ self._check(idx, f, exp)
+ f = lambda x: per == x
+ self._check(idx, f, exp)
+
+ f = lambda x: x == pd.NaT
+ exp = np.array([False, False, False, False], dtype=np.bool_)
+ self._check(idx, f, exp)
+ f = lambda x: pd.NaT == x
+ self._check(idx, f, exp)
+
+ f = lambda x: x != per
+ exp = np.array([True, True, False, True], dtype=np.bool_)
+ self._check(idx, f, exp)
+ f = lambda x: per != x
+ self._check(idx, f, exp)
+
+ f = lambda x: x != pd.NaT
+ exp = np.array([True, True, True, True], dtype=np.bool_)
+ self._check(idx, f, exp)
+ f = lambda x: pd.NaT != x
+ self._check(idx, f, exp)
+
+ f = lambda x: per >= x
+ exp = np.array([True, False, True, False], dtype=np.bool_)
+ self._check(idx, f, exp)
+
+ f = lambda x: x < per
+ exp = np.array([True, False, False, False], dtype=np.bool_)
+ self._check(idx, f, exp)
+
+ f = lambda x: x > pd.NaT
+ exp = np.array([False, False, False, False], dtype=np.bool_)
+ self._check(idx, f, exp)
+
+ f = lambda x: pd.NaT >= x
+ exp = np.array([False, False, False, False], dtype=np.bool_)
+ self._check(idx, f, exp)
+
+
+# ------------------------------------------------------------------
+# Arithmetic
+
+
+class TestPeriodFrameArithmetic:
+ def test_ops_frame_period(self):
+ # GH#13043
+ df = pd.DataFrame(
+ {
+ "A": [Period("2015-01", freq="M"), Period("2015-02", freq="M")],
+ "B": [Period("2014-01", freq="M"), Period("2014-02", freq="M")],
+ }
+ )
+ assert df["A"].dtype == "Period[M]"
+ assert df["B"].dtype == "Period[M]"
+
+ p = Period("2015-03", freq="M")
+ off = p.freq
+ # dtype will be object because of original dtype
+ exp = pd.DataFrame(
+ {
+ "A": np.array([2 * off, 1 * off], dtype=object),
+ "B": np.array([14 * off, 13 * off], dtype=object),
+ }
+ )
+ tm.assert_frame_equal(p - df, exp)
+ tm.assert_frame_equal(df - p, -1 * exp)
+
+ df2 = pd.DataFrame(
+ {
+ "A": [Period("2015-05", freq="M"), Period("2015-06", freq="M")],
+ "B": [Period("2015-05", freq="M"), Period("2015-06", freq="M")],
+ }
+ )
+ assert df2["A"].dtype == "Period[M]"
+ assert df2["B"].dtype == "Period[M]"
+
+ exp = pd.DataFrame(
+ {
+ "A": np.array([4 * off, 4 * off], dtype=object),
+ "B": np.array([16 * off, 16 * off], dtype=object),
+ }
+ )
+ tm.assert_frame_equal(df2 - df, exp)
+ tm.assert_frame_equal(df - df2, -1 * exp)
+
+
+class TestPeriodIndexArithmetic:
+ # ---------------------------------------------------------------
+ # __add__/__sub__ with PeriodIndex
+ # PeriodIndex + other is defined for integers and timedelta-like others
+ # PeriodIndex - other is defined for integers, timedelta-like others,
+ # and PeriodIndex (with matching freq)
+
+ def test_parr_add_iadd_parr_raises(self, box_with_array):
+ rng = period_range("1/1/2000", freq="D", periods=5)
+ other = period_range("1/6/2000", freq="D", periods=5)
+ # TODO: parametrize over boxes for other?
+
+ rng = tm.box_expected(rng, box_with_array)
+ # An earlier implementation of PeriodIndex addition performed
+ # a set operation (union). This has since been changed to
+ # raise a TypeError. See GH#14164 and GH#13077 for historical
+ # reference.
+ msg = r"unsupported operand type\(s\) for \+: .* and .*"
+ with pytest.raises(TypeError, match=msg):
+ rng + other
+
+ with pytest.raises(TypeError, match=msg):
+ rng += other
+
+ def test_pi_sub_isub_pi(self):
+ # GH#20049
+ # For historical reference see GH#14164, GH#13077.
+ # PeriodIndex subtraction originally performed set difference,
+ # then changed to raise TypeError before being implemented in GH#20049
+ rng = period_range("1/1/2000", freq="D", periods=5)
+ other = period_range("1/6/2000", freq="D", periods=5)
+
+ off = rng.freq
+ expected = pd.Index([-5 * off] * 5)
+ result = rng - other
+ tm.assert_index_equal(result, expected)
+
+ rng -= other
+ tm.assert_index_equal(rng, expected)
+
+ def test_pi_sub_pi_with_nat(self):
+ rng = period_range("1/1/2000", freq="D", periods=5)
+ other = rng[1:].insert(0, pd.NaT)
+ assert other[1:].equals(rng[1:])
+
+ result = rng - other
+ off = rng.freq
+ expected = pd.Index([pd.NaT, 0 * off, 0 * off, 0 * off, 0 * off])
+ tm.assert_index_equal(result, expected)
+
+ def test_parr_sub_pi_mismatched_freq(self, box_with_array, box_with_array2):
+ rng = period_range("1/1/2000", freq="D", periods=5)
+ other = period_range("1/6/2000", freq="h", periods=5)
+
+ rng = tm.box_expected(rng, box_with_array)
+ other = tm.box_expected(other, box_with_array2)
+ msg = r"Input has different freq=[hD] from PeriodArray\(freq=[Dh]\)"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng - other
+
+ @pytest.mark.parametrize("n", [1, 2, 3, 4])
+ def test_sub_n_gt_1_ticks(self, tick_classes, n):
+ # GH 23878
+ p1_d = "19910905"
+ p2_d = "19920406"
+ p1 = PeriodIndex([p1_d], freq=tick_classes(n))
+ p2 = PeriodIndex([p2_d], freq=tick_classes(n))
+
+ expected = PeriodIndex([p2_d], freq=p2.freq.base) - PeriodIndex(
+ [p1_d], freq=p1.freq.base
+ )
+
+ tm.assert_index_equal((p2 - p1), expected)
+
+ @pytest.mark.parametrize("n", [1, 2, 3, 4])
+ @pytest.mark.parametrize(
+ "offset, kwd_name",
+ [
+ (pd.offsets.YearEnd, "month"),
+ (pd.offsets.QuarterEnd, "startingMonth"),
+ (pd.offsets.MonthEnd, None),
+ (pd.offsets.Week, "weekday"),
+ ],
+ )
+ def test_sub_n_gt_1_offsets(self, offset, kwd_name, n):
+ # GH 23878
+ kwds = {kwd_name: 3} if kwd_name is not None else {}
+ p1_d = "19910905"
+ p2_d = "19920406"
+ freq = offset(n, normalize=False, **kwds)
+ p1 = PeriodIndex([p1_d], freq=freq)
+ p2 = PeriodIndex([p2_d], freq=freq)
+
+ result = p2 - p1
+ expected = PeriodIndex([p2_d], freq=freq.base) - PeriodIndex(
+ [p1_d], freq=freq.base
+ )
+
+ tm.assert_index_equal(result, expected)
+
+ # -------------------------------------------------------------
+ # Invalid Operations
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ # datetime scalars
+ Timestamp("2016-01-01"),
+ Timestamp("2016-01-01").to_pydatetime(),
+ Timestamp("2016-01-01").to_datetime64(),
+ # datetime-like arrays
+ pd.date_range("2016-01-01", periods=3, freq="h"),
+ pd.date_range("2016-01-01", periods=3, tz="Europe/Brussels"),
+ pd.date_range("2016-01-01", periods=3, freq="s")._data,
+ pd.date_range("2016-01-01", periods=3, tz="Asia/Tokyo")._data,
+ # Miscellaneous invalid types
+ 3.14,
+ np.array([2.0, 3.0, 4.0]),
+ ],
+ )
+ def test_parr_add_sub_invalid(self, other, box_with_array):
+ # GH#23215
+ rng = period_range("1/1/2000", freq="D", periods=3)
+ rng = tm.box_expected(rng, box_with_array)
+
+ msg = "|".join(
+ [
+ r"(:?cannot add PeriodArray and .*)",
+ r"(:?cannot subtract .* from (:?a\s)?.*)",
+ r"(:?unsupported operand type\(s\) for \+: .* and .*)",
+ r"unsupported operand type\(s\) for [+-]: .* and .*",
+ ]
+ )
+ assert_invalid_addsub_type(rng, other, msg)
+ with pytest.raises(TypeError, match=msg):
+ rng + other
+ with pytest.raises(TypeError, match=msg):
+ other + rng
+ with pytest.raises(TypeError, match=msg):
+ rng - other
+ with pytest.raises(TypeError, match=msg):
+ other - rng
+
+ # -----------------------------------------------------------------
+ # __add__/__sub__ with ndarray[datetime64] and ndarray[timedelta64]
+
+ def test_pi_add_sub_td64_array_non_tick_raises(self):
+ rng = period_range("1/1/2000", freq="Q", periods=3)
+ tdi = TimedeltaIndex(["-1 Day", "-1 Day", "-1 Day"])
+ tdarr = tdi.values
+
+ msg = r"Cannot add or subtract timedelta64\[ns\] dtype from period\[Q-DEC\]"
+ with pytest.raises(TypeError, match=msg):
+ rng + tdarr
+ with pytest.raises(TypeError, match=msg):
+ tdarr + rng
+
+ with pytest.raises(TypeError, match=msg):
+ rng - tdarr
+ msg = r"cannot subtract PeriodArray from TimedeltaArray"
+ with pytest.raises(TypeError, match=msg):
+ tdarr - rng
+
+ def test_pi_add_sub_td64_array_tick(self):
+ # PeriodIndex + Timedelta-like is allowed only with
+ # tick-like frequencies
+ rng = period_range("1/1/2000", freq="90D", periods=3)
+ tdi = TimedeltaIndex(["-1 Day", "-1 Day", "-1 Day"])
+ tdarr = tdi.values
+
+ expected = period_range("12/31/1999", freq="90D", periods=3)
+ result = rng + tdi
+ tm.assert_index_equal(result, expected)
+ result = rng + tdarr
+ tm.assert_index_equal(result, expected)
+ result = tdi + rng
+ tm.assert_index_equal(result, expected)
+ result = tdarr + rng
+ tm.assert_index_equal(result, expected)
+
+ expected = period_range("1/2/2000", freq="90D", periods=3)
+
+ result = rng - tdi
+ tm.assert_index_equal(result, expected)
+ result = rng - tdarr
+ tm.assert_index_equal(result, expected)
+
+ msg = r"cannot subtract .* from .*"
+ with pytest.raises(TypeError, match=msg):
+ tdarr - rng
+
+ with pytest.raises(TypeError, match=msg):
+ tdi - rng
+
+ @pytest.mark.parametrize("pi_freq", ["D", "W", "Q", "h"])
+ @pytest.mark.parametrize("tdi_freq", [None, "h"])
+ def test_parr_sub_td64array(self, box_with_array, tdi_freq, pi_freq):
+ box = box_with_array
+ xbox = box if box not in [pd.array, tm.to_array] else pd.Index
+
+ tdi = TimedeltaIndex(["1 hours", "2 hours"], freq=tdi_freq)
+ dti = Timestamp("2018-03-07 17:16:40") + tdi
+ pi = dti.to_period(pi_freq)
+
+ # TODO: parametrize over box for pi?
+ td64obj = tm.box_expected(tdi, box)
+
+ if pi_freq == "h":
+ result = pi - td64obj
+ expected = (pi.to_timestamp("s") - tdi).to_period(pi_freq)
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(result, expected)
+
+ # Subtract from scalar
+ result = pi[0] - td64obj
+ expected = (pi[0].to_timestamp("s") - tdi).to_period(pi_freq)
+ expected = tm.box_expected(expected, box)
+ tm.assert_equal(result, expected)
+
+ elif pi_freq == "D":
+ # Tick, but non-compatible
+ msg = (
+ "Cannot add/subtract timedelta-like from PeriodArray that is "
+ "not an integer multiple of the PeriodArray's freq."
+ )
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ pi - td64obj
+
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ pi[0] - td64obj
+
+ else:
+ # With non-Tick freq, we could not add timedelta64 array regardless
+ # of what its resolution is
+ msg = "Cannot add or subtract timedelta64"
+ with pytest.raises(TypeError, match=msg):
+ pi - td64obj
+ with pytest.raises(TypeError, match=msg):
+ pi[0] - td64obj
+
+ # -----------------------------------------------------------------
+ # operations with array/Index of DateOffset objects
+
+ @pytest.mark.parametrize("box", [np.array, pd.Index])
+ def test_pi_add_offset_array(self, performance_warning, box):
+ # GH#18849
+ pi = PeriodIndex([Period("2015Q1"), Period("2016Q2")])
+ offs = box(
+ [
+ pd.offsets.QuarterEnd(n=1, startingMonth=12),
+ pd.offsets.QuarterEnd(n=-2, startingMonth=12),
+ ]
+ )
+ expected = PeriodIndex([Period("2015Q2"), Period("2015Q4")]).astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ res = pi + offs
+ tm.assert_index_equal(res, expected)
+
+ with tm.assert_produces_warning(performance_warning):
+ res2 = offs + pi
+ tm.assert_index_equal(res2, expected)
+
+ unanchored = np.array([pd.offsets.Hour(n=1), pd.offsets.Minute(n=-2)])
+ # addition/subtraction ops with incompatible offsets should issue
+ # a PerformanceWarning and _then_ raise a TypeError.
+ msg = r"Input cannot be converted to Period\(freq=Q-DEC\)"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ with tm.assert_produces_warning(performance_warning):
+ pi + unanchored
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ with tm.assert_produces_warning(performance_warning):
+ unanchored + pi
+
+ @pytest.mark.parametrize("box", [np.array, pd.Index])
+ def test_pi_sub_offset_array(self, performance_warning, box):
+ # GH#18824
+ pi = PeriodIndex([Period("2015Q1"), Period("2016Q2")])
+ other = box(
+ [
+ pd.offsets.QuarterEnd(n=1, startingMonth=12),
+ pd.offsets.QuarterEnd(n=-2, startingMonth=12),
+ ]
+ )
+
+ expected = PeriodIndex([pi[n] - other[n] for n in range(len(pi))])
+ expected = expected.astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ res = pi - other
+ tm.assert_index_equal(res, expected)
+
+ anchored = box([pd.offsets.MonthEnd(), pd.offsets.Day(n=2)])
+
+ # addition/subtraction ops with anchored offsets should issue
+ # a PerformanceWarning and _then_ raise a TypeError.
+ msg = r"Input has different freq=-1M from Period\(freq=Q-DEC\)"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ with tm.assert_produces_warning(performance_warning):
+ pi - anchored
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ with tm.assert_produces_warning(performance_warning):
+ anchored - pi
+
+ def test_pi_add_iadd_int(self, one):
+ # Variants of `one` for #19012
+ rng = period_range("2000-01-01 09:00", freq="h", periods=10)
+ result = rng + one
+ expected = period_range("2000-01-01 10:00", freq="h", periods=10)
+ tm.assert_index_equal(result, expected)
+ rng += one
+ tm.assert_index_equal(rng, expected)
+
+ def test_pi_sub_isub_int(self, one):
+ """
+ PeriodIndex.__sub__ and __isub__ with several representations of
+ the integer 1, e.g. int, np.int64, np.uint8, ...
+ """
+ rng = period_range("2000-01-01 09:00", freq="h", periods=10)
+ result = rng - one
+ expected = period_range("2000-01-01 08:00", freq="h", periods=10)
+ tm.assert_index_equal(result, expected)
+ rng -= one
+ tm.assert_index_equal(rng, expected)
+
+ @pytest.mark.parametrize("five", [5, np.array(5, dtype=np.int64)])
+ def test_pi_sub_intlike(self, five):
+ rng = period_range("2007-01", periods=50)
+
+ result = rng - five
+ exp = rng + (-five)
+ tm.assert_index_equal(result, exp)
+
+ def test_pi_add_sub_int_array_freqn_gt1(self):
+ # GH#47209 test adding array of ints when freq.n > 1 matches
+ # scalar behavior
+ pi = period_range("2016-01-01", periods=10, freq="2D")
+ arr = np.arange(10)
+ result = pi + arr
+ expected = pd.Index([x + y for x, y in zip(pi, arr, strict=True)])
+ tm.assert_index_equal(result, expected)
+
+ result = pi - arr
+ expected = pd.Index([x - y for x, y in zip(pi, arr, strict=True)])
+ tm.assert_index_equal(result, expected)
+
+ def test_pi_sub_isub_offset(self):
+ # offset
+ # DateOffset
+ rng = period_range("2014", "2024", freq="Y")
+ result = rng - pd.offsets.YearEnd(5)
+ expected = period_range("2009", "2019", freq="Y")
+ tm.assert_index_equal(result, expected)
+ rng -= pd.offsets.YearEnd(5)
+ tm.assert_index_equal(rng, expected)
+
+ rng = period_range("2014-01", "2016-12", freq="M")
+ result = rng - pd.offsets.MonthEnd(5)
+ expected = period_range("2013-08", "2016-07", freq="M")
+ tm.assert_index_equal(result, expected)
+
+ rng -= pd.offsets.MonthEnd(5)
+ tm.assert_index_equal(rng, expected)
+
+ @pytest.mark.parametrize("transpose", [True, False])
+ def test_pi_add_offset_n_gt1(self, box_with_array, transpose):
+ # GH#23215
+ # add offset to PeriodIndex with freq.n > 1
+
+ per = Period("2016-01", freq="2M")
+ pi = PeriodIndex([per])
+
+ expected = PeriodIndex(["2016-03"], freq="2M")
+
+ pi = tm.box_expected(pi, box_with_array, transpose=transpose)
+ expected = tm.box_expected(expected, box_with_array, transpose=transpose)
+
+ result = pi + per.freq
+ tm.assert_equal(result, expected)
+
+ result = per.freq + pi
+ tm.assert_equal(result, expected)
+
+ def test_pi_add_offset_n_gt1_not_divisible(self, box_with_array):
+ # GH#23215
+ # PeriodIndex with freq.n > 1 add offset with offset.n % freq.n != 0
+ pi = PeriodIndex(["2016-01"], freq="2M")
+ expected = PeriodIndex(["2016-04"], freq="2M")
+
+ pi = tm.box_expected(pi, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = pi + to_offset("3ME")
+ tm.assert_equal(result, expected)
+
+ result = to_offset("3ME") + pi
+ tm.assert_equal(result, expected)
+
+ # ---------------------------------------------------------------
+ # __add__/__sub__ with integer arrays
+
+ @pytest.mark.parametrize("int_holder", [np.array, pd.Index])
+ @pytest.mark.parametrize("op", [operator.add, ops.radd])
+ def test_pi_add_intarray(self, int_holder, op):
+ # GH#19959
+ pi = PeriodIndex([Period("2015Q1"), Period("NaT")])
+ other = int_holder([4, -1])
+
+ result = op(pi, other)
+ expected = PeriodIndex([Period("2016Q1"), Period("NaT")])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("int_holder", [np.array, pd.Index])
+ def test_pi_sub_intarray(self, int_holder):
+ # GH#19959
+ pi = PeriodIndex([Period("2015Q1"), Period("NaT")])
+ other = int_holder([4, -1])
+
+ result = pi - other
+ expected = PeriodIndex([Period("2014Q1"), Period("NaT")])
+ tm.assert_index_equal(result, expected)
+
+ msg = r"bad operand type for unary -: 'PeriodArray'"
+ with pytest.raises(TypeError, match=msg):
+ other - pi
+
+ # ---------------------------------------------------------------
+ # Timedelta-like (timedelta, timedelta64, Timedelta, Tick)
+ # TODO: Some of these are misnomers because of non-Tick DateOffsets
+
+ def test_parr_add_timedeltalike_minute_gt1(self, three_days, box_with_array):
+ # GH#23031 adding a time-delta-like offset to a PeriodArray that has
+ # minute frequency with n != 1. A more general case is tested below
+ # in test_pi_add_timedeltalike_tick_gt1, but here we write out the
+ # expected result more explicitly.
+ other = three_days
+ rng = period_range("2014-05-01", periods=3, freq="2D")
+ rng = tm.box_expected(rng, box_with_array)
+
+ expected = PeriodIndex(["2014-05-04", "2014-05-06", "2014-05-08"], freq="2D")
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = rng + other
+ tm.assert_equal(result, expected)
+
+ result = other + rng
+ tm.assert_equal(result, expected)
+
+ # subtraction
+ expected = PeriodIndex(["2014-04-28", "2014-04-30", "2014-05-02"], freq="2D")
+ expected = tm.box_expected(expected, box_with_array)
+ result = rng - other
+ tm.assert_equal(result, expected)
+
+ msg = "|".join(
+ [
+ r"bad operand type for unary -: 'PeriodArray'",
+ r"cannot subtract PeriodArray from timedelta64\[[hD]\]",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ other - rng
+
+ @pytest.mark.parametrize("freqstr", ["5ns", "5us", "5ms", "5s", "5min", "5h", "5D"])
+ def test_parr_add_timedeltalike_tick_gt1(self, three_days, freqstr, box_with_array):
+ # GH#23031 adding a time-delta-like offset to a PeriodArray that has
+ # tick-like frequency with n != 1
+ other = three_days
+ rng = period_range("2014-05-01", periods=6, freq=freqstr)
+ first = rng[0]
+ rng = tm.box_expected(rng, box_with_array)
+
+ expected = period_range(first + other, periods=6, freq=freqstr)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = rng + other
+ tm.assert_equal(result, expected)
+
+ result = other + rng
+ tm.assert_equal(result, expected)
+
+ # subtraction
+ expected = period_range(first - other, periods=6, freq=freqstr)
+ expected = tm.box_expected(expected, box_with_array)
+ result = rng - other
+ tm.assert_equal(result, expected)
+ msg = "|".join(
+ [
+ r"bad operand type for unary -: 'PeriodArray'",
+ r"cannot subtract PeriodArray from timedelta64\[[hD]\]",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ other - rng
+
+ def test_pi_add_iadd_timedeltalike_daily(self, three_days):
+ # Tick
+ other = three_days
+ rng = period_range("2014-05-01", "2014-05-15", freq="D")
+ expected = period_range("2014-05-04", "2014-05-18", freq="D")
+
+ result = rng + other
+ tm.assert_index_equal(result, expected)
+
+ rng += other
+ tm.assert_index_equal(rng, expected)
+
+ def test_pi_sub_isub_timedeltalike_daily(self, three_days):
+ # Tick-like 3 Days
+ other = three_days
+ rng = period_range("2014-05-01", "2014-05-15", freq="D")
+ expected = period_range("2014-04-28", "2014-05-12", freq="D")
+
+ result = rng - other
+ tm.assert_index_equal(result, expected)
+
+ rng -= other
+ tm.assert_index_equal(rng, expected)
+
+ def test_parr_add_sub_timedeltalike_freq_mismatch_daily(
+ self, not_daily, box_with_array
+ ):
+ other = not_daily
+ rng = period_range("2014-05-01", "2014-05-15", freq="D")
+ rng = tm.box_expected(rng, box_with_array)
+
+ msg = "|".join(
+ [
+ # non-timedelta-like DateOffset
+ "Input has different freq(=.+)? from Period.*?\\(freq=D\\)",
+ # timedelta/td64/Timedelta but not a multiple of 24H
+ "Cannot add/subtract timedelta-like from PeriodArray that is "
+ "not an integer multiple of the PeriodArray's freq.",
+ ]
+ )
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng + other
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng += other
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng - other
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng -= other
+
+ def test_pi_add_iadd_timedeltalike_hourly(self, two_hours):
+ other = two_hours
+ rng = period_range("2014-01-01 10:00", "2014-01-05 10:00", freq="h")
+ expected = period_range("2014-01-01 12:00", "2014-01-05 12:00", freq="h")
+
+ result = rng + other
+ tm.assert_index_equal(result, expected)
+
+ rng += other
+ tm.assert_index_equal(rng, expected)
+
+ def test_parr_add_timedeltalike_mismatched_freq_hourly(
+ self, not_hourly, box_with_array
+ ):
+ other = not_hourly
+ rng = period_range("2014-01-01 10:00", "2014-01-05 10:00", freq="h")
+ rng = tm.box_expected(rng, box_with_array)
+ msg = "|".join(
+ [
+ # non-timedelta-like DateOffset
+ "Input has different freq(=.+)? from Period.*?\\(freq=h\\)",
+ # timedelta/td64/Timedelta but not a multiple of 24H
+ "Cannot add/subtract timedelta-like from PeriodArray that is "
+ "not an integer multiple of the PeriodArray's freq.",
+ ]
+ )
+
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng + other
+
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng += other
+
+ def test_pi_sub_isub_timedeltalike_hourly(self, two_hours):
+ other = two_hours
+ rng = period_range("2014-01-01 10:00", "2014-01-05 10:00", freq="h")
+ expected = period_range("2014-01-01 08:00", "2014-01-05 08:00", freq="h")
+
+ result = rng - other
+ tm.assert_index_equal(result, expected)
+
+ rng -= other
+ tm.assert_index_equal(rng, expected)
+
+ def test_add_iadd_timedeltalike_annual(self):
+ # offset
+ # DateOffset
+ rng = period_range("2014", "2024", freq="Y")
+ result = rng + pd.offsets.YearEnd(5)
+ expected = period_range("2019", "2029", freq="Y")
+ tm.assert_index_equal(result, expected)
+ rng += pd.offsets.YearEnd(5)
+ tm.assert_index_equal(rng, expected)
+
+ def test_pi_add_sub_timedeltalike_freq_mismatch_annual(self, mismatched_freq):
+ other = mismatched_freq
+ rng = period_range("2014", "2024", freq="Y")
+ msg = "Input has different freq(=.+)? from Period.*?\\(freq=Y-DEC\\)"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng + other
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng += other
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng - other
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng -= other
+
+ def test_pi_add_iadd_timedeltalike_M(self):
+ rng = period_range("2014-01", "2016-12", freq="M")
+ expected = period_range("2014-06", "2017-05", freq="M")
+
+ result = rng + pd.offsets.MonthEnd(5)
+ tm.assert_index_equal(result, expected)
+
+ rng += pd.offsets.MonthEnd(5)
+ tm.assert_index_equal(rng, expected)
+
+ def test_pi_add_sub_timedeltalike_freq_mismatch_monthly(self, mismatched_freq):
+ other = mismatched_freq
+ rng = period_range("2014-01", "2016-12", freq="M")
+ msg = "Input has different freq(=.+)? from Period.*?\\(freq=M\\)"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng + other
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng += other
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng - other
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ rng -= other
+
+ @pytest.mark.parametrize("transpose", [True, False])
+ def test_parr_add_sub_td64_nat(self, box_with_array, transpose):
+ # GH#23320 special handling for timedelta64("NaT")
+ pi = period_range("1994-04-01", periods=9, freq="19D")
+ other = np.timedelta64("NaT")
+ expected = PeriodIndex(["NaT"] * 9, freq="19D")
+
+ obj = tm.box_expected(pi, box_with_array, transpose=transpose)
+ expected = tm.box_expected(expected, box_with_array, transpose=transpose)
+
+ result = obj + other
+ tm.assert_equal(result, expected)
+ result = other + obj
+ tm.assert_equal(result, expected)
+ result = obj - other
+ tm.assert_equal(result, expected)
+ msg = r"cannot subtract .* from .*"
+ with pytest.raises(TypeError, match=msg):
+ other - obj
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ np.array(["NaT"] * 9, dtype="m8[ns]"),
+ TimedeltaArray._from_sequence(["NaT"] * 9, dtype="m8[ns]"),
+ ],
+ )
+ def test_parr_add_sub_tdt64_nat_array(self, box_with_array, other):
+ pi = period_range("1994-04-01", periods=9, freq="19D")
+ expected = PeriodIndex(["NaT"] * 9, freq="19D")
+
+ obj = tm.box_expected(pi, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = obj + other
+ tm.assert_equal(result, expected)
+ result = other + obj
+ tm.assert_equal(result, expected)
+ result = obj - other
+ tm.assert_equal(result, expected)
+ msg = r"cannot subtract .* from .*"
+ with pytest.raises(TypeError, match=msg):
+ other - obj
+
+ # some but not *all* NaT
+ other = other.copy()
+ other[0] = np.timedelta64(0, "ns")
+ expected = PeriodIndex([pi[0]] + ["NaT"] * 8, freq="19D")
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = obj + other
+ tm.assert_equal(result, expected)
+ result = other + obj
+ tm.assert_equal(result, expected)
+ result = obj - other
+ tm.assert_equal(result, expected)
+ with pytest.raises(TypeError, match=msg):
+ other - obj
+
+ # ---------------------------------------------------------------
+ # Unsorted
+
+ def test_parr_add_sub_index(self):
+ # Check that PeriodArray defers to Index on arithmetic ops
+ pi = period_range("2000-12-31", periods=3)
+ parr = pi.array
+
+ result = parr - pi
+ expected = pi - pi
+ tm.assert_index_equal(result, expected)
+
+ def test_parr_add_sub_object_array(self, performance_warning):
+ pi = period_range("2000-12-31", periods=3, freq="D")
+ parr = pi.array
+
+ other = np.array([Timedelta(days=1), pd.offsets.Day(2), 3])
+
+ with tm.assert_produces_warning(performance_warning):
+ result = parr + other
+
+ expected = PeriodIndex(
+ ["2001-01-01", "2001-01-03", "2001-01-05"], freq="D"
+ )._data.astype(object)
+ tm.assert_equal(result, expected)
+
+ with tm.assert_produces_warning(performance_warning):
+ result = parr - other
+
+ expected = PeriodIndex(["2000-12-30"] * 3, freq="D")._data.astype(object)
+ tm.assert_equal(result, expected)
+
+ def test_period_add_timestamp_raises(self, box_with_array):
+ # GH#17983
+ ts = Timestamp("2017")
+ per = Period("2017", freq="M")
+
+ arr = pd.Index([per], dtype="Period[M]")
+ arr = tm.box_expected(arr, box_with_array)
+
+ msg = "cannot add PeriodArray and Timestamp"
+ with pytest.raises(TypeError, match=msg):
+ arr + ts
+ with pytest.raises(TypeError, match=msg):
+ ts + arr
+
+ msg = "cannot add PeriodArray and DatetimeArray"
+ with pytest.raises(TypeError, match=msg):
+ arr + Series([ts])
+ with pytest.raises(TypeError, match=msg):
+ Series([ts]) + arr
+ with pytest.raises(TypeError, match=msg):
+ arr + pd.Index([ts])
+ with pytest.raises(TypeError, match=msg):
+ pd.Index([ts]) + arr
+
+ if box_with_array is pd.DataFrame:
+ msg = "cannot add PeriodArray and DatetimeArray"
+ else:
+ msg = r"unsupported operand type\(s\) for \+: 'Period' and 'DatetimeArray"
+ with pytest.raises(TypeError, match=msg):
+ arr + pd.DataFrame([ts])
+ if box_with_array is pd.DataFrame:
+ msg = "cannot add PeriodArray and DatetimeArray"
+ else:
+ msg = r"unsupported operand type\(s\) for \+: 'DatetimeArray' and 'Period'"
+ with pytest.raises(TypeError, match=msg):
+ pd.DataFrame([ts]) + arr
+
+
+class TestPeriodSeriesArithmetic:
+ def test_parr_add_timedeltalike_scalar(self, three_days, box_with_array):
+ # GH#13043
+ ser = Series(
+ [Period("2015-01-01", freq="D"), Period("2015-01-02", freq="D")],
+ name="xxx",
+ )
+ assert ser.dtype == "Period[D]"
+
+ expected = Series(
+ [Period("2015-01-04", freq="D"), Period("2015-01-05", freq="D")],
+ name="xxx",
+ )
+
+ obj = tm.box_expected(ser, box_with_array)
+ if box_with_array is pd.DataFrame:
+ assert (obj.dtypes == "Period[D]").all()
+
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = obj + three_days
+ tm.assert_equal(result, expected)
+
+ result = three_days + obj
+ tm.assert_equal(result, expected)
+
+ def test_ops_series_period(self):
+ # GH#13043
+ ser = Series(
+ [Period("2015-01-01", freq="D"), Period("2015-01-02", freq="D")],
+ name="xxx",
+ )
+ assert ser.dtype == "Period[D]"
+
+ per = Period("2015-01-10", freq="D")
+ off = per.freq
+ # dtype will be object because of original dtype
+ expected = Series([9 * off, 8 * off], name="xxx", dtype=object)
+ tm.assert_series_equal(per - ser, expected)
+ tm.assert_series_equal(ser - per, -1 * expected)
+
+ s2 = Series(
+ [Period("2015-01-05", freq="D"), Period("2015-01-04", freq="D")],
+ name="xxx",
+ )
+ assert s2.dtype == "Period[D]"
+
+ expected = Series([4 * off, 2 * off], name="xxx", dtype=object)
+ tm.assert_series_equal(s2 - ser, expected)
+ tm.assert_series_equal(ser - s2, -1 * expected)
+
+
+class TestPeriodIndexSeriesMethods:
+ """Test PeriodIndex and Period Series Ops consistency"""
+
+ def _check(self, values, func, expected):
+ idx = PeriodIndex(values)
+ result = func(idx)
+ tm.assert_equal(result, expected)
+
+ ser = Series(values)
+ result = func(ser)
+
+ exp = Series(expected, name=values.name)
+ tm.assert_series_equal(result, exp)
+
+ def test_pi_ops(self):
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "2011-03", "2011-04"], freq="M", name="idx"
+ )
+
+ expected = PeriodIndex(
+ ["2011-03", "2011-04", "2011-05", "2011-06"], freq="M", name="idx"
+ )
+
+ self._check(idx, lambda x: x + 2, expected)
+ self._check(idx, lambda x: 2 + x, expected)
+
+ self._check(idx + 2, lambda x: x - 2, idx)
+
+ result = idx - Period("2011-01", freq="M")
+ off = idx.freq
+ exp = pd.Index([0 * off, 1 * off, 2 * off, 3 * off], name="idx")
+ tm.assert_index_equal(result, exp)
+
+ result = Period("2011-01", freq="M") - idx
+ exp = pd.Index([0 * off, -1 * off, -2 * off, -3 * off], name="idx")
+ tm.assert_index_equal(result, exp)
+
+ @pytest.mark.parametrize("ng", ["str", 1.5])
+ @pytest.mark.parametrize(
+ "func",
+ [
+ lambda obj, ng: obj + ng,
+ lambda obj, ng: ng + obj,
+ lambda obj, ng: obj - ng,
+ lambda obj, ng: ng - obj,
+ lambda obj, ng: np.add(obj, ng),
+ lambda obj, ng: np.add(ng, obj),
+ lambda obj, ng: np.subtract(obj, ng),
+ lambda obj, ng: np.subtract(ng, obj),
+ ],
+ )
+ def test_parr_ops_errors(self, ng, func, box_with_array):
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "2011-03", "2011-04"], freq="M", name="idx"
+ )
+ obj = tm.box_expected(idx, box_with_array)
+ msg = "|".join(
+ [
+ r"unsupported operand type\(s\)",
+ "can only concatenate",
+ r"must be str",
+ "object to str implicitly",
+ ]
+ )
+
+ with pytest.raises(TypeError, match=msg):
+ func(obj, ng)
+
+ def test_pi_ops_nat(self):
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "NaT", "2011-04"], freq="M", name="idx"
+ )
+ expected = PeriodIndex(
+ ["2011-03", "2011-04", "NaT", "2011-06"], freq="M", name="idx"
+ )
+
+ self._check(idx, lambda x: x + 2, expected)
+ self._check(idx, lambda x: 2 + x, expected)
+ self._check(idx, lambda x: np.add(x, 2), expected)
+
+ self._check(idx + 2, lambda x: x - 2, idx)
+ self._check(idx + 2, lambda x: np.subtract(x, 2), idx)
+
+ # freq with mult
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "NaT", "2011-04"], freq="2M", name="idx"
+ )
+ expected = PeriodIndex(
+ ["2011-07", "2011-08", "NaT", "2011-10"], freq="2M", name="idx"
+ )
+
+ self._check(idx, lambda x: x + 3, expected)
+ self._check(idx, lambda x: 3 + x, expected)
+ self._check(idx, lambda x: np.add(x, 3), expected)
+
+ self._check(idx + 3, lambda x: x - 3, idx)
+ self._check(idx + 3, lambda x: np.subtract(x, 3), idx)
+
+ def test_pi_ops_array_int(self):
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "NaT", "2011-04"], freq="M", name="idx"
+ )
+ f = lambda x: x + np.array([1, 2, 3, 4])
+ exp = PeriodIndex(
+ ["2011-02", "2011-04", "NaT", "2011-08"], freq="M", name="idx"
+ )
+ self._check(idx, f, exp)
+
+ f = lambda x: np.add(x, np.array([4, -1, 1, 2]))
+ exp = PeriodIndex(
+ ["2011-05", "2011-01", "NaT", "2011-06"], freq="M", name="idx"
+ )
+ self._check(idx, f, exp)
+
+ f = lambda x: x - np.array([1, 2, 3, 4])
+ exp = PeriodIndex(
+ ["2010-12", "2010-12", "NaT", "2010-12"], freq="M", name="idx"
+ )
+ self._check(idx, f, exp)
+
+ f = lambda x: np.subtract(x, np.array([3, 2, 3, -2]))
+ exp = PeriodIndex(
+ ["2010-10", "2010-12", "NaT", "2011-06"], freq="M", name="idx"
+ )
+ self._check(idx, f, exp)
+
+ def test_pi_ops_offset(self):
+ idx = PeriodIndex(
+ ["2011-01-01", "2011-02-01", "2011-03-01", "2011-04-01"],
+ freq="D",
+ name="idx",
+ )
+ f = lambda x: x + pd.offsets.Day()
+ exp = PeriodIndex(
+ ["2011-01-02", "2011-02-02", "2011-03-02", "2011-04-02"],
+ freq="D",
+ name="idx",
+ )
+ self._check(idx, f, exp)
+
+ f = lambda x: x + pd.offsets.Day(2)
+ exp = PeriodIndex(
+ ["2011-01-03", "2011-02-03", "2011-03-03", "2011-04-03"],
+ freq="D",
+ name="idx",
+ )
+ self._check(idx, f, exp)
+
+ f = lambda x: x - pd.offsets.Day(2)
+ exp = PeriodIndex(
+ ["2010-12-30", "2011-01-30", "2011-02-27", "2011-03-30"],
+ freq="D",
+ name="idx",
+ )
+ self._check(idx, f, exp)
+
+ def test_pi_offset_errors(self):
+ idx = PeriodIndex(
+ ["2011-01-01", "2011-02-01", "2011-03-01", "2011-04-01"],
+ freq="D",
+ name="idx",
+ )
+ ser = Series(idx)
+
+ msg = (
+ "Cannot add/subtract timedelta-like from PeriodArray that is not "
+ "an integer multiple of the PeriodArray's freq"
+ )
+ for obj in [idx, ser]:
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ obj + pd.offsets.Hour(2)
+
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ pd.offsets.Hour(2) + obj
+
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ obj - pd.offsets.Hour(2)
+
+ def test_pi_sub_period(self):
+ # GH#13071
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "2011-03", "2011-04"], freq="M", name="idx"
+ )
+
+ result = idx - Period("2012-01", freq="M")
+ off = idx.freq
+ exp = pd.Index([-12 * off, -11 * off, -10 * off, -9 * off], name="idx")
+ tm.assert_index_equal(result, exp)
+
+ result = np.subtract(idx, Period("2012-01", freq="M"))
+ tm.assert_index_equal(result, exp)
+
+ result = Period("2012-01", freq="M") - idx
+ exp = pd.Index([12 * off, 11 * off, 10 * off, 9 * off], name="idx")
+ tm.assert_index_equal(result, exp)
+
+ result = np.subtract(Period("2012-01", freq="M"), idx)
+ tm.assert_index_equal(result, exp)
+
+ exp = TimedeltaIndex(
+ [np.nan, np.nan, np.nan, np.nan], name="idx", dtype="m8[ns]"
+ )
+ result = idx - Period("NaT", freq="M")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == exp.freq
+
+ result = Period("NaT", freq="M") - idx
+ tm.assert_index_equal(result, exp)
+ assert result.freq == exp.freq
+
+ def test_pi_sub_pdnat(self):
+ # GH#13071, GH#19389
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "NaT", "2011-04"], freq="M", name="idx"
+ )
+ exp = TimedeltaIndex([pd.NaT] * 4, name="idx", dtype="m8[ns]")
+ tm.assert_index_equal(pd.NaT - idx, exp)
+ tm.assert_index_equal(idx - pd.NaT, exp)
+
+ def test_pi_sub_period_nat(self):
+ # GH#13071
+ idx = PeriodIndex(
+ ["2011-01", "NaT", "2011-03", "2011-04"], freq="M", name="idx"
+ )
+
+ result = idx - Period("2012-01", freq="M")
+ off = idx.freq
+ exp = pd.Index([-12 * off, pd.NaT, -10 * off, -9 * off], name="idx")
+ tm.assert_index_equal(result, exp)
+
+ result = Period("2012-01", freq="M") - idx
+ exp = pd.Index([12 * off, pd.NaT, 10 * off, 9 * off], name="idx")
+ tm.assert_index_equal(result, exp)
+
+ exp = TimedeltaIndex(
+ [np.nan, np.nan, np.nan, np.nan], name="idx", dtype="m8[ns]"
+ )
+ tm.assert_index_equal(idx - Period("NaT", freq="M"), exp)
+ tm.assert_index_equal(Period("NaT", freq="M") - idx, exp)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_string.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_string.py
new file mode 100644
index 0000000000000000000000000000000000000000..46a3d1e8386eb29153700937bb82e43ddf23883d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_string.py
@@ -0,0 +1,472 @@
+import operator
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+from pandas.compat import HAS_PYARROW
+from pandas.errors import Pandas4Warning
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ NA,
+ ArrowDtype,
+ Series,
+ StringDtype,
+)
+import pandas._testing as tm
+from pandas.core.construction import extract_array
+
+
+def string_dtype_highest_priority(dtype1, dtype2):
+ if HAS_PYARROW:
+ DTYPE_HIERARCHY = [
+ StringDtype("python", na_value=np.nan),
+ StringDtype("pyarrow", na_value=np.nan),
+ StringDtype("python", na_value=NA),
+ StringDtype("pyarrow", na_value=NA),
+ ]
+ else:
+ DTYPE_HIERARCHY = [
+ StringDtype("python", na_value=np.nan),
+ StringDtype("python", na_value=NA),
+ ]
+
+ h1 = DTYPE_HIERARCHY.index(dtype1)
+ h2 = DTYPE_HIERARCHY.index(dtype2)
+ return DTYPE_HIERARCHY[max(h1, h2)]
+
+
+def test_eq_all_na():
+ pytest.importorskip("pyarrow")
+ a = pd.array([NA, NA], dtype=StringDtype("pyarrow"))
+ result = a == a
+ expected = pd.array([NA, NA], dtype="boolean[pyarrow]")
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_reversed_logical_ops(any_string_dtype):
+ # GH#60234
+ dtype = any_string_dtype
+ warn = None if dtype == object else Pandas4Warning
+ left = Series([True, False, False, True])
+ right = Series(["", "", "b", "c"], dtype=dtype)
+
+ msg = "operations between boolean dtype and"
+ with tm.assert_produces_warning(warn, match=msg):
+ result = left | right
+ expected = left | right.astype(bool)
+ tm.assert_series_equal(result, expected)
+
+ with tm.assert_produces_warning(warn, match=msg):
+ result = left & right
+ expected = left & right.astype(bool)
+ tm.assert_series_equal(result, expected)
+
+ with tm.assert_produces_warning(warn, match=msg):
+ result = left ^ right
+ expected = left ^ right.astype(bool)
+ tm.assert_series_equal(result, expected)
+
+
+def test_pathlib_path_division(any_string_dtype, request):
+ # GH#61940
+ if any_string_dtype == object:
+ mark = pytest.mark.xfail(
+ reason="with NA present we go through _masked_arith_op which "
+ "raises TypeError bc Path is not recognized by lib.is_scalar."
+ )
+ request.applymarker(mark)
+
+ item = Path("/Users/Irv/")
+ ser = Series(["A", "B", NA], dtype=any_string_dtype)
+
+ result = item / ser
+ expected = Series([item / "A", item / "B", ser.dtype.na_value], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ result = ser / item
+ expected = Series(["A" / item, "B" / item, ser.dtype.na_value], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+
+def test_mixed_object_comparison(any_string_dtype):
+ # GH#60228
+ dtype = any_string_dtype
+ ser = Series(["a", "b"], dtype=dtype)
+
+ mixed = Series([1, "b"], dtype=object)
+
+ result = ser == mixed
+ expected = Series([False, True], dtype=bool)
+ if dtype == object:
+ pass
+ elif dtype.storage == "python" and dtype.na_value is NA:
+ expected = expected.astype("boolean")
+ elif dtype.storage == "pyarrow" and dtype.na_value is NA:
+ expected = expected.astype("bool[pyarrow]")
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_pyarrow_numpy_string_invalid():
+ # GH#56008
+ pa = pytest.importorskip("pyarrow")
+ ser = Series([False, True])
+ ser2 = Series(["a", "b"], dtype=StringDtype(na_value=np.nan))
+ result = ser == ser2
+ expected_eq = Series(False, index=ser.index)
+ tm.assert_series_equal(result, expected_eq)
+
+ result = ser != ser2
+ expected_ne = Series(True, index=ser.index)
+ tm.assert_series_equal(result, expected_ne)
+
+ with pytest.raises(TypeError, match="Invalid comparison"):
+ ser > ser2
+
+ # GH#59505
+ ser3 = ser2.astype("string[pyarrow]")
+ result3_eq = ser3 == ser
+ tm.assert_series_equal(result3_eq, expected_eq.astype("bool[pyarrow]"))
+ result3_ne = ser3 != ser
+ tm.assert_series_equal(result3_ne, expected_ne.astype("bool[pyarrow]"))
+
+ with pytest.raises(TypeError, match="Invalid comparison"):
+ ser > ser3
+
+ ser4 = ser2.astype(ArrowDtype(pa.string()))
+ result4_eq = ser4 == ser
+ tm.assert_series_equal(result4_eq, expected_eq.astype("bool[pyarrow]"))
+ result4_ne = ser4 != ser
+ tm.assert_series_equal(result4_ne, expected_ne.astype("bool[pyarrow]"))
+
+ with pytest.raises(TypeError, match="Invalid comparison"):
+ ser > ser4
+
+
+def test_mul_bool_invalid(any_string_dtype):
+ # GH#62595
+ dtype = any_string_dtype
+ ser = Series(["a", "b", "c"], dtype=dtype)
+
+ if dtype == object:
+ pytest.skip("This is not expect to raise")
+ elif dtype.storage == "python":
+ msg = "Cannot multiply StringArray by bools. Explicitly cast to integers"
+ else:
+ msg = "Can only string multiply by an integer"
+
+ with pytest.raises(TypeError, match=msg):
+ False * ser
+ with pytest.raises(TypeError, match=msg):
+ ser * True
+ with pytest.raises(TypeError, match=msg):
+ ser * np.array([True, False, True], dtype=bool)
+ with pytest.raises(TypeError, match=msg):
+ np.array([True, False, True], dtype=bool) * ser
+
+
+def test_add(any_string_dtype, request):
+ dtype = any_string_dtype
+ if dtype == object:
+ mark = pytest.mark.xfail(
+ reason="Need to update expected for numpy object dtype"
+ )
+ request.applymarker(mark)
+
+ a = Series(["a", "b", "c", None, None], dtype=dtype)
+ b = Series(["x", "y", None, "z", None], dtype=dtype)
+
+ result = a + b
+ expected = Series(["ax", "by", None, None, None], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ result = a.add(b)
+ tm.assert_series_equal(result, expected)
+
+ result = a.radd(b)
+ expected = Series(["xa", "yb", None, None, None], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ result = a.add(b, fill_value="-")
+ expected = Series(["ax", "by", "c-", "-z", None], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_add_2d(any_string_dtype, request):
+ dtype = any_string_dtype
+
+ if dtype == object or dtype.storage == "pyarrow":
+ reason = "Failed: DID NOT RAISE "
+ mark = pytest.mark.xfail(raises=None, reason=reason)
+ request.applymarker(mark)
+
+ a = pd.array(["a", "b", "c"], dtype=dtype)
+ b = np.array([["a", "b", "c"]], dtype=object)
+ with pytest.raises(ValueError, match="3 != 1"):
+ a + b
+
+ s = Series(a)
+ with pytest.raises(ValueError, match="3 != 1"):
+ s + b
+
+
+def test_add_sequence(any_string_dtype, request, using_infer_string):
+ dtype = any_string_dtype
+ if (
+ dtype != object
+ and dtype.storage == "python"
+ and dtype.na_value is np.nan
+ and HAS_PYARROW
+ and using_infer_string
+ ):
+ mark = pytest.mark.xfail(
+ reason="As of GH#62522, the list gets wrapped with sanitize_array, "
+ "which casts to a higher-priority StringArray, so we get "
+ "NotImplemented."
+ )
+ request.applymarker(mark)
+ if dtype == np.dtype(object) and using_infer_string:
+ mark = pytest.mark.xfail(reason="Cannot broadcast list")
+ request.applymarker(mark)
+
+ a = pd.array(["a", "b", None, None], dtype=dtype)
+ other = ["x", None, "y", None]
+
+ result = a + other
+ expected = pd.array(["ax", None, None, None], dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+ result = other + a
+ expected = pd.array(["xa", None, None, None], dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_mul(any_string_dtype):
+ dtype = any_string_dtype
+ a = pd.array(["a", "b", None], dtype=dtype)
+ result = a * 2
+ expected = pd.array(["aa", "bb", None], dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+ result = 2 * a
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_add_strings(any_string_dtype, request):
+ dtype = any_string_dtype
+ if dtype != np.dtype(object):
+ mark = pytest.mark.xfail(reason="GH-28527")
+ request.applymarker(mark)
+ arr = pd.array(["a", "b", "c", "d"], dtype=dtype)
+ df = pd.DataFrame([["t", "y", "v", "w"]], dtype=object)
+ assert arr.__add__(df) is NotImplemented
+
+ result = arr + df
+ expected = pd.DataFrame([["at", "by", "cv", "dw"]]).astype(dtype)
+ tm.assert_frame_equal(result, expected)
+
+ result = df + arr
+ expected = pd.DataFrame([["ta", "yb", "vc", "wd"]]).astype(dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.xfail(reason="GH-28527")
+def test_add_frame(dtype):
+ arr = pd.array(["a", "b", np.nan, np.nan], dtype=dtype)
+ df = pd.DataFrame([["x", np.nan, "y", np.nan]])
+
+ assert arr.__add__(df) is NotImplemented
+
+ result = arr + df
+ expected = pd.DataFrame([["ax", np.nan, np.nan, np.nan]]).astype(dtype)
+ tm.assert_frame_equal(result, expected)
+
+ result = df + arr
+ expected = pd.DataFrame([["xa", np.nan, np.nan, np.nan]]).astype(dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_comparison_methods_scalar(comparison_op, any_string_dtype):
+ dtype = any_string_dtype
+ op_name = f"__{comparison_op.__name__}__"
+ a = pd.array(["a", None, "c"], dtype=dtype)
+ other = "a"
+ result = getattr(a, op_name)(other)
+ if dtype == object or dtype.na_value is np.nan:
+ expected = np.array([getattr(item, op_name)(other) for item in a])
+ if comparison_op == operator.ne:
+ expected[1] = True
+ else:
+ expected[1] = False
+ result = extract_array(result, extract_numpy=True)
+ tm.assert_numpy_array_equal(result, expected.astype(np.bool_))
+ else:
+ expected_dtype = "boolean[pyarrow]" if dtype.storage == "pyarrow" else "boolean"
+ expected = np.array([getattr(item, op_name)(other) for item in a], dtype=object)
+ expected = pd.array(expected, dtype=expected_dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_comparison_methods_scalar_pd_na(comparison_op, any_string_dtype):
+ dtype = any_string_dtype
+ op_name = f"__{comparison_op.__name__}__"
+ a = pd.array(["a", None, "c"], dtype=dtype)
+ result = getattr(a, op_name)(NA)
+
+ if dtype == np.dtype(object) or dtype.na_value is np.nan:
+ if operator.ne == comparison_op:
+ expected = np.array([True, True, True])
+ else:
+ expected = np.array([False, False, False])
+ result = extract_array(result, extract_numpy=True)
+ tm.assert_numpy_array_equal(result, expected)
+ else:
+ expected_dtype = "boolean[pyarrow]" if dtype.storage == "pyarrow" else "boolean"
+ expected = pd.array([None, None, None], dtype=expected_dtype)
+ tm.assert_extension_array_equal(result, expected)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_comparison_methods_scalar_not_string(comparison_op, any_string_dtype):
+ op_name = f"__{comparison_op.__name__}__"
+ dtype = any_string_dtype
+
+ a = pd.array(["a", None, "c"], dtype=dtype)
+ other = 42
+
+ if op_name not in ["__eq__", "__ne__"]:
+ with pytest.raises(TypeError, match="Invalid comparison|not supported between"):
+ getattr(a, op_name)(other)
+
+ return
+
+ result = getattr(a, op_name)(other)
+ result = extract_array(result, extract_numpy=True)
+
+ if dtype == np.dtype(object) or dtype.na_value is np.nan:
+ expected_data = {
+ "__eq__": [False, False, False],
+ "__ne__": [True, True, True],
+ }[op_name]
+ expected = np.array(expected_data)
+ tm.assert_numpy_array_equal(result, expected)
+ else:
+ expected_data = {"__eq__": [False, None, False], "__ne__": [True, None, True]}[
+ op_name
+ ]
+ expected_dtype = "boolean[pyarrow]" if dtype.storage == "pyarrow" else "boolean"
+ expected = pd.array(expected_data, dtype=expected_dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_comparison_methods_array(comparison_op, any_string_dtype, any_string_dtype2):
+ op_name = f"__{comparison_op.__name__}__"
+ dtype = any_string_dtype
+ dtype2 = any_string_dtype2
+
+ a = pd.array(["a", None, "c"], dtype=dtype)
+ other = pd.array([None, None, "c"], dtype=dtype2)
+ result = comparison_op(a, other)
+ result = extract_array(result, extract_numpy=True)
+
+ # ensure operation is commutative
+ result2 = comparison_op(other, a)
+ result2 = extract_array(result2, extract_numpy=True)
+ tm.assert_equal(result, result2)
+
+ if (dtype == object or dtype.na_value is np.nan) and (
+ dtype2 == object or dtype2.na_value is np.nan
+ ):
+ if operator.ne == comparison_op:
+ expected = np.array([True, True, False])
+ else:
+ expected = np.array([False, False, False])
+ expected[-1] = getattr(other[-1], op_name)(a[-1])
+ result = extract_array(result, extract_numpy=True)
+ tm.assert_numpy_array_equal(result, expected)
+
+ else:
+ if dtype == object:
+ max_dtype = dtype2
+ elif dtype2 == object:
+ max_dtype = dtype
+ else:
+ max_dtype = string_dtype_highest_priority(dtype, dtype2)
+ if max_dtype.storage == "python":
+ expected_dtype = "boolean"
+ else:
+ expected_dtype = "bool[pyarrow]"
+
+ expected = np.full(len(a), fill_value=None, dtype="object")
+ expected[-1] = getattr(other[-1], op_name)(a[-1])
+ expected = pd.array(expected, dtype=expected_dtype)
+ tm.assert_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_comparison_methods_array_arrow_extension(comparison_op, any_string_dtype):
+ # Test pd.ArrowDtype(pa.string()) against other string arrays
+ import pyarrow as pa
+
+ dtype2 = any_string_dtype
+
+ op_name = f"__{comparison_op.__name__}__"
+ dtype = ArrowDtype(pa.string())
+ a = pd.array(["a", None, "c"], dtype=dtype)
+ other = pd.array([None, None, "c"], dtype=dtype2)
+ result = comparison_op(a, other)
+
+ # ensure operation is commutative
+ result2 = comparison_op(other, a)
+ tm.assert_equal(result, result2)
+
+ expected = pd.array([None, None, True], dtype="bool[pyarrow]")
+ expected[-1] = getattr(other[-1], op_name)(a[-1])
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("box", [pd.array, pd.Index, Series])
+def test_comparison_methods_list(comparison_op, any_string_dtype, box, request):
+ dtype = any_string_dtype
+
+ if box is pd.array and dtype != object and dtype.na_value is np.nan:
+ mark = pytest.mark.xfail(
+ reason="After wrapping list, op returns NotImplemented, see GH#62522"
+ )
+ request.applymarker(mark)
+
+ op_name = f"__{comparison_op.__name__}__"
+
+ a = box(pd.array(["a", None, "c"], dtype=dtype))
+ item = "c"
+ other = [None, None, "c"]
+ result = comparison_op(a, other)
+
+ # ensure operation is commutative
+ result2 = comparison_op(other, a)
+ tm.assert_equal(result, result2)
+
+ if dtype == np.dtype(object) or dtype.na_value is np.nan:
+ if operator.ne == comparison_op:
+ expected = np.array([True, True, False])
+ else:
+ expected = np.array([False, False, False])
+ expected[-1] = getattr(item, op_name)(item)
+ if box is not pd.Index:
+ # if GH#62766 is addressed this check can be removed
+ expected = box(expected, dtype=expected.dtype)
+ tm.assert_equal(result, expected)
+
+ else:
+ expected_dtype = "boolean[pyarrow]" if dtype.storage == "pyarrow" else "boolean"
+ expected = np.full(len(a), fill_value=None, dtype="object")
+ expected[-1] = getattr(item, op_name)(item)
+ expected = pd.array(expected, dtype=expected_dtype)
+ expected = extract_array(expected, extract_numpy=True)
+ if box is not pd.Index:
+ # if GH#62766 is addressed this check can be removed
+ expected = tm.box_expected(expected, box)
+ tm.assert_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_timedelta64.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_timedelta64.py
new file mode 100644
index 0000000000000000000000000000000000000000..89a9148bed5575f24b99c0446f85fe4826062518
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arithmetic/test_timedelta64.py
@@ -0,0 +1,2331 @@
+# Arithmetic tests for DataFrame/Series/Index/Array classes that should
+# behave identically.
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import timezones
+from pandas.compat import WASM
+from pandas.errors import OutOfBoundsDatetime
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ NaT,
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ Timestamp,
+ offsets,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import NumpyExtensionArray
+from pandas.tests.arithmetic.common import (
+ assert_invalid_addsub_type,
+ assert_invalid_comparison,
+ get_upcast_box,
+)
+
+
+def assert_dtype(obj, expected_dtype):
+ """
+ Helper to check the dtype for a Series, Index, or single-column DataFrame.
+ """
+ dtype = tm.get_dtype(obj)
+
+ assert dtype == expected_dtype
+
+
+def get_expected_name(box, names):
+ if box is DataFrame:
+ # Since we are operating with a DataFrame and a non-DataFrame,
+ # the non-DataFrame is cast to Series and its name ignored.
+ exname = names[0]
+ elif box in [tm.to_array, pd.array]:
+ exname = names[1]
+ else:
+ exname = names[2]
+ return exname
+
+
+# ------------------------------------------------------------------
+# Timedelta64[ns] dtype Comparisons
+
+
+class TestTimedelta64ArrayLikeComparisons:
+ # Comparison tests for timedelta64[ns] vectors fully parametrized over
+ # DataFrame/Series/TimedeltaIndex/TimedeltaArray. Ideally all comparison
+ # tests will eventually end up here.
+
+ def test_compare_timedelta64_zerodim(self, box_with_array):
+ # GH#26689 should unbox when comparing with zerodim array
+ box = box_with_array
+ xbox = box_with_array if box_with_array not in [Index, pd.array] else np.ndarray
+
+ tdi = timedelta_range("2h", periods=4)
+ other = np.array(tdi.to_numpy()[0])
+
+ tdi = tm.box_expected(tdi, box)
+ res = tdi <= other
+ expected = np.array([True, False, False, False])
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(res, expected)
+
+ @pytest.mark.parametrize(
+ "td_scalar",
+ [
+ timedelta(days=1),
+ Timedelta(days=1),
+ Timedelta(days=1).to_timedelta64(),
+ offsets.Hour(24),
+ ],
+ )
+ def test_compare_timedeltalike_scalar(self, box_with_array, td_scalar):
+ # regression test for GH#5963
+ box = box_with_array
+ xbox = box if box not in [Index, pd.array] else np.ndarray
+
+ ser = Series([timedelta(days=1), timedelta(days=2)])
+ ser = tm.box_expected(ser, box)
+ actual = ser > td_scalar
+ expected = Series([False, True])
+ expected = tm.box_expected(expected, xbox)
+ tm.assert_equal(actual, expected)
+
+ @pytest.mark.parametrize(
+ "invalid",
+ [
+ 345600000000000,
+ "a",
+ Timestamp("2021-01-01"),
+ Timestamp("2021-01-01").now("UTC"),
+ Timestamp("2021-01-01").now().to_datetime64(),
+ Timestamp("2021-01-01").now().to_pydatetime(),
+ Timestamp("2021-01-01").date(),
+ np.array(4), # zero-dim mismatched dtype
+ ],
+ )
+ def test_td64_comparisons_invalid(self, box_with_array, invalid):
+ # GH#13624 for str
+ box = box_with_array
+
+ rng = timedelta_range("1 days", periods=10)
+ obj = tm.box_expected(rng, box)
+
+ assert_invalid_comparison(obj, invalid, box)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ list(range(10)),
+ np.arange(10),
+ np.arange(10).astype(np.float32),
+ np.arange(10).astype(object),
+ pd.date_range("1970-01-01", periods=10, tz="UTC").array,
+ np.array(pd.date_range("1970-01-01", periods=10)),
+ list(pd.date_range("1970-01-01", periods=10)),
+ pd.date_range("1970-01-01", periods=10).astype(object),
+ pd.period_range("1971-01-01", freq="D", periods=10).array,
+ pd.period_range("1971-01-01", freq="D", periods=10).astype(object),
+ ],
+ )
+ def test_td64arr_cmp_arraylike_invalid(self, other, box_with_array):
+ # We don't parametrize this over box_with_array because listlike
+ # other plays poorly with assert_invalid_comparison reversed checks
+
+ rng = timedelta_range("1 days", periods=10)._data
+ rng = tm.box_expected(rng, box_with_array)
+ assert_invalid_comparison(rng, other, box_with_array)
+
+ def test_td64arr_cmp_mixed_invalid(self):
+ rng = timedelta_range("1 days", periods=5)._data
+ other = np.array([0, 1, 2, rng[3], Timestamp("2021-01-01")])
+
+ result = rng == other
+ expected = np.array([False, False, False, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = rng != other
+ tm.assert_numpy_array_equal(result, ~expected)
+
+ msg = "Invalid comparison between|Cannot compare type|not supported between"
+ with pytest.raises(TypeError, match=msg):
+ rng < other
+ with pytest.raises(TypeError, match=msg):
+ rng > other
+ with pytest.raises(TypeError, match=msg):
+ rng <= other
+ with pytest.raises(TypeError, match=msg):
+ rng >= other
+
+
+class TestTimedelta64ArrayComparisons:
+ # TODO: All of these need to be parametrized over box
+
+ @pytest.mark.parametrize("dtype", [None, object])
+ def test_comp_nat(self, dtype):
+ left = TimedeltaIndex([Timedelta("1 days"), NaT, Timedelta("3 days")])
+ right = TimedeltaIndex([NaT, NaT, Timedelta("3 days")])
+
+ lhs, rhs = left, right
+ if dtype is object:
+ lhs, rhs = left.astype(object), right.astype(object)
+
+ result = rhs == lhs
+ expected = np.array([False, False, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = rhs != lhs
+ expected = np.array([True, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ expected = np.array([False, False, False])
+ tm.assert_numpy_array_equal(lhs == NaT, expected)
+ tm.assert_numpy_array_equal(NaT == rhs, expected)
+
+ expected = np.array([True, True, True])
+ tm.assert_numpy_array_equal(lhs != NaT, expected)
+ tm.assert_numpy_array_equal(NaT != lhs, expected)
+
+ expected = np.array([False, False, False])
+ tm.assert_numpy_array_equal(lhs < NaT, expected)
+ tm.assert_numpy_array_equal(NaT > lhs, expected)
+
+ @pytest.mark.parametrize(
+ "idx2",
+ [
+ TimedeltaIndex(
+ ["2 day", "2 day", NaT, NaT, "1 day 00:00:02", "5 days 00:00:03"]
+ ),
+ np.array(
+ [
+ np.timedelta64(2, "D"),
+ np.timedelta64(2, "D"),
+ np.timedelta64("nat"),
+ np.timedelta64("nat"),
+ np.timedelta64(1, "D") + np.timedelta64(2, "s"),
+ np.timedelta64(5, "D") + np.timedelta64(3, "s"),
+ ]
+ ),
+ ],
+ )
+ def test_comparisons_nat(self, idx2):
+ idx1 = TimedeltaIndex(
+ [
+ "1 day",
+ NaT,
+ "1 day 00:00:01",
+ NaT,
+ "1 day 00:00:01",
+ "5 day 00:00:03",
+ ]
+ )
+ # Check pd.NaT is handles as the same as np.nan
+ result = idx1 < idx2
+ expected = np.array([True, False, False, False, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx2 > idx1
+ expected = np.array([True, False, False, False, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 <= idx2
+ expected = np.array([True, False, False, False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx2 >= idx1
+ expected = np.array([True, False, False, False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 == idx2
+ expected = np.array([False, False, False, False, False, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1 != idx2
+ expected = np.array([True, True, True, True, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ # TODO: better name
+ def test_comparisons_coverage(self):
+ rng = timedelta_range("1 days", periods=10)
+
+ result = rng < rng[3]
+ expected = np.array([True, True, True] + [False] * 7)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = rng == list(rng)
+ exp = rng == rng
+ tm.assert_numpy_array_equal(result, exp)
+
+
+# ------------------------------------------------------------------
+# Timedelta64[ns] dtype Arithmetic Operations
+
+
+class TestTimedelta64ArithmeticUnsorted:
+ # Tests moved from type-specific test files but not
+ # yet sorted/parametrized/de-duplicated
+
+ def test_td64_op_with_list(self, box_with_array):
+ # GH#62353
+ box = box_with_array
+
+ left = TimedeltaIndex(["2D", "4D"])
+ left = tm.box_expected(left, box)
+
+ right = [Timestamp("2016-01-01"), Timestamp("2016-02-01")]
+
+ result = left + right
+ expected = DatetimeIndex(["2016-01-03", "2016-02-05"], dtype="M8[us]")
+ expected = tm.box_expected(expected, box)
+ tm.assert_equal(result, expected)
+
+ result2 = right + left
+ tm.assert_equal(result2, expected)
+
+ def test_ufunc_coercions(self):
+ # normal ops are also tested in tseries/test_timedeltas.py
+ idx = TimedeltaIndex(["2h", "4h", "6h", "8h", "10h"], freq="2h", name="x")
+
+ for result in [idx * 2, np.multiply(idx, 2)]:
+ assert isinstance(result, TimedeltaIndex)
+ exp = TimedeltaIndex(["4h", "8h", "12h", "16h", "20h"], freq="4h", name="x")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "4h"
+
+ for result in [idx / 2, np.divide(idx, 2)]:
+ assert isinstance(result, TimedeltaIndex)
+ exp = TimedeltaIndex(["1h", "2h", "3h", "4h", "5h"], freq="h", name="x")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "h"
+
+ for result in [-idx, np.negative(idx)]:
+ assert isinstance(result, TimedeltaIndex)
+ exp = TimedeltaIndex(
+ ["-2h", "-4h", "-6h", "-8h", "-10h"], freq="-2h", name="x"
+ )
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "-2h"
+
+ idx = TimedeltaIndex(["-2h", "-1h", "0h", "1h", "2h"], freq="h", name="x")
+ for result in [abs(idx), np.absolute(idx)]:
+ assert isinstance(result, TimedeltaIndex)
+ exp = TimedeltaIndex(["2h", "1h", "0h", "1h", "2h"], freq=None, name="x")
+ tm.assert_index_equal(result, exp)
+ assert result.freq is None
+
+ def test_subtraction_ops(self):
+ # with datetimes/timedelta and tdi/dti
+ tdi = TimedeltaIndex(["1 days", NaT, "2 days"], name="foo")
+ dti = pd.date_range("20130101", periods=3, name="bar")
+ td = Timedelta("1 days")
+ dt = Timestamp("20130101")
+
+ msg = "cannot subtract a datelike from a TimedeltaArray"
+ with pytest.raises(TypeError, match=msg):
+ tdi - dt
+ with pytest.raises(TypeError, match=msg):
+ tdi - dti
+
+ msg = r"unsupported operand type\(s\) for -"
+ with pytest.raises(TypeError, match=msg):
+ td - dt
+
+ msg = "cannot subtract DatetimeArray from Timedelta"
+ with pytest.raises(TypeError, match=msg):
+ td - dti
+
+ result = dt - dti
+ expected = TimedeltaIndex(["0 days", "-1 days", "-2 days"], name="bar")
+ tm.assert_index_equal(result, expected)
+
+ result = dti - dt
+ expected = TimedeltaIndex(["0 days", "1 days", "2 days"], name="bar")
+ tm.assert_index_equal(result, expected)
+
+ result = tdi - td
+ expected = TimedeltaIndex(["0 days", NaT, "1 days"], name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = td - tdi
+ expected = TimedeltaIndex(["0 days", NaT, "-1 days"], name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = dti - td
+ expected = DatetimeIndex(
+ ["20121231", "20130101", "20130102"], dtype="M8[us]", freq="D", name="bar"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = dt - tdi
+ expected = DatetimeIndex(
+ ["20121231", NaT, "20121230"], dtype="M8[us]", name="foo"
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_subtraction_ops_with_tz(self, box_with_array):
+ # check that dt/dti subtraction ops with tz are validated
+ dti = pd.date_range("20130101", periods=3)
+ dti = tm.box_expected(dti, box_with_array)
+ ts = Timestamp("20130101")
+ dt = ts.to_pydatetime()
+ dti_tz = pd.date_range("20130101", periods=3).tz_localize("US/Eastern")
+ dti_tz = tm.box_expected(dti_tz, box_with_array)
+ ts_tz = Timestamp("20130101").tz_localize("US/Eastern")
+ ts_tz2 = Timestamp("20130101").tz_localize("CET")
+ dt_tz = ts_tz.to_pydatetime()
+ td = Timedelta("1 days")
+
+ def _check(result, expected):
+ assert result == expected
+ assert isinstance(result, Timedelta)
+
+ # scalars
+ result = ts - ts
+ expected = Timedelta("0 days")
+ _check(result, expected)
+
+ result = dt_tz - ts_tz
+ expected = Timedelta("0 days")
+ _check(result, expected)
+
+ result = ts_tz - dt_tz
+ expected = Timedelta("0 days")
+ _check(result, expected)
+
+ # tz mismatches
+ msg = "Cannot subtract tz-naive and tz-aware datetime-like objects."
+ with pytest.raises(TypeError, match=msg):
+ dt_tz - ts
+ msg = "can't subtract offset-naive and offset-aware datetimes"
+ with pytest.raises(TypeError, match=msg):
+ dt_tz - dt
+ msg = "can't subtract offset-naive and offset-aware datetimes"
+ with pytest.raises(TypeError, match=msg):
+ dt - dt_tz
+ msg = "Cannot subtract tz-naive and tz-aware datetime-like objects."
+ with pytest.raises(TypeError, match=msg):
+ ts - dt_tz
+ with pytest.raises(TypeError, match=msg):
+ ts_tz2 - ts
+ with pytest.raises(TypeError, match=msg):
+ ts_tz2 - dt
+
+ msg = "Cannot subtract tz-naive and tz-aware"
+ # with dti
+ with pytest.raises(TypeError, match=msg):
+ dti - ts_tz
+ with pytest.raises(TypeError, match=msg):
+ dti_tz - ts
+
+ result = dti_tz - dt_tz
+ expected = TimedeltaIndex(["0 days", "1 days", "2 days"])
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ result = dt_tz - dti_tz
+ expected = TimedeltaIndex(["0 days", "-1 days", "-2 days"])
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ result = dti_tz - ts_tz
+ expected = TimedeltaIndex(["0 days", "1 days", "2 days"])
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ result = ts_tz - dti_tz
+ expected = TimedeltaIndex(["0 days", "-1 days", "-2 days"])
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ result = td - td
+ expected = Timedelta("0 days")
+ _check(result, expected)
+
+ result = dti_tz - td
+ expected = DatetimeIndex(["20121231", "20130101", "20130102"], tz="US/Eastern")
+ expected = tm.box_expected(expected, box_with_array)
+ tm.assert_equal(result, expected)
+
+ def test_dti_tdi_numeric_ops(self):
+ # These are normally union/diff set-like ops
+ tdi = TimedeltaIndex(["1 days", NaT, "2 days"], name="foo")
+ dti = pd.date_range("20130101", periods=3, name="bar")
+
+ result = tdi - tdi
+ expected = TimedeltaIndex(["0 days", NaT, "0 days"], name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = tdi + tdi
+ expected = TimedeltaIndex(["2 days", NaT, "4 days"], name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = dti - tdi # name will be reset
+ expected = DatetimeIndex(["20121231", NaT, "20130101"], dtype="M8[us]")
+ tm.assert_index_equal(result, expected)
+
+ def test_addition_ops(self):
+ # with datetimes/timedelta and tdi/dti
+ tdi = TimedeltaIndex(["1 days", NaT, "2 days"], name="foo")
+ dti = pd.date_range("20130101", periods=3, name="bar")
+ td = Timedelta("1 days")
+ dt = Timestamp("20130101")
+
+ result = tdi + dt
+ expected = DatetimeIndex(
+ ["20130102", NaT, "20130103"], dtype="M8[us]", name="foo"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = dt + tdi
+ expected = DatetimeIndex(
+ ["20130102", NaT, "20130103"], dtype="M8[us]", name="foo"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = td + tdi
+ expected = TimedeltaIndex(["2 days", NaT, "3 days"], name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = tdi + td
+ expected = TimedeltaIndex(["2 days", NaT, "3 days"], name="foo")
+ tm.assert_index_equal(result, expected)
+
+ # unequal length
+ msg = "cannot add indices of unequal length"
+ with pytest.raises(ValueError, match=msg):
+ tdi + dti[0:1]
+ with pytest.raises(ValueError, match=msg):
+ tdi[0:1] + dti
+
+ # random indexes
+ msg = "Addition/subtraction of integers and integer-arrays"
+ with pytest.raises(TypeError, match=msg):
+ tdi + Index([1, 2, 3], dtype=np.int64)
+
+ # this is a union!
+ # FIXME: don't leave commented-out
+ # pytest.raises(TypeError, lambda : Index([1,2,3]) + tdi)
+
+ result = tdi + dti # name will be reset
+ expected = DatetimeIndex(["20130102", NaT, "20130105"], dtype="M8[us]")
+ tm.assert_index_equal(result, expected)
+
+ result = dti + tdi # name will be reset
+ expected = DatetimeIndex(["20130102", NaT, "20130105"], dtype="M8[us]")
+ tm.assert_index_equal(result, expected)
+
+ result = dt + td
+ expected = Timestamp("20130102")
+ assert result == expected
+
+ result = td + dt
+ expected = Timestamp("20130102")
+ assert result == expected
+
+ # TODO: Needs more informative name, probably split up into
+ # more targeted tests
+ @pytest.mark.parametrize("freq", ["D", "B"])
+ def test_timedelta(self, freq):
+ index = pd.date_range("1/1/2000", periods=50, freq=freq, unit="ns")
+
+ shifted = index + timedelta(1)
+ back = shifted + timedelta(-1)
+ back = back._with_freq("infer")
+ tm.assert_index_equal(index, back)
+
+ if freq == "D":
+ expected = pd.tseries.offsets.Day(1)
+ assert index.freq == expected
+ assert shifted.freq == expected
+ assert back.freq == expected
+ else: # freq == 'B'
+ assert index.freq == pd.tseries.offsets.BusinessDay(1)
+ assert shifted.freq is None
+ assert back.freq == pd.tseries.offsets.BusinessDay(1)
+
+ result = index - timedelta(1)
+ expected = index + timedelta(-1)
+ tm.assert_index_equal(result, expected)
+
+ def test_timedelta_tick_arithmetic(self):
+ # GH#4134, buggy with timedeltas
+ rng = pd.date_range("2013", "2014")
+ s = Series(rng)
+ result1 = rng - offsets.Hour(1)
+ result2 = DatetimeIndex(s - np.timedelta64(100000000))
+ result3 = rng - np.timedelta64(100000000)
+ result4 = DatetimeIndex(s - offsets.Hour(1))
+
+ assert result1.freq == rng.freq
+ result1 = result1._with_freq(None)
+ tm.assert_index_equal(result1, result4)
+
+ assert result3.freq == rng.freq
+ result3 = result3._with_freq(None)
+ tm.assert_index_equal(result2, result3)
+
+ def test_tda_add_sub_index(self):
+ # Check that TimedeltaArray defers to Index on arithmetic ops
+ tdi = TimedeltaIndex(["1 days", NaT, "2 days"])
+ tda = tdi.array
+
+ dti = pd.date_range("1999-12-31", periods=3, freq="D")
+
+ result = tda + dti
+ expected = tdi + dti
+ tm.assert_index_equal(result, expected)
+
+ result = tda + tdi
+ expected = tdi + tdi
+ tm.assert_index_equal(result, expected)
+
+ result = tda - tdi
+ expected = tdi - tdi
+ tm.assert_index_equal(result, expected)
+
+ def test_tda_add_dt64_object_array(
+ self, performance_warning, box_with_array, tz_naive_fixture
+ ):
+ # Result should be cast back to DatetimeArray
+ box = box_with_array
+
+ dti = pd.date_range("2016-01-01", periods=3, tz=tz_naive_fixture)
+ dti = dti._with_freq(None)
+ tdi = dti - dti
+
+ obj = tm.box_expected(tdi, box)
+ other = tm.box_expected(dti, box)
+
+ with tm.assert_produces_warning(performance_warning):
+ result = obj + other.astype(object)
+ tm.assert_equal(result, other.astype(object))
+
+ # -------------------------------------------------------------
+ # Binary operations TimedeltaIndex and timedelta-like
+
+ def test_tdi_iadd_timedeltalike(self, two_hours, box_with_array):
+ # only test adding/sub offsets as + is now numeric
+ rng = timedelta_range("1 days", "10 days")
+ expected = timedelta_range("1 days 02:00:00", "10 days 02:00:00", freq="D")
+ if (
+ isinstance(two_hours, Timedelta)
+ and two_hours.unit == "ns"
+ and box_with_array is not pd.array
+ ):
+ # The EA op has to be _actually_ inplace so does not cast to a
+ # new dtype. For the others, the op can assign a new array
+ # and get the dtype that normally results from `rng + two_hours`
+ expected = expected.as_unit("ns")
+
+ rng = tm.box_expected(rng, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ orig_rng = rng
+ rng += two_hours
+ tm.assert_equal(rng, expected)
+ if box_with_array is not Index:
+ # Check that operation is actually inplace
+ tm.assert_equal(orig_rng, expected)
+
+ def test_tdi_isub_timedeltalike(self, two_hours, box_with_array):
+ # only test adding/sub offsets as - is now numeric
+ rng = timedelta_range("1 days", "10 days")
+ expected = timedelta_range("0 days 22:00:00", "9 days 22:00:00")
+ if (
+ isinstance(two_hours, Timedelta)
+ and two_hours.unit == "ns"
+ and box_with_array is not pd.array
+ ):
+ # The EA op has to be _actually_ inplace so does not cast to a
+ # new dtype. For the others, the op can assign a new array
+ # and get the dtype that normally results from `rng - two_hours`
+ expected = expected.as_unit("ns")
+
+ rng = tm.box_expected(rng, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ orig_rng = rng
+ rng -= two_hours
+ tm.assert_equal(rng, expected)
+ if box_with_array is not Index:
+ # Check that operation is actually inplace
+ tm.assert_equal(orig_rng, expected)
+
+ # -------------------------------------------------------------
+
+ def test_tdi_ops_attributes(self):
+ rng = timedelta_range("2 days", periods=5, freq="2D", name="x")
+
+ result = rng + 1 * rng.freq
+ exp = timedelta_range("4 days", periods=5, freq="2D", name="x")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "2D"
+
+ result = rng - 2 * rng.freq
+ exp = timedelta_range("-2 days", periods=5, freq="2D", name="x")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "2D"
+
+ result = rng * 2
+ exp = timedelta_range("4 days", periods=5, freq="4D", name="x")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "4D"
+
+ result = rng / 2
+ exp = timedelta_range("1 days", periods=5, freq="D", name="x")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "D"
+
+ result = -rng
+ exp = timedelta_range("-2 days", periods=5, freq="-2D", name="x")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == "-2D"
+
+ rng = timedelta_range("-2 days", periods=5, freq="D", name="x")
+
+ result = abs(rng)
+ exp = TimedeltaIndex(
+ ["2 days", "1 days", "0 days", "1 days", "2 days"], name="x"
+ )
+ tm.assert_index_equal(result, exp)
+ assert result.freq is None
+
+
+class TestAddSubNaTMasking:
+ # TODO: parametrize over boxes
+
+ @pytest.mark.parametrize("str_ts", ["1950-01-01", "1980-01-01"])
+ def test_tdarr_add_timestamp_nat_masking(self, box_with_array, str_ts):
+ # GH#17991 checking for overflow-masking with NaT
+ tdinat = pd.to_timedelta(["24658 days 11:15:00", "NaT"])
+ tdobj = tm.box_expected(tdinat, box_with_array)
+
+ ts = Timestamp(str_ts)
+ ts_variants = [
+ ts,
+ ts.to_pydatetime(),
+ ts.to_datetime64().astype("datetime64[ns]"),
+ ts.to_datetime64().astype("datetime64[D]"),
+ ]
+
+ for variant in ts_variants:
+ res = tdobj + variant
+ if box_with_array is DataFrame:
+ assert res.iloc[1, 1] is NaT
+ else:
+ assert res[1] is NaT
+
+ def test_tdi_add_overflow(self):
+ # See GH#14068
+ # preliminary test scalar analogue of vectorized tests below
+ # TODO: Make raised error message more informative and test
+ ts = Timestamp("2000").as_unit("ns")
+ with pytest.raises(OutOfBoundsDatetime, match="10155196800000000000"):
+ pd.to_timedelta(106580, "D") + ts
+ with pytest.raises(OutOfBoundsDatetime, match="10155196800000000000"):
+ ts + pd.to_timedelta(106580, "D")
+
+ _NaT = NaT._value + 1
+ td = pd.to_timedelta([106580], "D").as_unit("ns")
+ msg = "Overflow in int64 addition"
+ with pytest.raises(OverflowError, match=msg):
+ td + Timestamp("2000")
+ with pytest.raises(OverflowError, match=msg):
+ Timestamp("2000") + td
+ with pytest.raises(OverflowError, match=msg):
+ pd.to_timedelta([_NaT]) - Timedelta("1 days")
+ with pytest.raises(OverflowError, match=msg):
+ pd.to_timedelta(["5 days", _NaT]) - Timedelta("1 days")
+ with pytest.raises(OverflowError, match=msg):
+ (
+ pd.to_timedelta([_NaT, "5 days", "1 hours"])
+ - pd.to_timedelta(["7 seconds", _NaT, "4 hours"])
+ )
+
+ # These should not overflow!
+ exp = TimedeltaIndex([NaT], dtype="m8[us]")
+ result = pd.to_timedelta([NaT]) - Timedelta("1 days")
+ tm.assert_index_equal(result, exp)
+
+ exp = TimedeltaIndex(["4 days", NaT])
+ result = pd.to_timedelta(["5 days", NaT]) - Timedelta("1 days")
+ tm.assert_index_equal(result, exp)
+
+ exp = TimedeltaIndex([NaT, NaT, "5 hours"])
+ result = pd.to_timedelta([NaT, "5 days", "1 hours"]) + pd.to_timedelta(
+ ["7 seconds", NaT, "4 hours"]
+ )
+ tm.assert_index_equal(result, exp)
+
+
+class TestTimedeltaArraylikeAddSubOps:
+ # Tests for timedelta64[ns] __add__, __sub__, __radd__, __rsub__
+
+ def test_sub_nat_retain_unit(self):
+ ser = pd.to_timedelta(Series(["00:00:01"])).astype("m8[s]")
+
+ result = ser - NaT
+ expected = Series([NaT], dtype="m8[s]")
+ tm.assert_series_equal(result, expected)
+
+ # TODO: moved from tests.indexes.timedeltas.test_arithmetic; needs
+ # parametrization+de-duplication
+ def test_timedelta_ops_with_missing_values(self):
+ # setup
+ s1 = pd.to_timedelta(Series(["00:00:01"]))
+ s2 = pd.to_timedelta(Series(["00:00:02"]))
+
+ sn = pd.to_timedelta(Series([NaT], dtype="m8[us]"))
+
+ df1 = DataFrame(["00:00:01"]).apply(pd.to_timedelta)
+ df2 = DataFrame(["00:00:02"]).apply(pd.to_timedelta)
+
+ dfn = DataFrame([NaT._value]).apply(pd.to_timedelta).astype("m8[us]")
+
+ scalar1 = pd.to_timedelta("00:00:01")
+ scalar2 = pd.to_timedelta("00:00:02")
+ timedelta_NaT = pd.to_timedelta("NaT")
+
+ actual = scalar1 + scalar1
+ assert actual == scalar2
+ actual = scalar2 - scalar1
+ assert actual == scalar1
+
+ actual = s1 + s1
+ tm.assert_series_equal(actual, s2)
+ actual = s2 - s1
+ tm.assert_series_equal(actual, s1)
+
+ actual = s1 + scalar1
+ tm.assert_series_equal(actual, s2)
+ actual = scalar1 + s1
+ tm.assert_series_equal(actual, s2)
+ actual = s2 - scalar1
+ tm.assert_series_equal(actual, s1)
+ actual = -scalar1 + s2
+ tm.assert_series_equal(actual, s1)
+
+ actual = s1 + timedelta_NaT
+ tm.assert_series_equal(actual, sn)
+ actual = timedelta_NaT + s1
+ tm.assert_series_equal(actual, sn)
+ actual = s1 - timedelta_NaT
+ tm.assert_series_equal(actual, sn)
+ actual = -timedelta_NaT + s1
+ tm.assert_series_equal(actual, sn)
+
+ msg = "unsupported operand type"
+ with pytest.raises(TypeError, match=msg):
+ s1 + np.nan
+ with pytest.raises(TypeError, match=msg):
+ np.nan + s1
+ with pytest.raises(TypeError, match=msg):
+ s1 - np.nan
+ with pytest.raises(TypeError, match=msg):
+ -np.nan + s1
+
+ actual = s1 + NaT
+ tm.assert_series_equal(actual, sn)
+ actual = s2 - NaT
+ tm.assert_series_equal(actual, sn)
+
+ actual = s1 + df1
+ tm.assert_frame_equal(actual, df2)
+ actual = s2 - df1
+ tm.assert_frame_equal(actual, df1)
+ actual = df1 + s1
+ tm.assert_frame_equal(actual, df2)
+ actual = df2 - s1
+ tm.assert_frame_equal(actual, df1)
+
+ actual = df1 + df1
+ tm.assert_frame_equal(actual, df2)
+ actual = df2 - df1
+ tm.assert_frame_equal(actual, df1)
+
+ actual = df1 + scalar1
+ tm.assert_frame_equal(actual, df2)
+ actual = df2 - scalar1
+ tm.assert_frame_equal(actual, df1)
+
+ actual = df1 + timedelta_NaT
+ tm.assert_frame_equal(actual, dfn)
+ actual = df1 - timedelta_NaT
+ tm.assert_frame_equal(actual, dfn)
+
+ msg = "cannot subtract a datelike from|unsupported operand type"
+ with pytest.raises(TypeError, match=msg):
+ df1 + np.nan
+ with pytest.raises(TypeError, match=msg):
+ df1 - np.nan
+
+ actual = df1 + NaT # NaT is datetime, not timedelta
+ tm.assert_frame_equal(actual, dfn)
+ actual = df1 - NaT
+ tm.assert_frame_equal(actual, dfn)
+
+ # TODO: moved from tests.series.test_operators, needs splitting, cleanup,
+ # de-duplication, box-parametrization...
+ def test_operators_timedelta64(self):
+ # series ops
+ v1 = pd.date_range("2012-1-1", periods=3, freq="D", unit="ns")
+ v2 = pd.date_range("2012-1-2", periods=3, freq="D", unit="ns")
+ rs = Series(v2) - Series(v1)
+ xp = Series(1e9 * 3600 * 24, rs.index).astype("int64").astype("timedelta64[ns]")
+ tm.assert_series_equal(rs, xp)
+ assert rs.dtype == "timedelta64[ns]"
+
+ df = DataFrame({"A": v1})
+ td = Series([timedelta(days=i) for i in range(3)], dtype="m8[ns]")
+ assert td.dtype == "timedelta64[ns]"
+
+ # series on the rhs
+ result = df["A"] - df["A"].shift()
+ assert result.dtype == "timedelta64[ns]"
+
+ result = df["A"] + td
+ assert result.dtype == "M8[ns]"
+
+ # scalar Timestamp on rhs
+ maxa = df["A"].max()
+ assert isinstance(maxa, Timestamp)
+
+ resultb = df["A"] - df["A"].max()
+ assert resultb.dtype == "timedelta64[ns]"
+
+ # timestamp on lhs
+ result = resultb + df["A"]
+ values = [Timestamp("20111230"), Timestamp("20120101"), Timestamp("20120103")]
+ expected = Series(values, dtype="M8[ns]", name="A")
+ tm.assert_series_equal(result, expected)
+
+ # datetimes on rhs
+ result = df["A"] - datetime(2001, 1, 1)
+ expected = Series(
+ [timedelta(days=4017 + i) for i in range(3)], name="A", dtype="m8[ns]"
+ )
+ tm.assert_series_equal(result, expected)
+ assert result.dtype == "m8[ns]"
+
+ d = datetime(2001, 1, 1, 3, 4)
+ resulta = df["A"] - d
+ assert resulta.dtype == "m8[ns]"
+
+ # roundtrip
+ resultb = resulta + d
+ tm.assert_series_equal(df["A"], resultb)
+
+ # timedeltas on rhs
+ td = timedelta(days=1)
+ resulta = df["A"] + td
+ resultb = resulta - td
+ tm.assert_series_equal(resultb, df["A"])
+ assert resultb.dtype == "M8[ns]"
+
+ # roundtrip
+ td = timedelta(minutes=5, seconds=3)
+ resulta = df["A"] + td
+ resultb = resulta - td
+ tm.assert_series_equal(df["A"], resultb)
+ assert resultb.dtype == "M8[ns]"
+
+ # inplace
+ value = rs[2] + np.timedelta64(timedelta(minutes=5, seconds=1))
+ rs[2] += np.timedelta64(timedelta(minutes=5, seconds=1))
+ assert rs[2] == value
+
+ def test_timedelta64_ops_nat(self):
+ # GH 11349
+ timedelta_series = Series([NaT, Timedelta("1s")])
+ nat_series_dtype_timedelta = Series([NaT, NaT], dtype="timedelta64[us]")
+ single_nat_dtype_timedelta = Series([NaT], dtype="timedelta64[us]")
+
+ # subtraction
+ tm.assert_series_equal(timedelta_series - NaT, nat_series_dtype_timedelta)
+ tm.assert_series_equal(-NaT + timedelta_series, nat_series_dtype_timedelta)
+
+ tm.assert_series_equal(
+ timedelta_series - single_nat_dtype_timedelta, nat_series_dtype_timedelta
+ )
+ tm.assert_series_equal(
+ -single_nat_dtype_timedelta + timedelta_series, nat_series_dtype_timedelta
+ )
+
+ # addition
+ tm.assert_series_equal(
+ nat_series_dtype_timedelta + NaT, nat_series_dtype_timedelta
+ )
+ tm.assert_series_equal(
+ NaT + nat_series_dtype_timedelta, nat_series_dtype_timedelta
+ )
+
+ tm.assert_series_equal(
+ nat_series_dtype_timedelta + single_nat_dtype_timedelta,
+ nat_series_dtype_timedelta,
+ )
+ tm.assert_series_equal(
+ single_nat_dtype_timedelta + nat_series_dtype_timedelta,
+ nat_series_dtype_timedelta,
+ )
+
+ tm.assert_series_equal(timedelta_series + NaT, nat_series_dtype_timedelta)
+ tm.assert_series_equal(NaT + timedelta_series, nat_series_dtype_timedelta)
+
+ tm.assert_series_equal(
+ timedelta_series + single_nat_dtype_timedelta, nat_series_dtype_timedelta
+ )
+ tm.assert_series_equal(
+ single_nat_dtype_timedelta + timedelta_series, nat_series_dtype_timedelta
+ )
+
+ tm.assert_series_equal(
+ nat_series_dtype_timedelta + NaT, nat_series_dtype_timedelta
+ )
+ tm.assert_series_equal(
+ NaT + nat_series_dtype_timedelta, nat_series_dtype_timedelta
+ )
+
+ tm.assert_series_equal(
+ nat_series_dtype_timedelta + single_nat_dtype_timedelta,
+ nat_series_dtype_timedelta,
+ )
+ tm.assert_series_equal(
+ single_nat_dtype_timedelta + nat_series_dtype_timedelta,
+ nat_series_dtype_timedelta,
+ )
+
+ # multiplication
+ tm.assert_series_equal(
+ nat_series_dtype_timedelta * 1.0, nat_series_dtype_timedelta
+ )
+ tm.assert_series_equal(
+ 1.0 * nat_series_dtype_timedelta, nat_series_dtype_timedelta
+ )
+
+ tm.assert_series_equal(timedelta_series * 1, timedelta_series)
+ tm.assert_series_equal(1 * timedelta_series, timedelta_series)
+
+ tm.assert_series_equal(timedelta_series * 1.5, Series([NaT, Timedelta("1.5s")]))
+ tm.assert_series_equal(1.5 * timedelta_series, Series([NaT, Timedelta("1.5s")]))
+
+ tm.assert_series_equal(timedelta_series * np.nan, nat_series_dtype_timedelta)
+ tm.assert_series_equal(np.nan * timedelta_series, nat_series_dtype_timedelta)
+
+ # division
+ tm.assert_series_equal(timedelta_series / 2, Series([NaT, Timedelta("0.5s")]))
+ tm.assert_series_equal(timedelta_series / 2.0, Series([NaT, Timedelta("0.5s")]))
+ tm.assert_series_equal(timedelta_series / np.nan, nat_series_dtype_timedelta)
+
+ # -------------------------------------------------------------
+ # Binary operations td64 arraylike and datetime-like
+
+ @pytest.mark.parametrize("cls", [Timestamp, datetime, np.datetime64])
+ def test_td64arr_add_sub_datetimelike_scalar(
+ self, cls, box_with_array, tz_naive_fixture
+ ):
+ # GH#11925, GH#29558, GH#23215
+ tz = tz_naive_fixture
+
+ dt_scalar = Timestamp("2012-01-01", tz=tz)
+ if cls is datetime:
+ ts = dt_scalar.to_pydatetime()
+ elif cls is np.datetime64:
+ if tz_naive_fixture is not None:
+ pytest.skip(f"{cls} doesn't support {tz_naive_fixture}")
+ ts = dt_scalar.to_datetime64()
+ else:
+ ts = dt_scalar
+
+ tdi = timedelta_range("1 day", periods=3)
+ expected = pd.date_range("2012-01-02", periods=3, tz=tz)
+ if tz is not None and not timezones.is_utc(expected.tz):
+ # Day is no longer preserved by timedelta add/sub in pandas3 because
+ # it represents Calendar-Day instead of 24h
+ expected = expected._with_freq(None)
+
+ tdarr = tm.box_expected(tdi, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ tm.assert_equal(ts + tdarr, expected)
+ tm.assert_equal(tdarr + ts, expected)
+
+ expected2 = pd.date_range("2011-12-31", periods=3, freq="-1D", tz=tz)
+ if tz is not None and not timezones.is_utc(expected2.tz):
+ # Day is no longer preserved by timedelta add/sub in pandas3 because
+ # it represents Calendar-Day instead of 24h
+ expected2 = expected2._with_freq(None)
+ expected2 = tm.box_expected(expected2, box_with_array)
+
+ tm.assert_equal(ts - tdarr, expected2)
+ tm.assert_equal(ts + (-tdarr), expected2)
+
+ msg = "cannot subtract a datelike"
+ with pytest.raises(TypeError, match=msg):
+ tdarr - ts
+
+ def test_td64arr_add_datetime64_nat(self, box_with_array):
+ # GH#23215
+ other = np.datetime64("NaT")
+
+ tdi = timedelta_range("1 day", periods=3)
+ expected = DatetimeIndex(["NaT", "NaT", "NaT"], dtype="M8[us]")
+
+ tdser = tm.box_expected(tdi, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ tm.assert_equal(tdser + other, expected)
+ tm.assert_equal(other + tdser, expected)
+
+ def test_td64arr_sub_dt64_array(self, box_with_array):
+ dti = pd.date_range("2016-01-01", periods=3)
+ tdi = TimedeltaIndex(["-1 Day"] * 3)
+ dtarr = dti.values
+ expected = DatetimeIndex(dtarr) - tdi
+
+ tdi = tm.box_expected(tdi, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ msg = "cannot subtract a datelike from"
+ with pytest.raises(TypeError, match=msg):
+ tdi - dtarr
+
+ # TimedeltaIndex.__rsub__
+ result = dtarr - tdi
+ tm.assert_equal(result, expected)
+
+ def test_td64arr_add_dt64_array(self, box_with_array):
+ dti = pd.date_range("2016-01-01", periods=3)
+ tdi = TimedeltaIndex(["-1 Day"] * 3)
+ dtarr = dti.values
+ expected = DatetimeIndex(dtarr) + tdi
+
+ tdi = tm.box_expected(tdi, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = tdi + dtarr
+ tm.assert_equal(result, expected)
+ result = dtarr + tdi
+ tm.assert_equal(result, expected)
+
+ # ------------------------------------------------------------------
+ # Invalid __add__/__sub__ operations
+
+ @pytest.mark.parametrize("pi_freq", ["D", "W", "Q", "h"])
+ @pytest.mark.parametrize("tdi_freq", [None, "h"])
+ def test_td64arr_sub_periodlike(
+ self, box_with_array, box_with_array2, tdi_freq, pi_freq
+ ):
+ # GH#20049 subtracting PeriodIndex should raise TypeError
+ tdi = TimedeltaIndex(["1 hours", "2 hours"], freq=tdi_freq)
+ dti = Timestamp("2018-03-07 17:16:40") + tdi
+ pi = dti.to_period(pi_freq)
+ per = pi[0]
+
+ tdi = tm.box_expected(tdi, box_with_array)
+ pi = tm.box_expected(pi, box_with_array2)
+ msg = "|".join(
+ [
+ "cannot subtract",
+ "unsupported operand type",
+ r"bad operand type for unary \-: 'PeriodArray'",
+ r"Input has different freq=-1h from PeriodArray\(.*\)",
+ "Cannot add/subtract timedelta-like from PeriodArray",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ tdi - pi
+
+ # GH#13078 subtraction of Period scalar not supported
+ with pytest.raises(TypeError, match=msg):
+ tdi - per
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ # GH#12624 for str case
+ "a",
+ # GH#19123
+ 1,
+ 1.5,
+ np.array(2),
+ ],
+ )
+ def test_td64arr_addsub_numeric_scalar_invalid(self, box_with_array, other):
+ # vector-like others are tested in test_td64arr_add_sub_numeric_arr_invalid
+ tdser = Series(["59 Days", "59 Days", "NaT"], dtype="m8[ns]")
+ tdarr = tm.box_expected(tdser, box_with_array)
+
+ assert_invalid_addsub_type(tdarr, other)
+
+ @pytest.mark.parametrize(
+ "vec",
+ [
+ np.array([1, 2, 3]),
+ Index([1, 2, 3]),
+ Series([1, 2, 3]),
+ DataFrame([[1, 2, 3]]),
+ ],
+ ids=lambda x: type(x).__name__,
+ )
+ def test_td64arr_addsub_numeric_arr_invalid(
+ self, box_with_array, vec, any_real_numpy_dtype
+ ):
+ tdser = Series(["59 Days", "59 Days", "NaT"], dtype="m8[ns]")
+ tdarr = tm.box_expected(tdser, box_with_array)
+
+ vector = vec.astype(any_real_numpy_dtype)
+ assert_invalid_addsub_type(tdarr, vector)
+
+ def test_td64arr_add_sub_int(self, box_with_array, one):
+ # Variants of `one` for #19012, deprecated GH#22535
+ rng = timedelta_range("1 days 09:00:00", freq="h", periods=10)
+ tdarr = tm.box_expected(rng, box_with_array)
+
+ msg = "Addition/subtraction of integers"
+ assert_invalid_addsub_type(tdarr, one, msg)
+
+ # TODO: get inplace ops into assert_invalid_addsub_type
+ with pytest.raises(TypeError, match=msg):
+ tdarr += one
+ with pytest.raises(TypeError, match=msg):
+ tdarr -= one
+
+ def test_td64arr_add_sub_integer_array(self, box_with_array):
+ # GH#19959, deprecated GH#22535
+ # GH#22696 for DataFrame case, check that we don't dispatch to numpy
+ # implementation, which treats int64 as m8[ns]
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+
+ rng = timedelta_range("1 days 09:00:00", freq="h", periods=3)
+ tdarr = tm.box_expected(rng, box)
+ other = tm.box_expected([4, 3, 2], xbox)
+
+ msg = "Addition/subtraction of integers and integer-arrays"
+ assert_invalid_addsub_type(tdarr, other, msg)
+
+ def test_td64arr_addsub_integer_array_no_freq(self, box_with_array):
+ # GH#19959
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+
+ tdi = TimedeltaIndex(["1 Day", "NaT", "3 Hours"])
+ tdarr = tm.box_expected(tdi, box)
+ other = tm.box_expected([14, -1, 16], xbox)
+
+ msg = "Addition/subtraction of integers"
+ assert_invalid_addsub_type(tdarr, other, msg)
+
+ # ------------------------------------------------------------------
+ # Operations with timedelta-like others
+
+ def test_td64arr_add_sub_td64_array(self, box_with_array):
+ box = box_with_array
+ dti = pd.date_range("2016-01-01", periods=3)
+ tdi = dti - dti.shift(1)
+ tdarr = tdi.values
+
+ expected = 2 * tdi
+ tdi = tm.box_expected(tdi, box)
+ expected = tm.box_expected(expected, box)
+
+ result = tdi + tdarr
+ tm.assert_equal(result, expected)
+ result = tdarr + tdi
+ tm.assert_equal(result, expected)
+
+ expected_sub = 0 * tdi
+ result = tdi - tdarr
+ tm.assert_equal(result, expected_sub)
+ result = tdarr - tdi
+ tm.assert_equal(result, expected_sub)
+
+ def test_td64arr_add_sub_tdi(self, box_with_array, names):
+ # GH#17250 make sure result dtype is correct
+ # GH#19043 make sure names are propagated correctly
+ box = box_with_array
+ exname = get_expected_name(box, names)
+
+ tdi = TimedeltaIndex(["0 days", "1 day"], name=names[1])
+ tdi = np.array(tdi) if box in [tm.to_array, pd.array] else tdi
+ ser = Series(
+ [Timedelta(hours=3), Timedelta(hours=4)], name=names[0], dtype="m8[ns]"
+ )
+ expected = Series(
+ [Timedelta(hours=3), Timedelta(days=1, hours=4)],
+ name=exname,
+ dtype="m8[ns]",
+ )
+
+ ser = tm.box_expected(ser, box)
+ expected = tm.box_expected(expected, box)
+
+ result = tdi + ser
+ tm.assert_equal(result, expected)
+ assert_dtype(result, "timedelta64[ns]")
+
+ result = ser + tdi
+ tm.assert_equal(result, expected)
+ assert_dtype(result, "timedelta64[ns]")
+
+ expected = Series(
+ [Timedelta(hours=-3), Timedelta(days=1, hours=-4)],
+ name=exname,
+ dtype="m8[ns]",
+ )
+ expected = tm.box_expected(expected, box)
+
+ result = tdi - ser
+ tm.assert_equal(result, expected)
+ assert_dtype(result, "timedelta64[ns]")
+
+ result = ser - tdi
+ tm.assert_equal(result, -expected)
+ assert_dtype(result, "timedelta64[ns]")
+
+ @pytest.mark.parametrize("tdnat", [np.timedelta64("NaT"), NaT])
+ def test_td64arr_add_sub_td64_nat(self, box_with_array, tdnat):
+ # GH#18808, GH#23320 special handling for timedelta64("NaT")
+ box = box_with_array
+ tdi = TimedeltaIndex([NaT, Timedelta("1s")])
+ expected = TimedeltaIndex(["NaT"] * 2).as_unit("us")
+
+ obj = tm.box_expected(tdi, box)
+ expected = tm.box_expected(expected, box)
+
+ result = obj + tdnat
+ tm.assert_equal(result, expected)
+ result = tdnat + obj
+ tm.assert_equal(result, expected)
+ result = obj - tdnat
+ tm.assert_equal(result, expected)
+ result = tdnat - obj
+ tm.assert_equal(result, expected)
+
+ def test_td64arr_add_timedeltalike(self, two_hours, box_with_array):
+ # only test adding/sub offsets as + is now numeric
+ # GH#10699 for Tick cases
+ box = box_with_array
+ rng = timedelta_range("1 days", "10 days")
+ expected = timedelta_range("1 days 02:00:00", "10 days 02:00:00", freq="D")
+ if isinstance(two_hours, Timedelta) and two_hours.unit == "ns":
+ expected = expected.as_unit("ns")
+
+ rng = tm.box_expected(rng, box)
+ expected = tm.box_expected(expected, box)
+
+ result = rng + two_hours
+ tm.assert_equal(result, expected)
+
+ result = two_hours + rng
+ tm.assert_equal(result, expected)
+
+ def test_td64arr_sub_timedeltalike(self, two_hours, box_with_array):
+ # only test adding/sub offsets as - is now numeric
+ # GH#10699 for Tick cases
+ box = box_with_array
+ rng = timedelta_range("1 days", "10 days")
+ expected = timedelta_range("0 days 22:00:00", "9 days 22:00:00")
+ if isinstance(two_hours, Timedelta) and two_hours.unit == "ns":
+ expected = expected.as_unit("ns")
+
+ rng = tm.box_expected(rng, box)
+ expected = tm.box_expected(expected, box)
+
+ result = rng - two_hours
+ tm.assert_equal(result, expected)
+
+ result = two_hours - rng
+ tm.assert_equal(result, -expected)
+
+ # ------------------------------------------------------------------
+ # __add__/__sub__ with DateOffsets and arrays of DateOffsets
+
+ def test_td64arr_add_sub_offset_index(
+ self, performance_warning, names, box_with_array
+ ):
+ # GH#18849, GH#19744
+ box = box_with_array
+ exname = get_expected_name(box, names)
+
+ tdi = TimedeltaIndex(["1 days 00:00:00", "3 days 04:00:00"], name=names[0])
+ other = Index([offsets.Hour(n=1), offsets.Minute(n=-2)], name=names[1])
+ other = np.array(other) if box in [tm.to_array, pd.array] else other
+
+ expected = TimedeltaIndex(
+ [tdi[n] + other[n] for n in range(len(tdi))], freq="infer", name=exname
+ )
+ expected_sub = TimedeltaIndex(
+ [tdi[n] - other[n] for n in range(len(tdi))], freq="infer", name=exname
+ )
+
+ tdi = tm.box_expected(tdi, box)
+ expected = tm.box_expected(expected, box).astype(object)
+ expected_sub = tm.box_expected(expected_sub, box).astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ res = tdi + other
+ tm.assert_equal(res, expected)
+
+ with tm.assert_produces_warning(performance_warning):
+ res2 = other + tdi
+ tm.assert_equal(res2, expected)
+
+ with tm.assert_produces_warning(performance_warning):
+ res_sub = tdi - other
+ tm.assert_equal(res_sub, expected_sub)
+
+ def test_td64arr_add_sub_offset_array(self, performance_warning, box_with_array):
+ # GH#18849, GH#18824
+ box = box_with_array
+ tdi = TimedeltaIndex(["1 days 00:00:00", "3 days 04:00:00"])
+ other = np.array([offsets.Hour(n=1), offsets.Minute(n=-2)])
+
+ expected = TimedeltaIndex(
+ [tdi[n] + other[n] for n in range(len(tdi))], freq="infer"
+ )
+ expected_sub = TimedeltaIndex(
+ [tdi[n] - other[n] for n in range(len(tdi))], freq="infer"
+ )
+
+ tdi = tm.box_expected(tdi, box)
+ expected = tm.box_expected(expected, box).astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ res = tdi + other
+ tm.assert_equal(res, expected)
+
+ with tm.assert_produces_warning(performance_warning):
+ res2 = other + tdi
+ tm.assert_equal(res2, expected)
+
+ expected_sub = tm.box_expected(expected_sub, box_with_array).astype(object)
+ with tm.assert_produces_warning(performance_warning):
+ res_sub = tdi - other
+ tm.assert_equal(res_sub, expected_sub)
+
+ def test_td64arr_with_offset_series(
+ self, performance_warning, names, box_with_array
+ ):
+ # GH#18849
+ box = box_with_array
+ box2 = Series if box in [Index, tm.to_array, pd.array] else box
+ exname = get_expected_name(box, names)
+
+ tdi = TimedeltaIndex(["1 days 00:00:00", "3 days 04:00:00"], name=names[0])
+ other = Series([offsets.Hour(n=1), offsets.Minute(n=-2)], name=names[1])
+
+ expected_add = Series(
+ [tdi[n] + other[n] for n in range(len(tdi))], name=exname, dtype=object
+ )
+ obj = tm.box_expected(tdi, box)
+ expected_add = tm.box_expected(expected_add, box2).astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ res = obj + other
+ tm.assert_equal(res, expected_add)
+
+ with tm.assert_produces_warning(performance_warning):
+ res2 = other + obj
+ tm.assert_equal(res2, expected_add)
+
+ expected_sub = Series(
+ [tdi[n] - other[n] for n in range(len(tdi))], name=exname, dtype=object
+ )
+ expected_sub = tm.box_expected(expected_sub, box2).astype(object)
+
+ with tm.assert_produces_warning(performance_warning):
+ res3 = obj - other
+ tm.assert_equal(res3, expected_sub)
+
+ @pytest.mark.parametrize("obox", [np.array, Index, Series])
+ def test_td64arr_addsub_anchored_offset_arraylike(
+ self, performance_warning, obox, box_with_array
+ ):
+ # GH#18824
+ tdi = TimedeltaIndex(["1 days 00:00:00", "3 days 04:00:00"])
+ tdi = tm.box_expected(tdi, box_with_array)
+
+ anchored = obox([offsets.MonthEnd(), offsets.Day(n=2)])
+
+ # addition/subtraction ops with anchored offsets should issue
+ # a PerformanceWarning and _then_ raise a TypeError.
+ msg = "has incorrect type|cannot add the type MonthEnd"
+ with pytest.raises(TypeError, match=msg):
+ with tm.assert_produces_warning(performance_warning):
+ tdi + anchored
+ with pytest.raises(TypeError, match=msg):
+ with tm.assert_produces_warning(performance_warning):
+ anchored + tdi
+ with pytest.raises(TypeError, match=msg):
+ with tm.assert_produces_warning(performance_warning):
+ tdi - anchored
+ with pytest.raises(TypeError, match=msg):
+ with tm.assert_produces_warning(performance_warning):
+ anchored - tdi
+
+ # ------------------------------------------------------------------
+ # Unsorted
+
+ def test_td64arr_add_sub_object_array(self, performance_warning, box_with_array):
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+
+ tdi = timedelta_range("1 day", periods=3, freq="D")
+ tdarr = tm.box_expected(tdi, box)
+
+ other = np.array([Timedelta(days=1), offsets.Day(2), Timestamp("2000-01-04")])
+
+ with tm.assert_produces_warning(performance_warning):
+ result = tdarr + other
+
+ expected = Index(
+ [Timedelta(days=2), Timedelta(days=4), Timestamp("2000-01-07")]
+ )
+ expected = tm.box_expected(expected, xbox).astype(object)
+ tm.assert_equal(result, expected)
+
+ msg = "unsupported operand type|cannot subtract a datelike"
+ with pytest.raises(TypeError, match=msg):
+ with tm.assert_produces_warning(performance_warning):
+ tdarr - other
+
+ with tm.assert_produces_warning(performance_warning):
+ result = other - tdarr
+
+ expected = Index([Timedelta(0), Timedelta(0), Timestamp("2000-01-01")])
+ expected = tm.box_expected(expected, xbox).astype(object)
+ tm.assert_equal(result, expected)
+
+
+class TestTimedeltaArraylikeMulDivOps:
+ # Tests for timedelta64[ns]
+ # __mul__, __rmul__, __div__, __rdiv__, __floordiv__, __rfloordiv__
+
+ # ------------------------------------------------------------------
+ # Multiplication
+ # organized with scalar others first, then array-like
+
+ def test_td64arr_mul_int(self, box_with_array):
+ idx = TimedeltaIndex(np.arange(5, dtype="int64"))
+ idx = tm.box_expected(idx, box_with_array)
+
+ result = idx * 1
+ tm.assert_equal(result, idx)
+
+ result = 1 * idx
+ tm.assert_equal(result, idx)
+
+ def test_td64arr_mul_tdlike_scalar_raises(self, two_hours, box_with_array):
+ rng = timedelta_range("1 days", "10 days", name="foo")
+ rng = tm.box_expected(rng, box_with_array)
+ msg = "|".join(
+ [
+ "argument must be an integer",
+ "cannot use operands with types dtype",
+ "Cannot multiply with",
+ r"unsupported operand type\(s\) for \*",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ rng * two_hours
+
+ def test_tdi_mul_int_array_zerodim(self, box_with_array):
+ rng5 = np.arange(5, dtype="int64")
+ idx = TimedeltaIndex(rng5)
+ expected = TimedeltaIndex(rng5 * 5)
+
+ idx = tm.box_expected(idx, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = idx * np.array(5, dtype="int64")
+ tm.assert_equal(result, expected)
+
+ def test_tdi_mul_int_array(self, box_with_array):
+ rng5 = np.arange(5, dtype="int64")
+ idx = TimedeltaIndex(rng5)
+ expected = TimedeltaIndex(rng5**2)
+
+ idx = tm.box_expected(idx, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = idx * rng5
+ tm.assert_equal(result, expected)
+
+ def test_tdi_mul_int_series(self, box_with_array):
+ box = box_with_array
+ xbox = Series if box in [Index, tm.to_array, pd.array] else box
+
+ idx = TimedeltaIndex(np.arange(5, dtype="int64"))
+ expected = TimedeltaIndex(np.arange(5, dtype="int64") ** 2)
+
+ idx = tm.box_expected(idx, box)
+ expected = tm.box_expected(expected, xbox)
+
+ result = idx * Series(np.arange(5, dtype="int64"))
+ tm.assert_equal(result, expected)
+
+ def test_tdi_mul_float_series(self, box_with_array):
+ box = box_with_array
+ xbox = Series if box in [Index, tm.to_array, pd.array] else box
+
+ idx = TimedeltaIndex(np.arange(5, dtype="int64"))
+ idx = tm.box_expected(idx, box)
+
+ rng5f = np.arange(5, dtype="float64")
+ expected = TimedeltaIndex(rng5f * (rng5f + 1.0))
+ expected = tm.box_expected(expected, xbox)
+
+ result = idx * Series(rng5f + 1.0)
+ tm.assert_equal(result, expected)
+
+ # TODO: Put Series/DataFrame in others?
+ @pytest.mark.parametrize(
+ "other",
+ [
+ np.arange(1, 11),
+ Index(np.arange(1, 11), np.int64),
+ Index(range(1, 11), np.uint64),
+ Index(range(1, 11), np.float64),
+ pd.RangeIndex(1, 11),
+ ],
+ ids=lambda x: type(x).__name__,
+ )
+ def test_tdi_rmul_arraylike(self, other, box_with_array):
+ box = box_with_array
+
+ tdi = TimedeltaIndex(["1 Day"] * 10)
+ expected = timedelta_range("1 days", "10 days")._with_freq(None)
+
+ tdi = tm.box_expected(tdi, box)
+ xbox = get_upcast_box(tdi, other)
+
+ expected = tm.box_expected(expected, xbox)
+
+ result = other * tdi
+ tm.assert_equal(result, expected)
+ commute = tdi * other
+ tm.assert_equal(commute, expected)
+
+ def test_td64arr_mul_bool_scalar_raises(self, box_with_array):
+ # GH#58054
+ ser = Series(np.arange(5) * timedelta(hours=1), dtype="m8[ns]")
+ obj = tm.box_expected(ser, box_with_array)
+
+ msg = r"Cannot multiply 'timedelta64\[ns\]' by bool"
+ with pytest.raises(TypeError, match=msg):
+ True * obj
+ with pytest.raises(TypeError, match=msg):
+ obj * True
+ with pytest.raises(TypeError, match=msg):
+ np.True_ * obj
+ with pytest.raises(TypeError, match=msg):
+ obj * np.True_
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ bool,
+ "boolean",
+ pytest.param("bool[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ ],
+ )
+ def test_td64arr_mul_bool_raises(self, dtype, box_with_array):
+ # GH#58054
+ ser = Series(np.arange(5) * timedelta(hours=1), dtype="m8[ns]")
+ obj = tm.box_expected(ser, box_with_array)
+
+ other = Series(np.arange(5) < 0.5, dtype=dtype)
+ other = tm.box_expected(other, box_with_array)
+
+ msg = r"Cannot multiply 'timedelta64\[ns\]' by bool"
+ with pytest.raises(TypeError, match=msg):
+ obj * other
+
+ msg2 = msg.replace("rmul", "mul")
+ if dtype == "bool[pyarrow]":
+ # We go through ArrowEA.__mul__ which gives a different message
+ msg2 = (
+ r"operation 'mul' not supported for dtype 'bool\[pyarrow\]' "
+ r"with dtype 'timedelta64\[ns\]'"
+ )
+ with pytest.raises(TypeError, match=msg2):
+ other * obj
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ "Int64",
+ "Float64",
+ pytest.param("int64[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ ],
+ )
+ def test_td64arr_mul_masked(self, dtype, box_with_array):
+ ser = Series(np.arange(5) * timedelta(hours=1), dtype="m8[ns]")
+ obj = tm.box_expected(ser, box_with_array)
+
+ other = Series(np.arange(5), dtype=dtype)
+ other = tm.box_expected(other, box_with_array)
+
+ expected = Series([Timedelta(hours=n**2) for n in range(5)], dtype="m8[ns]")
+ expected = tm.box_expected(expected, box_with_array)
+ if dtype == "int64[pyarrow]":
+ expected = expected.astype("duration[ns][pyarrow]")
+
+ result = obj * other
+ tm.assert_equal(result, expected)
+ result = other * obj
+ tm.assert_equal(result, expected)
+
+ # ------------------------------------------------------------------
+ # __div__, __rdiv__
+
+ def test_td64arr_div_nat_invalid(self, box_with_array):
+ # don't allow division by NaT (maybe could in the future)
+ rng = timedelta_range("1 days", "10 days", name="foo")
+ rng = tm.box_expected(rng, box_with_array)
+
+ with pytest.raises(TypeError, match="unsupported operand type"):
+ rng / NaT
+ with pytest.raises(TypeError, match="Cannot divide NaTType by"):
+ NaT / rng
+
+ dt64nat = np.datetime64("NaT", "ns")
+ msg = "|".join(
+ [
+ # 'divide' on npdev as of 2021-12-18
+ "ufunc '(true_divide|divide)' cannot use operands",
+ "cannot perform __r?truediv__",
+ "Cannot divide datetime64 by TimedeltaArray",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ rng / dt64nat
+ with pytest.raises(TypeError, match=msg):
+ dt64nat / rng
+
+ def test_td64arr_div_td64nat(self, box_with_array):
+ # GH#23829
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+
+ rng = timedelta_range("1 days", "10 days")
+ rng = tm.box_expected(rng, box)
+
+ other = np.timedelta64("NaT")
+
+ expected = np.array([np.nan] * 10)
+ expected = tm.box_expected(expected, xbox)
+
+ result = rng / other
+ tm.assert_equal(result, expected)
+
+ result = other / rng
+ tm.assert_equal(result, expected)
+
+ def test_td64arr_div_int(self, box_with_array):
+ idx = TimedeltaIndex(np.arange(5, dtype="int64"))
+ idx = tm.box_expected(idx, box_with_array)
+
+ result = idx / 1
+ tm.assert_equal(result, idx)
+
+ with pytest.raises(TypeError, match="Cannot divide"):
+ # GH#23829
+ 1 / idx
+
+ def test_td64arr_div_tdlike_scalar(self, two_hours, box_with_array):
+ # GH#20088, GH#22163 ensure DataFrame returns correct dtype
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+
+ rng = timedelta_range("1 days", "10 days", name="foo")
+ expected = Index((np.arange(10) + 1) * 12, dtype=np.float64, name="foo")
+
+ rng = tm.box_expected(rng, box)
+ expected = tm.box_expected(expected, xbox)
+
+ result = rng / two_hours
+ tm.assert_equal(result, expected)
+
+ result = two_hours / rng
+ expected = 1 / expected
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("m", [1, 3, 10])
+ @pytest.mark.parametrize("unit", ["D", "h", "m", "s", "ms", "us", "ns"])
+ def test_td64arr_div_td64_scalar(self, m, unit, box_with_array):
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+
+ ser = Series([Timedelta(days=59)] * 3)
+ ser[2] = np.nan
+ flat = ser
+ ser = tm.box_expected(ser, box)
+
+ # op
+ expected = Series([x / np.timedelta64(m, unit) for x in flat])
+ expected = tm.box_expected(expected, xbox)
+ result = ser / np.timedelta64(m, unit)
+ tm.assert_equal(result, expected)
+
+ # reverse op
+ expected = Series([Timedelta(np.timedelta64(m, unit)) / x for x in flat])
+ expected = tm.box_expected(expected, xbox)
+ result = np.timedelta64(m, unit) / ser
+ tm.assert_equal(result, expected)
+
+ def test_td64arr_div_tdlike_scalar_with_nat(self, two_hours, box_with_array):
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+
+ rng = TimedeltaIndex(["1 days", NaT, "2 days"], name="foo")
+ expected = Index([12, np.nan, 24], dtype=np.float64, name="foo")
+
+ rng = tm.box_expected(rng, box)
+ expected = tm.box_expected(expected, xbox)
+
+ result = rng / two_hours
+ tm.assert_equal(result, expected)
+
+ result = two_hours / rng
+ expected = 1 / expected
+ tm.assert_equal(result, expected)
+
+ def test_td64arr_div_td64_ndarray(self, box_with_array):
+ # GH#22631
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+
+ rng = TimedeltaIndex(["1 days", NaT, "2 days"])
+ expected = Index([12, np.nan, 24], dtype=np.float64)
+
+ rng = tm.box_expected(rng, box)
+ expected = tm.box_expected(expected, xbox)
+
+ other = np.array([2, 4, 2], dtype="m8[h]")
+ result = rng / other
+ tm.assert_equal(result, expected)
+
+ result = rng / tm.box_expected(other, box)
+ tm.assert_equal(result, expected)
+
+ result = rng / other.astype(object)
+ tm.assert_equal(result, expected.astype(object))
+
+ result = rng / list(other)
+ tm.assert_equal(result, expected)
+
+ # reversed op
+ expected = 1 / expected
+ result = other / rng
+ tm.assert_equal(result, expected)
+
+ result = tm.box_expected(other, box) / rng
+ tm.assert_equal(result, expected)
+
+ result = other.astype(object) / rng
+ tm.assert_equal(result, expected)
+
+ result = list(other) / rng
+ tm.assert_equal(result, expected)
+
+ def test_tdarr_div_length_mismatch(self, box_with_array):
+ rng = TimedeltaIndex(["1 days", NaT, "2 days"])
+ mismatched = [1, 2, 3, 4]
+
+ rng = tm.box_expected(rng, box_with_array)
+ msg = "Cannot divide vectors|Unable to coerce to Series"
+ for obj in [mismatched, mismatched[:2]]:
+ # one shorter, one longer
+ for other in [obj, np.array(obj), Index(obj)]:
+ with pytest.raises(ValueError, match=msg):
+ rng / other
+ with pytest.raises(ValueError, match=msg):
+ other / rng
+
+ def test_td64_div_object_mixed_result(self, box_with_array):
+ # Case where we having a NaT in the result inseat of timedelta64("NaT")
+ # is misleading
+ orig = timedelta_range("1 Day", periods=3).insert(1, NaT)
+ tdi = tm.box_expected(orig, box_with_array, transpose=False)
+
+ other = np.array([orig[0], 1.5, 2.0, orig[2]], dtype=object)
+ other = tm.box_expected(other, box_with_array, transpose=False)
+
+ res = tdi / other
+
+ expected = Index([1.0, np.timedelta64("NaT", "us"), orig[0], 1.5], dtype=object)
+ expected = tm.box_expected(expected, box_with_array, transpose=False)
+ if isinstance(expected, NumpyExtensionArray):
+ expected = expected.to_numpy()
+ tm.assert_equal(res, expected)
+ if box_with_array is DataFrame:
+ # We have an np.timedelta64(NaT), not pd.NaT
+ assert isinstance(res.iloc[1, 0], np.timedelta64)
+
+ res = tdi // other
+
+ expected = Index([1, np.timedelta64("NaT", "us"), orig[0], 1], dtype=object)
+ expected = tm.box_expected(expected, box_with_array, transpose=False)
+ if isinstance(expected, NumpyExtensionArray):
+ expected = expected.to_numpy()
+ tm.assert_equal(res, expected)
+ if box_with_array is DataFrame:
+ # We have an np.timedelta64(NaT), not pd.NaT
+ assert isinstance(res.iloc[1, 0], np.timedelta64)
+
+ # ------------------------------------------------------------------
+ # __floordiv__, __rfloordiv__
+
+ @pytest.mark.skipif(WASM, reason="no fp exception support in wasm")
+ def test_td64arr_floordiv_td64arr_with_nat(self, box_with_array):
+ # GH#35529
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+
+ left = Series([1000, 222330, 30], dtype="timedelta64[ns]")
+ right = Series([1000, 222330, None], dtype="timedelta64[ns]")
+
+ left = tm.box_expected(left, box)
+ right = tm.box_expected(right, box)
+
+ expected = np.array([1.0, 1.0, np.nan], dtype=np.float64)
+ expected = tm.box_expected(expected, xbox)
+
+ with tm.maybe_produces_warning(
+ RuntimeWarning, box is pd.array, check_stacklevel=False
+ ):
+ result = left // right
+
+ tm.assert_equal(result, expected)
+
+ # case that goes through __rfloordiv__ with arraylike
+ with tm.maybe_produces_warning(
+ RuntimeWarning, box is pd.array, check_stacklevel=False
+ ):
+ result = np.asarray(left) // right
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning")
+ def test_td64arr_floordiv_tdscalar(self, box_with_array, scalar_td):
+ # GH#18831, GH#19125
+ box = box_with_array
+ xbox = np.ndarray if box is pd.array else box
+ td = Timedelta("5m3s") # i.e. (scalar_td - 1sec) / 2
+
+ td1 = Series([td, td, NaT], dtype="m8[ns]")
+ td1 = tm.box_expected(td1, box, transpose=False)
+
+ expected = Series([0, 0, np.nan])
+ expected = tm.box_expected(expected, xbox, transpose=False)
+
+ result = td1 // scalar_td
+ tm.assert_equal(result, expected)
+
+ # Reversed op
+ expected = Series([2, 2, np.nan])
+ expected = tm.box_expected(expected, xbox, transpose=False)
+
+ result = scalar_td // td1
+ tm.assert_equal(result, expected)
+
+ # same thing buts let's be explicit about calling __rfloordiv__
+ result = td1.__rfloordiv__(scalar_td)
+ tm.assert_equal(result, expected)
+
+ def test_td64arr_floordiv_int(self, box_with_array):
+ idx = TimedeltaIndex(np.arange(5, dtype="int64"))
+ idx = tm.box_expected(idx, box_with_array)
+ result = idx // 1
+ tm.assert_equal(result, idx)
+
+ pattern = "floor_divide cannot use operands|Cannot divide int by Timedelta*"
+ with pytest.raises(TypeError, match=pattern):
+ 1 // idx
+
+ # ------------------------------------------------------------------
+ # mod, divmod
+ # TODO: operations with timedelta-like arrays, numeric arrays,
+ # reversed ops
+
+ def test_td64arr_mod_tdscalar(
+ self, performance_warning, box_with_array, three_days
+ ):
+ tdi = timedelta_range("1 Day", "9 days")
+ tdarr = tm.box_expected(tdi, box_with_array)
+
+ expected = TimedeltaIndex(["1 Day", "2 Days", "0 Days"] * 3)
+ expected = tm.box_expected(expected, box_with_array)
+
+ if isinstance(three_days, offsets.Day):
+ msg = "unsupported operand type"
+ with pytest.raises(TypeError, match=msg):
+ tdarr % three_days
+ with pytest.raises(TypeError, match=msg):
+ divmod(tdarr, three_days)
+ with pytest.raises(TypeError, match=msg):
+ tdarr // three_days
+ return
+
+ result = tdarr % three_days
+ tm.assert_equal(result, expected)
+
+ if box_with_array is DataFrame and isinstance(three_days, pd.DateOffset):
+ # TODO: making expected be object here a result of DataFrame.__divmod__
+ # being defined in a naive way that does not dispatch to the underlying
+ # array's __divmod__
+ expected = expected.astype(object)
+ else:
+ performance_warning = False
+
+ with tm.assert_produces_warning(performance_warning):
+ result = divmod(tdarr, three_days)
+
+ tm.assert_equal(result[1], expected)
+ tm.assert_equal(result[0], tdarr // three_days)
+
+ def test_td64arr_mod_int(self, box_with_array):
+ tdi = timedelta_range("1 ns", "10 ns", periods=10)
+ tdarr = tm.box_expected(tdi, box_with_array)
+
+ expected = TimedeltaIndex(["1 ns", "0 ns"] * 5)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = tdarr % 2
+ tm.assert_equal(result, expected)
+
+ msg = "Cannot divide int by"
+ with pytest.raises(TypeError, match=msg):
+ 2 % tdarr
+
+ result = divmod(tdarr, 2)
+ tm.assert_equal(result[1], expected)
+ tm.assert_equal(result[0], tdarr // 2)
+
+ def test_td64arr_rmod_tdscalar(self, box_with_array, three_days):
+ tdi = timedelta_range("1 Day", "9 days")
+ tdarr = tm.box_expected(tdi, box_with_array)
+
+ expected = ["0 Days", "1 Day", "0 Days"] + ["3 Days"] * 6
+ expected = TimedeltaIndex(expected)
+ expected = tm.box_expected(expected, box_with_array)
+
+ if isinstance(three_days, offsets.Day):
+ msg = "Cannot divide Day by TimedeltaArray"
+ with pytest.raises(TypeError, match=msg):
+ three_days % tdarr
+ return
+
+ result = three_days % tdarr
+ tm.assert_equal(result, expected)
+
+ result = divmod(three_days, tdarr)
+ tm.assert_equal(result[1], expected)
+ tm.assert_equal(result[0], three_days // tdarr)
+
+ # ------------------------------------------------------------------
+ # Operations with invalid others
+
+ def test_td64arr_mul_tdscalar_invalid(self, box_with_array, scalar_td):
+ td1 = Series([timedelta(minutes=5, seconds=3)] * 3)
+ td1.iloc[2] = np.nan
+
+ td1 = tm.box_expected(td1, box_with_array)
+
+ # check that we are getting a TypeError
+ # with 'operate' (from core/ops.py) for the ops that are not
+ # defined
+ pattern = "operate|unsupported|cannot|not supported"
+ with pytest.raises(TypeError, match=pattern):
+ td1 * scalar_td
+ with pytest.raises(TypeError, match=pattern):
+ scalar_td * td1
+
+ def test_td64arr_mul_too_short_raises(self, box_with_array):
+ idx = TimedeltaIndex(np.arange(5, dtype="int64"))
+ idx = tm.box_expected(idx, box_with_array)
+ msg = "|".join(
+ [
+ "cannot use operands with types dtype",
+ "Cannot multiply with unequal lengths",
+ "Unable to coerce to Series",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ # length check before dtype check
+ idx * idx[:3]
+ with pytest.raises(ValueError, match=msg):
+ idx * np.array([1, 2])
+
+ def test_td64arr_mul_td64arr_raises(self, box_with_array):
+ idx = TimedeltaIndex(np.arange(5, dtype="int64"))
+ idx = tm.box_expected(idx, box_with_array)
+ msg = "cannot use operands with types dtype"
+ with pytest.raises(TypeError, match=msg):
+ idx * idx
+
+ # ------------------------------------------------------------------
+ # Operations with numeric others
+
+ def test_td64arr_mul_numeric_scalar(self, box_with_array, one):
+ # GH#4521
+ # divide/multiply by integers
+ tdser = Series(["59 Days", "59 Days", "NaT"], dtype="m8[ns]")
+ expected = Series(["-59 Days", "-59 Days", "NaT"], dtype="timedelta64[ns]")
+
+ tdser = tm.box_expected(tdser, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = tdser * (-one)
+ tm.assert_equal(result, expected)
+ result = (-one) * tdser
+ tm.assert_equal(result, expected)
+
+ expected = Series(["118 Days", "118 Days", "NaT"], dtype="timedelta64[ns]")
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = tdser * (2 * one)
+ tm.assert_equal(result, expected)
+ result = (2 * one) * tdser
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("two", [2, 2.0, np.array(2), np.array(2.0)])
+ def test_td64arr_div_numeric_scalar(self, box_with_array, two):
+ # GH#4521
+ # divide/multiply by integers
+ tdser = Series(["59 Days", "59 Days", "NaT"], dtype="m8[ns]")
+ expected = Series(["29.5D", "29.5D", "NaT"], dtype="timedelta64[ns]")
+
+ tdser = tm.box_expected(tdser, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = tdser / two
+ tm.assert_equal(result, expected)
+
+ with pytest.raises(TypeError, match="Cannot divide"):
+ two / tdser
+
+ @pytest.mark.parametrize("two", [2, 2.0, np.array(2), np.array(2.0)])
+ def test_td64arr_floordiv_numeric_scalar(self, box_with_array, two):
+ tdser = Series(["59 Days", "59 Days", "NaT"], dtype="m8[ns]")
+ expected = Series(["29.5D", "29.5D", "NaT"], dtype="timedelta64[ns]")
+
+ tdser = tm.box_expected(tdser, box_with_array)
+ expected = tm.box_expected(expected, box_with_array)
+
+ result = tdser // two
+ tm.assert_equal(result, expected)
+
+ with pytest.raises(TypeError, match="Cannot divide"):
+ two // tdser
+
+ @pytest.mark.parametrize(
+ "klass",
+ [np.array, Index, Series],
+ ids=lambda x: x.__name__,
+ )
+ def test_td64arr_rmul_numeric_array(
+ self,
+ box_with_array,
+ klass,
+ any_real_numpy_dtype,
+ ):
+ # GH#4521
+ # divide/multiply by integers
+
+ vector = klass([20, 30, 40])
+ tdser = Series(["59 Days", "59 Days", "NaT"], dtype="m8[ns]")
+ vector = vector.astype(any_real_numpy_dtype)
+
+ expected = Series(["1180 Days", "1770 Days", "NaT"], dtype="timedelta64[ns]")
+
+ tdser = tm.box_expected(tdser, box_with_array)
+ xbox = get_upcast_box(tdser, vector)
+
+ expected = tm.box_expected(expected, xbox)
+
+ result = tdser * vector
+ tm.assert_equal(result, expected)
+
+ result = vector * tdser
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "klass",
+ [np.array, Index, Series],
+ ids=lambda x: x.__name__,
+ )
+ def test_td64arr_div_numeric_array(
+ self, box_with_array, klass, any_real_numpy_dtype
+ ):
+ # GH#4521
+ # divide/multiply by integers
+
+ vector = klass([20, 30, 40])
+ tdser = Series(["59 Days", "59 Days", "NaT"], dtype="m8[ns]")
+ vector = vector.astype(any_real_numpy_dtype)
+
+ expected = Series(["2.95D", "1D 23h 12m", "NaT"], dtype="timedelta64[ns]")
+
+ tdser = tm.box_expected(tdser, box_with_array)
+ xbox = get_upcast_box(tdser, vector)
+ expected = tm.box_expected(expected, xbox)
+
+ result = tdser / vector
+ tm.assert_equal(result, expected)
+
+ pattern = "|".join(
+ [
+ "true_divide'? cannot use operands",
+ "cannot perform __div__",
+ "cannot perform __truediv__",
+ "unsupported operand",
+ "Cannot divide",
+ "ufunc 'divide' cannot use operands with types",
+ ]
+ )
+ with pytest.raises(TypeError, match=pattern):
+ vector / tdser
+
+ result = tdser / vector.astype(object)
+ if box_with_array is DataFrame:
+ expected = [tdser.iloc[0, n] / vector[n] for n in range(len(vector))]
+ expected = tm.box_expected(expected, xbox).astype(object)
+ expected[2] = expected[2].fillna(np.timedelta64("NaT", "ns"))
+ else:
+ expected = [tdser[n] / vector[n] for n in range(len(tdser))]
+ expected = [
+ x if x is not NaT else np.timedelta64("NaT", "ns") for x in expected
+ ]
+ if xbox is tm.to_array:
+ expected = tm.to_array(expected).astype(object)
+ else:
+ expected = xbox(expected, dtype=object)
+
+ tm.assert_equal(result, expected)
+
+ with pytest.raises(TypeError, match=pattern):
+ vector.astype(object) / tdser
+
+ def test_td64arr_mul_int_series(self, box_with_array, names):
+ # GH#19042 test for correct name attachment
+ box = box_with_array
+ exname = get_expected_name(box, names)
+
+ tdi = TimedeltaIndex(
+ ["0days", "1day", "2days", "3days", "4days"], name=names[0]
+ )
+ # TODO: Should we be parametrizing over types for `ser` too?
+ ser = Series([0, 1, 2, 3, 4], dtype=np.int64, name=names[1])
+
+ expected = Series(
+ ["0days", "1day", "4days", "9days", "16days"],
+ dtype="timedelta64[us]",
+ name=exname,
+ )
+
+ tdi = tm.box_expected(tdi, box)
+ xbox = get_upcast_box(tdi, ser)
+
+ expected = tm.box_expected(expected, xbox)
+
+ result = ser * tdi
+ tm.assert_equal(result, expected)
+
+ result = tdi * ser
+ tm.assert_equal(result, expected)
+
+ # TODO: Should we be parametrizing over types for `ser` too?
+ def test_float_series_rdiv_td64arr(self, box_with_array, names):
+ # GH#19042 test for correct name attachment
+ box = box_with_array
+ tdi = TimedeltaIndex(
+ ["0days", "1day", "2days", "3days", "4days"], name=names[0]
+ )
+ ser = Series([1.5, 3, 4.5, 6, 7.5], dtype=np.float64, name=names[1])
+
+ xname = names[2] if box not in [tm.to_array, pd.array] else names[1]
+ expected = Series(
+ [tdi[n] / ser[n] for n in range(len(ser))],
+ dtype="timedelta64[us]",
+ name=xname,
+ )
+
+ tdi = tm.box_expected(tdi, box)
+ xbox = get_upcast_box(tdi, ser)
+ expected = tm.box_expected(expected, xbox)
+
+ result = ser.__rtruediv__(tdi)
+ if box is DataFrame:
+ assert result is NotImplemented
+ else:
+ tm.assert_equal(result, expected)
+
+ def test_td64arr_all_nat_div_object_dtype_numeric(self, box_with_array):
+ # GH#39750 make sure we infer the result as td64
+ tdi = TimedeltaIndex([NaT, NaT], dtype="m8[ns]")
+
+ left = tm.box_expected(tdi, box_with_array)
+ right = np.array([2, 2.0], dtype=object)
+
+ tdnat = np.timedelta64("NaT", "ns")
+ expected = Index([tdnat] * 2, dtype=object)
+ if box_with_array is not Index:
+ expected = tm.box_expected(expected, box_with_array).astype(object)
+ if box_with_array in [Series, DataFrame]:
+ expected = expected.fillna(tdnat) # GH#18463
+
+ result = left / right
+ tm.assert_equal(result, expected)
+
+ result = left // right
+ tm.assert_equal(result, expected)
+
+
+class TestTimedelta64ArrayLikeArithmetic:
+ # Arithmetic tests for timedelta64[ns] vectors fully parametrized over
+ # DataFrame/Series/TimedeltaIndex/TimedeltaArray. Ideally all arithmetic
+ # tests will eventually end up here.
+
+ def test_td64arr_pow_invalid(self, scalar_td, box_with_array):
+ td1 = Series([timedelta(minutes=5, seconds=3)] * 3)
+ td1.iloc[2] = np.nan
+
+ td1 = tm.box_expected(td1, box_with_array)
+
+ # check that we are getting a TypeError
+ # with 'operate' (from core/ops.py) for the ops that are not
+ # defined
+ pattern = "operate|unsupported|cannot|not supported"
+ with pytest.raises(TypeError, match=pattern):
+ scalar_td**td1
+
+ with pytest.raises(TypeError, match=pattern):
+ td1**scalar_td
+
+
+def test_add_timestamp_to_timedelta():
+ # GH: 35897
+ timestamp = Timestamp("2021-01-01")
+ result = timestamp + timedelta_range("0s", "1s", periods=31)
+ expected = DatetimeIndex(
+ [
+ timestamp
+ + (
+ pd.to_timedelta("0.033333s") * i
+ + pd.to_timedelta("0.000001s") * divmod(i, 3)[0]
+ )
+ for i in range(31)
+ ]
+ )
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/categorical/test_operators.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/categorical/test_operators.py
new file mode 100644
index 0000000000000000000000000000000000000000..dbc6cc771574452907af64da7760678845937876
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/categorical/test_operators.py
@@ -0,0 +1,408 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestCategoricalOpsWithFactor:
+ def test_categories_none_comparisons(self):
+ factor = Categorical(["a", "b", "b", "a", "a", "c", "c", "c"], ordered=True)
+ tm.assert_categorical_equal(factor, factor)
+
+ def test_comparisons(self):
+ factor = Categorical(["a", "b", "b", "a", "a", "c", "c", "c"], ordered=True)
+ result = factor[factor == "a"]
+ expected = factor[np.asarray(factor) == "a"]
+ tm.assert_categorical_equal(result, expected)
+
+ result = factor[factor != "a"]
+ expected = factor[np.asarray(factor) != "a"]
+ tm.assert_categorical_equal(result, expected)
+
+ result = factor[factor < "c"]
+ expected = factor[np.asarray(factor) < "c"]
+ tm.assert_categorical_equal(result, expected)
+
+ result = factor[factor > "a"]
+ expected = factor[np.asarray(factor) > "a"]
+ tm.assert_categorical_equal(result, expected)
+
+ result = factor[factor >= "b"]
+ expected = factor[np.asarray(factor) >= "b"]
+ tm.assert_categorical_equal(result, expected)
+
+ result = factor[factor <= "b"]
+ expected = factor[np.asarray(factor) <= "b"]
+ tm.assert_categorical_equal(result, expected)
+
+ n = len(factor)
+
+ other = factor[np.random.default_rng(2).permutation(n)]
+ result = factor == other
+ expected = np.asarray(factor) == np.asarray(other)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = factor == "d"
+ expected = np.zeros(len(factor), dtype=bool)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # comparisons with categoricals
+ cat_rev = Categorical(["a", "b", "c"], categories=["c", "b", "a"], ordered=True)
+ cat_rev_base = Categorical(
+ ["b", "b", "b"], categories=["c", "b", "a"], ordered=True
+ )
+ cat = Categorical(["a", "b", "c"], ordered=True)
+ cat_base = Categorical(["b", "b", "b"], categories=cat.categories, ordered=True)
+
+ # comparisons need to take categories ordering into account
+ res_rev = cat_rev > cat_rev_base
+ exp_rev = np.array([True, False, False])
+ tm.assert_numpy_array_equal(res_rev, exp_rev)
+
+ res_rev = cat_rev < cat_rev_base
+ exp_rev = np.array([False, False, True])
+ tm.assert_numpy_array_equal(res_rev, exp_rev)
+
+ res = cat > cat_base
+ exp = np.array([False, False, True])
+ tm.assert_numpy_array_equal(res, exp)
+
+ # Only categories with same categories can be compared
+ msg = "Categoricals can only be compared if 'categories' are the same"
+ with pytest.raises(TypeError, match=msg):
+ cat > cat_rev
+
+ cat_rev_base2 = Categorical(["b", "b", "b"], categories=["c", "b", "a", "d"])
+
+ with pytest.raises(TypeError, match=msg):
+ cat_rev > cat_rev_base2
+
+ # Only categories with same ordering information can be compared
+ cat_unordered = cat.set_ordered(False)
+ assert not (cat > cat).any()
+
+ with pytest.raises(TypeError, match=msg):
+ cat > cat_unordered
+
+ # comparison (in both directions) with Series will raise
+ s = Series(["b", "b", "b"], dtype=object)
+ msg = (
+ "Cannot compare a Categorical for op __gt__ with type "
+ r""
+ )
+ with pytest.raises(TypeError, match=msg):
+ cat > s
+ with pytest.raises(TypeError, match=msg):
+ cat_rev > s
+ with pytest.raises(TypeError, match=msg):
+ s < cat
+ with pytest.raises(TypeError, match=msg):
+ s < cat_rev
+
+ # comparison with numpy.array will raise in both direction, but only on
+ # newer numpy versions
+ a = np.array(["b", "b", "b"], dtype=object)
+ with pytest.raises(TypeError, match=msg):
+ cat > a
+ with pytest.raises(TypeError, match=msg):
+ cat_rev > a
+
+ # Make sure that unequal comparison take the categories order in
+ # account
+ cat_rev = Categorical(list("abc"), categories=list("cba"), ordered=True)
+ exp = np.array([True, False, False])
+ res = cat_rev > "b"
+ tm.assert_numpy_array_equal(res, exp)
+
+ # check that zero-dim array gets unboxed
+ res = cat_rev > np.array("b")
+ tm.assert_numpy_array_equal(res, exp)
+
+
+class TestCategoricalOps:
+ @pytest.mark.parametrize(
+ "categories",
+ [["a", "b"], [0, 1], [Timestamp("2019"), Timestamp("2020")]],
+ )
+ def test_not_equal_with_na(self, categories):
+ # https://github.com/pandas-dev/pandas/issues/32276
+ c1 = Categorical.from_codes([-1, 0], categories=categories)
+ c2 = Categorical.from_codes([0, 1], categories=categories)
+
+ result = c1 != c2
+
+ assert result.all()
+
+ def test_compare_frame(self):
+ # GH#24282 check that Categorical.__cmp__(DataFrame) defers to frame
+ data = ["a", "b", 2, "a"]
+ cat = Categorical(data)
+
+ df = DataFrame(cat)
+
+ result = cat == df.T
+ expected = DataFrame([[True, True, True, True]])
+ tm.assert_frame_equal(result, expected)
+
+ result = cat[::-1] != df.T
+ expected = DataFrame([[False, True, True, False]])
+ tm.assert_frame_equal(result, expected)
+
+ def test_compare_frame_raises(self, comparison_op):
+ # alignment raises unless we transpose
+ op = comparison_op
+ cat = Categorical(["a", "b", 2, "a"])
+ df = DataFrame(cat)
+ msg = "Unable to coerce to Series, length must be 1: given 4"
+ with pytest.raises(ValueError, match=msg):
+ op(cat, df)
+
+ def test_datetime_categorical_comparison(self):
+ dt_cat = Categorical(date_range("2014-01-01", periods=3), ordered=True)
+ tm.assert_numpy_array_equal(dt_cat > dt_cat[0], np.array([False, True, True]))
+ tm.assert_numpy_array_equal(dt_cat[0] < dt_cat, np.array([False, True, True]))
+
+ def test_reflected_comparison_with_scalars(self):
+ # GH8658
+ cat = Categorical([1, 2, 3], ordered=True)
+ tm.assert_numpy_array_equal(cat > cat[0], np.array([False, True, True]))
+ tm.assert_numpy_array_equal(cat[0] < cat, np.array([False, True, True]))
+
+ def test_comparison_with_unknown_scalars(self):
+ # https://github.com/pandas-dev/pandas/issues/9836#issuecomment-92123057
+ # and following comparisons with scalars not in categories should raise
+ # for unequal comps, but not for equal/not equal
+ cat = Categorical([1, 2, 3], ordered=True)
+
+ msg = "Invalid comparison between dtype=category and int"
+ with pytest.raises(TypeError, match=msg):
+ cat < 4
+ with pytest.raises(TypeError, match=msg):
+ cat > 4
+ with pytest.raises(TypeError, match=msg):
+ 4 < cat
+ with pytest.raises(TypeError, match=msg):
+ 4 > cat
+
+ tm.assert_numpy_array_equal(cat == 4, np.array([False, False, False]))
+ tm.assert_numpy_array_equal(cat != 4, np.array([True, True, True]))
+
+ def test_comparison_with_tuple(self):
+ cat = Categorical(np.array(["foo", (0, 1), 3, (0, 1)], dtype=object))
+
+ result = cat == "foo"
+ expected = np.array([True, False, False, False], dtype=bool)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = cat == (0, 1)
+ expected = np.array([False, True, False, True], dtype=bool)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = cat != (0, 1)
+ tm.assert_numpy_array_equal(result, ~expected)
+
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ def test_comparison_of_ordered_categorical_with_nan_to_scalar(
+ self, compare_operators_no_eq_ne
+ ):
+ # https://github.com/pandas-dev/pandas/issues/26504
+ # BUG: fix ordered categorical comparison with missing values (#26504 )
+ # and following comparisons with scalars in categories with missing
+ # values should be evaluated as False
+
+ cat = Categorical([1, 2, 3, None], categories=[1, 2, 3], ordered=True)
+ scalar = 2
+ expected = getattr(np.array(cat), compare_operators_no_eq_ne)(scalar)
+ actual = getattr(cat, compare_operators_no_eq_ne)(scalar)
+ tm.assert_numpy_array_equal(actual, expected)
+
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ def test_comparison_of_ordered_categorical_with_nan_to_listlike(
+ self, compare_operators_no_eq_ne
+ ):
+ # https://github.com/pandas-dev/pandas/issues/26504
+ # and following comparisons of missing values in ordered Categorical
+ # with listlike should be evaluated as False
+
+ cat = Categorical([1, 2, 3, None], categories=[1, 2, 3], ordered=True)
+ other = Categorical([2, 2, 2, 2], categories=[1, 2, 3], ordered=True)
+ expected = getattr(np.array(cat), compare_operators_no_eq_ne)(2)
+ actual = getattr(cat, compare_operators_no_eq_ne)(other)
+ tm.assert_numpy_array_equal(actual, expected)
+
+ @pytest.mark.parametrize(
+ "data,reverse,base",
+ [(list("abc"), list("cba"), list("bbb")), ([1, 2, 3], [3, 2, 1], [2, 2, 2])],
+ )
+ def test_comparisons(self, data, reverse, base):
+ cat_rev = Series(Categorical(data, categories=reverse, ordered=True))
+ cat_rev_base = Series(Categorical(base, categories=reverse, ordered=True))
+ cat = Series(Categorical(data, ordered=True))
+ cat_base = Series(
+ Categorical(base, categories=cat.cat.categories, ordered=True)
+ )
+ s = Series(base, dtype=object if base == list("bbb") else None)
+ a = np.array(base)
+
+ # comparisons need to take categories ordering into account
+ res_rev = cat_rev > cat_rev_base
+ exp_rev = Series([True, False, False])
+ tm.assert_series_equal(res_rev, exp_rev)
+
+ res_rev = cat_rev < cat_rev_base
+ exp_rev = Series([False, False, True])
+ tm.assert_series_equal(res_rev, exp_rev)
+
+ res = cat > cat_base
+ exp = Series([False, False, True])
+ tm.assert_series_equal(res, exp)
+
+ scalar = base[1]
+ res = cat > scalar
+ exp = Series([False, False, True])
+ exp2 = cat.values > scalar
+ tm.assert_series_equal(res, exp)
+ tm.assert_numpy_array_equal(res.values, exp2)
+ res_rev = cat_rev > scalar
+ exp_rev = Series([True, False, False])
+ exp_rev2 = cat_rev.values > scalar
+ tm.assert_series_equal(res_rev, exp_rev)
+ tm.assert_numpy_array_equal(res_rev.values, exp_rev2)
+
+ # Only categories with same categories can be compared
+ msg = "Categoricals can only be compared if 'categories' are the same"
+ with pytest.raises(TypeError, match=msg):
+ cat > cat_rev
+
+ # categorical cannot be compared to Series or numpy array, and also
+ # not the other way around
+ msg = (
+ "Cannot compare a Categorical for op __gt__ with type "
+ r""
+ )
+ with pytest.raises(TypeError, match=msg):
+ cat > s
+ with pytest.raises(TypeError, match=msg):
+ cat_rev > s
+ with pytest.raises(TypeError, match=msg):
+ cat > a
+ with pytest.raises(TypeError, match=msg):
+ cat_rev > a
+
+ with pytest.raises(TypeError, match=msg):
+ s < cat
+ with pytest.raises(TypeError, match=msg):
+ s < cat_rev
+
+ with pytest.raises(TypeError, match=msg):
+ a < cat
+ with pytest.raises(TypeError, match=msg):
+ a < cat_rev
+
+ @pytest.mark.parametrize("box", [lambda x: x, Series])
+ def test_unordered_different_order_equal(self, box):
+ # https://github.com/pandas-dev/pandas/issues/16014
+ c1 = box(Categorical(["a", "b"], categories=["a", "b"], ordered=False))
+ c2 = box(Categorical(["a", "b"], categories=["b", "a"], ordered=False))
+ assert (c1 == c2).all()
+
+ c1 = box(Categorical(["a", "b"], categories=["a", "b"], ordered=False))
+ c2 = box(Categorical(["b", "a"], categories=["b", "a"], ordered=False))
+ assert (c1 != c2).all()
+
+ c1 = box(Categorical(["a", "a"], categories=["a", "b"], ordered=False))
+ c2 = box(Categorical(["b", "b"], categories=["b", "a"], ordered=False))
+ assert (c1 != c2).all()
+
+ c1 = box(Categorical(["a", "a"], categories=["a", "b"], ordered=False))
+ c2 = box(Categorical(["a", "b"], categories=["b", "a"], ordered=False))
+ result = c1 == c2
+ tm.assert_numpy_array_equal(np.array(result), np.array([True, False]))
+
+ def test_unordered_different_categories_raises(self):
+ c1 = Categorical(["a", "b"], categories=["a", "b"], ordered=False)
+ c2 = Categorical(["a", "c"], categories=["c", "a"], ordered=False)
+
+ with pytest.raises(TypeError, match=("Categoricals can only be compared")):
+ c1 == c2
+
+ def test_compare_different_lengths(self):
+ c1 = Categorical([], categories=["a", "b"])
+ c2 = Categorical([], categories=["a"])
+
+ msg = "Categoricals can only be compared if 'categories' are the same."
+ with pytest.raises(TypeError, match=msg):
+ c1 == c2
+
+ def test_compare_unordered_different_order(self):
+ # https://github.com/pandas-dev/pandas/issues/16603#issuecomment-
+ # 349290078
+ a = Categorical(["a"], categories=["a", "b"])
+ b = Categorical(["b"], categories=["b", "a"])
+ assert not a.equals(b)
+
+ def test_numeric_like_ops(self):
+ df = DataFrame({"value": np.random.default_rng(2).integers(0, 10000, 100)})
+ labels = [f"{i} - {i + 499}" for i in range(0, 10000, 500)]
+ cat_labels = Categorical(labels, labels)
+
+ df = df.sort_values(by=["value"], ascending=True)
+ df["value_group"] = pd.cut(
+ df.value, range(0, 10500, 500), right=False, labels=cat_labels
+ )
+
+ # numeric ops should not succeed
+ for op, str_rep in [
+ ("__add__", r"\+"),
+ ("__sub__", "-"),
+ ("__mul__", r"\*"),
+ ("__truediv__", "/"),
+ ]:
+ msg = f"Series cannot perform the operation {str_rep}|unsupported operand"
+ with pytest.raises(TypeError, match=msg):
+ getattr(df, op)(df)
+
+ # reduction ops should not succeed (unless specifically defined, e.g.
+ # min/max)
+ s = df["value_group"]
+ for op in ["kurt", "skew", "var", "std", "mean", "sum", "median"]:
+ msg = f"does not support operation '{op}'"
+ with pytest.raises(TypeError, match=msg):
+ getattr(s, op)(numeric_only=False)
+
+ def test_numeric_like_ops_series(self):
+ # numpy ops
+ s = Series(Categorical([1, 2, 3, 4]))
+ with pytest.raises(TypeError, match="does not support operation 'sum'"):
+ np.sum(s)
+
+ @pytest.mark.parametrize(
+ "op, str_rep",
+ [
+ ("__add__", r"\+"),
+ ("__sub__", "-"),
+ ("__mul__", r"\*"),
+ ("__truediv__", "/"),
+ ],
+ )
+ def test_numeric_like_ops_series_arith(self, op, str_rep):
+ # numeric ops on a Series
+ s = Series(Categorical([1, 2, 3, 4]))
+ msg = f"Series cannot perform the operation {str_rep}|unsupported operand"
+ with pytest.raises(TypeError, match=msg):
+ getattr(s, op)(2)
+
+ def test_numeric_like_ops_series_invalid(self):
+ # invalid ufunc
+ s = Series(Categorical([1, 2, 3, 4]))
+ msg = "Object with dtype category cannot perform the numpy op log"
+ with pytest.raises(TypeError, match=msg):
+ np.log(s)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/categorical/test_sorting.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/categorical/test_sorting.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae527065b3fb970263609881d217f5c6d2761231
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/categorical/test_sorting.py
@@ -0,0 +1,128 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ Index,
+)
+import pandas._testing as tm
+
+
+class TestCategoricalSort:
+ def test_argsort(self):
+ c = Categorical([5, 3, 1, 4, 2], ordered=True)
+
+ expected = np.array([2, 4, 1, 3, 0])
+ tm.assert_numpy_array_equal(
+ c.argsort(ascending=True), expected, check_dtype=False
+ )
+
+ expected = expected[::-1]
+ tm.assert_numpy_array_equal(
+ c.argsort(ascending=False), expected, check_dtype=False
+ )
+
+ def test_numpy_argsort(self):
+ c = Categorical([5, 3, 1, 4, 2], ordered=True)
+
+ expected = np.array([2, 4, 1, 3, 0])
+ tm.assert_numpy_array_equal(np.argsort(c), expected, check_dtype=False)
+
+ tm.assert_numpy_array_equal(
+ np.argsort(c, kind="mergesort"), expected, check_dtype=False
+ )
+
+ msg = "the 'axis' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.argsort(c, axis=0)
+
+ msg = "the 'order' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.argsort(c, order="C")
+
+ def test_sort_values(self):
+ # unordered cats are sortable
+ cat = Categorical(["a", "b", "b", "a"], ordered=False)
+ cat.sort_values()
+
+ cat = Categorical(["a", "c", "b", "d"], ordered=True)
+
+ # sort_values
+ res = cat.sort_values()
+ exp = np.array(["a", "b", "c", "d"], dtype=object)
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+ tm.assert_index_equal(res.categories, cat.categories)
+
+ cat = Categorical(
+ ["a", "c", "b", "d"], categories=["a", "b", "c", "d"], ordered=True
+ )
+ res = cat.sort_values()
+ exp = np.array(["a", "b", "c", "d"], dtype=object)
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+ tm.assert_index_equal(res.categories, cat.categories)
+
+ res = cat.sort_values(ascending=False)
+ exp = np.array(["d", "c", "b", "a"], dtype=object)
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+ tm.assert_index_equal(res.categories, cat.categories)
+
+ # sort (inplace order)
+ cat1 = cat.copy()
+ orig_codes = cat1._codes
+ cat1.sort_values(inplace=True)
+ assert cat1._codes is orig_codes
+ exp = np.array(["a", "b", "c", "d"], dtype=object)
+ tm.assert_numpy_array_equal(cat1.__array__(), exp)
+ tm.assert_index_equal(res.categories, cat.categories)
+
+ # reverse
+ cat = Categorical(["a", "c", "c", "b", "d"], ordered=True)
+ res = cat.sort_values(ascending=False)
+ exp_val = np.array(["d", "c", "c", "b", "a"], dtype=object)
+ exp_categories = Index(["a", "b", "c", "d"])
+ tm.assert_numpy_array_equal(res.__array__(), exp_val)
+ tm.assert_index_equal(res.categories, exp_categories)
+
+ def test_sort_values_na_position(self):
+ # see gh-12882
+ cat = Categorical([5, 2, np.nan, 2, np.nan], ordered=True)
+ exp_categories = Index([2, 5])
+
+ exp = np.array([2.0, 2.0, 5.0, np.nan, np.nan])
+ res = cat.sort_values() # default arguments
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+ tm.assert_index_equal(res.categories, exp_categories)
+
+ exp = np.array([np.nan, np.nan, 2.0, 2.0, 5.0])
+ res = cat.sort_values(ascending=True, na_position="first")
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+ tm.assert_index_equal(res.categories, exp_categories)
+
+ exp = np.array([np.nan, np.nan, 5.0, 2.0, 2.0])
+ res = cat.sort_values(ascending=False, na_position="first")
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+ tm.assert_index_equal(res.categories, exp_categories)
+
+ exp = np.array([2.0, 2.0, 5.0, np.nan, np.nan])
+ res = cat.sort_values(ascending=True, na_position="last")
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+ tm.assert_index_equal(res.categories, exp_categories)
+
+ exp = np.array([5.0, 2.0, 2.0, np.nan, np.nan])
+ res = cat.sort_values(ascending=False, na_position="last")
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+ tm.assert_index_equal(res.categories, exp_categories)
+
+ cat = Categorical(["a", "c", "b", "d", np.nan], ordered=True)
+ res = cat.sort_values(ascending=False, na_position="last")
+ exp_val = np.array(["d", "c", "b", "a", np.nan], dtype=object)
+ exp_categories = Index(["a", "b", "c", "d"])
+ tm.assert_numpy_array_equal(res.__array__(), exp_val)
+ tm.assert_index_equal(res.categories, exp_categories)
+
+ cat = Categorical(["a", "c", "b", "d", np.nan], ordered=True)
+ res = cat.sort_values(ascending=False, na_position="first")
+ exp_val = np.array([np.nan, "d", "c", "b", "a"], dtype=object)
+ exp_categories = Index(["a", "b", "c", "d"])
+ tm.assert_numpy_array_equal(res.__array__(), exp_val)
+ tm.assert_index_equal(res.categories, exp_categories)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/datetimes/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/datetimes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/datetimes/test_reductions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/datetimes/test_reductions.py
new file mode 100644
index 0000000000000000000000000000000000000000..74f7d8f50459ffc040aec09f3742a5bb9b641956
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/datetimes/test_reductions.py
@@ -0,0 +1,176 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.dtypes import DatetimeTZDtype
+
+import pandas as pd
+from pandas import NaT
+import pandas._testing as tm
+from pandas.core.arrays import DatetimeArray
+
+
+class TestReductions:
+ @pytest.fixture
+ def arr1d(self, tz_naive_fixture):
+ """Fixture returning DatetimeArray with parametrized timezones"""
+ tz = tz_naive_fixture
+ dtype = DatetimeTZDtype(tz=tz) if tz is not None else np.dtype("M8[ns]")
+ arr = DatetimeArray._from_sequence(
+ [
+ "2000-01-03",
+ "2000-01-03",
+ "NaT",
+ "2000-01-02",
+ "2000-01-05",
+ "2000-01-04",
+ ],
+ dtype=dtype,
+ )
+ return arr
+
+ def test_min_max(self, arr1d, unit):
+ arr = arr1d
+ arr = arr.as_unit(unit)
+ tz = arr.tz
+
+ result = arr.min()
+ expected = pd.Timestamp("2000-01-02", tz=tz).as_unit(unit)
+ assert result == expected
+ assert result.unit == expected.unit
+
+ result = arr.max()
+ expected = pd.Timestamp("2000-01-05", tz=tz).as_unit(unit)
+ assert result == expected
+ assert result.unit == expected.unit
+
+ result = arr.min(skipna=False)
+ assert result is NaT
+
+ result = arr.max(skipna=False)
+ assert result is NaT
+
+ @pytest.mark.parametrize("tz", [None, "US/Central"])
+ def test_min_max_empty(self, skipna, tz):
+ dtype = DatetimeTZDtype(tz=tz) if tz is not None else np.dtype("M8[ns]")
+ arr = DatetimeArray._from_sequence([], dtype=dtype)
+ result = arr.min(skipna=skipna)
+ assert result is NaT
+
+ result = arr.max(skipna=skipna)
+ assert result is NaT
+
+ @pytest.mark.parametrize("tz", [None, "US/Central"])
+ def test_median_empty(self, skipna, tz):
+ dtype = DatetimeTZDtype(tz=tz) if tz is not None else np.dtype("M8[ns]")
+ arr = DatetimeArray._from_sequence([], dtype=dtype)
+ result = arr.median(skipna=skipna)
+ assert result is NaT
+
+ arr = arr.reshape(0, 3)
+ result = arr.median(axis=0, skipna=skipna)
+ expected = type(arr)._from_sequence([NaT, NaT, NaT], dtype=arr.dtype)
+ tm.assert_equal(result, expected)
+
+ result = arr.median(axis=1, skipna=skipna)
+ expected = type(arr)._from_sequence([], dtype=arr.dtype)
+ tm.assert_equal(result, expected)
+
+ def test_median(self, arr1d):
+ arr = arr1d
+
+ result = arr.median()
+ assert result == arr[0]
+ result = arr.median(skipna=False)
+ assert result is NaT
+
+ result = arr.dropna().median(skipna=False)
+ assert result == arr[0]
+
+ result = arr.median(axis=0)
+ assert result == arr[0]
+
+ def test_median_axis(self, arr1d):
+ arr = arr1d
+ assert arr.median(axis=0) == arr.median()
+ assert arr.median(axis=0, skipna=False) is NaT
+
+ msg = r"abs\(axis\) must be less than ndim"
+ with pytest.raises(ValueError, match=msg):
+ arr.median(axis=1)
+
+ @pytest.mark.filterwarnings("ignore:All-NaN slice encountered:RuntimeWarning")
+ def test_median_2d(self, arr1d):
+ arr = arr1d.reshape(1, -1)
+
+ # axis = None
+ assert arr.median() == arr1d.median()
+ assert arr.median(skipna=False) is NaT
+
+ # axis = 0
+ result = arr.median(axis=0)
+ expected = arr1d
+ tm.assert_equal(result, expected)
+
+ # Since column 3 is all-NaT, we get NaT there with or without skipna
+ result = arr.median(axis=0, skipna=False)
+ expected = arr1d
+ tm.assert_equal(result, expected)
+
+ # axis = 1
+ result = arr.median(axis=1)
+ expected = type(arr)._from_sequence([arr1d.median()], dtype=arr.dtype)
+ tm.assert_equal(result, expected)
+
+ result = arr.median(axis=1, skipna=False)
+ expected = type(arr)._from_sequence([NaT], dtype=arr.dtype)
+ tm.assert_equal(result, expected)
+
+ def test_mean(self, arr1d):
+ arr = arr1d
+
+ # manually verified result
+ expected = arr[0] + 0.4 * pd.Timedelta(days=1)
+
+ result = arr.mean()
+ assert result == expected
+ result = arr.mean(skipna=False)
+ assert result is NaT
+
+ result = arr.dropna().mean(skipna=False)
+ assert result == expected
+
+ result = arr.mean(axis=0)
+ assert result == expected
+
+ def test_mean_2d(self):
+ dti = pd.date_range("2016-01-01", periods=6, tz="US/Pacific", unit="ns")
+ dta = dti._data.reshape(3, 2)
+
+ result = dta.mean(axis=0)
+ expected = dta[1]
+ tm.assert_datetime_array_equal(result, expected)
+
+ result = dta.mean(axis=1)
+ expected = dta[:, 0] + pd.Timedelta(hours=12)
+ tm.assert_datetime_array_equal(result, expected)
+
+ result = dta.mean(axis=None)
+ expected = dti.mean()
+ assert result == expected
+
+ def test_mean_empty(self, arr1d, skipna):
+ arr = arr1d[:0]
+
+ assert arr.mean(skipna=skipna) is NaT
+
+ arr2d = arr.reshape(0, 3)
+ result = arr2d.mean(axis=0, skipna=skipna)
+ expected = DatetimeArray._from_sequence([NaT, NaT, NaT], dtype=arr.dtype)
+ tm.assert_datetime_array_equal(result, expected)
+
+ result = arr2d.mean(axis=1, skipna=skipna)
+ expected = arr # i.e. 1D, empty
+ tm.assert_datetime_array_equal(result, expected)
+
+ result = arr2d.mean(axis=None, skipna=skipna)
+ assert result is NaT
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..95e544750be19de165e843655708e82669488030
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/conftest.py
@@ -0,0 +1,67 @@
+import pytest
+
+import pandas as pd
+from pandas.core.arrays.integer import (
+ Int8Dtype,
+ Int16Dtype,
+ Int32Dtype,
+ Int64Dtype,
+ UInt8Dtype,
+ UInt16Dtype,
+ UInt32Dtype,
+ UInt64Dtype,
+)
+
+
+@pytest.fixture(
+ params=[
+ Int8Dtype,
+ Int16Dtype,
+ Int32Dtype,
+ Int64Dtype,
+ UInt8Dtype,
+ UInt16Dtype,
+ UInt32Dtype,
+ UInt64Dtype,
+ ]
+)
+def dtype(request):
+ """Parametrized fixture returning integer 'dtype'"""
+ return request.param()
+
+
+@pytest.fixture
+def data(dtype):
+ """
+ Fixture returning 'data' array with valid and missing values according to
+ parametrized integer 'dtype'.
+
+ Used to test dtype conversion with and without missing values.
+ """
+ return pd.array(
+ [0, 1, 2, 3, pd.NA, 10, 11, pd.NA, 99, 100],
+ dtype=dtype,
+ )
+
+
+@pytest.fixture
+def data_missing(dtype):
+ """
+ Fixture returning array with exactly one NaN and one valid integer,
+ according to parametrized integer 'dtype'.
+
+ Used to test dtype conversion with and without missing values.
+ """
+ return pd.array([pd.NA, 1], dtype=dtype)
+
+
+@pytest.fixture(params=["data", "data_missing"])
+def all_data(request, data, data_missing):
+ """Parametrized fixture returning 'data' or 'data_missing' integer arrays.
+
+ Used to test dtype conversion with and without missing values.
+ """
+ if request.param == "data":
+ return data
+ elif request.param == "data_missing":
+ return data_missing
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_arithmetic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_arithmetic.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d17fe2505f596fe35c1fba0b4a242dcec186b8a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_arithmetic.py
@@ -0,0 +1,355 @@
+import operator
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core import ops
+from pandas.core.arrays import FloatingArray
+
+# Basic test for the arithmetic array ops
+# -----------------------------------------------------------------------------
+
+
+@pytest.mark.parametrize(
+ "opname, exp",
+ [("add", [1, 3, None, None, 9]), ("mul", [0, 2, None, None, 20])],
+ ids=["add", "mul"],
+)
+def test_add_mul(dtype, opname, exp):
+ a = pd.array([0, 1, None, 3, 4], dtype=dtype)
+ b = pd.array([1, 2, 3, None, 5], dtype=dtype)
+
+ # array / array
+ expected = pd.array(exp, dtype=dtype)
+
+ op = getattr(operator, opname)
+ result = op(a, b)
+ tm.assert_extension_array_equal(result, expected)
+
+ op = getattr(ops, "r" + opname)
+ result = op(a, b)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_sub(dtype):
+ a = pd.array([1, 2, 3, None, 5], dtype=dtype)
+ b = pd.array([0, 1, None, 3, 4], dtype=dtype)
+
+ result = a - b
+ expected = pd.array([1, 1, None, None, 1], dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_div(dtype):
+ a = pd.array([1, 2, 3, None, 5], dtype=dtype)
+ b = pd.array([0, 1, None, 3, 4], dtype=dtype)
+
+ result = a / b
+ expected = pd.array([np.inf, 2, None, None, 1.25], dtype="Float64")
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("zero, negative", [(0, False), (0.0, False), (-0.0, True)])
+def test_divide_by_zero(zero, negative, using_nan_is_na):
+ # https://github.com/pandas-dev/pandas/issues/27398, GH#22793
+ a = pd.array([0, 1, -1, None], dtype="Int64")
+ result = a / zero
+ exp_mask = np.array([False, False, False, True])
+ if using_nan_is_na:
+ exp_mask[0] = True
+ expected = FloatingArray(
+ np.array([np.nan, np.inf, -np.inf, 1], dtype="float64"),
+ exp_mask,
+ )
+ if negative:
+ expected *= -1
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_floordiv(dtype):
+ a = pd.array([1, 2, 3, None, 5], dtype=dtype)
+ b = pd.array([0, 1, None, 3, 4], dtype=dtype)
+
+ result = a // b
+ # Series op sets 1//0 to np.inf, which IntegerArray does not do (yet)
+ expected = pd.array([0, 2, None, None, 1], dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_floordiv_by_int_zero_no_mask(any_int_ea_dtype):
+ # GH 48223: Aligns with non-masked floordiv
+ # but differs from numpy
+ # https://github.com/pandas-dev/pandas/issues/30188#issuecomment-564452740
+ ser = pd.Series([0, 1], dtype=any_int_ea_dtype)
+ result = 1 // ser
+ expected = pd.Series([np.inf, 1.0], dtype="Float64")
+ tm.assert_series_equal(result, expected)
+
+ ser_non_nullable = ser.astype(ser.dtype.numpy_dtype)
+ result = 1 // ser_non_nullable
+ expected = expected.astype(np.float64)
+ tm.assert_series_equal(result, expected)
+
+
+def test_mod(dtype):
+ a = pd.array([1, 2, 3, None, 5], dtype=dtype)
+ b = pd.array([0, 1, None, 3, 4], dtype=dtype)
+
+ result = a % b
+ expected = pd.array([0, 0, None, None, 1], dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_pow_scalar(using_nan_is_na):
+ a = pd.array([-1, 0, 1, None, 2], dtype="Int64")
+ result = a**0
+ expected = pd.array([1, 1, 1, 1, 1], dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+
+ result = a**1
+ expected = pd.array([-1, 0, 1, None, 2], dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+
+ result = a**pd.NA
+ expected = pd.array([None, None, 1, None, None], dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+
+ result = a**np.nan
+ if using_nan_is_na:
+ expected = expected.astype("Float64")
+ else:
+ expected = FloatingArray(
+ np.array([np.nan, np.nan, 1, np.nan, np.nan], dtype="float64"),
+ np.array([False, False, False, True, False]),
+ )
+ tm.assert_extension_array_equal(result, expected)
+
+ # reversed
+ a = a[1:] # Can't raise integers to negative powers.
+
+ result = 0**a
+ expected = pd.array([1, 0, None, 0], dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+
+ result = 1**a
+ expected = pd.array([1, 1, 1, 1], dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+
+ result = pd.NA**a
+ expected = pd.array([1, None, None, None], dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+
+ result = np.nan**a
+ if using_nan_is_na:
+ expected = expected.astype("Float64")
+ else:
+ expected = FloatingArray(
+ np.array([1, np.nan, np.nan, np.nan], dtype="float64"),
+ np.array([False, False, True, False]),
+ )
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_pow_array():
+ a = pd.array([0, 0, 0, 1, 1, 1, None, None, None])
+ b = pd.array([0, 1, None, 0, 1, None, 0, 1, None])
+ result = a**b
+ expected = pd.array([1, 0, None, 1, 1, 1, 1, None, None])
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_rpow_one_to_na():
+ # https://github.com/pandas-dev/pandas/issues/22022
+ # https://github.com/pandas-dev/pandas/issues/29997
+ arr = pd.array([pd.NA, pd.NA], dtype="Int64")
+ result = np.array([1.0, 2.0]) ** arr
+ expected = pd.array([1.0, pd.NA], dtype="Float64")
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("other", [0, 0.5])
+def test_numpy_zero_dim_ndarray(other):
+ arr = pd.array([1, None, 2])
+ result = arr + np.array(other)
+ expected = arr + other
+ tm.assert_equal(result, expected)
+
+
+# Test generic characteristics / errors
+# -----------------------------------------------------------------------------
+
+
+def test_error_invalid_values(data, all_arithmetic_operators):
+ op = all_arithmetic_operators
+ s = pd.Series(data)
+ ops = getattr(s, op)
+
+ # invalid scalars
+ with tm.external_error_raised(TypeError):
+ ops("foo")
+ with tm.external_error_raised(TypeError):
+ ops(pd.Timestamp("20180101"))
+
+ # invalid array-likes
+ str_ser = pd.Series("foo", index=s.index)
+ # with pytest.raises(TypeError, match=msg):
+ if all_arithmetic_operators in [
+ "__mul__",
+ "__rmul__",
+ ]: # (data[~data.isna()] >= 0).all():
+ res = ops(str_ser)
+ expected = pd.Series(["foo" * x for x in data], index=s.index)
+ tm.assert_series_equal(res, expected)
+ else:
+ with tm.external_error_raised(TypeError):
+ ops(str_ser)
+
+ with tm.external_error_raised(TypeError):
+ ops(pd.Series(pd.date_range("20180101", periods=len(s))))
+
+
+# Various
+# -----------------------------------------------------------------------------
+
+
+# TODO test unsigned overflow
+
+
+def test_arith_coerce_scalar(data, all_arithmetic_operators, using_nan_is_na):
+ op = tm.get_op_from_name(all_arithmetic_operators)
+ s = pd.Series(data)
+ other = 0.01
+
+ result = op(s, other)
+ expected = op(s.astype(float), other)
+ expected = expected.astype("Float64")
+ if not using_nan_is_na:
+ expected[s.isna()] = pd.NA
+
+ # rmod results in NaN that wasn't NA in original nullable Series -> unmask it
+ if all_arithmetic_operators == "__rmod__" and not using_nan_is_na:
+ mask = (s == 0).fillna(False).to_numpy(bool)
+ expected.array._mask[mask] = False
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("other", [1.0, np.array(1.0)])
+def test_arithmetic_conversion(all_arithmetic_operators, other):
+ # if we have a float operand we should have a float result
+ # if that is equal to an integer
+ op = tm.get_op_from_name(all_arithmetic_operators)
+
+ s = pd.Series([1, 2, 3], dtype="Int64")
+ result = op(s, other)
+ assert result.dtype == "Float64"
+
+
+def test_cross_type_arithmetic():
+ df = pd.DataFrame(
+ {
+ "A": pd.Series([1, 2, pd.NA], dtype="Int64"),
+ "B": pd.Series([1, pd.NA, 3], dtype="UInt8"),
+ "C": [1, 2, 3],
+ }
+ )
+
+ result = df.A + df.C
+ expected = pd.Series([2, 4, pd.NA], dtype="Int64")
+ tm.assert_series_equal(result, expected)
+
+ result = (df.A + df.C) * 3 == 12
+ expected = pd.Series([False, True, None], dtype="boolean")
+ tm.assert_series_equal(result, expected)
+
+ result = df.A + df.B
+ expected = pd.Series([2, pd.NA, pd.NA], dtype="Int64")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("op", ["mean"])
+def test_reduce_to_float(op, using_python_scalars):
+ # some reduce ops always return float, even if the result
+ # is a rounded number
+ df = pd.DataFrame(
+ {
+ "A": ["a", "b", "b"],
+ "B": [1, None, 3],
+ "C": pd.array([1, None, 3], dtype="Int64"),
+ }
+ )
+
+ # op
+ result = getattr(df.C, op)()
+ if using_python_scalars:
+ assert type(result) == float
+ else:
+ assert isinstance(result, np.float64)
+
+ # groupby
+ result = getattr(df.groupby("A"), op)()
+
+ expected = pd.DataFrame(
+ {"B": np.array([1.0, 3.0]), "C": pd.array([1, 3], dtype="Float64")},
+ index=pd.Index(["a", "b"], name="A"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "source, neg_target, abs_target",
+ [
+ ([1, 2, 3], [-1, -2, -3], [1, 2, 3]),
+ ([1, 2, None], [-1, -2, None], [1, 2, None]),
+ ([-1, 0, 1], [1, 0, -1], [1, 0, 1]),
+ ],
+)
+def test_unary_int_operators(any_signed_int_ea_dtype, source, neg_target, abs_target):
+ dtype = any_signed_int_ea_dtype
+ arr = pd.array(source, dtype=dtype)
+ neg_result, pos_result, abs_result = -arr, +arr, abs(arr)
+ neg_target = pd.array(neg_target, dtype=dtype)
+ abs_target = pd.array(abs_target, dtype=dtype)
+
+ tm.assert_extension_array_equal(neg_result, neg_target)
+ tm.assert_extension_array_equal(pos_result, arr)
+ assert not tm.shares_memory(pos_result, arr)
+ tm.assert_extension_array_equal(abs_result, abs_target)
+
+
+def test_values_multiplying_large_series_by_NA():
+ # GH#33701
+
+ result = pd.NA * pd.Series(np.zeros(10001))
+ expected = pd.Series([pd.NA] * 10001)
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_bitwise(dtype):
+ left = pd.array([1, None, 3, 4], dtype=dtype)
+ right = pd.array([None, 3, 5, 4], dtype=dtype)
+
+ result = left | right
+ expected = pd.array([None, None, 3 | 5, 4 | 4], dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+ result = left & right
+ expected = pd.array([None, None, 3 & 5, 4 & 4], dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+ result = left ^ right
+ expected = pd.array([None, None, 3 ^ 5, 4 ^ 4], dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+ # TODO: desired behavior when operating with boolean? defer?
+
+ floats = right.astype("Float64")
+ with pytest.raises(TypeError, match="unsupported operand type"):
+ left | floats
+ with pytest.raises(TypeError, match="unsupported operand type"):
+ left & floats
+ with pytest.raises(TypeError, match="unsupported operand type"):
+ left ^ floats
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_comparison.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_comparison.py
new file mode 100644
index 0000000000000000000000000000000000000000..568b0b087bf1db9610960dba12ea2e0bab8f1729
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_comparison.py
@@ -0,0 +1,39 @@
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.tests.arrays.masked_shared import (
+ ComparisonOps,
+ NumericOps,
+)
+
+
+class TestComparisonOps(NumericOps, ComparisonOps):
+ @pytest.mark.parametrize("other", [True, False, pd.NA, -1, 0, 1])
+ def test_scalar(self, other, comparison_op, dtype):
+ ComparisonOps.test_scalar(self, other, comparison_op, dtype)
+
+ def test_compare_to_int(self, dtype, comparison_op):
+ # GH 28930
+ op_name = f"__{comparison_op.__name__}__"
+ s1 = pd.Series([1, None, 3], dtype=dtype)
+ s2 = pd.Series([1, None, 3], dtype="float")
+
+ method = getattr(s1, op_name)
+ result = method(2)
+
+ method = getattr(s2, op_name)
+ expected = method(2).astype("boolean")
+ expected[s2.isna()] = pd.NA
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_equals():
+ # GH-30652
+ # equals is generally tested in /tests/extension/base/methods, but this
+ # specifically tests that two arrays of the same class but different dtype
+ # do not evaluate equal
+ a1 = pd.array([1, 2, None], dtype="Int64")
+ a2 = pd.array([1, 2, None], dtype="Int32")
+ assert a1.equals(a2) is False
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_concat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_concat.py
new file mode 100644
index 0000000000000000000000000000000000000000..feba574da548fd597c25103f67821145bccec9ed
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_concat.py
@@ -0,0 +1,69 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "to_concat_dtypes, result_dtype",
+ [
+ (["Int64", "Int64"], "Int64"),
+ (["UInt64", "UInt64"], "UInt64"),
+ (["Int8", "Int8"], "Int8"),
+ (["Int8", "Int16"], "Int16"),
+ (["UInt8", "Int8"], "Int16"),
+ (["Int32", "UInt32"], "Int64"),
+ (["Int64", "UInt64"], "Float64"),
+ (["Int64", "boolean"], "object"),
+ (["UInt8", "boolean"], "object"),
+ ],
+)
+def test_concat_series(to_concat_dtypes, result_dtype):
+ # we expect the same dtypes as we would get with non-masked inputs,
+ # just masked where available.
+
+ result = pd.concat([pd.Series([0, 1, pd.NA], dtype=t) for t in to_concat_dtypes])
+ expected = pd.concat([pd.Series([0, 1, pd.NA], dtype=object)] * 2).astype(
+ result_dtype
+ )
+ tm.assert_series_equal(result, expected)
+
+ # order doesn't matter for result
+ result = pd.concat(
+ [pd.Series([0, 1, pd.NA], dtype=t) for t in to_concat_dtypes[::-1]]
+ )
+ expected = pd.concat([pd.Series([0, 1, pd.NA], dtype=object)] * 2).astype(
+ result_dtype
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "to_concat_dtypes, result_dtype",
+ [
+ (["Int64", "int64"], "Int64"),
+ (["UInt64", "uint64"], "UInt64"),
+ (["Int8", "int8"], "Int8"),
+ (["Int8", "int16"], "Int16"),
+ (["UInt8", "int8"], "Int16"),
+ (["Int32", "uint32"], "Int64"),
+ (["Int64", "uint64"], "Float64"),
+ (["Int64", "bool"], "object"),
+ (["UInt8", "bool"], "object"),
+ ],
+)
+def test_concat_series_with_numpy(to_concat_dtypes, result_dtype):
+ # we expect the same dtypes as we would get with non-masked inputs,
+ # just masked where available.
+
+ s1 = pd.Series([0, 1, pd.NA], dtype=to_concat_dtypes[0])
+ s2 = pd.Series(np.array([0, 1], dtype=to_concat_dtypes[1]))
+ result = pd.concat([s1, s2], ignore_index=True)
+ expected = pd.Series([0, 1, pd.NA, 0, 1], dtype=object).astype(result_dtype)
+ tm.assert_series_equal(result, expected)
+
+ # order doesn't matter for result
+ result = pd.concat([s2, s1], ignore_index=True)
+ expected = pd.Series([0, 1, 0, 1, pd.NA], dtype=object).astype(result_dtype)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_construction.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_construction.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e164ff36d1336369d727806671fae75b0ae12bb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_construction.py
@@ -0,0 +1,273 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.types import is_integer
+from pandas.core.arrays import IntegerArray
+from pandas.core.arrays.integer import (
+ Int8Dtype,
+ Int32Dtype,
+ Int64Dtype,
+)
+
+
+@pytest.fixture(params=[pd.array, IntegerArray._from_sequence])
+def constructor(request):
+ """Fixture returning parametrized IntegerArray from given sequence.
+
+ Used to test dtype conversions.
+ """
+ return request.param
+
+
+def test_uses_pandas_na():
+ a = pd.array([1, None], dtype=Int64Dtype())
+ assert a[1] is pd.NA
+
+
+def test_from_dtype_from_float(data, using_nan_is_na):
+ # construct from our dtype & string dtype
+ dtype = data.dtype
+
+ # from float
+ expected = pd.Series(data)
+ arr = data.to_numpy(na_value=np.nan, dtype="float")
+ if using_nan_is_na:
+ result = pd.Series(arr, dtype=str(dtype))
+ tm.assert_series_equal(result, expected)
+ else:
+ msg = "Cannot cast NaN value to Integer dtype"
+ with pytest.raises(ValueError, match=msg):
+ pd.Series(arr, dtype=str(dtype))
+
+ # from int / list
+ expected = pd.Series(data)
+ result = pd.Series(np.array(data).tolist(), dtype=str(dtype))
+ tm.assert_series_equal(result, expected)
+
+ # from int / array
+ expected = pd.Series(data).dropna().reset_index(drop=True)
+ dropped = np.array(data.dropna()).astype(np.dtype(dtype.type))
+ result = pd.Series(dropped, dtype=str(dtype))
+ tm.assert_series_equal(result, expected)
+
+
+def test_conversions(data_missing):
+ # astype to object series
+ df = pd.DataFrame({"A": data_missing})
+ result = df["A"].astype("object")
+ expected = pd.Series(np.array([pd.NA, 1], dtype=object), name="A")
+ tm.assert_series_equal(result, expected)
+
+ # convert to object ndarray
+ # we assert that we are exactly equal
+ # including type conversions of scalars
+ result = df["A"].astype("object").values
+ expected = np.array([pd.NA, 1], dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ for r, e in zip(result, expected, strict=True):
+ if pd.isnull(r):
+ assert pd.isnull(e)
+ elif is_integer(r):
+ assert r == e
+ assert is_integer(e)
+ else:
+ assert r == e
+ assert type(r) == type(e)
+
+
+def test_integer_array_constructor():
+ values = np.array([1, 2, 3, 4], dtype="int64")
+ mask = np.array([False, False, False, True], dtype="bool")
+
+ result = IntegerArray(values, mask)
+ expected = pd.array([1, 2, 3, pd.NA], dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+
+ msg = r".* should be .* numpy array. Use the 'pd.array' function instead"
+ with pytest.raises(TypeError, match=msg):
+ IntegerArray(values.tolist(), mask)
+
+ with pytest.raises(TypeError, match=msg):
+ IntegerArray(values, mask.tolist())
+
+ with pytest.raises(TypeError, match=msg):
+ IntegerArray(values.astype(float), mask)
+ msg = r"__init__\(\) missing 1 required positional argument: 'mask'"
+ with pytest.raises(TypeError, match=msg):
+ IntegerArray(values)
+
+
+def test_integer_array_constructor_copy():
+ values = np.array([1, 2, 3, 4], dtype="int64")
+ mask = np.array([False, False, False, True], dtype="bool")
+
+ result = IntegerArray(values, mask)
+ assert result._data is values
+ assert result._mask is mask
+
+ result = IntegerArray(values, mask, copy=True)
+ assert result._data is not values
+ assert result._mask is not mask
+
+
+@pytest.mark.parametrize(
+ "a, b",
+ [
+ ([1, None], [1, np.nan]),
+ ([None], [np.nan]),
+ ([None, np.nan], [np.nan, np.nan]),
+ ([np.nan, np.nan], [np.nan, np.nan]),
+ ],
+)
+def test_to_integer_array_none_is_nan(a, b, using_nan_is_na):
+ if using_nan_is_na:
+ result = pd.array(a, dtype="Int64")
+ expected = pd.array(b, dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+ else:
+ msg = "Cannot cast NaN value to Integer dtype"
+ with pytest.raises(ValueError, match=msg):
+ pd.array(b, dtype="Int64")
+
+
+@pytest.mark.parametrize(
+ "values",
+ [
+ ["foo", "bar"],
+ "foo",
+ 1,
+ 1.0,
+ pd.date_range("20130101", periods=2),
+ np.array(["foo"]),
+ [[1, 2], [3, 4]],
+ [np.nan, {"a": 1}],
+ ],
+)
+def test_to_integer_array_error(values):
+ # error in converting existing arrays to IntegerArrays
+ msg = "|".join(
+ [
+ "cannot convert float NaN to integer", # with not using_nan_is_na
+ r"cannot be converted to IntegerDtype",
+ r"invalid literal for int\(\) with base 10:",
+ r"values must be a 1D list-like",
+ r"Cannot pass scalar",
+ r"int\(\) argument must be a string",
+ ]
+ )
+ with pytest.raises((ValueError, TypeError), match=msg):
+ pd.array(values, dtype="Int64")
+
+ with pytest.raises((ValueError, TypeError), match=msg):
+ IntegerArray._from_sequence(values)
+
+
+def test_to_integer_array_inferred_dtype(constructor):
+ # if values has dtype -> respect it
+ result = constructor(np.array([1, 2], dtype="int8"))
+ assert result.dtype == Int8Dtype()
+ result = constructor(np.array([1, 2], dtype="int32"))
+ assert result.dtype == Int32Dtype()
+
+ # if values have no dtype -> always int64
+ result = constructor([1, 2])
+ assert result.dtype == Int64Dtype()
+
+
+def test_to_integer_array_dtype_keyword(constructor):
+ result = constructor([1, 2], dtype="Int8")
+ assert result.dtype == Int8Dtype()
+
+ # if values has dtype -> override it
+ result = constructor(np.array([1, 2], dtype="int8"), dtype="Int32")
+ assert result.dtype == Int32Dtype()
+
+
+def test_to_integer_array_float():
+ result = IntegerArray._from_sequence([1.0, 2.0], dtype="Int64")
+ expected = pd.array([1, 2], dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+
+ with pytest.raises(TypeError, match="cannot safely cast non-equivalent"):
+ IntegerArray._from_sequence([1.5, 2.0], dtype="Int64")
+
+ # for float dtypes, the itemsize is not preserved
+ result = IntegerArray._from_sequence(
+ np.array([1.0, 2.0], dtype="float32"), dtype="Int64"
+ )
+ assert result.dtype == Int64Dtype()
+
+
+def test_to_integer_array_str():
+ result = IntegerArray._from_sequence(["1", "2", None], dtype="Int64")
+ expected = pd.array([1, 2, pd.NA], dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
+
+ with pytest.raises(
+ ValueError, match=r"invalid literal for int\(\) with base 10: .*"
+ ):
+ IntegerArray._from_sequence(["1", "2", ""], dtype="Int64")
+
+ with pytest.raises(
+ ValueError, match=r"invalid literal for int\(\) with base 10: .*"
+ ):
+ IntegerArray._from_sequence(["1.5", "2.0"], dtype="Int64")
+
+
+@pytest.mark.parametrize(
+ "bool_values, int_values, target_dtype, expected_dtype",
+ [
+ ([False, True], [0, 1], Int64Dtype(), Int64Dtype()),
+ ([False, True], [0, 1], "Int64", Int64Dtype()),
+ ([False, True, np.nan], [0, 1, np.nan], Int64Dtype(), Int64Dtype()),
+ ],
+)
+def test_to_integer_array_bool(
+ constructor, bool_values, int_values, target_dtype, expected_dtype, using_nan_is_na
+):
+ if not using_nan_is_na and np.isnan(bool_values[-1]):
+ msg = "Cannot cast NaN value to Integer dtype"
+ with pytest.raises(ValueError, match=msg):
+ constructor(bool_values, dtype=target_dtype)
+ with pytest.raises(ValueError, match=msg):
+ pd.array(int_values, dtype=target_dtype)
+ return
+
+ result = constructor(bool_values, dtype=target_dtype)
+ assert result.dtype == expected_dtype
+ expected = pd.array(int_values, dtype=target_dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "values, to_dtype, result_dtype",
+ [
+ (np.array([1], dtype="int64"), None, Int64Dtype),
+ (np.array([1, np.nan]), None, Int64Dtype),
+ (np.array([1, np.nan]), "int8", Int8Dtype),
+ ],
+)
+def test_to_integer_array(values, to_dtype, result_dtype, using_nan_is_na):
+ # convert existing arrays to IntegerArrays
+ if not using_nan_is_na and np.isnan(values[-1]):
+ msg = "Cannot cast NaN value to Integer dtype"
+ with pytest.raises(ValueError, match=msg):
+ IntegerArray._from_sequence(values, dtype=to_dtype)
+ with pytest.raises(ValueError, match=msg):
+ pd.array(values, dtype=result_dtype())
+ return
+
+ result = IntegerArray._from_sequence(values, dtype=to_dtype)
+ assert result.dtype == result_dtype()
+ expected = pd.array(values, dtype=result_dtype())
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_integer_array_from_boolean():
+ # GH31104
+ expected = pd.array(np.array([True, False]), dtype="Int64")
+ result = pd.array(np.array([True, False], dtype=object), dtype="Int64")
+ tm.assert_extension_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_dtypes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_dtypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c4dc1d5450a3b51a40dd8338f22de253ab110b0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_dtypes.py
@@ -0,0 +1,315 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.generic import ABCIndex
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.arrays.integer import (
+ Int8Dtype,
+ UInt32Dtype,
+)
+
+
+def test_dtypes(dtype):
+ # smoke tests on auto dtype construction
+
+ if dtype.is_signed_integer:
+ assert np.dtype(dtype.type).kind == "i"
+ else:
+ assert np.dtype(dtype.type).kind == "u"
+ assert dtype.name is not None
+
+
+@pytest.mark.parametrize("op", ["sum", "min", "max", "prod"])
+def test_preserve_dtypes(op, using_python_scalars):
+ # for ops that enable (mean would actually work here
+ # but generally it is a float return value)
+ df = pd.DataFrame(
+ {
+ "A": ["a", "b", "b"],
+ "B": [1, None, 3],
+ "C": pd.array([1, None, 3], dtype="Int64"),
+ }
+ )
+
+ # op
+ result = getattr(df.C, op)()
+ if op in {"sum", "prod", "min", "max"} and not using_python_scalars:
+ assert isinstance(result, np.int64)
+ else:
+ assert isinstance(result, int)
+
+ # groupby
+ result = getattr(df.groupby("A"), op)()
+
+ expected = pd.DataFrame(
+ {"B": np.array([1.0, 3.0]), "C": pd.array([1, 3], dtype="Int64")},
+ index=pd.Index(["a", "b"], name="A"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_astype_nansafe():
+ # see gh-22343
+ arr = pd.array([pd.NA, 1, 2], dtype="Int8")
+ msg = "cannot convert NA to integer"
+
+ with pytest.raises(ValueError, match=msg):
+ arr.astype("uint32")
+
+
+def test_construct_index(all_data, dropna):
+ # ensure that we do not coerce to different Index dtype or non-index
+
+ all_data = all_data[:10]
+ if dropna:
+ other = np.array(all_data[~all_data.isna()])
+ else:
+ other = all_data
+
+ result = pd.Index(pd.array(other, dtype=all_data.dtype))
+ expected = pd.Index(other, dtype=all_data.dtype)
+ assert all_data.dtype == expected.dtype # dont coerce to object
+
+ tm.assert_index_equal(result, expected)
+
+
+def test_astype_index(all_data, dropna):
+ # as an int/uint index to Index
+
+ all_data = all_data[:10]
+ if dropna:
+ other = all_data[~all_data.isna()]
+ else:
+ other = all_data
+
+ dtype = all_data.dtype
+ idx = pd.Index(np.array(other))
+ assert isinstance(idx, ABCIndex)
+
+ result = idx.astype(dtype)
+ expected = idx.astype(object).astype(dtype)
+ tm.assert_index_equal(result, expected)
+
+
+def test_astype(all_data):
+ all_data = all_data[:10]
+
+ ints = all_data[~all_data.isna()]
+ mixed = all_data
+ dtype = Int8Dtype()
+
+ # coerce to same type - ints
+ s = pd.Series(ints)
+ result = s.astype(all_data.dtype)
+ expected = pd.Series(ints)
+ tm.assert_series_equal(result, expected)
+
+ # coerce to same other - ints
+ s = pd.Series(ints)
+ result = s.astype(dtype)
+ expected = pd.Series(ints, dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ # coerce to same numpy_dtype - ints
+ s = pd.Series(ints)
+ result = s.astype(all_data.dtype.numpy_dtype)
+ expected = pd.Series(ints._data.astype(all_data.dtype.numpy_dtype))
+ tm.assert_series_equal(result, expected)
+
+ # coerce to same type - mixed
+ s = pd.Series(mixed)
+ result = s.astype(all_data.dtype)
+ expected = pd.Series(mixed)
+ tm.assert_series_equal(result, expected)
+
+ # coerce to same other - mixed
+ s = pd.Series(mixed)
+ result = s.astype(dtype)
+ expected = pd.Series(mixed, dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ # coerce to same numpy_dtype - mixed
+ s = pd.Series(mixed)
+ msg = "cannot convert NA to integer"
+ with pytest.raises(ValueError, match=msg):
+ s.astype(all_data.dtype.numpy_dtype)
+
+ # coerce to object
+ s = pd.Series(mixed)
+ result = s.astype("object")
+ expected = pd.Series(np.asarray(mixed, dtype=object))
+ tm.assert_series_equal(result, expected)
+
+
+def test_astype_copy():
+ arr = pd.array([1, 2, 3, None], dtype="Int64")
+ orig = pd.array([1, 2, 3, None], dtype="Int64")
+
+ # copy=True -> ensure both data and mask are actual copies
+ result = arr.astype("Int64", copy=True)
+ assert result is not arr
+ assert not tm.shares_memory(result, arr)
+ result[0] = 10
+ tm.assert_extension_array_equal(arr, orig)
+ result[0] = pd.NA
+ tm.assert_extension_array_equal(arr, orig)
+
+ # copy=False
+ result = arr.astype("Int64", copy=False)
+ assert result is arr
+ assert np.shares_memory(result._data, arr._data)
+ assert np.shares_memory(result._mask, arr._mask)
+ result[0] = 10
+ assert arr[0] == 10
+ result[0] = pd.NA
+ assert arr[0] is pd.NA
+
+ # astype to different dtype -> always needs a copy -> even with copy=False
+ # we need to ensure that also the mask is actually copied
+ arr = pd.array([1, 2, 3, None], dtype="Int64")
+ orig = pd.array([1, 2, 3, None], dtype="Int64")
+
+ result = arr.astype("Int32", copy=False)
+ assert not tm.shares_memory(result, arr)
+ result[0] = 10
+ tm.assert_extension_array_equal(arr, orig)
+ result[0] = pd.NA
+ tm.assert_extension_array_equal(arr, orig)
+
+
+def test_astype_to_larger_numpy():
+ a = pd.array([1, 2], dtype="Int32")
+ result = a.astype("int64")
+ expected = np.array([1, 2], dtype="int64")
+ tm.assert_numpy_array_equal(result, expected)
+
+ a = pd.array([1, 2], dtype="UInt32")
+ result = a.astype("uint64")
+ expected = np.array([1, 2], dtype="uint64")
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", [Int8Dtype(), "Int8", UInt32Dtype(), "UInt32"])
+def test_astype_specific_casting(dtype):
+ s = pd.Series([1, 2, 3], dtype="Int64")
+ result = s.astype(dtype)
+ expected = pd.Series([1, 2, 3], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ s = pd.Series([1, 2, 3, None], dtype="Int64")
+ result = s.astype(dtype)
+ expected = pd.Series([1, 2, 3, None], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_astype_floating():
+ arr = pd.array([1, 2, None], dtype="Int64")
+ result = arr.astype("Float64")
+ expected = pd.array([1.0, 2.0, None], dtype="Float64")
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_astype_dt64():
+ # GH#32435
+ arr = pd.array([1, 2, 3, pd.NA]) * 10**9
+
+ result = arr.astype("datetime64[ns]")
+
+ expected = np.array([1, 2, 3, "NaT"], dtype="M8[s]").astype("M8[ns]")
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_construct_cast_invalid(dtype):
+ msg = "cannot safely"
+ arr = [1.2, 2.3, 3.7]
+ with pytest.raises(TypeError, match=msg):
+ pd.array(arr, dtype=dtype)
+
+ with pytest.raises(TypeError, match=msg):
+ pd.Series(arr).astype(dtype)
+
+ arr = [1.2, 2.3, 3.7, pd.NA]
+ with pytest.raises(TypeError, match=msg):
+ pd.array(arr, dtype=dtype)
+
+ with pytest.raises(TypeError, match=msg):
+ pd.Series(arr).astype(dtype)
+
+
+@pytest.mark.parametrize("in_series", [True, False])
+def test_to_numpy_na_nan(in_series):
+ a = pd.array([0, 1, None], dtype="Int64")
+ if in_series:
+ a = pd.Series(a)
+
+ result = a.to_numpy(dtype="float64", na_value=np.nan)
+ expected = np.array([0.0, 1.0, np.nan], dtype="float64")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = a.to_numpy(dtype="int64", na_value=-1)
+ expected = np.array([0, 1, -1], dtype="int64")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = a.to_numpy(dtype="bool", na_value=False)
+ expected = np.array([False, True, False], dtype="bool")
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("in_series", [True, False])
+@pytest.mark.parametrize("dtype", ["int32", "int64", "bool"])
+def test_to_numpy_dtype(dtype, in_series):
+ a = pd.array([0, 1], dtype="Int64")
+ if in_series:
+ a = pd.Series(a)
+
+ result = a.to_numpy(dtype=dtype)
+ expected = np.array([0, 1], dtype=dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["int64", "bool"])
+def test_to_numpy_na_raises(dtype):
+ a = pd.array([0, 1, None], dtype="Int64")
+ with pytest.raises(ValueError, match=dtype):
+ a.to_numpy(dtype=dtype)
+
+
+def test_to_numpy_readonly():
+ arr = pd.array([0, 1], dtype="Int64")
+ arr._readonly = True
+ result = arr.to_numpy()
+ assert not result.flags.writeable
+
+ result = arr.to_numpy(dtype="int64", copy=True)
+ assert result.flags.writeable
+
+ result = arr.to_numpy(dtype="int32")
+ assert result.flags.writeable
+
+ result = arr.to_numpy(dtype="object")
+ assert result.flags.writeable
+
+
+def test_astype_str(using_infer_string):
+ a = pd.array([1, 2, None], dtype="Int64")
+
+ if using_infer_string:
+ expected = pd.array(["1", "2", None], dtype=pd.StringDtype(na_value=np.nan))
+
+ tm.assert_extension_array_equal(a.astype(str), expected)
+ tm.assert_extension_array_equal(a.astype("str"), expected)
+ else:
+ expected = np.array(["1", "2", ""], dtype=f"{tm.ENDIAN}U21")
+
+ tm.assert_numpy_array_equal(a.astype(str), expected)
+ tm.assert_numpy_array_equal(a.astype("str"), expected)
+
+
+def test_astype_boolean():
+ # https://github.com/pandas-dev/pandas/issues/31102
+ a = pd.array([1, 0, -1, 2, None], dtype="Int64")
+ result = a.astype("boolean")
+ expected = pd.array([True, False, True, True, None], dtype="boolean")
+ tm.assert_extension_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce801db5cb58db73fea2e66f2b9eb75c4f59e534
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_indexing.py
@@ -0,0 +1,19 @@
+import pandas as pd
+import pandas._testing as tm
+
+
+def test_array_setitem_nullable_boolean_mask():
+ # GH 31446
+ ser = pd.Series([1, 2], dtype="Int64")
+ result = ser.where(ser > 1)
+ expected = pd.Series([pd.NA, 2], dtype="Int64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_array_setitem():
+ # GH 31446
+ arr = pd.array([1, 2], dtype="Int64")
+ arr[arr > 1] = 1
+
+ expected = pd.array([1, 1], dtype="Int64")
+ tm.assert_extension_array_equal(arr, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_repr.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_repr.py
new file mode 100644
index 0000000000000000000000000000000000000000..168210eed5d06a461bbf42dd1e1fae3db0fd851c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/integer/test_repr.py
@@ -0,0 +1,67 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas.core.arrays.integer import (
+ Int8Dtype,
+ Int16Dtype,
+ Int32Dtype,
+ Int64Dtype,
+ UInt8Dtype,
+ UInt16Dtype,
+ UInt32Dtype,
+ UInt64Dtype,
+)
+
+
+def test_dtypes(dtype):
+ # smoke tests on auto dtype construction
+
+ if dtype.is_signed_integer:
+ assert np.dtype(dtype.type).kind == "i"
+ else:
+ assert np.dtype(dtype.type).kind == "u"
+ assert dtype.name is not None
+
+
+@pytest.mark.parametrize(
+ "dtype, expected",
+ [
+ (Int8Dtype(), "Int8Dtype()"),
+ (Int16Dtype(), "Int16Dtype()"),
+ (Int32Dtype(), "Int32Dtype()"),
+ (Int64Dtype(), "Int64Dtype()"),
+ (UInt8Dtype(), "UInt8Dtype()"),
+ (UInt16Dtype(), "UInt16Dtype()"),
+ (UInt32Dtype(), "UInt32Dtype()"),
+ (UInt64Dtype(), "UInt64Dtype()"),
+ ],
+)
+def test_repr_dtype(dtype, expected):
+ assert repr(dtype) == expected
+
+
+def test_repr_array():
+ result = repr(pd.array([1, None, 3]))
+ expected = "\n[1, , 3]\nLength: 3, dtype: Int64"
+ assert result == expected
+
+
+def test_repr_array_long():
+ data = pd.array([1, 2, None] * 1000)
+ expected = (
+ "\n"
+ "[ 1, 2, , 1, 2, , 1, 2, , 1,\n"
+ " ...\n"
+ " , 1, 2, , 1, 2, , 1, 2, ]\n"
+ "Length: 3000, dtype: Int64"
+ )
+ result = repr(data)
+ assert result == expected
+
+
+def test_frame_repr(data_missing):
+ df = pd.DataFrame({"A": data_missing})
+ result = repr(df)
+ expected = " A\n0 \n1 1"
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/masked_shared.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/masked_shared.py
new file mode 100644
index 0000000000000000000000000000000000000000..545b14af2c98bcdfeea2969d859ca097e7e0db8b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/masked_shared.py
@@ -0,0 +1,155 @@
+"""
+Tests shared by MaskedArray subclasses.
+"""
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.tests.extension.base import BaseOpsUtil
+
+
+class ComparisonOps(BaseOpsUtil):
+ def _compare_other(self, data, op, other):
+ # array
+ result = pd.Series(op(data, other))
+ expected = pd.Series(op(data._data, other), dtype="boolean")
+
+ # fill the nan locations
+ expected[data._mask] = pd.NA
+
+ tm.assert_series_equal(result, expected)
+
+ # series
+ ser = pd.Series(data)
+ result = op(ser, other)
+
+ # Set nullable dtype here to avoid upcasting when setting to pd.NA below
+ expected = op(pd.Series(data._data), other).astype("boolean")
+
+ # fill the nan locations
+ expected[data._mask] = pd.NA
+
+ tm.assert_series_equal(result, expected)
+
+ # subclass will override to parametrize 'other'
+ def test_scalar(self, other, comparison_op, dtype):
+ op = comparison_op
+ left = pd.array([1, 0, None], dtype=dtype)
+
+ result = op(left, other)
+
+ if other is pd.NA:
+ expected = pd.array([None, None, None], dtype="boolean")
+ else:
+ values = op(left._data, other)
+ expected = pd.arrays.BooleanArray(values, left._mask, copy=True)
+ tm.assert_extension_array_equal(result, expected)
+
+ # ensure we haven't mutated anything inplace
+ result[0] = pd.NA
+ tm.assert_extension_array_equal(left, pd.array([1, 0, None], dtype=dtype))
+
+
+class NumericOps:
+ # Shared by IntegerArray and FloatingArray, not BooleanArray
+
+ def test_searchsorted_nan(self, dtype):
+ # The base class casts to object dtype, for which searchsorted returns
+ # 0 from the left and 10 from the right.
+ arr = pd.array(range(10), dtype=dtype)
+
+ assert arr.searchsorted(np.nan, side="left") == 10
+ assert arr.searchsorted(np.nan, side="right") == 10
+
+ def test_no_shared_mask(self, data):
+ result = data + 1
+ assert not tm.shares_memory(result, data)
+
+ def test_array(self, comparison_op, dtype):
+ op = comparison_op
+
+ left = pd.array([0, 1, 2, None, None, None], dtype=dtype)
+ right = pd.array([0, 1, None, 0, 1, None], dtype=dtype)
+
+ result = op(left, right)
+ values = op(left._data, right._data)
+ mask = left._mask | right._mask
+
+ expected = pd.arrays.BooleanArray(values, mask)
+ tm.assert_extension_array_equal(result, expected)
+
+ # ensure we haven't mutated anything inplace
+ result[0] = pd.NA
+ tm.assert_extension_array_equal(
+ left, pd.array([0, 1, 2, None, None, None], dtype=dtype)
+ )
+ tm.assert_extension_array_equal(
+ right, pd.array([0, 1, None, 0, 1, None], dtype=dtype)
+ )
+
+ def test_compare_with_booleanarray(self, comparison_op, dtype):
+ op = comparison_op
+
+ left = pd.array([True, False, None] * 3, dtype="boolean")
+ right = pd.array([0] * 3 + [1] * 3 + [None] * 3, dtype=dtype)
+ other = pd.array([False] * 3 + [True] * 3 + [None] * 3, dtype="boolean")
+
+ expected = op(left, other)
+ result = op(left, right)
+ tm.assert_extension_array_equal(result, expected)
+
+ # reversed op
+ expected = op(other, left)
+ result = op(right, left)
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_compare_to_string(self, dtype):
+ # GH#28930
+ ser = pd.Series([1, None], dtype=dtype)
+ result = ser == "a"
+ expected = pd.Series([False, pd.NA], dtype="boolean")
+
+ tm.assert_series_equal(result, expected)
+
+ def test_ufunc_with_out(self, dtype):
+ arr = pd.array([1, 2, 3], dtype=dtype)
+ arr2 = pd.array([1, 2, pd.NA], dtype=dtype)
+
+ mask = arr == arr
+ mask2 = arr2 == arr2
+
+ result = np.zeros(3, dtype=bool)
+ result |= mask
+ # If MaskedArray.__array_ufunc__ handled "out" appropriately,
+ # `result` should still be an ndarray.
+ assert isinstance(result, np.ndarray)
+ assert result.all()
+
+ # result |= mask worked because mask could be cast losslessly to
+ # boolean ndarray. mask2 can't, so this raises
+ result = np.zeros(3, dtype=bool)
+ msg = "Specify an appropriate 'na_value' for this dtype"
+ with pytest.raises(ValueError, match=msg):
+ result |= mask2
+
+ # addition
+ res = np.add(arr, arr2)
+ expected = pd.array([2, 4, pd.NA], dtype=dtype)
+ tm.assert_extension_array_equal(res, expected)
+
+ # when passing out=arr, we will modify 'arr' inplace.
+ res = np.add(arr, arr2, out=arr)
+ assert res is arr
+ tm.assert_extension_array_equal(res, expected)
+ tm.assert_extension_array_equal(arr, expected)
+
+ def test_mul_td64_array(self, dtype):
+ # GH#45622
+ arr = pd.array([1, 2, pd.NA], dtype=dtype)
+ other = np.arange(3, dtype=np.int64).view("m8[ns]")
+
+ result = arr * other
+ expected = pd.array([pd.Timedelta(0), pd.Timedelta(2), pd.NaT])
+ tm.assert_extension_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/numpy_/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/numpy_/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/numpy_/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/numpy_/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..225d64ad7d2580f877505f0ac3a459e2ea4f0f53
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/numpy_/test_indexing.py
@@ -0,0 +1,41 @@
+import numpy as np
+
+from pandas.core.dtypes.common import is_scalar
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class TestSearchsorted:
+ def test_searchsorted_string(self, string_dtype):
+ arr = pd.array(["a", "b", "c"], dtype=string_dtype)
+
+ result = arr.searchsorted("a", side="left")
+ assert is_scalar(result)
+ assert result == 0
+
+ result = arr.searchsorted("a", side="right")
+ assert is_scalar(result)
+ assert result == 1
+
+ def test_searchsorted_numeric_dtypes_scalar(self, any_real_numpy_dtype):
+ arr = pd.array([1, 3, 90], dtype=any_real_numpy_dtype)
+ result = arr.searchsorted(30)
+ assert is_scalar(result)
+ assert result == 2
+
+ result = arr.searchsorted([30])
+ expected = np.array([2], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_searchsorted_numeric_dtypes_vector(self, any_real_numpy_dtype):
+ arr = pd.array([1, 3, 90], dtype=any_real_numpy_dtype)
+ result = arr.searchsorted([2, 30])
+ expected = np.array([1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_searchsorted_sorter(self, any_real_numpy_dtype):
+ arr = pd.array([3, 1, 2], dtype=any_real_numpy_dtype)
+ result = arr.searchsorted([0, 3], sorter=np.argsort(arr))
+ expected = np.array([0, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/numpy_/test_numpy.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/numpy_/test_numpy.py
new file mode 100644
index 0000000000000000000000000000000000000000..b43b6a42ae084ffc71ae9e78e270d22b6f7afaf6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/numpy_/test_numpy.py
@@ -0,0 +1,411 @@
+"""
+Additional tests for NumpyExtensionArray that aren't covered by
+the interface tests.
+"""
+
+import numpy as np
+import pytest
+
+from pandas.compat.numpy import np_version_gt2
+
+from pandas.core.dtypes.dtypes import NumpyEADtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.arrays import NumpyExtensionArray
+
+
+@pytest.fixture(
+ params=[
+ np.array(["a", "b"], dtype=object),
+ np.array([0, 1], dtype=float),
+ np.array([0, 1], dtype=int),
+ np.array([0, 1 + 2j], dtype=complex),
+ np.array([True, False], dtype=bool),
+ np.array([0, 1], dtype="datetime64[ns]"),
+ np.array([0, 1], dtype="timedelta64[ns]"),
+ ],
+)
+def any_numpy_array(request):
+ """
+ Parametrized fixture for NumPy arrays with different dtypes.
+
+ This excludes string and bytes.
+ """
+ return request.param.copy()
+
+
+# ----------------------------------------------------------------------------
+# NumpyEADtype
+
+
+@pytest.mark.parametrize(
+ "dtype, expected",
+ [
+ ("bool", True),
+ ("int", True),
+ ("uint", True),
+ ("float", True),
+ ("complex", True),
+ ("str", False),
+ ("bytes", False),
+ ("datetime64[ns]", False),
+ ("object", False),
+ ("void", False),
+ ],
+)
+def test_is_numeric(dtype, expected):
+ dtype = NumpyEADtype(dtype)
+ assert dtype._is_numeric is expected
+
+
+@pytest.mark.parametrize(
+ "dtype, expected",
+ [
+ ("bool", True),
+ ("int", False),
+ ("uint", False),
+ ("float", False),
+ ("complex", False),
+ ("str", False),
+ ("bytes", False),
+ ("datetime64[ns]", False),
+ ("object", False),
+ ("void", False),
+ ],
+)
+def test_is_boolean(dtype, expected):
+ dtype = NumpyEADtype(dtype)
+ assert dtype._is_boolean is expected
+
+
+def test_repr():
+ dtype = NumpyEADtype(np.dtype("int64"))
+ assert repr(dtype) == "NumpyEADtype('int64')"
+
+
+def test_constructor_from_string():
+ result = NumpyEADtype.construct_from_string("int64")
+ expected = NumpyEADtype(np.dtype("int64"))
+ assert result == expected
+
+
+def test_dtype_idempotent(any_numpy_dtype):
+ dtype = NumpyEADtype(any_numpy_dtype)
+
+ result = NumpyEADtype(dtype)
+ assert result == dtype
+
+
+# ----------------------------------------------------------------------------
+# Construction
+
+
+def test_constructor_no_coercion():
+ with pytest.raises(ValueError, match="NumPy array"):
+ NumpyExtensionArray([1, 2, 3])
+
+
+def test_series_constructor_with_copy():
+ ndarray = np.array([1, 2, 3])
+ ser = pd.Series(NumpyExtensionArray(ndarray), copy=True)
+
+ assert ser.values is not ndarray
+
+
+def test_series_constructor_with_astype():
+ ndarray = np.array([1, 2, 3])
+ result = pd.Series(NumpyExtensionArray(ndarray), dtype="float64")
+ expected = pd.Series([1.0, 2.0, 3.0], dtype="float64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_from_sequence_dtype():
+ arr = np.array([1, 2, 3], dtype="int64")
+ result = NumpyExtensionArray._from_sequence(arr, dtype="uint64")
+ expected = NumpyExtensionArray(np.array([1, 2, 3], dtype="uint64"))
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_constructor_copy():
+ arr = np.array([0, 1])
+ result = NumpyExtensionArray(arr, copy=True)
+
+ assert not tm.shares_memory(result, arr)
+
+
+def test_constructor_with_data(any_numpy_array):
+ nparr = any_numpy_array
+ arr = NumpyExtensionArray(nparr)
+ assert arr.dtype.numpy_dtype == nparr.dtype
+
+
+# ----------------------------------------------------------------------------
+# Conversion
+
+
+def test_to_numpy():
+ arr = NumpyExtensionArray(np.array([1, 2, 3]))
+ result = arr.to_numpy()
+ assert result is arr._ndarray
+
+ result = arr.to_numpy(copy=True)
+ assert result is not arr._ndarray
+
+ result = arr.to_numpy(dtype="f8")
+ expected = np.array([1, 2, 3], dtype="f8")
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_to_numpy_readonly():
+ arr = NumpyExtensionArray(np.array([1, 2, 3]))
+ arr._readonly = True
+ result = arr.to_numpy()
+ assert not result.flags.writeable
+
+ result = arr.to_numpy(copy=True)
+ assert result.flags.writeable
+
+ result = arr.to_numpy(dtype="f8")
+ assert result.flags.writeable
+
+
+@pytest.mark.skipif(not np_version_gt2, reason="copy keyword introduced in np 2.0")
+@pytest.mark.parametrize("dtype", [None, "int64"])
+def test_asarray_readonly(dtype):
+ arr = NumpyExtensionArray(np.array([1, 2, 3], dtype="int64"))
+ arr._readonly = True
+ result = np.asarray(arr, dtype=dtype)
+ assert not result.flags.writeable
+
+ result = np.asarray(arr, dtype=dtype, copy=True)
+ assert result.flags.writeable
+
+ result = np.asarray(arr, dtype=dtype, copy=False)
+ assert not result.flags.writeable
+
+
+# ----------------------------------------------------------------------------
+# Setitem
+
+
+def test_setitem(any_numpy_array):
+ nparr = any_numpy_array
+ arr = NumpyExtensionArray(nparr, copy=True)
+
+ arr[0] = arr[1]
+ nparr[0] = nparr[1]
+
+ tm.assert_numpy_array_equal(arr.to_numpy(), nparr)
+
+
+# ----------------------------------------------------------------------------
+# Reductions
+
+
+def test_bad_reduce_raises():
+ arr = np.array([1, 2, 3], dtype="int64")
+ arr = NumpyExtensionArray(arr)
+ msg = "cannot perform not_a_method with type int"
+ with pytest.raises(TypeError, match=msg):
+ arr._reduce(msg)
+
+
+def test_validate_reduction_keyword_args():
+ arr = NumpyExtensionArray(np.array([1, 2, 3]))
+ msg = "the 'keepdims' parameter is not supported .*all"
+ with pytest.raises(ValueError, match=msg):
+ arr.all(keepdims=True)
+
+
+def test_np_max_nested_tuples():
+ # case where checking in ufunc.nout works while checking for tuples
+ # does not
+ vals = [
+ (("j", "k"), ("l", "m")),
+ (("l", "m"), ("o", "p")),
+ (("o", "p"), ("j", "k")),
+ ]
+ ser = pd.Series(vals)
+ arr = ser.array
+
+ assert arr.max() is arr[2]
+ assert ser.max() is arr[2]
+
+ result = np.maximum.reduce(arr)
+ assert result == arr[2]
+
+ result = np.maximum.reduce(ser)
+ assert result == arr[2]
+
+
+def test_np_reduce_2d():
+ raw = np.arange(12).reshape(4, 3)
+ arr = NumpyExtensionArray(raw)
+
+ res = np.maximum.reduce(arr, axis=0)
+ tm.assert_extension_array_equal(res, arr[-1])
+
+ alt = arr.max(axis=0)
+ tm.assert_extension_array_equal(alt, arr[-1])
+
+
+# ----------------------------------------------------------------------------
+# Ops
+
+
+@pytest.mark.parametrize("ufunc", [np.abs, np.negative, np.positive])
+def test_ufunc_unary(ufunc):
+ arr = NumpyExtensionArray(np.array([-1.0, 0.0, 1.0]))
+ result = ufunc(arr)
+ expected = NumpyExtensionArray(ufunc(arr._ndarray))
+ tm.assert_extension_array_equal(result, expected)
+
+ # same thing but with the 'out' keyword
+ out = NumpyExtensionArray(np.array([-9.0, -9.0, -9.0]))
+ ufunc(arr, out=out)
+ tm.assert_extension_array_equal(out, expected)
+
+
+def test_ufunc():
+ arr = NumpyExtensionArray(np.array([-1.0, 0.0, 1.0]))
+
+ r1, r2 = np.divmod(arr, np.add(arr, 2))
+ e1, e2 = np.divmod(arr._ndarray, np.add(arr._ndarray, 2))
+ e1 = NumpyExtensionArray(e1)
+ e2 = NumpyExtensionArray(e2)
+ tm.assert_extension_array_equal(r1, e1)
+ tm.assert_extension_array_equal(r2, e2)
+
+
+def test_basic_binop():
+ # Just a basic smoke test. The EA interface tests exercise this
+ # more thoroughly.
+ x = NumpyExtensionArray(np.array([1, 2, 3]))
+ result = x + x
+ expected = NumpyExtensionArray(np.array([2, 4, 6]))
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", [None, object])
+def test_setitem_object_typecode(dtype):
+ arr = NumpyExtensionArray(np.array(["a", "b", "c"], dtype=dtype))
+ arr[0] = "t"
+ expected = NumpyExtensionArray(np.array(["t", "b", "c"], dtype=dtype))
+ tm.assert_extension_array_equal(arr, expected)
+
+
+def test_setitem_no_coercion():
+ # https://github.com/pandas-dev/pandas/issues/28150
+ arr = NumpyExtensionArray(np.array([1, 2, 3]))
+ with pytest.raises(ValueError, match="int"):
+ arr[0] = "a"
+
+ # With a value that we do coerce, check that we coerce the value
+ # and not the underlying array.
+ arr[0] = 2.5
+ assert isinstance(arr[0], (int, np.integer)), type(arr[0])
+
+
+def test_setitem_preserves_views():
+ # GH#28150, see also extension test of the same name
+ arr = NumpyExtensionArray(np.array([1, 2, 3]))
+ view1 = arr.view()
+ view2 = arr[:]
+ view3 = np.asarray(arr)
+
+ arr[0] = 9
+ assert view1[0] == 9
+ assert view2[0] == 9
+ assert view3[0] == 9
+
+ arr[-1] = 2.5
+ view1[-1] = 5
+ assert arr[-1] == 5
+
+
+@pytest.mark.parametrize("dtype", [np.int64, np.uint64])
+def test_quantile_empty(dtype):
+ # we should get back np.nans, not -1s
+ arr = NumpyExtensionArray(np.array([], dtype=dtype))
+ idx = pd.Index([0.0, 0.5])
+
+ result = arr._quantile(idx, interpolation="linear")
+ expected = NumpyExtensionArray(np.array([np.nan, np.nan]))
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_factorize_unsigned():
+ # don't raise when calling factorize on unsigned int NumpyExtensionArray
+ arr = np.array([1, 2, 3], dtype=np.uint64)
+ obj = NumpyExtensionArray(arr)
+
+ res_codes, res_unique = obj.factorize()
+ exp_codes, exp_unique = pd.factorize(arr)
+
+ tm.assert_numpy_array_equal(res_codes, exp_codes)
+
+ tm.assert_extension_array_equal(res_unique, NumpyExtensionArray(exp_unique))
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ np.bool_,
+ np.uint8,
+ np.uint16,
+ np.uint32,
+ np.uint64,
+ np.int8,
+ np.int16,
+ np.int32,
+ np.int64,
+ ],
+)
+def test_take_assigns_floating_point_dtype(dtype):
+ # GH#62448.
+ if dtype == np.bool_:
+ array = NumpyExtensionArray(np.array([False, True, False], dtype=dtype))
+ expected = np.dtype(object)
+ else:
+ array = NumpyExtensionArray(np.array([1, 2, 3], dtype=dtype))
+ expected = np.float64
+
+ result = array.take([-1], allow_fill=True)
+ assert result.dtype.numpy_dtype == expected
+
+ result = array.take([-1], allow_fill=True, fill_value=5.0)
+ assert result.dtype.numpy_dtype == expected
+
+
+def test_take_preserves_boolean_arrays():
+ array = NumpyExtensionArray(np.array([False, True, False], dtype=np.bool_))
+ result = array.take([-1], allow_fill=False)
+ assert result.dtype.numpy_dtype == np.bool_
+
+
+# ----------------------------------------------------------------------------
+# Output formatting
+
+
+def test_array_repr(any_numpy_array):
+ # GH#61085
+ nparray = any_numpy_array
+ arr = NumpyExtensionArray(nparray)
+ if nparray.dtype == "object":
+ values = "['a', 'b']"
+ elif nparray.dtype == "float64":
+ values = "[0.0, 1.0]"
+ elif str(nparray.dtype).startswith("int"):
+ values = "[0, 1]"
+ elif nparray.dtype == "complex128":
+ values = "[0j, (1+2j)]"
+ elif nparray.dtype == "bool":
+ values = "[True, False]"
+ elif nparray.dtype == "datetime64[ns]":
+ values = "[1970-01-01T00:00:00.000000000, 1970-01-01T00:00:00.000000001]"
+ elif nparray.dtype == "timedelta64[ns]":
+ values = "[0 nanoseconds, 1 nanoseconds]"
+ expected = f"\n{values}\nLength: 2, dtype: {nparray.dtype}"
+ result = repr(arr)
+ assert result == expected, f"{result} vs {expected}"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_array.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_array.py
new file mode 100644
index 0000000000000000000000000000000000000000..a02926dd5e158cd914a3eff0bc061a01cabea323
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_array.py
@@ -0,0 +1,539 @@
+import datetime
+import decimal
+import zoneinfo
+
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.extensions import register_extension_dtype
+from pandas.arrays import (
+ BooleanArray,
+ DatetimeArray,
+ FloatingArray,
+ IntegerArray,
+ IntervalArray,
+ SparseArray,
+ TimedeltaArray,
+)
+from pandas.core.arrays import (
+ NumpyExtensionArray,
+ period_array,
+)
+from pandas.tests.extension.decimal import (
+ DecimalArray,
+ DecimalDtype,
+ to_decimal,
+)
+
+
+@pytest.mark.parametrize("dtype_unit", ["M8[h]", "M8[m]", "m8[h]"])
+def test_dt64_array(dtype_unit):
+ # GH#53817
+ dtype_var = np.dtype(dtype_unit)
+ msg = (
+ r"datetime64 and timedelta64 dtype resolutions other than "
+ r"'s', 'ms', 'us', and 'ns' are no longer supported."
+ )
+ with pytest.raises(ValueError, match=msg):
+ pd.array([], dtype=dtype_var)
+
+
+@pytest.mark.parametrize(
+ "data, dtype, expected",
+ [
+ # Basic NumPy defaults.
+ ([], None, FloatingArray._from_sequence([], dtype="Float64")),
+ ([1, 2], None, IntegerArray._from_sequence([1, 2], dtype="Int64")),
+ ([1, 2], object, NumpyExtensionArray(np.array([1, 2], dtype=object))),
+ (
+ [1, 2],
+ np.dtype("float32"),
+ NumpyExtensionArray(np.array([1.0, 2.0], dtype=np.dtype("float32"))),
+ ),
+ (
+ np.array([], dtype=object),
+ None,
+ NumpyExtensionArray(np.array([], dtype=object)),
+ ),
+ (
+ np.array([1, 2], dtype="int64"),
+ None,
+ IntegerArray._from_sequence([1, 2], dtype="Int64"),
+ ),
+ (
+ np.array([1.0, 2.0], dtype="float64"),
+ None,
+ FloatingArray._from_sequence([1.0, 2.0], dtype="Float64"),
+ ),
+ # String alias passes through to NumPy
+ ([1, 2], "float32", NumpyExtensionArray(np.array([1, 2], dtype="float32"))),
+ ([1, 2], "int64", NumpyExtensionArray(np.array([1, 2], dtype=np.int64))),
+ # GH#44715 FloatingArray does not support float16, so fall
+ # back to NumpyExtensionArray
+ (
+ np.array([1, 2], dtype=np.float16),
+ None,
+ NumpyExtensionArray(np.array([1, 2], dtype=np.float16)),
+ ),
+ # idempotency with e.g. pd.array(pd.array([1, 2], dtype="int64"))
+ (
+ NumpyExtensionArray(np.array([1, 2], dtype=np.int32)),
+ None,
+ NumpyExtensionArray(np.array([1, 2], dtype=np.int32)),
+ ),
+ # Period alias
+ (
+ [pd.Period("2000", "D"), pd.Period("2001", "D")],
+ "Period[D]",
+ period_array(["2000", "2001"], freq="D"),
+ ),
+ # Period dtype
+ (
+ [pd.Period("2000", "D")],
+ pd.PeriodDtype("D"),
+ period_array(["2000"], freq="D"),
+ ),
+ # Datetime (naive)
+ (
+ [1, 2],
+ np.dtype("datetime64[ns]"),
+ DatetimeArray._from_sequence(
+ np.array([1, 2], dtype="M8[ns]"), dtype="M8[ns]"
+ ),
+ ),
+ (
+ [1, 2],
+ np.dtype("datetime64[s]"),
+ DatetimeArray._from_sequence(
+ np.array([1, 2], dtype="M8[s]"), dtype="M8[s]"
+ ),
+ ),
+ (
+ np.array([1, 2], dtype="datetime64[ns]"),
+ None,
+ DatetimeArray._from_sequence(
+ np.array([1, 2], dtype="M8[ns]"), dtype="M8[ns]"
+ ),
+ ),
+ (
+ pd.DatetimeIndex(["2000", "2001"]),
+ np.dtype("datetime64[ns]"),
+ DatetimeArray._from_sequence(["2000", "2001"], dtype="M8[ns]"),
+ ),
+ (
+ pd.DatetimeIndex(["2000", "2001"]),
+ None,
+ DatetimeArray._from_sequence(["2000", "2001"], dtype="M8[us]"),
+ ),
+ (
+ ["2000", "2001"],
+ np.dtype("datetime64[ns]"),
+ DatetimeArray._from_sequence(["2000", "2001"], dtype="M8[ns]"),
+ ),
+ (
+ [pd.NaT, pd.NaT],
+ None,
+ DatetimeArray._from_sequence([pd.NaT, pd.NaT], dtype="M8[s]"),
+ ),
+ # Datetime (tz-aware)
+ (
+ ["2000", "2001"],
+ pd.DatetimeTZDtype(tz="CET"),
+ DatetimeArray._from_sequence(
+ ["2000", "2001"], dtype=pd.DatetimeTZDtype(tz="CET")
+ ),
+ ),
+ # Timedelta
+ (
+ ["1h", "2h"],
+ np.dtype("timedelta64[ns]"),
+ TimedeltaArray._from_sequence(["1h", "2h"], dtype="m8[ns]"),
+ ),
+ (
+ pd.TimedeltaIndex(["1h", "2h"]),
+ np.dtype("timedelta64[ns]"),
+ TimedeltaArray._from_sequence(["1h", "2h"], dtype="m8[ns]"),
+ ),
+ (
+ np.array([1, 2], dtype="m8[s]"),
+ np.dtype("timedelta64[s]"),
+ TimedeltaArray._from_sequence(
+ np.array([1, 2], dtype="m8[s]"), dtype="m8[s]"
+ ),
+ ),
+ (
+ pd.TimedeltaIndex(["1h", "2h"]),
+ None,
+ TimedeltaArray._from_sequence(["1h", "2h"], dtype="m8[us]"),
+ ),
+ (
+ # preserve non-nano, i.e. don't cast to NumpyExtensionArray
+ TimedeltaArray._simple_new(
+ np.arange(5, dtype=np.int64).view("m8[s]"), dtype=np.dtype("m8[s]")
+ ),
+ None,
+ TimedeltaArray._simple_new(
+ np.arange(5, dtype=np.int64).view("m8[s]"), dtype=np.dtype("m8[s]")
+ ),
+ ),
+ (
+ # preserve non-nano, i.e. don't cast to NumpyExtensionArray
+ TimedeltaArray._simple_new(
+ np.arange(5, dtype=np.int64).view("m8[s]"), dtype=np.dtype("m8[s]")
+ ),
+ np.dtype("m8[s]"),
+ TimedeltaArray._simple_new(
+ np.arange(5, dtype=np.int64).view("m8[s]"), dtype=np.dtype("m8[s]")
+ ),
+ ),
+ # Category
+ (["a", "b"], "category", pd.Categorical(["a", "b"])),
+ (
+ ["a", "b"],
+ pd.CategoricalDtype(None, ordered=True),
+ pd.Categorical(["a", "b"], ordered=True),
+ ),
+ # Interval
+ (
+ [pd.Interval(1, 2), pd.Interval(3, 4)],
+ "interval",
+ IntervalArray.from_tuples([(1, 2), (3, 4)]),
+ ),
+ # Sparse
+ ([0, 1], "Sparse[int64]", SparseArray([0, 1], dtype="int64")),
+ # IntegerNA
+ ([1, None], "Int16", pd.array([1, None], dtype="Int16")),
+ (
+ pd.Series([1, 2]),
+ None,
+ NumpyExtensionArray(np.array([1, 2], dtype=np.int64)),
+ ),
+ # String
+ (
+ ["a", None],
+ "string",
+ pd.StringDtype()
+ .construct_array_type()
+ ._from_sequence(["a", None], dtype=pd.StringDtype()),
+ ),
+ (
+ ["a", None],
+ "str",
+ pd.StringDtype(na_value=np.nan)
+ .construct_array_type()
+ ._from_sequence(["a", None], dtype=pd.StringDtype(na_value=np.nan))
+ if using_string_dtype()
+ else NumpyExtensionArray(np.array(["a", "None"])),
+ ),
+ (
+ ["a", None],
+ pd.StringDtype(),
+ pd.StringDtype()
+ .construct_array_type()
+ ._from_sequence(["a", None], dtype=pd.StringDtype()),
+ ),
+ (
+ ["a", None],
+ pd.StringDtype(na_value=np.nan),
+ pd.StringDtype(na_value=np.nan)
+ .construct_array_type()
+ ._from_sequence(["a", None], dtype=pd.StringDtype(na_value=np.nan)),
+ ),
+ (
+ # numpy array with string dtype
+ np.array(["a", "b"], dtype=str),
+ pd.StringDtype(),
+ pd.StringDtype()
+ .construct_array_type()
+ ._from_sequence(["a", "b"], dtype=pd.StringDtype()),
+ ),
+ (
+ # numpy array with string dtype
+ np.array(["a", "b"], dtype=str),
+ pd.StringDtype(na_value=np.nan),
+ pd.StringDtype(na_value=np.nan)
+ .construct_array_type()
+ ._from_sequence(["a", "b"], dtype=pd.StringDtype(na_value=np.nan)),
+ ),
+ # Boolean
+ (
+ [True, None],
+ "boolean",
+ BooleanArray._from_sequence([True, None], dtype="boolean"),
+ ),
+ (
+ [True, None],
+ pd.BooleanDtype(),
+ BooleanArray._from_sequence([True, None], dtype="boolean"),
+ ),
+ # Index
+ (pd.Index([1, 2]), None, NumpyExtensionArray(np.array([1, 2], dtype=np.int64))),
+ # Series[EA] returns the EA
+ (
+ pd.Series(pd.Categorical(["a", "b"], categories=["a", "b", "c"])),
+ None,
+ pd.Categorical(["a", "b"], categories=["a", "b", "c"]),
+ ),
+ # "3rd party" EAs work
+ ([decimal.Decimal(0), decimal.Decimal(1)], "decimal", to_decimal([0, 1])),
+ # pass an ExtensionArray, but a different dtype
+ (
+ period_array(["2000", "2001"], freq="D"),
+ "category",
+ pd.Categorical([pd.Period("2000", "D"), pd.Period("2001", "D")]),
+ ),
+ # Complex
+ (
+ np.array([complex(1), complex(2)], dtype=np.complex128),
+ None,
+ NumpyExtensionArray(
+ np.array([complex(1), complex(2)], dtype=np.complex128)
+ ),
+ ),
+ ],
+)
+def test_array(data, dtype, expected):
+ result = pd.array(data, dtype=dtype)
+ tm.assert_equal(result, expected)
+
+
+def test_array_copy():
+ a = np.array([1, 2])
+ # default is to copy
+ b = pd.array(a, dtype=a.dtype)
+ assert not tm.shares_memory(a, b)
+
+ # copy=True
+ b = pd.array(a, dtype=a.dtype, copy=True)
+ assert not tm.shares_memory(a, b)
+
+ # copy=False
+ b = pd.array(a, dtype=a.dtype, copy=False)
+ assert tm.shares_memory(a, b)
+
+
+@pytest.mark.parametrize(
+ "data, expected",
+ [
+ # period
+ (
+ [pd.Period("2000", "D"), pd.Period("2001", "D")],
+ period_array(["2000", "2001"], freq="D"),
+ ),
+ # interval
+ ([pd.Interval(0, 1), pd.Interval(1, 2)], IntervalArray.from_breaks([0, 1, 2])),
+ # datetime
+ (
+ [pd.Timestamp("2000").as_unit("s"), pd.Timestamp("2001").as_unit("s")],
+ DatetimeArray._from_sequence(["2000", "2001"], dtype="M8[s]"),
+ ),
+ (
+ [datetime.datetime(2000, 1, 1), datetime.datetime(2001, 1, 1)],
+ DatetimeArray._from_sequence(["2000", "2001"], dtype="M8[us]"),
+ ),
+ (
+ np.array([1, 2], dtype="M8[ns]"),
+ DatetimeArray._from_sequence(np.array([1, 2], dtype="M8[ns]")),
+ ),
+ (
+ np.array([1, 2], dtype="M8[us]"),
+ DatetimeArray._simple_new(
+ np.array([1, 2], dtype="M8[us]"), dtype=np.dtype("M8[us]")
+ ),
+ ),
+ # datetimetz
+ (
+ [
+ pd.Timestamp("2000", tz="CET").as_unit("s"),
+ pd.Timestamp("2001", tz="CET").as_unit("s"),
+ ],
+ DatetimeArray._from_sequence(
+ ["2000", "2001"], dtype=pd.DatetimeTZDtype(tz="CET", unit="s")
+ ),
+ ),
+ (
+ [
+ datetime.datetime(
+ 2000, 1, 1, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")
+ ),
+ datetime.datetime(
+ 2001, 1, 1, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")
+ ),
+ ],
+ DatetimeArray._from_sequence(
+ ["2000", "2001"],
+ dtype=pd.DatetimeTZDtype(
+ tz=zoneinfo.ZoneInfo("Europe/Berlin"), unit="us"
+ ),
+ ),
+ ),
+ # timedelta
+ (
+ [pd.Timedelta("1h"), pd.Timedelta("2h")],
+ TimedeltaArray._from_sequence(["1h", "2h"], dtype="m8[us]"),
+ ),
+ (
+ np.array([1, 2], dtype="m8[ns]"),
+ TimedeltaArray._from_sequence(
+ np.array([1, 2], dtype="m8[ns]"), dtype=np.dtype("m8[ns]")
+ ),
+ ),
+ (
+ np.array([1, 2], dtype="m8[us]"),
+ TimedeltaArray._from_sequence(
+ np.array([1, 2], dtype="m8[us]"), dtype=np.dtype("m8[us]")
+ ),
+ ),
+ # integer
+ ([1, 2], IntegerArray._from_sequence([1, 2], dtype="Int64")),
+ ([1, None], IntegerArray._from_sequence([1, None], dtype="Int64")),
+ ([1, pd.NA], IntegerArray._from_sequence([1, pd.NA], dtype="Int64")),
+ ([1, np.nan], IntegerArray._from_sequence([1, pd.NA], dtype="Int64")),
+ # float
+ ([0.1, 0.2], FloatingArray._from_sequence([0.1, 0.2], dtype="Float64")),
+ ([0.1, None], FloatingArray._from_sequence([0.1, pd.NA], dtype="Float64")),
+ ([0.1, np.nan], FloatingArray._from_sequence([0.1, pd.NA], dtype="Float64")),
+ ([0.1, pd.NA], FloatingArray._from_sequence([0.1, pd.NA], dtype="Float64")),
+ # integer-like float
+ ([1.0, 2.0], FloatingArray._from_sequence([1.0, 2.0], dtype="Float64")),
+ ([1.0, None], FloatingArray._from_sequence([1.0, pd.NA], dtype="Float64")),
+ ([1.0, np.nan], FloatingArray._from_sequence([1.0, pd.NA], dtype="Float64")),
+ ([1.0, pd.NA], FloatingArray._from_sequence([1.0, pd.NA], dtype="Float64")),
+ # mixed-integer-float
+ ([1, 2.0], FloatingArray._from_sequence([1.0, 2.0], dtype="Float64")),
+ (
+ [1, np.nan, 2.0],
+ FloatingArray._from_sequence([1.0, None, 2.0], dtype="Float64"),
+ ),
+ # string
+ (
+ ["a", "b"],
+ pd.StringDtype()
+ .construct_array_type()
+ ._from_sequence(["a", "b"], dtype=pd.StringDtype()),
+ ),
+ (
+ ["a", None],
+ pd.StringDtype()
+ .construct_array_type()
+ ._from_sequence(["a", None], dtype=pd.StringDtype()),
+ ),
+ (
+ # numpy array with string dtype
+ np.array(["a", "b"], dtype=str),
+ pd.StringDtype()
+ .construct_array_type()
+ ._from_sequence(["a", "b"], dtype=pd.StringDtype()),
+ ),
+ # Boolean
+ ([True, False], BooleanArray._from_sequence([True, False], dtype="boolean")),
+ ([True, None], BooleanArray._from_sequence([True, None], dtype="boolean")),
+ ],
+)
+def test_array_inference(data, expected):
+ result = pd.array(data)
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ # mix of frequencies
+ [pd.Period("2000", "D"), pd.Period("2001", "Y")],
+ # mix of closed
+ [pd.Interval(0, 1, closed="left"), pd.Interval(1, 2, closed="right")],
+ # Mix of timezones
+ [pd.Timestamp("2000", tz="CET"), pd.Timestamp("2000", tz="UTC")],
+ # Mix of tz-aware and tz-naive
+ [pd.Timestamp("2000", tz="CET"), pd.Timestamp("2000")],
+ np.array([pd.Timestamp("2000"), pd.Timestamp("2000", tz="CET")]),
+ ],
+)
+def test_array_inference_fails(data):
+ result = pd.array(data)
+ expected = NumpyExtensionArray(np.array(data, dtype=object))
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("data", [np.array(0)])
+def test_nd_raises(data):
+ with pytest.raises(ValueError, match="NumpyExtensionArray must be 1-dimensional"):
+ pd.array(data, dtype="int64")
+
+
+def test_scalar_raises():
+ with pytest.raises(ValueError, match="Cannot pass scalar '1'"):
+ pd.array(1)
+
+
+def test_dataframe_raises():
+ # GH#51167 don't accidentally cast to StringArray by doing inference on columns
+ df = pd.DataFrame([[1, 2], [3, 4]], columns=["A", "B"])
+ msg = "Cannot pass DataFrame to 'pandas.array'"
+ with pytest.raises(TypeError, match=msg):
+ pd.array(df)
+
+
+def test_bounds_check():
+ # GH21796
+ with pytest.raises(
+ TypeError, match=r"cannot safely cast non-equivalent int(32|64) to uint16"
+ ):
+ pd.array([-1, 2, 3], dtype="UInt16")
+
+
+# ---------------------------------------------------------------------------
+# A couple dummy classes to ensure that Series and Indexes are unboxed before
+# getting to the EA classes.
+
+
+@register_extension_dtype
+class DecimalDtype2(DecimalDtype):
+ name = "decimal2"
+
+ def construct_array_type(self):
+ """
+ Return the array type associated with this dtype.
+
+ Returns
+ -------
+ type
+ """
+ return DecimalArray2
+
+
+class DecimalArray2(DecimalArray):
+ @classmethod
+ def _from_sequence(cls, scalars, *, dtype=None, copy=False):
+ if isinstance(scalars, (pd.Series, pd.Index)):
+ raise TypeError("scalars should not be of type pd.Series or pd.Index")
+
+ return super()._from_sequence(scalars, dtype=dtype, copy=copy)
+
+
+def test_array_unboxes(index_or_series):
+ box = index_or_series
+
+ data = box([decimal.Decimal("1"), decimal.Decimal("2")])
+ dtype = DecimalDtype2()
+ # make sure it works
+ with pytest.raises(
+ TypeError, match="scalars should not be of type pd.Series or pd.Index"
+ ):
+ DecimalArray2._from_sequence(data, dtype=dtype)
+
+ result = pd.array(data, dtype="decimal2")
+ expected = DecimalArray2._from_sequence(data.values, dtype=dtype)
+ tm.assert_equal(result, expected)
+
+
+def test_array_to_numpy_na():
+ # GH#40638
+ arr = pd.array([pd.NA, 1], dtype="string[python]")
+ result = arr.to_numpy(na_value=True, dtype=bool)
+ expected = np.array([True, True])
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_datetimelike.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_datetimelike.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f6b3491a74693312e8c6c6e6f92faee127bc357
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_datetimelike.py
@@ -0,0 +1,1390 @@
+from __future__ import annotations
+
+import re
+import warnings
+
+import numpy as np
+import pytest
+
+from pandas._libs import (
+ NaT,
+ Timestamp,
+)
+from pandas._libs.tslibs import to_offset
+from pandas.compat.numpy import np_version_gt2
+
+from pandas.core.dtypes.dtypes import PeriodDtype
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Period,
+ PeriodIndex,
+ TimedeltaIndex,
+)
+import pandas._testing as tm
+from pandas.core.arrays import (
+ DatetimeArray,
+ NumpyExtensionArray,
+ PeriodArray,
+ TimedeltaArray,
+)
+
+
+# TODO: more freq variants
+@pytest.fixture(params=["D", "B", "W", "ME", "QE", "YE"])
+def freqstr(request):
+ """Fixture returning parametrized frequency in string format."""
+ return request.param
+
+
+@pytest.fixture
+def period_index(freqstr):
+ """
+ A fixture to provide PeriodIndex objects with different frequencies.
+
+ Most PeriodArray behavior is already tested in PeriodIndex tests,
+ so here we just test that the PeriodArray behavior matches
+ the PeriodIndex behavior.
+ """
+ # TODO: non-monotone indexes; NaTs, different start dates
+ with warnings.catch_warnings():
+ # suppress deprecation of Period[B]
+ warnings.filterwarnings(
+ "ignore", message="Period with BDay freq", category=FutureWarning
+ )
+ freqstr = PeriodDtype(to_offset(freqstr))._freqstr
+ pi = pd.period_range(start=Timestamp("2000-01-01"), periods=100, freq=freqstr)
+ return pi
+
+
+@pytest.fixture
+def datetime_index(freqstr):
+ """
+ A fixture to provide DatetimeIndex objects with different frequencies.
+
+ Most DatetimeArray behavior is already tested in DatetimeIndex tests,
+ so here we just test that the DatetimeArray behavior matches
+ the DatetimeIndex behavior.
+ """
+ # TODO: non-monotone indexes; NaTs, different start dates, timezones
+ dti = pd.date_range(
+ start=Timestamp("2000-01-01"), periods=100, freq=freqstr, unit="ns"
+ )
+ return dti
+
+
+@pytest.fixture
+def timedelta_index():
+ """
+ A fixture to provide TimedeltaIndex objects with different frequencies.
+ Most TimedeltaArray behavior is already tested in TimedeltaIndex tests,
+ so here we just test that the TimedeltaArray behavior matches
+ the TimedeltaIndex behavior.
+ """
+ # TODO: flesh this out
+ return TimedeltaIndex(["1 Day", "3 Hours", "NaT"])
+
+
+class SharedTests:
+ index_cls: type[DatetimeIndex | PeriodIndex | TimedeltaIndex]
+
+ @pytest.fixture
+ def arr1d(self):
+ """Fixture returning DatetimeArray with daily frequency."""
+ data = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ if self.array_cls is PeriodArray:
+ arr = self.array_cls(data, freq="D")
+ else:
+ arr = self.index_cls(data, freq="D")._data
+ return arr
+
+ def test_compare_len1_raises(self, arr1d):
+ # make sure we raise when comparing with different lengths, specific
+ # to the case where one has length-1, which numpy would broadcast
+ arr = arr1d
+ idx = self.index_cls(arr)
+
+ with pytest.raises(ValueError, match="Lengths must match"):
+ arr == arr[:1]
+
+ # test the index classes while we're at it, GH#23078
+ with pytest.raises(ValueError, match="Lengths must match"):
+ idx <= idx[[0]]
+
+ @pytest.mark.parametrize(
+ "result",
+ [
+ pd.date_range("2020", periods=3),
+ pd.date_range("2020", periods=3, tz="UTC"),
+ pd.timedelta_range("0 days", periods=3),
+ pd.period_range("2020Q1", periods=3, freq="Q"),
+ ],
+ )
+ def test_compare_with_Categorical(self, result):
+ expected = pd.Categorical(result)
+ assert all(result == expected)
+ assert not any(result != expected)
+
+ @pytest.mark.parametrize("reverse", [True, False])
+ @pytest.mark.parametrize("as_index", [True, False])
+ def test_compare_categorical_dtype(self, arr1d, as_index, reverse, ordered):
+ other = pd.Categorical(arr1d, ordered=ordered)
+ if as_index:
+ other = pd.CategoricalIndex(other)
+
+ left, right = arr1d, other
+ if reverse:
+ left, right = right, left
+
+ ones = np.ones(arr1d.shape, dtype=bool)
+ zeros = ~ones
+
+ result = left == right
+ tm.assert_numpy_array_equal(result, ones)
+
+ result = left != right
+ tm.assert_numpy_array_equal(result, zeros)
+
+ if not reverse and not as_index:
+ # Otherwise Categorical raises TypeError bc it is not ordered
+ # TODO: we should probably get the same behavior regardless?
+ result = left < right
+ tm.assert_numpy_array_equal(result, zeros)
+
+ result = left <= right
+ tm.assert_numpy_array_equal(result, ones)
+
+ result = left > right
+ tm.assert_numpy_array_equal(result, zeros)
+
+ result = left >= right
+ tm.assert_numpy_array_equal(result, ones)
+
+ def test_take(self):
+ data = np.arange(100, dtype="i8") * 24 * 3600 * 10**9
+ np.random.default_rng(2).shuffle(data)
+
+ if self.array_cls is PeriodArray:
+ arr = PeriodArray(data, dtype="period[D]")
+ else:
+ arr = self.index_cls(data)._data
+ idx = self.index_cls._simple_new(arr)
+
+ takers = [1, 4, 94]
+ result = arr.take(takers)
+ expected = idx.take(takers)
+
+ tm.assert_index_equal(self.index_cls(result), expected)
+
+ takers = np.array([1, 4, 94])
+ result = arr.take(takers)
+ expected = idx.take(takers)
+
+ tm.assert_index_equal(self.index_cls(result), expected)
+
+ @pytest.mark.parametrize("fill_value", [2, 2.0, Timestamp(2021, 1, 1, 12).time])
+ def test_take_fill_raises(self, fill_value, arr1d):
+ msg = f"value should be a '{arr1d._scalar_type.__name__}' or 'NaT'. Got"
+ with pytest.raises(TypeError, match=msg):
+ arr1d.take([0, 1], allow_fill=True, fill_value=fill_value)
+
+ def test_take_fill(self, arr1d):
+ arr = arr1d
+
+ result = arr.take([-1, 1], allow_fill=True, fill_value=None)
+ assert result[0] is NaT
+
+ result = arr.take([-1, 1], allow_fill=True, fill_value=np.nan)
+ assert result[0] is NaT
+
+ result = arr.take([-1, 1], allow_fill=True, fill_value=NaT)
+ assert result[0] is NaT
+
+ @pytest.mark.filterwarnings(
+ "ignore:Period with BDay freq is deprecated:FutureWarning"
+ )
+ def test_take_fill_str(self, arr1d):
+ # Cast str fill_value matching other fill_value-taking methods
+ result = arr1d.take([-1, 1], allow_fill=True, fill_value=str(arr1d[-1]))
+ expected = arr1d[[-1, 1]]
+ tm.assert_equal(result, expected)
+
+ msg = f"value should be a '{arr1d._scalar_type.__name__}' or 'NaT'. Got"
+ with pytest.raises(TypeError, match=msg):
+ arr1d.take([-1, 1], allow_fill=True, fill_value="foo")
+
+ def test_concat_same_type(self, arr1d):
+ arr = arr1d
+ idx = self.index_cls(arr)
+ idx = idx.insert(0, NaT)
+ arr = arr1d
+
+ result = arr._concat_same_type([arr[:-1], arr[1:], arr])
+ arr2 = arr.astype(object)
+ expected = self.index_cls(np.concatenate([arr2[:-1], arr2[1:], arr2]))
+
+ tm.assert_index_equal(self.index_cls(result), expected)
+
+ def test_unbox_scalar(self, arr1d):
+ result = arr1d._unbox_scalar(arr1d[0])
+ expected = arr1d._ndarray.dtype.type
+ assert isinstance(result, expected)
+
+ result = arr1d._unbox_scalar(NaT)
+ assert isinstance(result, expected)
+
+ msg = f"'value' should be a {self.scalar_type.__name__}."
+ with pytest.raises(ValueError, match=msg):
+ arr1d._unbox_scalar("foo")
+
+ def test_check_compatible_with(self, arr1d):
+ arr1d._check_compatible_with(arr1d[0])
+ arr1d._check_compatible_with(arr1d[:1])
+ arr1d._check_compatible_with(NaT)
+
+ def test_scalar_from_string(self, arr1d):
+ result = arr1d._scalar_from_string(str(arr1d[0]))
+ assert result == arr1d[0]
+
+ def test_reduce_invalid(self, arr1d):
+ msg = "does not support operation 'not a method'"
+ with pytest.raises(TypeError, match=msg):
+ arr1d._reduce("not a method")
+
+ @pytest.mark.parametrize("method", ["pad", "backfill"])
+ def test_fillna_method_doesnt_change_orig(self, method):
+ data = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ if self.array_cls is PeriodArray:
+ arr = self.array_cls(data, dtype="period[D]")
+ else:
+ dtype = "M8[ns]" if self.array_cls is DatetimeArray else "m8[ns]"
+ arr = self.array_cls._from_sequence(data, dtype=np.dtype(dtype))
+ arr[4] = NaT
+
+ fill_value = arr[3] if method == "pad" else arr[5]
+
+ result = arr._pad_or_backfill(method=method)
+ assert result[4] == fill_value
+
+ # check that the original was not changed
+ assert arr[4] is NaT
+
+ def test_searchsorted(self):
+ data = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ if self.array_cls is PeriodArray:
+ arr = self.array_cls(data, dtype="period[D]")
+ else:
+ dtype = "M8[ns]" if self.array_cls is DatetimeArray else "m8[ns]"
+ arr = self.array_cls._from_sequence(data, dtype=np.dtype(dtype))
+
+ # scalar
+ result = arr.searchsorted(arr[1])
+ assert result == 1
+
+ result = arr.searchsorted(arr[2], side="right")
+ assert result == 3
+
+ # own-type
+ result = arr.searchsorted(arr[1:3])
+ expected = np.array([1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = arr.searchsorted(arr[1:3], side="right")
+ expected = np.array([2, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # GH#29884 match numpy convention on whether NaT goes
+ # at the end or the beginning
+ result = arr.searchsorted(NaT)
+ assert result == 10
+
+ @pytest.mark.parametrize("box", [None, "index", "series"])
+ def test_searchsorted_castable_strings(
+ self, arr1d, box, string_storage, using_infer_string
+ ):
+ arr = arr1d
+ if box is None:
+ pass
+ elif box == "index":
+ # Test the equivalent Index.searchsorted method while we're here
+ arr = self.index_cls(arr)
+ else:
+ # Test the equivalent Series.searchsorted method while we're here
+ arr = pd.Series(arr)
+
+ # scalar
+ result = arr.searchsorted(str(arr[1]))
+ assert result == 1
+
+ result = arr.searchsorted(str(arr[2]), side="right")
+ assert result == 3
+
+ result = arr.searchsorted([str(x) for x in arr[1:3]])
+ expected = np.array([1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ with pytest.raises(
+ TypeError,
+ match=re.escape(
+ f"value should be a '{arr1d._scalar_type.__name__}', 'NaT', "
+ "or array of those. Got 'str' instead."
+ ),
+ ):
+ arr.searchsorted("foo")
+
+ msg = re.escape(
+ f"value should be a '{arr1d._scalar_type.__name__}', 'NaT', "
+ "or array of those. Got str array instead."
+ )
+ if not using_infer_string:
+ msg = msg.replace("str", "string")
+ with pd.option_context("string_storage", string_storage):
+ with pytest.raises(
+ TypeError,
+ match=msg,
+ ):
+ arr.searchsorted([str(arr[1]), "baz"])
+
+ def test_getitem_near_implementation_bounds(self):
+ # We only check tz-naive for DTA bc the bounds are slightly different
+ # for other tzs
+ i8vals = np.asarray([NaT._value + n for n in range(1, 5)], dtype="i8")
+ if self.array_cls is PeriodArray:
+ arr = self.array_cls(i8vals, dtype="period[ns]")
+ else:
+ arr = self.index_cls(i8vals, freq="ns")._data
+ arr[0] # should not raise OutOfBoundsDatetime
+
+ index = pd.Index(arr)
+ index[0] # should not raise OutOfBoundsDatetime
+
+ ser = pd.Series(arr)
+ ser[0] # should not raise OutOfBoundsDatetime
+
+ def test_getitem_2d(self, arr1d):
+ # 2d slicing on a 1D array
+ expected = type(arr1d)._simple_new(
+ arr1d._ndarray[:, np.newaxis], dtype=arr1d.dtype
+ )
+ result = arr1d[:, np.newaxis]
+ tm.assert_equal(result, expected)
+
+ # Lookup on a 2D array
+ arr2d = expected
+ expected = type(arr2d)._simple_new(arr2d._ndarray[:3, 0], dtype=arr2d.dtype)
+ result = arr2d[:3, 0]
+ tm.assert_equal(result, expected)
+
+ # Scalar lookup
+ result = arr2d[-1, 0]
+ expected = arr1d[-1]
+ assert result == expected
+
+ def test_iter_2d(self, arr1d):
+ data2d = arr1d._ndarray[:3, np.newaxis]
+ arr2d = type(arr1d)._simple_new(data2d, dtype=arr1d.dtype)
+ result = list(arr2d)
+ assert len(result) == 3
+ for x in result:
+ assert isinstance(x, type(arr1d))
+ assert x.ndim == 1
+ assert x.dtype == arr1d.dtype
+
+ def test_repr_2d(self, arr1d):
+ data2d = arr1d._ndarray[:3, np.newaxis]
+ arr2d = type(arr1d)._simple_new(data2d, dtype=arr1d.dtype)
+
+ result = repr(arr2d)
+
+ if isinstance(arr2d, TimedeltaArray):
+ expected = (
+ f"<{type(arr2d).__name__}>\n"
+ "[\n"
+ f"['{arr1d[0]._repr_base()}'],\n"
+ f"['{arr1d[1]._repr_base()}'],\n"
+ f"['{arr1d[2]._repr_base()}']\n"
+ "]\n"
+ f"Shape: (3, 1), dtype: {arr1d.dtype}"
+ )
+ else:
+ expected = (
+ f"<{type(arr2d).__name__}>\n"
+ "[\n"
+ f"['{arr1d[0]}'],\n"
+ f"['{arr1d[1]}'],\n"
+ f"['{arr1d[2]}']\n"
+ "]\n"
+ f"Shape: (3, 1), dtype: {arr1d.dtype}"
+ )
+
+ assert result == expected
+
+ def test_setitem(self):
+ data = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ if self.array_cls is PeriodArray:
+ arr = self.array_cls(data, dtype="period[D]")
+ else:
+ arr = self.index_cls(data, freq="D")._data
+
+ arr[0] = arr[1]
+ expected = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ expected[0] = expected[1]
+
+ tm.assert_numpy_array_equal(arr.asi8, expected)
+
+ arr[:2] = arr[-2:]
+ expected[:2] = expected[-2:]
+ tm.assert_numpy_array_equal(arr.asi8, expected)
+
+ def test_setitem_list_of_nats(self, arr1d):
+ # GH#63420
+ arr1d[:] = [NaT] * len(arr1d)
+ assert arr1d.isna().all()
+
+ @pytest.mark.parametrize(
+ "box",
+ [
+ pd.Index,
+ pd.Series,
+ np.array,
+ list,
+ NumpyExtensionArray,
+ ],
+ )
+ def test_setitem_object_dtype(self, box, arr1d):
+ expected = arr1d.copy()[::-1]
+ if expected.dtype.kind in ["m", "M"]:
+ expected = expected._with_freq(None)
+
+ vals = expected
+ if box is list:
+ vals = list(vals)
+ elif box is np.array:
+ # if we do np.array(x).astype(object) then dt64 and td64 cast to ints
+ vals = np.array(vals.astype(object))
+ elif box is NumpyExtensionArray:
+ vals = box(np.asarray(vals, dtype=object))
+ else:
+ vals = box(vals).astype(object)
+
+ arr1d[:] = vals
+
+ tm.assert_equal(arr1d, expected)
+
+ def test_setitem_strs(self, arr1d):
+ # Check that we parse strs in both scalar and listlike
+
+ # Setting list-like of strs
+ expected = arr1d.copy()
+ expected[[0, 1]] = arr1d[-2:]
+
+ result = arr1d.copy()
+ result[:2] = [str(x) for x in arr1d[-2:]]
+ tm.assert_equal(result, expected)
+
+ # Same thing but now for just a scalar str
+ expected = arr1d.copy()
+ expected[0] = arr1d[-1]
+
+ result = arr1d.copy()
+ result[0] = str(arr1d[-1])
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("as_index", [True, False])
+ def test_setitem_categorical(self, arr1d, as_index):
+ expected = arr1d.copy()[::-1]
+ if not isinstance(expected, PeriodArray):
+ expected = expected._with_freq(None)
+
+ cat = pd.Categorical(arr1d)
+ if as_index:
+ cat = pd.CategoricalIndex(cat)
+
+ arr1d[:] = cat[::-1]
+
+ tm.assert_equal(arr1d, expected)
+
+ def test_setitem_raises(self, arr1d):
+ arr = arr1d[:10]
+ val = arr[0]
+
+ with pytest.raises(IndexError, match="index 12 is out of bounds"):
+ arr[12] = val
+
+ with pytest.raises(TypeError, match="value should be a.* 'object'"):
+ arr[0] = object()
+
+ msg = "cannot set using a list-like indexer with a different length"
+ with pytest.raises(ValueError, match=msg):
+ # GH#36339
+ arr[[]] = [arr[1]]
+
+ msg = "cannot set using a slice indexer with a different length than"
+ with pytest.raises(ValueError, match=msg):
+ # GH#36339
+ arr[1:1] = arr[:3]
+
+ @pytest.mark.parametrize("box", [list, np.array, pd.Index, pd.Series])
+ def test_setitem_numeric_raises(self, arr1d, box):
+ # We dont case e.g. int64 to our own dtype for setitem
+
+ msg = (
+ f"value should be a '{arr1d._scalar_type.__name__}', "
+ "'NaT', or array of those. Got"
+ )
+ with pytest.raises(TypeError, match=msg):
+ arr1d[:2] = box([0, 1])
+
+ with pytest.raises(TypeError, match=msg):
+ arr1d[:2] = box([0.0, 1.0])
+
+ def test_inplace_arithmetic(self):
+ # GH#24115 check that iadd and isub are actually in-place
+ data = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ if self.array_cls is PeriodArray:
+ arr = self.array_cls(data, dtype="period[D]")
+ else:
+ arr = self.index_cls(data, freq="D")._data
+
+ expected = arr + pd.Timedelta(days=1)
+ arr += pd.Timedelta(days=1)
+ tm.assert_equal(arr, expected)
+
+ expected = arr - pd.Timedelta(days=1)
+ arr -= pd.Timedelta(days=1)
+ tm.assert_equal(arr, expected)
+
+ def test_shift_fill_int_deprecated(self, arr1d):
+ # GH#31971, enforced in 2.0
+ with pytest.raises(TypeError, match="value should be a"):
+ arr1d.shift(1, fill_value=1)
+
+ def test_median(self, arr1d):
+ arr = arr1d
+ if len(arr) % 2 == 0:
+ # make it easier to define `expected`
+ arr = arr[:-1]
+
+ expected = arr[len(arr) // 2]
+
+ result = arr.median()
+ assert type(result) is type(expected)
+ assert result == expected
+
+ arr[len(arr) // 2] = NaT
+ if not isinstance(expected, Period):
+ expected = arr[len(arr) // 2 - 1 : len(arr) // 2 + 2].mean()
+
+ assert arr.median(skipna=False) is NaT
+
+ result = arr.median()
+ assert type(result) is type(expected)
+ assert result == expected
+
+ assert arr[:0].median() is NaT
+ assert arr[:0].median(skipna=False) is NaT
+
+ # 2d Case
+ arr2 = arr.reshape(-1, 1)
+
+ result = arr2.median(axis=None)
+ assert type(result) is type(expected)
+ assert result == expected
+
+ assert arr2.median(axis=None, skipna=False) is NaT
+
+ result = arr2.median(axis=0)
+ expected2 = type(arr)._from_sequence([expected], dtype=arr.dtype)
+ tm.assert_equal(result, expected2)
+
+ result = arr2.median(axis=0, skipna=False)
+ expected2 = type(arr)._from_sequence([NaT], dtype=arr.dtype)
+ tm.assert_equal(result, expected2)
+
+ result = arr2.median(axis=1)
+ tm.assert_equal(result, arr)
+
+ result = arr2.median(axis=1, skipna=False)
+ tm.assert_equal(result, arr)
+
+ def test_from_integer_array(self):
+ arr = np.array([1, 2, 3], dtype=np.int64)
+ data = pd.array(arr, dtype="Int64")
+ if self.array_cls is PeriodArray:
+ expected = self.array_cls(arr, dtype=self.example_dtype)
+ result = self.array_cls(data, dtype=self.example_dtype)
+ else:
+ expected = self.array_cls._from_sequence(arr, dtype=self.example_dtype)
+ result = self.array_cls._from_sequence(data, dtype=self.example_dtype)
+
+ tm.assert_extension_array_equal(result, expected)
+
+
+class TestDatetimeArray(SharedTests):
+ index_cls = DatetimeIndex
+ array_cls = DatetimeArray
+ scalar_type = Timestamp
+ example_dtype = "M8[ns]"
+
+ @pytest.fixture
+ def arr1d(self, tz_naive_fixture, freqstr):
+ """
+ Fixture returning DatetimeArray with parametrized frequency and
+ timezones
+ """
+ tz = tz_naive_fixture
+ dti = pd.date_range(
+ "2016-01-01 01:01:00", periods=5, freq=freqstr, tz=tz, unit="ns"
+ )
+ dta = dti._data
+ return dta
+
+ def test_round(self, arr1d):
+ # GH#24064
+ dti = self.index_cls(arr1d)
+
+ result = dti.round(freq="2min")
+ expected = dti - pd.Timedelta(minutes=1)
+ expected = expected._with_freq(None)
+ tm.assert_index_equal(result, expected)
+
+ dta = dti._data
+ result = dta.round(freq="2min")
+ expected = expected._data._with_freq(None)
+ tm.assert_datetime_array_equal(result, expected)
+
+ def test_array_interface(self, datetime_index):
+ arr = datetime_index._data
+ copy_false = None if np_version_gt2 else False
+
+ # default asarray gives the same underlying data (for tz naive)
+ result = np.asarray(arr)
+ expected = arr._ndarray
+ assert result is expected
+ tm.assert_numpy_array_equal(result, expected)
+ result = np.array(arr, copy=copy_false)
+ assert result is expected
+ tm.assert_numpy_array_equal(result, expected)
+
+ # specifying M8[ns] gives the same result as default
+ result = np.asarray(arr, dtype="datetime64[ns]")
+ expected = arr._ndarray
+ assert result is expected
+ tm.assert_numpy_array_equal(result, expected)
+ result = np.array(arr, dtype="datetime64[ns]", copy=copy_false)
+ assert result is expected
+ tm.assert_numpy_array_equal(result, expected)
+ result = np.array(arr, dtype="datetime64[ns]")
+ if not np_version_gt2:
+ # TODO: GH 57739
+ assert result is not expected
+ tm.assert_numpy_array_equal(result, expected)
+
+ # to object dtype
+ result = np.asarray(arr, dtype=object)
+ expected = np.array(list(arr), dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # to other dtype always copies
+ result = np.asarray(arr, dtype="int64")
+ assert result is not arr.asi8
+ assert not np.may_share_memory(arr, result)
+ expected = arr.asi8.copy()
+ tm.assert_numpy_array_equal(result, expected)
+
+ # other dtypes handled by numpy
+ for dtype in ["float64", str]:
+ result = np.asarray(arr, dtype=dtype)
+ expected = np.asarray(arr).astype(dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_array_object_dtype(self, arr1d):
+ # GH#23524
+ arr = arr1d
+ dti = self.index_cls(arr1d)
+
+ expected = np.array(list(dti))
+
+ result = np.array(arr, dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # also test the DatetimeIndex method while we're at it
+ result = np.array(dti, dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_array_tz(self, arr1d):
+ # GH#23524
+ arr = arr1d
+ dti = self.index_cls(arr1d, copy=False)
+ copy_false = None if np_version_gt2 else False
+
+ expected = dti.asi8.view("M8[ns]")
+ result = np.array(arr, dtype="M8[ns]")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.array(arr, dtype="datetime64[ns]")
+ tm.assert_numpy_array_equal(result, expected)
+
+ # check that we are not making copies when setting copy=copy_false
+ result = np.array(arr, dtype="M8[ns]", copy=copy_false)
+ assert result.base is expected.base
+ assert result.base is not None
+ result = np.array(arr, dtype="datetime64[ns]", copy=copy_false)
+ assert result.base is expected.base
+ assert result.base is not None
+
+ def test_array_i8_dtype(self, arr1d):
+ arr = arr1d
+ dti = self.index_cls(arr1d)
+ copy_false = None if np_version_gt2 else False
+
+ expected = dti.asi8
+ result = np.array(arr, dtype="i8")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.array(arr, dtype=np.int64)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # check that we are still making copies when setting copy=copy_false
+ result = np.array(arr, dtype="i8", copy=copy_false)
+ assert result.base is not expected.base
+ assert result.base is None
+
+ def test_from_array_keeps_base(self):
+ # Ensure that DatetimeArray._ndarray.base isn't lost.
+ arr = np.array(["2000-01-01", "2000-01-02"], dtype="M8[ns]")
+ dta = DatetimeArray._from_sequence(arr, dtype=arr.dtype)
+
+ assert dta._ndarray is arr
+ dta = DatetimeArray._from_sequence(arr[:0], dtype=arr.dtype)
+ assert dta._ndarray.base is arr
+
+ def test_from_dti(self, arr1d):
+ arr = arr1d
+ dti = self.index_cls(arr1d)
+ assert list(dti) == list(arr)
+
+ # Check that Index.__new__ knows what to do with DatetimeArray
+ dti2 = pd.Index(arr)
+ assert isinstance(dti2, DatetimeIndex)
+ assert list(dti2) == list(arr)
+
+ def test_astype_object(self, arr1d):
+ arr = arr1d
+ dti = self.index_cls(arr1d)
+
+ asobj = arr.astype("O")
+ assert isinstance(asobj, np.ndarray)
+ assert asobj.dtype == "O"
+ assert list(asobj) == list(dti)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_to_period(self, datetime_index, freqstr):
+ dti = datetime_index
+ arr = dti._data
+
+ freqstr = PeriodDtype(to_offset(freqstr))._freqstr
+ expected = dti.to_period(freq=freqstr)
+ result = arr.to_period(freq=freqstr)
+ assert isinstance(result, PeriodArray)
+
+ tm.assert_equal(result, expected._data)
+
+ def test_to_period_2d(self, arr1d):
+ arr2d = arr1d.reshape(1, -1)
+
+ warn = None if arr1d.tz is None else UserWarning
+ with tm.assert_produces_warning(warn, match="will drop timezone information"):
+ result = arr2d.to_period("D")
+ expected = arr1d.to_period("D").reshape(1, -1)
+ tm.assert_period_array_equal(result, expected)
+
+ @pytest.mark.parametrize("propname", DatetimeArray._bool_ops)
+ def test_bool_properties(self, arr1d, propname):
+ # in this case _bool_ops is just `is_leap_year`
+ dti = self.index_cls(arr1d)
+ arr = arr1d
+ assert dti.freq == arr.freq
+
+ result = getattr(arr, propname)
+ expected = np.array(getattr(dti, propname), dtype=result.dtype)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("propname", DatetimeArray._field_ops)
+ def test_int_properties(self, arr1d, propname):
+ dti = self.index_cls(arr1d)
+ arr = arr1d
+
+ result = getattr(arr, propname)
+ expected = np.array(getattr(dti, propname), dtype=result.dtype)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_take_fill_valid(self, arr1d, fixed_now_ts):
+ arr = arr1d
+ dti = self.index_cls(arr1d)
+
+ now = fixed_now_ts.tz_localize(dti.tz)
+ result = arr.take([-1, 1], allow_fill=True, fill_value=now)
+ assert result[0] == now
+
+ msg = f"value should be a '{arr1d._scalar_type.__name__}' or 'NaT'. Got"
+ with pytest.raises(TypeError, match=msg):
+ # fill_value Timedelta invalid
+ arr.take([-1, 1], allow_fill=True, fill_value=now - now)
+
+ with pytest.raises(TypeError, match=msg):
+ # fill_value Period invalid
+ arr.take([-1, 1], allow_fill=True, fill_value=Period("2014Q1"))
+
+ tz = None if dti.tz is not None else "US/Eastern"
+ now = fixed_now_ts.tz_localize(tz)
+ msg = "Cannot compare tz-naive and tz-aware datetime-like objects"
+ with pytest.raises(TypeError, match=msg):
+ # Timestamp with mismatched tz-awareness
+ arr.take([-1, 1], allow_fill=True, fill_value=now)
+
+ value = NaT._value
+ msg = f"value should be a '{arr1d._scalar_type.__name__}' or 'NaT'. Got"
+ with pytest.raises(TypeError, match=msg):
+ # require NaT, not iNaT, as it could be confused with an integer
+ arr.take([-1, 1], allow_fill=True, fill_value=value)
+
+ value = np.timedelta64("NaT", "ns")
+ with pytest.raises(TypeError, match=msg):
+ # require appropriate-dtype if we have an NA value
+ arr.take([-1, 1], allow_fill=True, fill_value=value)
+
+ if arr.tz is not None:
+ # GH#37356
+ # Assuming here that arr1d fixture does not include Australia/Melbourne
+ value = fixed_now_ts.tz_localize("Australia/Melbourne")
+ result = arr.take([-1, 1], allow_fill=True, fill_value=value)
+
+ expected = arr.take(
+ [-1, 1],
+ allow_fill=True,
+ fill_value=value.tz_convert(arr.dtype.tz),
+ )
+ tm.assert_equal(result, expected)
+
+ def test_concat_same_type_invalid(self, arr1d):
+ # different timezones
+ arr = arr1d
+
+ if arr.tz is None:
+ other = arr.tz_localize("UTC")
+ else:
+ other = arr.tz_localize(None)
+
+ with pytest.raises(ValueError, match="to_concat must have the same"):
+ arr._concat_same_type([arr, other])
+
+ def test_concat_same_type_different_freq(self, unit):
+ # we *can* concatenate DTI with different freqs.
+ a = pd.date_range("2000", periods=2, freq="D", tz="US/Central", unit=unit)._data
+ b = pd.date_range("2000", periods=2, freq="h", tz="US/Central", unit=unit)._data
+ result = DatetimeArray._concat_same_type([a, b])
+ expected = (
+ pd.to_datetime(
+ [
+ "2000-01-01 00:00:00",
+ "2000-01-02 00:00:00",
+ "2000-01-01 00:00:00",
+ "2000-01-01 01:00:00",
+ ]
+ )
+ .tz_localize("US/Central")
+ .as_unit(unit)
+ ._data
+ )
+
+ tm.assert_datetime_array_equal(result, expected)
+
+ def test_strftime(self, arr1d, using_infer_string):
+ arr = arr1d
+
+ result = arr.strftime("%Y %b")
+ expected = np.array([ts.strftime("%Y %b") for ts in arr], dtype=object)
+ if using_infer_string:
+ expected = pd.array(expected, dtype=pd.StringDtype(na_value=np.nan))
+ tm.assert_equal(result, expected)
+
+ def test_strftime_nat(self, using_infer_string):
+ # GH 29578
+ arr = DatetimeIndex(["2019-01-01", NaT])._data
+
+ result = arr.strftime("%Y-%m-%d")
+ expected = np.array(["2019-01-01", np.nan], dtype=object)
+ if using_infer_string:
+ expected = pd.array(expected, dtype=pd.StringDtype(na_value=np.nan))
+ tm.assert_equal(result, expected)
+
+
+class TestTimedeltaArray(SharedTests):
+ index_cls = TimedeltaIndex
+ array_cls = TimedeltaArray
+ scalar_type = pd.Timedelta
+ example_dtype = "m8[ns]"
+
+ def test_from_tdi(self):
+ tdi = TimedeltaIndex(["1 Day", "3 Hours"])
+ arr = tdi._data
+ assert list(arr) == list(tdi)
+
+ # Check that Index.__new__ knows what to do with TimedeltaArray
+ tdi2 = pd.Index(arr)
+ assert isinstance(tdi2, TimedeltaIndex)
+ assert list(tdi2) == list(arr)
+
+ def test_astype_object(self):
+ tdi = TimedeltaIndex(["1 Day", "3 Hours"])
+ arr = tdi._data
+ asobj = arr.astype("O")
+ assert isinstance(asobj, np.ndarray)
+ assert asobj.dtype == "O"
+ assert list(asobj) == list(tdi)
+
+ def test_to_pytimedelta(self, timedelta_index):
+ tdi = timedelta_index
+ arr = tdi._data
+
+ expected = tdi.to_pytimedelta()
+ result = arr.to_pytimedelta()
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_total_seconds(self, timedelta_index):
+ tdi = timedelta_index
+ arr = tdi._data
+
+ expected = tdi.total_seconds()
+ result = arr.total_seconds()
+
+ tm.assert_numpy_array_equal(result, expected.values)
+
+ @pytest.mark.parametrize("propname", TimedeltaArray._field_ops)
+ def test_int_properties(self, timedelta_index, propname):
+ tdi = timedelta_index
+ arr = tdi._data
+
+ result = getattr(arr, propname)
+ expected = np.array(getattr(tdi, propname), dtype=result.dtype)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_array_interface(self, timedelta_index):
+ arr = timedelta_index._data
+ copy_false = None if np_version_gt2 else False
+
+ # default asarray gives the same underlying data
+ result = np.asarray(arr)
+ expected = arr._ndarray
+ assert result is expected
+ tm.assert_numpy_array_equal(result, expected)
+ result = np.array(arr, copy=copy_false)
+ assert result is expected
+ tm.assert_numpy_array_equal(result, expected)
+
+ # specifying m8[us] gives the same result as default
+ result = np.asarray(arr, dtype="timedelta64[us]")
+ expected = arr._ndarray
+ assert result is expected
+ tm.assert_numpy_array_equal(result, expected)
+ result = np.array(arr, dtype="timedelta64[us]", copy=copy_false)
+ assert result is expected
+ tm.assert_numpy_array_equal(result, expected)
+ result = np.array(arr, dtype="timedelta64[us]")
+ if not np_version_gt2:
+ # TODO: GH 57739
+ assert result is not expected
+ tm.assert_numpy_array_equal(result, expected)
+
+ # to object dtype
+ result = np.asarray(arr, dtype=object)
+ expected = np.array(list(arr), dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # to other dtype always copies
+ result = np.asarray(arr, dtype="int64")
+ assert result is not arr.asi8
+ assert not np.may_share_memory(arr, result)
+ expected = arr.asi8.copy()
+ tm.assert_numpy_array_equal(result, expected)
+
+ # other dtypes handled by numpy
+ for dtype in ["float64", str]:
+ result = np.asarray(arr, dtype=dtype)
+ expected = np.asarray(arr).astype(dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_take_fill_valid(self, timedelta_index, fixed_now_ts):
+ tdi = timedelta_index
+ arr = tdi._data
+
+ td1 = pd.Timedelta(days=1)
+ result = arr.take([-1, 1], allow_fill=True, fill_value=td1)
+ assert result[0] == td1
+
+ value = fixed_now_ts
+ msg = f"value should be a '{arr._scalar_type.__name__}' or 'NaT'. Got"
+ with pytest.raises(TypeError, match=msg):
+ # fill_value Timestamp invalid
+ arr.take([0, 1], allow_fill=True, fill_value=value)
+
+ value = fixed_now_ts.to_period("D")
+ with pytest.raises(TypeError, match=msg):
+ # fill_value Period invalid
+ arr.take([0, 1], allow_fill=True, fill_value=value)
+
+ value = np.datetime64("NaT", "ns")
+ with pytest.raises(TypeError, match=msg):
+ # require appropriate-dtype if we have an NA value
+ arr.take([-1, 1], allow_fill=True, fill_value=value)
+
+
+@pytest.mark.filterwarnings(r"ignore:Period with BDay freq is deprecated:FutureWarning")
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+class TestPeriodArray(SharedTests):
+ index_cls = PeriodIndex
+ array_cls = PeriodArray
+ scalar_type = Period
+ example_dtype = PeriodIndex([], freq="W").dtype
+
+ @pytest.fixture
+ def arr1d(self, period_index):
+ """
+ Fixture returning DatetimeArray from parametrized PeriodIndex objects
+ """
+ return period_index._data
+
+ def test_from_pi(self, arr1d):
+ pi = self.index_cls(arr1d)
+ arr = arr1d
+ assert list(arr) == list(pi)
+
+ # Check that Index.__new__ knows what to do with PeriodArray
+ pi2 = pd.Index(arr)
+ assert isinstance(pi2, PeriodIndex)
+ assert list(pi2) == list(arr)
+
+ def test_astype_object(self, arr1d):
+ pi = self.index_cls(arr1d)
+ arr = arr1d
+ asobj = arr.astype("O")
+ assert isinstance(asobj, np.ndarray)
+ assert asobj.dtype == "O"
+ assert list(asobj) == list(pi)
+
+ def test_take_fill_valid(self, arr1d):
+ arr = arr1d
+
+ value = NaT._value
+ msg = f"value should be a '{arr1d._scalar_type.__name__}' or 'NaT'. Got"
+ with pytest.raises(TypeError, match=msg):
+ # require NaT, not iNaT, as it could be confused with an integer
+ arr.take([-1, 1], allow_fill=True, fill_value=value)
+
+ value = np.timedelta64("NaT", "ns")
+ with pytest.raises(TypeError, match=msg):
+ # require appropriate-dtype if we have an NA value
+ arr.take([-1, 1], allow_fill=True, fill_value=value)
+
+ @pytest.mark.parametrize("how", ["S", "E"])
+ def test_to_timestamp(self, how, arr1d):
+ pi = self.index_cls(arr1d)
+ arr = arr1d
+
+ expected = DatetimeIndex(pi.to_timestamp(how=how))._data
+ result = arr.to_timestamp(how=how)
+ assert isinstance(result, DatetimeArray)
+
+ tm.assert_equal(result, expected)
+
+ def test_to_timestamp_roundtrip_bday(self):
+ # Case where infer_freq inside would choose "D" instead of "B"
+ dta = pd.date_range("2021-10-18", periods=3, freq="B", unit="ns")._data
+ parr = dta.to_period()
+ result = parr.to_timestamp()
+ assert result.freq == "B"
+ tm.assert_extension_array_equal(result, dta.as_unit("us"))
+
+ dta2 = dta[::2]
+ parr2 = dta2.to_period()
+ result2 = parr2.to_timestamp()
+ assert result2.freq == "2B"
+ tm.assert_extension_array_equal(result2, dta2.as_unit("us"))
+
+ parr3 = dta.to_period("2B")
+ result3 = parr3.to_timestamp()
+ assert result3.freq == "B"
+ tm.assert_extension_array_equal(result3, dta.as_unit("us"))
+
+ def test_to_timestamp_out_of_bounds(self):
+ # GH#19643 previously overflowed silently
+ pi = pd.period_range("1500", freq="Y", periods=3)
+ pi.to_timestamp()
+ dta = pi._data.to_timestamp()
+ assert dta[0] == Timestamp(1500, 1, 1)
+
+ @pytest.mark.parametrize("propname", PeriodArray._bool_ops)
+ def test_bool_properties(self, arr1d, propname):
+ # in this case _bool_ops is just `is_leap_year`
+ pi = self.index_cls(arr1d)
+ arr = arr1d
+
+ result = getattr(arr, propname)
+ expected = np.array(getattr(pi, propname))
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("propname", PeriodArray._field_ops)
+ def test_int_properties(self, arr1d, propname):
+ pi = self.index_cls(arr1d)
+ arr = arr1d
+
+ result = getattr(arr, propname)
+ expected = np.array(getattr(pi, propname))
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_array_interface(self, arr1d):
+ arr = arr1d
+
+ # default asarray gives objects
+ result = np.asarray(arr)
+ expected = np.array(list(arr), dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # to object dtype (same as default)
+ result = np.asarray(arr, dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # to int64 gives the underlying representation
+ result = np.asarray(arr, dtype="int64")
+ tm.assert_numpy_array_equal(result, arr.asi8)
+
+ result2 = np.asarray(arr, dtype="int64")
+ assert np.may_share_memory(result, result2)
+
+ result_copy1 = np.array(arr, dtype="int64", copy=True)
+ result_copy2 = np.array(arr, dtype="int64", copy=True)
+ assert not np.may_share_memory(result_copy1, result_copy2)
+
+ # to other dtypes
+ msg = r"float\(\) argument must be a string or a( real)? number, not 'Period'"
+ with pytest.raises(TypeError, match=msg):
+ np.asarray(arr, dtype="float64")
+
+ result = np.asarray(arr, dtype="S20")
+ expected = np.asarray(arr).astype("S20")
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_strftime(self, arr1d, using_infer_string):
+ arr = arr1d
+
+ result = arr.strftime("%Y")
+ expected = np.array([per.strftime("%Y") for per in arr], dtype=object)
+ if using_infer_string:
+ expected = pd.array(expected, dtype=pd.StringDtype(na_value=np.nan))
+ tm.assert_equal(result, expected)
+
+ def test_strftime_nat(self, using_infer_string):
+ # GH 29578
+ arr = PeriodArray(PeriodIndex(["2019-01-01", NaT], dtype="period[D]"))
+
+ result = arr.strftime("%Y-%m-%d")
+ expected = np.array(["2019-01-01", np.nan], dtype=object)
+ if using_infer_string:
+ expected = pd.array(expected, dtype=pd.StringDtype(na_value=np.nan))
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "arr,casting_nats",
+ [
+ (
+ TimedeltaIndex(["1 Day", "3 Hours", "NaT"])._data,
+ (NaT, np.timedelta64("NaT", "ns")),
+ ),
+ (
+ pd.date_range("2000-01-01", periods=3, freq="D")._data,
+ (NaT, np.datetime64("NaT", "ns")),
+ ),
+ (pd.period_range("2000-01-01", periods=3, freq="D")._data, (NaT,)),
+ ],
+ ids=lambda x: type(x).__name__,
+)
+def test_casting_nat_setitem_array(arr, casting_nats):
+ expected = type(arr)._from_sequence([NaT, arr[1], arr[2]], dtype=arr.dtype)
+
+ for nat in casting_nats:
+ arr = arr.copy()
+ arr[0] = nat
+ tm.assert_equal(arr, expected)
+
+
+@pytest.mark.parametrize(
+ "arr,non_casting_nats",
+ [
+ (
+ TimedeltaIndex(["1 Day", "3 Hours", "NaT"])._data,
+ (np.datetime64("NaT", "ns"), NaT._value),
+ ),
+ (
+ pd.date_range("2000-01-01", periods=3, freq="D")._data,
+ (np.timedelta64("NaT", "ns"), NaT._value),
+ ),
+ (
+ pd.period_range("2000-01-01", periods=3, freq="D")._data,
+ (np.datetime64("NaT", "ns"), np.timedelta64("NaT", "ns"), NaT._value),
+ ),
+ ],
+ ids=lambda x: type(x).__name__,
+)
+def test_invalid_nat_setitem_array(arr, non_casting_nats):
+ msg = (
+ "value should be a '(Timestamp|Timedelta|Period)', 'NaT', or array of those. "
+ "Got '(timedelta64|datetime64|int)' instead."
+ )
+
+ for nat in non_casting_nats:
+ with pytest.raises(TypeError, match=msg):
+ arr[0] = nat
+
+
+@pytest.mark.parametrize(
+ "arr",
+ [
+ pd.date_range("2000", periods=4)._values,
+ pd.timedelta_range("2000", periods=4)._values,
+ ],
+)
+def test_to_numpy_extra(arr):
+ arr[0] = NaT
+ original = arr.copy()
+
+ result = arr.to_numpy()
+ assert np.isnan(result[0])
+
+ result = arr.to_numpy(dtype="int64")
+ assert result[0] == -9223372036854775808
+
+ result = arr.to_numpy(dtype="int64", na_value=0)
+ assert result[0] == 0
+
+ result = arr.to_numpy(na_value=arr[1].to_numpy())
+ assert result[0] == result[1]
+
+ result = arr.to_numpy(na_value=arr[1].to_numpy(copy=False))
+ assert result[0] == result[1]
+
+ tm.assert_equal(arr, original)
+
+
+@pytest.mark.parametrize(
+ "arr",
+ [
+ pd.date_range("2000", periods=4)._values,
+ pd.timedelta_range("2000", periods=4)._values,
+ ],
+)
+def test_to_numpy_extra_readonly(arr):
+ arr[0] = NaT
+ original = arr.copy()
+ arr._readonly = True
+
+ result = arr.to_numpy(dtype=object)
+ assert result.flags.writeable
+
+ # numpy does not do zero-copy conversion from M8 to i8
+ result = arr.to_numpy(dtype="int64")
+ assert result.flags.writeable
+
+ tm.assert_equal(arr, original)
+
+
+@pytest.mark.parametrize("as_index", [True, False])
+@pytest.mark.parametrize(
+ "values",
+ [
+ pd.to_datetime(["2020-01-01", "2020-02-01"]),
+ pd.to_timedelta([1, 2], unit="D"),
+ PeriodIndex(["2020-01-01", "2020-02-01"], freq="D"),
+ ],
+)
+@pytest.mark.parametrize(
+ "klass",
+ [
+ list,
+ np.array,
+ pd.array,
+ pd.Series,
+ pd.Index,
+ pd.Categorical,
+ pd.CategoricalIndex,
+ ],
+)
+def test_searchsorted_datetimelike_with_listlike(values, klass, as_index):
+ # https://github.com/pandas-dev/pandas/issues/32762
+ if not as_index:
+ values = values._data
+
+ result = values.searchsorted(klass(values))
+ expected = np.array([0, 1], dtype=result.dtype)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "values",
+ [
+ pd.to_datetime(["2020-01-01", "2020-02-01"]),
+ pd.to_timedelta([1, 2], unit="D"),
+ PeriodIndex(["2020-01-01", "2020-02-01"], freq="D"),
+ ],
+)
+@pytest.mark.parametrize(
+ "arg", [[1, 2], ["a", "b"], [Timestamp("2020-01-01", tz="Europe/London")] * 2]
+)
+def test_searchsorted_datetimelike_with_listlike_invalid_dtype(values, arg):
+ # https://github.com/pandas-dev/pandas/issues/32762
+ msg = "[Unexpected type|Cannot compare]"
+ with pytest.raises(TypeError, match=msg):
+ values.searchsorted(arg)
+
+
+@pytest.mark.parametrize("klass", [list, tuple, np.array, pd.Series])
+def test_period_index_construction_from_strings(klass):
+ # https://github.com/pandas-dev/pandas/issues/26109
+ strings = ["2020Q1", "2020Q2"] * 2
+ data = klass(strings)
+ result = PeriodIndex(data, freq="Q")
+ expected = PeriodIndex([Period(s) for s in strings])
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["M8[ns]", "m8[ns]"])
+def test_from_pandas_array(dtype):
+ # GH#24615
+ data = np.array([1, 2, 3], dtype=dtype)
+ arr = NumpyExtensionArray(data)
+
+ cls = {"M8[ns]": DatetimeArray, "m8[ns]": TimedeltaArray}[dtype]
+
+ result = cls._from_sequence(arr, dtype=dtype)
+ expected = cls._from_sequence(data, dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+ func = {"M8[ns]": pd.to_datetime, "m8[ns]": pd.to_timedelta}[dtype]
+ result = func(arr).array
+ expected = func(data).array
+ tm.assert_equal(result, expected)
+
+ # Let's check the Indexes while we're here
+ idx_cls = {"M8[ns]": DatetimeIndex, "m8[ns]": TimedeltaIndex}[dtype]
+ result = idx_cls(arr)
+ expected = idx_cls(data)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_datetimes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_datetimes.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f1be30e5c0221d73abc148e15922db225d8dfed
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_datetimes.py
@@ -0,0 +1,848 @@
+"""
+Tests for DatetimeArray
+"""
+
+from __future__ import annotations
+
+from datetime import timedelta
+import operator
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import tz_compare
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.dtypes import DatetimeTZDtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.arrays import (
+ DatetimeArray,
+ TimedeltaArray,
+)
+
+
+class TestNonNano:
+ @pytest.fixture(params=["s", "ms", "us"])
+ def unit(self, request):
+ """Fixture returning parametrized time units"""
+ return request.param
+
+ @pytest.fixture
+ def dtype(self, unit, tz_naive_fixture):
+ tz = tz_naive_fixture
+ if tz is None:
+ return np.dtype(f"datetime64[{unit}]")
+ else:
+ return DatetimeTZDtype(unit=unit, tz=tz)
+
+ @pytest.fixture
+ def dta_dti(self, unit, dtype):
+ tz = getattr(dtype, "tz", None)
+
+ dti = pd.date_range("2016-01-01", periods=55, freq="D", tz=tz, unit="ns")
+ if tz is None:
+ arr = np.asarray(dti).astype(f"M8[{unit}]")
+ else:
+ arr = np.asarray(dti.tz_convert("UTC").tz_localize(None)).astype(
+ f"M8[{unit}]"
+ )
+
+ dta = DatetimeArray._simple_new(arr, dtype=dtype)
+ return dta, dti
+
+ @pytest.fixture
+ def dta(self, dta_dti):
+ dta, dti = dta_dti
+ return dta
+
+ def test_non_nano(self, unit, dtype):
+ arr = np.arange(5, dtype=np.int64).view(f"M8[{unit}]")
+ dta = DatetimeArray._simple_new(arr, dtype=dtype)
+
+ assert dta.dtype == dtype
+ assert dta[0].unit == unit
+ assert tz_compare(dta.tz, dta[0].tz)
+ assert (dta[0] == dta[:1]).all()
+
+ @pytest.mark.parametrize(
+ "field", DatetimeArray._field_ops + DatetimeArray._bool_ops
+ )
+ def test_fields(self, unit, field, dtype, dta_dti):
+ dta, dti = dta_dti
+
+ assert (dti == dta).all()
+
+ res = getattr(dta, field)
+ expected = getattr(dti._data, field)
+ tm.assert_numpy_array_equal(res, expected)
+
+ def test_normalize(self, unit):
+ dti = pd.date_range("2016-01-01 06:00:00", periods=55, freq="D")
+ arr = np.asarray(dti).astype(f"M8[{unit}]")
+
+ dta = DatetimeArray._simple_new(arr, dtype=arr.dtype)
+
+ assert not dta.is_normalized
+
+ # TODO: simplify once we can just .astype to other unit
+ exp = np.asarray(dti.normalize()).astype(f"M8[{unit}]")
+ expected = DatetimeArray._simple_new(exp, dtype=exp.dtype)
+
+ res = dta.normalize()
+ tm.assert_extension_array_equal(res, expected)
+
+ def test_normalize_overflow_raises(self):
+ # GH#60583
+ ts = pd.Timestamp.min
+ dta = DatetimeArray._from_sequence([ts], dtype="M8[ns]")
+
+ msg = "Cannot normalize Timestamp without integer overflow"
+ with pytest.raises(ValueError, match=msg):
+ dta.normalize()
+
+ def test_simple_new_requires_match(self, unit):
+ arr = np.arange(5, dtype=np.int64).view(f"M8[{unit}]")
+ dtype = DatetimeTZDtype(unit, "UTC")
+
+ dta = DatetimeArray._simple_new(arr, dtype=dtype)
+ assert dta.dtype == dtype
+
+ wrong = DatetimeTZDtype("ns", "UTC")
+ with pytest.raises(AssertionError, match="^$"):
+ DatetimeArray._simple_new(arr, dtype=wrong)
+
+ def test_std_non_nano(self, unit):
+ dti = pd.date_range("2016-01-01", periods=55, freq="D", unit="ns")
+ arr = np.asarray(dti).astype(f"M8[{unit}]")
+
+ dta = DatetimeArray._simple_new(arr, dtype=arr.dtype)
+
+ # we should match the nano-reso std, but floored to our reso.
+ res = dta.std()
+ assert res._creso == dta._creso
+ assert res == dti.std().floor(unit)
+
+ @pytest.mark.filterwarnings("ignore:Converting to PeriodArray.*:UserWarning")
+ def test_to_period(self, dta_dti):
+ dta, dti = dta_dti
+ result = dta.to_period("D")
+ expected = dti._data.to_period("D")
+
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_iter(self, dta):
+ res = next(iter(dta))
+ expected = dta[0]
+
+ assert type(res) is pd.Timestamp
+ assert res._value == expected._value
+ assert res._creso == expected._creso
+ assert res == expected
+
+ def test_astype_object(self, dta):
+ result = dta.astype(object)
+ assert all(x._creso == dta._creso for x in result)
+ assert all(x == y for x, y in zip(result, dta, strict=True))
+
+ def test_to_pydatetime(self, dta_dti):
+ dta, dti = dta_dti
+
+ result = dta.to_pydatetime()
+ expected = dti.to_pydatetime()
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("meth", ["time", "timetz", "date"])
+ def test_time_date(self, dta_dti, meth):
+ dta, dti = dta_dti
+
+ result = getattr(dta, meth)
+ expected = getattr(dti, meth)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_format_native_types(self, unit, dtype, dta_dti):
+ # In this case we should get the same formatted values with our nano
+ # version dti._data as we do with the non-nano dta
+ dta, dti = dta_dti
+
+ res = dta._format_native_types()
+ exp = dti._data._format_native_types()
+ tm.assert_numpy_array_equal(res, exp)
+
+ def test_repr(self, dta_dti, unit):
+ dta, dti = dta_dti
+
+ assert repr(dta) == repr(dti._data).replace("[ns", f"[{unit}")
+
+ # TODO: tests with td64
+ def test_compare_mismatched_resolutions(self, comparison_op):
+ # comparison that numpy gets wrong bc of silent overflows
+ op = comparison_op
+
+ iinfo = np.iinfo(np.int64)
+ vals = np.array([iinfo.min, iinfo.min + 1, iinfo.max], dtype=np.int64)
+
+ # Construct so that arr2[1] < arr[1] < arr[2] < arr2[2]
+ arr = np.array(vals).view("M8[ns]")
+ arr2 = arr.view("M8[s]")
+
+ left = DatetimeArray._simple_new(arr, dtype=arr.dtype)
+ right = DatetimeArray._simple_new(arr2, dtype=arr2.dtype)
+
+ if comparison_op is operator.eq:
+ expected = np.array([False, False, False])
+ elif comparison_op is operator.ne:
+ expected = np.array([True, True, True])
+ elif comparison_op in [operator.lt, operator.le]:
+ expected = np.array([False, False, True])
+ else:
+ expected = np.array([False, True, False])
+
+ result = op(left, right)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = op(left[1], right)
+ tm.assert_numpy_array_equal(result, expected)
+
+ if op not in [operator.eq, operator.ne]:
+ # check that numpy still gets this wrong; if it is fixed we may be
+ # able to remove compare_mismatched_resolutions
+ np_res = op(left._ndarray, right._ndarray)
+ tm.assert_numpy_array_equal(np_res[1:], ~expected[1:])
+
+ def test_add_mismatched_reso_doesnt_downcast(self):
+ # https://github.com/pandas-dev/pandas/pull/48748#issuecomment-1260181008
+ td = pd.Timedelta(microseconds=1)
+ dti = pd.date_range("2016-01-01", periods=3) - td
+ dta = dti._data.as_unit("us")
+
+ res = dta + td.as_unit("us")
+ # even though the result is an even number of days
+ # (so we _could_ downcast to unit="s"), we do not.
+ assert res.unit == "us"
+
+ @pytest.mark.parametrize(
+ "scalar",
+ [
+ timedelta(hours=2),
+ pd.Timedelta(hours=2),
+ np.timedelta64(2, "h"),
+ np.timedelta64(2 * 3600 * 1000, "ms"),
+ pd.offsets.Minute(120),
+ pd.offsets.Hour(2),
+ ],
+ )
+ def test_add_timedeltalike_scalar_mismatched_reso(self, dta_dti, scalar):
+ dta, dti = dta_dti
+
+ td = pd.Timedelta(scalar)
+ exp_unit = tm.get_finest_unit(dta.unit, td.unit)
+
+ expected = (dti + td)._data.as_unit(exp_unit)
+ result = dta + scalar
+ tm.assert_extension_array_equal(result, expected)
+
+ result = scalar + dta
+ tm.assert_extension_array_equal(result, expected)
+
+ expected = (dti - td)._data.as_unit(exp_unit)
+ result = dta - scalar
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_sub_datetimelike_scalar_mismatch(self):
+ dti = pd.date_range("2016-01-01", periods=3)
+ dta = dti._data.as_unit("us")
+
+ ts = dta[0].as_unit("s")
+
+ result = dta - ts
+ expected = (dti - dti[0])._data.as_unit("us")
+ assert result.dtype == "m8[us]"
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_sub_datetime64_reso_mismatch(self):
+ dti = pd.date_range("2016-01-01", periods=3)
+ left = dti._data.as_unit("s")
+ right = left.as_unit("ms")
+
+ result = left - right
+ exp_values = np.array([0, 0, 0], dtype="m8[ms]")
+ expected = TimedeltaArray._simple_new(
+ exp_values,
+ dtype=exp_values.dtype,
+ )
+ tm.assert_extension_array_equal(result, expected)
+ result2 = right - left
+ tm.assert_extension_array_equal(result2, expected)
+
+
+class TestDatetimeArrayComparisons:
+ # TODO: merge this into tests/arithmetic/test_datetime64 once it is
+ # sufficiently robust
+
+ def test_cmp_dt64_arraylike_tznaive(self, comparison_op):
+ # arbitrary tz-naive DatetimeIndex
+ op = comparison_op
+
+ dti = pd.date_range("2016-01-1", freq="MS", periods=9, tz=None)
+ arr = dti._data
+ assert arr.freq == dti.freq
+ assert arr.tz == dti.tz
+
+ right = dti
+
+ expected = np.ones(len(arr), dtype=bool)
+ if comparison_op.__name__ in ["ne", "gt", "lt"]:
+ # for these the comparisons should be all-False
+ expected = ~expected
+
+ result = op(arr, arr)
+ tm.assert_numpy_array_equal(result, expected)
+ for other in [
+ right,
+ np.array(right),
+ list(right),
+ tuple(right),
+ right.astype(object),
+ ]:
+ result = op(arr, other)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = op(other, arr)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestDatetimeArray:
+ def test_astype_ns_to_ms_near_bounds(self):
+ # GH#55979
+ ts = pd.Timestamp("1677-09-21 00:12:43.145225")
+ target = ts.as_unit("ms")
+
+ dta = DatetimeArray._from_sequence([ts], dtype="M8[ns]")
+ assert (dta.view("i8") == ts.as_unit("ns").value).all()
+
+ result = dta.astype("M8[ms]")
+ assert result[0] == target
+
+ expected = DatetimeArray._from_sequence([ts], dtype="M8[ms]")
+ assert (expected.view("i8") == target._value).all()
+
+ tm.assert_datetime_array_equal(result, expected)
+
+ def test_astype_non_nano_tznaive(self):
+ dti = pd.date_range("2016-01-01", periods=3)
+
+ res = dti.astype("M8[s]")
+ assert res.dtype == "M8[s]"
+
+ dta = dti._data
+ res = dta.astype("M8[s]")
+ assert res.dtype == "M8[s]"
+ assert isinstance(res, pd.core.arrays.DatetimeArray) # used to be ndarray
+
+ def test_astype_non_nano_tzaware(self):
+ dti = pd.date_range("2016-01-01", periods=3, tz="UTC")
+
+ res = dti.astype("M8[s, US/Pacific]")
+ assert res.dtype == "M8[s, US/Pacific]"
+
+ dta = dti._data
+ res = dta.astype("M8[s, US/Pacific]")
+ assert res.dtype == "M8[s, US/Pacific]"
+
+ # from non-nano to non-nano, preserving reso
+ res2 = res.astype("M8[s, UTC]")
+ assert res2.dtype == "M8[s, UTC]"
+ assert not tm.shares_memory(res2, res)
+
+ res3 = res.astype("M8[s, UTC]", copy=False)
+ assert res2.dtype == "M8[s, UTC]"
+ assert tm.shares_memory(res3, res)
+
+ def test_astype_to_same(self):
+ arr = DatetimeArray._from_sequence(
+ ["2000"], dtype=DatetimeTZDtype(tz="US/Central")
+ )
+ result = arr.astype(DatetimeTZDtype(tz="US/Central"), copy=False)
+ assert result is arr
+
+ @pytest.mark.parametrize("dtype", ["datetime64[ns]", "datetime64[ns, UTC]"])
+ @pytest.mark.parametrize(
+ "other", ["datetime64[ns]", "datetime64[ns, UTC]", "datetime64[ns, CET]"]
+ )
+ def test_astype_copies(self, dtype, other):
+ # https://github.com/pandas-dev/pandas/pull/32490
+ ser = pd.Series([1, 2], dtype=dtype)
+ orig = ser.copy()
+
+ err = False
+ if (dtype == "datetime64[ns]") ^ (other == "datetime64[ns]"):
+ # deprecated in favor of tz_localize
+ err = True
+
+ if err:
+ if dtype == "datetime64[ns]":
+ msg = "Use obj.tz_localize instead or series.dt.tz_localize instead"
+ else:
+ msg = "from timezone-aware dtype to timezone-naive dtype"
+ with pytest.raises(TypeError, match=msg):
+ ser.astype(other)
+ else:
+ t = ser.astype(other)
+ t[:] = pd.NaT
+ tm.assert_series_equal(ser, orig)
+
+ @pytest.mark.parametrize("dtype", [int, np.int32, np.int64, "uint32", "uint64"])
+ def test_astype_int(self, dtype):
+ arr = DatetimeArray._from_sequence(
+ [pd.Timestamp("2000"), pd.Timestamp("2001")], dtype="M8[ns]"
+ )
+
+ if np.dtype(dtype) != np.int64:
+ with pytest.raises(TypeError, match=r"Do obj.astype\('int64'\)"):
+ arr.astype(dtype)
+ return
+
+ result = arr.astype(dtype)
+ expected = arr._ndarray.view("i8")
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_astype_to_sparse_dt64(self):
+ # GH#50082
+ dti = pd.date_range("2016-01-01", periods=4)
+ dta = dti._data
+ result = dta.astype("Sparse[datetime64[ns]]")
+
+ assert result.dtype == "Sparse[datetime64[ns]]"
+ assert (result == dta).all()
+
+ def test_tz_setter_raises(self):
+ arr = DatetimeArray._from_sequence(
+ ["2000"], dtype=DatetimeTZDtype(tz="US/Central")
+ )
+ with pytest.raises(AttributeError, match="tz_localize"):
+ arr.tz = "UTC"
+
+ def test_setitem_str_impute_tz(self, tz_naive_fixture):
+ # Like for getitem, if we are passed a naive-like string, we impute
+ # our own timezone.
+ tz = tz_naive_fixture
+
+ data = np.array([1, 2, 3], dtype="M8[ns]")
+ dtype = data.dtype if tz is None else DatetimeTZDtype(tz=tz)
+ arr = DatetimeArray._from_sequence(data, dtype=dtype)
+ expected = arr.copy()
+
+ ts = pd.Timestamp("2020-09-08 16:50").tz_localize(tz)
+ setter = str(ts.tz_localize(None))
+
+ # Setting a scalar tznaive string
+ expected[0] = ts
+ arr[0] = setter
+ tm.assert_equal(arr, expected)
+
+ # Setting a listlike of tznaive strings
+ expected[1] = ts
+ arr[:2] = [setter, setter]
+ tm.assert_equal(arr, expected)
+
+ def test_setitem_different_tz_raises(self):
+ # pre-2.0 we required exact tz match, in 2.0 we require only
+ # tzawareness-match
+ data = np.array([1, 2, 3], dtype="M8[ns]")
+ arr = DatetimeArray._from_sequence(
+ data, copy=False, dtype=DatetimeTZDtype(tz="US/Central")
+ )
+ with pytest.raises(TypeError, match="Cannot compare tz-naive and tz-aware"):
+ arr[0] = pd.Timestamp("2000")
+
+ ts = pd.Timestamp("2000", tz="US/Eastern")
+ arr[0] = ts
+ assert arr[0] == ts.tz_convert("US/Central")
+
+ def test_setitem_clears_freq(self):
+ a = pd.date_range("2000", periods=2, freq="D", tz="US/Central")._data
+ a[0] = pd.Timestamp("2000", tz="US/Central")
+ assert a.freq is None
+
+ @pytest.mark.parametrize(
+ "obj",
+ [
+ pd.Timestamp("2021-01-01"),
+ pd.Timestamp("2021-01-01").to_datetime64(),
+ pd.Timestamp("2021-01-01").to_pydatetime(),
+ ],
+ )
+ def test_setitem_objects(self, obj):
+ # make sure we accept datetime64 and datetime in addition to Timestamp
+ dti = pd.date_range("2000", periods=2, freq="D")
+ arr = dti._data
+
+ arr[0] = obj
+ assert arr[0] == obj
+
+ def test_repeat_preserves_tz(self):
+ dti = pd.date_range("2000", periods=2, freq="D", tz="US/Central")
+ arr = dti._data
+
+ repeated = arr.repeat([1, 1])
+
+ # preserves tz and values, but not freq
+ expected = DatetimeArray._from_sequence(arr.asi8, dtype=arr.dtype)
+ tm.assert_equal(repeated, expected)
+
+ def test_value_counts_preserves_tz(self):
+ dti = pd.date_range("2000", periods=2, freq="D", tz="US/Central")
+ arr = dti._data.repeat([4, 3])
+
+ result = arr.value_counts()
+
+ # Note: not tm.assert_index_equal, since `freq`s do not match
+ assert result.index.equals(dti)
+
+ arr[-2] = pd.NaT
+ result = arr.value_counts(dropna=False)
+ expected = pd.Series([4, 2, 1], index=[dti[0], dti[1], pd.NaT], name="count")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("method", ["pad", "backfill"])
+ def test_fillna_preserves_tz(self, method):
+ dti = pd.date_range(
+ "2000-01-01", periods=5, freq="D", tz="US/Central", unit="ns"
+ )
+ arr = DatetimeArray._from_sequence(dti, dtype=dti.dtype, copy=True)
+ arr[2] = pd.NaT
+
+ fill_val = dti[1] if method == "pad" else dti[3]
+ expected = DatetimeArray._from_sequence(
+ [dti[0], dti[1], fill_val, dti[3], dti[4]],
+ dtype=DatetimeTZDtype(tz="US/Central"),
+ )
+
+ result = arr._pad_or_backfill(method=method)
+ tm.assert_extension_array_equal(result, expected)
+
+ # assert that arr and dti were not modified in-place
+ assert arr[2] is pd.NaT
+ assert dti[2] == pd.Timestamp("2000-01-03", tz="US/Central")
+
+ def test_fillna_2d(self):
+ dti = pd.date_range("2016-01-01", periods=6, tz="US/Pacific")
+ dta = dti._data.reshape(3, 2).copy()
+ dta[0, 1] = pd.NaT
+ dta[1, 0] = pd.NaT
+
+ res1 = dta._pad_or_backfill(method="pad")
+ expected1 = dta.copy()
+ expected1[1, 0] = dta[0, 0]
+ tm.assert_extension_array_equal(res1, expected1)
+
+ res2 = dta._pad_or_backfill(method="backfill")
+ expected2 = dta.copy()
+ expected2 = dta.copy()
+ expected2[1, 0] = dta[2, 0]
+ expected2[0, 1] = dta[1, 1]
+ tm.assert_extension_array_equal(res2, expected2)
+
+ # with different ordering for underlying ndarray; behavior should
+ # be unchanged
+ dta2 = dta._from_backing_data(dta._ndarray.copy(order="F"))
+ assert dta2._ndarray.flags["F_CONTIGUOUS"]
+ assert not dta2._ndarray.flags["C_CONTIGUOUS"]
+ tm.assert_extension_array_equal(dta, dta2)
+
+ res3 = dta2._pad_or_backfill(method="pad")
+ tm.assert_extension_array_equal(res3, expected1)
+
+ res4 = dta2._pad_or_backfill(method="backfill")
+ tm.assert_extension_array_equal(res4, expected2)
+
+ # test the DataFrame method while we're here
+ df = pd.DataFrame(dta)
+ res = df.ffill()
+ expected = pd.DataFrame(expected1)
+ tm.assert_frame_equal(res, expected)
+
+ res = df.bfill()
+ expected = pd.DataFrame(expected2)
+ tm.assert_frame_equal(res, expected)
+
+ def test_array_interface_tz(self):
+ tz = "US/Central"
+ data = pd.date_range("2017", periods=2, tz=tz, unit="ns")._data
+ result = np.asarray(data)
+
+ expected = np.array(
+ [
+ pd.Timestamp("2017-01-01T00:00:00", tz=tz),
+ pd.Timestamp("2017-01-02T00:00:00", tz=tz),
+ ],
+ dtype=object,
+ )
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.asarray(data, dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.asarray(data, dtype="M8[ns]")
+
+ expected = np.array(
+ ["2017-01-01T06:00:00", "2017-01-02T06:00:00"], dtype="M8[ns]"
+ )
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_array_interface(self):
+ data = pd.date_range("2017", periods=2, unit="ns")._data
+ expected = np.array(
+ ["2017-01-01T00:00:00", "2017-01-02T00:00:00"], dtype="datetime64[ns]"
+ )
+
+ result = np.asarray(data)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.asarray(data, dtype=object)
+ expected = np.array(
+ [pd.Timestamp("2017-01-01T00:00:00"), pd.Timestamp("2017-01-02T00:00:00")],
+ dtype=object,
+ )
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("index", [True, False])
+ def test_searchsorted_different_tz(self, index):
+ data = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ arr = pd.DatetimeIndex(data, freq="D")._data.tz_localize("Asia/Tokyo")
+ if index:
+ arr = pd.Index(arr)
+
+ expected = arr.searchsorted(arr[2])
+ result = arr.searchsorted(arr[2].tz_convert("UTC"))
+ assert result == expected
+
+ expected = arr.searchsorted(arr[2:6])
+ result = arr.searchsorted(arr[2:6].tz_convert("UTC"))
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("index", [True, False])
+ def test_searchsorted_tzawareness_compat(self, index):
+ data = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ arr = pd.DatetimeIndex(data, freq="D")._data
+ if index:
+ arr = pd.Index(arr)
+
+ mismatch = arr.tz_localize("Asia/Tokyo")
+
+ msg = "Cannot compare tz-naive and tz-aware datetime-like objects"
+ with pytest.raises(TypeError, match=msg):
+ arr.searchsorted(mismatch[0])
+ with pytest.raises(TypeError, match=msg):
+ arr.searchsorted(mismatch)
+
+ with pytest.raises(TypeError, match=msg):
+ mismatch.searchsorted(arr[0])
+ with pytest.raises(TypeError, match=msg):
+ mismatch.searchsorted(arr)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ 1,
+ np.int64(1),
+ 1.0,
+ np.timedelta64("NaT"),
+ pd.Timedelta(days=2),
+ "invalid",
+ np.arange(10, dtype="i8") * 24 * 3600 * 10**9,
+ np.arange(10).view("timedelta64[ns]") * 24 * 3600 * 10**9,
+ pd.Timestamp("2021-01-01").to_period("D"),
+ ],
+ )
+ @pytest.mark.parametrize("index", [True, False])
+ def test_searchsorted_invalid_types(self, other, index):
+ data = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ arr = pd.DatetimeIndex(data, freq="D")._data
+ if index:
+ arr = pd.Index(arr)
+
+ msg = "|".join(
+ [
+ "searchsorted requires compatible dtype or scalar",
+ "value should be a 'Timestamp', 'NaT', or array of those. Got",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ arr.searchsorted(other)
+
+ def test_shift_fill_value(self):
+ dti = pd.date_range("2016-01-01", periods=3)
+
+ dta = dti._data
+ expected = DatetimeArray._from_sequence(
+ np.roll(dta._ndarray, 1), dtype=dti.dtype
+ )
+
+ fv = dta[-1]
+ for fill_value in [fv, fv.to_pydatetime(), fv.to_datetime64()]:
+ result = dta.shift(1, fill_value=fill_value)
+ tm.assert_datetime_array_equal(result, expected)
+
+ dta = dta.tz_localize("UTC")
+ expected = expected.tz_localize("UTC")
+ fv = dta[-1]
+ for fill_value in [fv, fv.to_pydatetime()]:
+ result = dta.shift(1, fill_value=fill_value)
+ tm.assert_datetime_array_equal(result, expected)
+
+ def test_shift_value_tzawareness_mismatch(self):
+ dti = pd.date_range("2016-01-01", periods=3)
+
+ dta = dti._data
+
+ fv = dta[-1].tz_localize("UTC")
+ for invalid in [fv, fv.to_pydatetime()]:
+ with pytest.raises(TypeError, match="Cannot compare"):
+ dta.shift(1, fill_value=invalid)
+
+ dta = dta.tz_localize("UTC")
+ fv = dta[-1].tz_localize(None)
+ for invalid in [fv, fv.to_pydatetime(), fv.to_datetime64()]:
+ with pytest.raises(TypeError, match="Cannot compare"):
+ dta.shift(1, fill_value=invalid)
+
+ def test_shift_requires_tzmatch(self):
+ # pre-2.0 we required exact tz match, in 2.0 we require just
+ # matching tzawareness
+ dti = pd.date_range("2016-01-01", periods=3, tz="UTC")
+ dta = dti._data
+
+ fill_value = pd.Timestamp("2020-10-18 18:44", tz="US/Pacific")
+
+ result = dta.shift(1, fill_value=fill_value)
+ expected = dta.shift(1, fill_value=fill_value.tz_convert("UTC"))
+ tm.assert_equal(result, expected)
+
+ def test_tz_localize_t2d(self):
+ dti = pd.date_range("1994-05-12", periods=12, tz="US/Pacific")
+ dta = dti._data.reshape(3, 4)
+ result = dta.tz_localize(None)
+
+ expected = dta.ravel().tz_localize(None).reshape(dta.shape)
+ tm.assert_datetime_array_equal(result, expected)
+
+ roundtrip = expected.tz_localize("US/Pacific")
+ tm.assert_datetime_array_equal(roundtrip, dta)
+
+ @pytest.mark.parametrize(
+ "tz", ["US/Eastern", "dateutil/US/Eastern", "pytz/US/Eastern"]
+ )
+ def test_iter_zoneinfo_fold(self, tz):
+ # GH#49684
+ if tz.startswith("pytz/"):
+ pytz = pytest.importorskip("pytz")
+ tz = pytz.timezone(tz.removeprefix("pytz/"))
+ utc_vals = np.array(
+ [1320552000, 1320555600, 1320559200, 1320562800], dtype=np.int64
+ )
+ utc_vals *= 1_000_000_000
+
+ dta = (
+ DatetimeArray._from_sequence(utc_vals, dtype=np.dtype("M8[ns]"))
+ .tz_localize("UTC")
+ .tz_convert(tz)
+ )
+
+ left = dta[2]
+ right = list(dta)[2]
+ assert str(left) == str(right)
+ # previously there was a bug where with non-pytz right would be
+ # Timestamp('2011-11-06 01:00:00-0400', tz='US/Eastern')
+ # while left would be
+ # Timestamp('2011-11-06 01:00:00-0500', tz='US/Eastern')
+ # The .value's would match (so they would compare as equal),
+ # but the folds would not
+ assert left.utcoffset() == right.utcoffset()
+
+ # The same bug in ints_to_pydatetime affected .astype, so we test
+ # that here.
+ right2 = dta.astype(object)[2]
+ assert str(left) == str(right2)
+ assert left.utcoffset() == right2.utcoffset()
+
+ @pytest.mark.parametrize(
+ "freq",
+ ["2M", "2SM", "2sm", "2Q", "2Q-SEP", "1Y", "2Y-MAR", "2m", "2q-sep", "2y"],
+ )
+ def test_date_range_frequency_M_Q_Y_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ pd.date_range("1/1/2000", periods=4, freq=freq)
+
+ @pytest.mark.parametrize("freq_depr", ["2MIN", "2nS", "2Us"])
+ def test_date_range_uppercase_frequency_deprecated(self, freq_depr):
+ # GH#9586, GH#54939
+ depr_msg = (
+ f"'{freq_depr[1:]}' is deprecated and will be removed in a "
+ f"future version, please use '{freq_depr.lower()[1:]}' instead."
+ )
+
+ expected = pd.date_range("1/1/2000", periods=4, freq=freq_depr.lower())
+ with tm.assert_produces_warning(Pandas4Warning, match=depr_msg):
+ result = pd.date_range("1/1/2000", periods=4, freq=freq_depr)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "freq",
+ [
+ "2ye-mar",
+ "2ys",
+ "2qe",
+ "2qs-feb",
+ "2bqs",
+ "2sms",
+ "2bms",
+ "2cbme",
+ "2me",
+ ],
+ )
+ def test_date_range_lowercase_frequency_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ pd.date_range("1/1/2000", periods=4, freq=freq)
+
+ def test_date_range_lowercase_frequency_deprecated(self):
+ # GH#9586, GH#54939
+ depr_msg = "'w' is deprecated and will be removed in a future version"
+
+ expected = pd.date_range("1/1/2000", periods=4, freq="2W")
+ with tm.assert_produces_warning(Pandas4Warning, match=depr_msg):
+ result = pd.date_range("1/1/2000", periods=4, freq="2w")
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("freq", ["1A", "2A-MAR", "2a-mar"])
+ def test_date_range_frequency_A_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ pd.date_range("1/1/2000", periods=4, freq=freq)
+
+ @pytest.mark.parametrize("freq", ["2H", "2CBH", "2S"])
+ def test_date_range_uppercase_frequency_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ pd.date_range("1/1/2000", periods=4, freq=freq)
+
+
+def test_factorize_sort_without_freq():
+ dta = DatetimeArray._from_sequence([0, 2, 1], dtype="M8[ns]")
+
+ msg = r"call pd.factorize\(obj, sort=True\) instead"
+ with pytest.raises(NotImplementedError, match=msg):
+ dta.factorize(sort=True)
+
+ # Do TimedeltaArray while we're here
+ tda = dta - dta[0]
+ with pytest.raises(NotImplementedError, match=msg):
+ tda.factorize(sort=True)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_ndarray_backed.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_ndarray_backed.py
new file mode 100644
index 0000000000000000000000000000000000000000..2af59a03a5b3e774c1c0692399c285f0ec26a1dc
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_ndarray_backed.py
@@ -0,0 +1,76 @@
+"""
+Tests for subclasses of NDArrayBackedExtensionArray
+"""
+
+import numpy as np
+
+from pandas import (
+ CategoricalIndex,
+ date_range,
+)
+from pandas.core.arrays import (
+ Categorical,
+ DatetimeArray,
+ NumpyExtensionArray,
+ TimedeltaArray,
+)
+
+
+class TestEmpty:
+ def test_empty_categorical(self):
+ ci = CategoricalIndex(["a", "b", "c"], ordered=True)
+ dtype = ci.dtype
+
+ # case with int8 codes
+ shape = (4,)
+ result = Categorical._empty(shape, dtype=dtype)
+ assert isinstance(result, Categorical)
+ assert result.shape == shape
+ assert result._ndarray.dtype == np.int8
+
+ # case where repr would segfault if we didn't override base implementation
+ result = Categorical._empty((4096,), dtype=dtype)
+ assert isinstance(result, Categorical)
+ assert result.shape == (4096,)
+ assert result._ndarray.dtype == np.int8
+ repr(result)
+
+ # case with int16 codes
+ ci = CategoricalIndex(list(range(512)) * 4, ordered=False)
+ dtype = ci.dtype
+ result = Categorical._empty(shape, dtype=dtype)
+ assert isinstance(result, Categorical)
+ assert result.shape == shape
+ assert result._ndarray.dtype == np.int16
+
+ def test_empty_dt64tz(self):
+ dti = date_range("2016-01-01", periods=2, tz="Asia/Tokyo")
+ dtype = dti.dtype
+
+ shape = (0,)
+ result = DatetimeArray._empty(shape, dtype=dtype)
+ assert result.dtype == dtype
+ assert isinstance(result, DatetimeArray)
+ assert result.shape == shape
+
+ def test_empty_dt64(self):
+ shape = (3, 9)
+ result = DatetimeArray._empty(shape, dtype="datetime64[ns]")
+ assert isinstance(result, DatetimeArray)
+ assert result.shape == shape
+
+ def test_empty_td64(self):
+ shape = (3, 9)
+ result = TimedeltaArray._empty(shape, dtype="m8[ns]")
+ assert isinstance(result, TimedeltaArray)
+ assert result.shape == shape
+
+ def test_empty_pandas_array(self):
+ arr = NumpyExtensionArray(np.array([1, 2]))
+ dtype = arr.dtype
+
+ shape = (3, 9)
+ result = NumpyExtensionArray._empty(shape, dtype=dtype)
+ assert isinstance(result, NumpyExtensionArray)
+ assert result.dtype == dtype
+ assert result.shape == shape
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_period.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_period.py
new file mode 100644
index 0000000000000000000000000000000000000000..48453ba19e9a1f6971a2e56872ec42f1856d1dd0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_period.py
@@ -0,0 +1,184 @@
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import iNaT
+from pandas._libs.tslibs.period import IncompatibleFrequency
+
+from pandas.core.dtypes.base import _registry as registry
+from pandas.core.dtypes.dtypes import PeriodDtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.arrays import PeriodArray
+
+# ----------------------------------------------------------------------------
+# Dtype
+
+
+def test_registered():
+ assert PeriodDtype in registry.dtypes
+ result = registry.find("Period[D]")
+ expected = PeriodDtype("D")
+ assert result == expected
+
+
+# ----------------------------------------------------------------------------
+# period_array
+
+
+def test_asi8():
+ result = PeriodArray._from_sequence(["2000", "2001", None], dtype="period[D]").asi8
+ expected = np.array([10957, 11323, iNaT])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_take_raises():
+ arr = PeriodArray._from_sequence(["2000", "2001"], dtype="period[D]")
+ with pytest.raises(IncompatibleFrequency, match="freq"):
+ arr.take([0, -1], allow_fill=True, fill_value=pd.Period("2000", freq="W"))
+
+ msg = "value should be a 'Period' or 'NaT'. Got 'str' instead"
+ with pytest.raises(TypeError, match=msg):
+ arr.take([0, -1], allow_fill=True, fill_value="foo")
+
+
+def test_fillna_raises():
+ arr = PeriodArray._from_sequence(["2000", "2001", "2002"], dtype="period[D]")
+ with pytest.raises(ValueError, match="Length"):
+ arr.fillna(arr[:2])
+
+
+def test_fillna_copies():
+ arr = PeriodArray._from_sequence(["2000", "2001", "2002"], dtype="period[D]")
+ result = arr.fillna(pd.Period("2000", "D"))
+ assert result is not arr
+
+
+# ----------------------------------------------------------------------------
+# setitem
+
+
+@pytest.mark.parametrize(
+ "key, value, expected",
+ [
+ ([0], pd.Period("2000", "D"), [10957, 1, 2]),
+ ([0], None, [iNaT, 1, 2]),
+ ([0], np.nan, [iNaT, 1, 2]),
+ ([0, 1, 2], pd.Period("2000", "D"), [10957] * 3),
+ (
+ [0, 1, 2],
+ [pd.Period("2000", "D"), pd.Period("2001", "D"), pd.Period("2002", "D")],
+ [10957, 11323, 11688],
+ ),
+ ],
+)
+def test_setitem(key, value, expected):
+ arr = PeriodArray(np.arange(3), dtype="period[D]")
+ expected = PeriodArray(expected, dtype="period[D]")
+ arr[key] = value
+ tm.assert_period_array_equal(arr, expected)
+
+
+def test_setitem_raises_incompatible_freq():
+ arr = PeriodArray(np.arange(3), dtype="period[D]")
+ with pytest.raises(IncompatibleFrequency, match="freq"):
+ arr[0] = pd.Period("2000", freq="Y")
+
+ other = PeriodArray._from_sequence(["2000", "2001"], dtype="period[Y]")
+ with pytest.raises(IncompatibleFrequency, match="freq"):
+ arr[[0, 1]] = other
+
+
+def test_setitem_raises_length():
+ arr = PeriodArray(np.arange(3), dtype="period[D]")
+ with pytest.raises(ValueError, match="length"):
+ arr[[0, 1]] = [pd.Period("2000", freq="D")]
+
+
+def test_setitem_raises_type():
+ arr = PeriodArray(np.arange(3), dtype="period[D]")
+ with pytest.raises(TypeError, match="int"):
+ arr[0] = 1
+
+
+# ----------------------------------------------------------------------------
+# Ops
+
+
+def test_sub_period():
+ arr = PeriodArray._from_sequence(["2000", "2001"], dtype="period[D]")
+ other = pd.Period("2000", freq="M")
+ with pytest.raises(IncompatibleFrequency, match="freq"):
+ arr - other
+
+
+def test_sub_period_overflow():
+ # GH#47538
+ dti = pd.date_range("1677-09-22", periods=2, freq="D")
+ pi = dti.to_period("ns")
+
+ per = pd.Period._from_ordinal(10**14, pi.freq)
+
+ with pytest.raises(OverflowError, match="Overflow in int64 addition"):
+ pi - per
+
+ with pytest.raises(OverflowError, match="Overflow in int64 addition"):
+ per - pi
+
+
+# ----------------------------------------------------------------------------
+# Methods
+
+
+@pytest.mark.parametrize(
+ "other",
+ [
+ pd.Period("2000", freq="h"),
+ PeriodArray._from_sequence(["2000", "2001", "2000"], dtype="period[h]"),
+ ],
+)
+def test_where_different_freq_raises(other):
+ # GH#45768 The PeriodArray method raises, the Series method coerces
+ ser = pd.Series(
+ PeriodArray._from_sequence(["2000", "2001", "2002"], dtype="period[D]")
+ )
+ cond = np.array([True, False, True])
+
+ with pytest.raises(IncompatibleFrequency, match="freq"):
+ ser.array._where(cond, other)
+
+ res = ser.where(cond, other)
+ expected = ser.astype(object).where(cond, other)
+ tm.assert_series_equal(res, expected)
+
+
+# ----------------------------------------------------------------------------
+# Printing
+
+
+def test_repr_small():
+ arr = PeriodArray._from_sequence(["2000", "2001"], dtype="period[D]")
+ result = str(arr)
+ expected = (
+ "\n['2000-01-01', '2001-01-01']\nLength: 2, dtype: period[D]"
+ )
+ assert result == expected
+
+
+def test_repr_large():
+ arr = PeriodArray._from_sequence(["2000", "2001"] * 500, dtype="period[D]")
+ result = str(arr)
+ expected = (
+ "\n"
+ "['2000-01-01', '2001-01-01', '2000-01-01', '2001-01-01', "
+ "'2000-01-01',\n"
+ " '2001-01-01', '2000-01-01', '2001-01-01', '2000-01-01', "
+ "'2001-01-01',\n"
+ " ...\n"
+ " '2000-01-01', '2001-01-01', '2000-01-01', '2001-01-01', "
+ "'2000-01-01',\n"
+ " '2001-01-01', '2000-01-01', '2001-01-01', '2000-01-01', "
+ "'2001-01-01']\n"
+ "Length: 1000, dtype: period[D]"
+ )
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_timedeltas.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_timedeltas.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb7c7afdc6ff984c175c7a000e92b84b607c3b70
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/arrays/test_timedeltas.py
@@ -0,0 +1,312 @@
+from datetime import timedelta
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import Timedelta
+import pandas._testing as tm
+from pandas.core.arrays import (
+ DatetimeArray,
+ TimedeltaArray,
+)
+
+
+class TestNonNano:
+ @pytest.fixture(params=["s", "ms", "us"])
+ def unit(self, request):
+ return request.param
+
+ @pytest.fixture
+ def tda(self, unit):
+ arr = np.arange(5, dtype=np.int64).view(f"m8[{unit}]")
+ return TimedeltaArray._simple_new(arr, dtype=arr.dtype)
+
+ def test_non_nano(self, unit):
+ arr = np.arange(5, dtype=np.int64).view(f"m8[{unit}]")
+ tda = TimedeltaArray._simple_new(arr, dtype=arr.dtype)
+
+ assert tda.dtype == arr.dtype
+ assert tda[0].unit == unit
+
+ def test_as_unit_raises(self, tda):
+ # GH#50616
+ with pytest.raises(ValueError, match="Supported units"):
+ tda.as_unit("D")
+
+ tdi = pd.Index(tda)
+ with pytest.raises(ValueError, match="Supported units"):
+ tdi.as_unit("D")
+
+ @pytest.mark.parametrize("field", TimedeltaArray._field_ops)
+ def test_fields(self, tda, field):
+ as_nano = tda._ndarray.astype("m8[ns]")
+ tda_nano = TimedeltaArray._simple_new(as_nano, dtype=as_nano.dtype)
+
+ result = getattr(tda, field)
+ expected = getattr(tda_nano, field)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_to_pytimedelta(self, tda):
+ as_nano = tda._ndarray.astype("m8[ns]")
+ tda_nano = TimedeltaArray._simple_new(as_nano, dtype=as_nano.dtype)
+
+ result = tda.to_pytimedelta()
+ expected = tda_nano.to_pytimedelta()
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_total_seconds(self, unit, tda):
+ as_nano = tda._ndarray.astype("m8[ns]")
+ tda_nano = TimedeltaArray._simple_new(as_nano, dtype=as_nano.dtype)
+
+ result = tda.total_seconds()
+ expected = tda_nano.total_seconds()
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_timedelta_array_total_seconds(self):
+ # GH34290
+ expected = Timedelta("2 min").total_seconds()
+
+ result = pd.array([Timedelta("2 min")]).total_seconds()[0]
+ assert result == expected
+
+ def test_total_seconds_nanoseconds(self):
+ # issue #48521
+ start_time = pd.Series(["2145-11-02 06:00:00"]).astype("datetime64[ns]")
+ end_time = pd.Series(["2145-11-02 07:06:00"]).astype("datetime64[ns]")
+ expected = (end_time - start_time).values / np.timedelta64(1, "s")
+ result = (end_time - start_time).dt.total_seconds().values
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "nat", [np.datetime64("NaT", "ns"), np.datetime64("NaT", "us")]
+ )
+ def test_add_nat_datetimelike_scalar(self, nat, tda):
+ result = tda + nat
+ assert isinstance(result, DatetimeArray)
+ assert result._creso == tda._creso
+ assert result.isna().all()
+
+ result = nat + tda
+ assert isinstance(result, DatetimeArray)
+ assert result._creso == tda._creso
+ assert result.isna().all()
+
+ def test_add_pdnat(self, tda):
+ result = tda + pd.NaT
+ assert isinstance(result, TimedeltaArray)
+ assert result._creso == tda._creso
+ assert result.isna().all()
+
+ result = pd.NaT + tda
+ assert isinstance(result, TimedeltaArray)
+ assert result._creso == tda._creso
+ assert result.isna().all()
+
+ # TODO: 2022-07-11 this is the only test that gets to DTA.tz_convert
+ # or tz_localize with non-nano; implement tests specific to that.
+ def test_add_datetimelike_scalar(self, tda, tz_naive_fixture):
+ ts = pd.Timestamp("2016-01-01", tz=tz_naive_fixture).as_unit("ns")
+
+ expected = tda.as_unit("ns") + ts
+ res = tda + ts
+ tm.assert_extension_array_equal(res, expected)
+ res = ts + tda
+ tm.assert_extension_array_equal(res, expected)
+
+ ts += Timedelta(1) # case where we can't cast losslessly
+
+ exp_values = tda._ndarray + ts.asm8
+ expected = (
+ DatetimeArray._simple_new(exp_values, dtype=exp_values.dtype)
+ .tz_localize("UTC")
+ .tz_convert(ts.tz)
+ )
+
+ result = tda + ts
+ tm.assert_extension_array_equal(result, expected)
+
+ result = ts + tda
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_mul_scalar(self, tda):
+ other = 2
+ result = tda * other
+ expected = TimedeltaArray._simple_new(tda._ndarray * other, dtype=tda.dtype)
+ tm.assert_extension_array_equal(result, expected)
+ assert result._creso == tda._creso
+
+ def test_mul_listlike(self, tda):
+ other = np.arange(len(tda))
+ result = tda * other
+ expected = TimedeltaArray._simple_new(tda._ndarray * other, dtype=tda.dtype)
+ tm.assert_extension_array_equal(result, expected)
+ assert result._creso == tda._creso
+
+ def test_mul_listlike_object(self, tda):
+ other = np.arange(len(tda))
+ result = tda * other.astype(object)
+ expected = TimedeltaArray._simple_new(tda._ndarray * other, dtype=tda.dtype)
+ tm.assert_extension_array_equal(result, expected)
+ assert result._creso == tda._creso
+
+ def test_div_numeric_scalar(self, tda):
+ other = 2
+ result = tda / other
+ expected = TimedeltaArray._simple_new(tda._ndarray / other, dtype=tda.dtype)
+ tm.assert_extension_array_equal(result, expected)
+ assert result._creso == tda._creso
+
+ def test_div_td_scalar(self, tda):
+ other = timedelta(seconds=1)
+ result = tda / other
+ expected = tda._ndarray / np.timedelta64(1, "s")
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_div_numeric_array(self, tda):
+ other = np.arange(len(tda))
+ result = tda / other
+ expected = TimedeltaArray._simple_new(tda._ndarray / other, dtype=tda.dtype)
+ tm.assert_extension_array_equal(result, expected)
+ assert result._creso == tda._creso
+
+ def test_div_td_array(self, tda):
+ other = tda._ndarray + tda._ndarray[-1]
+ result = tda / other
+ expected = tda._ndarray / other
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_add_timedeltaarraylike(self, tda):
+ tda_nano = tda.astype("m8[ns]")
+
+ expected = tda_nano * 2
+ res = tda_nano + tda
+ tm.assert_extension_array_equal(res, expected)
+ res = tda + tda_nano
+ tm.assert_extension_array_equal(res, expected)
+
+ expected = tda_nano * 0
+ res = tda - tda_nano
+ tm.assert_extension_array_equal(res, expected)
+
+ res = tda_nano - tda
+ tm.assert_extension_array_equal(res, expected)
+
+
+class TestTimedeltaArray:
+ def test_astype_int(self, any_int_numpy_dtype):
+ arr = TimedeltaArray._from_sequence(
+ [Timedelta("1h"), Timedelta("2h")], dtype="m8[ns]"
+ )
+
+ if np.dtype(any_int_numpy_dtype) != np.int64:
+ with pytest.raises(TypeError, match=r"Do obj.astype\('int64'\)"):
+ arr.astype(any_int_numpy_dtype)
+ return
+
+ result = arr.astype(any_int_numpy_dtype)
+ expected = arr._ndarray.view("i8")
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_setitem_clears_freq(self):
+ a = pd.timedelta_range("1h", periods=2, freq="h")._data
+ a[0] = Timedelta("1h")
+ assert a.freq is None
+
+ @pytest.mark.parametrize(
+ "obj",
+ [
+ Timedelta(seconds=1),
+ Timedelta(seconds=1).to_timedelta64(),
+ Timedelta(seconds=1).to_pytimedelta(),
+ ],
+ )
+ def test_setitem_objects(self, obj):
+ # make sure we accept timedelta64 and timedelta in addition to Timedelta
+ tdi = pd.timedelta_range("2 Days", periods=4, freq="h")
+ arr = tdi._data
+
+ arr[0] = obj
+ assert arr[0] == Timedelta(seconds=1)
+
+ @pytest.mark.parametrize(
+ "other",
+ [
+ 1,
+ np.int64(1),
+ 1.0,
+ np.datetime64("NaT"),
+ pd.Timestamp("2021-01-01"),
+ "invalid",
+ np.arange(10, dtype="i8") * 24 * 3600 * 10**9,
+ (np.arange(10) * 24 * 3600 * 10**9).view("datetime64[ns]"),
+ pd.Timestamp("2021-01-01").to_period("D"),
+ ],
+ )
+ @pytest.mark.parametrize("index", [True, False])
+ def test_searchsorted_invalid_types(self, other, index):
+ data = np.arange(10, dtype="i8") * 24 * 3600 * 10**9
+ arr = pd.TimedeltaIndex(data, freq="D")._data
+ if index:
+ arr = pd.Index(arr)
+
+ msg = "|".join(
+ [
+ "searchsorted requires compatible dtype or scalar",
+ "value should be a 'Timedelta', 'NaT', or array of those. Got",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ arr.searchsorted(other)
+
+
+class TestUnaryOps:
+ def test_abs(self):
+ vals = np.array([-3600 * 10**9, "NaT", 7200 * 10**9], dtype="m8[ns]")
+ arr = TimedeltaArray._from_sequence(vals, dtype=vals.dtype)
+
+ evals = np.array([3600 * 10**9, "NaT", 7200 * 10**9], dtype="m8[ns]")
+ expected = TimedeltaArray._from_sequence(evals, dtype=evals.dtype)
+
+ result = abs(arr)
+ tm.assert_timedelta_array_equal(result, expected)
+
+ result2 = np.abs(arr)
+ tm.assert_timedelta_array_equal(result2, expected)
+
+ def test_pos(self):
+ vals = np.array([-3600 * 10**9, "NaT", 7200 * 10**9], dtype="m8[ns]")
+ arr = TimedeltaArray._from_sequence(vals, dtype=vals.dtype)
+
+ result = +arr
+ tm.assert_timedelta_array_equal(result, arr)
+ assert not tm.shares_memory(result, arr)
+
+ result2 = np.positive(arr)
+ tm.assert_timedelta_array_equal(result2, arr)
+ assert not tm.shares_memory(result2, arr)
+
+ def test_neg(self):
+ vals = np.array([-3600 * 10**9, "NaT", 7200 * 10**9], dtype="m8[ns]")
+ arr = TimedeltaArray._from_sequence(vals, dtype=vals.dtype)
+
+ evals = np.array([3600 * 10**9, "NaT", -7200 * 10**9], dtype="m8[ns]")
+ expected = TimedeltaArray._from_sequence(evals)
+
+ result = -arr
+ tm.assert_timedelta_array_equal(result, expected)
+
+ result2 = np.negative(arr)
+ tm.assert_timedelta_array_equal(result2, expected)
+
+ def test_neg_freq(self):
+ tdi = pd.timedelta_range("2 Days", periods=4, freq="h")
+ arr = tdi._data
+
+ expected = -tdi._data
+
+ result = -arr
+ tm.assert_timedelta_array_equal(result, expected)
+
+ result2 = np.negative(arr)
+ tm.assert_timedelta_array_equal(result2, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad0b394105742ca5de92a03a3da2c569c38da469
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/common.py
@@ -0,0 +1,9 @@
+from typing import Any
+
+from pandas import Index
+
+
+def allow_na_ops(obj: Any) -> bool:
+ """Whether to skip test cases including NaN"""
+ is_bool_index = isinstance(obj, Index) and obj.inferred_type == "boolean"
+ return not is_bool_index and obj._can_hold_na
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..dffd2009ef3733604dfb06d174701c7d8ca09f01
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_constructors.py
@@ -0,0 +1,192 @@
+from datetime import datetime
+import sys
+
+import numpy as np
+import pytest
+
+from pandas.compat import PYPY
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+from pandas.core.accessor import PandasDelegate
+from pandas.core.base import (
+ NoNewAttributesMixin,
+ PandasObject,
+)
+
+
+def series_via_frame_from_dict(x, **kwargs):
+ return DataFrame({"a": x}, **kwargs)["a"]
+
+
+def series_via_frame_from_scalar(x, **kwargs):
+ return DataFrame(x, **kwargs)[0]
+
+
+@pytest.fixture(
+ params=[
+ Series,
+ series_via_frame_from_dict,
+ series_via_frame_from_scalar,
+ Index,
+ ],
+ ids=["Series", "DataFrame-dict", "DataFrame-array", "Index"],
+)
+def constructor(request):
+ return request.param
+
+
+class TestPandasDelegate:
+ class Delegator:
+ _properties = ["prop"]
+ _methods = ["test_method"]
+
+ def _set_prop(self, value):
+ self.prop = value
+
+ def _get_prop(self):
+ return self.prop
+
+ prop = property(_get_prop, _set_prop, doc="foo property")
+
+ def test_method(self, *args, **kwargs):
+ """a test method"""
+
+ class Delegate(PandasDelegate, PandasObject):
+ def __init__(self, obj) -> None:
+ self.obj = obj
+
+ def test_invalid_delegation(self):
+ # these show that in order for the delegation to work
+ # the _delegate_* methods need to be overridden to not raise
+ # a TypeError
+
+ self.Delegate._add_delegate_accessors(
+ delegate=self.Delegator,
+ accessors=self.Delegator._properties,
+ typ="property",
+ )
+ self.Delegate._add_delegate_accessors(
+ delegate=self.Delegator, accessors=self.Delegator._methods, typ="method"
+ )
+
+ delegate = self.Delegate(self.Delegator())
+
+ msg = "You cannot access the property prop"
+ with pytest.raises(TypeError, match=msg):
+ delegate.prop
+
+ msg = "The property prop cannot be set"
+ with pytest.raises(TypeError, match=msg):
+ delegate.prop = 5
+
+ msg = "You cannot access the property prop"
+ with pytest.raises(TypeError, match=msg):
+ delegate.prop
+
+ @pytest.mark.skipif(PYPY, reason="not relevant for PyPy")
+ def test_memory_usage(self):
+ # Delegate does not implement memory_usage.
+ # Check that we fall back to in-built `__sizeof__`
+ # GH 12924
+ delegate = self.Delegate(self.Delegator())
+ sys.getsizeof(delegate)
+
+
+class TestNoNewAttributesMixin:
+ def test_mixin(self):
+ class T(NoNewAttributesMixin):
+ pass
+
+ t = T()
+ assert not hasattr(t, "__frozen")
+
+ t.a = "test"
+ assert t.a == "test"
+
+ t._freeze()
+ assert "__frozen" in dir(t)
+ assert getattr(t, "__frozen")
+ msg = "You cannot add any new attribute"
+ with pytest.raises(AttributeError, match=msg):
+ t.b = "test"
+
+ assert not hasattr(t, "b")
+
+
+class TestConstruction:
+ # test certain constructor behaviours on dtype inference across Series,
+ # Index and DataFrame
+
+ @pytest.mark.parametrize(
+ "a",
+ [
+ np.array(["2263-01-01"], dtype="datetime64[D]"),
+ np.array([datetime(2263, 1, 1)], dtype=object),
+ np.array([np.datetime64("2263-01-01", "D")], dtype=object),
+ np.array(["2263-01-01"], dtype=object),
+ ],
+ ids=[
+ "datetime64[D]",
+ "object-datetime.datetime",
+ "object-numpy-scalar",
+ "object-string",
+ ],
+ )
+ def test_constructor_datetime_outofbound(
+ self, a, constructor, request, using_infer_string
+ ):
+ # GH-26853 (+ bug GH-26206 out of bound non-ns unit)
+
+ # No dtype specified (dtype inference)
+ # datetime64[non-ns] raise error, other cases result in object dtype
+ # and preserve original data
+ result = constructor(a)
+ if a.dtype.kind == "M" or isinstance(a[0], np.datetime64):
+ # Can't fit in nanosecond bounds -> get the nearest supported unit
+ assert result.dtype == "M8[s]"
+ elif isinstance(a[0], datetime):
+ assert result.dtype == "M8[us]", result.dtype
+ else:
+ result = constructor(a)
+ if using_infer_string and "object-string" in request.node.callspec.id:
+ assert result.dtype == "string"
+ else:
+ assert result.dtype == "object"
+ tm.assert_numpy_array_equal(result.to_numpy(), a)
+
+ # Explicit dtype specified
+ # Forced conversion fails for all -> all cases raise error
+ msg = "Out of bounds|Out of bounds .* present at position 0"
+ with pytest.raises(pd.errors.OutOfBoundsDatetime, match=msg):
+ constructor(a, dtype="datetime64[ns]")
+
+ def test_constructor_datetime_nonns(self, constructor):
+ arr = np.array(["2020-01-01T00:00:00.000000"], dtype="datetime64[us]")
+ dta = pd.core.arrays.DatetimeArray._simple_new(arr, dtype=arr.dtype)
+ expected = constructor(dta)
+ assert expected.dtype == arr.dtype
+
+ result = constructor(arr)
+ tm.assert_equal(result, expected)
+
+ # https://github.com/pandas-dev/pandas/issues/34843
+ arr.flags.writeable = False
+ result = constructor(arr)
+ tm.assert_equal(result, expected)
+
+ def test_constructor_from_dict_keys(self, constructor, using_infer_string):
+ # https://github.com/pandas-dev/pandas/issues/60343
+ d = {"a": 1, "b": 2}
+ result = constructor(d.keys(), dtype="str")
+ if using_infer_string:
+ assert result.dtype == "str"
+ else:
+ assert result.dtype == "object"
+ expected = constructor(list(d.keys()), dtype="str")
+ tm.assert_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_conversion.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_conversion.py
new file mode 100644
index 0000000000000000000000000000000000000000..50575523157c12d26f1a3d5212352ef707714ba9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_conversion.py
@@ -0,0 +1,603 @@
+import numpy as np
+import pytest
+
+from pandas.compat import HAS_PYARROW
+from pandas.compat.numpy import np_version_gt2
+
+from pandas.core.dtypes.dtypes import DatetimeTZDtype
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import (
+ DatetimeArray,
+ IntervalArray,
+ NumpyExtensionArray,
+ PeriodArray,
+ SparseArray,
+ StringArray,
+ TimedeltaArray,
+)
+from pandas.core.arrays.string_arrow import ArrowStringArray
+
+
+class TestToIterable:
+ # test that we convert an iterable to python types
+
+ dtypes = [
+ ("int8", int),
+ ("int16", int),
+ ("int32", int),
+ ("int64", int),
+ ("uint8", int),
+ ("uint16", int),
+ ("uint32", int),
+ ("uint64", int),
+ ("float16", float),
+ ("float32", float),
+ ("float64", float),
+ ("datetime64[ns]", Timestamp),
+ ("datetime64[ns, US/Eastern]", Timestamp),
+ ("timedelta64[ns]", Timedelta),
+ ]
+
+ @pytest.mark.parametrize("dtype, rdtype", dtypes)
+ @pytest.mark.parametrize(
+ "method",
+ [
+ lambda x: x.tolist(),
+ lambda x: x.to_list(),
+ lambda x: list(x),
+ lambda x: list(x.__iter__()),
+ ],
+ ids=["tolist", "to_list", "list", "iter"],
+ )
+ def test_iterable(self, index_or_series, method, dtype, rdtype):
+ # gh-10904
+ # gh-13258
+ # coerce iteration to underlying python / pandas types
+ typ = index_or_series
+ if dtype == "float16" and issubclass(typ, pd.Index):
+ with pytest.raises(NotImplementedError, match="float16 indexes are not "):
+ typ([1], dtype=dtype)
+ return
+ s = typ([1], dtype=dtype)
+ result = method(s)[0]
+ assert isinstance(result, rdtype)
+
+ @pytest.mark.parametrize(
+ "dtype, rdtype, obj",
+ [
+ ("object", object, "a"),
+ ("object", int, 1),
+ ("category", object, "a"),
+ ("category", int, 1),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "method",
+ [
+ lambda x: x.tolist(),
+ lambda x: x.to_list(),
+ lambda x: list(x),
+ lambda x: list(x.__iter__()),
+ ],
+ ids=["tolist", "to_list", "list", "iter"],
+ )
+ def test_iterable_object_and_category(
+ self, index_or_series, method, dtype, rdtype, obj
+ ):
+ # gh-10904
+ # gh-13258
+ # coerce iteration to underlying python / pandas types
+ typ = index_or_series
+ s = typ([obj], dtype=dtype)
+ result = method(s)[0]
+ assert isinstance(result, rdtype)
+
+ @pytest.mark.parametrize("dtype, rdtype", dtypes)
+ def test_iterable_items(self, dtype, rdtype):
+ # gh-13258
+ # test if items yields the correct boxed scalars
+ # this only applies to series
+ s = Series([1], dtype=dtype)
+ _, result = next(iter(s.items()))
+ assert isinstance(result, rdtype)
+
+ _, result = next(iter(s.items()))
+ assert isinstance(result, rdtype)
+
+ @pytest.mark.parametrize(
+ "dtype, rdtype", [*dtypes, ("object", int), ("category", int)]
+ )
+ def test_iterable_map(self, index_or_series, dtype, rdtype):
+ # gh-13236
+ # coerce iteration to underlying python / pandas types
+ typ = index_or_series
+ if dtype == "float16" and issubclass(typ, pd.Index):
+ with pytest.raises(NotImplementedError, match="float16 indexes are not "):
+ typ([1], dtype=dtype)
+ return
+ s = typ([1], dtype=dtype)
+ result = s.map(type)[0]
+ if not isinstance(rdtype, tuple):
+ rdtype = (rdtype,)
+ assert result in rdtype
+
+ @pytest.mark.parametrize(
+ "method",
+ [
+ lambda x: x.tolist(),
+ lambda x: x.to_list(),
+ lambda x: list(x),
+ lambda x: list(x.__iter__()),
+ ],
+ ids=["tolist", "to_list", "list", "iter"],
+ )
+ def test_categorial_datetimelike(self, method):
+ i = CategoricalIndex([Timestamp("1999-12-31"), Timestamp("2000-12-31")])
+
+ result = method(i)[0]
+ assert isinstance(result, Timestamp)
+
+ def test_iter_box_dt64(self, unit):
+ vals = [Timestamp("2011-01-01"), Timestamp("2011-01-02")]
+ ser = Series(vals).dt.as_unit(unit)
+ assert ser.dtype == f"datetime64[{unit}]"
+ for res, exp in zip(ser, vals, strict=True):
+ assert isinstance(res, Timestamp)
+ assert res.tz is None
+ assert res == exp
+ assert res.unit == unit
+
+ def test_iter_box_dt64tz(self, unit):
+ vals = [
+ Timestamp("2011-01-01", tz="US/Eastern"),
+ Timestamp("2011-01-02", tz="US/Eastern"),
+ ]
+ ser = Series(vals).dt.as_unit(unit)
+
+ assert ser.dtype == f"datetime64[{unit}, US/Eastern]"
+ for res, exp in zip(ser, vals, strict=True):
+ assert isinstance(res, Timestamp)
+ assert res.tz == exp.tz
+ assert res == exp
+ assert res.unit == unit
+
+ def test_iter_box_timedelta64(self, unit):
+ # timedelta
+ vals = [Timedelta("1 days"), Timedelta("2 days")]
+ ser = Series(vals).dt.as_unit(unit)
+ assert ser.dtype == f"timedelta64[{unit}]"
+ for res, exp in zip(ser, vals, strict=True):
+ assert isinstance(res, Timedelta)
+ assert res == exp
+ assert res.unit == unit
+
+ def test_iter_box_period(self):
+ # period
+ vals = [pd.Period("2011-01-01", freq="M"), pd.Period("2011-01-02", freq="M")]
+ s = Series(vals)
+ assert s.dtype == "Period[M]"
+ for res, exp in zip(s, vals, strict=True):
+ assert isinstance(res, pd.Period)
+ assert res.freq == "ME"
+ assert res == exp
+
+
+@pytest.mark.parametrize(
+ "arr, expected_type, dtype",
+ [
+ (np.array([0, 1], dtype=np.int64), np.ndarray, "int64"),
+ (np.array(["a", "b"]), np.ndarray, "object"),
+ (pd.Categorical(["a", "b"]), pd.Categorical, "category"),
+ (
+ pd.DatetimeIndex(["2017", "2018"], tz="US/Central"),
+ DatetimeArray,
+ "datetime64[ns, US/Central]",
+ ),
+ (
+ pd.PeriodIndex([2018, 2019], freq="Y"),
+ PeriodArray,
+ pd.core.dtypes.dtypes.PeriodDtype("Y-DEC"),
+ ),
+ (pd.IntervalIndex.from_breaks([0, 1, 2]), IntervalArray, "interval"),
+ (
+ pd.DatetimeIndex(["2017", "2018"]),
+ DatetimeArray,
+ "datetime64[ns]",
+ ),
+ (
+ pd.TimedeltaIndex([10**10]),
+ TimedeltaArray,
+ "m8[ns]",
+ ),
+ ],
+)
+def test_values_consistent(arr, expected_type, dtype, using_infer_string):
+ if using_infer_string and dtype == "object":
+ expected_type = ArrowStringArray if HAS_PYARROW else StringArray
+ l_values = Series(arr)._values
+ r_values = pd.Index(arr)._values
+ assert type(l_values) is expected_type
+ assert type(l_values) is type(r_values)
+
+ tm.assert_equal(l_values, r_values)
+
+
+@pytest.mark.parametrize("arr", [np.array([1, 2, 3])])
+def test_numpy_array(arr):
+ ser = Series(arr)
+ result = ser.array
+ expected = NumpyExtensionArray(arr)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_numpy_array_all_dtypes(any_numpy_dtype):
+ ser = Series(dtype=any_numpy_dtype)
+ result = ser.array
+ if np.dtype(any_numpy_dtype).kind == "M":
+ assert isinstance(result, DatetimeArray)
+ elif np.dtype(any_numpy_dtype).kind == "m":
+ assert isinstance(result, TimedeltaArray)
+ else:
+ assert isinstance(result, NumpyExtensionArray)
+
+
+@pytest.mark.parametrize(
+ "arr, attr",
+ [
+ (pd.Categorical(["a", "b"]), "_codes"),
+ (PeriodArray._from_sequence(["2000", "2001"], dtype="period[D]"), "_ndarray"),
+ (pd.array([0, pd.NA], dtype="Int64"), "_data"),
+ (IntervalArray.from_breaks([0, 1]), "_left"),
+ (SparseArray([0, 1]), "_sparse_values"),
+ (
+ DatetimeArray._from_sequence(np.array([1, 2], dtype="datetime64[ns]")),
+ "_ndarray",
+ ),
+ # tz-aware Datetime
+ (
+ DatetimeArray._from_sequence(
+ np.array(
+ ["2000-01-01T12:00:00", "2000-01-02T12:00:00"], dtype="M8[ns]"
+ ),
+ dtype=DatetimeTZDtype(tz="US/Central"),
+ ),
+ "_ndarray",
+ ),
+ ],
+)
+def test_array(arr, attr, index_or_series):
+ box = index_or_series
+
+ result = box(arr, copy=False).array
+
+ if attr:
+ arr = getattr(arr, attr)
+ result = getattr(result, attr)
+
+ assert np.shares_memory(result, arr)
+
+
+def test_array_multiindex_raises():
+ idx = pd.MultiIndex.from_product([["A"], ["a", "b"]])
+ msg = "MultiIndex has no single backing array"
+ with pytest.raises(ValueError, match=msg):
+ idx.array
+
+
+@pytest.mark.parametrize(
+ "arr, expected, zero_copy",
+ [
+ (np.array([1, 2], dtype=np.int64), np.array([1, 2], dtype=np.int64), True),
+ (pd.Categorical(["a", "b"]), np.array(["a", "b"], dtype=object), False),
+ (
+ pd.core.arrays.period_array(["2000", "2001"], freq="D"),
+ np.array([pd.Period("2000", freq="D"), pd.Period("2001", freq="D")]),
+ False,
+ ),
+ (pd.array([0, pd.NA], dtype="Int64"), np.array([0, np.nan]), False),
+ (
+ IntervalArray.from_breaks([0, 1, 2]),
+ np.array([pd.Interval(0, 1), pd.Interval(1, 2)], dtype=object),
+ False,
+ ),
+ (SparseArray([0, 1]), np.array([0, 1], dtype=np.int64), False),
+ # tz-naive datetime
+ (
+ DatetimeArray._from_sequence(np.array(["2000", "2001"], dtype="M8[ns]")),
+ np.array(["2000", "2001"], dtype="M8[ns]"),
+ True,
+ ),
+ # tz-aware stays tz`-aware
+ (
+ DatetimeArray._from_sequence(
+ np.array(["2000-01-01T06:00:00", "2000-01-02T06:00:00"], dtype="M8[ns]")
+ )
+ .tz_localize("UTC")
+ .tz_convert("US/Central"),
+ np.array(
+ [
+ Timestamp("2000-01-01", tz="US/Central"),
+ Timestamp("2000-01-02", tz="US/Central"),
+ ]
+ ),
+ False,
+ ),
+ # Timedelta
+ (
+ TimedeltaArray._from_sequence(
+ np.array([0, 3600000000000], dtype="i8").view("m8[ns]"),
+ dtype=np.dtype("m8[ns]"),
+ ),
+ np.array([0, 3600000000000], dtype="m8[ns]"),
+ True,
+ ),
+ # GH#26406 tz is preserved in Categorical[dt64tz]
+ (
+ pd.Categorical(date_range("2016-01-01", periods=2, tz="US/Pacific")),
+ np.array(
+ [
+ Timestamp("2016-01-01", tz="US/Pacific"),
+ Timestamp("2016-01-02", tz="US/Pacific"),
+ ]
+ ),
+ False,
+ ),
+ ],
+)
+def test_to_numpy(arr, expected, zero_copy, index_or_series_or_array, using_nan_is_na):
+ if not using_nan_is_na and arr[-1] is pd.NA:
+ expected = np.array([0, pd.NA], dtype=object)
+
+ box = index_or_series_or_array
+
+ with tm.assert_produces_warning(None):
+ thing = box(arr)
+
+ result = thing.to_numpy()
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.asarray(thing)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # Additionally, we check the `copy=` semantics for array/asarray
+ # (these are implemented by us via `__array__`).
+ result_cp1 = np.array(thing, copy=True)
+ result_cp2 = np.array(thing, copy=True)
+ # When called with `copy=True` NumPy/we should ensure a copy was made
+ assert not np.may_share_memory(result_cp1, result_cp2)
+
+ if not np_version_gt2:
+ # copy=False semantics are only supported in NumPy>=2.
+ return
+
+ if not zero_copy:
+ with pytest.raises(ValueError, match="Unable to avoid copy while creating"):
+ # An error is always acceptable for `copy=False`
+ np.array(thing, copy=False)
+
+ else:
+ result_nocopy1 = np.array(thing, copy=False)
+ result_nocopy2 = np.array(thing, copy=False)
+ # If copy=False was given, these must share the same data
+ assert np.may_share_memory(result_nocopy1, result_nocopy2)
+
+
+@pytest.mark.parametrize("as_series", [True, False])
+@pytest.mark.parametrize(
+ "arr", [np.array([1, 2, 3], dtype="int64"), np.array(["a", "b", "c"], dtype=object)]
+)
+def test_to_numpy_copy(arr, as_series, using_infer_string):
+ obj = pd.Index(arr, copy=False)
+ if as_series:
+ obj = Series(obj.values, copy=False)
+
+ # no copy by default
+ result = obj.to_numpy()
+ if using_infer_string and arr.dtype == object and obj.dtype.storage == "pyarrow":
+ assert np.shares_memory(arr, result) is False
+ else:
+ assert np.shares_memory(arr, result) is True
+
+ result = obj.to_numpy(copy=False)
+ if using_infer_string and arr.dtype == object and obj.dtype.storage == "pyarrow":
+ assert np.shares_memory(arr, result) is False
+ else:
+ assert np.shares_memory(arr, result) is True
+
+ # copy=True
+ result = obj.to_numpy(copy=True)
+ assert np.shares_memory(arr, result) is False
+
+
+@pytest.mark.parametrize("as_series", [True, False])
+def test_to_numpy_dtype(as_series):
+ tz = "US/Eastern"
+ obj = pd.DatetimeIndex(["2000", "2001"], tz=tz)
+ if as_series:
+ obj = Series(obj)
+
+ # preserve tz by default
+ result = obj.to_numpy()
+ expected = np.array(
+ [Timestamp("2000", tz=tz), Timestamp("2001", tz=tz)], dtype=object
+ )
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = obj.to_numpy(dtype="object")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = obj.to_numpy(dtype="M8[ns]")
+ expected = np.array(["2000-01-01T05", "2001-01-01T05"], dtype="M8[ns]")
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "values, dtype, na_value, expected",
+ [
+ ([1, 2, None], "float64", 0, [1.0, 2.0, 0.0]),
+ (
+ [Timestamp("2000").as_unit("s"), Timestamp("2000").as_unit("s"), pd.NaT],
+ None,
+ Timestamp("2000").as_unit("s"),
+ [np.datetime64("2000-01-01T00:00:00", "s")] * 3,
+ ),
+ ],
+)
+def test_to_numpy_na_value_numpy_dtype(
+ index_or_series, values, dtype, na_value, expected
+):
+ obj = index_or_series(values)
+ result = obj.to_numpy(dtype=dtype, na_value=na_value)
+ expected = np.array(expected)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data, multiindex, dtype, na_value, expected",
+ [
+ (
+ [1, 2, None, 4],
+ [(0, "a"), (0, "b"), (1, "b"), (1, "c")],
+ float,
+ None,
+ [1.0, 2.0, np.nan, 4.0],
+ ),
+ (
+ [1, 2, None, 4],
+ [(0, "a"), (0, "b"), (1, "b"), (1, "c")],
+ float,
+ np.nan,
+ [1.0, 2.0, np.nan, 4.0],
+ ),
+ (
+ [1.0, 2.0, np.nan, 4.0],
+ [("a", 0), ("a", 1), ("a", 2), ("b", 0)],
+ int,
+ 0,
+ [1, 2, 0, 4],
+ ),
+ (
+ [Timestamp("2000").as_unit("s"), Timestamp("2000").as_unit("s"), pd.NaT],
+ [
+ (0, Timestamp("2021").as_unit("s")),
+ (0, Timestamp("2022").as_unit("s")),
+ (1, Timestamp("2000").as_unit("s")),
+ ],
+ None,
+ Timestamp("2000").as_unit("s"),
+ [np.datetime64("2000-01-01T00:00:00", "s")] * 3,
+ ),
+ ],
+)
+def test_to_numpy_multiindex_series_na_value(
+ data, multiindex, dtype, na_value, expected
+):
+ index = pd.MultiIndex.from_tuples(multiindex)
+ series = Series(data, index=index)
+ result = series.to_numpy(dtype=dtype, na_value=na_value)
+ expected = np.array(expected)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_to_numpy_kwargs_raises():
+ # numpy
+ s = Series([1, 2, 3])
+ msg = r"to_numpy\(\) got an unexpected keyword argument 'foo'"
+ with pytest.raises(TypeError, match=msg):
+ s.to_numpy(foo=True)
+
+ # extension
+ s = Series([1, 2, 3], dtype="Int64")
+ with pytest.raises(TypeError, match=msg):
+ s.to_numpy(foo=True)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ {"a": [1, 2, 3], "b": [1, 2, None]},
+ {"a": np.array([1, 2, 3]), "b": np.array([1, 2, np.nan])},
+ {"a": pd.array([1, 2, 3]), "b": pd.array([1, 2, None])},
+ ],
+)
+@pytest.mark.parametrize("dtype, na_value", [(float, np.nan), (object, None)])
+def test_to_numpy_dataframe_na_value(data, dtype, na_value):
+ # https://github.com/pandas-dev/pandas/issues/33820
+ df = pd.DataFrame(data)
+ result = df.to_numpy(dtype=dtype, na_value=na_value)
+ expected = np.array([[1, 1], [2, 2], [3, na_value]], dtype=dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data, expected_data",
+ [
+ (
+ {"a": pd.array([1, 2, None])},
+ [[1.0], [2.0], [np.nan]],
+ ),
+ (
+ {"a": [1, 2, 3], "b": [1, 2, 3]},
+ [[1, 1], [2, 2], [3, 3]],
+ ),
+ ],
+)
+def test_to_numpy_dataframe_single_block(data, expected_data):
+ # https://github.com/pandas-dev/pandas/issues/33820
+ df = pd.DataFrame(data)
+ result = df.to_numpy(dtype=float, na_value=np.nan)
+ expected = np.array(expected_data, dtype=float)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_to_numpy_dataframe_single_block_no_mutate():
+ # https://github.com/pandas-dev/pandas/issues/33820
+ result = pd.DataFrame(np.array([1.0, 2.0, np.nan]))
+ expected = pd.DataFrame(np.array([1.0, 2.0, np.nan]))
+ result.to_numpy(na_value=0.0)
+ tm.assert_frame_equal(result, expected)
+
+
+class TestAsArray:
+ @pytest.mark.parametrize("tz", [None, "US/Central"])
+ def test_asarray_object_dt64(self, tz):
+ ser = Series(date_range("2000", periods=2, tz=tz))
+
+ with tm.assert_produces_warning(None):
+ # Future behavior (for tzaware case) with no warning
+ result = np.asarray(ser, dtype=object)
+
+ expected = np.array(
+ [Timestamp("2000-01-01", tz=tz), Timestamp("2000-01-02", tz=tz)]
+ )
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_asarray_tz_naive(self):
+ # This shouldn't produce a warning.
+ ser = Series(date_range("2000", periods=2, unit="ns"))
+ expected = np.array(["2000-01-01", "2000-01-02"], dtype="M8[ns]")
+ result = np.asarray(ser)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_asarray_tz_aware(self):
+ tz = "US/Central"
+ ser = Series(date_range("2000", periods=2, tz=tz))
+ expected = np.array(["2000-01-01T06", "2000-01-02T06"], dtype="M8[ns]")
+ result = np.asarray(ser, dtype="datetime64[ns]")
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ # Old behavior with no warning
+ result = np.asarray(ser, dtype="M8[ns]")
+
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_fillna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_fillna.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a10338776d882c5a947199be73b3898cd375f75
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_fillna.py
@@ -0,0 +1,61 @@
+"""
+Though Index.fillna and Series.fillna has separate impl,
+test here to confirm these works as the same
+"""
+
+import numpy as np
+import pytest
+
+from pandas import MultiIndex
+import pandas._testing as tm
+from pandas.tests.base.common import allow_na_ops
+
+
+def test_fillna(index_or_series_obj):
+ # GH 11343
+ obj = index_or_series_obj
+
+ if isinstance(obj, MultiIndex):
+ msg = "fillna is not defined for MultiIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ obj.fillna(0)
+ return
+
+ # values will not be changed
+ fill_value = obj.values[0] if len(obj) > 0 else 0
+ result = obj.fillna(fill_value)
+
+ tm.assert_equal(obj, result)
+
+ # check shallow_copied
+ assert obj is not result
+
+
+@pytest.mark.parametrize("null_obj", [np.nan, None])
+def test_fillna_null(null_obj, index_or_series_obj):
+ # GH 11343
+ obj = index_or_series_obj
+ klass = type(obj)
+
+ if not allow_na_ops(obj):
+ pytest.skip(f"{klass} doesn't allow for NA operations")
+ elif len(obj) < 1:
+ pytest.skip("Test doesn't make sense on empty data")
+ elif isinstance(obj, MultiIndex):
+ pytest.skip(f"MultiIndex can't hold '{null_obj}'")
+
+ obj = obj.copy(deep=True)
+ values = obj._values
+ fill_value = values[0]
+ expected = values.copy()
+ values[0:2] = null_obj
+ expected[0:2] = fill_value
+
+ expected = klass(expected)
+ obj = klass(values)
+
+ result = obj.fillna(fill_value)
+ tm.assert_equal(result, expected)
+
+ # check shallow_copied
+ assert obj is not result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_misc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_misc.py
new file mode 100644
index 0000000000000000000000000000000000000000..0eedf2b5dc52683d46f1c733c86d0c113d69d6ec
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_misc.py
@@ -0,0 +1,189 @@
+import sys
+
+import numpy as np
+import pytest
+
+from pandas.compat import PYPY
+
+from pandas.core.dtypes.common import (
+ is_object_dtype,
+)
+
+import pandas as pd
+from pandas import (
+ Index,
+ Series,
+)
+
+
+def test_isnull_notnull_docstrings():
+ # GH#41855 make sure its clear these are aliases
+ doc = pd.DataFrame.notnull.__doc__
+ assert doc.strip().startswith("DataFrame.notnull is an alias for DataFrame.notna.")
+ doc = pd.DataFrame.isnull.__doc__
+ assert doc.strip().startswith("DataFrame.isnull is an alias for DataFrame.isna.")
+
+ doc = Series.notnull.__doc__
+ assert doc.startswith("\nSeries.notnull is an alias for Series.notna.\n")
+ doc = Series.isnull.__doc__
+ assert doc.startswith("\nSeries.isnull is an alias for Series.isna.\n")
+
+
+@pytest.mark.parametrize(
+ "op_name, op",
+ [
+ ("add", "+"),
+ ("sub", "-"),
+ ("mul", "*"),
+ ("mod", "%"),
+ ("pow", "**"),
+ ("truediv", "/"),
+ ("floordiv", "//"),
+ ],
+)
+def test_binary_ops_docstring(frame_or_series, op_name, op):
+ # not using the all_arithmetic_functions fixture with _get_opstr
+ # as _get_opstr is used internally in the dynamic implementation of the docstring
+ klass = frame_or_series
+
+ operand1 = klass.__name__.lower()
+ operand2 = "other"
+ expected_str = " ".join([operand1, op, operand2])
+ assert expected_str in getattr(klass, op_name).__doc__
+
+ # reverse version of the binary ops
+ expected_str = " ".join([operand2, op, operand1])
+ assert expected_str in getattr(klass, "r" + op_name).__doc__
+
+
+def test_ndarray_compat_properties(index_or_series_obj):
+ obj = index_or_series_obj
+
+ # Check that we work.
+ for p in ["shape", "dtype", "T", "nbytes"]:
+ assert getattr(obj, p, None) is not None
+
+ # deprecated properties
+ for p in ["strides", "itemsize", "base", "data"]:
+ assert not hasattr(obj, p)
+
+ msg = "can only convert an array of size 1 to a Python scalar"
+ with pytest.raises(ValueError, match=msg):
+ obj.item() # len > 1
+
+ assert obj.ndim == 1
+ assert obj.size == len(obj)
+
+ assert Index([1]).item() == 1
+ assert Series([1]).item() == 1
+
+
+@pytest.mark.skipif(PYPY, reason="not relevant for PyPy")
+def test_memory_usage(index_or_series_memory_obj):
+ obj = index_or_series_memory_obj
+ # Clear index caches so that len(obj) == 0 report 0 memory usage
+ if isinstance(obj, Series):
+ is_ser = True
+ obj.index._engine.clear_mapping()
+ else:
+ is_ser = False
+ obj._engine.clear_mapping()
+
+ res = obj.memory_usage()
+ res_deep = obj.memory_usage(deep=True)
+
+ def _is_object_dtype(obj):
+ if isinstance(obj, pd.MultiIndex):
+ return any(_is_object_dtype(level) for level in obj.levels)
+ elif isinstance(obj.dtype, pd.CategoricalDtype):
+ return _is_object_dtype(obj.dtype.categories)
+ elif isinstance(obj.dtype, pd.StringDtype):
+ return obj.dtype.storage == "python"
+ return is_object_dtype(obj)
+
+ has_objects = _is_object_dtype(obj) or (is_ser and _is_object_dtype(obj.index))
+
+ if len(obj) == 0:
+ expected = 0
+ assert res_deep == res == expected
+ elif has_objects:
+ # only deep will pick them up
+ assert res_deep > res
+ else:
+ assert res == res_deep
+
+ # sys.getsizeof will call the .memory_usage with
+ # deep=True, and add on some GC overhead
+ diff = res_deep - sys.getsizeof(obj)
+ assert abs(diff) < 100
+
+
+def test_memory_usage_components_series(series_with_simple_index):
+ series = series_with_simple_index
+ total_usage = series.memory_usage(index=True)
+ non_index_usage = series.memory_usage(index=False)
+ index_usage = series.index.memory_usage()
+ assert total_usage == non_index_usage + index_usage
+
+
+def test_memory_usage_components_narrow_series(any_real_numpy_dtype):
+ series = Series(
+ range(5),
+ dtype=any_real_numpy_dtype,
+ index=[f"i-{i}" for i in range(5)],
+ name="a",
+ )
+ total_usage = series.memory_usage(index=True)
+ non_index_usage = series.memory_usage(index=False)
+ index_usage = series.index.memory_usage()
+ assert total_usage == non_index_usage + index_usage
+
+
+def test_searchsorted(request, index_or_series_obj):
+ # numpy.searchsorted calls obj.searchsorted under the hood.
+ # See gh-12238
+ obj = index_or_series_obj
+
+ if isinstance(obj, pd.MultiIndex):
+ # See gh-14833
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="np.searchsorted doesn't work on pd.MultiIndex: GH 14833"
+ )
+ )
+ elif obj.dtype.kind == "c" and isinstance(obj, Index):
+ # TODO: Should Series cases also raise? Looks like they use numpy
+ # comparison semantics https://github.com/numpy/numpy/issues/15981
+ mark = pytest.mark.xfail(reason="complex objects are not comparable")
+ request.applymarker(mark)
+
+ max_obj = max(obj, default=0)
+ index = np.searchsorted(obj, max_obj)
+ assert 0 <= index <= len(obj)
+
+ index = np.searchsorted(obj, max_obj, sorter=range(len(obj)))
+ assert 0 <= index <= len(obj)
+
+
+def test_access_by_position(index_flat):
+ index = index_flat
+
+ if len(index) == 0:
+ pytest.skip("Test doesn't make sense on empty data")
+
+ series = Series(index)
+ assert index[0] == series.iloc[0]
+ assert index[5] == series.iloc[5]
+ assert index[-1] == series.iloc[-1]
+
+ size = len(index)
+ assert index[-1] == index[size - 1]
+
+ msg = f"index {size} is out of bounds for axis 0 with size {size}"
+ if isinstance(index.dtype, pd.StringDtype) and index.dtype.storage == "pyarrow":
+ msg = "index out of bounds"
+ with pytest.raises(IndexError, match=msg):
+ index[size]
+ msg = "single positional indexer is out-of-bounds"
+ with pytest.raises(IndexError, match=msg):
+ series.iloc[size]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_transpose.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_transpose.py
new file mode 100644
index 0000000000000000000000000000000000000000..246f33d27476cb419620fb8571984619785f9b62
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_transpose.py
@@ -0,0 +1,56 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ CategoricalDtype,
+ DataFrame,
+)
+import pandas._testing as tm
+
+
+def test_transpose(index_or_series_obj):
+ obj = index_or_series_obj
+ tm.assert_equal(obj.transpose(), obj)
+
+
+def test_transpose_non_default_axes(index_or_series_obj):
+ msg = "the 'axes' parameter is not supported"
+ obj = index_or_series_obj
+ with pytest.raises(ValueError, match=msg):
+ obj.transpose(1)
+ with pytest.raises(ValueError, match=msg):
+ obj.transpose(axes=1)
+
+
+def test_numpy_transpose(index_or_series_obj):
+ msg = "the 'axes' parameter is not supported"
+ obj = index_or_series_obj
+ tm.assert_equal(np.transpose(obj), obj)
+
+ with pytest.raises(ValueError, match=msg):
+ np.transpose(obj, axes=1)
+
+
+@pytest.mark.parametrize(
+ "data, transposed_data, index, columns, dtype",
+ [
+ ([[1], [2]], [[1, 2]], ["a", "a"], ["b"], int),
+ ([[1], [2]], [[1, 2]], ["a", "a"], ["b"], CategoricalDtype([1, 2])),
+ ([[1, 2]], [[1], [2]], ["b"], ["a", "a"], int),
+ ([[1, 2]], [[1], [2]], ["b"], ["a", "a"], CategoricalDtype([1, 2])),
+ ([[1, 2], [3, 4]], [[1, 3], [2, 4]], ["a", "a"], ["b", "b"], int),
+ (
+ [[1, 2], [3, 4]],
+ [[1, 3], [2, 4]],
+ ["a", "a"],
+ ["b", "b"],
+ CategoricalDtype([1, 2, 3, 4]),
+ ),
+ ],
+)
+def test_duplicate_labels(data, transposed_data, index, columns, dtype):
+ # GH 42380
+ df = DataFrame(data, index=index, columns=columns, dtype=dtype)
+ result = df.T
+ expected = DataFrame(transposed_data, index=columns, columns=index, dtype=dtype)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_unique.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_unique.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae6818322ccc4b4f06f92872f21fb7671acec023
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_unique.py
@@ -0,0 +1,128 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.tests.base.common import allow_na_ops
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+def test_unique(index_or_series_obj):
+ obj = index_or_series_obj
+ obj = np.repeat(obj, range(1, len(obj) + 1))
+ result = obj.unique()
+
+ # dict.fromkeys preserves the order
+ unique_values = list(dict.fromkeys(obj.values))
+ if isinstance(obj, pd.MultiIndex):
+ expected = pd.MultiIndex.from_tuples(unique_values)
+ expected.names = obj.names
+ tm.assert_index_equal(result, expected, exact=True)
+ elif isinstance(obj, pd.Index):
+ expected = pd.Index(unique_values, dtype=obj.dtype)
+ if isinstance(obj.dtype, pd.DatetimeTZDtype):
+ expected = expected.normalize()
+ tm.assert_index_equal(result, expected, exact=True)
+ else:
+ expected = np.array(unique_values)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+@pytest.mark.parametrize("null_obj", [np.nan, None])
+def test_unique_null(null_obj, index_or_series_obj, using_nan_is_na):
+ obj = index_or_series_obj
+
+ if not allow_na_ops(obj):
+ pytest.skip("type doesn't allow for NA operations")
+ elif len(obj) < 1:
+ pytest.skip("Test doesn't make sense on empty data")
+ elif isinstance(obj, pd.MultiIndex):
+ pytest.skip(f"MultiIndex can't hold '{null_obj}'")
+ elif (
+ null_obj is not None
+ and not using_nan_is_na
+ and obj.dtype in ["Int64", "UInt16", "Float32"]
+ ):
+ pytest.skip("NaN is not a valid NA for this dtype.")
+
+ obj = obj.copy(deep=True)
+ values = obj._values
+ values[0:2] = null_obj
+
+ klass = type(obj)
+ repeated_values = np.repeat(values, range(1, len(values) + 1))
+ obj = klass(repeated_values, dtype=obj.dtype)
+ result = obj.unique()
+
+ unique_values_raw = dict.fromkeys(obj.values)
+ # because np.nan == np.nan is False, but None == None is True
+ # np.nan would be duplicated, whereas None wouldn't
+ unique_values_not_null = [val for val in unique_values_raw if not pd.isnull(val)]
+ unique_values = [null_obj, *unique_values_not_null]
+
+ if isinstance(obj, pd.Index):
+ expected = pd.Index(unique_values, dtype=obj.dtype)
+ if isinstance(obj.dtype, pd.DatetimeTZDtype):
+ result = result.normalize()
+ expected = expected.normalize()
+ tm.assert_index_equal(result, expected, exact=True)
+ else:
+ expected = np.array(unique_values, dtype=obj.dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_nunique(index_or_series_obj):
+ obj = index_or_series_obj
+ obj = np.repeat(obj, range(1, len(obj) + 1))
+ expected = len(obj.unique())
+ assert obj.nunique(dropna=False) == expected
+
+
+@pytest.mark.parametrize("null_obj", [np.nan, None])
+def test_nunique_null(null_obj, index_or_series_obj):
+ obj = index_or_series_obj
+
+ if not allow_na_ops(obj):
+ pytest.skip("type doesn't allow for NA operations")
+ elif isinstance(obj, pd.MultiIndex):
+ pytest.skip(f"MultiIndex can't hold '{null_obj}'")
+
+ obj = obj.copy(deep=True)
+ values = obj._values
+ values[0:2] = null_obj
+
+ klass = type(obj)
+ repeated_values = np.repeat(values, range(1, len(values) + 1))
+ obj = klass(repeated_values, dtype=obj.dtype)
+
+ if isinstance(obj, pd.CategoricalIndex):
+ assert obj.nunique() == len(obj.categories)
+ assert obj.nunique(dropna=False) == len(obj.categories) + 1
+ else:
+ num_unique_values = len(obj.unique())
+ assert obj.nunique() == max(0, num_unique_values - 1)
+ assert obj.nunique(dropna=False) == max(0, num_unique_values)
+
+
+@pytest.mark.single_cpu
+def test_unique_bad_unicode(index_or_series):
+ # regression test for #34550
+ uval = "\ud83d" # smiley emoji
+
+ obj = index_or_series([uval] * 2, dtype=object)
+ result = obj.unique()
+
+ if isinstance(obj, pd.Index):
+ expected = pd.Index(["\ud83d"], dtype=object)
+ tm.assert_index_equal(result, expected, exact=True)
+ else:
+ expected = np.array(["\ud83d"], dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_nunique_dropna(dropna):
+ # GH37566
+ ser = pd.Series(["yes", "yes", pd.NA, np.nan, None, pd.NaT])
+ res = ser.nunique(dropna)
+ assert res == 1 if dropna else 5
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_value_counts.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_value_counts.py
new file mode 100644
index 0000000000000000000000000000000000000000..a9479acfff44a1e0f2b9321fa01bd4b2025cd8cf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/base/test_value_counts.py
@@ -0,0 +1,420 @@
+import collections
+from datetime import timedelta
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Index,
+ Interval,
+ IntervalIndex,
+ MultiIndex,
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ Timestamp,
+ array,
+)
+import pandas._testing as tm
+from pandas.tests.base.common import allow_na_ops
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+def test_value_counts(index_or_series_obj):
+ obj = index_or_series_obj
+ obj = np.repeat(obj, range(1, len(obj) + 1))
+ result = obj.value_counts()
+
+ counter = collections.Counter(obj)
+ expected = Series(dict(counter.most_common()), dtype=np.int64, name="count")
+
+ if obj.dtype != np.float16:
+ expected.index = expected.index.astype(obj.dtype)
+ else:
+ with pytest.raises(NotImplementedError, match="float16 indexes are not "):
+ expected.index.astype(obj.dtype)
+ return
+ if isinstance(expected.index, MultiIndex):
+ expected.index.names = obj.names
+ else:
+ expected.index.name = obj.name
+
+ if not isinstance(result.dtype, np.dtype):
+ if getattr(obj.dtype, "storage", "") == "pyarrow":
+ expected = expected.astype("int64[pyarrow]")
+ else:
+ # i.e IntegerDtype
+ expected = expected.astype("Int64")
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("null_obj", [np.nan, None])
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+def test_value_counts_null(null_obj, index_or_series_obj):
+ orig = index_or_series_obj
+
+ if not allow_na_ops(orig):
+ pytest.skip("type doesn't allow for NA operations")
+ elif len(orig) < 1:
+ pytest.skip("Test doesn't make sense on empty data")
+ elif isinstance(orig, MultiIndex):
+ pytest.skip(f"MultiIndex can't hold '{null_obj}'")
+
+ obj = orig.copy(deep=True)
+ values = obj._values
+ values[0:2] = null_obj
+
+ klass = type(obj)
+ repeated_values = np.repeat(values, range(1, len(values) + 1))
+ obj = klass(repeated_values, dtype=obj.dtype)
+
+ # because np.nan == np.nan is False, but None == None is True
+ # np.nan would be duplicated, whereas None wouldn't
+ counter = collections.Counter(obj.dropna())
+ expected = Series(dict(counter.most_common()), dtype=np.int64, name="count")
+
+ if obj.dtype != np.float16:
+ expected.index = expected.index.astype(obj.dtype)
+ else:
+ with pytest.raises(NotImplementedError, match="float16 indexes are not "):
+ expected.index.astype(obj.dtype)
+ return
+ expected.index.name = obj.name
+
+ result = obj.value_counts()
+
+ if not isinstance(result.dtype, np.dtype):
+ if getattr(obj.dtype, "storage", "") == "pyarrow":
+ expected = expected.astype("int64[pyarrow]")
+ else:
+ # i.e IntegerDtype
+ expected = expected.astype("Int64")
+ tm.assert_series_equal(result, expected)
+
+ expected[null_obj] = 3
+
+ result = obj.value_counts(dropna=False)
+ expected = expected.sort_index()
+ result = result.sort_index()
+ tm.assert_series_equal(result, expected)
+
+
+def test_value_counts_inferred(index_or_series, using_infer_string):
+ klass = index_or_series
+ s_values = ["a", "b", "b", "b", "b", "c", "d", "d", "a", "a"]
+ s = klass(s_values)
+ expected = Series([4, 3, 2, 1], index=["b", "a", "d", "c"], name="count")
+ tm.assert_series_equal(s.value_counts(), expected)
+
+ if isinstance(s, Index):
+ exp = Index(np.unique(np.array(s_values, dtype=np.object_)))
+ tm.assert_index_equal(s.unique(), exp)
+ else:
+ exp = np.unique(np.array(s_values, dtype=np.object_))
+ if using_infer_string:
+ exp = array(exp, dtype="str")
+ tm.assert_equal(s.unique(), exp)
+
+ assert s.nunique() == 4
+ # don't sort, have to sort after the fact as not sorting is
+ # platform-dep
+ hist = s.value_counts(sort=False).sort_values()
+ expected = Series([3, 1, 4, 2], index=list("acbd"), name="count").sort_values()
+ tm.assert_series_equal(hist, expected)
+
+ # sort ascending
+ hist = s.value_counts(ascending=True)
+ expected = Series([1, 2, 3, 4], index=list("cdab"), name="count")
+ tm.assert_series_equal(hist, expected)
+
+ # relative histogram.
+ hist = s.value_counts(normalize=True)
+ expected = Series(
+ [0.4, 0.3, 0.2, 0.1], index=["b", "a", "d", "c"], name="proportion"
+ )
+ tm.assert_series_equal(hist, expected)
+
+
+def test_value_counts_bins(index_or_series, using_infer_string):
+ klass = index_or_series
+ s_values = ["a", "b", "b", "b", "b", "c", "d", "d", "a", "a"]
+ s = klass(s_values)
+
+ # bins
+ msg = "bins argument only works with numeric data"
+ with pytest.raises(TypeError, match=msg):
+ s.value_counts(bins=1)
+
+ s1 = Series([1, 1, 2, 3])
+ res1 = s1.value_counts(bins=1)
+ exp1 = Series({Interval(0.997, 3.0): 4}, name="count")
+ tm.assert_series_equal(res1, exp1)
+ res1n = s1.value_counts(bins=1, normalize=True)
+ exp1n = Series({Interval(0.997, 3.0): 1.0}, name="proportion")
+ tm.assert_series_equal(res1n, exp1n)
+
+ if isinstance(s1, Index):
+ tm.assert_index_equal(s1.unique(), Index([1, 2, 3]))
+ else:
+ exp = np.array([1, 2, 3], dtype=np.int64)
+ tm.assert_numpy_array_equal(s1.unique(), exp)
+
+ assert s1.nunique() == 3
+
+ # these return the same
+ res4 = s1.value_counts(bins=4, dropna=True)
+ intervals = IntervalIndex.from_breaks([0.997, 1.5, 2.0, 2.5, 3.0])
+ exp4 = Series([2, 1, 1, 0], index=intervals.take([0, 1, 3, 2]), name="count")
+ tm.assert_series_equal(res4, exp4)
+
+ res4 = s1.value_counts(bins=4, dropna=False)
+ intervals = IntervalIndex.from_breaks([0.997, 1.5, 2.0, 2.5, 3.0])
+ exp4 = Series([2, 1, 1, 0], index=intervals.take([0, 1, 3, 2]), name="count")
+ tm.assert_series_equal(res4, exp4)
+
+ res4n = s1.value_counts(bins=4, normalize=True)
+ exp4n = Series(
+ [0.5, 0.25, 0.25, 0], index=intervals.take([0, 1, 3, 2]), name="proportion"
+ )
+ tm.assert_series_equal(res4n, exp4n)
+
+ # handle NA's properly
+ s_values = ["a", "b", "b", "b", np.nan, np.nan, "d", "d", "a", "a", "b"]
+ s = klass(s_values)
+ expected = Series([4, 3, 2], index=["b", "a", "d"], name="count")
+ tm.assert_series_equal(s.value_counts(), expected)
+
+ if isinstance(s, Index):
+ exp = Index(["a", "b", np.nan, "d"])
+ tm.assert_index_equal(s.unique(), exp)
+ else:
+ exp = np.array(["a", "b", np.nan, "d"], dtype=object)
+ if using_infer_string:
+ exp = array(exp, dtype="str")
+ tm.assert_equal(s.unique(), exp)
+ assert s.nunique() == 3
+
+ s = klass({}) if klass is dict else klass({}, dtype=object)
+ expected = Series([], dtype=np.int64, name="count")
+ tm.assert_series_equal(s.value_counts(), expected, check_index_type=False)
+ # returned dtype differs depending on original
+ if isinstance(s, Index):
+ tm.assert_index_equal(s.unique(), Index([]), exact=False)
+ else:
+ tm.assert_numpy_array_equal(s.unique(), np.array([]), check_dtype=False)
+
+ assert s.nunique() == 0
+
+
+def test_value_counts_datetime64(index_or_series, unit):
+ klass = index_or_series
+
+ # GH 3002, datetime64[ns]
+ # don't test names though
+ df = pd.DataFrame(
+ {
+ "person_id": ["xxyyzz", "xxyyzz", "xxyyzz", "xxyyww", "foofoo", "foofoo"],
+ "dt": pd.to_datetime(
+ [
+ "2010-01-01",
+ "2010-01-01",
+ "2010-01-01",
+ "2009-01-01",
+ "2008-09-09",
+ "2008-09-09",
+ ]
+ ).as_unit(unit),
+ "food": ["PIE", "GUM", "EGG", "EGG", "PIE", "GUM"],
+ }
+ )
+
+ s = klass(df["dt"].copy())
+ s.name = None
+ idx = pd.to_datetime(
+ ["2010-01-01 00:00:00", "2008-09-09 00:00:00", "2009-01-01 00:00:00"]
+ ).as_unit(unit)
+ expected_s = Series([3, 2, 1], index=idx, name="count")
+ tm.assert_series_equal(s.value_counts(), expected_s)
+
+ expected = array(
+ np.array(
+ ["2010-01-01 00:00:00", "2009-01-01 00:00:00", "2008-09-09 00:00:00"],
+ dtype=f"datetime64[{unit}]",
+ )
+ )
+ result = s.unique()
+ if isinstance(s, Index):
+ tm.assert_index_equal(result, DatetimeIndex(expected))
+ else:
+ tm.assert_extension_array_equal(result, expected)
+
+ assert s.nunique() == 3
+
+ # with NaT
+ s = df["dt"].copy()
+ s = klass(list(s.values) + [pd.NaT] * 4)
+ if klass is Series:
+ s = s.dt.as_unit(unit)
+ else:
+ s = s.as_unit(unit)
+
+ result = s.value_counts()
+ assert result.index.dtype == f"datetime64[{unit}]"
+ tm.assert_series_equal(result, expected_s)
+
+ result = s.value_counts(dropna=False)
+ expected_s = pd.concat(
+ [
+ Series([4], index=DatetimeIndex([pd.NaT]).as_unit(unit), name="count"),
+ expected_s,
+ ]
+ )
+ tm.assert_series_equal(result, expected_s)
+
+ assert s.dtype == f"datetime64[{unit}]"
+ unique = s.unique()
+ assert unique.dtype == f"datetime64[{unit}]"
+
+ # numpy_array_equal cannot compare pd.NaT
+ if isinstance(s, Index):
+ exp_idx = DatetimeIndex([*expected.tolist(), pd.NaT]).as_unit(unit)
+ tm.assert_index_equal(unique, exp_idx)
+ else:
+ tm.assert_extension_array_equal(unique[:3], expected)
+ assert pd.isna(unique[3])
+
+ assert s.nunique() == 3
+ assert s.nunique(dropna=False) == 4
+
+
+def test_value_counts_timedelta64(index_or_series, unit):
+ # timedelta64[ns]
+ klass = index_or_series
+
+ day = Timedelta(timedelta(1)).as_unit(unit)
+ tdi = TimedeltaIndex([day], name="dt").as_unit(unit)
+
+ tdvals = np.zeros(6, dtype=f"m8[{unit}]") + day
+ td = klass(tdvals, name="dt")
+
+ result = td.value_counts()
+ expected_s = Series([6], index=tdi, name="count")
+ tm.assert_series_equal(result, expected_s)
+
+ expected = tdi
+ result = td.unique()
+ if isinstance(td, Index):
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_extension_array_equal(result, expected._values)
+
+ td2 = day + np.zeros(6, dtype=f"m8[{unit}]")
+ td2 = klass(td2, name="dt")
+ result2 = td2.value_counts()
+ tm.assert_series_equal(result2, expected_s)
+
+
+def test_value_counts_with_nan(dropna, index_or_series):
+ # GH31944
+ klass = index_or_series
+ values = [True, pd.NA, np.nan]
+ obj = klass(values)
+ res = obj.value_counts(dropna=dropna)
+ if dropna is True:
+ expected = Series([1], index=Index([True], dtype=obj.dtype), name="count")
+ else:
+ expected = Series([1, 1, 1], index=[True, pd.NA, np.nan], name="count")
+ tm.assert_series_equal(res, expected)
+
+
+def test_value_counts_object_inference_deprecated():
+ # GH#56161
+ dti = pd.date_range("2016-01-01", periods=3, tz="UTC")
+
+ idx = dti.astype(object)
+ res = idx.value_counts()
+
+ exp = dti.value_counts()
+ exp.index = exp.index.astype(object)
+ tm.assert_series_equal(res, exp)
+
+
+@pytest.mark.parametrize(
+ ("index", "expected_index"),
+ [
+ [
+ pd.date_range("2016-01-01", periods=5, freq="D", unit="ns"),
+ pd.date_range("2016-01-01", periods=5, freq="D", unit="ns"),
+ ],
+ [
+ pd.timedelta_range(Timedelta(0), periods=5, freq="h"),
+ pd.timedelta_range(Timedelta(0), periods=5, freq="h"),
+ ],
+ [
+ DatetimeIndex(
+ [Timestamp("2016-01-01") + Timedelta(days=i) for i in range(1)]
+ + [Timestamp("2016-01-02")]
+ + [Timestamp("2016-01-01") + Timedelta(days=i) for i in range(1, 5)]
+ ),
+ DatetimeIndex(pd.date_range("2016-01-01", periods=5, freq="D", unit="us")),
+ ],
+ [
+ TimedeltaIndex(
+ [Timedelta(hours=i) for i in range(1)]
+ + [Timedelta(hours=1)]
+ + [Timedelta(hours=i) for i in range(1, 5)],
+ ),
+ TimedeltaIndex(pd.timedelta_range(Timedelta(hours=0), periods=5, freq="h")),
+ ],
+ [
+ DatetimeIndex(
+ [Timestamp("2016-01-01") + Timedelta(days=i) for i in range(2)]
+ + [Timestamp("2016-01-01") + Timedelta(days=i) for i in range(3, 5)],
+ ),
+ DatetimeIndex(
+ [Timestamp("2016-01-01") + Timedelta(days=i) for i in range(2)]
+ + [Timestamp("2016-01-01") + Timedelta(days=i) for i in range(3, 5)],
+ ),
+ ],
+ [
+ TimedeltaIndex(
+ [Timedelta(hours=i) for i in range(2)]
+ + [Timedelta(hours=i) for i in range(3, 5)],
+ ),
+ TimedeltaIndex(
+ [Timedelta(hours=i) for i in range(2)]
+ + [Timedelta(hours=i) for i in range(3, 5)],
+ ),
+ ],
+ [
+ DatetimeIndex(
+ [Timestamp("2016-01-01")]
+ + [pd.NaT]
+ + [Timestamp("2016-01-01") + Timedelta(days=i) for i in range(1, 5)],
+ ),
+ DatetimeIndex(
+ [Timestamp("2016-01-01")]
+ + [pd.NaT]
+ + [Timestamp("2016-01-01") + Timedelta(days=i) for i in range(1, 5)],
+ ),
+ ],
+ [
+ TimedeltaIndex(
+ [Timedelta(hours=0)]
+ + [pd.NaT]
+ + [Timedelta(hours=i) for i in range(1, 5)],
+ ),
+ TimedeltaIndex(
+ [Timedelta(hours=0)]
+ + [pd.NaT]
+ + [Timedelta(hours=i) for i in range(1, 5)],
+ ),
+ ],
+ ],
+)
+def test_value_counts_index_datetimelike(index, expected_index):
+ vc = index.value_counts(sort=False, dropna=False)
+ tm.assert_index_equal(vc.index, expected_index)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/computation/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/computation/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/computation/test_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/computation/test_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..856a5b3a22a95d35cc577050f52d762b065e3ddf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/computation/test_compat.py
@@ -0,0 +1,32 @@
+import pytest
+
+from pandas.compat._optional import VERSIONS
+
+import pandas as pd
+from pandas.core.computation import expr
+from pandas.core.computation.engines import ENGINES
+from pandas.util.version import Version
+
+
+def test_compat():
+ # test we have compat with our version of numexpr
+
+ from pandas.core.computation.check import NUMEXPR_INSTALLED
+
+ ne = pytest.importorskip("numexpr")
+
+ ver = ne.__version__
+ if Version(ver) < Version(VERSIONS["numexpr"]):
+ assert not NUMEXPR_INSTALLED
+ else:
+ assert NUMEXPR_INSTALLED
+
+
+@pytest.mark.parametrize("engine", ENGINES)
+@pytest.mark.parametrize("parser", expr.PARSERS)
+def test_invalid_numexpr_version(engine, parser):
+ if engine == "numexpr":
+ pytest.importorskip("numexpr")
+ a, b = 1, 2 # noqa: F841
+ res = pd.eval("a + b", engine=engine, parser=parser)
+ assert res == 3
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/computation/test_eval.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/computation/test_eval.py
new file mode 100644
index 0000000000000000000000000000000000000000..e14b997310baa321f85090e6c4dfc9068867e891
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/computation/test_eval.py
@@ -0,0 +1,2044 @@
+from __future__ import annotations
+
+from functools import reduce
+from itertools import product
+import operator
+
+import numpy as np
+import pytest
+
+from pandas.compat import (
+ PY312,
+ PY314,
+)
+from pandas.compat._optional import import_optional_dependency
+from pandas.errors import (
+ NumExprClobberingError,
+ PerformanceWarning,
+ UndefinedVariableError,
+)
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.common import (
+ is_bool,
+ is_float,
+ is_list_like,
+ is_scalar,
+)
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.computation import (
+ expr,
+ pytables,
+)
+from pandas.core.computation.engines import ENGINES
+from pandas.core.computation.expr import (
+ BaseExprVisitor,
+ PandasExprVisitor,
+ PythonExprVisitor,
+)
+from pandas.core.computation.expressions import (
+ NUMEXPR_INSTALLED,
+ USE_NUMEXPR,
+)
+from pandas.core.computation.ops import (
+ ARITH_OPS_SYMS,
+ _binary_math_ops,
+ _binary_ops_dict,
+ _unary_math_ops,
+)
+from pandas.core.computation.scope import DEFAULT_GLOBALS
+from pandas.util.version import Version
+
+numexpr = import_optional_dependency("numexpr", errors="ignore")
+
+
+@pytest.fixture(
+ params=(
+ pytest.param(
+ engine,
+ marks=[
+ pytest.mark.skipif(
+ engine == "numexpr" and not USE_NUMEXPR,
+ reason=f"numexpr enabled->{USE_NUMEXPR}, "
+ f"installed->{NUMEXPR_INSTALLED}",
+ ),
+ td.skip_if_no("numexpr"),
+ ],
+ )
+ for engine in ENGINES
+ )
+)
+def engine(request):
+ return request.param
+
+
+@pytest.fixture(params=expr.PARSERS)
+def parser(request):
+ return request.param
+
+
+def _eval_single_bin(lhs, cmp1, rhs, engine):
+ c = _binary_ops_dict[cmp1]
+ if ENGINES[engine].has_neg_frac:
+ try:
+ return c(lhs, rhs)
+ except ValueError as e:
+ if str(e).startswith(
+ "negative number cannot be raised to a fractional power"
+ ):
+ return np.nan
+ raise
+ return c(lhs, rhs)
+
+
+# TODO: using range(5) here is a kludge
+@pytest.fixture(
+ params=list(range(5)),
+ ids=["DataFrame", "Series", "SeriesNaN", "DataFrameNaN", "float"],
+)
+def lhs(request):
+ rng = np.random.default_rng(2)
+ if request.param == 0:
+ return DataFrame(rng.standard_normal((10, 5)))
+ elif request.param == 1:
+ return Series(rng.standard_normal(5))
+ elif request.param == 2:
+ return Series([1, 2, np.nan, np.nan, 5])
+ elif request.param == 3:
+ nan_df1 = DataFrame(rng.standard_normal((10, 5)))
+ nan_df1[nan_df1 > 0.5] = np.nan
+ return nan_df1
+ elif request.param == 4:
+ return rng.standard_normal()
+ else:
+ raise ValueError(f"{request.param}")
+
+
+rhs = lhs
+midhs = lhs
+
+
+@pytest.fixture
+def idx_func_dict():
+ return {
+ "i": lambda n: Index(np.arange(n), dtype=np.int64),
+ "f": lambda n: Index(np.arange(n), dtype=np.float64),
+ "s": lambda n: Index([f"{i}_{chr(i)}" for i in range(97, 97 + n)]),
+ "dt": lambda n: date_range("2020-01-01", periods=n),
+ "td": lambda n: timedelta_range("1 day", periods=n),
+ "p": lambda n: period_range("2020-01-01", periods=n, freq="D"),
+ }
+
+
+class TestEval:
+ @pytest.mark.parametrize(
+ "cmp1",
+ ["!=", "==", "<=", ">=", "<", ">"],
+ ids=["ne", "eq", "le", "ge", "lt", "gt"],
+ )
+ @pytest.mark.parametrize("cmp2", [">", "<"], ids=["gt", "lt"])
+ @pytest.mark.parametrize("binop", expr.BOOL_OPS_SYMS)
+ def test_complex_cmp_ops(self, cmp1, cmp2, binop, lhs, rhs, engine, parser):
+ if parser == "python" and binop in ["and", "or"]:
+ msg = "'BoolOp' nodes are not implemented"
+ ex = f"(lhs {cmp1} rhs) {binop} (lhs {cmp2} rhs)"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(ex, engine=engine, parser=parser)
+ return
+
+ lhs_new = _eval_single_bin(lhs, cmp1, rhs, engine)
+ rhs_new = _eval_single_bin(lhs, cmp2, rhs, engine)
+ expected = _eval_single_bin(lhs_new, binop, rhs_new, engine)
+
+ ex = f"(lhs {cmp1} rhs) {binop} (lhs {cmp2} rhs)"
+ result = pd.eval(ex, engine=engine, parser=parser)
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("cmp_op", expr.CMP_OPS_SYMS)
+ def test_simple_cmp_ops(self, cmp_op, lhs, rhs, engine, parser):
+ lhs = lhs < 0
+ rhs = rhs < 0
+
+ if parser == "python" and cmp_op in ["in", "not in"]:
+ msg = "'(In|NotIn)' nodes are not implemented"
+ ex = f"lhs {cmp_op} rhs"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(ex, engine=engine, parser=parser)
+ return
+
+ ex = f"lhs {cmp_op} rhs"
+ msg = "|".join(
+ [
+ r"only list-like( or dict-like)? objects are allowed to be "
+ r"passed to (DataFrame\.)?isin\(\), you passed a "
+ r"(`|')bool(`|')",
+ "argument of type 'bool' is not .*",
+ ]
+ )
+ if cmp_op in ("in", "not in") and not is_list_like(rhs):
+ with pytest.raises(TypeError, match=msg):
+ pd.eval(
+ ex,
+ engine=engine,
+ parser=parser,
+ local_dict={"lhs": lhs, "rhs": rhs},
+ )
+ else:
+ expected = _eval_single_bin(lhs, cmp_op, rhs, engine)
+ result = pd.eval(ex, engine=engine, parser=parser)
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("op", expr.CMP_OPS_SYMS)
+ def test_compound_invert_op(self, op, lhs, rhs, request, engine, parser):
+ if parser == "python" and op in ["in", "not in"]:
+ msg = "'(In|NotIn)' nodes are not implemented"
+ ex = f"~(lhs {op} rhs)"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(ex, engine=engine, parser=parser)
+ return
+
+ if (
+ is_float(lhs)
+ and not is_float(rhs)
+ and op in ["in", "not in"]
+ and engine == "python"
+ and parser == "pandas"
+ ):
+ mark = pytest.mark.xfail(
+ reason="Looks like expected is negative, unclear whether "
+ "expected is incorrect or result is incorrect"
+ )
+ request.applymarker(mark)
+ skip_these = ["in", "not in"]
+ ex = f"~(lhs {op} rhs)"
+
+ msg = "|".join(
+ [
+ r"only list-like( or dict-like)? objects are allowed to be "
+ r"passed to (DataFrame\.)?isin\(\), you passed a "
+ r"(`|')float(`|')",
+ "argument of type 'float' is not .*",
+ ]
+ )
+ if is_scalar(rhs) and op in skip_these:
+ with pytest.raises(TypeError, match=msg):
+ pd.eval(
+ ex,
+ engine=engine,
+ parser=parser,
+ local_dict={"lhs": lhs, "rhs": rhs},
+ )
+ else:
+ # compound
+ if is_scalar(lhs) and is_scalar(rhs):
+ lhs, rhs = (np.array([x]) for x in (lhs, rhs))
+ expected = _eval_single_bin(lhs, op, rhs, engine)
+ if is_scalar(expected):
+ expected = not expected
+ else:
+ expected = ~expected
+ result = pd.eval(ex, engine=engine, parser=parser)
+ tm.assert_almost_equal(expected, result)
+
+ @pytest.mark.parametrize("cmp1", ["<", ">"])
+ @pytest.mark.parametrize("cmp2", ["<", ">"])
+ def test_chained_cmp_op(self, cmp1, cmp2, lhs, midhs, rhs, engine, parser):
+ mid = midhs
+ if parser == "python":
+ ex1 = f"lhs {cmp1} mid {cmp2} rhs"
+ msg = "'BoolOp' nodes are not implemented"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(ex1, engine=engine, parser=parser)
+ return
+
+ lhs_new = _eval_single_bin(lhs, cmp1, mid, engine)
+ rhs_new = _eval_single_bin(mid, cmp2, rhs, engine)
+
+ if lhs_new is not None and rhs_new is not None:
+ ex1 = f"lhs {cmp1} mid {cmp2} rhs"
+ ex2 = f"lhs {cmp1} mid and mid {cmp2} rhs"
+ ex3 = f"(lhs {cmp1} mid) & (mid {cmp2} rhs)"
+ expected = _eval_single_bin(lhs_new, "&", rhs_new, engine)
+
+ for ex in (ex1, ex2, ex3):
+ result = pd.eval(ex, engine=engine, parser=parser)
+
+ tm.assert_almost_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "arith1", sorted(set(ARITH_OPS_SYMS).difference({"**", "//", "%"}))
+ )
+ def test_binary_arith_ops(self, arith1, lhs, rhs, engine, parser):
+ ex = f"lhs {arith1} rhs"
+ result = pd.eval(ex, engine=engine, parser=parser)
+ expected = _eval_single_bin(lhs, arith1, rhs, engine)
+
+ tm.assert_almost_equal(result, expected)
+ ex = f"lhs {arith1} rhs {arith1} rhs"
+ result = pd.eval(ex, engine=engine, parser=parser)
+ nlhs = _eval_single_bin(lhs, arith1, rhs, engine)
+ try:
+ nlhs, ghs = nlhs.align(rhs)
+ except (ValueError, TypeError, AttributeError):
+ # ValueError: series frame or frame series align
+ # TypeError, AttributeError: series or frame with scalar align
+ return
+ else:
+ if engine == "numexpr":
+ import numexpr as ne
+
+ # direct numpy comparison
+ expected = ne.evaluate(f"nlhs {arith1} ghs")
+ # Update assert statement due to unreliable numerical
+ # precision component (GH37328)
+ # TODO: update testing code so that assert_almost_equal statement
+ # can be replaced again by the assert_numpy_array_equal statement
+ tm.assert_almost_equal(result.values, expected)
+ else:
+ expected = eval(f"nlhs {arith1} ghs")
+ tm.assert_almost_equal(result, expected)
+
+ # modulus, pow, and floor division require special casing
+
+ def test_modulus(self, lhs, rhs, engine, parser):
+ ex = r"lhs % rhs"
+ result = pd.eval(ex, engine=engine, parser=parser)
+ expected = lhs % rhs
+ tm.assert_almost_equal(result, expected)
+
+ if engine == "numexpr":
+ import numexpr as ne
+
+ expected = ne.evaluate(r"expected % rhs")
+ if isinstance(result, (DataFrame, Series)):
+ tm.assert_almost_equal(result.values, expected)
+ else:
+ tm.assert_almost_equal(result, expected.item())
+ else:
+ expected = _eval_single_bin(expected, "%", rhs, engine)
+ tm.assert_almost_equal(result, expected)
+
+ def test_floor_division(self, lhs, rhs, engine, parser):
+ ex = "lhs // rhs"
+
+ if engine == "python" or (
+ engine == "numexpr" and Version(numexpr.__version__) >= Version("2.13.0")
+ ):
+ res = pd.eval(ex, engine=engine, parser=parser)
+ expected = lhs // rhs
+ tm.assert_equal(res, expected)
+ else:
+ msg = (
+ r"unsupported operand type\(s\) for //: 'VariableNode' and "
+ "'VariableNode'"
+ )
+ with pytest.raises(TypeError, match=msg):
+ pd.eval(
+ ex,
+ local_dict={"lhs": lhs, "rhs": rhs},
+ engine=engine,
+ parser=parser,
+ )
+
+ @td.skip_if_windows
+ def test_pow(self, lhs, rhs, engine, parser):
+ # odd failure on win32 platform, so skip
+ ex = "lhs ** rhs"
+ expected = _eval_single_bin(lhs, "**", rhs, engine)
+ result = pd.eval(ex, engine=engine, parser=parser)
+
+ if (
+ is_scalar(lhs)
+ and is_scalar(rhs)
+ and isinstance(expected, (complex, np.complexfloating))
+ and np.isnan(result)
+ ):
+ msg = "(DataFrame.columns|numpy array) are different"
+ with pytest.raises(AssertionError, match=msg):
+ tm.assert_numpy_array_equal(result, expected)
+ else:
+ tm.assert_almost_equal(result, expected)
+
+ ex = "(lhs ** rhs) ** rhs"
+ result = pd.eval(ex, engine=engine, parser=parser)
+
+ middle = _eval_single_bin(lhs, "**", rhs, engine)
+ expected = _eval_single_bin(middle, "**", rhs, engine)
+ tm.assert_almost_equal(result, expected)
+
+ def test_check_single_invert_op(self, lhs, engine, parser):
+ # simple
+ try:
+ elb = lhs.astype(bool)
+ except AttributeError:
+ elb = np.array([bool(lhs)])
+ expected = ~elb
+ result = pd.eval("~elb", engine=engine, parser=parser)
+ tm.assert_almost_equal(expected, result)
+
+ def test_frame_invert(self, engine, parser):
+ expr = "~lhs"
+
+ # ~ ##
+ # frame
+ # float always raises
+ lhs = DataFrame(np.random.default_rng(2).standard_normal((5, 2)))
+ if engine == "numexpr":
+ msg = "couldn't find matching opcode for 'invert_dd'"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(expr, engine=engine, parser=parser)
+ else:
+ msg = "ufunc 'invert' not supported for the input types"
+ with pytest.raises(TypeError, match=msg):
+ pd.eval(expr, engine=engine, parser=parser)
+
+ # int raises on numexpr
+ lhs = DataFrame(np.random.default_rng(2).integers(5, size=(5, 2)))
+ if engine == "numexpr" and Version(numexpr.__version__) < Version("2.13.0"):
+ msg = "couldn't find matching opcode for 'invert"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(expr, engine=engine, parser=parser)
+ else:
+ expect = ~lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_frame_equal(expect, result)
+
+ # bool always works
+ lhs = DataFrame(np.random.default_rng(2).standard_normal((5, 2)) > 0.5)
+ expect = ~lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_frame_equal(expect, result)
+
+ # object raises
+ lhs = DataFrame(
+ {"b": ["a", 1, 2.0], "c": np.random.default_rng(2).standard_normal(3) > 0.5}
+ )
+ if engine == "numexpr":
+ with pytest.raises(ValueError, match="unknown type object"):
+ pd.eval(expr, engine=engine, parser=parser)
+ else:
+ msg = "bad operand type for unary ~: 'str'"
+ with pytest.raises(TypeError, match=msg):
+ pd.eval(expr, engine=engine, parser=parser)
+
+ def test_series_invert(self, engine, parser):
+ # ~ ####
+ expr = "~lhs"
+
+ # series
+ # float raises
+ lhs = Series(np.random.default_rng(2).standard_normal(5))
+ if engine == "numexpr":
+ msg = "couldn't find matching opcode for 'invert_dd'"
+ with pytest.raises(NotImplementedError, match=msg):
+ result = pd.eval(expr, engine=engine, parser=parser)
+ else:
+ msg = "ufunc 'invert' not supported for the input types"
+ with pytest.raises(TypeError, match=msg):
+ pd.eval(expr, engine=engine, parser=parser)
+
+ # int raises on numexpr
+ lhs = Series(np.random.default_rng(2).integers(5, size=5))
+ if engine == "numexpr" and Version(numexpr.__version__) < Version("2.13.0"):
+ msg = "couldn't find matching opcode for 'invert"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(expr, engine=engine, parser=parser)
+ else:
+ expect = ~lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_series_equal(expect, result)
+
+ # bool
+ lhs = Series(np.random.default_rng(2).standard_normal(5) > 0.5)
+ expect = ~lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_series_equal(expect, result)
+
+ # float
+ # int
+ # bool
+
+ # object
+ lhs = Series(["a", 1, 2.0])
+ if engine == "numexpr":
+ with pytest.raises(ValueError, match="unknown type object"):
+ pd.eval(expr, engine=engine, parser=parser)
+ else:
+ msg = "bad operand type for unary ~: 'str'"
+ with pytest.raises(TypeError, match=msg):
+ pd.eval(expr, engine=engine, parser=parser)
+
+ def test_frame_negate(self, engine, parser):
+ expr = "-lhs"
+
+ # float
+ lhs = DataFrame(np.random.default_rng(2).standard_normal((5, 2)))
+ expect = -lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_frame_equal(expect, result)
+
+ # int
+ lhs = DataFrame(np.random.default_rng(2).integers(5, size=(5, 2)))
+ expect = -lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_frame_equal(expect, result)
+
+ # bool doesn't work with numexpr but works elsewhere
+ lhs = DataFrame(np.random.default_rng(2).standard_normal((5, 2)) > 0.5)
+ if engine == "numexpr":
+ msg = "couldn't find matching opcode for 'neg_bb'"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(expr, engine=engine, parser=parser)
+ else:
+ expect = -lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_frame_equal(expect, result)
+
+ def test_series_negate(self, engine, parser):
+ expr = "-lhs"
+
+ # float
+ lhs = Series(np.random.default_rng(2).standard_normal(5))
+ expect = -lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_series_equal(expect, result)
+
+ # int
+ lhs = Series(np.random.default_rng(2).integers(5, size=5))
+ expect = -lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_series_equal(expect, result)
+
+ # bool doesn't work with numexpr but works elsewhere
+ lhs = Series(np.random.default_rng(2).standard_normal(5) > 0.5)
+ if engine == "numexpr":
+ msg = "couldn't find matching opcode for 'neg_bb'"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(expr, engine=engine, parser=parser)
+ else:
+ expect = -lhs
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_series_equal(expect, result)
+
+ @pytest.mark.parametrize(
+ "lhs",
+ [
+ # Float
+ np.random.default_rng(2).standard_normal((5, 2)),
+ # Int
+ np.random.default_rng(2).integers(5, size=(5, 2)),
+ # bool doesn't work with numexpr but works elsewhere
+ np.array([True, False, True, False, True], dtype=np.bool_),
+ ],
+ )
+ def test_frame_pos(self, lhs, engine, parser):
+ lhs = DataFrame(lhs)
+ expr = "+lhs"
+ expect = lhs
+
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_frame_equal(expect, result)
+
+ @pytest.mark.parametrize(
+ "lhs",
+ [
+ # Float
+ np.random.default_rng(2).standard_normal(5),
+ # Int
+ np.random.default_rng(2).integers(5, size=5),
+ # bool doesn't work with numexpr but works elsewhere
+ np.array([True, False, True, False, True], dtype=np.bool_),
+ ],
+ )
+ def test_series_pos(self, lhs, engine, parser):
+ lhs = Series(lhs)
+ expr = "+lhs"
+ expect = lhs
+
+ result = pd.eval(expr, engine=engine, parser=parser)
+ tm.assert_series_equal(expect, result)
+
+ def test_scalar_unary(self, engine, parser):
+ msg = "bad operand type for unary ~: 'float'"
+ warn = None
+ if (PY314 and engine == "numexpr" and parser == "pandas") or (
+ PY312 and not (engine == "numexpr" and parser == "pandas")
+ ):
+ warn = DeprecationWarning
+ with pytest.raises(TypeError, match=msg):
+ pd.eval("~1.0", engine=engine, parser=parser)
+
+ assert pd.eval("-1.0", parser=parser, engine=engine) == -1.0
+ assert pd.eval("+1.0", parser=parser, engine=engine) == +1.0
+ assert pd.eval("~1", parser=parser, engine=engine) == ~1
+ assert pd.eval("-1", parser=parser, engine=engine) == -1
+ assert pd.eval("+1", parser=parser, engine=engine) == +1
+ with tm.assert_produces_warning(
+ warn, match="Bitwise inversion", check_stacklevel=False
+ ):
+ assert pd.eval("~True", parser=parser, engine=engine) == ~True
+ with tm.assert_produces_warning(
+ warn, match="Bitwise inversion", check_stacklevel=False
+ ):
+ assert pd.eval("~False", parser=parser, engine=engine) == ~False
+ assert pd.eval("-True", parser=parser, engine=engine) == -True
+ assert pd.eval("-False", parser=parser, engine=engine) == -False
+ assert pd.eval("+True", parser=parser, engine=engine) == +True
+ assert pd.eval("+False", parser=parser, engine=engine) == +False
+
+ def test_unary_in_array(self):
+ # GH 11235
+ # TODO: 2022-01-29: result return list with numexpr 2.7.3 in CI
+ # but cannot reproduce locally
+ result = np.array(
+ pd.eval("[-True, True, +True, -False, False, +False, -37, 37, ~37, +37]"),
+ dtype=np.object_,
+ )
+ expected = np.array(
+ [
+ -True,
+ True,
+ +True,
+ -False,
+ False,
+ +False,
+ -37,
+ 37,
+ ~37,
+ +37,
+ ],
+ dtype=np.object_,
+ )
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("expr", ["x < -0.1", "-5 > x"])
+ def test_float_comparison_bin_op(self, float_numpy_dtype, expr):
+ # GH 16363
+ df = DataFrame({"x": np.array([0], dtype=float_numpy_dtype)})
+ res = df.eval(expr)
+ assert res.values == np.array([False])
+
+ def test_unary_in_function(self):
+ # GH 46471
+ df = DataFrame({"x": [0, 1, np.nan]})
+
+ result = df.eval("x.fillna(-1)")
+ expected = df.x.fillna(-1)
+ # column name becomes None if using numexpr
+ # only check names when the engine is not numexpr
+ tm.assert_series_equal(result, expected, check_names=not USE_NUMEXPR)
+
+ result = df.eval("x.shift(1, fill_value=-1)")
+ expected = df.x.shift(1, fill_value=-1)
+ tm.assert_series_equal(result, expected, check_names=not USE_NUMEXPR)
+
+ @pytest.mark.parametrize(
+ "ex",
+ (
+ "1 or 2",
+ "1 and 2",
+ "a and b",
+ "a or b",
+ "1 or 2 and (3 + 2) > 3",
+ "2 * x > 2 or 1 and 2",
+ "2 * df > 3 and 1 or a",
+ ),
+ )
+ def test_disallow_scalar_bool_ops(self, ex, engine, parser):
+ x, a, b = np.random.default_rng(2).standard_normal(3), 1, 2 # noqa: F841
+ df = DataFrame(np.random.default_rng(2).standard_normal((3, 2))) # noqa: F841
+
+ msg = "cannot evaluate scalar only bool ops|'BoolOp' nodes are not"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(ex, engine=engine, parser=parser)
+
+ def test_identical(self, engine, parser):
+ # see gh-10546
+ x = 1
+ result = pd.eval("x", engine=engine, parser=parser)
+ assert result == 1
+ assert is_scalar(result)
+
+ x = 1.5
+ result = pd.eval("x", engine=engine, parser=parser)
+ assert result == 1.5
+ assert is_scalar(result)
+
+ x = False
+ result = pd.eval("x", engine=engine, parser=parser)
+ assert not result
+ assert is_bool(result)
+ assert is_scalar(result)
+
+ x = np.array([1])
+ result = pd.eval("x", engine=engine, parser=parser)
+ tm.assert_numpy_array_equal(result, np.array([1]))
+ assert result.shape == (1,)
+
+ x = np.array([1.5])
+ result = pd.eval("x", engine=engine, parser=parser)
+ tm.assert_numpy_array_equal(result, np.array([1.5]))
+ assert result.shape == (1,)
+
+ x = np.array([False]) # noqa: F841
+ result = pd.eval("x", engine=engine, parser=parser)
+ tm.assert_numpy_array_equal(result, np.array([False]))
+ assert result.shape == (1,)
+
+ def test_line_continuation(self, engine, parser):
+ # GH 11149
+ exp = """1 + 2 * \
+ 5 - 1 + 2 """
+ result = pd.eval(exp, engine=engine, parser=parser)
+ assert result == 12
+
+ def test_float_truncation(self, engine, parser):
+ # GH 14241
+ exp = "1000000000.006"
+ result = pd.eval(exp, engine=engine, parser=parser)
+ expected = np.float64(exp)
+ assert result == expected
+
+ df = DataFrame({"A": [1000000000.0009, 1000000000.0011, 1000000000.0015]})
+ cutoff = 1000000000.0006
+ result = df.query(f"A < {cutoff:.4f}")
+ assert result.empty
+
+ cutoff = 1000000000.0010
+ result = df.query(f"A > {cutoff:.4f}")
+ expected = df.loc[[1, 2], :]
+ tm.assert_frame_equal(expected, result)
+
+ exact = 1000000000.0011
+ result = df.query(f"A == {exact:.4f}")
+ expected = df.loc[[1], :]
+ tm.assert_frame_equal(expected, result)
+
+ def test_disallow_python_keywords(self):
+ # GH 18221
+ df = DataFrame([[0, 0, 0]], columns=["foo", "bar", "class"])
+ msg = "Python keyword not valid identifier in numexpr query"
+ with pytest.raises(SyntaxError, match=msg):
+ df.query("class == 0")
+
+ df = DataFrame()
+ df.index.name = "lambda"
+ with pytest.raises(SyntaxError, match=msg):
+ df.query("lambda == 0")
+
+ def test_true_false_logic(self):
+ # GH 25823
+ # This behavior is deprecated in Python 3.12
+ with tm.maybe_produces_warning(
+ DeprecationWarning, PY312, check_stacklevel=False
+ ):
+ assert pd.eval("not True") == -2
+ assert pd.eval("not False") == -1
+ assert pd.eval("True and not True") == 0
+
+ def test_and_logic_string_match(self):
+ # GH 25823
+ event = Series({"a": "hello"})
+ assert pd.eval(f"{event.str.match('hello').a}")
+ assert pd.eval(f"{event.str.match('hello').a and event.str.match('hello').a}")
+
+ def test_eval_keep_name(self, engine, parser):
+ df = Series([2, 15, 28], name="a").to_frame()
+ res = df.eval("a + a", engine=engine, parser=parser)
+ expected = Series([4, 30, 56], name="a")
+ tm.assert_series_equal(expected, res)
+
+ def test_eval_unmatching_names(self, engine, parser):
+ variable_name = Series([42], name="series_name")
+ res = pd.eval("variable_name + 0", engine=engine, parser=parser)
+ tm.assert_series_equal(variable_name, res)
+
+
+# -------------------------------------
+# gh-12388: Typecasting rules consistency with python
+
+
+class TestTypeCasting:
+ @pytest.mark.parametrize("op", ["+", "-", "*", "**", "/"])
+ # maybe someday... numexpr has too many upcasting rules now
+ # chain(*(np.core.sctypes[x] for x in ['uint', 'int', 'float']))
+ @pytest.mark.parametrize("left_right", [("df", "3"), ("3", "df")])
+ def test_binop_typecasting(
+ self, engine, parser, op, complex_or_float_dtype, left_right, request
+ ):
+ # GH#21374
+ dtype = complex_or_float_dtype
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)), dtype=dtype)
+ left, right = left_right
+ s = f"{left} {op} {right}"
+ res = pd.eval(s, engine=engine, parser=parser)
+ if dtype == "complex64" and engine == "numexpr":
+ mark = pytest.mark.xfail(
+ reason="numexpr issue with complex that are upcast "
+ "to complex 128 "
+ "https://github.com/pydata/numexpr/issues/492"
+ )
+ request.applymarker(mark)
+ assert df.values.dtype == dtype
+ assert res.values.dtype == dtype
+ tm.assert_frame_equal(res, eval(s), check_exact=False)
+
+
+# -------------------------------------
+# Basic and complex alignment
+
+
+def should_warn(*args):
+ not_mono = not any(map(operator.attrgetter("is_monotonic_increasing"), args))
+ only_one_dt = reduce(
+ operator.xor, (issubclass(x.dtype.type, np.datetime64) for x in args)
+ )
+ return not_mono and only_one_dt
+
+
+class TestAlignment:
+ index_types = ["i", "s", "dt"]
+ lhs_index_types = [*index_types, "s"] # 'p'
+
+ def test_align_nested_unary_op(self, engine, parser):
+ s = "df * ~2"
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
+ res = pd.eval(s, engine=engine, parser=parser)
+ tm.assert_frame_equal(res, df * ~2)
+
+ @pytest.mark.filterwarnings("always::RuntimeWarning")
+ @pytest.mark.parametrize("lr_idx_type", lhs_index_types)
+ @pytest.mark.parametrize("rr_idx_type", index_types)
+ @pytest.mark.parametrize("c_idx_type", index_types)
+ def test_basic_frame_alignment(
+ self, engine, parser, lr_idx_type, rr_idx_type, c_idx_type, idx_func_dict
+ ):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 10)),
+ index=idx_func_dict[lr_idx_type](10),
+ columns=idx_func_dict[c_idx_type](10),
+ )
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((20, 10)),
+ index=idx_func_dict[rr_idx_type](20),
+ columns=idx_func_dict[c_idx_type](10),
+ )
+ # only warns if not monotonic and not sortable
+ if should_warn(df.index, df2.index):
+ with tm.assert_produces_warning(RuntimeWarning):
+ res = pd.eval("df + df2", engine=engine, parser=parser)
+ else:
+ res = pd.eval("df + df2", engine=engine, parser=parser)
+ tm.assert_frame_equal(res, df + df2)
+
+ @pytest.mark.parametrize("r_idx_type", lhs_index_types)
+ @pytest.mark.parametrize("c_idx_type", lhs_index_types)
+ def test_frame_comparison(
+ self, engine, parser, r_idx_type, c_idx_type, idx_func_dict
+ ):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 10)),
+ index=idx_func_dict[r_idx_type](10),
+ columns=idx_func_dict[c_idx_type](10),
+ )
+ res = pd.eval("df < 2", engine=engine, parser=parser)
+ tm.assert_frame_equal(res, df < 2)
+
+ df3 = DataFrame(
+ np.random.default_rng(2).standard_normal(df.shape),
+ index=df.index,
+ columns=df.columns,
+ )
+ res = pd.eval("df < df3", engine=engine, parser=parser)
+ tm.assert_frame_equal(res, df < df3)
+
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ @pytest.mark.parametrize("r1", lhs_index_types)
+ @pytest.mark.parametrize("c1", index_types)
+ @pytest.mark.parametrize("r2", index_types)
+ @pytest.mark.parametrize("c2", index_types)
+ def test_medium_complex_frame_alignment(
+ self, engine, parser, r1, c1, r2, c2, idx_func_dict
+ ):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 2)),
+ index=idx_func_dict[r1](3),
+ columns=idx_func_dict[c1](2),
+ )
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 2)),
+ index=idx_func_dict[r2](4),
+ columns=idx_func_dict[c2](2),
+ )
+ df3 = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)),
+ index=idx_func_dict[r2](5),
+ columns=idx_func_dict[c2](2),
+ )
+ if should_warn(df.index, df2.index, df3.index):
+ with tm.assert_produces_warning(RuntimeWarning):
+ res = pd.eval("df + df2 + df3", engine=engine, parser=parser)
+ else:
+ res = pd.eval("df + df2 + df3", engine=engine, parser=parser)
+ tm.assert_frame_equal(res, df + df2 + df3)
+
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ @pytest.mark.parametrize("index_name", ["index", "columns"])
+ @pytest.mark.parametrize("c_idx_type", index_types)
+ @pytest.mark.parametrize("r_idx_type", lhs_index_types)
+ def test_basic_frame_series_alignment(
+ self, engine, parser, index_name, r_idx_type, c_idx_type, idx_func_dict
+ ):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 10)),
+ index=idx_func_dict[r_idx_type](10),
+ columns=idx_func_dict[c_idx_type](10),
+ )
+ index = getattr(df, index_name)
+ s = Series(np.random.default_rng(2).standard_normal(5), index[:5])
+
+ if should_warn(df.index, s.index):
+ with tm.assert_produces_warning(RuntimeWarning):
+ res = pd.eval("df + s", engine=engine, parser=parser)
+ else:
+ res = pd.eval("df + s", engine=engine, parser=parser)
+
+ if r_idx_type == "dt" or c_idx_type == "dt":
+ expected = df.add(s) if engine == "numexpr" else df + s
+ else:
+ expected = df + s
+ tm.assert_frame_equal(res, expected)
+
+ @pytest.mark.parametrize("index_name", ["index", "columns"])
+ @pytest.mark.parametrize(
+ "r_idx_type, c_idx_type",
+ [*list(product(["i", "s"], ["i", "s"])), ("dt", "dt")],
+ )
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ def test_basic_series_frame_alignment(
+ self, request, engine, parser, index_name, r_idx_type, c_idx_type, idx_func_dict
+ ):
+ if (
+ engine == "numexpr"
+ and parser in ("pandas", "python")
+ and index_name == "index"
+ and r_idx_type == "i"
+ and c_idx_type == "s"
+ ):
+ reason = (
+ f"Flaky column ordering when engine={engine}, "
+ f"parser={parser}, index_name={index_name}, "
+ f"r_idx_type={r_idx_type}, c_idx_type={c_idx_type}"
+ )
+ request.applymarker(pytest.mark.xfail(reason=reason, strict=False))
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 7)),
+ index=idx_func_dict[r_idx_type](10),
+ columns=idx_func_dict[c_idx_type](7),
+ )
+ index = getattr(df, index_name)
+ s = Series(np.random.default_rng(2).standard_normal(5), index[:5])
+ if should_warn(s.index, df.index):
+ with tm.assert_produces_warning(RuntimeWarning):
+ res = pd.eval("s + df", engine=engine, parser=parser)
+ else:
+ res = pd.eval("s + df", engine=engine, parser=parser)
+
+ if r_idx_type == "dt" or c_idx_type == "dt":
+ expected = df.add(s) if engine == "numexpr" else s + df
+ else:
+ expected = s + df
+ tm.assert_frame_equal(res, expected)
+
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ @pytest.mark.parametrize("c_idx_type", index_types)
+ @pytest.mark.parametrize("r_idx_type", lhs_index_types)
+ @pytest.mark.parametrize("index_name", ["index", "columns"])
+ @pytest.mark.parametrize("op", ["+", "*"])
+ def test_series_frame_commutativity(
+ self, engine, parser, index_name, op, r_idx_type, c_idx_type, idx_func_dict
+ ):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 10)),
+ index=idx_func_dict[r_idx_type](10),
+ columns=idx_func_dict[c_idx_type](10),
+ )
+ index = getattr(df, index_name)
+ s = Series(np.random.default_rng(2).standard_normal(5), index[:5])
+
+ lhs = f"s {op} df"
+ rhs = f"df {op} s"
+ if should_warn(df.index, s.index):
+ with tm.assert_produces_warning(RuntimeWarning):
+ a = pd.eval(lhs, engine=engine, parser=parser)
+ with tm.assert_produces_warning(RuntimeWarning):
+ b = pd.eval(rhs, engine=engine, parser=parser)
+ else:
+ a = pd.eval(lhs, engine=engine, parser=parser)
+ b = pd.eval(rhs, engine=engine, parser=parser)
+
+ if r_idx_type != "dt" and c_idx_type != "dt":
+ if engine == "numexpr":
+ tm.assert_frame_equal(a, b)
+
+ @pytest.mark.filterwarnings("always::RuntimeWarning")
+ @pytest.mark.parametrize("r1", lhs_index_types)
+ @pytest.mark.parametrize("c1", index_types)
+ @pytest.mark.parametrize("r2", index_types)
+ @pytest.mark.parametrize("c2", index_types)
+ def test_complex_series_frame_alignment(
+ self, engine, parser, r1, c1, r2, c2, idx_func_dict
+ ):
+ n = 3
+ m1 = 5
+ m2 = 2 * m1
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((m1, n)),
+ index=idx_func_dict[r1](m1),
+ columns=idx_func_dict[c1](n),
+ )
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((m2, n)),
+ index=idx_func_dict[r2](m2),
+ columns=idx_func_dict[c2](n),
+ )
+ index = df2.columns
+ ser = Series(np.random.default_rng(2).standard_normal(n), index[:n])
+
+ if r2 == "dt" or c2 == "dt":
+ if engine == "numexpr":
+ expected2 = df2.add(ser)
+ else:
+ expected2 = df2 + ser
+ else:
+ expected2 = df2 + ser
+
+ if r1 == "dt" or c1 == "dt":
+ if engine == "numexpr":
+ expected = expected2.add(df)
+ else:
+ expected = expected2 + df
+ else:
+ expected = expected2 + df
+
+ if should_warn(df2.index, ser.index, df.index):
+ with tm.assert_produces_warning(RuntimeWarning):
+ res = pd.eval("df2 + ser + df", engine=engine, parser=parser)
+ else:
+ res = pd.eval("df2 + ser + df", engine=engine, parser=parser)
+ assert res.shape == expected.shape
+ tm.assert_frame_equal(res, expected)
+
+ def test_performance_warning_for_poor_alignment(
+ self, performance_warning, engine, parser
+ ):
+ df = DataFrame(np.random.default_rng(2).standard_normal((1000, 10)))
+ s = Series(np.random.default_rng(2).standard_normal(10000))
+ if engine == "numexpr" and performance_warning:
+ seen = PerformanceWarning
+ else:
+ seen = False
+
+ msg = "Alignment difference on axis 1 is larger than an order of magnitude"
+ with tm.assert_produces_warning(seen, match=msg):
+ pd.eval("df + s", engine=engine, parser=parser)
+
+ s = Series(np.random.default_rng(2).standard_normal(1000))
+ with tm.assert_produces_warning(False):
+ pd.eval("df + s", engine=engine, parser=parser)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 10000)))
+ s = Series(np.random.default_rng(2).standard_normal(10000))
+ with tm.assert_produces_warning(False):
+ pd.eval("df + s", engine=engine, parser=parser)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 10)))
+ s = Series(np.random.default_rng(2).standard_normal(10000))
+
+ is_python_engine = engine == "python"
+
+ if not is_python_engine and performance_warning:
+ wrn = PerformanceWarning
+ else:
+ wrn = False
+
+ with tm.assert_produces_warning(wrn, match=msg) as w:
+ pd.eval("df + s", engine=engine, parser=parser)
+
+ if not is_python_engine and performance_warning:
+ assert len(w) == 1
+ msg = str(w[0].message)
+ logged = np.log10(s.size - df.shape[1])
+ expected = (
+ f"Alignment difference on axis 1 is larger "
+ f"than an order of magnitude on term 'df', "
+ f"by more than {logged:.4g}; performance may suffer."
+ )
+ assert msg == expected
+
+
+# ------------------------------------
+# Slightly more complex ops
+
+
+class TestOperations:
+ def eval(self, *args, **kwargs):
+ kwargs["level"] = kwargs.pop("level", 0) + 1
+ return pd.eval(*args, **kwargs)
+
+ def test_simple_arith_ops(self, engine, parser):
+ exclude_arith = []
+ if parser == "python":
+ exclude_arith = ["in", "not in"]
+
+ arith_ops = [
+ op
+ for op in expr.ARITH_OPS_SYMS + expr.CMP_OPS_SYMS
+ if op not in exclude_arith
+ ]
+
+ ops = (op for op in arith_ops if op != "//")
+
+ for op in ops:
+ ex = f"1 {op} 1"
+ ex2 = f"x {op} 1"
+ ex3 = f"1 {op} (x + 1)"
+
+ if op in ("in", "not in"):
+ msg = "argument of type 'int' is not .*"
+ with pytest.raises(TypeError, match=msg):
+ pd.eval(ex, engine=engine, parser=parser)
+ else:
+ expec = _eval_single_bin(1, op, 1, engine)
+ x = self.eval(ex, engine=engine, parser=parser)
+ assert x == expec
+
+ expec = _eval_single_bin(x, op, 1, engine)
+ y = self.eval(ex2, local_dict={"x": x}, engine=engine, parser=parser)
+ assert y == expec
+
+ expec = _eval_single_bin(1, op, x + 1, engine)
+ y = self.eval(ex3, local_dict={"x": x}, engine=engine, parser=parser)
+ assert y == expec
+
+ @pytest.mark.parametrize("rhs", [True, False])
+ @pytest.mark.parametrize("lhs", [True, False])
+ @pytest.mark.parametrize("op", expr.BOOL_OPS_SYMS)
+ def test_simple_bool_ops(self, rhs, lhs, op):
+ ex = f"{lhs} {op} {rhs}"
+
+ if parser == "python" and op in ["and", "or"]:
+ msg = "'BoolOp' nodes are not implemented"
+ with pytest.raises(NotImplementedError, match=msg):
+ self.eval(ex)
+ return
+
+ res = self.eval(ex)
+ exp = eval(ex)
+ assert res == exp
+
+ @pytest.mark.parametrize("rhs", [True, False])
+ @pytest.mark.parametrize("lhs", [True, False])
+ @pytest.mark.parametrize("op", expr.BOOL_OPS_SYMS)
+ def test_bool_ops_with_constants(self, rhs, lhs, op):
+ ex = f"{lhs} {op} {rhs}"
+
+ if parser == "python" and op in ["and", "or"]:
+ msg = "'BoolOp' nodes are not implemented"
+ with pytest.raises(NotImplementedError, match=msg):
+ self.eval(ex)
+ return
+
+ res = self.eval(ex)
+ exp = eval(ex)
+ assert res == exp
+
+ def test_4d_ndarray_fails(self):
+ x = np.random.default_rng(2).standard_normal((3, 4, 5, 6))
+ y = Series(np.random.default_rng(2).standard_normal(10))
+ msg = "N-dimensional objects, where N > 2, are not supported with eval"
+ with pytest.raises(NotImplementedError, match=msg):
+ self.eval("x + y", local_dict={"x": x, "y": y})
+
+ def test_constant(self):
+ x = self.eval("1")
+ assert x == 1
+
+ def test_single_variable(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ df2 = self.eval("df", local_dict={"df": df})
+ tm.assert_frame_equal(df, df2)
+
+ def test_failing_subscript_with_name_error(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 3))) # noqa: F841
+ with pytest.raises(NameError, match="name 'x' is not defined"):
+ self.eval("df[x > 2] > 2")
+
+ def test_lhs_expression_subscript(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
+ result = self.eval("(df + 1)[df > 2]", local_dict={"df": df})
+ expected = (df + 1)[df > 2]
+ tm.assert_frame_equal(result, expected)
+
+ def test_attr_expression(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 3)), columns=list("abc")
+ )
+ expr1 = "df.a < df.b"
+ expec1 = df.a < df.b
+ expr2 = "df.a + df.b + df.c"
+ expec2 = df.a + df.b + df.c
+ expr3 = "df.a + df.b + df.c[df.b < 0]"
+ expec3 = df.a + df.b + df.c[df.b < 0]
+ exprs = expr1, expr2, expr3
+ expecs = expec1, expec2, expec3
+ for e, expec in zip(exprs, expecs, strict=True):
+ tm.assert_series_equal(expec, self.eval(e, local_dict={"df": df}))
+
+ def test_assignment_fails(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 3)), columns=list("abc")
+ )
+ df2 = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
+ expr1 = "df = df2"
+ msg = "cannot assign without a target object"
+ with pytest.raises(ValueError, match=msg):
+ self.eval(expr1, local_dict={"df": df, "df2": df2})
+
+ def test_assignment_column_multiple_raise(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+ # multiple assignees
+ with pytest.raises(SyntaxError, match="invalid syntax"):
+ df.eval("d c = a + b")
+
+ def test_assignment_column_invalid_assign(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+ # invalid assignees
+ msg = "left hand side of an assignment must be a single name"
+ with pytest.raises(SyntaxError, match=msg):
+ df.eval("d,c = a + b")
+
+ def test_assignment_column_invalid_assign_function_call(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+ msg = "cannot assign to function call"
+ with pytest.raises(SyntaxError, match=msg):
+ df.eval('Timestamp("20131001") = a + b')
+
+ def test_assignment_single_assign_existing(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+ # single assignment - existing variable
+ expected = df.copy()
+ expected["a"] = expected["a"] + expected["b"]
+ df.eval("a = a + b", inplace=True)
+ tm.assert_frame_equal(df, expected)
+
+ def test_assignment_single_assign_new(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+ # single assignment - new variable
+ expected = df.copy()
+ expected["c"] = expected["a"] + expected["b"]
+ df.eval("c = a + b", inplace=True)
+ tm.assert_frame_equal(df, expected)
+
+ def test_assignment_single_assign_local_overlap(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+ df = df.copy()
+ a = 1 # noqa: F841
+ df.eval("a = 1 + b", inplace=True)
+
+ expected = df.copy()
+ expected["a"] = 1 + expected["b"]
+ tm.assert_frame_equal(df, expected)
+
+ def test_assignment_single_assign_name(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+
+ a = 1 # noqa: F841
+ old_a = df.a.copy()
+ df.eval("a = a + b", inplace=True)
+ result = old_a + df.b
+ tm.assert_series_equal(result, df.a, check_names=False)
+ assert result.name is None
+
+ def test_assignment_multiple_raises(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+ # multiple assignment
+ df.eval("c = a + b", inplace=True)
+ msg = "can only assign a single expression"
+ with pytest.raises(SyntaxError, match=msg):
+ df.eval("c = a = b")
+
+ def test_assignment_explicit(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+ # explicit targets
+ self.eval("c = df.a + df.b", local_dict={"df": df}, target=df, inplace=True)
+ expected = df.copy()
+ expected["c"] = expected["a"] + expected["b"]
+ tm.assert_frame_equal(df, expected)
+
+ def test_column_in(self, engine):
+ # GH 11235
+ df = DataFrame({"a": [11], "b": [-32]})
+ result = df.eval("a in [11, -32]", engine=engine)
+ expected = Series([True], name="a")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.xfail(reason="Unknown: Omitted test_ in name prior.")
+ def test_assignment_not_inplace(self):
+ # see gh-9297
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("ab")
+ )
+
+ actual = df.eval("c = a + b", inplace=False)
+ assert actual is not None
+
+ expected = df.copy()
+ expected["c"] = expected["a"] + expected["b"]
+ tm.assert_frame_equal(df, expected)
+
+ def test_multi_line_expression(self):
+ # GH 11149
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ expected = df.copy()
+
+ expected["c"] = expected["a"] + expected["b"]
+ expected["d"] = expected["c"] + expected["b"]
+ answer = df.eval(
+ """
+ c = a + b
+ d = c + b""",
+ inplace=True,
+ )
+ tm.assert_frame_equal(expected, df)
+ assert answer is None
+
+ expected["a"] = expected["a"] - 1
+ expected["e"] = expected["a"] + 2
+ answer = df.eval(
+ """
+ a = a - 1
+ e = a + 2""",
+ inplace=True,
+ )
+ tm.assert_frame_equal(expected, df)
+ assert answer is None
+
+ # multi-line not valid if not all assignments
+ msg = "Multi-line expressions are only valid if all expressions contain"
+ with pytest.raises(ValueError, match=msg):
+ df.eval(
+ """
+ a = b + 2
+ b - 2""",
+ inplace=False,
+ )
+
+ def test_multi_line_expression_not_inplace(self):
+ # GH 11149
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ expected = df.copy()
+
+ expected["c"] = expected["a"] + expected["b"]
+ expected["d"] = expected["c"] + expected["b"]
+ df = df.eval(
+ """
+ c = a + b
+ d = c + b""",
+ inplace=False,
+ )
+ tm.assert_frame_equal(expected, df)
+
+ expected["a"] = expected["a"] - 1
+ expected["e"] = expected["a"] + 2
+ df = df.eval(
+ """
+ a = a - 1
+ e = a + 2""",
+ inplace=False,
+ )
+ tm.assert_frame_equal(expected, df)
+
+ def test_multi_line_expression_local_variable(self):
+ # GH 15342
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ expected = df.copy()
+
+ local_var = 7
+ expected["c"] = expected["a"] * local_var
+ expected["d"] = expected["c"] + local_var
+ answer = df.eval(
+ """
+ c = a * @local_var
+ d = c + @local_var
+ """,
+ inplace=True,
+ )
+ tm.assert_frame_equal(expected, df)
+ assert answer is None
+
+ def test_multi_line_expression_callable_local_variable(self):
+ # 26426
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+
+ def local_func(a, b):
+ return b
+
+ expected = df.copy()
+ expected["c"] = expected["a"] * local_func(1, 7)
+ expected["d"] = expected["c"] + local_func(1, 7)
+ answer = df.eval(
+ """
+ c = a * @local_func(1, 7)
+ d = c + @local_func(1, 7)
+ """,
+ inplace=True,
+ )
+ tm.assert_frame_equal(expected, df)
+ assert answer is None
+
+ def test_multi_line_expression_callable_local_variable_with_kwargs(self):
+ # 26426
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+
+ def local_func(a, b):
+ return b
+
+ expected = df.copy()
+ expected["c"] = expected["a"] * local_func(b=7, a=1)
+ expected["d"] = expected["c"] + local_func(b=7, a=1)
+ answer = df.eval(
+ """
+ c = a * @local_func(b=7, a=1)
+ d = c + @local_func(b=7, a=1)
+ """,
+ inplace=True,
+ )
+ tm.assert_frame_equal(expected, df)
+ assert answer is None
+
+ def test_assignment_in_query(self):
+ # GH 8664
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ df_orig = df.copy()
+ msg = "cannot assign without a target object"
+ with pytest.raises(ValueError, match=msg):
+ df.query("a = 1")
+ tm.assert_frame_equal(df, df_orig)
+
+ def test_query_inplace(self):
+ # see gh-11149
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ expected = df.copy()
+ expected = expected[expected["a"] == 2]
+ df.query("a == 2", inplace=True)
+ tm.assert_frame_equal(expected, df)
+
+ df = {}
+ expected = {"a": 3}
+
+ self.eval("a = 1 + 2", target=df, inplace=True)
+ tm.assert_dict_equal(df, expected)
+
+ @pytest.mark.parametrize("invalid_target", [1, "cat", [1, 2], np.array([]), (1, 3)])
+ def test_cannot_item_assign(self, invalid_target):
+ msg = "Cannot assign expression output to target"
+ expression = "a = 1 + 2"
+
+ with pytest.raises(ValueError, match=msg):
+ self.eval(expression, target=invalid_target, inplace=True)
+
+ if hasattr(invalid_target, "copy"):
+ with pytest.raises(ValueError, match=msg):
+ self.eval(expression, target=invalid_target, inplace=False)
+
+ @pytest.mark.parametrize("invalid_target", [1, "cat", (1, 3)])
+ def test_cannot_copy_item(self, invalid_target):
+ msg = "Cannot return a copy of the target"
+ expression = "a = 1 + 2"
+
+ with pytest.raises(ValueError, match=msg):
+ self.eval(expression, target=invalid_target, inplace=False)
+
+ @pytest.mark.parametrize("target", [1, "cat", [1, 2], np.array([]), (1, 3), {1: 2}])
+ def test_inplace_no_assignment(self, target):
+ expression = "1 + 2"
+
+ assert self.eval(expression, target=target, inplace=False) == 3
+
+ msg = "Cannot operate inplace if there is no assignment"
+ with pytest.raises(ValueError, match=msg):
+ self.eval(expression, target=target, inplace=True)
+
+ def test_basic_period_index_boolean_expression(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((2, 2)),
+ columns=period_range("2020-01-01", freq="D", periods=2),
+ )
+ e = df < 2
+ r = self.eval("df < 2", local_dict={"df": df})
+ x = df < 2
+
+ tm.assert_frame_equal(r, e)
+ tm.assert_frame_equal(x, e)
+
+ def test_basic_period_index_subscript_expression(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((2, 2)),
+ columns=period_range("2020-01-01", freq="D", periods=2),
+ )
+ r = self.eval("df[df < 2 + 3]", local_dict={"df": df})
+ e = df[df < 2 + 3]
+ tm.assert_frame_equal(r, e)
+
+ def test_nested_period_index_subscript_expression(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((2, 2)),
+ columns=period_range("2020-01-01", freq="D", periods=2),
+ )
+ r = self.eval("df[df[df < 2] < 2] + df * 2", local_dict={"df": df})
+ e = df[df[df < 2] < 2] + df * 2
+ tm.assert_frame_equal(r, e)
+
+ def test_date_boolean(self, engine, parser):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
+ df["dates1"] = date_range("1/1/2012", periods=5)
+ res = self.eval(
+ "df.dates1 < 20130101",
+ local_dict={"df": df},
+ engine=engine,
+ parser=parser,
+ )
+ expec = df.dates1 < "20130101"
+ tm.assert_series_equal(res, expec)
+
+ def test_simple_in_ops(self, engine, parser):
+ if parser != "python":
+ res = pd.eval("1 in [1, 2]", engine=engine, parser=parser)
+ assert res
+
+ res = pd.eval("2 in (1, 2)", engine=engine, parser=parser)
+ assert res
+
+ res = pd.eval("3 in (1, 2)", engine=engine, parser=parser)
+ assert not res
+
+ res = pd.eval("3 not in (1, 2)", engine=engine, parser=parser)
+ assert res
+
+ res = pd.eval("[3] not in (1, 2)", engine=engine, parser=parser)
+ assert res
+
+ res = pd.eval("[3] in ([3], 2)", engine=engine, parser=parser)
+ assert res
+
+ res = pd.eval("[[3]] in [[[3]], 2]", engine=engine, parser=parser)
+ assert res
+
+ res = pd.eval("(3,) in [(3,), 2]", engine=engine, parser=parser)
+ assert res
+
+ res = pd.eval("(3,) not in [(3,), 2]", engine=engine, parser=parser)
+ assert not res
+
+ res = pd.eval("[(3,)] in [[(3,)], 2]", engine=engine, parser=parser)
+ assert res
+ else:
+ msg = "'In' nodes are not implemented"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval("1 in [1, 2]", engine=engine, parser=parser)
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval("2 in (1, 2)", engine=engine, parser=parser)
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval("3 in (1, 2)", engine=engine, parser=parser)
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval("[(3,)] in (1, 2, [(3,)])", engine=engine, parser=parser)
+ msg = "'NotIn' nodes are not implemented"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval("3 not in (1, 2)", engine=engine, parser=parser)
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval("[3] not in (1, 2, [[3]])", engine=engine, parser=parser)
+
+ def test_check_many_exprs(self, engine, parser):
+ a = 1 # noqa: F841
+ expr = " * ".join("a" * 33)
+ expected = 1
+ res = pd.eval(expr, engine=engine, parser=parser)
+ assert res == expected
+
+ @pytest.mark.parametrize(
+ "expr",
+ [
+ "df > 2 and df > 3",
+ "df > 2 or df > 3",
+ "not df > 2",
+ ],
+ )
+ def test_fails_and_or_not(self, expr, engine, parser):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
+ if parser == "python":
+ msg = "'BoolOp' nodes are not implemented"
+ if "not" in expr:
+ msg = "'Not' nodes are not implemented"
+
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(
+ expr,
+ local_dict={"df": df},
+ parser=parser,
+ engine=engine,
+ )
+ else:
+ # smoke-test, should not raise
+ pd.eval(
+ expr,
+ local_dict={"df": df},
+ parser=parser,
+ engine=engine,
+ )
+
+ @pytest.mark.parametrize("char", ["|", "&"])
+ def test_fails_ampersand_pipe(self, char, engine, parser):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 3))) # noqa: F841
+ ex = f"(df + 2)[df > 1] > 0 {char} (df > 0)"
+ if parser == "python":
+ msg = "cannot evaluate scalar only bool ops"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(ex, parser=parser, engine=engine)
+ else:
+ # smoke-test, should not raise
+ pd.eval(ex, parser=parser, engine=engine)
+
+
+class TestMath:
+ def eval(self, *args, **kwargs):
+ kwargs["level"] = kwargs.pop("level", 0) + 1
+ return pd.eval(*args, **kwargs)
+
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ @pytest.mark.parametrize("fn", _unary_math_ops)
+ def test_unary_functions(self, fn, engine, parser):
+ df = DataFrame({"a": np.random.default_rng(2).standard_normal(10)})
+ a = df.a
+
+ expr = f"{fn}(a)"
+ got = self.eval(expr, engine=engine, parser=parser)
+ with np.errstate(all="ignore"):
+ expect = getattr(np, fn)(a)
+ tm.assert_series_equal(got, expect)
+
+ @pytest.mark.parametrize("fn", _binary_math_ops)
+ def test_binary_functions(self, fn, engine, parser):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ }
+ )
+ a = df.a
+ b = df.b
+
+ expr = f"{fn}(a, b)"
+ got = self.eval(expr, engine=engine, parser=parser)
+ with np.errstate(all="ignore"):
+ expect = getattr(np, fn)(a, b)
+ tm.assert_almost_equal(got, expect)
+
+ def test_df_use_case(self, engine, parser):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ }
+ )
+ df.eval(
+ "e = arctan2(sin(a), b)",
+ engine=engine,
+ parser=parser,
+ inplace=True,
+ )
+ got = df.e
+ expect = np.arctan2(np.sin(df.a), df.b).rename("e")
+ tm.assert_series_equal(got, expect)
+
+ def test_df_arithmetic_subexpression(self, engine, parser):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ }
+ )
+ df.eval("e = sin(a + b)", engine=engine, parser=parser, inplace=True)
+ got = df.e
+ expect = np.sin(df.a + df.b).rename("e")
+ tm.assert_series_equal(got, expect)
+
+ @pytest.mark.parametrize(
+ "dtype, expect_dtype",
+ [
+ (np.int32, np.float64),
+ (np.int64, np.float64),
+ (np.float32, np.float32),
+ (np.float64, np.float64),
+ pytest.param(np.complex128, np.complex128, marks=td.skip_if_windows),
+ ],
+ )
+ def test_result_types(self, dtype, expect_dtype, engine, parser):
+ # xref https://github.com/pandas-dev/pandas/issues/12293
+ # this fails on Windows, apparently a floating point precision issue
+
+ # Did not test complex64 because DataFrame is converting it to
+ # complex128. Due to https://github.com/pandas-dev/pandas/issues/10952
+ df = DataFrame(
+ {"a": np.random.default_rng(2).standard_normal(10).astype(dtype)}
+ )
+ assert df.a.dtype == dtype
+ df.eval("b = sin(a)", engine=engine, parser=parser, inplace=True)
+ got = df.b
+ expect = np.sin(df.a).rename("b")
+ assert expect.dtype == got.dtype
+ assert expect_dtype == got.dtype
+ tm.assert_series_equal(got, expect)
+
+ def test_undefined_func(self, engine, parser):
+ df = DataFrame({"a": np.random.default_rng(2).standard_normal(10)})
+ msg = '"mysin" is not a supported function'
+
+ with pytest.raises(ValueError, match=msg):
+ df.eval("mysin(a)", engine=engine, parser=parser)
+
+ def test_keyword_arg(self, engine, parser):
+ df = DataFrame({"a": np.random.default_rng(2).standard_normal(10)})
+ msg = 'Function "sin" does not support keyword arguments'
+
+ with pytest.raises(TypeError, match=msg):
+ df.eval("sin(x=a)", engine=engine, parser=parser)
+
+
+_var_s = np.random.default_rng(2).standard_normal(10)
+
+
+class TestScope:
+ def test_global_scope(self, engine, parser):
+ e = "_var_s * 2"
+ tm.assert_numpy_array_equal(
+ _var_s * 2, pd.eval(e, engine=engine, parser=parser)
+ )
+
+ def test_no_new_locals(self, engine, parser):
+ x = 1
+ lcls = locals().copy()
+ pd.eval("x + 1", local_dict=lcls, engine=engine, parser=parser)
+ lcls2 = locals().copy()
+ lcls2.pop("lcls")
+ assert lcls == lcls2
+
+ def test_no_new_globals(self, engine, parser):
+ x = 1 # noqa: F841
+ gbls = globals().copy()
+ pd.eval("x + 1", engine=engine, parser=parser)
+ gbls2 = globals().copy()
+ assert gbls == gbls2
+
+ def test_empty_locals(self, engine, parser):
+ # GH 47084
+ x = 1 # noqa: F841
+ msg = "name 'x' is not defined"
+ with pytest.raises(UndefinedVariableError, match=msg):
+ pd.eval("x + 1", engine=engine, parser=parser, local_dict={})
+
+ def test_empty_globals(self, engine, parser):
+ # GH 47084
+ msg = "name '_var_s' is not defined"
+ e = "_var_s * 2"
+ with pytest.raises(UndefinedVariableError, match=msg):
+ pd.eval(e, engine=engine, parser=parser, global_dict={})
+
+
+@td.skip_if_no("numexpr")
+def test_invalid_engine():
+ msg = "Invalid engine 'asdf' passed"
+ with pytest.raises(KeyError, match=msg):
+ pd.eval("x + y", local_dict={"x": 1, "y": 2}, engine="asdf")
+
+
+@td.skip_if_no("numexpr")
+@pytest.mark.parametrize(
+ ("use_numexpr", "expected"),
+ (
+ (True, "numexpr"),
+ (False, "python"),
+ ),
+)
+def test_numexpr_option_respected(use_numexpr, expected):
+ # GH 32556
+ from pandas.core.computation.eval import _check_engine
+
+ with pd.option_context("compute.use_numexpr", use_numexpr):
+ result = _check_engine(None)
+ assert result == expected
+
+
+@td.skip_if_no("numexpr")
+def test_numexpr_option_incompatible_op():
+ # GH 32556
+ with pd.option_context("compute.use_numexpr", False):
+ df = DataFrame(
+ {"A": [True, False, True, False, None, None], "B": [1, 2, 3, 4, 5, 6]}
+ )
+ result = df.query("A.isnull()")
+ expected = DataFrame({"A": [None, None], "B": [5, 6]}, index=range(4, 6))
+ tm.assert_frame_equal(result, expected)
+
+
+@td.skip_if_no("numexpr")
+def test_invalid_parser():
+ msg = "Invalid parser 'asdf' passed"
+ with pytest.raises(KeyError, match=msg):
+ pd.eval("x + y", local_dict={"x": 1, "y": 2}, parser="asdf")
+
+
+_parsers: dict[str, type[BaseExprVisitor]] = {
+ "python": PythonExprVisitor,
+ "pytables": pytables.PyTablesExprVisitor,
+ "pandas": PandasExprVisitor,
+}
+
+
+@pytest.mark.parametrize("engine", ENGINES)
+@pytest.mark.parametrize("parser", _parsers)
+def test_disallowed_nodes(engine, parser):
+ VisitorClass = _parsers[parser]
+ inst = VisitorClass("x + 1", engine, parser)
+
+ for ops in VisitorClass.unsupported_nodes:
+ msg = "nodes are not implemented"
+ with pytest.raises(NotImplementedError, match=msg):
+ getattr(inst, ops)()
+
+
+def test_syntax_error_exprs(engine, parser):
+ e = "s +"
+ with pytest.raises(SyntaxError, match="invalid syntax"):
+ pd.eval(e, engine=engine, parser=parser)
+
+
+def test_name_error_exprs(engine, parser):
+ e = "s + t"
+ msg = "name 's' is not defined"
+ with pytest.raises(NameError, match=msg):
+ pd.eval(e, engine=engine, parser=parser)
+
+
+@pytest.mark.parametrize("express", ["a + @b", "@a + b", "@a + @b"])
+def test_invalid_local_variable_reference(engine, parser, express):
+ a, b = 1, 2 # noqa: F841
+
+ if parser != "pandas":
+ with pytest.raises(SyntaxError, match="The '@' prefix is only"):
+ pd.eval(express, engine=engine, parser=parser)
+ else:
+ with pytest.raises(SyntaxError, match="The '@' prefix is not"):
+ pd.eval(express, engine=engine, parser=parser)
+
+
+def test_numexpr_builtin_raises(engine, parser):
+ sin, dotted_line = 1, 2
+ if engine == "numexpr":
+ msg = "Variables in expression .+"
+ with pytest.raises(NumExprClobberingError, match=msg):
+ pd.eval("sin + dotted_line", engine=engine, parser=parser)
+ else:
+ res = pd.eval("sin + dotted_line", engine=engine, parser=parser)
+ assert res == sin + dotted_line
+
+
+def test_bad_resolver_raises(engine, parser):
+ cannot_resolve = 42, 3.0
+ with pytest.raises(TypeError, match="Resolver of type .+"):
+ pd.eval("1 + 2", resolvers=cannot_resolve, engine=engine, parser=parser)
+
+
+def test_empty_string_raises(engine, parser):
+ # GH 13139
+ with pytest.raises(ValueError, match="expr cannot be an empty string"):
+ pd.eval("", engine=engine, parser=parser)
+
+
+def test_more_than_one_expression_raises(engine, parser):
+ with pytest.raises(SyntaxError, match="only a single expression is allowed"):
+ pd.eval("1 + 1; 2 + 2", engine=engine, parser=parser)
+
+
+@pytest.mark.parametrize("cmp", ("and", "or"))
+@pytest.mark.parametrize("lhs", (int, float))
+@pytest.mark.parametrize("rhs", (int, float))
+def test_bool_ops_fails_on_scalars(lhs, cmp, rhs, engine, parser):
+ gen = {
+ int: lambda: np.random.default_rng(2).integers(10),
+ float: np.random.default_rng(2).standard_normal,
+ }
+
+ mid = gen[lhs]() # noqa: F841
+ lhs = gen[lhs]()
+ rhs = gen[rhs]()
+
+ ex1 = f"lhs {cmp} mid {cmp} rhs"
+ ex2 = f"lhs {cmp} mid and mid {cmp} rhs"
+ ex3 = f"(lhs {cmp} mid) & (mid {cmp} rhs)"
+ for ex in (ex1, ex2, ex3):
+ msg = "cannot evaluate scalar only bool ops|'BoolOp' nodes are not"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.eval(ex, engine=engine, parser=parser)
+
+
+@pytest.mark.parametrize(
+ "other",
+ [
+ "'x'",
+ "...",
+ ],
+)
+def test_equals_various(other):
+ df = DataFrame({"A": ["a", "b", "c"]}, dtype=object)
+ result = df.eval(f"A == {other}")
+ expected = Series([False, False, False], name="A")
+ tm.assert_series_equal(result, expected)
+
+
+def test_inf(engine, parser):
+ s = "inf + 1"
+ expected = np.inf
+ result = pd.eval(s, engine=engine, parser=parser)
+ assert result == expected
+
+
+@pytest.mark.parametrize("column", ["Temp(°C)", "Capacitance(μF)"])
+def test_query_token(engine, column):
+ # See: https://github.com/pandas-dev/pandas/pull/42826
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=[column, "b"]
+ )
+ expected = df[df[column] > 5]
+ query_string = f"`{column}` > 5"
+ result = df.query(query_string, engine=engine)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_negate_lt_eq_le(engine, parser):
+ df = DataFrame([[0, 10], [1, 20]], columns=["cat", "count"])
+ expected = df[~(df.cat > 0)]
+
+ result = df.query("~(cat > 0)", engine=engine, parser=parser)
+ tm.assert_frame_equal(result, expected)
+
+ if parser == "python":
+ msg = "'Not' nodes are not implemented"
+ with pytest.raises(NotImplementedError, match=msg):
+ df.query("not (cat > 0)", engine=engine, parser=parser)
+ else:
+ result = df.query("not (cat > 0)", engine=engine, parser=parser)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "column",
+ DEFAULT_GLOBALS.keys(),
+)
+def test_eval_no_support_column_name(request, column):
+ # GH 44603
+ if column in ["True", "False", "inf", "Inf"]:
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=KeyError,
+ reason=f"GH 47859 DataFrame eval not supported with {column}",
+ )
+ )
+
+ df = DataFrame(
+ np.random.default_rng(2).integers(0, 100, size=(10, 2)),
+ columns=[column, "col1"],
+ )
+ expected = df[df[column] > 6]
+ result = df.query(f"{column}>6")
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_set_inplace():
+ # https://github.com/pandas-dev/pandas/issues/47449
+ # Ensure we don't only update the DataFrame inplace, but also the actual
+ # column values, such that references to this column also get updated
+ df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
+ result_view = df[:]
+ ser = df["A"]
+ df.eval("A = B + C", inplace=True)
+ expected = DataFrame({"A": [11, 13, 15], "B": [4, 5, 6], "C": [7, 8, 9]})
+ tm.assert_frame_equal(df, expected)
+ expected = Series([1, 2, 3], name="A")
+ tm.assert_series_equal(ser, expected)
+ tm.assert_series_equal(result_view["A"], expected)
+
+
+@pytest.mark.parametrize("value", [1, "True", [1, 2, 3], 5.0])
+def test_validate_bool_args(value):
+ msg = 'For argument "inplace" expected type bool, received type'
+ with pytest.raises(ValueError, match=msg):
+ pd.eval("2+2", inplace=value)
+
+
+@td.skip_if_no("numexpr")
+def test_eval_float_div_numexpr():
+ # GH 59736
+ result = pd.eval("1 / 2", engine="numexpr")
+ expected = 0.5
+ assert result == expected
+
+
+def test_method_calls_on_binop():
+ # GH 61175
+ x = Series([1, 2, 3, 5])
+ y = Series([2, 3, 4])
+
+ # Method call on binary operation result
+ result = pd.eval("(x + y).dropna()")
+ expected = (x + y).dropna()
+ tm.assert_series_equal(result, expected)
+
+ # Test with other binary operations
+ result = pd.eval("(x * y).dropna()")
+ expected = (x * y).dropna()
+ tm.assert_series_equal(result, expected)
+
+ # Test with method chaining
+ result = pd.eval("(x + y).dropna().reset_index(drop=True)")
+ expected = (x + y).dropna().reset_index(drop=True)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/config/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/config/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/config/test_config.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/config/test_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..e2f526f0ca3249503b05b84ba08804236ba41a67
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/config/test_config.py
@@ -0,0 +1,499 @@
+import pytest
+
+from pandas._config import config as cf
+from pandas._config.config import OptionError
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class TestConfig:
+ @pytest.fixture(autouse=True)
+ def clean_config(self, monkeypatch):
+ with monkeypatch.context() as m:
+ m.setattr(cf, "_global_config", {})
+ m.setattr(cf, "options", cf.DictWrapper(cf._global_config))
+ m.setattr(cf, "_deprecated_options", {})
+ m.setattr(cf, "_registered_options", {})
+
+ # Our test fixture in conftest.py sets "chained_assignment"
+ # to "raise" only after all test methods have been setup.
+ # However, after this setup, there is no longer any
+ # "chained_assignment" option, so re-register it.
+ cf.register_option("chained_assignment", "raise")
+ yield
+
+ def test_api(self):
+ # the pandas object exposes the user API
+ assert hasattr(pd, "get_option")
+ assert hasattr(pd, "set_option")
+ assert hasattr(pd, "reset_option")
+ assert hasattr(pd, "describe_option")
+
+ def test_is_one_of_factory(self):
+ v = cf.is_one_of_factory([None, 12])
+
+ v(12)
+ v(None)
+ msg = r"Value must be one of None\|12"
+ with pytest.raises(ValueError, match=msg):
+ v(1.1)
+
+ def test_register_option(self):
+ cf.register_option("a", 1, "doc")
+
+ # can't register an already registered option
+ msg = "Option 'a' has already been registered"
+ with pytest.raises(OptionError, match=msg):
+ cf.register_option("a", 1, "doc")
+
+ # can't register an already registered option
+ msg = "Path prefix to option 'a' is already an option"
+ with pytest.raises(OptionError, match=msg):
+ cf.register_option("a.b.c.d1", 1, "doc")
+ with pytest.raises(OptionError, match=msg):
+ cf.register_option("a.b.c.d2", 1, "doc")
+
+ # no python keywords
+ msg = "for is a python keyword"
+ with pytest.raises(ValueError, match=msg):
+ cf.register_option("for", 0)
+ with pytest.raises(ValueError, match=msg):
+ cf.register_option("a.for.b", 0)
+ # must be valid identifier (ensure attribute access works)
+ msg = "oh my goddess! is not a valid identifier"
+ with pytest.raises(ValueError, match=msg):
+ cf.register_option("Oh my Goddess!", 0)
+
+ # we can register options several levels deep
+ # without predefining the intermediate steps
+ # and we can define differently named options
+ # in the same namespace
+ cf.register_option("k.b.c.d1", 1, "doc")
+ cf.register_option("k.b.c.d2", 1, "doc")
+
+ def test_describe_option(self):
+ cf.register_option("a", 1, "doc")
+ cf.register_option("b", 1, "doc2")
+ cf.deprecate_option("b", FutureWarning)
+
+ cf.register_option("c.d.e1", 1, "doc3")
+ cf.register_option("c.d.e2", 1, "doc4")
+ cf.register_option("f", 1)
+ cf.register_option("g.h", 1)
+ cf.register_option("k", 2)
+ cf.deprecate_option("g.h", FutureWarning, rkey="k")
+ cf.register_option("l", "foo")
+
+ # non-existent keys raise KeyError
+ msg = r"No such keys\(s\)"
+ with pytest.raises(OptionError, match=msg):
+ cf.describe_option("no.such.key")
+
+ # we can get the description for any key we registered
+ assert "doc" in cf.describe_option("a", _print_desc=False)
+ assert "doc2" in cf.describe_option("b", _print_desc=False)
+ assert "precated" in cf.describe_option("b", _print_desc=False)
+ assert "doc3" in cf.describe_option("c.d.e1", _print_desc=False)
+ assert "doc4" in cf.describe_option("c.d.e2", _print_desc=False)
+
+ # if no doc is specified we get a default message
+ # saying "description not available"
+ assert "available" in cf.describe_option("f", _print_desc=False)
+ assert "available" in cf.describe_option("g.h", _print_desc=False)
+ assert "precated" in cf.describe_option("g.h", _print_desc=False)
+ assert "k" in cf.describe_option("g.h", _print_desc=False)
+
+ # default is reported
+ assert "foo" in cf.describe_option("l", _print_desc=False)
+ # current value is reported
+ assert "bar" not in cf.describe_option("l", _print_desc=False)
+ cf.set_option("l", "bar")
+ assert "bar" in cf.describe_option("l", _print_desc=False)
+
+ @pytest.mark.parametrize("category", [DeprecationWarning, FutureWarning])
+ def test_case_insensitive(self, category):
+ cf.register_option("KanBAN", 1, "doc")
+
+ assert "doc" in cf.describe_option("kanbaN", _print_desc=False)
+ assert cf.get_option("kanBaN") == 1
+ cf.set_option("KanBan", 2)
+ assert cf.get_option("kAnBaN") == 2
+
+ # gets of non-existent keys fail
+ msg = r"No such keys\(s\): 'no_such_option'"
+ with pytest.raises(OptionError, match=msg):
+ cf.get_option("no_such_option")
+
+ cf.deprecate_option("KanBan", category)
+ msg = "'kanban' is deprecated, please refrain from using it."
+ with tm.assert_produces_warning(category, match=msg):
+ cf.get_option("kAnBaN")
+
+ def test_get_option(self):
+ cf.register_option("a", 1, "doc")
+ cf.register_option("b.c", "hullo", "doc2")
+ cf.register_option("b.b", None, "doc2")
+
+ # gets of existing keys succeed
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b.c") == "hullo"
+ assert cf.get_option("b.b") is None
+
+ # gets of non-existent keys fail
+ msg = r"No such keys\(s\): 'no_such_option'"
+ with pytest.raises(OptionError, match=msg):
+ cf.get_option("no_such_option")
+
+ def test_set_option(self):
+ cf.register_option("a", 1, "doc")
+ cf.register_option("b.c", "hullo", "doc2")
+ cf.register_option("b.b", None, "doc2")
+
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b.c") == "hullo"
+ assert cf.get_option("b.b") is None
+
+ cf.set_option("a", 2)
+ cf.set_option("b.c", "wurld")
+ cf.set_option("b.b", 1.1)
+
+ assert cf.get_option("a") == 2
+ assert cf.get_option("b.c") == "wurld"
+ assert cf.get_option("b.b") == 1.1
+
+ msg = r"No such keys\(s\): 'no.such.key'"
+ with pytest.raises(OptionError, match=msg):
+ cf.set_option("no.such.key", None)
+
+ def test_set_option_empty_args(self):
+ msg = "Must provide an even number of non-keyword arguments"
+ with pytest.raises(ValueError, match=msg):
+ cf.set_option()
+
+ def test_set_option_uneven_args(self):
+ msg = "Must provide an even number of non-keyword arguments"
+ with pytest.raises(ValueError, match=msg):
+ cf.set_option("a.b", 2, "b.c")
+
+ def test_set_option_invalid_single_argument_type(self):
+ msg = "Must provide an even number of non-keyword arguments"
+ with pytest.raises(ValueError, match=msg):
+ cf.set_option(2)
+
+ def test_set_option_multiple(self):
+ cf.register_option("a", 1, "doc")
+ cf.register_option("b.c", "hullo", "doc2")
+ cf.register_option("b.b", None, "doc2")
+
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b.c") == "hullo"
+ assert cf.get_option("b.b") is None
+
+ cf.set_option("a", "2", "b.c", None, "b.b", 10.0)
+
+ assert cf.get_option("a") == "2"
+ assert cf.get_option("b.c") is None
+ assert cf.get_option("b.b") == 10.0
+
+ def test_set_option_dict(self):
+ # GH 61093
+
+ cf.register_option("a", 1, "doc")
+ cf.register_option("b.c", "hullo", "doc2")
+ cf.register_option("b.b", None, "doc2")
+
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b.c") == "hullo"
+ assert cf.get_option("b.b") is None
+
+ options_dict = {"a": "2", "b.c": None, "b.b": 10.0}
+ cf.set_option(options_dict)
+
+ assert cf.get_option("a") == "2"
+ assert cf.get_option("b.c") is None
+ assert cf.get_option("b.b") == 10.0
+
+ def test_validation(self):
+ cf.register_option("a", 1, "doc", validator=cf.is_int)
+ cf.register_option("d", 1, "doc", validator=cf.is_nonnegative_int)
+ cf.register_option("b.c", "hullo", "doc2", validator=cf.is_text)
+
+ msg = "Value must have type ''"
+ with pytest.raises(ValueError, match=msg):
+ cf.register_option("a.b.c.d2", "NO", "doc", validator=cf.is_int)
+
+ cf.set_option("a", 2) # int is_int
+ cf.set_option("b.c", "wurld") # str is_str
+ cf.set_option("d", 2)
+ cf.set_option("d", None) # non-negative int can be None
+
+ # None not is_int
+ with pytest.raises(ValueError, match=msg):
+ cf.set_option("a", None)
+ with pytest.raises(ValueError, match=msg):
+ cf.set_option("a", "ab")
+
+ msg = "Value must be a nonnegative integer or None"
+ with pytest.raises(ValueError, match=msg):
+ cf.register_option("a.b.c.d3", "NO", "doc", validator=cf.is_nonnegative_int)
+ with pytest.raises(ValueError, match=msg):
+ cf.register_option("a.b.c.d3", -2, "doc", validator=cf.is_nonnegative_int)
+
+ msg = r"Value must be an instance of \|"
+ with pytest.raises(ValueError, match=msg):
+ cf.set_option("b.c", 1)
+
+ validator = cf.is_one_of_factory([None, cf.is_callable])
+ cf.register_option("b", lambda: None, "doc", validator=validator)
+ cf.set_option("b", "%.1f".format) # Formatter is callable
+ cf.set_option("b", None) # Formatter is none (default)
+ with pytest.raises(ValueError, match="Value must be a callable"):
+ cf.set_option("b", "%.1f")
+
+ def test_reset_option(self):
+ cf.register_option("a", 1, "doc", validator=cf.is_int)
+ cf.register_option("b.c", "hullo", "doc2", validator=cf.is_str)
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b.c") == "hullo"
+
+ cf.set_option("a", 2)
+ cf.set_option("b.c", "wurld")
+ assert cf.get_option("a") == 2
+ assert cf.get_option("b.c") == "wurld"
+
+ cf.reset_option("a")
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b.c") == "wurld"
+ cf.reset_option("b.c")
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b.c") == "hullo"
+
+ def test_reset_option_all(self):
+ cf.register_option("a", 1, "doc", validator=cf.is_int)
+ cf.register_option("b.c", "hullo", "doc2", validator=cf.is_str)
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b.c") == "hullo"
+
+ cf.set_option("a", 2)
+ cf.set_option("b.c", "wurld")
+ assert cf.get_option("a") == 2
+ assert cf.get_option("b.c") == "wurld"
+
+ cf.reset_option("all")
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b.c") == "hullo"
+
+ def test_deprecate_option(self):
+ # we can deprecate non-existent options
+ cf.deprecate_option("foo", FutureWarning)
+
+ with tm.assert_produces_warning(FutureWarning, match="deprecated"):
+ with pytest.raises(KeyError, match="No such keys.s.: 'foo'"):
+ cf.get_option("foo")
+
+ cf.register_option("a", 1, "doc", validator=cf.is_int)
+ cf.register_option("b.c", "hullo", "doc2")
+ cf.register_option("foo", "hullo", "doc2")
+
+ cf.deprecate_option("a", FutureWarning, removal_ver="nifty_ver")
+ with tm.assert_produces_warning(FutureWarning, match="eprecated.*nifty_ver"):
+ cf.get_option("a")
+
+ msg = "Option 'a' has already been defined as deprecated"
+ with pytest.raises(OptionError, match=msg):
+ cf.deprecate_option("a", FutureWarning)
+
+ cf.deprecate_option("b.c", FutureWarning, "zounds!")
+ with tm.assert_produces_warning(FutureWarning, match="zounds!"):
+ cf.get_option("b.c")
+
+ # test rerouting keys
+ cf.register_option("d.a", "foo", "doc2")
+ cf.register_option("d.dep", "bar", "doc2")
+ assert cf.get_option("d.a") == "foo"
+ assert cf.get_option("d.dep") == "bar"
+
+ cf.deprecate_option("d.dep", FutureWarning, rkey="d.a") # reroute d.dep to d.a
+ with tm.assert_produces_warning(FutureWarning, match="eprecated"):
+ assert cf.get_option("d.dep") == "foo"
+
+ with tm.assert_produces_warning(FutureWarning, match="eprecated"):
+ cf.set_option("d.dep", "baz") # should overwrite "d.a"
+
+ with tm.assert_produces_warning(FutureWarning, match="eprecated"):
+ assert cf.get_option("d.dep") == "baz"
+
+ def test_config_prefix(self):
+ with cf.config_prefix("base"):
+ cf.register_option("a", 1, "doc1")
+ cf.register_option("b", 2, "doc2")
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b") == 2
+
+ cf.set_option("a", 3)
+ cf.set_option("b", 4)
+ assert cf.get_option("a") == 3
+ assert cf.get_option("b") == 4
+
+ assert cf.get_option("base.a") == 3
+ assert cf.get_option("base.b") == 4
+ assert "doc1" in cf.describe_option("base.a", _print_desc=False)
+ assert "doc2" in cf.describe_option("base.b", _print_desc=False)
+
+ cf.reset_option("base.a")
+ cf.reset_option("base.b")
+
+ with cf.config_prefix("base"):
+ assert cf.get_option("a") == 1
+ assert cf.get_option("b") == 2
+
+ def test_callback(self):
+ k = [None]
+ v = [None]
+
+ def callback(key):
+ k.append(key)
+ v.append(cf.get_option(key))
+
+ cf.register_option("d.a", "foo", cb=callback)
+ cf.register_option("d.b", "foo", cb=callback)
+
+ del k[-1], v[-1]
+ cf.set_option("d.a", "fooz")
+ assert k[-1] == "d.a"
+ assert v[-1] == "fooz"
+
+ del k[-1], v[-1]
+ cf.set_option("d.b", "boo")
+ assert k[-1] == "d.b"
+ assert v[-1] == "boo"
+
+ del k[-1], v[-1]
+ cf.reset_option("d.b")
+ assert k[-1] == "d.b"
+
+ def test_set_ContextManager(self):
+ def eq(val):
+ assert cf.get_option("a") == val
+
+ cf.register_option("a", 0)
+ eq(0)
+ with cf.option_context("a", 15):
+ eq(15)
+ with cf.option_context("a", 25):
+ eq(25)
+ eq(15)
+ eq(0)
+
+ cf.set_option("a", 17)
+ eq(17)
+
+ # Test that option_context can be used as a decorator too (#34253).
+ @cf.option_context("a", 123)
+ def f():
+ eq(123)
+
+ f()
+
+ def test_set_ContextManager_dict(self):
+ def eq(val):
+ assert cf.get_option("a") == val
+ assert cf.get_option("b.c") == val
+
+ cf.register_option("a", 0)
+ cf.register_option("b.c", 0)
+
+ eq(0)
+ with cf.option_context({"a": 15, "b.c": 15}):
+ eq(15)
+ with cf.option_context({"a": 25, "b.c": 25}):
+ eq(25)
+ eq(15)
+ eq(0)
+
+ cf.set_option("a", 17)
+ cf.set_option("b.c", 17)
+ eq(17)
+
+ # Test that option_context can be used as a decorator too
+ @cf.option_context({"a": 123, "b.c": 123})
+ def f():
+ eq(123)
+
+ f()
+
+ def test_attribute_access(self):
+ holder = []
+
+ def f3(key):
+ holder.append(True)
+
+ cf.register_option("a", 0)
+ cf.register_option("c", 0, cb=f3)
+ options = cf.options
+
+ assert options.a == 0
+ with cf.option_context("a", 15):
+ assert options.a == 15
+
+ options.a = 500
+ assert cf.get_option("a") == 500
+
+ cf.reset_option("a")
+ assert options.a == cf.get_option("a")
+
+ msg = "You can only set the value of existing options"
+ with pytest.raises(OptionError, match=msg):
+ options.b = 1
+ with pytest.raises(OptionError, match=msg):
+ options.display = 1
+
+ # make sure callback kicks when using this form of setting
+ options.c = 1
+ assert len(holder) == 1
+
+ def test_option_context_scope(self):
+ # Ensure that creating a context does not affect the existing
+ # environment as it is supposed to be used with the `with` statement.
+ # See https://github.com/pandas-dev/pandas/issues/8514
+
+ original_value = 60
+ context_value = 10
+ option_name = "a"
+
+ cf.register_option(option_name, original_value)
+
+ # Ensure creating contexts didn't affect the current context.
+ ctx = cf.option_context(option_name, context_value)
+ assert cf.get_option(option_name) == original_value
+
+ # Ensure the correct value is available inside the context.
+ with ctx:
+ assert cf.get_option(option_name) == context_value
+
+ # Ensure the current context is reset
+ assert cf.get_option(option_name) == original_value
+
+ def test_dictwrapper_getattr(self):
+ options = cf.options
+ # GH 19789
+ with pytest.raises(OptionError, match="No such option"):
+ options.bananas
+ assert not hasattr(options, "bananas")
+
+
+def test_no_silent_downcasting_deprecated():
+ # GH#59502
+ with tm.assert_produces_warning(Pandas4Warning, match="is deprecated"):
+ cf.get_option("future.no_silent_downcasting")
+ with tm.assert_produces_warning(Pandas4Warning, match="is deprecated"):
+ cf.set_option("future.no_silent_downcasting", True)
+
+
+def test_option_context_invalid_option():
+ with pytest.raises(OptionError, match="No such keys"):
+ with cf.option_context("invalid", True):
+ pass
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/config/test_localization.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/config/test_localization.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9a0a44bf8c89f10537bc2b7c64d3bb418a9a4a7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/config/test_localization.py
@@ -0,0 +1,155 @@
+import codecs
+import locale
+import os
+
+import pytest
+
+from pandas._config.localization import (
+ can_set_locale,
+ get_locales,
+ set_locale,
+)
+
+from pandas.compat import ISMUSL
+
+import pandas as pd
+
+_all_locales = get_locales()
+
+# Don't run any of these tests if we have no locales.
+pytestmark = pytest.mark.skipif(not _all_locales, reason="Need locales")
+
+_skip_if_only_one_locale = pytest.mark.skipif(
+ len(_all_locales) <= 1, reason="Need multiple locales for meaningful test"
+)
+
+
+def _get_current_locale(lc_var: int = locale.LC_ALL) -> str:
+ # getlocale is not always compliant with setlocale, use setlocale. GH#46595
+ return locale.setlocale(lc_var)
+
+
+@pytest.mark.parametrize("lc_var", (locale.LC_ALL, locale.LC_CTYPE, locale.LC_TIME))
+def test_can_set_current_locale(lc_var):
+ # Can set the current locale
+ before_locale = _get_current_locale(lc_var)
+ assert can_set_locale(before_locale, lc_var=lc_var)
+ after_locale = _get_current_locale(lc_var)
+ assert before_locale == after_locale
+
+
+@pytest.mark.parametrize("lc_var", (locale.LC_ALL, locale.LC_CTYPE, locale.LC_TIME))
+def test_can_set_locale_valid_set(lc_var):
+ # Can set the default locale.
+ before_locale = _get_current_locale(lc_var)
+ assert can_set_locale("", lc_var=lc_var)
+ after_locale = _get_current_locale(lc_var)
+ assert before_locale == after_locale
+
+
+@pytest.mark.parametrize(
+ "lc_var",
+ (
+ locale.LC_ALL,
+ locale.LC_CTYPE,
+ pytest.param(
+ locale.LC_TIME,
+ marks=pytest.mark.skipif(
+ ISMUSL, reason="MUSL allows setting invalid LC_TIME."
+ ),
+ ),
+ ),
+)
+def test_can_set_locale_invalid_set(lc_var):
+ # Cannot set an invalid locale.
+ before_locale = _get_current_locale(lc_var)
+ assert not can_set_locale("non-existent_locale", lc_var=lc_var)
+ after_locale = _get_current_locale(lc_var)
+ assert before_locale == after_locale
+
+
+@pytest.mark.parametrize(
+ "lang,enc",
+ [
+ ("it_CH", "UTF-8"),
+ ("en_US", "ascii"),
+ ("zh_CN", "GB2312"),
+ ("it_IT", "ISO-8859-1"),
+ ],
+)
+@pytest.mark.parametrize("lc_var", (locale.LC_ALL, locale.LC_CTYPE, locale.LC_TIME))
+def test_can_set_locale_no_leak(lang, enc, lc_var):
+ # Test that can_set_locale does not leak even when returning False. See GH#46595
+ before_locale = _get_current_locale(lc_var)
+ can_set_locale((lang, enc), locale.LC_ALL)
+ after_locale = _get_current_locale(lc_var)
+ assert before_locale == after_locale
+
+
+def test_can_set_locale_invalid_get(monkeypatch):
+ # see GH#22129
+ # In some cases, an invalid locale can be set,
+ # but a subsequent getlocale() raises a ValueError.
+
+ def mock_get_locale():
+ raise ValueError
+
+ with monkeypatch.context() as m:
+ m.setattr(locale, "getlocale", mock_get_locale)
+ assert not can_set_locale("")
+
+
+def test_get_locales_at_least_one():
+ # see GH#9744
+ assert len(_all_locales) > 0
+
+
+@_skip_if_only_one_locale
+def test_get_locales_prefix():
+ first_locale = _all_locales[0]
+ assert len(get_locales(prefix=first_locale[:2])) > 0
+
+
+@_skip_if_only_one_locale
+@pytest.mark.parametrize(
+ "lang,enc",
+ [
+ ("it_CH", "UTF-8"),
+ ("en_US", "ascii"),
+ ("zh_CN", "GB2312"),
+ ("it_IT", "ISO-8859-1"),
+ ],
+)
+def test_set_locale(lang, enc):
+ before_locale = _get_current_locale()
+
+ enc = codecs.lookup(enc).name
+ new_locale = lang, enc
+
+ if not can_set_locale(new_locale):
+ msg = "unsupported locale setting"
+
+ with pytest.raises(locale.Error, match=msg):
+ with set_locale(new_locale):
+ pass
+ else:
+ with set_locale(new_locale) as normalized_locale:
+ new_lang, new_enc = normalized_locale.split(".")
+ new_enc = codecs.lookup(enc).name
+
+ normalized_locale = new_lang, new_enc
+ assert normalized_locale == new_locale
+
+ # Once we exit the "with" statement, locale should be back to what it was.
+ after_locale = _get_current_locale()
+ assert before_locale == after_locale
+
+
+def test_encoding_detected():
+ system_locale = os.environ.get("LC_ALL")
+ system_encoding = system_locale.split(".")[-1] if system_locale else "utf-8"
+
+ assert (
+ codecs.lookup(pd.options.display.encoding).name
+ == codecs.lookup(system_encoding).name
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/construction/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/construction/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/construction/test_extract_array.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/construction/test_extract_array.py
new file mode 100644
index 0000000000000000000000000000000000000000..4dd3eda8c995ce022e9d46b907323e79bcd679f8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/construction/test_extract_array.py
@@ -0,0 +1,18 @@
+from pandas import Index
+import pandas._testing as tm
+from pandas.core.construction import extract_array
+
+
+def test_extract_array_rangeindex():
+ ri = Index(range(5))
+
+ expected = ri._values
+ res = extract_array(ri, extract_numpy=True, extract_range=True)
+ tm.assert_numpy_array_equal(res, expected)
+ res = extract_array(ri, extract_numpy=False, extract_range=True)
+ tm.assert_numpy_array_equal(res, expected)
+
+ res = extract_array(ri, extract_numpy=True, extract_range=False)
+ tm.assert_index_equal(res, ri)
+ res = extract_array(ri, extract_numpy=False, extract_range=False)
+ tm.assert_index_equal(res, ri)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_datetimeindex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_datetimeindex.py
new file mode 100644
index 0000000000000000000000000000000000000000..ddbff4bafdeff0ca78539071fc46fba8caa4041d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_datetimeindex.py
@@ -0,0 +1,86 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ Series,
+ Timestamp,
+ array,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Setting a value on a view:FutureWarning"
+)
+
+
+@pytest.mark.parametrize("box", [lambda x: x, DatetimeIndex])
+def test_datetimeindex(box):
+ dt = date_range("2019-12-31", periods=3, freq="D")
+ ser = Series(dt)
+ idx = box(DatetimeIndex(ser))
+ expected = idx.copy(deep=True)
+ ser.iloc[0] = Timestamp("2020-12-31")
+ tm.assert_index_equal(idx, expected)
+
+
+def test_datetimeindex_tz_convert():
+ dt = date_range("2019-12-31", periods=3, freq="D", tz="Europe/Berlin")
+ ser = Series(dt)
+ idx = DatetimeIndex(ser).tz_convert("US/Eastern")
+ expected = idx.copy(deep=True)
+ ser.iloc[0] = Timestamp("2020-12-31", tz="Europe/Berlin")
+ tm.assert_index_equal(idx, expected)
+
+
+def test_datetimeindex_tz_localize():
+ dt = date_range("2019-12-31", periods=3, freq="D")
+ ser = Series(dt)
+ idx = DatetimeIndex(ser).tz_localize("Europe/Berlin")
+ expected = idx.copy(deep=True)
+ ser.iloc[0] = Timestamp("2020-12-31")
+ tm.assert_index_equal(idx, expected)
+
+
+def test_datetimeindex_isocalendar():
+ dt = date_range("2019-12-31", periods=3, freq="D")
+ ser = Series(dt)
+ df = DatetimeIndex(ser).isocalendar()
+ expected = df.index.copy(deep=True)
+ ser.iloc[0] = Timestamp("2020-12-31")
+ tm.assert_index_equal(df.index, expected)
+
+
+def test_index_values():
+ idx = date_range("2019-12-31", periods=3, freq="D")
+ result = idx.values
+ assert result.flags.writeable is False
+
+
+def test_constructor_copy_input_datetime_ndarray_default():
+ # GH 63388
+ arr = np.array(["2020-01-01", "2020-01-02"], dtype="datetime64[ns]")
+ idx = DatetimeIndex(arr)
+ assert not np.shares_memory(arr, get_array(idx))
+
+
+def test_constructor_copy_input_datetime_ea_default():
+ # GH 63388
+ arr = array(["2020-01-01", "2020-01-02"], dtype="datetime64[ns]")
+ idx = DatetimeIndex(arr)
+ assert not tm.shares_memory(arr, idx.array)
+
+
+def test_series_from_temporary_datetimeindex_readonly_data():
+ # GH 63388
+ arr = np.array(["2020-01-01", "2020-01-02"], dtype="datetime64[ns]")
+ arr.flags.writeable = False
+ ser = Series(DatetimeIndex(arr))
+ assert not np.shares_memory(arr, get_array(ser))
+ ser.iloc[0] = Timestamp("2020-01-01")
+ expected = Series(
+ [Timestamp("2020-01-01"), Timestamp("2020-01-02")], dtype="datetime64[ns]"
+ )
+ tm.assert_series_equal(ser, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_index.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa594bd051a88061fa27639847347c2210a19c5b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_index.py
@@ -0,0 +1,177 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ array,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+def index_view(index_data):
+ df = DataFrame({"a": index_data, "b": 1.5})
+ view = df[:]
+ df = df.set_index("a", drop=True)
+ idx = df.index
+ # df = None
+ return idx, view
+
+
+def test_set_index_update_column():
+ df = DataFrame({"a": [1, 2], "b": 1})
+ df = df.set_index("a", drop=False)
+ expected = df.index.copy(deep=True)
+ df.iloc[0, 0] = 100
+ tm.assert_index_equal(df.index, expected)
+
+
+def test_set_index_drop_update_column():
+ df = DataFrame({"a": [1, 2], "b": 1.5})
+ view = df[:]
+ df = df.set_index("a", drop=True)
+ expected = df.index.copy(deep=True)
+ view.iloc[0, 0] = 100
+ tm.assert_index_equal(df.index, expected)
+
+
+def test_set_index_series():
+ df = DataFrame({"a": [1, 2], "b": 1.5})
+ ser = Series([10, 11])
+ df = df.set_index(ser)
+ expected = df.index.copy(deep=True)
+ ser.iloc[0] = 100
+ tm.assert_index_equal(df.index, expected)
+
+
+def test_assign_index_as_series():
+ df = DataFrame({"a": [1, 2], "b": 1.5})
+ ser = Series([10, 11])
+ df.index = ser
+ expected = df.index.copy(deep=True)
+ ser.iloc[0] = 100
+ tm.assert_index_equal(df.index, expected)
+
+
+def test_assign_index_as_index():
+ df = DataFrame({"a": [1, 2], "b": 1.5})
+ ser = Series([10, 11])
+ rhs_index = Index(ser)
+ df.index = rhs_index
+ rhs_index = None # overwrite to clear reference
+ expected = df.index.copy(deep=True)
+ ser.iloc[0] = 100
+ tm.assert_index_equal(df.index, expected)
+
+
+def test_index_from_series():
+ ser = Series([1, 2])
+ idx = Index(ser)
+ expected = idx.copy(deep=True)
+ ser.iloc[0] = 100
+ tm.assert_index_equal(idx, expected)
+
+
+def test_index_from_series_copy():
+ ser = Series([1, 2])
+ idx = Index(ser, copy=True) # noqa: F841
+ arr = get_array(ser)
+ ser.iloc[0] = 100
+ assert np.shares_memory(get_array(ser), arr)
+
+
+def test_index_from_index():
+ ser = Series([1, 2])
+ idx = Index(ser)
+ idx = Index(idx)
+ expected = idx.copy(deep=True)
+ ser.iloc[0] = 100
+ tm.assert_index_equal(idx, expected)
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda x: x._shallow_copy(x._values),
+ lambda x: x.view(),
+ lambda x: x.take([0, 1]),
+ lambda x: x.repeat([1, 1]),
+ lambda x: x[slice(0, 2)],
+ lambda x: x[[0, 1]],
+ lambda x: x._getitem_slice(slice(0, 2)),
+ lambda x: x.delete([]),
+ lambda x: x.rename("b"),
+ lambda x: x.astype("Int64", copy=False),
+ ],
+ ids=[
+ "_shallow_copy",
+ "view",
+ "take",
+ "repeat",
+ "getitem_slice",
+ "getitem_list",
+ "_getitem_slice",
+ "delete",
+ "rename",
+ "astype",
+ ],
+)
+def test_index_ops(func, request):
+ idx, view_ = index_view([1, 2])
+ expected = idx.copy(deep=True)
+ if "astype" in request.node.callspec.id:
+ expected = expected.astype("Int64")
+ idx = func(idx)
+ view_.iloc[0, 0] = 100
+ tm.assert_index_equal(idx, expected, check_names=False)
+
+
+def test_infer_objects():
+ idx, view_ = index_view(["a", "b"])
+ expected = idx.copy(deep=True)
+ idx = idx.infer_objects(copy=False)
+ view_.iloc[0, 0] = "aaaa"
+ tm.assert_index_equal(idx, expected, check_names=False)
+
+
+def test_index_to_frame():
+ idx = Index([1, 2, 3], name="a")
+ expected = idx.copy(deep=True)
+ df = idx.to_frame()
+ assert np.shares_memory(get_array(df, "a"), idx._values)
+ assert not df._mgr._has_no_reference(0)
+
+ df.iloc[0, 0] = 100
+ tm.assert_index_equal(idx, expected)
+
+
+def test_index_values():
+ idx = Index([1, 2, 3])
+ result = idx.values
+ assert result.flags.writeable is False
+
+
+def test_constructor_copy_input_ndarray_default():
+ arr = np.array([0, 1])
+ idx = Index(arr)
+ assert not np.shares_memory(arr, get_array(idx))
+
+
+def test_constructor_copy_input_ea_default():
+ arr = array([0, 1], dtype="Int64")
+ idx = Index(arr)
+ assert not tm.shares_memory(arr, idx.array)
+
+
+def test_series_from_temporary_index_readonly_data():
+ # GH 63370
+ arr = np.array([0, 1], dtype=np.dtype(np.int8))
+ arr.flags.writeable = False
+ ser = Series(Index(arr))
+ assert not np.shares_memory(arr, get_array(ser))
+ assert ser._mgr._has_no_reference(0)
+ ser[[False, True]] = np.array([0, 2], dtype=np.dtype(np.int8))
+ expected = Series([0, 2], dtype=np.dtype(np.int8))
+ tm.assert_series_equal(ser, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_intervalindex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_intervalindex.py
new file mode 100644
index 0000000000000000000000000000000000000000..d30415d05e4e4c85e266f1a35e96119a639c5fc0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_intervalindex.py
@@ -0,0 +1,29 @@
+import numpy as np
+
+from pandas import (
+ Interval,
+ IntervalIndex,
+ Series,
+ array,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+def test_constructor_copy_input_interval_ea_default():
+ # GH 63388
+ arr = array([Interval(0, 1), Interval(1, 2)])
+ idx = IntervalIndex(arr)
+ assert not tm.shares_memory(arr, idx.array)
+
+
+def test_series_from_temporary_intervalindex_readonly_data():
+ # GH 63388
+ arr = array([Interval(0, 1), Interval(1, 2)])
+ arr._left.flags.writeable = False
+ arr._right.flags.writeable = False
+ ser = Series(IntervalIndex(arr))
+ assert not np.shares_memory(arr._left, get_array(ser)._left)
+ ser.iloc[0] = Interval(5, 6)
+ expected = Series([Interval(5, 6), Interval(1, 2)], dtype="interval[int64, right]")
+ tm.assert_series_equal(ser, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_periodindex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_periodindex.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f741d123f4a8c25744fb4c81f3ecaf8dc9778d0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_periodindex.py
@@ -0,0 +1,47 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Period,
+ PeriodIndex,
+ Series,
+ array,
+ period_range,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Setting a value on a view:FutureWarning"
+)
+
+
+@pytest.mark.parametrize("box", [lambda x: x, PeriodIndex])
+def test_periodindex(box):
+ dt = period_range("2019-12-31", periods=3, freq="D")
+ ser = Series(dt)
+ idx = box(PeriodIndex(ser))
+ expected = idx.copy(deep=True)
+ ser.iloc[0] = Period("2020-12-31")
+ tm.assert_index_equal(idx, expected)
+
+
+def test_constructor_copy_input_period_ea_default():
+ # GH 63388
+ arr = array(["2020-01-01", "2020-01-02"], dtype="period[D]")
+ idx = PeriodIndex(arr)
+ assert not tm.shares_memory(arr, idx.array)
+
+
+def test_series_from_temporary_periodindex_readonly_data():
+ # GH 63388
+ arr = array(["2020-01-01", "2020-01-02"], dtype="period[D]")
+ arr._ndarray.flags.writeable = False
+ ser = Series(PeriodIndex(arr))
+ assert not np.shares_memory(arr._ndarray, get_array(ser))
+ ser.iloc[0] = Period("2022-01-01", freq="D")
+ expected = Series(
+ [Period("2022-01-01", freq="D"), Period("2020-01-02", freq="D")],
+ dtype="period[D]",
+ )
+ tm.assert_series_equal(ser, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_timedeltaindex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_timedeltaindex.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f4dcc39fe6f6835380b7be8cf864e4a0963efc8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/index/test_timedeltaindex.py
@@ -0,0 +1,59 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ array,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Setting a value on a view:FutureWarning"
+)
+
+
+@pytest.mark.parametrize(
+ "cons",
+ [
+ lambda x: TimedeltaIndex(x),
+ lambda x: TimedeltaIndex(TimedeltaIndex(x)),
+ ],
+)
+def test_timedeltaindex(cons):
+ dt = timedelta_range("1 day", periods=3)
+ ser = Series(dt)
+ idx = cons(ser)
+ expected = idx.copy(deep=True)
+ ser.iloc[0] = Timedelta("5 days")
+ tm.assert_index_equal(idx, expected)
+
+
+def test_constructor_copy_input_timedelta_ndarray_default():
+ # GH 63388
+ arr = np.array([1, 2], dtype="timedelta64[ns]")
+ idx = TimedeltaIndex(arr)
+ assert not np.shares_memory(arr, get_array(idx))
+
+
+def test_constructor_copy_input_timedelta_ea_default():
+ # GH 63388
+ arr = array([1, 2], dtype="timedelta64[ns]")
+ idx = TimedeltaIndex(arr)
+ assert not tm.shares_memory(arr, idx.array)
+
+
+def test_series_from_temporary_timedeltaindex_readonly_data():
+ # GH 63388
+ arr = np.array([1, 2], dtype="timedelta64[ns]")
+ arr.flags.writeable = False
+ ser = Series(TimedeltaIndex(arr))
+ assert not np.shares_memory(arr, get_array(ser))
+ ser.iloc[0] = Timedelta(days=1)
+ expected = Series(
+ [Timedelta(days=1), Timedelta(nanoseconds=2)], dtype="timedelta64[ns]"
+ )
+ tm.assert_series_equal(ser, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_array.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_array.py
new file mode 100644
index 0000000000000000000000000000000000000000..22976f307cae6d8da9852c12aad9f85b14c5dd64
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_array.py
@@ -0,0 +1,229 @@
+import numpy as np
+import pytest
+
+from pandas.compat.numpy import np_version_gt2
+
+from pandas import (
+ DataFrame,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+# -----------------------------------------------------------------------------
+# Copy/view behaviour for accessing underlying array of Series/DataFrame
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda ser: ser.values,
+ lambda ser: np.asarray(ser.array),
+ lambda ser: np.asarray(ser),
+ lambda ser: np.array(ser, copy=False),
+ ],
+ ids=["values", "array", "np.asarray", "np.array"],
+)
+def test_series_values(request, method):
+ ser = Series([1, 2, 3], name="name")
+ ser_orig = ser.copy()
+
+ arr = method(ser)
+
+ if request.node.callspec.id == "array":
+ # https://github.com/pandas-dev/pandas/issues/63099
+ # .array for now does not return a read-only view
+ assert arr.flags.writeable is True
+ # updating the array updates the series
+ arr[0] = 0
+ assert ser.iloc[0] == 0
+ return
+
+ # .values still gives a view but is read-only
+ assert np.shares_memory(arr, get_array(ser, "name"))
+ assert arr.flags.writeable is False
+
+ # mutating series through arr therefore doesn't work
+ with pytest.raises(ValueError, match="read-only"):
+ arr[0] = 0
+ tm.assert_series_equal(ser, ser_orig)
+
+ # mutating the series itself still works
+ ser.iloc[0] = 0
+ assert ser.values[0] == 0
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda df: df.values,
+ lambda df: np.asarray(df),
+ lambda ser: np.array(ser, copy=False),
+ ],
+ ids=["values", "asarray", "array"],
+)
+def test_dataframe_values(method):
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ df_orig = df.copy()
+
+ arr = method(df)
+
+ # .values still gives a view but is read-only
+ assert np.shares_memory(arr, get_array(df, "a"))
+ assert arr.flags.writeable is False
+
+ # mutating series through arr therefore doesn't work
+ with pytest.raises(ValueError, match="read-only"):
+ arr[0, 0] = 0
+ tm.assert_frame_equal(df, df_orig)
+
+ # mutating the series itself still works
+ df.iloc[0, 0] = 0
+ assert df.values[0, 0] == 0
+
+
+def test_series_to_numpy():
+ ser = Series([1, 2, 3], name="name")
+ ser_orig = ser.copy()
+
+ # default: copy=False, no dtype or NAs
+ arr = ser.to_numpy()
+ # to_numpy still gives a view but is read-only
+ assert np.shares_memory(arr, get_array(ser, "name"))
+ assert arr.flags.writeable is False
+
+ # mutating series through arr therefore doesn't work
+ with pytest.raises(ValueError, match="read-only"):
+ arr[0] = 0
+ tm.assert_series_equal(ser, ser_orig)
+
+ # mutating the series itself still works
+ ser.iloc[0] = 0
+ assert ser.values[0] == 0
+
+ # specify copy=True gives a writeable array
+ ser = Series([1, 2, 3], name="name")
+ arr = ser.to_numpy(copy=True)
+ assert not np.shares_memory(arr, get_array(ser, "name"))
+ assert arr.flags.writeable is True
+
+ # specifying a dtype that already causes a copy also gives a writeable array
+ ser = Series([1, 2, 3], name="name")
+ arr = ser.to_numpy(dtype="float64")
+ assert not np.shares_memory(arr, get_array(ser, "name"))
+ assert arr.flags.writeable is True
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda ser: np.asarray(ser.values),
+ lambda ser: np.asarray(ser.array),
+ lambda ser: np.asarray(ser),
+ lambda ser: np.asarray(ser, dtype="int64"),
+ lambda ser: np.array(ser, copy=False),
+ ],
+ ids=["values", "array", "np.asarray", "np.asarray-dtype", "np.array"],
+)
+def test_series_values_ea_dtypes(request, method):
+ ser = Series([1, 2, 3], dtype="Int64")
+ ser_orig = ser.copy()
+
+ arr = method(ser)
+
+ if request.node.callspec.id in ("values", "array"):
+ # https://github.com/pandas-dev/pandas/issues/63099
+ # .array/values for now does not return a read-only view
+ assert arr.flags.writeable is True
+ # updating the array updates the series
+ arr[0] = 0
+ assert ser.iloc[0] == 0
+ return
+
+ # conversion to ndarray gives a view but is read-only
+ assert np.shares_memory(arr, get_array(ser))
+ assert arr.flags.writeable is False
+
+ # mutating series through arr therefore doesn't work
+ with pytest.raises(ValueError, match="read-only"):
+ arr[0] = 0
+ tm.assert_series_equal(ser, ser_orig)
+
+ # mutating the series itself still works
+ ser.iloc[0] = 0
+ assert ser.values[0] == 0
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda df: df.values,
+ lambda df: np.asarray(df),
+ lambda df: np.asarray(df, dtype="int64"),
+ lambda df: np.array(df, copy=False),
+ ],
+ ids=["values", "np.asarray", "np.asarray-dtype", "np.array"],
+)
+def test_dataframe_array_ea_dtypes(method):
+ df = DataFrame({"a": [1, 2, 3]}, dtype="Int64")
+ arr = method(df)
+
+ assert np.shares_memory(arr, get_array(df, "a"))
+ assert arr.flags.writeable is False
+
+
+def test_dataframe_array_string_dtype():
+ df = DataFrame({"a": ["a", "b"]}, dtype="string[python]")
+ arr = np.asarray(df)
+ assert np.shares_memory(arr, get_array(df, "a"))
+ assert arr.flags.writeable is False
+
+
+def test_series_array_string_dtype(any_string_dtype):
+ ser = Series(["a", "b"], dtype=any_string_dtype)
+ arr = np.asarray(ser)
+ if any_string_dtype == "string" and any_string_dtype.storage == "pyarrow":
+ # for pyarrow strings, the numpy arrays is not a view, so also does
+ # not need to be read-only (https://github.com/pandas-dev/pandas/pull/64035)
+ assert not np.shares_memory(arr, get_array(ser))
+ assert arr.flags.writeable is True
+ else:
+ assert np.shares_memory(arr, get_array(ser))
+ assert arr.flags.writeable is False
+
+
+def test_dataframe_multiple_numpy_dtypes():
+ df = DataFrame({"a": [1, 2, 3], "b": 1.5})
+ arr = np.asarray(df)
+ assert not np.shares_memory(arr, get_array(df, "a"))
+ assert arr.flags.writeable is True
+
+ if np_version_gt2:
+ # copy=False semantics are only supported in NumPy>=2.
+
+ with pytest.raises(ValueError, match="Unable to avoid copy while creating"):
+ arr = np.array(df, copy=False)
+
+ arr = np.array(df, copy=True)
+ assert arr.flags.writeable is True
+
+
+def test_dataframe_single_block_copy_true():
+ # the copy=False/None cases are tested above in test_dataframe_values
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ arr = np.array(df, copy=True)
+ assert not np.shares_memory(arr, get_array(df, "a"))
+ assert arr.flags.writeable is True
+
+
+def test_values_is_ea():
+ df = DataFrame({"a": date_range("2012-01-01", periods=3)})
+ arr = np.asarray(df)
+ assert arr.flags.writeable is False
+
+
+def test_empty_dataframe():
+ df = DataFrame()
+ arr = np.asarray(df)
+ assert arr.flags.writeable is True
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..c436391739ab282ffd612186a1406422e3b0774a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_astype.py
@@ -0,0 +1,230 @@
+import pickle
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+def test_astype_single_dtype():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": 1.5})
+ df_orig = df.copy()
+ df2 = df.astype("float64")
+
+ assert np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ # mutating df2 triggers a copy-on-write for that column/block
+ df2.iloc[0, 2] = 5.5
+ assert not np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ tm.assert_frame_equal(df, df_orig)
+
+ # mutating parent also doesn't update result
+ df2 = df.astype("float64")
+ df.iloc[0, 2] = 5.5
+ tm.assert_frame_equal(df2, df_orig.astype("float64"))
+
+
+@pytest.mark.parametrize("dtype", ["int64", "Int64"])
+@pytest.mark.parametrize("new_dtype", ["int64", "Int64", "int64[pyarrow]"])
+def test_astype_avoids_copy(dtype, new_dtype):
+ if new_dtype == "int64[pyarrow]":
+ pytest.importorskip("pyarrow")
+ df = DataFrame({"a": [1, 2, 3]}, dtype=dtype)
+ df_orig = df.copy()
+ df2 = df.astype(new_dtype)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ # mutating df2 triggers a copy-on-write for that column/block
+ df2.iloc[0, 0] = 10
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+ # mutating parent also doesn't update result
+ df2 = df.astype(new_dtype)
+ df.iloc[0, 0] = 100
+ tm.assert_frame_equal(df2, df_orig.astype(new_dtype))
+
+
+@pytest.mark.parametrize("dtype", ["float64", "int32", "Int32", "int32[pyarrow]"])
+def test_astype_different_target_dtype(dtype):
+ if dtype == "int32[pyarrow]":
+ pytest.importorskip("pyarrow")
+ df = DataFrame({"a": [1, 2, 3]})
+ df_orig = df.copy()
+ df2 = df.astype(dtype)
+
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert df2._mgr._has_no_reference(0)
+
+ df2.iloc[0, 0] = 5
+ tm.assert_frame_equal(df, df_orig)
+
+ # mutating parent also doesn't update result
+ df2 = df.astype(dtype)
+ df.iloc[0, 0] = 100
+ tm.assert_frame_equal(df2, df_orig.astype(dtype))
+
+
+def test_astype_numpy_to_ea():
+ ser = Series([1, 2, 3])
+ result = ser.astype("Int64")
+ assert np.shares_memory(get_array(ser), get_array(result))
+
+
+@pytest.mark.parametrize(
+ "dtype, new_dtype", [("object", "string[python]"), ("string[python]", "object")]
+)
+def test_astype_string_and_object(dtype, new_dtype):
+ df = DataFrame({"a": ["a", "b", "c"]}, dtype=dtype)
+ df_orig = df.copy()
+ df2 = df.astype(new_dtype)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ df2.iloc[0, 0] = "x"
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "dtype, new_dtype", [("object", "string[python]"), ("string[python]", "object")]
+)
+def test_astype_string_and_object_update_original(dtype, new_dtype):
+ df = DataFrame({"a": ["a", "b", "c"]}, dtype=dtype)
+ df2 = df.astype(new_dtype)
+ df_orig = df2.copy()
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ df.iloc[0, 0] = "x"
+ tm.assert_frame_equal(df2, df_orig)
+
+
+def test_astype_str_copy_on_pickle_roundrip():
+ # TODO(infer_string) this test can be removed after 3.0 (once str is the default)
+ # https://github.com/pandas-dev/pandas/issues/54654
+ # ensure_string_array may alter array inplace
+ base = Series(np.array([(1, 2), None, 1], dtype="object"))
+ base_copy = pickle.loads(pickle.dumps(base))
+ base_copy.astype(str)
+ tm.assert_series_equal(base, base_copy)
+
+
+def test_astype_string_copy_on_pickle_roundrip(any_string_dtype):
+ # https://github.com/pandas-dev/pandas/issues/54654
+ # ensure_string_array may alter array inplace
+ base = Series(np.array([(1, 2), None, 1], dtype="object"))
+ base_copy = pickle.loads(pickle.dumps(base))
+ base_copy.astype(any_string_dtype)
+ tm.assert_series_equal(base, base_copy)
+
+
+def test_astype_string_read_only_on_pickle_roundrip(any_string_dtype):
+ # https://github.com/pandas-dev/pandas/issues/54654
+ # ensure_string_array may alter read-only array inplace
+ base = Series(np.array([(1, 2), None, 1], dtype="object"))
+ base_copy = pickle.loads(pickle.dumps(base))
+ base_copy._values.flags.writeable = False
+ base_copy.astype(any_string_dtype)
+ tm.assert_series_equal(base, base_copy)
+
+
+def test_astype_dict_dtypes():
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": Series([1.5, 1.5, 1.5], dtype="float64")}
+ )
+ df_orig = df.copy()
+ df2 = df.astype({"a": "float64", "c": "float64"})
+
+ assert np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ assert np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ # mutating df2 triggers a copy-on-write for that column/block
+ df2.iloc[0, 2] = 5.5
+ assert not np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+
+ df2.iloc[0, 1] = 10
+ assert not np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_astype_different_datetime_resos():
+ df = DataFrame({"a": date_range("2019-12-31", periods=2, freq="D")})
+ result = df.astype("datetime64[ms]")
+
+ assert not np.shares_memory(get_array(df, "a"), get_array(result, "a"))
+ assert result._mgr._has_no_reference(0)
+
+
+def test_astype_different_timezones():
+ df = DataFrame(
+ {"a": date_range("2019-12-31", periods=5, freq="D", tz="US/Pacific", unit="ns")}
+ )
+ result = df.astype("datetime64[ns, Europe/Berlin]")
+ assert not result._mgr._has_no_reference(0)
+ assert np.shares_memory(get_array(df, "a"), get_array(result, "a"))
+
+
+def test_astype_different_timezones_different_reso():
+ df = DataFrame(
+ {"a": date_range("2019-12-31", periods=5, freq="D", tz="US/Pacific", unit="ns")}
+ )
+ result = df.astype("datetime64[ms, Europe/Berlin]")
+ assert result._mgr._has_no_reference(0)
+ assert not np.shares_memory(get_array(df, "a"), get_array(result, "a"))
+
+
+def test_astype_arrow_timestamp():
+ pytest.importorskip("pyarrow")
+ df = DataFrame(
+ {
+ "a": [
+ Timestamp("2020-01-01 01:01:01.000001"),
+ Timestamp("2020-01-01 01:01:01.000001"),
+ ]
+ },
+ dtype="M8[ns]",
+ )
+ result = df.astype("timestamp[ns][pyarrow]")
+ assert not result._mgr._has_no_reference(0)
+ assert np.shares_memory(get_array(df, "a"), get_array(result, "a")._pa_array)
+
+
+def test_convert_dtypes_infer_objects():
+ ser = Series(["a", "b", "c"])
+ ser_orig = ser.copy()
+ result = ser.convert_dtypes(
+ convert_integer=False,
+ convert_boolean=False,
+ convert_floating=False,
+ convert_string=False,
+ )
+
+ assert tm.shares_memory(get_array(ser), get_array(result))
+ result.iloc[0] = "x"
+ tm.assert_series_equal(ser, ser_orig)
+
+
+def test_convert_dtypes(using_infer_string):
+ df = DataFrame({"a": ["a", "b"], "b": [1, 2], "c": [1.5, 2.5], "d": [True, False]})
+ df_orig = df.copy()
+ df2 = df.convert_dtypes()
+
+ if using_infer_string:
+ # String column is already Arrow-backed, so memory is shared
+ assert tm.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ else:
+ # String column converts from object to Arrow, no memory sharing
+ assert not tm.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert tm.shares_memory(get_array(df2, "d"), get_array(df, "d"))
+ assert tm.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ assert tm.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ df2.iloc[0, 0] = "x"
+ df2.iloc[0, 1] = 10
+ tm.assert_frame_equal(df, df_orig)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_chained_assignment_deprecation.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_chained_assignment_deprecation.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8a75fcd380c446a6822f4c551af7e152053a56a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_chained_assignment_deprecation.py
@@ -0,0 +1,104 @@
+import numpy as np
+import pytest
+
+from pandas.compat import CHAINED_WARNING_DISABLED
+from pandas.errors import ChainedAssignmentError
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "indexer", [0, [0, 1], slice(0, 2), np.array([True, False, True])]
+)
+def test_series_setitem(indexer):
+ # ensure we only get a single warning for those typical cases of chained
+ # assignment
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+
+ # using custom check instead of tm.assert_produces_warning because that doesn't
+ # fail if multiple warnings are raised
+ if CHAINED_WARNING_DISABLED:
+ return
+ with pytest.warns() as record: # noqa: TID251
+ df["a"][indexer] = 0
+ assert len(record) == 1
+ assert record[0].category == ChainedAssignmentError
+
+
+@pytest.mark.parametrize(
+ "indexer", ["a", ["a", "b"], slice(0, 2), np.array([True, False, True])]
+)
+def test_frame_setitem(indexer):
+ df = DataFrame({"a": [1, 2, 3, 4, 5], "b": 1})
+
+ with tm.raises_chained_assignment_error():
+ df[0:3][indexer] = 10
+
+
+@pytest.mark.parametrize(
+ "indexer", [0, [0, 1], slice(0, 2), np.array([True, False, True])]
+)
+def test_series_iloc_setitem(indexer):
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+
+ with tm.raises_chained_assignment_error():
+ df["a"].iloc[indexer] = 0
+
+
+@pytest.mark.parametrize(
+ "indexer", [0, [0, 1], slice(0, 2), np.array([True, False, True])]
+)
+def test_frame_iloc_setitem(indexer):
+ df = DataFrame({"a": [1, 2, 3, 4, 5], "b": 1})
+
+ with tm.raises_chained_assignment_error():
+ df[0:3].iloc[indexer] = 10
+
+
+@pytest.mark.parametrize(
+ "indexer", [0, [0, 1], slice(0, 2), np.array([True, False, True])]
+)
+def test_series_loc_setitem(indexer):
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+
+ with tm.raises_chained_assignment_error():
+ df["a"].loc[indexer] = 0
+
+
+@pytest.mark.parametrize(
+ "indexer", [0, [0, 1], (0, "a"), slice(0, 2), np.array([True, False, True])]
+)
+def test_frame_loc_setitem(indexer):
+ df = DataFrame({"a": [1, 2, 3, 4, 5], "b": 1})
+
+ with tm.raises_chained_assignment_error():
+ df[0:3].loc[indexer] = 10
+
+
+def test_series_at_setitem():
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+
+ with tm.raises_chained_assignment_error():
+ df["a"].at[0] = 0
+
+
+def test_frame_at_setitem():
+ df = DataFrame({"a": [1, 2, 3, 4, 5], "b": 1})
+
+ with tm.raises_chained_assignment_error():
+ df[0:3].at[0, "a"] = 10
+
+
+def test_series_iat_setitem():
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+
+ with tm.raises_chained_assignment_error():
+ df["a"].iat[0] = 0
+
+
+def test_frame_iat_setitem():
+ df = DataFrame({"a": [1, 2, 3, 4, 5], "b": 1})
+
+ with tm.raises_chained_assignment_error():
+ df[0:3].iat[0, 0] = 10
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_clip.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_clip.py
new file mode 100644
index 0000000000000000000000000000000000000000..56df33db6d416e6ae2307139b531f48a012f8d4c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_clip.py
@@ -0,0 +1,72 @@
+import numpy as np
+
+from pandas import DataFrame
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+def test_clip_inplace_reference():
+ df = DataFrame({"a": [1.5, 2, 3]})
+ df_copy = df.copy()
+ arr_a = get_array(df, "a")
+ view = df[:]
+ df.clip(lower=2, inplace=True)
+
+ assert not np.shares_memory(get_array(df, "a"), arr_a)
+ assert df._mgr._has_no_reference(0)
+ assert view._mgr._has_no_reference(0)
+ tm.assert_frame_equal(df_copy, view)
+
+
+def test_clip_inplace_reference_no_op():
+ df = DataFrame({"a": [1.5, 2, 3]})
+ df_copy = df.copy()
+ arr_a = get_array(df, "a")
+ view = df[:]
+ df.clip(lower=0, inplace=True)
+
+ assert np.shares_memory(get_array(df, "a"), arr_a)
+
+ assert not df._mgr._has_no_reference(0)
+ assert not view._mgr._has_no_reference(0)
+ tm.assert_frame_equal(df_copy, view)
+
+
+def test_clip_inplace():
+ df = DataFrame({"a": [1.5, 2, 3]})
+ arr_a = get_array(df, "a")
+ df.clip(lower=2, inplace=True)
+
+ assert np.shares_memory(get_array(df, "a"), arr_a)
+ assert df._mgr._has_no_reference(0)
+
+
+def test_clip():
+ df = DataFrame({"a": [1.5, 2, 3]})
+ df_orig = df.copy()
+ df2 = df.clip(lower=2)
+
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ assert df._mgr._has_no_reference(0)
+ tm.assert_frame_equal(df_orig, df)
+
+
+def test_clip_no_op():
+ df = DataFrame({"a": [1.5, 2, 3]})
+ df2 = df.clip(lower=0)
+
+ assert not df._mgr._has_no_reference(0)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+
+def test_clip_chained_inplace():
+ df = DataFrame({"a": [1, 4, 2], "b": 1})
+ df_orig = df.copy()
+ with tm.raises_chained_assignment_error():
+ df["a"].clip(1, 2, inplace=True)
+ tm.assert_frame_equal(df, df_orig)
+
+ with tm.raises_chained_assignment_error():
+ df[["a"]].clip(1, 2, inplace=True)
+ tm.assert_frame_equal(df, df_orig)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..7204aea950314f6b6e08f64291b42491c1fd415d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_constructors.py
@@ -0,0 +1,382 @@
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ Period,
+ PeriodIndex,
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+# -----------------------------------------------------------------------------
+# Copy/view behaviour for Series / DataFrame constructors
+
+
+@pytest.mark.parametrize("dtype", [None, "int64"])
+def test_series_from_series(dtype):
+ # Case: constructing a Series from another Series object follows CoW rules:
+ # a new object is returned and thus mutations are not propagated
+ ser = Series([1, 2, 3], name="name")
+
+ # default is copy=False -> new Series is a shallow copy / view of original
+ result = Series(ser, dtype=dtype)
+
+ # the shallow copy still shares memory
+ assert np.shares_memory(get_array(ser), get_array(result))
+
+ assert result._mgr.blocks[0].refs.has_reference()
+
+ # mutating new series copy doesn't mutate original
+ result.iloc[0] = 0
+ assert ser.iloc[0] == 1
+ # mutating triggered a copy-on-write -> no longer shares memory
+ assert not np.shares_memory(get_array(ser), get_array(result))
+
+ # the same when modifying the parent
+ result = Series(ser, dtype=dtype)
+
+ # mutating original doesn't mutate new series
+ ser.iloc[0] = 0
+ assert result.iloc[0] == 1
+
+ # forcing copy=False still gives a CoW shallow copy
+ result = Series(ser, dtype=dtype, copy=False)
+ assert np.shares_memory(get_array(ser), get_array(result))
+ assert result._mgr.blocks[0].refs.has_reference()
+
+ # forcing copy=True still results in an actual hard copy up front
+ result = Series(ser, dtype=dtype, copy=True)
+ assert not np.shares_memory(get_array(ser), get_array(result))
+ assert ser._mgr._has_no_reference(0)
+
+
+def test_series_from_series_with_reindex():
+ # Case: constructing a Series from another Series with specifying an index
+ # that potentially requires a reindex of the values
+ ser = Series([1, 2, 3], name="name")
+
+ # passing an index that doesn't actually require a reindex of the values
+ # -> still getting a CoW shallow copy
+ for index in [
+ ser.index,
+ ser.index.copy(),
+ list(ser.index),
+ ser.index.rename("idx"),
+ ]:
+ result = Series(ser, index=index)
+ assert np.shares_memory(ser.values, result.values)
+ result.iloc[0] = 0
+ assert ser.iloc[0] == 1
+
+ # forcing copy=True still results in an actual hard copy up front
+ result = Series(ser, index=index, copy=True)
+ assert not np.shares_memory(ser.values, result.values)
+ assert not result._mgr.blocks[0].refs.has_reference()
+
+ # ensure that if an actual reindex is needed, we don't have any refs
+ # (mutating the result wouldn't trigger CoW)
+ result = Series(ser, index=[0, 1, 2, 3])
+ assert not np.shares_memory(ser.values, result.values)
+ assert not result._mgr.blocks[0].refs.has_reference()
+
+
+@pytest.mark.parametrize("dtype", [None, "int64"])
+@pytest.mark.parametrize("idx", [None, pd.RangeIndex(start=0, stop=3, step=1)])
+@pytest.mark.parametrize(
+ "arr", [np.array([1, 2, 3], dtype="int64"), pd.array([1, 2, 3], dtype="Int64")]
+)
+def test_series_from_array(idx, dtype, arr):
+ ser = Series(arr, dtype=dtype, index=idx)
+ ser_orig = ser.copy()
+ data = getattr(arr, "_data", arr)
+ assert not np.shares_memory(get_array(ser), data)
+
+ arr[0] = 100
+ tm.assert_series_equal(ser, ser_orig)
+
+ # if the user explicitly passes copy=False, we get an actual view
+ # not protected by CoW
+ ser = Series(arr, dtype=dtype, index=idx, copy=False)
+ assert np.shares_memory(get_array(ser), data)
+ arr[0] = 50
+ assert ser.iloc[0] == 50
+
+
+@pytest.mark.parametrize("copy", [True, False, None])
+def test_series_from_array_different_dtype(copy):
+ arr = np.array([1, 2, 3], dtype="int64")
+ ser = Series(arr, dtype="int32", copy=copy)
+ assert not np.shares_memory(get_array(ser), arr)
+
+
+@pytest.mark.parametrize(
+ "idx",
+ [
+ Index([1, 2]),
+ DatetimeIndex([Timestamp("2019-12-31"), Timestamp("2020-12-31")]),
+ PeriodIndex([Period("2019-12-31"), Period("2020-12-31")]),
+ TimedeltaIndex([Timedelta("1 days"), Timedelta("2 days")]),
+ ],
+)
+def test_series_from_index(idx):
+ ser = Series(idx)
+ expected = idx.copy(deep=True)
+ assert np.shares_memory(get_array(ser), get_array(idx))
+ assert not ser._mgr._has_no_reference(0)
+ ser.iloc[0] = ser.iloc[1]
+ tm.assert_index_equal(idx, expected)
+
+ # forcing copy=False still gives a CoW shallow copy
+ ser = Series(idx, copy=False)
+ assert np.shares_memory(get_array(ser), get_array(idx))
+ assert not ser._mgr._has_no_reference(0)
+ ser.iloc[0] = ser.iloc[1]
+ tm.assert_index_equal(idx, expected)
+
+ # forcing copy=True still results in a copy
+ ser = Series(idx, copy=True)
+ assert not np.shares_memory(get_array(ser), get_array(idx))
+ assert ser._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize("copy", [True, False, None])
+def test_series_from_index_different_dtypes(copy):
+ idx = Index([1, 2, 3], dtype="int64", copy=copy)
+ ser = Series(idx, dtype="int32")
+ assert not np.shares_memory(get_array(ser), get_array(idx))
+ assert ser._mgr._has_no_reference(0)
+
+
+def test_series_from_block_manager_different_dtype():
+ ser = Series([1, 2, 3], dtype="int64")
+ msg = "Passing a SingleBlockManager to Series"
+ with tm.assert_produces_warning(DeprecationWarning, match=msg):
+ ser2 = Series(ser._mgr, dtype="int32")
+ assert not np.shares_memory(get_array(ser), get_array(ser2))
+ assert ser2._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize("use_mgr", [True, False])
+@pytest.mark.parametrize("columns", [None, ["a"]])
+def test_dataframe_constructor_mgr_or_df(columns, use_mgr):
+ df = DataFrame({"a": [1, 2, 3]})
+ df_orig = df.copy()
+
+ if use_mgr:
+ data = df._mgr
+ warn = DeprecationWarning
+ else:
+ data = df
+ warn = None
+ msg = "Passing a BlockManager to DataFrame"
+ with tm.assert_produces_warning(warn, match=msg, check_stacklevel=False):
+ new_df = DataFrame(data)
+
+ assert np.shares_memory(get_array(df, "a"), get_array(new_df, "a"))
+ new_df.iloc[0] = 100
+
+ assert not np.shares_memory(get_array(df, "a"), get_array(new_df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("dtype", [None, "int64", "Int64"])
+@pytest.mark.parametrize("index", [None, [0, 1, 2]])
+@pytest.mark.parametrize("columns", [None, ["a", "b"], ["a", "b", "c"]])
+def test_dataframe_from_dict_of_series(columns, index, dtype):
+ # Case: constructing a DataFrame from Series objects with copy=False
+ # has to do a lazy following CoW rules
+ # (the default for DataFrame(dict) is still to copy to ensure consolidation)
+ s1 = Series([1, 2, 3])
+ s2 = Series([4, 5, 6])
+ s1_orig = s1.copy()
+ expected = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6]}, index=index, columns=columns, dtype=dtype
+ )
+
+ result = DataFrame(
+ {"a": s1, "b": s2}, index=index, columns=columns, dtype=dtype, copy=False
+ )
+
+ # the shallow copy still shares memory
+ assert np.shares_memory(get_array(result, "a"), get_array(s1))
+
+ # mutating the new dataframe doesn't mutate original
+ result.iloc[0, 0] = 10
+ assert not np.shares_memory(get_array(result, "a"), get_array(s1))
+ tm.assert_series_equal(s1, s1_orig)
+
+ # the same when modifying the parent series
+ s1 = Series([1, 2, 3])
+ s2 = Series([4, 5, 6])
+ result = DataFrame(
+ {"a": s1, "b": s2}, index=index, columns=columns, dtype=dtype, copy=False
+ )
+ s1.iloc[0] = 10
+ assert not np.shares_memory(get_array(result, "a"), get_array(s1))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", [None, "int64"])
+def test_dataframe_from_dict_of_series_with_reindex(dtype):
+ # Case: constructing a DataFrame from Series objects with copy=False
+ # and passing an index that requires an actual (no-view) reindex -> need
+ # to ensure the result doesn't have refs set up to unnecessarily trigger
+ # a copy on write
+ s1 = Series([1, 2, 3])
+ s2 = Series([4, 5, 6])
+ df = DataFrame({"a": s1, "b": s2}, index=[1, 2, 3], dtype=dtype, copy=False)
+
+ # df should own its memory, so mutating shouldn't trigger a copy
+ arr_before = get_array(df, "a")
+ assert not np.shares_memory(arr_before, get_array(s1))
+ df.iloc[0, 0] = 100
+ arr_after = get_array(df, "a")
+ assert np.shares_memory(arr_before, arr_after)
+
+
+@pytest.mark.parametrize(
+ "data, dtype",
+ [
+ ([1, 2], "int64"),
+ # 1D-only EA
+ ([1, 2], "Int64"),
+ pytest.param(
+ ["a", "b"],
+ "str",
+ marks=pytest.mark.xfail(
+ reason="TODO bug with infer_string=False and specifying dtype='str'"
+ )
+ if not using_string_dtype()
+ else [],
+ ),
+ (["a", "b"], object),
+ # 2D EA
+ (
+ [Timestamp("2020", tz="UTC"), Timestamp("2021", tz="UTC")],
+ "datetime64[ns, UTC]",
+ ),
+ ],
+ ids=["int", "int-ea", "str", "object", "datetime64tz"],
+)
+def test_dataframe_from_series_or_index(data, dtype, index_or_series):
+ obj = index_or_series(data, dtype=dtype)
+ obj_orig = obj.copy(deep=True) # deep=True needed for Index
+
+ # default is copy=False -> DataFrame holds a shallow copy of original Index/Series
+ df = DataFrame(obj)
+ assert tm.shares_memory(get_array(obj), get_array(df, 0))
+ assert not df._mgr._has_no_reference(0)
+
+ df.iloc[0, 0] = data[-1]
+ tm.assert_equal(obj, obj_orig)
+
+ # with passing the (identical) dtype -> same
+ df = DataFrame(obj, dtype=dtype)
+ assert tm.shares_memory(get_array(obj), get_array(df, 0))
+ assert not df._mgr._has_no_reference(0)
+
+ df.iloc[0, 0] = data[-1]
+ tm.assert_equal(obj, obj_orig)
+
+ # forcing copy=True still results in an actual hard copy up front
+ df = DataFrame(obj, copy=True)
+ if not (obj.dtype == "str" and obj.dtype.storage == "pyarrow"):
+ # ArrowExtensionArray deep copy still points to the same underlying data
+ assert not tm.shares_memory(get_array(obj), get_array(df, 0))
+ assert df._mgr._has_no_reference(0)
+
+ df.iloc[0, 0] = data[-1]
+ tm.assert_equal(obj, obj_orig)
+
+
+def test_dataframe_from_series_or_index_different_dtype(index_or_series):
+ obj = index_or_series([1, 2], dtype="int64")
+ df = DataFrame(obj, dtype="int32")
+ assert not np.shares_memory(get_array(obj), get_array(df, 0))
+ assert df._mgr._has_no_reference(0)
+
+
+def test_dataframe_from_series_dont_infer_datetime():
+ ser = Series([Timestamp("2019-12-31"), Timestamp("2020-12-31")], dtype=object)
+ df = DataFrame(ser)
+ assert df.dtypes.iloc[0] == np.dtype(object)
+ assert np.shares_memory(get_array(ser), get_array(df, 0))
+ assert not df._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize("index", [None, [0, 1, 2]])
+def test_dataframe_from_dict_of_series_with_dtype(index):
+ # Variant of above, but now passing a dtype that causes a copy
+ # -> need to ensure the result doesn't have refs set up to unnecessarily
+ # trigger a copy on write
+ s1 = Series([1.0, 2.0, 3.0])
+ s2 = Series([4, 5, 6])
+ df = DataFrame({"a": s1, "b": s2}, index=index, dtype="int64", copy=False)
+
+ # df should own its memory, so mutating shouldn't trigger a copy
+ arr_before = get_array(df, "a")
+ assert not np.shares_memory(arr_before, get_array(s1))
+ df.iloc[0, 0] = 100
+ arr_after = get_array(df, "a")
+ assert np.shares_memory(arr_before, arr_after)
+
+
+@pytest.mark.parametrize("copy", [False, None, True])
+def test_dataframe_from_numpy_array(copy):
+ arr = np.array([[1, 2], [3, 4]])
+ df = DataFrame(arr, copy=copy)
+
+ if copy is not False or copy is True:
+ assert not np.shares_memory(get_array(df, 0), arr)
+ else:
+ assert np.shares_memory(get_array(df, 0), arr)
+
+
+@pytest.mark.parametrize(
+ "data, dtype",
+ [
+ # 1D-only EA
+ ([1, 2], "Int64"),
+ # 2D EA
+ (
+ [Timestamp("2020", tz="UTC"), Timestamp("2021", tz="UTC")],
+ "datetime64[ns, UTC]",
+ ),
+ ],
+ ids=["int-ea", "datetime64tz"],
+)
+@pytest.mark.parametrize("copy", [False, None, True])
+def test_dataframe_from_extension_array(copy, data, dtype):
+ arr = pd.array(data, dtype=dtype)
+ df = DataFrame(arr, copy=copy)
+
+ if arr.dtype == "Int64":
+ # to ensure tm.shares_memory works correctly
+ # TODO fix in tm.shares_memory or get_array?
+ arr = arr._data
+
+ if copy is None or copy is True:
+ assert not tm.shares_memory(get_array(df, 0), arr)
+ else:
+ assert tm.shares_memory(get_array(df, 0), arr)
+
+
+def test_frame_from_dict_of_index():
+ idx = Index([1, 2, 3])
+ expected = idx.copy(deep=True)
+ df = DataFrame({"a": idx}, copy=False)
+ assert np.shares_memory(get_array(df, "a"), idx._values)
+ assert not df._mgr._has_no_reference(0)
+
+ df.iloc[0, 0] = 100
+ tm.assert_index_equal(idx, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_copy_deprecation.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_copy_deprecation.py
new file mode 100644
index 0000000000000000000000000000000000000000..acc87787dbe0a3b678bbdb347f775b59dab90d8b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_copy_deprecation.py
@@ -0,0 +1,100 @@
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ concat,
+ merge,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "meth, kwargs",
+ [
+ ("truncate", {}),
+ ("tz_convert", {"tz": "UTC"}),
+ ("tz_localize", {"tz": "UTC"}),
+ ("infer_objects", {}),
+ ("astype", {"dtype": "float64"}),
+ ("reindex", {"index": [2, 0, 1]}),
+ ("transpose", {}),
+ ("set_axis", {"labels": [1, 2, 3]}),
+ ("rename", {"index": {1: 2}}),
+ ("set_flags", {}),
+ ("to_period", {}),
+ ("to_timestamp", {}),
+ ("swaplevel", {"i": 0, "j": 1}),
+ ],
+)
+def test_copy_deprecation(meth, kwargs):
+ df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": 1})
+
+ if meth in ("tz_convert", "tz_localize", "to_period"):
+ tz = None if meth in ("tz_localize", "to_period") else "US/Eastern"
+ df.index = pd.date_range("2020-01-01", freq="D", periods=len(df), tz=tz)
+ elif meth == "to_timestamp":
+ df.index = pd.period_range("2020-01-01", freq="D", periods=len(df))
+ elif meth == "swaplevel":
+ df = df.set_index(["b", "c"])
+
+ if meth != "swaplevel":
+ with tm.assert_produces_warning(Pandas4Warning, match="copy"):
+ getattr(df, meth)(copy=False, **kwargs)
+
+ if meth != "transpose":
+ with tm.assert_produces_warning(Pandas4Warning, match="copy"):
+ getattr(df.a, meth)(copy=False, **kwargs)
+
+
+def test_copy_deprecation_reindex_like_align():
+ df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ # Somehow the stack level check is incorrect here
+ with tm.assert_produces_warning(
+ Pandas4Warning, match="copy", check_stacklevel=False
+ ):
+ df.reindex_like(df, copy=False)
+
+ with tm.assert_produces_warning(
+ Pandas4Warning, match="copy", check_stacklevel=False
+ ):
+ df.a.reindex_like(df.a, copy=False)
+
+ with tm.assert_produces_warning(
+ Pandas4Warning, match="copy", check_stacklevel=False
+ ):
+ df.align(df, copy=False)
+
+ with tm.assert_produces_warning(
+ Pandas4Warning, match="copy", check_stacklevel=False
+ ):
+ df.a.align(df.a, copy=False)
+
+
+def test_copy_deprecation_merge_concat():
+ df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+
+ with tm.assert_produces_warning(
+ Pandas4Warning, match="copy", check_stacklevel=False
+ ):
+ df.merge(df, copy=False)
+
+ with tm.assert_produces_warning(
+ Pandas4Warning, match="copy", check_stacklevel=False
+ ):
+ merge(df, df, copy=False)
+
+ with tm.assert_produces_warning(
+ Pandas4Warning, match="copy", check_stacklevel=False
+ ):
+ concat([df, df], copy=False)
+
+
+@pytest.mark.parametrize("value", [False, True, "warn"])
+def test_copy_on_write_deprecation_option(value):
+ msg = "Copy-on-Write can no longer be disabled"
+ # stacklevel points to contextlib due to use of context manager.
+ with tm.assert_produces_warning(Pandas4Warning, match=msg, check_stacklevel=False):
+ with pd.option_context("mode.copy_on_write", value):
+ pass
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_core_functionalities.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_core_functionalities.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad16bafdf0ee431b6af53835fc8c8ccbee7cac97
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_core_functionalities.py
@@ -0,0 +1,93 @@
+import numpy as np
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+def test_assigning_to_same_variable_removes_references():
+ df = DataFrame({"a": [1, 2, 3]})
+ df = df.reset_index()
+ assert df._mgr._has_no_reference(1)
+ arr = get_array(df, "a")
+ df.iloc[0, 1] = 100 # Write into a
+
+ assert np.shares_memory(arr, get_array(df, "a"))
+
+
+def test_setitem_dont_track_unnecessary_references():
+ df = DataFrame({"a": [1, 2, 3], "b": 1, "c": 1})
+
+ df["b"] = 100
+ arr = get_array(df, "a")
+ # We split the block in setitem, if we are not careful the new blocks will
+ # reference each other triggering a copy
+ df.iloc[0, 0] = 100
+ assert np.shares_memory(arr, get_array(df, "a"))
+
+
+def test_setitem_with_view_copies():
+ df = DataFrame({"a": [1, 2, 3], "b": 1, "c": 1})
+ view = df[:]
+ expected = df.copy()
+
+ df["b"] = 100
+ arr = get_array(df, "a")
+ df.iloc[0, 0] = 100 # Check that we correctly track reference
+ assert not np.shares_memory(arr, get_array(df, "a"))
+ tm.assert_frame_equal(view, expected)
+
+
+def test_setitem_with_view_invalidated_does_not_copy(request):
+ df = DataFrame({"a": [1, 2, 3], "b": 1, "c": 1})
+ view = df[:]
+
+ df["b"] = 100
+ arr = get_array(df, "a")
+ view = None # noqa: F841
+ # TODO(CoW) block gets split because of `df["b"] = 100`
+ # which introduces additional refs, even when those of `view` go out of scopes
+ df.iloc[0, 0] = 100
+ # Setitem split the block. Since the old block shared data with view
+ # all the new blocks are referencing view and each other. When view
+ # goes out of scope, they don't share data with any other block,
+ # so we should not trigger a copy
+ mark = pytest.mark.xfail(reason="blk.delete does not track references correctly")
+ request.applymarker(mark)
+ assert np.shares_memory(arr, get_array(df, "a"))
+
+
+def test_out_of_scope():
+ def func():
+ df = DataFrame({"a": [1, 2], "b": 1.5, "c": 1})
+ # create some subset
+ result = df[["a", "b"]]
+ return result
+
+ result = func()
+ assert not result._mgr.blocks[0].refs.has_reference()
+ assert not result._mgr.blocks[1].refs.has_reference()
+
+
+def test_delete():
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 3)), columns=["a", "b", "c"]
+ )
+ del df["b"]
+ assert not df._mgr.blocks[0].refs.has_reference()
+ assert not df._mgr.blocks[1].refs.has_reference()
+
+ df = df[["a"]]
+ assert not df._mgr.blocks[0].refs.has_reference()
+
+
+def test_delete_reference():
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 3)), columns=["a", "b", "c"]
+ )
+ x = df[:]
+ del df["b"]
+ assert df._mgr.blocks[0].refs.has_reference()
+ assert df._mgr.blocks[1].refs.has_reference()
+ assert x._mgr.blocks[0].refs.has_reference()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_functions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_functions.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e355ce1b5ed59cda09f0546ef4a76f40eb2f7d5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_functions.py
@@ -0,0 +1,332 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ concat,
+ merge,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+def test_concat_frames():
+ df = DataFrame({"b": ["a"] * 3}, dtype=object)
+ df2 = DataFrame({"a": ["a"] * 3}, dtype=object)
+ df_orig = df.copy()
+ result = concat([df, df2], axis=1)
+
+ assert np.shares_memory(get_array(result, "b"), get_array(df, "b"))
+ assert np.shares_memory(get_array(result, "a"), get_array(df2, "a"))
+
+ result.iloc[0, 0] = "d"
+ assert not np.shares_memory(get_array(result, "b"), get_array(df, "b"))
+ assert np.shares_memory(get_array(result, "a"), get_array(df2, "a"))
+
+ result.iloc[0, 1] = "d"
+ assert not np.shares_memory(get_array(result, "a"), get_array(df2, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_concat_frames_updating_input():
+ df = DataFrame({"b": ["a"] * 3}, dtype=object)
+ df2 = DataFrame({"a": ["a"] * 3}, dtype=object)
+ result = concat([df, df2], axis=1)
+
+ assert np.shares_memory(get_array(result, "b"), get_array(df, "b"))
+ assert np.shares_memory(get_array(result, "a"), get_array(df2, "a"))
+
+ expected = result.copy()
+ df.iloc[0, 0] = "d"
+ assert not np.shares_memory(get_array(result, "b"), get_array(df, "b"))
+ assert np.shares_memory(get_array(result, "a"), get_array(df2, "a"))
+
+ df2.iloc[0, 0] = "d"
+ assert not np.shares_memory(get_array(result, "a"), get_array(df2, "a"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_series():
+ ser = Series([1, 2], name="a")
+ ser2 = Series([3, 4], name="b")
+ ser_orig = ser.copy()
+ ser2_orig = ser2.copy()
+ result = concat([ser, ser2], axis=1)
+
+ assert np.shares_memory(get_array(result, "a"), ser.values)
+ assert np.shares_memory(get_array(result, "b"), ser2.values)
+
+ result.iloc[0, 0] = 100
+ assert not np.shares_memory(get_array(result, "a"), ser.values)
+ assert np.shares_memory(get_array(result, "b"), ser2.values)
+
+ result.iloc[0, 1] = 1000
+ assert not np.shares_memory(get_array(result, "b"), ser2.values)
+ tm.assert_series_equal(ser, ser_orig)
+ tm.assert_series_equal(ser2, ser2_orig)
+
+
+def test_concat_frames_chained():
+ df1 = DataFrame({"a": [1, 2, 3], "b": [0.1, 0.2, 0.3]})
+ df2 = DataFrame({"c": [4, 5, 6]})
+ df3 = DataFrame({"d": [4, 5, 6]})
+ result = concat([concat([df1, df2], axis=1), df3], axis=1)
+ expected = result.copy()
+
+ assert np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert np.shares_memory(get_array(result, "c"), get_array(df2, "c"))
+ assert np.shares_memory(get_array(result, "d"), get_array(df3, "d"))
+
+ df1.iloc[0, 0] = 100
+ assert not np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_series_chained():
+ ser1 = Series([1, 2, 3], name="a")
+ ser2 = Series([4, 5, 6], name="c")
+ ser3 = Series([4, 5, 6], name="d")
+ result = concat([concat([ser1, ser2], axis=1), ser3], axis=1)
+ expected = result.copy()
+
+ assert np.shares_memory(get_array(result, "a"), get_array(ser1, "a"))
+ assert np.shares_memory(get_array(result, "c"), get_array(ser2, "c"))
+ assert np.shares_memory(get_array(result, "d"), get_array(ser3, "d"))
+
+ ser1.iloc[0] = 100
+ assert not np.shares_memory(get_array(result, "a"), get_array(ser1, "a"))
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_series_updating_input():
+ ser = Series([1, 2], name="a")
+ ser2 = Series([3, 4], name="b")
+ expected = DataFrame({"a": [1, 2], "b": [3, 4]})
+ result = concat([ser, ser2], axis=1)
+
+ assert np.shares_memory(get_array(result, "a"), get_array(ser, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(ser2, "b"))
+
+ ser.iloc[0] = 100
+ assert not np.shares_memory(get_array(result, "a"), get_array(ser, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(ser2, "b"))
+ tm.assert_frame_equal(result, expected)
+
+ ser2.iloc[0] = 1000
+ assert not np.shares_memory(get_array(result, "b"), get_array(ser2, "b"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_mixed_series_frame():
+ df = DataFrame({"a": [1, 2, 3], "c": 1})
+ ser = Series([4, 5, 6], name="d")
+ result = concat([df, ser], axis=1)
+ expected = result.copy()
+
+ assert np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+ assert np.shares_memory(get_array(result, "c"), get_array(df, "c"))
+ assert np.shares_memory(get_array(result, "d"), get_array(ser, "d"))
+
+ ser.iloc[0] = 100
+ assert not np.shares_memory(get_array(result, "d"), get_array(ser, "d"))
+
+ df.iloc[0, 0] = 100
+ assert not np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_copy_keyword():
+ df = DataFrame({"a": [1, 2]})
+ df2 = DataFrame({"b": [1.5, 2.5]})
+
+ result = concat([df, df2], axis=1)
+
+ assert np.shares_memory(get_array(df, "a"), get_array(result, "a"))
+ assert np.shares_memory(get_array(df2, "b"), get_array(result, "b"))
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda df1, df2, **kwargs: df1.merge(df2, **kwargs),
+ lambda df1, df2, **kwargs: merge(df1, df2, **kwargs),
+ ],
+)
+def test_merge_on_key(func):
+ df1 = DataFrame({"key": Series(["a", "b", "c"], dtype=object), "a": [1, 2, 3]})
+ df2 = DataFrame({"key": Series(["a", "b", "c"], dtype=object), "b": [4, 5, 6]})
+ df1_orig = df1.copy()
+ df2_orig = df2.copy()
+
+ result = func(df1, df2, on="key")
+
+ assert np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+ assert np.shares_memory(get_array(result, "key"), get_array(df1, "key"))
+ assert not np.shares_memory(get_array(result, "key"), get_array(df2, "key"))
+
+ result.iloc[0, 1] = 0
+ assert not np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+
+ result.iloc[0, 2] = 0
+ assert not np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+ tm.assert_frame_equal(df1, df1_orig)
+ tm.assert_frame_equal(df2, df2_orig)
+
+
+def test_merge_on_index():
+ df1 = DataFrame({"a": [1, 2, 3]})
+ df2 = DataFrame({"b": [4, 5, 6]})
+ df1_orig = df1.copy()
+ df2_orig = df2.copy()
+
+ result = merge(df1, df2, left_index=True, right_index=True)
+
+ assert np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+
+ result.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+
+ result.iloc[0, 1] = 0
+ assert not np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+ tm.assert_frame_equal(df1, df1_orig)
+ tm.assert_frame_equal(df2, df2_orig)
+
+
+@pytest.mark.parametrize(
+ "func, how",
+ [
+ (lambda df1, df2, **kwargs: merge(df2, df1, on="key", **kwargs), "right"),
+ (lambda df1, df2, **kwargs: merge(df1, df2, on="key", **kwargs), "left"),
+ ],
+)
+def test_merge_on_key_enlarging_one(func, how):
+ df1 = DataFrame({"key": Series(["a", "b", "c"], dtype=object), "a": [1, 2, 3]})
+ df2 = DataFrame({"key": Series(["a", "b"], dtype=object), "b": [4, 5]})
+ df1_orig = df1.copy()
+ df2_orig = df2.copy()
+
+ result = func(df1, df2, how=how)
+
+ assert np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert not np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+ assert df2._mgr._has_no_reference(1)
+ assert df2._mgr._has_no_reference(0)
+ assert np.shares_memory(get_array(result, "key"), get_array(df1, "key")) is (
+ how == "left"
+ )
+ assert not np.shares_memory(get_array(result, "key"), get_array(df2, "key"))
+
+ if how == "left":
+ result.iloc[0, 1] = 0
+ else:
+ result.iloc[0, 2] = 0
+ assert not np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ tm.assert_frame_equal(df1, df1_orig)
+ tm.assert_frame_equal(df2, df2_orig)
+
+
+def test_merge_copy_keyword():
+ df = DataFrame({"a": [1, 2]})
+ df2 = DataFrame({"b": [3, 4.5]})
+
+ result = df.merge(df2, left_index=True, right_index=True)
+
+ assert np.shares_memory(get_array(df, "a"), get_array(result, "a"))
+ assert np.shares_memory(get_array(df2, "b"), get_array(result, "b"))
+
+
+def test_merge_upcasting_no_copy():
+ left = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ left_copy = left.copy()
+ right = DataFrame({"a": [1, 2, 3], "c": [7, 8, 9]}, dtype=object)
+ result = merge(left, right, on="a")
+ assert np.shares_memory(get_array(result, "b"), get_array(left, "b"))
+ assert not np.shares_memory(get_array(result, "a"), get_array(left, "a"))
+ tm.assert_frame_equal(left, left_copy)
+
+ result = merge(right, left, on="a")
+ assert np.shares_memory(get_array(result, "b"), get_array(left, "b"))
+ assert not np.shares_memory(get_array(result, "a"), get_array(left, "a"))
+ tm.assert_frame_equal(left, left_copy)
+
+
+def test_merge_indicator_no_deep_copy():
+ left = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ right = DataFrame({"a": [1, 2, 3], "c": [7, 8, 9]})
+ result = merge(left, right, on="a", indicator=True)
+ assert np.shares_memory(get_array(result, "b"), get_array(left, "b"))
+ assert np.shares_memory(get_array(result, "c"), get_array(right, "c"))
+
+
+@pytest.mark.parametrize("dtype", [object, "str"])
+def test_join_on_key(dtype):
+ df_index = Index(["a", "b", "c"], name="key", dtype=dtype)
+
+ df1 = DataFrame({"a": [1, 2, 3]}, index=df_index.copy(deep=True))
+ df2 = DataFrame({"b": [4, 5, 6]}, index=df_index.copy(deep=True))
+
+ df1_orig = df1.copy()
+ df2_orig = df2.copy()
+
+ result = df1.join(df2, on="key")
+
+ assert np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+ assert tm.shares_memory(get_array(result.index), get_array(df1.index))
+ assert not np.shares_memory(get_array(result.index), get_array(df2.index))
+
+ result.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+
+ result.iloc[0, 1] = 0
+ assert not np.shares_memory(get_array(result, "b"), get_array(df2, "b"))
+
+ tm.assert_frame_equal(df1, df1_orig)
+ tm.assert_frame_equal(df2, df2_orig)
+
+
+def test_join_multiple_dataframes_on_key():
+ df_index = Index(["a", "b", "c"], name="key", dtype=object)
+
+ df1 = DataFrame({"a": [1, 2, 3]}, index=df_index.copy(deep=True))
+ dfs_list = [
+ DataFrame({"b": [4, 5, 6]}, index=df_index.copy(deep=True)),
+ DataFrame({"c": [7, 8, 9]}, index=df_index.copy(deep=True)),
+ ]
+
+ df1_orig = df1.copy()
+ dfs_list_orig = [df.copy() for df in dfs_list]
+
+ result = df1.join(dfs_list)
+
+ assert np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(dfs_list[0], "b"))
+ assert np.shares_memory(get_array(result, "c"), get_array(dfs_list[1], "c"))
+ assert np.shares_memory(get_array(result.index), get_array(df1.index))
+ assert not np.shares_memory(get_array(result.index), get_array(dfs_list[0].index))
+ assert not np.shares_memory(get_array(result.index), get_array(dfs_list[1].index))
+
+ result.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(result, "a"), get_array(df1, "a"))
+ assert np.shares_memory(get_array(result, "b"), get_array(dfs_list[0], "b"))
+ assert np.shares_memory(get_array(result, "c"), get_array(dfs_list[1], "c"))
+
+ result.iloc[0, 1] = 0
+ assert not np.shares_memory(get_array(result, "b"), get_array(dfs_list[0], "b"))
+ assert np.shares_memory(get_array(result, "c"), get_array(dfs_list[1], "c"))
+
+ result.iloc[0, 2] = 0
+ assert not np.shares_memory(get_array(result, "c"), get_array(dfs_list[1], "c"))
+
+ tm.assert_frame_equal(df1, df1_orig)
+ for df, df_orig in zip(dfs_list, dfs_list_orig, strict=True):
+ tm.assert_frame_equal(df, df_orig)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..6606f76df0e3eb85f9c64bea0c8a6db53cf6ca86
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_indexing.py
@@ -0,0 +1,904 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import is_float_dtype
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+@pytest.fixture(params=["numpy", "nullable"])
+def backend(request):
+ if request.param == "numpy":
+
+ def make_dataframe(*args, **kwargs):
+ return DataFrame(*args, **kwargs)
+
+ def make_series(*args, **kwargs):
+ return Series(*args, **kwargs)
+
+ elif request.param == "nullable":
+
+ def make_dataframe(*args, **kwargs):
+ df = DataFrame(*args, **kwargs)
+ df_nullable = df.convert_dtypes()
+ # convert_dtypes will try to cast float to int if there is no loss in
+ # precision -> undo that change
+ for col in df.columns:
+ if is_float_dtype(df[col].dtype) and not is_float_dtype(
+ df_nullable[col].dtype
+ ):
+ df_nullable[col] = df_nullable[col].astype("Float64")
+ # copy final result to ensure we start with a fully self-owning DataFrame
+ return df_nullable.copy()
+
+ def make_series(*args, **kwargs):
+ ser = Series(*args, **kwargs)
+ return ser.convert_dtypes().copy()
+
+ return request.param, make_dataframe, make_series
+
+
+# -----------------------------------------------------------------------------
+# Indexing operations taking subset + modifying the subset/parent
+
+
+def test_subset_column_selection(backend):
+ # Case: taking a subset of the columns of a DataFrame
+ # + afterwards modifying the subset
+ _, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+
+ subset = df[["a", "c"]]
+
+ assert subset.index is not df.index
+
+ # the subset shares memory ...
+ assert np.shares_memory(get_array(subset, "a"), get_array(df, "a"))
+ # ... but uses CoW when being modified
+ subset.iloc[0, 0] = 0
+
+ assert not np.shares_memory(get_array(subset, "a"), get_array(df, "a"))
+
+ expected = DataFrame({"a": [0, 2, 3], "c": [0.1, 0.2, 0.3]})
+ tm.assert_frame_equal(subset, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_subset_column_selection_modify_parent(backend):
+ # Case: taking a subset of the columns of a DataFrame
+ # + afterwards modifying the parent
+ _, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+
+ subset = df[["a", "c"]]
+
+ # the subset shares memory ...
+ assert np.shares_memory(get_array(subset, "a"), get_array(df, "a"))
+ # ... but parent uses CoW parent when it is modified
+ df.iloc[0, 0] = 0
+
+ assert not np.shares_memory(get_array(subset, "a"), get_array(df, "a"))
+ # different column/block still shares memory
+ assert np.shares_memory(get_array(subset, "c"), get_array(df, "c"))
+
+ expected = DataFrame({"a": [1, 2, 3], "c": [0.1, 0.2, 0.3]})
+ tm.assert_frame_equal(subset, expected)
+
+
+def test_subset_row_slice(backend):
+ # Case: taking a subset of the rows of a DataFrame using a slice
+ # + afterwards modifying the subset
+ _, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+
+ subset = df[1:3]
+ subset._mgr._verify_integrity()
+
+ assert subset.columns is not df.columns
+ assert np.shares_memory(get_array(subset, "a"), get_array(df, "a"))
+
+ subset.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(subset, "a"), get_array(df, "a"))
+
+ subset._mgr._verify_integrity()
+
+ expected = DataFrame({"a": [0, 3], "b": [5, 6], "c": [0.2, 0.3]}, index=range(1, 3))
+ tm.assert_frame_equal(subset, expected)
+ # original parent dataframe is not modified (CoW)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"]
+)
+def test_subset_column_slice(backend, dtype):
+ # Case: taking a subset of the columns of a DataFrame using a slice
+ # + afterwards modifying the subset
+ dtype_backend, DataFrame, _ = backend
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": np.array([7, 8, 9], dtype=dtype)}
+ )
+ df_orig = df.copy()
+
+ subset = df.iloc[:, 1:]
+ subset._mgr._verify_integrity()
+
+ assert subset.index is not df.index
+ assert np.shares_memory(get_array(subset, "b"), get_array(df, "b"))
+
+ subset.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(subset, "b"), get_array(df, "b"))
+
+ expected = DataFrame({"b": [0, 5, 6], "c": np.array([7, 8, 9], dtype=dtype)})
+ tm.assert_frame_equal(subset, expected)
+ # original parent dataframe is not modified (also not for BlockManager case,
+ # except for single block)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"]
+)
+@pytest.mark.parametrize(
+ "row_indexer",
+ [slice(1, 2), np.array([False, True, True]), np.array([1, 2])],
+ ids=["slice", "mask", "array"],
+)
+@pytest.mark.parametrize(
+ "column_indexer",
+ [slice("b", "c"), np.array([False, True, True]), ["b", "c"]],
+ ids=["slice", "mask", "array"],
+)
+def test_subset_loc_rows_columns(
+ backend,
+ dtype,
+ row_indexer,
+ column_indexer,
+):
+ # Case: taking a subset of the rows+columns of a DataFrame using .loc
+ # + afterwards modifying the subset
+ # Generic test for several combinations of row/column indexers, not all
+ # of those could actually return a view / need CoW (so this test is not
+ # checking memory sharing, only ensuring subsequent mutation doesn't
+ # affect the parent dataframe)
+ dtype_backend, DataFrame, _ = backend
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": np.array([7, 8, 9], dtype=dtype)}
+ )
+ df_orig = df.copy()
+
+ subset = df.loc[row_indexer, column_indexer]
+
+ assert subset.index is not df.index
+ assert subset.columns is not df.columns
+
+ # modifying the subset never modifies the parent
+ subset.iloc[0, 0] = 0
+
+ expected = DataFrame(
+ {"b": [0, 6], "c": np.array([8, 9], dtype=dtype)}, index=range(1, 3)
+ )
+ tm.assert_frame_equal(subset, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"]
+)
+@pytest.mark.parametrize(
+ "row_indexer",
+ [slice(1, 3), np.array([False, True, True]), np.array([1, 2])],
+ ids=["slice", "mask", "array"],
+)
+@pytest.mark.parametrize(
+ "column_indexer",
+ [slice(1, 3), np.array([False, True, True]), [1, 2]],
+ ids=["slice", "mask", "array"],
+)
+def test_subset_iloc_rows_columns(
+ backend,
+ dtype,
+ row_indexer,
+ column_indexer,
+):
+ # Case: taking a subset of the rows+columns of a DataFrame using .iloc
+ # + afterwards modifying the subset
+ # Generic test for several combinations of row/column indexers, not all
+ # of those could actually return a view / need CoW (so this test is not
+ # checking memory sharing, only ensuring subsequent mutation doesn't
+ # affect the parent dataframe)
+ dtype_backend, DataFrame, _ = backend
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": np.array([7, 8, 9], dtype=dtype)}
+ )
+ df_orig = df.copy()
+
+ subset = df.iloc[row_indexer, column_indexer]
+
+ assert subset.index is not df.index
+ assert subset.columns is not df.columns
+
+ # modifying the subset never modifies the parent
+ subset.iloc[0, 0] = 0
+
+ expected = DataFrame(
+ {"b": [0, 6], "c": np.array([8, 9], dtype=dtype)}, index=range(1, 3)
+ )
+ tm.assert_frame_equal(subset, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "indexer",
+ [slice(0, 2), np.array([True, True, False]), np.array([0, 1])],
+ ids=["slice", "mask", "array"],
+)
+def test_subset_set_with_row_indexer(backend, indexer_si, indexer):
+ # Case: setting values with a row indexer on a viewing subset
+ # subset[indexer] = value and subset.iloc[indexer] = value
+ _, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3, 4], "b": [4, 5, 6, 7], "c": [0.1, 0.2, 0.3, 0.4]})
+ df_orig = df.copy()
+ subset = df[1:4]
+
+ if (
+ indexer_si is tm.setitem
+ and isinstance(indexer, np.ndarray)
+ and indexer.dtype == "int"
+ ):
+ pytest.skip("setitem with labels selects on columns")
+
+ indexer_si(subset)[indexer] = 0
+
+ expected = DataFrame(
+ {"a": [0, 0, 4], "b": [0, 0, 7], "c": [0.0, 0.0, 0.4]}, index=range(1, 4)
+ )
+ tm.assert_frame_equal(subset, expected)
+ # original parent dataframe is not modified (CoW)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_subset_set_with_mask(backend):
+ # Case: setting values with a mask on a viewing subset: subset[mask] = value
+ _, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3, 4], "b": [4, 5, 6, 7], "c": [0.1, 0.2, 0.3, 0.4]})
+ df_orig = df.copy()
+ subset = df[1:4]
+
+ mask = subset > 3
+
+ subset[mask] = 0
+
+ expected = DataFrame(
+ {"a": [2, 3, 0], "b": [0, 0, 0], "c": [0.20, 0.3, 0.4]}, index=range(1, 4)
+ )
+ tm.assert_frame_equal(subset, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_subset_set_column(backend):
+ # Case: setting a single column on a viewing subset -> subset[col] = value
+ dtype_backend, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ subset = df[1:3]
+
+ if dtype_backend == "numpy":
+ arr = np.array([10, 11], dtype="int64")
+ else:
+ arr = pd.array([10, 11], dtype="Int64")
+
+ subset["a"] = arr
+ subset._mgr._verify_integrity()
+ expected = DataFrame(
+ {"a": [10, 11], "b": [5, 6], "c": [0.2, 0.3]}, index=range(1, 3)
+ )
+ tm.assert_frame_equal(subset, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"]
+)
+def test_subset_set_column_with_loc(backend, dtype):
+ # Case: setting a single column with loc on a viewing subset
+ # -> subset.loc[:, col] = value
+ _, DataFrame, _ = backend
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": np.array([7, 8, 9], dtype=dtype)}
+ )
+ df_orig = df.copy()
+ subset = df[1:3]
+
+ subset.loc[:, "a"] = np.array([10, 11], dtype="int64")
+
+ subset._mgr._verify_integrity()
+ expected = DataFrame(
+ {"a": [10, 11], "b": [5, 6], "c": np.array([8, 9], dtype=dtype)},
+ index=range(1, 3),
+ )
+ tm.assert_frame_equal(subset, expected)
+ # original parent dataframe is not modified (CoW)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_subset_set_column_with_loc2(backend):
+ # Case: setting a single column with loc on a viewing subset
+ # -> subset.loc[:, col] = value
+ # separate test for case of DataFrame of a single column -> takes a separate
+ # code path
+ _, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3]})
+ df_orig = df.copy()
+ subset = df[1:3]
+
+ subset.loc[:, "a"] = 0
+
+ subset._mgr._verify_integrity()
+ expected = DataFrame({"a": [0, 0]}, index=range(1, 3))
+ tm.assert_frame_equal(subset, expected)
+ # original parent dataframe is not modified (CoW)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"]
+)
+def test_subset_set_columns(backend, dtype):
+ # Case: setting multiple columns on a viewing subset
+ # -> subset[[col1, col2]] = value
+ dtype_backend, DataFrame, _ = backend
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": np.array([7, 8, 9], dtype=dtype)}
+ )
+ df_orig = df.copy()
+ subset = df[1:3]
+
+ subset[["a", "c"]] = 0
+
+ subset._mgr._verify_integrity()
+ # first and third column should certainly have no references anymore
+ assert all(subset._mgr._has_no_reference(i) for i in [0, 2])
+ expected = DataFrame({"a": [0, 0], "b": [5, 6], "c": [0, 0]}, index=range(1, 3))
+ if dtype_backend == "nullable":
+ # there is not yet a global option, so overriding a column by setting a scalar
+ # defaults to numpy dtype even if original column was nullable
+ expected["a"] = expected["a"].astype("int64")
+ expected["c"] = expected["c"].astype("int64")
+
+ tm.assert_frame_equal(subset, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "indexer",
+ [slice("a", "b"), np.array([True, True, False]), ["a", "b"]],
+ ids=["slice", "mask", "array"],
+)
+def test_subset_set_with_column_indexer(backend, indexer):
+ # Case: setting multiple columns with a column indexer on a viewing subset
+ # -> subset.loc[:, [col1, col2]] = value
+ _, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [0.1, 0.2, 0.3], "c": [4, 5, 6]})
+ df_orig = df.copy()
+ subset = df[1:3]
+
+ subset.loc[:, indexer] = 0
+
+ subset._mgr._verify_integrity()
+ expected = DataFrame({"a": [0, 0], "b": [0.0, 0.0], "c": [5, 6]}, index=range(1, 3))
+ tm.assert_frame_equal(subset, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda df: df[["a", "b"]][0:2],
+ lambda df: df[0:2][["a", "b"]],
+ lambda df: df[["a", "b"]].iloc[0:2],
+ lambda df: df[["a", "b"]].loc[0:1],
+ lambda df: df[0:2].iloc[:, 0:2],
+ lambda df: df[0:2].loc[:, "a":"b"], # type: ignore[misc]
+ ],
+ ids=[
+ "row-getitem-slice",
+ "column-getitem",
+ "row-iloc-slice",
+ "row-loc-slice",
+ "column-iloc-slice",
+ "column-loc-slice",
+ ],
+)
+@pytest.mark.parametrize(
+ "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"]
+)
+def test_subset_chained_getitem(
+ request,
+ backend,
+ method,
+ dtype,
+):
+ # Case: creating a subset using multiple, chained getitem calls using views
+ # still needs to guarantee proper CoW behaviour
+ _, DataFrame, _ = backend
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": np.array([7, 8, 9], dtype=dtype)}
+ )
+ df_orig = df.copy()
+
+ # modify subset -> don't modify parent
+ subset = method(df)
+
+ subset.iloc[0, 0] = 0
+ tm.assert_frame_equal(df, df_orig)
+
+ # modify parent -> don't modify subset
+ subset = method(df)
+ df.iloc[0, 0] = 0
+ expected = DataFrame({"a": [1, 2], "b": [4, 5]})
+ tm.assert_frame_equal(subset, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"]
+)
+def test_subset_chained_getitem_column(backend, dtype):
+ # Case: creating a subset using multiple, chained getitem calls using views
+ # still needs to guarantee proper CoW behaviour
+ dtype_backend, DataFrame, Series = backend
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": np.array([7, 8, 9], dtype=dtype)}
+ )
+ df_orig = df.copy()
+
+ # modify subset -> don't modify parent
+ subset = df[:]["a"][0:2]
+ subset.iloc[0] = 0
+ tm.assert_frame_equal(df, df_orig)
+
+ # modify parent -> don't modify subset
+ subset = df[:]["a"][0:2]
+ df.iloc[0, 0] = 0
+ expected = Series([1, 2], name="a")
+ tm.assert_series_equal(subset, expected)
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda s: s["a":"c"]["a":"b"], # type: ignore[misc]
+ lambda s: s.iloc[0:3].iloc[0:2],
+ lambda s: s.loc["a":"c"].loc["a":"b"], # type: ignore[misc]
+ lambda s: (
+ s.loc["a":"c"] # type: ignore[misc]
+ .iloc[0:3]
+ .iloc[0:2]
+ .loc["a":"b"] # type: ignore[misc]
+ .iloc[0:1]
+ ),
+ ],
+ ids=["getitem", "iloc", "loc", "long-chain"],
+)
+def test_subset_chained_getitem_series(backend, method):
+ # Case: creating a subset using multiple, chained getitem calls using views
+ # still needs to guarantee proper CoW behaviour
+ _, _, Series = backend
+ s = Series([1, 2, 3], index=["a", "b", "c"])
+ s_orig = s.copy()
+
+ # modify subset -> don't modify parent
+ subset = method(s)
+ subset.iloc[0] = 0
+ tm.assert_series_equal(s, s_orig)
+
+ # modify parent -> don't modify subset
+ subset = s.iloc[0:3].iloc[0:2]
+ s.iloc[0] = 0
+ expected = Series([1, 2], index=["a", "b"])
+ tm.assert_series_equal(subset, expected)
+
+
+def test_subset_chained_single_block_row():
+ # not parametrizing this for dtype backend, since this explicitly tests single block
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})
+ df_orig = df.copy()
+
+ # modify subset -> don't modify parent
+ subset = df[:].iloc[0].iloc[0:2]
+ subset.iloc[0] = 0
+ tm.assert_frame_equal(df, df_orig)
+
+ # modify parent -> don't modify subset
+ subset = df[:].iloc[0].iloc[0:2]
+ df.iloc[0, 0] = 0
+ expected = Series([1, 4], index=["a", "b"], name=0)
+ tm.assert_series_equal(subset, expected)
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda df: df[:],
+ lambda df: df.loc[:, :],
+ lambda df: df.loc[:],
+ lambda df: df.iloc[:, :],
+ lambda df: df.iloc[:],
+ ],
+ ids=["getitem", "loc", "loc-rows", "iloc", "iloc-rows"],
+)
+def test_null_slice(backend, method):
+ # Case: also all variants of indexing with a null slice (:) should return
+ # new objects to ensure we correctly use CoW for the results
+ dtype_backend, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})
+ df_orig = df.copy()
+
+ df2 = method(df)
+
+ # we always return new objects (shallow copy), regardless of CoW or not
+ assert df2 is not df
+ assert df2.index is not df.index
+ assert df2.columns is not df.columns
+
+ # and those trigger CoW when mutated
+ df2.iloc[0, 0] = 0
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda s: s[:],
+ lambda s: s.loc[:],
+ lambda s: s.iloc[:],
+ ],
+ ids=["getitem", "loc", "iloc"],
+)
+def test_null_slice_series(backend, method):
+ _, _, Series = backend
+ s = Series([1, 2, 3], index=["a", "b", "c"])
+ s_orig = s.copy()
+
+ s2 = method(s)
+
+ # we always return new objects, regardless of CoW or not
+ assert s2 is not s
+ assert s2.index is not s.index
+
+ # and those trigger CoW when mutated
+ s2.iloc[0] = 0
+ tm.assert_series_equal(s, s_orig)
+
+
+# TODO add more tests modifying the parent
+
+
+# -----------------------------------------------------------------------------
+# Series -- Indexing operations taking subset + modifying the subset/parent
+
+
+def test_series_getitem_slice(backend):
+ # Case: taking a slice of a Series + afterwards modifying the subset
+ _, _, Series = backend
+ s = Series([1, 2, 3], index=["a", "b", "c"])
+ s_orig = s.copy()
+
+ subset = s[:]
+ assert np.shares_memory(get_array(subset), get_array(s))
+ assert subset.index is not s.index
+
+ subset.iloc[0] = 0
+
+ assert not np.shares_memory(get_array(subset), get_array(s))
+
+ expected = Series([0, 2, 3], index=["a", "b", "c"])
+ tm.assert_series_equal(subset, expected)
+
+ # original parent series is not modified (CoW)
+ tm.assert_series_equal(s, s_orig)
+
+
+def test_series_getitem_ellipsis():
+ # Case: taking a view of a Series using Ellipsis + afterwards modifying the subset
+ s = Series([1, 2, 3])
+ s_orig = s.copy()
+
+ subset = s[...]
+ assert np.shares_memory(get_array(subset), get_array(s))
+ assert subset.index is not s.index
+
+ subset.iloc[0] = 0
+
+ assert not np.shares_memory(get_array(subset), get_array(s))
+
+ expected = Series([0, 2, 3])
+ tm.assert_series_equal(subset, expected)
+
+ # original parent series is not modified (CoW)
+ tm.assert_series_equal(s, s_orig)
+
+
+@pytest.mark.parametrize(
+ "indexer",
+ [slice(0, 2), np.array([True, True, False]), np.array([0, 1])],
+ ids=["slice", "mask", "array"],
+)
+def test_series_subset_set_with_indexer(backend, indexer_si, indexer):
+ # Case: setting values in a viewing Series with an indexer
+ _, _, Series = backend
+ s = Series([1, 2, 3], index=["a", "b", "c"])
+ s_orig = s.copy()
+ subset = s[:]
+
+ if (
+ indexer_si is tm.setitem
+ and isinstance(indexer, np.ndarray)
+ and indexer.dtype.kind == "i"
+ ):
+ # In 3.0 we treat integers as always-labels
+ with pytest.raises(KeyError):
+ indexer_si(subset)[indexer] = 0
+ return
+
+ indexer_si(subset)[indexer] = 0
+ expected = Series([0, 0, 3], index=["a", "b", "c"])
+ tm.assert_series_equal(subset, expected)
+
+ tm.assert_series_equal(s, s_orig)
+
+
+# -----------------------------------------------------------------------------
+# del operator
+
+
+def test_del_frame(backend):
+ # Case: deleting a column with `del` on a viewing child dataframe should
+ # not modify parent + update the references
+ dtype_backend, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df[:]
+
+ assert np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+
+ del df2["b"]
+
+ assert np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+ tm.assert_frame_equal(df, df_orig)
+ tm.assert_frame_equal(df2, df_orig[["a", "c"]])
+ df2._mgr._verify_integrity()
+
+ df.loc[0, "b"] = 200
+ assert np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+ df_orig = df.copy()
+
+ df2.loc[0, "a"] = 100
+ # modifying child after deleting a column still doesn't update parent
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_del_series(backend):
+ _, _, Series = backend
+ s = Series([1, 2, 3], index=["a", "b", "c"])
+ s_orig = s.copy()
+ s2 = s[:]
+
+ assert np.shares_memory(get_array(s), get_array(s2))
+
+ del s2["a"]
+
+ assert not np.shares_memory(get_array(s), get_array(s2))
+ tm.assert_series_equal(s, s_orig)
+ tm.assert_series_equal(s2, s_orig[["b", "c"]])
+
+ # modifying s2 doesn't need copy on write (due to `del`, s2 is backed by new array)
+ values = s2.values
+ s2.loc["b"] = 100
+ assert values[0] == 100
+
+
+# -----------------------------------------------------------------------------
+# Accessing column as Series
+
+
+def test_column_as_series(backend):
+ # Case: selecting a single column now also uses Copy-on-Write
+ dtype_backend, DataFrame, Series = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+
+ s = df["a"]
+
+ assert s.index is not df.index
+ assert np.shares_memory(get_array(s, "a"), get_array(df, "a"))
+
+ s[0] = 0
+
+ expected = Series([0, 2, 3], name="a")
+ tm.assert_series_equal(s, expected)
+ # assert not np.shares_memory(s.values, get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+ # ensure cached series on getitem is not the changed series
+ tm.assert_series_equal(df["a"], df_orig["a"])
+
+
+def test_column_as_series_set_with_upcast(backend):
+ # Case: selecting a single column now also uses Copy-on-Write -> when
+ # setting a value causes an upcast, we don't need to update the parent
+ # DataFrame through the cache mechanism
+ dtype_backend, DataFrame, Series = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+
+ s = df["a"]
+ if dtype_backend == "nullable":
+ with pytest.raises(TypeError, match="Invalid value"):
+ s[0] = "foo"
+ expected = Series([1, 2, 3], name="a")
+ tm.assert_series_equal(s, expected)
+ tm.assert_frame_equal(df, df_orig)
+ # ensure cached series on getitem is not the changed series
+ tm.assert_series_equal(df["a"], df_orig["a"])
+ else:
+ with pytest.raises(TypeError, match="Invalid value"):
+ s[0] = "foo"
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda df: df["a"],
+ lambda df: df.loc[:, "a"],
+ lambda df: df.iloc[:, 0],
+ ],
+ ids=["getitem", "loc", "iloc"],
+)
+def test_column_as_series_no_item_cache(request, backend, method):
+ # Case: selecting a single column (which now also uses Copy-on-Write to protect
+ # the view) should always give a new object (i.e. not make use of a cache)
+ dtype_backend, DataFrame, _ = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+
+ s1 = method(df)
+ s2 = method(df)
+
+ assert s1 is not s2
+ assert s1.index is not df.index
+ assert s1.index is not s2.index
+
+ s1.iloc[0] = 0
+
+ tm.assert_series_equal(s2, df_orig["a"])
+ tm.assert_frame_equal(df, df_orig)
+
+
+# TODO add tests for other indexing methods on the Series
+
+
+def test_dataframe_add_column_from_series(backend):
+ # Case: adding a new column to a DataFrame from an existing column/series
+ # -> delays copy under CoW
+ _, DataFrame, Series = backend
+ df = DataFrame({"a": [1, 2, 3], "b": [0.1, 0.2, 0.3]})
+
+ s = Series([10, 11, 12])
+ df["new"] = s
+ assert np.shares_memory(get_array(df, "new"), get_array(s))
+
+ # editing series -> doesn't modify column in frame
+ s[0] = 0
+ expected = DataFrame({"a": [1, 2, 3], "b": [0.1, 0.2, 0.3], "new": [10, 11, 12]})
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.parametrize("val", [100, "a"])
+@pytest.mark.parametrize(
+ "indexer_func, indexer",
+ [
+ (tm.loc, (0, "a")),
+ (tm.iloc, (0, 0)),
+ (tm.loc, ([0], "a")),
+ (tm.iloc, ([0], 0)),
+ (tm.loc, (slice(None), "a")),
+ (tm.iloc, (slice(None), 0)),
+ ],
+)
+@pytest.mark.parametrize(
+ "col", [[0.1, 0.2, 0.3], [7, 8, 9]], ids=["mixed-block", "single-block"]
+)
+def test_set_value_copy_only_necessary_column(indexer_func, indexer, val, col):
+ # When setting inplace, only copy column that is modified instead of the whole
+ # block (by splitting the block)
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": col})
+ df_orig = df.copy()
+ view = df[:]
+
+ if val == "a":
+ with pytest.raises(TypeError, match="Invalid value"):
+ indexer_func(df)[indexer] = val
+ else:
+ indexer_func(df)[indexer] = val
+
+ assert np.shares_memory(get_array(df, "b"), get_array(view, "b"))
+ assert not np.shares_memory(get_array(df, "a"), get_array(view, "a"))
+ tm.assert_frame_equal(view, df_orig)
+
+
+def test_series_midx_slice():
+ ser = Series([1, 2, 3], index=pd.MultiIndex.from_arrays([[1, 1, 2], [3, 4, 5]]))
+ ser_orig = ser.copy()
+ result = ser[1]
+ assert np.shares_memory(get_array(ser), get_array(result))
+ result.iloc[0] = 100
+ tm.assert_series_equal(ser, ser_orig)
+
+
+def test_getitem_midx_slice():
+ df = DataFrame({("a", "x"): [1, 2], ("a", "y"): 1, ("b", "x"): 2})
+ df_orig = df.copy()
+ new_df = df[("a",)]
+
+ assert not new_df._mgr._has_no_reference(0)
+
+ assert np.shares_memory(get_array(df, ("a", "x")), get_array(new_df, "x"))
+ new_df.iloc[0, 0] = 100
+ tm.assert_frame_equal(df_orig, df)
+
+
+def test_series_midx_tuples_slice():
+ ser = Series(
+ [1, 2, 3],
+ index=pd.MultiIndex.from_tuples([((1, 2), 3), ((1, 2), 4), ((2, 3), 4)]),
+ )
+ result = ser[(1, 2)]
+ assert np.shares_memory(get_array(ser), get_array(result))
+ result.iloc[0] = 100
+ expected = Series(
+ [1, 2, 3],
+ index=pd.MultiIndex.from_tuples([((1, 2), 3), ((1, 2), 4), ((2, 3), 4)]),
+ )
+ tm.assert_series_equal(ser, expected)
+
+
+def test_midx_read_only_bool_indexer():
+ # GH#56635
+ def mklbl(prefix, n):
+ return [f"{prefix}{i}" for i in range(n)]
+
+ idx = pd.MultiIndex.from_product(
+ [mklbl("A", 4), mklbl("B", 2), mklbl("C", 4), mklbl("D", 2)]
+ )
+ cols = pd.MultiIndex.from_tuples(
+ [("a", "foo"), ("a", "bar"), ("b", "foo"), ("b", "bah")], names=["lvl0", "lvl1"]
+ )
+ df = DataFrame(1, index=idx, columns=cols).sort_index().sort_index(axis=1)
+
+ mask = df[("a", "foo")] == 1
+ expected_mask = mask.copy()
+ result = df.loc[pd.IndexSlice[mask, :, ["C1", "C3"]], :]
+ expected = df.loc[pd.IndexSlice[:, :, ["C1", "C3"]], :]
+ tm.assert_frame_equal(result, expected)
+ tm.assert_series_equal(mask, expected_mask)
+
+
+def test_loc_enlarging_with_dataframe():
+ df = DataFrame({"a": [1, 2, 3]})
+ rhs = DataFrame({"b": [1, 2, 3], "c": [4, 5, 6]})
+ rhs_orig = rhs.copy()
+ df.loc[:, ["b", "c"]] = rhs
+ assert np.shares_memory(get_array(df, "b"), get_array(rhs, "b"))
+ assert np.shares_memory(get_array(df, "c"), get_array(rhs, "c"))
+ assert not df._mgr._has_no_reference(1)
+
+ df.iloc[0, 1] = 100
+ tm.assert_frame_equal(rhs, rhs_orig)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_internals.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_internals.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7baf01ecc36e5a3cfed4ba443d244ac669344fd
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_internals.py
@@ -0,0 +1,112 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+def test_consolidate():
+ # create unconsolidated DataFrame
+ df = DataFrame({"a": [1, 2, 3], "b": [0.1, 0.2, 0.3]})
+ df["c"] = [4, 5, 6]
+
+ # take a viewing subset
+ subset = df[:]
+
+ # each block of subset references a block of df
+ assert all(blk.refs.has_reference() for blk in subset._mgr.blocks)
+
+ # consolidate the two int64 blocks
+ subset._consolidate_inplace()
+
+ # the float64 block still references the parent one because it still a view
+ assert subset._mgr.blocks[0].refs.has_reference()
+ # equivalent of assert np.shares_memory(df["b"].values, subset["b"].values)
+ # but avoids caching df["b"]
+ assert np.shares_memory(get_array(df, "b"), get_array(subset, "b"))
+
+ # the new consolidated int64 block does not reference another
+ assert not subset._mgr.blocks[1].refs.has_reference()
+
+ # the parent dataframe now also only is linked for the float column
+ assert not df._mgr.blocks[0].refs.has_reference()
+ assert df._mgr.blocks[1].refs.has_reference()
+ assert not df._mgr.blocks[2].refs.has_reference()
+
+ # and modifying subset still doesn't modify parent
+ subset.iloc[0, 1] = 0.0
+ assert not df._mgr.blocks[1].refs.has_reference()
+ assert df.loc[0, "b"] == 0.1
+
+
+@pytest.mark.parametrize("dtype", [np.intp, np.int8])
+@pytest.mark.parametrize(
+ "locs, arr",
+ [
+ ([0], np.array([-1, -2, -3])),
+ ([1], np.array([-1, -2, -3])),
+ ([5], np.array([-1, -2, -3])),
+ ([0, 1], np.array([[-1, -2, -3], [-4, -5, -6]]).T),
+ ([0, 2], np.array([[-1, -2, -3], [-4, -5, -6]]).T),
+ ([0, 1, 2], np.array([[-1, -2, -3], [-4, -5, -6], [-4, -5, -6]]).T),
+ ([1, 2], np.array([[-1, -2, -3], [-4, -5, -6]]).T),
+ ([1, 3], np.array([[-1, -2, -3], [-4, -5, -6]]).T),
+ ],
+)
+def test_iset_splits_blocks_inplace(locs, arr, dtype):
+ # Nothing currently calls iset with
+ # more than 1 loc with inplace=True (only happens with inplace=False)
+ # but ensure that it works
+ df = DataFrame(
+ {
+ "a": [1, 2, 3],
+ "b": [4, 5, 6],
+ "c": [7, 8, 9],
+ "d": [10, 11, 12],
+ "e": [13, 14, 15],
+ "f": Series(["a", "b", "c"], dtype=object),
+ },
+ )
+ arr = arr.astype(dtype)
+ df_orig = df.copy()
+ df2 = df.copy(deep=False) # Trigger a CoW (if enabled, otherwise makes copy)
+ df2._mgr.iset(locs, arr, inplace=True)
+
+ tm.assert_frame_equal(df, df_orig)
+ for i, col in enumerate(df.columns):
+ if i not in locs:
+ assert np.shares_memory(get_array(df, col), get_array(df2, col))
+
+
+def test_exponential_backoff():
+ # GH#55518
+ df = DataFrame({"a": [1, 2, 3]})
+ for i in range(490):
+ df.copy(deep=False)
+
+ assert len(df._mgr.blocks[0].refs.referenced_blocks) == 491
+
+ df = DataFrame({"a": [1, 2, 3]})
+ dfs = [df.copy(deep=False) for i in range(510)]
+
+ for i in range(20):
+ df.copy(deep=False)
+ assert len(df._mgr.blocks[0].refs.referenced_blocks) == 531
+ assert df._mgr.blocks[0].refs.clear_counter == 1000
+
+ for i in range(500):
+ df.copy(deep=False)
+
+ # Don't reduce since we still have over 500 objects alive
+ assert df._mgr.blocks[0].refs.clear_counter == 1000
+
+ dfs = dfs[:300]
+ for i in range(500):
+ df.copy(deep=False)
+
+ # Reduce since there are less than 500 objects alive
+ assert df._mgr.blocks[0].refs.clear_counter == 500
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_interp_fillna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_interp_fillna.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5880e99df5d7122c1bb396189ae735dfe2e77ba
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_interp_fillna.py
@@ -0,0 +1,307 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ NA,
+ DataFrame,
+ Interval,
+ NaT,
+ Series,
+ Timestamp,
+ interval_range,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+@pytest.mark.parametrize("method", ["pad", "nearest", "linear"])
+def test_interpolate_no_op(method):
+ df = DataFrame({"a": [1, 2]})
+ df_orig = df.copy()
+
+ if method == "pad":
+ msg = f"Can not interpolate with method={method}"
+ with pytest.raises(ValueError, match=msg):
+ df.interpolate(method=method)
+ else:
+ result = df.interpolate(method=method)
+ assert np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+ assert result.index is not df.index
+ assert result.columns is not df.columns
+
+ result.iloc[0, 0] = 100
+
+ assert not np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("func", ["ffill", "bfill"])
+def test_interp_fill_functions(func):
+ # Check that these takes the same code paths as interpolate
+ df = DataFrame({"a": [1, 2]})
+ df_orig = df.copy()
+
+ result = getattr(df, func)()
+
+ assert np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+ assert result.index is not df.index
+ assert result.columns is not df.columns
+
+ result.iloc[0, 0] = 100
+ assert not np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("func", ["ffill", "bfill"])
+@pytest.mark.parametrize(
+ "vals", [[1, np.nan, 2], [Timestamp("2019-12-31"), NaT, Timestamp("2020-12-31")]]
+)
+def test_interpolate_triggers_copy(vals, func):
+ df = DataFrame({"a": vals})
+ result = getattr(df, func)()
+
+ assert not np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+ # Check that we don't have references when triggering a copy
+ assert result._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize(
+ "vals", [[1, np.nan, 2], [Timestamp("2019-12-31"), NaT, Timestamp("2020-12-31")]]
+)
+def test_interpolate_inplace_no_reference_no_copy(vals):
+ df = DataFrame({"a": vals})
+ arr = get_array(df, "a")
+ df.interpolate(method="linear", inplace=True)
+
+ assert np.shares_memory(arr, get_array(df, "a"))
+ # Check that we don't have references when triggering a copy
+ assert df._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize(
+ "vals", [[1, np.nan, 2], [Timestamp("2019-12-31"), NaT, Timestamp("2020-12-31")]]
+)
+def test_interpolate_inplace_with_refs(vals):
+ df = DataFrame({"a": [1, np.nan, 2]})
+ df_orig = df.copy()
+ arr = get_array(df, "a")
+ view = df[:]
+ df.interpolate(method="linear", inplace=True)
+ # Check that copy was triggered in interpolate and that we don't
+ # have any references left
+ assert not np.shares_memory(arr, get_array(df, "a"))
+ tm.assert_frame_equal(df_orig, view)
+ assert df._mgr._has_no_reference(0)
+ assert view._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize("func", ["ffill", "bfill"])
+@pytest.mark.parametrize("dtype", ["float64", "Float64"])
+def test_interp_fill_functions_inplace(func, dtype):
+ # Check that these takes the same code paths as interpolate
+ df = DataFrame({"a": [1, np.nan, 2]}, dtype=dtype)
+ df_orig = df.copy()
+ arr = get_array(df, "a")
+ view = df[:]
+
+ getattr(df, func)(inplace=True)
+
+ # Check that copy was triggered in interpolate and that we don't
+ # have any references left
+ assert not np.shares_memory(arr, get_array(df, "a"))
+ tm.assert_frame_equal(df_orig, view)
+ assert df._mgr._has_no_reference(0)
+ assert view._mgr._has_no_reference(0)
+
+
+def test_interpolate_cannot_with_object_dtype():
+ df = DataFrame({"a": ["a", np.nan, "c"], "b": 1})
+ df["a"] = df["a"].astype(object)
+
+ msg = "DataFrame cannot interpolate with object dtype"
+ with pytest.raises(TypeError, match=msg):
+ df.interpolate()
+
+
+def test_interpolate_object_convert_no_op():
+ df = DataFrame({"a": ["a", "b", "c"], "b": 1})
+ df["a"] = df["a"].astype(object)
+ arr_a = get_array(df, "a")
+
+ # Now CoW makes a copy, it should not!
+ assert df._mgr._has_no_reference(0)
+ assert np.shares_memory(arr_a, get_array(df, "a"))
+
+
+def test_interpolate_object_convert_copies():
+ df = DataFrame({"a": [1, np.nan, 2.5], "b": 1})
+ arr_a = get_array(df, "a")
+ msg = "Can not interpolate with method=pad"
+ with pytest.raises(ValueError, match=msg):
+ df.interpolate(method="pad", inplace=True)
+
+ assert df._mgr._has_no_reference(0)
+ assert np.shares_memory(arr_a, get_array(df, "a"))
+
+
+def test_interpolate_downcast_reference_triggers_copy():
+ df = DataFrame({"a": [1, np.nan, 2.5], "b": 1})
+ df_orig = df.copy()
+ arr_a = get_array(df, "a")
+ view = df[:]
+
+ msg = "Can not interpolate with method=pad"
+ with pytest.raises(ValueError, match=msg):
+ df.interpolate(method="pad", inplace=True)
+ assert df._mgr._has_no_reference(0)
+ assert not np.shares_memory(arr_a, get_array(df, "a"))
+
+ tm.assert_frame_equal(df_orig, view)
+
+
+def test_fillna():
+ df = DataFrame({"a": [1.5, np.nan], "b": 1})
+ df_orig = df.copy()
+
+ df2 = df.fillna(5.5)
+ assert np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+ assert df2.index is not df.index
+ assert df2.columns is not df.columns
+
+ df2.iloc[0, 1] = 100
+ tm.assert_frame_equal(df_orig, df)
+
+
+def test_fillna_dict():
+ df = DataFrame({"a": [1.5, np.nan], "b": 1})
+ df_orig = df.copy()
+
+ df2 = df.fillna({"a": 100.5})
+ assert np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+ assert not np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+
+ df2.iloc[0, 1] = 100
+ tm.assert_frame_equal(df_orig, df)
+
+
+def test_fillna_inplace():
+ df = DataFrame({"a": [1.5, np.nan], "b": 1})
+ arr_a = get_array(df, "a")
+ arr_b = get_array(df, "b")
+
+ df.fillna(5.5, inplace=True)
+ assert np.shares_memory(get_array(df, "a"), arr_a)
+ assert np.shares_memory(get_array(df, "b"), arr_b)
+ assert df._mgr._has_no_reference(0)
+ assert df._mgr._has_no_reference(1)
+
+
+def test_fillna_inplace_reference():
+ df = DataFrame({"a": [1.5, np.nan], "b": 1})
+ df_orig = df.copy()
+ arr_a = get_array(df, "a")
+ arr_b = get_array(df, "b")
+ view = df[:]
+
+ df.fillna(5.5, inplace=True)
+ assert not np.shares_memory(get_array(df, "a"), arr_a)
+ assert np.shares_memory(get_array(df, "b"), arr_b)
+ assert view._mgr._has_no_reference(0)
+ assert df._mgr._has_no_reference(0)
+ tm.assert_frame_equal(view, df_orig)
+ expected = DataFrame({"a": [1.5, 5.5], "b": 1})
+ tm.assert_frame_equal(df, expected)
+
+
+def test_fillna_interval_inplace_reference():
+ # Set dtype explicitly to avoid implicit cast when setting nan
+ ser = Series(
+ interval_range(start=0, end=5), name="a", dtype="interval[float64, right]"
+ )
+ ser.iloc[1] = np.nan
+
+ ser_orig = ser.copy()
+ view = ser[:]
+ ser.fillna(value=Interval(left=0, right=5), inplace=True)
+
+ assert not np.shares_memory(
+ get_array(ser, "a").left.values, get_array(view, "a").left.values
+ )
+ tm.assert_series_equal(view, ser_orig)
+
+
+def test_fillna_series_empty_arg():
+ ser = Series([1, np.nan, 2])
+ ser_orig = ser.copy()
+ result = ser.fillna({})
+ assert np.shares_memory(get_array(ser), get_array(result))
+
+ ser.iloc[0] = 100.5
+ tm.assert_series_equal(ser_orig, result)
+
+
+def test_fillna_series_empty_arg_inplace():
+ ser = Series([1, np.nan, 2])
+ arr = get_array(ser)
+ ser.fillna({}, inplace=True)
+
+ assert np.shares_memory(get_array(ser), arr)
+ assert ser._mgr._has_no_reference(0)
+
+
+def test_fillna_ea_noop_shares_memory(any_numeric_ea_and_arrow_dtype):
+ df = DataFrame({"a": [1, NA, 3], "b": 1}, dtype=any_numeric_ea_and_arrow_dtype)
+ df_orig = df.copy()
+ df2 = df.fillna(100)
+
+ assert not np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+
+ assert np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+ assert not df2._mgr._has_no_reference(1)
+ tm.assert_frame_equal(df_orig, df)
+
+ df2.iloc[0, 1] = 100
+ assert not np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+ assert df2._mgr._has_no_reference(1)
+ assert df._mgr._has_no_reference(1)
+ tm.assert_frame_equal(df_orig, df)
+
+
+def test_fillna_inplace_ea_noop_shares_memory(any_numeric_ea_and_arrow_dtype):
+ df = DataFrame({"a": [1, NA, 3], "b": 1}, dtype=any_numeric_ea_and_arrow_dtype)
+ df_orig = df.copy()
+ view = df[:]
+ df.fillna(100, inplace=True)
+ assert not np.shares_memory(get_array(df, "a"), get_array(view, "a"))
+
+ assert np.shares_memory(get_array(df, "b"), get_array(view, "b"))
+ assert not df._mgr._has_no_reference(1)
+ assert not view._mgr._has_no_reference(1)
+
+ df.iloc[0, 1] = 100
+ tm.assert_frame_equal(df_orig, view)
+
+
+def test_fillna_chained_assignment():
+ df = DataFrame({"a": [1, np.nan, 2], "b": 1})
+ df_orig = df.copy()
+ with tm.raises_chained_assignment_error():
+ df["a"].fillna(100, inplace=True)
+ tm.assert_frame_equal(df, df_orig)
+
+ with tm.raises_chained_assignment_error():
+ df[["a"]].fillna(100, inplace=True)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("func", ["interpolate", "ffill", "bfill"])
+def test_interpolate_chained_assignment(func):
+ df = DataFrame({"a": [1, np.nan, 2], "b": 1})
+ df_orig = df.copy()
+ with tm.raises_chained_assignment_error():
+ getattr(df["a"], func)(inplace=True)
+ tm.assert_frame_equal(df, df_orig)
+
+ with tm.raises_chained_assignment_error():
+ getattr(df[["a"]], func)(inplace=True)
+ tm.assert_frame_equal(df, df_orig)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_methods.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_methods.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb19132d16a072304faa16599711a7d4be4122b8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_methods.py
@@ -0,0 +1,1601 @@
+import numpy as np
+import pytest
+
+from pandas.compat import HAS_PYARROW
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Period,
+ Series,
+ Timestamp,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+from pandas.util.version import Version
+
+
+def test_copy():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_copy = df.copy()
+
+ # the deep copy by defaults takes a shallow copy of the Index
+ assert df_copy.index is not df.index
+ assert df_copy.columns is not df.columns
+ assert df_copy.index.is_(df.index)
+ assert df_copy.columns.is_(df.columns)
+
+ # the deep copy doesn't share memory
+ assert not np.shares_memory(get_array(df_copy, "a"), get_array(df, "a"))
+ assert not df_copy._mgr.blocks[0].refs.has_reference()
+ assert not df_copy._mgr.blocks[1].refs.has_reference()
+
+ assert df_copy.index is not df.index
+ assert df_copy.columns is not df.columns
+
+ # mutating copy doesn't mutate original
+ df_copy.iloc[0, 0] = 0
+ assert df.iloc[0, 0] == 1
+
+
+def test_copy_shallow():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_copy = df.copy(deep=False)
+
+ # the shallow copy also makes a shallow copy of the index
+ assert df_copy.index is not df.index
+ assert df_copy.columns is not df.columns
+ assert df_copy.index.is_(df.index)
+ assert df_copy.columns.is_(df.columns)
+
+ # the shallow copy still shares memory
+ assert np.shares_memory(get_array(df_copy, "a"), get_array(df, "a"))
+ assert df_copy._mgr.blocks[0].refs.has_reference()
+ assert df_copy._mgr.blocks[1].refs.has_reference()
+
+ # mutating shallow copy doesn't mutate original
+ df_copy.iloc[0, 0] = 0
+ assert df.iloc[0, 0] == 1
+ # mutating triggered a copy-on-write -> no longer shares memory
+ assert not np.shares_memory(get_array(df_copy, "a"), get_array(df, "a"))
+ # but still shares memory for the other columns/blocks
+ assert np.shares_memory(get_array(df_copy, "c"), get_array(df, "c"))
+
+
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
+@pytest.mark.parametrize("copy", [True, None, False])
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda df, copy: df.rename(columns=str.lower, copy=copy),
+ lambda df, copy: df.reindex(columns=["a", "c"], copy=copy),
+ lambda df, copy: df.reindex_like(df, copy=copy),
+ lambda df, copy: df.align(df, copy=copy)[0],
+ lambda df, copy: df.set_axis(["a", "b", "c"], axis="index", copy=copy),
+ lambda df, copy: df.rename_axis(index="test", copy=copy),
+ lambda df, copy: df.rename_axis(columns="test", copy=copy),
+ lambda df, copy: df.astype({"b": "int64"}, copy=copy),
+ # lambda df, copy: df.swaplevel(0, 0, copy=copy),
+ lambda df, copy: df.truncate(0, 5, copy=copy),
+ lambda df, copy: df.infer_objects(copy=copy),
+ lambda df, copy: df.to_timestamp(copy=copy),
+ lambda df, copy: df.to_period(freq="D", copy=copy),
+ lambda df, copy: df.tz_localize("US/Central", copy=copy),
+ lambda df, copy: df.tz_convert("US/Central", copy=copy),
+ lambda df, copy: df.set_flags(allows_duplicate_labels=False, copy=copy),
+ ],
+ ids=[
+ "rename",
+ "reindex",
+ "reindex_like",
+ "align",
+ "set_axis",
+ "rename_axis0",
+ "rename_axis1",
+ "astype",
+ # "swaplevel", # only series
+ "truncate",
+ "infer_objects",
+ "to_timestamp",
+ "to_period",
+ "tz_localize",
+ "tz_convert",
+ "set_flags",
+ ],
+)
+def test_methods_copy_keyword(request, method, copy):
+ index = None
+ if "to_timestamp" in request.node.callspec.id:
+ index = period_range("2012-01-01", freq="D", periods=3)
+ elif "to_period" in request.node.callspec.id:
+ index = date_range("2012-01-01", freq="D", periods=3)
+ elif "tz_localize" in request.node.callspec.id:
+ index = date_range("2012-01-01", freq="D", periods=3)
+ elif "tz_convert" in request.node.callspec.id:
+ index = date_range("2012-01-01", freq="D", periods=3, tz="Europe/Brussels")
+
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]}, index=index)
+ df2 = method(df, copy=copy)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+
+@pytest.mark.filterwarnings("ignore::DeprecationWarning")
+@pytest.mark.parametrize("copy", [True, None, False])
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda ser, copy: ser.rename(index={0: 100}, copy=copy),
+ lambda ser, copy: ser.rename(None, copy=copy),
+ lambda ser, copy: ser.reindex(index=ser.index, copy=copy),
+ lambda ser, copy: ser.reindex_like(ser, copy=copy),
+ lambda ser, copy: ser.align(ser, copy=copy)[0],
+ lambda ser, copy: ser.set_axis(["a", "b", "c"], axis="index", copy=copy),
+ lambda ser, copy: ser.rename_axis(index="test", copy=copy),
+ lambda ser, copy: ser.astype("int64", copy=copy),
+ lambda ser, copy: ser.swaplevel(0, 1, copy=copy),
+ lambda ser, copy: ser.truncate(0, 5, copy=copy),
+ lambda ser, copy: ser.infer_objects(copy=copy),
+ lambda ser, copy: ser.to_timestamp(copy=copy),
+ lambda ser, copy: ser.to_period(freq="D", copy=copy),
+ lambda ser, copy: ser.tz_localize("US/Central", copy=copy),
+ lambda ser, copy: ser.tz_convert("US/Central", copy=copy),
+ lambda ser, copy: ser.set_flags(allows_duplicate_labels=False, copy=copy),
+ ],
+ ids=[
+ "rename (dict)",
+ "rename",
+ "reindex",
+ "reindex_like",
+ "align",
+ "set_axis",
+ "rename_axis0",
+ "astype",
+ "swaplevel",
+ "truncate",
+ "infer_objects",
+ "to_timestamp",
+ "to_period",
+ "tz_localize",
+ "tz_convert",
+ "set_flags",
+ ],
+)
+def test_methods_series_copy_keyword(request, method, copy):
+ index = None
+ if "to_timestamp" in request.node.callspec.id:
+ index = period_range("2012-01-01", freq="D", periods=3)
+ elif "to_period" in request.node.callspec.id:
+ index = date_range("2012-01-01", freq="D", periods=3)
+ elif "tz_localize" in request.node.callspec.id:
+ index = date_range("2012-01-01", freq="D", periods=3)
+ elif "tz_convert" in request.node.callspec.id:
+ index = date_range("2012-01-01", freq="D", periods=3, tz="Europe/Brussels")
+ elif "swaplevel" in request.node.callspec.id:
+ index = MultiIndex.from_arrays([[1, 2, 3], [4, 5, 6]])
+
+ ser = Series([1, 2, 3], index=index)
+ ser2 = method(ser, copy=copy)
+ assert np.shares_memory(get_array(ser2), get_array(ser))
+
+
+# -----------------------------------------------------------------------------
+# DataFrame methods returning new DataFrame using shallow copy
+
+
+def test_reset_index():
+ # Case: resetting the index (i.e. adding a new column) + mutating the
+ # resulting dataframe
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]}, index=[10, 11, 12]
+ )
+ df_orig = df.copy()
+ df2 = df.reset_index()
+ df2._mgr._verify_integrity()
+
+ # still shares memory (df2 is a shallow copy)
+ assert np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ assert np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ # mutating df2 triggers a copy-on-write for that column / block
+ df2.iloc[0, 2] = 0
+ assert not np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ assert np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("index", [pd.RangeIndex(0, 2), Index([1, 2])])
+def test_reset_index_series_drop(index):
+ ser = Series([1, 2], index=index)
+ ser_orig = ser.copy()
+ ser2 = ser.reset_index(drop=True)
+ assert np.shares_memory(get_array(ser), get_array(ser2))
+ assert not ser._mgr._has_no_reference(0)
+
+ ser2.iloc[0] = 100
+ tm.assert_series_equal(ser, ser_orig)
+
+
+def test_groupby_column_index_in_references():
+ df = DataFrame(
+ {"A": ["a", "b", "c", "d"], "B": [1, 2, 3, 4], "C": ["a", "a", "b", "b"]}
+ )
+ df = df.set_index("A")
+ key = df["C"]
+ result = df.groupby(key, observed=True).sum()
+ expected = df.groupby("C", observed=True).sum()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_modify_series():
+ # https://github.com/pandas-dev/pandas/issues/63219
+ # Modifying a Series after using it to groupby should not impact
+ # the groupby operation.
+ ser = Series([1, 2, 1])
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ gb = df.groupby(ser)
+ ser.iloc[0] = 100
+ result = gb.sum()
+ expected = DataFrame({"a": [4, 2], "b": [10, 5]}, index=[1, 2])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_rename_columns():
+ # Case: renaming columns returns a new dataframe
+ # + afterwards modifying the result
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.rename(columns=str.upper)
+
+ assert np.shares_memory(get_array(df2, "A"), get_array(df, "a"))
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "A"), get_array(df, "a"))
+ assert np.shares_memory(get_array(df2, "C"), get_array(df, "c"))
+ expected = DataFrame({"A": [0, 2, 3], "B": [4, 5, 6], "C": [0.1, 0.2, 0.3]})
+ tm.assert_frame_equal(df2, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_rename_columns_modify_parent():
+ # Case: renaming columns returns a new dataframe
+ # + afterwards modifying the original (parent) dataframe
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df2 = df.rename(columns=str.upper)
+ df2_orig = df2.copy()
+
+ assert np.shares_memory(get_array(df2, "A"), get_array(df, "a"))
+ df.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "A"), get_array(df, "a"))
+ assert np.shares_memory(get_array(df2, "C"), get_array(df, "c"))
+ expected = DataFrame({"a": [0, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ tm.assert_frame_equal(df, expected)
+ tm.assert_frame_equal(df2, df2_orig)
+
+
+def test_pipe():
+ df = DataFrame({"a": [1, 2, 3], "b": 1.5})
+ df_orig = df.copy()
+
+ def testfunc(df):
+ return df
+
+ df2 = df.pipe(testfunc)
+
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ # mutating df2 triggers a copy-on-write for that column
+ df2.iloc[0, 0] = 0
+ tm.assert_frame_equal(df, df_orig)
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+
+
+def test_pipe_modify_df():
+ df = DataFrame({"a": [1, 2, 3], "b": 1.5})
+ df_orig = df.copy()
+
+ def testfunc(df):
+ df.iloc[0, 0] = 100
+ return df
+
+ df2 = df.pipe(testfunc)
+
+ assert np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+
+ tm.assert_frame_equal(df, df_orig)
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+
+
+def test_reindex_columns():
+ # Case: reindexing the column returns a new dataframe
+ # + afterwards modifying the result
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.reindex(columns=["a", "c"])
+
+ # still shares memory (df2 is a shallow copy)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ # mutating df2 triggers a copy-on-write for that column
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ lambda idx: idx,
+ lambda idx: idx.view(),
+ lambda idx: idx.copy(),
+ lambda idx: list(idx),
+ ],
+ ids=["identical", "view", "copy", "values"],
+)
+def test_reindex_rows(index):
+ # Case: reindexing the rows with an index that matches the current index
+ # can use a shallow copy
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.reindex(index=index(df.index))
+
+ # still shares memory (df2 is a shallow copy)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ # mutating df2 triggers a copy-on-write for that column
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_drop_on_column():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.drop(columns="a")
+ df2._mgr._verify_integrity()
+
+ assert np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ assert np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ assert np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_select_dtypes():
+ # Case: selecting columns using `select_dtypes()` returns a new dataframe
+ # + afterwards modifying the result
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.select_dtypes("int64")
+ df2._mgr._verify_integrity()
+
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ # mutating df2 triggers a copy-on-write for that column/block
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "filter_kwargs", [{"items": ["a"]}, {"like": "a"}, {"regex": "a"}]
+)
+def test_filter(filter_kwargs):
+ # Case: selecting columns using `filter()` returns a new dataframe
+ # + afterwards modifying the result
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.filter(**filter_kwargs)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ # mutating df2 triggers a copy-on-write for that column/block
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_shift_no_op():
+ df = DataFrame(
+ [[1, 2], [3, 4], [5, 6]],
+ index=date_range("2020-01-01", "2020-01-03"),
+ columns=["a", "b"],
+ )
+ df_orig = df.copy()
+ df2 = df.shift(periods=0)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert df2.index is not df.index
+ assert df2.columns is not df.columns
+
+ df.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+ assert np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+ tm.assert_frame_equal(df2, df_orig)
+
+
+def test_shift_index():
+ df = DataFrame(
+ [[1, 2], [3, 4], [5, 6]],
+ index=date_range("2020-01-01", "2020-01-03"),
+ columns=["a", "b"],
+ )
+ df2 = df.shift(periods=1, axis=0)
+
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert df2.index is not df.index
+ assert df2.columns is not df.columns
+
+
+def test_shift_rows_freq():
+ df = DataFrame(
+ [[1, 2], [3, 4], [5, 6]],
+ index=date_range("2020-01-01", "2020-01-03"),
+ columns=["a", "b"],
+ )
+ df_orig = df.copy()
+ df_orig.index = date_range("2020-01-02", "2020-01-04")
+ df2 = df.shift(periods=1, freq="1D")
+
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ df.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+ tm.assert_frame_equal(df2, df_orig)
+
+
+def test_shift_columns():
+ df = DataFrame(
+ [[1, 2], [3, 4], [5, 6]], columns=date_range("2020-01-01", "2020-01-02")
+ )
+ df2 = df.shift(periods=1, axis=1)
+
+ assert np.shares_memory(get_array(df2, "2020-01-02"), get_array(df, "2020-01-01"))
+ df.iloc[0, 0] = 0
+ assert not np.shares_memory(
+ get_array(df2, "2020-01-02"), get_array(df, "2020-01-01")
+ )
+ expected = DataFrame(
+ [[np.nan, 1], [np.nan, 3], [np.nan, 5]],
+ columns=date_range("2020-01-01", "2020-01-02"),
+ )
+ tm.assert_frame_equal(df2, expected)
+
+
+def test_pop():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ view_original = df[:]
+ result = df.pop("a")
+
+ assert np.shares_memory(result.values, get_array(view_original, "a"))
+ assert np.shares_memory(get_array(df, "b"), get_array(view_original, "b"))
+
+ result.iloc[0] = 0
+ assert not np.shares_memory(result.values, get_array(view_original, "a"))
+ df.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df, "b"), get_array(view_original, "b"))
+ tm.assert_frame_equal(view_original, df_orig)
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda x, y: x.align(y),
+ lambda x, y: x.align(y.a, axis=0),
+ lambda x, y: x.align(y.a.iloc[slice(0, 1)], axis=1),
+ ],
+)
+def test_align_frame(func):
+ df = DataFrame({"a": [1, 2, 3], "b": "a"})
+ df_orig = df.copy()
+ df_changed = df[["b", "a"]].copy()
+ df2, _ = func(df, df_changed)
+
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_align_series():
+ ser = Series([1, 2])
+ ser_orig = ser.copy()
+ ser_other = ser.copy()
+ ser2, ser_other_result = ser.align(ser_other)
+
+ assert np.shares_memory(ser2.values, ser.values)
+ assert np.shares_memory(ser_other_result.values, ser_other.values)
+ ser2.iloc[0] = 0
+ ser_other_result.iloc[0] = 0
+ assert not np.shares_memory(ser2.values, ser.values)
+ assert not np.shares_memory(ser_other_result.values, ser_other.values)
+ tm.assert_series_equal(ser, ser_orig)
+ tm.assert_series_equal(ser_other, ser_orig)
+
+
+def test_align_copy_false():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ df_orig = df.copy()
+ df2, df3 = df.align(df)
+
+ assert np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+ assert np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+
+ df2.loc[0, "a"] = 0
+ tm.assert_frame_equal(df, df_orig) # Original is unchanged
+
+ df3.loc[0, "a"] = 0
+ tm.assert_frame_equal(df, df_orig) # Original is unchanged
+
+
+def test_align_with_series_copy_false():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ ser = Series([1, 2, 3], name="x")
+ ser_orig = ser.copy()
+ df_orig = df.copy()
+ df2, ser2 = df.align(ser, axis=0)
+
+ assert np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+ assert np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+ assert np.shares_memory(get_array(ser, "x"), get_array(ser2, "x"))
+
+ df2.loc[0, "a"] = 0
+ tm.assert_frame_equal(df, df_orig) # Original is unchanged
+
+ ser2.loc[0] = 0
+ tm.assert_series_equal(ser, ser_orig) # Original is unchanged
+
+
+def test_to_frame():
+ # Case: converting a Series to a DataFrame with to_frame
+ ser = Series([1, 2, 3])
+ ser_orig = ser.copy()
+
+ df = ser[:].to_frame()
+
+ # currently this always returns a "view"
+ assert np.shares_memory(ser.values, get_array(df, 0))
+
+ df.iloc[0, 0] = 0
+
+ # mutating df triggers a copy-on-write for that column
+ assert not np.shares_memory(ser.values, get_array(df, 0))
+ tm.assert_series_equal(ser, ser_orig)
+
+ # modify original series -> don't modify dataframe
+ df = ser[:].to_frame()
+ ser.iloc[0] = 0
+
+ tm.assert_frame_equal(df, ser_orig.to_frame())
+
+ df = ser.to_frame()
+ assert df.index is not ser.index
+
+
+@pytest.mark.parametrize(
+ "method, idx",
+ [
+ (lambda df: df.copy(deep=False).copy(deep=False), 0),
+ (lambda df: df.reset_index().reset_index(), 2),
+ (lambda df: df.rename(columns=str.upper).rename(columns=str.lower), 0),
+ (lambda df: df.copy(deep=False).select_dtypes(include="number"), 0),
+ ],
+ ids=["shallow-copy", "reset_index", "rename", "select_dtypes"],
+)
+def test_chained_methods(method, idx):
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+
+ # modify df2 -> don't modify df
+ df2 = method(df)
+ df2.iloc[0, idx] = 0
+ tm.assert_frame_equal(df, df_orig)
+
+ # modify df -> don't modify df2
+ df2 = method(df)
+ df.iloc[0, 0] = 0
+ tm.assert_frame_equal(df2.iloc[:, idx:], df_orig)
+
+
+@pytest.mark.parametrize("obj", [Series([1, 2], name="a"), DataFrame({"a": [1, 2]})])
+def test_to_timestamp(obj):
+ obj.index = Index([Period("2012-1-1", freq="D"), Period("2012-1-2", freq="D")])
+
+ obj_orig = obj.copy()
+ obj2 = obj.to_timestamp()
+
+ assert np.shares_memory(get_array(obj2, "a"), get_array(obj, "a"))
+
+ # mutating obj2 triggers a copy-on-write for that column / block
+ obj2.iloc[0] = 0
+ assert not np.shares_memory(get_array(obj2, "a"), get_array(obj, "a"))
+ tm.assert_equal(obj, obj_orig)
+
+
+@pytest.mark.parametrize("obj", [Series([1, 2], name="a"), DataFrame({"a": [1, 2]})])
+def test_to_period(obj):
+ obj.index = Index([Timestamp("2019-12-31"), Timestamp("2020-12-31")])
+
+ obj_orig = obj.copy()
+ obj2 = obj.to_period(freq="Y")
+
+ assert np.shares_memory(get_array(obj2, "a"), get_array(obj, "a"))
+
+ # mutating obj2 triggers a copy-on-write for that column / block
+ obj2.iloc[0] = 0
+ assert not np.shares_memory(get_array(obj2, "a"), get_array(obj, "a"))
+ tm.assert_equal(obj, obj_orig)
+
+
+def test_set_index():
+ # GH 49473
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.set_index("a")
+
+ assert np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+
+ # mutating df2 triggers a copy-on-write for that column / block
+ df2.iloc[0, 1] = 0
+ assert not np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_set_index_mutating_parent_does_not_mutate_index():
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+ result = df.set_index("a")
+ expected = result.copy()
+
+ df.iloc[0, 0] = 100
+ tm.assert_frame_equal(result, expected)
+
+
+def test_add_prefix():
+ # GH 49473
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.add_prefix("CoW_")
+
+ assert np.shares_memory(get_array(df2, "CoW_a"), get_array(df, "a"))
+ df2.iloc[0, 0] = 0
+
+ assert not np.shares_memory(get_array(df2, "CoW_a"), get_array(df, "a"))
+
+ assert np.shares_memory(get_array(df2, "CoW_c"), get_array(df, "c"))
+ expected = DataFrame(
+ {"CoW_a": [0, 2, 3], "CoW_b": [4, 5, 6], "CoW_c": [0.1, 0.2, 0.3]}
+ )
+ tm.assert_frame_equal(df2, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_add_suffix():
+ # GH 49473
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.add_suffix("_CoW")
+ assert np.shares_memory(get_array(df2, "a_CoW"), get_array(df, "a"))
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a_CoW"), get_array(df, "a"))
+ assert np.shares_memory(get_array(df2, "c_CoW"), get_array(df, "c"))
+ expected = DataFrame(
+ {"a_CoW": [0, 2, 3], "b_CoW": [4, 5, 6], "c_CoW": [0.1, 0.2, 0.3]}
+ )
+ tm.assert_frame_equal(df2, expected)
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("axis, val", [(0, 5.5), (1, np.nan)])
+def test_dropna(axis, val):
+ df = DataFrame({"a": [1, 2, 3], "b": [4, val, 6], "c": "d"})
+ df_orig = df.copy()
+ df2 = df.dropna(axis=axis)
+
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("val", [5, 5.5])
+def test_dropna_series(val):
+ ser = Series([1, val, 4])
+ ser_orig = ser.copy()
+ ser2 = ser.dropna()
+ assert np.shares_memory(ser2.values, ser.values)
+
+ ser2.iloc[0] = 0
+ assert not np.shares_memory(ser2.values, ser.values)
+ tm.assert_series_equal(ser, ser_orig)
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda df: df.head(),
+ lambda df: df.head(2),
+ lambda df: df.tail(),
+ lambda df: df.tail(3),
+ ],
+)
+def test_head_tail(method):
+ df = DataFrame({"a": [1, 2, 3], "b": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = method(df)
+ df2._mgr._verify_integrity()
+
+ # We are explicitly deviating for CoW here to make an eager copy (avoids
+ # tracking references for very cheap ops)
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert not np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+
+ # modify df2 to trigger CoW for that block
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_infer_objects(using_infer_string):
+ df = DataFrame(
+ {"a": [1, 2], "b": Series(["x", "y"], dtype=object), "c": 1, "d": "x"}
+ )
+ df_orig = df.copy()
+ df2 = df.infer_objects()
+
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ if using_infer_string and HAS_PYARROW:
+ assert not tm.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ else:
+ assert np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+
+ df2.iloc[0, 0] = 0
+ df2.iloc[0, 1] = "d"
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert not np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_infer_objects_no_reference(using_infer_string):
+ df = DataFrame(
+ {
+ "a": [1, 2],
+ "b": Series(["x", "y"], dtype=object),
+ "c": 1,
+ "d": Series(
+ [Timestamp("2019-12-31"), Timestamp("2020-12-31")], dtype="object"
+ ),
+ "e": Series(["z", "w"], dtype=object),
+ }
+ )
+ df = df.infer_objects()
+
+ arr_a = get_array(df, "a")
+ arr_b = get_array(df, "b")
+ arr_d = get_array(df, "d")
+
+ df.iloc[0, 0] = 0
+ df.iloc[0, 1] = "d"
+ df.iloc[0, 3] = Timestamp("2018-12-31")
+ assert np.shares_memory(arr_a, get_array(df, "a"))
+ if using_infer_string and HAS_PYARROW:
+ # note that the underlying memory of arr_b has been copied anyway
+ # because of the assignment, but the EA is updated inplace so still
+ # appears the share memory
+ assert tm.shares_memory(arr_b, get_array(df, "b"))
+ else:
+ # TODO(CoW): Block splitting causes references here
+ assert not np.shares_memory(arr_b, get_array(df, "b"))
+ assert np.shares_memory(arr_d, get_array(df, "d"))
+
+
+def test_infer_objects_reference():
+ df = DataFrame(
+ {
+ "a": [1, 2],
+ "b": Series(["x", "y"], dtype=object),
+ "c": 1,
+ "d": Series(
+ [Timestamp("2019-12-31"), Timestamp("2020-12-31")], dtype="object"
+ ),
+ }
+ )
+ view = df[:] # noqa: F841
+ df = df.infer_objects()
+
+ arr_a = get_array(df, "a")
+ arr_b = get_array(df, "b")
+ arr_d = get_array(df, "d")
+
+ df.iloc[0, 0] = 0
+ df.iloc[0, 1] = "d"
+ df.iloc[0, 3] = Timestamp("2018-12-31")
+ assert not np.shares_memory(arr_a, get_array(df, "a"))
+ assert not np.shares_memory(arr_b, get_array(df, "b"))
+ assert np.shares_memory(arr_d, get_array(df, "d"))
+
+
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"before": "a", "after": "b", "axis": 1},
+ {"before": 0, "after": 1, "axis": 0},
+ ],
+)
+def test_truncate(kwargs):
+ df = DataFrame({"a": [1, 2, 3], "b": 1, "c": 2})
+ df_orig = df.copy()
+ df2 = df.truncate(**kwargs)
+ df2._mgr._verify_integrity()
+
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("method", ["assign", "drop_duplicates"])
+def test_assign_drop_duplicates(method):
+ df = DataFrame({"a": [1, 2, 3]})
+ df_orig = df.copy()
+ df2 = getattr(df, method)()
+ df2._mgr._verify_integrity()
+
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("obj", [Series([1, 2]), DataFrame({"a": [1, 2]})])
+def test_take(obj):
+ # Check that no copy is made when we take all rows in original order
+ obj_orig = obj.copy()
+ obj2 = obj.take([0, 1])
+ assert np.shares_memory(obj2.values, obj.values)
+
+ obj2.iloc[0] = 0
+ assert not np.shares_memory(obj2.values, obj.values)
+ tm.assert_equal(obj, obj_orig)
+
+
+@pytest.mark.parametrize("obj", [Series([1, 2]), DataFrame({"a": [1, 2]})])
+def test_between_time(obj):
+ obj.index = date_range("2018-04-09", periods=2, freq="1D20min")
+ obj_orig = obj.copy()
+ obj2 = obj.between_time("0:00", "1:00")
+ assert np.shares_memory(obj2.values, obj.values)
+
+ obj2.iloc[0] = 0
+ assert not np.shares_memory(obj2.values, obj.values)
+ tm.assert_equal(obj, obj_orig)
+
+
+def test_reindex_like():
+ df = DataFrame({"a": [1, 2], "b": "a"})
+ other = DataFrame({"b": "a", "a": [1, 2]})
+
+ df_orig = df.copy()
+ df2 = df.reindex_like(other)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ df2.iloc[0, 1] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_sort_index():
+ # GH 49473
+ ser = Series([1, 2, 3])
+ ser_orig = ser.copy()
+ ser2 = ser.sort_index()
+ assert np.shares_memory(ser.values, ser2.values)
+
+ # mutating ser triggers a copy-on-write for the column / block
+ ser2.iloc[0] = 0
+ assert not np.shares_memory(ser2.values, ser.values)
+ tm.assert_series_equal(ser, ser_orig)
+
+
+@pytest.mark.parametrize(
+ "obj, kwargs",
+ [(Series([1, 2, 3], name="a"), {}), (DataFrame({"a": [1, 2, 3]}), {"by": "a"})],
+)
+def test_sort_values(obj, kwargs):
+ obj_orig = obj.copy()
+ obj2 = obj.sort_values(**kwargs)
+ assert np.shares_memory(get_array(obj2, "a"), get_array(obj, "a"))
+
+ # mutating df triggers a copy-on-write for the column / block
+ obj2.iloc[0] = 0
+ assert not np.shares_memory(get_array(obj2, "a"), get_array(obj, "a"))
+ tm.assert_equal(obj, obj_orig)
+
+
+@pytest.mark.parametrize(
+ "obj, kwargs",
+ [(Series([1, 2, 3], name="a"), {}), (DataFrame({"a": [1, 2, 3]}), {"by": "a"})],
+)
+def test_sort_values_inplace(obj, kwargs):
+ obj_orig = obj.copy()
+ view = obj[:]
+ obj.sort_values(inplace=True, **kwargs)
+
+ assert np.shares_memory(get_array(obj, "a"), get_array(view, "a"))
+
+ # mutating obj triggers a copy-on-write for the column / block
+ obj.iloc[0] = 0
+ assert not np.shares_memory(get_array(obj, "a"), get_array(view, "a"))
+ tm.assert_equal(view, obj_orig)
+
+
+@pytest.mark.parametrize("decimals", [-1, 0, 1])
+def test_round(decimals):
+ df = DataFrame({"a": [1, 2], "b": "c"})
+ df_orig = df.copy()
+ df2 = df.round(decimals=decimals)
+
+ assert tm.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ # TODO: Make inplace by using out parameter of ndarray.round?
+ if decimals >= 0 and Version(np.__version__) < Version("2.4.0.dev0"):
+ # Ensure lazy copy if no-op
+ # TODO: Cannot rely on Numpy returning view after version 2.3
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ else:
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert df2.index is not df.index
+ assert df2.columns is not df.columns
+
+ df2.iloc[0, 1] = "d"
+ df2.iloc[0, 0] = 4
+ assert not np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_reorder_levels():
+ index = MultiIndex.from_tuples(
+ [(1, 1), (1, 2), (2, 1), (2, 2)], names=["one", "two"]
+ )
+ df = DataFrame({"a": [1, 2, 3, 4]}, index=index)
+ df_orig = df.copy()
+ df2 = df.reorder_levels(order=["two", "one"])
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_series_reorder_levels():
+ index = MultiIndex.from_tuples(
+ [(1, 1), (1, 2), (2, 1), (2, 2)], names=["one", "two"]
+ )
+ ser = Series([1, 2, 3, 4], index=index)
+ ser_orig = ser.copy()
+ ser2 = ser.reorder_levels(order=["two", "one"])
+ assert np.shares_memory(ser2.values, ser.values)
+
+ ser2.iloc[0] = 0
+ assert not np.shares_memory(ser2.values, ser.values)
+ tm.assert_series_equal(ser, ser_orig)
+
+
+@pytest.mark.parametrize("obj", [Series([1, 2, 3]), DataFrame({"a": [1, 2, 3]})])
+def test_swaplevel(obj):
+ index = MultiIndex.from_tuples([(1, 1), (1, 2), (2, 1)], names=["one", "two"])
+ obj.index = index
+ obj_orig = obj.copy()
+ obj2 = obj.swaplevel()
+ assert np.shares_memory(obj2.values, obj.values)
+
+ obj2.iloc[0] = 0
+ assert not np.shares_memory(obj2.values, obj.values)
+ tm.assert_equal(obj, obj_orig)
+
+
+def test_frame_set_axis():
+ # GH 49473
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+ df2 = df.set_axis(["a", "b", "c"], axis="index")
+
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ # mutating df2 triggers a copy-on-write for that column / block
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_series_set_axis():
+ # GH 49473
+ ser = Series([1, 2, 3])
+ ser_orig = ser.copy()
+ ser2 = ser.set_axis(["a", "b", "c"], axis="index")
+ assert np.shares_memory(ser, ser2)
+
+ # mutating ser triggers a copy-on-write for the column / block
+ ser2.iloc[0] = 0
+ assert not np.shares_memory(ser2, ser)
+ tm.assert_series_equal(ser, ser_orig)
+
+
+def test_set_flags():
+ ser = Series([1, 2, 3])
+ ser_orig = ser.copy()
+ ser2 = ser.set_flags(allows_duplicate_labels=False)
+
+ assert np.shares_memory(ser, ser2)
+
+ # mutating ser triggers a copy-on-write for the column / block
+ ser2.iloc[0] = 0
+ assert not np.shares_memory(ser2, ser)
+ tm.assert_series_equal(ser, ser_orig)
+
+
+@pytest.mark.parametrize("kwargs", [{"mapper": "test"}, {"index": "test"}])
+def test_rename_axis(kwargs):
+ df = DataFrame({"a": [1, 2, 3, 4]}, index=Index([1, 2, 3, 4], name="a"))
+ df_orig = df.copy()
+ df2 = df.rename_axis(**kwargs)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ df2.iloc[0, 0] = 0
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize(
+ "func, tz", [("tz_convert", "Europe/Berlin"), ("tz_localize", None)]
+)
+def test_tz_convert_localize(func, tz):
+ # GH 49473
+ ser = Series(
+ [1, 2], index=date_range(start="2014-08-01 09:00", freq="h", periods=2, tz=tz)
+ )
+ ser_orig = ser.copy()
+ ser2 = getattr(ser, func)("US/Central")
+ assert np.shares_memory(ser.values, ser2.values)
+
+ # mutating ser triggers a copy-on-write for the column / block
+ ser2.iloc[0] = 0
+ assert not np.shares_memory(ser2.values, ser.values)
+ tm.assert_series_equal(ser, ser_orig)
+
+
+def test_droplevel():
+ # GH 49473
+ index = MultiIndex.from_tuples([(1, 1), (1, 2), (2, 1)], names=["one", "two"])
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]}, index=index)
+ df_orig = df.copy()
+ df2 = df.droplevel(0)
+
+ assert np.shares_memory(get_array(df2, "c"), get_array(df, "c"))
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ # mutating df2 triggers a copy-on-write for that column / block
+ df2.iloc[0, 0] = 0
+
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert np.shares_memory(get_array(df2, "b"), get_array(df, "b"))
+
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_squeeze():
+ df = DataFrame({"a": [1, 2, 3]})
+ df_orig = df.copy()
+ series = df.squeeze()
+
+ # Should share memory regardless of CoW since squeeze is just an iloc
+ assert np.shares_memory(series.values, get_array(df, "a"))
+
+ # mutating squeezed df triggers a copy-on-write for that column/block
+ series.iloc[0] = 0
+ assert not np.shares_memory(series.values, get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_items():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})
+ df_orig = df.copy()
+
+ # Test this twice, since the second time, the item cache will be
+ # triggered, and we want to make sure it still works then.
+ for i in range(2):
+ for name, ser in df.items():
+ assert np.shares_memory(get_array(ser, name), get_array(df, name))
+
+ # mutating df triggers a copy-on-write for that column / block
+ ser.iloc[0] = 0
+
+ assert not np.shares_memory(get_array(ser, name), get_array(df, name))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("dtype", ["int64", "Int64"])
+def test_putmask(dtype):
+ df = DataFrame({"a": [1, 2], "b": 1, "c": 2}, dtype=dtype)
+ view = df[:]
+ df_orig = df.copy()
+ df[df == df] = 5
+
+ assert not np.shares_memory(get_array(view, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(view, df_orig)
+
+
+@pytest.mark.parametrize("dtype", ["int64", "Int64"])
+def test_putmask_no_reference(dtype):
+ df = DataFrame({"a": [1, 2], "b": 1, "c": 2}, dtype=dtype)
+ arr_a = get_array(df, "a")
+ df[df == df] = 5
+ assert np.shares_memory(arr_a, get_array(df, "a"))
+
+
+@pytest.mark.parametrize("dtype", ["float64", "Float64"])
+def test_putmask_aligns_rhs_no_reference(dtype):
+ df = DataFrame({"a": [1.5, 2], "b": 1.5}, dtype=dtype)
+ arr_a = get_array(df, "a")
+ df[df == df] = DataFrame({"a": [5.5, 5]})
+ assert np.shares_memory(arr_a, get_array(df, "a"))
+
+
+@pytest.mark.parametrize("val, exp, raises", [(5.5, True, True), (5, False, False)])
+def test_putmask_dont_copy_some_blocks(val, exp, raises: bool):
+ df = DataFrame({"a": [1, 2], "b": 1, "c": 1.5})
+ view = df[:]
+ df_orig = df.copy()
+ indexer = DataFrame(
+ [[True, False, False], [True, False, False]], columns=list("abc")
+ )
+ if raises:
+ with pytest.raises(TypeError, match="Invalid value"):
+ df[indexer] = val
+ else:
+ df[indexer] = val
+ assert not np.shares_memory(get_array(view, "a"), get_array(df, "a"))
+ # TODO(CoW): Could split blocks to avoid copying the whole block
+ assert np.shares_memory(get_array(view, "b"), get_array(df, "b")) is exp
+ assert np.shares_memory(get_array(view, "c"), get_array(df, "c"))
+ assert df._mgr._has_no_reference(1) is not exp
+ assert not df._mgr._has_no_reference(2)
+ tm.assert_frame_equal(view, df_orig)
+
+
+@pytest.mark.parametrize("dtype", ["int64", "Int64"])
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda ser: ser.where(ser > 0, 10),
+ lambda ser: ser.mask(ser <= 0, 10),
+ ],
+)
+def test_where_mask_noop(dtype, func):
+ ser = Series([1, 2, 3], dtype=dtype)
+ ser_orig = ser.copy()
+
+ result = func(ser)
+ assert np.shares_memory(get_array(ser), get_array(result))
+ assert result.index is not ser.index
+
+ result.iloc[0] = 10
+ assert not np.shares_memory(get_array(ser), get_array(result))
+ tm.assert_series_equal(ser, ser_orig)
+
+
+@pytest.mark.parametrize("dtype", ["int64", "Int64"])
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda ser: ser.where(ser < 0, 10),
+ lambda ser: ser.mask(ser >= 0, 10),
+ ],
+)
+def test_where_mask(dtype, func):
+ ser = Series([1, 2, 3], dtype=dtype)
+ ser_orig = ser.copy()
+
+ result = func(ser)
+
+ assert not np.shares_memory(get_array(ser), get_array(result))
+ assert result.index is not ser.index
+ tm.assert_series_equal(ser, ser_orig)
+
+
+@pytest.mark.parametrize("dtype, val", [("int64", 10.5), ("Int64", 10)])
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda df, val: df.where(df < 0, val),
+ lambda df, val: df.mask(df >= 0, val),
+ ],
+)
+def test_where_mask_noop_on_single_column(dtype, val, func):
+ df = DataFrame({"a": [1, 2, 3], "b": [-4, -5, -6]}, dtype=dtype)
+ df_orig = df.copy()
+
+ result = func(df, val)
+ assert np.shares_memory(get_array(df, "b"), get_array(result, "b"))
+ assert not np.shares_memory(get_array(df, "a"), get_array(result, "a"))
+
+ result.iloc[0, 1] = 10
+ assert not np.shares_memory(get_array(df, "b"), get_array(result, "b"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("func", ["mask", "where"])
+def test_chained_where_mask(func):
+ df = DataFrame({"a": [1, 4, 2], "b": 1})
+ df_orig = df.copy()
+ with tm.raises_chained_assignment_error():
+ getattr(df["a"], func)(df["a"] > 2, 5, inplace=True)
+ tm.assert_frame_equal(df, df_orig)
+
+ with tm.raises_chained_assignment_error():
+ getattr(df[["a"]], func)(df["a"] > 2, 5, inplace=True)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_asfreq_noop():
+ df = DataFrame(
+ {"a": [0.0, None, 2.0, 3.0]},
+ index=date_range("1/1/2000", periods=4, freq="min"),
+ )
+ df_orig = df.copy()
+ df2 = df.asfreq(freq="min")
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+ # mutating df2 triggers a copy-on-write for that column / block
+ df2.iloc[0, 0] = 0
+
+ assert not np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_iterrows():
+ df = DataFrame({"a": 0, "b": 1}, index=[1, 2, 3])
+ df_orig = df.copy()
+
+ for _, sub in df.iterrows():
+ sub.iloc[0] = 100
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_interpolate_creates_copy():
+ # GH#51126
+ df = DataFrame({"a": [1.5, np.nan, 3]})
+ view = df[:]
+ expected = df.copy()
+
+ df.ffill(inplace=True)
+ df.iloc[0, 0] = 100.5
+ tm.assert_frame_equal(view, expected)
+
+
+def test_isetitem():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})
+ df_orig = df.copy()
+ df2 = df.copy(deep=False) # Trigger a CoW
+ df2.isetitem(1, np.array([-1, -2, -3])) # This is inplace
+ assert np.shares_memory(get_array(df, "c"), get_array(df2, "c"))
+ assert np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+
+ df2.loc[0, "a"] = 0
+ tm.assert_frame_equal(df, df_orig) # Original is unchanged
+ assert np.shares_memory(get_array(df, "c"), get_array(df2, "c"))
+
+
+@pytest.mark.parametrize(
+ "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"]
+)
+def test_isetitem_series(dtype):
+ df = DataFrame({"a": [1, 2, 3], "b": np.array([4, 5, 6], dtype=dtype)})
+ ser = Series([7, 8, 9])
+ ser_orig = ser.copy()
+ df.isetitem(0, ser)
+
+ assert np.shares_memory(get_array(df, "a"), get_array(ser))
+ assert not df._mgr._has_no_reference(0)
+
+ # mutating dataframe doesn't update series
+ df.loc[0, "a"] = 0
+ tm.assert_series_equal(ser, ser_orig)
+
+ # mutating series doesn't update dataframe
+ df = DataFrame({"a": [1, 2, 3], "b": np.array([4, 5, 6], dtype=dtype)})
+ ser = Series([7, 8, 9])
+ df.isetitem(0, ser)
+
+ ser.loc[0] = 0
+ expected = DataFrame({"a": [7, 8, 9], "b": np.array([4, 5, 6], dtype=dtype)})
+ tm.assert_frame_equal(df, expected)
+
+
+def test_isetitem_frame():
+ df = DataFrame({"a": [1, 2, 3], "b": 1, "c": 2})
+ rhs = DataFrame({"a": [4, 5, 6], "b": 2})
+ df.isetitem([0, 1], rhs)
+ assert np.shares_memory(get_array(df, "a"), get_array(rhs, "a"))
+ assert np.shares_memory(get_array(df, "b"), get_array(rhs, "b"))
+ assert not df._mgr._has_no_reference(0)
+ expected = df.copy()
+ rhs.iloc[0, 0] = 100
+ rhs.iloc[0, 1] = 100
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.parametrize("key", ["a", ["a"]])
+def test_get(key):
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ df_orig = df.copy()
+
+ result = df.get(key)
+
+ assert np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+ result.iloc[0] = 0
+ assert not np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("axis, key", [(0, 0), (1, "a")])
+@pytest.mark.parametrize(
+ "dtype", ["int64", "float64"], ids=["single-block", "mixed-block"]
+)
+def test_xs(axis, key, dtype):
+ single_block = dtype == "int64"
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [4, 5, 6], "c": np.array([7, 8, 9], dtype=dtype)}
+ )
+ df_orig = df.copy()
+
+ result = df.xs(key, axis=axis)
+
+ if axis == 1 or single_block:
+ assert np.shares_memory(get_array(df, "a"), get_array(result))
+ else:
+ assert result._mgr._has_no_reference(0)
+ if axis == 0:
+ assert result.index is not df.columns
+ else:
+ assert result.index is not df.index
+
+ result.iloc[0] = 0
+ tm.assert_frame_equal(df, df_orig)
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+@pytest.mark.parametrize("key, level", [("l1", 0), (2, 1)])
+def test_xs_multiindex(key, level, axis):
+ arr = np.arange(18).reshape(6, 3)
+ index = MultiIndex.from_product([["l1", "l2"], [1, 2, 3]], names=["lev1", "lev2"])
+ df = DataFrame(arr, index=index, columns=list("abc"))
+ if axis == 1:
+ df = df.transpose().copy()
+ df_orig = df.copy()
+
+ result = df.xs(key, level=level, axis=axis)
+
+ if level == 0:
+ assert np.shares_memory(
+ get_array(df, df.columns[0]), get_array(result, result.columns[0])
+ )
+ assert result.index is not df.index
+ assert result.columns is not df.columns
+
+ result.iloc[0, 0] = 0
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_update_frame():
+ df1 = DataFrame({"a": [1.0, 2.0, 3.0], "b": [4.0, 5.0, 6.0]})
+ df2 = DataFrame({"b": [100.0]}, index=[1])
+ df1_orig = df1.copy()
+ view = df1[:]
+ df1.update(df2)
+
+ expected = DataFrame({"a": [1.0, 2.0, 3.0], "b": [4.0, 100.0, 6.0]})
+ tm.assert_frame_equal(df1, expected)
+ # df1 is updated, but its view not
+ tm.assert_frame_equal(view, df1_orig)
+ assert np.shares_memory(get_array(df1, "a"), get_array(view, "a"))
+ assert not np.shares_memory(get_array(df1, "b"), get_array(view, "b"))
+
+
+def test_update_series():
+ ser1 = Series([1.0, 2.0, 3.0])
+ ser2 = Series([100.0], index=[1])
+ ser1_orig = ser1.copy()
+ view = ser1[:]
+
+ ser1.update(ser2)
+
+ expected = Series([1.0, 100.0, 3.0])
+ tm.assert_series_equal(ser1, expected)
+ # ser1 is updated, but its view not
+ tm.assert_series_equal(view, ser1_orig)
+
+
+def test_update_chained_assignment():
+ df = DataFrame({"a": [1, 2, 3]})
+ ser2 = Series([100.0], index=[1])
+ df_orig = df.copy()
+ with tm.raises_chained_assignment_error():
+ df["a"].update(ser2)
+ tm.assert_frame_equal(df, df_orig)
+
+ with tm.raises_chained_assignment_error():
+ df[["a"]].update(ser2.to_frame())
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_inplace_arithmetic_series():
+ ser = Series([1, 2, 3])
+ ser_orig = ser.copy()
+ data = get_array(ser)
+ ser *= 2
+ # https://github.com/pandas-dev/pandas/pull/55745
+ # changed to NOT update inplace because there is no benefit (actual
+ # operation already done non-inplace). This was only for the optics
+ # of updating the backing array inplace, but we no longer want to make
+ # that guarantee
+ assert not np.shares_memory(get_array(ser), data)
+ tm.assert_numpy_array_equal(data, get_array(ser_orig))
+
+
+def test_inplace_arithmetic_series_with_reference():
+ ser = Series([1, 2, 3])
+ ser_orig = ser.copy()
+ view = ser[:]
+ ser *= 2
+ assert not np.shares_memory(get_array(ser), get_array(view))
+ tm.assert_series_equal(ser_orig, view)
+
+
+def test_transpose():
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+ df_orig = df.copy()
+ result = df.transpose()
+ assert np.shares_memory(get_array(df, "a"), get_array(result, 0))
+
+ result.iloc[0, 0] = 100
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_transpose_different_dtypes():
+ df = DataFrame({"a": [1, 2, 3], "b": 1.5})
+ df_orig = df.copy()
+ result = df.T
+
+ assert not np.shares_memory(get_array(df, "a"), get_array(result, 0))
+ result.iloc[0, 0] = 100
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_transpose_ea_single_column():
+ df = DataFrame({"a": [1, 2, 3]}, dtype="Int64")
+ result = df.T
+
+ assert not np.shares_memory(get_array(df, "a"), get_array(result, 0))
+
+
+def test_transform_frame():
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+ df_orig = df.copy()
+
+ def func(ser):
+ ser.iloc[0] = 100
+ return ser
+
+ df.transform(func)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_transform_series():
+ ser = Series([1, 2, 3])
+ ser_orig = ser.copy()
+
+ def func(ser):
+ ser.iloc[0] = 100
+ return ser
+
+ ser.transform(func)
+ tm.assert_series_equal(ser, ser_orig)
+
+
+def test_count_read_only_array():
+ df = DataFrame({"a": [1, 2], "b": 3})
+ result = df.count()
+ result.iloc[0] = 100
+ expected = Series([100, 2], index=["a", "b"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_insert_series():
+ df = DataFrame({"a": [1, 2, 3]})
+ ser = Series([1, 2, 3])
+ ser_orig = ser.copy()
+ df.insert(loc=1, value=ser, column="b")
+ assert np.shares_memory(get_array(ser), get_array(df, "b"))
+ assert not df._mgr._has_no_reference(1)
+
+ df.iloc[0, 1] = 100
+ tm.assert_series_equal(ser, ser_orig)
+
+
+def test_eval():
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+ df_orig = df.copy()
+
+ result = df.eval("c = a+b")
+ assert np.shares_memory(get_array(df, "a"), get_array(result, "a"))
+
+ result.iloc[0, 0] = 100
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_eval_inplace():
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+ df_orig = df.copy()
+ df_view = df[:]
+
+ df.eval("c = a+b", inplace=True)
+ assert np.shares_memory(get_array(df, "a"), get_array(df_view, "a"))
+
+ df.iloc[0, 0] = 100
+ tm.assert_frame_equal(df_view, df_orig)
+
+
+def test_apply_modify_row():
+ # Case: applying a function on each row as a Series object, where the
+ # function mutates the row object (which needs to trigger CoW if row is a view)
+ df = DataFrame({"A": [1, 2], "B": [3, 4]})
+ df_orig = df.copy()
+
+ def transform(row):
+ row["B"] = 100
+ return row
+
+ df.apply(transform, axis=1)
+
+ tm.assert_frame_equal(df, df_orig)
+
+ # row Series is a copy
+ df = DataFrame({"A": [1, 2], "B": ["b", "c"]})
+ df_orig = df.copy()
+
+ with tm.assert_produces_warning(None):
+ df.apply(transform, axis=1)
+
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_reduce():
+ df = DataFrame({"a": [1, 2, 3], "b": 1.5})
+
+ result = df.sum()
+ assert result.index is not df.columns
+
+ result = df.groupby([0, 0, 1]).sum()
+ assert result.columns is not df.columns
+
+ result = df.quantile(0.5)
+ assert result.index is not df.columns
+ result = df.quantile([0.25, 0.5, 0.75])
+ assert result.columns is not df.columns
+
+
+def test_diff():
+ df = DataFrame({"a": [1, 2, 3], "b": 1.5})
+
+ result = df.diff()
+ assert result.index is not df.index
+ assert result.columns is not df.columns
+
+ ser = Series([1, 2, 3])
+ result = ser.diff()
+ assert result.index is not ser.index
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_replace.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_replace.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4838a5e68ab8328d6263289bc73309424edf458
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_replace.py
@@ -0,0 +1,356 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ DataFrame,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+
+@pytest.mark.parametrize(
+ "replace_kwargs",
+ [
+ {"to_replace": {"a": 1, "b": 4}, "value": -1},
+ # Test CoW splits blocks to avoid copying unchanged columns
+ {"to_replace": {"a": 1}, "value": -1},
+ {"to_replace": {"b": 4}, "value": -1},
+ {"to_replace": {"b": {4: 1}}},
+ # TODO: Add these in a further optimization
+ # We would need to see which columns got replaced in the mask
+ # which could be expensive
+ # {"to_replace": {"b": 1}},
+ # 1
+ ],
+)
+def test_replace(replace_kwargs):
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [0.1, 0.2, 0.3]})
+ df_orig = df.copy()
+
+ df_replaced = df.replace(**replace_kwargs)
+
+ if (df_replaced["b"] == df["b"]).all():
+ assert np.shares_memory(get_array(df_replaced, "b"), get_array(df, "b"))
+ assert tm.shares_memory(get_array(df_replaced, "c"), get_array(df, "c"))
+
+ # mutating squeezed df triggers a copy-on-write for that column/block
+ df_replaced.loc[0, "c"] = -1
+ assert not np.shares_memory(get_array(df_replaced, "c"), get_array(df, "c"))
+
+ if "a" in replace_kwargs["to_replace"]:
+ arr = get_array(df_replaced, "a")
+ df_replaced.loc[0, "a"] = 100
+ assert np.shares_memory(get_array(df_replaced, "a"), arr)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_replace_regex_inplace_refs():
+ df = DataFrame({"a": ["aaa", "bbb"]})
+ df_orig = df.copy()
+ view = df[:]
+ arr = get_array(df, "a")
+ df.replace(to_replace=r"^a.*$", value="new", inplace=True, regex=True)
+ assert not np.shares_memory(arr, get_array(df, "a"))
+ assert df._mgr._has_no_reference(0)
+ tm.assert_frame_equal(view, df_orig)
+
+
+def test_replace_regex_inplace():
+ df = DataFrame({"a": ["aaa", "bbb"]})
+ arr = get_array(df, "a")
+ df.replace(to_replace=r"^a.*$", value="new", inplace=True, regex=True)
+ assert df._mgr._has_no_reference(0)
+ assert tm.shares_memory(arr, get_array(df, "a"))
+
+ df_orig = df.copy()
+ df2 = df.replace(to_replace=r"^b.*$", value="new", regex=True)
+ tm.assert_frame_equal(df_orig, df)
+ assert not tm.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+
+def test_replace_regex_inplace_no_op():
+ df = DataFrame({"a": [1, 2]})
+ arr = get_array(df, "a")
+ df.replace(to_replace=r"^a.$", value="new", inplace=True, regex=True)
+ assert df._mgr._has_no_reference(0)
+ assert np.shares_memory(arr, get_array(df, "a"))
+
+ df_orig = df.copy()
+ df2 = df.replace(to_replace=r"^x.$", value="new", regex=True)
+ tm.assert_frame_equal(df_orig, df)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+
+
+def test_replace_mask_all_false_second_block():
+ df = DataFrame({"a": [1.5, 2, 3], "b": 100.5, "c": 1, "d": 2})
+ df_orig = df.copy()
+
+ df2 = df.replace(to_replace=1.5, value=55.5)
+
+ # TODO: Block splitting would allow us to avoid copying b
+ assert np.shares_memory(get_array(df, "c"), get_array(df2, "c"))
+ assert not np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+
+ df2.loc[0, "c"] = 1
+ tm.assert_frame_equal(df, df_orig) # Original is unchanged
+
+ assert not np.shares_memory(get_array(df, "c"), get_array(df2, "c"))
+ assert np.shares_memory(get_array(df, "d"), get_array(df2, "d"))
+
+
+def test_replace_coerce_single_column():
+ df = DataFrame({"a": [1.5, 2, 3], "b": 100.5})
+ df_orig = df.copy()
+
+ df2 = df.replace(to_replace=1.5, value="a")
+ assert np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+ assert not np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+
+ df2.loc[0, "b"] = 0.5
+ tm.assert_frame_equal(df, df_orig) # Original is unchanged
+ assert not np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+
+
+def test_replace_to_replace_wrong_dtype():
+ df = DataFrame({"a": [1.5, 2, 3], "b": 100.5})
+ df_orig = df.copy()
+
+ df2 = df.replace(to_replace="xxx", value=1.5)
+
+ assert np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+ assert np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+
+ df2.loc[0, "b"] = 0.5
+ tm.assert_frame_equal(df, df_orig) # Original is unchanged
+ assert not np.shares_memory(get_array(df, "b"), get_array(df2, "b"))
+
+
+def test_replace_list_categorical():
+ df = DataFrame({"a": ["a", "b", "c"]}, dtype="category")
+ arr = get_array(df, "a")
+
+ df.replace(["c"], value="a", inplace=True)
+ assert np.shares_memory(arr.codes, get_array(df, "a").codes)
+ assert df._mgr._has_no_reference(0)
+
+ df_orig = df.copy()
+ df.replace(["b"], value="a")
+ df2 = df.apply(lambda x: x.cat.rename_categories({"b": "d"}))
+ assert not np.shares_memory(arr.codes, get_array(df2, "a").codes)
+
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_replace_list_inplace_refs_categorical():
+ df = DataFrame({"a": ["a", "b", "c"]}, dtype="category")
+ view = df[:]
+ df_orig = df.copy()
+ df.replace(["c"], value="a", inplace=True)
+ tm.assert_frame_equal(df_orig, view)
+
+
+@pytest.mark.parametrize("to_replace", [1.5, [1.5], []])
+def test_replace_inplace(to_replace):
+ df = DataFrame({"a": [1.5, 2, 3]})
+ arr_a = get_array(df, "a")
+ df.replace(to_replace=1.5, value=15.5, inplace=True)
+
+ assert np.shares_memory(get_array(df, "a"), arr_a)
+ assert df._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize("to_replace", [1.5, [1.5]])
+def test_replace_inplace_reference(to_replace):
+ df = DataFrame({"a": [1.5, 2, 3]})
+ arr_a = get_array(df, "a")
+ view = df[:]
+ df.replace(to_replace=to_replace, value=15.5, inplace=True)
+
+ assert not np.shares_memory(get_array(df, "a"), arr_a)
+ assert df._mgr._has_no_reference(0)
+ assert view._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize("to_replace", ["a", 100.5])
+def test_replace_inplace_reference_no_op(to_replace):
+ df = DataFrame({"a": [1.5, 2, 3]})
+ arr_a = get_array(df, "a")
+ view = df[:]
+ df.replace(to_replace=to_replace, value=15.5, inplace=True)
+
+ assert np.shares_memory(get_array(df, "a"), arr_a)
+ assert not df._mgr._has_no_reference(0)
+ assert not view._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize("to_replace", [1, [1]])
+def test_replace_categorical_inplace_reference(to_replace):
+ df = DataFrame({"a": Categorical([1, 2, 3])})
+ df_orig = df.copy()
+ arr_a = get_array(df, "a")
+ view = df[:]
+ df.replace(to_replace=to_replace, value=1, inplace=True)
+ assert not np.shares_memory(get_array(df, "a").codes, arr_a.codes)
+ assert df._mgr._has_no_reference(0)
+ assert view._mgr._has_no_reference(0)
+ tm.assert_frame_equal(view, df_orig)
+
+
+def test_replace_categorical_inplace():
+ df = DataFrame({"a": Categorical([1, 2, 3])})
+ arr_a = get_array(df, "a")
+ df.replace(to_replace=1, value=1, inplace=True)
+
+ assert np.shares_memory(get_array(df, "a").codes, arr_a.codes)
+ assert df._mgr._has_no_reference(0)
+
+ expected = DataFrame({"a": Categorical([1, 2, 3])})
+ tm.assert_frame_equal(df, expected)
+
+
+def test_replace_categorical():
+ df = DataFrame({"a": Categorical([1, 2, 3])})
+ df_orig = df.copy()
+ df2 = df.replace(to_replace=1, value=1)
+
+ assert df._mgr._has_no_reference(0)
+ assert df2._mgr._has_no_reference(0)
+ assert not np.shares_memory(get_array(df, "a").codes, get_array(df2, "a").codes)
+ tm.assert_frame_equal(df, df_orig)
+
+ arr_a = get_array(df2, "a").codes
+ df2.iloc[0, 0] = 2.0
+ assert np.shares_memory(get_array(df2, "a").codes, arr_a)
+
+
+@pytest.mark.parametrize("method", ["where", "mask"])
+def test_masking_inplace(method):
+ df = DataFrame({"a": [1.5, 2, 3]})
+ df_orig = df.copy()
+ arr_a = get_array(df, "a")
+ view = df[:]
+
+ method = getattr(df, method)
+ method(df["a"] > 1.6, -1, inplace=True)
+
+ assert not np.shares_memory(get_array(df, "a"), arr_a)
+ assert df._mgr._has_no_reference(0)
+ assert view._mgr._has_no_reference(0)
+ tm.assert_frame_equal(view, df_orig)
+
+
+def test_replace_empty_list():
+ df = DataFrame({"a": [1, 2]})
+
+ df2 = df.replace([], [])
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ assert not df._mgr._has_no_reference(0)
+ arr_a = get_array(df, "a")
+ df.replace([], [])
+ assert np.shares_memory(get_array(df, "a"), arr_a)
+ assert not df._mgr._has_no_reference(0)
+ assert not df2._mgr._has_no_reference(0)
+
+
+@pytest.mark.parametrize("value", ["d", None])
+def test_replace_object_list_inplace(value):
+ df = DataFrame({"a": ["a", "b", "c"]}, dtype=object)
+ arr = get_array(df, "a")
+ df.replace(["c"], value, inplace=True)
+ assert np.shares_memory(arr, get_array(df, "a"))
+ assert df._mgr._has_no_reference(0)
+
+
+def test_replace_list_multiple_elements_inplace():
+ df = DataFrame({"a": [1, 2, 3]})
+ arr = get_array(df, "a")
+ df.replace([1, 2], 4, inplace=True)
+ assert np.shares_memory(arr, get_array(df, "a"))
+ assert df._mgr._has_no_reference(0)
+
+
+def test_replace_list_none():
+ df = DataFrame({"a": ["a", "b", "c"]})
+
+ df_orig = df.copy()
+ df2 = df.replace(["b"], value=None)
+ tm.assert_frame_equal(df, df_orig)
+
+ assert not np.shares_memory(get_array(df, "a"), get_array(df2, "a"))
+
+ # replace multiple values that don't actually replace anything with None
+ # https://github.com/pandas-dev/pandas/issues/59770
+ df3 = df.replace(["d", "e", "f"], value=None)
+ tm.assert_frame_equal(df3, df_orig)
+ assert tm.shares_memory(get_array(df, "a"), get_array(df3, "a"))
+
+
+def test_replace_list_none_inplace_refs():
+ df = DataFrame({"a": ["a", "b", "c"]})
+ arr = get_array(df, "a")
+ df_orig = df.copy()
+ view = df[:]
+ df.replace(["a"], value=None, inplace=True)
+ assert df._mgr._has_no_reference(0)
+ assert not np.shares_memory(arr, get_array(df, "a"))
+ tm.assert_frame_equal(df_orig, view)
+
+
+def test_replace_columnwise_no_op_inplace():
+ df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3]})
+ view = df[:]
+ df_orig = df.copy()
+ df.replace({"a": 10}, 100, inplace=True)
+ assert np.shares_memory(get_array(view, "a"), get_array(df, "a"))
+ df.iloc[0, 0] = 100
+ tm.assert_frame_equal(view, df_orig)
+
+
+def test_replace_columnwise_no_op():
+ df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3]})
+ df_orig = df.copy()
+ df2 = df.replace({"a": 10}, 100)
+ assert np.shares_memory(get_array(df2, "a"), get_array(df, "a"))
+ df2.iloc[0, 0] = 100
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_replace_chained_assignment():
+ df = DataFrame({"a": [1, np.nan, 2], "b": 1})
+ df_orig = df.copy()
+ with tm.raises_chained_assignment_error():
+ df["a"].replace(1, 100, inplace=True)
+ tm.assert_frame_equal(df, df_orig)
+
+ with tm.raises_chained_assignment_error():
+ df[["a"]].replace(1, 100, inplace=True)
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_replace_listlike():
+ df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3]})
+ df_orig = df.copy()
+
+ result = df.replace([200, 201], [11, 11])
+ assert np.shares_memory(get_array(result, "a"), get_array(df, "a"))
+
+ result.iloc[0, 0] = 100
+ tm.assert_frame_equal(df, df)
+
+ result = df.replace([200, 2], [10, 10])
+ assert not np.shares_memory(get_array(df, "a"), get_array(result, "a"))
+ tm.assert_frame_equal(df, df_orig)
+
+
+def test_replace_listlike_inplace():
+ df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3]})
+ arr = get_array(df, "a")
+ df.replace([200, 2], [10, 11], inplace=True)
+ assert np.shares_memory(get_array(df, "a"), arr)
+
+ view = df[:]
+ df_orig = df.copy()
+ df.replace([200, 3], [10, 11], inplace=True)
+ assert not np.shares_memory(get_array(df, "a"), arr)
+ tm.assert_frame_equal(view, df_orig)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_setitem.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_setitem.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f28e9826c7a1bb2b5379e005f0bd2fd57ef4067
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_setitem.py
@@ -0,0 +1,142 @@
+import numpy as np
+
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ RangeIndex,
+ Series,
+)
+import pandas._testing as tm
+from pandas.tests.copy_view.util import get_array
+
+# -----------------------------------------------------------------------------
+# Copy/view behaviour for the values that are set in a DataFrame
+
+
+def test_set_column_with_array():
+ # Case: setting an array as a new column (df[col] = arr) copies that data
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ arr = np.array([1, 2, 3], dtype="int64")
+
+ df["c"] = arr
+
+ # the array data is copied
+ assert not np.shares_memory(get_array(df, "c"), arr)
+ # and thus modifying the array does not modify the DataFrame
+ arr[0] = 0
+ tm.assert_series_equal(df["c"], Series([1, 2, 3], name="c"))
+
+
+def test_set_column_with_series():
+ # Case: setting a series as a new column (df[col] = s) copies that data
+ # (with delayed copy with CoW)
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ ser = Series([1, 2, 3])
+
+ df["c"] = ser
+
+ assert np.shares_memory(get_array(df, "c"), get_array(ser))
+
+ # and modifying the series does not modify the DataFrame
+ ser.iloc[0] = 0
+ assert ser.iloc[0] == 0
+ tm.assert_series_equal(df["c"], Series([1, 2, 3], name="c"))
+
+
+def test_set_column_with_index():
+ # Case: setting an index as a new column (df[col] = idx) copies that data
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ idx = Index([1, 2, 3])
+
+ df["c"] = idx
+
+ # the index data is copied
+ assert not np.shares_memory(get_array(df, "c"), idx.values)
+
+ idx = RangeIndex(1, 4)
+ arr = idx.values
+
+ df["d"] = idx
+
+ assert not np.shares_memory(get_array(df, "d"), arr)
+
+
+def test_set_columns_with_dataframe():
+ # Case: setting a DataFrame as new columns copies that data
+ # (with delayed copy with CoW)
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ df2 = DataFrame({"c": [7, 8, 9], "d": [10, 11, 12]})
+
+ df[["c", "d"]] = df2
+
+ assert np.shares_memory(get_array(df, "c"), get_array(df2, "c"))
+ # and modifying the set DataFrame does not modify the original DataFrame
+ df2.iloc[0, 0] = 0
+ tm.assert_series_equal(df["c"], Series([7, 8, 9], name="c"))
+
+
+def test_setitem_series_no_copy():
+ # Case: setting a Series as column into a DataFrame can delay copying that data
+ df = DataFrame({"a": [1, 2, 3]})
+ rhs = Series([4, 5, 6])
+ rhs_orig = rhs.copy()
+
+ # adding a new column
+ df["b"] = rhs
+ assert np.shares_memory(get_array(rhs), get_array(df, "b"))
+
+ df.iloc[0, 1] = 100
+ tm.assert_series_equal(rhs, rhs_orig)
+
+
+def test_setitem_series_no_copy_single_block():
+ # Overwriting an existing column that is a single block
+ df = DataFrame({"a": [1, 2, 3], "b": [0.1, 0.2, 0.3]})
+ rhs = Series([4, 5, 6])
+ rhs_orig = rhs.copy()
+
+ df["a"] = rhs
+ assert np.shares_memory(get_array(rhs), get_array(df, "a"))
+
+ df.iloc[0, 0] = 100
+ tm.assert_series_equal(rhs, rhs_orig)
+
+
+def test_setitem_series_no_copy_split_block():
+ # Overwriting an existing column that is part of a larger block
+ df = DataFrame({"a": [1, 2, 3], "b": 1})
+ rhs = Series([4, 5, 6])
+ rhs_orig = rhs.copy()
+
+ df["b"] = rhs
+ assert np.shares_memory(get_array(rhs), get_array(df, "b"))
+
+ df.iloc[0, 1] = 100
+ tm.assert_series_equal(rhs, rhs_orig)
+
+
+def test_setitem_series_column_midx_broadcasting():
+ # Setting a Series to multiple columns will repeat the data
+ # (currently copying the data eagerly)
+ df = DataFrame(
+ [[1, 2, 3], [3, 4, 5]],
+ columns=MultiIndex.from_arrays([["a", "a", "b"], [1, 2, 3]]),
+ )
+ rhs = Series([10, 11])
+ df["a"] = rhs
+ assert not np.shares_memory(get_array(rhs), df._get_column_array(0))
+ assert df._mgr._has_no_reference(0)
+
+
+def test_set_column_with_inplace_operator():
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+
+ # this should not raise any warning
+ with tm.assert_produces_warning(None):
+ df["a"] += 1
+
+ # when it is not in a chain, then it should produce a warning
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ ser = df["a"]
+ ser += 1
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_util.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_util.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff55330d70b28c5459a4c0915dd93c8640a91add
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/test_util.py
@@ -0,0 +1,14 @@
+import numpy as np
+
+from pandas import DataFrame
+from pandas.tests.copy_view.util import get_array
+
+
+def test_get_array_numpy():
+ df = DataFrame({"a": [1, 2, 3]})
+ assert np.shares_memory(get_array(df, "a"), get_array(df, "a"))
+
+
+def test_get_array_masked():
+ df = DataFrame({"a": [1, 2, 3]}, dtype="Int64")
+ assert np.shares_memory(get_array(df, "a"), get_array(df, "a"))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/util.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..969334424936559767b0bca87093acfec52f9763
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/copy_view/util.py
@@ -0,0 +1,30 @@
+from pandas import (
+ Categorical,
+ Index,
+ Series,
+)
+from pandas.core.arrays import BaseMaskedArray
+
+
+def get_array(obj, col=None):
+ """
+ Helper method to get array for a DataFrame column or a Series.
+
+ Equivalent of df[col].values, but without going through normal getitem,
+ which triggers tracking references / CoW (and we might be testing that
+ this is done by some other operation).
+ """
+ if isinstance(obj, Index):
+ arr = obj._values
+ elif isinstance(obj, Series) and (col is None or obj.name == col):
+ arr = obj._values
+ else:
+ assert col is not None
+ icol = obj.columns.get_loc(col)
+ assert isinstance(icol, int)
+ arr = obj._get_column_array(icol)
+ if isinstance(arr, BaseMaskedArray):
+ return arr._data
+ elif isinstance(arr, Categorical):
+ return arr
+ return getattr(arr, "_ndarray", arr)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_box_unbox.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_box_unbox.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f3374203836dc29a0f6999a3db93a7b3a297e96
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_box_unbox.py
@@ -0,0 +1,104 @@
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.cast import (
+ maybe_box_native,
+ maybe_unbox_numpy_scalar,
+)
+
+from pandas import (
+ Interval,
+ Period,
+ Timedelta,
+ Timestamp,
+)
+
+
+@pytest.mark.parametrize(
+ "obj,expected_dtype",
+ [
+ (b"\x00\x10", bytes),
+ (4, int),
+ (np.uint(4), int),
+ (np.int32(-4), int),
+ (np.uint8(4), int),
+ (float(454.98), float),
+ (np.float16(0.4), float),
+ (np.float64(1.4), float),
+ (np.bool_(False), bool),
+ (datetime(2005, 2, 25), datetime),
+ (np.datetime64("2005-02-25"), Timestamp),
+ (Timestamp("2005-02-25"), Timestamp),
+ (np.timedelta64(1, "D"), Timedelta),
+ (Timedelta(1, "D"), Timedelta),
+ (Interval(0, 1), Interval),
+ (Period("4Q2005"), Period),
+ ],
+)
+def test_maybe_box_native(obj, expected_dtype):
+ boxed_obj = maybe_box_native(obj)
+ result_dtype = type(boxed_obj)
+ assert result_dtype is expected_dtype
+
+
+@pytest.mark.parametrize("typecode", np.typecodes["All"])
+def test_maybe_unbox_numpy_scalar(typecode, using_python_scalars):
+ # https://github.com/pandas-dev/pandas/pull/63016
+ if typecode == "?":
+ scalar = False
+ expected = bool
+ elif typecode in "bhilqnpBHILQNP":
+ scalar = 0
+ expected = int
+ elif typecode in "efdg":
+ scalar = 0.0
+ expected = float
+ elif typecode in "FDG":
+ scalar = 0.0 + 0.0j
+ expected = complex
+ elif typecode in "SV":
+ scalar = b""
+ expected = bytes
+ elif typecode == "U":
+ scalar = ""
+ expected = str
+ elif typecode == "O":
+ scalar = 0
+ expected = int
+ elif typecode == "M":
+ scalar = datetime(2025, 1, 1)
+ expected = Timestamp
+ elif typecode == "m":
+ scalar = timedelta(seconds=3)
+ expected = Timedelta
+ else:
+ raise ValueError(f"typecode {typecode} not recognized")
+ value = np.array([scalar], dtype=typecode)[0]
+ result = maybe_unbox_numpy_scalar(value)
+ if using_python_scalars:
+ assert type(result) == expected
+ else:
+ assert result is value
+
+
+def test_maybe_unbox_numpy_scalar_timestamp(unit, using_python_scalars):
+ # https://github.com/pandas-dev/pandas/pull/63016
+ value = np.datetime64(1, unit)
+ expected = Timestamp(1, unit=unit) if using_python_scalars else value
+ result = maybe_unbox_numpy_scalar(value)
+ assert result == expected
+ assert type(result) == type(expected)
+
+
+def test_maybe_unbox_numpy_scalar_datetime(unit, using_python_scalars):
+ # https://github.com/pandas-dev/pandas/pull/63016
+ value = np.timedelta64(1, unit)
+ expected = Timedelta(1, unit=unit) if using_python_scalars else value
+ result = maybe_unbox_numpy_scalar(value)
+ assert result == expected
+ assert type(result) == type(expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_can_hold_element.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_can_hold_element.py
new file mode 100644
index 0000000000000000000000000000000000000000..b833c601ec2c87cf52281c430a7f7f7af3ce2984
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_can_hold_element.py
@@ -0,0 +1,108 @@
+import numpy as np
+
+from pandas.core.dtypes.cast import can_hold_element
+
+from pandas import Categorical
+
+
+def test_can_hold_element_range(any_int_numpy_dtype):
+ # GH#44261
+ dtype = np.dtype(any_int_numpy_dtype)
+ arr = np.array([], dtype=dtype)
+
+ rng = range(2, 127)
+ assert can_hold_element(arr, rng)
+
+ # negatives -> can't be held by uint dtypes
+ rng = range(-2, 127)
+ if dtype.kind == "i":
+ assert can_hold_element(arr, rng)
+ else:
+ assert not can_hold_element(arr, rng)
+
+ rng = range(2, 255)
+ if dtype == "int8":
+ assert not can_hold_element(arr, rng)
+ else:
+ assert can_hold_element(arr, rng)
+
+ rng = range(-255, 65537)
+ if dtype.kind == "u":
+ assert not can_hold_element(arr, rng)
+ elif dtype.itemsize < 4:
+ assert not can_hold_element(arr, rng)
+ else:
+ assert can_hold_element(arr, rng)
+
+ # empty
+ rng = range(-(10**10), -(10**10))
+ assert len(rng) == 0
+ # assert can_hold_element(arr, rng)
+
+ rng = range(10**10, 10**10)
+ assert len(rng) == 0
+ assert can_hold_element(arr, rng)
+
+
+def test_can_hold_element_int_values_float_ndarray():
+ arr = np.array([], dtype=np.int64)
+
+ element = np.array([1.0, 2.0])
+ assert can_hold_element(arr, element)
+
+ assert not can_hold_element(arr, element + 0.5)
+
+ # integer but not losslessly castable to int64
+ element = np.array([3, 2**65], dtype=np.float64)
+ assert not can_hold_element(arr, element)
+
+
+def test_can_hold_element_int8_int():
+ arr = np.array([], dtype=np.int8)
+
+ element = 2
+ assert can_hold_element(arr, element)
+ assert can_hold_element(arr, np.int8(element))
+ assert can_hold_element(arr, np.uint8(element))
+ assert can_hold_element(arr, np.int16(element))
+ assert can_hold_element(arr, np.uint16(element))
+ assert can_hold_element(arr, np.int32(element))
+ assert can_hold_element(arr, np.uint32(element))
+ assert can_hold_element(arr, np.int64(element))
+ assert can_hold_element(arr, np.uint64(element))
+
+ element = 2**9
+ assert not can_hold_element(arr, element)
+ assert not can_hold_element(arr, np.int16(element))
+ assert not can_hold_element(arr, np.uint16(element))
+ assert not can_hold_element(arr, np.int32(element))
+ assert not can_hold_element(arr, np.uint32(element))
+ assert not can_hold_element(arr, np.int64(element))
+ assert not can_hold_element(arr, np.uint64(element))
+
+
+def test_can_hold_element_bool():
+ arr = np.array([], dtype=bool)
+
+ element = True
+ assert can_hold_element(arr, element)
+ assert can_hold_element(arr, np.array([element]))
+ assert can_hold_element(arr, np.array([element], dtype=object))
+
+ element = 1
+ assert not can_hold_element(arr, element)
+ assert not can_hold_element(arr, np.array([element]))
+ assert not can_hold_element(arr, np.array([element], dtype=object))
+
+ element = np.nan
+ assert not can_hold_element(arr, element)
+ assert not can_hold_element(arr, np.array([element]))
+ assert not can_hold_element(arr, np.array([element], dtype=object))
+
+
+def test_can_hold_element_categorical():
+ # GH#56376
+ arr = np.array([], dtype=np.float64)
+ cat = Categorical([1, 2, None])
+
+ assert can_hold_element(arr, cat)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_construct_from_scalar.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_construct_from_scalar.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ce04ce2e64cda1d3fc7c48390baa91ee2b06525
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_construct_from_scalar.py
@@ -0,0 +1,55 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.cast import construct_1d_arraylike_from_scalar
+from pandas.core.dtypes.dtypes import CategoricalDtype
+
+from pandas import (
+ Categorical,
+ Timedelta,
+)
+import pandas._testing as tm
+
+
+def test_cast_1d_array_like_from_scalar_categorical():
+ # see gh-19565
+ #
+ # Categorical result from scalar did not maintain
+ # categories and ordering of the passed dtype.
+ cats = ["a", "b", "c"]
+ cat_type = CategoricalDtype(categories=cats, ordered=False)
+ expected = Categorical(["a", "a"], categories=cats)
+
+ result = construct_1d_arraylike_from_scalar("a", len(expected), cat_type)
+ tm.assert_categorical_equal(result, expected)
+
+
+def test_cast_1d_array_like_from_timestamp(fixed_now_ts):
+ # check we dont lose nanoseconds
+ ts = fixed_now_ts + Timedelta(1)
+ res = construct_1d_arraylike_from_scalar(ts, 2, np.dtype("M8[ns]"))
+ assert res[0] == ts
+
+
+def test_cast_1d_array_like_from_timedelta():
+ # check we dont lose nanoseconds
+ td = Timedelta(1)
+ res = construct_1d_arraylike_from_scalar(td, 2, np.dtype("m8[ns]"))
+ assert res[0] == td
+
+
+def test_cast_1d_array_like_mismatched_datetimelike():
+ td = np.timedelta64("NaT", "ns")
+ dt = np.datetime64("NaT", "ns")
+
+ with pytest.raises(TypeError, match="Cannot cast"):
+ construct_1d_arraylike_from_scalar(td, 2, dt.dtype)
+
+ with pytest.raises(TypeError, match="Cannot cast"):
+ construct_1d_arraylike_from_scalar(np.timedelta64(4, "ns"), 2, dt.dtype)
+
+ with pytest.raises(TypeError, match="Cannot cast"):
+ construct_1d_arraylike_from_scalar(dt, 2, td.dtype)
+
+ with pytest.raises(TypeError, match="Cannot cast"):
+ construct_1d_arraylike_from_scalar(np.datetime64(4, "ns"), 2, td.dtype)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_construct_ndarray.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_construct_ndarray.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b9b2dfda6e8b81a0f1f29d3ba97589b9d385600
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_construct_ndarray.py
@@ -0,0 +1,36 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.construction import sanitize_array
+
+
+@pytest.mark.parametrize(
+ "values, dtype, expected",
+ [
+ ([1, 2, 3], None, np.array([1, 2, 3], dtype=np.int64)),
+ (np.array([1, 2, 3]), None, np.array([1, 2, 3])),
+ (["1", "2", None], None, np.array(["1", "2", None])),
+ (["1", "2", None], np.dtype("str"), np.array(["1", "2", None])),
+ ([1, 2, None], np.dtype("str"), np.array(["1", "2", None])),
+ ],
+)
+def test_construct_1d_ndarray_preserving_na(
+ values, dtype, expected, using_infer_string
+):
+ result = sanitize_array(values, index=None, dtype=dtype)
+ if using_infer_string and expected.dtype == object and dtype is None:
+ tm.assert_extension_array_equal(result, pd.array(expected, dtype="str"))
+ else:
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["m8[ns]", "M8[ns]"])
+def test_construct_1d_ndarray_preserving_na_datetimelike(dtype):
+ arr = np.arange(5, dtype=np.int64).view(dtype)
+ expected = np.array(list(arr), dtype=object)
+ assert all(isinstance(x, type(arr[0])) for x in expected)
+
+ result = sanitize_array(arr, index=None, dtype=np.dtype(object))
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_construct_object_arr.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_construct_object_arr.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb44f91f34dec80c090d3ce3fc9a2dbd4578bb57
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_construct_object_arr.py
@@ -0,0 +1,20 @@
+import pytest
+
+from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
+
+
+@pytest.mark.parametrize("datum1", [1, 2.0, "3", (4, 5), [6, 7], None])
+@pytest.mark.parametrize("datum2", [8, 9.0, "10", (11, 12), [13, 14], None])
+def test_cast_1d_array(datum1, datum2):
+ data = [datum1, datum2]
+ result = construct_1d_object_array_from_listlike(data)
+
+ # Direct comparison fails: https://github.com/numpy/numpy/issues/10218
+ assert result.dtype == "object"
+ assert list(result) == data
+
+
+@pytest.mark.parametrize("val", [1, 2.0, None])
+def test_cast_1d_array_invalid_scalar(val):
+ with pytest.raises(TypeError, match="has no len()"):
+ construct_1d_object_array_from_listlike(val)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_dict_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_dict_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..13dc82d779f953fbea54323785bdcadc3e24dfd8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_dict_compat.py
@@ -0,0 +1,14 @@
+import numpy as np
+
+from pandas.core.dtypes.cast import dict_compat
+
+from pandas import Timestamp
+
+
+def test_dict_compat():
+ data_datetime64 = {np.datetime64("1990-03-15"): 1, np.datetime64("2015-03-15"): 2}
+ data_unchanged = {1: 2, 3: 4, 5: 6}
+ expected = {Timestamp("1990-3-15"): 1, Timestamp("2015-03-15"): 2}
+ assert dict_compat(data_datetime64) == expected
+ assert dict_compat(expected) == expected
+ assert dict_compat(data_unchanged) == data_unchanged
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_downcast.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_downcast.py
new file mode 100644
index 0000000000000000000000000000000000000000..763a6a54dc4e1a8a15e22b1af38895fdfee0cf36
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_downcast.py
@@ -0,0 +1,54 @@
+import decimal
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.cast import maybe_downcast_to_dtype
+
+from pandas import (
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "arr,dtype,expected",
+ [
+ (
+ # This is a judgement call, but we do _not_ downcast Decimal
+ # objects
+ np.array([decimal.Decimal("0.0")]),
+ np.dtype("int64"),
+ np.array([decimal.Decimal("0.0")]),
+ ),
+ ],
+)
+def test_downcast(arr, expected, dtype):
+ result = maybe_downcast_to_dtype(arr, dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_downcast_booleans():
+ # see gh-16875: coercing of booleans.
+ ser = Series([True, True, False])
+ result = maybe_downcast_to_dtype(ser, np.dtype(np.float64))
+
+ expected = ser.values
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_downcast_conversion_empty(any_real_numpy_dtype):
+ dtype = any_real_numpy_dtype
+ arr = np.array([], dtype=dtype)
+ result = maybe_downcast_to_dtype(arr, np.dtype("int64"))
+ tm.assert_numpy_array_equal(result, np.array([], dtype=np.int64))
+
+
+@pytest.mark.parametrize("klass", [np.datetime64, np.timedelta64])
+def test_datetime_likes_nan(klass):
+ dtype = np.dtype(klass.__name__ + "[ns]")
+ arr = np.array([1, 2, np.nan])
+
+ exp = np.array([1, 2, klass("NaT")], dtype)
+ res = maybe_downcast_to_dtype(arr, dtype)
+ tm.assert_numpy_array_equal(res, exp)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_find_common_type.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_find_common_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..83ef7382fbe8a27ad96511a3675c51b9eadc2331
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_find_common_type.py
@@ -0,0 +1,175 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.cast import find_common_type
+from pandas.core.dtypes.common import pandas_dtype
+from pandas.core.dtypes.dtypes import (
+ CategoricalDtype,
+ DatetimeTZDtype,
+ IntervalDtype,
+ PeriodDtype,
+)
+
+from pandas import (
+ Categorical,
+ Index,
+)
+
+
+@pytest.mark.parametrize(
+ "source_dtypes,expected_common_dtype",
+ [
+ ((np.int64,), np.int64),
+ ((np.uint64,), np.uint64),
+ ((np.float32,), np.float32),
+ ((object,), object),
+ # Into ints.
+ ((np.int16, np.int64), np.int64),
+ ((np.int32, np.uint32), np.int64),
+ ((np.uint16, np.uint64), np.uint64),
+ # Into floats.
+ ((np.float16, np.float32), np.float32),
+ ((np.float16, np.int16), np.float32),
+ ((np.float32, np.int16), np.float32),
+ ((np.uint64, np.int64), np.float64),
+ ((np.int16, np.float64), np.float64),
+ ((np.float16, np.int64), np.float64),
+ # Into others.
+ ((np.complex128, np.int32), np.complex128),
+ ((object, np.float32), object),
+ ((object, np.int16), object),
+ # Bool with int.
+ ((np.dtype("bool"), np.int64), object),
+ ((np.dtype("bool"), np.int32), object),
+ ((np.dtype("bool"), np.int16), object),
+ ((np.dtype("bool"), np.int8), object),
+ ((np.dtype("bool"), np.uint64), object),
+ ((np.dtype("bool"), np.uint32), object),
+ ((np.dtype("bool"), np.uint16), object),
+ ((np.dtype("bool"), np.uint8), object),
+ # Bool with float.
+ ((np.dtype("bool"), np.float64), object),
+ ((np.dtype("bool"), np.float32), object),
+ (
+ (np.dtype("datetime64[ns]"), np.dtype("datetime64[ns]")),
+ np.dtype("datetime64[ns]"),
+ ),
+ (
+ (np.dtype("timedelta64[ns]"), np.dtype("timedelta64[ns]")),
+ np.dtype("timedelta64[ns]"),
+ ),
+ (
+ (np.dtype("datetime64[ns]"), np.dtype("datetime64[ms]")),
+ np.dtype("datetime64[ns]"),
+ ),
+ (
+ (np.dtype("timedelta64[ms]"), np.dtype("timedelta64[ns]")),
+ np.dtype("timedelta64[ns]"),
+ ),
+ ((np.dtype("datetime64[ns]"), np.dtype("timedelta64[ns]")), object),
+ ((np.dtype("datetime64[ns]"), np.int64), object),
+ ],
+)
+def test_numpy_dtypes(source_dtypes, expected_common_dtype):
+ source_dtypes = [pandas_dtype(x) for x in source_dtypes]
+ assert find_common_type(source_dtypes) == expected_common_dtype
+
+
+def test_raises_empty_input():
+ with pytest.raises(ValueError, match="no types given"):
+ find_common_type([])
+
+
+@pytest.mark.parametrize(
+ "dtypes,exp_type",
+ [
+ ([CategoricalDtype()], "category"),
+ ([object, CategoricalDtype()], object),
+ ([CategoricalDtype(), CategoricalDtype()], "category"),
+ ],
+)
+def test_categorical_dtype(dtypes, exp_type):
+ assert find_common_type(dtypes) == exp_type
+
+
+def test_datetimetz_dtype_match():
+ dtype = DatetimeTZDtype(unit="ns", tz="US/Eastern")
+ assert find_common_type([dtype, dtype]) == "datetime64[ns, US/Eastern]"
+
+
+@pytest.mark.parametrize(
+ "dtype2",
+ [
+ DatetimeTZDtype(unit="ns", tz="Asia/Tokyo"),
+ np.dtype("datetime64[ns]"),
+ object,
+ np.int64,
+ ],
+)
+def test_datetimetz_dtype_mismatch(dtype2):
+ dtype = DatetimeTZDtype(unit="ns", tz="US/Eastern")
+ assert find_common_type([dtype, dtype2]) == object
+ assert find_common_type([dtype2, dtype]) == object
+
+
+def test_period_dtype_match():
+ dtype = PeriodDtype(freq="D")
+ assert find_common_type([dtype, dtype]) == "period[D]"
+
+
+@pytest.mark.parametrize(
+ "dtype2",
+ [
+ DatetimeTZDtype(unit="ns", tz="Asia/Tokyo"),
+ PeriodDtype(freq="2D"),
+ PeriodDtype(freq="h"),
+ np.dtype("datetime64[ns]"),
+ object,
+ np.int64,
+ ],
+)
+def test_period_dtype_mismatch(dtype2):
+ dtype = PeriodDtype(freq="D")
+ assert find_common_type([dtype, dtype2]) == object
+ assert find_common_type([dtype2, dtype]) == object
+
+
+interval_dtypes = [
+ IntervalDtype(np.int64, "right"),
+ IntervalDtype(np.float64, "right"),
+ IntervalDtype(np.uint64, "right"),
+ IntervalDtype(DatetimeTZDtype(unit="ns", tz="US/Eastern"), "right"),
+ IntervalDtype("M8[ns]", "right"),
+ IntervalDtype("m8[ns]", "right"),
+]
+
+
+@pytest.mark.parametrize("left", interval_dtypes)
+@pytest.mark.parametrize("right", interval_dtypes)
+def test_interval_dtype(left, right):
+ result = find_common_type([left, right])
+
+ if left is right:
+ assert result is left
+
+ elif left.subtype.kind in ["i", "u", "f"]:
+ # i.e. numeric
+ if right.subtype.kind in ["i", "u", "f"]:
+ # both numeric -> common numeric subtype
+ expected = IntervalDtype(np.float64, "right")
+ assert result == expected
+ else:
+ assert result == object
+
+ else:
+ assert result == object
+
+
+@pytest.mark.parametrize("dtype", interval_dtypes)
+def test_interval_dtype_with_categorical(dtype):
+ obj = Index([], dtype=dtype)
+
+ cat = Categorical([], categories=obj)
+
+ result = find_common_type([dtype, cat.dtype])
+ assert result == dtype
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_infer_datetimelike.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_infer_datetimelike.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c3844e69586d2f49377e77910627ee42fef9bb2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_infer_datetimelike.py
@@ -0,0 +1,28 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ NaT,
+ Series,
+ Timestamp,
+)
+
+
+@pytest.mark.parametrize(
+ "data,exp_size",
+ [
+ # see gh-16362.
+ ([[NaT, "a", "b", 0], [NaT, "b", "c", 1]], 8),
+ ([[NaT, "a", 0], [NaT, "b", 1]], 6),
+ ],
+)
+def test_maybe_infer_to_datetimelike_df_construct(data, exp_size):
+ result = DataFrame(np.array(data))
+ assert result.size == exp_size
+
+
+def test_maybe_infer_to_datetimelike_ser_construct():
+ # see gh-19671.
+ result = Series(["M1701", Timestamp("20130101")])
+ assert result.dtype.kind == "O"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_infer_dtype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_infer_dtype.py
new file mode 100644
index 0000000000000000000000000000000000000000..86124c19db82463d19570e931f2ac9e9dad42dbe
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_infer_dtype.py
@@ -0,0 +1,216 @@
+from datetime import (
+ date,
+ datetime,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.cast import (
+ infer_dtype_from,
+ infer_dtype_from_array,
+ infer_dtype_from_scalar,
+)
+from pandas.core.dtypes.common import is_dtype_equal
+
+from pandas import (
+ Categorical,
+ Interval,
+ Period,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+)
+
+
+def test_infer_dtype_from_int_scalar(any_int_numpy_dtype):
+ # Test that infer_dtype_from_scalar is
+ # returning correct dtype for int and float.
+ data = np.dtype(any_int_numpy_dtype).type(12)
+ dtype, val = infer_dtype_from_scalar(data)
+ assert dtype == type(data)
+
+
+def test_infer_dtype_from_float_scalar(float_numpy_dtype):
+ float_numpy_dtype = np.dtype(float_numpy_dtype).type
+ data = float_numpy_dtype(12)
+
+ dtype, val = infer_dtype_from_scalar(data)
+ assert dtype == float_numpy_dtype
+
+
+@pytest.mark.parametrize(
+ "data,exp_dtype", [(12, np.int64), (np.float64(12), np.float64)]
+)
+def test_infer_dtype_from_python_scalar(data, exp_dtype):
+ dtype, val = infer_dtype_from_scalar(data)
+ assert dtype == exp_dtype
+
+
+@pytest.mark.parametrize("bool_val", [True, False])
+def test_infer_dtype_from_boolean(bool_val):
+ dtype, val = infer_dtype_from_scalar(bool_val)
+ assert dtype == np.bool_
+
+
+def test_infer_dtype_from_complex(complex_dtype):
+ data = np.dtype(complex_dtype).type(1)
+ dtype, val = infer_dtype_from_scalar(data)
+ assert dtype == np.complex128
+
+
+def test_infer_dtype_from_datetime():
+ dt64 = np.datetime64(1, "ns")
+ dtype, val = infer_dtype_from_scalar(dt64)
+ assert dtype == "M8[ns]"
+
+ ts = Timestamp(1)
+ dtype, val = infer_dtype_from_scalar(ts)
+ assert dtype == "M8[ns]"
+
+ dt = datetime(2000, 1, 1, 0, 0)
+ dtype, val = infer_dtype_from_scalar(dt)
+ assert dtype == "M8[us]"
+
+
+def test_infer_dtype_from_timedelta():
+ td64 = np.timedelta64(1, "ns")
+ dtype, val = infer_dtype_from_scalar(td64)
+ assert dtype == "m8[ns]"
+
+ pytd = timedelta(1)
+ dtype, val = infer_dtype_from_scalar(pytd)
+ assert dtype == "m8[us]"
+
+ td = Timedelta(1)
+ dtype, val = infer_dtype_from_scalar(td)
+ assert dtype == "m8[ns]"
+
+
+@pytest.mark.parametrize("freq", ["M", "D"])
+def test_infer_dtype_from_period(freq):
+ p = Period("2011-01-01", freq=freq)
+ dtype, val = infer_dtype_from_scalar(p)
+
+ exp_dtype = f"period[{freq}]"
+
+ assert dtype == exp_dtype
+ assert val == p
+
+
+def test_infer_dtype_misc():
+ dt = date(2000, 1, 1)
+ dtype, val = infer_dtype_from_scalar(dt)
+ assert dtype == np.object_
+
+ ts = Timestamp(1, tz="US/Eastern")
+ dtype, val = infer_dtype_from_scalar(ts)
+ assert dtype == "datetime64[ns, US/Eastern]"
+
+
+@pytest.mark.parametrize("tz", ["UTC", "US/Eastern", "Asia/Tokyo"])
+def test_infer_from_scalar_tz(tz):
+ dt = Timestamp(1, tz=tz)
+ dtype, val = infer_dtype_from_scalar(dt)
+
+ exp_dtype = f"datetime64[ns, {tz}]"
+
+ assert dtype == exp_dtype
+ assert val == dt
+
+
+@pytest.mark.parametrize(
+ "left, right, subtype",
+ [
+ (0, 1, "int64"),
+ (0.0, 1.0, "float64"),
+ (Timestamp(0), Timestamp(1), "datetime64[ns]"),
+ (Timestamp(0, tz="UTC"), Timestamp(1, tz="UTC"), "datetime64[ns, UTC]"),
+ (Timedelta(0), Timedelta(1), "timedelta64[ns]"),
+ ],
+)
+def test_infer_from_interval(left, right, subtype, closed):
+ # GH 30337
+ interval = Interval(left, right, closed)
+ result_dtype, result_value = infer_dtype_from_scalar(interval)
+ expected_dtype = f"interval[{subtype}, {closed}]"
+ assert result_dtype == expected_dtype
+ assert result_value == interval
+
+
+def test_infer_dtype_from_scalar_errors():
+ msg = "invalid ndarray passed to infer_dtype_from_scalar"
+
+ with pytest.raises(ValueError, match=msg):
+ infer_dtype_from_scalar(np.array([1]))
+
+
+@pytest.mark.parametrize(
+ "value, expected",
+ [
+ ("foo", np.object_),
+ (b"foo", np.object_),
+ (1, np.int64),
+ (1.5, np.float64),
+ (np.datetime64("2016-01-01"), np.dtype("M8[s]")),
+ (Timestamp("20160101").as_unit("s"), np.dtype("M8[s]")),
+ (Timestamp("20160101", tz="UTC").as_unit("s"), "datetime64[s, UTC]"),
+ ],
+)
+def test_infer_dtype_from_scalar(value, expected, using_infer_string):
+ dtype, _ = infer_dtype_from_scalar(value)
+ if using_infer_string and value == "foo":
+ expected = "string"
+ assert is_dtype_equal(dtype, expected)
+
+ with pytest.raises(TypeError, match="must be list-like"):
+ infer_dtype_from_array(value)
+
+
+@pytest.mark.parametrize(
+ "arr, expected",
+ [
+ ([1], np.dtype(int)),
+ (np.array([1], dtype=np.int64), np.int64),
+ ([np.nan, 1, ""], np.object_),
+ (np.array([[1.0, 2.0]]), np.float64),
+ (Categorical(list("aabc")), "category"),
+ (Categorical([1, 2, 3]), "category"),
+ (date_range("20160101", periods=3, unit="ns"), np.dtype("=M8[ns]")),
+ (
+ date_range("20160101", periods=3, tz="US/Eastern", unit="ns"),
+ "datetime64[ns, US/Eastern]",
+ ),
+ (Series([1.0, 2, 3]), np.float64),
+ (Series(list("abc")), np.object_),
+ (
+ Series(date_range("20160101", periods=3, tz="US/Eastern", unit="ns")),
+ "datetime64[ns, US/Eastern]",
+ ),
+ ],
+)
+def test_infer_dtype_from_array(arr, expected, using_infer_string):
+ dtype, _ = infer_dtype_from_array(arr)
+ if (
+ using_infer_string
+ and isinstance(arr, Series)
+ and arr.tolist() == ["a", "b", "c"]
+ ):
+ expected = "string"
+ assert is_dtype_equal(dtype, expected)
+
+
+@pytest.mark.parametrize("cls", [np.datetime64, np.timedelta64])
+def test_infer_dtype_from_scalar_zerodim_datetimelike(cls):
+ # ndarray.item() can incorrectly return int instead of td64/dt64
+ val = cls(1234, "ns")
+ arr = np.array(val)
+
+ dtype, res = infer_dtype_from_scalar(arr)
+ assert dtype.type is cls
+ assert isinstance(res, cls)
+
+ dtype, res = infer_dtype_from(arr)
+ assert dtype.type is cls
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_promote.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_promote.py
new file mode 100644
index 0000000000000000000000000000000000000000..021107724bef73d998191d65b55fb29848fc8b9a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/cast/test_promote.py
@@ -0,0 +1,530 @@
+"""
+These test the method maybe_promote from core/dtypes/cast.py
+"""
+
+import datetime
+from decimal import Decimal
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import NaT
+
+from pandas.core.dtypes.cast import maybe_promote
+from pandas.core.dtypes.common import is_scalar
+from pandas.core.dtypes.dtypes import DatetimeTZDtype
+from pandas.core.dtypes.missing import isna
+
+import pandas as pd
+
+
+def _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar=None):
+ """
+ Auxiliary function to unify testing of scalar/array promotion.
+
+ Parameters
+ ----------
+ dtype : dtype
+ The value to pass on as the first argument to maybe_promote.
+ fill_value : scalar
+ The value to pass on as the second argument to maybe_promote as
+ a scalar.
+ expected_dtype : dtype
+ The expected dtype returned by maybe_promote (by design this is the
+ same regardless of whether fill_value was passed as a scalar or in an
+ array!).
+ exp_val_for_scalar : scalar
+ The expected value for the (potentially upcast) fill_value returned by
+ maybe_promote.
+ """
+ assert is_scalar(fill_value)
+
+ # here, we pass on fill_value as a scalar directly; the expected value
+ # returned from maybe_promote is fill_value, potentially upcast to the
+ # returned dtype.
+ result_dtype, result_fill_value = maybe_promote(dtype, fill_value)
+ expected_fill_value = exp_val_for_scalar
+
+ assert result_dtype == expected_dtype
+ _assert_match(result_fill_value, expected_fill_value)
+
+
+def _assert_match(result_fill_value, expected_fill_value):
+ # GH#23982/25425 require the same type in addition to equality/NA-ness
+ res_type = type(result_fill_value)
+ ex_type = type(expected_fill_value)
+
+ if hasattr(result_fill_value, "dtype"):
+ # Compare types in a way that is robust to platform-specific
+ # idiosyncrasies where e.g. sometimes we get "ulonglong" as an alias
+ # for "uint64" or "intc" as an alias for "int32"
+ assert result_fill_value.dtype.kind == expected_fill_value.dtype.kind
+ assert result_fill_value.dtype.itemsize == expected_fill_value.dtype.itemsize
+ else:
+ # On some builds, type comparison fails, e.g. np.int32 != np.int32
+ assert res_type == ex_type or res_type.__name__ == ex_type.__name__
+
+ match_value = result_fill_value == expected_fill_value
+ if match_value is pd.NA:
+ match_value = False
+
+ # Note: type check above ensures that we have the _same_ NA value
+ # for missing values, None == None (which is checked
+ # through match_value above), but np.nan != np.nan and pd.NaT != pd.NaT
+ match_missing = isna(result_fill_value) and isna(expected_fill_value)
+
+ assert match_value or match_missing
+
+
+@pytest.mark.parametrize(
+ "dtype, fill_value, expected_dtype",
+ [
+ # size 8
+ ("int8", 1, "int8"),
+ ("int8", np.iinfo("int8").max + 1, "int16"),
+ ("int8", np.iinfo("int16").max + 1, "int32"),
+ ("int8", np.iinfo("int32").max + 1, "int64"),
+ ("int8", np.iinfo("int64").max + 1, "object"),
+ ("int8", -1, "int8"),
+ ("int8", np.iinfo("int8").min - 1, "int16"),
+ ("int8", np.iinfo("int16").min - 1, "int32"),
+ ("int8", np.iinfo("int32").min - 1, "int64"),
+ ("int8", np.iinfo("int64").min - 1, "object"),
+ # keep signed-ness as long as possible
+ ("uint8", 1, "uint8"),
+ ("uint8", np.iinfo("int8").max + 1, "uint8"),
+ ("uint8", np.iinfo("uint8").max + 1, "uint16"),
+ ("uint8", np.iinfo("int16").max + 1, "uint16"),
+ ("uint8", np.iinfo("uint16").max + 1, "uint32"),
+ ("uint8", np.iinfo("int32").max + 1, "uint32"),
+ ("uint8", np.iinfo("uint32").max + 1, "uint64"),
+ ("uint8", np.iinfo("int64").max + 1, "uint64"),
+ ("uint8", np.iinfo("uint64").max + 1, "object"),
+ # max of uint8 cannot be contained in int8
+ ("uint8", -1, "int16"),
+ ("uint8", np.iinfo("int8").min - 1, "int16"),
+ ("uint8", np.iinfo("int16").min - 1, "int32"),
+ ("uint8", np.iinfo("int32").min - 1, "int64"),
+ ("uint8", np.iinfo("int64").min - 1, "object"),
+ # size 16
+ ("int16", 1, "int16"),
+ ("int16", np.iinfo("int8").max + 1, "int16"),
+ ("int16", np.iinfo("int16").max + 1, "int32"),
+ ("int16", np.iinfo("int32").max + 1, "int64"),
+ ("int16", np.iinfo("int64").max + 1, "object"),
+ ("int16", -1, "int16"),
+ ("int16", np.iinfo("int8").min - 1, "int16"),
+ ("int16", np.iinfo("int16").min - 1, "int32"),
+ ("int16", np.iinfo("int32").min - 1, "int64"),
+ ("int16", np.iinfo("int64").min - 1, "object"),
+ ("uint16", 1, "uint16"),
+ ("uint16", np.iinfo("int8").max + 1, "uint16"),
+ ("uint16", np.iinfo("uint8").max + 1, "uint16"),
+ ("uint16", np.iinfo("int16").max + 1, "uint16"),
+ ("uint16", np.iinfo("uint16").max + 1, "uint32"),
+ ("uint16", np.iinfo("int32").max + 1, "uint32"),
+ ("uint16", np.iinfo("uint32").max + 1, "uint64"),
+ ("uint16", np.iinfo("int64").max + 1, "uint64"),
+ ("uint16", np.iinfo("uint64").max + 1, "object"),
+ ("uint16", -1, "int32"),
+ ("uint16", np.iinfo("int8").min - 1, "int32"),
+ ("uint16", np.iinfo("int16").min - 1, "int32"),
+ ("uint16", np.iinfo("int32").min - 1, "int64"),
+ ("uint16", np.iinfo("int64").min - 1, "object"),
+ # size 32
+ ("int32", 1, "int32"),
+ ("int32", np.iinfo("int8").max + 1, "int32"),
+ ("int32", np.iinfo("int16").max + 1, "int32"),
+ ("int32", np.iinfo("int32").max + 1, "int64"),
+ ("int32", np.iinfo("int64").max + 1, "object"),
+ ("int32", -1, "int32"),
+ ("int32", np.iinfo("int8").min - 1, "int32"),
+ ("int32", np.iinfo("int16").min - 1, "int32"),
+ ("int32", np.iinfo("int32").min - 1, "int64"),
+ ("int32", np.iinfo("int64").min - 1, "object"),
+ ("uint32", 1, "uint32"),
+ ("uint32", np.iinfo("int8").max + 1, "uint32"),
+ ("uint32", np.iinfo("uint8").max + 1, "uint32"),
+ ("uint32", np.iinfo("int16").max + 1, "uint32"),
+ ("uint32", np.iinfo("uint16").max + 1, "uint32"),
+ ("uint32", np.iinfo("int32").max + 1, "uint32"),
+ ("uint32", np.iinfo("uint32").max + 1, "uint64"),
+ ("uint32", np.iinfo("int64").max + 1, "uint64"),
+ ("uint32", np.iinfo("uint64").max + 1, "object"),
+ ("uint32", -1, "int64"),
+ ("uint32", np.iinfo("int8").min - 1, "int64"),
+ ("uint32", np.iinfo("int16").min - 1, "int64"),
+ ("uint32", np.iinfo("int32").min - 1, "int64"),
+ ("uint32", np.iinfo("int64").min - 1, "object"),
+ # size 64
+ ("int64", 1, "int64"),
+ ("int64", np.iinfo("int8").max + 1, "int64"),
+ ("int64", np.iinfo("int16").max + 1, "int64"),
+ ("int64", np.iinfo("int32").max + 1, "int64"),
+ ("int64", np.iinfo("int64").max + 1, "object"),
+ ("int64", -1, "int64"),
+ ("int64", np.iinfo("int8").min - 1, "int64"),
+ ("int64", np.iinfo("int16").min - 1, "int64"),
+ ("int64", np.iinfo("int32").min - 1, "int64"),
+ ("int64", np.iinfo("int64").min - 1, "object"),
+ ("uint64", 1, "uint64"),
+ ("uint64", np.iinfo("int8").max + 1, "uint64"),
+ ("uint64", np.iinfo("uint8").max + 1, "uint64"),
+ ("uint64", np.iinfo("int16").max + 1, "uint64"),
+ ("uint64", np.iinfo("uint16").max + 1, "uint64"),
+ ("uint64", np.iinfo("int32").max + 1, "uint64"),
+ ("uint64", np.iinfo("uint32").max + 1, "uint64"),
+ ("uint64", np.iinfo("int64").max + 1, "uint64"),
+ ("uint64", np.iinfo("uint64").max + 1, "object"),
+ ("uint64", -1, "object"),
+ ("uint64", np.iinfo("int8").min - 1, "object"),
+ ("uint64", np.iinfo("int16").min - 1, "object"),
+ ("uint64", np.iinfo("int32").min - 1, "object"),
+ ("uint64", np.iinfo("int64").min - 1, "object"),
+ ],
+)
+def test_maybe_promote_int_with_int(dtype, fill_value, expected_dtype):
+ dtype = np.dtype(dtype)
+ expected_dtype = np.dtype(expected_dtype)
+
+ # output is not a generic int, but corresponds to expected_dtype
+ exp_val_for_scalar = np.array([fill_value], dtype=expected_dtype)[0]
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_int_with_float(any_int_numpy_dtype, float_numpy_dtype):
+ dtype = np.dtype(any_int_numpy_dtype)
+ fill_dtype = np.dtype(float_numpy_dtype)
+
+ # create array of given dtype; casts "1" to correct dtype
+ fill_value = np.array([1], dtype=fill_dtype)[0]
+
+ # filling int with float always upcasts to float64
+ expected_dtype = np.float64
+ # fill_value can be different float type
+ exp_val_for_scalar = np.float64(fill_value)
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_float_with_int(float_numpy_dtype, any_int_numpy_dtype):
+ dtype = np.dtype(float_numpy_dtype)
+ fill_dtype = np.dtype(any_int_numpy_dtype)
+
+ # create array of given dtype; casts "1" to correct dtype
+ fill_value = np.array([1], dtype=fill_dtype)[0]
+
+ # filling float with int always keeps float dtype
+ # because: np.finfo('float32').max > np.iinfo('uint64').max
+ expected_dtype = dtype
+ # output is not a generic float, but corresponds to expected_dtype
+ exp_val_for_scalar = np.array([fill_value], dtype=expected_dtype)[0]
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+@pytest.mark.parametrize(
+ "dtype, fill_value, expected_dtype",
+ [
+ # float filled with float
+ ("float32", 1, "float32"),
+ ("float32", float(np.finfo("float32").max) * 1.1, "float64"),
+ ("float64", 1, "float64"),
+ ("float64", float(np.finfo("float32").max) * 1.1, "float64"),
+ # complex filled with float
+ ("complex64", 1, "complex64"),
+ ("complex64", float(np.finfo("float32").max) * 1.1, "complex128"),
+ ("complex128", 1, "complex128"),
+ ("complex128", float(np.finfo("float32").max) * 1.1, "complex128"),
+ # float filled with complex
+ ("float32", 1 + 1j, "complex64"),
+ ("float32", float(np.finfo("float32").max) * (1.1 + 1j), "complex128"),
+ ("float64", 1 + 1j, "complex128"),
+ ("float64", float(np.finfo("float32").max) * (1.1 + 1j), "complex128"),
+ # complex filled with complex
+ ("complex64", 1 + 1j, "complex64"),
+ ("complex64", float(np.finfo("float32").max) * (1.1 + 1j), "complex128"),
+ ("complex128", 1 + 1j, "complex128"),
+ ("complex128", float(np.finfo("float32").max) * (1.1 + 1j), "complex128"),
+ ],
+)
+def test_maybe_promote_float_with_float(dtype, fill_value, expected_dtype):
+ dtype = np.dtype(dtype)
+ expected_dtype = np.dtype(expected_dtype)
+
+ # output is not a generic float, but corresponds to expected_dtype
+ exp_val_for_scalar = np.array([fill_value], dtype=expected_dtype)[0]
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_bool_with_any(any_numpy_dtype):
+ dtype = np.dtype(bool)
+ fill_dtype = np.dtype(any_numpy_dtype)
+
+ # create array of given dtype; casts "1" to correct dtype
+ fill_value = np.array([1], dtype=fill_dtype)[0]
+
+ # filling bool with anything but bool casts to object
+ expected_dtype = np.dtype(object) if fill_dtype != bool else fill_dtype
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_any_with_bool(any_numpy_dtype):
+ dtype = np.dtype(any_numpy_dtype)
+ fill_value = True
+
+ # filling anything but bool with bool casts to object
+ expected_dtype = np.dtype(object) if dtype != bool else dtype
+ # output is not a generic bool, but corresponds to expected_dtype
+ exp_val_for_scalar = np.array([fill_value], dtype=expected_dtype)[0]
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_bytes_with_any(bytes_dtype, any_numpy_dtype):
+ dtype = np.dtype(bytes_dtype)
+ fill_dtype = np.dtype(any_numpy_dtype)
+
+ # create array of given dtype; casts "1" to correct dtype
+ fill_value = np.array([1], dtype=fill_dtype)[0]
+
+ # we never use bytes dtype internally, always promote to object
+ expected_dtype = np.dtype(np.object_)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_any_with_bytes(any_numpy_dtype):
+ dtype = np.dtype(any_numpy_dtype)
+
+ # create array of given dtype
+ fill_value = b"abc"
+
+ # we never use bytes dtype internally, always promote to object
+ expected_dtype = np.dtype(np.object_)
+ # output is not a generic bytes, but corresponds to expected_dtype
+ exp_val_for_scalar = np.array([fill_value], dtype=expected_dtype)[0]
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_datetime64_with_any(datetime64_dtype, any_numpy_dtype):
+ dtype = np.dtype(datetime64_dtype)
+ fill_dtype = np.dtype(any_numpy_dtype)
+
+ # create array of given dtype; casts "1" to correct dtype
+ fill_value = np.array([1], dtype=fill_dtype)[0]
+
+ # filling datetime with anything but datetime casts to object
+ if fill_dtype.kind == "M":
+ expected_dtype = dtype
+ # for datetime dtypes, scalar values get cast to to_datetime64
+ exp_val_for_scalar = pd.Timestamp(fill_value).to_datetime64()
+ else:
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+@pytest.mark.parametrize(
+ "fill_value",
+ [
+ pd.Timestamp("now"),
+ np.datetime64("now"),
+ datetime.datetime.now(),
+ datetime.date.today(),
+ ],
+ ids=["pd.Timestamp", "np.datetime64", "datetime.datetime", "datetime.date"],
+)
+def test_maybe_promote_any_with_datetime64(any_numpy_dtype, fill_value):
+ dtype = np.dtype(any_numpy_dtype)
+
+ # filling datetime with anything but datetime casts to object
+ if dtype.kind == "M":
+ expected_dtype = dtype
+ # for datetime dtypes, scalar values get cast to pd.Timestamp.value
+ exp_val_for_scalar = pd.Timestamp(fill_value).to_datetime64()
+ else:
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ if type(fill_value) is datetime.date and dtype.kind == "M":
+ # Casting date to dt64 is deprecated, in 2.0 enforced to cast to object
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+@pytest.mark.parametrize(
+ "fill_value",
+ [
+ pd.Timestamp(2023, 1, 1),
+ np.datetime64("2023-01-01"),
+ datetime.datetime(2023, 1, 1),
+ datetime.date(2023, 1, 1),
+ ],
+ ids=["pd.Timestamp", "np.datetime64", "datetime.datetime", "datetime.date"],
+)
+def test_maybe_promote_any_numpy_dtype_with_datetimetz(
+ any_numpy_dtype, tz_aware_fixture, fill_value
+):
+ dtype = np.dtype(any_numpy_dtype)
+ fill_dtype = DatetimeTZDtype(tz=tz_aware_fixture)
+
+ fill_value = pd.Series([fill_value], dtype=fill_dtype)[0]
+
+ # filling any numpy dtype with datetimetz casts to object
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_timedelta64_with_any(timedelta64_dtype, any_numpy_dtype):
+ dtype = np.dtype(timedelta64_dtype)
+ fill_dtype = np.dtype(any_numpy_dtype)
+
+ # create array of given dtype; casts "1" to correct dtype
+ fill_value = np.array([1], dtype=fill_dtype)[0]
+
+ # filling timedelta with anything but timedelta casts to object
+ if fill_dtype.kind == "m":
+ expected_dtype = dtype
+ # for timedelta dtypes, scalar values get cast to pd.Timedelta.value
+ exp_val_for_scalar = pd.Timedelta(fill_value).to_timedelta64()
+ else:
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+@pytest.mark.parametrize(
+ "fill_value",
+ [pd.Timedelta(days=1), np.timedelta64(24, "h"), datetime.timedelta(1)],
+ ids=["pd.Timedelta", "np.timedelta64", "datetime.timedelta"],
+)
+def test_maybe_promote_any_with_timedelta64(any_numpy_dtype, fill_value):
+ dtype = np.dtype(any_numpy_dtype)
+
+ # filling anything but timedelta with timedelta casts to object
+ if dtype.kind == "m":
+ expected_dtype = dtype
+ # for timedelta dtypes, scalar values get cast to pd.Timedelta.value
+ exp_val_for_scalar = pd.Timedelta(fill_value).to_timedelta64()
+ else:
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_string_with_any(string_dtype, any_numpy_dtype):
+ dtype = np.dtype(string_dtype)
+ fill_dtype = np.dtype(any_numpy_dtype)
+
+ # create array of given dtype; casts "1" to correct dtype
+ fill_value = np.array([1], dtype=fill_dtype)[0]
+
+ # filling string with anything casts to object
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_any_with_string(any_numpy_dtype):
+ dtype = np.dtype(any_numpy_dtype)
+
+ # create array of given dtype
+ fill_value = "abc"
+
+ # filling anything with a string casts to object
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_object_with_any(object_dtype, any_numpy_dtype):
+ dtype = np.dtype(object_dtype)
+ fill_dtype = np.dtype(any_numpy_dtype)
+
+ # create array of given dtype; casts "1" to correct dtype
+ fill_value = np.array([1], dtype=fill_dtype)[0]
+
+ # filling object with anything stays object
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_any_with_object(any_numpy_dtype):
+ dtype = np.dtype(any_numpy_dtype)
+
+ # create array of object dtype from a scalar value (i.e. passing
+ # dtypes.common.is_scalar), which can however not be cast to int/float etc.
+ fill_value = pd.DateOffset(1)
+
+ # filling object with anything stays object
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
+
+
+def test_maybe_promote_any_numpy_dtype_with_na(any_numpy_dtype, nulls_fixture):
+ fill_value = nulls_fixture
+ dtype = np.dtype(any_numpy_dtype)
+
+ if isinstance(fill_value, Decimal):
+ # Subject to change, but ATM (When Decimal(NAN) is being added to nulls_fixture)
+ # this is the existing behavior in maybe_promote,
+ # hinges on is_valid_na_for_dtype
+ if dtype.kind in "iufc":
+ if dtype.kind in "iu":
+ expected_dtype = np.dtype(np.float64)
+ else:
+ expected_dtype = dtype
+ exp_val_for_scalar = np.nan
+ else:
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+ elif dtype.kind in "iu" and fill_value is not NaT:
+ # integer + other missing value (np.nan / None) casts to float
+ expected_dtype = np.float64
+ exp_val_for_scalar = np.nan
+ elif dtype == object and fill_value is NaT:
+ # inserting into object does not cast the value
+ # but *does* cast None to np.nan
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = fill_value
+ elif dtype.kind in "mM":
+ # datetime / timedelta cast all missing values to dtyped-NaT
+ expected_dtype = dtype
+ exp_val_for_scalar = dtype.type("NaT", "ns")
+ elif fill_value is NaT:
+ # NaT upcasts everything that's not datetime/timedelta to object
+ expected_dtype = np.dtype(object)
+ exp_val_for_scalar = NaT
+ elif dtype.kind in "fc":
+ # float / complex + missing value (!= NaT) stays the same
+ expected_dtype = dtype
+ exp_val_for_scalar = np.nan
+ else:
+ # all other cases cast to object, and use np.nan as missing value
+ expected_dtype = np.dtype(object)
+ if fill_value is pd.NA:
+ exp_val_for_scalar = pd.NA
+ else:
+ exp_val_for_scalar = np.nan
+
+ _check_promote(dtype, fill_value, expected_dtype, exp_val_for_scalar)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/test_common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/test_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1d8195321140a0bc9e43473b561c5f2d09a1973
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/test_common.py
@@ -0,0 +1,882 @@
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from pandas.compat import HAS_PYARROW
+from pandas.errors import Pandas4Warning
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.astype import astype_array
+import pandas.core.dtypes.common as com
+from pandas.core.dtypes.dtypes import (
+ CategoricalDtype,
+ CategoricalDtypeType,
+ DatetimeTZDtype,
+ ExtensionDtype,
+ IntervalDtype,
+ PeriodDtype,
+)
+from pandas.core.dtypes.missing import isna
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.types import pandas_dtype
+from pandas.arrays import SparseArray
+from pandas.util.version import Version
+
+
+# EA & Actual Dtypes
+def to_ea_dtypes(dtypes):
+ """convert list of string dtypes to EA dtype"""
+ return [getattr(pd, dt + "Dtype") for dt in dtypes]
+
+
+def to_numpy_dtypes(dtypes):
+ """convert list of string dtypes to numpy dtype"""
+ return [getattr(np, dt) for dt in dtypes if isinstance(dt, str)]
+
+
+class TestNumpyEADtype:
+ # Passing invalid dtype, both as a string or object, must raise TypeError
+ # Per issue GH15520
+ @pytest.mark.parametrize("box", [pd.Timestamp, "pd.Timestamp", list])
+ def test_invalid_dtype_error(self, box):
+ with pytest.raises(TypeError, match="not understood"):
+ com.pandas_dtype(box)
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ object,
+ "float64",
+ np.object_,
+ np.dtype("object"),
+ "O",
+ np.float64,
+ float,
+ np.dtype("float64"),
+ "object_",
+ ],
+ )
+ def test_pandas_dtype_valid(self, dtype):
+ assert com.pandas_dtype(dtype) == dtype
+
+ @pytest.mark.parametrize(
+ "dtype", ["M8[ns]", "m8[ns]", "object", "float64", "int64"]
+ )
+ def test_numpy_dtype(self, dtype):
+ assert com.pandas_dtype(dtype) == np.dtype(dtype)
+
+ def test_numpy_string_dtype(self):
+ # do not parse freq-like string as period dtype
+ assert com.pandas_dtype("U") == np.dtype("U")
+ assert com.pandas_dtype("S") == np.dtype("S")
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ "datetime64[ns, US/Eastern]",
+ "datetime64[ns, Asia/Tokyo]",
+ "datetime64[ns, UTC]",
+ # GH#33885 check that the M8 alias is understood
+ "M8[ns, US/Eastern]",
+ "M8[ns, Asia/Tokyo]",
+ "M8[ns, UTC]",
+ ],
+ )
+ def test_datetimetz_dtype(self, dtype):
+ assert com.pandas_dtype(dtype) == DatetimeTZDtype.construct_from_string(dtype)
+ assert com.pandas_dtype(dtype) == dtype
+
+ def test_categorical_dtype(self):
+ assert com.pandas_dtype("category") == CategoricalDtype()
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ "period[D]",
+ "period[3M]",
+ "period[us]",
+ "Period[D]",
+ "Period[3M]",
+ "Period[us]",
+ ],
+ )
+ def test_period_dtype(self, dtype):
+ assert com.pandas_dtype(dtype) is not PeriodDtype(dtype)
+ assert com.pandas_dtype(dtype) == PeriodDtype(dtype)
+ assert com.pandas_dtype(dtype) == dtype
+
+
+dtypes = {
+ "datetime_tz": com.pandas_dtype("datetime64[ns, US/Eastern]"),
+ "datetime": com.pandas_dtype("datetime64[ns]"),
+ "timedelta": com.pandas_dtype("timedelta64[ns]"),
+ "period": PeriodDtype("D"),
+ "integer": np.dtype(np.int64),
+ "float": np.dtype(np.float64),
+ "object": np.dtype(object),
+ "category": com.pandas_dtype("category"),
+ "string": pd.StringDtype("python"),
+}
+
+
+@pytest.mark.parametrize("name1,dtype1", list(dtypes.items()), ids=lambda x: str(x))
+@pytest.mark.parametrize("name2,dtype2", list(dtypes.items()), ids=lambda x: str(x))
+def test_dtype_equal(name1, dtype1, name2, dtype2):
+ # match equal to self, but not equal to other
+ assert com.is_dtype_equal(dtype1, dtype1)
+ if name1 != name2:
+ assert not com.is_dtype_equal(dtype1, dtype2)
+
+
+@pytest.mark.parametrize("name,dtype", list(dtypes.items()), ids=lambda x: str(x))
+def test_pyarrow_string_import_error(name, dtype):
+ # GH-44276
+ assert not com.is_dtype_equal(dtype, "string[pyarrow]")
+
+
+@pytest.mark.parametrize(
+ "dtype1,dtype2",
+ [
+ (np.int8, np.int64),
+ (np.int16, np.int64),
+ (np.int32, np.int64),
+ (np.float32, np.float64),
+ (PeriodDtype("D"), PeriodDtype("2D")), # PeriodType
+ (
+ com.pandas_dtype("datetime64[ns, US/Eastern]"),
+ com.pandas_dtype("datetime64[ns, CET]"),
+ ), # Datetime
+ (None, None), # gh-15941: no exception should be raised.
+ ],
+)
+def test_dtype_equal_strict(dtype1, dtype2):
+ assert not com.is_dtype_equal(dtype1, dtype2)
+
+
+def get_is_dtype_funcs():
+ """
+ Get all functions in pandas.core.dtypes.common that
+ begin with 'is_' and end with 'dtype'
+
+ """
+ fnames = [f for f in dir(com) if (f.startswith("is_") and f.endswith("dtype"))]
+ fnames.remove("is_string_or_object_np_dtype") # fastpath requires np.dtype obj
+ return [getattr(com, fname) for fname in fnames]
+
+
+@pytest.mark.filterwarnings(
+ "ignore:is_categorical_dtype is deprecated:DeprecationWarning"
+)
+@pytest.mark.parametrize("func", get_is_dtype_funcs(), ids=lambda x: x.__name__)
+def test_get_dtype_error_catch(func):
+ # see gh-15941
+ #
+ # No exception should be raised.
+
+ msg = f"{func.__name__} is deprecated"
+ warn = None
+ if (
+ func is com.is_int64_dtype
+ or func is com.is_interval_dtype
+ or func is com.is_datetime64tz_dtype
+ or func is com.is_categorical_dtype
+ or func is com.is_period_dtype
+ ):
+ warn = Pandas4Warning
+
+ with tm.assert_produces_warning(warn, match=msg):
+ assert not func(None)
+
+
+def test_is_object():
+ assert com.is_object_dtype(object)
+ assert com.is_object_dtype(np.array([], dtype=object))
+
+ assert not com.is_object_dtype(int)
+ assert not com.is_object_dtype(np.array([], dtype=int))
+ assert not com.is_object_dtype([1, 2, 3])
+
+
+@pytest.mark.parametrize(
+ "check_scipy", [False, pytest.param(True, marks=td.skip_if_no("scipy"))]
+)
+def test_is_sparse(check_scipy):
+ msg = "is_sparse is deprecated"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ assert com.is_sparse(SparseArray([1, 2, 3]))
+
+ assert not com.is_sparse(np.array([1, 2, 3]))
+
+ if check_scipy:
+ import scipy.sparse
+
+ assert not com.is_sparse(scipy.sparse.bsr_matrix([1, 2, 3]))
+
+
+def test_is_scipy_sparse():
+ sp_sparse = pytest.importorskip("scipy.sparse")
+
+ assert com.is_scipy_sparse(sp_sparse.bsr_matrix([1, 2, 3]))
+
+ assert not com.is_scipy_sparse(SparseArray([1, 2, 3]))
+
+
+def test_is_datetime64_dtype():
+ assert not com.is_datetime64_dtype(object)
+ assert not com.is_datetime64_dtype([1, 2, 3])
+ assert not com.is_datetime64_dtype(np.array([], dtype=int))
+
+ assert com.is_datetime64_dtype(np.datetime64)
+ assert com.is_datetime64_dtype(np.array([], dtype=np.datetime64))
+
+
+def test_is_datetime64tz_dtype():
+ msg = "is_datetime64tz_dtype is deprecated"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ assert not com.is_datetime64tz_dtype(object)
+ assert not com.is_datetime64tz_dtype([1, 2, 3])
+ assert not com.is_datetime64tz_dtype(pd.DatetimeIndex([1, 2, 3]))
+ assert com.is_datetime64tz_dtype(pd.DatetimeIndex(["2000"], tz="US/Eastern"))
+
+
+def test_custom_ea_kind_M_not_datetime64tz():
+ # GH 34986
+ class NotTZDtype(ExtensionDtype):
+ @property
+ def kind(self) -> str:
+ return "M"
+
+ not_tz_dtype = NotTZDtype()
+ msg = "is_datetime64tz_dtype is deprecated"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ assert not com.is_datetime64tz_dtype(not_tz_dtype)
+ assert not com.needs_i8_conversion(not_tz_dtype)
+
+
+def test_is_timedelta64_dtype():
+ assert not com.is_timedelta64_dtype(object)
+ assert not com.is_timedelta64_dtype(None)
+ assert not com.is_timedelta64_dtype([1, 2, 3])
+ assert not com.is_timedelta64_dtype(np.array([], dtype=np.datetime64))
+ assert not com.is_timedelta64_dtype("0 days")
+ assert not com.is_timedelta64_dtype("0 days 00:00:00")
+ assert not com.is_timedelta64_dtype(["0 days 00:00:00"])
+ assert not com.is_timedelta64_dtype("NO DATE")
+
+ assert com.is_timedelta64_dtype(np.timedelta64)
+ assert com.is_timedelta64_dtype(pd.Series([], dtype="timedelta64[ns]"))
+ assert com.is_timedelta64_dtype(pd.to_timedelta(["0 days", "1 days"]))
+
+
+def test_is_period_dtype():
+ msg = "is_period_dtype is deprecated"
+ with tm.assert_produces_warning(DeprecationWarning, match=msg):
+ assert not com.is_period_dtype(object)
+ assert not com.is_period_dtype([1, 2, 3])
+ assert not com.is_period_dtype(pd.Period("2017-01-01"))
+
+ assert com.is_period_dtype(PeriodDtype(freq="D"))
+ assert com.is_period_dtype(pd.PeriodIndex([], freq="Y"))
+
+
+def test_is_interval_dtype():
+ msg = "is_interval_dtype is deprecated"
+ with tm.assert_produces_warning(DeprecationWarning, match=msg):
+ assert not com.is_interval_dtype(object)
+ assert not com.is_interval_dtype([1, 2, 3])
+
+ assert com.is_interval_dtype(IntervalDtype())
+
+ interval = pd.Interval(1, 2, closed="right")
+ assert not com.is_interval_dtype(interval)
+ assert com.is_interval_dtype(pd.IntervalIndex([interval]))
+
+
+def test_is_categorical_dtype():
+ msg = "is_categorical_dtype is deprecated"
+ with tm.assert_produces_warning(DeprecationWarning, match=msg):
+ assert not com.is_categorical_dtype(object)
+ assert not com.is_categorical_dtype([1, 2, 3])
+
+ assert com.is_categorical_dtype(CategoricalDtype())
+ assert com.is_categorical_dtype(pd.Categorical([1, 2, 3]))
+ assert com.is_categorical_dtype(pd.CategoricalIndex([1, 2, 3]))
+
+
+@pytest.mark.parametrize(
+ "dtype, expected",
+ [
+ (int, False),
+ (pd.Series([1, 2]), False),
+ (str, True),
+ (object, True),
+ (np.array(["a", "b"]), True),
+ (pd.StringDtype(), True),
+ (pd.Index([], dtype="O"), True),
+ ],
+)
+def test_is_string_dtype(dtype, expected):
+ # GH#54661
+
+ result = com.is_string_dtype(dtype)
+ assert result is expected
+
+
+@pytest.mark.parametrize(
+ "data",
+ [[(0, 1), (1, 1)], pd.Categorical([1, 2, 3]), np.array([1, 2], dtype=object)],
+)
+def test_is_string_dtype_arraylike_with_object_elements_not_strings(data):
+ # GH 15585
+ assert not com.is_string_dtype(pd.Series(data))
+
+
+def test_is_string_dtype_nullable(nullable_string_dtype):
+ assert com.is_string_dtype(pd.array(["a", "b"], dtype=nullable_string_dtype))
+
+
+integer_dtypes: list = []
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ *integer_dtypes,
+ pd.Series([1, 2]),
+ *tm.ALL_INT_NUMPY_DTYPES,
+ *to_numpy_dtypes(tm.ALL_INT_NUMPY_DTYPES),
+ *tm.ALL_INT_EA_DTYPES,
+ *to_ea_dtypes(tm.ALL_INT_EA_DTYPES),
+ ],
+)
+def test_is_integer_dtype(dtype):
+ assert com.is_integer_dtype(dtype)
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ str,
+ float,
+ np.datetime64,
+ np.timedelta64,
+ pd.Index([1, 2.0]),
+ np.array(["a", "b"]),
+ np.array([], dtype=np.timedelta64),
+ ],
+)
+def test_is_not_integer_dtype(dtype):
+ assert not com.is_integer_dtype(dtype)
+
+
+signed_integer_dtypes: list = []
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ *signed_integer_dtypes,
+ pd.Series([1, 2]),
+ *tm.SIGNED_INT_NUMPY_DTYPES,
+ *to_numpy_dtypes(tm.SIGNED_INT_NUMPY_DTYPES),
+ *tm.SIGNED_INT_EA_DTYPES,
+ *to_ea_dtypes(tm.SIGNED_INT_EA_DTYPES),
+ ],
+)
+def test_is_signed_integer_dtype(dtype):
+ assert com.is_integer_dtype(dtype)
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ str,
+ float,
+ np.datetime64,
+ np.timedelta64,
+ pd.Index([1, 2.0]),
+ np.array(["a", "b"]),
+ np.array([], dtype=np.timedelta64),
+ *tm.UNSIGNED_INT_NUMPY_DTYPES,
+ *to_numpy_dtypes(tm.UNSIGNED_INT_NUMPY_DTYPES),
+ *tm.UNSIGNED_INT_EA_DTYPES,
+ *to_ea_dtypes(tm.UNSIGNED_INT_EA_DTYPES),
+ ],
+)
+def test_is_not_signed_integer_dtype(dtype):
+ assert not com.is_signed_integer_dtype(dtype)
+
+
+unsigned_integer_dtypes: list = []
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ *unsigned_integer_dtypes,
+ pd.Series([1, 2], dtype=np.uint32),
+ *tm.UNSIGNED_INT_NUMPY_DTYPES,
+ *to_numpy_dtypes(tm.UNSIGNED_INT_NUMPY_DTYPES),
+ *tm.UNSIGNED_INT_EA_DTYPES,
+ *to_ea_dtypes(tm.UNSIGNED_INT_EA_DTYPES),
+ ],
+)
+def test_is_unsigned_integer_dtype(dtype):
+ assert com.is_unsigned_integer_dtype(dtype)
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ str,
+ float,
+ np.datetime64,
+ np.timedelta64,
+ pd.Index([1, 2.0]),
+ np.array(["a", "b"]),
+ np.array([], dtype=np.timedelta64),
+ *tm.SIGNED_INT_NUMPY_DTYPES,
+ *to_numpy_dtypes(tm.SIGNED_INT_NUMPY_DTYPES),
+ *tm.SIGNED_INT_EA_DTYPES,
+ *to_ea_dtypes(tm.SIGNED_INT_EA_DTYPES),
+ ],
+)
+def test_is_not_unsigned_integer_dtype(dtype):
+ assert not com.is_unsigned_integer_dtype(dtype)
+
+
+@pytest.mark.parametrize(
+ "dtype", [np.int64, np.array([1, 2], dtype=np.int64), "Int64", pd.Int64Dtype]
+)
+def test_is_int64_dtype(dtype):
+ msg = "is_int64_dtype is deprecated"
+ with tm.assert_produces_warning(DeprecationWarning, match=msg):
+ assert com.is_int64_dtype(dtype)
+
+
+def test_type_comparison_with_numeric_ea_dtype(any_numeric_ea_dtype):
+ # GH#43038
+ assert pandas_dtype(any_numeric_ea_dtype) == any_numeric_ea_dtype
+
+
+def test_type_comparison_with_real_numpy_dtype(any_real_numpy_dtype):
+ # GH#43038
+ assert pandas_dtype(any_real_numpy_dtype) == any_real_numpy_dtype
+
+
+def test_type_comparison_with_signed_int_ea_dtype_and_signed_int_numpy_dtype(
+ any_signed_int_ea_dtype, any_signed_int_numpy_dtype
+):
+ # GH#43038
+ assert not pandas_dtype(any_signed_int_ea_dtype) == any_signed_int_numpy_dtype
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ str,
+ float,
+ np.int32,
+ np.uint64,
+ pd.Index([1, 2.0]),
+ np.array(["a", "b"]),
+ np.array([1, 2], dtype=np.uint32),
+ "int8",
+ "Int8",
+ pd.Int8Dtype,
+ ],
+)
+def test_is_not_int64_dtype(dtype):
+ msg = "is_int64_dtype is deprecated"
+ with tm.assert_produces_warning(DeprecationWarning, match=msg):
+ assert not com.is_int64_dtype(dtype)
+
+
+def test_is_datetime64_any_dtype():
+ assert not com.is_datetime64_any_dtype(int)
+ assert not com.is_datetime64_any_dtype(str)
+ assert not com.is_datetime64_any_dtype(np.array([1, 2]))
+ assert not com.is_datetime64_any_dtype(np.array(["a", "b"]))
+
+ assert com.is_datetime64_any_dtype(np.datetime64)
+ assert com.is_datetime64_any_dtype(np.array([], dtype=np.datetime64))
+ assert com.is_datetime64_any_dtype(DatetimeTZDtype("ns", "US/Eastern"))
+ assert com.is_datetime64_any_dtype(
+ pd.DatetimeIndex([1, 2, 3], dtype="datetime64[ns]")
+ )
+
+
+def test_is_datetime64_ns_dtype():
+ assert not com.is_datetime64_ns_dtype(int)
+ assert not com.is_datetime64_ns_dtype(str)
+ assert not com.is_datetime64_ns_dtype(np.datetime64)
+ assert not com.is_datetime64_ns_dtype(np.array([1, 2]))
+ assert not com.is_datetime64_ns_dtype(np.array(["a", "b"]))
+ assert not com.is_datetime64_ns_dtype(np.array([], dtype=np.datetime64))
+
+ # This datetime array has the wrong unit (ps instead of ns)
+ assert not com.is_datetime64_ns_dtype(np.array([], dtype="datetime64[ps]"))
+
+ assert com.is_datetime64_ns_dtype(DatetimeTZDtype("ns", "US/Eastern"))
+ assert com.is_datetime64_ns_dtype(
+ pd.DatetimeIndex([1, 2, 3], dtype=np.dtype("datetime64[ns]"))
+ )
+
+ # non-nano dt64tz
+ assert not com.is_datetime64_ns_dtype(DatetimeTZDtype("us", "US/Eastern"))
+
+
+def test_is_timedelta64_ns_dtype():
+ assert not com.is_timedelta64_ns_dtype(np.dtype("m8[ps]"))
+ assert not com.is_timedelta64_ns_dtype(np.array([1, 2], dtype=np.timedelta64))
+
+ assert com.is_timedelta64_ns_dtype(np.dtype("m8[ns]"))
+ assert com.is_timedelta64_ns_dtype(np.array([1, 2], dtype="m8[ns]"))
+
+
+def test_is_numeric_v_string_like():
+ assert not com.is_numeric_v_string_like(np.array([1]), 1)
+ assert not com.is_numeric_v_string_like(np.array([1]), np.array([2]))
+ assert not com.is_numeric_v_string_like(np.array(["foo"]), np.array(["foo"]))
+
+ assert com.is_numeric_v_string_like(np.array([1]), "foo")
+ assert com.is_numeric_v_string_like(np.array([1, 2]), np.array(["foo"]))
+ assert com.is_numeric_v_string_like(np.array(["foo"]), np.array([1, 2]))
+
+
+def test_needs_i8_conversion():
+ assert not com.needs_i8_conversion(str)
+ assert not com.needs_i8_conversion(np.int64)
+ assert not com.needs_i8_conversion(pd.Series([1, 2]))
+ assert not com.needs_i8_conversion(np.array(["a", "b"]))
+
+ assert not com.needs_i8_conversion(np.datetime64)
+ assert com.needs_i8_conversion(np.dtype(np.datetime64))
+ assert not com.needs_i8_conversion(pd.Series([], dtype="timedelta64[ns]"))
+ assert com.needs_i8_conversion(pd.Series([], dtype="timedelta64[ns]").dtype)
+ assert not com.needs_i8_conversion(pd.DatetimeIndex(["2000"], tz="US/Eastern"))
+ assert com.needs_i8_conversion(pd.DatetimeIndex(["2000"], tz="US/Eastern").dtype)
+
+
+def test_is_numeric_dtype():
+ assert not com.is_numeric_dtype(str)
+ assert not com.is_numeric_dtype(np.datetime64)
+ assert not com.is_numeric_dtype(np.timedelta64)
+ assert not com.is_numeric_dtype(np.array(["a", "b"]))
+ assert not com.is_numeric_dtype(np.array([], dtype=np.timedelta64))
+
+ assert com.is_numeric_dtype(int)
+ assert com.is_numeric_dtype(float)
+ assert com.is_numeric_dtype(np.uint64)
+ assert com.is_numeric_dtype(pd.Series([1, 2]))
+ assert com.is_numeric_dtype(pd.Index([1, 2.0]))
+
+ class MyNumericDType(ExtensionDtype):
+ @property
+ def type(self):
+ return str
+
+ @property
+ def name(self):
+ raise NotImplementedError
+
+ def construct_array_type(self):
+ raise NotImplementedError
+
+ def _is_numeric(self) -> bool:
+ return True
+
+ assert com.is_numeric_dtype(MyNumericDType())
+
+
+def test_is_any_real_numeric_dtype():
+ assert not com.is_any_real_numeric_dtype(str)
+ assert not com.is_any_real_numeric_dtype(bool)
+ assert not com.is_any_real_numeric_dtype(complex)
+ assert not com.is_any_real_numeric_dtype(object)
+ assert not com.is_any_real_numeric_dtype(np.datetime64)
+ assert not com.is_any_real_numeric_dtype(np.array(["a", "b", complex(1, 2)]))
+ assert not com.is_any_real_numeric_dtype(pd.DataFrame([complex(1, 2), True]))
+
+ assert com.is_any_real_numeric_dtype(int)
+ assert com.is_any_real_numeric_dtype(float)
+ assert com.is_any_real_numeric_dtype(np.array([1, 2.5]))
+
+
+def test_is_float_dtype():
+ assert not com.is_float_dtype(str)
+ assert not com.is_float_dtype(int)
+ assert not com.is_float_dtype(pd.Series([1, 2]))
+ assert not com.is_float_dtype(np.array(["a", "b"]))
+
+ assert com.is_float_dtype(float)
+ assert com.is_float_dtype(pd.Index([1, 2.0]))
+
+
+def test_is_bool_dtype():
+ assert not com.is_bool_dtype(int)
+ assert not com.is_bool_dtype(str)
+ assert not com.is_bool_dtype(pd.Series([1, 2]))
+ assert not com.is_bool_dtype(pd.Series(["a", "b"], dtype="category"))
+ assert not com.is_bool_dtype(np.array(["a", "b"]))
+ assert not com.is_bool_dtype(pd.Index(["a", "b"]))
+ assert not com.is_bool_dtype("Int64")
+
+ assert com.is_bool_dtype(bool)
+ assert com.is_bool_dtype(np.bool_)
+ assert com.is_bool_dtype(pd.Series([True, False], dtype="category"))
+ assert com.is_bool_dtype(np.array([True, False]))
+ assert com.is_bool_dtype(pd.Index([True, False]))
+
+ assert com.is_bool_dtype(pd.BooleanDtype())
+ assert com.is_bool_dtype(pd.array([True, False, None], dtype="boolean"))
+ assert com.is_bool_dtype("boolean")
+
+
+def test_is_bool_dtype_numpy_error():
+ # GH39010
+ assert not com.is_bool_dtype("0 - Name")
+
+
+@pytest.mark.parametrize(
+ "check_scipy", [False, pytest.param(True, marks=td.skip_if_no("scipy"))]
+)
+def test_is_extension_array_dtype(check_scipy):
+ assert not com.is_extension_array_dtype([1, 2, 3])
+ assert not com.is_extension_array_dtype(np.array([1, 2, 3]))
+ assert not com.is_extension_array_dtype(pd.DatetimeIndex([1, 2, 3]))
+
+ cat = pd.Categorical([1, 2, 3])
+ assert com.is_extension_array_dtype(cat)
+ assert com.is_extension_array_dtype(pd.Series(cat))
+ assert com.is_extension_array_dtype(SparseArray([1, 2, 3]))
+ assert com.is_extension_array_dtype(pd.DatetimeIndex(["2000"], tz="US/Eastern"))
+
+ dtype = DatetimeTZDtype("ns", tz="US/Eastern")
+ s = pd.Series([], dtype=dtype)
+ assert com.is_extension_array_dtype(s)
+
+ if check_scipy:
+ import scipy.sparse
+
+ assert not com.is_extension_array_dtype(scipy.sparse.bsr_matrix([1, 2, 3]))
+
+
+def test_is_complex_dtype():
+ assert not com.is_complex_dtype(int)
+ assert not com.is_complex_dtype(str)
+ assert not com.is_complex_dtype(pd.Series([1, 2]))
+ assert not com.is_complex_dtype(np.array(["a", "b"]))
+
+ assert com.is_complex_dtype(np.complex128)
+ assert com.is_complex_dtype(complex)
+ assert com.is_complex_dtype(np.array([1 + 1j, 5]))
+
+
+@pytest.mark.parametrize(
+ "input_param,result",
+ [
+ (int, np.dtype(int)),
+ ("int32", np.dtype("int32")),
+ (float, np.dtype(float)),
+ ("float64", np.dtype("float64")),
+ (np.dtype("float64"), np.dtype("float64")),
+ (str, np.dtype(str)),
+ (pd.Series([1, 2], dtype=np.dtype("int16")), np.dtype("int16")),
+ (pd.Series(["a", "b"], dtype=object), np.dtype(object)),
+ (pd.Index([1, 2]), np.dtype("int64")),
+ (pd.Index(["a", "b"], dtype=object), np.dtype(object)),
+ ("category", "category"),
+ (pd.Categorical(["a", "b"]).dtype, CategoricalDtype(["a", "b"])),
+ (pd.Categorical(["a", "b"]), CategoricalDtype(["a", "b"])),
+ (pd.CategoricalIndex(["a", "b"]).dtype, CategoricalDtype(["a", "b"])),
+ (pd.CategoricalIndex(["a", "b"]), CategoricalDtype(["a", "b"])),
+ (CategoricalDtype(), CategoricalDtype()),
+ (pd.DatetimeIndex([1, 2]), np.dtype("=M8[ns]")),
+ (pd.DatetimeIndex([1, 2]).dtype, np.dtype("=M8[ns]")),
+ (" df.two.sum()
+
+ with tm.assert_produces_warning(None):
+ # successfully modify column in place
+ # this should not raise a warning
+ df.one += 1
+ assert df.one.iloc[0] == 2
+
+ with tm.assert_produces_warning(None):
+ # successfully add an attribute to a series
+ # this should not raise a warning
+ df.two.not_an_index = [1, 2]
+
+ with tm.assert_produces_warning(UserWarning, match="doesn't allow columns"):
+ # warn when setting column to nonexistent name
+ df.four = df.two + 2
+ assert df.four.sum() > df.two.sum()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/test_inference.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/test_inference.py
new file mode 100644
index 0000000000000000000000000000000000000000..5cea1276f7f4bd5d5077b29aa698484d324ccc7f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/test_inference.py
@@ -0,0 +1,2155 @@
+"""
+These the test the public routines exposed in types/common.py
+related to inference and not otherwise tested in types/test_common.py
+
+"""
+
+import collections
+from collections import namedtuple
+from collections.abc import Iterator
+from datetime import (
+ date,
+ datetime,
+ time,
+ timedelta,
+ timezone,
+)
+from decimal import Decimal
+from fractions import Fraction
+from io import StringIO
+import itertools
+from numbers import Number
+import re
+import sys
+from typing import (
+ Generic,
+ TypeVar,
+)
+
+import numpy as np
+import pytest
+
+from pandas._libs import (
+ lib,
+ missing as libmissing,
+ ops as libops,
+)
+from pandas.compat import PY312
+from pandas.compat.numpy import np_version_gt2
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes import inference
+from pandas.core.dtypes.cast import find_result_type
+from pandas.core.dtypes.common import (
+ ensure_int32,
+ is_bool,
+ is_complex,
+ is_datetime64_any_dtype,
+ is_datetime64_dtype,
+ is_datetime64_ns_dtype,
+ is_datetime64tz_dtype,
+ is_float,
+ is_integer,
+ is_number,
+ is_scalar,
+ is_scipy_sparse,
+ is_timedelta64_dtype,
+ is_timedelta64_ns_dtype,
+)
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ DateOffset,
+ DatetimeIndex,
+ Index,
+ Interval,
+ Period,
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.core.arrays import (
+ BooleanArray,
+ FloatingArray,
+ IntegerArray,
+)
+
+
+@pytest.fixture(params=[True, False], ids=str)
+def coerce(request):
+ return request.param
+
+
+class MockNumpyLikeArray:
+ """
+ A class which is numpy-like (e.g. Pint's Quantity) but not actually numpy
+
+ The key is that it is not actually a numpy array so
+ ``util.is_array(mock_numpy_like_array_instance)`` returns ``False``. Other
+ important properties are that the class defines a :meth:`__iter__` method
+ (so that ``isinstance(abc.Iterable)`` returns ``True``) and has a
+ :meth:`ndim` property, as pandas special-cases 0-dimensional arrays in some
+ cases.
+
+ We expect pandas to behave with respect to such duck arrays exactly as
+ with real numpy arrays. In particular, a 0-dimensional duck array is *NOT*
+ a scalar (`is_scalar(np.array(1)) == False`), but it is not list-like either.
+ """
+
+ def __init__(self, values) -> None:
+ self._values = values
+
+ def __iter__(self) -> Iterator:
+ iter_values = iter(self._values)
+
+ def it_outer():
+ yield from iter_values
+
+ return it_outer()
+
+ def __len__(self) -> int:
+ return len(self._values)
+
+ def __array__(self, dtype=None, copy=None):
+ return np.asarray(self._values, dtype=dtype)
+
+ @property
+ def ndim(self):
+ return self._values.ndim
+
+ @property
+ def dtype(self):
+ return self._values.dtype
+
+ @property
+ def size(self):
+ return self._values.size
+
+ @property
+ def shape(self):
+ return self._values.shape
+
+
+# collect all objects to be tested for list-like-ness; use tuples of objects,
+# whether they are list-like or not (special casing for sets), and their ID
+ll_params = [
+ ([1], True, "list"),
+ ([], True, "list-empty"),
+ ((1,), True, "tuple"),
+ ((), True, "tuple-empty"),
+ ({"a": 1}, True, "dict"),
+ ({}, True, "dict-empty"),
+ ({"a", 1}, "set", "set"),
+ (set(), "set", "set-empty"),
+ (frozenset({"a", 1}), "set", "frozenset"),
+ (frozenset(), "set", "frozenset-empty"),
+ (iter([1, 2]), True, "iterator"),
+ (iter([]), True, "iterator-empty"),
+ ((x for x in [1, 2]), True, "generator"),
+ ((_ for _ in []), True, "generator-empty"),
+ (Series([1]), True, "Series"),
+ (Series([], dtype=object), True, "Series-empty"),
+ # Series.str will still raise a TypeError if iterated
+ (Series(["a"]).str, True, "StringMethods"),
+ (Series([], dtype="O").str, True, "StringMethods-empty"),
+ (Index([1]), True, "Index"),
+ (Index([]), True, "Index-empty"),
+ (DataFrame([[1]]), True, "DataFrame"),
+ (DataFrame(), True, "DataFrame-empty"),
+ (np.ndarray((2,) * 1), True, "ndarray-1d"),
+ (np.array([]), True, "ndarray-1d-empty"),
+ (np.ndarray((2,) * 2), True, "ndarray-2d"),
+ (np.array([[]]), True, "ndarray-2d-empty"),
+ (np.ndarray((2,) * 3), True, "ndarray-3d"),
+ (np.array([[[]]]), True, "ndarray-3d-empty"),
+ (np.ndarray((2,) * 4), True, "ndarray-4d"),
+ (np.array([[[[]]]]), True, "ndarray-4d-empty"),
+ (np.array(2), False, "ndarray-0d"),
+ (MockNumpyLikeArray(np.ndarray((2,) * 1)), True, "duck-ndarray-1d"),
+ (MockNumpyLikeArray(np.array([])), True, "duck-ndarray-1d-empty"),
+ (MockNumpyLikeArray(np.ndarray((2,) * 2)), True, "duck-ndarray-2d"),
+ (MockNumpyLikeArray(np.array([[]])), True, "duck-ndarray-2d-empty"),
+ (MockNumpyLikeArray(np.ndarray((2,) * 3)), True, "duck-ndarray-3d"),
+ (MockNumpyLikeArray(np.array([[[]]])), True, "duck-ndarray-3d-empty"),
+ (MockNumpyLikeArray(np.ndarray((2,) * 4)), True, "duck-ndarray-4d"),
+ (MockNumpyLikeArray(np.array([[[[]]]])), True, "duck-ndarray-4d-empty"),
+ (MockNumpyLikeArray(np.array(2)), False, "duck-ndarray-0d"),
+ (1, False, "int"),
+ (b"123", False, "bytes"),
+ (b"", False, "bytes-empty"),
+ ("123", False, "string"),
+ ("", False, "string-empty"),
+ (str, False, "string-type"),
+ (object(), False, "object"),
+ (np.nan, False, "NaN"),
+ (None, False, "None"),
+]
+objs, expected, ids = zip(*ll_params, strict=True)
+
+
+@pytest.fixture(params=zip(objs, expected, strict=True), ids=ids)
+def maybe_list_like(request):
+ return request.param
+
+
+def test_is_list_like(maybe_list_like):
+ obj, expected = maybe_list_like
+ expected = True if expected == "set" else expected
+ assert inference.is_list_like(obj) == expected
+
+
+def test_is_list_like_disallow_sets(maybe_list_like):
+ obj, expected = maybe_list_like
+ expected = False if expected == "set" else expected
+ assert inference.is_list_like(obj, allow_sets=False) == expected
+
+
+def test_is_list_like_recursion():
+ # GH 33721
+ # interpreter would crash with SIGABRT
+ def list_like():
+ inference.is_list_like([])
+ list_like()
+
+ rec_limit = sys.getrecursionlimit()
+ try:
+ # Limit to avoid stack overflow on Windows CI
+ sys.setrecursionlimit(100)
+ with tm.external_error_raised(RecursionError):
+ list_like()
+ finally:
+ sys.setrecursionlimit(rec_limit)
+
+
+def test_is_list_like_iter_is_none():
+ # GH 43373
+ # is_list_like was yielding false positives with __iter__ == None
+ class NotListLike:
+ def __getitem__(self, item):
+ return self
+
+ __iter__ = None
+
+ assert not inference.is_list_like(NotListLike())
+
+
+def test_is_list_like_generic():
+ # GH 49649
+ # is_list_like was yielding false positives for Generic classes in python 3.11
+ T = TypeVar("T")
+
+ class MyDataFrame(DataFrame, Generic[T]): ...
+
+ tstc = MyDataFrame[int]
+ tst = MyDataFrame[int]({"x": [1, 2, 3]})
+
+ assert not inference.is_list_like(tstc)
+ assert isinstance(tst, DataFrame)
+ assert inference.is_list_like(tst)
+
+
+def test_is_list_like_native_container_types():
+ # GH 61565
+ # is_list_like was yielding false positives for native container types
+ assert not inference.is_list_like(list[int])
+ assert not inference.is_list_like(list[str])
+ assert not inference.is_list_like(tuple[int])
+ assert not inference.is_list_like(tuple[str])
+
+
+def test_is_sequence():
+ is_seq = inference.is_sequence
+ assert is_seq((1, 2))
+ assert is_seq([1, 2])
+ assert not is_seq("abcd")
+ assert not is_seq(np.int64)
+
+ class A:
+ def __getitem__(self, item):
+ return 1
+
+ assert not is_seq(A())
+
+
+def test_is_array_like():
+ assert inference.is_array_like(Series([], dtype=object))
+ assert inference.is_array_like(Series([1, 2]))
+ assert inference.is_array_like(np.array(["a", "b"]))
+ assert inference.is_array_like(Index(["2016-01-01"]))
+ assert inference.is_array_like(np.array([2, 3]))
+ assert inference.is_array_like(MockNumpyLikeArray(np.array([2, 3])))
+
+ class DtypeList(list):
+ dtype = "special"
+
+ assert inference.is_array_like(DtypeList())
+
+ assert not inference.is_array_like([1, 2, 3])
+ assert not inference.is_array_like(())
+ assert not inference.is_array_like("foo")
+ assert not inference.is_array_like(123)
+
+
+@pytest.mark.parametrize(
+ "inner",
+ [
+ [],
+ [1],
+ (1,),
+ (1, 2),
+ {"a": 1},
+ {1, "a"},
+ Series([1]),
+ Series([], dtype=object),
+ Series(["a"]).str,
+ (x for x in range(5)),
+ ],
+)
+@pytest.mark.parametrize("outer", [list, Series, np.array, tuple])
+def test_is_nested_list_like_passes(inner, outer):
+ result = outer([inner for _ in range(5)])
+ assert inference.is_list_like(result)
+
+
+@pytest.mark.parametrize(
+ "obj",
+ [
+ "abc",
+ [],
+ [1],
+ (1,),
+ ["a"],
+ "a",
+ {"a"},
+ [1, 2, 3],
+ Series([1]),
+ DataFrame({"A": [1]}),
+ ([1, 2] for _ in range(5)),
+ ],
+)
+def test_is_nested_list_like_fails(obj):
+ assert not inference.is_nested_list_like(obj)
+
+
+@pytest.mark.parametrize("ll", [{}, {"A": 1}, Series([1]), collections.defaultdict()])
+def test_is_dict_like_passes(ll):
+ assert inference.is_dict_like(ll)
+
+
+@pytest.mark.parametrize(
+ "ll",
+ [
+ "1",
+ 1,
+ [1, 2],
+ (1, 2),
+ range(2),
+ Index([1]),
+ dict,
+ collections.defaultdict,
+ Series,
+ ],
+)
+def test_is_dict_like_fails(ll):
+ assert not inference.is_dict_like(ll)
+
+
+@pytest.mark.parametrize("has_keys", [True, False])
+@pytest.mark.parametrize("has_getitem", [True, False])
+@pytest.mark.parametrize("has_contains", [True, False])
+def test_is_dict_like_duck_type(has_keys, has_getitem, has_contains):
+ class DictLike:
+ def __init__(self, d) -> None:
+ self.d = d
+
+ if has_keys:
+
+ def keys(self):
+ return self.d.keys()
+
+ if has_getitem:
+
+ def __getitem__(self, key):
+ return self.d.__getitem__(key)
+
+ if has_contains:
+
+ def __contains__(self, key) -> bool:
+ return self.d.__contains__(key)
+
+ d = DictLike({1: 2})
+ result = inference.is_dict_like(d)
+ expected = has_keys and has_getitem and has_contains
+
+ assert result is expected
+
+
+def test_is_file_like():
+ class MockFile:
+ pass
+
+ is_file = inference.is_file_like
+
+ data = StringIO("data")
+ assert is_file(data)
+
+ # No read / write attributes
+ # No iterator attributes
+ m = MockFile()
+ assert not is_file(m)
+
+ MockFile.write = lambda self: 0
+
+ # Write attribute but not an iterator
+ m = MockFile()
+ assert not is_file(m)
+
+ # gh-16530: Valid iterator just means we have the
+ # __iter__ attribute for our purposes.
+ MockFile.__iter__ = lambda self: self
+
+ # Valid write-only file
+ m = MockFile()
+ assert is_file(m)
+
+ del MockFile.write
+ MockFile.read = lambda self: 0
+
+ # Valid read-only file
+ m = MockFile()
+ assert is_file(m)
+
+ # Iterator but no read / write attributes
+ data = [1, 2, 3]
+ assert not is_file(data)
+
+
+test_tuple = collections.namedtuple("test_tuple", ["a", "b", "c"])
+
+
+@pytest.mark.parametrize("ll", [test_tuple(1, 2, 3)])
+def test_is_names_tuple_passes(ll):
+ assert inference.is_named_tuple(ll)
+
+
+@pytest.mark.parametrize("ll", [(1, 2, 3), "a", Series({"pi": 3.14})])
+def test_is_names_tuple_fails(ll):
+ assert not inference.is_named_tuple(ll)
+
+
+def test_is_hashable():
+ # all new-style classes are hashable by default
+ class HashableClass:
+ pass
+
+ class UnhashableClass1:
+ __hash__ = None
+
+ class UnhashableClass2:
+ def __hash__(self):
+ raise TypeError("Not hashable")
+
+ # Temporary helper for Python 3.11 compatibility.
+ # This can be removed once support for Python 3.11 is dropped.
+ class HashableSlice:
+ def __init__(self, start, stop, step=None):
+ self.slice = slice(start, stop, step)
+
+ def __eq__(self, other):
+ return isinstance(other, HashableSlice) and self.slice == other.slice
+
+ def __hash__(self):
+ return hash((self.slice.start, self.slice.stop, self.slice.step))
+
+ def __repr__(self):
+ return (
+ f"HashableSlice({self.slice.start}, {self.slice.stop}, "
+ f"{self.slice.step})"
+ )
+
+ hashable = (1, 3.14, np.float64(3.14), "a", (), (1,), HashableClass())
+ not_hashable = ([], UnhashableClass1())
+ abc_hashable_not_really_hashable = (([],), UnhashableClass2())
+ hashable_slice = HashableSlice(1, 2)
+ tuple_with_slice = (slice(1, 2), 3)
+
+ for i in hashable:
+ assert inference.is_hashable(i)
+ assert inference.is_hashable(i, allow_slice=True)
+ assert inference.is_hashable(i, allow_slice=False)
+ for i in not_hashable:
+ assert not inference.is_hashable(i)
+ assert not inference.is_hashable(i, allow_slice=True)
+ assert not inference.is_hashable(i, allow_slice=False)
+ for i in abc_hashable_not_really_hashable:
+ assert not inference.is_hashable(i)
+ assert not inference.is_hashable(i, allow_slice=True)
+ assert not inference.is_hashable(i, allow_slice=False)
+
+ assert inference.is_hashable(hashable_slice)
+ assert inference.is_hashable(hashable_slice, allow_slice=True)
+ assert inference.is_hashable(hashable_slice, allow_slice=False)
+
+ if PY312:
+ for obj in [slice(1, 2), tuple_with_slice]:
+ assert inference.is_hashable(obj)
+ assert inference.is_hashable(obj, allow_slice=True)
+ assert not inference.is_hashable(obj, allow_slice=False)
+ else:
+ for obj in [slice(1, 2), tuple_with_slice]:
+ assert not inference.is_hashable(obj)
+ assert not inference.is_hashable(obj, allow_slice=True)
+ assert not inference.is_hashable(obj, allow_slice=False)
+
+ # numpy.array is no longer collections.abc.Hashable as of
+ # https://github.com/numpy/numpy/pull/5326, just test
+ # is_hashable()
+ assert not inference.is_hashable(np.array([]))
+
+
+@pytest.mark.parametrize("ll", [re.compile("ad")])
+def test_is_re_passes(ll):
+ assert inference.is_re(ll)
+
+
+@pytest.mark.parametrize("ll", ["x", 2, 3, object()])
+def test_is_re_fails(ll):
+ assert not inference.is_re(ll)
+
+
+@pytest.mark.parametrize(
+ "ll", [r"a", "x", r"asdf", re.compile("adsf"), r"\u2233\s*", re.compile(r"")]
+)
+def test_is_recompilable_passes(ll):
+ assert inference.is_re_compilable(ll)
+
+
+@pytest.mark.parametrize("ll", [1, [], object()])
+def test_is_recompilable_fails(ll):
+ assert not inference.is_re_compilable(ll)
+
+
+class TestInference:
+ @pytest.mark.parametrize(
+ "arr",
+ [
+ np.array(list("abc"), dtype="S1"),
+ np.array(list("abc"), dtype="S1").astype(object),
+ [b"a", np.nan, b"c"],
+ ],
+ )
+ def test_infer_dtype_bytes(self, arr):
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "bytes"
+
+ @pytest.mark.parametrize(
+ "value, expected",
+ [
+ (float("inf"), True),
+ (np.inf, True),
+ (-np.inf, False),
+ (1, False),
+ ("a", False),
+ ],
+ )
+ def test_isposinf_scalar(self, value, expected):
+ # GH 11352
+ result = libmissing.isposinf_scalar(value)
+ assert result is expected
+
+ @pytest.mark.parametrize(
+ "value, expected",
+ [
+ (float("-inf"), True),
+ (-np.inf, True),
+ (np.inf, False),
+ (1, False),
+ ("a", False),
+ ],
+ )
+ def test_isneginf_scalar(self, value, expected):
+ result = libmissing.isneginf_scalar(value)
+ assert result is expected
+
+ @pytest.mark.parametrize(
+ "convert_to_masked_nullable, exp",
+ [
+ (
+ True,
+ BooleanArray(
+ np.array([True, False], dtype="bool"), np.array([False, True])
+ ),
+ ),
+ (False, np.array([True, np.nan], dtype="object")),
+ ],
+ )
+ def test_maybe_convert_nullable_boolean(self, convert_to_masked_nullable, exp):
+ # GH 40687
+ arr = np.array([True, np.nan], dtype=object)
+ result = libops.maybe_convert_bool(
+ arr, set(), convert_to_masked_nullable=convert_to_masked_nullable
+ )
+ if convert_to_masked_nullable:
+ tm.assert_extension_array_equal(BooleanArray(*result), exp)
+ else:
+ result = result[0]
+ tm.assert_numpy_array_equal(result, exp)
+
+ @pytest.mark.parametrize("convert_to_masked_nullable", [True, False])
+ @pytest.mark.parametrize("coerce_numeric", [True, False])
+ @pytest.mark.parametrize(
+ "infinity", ["inf", "inF", "iNf", "Inf", "iNF", "InF", "INf", "INF"]
+ )
+ @pytest.mark.parametrize("prefix", ["", "-", "+"])
+ def test_maybe_convert_numeric_infinities(
+ self, coerce_numeric, infinity, prefix, convert_to_masked_nullable
+ ):
+ # see gh-13274
+ result, _ = lib.maybe_convert_numeric(
+ np.array([prefix + infinity], dtype=object),
+ na_values={"", "NULL", "nan"},
+ coerce_numeric=coerce_numeric,
+ convert_to_masked_nullable=convert_to_masked_nullable,
+ )
+ expected = np.array([np.inf if prefix in ["", "+"] else -np.inf])
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("convert_to_masked_nullable", [True, False])
+ def test_maybe_convert_numeric_infinities_raises(self, convert_to_masked_nullable):
+ msg = "Unable to parse string"
+ with pytest.raises(ValueError, match=msg):
+ lib.maybe_convert_numeric(
+ np.array(["foo_inf"], dtype=object),
+ na_values={"", "NULL", "nan"},
+ coerce_numeric=False,
+ convert_to_masked_nullable=convert_to_masked_nullable,
+ )
+
+ @pytest.mark.parametrize("convert_to_masked_nullable", [True, False])
+ def test_maybe_convert_numeric_post_floatify_nan(
+ self, coerce, convert_to_masked_nullable
+ ):
+ # see gh-13314
+ data = np.array(["1.200", "-999.000", "4.500"], dtype=object)
+ expected = np.array([1.2, np.nan, 4.5], dtype=np.float64)
+ nan_values = {-999, -999.0}
+
+ out = lib.maybe_convert_numeric(
+ data,
+ nan_values,
+ coerce,
+ convert_to_masked_nullable=convert_to_masked_nullable,
+ )
+ if convert_to_masked_nullable:
+ expected = FloatingArray(expected, np.isnan(expected))
+ tm.assert_extension_array_equal(expected, FloatingArray(*out))
+ else:
+ out = out[0]
+ tm.assert_numpy_array_equal(out, expected)
+
+ def test_convert_infs(self):
+ arr = np.array(["inf", "inf", "inf"], dtype="O")
+ result, _ = lib.maybe_convert_numeric(arr, set(), False)
+ assert result.dtype == np.float64
+
+ arr = np.array(["-inf", "-inf", "-inf"], dtype="O")
+ result, _ = lib.maybe_convert_numeric(arr, set(), False)
+ assert result.dtype == np.float64
+
+ def test_scientific_no_exponent(self):
+ # See PR 12215
+ arr = np.array(["42E", "2E", "99e", "6e"], dtype="O")
+ result, _ = lib.maybe_convert_numeric(arr, set(), False, True)
+ assert np.all(np.isnan(result))
+
+ def test_convert_non_hashable(self):
+ # GH13324
+ # make sure that we are handing non-hashables
+ arr = np.array([[10.0, 2], 1.0, "apple"], dtype=object)
+ result, _ = lib.maybe_convert_numeric(arr, set(), False, True)
+ tm.assert_numpy_array_equal(result, np.array([np.nan, 1.0, np.nan]))
+
+ def test_convert_numeric_uint64(self):
+ arr = np.array([2**63], dtype=object)
+ exp = np.array([2**63], dtype=np.uint64)
+ tm.assert_numpy_array_equal(lib.maybe_convert_numeric(arr, set())[0], exp)
+
+ arr = np.array([str(2**63)], dtype=object)
+ exp = np.array([2**63], dtype=np.uint64)
+ tm.assert_numpy_array_equal(lib.maybe_convert_numeric(arr, set())[0], exp)
+
+ arr = np.array([np.uint64(2**63)], dtype=object)
+ exp = np.array([2**63], dtype=np.uint64)
+ tm.assert_numpy_array_equal(lib.maybe_convert_numeric(arr, set())[0], exp)
+
+ @pytest.mark.parametrize(
+ "arr",
+ [
+ np.array([2**63, np.nan], dtype=object),
+ np.array([str(2**63), np.nan], dtype=object),
+ np.array([np.nan, 2**63], dtype=object),
+ np.array([np.nan, str(2**63)], dtype=object),
+ ],
+ )
+ def test_convert_numeric_uint64_nan(self, coerce, arr):
+ expected = arr.astype(float) if coerce else arr.copy()
+ result, _ = lib.maybe_convert_numeric(arr, set(), coerce_numeric=coerce)
+ tm.assert_almost_equal(result, expected)
+
+ @pytest.mark.parametrize("convert_to_masked_nullable", [True, False])
+ def test_convert_numeric_uint64_nan_values(
+ self, coerce, convert_to_masked_nullable
+ ):
+ arr = np.array([2**63, 2**63 + 1], dtype=object)
+ na_values = {2**63}
+
+ expected = np.array([np.nan, 2**63 + 1], dtype=float) if coerce else arr.copy()
+ result = lib.maybe_convert_numeric(
+ arr,
+ na_values,
+ coerce_numeric=coerce,
+ convert_to_masked_nullable=convert_to_masked_nullable,
+ )
+ if convert_to_masked_nullable and coerce:
+ expected = IntegerArray(
+ np.array([0, 2**63 + 1], dtype="u8"),
+ np.array([True, False], dtype="bool"),
+ )
+ result = IntegerArray(*result)
+ else:
+ result = result[0] # discard mask
+ tm.assert_almost_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "case",
+ [
+ np.array([2**63, -1], dtype=object),
+ np.array([str(2**63), -1], dtype=object),
+ np.array([str(2**63), str(-1)], dtype=object),
+ np.array([-1, 2**63], dtype=object),
+ np.array([-1, str(2**63)], dtype=object),
+ np.array([str(-1), str(2**63)], dtype=object),
+ ],
+ )
+ @pytest.mark.parametrize("convert_to_masked_nullable", [True, False])
+ def test_convert_numeric_int64_uint64(
+ self, case, coerce, convert_to_masked_nullable
+ ):
+ expected = case.astype(float) if coerce else case.copy()
+ result, _ = lib.maybe_convert_numeric(
+ case,
+ set(),
+ coerce_numeric=coerce,
+ convert_to_masked_nullable=convert_to_masked_nullable,
+ )
+
+ tm.assert_almost_equal(result, expected)
+
+ @pytest.mark.parametrize("convert_to_masked_nullable", [True, False])
+ def test_convert_numeric_string_uint64(self, convert_to_masked_nullable):
+ # GH32394
+ result = lib.maybe_convert_numeric(
+ np.array(["uint64"], dtype=object),
+ set(),
+ coerce_numeric=True,
+ convert_to_masked_nullable=convert_to_masked_nullable,
+ )
+ if convert_to_masked_nullable:
+ result = FloatingArray(*result)
+ else:
+ result = result[0]
+ assert np.isnan(result)
+
+ @pytest.mark.parametrize("value", [-(2**63) - 1, 2**64])
+ def test_convert_int_overflow(self, value):
+ # see gh-18584
+ arr = np.array([value], dtype=object)
+ result = lib.maybe_convert_objects(arr)
+ tm.assert_numpy_array_equal(arr, result)
+
+ @pytest.mark.parametrize(
+ "value, expected_value",
+ [
+ (-(1 << 65), -(1 << 65)),
+ (1 << 65, 1 << 65),
+ (str(1 << 65), 1 << 65),
+ (f"-{1 << 65}", -(1 << 65)),
+ ],
+ )
+ @pytest.mark.parametrize("coerce_numeric", [False, True])
+ def test_convert_numeric_overflow(self, value, expected_value, coerce_numeric):
+ arr = np.array([value], dtype=object)
+ expected = np.array([expected_value], dtype=float if coerce_numeric else object)
+ result, _ = lib.maybe_convert_numeric(
+ arr,
+ set(),
+ coerce_numeric=coerce_numeric,
+ )
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("val", [None, np.nan, float("nan")])
+ @pytest.mark.parametrize("dtype", ["M8[ns]", "m8[ns]"])
+ def test_maybe_convert_objects_nat_inference(self, val, dtype):
+ dtype = np.dtype(dtype)
+ vals = np.array([pd.NaT, val], dtype=object)
+ result = lib.maybe_convert_objects(
+ vals,
+ convert_non_numeric=True,
+ dtype_if_all_nat=dtype,
+ )
+ assert result.dtype == dtype
+ assert np.isnat(result).all()
+
+ result = lib.maybe_convert_objects(
+ vals[::-1],
+ convert_non_numeric=True,
+ dtype_if_all_nat=dtype,
+ )
+ assert result.dtype == dtype
+ assert np.isnat(result).all()
+
+ @pytest.mark.parametrize(
+ "value, expected_dtype",
+ [
+ # see gh-4471
+ ([2**63], np.uint64),
+ # NumPy bug: can't compare uint64 to int64, as that
+ # results in both casting to float64, so we should
+ # make sure that this function is robust against it
+ ([np.uint64(2**63)], np.uint64),
+ ([2, -1], np.int64),
+ ([2**63, -1], object),
+ # GH#47294
+ ([np.uint8(1)], np.uint8),
+ ([np.uint16(1)], np.uint16),
+ ([np.uint32(1)], np.uint32),
+ ([np.uint64(1)], np.uint64),
+ ([np.uint8(2), np.uint16(1)], np.uint16),
+ ([np.uint32(2), np.uint16(1)], np.uint32),
+ ([np.uint32(2), -1], object),
+ ([np.uint32(2), 1], np.uint64),
+ ([np.uint32(2), np.int32(1)], object),
+ ],
+ )
+ def test_maybe_convert_objects_uint(self, value, expected_dtype):
+ arr = np.array(value, dtype=object)
+ exp = np.array(value, dtype=expected_dtype)
+ tm.assert_numpy_array_equal(lib.maybe_convert_objects(arr), exp)
+
+ def test_maybe_convert_objects_datetime(self):
+ # GH27438
+ arr = np.array(
+ [np.datetime64("2000-01-01"), np.timedelta64(1, "s")], dtype=object
+ )
+ exp = arr.copy()
+ out = lib.maybe_convert_objects(arr, convert_non_numeric=True)
+ tm.assert_numpy_array_equal(out, exp)
+
+ arr = np.array([pd.NaT, np.timedelta64(1, "s")], dtype=object)
+ exp = np.array([np.timedelta64("NaT"), np.timedelta64(1, "s")], dtype="m8[s]")
+ out = lib.maybe_convert_objects(arr, convert_non_numeric=True)
+ tm.assert_numpy_array_equal(out, exp)
+
+ # with convert_non_numeric=True, the nan is a valid NA value for td64
+ arr = np.array([np.timedelta64(1, "s"), np.nan], dtype=object)
+ exp = exp[::-1]
+ out = lib.maybe_convert_objects(arr, convert_non_numeric=True)
+ tm.assert_numpy_array_equal(out, exp)
+
+ def test_maybe_convert_objects_dtype_if_all_nat(self):
+ arr = np.array([pd.NaT, pd.NaT], dtype=object)
+ out = lib.maybe_convert_objects(arr, convert_non_numeric=True)
+ # no dtype_if_all_nat passed -> we dont guess
+ tm.assert_numpy_array_equal(out, arr)
+
+ out = lib.maybe_convert_objects(
+ arr,
+ convert_non_numeric=True,
+ dtype_if_all_nat=np.dtype("timedelta64[ns]"),
+ )
+ exp = np.array(["NaT", "NaT"], dtype="timedelta64[ns]")
+ tm.assert_numpy_array_equal(out, exp)
+
+ out = lib.maybe_convert_objects(
+ arr,
+ convert_non_numeric=True,
+ dtype_if_all_nat=np.dtype("datetime64[ns]"),
+ )
+ exp = np.array(["NaT", "NaT"], dtype="datetime64[ns]")
+ tm.assert_numpy_array_equal(out, exp)
+
+ def test_maybe_convert_objects_dtype_if_all_nat_invalid(self):
+ # we accept datetime64[ns], timedelta64[ns], and EADtype
+ arr = np.array([pd.NaT, pd.NaT], dtype=object)
+
+ with pytest.raises(ValueError, match="int64"):
+ lib.maybe_convert_objects(
+ arr,
+ convert_non_numeric=True,
+ dtype_if_all_nat=np.dtype("int64"),
+ )
+
+ @pytest.mark.parametrize("dtype", ["datetime64[ns]", "timedelta64[ns]"])
+ def test_maybe_convert_objects_datetime_overflow_safe(self, dtype):
+ stamp = datetime(2363, 10, 4) # Enterprise-D launch date
+ if dtype == "timedelta64[ns]":
+ stamp = stamp - datetime(1970, 1, 1)
+ arr = np.array([stamp], dtype=object)
+
+ out = lib.maybe_convert_objects(arr, convert_non_numeric=True)
+ # no OutOfBoundsDatetime/OutOfBoundsTimedeltas
+ if dtype == "datetime64[ns]":
+ expected = np.array(["2363-10-04"], dtype="M8[us]")
+ else:
+ expected = arr.astype("m8[us]")
+ tm.assert_numpy_array_equal(out, expected)
+
+ def test_maybe_convert_objects_mixed_datetimes(self):
+ ts = Timestamp("now")
+ vals = [ts, ts.to_pydatetime(), ts.to_datetime64(), pd.NaT, np.nan, None]
+
+ for data in itertools.permutations(vals):
+ data = np.array(list(data), dtype=object)
+ expected = DatetimeIndex(data)._data._ndarray
+ result = lib.maybe_convert_objects(data, convert_non_numeric=True)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_maybe_convert_objects_timedelta64_nat(self):
+ obj = np.timedelta64("NaT", "ns")
+ arr = np.array([obj], dtype=object)
+ assert arr[0] is obj
+
+ result = lib.maybe_convert_objects(arr, convert_non_numeric=True)
+
+ expected = np.array([obj], dtype="m8[ns]")
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "exp",
+ [
+ IntegerArray(np.array([2, 0], dtype="i8"), np.array([False, True])),
+ IntegerArray(np.array([2, 0], dtype="int64"), np.array([False, True])),
+ ],
+ )
+ def test_maybe_convert_objects_nullable_integer(self, exp):
+ # GH27335
+ arr = np.array([2, np.nan], dtype=object)
+ result = lib.maybe_convert_objects(arr, convert_to_nullable_dtype=True)
+
+ tm.assert_extension_array_equal(result, exp)
+
+ @pytest.mark.parametrize(
+ "dtype, val", [("int64", 1), ("uint64", np.iinfo(np.int64).max + 1)]
+ )
+ def test_maybe_convert_objects_nullable_none(self, dtype, val):
+ # GH#50043
+ arr = np.array([val, None, 3], dtype="object")
+ result = lib.maybe_convert_objects(arr, convert_to_nullable_dtype=True)
+ expected = IntegerArray(
+ np.array([val, 0, 3], dtype=dtype), np.array([False, True, False])
+ )
+ tm.assert_extension_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "convert_to_masked_nullable, exp",
+ [
+ (True, IntegerArray(np.array([2, 0], dtype="i8"), np.array([False, True]))),
+ (False, np.array([2, np.nan], dtype="float64")),
+ ],
+ )
+ def test_maybe_convert_numeric_nullable_integer(
+ self, convert_to_masked_nullable, exp
+ ):
+ # GH 40687
+ arr = np.array([2, np.nan], dtype=object)
+ result = lib.maybe_convert_numeric(
+ arr, set(), convert_to_masked_nullable=convert_to_masked_nullable
+ )
+ if convert_to_masked_nullable:
+ result = IntegerArray(*result)
+ tm.assert_extension_array_equal(result, exp)
+ else:
+ result = result[0]
+ tm.assert_numpy_array_equal(result, exp)
+
+ @pytest.mark.parametrize(
+ "convert_to_masked_nullable, exp",
+ [
+ (
+ True,
+ FloatingArray(
+ np.array([2.0, 0.0], dtype="float64"), np.array([False, True])
+ ),
+ ),
+ (False, np.array([2.0, np.nan], dtype="float64")),
+ ],
+ )
+ def test_maybe_convert_numeric_floating_array(
+ self, convert_to_masked_nullable, exp
+ ):
+ # GH 40687
+ arr = np.array([2.0, np.nan], dtype=object)
+ result = lib.maybe_convert_numeric(
+ arr, set(), convert_to_masked_nullable=convert_to_masked_nullable
+ )
+ if convert_to_masked_nullable:
+ tm.assert_extension_array_equal(FloatingArray(*result), exp)
+ else:
+ result = result[0]
+ tm.assert_numpy_array_equal(result, exp)
+
+ def test_maybe_convert_objects_bool_nan(self):
+ # GH32146
+ ind = Index([True, False, np.nan], dtype=object)
+ exp = np.array([True, False, np.nan], dtype=object)
+ out = lib.maybe_convert_objects(ind.values, safe=1)
+ tm.assert_numpy_array_equal(out, exp)
+
+ def test_maybe_convert_objects_nullable_boolean(self):
+ # GH50047
+ arr = np.array([True, False], dtype=object)
+ exp = BooleanArray._from_sequence([True, False], dtype="boolean")
+ out = lib.maybe_convert_objects(arr, convert_to_nullable_dtype=True)
+ tm.assert_extension_array_equal(out, exp)
+
+ arr = np.array([True, False, pd.NaT], dtype=object)
+ exp = np.array([True, False, pd.NaT], dtype=object)
+ out = lib.maybe_convert_objects(arr, convert_to_nullable_dtype=True)
+ tm.assert_numpy_array_equal(out, exp)
+
+ @pytest.mark.parametrize("val", [None, np.nan])
+ def test_maybe_convert_objects_nullable_boolean_na(self, val):
+ # GH50047
+ arr = np.array([True, False, val], dtype=object)
+ exp = BooleanArray(
+ np.array([True, False, False]), np.array([False, False, True])
+ )
+ out = lib.maybe_convert_objects(arr, convert_to_nullable_dtype=True)
+ tm.assert_extension_array_equal(out, exp)
+
+ @pytest.mark.parametrize(
+ "data0",
+ [
+ True,
+ 1,
+ 1.0,
+ 1.0 + 1.0j,
+ np.int8(1),
+ np.int16(1),
+ np.int32(1),
+ np.int64(1),
+ np.float16(1),
+ np.float32(1),
+ np.float64(1),
+ np.complex64(1),
+ np.complex128(1),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "data1",
+ [
+ True,
+ 1,
+ 1.0,
+ 1.0 + 1.0j,
+ np.int8(1),
+ np.int16(1),
+ np.int32(1),
+ np.int64(1),
+ np.float16(1),
+ np.float32(1),
+ np.float64(1),
+ np.complex64(1),
+ np.complex128(1),
+ ],
+ )
+ def test_maybe_convert_objects_itemsize(self, data0, data1):
+ # GH 40908
+ data = [data0, data1]
+ arr = np.array(data, dtype="object")
+
+ common_kind = np.result_type(type(data0), type(data1)).kind
+ kind0 = "python" if not hasattr(data0, "dtype") else data0.dtype.kind
+ kind1 = "python" if not hasattr(data1, "dtype") else data1.dtype.kind
+ if kind0 != "python" and kind1 != "python":
+ kind = common_kind
+ itemsize = max(data0.dtype.itemsize, data1.dtype.itemsize)
+ elif is_bool(data0) or is_bool(data1):
+ kind = "bool" if (is_bool(data0) and is_bool(data1)) else "object"
+ itemsize = ""
+ elif is_complex(data0) or is_complex(data1):
+ kind = common_kind
+ itemsize = 16
+ else:
+ kind = common_kind
+ itemsize = 8
+
+ expected = np.array(data, dtype=f"{kind}{itemsize}")
+ result = lib.maybe_convert_objects(arr)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_mixed_dtypes_remain_object_array(self):
+ # GH14956
+ arr = np.array([datetime(2015, 1, 1, tzinfo=timezone.utc), 1], dtype=object)
+ result = lib.maybe_convert_objects(arr, convert_non_numeric=True)
+ tm.assert_numpy_array_equal(result, arr)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [
+ pd.IntervalIndex.from_breaks(range(5), closed="both"),
+ pd.period_range("2016-01-01", periods=3, freq="D"),
+ ],
+ )
+ def test_maybe_convert_objects_ea(self, idx):
+ result = lib.maybe_convert_objects(
+ np.array(idx, dtype=object),
+ convert_non_numeric=True,
+ )
+ tm.assert_extension_array_equal(result, idx._data)
+
+
+class TestTypeInference:
+ # Dummy class used for testing with Python objects
+ class Dummy:
+ pass
+
+ def test_inferred_dtype_fixture(self, any_skipna_inferred_dtype):
+ # see pandas/conftest.py
+ inferred_dtype, values = any_skipna_inferred_dtype
+
+ # make sure the inferred dtype of the fixture is as requested
+ assert inferred_dtype == lib.infer_dtype(values, skipna=True)
+
+ def test_length_zero(self, skipna):
+ result = lib.infer_dtype(np.array([], dtype="i4"), skipna=skipna)
+ assert result == "integer"
+
+ result = lib.infer_dtype([], skipna=skipna)
+ assert result == "empty"
+
+ # GH 18004
+ arr = np.array([np.array([], dtype=object), np.array([], dtype=object)])
+ result = lib.infer_dtype(arr, skipna=skipna)
+ assert result == "empty"
+
+ def test_integers(self):
+ arr = np.array([1, 2, 3, np.int64(4), np.int32(5)], dtype="O")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "integer"
+
+ arr = np.array([1, 2, 3, np.int64(4), np.int32(5), "foo"], dtype="O")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "mixed-integer"
+
+ arr = np.array([1, 2, 3, 4, 5], dtype="i4")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "integer"
+
+ @pytest.mark.parametrize(
+ "arr, skipna",
+ [
+ ([1, 2, np.nan, np.nan, 3], False),
+ ([1, 2, np.nan, np.nan, 3], True),
+ ([1, 2, 3, np.int64(4), np.int32(5), np.nan], False),
+ ([1, 2, 3, np.int64(4), np.int32(5), np.nan], True),
+ ],
+ )
+ def test_integer_na(self, arr, skipna):
+ # GH 27392
+ result = lib.infer_dtype(np.array(arr, dtype="O"), skipna=skipna)
+ expected = "integer" if skipna else "integer-na"
+ assert result == expected
+
+ def test_infer_dtype_skipna_default(self):
+ # infer_dtype `skipna` default deprecated in GH#24050,
+ # changed to True in GH#29876
+ arr = np.array([1, 2, 3, np.nan], dtype=object)
+
+ result = lib.infer_dtype(arr)
+ assert result == "integer"
+
+ def test_bools(self):
+ arr = np.array([True, False, True, True, True], dtype="O")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "boolean"
+
+ arr = np.array([np.bool_(True), np.bool_(False)], dtype="O")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "boolean"
+
+ arr = np.array([True, False, True, "foo"], dtype="O")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "mixed"
+
+ arr = np.array([True, False, True], dtype=bool)
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "boolean"
+
+ arr = np.array([True, np.nan, False], dtype="O")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "boolean"
+
+ result = lib.infer_dtype(arr, skipna=False)
+ assert result == "mixed"
+
+ def test_floats(self):
+ arr = np.array([1.0, 2.0, 3.0, np.float64(4), np.float32(5)], dtype="O")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "floating"
+
+ arr = np.array([1, 2, 3, np.float64(4), np.float32(5), "foo"], dtype="O")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "mixed-integer"
+
+ arr = np.array([1, 2, 3, 4, 5], dtype="f4")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "floating"
+
+ arr = np.array([1, 2, 3, 4, 5], dtype="f8")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "floating"
+
+ def test_decimals(self):
+ # GH15690
+ arr = np.array([Decimal(1), Decimal(2), Decimal(3)])
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "decimal"
+
+ arr = np.array([1.0, 2.0, Decimal(3)])
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "mixed"
+
+ result = lib.infer_dtype(arr[::-1], skipna=True)
+ assert result == "mixed"
+
+ arr = np.array([Decimal(1), Decimal("NaN"), Decimal(3)])
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "decimal"
+
+ arr = np.array([Decimal(1), np.nan, Decimal(3)], dtype="O")
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "decimal"
+
+ # complex is compatible with nan, so skipna has no effect
+ def test_complex(self, skipna):
+ # gets cast to complex on array construction
+ arr = np.array([1.0, 2.0, 1 + 1j])
+ result = lib.infer_dtype(arr, skipna=skipna)
+ assert result == "complex"
+
+ arr = np.array([1.0, 2.0, 1 + 1j], dtype="O")
+ result = lib.infer_dtype(arr, skipna=skipna)
+ assert result == "mixed"
+
+ result = lib.infer_dtype(arr[::-1], skipna=skipna)
+ assert result == "mixed"
+
+ # gets cast to complex on array construction
+ arr = np.array([1, np.nan, 1 + 1j])
+ result = lib.infer_dtype(arr, skipna=skipna)
+ assert result == "complex"
+
+ arr = np.array([1.0, np.nan, 1 + 1j], dtype="O")
+ result = lib.infer_dtype(arr, skipna=skipna)
+ assert result == "mixed"
+
+ # complex with nans stays complex
+ arr = np.array([1 + 1j, np.nan, 3 + 3j], dtype="O")
+ result = lib.infer_dtype(arr, skipna=skipna)
+ assert result == "complex"
+
+ # test smaller complex dtype; will pass through _try_infer_map fastpath
+ arr = np.array([1 + 1j, np.nan, 3 + 3j], dtype=np.complex64)
+ result = lib.infer_dtype(arr, skipna=skipna)
+ assert result == "complex"
+
+ def test_string(self):
+ pass
+
+ def test_unicode(self):
+ arr = ["a", np.nan, "c"]
+ result = lib.infer_dtype(arr, skipna=False)
+ # This currently returns "mixed", but it's not clear that's optimal.
+ # This could also return "string" or "mixed-string"
+ assert result == "mixed"
+
+ # even though we use skipna, we are only skipping those NAs that are
+ # considered matching by is_string_array
+ arr = ["a", np.nan, "c"]
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "string"
+
+ arr = ["a", pd.NA, "c"]
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "string"
+
+ arr = ["a", pd.NaT, "c"]
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "mixed"
+
+ arr = ["a", "c"]
+ result = lib.infer_dtype(arr, skipna=False)
+ assert result == "string"
+
+ @pytest.mark.parametrize(
+ "dtype, missing, skipna, expected",
+ [
+ (float, np.nan, False, "floating"),
+ (float, np.nan, True, "floating"),
+ (object, np.nan, False, "floating"),
+ (object, np.nan, True, "empty"),
+ (object, None, False, "mixed"),
+ (object, None, True, "empty"),
+ ],
+ )
+ @pytest.mark.parametrize("box", [Series, np.array])
+ def test_object_empty(self, box, missing, dtype, skipna, expected):
+ # GH 23421
+ arr = box([missing, missing], dtype=dtype)
+
+ result = lib.infer_dtype(arr, skipna=skipna)
+ assert result == expected
+
+ def test_datetime(self):
+ dates = [datetime(2012, 1, x) for x in range(1, 20)]
+ index = Index(dates)
+ assert index.inferred_type == "datetime64"
+
+ def test_infer_dtype_datetime64(self):
+ arr = np.array(
+ [np.datetime64("2011-01-01"), np.datetime64("2011-01-01")], dtype=object
+ )
+ assert lib.infer_dtype(arr, skipna=True) == "datetime64"
+
+ @pytest.mark.parametrize("na_value", [pd.NaT, np.nan])
+ def test_infer_dtype_datetime64_with_na(self, na_value):
+ # starts with nan
+ arr = np.array([na_value, np.datetime64("2011-01-02")])
+ assert lib.infer_dtype(arr, skipna=True) == "datetime64"
+
+ arr = np.array([na_value, np.datetime64("2011-01-02"), na_value])
+ assert lib.infer_dtype(arr, skipna=True) == "datetime64"
+
+ @pytest.mark.parametrize(
+ "arr",
+ [
+ np.array(
+ [np.timedelta64("nat"), np.datetime64("2011-01-02")], dtype=object
+ ),
+ np.array(
+ [np.datetime64("2011-01-02"), np.timedelta64("nat")], dtype=object
+ ),
+ np.array([np.datetime64("2011-01-01"), Timestamp("2011-01-02")]),
+ np.array([Timestamp("2011-01-02"), np.datetime64("2011-01-01")]),
+ np.array([np.nan, Timestamp("2011-01-02"), 1.1]),
+ np.array([np.nan, "2011-01-01", Timestamp("2011-01-02")], dtype=object),
+ np.array([np.datetime64("nat"), np.timedelta64(1, "D")], dtype=object),
+ np.array([np.timedelta64(1, "D"), np.datetime64("nat")], dtype=object),
+ ],
+ )
+ def test_infer_datetimelike_dtype_mixed(self, arr):
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ def test_infer_dtype_mixed_integer(self):
+ arr = np.array([np.nan, Timestamp("2011-01-02"), 1])
+ assert lib.infer_dtype(arr, skipna=True) == "mixed-integer"
+
+ @pytest.mark.parametrize(
+ "arr",
+ [
+ [Timestamp("2011-01-01"), Timestamp("2011-01-02")],
+ [datetime(2011, 1, 1), datetime(2012, 2, 1)],
+ [datetime(2011, 1, 1), Timestamp("2011-01-02")],
+ ],
+ )
+ def test_infer_dtype_datetime(self, arr):
+ assert lib.infer_dtype(np.array(arr), skipna=True) == "datetime"
+
+ @pytest.mark.parametrize("na_value", [pd.NaT, np.nan])
+ @pytest.mark.parametrize(
+ "time_stamp", [Timestamp("2011-01-01"), datetime(2011, 1, 1)]
+ )
+ def test_infer_dtype_datetime_with_na(self, na_value, time_stamp):
+ # starts with nan
+ arr = np.array([na_value, time_stamp])
+ assert lib.infer_dtype(arr, skipna=True) == "datetime"
+
+ arr = np.array([na_value, time_stamp, na_value])
+ assert lib.infer_dtype(arr, skipna=True) == "datetime"
+
+ @pytest.mark.parametrize(
+ "arr",
+ [
+ np.array([Timedelta("1 days"), Timedelta("2 days")]),
+ np.array([np.timedelta64(1, "D"), np.timedelta64(2, "D")], dtype=object),
+ np.array([timedelta(1), timedelta(2)]),
+ ],
+ )
+ def test_infer_dtype_timedelta(self, arr):
+ assert lib.infer_dtype(arr, skipna=True) == "timedelta"
+
+ @pytest.mark.parametrize("na_value", [pd.NaT, np.nan])
+ @pytest.mark.parametrize(
+ "delta", [Timedelta("1 days"), np.timedelta64(1, "D"), timedelta(1)]
+ )
+ def test_infer_dtype_timedelta_with_na(self, na_value, delta):
+ # starts with nan
+ arr = np.array([na_value, delta])
+ assert lib.infer_dtype(arr, skipna=True) == "timedelta"
+
+ arr = np.array([na_value, delta, na_value])
+ assert lib.infer_dtype(arr, skipna=True) == "timedelta"
+
+ def test_infer_dtype_period(self):
+ # GH 13664
+ arr = np.array([Period("2011-01", freq="D"), Period("2011-02", freq="D")])
+ assert lib.infer_dtype(arr, skipna=True) == "period"
+
+ # non-homogeneous freqs -> mixed
+ arr = np.array([Period("2011-01", freq="D"), Period("2011-02", freq="M")])
+ assert lib.infer_dtype(arr, skipna=True) == "mixed"
+
+ def test_infer_dtype_period_array(self, index_or_series_or_array, skipna):
+ klass = index_or_series_or_array
+ # https://github.com/pandas-dev/pandas/issues/23553
+ values = klass(
+ [
+ Period("2011-01-01", freq="D"),
+ Period("2011-01-02", freq="D"),
+ pd.NaT,
+ ]
+ )
+ assert lib.infer_dtype(values, skipna=skipna) == "period"
+
+ # periods but mixed freq
+ values = klass(
+ [
+ Period("2011-01-01", freq="D"),
+ Period("2011-01-02", freq="M"),
+ pd.NaT,
+ ]
+ )
+ # with pd.array this becomes NumpyExtensionArray which ends up
+ # as "unknown-array"
+ exp = "unknown-array" if klass is pd.array else "mixed"
+ assert lib.infer_dtype(values, skipna=skipna) == exp
+
+ def test_infer_dtype_period_mixed(self):
+ arr = np.array(
+ [Period("2011-01", freq="M"), np.datetime64("nat")], dtype=object
+ )
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ arr = np.array(
+ [np.datetime64("nat"), Period("2011-01", freq="M")], dtype=object
+ )
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ @pytest.mark.parametrize("na_value", [pd.NaT, np.nan])
+ def test_infer_dtype_period_with_na(self, na_value):
+ # starts with nan
+ arr = np.array([na_value, Period("2011-01", freq="D")])
+ assert lib.infer_dtype(arr, skipna=True) == "period"
+
+ arr = np.array([na_value, Period("2011-01", freq="D"), na_value])
+ assert lib.infer_dtype(arr, skipna=True) == "period"
+
+ @pytest.mark.parametrize("na_value", [pd.NA, np.nan])
+ def test_infer_dtype_numeric_with_na(self, na_value):
+ # GH61621
+ ser = Series([1, 2, na_value], dtype=object)
+ assert lib.infer_dtype(ser, skipna=True) == "integer"
+
+ ser = Series([1.0, 2.0, na_value], dtype=object)
+ assert lib.infer_dtype(ser, skipna=True) == "floating"
+
+ # GH#61976
+ ser = Series([1 + 1j, na_value], dtype=object)
+ assert lib.infer_dtype(ser, skipna=True) == "complex"
+
+ def test_infer_dtype_all_nan_nat_like(self):
+ arr = np.array([np.nan, np.nan])
+ assert lib.infer_dtype(arr, skipna=True) == "floating"
+
+ # nan and None mix are result in mixed
+ arr = np.array([np.nan, np.nan, None])
+ assert lib.infer_dtype(arr, skipna=True) == "empty"
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ arr = np.array([None, np.nan, np.nan])
+ assert lib.infer_dtype(arr, skipna=True) == "empty"
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ # pd.NaT
+ arr = np.array([pd.NaT])
+ assert lib.infer_dtype(arr, skipna=False) == "datetime"
+
+ arr = np.array([pd.NaT, np.nan])
+ assert lib.infer_dtype(arr, skipna=False) == "datetime"
+
+ arr = np.array([np.nan, pd.NaT])
+ assert lib.infer_dtype(arr, skipna=False) == "datetime"
+
+ arr = np.array([np.nan, pd.NaT, np.nan])
+ assert lib.infer_dtype(arr, skipna=False) == "datetime"
+
+ arr = np.array([None, pd.NaT, None])
+ assert lib.infer_dtype(arr, skipna=False) == "datetime"
+
+ # np.datetime64(nat)
+ arr = np.array([np.datetime64("nat")])
+ assert lib.infer_dtype(arr, skipna=False) == "datetime64"
+
+ for n in [np.nan, pd.NaT, None]:
+ arr = np.array([n, np.datetime64("nat"), n])
+ assert lib.infer_dtype(arr, skipna=False) == "datetime64"
+
+ arr = np.array([pd.NaT, n, np.datetime64("nat"), n])
+ assert lib.infer_dtype(arr, skipna=False) == "datetime64"
+
+ arr = np.array([np.timedelta64("nat")], dtype=object)
+ assert lib.infer_dtype(arr, skipna=False) == "timedelta"
+
+ for n in [np.nan, pd.NaT, None]:
+ arr = np.array([n, np.timedelta64("nat"), n])
+ assert lib.infer_dtype(arr, skipna=False) == "timedelta"
+
+ arr = np.array([pd.NaT, n, np.timedelta64("nat"), n])
+ assert lib.infer_dtype(arr, skipna=False) == "timedelta"
+
+ # datetime / timedelta mixed
+ arr = np.array([pd.NaT, np.datetime64("nat"), np.timedelta64("nat"), np.nan])
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ arr = np.array([np.timedelta64("nat"), np.datetime64("nat")], dtype=object)
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ def test_is_datetimelike_array_all_nan_nat_like(self):
+ arr = np.array([np.nan, pd.NaT, np.datetime64("nat")])
+ assert lib.is_datetime_array(arr)
+ assert lib.is_datetime64_array(arr)
+ assert not lib.is_timedelta_or_timedelta64_array(arr)
+
+ arr = np.array([np.nan, pd.NaT, np.timedelta64("nat")])
+ assert not lib.is_datetime_array(arr)
+ assert not lib.is_datetime64_array(arr)
+ assert lib.is_timedelta_or_timedelta64_array(arr)
+
+ arr = np.array([np.nan, pd.NaT, np.datetime64("nat"), np.timedelta64("nat")])
+ assert not lib.is_datetime_array(arr)
+ assert not lib.is_datetime64_array(arr)
+ assert not lib.is_timedelta_or_timedelta64_array(arr)
+
+ arr = np.array([np.nan, pd.NaT])
+ assert lib.is_datetime_array(arr)
+ assert lib.is_datetime64_array(arr)
+ assert lib.is_timedelta_or_timedelta64_array(arr)
+
+ arr = np.array([np.nan, np.nan], dtype=object)
+ assert not lib.is_datetime_array(arr)
+ assert not lib.is_datetime64_array(arr)
+ assert not lib.is_timedelta_or_timedelta64_array(arr)
+
+ assert lib.is_datetime_with_singletz_array(
+ np.array(
+ [
+ Timestamp("20130101", tz="US/Eastern"),
+ Timestamp("20130102", tz="US/Eastern"),
+ ],
+ dtype=object,
+ )
+ )
+ assert not lib.is_datetime_with_singletz_array(
+ np.array(
+ [
+ Timestamp("20130101", tz="US/Eastern"),
+ Timestamp("20130102", tz="CET"),
+ ],
+ dtype=object,
+ )
+ )
+
+ @pytest.mark.parametrize(
+ "func",
+ [
+ "is_datetime_array",
+ "is_datetime64_array",
+ "is_bool_array",
+ "is_timedelta_or_timedelta64_array",
+ "is_date_array",
+ "is_time_array",
+ "is_interval_array",
+ ],
+ )
+ def test_other_dtypes_for_array(self, func):
+ func = getattr(lib, func)
+ arr = np.array(["foo", "bar"])
+ assert not func(arr)
+ assert not func(arr.reshape(2, 1))
+
+ arr = np.array([1, 2])
+ assert not func(arr)
+ assert not func(arr.reshape(2, 1))
+
+ def test_date(self):
+ dates = [date(2012, 1, day) for day in range(1, 20)]
+ index = Index(dates)
+ assert index.inferred_type == "date"
+
+ dates = [date(2012, 1, day) for day in range(1, 20)] + [np.nan]
+ result = lib.infer_dtype(dates, skipna=False)
+ assert result == "mixed"
+
+ result = lib.infer_dtype(dates, skipna=True)
+ assert result == "date"
+
+ @pytest.mark.parametrize(
+ "values",
+ [
+ [date(2020, 1, 1), Timestamp("2020-01-01")],
+ [Timestamp("2020-01-01"), date(2020, 1, 1)],
+ [date(2020, 1, 1), pd.NaT],
+ [pd.NaT, date(2020, 1, 1)],
+ ],
+ )
+ def test_infer_dtype_date_order_invariant(self, values, skipna):
+ # https://github.com/pandas-dev/pandas/issues/33741
+ result = lib.infer_dtype(values, skipna=skipna)
+ assert result == "date"
+
+ def test_is_numeric_array(self):
+ assert lib.is_float_array(np.array([1, 2.0]))
+ assert lib.is_float_array(np.array([1, 2.0, np.nan]))
+ assert not lib.is_float_array(np.array([1, 2]))
+
+ assert lib.is_integer_array(np.array([1, 2]))
+ assert not lib.is_integer_array(np.array([1, 2.0]))
+
+ def test_is_string_array(self):
+ # We should only be accepting pd.NA, np.nan,
+ # other floating point nans e.g. float('nan')]
+ # when skipna is True.
+ assert lib.is_string_array(np.array(["foo", "bar"]))
+ assert not lib.is_string_array(
+ np.array(["foo", "bar", pd.NA], dtype=object), skipna=False
+ )
+ assert lib.is_string_array(
+ np.array(["foo", "bar", pd.NA], dtype=object), skipna=True
+ )
+ # we allow NaN/None in the StringArray constructor, so its allowed here
+ assert lib.is_string_array(
+ np.array(["foo", "bar", None], dtype=object), skipna=True
+ )
+ assert lib.is_string_array(
+ np.array(["foo", "bar", np.nan], dtype=object), skipna=True
+ )
+ # But not e.g. datetimelike or Decimal NAs
+ assert not lib.is_string_array(
+ np.array(["foo", "bar", pd.NaT], dtype=object), skipna=True
+ )
+ assert not lib.is_string_array(
+ np.array(["foo", "bar", np.datetime64("NaT")], dtype=object), skipna=True
+ )
+ assert not lib.is_string_array(
+ np.array(["foo", "bar", Decimal("NaN")], dtype=object), skipna=True
+ )
+
+ assert not lib.is_string_array(
+ np.array(["foo", "bar", None], dtype=object), skipna=False
+ )
+ assert not lib.is_string_array(
+ np.array(["foo", "bar", np.nan], dtype=object), skipna=False
+ )
+ assert not lib.is_string_array(np.array([1, 2]))
+
+ def test_is_interval_array_subclass(self):
+ # GH#46945
+
+ class TimestampsInterval(Interval):
+ def __init__(self, left: str, right: str, closed="both") -> None:
+ super().__init__(Timestamp(left), Timestamp(right), closed)
+
+ @property
+ def seconds(self) -> float:
+ return self.length.seconds
+
+ item = TimestampsInterval("1970-01-01 00:00:00", "1970-01-01 00:00:01")
+ arr = np.array([item], dtype=object)
+ assert not lib.is_interval_array(arr)
+ assert lib.infer_dtype(arr) != "interval"
+ out = Series([item])[0]
+ assert isinstance(out, TimestampsInterval)
+
+ @pytest.mark.parametrize(
+ "func",
+ [
+ "is_bool_array",
+ "is_date_array",
+ "is_datetime_array",
+ "is_datetime64_array",
+ "is_float_array",
+ "is_integer_array",
+ "is_interval_array",
+ "is_string_array",
+ "is_time_array",
+ "is_timedelta_or_timedelta64_array",
+ ],
+ )
+ def test_is_dtype_array_empty_obj(self, func):
+ # https://github.com/pandas-dev/pandas/pull/60796
+ func = getattr(lib, func)
+
+ arr = np.empty((2, 0), dtype=object)
+ assert not func(arr)
+
+ arr = np.empty((0, 2), dtype=object)
+ assert not func(arr)
+
+ def test_to_object_array_tuples(self):
+ r = (5, 6)
+ values = [r]
+ lib.to_object_array_tuples(values)
+
+ # make sure record array works
+ record = namedtuple("record", "x y")
+ r = record(5, 6)
+ values = [r]
+ lib.to_object_array_tuples(values)
+
+ def test_object(self):
+ # GH 7431
+ # cannot infer more than this as only a single element
+ arr = np.array([None], dtype="O")
+ result = lib.infer_dtype(arr, skipna=False)
+ assert result == "mixed"
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "empty"
+
+ def test_to_object_array_width(self):
+ # see gh-13320
+ rows = [[1, 2, 3], [4, 5, 6]]
+
+ expected = np.array(rows, dtype=object)
+ out = lib.to_object_array(rows)
+ tm.assert_numpy_array_equal(out, expected)
+
+ expected = np.array(rows, dtype=object)
+ out = lib.to_object_array(rows, min_width=1)
+ tm.assert_numpy_array_equal(out, expected)
+
+ expected = np.array(
+ [[1, 2, 3, None, None], [4, 5, 6, None, None]], dtype=object
+ )
+ out = lib.to_object_array(rows, min_width=5)
+ tm.assert_numpy_array_equal(out, expected)
+
+ def test_categorical(self):
+ # GH 8974
+ arr = Categorical(list("abc"))
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "categorical"
+
+ result = lib.infer_dtype(Series(arr), skipna=True)
+ assert result == "categorical"
+
+ arr = Categorical([None, None, None], categories=["cegfab"], ordered=True)
+ result = lib.infer_dtype(arr, skipna=True)
+ assert result == "categorical"
+
+ result = lib.infer_dtype(Series(arr), skipna=True)
+ assert result == "categorical"
+
+ @pytest.mark.parametrize("asobject", [True, False])
+ def test_interval(self, asobject):
+ idx = pd.IntervalIndex.from_breaks(range(5), closed="both")
+ if asobject:
+ idx = idx.astype(object)
+
+ inferred = lib.infer_dtype(idx, skipna=False)
+ assert inferred == "interval"
+
+ inferred = lib.infer_dtype(idx._data, skipna=False)
+ assert inferred == "interval"
+
+ inferred = lib.infer_dtype(Series(idx, dtype=idx.dtype), skipna=False)
+ assert inferred == "interval"
+
+ @pytest.mark.parametrize("value", [Timestamp(0), Timedelta(0), 0, 0.0])
+ def test_interval_mismatched_closed(self, value):
+ first = Interval(value, value, closed="left")
+ second = Interval(value, value, closed="right")
+
+ # if closed match, we should infer "interval"
+ arr = np.array([first, first], dtype=object)
+ assert lib.infer_dtype(arr, skipna=False) == "interval"
+
+ # if closed dont match, we should _not_ get "interval"
+ arr2 = np.array([first, second], dtype=object)
+ assert lib.infer_dtype(arr2, skipna=False) == "mixed"
+
+ def test_interval_mismatched_subtype(self):
+ first = Interval(0, 1, closed="left")
+ second = Interval(Timestamp(0), Timestamp(1), closed="left")
+ third = Interval(Timedelta(0), Timedelta(1), closed="left")
+
+ arr = np.array([first, second])
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ arr = np.array([second, third])
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ arr = np.array([first, third])
+ assert lib.infer_dtype(arr, skipna=False) == "mixed"
+
+ # float vs int subdtype are compatible
+ flt_interval = Interval(1.5, 2.5, closed="left")
+ arr = np.array([first, flt_interval], dtype=object)
+ assert lib.infer_dtype(arr, skipna=False) == "interval"
+
+ @pytest.mark.parametrize("data", [["a", "b", "c"], ["a", "b", pd.NA]])
+ def test_string_dtype(
+ self, data, skipna, index_or_series_or_array, nullable_string_dtype
+ ):
+ # StringArray
+ val = index_or_series_or_array(data, dtype=nullable_string_dtype)
+ inferred = lib.infer_dtype(val, skipna=skipna)
+ assert inferred == "string"
+
+ @pytest.mark.parametrize("data", [[True, False, True], [True, False, pd.NA]])
+ def test_boolean_dtype(self, data, skipna, index_or_series_or_array):
+ # BooleanArray
+ val = index_or_series_or_array(data, dtype="boolean")
+ inferred = lib.infer_dtype(val, skipna=skipna)
+ assert inferred == "boolean"
+
+
+class TestNumberScalar:
+ def test_is_number(self):
+ assert is_number(True)
+ assert is_number(1)
+ assert is_number(1.1)
+ assert is_number(1 + 3j)
+ assert is_number(np.int64(1))
+ assert is_number(np.float64(1.1))
+ assert is_number(np.complex128(1 + 3j))
+ assert is_number(np.nan)
+
+ assert not is_number(None)
+ assert not is_number("x")
+ assert not is_number(datetime(2011, 1, 1))
+ assert not is_number(np.datetime64("2011-01-01"))
+ assert not is_number(Timestamp("2011-01-01"))
+ assert not is_number(Timestamp("2011-01-01", tz="US/Eastern"))
+ assert not is_number(timedelta(1000))
+ assert not is_number(Timedelta("1 days"))
+
+ # questionable
+ assert not is_number(np.bool_(False))
+ assert is_number(np.timedelta64(1, "D"))
+
+ def test_is_bool(self):
+ assert is_bool(True)
+ assert is_bool(False)
+ assert is_bool(np.bool_(False))
+
+ assert not is_bool(1)
+ assert not is_bool(1.1)
+ assert not is_bool(1 + 3j)
+ assert not is_bool(np.int64(1))
+ assert not is_bool(np.float64(1.1))
+ assert not is_bool(np.complex128(1 + 3j))
+ assert not is_bool(np.nan)
+ assert not is_bool(None)
+ assert not is_bool("x")
+ assert not is_bool(datetime(2011, 1, 1))
+ assert not is_bool(np.datetime64("2011-01-01"))
+ assert not is_bool(Timestamp("2011-01-01"))
+ assert not is_bool(Timestamp("2011-01-01", tz="US/Eastern"))
+ assert not is_bool(timedelta(1000))
+ assert not is_bool(np.timedelta64(1, "D"))
+ assert not is_bool(Timedelta("1 days"))
+
+ def test_is_integer(self):
+ assert is_integer(1)
+ assert is_integer(np.int64(1))
+
+ assert not is_integer(True)
+ assert not is_integer(1.1)
+ assert not is_integer(1 + 3j)
+ assert not is_integer(False)
+ assert not is_integer(np.bool_(False))
+ assert not is_integer(np.float64(1.1))
+ assert not is_integer(np.complex128(1 + 3j))
+ assert not is_integer(np.nan)
+ assert not is_integer(None)
+ assert not is_integer("x")
+ assert not is_integer(datetime(2011, 1, 1))
+ assert not is_integer(np.datetime64("2011-01-01"))
+ assert not is_integer(Timestamp("2011-01-01"))
+ assert not is_integer(Timestamp("2011-01-01", tz="US/Eastern"))
+ assert not is_integer(timedelta(1000))
+ assert not is_integer(Timedelta("1 days"))
+ assert not is_integer(np.timedelta64(1, "D"))
+
+ def test_is_float(self):
+ assert is_float(1.1)
+ assert is_float(np.float64(1.1))
+ assert is_float(np.nan)
+
+ assert not is_float(True)
+ assert not is_float(1)
+ assert not is_float(1 + 3j)
+ assert not is_float(False)
+ assert not is_float(np.bool_(False))
+ assert not is_float(np.int64(1))
+ assert not is_float(np.complex128(1 + 3j))
+ assert not is_float(None)
+ assert not is_float("x")
+ assert not is_float(datetime(2011, 1, 1))
+ assert not is_float(np.datetime64("2011-01-01"))
+ assert not is_float(Timestamp("2011-01-01"))
+ assert not is_float(Timestamp("2011-01-01", tz="US/Eastern"))
+ assert not is_float(timedelta(1000))
+ assert not is_float(np.timedelta64(1, "D"))
+ assert not is_float(Timedelta("1 days"))
+
+ def test_is_datetime_dtypes(self):
+ ts = pd.date_range("20130101", periods=3, unit="ns")
+ tsa = pd.date_range("20130101", periods=3, tz="US/Eastern", unit="ns")
+
+ msg = "is_datetime64tz_dtype is deprecated"
+
+ assert is_datetime64_dtype("datetime64")
+ assert is_datetime64_dtype("datetime64[ns]")
+ assert is_datetime64_dtype(ts)
+ assert not is_datetime64_dtype(tsa)
+
+ assert not is_datetime64_ns_dtype("datetime64")
+ assert is_datetime64_ns_dtype("datetime64[ns]")
+ assert is_datetime64_ns_dtype(ts)
+ assert is_datetime64_ns_dtype(tsa)
+
+ assert is_datetime64_any_dtype("datetime64")
+ assert is_datetime64_any_dtype("datetime64[ns]")
+ assert is_datetime64_any_dtype(ts)
+ assert is_datetime64_any_dtype(tsa)
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ assert not is_datetime64tz_dtype("datetime64")
+ assert not is_datetime64tz_dtype("datetime64[ns]")
+ assert not is_datetime64tz_dtype(ts)
+ assert is_datetime64tz_dtype(tsa)
+
+ @pytest.mark.parametrize("tz", ["US/Eastern", "UTC"])
+ def test_is_datetime_dtypes_with_tz(self, tz):
+ dtype = f"datetime64[ns, {tz}]"
+ assert not is_datetime64_dtype(dtype)
+
+ msg = "is_datetime64tz_dtype is deprecated"
+ with tm.assert_produces_warning(DeprecationWarning, match=msg):
+ assert is_datetime64tz_dtype(dtype)
+ assert is_datetime64_ns_dtype(dtype)
+ assert is_datetime64_any_dtype(dtype)
+
+ def test_is_timedelta(self):
+ assert is_timedelta64_dtype("timedelta64")
+ assert is_timedelta64_dtype("timedelta64[ns]")
+ assert not is_timedelta64_ns_dtype("timedelta64")
+ assert is_timedelta64_ns_dtype("timedelta64[ns]")
+
+ tdi = TimedeltaIndex([1e14, 2e14], dtype="timedelta64[ns]")
+ assert is_timedelta64_dtype(tdi)
+ assert is_timedelta64_ns_dtype(tdi)
+ assert is_timedelta64_ns_dtype(tdi.astype("timedelta64[ns]"))
+
+ assert not is_timedelta64_ns_dtype(Index([], dtype=np.float64))
+ assert not is_timedelta64_ns_dtype(Index([], dtype=np.int64))
+
+
+class TestIsScalar:
+ def test_is_scalar_builtin_scalars(self):
+ assert is_scalar(None)
+ assert is_scalar(True)
+ assert is_scalar(False)
+ assert is_scalar(Fraction())
+ assert is_scalar(0.0)
+ assert is_scalar(1)
+ assert is_scalar(complex(2))
+ assert is_scalar(float("NaN"))
+ assert is_scalar(np.nan)
+ assert is_scalar("foobar")
+ assert is_scalar(b"foobar")
+ assert is_scalar(datetime(2014, 1, 1))
+ assert is_scalar(date(2014, 1, 1))
+ assert is_scalar(time(12, 0))
+ assert is_scalar(timedelta(hours=1))
+ assert is_scalar(pd.NaT)
+ assert is_scalar(pd.NA)
+
+ def test_is_scalar_builtin_nonscalars(self):
+ assert not is_scalar({})
+ assert not is_scalar([])
+ assert not is_scalar([1])
+ assert not is_scalar(())
+ assert not is_scalar((1,))
+ assert not is_scalar(slice(None))
+ assert not is_scalar(Ellipsis)
+
+ def test_is_scalar_numpy_array_scalars(self):
+ assert is_scalar(np.int64(1))
+ assert is_scalar(np.float64(1.0))
+ assert is_scalar(np.int32(1))
+ assert is_scalar(np.complex64(2))
+ assert is_scalar(np.object_("foobar"))
+ assert is_scalar(np.str_("foobar"))
+ assert is_scalar(np.bytes_(b"foobar"))
+ assert is_scalar(np.datetime64("2014-01-01"))
+ assert is_scalar(np.timedelta64(1, "h"))
+
+ @pytest.mark.parametrize(
+ "zerodim",
+ [
+ 1,
+ "foobar",
+ np.datetime64("2014-01-01"),
+ np.timedelta64(1, "h"),
+ np.datetime64("NaT"),
+ ],
+ )
+ def test_is_scalar_numpy_zerodim_arrays(self, zerodim):
+ zerodim = np.array(zerodim)
+ assert not is_scalar(zerodim)
+ assert is_scalar(lib.item_from_zerodim(zerodim))
+
+ @pytest.mark.parametrize("arr", [np.array([]), np.array([[]])])
+ def test_is_scalar_numpy_arrays(self, arr):
+ assert not is_scalar(arr)
+ assert not is_scalar(MockNumpyLikeArray(arr))
+
+ def test_is_scalar_pandas_scalars(self):
+ assert is_scalar(Timestamp("2014-01-01"))
+ assert is_scalar(Timedelta(hours=1))
+ assert is_scalar(Period("2014-01-01"))
+ assert is_scalar(Interval(left=0, right=1))
+ assert is_scalar(DateOffset(days=1))
+ assert is_scalar(pd.offsets.Minute(3))
+
+ def test_is_scalar_pandas_containers(self):
+ assert not is_scalar(Series(dtype=object))
+ assert not is_scalar(Series([1]))
+ assert not is_scalar(DataFrame())
+ assert not is_scalar(DataFrame([[1]]))
+ assert not is_scalar(Index([]))
+ assert not is_scalar(Index([1]))
+ assert not is_scalar(Categorical([]))
+ assert not is_scalar(DatetimeIndex([])._data)
+ assert not is_scalar(TimedeltaIndex([])._data)
+ assert not is_scalar(DatetimeIndex([])._data.to_period("D"))
+ assert not is_scalar(pd.array([1, 2, 3]))
+
+ def test_is_scalar_number(self):
+ # Number() is not recognized by PyNumber_Check, so by extension
+ # is not recognized by is_scalar, but instances of non-abstract
+ # subclasses are.
+
+ class Numeric(Number):
+ def __init__(self, value) -> None:
+ self.value = value
+
+ def __int__(self) -> int:
+ return self.value
+
+ num = Numeric(1)
+ assert is_scalar(num)
+
+
+@pytest.mark.parametrize("unit", ["ms", "us", "ns"])
+def test_datetimeindex_from_empty_datetime64_array(unit):
+ idx = DatetimeIndex(np.array([], dtype=f"datetime64[{unit}]"))
+ assert len(idx) == 0
+
+
+def test_nan_to_nat_conversions():
+ df = DataFrame(
+ {"A": np.asarray(range(10), dtype="float64"), "B": Timestamp("20010101")}
+ )
+ df.iloc[3:6, :] = np.nan
+ result = df.loc[4, "B"]
+ assert result is pd.NaT
+
+ s = df["B"].copy()
+ s[8:9] = np.nan
+ assert s[8] is pd.NaT
+
+
+@pytest.mark.filterwarnings("ignore::PendingDeprecationWarning")
+@pytest.mark.parametrize("spmatrix", ["bsr", "coo", "csc", "csr", "dia", "dok", "lil"])
+def test_is_scipy_sparse(spmatrix):
+ sparse = pytest.importorskip("scipy.sparse")
+
+ klass = getattr(sparse, spmatrix + "_matrix")
+ assert is_scipy_sparse(klass([[0, 1]]))
+ assert not is_scipy_sparse(np.array([1]))
+
+
+def test_ensure_int32():
+ values = np.arange(10, dtype=np.int32)
+ result = ensure_int32(values)
+ assert result.dtype == np.int32
+
+ values = np.arange(10, dtype=np.int64)
+ result = ensure_int32(values)
+ assert result.dtype == np.int32
+
+
+@pytest.mark.parametrize(
+ "right,result",
+ [
+ (0, np.uint8),
+ (-1, np.int16),
+ (300, np.uint16),
+ # For floats, we just upcast directly to float64 instead of trying to
+ # find a smaller floating dtype
+ (300.0, np.uint16), # for integer floats, we convert them to ints
+ (300.1, np.float64),
+ (np.int16(300), np.int16 if np_version_gt2 else np.uint16),
+ ],
+)
+def test_find_result_type_uint_int(right, result):
+ left_dtype = np.dtype("uint8")
+ assert find_result_type(left_dtype, right) == result
+
+
+@pytest.mark.parametrize(
+ "right,result",
+ [
+ (0, np.int8),
+ (-1, np.int8),
+ (300, np.int16),
+ # For floats, we just upcast directly to float64 instead of trying to
+ # find a smaller floating dtype
+ (300.0, np.int16), # for integer floats, we convert them to ints
+ (300.1, np.float64),
+ (np.int16(300), np.int16),
+ ],
+)
+def test_find_result_type_int_int(right, result):
+ left_dtype = np.dtype("int8")
+ assert find_result_type(left_dtype, right) == result
+
+
+@pytest.mark.parametrize(
+ "right,result",
+ [
+ (300.0, np.float64),
+ (np.float32(300), np.float32),
+ ],
+)
+def test_find_result_type_floats(right, result):
+ left_dtype = np.dtype("float16")
+ assert find_result_type(left_dtype, right) == result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/test_missing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/test_missing.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5b22ac30d820b4d1c2178bf3a532fafc9f917e3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/dtypes/test_missing.py
@@ -0,0 +1,876 @@
+from datetime import datetime
+from decimal import Decimal
+
+import numpy as np
+import pytest
+
+from pandas._libs import missing as libmissing
+from pandas._libs.tslibs import iNaT
+
+from pandas.core.dtypes.common import (
+ is_float,
+ is_scalar,
+ pandas_dtype,
+)
+from pandas.core.dtypes.dtypes import (
+ CategoricalDtype,
+ DatetimeTZDtype,
+ IntervalDtype,
+ PeriodDtype,
+)
+from pandas.core.dtypes.missing import (
+ array_equivalent,
+ is_valid_na_for_dtype,
+ isna,
+ isnull,
+ na_value_for_dtype,
+ notna,
+ notnull,
+)
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Index,
+ NaT,
+ Series,
+ TimedeltaIndex,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+fix_now = pd.Timestamp("2021-01-01")
+fix_utcnow = pd.Timestamp("2021-01-01", tz="UTC")
+
+
+@pytest.mark.parametrize("notna_f", [notna, notnull])
+def test_notna_notnull(notna_f):
+ assert notna_f(1.0)
+ assert not notna_f(None)
+ assert not notna_f(np.nan)
+
+
+@pytest.mark.parametrize("null_func", [notna, notnull, isna, isnull])
+@pytest.mark.parametrize(
+ "ser",
+ [
+ Series(
+ [str(i) for i in range(5)],
+ index=Index([str(i) for i in range(5)], dtype=object),
+ dtype=object,
+ ),
+ Series(range(5), date_range("2020-01-01", periods=5)),
+ Series(range(5), period_range("2020-01-01", periods=5)),
+ ],
+)
+def test_null_check_is_series(null_func, ser):
+ assert isinstance(null_func(ser), Series)
+
+
+class TestIsNA:
+ def test_0d_array(self):
+ assert isna(np.array(np.nan))
+ assert not isna(np.array(0.0))
+ assert not isna(np.array(0))
+ # test object dtype
+ assert isna(np.array(np.nan, dtype=object))
+ assert not isna(np.array(0.0, dtype=object))
+ assert not isna(np.array(0, dtype=object))
+
+ @pytest.mark.parametrize("shape", [(4, 0), (4,)])
+ def test_empty_object(self, shape):
+ arr = np.empty(shape=shape, dtype=object)
+ result = isna(arr)
+ expected = np.ones(shape=shape, dtype=bool)
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("isna_f", [isna, isnull])
+ def test_isna_isnull(self, isna_f):
+ assert not isna_f(1.0)
+ assert isna_f(None)
+ assert isna_f(np.nan)
+ assert float("nan")
+ assert not isna_f(np.inf)
+ assert not isna_f(-np.inf)
+
+ # type
+ assert not isna_f(type(Series(dtype=object)))
+ assert not isna_f(type(Series(dtype=np.float64)))
+ assert not isna_f(type(pd.DataFrame()))
+
+ @pytest.mark.parametrize("isna_f", [isna, isnull])
+ @pytest.mark.parametrize(
+ "data",
+ [
+ np.arange(4, dtype=float),
+ [0.0, 1.0, 0.0, 1.0],
+ Series(list("abcd"), dtype=object),
+ date_range("2020-01-01", periods=4),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "index",
+ [
+ date_range("2020-01-01", periods=4),
+ range(4),
+ period_range("2020-01-01", periods=4),
+ ],
+ )
+ def test_isna_isnull_frame(self, isna_f, data, index):
+ # frame
+ df = pd.DataFrame(data, index=index)
+ result = isna_f(df)
+ expected = df.apply(isna_f)
+ tm.assert_frame_equal(result, expected)
+
+ def test_isna_lists(self):
+ result = isna([[False]])
+ exp = np.array([[False]])
+ tm.assert_numpy_array_equal(result, exp)
+
+ result = isna([[1], [2]])
+ exp = np.array([[False], [False]])
+ tm.assert_numpy_array_equal(result, exp)
+
+ # list of strings / unicode
+ result = isna(["foo", "bar"])
+ exp = np.array([False, False])
+ tm.assert_numpy_array_equal(result, exp)
+
+ result = isna(["foo", "bar"])
+ exp = np.array([False, False])
+ tm.assert_numpy_array_equal(result, exp)
+
+ # GH20675
+ result = isna([np.nan, "world"])
+ exp = np.array([True, False])
+ tm.assert_numpy_array_equal(result, exp)
+
+ def test_isna_nat(self):
+ result = isna([NaT])
+ exp = np.array([True])
+ tm.assert_numpy_array_equal(result, exp)
+
+ result = isna(np.array([NaT], dtype=object))
+ exp = np.array([True])
+ tm.assert_numpy_array_equal(result, exp)
+
+ def test_isna_numpy_nat(self):
+ arr = np.array(
+ [
+ NaT,
+ np.datetime64("NaT"),
+ np.timedelta64("NaT"),
+ np.datetime64("NaT", "s"),
+ ]
+ )
+ result = isna(arr)
+ expected = np.array([True] * 4)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_isna_datetime(self):
+ assert not isna(datetime.now())
+ assert notna(datetime.now())
+
+ idx = date_range("1/1/1990", periods=20)
+ exp = np.ones(len(idx), dtype=bool)
+ tm.assert_numpy_array_equal(notna(idx), exp)
+
+ idx = np.asarray(idx)
+ idx[0] = iNaT
+ idx = DatetimeIndex(idx)
+ mask = isna(idx)
+ assert mask[0]
+ exp = np.array([True] + [False] * (len(idx) - 1), dtype=bool)
+ tm.assert_numpy_array_equal(mask, exp)
+
+ # GH 9129
+ pidx = idx.to_period(freq="M")
+ mask = isna(pidx)
+ assert mask[0]
+ exp = np.array([True] + [False] * (len(idx) - 1), dtype=bool)
+ tm.assert_numpy_array_equal(mask, exp)
+
+ mask = isna(pidx[1:])
+ exp = np.zeros(len(mask), dtype=bool)
+ tm.assert_numpy_array_equal(mask, exp)
+
+ def test_isna_old_datetimelike(self):
+ # isna_old should work for dt64tz, td64, and period, not just tznaive
+ dti = date_range("2016-01-01", periods=3)
+ dta = dti._data
+ dta[-1] = NaT
+ expected = np.array([False, False, True], dtype=bool)
+
+ objs = [dta, dta.tz_localize("US/Eastern"), dta - dta, dta.to_period("D")]
+
+ for obj in objs:
+ result = isna(obj)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "value, expected",
+ [
+ (np.complex128(np.nan), True),
+ (np.float64(1), False),
+ (np.array([1, 1 + 0j, np.nan, 3]), np.array([False, False, True, False])),
+ (
+ np.array([1, 1 + 0j, np.nan, 3], dtype=object),
+ np.array([False, False, True, False]),
+ ),
+ (
+ np.array([1, 1 + 0j, np.nan, 3]).astype(object),
+ np.array([False, False, True, False]),
+ ),
+ ],
+ )
+ def test_complex(self, value, expected):
+ result = isna(value)
+ if is_scalar(result):
+ assert result is expected
+ else:
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_datetime_other_units(self):
+ idx = DatetimeIndex(["2011-01-01", "NaT", "2011-01-02"])
+ exp = np.array([False, True, False])
+ tm.assert_numpy_array_equal(isna(idx), exp)
+ tm.assert_numpy_array_equal(notna(idx), ~exp)
+ tm.assert_numpy_array_equal(isna(idx.values), exp)
+ tm.assert_numpy_array_equal(notna(idx.values), ~exp)
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ "datetime64[D]",
+ "datetime64[h]",
+ "datetime64[m]",
+ "datetime64[s]",
+ "datetime64[ms]",
+ "datetime64[us]",
+ "datetime64[ns]",
+ ],
+ )
+ def test_datetime_other_units_astype(self, dtype):
+ idx = DatetimeIndex(["2011-01-01", "NaT", "2011-01-02"])
+ values = idx.values.astype(dtype)
+
+ exp = np.array([False, True, False])
+ tm.assert_numpy_array_equal(isna(values), exp)
+ tm.assert_numpy_array_equal(notna(values), ~exp)
+
+ exp = Series([False, True, False])
+ s = Series(values)
+ tm.assert_series_equal(isna(s), exp)
+ tm.assert_series_equal(notna(s), ~exp)
+ s = Series(values, dtype=object)
+ tm.assert_series_equal(isna(s), exp)
+ tm.assert_series_equal(notna(s), ~exp)
+
+ def test_timedelta_other_units(self):
+ idx = TimedeltaIndex(["1 days", "NaT", "2 days"])
+ exp = np.array([False, True, False])
+ tm.assert_numpy_array_equal(isna(idx), exp)
+ tm.assert_numpy_array_equal(notna(idx), ~exp)
+ tm.assert_numpy_array_equal(isna(idx.values), exp)
+ tm.assert_numpy_array_equal(notna(idx.values), ~exp)
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ "timedelta64[D]",
+ "timedelta64[h]",
+ "timedelta64[m]",
+ "timedelta64[s]",
+ "timedelta64[ms]",
+ "timedelta64[us]",
+ "timedelta64[ns]",
+ ],
+ )
+ def test_timedelta_other_units_dtype(self, dtype):
+ idx = TimedeltaIndex(["1 days", "NaT", "2 days"])
+ values = idx.values.astype(dtype)
+
+ exp = np.array([False, True, False])
+ tm.assert_numpy_array_equal(isna(values), exp)
+ tm.assert_numpy_array_equal(notna(values), ~exp)
+
+ exp = Series([False, True, False])
+ s = Series(values)
+ tm.assert_series_equal(isna(s), exp)
+ tm.assert_series_equal(notna(s), ~exp)
+ s = Series(values, dtype=object)
+ tm.assert_series_equal(isna(s), exp)
+ tm.assert_series_equal(notna(s), ~exp)
+
+ def test_period(self):
+ idx = pd.PeriodIndex(["2011-01", "NaT", "2012-01"], freq="M")
+ exp = np.array([False, True, False])
+ tm.assert_numpy_array_equal(isna(idx), exp)
+ tm.assert_numpy_array_equal(notna(idx), ~exp)
+
+ exp = Series([False, True, False])
+ s = Series(idx)
+ tm.assert_series_equal(isna(s), exp)
+ tm.assert_series_equal(notna(s), ~exp)
+ s = Series(idx, dtype=object)
+ tm.assert_series_equal(isna(s), exp)
+ tm.assert_series_equal(notna(s), ~exp)
+
+ def test_decimal(self):
+ # scalars GH#23530
+ a = Decimal("1.0")
+ assert isna(a) is False
+ assert notna(a) is True
+
+ b = Decimal("NaN")
+ assert isna(b) is True
+ assert notna(b) is False
+
+ # array
+ arr = np.array([a, b])
+ expected = np.array([False, True])
+ result = isna(arr)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = notna(arr)
+ tm.assert_numpy_array_equal(result, ~expected)
+
+ # series
+ ser = Series(arr)
+ expected = Series(expected)
+ result = isna(ser)
+ tm.assert_series_equal(result, expected)
+
+ result = notna(ser)
+ tm.assert_series_equal(result, ~expected)
+
+ # index
+ idx = Index(arr)
+ expected = np.array([False, True])
+ result = isna(idx)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = notna(idx)
+ tm.assert_numpy_array_equal(result, ~expected)
+
+
+@pytest.mark.parametrize("dtype_equal", [True, False])
+def test_array_equivalent(dtype_equal):
+ assert array_equivalent(
+ np.array([np.nan, np.nan]), np.array([np.nan, np.nan]), dtype_equal=dtype_equal
+ )
+ assert array_equivalent(
+ np.array([np.nan, 1, np.nan]),
+ np.array([np.nan, 1, np.nan]),
+ dtype_equal=dtype_equal,
+ )
+ assert array_equivalent(
+ np.array([np.nan, None], dtype="object"),
+ np.array([np.nan, None], dtype="object"),
+ dtype_equal=dtype_equal,
+ )
+ # Check the handling of nested arrays in array_equivalent_object
+ assert array_equivalent(
+ np.array([np.array([np.nan, None], dtype="object"), None], dtype="object"),
+ np.array([np.array([np.nan, None], dtype="object"), None], dtype="object"),
+ dtype_equal=dtype_equal,
+ )
+ assert array_equivalent(
+ np.array([np.nan, 1 + 1j], dtype="complex"),
+ np.array([np.nan, 1 + 1j], dtype="complex"),
+ dtype_equal=dtype_equal,
+ )
+ assert not array_equivalent(
+ np.array([np.nan, 1 + 1j], dtype="complex"),
+ np.array([np.nan, 1 + 2j], dtype="complex"),
+ dtype_equal=dtype_equal,
+ )
+ assert not array_equivalent(
+ np.array([np.nan, 1, np.nan]),
+ np.array([np.nan, 2, np.nan]),
+ dtype_equal=dtype_equal,
+ )
+ assert not array_equivalent(
+ np.array(["a", "b", "c", "d"]), np.array(["e", "e"]), dtype_equal=dtype_equal
+ )
+ assert array_equivalent(
+ Index([0, np.nan]), Index([0, np.nan]), dtype_equal=dtype_equal
+ )
+ assert not array_equivalent(
+ Index([0, np.nan]), Index([1, np.nan]), dtype_equal=dtype_equal
+ )
+
+
+@pytest.mark.parametrize("dtype_equal", [True, False])
+def test_array_equivalent_tdi(dtype_equal):
+ assert array_equivalent(
+ TimedeltaIndex([0, np.nan]),
+ TimedeltaIndex([0, np.nan]),
+ dtype_equal=dtype_equal,
+ )
+ assert not array_equivalent(
+ TimedeltaIndex([0, np.nan]),
+ TimedeltaIndex([1, np.nan]),
+ dtype_equal=dtype_equal,
+ )
+
+
+@pytest.mark.parametrize("dtype_equal", [True, False])
+def test_array_equivalent_dti(dtype_equal):
+ assert array_equivalent(
+ DatetimeIndex([0, np.nan]), DatetimeIndex([0, np.nan]), dtype_equal=dtype_equal
+ )
+ assert not array_equivalent(
+ DatetimeIndex([0, np.nan]), DatetimeIndex([1, np.nan]), dtype_equal=dtype_equal
+ )
+
+ dti1 = DatetimeIndex([0, np.nan], tz="US/Eastern")
+ dti2 = DatetimeIndex([0, np.nan], tz="CET")
+ dti3 = DatetimeIndex([1, np.nan], tz="US/Eastern")
+
+ assert array_equivalent(
+ dti1,
+ dti1,
+ dtype_equal=dtype_equal,
+ )
+ assert not array_equivalent(
+ dti1,
+ dti3,
+ dtype_equal=dtype_equal,
+ )
+ # The rest are not dtype_equal
+ assert not array_equivalent(DatetimeIndex([0, np.nan]), dti1)
+ assert array_equivalent(
+ dti2,
+ dti1,
+ )
+
+ assert not array_equivalent(DatetimeIndex([0, np.nan]), TimedeltaIndex([0, np.nan]))
+
+
+@pytest.mark.parametrize(
+ "val", [1, 1.1, 1 + 1j, True, "abc", [1, 2], (1, 2), {1, 2}, {"a": 1}, None]
+)
+def test_array_equivalent_series(val):
+ arr = np.array([1, 2])
+ assert not array_equivalent(Series([arr, arr]), Series([arr, val]))
+
+
+def test_array_equivalent_array_mismatched_shape():
+ # to trigger the motivating bug, the first N elements of the arrays need
+ # to match
+ first = np.array([1, 2, 3])
+ second = np.array([1, 2])
+
+ left = Series([first, "a"], dtype=object)
+ right = Series([second, "a"], dtype=object)
+ assert not array_equivalent(left, right)
+
+
+def test_array_equivalent_array_mismatched_dtype():
+ # same shape, different dtype can still be equivalent
+ first = np.array([1, 2], dtype=np.float64)
+ second = np.array([1, 2])
+
+ left = Series([first, "a"], dtype=object)
+ right = Series([second, "a"], dtype=object)
+ assert array_equivalent(left, right)
+
+
+def test_array_equivalent_different_dtype_but_equal():
+ # Unclear if this is exposed anywhere in the public-facing API
+ assert array_equivalent(np.array([1, 2]), np.array([1.0, 2.0]))
+
+
+@pytest.mark.parametrize(
+ "lvalue, rvalue",
+ [
+ # There are 3 variants for each of lvalue and rvalue. We include all
+ # three for the tz-naive `now` and exclude the datetim64 variant
+ # for utcnow because it drops tzinfo.
+ (fix_now, fix_utcnow),
+ (fix_now.to_datetime64(), fix_utcnow),
+ (fix_now.to_pydatetime(), fix_utcnow),
+ (fix_now.to_datetime64(), fix_utcnow.to_pydatetime()),
+ (fix_now.to_pydatetime(), fix_utcnow.to_pydatetime()),
+ ],
+)
+def test_array_equivalent_tzawareness(lvalue, rvalue):
+ # we shouldn't raise if comparing tzaware and tznaive datetimes
+ left = np.array([lvalue], dtype=object)
+ right = np.array([rvalue], dtype=object)
+
+ assert not array_equivalent(left, right, strict_nan=True)
+ assert not array_equivalent(left, right, strict_nan=False)
+
+
+def test_array_equivalent_compat():
+ # see gh-13388
+ m = np.array([(1, 2), (3, 4)], dtype=[("a", int), ("b", float)])
+ n = np.array([(1, 2), (3, 4)], dtype=[("a", int), ("b", float)])
+ assert array_equivalent(m, n, strict_nan=True)
+ assert array_equivalent(m, n, strict_nan=False)
+
+ m = np.array([(1, 2), (3, 4)], dtype=[("a", int), ("b", float)])
+ n = np.array([(1, 2), (4, 3)], dtype=[("a", int), ("b", float)])
+ assert not array_equivalent(m, n, strict_nan=True)
+ assert not array_equivalent(m, n, strict_nan=False)
+
+ m = np.array([(1, 2), (3, 4)], dtype=[("a", int), ("b", float)])
+ n = np.array([(1, 2), (3, 4)], dtype=[("b", int), ("a", float)])
+ assert not array_equivalent(m, n, strict_nan=True)
+ assert not array_equivalent(m, n, strict_nan=False)
+
+
+@pytest.mark.parametrize("dtype", ["O", "S", "U"])
+def test_array_equivalent_str(dtype):
+ assert array_equivalent(
+ np.array(["A", "B"], dtype=dtype), np.array(["A", "B"], dtype=dtype)
+ )
+ assert not array_equivalent(
+ np.array(["A", "B"], dtype=dtype), np.array(["A", "X"], dtype=dtype)
+ )
+
+
+@pytest.mark.parametrize("strict_nan", [True, False])
+def test_array_equivalent_nested(strict_nan):
+ # reached in groupby aggregations, make sure we use np.any when checking
+ # if the comparison is truthy
+ left = np.array([np.array([50, 70, 90]), np.array([20, 30])], dtype=object)
+ right = np.array([np.array([50, 70, 90]), np.array([20, 30])], dtype=object)
+
+ assert array_equivalent(left, right, strict_nan=strict_nan)
+ assert not array_equivalent(left, right[::-1], strict_nan=strict_nan)
+
+ left = np.empty(2, dtype=object)
+ left[:] = [np.array([50, 70, 90]), np.array([20, 30, 40])]
+ right = np.empty(2, dtype=object)
+ right[:] = [np.array([50, 70, 90]), np.array([20, 30, 40])]
+ assert array_equivalent(left, right, strict_nan=strict_nan)
+ assert not array_equivalent(left, right[::-1], strict_nan=strict_nan)
+
+ left = np.array([np.array([50, 50, 50]), np.array([40, 40])], dtype=object)
+ right = np.array([50, 40])
+ assert not array_equivalent(left, right, strict_nan=strict_nan)
+
+
+@pytest.mark.filterwarnings("ignore:elementwise comparison failed:DeprecationWarning")
+@pytest.mark.parametrize("strict_nan", [True, False])
+def test_array_equivalent_nested2(strict_nan):
+ # more than one level of nesting
+ left = np.array(
+ [
+ np.array([np.array([50, 70]), np.array([90])], dtype=object),
+ np.array([np.array([20, 30])], dtype=object),
+ ],
+ dtype=object,
+ )
+ right = np.array(
+ [
+ np.array([np.array([50, 70]), np.array([90])], dtype=object),
+ np.array([np.array([20, 30])], dtype=object),
+ ],
+ dtype=object,
+ )
+ assert array_equivalent(left, right, strict_nan=strict_nan)
+ assert not array_equivalent(left, right[::-1], strict_nan=strict_nan)
+
+ left = np.array([np.array([np.array([50, 50, 50])], dtype=object)], dtype=object)
+ right = np.array([50])
+ assert not array_equivalent(left, right, strict_nan=strict_nan)
+
+
+@pytest.mark.parametrize("strict_nan", [True, False])
+def test_array_equivalent_nested_list(strict_nan):
+ left = np.array([[50, 70, 90], [20, 30]], dtype=object)
+ right = np.array([[50, 70, 90], [20, 30]], dtype=object)
+
+ assert array_equivalent(left, right, strict_nan=strict_nan)
+ assert not array_equivalent(left, right[::-1], strict_nan=strict_nan)
+
+ left = np.array([[50, 50, 50], [40, 40]], dtype=object)
+ right = np.array([50, 40])
+ assert not array_equivalent(left, right, strict_nan=strict_nan)
+
+
+@pytest.mark.filterwarnings("ignore:elementwise comparison failed:DeprecationWarning")
+@pytest.mark.xfail(reason="failing")
+@pytest.mark.parametrize("strict_nan", [True, False])
+def test_array_equivalent_nested_mixed_list(strict_nan):
+ # mixed arrays / lists in left and right
+ # https://github.com/pandas-dev/pandas/issues/50360
+ left = np.array([np.array([1, 2, 3]), np.array([4, 5])], dtype=object)
+ right = np.array([[1, 2, 3], [4, 5]], dtype=object)
+
+ assert array_equivalent(left, right, strict_nan=strict_nan)
+ assert not array_equivalent(left, right[::-1], strict_nan=strict_nan)
+
+ # multiple levels of nesting
+ left = np.array(
+ [
+ np.array([np.array([1, 2, 3]), np.array([4, 5])], dtype=object),
+ np.array([np.array([6]), np.array([7, 8]), np.array([9])], dtype=object),
+ ],
+ dtype=object,
+ )
+ right = np.array([[[1, 2, 3], [4, 5]], [[6], [7, 8], [9]]], dtype=object)
+ assert array_equivalent(left, right, strict_nan=strict_nan)
+ assert not array_equivalent(left, right[::-1], strict_nan=strict_nan)
+
+ # same-length lists
+ subarr = np.empty(2, dtype=object)
+ subarr[:] = [
+ np.array([None, "b"], dtype=object),
+ np.array(["c", "d"], dtype=object),
+ ]
+ left = np.array([subarr, None], dtype=object)
+ right = np.array([[[None, "b"], ["c", "d"]], None], dtype=object)
+ assert array_equivalent(left, right, strict_nan=strict_nan)
+ assert not array_equivalent(left, right[::-1], strict_nan=strict_nan)
+
+
+@pytest.mark.xfail(reason="failing")
+@pytest.mark.parametrize("strict_nan", [True, False])
+def test_array_equivalent_nested_dicts(strict_nan):
+ left = np.array([{"f1": 1, "f2": np.array(["a", "b"], dtype=object)}], dtype=object)
+ right = np.array(
+ [{"f1": 1, "f2": np.array(["a", "b"], dtype=object)}], dtype=object
+ )
+ assert array_equivalent(left, right, strict_nan=strict_nan)
+ assert not array_equivalent(left, right[::-1], strict_nan=strict_nan)
+
+ right2 = np.array([{"f1": 1, "f2": ["a", "b"]}], dtype=object)
+ assert array_equivalent(left, right2, strict_nan=strict_nan)
+ assert not array_equivalent(left, right2[::-1], strict_nan=strict_nan)
+
+
+def test_array_equivalent_index_with_tuples():
+ # GH#48446
+ idx1 = Index(np.array([(pd.NA, 4), (1, 1)], dtype="object"))
+ idx2 = Index(np.array([(1, 1), (pd.NA, 4)], dtype="object"))
+ assert not array_equivalent(idx1, idx2)
+ assert not idx1.equals(idx2)
+ assert not array_equivalent(idx2, idx1)
+ assert not idx2.equals(idx1)
+
+ idx1 = Index(np.array([(4, pd.NA), (1, 1)], dtype="object"))
+ idx2 = Index(np.array([(1, 1), (4, pd.NA)], dtype="object"))
+ assert not array_equivalent(idx1, idx2)
+ assert not idx1.equals(idx2)
+ assert not array_equivalent(idx2, idx1)
+ assert not idx2.equals(idx1)
+
+
+@pytest.mark.parametrize(
+ "dtype, na_value",
+ [
+ # Datetime-like
+ (np.dtype("M8[ns]"), np.datetime64("NaT", "ns")),
+ (np.dtype("m8[ns]"), np.timedelta64("NaT", "ns")),
+ (DatetimeTZDtype.construct_from_string("datetime64[ns, US/Eastern]"), NaT),
+ (PeriodDtype("M"), NaT),
+ # Integer
+ ("u1", 0),
+ ("u2", 0),
+ ("u4", 0),
+ ("u8", 0),
+ ("i1", 0),
+ ("i2", 0),
+ ("i4", 0),
+ ("i8", 0),
+ # Bool
+ ("bool", False),
+ # Float
+ ("f2", np.nan),
+ ("f4", np.nan),
+ ("f8", np.nan),
+ # Complex
+ ("c8", np.nan),
+ ("c16", np.nan),
+ # Object
+ ("O", np.nan),
+ # Interval
+ (IntervalDtype(), np.nan),
+ ],
+)
+def test_na_value_for_dtype(dtype, na_value):
+ result = na_value_for_dtype(pandas_dtype(dtype))
+ # identify check doesn't work for datetime64/timedelta64("NaT") bc they
+ # are not singletons
+ assert result is na_value or (
+ isna(result) and isna(na_value) and type(result) is type(na_value)
+ )
+
+
+class TestNAObj:
+ def _check_behavior(self, arr, expected):
+ result = libmissing.isnaobj(arr)
+ tm.assert_numpy_array_equal(result, expected)
+
+ arr = np.atleast_2d(arr)
+ expected = np.atleast_2d(expected)
+
+ result = libmissing.isnaobj(arr)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # Test fortran order
+ arr = arr.copy(order="F")
+ result = libmissing.isnaobj(arr)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_basic(self):
+ arr = np.array([1, None, "foo", -5.1, NaT, np.nan])
+ expected = np.array([False, True, False, False, True, True])
+
+ self._check_behavior(arr, expected)
+
+ def test_non_obj_dtype(self):
+ arr = np.array([1, 3, np.nan, 5], dtype=float)
+ expected = np.array([False, False, True, False])
+
+ self._check_behavior(arr, expected)
+
+ def test_empty_arr(self):
+ arr = np.array([])
+ expected = np.array([], dtype=bool)
+
+ self._check_behavior(arr, expected)
+
+ def test_empty_str_inp(self):
+ arr = np.array([""]) # empty but not na
+ expected = np.array([False])
+
+ self._check_behavior(arr, expected)
+
+ def test_empty_like(self):
+ # see gh-13717: no segfaults!
+ arr = np.empty_like([None])
+ expected = np.array([True])
+
+ self._check_behavior(arr, expected)
+
+
+m8_units = ["as", "ps", "ns", "us", "ms", "s", "m", "h", "D", "W", "M", "Y"]
+
+na_vals = (
+ [
+ None,
+ NaT,
+ float("NaN"),
+ complex("NaN"),
+ np.nan,
+ np.float64("NaN"),
+ np.float32("NaN"),
+ np.complex64(np.nan),
+ np.complex128(np.nan),
+ np.datetime64("NaT"),
+ np.timedelta64("NaT"),
+ ]
+ + [np.datetime64("NaT", unit) for unit in m8_units] # type: ignore[call-overload]
+ + [np.timedelta64("NaT", unit) for unit in m8_units] # type: ignore[call-overload]
+)
+
+inf_vals = [
+ float("inf"),
+ float("-inf"),
+ complex("inf"),
+ complex("-inf"),
+ np.inf,
+ -np.inf,
+]
+
+int_na_vals = [
+ # Values that match iNaT, which we treat as null in specific cases
+ np.int64(NaT._value),
+ int(NaT._value),
+]
+
+sometimes_na_vals = [Decimal("NaN")]
+
+never_na_vals = [
+ # float/complex values that when viewed as int64 match iNaT
+ -0.0,
+ np.float64("-0.0"),
+ -0j,
+ np.complex64(-0j),
+]
+
+
+class TestLibMissing:
+ @pytest.mark.parametrize("func", [libmissing.checknull, isna])
+ @pytest.mark.parametrize(
+ "value",
+ na_vals + sometimes_na_vals, # type: ignore[operator]
+ )
+ def test_checknull_na_vals(self, func, value):
+ assert func(value)
+
+ @pytest.mark.parametrize("func", [libmissing.checknull, isna])
+ @pytest.mark.parametrize("value", inf_vals)
+ def test_checknull_inf_vals(self, func, value):
+ assert not func(value)
+
+ @pytest.mark.parametrize("func", [libmissing.checknull, isna])
+ @pytest.mark.parametrize("value", int_na_vals)
+ def test_checknull_intna_vals(self, func, value):
+ assert not func(value)
+
+ @pytest.mark.parametrize("func", [libmissing.checknull, isna])
+ @pytest.mark.parametrize("value", never_na_vals)
+ def test_checknull_never_na_vals(self, func, value):
+ assert not func(value)
+
+ @pytest.mark.parametrize(
+ "value",
+ na_vals + sometimes_na_vals, # type: ignore[operator]
+ )
+ def test_checknull_old_na_vals(self, value):
+ assert libmissing.checknull(value)
+
+ @pytest.mark.parametrize("value", int_na_vals)
+ def test_checknull_old_intna_vals(self, value):
+ assert not libmissing.checknull(value)
+
+ def test_is_matching_na(self, nulls_fixture, nulls_fixture2):
+ left = nulls_fixture
+ right = nulls_fixture2
+
+ assert libmissing.is_matching_na(left, left)
+
+ if left is right:
+ assert libmissing.is_matching_na(left, right)
+ elif is_float(left) and is_float(right):
+ # np.nan vs float("NaN") we consider as matching
+ assert libmissing.is_matching_na(left, right)
+ elif type(left) is type(right):
+ # e.g. both Decimal("NaN")
+ assert libmissing.is_matching_na(left, right)
+ else:
+ assert not libmissing.is_matching_na(left, right)
+
+ def test_is_matching_na_nan_matches_none(self):
+ assert not libmissing.is_matching_na(None, np.nan)
+ assert not libmissing.is_matching_na(np.nan, None)
+
+ assert libmissing.is_matching_na(None, np.nan, nan_matches_none=True)
+ assert libmissing.is_matching_na(np.nan, None, nan_matches_none=True)
+
+
+class TestIsValidNAForDtype:
+ def test_is_valid_na_for_dtype_interval(self):
+ dtype = IntervalDtype("int64", "left")
+ assert not is_valid_na_for_dtype(NaT, dtype)
+
+ dtype = IntervalDtype("datetime64[ns]", "both")
+ assert not is_valid_na_for_dtype(NaT, dtype)
+
+ def test_is_valid_na_for_dtype_categorical(self):
+ dtype = CategoricalDtype(categories=[0, 1, 2])
+ assert is_valid_na_for_dtype(np.nan, dtype)
+
+ assert not is_valid_na_for_dtype(NaT, dtype)
+ assert not is_valid_na_for_dtype(np.datetime64("NaT", "ns"), dtype)
+ assert not is_valid_na_for_dtype(np.timedelta64("NaT", "ns"), dtype)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/array_with_attr/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/array_with_attr/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..49da6af024a31726743815ba1e36d66c03daafe5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/array_with_attr/__init__.py
@@ -0,0 +1,6 @@
+from pandas.tests.extension.array_with_attr.array import (
+ FloatAttrArray,
+ FloatAttrDtype,
+)
+
+__all__ = ["FloatAttrArray", "FloatAttrDtype"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/array_with_attr/array.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/array_with_attr/array.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9cd16b102db4722e98a51f578d5c1a4c6bc0b95
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/array_with_attr/array.py
@@ -0,0 +1,89 @@
+"""
+Test extension array that has custom attribute information (not stored on the dtype).
+
+"""
+
+from __future__ import annotations
+
+import numbers
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from pandas.core.dtypes.base import ExtensionDtype
+
+import pandas as pd
+from pandas.core.arrays import ExtensionArray
+
+if TYPE_CHECKING:
+ from pandas._typing import type_t
+
+
+class FloatAttrDtype(ExtensionDtype):
+ type = float
+ name = "float_attr"
+ na_value = np.nan
+
+ def construct_array_type(self) -> type_t[FloatAttrArray]:
+ """
+ Return the array type associated with this dtype.
+
+ Returns
+ -------
+ type
+ """
+ return FloatAttrArray
+
+
+class FloatAttrArray(ExtensionArray):
+ dtype = FloatAttrDtype()
+ __array_priority__ = 1000
+
+ def __init__(self, values, attr=None) -> None:
+ if not isinstance(values, np.ndarray):
+ raise TypeError("Need to pass a numpy array of float64 dtype as values")
+ if not values.dtype == "float64":
+ raise TypeError("Need to pass a numpy array of float64 dtype as values")
+ self.data = values
+ self.attr = attr
+
+ @classmethod
+ def _from_sequence(cls, scalars, *, dtype=None, copy=False):
+ if not copy:
+ data = np.asarray(scalars, dtype="float64")
+ else:
+ data = np.array(scalars, dtype="float64", copy=copy)
+ return cls(data)
+
+ def __getitem__(self, item):
+ if isinstance(item, numbers.Integral):
+ return self.data[item]
+ else:
+ # slice, list-like, mask
+ item = pd.api.indexers.check_array_indexer(self, item)
+ return type(self)(self.data[item], self.attr)
+
+ def __len__(self) -> int:
+ return len(self.data)
+
+ def isna(self):
+ return np.isnan(self.data)
+
+ def take(self, indexer, allow_fill=False, fill_value=None):
+ from pandas.api.extensions import take
+
+ data = self.data
+ if allow_fill and fill_value is None:
+ fill_value = self.dtype.na_value
+
+ result = take(data, indexer, fill_value=fill_value, allow_fill=allow_fill)
+ return type(self)(result, self.attr)
+
+ def copy(self):
+ return type(self)(self.data.copy(), self.attr)
+
+ @classmethod
+ def _concat_same_type(cls, to_concat):
+ data = np.concatenate([x.data for x in to_concat])
+ attr = to_concat[0].attr if len(to_concat) else None
+ return cls(data, attr)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/array_with_attr/test_array_with_attr.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/array_with_attr/test_array_with_attr.py
new file mode 100644
index 0000000000000000000000000000000000000000..3735fe40a0d67784b3603a177b6694e56e26d479
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/array_with_attr/test_array_with_attr.py
@@ -0,0 +1,33 @@
+import numpy as np
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.tests.extension.array_with_attr import FloatAttrArray
+
+
+def test_concat_with_all_na():
+ # https://github.com/pandas-dev/pandas/pull/47762
+ # ensure that attribute of the column array is preserved (when it gets
+ # preserved in reindexing the array) during merge/concat
+ arr = FloatAttrArray(np.array([np.nan, np.nan], dtype="float64"), attr="test")
+
+ df1 = pd.DataFrame({"col": arr, "key": [0, 1]})
+ df2 = pd.DataFrame({"key": [0, 1], "col2": [1, 2]})
+ result = pd.merge(df1, df2, on="key")
+ expected = pd.DataFrame({"col": arr, "key": [0, 1], "col2": [1, 2]})
+ tm.assert_frame_equal(result, expected)
+ assert result["col"].array.attr == "test"
+
+ df1 = pd.DataFrame({"col": arr, "key": [0, 1]})
+ df2 = pd.DataFrame({"key": [0, 2], "col2": [1, 2]})
+ result = pd.merge(df1, df2, on="key")
+ expected = pd.DataFrame({"col": arr.take([0]), "key": [0], "col2": [1]})
+ tm.assert_frame_equal(result, expected)
+ assert result["col"].array.attr == "test"
+
+ result = pd.concat([df1.set_index("key"), df2.set_index("key")], axis=1)
+ expected = pd.DataFrame(
+ {"col": arr.take([0, 1, -1]), "col2": [1, np.nan, 2], "key": [0, 1, 2]}
+ ).set_index("key")
+ tm.assert_frame_equal(result, expected)
+ assert result["col"].array.attr == "test"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2abf739d2428d76dc2f9d68cdb61681e84d6a609
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/__init__.py
@@ -0,0 +1,89 @@
+"""
+Base test suite for extension arrays.
+
+These tests are intended for third-party libraries to subclass to validate
+that their extension arrays and dtypes satisfy the interface. Moving or
+renaming the tests should not be done lightly.
+
+Libraries are expected to implement a few pytest fixtures to provide data
+for the tests. The fixtures may be located in either
+
+* The same module as your test class.
+* A ``conftest.py`` in the same directory as your test class.
+
+The full list of fixtures may be found in the ``conftest.py`` next to this
+file.
+
+.. code-block:: python
+
+ import pytest
+ from pandas.tests.extension.base import BaseDtypeTests
+
+
+ @pytest.fixture
+ def dtype():
+ return MyDtype()
+
+
+ class TestMyDtype(BaseDtypeTests):
+ pass
+
+
+Your class ``TestDtype`` will inherit all the tests defined on
+``BaseDtypeTests``. pytest's fixture discover will supply your ``dtype``
+wherever the test requires it. You're free to implement additional tests.
+
+"""
+
+from pandas.tests.extension.base.accumulate import BaseAccumulateTests
+from pandas.tests.extension.base.casting import BaseCastingTests
+from pandas.tests.extension.base.constructors import BaseConstructorsTests
+from pandas.tests.extension.base.dim2 import ( # noqa: F401
+ Dim2CompatTests,
+ NDArrayBacked2DTests,
+)
+from pandas.tests.extension.base.dtype import BaseDtypeTests
+from pandas.tests.extension.base.getitem import BaseGetitemTests
+from pandas.tests.extension.base.groupby import BaseGroupbyTests
+from pandas.tests.extension.base.index import BaseIndexTests
+from pandas.tests.extension.base.interface import BaseInterfaceTests
+from pandas.tests.extension.base.io import BaseParsingTests
+from pandas.tests.extension.base.methods import BaseMethodsTests
+from pandas.tests.extension.base.missing import BaseMissingTests
+from pandas.tests.extension.base.ops import ( # noqa: F401
+ BaseArithmeticOpsTests,
+ BaseComparisonOpsTests,
+ BaseOpsUtil,
+ BaseUnaryOpsTests,
+)
+from pandas.tests.extension.base.printing import BasePrintingTests
+from pandas.tests.extension.base.reduce import BaseReduceTests
+from pandas.tests.extension.base.reshaping import BaseReshapingTests
+from pandas.tests.extension.base.setitem import BaseSetitemTests
+
+
+# One test class that you can inherit as an alternative to inheriting all the
+# test classes above.
+# Note this excludes Dim2CompatTests and NDArrayBacked2DTests.
+class ExtensionTests(
+ BaseAccumulateTests,
+ BaseCastingTests,
+ BaseConstructorsTests,
+ BaseDtypeTests,
+ BaseGetitemTests,
+ BaseGroupbyTests,
+ BaseIndexTests,
+ BaseInterfaceTests,
+ BaseParsingTests,
+ BaseMethodsTests,
+ BaseMissingTests,
+ BaseArithmeticOpsTests,
+ BaseComparisonOpsTests,
+ BaseUnaryOpsTests,
+ BasePrintingTests,
+ BaseReduceTests,
+ BaseReshapingTests,
+ BaseSetitemTests,
+ Dim2CompatTests,
+):
+ pass
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/accumulate.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/accumulate.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a2f186c2a00bf933cf3fdbcb7a07482930846e6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/accumulate.py
@@ -0,0 +1,40 @@
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class BaseAccumulateTests:
+ """
+ Accumulation specific tests. Generally these only
+ make sense for numeric/boolean operations.
+ """
+
+ def _supports_accumulation(self, ser: pd.Series, op_name: str) -> bool:
+ # Do we expect this accumulation to be supported for this dtype?
+ # We default to assuming "no"; subclass authors should override here.
+ return False
+
+ def check_accumulate(self, ser: pd.Series, op_name: str, skipna: bool):
+ try:
+ alt = ser.astype("float64")
+ except (TypeError, ValueError):
+ # e.g. Period can't be cast to float64 (TypeError)
+ # String can't be cast to float64 (ValueError)
+ alt = ser.astype(object)
+
+ result = getattr(ser, op_name)(skipna=skipna)
+ expected = getattr(alt, op_name)(skipna=skipna)
+ tm.assert_series_equal(result, expected, check_dtype=False)
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_accumulate_series(self, data, all_numeric_accumulations, skipna):
+ op_name = all_numeric_accumulations
+ ser = pd.Series(data)
+
+ if self._supports_accumulation(ser, op_name):
+ self.check_accumulate(ser, op_name, skipna)
+ else:
+ with pytest.raises((NotImplementedError, TypeError)):
+ # TODO: require TypeError for things that will _never_ work?
+ getattr(ser, op_name)(skipna=skipna)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/base.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..747ebee738c1ee5cf9bbcc858aedd55dea39b38c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/base.py
@@ -0,0 +1,2 @@
+class BaseExtensionTests:
+ pass
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/casting.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/casting.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e1d6109e21194b71b81def1bce4a2e91cbbb70c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/casting.py
@@ -0,0 +1,96 @@
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.internals.blocks import NumpyBlock
+
+
+class BaseCastingTests:
+ """Casting to and from ExtensionDtypes"""
+
+ def test_astype_object_series(self, all_data):
+ ser = pd.Series(all_data, name="A")
+ result = ser.astype(object)
+ assert result.dtype == np.dtype(object)
+ if hasattr(result._mgr, "blocks"):
+ blk = result._mgr.blocks[0]
+ assert isinstance(blk, NumpyBlock)
+ assert blk.is_object
+ assert isinstance(result._mgr.array, np.ndarray)
+ assert result._mgr.array.dtype == np.dtype(object)
+
+ def test_astype_object_frame(self, all_data):
+ df = pd.DataFrame({"A": all_data})
+
+ result = df.astype(object)
+ if hasattr(result._mgr, "blocks"):
+ blk = result._mgr.blocks[0]
+ assert isinstance(blk, NumpyBlock), type(blk)
+ assert blk.is_object
+ arr = result._mgr.blocks[0].values
+ assert isinstance(arr, np.ndarray)
+ assert arr.dtype == np.dtype(object)
+
+ # check that we can compare the dtypes
+ comp = result.dtypes == df.dtypes
+ assert not comp.any()
+
+ def test_tolist(self, data):
+ result = pd.Series(data).tolist()
+ expected = list(data)
+ assert result == expected
+
+ def test_astype_str(self, data):
+ result = pd.Series(data[:2]).astype(str)
+ expected = pd.Series([str(x) for x in data[:2]], dtype=str)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "nullable_string_dtype",
+ [
+ "string[python]",
+ pytest.param("string[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ ],
+ )
+ def test_astype_string(self, data, nullable_string_dtype):
+ # GH-33465, GH#45326 as of 2.0 we decode bytes instead of calling str(obj)
+ def as_str(x):
+ if isinstance(x, bytes):
+ return x.decode()
+ elif x is data.dtype.na_value:
+ return x
+ else:
+ return str(x)
+
+ result = pd.Series(data[:5]).astype(nullable_string_dtype)
+ expected = pd.Series(
+ [as_str(x) for x in data[:5]],
+ dtype=nullable_string_dtype,
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_to_numpy(self, data):
+ expected = np.asarray(data)
+
+ result = data.to_numpy()
+ tm.assert_equal(result, expected)
+
+ result = pd.Series(data).to_numpy()
+ tm.assert_equal(result, expected)
+
+ def test_astype_empty_dataframe(self, dtype):
+ # https://github.com/pandas-dev/pandas/issues/33113
+ df = pd.DataFrame()
+ result = df.astype(dtype)
+ tm.assert_frame_equal(result, df)
+
+ @pytest.mark.parametrize("copy", [True, False])
+ def test_astype_own_type(self, data, copy):
+ # ensure that astype returns the original object for equal dtype and copy=False
+ # https://github.com/pandas-dev/pandas/issues/28488
+ result = data.astype(data.dtype, copy=copy)
+ assert (result is data) is (not copy)
+ tm.assert_extension_array_equal(result, data)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..639dc874c9fb9ffa6e259c1e5e4dfbd18c2a7d58
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/constructors.py
@@ -0,0 +1,142 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.extensions import ExtensionArray
+from pandas.core.internals.blocks import EABackedBlock
+
+
+class BaseConstructorsTests:
+ def test_from_sequence_from_cls(self, data):
+ result = type(data)._from_sequence(data, dtype=data.dtype)
+ tm.assert_extension_array_equal(result, data)
+
+ data = data[:0]
+ result = type(data)._from_sequence(data, dtype=data.dtype)
+ tm.assert_extension_array_equal(result, data)
+
+ def test_array_from_scalars(self, data):
+ scalars = [data[0], data[1], data[2]]
+ result = data._from_sequence(scalars, dtype=data.dtype)
+ assert isinstance(result, type(data))
+
+ def test_series_constructor(self, data):
+ result = pd.Series(data, copy=False)
+ assert result.dtype == data.dtype
+ assert len(result) == len(data)
+ if hasattr(result._mgr, "blocks"):
+ assert isinstance(result._mgr.blocks[0], EABackedBlock)
+ assert result._mgr.array is data
+
+ # Series[EA] is unboxed / boxed correctly
+ result2 = pd.Series(result)
+ assert result2.dtype == data.dtype
+ if hasattr(result._mgr, "blocks"):
+ assert isinstance(result2._mgr.blocks[0], EABackedBlock)
+
+ def test_series_constructor_no_data_with_index(self, dtype, na_value):
+ result = pd.Series(index=[1, 2, 3], dtype=dtype)
+ expected = pd.Series([na_value] * 3, index=[1, 2, 3], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ # GH 33559 - empty index
+ result = pd.Series(index=[], dtype=dtype)
+ expected = pd.Series([], index=pd.Index([], dtype="object"), dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_series_constructor_scalar_na_with_index(self, dtype, na_value):
+ result = pd.Series(na_value, index=[1, 2, 3], dtype=dtype)
+ expected = pd.Series([na_value] * 3, index=[1, 2, 3], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_series_constructor_scalar_with_index(self, data, dtype):
+ scalar = data[0]
+ result = pd.Series(scalar, index=[1, 2, 3], dtype=dtype)
+ expected = pd.Series([scalar] * 3, index=[1, 2, 3], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ result = pd.Series(scalar, index=["foo"], dtype=dtype)
+ expected = pd.Series([scalar], index=["foo"], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("from_series", [True, False])
+ def test_dataframe_constructor_from_dict(self, data, from_series):
+ if from_series:
+ data = pd.Series(data)
+ result = pd.DataFrame({"A": data})
+ assert result.dtypes["A"] == data.dtype
+ assert result.shape == (len(data), 1)
+ if hasattr(result._mgr, "blocks"):
+ assert isinstance(result._mgr.blocks[0], EABackedBlock)
+ assert isinstance(result._mgr.blocks[0].values, ExtensionArray)
+
+ def test_dataframe_from_series(self, data):
+ result = pd.DataFrame(pd.Series(data))
+ assert result.dtypes[0] == data.dtype
+ assert result.shape == (len(data), 1)
+ if hasattr(result._mgr, "blocks"):
+ assert isinstance(result._mgr.blocks[0], EABackedBlock)
+ assert isinstance(result._mgr.blocks[0].values, ExtensionArray)
+
+ def test_series_given_mismatched_index_raises(self, data):
+ msg = r"Length of values \(3\) does not match length of index \(5\)"
+ with pytest.raises(ValueError, match=msg):
+ pd.Series(data[:3], index=[0, 1, 2, 3, 4])
+
+ def test_from_dtype(self, data):
+ # construct from our dtype & string dtype
+ dtype = data.dtype
+
+ expected = pd.Series(data)
+ result = pd.Series(list(data), dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ result = pd.Series(list(data), dtype=str(dtype))
+ tm.assert_series_equal(result, expected)
+
+ # gh-30280
+
+ expected = pd.DataFrame(data).astype(dtype)
+ result = pd.DataFrame(list(data), dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+ result = pd.DataFrame(list(data), dtype=str(dtype))
+ tm.assert_frame_equal(result, expected)
+
+ def test_pandas_array(self, data):
+ # pd.array(extension_array) should be idempotent...
+ result = pd.array(data)
+ tm.assert_extension_array_equal(result, data)
+
+ def test_pandas_array_dtype(self, data):
+ # ... but specifying dtype will override idempotency
+ result = pd.array(data, dtype=np.dtype(object))
+ expected = pd.arrays.NumpyExtensionArray(np.asarray(data, dtype=object))
+ tm.assert_equal(result, expected)
+
+ def test_construct_empty_dataframe(self, dtype):
+ # GH 33623
+ result = pd.DataFrame(columns=["a"], dtype=dtype)
+ expected = pd.DataFrame(
+ {"a": pd.array([], dtype=dtype)}, index=pd.RangeIndex(0)
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_empty(self, dtype):
+ cls = dtype.construct_array_type()
+ result = cls._empty((4,), dtype=dtype)
+ assert isinstance(result, cls)
+ assert result.dtype == dtype
+ assert result.shape == (4,)
+
+ # GH#19600 method on ExtensionDtype
+ result2 = dtype.empty((4,))
+ assert isinstance(result2, cls)
+ assert result2.dtype == dtype
+ assert result2.shape == (4,)
+
+ result2 = dtype.empty(4)
+ assert isinstance(result2, cls)
+ assert result2.dtype == dtype
+ assert result2.shape == (4,)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/dim2.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/dim2.py
new file mode 100644
index 0000000000000000000000000000000000000000..890766acbd610e3585e8b434589aa9fb42a915c4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/dim2.py
@@ -0,0 +1,356 @@
+"""
+Tests for 2D compatibility.
+"""
+
+import numpy as np
+import pytest
+
+from pandas._libs.missing import is_matching_na
+
+from pandas.core.dtypes.common import (
+ is_bool_dtype,
+ is_integer_dtype,
+)
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.arrays.integer import NUMPY_INT_TO_DTYPE
+
+
+class Dim2CompatTests:
+ # Note: these are ONLY for ExtensionArray subclasses that support 2D arrays.
+ # i.e. not for pyarrow-backed EAs.
+
+ @pytest.fixture(autouse=True)
+ def skip_if_doesnt_support_2d(self, dtype, request):
+ if not dtype._supports_2d:
+ node = request.node
+ # In cases where we are mixed in to ExtensionTests, we only want to
+ # skip tests that are defined in Dim2CompatTests
+ test_func = node._obj
+ if test_func.__qualname__.startswith("Dim2CompatTests"):
+ # TODO: is there a less hacky way of checking this?
+ pytest.skip(f"{dtype} does not support 2D.")
+
+ def test_shift_2d(self, data):
+ arr2d = data.repeat(2).reshape(-1, 2)
+
+ for n in [1, -2]:
+ for fill_value in [None, data[0]]:
+ result = arr2d.shift(n, fill_value=fill_value)
+ expected_col = data.shift(n, fill_value=fill_value)
+ tm.assert_extension_array_equal(result[:, 0], expected_col)
+ tm.assert_extension_array_equal(result[:, 1], expected_col)
+
+ def test_transpose(self, data):
+ arr2d = data.repeat(2).reshape(-1, 2)
+ shape = arr2d.shape
+ assert shape[0] != shape[-1] # otherwise the rest of the test is useless
+
+ assert arr2d.T.shape == shape[::-1]
+
+ def test_frame_from_2d_array(self, data):
+ arr2d = data.repeat(2).reshape(-1, 2)
+
+ df = pd.DataFrame(arr2d)
+ expected = pd.DataFrame({0: arr2d[:, 0], 1: arr2d[:, 1]})
+ tm.assert_frame_equal(df, expected)
+
+ def test_swapaxes(self, data):
+ arr2d = data.repeat(2).reshape(-1, 2)
+
+ result = arr2d.swapaxes(0, 1)
+ expected = arr2d.T
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_delete_2d(self, data):
+ arr2d = data.repeat(3).reshape(-1, 3)
+
+ # axis = 0
+ result = arr2d.delete(1, axis=0)
+ expected = data.delete(1).repeat(3).reshape(-1, 3)
+ tm.assert_extension_array_equal(result, expected)
+
+ # axis = 1
+ result = arr2d.delete(1, axis=1)
+ expected = data.repeat(2).reshape(-1, 2)
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_take_2d(self, data):
+ arr2d = data.reshape(-1, 1)
+
+ result = arr2d.take([0, 0, -1], axis=0)
+
+ expected = data.take([0, 0, -1]).reshape(-1, 1)
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_repr_2d(self, data):
+ # this could fail in a corner case where an element contained the name
+ res = repr(data.reshape(1, -1))
+ assert res.count(f"<{type(data).__name__}") == 1
+
+ res = repr(data.reshape(-1, 1))
+ assert res.count(f"<{type(data).__name__}") == 1
+
+ def test_reshape(self, data):
+ arr2d = data.reshape(-1, 1)
+ assert arr2d.shape == (data.size, 1)
+ assert len(arr2d) == len(data)
+
+ arr2d = data.reshape((-1, 1))
+ assert arr2d.shape == (data.size, 1)
+ assert len(arr2d) == len(data)
+
+ with tm.external_error_raised(ValueError):
+ data.reshape((data.size, 2))
+ with tm.external_error_raised(ValueError):
+ data.reshape(data.size, 2)
+
+ def test_getitem_2d(self, data):
+ arr2d = data.reshape(1, -1)
+
+ result = arr2d[0]
+ tm.assert_extension_array_equal(result, data)
+
+ with pytest.raises(IndexError):
+ arr2d[1]
+
+ with pytest.raises(IndexError):
+ arr2d[-2]
+
+ result = arr2d[:]
+ tm.assert_extension_array_equal(result, arr2d)
+
+ result = arr2d[:, :]
+ tm.assert_extension_array_equal(result, arr2d)
+
+ result = arr2d[:, 0]
+ expected = data[[0]]
+ tm.assert_extension_array_equal(result, expected)
+
+ # dimension-expanding getitem on 1D
+ result = data[:, np.newaxis]
+ tm.assert_extension_array_equal(result, arr2d.T)
+
+ def test_iter_2d(self, data):
+ arr2d = data.reshape(1, -1)
+
+ objs = list(iter(arr2d))
+ assert len(objs) == arr2d.shape[0]
+
+ for obj in objs:
+ assert isinstance(obj, type(data))
+ assert obj.dtype == data.dtype
+ assert obj.ndim == 1
+ assert len(obj) == arr2d.shape[1]
+
+ def test_tolist_2d(self, data):
+ arr2d = data.reshape(1, -1)
+
+ result = arr2d.tolist()
+ expected = [data.tolist()]
+
+ assert isinstance(result, list)
+ assert all(isinstance(x, list) for x in result)
+
+ assert result == expected
+
+ def test_concat_2d(self, data):
+ left = type(data)._concat_same_type([data, data]).reshape(-1, 2)
+ right = left.copy()
+
+ # axis=0
+ result = left._concat_same_type([left, right], axis=0)
+ expected = data._concat_same_type([data] * 4).reshape(-1, 2)
+ tm.assert_extension_array_equal(result, expected)
+
+ # axis=1
+ result = left._concat_same_type([left, right], axis=1)
+ assert result.shape == (len(data), 4)
+ tm.assert_extension_array_equal(result[:, :2], left)
+ tm.assert_extension_array_equal(result[:, 2:], right)
+
+ # axis > 1 -> invalid
+ msg = "axis 2 is out of bounds for array of dimension 2"
+ with pytest.raises(ValueError, match=msg):
+ left._concat_same_type([left, right], axis=2)
+
+ @pytest.mark.parametrize("method", ["backfill", "pad"])
+ def test_fillna_2d_method(self, data_missing, method):
+ # pad_or_backfill is always along axis=0
+ arr = data_missing.repeat(2).reshape(2, 2)
+ assert arr[0].isna().all()
+ assert not arr[1].isna().any()
+
+ result = arr._pad_or_backfill(method=method, limit=None)
+
+ expected = data_missing._pad_or_backfill(method=method).repeat(2).reshape(2, 2)
+ tm.assert_extension_array_equal(result, expected)
+
+ # Reverse so that backfill is not a no-op.
+ arr2 = arr[::-1]
+ assert not arr2[0].isna().any()
+ assert arr2[1].isna().all()
+
+ result2 = arr2._pad_or_backfill(method=method, limit=None)
+
+ expected2 = (
+ data_missing[::-1]._pad_or_backfill(method=method).repeat(2).reshape(2, 2)
+ )
+ tm.assert_extension_array_equal(result2, expected2)
+
+ @pytest.mark.parametrize("method", ["mean", "median", "var", "std", "sum", "prod"])
+ def test_reductions_2d_axis_none(self, data, method):
+ arr2d = data.reshape(1, -1)
+
+ err_expected = None
+ err_result = None
+ try:
+ expected = getattr(data, method)()
+ except Exception as err:
+ # if the 1D reduction is invalid, the 2D reduction should be as well
+ err_expected = err
+ try:
+ result = getattr(arr2d, method)(axis=None)
+ except Exception as err2:
+ err_result = err2
+
+ else:
+ result = getattr(arr2d, method)(axis=None)
+
+ if err_result is not None or err_expected is not None:
+ assert type(err_result) == type(err_expected)
+ return
+
+ assert is_matching_na(result, expected) or result == expected
+
+ @pytest.mark.parametrize("method", ["mean", "median", "var", "std", "sum", "prod"])
+ @pytest.mark.parametrize("min_count", [0, 1])
+ def test_reductions_2d_axis0(self, data, method, min_count):
+ if min_count == 1 and method not in ["sum", "prod"]:
+ pytest.skip(f"min_count not relevant for {method}")
+
+ arr2d = data.reshape(1, -1)
+
+ kwargs = {}
+ if method in ["std", "var"]:
+ # pass ddof=0 so we get all-zero std instead of all-NA std
+ kwargs["ddof"] = 0
+ elif method in ["prod", "sum"]:
+ kwargs["min_count"] = min_count
+
+ try:
+ result = getattr(arr2d, method)(axis=0, **kwargs)
+ except Exception as err:
+ try:
+ getattr(data, method)()
+ except Exception as err2:
+ assert type(err) == type(err2)
+ return
+ else:
+ raise AssertionError("Both reductions should raise or neither")
+
+ def get_reduction_result_dtype(dtype):
+ # windows and 32bit builds will in some cases have int32/uint32
+ # where other builds will have int64/uint64.
+ if dtype.itemsize == 8:
+ return dtype
+ elif dtype.kind in "ib":
+ return NUMPY_INT_TO_DTYPE[np.dtype(int)]
+ else:
+ # i.e. dtype.kind == "u"
+ return NUMPY_INT_TO_DTYPE[np.dtype("uint")]
+
+ if method in ["sum", "prod"]:
+ # std and var are not dtype-preserving
+ expected = data
+ if data.dtype.kind in "iub":
+ dtype = get_reduction_result_dtype(data.dtype)
+ expected = data.astype(dtype)
+ assert dtype == expected.dtype
+
+ if min_count == 0:
+ fill_value = 1 if method == "prod" else 0
+ expected = expected.fillna(fill_value)
+
+ tm.assert_extension_array_equal(result, expected)
+ elif method == "median":
+ # std and var are not dtype-preserving
+ expected = data
+ tm.assert_extension_array_equal(result, expected)
+ elif method in ["mean", "std", "var"]:
+ if is_integer_dtype(data) or is_bool_dtype(data):
+ data = data.astype("Float64")
+ if method == "mean":
+ tm.assert_extension_array_equal(result, data)
+ else:
+ tm.assert_extension_array_equal(result, data - data)
+
+ @pytest.mark.parametrize("method", ["mean", "median", "var", "std", "sum", "prod"])
+ def test_reductions_2d_axis1(self, data, method):
+ arr2d = data.reshape(1, -1)
+
+ try:
+ result = getattr(arr2d, method)(axis=1)
+ except Exception as err:
+ try:
+ getattr(data, method)()
+ except Exception as err2:
+ assert type(err) == type(err2)
+ return
+ else:
+ raise AssertionError("Both reductions should raise or neither")
+
+ # not necessarily type/dtype-preserving, so weaker assertions
+ assert result.shape == (1,)
+ expected_scalar = getattr(data, method)()
+ res = result[0]
+ assert is_matching_na(res, expected_scalar) or res == expected_scalar
+
+
+class NDArrayBacked2DTests(Dim2CompatTests):
+ # More specific tests for NDArrayBackedExtensionArray subclasses
+
+ def test_copy_order(self, data):
+ # We should be matching numpy semantics for the "order" keyword in 'copy'
+ arr2d = data.repeat(2).reshape(-1, 2)
+ assert arr2d._ndarray.flags["C_CONTIGUOUS"]
+
+ res = arr2d.copy()
+ assert res._ndarray.flags["C_CONTIGUOUS"]
+
+ res = arr2d[::2, ::2].copy()
+ assert res._ndarray.flags["C_CONTIGUOUS"]
+
+ res = arr2d.copy("F")
+ assert not res._ndarray.flags["C_CONTIGUOUS"]
+ assert res._ndarray.flags["F_CONTIGUOUS"]
+
+ res = arr2d.copy("K")
+ assert res._ndarray.flags["C_CONTIGUOUS"]
+
+ res = arr2d.T.copy("K")
+ assert not res._ndarray.flags["C_CONTIGUOUS"]
+ assert res._ndarray.flags["F_CONTIGUOUS"]
+
+ # order not accepted by numpy
+ msg = r"order must be one of 'C', 'F', 'A', or 'K' \(got 'Q'\)"
+ with pytest.raises(ValueError, match=msg):
+ arr2d.copy("Q")
+
+ # neither contiguity
+ arr_nc = arr2d[::2]
+ assert not arr_nc._ndarray.flags["C_CONTIGUOUS"]
+ assert not arr_nc._ndarray.flags["F_CONTIGUOUS"]
+
+ assert arr_nc.copy()._ndarray.flags["C_CONTIGUOUS"]
+ assert not arr_nc.copy()._ndarray.flags["F_CONTIGUOUS"]
+
+ assert arr_nc.copy("C")._ndarray.flags["C_CONTIGUOUS"]
+ assert not arr_nc.copy("C")._ndarray.flags["F_CONTIGUOUS"]
+
+ assert not arr_nc.copy("F")._ndarray.flags["C_CONTIGUOUS"]
+ assert arr_nc.copy("F")._ndarray.flags["F_CONTIGUOUS"]
+
+ assert arr_nc.copy("K")._ndarray.flags["C_CONTIGUOUS"]
+ assert not arr_nc.copy("K")._ndarray.flags["F_CONTIGUOUS"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/dtype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/dtype.py
new file mode 100644
index 0000000000000000000000000000000000000000..38478ed3c40ae0464f0260e789ebf7ae37095bc5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/dtype.py
@@ -0,0 +1,123 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.types import (
+ infer_dtype,
+ is_object_dtype,
+ is_string_dtype,
+)
+
+
+class BaseDtypeTests:
+ """Base class for ExtensionDtype classes"""
+
+ def test_name(self, dtype):
+ assert isinstance(dtype.name, str)
+
+ def test_kind(self, dtype):
+ valid = set("biufcmMOSUV")
+ assert dtype.kind in valid
+
+ def test_is_dtype_from_name(self, dtype):
+ result = type(dtype).is_dtype(dtype.name)
+ assert result is True
+
+ def test_is_dtype_unboxes_dtype(self, data, dtype):
+ assert dtype.is_dtype(data) is True
+
+ def test_is_dtype_from_self(self, dtype):
+ result = type(dtype).is_dtype(dtype)
+ assert result is True
+
+ def test_is_dtype_other_input(self, dtype):
+ assert dtype.is_dtype([1, 2, 3]) is False
+
+ def test_is_not_string_type(self, dtype):
+ assert not is_string_dtype(dtype)
+
+ def test_is_not_object_type(self, dtype):
+ assert not is_object_dtype(dtype)
+
+ def test_eq_with_str(self, dtype):
+ assert dtype == dtype.name
+ assert dtype != dtype.name + "-suffix"
+
+ def test_eq_with_numpy_object(self, dtype):
+ assert dtype != np.dtype("object")
+
+ def test_eq_with_self(self, dtype):
+ assert dtype == dtype
+ assert dtype != object()
+
+ def test_array_type(self, data, dtype):
+ assert dtype.construct_array_type() is type(data)
+
+ def test_check_dtype(self, data):
+ dtype = data.dtype
+
+ # check equivalency for using .dtypes
+ df = pd.DataFrame(
+ {
+ "A": pd.Series(data, dtype=dtype),
+ "B": data,
+ "C": pd.Series(["foo"] * len(data), dtype=object),
+ "D": 1,
+ }
+ )
+ result = df.dtypes == str(dtype)
+ assert np.dtype("int64") != "Int64"
+
+ expected = pd.Series([True, True, False, False], index=list("ABCD"))
+
+ tm.assert_series_equal(result, expected)
+
+ expected = pd.Series([True, True, False, False], index=list("ABCD"))
+ result = df.dtypes.apply(str) == str(dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_hashable(self, dtype):
+ hash(dtype) # no error
+
+ def test_str(self, dtype):
+ assert str(dtype) == dtype.name
+
+ def test_eq(self, dtype):
+ assert dtype == dtype.name
+ assert dtype != "another_type"
+
+ def test_construct_from_string_own_name(self, dtype):
+ result = dtype.construct_from_string(dtype.name)
+ assert type(result) is type(dtype)
+
+ # check OK as classmethod
+ result = type(dtype).construct_from_string(dtype.name)
+ assert type(result) is type(dtype)
+
+ def test_construct_from_string_another_type_raises(self, dtype):
+ msg = f"Cannot construct a '{type(dtype).__name__}' from 'another_type'"
+ with pytest.raises(TypeError, match=msg):
+ type(dtype).construct_from_string("another_type")
+
+ def test_construct_from_string_wrong_type_raises(self, dtype):
+ with pytest.raises(
+ TypeError,
+ match="'construct_from_string' expects a string, got ",
+ ):
+ type(dtype).construct_from_string(0)
+
+ def test_get_common_dtype(self, dtype):
+ # in practice we will not typically call this with a 1-length list
+ # (we shortcut to just use that dtype as the common dtype), but
+ # still testing as good practice to have this working (and it is the
+ # only case we can test in general)
+ assert dtype._get_common_dtype([dtype]) == dtype
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_infer_dtype(self, data, data_missing, skipna):
+ # only testing that this works without raising an error
+ res = infer_dtype(data, skipna=skipna)
+ assert isinstance(res, str)
+ res = infer_dtype(data_missing, skipna=skipna)
+ assert isinstance(res, str)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/getitem.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/getitem.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c4fe753e29e3a652d076013e31358a5566f745f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/getitem.py
@@ -0,0 +1,499 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class BaseGetitemTests:
+ """Tests for ExtensionArray.__getitem__."""
+
+ def test_iloc_series(self, data):
+ ser = pd.Series(data)
+ result = ser.iloc[:4]
+ expected = pd.Series(data[:4])
+ tm.assert_series_equal(result, expected)
+
+ result = ser.iloc[[0, 1, 2, 3]]
+ tm.assert_series_equal(result, expected)
+
+ def test_iloc_frame(self, data):
+ df = pd.DataFrame({"A": data, "B": np.arange(len(data), dtype="int64")})
+ expected = pd.DataFrame({"A": data[:4]})
+
+ # slice -> frame
+ result = df.iloc[:4, [0]]
+ tm.assert_frame_equal(result, expected)
+
+ # sequence -> frame
+ result = df.iloc[[0, 1, 2, 3], [0]]
+ tm.assert_frame_equal(result, expected)
+
+ expected = pd.Series(data[:4], name="A")
+
+ # slice -> series
+ result = df.iloc[:4, 0]
+ tm.assert_series_equal(result, expected)
+
+ # sequence -> series
+ result = df.iloc[:4, 0]
+ tm.assert_series_equal(result, expected)
+
+ # GH#32959 slice columns with step
+ result = df.iloc[:, ::2]
+ tm.assert_frame_equal(result, df[["A"]])
+ result = df[["B", "A"]].iloc[:, ::2]
+ tm.assert_frame_equal(result, df[["B"]])
+
+ def test_iloc_frame_single_block(self, data):
+ # GH#32959 null slice along index, slice along columns with single-block
+ df = pd.DataFrame({"A": data})
+
+ result = df.iloc[:, :]
+ tm.assert_frame_equal(result, df)
+
+ result = df.iloc[:, :1]
+ tm.assert_frame_equal(result, df)
+
+ result = df.iloc[:, :2]
+ tm.assert_frame_equal(result, df)
+
+ result = df.iloc[:, ::2]
+ tm.assert_frame_equal(result, df)
+
+ result = df.iloc[:, 1:2]
+ tm.assert_frame_equal(result, df.iloc[:, :0])
+
+ result = df.iloc[:, -1:]
+ tm.assert_frame_equal(result, df)
+
+ def test_loc_series(self, data):
+ ser = pd.Series(data)
+ result = ser.loc[:3]
+ expected = pd.Series(data[:4])
+ tm.assert_series_equal(result, expected)
+
+ result = ser.loc[[0, 1, 2, 3]]
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_frame(self, data):
+ df = pd.DataFrame({"A": data, "B": np.arange(len(data), dtype="int64")})
+ expected = pd.DataFrame({"A": data[:4]})
+
+ # slice -> frame
+ result = df.loc[:3, ["A"]]
+ tm.assert_frame_equal(result, expected)
+
+ # sequence -> frame
+ result = df.loc[[0, 1, 2, 3], ["A"]]
+ tm.assert_frame_equal(result, expected)
+
+ expected = pd.Series(data[:4], name="A")
+
+ # slice -> series
+ result = df.loc[:3, "A"]
+ tm.assert_series_equal(result, expected)
+
+ # sequence -> series
+ result = df.loc[:3, "A"]
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_iloc_frame_single_dtype(self, data):
+ # GH#27110 bug in ExtensionBlock.iget caused df.iloc[n] to incorrectly
+ # return a scalar
+ df = pd.DataFrame({"A": data})
+ expected = pd.Series([data[2]], index=["A"], name=2, dtype=data.dtype)
+
+ result = df.loc[2]
+ tm.assert_series_equal(result, expected)
+
+ expected = pd.Series(
+ [data[-1]], index=["A"], name=len(data) - 1, dtype=data.dtype
+ )
+ result = df.iloc[-1]
+ tm.assert_series_equal(result, expected)
+
+ def test_getitem_scalar(self, data):
+ result = data[0]
+ assert isinstance(result, data.dtype.type)
+
+ result = pd.Series(data)[0]
+ assert isinstance(result, data.dtype.type)
+
+ def test_getitem_invalid(self, data):
+ # TODO: box over scalar, [scalar], (scalar,)?
+
+ msg = (
+ r"only integers, slices \(`:`\), ellipsis \(`...`\), numpy.newaxis "
+ r"\(`None`\) and integer or boolean arrays are valid indices"
+ )
+ with pytest.raises(IndexError, match=msg):
+ data["foo"]
+ with pytest.raises(IndexError, match=msg):
+ data[2.5]
+
+ ub = len(data)
+ msg = "|".join(
+ [
+ "list index out of range", # json
+ "index out of bounds", # pyarrow
+ "Out of bounds access", # Sparse
+ f"loc must be an integer between -{ub} and {ub}", # Sparse
+ f"index {ub + 1} is out of bounds for axis 0 with size {ub}",
+ f"index -{ub + 1} is out of bounds for axis 0 with size {ub}",
+ ]
+ )
+ with pytest.raises(IndexError, match=msg):
+ data[ub + 1]
+ with pytest.raises(IndexError, match=msg):
+ data[-ub - 1]
+
+ def test_getitem_scalar_na(self, data_missing, na_cmp, na_value):
+ result = data_missing[0]
+ assert na_cmp(result, na_value)
+
+ def test_getitem_empty(self, data):
+ # Indexing with empty list
+ result = data[[]]
+ assert len(result) == 0
+ assert isinstance(result, type(data))
+
+ expected = data[np.array([], dtype="int64")]
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_getitem_mask(self, data):
+ # Empty mask, raw array
+ mask = np.zeros(len(data), dtype=bool)
+ result = data[mask]
+ assert len(result) == 0
+ assert isinstance(result, type(data))
+
+ # Empty mask, in series
+ mask = np.zeros(len(data), dtype=bool)
+ result = pd.Series(data)[mask]
+ assert len(result) == 0
+ assert result.dtype == data.dtype
+
+ # non-empty mask, raw array
+ mask[0] = True
+ result = data[mask]
+ assert len(result) == 1
+ assert isinstance(result, type(data))
+
+ # non-empty mask, in series
+ result = pd.Series(data)[mask]
+ assert len(result) == 1
+ assert result.dtype == data.dtype
+
+ def test_getitem_mask_raises(self, data):
+ mask = np.array([True, False])
+ msg = f"Boolean index has wrong length: 2 instead of {len(data)}"
+ with pytest.raises(IndexError, match=msg):
+ data[mask]
+
+ mask = pd.array(mask, dtype="boolean")
+ with pytest.raises(IndexError, match=msg):
+ data[mask]
+
+ def test_getitem_boolean_array_mask(self, data):
+ mask = pd.array(np.zeros(data.shape, dtype="bool"), dtype="boolean")
+ result = data[mask]
+ assert len(result) == 0
+ assert isinstance(result, type(data))
+
+ result = pd.Series(data)[mask]
+ assert len(result) == 0
+ assert result.dtype == data.dtype
+
+ mask[:5] = True
+ expected = data.take([0, 1, 2, 3, 4])
+ result = data[mask]
+ tm.assert_extension_array_equal(result, expected)
+
+ expected = pd.Series(expected)
+ result = pd.Series(data)[mask]
+ tm.assert_series_equal(result, expected)
+
+ def test_getitem_boolean_na_treated_as_false(self, data):
+ # https://github.com/pandas-dev/pandas/issues/31503
+ mask = pd.array(np.zeros(data.shape, dtype="bool"), dtype="boolean")
+ mask[:2] = pd.NA
+ mask[2:4] = True
+
+ result = data[mask]
+ expected = data[mask.fillna(False)]
+
+ tm.assert_extension_array_equal(result, expected)
+
+ s = pd.Series(data)
+
+ result = s[mask]
+ expected = s[mask.fillna(False)]
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [[0, 1, 2], pd.array([0, 1, 2], dtype="Int64"), np.array([0, 1, 2])],
+ ids=["list", "integer-array", "numpy-array"],
+ )
+ def test_getitem_integer_array(self, data, idx):
+ result = data[idx]
+ assert len(result) == 3
+ assert isinstance(result, type(data))
+ expected = data.take([0, 1, 2])
+ tm.assert_extension_array_equal(result, expected)
+
+ expected = pd.Series(expected)
+ result = pd.Series(data)[idx]
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [[0, 1, 2, pd.NA], pd.array([0, 1, 2, pd.NA], dtype="Int64")],
+ ids=["list", "integer-array"],
+ )
+ def test_getitem_integer_with_missing_raises(self, data, idx):
+ msg = "Cannot index with an integer indexer containing NA values"
+ with pytest.raises(ValueError, match=msg):
+ data[idx]
+
+ @pytest.mark.xfail(
+ reason="Tries label-based and raises KeyError; "
+ "in some cases raises when calling np.asarray"
+ )
+ @pytest.mark.parametrize(
+ "idx",
+ [[0, 1, 2, pd.NA], pd.array([0, 1, 2, pd.NA], dtype="Int64")],
+ ids=["list", "integer-array"],
+ )
+ def test_getitem_series_integer_with_missing_raises(self, data, idx):
+ msg = "Cannot index with an integer indexer containing NA values"
+ # TODO: this raises KeyError about labels not found (it tries label-based)
+
+ ser = pd.Series(data, index=[chr(100 + i) for i in range(len(data))])
+ with pytest.raises(ValueError, match=msg):
+ ser[idx]
+
+ def test_getitem_slice(self, data):
+ # getitem[slice] should return an array
+ result = data[slice(0)] # empty
+ assert isinstance(result, type(data))
+
+ result = data[slice(1)] # scalar
+ assert isinstance(result, type(data))
+
+ def test_getitem_ellipsis_and_slice(self, data):
+ # GH#40353 this is called from slice_block_rows
+ result = data[..., :]
+ tm.assert_extension_array_equal(result, data)
+
+ result = data[:, ...]
+ tm.assert_extension_array_equal(result, data)
+
+ result = data[..., :3]
+ tm.assert_extension_array_equal(result, data[:3])
+
+ result = data[:3, ...]
+ tm.assert_extension_array_equal(result, data[:3])
+
+ result = data[..., ::2]
+ tm.assert_extension_array_equal(result, data[::2])
+
+ result = data[::2, ...]
+ tm.assert_extension_array_equal(result, data[::2])
+
+ def test_get(self, data):
+ # GH 20882
+ s = pd.Series(data, index=[2 * i for i in range(len(data))])
+ assert s.get(4) == s.iloc[2]
+
+ result = s.get([4, 6])
+ expected = s.iloc[[2, 3]]
+ tm.assert_series_equal(result, expected)
+
+ result = s.get(slice(2))
+ expected = s.iloc[[0, 1]]
+ tm.assert_series_equal(result, expected)
+
+ assert s.get(-1) is None
+ assert s.get(s.index.max() + 1) is None
+
+ s = pd.Series(data[:6], index=list("abcdef"))
+ assert s.get("c") == s.iloc[2]
+
+ result = s.get(slice("b", "d"))
+ expected = s.iloc[[1, 2, 3]]
+ tm.assert_series_equal(result, expected)
+
+ result = s.get("Z")
+ assert result is None
+
+ # As of 3.0, getitem with int keys treats them as labels
+ assert s.get(4) is None
+ assert s.get(-1) is None
+ assert s.get(len(s)) is None
+
+ # GH 21257
+ s = pd.Series(data)
+ with tm.assert_produces_warning(None):
+ # GH#45324 make sure we aren't giving a spurious FutureWarning
+ s2 = s[::2]
+ assert s2.get(1) is None
+
+ def test_take_sequence(self, data):
+ result = pd.Series(data)[[0, 1, 3]]
+ assert result.iloc[0] == data[0]
+ assert result.iloc[1] == data[1]
+ assert result.iloc[2] == data[3]
+
+ def test_take(self, data, na_value, na_cmp):
+ result = data.take([0, -1])
+ assert result.dtype == data.dtype
+ assert result[0] == data[0]
+ assert result[1] == data[-1]
+
+ result = data.take([0, -1], allow_fill=True, fill_value=na_value)
+ assert result[0] == data[0]
+ assert na_cmp(result[1], na_value)
+
+ with pytest.raises(IndexError, match="out of bounds"):
+ data.take([len(data) + 1])
+
+ def test_take_empty(self, data, na_value, na_cmp):
+ empty = data[:0]
+
+ result = empty.take([-1], allow_fill=True)
+ assert na_cmp(result[0], na_value)
+
+ msg = "cannot do a non-empty take from an empty axes|out of bounds"
+
+ with pytest.raises(IndexError, match=msg):
+ empty.take([-1])
+
+ with pytest.raises(IndexError, match="cannot do a non-empty take"):
+ empty.take([0, 1])
+
+ def test_take_negative(self, data):
+ # https://github.com/pandas-dev/pandas/issues/20640
+ n = len(data)
+ result = data.take([0, -n, n - 1, -1])
+ expected = data.take([0, 0, n - 1, n - 1])
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_take_non_na_fill_value(self, data_missing):
+ fill_value = data_missing[1] # valid
+ na = data_missing[0]
+
+ arr = data_missing._from_sequence(
+ [na, fill_value, na], dtype=data_missing.dtype
+ )
+ result = arr.take([-1, 1], fill_value=fill_value, allow_fill=True)
+ expected = arr.take([1, 1])
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_take_pandas_style_negative_raises(self, data, na_value):
+ with tm.external_error_raised(ValueError):
+ data.take([0, -2], fill_value=na_value, allow_fill=True)
+
+ @pytest.mark.parametrize("allow_fill", [True, False])
+ def test_take_out_of_bounds_raises(self, data, allow_fill):
+ arr = data[:3]
+
+ with pytest.raises(IndexError, match="out of bounds|out-of-bounds"):
+ arr.take(np.asarray([0, 3]), allow_fill=allow_fill)
+
+ def test_take_series(self, data):
+ s = pd.Series(data)
+ result = s.take([0, -1])
+ expected = pd.Series(
+ data._from_sequence([data[0], data[len(data) - 1]], dtype=s.dtype),
+ index=s.index.take([0, -1]),
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_reindex(self, data, na_value):
+ s = pd.Series(data)
+ result = s.reindex([0, 1, 3])
+ expected = pd.Series(data.take([0, 1, 3]), index=[0, 1, 3])
+ tm.assert_series_equal(result, expected)
+
+ n = len(data)
+ result = s.reindex([-1, 0, n])
+ expected = pd.Series(
+ data._from_sequence([na_value, data[0], na_value], dtype=s.dtype),
+ index=[-1, 0, n],
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = s.reindex([n, n + 1])
+ expected = pd.Series(
+ data._from_sequence([na_value, na_value], dtype=s.dtype),
+ index=range(n, n + 2, 1),
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_reindex_non_na_fill_value(self, data_missing):
+ valid = data_missing[1]
+ na = data_missing[0]
+
+ arr = data_missing._from_sequence([na, valid], dtype=data_missing.dtype)
+ ser = pd.Series(arr)
+ result = ser.reindex([0, 1, 2], fill_value=valid)
+ expected = pd.Series(
+ data_missing._from_sequence([na, valid, valid], dtype=data_missing.dtype)
+ )
+
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_len1(self, data):
+ # see GH-27785 take_nd with indexer of len 1 resulting in wrong ndim
+ df = pd.DataFrame({"A": data})
+ res = df.loc[[0], "A"]
+ assert res.ndim == 1
+ assert res._mgr.blocks[0].ndim == 1
+ if hasattr(res._mgr, "blocks"):
+ assert res._mgr._block.ndim == 1
+
+ def test_item(self, data):
+ # https://github.com/pandas-dev/pandas/pull/30175
+ s = pd.Series(data)
+ result = s[:1].item()
+ assert result == data[0]
+
+ msg = "can only convert an array of size 1 to a Python scalar"
+ with pytest.raises(ValueError, match=msg):
+ s[:0].item()
+
+ with pytest.raises(ValueError, match=msg):
+ s.item()
+
+ def test_array_item(self, data):
+ # GH#63876, GH#64129
+ arr = data[:1]
+ assert arr.item() == data[0]
+
+ msg = "can only convert an array of size 1 to a Python scalar"
+ with pytest.raises(ValueError, match=msg):
+ data[:2].item()
+ with pytest.raises(ValueError, match=msg):
+ data[:0].item()
+
+ def test_array_item_with_index(self, data):
+ # GH#63876, GH#64129
+ assert data.item(0) == data[0]
+ assert data.item(-1) == data[-1]
+
+ with tm.external_error_raised(IndexError):
+ data.item(len(data))
+
+ msg = "index must be an integer"
+ with pytest.raises(TypeError, match=msg):
+ data.item([0])
+
+ def test_getitem_propagates_readonly_property(self, data):
+ # ensure read-only propagates if getitem returns view
+ data._readonly = True
+
+ result = data[:]
+ assert result._readonly
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/groupby.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/groupby.py
new file mode 100644
index 0000000000000000000000000000000000000000..60cade97ab528245af077749c5d7e8bd61fc09be
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/groupby.py
@@ -0,0 +1,172 @@
+import re
+
+import pytest
+
+from pandas.core.dtypes.common import (
+ is_bool_dtype,
+ is_numeric_dtype,
+ is_object_dtype,
+ is_string_dtype,
+)
+
+import pandas as pd
+import pandas._testing as tm
+
+
+@pytest.mark.filterwarnings(
+ "ignore:The default of observed=False is deprecated:FutureWarning"
+)
+class BaseGroupbyTests:
+ """Groupby-specific tests."""
+
+ def test_grouping_grouper(self, data_for_grouping):
+ df = pd.DataFrame(
+ {
+ "A": pd.Series(
+ ["B", "B", None, None, "A", "A", "B", "C"], dtype=object
+ ),
+ "B": data_for_grouping,
+ }
+ )
+ gr1 = df.groupby("A")._grouper.groupings[0]
+ gr2 = df.groupby("B")._grouper.groupings[0]
+
+ tm.assert_numpy_array_equal(gr1.grouping_vector, df.A.values)
+ tm.assert_extension_array_equal(gr2.grouping_vector, data_for_grouping)
+
+ @pytest.mark.parametrize("as_index", [True, False])
+ def test_groupby_extension_agg(self, as_index, data_for_grouping):
+ df = pd.DataFrame({"A": [1, 1, 2, 2, 3, 3, 1, 4], "B": data_for_grouping})
+
+ is_bool = data_for_grouping.dtype._is_boolean
+ if is_bool:
+ # only 2 unique values, and the final entry has c==b
+ # (see data_for_grouping docstring)
+ df = df.iloc[:-1]
+
+ result = df.groupby("B", as_index=as_index).A.mean()
+ _, uniques = pd.factorize(data_for_grouping, sort=True)
+
+ exp_vals = [3.0, 1.0, 4.0]
+ if is_bool:
+ exp_vals = exp_vals[:-1]
+ if as_index:
+ index = pd.Index(uniques, name="B")
+ expected = pd.Series(exp_vals, index=index, name="A")
+ tm.assert_series_equal(result, expected)
+ else:
+ expected = pd.DataFrame({"B": uniques, "A": exp_vals})
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_agg_extension(self, data_for_grouping):
+ # GH#38980 groupby agg on extension type fails for non-numeric types
+ df = pd.DataFrame({"A": [1, 1, 2, 2, 3, 3, 1, 4], "B": data_for_grouping})
+
+ expected = df.iloc[[0, 2, 4, 7]]
+ expected = expected.set_index("A")
+
+ result = df.groupby("A").agg({"B": "first"})
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("A").agg("first")
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("A").first()
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_extension_no_sort(self, data_for_grouping):
+ df = pd.DataFrame({"A": [1, 1, 2, 2, 3, 3, 1, 4], "B": data_for_grouping})
+
+ is_bool = data_for_grouping.dtype._is_boolean
+ if is_bool:
+ # only 2 unique values, and the final entry has c==b
+ # (see data_for_grouping docstring)
+ df = df.iloc[:-1]
+
+ result = df.groupby("B", sort=False).A.mean()
+ _, index = pd.factorize(data_for_grouping, sort=False)
+
+ index = pd.Index(index, name="B")
+ exp_vals = [1.0, 3.0, 4.0]
+ if is_bool:
+ exp_vals = exp_vals[:-1]
+ expected = pd.Series(exp_vals, index=index, name="A")
+ tm.assert_series_equal(result, expected)
+
+ def test_groupby_extension_transform(self, data_for_grouping):
+ is_bool = data_for_grouping.dtype._is_boolean
+
+ valid = data_for_grouping[~data_for_grouping.isna()]
+ df = pd.DataFrame({"A": [1, 1, 3, 3, 1, 4], "B": valid})
+ is_bool = data_for_grouping.dtype._is_boolean
+ if is_bool:
+ # only 2 unique values, and the final entry has c==b
+ # (see data_for_grouping docstring)
+ df = df.iloc[:-1]
+
+ result = df.groupby("B").A.transform(len)
+ expected = pd.Series([3, 3, 2, 2, 3, 1], name="A")
+ if is_bool:
+ expected = expected[:-1]
+
+ tm.assert_series_equal(result, expected)
+
+ def test_groupby_extension_apply(self, data_for_grouping, groupby_apply_op):
+ df = pd.DataFrame({"A": [1, 1, 2, 2, 3, 3, 1, 4], "B": data_for_grouping})
+ df.groupby("B", group_keys=False, observed=False).apply(groupby_apply_op)
+ df.groupby("B", group_keys=False, observed=False).A.apply(groupby_apply_op)
+ df.groupby("A", group_keys=False, observed=False).apply(groupby_apply_op)
+ df.groupby("A", group_keys=False, observed=False).B.apply(groupby_apply_op)
+
+ def test_groupby_apply_identity(self, data_for_grouping):
+ df = pd.DataFrame({"A": [1, 1, 2, 2, 3, 3, 1, 4], "B": data_for_grouping})
+ result = df.groupby("A").B.apply(lambda x: x.array)
+ expected = pd.Series(
+ [
+ df.B.iloc[[0, 1, 6]].array,
+ df.B.iloc[[2, 3]].array,
+ df.B.iloc[[4, 5]].array,
+ df.B.iloc[[7]].array,
+ ],
+ index=pd.Index([1, 2, 3, 4], name="A"),
+ name="B",
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_in_numeric_groupby(self, data_for_grouping):
+ df = pd.DataFrame(
+ {
+ "A": [1, 1, 2, 2, 3, 3, 1, 4],
+ "B": data_for_grouping,
+ "C": [1, 1, 1, 1, 1, 1, 1, 1],
+ }
+ )
+
+ dtype = data_for_grouping.dtype
+ if (
+ is_numeric_dtype(dtype)
+ or is_bool_dtype(dtype)
+ or dtype.name == "decimal"
+ or is_string_dtype(dtype)
+ or is_object_dtype(dtype)
+ or dtype.kind == "m" # in particular duration[*][pyarrow]
+ ):
+ expected = pd.Index(["B", "C"])
+ result = df.groupby("A").sum().columns
+ else:
+ expected = pd.Index(["C"])
+
+ msg = "|".join(
+ [
+ # period
+ "does not support sum operations",
+ # datetime
+ "does not support operation 'sum'",
+ # all others
+ re.escape(f"agg function failed [how->sum,dtype->{dtype}"),
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ df.groupby("A").sum()
+ result = df.groupby("A").sum(numeric_only=True).columns
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/index.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/index.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7bfebec92287c65ae648699a2c3c36ec71e044a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/index.py
@@ -0,0 +1,20 @@
+"""
+Tests for Indexes backed by arbitrary ExtensionArrays.
+"""
+
+import pandas as pd
+
+
+class BaseIndexTests:
+ """Tests for Index object backed by an ExtensionArray"""
+
+ def test_index_from_array(self, data):
+ idx = pd.Index(data)
+ assert data.dtype == idx.dtype
+
+ def test_index_from_listlike_with_dtype(self, data):
+ idx = pd.Index(data, dtype=data.dtype)
+ assert idx.dtype == data.dtype
+
+ idx = pd.Index(list(data), dtype=data.dtype)
+ assert idx.dtype == data.dtype
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/interface.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/interface.py
new file mode 100644
index 0000000000000000000000000000000000000000..d01fe4183eeea109e921dfc9fb5fc744b89637df
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/interface.py
@@ -0,0 +1,172 @@
+import numpy as np
+import pytest
+
+from pandas.compat.numpy import np_version_gt2
+
+from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
+from pandas.core.dtypes.common import is_extension_array_dtype
+from pandas.core.dtypes.dtypes import ExtensionDtype
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class BaseInterfaceTests:
+ """Tests that the basic interface is satisfied."""
+
+ # ------------------------------------------------------------------------
+ # Interface
+ # ------------------------------------------------------------------------
+
+ def test_len(self, data):
+ assert len(data) == 10
+
+ def test_size(self, data):
+ assert data.size == 10
+
+ def test_ndim(self, data):
+ assert data.ndim == 1
+
+ def test_can_hold_na_valid(self, data):
+ # GH-20761
+ assert data._can_hold_na is True
+
+ def test_contains(self, data, data_missing, using_nan_is_na):
+ # GH-37867
+ # Tests for membership checks. Membership checks for nan-likes is tricky and
+ # the settled on rule is: `nan_like in arr` is True if nan_like is
+ # arr.dtype.na_value and arr.isna().any() is True. Else the check returns False.
+
+ na_value = data.dtype.na_value
+ # ensure data without missing values
+ data = data[~data.isna()]
+
+ # first elements are non-missing
+ assert data[0] in data
+ assert data_missing[0] in data_missing
+
+ # check the presence of na_value
+ assert na_value in data_missing
+ assert na_value not in data
+
+ # the data can never contain other nan-likes than na_value
+ for na_value_obj in tm.NULL_OBJECTS:
+ if na_value_obj is na_value or type(na_value_obj) == type(na_value):
+ # type check for e.g. two instances of Decimal("NAN")
+ continue
+ assert na_value_obj not in data
+ if (
+ using_nan_is_na
+ and isinstance(na_value_obj, float)
+ and isinstance(
+ data,
+ (
+ pd.core.arrays.BaseMaskedArray,
+ pd.core.arrays.ArrowExtensionArray,
+ ),
+ )
+ ):
+ # TODO: wrong place for this override
+ assert na_value_obj in data_missing
+ else:
+ assert na_value_obj not in data_missing
+
+ def test_memory_usage(self, data):
+ s = pd.Series(data)
+ result = s.memory_usage(index=False)
+ assert result == s.nbytes
+
+ def test_array_interface(self, data):
+ result = np.array(data)
+ assert result[0] == data[0]
+
+ result = np.array(data, dtype=object)
+ expected = np.array(list(data), dtype=object)
+ if expected.ndim > 1:
+ # nested data, explicitly construct as 1D
+ expected = construct_1d_object_array_from_listlike(list(data))
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_array_interface_copy(self, data):
+ result_copy1 = np.array(data, copy=True)
+ result_copy2 = np.array(data, copy=True)
+ assert not np.may_share_memory(result_copy1, result_copy2)
+
+ if not np_version_gt2:
+ # copy=False semantics are only supported in NumPy>=2.
+ return
+
+ try:
+ result_nocopy1 = np.array(data, copy=False)
+ except ValueError:
+ # An error is always acceptable for `copy=False`
+ return
+
+ result_nocopy2 = np.array(data, copy=False)
+ # If copy=False was given and did not raise, these must share the same data
+ assert np.may_share_memory(result_nocopy1, result_nocopy2)
+
+ def test_is_extension_array_dtype(self, data):
+ assert is_extension_array_dtype(data)
+ assert is_extension_array_dtype(data.dtype)
+ assert is_extension_array_dtype(pd.Series(data))
+ assert isinstance(data.dtype, ExtensionDtype)
+
+ def test_no_values_attribute(self, data):
+ # GH-20735: EA's with .values attribute give problems with internal
+ # code, disallowing this for now until solved
+ assert not hasattr(data, "values")
+ assert not hasattr(data, "_values")
+
+ def test_is_numeric_honored(self, data):
+ result = pd.Series(data)
+ if hasattr(result._mgr, "blocks"):
+ assert result._mgr.blocks[0].is_numeric is data.dtype._is_numeric
+
+ def test_isna_extension_array(self, data_missing):
+ # If your `isna` returns an ExtensionArray, you must also implement
+ # _reduce. At the *very* least, you must implement any and all
+ na = data_missing.isna()
+ if is_extension_array_dtype(na):
+ assert na._reduce("any")
+ assert na.any()
+
+ assert not na._reduce("all")
+ assert not na.all()
+
+ assert na.dtype._is_boolean
+
+ def test_copy(self, data):
+ # GH#27083 removing deep keyword from EA.copy
+ assert data[0] != data[1]
+ result = data.copy()
+
+ if data.dtype._is_immutable:
+ pytest.skip(f"test_copy assumes mutability and {data.dtype} is immutable")
+
+ data[1] = data[0]
+ assert result[1] != result[0]
+
+ def test_view(self, data):
+ # view with no dtype should return a shallow copy, *not* the same
+ # object
+ assert data[1] != data[0]
+
+ result = data.view()
+ assert result is not data
+ assert type(result) == type(data)
+
+ if data.dtype._is_immutable:
+ pytest.skip(f"test_view assumes mutability and {data.dtype} is immutable")
+
+ result[1] = result[0]
+ assert data[1] == data[0]
+
+ # check specifically that the `dtype` kwarg is accepted
+ data.view(dtype=None)
+
+ def test_tolist(self, data):
+ result = data.tolist()
+ expected = list(data)
+ assert isinstance(result, list)
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/io.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/io.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7367fceeb52f5a060935b6ea789bc013d28477e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/io.py
@@ -0,0 +1,39 @@
+from io import StringIO
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.arrays import ExtensionArray
+
+
+class BaseParsingTests:
+ @pytest.mark.parametrize("engine", ["c", "python"])
+ def test_EA_types(self, engine, data, request):
+ if isinstance(data.dtype, pd.CategoricalDtype):
+ # in parsers.pyx _convert_with_dtype there is special-casing for
+ # Categorical that preempts _from_sequence_of_strings
+ pass
+ elif isinstance(data.dtype, pd.core.dtypes.dtypes.NumpyEADtype):
+ # These get unwrapped internally so are treated as numpy dtypes
+ # in the parsers.pyx code
+ pass
+ elif (
+ type(data)._from_sequence_of_strings.__func__
+ is ExtensionArray._from_sequence_of_strings.__func__
+ ):
+ # i.e. the EA hasn't overridden _from_sequence_of_strings
+ mark = pytest.mark.xfail(
+ reason="_from_sequence_of_strings not implemented",
+ raises=NotImplementedError,
+ )
+ request.node.add_marker(mark)
+
+ df = pd.DataFrame({"with_dtype": pd.Series(data, dtype=str(data.dtype))})
+ csv_output = df.to_csv(index=False, na_rep=np.nan)
+ result = pd.read_csv(
+ StringIO(csv_output), dtype={"with_dtype": str(data.dtype)}, engine=engine
+ )
+ expected = df
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/methods.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/methods.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2bf621a046696ccfe4ffdfa94ed6ce56d5c3f77
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/methods.py
@@ -0,0 +1,738 @@
+import inspect
+import operator
+
+import numpy as np
+import pytest
+
+from pandas._typing import Dtype
+
+from pandas.core.dtypes.common import is_bool_dtype
+from pandas.core.dtypes.dtypes import NumpyEADtype
+from pandas.core.dtypes.missing import na_value_for_dtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.sorting import nargsort
+
+
+class BaseMethodsTests:
+ """Various Series and DataFrame methods."""
+
+ def test_hash_pandas_object(self, data):
+ # _hash_pandas_object should return a uint64 ndarray of the same length
+ # as the data
+ from pandas.core.util.hashing import _default_hash_key
+
+ res = data._hash_pandas_object(
+ encoding="utf-8", hash_key=_default_hash_key, categorize=False
+ )
+ assert res.dtype == np.uint64
+ assert res.shape == data.shape
+
+ def test_value_counts_default_dropna(self, data):
+ # make sure we have consistent default dropna kwarg
+ if not hasattr(data, "value_counts"):
+ pytest.skip(f"value_counts is not implemented for {type(data)}")
+ sig = inspect.signature(data.value_counts)
+ kwarg = sig.parameters["dropna"]
+ assert kwarg.default is True
+
+ @pytest.mark.parametrize("dropna", [True, False])
+ def test_value_counts(self, all_data, dropna):
+ if dropna:
+ other = all_data[~all_data.isna()]
+ else:
+ other = all_data
+
+ result = pd.Series(all_data).value_counts(dropna=dropna).sort_index()
+ expected = pd.Series(other).value_counts(dropna=dropna).sort_index()
+
+ tm.assert_series_equal(result, expected)
+
+ def test_value_counts_with_normalize(self, data):
+ # GH 33172
+ data = data.unique()
+ values = np.array(data[~data.isna()])
+ ser = pd.Series(data, dtype=data.dtype)
+
+ result = ser.value_counts(normalize=True).sort_index()
+
+ if not isinstance(data, pd.Categorical):
+ expected = pd.Series(
+ [1 / len(values)] * len(values), index=result.index, name="proportion"
+ )
+ else:
+ expected = pd.Series(0.0, index=result.index, name="proportion")
+ expected[result > 0] = 1 / len(values)
+
+ if isinstance(data.dtype, pd.StringDtype) and data.dtype.na_value is np.nan:
+ # TODO: avoid special-casing
+ expected = expected.astype("float64")
+ elif getattr(data.dtype, "storage", "") == "pyarrow" or isinstance(
+ data.dtype, pd.ArrowDtype
+ ):
+ # TODO: avoid special-casing
+ expected = expected.astype("double[pyarrow]")
+ elif na_value_for_dtype(data.dtype) is pd.NA:
+ # TODO(GH#44692): avoid special-casing
+ expected = expected.astype("Float64")
+
+ tm.assert_series_equal(result, expected)
+
+ def test_count(self, data_missing):
+ df = pd.DataFrame({"A": data_missing})
+ result = df.count(axis="columns")
+ expected = pd.Series([0, 1])
+ tm.assert_series_equal(result, expected)
+
+ def test_series_count(self, data_missing):
+ # GH#26835
+ ser = pd.Series(data_missing)
+ result = ser.count()
+ expected = 1
+ assert result == expected
+
+ def test_apply_simple_series(self, data):
+ result = pd.Series(data).apply(id)
+ assert isinstance(result, pd.Series)
+
+ @pytest.mark.parametrize("na_action", [None, "ignore"])
+ def test_map(self, data_missing, na_action):
+ result = data_missing.map(lambda x: x, na_action=na_action)
+ expected = data_missing.to_numpy()
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_argsort(self, data_for_sorting):
+ result = pd.Series(data_for_sorting).argsort()
+ # argsort result gets passed to take, so should be np.intp
+ expected = pd.Series(np.array([2, 0, 1], dtype=np.intp))
+ tm.assert_series_equal(result, expected)
+
+ def test_argsort_missing_array(self, data_missing_for_sorting):
+ result = data_missing_for_sorting.argsort()
+ # argsort result gets passed to take, so should be np.intp
+ expected = np.array([2, 0, 1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_argsort_missing(self, data_missing_for_sorting):
+ result = pd.Series(data_missing_for_sorting).argsort()
+ expected = pd.Series(np.array([2, 0, 1], dtype=np.intp))
+ tm.assert_series_equal(result, expected)
+
+ def test_argmin_argmax(self, data_for_sorting, data_missing_for_sorting, na_value):
+ # GH 24382
+ is_bool = data_for_sorting.dtype._is_boolean
+
+ exp_argmax = 1
+ exp_argmax_repeated = 3
+ if is_bool:
+ # See data_for_sorting docstring
+ exp_argmax = 0
+ exp_argmax_repeated = 1
+
+ # data_for_sorting -> [B, C, A] with A < B < C
+ assert data_for_sorting.argmax() == exp_argmax
+ assert data_for_sorting.argmin() == 2
+
+ # with repeated values -> first occurrence
+ data = data_for_sorting.take([2, 0, 0, 1, 1, 2])
+ assert data.argmax() == exp_argmax_repeated
+ assert data.argmin() == 0
+
+ # with missing values
+ # data_missing_for_sorting -> [B, NA, A] with A < B and NA missing.
+ assert data_missing_for_sorting.argmax() == 0
+ assert data_missing_for_sorting.argmin() == 2
+
+ @pytest.mark.parametrize("method", ["argmax", "argmin"])
+ def test_argmin_argmax_empty_array(self, method, data):
+ # GH 24382
+ err_msg = "attempt to get"
+ with pytest.raises(ValueError, match=err_msg):
+ getattr(data[:0], method)()
+
+ @pytest.mark.parametrize("method", ["argmax", "argmin"])
+ def test_argmin_argmax_all_na(self, method, data, na_value):
+ # all missing with skipna=True is the same as empty
+ err_msg = "attempt to get"
+ data_na = type(data)._from_sequence([na_value, na_value], dtype=data.dtype)
+ with pytest.raises(ValueError, match=err_msg):
+ getattr(data_na, method)()
+
+ @pytest.mark.parametrize(
+ "op_name, skipna, expected",
+ [
+ ("idxmax", True, 0),
+ ("idxmin", True, 2),
+ ("argmax", True, 0),
+ ("argmin", True, 2),
+ ("idxmax", False, -1),
+ ("idxmin", False, -1),
+ ("argmax", False, -1),
+ ("argmin", False, -1),
+ ],
+ )
+ def test_argreduce_series(
+ self, data_missing_for_sorting, op_name, skipna, expected
+ ):
+ # data_missing_for_sorting -> [B, NA, A] with A < B and NA missing.
+ ser = pd.Series(data_missing_for_sorting)
+ if expected == -1:
+ with pytest.raises(ValueError, match="Encountered an NA value"):
+ getattr(ser, op_name)(skipna=skipna)
+ else:
+ result = getattr(ser, op_name)(skipna=skipna)
+ tm.assert_almost_equal(result, expected)
+
+ def test_argmax_argmin_no_skipna_notimplemented(self, data_missing_for_sorting):
+ # GH#38733
+ data = data_missing_for_sorting
+
+ with pytest.raises(ValueError, match="Encountered an NA value"):
+ data.argmin(skipna=False)
+
+ with pytest.raises(ValueError, match="Encountered an NA value"):
+ data.argmax(skipna=False)
+
+ @pytest.mark.parametrize(
+ "na_position, expected",
+ [
+ ("last", np.array([2, 0, 1], dtype=np.dtype("intp"))),
+ ("first", np.array([1, 2, 0], dtype=np.dtype("intp"))),
+ ],
+ )
+ def test_nargsort(self, data_missing_for_sorting, na_position, expected):
+ # GH 25439
+ result = nargsort(data_missing_for_sorting, na_position=na_position)
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("ascending", [True, False])
+ def test_sort_values(self, data_for_sorting, ascending, sort_by_key):
+ ser = pd.Series(data_for_sorting)
+ result = ser.sort_values(ascending=ascending, key=sort_by_key)
+ expected = ser.iloc[[2, 0, 1]]
+ if not ascending:
+ # GH 35922. Expect stable sort
+ if ser.nunique() == 2:
+ expected = ser.iloc[[0, 1, 2]]
+ else:
+ expected = ser.iloc[[1, 0, 2]]
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("ascending", [True, False])
+ def test_sort_values_missing(
+ self, data_missing_for_sorting, ascending, sort_by_key
+ ):
+ ser = pd.Series(data_missing_for_sorting)
+ result = ser.sort_values(ascending=ascending, key=sort_by_key)
+ if ascending:
+ expected = ser.iloc[[2, 0, 1]]
+ else:
+ expected = ser.iloc[[0, 2, 1]]
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("ascending", [True, False])
+ def test_sort_values_frame(self, data_for_sorting, ascending):
+ df = pd.DataFrame({"A": [1, 2, 1], "B": data_for_sorting})
+ result = df.sort_values(["A", "B"])
+ expected = pd.DataFrame(
+ {"A": [1, 1, 2], "B": data_for_sorting.take([2, 0, 1])}, index=[2, 0, 1]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("keep", ["first", "last", False])
+ def test_duplicated(self, data, keep):
+ arr = data.take([0, 1, 0, 1])
+ result = arr.duplicated(keep=keep)
+ if keep == "first":
+ expected = np.array([False, False, True, True])
+ elif keep == "last":
+ expected = np.array([True, True, False, False])
+ else:
+ expected = np.array([True, True, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("box", [pd.Series, lambda x: x])
+ @pytest.mark.parametrize("method", [lambda x: x.unique(), pd.unique])
+ def test_unique(self, data, box, method):
+ duplicated = box(data._from_sequence([data[0], data[0]], dtype=data.dtype))
+
+ result = method(duplicated)
+
+ assert len(result) == 1
+ assert isinstance(result, type(data))
+ assert result[0] == duplicated[0]
+
+ def test_factorize(self, data_for_grouping):
+ codes, uniques = pd.factorize(data_for_grouping, use_na_sentinel=True)
+
+ is_bool = data_for_grouping.dtype._is_boolean
+ if is_bool:
+ # only 2 unique values
+ expected_codes = np.array([0, 0, -1, -1, 1, 1, 0, 0], dtype=np.intp)
+ expected_uniques = data_for_grouping.take([0, 4])
+ else:
+ expected_codes = np.array([0, 0, -1, -1, 1, 1, 0, 2], dtype=np.intp)
+ expected_uniques = data_for_grouping.take([0, 4, 7])
+
+ tm.assert_numpy_array_equal(codes, expected_codes)
+ tm.assert_extension_array_equal(uniques, expected_uniques)
+
+ def test_factorize_equivalence(self, data_for_grouping):
+ codes_1, uniques_1 = pd.factorize(data_for_grouping, use_na_sentinel=True)
+ codes_2, uniques_2 = data_for_grouping.factorize(use_na_sentinel=True)
+
+ tm.assert_numpy_array_equal(codes_1, codes_2)
+ tm.assert_extension_array_equal(uniques_1, uniques_2)
+ assert len(uniques_1) == len(pd.unique(uniques_1))
+ assert uniques_1.dtype == data_for_grouping.dtype
+
+ def test_factorize_empty(self, data):
+ codes, uniques = pd.factorize(data[:0])
+ expected_codes = np.array([], dtype=np.intp)
+ expected_uniques = type(data)._from_sequence([], dtype=data[:0].dtype)
+
+ tm.assert_numpy_array_equal(codes, expected_codes)
+ tm.assert_extension_array_equal(uniques, expected_uniques)
+
+ def test_fillna_limit_frame(self, data_missing):
+ # GH#58001
+ df = pd.DataFrame({"A": data_missing.take([0, 1, 0, 1])})
+ expected = pd.DataFrame({"A": data_missing.take([1, 1, 0, 1])})
+ result = df.fillna(value=data_missing[1], limit=1)
+ tm.assert_frame_equal(result, expected)
+
+ def test_fillna_limit_series(self, data_missing):
+ # GH#58001
+ ser = pd.Series(data_missing.take([0, 1, 0, 1]))
+ expected = pd.Series(data_missing.take([1, 1, 0, 1]))
+ result = ser.fillna(value=data_missing[1], limit=1)
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_copy_frame(self, data_missing):
+ arr = data_missing.take([1, 1])
+ df = pd.DataFrame({"A": arr})
+ df_orig = df.copy()
+
+ filled_val = df.iloc[0, 0]
+ result = df.fillna(filled_val)
+
+ result.iloc[0, 0] = filled_val
+
+ tm.assert_frame_equal(df, df_orig)
+
+ def test_fillna_copy_series(self, data_missing):
+ arr = data_missing.take([1, 1])
+ ser = pd.Series(arr, copy=False)
+ ser_orig = ser.copy()
+
+ filled_val = ser[0]
+ result = ser.fillna(filled_val)
+ result.iloc[0] = filled_val
+
+ tm.assert_series_equal(ser, ser_orig)
+
+ def test_fillna_length_mismatch(self, data_missing):
+ msg = "Length of 'value' does not match."
+ with pytest.raises(ValueError, match=msg):
+ data_missing.fillna(data_missing.take([1]))
+
+ # Subclasses can override if we expect e.g Sparse[bool], boolean, pyarrow[bool]
+ _combine_le_expected_dtype: Dtype = NumpyEADtype("bool")
+
+ def test_combine_le(self, data_repeated):
+ # GH 20825
+ # Test that combine works when doing a <= (le) comparison
+ orig_data1, orig_data2 = data_repeated(2)
+ s1 = pd.Series(orig_data1)
+ s2 = pd.Series(orig_data2)
+ result = s1.combine(s2, lambda x1, x2: x1 <= x2)
+ expected = pd.Series(
+ pd.array(
+ [
+ a <= b
+ for (a, b) in zip(list(orig_data1), list(orig_data2), strict=True)
+ ],
+ dtype=self._combine_le_expected_dtype,
+ )
+ )
+ tm.assert_series_equal(result, expected)
+
+ val = s1.iloc[0]
+ result = s1.combine(val, lambda x1, x2: x1 <= x2)
+ expected = pd.Series(
+ pd.array(
+ [a <= val for a in list(orig_data1)],
+ dtype=self._combine_le_expected_dtype,
+ )
+ )
+ tm.assert_series_equal(result, expected)
+
+ def _construct_for_combine_add(self, left, right):
+ if isinstance(right, type(left)):
+ return left._from_sequence(
+ [a + b for (a, b) in zip(list(left), list(right), strict=True)],
+ dtype=left.dtype,
+ )
+ else:
+ return left._from_sequence(
+ [a + right for a in list(left)],
+ dtype=left.dtype,
+ )
+
+ def test_combine_add(self, data_repeated):
+ # GH 20825
+ orig_data1, orig_data2 = data_repeated(2)
+ s1 = pd.Series(orig_data1)
+ s2 = pd.Series(orig_data2)
+
+ # Check if the operation is supported pointwise for our scalars. If not,
+ # we will expect Series.combine to raise as well.
+ try:
+ with np.errstate(over="ignore"):
+ arr = self._construct_for_combine_add(orig_data1, orig_data2)
+ except TypeError:
+ # If the operation is not supported pointwise for our scalars,
+ # then Series.combine should also raise
+ with pytest.raises(TypeError):
+ s1.combine(s2, lambda x1, x2: x1 + x2)
+ return
+ expected = pd.Series(arr)
+
+ result = s1.combine(s2, lambda x1, x2: x1 + x2)
+ tm.assert_series_equal(result, expected)
+
+ val = s1.iloc[0]
+ result = s1.combine(val, lambda x1, x2: x1 + x2)
+ arr = self._construct_for_combine_add(orig_data1, val)
+ expected = pd.Series(arr)
+ tm.assert_series_equal(result, expected)
+
+ def test_combine_first(self, data):
+ # https://github.com/pandas-dev/pandas/issues/24147
+ a = pd.Series(data[:3])
+ b = pd.Series(data[2:5], index=[2, 3, 4])
+ result = a.combine_first(b)
+ expected = pd.Series(data[:5])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("frame", [True, False])
+ @pytest.mark.parametrize(
+ "periods, indices",
+ [(-2, [2, 3, 4, -1, -1]), (0, [0, 1, 2, 3, 4]), (2, [-1, -1, 0, 1, 2])],
+ )
+ def test_container_shift(self, data, frame, periods, indices):
+ # https://github.com/pandas-dev/pandas/issues/22386
+ subset = data[:5]
+ data = pd.Series(subset, name="A")
+ expected = pd.Series(subset.take(indices, allow_fill=True), name="A")
+
+ if frame:
+ result = data.to_frame(name="A").assign(B=1).shift(periods)
+ expected = pd.concat(
+ [expected, pd.Series([1] * 5, name="B").shift(periods)], axis=1
+ )
+ compare = tm.assert_frame_equal
+ else:
+ result = data.shift(periods)
+ compare = tm.assert_series_equal
+
+ compare(result, expected)
+
+ def test_shift_0_periods(self, data):
+ # GH#33856 shifting with periods=0 should return a copy, not same obj
+ result = data.shift(0)
+ assert data[0] != data[1] # otherwise below is invalid
+ data[0] = data[1]
+ assert result[0] != result[1] # i.e. not the same object/view
+
+ @pytest.mark.parametrize("periods", [1, -2])
+ def test_diff(self, data, periods):
+ data = data[:5]
+ if is_bool_dtype(data.dtype):
+ op = operator.xor
+ else:
+ op = operator.sub
+ try:
+ # does this array implement ops?
+ op(data, data)
+ except Exception:
+ pytest.skip(f"{type(data)} does not support diff")
+ s = pd.Series(data)
+ result = s.diff(periods)
+ expected = pd.Series(op(data, data.shift(periods)))
+ tm.assert_series_equal(result, expected)
+
+ df = pd.DataFrame({"A": data, "B": [1.0] * 5})
+ result = df.diff(periods)
+ if periods == 1:
+ b = [np.nan, 0, 0, 0, 0]
+ else:
+ b = [0, 0, 0, np.nan, np.nan]
+ expected = pd.DataFrame({"A": expected, "B": b})
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "periods, indices",
+ [[-4, [-1, -1]], [-1, [1, -1]], [0, [0, 1]], [1, [-1, 0]], [4, [-1, -1]]],
+ )
+ def test_shift_non_empty_array(self, data, periods, indices):
+ # https://github.com/pandas-dev/pandas/issues/23911
+ subset = data[:2]
+ result = subset.shift(periods)
+ expected = subset.take(indices, allow_fill=True)
+ tm.assert_extension_array_equal(result, expected)
+
+ @pytest.mark.parametrize("periods", [-4, -1, 0, 1, 4])
+ def test_shift_empty_array(self, data, periods):
+ # https://github.com/pandas-dev/pandas/issues/23911
+ empty = data[:0]
+ result = empty.shift(periods)
+ expected = empty
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_shift_zero_copies(self, data):
+ # GH#31502
+ result = data.shift(0)
+ assert result is not data
+
+ result = data[:0].shift(2)
+ assert result is not data
+
+ def test_shift_fill_value(self, data):
+ arr = data[:4]
+ fill_value = data[0]
+ result = arr.shift(1, fill_value=fill_value)
+ expected = data.take([0, 0, 1, 2])
+ tm.assert_extension_array_equal(result, expected)
+
+ result = arr.shift(-2, fill_value=fill_value)
+ expected = data.take([2, 3, 0, 0])
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_not_hashable(self, data):
+ # We are in general mutable, so not hashable
+ with pytest.raises(TypeError, match="unhashable type"):
+ hash(data)
+
+ def test_hash_pandas_object_works(self, data, as_frame):
+ # https://github.com/pandas-dev/pandas/issues/23066
+ data = pd.Series(data)
+ if as_frame:
+ data = data.to_frame()
+ a = pd.util.hash_pandas_object(data)
+ b = pd.util.hash_pandas_object(data)
+ tm.assert_equal(a, b)
+
+ def test_searchsorted(self, data_for_sorting, as_series):
+ if data_for_sorting.dtype._is_boolean:
+ return self._test_searchsorted_bool_dtypes(data_for_sorting, as_series)
+
+ b, c, a = data_for_sorting
+ arr = data_for_sorting.take([2, 0, 1]) # to get [a, b, c]
+
+ if as_series:
+ arr = pd.Series(arr)
+ assert arr.searchsorted(a) == 0
+ assert arr.searchsorted(a, side="right") == 1
+
+ assert arr.searchsorted(b) == 1
+ assert arr.searchsorted(b, side="right") == 2
+
+ assert arr.searchsorted(c) == 2
+ assert arr.searchsorted(c, side="right") == 3
+
+ result = arr.searchsorted(arr.take([0, 2]))
+ expected = np.array([0, 2], dtype=np.intp)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ # sorter
+ sorter = np.array([1, 2, 0])
+ assert data_for_sorting.searchsorted(a, sorter=sorter) == 0
+
+ def _test_searchsorted_bool_dtypes(self, data_for_sorting, as_series):
+ # We call this from test_searchsorted in cases where we have a
+ # boolean-like dtype. The non-bool test assumes we have more than 2
+ # unique values.
+ dtype = data_for_sorting.dtype
+ data_for_sorting = pd.array([True, False], dtype=dtype)
+ b, a = data_for_sorting
+ arr = type(data_for_sorting)._from_sequence([a, b], dtype=dtype)
+
+ if as_series:
+ arr = pd.Series(arr)
+ assert arr.searchsorted(a) == 0
+ assert arr.searchsorted(a, side="right") == 1
+
+ assert arr.searchsorted(b) == 1
+ assert arr.searchsorted(b, side="right") == 2
+
+ result = arr.searchsorted(arr.take([0, 1]))
+ expected = np.array([0, 1], dtype=np.intp)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ # sorter
+ sorter = np.array([1, 0])
+ assert data_for_sorting.searchsorted(a, sorter=sorter) == 0
+
+ def test_where_series(self, data, na_value, as_frame):
+ assert data[0] != data[1]
+ cls = type(data)
+ a, b = data[:2]
+
+ orig = pd.Series(cls._from_sequence([a, a, b, b], dtype=data.dtype))
+ ser = orig.copy()
+ cond = np.array([True, True, False, False])
+
+ if as_frame:
+ ser = ser.to_frame(name="a")
+ cond = cond.reshape(-1, 1)
+
+ result = ser.where(cond)
+ expected = pd.Series(
+ cls._from_sequence([a, a, na_value, na_value], dtype=data.dtype)
+ )
+
+ if as_frame:
+ expected = expected.to_frame(name="a")
+ tm.assert_equal(result, expected)
+
+ ser.mask(~cond, inplace=True)
+ tm.assert_equal(ser, expected)
+
+ # array other
+ ser = orig.copy()
+ if as_frame:
+ ser = ser.to_frame(name="a")
+ cond = np.array([True, False, True, True])
+ other = cls._from_sequence([a, b, a, b], dtype=data.dtype)
+ if as_frame:
+ other = pd.DataFrame({"a": other})
+ cond = pd.DataFrame({"a": cond})
+ result = ser.where(cond, other)
+ expected = pd.Series(cls._from_sequence([a, b, b, b], dtype=data.dtype))
+ if as_frame:
+ expected = expected.to_frame(name="a")
+ tm.assert_equal(result, expected)
+
+ ser.mask(~cond, other, inplace=True)
+ tm.assert_equal(ser, expected)
+
+ @pytest.mark.parametrize("repeats", [0, 1, 2, [1, 2, 3]])
+ def test_repeat(self, data, repeats, as_series, use_numpy):
+ arr = type(data)._from_sequence(data[:3], dtype=data.dtype)
+ if as_series:
+ arr = pd.Series(arr)
+
+ result = np.repeat(arr, repeats) if use_numpy else arr.repeat(repeats)
+
+ repeats = [repeats] * 3 if isinstance(repeats, int) else repeats
+ expected = [x for x, n in zip(arr, repeats, strict=True) for _ in range(n)]
+ expected = type(data)._from_sequence(expected, dtype=data.dtype)
+ if as_series:
+ expected = pd.Series(expected, index=arr.index.repeat(repeats))
+
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "repeats, kwargs, error, msg",
+ [
+ (2, {"axis": 1}, ValueError, "axis"),
+ (-1, {}, ValueError, "negative"),
+ ([1, 2], {}, ValueError, "shape"),
+ (2, {"foo": "bar"}, TypeError, "'foo'"),
+ ],
+ )
+ def test_repeat_raises(self, data, repeats, kwargs, error, msg, use_numpy):
+ with pytest.raises(error, match=msg):
+ if use_numpy:
+ np.repeat(data, repeats, **kwargs)
+ else:
+ data.repeat(repeats, **kwargs)
+
+ def test_delete(self, data):
+ result = data.delete(0)
+ expected = data[1:]
+ tm.assert_extension_array_equal(result, expected)
+
+ result = data.delete([1, 3])
+ expected = data._concat_same_type([data[[0]], data[[2]], data[4:]])
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_insert(self, data):
+ # insert at the beginning
+ result = data[1:].insert(0, data[0])
+ tm.assert_extension_array_equal(result, data)
+
+ result = data[1:].insert(-len(data[1:]), data[0])
+ tm.assert_extension_array_equal(result, data)
+
+ # insert at the middle
+ result = data[:-1].insert(4, data[-1])
+
+ taker = np.arange(len(data))
+ taker[5:] = taker[4:-1]
+ taker[4] = len(data) - 1
+ expected = data.take(taker)
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_insert_invalid(self, data, invalid_scalar):
+ item = invalid_scalar
+
+ with pytest.raises((TypeError, ValueError)):
+ data.insert(0, item)
+
+ with pytest.raises((TypeError, ValueError)):
+ data.insert(4, item)
+
+ with pytest.raises((TypeError, ValueError)):
+ data.insert(len(data) - 1, item)
+
+ def test_insert_invalid_loc(self, data):
+ ub = len(data)
+
+ with pytest.raises(IndexError):
+ data.insert(ub + 1, data[0])
+
+ with pytest.raises(IndexError):
+ data.insert(-ub - 1, data[0])
+
+ with pytest.raises(TypeError):
+ # we expect TypeError here instead of IndexError to match np.insert
+ data.insert(1.5, data[0])
+
+ @pytest.mark.parametrize("box", [pd.array, pd.Series, pd.DataFrame])
+ def test_equals(self, data, na_value, as_series, box):
+ data2 = type(data)._from_sequence([data[0]] * len(data), dtype=data.dtype)
+ data_na = type(data)._from_sequence([na_value] * len(data), dtype=data.dtype)
+
+ data = tm.box_expected(data, box, transpose=False)
+ data2 = tm.box_expected(data2, box, transpose=False)
+ data_na = tm.box_expected(data_na, box, transpose=False)
+
+ # we are asserting with `is True/False` explicitly, to test that the
+ # result is an actual Python bool, and not something "truthy"
+
+ assert data.equals(data) is True
+ assert data.equals(data.copy()) is True
+
+ # unequal other data
+ assert data.equals(data2) is False
+ assert data.equals(data_na) is False
+
+ # different length
+ assert data[:2].equals(data[:3]) is False
+
+ # empty are equal
+ assert data[:0].equals(data[:0]) is True
+
+ # other types
+ assert data.equals(None) is False
+ assert data[[0]].equals(data[0]) is False
+
+ def test_equals_same_data_different_object(self, data):
+ # https://github.com/pandas-dev/pandas/issues/34660
+ assert pd.Series(data).equals(pd.Series(data))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/missing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/missing.py
new file mode 100644
index 0000000000000000000000000000000000000000..0277bb59bc7f5483b0ca6ebbd3552c0df1da128d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/missing.py
@@ -0,0 +1,195 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class BaseMissingTests:
+ def test_isna(self, data_missing):
+ expected = np.array([True, False])
+
+ result = pd.isna(data_missing)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = pd.Series(data_missing).isna()
+ expected = pd.Series(expected)
+ tm.assert_series_equal(result, expected)
+
+ # GH 21189
+ result = pd.Series(data_missing).drop([0, 1]).isna()
+ expected = pd.Series([], dtype=bool)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("na_func", ["isna", "notna"])
+ def test_isna_returns_copy(self, data_missing, na_func):
+ result = pd.Series(data_missing)
+ expected = result.copy()
+ mask = getattr(result, na_func)()
+ if isinstance(mask.dtype, pd.SparseDtype):
+ # TODO: GH 57739
+ mask = np.array(mask)
+ mask.flags.writeable = True
+
+ mask[:] = True
+ tm.assert_series_equal(result, expected)
+
+ def test_dropna_array(self, data_missing):
+ result = data_missing.dropna()
+ expected = data_missing[[1]]
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_dropna_series(self, data_missing):
+ ser = pd.Series(data_missing)
+ result = ser.dropna()
+ expected = ser.iloc[[1]]
+ tm.assert_series_equal(result, expected)
+
+ def test_dropna_frame(self, data_missing):
+ df = pd.DataFrame({"A": data_missing}, columns=pd.Index(["A"], dtype=object))
+
+ # defaults
+ result = df.dropna()
+ expected = df.iloc[[1]]
+ tm.assert_frame_equal(result, expected)
+
+ # axis = 1
+ result = df.dropna(axis="columns")
+ expected = pd.DataFrame(index=pd.RangeIndex(2), columns=pd.Index([]))
+ tm.assert_frame_equal(result, expected)
+
+ # multiple
+ df = pd.DataFrame({"A": data_missing, "B": [1, np.nan]})
+ result = df.dropna()
+ expected = df.iloc[:0]
+ tm.assert_frame_equal(result, expected)
+
+ def test_fillna_scalar(self, data_missing):
+ valid = data_missing[1]
+ result = data_missing.fillna(valid)
+ expected = data_missing.fillna(valid)
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_fillna_with_none(self, data_missing):
+ # GH#57723
+ result = data_missing.fillna(None)
+ expected = data_missing
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_fillna_limit_pad(self, data_missing):
+ arr = data_missing.take([1, 0, 0, 0, 1])
+ result = pd.Series(arr).ffill(limit=2)
+ expected = pd.Series(data_missing.take([1, 1, 1, 0, 1]))
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "limit_area, input_ilocs, expected_ilocs",
+ [
+ ("outside", [1, 0, 0, 0, 1], [1, 0, 0, 0, 1]),
+ ("outside", [1, 0, 1, 0, 1], [1, 0, 1, 0, 1]),
+ ("outside", [0, 1, 1, 1, 0], [0, 1, 1, 1, 1]),
+ ("outside", [0, 1, 0, 1, 0], [0, 1, 0, 1, 1]),
+ ("inside", [1, 0, 0, 0, 1], [1, 1, 1, 1, 1]),
+ ("inside", [1, 0, 1, 0, 1], [1, 1, 1, 1, 1]),
+ ("inside", [0, 1, 1, 1, 0], [0, 1, 1, 1, 0]),
+ ("inside", [0, 1, 0, 1, 0], [0, 1, 1, 1, 0]),
+ ],
+ )
+ def test_ffill_limit_area(
+ self, data_missing, limit_area, input_ilocs, expected_ilocs
+ ):
+ # GH#56616
+ arr = data_missing.take(input_ilocs)
+ result = pd.Series(arr).ffill(limit_area=limit_area)
+ expected = pd.Series(data_missing.take(expected_ilocs))
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_limit_backfill(self, data_missing):
+ arr = data_missing.take([1, 0, 0, 0, 1])
+ result = pd.Series(arr).bfill(limit=2)
+ expected = pd.Series(data_missing.take([1, 0, 1, 1, 1]))
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_no_op_returns_copy(self, data):
+ data = data[~data.isna()]
+
+ valid = data[0]
+ result = data.fillna(valid)
+ assert result is not data
+ tm.assert_extension_array_equal(result, data)
+
+ result = data._pad_or_backfill(method="backfill")
+ assert result is not data
+ tm.assert_extension_array_equal(result, data)
+
+ def test_fillna_readonly(self, data_missing):
+ data = data_missing.copy()
+ data._readonly = True
+
+ # by default fillna(copy=True), then this works fine
+ result = data.fillna(data_missing[1])
+ assert result[0] == data_missing[1]
+ tm.assert_extension_array_equal(data, data_missing)
+
+ # but with copy=False, this raises for EAs that respect the copy keyword
+ with pytest.raises(ValueError, match="Cannot modify read-only array"):
+ data.fillna(data_missing[1], copy=False)
+ tm.assert_extension_array_equal(data, data_missing)
+
+ def test_fillna_series(self, data_missing):
+ fill_value = data_missing[1]
+ ser = pd.Series(data_missing)
+
+ result = ser.fillna(fill_value)
+ expected = pd.Series(
+ data_missing._from_sequence(
+ [fill_value, fill_value], dtype=data_missing.dtype
+ )
+ )
+ tm.assert_series_equal(result, expected)
+
+ # Fill with a series
+ result = ser.fillna(expected)
+ tm.assert_series_equal(result, expected)
+
+ # Fill with a series not affecting the missing values
+ result = ser.fillna(ser)
+ tm.assert_series_equal(result, ser)
+
+ def test_fillna_series_method(self, data_missing, fillna_method):
+ fill_value = data_missing[1]
+
+ if fillna_method == "ffill":
+ data_missing = data_missing[::-1]
+
+ result = getattr(pd.Series(data_missing), fillna_method)()
+ expected = pd.Series(
+ data_missing._from_sequence(
+ [fill_value, fill_value], dtype=data_missing.dtype
+ )
+ )
+
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_frame(self, data_missing):
+ fill_value = data_missing[1]
+
+ result = pd.DataFrame({"A": data_missing, "B": [1, 2]}).fillna(fill_value)
+
+ expected = pd.DataFrame(
+ {
+ "A": data_missing._from_sequence(
+ [fill_value, fill_value], dtype=data_missing.dtype
+ ),
+ "B": [1, 2],
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_fillna_fill_other(self, data):
+ result = pd.DataFrame({"A": data, "B": [np.nan] * len(data)}).fillna({"B": 0.0})
+
+ expected = pd.DataFrame({"A": data, "B": [0.0] * len(result)})
+
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/ops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/ops.py
new file mode 100644
index 0000000000000000000000000000000000000000..583435b674ba1fccb5538e885f63ced46a1a824b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/ops.py
@@ -0,0 +1,287 @@
+from __future__ import annotations
+
+from typing import final
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import is_string_dtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core import ops
+
+
+class BaseOpsUtil:
+ series_scalar_exc: type[Exception] | None = TypeError
+ frame_scalar_exc: type[Exception] | None = TypeError
+ series_array_exc: type[Exception] | None = TypeError
+ divmod_exc: type[Exception] | None = TypeError
+
+ def _get_expected_exception(
+ self, op_name: str, obj, other
+ ) -> type[Exception] | tuple[type[Exception], ...] | None:
+ # Find the Exception, if any we expect to raise calling
+ # obj.__op_name__(other)
+
+ # The self.obj_bar_exc pattern isn't great in part because it can depend
+ # on op_name or dtypes, but we use it here for backward-compatibility.
+ if op_name in ["__divmod__", "__rdivmod__"]:
+ result = self.divmod_exc
+ elif isinstance(obj, pd.Series) and isinstance(other, pd.Series):
+ result = self.series_array_exc
+ elif isinstance(obj, pd.Series):
+ result = self.series_scalar_exc
+ else:
+ result = self.frame_scalar_exc
+
+ return result
+
+ def _cast_pointwise_result(self, op_name: str, obj, other, pointwise_result):
+ # In _check_op we check that the result of a pointwise operation
+ # (found via _combine) matches the result of the vectorized
+ # operation obj.__op_name__(other).
+ # In some cases pandas dtype inference on the scalar result may not
+ # give a matching dtype even if both operations are behaving "correctly".
+ # In these cases, do extra required casting here.
+ return pointwise_result
+
+ def get_op_from_name(self, op_name: str):
+ return tm.get_op_from_name(op_name)
+
+ # Subclasses are not expected to need to override check_opname, _check_op,
+ # _check_divmod_op, or _combine.
+ # Ideally any relevant overriding can be done in _cast_pointwise_result,
+ # get_op_from_name, and the specification of `exc`. If you find a use
+ # case that still requires overriding _check_op or _combine, please let
+ # us know at github.com/pandas-dev/pandas/issues
+ @final
+ def check_opname(self, ser: pd.Series, op_name: str, other):
+ exc = self._get_expected_exception(op_name, ser, other)
+ op = self.get_op_from_name(op_name)
+
+ self._check_op(ser, op, other, op_name, exc)
+
+ # see comment on check_opname
+ @final
+ def _combine(self, obj, other, op):
+ if isinstance(obj, pd.DataFrame):
+ if len(obj.columns) != 1:
+ raise NotImplementedError
+ expected = obj.iloc[:, 0].combine(other, op).to_frame()
+ else:
+ expected = obj.combine(other, op)
+ return expected
+
+ # see comment on check_opname
+ @final
+ def _check_op(
+ self, ser: pd.Series, op, other, op_name: str, exc=NotImplementedError
+ ):
+ # Check that the Series/DataFrame arithmetic/comparison method matches
+ # the pointwise result from _combine.
+
+ if exc is None:
+ result = op(ser, other)
+ expected = self._combine(ser, other, op)
+ expected = self._cast_pointwise_result(op_name, ser, other, expected)
+ assert isinstance(result, type(ser))
+ tm.assert_equal(result, expected)
+ else:
+ with pytest.raises(exc):
+ op(ser, other)
+
+ # see comment on check_opname
+ @final
+ def _check_divmod_op(self, ser: pd.Series, op, other):
+ # check that divmod behavior matches behavior of floordiv+mod
+ if op is divmod:
+ exc = self._get_expected_exception("__divmod__", ser, other)
+ else:
+ exc = self._get_expected_exception("__rdivmod__", ser, other)
+ if exc is None:
+ result_div, result_mod = op(ser, other)
+ if op is divmod:
+ expected_div, expected_mod = ser // other, ser % other
+ else:
+ expected_div, expected_mod = other // ser, other % ser
+ tm.assert_series_equal(result_div, expected_div)
+ tm.assert_series_equal(result_mod, expected_mod)
+ else:
+ with pytest.raises(exc):
+ divmod(ser, other)
+
+
+class BaseArithmeticOpsTests(BaseOpsUtil):
+ """
+ Various Series and DataFrame arithmetic ops methods.
+
+ Subclasses supporting various ops should set the class variables
+ to indicate that they support ops of that kind
+
+ * series_scalar_exc = TypeError
+ * frame_scalar_exc = TypeError
+ * series_array_exc = TypeError
+ * divmod_exc = TypeError
+ """
+
+ series_scalar_exc: type[Exception] | None = TypeError
+ frame_scalar_exc: type[Exception] | None = TypeError
+ series_array_exc: type[Exception] | None = TypeError
+ divmod_exc: type[Exception] | None = TypeError
+
+ def test_arith_series_with_scalar(self, data, all_arithmetic_operators):
+ # series & scalar
+ if all_arithmetic_operators == "__rmod__" and is_string_dtype(data.dtype):
+ pytest.skip("Skip testing Python string formatting")
+
+ op_name = all_arithmetic_operators
+ ser = pd.Series(data)
+ self.check_opname(ser, op_name, ser.iloc[0])
+
+ def test_arith_frame_with_scalar(self, data, all_arithmetic_operators):
+ # frame & scalar
+ if all_arithmetic_operators == "__rmod__" and is_string_dtype(data.dtype):
+ pytest.skip("Skip testing Python string formatting")
+
+ op_name = all_arithmetic_operators
+ df = pd.DataFrame({"A": data})
+ self.check_opname(df, op_name, data[0])
+
+ def test_arith_series_with_array(self, data, all_arithmetic_operators):
+ # ndarray & other series
+ op_name = all_arithmetic_operators
+ ser = pd.Series(data)
+ self.check_opname(ser, op_name, pd.Series([ser.iloc[0]] * len(ser)))
+
+ def test_divmod(self, data):
+ ser = pd.Series(data)
+ self._check_divmod_op(ser, divmod, 1)
+ self._check_divmod_op(1, ops.rdivmod, ser)
+
+ def test_divmod_series_array(self, data, data_for_twos):
+ ser = pd.Series(data)
+ self._check_divmod_op(ser, divmod, data)
+
+ other = data_for_twos
+ self._check_divmod_op(other, ops.rdivmod, ser)
+
+ other = pd.Series(other)
+ self._check_divmod_op(other, ops.rdivmod, ser)
+
+ def test_add_series_with_extension_array(self, data):
+ # Check adding an ExtensionArray to a Series of the same dtype matches
+ # the behavior of adding the arrays directly and then wrapping in a
+ # Series.
+
+ ser = pd.Series(data)
+
+ exc = self._get_expected_exception("__add__", ser, data)
+ if exc is not None:
+ with pytest.raises(exc):
+ ser + data
+ return
+
+ result = ser + data
+ expected = pd.Series(data + data)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("box", [pd.Series, pd.DataFrame, pd.Index])
+ @pytest.mark.parametrize(
+ "op_name",
+ [
+ x
+ for x in tm.arithmetic_dunder_methods + tm.comparison_dunder_methods
+ if not x.startswith("__r")
+ ],
+ )
+ def test_direct_arith_with_ndframe_returns_not_implemented(
+ self, data, box, op_name
+ ):
+ # EAs should return NotImplemented for ops with Series/DataFrame/Index
+ # Pandas takes care of unboxing the series and calling the EA's op.
+ other = box(data)
+
+ if hasattr(data, op_name):
+ result = getattr(data, op_name)(other)
+ assert result is NotImplemented
+
+
+class BaseComparisonOpsTests(BaseOpsUtil):
+ """Various Series and DataFrame comparison ops methods."""
+
+ def _compare_other(self, ser: pd.Series, data, op, other):
+ if op.__name__ in ["eq", "ne"]:
+ # comparison should match point-wise comparisons
+ result = op(ser, other)
+ expected = ser.combine(other, op)
+ expected = self._cast_pointwise_result(op.__name__, ser, other, expected)
+ tm.assert_series_equal(result, expected)
+
+ else:
+ exc = None
+ try:
+ result = op(ser, other)
+ except Exception as err:
+ exc = err
+
+ if exc is None:
+ # Didn't error, then should match pointwise behavior
+ expected = ser.combine(other, op)
+ expected = self._cast_pointwise_result(
+ op.__name__, ser, other, expected
+ )
+ tm.assert_series_equal(result, expected)
+ else:
+ with pytest.raises(type(exc)):
+ ser.combine(other, op)
+
+ def test_compare_scalar(self, data, comparison_op):
+ ser = pd.Series(data)
+ self._compare_other(ser, data, comparison_op, 0)
+
+ def test_compare_array(self, data, comparison_op):
+ ser = pd.Series(data)
+ other = pd.Series([data[0]] * len(data), dtype=data.dtype)
+ self._compare_other(ser, data, comparison_op, other)
+
+
+class BaseUnaryOpsTests(BaseOpsUtil):
+ def test_invert(self, data):
+ ser = pd.Series(data, name="name")
+ try:
+ [~x for x in data]
+ except TypeError:
+ # scalars don't support invert -> we don't expect the vectorized
+ # operation to succeed
+ with pytest.raises(TypeError):
+ ~ser
+ with pytest.raises(TypeError):
+ ~data
+ else:
+ # Note we do not reuse the pointwise result to construct expected
+ # because python semantics for negating bools are weird see GH#54569
+ result = ~ser
+ expected = pd.Series(~data, name="name")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("ufunc", [np.positive, np.negative, np.abs])
+ def test_unary_ufunc_dunder_equivalence(self, data, ufunc):
+ # the dunder __pos__ works if and only if np.positive works,
+ # same for __neg__/np.negative and __abs__/np.abs
+ attr = {np.positive: "__pos__", np.negative: "__neg__", np.abs: "__abs__"}[
+ ufunc
+ ]
+
+ exc = None
+ try:
+ result = getattr(data, attr)()
+ except Exception as err:
+ exc = err
+
+ # if __pos__ raised, then so should the ufunc
+ with pytest.raises((type(exc), TypeError)):
+ ufunc(data)
+ else:
+ alt = ufunc(data)
+ tm.assert_extension_array_equal(result, alt)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/printing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/printing.py
new file mode 100644
index 0000000000000000000000000000000000000000..f963c21ceea370c68af793b963daef7c6b7ea328
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/printing.py
@@ -0,0 +1,41 @@
+import io
+
+import pytest
+
+import pandas as pd
+
+
+class BasePrintingTests:
+ """Tests checking the formatting of your EA when printed."""
+
+ @pytest.mark.parametrize("size", ["big", "small"])
+ def test_array_repr(self, data, size):
+ if size == "small":
+ data = data[:5]
+ else:
+ data = type(data)._concat_same_type([data] * 20)
+
+ result = repr(data)
+ assert type(data).__name__ in result
+ assert f"Length: {len(data)}" in result
+ assert str(data.dtype) in result
+ if size == "big":
+ assert "..." in result
+
+ def test_array_repr_unicode(self, data):
+ result = str(data)
+ assert isinstance(result, str)
+
+ def test_series_repr(self, data):
+ ser = pd.Series(data)
+ assert data.dtype.name in repr(ser)
+
+ def test_dataframe_repr(self, data):
+ df = pd.DataFrame({"A": data})
+ repr(df)
+
+ def test_dtype_name_in_info(self, data):
+ buf = io.StringIO()
+ pd.DataFrame({"A": data}).info(buf=buf)
+ result = buf.getvalue()
+ assert data.dtype.name in result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/reduce.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/reduce.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b3431d938f96ac980f4a059cb18ed0aef46bc2a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/reduce.py
@@ -0,0 +1,128 @@
+from typing import final
+
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class BaseReduceTests:
+ """
+ Reduction specific tests. Generally these only
+ make sense for numeric/boolean operations.
+ """
+
+ def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool:
+ # Specify if we expect this reduction to succeed.
+ return False
+
+ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool):
+ # We perform the same operation on the np.float64 data and check
+ # that the results match. Override if you need to cast to something
+ # other than float64.
+ res_op = getattr(ser, op_name)
+
+ try:
+ alt = ser.astype("float64")
+ except (TypeError, ValueError):
+ # e.g. Interval can't cast (TypeError), StringArray can't cast
+ # (ValueError), so let's cast to object and do
+ # the reduction pointwise
+ alt = ser.astype(object)
+
+ exp_op = getattr(alt, op_name)
+ if op_name == "count":
+ result = res_op()
+ expected = exp_op()
+ else:
+ result = res_op(skipna=skipna)
+ expected = exp_op(skipna=skipna)
+ tm.assert_almost_equal(result, expected)
+
+ def _get_expected_reduction_dtype(self, arr, op_name: str, skipna: bool):
+ # Find the expected dtype when the given reduction is done on a DataFrame
+ # column with this array. The default assumes float64-like behavior,
+ # i.e. retains the dtype.
+ return arr.dtype
+
+ # We anticipate that authors should not need to override check_reduce_frame,
+ # but should be able to do any necessary overriding in
+ # _get_expected_reduction_dtype. If you have a use case where this
+ # does not hold, please let us know at github.com/pandas-dev/pandas/issues.
+ @final
+ def check_reduce_frame(self, ser: pd.Series, op_name: str, skipna: bool):
+ # Check that the 2D reduction done in a DataFrame reduction "looks like"
+ # a wrapped version of the 1D reduction done by Series.
+ arr = ser.array
+ df = pd.DataFrame({"a": arr})
+
+ kwargs = {"ddof": 1} if op_name in ["var", "std", "sem"] else {}
+
+ cmp_dtype = self._get_expected_reduction_dtype(arr, op_name, skipna)
+
+ # The DataFrame method just calls arr._reduce with keepdims=True,
+ # so this first check is perfunctory.
+ result1 = arr._reduce(op_name, skipna=skipna, keepdims=True, **kwargs)
+ result2 = getattr(df, op_name)(skipna=skipna, **kwargs).array
+ tm.assert_extension_array_equal(result1, result2)
+
+ # Check that the 2D reduction looks like a wrapped version of the
+ # 1D reduction
+ if not skipna and ser.isna().any():
+ expected = pd.array([pd.NA], dtype=cmp_dtype)
+ else:
+ exp_value = getattr(ser.dropna(), op_name)()
+ expected = pd.array([exp_value], dtype=cmp_dtype)
+
+ tm.assert_extension_array_equal(result1, expected)
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_series_boolean(self, data, all_boolean_reductions, skipna):
+ op_name = all_boolean_reductions
+ ser = pd.Series(data)
+
+ if not self._supports_reduction(ser, op_name):
+ # TODO: the message being checked here isn't actually checking anything
+ msg = (
+ "[Cc]annot perform|Categorical is not ordered for operation|"
+ "does not support operation|"
+ )
+
+ with pytest.raises(TypeError, match=msg):
+ getattr(ser, op_name)(skipna=skipna)
+
+ else:
+ self.check_reduce(ser, op_name, skipna)
+
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna):
+ op_name = all_numeric_reductions
+ ser = pd.Series(data)
+
+ if not self._supports_reduction(ser, op_name):
+ # TODO: the message being checked here isn't actually checking anything
+ msg = (
+ "[Cc]annot perform|Categorical is not ordered for operation|"
+ "does not support operation|"
+ )
+
+ with pytest.raises(TypeError, match=msg):
+ getattr(ser, op_name)(skipna=skipna)
+
+ else:
+ # min/max with empty produce numpy warnings
+ self.check_reduce(ser, op_name, skipna)
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_frame(self, data, all_numeric_reductions, skipna):
+ op_name = all_numeric_reductions
+ ser = pd.Series(data)
+
+ if op_name == "count":
+ pytest.skip(f"{op_name} not an array method")
+
+ if not self._supports_reduction(ser, op_name):
+ pytest.skip(f"Reduction {op_name} not supported for this dtype")
+
+ self.check_reduce_frame(ser, op_name, skipna)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/reshaping.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/reshaping.py
new file mode 100644
index 0000000000000000000000000000000000000000..795e6dd4d5a71202456fe5276c544a7332f36946
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/reshaping.py
@@ -0,0 +1,389 @@
+import itertools
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.dtypes import NumpyEADtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.extensions import ExtensionArray
+from pandas.core.internals.blocks import EABackedBlock
+
+
+class BaseReshapingTests:
+ """Tests for reshaping and concatenation."""
+
+ @pytest.mark.parametrize("in_frame", [True, False])
+ def test_concat(self, data, in_frame):
+ wrapped = pd.Series(data)
+ if in_frame:
+ wrapped = pd.DataFrame(wrapped)
+ result = pd.concat([wrapped, wrapped], ignore_index=True)
+
+ assert len(result) == len(data) * 2
+
+ if in_frame:
+ dtype = result.dtypes[0]
+ else:
+ dtype = result.dtype
+
+ assert dtype == data.dtype
+ if hasattr(result._mgr, "blocks"):
+ assert isinstance(result._mgr.blocks[0], EABackedBlock)
+ assert isinstance(result._mgr.blocks[0].values, ExtensionArray)
+
+ @pytest.mark.parametrize("in_frame", [True, False])
+ def test_concat_all_na_block(self, data_missing, in_frame):
+ valid_block = pd.Series(data_missing.take([1, 1]), index=range(2))
+ na_block = pd.Series(data_missing.take([0, 0]), index=range(2, 4))
+ if in_frame:
+ valid_block = pd.DataFrame({"a": valid_block})
+ na_block = pd.DataFrame({"a": na_block})
+ result = pd.concat([valid_block, na_block])
+ if in_frame:
+ expected = pd.DataFrame({"a": data_missing.take([1, 1, 0, 0])})
+ tm.assert_frame_equal(result, expected)
+ else:
+ expected = pd.Series(data_missing.take([1, 1, 0, 0]))
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_mixed_dtypes(self, data):
+ # https://github.com/pandas-dev/pandas/issues/20762
+ df1 = pd.DataFrame({"A": data[:3]})
+ df2 = pd.DataFrame({"A": [1, 2, 3]})
+ df3 = pd.DataFrame({"A": ["a", "b", "c"]}).astype("category")
+ dfs = [df1, df2, df3]
+
+ # dataframes
+ result = pd.concat(dfs)
+ expected = pd.concat([x.astype(object) for x in dfs])
+ tm.assert_frame_equal(result, expected)
+
+ # series
+ result = pd.concat([x["A"] for x in dfs])
+ expected = pd.concat([x["A"].astype(object) for x in dfs])
+ tm.assert_series_equal(result, expected)
+
+ # simple test for just EA and one other
+ result = pd.concat([df1, df2.astype(object)])
+ expected = pd.concat([df1.astype("object"), df2.astype("object")])
+ tm.assert_frame_equal(result, expected)
+
+ result = pd.concat([df1["A"], df2["A"].astype(object)])
+ expected = pd.concat([df1["A"].astype("object"), df2["A"].astype("object")])
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_columns(self, data, na_value):
+ df1 = pd.DataFrame({"A": data[:3]})
+ df2 = pd.DataFrame({"B": [1, 2, 3]})
+
+ expected = pd.DataFrame({"A": data[:3], "B": [1, 2, 3]})
+ result = pd.concat([df1, df2], axis=1)
+ tm.assert_frame_equal(result, expected)
+ result = pd.concat([df1["A"], df2["B"]], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ # non-aligned
+ df2 = pd.DataFrame({"B": [1, 2, 3]}, index=[1, 2, 3])
+ expected = pd.DataFrame(
+ {
+ "A": data._from_sequence([*list(data[:3]), na_value], dtype=data.dtype),
+ "B": [np.nan, 1, 2, 3],
+ }
+ )
+
+ result = pd.concat([df1, df2], axis=1)
+ tm.assert_frame_equal(result, expected)
+ result = pd.concat([df1["A"], df2["B"]], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_extension_arrays_copy_false(self, data, na_value):
+ # GH 20756
+ df1 = pd.DataFrame({"A": data[:3]})
+ df2 = pd.DataFrame({"B": data[3:7]})
+ expected = pd.DataFrame(
+ {
+ "A": data._from_sequence([*list(data[:3]), na_value], dtype=data.dtype),
+ "B": data[3:7],
+ }
+ )
+ result = pd.concat([df1, df2], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_with_reindex(self, data):
+ # GH-33027
+ a = pd.DataFrame({"a": data[:5]})
+ b = pd.DataFrame({"b": data[:5]})
+ result = pd.concat([a, b], ignore_index=True)
+ expected = pd.DataFrame(
+ {
+ "a": data.take(list(range(5)) + ([-1] * 5), allow_fill=True),
+ "b": data.take(([-1] * 5) + list(range(5)), allow_fill=True),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_align(self, data, na_value):
+ a = data[:3]
+ b = data[2:5]
+ r1, r2 = pd.Series(a).align(pd.Series(b, index=[1, 2, 3]))
+
+ # Assumes that the ctor can take a list of scalars of the type
+ e1 = pd.Series(data._from_sequence([*list(a), na_value], dtype=data.dtype))
+ e2 = pd.Series(data._from_sequence([na_value, *list(b)], dtype=data.dtype))
+ tm.assert_series_equal(r1, e1)
+ tm.assert_series_equal(r2, e2)
+
+ def test_align_frame(self, data, na_value):
+ a = data[:3]
+ b = data[2:5]
+ r1, r2 = pd.DataFrame({"A": a}).align(pd.DataFrame({"A": b}, index=[1, 2, 3]))
+
+ # Assumes that the ctor can take a list of scalars of the type
+ e1 = pd.DataFrame(
+ {"A": data._from_sequence([*list(a), na_value], dtype=data.dtype)}
+ )
+ e2 = pd.DataFrame(
+ {"A": data._from_sequence([na_value, *list(b)], dtype=data.dtype)}
+ )
+ tm.assert_frame_equal(r1, e1)
+ tm.assert_frame_equal(r2, e2)
+
+ def test_align_series_frame(self, data, na_value):
+ # https://github.com/pandas-dev/pandas/issues/20576
+ ser = pd.Series(data, name="a")
+ df = pd.DataFrame({"col": np.arange(len(ser) + 1)})
+ r1, r2 = ser.align(df)
+
+ e1 = pd.Series(
+ data._from_sequence([*list(data), na_value], dtype=data.dtype),
+ name=ser.name,
+ )
+
+ tm.assert_series_equal(r1, e1)
+ tm.assert_frame_equal(r2, df)
+
+ def test_set_frame_expand_regular_with_extension(self, data):
+ df = pd.DataFrame({"A": [1] * len(data)})
+ df["B"] = data
+ expected = pd.DataFrame({"A": [1] * len(data), "B": data})
+ tm.assert_frame_equal(df, expected)
+
+ def test_set_frame_expand_extension_with_regular(self, data):
+ df = pd.DataFrame({"A": data})
+ df["B"] = [1] * len(data)
+ expected = pd.DataFrame({"A": data, "B": [1] * len(data)})
+ tm.assert_frame_equal(df, expected)
+
+ def test_set_frame_overwrite_object(self, data):
+ # https://github.com/pandas-dev/pandas/issues/20555
+ df = pd.DataFrame({"A": [1] * len(data)}, dtype=object)
+ df["A"] = data
+ assert df.dtypes["A"] == data.dtype
+
+ def test_merge(self, data, na_value):
+ # GH-20743
+ df1 = pd.DataFrame({"ext": data[:3], "int1": [1, 2, 3], "key": [0, 1, 2]})
+ df2 = pd.DataFrame({"int2": [1, 2, 3, 4], "key": [0, 0, 1, 3]})
+
+ res = pd.merge(df1, df2)
+ exp = pd.DataFrame(
+ {
+ "int1": [1, 1, 2],
+ "int2": [1, 2, 3],
+ "key": [0, 0, 1],
+ "ext": data._from_sequence(
+ [data[0], data[0], data[1]], dtype=data.dtype
+ ),
+ }
+ )
+ tm.assert_frame_equal(res, exp[["ext", "int1", "key", "int2"]])
+
+ res = pd.merge(df1, df2, how="outer")
+ exp = pd.DataFrame(
+ {
+ "int1": [1, 1, 2, 3, np.nan],
+ "int2": [1, 2, 3, np.nan, 4],
+ "key": [0, 0, 1, 2, 3],
+ "ext": data._from_sequence(
+ [data[0], data[0], data[1], data[2], na_value], dtype=data.dtype
+ ),
+ }
+ )
+ tm.assert_frame_equal(res, exp[["ext", "int1", "key", "int2"]])
+
+ def test_merge_on_extension_array(self, data):
+ # GH 23020
+ a, b = data[:2]
+ key = type(data)._from_sequence([a, b], dtype=data.dtype)
+
+ df = pd.DataFrame({"key": key, "val": [1, 2]})
+ result = pd.merge(df, df, on="key")
+ expected = pd.DataFrame({"key": key, "val_x": [1, 2], "val_y": [1, 2]})
+ tm.assert_frame_equal(result, expected)
+
+ # order
+ result = pd.merge(df.iloc[[1, 0]], df, on="key")
+ expected = expected.iloc[[1, 0]].reset_index(drop=True)
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_on_extension_array_duplicates(self, data):
+ # GH 23020
+ a, b = data[:2]
+ key = type(data)._from_sequence([a, b, a], dtype=data.dtype)
+ df1 = pd.DataFrame({"key": key, "val": [1, 2, 3]})
+ df2 = pd.DataFrame({"key": key, "val": [1, 2, 3]})
+
+ result = pd.merge(df1, df2, on="key")
+ expected = pd.DataFrame(
+ {
+ "key": key.take([0, 0, 1, 2, 2]),
+ "val_x": [1, 1, 2, 3, 3],
+ "val_y": [1, 3, 2, 1, 3],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.filterwarnings(
+ "ignore:The previous implementation of stack is deprecated"
+ )
+ @pytest.mark.parametrize(
+ "columns",
+ [
+ ["A", "B"],
+ pd.MultiIndex.from_tuples(
+ [("A", "a"), ("A", "b")], names=["outer", "inner"]
+ ),
+ ],
+ )
+ @pytest.mark.parametrize("future_stack", [True, False])
+ def test_stack(self, data, columns, future_stack):
+ df = pd.DataFrame({"A": data[:5], "B": data[:5]})
+ df.columns = columns
+ result = df.stack(future_stack=future_stack)
+ expected = df.astype(object).stack(future_stack=future_stack)
+ # we need a second astype(object), in case the constructor inferred
+ # object -> specialized, as is done for period.
+ expected = expected.astype(object)
+
+ if isinstance(expected, pd.Series):
+ if future_stack and isinstance(data.dtype, NumpyEADtype):
+ # GH#58817 future_stack=True constructs the result specifying the dtype
+ # using the dtype of the input; we thus get the underlying
+ # NumPy dtype as the result instead of the NumpyExtensionArray
+ assert result.dtype == df.iloc[:, 0].to_numpy().dtype
+ else:
+ assert result.dtype == df.iloc[:, 0].dtype
+ else:
+ assert all(result.dtypes == df.iloc[:, 0].dtype)
+
+ result = result.astype(object)
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ # Two levels, uniform.
+ pd.MultiIndex.from_product(([["A", "B"], ["a", "b"]]), names=["a", "b"]),
+ # non-uniform
+ pd.MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "b")]),
+ # three levels, non-uniform
+ pd.MultiIndex.from_product([("A", "B"), ("a", "b", "c"), (0, 1, 2)]),
+ pd.MultiIndex.from_tuples(
+ [
+ ("A", "a", 1),
+ ("A", "b", 0),
+ ("A", "a", 0),
+ ("B", "a", 0),
+ ("B", "c", 1),
+ ]
+ ),
+ ],
+ )
+ @pytest.mark.parametrize("obj", ["series", "frame"])
+ def test_unstack(self, data, index, obj):
+ final_length = min(len(index), len(data))
+ index = index[:final_length]
+ data = data[:final_length]
+ if obj == "series":
+ ser = pd.Series(data, index=index)
+ else:
+ ser = pd.DataFrame({"A": data, "B": data}, index=index)
+
+ n = index.nlevels
+ levels = list(range(n))
+ # [0, 1, 2]
+ # [(0,), (1,), (2,), (0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
+ combinations = itertools.chain.from_iterable(
+ itertools.permutations(levels, i) for i in range(1, n)
+ )
+
+ for level in combinations:
+ result = ser.unstack(level=level)
+ assert all(
+ isinstance(result[col].array, type(data)) for col in result.columns
+ )
+
+ if obj == "series":
+ # We should get the same result with to_frame+unstack+droplevel
+ df = ser.to_frame()
+
+ alt = df.unstack(level=level).droplevel(0, axis=1)
+ tm.assert_frame_equal(result, alt)
+
+ obj_ser = ser.astype(object)
+
+ expected = obj_ser.unstack(level=level, fill_value=data.dtype.na_value)
+ if obj == "series":
+ assert (expected.dtypes == object).all()
+
+ result = result.astype(object)
+ tm.assert_frame_equal(result, expected)
+
+ def test_ravel(self, data):
+ # as long as EA is 1D-only, ravel is a no-op
+ result = data.ravel()
+ assert type(result) == type(data)
+
+ if data.dtype._is_immutable:
+ pytest.skip(f"test_ravel assumes mutability and {data.dtype} is immutable")
+
+ # Check that we have a view, not a copy
+ result[0] = result[1]
+ assert data[0] == data[1]
+
+ def test_transpose(self, data):
+ result = data.transpose()
+ assert type(result) == type(data)
+
+ # check we get a new object
+ assert result is not data
+
+ # If we ever _did_ support 2D, shape should be reversed
+ assert result.shape == data.shape[::-1]
+
+ if data.dtype._is_immutable:
+ pytest.skip(
+ f"test_transpose assumes mutability and {data.dtype} is immutable"
+ )
+
+ # Check that we have a view, not a copy
+ result[0] = result[1]
+ assert data[0] == data[1]
+
+ def test_transpose_frame(self, data):
+ df = pd.DataFrame({"A": data[:4], "B": data[:4]}, index=["a", "b", "c", "d"])
+ result = df.T
+ expected = pd.DataFrame(
+ {
+ "a": type(data)._from_sequence([data[0]] * 2, dtype=data.dtype),
+ "b": type(data)._from_sequence([data[1]] * 2, dtype=data.dtype),
+ "c": type(data)._from_sequence([data[2]] * 2, dtype=data.dtype),
+ "d": type(data)._from_sequence([data[3]] * 2, dtype=data.dtype),
+ },
+ index=["A", "B"],
+ )
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(np.transpose(np.transpose(df)), df)
+ tm.assert_frame_equal(np.transpose(np.transpose(df[["A"]])), df[["A"]])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/setitem.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/setitem.py
new file mode 100644
index 0000000000000000000000000000000000000000..d32749d044d2586d021767e8ccd19b769e73a20f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/base/setitem.py
@@ -0,0 +1,536 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import is_hashable
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class BaseSetitemTests:
+ @pytest.fixture(
+ params=[
+ lambda x: x.index,
+ lambda x: list(x.index),
+ lambda x: slice(None),
+ lambda x: slice(0, len(x)),
+ lambda x: range(len(x)),
+ lambda x: list(range(len(x))),
+ lambda x: np.ones(len(x), dtype=bool),
+ ],
+ ids=[
+ "index",
+ "list[index]",
+ "null_slice",
+ "full_slice",
+ "range",
+ "list(range)",
+ "mask",
+ ],
+ )
+ def full_indexer(self, request):
+ """
+ Fixture for an indexer to pass to obj.loc to get/set the full length of the
+ object.
+
+ In some cases, assumes that obj.index is the default RangeIndex.
+ """
+ return request.param
+
+ @pytest.fixture(autouse=True)
+ def skip_if_immutable(self, dtype, request):
+ if dtype._is_immutable:
+ node = request.node
+ if node.name.split("[")[0] == "test_is_immutable":
+ # This fixture is auto-used, but we want to not-skip
+ # test_is_immutable.
+ return
+
+ # When BaseSetitemTests is mixed into ExtensionTests, we only
+ # want this fixture to operate on the tests defined in this
+ # class/file.
+ defined_in = node.function.__qualname__.split(".")[0]
+ if defined_in == "BaseSetitemTests":
+ pytest.skip("__setitem__ test not applicable with immutable dtype")
+
+ def test_is_immutable(self, data):
+ if data.dtype._is_immutable:
+ with pytest.raises(TypeError):
+ data[0] = data[0]
+ else:
+ data[0] = data[1]
+ assert data[0] == data[1]
+
+ def test_setitem_scalar_series(self, data, box_in_series):
+ if box_in_series:
+ data = pd.Series(data)
+ data[0] = data[1]
+ assert data[0] == data[1]
+
+ def test_setitem_sequence(self, data, box_in_series):
+ if box_in_series:
+ data = pd.Series(data)
+ original = data.copy()
+
+ data[[0, 1]] = [data[1], data[0]]
+ assert data[0] == original[1]
+ assert data[1] == original[0]
+
+ def test_setitem_sequence_mismatched_length_raises(self, data, as_array):
+ ser = pd.Series(data)
+ original = ser.copy()
+ value = [data[0]]
+ if as_array:
+ value = data._from_sequence(value, dtype=data.dtype)
+
+ xpr = "cannot set using a {} indexer with a different length"
+ with pytest.raises(ValueError, match=xpr.format("list-like")):
+ ser[[0, 1]] = value
+ # Ensure no modifications made before the exception
+ tm.assert_series_equal(ser, original)
+
+ with pytest.raises(ValueError, match=xpr.format("slice")):
+ ser[slice(3)] = value
+ tm.assert_series_equal(ser, original)
+
+ def test_setitem_empty_indexer(self, data, box_in_series):
+ if box_in_series:
+ data = pd.Series(data)
+ original = data.copy()
+ data[np.array([], dtype=int)] = []
+ tm.assert_equal(data, original)
+
+ def test_setitem_sequence_broadcasts(self, data, box_in_series):
+ if box_in_series:
+ data = pd.Series(data)
+ data[[0, 1]] = data[2]
+ assert data[0] == data[2]
+ assert data[1] == data[2]
+
+ @pytest.mark.parametrize("setter", ["loc", "iloc"])
+ def test_setitem_scalar(self, data, setter):
+ arr = pd.Series(data)
+ setter = getattr(arr, setter)
+ setter[0] = data[1]
+ assert arr[0] == data[1]
+
+ def test_setitem_loc_scalar_mixed(self, data):
+ df = pd.DataFrame({"A": np.arange(len(data)), "B": data})
+ df.loc[0, "B"] = data[1]
+ assert df.loc[0, "B"] == data[1]
+
+ def test_setitem_loc_scalar_single(self, data):
+ df = pd.DataFrame({"B": data})
+ df.loc[10, "B"] = data[1]
+ assert df.loc[10, "B"] == data[1]
+
+ def test_setitem_loc_scalar_multiple_homogoneous(self, data):
+ df = pd.DataFrame({"A": data, "B": data})
+ df.loc[10, "B"] = data[1]
+ assert df.loc[10, "B"] == data[1]
+
+ def test_setitem_iloc_scalar_mixed(self, data):
+ df = pd.DataFrame({"A": np.arange(len(data)), "B": data})
+ df.iloc[0, 1] = data[1]
+ assert df.loc[0, "B"] == data[1]
+
+ def test_setitem_iloc_scalar_single(self, data):
+ df = pd.DataFrame({"B": data})
+ df.iloc[9, 0] = data[1]
+ assert df.loc[9, "B"] == data[1]
+
+ def test_setitem_iloc_scalar_multiple_homogoneous(self, data):
+ df = pd.DataFrame({"A": data, "B": data})
+ df.iloc[9, 1] = data[1]
+ assert df.loc[9, "B"] == data[1]
+
+ @pytest.mark.parametrize(
+ "mask",
+ [
+ np.array([True, True, True, False, False]),
+ pd.array([True, True, True, False, False], dtype="boolean"),
+ pd.array([True, True, True, pd.NA, pd.NA], dtype="boolean"),
+ ],
+ ids=["numpy-array", "boolean-array", "boolean-array-na"],
+ )
+ def test_setitem_mask(self, data, mask, box_in_series):
+ arr = data[:5].copy()
+ expected = arr.take([0, 0, 0, 3, 4])
+ if box_in_series:
+ arr = pd.Series(arr)
+ expected = pd.Series(expected)
+ arr[mask] = data[0]
+ tm.assert_equal(expected, arr)
+
+ def test_setitem_mask_raises(self, data, box_in_series):
+ # wrong length
+ mask = np.array([True, False])
+
+ if box_in_series:
+ data = pd.Series(data)
+
+ with pytest.raises(IndexError, match="wrong length"):
+ data[mask] = data[0]
+
+ mask = pd.array(mask, dtype="boolean")
+ with pytest.raises(IndexError, match="wrong length"):
+ data[mask] = data[0]
+
+ def test_setitem_mask_boolean_array_with_na(self, data, box_in_series):
+ mask = pd.array(np.zeros(data.shape, dtype="bool"), dtype="boolean")
+ mask[:3] = True
+ mask[3:5] = pd.NA
+
+ if box_in_series:
+ data = pd.Series(data)
+
+ data[mask] = data[0]
+
+ assert (data[:3] == data[0]).all()
+
+ @pytest.mark.parametrize(
+ "idx",
+ [[0, 1, 2], pd.array([0, 1, 2], dtype="Int64"), np.array([0, 1, 2])],
+ ids=["list", "integer-array", "numpy-array"],
+ )
+ def test_setitem_integer_array(self, data, idx, box_in_series):
+ arr = data[:5].copy()
+ expected = data.take([0, 0, 0, 3, 4])
+
+ if box_in_series:
+ arr = pd.Series(arr)
+ expected = pd.Series(expected)
+
+ arr[idx] = arr[0]
+ tm.assert_equal(arr, expected)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [[0, 0, 1], pd.array([0, 0, 1], dtype="Int64"), np.array([0, 0, 1])],
+ ids=["list", "integer-array", "numpy-array"],
+ )
+ def test_setitem_integer_array_with_repeats(self, data, idx, box_in_series):
+ arr = data[:5].copy()
+ expected = data.take([2, 3, 2, 3, 4])
+
+ if box_in_series:
+ arr = pd.Series(arr)
+ expected = pd.Series(expected)
+
+ arr[idx] = [arr[2], arr[2], arr[3]]
+ tm.assert_equal(arr, expected)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [
+ [0, 1, 2, pd.NA],
+ pd.array([0, 1, 2, pd.NA], dtype="Int64"),
+ ],
+ ids=["list", "integer-array"],
+ )
+ @pytest.mark.parametrize("box_in_series", [True, False])
+ def test_setitem_integer_with_missing_raises(self, data, idx, box_in_series):
+ arr = data.copy()
+
+ msg = "Cannot index with an integer indexer containing NA values"
+ err = ValueError
+
+ if box_in_series:
+ # The integer labels are not present in the (string) index, so
+ # we get KeyErrors
+ arr = pd.Series(data, index=[chr(100 + i) for i in range(len(data))])
+ msg = "0"
+ err = KeyError
+
+ with pytest.raises(err, match=msg):
+ arr[idx] = arr[0]
+
+ @pytest.mark.parametrize("as_callable", [True, False])
+ @pytest.mark.parametrize("setter", ["loc", None])
+ def test_setitem_mask_aligned(self, data, as_callable, setter):
+ ser = pd.Series(data)
+ mask = np.zeros(len(data), dtype=bool)
+ mask[:2] = True
+
+ if as_callable:
+ mask2 = lambda x: mask
+ else:
+ mask2 = mask
+
+ if setter:
+ # loc
+ target = getattr(ser, setter)
+ else:
+ # Series.__setitem__
+ target = ser
+
+ target[mask2] = data[5:7]
+
+ ser[mask2] = data[5:7]
+ assert ser[0] == data[5]
+ assert ser[1] == data[6]
+
+ @pytest.mark.parametrize("setter", ["loc", None])
+ def test_setitem_mask_broadcast(self, data, setter):
+ ser = pd.Series(data)
+ mask = np.zeros(len(data), dtype=bool)
+ mask[:2] = True
+
+ if setter: # loc
+ target = getattr(ser, setter)
+ else: # __setitem__
+ target = ser
+
+ target[mask] = data[9]
+ assert ser[0] == data[9]
+ assert ser[1] == data[9]
+
+ def test_setitem_expand_columns(self, data):
+ df = pd.DataFrame({"A": data})
+ result = df.copy()
+ result["B"] = 1
+ expected = pd.DataFrame({"A": data, "B": [1] * len(data)})
+ tm.assert_frame_equal(result, expected)
+
+ result = df.copy()
+ result.loc[:, "B"] = 1
+ tm.assert_frame_equal(result, expected)
+
+ # overwrite with new type
+ result["B"] = data
+ expected = pd.DataFrame({"A": data, "B": data})
+ tm.assert_frame_equal(result, expected)
+
+ def test_setitem_expand_with_extension(self, data):
+ df = pd.DataFrame({"A": [1] * len(data)})
+ result = df.copy()
+ result["B"] = data
+ expected = pd.DataFrame({"A": [1] * len(data), "B": data})
+ tm.assert_frame_equal(result, expected)
+
+ result = df.copy()
+ result.loc[:, "B"] = data
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_setitem_with_expansion_preserves_ea_index_dtype(self, data):
+ # GH#41626 retain index.dtype in setitem-with-expansion
+ if not is_hashable(data[0]):
+ pytest.skip("Test does not apply to non-hashable data.")
+ data = data.unique()
+ expected = pd.DataFrame({"A": range(len(data))}, index=data)
+ df = expected.iloc[:-1]
+ ser = df["A"]
+ item = data[-1]
+
+ df.loc[item] = len(data) - 1
+ tm.assert_frame_equal(df, expected)
+
+ ser.loc[item] = len(data) - 1
+ tm.assert_series_equal(ser, expected["A"])
+
+ def test_setitem_frame_invalid_length(self, data):
+ df = pd.DataFrame({"A": [1] * len(data)})
+ xpr = (
+ rf"Length of values \({len(data[:5])}\) "
+ rf"does not match length of index \({len(df)}\)"
+ )
+ with pytest.raises(ValueError, match=xpr):
+ df["B"] = data[:5]
+
+ def test_setitem_tuple_index(self, data):
+ ser = pd.Series(data[:2], index=[(0, 0), (0, 1)])
+ expected = pd.Series(data.take([1, 1]), index=ser.index)
+ ser[(0, 0)] = data[1]
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_slice(self, data, box_in_series):
+ arr = data[:5].copy()
+ expected = data.take([0, 0, 0, 3, 4])
+ if box_in_series:
+ arr = pd.Series(arr)
+ expected = pd.Series(expected)
+
+ arr[:3] = data[0]
+ tm.assert_equal(arr, expected)
+
+ def test_setitem_loc_iloc_slice(self, data):
+ arr = data[:5].copy()
+ s = pd.Series(arr, index=["a", "b", "c", "d", "e"])
+ expected = pd.Series(data.take([0, 0, 0, 3, 4]), index=s.index)
+
+ result = s.copy()
+ result.iloc[:3] = data[0]
+ tm.assert_equal(result, expected)
+
+ result = s.copy()
+ result.loc[:"c"] = data[0]
+ tm.assert_equal(result, expected)
+
+ def test_setitem_slice_mismatch_length_raises(self, data):
+ arr = data[:5]
+ with tm.external_error_raised(ValueError):
+ arr[:1] = arr[:2]
+
+ def test_setitem_slice_array(self, data):
+ arr = data[:5].copy()
+ arr[:5] = data[-5:]
+ tm.assert_extension_array_equal(arr, data[-5:])
+
+ def test_setitem_scalar_key_sequence_raise(self, data):
+ arr = data[:5].copy()
+ with tm.external_error_raised(ValueError):
+ arr[0] = arr[[0, 1]]
+
+ def test_setitem_preserves_views(self, data):
+ # GH#28150 setitem shouldn't swap the underlying data
+ view1 = data.view()
+ view2 = data[:]
+
+ data[0] = data[1]
+ assert view1[0] == data[1]
+ assert view2[0] == data[1]
+
+ def test_setitem_with_expansion_dataframe_column(self, data, full_indexer):
+ # https://github.com/pandas-dev/pandas/issues/32395
+ df = expected = pd.DataFrame(pd.Series(data))
+ result = pd.DataFrame(index=df.index)
+
+ key = full_indexer(df)
+ result.loc[key, 0] = df[0]
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_setitem_with_expansion_row(self, data, na_value):
+ df = pd.DataFrame({"data": data[:1]})
+
+ df.loc[1, "data"] = data[1]
+ expected = pd.DataFrame({"data": data[:2]})
+ tm.assert_frame_equal(df, expected)
+
+ # https://github.com/pandas-dev/pandas/issues/47284
+ df.loc[2, "data"] = na_value
+ expected = pd.DataFrame(
+ {"data": pd.Series([data[0], data[1], na_value], dtype=data.dtype)}
+ )
+ tm.assert_frame_equal(df, expected)
+
+ def test_setitem_series(self, data, full_indexer):
+ # https://github.com/pandas-dev/pandas/issues/32395
+ ser = pd.Series(data, name="data")
+ result = pd.Series(index=ser.index, dtype=object, name="data")
+
+ # because result has object dtype, the attempt to do setting inplace
+ # is successful, and object dtype is retained
+ key = full_indexer(ser)
+ result.loc[key] = ser
+
+ expected = pd.Series(
+ data.astype(object), index=ser.index, name="data", dtype=object
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_setitem_frame_2d_values(self, data):
+ # GH#44514
+ df = pd.DataFrame({"A": data})
+ orig = df.copy()
+
+ df.iloc[:] = df.copy()
+ tm.assert_frame_equal(df, orig)
+
+ df.iloc[:-1] = df.iloc[:-1].copy()
+ tm.assert_frame_equal(df, orig)
+
+ df.iloc[:] = df.values
+ tm.assert_frame_equal(df, orig)
+
+ df.iloc[:-1] = df.values[:-1]
+ tm.assert_frame_equal(df, orig)
+
+ def test_delitem_series(self, data):
+ # GH#40763
+ ser = pd.Series(data, name="data")
+
+ taker = np.arange(len(ser))
+ taker = np.delete(taker, 1)
+
+ expected = ser[taker]
+ del ser[1]
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_invalid(self, data, invalid_scalar):
+ # messages vary by subclass, so we do not test it
+ with pytest.raises((ValueError, TypeError), match=None):
+ data[0] = invalid_scalar
+
+ with pytest.raises((ValueError, TypeError), match=None):
+ data[:] = invalid_scalar
+
+ def test_setitem_2d_values(self, data):
+ # GH50085
+ original = data.copy()
+ df = pd.DataFrame({"a": data, "b": data})
+ df.loc[[0, 1], :] = df.loc[[1, 0], :].values
+ assert (df.loc[0, :] == original[1]).all()
+ assert (df.loc[1, :] == original[0]).all()
+
+ def test_readonly_property(self, data):
+ assert data._readonly is False
+
+ data._readonly = True
+ assert data._readonly is True
+
+ data_orig = data.copy()
+ assert data_orig._readonly is False
+
+ with pytest.raises(ValueError, match="Cannot modify read-only array"):
+ data[0] = data[1]
+
+ with pytest.raises(ValueError, match="Cannot modify read-only array"):
+ data[0:3] = data[1]
+
+ with pytest.raises(ValueError, match="Cannot modify read-only array"):
+ data[np.array([True] * len(data))] = data[1]
+
+ tm.assert_extension_array_equal(data, data_orig)
+
+ def test_readonly_propagates_to_numpy_array(self, data):
+ data._readonly = True
+
+ # when we ask for a copy, the result should never be readonly
+ arr = np.array(data)
+ assert arr.flags.writeable
+
+ # when we don't ask for a copy -> if the conversion is zero-copy,
+ # the result should be readonly
+ arr1 = np.asarray(data)
+ arr2 = np.asarray(data)
+ if np.shares_memory(arr1, arr2):
+ assert not arr1.flags.writeable
+ else:
+ assert arr1.flags.writeable
+
+ def test_readonly_propagates_to_numpy_array_method(self, data):
+ data._readonly = True
+
+ # when we ask for a copy, the result should never be readonly
+ arr = data.to_numpy(copy=True)
+ assert arr.flags.writeable
+
+ # when we don't ask for a copy -> if the conversion is zero-copy,
+ # the result should be readonly
+ arr1 = data.to_numpy(copy=False)
+ arr2 = data.to_numpy(copy=False)
+ if np.shares_memory(arr1, arr2):
+ assert not arr1.flags.writeable
+ else:
+ assert arr1.flags.writeable
+
+ # non-NA fill value should always result in a copy
+ if data.isna().any():
+ arr = data.to_numpy(copy=False, na_value=data[0])
+ if isinstance(data.dtype, pd.ArrowDtype) and data.dtype.kind == "f":
+ # for float dtype, after the fillna, the conversion from pyarrow to
+ # numpy is zero-copy, and pyarrow will mark the array as readonly
+ assert not arr.flags.writeable
+ else:
+ assert arr.flags.writeable
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..1376af5e51a6b6255625c257031e74a33d7b7b77
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/conftest.py
@@ -0,0 +1,214 @@
+import operator
+
+import pytest
+
+from pandas import Series
+
+
+@pytest.fixture
+def dtype():
+ """A fixture providing the ExtensionDtype to validate."""
+ raise NotImplementedError
+
+
+@pytest.fixture
+def data():
+ """
+ Length-10 array for this type.
+
+ * data[0] and data[1] should both be non missing
+ * data[0] and data[1] should not be equal
+ """
+ raise NotImplementedError
+
+
+@pytest.fixture
+def data_for_twos(dtype):
+ """
+ Length-10 array in which all the elements are two.
+
+ Call pytest.skip in your fixture if the dtype does not support divmod.
+ """
+ if not (dtype._is_numeric or dtype.kind == "m"):
+ # Object-dtypes may want to allow this, but for the most part
+ # only numeric and timedelta-like dtypes will need to implement this.
+ pytest.skip(f"{dtype} is not a numeric dtype")
+
+ raise NotImplementedError
+
+
+@pytest.fixture
+def data_missing():
+ """Length-2 array with [NA, Valid]"""
+ raise NotImplementedError
+
+
+@pytest.fixture(params=["data", "data_missing"])
+def all_data(request, data, data_missing):
+ """Parametrized fixture giving 'data' and 'data_missing'"""
+ if request.param == "data":
+ return data
+ elif request.param == "data_missing":
+ return data_missing
+
+
+@pytest.fixture
+def data_repeated(data):
+ """
+ Generate many datasets.
+
+ Parameters
+ ----------
+ data : fixture implementing `data`
+
+ Returns
+ -------
+ Callable[[int], Generator]:
+ A callable that takes a `count` argument and
+ returns a generator yielding `count` datasets.
+ """
+
+ def gen(count):
+ for _ in range(count):
+ yield data
+
+ return gen
+
+
+@pytest.fixture
+def data_for_sorting():
+ """
+ Length-3 array with a known sort order.
+
+ This should be three items [B, C, A] with
+ A < B < C
+
+ For boolean dtypes (for which there are only 2 values available),
+ set B=C=True
+ """
+ raise NotImplementedError
+
+
+@pytest.fixture
+def data_missing_for_sorting():
+ """
+ Length-3 array with a known sort order.
+
+ This should be three items [B, NA, A] with
+ A < B and NA missing.
+ """
+ raise NotImplementedError
+
+
+@pytest.fixture
+def na_cmp():
+ """
+ Binary operator for comparing NA values.
+
+ Should return a function of two arguments that returns
+ True if both arguments are (scalar) NA for your type.
+
+ By default, uses ``operator.is_``
+ """
+ return operator.is_
+
+
+@pytest.fixture
+def na_value(dtype):
+ """
+ The scalar missing value for this type. Default dtype.na_value.
+
+ TODO: can be removed in 3.x (see https://github.com/pandas-dev/pandas/pull/54930)
+ """
+ return dtype.na_value
+
+
+@pytest.fixture
+def data_for_grouping():
+ """
+ Data for factorization, grouping, and unique tests.
+
+ Expected to be like [B, B, NA, NA, A, A, B, C]
+
+ Where A < B < C and NA is missing.
+
+ If a dtype has _is_boolean = True, i.e. only 2 unique non-NA entries,
+ then set C=B.
+ """
+ raise NotImplementedError
+
+
+@pytest.fixture(params=[True, False])
+def box_in_series(request):
+ """Whether to box the data in a Series"""
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ lambda x: 1,
+ lambda x: [1] * len(x),
+ lambda x: Series([1] * len(x)),
+ lambda x: x,
+ ],
+ ids=["scalar", "list", "series", "object"],
+)
+def groupby_apply_op(request):
+ """
+ Functions to test groupby.apply().
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def as_frame(request):
+ """
+ Boolean fixture to support Series and Series.to_frame() comparison testing.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def as_series(request):
+ """
+ Boolean fixture to support arr and Series(arr) comparison testing.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def use_numpy(request):
+ """
+ Boolean fixture to support comparison testing of ExtensionDtype array
+ and numpy array.
+ """
+ return request.param
+
+
+@pytest.fixture(params=["ffill", "bfill"])
+def fillna_method(request):
+ """
+ Parametrized fixture giving method parameters 'ffill' and 'bfill' for
+ Series. testing.
+ """
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def as_array(request):
+ """
+ Boolean fixture to support ExtensionDtype _from_sequence method testing.
+ """
+ return request.param
+
+
+@pytest.fixture
+def invalid_scalar(data):
+ """
+ A scalar that *cannot* be held by this ExtensionArray.
+
+ The default should work for most subclasses, but is not guaranteed.
+
+ If the array can hold any item (i.e. object dtype), then use pytest.skip.
+ """
+ return object.__new__(object)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/date/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/date/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a8c7e9f57a5da982530b8db854edd37baf13b6b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/date/__init__.py
@@ -0,0 +1,6 @@
+from pandas.tests.extension.date.array import (
+ DateArray,
+ DateDtype,
+)
+
+__all__ = ["DateArray", "DateDtype"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/date/array.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/date/array.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c844622b1d73fe727abbf04a6653d8f69f6540b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/date/array.py
@@ -0,0 +1,193 @@
+from __future__ import annotations
+
+import datetime as dt
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ cast,
+)
+
+import numpy as np
+
+from pandas.core.dtypes.dtypes import register_extension_dtype
+
+from pandas.api.extensions import (
+ ExtensionArray,
+ ExtensionDtype,
+)
+from pandas.api.types import pandas_dtype
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+
+ from pandas._typing import (
+ Dtype,
+ PositionalIndexer,
+ )
+
+
+@register_extension_dtype
+class DateDtype(ExtensionDtype):
+ @property
+ def type(self):
+ return dt.date
+
+ @property
+ def name(self):
+ return "DateDtype"
+
+ @classmethod
+ def construct_from_string(cls, string: str):
+ if not isinstance(string, str):
+ raise TypeError(
+ f"'construct_from_string' expects a string, got {type(string)}"
+ )
+
+ if string == cls.__name__:
+ return cls()
+ else:
+ raise TypeError(f"Cannot construct a '{cls.__name__}' from '{string}'")
+
+ def construct_array_type(self):
+ return DateArray
+
+ @property
+ def na_value(self):
+ return dt.date.min
+
+ def __repr__(self) -> str:
+ return self.name
+
+
+class DateArray(ExtensionArray):
+ def __init__(
+ self,
+ dates: (
+ dt.date
+ | Sequence[dt.date]
+ | tuple[np.ndarray, np.ndarray, np.ndarray]
+ | np.ndarray
+ ),
+ ) -> None:
+ if isinstance(dates, dt.date):
+ self._year = np.array([dates.year])
+ self._month = np.array([dates.month])
+ self._day = np.array([dates.year])
+ return
+
+ ldates = len(dates)
+ if isinstance(dates, list):
+ # pre-allocate the arrays since we know the size before hand
+ self._year = np.zeros(ldates, dtype=np.uint16) # 65535 (0, 9999)
+ self._month = np.zeros(ldates, dtype=np.uint8) # 255 (1, 31)
+ self._day = np.zeros(ldates, dtype=np.uint8) # 255 (1, 12)
+ # populate them
+ for i, (y, m, d) in enumerate(
+ (date.year, date.month, date.day) for date in dates
+ ):
+ self._year[i] = y
+ self._month[i] = m
+ self._day[i] = d
+
+ elif isinstance(dates, tuple):
+ # only support triples
+ if ldates != 3:
+ raise ValueError("only triples are valid")
+ # check if all elements have the same type
+ if any(not isinstance(x, np.ndarray) for x in dates):
+ raise TypeError("invalid type")
+ ly, lm, ld = (len(cast(np.ndarray, d)) for d in dates)
+ if not ly == lm == ld:
+ raise ValueError(
+ f"tuple members must have the same length: {(ly, lm, ld)}"
+ )
+ self._year = dates[0].astype(np.uint16)
+ self._month = dates[1].astype(np.uint8)
+ self._day = dates[2].astype(np.uint8)
+
+ elif isinstance(dates, np.ndarray) and dates.dtype == "U10":
+ self._year = np.zeros(ldates, dtype=np.uint16) # 65535 (0, 9999)
+ self._month = np.zeros(ldates, dtype=np.uint8) # 255 (1, 31)
+ self._day = np.zeros(ldates, dtype=np.uint8) # 255 (1, 12)
+
+ # error: "object_" object is not iterable
+ obj = np.char.split(dates, sep="-")
+ for (i,), (y, m, d) in np.ndenumerate(obj):
+ self._year[i] = int(y)
+ self._month[i] = int(m)
+ self._day[i] = int(d)
+
+ else:
+ raise TypeError(f"{type(dates)} is not supported")
+
+ @property
+ def dtype(self) -> ExtensionDtype:
+ return DateDtype()
+
+ def astype(self, dtype, copy=True):
+ dtype = pandas_dtype(dtype)
+
+ if isinstance(dtype, DateDtype):
+ data = self.copy() if copy else self
+ else:
+ data = self.to_numpy(dtype=dtype, copy=copy, na_value=dt.date.min)
+
+ return data
+
+ @property
+ def nbytes(self) -> int:
+ return self._year.nbytes + self._month.nbytes + self._day.nbytes
+
+ def __len__(self) -> int:
+ return len(self._year) # all 3 arrays are enforced to have the same length
+
+ def __getitem__(self, item: PositionalIndexer):
+ if isinstance(item, int):
+ return dt.date(self._year[item], self._month[item], self._day[item])
+ else:
+ raise NotImplementedError("only ints are supported as indexes")
+
+ def __setitem__(self, key: int | slice | np.ndarray, value: Any) -> None:
+ if self._readonly:
+ raise ValueError("Cannot modify read-only array")
+
+ if not isinstance(key, int):
+ raise NotImplementedError("only ints are supported as indexes")
+
+ if not isinstance(value, dt.date):
+ raise TypeError("you can only set datetime.date types")
+
+ self._year[key] = value.year
+ self._month[key] = value.month
+ self._day[key] = value.day
+
+ def __repr__(self) -> str:
+ return f"DateArray{list(zip(self._year, self._month, self._day, strict=True))}"
+
+ def copy(self) -> DateArray:
+ return DateArray((self._year.copy(), self._month.copy(), self._day.copy()))
+
+ def view(self, dtype: Dtype | None = None) -> DateArray:
+ return DateArray((self._year.view(), self._month.view(), self._day.view()))
+
+ def isna(self) -> np.ndarray:
+ return np.logical_and(
+ np.logical_and(
+ self._year == dt.date.min.year, self._month == dt.date.min.month
+ ),
+ self._day == dt.date.min.day,
+ )
+
+ @classmethod
+ def _from_sequence(cls, scalars, *, dtype: Dtype | None = None, copy=False):
+ if isinstance(scalars, dt.date):
+ raise TypeError
+ elif isinstance(scalars, DateArray):
+ if dtype is not None:
+ return scalars.astype(dtype, copy=copy)
+ if copy:
+ return scalars.copy()
+ return scalars[:]
+ elif isinstance(scalars, np.ndarray):
+ scalars = scalars.astype("U10") # 10 chars for yyyy-mm-dd
+ return DateArray(scalars)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/decimal/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/decimal/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..47b1c7c57a47a5c59c5711cf8ca7de1a65013103
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/decimal/__init__.py
@@ -0,0 +1,8 @@
+from pandas.tests.extension.decimal.array import (
+ DecimalArray,
+ DecimalDtype,
+ make_data,
+ to_decimal,
+)
+
+__all__ = ["DecimalArray", "DecimalDtype", "make_data", "to_decimal"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/decimal/array.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/decimal/array.py
new file mode 100644
index 0000000000000000000000000000000000000000..250d2723096c43aa4cb82047a01a7a9f720ba46d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/decimal/array.py
@@ -0,0 +1,315 @@
+from __future__ import annotations
+
+import decimal
+import numbers
+import sys
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from pandas.core.dtypes.base import ExtensionDtype
+from pandas.core.dtypes.common import (
+ is_dtype_equal,
+ is_float,
+ is_integer,
+ pandas_dtype,
+)
+
+import pandas as pd
+from pandas.api.extensions import (
+ no_default,
+ register_extension_dtype,
+)
+from pandas.api.types import (
+ is_list_like,
+ is_scalar,
+)
+from pandas.core import arraylike
+from pandas.core.arraylike import OpsMixin
+from pandas.core.arrays import (
+ ExtensionArray,
+ ExtensionScalarOpsMixin,
+)
+from pandas.core.indexers import (
+ check_array_indexer,
+ getitem_returns_view,
+)
+
+if TYPE_CHECKING:
+ from pandas._typing import type_t
+
+
+@register_extension_dtype
+class DecimalDtype(ExtensionDtype):
+ type = decimal.Decimal
+ name = "decimal"
+ na_value = decimal.Decimal("NaN")
+ _metadata = ("context",)
+
+ def __init__(self, context=None) -> None:
+ self.context = context or decimal.getcontext()
+
+ def __repr__(self) -> str:
+ return f"DecimalDtype(context={self.context})"
+
+ def construct_array_type(self) -> type_t[DecimalArray]:
+ """
+ Return the array type associated with this dtype.
+
+ Returns
+ -------
+ type
+ """
+ return DecimalArray
+
+ @property
+ def _is_numeric(self) -> bool:
+ return True
+
+
+class DecimalArray(OpsMixin, ExtensionScalarOpsMixin, ExtensionArray):
+ __array_priority__ = 1000
+
+ def __init__(self, values, dtype=None, copy=False, context=None) -> None:
+ for i, val in enumerate(values):
+ if is_float(val) or is_integer(val):
+ if np.isnan(val):
+ values[i] = DecimalDtype.na_value
+ else:
+ # error: Argument 1 has incompatible type "float | int |
+ # integer[Any]"; expected "Decimal | float | str | tuple[int,
+ # Sequence[int], int]"
+ values[i] = DecimalDtype.type(val) # type: ignore[arg-type]
+ elif not isinstance(val, decimal.Decimal):
+ raise TypeError("All values must be of type " + str(decimal.Decimal))
+ values = np.asarray(values, dtype=object)
+
+ self._data = values
+ # Some aliases for common attribute names to ensure pandas supports
+ # these
+ self._items = self.data = self._data
+ # those aliases are currently not working due to assumptions
+ # in internal code (GH-20735)
+ # self._values = self.values = self.data
+ self._dtype = DecimalDtype(context)
+
+ @property
+ def dtype(self):
+ return self._dtype
+
+ @classmethod
+ def _from_sequence(cls, scalars, *, dtype=None, copy=False):
+ return cls(scalars)
+
+ @classmethod
+ def _from_sequence_of_strings(cls, strings, *, dtype: ExtensionDtype, copy=False):
+ return cls._from_sequence(
+ [decimal.Decimal(x) for x in strings], dtype=dtype, copy=copy
+ )
+
+ @classmethod
+ def _from_factorized(cls, values, original):
+ return cls(values)
+
+ # test to ensure that the base class _cast_pointwise_result works as expected
+ # def _cast_pointwise_result(self, values):
+ # try:
+ # # If this were ever made a non-test EA, special-casing could
+ # # be avoided by handling Decimal in maybe_convert_objects
+ # res = type(self)._from_sequence(values, dtype=self.dtype)
+ # except (ValueError, TypeError):
+ # return values
+ # return res
+
+ _HANDLED_TYPES = (decimal.Decimal, numbers.Number, np.ndarray)
+
+ def to_numpy(
+ self,
+ dtype=None,
+ copy: bool = False,
+ na_value: object = no_default,
+ decimals=None,
+ ) -> np.ndarray:
+ result = np.asarray(self, dtype=dtype)
+ if decimals is not None:
+ result = np.asarray([round(x, decimals) for x in result])
+ return result
+
+ def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs, **kwargs):
+ if not all(isinstance(t, (*self._HANDLED_TYPES, DecimalArray)) for t in inputs):
+ return NotImplemented
+
+ result = arraylike.maybe_dispatch_ufunc_to_dunder_op(
+ self, ufunc, method, *inputs, **kwargs
+ )
+ if result is not NotImplemented:
+ # e.g. test_array_ufunc_series_scalar_other
+ return result
+
+ if "out" in kwargs:
+ return arraylike.dispatch_ufunc_with_out(
+ self, ufunc, method, *inputs, **kwargs
+ )
+
+ inputs = tuple(x._data if isinstance(x, DecimalArray) else x for x in inputs)
+ result = getattr(ufunc, method)(*inputs, **kwargs)
+
+ if method == "reduce":
+ result = arraylike.dispatch_reduction_ufunc(
+ self, ufunc, method, *inputs, **kwargs
+ )
+ if result is not NotImplemented:
+ return result
+
+ def reconstruct(x):
+ if isinstance(x, (decimal.Decimal, numbers.Number)):
+ return x
+ else:
+ return type(self)._from_sequence(x, dtype=self.dtype)
+
+ if ufunc.nout > 1:
+ return tuple(reconstruct(x) for x in result)
+ else:
+ return reconstruct(result)
+
+ def __getitem__(self, item):
+ if isinstance(item, numbers.Integral):
+ return self._data[item]
+ else:
+ # array, slice.
+ item = pd.api.indexers.check_array_indexer(self, item)
+ result = type(self)(self._data[item], context=self.dtype.context)
+ if getitem_returns_view(self, item):
+ result._readonly = self._readonly
+ return result
+
+ def take(self, indexer, allow_fill=False, fill_value=None):
+ from pandas.api.extensions import take
+
+ data = self._data
+ if allow_fill and fill_value is None:
+ fill_value = self.dtype.na_value
+
+ result = take(data, indexer, fill_value=fill_value, allow_fill=allow_fill)
+ return self._from_sequence(result, dtype=self.dtype)
+
+ def copy(self):
+ return type(self)(self._data.copy(), dtype=self.dtype)
+
+ def astype(self, dtype, copy=True):
+ if is_dtype_equal(dtype, self._dtype):
+ if not copy:
+ return self
+ dtype = pandas_dtype(dtype)
+ if isinstance(dtype, type(self.dtype)):
+ return type(self)(self._data, copy=copy, context=dtype.context)
+
+ return super().astype(dtype, copy=copy)
+
+ def __setitem__(self, key, value) -> None:
+ if self._readonly:
+ raise ValueError("Cannot modify read-only array")
+
+ if is_list_like(value):
+ if is_scalar(key):
+ raise ValueError("setting an array element with a sequence.")
+ value = [decimal.Decimal(v) for v in value]
+ else:
+ value = decimal.Decimal(value)
+
+ key = check_array_indexer(self, key)
+ self._data[key] = value
+
+ def __len__(self) -> int:
+ return len(self._data)
+
+ def __contains__(self, item) -> bool | np.bool_:
+ if not isinstance(item, decimal.Decimal):
+ return False
+ elif item.is_nan():
+ return self.isna().any()
+ else:
+ return super().__contains__(item)
+
+ @property
+ def nbytes(self) -> int:
+ n = len(self)
+ if n:
+ return n * sys.getsizeof(self[0])
+ return 0
+
+ def isna(self):
+ return np.array([x.is_nan() for x in self._data], dtype=bool)
+
+ @property
+ def _na_value(self):
+ return decimal.Decimal("NaN")
+
+ def _formatter(self, boxed=False):
+ if boxed:
+ return "Decimal: {}".format
+ return repr
+
+ @classmethod
+ def _concat_same_type(cls, to_concat):
+ return cls(np.concatenate([x._data for x in to_concat]))
+
+ def _reduce(
+ self, name: str, *, skipna: bool = True, keepdims: bool = False, **kwargs
+ ):
+ if skipna and self.isna().any():
+ # If we don't have any NAs, we can ignore skipna
+ other = self[~self.isna()]
+ result = other._reduce(name, **kwargs)
+ elif name == "sum" and len(self) == 0:
+ # GH#29630 avoid returning int 0 or np.bool_(False) on old numpy
+ result = decimal.Decimal(0)
+ else:
+ try:
+ op = getattr(self.data, name)
+ except AttributeError as err:
+ raise NotImplementedError(
+ f"decimal does not support the {name} operation"
+ ) from err
+ result = op(axis=0)
+
+ if keepdims:
+ return type(self)([result])
+ else:
+ return result
+
+ def _cmp_method(self, other, op):
+ # For use with OpsMixin
+ def convert_values(param):
+ if isinstance(param, ExtensionArray) or is_list_like(param):
+ ovalues = param
+ else:
+ # Assume it's an object
+ ovalues = [param] * len(self)
+ return ovalues
+
+ lvalues = self
+ rvalues = convert_values(other)
+
+ # If the operator is not defined for the underlying objects,
+ # a TypeError should be raised
+ res = [op(a, b) for (a, b) in zip(lvalues, rvalues, strict=True)]
+
+ return np.asarray(res, dtype=bool)
+
+ # We override fillna here to simulate a 3rd party EA that has done so. This
+ # lets us test a 3rd-party EA that has not yet updated to include a "copy"
+ # keyword in its fillna method.
+ def fillna(self, value=None, limit=None):
+ return super().fillna(value=value, limit=limit, copy=True)
+
+
+def to_decimal(values, context=None):
+ return DecimalArray([decimal.Decimal(x) for x in values], context=context)
+
+
+def make_data(n: int):
+ return [decimal.Decimal(val) for val in np.random.default_rng(2).random(n)]
+
+
+DecimalArray._add_arithmetic_ops()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/decimal/test_decimal.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/decimal/test_decimal.py
new file mode 100644
index 0000000000000000000000000000000000000000..5374121a37f63bccd5a0a2e9a17243b69f06f633
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/decimal/test_decimal.py
@@ -0,0 +1,438 @@
+from __future__ import annotations
+
+import decimal
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.tests.extension import base
+from pandas.tests.extension.decimal.array import (
+ DecimalArray,
+ DecimalDtype,
+ make_data,
+ to_decimal,
+)
+
+
+@pytest.fixture
+def dtype():
+ return DecimalDtype()
+
+
+@pytest.fixture
+def data():
+ return DecimalArray(make_data(10))
+
+
+@pytest.fixture
+def data_for_twos():
+ return DecimalArray([decimal.Decimal(2) for _ in range(10)])
+
+
+@pytest.fixture
+def data_missing():
+ return DecimalArray([decimal.Decimal("NaN"), decimal.Decimal(1)])
+
+
+@pytest.fixture
+def data_for_sorting():
+ return DecimalArray(
+ [decimal.Decimal("1"), decimal.Decimal("2"), decimal.Decimal("0")]
+ )
+
+
+@pytest.fixture
+def data_missing_for_sorting():
+ return DecimalArray(
+ [decimal.Decimal("1"), decimal.Decimal("NaN"), decimal.Decimal("0")]
+ )
+
+
+@pytest.fixture
+def na_cmp():
+ return lambda x, y: x.is_nan() and y.is_nan()
+
+
+@pytest.fixture
+def data_for_grouping():
+ b = decimal.Decimal("1.0")
+ a = decimal.Decimal("0.0")
+ c = decimal.Decimal("2.0")
+ na = decimal.Decimal("NaN")
+ return DecimalArray([b, b, na, na, a, a, b, c])
+
+
+class TestDecimalArray(base.ExtensionTests):
+ def _get_expected_exception(
+ self, op_name: str, obj, other
+ ) -> type[Exception] | tuple[type[Exception], ...] | None:
+ return None
+
+ def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool:
+ if op_name in ["kurt", "sem"]:
+ return False
+ return True
+
+ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool):
+ if op_name == "count":
+ return super().check_reduce(ser, op_name, skipna)
+ else:
+ result = getattr(ser, op_name)(skipna=skipna)
+ expected = getattr(np.asarray(ser), op_name)()
+ tm.assert_almost_equal(result, expected)
+
+ def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna, request):
+ if all_numeric_reductions in ["kurt", "skew", "sem", "median"]:
+ mark = pytest.mark.xfail(raises=NotImplementedError)
+ request.applymarker(mark)
+ super().test_reduce_series_numeric(data, all_numeric_reductions, skipna)
+
+ def test_reduce_frame(self, data, all_numeric_reductions, skipna, request):
+ op_name = all_numeric_reductions
+ if op_name in ["skew", "median"]:
+ mark = pytest.mark.xfail(raises=NotImplementedError)
+ request.applymarker(mark)
+
+ return super().test_reduce_frame(data, all_numeric_reductions, skipna)
+
+ def test_compare_scalar(self, data, comparison_op):
+ ser = pd.Series(data)
+ self._compare_other(ser, data, comparison_op, 0.5)
+
+ def test_compare_array(self, data, comparison_op):
+ ser = pd.Series(data)
+
+ alter = np.random.default_rng(2).choice([-1, 0, 1], len(data))
+ # Randomly double, halve or keep same value
+ other = pd.Series(data) * [decimal.Decimal(pow(2.0, i)) for i in alter]
+ self._compare_other(ser, data, comparison_op, other)
+
+ def test_arith_series_with_array(self, data, all_arithmetic_operators):
+ op_name = all_arithmetic_operators
+ ser = pd.Series(data)
+
+ context = decimal.getcontext()
+ divbyzerotrap = context.traps[decimal.DivisionByZero]
+ invalidoptrap = context.traps[decimal.InvalidOperation]
+ context.traps[decimal.DivisionByZero] = 0
+ context.traps[decimal.InvalidOperation] = 0
+
+ # Decimal supports ops with int, but not float
+ other = pd.Series([int(d * 100) for d in data])
+ self.check_opname(ser, op_name, other)
+
+ if "mod" not in op_name:
+ self.check_opname(ser, op_name, ser * 2)
+
+ self.check_opname(ser, op_name, 0)
+ self.check_opname(ser, op_name, 5)
+ context.traps[decimal.DivisionByZero] = divbyzerotrap
+ context.traps[decimal.InvalidOperation] = invalidoptrap
+
+ def test_fillna_frame(self, data_missing):
+ msg = "ExtensionArray.fillna added a 'copy' keyword"
+ with tm.assert_produces_warning(
+ Pandas4Warning, match=msg, check_stacklevel=False
+ ):
+ super().test_fillna_frame(data_missing)
+
+ def test_fillna_series(self, data_missing):
+ msg = "ExtensionArray.fillna added a 'copy' keyword"
+ with tm.assert_produces_warning(
+ DeprecationWarning, match=msg, check_stacklevel=False
+ ):
+ super().test_fillna_series(data_missing)
+
+ def test_fillna_with_none(self, data_missing):
+ # GH#57723
+ # EAs that don't have special logic for None will raise, unlike pandas'
+ # which interpret None as the NA value for the dtype.
+ msg = "conversion from NoneType to Decimal is not supported"
+ with pytest.raises(TypeError, match=msg):
+ super().test_fillna_with_none(data_missing)
+
+ def test_fillna_limit_frame(self, data_missing):
+ # GH#58001
+ msg = "ExtensionArray.fillna added a 'copy' keyword"
+ with tm.assert_produces_warning(
+ DeprecationWarning, match=msg, check_stacklevel=False
+ ):
+ super().test_fillna_limit_frame(data_missing)
+
+ def test_fillna_limit_series(self, data_missing):
+ # GH#58001
+ msg = "ExtensionArray.fillna added a 'copy' keyword"
+ with tm.assert_produces_warning(
+ DeprecationWarning, match=msg, check_stacklevel=False
+ ):
+ super().test_fillna_limit_series(data_missing)
+
+ @pytest.mark.xfail(reason="copy keyword is missing")
+ def test_fillna_readonly(self, data_missing):
+ super().test_fillna_readonly(data_missing)
+
+ def test_series_repr(self, data):
+ # Overriding this base test to explicitly test that
+ # the custom _formatter is used
+ ser = pd.Series(data)
+ assert data.dtype.name in repr(ser)
+ assert "Decimal: " in repr(ser)
+
+ @pytest.mark.xfail(reason="Inconsistent array-vs-scalar behavior")
+ @pytest.mark.parametrize("ufunc", [np.positive, np.negative, np.abs])
+ def test_unary_ufunc_dunder_equivalence(self, data, ufunc):
+ super().test_unary_ufunc_dunder_equivalence(data, ufunc)
+
+
+def test_take_na_value_other_decimal():
+ arr = DecimalArray([decimal.Decimal("1.0"), decimal.Decimal("2.0")])
+ result = arr.take([0, -1], allow_fill=True, fill_value=decimal.Decimal("-1.0"))
+ expected = DecimalArray([decimal.Decimal("1.0"), decimal.Decimal("-1.0")])
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_series_constructor_coerce_data_to_extension_dtype():
+ dtype = DecimalDtype()
+ ser = pd.Series([0, 1, 2], dtype=dtype)
+
+ arr = DecimalArray(
+ [decimal.Decimal(0), decimal.Decimal(1), decimal.Decimal(2)],
+ dtype=dtype,
+ )
+ exp = pd.Series(arr)
+ tm.assert_series_equal(ser, exp)
+
+
+def test_series_constructor_with_dtype():
+ arr = DecimalArray([decimal.Decimal("10.0")])
+ result = pd.Series(arr, dtype=DecimalDtype())
+ expected = pd.Series(arr)
+ tm.assert_series_equal(result, expected)
+
+ result = pd.Series(arr, dtype="int64")
+ expected = pd.Series([10])
+ tm.assert_series_equal(result, expected)
+
+
+def test_dataframe_constructor_with_dtype():
+ arr = DecimalArray([decimal.Decimal("10.0")])
+
+ result = pd.DataFrame({"A": arr}, dtype=DecimalDtype())
+ expected = pd.DataFrame({"A": arr})
+ tm.assert_frame_equal(result, expected)
+
+ arr = DecimalArray([decimal.Decimal("10.0")])
+ result = pd.DataFrame({"A": arr}, dtype="int64")
+ expected = pd.DataFrame({"A": [10]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_astype_dispatches(frame_or_series):
+ # This is a dtype-specific test that ensures Series[decimal].astype
+ # gets all the way through to ExtensionArray.astype
+ # Designing a reliable smoke test that works for arbitrary data types
+ # is difficult.
+ data = pd.Series(DecimalArray([decimal.Decimal(2)]), name="a")
+ ctx = decimal.Context()
+ ctx.prec = 5
+
+ data = frame_or_series(data)
+
+ result = data.astype(DecimalDtype(ctx))
+
+ if frame_or_series is pd.DataFrame:
+ result = result["a"]
+
+ assert result.dtype.context.prec == ctx.prec
+
+
+class DecimalArrayWithoutFromSequence(DecimalArray):
+ """Helper class for testing error handling in _from_sequence."""
+
+ @classmethod
+ def _from_sequence(cls, scalars, *, dtype=None, copy=False):
+ raise KeyError("For the test")
+
+
+class DecimalArrayWithoutCoercion(DecimalArrayWithoutFromSequence):
+ @classmethod
+ def _create_arithmetic_method(cls, op):
+ return cls._create_method(op, coerce_to_dtype=False)
+
+
+DecimalArrayWithoutCoercion._add_arithmetic_ops()
+
+
+def test_scalar_ops_from_sequence_raises():
+ # op(EA, EA) should return an EA, or an ndarray if it's not possible
+ # to return an EA with the return values.
+ arr = DecimalArrayWithoutCoercion([decimal.Decimal("1.0"), decimal.Decimal("2.0")])
+ result = arr + arr
+ expected = np.array(
+ [decimal.Decimal("2.0"), decimal.Decimal("4.0")], dtype="object"
+ )
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "reverse, expected_div, expected_mod",
+ [(False, [0, 1, 1, 2], [1, 0, 1, 0]), (True, [2, 1, 0, 0], [0, 0, 2, 2])],
+)
+def test_divmod_array(reverse, expected_div, expected_mod):
+ # https://github.com/pandas-dev/pandas/issues/22930
+ arr = to_decimal([1, 2, 3, 4])
+ if reverse:
+ div, mod = divmod(2, arr)
+ else:
+ div, mod = divmod(arr, 2)
+ expected_div = to_decimal(expected_div)
+ expected_mod = to_decimal(expected_mod)
+
+ tm.assert_extension_array_equal(div, expected_div)
+ tm.assert_extension_array_equal(mod, expected_mod)
+
+
+def test_ufunc_fallback(data):
+ a = data[:5]
+ s = pd.Series(a, index=range(3, 8))
+ result = np.abs(s)
+ expected = pd.Series(np.abs(a), index=range(3, 8))
+ tm.assert_series_equal(result, expected)
+
+
+def test_array_ufunc():
+ a = to_decimal([1, 2, 3])
+ result = np.exp(a)
+ expected = to_decimal(np.exp(a._data))
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_array_ufunc_series():
+ a = to_decimal([1, 2, 3])
+ s = pd.Series(a)
+ result = np.exp(s)
+ expected = pd.Series(to_decimal(np.exp(a._data)))
+ tm.assert_series_equal(result, expected)
+
+
+def test_array_ufunc_series_scalar_other():
+ # check _HANDLED_TYPES
+ a = to_decimal([1, 2, 3])
+ s = pd.Series(a)
+ result = np.add(s, decimal.Decimal(1))
+ expected = pd.Series(np.add(a, decimal.Decimal(1)))
+ tm.assert_series_equal(result, expected)
+
+
+def test_array_ufunc_series_defer():
+ a = to_decimal([1, 2, 3])
+ s = pd.Series(a)
+
+ expected = pd.Series(to_decimal([2, 4, 6]))
+ r1 = np.add(s, a)
+ r2 = np.add(a, s)
+
+ tm.assert_series_equal(r1, expected)
+ tm.assert_series_equal(r2, expected)
+
+
+def test_groupby_agg():
+ # Ensure that the result of agg is inferred to be decimal dtype
+ # https://github.com/pandas-dev/pandas/issues/29141
+
+ data = make_data(5)
+ df = pd.DataFrame(
+ {"id1": [0, 0, 0, 1, 1], "id2": [0, 1, 0, 1, 1], "decimals": DecimalArray(data)}
+ )
+
+ # single key, selected column
+ expected = pd.Series(to_decimal([data[0], data[3]]))
+ result = df.groupby("id1")["decimals"].agg(lambda x: x.iloc[0])
+ tm.assert_series_equal(result, expected, check_names=False)
+ result = df["decimals"].groupby(df["id1"]).agg(lambda x: x.iloc[0])
+ tm.assert_series_equal(result, expected, check_names=False)
+
+ # multiple keys, selected column
+ expected = pd.Series(
+ to_decimal([data[0], data[1], data[3]]),
+ index=pd.MultiIndex.from_tuples([(0, 0), (0, 1), (1, 1)]),
+ )
+ result = df.groupby(["id1", "id2"])["decimals"].agg(lambda x: x.iloc[0])
+ tm.assert_series_equal(result, expected, check_names=False)
+ result = df["decimals"].groupby([df["id1"], df["id2"]]).agg(lambda x: x.iloc[0])
+ tm.assert_series_equal(result, expected, check_names=False)
+
+ # multiple columns
+ expected = pd.DataFrame({"id2": [0, 1], "decimals": to_decimal([data[0], data[3]])})
+ result = df.groupby("id1").agg(lambda x: x.iloc[0])
+ tm.assert_frame_equal(result, expected, check_names=False)
+
+
+def test_groupby_agg_ea_method(monkeypatch):
+ # Ensure that the result of agg is inferred to be decimal dtype
+ # https://github.com/pandas-dev/pandas/issues/29141
+
+ def DecimalArray__my_sum(self):
+ return np.sum(np.array(self))
+
+ monkeypatch.setattr(DecimalArray, "my_sum", DecimalArray__my_sum, raising=False)
+
+ data = make_data(5)
+ df = pd.DataFrame({"id": [0, 0, 0, 1, 1], "decimals": DecimalArray(data)})
+ expected = pd.Series(to_decimal([data[0] + data[1] + data[2], data[3] + data[4]]))
+
+ result = df.groupby("id")["decimals"].agg(lambda x: x.values.my_sum())
+ tm.assert_series_equal(result, expected, check_names=False)
+ s = pd.Series(DecimalArray(data))
+ grouper = np.array([0, 0, 0, 1, 1], dtype=np.int64)
+ result = s.groupby(grouper).agg(lambda x: x.values.my_sum())
+ tm.assert_series_equal(result, expected, check_names=False)
+
+
+def test_indexing_no_materialize(monkeypatch):
+ # See https://github.com/pandas-dev/pandas/issues/29708
+ # Ensure that indexing operations do not materialize (convert to a numpy
+ # array) the ExtensionArray unnecessary
+
+ def DecimalArray__array__(self, dtype=None):
+ raise Exception("tried to convert a DecimalArray to a numpy array")
+
+ monkeypatch.setattr(DecimalArray, "__array__", DecimalArray__array__, raising=False)
+
+ data = make_data(10)
+ s = pd.Series(DecimalArray(data))
+ df = pd.DataFrame({"a": s, "b": range(len(s))})
+
+ # ensure the following operations do not raise an error
+ s[s > 0.5]
+ df[s > 0.5]
+ s.at[0]
+ df.at[0, "a"]
+
+
+def test_to_numpy_keyword():
+ # test the extra keyword
+ values = [decimal.Decimal("1.1111"), decimal.Decimal("2.2222")]
+ expected = np.array(
+ [decimal.Decimal("1.11"), decimal.Decimal("2.22")], dtype="object"
+ )
+ a = pd.array(values, dtype="decimal")
+ result = a.to_numpy(decimals=2)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = pd.Series(a).to_numpy(decimals=2)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_array_copy_on_write():
+ df = pd.DataFrame({"a": [decimal.Decimal(2), decimal.Decimal(3)]}, dtype="object")
+ df2 = df.astype(DecimalDtype())
+ df.iloc[0, 0] = 0
+ expected = pd.DataFrame(
+ {"a": [decimal.Decimal(2), decimal.Decimal(3)]}, dtype=DecimalDtype()
+ )
+ tm.assert_equal(df2.values, expected.values)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/json/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/json/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ebfd54a5b0d6bf1ff2c4602ed72f5214e32608f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/json/__init__.py
@@ -0,0 +1,7 @@
+from pandas.tests.extension.json.array import (
+ JSONArray,
+ JSONDtype,
+ make_data,
+)
+
+__all__ = ["JSONArray", "JSONDtype", "make_data"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/json/array.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/json/array.py
new file mode 100644
index 0000000000000000000000000000000000000000..5eb1a9ff286fa018bb07ba6a80298ea67235f2f5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/json/array.py
@@ -0,0 +1,282 @@
+"""
+Test extension array for storing nested data in a pandas container.
+
+The JSONArray stores lists of dictionaries. The storage mechanism is a list,
+not an ndarray.
+
+Note
+----
+We currently store lists of UserDicts. Pandas has a few places
+internally that specifically check for dicts, and does non-scalar things
+in that case. We *want* the dictionaries to be treated as scalars, so we
+hack around pandas by using UserDicts.
+"""
+
+from __future__ import annotations
+
+from collections import (
+ UserDict,
+ abc,
+)
+import itertools
+import numbers
+import string
+import sys
+from typing import (
+ TYPE_CHECKING,
+ Any,
+)
+
+import numpy as np
+
+from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
+from pandas.core.dtypes.common import (
+ is_bool_dtype,
+ is_list_like,
+ pandas_dtype,
+)
+
+import pandas as pd
+from pandas.api.extensions import (
+ ExtensionArray,
+ ExtensionDtype,
+)
+from pandas.core.indexers import (
+ getitem_returns_view,
+ unpack_tuple_and_ellipses,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
+ from pandas._typing import type_t
+
+
+class JSONDtype(ExtensionDtype):
+ type = abc.Mapping
+ name = "json"
+ na_value: Mapping[str, Any] = UserDict()
+
+ def construct_array_type(self) -> type_t[JSONArray]:
+ """
+ Return the array type associated with this dtype.
+
+ Returns
+ -------
+ type
+ """
+ return JSONArray
+
+
+class JSONArray(ExtensionArray):
+ dtype = JSONDtype()
+ __array_priority__ = 1000
+
+ def __init__(self, values, dtype=None, copy=False) -> None:
+ for val in values:
+ if not isinstance(val, self.dtype.type):
+ raise TypeError("All values must be of type " + str(self.dtype.type))
+ self.data = values
+
+ # Some aliases for common attribute names to ensure pandas supports
+ # these
+ self._items = self._data = self.data
+ # those aliases are currently not working due to assumptions
+ # in internal code (GH-20735)
+ # self._values = self.values = self.data
+
+ @classmethod
+ def _from_sequence(cls, scalars, *, dtype=None, copy=False):
+ return cls(scalars)
+
+ @classmethod
+ def _from_factorized(cls, values, original):
+ return cls([UserDict(x) for x in values if x != ()])
+
+ def _cast_pointwise_result(self, values):
+ try:
+ return type(self)._from_sequence(values, dtype=self.dtype)
+ except (ValueError, TypeError):
+ # TODO replace with public function
+ from pandas._libs import lib
+
+ values = np.asarray(values, dtype=object)
+ return lib.maybe_convert_objects(values, convert_non_numeric=True)
+
+ def __getitem__(self, item):
+ if isinstance(item, tuple):
+ item = unpack_tuple_and_ellipses(item)
+
+ if isinstance(item, numbers.Integral):
+ return self.data[item]
+ elif isinstance(item, slice) and item == slice(None):
+ # Make sure we get a view
+ result = type(self)(self.data)
+ result._readonly = self._readonly
+ return result
+ elif isinstance(item, slice):
+ # slice
+ result = type(self)(self.data[item])
+ if getitem_returns_view(self, item):
+ result._readonly = self._readonly
+ return result
+ elif not is_list_like(item):
+ # e.g. "foo" or 2.5
+ # exception message copied from numpy
+ raise IndexError(
+ r"only integers, slices (`:`), ellipsis (`...`), numpy.newaxis "
+ r"(`None`) and integer or boolean arrays are valid indices"
+ )
+ else:
+ item = pd.api.indexers.check_array_indexer(self, item)
+ if is_bool_dtype(item.dtype):
+ return type(self)._from_sequence(
+ [x for x, m in zip(self, item, strict=True) if m], dtype=self.dtype
+ )
+ # integer
+ return type(self)([self.data[i] for i in item])
+
+ def __setitem__(self, key, value) -> None:
+ if self._readonly:
+ raise ValueError("Cannot modify read-only array")
+
+ if isinstance(key, numbers.Integral):
+ self.data[key] = value
+ else:
+ if not isinstance(value, (type(self), abc.Sequence)):
+ # broadcast value
+ value = itertools.cycle([value])
+
+ if isinstance(key, np.ndarray) and key.dtype == "bool":
+ # masking
+ for i, (k, v) in enumerate(zip(key, value, strict=False)):
+ if k:
+ assert isinstance(v, self.dtype.type)
+ self.data[i] = v
+ else:
+ for k, v in zip(key, value, strict=False):
+ assert isinstance(v, self.dtype.type)
+ self.data[k] = v
+
+ def __len__(self) -> int:
+ return len(self.data)
+
+ def __eq__(self, other):
+ return NotImplemented
+
+ def __ne__(self, other):
+ return NotImplemented
+
+ def __array__(self, dtype=None, copy=None):
+ if copy is False:
+ raise ValueError(
+ "Unable to avoid copy while creating an array as requested."
+ )
+ if dtype is None:
+ dtype = object
+ if dtype == object:
+ # on py38 builds it looks like numpy is inferring to a non-1D array
+ return construct_1d_object_array_from_listlike(list(self))
+ if copy is None:
+ # Note: branch avoids `copy=None` for NumPy 1.x support
+ return np.asarray(self.data, dtype=dtype)
+ return np.asarray(self.data, dtype=dtype, copy=copy)
+
+ @property
+ def nbytes(self) -> int:
+ return sys.getsizeof(self.data)
+
+ def isna(self):
+ return np.array([x == self.dtype.na_value for x in self.data], dtype=bool)
+
+ def take(self, indexer, allow_fill=False, fill_value=None):
+ # re-implement here, since NumPy has trouble setting
+ # sized objects like UserDicts into scalar slots of
+ # an ndarary.
+ indexer = np.asarray(indexer)
+ msg = (
+ "Index is out of bounds or cannot do a non-empty take from an empty array."
+ )
+
+ if allow_fill:
+ if fill_value is None:
+ fill_value = self.dtype.na_value
+ # bounds check
+ if (indexer < -1).any():
+ raise ValueError
+ try:
+ output = [
+ self.data[loc] if loc != -1 else fill_value for loc in indexer
+ ]
+ except IndexError as err:
+ raise IndexError(msg) from err
+ else:
+ try:
+ output = [self.data[loc] for loc in indexer]
+ except IndexError as err:
+ raise IndexError(msg) from err
+
+ return type(self)._from_sequence(output, dtype=self.dtype)
+
+ def copy(self):
+ return type(self)(self.data[:])
+
+ def astype(self, dtype, copy=True):
+ # NumPy has issues when all the dicts are the same length.
+ # np.array([UserDict(...), UserDict(...)]) fails,
+ # but np.array([{...}, {...}]) works, so cast.
+ from pandas.core.arrays.string_ import StringDtype
+
+ dtype = pandas_dtype(dtype)
+ # needed to add this check for the Series constructor
+ if isinstance(dtype, type(self.dtype)) and dtype == self.dtype:
+ if copy:
+ return self.copy()
+ return self
+ elif isinstance(dtype, StringDtype):
+ arr_cls = dtype.construct_array_type()
+ return arr_cls._from_sequence(self, dtype=dtype, copy=False)
+ elif not copy:
+ return np.asarray([dict(x) for x in self], dtype=dtype)
+ else:
+ return np.array([dict(x) for x in self], dtype=dtype, copy=copy)
+
+ def unique(self):
+ # Parent method doesn't work since np.array will try to infer
+ # a 2-dim object.
+ return type(self)([dict(x) for x in {tuple(d.items()) for d in self.data}])
+
+ @classmethod
+ def _concat_same_type(cls, to_concat):
+ data = list(itertools.chain.from_iterable(x.data for x in to_concat))
+ return cls(data)
+
+ def _values_for_factorize(self):
+ frozen = self._values_for_argsort()
+ if len(frozen) == 0:
+ # factorize_array expects 1-d array, this is a len-0 2-d array.
+ frozen = frozen.ravel()
+ return frozen, ()
+
+ def _values_for_argsort(self):
+ # Bypass NumPy's shape inference to get a (N,) array of tuples.
+ frozen = [tuple(x.items()) for x in self]
+ return construct_1d_object_array_from_listlike(frozen)
+
+ def _pad_or_backfill(self, *, method, limit=None, copy=True):
+ # GH#56616 - test EA method without limit_area argument
+ return super()._pad_or_backfill(method=method, limit=limit, copy=copy)
+
+
+def make_data(n: int):
+ # TODO: Use a regular dict. See _NDFrameIndexer._setitem_with_indexer
+ rng = np.random.default_rng(2)
+ return [
+ UserDict(
+ [
+ (rng.choice(list(string.ascii_letters)), rng.integers(0, 100))
+ for _ in range(rng.integers(0, 10))
+ ]
+ )
+ for _ in range(n)
+ ]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/json/test_json.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/json/test_json.py
new file mode 100644
index 0000000000000000000000000000000000000000..f146f3875672b489d601a4aa8977dbd383e045e2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/json/test_json.py
@@ -0,0 +1,501 @@
+import collections
+import operator
+import sys
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.tests.extension import base
+from pandas.tests.extension.json.array import (
+ JSONArray,
+ JSONDtype,
+ make_data,
+)
+
+# We intentionally don't run base.BaseSetitemTests because pandas'
+# internals has trouble setting sequences of values into scalar positions.
+unhashable = pytest.mark.xfail(reason="Unhashable")
+
+
+@pytest.fixture
+def dtype():
+ return JSONDtype()
+
+
+@pytest.fixture
+def data():
+ """Length-10 JSONArray for semantics test."""
+ data = make_data(10)
+
+ # Why the while loop? NumPy is unable to construct an ndarray from
+ # equal-length ndarrays. Many of our operations involve coercing the
+ # EA to an ndarray of objects. To avoid random test failures, we ensure
+ # that our data is coercible to an ndarray. Several tests deal with only
+ # the first two elements, so that's what we'll check.
+
+ while len(data[0]) == len(data[1]):
+ data = make_data(10)
+
+ return JSONArray(data)
+
+
+@pytest.fixture
+def data_missing():
+ """Length 2 array with [NA, Valid]"""
+ return JSONArray([{}, {"a": 10}])
+
+
+@pytest.fixture
+def data_for_sorting():
+ return JSONArray([{"b": 1}, {"c": 4}, {"a": 2, "c": 3}])
+
+
+@pytest.fixture
+def data_missing_for_sorting():
+ return JSONArray([{"b": 1}, {}, {"a": 4}])
+
+
+@pytest.fixture
+def na_cmp():
+ return operator.eq
+
+
+@pytest.fixture
+def data_for_grouping():
+ return JSONArray(
+ [
+ {"b": 1},
+ {"b": 1},
+ {},
+ {},
+ {"a": 0, "c": 2},
+ {"a": 0, "c": 2},
+ {"b": 1},
+ {"c": 2},
+ ]
+ )
+
+
+class TestJSONArray(base.ExtensionTests):
+ @pytest.mark.xfail(
+ reason="comparison method not implemented for JSONArray (GH-37867)"
+ )
+ def test_contains(self, data):
+ # GH-37867
+ super().test_contains(data)
+
+ @pytest.mark.xfail(reason="not implemented constructor from dtype")
+ def test_from_dtype(self, data):
+ # construct from our dtype & string dtype
+ super().test_from_dtype(data)
+
+ @pytest.mark.xfail(reason="RecursionError, GH-33900")
+ def test_series_constructor_no_data_with_index(self, dtype, na_value):
+ # RecursionError: maximum recursion depth exceeded in comparison
+ rec_limit = sys.getrecursionlimit()
+ try:
+ # Limit to avoid stack overflow on Windows CI
+ sys.setrecursionlimit(100)
+ super().test_series_constructor_no_data_with_index(dtype, na_value)
+ finally:
+ sys.setrecursionlimit(rec_limit)
+
+ @pytest.mark.xfail(reason="RecursionError, GH-33900")
+ def test_series_constructor_scalar_na_with_index(self, dtype, na_value):
+ # RecursionError: maximum recursion depth exceeded in comparison
+ rec_limit = sys.getrecursionlimit()
+ try:
+ # Limit to avoid stack overflow on Windows CI
+ sys.setrecursionlimit(100)
+ super().test_series_constructor_scalar_na_with_index(dtype, na_value)
+ finally:
+ sys.setrecursionlimit(rec_limit)
+
+ @pytest.mark.xfail(reason="collection as scalar, GH-33901")
+ def test_series_constructor_scalar_with_index(self, data, dtype):
+ # TypeError: All values must be of type
+ rec_limit = sys.getrecursionlimit()
+ try:
+ # Limit to avoid stack overflow on Windows CI
+ sys.setrecursionlimit(100)
+ super().test_series_constructor_scalar_with_index(data, dtype)
+ finally:
+ sys.setrecursionlimit(rec_limit)
+
+ @pytest.mark.xfail(reason="Different definitions of NA")
+ def test_stack(self):
+ """
+ The test does .astype(object).stack(). If we happen to have
+ any missing values in `data`, then we'll end up with different
+ rows since we consider `{}` NA, but `.astype(object)` doesn't.
+ """
+ super().test_stack()
+
+ @pytest.mark.xfail(reason="dict for NA")
+ def test_unstack(self, data, index):
+ # The base test has NaN for the expected NA value.
+ # this matches otherwise
+ return super().test_unstack(data, index)
+
+ @pytest.mark.xfail(reason="Setting a dict as a scalar")
+ def test_fillna_series(self):
+ """We treat dictionaries as a mapping in fillna, not a scalar."""
+ super().test_fillna_series()
+
+ @pytest.mark.xfail(reason="Setting a dict as a scalar")
+ def test_fillna_frame(self):
+ """We treat dictionaries as a mapping in fillna, not a scalar."""
+ super().test_fillna_frame()
+
+ def test_fillna_with_none(self, data_missing):
+ # GH#57723
+ # EAs that don't have special logic for None will raise, unlike pandas'
+ # which interpret None as the NA value for the dtype.
+ with pytest.raises(AssertionError):
+ super().test_fillna_with_none(data_missing)
+
+ @pytest.mark.xfail(reason="fill value is a dictionary, takes incorrect code path")
+ def test_fillna_limit_frame(self, data_missing):
+ # GH#58001
+ super().test_fillna_limit_frame(data_missing)
+
+ @pytest.mark.xfail(reason="fill value is a dictionary, takes incorrect code path")
+ def test_fillna_limit_series(self, data_missing):
+ # GH#58001
+ super().test_fillna_limit_frame(data_missing)
+
+ @pytest.mark.parametrize(
+ "limit_area, input_ilocs, expected_ilocs",
+ [
+ ("outside", [1, 0, 0, 0, 1], [1, 0, 0, 0, 1]),
+ ("outside", [1, 0, 1, 0, 1], [1, 0, 1, 0, 1]),
+ ("outside", [0, 1, 1, 1, 0], [0, 1, 1, 1, 1]),
+ ("outside", [0, 1, 0, 1, 0], [0, 1, 0, 1, 1]),
+ ("inside", [1, 0, 0, 0, 1], [1, 1, 1, 1, 1]),
+ ("inside", [1, 0, 1, 0, 1], [1, 1, 1, 1, 1]),
+ ("inside", [0, 1, 1, 1, 0], [0, 1, 1, 1, 0]),
+ ("inside", [0, 1, 0, 1, 0], [0, 1, 1, 1, 0]),
+ ],
+ )
+ def test_ffill_limit_area(
+ self, data_missing, limit_area, input_ilocs, expected_ilocs
+ ):
+ # GH#56616
+ msg = "JSONArray does not implement limit_area"
+ with pytest.raises(NotImplementedError, match=msg):
+ super().test_ffill_limit_area(
+ data_missing, limit_area, input_ilocs, expected_ilocs
+ )
+
+ def test_value_counts(self, all_data, dropna, request):
+ if len(all_data) == 10 or dropna:
+ request.applymarker(unhashable)
+ super().test_value_counts(all_data, dropna)
+
+ @unhashable
+ def test_sort_values_frame(self):
+ # TODO (EA.factorize): see if _values_for_factorize allows this.
+ super().test_sort_values_frame()
+
+ @pytest.mark.xfail(reason="combine for JSONArray not supported")
+ def test_combine_le(self, data_repeated):
+ super().test_combine_le(data_repeated)
+
+ @pytest.mark.xfail(
+ reason="combine for JSONArray not supported - "
+ "may pass depending on random data",
+ strict=False,
+ raises=AssertionError,
+ )
+ def test_combine_first(self, data):
+ super().test_combine_first(data)
+
+ @pytest.mark.xfail(reason="broadcasting error")
+ def test_where_series(self, data, na_value):
+ # Fails with
+ # *** ValueError: operands could not be broadcast together
+ # with shapes (4,) (4,) (0,)
+ super().test_where_series(data, na_value)
+
+ @pytest.mark.xfail(reason="Can't compare dicts.")
+ def test_searchsorted(self, data_for_sorting):
+ super().test_searchsorted(data_for_sorting)
+
+ @pytest.mark.xfail(reason="Can't compare dicts.")
+ def test_equals(self, data, na_value, as_series):
+ super().test_equals(data, na_value, as_series)
+
+ @pytest.mark.skip("fill-value is interpreted as a dict of values")
+ def test_fillna_copy_frame(self, data_missing):
+ super().test_fillna_copy_frame(data_missing)
+
+ @pytest.mark.xfail(reason="Fails with CoW")
+ def test_equals_same_data_different_object(self, data):
+ super().test_equals_same_data_different_object(data)
+
+ @pytest.mark.xfail(reason="failing on np.array(self, dtype=str)")
+ def test_astype_str(self):
+ """This currently fails in NumPy on np.array(self, dtype=str) with
+
+ *** ValueError: setting an array element with a sequence
+ """
+ super().test_astype_str()
+
+ @unhashable
+ def test_groupby_extension_transform(self):
+ """
+ This currently fails in Series.name.setter, since the
+ name must be hashable, but the value is a dictionary.
+ I think this is what we want, i.e. `.name` should be the original
+ values, and not the values for factorization.
+ """
+ super().test_groupby_extension_transform()
+
+ @unhashable
+ def test_groupby_extension_apply(self):
+ """
+ This fails in Index._do_unique_check with
+
+ > hash(val)
+ E TypeError: unhashable type: 'UserDict' with
+
+ I suspect that once we support Index[ExtensionArray],
+ we'll be able to dispatch unique.
+ """
+ super().test_groupby_extension_apply()
+
+ @unhashable
+ def test_groupby_extension_agg(self):
+ """
+ This fails when we get to tm.assert_series_equal when left.index
+ contains dictionaries, which are not hashable.
+ """
+ super().test_groupby_extension_agg()
+
+ @unhashable
+ def test_groupby_extension_no_sort(self):
+ """
+ This fails when we get to tm.assert_series_equal when left.index
+ contains dictionaries, which are not hashable.
+ """
+ super().test_groupby_extension_no_sort()
+
+ def test_arith_frame_with_scalar(self, data, all_arithmetic_operators, request):
+ if len(data[0]) != 1:
+ mark = pytest.mark.xfail(reason="raises in coercing to Series")
+ request.applymarker(mark)
+ super().test_arith_frame_with_scalar(data, all_arithmetic_operators)
+
+ def test_compare_array(self, data, comparison_op, request):
+ if comparison_op.__name__ in ["eq", "ne"]:
+ mark = pytest.mark.xfail(reason="Comparison methods not implemented")
+ request.applymarker(mark)
+ super().test_compare_array(data, comparison_op)
+
+ @pytest.mark.xfail(reason="ValueError: Must have equal len keys and value")
+ def test_setitem_loc_scalar_mixed(self, data):
+ super().test_setitem_loc_scalar_mixed(data)
+
+ @pytest.mark.xfail(reason="ValueError: Must have equal len keys and value")
+ def test_setitem_loc_scalar_multiple_homogoneous(self, data):
+ super().test_setitem_loc_scalar_multiple_homogoneous(data)
+
+ @pytest.mark.xfail(reason="ValueError: Must have equal len keys and value")
+ def test_setitem_iloc_scalar_mixed(self, data):
+ super().test_setitem_iloc_scalar_mixed(data)
+
+ @pytest.mark.xfail(reason="ValueError: Must have equal len keys and value")
+ def test_setitem_iloc_scalar_multiple_homogoneous(self, data):
+ super().test_setitem_iloc_scalar_multiple_homogoneous(data)
+
+ @pytest.mark.parametrize(
+ "mask",
+ [
+ np.array([True, True, True, False, False]),
+ pd.array([True, True, True, False, False], dtype="boolean"),
+ pd.array([True, True, True, pd.NA, pd.NA], dtype="boolean"),
+ ],
+ ids=["numpy-array", "boolean-array", "boolean-array-na"],
+ )
+ def test_setitem_mask(self, data, mask, box_in_series, request):
+ if box_in_series:
+ mark = pytest.mark.xfail(
+ reason="cannot set using a list-like indexer with a different length"
+ )
+ request.applymarker(mark)
+ elif not isinstance(mask, np.ndarray):
+ mark = pytest.mark.xfail(reason="Issues unwanted DeprecationWarning")
+ request.applymarker(mark)
+ super().test_setitem_mask(data, mask, box_in_series)
+
+ def test_setitem_mask_raises(self, data, box_in_series, request):
+ if not box_in_series:
+ mark = pytest.mark.xfail(reason="Fails to raise")
+ request.applymarker(mark)
+
+ super().test_setitem_mask_raises(data, box_in_series)
+
+ @pytest.mark.xfail(
+ reason="cannot set using a list-like indexer with a different length"
+ )
+ def test_setitem_mask_boolean_array_with_na(self, data, box_in_series):
+ super().test_setitem_mask_boolean_array_with_na(data, box_in_series)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [[0, 1, 2], pd.array([0, 1, 2], dtype="Int64"), np.array([0, 1, 2])],
+ ids=["list", "integer-array", "numpy-array"],
+ )
+ def test_setitem_integer_array(self, data, idx, box_in_series, request):
+ if box_in_series:
+ mark = pytest.mark.xfail(
+ reason="cannot set using a list-like indexer with a different length"
+ )
+ request.applymarker(mark)
+ super().test_setitem_integer_array(data, idx, box_in_series)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [
+ [0, 1, 2, pd.NA],
+ pd.array([0, 1, 2, pd.NA], dtype="Int64"),
+ ],
+ ids=["list", "integer-array"],
+ )
+ @pytest.mark.parametrize(
+ "box_in_series",
+ [
+ True,
+ pytest.param(
+ False,
+ marks=pytest.mark.xfail(
+ reason="list indices must be integers or slices, not NAType"
+ ),
+ ),
+ ],
+ )
+ def test_setitem_integer_with_missing_raises(self, data, idx, box_in_series):
+ super().test_setitem_integer_with_missing_raises(data, idx, box_in_series)
+
+ @pytest.mark.xfail(reason="Fails to raise")
+ def test_setitem_scalar_key_sequence_raise(self, data):
+ super().test_setitem_scalar_key_sequence_raise(data)
+
+ def test_setitem_with_expansion_dataframe_column(self, data, full_indexer, request):
+ if "full_slice" in request.node.name:
+ mark = pytest.mark.xfail(reason="slice is not iterable")
+ request.applymarker(mark)
+ super().test_setitem_with_expansion_dataframe_column(data, full_indexer)
+
+ @pytest.mark.xfail(reason="slice is not iterable")
+ def test_setitem_frame_2d_values(self, data):
+ super().test_setitem_frame_2d_values(data)
+
+ @pytest.mark.xfail(
+ reason="cannot set using a list-like indexer with a different length"
+ )
+ @pytest.mark.parametrize("setter", ["loc", None])
+ def test_setitem_mask_broadcast(self, data, setter):
+ super().test_setitem_mask_broadcast(data, setter)
+
+ @pytest.mark.xfail(
+ reason="cannot set using a slice indexer with a different length"
+ )
+ def test_setitem_slice(self, data, box_in_series):
+ super().test_setitem_slice(data, box_in_series)
+
+ @pytest.mark.xfail(reason="slice object is not iterable")
+ def test_setitem_loc_iloc_slice(self, data):
+ super().test_setitem_loc_iloc_slice(data)
+
+ @pytest.mark.xfail(reason="slice object is not iterable")
+ def test_setitem_slice_mismatch_length_raises(self, data):
+ super().test_setitem_slice_mismatch_length_raises(data)
+
+ @pytest.mark.xfail(reason="slice object is not iterable")
+ def test_setitem_slice_array(self, data):
+ super().test_setitem_slice_array(data)
+
+ @pytest.mark.xfail(reason="Fail to raise")
+ def test_setitem_invalid(self, data, invalid_scalar):
+ super().test_setitem_invalid(data, invalid_scalar)
+
+ @pytest.mark.xfail(
+ reason="result readonly flag is incorrect and does not support na_value"
+ )
+ def test_readonly_propagates_to_numpy_array_method(self, data):
+ super().test_readonly_propagates_to_numpy_array_method(data)
+
+ @pytest.mark.xfail(reason="only integer scalar arrays can be converted")
+ def test_setitem_2d_values(self, data):
+ super().test_setitem_2d_values(data)
+
+ @pytest.mark.xfail(reason="data type 'json' not understood")
+ @pytest.mark.parametrize("engine", ["c", "python"])
+ def test_EA_types(self, engine, data, request):
+ super().test_EA_types(engine, data, request)
+
+
+def custom_assert_series_equal(left, right, *args, **kwargs):
+ # NumPy doesn't handle an array of equal-length UserDicts.
+ # The default assert_series_equal eventually does a
+ # Series.values, which raises. We work around it by
+ # converting the UserDicts to dicts.
+ if left.dtype.name == "json":
+ assert left.dtype == right.dtype
+ left = pd.Series(
+ JSONArray(left.values.astype(object)), index=left.index, name=left.name
+ )
+ right = pd.Series(
+ JSONArray(right.values.astype(object)),
+ index=right.index,
+ name=right.name,
+ )
+ tm.assert_series_equal(left, right, *args, **kwargs)
+
+
+def custom_assert_frame_equal(left, right, *args, **kwargs):
+ obj_type = kwargs.get("obj", "DataFrame")
+ tm.assert_index_equal(
+ left.columns,
+ right.columns,
+ exact=kwargs.get("check_column_type", "equiv"),
+ check_names=kwargs.get("check_names", True),
+ check_exact=kwargs.get("check_exact", False),
+ check_categorical=kwargs.get("check_categorical", True),
+ obj=f"{obj_type}.columns",
+ )
+
+ jsons = (left.dtypes == "json").index
+
+ for col in jsons:
+ custom_assert_series_equal(left[col], right[col], *args, **kwargs)
+
+ left = left.drop(columns=jsons)
+ right = right.drop(columns=jsons)
+ tm.assert_frame_equal(left, right, *args, **kwargs)
+
+
+def test_custom_asserts():
+ # This would always trigger the KeyError from trying to put
+ # an array of equal-length UserDicts inside an ndarray.
+ data = JSONArray(
+ [
+ collections.UserDict({"a": 1}),
+ collections.UserDict({"b": 2}),
+ collections.UserDict({"c": 3}),
+ ]
+ )
+ a = pd.Series(data)
+ custom_assert_series_equal(a, a)
+ custom_assert_frame_equal(a.to_frame(), a.to_frame())
+
+ b = pd.Series(data.take([0, 0, 1]))
+ msg = r"Series are different"
+ with pytest.raises(AssertionError, match=msg):
+ custom_assert_series_equal(a, b)
+
+ with pytest.raises(AssertionError, match=msg):
+ custom_assert_frame_equal(a.to_frame(), b.to_frame())
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/list/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/list/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f3f2f35377882a0fae603edfc8edb46371429fe
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/list/__init__.py
@@ -0,0 +1,7 @@
+from pandas.tests.extension.list.array import (
+ ListArray,
+ ListDtype,
+ make_data,
+)
+
+__all__ = ["ListArray", "ListDtype", "make_data"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/list/array.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/list/array.py
new file mode 100644
index 0000000000000000000000000000000000000000..6da5d1cf25fad3f2e32d8ce3335124684874ae6c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/list/array.py
@@ -0,0 +1,136 @@
+"""
+Test extension array for storing nested data in a pandas container.
+
+The ListArray stores an ndarray of lists.
+"""
+
+from __future__ import annotations
+
+import numbers
+import string
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from pandas.core.dtypes.base import ExtensionDtype
+
+import pandas as pd
+from pandas.api.types import (
+ is_object_dtype,
+ is_string_dtype,
+)
+from pandas.core.arrays import ExtensionArray
+
+if TYPE_CHECKING:
+ from pandas._typing import type_t
+
+
+class ListDtype(ExtensionDtype):
+ type = list
+ name = "list"
+ na_value = np.nan
+
+ def construct_array_type(self) -> type_t[ListArray]:
+ """
+ Return the array type associated with this dtype.
+
+ Returns
+ -------
+ type
+ """
+ return ListArray
+
+
+class ListArray(ExtensionArray):
+ dtype = ListDtype()
+ __array_priority__ = 1000
+
+ def __init__(self, values, dtype=None, copy=False) -> None:
+ if not isinstance(values, np.ndarray):
+ raise TypeError("Need to pass a numpy array as values")
+ for val in values:
+ if not isinstance(val, self.dtype.type) and not pd.isna(val):
+ raise TypeError("All values must be of type " + str(self.dtype.type))
+ self.data = values
+
+ @classmethod
+ def _from_sequence(cls, scalars, *, dtype=None, copy=False):
+ data = np.empty(len(scalars), dtype=object)
+ data[:] = scalars
+ return cls(data)
+
+ def __getitem__(self, item):
+ if isinstance(item, numbers.Integral):
+ return self.data[item]
+ else:
+ # slice, list-like, mask
+ return type(self)(self.data[item])
+
+ def __len__(self) -> int:
+ return len(self.data)
+
+ def isna(self):
+ return np.array(
+ [not isinstance(x, list) and np.isnan(x) for x in self.data], dtype=bool
+ )
+
+ def take(self, indexer, allow_fill=False, fill_value=None):
+ # re-implement here, since NumPy has trouble setting
+ # sized objects like UserDicts into scalar slots of
+ # an ndarary.
+ indexer = np.asarray(indexer)
+ msg = (
+ "Index is out of bounds or cannot do a non-empty take from an empty array."
+ )
+
+ if allow_fill:
+ if fill_value is None:
+ fill_value = self.dtype.na_value
+ # bounds check
+ if (indexer < -1).any():
+ raise ValueError
+ try:
+ output = [
+ self.data[loc] if loc != -1 else fill_value for loc in indexer
+ ]
+ except IndexError as err:
+ raise IndexError(msg) from err
+ else:
+ try:
+ output = [self.data[loc] for loc in indexer]
+ except IndexError as err:
+ raise IndexError(msg) from err
+
+ return self._from_sequence(output)
+
+ def copy(self):
+ return type(self)(self.data[:])
+
+ def astype(self, dtype, copy=True):
+ if isinstance(dtype, type(self.dtype)) and dtype == self.dtype:
+ if copy:
+ return self.copy()
+ return self
+ elif is_string_dtype(dtype) and not is_object_dtype(dtype):
+ # numpy has problems with astype(str) for nested elements
+ return np.array([str(x) for x in self.data], dtype=dtype)
+ elif not copy:
+ return np.asarray(self.data, dtype=dtype)
+ else:
+ return np.array(self.data, dtype=dtype, copy=copy)
+
+ @classmethod
+ def _concat_same_type(cls, to_concat):
+ data = np.concatenate([x.data for x in to_concat])
+ return cls(data)
+
+
+def make_data(n: int):
+ # TODO: Use a regular dict. See _NDFrameIndexer._setitem_with_indexer
+ rng = np.random.default_rng(2)
+ data = np.empty(n, dtype=object)
+ data[:] = [
+ [rng.choice(list(string.ascii_letters)) for _ in range(rng.integers(0, 10))]
+ for _ in range(n)
+ ]
+ return data
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/list/test_list.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/list/test_list.py
new file mode 100644
index 0000000000000000000000000000000000000000..3cb1c1916492cb7c80ce8cf43bfa25317d066301
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/list/test_list.py
@@ -0,0 +1,33 @@
+import pytest
+
+import pandas as pd
+from pandas.tests.extension.list.array import (
+ ListArray,
+ ListDtype,
+ make_data,
+)
+
+
+@pytest.fixture
+def dtype():
+ return ListDtype()
+
+
+@pytest.fixture
+def data():
+ """Length-10 ListArray for semantics test."""
+ data = make_data(10)
+
+ while len(data[0]) == len(data[1]):
+ data = make_data(10)
+
+ return ListArray(data)
+
+
+def test_to_csv(data):
+ # https://github.com/pandas-dev/pandas/issues/28840
+ # array with list-likes fail when doing astype(str) on the numpy array
+ # which was done in get_values_for_csv
+ df = pd.DataFrame({"a": data})
+ res = df.to_csv()
+ assert str(data[0]) in res
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_arrow.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_arrow.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6a30c6a9fd5b20a5e377ec9ced8d7d4f8a6965b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_arrow.py
@@ -0,0 +1,3967 @@
+"""
+This file contains a minimal set of tests for compliance with the extension
+array interface test suite, and should contain no other tests.
+The test suite for the full functionality of the array is located in
+`pandas/tests/arrays/`.
+The tests in this file are inherited from the BaseExtensionTests, and only
+minimal tweaks should be applied to get the tests passing (by overwriting a
+parent method).
+Additional tests should either be added to one of the BaseExtensionTests
+classes (if they are relevant for the extension interface for all dtypes), or
+be added to the array-specific tests in `pandas/tests/arrays/`.
+"""
+
+from __future__ import annotations
+
+from datetime import (
+ date,
+ datetime,
+ time,
+ timedelta,
+)
+from decimal import Decimal
+from io import (
+ BytesIO,
+ StringIO,
+)
+import operator
+import pickle
+import re
+import sys
+
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+from pandas._libs.tslibs import timezones
+from pandas.compat import (
+ PY312,
+ is_ci_environment,
+ is_platform_windows,
+ pa_version_under14p0,
+ pa_version_under19p0,
+ pa_version_under20p0,
+ pa_version_under21p0,
+)
+from pandas.compat.pyarrow import pa_version_under22p0
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.common import pandas_dtype
+from pandas.core.dtypes.dtypes import (
+ ArrowDtype,
+ CategoricalDtypeType,
+)
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.extensions import no_default
+from pandas.api.types import (
+ is_bool_dtype,
+ is_datetime64_any_dtype,
+ is_float_dtype,
+ is_integer_dtype,
+ is_numeric_dtype,
+ is_signed_integer_dtype,
+ is_string_dtype,
+ is_unsigned_integer_dtype,
+)
+from pandas.tests.extension import base
+
+pa = pytest.importorskip("pyarrow")
+
+from pandas.core.arrays.arrow.array import ArrowExtensionArray
+from pandas.core.arrays.arrow.extension_types import ArrowPeriodType
+
+
+def _require_timezone_database(request):
+ if is_platform_windows() and is_ci_environment() and pa_version_under22p0:
+ mark = pytest.mark.xfail(
+ raises=pa.ArrowInvalid,
+ reason=(
+ "TODO: Set ARROW_TIMEZONE_DATABASE environment variable "
+ "on CI to path to the tzdata for pyarrow."
+ ),
+ )
+ request.applymarker(mark)
+
+
+@pytest.fixture(params=tm.ALL_PYARROW_DTYPES, ids=str)
+def dtype(request):
+ return ArrowDtype(pyarrow_dtype=request.param)
+
+
+@pytest.fixture
+def data(dtype):
+ pa_dtype = dtype.pyarrow_dtype
+ if pa.types.is_boolean(pa_dtype):
+ data = [True, False] * 2 + [None] + [True, False] + [None] + [True, False]
+ elif pa.types.is_floating(pa_dtype):
+ data = [1.0, 0.0] * 2 + [None] + [-2.0, -1.0] + [None] + [0.5, 99.5]
+ elif pa.types.is_signed_integer(pa_dtype):
+ data = [1, 0] * 2 + [None] + [-2, -1] + [None] + [1, 99]
+ elif pa.types.is_unsigned_integer(pa_dtype):
+ data = [1, 0] * 2 + [None] + [2, 1] + [None] + [1, 99]
+ elif pa.types.is_decimal(pa_dtype):
+ data = (
+ [Decimal("1"), Decimal("0.0")] * 2
+ + [None]
+ + [Decimal("-2.0"), Decimal("-1.0")]
+ + [None]
+ + [Decimal("0.5"), Decimal("33.123")]
+ )
+ elif pa.types.is_date(pa_dtype):
+ data = (
+ [date(2022, 1, 1), date(1999, 12, 31)] * 2
+ + [None]
+ + [date(2022, 1, 1), date(2022, 1, 1)]
+ + [None]
+ + [date(1999, 12, 31), date(1999, 12, 31)]
+ )
+ elif pa.types.is_timestamp(pa_dtype):
+ data = (
+ [datetime(2020, 1, 1, 1, 1, 1, 1), datetime(1999, 1, 1, 1, 1, 1, 1)] * 2
+ + [None]
+ + [datetime(2020, 1, 1, 1), datetime(1999, 1, 1, 1)]
+ + [None]
+ + [datetime(2020, 1, 1), datetime(1999, 1, 1)]
+ )
+ elif pa.types.is_duration(pa_dtype):
+ data = (
+ [timedelta(1), timedelta(1, 1)] * 2
+ + [None]
+ + [timedelta(-1), timedelta(0)]
+ + [None]
+ + [timedelta(-10), timedelta(10)]
+ )
+ elif pa.types.is_time(pa_dtype):
+ data = (
+ [time(12, 0), time(0, 12)] * 2
+ + [None]
+ + [time(0, 0), time(1, 1)]
+ + [None]
+ + [time(0, 5), time(5, 0)]
+ )
+ elif pa.types.is_string(pa_dtype):
+ data = ["a", "b"] * 2 + [None] + ["1", "2"] + [None] + ["!", ">"]
+ elif pa.types.is_binary(pa_dtype):
+ data = [b"a", b"b"] * 2 + [None] + [b"1", b"2"] + [None] + [b"!", b">"]
+ else:
+ raise NotImplementedError
+ return pd.array(data, dtype=dtype)
+
+
+@pytest.fixture
+def data_missing(data):
+ """Length-2 array with [NA, Valid]"""
+ return type(data)._from_sequence([None, data[0]], dtype=data.dtype)
+
+
+@pytest.fixture(params=["data", "data_missing"])
+def all_data(request, data, data_missing):
+ """Parametrized fixture returning 'data' or 'data_missing' integer arrays.
+
+ Used to test dtype conversion with and without missing values.
+ """
+ if request.param == "data":
+ return data
+ elif request.param == "data_missing":
+ return data_missing
+
+
+@pytest.fixture
+def data_for_grouping(dtype):
+ """
+ Data for factorization, grouping, and unique tests.
+
+ Expected to be like [B, B, NA, NA, A, A, B, C]
+
+ Where A < B < C and NA is missing
+ """
+ pa_dtype = dtype.pyarrow_dtype
+ if pa.types.is_boolean(pa_dtype):
+ A = False
+ B = True
+ C = True
+ elif pa.types.is_floating(pa_dtype):
+ A = -1.1
+ B = 0.0
+ C = 1.1
+ elif pa.types.is_signed_integer(pa_dtype):
+ A = -1
+ B = 0
+ C = 1
+ elif pa.types.is_unsigned_integer(pa_dtype):
+ A = 0
+ B = 1
+ C = 10
+ elif pa.types.is_date(pa_dtype):
+ A = date(1999, 12, 31)
+ B = date(2010, 1, 1)
+ C = date(2022, 1, 1)
+ elif pa.types.is_timestamp(pa_dtype):
+ A = datetime(1999, 1, 1, 1, 1, 1, 1)
+ B = datetime(2020, 1, 1)
+ C = datetime(2020, 1, 1, 1)
+ elif pa.types.is_duration(pa_dtype):
+ A = timedelta(-1)
+ B = timedelta(0)
+ C = timedelta(1, 4)
+ elif pa.types.is_time(pa_dtype):
+ A = time(0, 0)
+ B = time(0, 12)
+ C = time(12, 12)
+ elif pa.types.is_string(pa_dtype):
+ A = "a"
+ B = "b"
+ C = "c"
+ elif pa.types.is_binary(pa_dtype):
+ A = b"a"
+ B = b"b"
+ C = b"c"
+ elif pa.types.is_decimal(pa_dtype):
+ A = Decimal("-1.1")
+ B = Decimal("0.0")
+ C = Decimal("1.1")
+ else:
+ raise NotImplementedError
+ return pd.array([B, B, None, None, A, A, B, C], dtype=dtype)
+
+
+@pytest.fixture
+def data_for_sorting(data_for_grouping):
+ """
+ Length-3 array with a known sort order.
+
+ This should be three items [B, C, A] with
+ A < B < C
+ """
+ return type(data_for_grouping)._from_sequence(
+ [data_for_grouping[0], data_for_grouping[7], data_for_grouping[4]],
+ dtype=data_for_grouping.dtype,
+ )
+
+
+@pytest.fixture
+def data_missing_for_sorting(data_for_grouping):
+ """
+ Length-3 array with a known sort order.
+
+ This should be three items [B, NA, A] with
+ A < B and NA missing.
+ """
+ return type(data_for_grouping)._from_sequence(
+ [data_for_grouping[0], data_for_grouping[2], data_for_grouping[4]],
+ dtype=data_for_grouping.dtype,
+ )
+
+
+@pytest.fixture
+def data_for_twos(data):
+ """Length-100 array in which all the elements are two."""
+ pa_dtype = data.dtype.pyarrow_dtype
+ if (
+ pa.types.is_integer(pa_dtype)
+ or pa.types.is_floating(pa_dtype)
+ or pa.types.is_decimal(pa_dtype)
+ or pa.types.is_duration(pa_dtype)
+ ):
+ return pd.array([2] * 10, dtype=data.dtype)
+ # tests will be xfailed where 2 is not a valid scalar for pa_dtype
+ return data
+ # TODO: skip otherwise?
+
+
+class TestArrowArray(base.ExtensionTests):
+ def _construct_for_combine_add(self, left, right):
+ dtype = left.dtype
+
+ # in a couple cases, addition is not dtype-preserving
+ if dtype == "bool[pyarrow]":
+ dtype = pandas_dtype("int64[pyarrow]")
+ elif dtype == "int8[pyarrow]" and isinstance(right, type(left)):
+ dtype = pandas_dtype("int64[pyarrow]")
+
+ if isinstance(right, type(left)):
+ return left._from_sequence(
+ [a + b for (a, b) in zip(list(left), list(right), strict=True)],
+ dtype=dtype,
+ )
+ else:
+ return left._from_sequence(
+ [a + right for a in list(left)],
+ dtype=dtype,
+ )
+
+ def test_compare_scalar(self, data, comparison_op):
+ ser = pd.Series(data)
+ self._compare_other(ser, data, comparison_op, data[0])
+
+ def test_compare_range_len(self, data, comparison_op):
+ # GH#63429
+ ser = pd.Series(data)
+ range_test = range(len(ser))
+ self._compare_other(ser, range_test, comparison_op, range_test)
+
+ @pytest.mark.parametrize("na_action", [None, "ignore"])
+ def test_map(self, data_missing, na_action, using_nan_is_na):
+ if data_missing.dtype.kind in "mM":
+ result = data_missing.map(lambda x: x, na_action=na_action)
+ expected = data_missing.to_numpy(dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+ else:
+ result = data_missing.map(lambda x: x, na_action=na_action)
+ if data_missing.dtype == "float32[pyarrow]" and using_nan_is_na:
+ # map roundtrips through objects, which converts to float64
+ expected = data_missing.to_numpy(dtype="float64", na_value=np.nan)
+ else:
+ expected = data_missing.to_numpy()
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_astype_str(self, data, request, using_infer_string):
+ pa_dtype = data.dtype.pyarrow_dtype
+ if pa.types.is_binary(pa_dtype):
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=f"For {pa_dtype} .astype(str) decodes.",
+ )
+ )
+ elif not using_infer_string and (
+ (pa.types.is_timestamp(pa_dtype) and pa_dtype.tz is None)
+ or pa.types.is_duration(pa_dtype)
+ ):
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="pd.Timestamp/pd.Timedelta repr different from numpy repr",
+ )
+ )
+ super().test_astype_str(data)
+
+ def test_from_dtype(self, data, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+ if pa.types.is_string(pa_dtype) or pa.types.is_decimal(pa_dtype):
+ if pa.types.is_string(pa_dtype):
+ reason = "ArrowDtype(pa.string()) != StringDtype('pyarrow')"
+ else:
+ reason = f"pyarrow.type_for_alias cannot infer {pa_dtype}"
+
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=reason,
+ )
+ )
+ super().test_from_dtype(data)
+
+ def test_from_sequence_pa_array(self, data):
+ # https://github.com/pandas-dev/pandas/pull/47034#discussion_r955500784
+ # data._pa_array = pa.ChunkedArray
+ result = type(data)._from_sequence(data._pa_array, dtype=data.dtype)
+ tm.assert_extension_array_equal(result, data)
+ assert isinstance(result._pa_array, pa.ChunkedArray)
+
+ result = type(data)._from_sequence(
+ data._pa_array.combine_chunks(), dtype=data.dtype
+ )
+ tm.assert_extension_array_equal(result, data)
+ assert isinstance(result._pa_array, pa.ChunkedArray)
+
+ def test_from_sequence_pa_array_notimplemented(self, request):
+ dtype = ArrowDtype(pa.month_day_nano_interval())
+ with pytest.raises(NotImplementedError, match="Converting strings to"):
+ ArrowExtensionArray._from_sequence_of_strings(["12-1"], dtype=dtype)
+
+ def test_from_sequence_of_strings_pa_array(self, data, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+ if pa.types.is_timestamp(pa_dtype) and pa_dtype.tz is not None:
+ _require_timezone_database(request)
+
+ pa_array = data._pa_array.cast(pa.string())
+ result = type(data)._from_sequence_of_strings(pa_array, dtype=data.dtype)
+ tm.assert_extension_array_equal(result, data)
+
+ pa_array = pa_array.combine_chunks()
+ result = type(data)._from_sequence_of_strings(pa_array, dtype=data.dtype)
+ tm.assert_extension_array_equal(result, data)
+
+ def check_accumulate(self, ser, op_name, skipna):
+ result = getattr(ser, op_name)(skipna=skipna)
+
+ pa_type = ser.dtype.pyarrow_dtype
+ if pa.types.is_temporal(pa_type):
+ # Just check that we match the integer behavior.
+ if pa_type.bit_width == 32:
+ int_type = "int32[pyarrow]"
+ else:
+ int_type = "int64[pyarrow]"
+ ser = ser.astype(int_type)
+ result = result.astype(int_type)
+
+ result = result.astype("Float64")
+ expected = getattr(ser.astype("Float64"), op_name)(skipna=skipna)
+ tm.assert_series_equal(result, expected, check_dtype=False)
+
+ def _supports_accumulation(self, ser: pd.Series, op_name: str) -> bool:
+ # error: Item "dtype[Any]" of "dtype[Any] | ExtensionDtype" has no
+ # attribute "pyarrow_dtype"
+ pa_type = ser.dtype.pyarrow_dtype # type: ignore[union-attr]
+
+ if pa.types.is_binary(pa_type) or pa.types.is_decimal(pa_type):
+ if op_name in ["cumsum", "cumprod", "cummax", "cummin"]:
+ return False
+ elif pa.types.is_string(pa_type):
+ if op_name == "cumprod":
+ return False
+ elif pa.types.is_boolean(pa_type):
+ if op_name in ["cumprod", "cummax", "cummin"]:
+ return False
+ elif pa.types.is_temporal(pa_type):
+ if op_name == "cumsum" and not pa.types.is_duration(pa_type):
+ return False
+ elif op_name == "cumprod":
+ return False
+ return True
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_accumulate_series(self, data, all_numeric_accumulations, skipna, request):
+ pa_type = data.dtype.pyarrow_dtype
+ op_name = all_numeric_accumulations
+
+ if pa.types.is_string(pa_type) and op_name in ["cumsum", "cummin", "cummax"]:
+ # https://github.com/pandas-dev/pandas/pull/60633
+ # Doesn't fit test structure, tested in series/test_cumulative.py instead.
+ return
+
+ ser = pd.Series(data)
+
+ if not self._supports_accumulation(ser, op_name):
+ # The base class test will check that we raise
+ return super().test_accumulate_series(
+ data, all_numeric_accumulations, skipna
+ )
+
+ if all_numeric_accumulations == "cumsum" and (
+ pa.types.is_boolean(pa_type) or pa.types.is_decimal(pa_type)
+ ):
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=f"{all_numeric_accumulations} not implemented for {pa_type}",
+ raises=TypeError,
+ )
+ )
+
+ self.check_accumulate(ser, op_name, skipna)
+
+ def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool:
+ if op_name == "kurt" or (pa_version_under20p0 and op_name == "skew"):
+ return False
+
+ dtype = ser.dtype
+ # error: Item "dtype[Any]" of "dtype[Any] | ExtensionDtype" has
+ # no attribute "pyarrow_dtype"
+ pa_dtype = dtype.pyarrow_dtype # type: ignore[union-attr]
+ if pa.types.is_temporal(pa_dtype) and op_name in ["sum", "var", "prod", "skew"]:
+ if pa.types.is_duration(pa_dtype) and op_name in ["sum"]:
+ # summing timedeltas is one case that *is* well-defined
+ pass
+ else:
+ return False
+ elif pa.types.is_binary(pa_dtype) and op_name in ["sum", "skew"]:
+ return False
+ elif (
+ pa.types.is_string(pa_dtype) or pa.types.is_binary(pa_dtype)
+ ) and op_name in ["mean", "median", "prod", "std", "sem", "var", "skew"]:
+ return False
+
+ if (
+ pa.types.is_temporal(pa_dtype)
+ and not pa.types.is_duration(pa_dtype)
+ and op_name in ["any", "all"]
+ ):
+ # xref GH#34479 we support this in our non-pyarrow datetime64 dtypes,
+ # but it isn't obvious we _should_. For now, we keep the pyarrow
+ # behavior which does not support this.
+ return False
+
+ if pa.types.is_boolean(pa_dtype) and op_name in [
+ "median",
+ "std",
+ "var",
+ "skew",
+ "kurt",
+ "sem",
+ ]:
+ return False
+
+ return True
+
+ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool):
+ # error: Item "dtype[Any]" of "dtype[Any] | ExtensionDtype" has no
+ # attribute "pyarrow_dtype"
+ pa_dtype = ser.dtype.pyarrow_dtype # type: ignore[union-attr]
+ if pa.types.is_integer(pa_dtype) or pa.types.is_floating(pa_dtype):
+ alt = ser.astype("Float64")
+ else:
+ # TODO: in the opposite case, aren't we testing... nothing? For
+ # e.g. date/time dtypes trying to calculate 'expected' by converting
+ # to object will raise for mean, std etc
+ alt = ser
+
+ # TODO: in the opposite case, aren't we testing... nothing?
+ if op_name == "count":
+ result = getattr(ser, op_name)()
+ expected = getattr(alt, op_name)()
+ else:
+ result = getattr(ser, op_name)(skipna=skipna)
+ expected = getattr(alt, op_name)(skipna=skipna)
+ tm.assert_almost_equal(result, expected)
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_series_boolean(
+ self, data, all_boolean_reductions, skipna, na_value, request
+ ):
+ pa_dtype = data.dtype.pyarrow_dtype
+ xfail_mark = pytest.mark.xfail(
+ raises=TypeError,
+ reason=(
+ f"{all_boolean_reductions} is not implemented in "
+ f"pyarrow={pa.__version__} for {pa_dtype}"
+ ),
+ )
+ if pa.types.is_string(pa_dtype) or pa.types.is_binary(pa_dtype):
+ # We *might* want to make this behave like the non-pyarrow cases,
+ # but have not yet decided.
+ request.applymarker(xfail_mark)
+
+ return super().test_reduce_series_boolean(data, all_boolean_reductions, skipna)
+
+ def _get_expected_reduction_dtype(self, arr, op_name: str, skipna: bool):
+ pa_type = arr._pa_array.type
+
+ if op_name in ["max", "min"]:
+ cmp_dtype = arr.dtype
+ elif pa.types.is_temporal(pa_type):
+ if op_name in ["std", "sem"]:
+ if pa.types.is_duration(pa_type):
+ cmp_dtype = arr.dtype
+ elif pa.types.is_date(pa_type):
+ cmp_dtype = ArrowDtype(pa.duration("s"))
+ elif pa.types.is_time(pa_type):
+ cmp_dtype = ArrowDtype(pa.duration(pa_type.unit))
+ else:
+ cmp_dtype = ArrowDtype(pa.duration(pa_type.unit))
+ else:
+ cmp_dtype = arr.dtype
+ elif arr.dtype.name == "decimal128(7, 3)[pyarrow]":
+ if op_name == "sum" and not pa_version_under21p0:
+ # https://github.com/apache/arrow/pull/44184
+ cmp_dtype = ArrowDtype(pa.decimal128(38, 3))
+ elif op_name not in ["median", "var", "std", "sem", "skew"]:
+ cmp_dtype = arr.dtype
+ else:
+ cmp_dtype = "float64[pyarrow]"
+ elif op_name in ["median", "var", "std", "mean", "skew", "sem"]:
+ cmp_dtype = "float64[pyarrow]"
+ elif op_name in ["sum", "prod"] and pa.types.is_boolean(pa_type):
+ cmp_dtype = "uint64[pyarrow]"
+ elif op_name == "sum" and pa.types.is_string(pa_type):
+ cmp_dtype = arr.dtype
+ else:
+ cmp_dtype = {
+ "i": "int64[pyarrow]",
+ "u": "uint64[pyarrow]",
+ "f": "float64[pyarrow]",
+ }[arr.dtype.kind]
+ return cmp_dtype
+
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna, request):
+ if (
+ not pa_version_under20p0
+ and skipna
+ and all_numeric_reductions == "skew"
+ and (
+ pa.types.is_integer(data.dtype.pyarrow_dtype)
+ or pa.types.is_floating(data.dtype.pyarrow_dtype)
+ )
+ ):
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="https://github.com/apache/arrow/issues/45733",
+ )
+ )
+ return super().test_reduce_series_numeric(data, all_numeric_reductions, skipna)
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_frame(self, data, all_numeric_reductions, skipna, request):
+ op_name = all_numeric_reductions
+ if op_name == "skew" and pa_version_under20p0:
+ if data.dtype._is_numeric:
+ mark = pytest.mark.xfail(reason="skew not implemented")
+ request.applymarker(mark)
+ return super().test_reduce_frame(data, all_numeric_reductions, skipna)
+
+ @pytest.mark.parametrize("typ", ["int64", "uint64", "float64"])
+ def test_median_not_approximate(self, typ):
+ # GH 52679
+ result = pd.Series([1, 2], dtype=f"{typ}[pyarrow]").median()
+ assert result == 1.5
+
+ def test_construct_from_string_own_name(self, dtype, request):
+ pa_dtype = dtype.pyarrow_dtype
+ if pa.types.is_decimal(pa_dtype):
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=NotImplementedError,
+ reason=f"pyarrow.type_for_alias cannot infer {pa_dtype}",
+ )
+ )
+
+ if pa.types.is_string(pa_dtype):
+ # We still support StringDtype('pyarrow') over ArrowDtype(pa.string())
+ msg = r"string\[pyarrow\] should be constructed by StringDtype"
+ with pytest.raises(TypeError, match=msg):
+ dtype.construct_from_string(dtype.name)
+
+ return
+
+ super().test_construct_from_string_own_name(dtype)
+
+ def test_is_dtype_from_name(self, dtype, request):
+ pa_dtype = dtype.pyarrow_dtype
+ if pa.types.is_string(pa_dtype):
+ # We still support StringDtype('pyarrow') over ArrowDtype(pa.string())
+ assert not type(dtype).is_dtype(dtype.name)
+ else:
+ if pa.types.is_decimal(pa_dtype):
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=NotImplementedError,
+ reason=f"pyarrow.type_for_alias cannot infer {pa_dtype}",
+ )
+ )
+ super().test_is_dtype_from_name(dtype)
+
+ def test_construct_from_string_another_type_raises(self, dtype):
+ msg = r"'another_type' must end with '\[pyarrow\]'"
+ with pytest.raises(TypeError, match=msg):
+ type(dtype).construct_from_string("another_type")
+
+ def test_get_common_dtype(self, dtype, request):
+ pa_dtype = dtype.pyarrow_dtype
+ if (
+ pa.types.is_date(pa_dtype)
+ or pa.types.is_time(pa_dtype)
+ or (pa.types.is_timestamp(pa_dtype) and pa_dtype.tz is not None)
+ or pa.types.is_binary(pa_dtype)
+ or pa.types.is_decimal(pa_dtype)
+ ):
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=(
+ f"{pa_dtype} does not have associated numpy "
+ f"dtype findable by find_common_type"
+ )
+ )
+ )
+ super().test_get_common_dtype(dtype)
+
+ def test_is_not_string_type(self, dtype):
+ pa_dtype = dtype.pyarrow_dtype
+ if pa.types.is_string(pa_dtype):
+ assert is_string_dtype(dtype)
+ else:
+ super().test_is_not_string_type(dtype)
+
+ @pytest.mark.xfail(
+ reason="GH 45419: pyarrow.ChunkedArray does not support views.", run=False
+ )
+ def test_view(self, data):
+ super().test_view(data)
+
+ def test_fillna_no_op_returns_copy(self, data):
+ data = data[~data.isna()]
+
+ valid = data[0]
+ result = data.fillna(valid)
+ assert result is not data
+ tm.assert_extension_array_equal(result, data)
+
+ def test_fillna_readonly(self, data_missing):
+ data = data_missing.copy()
+ data._readonly = True
+
+ # by default fillna(copy=True), then this works fine
+ result = data.fillna(data_missing[1])
+ assert result[0] == data_missing[1]
+ tm.assert_extension_array_equal(data, data_missing)
+
+ # fillna(copy=False) is generally not honored by Arrow-backed array,
+ # but always returns new data -> same result as above
+ result = data.fillna(data_missing[1])
+ assert result[0] == data_missing[1]
+ tm.assert_extension_array_equal(data, data_missing)
+
+ @pytest.mark.xfail(
+ reason="GH 45419: pyarrow.ChunkedArray does not support views", run=False
+ )
+ def test_transpose(self, data):
+ super().test_transpose(data)
+
+ @pytest.mark.xfail(
+ reason="GH 45419: pyarrow.ChunkedArray does not support views", run=False
+ )
+ def test_setitem_preserves_views(self, data):
+ super().test_setitem_preserves_views(data)
+
+ @pytest.mark.parametrize("dtype_backend", ["pyarrow", no_default])
+ @pytest.mark.parametrize("engine", ["c", "python"])
+ def test_EA_types(self, engine, data, dtype_backend, request, using_nan_is_na):
+ pa_dtype = data.dtype.pyarrow_dtype
+ if pa.types.is_decimal(pa_dtype):
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=NotImplementedError,
+ reason=f"Parameterized types {pa_dtype} not supported.",
+ )
+ )
+ elif pa.types.is_timestamp(pa_dtype) and pa_dtype.unit in ("us", "ns"):
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=ValueError,
+ reason="https://github.com/pandas-dev/pandas/issues/49767",
+ )
+ )
+ elif pa.types.is_binary(pa_dtype):
+ request.applymarker(
+ pytest.mark.xfail(reason="CSV parsers don't correctly handle binary")
+ )
+ df = pd.DataFrame({"with_dtype": pd.Series(data, dtype=str(data.dtype))})
+ if not using_nan_is_na:
+ csv_output = df.to_csv(index=False, na_rep="NA")
+ else:
+ csv_output = df.to_csv(index=False, na_rep=np.nan)
+ if pa.types.is_binary(pa_dtype):
+ csv_output = BytesIO(csv_output)
+ else:
+ csv_output = StringIO(csv_output)
+ result = pd.read_csv(
+ csv_output,
+ dtype={"with_dtype": str(data.dtype)},
+ engine=engine,
+ dtype_backend=dtype_backend,
+ )
+ expected = df
+ tm.assert_frame_equal(result, expected)
+
+ def test_invert(self, data, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+ if not (
+ pa.types.is_boolean(pa_dtype)
+ or pa.types.is_integer(pa_dtype)
+ or pa.types.is_string(pa_dtype)
+ ):
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=pa.ArrowNotImplementedError,
+ reason=f"pyarrow.compute.invert does support {pa_dtype}",
+ )
+ )
+ if PY312 and pa.types.is_boolean(pa_dtype):
+ with tm.assert_produces_warning(
+ DeprecationWarning, match="Bitwise inversion", check_stacklevel=False
+ ):
+ super().test_invert(data)
+ else:
+ super().test_invert(data)
+
+ @pytest.mark.parametrize("periods", [1, -2])
+ def test_diff(self, data, periods, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+ if pa.types.is_unsigned_integer(pa_dtype) and periods == 1:
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=pa.ArrowInvalid,
+ reason=(
+ f"diff with {pa_dtype} and periods={periods} will overflow"
+ ),
+ )
+ )
+ super().test_diff(data, periods)
+
+ def test_value_counts_returns_pyarrow_int64(self, data):
+ # GH 51462
+ data = data[:10]
+ result = data.value_counts()
+ assert result.dtype == ArrowDtype(pa.int64())
+
+ _combine_le_expected_dtype = "bool[pyarrow]"
+
+ def get_op_from_name(self, op_name):
+ short_opname = op_name.strip("_")
+ if short_opname == "rtruediv":
+ # use the numpy version that won't raise on division by zero
+
+ def rtruediv(x, y):
+ return np.divide(y, x)
+
+ return rtruediv
+ elif short_opname == "rfloordiv":
+ return lambda x, y: np.floor_divide(y, x)
+
+ return tm.get_op_from_name(op_name)
+
+ # TODO: use EA._cast_pointwise_result, same with other test files that
+ # override this
+ def _cast_pointwise_result(self, op_name: str, obj, other, pointwise_result):
+ # BaseOpsUtil._combine can upcast expected dtype
+ # (because it generates expected on python scalars)
+ # while ArrowExtensionArray maintains original type
+ expected = pointwise_result
+
+ if op_name in ["eq", "ne", "lt", "le", "gt", "ge"]:
+ return pointwise_result.astype("boolean[pyarrow]")
+
+ original_dtype = tm.get_dtype(expected)
+
+ was_frame = False
+ if isinstance(expected, pd.DataFrame):
+ was_frame = True
+ expected_data = expected.iloc[:, 0]
+ else:
+ expected_data = expected
+
+ # the pointwise method will have retained our original dtype, while
+ # the op(ser, other) version will have cast to 64bit
+ if type(other) is int and op_name not in ["__floordiv__"]:
+ if original_dtype.kind == "f":
+ return expected.astype("float64[pyarrow]")
+ else:
+ return expected.astype("int64[pyarrow]")
+ elif type(other) is float:
+ return expected.astype("float64[pyarrow]")
+
+ # error: Item "ExtensionDtype" of "dtype[Any] | ExtensionDtype" has
+ # no attribute "pyarrow_dtype"
+ orig_pa_type = original_dtype.pyarrow_dtype # type: ignore[union-attr]
+ if not was_frame and isinstance(other, pd.Series):
+ # i.e. test_arith_series_with_array
+ if not (
+ pa.types.is_floating(orig_pa_type)
+ or (
+ pa.types.is_integer(orig_pa_type)
+ and op_name not in ["__truediv__", "__rtruediv__"]
+ )
+ or pa.types.is_duration(orig_pa_type)
+ or pa.types.is_timestamp(orig_pa_type)
+ or pa.types.is_date(orig_pa_type)
+ or pa.types.is_decimal(orig_pa_type)
+ ):
+ # base class _combine always returns int64, while
+ # ArrowExtensionArray does not upcast
+ return expected
+ elif not (
+ (op_name == "__floordiv__" and pa.types.is_integer(orig_pa_type))
+ or pa.types.is_duration(orig_pa_type)
+ or pa.types.is_timestamp(orig_pa_type)
+ or pa.types.is_date(orig_pa_type)
+ or pa.types.is_decimal(orig_pa_type)
+ ):
+ # base class _combine always returns int64, while
+ # ArrowExtensionArray does not upcast
+ return expected
+
+ pa_expected = pa.array(expected_data._values)
+
+ if pa.types.is_decimal(pa_expected.type) and pa.types.is_decimal(orig_pa_type):
+ # decimal precision can resize in the result type depending on data
+ # just compare the float values
+ alt = getattr(obj, op_name)(other)
+ alt_dtype = tm.get_dtype(alt)
+ assert isinstance(alt_dtype, ArrowDtype)
+ if op_name == "__pow__" and isinstance(other, Decimal):
+ # TODO: would it make more sense to retain Decimal here?
+ alt_dtype = ArrowDtype(pa.float64())
+ elif (
+ op_name == "__pow__"
+ and isinstance(other, pd.Series)
+ and other.dtype == original_dtype
+ ):
+ # TODO: would it make more sense to retain Decimal here?
+ alt_dtype = ArrowDtype(pa.float64())
+ else:
+ assert pa.types.is_decimal(alt_dtype.pyarrow_dtype)
+ return expected.astype(alt_dtype)
+
+ else:
+ pa_expected = pa_expected.cast(orig_pa_type)
+
+ pd_expected = type(expected_data._values)(pa_expected)
+ if was_frame:
+ expected = pd.DataFrame(
+ pd_expected, index=expected.index, columns=expected.columns
+ )
+ else:
+ expected = pd.Series(pd_expected)
+ return expected
+
+ def _is_temporal_supported(self, opname, pa_dtype):
+ return (
+ (
+ opname in ("__add__", "__radd__")
+ or (
+ opname
+ in ("__truediv__", "__rtruediv__", "__floordiv__", "__rfloordiv__")
+ and not pa_version_under14p0
+ )
+ )
+ and pa.types.is_duration(pa_dtype)
+ ) or (opname in ("__sub__", "__rsub__") and pa.types.is_temporal(pa_dtype))
+
+ def _get_expected_exception(
+ self, op_name: str, obj, other
+ ) -> type[Exception] | tuple[type[Exception], ...] | None:
+ if op_name in ("__divmod__", "__rdivmod__"):
+ return (NotImplementedError, TypeError)
+
+ exc: type[Exception] | tuple[type[Exception], ...] | None
+ dtype = tm.get_dtype(obj)
+ # error: Item "dtype[Any]" of "dtype[Any] | ExtensionDtype" has no
+ # attribute "pyarrow_dtype"
+ pa_dtype = dtype.pyarrow_dtype # type: ignore[union-attr]
+
+ arrow_temporal_supported = self._is_temporal_supported(op_name, pa_dtype)
+ if op_name in {
+ "__mod__",
+ "__rmod__",
+ }:
+ exc = (NotImplementedError, TypeError)
+ elif arrow_temporal_supported:
+ exc = None
+ elif op_name in ["__add__", "__radd__"] and (
+ pa.types.is_string(pa_dtype) or pa.types.is_binary(pa_dtype)
+ ):
+ exc = None
+ elif not (
+ pa.types.is_floating(pa_dtype)
+ or pa.types.is_integer(pa_dtype)
+ or pa.types.is_decimal(pa_dtype)
+ ):
+ exc = TypeError
+ else:
+ exc = None
+ return exc
+
+ def _get_arith_xfail_marker(self, opname, pa_dtype):
+ mark = None
+
+ arrow_temporal_supported = self._is_temporal_supported(opname, pa_dtype)
+
+ if opname == "__rpow__" and (
+ pa.types.is_floating(pa_dtype)
+ or pa.types.is_integer(pa_dtype)
+ or pa.types.is_decimal(pa_dtype)
+ ):
+ mark = pytest.mark.xfail(
+ reason=(
+ f"GH#29997: 1**pandas.NA == 1 while 1**pyarrow.NA == NULL "
+ f"for {pa_dtype}"
+ )
+ )
+ elif arrow_temporal_supported and (
+ pa.types.is_time(pa_dtype)
+ or (
+ opname
+ in ("__truediv__", "__rtruediv__", "__floordiv__", "__rfloordiv__")
+ and pa.types.is_duration(pa_dtype)
+ )
+ ):
+ mark = pytest.mark.xfail(
+ raises=TypeError,
+ reason=(
+ f"{opname} not supported betweenpd.NA and {pa_dtype} Python scalar"
+ ),
+ )
+ elif opname == "__rfloordiv__" and (
+ pa.types.is_integer(pa_dtype) or pa.types.is_decimal(pa_dtype)
+ ):
+ mark = pytest.mark.xfail(
+ raises=pa.ArrowInvalid,
+ reason="divide by 0",
+ )
+ elif opname == "__rtruediv__" and pa.types.is_decimal(pa_dtype):
+ mark = pytest.mark.xfail(
+ raises=pa.ArrowInvalid,
+ reason="divide by 0",
+ )
+
+ return mark
+
+ def test_arith_series_with_scalar(self, data, all_arithmetic_operators, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+
+ if all_arithmetic_operators == "__rmod__" and pa.types.is_binary(pa_dtype):
+ pytest.skip("Skip testing Python string formatting")
+
+ mark = self._get_arith_xfail_marker(all_arithmetic_operators, pa_dtype)
+ if mark is not None:
+ request.applymarker(mark)
+
+ super().test_arith_series_with_scalar(data, all_arithmetic_operators)
+
+ def test_arith_frame_with_scalar(self, data, all_arithmetic_operators, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+
+ if all_arithmetic_operators == "__rmod__" and (
+ pa.types.is_string(pa_dtype) or pa.types.is_binary(pa_dtype)
+ ):
+ pytest.skip("Skip testing Python string formatting")
+
+ mark = self._get_arith_xfail_marker(all_arithmetic_operators, pa_dtype)
+ if mark is not None:
+ request.applymarker(mark)
+
+ super().test_arith_frame_with_scalar(data, all_arithmetic_operators)
+
+ def test_arith_series_with_array(self, data, all_arithmetic_operators, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+
+ if all_arithmetic_operators in (
+ "__sub__",
+ "__rsub__",
+ ) and pa.types.is_unsigned_integer(pa_dtype):
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=pa.ArrowInvalid,
+ reason=(
+ f"Implemented pyarrow.compute.subtract_checked "
+ f"which raises on overflow for {pa_dtype}"
+ ),
+ )
+ )
+
+ mark = self._get_arith_xfail_marker(all_arithmetic_operators, pa_dtype)
+ if mark is not None:
+ request.applymarker(mark)
+
+ op_name = all_arithmetic_operators
+ ser = pd.Series(data)
+ # pd.Series([ser.iloc[0]] * len(ser)) may not return ArrowExtensionArray
+ # since ser.iloc[0] is a python scalar
+ other = pd.Series(pd.array([ser.iloc[0]] * len(ser), dtype=data.dtype))
+
+ self.check_opname(ser, op_name, other)
+
+ def test_add_series_with_extension_array(self, data, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+
+ if pa_dtype.equals("int8"):
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=pa.ArrowInvalid,
+ reason=f"raises on overflow for {pa_dtype}",
+ )
+ )
+ super().test_add_series_with_extension_array(data)
+
+ def test_invalid_other_comp(self, data, comparison_op):
+ # GH 48833
+ with pytest.raises(
+ NotImplementedError, match=".* not implemented for "
+ ):
+ comparison_op(data, object())
+
+ @pytest.mark.parametrize("masked_dtype", ["boolean", "Int64", "Float64"])
+ def test_comp_masked_numpy(self, masked_dtype, comparison_op):
+ # GH 52625
+ data = [1, 0, None]
+ ser_masked = pd.Series(data, dtype=masked_dtype)
+ ser_pa = pd.Series(data, dtype=f"{masked_dtype.lower()}[pyarrow]")
+ result = comparison_op(ser_pa, ser_masked)
+ if comparison_op in [operator.lt, operator.gt, operator.ne]:
+ exp = [False, False, None]
+ else:
+ exp = [True, True, None]
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_setitem_with_expansion_preserves_ea_index_dtype(self, data, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+ if pa.types.is_date(pa_dtype):
+ mark = pytest.mark.xfail(
+ reason="GH#62343 incorrectly casts to timestamp[ms][pyarrow]"
+ )
+ request.applymarker(mark)
+ super().test_loc_setitem_with_expansion_preserves_ea_index_dtype(data)
+
+
+class TestLogicalOps:
+ """Various Series and DataFrame logical ops methods."""
+
+ def test_kleene_or(self):
+ a = pd.Series([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean[pyarrow]")
+ b = pd.Series([True, False, None] * 3, dtype="boolean[pyarrow]")
+ result = a | b
+ expected = pd.Series(
+ [True, True, True, True, False, None, True, None, None],
+ dtype="boolean[pyarrow]",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = b | a
+ tm.assert_series_equal(result, expected)
+
+ # ensure we haven't mutated anything inplace
+ tm.assert_series_equal(
+ a,
+ pd.Series([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean[pyarrow]"),
+ )
+ tm.assert_series_equal(
+ b, pd.Series([True, False, None] * 3, dtype="boolean[pyarrow]")
+ )
+
+ @pytest.mark.parametrize(
+ "other, expected",
+ [
+ (None, [True, None, None]),
+ (pd.NA, [True, None, None]),
+ (True, [True, True, True]),
+ (np.bool_(True), [True, True, True]),
+ (False, [True, False, None]),
+ (np.bool_(False), [True, False, None]),
+ ],
+ )
+ def test_kleene_or_scalar(self, other, expected):
+ a = pd.Series([True, False, None], dtype="boolean[pyarrow]")
+ result = a | other
+ expected = pd.Series(expected, dtype="boolean[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+ result = other | a
+ tm.assert_series_equal(result, expected)
+
+ # ensure we haven't mutated anything inplace
+ tm.assert_series_equal(
+ a, pd.Series([True, False, None], dtype="boolean[pyarrow]")
+ )
+
+ def test_kleene_and(self):
+ a = pd.Series([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean[pyarrow]")
+ b = pd.Series([True, False, None] * 3, dtype="boolean[pyarrow]")
+ result = a & b
+ expected = pd.Series(
+ [True, False, None, False, False, False, None, False, None],
+ dtype="boolean[pyarrow]",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = b & a
+ tm.assert_series_equal(result, expected)
+
+ # ensure we haven't mutated anything inplace
+ tm.assert_series_equal(
+ a,
+ pd.Series([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean[pyarrow]"),
+ )
+ tm.assert_series_equal(
+ b, pd.Series([True, False, None] * 3, dtype="boolean[pyarrow]")
+ )
+
+ @pytest.mark.parametrize(
+ "other, expected",
+ [
+ (None, [None, False, None]),
+ (pd.NA, [None, False, None]),
+ (True, [True, False, None]),
+ (False, [False, False, False]),
+ (np.bool_(True), [True, False, None]),
+ (np.bool_(False), [False, False, False]),
+ ],
+ )
+ def test_kleene_and_scalar(self, other, expected):
+ a = pd.Series([True, False, None], dtype="boolean[pyarrow]")
+ result = a & other
+ expected = pd.Series(expected, dtype="boolean[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+ result = other & a
+ tm.assert_series_equal(result, expected)
+
+ # ensure we haven't mutated anything inplace
+ tm.assert_series_equal(
+ a, pd.Series([True, False, None], dtype="boolean[pyarrow]")
+ )
+
+ def test_kleene_xor(self):
+ a = pd.Series([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean[pyarrow]")
+ b = pd.Series([True, False, None] * 3, dtype="boolean[pyarrow]")
+ result = a ^ b
+ expected = pd.Series(
+ [False, True, None, True, False, None, None, None, None],
+ dtype="boolean[pyarrow]",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = b ^ a
+ tm.assert_series_equal(result, expected)
+
+ # ensure we haven't mutated anything inplace
+ tm.assert_series_equal(
+ a,
+ pd.Series([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean[pyarrow]"),
+ )
+ tm.assert_series_equal(
+ b, pd.Series([True, False, None] * 3, dtype="boolean[pyarrow]")
+ )
+
+ @pytest.mark.parametrize(
+ "other, expected",
+ [
+ (None, [None, None, None]),
+ (pd.NA, [None, None, None]),
+ (True, [False, True, None]),
+ (np.bool_(True), [False, True, None]),
+ (np.bool_(False), [True, False, None]),
+ ],
+ )
+ def test_kleene_xor_scalar(self, other, expected):
+ a = pd.Series([True, False, None], dtype="boolean[pyarrow]")
+ result = a ^ other
+ expected = pd.Series(expected, dtype="boolean[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+ result = other ^ a
+ tm.assert_series_equal(result, expected)
+
+ # ensure we haven't mutated anything inplace
+ tm.assert_series_equal(
+ a, pd.Series([True, False, None], dtype="boolean[pyarrow]")
+ )
+
+ @pytest.mark.parametrize(
+ "op, exp",
+ [
+ ["__and__", True],
+ ["__or__", True],
+ ["__xor__", False],
+ ],
+ )
+ def test_logical_masked_numpy(self, op, exp):
+ # GH 52625
+ data = [True, False, None]
+ ser_masked = pd.Series(data, dtype="boolean")
+ ser_pa = pd.Series(data, dtype="boolean[pyarrow]")
+ result = getattr(ser_pa, op)(ser_masked)
+ expected = pd.Series([exp, False, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("pa_type", tm.ALL_INT_PYARROW_DTYPES)
+def test_bitwise(pa_type):
+ # GH 54495
+ dtype = ArrowDtype(pa_type)
+ left = pd.Series([1, None, 3, 4], dtype=dtype)
+ right = pd.Series([None, 3, 5, 4], dtype=dtype)
+
+ result = left | right
+ expected = pd.Series([None, None, 3 | 5, 4 | 4], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ result = left & right
+ expected = pd.Series([None, None, 3 & 5, 4 & 4], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ result = left ^ right
+ expected = pd.Series([None, None, 3 ^ 5, 4 ^ 4], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ result = ~left
+ expected = ~(left.fillna(0).to_numpy())
+ expected = pd.Series(expected, dtype=dtype).mask(left.isnull())
+ tm.assert_series_equal(result, expected)
+
+
+def test_arrowdtype_construct_from_string_type_with_unsupported_parameters():
+ with pytest.raises(NotImplementedError, match="Passing pyarrow type"):
+ ArrowDtype.construct_from_string("not_a_real_dype[s, tz=UTC][pyarrow]")
+
+ with pytest.raises(NotImplementedError, match="Passing pyarrow type"):
+ ArrowDtype.construct_from_string("decimal(7, 2)[pyarrow]")
+
+
+def test_arrowdtype_construct_from_string_supports_dt64tz():
+ # as of GH#50689, timestamptz is supported
+ dtype = ArrowDtype.construct_from_string("timestamp[s, tz=UTC][pyarrow]")
+ expected = ArrowDtype(pa.timestamp("s", "UTC"))
+ assert dtype == expected
+
+
+def test_arrowdtype_construct_from_string_type_only_one_pyarrow():
+ # GH#51225
+ invalid = "int64[pyarrow]foobar[pyarrow]"
+ msg = (
+ r"Passing pyarrow type specific parameters \(\[pyarrow\]\) in the "
+ r"string is not supported\."
+ )
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.Series(range(3), dtype=invalid)
+
+
+def test_arrow_string_multiplication():
+ # GH 56537
+ binary = pd.Series(["abc", "defg"], dtype=ArrowDtype(pa.string()))
+ repeat = pd.Series([2, -2], dtype="int64[pyarrow]")
+ result = binary * repeat
+ expected = pd.Series(["abcabc", ""], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+ reflected_result = repeat * binary
+ tm.assert_series_equal(result, reflected_result)
+
+
+def test_arrow_string_multiplication_scalar_repeat():
+ binary = pd.Series(["abc", "defg"], dtype=ArrowDtype(pa.string()))
+ result = binary * 2
+ expected = pd.Series(["abcabc", "defgdefg"], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+ reflected_result = 2 * binary
+ tm.assert_series_equal(reflected_result, expected)
+
+
+@pytest.mark.parametrize(
+ "interpolation", ["linear", "lower", "higher", "nearest", "midpoint"]
+)
+@pytest.mark.parametrize("quantile", [0.5, [0.5, 0.5]])
+def test_quantile(data, interpolation, quantile, request):
+ pa_dtype = data.dtype.pyarrow_dtype
+
+ data = data.take([0, 0, 0])
+ ser = pd.Series(data)
+
+ if (
+ pa.types.is_string(pa_dtype)
+ or pa.types.is_binary(pa_dtype)
+ or pa.types.is_boolean(pa_dtype)
+ ):
+ # For string, bytes, and bool, we don't *expect* to have quantile work
+ # Note this matches the non-pyarrow behavior
+ msg = r"Function 'quantile' has no kernel matching input types \(.*\)"
+ with pytest.raises(pa.ArrowNotImplementedError, match=msg):
+ ser.quantile(q=quantile, interpolation=interpolation)
+ return
+
+ if (
+ pa.types.is_integer(pa_dtype)
+ or pa.types.is_floating(pa_dtype)
+ or pa.types.is_decimal(pa_dtype)
+ ):
+ pass
+ elif pa.types.is_temporal(data._pa_array.type):
+ pass
+ else:
+ request.applymarker(
+ pytest.mark.xfail(
+ raises=pa.ArrowNotImplementedError,
+ reason=f"quantile not supported by pyarrow for {pa_dtype}",
+ )
+ )
+ data = data.take([0, 0, 0])
+ ser = pd.Series(data)
+ result = ser.quantile(q=quantile, interpolation=interpolation)
+
+ if pa.types.is_timestamp(pa_dtype) and interpolation not in ["lower", "higher"]:
+ # rounding error will make the check below fail
+ # (e.g. '2020-01-01 01:01:01.000001' vs '2020-01-01 01:01:01.000001024'),
+ # so we'll check for now that we match the numpy analogue
+ if pa_dtype.tz:
+ pd_dtype = f"M8[{pa_dtype.unit}, {pa_dtype.tz}]"
+ else:
+ pd_dtype = f"M8[{pa_dtype.unit}]"
+ ser_np = ser.astype(pd_dtype)
+
+ expected = ser_np.quantile(q=quantile, interpolation=interpolation)
+ if quantile == 0.5:
+ if pa_dtype.unit == "us":
+ expected = expected.to_pydatetime(warn=False)
+ assert result == expected
+ else:
+ if pa_dtype.unit == "us":
+ expected = expected.dt.floor("us")
+ tm.assert_series_equal(result, expected.astype(data.dtype))
+ return
+
+ if quantile == 0.5:
+ assert result == data[0]
+ else:
+ # Just check the values
+ expected = pd.Series(data.take([0, 0]), index=[0.5, 0.5])
+ if (
+ pa.types.is_integer(pa_dtype)
+ or pa.types.is_floating(pa_dtype)
+ or pa.types.is_decimal(pa_dtype)
+ ):
+ expected = expected.astype("float64[pyarrow]")
+ result = result.astype("float64[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "take_idx, exp_idx",
+ [[[0, 0, 2, 2, 4, 4], [4, 0]], [[0, 0, 0, 2, 4, 4], [0]]],
+ ids=["multi_mode", "single_mode"],
+)
+def test_mode_dropna_true(data_for_grouping, take_idx, exp_idx):
+ data = data_for_grouping.take(take_idx)
+ ser = pd.Series(data)
+ result = ser.mode(dropna=True)
+ expected = pd.Series(data_for_grouping.take(exp_idx))
+ tm.assert_series_equal(result, expected)
+
+
+def test_mode_dropna_false_mode_na(data):
+ # GH 50982
+ more_nans = pd.Series([None, None, data[0]], dtype=data.dtype)
+ result = more_nans.mode(dropna=False)
+ expected = pd.Series([None], dtype=data.dtype)
+ tm.assert_series_equal(result, expected)
+
+ expected = pd.Series([data[0], None], dtype=data.dtype)
+ result = expected.mode(dropna=False)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "arrow_dtype, expected_type",
+ [
+ [pa.binary(), bytes],
+ [pa.binary(16), bytes],
+ [pa.large_binary(), bytes],
+ [pa.large_string(), str],
+ [pa.list_(pa.int64()), list],
+ [pa.large_list(pa.int64()), list],
+ [pa.map_(pa.string(), pa.int64()), list],
+ [pa.struct([("f1", pa.int8()), ("f2", pa.string())]), dict],
+ [pa.dictionary(pa.int64(), pa.int64()), CategoricalDtypeType],
+ ],
+)
+def test_arrow_dtype_type(arrow_dtype, expected_type):
+ # GH 51845
+ # TODO: Redundant with test_getitem_scalar once arrow_dtype exists in data fixture
+ assert ArrowDtype(arrow_dtype).type == expected_type
+
+
+def test_is_bool_dtype():
+ # GH 22667
+ data = ArrowExtensionArray(pa.array([True, False, True]))
+ assert is_bool_dtype(data)
+ assert pd.core.common.is_bool_indexer(data)
+ s = pd.Series(range(len(data)))
+ result = s[data]
+ expected = s[np.asarray(data)]
+ tm.assert_series_equal(result, expected)
+
+
+def test_is_numeric_dtype(data):
+ # GH 50563
+ pa_type = data.dtype.pyarrow_dtype
+ if (
+ pa.types.is_floating(pa_type)
+ or pa.types.is_integer(pa_type)
+ or pa.types.is_decimal(pa_type)
+ ):
+ assert is_numeric_dtype(data)
+ else:
+ assert not is_numeric_dtype(data)
+
+
+def test_is_integer_dtype(data):
+ # GH 50667
+ pa_type = data.dtype.pyarrow_dtype
+ if pa.types.is_integer(pa_type):
+ assert is_integer_dtype(data)
+ else:
+ assert not is_integer_dtype(data)
+
+
+def test_is_signed_integer_dtype(data):
+ pa_type = data.dtype.pyarrow_dtype
+ if pa.types.is_signed_integer(pa_type):
+ assert is_signed_integer_dtype(data)
+ else:
+ assert not is_signed_integer_dtype(data)
+
+
+def test_is_unsigned_integer_dtype(data):
+ pa_type = data.dtype.pyarrow_dtype
+ if pa.types.is_unsigned_integer(pa_type):
+ assert is_unsigned_integer_dtype(data)
+ else:
+ assert not is_unsigned_integer_dtype(data)
+
+
+def test_is_datetime64_any_dtype(data):
+ pa_type = data.dtype.pyarrow_dtype
+ if pa.types.is_timestamp(pa_type) or pa.types.is_date(pa_type):
+ assert is_datetime64_any_dtype(data)
+ else:
+ assert not is_datetime64_any_dtype(data)
+
+
+def test_is_float_dtype(data):
+ pa_type = data.dtype.pyarrow_dtype
+ if pa.types.is_floating(pa_type):
+ assert is_float_dtype(data)
+ else:
+ assert not is_float_dtype(data)
+
+
+def test_pickle_roundtrip(data):
+ # GH 42600
+ expected = pd.Series(data)
+ expected_sliced = expected.head(2)
+ full_pickled = pickle.dumps(expected)
+ sliced_pickled = pickle.dumps(expected_sliced)
+
+ assert len(full_pickled) > len(sliced_pickled)
+
+ result = pickle.loads(full_pickled)
+ tm.assert_series_equal(result, expected)
+
+ result_sliced = pickle.loads(sliced_pickled)
+ tm.assert_series_equal(result_sliced, expected_sliced)
+
+
+def test_astype_from_non_pyarrow(data):
+ # GH49795
+ np_arr = data.to_numpy()
+ pd_array = pd.array(np_arr, dtype=np_arr.dtype)
+ result = pd_array.astype(data.dtype)
+ assert not isinstance(pd_array.dtype, ArrowDtype)
+ assert isinstance(result.dtype, ArrowDtype)
+ tm.assert_extension_array_equal(result, data)
+
+
+def test_astype_float_from_non_pyarrow_str():
+ # GH50430
+ ser = pd.Series(["1.0"])
+ result = ser.astype("float64[pyarrow]")
+ expected = pd.Series([1.0], dtype="float64[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+
+def test_astype_errors_ignore():
+ # GH 55399
+ expected = pd.DataFrame({"col": [17000000]}, dtype="int32[pyarrow]")
+ result = expected.astype("float[pyarrow]", errors="ignore")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_to_numpy_with_defaults(data, using_nan_is_na):
+ # GH49973
+ result = data.to_numpy()
+
+ pa_type = data._pa_array.type
+ if pa.types.is_duration(pa_type) or pa.types.is_timestamp(pa_type):
+ pytest.skip("Tested in test_to_numpy_temporal")
+ elif pa.types.is_date(pa_type):
+ expected = np.array(list(data))
+ else:
+ expected = np.array(data._pa_array)
+
+ if data._hasna and (not is_numeric_dtype(data.dtype) or not using_nan_is_na):
+ expected = expected.astype(object)
+ expected[pd.isna(data)] = pd.NA
+
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_to_numpy_int_with_na(using_nan_is_na):
+ # GH51227: ensure to_numpy does not convert int to float
+ data = [1, None]
+ arr = pd.array(data, dtype="int64[pyarrow]")
+ result = arr.to_numpy()
+ if not using_nan_is_na:
+ expected = np.array([1, pd.NA], dtype=object)
+ else:
+ expected = np.array([1, np.nan])
+ assert isinstance(result[0], float)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("na_val, exp", [(lib.no_default, np.nan), (1, 1)])
+def test_to_numpy_null_array(na_val, exp):
+ # GH#52443
+ arr = pd.array([pd.NA, pd.NA], dtype="null[pyarrow]")
+ result = arr.to_numpy(dtype="float64", na_value=na_val)
+ expected = np.array([exp] * 2, dtype="float64")
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_to_numpy_null_array_no_dtype():
+ # GH#52443
+ arr = pd.array([pd.NA, pd.NA], dtype="null[pyarrow]")
+ result = arr.to_numpy(dtype=None)
+ expected = np.array([pd.NA] * 2, dtype="object")
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_to_numpy_without_dtype():
+ # GH 54808
+ arr = pd.array([True, pd.NA], dtype="boolean[pyarrow]")
+ result = arr.to_numpy(na_value=False)
+ expected = np.array([True, False], dtype=np.bool_)
+ tm.assert_numpy_array_equal(result, expected)
+
+ arr = pd.array([1.0, pd.NA], dtype="float32[pyarrow]")
+ result = arr.to_numpy(na_value=0.0)
+ expected = np.array([1.0, 0.0], dtype=np.float32)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_setitem_null_slice(data):
+ # GH50248
+ orig = data.copy()
+
+ result = orig.copy()
+ result[:] = data[0]
+ expected = ArrowExtensionArray._from_sequence(
+ [data[0]] * len(data),
+ dtype=data.dtype,
+ )
+ tm.assert_extension_array_equal(result, expected)
+
+ result = orig.copy()
+ result[:] = data[::-1]
+ expected = data[::-1]
+ tm.assert_extension_array_equal(result, expected)
+
+ result = orig.copy()
+ result[:] = data.tolist()
+ expected = data
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_setitem_invalid_dtype(data):
+ # GH50248
+ pa_type = data._pa_array.type
+ if pa.types.is_string(pa_type) or pa.types.is_binary(pa_type):
+ fill_value = 123
+ err = TypeError
+ msg = "Invalid value '123' for dtype"
+ elif (
+ pa.types.is_integer(pa_type)
+ or pa.types.is_floating(pa_type)
+ or pa.types.is_boolean(pa_type)
+ ):
+ fill_value = "foo"
+ err = pa.ArrowInvalid
+ msg = "Could not convert"
+ else:
+ fill_value = "foo"
+ err = TypeError
+ msg = "Invalid value 'foo' for dtype"
+ with pytest.raises(err, match=msg):
+ data[:] = fill_value
+
+
+def test_from_arrow_respecting_given_dtype():
+ date_array = pa.array(
+ [pd.Timestamp("2019-12-31"), pd.Timestamp("2019-12-31")], type=pa.date32()
+ )
+ result = date_array.to_pandas(
+ types_mapper={pa.date32(): ArrowDtype(pa.date64())}.get
+ )
+ expected = pd.Series(
+ [pd.Timestamp("2019-12-31"), pd.Timestamp("2019-12-31")],
+ dtype=ArrowDtype(pa.date64()),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_from_arrow_respecting_given_dtype_unsafe():
+ array = pa.array([1.5, 2.5], type=pa.float64())
+ with tm.external_error_raised(pa.ArrowInvalid):
+ array.to_pandas(types_mapper={pa.float64(): ArrowDtype(pa.int64())}.get)
+
+
+def test_round():
+ dtype = "float64[pyarrow]"
+
+ ser = pd.Series([0.0, 1.23, 2.56, pd.NA], dtype=dtype)
+ result = ser.round(1)
+ expected = pd.Series([0.0, 1.2, 2.6, pd.NA], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ ser = pd.Series([123.4, pd.NA, 56.78], dtype=dtype)
+ result = ser.round(-1)
+ expected = pd.Series([120.0, pd.NA, 60.0], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_searchsorted_with_na_raises(data_for_sorting, as_series):
+ # GH50447
+ b, c, a = data_for_sorting
+ arr = data_for_sorting.take([2, 0, 1]) # to get [a, b, c]
+ arr[-1] = pd.NA
+
+ if as_series:
+ arr = pd.Series(arr)
+
+ msg = (
+ "searchsorted requires array to be sorted, "
+ "which is impossible with NAs present."
+ )
+ with pytest.raises(ValueError, match=msg):
+ arr.searchsorted(b)
+
+
+def test_sort_values_dictionary():
+ df = pd.DataFrame(
+ {
+ "a": pd.Series(
+ ["x", "y"], dtype=ArrowDtype(pa.dictionary(pa.int32(), pa.string()))
+ ),
+ "b": [1, 2],
+ },
+ )
+ expected = df.copy()
+ result = df.sort_values(by=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("pat", ["abc", "a[a-z]{2}"])
+def test_str_count(pat):
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.count(pat)
+ expected = pd.Series([1, None], dtype=ArrowDtype(pa.int32()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_count_flags_unsupported():
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ with pytest.raises(NotImplementedError, match="count not"):
+ ser.str.count("abc", flags=1)
+
+
+@pytest.mark.parametrize(
+ "side, str_func", [["left", "rjust"], ["right", "ljust"], ["both", "center"]]
+)
+def test_str_pad(side, str_func):
+ ser = pd.Series(["a", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.pad(width=3, side=side, fillchar="x")
+ expected = pd.Series(
+ [getattr("a", str_func)(3, "x"), None], dtype=ArrowDtype(pa.string())
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_pad_invalid_side():
+ ser = pd.Series(["a", None], dtype=ArrowDtype(pa.string()))
+ with pytest.raises(ValueError, match="Invalid side: foo"):
+ ser.str.pad(3, "foo", "x")
+
+
+@pytest.mark.parametrize(
+ "pat, case, na, regex, exp",
+ [
+ ["ab", False, None, False, [True, None]],
+ ["Ab", True, None, False, [False, None]],
+ ["ab", False, True, False, [True, True]],
+ ["a[a-z]{1}", False, None, True, [True, None]],
+ ["A[a-z]{1}", True, None, True, [False, None]],
+ ],
+)
+def test_str_contains(pat, case, na, regex, exp):
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.contains(pat, case=case, na=na, regex=regex)
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_contains_flags_unsupported():
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ with pytest.raises(NotImplementedError, match="contains not"):
+ ser.str.contains("a", flags=1)
+
+
+def test_str_contains_re2_unicode_escape():
+ # GH 63901
+ ser = pd.Series(["a", "\u0e01", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.contains(r"[\x{0e00}-\x{0e7f}]")
+ expected = pd.Series([False, True, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "side, pat, na, exp",
+ [
+ ["startswith", "ab", None, [True, None, False]],
+ ["startswith", "b", False, [False, False, False]],
+ ["endswith", "b", True, [False, True, False]],
+ ["endswith", "bc", None, [True, None, False]],
+ ["startswith", ("a", "e", "g"), None, [True, None, True]],
+ ["endswith", ("a", "c", "g"), None, [True, None, True]],
+ ["startswith", (), None, [False, None, False]],
+ ["endswith", (), None, [False, None, False]],
+ ],
+)
+def test_str_start_ends_with(side, pat, na, exp):
+ ser = pd.Series(["abc", None, "efg"], dtype=ArrowDtype(pa.string()))
+ result = getattr(ser.str, side)(pat, na=na)
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("side", ("startswith", "endswith"))
+def test_str_starts_ends_with_all_nulls_empty_tuple(side):
+ ser = pd.Series([None, None], dtype=ArrowDtype(pa.string()))
+ result = getattr(ser.str, side)(())
+
+ # bool datatype preserved for all nulls.
+ expected = pd.Series([None, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "arg_name, arg",
+ [["pat", re.compile("b")], ["repl", str], ["case", False], ["flags", 1]],
+)
+def test_str_replace_unsupported(arg_name, arg):
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ kwargs = {"pat": "b", "repl": "x", "regex": True}
+ kwargs[arg_name] = arg
+ with pytest.raises(NotImplementedError, match="replace is not supported"):
+ ser.str.replace(**kwargs)
+
+
+@pytest.mark.parametrize(
+ "pat, repl, n, regex, exp",
+ [
+ ["a", "x", -1, False, ["xbxc", None]],
+ ["a", "x", 1, False, ["xbac", None]],
+ ["[a-b]", "x", -1, True, ["xxxc", None]],
+ ],
+)
+def test_str_replace(pat, repl, n, regex, exp):
+ ser = pd.Series(["abac", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.replace(pat, repl, n=n, regex=regex)
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_replace_re2_unicode_property():
+ ser = pd.Series(["Jan", "Feb", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.replace(r"\p{Lu}", "U", regex=True)
+ expected = pd.Series(["Uan", "Ueb", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_replace_negative_n():
+ # GH 56404
+ ser = pd.Series(["abc", "aaaaaa"], dtype=ArrowDtype(pa.string()))
+ actual = ser.str.replace("a", "", -3, True)
+ expected = pd.Series(["bc", ""], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(expected, actual)
+
+ # Same bug for pyarrow-backed StringArray GH#59628
+ ser2 = ser.astype(pd.StringDtype(storage="pyarrow"))
+ actual2 = ser2.str.replace("a", "", -3, True)
+ expected2 = expected.astype(ser2.dtype)
+ tm.assert_series_equal(expected2, actual2)
+
+ ser3 = ser.astype(pd.StringDtype(storage="pyarrow", na_value=np.nan))
+ actual3 = ser3.str.replace("a", "", -3, True)
+ expected3 = expected.astype(ser3.dtype)
+ tm.assert_series_equal(expected3, actual3)
+
+
+def test_str_repeat_unsupported():
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ with pytest.raises(NotImplementedError, match="repeat is not"):
+ ser.str.repeat([1, 2])
+
+
+def test_str_repeat():
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.repeat(2)
+ expected = pd.Series(["abcabc", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "pat, case, na, exp",
+ [
+ ["ab", False, None, [True, None]],
+ ["Ab", True, None, [False, None]],
+ ["bc", True, None, [False, None]],
+ ["ab", False, True, [True, True]],
+ ["a[a-z]{1}", False, None, [True, None]],
+ ["A[a-z]{1}", True, None, [False, None]],
+ ],
+)
+def test_str_match(pat, case, na, exp):
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.match(pat, case=case, na=na)
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "pat, case, na, exp",
+ # Note: keep cases in sync with
+ # pandas/tests/strings/test_find_replace.py::test_str_fullmatch_extra_cases
+ [
+ ["abc", False, None, [True, False, False, None]],
+ ["Abc", True, None, [False, False, False, None]],
+ ["bc", True, None, [False, False, False, None]],
+ ["ab", False, None, [False, False, False, None]],
+ ["a[a-z]{2}", False, None, [True, False, False, None]],
+ ["A[a-z]{1}", True, None, [False, False, False, None]],
+ # GH Issue: #56652
+ ["abc$", False, None, [True, False, False, None]],
+ ["abc\\$", False, None, [False, True, False, None]],
+ ["Abc$", True, None, [False, False, False, None]],
+ ["Abc\\$", True, None, [False, False, False, None]],
+ # https://github.com/pandas-dev/pandas/issues/61072
+ ["(abc)|(abx)", True, None, [True, False, False, None]],
+ ["((abc)|(abx))", True, None, [True, False, False, None]],
+ ],
+)
+def test_str_fullmatch(pat, case, na, exp):
+ ser = pd.Series(["abc", "abc$", "$abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.fullmatch(pat, case=case, na=na)
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "sub, start, end, exp",
+ [
+ ["ab", 0, None, [0, None]],
+ ["bc", 1, 3, [1, None]],
+ ["ab", 1, 3, [-1, None]],
+ ["ab", -3, -3, [-1, None]],
+ ],
+)
+def test_str_find(sub, start, end, exp):
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.find(sub, start=start, end=end)
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.int64()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_find_negative_start():
+ # GH 56411
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.find(sub="b", start=-1000, end=3)
+ expected = pd.Series([1, None], dtype=ArrowDtype(pa.int64()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_find_no_end():
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.find("ab", start=1)
+ expected = pd.Series([-1, None], dtype="int64[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_find_negative_start_negative_end():
+ # GH 56791
+ ser = pd.Series(["abcdefg", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.find(sub="d", start=-6, end=-3)
+ expected = pd.Series([3, None], dtype=ArrowDtype(pa.int64()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_find_large_start():
+ # GH 56791
+ ser = pd.Series(["abcdefg", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.find(sub="d", start=16)
+ expected = pd.Series([-1, None], dtype=ArrowDtype(pa.int64()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("start", [-15, -3, 0, 1, 15, None])
+@pytest.mark.parametrize("end", [-15, -1, 0, 3, 15, None])
+@pytest.mark.parametrize("sub", ["", "az", "abce", "a", "caa"])
+def test_str_find_e2e(start, end, sub):
+ s = pd.Series(
+ ["abcaadef", "abc", "abcdeddefgj8292", "ab", "a", ""],
+ dtype=ArrowDtype(pa.string()),
+ )
+ object_series = s.astype(pd.StringDtype(storage="python"))
+ result = s.str.find(sub, start, end)
+ expected = object_series.str.find(sub, start, end).astype(result.dtype)
+ tm.assert_series_equal(result, expected)
+
+ arrow_str_series = s.astype(pd.StringDtype(storage="pyarrow"))
+ result2 = arrow_str_series.str.find(sub, start, end).astype(result.dtype)
+ tm.assert_series_equal(result2, expected)
+
+
+def test_str_find_negative_start_negative_end_no_match():
+ # GH 56791
+ ser = pd.Series(["abcdefg", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.find(sub="d", start=-3, end=-6)
+ expected = pd.Series([-1, None], dtype=ArrowDtype(pa.int64()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "i, exp",
+ [
+ [1, ["b", "e", None]],
+ [-1, ["c", "e", None]],
+ [2, ["c", None, None]],
+ [-3, ["a", None, None]],
+ [4, [None, None, None]],
+ ],
+)
+def test_str_get(i, exp):
+ ser = pd.Series(["abc", "de", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.get(i)
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.xfail(
+ reason="TODO: StringMethods._validate should support Arrow list types",
+ raises=AttributeError,
+)
+def test_str_join():
+ ser = pd.Series(ArrowExtensionArray(pa.array([list("abc"), list("123"), None])))
+ result = ser.str.join("=")
+ expected = pd.Series(["a=b=c", "1=2=3", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_join_string_type():
+ ser = pd.Series(ArrowExtensionArray(pa.array(["abc", "123", None])))
+ result = ser.str.join("=")
+ expected = pd.Series(["a=b=c", "1=2=3", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "start, stop, step, exp",
+ [
+ [None, 2, None, ["ab", None]],
+ [None, 2, 1, ["ab", None]],
+ [1, 3, 1, ["bc", None]],
+ (None, None, -1, ["dcba", None]),
+ ],
+)
+def test_str_slice(start, stop, step, exp):
+ ser = pd.Series(["abcd", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.slice(start, stop, step)
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "start, stop, repl, exp",
+ [
+ [1, 2, "x", ["axcd", None]],
+ [None, 2, "x", ["xcd", None]],
+ [None, 2, None, ["cd", None]],
+ ],
+)
+def test_str_slice_replace(start, stop, repl, exp):
+ ser = pd.Series(["abcd", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.slice_replace(start, stop, repl)
+ expected = pd.Series(exp, dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "value, method, exp",
+ [
+ ["a1c", "isalnum", True],
+ ["!|,", "isalnum", False],
+ ["aaa", "isalpha", True],
+ ["!!!", "isalpha", False],
+ ["٠", "isdecimal", True], # noqa: RUF001
+ ["~!", "isdecimal", False],
+ ["2", "isdigit", True],
+ ["~", "isdigit", False],
+ ["aaa", "islower", True],
+ ["aaA", "islower", False],
+ ["123", "isnumeric", True],
+ ["11I", "isnumeric", False],
+ [" ", "isspace", True],
+ ["", "isspace", False],
+ ["The That", "istitle", True],
+ ["the That", "istitle", False],
+ ["AAA", "isupper", True],
+ ["AAc", "isupper", False],
+ ],
+)
+def test_str_is_functions(value, method, exp):
+ ser = pd.Series([value, None], dtype=ArrowDtype(pa.string()))
+ result = getattr(ser.str, method)()
+ expected = pd.Series([exp, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "method, exp",
+ [
+ ["capitalize", "Abc def"],
+ ["title", "Abc Def"],
+ ["swapcase", "AbC Def"],
+ ["lower", "abc def"],
+ ["upper", "ABC DEF"],
+ ["casefold", "abc def"],
+ ],
+)
+def test_str_transform_functions(method, exp):
+ ser = pd.Series(["aBc dEF", None], dtype=ArrowDtype(pa.string()))
+ result = getattr(ser.str, method)()
+ expected = pd.Series([exp, None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_len():
+ ser = pd.Series(["abcd", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.len()
+ expected = pd.Series([4, None], dtype=ArrowDtype(pa.int32()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "method, to_strip, val",
+ [
+ ["strip", None, " abc "],
+ ["strip", "x", "xabcx"],
+ ["lstrip", None, " abc"],
+ ["lstrip", "x", "xabc"],
+ ["rstrip", None, "abc "],
+ ["rstrip", "x", "abcx"],
+ ],
+)
+def test_str_strip(method, to_strip, val):
+ ser = pd.Series([val, None], dtype=ArrowDtype(pa.string()))
+ result = getattr(ser.str, method)(to_strip=to_strip)
+ expected = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("val", ["abc123", "abc"])
+def test_str_removesuffix(val):
+ ser = pd.Series([val, None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.removesuffix("123")
+ expected = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("val", ["123abc", "abc"])
+def test_str_removeprefix(val):
+ ser = pd.Series([val, None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.removeprefix("123")
+ expected = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("errors", ["ignore", "strict"])
+@pytest.mark.parametrize(
+ "encoding, exp",
+ [
+ ("utf8", {"little": b"abc", "big": "abc"}),
+ (
+ "utf32",
+ {
+ "little": b"\xff\xfe\x00\x00a\x00\x00\x00b\x00\x00\x00c\x00\x00\x00",
+ "big": b"\x00\x00\xfe\xff\x00\x00\x00a\x00\x00\x00b\x00\x00\x00c",
+ },
+ ),
+ ],
+ ids=["utf8", "utf32"],
+)
+def test_str_encode(errors, encoding, exp):
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.encode(encoding, errors)
+ expected = pd.Series([exp[sys.byteorder], None], dtype=ArrowDtype(pa.binary()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("flags", [0, 2])
+def test_str_findall(flags):
+ ser = pd.Series(["abc", "efg", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.findall("b", flags=flags)
+ expected = pd.Series([["b"], [], None], dtype=ArrowDtype(pa.list_(pa.string())))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["index", "rindex"])
+@pytest.mark.parametrize(
+ "start, end",
+ [
+ [0, None],
+ [1, 4],
+ ],
+)
+def test_str_r_index(method, start, end):
+ ser = pd.Series(["abcba", None], dtype=ArrowDtype(pa.string()))
+ result = getattr(ser.str, method)("c", start, end)
+ expected = pd.Series([2, None], dtype=ArrowDtype(pa.int64()))
+ tm.assert_series_equal(result, expected)
+
+ with pytest.raises(ValueError, match="substring not found"):
+ getattr(ser.str, method)("foo", start, end)
+
+
+@pytest.mark.parametrize("form", ["NFC", "NFKC"])
+def test_str_normalize(form):
+ ser = pd.Series(["abc", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.normalize(form)
+ expected = ser.copy()
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "start, end",
+ [
+ [0, None],
+ [1, 4],
+ ],
+)
+def test_str_rfind(start, end):
+ ser = pd.Series(["abcba", "foo", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.rfind("c", start, end)
+ expected = pd.Series([2, -1, None], dtype=ArrowDtype(pa.int64()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_translate():
+ ser = pd.Series(["abcba", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.translate({97: "b"})
+ expected = pd.Series(["bbcbb", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_wrap():
+ ser = pd.Series(["abcba", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.wrap(3)
+ expected = pd.Series(["abc\nba", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_get_dummies():
+ ser = pd.Series(["a|b", None, "a|c"], dtype=ArrowDtype(pa.string()))
+ result = ser.str.get_dummies()
+ expected = pd.DataFrame(
+ [[True, True, False], [False, False, False], [True, False, True]],
+ dtype=ArrowDtype(pa.bool_()),
+ columns=["a", "b", "c"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_str_partition():
+ ser = pd.Series(["abcba", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.partition("b")
+ expected = pd.DataFrame(
+ [["a", "b", "cba"], [None, None, None]],
+ dtype=ArrowDtype(pa.string()),
+ columns=pd.RangeIndex(3),
+ )
+ tm.assert_frame_equal(result, expected, check_column_type=True)
+
+ result = ser.str.partition("b", expand=False)
+ expected = pd.Series(ArrowExtensionArray(pa.array([["a", "b", "cba"], None])))
+ tm.assert_series_equal(result, expected)
+
+ result = ser.str.rpartition("b")
+ expected = pd.DataFrame(
+ [["abc", "b", "a"], [None, None, None]],
+ dtype=ArrowDtype(pa.string()),
+ columns=pd.RangeIndex(3),
+ )
+ tm.assert_frame_equal(result, expected, check_column_type=True)
+
+ result = ser.str.rpartition("b", expand=False)
+ expected = pd.Series(ArrowExtensionArray(pa.array([["abc", "b", "a"], None])))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["rsplit", "split"])
+def test_str_split_pat_none(method):
+ # GH 56271
+ ser = pd.Series(["a1 cbc\nb", None], dtype=ArrowDtype(pa.string()))
+ result = getattr(ser.str, method)()
+ expected = pd.Series(ArrowExtensionArray(pa.array([["a1", "cbc", "b"], None])))
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_split():
+ # GH 52401
+ ser = pd.Series(["a1cbcb", "a2cbcb", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.split("c")
+ expected = pd.Series(
+ ArrowExtensionArray(pa.array([["a1", "b", "b"], ["a2", "b", "b"], None]))
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = ser.str.split("c", n=1)
+ expected = pd.Series(
+ ArrowExtensionArray(pa.array([["a1", "bcb"], ["a2", "bcb"], None]))
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = ser.str.split("[1-2]", regex=True)
+ expected = pd.Series(
+ ArrowExtensionArray(pa.array([["a", "cbcb"], ["a", "cbcb"], None]))
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = ser.str.split("[1-2]", regex=True, expand=True)
+ expected = pd.DataFrame(
+ {
+ 0: ArrowExtensionArray(pa.array(["a", "a", None])),
+ 1: ArrowExtensionArray(pa.array(["cbcb", "cbcb", None])),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = ser.str.split("1", expand=True)
+ expected = pd.DataFrame(
+ {
+ 0: ArrowExtensionArray(pa.array(["a", "a2cbcb", None])),
+ 1: ArrowExtensionArray(pa.array(["cbcb", None, None])),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_str_rsplit():
+ # GH 52401
+ ser = pd.Series(["a1cbcb", "a2cbcb", None], dtype=ArrowDtype(pa.string()))
+ result = ser.str.rsplit("c")
+ expected = pd.Series(
+ ArrowExtensionArray(pa.array([["a1", "b", "b"], ["a2", "b", "b"], None]))
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = ser.str.rsplit("c", n=1)
+ expected = pd.Series(
+ ArrowExtensionArray(pa.array([["a1cb", "b"], ["a2cb", "b"], None]))
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = ser.str.rsplit("c", n=1, expand=True)
+ expected = pd.DataFrame(
+ {
+ 0: ArrowExtensionArray(pa.array(["a1cb", "a2cb", None])),
+ 1: ArrowExtensionArray(pa.array(["b", "b", None])),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = ser.str.rsplit("1", expand=True)
+ expected = pd.DataFrame(
+ {
+ 0: ArrowExtensionArray(pa.array(["a", "a2cbcb", None])),
+ 1: ArrowExtensionArray(pa.array(["cbcb", None, None])),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_str_extract_non_symbolic():
+ ser = pd.Series(["a1", "b2", "c3"], dtype=ArrowDtype(pa.string()))
+ with pytest.raises(ValueError, match="pat=.* must contain a symbolic group name."):
+ ser.str.extract(r"[ab](\d)")
+
+
+@pytest.mark.parametrize("expand", [True, False])
+def test_str_extract(expand):
+ ser = pd.Series(["a1", "b2", "c3"], dtype=ArrowDtype(pa.string()))
+ result = ser.str.extract(r"(?P[ab])(?P\d)", expand=expand)
+ expected = pd.DataFrame(
+ {
+ "letter": ArrowExtensionArray(pa.array(["a", "b", None])),
+ "digit": ArrowExtensionArray(pa.array(["1", "2", None])),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_str_extract_expand():
+ ser = pd.Series(["a1", "b2", "c3"], dtype=ArrowDtype(pa.string()))
+ result = ser.str.extract(r"[ab](?P\d)", expand=True)
+ expected = pd.DataFrame(
+ {
+ "digit": ArrowExtensionArray(pa.array(["1", "2", None])),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = ser.str.extract(r"[ab](?P\d)", expand=False)
+ expected = pd.Series(ArrowExtensionArray(pa.array(["1", "2", None])), name="digit")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"])
+def test_duration_from_strings_with_nat(unit):
+ # GH51175
+ strings = ["1000", "NaT"]
+ pa_type = pa.duration(unit)
+ dtype = ArrowDtype(pa_type)
+ result = ArrowExtensionArray._from_sequence_of_strings(strings, dtype=dtype)
+ expected = ArrowExtensionArray(pa.array([1000, None], type=pa_type))
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_unsupported_dt(data):
+ pa_dtype = data.dtype.pyarrow_dtype
+ if not pa.types.is_temporal(pa_dtype):
+ with pytest.raises(
+ AttributeError, match="Can only use .dt accessor with datetimelike values"
+ ):
+ pd.Series(data).dt
+
+
+@pytest.mark.parametrize(
+ "prop, expected",
+ [
+ ["year", 2023],
+ ["day", 2],
+ ["day_of_week", 0],
+ ["dayofweek", 0],
+ ["weekday", 0],
+ ["day_of_year", 2],
+ ["dayofyear", 2],
+ ["hour", 3],
+ ["minute", 4],
+ ["is_leap_year", False],
+ ["microsecond", 2000],
+ ["month", 1],
+ ["nanosecond", 6],
+ ["quarter", 1],
+ ["second", 7],
+ ["date", date(2023, 1, 2)],
+ ["time", time(3, 4, 7, 2000)],
+ ],
+)
+def test_dt_properties(prop, expected):
+ ser = pd.Series(
+ [
+ pd.Timestamp(
+ year=2023,
+ month=1,
+ day=2,
+ hour=3,
+ minute=4,
+ second=7,
+ microsecond=2000,
+ nanosecond=6,
+ ),
+ None,
+ ],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ result = getattr(ser.dt, prop)
+ exp_type = None
+ if isinstance(expected, date):
+ exp_type = pa.date32()
+ elif isinstance(expected, time):
+ exp_type = pa.time64("ns")
+ expected = pd.Series(ArrowExtensionArray(pa.array([expected, None], type=exp_type)))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("microsecond", [2000, 5, 0])
+def test_dt_microsecond(microsecond):
+ # GH 59183
+ ser = pd.Series(
+ [
+ pd.Timestamp(
+ year=2024,
+ month=7,
+ day=7,
+ second=5,
+ microsecond=microsecond,
+ nanosecond=6,
+ ),
+ None,
+ ],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ result = ser.dt.microsecond
+ expected = pd.Series([microsecond, None], dtype="int64[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+
+def test_dt_is_month_start_end():
+ ser = pd.Series(
+ [
+ datetime(year=2023, month=12, day=2, hour=3),
+ datetime(year=2023, month=1, day=1, hour=3),
+ datetime(year=2023, month=3, day=31, hour=3),
+ None,
+ ],
+ dtype=ArrowDtype(pa.timestamp("us")),
+ )
+ result = ser.dt.is_month_start
+ expected = pd.Series([False, True, False, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+ result = ser.dt.is_month_end
+ expected = pd.Series([False, False, True, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_dt_is_year_start_end():
+ ser = pd.Series(
+ [
+ datetime(year=2023, month=12, day=31, hour=3),
+ datetime(year=2023, month=1, day=1, hour=3),
+ datetime(year=2023, month=3, day=31, hour=3),
+ None,
+ ],
+ dtype=ArrowDtype(pa.timestamp("us")),
+ )
+ result = ser.dt.is_year_start
+ expected = pd.Series([False, True, False, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+ result = ser.dt.is_year_end
+ expected = pd.Series([True, False, False, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_dt_is_quarter_start_end():
+ ser = pd.Series(
+ [
+ datetime(year=2023, month=11, day=30, hour=3),
+ datetime(year=2023, month=1, day=1, hour=3),
+ datetime(year=2023, month=3, day=31, hour=3),
+ None,
+ ],
+ dtype=ArrowDtype(pa.timestamp("us")),
+ )
+ result = ser.dt.is_quarter_start
+ expected = pd.Series([False, True, False, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+ result = ser.dt.is_quarter_end
+ expected = pd.Series([False, False, True, None], dtype=ArrowDtype(pa.bool_()))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["days_in_month", "daysinmonth"])
+def test_dt_days_in_month(method):
+ ser = pd.Series(
+ [
+ datetime(year=2023, month=3, day=30, hour=3),
+ datetime(year=2023, month=4, day=1, hour=3),
+ datetime(year=2023, month=2, day=3, hour=3),
+ None,
+ ],
+ dtype=ArrowDtype(pa.timestamp("us")),
+ )
+ result = getattr(ser.dt, method)
+ expected = pd.Series([31, 30, 28, None], dtype=ArrowDtype(pa.int64()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_dt_normalize():
+ ser = pd.Series(
+ [
+ datetime(year=2023, month=3, day=30),
+ datetime(year=2023, month=4, day=1, hour=3),
+ datetime(year=2023, month=2, day=3, hour=23, minute=59, second=59),
+ None,
+ ],
+ dtype=ArrowDtype(pa.timestamp("us")),
+ )
+ result = ser.dt.normalize()
+ expected = pd.Series(
+ [
+ datetime(year=2023, month=3, day=30),
+ datetime(year=2023, month=4, day=1),
+ datetime(year=2023, month=2, day=3),
+ None,
+ ],
+ dtype=ArrowDtype(pa.timestamp("us")),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("unit", ["us", "ns"])
+def test_dt_time_preserve_unit(unit):
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp(unit)),
+ )
+ assert ser.dt.unit == unit
+
+ result = ser.dt.time
+ expected = pd.Series(
+ ArrowExtensionArray(pa.array([time(3, 0), None], type=pa.time64(unit)))
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("tz", [None, "UTC", "US/Pacific"])
+def test_dt_tz(tz):
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp("ns", tz=tz)),
+ )
+ result = ser.dt.tz
+ assert result == timezones.maybe_get_tz(tz)
+
+
+def test_dt_isocalendar():
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ result = ser.dt.isocalendar()
+ expected = pd.DataFrame(
+ [[2023, 1, 1], [0, 0, 0]],
+ columns=["year", "week", "day"],
+ dtype="int64[pyarrow]",
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "method, exp", [["day_name", "Sunday"], ["month_name", "January"]]
+)
+def test_dt_day_month_name(method, exp, request):
+ # GH 52388
+ _require_timezone_database(request)
+
+ ser = pd.Series([datetime(2023, 1, 1), None], dtype=ArrowDtype(pa.timestamp("ms")))
+ result = getattr(ser.dt, method)()
+ expected = pd.Series([exp, None], dtype=ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_dt_strftime(request):
+ _require_timezone_database(request)
+
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ result = ser.dt.strftime("%Y-%m-%dT%H:%M:%S")
+ expected = pd.Series(
+ ["2023-01-02T03:00:00.000000000", None], dtype=ArrowDtype(pa.string())
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["ceil", "floor", "round"])
+def test_dt_roundlike_tz_options_not_supported(method):
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ with pytest.raises(NotImplementedError, match="ambiguous is not supported."):
+ getattr(ser.dt, method)("1h", ambiguous="NaT")
+
+ with pytest.raises(NotImplementedError, match="nonexistent is not supported."):
+ getattr(ser.dt, method)("1h", nonexistent="NaT")
+
+
+@pytest.mark.parametrize("method", ["ceil", "floor", "round"])
+def test_dt_roundlike_unsupported_freq(method):
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ with pytest.raises(ValueError, match="freq='1B' is not supported"):
+ getattr(ser.dt, method)("1B")
+
+ with pytest.raises(ValueError, match="Must specify a valid frequency: None"):
+ getattr(ser.dt, method)(None)
+
+
+@pytest.mark.parametrize("freq", ["D", "h", "min", "s", "ms", "us", "ns"])
+@pytest.mark.parametrize("method", ["ceil", "floor", "round"])
+def test_dt_ceil_year_floor(freq, method):
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=1), None],
+ )
+ pa_dtype = ArrowDtype(pa.timestamp("ns"))
+ expected = getattr(ser.dt, method)(f"1{freq}").astype(pa_dtype)
+ result = getattr(ser.astype(pa_dtype).dt, method)(f"1{freq}")
+ tm.assert_series_equal(result, expected)
+
+
+def test_dt_to_pydatetime():
+ # GH 51859
+ data = [datetime(2022, 1, 1), datetime(2023, 1, 1)]
+ ser = pd.Series(data, dtype=ArrowDtype(pa.timestamp("ns")))
+ result = ser.dt.to_pydatetime()
+ expected = pd.Series(data, dtype=object)
+ tm.assert_series_equal(result, expected)
+ assert all(type(expected.iloc[i]) is datetime for i in range(len(expected)))
+
+ expected = ser.astype("datetime64[ns]").dt.to_pydatetime()
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("date_type", [32, 64])
+def test_dt_to_pydatetime_date_error(date_type):
+ # GH 52812
+ ser = pd.Series(
+ [date(2022, 12, 31)],
+ dtype=ArrowDtype(getattr(pa, f"date{date_type}")()),
+ )
+ with pytest.raises(ValueError, match="to_pydatetime cannot be called with"):
+ ser.dt.to_pydatetime()
+
+
+def test_dt_tz_localize_unsupported_tz_options():
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ with pytest.raises(NotImplementedError, match="ambiguous='NaT' is not supported"):
+ ser.dt.tz_localize("UTC", ambiguous="NaT")
+
+ with pytest.raises(NotImplementedError, match="nonexistent='NaT' is not supported"):
+ ser.dt.tz_localize("UTC", nonexistent="NaT")
+
+
+def test_dt_tz_localize_none(request):
+ _require_timezone_database(request)
+
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp("ns", tz="US/Pacific")),
+ )
+ result = ser.dt.tz_localize(None)
+ expected = pd.Series(
+ [ser[0].tz_localize(None), None],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("unit", ["us", "ns"])
+def test_dt_tz_localize(unit, request):
+ _require_timezone_database(request)
+
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp(unit)),
+ )
+ result = ser.dt.tz_localize("US/Pacific")
+ exp_data = pa.array(
+ [datetime(year=2023, month=1, day=2, hour=3), None], type=pa.timestamp(unit)
+ )
+ exp_data = pa.compute.assume_timezone(exp_data, "US/Pacific")
+ expected = pd.Series(ArrowExtensionArray(exp_data))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "nonexistent, exp_date",
+ [
+ ["shift_forward", datetime(year=2023, month=3, day=12, hour=3)],
+ ["shift_backward", pd.Timestamp("2023-03-12 01:59:59.999999999")],
+ ],
+)
+def test_dt_tz_localize_nonexistent(nonexistent, exp_date, request):
+ _require_timezone_database(request)
+
+ ser = pd.Series(
+ [datetime(year=2023, month=3, day=12, hour=2, minute=30), None],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ result = ser.dt.tz_localize("US/Pacific", nonexistent=nonexistent)
+ exp_data = pa.array([exp_date, None], type=pa.timestamp("ns"))
+ exp_data = pa.compute.assume_timezone(exp_data, "US/Pacific")
+ expected = pd.Series(ArrowExtensionArray(exp_data))
+ tm.assert_series_equal(result, expected)
+
+
+def test_dt_tz_convert_not_tz_raises():
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ with pytest.raises(TypeError, match="Cannot convert tz-naive timestamps"):
+ ser.dt.tz_convert("UTC")
+
+
+def test_dt_tz_convert_none():
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp("ns", "US/Pacific")),
+ )
+ result = ser.dt.tz_convert(None)
+ expected = pd.Series(
+ [ser[0].tz_convert(None), None],
+ dtype=ArrowDtype(pa.timestamp("ns")),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("unit", ["us", "ns"])
+def test_dt_tz_convert(unit):
+ ser = pd.Series(
+ [datetime(year=2023, month=1, day=2, hour=3), None],
+ dtype=ArrowDtype(pa.timestamp(unit, "US/Pacific")),
+ )
+ result = ser.dt.tz_convert("US/Eastern")
+ expected = pd.Series(
+ [ser[0].tz_convert("US/Eastern"), None],
+ dtype=ArrowDtype(pa.timestamp(unit, "US/Eastern")),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["timestamp[ms][pyarrow]", "duration[ms][pyarrow]"])
+def test_as_unit(dtype):
+ # GH 52284
+ ser = pd.Series([1000, None], dtype=dtype)
+ result = ser.dt.as_unit("ns")
+ expected = ser.astype(dtype.replace("ms", "ns"))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "from_unit,to_unit",
+ [
+ ("ns", "us"),
+ ("ns", "ms"),
+ ("ns", "s"),
+ ("us", "ms"),
+ ("us", "s"),
+ ("ms", "s"),
+ ("s", "ms"),
+ ("s", "us"),
+ ("s", "ns"),
+ ("ms", "us"),
+ ("ms", "ns"),
+ ("us", "ns"),
+ ],
+)
+def test_as_unit_duration_truncation(from_unit, to_unit):
+ # Test that as_unit truncates correctly (matches NumPy behavior)
+ # Value with sub-unit precision to test truncation
+ ser_numpy = pd.Series(
+ pd.to_timedelta([93784567890123, None], unit="ns").as_unit(from_unit)
+ )
+ ser_arrow = ser_numpy.astype(f"duration[{from_unit}][pyarrow]")
+
+ result = ser_arrow.dt.as_unit(to_unit)
+ expected = ser_numpy.dt.as_unit(to_unit).astype(f"duration[{to_unit}][pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "from_unit,to_unit",
+ [
+ ("ns", "us"),
+ ("ns", "ms"),
+ ("ns", "s"),
+ ("s", "ns"),
+ ("ms", "ns"),
+ ("us", "ns"),
+ ],
+)
+def test_as_unit_timestamp(from_unit, to_unit):
+ # Test timestamp as_unit matches NumPy behavior
+ # Create Arrow series directly to preserve nulls correctly
+ ser_arrow = pd.Series(
+ [pd.Timestamp("2024-01-15 12:30:45.123456789"), None],
+ dtype=f"timestamp[{from_unit}][pyarrow]",
+ )
+ ser_numpy = ser_arrow.astype(f"datetime64[{from_unit}]")
+
+ result = ser_arrow.dt.as_unit(to_unit)
+ expected_numpy = ser_numpy.dt.as_unit(to_unit)
+ # Compare values (excluding null handling differences)
+ tm.assert_almost_equal(
+ result.dropna().to_numpy(dtype=f"datetime64[{to_unit}]"),
+ expected_numpy.dropna().to_numpy(),
+ )
+ # Verify nulls are preserved
+ assert result.isna().sum() == ser_arrow.isna().sum()
+
+
+@pytest.mark.parametrize("to_unit", ["s", "ms", "us", "ns"])
+def test_as_unit_timestamp_with_timezone(to_unit):
+ # Test that timezone is preserved
+ ser_numpy = pd.Series(
+ pd.to_datetime(["2024-01-15 12:30:45.123456789"])
+ .tz_localize("US/Eastern")
+ .as_unit("ns")
+ )
+ ser_arrow = ser_numpy.astype("timestamp[ns, US/Eastern][pyarrow]")
+
+ result = ser_arrow.dt.as_unit(to_unit)
+ expected = ser_numpy.dt.as_unit(to_unit).astype(
+ f"timestamp[{to_unit}, US/Eastern][pyarrow]"
+ )
+ tm.assert_series_equal(result, expected)
+ assert str(result.dtype) == f"timestamp[{to_unit}, tz=US/Eastern][pyarrow]"
+
+
+def test_as_unit_date_raises():
+ # as_unit should raise for date types
+ ser = pd.Series([1, 2], dtype=ArrowDtype(pa.date32()))
+ with pytest.raises(NotImplementedError, match="as_unit not implemented"):
+ ser.dt.as_unit("ns")
+
+
+@pytest.mark.parametrize(
+ "prop, expected",
+ [
+ ["days", 1],
+ ["seconds", 2],
+ ["microseconds", 3],
+ ["nanoseconds", 4],
+ ],
+)
+def test_dt_timedelta_properties(prop, expected):
+ # GH 52284
+ ser = pd.Series(
+ [
+ pd.Timedelta(
+ days=1,
+ seconds=2,
+ microseconds=3,
+ nanoseconds=4,
+ ),
+ None,
+ ],
+ dtype=ArrowDtype(pa.duration("ns")),
+ )
+ result = getattr(ser.dt, prop)
+ expected = pd.Series(
+ ArrowExtensionArray(pa.array([expected, None], type=pa.int32()))
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_dt_timedelta_total_seconds():
+ # GH 52284
+ ser = pd.Series(
+ [
+ pd.Timedelta(
+ days=1,
+ seconds=2,
+ microseconds=3,
+ nanoseconds=4,
+ ),
+ None,
+ ],
+ dtype=ArrowDtype(pa.duration("ns")),
+ )
+ result = ser.dt.total_seconds()
+ expected = pd.Series(
+ ArrowExtensionArray(pa.array([86402.000003, None], type=pa.float64()))
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_dt_to_pytimedelta():
+ # GH 52284
+ data = [timedelta(1, 2, 3), timedelta(1, 2, 4)]
+ ser = pd.Series(data, dtype=ArrowDtype(pa.duration("ns")))
+
+ msg = "The behavior of ArrowTemporalProperties.to_pytimedelta is deprecated"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = ser.dt.to_pytimedelta()
+ expected = np.array(data, dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+ assert all(type(res) is timedelta for res in result)
+
+ msg = "The behavior of TimedeltaProperties.to_pytimedelta is deprecated"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = ser.astype("timedelta64[ns]").dt.to_pytimedelta()
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_dt_components():
+ # GH 52284
+ ser = pd.Series(
+ [
+ pd.Timedelta(
+ days=1,
+ seconds=2,
+ microseconds=3,
+ nanoseconds=4,
+ ),
+ None,
+ ],
+ dtype=ArrowDtype(pa.duration("ns")),
+ )
+ result = ser.dt.components
+ expected = pd.DataFrame(
+ [[1, 0, 0, 2, 0, 3, 4], [pd.NA, pd.NA, pd.NA, pd.NA, pd.NA, pd.NA, pd.NA]],
+ columns=[
+ "days",
+ "hours",
+ "minutes",
+ "seconds",
+ "milliseconds",
+ "microseconds",
+ "nanoseconds",
+ ],
+ dtype="int32[pyarrow]",
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dt_components_large_values():
+ ser = pd.Series(
+ [
+ pd.Timedelta("365 days 23:59:59.999000"),
+ None,
+ ],
+ dtype=ArrowDtype(pa.duration("ns")),
+ )
+ result = ser.dt.components
+ expected = pd.DataFrame(
+ [
+ [365, 23, 59, 59, 999, 0, 0],
+ [pd.NA, pd.NA, pd.NA, pd.NA, pd.NA, pd.NA, pd.NA],
+ ],
+ columns=[
+ "days",
+ "hours",
+ "minutes",
+ "seconds",
+ "milliseconds",
+ "microseconds",
+ "nanoseconds",
+ ],
+ dtype="int32[pyarrow]",
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("skipna", [True, False])
+def test_boolean_reduce_series_all_null(all_boolean_reductions, skipna):
+ # GH51624
+ ser = pd.Series([None], dtype="float64[pyarrow]")
+ result = getattr(ser, all_boolean_reductions)(skipna=skipna)
+ if skipna:
+ expected = all_boolean_reductions == "all"
+ else:
+ expected = pd.NA
+ assert result is expected
+
+
+def test_from_sequence_of_strings_boolean():
+ true_strings = ["true", "TRUE", "True", "1", "1.0"]
+ false_strings = ["false", "FALSE", "False", "0", "0.0"]
+ nulls = [None]
+ strings = true_strings + false_strings + nulls
+ bools = (
+ [True] * len(true_strings) + [False] * len(false_strings) + [None] * len(nulls)
+ )
+
+ dtype = ArrowDtype(pa.bool_())
+ result = ArrowExtensionArray._from_sequence_of_strings(strings, dtype=dtype)
+ expected = pd.array(bools, dtype="boolean[pyarrow]")
+ tm.assert_extension_array_equal(result, expected)
+
+ strings = ["True", "foo"]
+ with pytest.raises(pa.ArrowInvalid, match="Failed to parse"):
+ ArrowExtensionArray._from_sequence_of_strings(strings, dtype=dtype)
+
+
+def test_concat_empty_arrow_backed_series(dtype):
+ # GH#51734
+ ser = pd.Series([], dtype=dtype)
+ expected = ser.copy()
+ result = pd.concat([ser[np.array([], dtype=np.bool_)]])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["string", "string[pyarrow]"])
+def test_series_from_string_array(dtype):
+ arr = pa.array("the quick brown fox".split())
+ ser = pd.Series(arr, dtype=dtype)
+ expected = pd.Series(ArrowExtensionArray(arr), dtype=dtype)
+ tm.assert_series_equal(ser, expected)
+
+
+# _data was renamed to _pa_data
+class OldArrowExtensionArray(ArrowExtensionArray):
+ def __getstate__(self):
+ state = super().__getstate__()
+ state["_data"] = state.pop("_pa_array")
+ return state
+
+
+def test_pickle_old_arrowextensionarray():
+ data = pa.array([1])
+ expected = OldArrowExtensionArray(data)
+ result = pickle.loads(pickle.dumps(expected))
+ tm.assert_extension_array_equal(result, expected)
+ assert result._pa_array == pa.chunked_array(data)
+ assert not hasattr(result, "_data")
+
+
+def test_setitem_boolean_replace_with_mask_segfault():
+ # GH#52059
+ N = 145_000
+ arr = ArrowExtensionArray(pa.chunked_array([np.ones((N,), dtype=np.bool_)]))
+ expected = arr.copy()
+ arr[np.zeros((N,), dtype=np.bool_)] = False
+ assert arr._pa_array == expected._pa_array
+
+
+def test_setitem_na_chunked_string_if_else():
+ # GH#64320
+ df = pd.concat(
+ [
+ pd.DataFrame({"a": ["x"] * 5, "b": ["x"] * 5}),
+ pd.DataFrame({"a": ["x"] * 5, "b": ["x"] * 5}),
+ ],
+ ignore_index=True,
+ )
+ for _ in range(5):
+ df.loc[[0], "a"] = pd.NA
+ assert pd.isna(df["a"].iloc[0])
+ assert (df["a"].iloc[1:] == "x").all()
+ assert (df["b"] == "x").all()
+
+
+@pytest.mark.parametrize(
+ "data, arrow_dtype",
+ [
+ ([b"a", b"b"], pa.large_binary()),
+ (["a", "b"], pa.large_string()),
+ ],
+)
+def test_conversion_large_dtypes_from_numpy_array(data, arrow_dtype):
+ dtype = ArrowDtype(arrow_dtype)
+ result = pd.array(np.array(data), dtype=dtype)
+ expected = pd.array(data, dtype=dtype)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_concat_null_array():
+ df = pd.DataFrame({"a": [None, None]}, dtype=ArrowDtype(pa.null()))
+ df2 = pd.DataFrame({"a": [0, 1]}, dtype="int64[pyarrow]")
+
+ result = pd.concat([df, df2], ignore_index=True)
+ expected = pd.DataFrame({"a": [None, None, 0, 1]}, dtype="int64[pyarrow]")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("pa_type", tm.ALL_INT_PYARROW_DTYPES + tm.FLOAT_PYARROW_DTYPES)
+def test_describe_numeric_data(pa_type):
+ # GH 52470
+ data = pd.Series([1, 2, 3], dtype=ArrowDtype(pa_type))
+ result = data.describe()
+ expected = pd.Series(
+ [3, 2, 1, 1, 1.5, 2.0, 2.5, 3],
+ dtype=ArrowDtype(pa.float64()),
+ index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("pa_type", tm.TIMEDELTA_PYARROW_DTYPES)
+def test_describe_timedelta_data(pa_type):
+ # GH53001
+ data = pd.Series(range(1, 10), dtype=ArrowDtype(pa_type))
+ result = data.describe()
+ expected = pd.Series(
+ [9, *pd.to_timedelta([5, 2, 1, 3, 5, 7, 9], unit=pa_type.unit).tolist()],
+ dtype=object,
+ index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("pa_type", tm.DATETIME_PYARROW_DTYPES)
+def test_describe_datetime_data(pa_type):
+ # GH53001
+ data = pd.Series(range(1, 10), dtype=ArrowDtype(pa_type))
+ result = data.describe()
+ expected = pd.Series(
+ [9]
+ + [
+ pd.Timestamp(v, tz=pa_type.tz, unit=pa_type.unit)
+ for v in [5, 1, 3, 5, 7, 9]
+ ],
+ dtype=object,
+ index=["count", "mean", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "pa_type", tm.DATETIME_PYARROW_DTYPES + tm.TIMEDELTA_PYARROW_DTYPES
+)
+def test_quantile_temporal(pa_type):
+ # GH52678
+ data = [1, 2, 3]
+ ser = pd.Series(data, dtype=ArrowDtype(pa_type))
+ result = ser.quantile(0.1)
+ expected = ser[0]
+ assert result == expected
+
+
+def test_date32_repr():
+ # GH48238
+ arrow_dt = pa.array([date.fromisoformat("2020-01-01")], type=pa.date32())
+ ser = pd.Series(arrow_dt, dtype=ArrowDtype(arrow_dt.type))
+ assert repr(ser) == "0 2020-01-01\ndtype: date32[day][pyarrow]"
+
+
+def test_duration_overflow_from_ndarray_containing_nat():
+ # GH52843
+ data_ts = pd.to_datetime([1, None])
+ data_td = pd.to_timedelta([1, None])
+ ser_ts = pd.Series(data_ts, dtype=ArrowDtype(pa.timestamp("ns")))
+ ser_td = pd.Series(data_td, dtype=ArrowDtype(pa.duration("ns")))
+ result = ser_ts + ser_td
+ expected = pd.Series([2, None], dtype=ArrowDtype(pa.timestamp("ns")))
+ tm.assert_series_equal(result, expected)
+
+
+def test_infer_dtype_pyarrow_dtype(data, request):
+ res = lib.infer_dtype(data)
+ assert res != "unknown-array"
+
+ if data._hasna and res in ["datetime64", "timedelta64"]:
+ mark = pytest.mark.xfail(
+ reason="in infer_dtype pd.NA is not ignored in these cases "
+ "even with skipna=True in the list(data) check below"
+ )
+ request.applymarker(mark)
+
+ assert res == lib.infer_dtype(list(data), skipna=True)
+
+
+@pytest.mark.parametrize(
+ "pa_type", tm.DATETIME_PYARROW_DTYPES + tm.TIMEDELTA_PYARROW_DTYPES
+)
+def test_from_sequence_temporal(pa_type):
+ # GH 53171
+ val = 3
+ unit = pa_type.unit
+ if pa.types.is_duration(pa_type):
+ seq = [pd.Timedelta(val, unit=unit).as_unit(unit)]
+ else:
+ seq = [pd.Timestamp(val, unit=unit, tz=pa_type.tz).as_unit(unit)]
+
+ result = ArrowExtensionArray._from_sequence(seq, dtype=pa_type)
+ expected = ArrowExtensionArray(pa.array([val], type=pa_type))
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "pa_type", tm.DATETIME_PYARROW_DTYPES + tm.TIMEDELTA_PYARROW_DTYPES
+)
+def test_setitem_temporal(pa_type):
+ # GH 53171
+ unit = pa_type.unit
+ if pa.types.is_duration(pa_type):
+ val = pd.Timedelta(1, unit=unit).as_unit(unit)
+ else:
+ val = pd.Timestamp(1, unit=unit, tz=pa_type.tz).as_unit(unit)
+
+ arr = ArrowExtensionArray(pa.array([1, 2, 3], type=pa_type))
+
+ result = arr.copy()
+ result[:] = val
+ expected = ArrowExtensionArray(pa.array([1, 1, 1], type=pa_type))
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "pa_type", tm.DATETIME_PYARROW_DTYPES + tm.TIMEDELTA_PYARROW_DTYPES
+)
+def test_arithmetic_temporal(pa_type, request):
+ # GH 53171
+ arr = ArrowExtensionArray(pa.array([1, 2, 3], type=pa_type))
+ unit = pa_type.unit
+ result = arr - pd.Timedelta(1, unit=unit).as_unit(unit)
+ expected = ArrowExtensionArray(pa.array([0, 1, 2], type=pa_type))
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "pa_type", tm.DATETIME_PYARROW_DTYPES + tm.TIMEDELTA_PYARROW_DTYPES
+)
+def test_comparison_temporal(pa_type):
+ # GH 53171
+ unit = pa_type.unit
+ if pa.types.is_duration(pa_type):
+ val = pd.Timedelta(1, unit=unit).as_unit(unit)
+ else:
+ val = pd.Timestamp(1, unit=unit, tz=pa_type.tz).as_unit(unit)
+
+ arr = ArrowExtensionArray(pa.array([1, 2, 3], type=pa_type))
+
+ result = arr > val
+ expected = ArrowExtensionArray(pa.array([False, True, True], type=pa.bool_()))
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "pa_type", tm.DATETIME_PYARROW_DTYPES + tm.TIMEDELTA_PYARROW_DTYPES
+)
+def test_getitem_temporal(pa_type):
+ # GH 53326
+ arr = ArrowExtensionArray(pa.array([1, 2, 3], type=pa_type))
+ result = arr[1]
+ if pa.types.is_duration(pa_type):
+ expected = pd.Timedelta(2, unit=pa_type.unit).as_unit(pa_type.unit)
+ assert isinstance(result, pd.Timedelta)
+ else:
+ expected = pd.Timestamp(2, unit=pa_type.unit, tz=pa_type.tz).as_unit(
+ pa_type.unit
+ )
+ assert isinstance(result, pd.Timestamp)
+ assert result.unit == expected.unit
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "pa_type", tm.DATETIME_PYARROW_DTYPES + tm.TIMEDELTA_PYARROW_DTYPES
+)
+def test_iter_temporal(pa_type):
+ # GH 53326
+ arr = ArrowExtensionArray(pa.array([1, None], type=pa_type))
+ result = list(arr)
+ if pa.types.is_duration(pa_type):
+ expected = [
+ pd.Timedelta(1, unit=pa_type.unit).as_unit(pa_type.unit),
+ pd.NA,
+ ]
+ assert isinstance(result[0], pd.Timedelta)
+ else:
+ expected = [
+ pd.Timestamp(1, unit=pa_type.unit, tz=pa_type.tz).as_unit(pa_type.unit),
+ pd.NA,
+ ]
+ assert isinstance(result[0], pd.Timestamp)
+ assert result[0].unit == expected[0].unit
+ assert result == expected
+
+
+def test_groupby_series_size_returns_pa_int(data):
+ # GH 54132
+ ser = pd.Series(data[:3], index=["a", "a", "b"])
+ result = ser.groupby(level=0).size()
+ expected = pd.Series([2, 1], dtype="int64[pyarrow]", index=["a", "b"])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "pa_type", tm.DATETIME_PYARROW_DTYPES + tm.TIMEDELTA_PYARROW_DTYPES, ids=repr
+)
+@pytest.mark.parametrize("dtype", [None, object])
+def test_to_numpy_temporal(pa_type, dtype):
+ # GH 53326
+ # GH 55997: Return datetime64/timedelta64 types with NaT if possible
+ arr = ArrowExtensionArray(pa.array([1, None], type=pa_type))
+ result = arr.to_numpy(dtype=dtype)
+ if pa.types.is_duration(pa_type):
+ value = pd.Timedelta(1, unit=pa_type.unit).as_unit(pa_type.unit)
+ else:
+ value = pd.Timestamp(1, unit=pa_type.unit, tz=pa_type.tz).as_unit(pa_type.unit)
+
+ if dtype == object or (pa.types.is_timestamp(pa_type) and pa_type.tz is not None):
+ if dtype == object:
+ na = pd.NA
+ else:
+ na = pd.NaT
+ expected = np.array([value, na], dtype=object)
+ assert result[0].unit == value.unit
+ else:
+ na = pa_type.to_pandas_dtype().type("nat", pa_type.unit)
+ value = value.to_numpy()
+ expected = np.array([value, na])
+ assert np.datetime_data(result[0])[0] == pa_type.unit
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_groupby_count_return_arrow_dtype(data_missing):
+ df = pd.DataFrame({"A": [1, 1], "B": data_missing, "C": data_missing})
+ result = df.groupby("A").count()
+ expected = pd.DataFrame(
+ [[1, 1]],
+ index=pd.Index([1], name="A"),
+ columns=["B", "C"],
+ dtype="int64[pyarrow]",
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_fixed_size_list():
+ # GH#55000
+ ser = pd.Series(
+ [[1, 2], [3, 4]], dtype=ArrowDtype(pa.list_(pa.int64(), list_size=2))
+ )
+ result = ser.dtype.type
+ assert result == list
+
+
+def test_arrowextensiondtype_dataframe_repr():
+ # GH 54062
+ df = pd.DataFrame(
+ pd.period_range("2012", periods=3),
+ columns=["col"],
+ dtype=ArrowDtype(ArrowPeriodType("D")),
+ )
+ result = repr(df)
+ # TODO: repr value may not be expected; address how
+ # pyarrow.ExtensionType values are displayed
+ expected = " col\n0 15340\n1 15341\n2 15342"
+ assert result == expected
+
+
+def test_pow_missing_operand():
+ # GH 55512
+ k = pd.Series([2, None], dtype="int64[pyarrow]")
+ result = k.pow(None, fill_value=3)
+ expected = pd.Series([8, None], dtype="int64[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+
+def test_decimal_parse_raises():
+ # GH 56984
+ ser = pd.Series(["1.2345"], dtype=ArrowDtype(pa.string()))
+ with pytest.raises(
+ pa.lib.ArrowInvalid, match="Rescaling Decimal(128)? value would cause data loss"
+ ):
+ ser.astype(ArrowDtype(pa.decimal128(1, 0)))
+
+
+def test_decimal_parse_succeeds():
+ # GH 56984
+ ser = pd.Series(["1.2345"], dtype=ArrowDtype(pa.string()))
+ dtype = ArrowDtype(pa.decimal128(5, 4))
+ result = ser.astype(dtype)
+ expected = pd.Series([Decimal("1.2345")], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("pa_type", tm.TIMEDELTA_PYARROW_DTYPES)
+def test_duration_fillna_numpy(pa_type):
+ # GH 54707
+ ser1 = pd.Series([None, 2], dtype=ArrowDtype(pa_type))
+ ser2 = pd.Series(np.array([1, 3], dtype=f"m8[{pa_type.unit}]"))
+ result = ser1.fillna(ser2)
+ expected = pd.Series([1, 2], dtype=ArrowDtype(pa_type))
+ tm.assert_series_equal(result, expected)
+
+
+def test_comparison_not_propagating_arrow_error():
+ # GH#54944
+ a = pd.Series([1 << 63], dtype="uint64[pyarrow]")
+ b = pd.Series([None], dtype="int64[pyarrow]")
+ with pytest.raises(pa.lib.ArrowInvalid, match="Integer value"):
+ a < b
+
+
+def test_factorize_chunked_dictionary():
+ # GH 54844
+ pa_array = pa.chunked_array(
+ [pa.array(["a"]).dictionary_encode(), pa.array(["b"]).dictionary_encode()]
+ )
+ ser = pd.Series(ArrowExtensionArray(pa_array))
+ res_indices, res_uniques = ser.factorize()
+ exp_indices = np.array([0, 1], dtype=np.intp)
+ exp_uniques = pd.Index(ArrowExtensionArray(pa_array.combine_chunks()))
+ tm.assert_numpy_array_equal(res_indices, exp_indices)
+ tm.assert_index_equal(res_uniques, exp_uniques)
+
+
+def test_factorize_dictionary_with_na():
+ # GH#60567
+ arr = pd.array(
+ ["a1", pd.NA], dtype=ArrowDtype(pa.dictionary(pa.int32(), pa.utf8()))
+ )
+ indices, uniques = arr.factorize(use_na_sentinel=False)
+ expected_indices = np.array([0, 1], dtype=np.intp)
+ expected_uniques = pd.array(["a1", None], dtype=ArrowDtype(pa.string()))
+ tm.assert_numpy_array_equal(indices, expected_indices)
+ tm.assert_extension_array_equal(uniques, expected_uniques)
+
+
+def test_dictionary_astype_categorical():
+ # GH#56672
+ arrs = [
+ pa.array(np.array(["a", "x", "c", "a"])).dictionary_encode(),
+ pa.array(np.array(["a", "d", "c"])).dictionary_encode(),
+ ]
+ ser = pd.Series(ArrowExtensionArray(pa.chunked_array(arrs)))
+ result = ser.astype("category")
+ categories = pd.Index(["a", "x", "c", "d"], dtype=ArrowDtype(pa.string()))
+ expected = pd.Series(
+ ["a", "x", "c", "a", "a", "d", "c"],
+ dtype=pd.CategoricalDtype(categories=categories),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_arrow_floordiv():
+ # GH 55561
+ a = pd.Series([-7], dtype="int64[pyarrow]")
+ b = pd.Series([4], dtype="int64[pyarrow]")
+ expected = pd.Series([-2], dtype="int64[pyarrow]")
+ result = a // b
+ tm.assert_series_equal(result, expected)
+
+
+def test_arrow_floordiv_large_values():
+ # GH 56645
+ a = pd.Series([1425801600000000000], dtype="int64[pyarrow]")
+ expected = pd.Series([1425801600000], dtype="int64[pyarrow]")
+ result = a // 1_000_000
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["int64[pyarrow]", "uint64[pyarrow]"])
+def test_arrow_floordiv_large_integral_result(dtype):
+ # GH 56676
+ a = pd.Series([18014398509481983], dtype=dtype)
+ result = a // 1
+ tm.assert_series_equal(result, a)
+
+
+@pytest.mark.parametrize("pa_type", tm.SIGNED_INT_PYARROW_DTYPES)
+def test_arrow_floordiv_larger_divisor(pa_type):
+ # GH 56676
+ dtype = ArrowDtype(pa_type)
+ a = pd.Series([-23], dtype=dtype)
+ result = a // 24
+ expected = pd.Series([-1], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("pa_type", tm.SIGNED_INT_PYARROW_DTYPES)
+def test_arrow_floordiv_integral_invalid(pa_type):
+ # GH 56676
+ min_value = np.iinfo(pa_type.to_pandas_dtype()).min
+ a = pd.Series([min_value], dtype=ArrowDtype(pa_type))
+ with pytest.raises(pa.lib.ArrowInvalid, match="overflow|not in range"):
+ a // -1
+ with pytest.raises(pa.lib.ArrowInvalid, match="divide by zero"):
+ a // 0
+
+
+@pytest.mark.parametrize("dtype", tm.FLOAT_PYARROW_DTYPES_STR_REPR)
+def test_arrow_floordiv_floating_0_divisor(dtype):
+ # GH 56676
+ a = pd.Series([2], dtype=dtype)
+ result = a // 0
+ expected = pd.Series([float("inf")], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["float64", "datetime64[ns]", "timedelta64[ns]"])
+def test_astype_int_with_null_to_numpy_dtype(dtype):
+ # GH 57093
+ ser = pd.Series([1, None], dtype="int64[pyarrow]")
+ result = ser.astype(dtype)
+ expected = pd.Series([1, None], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("pa_type", tm.ALL_INT_PYARROW_DTYPES)
+def test_arrow_integral_floordiv_large_values(pa_type):
+ # GH 56676
+ max_value = np.iinfo(pa_type.to_pandas_dtype()).max
+ dtype = ArrowDtype(pa_type)
+ a = pd.Series([max_value], dtype=dtype)
+ b = pd.Series([1], dtype=dtype)
+ result = a // b
+ tm.assert_series_equal(result, a)
+
+
+@pytest.mark.parametrize("dtype", ["int64[pyarrow]", "uint64[pyarrow]"])
+def test_arrow_true_division_large_divisor(dtype):
+ # GH 56706
+ a = pd.Series([0], dtype=dtype)
+ b = pd.Series([18014398509481983], dtype=dtype)
+ expected = pd.Series([0], dtype="float64[pyarrow]")
+ result = a / b
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["int64[pyarrow]", "uint64[pyarrow]"])
+def test_arrow_floor_division_large_divisor(dtype):
+ # GH 56706
+ a = pd.Series([0], dtype=dtype)
+ b = pd.Series([18014398509481983], dtype=dtype)
+ expected = pd.Series([0], dtype=dtype)
+ result = a // b
+ tm.assert_series_equal(result, expected)
+
+
+def test_string_to_datetime_parsing_cast():
+ # GH 56266
+ string_dates = ["2020-01-01 04:30:00", "2020-01-02 00:00:00", "2020-01-03 00:00:00"]
+ result = pd.Series(string_dates, dtype="timestamp[s][pyarrow]")
+
+ pd_res = pd.to_datetime(string_dates).as_unit("s")
+ expected = pd.Series(ArrowExtensionArray(pa.array(pd_res, from_pandas=True)))
+ tm.assert_series_equal(result, expected)
+
+
+def test_interpolate_not_numeric(data):
+ if not data.dtype._is_numeric:
+ ser = pd.Series(data)
+ msg = re.escape(f"Cannot interpolate with {ser.dtype} dtype")
+ with pytest.raises(TypeError, match=msg):
+ pd.Series(data).interpolate()
+
+
+@pytest.mark.parametrize("dtype", ["int64[pyarrow]", "float64[pyarrow]"])
+def test_interpolate_linear(dtype):
+ ser = pd.Series([None, 1, 2, None, 4, None], dtype=dtype)
+ result = ser.interpolate()
+ expected = pd.Series([None, 1, 2, 3, 4, None], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_string_to_time_parsing_cast():
+ # GH 56463
+ string_times = ["11:41:43.076160"]
+ result = pd.Series(string_times, dtype="time64[us][pyarrow]")
+ expected = pd.Series(
+ ArrowExtensionArray(pa.array([time(11, 41, 43, 76160)], from_pandas=True))
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_to_numpy_float():
+ # GH#56267
+ ser = pd.Series([32, 40, None], dtype="float[pyarrow]")
+ result = ser.astype("float64")
+ expected = pd.Series([32, 40, np.nan], dtype="float64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_to_numpy_timestamp_to_int():
+ # GH 55997
+ ser = pd.Series(["2020-01-01 04:30:00"], dtype="timestamp[ns][pyarrow]")
+ result = ser.to_numpy(dtype=np.int64)
+ expected = np.array([1577853000000000000])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("arrow_type", [pa.large_string(), pa.string()])
+def test_cast_dictionary_different_value_dtype(arrow_type):
+ df = pd.DataFrame({"a": ["x", "y"]}, dtype="string[pyarrow]")
+ data_type = ArrowDtype(pa.dictionary(pa.int32(), arrow_type))
+ result = df.astype({"a": data_type})
+ assert result.dtypes.iloc[0] == data_type
+
+
+def test_map_numeric_na_action(using_nan_is_na):
+ ser = pd.Series([32, 40, None], dtype="int64[pyarrow]")
+ result = ser.map(lambda x: 42, na_action="ignore")
+ if not using_nan_is_na:
+ expected = pd.Series([42.0, 42.0, pd.NA], dtype="object")
+ else:
+ expected = pd.Series([42.0, 42.0, np.nan], dtype="float64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_categorical_from_arrow_dictionary():
+ # GH 60563
+ df = pd.DataFrame(
+ {"A": ["a1", "a2"]}, dtype=ArrowDtype(pa.dictionary(pa.int32(), pa.utf8()))
+ )
+ result = df.value_counts(dropna=False)
+ expected = pd.Series(
+ [1, 1],
+ index=pd.MultiIndex.from_arrays(
+ [pd.Index(["a1", "a2"], dtype=ArrowDtype(pa.string()), name="A")]
+ ),
+ name="count",
+ dtype="int64",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.skipif(
+ pa_version_under19p0, reason="pa.json_ was introduced in pyarrow v19.0"
+)
+def test_arrow_json_type():
+ # GH 60958
+ dtype = ArrowDtype(pa.json_(pa.string()))
+ result = dtype.type
+ assert result == str
+
+
+def test_timestamp_dtype_disallows_decimal():
+ # GH#61773 constructing with pyarrow timestamp dtype should disallow
+ # Decimal NaN, just like pd.to_datetime
+ vals = [pd.Timestamp("2016-01-02 03:04:05"), Decimal("NaN")]
+
+ msg = " is not convertible to datetime"
+ with pytest.raises(TypeError, match=msg):
+ # Check that the non-pyarrow version raises as expected
+ pd.to_datetime(vals)
+
+ with pytest.raises(TypeError, match=msg):
+ pd.array(vals, dtype=ArrowDtype(pa.timestamp("us")))
+
+
+def test_timestamp_dtype_matches_to_datetime():
+ # GH#61775
+ dtype1 = "datetime64[ns, US/Eastern]"
+ dtype2 = "timestamp[ns, US/Eastern][pyarrow]"
+
+ ts = pd.Timestamp("2025-07-03 18:10")
+
+ result = pd.Series([ts], dtype=dtype2)
+ expected = pd.Series([ts], dtype=dtype1).convert_dtypes(dtype_backend="pyarrow")
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_timestamp_vs_dt64_comparison():
+ # GH#60937
+ left = pd.Series(["2016-01-01"], dtype="timestamp[ns][pyarrow]")
+ right = left.astype("datetime64[ns]")
+
+ result = left == right
+ expected = pd.Series([True], dtype="bool[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+ result = right == left
+ tm.assert_series_equal(result, expected)
+
+
+# TODO: reuse assert_invalid_comparison?
+def test_date_vs_timestamp_scalar_comparison():
+ # GH#62157 match non-pyarrow behavior
+ ser = pd.Series(["2016-01-01"], dtype="date32[pyarrow]")
+ ser2 = ser.astype("timestamp[ns][pyarrow]")
+
+ ts = ser2[0]
+ dt = ser[0]
+
+ # date dtype don't match a Timestamp object
+ assert not (ser == ts).any()
+ assert not (ts == ser).any()
+
+ # timestamp dtype doesn't match date object
+ assert not (ser2 == dt).any()
+ assert not (dt == ser2).any()
+
+
+# TODO: reuse assert_invalid_comparison?
+def test_date_vs_timestamp_array_comparison():
+ # GH#62157 match non-pyarrow behavior
+ # GH#
+ ser = pd.Series(["2016-01-01"], dtype="date32[pyarrow]")
+ ser2 = ser.astype("timestamp[ns][pyarrow]")
+ ser3 = ser.astype("datetime64[ns]")
+
+ assert not (ser == ser2).any()
+ assert not (ser2 == ser).any()
+ assert (ser != ser2).all()
+ assert (ser2 != ser).all()
+
+ assert not (ser == ser3).any()
+ assert not (ser3 == ser).any()
+ assert (ser != ser3).all()
+ assert (ser3 != ser).all()
+
+
+def test_ops_with_nan_is_na(using_nan_is_na):
+ # GH#61732
+ ser = pd.Series([-1, 0, 1], dtype="int64[pyarrow]")
+
+ result = ser - np.nan
+ if using_nan_is_na:
+ assert result.isna().all()
+ else:
+ assert not result.isna().any()
+
+ result = ser * np.nan
+ if using_nan_is_na:
+ assert result.isna().all()
+ else:
+ assert not result.isna().any()
+
+ result = ser / 0
+ if using_nan_is_na:
+ assert result.isna()[1]
+ else:
+ assert not result.isna()[1]
+
+
+def test_setitem_float_nan_is_na(using_nan_is_na):
+ # GH#61732
+ ser = pd.Series([-1, 0, 1], dtype="int64[pyarrow]")
+
+ if using_nan_is_na:
+ ser[1] = np.nan
+ assert ser.isna()[1]
+ else:
+ msg = "Could not convert nan with type float: tried to convert to int64"
+ with pytest.raises(pa.lib.ArrowInvalid, match=msg):
+ ser[1] = np.nan
+
+ ser = pd.Series([-1, np.nan, 1], dtype="float64[pyarrow]")
+ if using_nan_is_na:
+ assert ser.isna()[1]
+ assert ser[1] is pd.NA
+
+ ser[1] = np.nan
+ assert ser[1] is pd.NA
+
+ else:
+ assert not ser.isna()[1]
+ assert isinstance(ser[1], float)
+ assert np.isnan(ser[1])
+
+ ser[2] = np.nan
+ assert isinstance(ser[2], float)
+ assert np.isnan(ser[2])
+
+
+def test_pow_with_all_na_float():
+ # GH#62520
+
+ s = pd.Series([None, None], dtype="float64[pyarrow]")
+ result = s.pow(2)
+ expected = pd.Series([pd.NA, pd.NA], dtype="float64[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+
+def test_mul_numpy_nullable_with_pyarrow_float():
+ # GH#58602
+ left = pd.Series(range(5), dtype="Float64")
+ right = pd.Series(range(5), dtype="float64[pyarrow]")
+
+ expected = pd.Series([0, 1, 4, 9, 16], dtype="float64[pyarrow]")
+
+ result = left * right
+ tm.assert_series_equal(result, expected)
+
+ result2 = right * left
+ tm.assert_series_equal(result2, expected)
+
+ # while we're here, let's check __eq__
+ result3 = left == right
+ expected3 = pd.Series([True] * 5, dtype="bool[pyarrow]")
+ tm.assert_series_equal(result3, expected3)
+
+ result4 = right == left
+ tm.assert_series_equal(result4, expected3)
+
+
+@pytest.mark.parametrize(
+ "type_name, expected_size",
+ [
+ # Integer types
+ ("int8", 1),
+ ("int16", 2),
+ ("int32", 4),
+ ("int64", 8),
+ ("uint8", 1),
+ ("uint16", 2),
+ ("uint32", 4),
+ ("uint64", 8),
+ # Floating point types
+ ("float16", 2),
+ ("float32", 4),
+ ("float64", 8),
+ # Boolean
+ ("bool_", 1),
+ # Date and timestamp types
+ ("date32", 4),
+ ("date64", 8),
+ ("timestamp", 8),
+ # Time types
+ ("time32", 4),
+ ("time64", 8),
+ # Decimal types
+ ("decimal128", 16),
+ ("decimal256", 32),
+ ],
+)
+def test_arrow_dtype_itemsize_fixed_width(type_name, expected_size):
+ # GH 57948
+
+ parametric_type_map = {
+ "timestamp": pa.timestamp("ns"),
+ "time32": pa.time32("s"),
+ "time64": pa.time64("ns"),
+ "decimal128": pa.decimal128(38, 10),
+ "decimal256": pa.decimal256(76, 10),
+ }
+
+ if type_name in parametric_type_map:
+ arrow_type = parametric_type_map.get(type_name)
+ else:
+ arrow_type = getattr(pa, type_name)()
+ dtype = ArrowDtype(arrow_type)
+
+ if type_name == "bool_":
+ expected_size = dtype.numpy_dtype.itemsize
+
+ assert dtype.itemsize == expected_size, (
+ f"{type_name} expected {expected_size}, got {dtype.itemsize} "
+ f"(bit_width={getattr(dtype.pyarrow_dtype, 'bit_width', 'N/A')})"
+ )
+
+
+@pytest.mark.parametrize("type_name", ["string", "binary", "large_string"])
+def test_arrow_dtype_itemsize_variable_width(type_name):
+ # GH 57948
+
+ arrow_type = getattr(pa, type_name)()
+ dtype = ArrowDtype(arrow_type)
+
+ assert dtype.itemsize == dtype.numpy_dtype.itemsize
+
+
+def test_cast_pontwise_result_decimal_nan():
+ # GH#62522 we don't want to get back null[pyarrow] here
+ ser = pd.Series([], dtype="float64[pyarrow]")
+ arr = ser.array
+ item = Decimal("NaN")
+
+ result = arr._cast_pointwise_result([item])
+
+ pa_type = result.dtype.pyarrow_dtype
+ assert pa.types.is_decimal(pa_type)
+
+
+def test_ufunc_retains_missing():
+ # GH#62800
+ ser = pd.Series([0.1, pd.NA], dtype="float64[pyarrow]")
+
+ result = np.sin(ser)
+
+ expected = pd.Series([np.sin(0.1), pd.NA], dtype="float64[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["sum", "min", "max", "mean", "median"])
+def test_duration_reduction_consistency(unit, method):
+ # GH#63170
+ dtype = f"duration[{unit}][pyarrow]"
+ ser = pd.Series([timedelta(seconds=1), timedelta(seconds=2)], dtype=dtype)
+ result = getattr(ser, method)()
+ assert isinstance(result, pd.Timedelta), (
+ f"{method} for {unit} returned {type(result)}"
+ )
+ assert result.unit == unit
+
+
+@pytest.mark.parametrize("method", ["min", "max", "median"])
+def test_timestamp_reduction_consistency(unit, method):
+ # GH#63170
+ dtype = f"timestamp[{unit}][pyarrow]"
+ ser = pd.Series([datetime(2024, 1, 1), datetime(2024, 1, 3)], dtype=dtype)
+ result = getattr(ser, method)()
+ assert isinstance(result, pd.Timestamp), (
+ f"{method} for {unit} returned {type(result)}"
+ )
+ assert result.unit == unit
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_categorical.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_categorical.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb671e74f4b25924121e83cd687530136d1fb54a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_categorical.py
@@ -0,0 +1,192 @@
+"""
+This file contains a minimal set of tests for compliance with the extension
+array interface test suite, and should contain no other tests.
+The test suite for the full functionality of the array is located in
+`pandas/tests/arrays/`.
+
+The tests in this file are inherited from the BaseExtensionTests, and only
+minimal tweaks should be applied to get the tests passing (by overwriting a
+parent method).
+
+Additional tests should either be added to one of the BaseExtensionTests
+classes (if they are relevant for the extension interface for all dtypes), or
+be added to the array-specific tests in `pandas/tests/arrays/`.
+
+"""
+
+import string
+
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+import pandas as pd
+from pandas import Categorical
+import pandas._testing as tm
+from pandas.api.types import CategoricalDtype
+from pandas.tests.extension import base
+
+
+def make_data(n: int):
+ while True:
+ values = np.random.default_rng(2).choice(list(string.ascii_letters), size=n)
+ # ensure we meet the requirements
+ # 1. first two not null
+ # 2. first and second are different
+ if values[0] != values[1]:
+ break
+ return values
+
+
+@pytest.fixture
+def dtype():
+ return CategoricalDtype()
+
+
+@pytest.fixture
+def data():
+ """Length-100 array for this type.
+
+ * data[0] and data[1] should both be non missing
+ * data[0] and data[1] should not be equal
+ """
+ return Categorical(make_data(10))
+
+
+@pytest.fixture
+def data_missing():
+ """Length 2 array with [NA, Valid]"""
+ return Categorical([np.nan, "A"])
+
+
+@pytest.fixture
+def data_for_sorting():
+ return Categorical(["A", "B", "C"], categories=["C", "A", "B"], ordered=True)
+
+
+@pytest.fixture
+def data_missing_for_sorting():
+ return Categorical(["A", None, "B"], categories=["B", "A"], ordered=True)
+
+
+@pytest.fixture
+def data_for_grouping():
+ return Categorical(["a", "a", None, None, "b", "b", "a", "c"])
+
+
+class TestCategorical(base.ExtensionTests):
+ def test_contains(self, data, data_missing):
+ # GH-37867
+ # na value handling in Categorical.__contains__ is deprecated.
+ # See base.BaseInterFaceTests.test_contains for more details.
+
+ na_value = data.dtype.na_value
+ # ensure data without missing values
+ data = data[~data.isna()]
+
+ # first elements are non-missing
+ assert data[0] in data
+ assert data_missing[0] in data_missing
+
+ # check the presence of na_value
+ assert na_value in data_missing
+ assert na_value not in data
+
+ # Categoricals can contain other nan-likes than na_value
+ for na_value_obj in tm.NULL_OBJECTS:
+ if na_value_obj is na_value:
+ continue
+ assert na_value_obj not in data
+ # this section suffers from super method
+ if not using_string_dtype():
+ assert na_value_obj in data_missing
+
+ def test_empty(self, dtype):
+ cls = dtype.construct_array_type()
+ result = cls._empty((4,), dtype=dtype)
+
+ assert isinstance(result, cls)
+ # the dtype we passed is not initialized, so will not match the
+ # dtype on our result.
+ assert result.dtype == CategoricalDtype([])
+
+ @pytest.mark.skip(reason="Backwards compatibility")
+ def test_getitem_scalar(self, data):
+ # CategoricalDtype.type isn't "correct" since it should
+ # be a parent of the elements (object). But don't want
+ # to break things by changing.
+ super().test_getitem_scalar(data)
+
+ def test_combine_add(self, data_repeated):
+ # GH 20825
+ # When adding categoricals in combine, result is a string
+ orig_data1, orig_data2 = data_repeated(2)
+ s1 = pd.Series(orig_data1)
+ s2 = pd.Series(orig_data2)
+ result = s1.combine(s2, lambda x1, x2: x1 + x2)
+ expected = pd.Series(
+ [a + b for (a, b) in zip(list(orig_data1), list(orig_data2), strict=True)]
+ )
+ tm.assert_series_equal(result, expected)
+
+ val = s1.iloc[0]
+ result = s1.combine(val, lambda x1, x2: x1 + x2)
+ expected = pd.Series([a + val for a in list(orig_data1)])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("na_action", [None, "ignore"])
+ def test_map(self, data, na_action):
+ result = data.map(lambda x: x, na_action=na_action)
+ tm.assert_extension_array_equal(result, data)
+
+ def test_arith_frame_with_scalar(self, data, all_arithmetic_operators, request):
+ # frame & scalar
+ op_name = all_arithmetic_operators
+ if op_name == "__rmod__":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="rmod never called when string is first argument"
+ )
+ )
+ super().test_arith_frame_with_scalar(data, op_name)
+
+ def test_arith_series_with_scalar(self, data, all_arithmetic_operators, request):
+ op_name = all_arithmetic_operators
+ if op_name == "__rmod__":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="rmod never called when string is first argument"
+ )
+ )
+ super().test_arith_series_with_scalar(data, op_name)
+
+ def _compare_other(self, ser: pd.Series, data, op, other):
+ op_name = f"__{op.__name__}__"
+ if op_name not in ["__eq__", "__ne__"]:
+ msg = "Unordered Categoricals can only compare equality or not"
+ with pytest.raises(TypeError, match=msg):
+ op(data, other)
+ else:
+ return super()._compare_other(ser, data, op, other)
+
+ @pytest.mark.xfail(reason="Categorical overrides __repr__")
+ @pytest.mark.parametrize("size", ["big", "small"])
+ def test_array_repr(self, data, size):
+ super().test_array_repr(data, size)
+
+ @pytest.mark.xfail(reason="TBD")
+ @pytest.mark.parametrize("as_index", [True, False])
+ def test_groupby_extension_agg(self, as_index, data_for_grouping):
+ super().test_groupby_extension_agg(as_index, data_for_grouping)
+
+
+class Test2DCompat(base.NDArrayBacked2DTests):
+ def test_repr_2d(self, data):
+ # Categorical __repr__ doesn't include "Categorical", so we need
+ # to special-case
+ res = repr(data.reshape(1, -1))
+ assert res.count("\nCategories") == 1
+
+ res = repr(data.reshape(-1, 1))
+ assert res.count("\nCategories") == 1
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa5c91f478c45c363c34a7de1083154baa8f752b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_common.py
@@ -0,0 +1,110 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes import dtypes
+from pandas.core.dtypes.common import is_extension_array_dtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.arrays import ExtensionArray
+
+
+class DummyDtype(dtypes.ExtensionDtype):
+ pass
+
+
+class DummyArray(ExtensionArray):
+ def __init__(self, data) -> None:
+ self.data = data
+
+ def __array__(self, dtype=None, copy=None):
+ return self.data
+
+ @property
+ def dtype(self):
+ return DummyDtype()
+
+ def astype(self, dtype, copy=True):
+ # we don't support anything but a single dtype
+ if isinstance(dtype, DummyDtype):
+ if copy:
+ return type(self)(self.data)
+ return self
+ elif not copy:
+ return np.asarray(self, dtype=dtype)
+ else:
+ return np.array(self, dtype=dtype, copy=copy)
+
+
+class TestExtensionArrayDtype:
+ @pytest.mark.parametrize(
+ "values",
+ [
+ pd.Categorical([]),
+ pd.Categorical([]).dtype,
+ pd.Series(pd.Categorical([])),
+ DummyDtype(),
+ DummyArray(np.array([1, 2])),
+ ],
+ )
+ def test_is_extension_array_dtype(self, values):
+ assert is_extension_array_dtype(values)
+
+ @pytest.mark.parametrize("values", [np.array([]), pd.Series(np.array([]))])
+ def test_is_not_extension_array_dtype(self, values):
+ assert not is_extension_array_dtype(values)
+
+
+def test_astype():
+ arr = DummyArray(np.array([1, 2, 3]))
+ expected = np.array([1, 2, 3], dtype=object)
+
+ result = arr.astype(object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = arr.astype("object")
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_astype_no_copy():
+ arr = DummyArray(np.array([1, 2, 3], dtype=np.int64))
+ result = arr.astype(arr.dtype, copy=False)
+
+ assert arr is result
+
+ result = arr.astype(arr.dtype)
+ assert arr is not result
+
+
+@pytest.mark.parametrize("dtype", [dtypes.CategoricalDtype(), dtypes.IntervalDtype()])
+def test_is_extension_array_dtype(dtype):
+ assert isinstance(dtype, dtypes.ExtensionDtype)
+ assert is_extension_array_dtype(dtype)
+
+
+class CapturingStringArray(pd.arrays.StringArray):
+ """Extend StringArray to capture arguments to __getitem__"""
+
+ def __getitem__(self, item):
+ self.last_item_arg = item
+ return super().__getitem__(item)
+
+
+def test_ellipsis_index():
+ # GH#42430 1D slices over extension types turn into N-dimensional slices
+ # over ExtensionArrays
+ dtype = pd.StringDtype()
+ df = pd.DataFrame(
+ {
+ "col1": CapturingStringArray(
+ np.array(["hello", "world"], dtype=object), dtype=dtype
+ )
+ }
+ )
+ _ = df.iloc[:1]
+
+ # String comparison because there's no native way to compare slices.
+ # Before the fix for GH#42430, last_item_arg would get set to the 2D slice
+ # (Ellipsis, slice(None, 1, None))
+ out = df["col1"]._values.last_item_arg
+ assert str(out) == "slice(None, 1, None)"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_datetime.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab9eff220914da0b9d33ff81bafc8e644d2290b8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_datetime.py
@@ -0,0 +1,148 @@
+"""
+This file contains a minimal set of tests for compliance with the extension
+array interface test suite, and should contain no other tests.
+The test suite for the full functionality of the array is located in
+`pandas/tests/arrays/`.
+
+The tests in this file are inherited from the BaseExtensionTests, and only
+minimal tweaks should be applied to get the tests passing (by overwriting a
+parent method).
+
+Additional tests should either be added to one of the BaseExtensionTests
+classes (if they are relevant for the extension interface for all dtypes), or
+be added to the array-specific tests in `pandas/tests/arrays/`.
+
+"""
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.dtypes import DatetimeTZDtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.arrays import DatetimeArray
+from pandas.tests.extension import base
+
+
+@pytest.fixture
+def dtype():
+ return DatetimeTZDtype(unit="ns", tz="US/Central")
+
+
+@pytest.fixture
+def data(dtype):
+ data = DatetimeArray._from_sequence(
+ pd.date_range("2000", periods=10, tz=dtype.tz), dtype=dtype
+ )
+ return data
+
+
+@pytest.fixture
+def data_missing(dtype):
+ return DatetimeArray._from_sequence(
+ np.array(["NaT", "2000-01-01"], dtype="datetime64[ns]"), dtype=dtype
+ )
+
+
+@pytest.fixture
+def data_for_sorting(dtype):
+ a = pd.Timestamp("2000-01-01")
+ b = pd.Timestamp("2000-01-02")
+ c = pd.Timestamp("2000-01-03")
+ return DatetimeArray._from_sequence(
+ np.array([b, c, a], dtype="datetime64[ns]"), dtype=dtype
+ )
+
+
+@pytest.fixture
+def data_missing_for_sorting(dtype):
+ a = pd.Timestamp("2000-01-01")
+ b = pd.Timestamp("2000-01-02")
+ return DatetimeArray._from_sequence(
+ np.array([b, "NaT", a], dtype="datetime64[ns]"), dtype=dtype
+ )
+
+
+@pytest.fixture
+def data_for_grouping(dtype):
+ """
+ Expected to be like [B, B, NA, NA, A, A, B, C]
+
+ Where A < B < C and NA is missing
+ """
+ a = pd.Timestamp("2000-01-01")
+ b = pd.Timestamp("2000-01-02")
+ c = pd.Timestamp("2000-01-03")
+ na = "NaT"
+ return DatetimeArray._from_sequence(
+ np.array([b, b, na, na, a, a, b, c], dtype="datetime64[ns]"), dtype=dtype
+ )
+
+
+@pytest.fixture
+def na_cmp():
+ def cmp(a, b):
+ return a is pd.NaT and a is b
+
+ return cmp
+
+
+# ----------------------------------------------------------------------------
+class TestDatetimeArray(base.ExtensionTests):
+ def _get_expected_exception(self, op_name, obj, other):
+ if op_name in ["__sub__", "__rsub__"]:
+ return None
+ return super()._get_expected_exception(op_name, obj, other)
+
+ def _get_expected_reduction_dtype(self, arr, op_name: str, skipna: bool):
+ if op_name == "std":
+ return "timedelta64[ns]"
+ return arr.dtype
+
+ def _supports_accumulation(self, ser, op_name: str) -> bool:
+ return op_name in ["cummin", "cummax"]
+
+ def _supports_reduction(self, obj, op_name: str) -> bool:
+ return op_name in ["min", "max", "median", "mean", "std", "any", "all"]
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_series_boolean(self, data, all_boolean_reductions, skipna):
+ meth = all_boolean_reductions
+ msg = f"datetime64 type does not support operation '{meth}'"
+ with pytest.raises(TypeError, match=msg):
+ super().test_reduce_series_boolean(data, all_boolean_reductions, skipna)
+
+ def test_series_constructor(self, data):
+ # Series construction drops any .freq attr
+ data = data._with_freq(None)
+ super().test_series_constructor(data)
+
+ @pytest.mark.parametrize("na_action", [None, "ignore"])
+ def test_map(self, data, na_action):
+ result = data.map(lambda x: x, na_action=na_action)
+ tm.assert_extension_array_equal(result, data)
+
+ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool):
+ if op_name in ["median", "mean", "std"]:
+ alt = ser.astype("int64")
+
+ res_op = getattr(ser, op_name)
+ exp_op = getattr(alt, op_name)
+ result = res_op(skipna=skipna)
+ expected = exp_op(skipna=skipna)
+ if op_name in ["mean", "median"]:
+ # error: Item "dtype[Any]" of "dtype[Any] | ExtensionDtype"
+ # has no attribute "tz"
+ tz = ser.dtype.tz # type: ignore[union-attr]
+ expected = pd.Timestamp(expected, tz=tz)
+ else:
+ expected = pd.Timedelta(expected)
+ tm.assert_almost_equal(result, expected)
+
+ else:
+ return super().check_reduce(ser, op_name, skipna)
+
+
+class Test2DCompat(base.NDArrayBacked2DTests):
+ pass
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_extension.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_extension.py
new file mode 100644
index 0000000000000000000000000000000000000000..456f4863b1c313c624ac42905a3b4117f6ba556d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_extension.py
@@ -0,0 +1,27 @@
+"""
+Tests for behavior if an author does *not* implement EA methods.
+"""
+
+import numpy as np
+import pytest
+
+from pandas.core.arrays import ExtensionArray
+
+
+class MyEA(ExtensionArray):
+ def __init__(self, values) -> None:
+ self._values = values
+
+
+@pytest.fixture
+def data():
+ arr = np.arange(10)
+ return MyEA(arr)
+
+
+class TestExtensionArray:
+ def test_errors(self, data, all_arithmetic_operators):
+ # invalid ops
+ op_name = all_arithmetic_operators
+ with pytest.raises(AttributeError):
+ getattr(data, op_name)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_interval.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_interval.py
new file mode 100644
index 0000000000000000000000000000000000000000..47bc26ba4a7666b67fe774b4287a6e9660cd82c8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_interval.py
@@ -0,0 +1,147 @@
+"""
+This file contains a minimal set of tests for compliance with the extension
+array interface test suite, and should contain no other tests.
+The test suite for the full functionality of the array is located in
+`pandas/tests/arrays/`.
+
+The tests in this file are inherited from the BaseExtensionTests, and only
+minimal tweaks should be applied to get the tests passing (by overwriting a
+parent method).
+
+Additional tests should either be added to one of the BaseExtensionTests
+classes (if they are relevant for the extension interface for all dtypes), or
+be added to the array-specific tests in `pandas/tests/arrays/`.
+
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.dtypes import IntervalDtype
+
+from pandas import Interval
+from pandas.core.arrays import IntervalArray
+from pandas.tests.extension import base
+
+if TYPE_CHECKING:
+ import pandas as pd
+
+
+def make_data(n: int):
+ left_array = np.random.default_rng(2).uniform(size=n).cumsum()
+ right_array = left_array + np.random.default_rng(2).uniform(size=n)
+ return [
+ Interval(left, right)
+ for left, right in zip(left_array, right_array, strict=True)
+ ]
+
+
+@pytest.fixture
+def dtype():
+ return IntervalDtype()
+
+
+@pytest.fixture
+def data():
+ """Length-10 IntervalArray for semantics test."""
+ return IntervalArray(make_data(10))
+
+
+@pytest.fixture
+def data_missing():
+ """Length 2 array with [NA, Valid]"""
+ return IntervalArray.from_tuples([None, (0, 1)])
+
+
+@pytest.fixture
+def data_for_twos():
+ pytest.skip("Interval is not a numeric dtype")
+
+
+@pytest.fixture
+def data_for_sorting():
+ return IntervalArray.from_tuples([(1, 2), (2, 3), (0, 1)])
+
+
+@pytest.fixture
+def data_missing_for_sorting():
+ return IntervalArray.from_tuples([(1, 2), None, (0, 1)])
+
+
+@pytest.fixture
+def data_for_grouping():
+ a = (0, 1)
+ b = (1, 2)
+ c = (2, 3)
+ return IntervalArray.from_tuples([b, b, None, None, a, a, b, c])
+
+
+class TestIntervalArray(base.ExtensionTests):
+ divmod_exc = TypeError
+
+ def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool:
+ return op_name in ["min", "max"]
+
+ def test_fillna_limit_frame(self, data_missing):
+ # GH#58001
+ with pytest.raises(ValueError, match="limit must be None"):
+ super().test_fillna_limit_frame(data_missing)
+
+ def test_fillna_limit_series(self, data_missing):
+ # GH#58001
+ with pytest.raises(ValueError, match="limit must be None"):
+ super().test_fillna_limit_frame(data_missing)
+
+ @pytest.mark.xfail(
+ reason="Raises with incorrect message bc it disallows *all* listlikes "
+ "instead of just wrong-length listlikes"
+ )
+ def test_fillna_length_mismatch(self, data_missing):
+ super().test_fillna_length_mismatch(data_missing)
+
+ @pytest.mark.xfail(reason="copy=False is not Implemented")
+ def test_fillna_readonly(self, data_missing):
+ super().test_fillna_readonly(data_missing)
+
+ @pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in cast:RuntimeWarning"
+ )
+ def test_hash_pandas_object(self, data):
+ super().test_hash_pandas_object(data)
+
+ @pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in cast:RuntimeWarning"
+ )
+ def test_hash_pandas_object_works(self, data, as_frame):
+ super().test_hash_pandas_object_works(data, as_frame)
+
+ @pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in cast:RuntimeWarning"
+ )
+ @pytest.mark.parametrize("engine", ["c", "python"])
+ def test_EA_types(self, engine, data, request):
+ super().test_EA_types(engine, data, request)
+
+ @pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in cast:RuntimeWarning"
+ )
+ def test_astype_str(self, data):
+ super().test_astype_str(data)
+
+ @pytest.mark.xfail(
+ reason="Test is invalid for IntervalDtype, needs to be adapted for "
+ "this dtype with an index with index._index_as_unique."
+ )
+ def test_loc_setitem_with_expansion_preserves_ea_index_dtype(self, data):
+ super().test_loc_setitem_with_expansion_preserves_ea_index_dtype(data)
+
+
+# TODO: either belongs in tests.arrays.interval or move into base tests.
+def test_fillna_non_scalar_raises(data_missing):
+ msg = "can only insert Interval objects and NA into an IntervalArray"
+ with pytest.raises(TypeError, match=msg):
+ data_missing.fillna([1, 1])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_masked.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_masked.py
new file mode 100644
index 0000000000000000000000000000000000000000..fadc51ea714c0413133c1ced5b6666e52fd1cf51
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_masked.py
@@ -0,0 +1,375 @@
+"""
+This file contains a minimal set of tests for compliance with the extension
+array interface test suite, and should contain no other tests.
+The test suite for the full functionality of the array is located in
+`pandas/tests/arrays/`.
+
+The tests in this file are inherited from the BaseExtensionTests, and only
+minimal tweaks should be applied to get the tests passing (by overwriting a
+parent method).
+
+Additional tests should either be added to one of the BaseExtensionTests
+classes (if they are relevant for the extension interface for all dtypes), or
+be added to the array-specific tests in `pandas/tests/arrays/`.
+
+"""
+
+import numpy as np
+import pytest
+
+from pandas.compat import (
+ IS64,
+ is_platform_windows,
+)
+from pandas.compat.numpy import np_version_gt2
+
+from pandas.core.dtypes.common import (
+ is_float_dtype,
+ is_signed_integer_dtype,
+ is_unsigned_integer_dtype,
+)
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.arrays.boolean import BooleanDtype
+from pandas.core.arrays.floating import (
+ Float32Dtype,
+ Float64Dtype,
+)
+from pandas.core.arrays.integer import (
+ Int8Dtype,
+ Int16Dtype,
+ Int32Dtype,
+ Int64Dtype,
+ UInt8Dtype,
+ UInt16Dtype,
+ UInt32Dtype,
+ UInt64Dtype,
+)
+from pandas.tests.extension import base
+
+is_windows_or_32bit = (is_platform_windows() and not np_version_gt2) or not IS64
+
+pytestmark = [
+ pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in divide:RuntimeWarning"
+ ),
+ pytest.mark.filterwarnings("ignore:Mean of empty slice:RuntimeWarning"),
+ # overflow only relevant for Floating dtype cases cases
+ pytest.mark.filterwarnings("ignore:overflow encountered in reduce:RuntimeWarning"),
+]
+
+
+def make_data():
+ return [1, 2, 3, 4, pd.NA, 10, 11, pd.NA, 99, 100]
+
+
+def make_float_data():
+ return [0.1, 0.2, 0.3, 0.4, pd.NA, 1.0, 1.1, pd.NA, 9.9, 10.0]
+
+
+def make_bool_data():
+ return [True, False] * 2 + [np.nan] + [True, False] + [np.nan] + [True, False]
+
+
+@pytest.fixture(
+ params=[
+ Int8Dtype,
+ Int16Dtype,
+ Int32Dtype,
+ Int64Dtype,
+ UInt8Dtype,
+ UInt16Dtype,
+ UInt32Dtype,
+ UInt64Dtype,
+ Float32Dtype,
+ Float64Dtype,
+ BooleanDtype,
+ ]
+)
+def dtype(request):
+ return request.param()
+
+
+@pytest.fixture
+def data(dtype):
+ if dtype.kind == "f":
+ data = make_float_data()
+ elif dtype.kind == "b":
+ data = make_bool_data()
+ else:
+ data = make_data()
+ return pd.array(data, dtype=dtype)
+
+
+@pytest.fixture
+def data_for_twos(dtype):
+ if dtype.kind == "b":
+ return pd.array(np.ones(10), dtype=dtype)
+ return pd.array(np.ones(10) * 2, dtype=dtype)
+
+
+@pytest.fixture
+def data_missing(dtype):
+ if dtype.kind == "f":
+ return pd.array([pd.NA, 0.1], dtype=dtype)
+ elif dtype.kind == "b":
+ return pd.array([np.nan, True], dtype=dtype)
+ return pd.array([pd.NA, 1], dtype=dtype)
+
+
+@pytest.fixture
+def data_for_sorting(dtype):
+ if dtype.kind == "f":
+ return pd.array([0.1, 0.2, 0.0], dtype=dtype)
+ elif dtype.kind == "b":
+ return pd.array([True, True, False], dtype=dtype)
+ return pd.array([1, 2, 0], dtype=dtype)
+
+
+@pytest.fixture
+def data_missing_for_sorting(dtype):
+ if dtype.kind == "f":
+ return pd.array([0.1, pd.NA, 0.0], dtype=dtype)
+ elif dtype.kind == "b":
+ return pd.array([True, np.nan, False], dtype=dtype)
+ return pd.array([1, pd.NA, 0], dtype=dtype)
+
+
+@pytest.fixture
+def na_cmp():
+ # we are pd.NA
+ return lambda x, y: x is pd.NA and y is pd.NA
+
+
+@pytest.fixture
+def data_for_grouping(dtype):
+ if dtype.kind == "f":
+ b = 0.1
+ a = 0.0
+ c = 0.2
+ elif dtype.kind == "b":
+ b = True
+ a = False
+ c = b
+ else:
+ b = 1
+ a = 0
+ c = 2
+
+ na = pd.NA
+ return pd.array([b, b, na, na, a, a, b, c], dtype=dtype)
+
+
+class TestMaskedArrays(base.ExtensionTests):
+ _combine_le_expected_dtype = "boolean"
+
+ @pytest.fixture(autouse=True)
+ def skip_if_doesnt_support_2d(self, dtype, request):
+ # Override the fixture so that we run these tests.
+ assert not dtype._supports_2d
+ # If dtype._supports_2d is ever changed to True, then this fixture
+ # override becomes unnecessary.
+
+ @pytest.mark.parametrize("na_action", [None, "ignore"])
+ def test_map(self, data_missing, na_action, using_nan_is_na):
+ result = data_missing.map(lambda x: x, na_action=na_action)
+ if data_missing.dtype == Float32Dtype() and using_nan_is_na:
+ # map roundtrips through objects, which converts to float64
+ expected = data_missing.to_numpy(dtype="float64", na_value=np.nan)
+ else:
+ expected = data_missing.to_numpy()
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_map_na_action_ignore(self, data_missing_for_sorting, using_nan_is_na):
+ zero = data_missing_for_sorting[2]
+ result = data_missing_for_sorting.map(lambda x: zero, na_action="ignore")
+ if data_missing_for_sorting.dtype.kind == "b":
+ expected = np.array([False, pd.NA, False], dtype=object)
+ elif not using_nan_is_na:
+ # TODO: would we prefer to get NaN in this case to get a non-object?
+ expected = np.array([zero, pd.NA, zero], dtype=object)
+ else:
+ expected = np.array([zero, np.nan, zero])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def _get_expected_exception(self, op_name, obj, other):
+ try:
+ dtype = tm.get_dtype(obj)
+ except AttributeError:
+ # passed arguments reversed
+ dtype = tm.get_dtype(other)
+
+ if dtype.kind == "b":
+ if op_name.strip("_").lstrip("r") in ["pow", "truediv", "floordiv"]:
+ # match behavior with non-masked bool dtype
+ return NotImplementedError
+ elif op_name in ["__sub__", "__rsub__"]:
+ # exception message would include "numpy boolean subtract""
+ return TypeError
+ return None
+ return None
+
+ def _cast_pointwise_result(self, op_name: str, obj, other, pointwise_result):
+ sdtype = tm.get_dtype(obj)
+ expected = pointwise_result
+
+ if sdtype.kind == "b":
+ if op_name in (
+ "__mod__",
+ "__rmod__",
+ ):
+ # combine keeps boolean type
+ expected = expected.astype("Int8")
+
+ return expected
+
+ def test_divmod_series_array(self, data, data_for_twos, request):
+ if data.dtype.kind == "b":
+ mark = pytest.mark.xfail(
+ reason="Inconsistency between floordiv and divmod; we raise for "
+ "floordiv but not for divmod. This matches what we do for "
+ "non-masked bool dtype."
+ )
+ request.applymarker(mark)
+ super().test_divmod_series_array(data, data_for_twos)
+
+ def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool:
+ if op_name in ["any", "all"] and ser.dtype.kind != "b":
+ pytest.skip(reason="Tested in tests/reductions/test_reductions.py")
+ return True
+
+ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool):
+ # overwrite to ensure pd.NA is tested instead of np.nan
+ # https://github.com/pandas-dev/pandas/issues/30958
+
+ cmp_dtype = "int64"
+ if ser.dtype.kind == "f":
+ # Item "dtype[Any]" of "Union[dtype[Any], ExtensionDtype]" has
+ # no attribute "numpy_dtype"
+ cmp_dtype = ser.dtype.numpy_dtype # type: ignore[union-attr]
+ elif ser.dtype.kind == "b":
+ if op_name in ["min", "max"]:
+ cmp_dtype = "bool"
+
+ # TODO: prod with integer dtypes does *not* match the result we would
+ # get if we used object for cmp_dtype. In that cae the object result
+ # is a large integer while the non-object case overflows and returns 0
+ alt = ser.dropna().astype(cmp_dtype)
+ if op_name == "count":
+ result = getattr(ser, op_name)()
+ expected = getattr(alt, op_name)()
+ else:
+ result = getattr(ser, op_name)(skipna=skipna)
+ expected = getattr(alt, op_name)(skipna=skipna)
+ if not skipna and ser.isna().any() and op_name not in ["any", "all"]:
+ expected = pd.NA
+ tm.assert_almost_equal(result, expected)
+
+ def _get_expected_reduction_dtype(self, arr, op_name: str, skipna: bool):
+ if is_float_dtype(arr.dtype):
+ cmp_dtype = arr.dtype.name
+ elif op_name in ["mean", "median", "var", "std", "skew", "kurt", "sem"]:
+ cmp_dtype = "Float64"
+ elif op_name in ["max", "min"]:
+ cmp_dtype = arr.dtype.name
+ elif arr.dtype in ["Int64", "UInt64"]:
+ cmp_dtype = arr.dtype.name
+ elif is_signed_integer_dtype(arr.dtype):
+ # TODO: Why does Window Numpy 2.0 dtype depend on skipna?
+ cmp_dtype = (
+ "Int32"
+ if (is_platform_windows() and (not np_version_gt2 or not skipna))
+ or not IS64
+ else "Int64"
+ )
+ elif is_unsigned_integer_dtype(arr.dtype):
+ cmp_dtype = (
+ "UInt32"
+ if (is_platform_windows() and (not np_version_gt2 or not skipna))
+ or not IS64
+ else "UInt64"
+ )
+ elif arr.dtype.kind == "b":
+ if op_name in ["min", "max"]:
+ cmp_dtype = "boolean"
+ elif op_name in ["sum", "prod"]:
+ cmp_dtype = (
+ "Int32"
+ if (is_platform_windows() and (not np_version_gt2 or not skipna))
+ or not IS64
+ else "Int64"
+ )
+ else:
+ raise TypeError("not supposed to reach this")
+ else:
+ raise TypeError("not supposed to reach this")
+ return cmp_dtype
+
+ def _supports_accumulation(self, ser: pd.Series, op_name: str) -> bool:
+ return True
+
+ def check_accumulate(self, ser: pd.Series, op_name: str, skipna: bool):
+ # overwrite to ensure pd.NA is tested instead of np.nan
+ # https://github.com/pandas-dev/pandas/issues/30958
+ length = 64
+ if is_windows_or_32bit:
+ # Item "ExtensionDtype" of "Union[dtype[Any], ExtensionDtype]" has
+ # no attribute "itemsize"
+ if not ser.dtype.itemsize == 8: # type: ignore[union-attr]
+ length = 32
+
+ if ser.dtype.name.startswith("U"):
+ expected_dtype = f"UInt{length}"
+ elif ser.dtype.name.startswith("I"):
+ expected_dtype = f"Int{length}"
+ elif ser.dtype.name.startswith("F"):
+ # Incompatible types in assignment (expression has type
+ # "Union[dtype[Any], ExtensionDtype]", variable has type "str")
+ expected_dtype = ser.dtype # type: ignore[assignment]
+ elif ser.dtype.kind == "b":
+ if op_name in ("cummin", "cummax"):
+ expected_dtype = "boolean"
+ else:
+ expected_dtype = f"Int{length}"
+
+ if expected_dtype == "Float32" and op_name == "cumprod" and skipna:
+ # TODO: xfail?
+ pytest.skip(
+ f"Float32 precision lead to large differences with op {op_name} "
+ f"and skipna={skipna}"
+ )
+
+ if op_name == "cumsum":
+ pass
+ elif op_name in ["cummax", "cummin"]:
+ expected_dtype = ser.dtype # type: ignore[assignment]
+ elif op_name == "cumprod":
+ ser = ser[:12]
+ else:
+ raise NotImplementedError(f"{op_name} not supported")
+
+ result = getattr(ser, op_name)(skipna=skipna)
+ expected = pd.Series(
+ pd.array(
+ getattr(ser.astype("float64"), op_name)(skipna=skipna),
+ dtype="Float64",
+ )
+ )
+ expected[np.isnan(expected)] = pd.NA
+ expected = expected.astype(expected_dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_setitem_with_expansion_preserves_ea_index_dtype(self, data, request):
+ super().test_loc_setitem_with_expansion_preserves_ea_index_dtype(data)
+
+
+@pytest.mark.parametrize(
+ "arr", [pd.array([True, False]), pd.array([1, 2]), pd.array([1.0, 2.0])]
+)
+def test_cast_pointwise_result_all_na_respects_original_dtype(arr):
+ # GH#62344
+ values = [pd.NA, pd.NA]
+ result = arr._cast_pointwise_result(values)
+ assert result.dtype == arr.dtype
+ assert all(x is pd.NA for x in result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_numpy.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_numpy.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3f619e4263df86162ef6d9dee9698aa7492d83d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_numpy.py
@@ -0,0 +1,439 @@
+"""
+This file contains a minimal set of tests for compliance with the extension
+array interface test suite, and should contain no other tests.
+The test suite for the full functionality of the array is located in
+`pandas/tests/arrays/`.
+
+The tests in this file are inherited from the BaseExtensionTests, and only
+minimal tweaks should be applied to get the tests passing (by overwriting a
+parent method).
+
+Additional tests should either be added to one of the BaseExtensionTests
+classes (if they are relevant for the extension interface for all dtypes), or
+be added to the array-specific tests in `pandas/tests/arrays/`.
+
+Note: we do not bother with base.BaseIndexTests because NumpyExtensionArray
+will never be held in an Index.
+"""
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.dtypes import NumpyEADtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.types import is_object_dtype
+from pandas.core.arrays.numpy_ import NumpyExtensionArray
+from pandas.tests.extension import base
+
+orig_assert_attr_equal = tm.assert_attr_equal
+
+
+def _assert_attr_equal(attr: str, left, right, obj: str = "Attributes"):
+ """
+ patch tm.assert_attr_equal so NumpyEADtype("object") is closed enough to
+ np.dtype("object")
+ """
+ if attr == "dtype":
+ lattr = getattr(left, "dtype", None)
+ rattr = getattr(right, "dtype", None)
+ if isinstance(lattr, NumpyEADtype) and not isinstance(rattr, NumpyEADtype):
+ left = left.astype(lattr.numpy_dtype)
+ elif isinstance(rattr, NumpyEADtype) and not isinstance(lattr, NumpyEADtype):
+ right = right.astype(rattr.numpy_dtype)
+
+ orig_assert_attr_equal(attr, left, right, obj)
+
+
+@pytest.fixture(params=["float", "object"])
+def dtype(request):
+ return NumpyEADtype(np.dtype(request.param))
+
+
+@pytest.fixture
+def allow_in_pandas(monkeypatch):
+ """
+ A monkeypatch to tells pandas to let us in.
+
+ By default, passing a NumpyExtensionArray to an index / series / frame
+ constructor will unbox that NumpyExtensionArray to an ndarray, and treat
+ it as a non-EA column. We don't want people using EAs without
+ reason.
+
+ The mechanism for this is a check against ABCNumpyExtensionArray
+ in each constructor.
+
+ But, for testing, we need to allow them in pandas. So we patch
+ the _typ of NumpyExtensionArray, so that we evade the ABCNumpyExtensionArray
+ check.
+ """
+ with monkeypatch.context() as m:
+ m.setattr(NumpyExtensionArray, "_typ", "extension")
+ m.setattr(tm.asserters, "assert_attr_equal", _assert_attr_equal)
+ yield
+
+
+@pytest.fixture
+def data(allow_in_pandas, dtype):
+ if dtype.numpy_dtype == "object":
+ arr = pd.Series([(i,) for i in range(10)])._values
+ else:
+ arr = np.arange(1, 11, dtype=dtype._dtype)
+ return NumpyExtensionArray(arr)
+
+
+@pytest.fixture
+def data_missing(allow_in_pandas, dtype):
+ if dtype.numpy_dtype == "object":
+ return NumpyExtensionArray(np.array([np.nan, (1,)], dtype=object))
+ return NumpyExtensionArray(np.array([np.nan, 1.0]))
+
+
+@pytest.fixture
+def na_cmp():
+ def cmp(a, b):
+ return np.isnan(a) and np.isnan(b)
+
+ return cmp
+
+
+@pytest.fixture
+def data_for_sorting(allow_in_pandas, dtype):
+ """Length-3 array with a known sort order.
+
+ This should be three items [B, C, A] with
+ A < B < C
+ """
+ if dtype.numpy_dtype == "object":
+ # Use an empty tuple for first element, then remove,
+ # to disable np.array's shape inference.
+ return NumpyExtensionArray(np.array([(), (2,), (3,), (1,)], dtype=object)[1:])
+ return NumpyExtensionArray(np.array([1, 2, 0]))
+
+
+@pytest.fixture
+def data_missing_for_sorting(allow_in_pandas, dtype):
+ """Length-3 array with a known sort order.
+
+ This should be three items [B, NA, A] with
+ A < B and NA missing.
+ """
+ if dtype.numpy_dtype == "object":
+ return NumpyExtensionArray(np.array([(1,), np.nan, (0,)], dtype=object))
+ return NumpyExtensionArray(np.array([1, np.nan, 0]))
+
+
+@pytest.fixture
+def data_for_grouping(allow_in_pandas, dtype):
+ """Data for factorization, grouping, and unique tests.
+
+ Expected to be like [B, B, NA, NA, A, A, B, C]
+
+ Where A < B < C and NA is missing
+ """
+ if dtype.numpy_dtype == "object":
+ a, b, c = (1,), (2,), (3,)
+ else:
+ a, b, c = np.arange(3)
+ return NumpyExtensionArray(
+ np.array([b, b, np.nan, np.nan, a, a, b, c], dtype=dtype.numpy_dtype)
+ )
+
+
+@pytest.fixture
+def data_for_twos(dtype):
+ if dtype.kind == "O":
+ pytest.skip(f"{dtype} is not a numeric dtype")
+ arr = np.ones(10) * 2
+ return NumpyExtensionArray._from_sequence(arr, dtype=dtype)
+
+
+@pytest.fixture
+def skip_numpy_object(dtype, request):
+ """
+ Tests for NumpyExtensionArray with nested data. Users typically won't create
+ these objects via `pd.array`, but they can show up through `.array`
+ on a Series with nested data. Many of the base tests fail, as they aren't
+ appropriate for nested data.
+
+ This fixture allows these tests to be skipped when used as a usefixtures
+ marker to either an individual test or a test class.
+ """
+ if dtype == "object":
+ mark = pytest.mark.xfail(reason="Fails for object dtype")
+ request.applymarker(mark)
+
+
+skip_nested = pytest.mark.usefixtures("skip_numpy_object")
+
+
+class TestNumpyExtensionArray(base.ExtensionTests):
+ @pytest.mark.skip(reason="We don't register our dtype")
+ # We don't want to register. This test should probably be split in two.
+ def test_from_dtype(self, data):
+ pass
+
+ @skip_nested
+ def test_series_constructor_scalar_with_index(self, data, dtype):
+ # ValueError: Length of passed values is 1, index implies 3.
+ super().test_series_constructor_scalar_with_index(data, dtype)
+
+ def test_check_dtype(self, data, request, using_infer_string):
+ if data.dtype.numpy_dtype == "object":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=f"NumpyExtensionArray expectedly clashes with a "
+ f"NumPy name: {data.dtype.numpy_dtype}"
+ )
+ )
+ super().test_check_dtype(data)
+
+ def test_is_not_object_type(self, dtype, request):
+ if dtype.numpy_dtype == "object":
+ # Different from BaseDtypeTests.test_is_not_object_type
+ # because NumpyEADtype(object) is an object type
+ assert is_object_dtype(dtype)
+ else:
+ super().test_is_not_object_type(dtype)
+
+ @skip_nested
+ def test_getitem_scalar(self, data):
+ # AssertionError
+ super().test_getitem_scalar(data)
+
+ @skip_nested
+ def test_shift_fill_value(self, data):
+ # np.array shape inference. Shift implementation fails.
+ super().test_shift_fill_value(data)
+
+ @skip_nested
+ def test_fillna_limit_frame(self, data_missing):
+ # GH#58001
+ # The "scalar" for this array isn't a scalar.
+ super().test_fillna_limit_frame(data_missing)
+
+ @skip_nested
+ def test_fillna_limit_series(self, data_missing):
+ # GH#58001
+ # The "scalar" for this array isn't a scalar.
+ super().test_fillna_limit_series(data_missing)
+
+ @skip_nested
+ def test_fillna_copy_frame(self, data_missing):
+ # The "scalar" for this array isn't a scalar.
+ super().test_fillna_copy_frame(data_missing)
+
+ @skip_nested
+ def test_fillna_copy_series(self, data_missing):
+ # The "scalar" for this array isn't a scalar.
+ super().test_fillna_copy_series(data_missing)
+
+ @skip_nested
+ def test_searchsorted(self, data_for_sorting, as_series):
+ # TODO: NumpyExtensionArray.searchsorted calls ndarray.searchsorted which
+ # isn't quite what we want in nested data cases. Instead we need to
+ # adapt something like libindex._bin_search.
+ super().test_searchsorted(data_for_sorting, as_series)
+
+ @pytest.mark.xfail(reason="NumpyExtensionArray.diff may fail on dtype")
+ def test_diff(self, data, periods):
+ return super().test_diff(data, periods)
+
+ def test_insert(self, data, request):
+ if data.dtype.numpy_dtype == object:
+ mark = pytest.mark.xfail(reason="Dimension mismatch in np.concatenate")
+ request.applymarker(mark)
+
+ super().test_insert(data)
+
+ @skip_nested
+ def test_insert_invalid(self, data, invalid_scalar):
+ # NumpyExtensionArray[object] can hold anything, so skip
+ super().test_insert_invalid(data, invalid_scalar)
+
+ divmod_exc = None
+ series_scalar_exc = None
+ frame_scalar_exc = None
+ series_array_exc = None
+
+ def test_divmod(self, data):
+ divmod_exc = None
+ if data.dtype.kind == "O":
+ divmod_exc = TypeError
+ self.divmod_exc = divmod_exc
+ super().test_divmod(data)
+
+ def test_divmod_series_array(self, data):
+ ser = pd.Series(data)
+ exc = None
+ if data.dtype.kind == "O":
+ exc = TypeError
+ self.divmod_exc = exc
+ self._check_divmod_op(ser, divmod, data)
+
+ def test_arith_series_with_scalar(self, data, all_arithmetic_operators, request):
+ opname = all_arithmetic_operators
+ series_scalar_exc = None
+ if data.dtype.numpy_dtype == object:
+ if opname in ["__mul__", "__rmul__"]:
+ mark = pytest.mark.xfail(
+ reason="the Series.combine step raises but not the Series method."
+ )
+ request.node.add_marker(mark)
+ series_scalar_exc = TypeError
+ self.series_scalar_exc = series_scalar_exc
+ super().test_arith_series_with_scalar(data, all_arithmetic_operators)
+
+ def test_arith_series_with_array(self, data, all_arithmetic_operators):
+ opname = all_arithmetic_operators
+ series_array_exc = None
+ if data.dtype.numpy_dtype == object and opname not in ["__add__", "__radd__"]:
+ series_array_exc = TypeError
+ self.series_array_exc = series_array_exc
+ super().test_arith_series_with_array(data, all_arithmetic_operators)
+
+ def test_arith_frame_with_scalar(self, data, all_arithmetic_operators, request):
+ opname = all_arithmetic_operators
+ frame_scalar_exc = None
+ if data.dtype.numpy_dtype == object:
+ if opname in ["__mul__", "__rmul__"]:
+ mark = pytest.mark.xfail(
+ reason="the Series.combine step raises but not the Series method."
+ )
+ request.node.add_marker(mark)
+ frame_scalar_exc = TypeError
+ self.frame_scalar_exc = frame_scalar_exc
+ super().test_arith_frame_with_scalar(data, all_arithmetic_operators)
+
+ def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool:
+ if ser.dtype.kind == "O":
+ return op_name in ["sum", "min", "max", "any", "all"]
+ return True
+
+ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool):
+ res_op = getattr(ser, op_name)
+ # avoid coercing int -> float. Just cast to the actual numpy type.
+ # error: Item "ExtensionDtype" of "dtype[Any] | ExtensionDtype" has
+ # no attribute "numpy_dtype"
+ cmp_dtype = ser.dtype.numpy_dtype # type: ignore[union-attr]
+ alt = ser.astype(cmp_dtype)
+ exp_op = getattr(alt, op_name)
+ if op_name == "count":
+ result = res_op()
+ expected = exp_op()
+ else:
+ result = res_op(skipna=skipna)
+ expected = exp_op(skipna=skipna)
+ tm.assert_almost_equal(result, expected)
+
+ @pytest.mark.skip("TODO: tests not written yet")
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_frame(self, data, all_numeric_reductions, skipna):
+ pass
+
+ @skip_nested
+ def test_fillna_series(self, data_missing):
+ # Non-scalar "scalar" values.
+ super().test_fillna_series(data_missing)
+
+ @skip_nested
+ def test_fillna_frame(self, data_missing):
+ # Non-scalar "scalar" values.
+ super().test_fillna_frame(data_missing)
+
+ @skip_nested
+ def test_fillna_readonly(self, data_missing):
+ # Non-scalar "scalar" values.
+ super().test_fillna_readonly(data_missing)
+
+ @skip_nested
+ def test_setitem_invalid(self, data, invalid_scalar):
+ # object dtype can hold anything, so doesn't raise
+ super().test_setitem_invalid(data, invalid_scalar)
+
+ @skip_nested
+ def test_setitem_sequence_broadcasts(self, data, box_in_series):
+ # ValueError: cannot set using a list-like indexer with a different
+ # length than the value
+ super().test_setitem_sequence_broadcasts(data, box_in_series)
+
+ @skip_nested
+ @pytest.mark.parametrize("setter", ["loc", None])
+ def test_setitem_mask_broadcast(self, data, setter):
+ # ValueError: cannot set using a list-like indexer with a different
+ # length than the value
+ super().test_setitem_mask_broadcast(data, setter)
+
+ @skip_nested
+ def test_setitem_scalar_key_sequence_raise(self, data):
+ # Failed: DID NOT RAISE
+ super().test_setitem_scalar_key_sequence_raise(data)
+
+ # TODO: there is some issue with NumpyExtensionArray, therefore,
+ # skip the setitem test for now, and fix it later (GH 31446)
+
+ @skip_nested
+ @pytest.mark.parametrize(
+ "mask",
+ [
+ np.array([True, True, True, False, False]),
+ pd.array([True, True, True, False, False], dtype="boolean"),
+ ],
+ ids=["numpy-array", "boolean-array"],
+ )
+ def test_setitem_mask(self, data, mask, box_in_series):
+ super().test_setitem_mask(data, mask, box_in_series)
+
+ @skip_nested
+ @pytest.mark.parametrize(
+ "idx",
+ [[0, 1, 2], pd.array([0, 1, 2], dtype="Int64"), np.array([0, 1, 2])],
+ ids=["list", "integer-array", "numpy-array"],
+ )
+ def test_setitem_integer_array(self, data, idx, box_in_series):
+ super().test_setitem_integer_array(data, idx, box_in_series)
+
+ @skip_nested
+ def test_setitem_slice(self, data, box_in_series):
+ super().test_setitem_slice(data, box_in_series)
+
+ @skip_nested
+ def test_setitem_loc_iloc_slice(self, data):
+ super().test_setitem_loc_iloc_slice(data)
+
+ def test_setitem_with_expansion_dataframe_column(self, data, full_indexer):
+ # https://github.com/pandas-dev/pandas/issues/32395
+ df = expected = pd.DataFrame({"data": pd.Series(data)})
+ result = pd.DataFrame(index=df.index)
+
+ # because result has object dtype, the attempt to do setting inplace
+ # is successful, and object dtype is retained
+ key = full_indexer(df)
+ result.loc[key, "data"] = df["data"]
+
+ # base class method has expected = df; NumpyExtensionArray behaves oddly because
+ # we patch _typ for these tests.
+ if data.dtype.numpy_dtype != object:
+ if not isinstance(key, slice) or key != slice(None):
+ expected = pd.DataFrame({"data": data.to_numpy()})
+ tm.assert_frame_equal(result, expected, check_column_type=False)
+
+ @pytest.mark.xfail(reason="NumpyEADtype is unpacked")
+ def test_index_from_listlike_with_dtype(self, data):
+ super().test_index_from_listlike_with_dtype(data)
+
+ @skip_nested
+ @pytest.mark.parametrize("engine", ["c", "python"])
+ def test_EA_types(self, engine, data, request):
+ super().test_EA_types(engine, data, request)
+
+ def test_loc_setitem_with_expansion_preserves_ea_index_dtype(self, data, request):
+ if isinstance(data[-1], tuple):
+ mark = pytest.mark.xfail(reason="Unpacks tuple")
+ request.applymarker(mark)
+ super().test_loc_setitem_with_expansion_preserves_ea_index_dtype(data)
+
+
+class Test2DCompat(base.NDArrayBacked2DTests):
+ pass
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_period.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_period.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3be4e2b4420a569b2b5249c60a570897c847474
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_period.py
@@ -0,0 +1,116 @@
+"""
+This file contains a minimal set of tests for compliance with the extension
+array interface test suite, and should contain no other tests.
+The test suite for the full functionality of the array is located in
+`pandas/tests/arrays/`.
+
+The tests in this file are inherited from the BaseExtensionTests, and only
+minimal tweaks should be applied to get the tests passing (by overwriting a
+parent method).
+
+Additional tests should either be added to one of the BaseExtensionTests
+classes (if they are relevant for the extension interface for all dtypes), or
+be added to the array-specific tests in `pandas/tests/arrays/`.
+
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+import pytest
+
+from pandas._libs import (
+ Period,
+ iNaT,
+)
+
+from pandas.core.dtypes.dtypes import PeriodDtype
+
+import pandas._testing as tm
+from pandas.core.arrays import PeriodArray
+from pandas.tests.extension import base
+
+if TYPE_CHECKING:
+ import pandas as pd
+
+
+@pytest.fixture(params=["D", "2D"])
+def dtype(request):
+ return PeriodDtype(freq=request.param)
+
+
+@pytest.fixture
+def data(dtype):
+ return PeriodArray(np.arange(1970, 1980), dtype=dtype)
+
+
+@pytest.fixture
+def data_for_sorting(dtype):
+ return PeriodArray([2018, 2019, 2017], dtype=dtype)
+
+
+@pytest.fixture
+def data_missing(dtype):
+ return PeriodArray([iNaT, 2017], dtype=dtype)
+
+
+@pytest.fixture
+def data_missing_for_sorting(dtype):
+ return PeriodArray([2018, iNaT, 2017], dtype=dtype)
+
+
+@pytest.fixture
+def data_for_grouping(dtype):
+ B = 2018
+ NA = iNaT
+ A = 2017
+ C = 2019
+ return PeriodArray([B, B, NA, NA, A, A, B, C], dtype=dtype)
+
+
+class TestPeriodArray(base.ExtensionTests):
+ def _get_expected_exception(self, op_name, obj, other):
+ if op_name in ("__sub__", "__rsub__"):
+ return None
+ return super()._get_expected_exception(op_name, obj, other)
+
+ def _supports_accumulation(self, ser, op_name: str) -> bool:
+ return op_name in ["cummin", "cummax"]
+
+ def _supports_reduction(self, obj, op_name: str) -> bool:
+ return op_name in ["min", "max", "median"]
+
+ def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool):
+ if op_name == "median":
+ res_op = getattr(ser, op_name)
+
+ alt = ser.astype("int64")
+
+ exp_op = getattr(alt, op_name)
+ result = res_op(skipna=skipna)
+ expected = exp_op(skipna=skipna)
+ # error: Item "dtype[Any]" of "dtype[Any] | ExtensionDtype" has no
+ # attribute "freq"
+ freq = ser.dtype.freq # type: ignore[union-attr]
+ expected = Period._from_ordinal(int(expected), freq=freq)
+ tm.assert_almost_equal(result, expected)
+
+ else:
+ return super().check_reduce(ser, op_name, skipna)
+
+ @pytest.mark.parametrize("periods", [1, -2])
+ # NOTE: RuntimeWarning on Windows(non-ARM) platforms (in CI)
+ @pytest.mark.filterwarnings("ignore::RuntimeWarning")
+ def test_diff(self, request, data, periods):
+ super().test_diff(data, periods)
+
+ @pytest.mark.parametrize("na_action", [None, "ignore"])
+ def test_map(self, data, na_action):
+ result = data.map(lambda x: x, na_action=na_action)
+ tm.assert_extension_array_equal(result, data)
+
+
+class Test2DCompat(base.NDArrayBacked2DTests):
+ pass
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_sparse.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_sparse.py
new file mode 100644
index 0000000000000000000000000000000000000000..59c752cd24163b1a698a9296974e1f595a359fbf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_sparse.py
@@ -0,0 +1,517 @@
+"""
+This file contains a minimal set of tests for compliance with the extension
+array interface test suite, and should contain no other tests.
+The test suite for the full functionality of the array is located in
+`pandas/tests/arrays/`.
+
+The tests in this file are inherited from the BaseExtensionTests, and only
+minimal tweaks should be applied to get the tests passing (by overwriting a
+parent method).
+
+Additional tests should either be added to one of the BaseExtensionTests
+classes (if they are relevant for the extension interface for all dtypes), or
+be added to the array-specific tests in `pandas/tests/arrays/`.
+
+"""
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import SparseDtype
+import pandas._testing as tm
+from pandas.arrays import SparseArray
+from pandas.tests.extension import base
+
+
+def make_data(fill_value, n: int):
+ rng = np.random.default_rng(2)
+ if np.isnan(fill_value):
+ data = rng.uniform(size=n)
+ else:
+ data = rng.integers(1, 100, size=n, dtype=int)
+ if data[0] == data[1]:
+ data[0] += 1
+
+ data[2::3] = fill_value
+ return data
+
+
+@pytest.fixture
+def dtype():
+ return SparseDtype()
+
+
+@pytest.fixture(params=[0, np.nan])
+def data(request):
+ """Length-10 SparseArray for semantics test."""
+ res = SparseArray(make_data(request.param, 10), fill_value=request.param)
+ return res
+
+
+@pytest.fixture
+def data_for_twos():
+ return SparseArray(np.ones(10) * 2)
+
+
+@pytest.fixture(params=[0, np.nan])
+def data_missing(request):
+ """Length 2 array with [NA, Valid]"""
+ return SparseArray([np.nan, 1], fill_value=request.param)
+
+
+@pytest.fixture(params=[0, np.nan])
+def data_repeated(request):
+ """Return different versions of data for count times"""
+
+ def gen(count):
+ for _ in range(count):
+ yield SparseArray(make_data(request.param, 10), fill_value=request.param)
+
+ return gen
+
+
+@pytest.fixture(params=[0, np.nan])
+def data_for_sorting(request):
+ return SparseArray([2, 3, 1], fill_value=request.param)
+
+
+@pytest.fixture(params=[0, np.nan])
+def data_missing_for_sorting(request):
+ return SparseArray([2, np.nan, 1], fill_value=request.param)
+
+
+@pytest.fixture
+def na_cmp():
+ return lambda left, right: pd.isna(left) and pd.isna(right)
+
+
+@pytest.fixture(params=[0, np.nan])
+def data_for_grouping(request):
+ return SparseArray([1, 1, np.nan, np.nan, 2, 2, 1, 3], fill_value=request.param)
+
+
+@pytest.fixture(params=[0, np.nan])
+def data_for_compare(request):
+ return SparseArray([0, 0, np.nan, -2, -1, 4, 2, 3, 0, 0], fill_value=request.param)
+
+
+class TestSparseArray(base.ExtensionTests):
+ def _supports_reduction(self, obj, op_name: str) -> bool:
+ return True
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna, request):
+ if all_numeric_reductions in [
+ "prod",
+ "median",
+ "var",
+ "std",
+ "sem",
+ "skew",
+ "kurt",
+ ]:
+ mark = pytest.mark.xfail(
+ reason="This should be viable but is not implemented"
+ )
+ request.node.add_marker(mark)
+ elif (
+ all_numeric_reductions in ["sum", "max", "min", "mean"]
+ and data.dtype.kind == "f"
+ and not skipna
+ ):
+ mark = pytest.mark.xfail(reason="getting a non-nan float")
+ request.node.add_marker(mark)
+
+ super().test_reduce_series_numeric(data, all_numeric_reductions, skipna)
+
+ @pytest.mark.parametrize("skipna", [True, False])
+ def test_reduce_frame(self, data, all_numeric_reductions, skipna, request):
+ if all_numeric_reductions in [
+ "prod",
+ "median",
+ "var",
+ "std",
+ "sem",
+ "skew",
+ "kurt",
+ ]:
+ mark = pytest.mark.xfail(
+ reason="This should be viable but is not implemented"
+ )
+ request.node.add_marker(mark)
+ elif (
+ all_numeric_reductions in ["sum", "max", "min", "mean"]
+ and data.dtype.kind == "f"
+ and not skipna
+ ):
+ mark = pytest.mark.xfail(reason="ExtensionArray NA mask are different")
+ request.node.add_marker(mark)
+
+ super().test_reduce_frame(data, all_numeric_reductions, skipna)
+
+ def _check_unsupported(self, data):
+ if data.dtype == SparseDtype(int, 0):
+ pytest.skip("Can't store nan in int array.")
+
+ def test_concat_mixed_dtypes(self, data):
+ # https://github.com/pandas-dev/pandas/issues/20762
+ # This should be the same, aside from concat([sparse, float])
+ df1 = pd.DataFrame({"A": data[:3]})
+ df2 = pd.DataFrame({"A": [1, 2, 3]})
+ df3 = pd.DataFrame({"A": ["a", "b", "c"]}).astype("category")
+ dfs = [df1, df2, df3]
+
+ # dataframes
+ result = pd.concat(dfs)
+ expected = pd.concat(
+ [x.apply(lambda s: np.asarray(s).astype(object)) for x in dfs]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.filterwarnings(
+ "ignore:The previous implementation of stack is deprecated"
+ )
+ @pytest.mark.parametrize(
+ "columns",
+ [
+ ["A", "B"],
+ pd.MultiIndex.from_tuples(
+ [("A", "a"), ("A", "b")], names=["outer", "inner"]
+ ),
+ ],
+ )
+ @pytest.mark.parametrize("future_stack", [True, False])
+ def test_stack(self, data, columns, future_stack):
+ super().test_stack(data, columns, future_stack)
+
+ def test_concat_columns(self, data, na_value):
+ self._check_unsupported(data)
+ super().test_concat_columns(data, na_value)
+
+ def test_concat_extension_arrays_copy_false(self, data, na_value):
+ self._check_unsupported(data)
+ super().test_concat_extension_arrays_copy_false(data, na_value)
+
+ def test_align(self, data, na_value):
+ self._check_unsupported(data)
+ super().test_align(data, na_value)
+
+ def test_align_frame(self, data, na_value):
+ self._check_unsupported(data)
+ super().test_align_frame(data, na_value)
+
+ def test_align_series_frame(self, data, na_value):
+ self._check_unsupported(data)
+ super().test_align_series_frame(data, na_value)
+
+ def test_merge(self, data, na_value):
+ self._check_unsupported(data)
+ super().test_merge(data, na_value)
+
+ def test_get(self, data):
+ ser = pd.Series(data, index=[2 * i for i in range(len(data))])
+ if np.isnan(ser.values.fill_value):
+ assert np.isnan(ser.get(4)) and np.isnan(ser.iloc[2])
+ else:
+ assert ser.get(4) == ser.iloc[2]
+ assert ser.get(2) == ser.iloc[1]
+
+ def test_array_item_with_index(self, data, request):
+ # TODO https://github.com/pandas-dev/pandas/pull/64183
+ request.node.add_marker(pytest.mark.xfail(reason="SparseArray getitem buggy"))
+ super().test_array_item_with_index(data)
+
+ def test_reindex(self, data, na_value):
+ self._check_unsupported(data)
+ super().test_reindex(data, na_value)
+
+ def test_isna(self, data_missing):
+ sarr = SparseArray(data_missing)
+ expected_dtype = SparseDtype(bool, pd.isna(data_missing.dtype.fill_value))
+ expected = SparseArray([True, False], dtype=expected_dtype)
+ result = sarr.isna()
+ tm.assert_sp_array_equal(result, expected)
+
+ # test isna for arr without na
+ sarr = sarr.fillna(0)
+ expected_dtype = SparseDtype(bool, pd.isna(data_missing.dtype.fill_value))
+ expected = SparseArray([False, False], fill_value=False, dtype=expected_dtype)
+ tm.assert_equal(sarr.isna(), expected)
+
+ def test_fillna_no_op_returns_copy(self, data, request):
+ super().test_fillna_no_op_returns_copy(data)
+
+ def test_fillna_readonly(self, data_missing):
+ # copy keyword is ignored by SparseArray.fillna
+ # -> copy=True vs False doesn't make a difference
+ data = data_missing.copy()
+ data._readonly = True
+
+ result = data.fillna(data_missing[1])
+ assert result[0] == data_missing[1]
+ tm.assert_extension_array_equal(data, data_missing)
+
+ # fillna(copy=False) is ignored -> so same result as above
+ result = data.fillna(data_missing[1], copy=False)
+ assert result[0] == data_missing[1]
+ tm.assert_extension_array_equal(data, data_missing)
+
+ @pytest.mark.xfail(reason="Unsupported")
+ def test_fillna_series(self, data_missing):
+ # this one looks doable.
+ # TODO: this fails bc we do not pass through data_missing. If we did,
+ # the 0-fill case would xpass
+ super().test_fillna_series()
+
+ def test_fillna_frame(self, data_missing):
+ # Have to override to specify that fill_value will change.
+ fill_value = data_missing[1]
+
+ result = pd.DataFrame({"A": data_missing, "B": [1, 2]}).fillna(fill_value)
+
+ if pd.isna(data_missing.fill_value):
+ dtype = SparseDtype(data_missing.dtype, fill_value)
+ else:
+ dtype = data_missing.dtype
+
+ expected = pd.DataFrame(
+ {
+ "A": data_missing._from_sequence([fill_value, fill_value], dtype=dtype),
+ "B": [1, 2],
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_fillna_limit_frame(self, data_missing):
+ # GH#58001
+ with pytest.raises(ValueError, match="limit must be None"):
+ super().test_fillna_limit_frame(data_missing)
+
+ def test_fillna_limit_series(self, data_missing):
+ # GH#58001
+ with pytest.raises(ValueError, match="limit must be None"):
+ super().test_fillna_limit_frame(data_missing)
+
+ _combine_le_expected_dtype = "Sparse[bool]"
+
+ def test_fillna_copy_frame(self, data_missing):
+ arr = data_missing.take([1, 1])
+ df = pd.DataFrame({"A": arr}, copy=False)
+
+ filled_val = df.iloc[0, 0]
+ result = df.fillna(filled_val)
+
+ if hasattr(df._mgr, "blocks"):
+ assert df.values.base is result.values.base
+ assert df.A._values.to_dense() is arr.to_dense()
+
+ def test_fillna_copy_series(self, data_missing):
+ arr = data_missing.take([1, 1])
+ ser = pd.Series(arr, copy=False)
+
+ filled_val = ser[0]
+ result = ser.fillna(filled_val)
+
+ assert ser._values is result._values
+ assert ser._values.to_dense() is arr.to_dense()
+
+ @pytest.mark.xfail(reason="Not Applicable")
+ def test_fillna_length_mismatch(self, data_missing):
+ super().test_fillna_length_mismatch(data_missing)
+
+ def test_where_series(self, data, na_value):
+ assert data[0] != data[1]
+ cls = type(data)
+ a, b = data[:2]
+
+ ser = pd.Series(cls._from_sequence([a, a, b, b], dtype=data.dtype))
+
+ cond = np.array([True, True, False, False])
+ result = ser.where(cond)
+
+ new_dtype = SparseDtype("float", 0.0)
+ expected = pd.Series(
+ cls._from_sequence([a, a, na_value, na_value], dtype=new_dtype)
+ )
+ tm.assert_series_equal(result, expected)
+
+ other = cls._from_sequence([a, b, a, b], dtype=data.dtype)
+ cond = np.array([True, False, True, True])
+ result = ser.where(cond, other)
+ expected = pd.Series(cls._from_sequence([a, b, b, b], dtype=data.dtype))
+ tm.assert_series_equal(result, expected)
+
+ def test_searchsorted(self, performance_warning, data_for_sorting, as_series):
+ with tm.assert_produces_warning(performance_warning, check_stacklevel=False):
+ super().test_searchsorted(data_for_sorting, as_series)
+
+ def test_shift_0_periods(self, data):
+ # GH#33856 shifting with periods=0 should return a copy, not same obj
+ result = data.shift(0)
+
+ data._sparse_values[0] = data._sparse_values[1]
+ assert result._sparse_values[0] != result._sparse_values[1]
+
+ @pytest.mark.parametrize("method", ["argmax", "argmin"])
+ def test_argmin_argmax_all_na(self, method, data, na_value):
+ # overriding because Sparse[int64, 0] cannot handle na_value
+ self._check_unsupported(data)
+ super().test_argmin_argmax_all_na(method, data, na_value)
+
+ @pytest.mark.fails_arm_wheels
+ @pytest.mark.parametrize("box", [pd.array, pd.Series, pd.DataFrame])
+ def test_equals(self, data, na_value, as_series, box):
+ self._check_unsupported(data)
+ super().test_equals(data, na_value, as_series, box)
+
+ @pytest.mark.fails_arm_wheels
+ def test_equals_same_data_different_object(self, data):
+ super().test_equals_same_data_different_object(data)
+
+ @pytest.mark.parametrize(
+ "func, na_action, expected",
+ [
+ (lambda x: x, None, SparseArray([1.0, np.nan])),
+ (lambda x: x, "ignore", SparseArray([1.0, np.nan])),
+ (str, None, SparseArray(["1.0", "nan"], fill_value="nan")),
+ (str, "ignore", SparseArray(["1.0", np.nan])),
+ ],
+ )
+ def test_map(self, func, na_action, expected):
+ # GH52096
+ data = SparseArray([1, np.nan])
+ result = data.map(func, na_action=na_action)
+ tm.assert_extension_array_equal(result, expected)
+
+ @pytest.mark.parametrize("na_action", [None, "ignore"])
+ def test_map_raises(self, data, na_action):
+ # GH52096
+ msg = "fill value in the sparse values not supported"
+ with pytest.raises(ValueError, match=msg):
+ data.map(lambda x: np.nan, na_action=na_action)
+
+ @pytest.mark.xfail(raises=TypeError, reason="no sparse StringDtype")
+ def test_astype_string(self, data, nullable_string_dtype):
+ # TODO: this fails bc we do not pass through nullable_string_dtype;
+ # If we did, the 0-cases would xpass
+ super().test_astype_string(data)
+
+ series_scalar_exc = None
+ frame_scalar_exc = None
+ divmod_exc = None
+ series_array_exc = None
+
+ def _skip_if_different_combine(self, data):
+ if data.fill_value == 0:
+ # arith ops call on dtype.fill_value so that the sparsity
+ # is maintained. Combine can't be called on a dtype in
+ # general, so we can't make the expected. This is tested elsewhere
+ pytest.skip("Incorrected expected from Series.combine and tested elsewhere")
+
+ def test_arith_series_with_scalar(self, data, all_arithmetic_operators):
+ self._skip_if_different_combine(data)
+ super().test_arith_series_with_scalar(data, all_arithmetic_operators)
+
+ def test_arith_series_with_array(self, data, all_arithmetic_operators):
+ self._skip_if_different_combine(data)
+ super().test_arith_series_with_array(data, all_arithmetic_operators)
+
+ def test_arith_frame_with_scalar(self, data, all_arithmetic_operators, request):
+ if data.dtype.fill_value != 0:
+ pass
+ elif all_arithmetic_operators.strip("_") not in [
+ "mul",
+ "rmul",
+ "floordiv",
+ "rfloordiv",
+ "truediv",
+ "rtruediv",
+ "pow",
+ "mod",
+ "rmod",
+ ]:
+ mark = pytest.mark.xfail(reason="result dtype.fill_value mismatch")
+ request.applymarker(mark)
+ super().test_arith_frame_with_scalar(data, all_arithmetic_operators)
+
+ def _compare_other(
+ self, ser: pd.Series, data_for_compare: SparseArray, comparison_op, other
+ ):
+ op = comparison_op
+
+ result = op(data_for_compare, other)
+ if isinstance(other, pd.Series):
+ assert isinstance(result, pd.Series)
+ assert isinstance(result.dtype, SparseDtype)
+ else:
+ assert isinstance(result, SparseArray)
+ assert result.dtype.subtype == np.bool_
+
+ if isinstance(other, pd.Series):
+ fill_value = op(data_for_compare.fill_value, other._values.fill_value)
+ expected = SparseArray(
+ op(data_for_compare.to_dense(), np.asarray(other)),
+ fill_value=fill_value,
+ dtype=np.bool_,
+ )
+
+ else:
+ fill_value = np.all(
+ op(np.asarray(data_for_compare.fill_value), np.asarray(other))
+ )
+
+ expected = SparseArray(
+ op(data_for_compare.to_dense(), np.asarray(other)),
+ fill_value=fill_value,
+ dtype=np.bool_,
+ )
+ if isinstance(other, pd.Series):
+ # error: Incompatible types in assignment
+ expected = pd.Series(expected) # type: ignore[assignment]
+ tm.assert_equal(result, expected)
+
+ def test_scalar(self, data_for_compare: SparseArray, comparison_op):
+ ser = pd.Series(data_for_compare)
+ self._compare_other(ser, data_for_compare, comparison_op, 0)
+ self._compare_other(ser, data_for_compare, comparison_op, 1)
+ self._compare_other(ser, data_for_compare, comparison_op, -1)
+ self._compare_other(ser, data_for_compare, comparison_op, np.nan)
+
+ def test_array(self, data_for_compare: SparseArray, comparison_op, request):
+ if data_for_compare.dtype.fill_value == 0 and comparison_op.__name__ in [
+ "eq",
+ "ge",
+ "le",
+ ]:
+ mark = pytest.mark.xfail(reason="Wrong fill_value")
+ request.applymarker(mark)
+
+ arr = np.linspace(-4, 5, 10)
+ ser = pd.Series(data_for_compare)
+ self._compare_other(ser, data_for_compare, comparison_op, arr)
+
+ def test_sparse_array(self, data_for_compare: SparseArray, comparison_op, request):
+ if data_for_compare.dtype.fill_value == 0 and comparison_op.__name__ != "gt":
+ mark = pytest.mark.xfail(reason="Wrong fill_value")
+ request.applymarker(mark)
+
+ ser = pd.Series(data_for_compare)
+ arr = data_for_compare + 1
+ self._compare_other(ser, data_for_compare, comparison_op, arr)
+ arr = data_for_compare * 2
+ self._compare_other(ser, data_for_compare, comparison_op, arr)
+
+ @pytest.mark.xfail(reason="Different repr")
+ def test_array_repr(self, data, size):
+ super().test_array_repr(data, size)
+
+ @pytest.mark.xfail(reason="result does not match expected")
+ @pytest.mark.parametrize("as_index", [True, False])
+ def test_groupby_extension_agg(self, as_index, data_for_grouping):
+ super().test_groupby_extension_agg(as_index, data_for_grouping)
+
+
+def test_array_type_with_arg(dtype):
+ assert dtype.construct_array_type() is SparseArray
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_string.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_string.py
new file mode 100644
index 0000000000000000000000000000000000000000..07c957beef6522267e393bbfc27e02fcfb487d3c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/test_string.py
@@ -0,0 +1,308 @@
+"""
+This file contains a minimal set of tests for compliance with the extension
+array interface test suite, and should contain no other tests.
+The test suite for the full functionality of the array is located in
+`pandas/tests/arrays/`.
+
+The tests in this file are inherited from the BaseExtensionTests, and only
+minimal tweaks should be applied to get the tests passing (by overwriting a
+parent method).
+
+Additional tests should either be added to one of the BaseExtensionTests
+classes (if they are relevant for the extension interface for all dtypes), or
+be added to the array-specific tests in `pandas/tests/arrays/`.
+
+"""
+
+from __future__ import annotations
+
+import string
+from typing import cast
+
+import numpy as np
+import pytest
+
+from pandas.compat import HAS_PYARROW
+
+from pandas.core.dtypes.base import StorageExtensionDtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.types import is_string_dtype
+from pandas.core.arrays import ArrowStringArray
+from pandas.core.arrays.string_ import StringDtype
+from pandas.tests.arithmetic.test_string import string_dtype_highest_priority
+from pandas.tests.extension import base
+
+
+def maybe_split_array(arr, chunked):
+ if not chunked:
+ return arr
+ elif arr.dtype.storage != "pyarrow":
+ return arr
+
+ pa = pytest.importorskip("pyarrow")
+
+ arrow_array = arr._pa_array
+ split = len(arrow_array) // 2
+ arrow_array = pa.chunked_array(
+ [*arrow_array[:split].chunks, *arrow_array[split:].chunks]
+ )
+ assert arrow_array.num_chunks == 2
+ return arr._from_pyarrow_array(arrow_array)
+
+
+@pytest.fixture(params=[True, False])
+def chunked(request):
+ return request.param
+
+
+@pytest.fixture
+def dtype(string_dtype_arguments):
+ storage, na_value = string_dtype_arguments
+ return StringDtype(storage=storage, na_value=na_value)
+
+
+@pytest.fixture
+def data(dtype, chunked):
+ strings = np.random.default_rng(2).choice(list(string.ascii_letters), size=10)
+ while strings[0] == strings[1]:
+ strings = np.random.default_rng(2).choice(list(string.ascii_letters), size=10)
+
+ arr = dtype.construct_array_type()._from_sequence(strings, dtype=dtype)
+ return maybe_split_array(arr, chunked)
+
+
+@pytest.fixture
+def data_missing(dtype, chunked):
+ """Length 2 array with [NA, Valid]"""
+ arr = dtype.construct_array_type()._from_sequence([pd.NA, "A"], dtype=dtype)
+ return maybe_split_array(arr, chunked)
+
+
+@pytest.fixture
+def data_for_sorting(dtype, chunked):
+ arr = dtype.construct_array_type()._from_sequence(["B", "C", "A"], dtype=dtype)
+ return maybe_split_array(arr, chunked)
+
+
+@pytest.fixture
+def data_missing_for_sorting(dtype, chunked):
+ arr = dtype.construct_array_type()._from_sequence(["B", pd.NA, "A"], dtype=dtype)
+ return maybe_split_array(arr, chunked)
+
+
+@pytest.fixture
+def data_for_grouping(dtype, chunked):
+ arr = dtype.construct_array_type()._from_sequence(
+ ["B", "B", pd.NA, pd.NA, "A", "A", "B", "C"], dtype=dtype
+ )
+ return maybe_split_array(arr, chunked)
+
+
+class TestStringArray(base.ExtensionTests):
+ def test_combine_le(self, data_repeated):
+ dtype = next(iter(data_repeated(2))).dtype
+ if dtype.storage == "pyarrow" and dtype.na_value is pd.NA:
+ self._combine_le_expected_dtype = "bool[pyarrow]"
+ else:
+ self._combine_le_expected_dtype = "bool"
+ return super().test_combine_le(data_repeated)
+
+ def test_eq_with_str(self, dtype):
+ super().test_eq_with_str(dtype)
+
+ if dtype.na_value is pd.NA:
+ # only the NA-variant supports parametrized string alias
+ assert dtype == f"string[{dtype.storage}]"
+ elif dtype.storage == "pyarrow":
+ assert dtype == "str"
+
+ def test_is_not_string_type(self, dtype):
+ # Different from BaseDtypeTests.test_is_not_string_type
+ # because StringDtype is a string type
+ assert is_string_dtype(dtype)
+
+ def test_is_dtype_from_name(self, dtype, using_infer_string):
+ if dtype.na_value is np.nan and not using_infer_string:
+ result = type(dtype).is_dtype(dtype.name)
+ assert result is False
+ else:
+ super().test_is_dtype_from_name(dtype)
+
+ def test_construct_from_string_own_name(self, dtype, using_infer_string):
+ if dtype.na_value is np.nan and not using_infer_string:
+ with pytest.raises(TypeError, match="Cannot construct a 'StringDtype'"):
+ dtype.construct_from_string(dtype.name)
+ else:
+ super().test_construct_from_string_own_name(dtype)
+
+ def test_view(self, data):
+ if data.dtype.storage == "pyarrow":
+ pytest.skip(reason="2D support not implemented for ArrowStringArray")
+ super().test_view(data)
+
+ def test_from_dtype(self, data):
+ # base test uses string representation of dtype
+ pass
+
+ def test_transpose(self, data):
+ if data.dtype.storage == "pyarrow":
+ pytest.skip(reason="2D support not implemented for ArrowStringArray")
+ super().test_transpose(data)
+
+ def test_setitem_preserves_views(self, data):
+ if data.dtype.storage == "pyarrow":
+ pytest.skip(reason="2D support not implemented for ArrowStringArray")
+ super().test_setitem_preserves_views(data)
+
+ def test_dropna_array(self, data_missing):
+ result = data_missing.dropna()
+ expected = data_missing[[1]]
+ tm.assert_extension_array_equal(result, expected)
+
+ def test_fillna_no_op_returns_copy(self, data):
+ data = data[~data.isna()]
+
+ valid = data[0]
+ result = data.fillna(valid)
+ assert result is not data
+ tm.assert_extension_array_equal(result, data)
+
+ def test_fillna_readonly(self, data_missing):
+ data = data_missing.copy()
+ data._readonly = True
+
+ # by default fillna(copy=True), then this works fine
+ result = data.fillna(data_missing[1])
+ assert result[0] == data_missing[1]
+ tm.assert_extension_array_equal(data, data_missing)
+
+ # fillna(copy=False) is generally not honored by Arrow-backed array,
+ # but always returns new data -> same result as above
+ if data.dtype.storage == "pyarrow":
+ result = data.fillna(data_missing[1])
+ assert result[0] == data_missing[1]
+ else:
+ with pytest.raises(ValueError, match="Cannot modify read-only array"):
+ data.fillna(data_missing[1], copy=False)
+ tm.assert_extension_array_equal(data, data_missing)
+
+ def _get_expected_exception(
+ self, op_name: str, obj, other
+ ) -> type[Exception] | tuple[type[Exception], ...] | None:
+ if op_name in [
+ "__mod__",
+ "__rmod__",
+ "__divmod__",
+ "__rdivmod__",
+ "__pow__",
+ "__rpow__",
+ ]:
+ return TypeError
+ elif op_name in ["__mul__", "__rmul__"]:
+ # Can only multiply strings by integers
+ return TypeError
+ elif op_name in [
+ "__truediv__",
+ "__rtruediv__",
+ "__floordiv__",
+ "__rfloordiv__",
+ "__sub__",
+ "__rsub__",
+ ]:
+ return TypeError
+
+ return None
+
+ def _supports_reduction(self, ser: pd.Series, op_name: str) -> bool:
+ return op_name in ["min", "max", "sum"] or (
+ ser.dtype.na_value is np.nan # type: ignore[union-attr]
+ and op_name in ("any", "all")
+ )
+
+ def _supports_accumulation(self, ser: pd.Series, op_name: str) -> bool:
+ assert isinstance(ser.dtype, StorageExtensionDtype)
+ return op_name in ["cummin", "cummax", "cumsum"]
+
+ def _cast_pointwise_result(self, op_name: str, obj, other, pointwise_result):
+ dtype = cast(StringDtype, tm.get_dtype(obj))
+ if op_name in ["__add__", "__radd__"]:
+ cast_to = dtype
+ dtype_other = tm.get_dtype(other) if not isinstance(other, str) else None
+ if isinstance(dtype_other, StringDtype):
+ cast_to = string_dtype_highest_priority(dtype, dtype_other)
+ elif dtype.na_value is np.nan:
+ cast_to = np.bool_ # type: ignore[assignment]
+ elif dtype.storage == "pyarrow":
+ cast_to = "bool[pyarrow]" # type: ignore[assignment]
+ else:
+ cast_to = "boolean" # type: ignore[assignment]
+ return pointwise_result.astype(cast_to)
+
+ def test_compare_scalar(self, data, comparison_op):
+ ser = pd.Series(data)
+ self._compare_other(ser, data, comparison_op, "abc")
+
+ def test_groupby_extension_apply(self, data_for_grouping, groupby_apply_op):
+ super().test_groupby_extension_apply(data_for_grouping, groupby_apply_op)
+
+ def test_combine_add(self, data_repeated, using_infer_string, request):
+ dtype = next(data_repeated(1)).dtype
+ if not using_infer_string and dtype.storage == "python":
+ mark = pytest.mark.xfail(
+ reason="The pointwise operation result will be inferred to "
+ "string[nan, pyarrow], which does not match the input dtype"
+ )
+ request.applymarker(mark)
+ super().test_combine_add(data_repeated)
+
+ def test_arith_series_with_array(
+ self, data, all_arithmetic_operators, using_infer_string, request
+ ):
+ dtype = data.dtype
+ if (
+ using_infer_string
+ and all_arithmetic_operators == "__radd__"
+ and dtype.na_value is pd.NA
+ and (HAS_PYARROW or dtype.storage == "pyarrow")
+ ):
+ # TODO(infer_string)
+ mark = pytest.mark.xfail(
+ reason="The pointwise operation result will be inferred to "
+ "string[nan, pyarrow], which does not match the input dtype"
+ )
+ request.applymarker(mark)
+ super().test_arith_series_with_array(data, all_arithmetic_operators)
+
+ def test_loc_setitem_with_expansion_preserves_ea_index_dtype(
+ self, data, request, using_infer_string
+ ):
+ if not using_infer_string and data.dtype.storage == "python":
+ mark = pytest.mark.xfail(reason="Casts to object")
+ request.applymarker(mark)
+ super().test_loc_setitem_with_expansion_preserves_ea_index_dtype(data)
+
+
+class Test2DCompat(base.Dim2CompatTests):
+ @pytest.fixture(autouse=True)
+ def arrow_not_supported(self, data):
+ if isinstance(data, ArrowStringArray):
+ pytest.skip(reason="2D support not implemented for ArrowStringArray")
+
+
+def test_searchsorted_with_na_raises(data_for_sorting, as_series):
+ # GH50447
+ b, c, a = data_for_sorting
+ arr = data_for_sorting.take([2, 0, 1]) # to get [a, b, c]
+ arr[-1] = pd.NA
+
+ if as_series:
+ arr = pd.Series(arr)
+
+ msg = (
+ "searchsorted requires array to be sorted, "
+ "which is impossible with NAs present."
+ )
+ with pytest.raises(ValueError, match=msg):
+ arr.searchsorted(b)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/uuid/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/uuid/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/uuid/test_uuid.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/uuid/test_uuid.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ff75df6acea4bd9a5a10104ced1492c723097e0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/extension/uuid/test_uuid.py
@@ -0,0 +1,93 @@
+from __future__ import annotations
+
+from typing import (
+ TYPE_CHECKING,
+ ClassVar,
+ Self,
+)
+from uuid import UUID
+
+import numpy as np
+
+from pandas.core.dtypes.dtypes import ExtensionDtype
+
+import pandas as pd
+from pandas.core.arrays.base import ExtensionArray
+
+if TYPE_CHECKING:
+ import builtins
+ from collections.abc import Iterable
+
+ from numpy.typing import NDArray
+
+ from pandas._typing import (
+ Dtype,
+ ScalarIndexer,
+ )
+
+
+# 16 void bytes: 128 bit, every pattern valid, no funky behavior like 0 stripping.
+_UuidNumpyDtype = np.dtype("V16")
+
+
+class UuidDtype(ExtensionDtype):
+ # ExtensionDtype essential API (3 class attrs and methods)
+
+ name: ClassVar[str] = "uuid"
+ type: ClassVar[builtins.type[UUID]] = UUID
+
+ @classmethod
+ def construct_array_type(cls) -> builtins.type[UuidExtensionArray]:
+ return UuidExtensionArray
+
+ # ExtensionDtype overrides
+ kind: ClassVar[str] = _UuidNumpyDtype.kind
+
+
+class UuidExtensionArray(ExtensionArray):
+ # Implementation details and convenience
+
+ _data: NDArray[np.void]
+
+ def __init__(self, values: Iterable[UUID], *, copy: bool = False) -> None:
+ self._data = np.array([x.bytes for x in values], dtype=_UuidNumpyDtype)
+
+ # Parts of ExtensionArray's essential API required for tests:
+
+ dtype: ClassVar[UuidDtype] = UuidDtype()
+
+ @classmethod
+ def _from_sequence(
+ cls,
+ scalars: Iterable[UUID],
+ *,
+ dtype: Dtype | None = None,
+ copy: bool = False,
+ ) -> Self:
+ if dtype is None:
+ dtype = UuidDtype()
+ return cls(scalars, copy=copy)
+
+ def __getitem__(self, index: ScalarIndexer) -> UUID: # type: ignore[override]
+ assert isinstance(index, int | np.integer)
+ return UUID(bytes=self._data[index].tobytes())
+
+ def __len__(self) -> int:
+ return len(self._data)
+
+
+def test_construct() -> None:
+ """Tests that we can construct UuidExtensionArray from a list of valid values."""
+ from uuid import uuid4
+
+ a = UuidExtensionArray([UUID(int=0), u := uuid4()])
+ assert a[0].int == 0
+ assert a[1] == u
+
+
+def test_series() -> None:
+ """Tests that Series accepts (unstructured) void ExtensionDtypes."""
+ from uuid import uuid4
+
+ s = pd.Series([u := uuid4()], dtype=UuidDtype(), name="s")
+ assert str(u) in str(s)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_duplicate_labels.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_duplicate_labels.py
new file mode 100644
index 0000000000000000000000000000000000000000..4de8c8df852f475d0bbea99ec1f0b8bbdbae117a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_duplicate_labels.py
@@ -0,0 +1,390 @@
+"""Tests dealing with the NDFrame.allows_duplicates."""
+
+import operator
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+not_implemented = pytest.mark.xfail(reason="Not implemented.")
+
+# ----------------------------------------------------------------------------
+# Preservation
+
+
+class TestPreserves:
+ @pytest.mark.parametrize(
+ "cls, data",
+ [
+ (pd.Series, np.array([])),
+ (pd.Series, [1, 2]),
+ (pd.DataFrame, {}),
+ (pd.DataFrame, {"A": [1, 2]}),
+ ],
+ )
+ def test_construction_ok(self, cls, data):
+ result = cls(data)
+ assert result.flags.allows_duplicate_labels is True
+
+ result = cls(data).set_flags(allows_duplicate_labels=False)
+ assert result.flags.allows_duplicate_labels is False
+
+ @pytest.mark.parametrize(
+ "func",
+ [
+ operator.itemgetter(["a"]),
+ operator.methodcaller("add", 1),
+ operator.methodcaller("rename", str.upper),
+ operator.methodcaller("rename", "name"),
+ operator.methodcaller("abs"),
+ np.abs,
+ ],
+ )
+ def test_preserved_series(self, func):
+ s = pd.Series([0, 1], index=["a", "b"]).set_flags(allows_duplicate_labels=False)
+ assert func(s).flags.allows_duplicate_labels is False
+
+ @pytest.mark.parametrize("index", [["a", "b", "c"], ["a", "b"]])
+ # TODO: frame
+ @not_implemented
+ def test_align(self, index):
+ other = pd.Series(0, index=index)
+ s = pd.Series([0, 1], index=["a", "b"]).set_flags(allows_duplicate_labels=False)
+ a, b = s.align(other)
+ assert a.flags.allows_duplicate_labels is False
+ assert b.flags.allows_duplicate_labels is False
+
+ def test_preserved_frame(self):
+ df = pd.DataFrame({"A": [1, 2], "B": [3, 4]}, index=["a", "b"]).set_flags(
+ allows_duplicate_labels=False
+ )
+ assert df.loc[["a"]].flags.allows_duplicate_labels is False
+ assert df.loc[:, ["A", "B"]].flags.allows_duplicate_labels is False
+
+ def test_to_frame(self):
+ ser = pd.Series(dtype=float).set_flags(allows_duplicate_labels=False)
+ assert ser.to_frame().flags.allows_duplicate_labels is False
+
+ @pytest.mark.parametrize("func", ["add", "sub"])
+ @pytest.mark.parametrize("frame", [False, True])
+ @pytest.mark.parametrize("other", [1, pd.Series([1, 2], name="A")])
+ def test_binops(self, func, other, frame):
+ df = pd.Series([1, 2], name="A", index=["a", "b"]).set_flags(
+ allows_duplicate_labels=False
+ )
+ if frame:
+ df = df.to_frame()
+ if isinstance(other, pd.Series) and frame:
+ other = other.to_frame()
+ func = operator.methodcaller(func, other)
+ assert df.flags.allows_duplicate_labels is False
+ assert func(df).flags.allows_duplicate_labels is False
+
+ def test_preserve_getitem(self):
+ df = pd.DataFrame({"A": [1, 2]}).set_flags(allows_duplicate_labels=False)
+ assert df[["A"]].flags.allows_duplicate_labels is False
+ assert df["A"].flags.allows_duplicate_labels is False
+ assert df.loc[0].flags.allows_duplicate_labels is False
+ assert df.loc[[0]].flags.allows_duplicate_labels is False
+ assert df.loc[0, ["A"]].flags.allows_duplicate_labels is False
+
+ @pytest.mark.parametrize(
+ "objs, kwargs",
+ [
+ # Series
+ (
+ [
+ pd.Series(1, index=["a", "b"]),
+ pd.Series(2, index=["c", "d"]),
+ ],
+ {},
+ ),
+ (
+ [
+ pd.Series(1, index=["a", "b"]),
+ pd.Series(2, index=["a", "b"]),
+ ],
+ {"ignore_index": True},
+ ),
+ (
+ [
+ pd.Series(1, index=["a", "b"]),
+ pd.Series(2, index=["a", "b"]),
+ ],
+ {"axis": 1},
+ ),
+ # Frame
+ (
+ [
+ pd.DataFrame({"A": [1, 2]}, index=["a", "b"]),
+ pd.DataFrame({"A": [1, 2]}, index=["c", "d"]),
+ ],
+ {},
+ ),
+ (
+ [
+ pd.DataFrame({"A": [1, 2]}, index=["a", "b"]),
+ pd.DataFrame({"A": [1, 2]}, index=["a", "b"]),
+ ],
+ {"ignore_index": True},
+ ),
+ (
+ [
+ pd.DataFrame({"A": [1, 2]}, index=["a", "b"]),
+ pd.DataFrame({"B": [1, 2]}, index=["a", "b"]),
+ ],
+ {"axis": 1},
+ ),
+ # Series / Frame
+ (
+ [
+ pd.DataFrame({"A": [1, 2]}, index=["a", "b"]),
+ pd.Series([1, 2], index=["a", "b"], name="B"),
+ ],
+ {"axis": 1},
+ ),
+ ],
+ )
+ def test_concat(self, objs, kwargs):
+ objs = [x.set_flags(allows_duplicate_labels=False) for x in objs]
+ result = pd.concat(objs, **kwargs)
+ assert result.flags.allows_duplicate_labels is False
+
+ @pytest.mark.parametrize(
+ "left, right, expected",
+ [
+ # false false false
+ pytest.param(
+ pd.DataFrame({"A": [0, 1]}, index=["a", "b"]).set_flags(
+ allows_duplicate_labels=False
+ ),
+ pd.DataFrame({"B": [0, 1]}, index=["a", "d"]).set_flags(
+ allows_duplicate_labels=False
+ ),
+ False,
+ ),
+ # false true false
+ pytest.param(
+ pd.DataFrame({"A": [0, 1]}, index=["a", "b"]).set_flags(
+ allows_duplicate_labels=False
+ ),
+ pd.DataFrame({"B": [0, 1]}, index=["a", "d"]),
+ False,
+ ),
+ # true true true
+ (
+ pd.DataFrame({"A": [0, 1]}, index=["a", "b"]),
+ pd.DataFrame({"B": [0, 1]}, index=["a", "d"]),
+ True,
+ ),
+ ],
+ )
+ def test_merge(self, left, right, expected):
+ result = pd.merge(left, right, left_index=True, right_index=True)
+ assert result.flags.allows_duplicate_labels is expected
+
+ @not_implemented
+ def test_groupby(self):
+ # XXX: This is under tested
+ # TODO:
+ # - apply
+ # - transform
+ # - Should passing a grouper that disallows duplicates propagate?
+ df = pd.DataFrame({"A": [1, 2, 3]}).set_flags(allows_duplicate_labels=False)
+ result = df.groupby([0, 0, 1]).agg("count")
+ assert result.flags.allows_duplicate_labels is False
+
+ @pytest.mark.parametrize("frame", [True, False])
+ @not_implemented
+ def test_window(self, frame):
+ df = pd.Series(
+ 1,
+ index=pd.date_range("2000", periods=12),
+ name="A",
+ allows_duplicate_labels=False,
+ )
+ if frame:
+ df = df.to_frame()
+ assert df.rolling(3).mean().flags.allows_duplicate_labels is False
+ assert df.ewm(3).mean().flags.allows_duplicate_labels is False
+ assert df.expanding(3).mean().flags.allows_duplicate_labels is False
+
+
+# ----------------------------------------------------------------------------
+# Raises
+
+
+class TestRaises:
+ @pytest.mark.parametrize(
+ "cls, axes",
+ [
+ (pd.Series, {"index": ["a", "a"], "dtype": float}),
+ (pd.DataFrame, {"index": ["a", "a"]}),
+ (pd.DataFrame, {"index": ["a", "a"], "columns": ["b", "b"]}),
+ (pd.DataFrame, {"columns": ["b", "b"]}),
+ ],
+ )
+ def test_set_flags_with_duplicates(self, cls, axes):
+ result = cls(**axes)
+ assert result.flags.allows_duplicate_labels is True
+
+ msg = "Index has duplicates."
+ with pytest.raises(pd.errors.DuplicateLabelError, match=msg):
+ cls(**axes).set_flags(allows_duplicate_labels=False)
+
+ @pytest.mark.parametrize(
+ "data",
+ [
+ pd.Series(index=[0, 0], dtype=float),
+ pd.DataFrame(index=[0, 0]),
+ pd.DataFrame(columns=[0, 0]),
+ ],
+ )
+ def test_setting_allows_duplicate_labels_raises(self, data):
+ msg = "Index has duplicates."
+ with pytest.raises(pd.errors.DuplicateLabelError, match=msg):
+ data.flags.allows_duplicate_labels = False
+
+ assert data.flags.allows_duplicate_labels is True
+
+ def test_series_raises(self):
+ a = pd.Series(0, index=["a", "b"])
+ b = pd.Series([0, 1], index=["a", "b"]).set_flags(allows_duplicate_labels=False)
+ msg = "Index has duplicates."
+ with pytest.raises(pd.errors.DuplicateLabelError, match=msg):
+ pd.concat([a, b])
+
+ @pytest.mark.parametrize(
+ "getter, target",
+ [
+ (operator.itemgetter(["A", "A"]), None),
+ # loc
+ (operator.itemgetter(["a", "a"]), "loc"),
+ pytest.param(operator.itemgetter(("a", ["A", "A"])), "loc"),
+ (operator.itemgetter((["a", "a"], "A")), "loc"),
+ # iloc
+ (operator.itemgetter([0, 0]), "iloc"),
+ pytest.param(operator.itemgetter((0, [0, 0])), "iloc"),
+ pytest.param(operator.itemgetter(([0, 0], 0)), "iloc"),
+ ],
+ )
+ def test_getitem_raises(self, getter, target):
+ df = pd.DataFrame({"A": [1, 2], "B": [3, 4]}, index=["a", "b"]).set_flags(
+ allows_duplicate_labels=False
+ )
+ if target:
+ # df, df.loc, or df.iloc
+ target = getattr(df, target)
+ else:
+ target = df
+
+ msg = "Index has duplicates."
+ with pytest.raises(pd.errors.DuplicateLabelError, match=msg):
+ getter(target)
+
+ def test_concat_raises(self):
+ objs = [
+ pd.Series(1, index=[0, 1], name="a"),
+ pd.Series(2, index=[0, 1], name="a"),
+ ]
+ objs = [x.set_flags(allows_duplicate_labels=False) for x in objs]
+ msg = "Index has duplicates."
+ with pytest.raises(pd.errors.DuplicateLabelError, match=msg):
+ pd.concat(objs, axis=1)
+
+ def test_merge_raises(self):
+ a = pd.DataFrame({"A": [0, 1, 2]}, index=["a", "b", "c"]).set_flags(
+ allows_duplicate_labels=False
+ )
+ b = pd.DataFrame({"B": [0, 1, 2]}, index=["a", "b", "b"])
+ msg = "Index has duplicates."
+ with pytest.raises(pd.errors.DuplicateLabelError, match=msg):
+ pd.merge(a, b, left_index=True, right_index=True)
+
+
+@pytest.mark.parametrize(
+ "idx",
+ [
+ pd.Index([1, 1]),
+ pd.Index(["a", "a"]),
+ pd.Index([1.1, 1.1]),
+ pd.PeriodIndex([pd.Period("2000", "D")] * 2),
+ pd.DatetimeIndex([pd.Timestamp("2000")] * 2),
+ pd.TimedeltaIndex([pd.Timedelta("1D")] * 2),
+ pd.CategoricalIndex(["a", "a"]),
+ pd.IntervalIndex([pd.Interval(0, 1)] * 2),
+ pd.MultiIndex.from_tuples([("a", 1), ("a", 1)]),
+ ],
+ ids=lambda x: type(x).__name__,
+)
+def test_raises_basic(idx):
+ msg = "Index has duplicates."
+ with pytest.raises(pd.errors.DuplicateLabelError, match=msg):
+ pd.Series(1, index=idx).set_flags(allows_duplicate_labels=False)
+
+ with pytest.raises(pd.errors.DuplicateLabelError, match=msg):
+ pd.DataFrame({"A": [1, 1]}, index=idx).set_flags(allows_duplicate_labels=False)
+
+ with pytest.raises(pd.errors.DuplicateLabelError, match=msg):
+ pd.DataFrame([[1, 2]], columns=idx).set_flags(allows_duplicate_labels=False)
+
+
+def test_format_duplicate_labels_message():
+ idx = pd.Index(["a", "b", "a", "b", "c"])
+ result = idx._format_duplicate_message()
+ expected = pd.DataFrame(
+ {"positions": [[0, 2], [1, 3]]}, index=pd.Index(["a", "b"], name="label")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_format_duplicate_labels_message_multi():
+ idx = pd.MultiIndex.from_product([["A"], ["a", "b", "a", "b", "c"]])
+ result = idx._format_duplicate_message()
+ expected = pd.DataFrame(
+ {"positions": [[0, 2], [1, 3]]},
+ index=pd.MultiIndex.from_product([["A"], ["a", "b"]]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dataframe_insert_raises():
+ df = pd.DataFrame({"A": [1, 2]}).set_flags(allows_duplicate_labels=False)
+ msg = "Cannot specify"
+ with pytest.raises(ValueError, match=msg):
+ df.insert(0, "A", [3, 4], allow_duplicates=True)
+
+
+@pytest.mark.parametrize(
+ "method, frame_only",
+ [
+ (operator.methodcaller("set_index", "A", inplace=True), True),
+ (operator.methodcaller("reset_index", inplace=True), True),
+ (operator.methodcaller("rename", lambda x: x, inplace=True), False),
+ ],
+)
+def test_inplace_raises(method, frame_only):
+ df = pd.DataFrame({"A": [0, 0], "B": [1, 2]}).set_flags(
+ allows_duplicate_labels=False
+ )
+ s = df["A"]
+ s.flags.allows_duplicate_labels = False
+ msg = "Cannot specify"
+
+ with pytest.raises(ValueError, match=msg):
+ method(df)
+ if not frame_only:
+ with pytest.raises(ValueError, match=msg):
+ method(s)
+
+
+def test_pickle(temp_file):
+ a = pd.Series([1, 2]).set_flags(allows_duplicate_labels=False)
+ b = tm.round_trip_pickle(a, temp_file)
+ tm.assert_series_equal(a, b)
+
+ a = pd.DataFrame({"A": []}).set_flags(allows_duplicate_labels=False)
+ b = tm.round_trip_pickle(a, temp_file)
+ tm.assert_frame_equal(a, b)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_finalize.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_finalize.py
new file mode 100644
index 0000000000000000000000000000000000000000..de972f2f2f9c7a4101d8e00a3db22e17fa464822
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_finalize.py
@@ -0,0 +1,792 @@
+"""
+An exhaustive list of pandas methods exercising NDFrame.__finalize__.
+"""
+
+from copy import deepcopy
+from datetime import time
+import operator
+import re
+
+import numpy as np
+import pytest
+
+from pandas._typing import MergeHow
+
+import pandas as pd
+
+# TODO:
+# * Binary methods (mul, div, etc.)
+# * Binary outputs (align, etc.)
+# * top-level methods (concat, merge, get_dummies, etc.)
+# * window
+# * cumulative reductions
+
+not_implemented_mark = pytest.mark.xfail(reason="not implemented")
+
+mi = pd.MultiIndex.from_product([["a", "b"], [0, 1]], names=["A", "B"])
+
+frame_data = ({"A": [1]},)
+frame_mi_data = ({"A": [1, 2, 3, 4]}, mi)
+
+
+# Tuple of
+# - Callable: Constructor (Series, DataFrame)
+# - Tuple: Constructor args
+# - Callable: pass the constructed value with attrs set to this.
+
+_all_methods = [
+ (pd.Series, ([0],), operator.methodcaller("take", [])),
+ (pd.Series, ([0],), operator.methodcaller("__getitem__", [True])),
+ (pd.Series, ([0],), operator.methodcaller("repeat", 2)),
+ (pd.Series, ([0],), operator.methodcaller("reset_index")),
+ (pd.Series, ([0],), operator.methodcaller("reset_index", drop=True)),
+ (pd.Series, ([0],), operator.methodcaller("to_frame")),
+ (pd.Series, ([0, 0],), operator.methodcaller("drop_duplicates")),
+ (pd.Series, ([0, 0],), operator.methodcaller("duplicated")),
+ (pd.Series, ([0, 0],), operator.methodcaller("round")),
+ (pd.Series, ([0, 0],), operator.methodcaller("rename", lambda x: x + 1)),
+ (pd.Series, ([0, 0],), operator.methodcaller("rename", "name")),
+ (pd.Series, ([0, 0],), operator.methodcaller("set_axis", ["a", "b"])),
+ (pd.Series, ([0, 0],), operator.methodcaller("reindex", [1, 0])),
+ (pd.Series, ([0, 0],), operator.methodcaller("drop", [0])),
+ (pd.Series, (pd.array([0, pd.NA]),), operator.methodcaller("fillna", 0)),
+ (pd.Series, ([0, 0],), operator.methodcaller("replace", {0: 1})),
+ (pd.Series, ([0, 0],), operator.methodcaller("shift")),
+ (pd.Series, ([0, 0],), operator.methodcaller("isin", [0, 1])),
+ (pd.Series, ([0, 0],), operator.methodcaller("between", 0, 2)),
+ (pd.Series, ([0, 0],), operator.methodcaller("isna")),
+ (pd.Series, ([0, 0],), operator.methodcaller("isnull")),
+ (pd.Series, ([0, 0],), operator.methodcaller("notna")),
+ (pd.Series, ([0, 0],), operator.methodcaller("notnull")),
+ (pd.Series, ([1],), operator.methodcaller("add", pd.Series([1]))),
+ # TODO: mul, div, etc.
+ (
+ pd.Series,
+ ([0], pd.period_range("2000", periods=1)),
+ operator.methodcaller("to_timestamp"),
+ ),
+ (
+ pd.Series,
+ ([0], pd.date_range("2000", periods=1)),
+ operator.methodcaller("to_period"),
+ ),
+ pytest.param(
+ (
+ pd.DataFrame,
+ frame_data,
+ operator.methodcaller("dot", pd.DataFrame(index=["A"])),
+ ),
+ marks=pytest.mark.xfail(reason="Implement binary finalize"),
+ ),
+ (pd.DataFrame, frame_data, operator.methodcaller("transpose")),
+ (pd.DataFrame, frame_data, operator.methodcaller("__getitem__", "A")),
+ (pd.DataFrame, frame_data, operator.methodcaller("__getitem__", ["A"])),
+ (pd.DataFrame, frame_data, operator.methodcaller("__getitem__", np.array([True]))),
+ (pd.DataFrame, ({("A", "a"): [1]},), operator.methodcaller("__getitem__", ["A"])),
+ (pd.DataFrame, frame_data, operator.methodcaller("query", "A == 1")),
+ (pd.DataFrame, frame_data, operator.methodcaller("eval", "A + 1", engine="python")),
+ (pd.DataFrame, frame_data, operator.methodcaller("select_dtypes", include="int")),
+ (pd.DataFrame, frame_data, operator.methodcaller("assign", b=1)),
+ (pd.DataFrame, frame_data, operator.methodcaller("set_axis", ["A"])),
+ (pd.DataFrame, frame_data, operator.methodcaller("reindex", [0, 1])),
+ (pd.DataFrame, frame_data, operator.methodcaller("drop", columns=["A"])),
+ (pd.DataFrame, frame_data, operator.methodcaller("drop", index=[0])),
+ (pd.DataFrame, frame_data, operator.methodcaller("rename", columns={"A": "a"})),
+ (pd.DataFrame, frame_data, operator.methodcaller("rename", index=lambda x: x)),
+ (pd.DataFrame, frame_data, operator.methodcaller("fillna", "A")),
+ (pd.DataFrame, frame_data, operator.methodcaller("set_index", "A")),
+ (pd.DataFrame, frame_data, operator.methodcaller("reset_index")),
+ (pd.DataFrame, frame_data, operator.methodcaller("isna")),
+ (pd.DataFrame, frame_data, operator.methodcaller("isnull")),
+ (pd.DataFrame, frame_data, operator.methodcaller("notna")),
+ (pd.DataFrame, frame_data, operator.methodcaller("notnull")),
+ (pd.DataFrame, frame_data, operator.methodcaller("dropna")),
+ (pd.DataFrame, frame_data, operator.methodcaller("drop_duplicates")),
+ (pd.DataFrame, frame_data, operator.methodcaller("duplicated")),
+ (pd.DataFrame, frame_data, operator.methodcaller("sort_values", by="A")),
+ (pd.DataFrame, frame_data, operator.methodcaller("sort_index")),
+ (pd.DataFrame, frame_data, operator.methodcaller("nlargest", 1, "A")),
+ (pd.DataFrame, frame_data, operator.methodcaller("nsmallest", 1, "A")),
+ (pd.DataFrame, frame_mi_data, operator.methodcaller("swaplevel")),
+ (
+ pd.DataFrame,
+ frame_data,
+ operator.methodcaller("add", pd.DataFrame(*frame_data)),
+ ),
+ # TODO: div, mul, etc.
+ (
+ pd.DataFrame,
+ frame_data,
+ operator.methodcaller("combine", pd.DataFrame(*frame_data), operator.add),
+ ),
+ (
+ pd.DataFrame,
+ frame_data,
+ operator.methodcaller("combine_first", pd.DataFrame(*frame_data)),
+ ),
+ pytest.param(
+ (
+ pd.DataFrame,
+ frame_data,
+ operator.methodcaller("update", pd.DataFrame(*frame_data)),
+ ),
+ marks=not_implemented_mark,
+ ),
+ (pd.DataFrame, frame_data, operator.methodcaller("pivot", columns="A")),
+ (
+ pd.DataFrame,
+ ({"A": [1], "B": [1]},),
+ operator.methodcaller("pivot_table", columns="A"),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [1], "B": [1]},),
+ operator.methodcaller("pivot_table", columns="A", aggfunc=["mean", "sum"]),
+ ),
+ (pd.DataFrame, frame_data, operator.methodcaller("stack")),
+ (pd.DataFrame, frame_data, operator.methodcaller("explode", "A")),
+ (pd.DataFrame, frame_mi_data, operator.methodcaller("unstack")),
+ (
+ pd.DataFrame,
+ ({"A": ["a", "b", "c"], "B": [1, 3, 5], "C": [2, 4, 6]},),
+ operator.methodcaller("melt", id_vars=["A"], value_vars=["B"]),
+ ),
+ (pd.DataFrame, frame_data, operator.methodcaller("map", lambda x: x)),
+ (pd.DataFrame, frame_data, operator.methodcaller("round", 2)),
+ (pd.DataFrame, frame_data, operator.methodcaller("corr")),
+ pytest.param(
+ (pd.DataFrame, frame_data, operator.methodcaller("cov")),
+ marks=[
+ pytest.mark.filterwarnings("ignore::RuntimeWarning"),
+ ],
+ ),
+ (
+ pd.DataFrame,
+ frame_data,
+ operator.methodcaller("corrwith", pd.DataFrame(*frame_data)),
+ ),
+ (pd.DataFrame, frame_data, operator.methodcaller("count")),
+ (pd.DataFrame, frame_data, operator.methodcaller("nunique")),
+ (pd.DataFrame, frame_data, operator.methodcaller("idxmin")),
+ (pd.DataFrame, frame_data, operator.methodcaller("idxmax")),
+ (pd.DataFrame, frame_data, operator.methodcaller("mode")),
+ (pd.Series, [0], operator.methodcaller("mode")),
+ (pd.DataFrame, frame_data, operator.methodcaller("median")),
+ (
+ pd.DataFrame,
+ frame_data,
+ operator.methodcaller("quantile", numeric_only=True),
+ ),
+ (
+ pd.DataFrame,
+ frame_data,
+ operator.methodcaller("quantile", q=[0.25, 0.75], numeric_only=True),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [pd.Timedelta(days=1), pd.Timedelta(days=2)]},),
+ operator.methodcaller("quantile", numeric_only=False),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [np.datetime64("2022-01-01"), np.datetime64("2022-01-02")]},),
+ operator.methodcaller("quantile", numeric_only=True),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [1]}, [pd.Period("2000", "D")]),
+ operator.methodcaller("to_timestamp"),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [1]}, [pd.Timestamp("2000")]),
+ operator.methodcaller("to_period", freq="D"),
+ ),
+ (pd.DataFrame, frame_mi_data, operator.methodcaller("isin", [1])),
+ (pd.DataFrame, frame_mi_data, operator.methodcaller("isin", pd.Series([1]))),
+ (
+ pd.DataFrame,
+ frame_mi_data,
+ operator.methodcaller("isin", pd.DataFrame({"A": [1]})),
+ ),
+ (pd.DataFrame, frame_mi_data, operator.methodcaller("droplevel", "A")),
+ (pd.DataFrame, frame_data, operator.methodcaller("pop", "A")),
+ # Squeeze on columns, otherwise we'll end up with a scalar
+ (pd.DataFrame, frame_data, operator.methodcaller("squeeze", axis="columns")),
+ (pd.Series, ([1, 2],), operator.methodcaller("squeeze")),
+ (pd.Series, ([1, 2],), operator.methodcaller("rename_axis", index="a")),
+ (pd.DataFrame, frame_data, operator.methodcaller("rename_axis", columns="a")),
+ # Unary ops
+ (pd.DataFrame, frame_data, operator.neg),
+ (pd.Series, [1], operator.neg),
+ (pd.DataFrame, frame_data, operator.pos),
+ (pd.Series, [1], operator.pos),
+ (pd.DataFrame, frame_data, operator.inv),
+ (pd.Series, [1], operator.inv),
+ (pd.DataFrame, frame_data, abs),
+ (pd.Series, [1], abs),
+ (pd.DataFrame, frame_data, round),
+ (pd.Series, [1], round),
+ (pd.DataFrame, frame_data, operator.methodcaller("take", [0, 0])),
+ (pd.DataFrame, frame_mi_data, operator.methodcaller("xs", "a")),
+ (pd.Series, (1, mi), operator.methodcaller("xs", "a")),
+ (pd.DataFrame, frame_data, operator.methodcaller("get", "A")),
+ (
+ pd.DataFrame,
+ frame_data,
+ operator.methodcaller("reindex_like", pd.DataFrame({"A": [1, 2, 3]})),
+ ),
+ (
+ pd.Series,
+ frame_data,
+ operator.methodcaller("reindex_like", pd.Series([0, 1, 2])),
+ ),
+ (pd.DataFrame, frame_data, operator.methodcaller("add_prefix", "_")),
+ (pd.DataFrame, frame_data, operator.methodcaller("add_suffix", "_")),
+ (pd.Series, (1, ["a", "b"]), operator.methodcaller("add_prefix", "_")),
+ (pd.Series, (1, ["a", "b"]), operator.methodcaller("add_suffix", "_")),
+ (pd.Series, ([3, 2],), operator.methodcaller("sort_values")),
+ (pd.Series, ([1] * 10,), operator.methodcaller("head")),
+ (pd.DataFrame, ({"A": [1] * 10},), operator.methodcaller("head")),
+ (pd.Series, ([1] * 10,), operator.methodcaller("tail")),
+ (pd.DataFrame, ({"A": [1] * 10},), operator.methodcaller("tail")),
+ (pd.Series, ([1, 2],), operator.methodcaller("sample", n=2, replace=True)),
+ (pd.DataFrame, (frame_data,), operator.methodcaller("sample", n=2, replace=True)),
+ (pd.Series, ([1, 2],), operator.methodcaller("astype", float)),
+ (pd.DataFrame, frame_data, operator.methodcaller("astype", float)),
+ (pd.Series, ([1, 2],), operator.methodcaller("copy")),
+ (pd.DataFrame, frame_data, operator.methodcaller("copy")),
+ (pd.Series, ([1, 2], None, object), operator.methodcaller("infer_objects")),
+ (
+ pd.DataFrame,
+ ({"A": np.array([1, 2], dtype=object)},),
+ operator.methodcaller("infer_objects"),
+ ),
+ (pd.Series, ([1, 2],), operator.methodcaller("convert_dtypes")),
+ (pd.DataFrame, frame_data, operator.methodcaller("convert_dtypes")),
+ (pd.Series, ([1, None, 3],), operator.methodcaller("interpolate")),
+ (pd.DataFrame, ({"A": [1, None, 3]},), operator.methodcaller("interpolate")),
+ (pd.Series, ([1, 2],), operator.methodcaller("clip", lower=1)),
+ (pd.DataFrame, frame_data, operator.methodcaller("clip", lower=1)),
+ (
+ pd.Series,
+ (1, pd.date_range("2000", periods=4)),
+ operator.methodcaller("asfreq", "h"),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
+ operator.methodcaller("asfreq", "h"),
+ ),
+ (
+ pd.Series,
+ (1, pd.date_range("2000", periods=4)),
+ operator.methodcaller("at_time", time(12)),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
+ operator.methodcaller("at_time", time(12)),
+ ),
+ (
+ pd.Series,
+ (1, pd.date_range("2000", periods=4)),
+ operator.methodcaller("between_time", "12:00", "13:00"),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
+ operator.methodcaller("between_time", "12:00", "13:00"),
+ ),
+ (pd.Series, ([1, 2],), operator.methodcaller("rank")),
+ (pd.DataFrame, frame_data, operator.methodcaller("rank")),
+ (pd.Series, ([1, 2],), operator.methodcaller("where", np.array([True, False]))),
+ (pd.DataFrame, frame_data, operator.methodcaller("where", np.array([[True]]))),
+ (pd.Series, ([1, 2],), operator.methodcaller("mask", np.array([True, False]))),
+ (pd.DataFrame, frame_data, operator.methodcaller("mask", np.array([[True]]))),
+ (pd.Series, ([1, 2],), operator.methodcaller("truncate", before=0)),
+ (pd.DataFrame, frame_data, operator.methodcaller("truncate", before=0)),
+ (
+ pd.Series,
+ (1, pd.date_range("2000", periods=4, tz="UTC")),
+ operator.methodcaller("tz_convert", "CET"),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4, tz="UTC")),
+ operator.methodcaller("tz_convert", "CET"),
+ ),
+ (
+ pd.Series,
+ (1, pd.date_range("2000", periods=4)),
+ operator.methodcaller("tz_localize", "CET"),
+ ),
+ (
+ pd.DataFrame,
+ ({"A": [1, 1, 1, 1]}, pd.date_range("2000", periods=4)),
+ operator.methodcaller("tz_localize", "CET"),
+ ),
+ (pd.Series, ([1, 2],), operator.methodcaller("describe")),
+ (pd.DataFrame, frame_data, operator.methodcaller("describe")),
+ (pd.Series, ([1, 2],), operator.methodcaller("pct_change")),
+ (pd.DataFrame, frame_data, operator.methodcaller("pct_change")),
+ (pd.Series, ([1],), operator.methodcaller("transform", lambda x: x - x.min())),
+ (
+ pd.DataFrame,
+ frame_mi_data,
+ operator.methodcaller("transform", lambda x: x - x.min()),
+ ),
+ (pd.Series, ([1],), operator.methodcaller("apply", lambda x: x)),
+ (pd.DataFrame, frame_mi_data, operator.methodcaller("apply", lambda x: x)),
+ # Cumulative reductions
+ (pd.Series, ([1],), operator.methodcaller("cumsum")),
+ (pd.DataFrame, frame_data, operator.methodcaller("cumsum")),
+ (pd.Series, ([1],), operator.methodcaller("cummin")),
+ (pd.DataFrame, frame_data, operator.methodcaller("cummin")),
+ (pd.Series, ([1],), operator.methodcaller("cummax")),
+ (pd.DataFrame, frame_data, operator.methodcaller("cummax")),
+ (pd.Series, ([1],), operator.methodcaller("cumprod")),
+ (pd.DataFrame, frame_data, operator.methodcaller("cumprod")),
+ # Reductions
+ (pd.DataFrame, frame_data, operator.methodcaller("any")),
+ (pd.DataFrame, frame_data, operator.methodcaller("all")),
+ (pd.DataFrame, frame_data, operator.methodcaller("min")),
+ (pd.DataFrame, frame_data, operator.methodcaller("max")),
+ (pd.DataFrame, frame_data, operator.methodcaller("sum")),
+ (pd.DataFrame, frame_data, operator.methodcaller("std")),
+ (pd.DataFrame, frame_data, operator.methodcaller("mean")),
+ (pd.DataFrame, frame_data, operator.methodcaller("prod")),
+ (pd.DataFrame, frame_data, operator.methodcaller("sem")),
+ (pd.DataFrame, frame_data, operator.methodcaller("skew")),
+ (pd.DataFrame, frame_data, operator.methodcaller("kurt")),
+]
+
+
+def idfn(x):
+ xpr = re.compile(r"'(.*)?'")
+ m = xpr.search(str(x))
+ if m:
+ return m.group(1)
+ else:
+ return str(x)
+
+
+@pytest.mark.parametrize("ndframe_method", _all_methods, ids=lambda x: idfn(x[-1]))
+def test_finalize_called(ndframe_method):
+ cls, init_args, method = ndframe_method
+ ndframe = cls(*init_args)
+
+ ndframe.attrs = {"a": 1}
+ result = method(ndframe)
+
+ assert result.attrs == {"a": 1}
+
+
+@not_implemented_mark
+def test_finalize_called_eval_numexpr():
+ pytest.importorskip("numexpr")
+ df = pd.DataFrame({"A": [1, 2]})
+ df.attrs["A"] = 1
+ result = df.eval("A + 1", engine="numexpr")
+ assert result.attrs == {"A": 1}
+
+
+# ----------------------------------------------------------------------------
+# Binary operations
+
+
+@pytest.mark.parametrize("annotate", ["left", "right", "both"])
+@pytest.mark.parametrize(
+ "args",
+ [
+ (1, pd.Series([1])),
+ (1, pd.DataFrame({"A": [1]})),
+ (pd.Series([1]), 1),
+ (pd.DataFrame({"A": [1]}), 1),
+ (pd.Series([1]), pd.Series([1])),
+ (pd.DataFrame({"A": [1]}), pd.DataFrame({"A": [1]})),
+ (pd.Series([1]), pd.DataFrame({"A": [1]})),
+ (pd.DataFrame({"A": [1]}), pd.Series([1])),
+ ],
+ ids=lambda x: f"({type(x[0]).__name__},{type(x[1]).__name__})",
+)
+def test_binops(request, args, annotate, all_binary_operators):
+ # This generates 624 tests... Is that needed?
+ left, right = args
+ if isinstance(left, (pd.DataFrame, pd.Series)):
+ left.attrs = {}
+ if isinstance(right, (pd.DataFrame, pd.Series)):
+ right.attrs = {}
+
+ if annotate == "left" and isinstance(left, int):
+ pytest.skip("left is an int and doesn't support .attrs")
+ if annotate == "right" and isinstance(right, int):
+ pytest.skip("right is an int and doesn't support .attrs")
+
+ if annotate in {"left", "both"} and not isinstance(left, int):
+ left.attrs = {"a": 1}
+ if annotate in {"right", "both"} and not isinstance(right, int):
+ right.attrs = {"a": 1}
+
+ is_cmp = all_binary_operators in [
+ operator.eq,
+ operator.ne,
+ operator.gt,
+ operator.ge,
+ operator.lt,
+ operator.le,
+ ]
+ if is_cmp and isinstance(left, pd.DataFrame) and isinstance(right, pd.Series):
+ # in 2.0 silent alignment on comparisons was removed xref GH#28759
+ left, right = left.align(right, axis=1)
+ elif is_cmp and isinstance(left, pd.Series) and isinstance(right, pd.DataFrame):
+ right, left = right.align(left, axis=1)
+
+ result = all_binary_operators(left, right)
+ assert result.attrs == {"a": 1}
+
+
+@pytest.mark.parametrize("left", [pd.Series, pd.DataFrame])
+@pytest.mark.parametrize("right", [pd.Series, pd.DataFrame])
+def test_attrs_binary_operations(all_binary_operators, left, right):
+ # GH 51607
+ attrs = {"a": 1}
+ left = left([1])
+ left.attrs = attrs
+ right = right([2])
+ assert all_binary_operators(left, right).attrs == attrs
+ assert all_binary_operators(right, left).attrs == attrs
+
+
+# ----------------------------------------------------------------------------
+# Accessors
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ operator.methodcaller("capitalize"),
+ operator.methodcaller("casefold"),
+ operator.methodcaller("cat", ["a"]),
+ operator.methodcaller("contains", "a"),
+ operator.methodcaller("count", "a"),
+ operator.methodcaller("encode", "utf-8"),
+ operator.methodcaller("endswith", "a"),
+ operator.methodcaller("extract", r"(\w)(\d)"),
+ operator.methodcaller("extract", r"(\w)(\d)", expand=False),
+ operator.methodcaller("find", "a"),
+ operator.methodcaller("findall", "a"),
+ operator.methodcaller("get", 0),
+ operator.methodcaller("index", "a"),
+ operator.methodcaller("len"),
+ operator.methodcaller("ljust", 4),
+ operator.methodcaller("lower"),
+ operator.methodcaller("lstrip"),
+ operator.methodcaller("match", r"\w"),
+ operator.methodcaller("normalize", "NFC"),
+ operator.methodcaller("pad", 4),
+ operator.methodcaller("partition", "a"),
+ operator.methodcaller("repeat", 2),
+ operator.methodcaller("replace", "a", "b"),
+ operator.methodcaller("rfind", "a"),
+ operator.methodcaller("rindex", "a"),
+ operator.methodcaller("rjust", 4),
+ operator.methodcaller("rpartition", "a"),
+ operator.methodcaller("rstrip"),
+ operator.methodcaller("slice", 4),
+ operator.methodcaller("slice_replace", 1, repl="a"),
+ operator.methodcaller("startswith", "a"),
+ operator.methodcaller("strip"),
+ operator.methodcaller("swapcase"),
+ operator.methodcaller("translate", {"a": "b"}),
+ operator.methodcaller("upper"),
+ operator.methodcaller("wrap", 4),
+ operator.methodcaller("zfill", 4),
+ operator.methodcaller("isalnum"),
+ operator.methodcaller("isalpha"),
+ operator.methodcaller("isdigit"),
+ operator.methodcaller("isspace"),
+ operator.methodcaller("islower"),
+ operator.methodcaller("isupper"),
+ operator.methodcaller("istitle"),
+ operator.methodcaller("isnumeric"),
+ operator.methodcaller("isdecimal"),
+ operator.methodcaller("get_dummies"),
+ ],
+ ids=idfn,
+)
+def test_string_method(method):
+ s = pd.Series(["a1"])
+ s.attrs = {"a": 1}
+ result = method(s.str)
+ assert result.attrs == {"a": 1}
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ operator.methodcaller("to_period"),
+ operator.methodcaller("tz_localize", "CET"),
+ operator.methodcaller("normalize"),
+ operator.methodcaller("strftime", "%Y"),
+ operator.methodcaller("round", "h"),
+ operator.methodcaller("floor", "h"),
+ operator.methodcaller("ceil", "h"),
+ operator.methodcaller("month_name"),
+ operator.methodcaller("day_name"),
+ ],
+ ids=idfn,
+)
+def test_datetime_method(method):
+ s = pd.Series(pd.date_range("2000", periods=4))
+ s.attrs = {"a": 1}
+ result = method(s.dt)
+ assert result.attrs == {"a": 1}
+
+
+@pytest.mark.parametrize(
+ "attr",
+ [
+ "date",
+ "time",
+ "timetz",
+ "year",
+ "month",
+ "day",
+ "hour",
+ "minute",
+ "second",
+ "microsecond",
+ "nanosecond",
+ "dayofweek",
+ "day_of_week",
+ "dayofyear",
+ "day_of_year",
+ "quarter",
+ "is_month_start",
+ "is_month_end",
+ "is_quarter_start",
+ "is_quarter_end",
+ "is_year_start",
+ "is_year_end",
+ "is_leap_year",
+ "daysinmonth",
+ "days_in_month",
+ ],
+)
+def test_datetime_property(attr):
+ s = pd.Series(pd.date_range("2000", periods=4))
+ s.attrs = {"a": 1}
+ result = getattr(s.dt, attr)
+ assert result.attrs == {"a": 1}
+
+
+@pytest.mark.parametrize(
+ "attr", ["days", "seconds", "microseconds", "nanoseconds", "components"]
+)
+def test_timedelta_property(attr):
+ s = pd.Series(pd.timedelta_range("2000", periods=4))
+ s.attrs = {"a": 1}
+ result = getattr(s.dt, attr)
+ assert result.attrs == {"a": 1}
+
+
+@pytest.mark.parametrize("method", [operator.methodcaller("total_seconds")])
+def test_timedelta_methods(method):
+ s = pd.Series(pd.timedelta_range("2000", periods=4))
+ s.attrs = {"a": 1}
+ result = method(s.dt)
+ assert result.attrs == {"a": 1}
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ operator.methodcaller("add_categories", ["c"]),
+ operator.methodcaller("as_ordered"),
+ operator.methodcaller("as_unordered"),
+ lambda x: x.codes,
+ operator.methodcaller("remove_categories", "a"),
+ operator.methodcaller("remove_unused_categories"),
+ operator.methodcaller("rename_categories", {"a": "A", "b": "B"}),
+ operator.methodcaller("reorder_categories", ["b", "a"]),
+ operator.methodcaller("set_categories", ["A", "B"]),
+ ],
+)
+@not_implemented_mark
+def test_categorical_accessor(method):
+ s = pd.Series(["a", "b"], dtype="category")
+ s.attrs = {"a": 1}
+ result = method(s.cat)
+ assert result.attrs == {"a": 1}
+
+
+# ----------------------------------------------------------------------------
+# Groupby
+
+
+@pytest.mark.parametrize(
+ "obj", [pd.Series([0, 0]), pd.DataFrame({"A": [0, 1], "B": [1, 2]})]
+)
+@pytest.mark.parametrize(
+ "method",
+ [
+ operator.methodcaller("sum"),
+ lambda x: x.apply(lambda y: y),
+ lambda x: x.agg("sum"),
+ lambda x: x.agg("mean"),
+ lambda x: x.agg("median"),
+ ],
+)
+def test_groupby_finalize(obj, method):
+ obj.attrs = {"a": 1}
+ result = method(obj.groupby([0, 0], group_keys=False))
+ assert result.attrs == {"a": 1}
+
+
+@pytest.mark.parametrize(
+ "obj", [pd.Series([0, 0]), pd.DataFrame({"A": [0, 1], "B": [1, 2]})]
+)
+@pytest.mark.parametrize(
+ "method",
+ [
+ lambda x: x.agg(["sum", "count"]),
+ lambda x: x.agg("std"),
+ lambda x: x.agg("var"),
+ lambda x: x.agg("sem"),
+ lambda x: x.agg("size"),
+ lambda x: x.agg("ohlc"),
+ ],
+)
+@not_implemented_mark
+def test_groupby_finalize_not_implemented(obj, method):
+ obj.attrs = {"a": 1}
+ result = method(obj.groupby([0, 0]))
+ assert result.attrs == {"a": 1}
+
+
+def test_finalize_frame_series_name():
+ # https://github.com/pandas-dev/pandas/pull/37186/files#r506978889
+ # ensure we don't copy the column `name` to the Series.
+ df = pd.DataFrame({"name": [1, 2]})
+ result = pd.Series([1, 2]).__finalize__(df)
+ assert result.name is None
+
+
+# ----------------------------------------------------------------------------
+# Merge
+
+
+@pytest.mark.parametrize(
+ ["allow_on_left", "allow_on_right"],
+ [(False, False), (False, True), (True, False), (True, True)],
+)
+@pytest.mark.parametrize(
+ "how",
+ [
+ "left",
+ "right",
+ "inner",
+ "outer",
+ "left_anti",
+ "right_anti",
+ "cross",
+ ],
+)
+def test_merge_correctly_sets_duplication_allowance_flag(
+ how: MergeHow,
+ allow_on_left: bool,
+ allow_on_right: bool,
+):
+ left = pd.DataFrame({"test": [1]}).set_flags(allows_duplicate_labels=allow_on_left)
+ right = pd.DataFrame({"test": [1]}).set_flags(
+ allows_duplicate_labels=allow_on_right,
+ )
+
+ if not how == "cross":
+ result = left.merge(right, how=how, on="test")
+ else:
+ result = left.merge(right, how=how)
+
+ expected_duplication_allowance = allow_on_left and allow_on_right
+ assert result.flags.allows_duplicate_labels == expected_duplication_allowance
+
+
+@pytest.mark.parametrize(
+ ["allow_on_left", "allow_on_right"],
+ [(False, False), (False, True), (True, False), (True, True)],
+)
+def test_merge_asof_correctly_sets_duplication_allowance_flag(
+ allow_on_left: bool,
+ allow_on_right: bool,
+):
+ left = pd.DataFrame({"test": [1]}).set_flags(allows_duplicate_labels=allow_on_left)
+ right = pd.DataFrame({"test": [1]}).set_flags(
+ allows_duplicate_labels=allow_on_right,
+ )
+
+ result = pd.merge_asof(left, right)
+
+ expected_duplication_allowance = allow_on_left and allow_on_right
+ assert result.flags.allows_duplicate_labels == expected_duplication_allowance
+
+
+def test_merge_propagates_metadata_from_equal_input_metadata():
+ metadata = {"a": [1, 2]}
+ left = pd.DataFrame({"test": [1]})
+ left.attrs = metadata
+ right = pd.DataFrame({"test": [1]})
+ right.attrs = deepcopy(metadata)
+
+ result = left.merge(right, how="inner", on="test")
+
+ assert result.attrs == metadata
+
+ # Verify that merge deep-copies the attr dictionary.
+ assert result.attrs is not left.attrs
+ assert result.attrs is not right.attrs
+ assert result.attrs["a"] is not left.attrs["a"]
+ assert result.attrs["a"] is not right.attrs["a"]
+
+
+def test_merge_does_not_propagate_metadata_from_unequal_input_metadata():
+ left = pd.DataFrame({"test": [1]})
+ left.attrs = {"a": 2}
+ right = pd.DataFrame({"test": [1]})
+ right.attrs = {"b": 3}
+
+ result = left.merge(right, how="inner", on="test")
+
+ assert result.attrs == {}
+
+
+@pytest.mark.parametrize(
+ ["left_has_metadata", "right_has_metadata", "expected"],
+ [
+ (False, True, {}),
+ (True, False, {}),
+ (False, False, {}),
+ ],
+ ids=["left-empty", "right-empty", "both-empty"],
+)
+def test_merge_does_not_propagate_metadata_if_one_input_has_no_metadata(
+ left_has_metadata: bool,
+ right_has_metadata: bool,
+ expected: dict,
+):
+ left = pd.DataFrame({"test": [1]})
+ right = pd.DataFrame({"test": [1]})
+
+ if left_has_metadata:
+ left.attrs = {"a": [1, 2]}
+ else:
+ left.attrs = {}
+
+ if right_has_metadata:
+ right.attrs = {"a": [1, 2]}
+ else:
+ right.attrs = {}
+
+ result = left.merge(right, how="inner", on="test")
+
+ assert result.attrs == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_frame.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_frame.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2d24cceeab0c0a241338b9826bd420489e0dbba
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_frame.py
@@ -0,0 +1,202 @@
+from copy import deepcopy
+from operator import methodcaller
+from typing import Literal
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestDataFrame:
+ @pytest.mark.parametrize("func", ["_set_axis_name", "rename_axis"])
+ def test_set_axis_name(self, func):
+ df = DataFrame([[1, 2], [3, 4]])
+
+ result = methodcaller(func, "foo")(df)
+ assert df.index.name is None
+ assert result.index.name == "foo"
+
+ result = methodcaller(func, "cols", axis=1)(df)
+ assert df.columns.name is None
+ assert result.columns.name == "cols"
+
+ @pytest.mark.parametrize("func", ["_set_axis_name", "rename_axis"])
+ def test_set_axis_name_mi(self, func):
+ df = DataFrame(
+ np.empty((3, 3)),
+ index=MultiIndex.from_tuples([("A", x) for x in list("aBc")]),
+ columns=MultiIndex.from_tuples([("C", x) for x in list("xyz")]),
+ )
+
+ level_names = ["L1", "L2"]
+
+ result = methodcaller(func, level_names)(df)
+ assert result.index.names == level_names
+ assert result.columns.names == [None, None]
+
+ result = methodcaller(func, level_names, axis=1)(df)
+ assert result.columns.names == ["L1", "L2"]
+ assert result.index.names == [None, None]
+
+ def test_nonzero_single_element(self):
+ df = DataFrame([[False, False]])
+ msg_err = "The truth value of a DataFrame is ambiguous"
+ with pytest.raises(ValueError, match=msg_err):
+ bool(df)
+
+ def test_metadata_propagation_indiv_groupby(self):
+ # groupby
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+ result = df.groupby("A").sum()
+ tm.assert_metadata_equivalent(df, result)
+
+ def test_metadata_propagation_indiv_resample(self):
+ # resample
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((1000, 2)),
+ index=date_range("20130101", periods=1000, freq="s"),
+ )
+ result = df.resample("1min")
+ tm.assert_metadata_equivalent(df, result)
+
+ def test_metadata_propagation_indiv(self, monkeypatch):
+ # merging with override
+ # GH 6923
+
+ def finalize(
+ self: DataFrame,
+ other: DataFrame,
+ method: Literal["merge", "concat"] | None = None,
+ **kwargs,
+ ):
+ for name in self._metadata:
+ if method == "merge":
+ left, right = other.input_objs
+ value = getattr(left, name, "") + "|" + getattr(right, name, "")
+ object.__setattr__(self, name, value)
+ elif method == "concat":
+ value = "+".join(
+ [
+ getattr(o, name)
+ for o in other.input_objs
+ if getattr(o, name, None)
+ ]
+ )
+ object.__setattr__(self, name, value)
+ else:
+ object.__setattr__(self, name, getattr(other, name, ""))
+
+ return self
+
+ with monkeypatch.context() as m:
+ m.setattr(DataFrame, "_metadata", ["filename"])
+ m.setattr(DataFrame, "__finalize__", finalize)
+
+ df1 = DataFrame(
+ np.random.default_rng(2).integers(0, 4, (3, 2)), columns=["a", "b"]
+ )
+ df2 = DataFrame(
+ np.random.default_rng(2).integers(0, 4, (3, 2)), columns=["c", "d"]
+ )
+ DataFrame._metadata = ["filename"]
+ df1.filename = "fname1.csv"
+ df2.filename = "fname2.csv"
+
+ result = df1.merge(df2, left_on=["a"], right_on=["c"], how="inner")
+ assert result.filename == "fname1.csv|fname2.csv"
+
+ # concat
+ # GH#6927
+ df1 = DataFrame(
+ np.random.default_rng(2).integers(0, 4, (3, 2)), columns=list("ab")
+ )
+ df1.filename = "foo"
+
+ result = pd.concat([df1, df1])
+ assert result.filename == "foo+foo"
+
+ def test_set_attribute(self):
+ # Test for consistent setattr behavior when an attribute and a column
+ # have the same name (Issue #8994)
+ df = DataFrame({"x": [1, 2, 3]})
+
+ df.y = 2
+ df["y"] = [2, 4, 6]
+ df.y = 5
+
+ assert df.y == 5
+ tm.assert_series_equal(df["y"], Series([2, 4, 6], name="y"))
+
+ def test_deepcopy_empty(self):
+ # This test covers empty frame copying with non-empty column sets
+ # as reported in issue GH15370
+ empty_frame = DataFrame(data=[], index=[], columns=["A"])
+ empty_frame_copy = deepcopy(empty_frame)
+
+ tm.assert_frame_equal(empty_frame_copy, empty_frame)
+
+
+# formerly in Generic but only test DataFrame
+class TestDataFrame2:
+ @pytest.mark.parametrize("value", [1, "True", [1, 2, 3], 5.0])
+ def test_validate_bool_args(self, value):
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+
+ msg = 'For argument "inplace" expected type bool, received type'
+ with pytest.raises(ValueError, match=msg):
+ df.copy().rename_axis(mapper={"a": "x", "b": "y"}, axis=1, inplace=value)
+
+ with pytest.raises(ValueError, match=msg):
+ df.copy().drop("a", axis=1, inplace=value)
+
+ with pytest.raises(ValueError, match=msg):
+ df.copy().fillna(value=0, inplace=value)
+
+ with pytest.raises(ValueError, match=msg):
+ df.copy().replace(to_replace=1, value=7, inplace=value)
+
+ with pytest.raises(ValueError, match=msg):
+ df.copy().interpolate(inplace=value)
+
+ with pytest.raises(ValueError, match=msg):
+ df.copy()._where(cond=df.a > 2, inplace=value)
+
+ with pytest.raises(ValueError, match=msg):
+ df.copy().mask(cond=df.a > 2, inplace=value)
+
+ def test_unexpected_keyword(self):
+ # GH8597
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=["jim", "joe"]
+ )
+ ca = pd.Categorical([0, 0, 2, 2, 3, np.nan])
+ ts = df["joe"].copy()
+ ts[2] = np.nan
+
+ msg = "unexpected keyword"
+ with pytest.raises(TypeError, match=msg):
+ df.drop("joe", axis=1, in_place=True)
+
+ with pytest.raises(TypeError, match=msg):
+ df.reindex([1, 0], inplace=True)
+
+ with pytest.raises(TypeError, match=msg):
+ ca.fillna(0, inplace=True)
+
+ with pytest.raises(TypeError, match=msg):
+ ts.fillna(0, in_place=True)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_generic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_generic.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee6503b6929b615163e42cef167b34e87539c001
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_generic.py
@@ -0,0 +1,494 @@
+from copy import (
+ copy,
+ deepcopy,
+)
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import is_scalar
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+# ----------------------------------------------------------------------
+# Generic types test cases
+
+
+def construct(box, shape, value=None, dtype=None, **kwargs):
+ """
+ construct an object for the given shape
+ if value is specified use that if its a scalar
+ if value is an array, repeat it as needed
+ """
+ if isinstance(shape, int):
+ shape = tuple([shape] * box._AXIS_LEN)
+ if value is not None:
+ if is_scalar(value):
+ if value == "empty":
+ arr = None
+ dtype = np.float64
+
+ # remove the info axis
+ kwargs.pop(box._info_axis_name, None)
+ else:
+ arr = np.empty(shape, dtype=dtype)
+ arr.fill(value)
+ else:
+ fshape = np.prod(shape)
+ arr = value.ravel()
+ new_shape = fshape / arr.shape[0]
+ if fshape % arr.shape[0] != 0:
+ raise Exception("invalid value passed in construct")
+
+ arr = np.repeat(arr, new_shape).reshape(shape)
+ else:
+ arr = np.random.default_rng(2).standard_normal(shape)
+ return box(arr, dtype=dtype, **kwargs)
+
+
+class TestGeneric:
+ @pytest.mark.parametrize(
+ "func",
+ [
+ str.lower,
+ {x: x.lower() for x in list("ABCD")},
+ Series({x: x.lower() for x in list("ABCD")}),
+ ],
+ )
+ def test_rename(self, frame_or_series, func):
+ # single axis
+ idx = list("ABCD")
+
+ for axis in frame_or_series._AXIS_ORDERS:
+ kwargs = {axis: idx}
+ obj = construct(frame_or_series, 4, **kwargs)
+
+ # rename a single axis
+ result = obj.rename(**{axis: func})
+ expected = obj.copy()
+ setattr(expected, axis, list("abcd"))
+ tm.assert_equal(result, expected)
+
+ def test_get_numeric_data(self, frame_or_series):
+ n = 4
+ kwargs = {
+ frame_or_series._get_axis_name(i): list(range(n))
+ for i in range(frame_or_series._AXIS_LEN)
+ }
+
+ # get the numeric data
+ o = construct(frame_or_series, n, **kwargs)
+ result = o._get_numeric_data()
+ tm.assert_equal(result, o)
+
+ # non-inclusion
+ result = o._get_bool_data()
+ expected = construct(frame_or_series, n, value="empty", **kwargs)
+ if isinstance(o, DataFrame):
+ # preserve columns dtype
+ expected.columns = o.columns[:0]
+ tm.assert_equal(result, expected)
+
+ # get the bool data
+ arr = np.array([True, True, False, True])
+ o = construct(frame_or_series, n, value=arr, **kwargs)
+ result = o._get_numeric_data()
+ tm.assert_equal(result, o)
+
+ def test_get_bool_data_empty_preserve_index(self):
+ expected = Series([], dtype="bool")
+ result = expected._get_bool_data()
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ def test_nonzero(self, frame_or_series):
+ # GH 4633
+ # look at the boolean/nonzero behavior for objects
+ obj = construct(frame_or_series, shape=4)
+ msg = f"The truth value of a {frame_or_series.__name__} is ambiguous"
+ with pytest.raises(ValueError, match=msg):
+ bool(obj == 0)
+ with pytest.raises(ValueError, match=msg):
+ bool(obj == 1)
+ with pytest.raises(ValueError, match=msg):
+ bool(obj)
+
+ obj = construct(frame_or_series, shape=4, value=1)
+ with pytest.raises(ValueError, match=msg):
+ bool(obj == 0)
+ with pytest.raises(ValueError, match=msg):
+ bool(obj == 1)
+ with pytest.raises(ValueError, match=msg):
+ bool(obj)
+
+ obj = construct(frame_or_series, shape=4, value=np.nan)
+ with pytest.raises(ValueError, match=msg):
+ bool(obj == 0)
+ with pytest.raises(ValueError, match=msg):
+ bool(obj == 1)
+ with pytest.raises(ValueError, match=msg):
+ bool(obj)
+
+ # empty
+ obj = construct(frame_or_series, shape=0)
+ with pytest.raises(ValueError, match=msg):
+ bool(obj)
+
+ # invalid behaviors
+
+ obj1 = construct(frame_or_series, shape=4, value=1)
+ obj2 = construct(frame_or_series, shape=4, value=1)
+
+ with pytest.raises(ValueError, match=msg):
+ if obj1:
+ pass
+
+ with pytest.raises(ValueError, match=msg):
+ obj1 and obj2
+ with pytest.raises(ValueError, match=msg):
+ obj1 or obj2
+ with pytest.raises(ValueError, match=msg):
+ not obj1
+
+ def test_frame_or_series_compound_dtypes(self, frame_or_series):
+ # see gh-5191
+ # Compound dtypes should raise NotImplementedError.
+
+ def f(dtype):
+ return construct(frame_or_series, shape=3, value=1, dtype=dtype)
+
+ msg = (
+ "compound dtypes are not implemented "
+ f"in the {frame_or_series.__name__} constructor"
+ )
+
+ with pytest.raises(NotImplementedError, match=msg):
+ f([("A", "datetime64[h]"), ("B", "str"), ("C", "int32")])
+
+ # these work (though results may be unexpected)
+ f("int64")
+ f("float64")
+ f("M8[ns]")
+
+ def test_metadata_propagation(self, frame_or_series):
+ # check that the metadata matches up on the resulting ops
+
+ o = construct(frame_or_series, shape=3)
+ o.name = "foo"
+ o2 = construct(frame_or_series, shape=3)
+ o2.name = "bar"
+
+ # ----------
+ # preserving
+ # ----------
+
+ # simple ops with scalars
+ for op in ["__add__", "__sub__", "__truediv__", "__mul__"]:
+ result = getattr(o, op)(1)
+ tm.assert_metadata_equivalent(o, result)
+
+ # ops with like
+ for op in ["__add__", "__sub__", "__truediv__", "__mul__"]:
+ result = getattr(o, op)(o)
+ tm.assert_metadata_equivalent(o, result)
+
+ # simple boolean
+ for op in ["__eq__", "__le__", "__ge__"]:
+ v1 = getattr(o, op)(o)
+ tm.assert_metadata_equivalent(o, v1)
+ tm.assert_metadata_equivalent(o, v1 & v1)
+ tm.assert_metadata_equivalent(o, v1 | v1)
+
+ # combine_first
+ result = o.combine_first(o2)
+ tm.assert_metadata_equivalent(o, result)
+
+ # ---------------------------
+ # non-preserving (by default)
+ # ---------------------------
+
+ # add non-like
+ result = o + o2
+ tm.assert_metadata_equivalent(result)
+
+ # simple boolean
+ for op in ["__eq__", "__le__", "__ge__"]:
+ # this is a name matching op
+ v1 = getattr(o, op)(o)
+ v2 = getattr(o, op)(o2)
+ tm.assert_metadata_equivalent(v2)
+ tm.assert_metadata_equivalent(v1 & v2)
+ tm.assert_metadata_equivalent(v1 | v2)
+
+ def test_size_compat(self, frame_or_series):
+ # GH8846
+ # size property should be defined
+
+ o = construct(frame_or_series, shape=10)
+ assert o.size == np.prod(o.shape)
+ assert o.size == 10 ** len(o.axes)
+
+ def test_split_compat(self, frame_or_series):
+ # xref GH8846
+ o = construct(frame_or_series, shape=10)
+ assert len(np.array_split(o, 5)) == 5
+ assert len(np.array_split(o, 2)) == 2
+
+ # See gh-12301
+ def test_stat_unexpected_keyword(self, frame_or_series):
+ obj = construct(frame_or_series, 5)
+ starwars = "Star Wars"
+ errmsg = "unexpected keyword"
+
+ with pytest.raises(TypeError, match=errmsg):
+ obj.max(epic=starwars) # stat_function
+ with pytest.raises(TypeError, match=errmsg):
+ obj.var(epic=starwars) # stat_function_ddof
+ with pytest.raises(TypeError, match=errmsg):
+ obj.sum(epic=starwars) # cum_function
+ with pytest.raises(TypeError, match=errmsg):
+ obj.any(epic=starwars) # logical_function
+
+ @pytest.mark.parametrize("func", ["sum", "cumsum", "any", "var"])
+ def test_api_compat(self, func, frame_or_series):
+ # GH 12021
+ # compat for __name__, __qualname__
+
+ obj = construct(frame_or_series, 5)
+ f = getattr(obj, func)
+ assert f.__name__ == func
+ assert f.__qualname__.endswith(func)
+
+ def test_stat_non_defaults_args(self, frame_or_series):
+ obj = construct(frame_or_series, 5)
+ out = np.array([0])
+ errmsg = "the 'out' parameter is not supported"
+
+ with pytest.raises(ValueError, match=errmsg):
+ obj.max(out=out) # stat_function
+ with pytest.raises(ValueError, match=errmsg):
+ obj.var(out=out) # stat_function_ddof
+ with pytest.raises(ValueError, match=errmsg):
+ obj.sum(out=out) # cum_function
+ with pytest.raises(ValueError, match=errmsg):
+ obj.any(out=out) # logical_function
+
+ def test_truncate_out_of_bounds(self, frame_or_series):
+ # GH11382
+
+ # small
+ shape = [2000] + ([1] * (frame_or_series._AXIS_LEN - 1))
+ small = construct(frame_or_series, shape, dtype="int8", value=1)
+ tm.assert_equal(small.truncate(), small)
+ tm.assert_equal(small.truncate(before=0, after=3e3), small)
+ tm.assert_equal(small.truncate(before=-1, after=2e3), small)
+
+ # big
+ shape = [2_000_000] + ([1] * (frame_or_series._AXIS_LEN - 1))
+ big = construct(frame_or_series, shape, dtype="int8", value=1)
+ tm.assert_equal(big.truncate(), big)
+ tm.assert_equal(big.truncate(before=0, after=3e6), big)
+ tm.assert_equal(big.truncate(before=-1, after=2e6), big)
+
+ @pytest.mark.parametrize(
+ "func",
+ [copy, deepcopy, lambda x: x.copy(deep=False), lambda x: x.copy(deep=True)],
+ )
+ @pytest.mark.parametrize("shape", [0, 1, 2])
+ def test_copy_and_deepcopy(self, frame_or_series, shape, func):
+ # GH 15444
+ obj = construct(frame_or_series, shape)
+ obj_copy = func(obj)
+ assert obj_copy is not obj
+ tm.assert_equal(obj_copy, obj)
+
+ def test_stdlib_copy_shallow_copies(self, frame_or_series):
+ obj = frame_or_series(range(3))
+ obj_copy = copy(obj)
+ assert tm.shares_memory(obj, obj_copy)
+
+
+class TestNDFrame:
+ # tests that don't fit elsewhere
+
+ @pytest.mark.parametrize(
+ "ser",
+ [
+ Series(range(10), dtype=np.float64),
+ Series([str(i) for i in range(10)], dtype=object),
+ ],
+ )
+ def test_squeeze_series_noop(self, ser):
+ # noop
+ tm.assert_series_equal(ser.squeeze(), ser)
+
+ def test_squeeze_frame_noop(self):
+ # noop
+ df = DataFrame(np.eye(2))
+ tm.assert_frame_equal(df.squeeze(), df)
+
+ def test_squeeze_frame_reindex(self):
+ # squeezing
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ ).reindex(columns=["A"])
+ tm.assert_series_equal(df.squeeze(), df["A"])
+
+ def test_squeeze_0_len_dim(self):
+ # don't fail with 0 length dimensions GH11229 & GH8999
+ empty_series = Series([], name="five", dtype=np.float64)
+ empty_frame = DataFrame([empty_series])
+ tm.assert_series_equal(empty_series, empty_series.squeeze())
+ tm.assert_series_equal(empty_series, empty_frame.squeeze())
+
+ def test_squeeze_axis(self):
+ # axis argument
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((1, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=1, freq="B"),
+ ).iloc[:, :1]
+ assert df.shape == (1, 1)
+ tm.assert_series_equal(df.squeeze(axis=0), df.iloc[0])
+ tm.assert_series_equal(df.squeeze(axis="index"), df.iloc[0])
+ tm.assert_series_equal(df.squeeze(axis=1), df.iloc[:, 0])
+ tm.assert_series_equal(df.squeeze(axis="columns"), df.iloc[:, 0])
+ assert df.squeeze() == df.iloc[0, 0]
+ msg = "No axis named 2 for object type DataFrame"
+ with pytest.raises(ValueError, match=msg):
+ df.squeeze(axis=2)
+ msg = "No axis named x for object type DataFrame"
+ with pytest.raises(ValueError, match=msg):
+ df.squeeze(axis="x")
+
+ def test_squeeze_axis_len_3(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=3, freq="B"),
+ )
+ tm.assert_frame_equal(df.squeeze(axis=0), df)
+
+ def test_numpy_squeeze(self):
+ s = Series(range(2), dtype=np.float64)
+ tm.assert_series_equal(np.squeeze(s), s)
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ ).reindex(columns=["A"])
+ tm.assert_series_equal(np.squeeze(df), df["A"])
+
+ @pytest.mark.parametrize(
+ "ser",
+ [
+ Series(range(10), dtype=np.float64),
+ Series([str(i) for i in range(10)], dtype=object),
+ ],
+ )
+ def test_transpose_series(self, ser):
+ # calls implementation in pandas/core/base.py
+ tm.assert_series_equal(ser.transpose(), ser)
+
+ def test_transpose_frame(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ tm.assert_frame_equal(df.transpose().transpose(), df)
+
+ def test_numpy_transpose(self, frame_or_series):
+ obj = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ obj = tm.get_obj(obj, frame_or_series)
+
+ if frame_or_series is Series:
+ # 1D -> np.transpose is no-op
+ tm.assert_series_equal(np.transpose(obj), obj)
+
+ # round-trip preserved
+ tm.assert_equal(np.transpose(np.transpose(obj)), obj)
+
+ msg = "the 'axes' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.transpose(obj, axes=1)
+
+ @pytest.mark.parametrize(
+ "ser",
+ [
+ Series(range(10), dtype=np.float64),
+ Series([str(i) for i in range(10)], dtype=object),
+ ],
+ )
+ def test_take_series(self, ser):
+ indices = [1, 5, -2, 6, 3, -1]
+ out = ser.take(indices)
+ expected = Series(
+ data=ser.values.take(indices),
+ index=ser.index.take(indices),
+ dtype=ser.dtype,
+ )
+ tm.assert_series_equal(out, expected)
+
+ def test_take_frame(self):
+ indices = [1, 5, -2, 6, 3, -1]
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ out = df.take(indices)
+ expected = DataFrame(
+ data=df.values.take(indices, axis=0),
+ index=df.index.take(indices),
+ columns=df.columns,
+ )
+ tm.assert_frame_equal(out, expected)
+
+ def test_take_invalid_kwargs(self, frame_or_series):
+ indices = [-3, 2, 0, 1]
+
+ obj = DataFrame(range(5))
+ obj = tm.get_obj(obj, frame_or_series)
+
+ msg = r"take\(\) got an unexpected keyword argument 'foo'"
+ with pytest.raises(TypeError, match=msg):
+ obj.take(indices, foo=2)
+
+ msg = "the 'out' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ obj.take(indices, out=indices)
+
+ msg = "the 'mode' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ obj.take(indices, mode="clip")
+
+ def test_axis_classmethods(self, frame_or_series):
+ box = frame_or_series
+ obj = box(dtype=object)
+ values = box._AXIS_TO_AXIS_NUMBER.keys()
+ for v in values:
+ assert obj._get_axis_number(v) == box._get_axis_number(v)
+ assert obj._get_axis_name(v) == box._get_axis_name(v)
+ assert obj._get_block_manager_axis(v) == box._get_block_manager_axis(v)
+
+ def test_flags_identity(self, frame_or_series):
+ obj = Series([1, 2])
+ if frame_or_series is DataFrame:
+ obj = obj.to_frame()
+
+ assert obj.flags is obj.flags
+ obj2 = obj.copy()
+ assert obj2.flags is not obj.flags
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_label_or_level_utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_label_or_level_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..797f3d488ab18285fd2a829075f5bd06985b25a3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_label_or_level_utils.py
@@ -0,0 +1,329 @@
+import pytest
+
+from pandas.core.dtypes.missing import array_equivalent
+
+import pandas as pd
+
+
+# Fixtures
+# ========
+@pytest.fixture
+def df():
+ """DataFrame with columns 'L1', 'L2', and 'L3'"""
+ return pd.DataFrame({"L1": [1, 2, 3], "L2": [11, 12, 13], "L3": ["A", "B", "C"]})
+
+
+@pytest.fixture(params=[[], ["L1"], ["L1", "L2"], ["L1", "L2", "L3"]])
+def df_levels(request, df):
+ """DataFrame with columns or index levels 'L1', 'L2', and 'L3'"""
+ levels = request.param
+
+ if levels:
+ df = df.set_index(levels)
+
+ return df
+
+
+@pytest.fixture
+def df_ambig(df):
+ """DataFrame with levels 'L1' and 'L2' and labels 'L1' and 'L3'"""
+ df = df.set_index(["L1", "L2"])
+
+ df["L1"] = df["L3"]
+
+ return df
+
+
+# Test is label/level reference
+# =============================
+def get_labels_levels(df_levels):
+ expected_labels = list(df_levels.columns)
+ expected_levels = [name for name in df_levels.index.names if name is not None]
+ return expected_labels, expected_levels
+
+
+def assert_label_reference(frame, labels, axis):
+ for label in labels:
+ assert frame._is_label_reference(label, axis=axis)
+ assert not frame._is_level_reference(label, axis=axis)
+ assert frame._is_label_or_level_reference(label, axis=axis)
+
+
+def assert_level_reference(frame, levels, axis):
+ for level in levels:
+ assert frame._is_level_reference(level, axis=axis)
+ assert not frame._is_label_reference(level, axis=axis)
+ assert frame._is_label_or_level_reference(level, axis=axis)
+
+
+# DataFrame
+# ---------
+def test_is_level_or_label_reference_df_simple(df_levels, axis):
+ axis = df_levels._get_axis_number(axis)
+ # Compute expected labels and levels
+ expected_labels, expected_levels = get_labels_levels(df_levels)
+
+ # Transpose frame if axis == 1
+ if axis == 1:
+ df_levels = df_levels.T
+
+ # Perform checks
+ assert_level_reference(df_levels, expected_levels, axis=axis)
+ assert_label_reference(df_levels, expected_labels, axis=axis)
+
+
+def test_is_level_reference_df_ambig(df_ambig, axis):
+ axis = df_ambig._get_axis_number(axis)
+
+ # Transpose frame if axis == 1
+ if axis == 1:
+ df_ambig = df_ambig.T
+
+ # df has both an on-axis level and off-axis label named L1
+ # Therefore L1 should reference the label, not the level
+ assert_label_reference(df_ambig, ["L1"], axis=axis)
+
+ # df has an on-axis level named L2 and it is not ambiguous
+ # Therefore L2 is a level reference
+ assert_level_reference(df_ambig, ["L2"], axis=axis)
+
+ # df has a column named L3 and it is not a level reference
+ assert_label_reference(df_ambig, ["L3"], axis=axis)
+
+
+# Series
+# ------
+def test_is_level_reference_series_simple_axis0(df):
+ # Make series with L1 as index
+ s = df.set_index("L1").L2
+ assert_level_reference(s, ["L1"], axis=0)
+ assert not s._is_level_reference("L2")
+
+ # Make series with L1 and L2 as index
+ s = df.set_index(["L1", "L2"]).L3
+ assert_level_reference(s, ["L1", "L2"], axis=0)
+ assert not s._is_level_reference("L3")
+
+
+def test_is_level_reference_series_axis1_error(df):
+ # Make series with L1 as index
+ s = df.set_index("L1").L2
+
+ with pytest.raises(ValueError, match="No axis named 1"):
+ s._is_level_reference("L1", axis=1)
+
+
+# Test _check_label_or_level_ambiguity_df
+# =======================================
+
+
+# DataFrame
+# ---------
+def test_check_label_or_level_ambiguity_df(df_ambig, axis):
+ axis = df_ambig._get_axis_number(axis)
+ # Transpose frame if axis == 1
+ if axis == 1:
+ df_ambig = df_ambig.T
+ msg = "'L1' is both a column level and an index label"
+
+ else:
+ msg = "'L1' is both an index level and a column label"
+ # df_ambig has both an on-axis level and off-axis label named L1
+ # Therefore, L1 is ambiguous.
+ with pytest.raises(ValueError, match=msg):
+ df_ambig._check_label_or_level_ambiguity("L1", axis=axis)
+
+ # df_ambig has an on-axis level named L2,, and it is not ambiguous.
+ df_ambig._check_label_or_level_ambiguity("L2", axis=axis)
+
+ # df_ambig has an off-axis label named L3, and it is not ambiguous
+ assert not df_ambig._check_label_or_level_ambiguity("L3", axis=axis)
+
+
+# Series
+# ------
+def test_check_label_or_level_ambiguity_series(df):
+ # A series has no columns and therefore references are never ambiguous
+
+ # Make series with L1 as index
+ s = df.set_index("L1").L2
+ s._check_label_or_level_ambiguity("L1", axis=0)
+ s._check_label_or_level_ambiguity("L2", axis=0)
+
+ # Make series with L1 and L2 as index
+ s = df.set_index(["L1", "L2"]).L3
+ s._check_label_or_level_ambiguity("L1", axis=0)
+ s._check_label_or_level_ambiguity("L2", axis=0)
+ s._check_label_or_level_ambiguity("L3", axis=0)
+
+
+def test_check_label_or_level_ambiguity_series_axis1_error(df):
+ # Make series with L1 as index
+ s = df.set_index("L1").L2
+
+ with pytest.raises(ValueError, match="No axis named 1"):
+ s._check_label_or_level_ambiguity("L1", axis=1)
+
+
+# Test _get_label_or_level_values
+# ===============================
+def assert_label_values(frame, labels, axis):
+ axis = frame._get_axis_number(axis)
+ for label in labels:
+ if axis == 0:
+ expected = frame[label]._values
+ else:
+ expected = frame.loc[label]._values
+
+ result = frame._get_label_or_level_values(label, axis=axis)
+ assert array_equivalent(expected, result)
+
+
+def assert_level_values(frame, levels, axis):
+ axis = frame._get_axis_number(axis)
+ for level in levels:
+ if axis == 0:
+ expected = frame.index.get_level_values(level=level)._values
+ else:
+ expected = frame.columns.get_level_values(level=level)._values
+
+ result = frame._get_label_or_level_values(level, axis=axis)
+ assert array_equivalent(expected, result)
+
+
+# DataFrame
+# ---------
+def test_get_label_or_level_values_df_simple(df_levels, axis):
+ # Compute expected labels and levels
+ expected_labels, expected_levels = get_labels_levels(df_levels)
+
+ axis = df_levels._get_axis_number(axis)
+ # Transpose frame if axis == 1
+ if axis == 1:
+ df_levels = df_levels.T
+
+ # Perform checks
+ assert_label_values(df_levels, expected_labels, axis=axis)
+ assert_level_values(df_levels, expected_levels, axis=axis)
+
+
+def test_get_label_or_level_values_df_ambig(df_ambig, axis):
+ axis = df_ambig._get_axis_number(axis)
+ # Transpose frame if axis == 1
+ if axis == 1:
+ df_ambig = df_ambig.T
+
+ # df has an on-axis level named L2, and it is not ambiguous.
+ assert_level_values(df_ambig, ["L2"], axis=axis)
+
+ # df has an off-axis label named L3, and it is not ambiguous.
+ assert_label_values(df_ambig, ["L3"], axis=axis)
+
+
+def test_get_label_or_level_values_df_duplabels(df, axis):
+ df = df.set_index(["L1"])
+ df_duplabels = pd.concat([df, df["L2"]], axis=1)
+ axis = df_duplabels._get_axis_number(axis)
+ # Transpose frame if axis == 1
+ if axis == 1:
+ df_duplabels = df_duplabels.T
+
+ # df has unambiguous level 'L1'
+ assert_level_values(df_duplabels, ["L1"], axis=axis)
+
+ # df has unique label 'L3'
+ assert_label_values(df_duplabels, ["L3"], axis=axis)
+
+ # df has duplicate labels 'L2'
+ if axis == 0:
+ expected_msg = "The column label 'L2' is not unique"
+ else:
+ expected_msg = "The index label 'L2' is not unique"
+
+ with pytest.raises(ValueError, match=expected_msg):
+ assert_label_values(df_duplabels, ["L2"], axis=axis)
+
+
+# Series
+# ------
+def test_get_label_or_level_values_series_axis0(df):
+ # Make series with L1 as index
+ s = df.set_index("L1").L2
+ assert_level_values(s, ["L1"], axis=0)
+
+ # Make series with L1 and L2 as index
+ s = df.set_index(["L1", "L2"]).L3
+ assert_level_values(s, ["L1", "L2"], axis=0)
+
+
+def test_get_label_or_level_values_series_axis1_error(df):
+ # Make series with L1 as index
+ s = df.set_index("L1").L2
+
+ with pytest.raises(ValueError, match="No axis named 1"):
+ s._get_label_or_level_values("L1", axis=1)
+
+
+# Test _drop_labels_or_levels
+# ===========================
+def assert_labels_dropped(frame, labels, axis):
+ axis = frame._get_axis_number(axis)
+ for label in labels:
+ df_dropped = frame._drop_labels_or_levels(label, axis=axis)
+
+ if axis == 0:
+ assert label in frame.columns
+ assert label not in df_dropped.columns
+ else:
+ assert label in frame.index
+ assert label not in df_dropped.index
+
+
+def assert_levels_dropped(frame, levels, axis):
+ axis = frame._get_axis_number(axis)
+ for level in levels:
+ df_dropped = frame._drop_labels_or_levels(level, axis=axis)
+
+ if axis == 0:
+ assert level in frame.index.names
+ assert level not in df_dropped.index.names
+ else:
+ assert level in frame.columns.names
+ assert level not in df_dropped.columns.names
+
+
+# DataFrame
+# ---------
+def test_drop_labels_or_levels_df(df_levels, axis):
+ # Compute expected labels and levels
+ expected_labels, expected_levels = get_labels_levels(df_levels)
+
+ axis = df_levels._get_axis_number(axis)
+ # Transpose frame if axis == 1
+ if axis == 1:
+ df_levels = df_levels.T
+
+ # Perform checks
+ assert_labels_dropped(df_levels, expected_labels, axis=axis)
+ assert_levels_dropped(df_levels, expected_levels, axis=axis)
+
+ with pytest.raises(ValueError, match="not valid labels or levels"):
+ df_levels._drop_labels_or_levels("L4", axis=axis)
+
+
+# Series
+# ------
+def test_drop_labels_or_levels_series(df):
+ # Make series with L1 as index
+ s = df.set_index("L1").L2
+ assert_levels_dropped(s, ["L1"], axis=0)
+
+ with pytest.raises(ValueError, match="not valid labels or levels"):
+ s._drop_labels_or_levels("L4", axis=0)
+
+ # Make series with L1 and L2 as index
+ s = df.set_index(["L1", "L2"]).L3
+ assert_levels_dropped(s, ["L1", "L2"], axis=0)
+
+ with pytest.raises(ValueError, match="not valid labels or levels"):
+ s._drop_labels_or_levels("L4", axis=0)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_series.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_series.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ea4b8be5cf91731107a41b107c28c9e137ab994
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_series.py
@@ -0,0 +1,119 @@
+from operator import methodcaller
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ MultiIndex,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestSeries:
+ @pytest.mark.parametrize("func", ["rename_axis", "_set_axis_name"])
+ def test_set_axis_name_mi(self, func):
+ ser = Series(
+ [11, 21, 31],
+ index=MultiIndex.from_tuples(
+ [("A", x) for x in ["a", "B", "c"]], names=["l1", "l2"]
+ ),
+ )
+
+ result = methodcaller(func, ["L1", "L2"])(ser)
+ assert ser.index.name is None
+ assert ser.index.names == ["l1", "l2"]
+ assert result.index.name is None
+ assert result.index.names, ["L1", "L2"]
+
+ def test_set_axis_name_raises(self):
+ ser = Series([1])
+ msg = "No axis named 1 for object type Series"
+ with pytest.raises(ValueError, match=msg):
+ ser._set_axis_name(name="a", axis=1)
+
+ def test_get_bool_data_preserve_dtype(self):
+ ser = Series([True, False, True])
+ result = ser._get_bool_data()
+ tm.assert_series_equal(result, ser)
+
+ @pytest.mark.parametrize("data", [np.nan, pd.NaT, True, False])
+ def test_nonzero_single_element_raise_1(self, data):
+ # single item nan to raise
+ series = Series([data])
+
+ msg = "The truth value of a Series is ambiguous"
+ with pytest.raises(ValueError, match=msg):
+ bool(series)
+
+ @pytest.mark.parametrize("data", [(True, True), (False, False)])
+ def test_nonzero_multiple_element_raise(self, data):
+ # multiple bool are still an error
+ msg_err = "The truth value of a Series is ambiguous"
+ series = Series([data])
+ with pytest.raises(ValueError, match=msg_err):
+ bool(series)
+
+ @pytest.mark.parametrize("data", [1, 0, "a", 0.0])
+ def test_nonbool_single_element_raise(self, data):
+ # single non-bool are an error
+ msg_err1 = "The truth value of a Series is ambiguous"
+ series = Series([data])
+ with pytest.raises(ValueError, match=msg_err1):
+ bool(series)
+
+ def test_metadata_propagation_indiv_resample(self):
+ # resample
+ ts = Series(
+ np.random.default_rng(2).random(1000),
+ index=date_range("20130101", periods=1000, freq="s"),
+ name="foo",
+ )
+ result = ts.resample("1min").mean()
+ tm.assert_metadata_equivalent(ts, result)
+
+ result = ts.resample("1min").min()
+ tm.assert_metadata_equivalent(ts, result)
+
+ result = ts.resample("1min").apply(lambda x: x.sum())
+ tm.assert_metadata_equivalent(ts, result)
+
+ def test_metadata_propagation_indiv(self, monkeypatch):
+ # check that the metadata matches up on the resulting ops
+
+ ser = Series(range(3), range(3))
+ ser.name = "foo"
+ ser2 = Series(range(3), range(3))
+ ser2.name = "bar"
+
+ result = ser.T
+ tm.assert_metadata_equivalent(ser, result)
+
+ def finalize(self, other, method=None, **kwargs):
+ for name in self._metadata:
+ if method == "concat" and name == "filename":
+ value = "+".join(
+ [
+ getattr(obj, name)
+ for obj in other.input_objs
+ if getattr(obj, name, None)
+ ]
+ )
+ object.__setattr__(self, name, value)
+ else:
+ object.__setattr__(self, name, getattr(other, name, None))
+
+ return self
+
+ with monkeypatch.context() as m:
+ m.setattr(Series, "_metadata", ["name", "filename"])
+ m.setattr(Series, "__finalize__", finalize)
+
+ ser.filename = "foo"
+ ser2.filename = "bar"
+
+ result = pd.concat([ser, ser2])
+ assert result.filename == "foo+bar"
+ assert result.name is None
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_to_xarray.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_to_xarray.py
new file mode 100644
index 0000000000000000000000000000000000000000..3aabdb6d7869a3f007fa50ad3adb56544d575077
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/generic/test_to_xarray.py
@@ -0,0 +1,126 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ DataFrame,
+ MultiIndex,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.util.version import Version
+
+xarray = pytest.importorskip("xarray")
+
+if xarray is not None and Version(xarray.__version__) < Version("2025.1.0"):
+ pytestmark = pytest.mark.filterwarnings(
+ "ignore:Converting non-nanosecond precision:UserWarning"
+ )
+
+
+class TestDataFrameToXArray:
+ @pytest.fixture
+ def df(self):
+ return DataFrame(
+ {
+ "a": list("abcd"),
+ "b": list(range(1, 5)),
+ "c": np.arange(3, 7).astype("u1"),
+ "d": np.arange(4.0, 8.0, dtype="float64"),
+ "e": [True, False, True, False],
+ "f": Categorical(list("abcd")),
+ "g": date_range("20130101", periods=4),
+ "h": date_range("20130101", periods=4, tz="US/Eastern"),
+ }
+ )
+
+ def test_to_xarray_index_types(self, index_flat, df, request):
+ index = index_flat
+ # MultiIndex is tested in test_to_xarray_with_multiindex
+ if len(index) == 0:
+ pytest.skip("Test doesn't make sense for empty index")
+ if Version(xarray.__version__) < Version("2025.9.0"):
+ pytest.skip("Xarray bug https://github.com/pydata/xarray/issues/9661")
+
+ df.index = index[:4]
+ df.index.name = "foo"
+ df.columns.name = "bar"
+ result = df.to_xarray()
+ assert result.sizes["foo"] == 4
+ assert len(result.coords) == 1
+ assert len(result.data_vars) == 8
+ tm.assert_almost_equal(list(result.coords.keys()), ["foo"])
+ assert isinstance(result, xarray.Dataset)
+
+ # idempotency
+ # datetimes w/tz are preserved
+ # column names are lost
+ expected = df.copy()
+ expected.columns.name = None
+ tm.assert_frame_equal(result.to_dataframe(), expected)
+
+ def test_to_xarray_empty(self, df):
+ df.index.name = "foo"
+ result = df[0:0].to_xarray()
+ assert result.sizes["foo"] == 0
+ assert isinstance(result, xarray.Dataset)
+
+ def test_to_xarray_with_multiindex(self, df, using_infer_string):
+ # MultiIndex
+ df.index = MultiIndex.from_product([["a"], range(4)], names=["one", "two"])
+ result = df.to_xarray()
+ assert result.sizes["one"] == 1
+ assert result.sizes["two"] == 4
+ assert len(result.coords) == 2
+ assert len(result.data_vars) == 8
+ tm.assert_almost_equal(list(result.coords.keys()), ["one", "two"])
+ assert isinstance(result, xarray.Dataset)
+
+ result = result.to_dataframe()
+ expected = df.copy()
+ expected["f"] = expected["f"].astype(
+ object if not using_infer_string else "str"
+ )
+ if Version(xarray.__version__) < Version("2025.1.0"):
+ expected["g"] = expected["g"].astype("M8[ns]")
+ expected["h"] = expected["h"].astype("M8[ns, US/Eastern]")
+ expected.columns.name = None
+ tm.assert_frame_equal(result, expected)
+
+
+class TestSeriesToXArray:
+ def test_to_xarray_index_types(self, index_flat, request):
+ # MultiIndex is tested in test_to_xarray_with_multiindex
+ index = index_flat
+
+ ser = Series(range(len(index)), index=index, dtype="int64")
+ ser.index.name = "foo"
+ result = ser.to_xarray()
+ repr(result)
+ assert len(result) == len(index)
+ assert len(result.coords) == 1
+ tm.assert_almost_equal(list(result.coords.keys()), ["foo"])
+ assert isinstance(result, xarray.DataArray)
+
+ # idempotency
+ tm.assert_series_equal(result.to_series(), ser)
+
+ def test_to_xarray_empty(self):
+ ser = Series([], dtype=object)
+ ser.index.name = "foo"
+ result = ser.to_xarray()
+ assert len(result) == 0
+ assert len(result.coords) == 1
+ tm.assert_almost_equal(list(result.coords.keys()), ["foo"])
+ assert isinstance(result, xarray.DataArray)
+
+ def test_to_xarray_with_multiindex(self):
+ mi = MultiIndex.from_product([["a", "b"], range(3)], names=["one", "two"])
+ ser = Series(range(6), dtype="int64", index=mi)
+ result = ser.to_xarray()
+ assert len(result) == 2
+ tm.assert_almost_equal(list(result.coords.keys()), ["one", "two"])
+ assert isinstance(result, xarray.DataArray)
+ res = result.to_series()
+ tm.assert_series_equal(res, ser)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..79046cd7ed415166e0c81ff645174015fc48eaf6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/__init__.py
@@ -0,0 +1,25 @@
+def get_groupby_method_args(name, obj):
+ """
+ Get required arguments for a groupby method.
+
+ When parametrizing a test over groupby methods (e.g. "sum", "mean"),
+ it is often the case that arguments are required for certain methods.
+
+ Parameters
+ ----------
+ name: str
+ Name of the method.
+ obj: Series or DataFrame
+ pandas object that is being grouped.
+
+ Returns
+ -------
+ A tuple of required arguments for the method.
+ """
+ if name in ("nth", "take"):
+ return (0,)
+ if name == "quantile":
+ return (0.5,)
+ if name == "corrwith":
+ return (obj,)
+ return ()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_aggregate.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_aggregate.py
new file mode 100644
index 0000000000000000000000000000000000000000..517d4bdd97f299dcba01d0e57318840d4af7cb7b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_aggregate.py
@@ -0,0 +1,1984 @@
+"""
+test .agg behavior / note that .apply is tested generally in test_groupby.py
+"""
+
+import datetime
+import functools
+from functools import partial
+
+import numpy as np
+import pytest
+
+from pandas.errors import SpecificationError
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.common import is_integer_dtype
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ concat,
+ to_datetime,
+)
+import pandas._testing as tm
+from pandas.arrays import ArrowExtensionArray
+from pandas.core.groupby.grouper import Grouping
+
+
+def test_groupby_agg_no_extra_calls():
+ # GH#31760
+ df = DataFrame({"key": ["a", "b", "c", "c"], "value": [1, 2, 3, 4]})
+ gb = df.groupby("key")["value"]
+
+ def dummy_func(x):
+ assert len(x) != 0
+ return x.sum()
+
+ gb.agg(dummy_func)
+
+
+def test_agg_regression1(tsframe):
+ grouped = tsframe.groupby([lambda x: x.year, lambda x: x.month])
+ result = grouped.agg("mean")
+ expected = grouped.mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_must_agg(df):
+ grouped = df.groupby("A")["C"]
+
+ msg = "Must produce aggregated value"
+ with pytest.raises(Exception, match=msg):
+ grouped.agg(lambda x: x.describe())
+ with pytest.raises(Exception, match=msg):
+ grouped.agg(lambda x: x.index[:2])
+
+
+def test_agg_ser_multi_key(df):
+ f = lambda x: x.sum()
+ results = df.C.groupby([df.A, df.B]).aggregate(f)
+ expected = df.groupby(["A", "B"]).sum()["C"]
+ tm.assert_series_equal(results, expected)
+
+
+def test_agg_with_missing_values():
+ # GH#58810
+ missing_df = DataFrame(
+ {
+ "nan": [np.nan, np.nan, np.nan, np.nan],
+ "na": [pd.NA, pd.NA, pd.NA, pd.NA],
+ "nat": [pd.NaT, pd.NaT, pd.NaT, pd.NaT],
+ "none": [None, None, None, None],
+ "values": [1, 2, 3, 4],
+ }
+ )
+
+ result = missing_df.agg(x=("nan", "min"), y=("na", "min"), z=("values", "sum"))
+
+ expected = DataFrame(
+ {
+ "nan": [np.nan, np.nan, np.nan],
+ "na": [np.nan, np.nan, np.nan],
+ "values": [np.nan, np.nan, 10.0],
+ },
+ index=["x", "y", "z"],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregation_mixed_dtype():
+ # GH 6212
+ expected = DataFrame(
+ {
+ "v1": [5, 5, 7, np.nan, 3, 3, 4, 1],
+ "v2": [55, 55, 77, np.nan, 33, 33, 44, 11],
+ },
+ index=MultiIndex.from_tuples(
+ [
+ (1, 95),
+ (1, 99),
+ (2, 95),
+ (2, 99),
+ ("big", "damp"),
+ ("blue", "dry"),
+ ("red", "red"),
+ ("red", "wet"),
+ ],
+ names=["by1", "by2"],
+ ),
+ )
+
+ df = DataFrame(
+ {
+ "v1": [1, 3, 5, 7, 8, 3, 5, np.nan, 4, 5, 7, 9],
+ "v2": [11, 33, 55, 77, 88, 33, 55, np.nan, 44, 55, 77, 99],
+ "by1": ["red", "blue", 1, 2, np.nan, "big", 1, 2, "red", 1, np.nan, 12],
+ "by2": [
+ "wet",
+ "dry",
+ 99,
+ 95,
+ np.nan,
+ "damp",
+ 95,
+ 99,
+ "red",
+ 99,
+ np.nan,
+ np.nan,
+ ],
+ }
+ )
+
+ g = df.groupby(["by1", "by2"])
+ result = g[["v1", "v2"]].mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_apply_corner(ts, tsframe):
+ # nothing to group, all NA
+ grouped = ts.groupby(ts * np.nan, group_keys=False)
+ assert ts.dtype == np.float64
+
+ # groupby float64 values results in a float64 Index
+ exp = Series([], dtype=np.float64, index=Index([], dtype=np.float64))
+ tm.assert_series_equal(grouped.sum(), exp)
+ tm.assert_series_equal(grouped.agg("sum"), exp)
+ tm.assert_series_equal(grouped.apply("sum"), exp, check_index_type=False)
+
+ # DataFrame
+ grouped = tsframe.groupby(tsframe["A"] * np.nan, group_keys=False)
+ exp_df = DataFrame(
+ columns=tsframe.columns,
+ dtype=float,
+ index=Index([], name="A", dtype=np.float64),
+ )
+ tm.assert_frame_equal(grouped.sum(), exp_df)
+ tm.assert_frame_equal(grouped.agg("sum"), exp_df)
+
+ res = grouped.apply(np.sum, axis=0)
+ exp_df = exp_df.reset_index(drop=True)
+ tm.assert_frame_equal(res, exp_df)
+
+
+def test_with_na_groups(any_real_numpy_dtype):
+ index = Index(np.arange(10))
+ values = Series(np.ones(10), index, dtype=any_real_numpy_dtype)
+ labels = Series(
+ [np.nan, "foo", "bar", "bar", np.nan, np.nan, "bar", "bar", np.nan, "foo"],
+ index=index,
+ )
+
+ # this SHOULD be an int
+ grouped = values.groupby(labels)
+ agged = grouped.agg(len)
+ expected = Series([4, 2], index=["bar", "foo"])
+
+ tm.assert_series_equal(agged, expected, check_dtype=False)
+
+ # assert issubclass(agged.dtype.type, np.integer)
+
+ # explicitly return a float from my function
+ def f(x):
+ return float(len(x))
+
+ agged = grouped.agg(f)
+ expected = Series([4.0, 2.0], index=["bar", "foo"])
+ if values.dtype == np.float32:
+ expected = expected.astype(np.float32)
+
+ tm.assert_series_equal(agged, expected)
+
+
+def test_agg_grouping_is_list_tuple(ts):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=pd.date_range("2000-01-01", periods=30, freq="B"),
+ )
+
+ grouped = df.groupby(lambda x: x.year)
+ grouper = grouped._grouper.groupings[0].grouping_vector
+ grouped._grouper.groupings[0] = Grouping(ts.index, list(grouper))
+
+ result = grouped.agg("mean")
+ expected = grouped.mean()
+ tm.assert_frame_equal(result, expected)
+
+ grouped._grouper.groupings[0] = Grouping(ts.index, tuple(grouper))
+
+ result = grouped.agg("mean")
+ expected = grouped.mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_python_multiindex(multiindex_dataframe_random_data):
+ grouped = multiindex_dataframe_random_data.groupby(["A", "B"])
+
+ result = grouped.agg("mean")
+ expected = grouped.mean()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "groupbyfunc", [lambda x: x.weekday(), [lambda x: x.month, lambda x: x.weekday()]]
+)
+def test_aggregate_str_func(tsframe, groupbyfunc):
+ grouped = tsframe.groupby(groupbyfunc)
+
+ # single series
+ result = grouped["A"].agg("std")
+ expected = grouped["A"].std()
+ tm.assert_series_equal(result, expected)
+
+ # group frame by function name
+ result = grouped.aggregate("var")
+ expected = grouped.var()
+ tm.assert_frame_equal(result, expected)
+
+ # group frame by function dict
+ result = grouped.agg({"A": "var", "B": "std", "C": "mean", "D": "sem"})
+ expected = DataFrame(
+ {
+ "A": grouped["A"].var(),
+ "B": grouped["B"].std(),
+ "C": grouped["C"].mean(),
+ "D": grouped["D"].sem(),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_std_masked_dtype(any_numeric_ea_dtype):
+ # GH#35516
+ df = DataFrame(
+ {
+ "a": [2, 1, 1, 1, 2, 2, 1],
+ "b": Series([pd.NA, 1, 2, 1, 1, 1, 2], dtype="Float64"),
+ }
+ )
+ result = df.groupby("a").std()
+ expected = DataFrame(
+ {"b": [0.57735, 0]}, index=Index([1, 2], name="a"), dtype="Float64"
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_str_with_kwarg_axis_1_raises(df, reduction_func):
+ gb = df.groupby(level=0)
+ msg = f"Operation {reduction_func} does not support axis=1"
+ with pytest.raises(ValueError, match=msg):
+ gb.agg(reduction_func, axis=1)
+
+
+def test_aggregate_item_by_item(df):
+ grouped = df.groupby("A")
+
+ aggfun_0 = lambda ser: ser.size
+ result = grouped.agg(aggfun_0)
+ foosum = (df.A == "foo").sum()
+ barsum = (df.A == "bar").sum()
+ K = len(result.columns)
+
+ # GH5782
+ exp = Series(np.array([foosum] * K), index=list("BCD"), name="foo")
+ tm.assert_series_equal(result.xs("foo"), exp)
+
+ exp = Series(np.array([barsum] * K), index=list("BCD"), name="bar")
+ tm.assert_almost_equal(result.xs("bar"), exp)
+
+ def aggfun_1(ser):
+ return ser.size
+
+ result = DataFrame().groupby(df.A).agg(aggfun_1)
+ assert isinstance(result, DataFrame)
+ assert len(result) == 0
+
+
+def test_wrap_agg_out(three_group):
+ grouped = three_group.groupby(["A", "B"])
+
+ def func(ser):
+ if ser.dtype in (object, "string"):
+ raise TypeError("Test error message")
+ return ser.sum()
+
+ with pytest.raises(TypeError, match="Test error message"):
+ grouped.aggregate(func)
+ result = grouped[["D", "E", "F"]].aggregate(func)
+ exp_grouped = three_group.loc[:, ["A", "B", "D", "E", "F"]]
+ expected = exp_grouped.groupby(["A", "B"]).aggregate(func)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_multiple_functions_maintain_order(df):
+ # GH #610
+ funcs = [("mean", np.mean), ("max", np.max), ("min", np.min)]
+ result = df.groupby("A")["C"].agg(funcs)
+ exp_cols = Index(["mean", "max", "min"])
+
+ tm.assert_index_equal(result.columns, exp_cols)
+
+
+def test_series_index_name(df):
+ grouped = df.loc[:, ["C"]].groupby(df["A"])
+ result = grouped.agg(lambda x: x.mean())
+ assert result.index.name == "A"
+
+
+def test_agg_multiple_functions_same_name():
+ # GH 30880
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((1000, 3)),
+ index=pd.date_range("1/1/2012", freq="s", periods=1000),
+ columns=["A", "B", "C"],
+ )
+ result = df.resample("3min").agg(
+ {"A": [partial(np.quantile, q=0.9999), partial(np.quantile, q=0.1111)]}
+ )
+ expected_index = pd.date_range("1/1/2012", freq="3min", periods=6)
+ expected_columns = MultiIndex.from_tuples([("A", "quantile"), ("A", "quantile")])
+ expected_values = np.array(
+ [df.resample("3min").A.quantile(q=q).values for q in [0.9999, 0.1111]]
+ ).T
+ expected = DataFrame(
+ expected_values, columns=expected_columns, index=expected_index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_multiple_functions_same_name_with_ohlc_present():
+ # GH 30880
+ # ohlc expands dimensions, so different test to the above is required.
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((1000, 3)),
+ index=pd.date_range("1/1/2012", freq="s", periods=1000, name="dti"),
+ columns=Index(["A", "B", "C"], name="alpha"),
+ )
+ result = df.resample("3min").agg(
+ {"A": ["ohlc", partial(np.quantile, q=0.9999), partial(np.quantile, q=0.1111)]}
+ )
+ expected_index = pd.date_range("1/1/2012", freq="3min", periods=6, name="dti")
+ expected_columns = MultiIndex.from_tuples(
+ [
+ ("A", "ohlc", "open"),
+ ("A", "ohlc", "high"),
+ ("A", "ohlc", "low"),
+ ("A", "ohlc", "close"),
+ ("A", "quantile", "A"),
+ ("A", "quantile", "A"),
+ ],
+ names=["alpha", None, None],
+ )
+ non_ohlc_expected_values = np.array(
+ [df.resample("3min").A.quantile(q=q).values for q in [0.9999, 0.1111]]
+ ).T
+ expected_values = np.hstack(
+ [df.resample("3min").A.ohlc(), non_ohlc_expected_values]
+ )
+ expected = DataFrame(
+ expected_values, columns=expected_columns, index=expected_index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_multiple_functions_tuples_and_non_tuples(df):
+ # #1359
+ # Columns B and C would cause partial failure
+ df = df.drop(columns=["B", "C"])
+
+ funcs = [("foo", "mean"), "std"]
+ ex_funcs = [("foo", "mean"), ("std", "std")]
+
+ result = df.groupby("A")["D"].agg(funcs)
+ expected = df.groupby("A")["D"].agg(ex_funcs)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("A").agg(funcs)
+ expected = df.groupby("A").agg(ex_funcs)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_more_flexible_frame_multi_function(df):
+ grouped = df.groupby("A")
+
+ exmean = grouped.agg({"C": "mean", "D": "mean"})
+ exstd = grouped.agg({"C": "std", "D": "std"})
+
+ expected = concat([exmean, exstd], keys=["mean", "std"], axis=1)
+ expected = expected.swaplevel(0, 1, axis=1).sort_index(level=0, axis=1)
+
+ d = {"C": ["mean", "std"], "D": ["mean", "std"]}
+ result = grouped.aggregate(d)
+
+ tm.assert_frame_equal(result, expected)
+
+ # be careful
+ result = grouped.aggregate({"C": "mean", "D": ["mean", "std"]})
+ expected = grouped.aggregate({"C": "mean", "D": ["mean", "std"]})
+ tm.assert_frame_equal(result, expected)
+
+ def numpymean(x):
+ return np.mean(x)
+
+ def numpystd(x):
+ return np.std(x, ddof=1)
+
+ # this uses column selection & renaming
+ msg = r"nested renamer is not supported"
+ d = {"C": "mean", "D": {"foo": "mean", "bar": "std"}}
+ with pytest.raises(SpecificationError, match=msg):
+ grouped.aggregate(d)
+
+ # But without renaming, these functions are OK
+ d = {"C": ["mean"], "D": [numpymean, numpystd]}
+ grouped.aggregate(d)
+
+
+def test_multi_function_flexible_mix(df):
+ # GH #1268
+ grouped = df.groupby("A")
+
+ # Expected
+ d = {"C": {"foo": "mean", "bar": "std"}, "D": {"sum": "sum"}}
+ # this uses column selection & renaming
+ msg = r"nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ grouped.aggregate(d)
+
+ # Test 1
+ d = {"C": {"foo": "mean", "bar": "std"}, "D": "sum"}
+ # this uses column selection & renaming
+ with pytest.raises(SpecificationError, match=msg):
+ grouped.aggregate(d)
+
+ # Test 2
+ d = {"C": {"foo": "mean", "bar": "std"}, "D": "sum"}
+ # this uses column selection & renaming
+ with pytest.raises(SpecificationError, match=msg):
+ grouped.aggregate(d)
+
+
+def test_groupby_agg_coercing_bools():
+ # issue 14873
+ dat = DataFrame({"a": [1, 1, 2, 2], "b": [0, 1, 2, 3], "c": [None, None, 1, 1]})
+ gp = dat.groupby("a")
+
+ index = Index([1, 2], name="a")
+
+ result = gp["b"].aggregate(lambda x: (x != 0).all())
+ expected = Series([False, True], index=index, name="b")
+ tm.assert_series_equal(result, expected)
+
+ result = gp["c"].aggregate(lambda x: x.isnull().all())
+ expected = Series([True, False], index=index, name="c")
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_agg_dict_with_getitem():
+ # issue 25471
+ dat = DataFrame({"A": ["A", "A", "B", "B", "B"], "B": [1, 2, 1, 1, 2]})
+ result = dat.groupby("A")[["B"]].agg({"B": "sum"})
+
+ expected = DataFrame({"B": [3, 4]}, index=["A", "B"]).rename_axis("A", axis=0)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_agg_dict_dup_columns():
+ # GH#55006
+ df = DataFrame(
+ [[1, 2, 3, 4], [1, 3, 4, 5], [2, 4, 5, 6]],
+ columns=["a", "b", "c", "c"],
+ )
+ gb = df.groupby("a")
+ result = gb.agg({"b": "sum"})
+ expected = DataFrame({"b": [5, 4]}, index=Index([1, 2], name="a"))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "op",
+ [
+ lambda x: x.sum(),
+ lambda x: x.cumsum(),
+ lambda x: x.transform("sum"),
+ lambda x: x.transform("cumsum"),
+ lambda x: x.agg("sum"),
+ lambda x: x.agg("cumsum"),
+ ],
+)
+def test_bool_agg_dtype(op):
+ # GH 7001
+ # Bool sum aggregations result in int
+ df = DataFrame({"a": [1, 1], "b": [False, True]})
+ s = df.set_index("a")["b"]
+
+ result = op(df.groupby("a"))["b"].dtype
+ assert is_integer_dtype(result)
+
+ result = op(s.groupby("a")).dtype
+ assert is_integer_dtype(result)
+
+
+@pytest.mark.parametrize(
+ "keys, agg_index",
+ [
+ (["a"], Index([1], name="a")),
+ (["a", "b"], MultiIndex([[1], [2]], [[0], [0]], names=["a", "b"])),
+ ],
+)
+@pytest.mark.parametrize(
+ "input_dtype", ["bool", "int32", "int64", "float32", "float64"]
+)
+@pytest.mark.parametrize(
+ "result_dtype", ["bool", "int32", "int64", "float32", "float64"]
+)
+@pytest.mark.parametrize("method", ["apply", "aggregate", "transform"])
+def test_callable_result_dtype_frame(
+ keys, agg_index, input_dtype, result_dtype, method
+):
+ # GH 21240
+ df = DataFrame({"a": [1], "b": [2], "c": [True]})
+ df["c"] = df["c"].astype(input_dtype)
+ op = getattr(df.groupby(keys)[["c"]], method)
+ result = op(lambda x: x.astype(result_dtype).iloc[0])
+ expected_index = pd.RangeIndex(0, 1) if method == "transform" else agg_index
+
+ if method == "aggregate":
+ # _cast_pointwise_result retains the input's dtype where feasible
+ if input_dtype == "float32" and result_dtype == "float64":
+ result_dtype = "float32"
+ if input_dtype == "int32" and result_dtype == "int64":
+ result_dtype = "int32"
+
+ expected = DataFrame({"c": [df["c"].iloc[0]]}, index=expected_index).astype(
+ result_dtype
+ )
+ if method == "apply":
+ expected.columns.names = [0]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "keys, agg_index",
+ [
+ (["a"], Index([1], name="a")),
+ (["a", "b"], MultiIndex([[1], [2]], [[0], [0]], names=["a", "b"])),
+ ],
+)
+@pytest.mark.parametrize("input", [True, 1, 1.0])
+@pytest.mark.parametrize("dtype", [bool, int, float])
+@pytest.mark.parametrize("method", ["apply", "aggregate", "transform"])
+def test_callable_result_dtype_series(keys, agg_index, input, dtype, method):
+ # GH 21240
+ df = DataFrame({"a": [1], "b": [2], "c": [input]})
+ op = getattr(df.groupby(keys)["c"], method)
+ result = op(lambda x: x.astype(dtype).iloc[0])
+ expected_index = pd.RangeIndex(0, 1) if method == "transform" else agg_index
+ expected = Series([df["c"].iloc[0]], index=expected_index, name="c").astype(dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_order_aggregate_multiple_funcs():
+ # GH 25692
+ df = DataFrame({"A": [1, 1, 2, 2], "B": [1, 2, 3, 4]})
+
+ res = df.groupby("A").agg(["sum", "max", "mean", "ohlc", "min"])
+ result = res.columns.levels[1]
+
+ expected = Index(["sum", "max", "mean", "ohlc", "min"])
+
+ tm.assert_index_equal(result, expected)
+
+
+def test_ohlc_ea_dtypes(any_numeric_ea_dtype):
+ # GH#37493
+ df = DataFrame(
+ {"a": [1, 1, 2, 3, 4, 4], "b": [22, 11, pd.NA, 10, 20, pd.NA]},
+ dtype=any_numeric_ea_dtype,
+ )
+ gb = df.groupby("a")
+ result = gb.ohlc()
+ expected = DataFrame(
+ [[22, 22, 11, 11], [pd.NA] * 4, [10] * 4, [20] * 4],
+ columns=MultiIndex.from_product([["b"], ["open", "high", "low", "close"]]),
+ index=Index([1, 2, 3, 4], dtype=any_numeric_ea_dtype, name="a"),
+ dtype=any_numeric_ea_dtype,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ gb2 = df.groupby("a", as_index=False)
+ result2 = gb2.ohlc()
+ expected2 = expected.reset_index()
+ tm.assert_frame_equal(result2, expected2)
+
+
+@pytest.mark.parametrize("dtype", [np.int64, np.uint64])
+@pytest.mark.parametrize("how", ["first", "last", "min", "max", "mean", "median"])
+def test_uint64_type_handling(dtype, how):
+ # GH 26310
+ df = DataFrame({"x": 6903052872240755750, "y": [1, 2]})
+ expected = df.groupby("y").agg({"x": how})
+ df.x = df.x.astype(dtype)
+ result = df.groupby("y").agg({"x": how})
+ if how not in ("mean", "median"):
+ # mean and median always result in floats
+ result.x = result.x.astype(np.int64)
+ tm.assert_frame_equal(result, expected, check_exact=True)
+
+
+def test_func_duplicates_raises():
+ # GH28426
+ msg = "Function names"
+ df = DataFrame({"A": [0, 0, 1, 1], "B": [1, 2, 3, 4]})
+ with pytest.raises(SpecificationError, match=msg):
+ df.groupby("A").agg(["min", "min"])
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ pd.CategoricalIndex(list("abc")),
+ pd.interval_range(0, 3),
+ pd.period_range("2020", periods=3, freq="D"),
+ MultiIndex.from_tuples([("a", 0), ("a", 1), ("b", 0)]),
+ ],
+)
+def test_agg_index_has_complex_internals(index):
+ # GH 31223
+ df = DataFrame({"group": [1, 1, 2], "value": [0, 1, 0]}, index=index)
+ result = df.groupby("group").agg({"value": Series.nunique})
+ expected = DataFrame({"group": [1, 2], "value": [2, 1]}).set_index("group")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_split_block():
+ # https://github.com/pandas-dev/pandas/issues/31522
+ df = DataFrame(
+ {
+ "key1": ["a", "a", "b", "b", "a"],
+ "key2": ["one", "two", "one", "two", "one"],
+ "key3": ["three", "three", "three", "six", "six"],
+ }
+ )
+ result = df.groupby("key1").min()
+ expected = DataFrame(
+ {"key2": ["one", "one"], "key3": ["six", "six"]},
+ index=Index(["a", "b"], name="key1"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_split_object_part_datetime():
+ # https://github.com/pandas-dev/pandas/pull/31616
+ df = DataFrame(
+ {
+ "A": pd.date_range("2000", periods=4),
+ "B": ["a", "b", "c", "d"],
+ "C": [1, 2, 3, 4],
+ "D": ["b", "c", "d", "e"],
+ "E": pd.date_range("2000", periods=4),
+ "F": [1, 2, 3, 4],
+ }
+ ).astype(object)
+ result = df.groupby([0, 0, 0, 0]).min()
+ expected = DataFrame(
+ {
+ "A": [pd.Timestamp("2000")],
+ "B": ["a"],
+ "C": [1],
+ "D": ["b"],
+ "E": [pd.Timestamp("2000")],
+ "F": [1],
+ },
+ index=np.array([0]),
+ dtype=object,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+class TestNamedAggregationSeries:
+ def test_series_named_agg(self):
+ df = Series([1, 2, 3, 4])
+ gr = df.groupby([0, 0, 1, 1])
+ result = gr.agg(a="sum", b="min")
+ expected = DataFrame(
+ {"a": [3, 7], "b": [1, 3]}, columns=["a", "b"], index=np.array([0, 1])
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = gr.agg(b="min", a="sum")
+ expected = expected[["b", "a"]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_no_args_raises(self):
+ gr = Series([1, 2]).groupby([0, 1])
+ with pytest.raises(TypeError, match="Must provide"):
+ gr.agg()
+
+ # but we do allow this
+ result = gr.agg([])
+ expected = DataFrame(columns=[])
+ tm.assert_frame_equal(result, expected)
+
+ def test_series_named_agg_duplicates_no_raises(self):
+ # GH28426
+ gr = Series([1, 2, 3]).groupby([0, 0, 1])
+ grouped = gr.agg(a="sum", b="sum")
+ expected = DataFrame({"a": [3, 3], "b": [3, 3]}, index=np.array([0, 1]))
+ tm.assert_frame_equal(expected, grouped)
+
+ def test_mangled(self):
+ gr = Series([1, 2, 3]).groupby([0, 0, 1])
+ result = gr.agg(a=lambda x: 0, b=lambda x: 1)
+ expected = DataFrame({"a": [0, 0], "b": [1, 1]}, index=np.array([0, 1]))
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "inp",
+ [
+ pd.NamedAgg(column="anything", aggfunc="min"),
+ ("anything", "min"),
+ ["anything", "min"],
+ ],
+ )
+ def test_named_agg_nametuple(self, inp):
+ # GH34422
+ s = Series([1, 1, 2, 2, 3, 3, 4, 5])
+ msg = f"func is expected but received {type(inp).__name__}"
+ with pytest.raises(TypeError, match=msg):
+ s.groupby(s.values).agg(a=inp)
+
+
+class TestNamedAggregationDataFrame:
+ def test_agg_relabel(self):
+ df = DataFrame(
+ {"group": ["a", "a", "b", "b"], "A": [0, 1, 2, 3], "B": [5, 6, 7, 8]}
+ )
+ result = df.groupby("group").agg(a_max=("A", "max"), b_max=("B", "max"))
+ expected = DataFrame(
+ {"a_max": [1, 3], "b_max": [6, 8]},
+ index=Index(["a", "b"], name="group"),
+ columns=["a_max", "b_max"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # order invariance
+ p98 = functools.partial(np.percentile, q=98)
+ result = df.groupby("group").agg(
+ b_min=("B", "min"),
+ a_min=("A", "min"),
+ a_mean=("A", "mean"),
+ a_max=("A", "max"),
+ b_max=("B", "max"),
+ a_98=("A", p98),
+ )
+ expected = DataFrame(
+ {
+ "b_min": [5, 7],
+ "a_min": [0, 2],
+ "a_mean": [0.5, 2.5],
+ "a_max": [1, 3],
+ "b_max": [6, 8],
+ "a_98": [0.98, 2.98],
+ },
+ index=Index(["a", "b"], name="group"),
+ columns=["b_min", "a_min", "a_mean", "a_max", "b_max", "a_98"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_agg_relabel_non_identifier(self):
+ df = DataFrame(
+ {"group": ["a", "a", "b", "b"], "A": [0, 1, 2, 3], "B": [5, 6, 7, 8]}
+ )
+
+ result = df.groupby("group").agg(**{"my col": ("A", "max")})
+ expected = DataFrame({"my col": [1, 3]}, index=Index(["a", "b"], name="group"))
+ tm.assert_frame_equal(result, expected)
+
+ def test_duplicate_no_raises(self):
+ # GH 28426, if use same input function on same column,
+ # no error should raise
+ df = DataFrame({"A": [0, 0, 1, 1], "B": [1, 2, 3, 4]})
+
+ grouped = df.groupby("A").agg(a=("B", "min"), b=("B", "min"))
+ expected = DataFrame({"a": [1, 3], "b": [1, 3]}, index=Index([0, 1], name="A"))
+ tm.assert_frame_equal(grouped, expected)
+
+ quant50 = functools.partial(np.percentile, q=50)
+ quant70 = functools.partial(np.percentile, q=70)
+ quant50.__name__ = "quant50"
+ quant70.__name__ = "quant70"
+
+ test = DataFrame({"col1": ["a", "a", "b", "b", "b"], "col2": [1, 2, 3, 4, 5]})
+
+ grouped = test.groupby("col1").agg(
+ quantile_50=("col2", quant50), quantile_70=("col2", quant70)
+ )
+ expected = DataFrame(
+ {"quantile_50": [1.5, 4.0], "quantile_70": [1.7, 4.4]},
+ index=Index(["a", "b"], name="col1"),
+ )
+ tm.assert_frame_equal(grouped, expected)
+
+ def test_agg_relabel_with_level(self):
+ df = DataFrame(
+ {"A": [0, 0, 1, 1], "B": [1, 2, 3, 4]},
+ index=MultiIndex.from_product([["A", "B"], ["a", "b"]]),
+ )
+ result = df.groupby(level=0).agg(
+ aa=("A", "max"), bb=("A", "min"), cc=("B", "mean")
+ )
+ expected = DataFrame(
+ {"aa": [0, 1], "bb": [0, 1], "cc": [1.5, 3.5]}, index=["A", "B"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_agg_relabel_other_raises(self):
+ df = DataFrame({"A": [0, 0, 1], "B": [1, 2, 3]})
+ grouped = df.groupby("A")
+ match = "Must provide"
+ with pytest.raises(TypeError, match=match):
+ grouped.agg(foo=1)
+
+ with pytest.raises(TypeError, match=match):
+ grouped.agg()
+
+ with pytest.raises(TypeError, match=match):
+ grouped.agg(a=("B", "max"), b=(1, 2, 3))
+
+ def test_missing_raises(self):
+ df = DataFrame({"A": [0, 1], "B": [1, 2]})
+ msg = r"Label\(s\) \['C'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ df.groupby("A").agg(c=("C", "sum"))
+
+ def test_agg_namedtuple(self):
+ df = DataFrame({"A": [0, 1], "B": [1, 2]})
+ result = df.groupby("A").agg(
+ b=pd.NamedAgg("B", "sum"), c=pd.NamedAgg(column="B", aggfunc="count")
+ )
+ expected = df.groupby("A").agg(b=("B", "sum"), c=("B", "count"))
+ tm.assert_frame_equal(result, expected)
+
+ def n_between(self, ser, low, high, **kwargs):
+ return ser.between(low, high, **kwargs).sum()
+
+ def test_namedagg_args(self):
+ # https://github.com/pandas-dev/pandas/issues/58283
+ df = DataFrame({"A": [0, 0, 1, 1], "B": [-1, 0, 1, 2]})
+
+ result = df.groupby("A").agg(
+ count_between=pd.NamedAgg("B", self.n_between, 0, 1)
+ )
+ expected = DataFrame({"count_between": [1, 1]}, index=Index([0, 1], name="A"))
+ tm.assert_frame_equal(result, expected)
+
+ def test_namedagg_kwargs(self):
+ # https://github.com/pandas-dev/pandas/issues/58283
+ df = DataFrame({"A": [0, 0, 1, 1], "B": [-1, 0, 1, 2]})
+
+ result = df.groupby("A").agg(
+ count_between_kw=pd.NamedAgg("B", self.n_between, 0, 1, inclusive="both")
+ )
+ expected = DataFrame(
+ {"count_between_kw": [1, 1]}, index=Index([0, 1], name="A")
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_namedagg_args_and_kwargs(self):
+ # https://github.com/pandas-dev/pandas/issues/58283
+ df = DataFrame({"A": [0, 0, 1, 1], "B": [-1, 0, 1, 2]})
+
+ result = df.groupby("A").agg(
+ count_between_mix=pd.NamedAgg(
+ "B", self.n_between, 0, 1, inclusive="neither"
+ )
+ )
+ expected = DataFrame(
+ {"count_between_mix": [0, 0]}, index=Index([0, 1], name="A")
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_multiple_named_agg_with_args_and_kwargs(self):
+ # https://github.com/pandas-dev/pandas/issues/58283
+ df = DataFrame({"A": [0, 1, 2, 3], "B": [1, 2, 3, 4]})
+
+ result = df.groupby("A").agg(
+ n_between01=pd.NamedAgg("B", self.n_between, 0, 1),
+ n_between13=pd.NamedAgg("B", self.n_between, 1, 3),
+ n_between02=pd.NamedAgg("B", self.n_between, 0, 2),
+ )
+ expected = DataFrame(
+ {
+ "n_between01": [1, 0, 0, 0],
+ "n_between13": [1, 1, 1, 0],
+ "n_between02": [1, 1, 0, 0],
+ },
+ index=Index([0, 1, 2, 3], name="A"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_mangled(self):
+ df = DataFrame({"A": [0, 1], "B": [1, 2], "C": [3, 4]})
+ result = df.groupby("A").agg(b=("B", lambda x: 0), c=("C", lambda x: 1))
+ expected = DataFrame({"b": [0, 0], "c": [1, 1]}, index=Index([0, 1], name="A"))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "agg_col1, agg_col2, agg_col3, agg_result1, agg_result2, agg_result3",
+ [
+ (
+ (("y", "A"), "max"),
+ (("y", "A"), np.mean),
+ (("y", "B"), "mean"),
+ [1, 3],
+ [0.5, 2.5],
+ [5.5, 7.5],
+ ),
+ (
+ (("y", "A"), lambda x: max(x)),
+ (("y", "A"), lambda x: 1),
+ (("y", "B"), np.mean),
+ [1, 3],
+ [1, 1],
+ [5.5, 7.5],
+ ),
+ (
+ pd.NamedAgg(("y", "A"), "max"),
+ pd.NamedAgg(("y", "B"), np.mean),
+ pd.NamedAgg(("y", "A"), lambda x: 1),
+ [1, 3],
+ [5.5, 7.5],
+ [1, 1],
+ ),
+ ],
+)
+def test_agg_relabel_multiindex_column(
+ agg_col1, agg_col2, agg_col3, agg_result1, agg_result2, agg_result3
+):
+ # GH 29422, add tests for multiindex column cases
+ df = DataFrame(
+ {"group": ["a", "a", "b", "b"], "A": [0, 1, 2, 3], "B": [5, 6, 7, 8]}
+ )
+ df.columns = MultiIndex.from_tuples([("x", "group"), ("y", "A"), ("y", "B")])
+ idx = Index(["a", "b"], name=("x", "group"))
+
+ result = df.groupby(("x", "group")).agg(a_max=(("y", "A"), "max"))
+ expected = DataFrame({"a_max": [1, 3]}, index=idx)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(("x", "group")).agg(
+ col_1=agg_col1, col_2=agg_col2, col_3=agg_col3
+ )
+ expected = DataFrame(
+ {"col_1": agg_result1, "col_2": agg_result2, "col_3": agg_result3}, index=idx
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_relabel_multiindex_raises_not_exist():
+ # GH 29422, add test for raises scenario when aggregate column does not exist
+ df = DataFrame(
+ {"group": ["a", "a", "b", "b"], "A": [0, 1, 2, 3], "B": [5, 6, 7, 8]}
+ )
+ df.columns = MultiIndex.from_tuples([("x", "group"), ("y", "A"), ("y", "B")])
+
+ with pytest.raises(KeyError, match="do not exist"):
+ df.groupby(("x", "group")).agg(a=(("Y", "a"), "max"))
+
+
+def test_agg_relabel_multiindex_duplicates():
+ # GH29422, add test for raises scenario when getting duplicates
+ # GH28426, after this change, duplicates should also work if the relabelling is
+ # different
+ df = DataFrame(
+ {"group": ["a", "a", "b", "b"], "A": [0, 1, 2, 3], "B": [5, 6, 7, 8]}
+ )
+ df.columns = MultiIndex.from_tuples([("x", "group"), ("y", "A"), ("y", "B")])
+
+ result = df.groupby(("x", "group")).agg(
+ a=(("y", "A"), "min"), b=(("y", "A"), "min")
+ )
+ idx = Index(["a", "b"], name=("x", "group"))
+ expected = DataFrame({"a": [0, 2], "b": [0, 2]}, index=idx)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("kwargs", [{"c": ["min"]}, {"b": [], "c": ["min"]}])
+def test_groupby_aggregate_empty_key(kwargs):
+ # GH: 32580
+ df = DataFrame({"a": [1, 1, 2], "b": [1, 2, 3], "c": [1, 2, 4]})
+ result = df.groupby("a").agg(kwargs)
+ expected = DataFrame(
+ [1, 4],
+ index=Index([1, 2], dtype="int64", name="a"),
+ columns=MultiIndex.from_tuples([["c", "min"]]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregate_empty_key_empty_return():
+ # GH: 32580 Check if everything works, when return is empty
+ df = DataFrame({"a": [1, 1, 2], "b": [1, 2, 3], "c": [1, 2, 4]})
+ result = df.groupby("a").agg({"b": []})
+ expected = DataFrame(columns=MultiIndex(levels=[["b"], []], codes=[[], []]))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregate_empty_with_multiindex_frame():
+ # GH 39178
+ df = DataFrame(columns=["a", "b", "c"])
+ result = df.groupby(["a", "b"], group_keys=False).agg(d=("c", list))
+ expected = DataFrame(
+ columns=["d"], index=MultiIndex([[], []], [[], []], names=["a", "b"])
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_grouby_agg_loses_results_with_as_index_false_relabel():
+ # GH 32240: When the aggregate function relabels column names and
+ # as_index=False is specified, the results are dropped.
+
+ df = DataFrame(
+ {"key": ["x", "y", "z", "x", "y", "z"], "val": [1.0, 0.8, 2.0, 3.0, 3.6, 0.75]}
+ )
+
+ grouped = df.groupby("key", as_index=False)
+ result = grouped.agg(min_val=pd.NamedAgg(column="val", aggfunc="min"))
+ expected = DataFrame({"key": ["x", "y", "z"], "min_val": [1.0, 0.8, 0.75]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_grouby_agg_loses_results_with_as_index_false_relabel_multiindex():
+ # GH 32240: When the aggregate function relabels column names and
+ # as_index=False is specified, the results are dropped. Check if
+ # multiindex is returned in the right order
+
+ df = DataFrame(
+ {
+ "key": ["x", "y", "x", "y", "x", "x"],
+ "key1": ["a", "b", "c", "b", "a", "c"],
+ "val": [1.0, 0.8, 2.0, 3.0, 3.6, 0.75],
+ }
+ )
+
+ grouped = df.groupby(["key", "key1"], as_index=False)
+ result = grouped.agg(min_val=pd.NamedAgg(column="val", aggfunc="min"))
+ expected = DataFrame(
+ {"key": ["x", "x", "y"], "key1": ["a", "c", "b"], "min_val": [1.0, 0.75, 0.8]}
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_as_index_agg(df):
+ grouped = df.groupby("A", as_index=False)
+
+ # single-key
+
+ result = grouped[["C", "D"]].agg("mean")
+ expected = grouped.mean(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+ result2 = grouped.agg({"C": "mean", "D": "sum"})
+ expected2 = grouped.mean(numeric_only=True)
+ expected2["D"] = grouped.sum()["D"]
+ tm.assert_frame_equal(result2, expected2)
+
+ grouped = df.groupby("A", as_index=True)
+
+ msg = r"nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ grouped["C"].agg({"Q": "sum"})
+
+ # multi-key
+
+ grouped = df.groupby(["A", "B"], as_index=False)
+
+ result = grouped.agg("mean")
+ expected = grouped.mean()
+ tm.assert_frame_equal(result, expected)
+
+ result2 = grouped.agg({"C": "mean", "D": "sum"})
+ expected2 = grouped.mean()
+ expected2["D"] = grouped.sum()["D"]
+ tm.assert_frame_equal(result2, expected2)
+
+ expected3 = grouped["C"].sum()
+ expected3 = DataFrame(expected3).rename(columns={"C": "Q"})
+ msg = "nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ grouped["C"].agg({"Q": "sum"})
+
+ # GH7115 & GH8112 & GH8582
+ df = DataFrame(
+ np.random.default_rng(2).integers(0, 100, (50, 3)),
+ columns=["jim", "joe", "jolie"],
+ )
+ ts = Series(np.random.default_rng(2).integers(5, 10, 50), name="jim")
+
+ gr = df.groupby(ts)
+ gr.nth(0) # invokes set_selection_from_grouper internally
+
+ for attr in ["mean", "max", "count", "idxmax", "cumsum", "all"]:
+ gr = df.groupby(ts, as_index=False)
+ left = getattr(gr, attr)()
+
+ gr = df.groupby(ts.values, as_index=True)
+ right = getattr(gr, attr)().reset_index(drop=True)
+
+ tm.assert_frame_equal(left, right)
+
+
+@pytest.mark.parametrize(
+ "func", [lambda s: s.mean(), lambda s: np.mean(s), lambda s: np.nanmean(s)]
+)
+def test_multiindex_custom_func(func):
+ # GH 31777
+ data = [[1, 4, 2], [5, 7, 1]]
+ df = DataFrame(
+ data,
+ columns=MultiIndex.from_arrays(
+ [[1, 1, 2], [3, 4, 3]], names=["Sisko", "Janeway"]
+ ),
+ )
+ result = df.groupby(np.array([0, 1])).agg(func)
+ expected_dict = {
+ (1, 3): {0: 1.0, 1: 5.0},
+ (1, 4): {0: 4.0, 1: 7.0},
+ (2, 3): {0: 2.0, 1: 1.0},
+ }
+ expected = DataFrame(expected_dict, index=np.array([0, 1]), columns=df.columns)
+ tm.assert_frame_equal(result, expected)
+
+
+def myfunc(s):
+ return np.percentile(s, q=0.90)
+
+
+@pytest.mark.parametrize("func", [lambda s: np.percentile(s, q=0.90), myfunc])
+def test_lambda_named_agg(func):
+ # see gh-28467
+ animals = DataFrame(
+ {
+ "kind": ["cat", "dog", "cat", "dog"],
+ "height": [9.1, 6.0, 9.5, 34.0],
+ "weight": [7.9, 7.5, 9.9, 198.0],
+ }
+ )
+
+ result = animals.groupby("kind").agg(
+ mean_height=("height", "mean"), perc90=("height", func)
+ )
+ expected = DataFrame(
+ [[9.3, 9.1036], [20.0, 6.252]],
+ columns=["mean_height", "perc90"],
+ index=Index(["cat", "dog"], name="kind"),
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_aggregate_mixed_types():
+ # GH 16916
+ df = DataFrame(
+ data=np.array([0] * 9).reshape(3, 3), columns=list("XYZ"), index=list("abc")
+ )
+ df["grouping"] = ["group 1", "group 1", 2]
+ result = df.groupby("grouping").aggregate(lambda x: x.tolist())
+ expected_data = [[[0], [0], [0]], [[0, 0], [0, 0], [0, 0]]]
+ expected = DataFrame(
+ expected_data,
+ index=Index([2, "group 1"], dtype="object", name="grouping"),
+ columns=Index(["X", "Y", "Z"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.xfail(reason="Not implemented;see GH 31256")
+def test_aggregate_udf_na_extension_type():
+ # https://github.com/pandas-dev/pandas/pull/31359
+ # This is currently failing to cast back to Int64Dtype.
+ # The presence of the NA causes two problems
+ # 1. NA is not an instance of Int64Dtype.type (numpy.int64)
+ # 2. The presence of an NA forces object type, so the non-NA values is
+ # a Python int rather than a NumPy int64. Python ints aren't
+ # instances of numpy.int64.
+ def aggfunc(x):
+ if all(x > 2):
+ return 1
+ else:
+ return pd.NA
+
+ df = DataFrame({"A": pd.array([1, 2, 3])})
+ result = df.groupby([1, 1, 2]).agg(aggfunc)
+ expected = DataFrame({"A": pd.array([1, pd.NA], dtype="Int64")}, index=[1, 2])
+ tm.assert_frame_equal(result, expected)
+
+
+class TestLambdaMangling:
+ def test_basic(self):
+ df = DataFrame({"A": [0, 0, 1, 1], "B": [1, 2, 3, 4]})
+ result = df.groupby("A").agg({"B": [lambda x: 0, lambda x: 1]})
+
+ expected = DataFrame(
+ {("B", ""): [0, 0], ("B", ""): [1, 1]},
+ index=Index([0, 1], name="A"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_mangle_series_groupby(self):
+ gr = Series([1, 2, 3, 4]).groupby([0, 0, 1, 1])
+ result = gr.agg([lambda x: 0, lambda x: 1])
+ exp_data = {"": [0, 0], "": [1, 1]}
+ expected = DataFrame(exp_data, index=np.array([0, 1]))
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.xfail(reason="GH-26611. kwargs for multi-agg.")
+ def test_with_kwargs(self):
+ f1 = lambda x, y, b=1: x.sum() + y + b
+ f2 = lambda x, y, b=2: x.sum() + y * b
+ result = Series([1, 2]).groupby([0, 0]).agg([f1, f2], 0)
+ expected = DataFrame({"": [4], "": [6]})
+ tm.assert_frame_equal(result, expected)
+
+ result = Series([1, 2]).groupby([0, 0]).agg([f1, f2], 0, b=10)
+ expected = DataFrame({"": [13], "": [30]})
+ tm.assert_frame_equal(result, expected)
+
+ def test_agg_with_one_lambda(self):
+ # GH 25719, write tests for DataFrameGroupby.agg with only one lambda
+ df = DataFrame(
+ {
+ "kind": ["cat", "dog", "cat", "dog"],
+ "height": [9.1, 6.0, 9.5, 34.0],
+ "weight": [7.9, 7.5, 9.9, 198.0],
+ }
+ )
+
+ columns = ["height_sqr_min", "height_max", "weight_max"]
+ expected = DataFrame(
+ {
+ "height_sqr_min": [82.81, 36.00],
+ "height_max": [9.5, 34.0],
+ "weight_max": [9.9, 198.0],
+ },
+ index=Index(["cat", "dog"], name="kind"),
+ columns=columns,
+ )
+
+ # check pd.NameAgg case
+ result1 = df.groupby(by="kind").agg(
+ height_sqr_min=pd.NamedAgg(column="height", aggfunc=lambda x: np.min(x**2)),
+ height_max=pd.NamedAgg(column="height", aggfunc="max"),
+ weight_max=pd.NamedAgg(column="weight", aggfunc="max"),
+ )
+ tm.assert_frame_equal(result1, expected)
+
+ # check agg(key=(col, aggfunc)) case
+ result2 = df.groupby(by="kind").agg(
+ height_sqr_min=("height", lambda x: np.min(x**2)),
+ height_max=("height", "max"),
+ weight_max=("weight", "max"),
+ )
+ tm.assert_frame_equal(result2, expected)
+
+ def test_agg_multiple_lambda(self):
+ # GH25719, test for DataFrameGroupby.agg with multiple lambdas
+ # with mixed aggfunc
+ df = DataFrame(
+ {
+ "kind": ["cat", "dog", "cat", "dog"],
+ "height": [9.1, 6.0, 9.5, 34.0],
+ "weight": [7.9, 7.5, 9.9, 198.0],
+ }
+ )
+ columns = [
+ "height_sqr_min",
+ "height_max",
+ "weight_max",
+ "height_max_2",
+ "weight_min",
+ ]
+ expected = DataFrame(
+ {
+ "height_sqr_min": [82.81, 36.00],
+ "height_max": [9.5, 34.0],
+ "weight_max": [9.9, 198.0],
+ "height_max_2": [9.5, 34.0],
+ "weight_min": [7.9, 7.5],
+ },
+ index=Index(["cat", "dog"], name="kind"),
+ columns=columns,
+ )
+
+ # check agg(key=(col, aggfunc)) case
+ result1 = df.groupby(by="kind").agg(
+ height_sqr_min=("height", lambda x: np.min(x**2)),
+ height_max=("height", "max"),
+ weight_max=("weight", "max"),
+ height_max_2=("height", lambda x: np.max(x)),
+ weight_min=("weight", lambda x: np.min(x)),
+ )
+ tm.assert_frame_equal(result1, expected)
+
+ # check pd.NamedAgg case
+ result2 = df.groupby(by="kind").agg(
+ height_sqr_min=pd.NamedAgg(column="height", aggfunc=lambda x: np.min(x**2)),
+ height_max=pd.NamedAgg(column="height", aggfunc="max"),
+ weight_max=pd.NamedAgg(column="weight", aggfunc="max"),
+ height_max_2=pd.NamedAgg(column="height", aggfunc=lambda x: np.max(x)),
+ weight_min=pd.NamedAgg(column="weight", aggfunc=lambda x: np.min(x)),
+ )
+ tm.assert_frame_equal(result2, expected)
+
+
+def test_pass_args_kwargs_duplicate_columns(tsframe, as_index):
+ # go through _aggregate_frame with self.axis == 0 and duplicate columns
+ tsframe.columns = ["A", "B", "A", "C"]
+ gb = tsframe.groupby(lambda x: x.month, as_index=as_index)
+
+ res = gb.agg(np.percentile, 80, axis=0)
+
+ ex_data = {
+ 1: tsframe[tsframe.index.month == 1].quantile(0.8),
+ 2: tsframe[tsframe.index.month == 2].quantile(0.8),
+ }
+ expected = DataFrame(ex_data).T
+ if not as_index:
+ expected.insert(0, "index", [1, 2])
+ expected.index = Index(range(2))
+
+ tm.assert_frame_equal(res, expected)
+
+
+def test_groupby_get_by_index():
+ # GH 33439
+ df = DataFrame({"A": ["S", "W", "W"], "B": [1.0, 1.0, 2.0]})
+ res = df.groupby("A").agg({"B": lambda x: x.get(x.index[-1])})
+ expected = DataFrame({"A": ["S", "W"], "B": [1.0, 2.0]}).set_index("A")
+ tm.assert_frame_equal(res, expected)
+
+
+@pytest.mark.parametrize(
+ "grp_col_dict, exp_data",
+ [
+ ({"nr": "min", "cat_ord": "min"}, {"nr": [1, 5], "cat_ord": ["a", "c"]}),
+ ({"cat_ord": "min"}, {"cat_ord": ["a", "c"]}),
+ ({"nr": "min"}, {"nr": [1, 5]}),
+ ],
+)
+def test_groupby_single_agg_cat_cols(grp_col_dict, exp_data):
+ # test single aggregations on ordered categorical cols GHGH27800
+
+ # create the result dataframe
+ input_df = DataFrame(
+ {
+ "nr": [1, 2, 3, 4, 5, 6, 7, 8],
+ "cat_ord": list("aabbccdd"),
+ "cat": list("aaaabbbb"),
+ }
+ )
+
+ input_df = input_df.astype({"cat": "category", "cat_ord": "category"})
+ input_df["cat_ord"] = input_df["cat_ord"].cat.as_ordered()
+ result_df = input_df.groupby("cat", observed=False).agg(grp_col_dict)
+
+ # create expected dataframe
+ cat_index = pd.CategoricalIndex(
+ ["a", "b"], categories=["a", "b"], ordered=False, name="cat", dtype="category"
+ )
+
+ expected_df = DataFrame(data=exp_data, index=cat_index)
+
+ if "cat_ord" in expected_df:
+ # ordered categorical columns should be preserved
+ dtype = input_df["cat_ord"].dtype
+ expected_df["cat_ord"] = expected_df["cat_ord"].astype(dtype)
+
+ tm.assert_frame_equal(result_df, expected_df)
+
+
+@pytest.mark.parametrize(
+ "grp_col_dict, exp_data",
+ [
+ ({"nr": ["min", "max"], "cat_ord": "min"}, [(1, 4, "a"), (5, 8, "c")]),
+ ({"nr": "min", "cat_ord": ["min", "max"]}, [(1, "a", "b"), (5, "c", "d")]),
+ ({"cat_ord": ["min", "max"]}, [("a", "b"), ("c", "d")]),
+ ],
+)
+def test_groupby_combined_aggs_cat_cols(grp_col_dict, exp_data):
+ # test combined aggregations on ordered categorical cols GH27800
+
+ # create the result dataframe
+ input_df = DataFrame(
+ {
+ "nr": [1, 2, 3, 4, 5, 6, 7, 8],
+ "cat_ord": list("aabbccdd"),
+ "cat": list("aaaabbbb"),
+ }
+ )
+
+ input_df = input_df.astype({"cat": "category", "cat_ord": "category"})
+ input_df["cat_ord"] = input_df["cat_ord"].cat.as_ordered()
+ result_df = input_df.groupby("cat", observed=False).agg(grp_col_dict)
+
+ # create expected dataframe
+ cat_index = pd.CategoricalIndex(
+ ["a", "b"], categories=["a", "b"], ordered=False, name="cat", dtype="category"
+ )
+
+ # unpack the grp_col_dict to create the multi-index tuple
+ # this tuple will be used to create the expected dataframe index
+ multi_index_list = []
+ for k, v in grp_col_dict.items():
+ if isinstance(v, list):
+ multi_index_list.extend([k, value] for value in v)
+ else:
+ multi_index_list.append([k, v])
+ multi_index = MultiIndex.from_tuples(tuple(multi_index_list))
+
+ expected_df = DataFrame(data=exp_data, columns=multi_index, index=cat_index)
+ for col in expected_df.columns:
+ if isinstance(col, tuple) and "cat_ord" in col:
+ # ordered categorical should be preserved
+ expected_df[col] = expected_df[col].astype(input_df["cat_ord"].dtype)
+
+ tm.assert_frame_equal(result_df, expected_df)
+
+
+def test_nonagg_agg():
+ # GH 35490 - Single/Multiple agg of non-agg function give same results
+ # TODO: agg should raise for functions that don't aggregate
+ df = DataFrame({"a": [1, 1, 2, 2], "b": [1, 2, 2, 1]})
+ g = df.groupby("a")
+
+ result = g.agg(["cumsum"])
+ result.columns = result.columns.droplevel(-1)
+ expected = g.agg("cumsum")
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_aggregate_datetime_objects():
+ # https://github.com/pandas-dev/pandas/issues/36003
+ # ensure we don't raise an error but keep object dtype for out-of-bounds
+ # datetimes
+ df = DataFrame(
+ {
+ "A": ["X", "Y"],
+ "B": [
+ datetime.datetime(2005, 1, 1, 10, 30, 23, 540000),
+ datetime.datetime(3005, 1, 1, 10, 30, 23, 540000),
+ ],
+ }
+ )
+ result = df.groupby("A").B.max()
+ expected = df.set_index("A")["B"]
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_index_object_dtype():
+ # GH 40014
+ df = DataFrame({"c0": ["x", "x", "x"], "c1": ["x", "x", "y"], "p": [0, 1, 2]})
+ df.index = df.index.astype("O")
+ grouped = df.groupby(["c0", "c1"])
+ res = grouped.p.agg(lambda x: all(x > 0))
+ # Check that providing a user-defined function in agg()
+ # produces the correct index shape when using an object-typed index.
+ expected_index = MultiIndex.from_tuples(
+ [("x", "x"), ("x", "y")], names=("c0", "c1")
+ )
+ expected = Series([False, True], index=expected_index, name="p")
+ tm.assert_series_equal(res, expected)
+
+
+def test_timeseries_groupby_agg():
+ # GH#43290
+
+ def func(ser):
+ if ser.isna().all():
+ return None
+ return np.sum(ser)
+
+ df = DataFrame([1.0], index=[pd.Timestamp("2018-01-16 00:00:00+00:00")])
+ res = df.groupby(lambda x: 1).agg(func)
+
+ expected = DataFrame([[1.0]], index=[1])
+ tm.assert_frame_equal(res, expected)
+
+
+def test_groupby_agg_precision(any_real_numeric_dtype):
+ if any_real_numeric_dtype in tm.ALL_INT_NUMPY_DTYPES:
+ max_value = np.iinfo(any_real_numeric_dtype).max
+ if any_real_numeric_dtype in tm.FLOAT_NUMPY_DTYPES:
+ max_value = np.finfo(any_real_numeric_dtype).max
+ if any_real_numeric_dtype in tm.FLOAT_EA_DTYPES:
+ max_value = np.finfo(any_real_numeric_dtype.lower()).max
+ if any_real_numeric_dtype in tm.ALL_INT_EA_DTYPES:
+ max_value = np.iinfo(any_real_numeric_dtype.lower()).max
+
+ df = DataFrame(
+ {
+ "key1": ["a"],
+ "key2": ["b"],
+ "key3": pd.array([max_value], dtype=any_real_numeric_dtype),
+ }
+ )
+ arrays = [["a"], ["b"]]
+ index = MultiIndex.from_arrays(arrays, names=("key1", "key2"))
+
+ expected = DataFrame(
+ {"key3": pd.array([max_value], dtype=any_real_numeric_dtype)}, index=index
+ )
+ result = df.groupby(["key1", "key2"]).agg(lambda x: x)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregate_directory(reduction_func):
+ # GH#32793
+ if reduction_func in ["corrwith", "nth"]:
+ return None
+
+ obj = DataFrame([[0, 1], [0, np.nan]])
+
+ result_reduced_series = obj.groupby(0).agg(reduction_func)
+ result_reduced_frame = obj.groupby(0).agg({1: reduction_func})
+
+ if reduction_func in ["size", "ngroup"]:
+ # names are different: None / 1
+ tm.assert_series_equal(
+ result_reduced_series, result_reduced_frame[1], check_names=False
+ )
+ else:
+ tm.assert_frame_equal(result_reduced_series, result_reduced_frame)
+ tm.assert_series_equal(
+ result_reduced_series.dtypes, result_reduced_frame.dtypes
+ )
+
+
+def test_group_mean_timedelta_nat():
+ # GH43132
+ data = Series(["1 day", "3 days", "NaT"], dtype="timedelta64[ns]")
+ expected = Series(["2 days"], dtype="timedelta64[ns]", index=np.array([0]))
+
+ result = data.groupby([0, 0, 0]).mean()
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "input_data, expected_output",
+ [
+ ( # no timezone
+ ["2021-01-01T00:00", "NaT", "2021-01-01T02:00"],
+ ["2021-01-01T01:00"],
+ ),
+ ( # timezone
+ ["2021-01-01T00:00-0100", "NaT", "2021-01-01T02:00-0100"],
+ ["2021-01-01T01:00-0100"],
+ ),
+ ],
+)
+def test_group_mean_datetime64_nat(input_data, expected_output):
+ # GH43132
+ data = to_datetime(Series(input_data))
+ expected = to_datetime(Series(expected_output, index=np.array([0])))
+
+ result = data.groupby([0, 0, 0]).mean()
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func, output", [("mean", [8 + 18j, 10 + 22j]), ("sum", [40 + 90j, 50 + 110j])]
+)
+def test_groupby_complex(func, output):
+ # GH#43701
+ data = Series(np.arange(20).reshape(10, 2).dot([1, 2j]))
+ result = data.groupby(data.index % 2).agg(func)
+ expected = Series(output)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["min", "max", "var"])
+def test_groupby_complex_raises(func):
+ # GH#43701
+ data = Series(np.arange(20).reshape(10, 2).dot([1, 2j]))
+ msg = "No matching signature found"
+ with pytest.raises(TypeError, match=msg):
+ data.groupby(data.index % 2).agg(func)
+
+
+@pytest.mark.parametrize(
+ "test, constant",
+ [
+ ([[20, "A"], [20, "B"], [10, "C"]], {0: [10, 20], 1: ["C", ["A", "B"]]}),
+ ([[20, "A"], [20, "B"], [30, "C"]], {0: [20, 30], 1: [["A", "B"], "C"]}),
+ ([["a", 1], ["a", 1], ["b", 2], ["b", 3]], {0: ["a", "b"], 1: [1, [2, 3]]}),
+ pytest.param(
+ [["a", 1], ["a", 2], ["b", 3], ["b", 3]],
+ {0: ["a", "b"], 1: [[1, 2], 3]},
+ marks=pytest.mark.xfail,
+ ),
+ ],
+)
+def test_agg_of_mode_list(test, constant):
+ # GH#25581
+ df1 = DataFrame(test)
+ result = df1.groupby(0).agg(Series.mode)
+ # Mode usually only returns 1 value, but can return a list in the case of a tie.
+
+ expected = DataFrame(constant)
+ expected = expected.set_index(0)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dataframe_groupy_agg_list_like_func_with_args():
+ # GH#50624
+ df = DataFrame({"x": [1, 2, 3], "y": ["a", "b", "c"]})
+ gb = df.groupby("y")
+
+ def foo1(x, a=1, c=0):
+ return x.sum() + a + c
+
+ def foo2(x, b=2, c=0):
+ return x.sum() + b + c
+
+ msg = r"foo1\(\) got an unexpected keyword argument 'b'"
+ with pytest.raises(TypeError, match=msg):
+ gb.agg([foo1, foo2], 3, b=3, c=4)
+
+ result = gb.agg([foo1, foo2], 3, c=4)
+ expected = DataFrame(
+ [[8, 8], [9, 9], [10, 10]],
+ index=Index(["a", "b", "c"], name="y"),
+ columns=MultiIndex.from_tuples([("x", "foo1"), ("x", "foo2")]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_series_groupy_agg_list_like_func_with_args():
+ # GH#50624
+ s = Series([1, 2, 3])
+ sgb = s.groupby(s)
+
+ def foo1(x, a=1, c=0):
+ return x.sum() + a + c
+
+ def foo2(x, b=2, c=0):
+ return x.sum() + b + c
+
+ msg = r"foo1\(\) got an unexpected keyword argument 'b'"
+ with pytest.raises(TypeError, match=msg):
+ sgb.agg([foo1, foo2], 3, b=3, c=4)
+
+ result = sgb.agg([foo1, foo2], 3, c=4)
+ expected = DataFrame(
+ [[8, 8], [9, 9], [10, 10]], index=Index([1, 2, 3]), columns=["foo1", "foo2"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_groupings_selection():
+ # GH#51186 - a selected grouping should be in the output of agg
+ df = DataFrame({"a": [1, 1, 2], "b": [3, 3, 4], "c": [5, 6, 7]})
+ gb = df.groupby(["a", "b"])
+ selected_gb = gb[["b", "c"]]
+ result = selected_gb.agg(lambda x: x.sum())
+ index = MultiIndex(
+ levels=[[1, 2], [3, 4]], codes=[[0, 1], [0, 1]], names=["a", "b"]
+ )
+ expected = DataFrame({"b": [6, 4], "c": [11, 7]}, index=index)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_multiple_with_as_index_false_subset_to_a_single_column():
+ # GH#50724
+ df = DataFrame({"a": [1, 1, 2], "b": [3, 4, 5]})
+ gb = df.groupby("a", as_index=False)["b"]
+ result = gb.agg(["sum", "mean"])
+ expected = DataFrame({"a": [1, 2], "sum": [7, 5], "mean": [3.5, 5.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_with_as_index_false_with_list():
+ # GH#52849
+ df = DataFrame({"a1": [0, 0, 1], "a2": [2, 3, 3], "b": [4, 5, 6]})
+ gb = df.groupby(by=["a1", "a2"], as_index=False)
+ result = gb.agg(["sum"])
+
+ expected = DataFrame(
+ data=[[0, 2, 4], [0, 3, 5], [1, 3, 6]],
+ columns=MultiIndex.from_tuples([("a1", ""), ("a2", ""), ("b", "sum")]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_agg_extension_timedelta_cumsum_with_named_aggregation():
+ # GH#41720
+ expected = DataFrame(
+ {
+ "td": {
+ 0: pd.Timedelta("0 days 01:00:00"),
+ 1: pd.Timedelta("0 days 01:15:00"),
+ 2: pd.Timedelta("0 days 01:15:00"),
+ }
+ }
+ )
+ df = DataFrame(
+ {
+ "td": Series(
+ ["0 days 01:00:00", "0 days 00:15:00", "0 days 01:15:00"],
+ dtype="timedelta64[us]",
+ ),
+ "grps": ["a", "a", "b"],
+ }
+ )
+ gb = df.groupby("grps")
+ result = gb.agg(td=("td", "cumsum"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregation_empty_group():
+ # https://github.com/pandas-dev/pandas/issues/18869
+ def func(x):
+ if len(x) == 0:
+ raise ValueError("length must not be 0")
+ return len(x)
+
+ df = DataFrame(
+ {"A": pd.Categorical(["a", "a"], categories=["a", "b", "c"]), "B": [1, 1]}
+ )
+ msg = "length must not be 0"
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("A", observed=False).agg(func)
+
+
+def test_groupby_aggregation_duplicate_columns_single_dict_value():
+ # GH#55041
+ df = DataFrame(
+ [[1, 2, 3, 4], [1, 3, 4, 5], [2, 4, 5, 6]],
+ columns=["a", "b", "c", "c"],
+ )
+ gb = df.groupby("a")
+ result = gb.agg({"c": "sum"})
+
+ expected = DataFrame(
+ [[7, 9], [5, 6]], columns=["c", "c"], index=Index([1, 2], name="a")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregation_duplicate_columns_multiple_dict_values():
+ # GH#55041
+ df = DataFrame(
+ [[1, 2, 3, 4], [1, 3, 4, 5], [2, 4, 5, 6]],
+ columns=["a", "b", "c", "c"],
+ )
+ gb = df.groupby("a")
+ result = gb.agg({"c": ["sum", "min", "max", "min"]})
+
+ expected = DataFrame(
+ [[7, 3, 4, 3, 9, 4, 5, 4], [5, 5, 5, 5, 6, 6, 6, 6]],
+ columns=MultiIndex(
+ levels=[["c"], ["sum", "min", "max"]],
+ codes=[[0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 2, 1, 0, 1, 2, 1]],
+ ),
+ index=Index([1, 2], name="a"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregation_duplicate_columns_some_empty_result():
+ # GH#55041
+ df = DataFrame(
+ [
+ [1, 9843, 43, 54, 7867],
+ [2, 940, 9, -34, 44],
+ [1, -34, -546, -549358, 0],
+ [2, 244, -33, -100, 44],
+ ],
+ columns=["a", "b", "b", "c", "c"],
+ )
+ gb = df.groupby("a")
+ result = gb.agg({"b": [], "c": ["var"]})
+
+ expected = DataFrame(
+ [[1.509268e11, 30944844.5], [2.178000e03, 0.0]],
+ columns=MultiIndex(levels=[["c"], ["var"]], codes=[[0, 0], [0, 0]]),
+ index=Index([1, 2], name="a"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregation_multi_index_duplicate_columns():
+ # GH#55041
+ df = DataFrame(
+ [
+ [1, -9843, 43, 54, 7867],
+ [2, 940, 9, -34, 44],
+ [1, -34, 546, -549358, 0],
+ [2, 244, -33, -100, 44],
+ ],
+ columns=MultiIndex(
+ levels=[["level1.1", "level1.2"], ["level2.1", "level2.2"]],
+ codes=[[0, 0, 0, 1, 1], [0, 1, 1, 0, 1]],
+ ),
+ index=MultiIndex(
+ levels=[["level1.1", "level1.2"], ["level2.1", "level2.2"]],
+ codes=[[0, 0, 0, 1], [0, 1, 1, 0]],
+ ),
+ )
+ gb = df.groupby(level=0)
+ result = gb.agg({("level1.1", "level2.2"): "min"})
+
+ expected = DataFrame(
+ [[-9843, 9], [244, -33]],
+ columns=MultiIndex(levels=[["level1.1"], ["level2.2"]], codes=[[0, 0], [0, 0]]),
+ index=Index(["level1.1", "level1.2"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregation_func_list_multi_index_duplicate_columns():
+ # GH#55041
+ df = DataFrame(
+ [
+ [1, -9843, 43, 54, 7867],
+ [2, 940, 9, -34, 44],
+ [1, -34, 546, -549358, 0],
+ [2, 244, -33, -100, 44],
+ ],
+ columns=MultiIndex(
+ levels=[["level1.1", "level1.2"], ["level2.1", "level2.2"]],
+ codes=[[0, 0, 0, 1, 1], [0, 1, 1, 0, 1]],
+ ),
+ index=MultiIndex(
+ levels=[["level1.1", "level1.2"], ["level2.1", "level2.2"]],
+ codes=[[0, 0, 0, 1], [0, 1, 1, 0]],
+ ),
+ )
+ gb = df.groupby(level=0)
+ result = gb.agg({("level1.1", "level2.2"): ["min", "max"]})
+
+ expected = DataFrame(
+ [[-9843, 940, 9, 546], [244, 244, -33, -33]],
+ columns=MultiIndex(
+ levels=[["level1.1"], ["level2.2"], ["min", "max"]],
+ codes=[[0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 0, 1]],
+ ),
+ index=Index(["level1.1", "level1.2"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+@pytest.mark.parametrize(
+ "dtype",
+ ["float[pyarrow]", "int64[pyarrow]", "uint64[pyarrow]", "bool[pyarrow]"],
+)
+def test_agg_lambda_pyarrow_dtype_conversion(dtype):
+ # GH#59601
+ # Test PyArrow dtype conversion back to PyArrow dtype
+ df = DataFrame(
+ {
+ "A": ["c1", "c2", "c3", "c1", "c2", "c3"],
+ "B": pd.array([100, 200, 255, 0, 199, 40392], dtype=dtype),
+ }
+ )
+ gb = df.groupby("A")
+ result = gb.agg(lambda x: x.min())
+
+ expected = DataFrame(
+ {"B": pd.array([0, 199, 255], dtype=dtype)},
+ index=Index(["c1", "c2", "c3"], name="A"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_agg_lambda_complex128_dtype_conversion():
+ # GH#59601
+ df = DataFrame(
+ {"A": ["c1", "c2", "c3"], "B": pd.array([100, 200, 255], "int64[pyarrow]")}
+ )
+ gb = df.groupby("A")
+ result = gb.agg(lambda x: complex(x.sum(), x.count()))
+
+ expected = DataFrame(
+ {
+ "B": pd.array(
+ [complex(100, 1), complex(200, 1), complex(255, 1)], dtype="complex128"
+ ),
+ },
+ index=Index(["c1", "c2", "c3"], name="A"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_agg_lambda_numpy_uint64_to_pyarrow_dtype_conversion():
+ # GH#59601
+ df = DataFrame(
+ {
+ "A": ["c1", "c2", "c3"],
+ "B": pd.array([100, 200, 255], dtype="uint64[pyarrow]"),
+ }
+ )
+ gb = df.groupby("A")
+ result = gb.agg(lambda x: np.uint64(x.sum()))
+
+ expected = DataFrame(
+ {
+ "B": pd.array([100, 200, 255], dtype="uint64[pyarrow]"),
+ },
+ index=Index(["c1", "c2", "c3"], name="A"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_agg_lambda_pyarrow_struct_to_object_dtype_conversion():
+ # GH#59601
+ import pyarrow as pa
+
+ df = DataFrame(
+ {
+ "A": ["c1", "c2", "c3"],
+ "B": pd.array([100, 200, 255], dtype="int64[pyarrow]"),
+ }
+ )
+ gb = df.groupby("A")
+ result = gb.agg(lambda x: {"number": 1})
+
+ arr = pa.array([{"number": 1}, {"number": 1}, {"number": 1}])
+ expected = DataFrame(
+ {"B": ArrowExtensionArray(arr)},
+ index=Index(["c1", "c2", "c3"], name="A"),
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregate_empty_builtin_sum():
+ df = DataFrame(columns=["Group", "Data"])
+ result = df.groupby(["Group"], as_index=False)["Data"].agg("sum")
+ expected = DataFrame(columns=["Group", "Data"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregate_empty_udf():
+ def func(x):
+ return sum(x)
+
+ df = DataFrame(columns=["Group", "Data"])
+ result = df.groupby(["Group"], as_index=False)["Data"].agg(func)
+ expected = DataFrame(columns=["Group", "Data"])
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_cython.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_cython.py
new file mode 100644
index 0000000000000000000000000000000000000000..a706ea795a0e298e3051ab93af8da96ba13d3d8f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_cython.py
@@ -0,0 +1,415 @@
+"""
+test cython .agg behavior
+"""
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import (
+ is_float_dtype,
+ is_integer_dtype,
+)
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ NaT,
+ Series,
+ Timedelta,
+ Timestamp,
+ bdate_range,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "op_name",
+ [
+ "count",
+ "sum",
+ "std",
+ "var",
+ "sem",
+ "mean",
+ pytest.param(
+ "median",
+ # ignore mean of empty slice
+ # and all-NaN
+ marks=[pytest.mark.filterwarnings("ignore::RuntimeWarning")],
+ ),
+ "prod",
+ "min",
+ "max",
+ ],
+)
+def test_cythonized_aggers(op_name):
+ data = {
+ "A": [0, 0, 0, 0, 1, 1, 1, 1, 1, 1.0, np.nan, np.nan],
+ "B": ["A", "B"] * 6,
+ "C": np.random.default_rng(2).standard_normal(12),
+ }
+ df = DataFrame(data)
+ df.loc[2:10:2, "C"] = np.nan
+
+ op = lambda x: getattr(x, op_name)()
+
+ # single column
+ grouped = df.drop(["B"], axis=1).groupby("A")
+ exp = {cat: op(group["C"]) for cat, group in grouped}
+ exp = DataFrame({"C": exp})
+ exp.index.name = "A"
+ result = op(grouped)
+ tm.assert_frame_equal(result, exp)
+
+ # multiple columns
+ grouped = df.groupby(["A", "B"])
+ expd = {}
+ for (cat1, cat2), group in grouped:
+ expd.setdefault(cat1, {})[cat2] = op(group["C"])
+ exp = DataFrame(expd).T.stack()
+ exp.index.names = ["A", "B"]
+ exp.name = "C"
+
+ result = op(grouped)["C"]
+ if op_name in ["sum", "prod"]:
+ tm.assert_series_equal(result, exp)
+
+
+def test_cython_agg_boolean():
+ frame = DataFrame(
+ {
+ "a": np.random.default_rng(2).integers(0, 5, 50),
+ "b": np.random.default_rng(2).integers(0, 2, 50).astype("bool"),
+ }
+ )
+ result = frame.groupby("a")["b"].mean()
+ # GH#53425
+ expected = frame.groupby("a")["b"].agg(np.mean)
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_cython_agg_nothing_to_agg():
+ frame = DataFrame(
+ {"a": np.random.default_rng(2).integers(0, 5, 50), "b": ["foo", "bar"] * 25}
+ )
+
+ msg = "Cannot use numeric_only=True with SeriesGroupBy.mean and non-numeric dtypes"
+ with pytest.raises(TypeError, match=msg):
+ frame.groupby("a")["b"].mean(numeric_only=True)
+
+ frame = DataFrame(
+ {"a": np.random.default_rng(2).integers(0, 5, 50), "b": ["foo", "bar"] * 25}
+ )
+
+ result = frame[["b"]].groupby(frame["a"]).mean(numeric_only=True)
+ expected = DataFrame(
+ [],
+ index=frame["a"].sort_values().drop_duplicates(),
+ columns=Index([], dtype="str"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cython_agg_nothing_to_agg_with_dates():
+ frame = DataFrame(
+ {
+ "a": np.random.default_rng(2).integers(0, 5, 50),
+ "b": ["foo", "bar"] * 25,
+ "dates": pd.date_range("now", periods=50, freq="min"),
+ }
+ )
+ msg = "Cannot use numeric_only=True with SeriesGroupBy.mean and non-numeric dtypes"
+ with pytest.raises(TypeError, match=msg):
+ frame.groupby("b").dates.mean(numeric_only=True)
+
+
+def test_cython_agg_return_dict():
+ # GH 16741
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+
+ ts = df.groupby("A")["B"].agg(lambda x: x.value_counts().to_dict())
+ expected = Series(
+ [{"two": 1, "one": 1, "three": 1}, {"two": 2, "one": 2, "three": 1}],
+ index=Index(["bar", "foo"], name="A"),
+ name="B",
+ )
+ tm.assert_series_equal(ts, expected)
+
+
+def test_cython_fail_agg():
+ dr = bdate_range("1/1/2000", periods=50)
+ ts = Series(["A", "B", "C", "D", "E"] * 10, dtype=object, index=dr)
+
+ grouped = ts.groupby(lambda x: x.month)
+ summed = grouped.sum()
+ expected = grouped.agg(np.sum).astype(object)
+ tm.assert_series_equal(summed, expected)
+
+
+@pytest.mark.parametrize(
+ "op, targop",
+ [
+ ("mean", np.mean),
+ ("median", np.median),
+ ("var", np.var),
+ ("sum", np.sum),
+ ("prod", np.prod),
+ ("min", np.min),
+ ("max", np.max),
+ ("first", lambda x: x.iloc[0]),
+ ("last", lambda x: x.iloc[-1]),
+ ],
+)
+def test__cython_agg_general(op, targop):
+ df = DataFrame(np.random.default_rng(2).standard_normal(1000))
+ labels = np.random.default_rng(2).integers(0, 50, size=1000).astype(float)
+ kwargs = {"ddof": 1} if op == "var" else {}
+ if op not in ["first", "last"]:
+ kwargs["axis"] = 0
+
+ result = df.groupby(labels)._cython_agg_general(op, alt=None, numeric_only=True)
+ expected = df.groupby(labels).agg(targop, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "op, targop",
+ [
+ ("mean", np.mean),
+ ("median", lambda x: np.median(x) if len(x) > 0 else np.nan),
+ ("var", lambda x: np.var(x, ddof=1)),
+ ("min", np.min),
+ ("max", np.max),
+ ],
+)
+def test_cython_agg_empty_buckets(op, targop, observed):
+ df = DataFrame([11, 12, 13])
+ grps = range(0, 55, 5)
+
+ # calling _cython_agg_general directly, instead of via the user API
+ # which sets different values for min_count, so do that here.
+ g = df.groupby(pd.cut(df[0], grps), observed=observed)
+ result = g._cython_agg_general(op, alt=None, numeric_only=True)
+
+ g = df.groupby(pd.cut(df[0], grps), observed=observed)
+ expected = g.agg(lambda x: targop(x))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cython_agg_empty_buckets_nanops(observed):
+ # GH-18869 can't call nanops on empty groups, so hardcode expected
+ # for these
+ df = DataFrame([11, 12, 13], columns=["a"])
+ grps = np.arange(0, 25, 5, dtype=int)
+ # add / sum
+ result = df.groupby(pd.cut(df["a"], grps), observed=observed)._cython_agg_general(
+ "sum", alt=None, numeric_only=True
+ )
+ intervals = pd.interval_range(0, 20, freq=5)
+ expected = DataFrame(
+ {"a": [0, 0, 36, 0]},
+ index=pd.CategoricalIndex(intervals, name="a", ordered=True),
+ )
+ if observed:
+ expected = expected[expected.a != 0]
+
+ tm.assert_frame_equal(result, expected)
+
+ # prod
+ result = df.groupby(pd.cut(df["a"], grps), observed=observed)._cython_agg_general(
+ "prod", alt=None, numeric_only=True
+ )
+ expected = DataFrame(
+ {"a": [1, 1, 1716, 1]},
+ index=pd.CategoricalIndex(intervals, name="a", ordered=True),
+ )
+ if observed:
+ expected = expected[expected.a != 1]
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("op", ["first", "last", "max", "min"])
+@pytest.mark.parametrize(
+ "data", [Timestamp("2016-10-14 21:00:44.557"), Timedelta("17088 days 21:00:44.557")]
+)
+def test_cython_with_timestamp_and_nat(op, data):
+ # https://github.com/pandas-dev/pandas/issues/19526
+ df = DataFrame({"a": [0, 1], "b": [data, NaT]})
+ index = Index([0, 1], name="a")
+
+ # We will group by a and test the cython aggregations
+ expected = DataFrame({"b": [data, NaT]}, index=index)
+
+ result = df.groupby("a").aggregate(op)
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize(
+ "agg",
+ [
+ "min",
+ "max",
+ "count",
+ "sum",
+ "prod",
+ "var",
+ "mean",
+ "median",
+ "ohlc",
+ "cumprod",
+ "cumsum",
+ "shift",
+ "any",
+ "all",
+ "quantile",
+ "first",
+ "last",
+ "rank",
+ "cummin",
+ "cummax",
+ ],
+)
+def test_read_only_buffer_source_agg(agg):
+ # https://github.com/pandas-dev/pandas/issues/36014
+ df = DataFrame(
+ {
+ "sepal_length": [5.1, 4.9, 4.7, 4.6, 5.0],
+ "species": ["setosa", "setosa", "setosa", "setosa", "setosa"],
+ }
+ )
+ df._mgr.blocks[0].values.flags.writeable = False
+
+ result = df.groupby(["species"]).agg({"sepal_length": agg})
+ expected = df.copy().groupby(["species"]).agg({"sepal_length": agg})
+
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "op_name",
+ [
+ "count",
+ "sum",
+ "std",
+ "var",
+ "sem",
+ "mean",
+ "median",
+ "prod",
+ "min",
+ "max",
+ ],
+)
+def test_cython_agg_nullable_int(op_name):
+ # ensure that the cython-based aggregations don't fail for nullable dtype
+ # (eg https://github.com/pandas-dev/pandas/issues/37415)
+ df = DataFrame(
+ {
+ "A": ["A", "B"] * 5,
+ "B": pd.array([1, 2, 3, 4, 5, 6, 7, 8, 9, pd.NA], dtype="Int64"),
+ }
+ )
+ result = getattr(df.groupby("A")["B"], op_name)()
+ df2 = df.assign(B=df["B"].astype("float64"))
+ expected = getattr(df2.groupby("A")["B"], op_name)()
+ if op_name in ("mean", "median"):
+ convert_integer = False
+ else:
+ convert_integer = True
+ expected = expected.convert_dtypes(convert_integer=convert_integer)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["Int64", "Float64", "boolean"])
+def test_count_masked_returns_masked_dtype(dtype):
+ df = DataFrame(
+ {
+ "A": [1, 1],
+ "B": pd.array([1, pd.NA], dtype=dtype),
+ "C": pd.array([1, 1], dtype=dtype),
+ }
+ )
+ result = df.groupby("A").count()
+ expected = DataFrame(
+ [[1, 2]], index=Index([1], name="A"), columns=["B", "C"], dtype="Int64"
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("with_na", [True, False])
+@pytest.mark.parametrize(
+ "op_name, action",
+ [
+ # ("count", "always_int"),
+ ("sum", "large_int"),
+ # ("std", "always_float"),
+ ("var", "always_float"),
+ # ("sem", "always_float"),
+ ("mean", "always_float"),
+ ("median", "always_float"),
+ ("prod", "large_int"),
+ ("min", "preserve"),
+ ("max", "preserve"),
+ ("first", "preserve"),
+ ("last", "preserve"),
+ ],
+)
+@pytest.mark.parametrize(
+ "data",
+ [
+ pd.array([1, 2, 3, 4], dtype="Int64"),
+ pd.array([1, 2, 3, 4], dtype="Int8"),
+ pd.array([0.1, 0.2, 0.3, 0.4], dtype="Float32"),
+ pd.array([0.1, 0.2, 0.3, 0.4], dtype="Float64"),
+ pd.array([True, True, False, False], dtype="boolean"),
+ ],
+)
+def test_cython_agg_EA_known_dtypes(data, op_name, action, with_na):
+ if with_na:
+ data[3] = pd.NA
+
+ df = DataFrame({"key": ["a", "a", "b", "b"], "col": data})
+ grouped = df.groupby("key")
+
+ if action == "always_int":
+ # always Int64
+ expected_dtype = pd.Int64Dtype()
+ elif action == "large_int":
+ # for any int/bool use Int64, for float preserve dtype
+ if is_float_dtype(data.dtype):
+ expected_dtype = data.dtype
+ elif is_integer_dtype(data.dtype):
+ # match the numpy dtype we'd get with the non-nullable analogue
+ expected_dtype = data.dtype
+ else:
+ expected_dtype = pd.Int64Dtype()
+ elif action == "always_float":
+ # for any int/bool use Float64, for float preserve dtype
+ if is_float_dtype(data.dtype):
+ expected_dtype = data.dtype
+ else:
+ expected_dtype = pd.Float64Dtype()
+ elif action == "preserve":
+ expected_dtype = data.dtype
+
+ result = getattr(grouped, op_name)()
+ assert result["col"].dtype == expected_dtype
+
+ result = grouped.aggregate(op_name)
+ assert result["col"].dtype == expected_dtype
+
+ result = getattr(grouped["col"], op_name)()
+ assert result.dtype == expected_dtype
+
+ result = grouped["col"].aggregate(op_name)
+ assert result.dtype == expected_dtype
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_numba.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_numba.py
new file mode 100644
index 0000000000000000000000000000000000000000..afddc90fdd055189a7bf25cd3a4fa4a8a60a8a05
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_numba.py
@@ -0,0 +1,441 @@
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_arm
+from pandas.errors import NumbaUtilError
+
+from pandas import (
+ DataFrame,
+ Index,
+ NamedAgg,
+ Series,
+ option_context,
+)
+import pandas._testing as tm
+from pandas.util.version import Version
+
+pytestmark = [pytest.mark.single_cpu]
+
+numba = pytest.importorskip("numba")
+pytestmark.append(
+ pytest.mark.skipif(
+ Version(numba.__version__) == Version("0.61") and is_platform_arm(),
+ reason=f"Segfaults on ARM platforms with numba {numba.__version__}",
+ )
+)
+
+
+def test_correct_function_signature():
+ pytest.importorskip("numba")
+
+ def incorrect_function(x):
+ return sum(x) * 2.7
+
+ data = DataFrame(
+ {"key": ["a", "a", "b", "b", "a"], "data": [1.0, 2.0, 3.0, 4.0, 5.0]},
+ columns=["key", "data"],
+ )
+ with pytest.raises(NumbaUtilError, match="The first 2"):
+ data.groupby("key").agg(incorrect_function, engine="numba")
+
+ with pytest.raises(NumbaUtilError, match="The first 2"):
+ data.groupby("key")["data"].agg(incorrect_function, engine="numba")
+
+
+def test_check_nopython_kwargs():
+ pytest.importorskip("numba")
+
+ def incorrect_function(values, index, *, a):
+ return sum(values) * 2.7 + a
+
+ def correct_function(values, index, a):
+ return sum(values) * 2.7 + a
+
+ data = DataFrame(
+ {"key": ["a", "a", "b", "b", "a"], "data": [1.0, 2.0, 3.0, 4.0, 5.0]},
+ columns=["key", "data"],
+ )
+ expected = data.groupby("key").sum() * 2.7
+
+ # py signature binding
+ with pytest.raises(
+ TypeError, match="missing a required (keyword-only argument|argument): 'a'"
+ ):
+ data.groupby("key").agg(incorrect_function, engine="numba", b=1)
+ with pytest.raises(TypeError, match="missing a required argument: 'a'"):
+ data.groupby("key").agg(correct_function, engine="numba", b=1)
+
+ with pytest.raises(
+ TypeError, match="missing a required (keyword-only argument|argument): 'a'"
+ ):
+ data.groupby("key")["data"].agg(incorrect_function, engine="numba", b=1)
+ with pytest.raises(TypeError, match="missing a required argument: 'a'"):
+ data.groupby("key")["data"].agg(correct_function, engine="numba", b=1)
+
+ # numba signature check after binding
+ with pytest.raises(NumbaUtilError, match="numba does not support"):
+ data.groupby("key").agg(incorrect_function, engine="numba", a=1)
+ actual = data.groupby("key").agg(correct_function, engine="numba", a=1)
+ tm.assert_frame_equal(expected + 1, actual)
+
+ with pytest.raises(NumbaUtilError, match="numba does not support"):
+ data.groupby("key")["data"].agg(incorrect_function, engine="numba", a=1)
+ actual = data.groupby("key")["data"].agg(correct_function, engine="numba", a=1)
+ tm.assert_series_equal(expected["data"] + 1, actual)
+
+
+@pytest.mark.filterwarnings("ignore")
+# Filter warnings when parallel=True and the function can't be parallelized by Numba
+@pytest.mark.parametrize("jit", [True, False])
+def test_numba_vs_cython(jit, frame_or_series, nogil, parallel, nopython, as_index):
+ pytest.importorskip("numba")
+
+ def func_numba(values, index):
+ return np.mean(values) * 2.7
+
+ if jit:
+ # Test accepted jitted functions
+ import numba
+
+ func_numba = numba.jit(func_numba)
+
+ data = DataFrame(
+ {0: ["a", "a", "b", "b", "a"], 1: [1.0, 2.0, 3.0, 4.0, 5.0]}, columns=[0, 1]
+ )
+ engine_kwargs = {"nogil": nogil, "parallel": parallel, "nopython": nopython}
+ grouped = data.groupby(0, as_index=as_index)
+ if frame_or_series is Series:
+ grouped = grouped[1]
+
+ result = grouped.agg(func_numba, engine="numba", engine_kwargs=engine_kwargs)
+ expected = grouped.agg(lambda x: np.mean(x) * 2.7, engine="cython")
+
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.filterwarnings("ignore")
+# Filter warnings when parallel=True and the function can't be parallelized by Numba
+@pytest.mark.parametrize("jit", [True, False])
+def test_cache(jit, frame_or_series, nogil, parallel, nopython):
+ # Test that the functions are cached correctly if we switch functions
+ pytest.importorskip("numba")
+
+ def func_1(values, index):
+ return np.mean(values) - 3.4
+
+ def func_2(values, index):
+ return np.mean(values) * 2.7
+
+ if jit:
+ import numba
+
+ func_1 = numba.jit(func_1)
+ func_2 = numba.jit(func_2)
+
+ data = DataFrame(
+ {0: ["a", "a", "b", "b", "a"], 1: [1.0, 2.0, 3.0, 4.0, 5.0]}, columns=[0, 1]
+ )
+ engine_kwargs = {"nogil": nogil, "parallel": parallel, "nopython": nopython}
+ grouped = data.groupby(0)
+ if frame_or_series is Series:
+ grouped = grouped[1]
+
+ result = grouped.agg(func_1, engine="numba", engine_kwargs=engine_kwargs)
+ expected = grouped.agg(lambda x: np.mean(x) - 3.4, engine="cython")
+ tm.assert_equal(result, expected)
+
+ # Add func_2 to the cache
+ result = grouped.agg(func_2, engine="numba", engine_kwargs=engine_kwargs)
+ expected = grouped.agg(lambda x: np.mean(x) * 2.7, engine="cython")
+ tm.assert_equal(result, expected)
+
+ # Retest func_1 which should use the cache
+ result = grouped.agg(func_1, engine="numba", engine_kwargs=engine_kwargs)
+ expected = grouped.agg(lambda x: np.mean(x) - 3.4, engine="cython")
+ tm.assert_equal(result, expected)
+
+
+def test_use_global_config():
+ pytest.importorskip("numba")
+
+ def func_1(values, index):
+ return np.mean(values) - 3.4
+
+ data = DataFrame(
+ {0: ["a", "a", "b", "b", "a"], 1: [1.0, 2.0, 3.0, 4.0, 5.0]}, columns=[0, 1]
+ )
+ grouped = data.groupby(0)
+ expected = grouped.agg(func_1, engine="numba")
+ with option_context("compute.use_numba", True):
+ result = grouped.agg(func_1, engine=None)
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize(
+ "agg_kwargs",
+ [
+ {"func": ["min", "max"]},
+ {"func": "min"},
+ {"func": {1: ["min", "max"], 2: "sum"}},
+ {"bmin": NamedAgg(column=1, aggfunc="min")},
+ ],
+)
+def test_multifunc_numba_vs_cython_frame(agg_kwargs):
+ pytest.importorskip("numba")
+ data = DataFrame(
+ {
+ 0: ["a", "a", "b", "b", "a"],
+ 1: [1.0, 2.0, 3.0, 4.0, 5.0],
+ 2: [1, 2, 3, 4, 5],
+ },
+ columns=[0, 1, 2],
+ )
+ grouped = data.groupby(0)
+ result = grouped.agg(**agg_kwargs, engine="numba")
+ expected = grouped.agg(**agg_kwargs, engine="cython")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["sum", "mean", "var", "std", "min", "max"])
+def test_multifunc_numba_vs_cython_frame_noskipna(func):
+ pytest.importorskip("numba")
+ data = DataFrame(
+ {
+ 0: ["a", "a", "b", "b", "a"],
+ 1: [1.0, np.nan, 3.0, 4.0, 5.0],
+ 2: [1, 2, 3, 4, 5],
+ },
+ columns=[0, 1, 2],
+ )
+ grouped = data.groupby(0)
+ result = grouped.agg(func, skipna=False, engine="numba")
+ expected = grouped.agg(func, skipna=False, engine="cython")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "agg_kwargs,expected_func",
+ [
+ ({"func": lambda values, index: values.sum()}, "sum"),
+ # FIXME
+ pytest.param(
+ {
+ "func": [
+ lambda values, index: values.sum(),
+ lambda values, index: values.min(),
+ ]
+ },
+ ["sum", "min"],
+ marks=pytest.mark.xfail(
+ reason="This doesn't work yet! Fails in nopython pipeline!"
+ ),
+ ),
+ ],
+)
+def test_multifunc_numba_udf_frame(agg_kwargs, expected_func):
+ pytest.importorskip("numba")
+ data = DataFrame(
+ {
+ 0: ["a", "a", "b", "b", "a"],
+ 1: [1.0, 2.0, 3.0, 4.0, 5.0],
+ 2: [1, 2, 3, 4, 5],
+ },
+ columns=[0, 1, 2],
+ )
+ grouped = data.groupby(0)
+ result = grouped.agg(**agg_kwargs, engine="numba")
+ expected = grouped.agg(expected_func, engine="cython")
+ # check_dtype can be removed if GH 44952 is addressed
+ # Currently, UDFs still always return float64 while reductions can preserve dtype
+ tm.assert_frame_equal(result, expected, check_dtype=False)
+
+
+@pytest.mark.parametrize(
+ "agg_kwargs",
+ [{"func": ["min", "max"]}, {"func": "min"}, {"min_val": "min", "max_val": "max"}],
+)
+def test_multifunc_numba_vs_cython_series(agg_kwargs):
+ pytest.importorskip("numba")
+ labels = ["a", "a", "b", "b", "a"]
+ data = Series([1.0, 2.0, 3.0, 4.0, 5.0])
+ grouped = data.groupby(labels)
+ agg_kwargs["engine"] = "numba"
+ result = grouped.agg(**agg_kwargs)
+ agg_kwargs["engine"] = "cython"
+ expected = grouped.agg(**agg_kwargs)
+ if isinstance(expected, DataFrame):
+ tm.assert_frame_equal(result, expected)
+ else:
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.single_cpu
+@pytest.mark.parametrize(
+ "data,agg_kwargs",
+ [
+ (Series([1.0, 2.0, 3.0, 4.0, 5.0]), {"func": ["min", "max"]}),
+ (Series([1.0, 2.0, 3.0, 4.0, 5.0]), {"func": "min"}),
+ (
+ DataFrame(
+ {1: [1.0, 2.0, 3.0, 4.0, 5.0], 2: [1, 2, 3, 4, 5]}, columns=[1, 2]
+ ),
+ {"func": ["min", "max"]},
+ ),
+ (
+ DataFrame(
+ {1: [1.0, 2.0, 3.0, 4.0, 5.0], 2: [1, 2, 3, 4, 5]}, columns=[1, 2]
+ ),
+ {"func": "min"},
+ ),
+ (
+ DataFrame(
+ {1: [1.0, 2.0, 3.0, 4.0, 5.0], 2: [1, 2, 3, 4, 5]}, columns=[1, 2]
+ ),
+ {"func": {1: ["min", "max"], 2: "sum"}},
+ ),
+ (
+ DataFrame(
+ {1: [1.0, 2.0, 3.0, 4.0, 5.0], 2: [1, 2, 3, 4, 5]}, columns=[1, 2]
+ ),
+ {"min_col": NamedAgg(column=1, aggfunc="min")},
+ ),
+ ],
+)
+def test_multifunc_numba_kwarg_propagation(data, agg_kwargs):
+ pytest.importorskip("numba")
+ labels = ["a", "a", "b", "b", "a"]
+ grouped = data.groupby(labels)
+ result = grouped.agg(**agg_kwargs, engine="numba", engine_kwargs={"parallel": True})
+ expected = grouped.agg(**agg_kwargs, engine="numba")
+ if isinstance(expected, DataFrame):
+ tm.assert_frame_equal(result, expected)
+ else:
+ tm.assert_series_equal(result, expected)
+
+
+def test_args_not_cached():
+ # GH 41647
+ pytest.importorskip("numba")
+
+ def sum_last(values, index, n):
+ return values[-n:].sum()
+
+ df = DataFrame({"id": [0, 0, 1, 1], "x": [1, 1, 1, 1]})
+ grouped_x = df.groupby("id")["x"]
+ result = grouped_x.agg(sum_last, 1, engine="numba")
+ expected = Series([1.0] * 2, name="x", index=Index([0, 1], name="id"))
+ tm.assert_series_equal(result, expected)
+
+ result = grouped_x.agg(sum_last, 2, engine="numba")
+ expected = Series([2.0] * 2, name="x", index=Index([0, 1], name="id"))
+ tm.assert_series_equal(result, expected)
+
+
+def test_index_data_correctly_passed():
+ # GH 43133
+ pytest.importorskip("numba")
+
+ def f(values, index):
+ return np.mean(index)
+
+ df = DataFrame({"group": ["A", "A", "B"], "v": [4, 5, 6]}, index=[-1, -2, -3])
+ result = df.groupby("group").aggregate(f, engine="numba")
+ expected = DataFrame(
+ [-1.5, -3.0], columns=["v"], index=Index(["A", "B"], name="group")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_engine_kwargs_not_cached():
+ # If the user passes a different set of engine_kwargs don't return the same
+ # jitted function
+ pytest.importorskip("numba")
+ nogil = True
+ parallel = False
+ nopython = True
+
+ def func_kwargs(values, index):
+ return nogil + parallel + nopython
+
+ engine_kwargs = {"nopython": nopython, "nogil": nogil, "parallel": parallel}
+ df = DataFrame({"value": [0, 0, 0]})
+ result = df.groupby(level=0).aggregate(
+ func_kwargs, engine="numba", engine_kwargs=engine_kwargs
+ )
+ expected = DataFrame({"value": [2.0, 2.0, 2.0]})
+ tm.assert_frame_equal(result, expected)
+
+ nogil = False
+ engine_kwargs = {"nopython": nopython, "nogil": nogil, "parallel": parallel}
+ result = df.groupby(level=0).aggregate(
+ func_kwargs, engine="numba", engine_kwargs=engine_kwargs
+ )
+ expected = DataFrame({"value": [1.0, 1.0, 1.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_multiindex_one_key(nogil, parallel, nopython):
+ pytest.importorskip("numba")
+
+ def numba_func(values, index):
+ return 1
+
+ df = DataFrame([{"A": 1, "B": 2, "C": 3}]).set_index(["A", "B"])
+ engine_kwargs = {"nopython": nopython, "nogil": nogil, "parallel": parallel}
+ result = df.groupby("A").agg(
+ numba_func, engine="numba", engine_kwargs=engine_kwargs
+ )
+ expected = DataFrame([1.0], index=Index([1], name="A"), columns=["C"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_multiindex_multi_key_not_supported(nogil, parallel, nopython):
+ pytest.importorskip("numba")
+
+ def numba_func(values, index):
+ return 1
+
+ df = DataFrame([{"A": 1, "B": 2, "C": 3}]).set_index(["A", "B"])
+ engine_kwargs = {"nopython": nopython, "nogil": nogil, "parallel": parallel}
+ with pytest.raises(NotImplementedError, match="more than 1 grouping labels"):
+ df.groupby(["A", "B"]).agg(
+ numba_func, engine="numba", engine_kwargs=engine_kwargs
+ )
+
+
+def test_multilabel_numba_vs_cython(numba_supported_reductions):
+ pytest.importorskip("numba")
+ reduction, kwargs = numba_supported_reductions
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+ gb = df.groupby(["A", "B"])
+ res_agg = gb.agg(reduction, engine="numba", **kwargs)
+ expected_agg = gb.agg(reduction, engine="cython", **kwargs)
+ tm.assert_frame_equal(res_agg, expected_agg)
+ # Test that calling the aggregation directly also works
+ direct_res = getattr(gb, reduction)(engine="numba", **kwargs)
+ direct_expected = getattr(gb, reduction)(engine="cython", **kwargs)
+ tm.assert_frame_equal(direct_res, direct_expected)
+
+
+def test_multilabel_udf_numba_vs_cython():
+ pytest.importorskip("numba")
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+ gb = df.groupby(["A", "B"])
+ result = gb.agg(lambda values, index: values.min(), engine="numba")
+ expected = gb.agg(lambda x: x.min(), engine="cython")
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_other.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_other.py
new file mode 100644
index 0000000000000000000000000000000000000000..029b6b8bdd7f2fc00d1d942cd439f83c6229875f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/aggregate/test_other.py
@@ -0,0 +1,667 @@
+"""
+test all other .agg behavior
+"""
+
+import datetime as dt
+from functools import partial
+
+import numpy as np
+import pytest
+
+from pandas.errors import SpecificationError
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ PeriodIndex,
+ Series,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+from pandas.io.formats.printing import pprint_thing
+
+
+def test_agg_partial_failure_raises():
+ # GH#43741
+
+ df = DataFrame(
+ {
+ "data1": np.random.default_rng(2).standard_normal(5),
+ "data2": np.random.default_rng(2).standard_normal(5),
+ "key1": ["a", "a", "b", "b", "a"],
+ "key2": ["one", "two", "one", "two", "one"],
+ }
+ )
+ grouped = df.groupby("key1")
+
+ def peak_to_peak(arr):
+ return arr.max() - arr.min()
+
+ with pytest.raises(TypeError, match="unsupported operand type"):
+ grouped.agg([peak_to_peak])
+
+ with pytest.raises(TypeError, match="unsupported operand type"):
+ grouped.agg(peak_to_peak)
+
+
+def test_agg_datetimes_mixed():
+ data = [[1, "2012-01-01", 1.0], [2, "2012-01-02", 2.0], [3, None, 3.0]]
+
+ df1 = DataFrame(
+ {
+ "key": [x[0] for x in data],
+ "date": [x[1] for x in data],
+ "value": [x[2] for x in data],
+ }
+ )
+
+ data = [
+ [
+ row[0],
+ (dt.datetime.strptime(row[1], "%Y-%m-%d").date() if row[1] else None),
+ row[2],
+ ]
+ for row in data
+ ]
+
+ df2 = DataFrame(
+ {
+ "key": [x[0] for x in data],
+ "date": [x[1] for x in data],
+ "value": [x[2] for x in data],
+ }
+ )
+
+ df1["weights"] = df1["value"] / df1["value"].sum()
+ gb1 = df1.groupby("date").aggregate("sum")
+
+ df2["weights"] = df1["value"] / df1["value"].sum()
+ gb2 = df2.groupby("date").aggregate("sum")
+
+ assert len(gb1) == len(gb2)
+
+
+def test_agg_period_index():
+ prng = period_range("2012-1-1", freq="M", periods=3)
+ df = DataFrame(np.random.default_rng(2).standard_normal((3, 2)), index=prng)
+ rs = df.groupby(level=0).sum()
+ assert isinstance(rs.index, PeriodIndex)
+
+ # GH 3579
+ index = period_range(start="1999-01", periods=5, freq="M")
+ s1 = Series(np.random.default_rng(2).random(len(index)), index=index)
+ s2 = Series(np.random.default_rng(2).random(len(index)), index=index)
+ df = DataFrame.from_dict({"s1": s1, "s2": s2})
+ grouped = df.groupby(df.index.month)
+ list(grouped)
+
+
+def test_agg_dict_parameter_cast_result_dtypes():
+ # GH 12821
+
+ df = DataFrame(
+ {
+ "class": ["A", "A", "B", "B", "C", "C", "D", "D"],
+ "time": date_range("1/1/2011", periods=8, freq="h"),
+ }
+ )
+ df.loc[[0, 1, 2, 5], "time"] = None
+
+ # test for `first` function
+ exp = df.loc[[0, 3, 4, 6]].set_index("class")
+ grouped = df.groupby("class")
+ tm.assert_frame_equal(grouped.first(), exp)
+ tm.assert_frame_equal(grouped.agg("first"), exp)
+ tm.assert_frame_equal(grouped.agg({"time": "first"}), exp)
+ tm.assert_series_equal(grouped.time.first(), exp["time"])
+ tm.assert_series_equal(grouped.time.agg("first"), exp["time"])
+
+ # test for `last` function
+ exp = df.loc[[0, 3, 4, 7]].set_index("class")
+ grouped = df.groupby("class")
+ tm.assert_frame_equal(grouped.last(), exp)
+ tm.assert_frame_equal(grouped.agg("last"), exp)
+ tm.assert_frame_equal(grouped.agg({"time": "last"}), exp)
+ tm.assert_series_equal(grouped.time.last(), exp["time"])
+ tm.assert_series_equal(grouped.time.agg("last"), exp["time"])
+
+ # count
+ exp = Series([2, 2, 2, 2], index=Index(list("ABCD"), name="class"), name="time")
+ tm.assert_series_equal(grouped.time.agg(len), exp)
+ tm.assert_series_equal(grouped.time.size(), exp)
+
+ exp = Series([0, 1, 1, 2], index=Index(list("ABCD"), name="class"), name="time")
+ tm.assert_series_equal(grouped.time.count(), exp)
+
+
+def test_agg_cast_results_dtypes():
+ # similar to GH12821
+ # xref #11444
+ u = [dt.datetime(2015, x + 1, 1) for x in range(12)]
+ v = list("aaabbbbbbccd")
+ df = DataFrame({"X": v, "Y": u})
+
+ result = df.groupby("X")["Y"].agg(len)
+ expected = df.groupby("X")["Y"].count()
+ tm.assert_series_equal(result, expected)
+
+
+def test_aggregate_float64_no_int64():
+ # see gh-11199
+ df = DataFrame({"a": [1, 2, 3, 4, 5], "b": [1, 2, 2, 4, 5], "c": [1, 2, 3, 4, 5]})
+
+ expected = DataFrame({"a": [1, 2.5, 4, 5]}, index=[1, 2, 4, 5])
+ expected.index.name = "b"
+
+ result = df.groupby("b")[["a"]].mean()
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame({"a": [1, 2.5, 4, 5], "c": [1, 2.5, 4, 5]}, index=[1, 2, 4, 5])
+ expected.index.name = "b"
+
+ result = df.groupby("b")[["a", "c"]].mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_aggregate_api_consistency():
+ # GH 9052
+ # make sure that the aggregates via dict
+ # are consistent
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "two", "two", "two", "one", "two"],
+ "C": np.random.default_rng(2).standard_normal(8) + 1.0,
+ "D": np.arange(8),
+ }
+ )
+
+ grouped = df.groupby(["A", "B"])
+ c_mean = grouped["C"].mean()
+ c_sum = grouped["C"].sum()
+ d_mean = grouped["D"].mean()
+ d_sum = grouped["D"].sum()
+
+ result = grouped["D"].agg(["sum", "mean"])
+ expected = pd.concat([d_sum, d_mean], axis=1)
+ expected.columns = ["sum", "mean"]
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+ result = grouped.agg(["sum", "mean"])
+ expected = pd.concat([c_sum, c_mean, d_sum, d_mean], axis=1)
+ expected.columns = MultiIndex.from_product([["C", "D"], ["sum", "mean"]])
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+ result = grouped[["D", "C"]].agg(["sum", "mean"])
+ expected = pd.concat([d_sum, d_mean, c_sum, c_mean], axis=1)
+ expected.columns = MultiIndex.from_product([["D", "C"], ["sum", "mean"]])
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+ result = grouped.agg({"C": "mean", "D": "sum"})
+ expected = pd.concat([d_sum, c_mean], axis=1)
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+ result = grouped.agg({"C": ["mean", "sum"], "D": ["mean", "sum"]})
+ expected = pd.concat([c_mean, c_sum, d_mean, d_sum], axis=1)
+ expected.columns = MultiIndex.from_product([["C", "D"], ["mean", "sum"]])
+
+ msg = r"Label\(s\) \['r', 'r2'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ grouped[["D", "C"]].agg({"r": "sum", "r2": "mean"})
+
+
+def test_agg_dict_renaming_deprecation():
+ # 15931
+ df = DataFrame({"A": [1, 1, 1, 2, 2], "B": range(5), "C": range(5)})
+
+ msg = r"nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ df.groupby("A").agg(
+ {"B": {"foo": ["sum", "max"]}, "C": {"bar": ["count", "min"]}}
+ )
+
+ msg = r"Label\(s\) \['ma'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ df.groupby("A")[["B", "C"]].agg({"ma": "max"})
+
+ msg = r"nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ df.groupby("A").B.agg({"foo": "count"})
+
+
+def test_agg_compat():
+ # GH 12334
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "two", "two", "two", "one", "two"],
+ "C": np.random.default_rng(2).standard_normal(8) + 1.0,
+ "D": np.arange(8),
+ }
+ )
+
+ g = df.groupby(["A", "B"])
+
+ msg = r"nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ g["D"].agg({"C": ["sum", "std"]})
+
+ with pytest.raises(SpecificationError, match=msg):
+ g["D"].agg({"C": "sum", "D": "std"})
+
+
+def test_agg_nested_dicts():
+ # API change for disallowing these types of nested dicts
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "two", "two", "two", "one", "two"],
+ "C": np.random.default_rng(2).standard_normal(8) + 1.0,
+ "D": np.arange(8),
+ }
+ )
+
+ g = df.groupby(["A", "B"])
+
+ msg = r"nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ g.aggregate({"r1": {"C": ["mean", "sum"]}, "r2": {"D": ["mean", "sum"]}})
+
+ with pytest.raises(SpecificationError, match=msg):
+ g.agg({"C": {"ra": ["mean", "std"]}, "D": {"rb": ["mean", "std"]}})
+
+ # same name as the original column
+ # GH9052
+ with pytest.raises(SpecificationError, match=msg):
+ g["D"].agg({"result1": np.sum, "result2": np.mean})
+
+ with pytest.raises(SpecificationError, match=msg):
+ g["D"].agg({"D": np.sum, "result2": np.mean})
+
+
+def test_agg_item_by_item_raise_typeerror():
+ df = DataFrame(np.random.default_rng(2).integers(10, size=(20, 10)))
+
+ def raiseException(df):
+ pprint_thing("----------------------------------------")
+ pprint_thing(df.to_string())
+ raise TypeError("test")
+
+ with pytest.raises(TypeError, match="test"):
+ df.groupby(0).agg(raiseException)
+
+
+def test_series_agg_multikey():
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ grouped = ts.groupby([lambda x: x.year, lambda x: x.month])
+
+ result = grouped.agg("sum")
+ expected = grouped.sum()
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_agg_multi_pure_python():
+ data = DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "C": [
+ "dull",
+ "dull",
+ "shiny",
+ "dull",
+ "dull",
+ "shiny",
+ "shiny",
+ "dull",
+ "shiny",
+ "shiny",
+ "shiny",
+ ],
+ "D": np.random.default_rng(2).standard_normal(11),
+ "E": np.random.default_rng(2).standard_normal(11),
+ "F": np.random.default_rng(2).standard_normal(11),
+ }
+ )
+
+ def bad(x):
+ if isinstance(x.values, np.ndarray):
+ assert len(x.values.base) > 0
+ return "foo"
+
+ result = data.groupby(["A", "B"]).agg(bad)
+ expected = data.groupby(["A", "B"]).agg(lambda x: "foo")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_consistency():
+ # agg with ([]) and () not consistent
+ # GH 6715
+ def P1(a):
+ return np.percentile(a.dropna(), q=1)
+
+ df = DataFrame(
+ {
+ "col1": [1, 2, 3, 4],
+ "col2": [10, 25, 26, 31],
+ "date": [
+ dt.date(2013, 2, 10),
+ dt.date(2013, 2, 10),
+ dt.date(2013, 2, 11),
+ dt.date(2013, 2, 11),
+ ],
+ }
+ )
+
+ g = df.groupby("date")
+
+ expected = g.agg([P1])
+ expected.columns = expected.columns.levels[0]
+
+ result = g.agg(P1)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_callables():
+ # GH 7929
+ df = DataFrame({"foo": [1, 2], "bar": [3, 4]}).astype(np.int64)
+
+ class fn_class:
+ def __call__(self, x):
+ return sum(x)
+
+ equiv_callables = [
+ sum,
+ np.sum,
+ lambda x: sum(x),
+ lambda x: x.sum(),
+ partial(sum),
+ fn_class(),
+ ]
+
+ expected = df.groupby("foo").agg("sum")
+ for ecall in equiv_callables:
+ result = df.groupby("foo").agg(ecall)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_over_numpy_arrays():
+ # GH 3788
+ df = DataFrame(
+ [
+ [1, np.array([10, 20, 30])],
+ [1, np.array([40, 50, 60])],
+ [2, np.array([20, 30, 40])],
+ ],
+ columns=["category", "arraydata"],
+ )
+ gb = df.groupby("category")
+
+ expected_data = [[np.array([50, 70, 90])], [np.array([20, 30, 40])]]
+ expected_index = Index([1, 2], name="category")
+ expected_column = ["arraydata"]
+ expected = DataFrame(expected_data, index=expected_index, columns=expected_column)
+
+ alt = gb.sum(numeric_only=False)
+ tm.assert_frame_equal(alt, expected)
+
+ result = gb.agg("sum", numeric_only=False)
+ tm.assert_frame_equal(result, expected)
+
+ # FIXME: the original version of this test called `gb.agg(sum)`
+ # and that raises TypeError if `numeric_only=False` is passed
+
+
+@pytest.mark.parametrize("as_period", [True, False])
+def test_agg_tzaware_non_datetime_result(as_period):
+ # discussed in GH#29589, fixed in GH#29641, operating on tzaware values
+ # with function that is not dtype-preserving
+ dti = date_range("2012-01-01", periods=4, tz="UTC")
+ if as_period:
+ dti = dti.tz_localize(None).to_period("D")
+
+ df = DataFrame({"a": [0, 0, 1, 1], "b": dti})
+ gb = df.groupby("a")
+
+ # Case that _does_ preserve the dtype
+ result = gb["b"].agg(lambda x: x.iloc[0])
+ expected = Series(dti[::2], name="b")
+ expected.index.name = "a"
+ tm.assert_series_equal(result, expected)
+
+ # Cases that do _not_ preserve the dtype
+ result = gb["b"].agg(lambda x: x.iloc[0].year)
+ expected = Series([2012, 2012], name="b")
+ expected.index.name = "a"
+ tm.assert_series_equal(result, expected)
+
+ result = gb["b"].agg(lambda x: x.iloc[-1] - x.iloc[0])
+ expected = Series(
+ [pd.Timedelta(days=1), pd.Timedelta(days=1)], name="b", dtype="m8[us]"
+ )
+ expected.index.name = "a"
+ if as_period:
+ expected = Series([pd.offsets.Day(1), pd.offsets.Day(1)], name="b")
+ expected.index.name = "a"
+ tm.assert_series_equal(result, expected)
+
+
+def test_agg_timezone_round_trip():
+ # GH 15426
+ ts = pd.Timestamp("2016-01-01 12:00:00", tz="US/Pacific")
+ df = DataFrame({"a": 1, "b": [ts + dt.timedelta(minutes=nn) for nn in range(10)]})
+
+ result1 = df.groupby("a")["b"].agg("min").iloc[0]
+ result2 = df.groupby("a")["b"].agg(lambda x: np.min(x)).iloc[0]
+ result3 = df.groupby("a")["b"].min().iloc[0]
+
+ assert result1 == ts
+ assert result2 == ts
+ assert result3 == ts
+
+ dates = [
+ pd.Timestamp(f"2016-01-0{i:d} 12:00:00", tz="US/Pacific") for i in range(1, 5)
+ ]
+ df = DataFrame({"A": ["a", "b"] * 2, "B": dates})
+ grouped = df.groupby("A")
+
+ ts = df["B"].iloc[0]
+ assert ts == grouped.nth(0)["B"].iloc[0]
+ assert ts == grouped.head(1)["B"].iloc[0]
+ assert ts == grouped.first()["B"].iloc[0]
+
+ # GH#27110 applying iloc should return a DataFrame
+ assert ts == grouped.apply(lambda x: x.iloc[0])["B"].iloc[0]
+
+ ts = df["B"].iloc[2]
+ assert ts == grouped.last()["B"].iloc[0]
+
+ # GH#27110 applying iloc should return a DataFrame
+ assert ts == grouped.apply(lambda x: x.iloc[-1])["B"].iloc[0]
+
+
+def test_sum_uint64_overflow():
+ # see gh-14758
+ # Convert to uint64 and don't overflow
+ df = DataFrame([[1, 2], [3, 4], [5, 6]], dtype=object)
+ df = df + 9223372036854775807
+
+ index = Index(
+ [9223372036854775808, 9223372036854775810, 9223372036854775812], dtype=np.uint64
+ )
+ expected = DataFrame(
+ {1: [9223372036854775809, 9223372036854775811, 9223372036854775813]},
+ index=index,
+ dtype=object,
+ )
+
+ expected.index.name = 0
+ result = df.groupby(0).sum(numeric_only=False)
+ tm.assert_frame_equal(result, expected)
+
+ # out column is non-numeric, so with numeric_only=True it is dropped
+ result2 = df.groupby(0).sum(numeric_only=True)
+ expected2 = expected[[]]
+ tm.assert_frame_equal(result2, expected2)
+
+
+@pytest.mark.parametrize(
+ "structure, cast_as",
+ [
+ (tuple, tuple),
+ (list, list),
+ (lambda x: tuple(x), tuple),
+ (lambda x: list(x), list),
+ ],
+)
+def test_agg_structs_dataframe(structure, cast_as):
+ df = DataFrame(
+ {"A": [1, 1, 1, 3, 3, 3], "B": [1, 1, 1, 4, 4, 4], "C": [1, 1, 1, 3, 4, 4]}
+ )
+
+ result = df.groupby(["A", "B"]).aggregate(structure)
+ expected = DataFrame(
+ {"C": {(1, 1): cast_as([1, 1, 1]), (3, 4): cast_as([3, 4, 4])}}
+ )
+ expected.index.names = ["A", "B"]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "structure, cast_as",
+ [
+ (tuple, tuple),
+ (list, list),
+ (lambda x: tuple(x), tuple),
+ (lambda x: list(x), list),
+ ],
+)
+def test_agg_structs_series(structure, cast_as):
+ # Issue #18079
+ df = DataFrame(
+ {"A": [1, 1, 1, 3, 3, 3], "B": [1, 1, 1, 4, 4, 4], "C": [1, 1, 1, 3, 4, 4]}
+ )
+
+ result = df.groupby("A")["C"].aggregate(structure)
+ expected = Series([cast_as([1, 1, 1]), cast_as([3, 4, 4])], index=[1, 3], name="C")
+ expected.index.name = "A"
+ tm.assert_series_equal(result, expected)
+
+
+def test_agg_category_nansum(observed):
+ categories = ["a", "b", "c"]
+ df = DataFrame(
+ {"A": pd.Categorical(["a", "a", "b"], categories=categories), "B": [1, 2, 3]}
+ )
+ result = df.groupby("A", observed=observed).B.agg(np.nansum)
+ expected = Series(
+ [3, 3, 0],
+ index=pd.CategoricalIndex(["a", "b", "c"], categories=categories, name="A"),
+ name="B",
+ )
+ if observed:
+ expected = expected[expected != 0]
+ tm.assert_series_equal(result, expected)
+
+
+def test_agg_list_like_func():
+ # GH 18473
+ df = DataFrame({"A": [str(x) for x in range(3)], "B": [str(x) for x in range(3)]})
+ grouped = df.groupby("A", as_index=False, sort=False)
+ result = grouped.agg({"B": lambda x: list(x)})
+ expected = DataFrame(
+ {"A": [str(x) for x in range(3)], "B": [[str(x)] for x in range(3)]}
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_lambda_with_timezone():
+ # GH 23683
+ df = DataFrame(
+ {
+ "tag": [1, 1],
+ "date": [
+ pd.Timestamp("2018-01-01", tz="UTC"),
+ pd.Timestamp("2018-01-02", tz="UTC"),
+ ],
+ }
+ )
+ result = df.groupby("tag").agg({"date": lambda e: e.head(1)})
+ expected = DataFrame(
+ [pd.Timestamp("2018-01-01", tz="UTC")],
+ index=Index([1], name="tag"),
+ columns=["date"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "err_cls",
+ [
+ NotImplementedError,
+ RuntimeError,
+ KeyError,
+ IndexError,
+ OSError,
+ ValueError,
+ ArithmeticError,
+ AttributeError,
+ ],
+)
+def test_groupby_agg_err_catching(err_cls):
+ # make sure we suppress anything other than TypeError or AssertionError
+ # in _python_agg_general
+
+ # Use a non-standard EA to make sure we don't go down ndarray paths
+ from pandas.tests.extension.decimal.array import (
+ DecimalArray,
+ make_data,
+ to_decimal,
+ )
+
+ data = make_data(5)
+ df = DataFrame(
+ {"id1": [0, 0, 0, 1, 1], "id2": [0, 1, 0, 1, 1], "decimals": DecimalArray(data)}
+ )
+
+ expected = Series(to_decimal([data[0], data[3]]))
+
+ def weird_func(x):
+ # weird function that raise something other than TypeError or IndexError
+ # in _python_agg_general
+ if len(x) == 0:
+ raise err_cls
+ return x.iloc[0]
+
+ result = df["decimals"].groupby(df["id1"]).agg(weird_func)
+ tm.assert_series_equal(result, expected, check_names=False)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..2745f7c2b8d0faf0842d23f1584e9dd079a5ef30
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/conftest.py
@@ -0,0 +1,166 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ date_range,
+)
+from pandas.core.groupby.base import (
+ reduction_kernels,
+ transformation_kernels,
+)
+
+
+@pytest.fixture
+def df():
+ return DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+
+
+@pytest.fixture
+def ts():
+ return Series(
+ np.random.default_rng(2).standard_normal(30),
+ index=date_range("2000-01-01", periods=30, freq="B"),
+ )
+
+
+@pytest.fixture
+def tsframe():
+ return DataFrame(
+ np.random.default_rng(2).standard_normal((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=30, freq="B"),
+ )
+
+
+@pytest.fixture
+def three_group():
+ return DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "C": [
+ "dull",
+ "dull",
+ "shiny",
+ "dull",
+ "dull",
+ "shiny",
+ "shiny",
+ "dull",
+ "shiny",
+ "shiny",
+ "shiny",
+ ],
+ "D": np.random.default_rng(2).standard_normal(11),
+ "E": np.random.default_rng(2).standard_normal(11),
+ "F": np.random.default_rng(2).standard_normal(11),
+ }
+ )
+
+
+@pytest.fixture
+def slice_test_df():
+ data = [
+ [0, "a", "a0_at_0"],
+ [1, "b", "b0_at_1"],
+ [2, "a", "a1_at_2"],
+ [3, "b", "b1_at_3"],
+ [4, "c", "c0_at_4"],
+ [5, "a", "a2_at_5"],
+ [6, "a", "a3_at_6"],
+ [7, "a", "a4_at_7"],
+ ]
+ df = DataFrame(data, columns=["Index", "Group", "Value"])
+ return df.set_index("Index")
+
+
+@pytest.fixture
+def slice_test_grouped(slice_test_df):
+ return slice_test_df.groupby("Group", as_index=False)
+
+
+@pytest.fixture(params=sorted(reduction_kernels))
+def reduction_func(request):
+ """
+ yields the string names of all groupby reduction functions, one at a time.
+ """
+ return request.param
+
+
+@pytest.fixture(params=sorted(transformation_kernels))
+def transformation_func(request):
+ """yields the string names of all groupby transformation functions."""
+ return request.param
+
+
+@pytest.fixture(params=sorted(reduction_kernels) + sorted(transformation_kernels))
+def groupby_func(request):
+ """yields both aggregation and transformation functions."""
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ ("mean", {}),
+ ("var", {"ddof": 1}),
+ ("var", {"ddof": 0}),
+ ("std", {"ddof": 1}),
+ ("std", {"ddof": 0}),
+ ("sum", {}),
+ ("min", {}),
+ ("max", {}),
+ ("sum", {"min_count": 2}),
+ ("min", {"min_count": 2}),
+ ("max", {"min_count": 2}),
+ ],
+ ids=[
+ "mean",
+ "var_1",
+ "var_0",
+ "std_1",
+ "std_0",
+ "sum",
+ "min",
+ "max",
+ "sum-min_count",
+ "min-min_count",
+ "max-min_count",
+ ],
+)
+def numba_supported_reductions(request):
+ """reductions supported with engine='numba'"""
+ return request.param
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_describe.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_describe.py
new file mode 100644
index 0000000000000000000000000000000000000000..72bdfebc5eeb75de8b0beb2eef9770d5178240e0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_describe.py
@@ -0,0 +1,271 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def test_apply_describe_bug(multiindex_dataframe_random_data):
+ grouped = multiindex_dataframe_random_data.groupby(level="first")
+ grouped.describe() # it works!
+
+
+def test_series_describe_multikey():
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ grouped = ts.groupby([lambda x: x.year, lambda x: x.month])
+ result = grouped.describe()
+ tm.assert_series_equal(result["mean"], grouped.mean(), check_names=False)
+ tm.assert_series_equal(result["std"], grouped.std(), check_names=False)
+ tm.assert_series_equal(result["min"], grouped.min(), check_names=False)
+
+
+def test_series_describe_single():
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ grouped = ts.groupby(lambda x: x.month)
+ result = grouped.apply(lambda x: x.describe())
+ expected = grouped.describe().stack()
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("keys", ["key1", ["key1", "key2"]])
+def test_series_describe_as_index(as_index, keys):
+ # GH#49256
+ df = DataFrame(
+ {
+ "key1": ["one", "two", "two", "three", "two"],
+ "key2": ["one", "two", "two", "three", "two"],
+ "foo2": [1, 2, 4, 4, 6],
+ }
+ )
+ gb = df.groupby(keys, as_index=as_index)["foo2"]
+ result = gb.describe()
+ expected = DataFrame(
+ {
+ "key1": ["one", "three", "two"],
+ "count": [1.0, 1.0, 3.0],
+ "mean": [1.0, 4.0, 4.0],
+ "std": [np.nan, np.nan, 2.0],
+ "min": [1.0, 4.0, 2.0],
+ "25%": [1.0, 4.0, 3.0],
+ "50%": [1.0, 4.0, 4.0],
+ "75%": [1.0, 4.0, 5.0],
+ "max": [1.0, 4.0, 6.0],
+ }
+ )
+ if len(keys) == 2:
+ expected.insert(1, "key2", expected["key1"])
+ if as_index:
+ expected = expected.set_index(keys)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_frame_describe_multikey(tsframe):
+ grouped = tsframe.groupby([lambda x: x.year, lambda x: x.month])
+ result = grouped.describe()
+ desc_groups = []
+ for col in tsframe:
+ group = grouped[col].describe()
+ # GH 17464 - Remove duplicate MultiIndex levels
+ group_col = MultiIndex(
+ levels=[Index([col], dtype=tsframe.columns.dtype), group.columns],
+ codes=[[0] * len(group.columns), range(len(group.columns))],
+ )
+ group = DataFrame(group.values, columns=group_col, index=group.index)
+ desc_groups.append(group)
+ expected = pd.concat(desc_groups, axis=1)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_frame_describe_tupleindex():
+ # GH 14848 - regression from 0.19.0 to 0.19.1
+ name = "k"
+ df = DataFrame(
+ {
+ "x": [1, 2, 3, 4, 5] * 3,
+ name: [(0, 0, 1), (0, 1, 0), (1, 0, 0)] * 5,
+ }
+ )
+ result = df.groupby(name).describe()
+ expected = DataFrame(
+ [[5.0, 3.0, 1.581139, 1.0, 2.0, 3.0, 4.0, 5.0]] * 3,
+ index=Index([(0, 0, 1), (0, 1, 0), (1, 0, 0)], tupleize_cols=False, name=name),
+ columns=MultiIndex.from_arrays(
+ [["x"] * 8, ["count", "mean", "std", "min", "25%", "50%", "75%", "max"]]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_frame_describe_unstacked_format():
+ # GH 4792
+ prices = {
+ Timestamp("2011-01-06 10:59:05", tz=None): 24990,
+ Timestamp("2011-01-06 12:43:33", tz=None): 25499,
+ Timestamp("2011-01-06 12:54:09", tz=None): 25499,
+ }
+ volumes = {
+ Timestamp("2011-01-06 10:59:05", tz=None): 1500000000,
+ Timestamp("2011-01-06 12:43:33", tz=None): 5000000000,
+ Timestamp("2011-01-06 12:54:09", tz=None): 100000000,
+ }
+ df = DataFrame({"PRICE": prices, "VOLUME": volumes})
+ result = df.groupby("PRICE").VOLUME.describe()
+ data = [
+ df[df.PRICE == 24990].VOLUME.describe().values.tolist(),
+ df[df.PRICE == 25499].VOLUME.describe().values.tolist(),
+ ]
+ expected = DataFrame(
+ data,
+ index=Index([24990, 25499], name="PRICE"),
+ columns=["count", "mean", "std", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.filterwarnings(
+ "ignore:"
+ "indexing past lexsort depth may impact performance:"
+ "pandas.errors.PerformanceWarning"
+)
+@pytest.mark.parametrize("keys", [["a1"], ["a1", "a2"]])
+def test_describe_with_duplicate_output_column_names(as_index, keys):
+ # GH 35314
+ df = DataFrame(
+ {
+ "a1": [99, 99, 99, 88, 88, 88],
+ "a2": [99, 99, 99, 88, 88, 88],
+ "b": [1, 2, 3, 4, 5, 6],
+ "c": [10, 20, 30, 40, 50, 60],
+ },
+ columns=["a1", "a2", "b", "b"],
+ copy=False,
+ )
+ if keys == ["a1"]:
+ df = df.drop(columns="a2")
+
+ expected = (
+ DataFrame.from_records(
+ [
+ ("b", "count", 3.0, 3.0),
+ ("b", "mean", 5.0, 2.0),
+ ("b", "std", 1.0, 1.0),
+ ("b", "min", 4.0, 1.0),
+ ("b", "25%", 4.5, 1.5),
+ ("b", "50%", 5.0, 2.0),
+ ("b", "75%", 5.5, 2.5),
+ ("b", "max", 6.0, 3.0),
+ ("b", "count", 3.0, 3.0),
+ ("b", "mean", 5.0, 2.0),
+ ("b", "std", 1.0, 1.0),
+ ("b", "min", 4.0, 1.0),
+ ("b", "25%", 4.5, 1.5),
+ ("b", "50%", 5.0, 2.0),
+ ("b", "75%", 5.5, 2.5),
+ ("b", "max", 6.0, 3.0),
+ ],
+ )
+ .set_index([0, 1])
+ .T
+ )
+ expected.columns.names = [None, None]
+ if len(keys) == 2:
+ expected.index = MultiIndex(
+ levels=[[88, 99], [88, 99]], codes=[[0, 1], [0, 1]], names=["a1", "a2"]
+ )
+ else:
+ expected.index = Index([88, 99], name="a1")
+
+ if not as_index:
+ expected = expected.reset_index()
+
+ result = df.groupby(keys, as_index=as_index).describe()
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_describe_duplicate_columns():
+ # GH#50806
+ df = DataFrame([[0, 1, 2, 3]])
+ df.columns = [0, 1, 2, 0]
+ gb = df.groupby(df[1])
+ result = gb.describe(percentiles=[])
+
+ columns = ["count", "mean", "std", "min", "max"]
+ frames = [
+ DataFrame([[1.0, val, np.nan, val, val]], index=[1], columns=columns)
+ for val in (0.0, 2.0, 3.0)
+ ]
+ expected = pd.concat(frames, axis=1)
+ expected.columns = MultiIndex(
+ levels=[[0, 2], columns],
+ codes=[5 * [0] + 5 * [1] + 5 * [0], 3 * list(range(5))],
+ )
+ expected.index.names = [1]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_describe_non_cython_paths():
+ # GH#5610 non-cython calls should not include the grouper
+ # Tests for code not expected to go through cython paths.
+ df = DataFrame(
+ [[1, 2, "foo"], [1, np.nan, "bar"], [3, np.nan, "baz"]],
+ columns=["A", "B", "C"],
+ )
+ gb = df.groupby("A")
+ expected_index = Index([1, 3], name="A")
+ expected_col = MultiIndex(
+ levels=[["B"], ["count", "mean", "std", "min", "25%", "50%", "75%", "max"]],
+ codes=[[0] * 8, list(range(8))],
+ )
+ expected = DataFrame(
+ [
+ [1.0, 2.0, np.nan, 2.0, 2.0, 2.0, 2.0, 2.0],
+ [0.0, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
+ ],
+ index=expected_index,
+ columns=expected_col,
+ )
+ result = gb.describe()
+ tm.assert_frame_equal(result, expected)
+
+ gni = df.groupby("A", as_index=False)
+ expected = expected.reset_index()
+ result = gni.describe()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", [int, float, object])
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"percentiles": [0.10, 0.20, 0.30], "include": "all", "exclude": None},
+ {"percentiles": [0.10, 0.20, 0.30], "include": None, "exclude": ["int"]},
+ {"percentiles": [0.10, 0.20, 0.30], "include": ["int"], "exclude": None},
+ ],
+)
+def test_groupby_empty_dataset(dtype, kwargs):
+ # GH#41575
+ df = DataFrame([[1, 2, 3]], columns=["A", "B", "C"], dtype=dtype)
+ df["B"] = df["B"].astype(int)
+ df["C"] = df["C"].astype(float)
+
+ result = df.iloc[:0].groupby("A").describe(**kwargs)
+ expected = df.groupby("A").describe(**kwargs).reset_index(drop=True).iloc[:0]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[:0].groupby("A").B.describe(**kwargs)
+ expected = df.groupby("A").B.describe(**kwargs).reset_index(drop=True).iloc[:0]
+ expected.index = Index([], dtype=df.columns.dtype)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_groupby_shift_diff.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_groupby_shift_diff.py
new file mode 100644
index 0000000000000000000000000000000000000000..60e5fd9fa186317eb3404c720496348e0a6e0178
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_groupby_shift_diff.py
@@ -0,0 +1,268 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ NaT,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def test_group_shift_with_null_key():
+ # This test is designed to replicate the segfault in issue #13813.
+ n_rows = 1200
+
+ # Generate a moderately large dataframe with occasional missing
+ # values in column `B`, and then group by [`A`, `B`]. This should
+ # force `-1` in `labels` array of `g._grouper.group_info` exactly
+ # at those places, where the group-by key is partially missing.
+ df = DataFrame(
+ [(i % 12, i % 3 if i % 3 else np.nan, i) for i in range(n_rows)],
+ dtype=float,
+ columns=["A", "B", "Z"],
+ index=None,
+ )
+ g = df.groupby(["A", "B"])
+
+ expected = DataFrame(
+ [(i + 12 if i % 3 and i < n_rows - 12 else np.nan) for i in range(n_rows)],
+ dtype=float,
+ columns=["Z"],
+ index=None,
+ )
+ result = g.shift(-1)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_group_shift_with_fill_value():
+ # GH #24128
+ n_rows = 24
+ df = DataFrame(
+ [(i % 12, i % 3, i) for i in range(n_rows)],
+ dtype=float,
+ columns=["A", "B", "Z"],
+ index=None,
+ )
+ g = df.groupby(["A", "B"])
+
+ expected = DataFrame(
+ [(i + 12 if i < n_rows - 12 else 0) for i in range(n_rows)],
+ dtype=float,
+ columns=["Z"],
+ index=None,
+ )
+ result = g.shift(-1, fill_value=0)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_group_shift_lose_timezone():
+ # GH 30134
+ now_dt = Timestamp.now("UTC").as_unit("ns")
+ df = DataFrame({"a": [1, 1], "date": now_dt})
+ result = df.groupby("a").shift(0).iloc[0]
+ expected = Series({"date": now_dt}, name=result.name)
+ tm.assert_series_equal(result, expected)
+
+
+def test_group_diff_real_series(any_real_numpy_dtype):
+ df = DataFrame(
+ {"a": [1, 2, 3, 3, 2], "b": [1, 2, 3, 4, 5]},
+ dtype=any_real_numpy_dtype,
+ )
+ result = df.groupby("a")["b"].diff()
+ exp_dtype = "float"
+ if any_real_numpy_dtype in ["int8", "int16", "float32"]:
+ exp_dtype = "float32"
+ expected = Series([np.nan, np.nan, np.nan, 1.0, 3.0], dtype=exp_dtype, name="b")
+ tm.assert_series_equal(result, expected)
+
+
+def test_group_diff_real_frame(any_real_numpy_dtype):
+ df = DataFrame(
+ {
+ "a": [1, 2, 3, 3, 2],
+ "b": [1, 2, 3, 4, 5],
+ "c": [1, 2, 3, 4, 6],
+ },
+ dtype=any_real_numpy_dtype,
+ )
+ result = df.groupby("a").diff()
+ exp_dtype = "float"
+ if any_real_numpy_dtype in ["int8", "int16", "float32"]:
+ exp_dtype = "float32"
+ expected = DataFrame(
+ {
+ "b": [np.nan, np.nan, np.nan, 1.0, 3.0],
+ "c": [np.nan, np.nan, np.nan, 1.0, 4.0],
+ },
+ dtype=exp_dtype,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ [
+ Timestamp("2013-01-01"),
+ Timestamp("2013-01-02"),
+ Timestamp("2013-01-03"),
+ ],
+ [Timedelta("5 days"), Timedelta("6 days"), Timedelta("7 days")],
+ ],
+)
+def test_group_diff_datetimelike(data, unit):
+ df = DataFrame({"a": [1, 2, 2], "b": data})
+ df["b"] = df["b"].dt.as_unit(unit)
+ result = df.groupby("a")["b"].diff()
+ expected = Series([NaT, NaT, Timedelta("1 days")], name="b").dt.as_unit(unit)
+ tm.assert_series_equal(result, expected)
+
+
+def test_group_diff_bool():
+ df = DataFrame({"a": [1, 2, 3, 3, 2], "b": [True, True, False, False, True]})
+ result = df.groupby("a")["b"].diff()
+ expected = Series([np.nan, np.nan, np.nan, False, False], name="b")
+ tm.assert_series_equal(result, expected)
+
+
+def test_group_diff_object_raises(object_dtype):
+ df = DataFrame(
+ {"a": ["foo", "bar", "bar"], "b": ["baz", "foo", "foo"]}, dtype=object_dtype
+ )
+ with pytest.raises(TypeError, match=r"unsupported operand type\(s\) for -"):
+ df.groupby("a")["b"].diff()
+
+
+def test_empty_shift_with_fill():
+ # GH 41264, single-index check
+ df = DataFrame(columns=["a", "b", "c"])
+ shifted = df.groupby(["a"]).shift(1)
+ shifted_with_fill = df.groupby(["a"]).shift(1, fill_value=0)
+ tm.assert_frame_equal(shifted, shifted_with_fill)
+ tm.assert_index_equal(shifted.index, shifted_with_fill.index)
+
+
+def test_multindex_empty_shift_with_fill():
+ # GH 41264, multi-index check
+ df = DataFrame(columns=["a", "b", "c"])
+ shifted = df.groupby(["a", "b"]).shift(1)
+ shifted_with_fill = df.groupby(["a", "b"]).shift(1, fill_value=0)
+ tm.assert_frame_equal(shifted, shifted_with_fill)
+ tm.assert_index_equal(shifted.index, shifted_with_fill.index)
+
+
+def test_shift_periods_freq():
+ # GH 54093
+ data = {"a": [1, 2, 3, 4, 5, 6], "b": [0, 0, 0, 1, 1, 1]}
+ df = DataFrame(data, index=date_range(start="20100101", periods=6))
+ result = df.groupby(df.index).shift(periods=-2, freq="D")
+ expected = DataFrame(data, index=date_range(start="2009-12-30", periods=6))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_shift_disallow_freq_and_fill_value():
+ # GH 53832
+ data = {"a": [1, 2, 3, 4, 5, 6], "b": [0, 0, 0, 1, 1, 1]}
+ df = DataFrame(data, index=date_range(start="20100101", periods=6))
+ msg = "Passing a 'freq' together with a 'fill_value'"
+ with pytest.raises(ValueError, match=msg):
+ df.groupby(df.index).shift(periods=-2, freq="D", fill_value="1")
+
+
+def test_shift_disallow_suffix_if_periods_is_int():
+ # GH#44424
+ data = {"a": [1, 2, 3, 4, 5, 6], "b": [0, 0, 0, 1, 1, 1]}
+ df = DataFrame(data)
+ msg = "Cannot specify `suffix` if `periods` is an int."
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("b").shift(1, suffix="fails")
+
+
+def test_group_shift_with_multiple_periods():
+ # GH#44424
+ df = DataFrame({"a": [1, 2, 3, 3, 2], "b": [True, True, False, False, True]})
+
+ shifted_df = df.groupby("b")[["a"]].shift([0, 1])
+ expected_df = DataFrame(
+ {"a_0": [1, 2, 3, 3, 2], "a_1": [np.nan, 1.0, np.nan, 3.0, 2.0]}
+ )
+ tm.assert_frame_equal(shifted_df, expected_df)
+
+ # series
+ shifted_series = df.groupby("b")["a"].shift([0, 1])
+ tm.assert_frame_equal(shifted_series, expected_df)
+
+
+def test_group_shift_with_multiple_periods_and_freq():
+ # GH#44424
+ df = DataFrame(
+ {"a": [1, 2, 3, 4, 5], "b": [True, True, False, False, True]},
+ index=date_range("1/1/2000", periods=5, freq="h"),
+ )
+ shifted_df = df.groupby("b")[["a"]].shift(
+ [0, 1],
+ freq="h",
+ )
+ expected_df = DataFrame(
+ {
+ "a_0": [1.0, 2.0, 3.0, 4.0, 5.0, np.nan],
+ "a_1": [
+ np.nan,
+ 1.0,
+ 2.0,
+ 3.0,
+ 4.0,
+ 5.0,
+ ],
+ },
+ index=date_range("1/1/2000", periods=6, freq="h"),
+ )
+ tm.assert_frame_equal(shifted_df, expected_df)
+
+
+def test_group_shift_with_multiple_periods_and_fill_value():
+ # GH#44424
+ df = DataFrame(
+ {"a": [1, 2, 3, 4, 5], "b": [True, True, False, False, True]},
+ )
+ shifted_df = df.groupby("b")[["a"]].shift([0, 1], fill_value=-1)
+ expected_df = DataFrame(
+ {"a_0": [1, 2, 3, 4, 5], "a_1": [-1, 1, -1, 3, 2]},
+ )
+ tm.assert_frame_equal(shifted_df, expected_df)
+
+
+def test_group_shift_with_multiple_periods_and_both_fill_and_freq_deprecated():
+ # GH#44424
+ df = DataFrame(
+ {"a": [1, 2, 3, 4, 5], "b": [True, True, False, False, True]},
+ index=date_range("1/1/2000", periods=5, freq="h"),
+ )
+ msg = "Passing a 'freq' together with a 'fill_value'"
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("b")[["a"]].shift([1, 2], fill_value=1, freq="h")
+
+
+def test_groupby_shift_multiple_periods_unsorted_index():
+ # https://github.com/pandas-dev/pandas/pull/62843
+ idx = date_range("1/1/2000", periods=4, freq="h")
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": [True, True, False]},
+ index=[idx[2], idx[0], idx[1]],
+ )
+ result = df.groupby("b")[["a"]].shift([0, 1], freq="h")
+ expected = DataFrame(
+ {
+ "a_0": [1.0, 2.0, 3.0, np.nan],
+ "a_1": [3.0, np.nan, 2.0, 1.0],
+ },
+ index=[idx[2], idx[0], idx[1], idx[3]],
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_is_monotonic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_is_monotonic.py
new file mode 100644
index 0000000000000000000000000000000000000000..3428fc90f6e51a0bde0aba9c8ea08ebf414e5556
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_is_monotonic.py
@@ -0,0 +1,78 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "in_vals, out_vals",
+ [
+ # Basics: strictly increasing (T), strictly decreasing (F),
+ # abs val increasing (F), non-strictly increasing (T)
+ ([1, 2, 5, 3, 2, 0, 4, 5, -6, 1, 1], [True, False, False, True]),
+ # Test with inf vals
+ (
+ [1, 2.1, np.inf, 3, 2, np.inf, -np.inf, 5, 11, 1, -np.inf],
+ [True, False, True, False],
+ ),
+ # Test with nan vals; should always be False
+ (
+ [1, 2, np.nan, 3, 2, np.nan, np.nan, 5, -np.inf, 1, np.nan],
+ [False, False, False, False],
+ ),
+ ],
+)
+def test_is_monotonic_increasing(in_vals, out_vals):
+ # GH 17015
+ source_dict = {
+ "A": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"],
+ "B": ["a", "a", "a", "b", "b", "b", "c", "c", "c", "d", "d"],
+ "C": in_vals,
+ }
+ df = DataFrame(source_dict)
+ result = df.groupby("B").C.is_monotonic_increasing
+ index = Index(list("abcd"), name="B")
+ expected = Series(index=index, data=out_vals, name="C")
+ tm.assert_series_equal(result, expected)
+
+ # Also check result equal to manually taking x.is_monotonic_increasing.
+ expected = df.groupby(["B"]).C.apply(lambda x: x.is_monotonic_increasing)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "in_vals, out_vals",
+ [
+ # Basics: strictly decreasing (T), strictly increasing (F),
+ # abs val decreasing (F), non-strictly increasing (T)
+ ([10, 9, 7, 3, 4, 5, -3, 2, 0, 1, 1], [True, False, False, True]),
+ # Test with inf vals
+ (
+ [np.inf, 1, -np.inf, np.inf, 2, -3, -np.inf, 5, -3, -np.inf, -np.inf],
+ [True, True, False, True],
+ ),
+ # Test with nan vals; should always be False
+ (
+ [1, 2, np.nan, 3, 2, np.nan, np.nan, 5, -np.inf, 1, np.nan],
+ [False, False, False, False],
+ ),
+ ],
+)
+def test_is_monotonic_decreasing(in_vals, out_vals):
+ # GH 17015
+ source_dict = {
+ "A": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"],
+ "B": ["a", "a", "a", "b", "b", "b", "c", "c", "c", "d", "d"],
+ "C": in_vals,
+ }
+
+ df = DataFrame(source_dict)
+ result = df.groupby("B").C.is_monotonic_decreasing
+ index = Index(list("abcd"), name="B")
+ expected = Series(index=index, data=out_vals, name="C")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_kurt.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_kurt.py
new file mode 100644
index 0000000000000000000000000000000000000000..7aac23c2147fb48d73af223bca489ac0ec1a5fff
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_kurt.py
@@ -0,0 +1,90 @@
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+import pandas._testing as tm
+
+
+def test_groupby_kurt_equivalence():
+ # GH#40139
+ # Test that that groupby kurt method (which uses libgroupby.group_kurt)
+ # matches the results of operating group-by-group (which uses nanops.nankurt)
+ nrows = 1000
+ ngroups = 3
+ ncols = 2
+ nan_frac = 0.05
+
+ arr = np.random.default_rng(2).standard_normal((nrows, ncols))
+ arr[np.random.default_rng(2).random(nrows) < nan_frac] = np.nan
+
+ df = pd.DataFrame(arr)
+ grps = np.random.default_rng(2).integers(0, ngroups, size=nrows)
+ gb = df.groupby(grps)
+
+ result = gb.kurt()
+
+ grpwise = [grp.kurt().to_frame(i).T for i, grp in gb]
+ expected = pd.concat(grpwise, axis=0)
+ expected.index = expected.index.astype("int64") # 32bit builds
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ pytest.param("float64[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ "Float64",
+ ],
+)
+def test_groupby_kurt_arrow_float64(dtype):
+ # GH#40139
+ # Test groupby.kurt() with float64[pyarrow] and Float64 dtypes
+ df = pd.DataFrame(
+ {
+ "x": [1.0, pd.NA, 3.2, 4.8, 2.3, 1.9, 8.9],
+ "y": [1.6, 3.3, 3.2, 6.8, 1.3, 2.9, 9.0],
+ },
+ dtype=dtype,
+ )
+ gb = df.groupby(by=lambda x: 0)
+
+ result = gb.kurt()
+ expected = pd.DataFrame({"x": [2.1644713], "y": [0.1513969]}, dtype=dtype)
+ tm.assert_almost_equal(result, expected)
+
+
+def test_groupby_kurt_noskipna():
+ # GH#40139
+ # Test groupby.kurt() with skipna = False
+ df = pd.DataFrame(
+ {
+ "x": [1.0, np.nan, 3.2, 4.8, 2.3, 1.9, 8.9],
+ "y": [1.6, 3.3, 3.2, 6.8, 1.3, 2.9, 9.0],
+ }
+ )
+ gb = df.groupby(by=lambda x: 0)
+
+ result = gb.kurt(skipna=False)
+ expected = pd.DataFrame({"x": [np.nan], "y": [0.1513969]})
+ tm.assert_almost_equal(result, expected)
+
+
+def test_groupby_kurt_all_ones():
+ # GH#40139
+ # Test groupby.kurt() with constant values
+ df = pd.DataFrame(
+ {
+ "x": [1.0] * 10,
+ }
+ )
+ gb = df.groupby(by=lambda x: 0)
+
+ result = gb.kurt(skipna=False)
+ expected = pd.DataFrame(
+ {
+ "x": [0.0], # Same behavior as pd.DataFrame.kurt()
+ }
+ )
+ tm.assert_almost_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_nlargest_nsmallest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_nlargest_nsmallest.py
new file mode 100644
index 0000000000000000000000000000000000000000..1225c325c4c23eef8cf4d597d31925dcbeaec923
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_nlargest_nsmallest.py
@@ -0,0 +1,114 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ MultiIndex,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def test_nlargest():
+ a = Series([1, 3, 5, 7, 2, 9, 0, 4, 6, 10])
+ b = Series(list("a" * 5 + "b" * 5))
+ gb = a.groupby(b)
+ r = gb.nlargest(3)
+ e = Series(
+ [7, 5, 3, 10, 9, 6],
+ index=MultiIndex.from_arrays([list("aaabbb"), [3, 2, 1, 9, 5, 8]]),
+ )
+ tm.assert_series_equal(r, e)
+
+ a = Series([1, 1, 3, 2, 0, 3, 3, 2, 1, 0])
+ gb = a.groupby(b)
+ e = Series(
+ [3, 2, 1, 3, 3, 2],
+ index=MultiIndex.from_arrays([list("aaabbb"), [2, 3, 1, 6, 5, 7]]),
+ )
+ tm.assert_series_equal(gb.nlargest(3, keep="last"), e)
+
+
+def test_nlargest_mi_grouper():
+ # see gh-21411
+ npr = np.random.default_rng(2)
+
+ dts = date_range("20180101", periods=10)
+ iterables = [dts, ["one", "two"]]
+
+ idx = MultiIndex.from_product(iterables, names=["first", "second"])
+ s = Series(npr.standard_normal(20), index=idx)
+
+ result = s.groupby("first").nlargest(1)
+
+ exp_idx = MultiIndex.from_tuples(
+ [
+ (dts[0], dts[0], "one"),
+ (dts[1], dts[1], "one"),
+ (dts[2], dts[2], "one"),
+ (dts[3], dts[3], "two"),
+ (dts[4], dts[4], "one"),
+ (dts[5], dts[5], "one"),
+ (dts[6], dts[6], "one"),
+ (dts[7], dts[7], "one"),
+ (dts[8], dts[8], "one"),
+ (dts[9], dts[9], "one"),
+ ],
+ names=["first", "first", "second"],
+ )
+
+ exp_values = [
+ 0.18905338179353307,
+ -0.41306354339189344,
+ 1.799707382720902,
+ 0.7738065867276614,
+ 0.28121066979764925,
+ 0.9775674511260357,
+ -0.3288239040579627,
+ 0.45495807124085547,
+ 0.5452887139646817,
+ 0.12682784711186987,
+ ]
+
+ expected = Series(exp_values, index=exp_idx)
+ tm.assert_series_equal(result, expected, check_exact=False, rtol=1e-3)
+
+
+def test_nsmallest():
+ a = Series([1, 3, 5, 7, 2, 9, 0, 4, 6, 10])
+ b = Series(list("a" * 5 + "b" * 5))
+ gb = a.groupby(b)
+ r = gb.nsmallest(3)
+ e = Series(
+ [1, 2, 3, 0, 4, 6],
+ index=MultiIndex.from_arrays([list("aaabbb"), [0, 4, 1, 6, 7, 8]]),
+ )
+ tm.assert_series_equal(r, e)
+
+ a = Series([1, 1, 3, 2, 0, 3, 3, 2, 1, 0])
+ gb = a.groupby(b)
+ e = Series(
+ [0, 1, 1, 0, 1, 2],
+ index=MultiIndex.from_arrays([list("aaabbb"), [4, 1, 0, 9, 8, 7]]),
+ )
+ tm.assert_series_equal(gb.nsmallest(3, keep="last"), e)
+
+
+@pytest.mark.parametrize(
+ "data, groups",
+ [([0, 1, 2, 3], [0, 0, 1, 1]), ([0], [0])],
+)
+@pytest.mark.parametrize("dtype", [None, *tm.ALL_INT_NUMPY_DTYPES])
+def test_nlargest_and_smallest_noop(data, groups, dtype, nselect_method):
+ # GH 15272, GH 16345, GH 29129
+ # Test nlargest/smallest when it results in a noop,
+ # i.e. input is sorted and group size <= n
+ if dtype is not None:
+ data = np.array(data, dtype=dtype)
+ if nselect_method == "nlargest":
+ data = list(reversed(data))
+ ser = Series(data, name="a")
+ result = getattr(ser.groupby(groups), nselect_method)(n=2)
+ expidx = np.array(groups, dtype=int) if isinstance(groups, list) else groups
+ expected = Series(data, index=MultiIndex.from_arrays([expidx, ser.index]), name="a")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_nth.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_nth.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9c8706ec92025cc3dddbfb99c1268b3c7b6182e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_nth.py
@@ -0,0 +1,848 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ isna,
+)
+import pandas._testing as tm
+
+
+def test_first_last_nth(df):
+ # tests for first / last / nth
+ grouped = df.groupby("A")
+ first = grouped.first()
+ expected = df.loc[[1, 0], ["B", "C", "D"]]
+ expected.index = Index(["bar", "foo"], name="A")
+ expected = expected.sort_index()
+ tm.assert_frame_equal(first, expected)
+
+ nth = grouped.nth(0)
+ expected = df.loc[[0, 1]]
+ tm.assert_frame_equal(nth, expected)
+
+ last = grouped.last()
+ expected = df.loc[[5, 7], ["B", "C", "D"]]
+ expected.index = Index(["bar", "foo"], name="A")
+ tm.assert_frame_equal(last, expected)
+
+ nth = grouped.nth(-1)
+ expected = df.iloc[[5, 7]]
+ tm.assert_frame_equal(nth, expected)
+
+ nth = grouped.nth(1)
+ expected = df.iloc[[2, 3]]
+ tm.assert_frame_equal(nth, expected)
+
+ # it works!
+ grouped["B"].first()
+ grouped["B"].last()
+ grouped["B"].nth(0)
+
+ df = df.copy()
+ df.loc[df["A"] == "foo", "B"] = np.nan
+ grouped = df.groupby("A")
+ assert isna(grouped["B"].first()["foo"])
+ assert isna(grouped["B"].last()["foo"])
+ assert isna(grouped["B"].nth(0).iloc[0])
+
+ # v0.14.0 whatsnew
+ df = DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=["A", "B"])
+ g = df.groupby("A")
+ result = g.first()
+ expected = df.iloc[[1, 2]].set_index("A")
+ tm.assert_frame_equal(result, expected)
+
+ expected = df.iloc[[1, 2]]
+ result = g.nth(0, dropna="any")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["first", "last"])
+def test_first_last_with_na_object(method, nulls_fixture):
+ # https://github.com/pandas-dev/pandas/issues/32123
+ groups = DataFrame({"a": [1, 1, 2, 2], "b": [1, 2, 3, nulls_fixture]}).groupby("a")
+ result = getattr(groups, method)()
+
+ if method == "first":
+ values = [1, 3]
+ else:
+ values = [2, 3]
+
+ values = np.array(values, dtype=result["b"].dtype)
+ idx = Index([1, 2], name="a")
+ expected = DataFrame({"b": values}, index=idx)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("index", [0, -1])
+def test_nth_with_na_object(index, nulls_fixture):
+ # https://github.com/pandas-dev/pandas/issues/32123
+ df = DataFrame({"a": [1, 1, 2, 2], "b": [1, 2, 3, nulls_fixture]})
+ groups = df.groupby("a")
+ result = groups.nth(index)
+ expected = df.iloc[[0, 2]] if index == 0 else df.iloc[[1, 3]]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["first", "last"])
+def test_first_last_with_None(method):
+ # https://github.com/pandas-dev/pandas/issues/32800
+ # None should be preserved as object dtype
+ df = DataFrame.from_dict({"id": ["a"], "value": [None]})
+ groups = df.groupby("id", as_index=False)
+ result = getattr(groups, method)()
+
+ tm.assert_frame_equal(result, df)
+
+
+@pytest.mark.parametrize("method", ["first", "last"])
+@pytest.mark.parametrize(
+ "df, expected",
+ [
+ (
+ DataFrame({"id": "a", "value": [None, "foo", np.nan]}),
+ DataFrame({"value": ["foo"]}, index=Index(["a"], name="id")),
+ ),
+ (
+ DataFrame({"id": "a", "value": [np.nan]}, dtype=object),
+ DataFrame({"value": [None]}, index=Index(["a"], name="id")),
+ ),
+ ],
+)
+def test_first_last_with_None_expanded(method, df, expected):
+ # GH 32800, 38286
+ result = getattr(df.groupby("id"), method)()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_first_last_nth_dtypes():
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.array(np.random.default_rng(2).standard_normal(8), dtype="float32"),
+ }
+ )
+ df["E"] = True
+ df["F"] = 1
+
+ # tests for first / last / nth
+ grouped = df.groupby("A")
+ first = grouped.first()
+ expected = df.loc[[1, 0], ["B", "C", "D", "E", "F"]]
+ expected.index = Index(["bar", "foo"], name="A")
+ expected = expected.sort_index()
+ tm.assert_frame_equal(first, expected)
+
+ last = grouped.last()
+ expected = df.loc[[5, 7], ["B", "C", "D", "E", "F"]]
+ expected.index = Index(["bar", "foo"], name="A")
+ expected = expected.sort_index()
+ tm.assert_frame_equal(last, expected)
+
+ nth = grouped.nth(1)
+ expected = df.iloc[[2, 3]]
+ tm.assert_frame_equal(nth, expected)
+
+
+def test_first_last_nth_dtypes2():
+ # GH 2763, first/last shifting dtypes
+ idx = list(range(10))
+ idx.append(9)
+ ser = Series(data=range(11), index=idx, name="IntCol")
+ assert ser.dtype == "int64"
+ f = ser.groupby(level=0).first()
+ assert f.dtype == "int64"
+
+
+def test_first_last_nth_nan_dtype():
+ # GH 33591
+ df = DataFrame({"data": ["A"], "nans": Series([None], dtype=object)})
+ grouped = df.groupby("data")
+
+ expected = df.set_index("data").nans
+ tm.assert_series_equal(grouped.nans.first(), expected)
+ tm.assert_series_equal(grouped.nans.last(), expected)
+
+ expected = df.nans
+ tm.assert_series_equal(grouped.nans.nth(-1), expected)
+ tm.assert_series_equal(grouped.nans.nth(0), expected)
+
+
+def test_first_strings_timestamps():
+ # GH 11244
+ test = DataFrame(
+ {
+ Timestamp("2012-01-01 00:00:00"): ["a", "b"],
+ Timestamp("2012-01-02 00:00:00"): ["c", "d"],
+ "name": ["e", "e"],
+ "aaaa": ["f", "g"],
+ }
+ )
+ result = test.groupby("name").first()
+ expected = DataFrame(
+ [["a", "c", "f"]],
+ columns=Index([Timestamp("2012-01-01"), Timestamp("2012-01-02"), "aaaa"]),
+ index=Index(["e"], name="name"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_nth():
+ df = DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=["A", "B"])
+ gb = df.groupby("A")
+
+ tm.assert_frame_equal(gb.nth(0), df.iloc[[0, 2]])
+ tm.assert_frame_equal(gb.nth(1), df.iloc[[1]])
+ tm.assert_frame_equal(gb.nth(2), df.loc[[]])
+ tm.assert_frame_equal(gb.nth(-1), df.iloc[[1, 2]])
+ tm.assert_frame_equal(gb.nth(-2), df.iloc[[0]])
+ tm.assert_frame_equal(gb.nth(-3), df.loc[[]])
+ tm.assert_series_equal(gb.B.nth(0), df.B.iloc[[0, 2]])
+ tm.assert_series_equal(gb.B.nth(1), df.B.iloc[[1]])
+ tm.assert_frame_equal(gb[["B"]].nth(0), df[["B"]].iloc[[0, 2]])
+
+ tm.assert_frame_equal(gb.nth(0, dropna="any"), df.iloc[[1, 2]])
+ tm.assert_frame_equal(gb.nth(-1, dropna="any"), df.iloc[[1, 2]])
+
+ tm.assert_frame_equal(gb.nth(7, dropna="any"), df.iloc[:0])
+ tm.assert_frame_equal(gb.nth(2, dropna="any"), df.iloc[:0])
+
+
+def test_nth2():
+ # out of bounds, regression from 0.13.1
+ # GH 6621
+ df = DataFrame(
+ {
+ "color": {0: "green", 1: "green", 2: "red", 3: "red", 4: "red"},
+ "food": {0: "ham", 1: "eggs", 2: "eggs", 3: "ham", 4: "pork"},
+ "two": {
+ 0: 1.5456590000000001,
+ 1: -0.070345000000000005,
+ 2: -2.4004539999999999,
+ 3: 0.46206000000000003,
+ 4: 0.52350799999999997,
+ },
+ "one": {
+ 0: 0.56573799999999996,
+ 1: -0.9742360000000001,
+ 2: 1.033801,
+ 3: -0.78543499999999999,
+ 4: 0.70422799999999997,
+ },
+ }
+ ).set_index(["color", "food"])
+
+ result = df.groupby(level=0, as_index=False).nth(2)
+ expected = df.iloc[[-1]]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(level=0, as_index=False).nth(3)
+ expected = df.loc[[]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_nth3():
+ # GH 7559
+ # from the vbench
+ df = DataFrame(np.random.default_rng(2).integers(1, 10, (100, 2)), dtype="int64")
+ ser = df[1]
+ gb = df[0]
+ expected = ser.groupby(gb).first()
+ expected2 = ser.groupby(gb).apply(lambda x: x.iloc[0])
+ tm.assert_series_equal(expected2, expected, check_names=False)
+ assert expected.name == 1
+ assert expected2.name == 1
+
+ # validate first
+ v = ser[gb == 1].iloc[0]
+ assert expected.iloc[0] == v
+ assert expected2.iloc[0] == v
+
+ with pytest.raises(ValueError, match="For a DataFrame"):
+ ser.groupby(gb, sort=False).nth(0, dropna=True)
+
+
+def test_nth4():
+ # doc example
+ df = DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=["A", "B"])
+ gb = df.groupby("A")
+ result = gb.B.nth(0, dropna="all")
+ expected = df.B.iloc[[1, 2]]
+ tm.assert_series_equal(result, expected)
+
+
+def test_nth5():
+ # test multiple nth values
+ df = DataFrame([[1, np.nan], [1, 3], [1, 4], [5, 6], [5, 7]], columns=["A", "B"])
+ gb = df.groupby("A")
+
+ tm.assert_frame_equal(gb.nth(0), df.iloc[[0, 3]])
+ tm.assert_frame_equal(gb.nth([0]), df.iloc[[0, 3]])
+ tm.assert_frame_equal(gb.nth([0, 1]), df.iloc[[0, 1, 3, 4]])
+ tm.assert_frame_equal(gb.nth([0, -1]), df.iloc[[0, 2, 3, 4]])
+ tm.assert_frame_equal(gb.nth([0, 1, 2]), df.iloc[[0, 1, 2, 3, 4]])
+ tm.assert_frame_equal(gb.nth([0, 1, -1]), df.iloc[[0, 1, 2, 3, 4]])
+ tm.assert_frame_equal(gb.nth([2]), df.iloc[[2]])
+ tm.assert_frame_equal(gb.nth([3, 4]), df.loc[[]])
+
+
+def test_nth_bdays(unit):
+ business_dates = pd.date_range(
+ start="4/1/2014", end="6/30/2014", freq="B", unit=unit
+ )
+ df = DataFrame(1, index=business_dates, columns=["a", "b"])
+ # get the first, fourth and last two business days for each month
+ key = [df.index.year, df.index.month]
+ result = df.groupby(key, as_index=False).nth([0, 3, -2, -1])
+ expected_dates = pd.to_datetime(
+ [
+ "2014/4/1",
+ "2014/4/4",
+ "2014/4/29",
+ "2014/4/30",
+ "2014/5/1",
+ "2014/5/6",
+ "2014/5/29",
+ "2014/5/30",
+ "2014/6/2",
+ "2014/6/5",
+ "2014/6/27",
+ "2014/6/30",
+ ]
+ ).as_unit(unit)
+ expected = DataFrame(1, columns=["a", "b"], index=expected_dates)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_nth_multi_grouper(three_group):
+ # PR 9090, related to issue 8979
+ # test nth on multiple groupers
+ grouped = three_group.groupby(["A", "B"])
+ result = grouped.nth(0)
+ expected = three_group.iloc[[0, 3, 4, 7]]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data, expected_first, expected_last",
+ [
+ (
+ {
+ "id": ["A"],
+ "time": Timestamp("2012-02-01 14:00:00", tz="US/Central"),
+ "foo": [1],
+ },
+ {
+ "id": ["A"],
+ "time": Timestamp("2012-02-01 14:00:00", tz="US/Central"),
+ "foo": [1],
+ },
+ {
+ "id": ["A"],
+ "time": Timestamp("2012-02-01 14:00:00", tz="US/Central"),
+ "foo": [1],
+ },
+ ),
+ (
+ {
+ "id": ["A", "B", "A"],
+ "time": [
+ Timestamp("2012-01-01 13:00:00", tz="America/New_York"),
+ Timestamp("2012-02-01 14:00:00", tz="US/Central"),
+ Timestamp("2012-03-01 12:00:00", tz="Europe/London"),
+ ],
+ "foo": [1, 2, 3],
+ },
+ {
+ "id": ["A", "B"],
+ "time": [
+ Timestamp("2012-01-01 13:00:00", tz="America/New_York"),
+ Timestamp("2012-02-01 14:00:00", tz="US/Central"),
+ ],
+ "foo": [1, 2],
+ },
+ {
+ "id": ["A", "B"],
+ "time": [
+ Timestamp("2012-03-01 12:00:00", tz="Europe/London"),
+ Timestamp("2012-02-01 14:00:00", tz="US/Central"),
+ ],
+ "foo": [3, 2],
+ },
+ ),
+ ],
+)
+def test_first_last_tz(data, expected_first, expected_last):
+ # GH15884
+ # Test that the timezone is retained when calling first
+ # or last on groupby with as_index=False
+
+ df = DataFrame(data)
+
+ result = df.groupby("id", as_index=False).first()
+ expected = DataFrame(expected_first)
+ cols = ["id", "time", "foo"]
+ tm.assert_frame_equal(result[cols], expected[cols])
+
+ result = df.groupby("id", as_index=False)["time"].first()
+ tm.assert_frame_equal(result, expected[["id", "time"]])
+
+ result = df.groupby("id", as_index=False).last()
+ expected = DataFrame(expected_last)
+ cols = ["id", "time", "foo"]
+ tm.assert_frame_equal(result[cols], expected[cols])
+
+ result = df.groupby("id", as_index=False)["time"].last()
+ tm.assert_frame_equal(result, expected[["id", "time"]])
+
+
+@pytest.mark.parametrize(
+ "method, ts, alpha",
+ [
+ ["first", Timestamp("2013-01-01", tz="US/Eastern"), "a"],
+ ["last", Timestamp("2013-01-02", tz="US/Eastern"), "b"],
+ ],
+)
+def test_first_last_tz_multi_column(method, ts, alpha, unit):
+ # GH 21603
+ category_string = Series(list("abc")).astype("category")
+ dti = pd.date_range("20130101", periods=3, tz="US/Eastern", unit=unit)
+ df = DataFrame(
+ {
+ "group": [1, 1, 2],
+ "category_string": category_string,
+ "datetimetz": dti,
+ }
+ )
+ result = getattr(df.groupby("group"), method)()
+ expected = DataFrame(
+ {
+ "category_string": pd.Categorical(
+ [alpha, "c"], dtype=category_string.dtype
+ ),
+ "datetimetz": [ts, Timestamp("2013-01-03", tz="US/Eastern")],
+ },
+ index=Index([1, 2], name="group"),
+ )
+ expected["datetimetz"] = expected["datetimetz"].dt.as_unit(unit)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "values",
+ [
+ pd.array([True, False], dtype="boolean"),
+ pd.array([1, 2], dtype="Int64"),
+ pd.to_datetime(["2020-01-01", "2020-02-01"]),
+ pd.to_timedelta([1, 2], unit="D"),
+ ],
+)
+@pytest.mark.parametrize("function", ["first", "last", "min", "max"])
+def test_first_last_extension_array_keeps_dtype(values, function):
+ # https://github.com/pandas-dev/pandas/issues/33071
+ # https://github.com/pandas-dev/pandas/issues/32194
+ df = DataFrame({"a": [1, 2], "b": values})
+ grouped = df.groupby("a")
+ idx = Index([1, 2], name="a")
+ expected_series = Series(values, name="b", index=idx)
+ expected_frame = DataFrame({"b": values}, index=idx)
+
+ result_series = getattr(grouped["b"], function)()
+ tm.assert_series_equal(result_series, expected_series)
+
+ result_frame = grouped.agg({"b": function})
+ tm.assert_frame_equal(result_frame, expected_frame)
+
+
+def test_nth_multi_index_as_expected():
+ # PR 9090, related to issue 8979
+ # test nth on MultiIndex
+ three_group = DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "C": [
+ "dull",
+ "dull",
+ "shiny",
+ "dull",
+ "dull",
+ "shiny",
+ "shiny",
+ "dull",
+ "shiny",
+ "shiny",
+ "shiny",
+ ],
+ }
+ )
+ grouped = three_group.groupby(["A", "B"])
+ result = grouped.nth(0)
+ expected = three_group.iloc[[0, 3, 4, 7]]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "op, n, expected_rows",
+ [
+ ("head", -1, [0]),
+ ("head", 0, []),
+ ("head", 1, [0, 2]),
+ ("head", 7, [0, 1, 2]),
+ ("tail", -1, [1]),
+ ("tail", 0, []),
+ ("tail", 1, [1, 2]),
+ ("tail", 7, [0, 1, 2]),
+ ],
+)
+@pytest.mark.parametrize("columns", [None, [], ["A"], ["B"], ["A", "B"]])
+def test_groupby_head_tail(op, n, expected_rows, columns, as_index):
+ df = DataFrame([[1, 2], [1, 4], [5, 6]], columns=["A", "B"])
+ g = df.groupby("A", as_index=as_index)
+ expected = df.iloc[expected_rows]
+ if columns is not None:
+ g = g[columns]
+ expected = expected[columns]
+ result = getattr(g, op)(n)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_group_selection_cache():
+ # GH 12839 nth, head, and tail should return same result consistently
+ df = DataFrame([[1, 2], [1, 4], [5, 6]], columns=["A", "B"])
+ expected = df.iloc[[0, 2]]
+
+ g = df.groupby("A")
+ result1 = g.head(n=2)
+ result2 = g.nth(0)
+ tm.assert_frame_equal(result1, df)
+ tm.assert_frame_equal(result2, expected)
+
+ g = df.groupby("A")
+ result1 = g.tail(n=2)
+ result2 = g.nth(0)
+ tm.assert_frame_equal(result1, df)
+ tm.assert_frame_equal(result2, expected)
+
+ g = df.groupby("A")
+ result1 = g.nth(0)
+ result2 = g.head(n=2)
+ tm.assert_frame_equal(result1, expected)
+ tm.assert_frame_equal(result2, df)
+
+ g = df.groupby("A")
+ result1 = g.nth(0)
+ result2 = g.tail(n=2)
+ tm.assert_frame_equal(result1, expected)
+ tm.assert_frame_equal(result2, df)
+
+
+def test_nth_empty():
+ # GH 16064
+ df = DataFrame(index=[0], columns=["a", "b", "c"])
+ result = df.groupby("a").nth(10)
+ expected = df.iloc[:0]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(["a", "b"]).nth(10)
+ expected = df.iloc[:0]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_nth_column_order():
+ # GH 20760
+ # Check that nth preserves column order
+ df = DataFrame(
+ [[1, "b", 100], [1, "a", 50], [1, "a", np.nan], [2, "c", 200], [2, "d", 150]],
+ columns=["A", "C", "B"],
+ )
+ result = df.groupby("A").nth(0)
+ expected = df.iloc[[0, 3]]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("A").nth(-1, dropna="any")
+ expected = df.iloc[[1, 4]]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dropna", [None, "any", "all"])
+def test_nth_nan_in_grouper(dropna):
+ # GH 26011
+ df = DataFrame(
+ {
+ "a": [np.nan, "a", np.nan, "b", np.nan],
+ "b": [0, 2, 4, 6, 8],
+ "c": [1, 3, 5, 7, 9],
+ }
+ )
+ result = df.groupby("a").nth(0, dropna=dropna)
+ expected = df.iloc[[1, 3]]
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dropna", [None, "any", "all"])
+def test_nth_nan_in_grouper_series(dropna):
+ # GH 26454
+ df = DataFrame(
+ {
+ "a": [np.nan, "a", np.nan, "b", np.nan],
+ "b": [0, 2, 4, 6, 8],
+ }
+ )
+ result = df.groupby("a")["b"].nth(0, dropna=dropna)
+ expected = df["b"].iloc[[1, 3]]
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_first_categorical_and_datetime_data_nat():
+ # GH 20520
+ df = DataFrame(
+ {
+ "group": ["first", "first", "second", "third", "third"],
+ "time": 5 * [np.datetime64("NaT")],
+ "categories": Series(["a", "b", "c", "a", "b"], dtype="category"),
+ }
+ )
+ result = df.groupby("group").first()
+ expected = DataFrame(
+ {
+ "time": 3 * [np.datetime64("NaT")],
+ "categories": Series(["a", "c", "a"]).astype(
+ pd.CategoricalDtype(["a", "b", "c"])
+ ),
+ }
+ )
+ expected.index = Index(["first", "second", "third"], name="group")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_first_multi_key_groupby_categorical():
+ # GH 22512
+ df = DataFrame(
+ {
+ "A": [1, 1, 1, 2, 2],
+ "B": [100, 100, 200, 100, 100],
+ "C": ["apple", "orange", "mango", "mango", "orange"],
+ "D": ["jupiter", "mercury", "mars", "venus", "venus"],
+ }
+ )
+ df = df.astype({"D": "category"})
+ result = df.groupby(by=["A", "B"]).first()
+ expected = DataFrame(
+ {
+ "C": ["apple", "mango", "mango"],
+ "D": Series(["jupiter", "mars", "venus"]).astype(
+ pd.CategoricalDtype(["jupiter", "mars", "mercury", "venus"])
+ ),
+ }
+ )
+ expected.index = MultiIndex.from_tuples(
+ [(1, 100), (1, 200), (2, 100)], names=["A", "B"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["first", "last", "nth"])
+def test_groupby_last_first_nth_with_none(method, nulls_fixture):
+ # GH29645
+ expected = Series(["y"], dtype=object)
+ data = Series(
+ [nulls_fixture, nulls_fixture, nulls_fixture, "y", nulls_fixture],
+ index=[0, 0, 0, 0, 0],
+ dtype=object,
+ ).groupby(level=0)
+
+ if method == "nth":
+ result = getattr(data, method)(3)
+ else:
+ result = getattr(data, method)()
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "arg, expected_rows",
+ [
+ [slice(None, 3, 2), [0, 1, 4, 5]],
+ [slice(None, -2), [0, 2, 5]],
+ [[slice(None, 2), slice(-2, None)], [0, 1, 2, 3, 4, 6, 7]],
+ [[0, 1, slice(-2, None)], [0, 1, 2, 3, 4, 6, 7]],
+ ],
+)
+def test_slice(slice_test_df, slice_test_grouped, arg, expected_rows):
+ # Test slices GH #42947
+
+ result = slice_test_grouped.nth[arg]
+ equivalent = slice_test_grouped.nth(arg)
+ expected = slice_test_df.iloc[expected_rows]
+
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(equivalent, expected)
+
+
+def test_nth_indexed(slice_test_df, slice_test_grouped):
+ # Test index notation GH #44688
+
+ result = slice_test_grouped.nth[0, 1, -2:]
+ equivalent = slice_test_grouped.nth([0, 1, slice(-2, None)])
+ expected = slice_test_df.iloc[[0, 1, 2, 3, 4, 6, 7]]
+
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(equivalent, expected)
+
+
+def test_invalid_argument(slice_test_grouped):
+ # Test for error on invalid argument
+
+ with pytest.raises(TypeError, match="Invalid index"):
+ slice_test_grouped.nth(3.14)
+
+
+def test_negative_step(slice_test_grouped):
+ # Test for error on negative slice step
+
+ with pytest.raises(ValueError, match="Invalid step"):
+ slice_test_grouped.nth(slice(None, None, -1))
+
+
+def test_np_ints(slice_test_df, slice_test_grouped):
+ # Test np ints work
+
+ result = slice_test_grouped.nth(np.array([0, 1]))
+ expected = slice_test_df.iloc[[0, 1, 2, 3, 4]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_nth_interval():
+ # GH#24205
+ idx_result = MultiIndex(
+ [
+ pd.CategoricalIndex([pd.Interval(0, 1), pd.Interval(1, 2)]),
+ pd.CategoricalIndex([pd.Interval(0, 10), pd.Interval(10, 20)]),
+ ],
+ [[0, 0, 0, 1, 1], [0, 1, 1, 0, -1]],
+ )
+ df_result = DataFrame({"col": range(len(idx_result))}, index=idx_result)
+ result = df_result.groupby(level=[0, 1], observed=False).nth(0)
+ val_expected = [0, 1, 3]
+ idx_expected = MultiIndex(
+ [
+ pd.CategoricalIndex([pd.Interval(0, 1), pd.Interval(1, 2)]),
+ pd.CategoricalIndex([pd.Interval(0, 10), pd.Interval(10, 20)]),
+ ],
+ [[0, 0, 1], [0, 1, 0]],
+ )
+ expected = DataFrame(val_expected, index=idx_expected, columns=["col"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in remainder:RuntimeWarning"
+)
+def test_head_tail_dropna_true():
+ # GH#45089
+ df = DataFrame(
+ [["a", "z"], ["b", np.nan], ["c", np.nan], ["c", np.nan]], columns=["X", "Y"]
+ )
+ expected = DataFrame([["a", "z"]], columns=["X", "Y"])
+
+ result = df.groupby(["X", "Y"]).head(n=1)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(["X", "Y"]).tail(n=1)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(["X", "Y"]).nth(n=0)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_head_tail_dropna_false():
+ # GH#45089
+ df = DataFrame([["a", "z"], ["b", np.nan], ["c", np.nan]], columns=["X", "Y"])
+ expected = DataFrame([["a", "z"], ["b", np.nan], ["c", np.nan]], columns=["X", "Y"])
+
+ result = df.groupby(["X", "Y"], dropna=False).head(n=1)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(["X", "Y"], dropna=False).tail(n=1)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(["X", "Y"], dropna=False).nth(n=0)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("selection", ("b", ["b"], ["b", "c"]))
+@pytest.mark.parametrize("dropna", ["any", "all", None])
+def test_nth_after_selection(selection, dropna):
+ # GH#11038, GH#53518
+ df = DataFrame(
+ {
+ "a": [1, 1, 2],
+ "b": [np.nan, 3, 4],
+ "c": [5, 6, 7],
+ }
+ )
+ gb = df.groupby("a")[selection]
+ result = gb.nth(0, dropna=dropna)
+ if dropna == "any" or (dropna == "all" and selection != ["b", "c"]):
+ locs = [1, 2]
+ else:
+ locs = [0, 2]
+ expected = df.loc[locs, selection]
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ (
+ Timestamp("2011-01-15 12:50:28.502376"),
+ Timestamp("2011-01-20 12:50:28.593448"),
+ ),
+ (24650000000000001, 24650000000000002),
+ ],
+)
+def test_groupby_nth_int_like_precision(data):
+ # GH#6620, GH#9311
+ df = DataFrame({"a": [1, 1], "b": data})
+
+ grouped = df.groupby("a")
+ result = grouped.nth(0)
+ expected = DataFrame({"a": 1, "b": [data[0]]})
+
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_quantile.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_quantile.py
new file mode 100644
index 0000000000000000000000000000000000000000..02a78c3c1308fca08052ac154fd53894069ead28
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_quantile.py
@@ -0,0 +1,459 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "interpolation", ["linear", "lower", "higher", "nearest", "midpoint"]
+)
+@pytest.mark.parametrize(
+ "a_vals,b_vals",
+ [
+ # Ints
+ ([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]),
+ ([1, 2, 3, 4], [4, 3, 2, 1]),
+ ([1, 2, 3, 4, 5], [4, 3, 2, 1]),
+ # Floats
+ ([1.0, 2.0, 3.0, 4.0, 5.0], [5.0, 4.0, 3.0, 2.0, 1.0]),
+ # Missing data
+ ([1.0, np.nan, 3.0, np.nan, 5.0], [5.0, np.nan, 3.0, np.nan, 1.0]),
+ ([np.nan, 4.0, np.nan, 2.0, np.nan], [np.nan, 4.0, np.nan, 2.0, np.nan]),
+ # Timestamps
+ (
+ pd.date_range("1/1/18", freq="D", periods=5),
+ pd.date_range("1/1/18", freq="D", periods=5)[::-1],
+ ),
+ (
+ pd.date_range("1/1/18", freq="D", periods=5).as_unit("s"),
+ pd.date_range("1/1/18", freq="D", periods=5)[::-1].as_unit("s"),
+ ),
+ # All NA
+ ([np.nan] * 5, [np.nan] * 5),
+ ],
+)
+@pytest.mark.parametrize("q", [0, 0.25, 0.5, 0.75, 1])
+def test_quantile(interpolation, a_vals, b_vals, q):
+ all_vals = pd.concat([pd.Series(a_vals), pd.Series(b_vals)])
+
+ a_expected = pd.Series(a_vals).quantile(q, interpolation=interpolation)
+ b_expected = pd.Series(b_vals).quantile(q, interpolation=interpolation)
+
+ df = DataFrame({"key": ["a"] * len(a_vals) + ["b"] * len(b_vals), "val": all_vals})
+
+ expected = DataFrame(
+ [a_expected, b_expected], columns=["val"], index=Index(["a", "b"], name="key")
+ )
+ if all_vals.dtype.kind == "M" and expected.dtypes.values[0].kind == "M":
+ # TODO(non-nano): this should be unnecessary once array_to_datetime
+ # correctly infers non-nano from Timestamp.unit
+ expected = expected.astype(all_vals.dtype)
+ result = df.groupby("key").quantile(q, interpolation=interpolation)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_quantile_array():
+ # https://github.com/pandas-dev/pandas/issues/27526
+ df = DataFrame({"A": [0, 1, 2, 3, 4]})
+ key = np.array([0, 0, 1, 1, 1], dtype=np.int64)
+ result = df.groupby(key).quantile([0.25])
+
+ index = pd.MultiIndex.from_product([[0, 1], [0.25]])
+ expected = DataFrame({"A": [0.25, 2.50]}, index=index)
+ tm.assert_frame_equal(result, expected)
+
+ df = DataFrame({"A": [0, 1, 2, 3], "B": [4, 5, 6, 7]})
+ index = pd.MultiIndex.from_product([[0, 1], [0.25, 0.75]])
+
+ key = np.array([0, 0, 1, 1], dtype=np.int64)
+ result = df.groupby(key).quantile([0.25, 0.75])
+ expected = DataFrame(
+ {"A": [0.25, 0.75, 2.25, 2.75], "B": [4.25, 4.75, 6.25, 6.75]}, index=index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_quantile_array2():
+ # https://github.com/pandas-dev/pandas/pull/28085#issuecomment-524066959
+ arr = np.random.default_rng(2).integers(0, 5, size=(10, 3), dtype=np.int64)
+ df = DataFrame(arr, columns=list("ABC"))
+ result = df.groupby("A").quantile([0.3, 0.7])
+ expected = DataFrame(
+ {
+ "B": [2.0, 2.0, 2.3, 2.7, 0.3, 0.7, 3.2, 4.0, 0.3, 0.7],
+ "C": [1.0, 1.0, 1.9, 3.0999999999999996, 0.3, 0.7, 2.6, 3.0, 1.2, 2.8],
+ },
+ index=pd.MultiIndex.from_product(
+ [[0, 1, 2, 3, 4], [0.3, 0.7]], names=["A", None]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_quantile_array_no_sort():
+ df = DataFrame({"A": [0, 1, 2], "B": [3, 4, 5]})
+ key = np.array([1, 0, 1], dtype=np.int64)
+ result = df.groupby(key, sort=False).quantile([0.25, 0.5, 0.75])
+ expected = DataFrame(
+ {"A": [0.5, 1.0, 1.5, 1.0, 1.0, 1.0], "B": [3.5, 4.0, 4.5, 4.0, 4.0, 4.0]},
+ index=pd.MultiIndex.from_product([[1, 0], [0.25, 0.5, 0.75]]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(key, sort=False).quantile([0.75, 0.25])
+ expected = DataFrame(
+ {"A": [1.5, 0.5, 1.0, 1.0], "B": [4.5, 3.5, 4.0, 4.0]},
+ index=pd.MultiIndex.from_product([[1, 0], [0.75, 0.25]]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_quantile_array_multiple_levels():
+ df = DataFrame(
+ {"A": [0, 1, 2], "B": [3, 4, 5], "c": ["a", "a", "a"], "d": ["a", "a", "b"]}
+ )
+ result = df.groupby(["c", "d"]).quantile([0.25, 0.75])
+ index = pd.MultiIndex.from_tuples(
+ [("a", "a", 0.25), ("a", "a", 0.75), ("a", "b", 0.25), ("a", "b", 0.75)],
+ names=["c", "d", None],
+ )
+ expected = DataFrame(
+ {"A": [0.25, 0.75, 2.0, 2.0], "B": [3.25, 3.75, 5.0, 5.0]}, index=index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("frame_size", [(2, 3), (100, 10)])
+@pytest.mark.parametrize("groupby", [[0], [0, 1]])
+@pytest.mark.parametrize("q", [[0.5, 0.6]])
+def test_groupby_quantile_with_arraylike_q_and_int_columns(frame_size, groupby, q):
+ # GH30289
+ nrow, ncol = frame_size
+ df = DataFrame(np.array([ncol * [_ % 4] for _ in range(nrow)]), columns=range(ncol))
+
+ idx_levels = [np.arange(min(nrow, 4))] * len(groupby) + [q]
+ idx_codes = [[x for x in range(min(nrow, 4)) for _ in q]] * len(groupby) + [
+ list(range(len(q))) * min(nrow, 4)
+ ]
+ expected_index = pd.MultiIndex(
+ levels=idx_levels, codes=idx_codes, names=[*groupby, None]
+ )
+ expected_values = [
+ [float(x)] * (ncol - len(groupby)) for x in range(min(nrow, 4)) for _ in q
+ ]
+ expected_columns = [x for x in range(ncol) if x not in groupby]
+ expected = DataFrame(
+ expected_values, index=expected_index, columns=expected_columns
+ )
+ result = df.groupby(groupby).quantile(q)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_quantile_raises():
+ df = DataFrame([["foo", "a"], ["foo", "b"], ["foo", "c"]], columns=["key", "val"])
+
+ msg = "dtype '(object|str)' does not support operation 'quantile'"
+ with pytest.raises(TypeError, match=msg):
+ df.groupby("key").quantile()
+
+
+def test_quantile_out_of_bounds_q_raises():
+ # https://github.com/pandas-dev/pandas/issues/27470
+ df = DataFrame({"a": [0, 0, 0, 1, 1, 1], "b": range(6)})
+ g = df.groupby([0, 0, 0, 1, 1, 1])
+ with pytest.raises(ValueError, match="Got '50.0' instead"):
+ g.quantile(50)
+
+ with pytest.raises(ValueError, match="Got '-1.0' instead"):
+ g.quantile(-1)
+
+
+def test_quantile_missing_group_values_no_segfaults():
+ # GH 28662
+ data = np.array([1.0, np.nan, 1.0])
+ df = DataFrame({"key": data, "val": range(3)})
+
+ # Random segfaults; would have been guaranteed in loop
+ grp = df.groupby("key")
+ for _ in range(100):
+ grp.quantile()
+
+
+@pytest.mark.parametrize(
+ "key, val, expected_key, expected_val",
+ [
+ ([1.0, np.nan, 3.0, np.nan], range(4), [1.0, 3.0], [0.0, 2.0]),
+ ([1.0, np.nan, 2.0, 2.0], range(4), [1.0, 2.0], [0.0, 2.5]),
+ (["a", "b", "b", np.nan], range(4), ["a", "b"], [0, 1.5]),
+ ([0], [42], [0], [42.0]),
+ ([], [], np.array([], dtype="float64"), np.array([], dtype="float64")),
+ ],
+)
+def test_quantile_missing_group_values_correct_results(
+ key, val, expected_key, expected_val
+):
+ # GH 28662, GH 33200, GH 33569
+ df = DataFrame({"key": key, "val": val})
+
+ expected = DataFrame(
+ expected_val, index=Index(expected_key, name="key"), columns=["val"]
+ )
+
+ grp = df.groupby("key")
+
+ result = grp.quantile(0.5)
+ tm.assert_frame_equal(result, expected)
+
+ result = grp.quantile()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "values",
+ [
+ pd.array([1, 0, None] * 2, dtype="Int64"),
+ pd.array([True, False, None] * 2, dtype="boolean"),
+ ],
+)
+@pytest.mark.parametrize("q", [0.5, [0.0, 0.5, 1.0]])
+def test_groupby_quantile_nullable_array(values, q):
+ # https://github.com/pandas-dev/pandas/issues/33136
+ df = DataFrame({"a": ["x"] * 3 + ["y"] * 3, "b": values})
+ result = df.groupby("a")["b"].quantile(q)
+
+ if isinstance(q, list):
+ idx = pd.MultiIndex.from_product((["x", "y"], q), names=["a", None])
+ true_quantiles = [0.0, 0.5, 1.0]
+ else:
+ idx = Index(["x", "y"], name="a")
+ true_quantiles = [0.5]
+
+ expected = pd.Series(true_quantiles * 2, index=idx, name="b", dtype="Float64")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("q", [0.5, [0.0, 0.5, 1.0]])
+@pytest.mark.parametrize("numeric_only", [True, False])
+def test_groupby_quantile_raises_on_invalid_dtype(q, numeric_only):
+ df = DataFrame({"a": [1], "b": [2.0], "c": ["x"]})
+ if numeric_only:
+ result = df.groupby("a").quantile(q, numeric_only=numeric_only)
+ expected = df.groupby("a")[["b"]].quantile(q)
+ tm.assert_frame_equal(result, expected)
+ else:
+ msg = "dtype '.*' does not support operation 'quantile'"
+ with pytest.raises(TypeError, match=msg):
+ df.groupby("a").quantile(q, numeric_only=numeric_only)
+
+
+def test_groupby_quantile_NA_float(any_float_dtype):
+ # GH#42849
+ dtype = pd.Series([], dtype=any_float_dtype).dtype
+ item = np.nan if isinstance(dtype, np.dtype) else pd.NA
+ df = DataFrame({"x": [1, 1], "y": [0.2, item]}, dtype=any_float_dtype)
+ result = df.groupby("x")["y"].quantile(0.5)
+ exp_index = Index([1.0], dtype=any_float_dtype, name="x")
+
+ if any_float_dtype in ["Float32", "Float64"]:
+ expected_dtype = any_float_dtype
+ else:
+ expected_dtype = None
+
+ expected = pd.Series([0.2], dtype=expected_dtype, index=exp_index, name="y")
+ tm.assert_series_equal(result, expected)
+
+ result = df.groupby("x")["y"].quantile([0.5, 0.75])
+ expected = pd.Series(
+ [0.2] * 2,
+ index=pd.MultiIndex.from_product((exp_index, [0.5, 0.75]), names=["x", None]),
+ name="y",
+ dtype=expected_dtype,
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_quantile_NA_int(any_int_ea_dtype):
+ # GH#42849
+ df = DataFrame({"x": [1, 1], "y": [2, 5]}, dtype=any_int_ea_dtype)
+ result = df.groupby("x")["y"].quantile(0.5)
+ expected = pd.Series(
+ [3.5],
+ dtype="Float64",
+ index=Index([1], name="x", dtype=any_int_ea_dtype),
+ name="y",
+ )
+ tm.assert_series_equal(expected, result)
+
+ result = df.groupby("x").quantile(0.5)
+ expected = DataFrame(
+ {"y": 3.5}, dtype="Float64", index=Index([1], name="x", dtype=any_int_ea_dtype)
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "interpolation, val1, val2", [("lower", 2, 2), ("higher", 2, 3), ("nearest", 2, 2)]
+)
+def test_groupby_quantile_all_na_group_masked(
+ interpolation, val1, val2, any_numeric_ea_dtype
+):
+ # GH#37493
+ df = DataFrame(
+ {"a": [1, 1, 1, 2], "b": [1, 2, 3, pd.NA]}, dtype=any_numeric_ea_dtype
+ )
+ result = df.groupby("a").quantile(q=[0.5, 0.7], interpolation=interpolation)
+ expected = DataFrame(
+ {"b": [val1, val2, pd.NA, pd.NA]},
+ dtype=any_numeric_ea_dtype,
+ index=pd.MultiIndex.from_arrays(
+ [pd.Series([1, 1, 2, 2], dtype=any_numeric_ea_dtype), [0.5, 0.7, 0.5, 0.7]],
+ names=["a", None],
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("interpolation", ["midpoint", "linear"])
+def test_groupby_quantile_all_na_group_masked_interp(
+ interpolation, any_numeric_ea_dtype
+):
+ # GH#37493
+ df = DataFrame(
+ {"a": [1, 1, 1, 2], "b": [1, 2, 3, pd.NA]}, dtype=any_numeric_ea_dtype
+ )
+ result = df.groupby("a").quantile(q=[0.5, 0.75], interpolation=interpolation)
+
+ if any_numeric_ea_dtype == "Float32":
+ expected_dtype = any_numeric_ea_dtype
+ else:
+ expected_dtype = "Float64"
+
+ expected = DataFrame(
+ {"b": [2.0, 2.5, pd.NA, pd.NA]},
+ dtype=expected_dtype,
+ index=pd.MultiIndex.from_arrays(
+ [
+ pd.Series([1, 1, 2, 2], dtype=any_numeric_ea_dtype),
+ [0.5, 0.75, 0.5, 0.75],
+ ],
+ names=["a", None],
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["Float64", "Float32"])
+def test_groupby_quantile_allNA_column(dtype):
+ # GH#42849
+ df = DataFrame({"x": [1, 1], "y": [pd.NA] * 2}, dtype=dtype)
+ result = df.groupby("x")["y"].quantile(0.5)
+ expected = pd.Series(
+ [pd.NA], dtype=dtype, index=Index([1.0], dtype=dtype), name="y"
+ )
+ expected.index.name = "x"
+ tm.assert_series_equal(expected, result)
+
+
+def test_groupby_timedelta_quantile():
+ # GH: 29485
+ tdi = pd.to_timedelta(np.arange(4), unit="s").as_unit("us")
+ df = DataFrame({"value": tdi, "group": [1, 1, 2, 2]})
+ result = df.groupby("group").quantile(0.99)
+ expected = DataFrame(
+ {
+ "value": [
+ pd.Timedelta("0 days 00:00:00.990000"),
+ pd.Timedelta("0 days 00:00:02.990000"),
+ ]
+ },
+ index=Index([1, 2], name="group"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_timestamp_groupby_quantile(unit):
+ # GH 33168
+ dti = pd.date_range(
+ start="2020-04-19 00:00:00", freq="1min", periods=100, tz="UTC", unit=unit
+ ).floor("1h")
+ df = DataFrame(
+ {
+ "timestamp": dti,
+ "category": list(range(1, 101)),
+ "value": list(range(101, 201)),
+ }
+ )
+
+ result = df.groupby("timestamp").quantile([0.2, 0.8])
+
+ mi = pd.MultiIndex.from_product([dti[::99], [0.2, 0.8]], names=("timestamp", None))
+ expected = DataFrame(
+ [
+ {"category": 12.8, "value": 112.8},
+ {"category": 48.2, "value": 148.2},
+ {"category": 68.8, "value": 168.8},
+ {"category": 92.2, "value": 192.2},
+ ],
+ index=mi,
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_quantile_dt64tz_period():
+ # GH#51373
+ dti = pd.date_range("2016-01-01", periods=1000, unit="ns")
+ df = pd.Series(dti).to_frame().copy()
+ df[1] = dti.tz_localize("US/Pacific")
+ df[2] = dti.to_period("D")
+ df[3] = dti - dti[0]
+ df.iloc[-1] = pd.NaT
+
+ by = np.tile(np.arange(5), 200)
+ gb = df.groupby(by)
+
+ result = gb.quantile(0.5)
+
+ # Check that we match the group-by-group result
+ exp = {i: df.iloc[i::5].quantile(0.5) for i in range(5)}
+ expected = DataFrame(exp).T.infer_objects()
+ expected.index = expected.index.astype(int)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_quantile_nonmulti_levels_order():
+ # Non-regression test for GH #53009
+ ind = pd.MultiIndex.from_tuples(
+ [
+ (0, "a", "B"),
+ (0, "a", "A"),
+ (0, "b", "B"),
+ (0, "b", "A"),
+ (1, "a", "B"),
+ (1, "a", "A"),
+ (1, "b", "B"),
+ (1, "b", "A"),
+ ],
+ names=["sample", "cat0", "cat1"],
+ )
+ ser = pd.Series(range(8), index=ind)
+ result = ser.groupby(level="cat1", sort=False).quantile([0.2, 0.8])
+
+ qind = pd.MultiIndex.from_tuples(
+ [("B", 0.2), ("B", 0.8), ("A", 0.2), ("A", 0.8)], names=["cat1", None]
+ )
+ expected = pd.Series([1.2, 4.8, 2.2, 5.8], index=qind)
+
+ tm.assert_series_equal(result, expected)
+
+ # We need to check that index levels are not sorted
+ expected_levels = pd.core.indexes.frozen.FrozenList([["B", "A"], [0.2, 0.8]])
+ tm.assert_equal(result.index.levels, expected_levels)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_rank.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_rank.py
new file mode 100644
index 0000000000000000000000000000000000000000..1fb878cbc1111e575ae9e783702f2129757555e8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_rank.py
@@ -0,0 +1,643 @@
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ NaT,
+ Series,
+ concat,
+)
+import pandas._testing as tm
+
+
+def test_rank_unordered_categorical_typeerror():
+ # GH#51034 should be TypeError, not NotImplementedError
+ cat = pd.Categorical([], ordered=False)
+ ser = Series(cat)
+ df = ser.to_frame()
+
+ msg = "Cannot perform rank with non-ordered Categorical"
+
+ gb = ser.groupby(cat, observed=False)
+ with pytest.raises(TypeError, match=msg):
+ gb.rank()
+
+ gb2 = df.groupby(cat, observed=False)
+ with pytest.raises(TypeError, match=msg):
+ gb2.rank()
+
+
+def test_rank_apply():
+ lev1 = np.array(["a" * 10] * 100, dtype=object)
+ lev2 = np.array(["b" * 10] * 130, dtype=object)
+ lab1 = np.random.default_rng(2).integers(0, 100, size=500, dtype=int)
+ lab2 = np.random.default_rng(2).integers(0, 130, size=500, dtype=int)
+
+ df = DataFrame(
+ {
+ "value": np.random.default_rng(2).standard_normal(500),
+ "key1": lev1.take(lab1),
+ "key2": lev2.take(lab2),
+ }
+ )
+
+ result = df.groupby(["key1", "key2"]).value.rank()
+
+ expected = [piece.value.rank() for key, piece in df.groupby(["key1", "key2"])]
+ expected = concat(expected, axis=0)
+ expected = expected.reindex(result.index)
+ tm.assert_series_equal(result, expected)
+
+ result = df.groupby(["key1", "key2"]).value.rank(pct=True)
+
+ expected = [
+ piece.value.rank(pct=True) for key, piece in df.groupby(["key1", "key2"])
+ ]
+ expected = concat(expected, axis=0)
+ expected = expected.reindex(result.index)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("grps", [["qux"], ["qux", "quux"]])
+@pytest.mark.parametrize(
+ "vals",
+ [
+ np.array([2, 2, 8, 2, 6], dtype=dtype)
+ for dtype in ["i8", "i4", "i2", "i1", "u8", "u4", "u2", "u1", "f8", "f4", "f2"]
+ ]
+ + [
+ [
+ pd.Timestamp("2018-01-02"),
+ pd.Timestamp("2018-01-02"),
+ pd.Timestamp("2018-01-08"),
+ pd.Timestamp("2018-01-02"),
+ pd.Timestamp("2018-01-06"),
+ ],
+ [
+ pd.Timestamp("2018-01-02", tz="US/Pacific"),
+ pd.Timestamp("2018-01-02", tz="US/Pacific"),
+ pd.Timestamp("2018-01-08", tz="US/Pacific"),
+ pd.Timestamp("2018-01-02", tz="US/Pacific"),
+ pd.Timestamp("2018-01-06", tz="US/Pacific"),
+ ],
+ [
+ pd.Timestamp("2018-01-02") - pd.Timestamp(0),
+ pd.Timestamp("2018-01-02") - pd.Timestamp(0),
+ pd.Timestamp("2018-01-08") - pd.Timestamp(0),
+ pd.Timestamp("2018-01-02") - pd.Timestamp(0),
+ pd.Timestamp("2018-01-06") - pd.Timestamp(0),
+ ],
+ [
+ pd.Timestamp("2018-01-02").to_period("D"),
+ pd.Timestamp("2018-01-02").to_period("D"),
+ pd.Timestamp("2018-01-08").to_period("D"),
+ pd.Timestamp("2018-01-02").to_period("D"),
+ pd.Timestamp("2018-01-06").to_period("D"),
+ ],
+ ],
+ ids=lambda x: type(x[0]),
+)
+@pytest.mark.parametrize(
+ "ties_method,ascending,pct,exp",
+ [
+ ("average", True, False, [2.0, 2.0, 5.0, 2.0, 4.0]),
+ ("average", True, True, [0.4, 0.4, 1.0, 0.4, 0.8]),
+ ("average", False, False, [4.0, 4.0, 1.0, 4.0, 2.0]),
+ ("average", False, True, [0.8, 0.8, 0.2, 0.8, 0.4]),
+ ("min", True, False, [1.0, 1.0, 5.0, 1.0, 4.0]),
+ ("min", True, True, [0.2, 0.2, 1.0, 0.2, 0.8]),
+ ("min", False, False, [3.0, 3.0, 1.0, 3.0, 2.0]),
+ ("min", False, True, [0.6, 0.6, 0.2, 0.6, 0.4]),
+ ("max", True, False, [3.0, 3.0, 5.0, 3.0, 4.0]),
+ ("max", True, True, [0.6, 0.6, 1.0, 0.6, 0.8]),
+ ("max", False, False, [5.0, 5.0, 1.0, 5.0, 2.0]),
+ ("max", False, True, [1.0, 1.0, 0.2, 1.0, 0.4]),
+ ("first", True, False, [1.0, 2.0, 5.0, 3.0, 4.0]),
+ ("first", True, True, [0.2, 0.4, 1.0, 0.6, 0.8]),
+ ("first", False, False, [3.0, 4.0, 1.0, 5.0, 2.0]),
+ ("first", False, True, [0.6, 0.8, 0.2, 1.0, 0.4]),
+ ("dense", True, False, [1.0, 1.0, 3.0, 1.0, 2.0]),
+ ("dense", True, True, [1.0 / 3.0, 1.0 / 3.0, 3.0 / 3.0, 1.0 / 3.0, 2.0 / 3.0]),
+ ("dense", False, False, [3.0, 3.0, 1.0, 3.0, 2.0]),
+ ("dense", False, True, [3.0 / 3.0, 3.0 / 3.0, 1.0 / 3.0, 3.0 / 3.0, 2.0 / 3.0]),
+ ],
+)
+def test_rank_args(grps, vals, ties_method, ascending, pct, exp):
+ key = np.repeat(grps, len(vals))
+
+ orig_vals = vals
+ vals = list(vals) * len(grps)
+ if isinstance(orig_vals, np.ndarray):
+ vals = np.array(vals, dtype=orig_vals.dtype)
+
+ df = DataFrame({"key": key, "val": vals})
+ result = df.groupby("key").rank(method=ties_method, ascending=ascending, pct=pct)
+
+ exp_df = DataFrame(exp * len(grps), columns=["val"])
+ tm.assert_frame_equal(result, exp_df)
+
+
+@pytest.mark.parametrize("grps", [["qux"], ["qux", "quux"]])
+@pytest.mark.parametrize(
+ "vals", [[-np.inf, -np.inf, np.nan, 1.0, np.nan, np.inf, np.inf]]
+)
+@pytest.mark.parametrize(
+ "ties_method,ascending,na_option,exp",
+ [
+ ("average", True, "keep", [1.5, 1.5, np.nan, 3, np.nan, 4.5, 4.5]),
+ ("average", True, "top", [3.5, 3.5, 1.5, 5.0, 1.5, 6.5, 6.5]),
+ ("average", True, "bottom", [1.5, 1.5, 6.5, 3.0, 6.5, 4.5, 4.5]),
+ ("average", False, "keep", [4.5, 4.5, np.nan, 3, np.nan, 1.5, 1.5]),
+ ("average", False, "top", [6.5, 6.5, 1.5, 5.0, 1.5, 3.5, 3.5]),
+ ("average", False, "bottom", [4.5, 4.5, 6.5, 3.0, 6.5, 1.5, 1.5]),
+ ("min", True, "keep", [1.0, 1.0, np.nan, 3.0, np.nan, 4.0, 4.0]),
+ ("min", True, "top", [3.0, 3.0, 1.0, 5.0, 1.0, 6.0, 6.0]),
+ ("min", True, "bottom", [1.0, 1.0, 6.0, 3.0, 6.0, 4.0, 4.0]),
+ ("min", False, "keep", [4.0, 4.0, np.nan, 3.0, np.nan, 1.0, 1.0]),
+ ("min", False, "top", [6.0, 6.0, 1.0, 5.0, 1.0, 3.0, 3.0]),
+ ("min", False, "bottom", [4.0, 4.0, 6.0, 3.0, 6.0, 1.0, 1.0]),
+ ("max", True, "keep", [2.0, 2.0, np.nan, 3.0, np.nan, 5.0, 5.0]),
+ ("max", True, "top", [4.0, 4.0, 2.0, 5.0, 2.0, 7.0, 7.0]),
+ ("max", True, "bottom", [2.0, 2.0, 7.0, 3.0, 7.0, 5.0, 5.0]),
+ ("max", False, "keep", [5.0, 5.0, np.nan, 3.0, np.nan, 2.0, 2.0]),
+ ("max", False, "top", [7.0, 7.0, 2.0, 5.0, 2.0, 4.0, 4.0]),
+ ("max", False, "bottom", [5.0, 5.0, 7.0, 3.0, 7.0, 2.0, 2.0]),
+ ("first", True, "keep", [1.0, 2.0, np.nan, 3.0, np.nan, 4.0, 5.0]),
+ ("first", True, "top", [3.0, 4.0, 1.0, 5.0, 2.0, 6.0, 7.0]),
+ ("first", True, "bottom", [1.0, 2.0, 6.0, 3.0, 7.0, 4.0, 5.0]),
+ ("first", False, "keep", [4.0, 5.0, np.nan, 3.0, np.nan, 1.0, 2.0]),
+ ("first", False, "top", [6.0, 7.0, 1.0, 5.0, 2.0, 3.0, 4.0]),
+ ("first", False, "bottom", [4.0, 5.0, 6.0, 3.0, 7.0, 1.0, 2.0]),
+ ("dense", True, "keep", [1.0, 1.0, np.nan, 2.0, np.nan, 3.0, 3.0]),
+ ("dense", True, "top", [2.0, 2.0, 1.0, 3.0, 1.0, 4.0, 4.0]),
+ ("dense", True, "bottom", [1.0, 1.0, 4.0, 2.0, 4.0, 3.0, 3.0]),
+ ("dense", False, "keep", [3.0, 3.0, np.nan, 2.0, np.nan, 1.0, 1.0]),
+ ("dense", False, "top", [4.0, 4.0, 1.0, 3.0, 1.0, 2.0, 2.0]),
+ ("dense", False, "bottom", [3.0, 3.0, 4.0, 2.0, 4.0, 1.0, 1.0]),
+ ],
+)
+def test_infs_n_nans(grps, vals, ties_method, ascending, na_option, exp):
+ # GH 20561
+ key = np.repeat(grps, len(vals))
+ vals = vals * len(grps)
+ df = DataFrame({"key": key, "val": vals})
+ result = df.groupby("key").rank(
+ method=ties_method, ascending=ascending, na_option=na_option
+ )
+ exp_df = DataFrame(exp * len(grps), columns=["val"])
+ tm.assert_frame_equal(result, exp_df)
+
+
+@pytest.mark.parametrize("grps", [["qux"], ["qux", "quux"]])
+@pytest.mark.parametrize(
+ "vals",
+ [
+ np.array([2, 2, np.nan, 8, 2, 6, np.nan, np.nan], dtype=dtype)
+ for dtype in ["f8", "f4", "f2"]
+ ]
+ + [
+ [
+ pd.Timestamp("2018-01-02"),
+ pd.Timestamp("2018-01-02"),
+ np.nan,
+ pd.Timestamp("2018-01-08"),
+ pd.Timestamp("2018-01-02"),
+ pd.Timestamp("2018-01-06"),
+ np.nan,
+ np.nan,
+ ],
+ [
+ pd.Timestamp("2018-01-02", tz="US/Pacific"),
+ pd.Timestamp("2018-01-02", tz="US/Pacific"),
+ np.nan,
+ pd.Timestamp("2018-01-08", tz="US/Pacific"),
+ pd.Timestamp("2018-01-02", tz="US/Pacific"),
+ pd.Timestamp("2018-01-06", tz="US/Pacific"),
+ np.nan,
+ np.nan,
+ ],
+ [
+ pd.Timestamp("2018-01-02") - pd.Timestamp(0),
+ pd.Timestamp("2018-01-02") - pd.Timestamp(0),
+ np.nan,
+ pd.Timestamp("2018-01-08") - pd.Timestamp(0),
+ pd.Timestamp("2018-01-02") - pd.Timestamp(0),
+ pd.Timestamp("2018-01-06") - pd.Timestamp(0),
+ np.nan,
+ np.nan,
+ ],
+ [
+ pd.Timestamp("2018-01-02").to_period("D"),
+ pd.Timestamp("2018-01-02").to_period("D"),
+ np.nan,
+ pd.Timestamp("2018-01-08").to_period("D"),
+ pd.Timestamp("2018-01-02").to_period("D"),
+ pd.Timestamp("2018-01-06").to_period("D"),
+ np.nan,
+ np.nan,
+ ],
+ ],
+ ids=lambda x: type(x[0]),
+)
+@pytest.mark.parametrize(
+ "ties_method,ascending,na_option,pct,exp",
+ [
+ (
+ "average",
+ True,
+ "keep",
+ False,
+ [2.0, 2.0, np.nan, 5.0, 2.0, 4.0, np.nan, np.nan],
+ ),
+ (
+ "average",
+ True,
+ "keep",
+ True,
+ [0.4, 0.4, np.nan, 1.0, 0.4, 0.8, np.nan, np.nan],
+ ),
+ (
+ "average",
+ False,
+ "keep",
+ False,
+ [4.0, 4.0, np.nan, 1.0, 4.0, 2.0, np.nan, np.nan],
+ ),
+ (
+ "average",
+ False,
+ "keep",
+ True,
+ [0.8, 0.8, np.nan, 0.2, 0.8, 0.4, np.nan, np.nan],
+ ),
+ ("min", True, "keep", False, [1.0, 1.0, np.nan, 5.0, 1.0, 4.0, np.nan, np.nan]),
+ ("min", True, "keep", True, [0.2, 0.2, np.nan, 1.0, 0.2, 0.8, np.nan, np.nan]),
+ (
+ "min",
+ False,
+ "keep",
+ False,
+ [3.0, 3.0, np.nan, 1.0, 3.0, 2.0, np.nan, np.nan],
+ ),
+ ("min", False, "keep", True, [0.6, 0.6, np.nan, 0.2, 0.6, 0.4, np.nan, np.nan]),
+ ("max", True, "keep", False, [3.0, 3.0, np.nan, 5.0, 3.0, 4.0, np.nan, np.nan]),
+ ("max", True, "keep", True, [0.6, 0.6, np.nan, 1.0, 0.6, 0.8, np.nan, np.nan]),
+ (
+ "max",
+ False,
+ "keep",
+ False,
+ [5.0, 5.0, np.nan, 1.0, 5.0, 2.0, np.nan, np.nan],
+ ),
+ ("max", False, "keep", True, [1.0, 1.0, np.nan, 0.2, 1.0, 0.4, np.nan, np.nan]),
+ (
+ "first",
+ True,
+ "keep",
+ False,
+ [1.0, 2.0, np.nan, 5.0, 3.0, 4.0, np.nan, np.nan],
+ ),
+ (
+ "first",
+ True,
+ "keep",
+ True,
+ [0.2, 0.4, np.nan, 1.0, 0.6, 0.8, np.nan, np.nan],
+ ),
+ (
+ "first",
+ False,
+ "keep",
+ False,
+ [3.0, 4.0, np.nan, 1.0, 5.0, 2.0, np.nan, np.nan],
+ ),
+ (
+ "first",
+ False,
+ "keep",
+ True,
+ [0.6, 0.8, np.nan, 0.2, 1.0, 0.4, np.nan, np.nan],
+ ),
+ (
+ "dense",
+ True,
+ "keep",
+ False,
+ [1.0, 1.0, np.nan, 3.0, 1.0, 2.0, np.nan, np.nan],
+ ),
+ (
+ "dense",
+ True,
+ "keep",
+ True,
+ [
+ 1.0 / 3.0,
+ 1.0 / 3.0,
+ np.nan,
+ 3.0 / 3.0,
+ 1.0 / 3.0,
+ 2.0 / 3.0,
+ np.nan,
+ np.nan,
+ ],
+ ),
+ (
+ "dense",
+ False,
+ "keep",
+ False,
+ [3.0, 3.0, np.nan, 1.0, 3.0, 2.0, np.nan, np.nan],
+ ),
+ (
+ "dense",
+ False,
+ "keep",
+ True,
+ [
+ 3.0 / 3.0,
+ 3.0 / 3.0,
+ np.nan,
+ 1.0 / 3.0,
+ 3.0 / 3.0,
+ 2.0 / 3.0,
+ np.nan,
+ np.nan,
+ ],
+ ),
+ ("average", True, "bottom", False, [2.0, 2.0, 7.0, 5.0, 2.0, 4.0, 7.0, 7.0]),
+ (
+ "average",
+ True,
+ "bottom",
+ True,
+ [0.25, 0.25, 0.875, 0.625, 0.25, 0.5, 0.875, 0.875],
+ ),
+ ("average", False, "bottom", False, [4.0, 4.0, 7.0, 1.0, 4.0, 2.0, 7.0, 7.0]),
+ (
+ "average",
+ False,
+ "bottom",
+ True,
+ [0.5, 0.5, 0.875, 0.125, 0.5, 0.25, 0.875, 0.875],
+ ),
+ ("min", True, "bottom", False, [1.0, 1.0, 6.0, 5.0, 1.0, 4.0, 6.0, 6.0]),
+ (
+ "min",
+ True,
+ "bottom",
+ True,
+ [0.125, 0.125, 0.75, 0.625, 0.125, 0.5, 0.75, 0.75],
+ ),
+ ("min", False, "bottom", False, [3.0, 3.0, 6.0, 1.0, 3.0, 2.0, 6.0, 6.0]),
+ (
+ "min",
+ False,
+ "bottom",
+ True,
+ [0.375, 0.375, 0.75, 0.125, 0.375, 0.25, 0.75, 0.75],
+ ),
+ ("max", True, "bottom", False, [3.0, 3.0, 8.0, 5.0, 3.0, 4.0, 8.0, 8.0]),
+ ("max", True, "bottom", True, [0.375, 0.375, 1.0, 0.625, 0.375, 0.5, 1.0, 1.0]),
+ ("max", False, "bottom", False, [5.0, 5.0, 8.0, 1.0, 5.0, 2.0, 8.0, 8.0]),
+ (
+ "max",
+ False,
+ "bottom",
+ True,
+ [0.625, 0.625, 1.0, 0.125, 0.625, 0.25, 1.0, 1.0],
+ ),
+ ("first", True, "bottom", False, [1.0, 2.0, 6.0, 5.0, 3.0, 4.0, 7.0, 8.0]),
+ (
+ "first",
+ True,
+ "bottom",
+ True,
+ [0.125, 0.25, 0.75, 0.625, 0.375, 0.5, 0.875, 1.0],
+ ),
+ ("first", False, "bottom", False, [3.0, 4.0, 6.0, 1.0, 5.0, 2.0, 7.0, 8.0]),
+ (
+ "first",
+ False,
+ "bottom",
+ True,
+ [0.375, 0.5, 0.75, 0.125, 0.625, 0.25, 0.875, 1.0],
+ ),
+ ("dense", True, "bottom", False, [1.0, 1.0, 4.0, 3.0, 1.0, 2.0, 4.0, 4.0]),
+ ("dense", True, "bottom", True, [0.25, 0.25, 1.0, 0.75, 0.25, 0.5, 1.0, 1.0]),
+ ("dense", False, "bottom", False, [3.0, 3.0, 4.0, 1.0, 3.0, 2.0, 4.0, 4.0]),
+ ("dense", False, "bottom", True, [0.75, 0.75, 1.0, 0.25, 0.75, 0.5, 1.0, 1.0]),
+ ],
+)
+def test_rank_args_missing(grps, vals, ties_method, ascending, na_option, pct, exp):
+ key = np.repeat(grps, len(vals))
+
+ orig_vals = vals
+ vals = list(vals) * len(grps)
+ if isinstance(orig_vals, np.ndarray):
+ vals = np.array(vals, dtype=orig_vals.dtype)
+
+ df = DataFrame({"key": key, "val": vals})
+ result = df.groupby("key").rank(
+ method=ties_method, ascending=ascending, na_option=na_option, pct=pct
+ )
+
+ exp_df = DataFrame(exp * len(grps), columns=["val"])
+ tm.assert_frame_equal(result, exp_df)
+
+
+@pytest.mark.parametrize(
+ "pct,exp", [(False, [3.0, 3.0, 3.0, 3.0, 3.0]), (True, [0.6, 0.6, 0.6, 0.6, 0.6])]
+)
+def test_rank_resets_each_group(pct, exp):
+ df = DataFrame(
+ {"key": ["a", "a", "a", "a", "a", "b", "b", "b", "b", "b"], "val": [1] * 10}
+ )
+ result = df.groupby("key").rank(pct=pct)
+ exp_df = DataFrame(exp * 2, columns=["val"])
+ tm.assert_frame_equal(result, exp_df)
+
+
+@pytest.mark.parametrize(
+ "dtype", ["int64", "int32", "uint64", "uint32", "float64", "float32"]
+)
+@pytest.mark.parametrize("upper", [True, False])
+def test_rank_avg_even_vals(dtype, upper):
+ if upper:
+ # use IntegerDtype/FloatingDtype
+ dtype = dtype[0].upper() + dtype[1:]
+ dtype = dtype.replace("Ui", "UI")
+ df = DataFrame({"key": ["a"] * 4, "val": [1] * 4})
+ df["val"] = df["val"].astype(dtype)
+ assert df["val"].dtype == dtype
+
+ result = df.groupby("key").rank()
+ exp_df = DataFrame([2.5, 2.5, 2.5, 2.5], columns=["val"])
+ if upper:
+ exp_df = exp_df.astype("Float64")
+ tm.assert_frame_equal(result, exp_df)
+
+
+@pytest.mark.parametrize("na_option", ["keep", "top", "bottom"])
+@pytest.mark.parametrize("pct", [True, False])
+@pytest.mark.parametrize(
+ "vals", [["bar", "bar", "foo", "bar", "baz"], ["bar", np.nan, "foo", np.nan, "baz"]]
+)
+def test_rank_object_dtype(rank_method, ascending, na_option, pct, vals):
+ df = DataFrame({"key": ["foo"] * 5, "val": vals})
+ mask = df["val"].isna()
+
+ gb = df.groupby("key")
+ res = gb.rank(method=rank_method, ascending=ascending, na_option=na_option, pct=pct)
+
+ # construct our expected by using numeric values with the same ordering
+ if mask.any():
+ df2 = DataFrame({"key": ["foo"] * 5, "val": [0, np.nan, 2, np.nan, 1]})
+ else:
+ df2 = DataFrame({"key": ["foo"] * 5, "val": [0, 0, 2, 0, 1]})
+
+ gb2 = df2.groupby("key")
+ alt = gb2.rank(
+ method=rank_method, ascending=ascending, na_option=na_option, pct=pct
+ )
+
+ tm.assert_frame_equal(res, alt)
+
+
+@pytest.mark.parametrize("na_option", [True, "bad", 1])
+@pytest.mark.parametrize("pct", [True, False])
+@pytest.mark.parametrize(
+ "vals",
+ [
+ ["bar", "bar", "foo", "bar", "baz"],
+ ["bar", np.nan, "foo", np.nan, "baz"],
+ [1, np.nan, 2, np.nan, 3],
+ ],
+)
+def test_rank_naoption_raises(rank_method, ascending, na_option, pct, vals):
+ df = DataFrame({"key": ["foo"] * 5, "val": vals})
+ msg = "na_option must be one of 'keep', 'top', or 'bottom'"
+
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("key").rank(
+ method=rank_method, ascending=ascending, na_option=na_option, pct=pct
+ )
+
+
+def test_rank_empty_group():
+ # see gh-22519
+ column = "A"
+ df = DataFrame({"A": [0, 1, 0], "B": [1.0, np.nan, 2.0]})
+
+ result = df.groupby(column).B.rank(pct=True)
+ expected = Series([0.5, np.nan, 1.0], name="B")
+ tm.assert_series_equal(result, expected)
+
+ result = df.groupby(column).rank(pct=True)
+ expected = DataFrame({"B": [0.5, np.nan, 1.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "input_key,input_value,output_value",
+ [
+ ([1, 2], [1, 1], [1.0, 1.0]),
+ ([1, 1, 2, 2], [1, 2, 1, 2], [0.5, 1.0, 0.5, 1.0]),
+ ([1, 1, 2, 2], [1, 2, 1, np.nan], [0.5, 1.0, 1.0, np.nan]),
+ ([1, 1, 2], [1, 2, np.nan], [0.5, 1.0, np.nan]),
+ ],
+)
+def test_rank_zero_div(input_key, input_value, output_value):
+ # GH 23666
+ df = DataFrame({"A": input_key, "B": input_value})
+
+ result = df.groupby("A").rank(method="dense", pct=True)
+ expected = DataFrame({"B": output_value})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_rank_min_int():
+ # GH-32859
+ df = DataFrame(
+ {
+ "grp": [1, 1, 2],
+ "int_col": [
+ np.iinfo(np.int64).min,
+ np.iinfo(np.int64).max,
+ np.iinfo(np.int64).min,
+ ],
+ "datetimelike": [NaT, datetime(2001, 1, 1), NaT],
+ }
+ )
+
+ result = df.groupby("grp").rank()
+ expected = DataFrame(
+ {"int_col": [1.0, 2.0, 1.0], "datetimelike": [np.nan, 1.0, np.nan]}
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("use_nan", [True, False])
+def test_rank_pct_equal_values_on_group_transition(use_nan):
+ # GH#40518
+ fill_value = np.nan if use_nan else 3
+ df = DataFrame(
+ [
+ [-1, 1],
+ [-1, 2],
+ [1, fill_value],
+ [-1, fill_value],
+ ],
+ columns=["group", "val"],
+ )
+ result = df.groupby(["group"])["val"].rank(
+ method="dense",
+ pct=True,
+ )
+ if use_nan:
+ expected = Series([0.5, 1, np.nan, np.nan], name="val")
+ else:
+ expected = Series([1 / 3, 2 / 3, 1, 1], name="val")
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_non_unique_index():
+ # GH 16577
+ df = DataFrame(
+ {"A": [1.0, 2.0, 3.0, np.nan], "value": 1.0},
+ index=[pd.Timestamp("20170101", tz="US/Eastern")] * 4,
+ )
+ result = df.groupby([df.index, "A"]).value.rank(ascending=True, pct=True)
+ expected = Series(
+ [1.0, 1.0, 1.0, np.nan],
+ index=[pd.Timestamp("20170101", tz="US/Eastern")] * 4,
+ name="value",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_rank_categorical():
+ cat = pd.Categorical(["a", "a", "b", np.nan, "c", "b"], ordered=True)
+ cat2 = pd.Categorical([1, 2, 3, np.nan, 4, 5], ordered=True)
+
+ df = DataFrame({"col1": [0, 1, 0, 1, 0, 1], "col2": cat, "col3": cat2})
+
+ gb = df.groupby("col1")
+
+ res = gb.rank()
+
+ expected = df.astype(object).groupby("col1").rank()
+ tm.assert_frame_equal(res, expected)
+
+
+@pytest.mark.parametrize("na_option", ["top", "bottom"])
+def test_groupby_op_with_nullables(na_option):
+ # GH 54206
+ df = DataFrame({"x": [None]}, dtype="Float64")
+ result = df.groupby("x", dropna=False)["x"].rank(method="min", na_option=na_option)
+ expected = Series([1.0], dtype="Float64", name=result.name)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_sample.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_sample.py
new file mode 100644
index 0000000000000000000000000000000000000000..4dd474741740d4abdea1ebabf2b36c3b68d690ad
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_sample.py
@@ -0,0 +1,154 @@
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("n, frac", [(2, None), (None, 0.2)])
+def test_groupby_sample_balanced_groups_shape(n, frac):
+ values = [1] * 10 + [2] * 10
+ df = DataFrame({"a": values, "b": values})
+
+ result = df.groupby("a").sample(n=n, frac=frac)
+ values = [1] * 2 + [2] * 2
+ expected = DataFrame({"a": values, "b": values}, index=result.index)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("a")["b"].sample(n=n, frac=frac)
+ expected = Series(values, name="b", index=result.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_sample_unbalanced_groups_shape():
+ values = [1] * 10 + [2] * 20
+ df = DataFrame({"a": values, "b": values})
+
+ result = df.groupby("a").sample(n=5)
+ values = [1] * 5 + [2] * 5
+ expected = DataFrame({"a": values, "b": values}, index=result.index)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("a")["b"].sample(n=5)
+ expected = Series(values, name="b", index=result.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_sample_index_value_spans_groups():
+ values = [1] * 3 + [2] * 3
+ df = DataFrame({"a": values, "b": values}, index=[1, 2, 2, 2, 2, 2])
+
+ result = df.groupby("a").sample(n=2)
+ values = [1] * 2 + [2] * 2
+ expected = DataFrame({"a": values, "b": values}, index=result.index)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("a")["b"].sample(n=2)
+ expected = Series(values, name="b", index=result.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_sample_n_and_frac_raises():
+ df = DataFrame({"a": [1, 2], "b": [1, 2]})
+ msg = "Please enter a value for `frac` OR `n`, not both"
+
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("a").sample(n=1, frac=1.0)
+
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("a")["b"].sample(n=1, frac=1.0)
+
+
+def test_groupby_sample_frac_gt_one_without_replacement_raises():
+ df = DataFrame({"a": [1, 2], "b": [1, 2]})
+ msg = "Replace has to be set to `True` when upsampling the population `frac` > 1."
+
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("a").sample(frac=1.5, replace=False)
+
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("a")["b"].sample(frac=1.5, replace=False)
+
+
+@pytest.mark.parametrize("n", [-1, 1.5])
+def test_groupby_sample_invalid_n_raises(n):
+ df = DataFrame({"a": [1, 2], "b": [1, 2]})
+
+ if n < 0:
+ msg = "A negative number of rows requested. Please provide `n` >= 0."
+ else:
+ msg = "Only integers accepted as `n` values"
+
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("a").sample(n=n)
+
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("a")["b"].sample(n=n)
+
+
+def test_groupby_sample_oversample():
+ values = [1] * 10 + [2] * 10
+ df = DataFrame({"a": values, "b": values})
+
+ result = df.groupby("a").sample(frac=2.0, replace=True)
+ values = [1] * 20 + [2] * 20
+ expected = DataFrame({"a": values, "b": values}, index=result.index)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("a")["b"].sample(frac=2.0, replace=True)
+ expected = Series(values, name="b", index=result.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_sample_without_n_or_frac():
+ values = [1] * 10 + [2] * 10
+ df = DataFrame({"a": values, "b": values})
+
+ result = df.groupby("a").sample(n=None, frac=None)
+ expected = DataFrame({"a": [1, 2], "b": [1, 2]}, index=result.index)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("a")["b"].sample(n=None, frac=None)
+ expected = Series([1, 2], name="b", index=result.index)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index, expected_index",
+ [(["w", "x", "y", "z"], ["w", "w", "y", "y"]), ([3, 4, 5, 6], [3, 3, 5, 5])],
+)
+def test_groupby_sample_with_weights(index, expected_index):
+ # GH 39927 - tests for integer index needed
+ values = [1] * 2 + [2] * 2
+ df = DataFrame({"a": values, "b": values}, index=Index(index))
+
+ result = df.groupby("a").sample(n=2, replace=True, weights=[1, 0, 1, 0])
+ expected = DataFrame({"a": values, "b": values}, index=Index(expected_index))
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("a")["b"].sample(n=2, replace=True, weights=[1, 0, 1, 0])
+ expected = Series(values, name="b", index=Index(expected_index))
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_sample_with_selections():
+ # GH 39928
+ values = [1] * 10 + [2] * 10
+ df = DataFrame({"a": values, "b": values, "c": values})
+
+ result = df.groupby("a")[["b", "c"]].sample(n=None, frac=None)
+ expected = DataFrame({"b": [1, 2], "c": [1, 2]}, index=result.index)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_sample_with_empty_inputs():
+ # GH48459
+ df = DataFrame({"a": [], "b": []})
+ groupby_df = df.groupby("a")
+
+ result = groupby_df.sample()
+ expected = df
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_size.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_size.py
new file mode 100644
index 0000000000000000000000000000000000000000..6664563bd2272249c309ad44548676b1aa0d2f21
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_size.py
@@ -0,0 +1,90 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ PeriodIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("by", ["A", "B", ["A", "B"]])
+def test_size(df, by):
+ grouped = df.groupby(by=by)
+ result = grouped.size()
+ for key, group in grouped:
+ assert result[key] == len(group)
+
+
+@pytest.mark.parametrize("by", ["A", "B", ["A", "B"]])
+def test_size_sort(sort, by):
+ df = DataFrame(np.random.default_rng(2).choice(20, (1000, 3)), columns=list("ABC"))
+ left = df.groupby(by=by, sort=sort).size()
+ right = df.groupby(by=by, sort=sort)["C"].apply(lambda a: a.shape[0])
+ tm.assert_series_equal(left, right, check_names=False)
+
+
+def test_size_series_dataframe():
+ # https://github.com/pandas-dev/pandas/issues/11699
+ df = DataFrame(columns=["A", "B"])
+ out = Series(dtype="int64", index=Index([], name="A"))
+ tm.assert_series_equal(df.groupby("A").size(), out)
+
+
+def test_size_groupby_all_null():
+ # https://github.com/pandas-dev/pandas/issues/23050
+ # Assert no 'Value Error : Length of passed values is 2, index implies 0'
+ df = DataFrame({"A": [None, None]}) # all-null groups
+ result = df.groupby("A").size()
+ expected = Series(dtype="int64", index=Index([], name="A"))
+ tm.assert_series_equal(result, expected)
+
+
+def test_size_period_index():
+ # https://github.com/pandas-dev/pandas/issues/34010
+ ser = Series([1], index=PeriodIndex(["2000"], name="A", freq="D"))
+ grp = ser.groupby(level="A")
+ result = grp.size()
+ tm.assert_series_equal(result, ser)
+
+
+def test_size_on_categorical(as_index):
+ df = DataFrame([[1, 1], [2, 2]], columns=["A", "B"])
+ df["A"] = df["A"].astype("category")
+ result = df.groupby(["A", "B"], as_index=as_index, observed=False).size()
+
+ expected = DataFrame(
+ [[1, 1, 1], [1, 2, 0], [2, 1, 0], [2, 2, 1]], columns=["A", "B", "size"]
+ )
+ expected["A"] = expected["A"].astype("category")
+ if as_index:
+ expected = expected.set_index(["A", "B"])["size"].rename(None)
+
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["Int64", "Float64", "boolean"])
+def test_size_series_masked_type_returns_Int64(dtype):
+ # GH 54132
+ ser = Series([1, 1, 1], index=["a", "a", "b"], dtype=dtype)
+ result = ser.groupby(level=0).size()
+ expected = Series([2, 1], dtype="Int64", index=["a", "b"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_size_strings(any_string_dtype, using_infer_string):
+ # GH#55627
+ dtype = any_string_dtype
+ df = DataFrame({"a": ["a", "a", "b"], "b": "a"}, dtype=dtype)
+ result = df.groupby("a")["b"].size()
+ exp_dtype = "Int64" if dtype == "string[pyarrow]" else "int64"
+ exp_index_dtype = "str" if using_infer_string and dtype == "object" else dtype
+ expected = Series(
+ [2, 1],
+ index=Index(["a", "b"], name="a", dtype=exp_index_dtype),
+ name="b",
+ dtype=exp_dtype,
+ )
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_skew.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_skew.py
new file mode 100644
index 0000000000000000000000000000000000000000..563da89b6ab24a898f042f0e21377ccc2709b072
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_skew.py
@@ -0,0 +1,27 @@
+import numpy as np
+
+import pandas as pd
+import pandas._testing as tm
+
+
+def test_groupby_skew_equivalence():
+ # Test that that groupby skew method (which uses libgroupby.group_skew)
+ # matches the results of operating group-by-group (which uses nanops.nanskew)
+ nrows = 1000
+ ngroups = 3
+ ncols = 2
+ nan_frac = 0.05
+
+ arr = np.random.default_rng(2).standard_normal((nrows, ncols))
+ arr[np.random.default_rng(2).random(nrows) < nan_frac] = np.nan
+
+ df = pd.DataFrame(arr)
+ grps = np.random.default_rng(2).integers(0, ngroups, size=nrows)
+ gb = df.groupby(grps)
+
+ result = gb.skew()
+
+ grpwise = [grp.skew().to_frame(i).T for i, grp in gb]
+ expected = pd.concat(grpwise, axis=0)
+ expected.index = expected.index.astype(result.index.dtype) # 32bit builds
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_value_counts.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_value_counts.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7dc048d8072aa16cf5151e0ad09622529e12524
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/methods/test_value_counts.py
@@ -0,0 +1,1238 @@
+"""
+these are systematically testing all of the args to value_counts
+with different size combinations. This is to ensure stability of the sorting
+and proper parameter handling
+"""
+
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ CategoricalIndex,
+ DataFrame,
+ Grouper,
+ Index,
+ MultiIndex,
+ Series,
+ date_range,
+ to_datetime,
+)
+import pandas._testing as tm
+
+
+def tests_value_counts_index_names_category_column():
+ # GH44324 Missing name of index category column
+ df = DataFrame(
+ {
+ "gender": ["female"],
+ "country": ["US"],
+ }
+ )
+ df["gender"] = df["gender"].astype("category")
+ result = df.groupby("country")["gender"].value_counts()
+
+ # Construct expected, very specific multiindex
+ df_mi_expected = DataFrame([["US", "female"]], columns=["country", "gender"])
+ df_mi_expected["gender"] = df_mi_expected["gender"].astype("category")
+ mi_expected = MultiIndex.from_frame(df_mi_expected)
+ expected = Series([1], index=mi_expected, name="count")
+
+ tm.assert_series_equal(result, expected)
+
+
+def seed_df(seed_nans, n, m):
+ days = date_range("2015-08-24", periods=10)
+
+ frame = DataFrame(
+ {
+ "1st": np.random.default_rng(2).choice(list("abcd"), n),
+ "2nd": np.random.default_rng(2).choice(days, n),
+ "3rd": np.random.default_rng(2).integers(1, m + 1, n),
+ }
+ )
+
+ if seed_nans:
+ # Explicitly cast to float to avoid implicit cast when setting nan
+ frame["3rd"] = frame["3rd"].astype("float")
+ frame.loc[1::11, "1st"] = np.nan
+ frame.loc[3::17, "2nd"] = np.nan
+ frame.loc[7::19, "3rd"] = np.nan
+ frame.loc[8::19, "3rd"] = np.nan
+ frame.loc[9::19, "3rd"] = np.nan
+
+ return frame
+
+
+@pytest.mark.slow
+@pytest.mark.parametrize("seed_nans", [True, False])
+@pytest.mark.parametrize("num_rows", [10, 50])
+@pytest.mark.parametrize("max_int", [5, 20])
+@pytest.mark.parametrize("keys", ["1st", "2nd", ["1st", "2nd"]], ids=repr)
+@pytest.mark.parametrize("bins", [None, [0, 5]], ids=repr)
+@pytest.mark.parametrize("isort", [True, False])
+@pytest.mark.parametrize("normalize, name", [(True, "proportion"), (False, "count")])
+def test_series_groupby_value_counts(
+ seed_nans,
+ num_rows,
+ max_int,
+ keys,
+ bins,
+ isort,
+ normalize,
+ name,
+ sort,
+ ascending,
+ dropna,
+):
+ df = seed_df(seed_nans, num_rows, max_int)
+
+ def rebuild_index(df):
+ arr = list(map(df.index.get_level_values, range(df.index.nlevels)))
+ df.index = MultiIndex.from_arrays(arr, names=df.index.names)
+ return df
+
+ kwargs = {
+ "normalize": normalize,
+ "sort": sort,
+ "ascending": ascending,
+ "dropna": dropna,
+ "bins": bins,
+ }
+
+ gr = df.groupby(keys, sort=isort)
+ left = gr["3rd"].value_counts(**kwargs)
+
+ gr = df.groupby(keys, sort=isort)
+ right = gr["3rd"].apply(Series.value_counts, **kwargs)
+ right.index.names = [*right.index.names[:-1], "3rd"]
+ # https://github.com/pandas-dev/pandas/issues/49909
+ right = right.rename(name)
+
+ # have to sort on index because of unstable sort on values
+ left, right = map(rebuild_index, (left, right)) # xref GH9212
+ tm.assert_series_equal(left.sort_index(), right.sort_index())
+
+
+@pytest.mark.parametrize("utc", [True, False])
+def test_series_groupby_value_counts_with_grouper(utc):
+ # GH28479
+ df = DataFrame(
+ {
+ "Timestamp": [
+ 1565083561,
+ 1565083561 + 86400,
+ 1565083561 + 86500,
+ 1565083561 + 86400 * 2,
+ 1565083561 + 86400 * 3,
+ 1565083561 + 86500 * 3,
+ 1565083561 + 86400 * 4,
+ ],
+ "Food": ["apple", "apple", "banana", "banana", "orange", "orange", "pear"],
+ }
+ ).drop([3])
+
+ df["Datetime"] = to_datetime(df["Timestamp"], utc=utc, unit="s")
+ dfg = df.groupby(Grouper(freq="1D", key="Datetime"))
+
+ # have to sort on index because of unstable sort on values xref GH9212
+ result = dfg["Food"].value_counts().sort_index()
+ expected = dfg["Food"].apply(Series.value_counts).sort_index()
+ expected.index.names = result.index.names
+ # https://github.com/pandas-dev/pandas/issues/49909
+ expected = expected.rename("count")
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("columns", [["A", "B"], ["A", "B", "C"]])
+def test_series_groupby_value_counts_empty(columns):
+ # GH39172
+ df = DataFrame(columns=columns)
+ dfg = df.groupby(columns[:-1])
+
+ result = dfg[columns[-1]].value_counts()
+ expected = Series([], dtype=result.dtype, name="count")
+ expected.index = MultiIndex.from_arrays([[]] * len(columns), names=columns)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("columns", [["A", "B"], ["A", "B", "C"]])
+def test_series_groupby_value_counts_one_row(columns):
+ # GH42618
+ df = DataFrame(data=[range(len(columns))], columns=columns)
+ dfg = df.groupby(columns[:-1])
+
+ result = dfg[columns[-1]].value_counts()
+ expected = df.value_counts()
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_groupby_value_counts_on_categorical():
+ # GH38672
+
+ s = Series(Categorical(["a"], categories=["a", "b"]))
+ result = s.groupby([0]).value_counts()
+
+ expected = Series(
+ data=[1, 0],
+ index=MultiIndex.from_arrays(
+ [
+ np.array([0, 0]),
+ CategoricalIndex(
+ ["a", "b"], categories=["a", "b"], ordered=False, dtype="category"
+ ),
+ ]
+ ),
+ name="count",
+ )
+
+ # Expected:
+ # 0 a 1
+ # b 0
+ # dtype: int64
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_groupby_value_counts_no_sort():
+ # GH#50482
+ df = DataFrame(
+ {
+ "gender": ["male", "male", "female", "male", "female", "male"],
+ "education": ["low", "medium", "high", "low", "high", "low"],
+ "country": ["US", "FR", "US", "FR", "FR", "FR"],
+ }
+ )
+ gb = df.groupby(["country", "gender"], sort=False)["education"]
+ result = gb.value_counts(sort=False)
+ index = MultiIndex(
+ levels=[["US", "FR"], ["male", "female"], ["low", "medium", "high"]],
+ codes=[[0, 1, 0, 1, 1], [0, 0, 1, 0, 1], [0, 1, 2, 0, 2]],
+ names=["country", "gender", "education"],
+ )
+ expected = Series([1, 1, 1, 2, 1], index=index, name="count")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.fixture
+def education_df():
+ return DataFrame(
+ {
+ "gender": ["male", "male", "female", "male", "female", "male"],
+ "education": ["low", "medium", "high", "low", "high", "low"],
+ "country": ["US", "FR", "US", "FR", "FR", "FR"],
+ }
+ )
+
+
+def test_bad_subset(education_df):
+ gp = education_df.groupby("country")
+ with pytest.raises(ValueError, match="subset"):
+ gp.value_counts(subset=["country"])
+
+
+def test_basic(education_df, request):
+ # gh43564
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=(
+ "pandas default unstable sorting of duplicates"
+ "issue with numpy>=1.25 with AVX instructions"
+ ),
+ strict=False,
+ )
+ )
+ result = education_df.groupby("country")[["gender", "education"]].value_counts(
+ normalize=True
+ )
+ expected = Series(
+ data=[0.5, 0.25, 0.25, 0.5, 0.5],
+ index=MultiIndex.from_tuples(
+ [
+ ("FR", "male", "low"),
+ ("FR", "male", "medium"),
+ ("FR", "female", "high"),
+ ("US", "male", "low"),
+ ("US", "female", "high"),
+ ],
+ names=["country", "gender", "education"],
+ ),
+ name="proportion",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def _frame_value_counts(df, keys, normalize, sort, ascending):
+ return df[keys].value_counts(normalize=normalize, sort=sort, ascending=ascending)
+
+
+@pytest.mark.parametrize("groupby", ["column", "array", "function"])
+@pytest.mark.parametrize("normalize, name", [(True, "proportion"), (False, "count")])
+@pytest.mark.parametrize(
+ "sort, ascending",
+ [
+ (False, None),
+ (True, True),
+ (True, False),
+ ],
+)
+@pytest.mark.parametrize("frame", [True, False])
+def test_against_frame_and_seriesgroupby(
+ education_df,
+ groupby,
+ normalize,
+ name,
+ sort,
+ ascending,
+ as_index,
+ frame,
+ request,
+ using_infer_string,
+):
+ # test all parameters:
+ # - Use column, array or function as by= parameter
+ # - Whether or not to normalize
+ # - Whether or not to sort and how
+ # - Whether or not to use the groupby as an index
+ # - 3-way compare against:
+ # - apply with :meth:`~DataFrame.value_counts`
+ # - `~SeriesGroupBy.value_counts`
+ if frame and sort and normalize:
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=(
+ "pandas default unstable sorting of duplicates"
+ "issue with numpy>=1.25 with AVX instructions"
+ ),
+ strict=False,
+ )
+ )
+ by = {
+ "column": "country",
+ "array": education_df["country"].values,
+ "function": lambda x: education_df["country"][x] == "US",
+ }[groupby]
+
+ gp = education_df.groupby(by=by, as_index=as_index)
+ result = gp[["gender", "education"]].value_counts(
+ normalize=normalize, sort=sort, ascending=ascending
+ )
+ if frame:
+ # compare against apply with DataFrame value_counts
+ expected = gp.apply(
+ _frame_value_counts, ["gender", "education"], normalize, sort, ascending
+ )
+
+ if as_index:
+ tm.assert_series_equal(result, expected)
+ else:
+ name = "proportion" if normalize else "count"
+ expected = expected.reset_index().rename({0: name}, axis=1)
+ if groupby in ["array", "function"] and (not as_index and frame):
+ expected.insert(loc=0, column="level_0", value=result["level_0"])
+ else:
+ expected.insert(loc=0, column="country", value=result["country"])
+ tm.assert_frame_equal(result, expected)
+ else:
+ # compare against SeriesGroupBy value_counts
+ education_df["both"] = education_df["gender"] + "-" + education_df["education"]
+ expected = gp["both"].value_counts(
+ normalize=normalize, sort=sort, ascending=ascending
+ )
+ expected.name = name
+ if as_index:
+ index_frame = expected.index.to_frame(index=False)
+ index_frame["gender"] = index_frame["both"].str.split("-").str.get(0)
+ index_frame["education"] = index_frame["both"].str.split("-").str.get(1)
+ both_dtype = index_frame["both"].dtype
+ index_frame = index_frame.astype(
+ {"gender": both_dtype, "education": both_dtype}
+ )
+ del index_frame["both"]
+ index_frame2 = index_frame.rename({0: None}, axis=1)
+ expected.index = MultiIndex.from_frame(index_frame2)
+
+ if index_frame2.columns.isna()[0]:
+ # with using_infer_string, the columns in index_frame as string
+ # dtype, which makes the rename({0: None}) above use np.nan
+ # instead of None, so we need to set None more explicitly.
+ expected.index.names = [None, *expected.index.names[1:]]
+ tm.assert_series_equal(result, expected)
+ else:
+ expected.insert(1, "gender", expected["both"].str.split("-").str.get(0))
+ expected.insert(2, "education", expected["both"].str.split("-").str.get(1))
+ if using_infer_string:
+ expected = expected.astype({"gender": "str", "education": "str"})
+ del expected["both"]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("normalize", [True, False])
+@pytest.mark.parametrize(
+ "sort, ascending, expected_rows, expected_count, expected_group_size",
+ [
+ (False, None, [0, 1, 2, 3, 4], [1, 1, 1, 2, 1], [1, 3, 1, 3, 1]),
+ (True, False, [3, 0, 1, 2, 4], [2, 1, 1, 1, 1], [3, 1, 3, 1, 1]),
+ (True, True, [0, 1, 2, 4, 3], [1, 1, 1, 1, 2], [1, 3, 1, 1, 3]),
+ ],
+)
+def test_compound(
+ education_df,
+ normalize,
+ sort,
+ ascending,
+ expected_rows,
+ expected_count,
+ expected_group_size,
+ any_string_dtype,
+ using_infer_string,
+):
+ dtype = any_string_dtype
+ education_df = education_df.astype(dtype)
+ education_df.columns = education_df.columns.astype(dtype)
+ # Multiple groupby keys and as_index=False
+ gp = education_df.groupby(["country", "gender"], as_index=False, sort=False)
+ result = gp["education"].value_counts(
+ normalize=normalize, sort=sort, ascending=ascending
+ )
+ expected = DataFrame()
+ for column in ["country", "gender", "education"]:
+ expected[column] = [education_df[column][row] for row in expected_rows]
+ expected = expected.astype(dtype)
+ expected.columns = expected.columns.astype(dtype)
+ if normalize:
+ expected["proportion"] = expected_count
+ expected["proportion"] /= expected_group_size
+ if dtype == "string[pyarrow]":
+ # TODO(nullable) also string[python] should return nullable dtypes
+ expected["proportion"] = expected["proportion"].convert_dtypes()
+ else:
+ expected["count"] = expected_count
+ if dtype == "string[pyarrow]":
+ expected["count"] = expected["count"].convert_dtypes()
+ if using_infer_string and dtype == object:
+ expected = expected.astype(
+ {"country": "str", "gender": "str", "education": "str"}
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "sort, ascending, normalize, name, expected_data, expected_index",
+ [
+ (False, None, False, "count", [1, 2, 1], [(1, 1, 1), (2, 4, 6), (2, 0, 0)]),
+ (True, True, False, "count", [1, 1, 2], [(1, 1, 1), (2, 6, 4), (2, 0, 0)]),
+ (True, False, False, "count", [2, 1, 1], [(1, 1, 1), (4, 2, 6), (0, 2, 0)]),
+ (
+ True,
+ False,
+ True,
+ "proportion",
+ [0.5, 0.25, 0.25],
+ [(1, 1, 1), (4, 2, 6), (0, 2, 0)],
+ ),
+ ],
+)
+def test_data_frame_value_counts(
+ sort, ascending, normalize, name, expected_data, expected_index
+):
+ # 3-way compare with :meth:`~DataFrame.value_counts`
+ # Tests from frame/methods/test_value_counts.py
+ animals_df = DataFrame(
+ {"key": [1, 1, 1, 1], "num_legs": [2, 4, 4, 6], "num_wings": [2, 0, 0, 0]},
+ index=["falcon", "dog", "cat", "ant"],
+ )
+ result_frame = animals_df.value_counts(
+ sort=sort, ascending=ascending, normalize=normalize
+ )
+ expected = Series(
+ data=expected_data,
+ index=MultiIndex.from_arrays(
+ expected_index, names=["key", "num_legs", "num_wings"]
+ ),
+ name=name,
+ )
+ tm.assert_series_equal(result_frame, expected)
+
+ result_frame_groupby = animals_df.groupby("key").value_counts(
+ sort=sort, ascending=ascending, normalize=normalize
+ )
+
+ tm.assert_series_equal(result_frame_groupby, expected)
+
+
+@pytest.mark.parametrize(
+ "group_dropna, count_dropna, expected_rows, expected_values",
+ [
+ (
+ False,
+ False,
+ [0, 1, 3, 5, 6, 7, 8, 2, 4],
+ [0.5, 0.5, 1.0, 0.25, 0.25, 0.25, 0.25, 1.0, 1.0],
+ ),
+ (False, True, [0, 1, 3, 5, 2, 4], [0.5, 0.5, 1.0, 1.0, 1.0, 1.0]),
+ (True, False, [0, 1, 5, 6, 7, 8], [0.5, 0.5, 0.25, 0.25, 0.25, 0.25]),
+ (True, True, [0, 1, 5], [0.5, 0.5, 1.0]),
+ ],
+)
+def test_dropna_combinations(
+ group_dropna, count_dropna, expected_rows, expected_values, request
+):
+ if not group_dropna:
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=(
+ "pandas default unstable sorting of duplicates"
+ "issue with numpy>=1.25 with AVX instructions"
+ ),
+ strict=False,
+ )
+ )
+ nulls_df = DataFrame(
+ {
+ "A": [1, 1, np.nan, 4, np.nan, 6, 6, 6, 6],
+ "B": [1, 1, 3, np.nan, np.nan, 6, 6, 6, 6],
+ "C": [1, 2, 3, 4, 5, 6, np.nan, 8, np.nan],
+ "D": [1, 2, 3, 4, 5, 6, 7, np.nan, np.nan],
+ }
+ )
+ gp = nulls_df.groupby(["A", "B"], dropna=group_dropna)
+ result = gp.value_counts(normalize=True, sort=True, dropna=count_dropna)
+ columns = DataFrame()
+ for column in nulls_df.columns:
+ columns[column] = [nulls_df[column][row] for row in expected_rows]
+ index = MultiIndex.from_frame(columns)
+ expected = Series(data=expected_values, index=index, name="proportion")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dropna, expected_data, expected_index",
+ [
+ (
+ True,
+ [1, 1],
+ MultiIndex.from_arrays(
+ [(1, 1), ("John", "Beth"), ("Smith", "Louise")],
+ names=["key", "first_name", "middle_name"],
+ ),
+ ),
+ (
+ False,
+ [1, 1, 1, 1],
+ MultiIndex(
+ levels=[
+ Index([1]),
+ Index(["Anne", "Beth", "John"]),
+ Index(["Louise", "Smith", np.nan]),
+ ],
+ codes=[[0, 0, 0, 0], [2, 0, 2, 1], [1, 2, 2, 0]],
+ names=["key", "first_name", "middle_name"],
+ ),
+ ),
+ ],
+)
+@pytest.mark.parametrize("normalize, name", [(False, "count"), (True, "proportion")])
+def test_data_frame_value_counts_dropna(
+ nulls_fixture, dropna, normalize, name, expected_data, expected_index
+):
+ # GH 41334
+ # 3-way compare with :meth:`~DataFrame.value_counts`
+ # Tests with nulls from frame/methods/test_value_counts.py
+ names_with_nulls_df = DataFrame(
+ {
+ "key": [1, 1, 1, 1],
+ "first_name": ["John", "Anne", "John", "Beth"],
+ "middle_name": ["Smith", nulls_fixture, nulls_fixture, "Louise"],
+ },
+ )
+ result_frame = names_with_nulls_df.value_counts(dropna=dropna, normalize=normalize)
+ expected = Series(
+ data=expected_data,
+ index=expected_index,
+ name=name,
+ )
+ if normalize:
+ expected /= float(len(expected_data))
+
+ tm.assert_series_equal(result_frame, expected)
+
+ result_frame_groupby = names_with_nulls_df.groupby("key").value_counts(
+ dropna=dropna, normalize=normalize
+ )
+
+ tm.assert_series_equal(result_frame_groupby, expected)
+
+
+@pytest.mark.parametrize("observed", [False, True])
+@pytest.mark.parametrize(
+ "normalize, name, expected_data",
+ [
+ (
+ False,
+ "count",
+ np.array([2, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0], dtype=np.int64),
+ ),
+ (
+ True,
+ "proportion",
+ np.array([0.5, 0.25, 0.25, 0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.0]),
+ ),
+ ],
+)
+def test_categorical_single_grouper_with_only_observed_categories(
+ education_df, as_index, observed, normalize, name, expected_data, request
+):
+ # Test single categorical grouper with only observed grouping categories
+ # when non-groupers are also categorical
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=(
+ "pandas default unstable sorting of duplicates"
+ "issue with numpy>=1.25 with AVX instructions"
+ ),
+ strict=False,
+ )
+ )
+
+ gp = education_df.astype("category").groupby(
+ "country", as_index=as_index, observed=observed
+ )
+ result = gp.value_counts(normalize=normalize)
+
+ expected_index = MultiIndex.from_tuples(
+ [
+ ("FR", "male", "low"),
+ ("FR", "male", "medium"),
+ ("FR", "female", "high"),
+ ("FR", "male", "high"),
+ ("FR", "female", "low"),
+ ("FR", "female", "medium"),
+ ("US", "male", "low"),
+ ("US", "female", "high"),
+ ("US", "male", "medium"),
+ ("US", "male", "high"),
+ ("US", "female", "low"),
+ ("US", "female", "medium"),
+ ],
+ names=["country", "gender", "education"],
+ )
+
+ expected_series = Series(
+ data=expected_data,
+ index=expected_index,
+ name=name,
+ )
+ for i in range(3):
+ expected_series.index = expected_series.index.set_levels(
+ CategoricalIndex(expected_series.index.levels[i]), level=i
+ )
+
+ if as_index:
+ tm.assert_series_equal(result, expected_series)
+ else:
+ expected = expected_series.reset_index(
+ name="proportion" if normalize else "count"
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def assert_categorical_single_grouper(
+ education_df, as_index, observed, expected_index, normalize, name, expected_data
+):
+ # Test single categorical grouper when non-groupers are also categorical
+ education_df = education_df.copy().astype("category")
+
+ # Add non-observed grouping categories
+ education_df["country"] = education_df["country"].cat.add_categories(["ASIA"])
+
+ gp = education_df.groupby("country", as_index=as_index, observed=observed)
+ result = gp.value_counts(normalize=normalize)
+
+ expected_series = Series(
+ data=expected_data,
+ index=MultiIndex.from_tuples(
+ expected_index,
+ names=["country", "gender", "education"],
+ ),
+ name=name,
+ )
+ for i in range(3):
+ index_level = CategoricalIndex(expected_series.index.levels[i])
+ if i == 0:
+ index_level = index_level.set_categories(
+ education_df["country"].cat.categories
+ )
+ expected_series.index = expected_series.index.set_levels(index_level, level=i)
+
+ if as_index:
+ tm.assert_series_equal(result, expected_series)
+ else:
+ expected = expected_series.reset_index(name=name)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "normalize, name, expected_data",
+ [
+ (
+ False,
+ "count",
+ np.array([2, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0], dtype=np.int64),
+ ),
+ (
+ True,
+ "proportion",
+ np.array([0.5, 0.25, 0.25, 0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.0]),
+ ),
+ ],
+)
+def test_categorical_single_grouper_observed_true(
+ education_df, as_index, normalize, name, expected_data, request
+):
+ # GH#46357
+
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=(
+ "pandas default unstable sorting of duplicates"
+ "issue with numpy>=1.25 with AVX instructions"
+ ),
+ strict=False,
+ )
+ )
+
+ expected_index = [
+ ("FR", "male", "low"),
+ ("FR", "male", "medium"),
+ ("FR", "female", "high"),
+ ("FR", "male", "high"),
+ ("FR", "female", "low"),
+ ("FR", "female", "medium"),
+ ("US", "male", "low"),
+ ("US", "female", "high"),
+ ("US", "male", "medium"),
+ ("US", "male", "high"),
+ ("US", "female", "low"),
+ ("US", "female", "medium"),
+ ]
+
+ assert_categorical_single_grouper(
+ education_df=education_df,
+ as_index=as_index,
+ observed=True,
+ expected_index=expected_index,
+ normalize=normalize,
+ name=name,
+ expected_data=expected_data,
+ )
+
+
+@pytest.mark.parametrize(
+ "normalize, name, expected_data",
+ [
+ (
+ False,
+ "count",
+ np.array(
+ [2, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=np.int64
+ ),
+ ),
+ (
+ True,
+ "proportion",
+ np.array(
+ [
+ 0.5,
+ 0.25,
+ 0.25,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.5,
+ 0.5,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ ]
+ ),
+ ),
+ ],
+)
+def test_categorical_single_grouper_observed_false(
+ education_df, as_index, normalize, name, expected_data, request
+):
+ # GH#46357
+
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=(
+ "pandas default unstable sorting of duplicates"
+ "issue with numpy>=1.25 with AVX instructions"
+ ),
+ strict=False,
+ )
+ )
+
+ expected_index = [
+ ("FR", "male", "low"),
+ ("FR", "male", "medium"),
+ ("FR", "female", "high"),
+ ("FR", "male", "high"),
+ ("FR", "female", "low"),
+ ("FR", "female", "medium"),
+ ("US", "male", "low"),
+ ("US", "female", "high"),
+ ("US", "male", "medium"),
+ ("US", "male", "high"),
+ ("US", "female", "low"),
+ ("US", "female", "medium"),
+ ("ASIA", "male", "low"),
+ ("ASIA", "male", "medium"),
+ ("ASIA", "male", "high"),
+ ("ASIA", "female", "low"),
+ ("ASIA", "female", "medium"),
+ ("ASIA", "female", "high"),
+ ]
+
+ assert_categorical_single_grouper(
+ education_df=education_df,
+ as_index=as_index,
+ observed=False,
+ expected_index=expected_index,
+ normalize=normalize,
+ name=name,
+ expected_data=expected_data,
+ )
+
+
+@pytest.mark.parametrize(
+ "observed, expected_index",
+ [
+ (
+ False,
+ [
+ ("FR", "high", "female"),
+ ("FR", "high", "male"),
+ ("FR", "low", "male"),
+ ("FR", "low", "female"),
+ ("FR", "medium", "male"),
+ ("FR", "medium", "female"),
+ ("US", "high", "female"),
+ ("US", "high", "male"),
+ ("US", "low", "male"),
+ ("US", "low", "female"),
+ ("US", "medium", "male"),
+ ("US", "medium", "female"),
+ ],
+ ),
+ (
+ True,
+ [
+ ("FR", "high", "female"),
+ ("FR", "low", "male"),
+ ("FR", "medium", "male"),
+ ("US", "high", "female"),
+ ("US", "low", "male"),
+ ],
+ ),
+ ],
+)
+@pytest.mark.parametrize(
+ "normalize, name, expected_data",
+ [
+ (
+ False,
+ "count",
+ np.array([1, 0, 2, 0, 1, 0, 1, 0, 1, 0, 0, 0], dtype=np.int64),
+ ),
+ (
+ True,
+ "proportion",
+ # NaN values corresponds to non-observed groups
+ np.array([1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0]),
+ ),
+ ],
+)
+def test_categorical_multiple_groupers(
+ education_df, as_index, observed, expected_index, normalize, name, expected_data
+):
+ # GH#46357
+
+ # Test multiple categorical groupers when non-groupers are non-categorical
+ education_df = education_df.copy()
+ education_df["country"] = education_df["country"].astype("category")
+ education_df["education"] = education_df["education"].astype("category")
+
+ gp = education_df.groupby(
+ ["country", "education"], as_index=as_index, observed=observed
+ )
+ result = gp.value_counts(normalize=normalize)
+
+ expected_series = Series(
+ data=expected_data[expected_data > 0.0] if observed else expected_data,
+ index=MultiIndex.from_tuples(
+ expected_index,
+ names=["country", "education", "gender"],
+ ),
+ name=name,
+ )
+ for i in range(2):
+ expected_series.index = expected_series.index.set_levels(
+ CategoricalIndex(expected_series.index.levels[i]), level=i
+ )
+
+ if as_index:
+ tm.assert_series_equal(result, expected_series)
+ else:
+ expected = expected_series.reset_index(
+ name="proportion" if normalize else "count"
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("observed", [False, True])
+@pytest.mark.parametrize(
+ "normalize, name, expected_data",
+ [
+ (
+ False,
+ "count",
+ np.array([2, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0], dtype=np.int64),
+ ),
+ (
+ True,
+ "proportion",
+ # NaN values corresponds to non-observed groups
+ np.array([0.5, 0.25, 0.25, 0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.0]),
+ ),
+ ],
+)
+def test_categorical_non_groupers(
+ education_df, as_index, observed, normalize, name, expected_data, request
+):
+ # GH#46357 Test non-observed categories are included in the result,
+ # regardless of `observed`
+
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=(
+ "pandas default unstable sorting of duplicates"
+ "issue with numpy>=1.25 with AVX instructions"
+ ),
+ strict=False,
+ )
+ )
+
+ education_df = education_df.copy()
+ education_df["gender"] = education_df["gender"].astype("category")
+ education_df["education"] = education_df["education"].astype("category")
+
+ gp = education_df.groupby("country", as_index=as_index, observed=observed)
+ result = gp.value_counts(normalize=normalize)
+
+ expected_index = [
+ ("FR", "male", "low"),
+ ("FR", "male", "medium"),
+ ("FR", "female", "high"),
+ ("FR", "male", "high"),
+ ("FR", "female", "low"),
+ ("FR", "female", "medium"),
+ ("US", "male", "low"),
+ ("US", "female", "high"),
+ ("US", "male", "medium"),
+ ("US", "male", "high"),
+ ("US", "female", "low"),
+ ("US", "female", "medium"),
+ ]
+ expected_series = Series(
+ data=expected_data,
+ index=MultiIndex.from_tuples(
+ expected_index,
+ names=["country", "gender", "education"],
+ ),
+ name=name,
+ )
+ for i in range(1, 3):
+ expected_series.index = expected_series.index.set_levels(
+ CategoricalIndex(expected_series.index.levels[i]), level=i
+ )
+
+ if as_index:
+ tm.assert_series_equal(result, expected_series)
+ else:
+ expected = expected_series.reset_index(
+ name="proportion" if normalize else "count"
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "normalize, expected_label, expected_values",
+ [
+ (False, "count", [1, 1, 1]),
+ (True, "proportion", [0.5, 0.5, 1.0]),
+ ],
+)
+def test_mixed_groupings(normalize, expected_label, expected_values):
+ # Test multiple groupings
+ df = DataFrame({"A": [1, 2, 1], "B": [1, 2, 3]})
+ gp = df.groupby([[4, 5, 4], "A", lambda i: 7 if i == 1 else 8], as_index=False)
+ result = gp.value_counts(sort=True, normalize=normalize)
+ expected = DataFrame(
+ {
+ "level_0": np.array([4, 4, 5], dtype=int),
+ "A": [1, 1, 2],
+ "level_2": [8, 8, 7],
+ "B": [1, 3, 2],
+ expected_label: expected_values,
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "test, columns, expected_names",
+ [
+ ("repeat", list("abbde"), ["a", None, "d", "b", "b", "e"]),
+ ("level", [*list("abcd"), "level_1"], ["a", None, "d", "b", "c", "level_1"]),
+ ],
+)
+def test_column_label_duplicates(test, columns, expected_names, as_index):
+ # GH 44992
+ # Test for duplicate input column labels and generated duplicate labels
+ df = DataFrame([[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]], columns=columns)
+ expected_data = [(1, 0, 7, 3, 5, 9), (2, 1, 8, 4, 6, 10)]
+ keys = ["a", np.array([0, 1], dtype=np.int64), "d"]
+ result = df.groupby(keys, as_index=as_index).value_counts()
+ if as_index:
+ expected = Series(
+ data=(1, 1),
+ index=MultiIndex.from_tuples(
+ expected_data,
+ names=expected_names,
+ ),
+ name="count",
+ )
+ tm.assert_series_equal(result, expected)
+ else:
+ expected_data = [[*list(row), 1] for row in expected_data]
+ expected_columns = list(expected_names)
+ expected_columns[1] = "level_1"
+ expected_columns.append("count")
+ expected = DataFrame(expected_data, columns=expected_columns)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "normalize, expected_label",
+ [
+ (False, "count"),
+ (True, "proportion"),
+ ],
+)
+def test_result_label_duplicates(normalize, expected_label):
+ # Test for result column label duplicating an input column label
+ gb = DataFrame([[1, 2, 3]], columns=["a", "b", expected_label]).groupby(
+ "a", as_index=False
+ )
+ msg = f"Column label '{expected_label}' is duplicate of result column"
+ with pytest.raises(ValueError, match=msg):
+ gb.value_counts(normalize=normalize)
+
+
+def test_ambiguous_grouping():
+ # Test that groupby is not confused by groupings length equal to row count
+ df = DataFrame({"a": [1, 1]})
+ gb = df.groupby(np.array([1, 1], dtype=np.int64))
+ result = gb.value_counts()
+ expected = Series(
+ [2], index=MultiIndex.from_tuples([[1, 1]], names=[None, "a"]), name="count"
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_subset_overlaps_gb_key_raises():
+ # GH 46383
+ df = DataFrame({"c1": ["a", "b", "c"], "c2": ["x", "y", "y"]}, index=[0, 1, 1])
+ msg = "Keys {'c1'} in subset cannot be in the groupby column keys."
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("c1").value_counts(subset=["c1"])
+
+
+def test_subset_doesnt_exist_in_frame():
+ # GH 46383
+ df = DataFrame({"c1": ["a", "b", "c"], "c2": ["x", "y", "y"]}, index=[0, 1, 1])
+ msg = "Keys {'c3'} in subset do not exist in the DataFrame."
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("c1").value_counts(subset=["c3"])
+
+
+def test_subset():
+ # GH 46383
+ df = DataFrame({"c1": ["a", "b", "c"], "c2": ["x", "y", "y"]}, index=[0, 1, 1])
+ result = df.groupby(level=0).value_counts(subset=["c2"])
+ expected = Series(
+ [1, 2],
+ index=MultiIndex.from_arrays([[0, 1], ["x", "y"]], names=[None, "c2"]),
+ name="count",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_subset_duplicate_columns():
+ # GH 46383
+ df = DataFrame(
+ [["a", "x", "x"], ["b", "y", "y"], ["b", "y", "y"]],
+ index=[0, 1, 1],
+ columns=["c1", "c2", "c2"],
+ )
+ result = df.groupby(level=0).value_counts(subset=["c2"])
+ expected = Series(
+ [1, 2],
+ index=MultiIndex.from_arrays(
+ [[0, 1], ["x", "y"], ["x", "y"]], names=[None, "c2", "c2"]
+ ),
+ name="count",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("utc", [True, False])
+def test_value_counts_time_grouper(utc, unit):
+ # GH#50486
+ df = DataFrame(
+ {
+ "Timestamp": [
+ 1565083561,
+ 1565083561 + 86400,
+ 1565083561 + 86500,
+ 1565083561 + 86400 * 2,
+ 1565083561 + 86400 * 3,
+ 1565083561 + 86500 * 3,
+ 1565083561 + 86400 * 4,
+ ],
+ "Food": ["apple", "apple", "banana", "banana", "orange", "orange", "pear"],
+ }
+ ).drop([3])
+
+ df["Datetime"] = to_datetime(df["Timestamp"], utc=utc, unit="s").dt.as_unit(unit)
+ gb = df.groupby(Grouper(freq="1D", key="Datetime"))
+ result = gb.value_counts()
+ dates = to_datetime(
+ ["2019-08-06", "2019-08-07", "2019-08-09", "2019-08-10"], utc=utc
+ ).as_unit(unit)
+ timestamps = df["Timestamp"].unique()
+ index = MultiIndex(
+ levels=[dates, timestamps, ["apple", "banana", "orange", "pear"]],
+ codes=[[0, 1, 1, 2, 2, 3], range(6), [0, 0, 1, 2, 2, 3]],
+ names=["Datetime", "Timestamp", "Food"],
+ )
+ expected = Series(1, index=index, name="count")
+ tm.assert_series_equal(result, expected)
+
+
+def test_value_counts_integer_columns():
+ # GH#55627
+ df = DataFrame({1: ["a", "a", "a"], 2: ["a", "a", "d"], 3: ["a", "b", "c"]})
+ gp = df.groupby([1, 2], as_index=False, sort=False)
+ result = gp[3].value_counts()
+ expected = DataFrame(
+ {1: ["a", "a", "a"], 2: ["a", "a", "d"], 3: ["a", "b", "c"], "count": 1}
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("vc_sort", [True, False])
+@pytest.mark.parametrize("normalize", [True, False])
+def test_value_counts_sort(sort, vc_sort, normalize):
+ # GH#55951
+ df = DataFrame({"a": [2, 1, 1, 1], 0: [3, 4, 3, 3]})
+ gb = df.groupby("a", sort=sort)
+ result = gb.value_counts(sort=vc_sort, normalize=normalize)
+
+ if normalize:
+ values = [2 / 3, 1 / 3, 1.0]
+ else:
+ values = [2, 1, 1]
+ index = MultiIndex(
+ levels=[[1, 2], [3, 4]], codes=[[0, 0, 1], [0, 1, 0]], names=["a", 0]
+ )
+ expected = Series(values, index=index, name="proportion" if normalize else "count")
+ if sort and vc_sort:
+ taker = [0, 1, 2]
+ elif sort and not vc_sort:
+ taker = [1, 0, 2]
+ elif not sort and vc_sort:
+ taker = [0, 2, 1]
+ else:
+ taker = [2, 1, 0]
+ expected = expected.take(taker)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("vc_sort", [True, False])
+@pytest.mark.parametrize("normalize", [True, False])
+def test_value_counts_sort_categorical(sort, vc_sort, normalize):
+ # GH#55951
+ df = DataFrame({"a": [2, 1, 1, 1], 0: [3, 4, 3, 3]}, dtype="category")
+ gb = df.groupby("a", sort=sort, observed=True)
+ result = gb.value_counts(sort=vc_sort, normalize=normalize)
+
+ if normalize:
+ values = [2 / 3, 1 / 3, 1.0, 0.0]
+ else:
+ values = [2, 1, 1, 0]
+ name = "proportion" if normalize else "count"
+ expected = DataFrame(
+ {
+ "a": Categorical([1, 1, 2, 2]),
+ 0: Categorical([3, 4, 3, 4]),
+ name: values,
+ }
+ ).set_index(["a", 0])[name]
+ if sort and vc_sort:
+ taker = [0, 1, 2, 3]
+ elif sort and not vc_sort:
+ taker = [0, 1, 2, 3]
+ elif not sort and vc_sort:
+ taker = [0, 2, 1, 3]
+ else:
+ taker = [2, 1, 0, 3]
+ expected = expected.take(taker)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("groupby_sort", [True, False])
+def test_value_counts_all_na(sort, dropna, groupby_sort):
+ # GH#59989
+ df = DataFrame({"a": [2, 1, 1], "b": np.nan})
+ gb = df.groupby("a", sort=groupby_sort)
+ result = gb.value_counts(sort=sort, dropna=dropna)
+
+ kwargs = {"levels": [[1, 2], [np.nan]], "names": ["a", "b"]}
+ if dropna:
+ data = []
+ index = MultiIndex(codes=[[], []], **kwargs)
+ elif not groupby_sort and not sort:
+ data = [1, 2]
+ index = MultiIndex(codes=[[1, 0], [0, 0]], **kwargs)
+ else:
+ data = [2, 1]
+ index = MultiIndex(codes=[[0, 1], [0, 0]], **kwargs)
+ expected = Series(data, index=index, dtype="int64", name="count")
+
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_all_methods.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_all_methods.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a012f5da4aa827dbea1350d7a874ca6e0d85233
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_all_methods.py
@@ -0,0 +1,105 @@
+"""
+Tests that apply to all groupby operation methods.
+
+The only tests that should appear here are those that use the `groupby_func` fixture.
+Even if it does use that fixture, prefer a more specific test file if it available
+such as:
+
+ - test_categorical
+ - test_groupby_dropna
+ - test_groupby_subclass
+ - test_raises
+"""
+
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import DataFrame
+import pandas._testing as tm
+from pandas.tests.groupby import get_groupby_method_args
+
+
+def test_multiindex_group_all_columns_when_empty(groupby_func):
+ # GH 32464
+ df = DataFrame({"a": [], "b": [], "c": []}).set_index(["a", "b", "c"])
+ gb = df.groupby(["a", "b", "c"], group_keys=True)
+ method = getattr(gb, groupby_func)
+ args = get_groupby_method_args(groupby_func, df)
+ if groupby_func == "corrwith":
+ warn = Pandas4Warning
+ warn_msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ warn_msg = ""
+ with tm.assert_produces_warning(warn, match=warn_msg):
+ result = method(*args).index
+ expected = df.index
+ tm.assert_index_equal(result, expected)
+
+
+def test_duplicate_columns(request, groupby_func, as_index):
+ # GH#50806
+ if groupby_func == "corrwith":
+ msg = "GH#50845 - corrwith fails when there are duplicate columns"
+ request.applymarker(pytest.mark.xfail(reason=msg))
+ df = DataFrame([[1, 3, 6], [1, 4, 7], [2, 5, 8]], columns=list("abb"))
+ args = get_groupby_method_args(groupby_func, df)
+ gb = df.groupby("a", as_index=as_index)
+ result = getattr(gb, groupby_func)(*args)
+
+ expected_df = df.set_axis(["a", "b", "c"], axis=1)
+ expected_args = get_groupby_method_args(groupby_func, expected_df)
+ expected_gb = expected_df.groupby("a", as_index=as_index)
+ expected = getattr(expected_gb, groupby_func)(*expected_args)
+ if groupby_func not in ("size", "ngroup", "cumcount"):
+ expected = expected.rename(columns={"c": "b"})
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "idx",
+ [
+ pd.Index(["a", "a"], name="foo"),
+ pd.MultiIndex.from_tuples((("a", "a"), ("a", "a")), names=["foo", "bar"]),
+ ],
+)
+def test_dup_labels_output_shape(groupby_func, idx):
+ if groupby_func in {"size", "ngroup", "cumcount"}:
+ pytest.skip(f"Not applicable for {groupby_func}")
+
+ df = DataFrame([[1, 1]], columns=idx)
+ grp_by = df.groupby([0])
+
+ args = get_groupby_method_args(groupby_func, df)
+ if groupby_func == "corrwith":
+ warn = Pandas4Warning
+ warn_msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ warn_msg = ""
+ with tm.assert_produces_warning(warn, match=warn_msg):
+ result = getattr(grp_by, groupby_func)(*args)
+
+ assert result.shape == (1, 2)
+ tm.assert_index_equal(result.columns, idx)
+
+
+def test_not_c_contiguous_mask(groupby_func):
+ # https://github.com/pandas-dev/pandas/issues/61031
+ if groupby_func == "corrwith":
+ # corrwith is deprecated
+ return
+ df = DataFrame({"a": [1, 1, 2], "b": [3, 4, 5]}, dtype="Int64")
+ reversed = DataFrame(
+ {"a": [2, 1, 1], "b": [5, 4, 3]}, dtype="Int64", index=[2, 1, 0]
+ )[::-1]
+ assert not reversed["b"].array._mask.flags["C_CONTIGUOUS"]
+ args = get_groupby_method_args(groupby_func, df)
+
+ gb_reversed = reversed.groupby("a")
+ result = getattr(gb_reversed, groupby_func)(*args)
+ gb = df.groupby("a")
+ expected = getattr(gb, groupby_func)(*args)
+ tm.assert_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..215e627abb018f04bff337056dee9c4a60312d62
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_api.py
@@ -0,0 +1,274 @@
+"""
+Tests of the groupby API, including internal consistency and with other pandas objects.
+
+Tests in this file should only check the existence, names, and arguments of groupby
+methods. It should not test the results of any groupby operation.
+"""
+
+import inspect
+
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+)
+from pandas.core.groupby.base import (
+ groupby_other_methods,
+ reduction_kernels,
+ transformation_kernels,
+)
+from pandas.core.groupby.generic import (
+ DataFrameGroupBy,
+ SeriesGroupBy,
+)
+
+
+def test_tab_completion(multiindex_dataframe_random_data):
+ grp = multiindex_dataframe_random_data.groupby(level="second")
+ results = {v for v in dir(grp) if not v.startswith("_")}
+ expected = {
+ "A",
+ "B",
+ "C",
+ "agg",
+ "aggregate",
+ "apply",
+ "boxplot",
+ "filter",
+ "first",
+ "get_group",
+ "groups",
+ "hist",
+ "indices",
+ "last",
+ "max",
+ "mean",
+ "median",
+ "min",
+ "ngroups",
+ "nth",
+ "ohlc",
+ "plot",
+ "prod",
+ "size",
+ "std",
+ "sum",
+ "transform",
+ "var",
+ "sem",
+ "count",
+ "nunique",
+ "head",
+ "describe",
+ "cummax",
+ "quantile",
+ "rank",
+ "cumprod",
+ "tail",
+ "resample",
+ "cummin",
+ "cumsum",
+ "cumcount",
+ "ngroup",
+ "all",
+ "shift",
+ "skew",
+ "kurt",
+ "take",
+ "pct_change",
+ "any",
+ "corr",
+ "corrwith",
+ "cov",
+ "ndim",
+ "diff",
+ "idxmax",
+ "idxmin",
+ "ffill",
+ "bfill",
+ "rolling",
+ "expanding",
+ "pipe",
+ "sample",
+ "ewm",
+ "value_counts",
+ }
+ assert results == expected
+
+
+def test_all_methods_categorized(multiindex_dataframe_random_data):
+ grp = multiindex_dataframe_random_data.groupby(
+ multiindex_dataframe_random_data.iloc[:, 0]
+ )
+ names = {_ for _ in dir(grp) if not _.startswith("_")} - set(
+ multiindex_dataframe_random_data.columns
+ )
+ new_names = set(names)
+ new_names -= reduction_kernels
+ new_names -= transformation_kernels
+ new_names -= groupby_other_methods
+
+ assert not reduction_kernels & transformation_kernels
+ assert not reduction_kernels & groupby_other_methods
+ assert not transformation_kernels & groupby_other_methods
+
+ # new public method?
+ if new_names:
+ msg = f"""
+There are uncategorized methods defined on the Grouper class:
+{new_names}.
+
+Was a new method recently added?
+
+Every public method On Grouper must appear in exactly one the
+following three lists defined in pandas.core.groupby.base:
+- `reduction_kernels`
+- `transformation_kernels`
+- `groupby_other_methods`
+see the comments in pandas/core/groupby/base.py for guidance on
+how to fix this test.
+ """
+ raise AssertionError(msg)
+
+ # removed a public method?
+ all_categorized = reduction_kernels | transformation_kernels | groupby_other_methods
+ if names != all_categorized:
+ msg = f"""
+Some methods which are supposed to be on the Grouper class
+are missing:
+{all_categorized - names}.
+
+They're still defined in one of the lists that live in pandas/core/groupby/base.py.
+If you removed a method, you should update them
+"""
+ raise AssertionError(msg)
+
+
+def test_frame_consistency(groupby_func):
+ # GH#48028
+ if groupby_func in ("first", "last"):
+ msg = "first and last don't exist for DataFrame anymore"
+ pytest.skip(reason=msg)
+
+ if groupby_func in ("cumcount", "ngroup"):
+ assert not hasattr(DataFrame, groupby_func)
+ return
+
+ frame_method = getattr(DataFrame, groupby_func)
+ gb_method = getattr(DataFrameGroupBy, groupby_func)
+ result = set(inspect.signature(gb_method).parameters)
+ if groupby_func == "size":
+ # "size" is a method on GroupBy but property on DataFrame:
+ expected = {"self"}
+ else:
+ expected = set(inspect.signature(frame_method).parameters)
+
+ # Exclude certain arguments from result and expected depending on the operation
+ # Some of these may be purposeful inconsistencies between the APIs
+ exclude_expected, exclude_result = set(), set()
+ if groupby_func in ("any", "all"):
+ exclude_expected = {"kwargs", "bool_only", "axis"}
+ elif groupby_func in ("count",):
+ exclude_expected = {"numeric_only", "axis"}
+ elif groupby_func in ("nunique",):
+ exclude_expected = {"axis"}
+ elif groupby_func in ("max", "min"):
+ exclude_expected = {"axis", "kwargs"}
+ exclude_result = {"min_count", "engine", "engine_kwargs"}
+ elif groupby_func in ("sum", "mean", "std", "var"):
+ exclude_expected = {"axis", "kwargs"}
+ exclude_result = {"engine", "engine_kwargs"}
+ elif groupby_func in ("median", "prod", "sem"):
+ exclude_expected = {"axis", "kwargs"}
+ elif groupby_func in ("bfill", "ffill"):
+ exclude_expected = {"inplace", "axis", "limit_area"}
+ elif groupby_func in ("cummax", "cummin"):
+ exclude_expected = {"axis", "skipna", "args"}
+ elif groupby_func in ("cumprod", "cumsum"):
+ exclude_expected = {"axis", "skipna"}
+ elif groupby_func in ("pct_change",):
+ exclude_expected = {"kwargs"}
+ elif groupby_func in ("rank",):
+ exclude_expected = {"numeric_only"}
+ elif groupby_func in ("quantile",):
+ exclude_expected = {"method", "axis"}
+ elif groupby_func in ["corrwith"]:
+ exclude_expected = {"min_periods"}
+ if groupby_func not in ["pct_change", "size"]:
+ exclude_expected |= {"axis"}
+
+ # Ensure excluded arguments are actually in the signatures
+ assert result & exclude_result == exclude_result
+ assert expected & exclude_expected == exclude_expected
+
+ result -= exclude_result
+ expected -= exclude_expected
+ assert result == expected
+
+
+def test_series_consistency(request, groupby_func):
+ # GH#48028
+ if groupby_func in ("first", "last"):
+ msg = "first and last don't exist for Series anymore"
+ pytest.skip(msg)
+
+ if groupby_func in ("cumcount", "corrwith", "ngroup"):
+ assert not hasattr(Series, groupby_func)
+ return
+
+ series_method = getattr(Series, groupby_func)
+ gb_method = getattr(SeriesGroupBy, groupby_func)
+ result = set(inspect.signature(gb_method).parameters)
+ if groupby_func == "size":
+ # "size" is a method on GroupBy but property on Series
+ expected = {"self"}
+ else:
+ expected = set(inspect.signature(series_method).parameters)
+
+ # Exclude certain arguments from result and expected depending on the operation
+ # Some of these may be purposeful inconsistencies between the APIs
+ exclude_expected, exclude_result = set(), set()
+ if groupby_func in ("any", "all"):
+ exclude_expected = {"kwargs", "bool_only", "axis"}
+ elif groupby_func in ("max", "min"):
+ exclude_expected = {"axis", "kwargs"}
+ exclude_result = {"min_count", "engine", "engine_kwargs"}
+ elif groupby_func in ("sum", "mean", "std", "var"):
+ exclude_expected = {"axis", "kwargs"}
+ exclude_result = {"engine", "engine_kwargs"}
+ elif groupby_func in ("median", "prod", "sem"):
+ exclude_expected = {"axis", "kwargs"}
+ elif groupby_func in ("bfill", "ffill"):
+ exclude_expected = {"inplace", "axis", "limit_area"}
+ elif groupby_func in ("cummax", "cummin"):
+ exclude_expected = {"skipna", "args"}
+ exclude_result = {"numeric_only"}
+ elif groupby_func in ("cumprod", "cumsum"):
+ exclude_expected = {"skipna"}
+ exclude_result = {"numeric_only"}
+ elif groupby_func in ("pct_change",):
+ exclude_expected = {"kwargs"}
+ elif groupby_func in ("rank",):
+ exclude_expected = {"numeric_only"}
+ elif groupby_func in ("idxmin", "idxmax"):
+ exclude_expected = {"args", "kwargs"}
+ elif groupby_func in ("quantile",):
+ exclude_result = {"numeric_only"}
+ if groupby_func not in [
+ "diff",
+ "pct_change",
+ "count",
+ "nunique",
+ "quantile",
+ "size",
+ ]:
+ exclude_expected |= {"axis"}
+
+ # Ensure excluded arguments are actually in the signatures
+ assert result & exclude_result == exclude_result
+ assert expected & exclude_expected == exclude_expected
+
+ result -= exclude_result
+ expected -= exclude_expected
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_apply.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_apply.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7c9168d6003e4ea964b1b41c7059e4afe72c8cb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_apply.py
@@ -0,0 +1,1543 @@
+from datetime import (
+ date,
+ datetime,
+)
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ bdate_range,
+)
+import pandas._testing as tm
+from pandas.tests.groupby import get_groupby_method_args
+
+
+def test_apply_func_that_appends_group_to_list_without_copy():
+ # GH: 17718
+
+ df = DataFrame(1, index=list(range(10)) * 10, columns=[0]).reset_index()
+ groups = []
+
+ def store(group):
+ groups.append(group)
+
+ df.groupby("index").apply(store)
+ expected_value = DataFrame({0: [1] * 10}, index=pd.RangeIndex(0, 100, 10))
+ expected_value.columns = expected_value.columns.astype(object)
+
+ tm.assert_frame_equal(groups[0], expected_value)
+
+
+def test_apply_index_date(using_infer_string):
+ # GH 5788
+ ts = [
+ "2011-05-16 00:00",
+ "2011-05-16 01:00",
+ "2011-05-16 02:00",
+ "2011-05-16 03:00",
+ "2011-05-17 02:00",
+ "2011-05-17 03:00",
+ "2011-05-17 04:00",
+ "2011-05-17 05:00",
+ "2011-05-18 02:00",
+ "2011-05-18 03:00",
+ "2011-05-18 04:00",
+ "2011-05-18 05:00",
+ ]
+ df = DataFrame(
+ {
+ "value": [
+ 1.40893,
+ 1.40760,
+ 1.40750,
+ 1.40649,
+ 1.40893,
+ 1.40760,
+ 1.40750,
+ 1.40649,
+ 1.40893,
+ 1.40760,
+ 1.40750,
+ 1.40649,
+ ],
+ },
+ index=Index(pd.to_datetime(ts), name="date_time"),
+ )
+ expected = df.groupby(df.index.date).idxmax()
+ result = df.groupby(df.index.date).apply(lambda x: x.idxmax())
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_index_date_object():
+ # GH 5789
+ # don't auto coerce dates
+ ts = [
+ "2011-05-16 00:00",
+ "2011-05-16 01:00",
+ "2011-05-16 02:00",
+ "2011-05-16 03:00",
+ "2011-05-17 02:00",
+ "2011-05-17 03:00",
+ "2011-05-17 04:00",
+ "2011-05-17 05:00",
+ "2011-05-18 02:00",
+ "2011-05-18 03:00",
+ "2011-05-18 04:00",
+ "2011-05-18 05:00",
+ ]
+ df = DataFrame([row.split() for row in ts], columns=["date", "time"])
+ df["value"] = [
+ 1.40893,
+ 1.40760,
+ 1.40750,
+ 1.40649,
+ 1.40893,
+ 1.40760,
+ 1.40750,
+ 1.40649,
+ 1.40893,
+ 1.40760,
+ 1.40750,
+ 1.40649,
+ ]
+ exp_idx = Index(["2011-05-16", "2011-05-17", "2011-05-18"], name="date")
+ expected = Series(["00:00", "02:00", "02:00"], index=exp_idx)
+ result = df.groupby("date").apply(lambda x: x["time"][x["value"].idxmax()])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "df, group_names",
+ [
+ (DataFrame({"a": [1, 1, 1, 2, 3], "b": ["a", "a", "a", "b", "c"]}), [1, 2, 3]),
+ (DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]}), [0, 1]),
+ (DataFrame({"a": [1]}), [1]),
+ (DataFrame({"a": [1, 1, 1, 2, 2, 1, 1, 2], "b": range(8)}), [1, 2]),
+ (DataFrame({"a": [1, 2, 3, 1, 2, 3], "two": [4, 5, 6, 7, 8, 9]}), [1, 2, 3]),
+ (
+ DataFrame(
+ {
+ "a": list("aaabbbcccc"),
+ "B": [3, 4, 3, 6, 5, 2, 1, 9, 5, 4],
+ "C": [4, 0, 2, 2, 2, 7, 8, 6, 2, 8],
+ }
+ ),
+ ["a", "b", "c"],
+ ),
+ (DataFrame([[1, 2, 3], [2, 2, 3]], columns=["a", "b", "c"]), [1, 2]),
+ ],
+ ids=[
+ "GH2936",
+ "GH7739 & GH10519",
+ "GH10519",
+ "GH2656",
+ "GH12155",
+ "GH20084",
+ "GH21417",
+ ],
+)
+def test_group_apply_once_per_group(df, group_names):
+ # GH2936, GH7739, GH10519, GH2656, GH12155, GH20084, GH21417
+
+ # This test should ensure that a function is only evaluated
+ # once per group. Previously the function has been evaluated twice
+ # on the first group to check if the Cython index slider is safe to use
+ # This test ensures that the side effect (append to list) is only triggered
+ # once per group
+
+ names = []
+ # cannot parameterize over the functions since they need external
+ # `names` to detect side effects
+
+ def f_copy(group):
+ # this takes the fast apply path
+ names.append(group.name)
+ return group.copy()
+
+ def f_nocopy(group):
+ # this takes the slow apply path
+ names.append(group.name)
+ return group
+
+ def f_scalar(group):
+ # GH7739, GH2656
+ names.append(group.name)
+ return 0
+
+ def f_none(group):
+ # GH10519, GH12155, GH21417
+ names.append(group.name)
+
+ def f_constant_df(group):
+ # GH2936, GH20084
+ names.append(group.name)
+ return DataFrame({"a": [1], "b": [1]})
+
+ for func in [f_copy, f_nocopy, f_scalar, f_none, f_constant_df]:
+ del names[:]
+
+ df.groupby("a").apply(func)
+ assert names == group_names
+
+
+def test_group_apply_once_per_group2(capsys):
+ # GH: 31111
+ # groupby-apply need to execute len(set(group_by_columns)) times
+
+ expected = 2 # Number of times `apply` should call a function for the current test
+
+ df = DataFrame(
+ {
+ "group_by_column": [0, 0, 0, 0, 1, 1, 1, 1],
+ "test_column": ["0", "2", "4", "6", "8", "10", "12", "14"],
+ },
+ index=["0", "2", "4", "6", "8", "10", "12", "14"],
+ )
+
+ df.groupby("group_by_column", group_keys=False).apply(
+ lambda df: print("function_called")
+ )
+
+ result = capsys.readouterr().out.count("function_called")
+ # If `groupby` behaves unexpectedly, this test will break
+ assert result == expected
+
+
+def test_apply_fast_slow_identical():
+ # GH 31613
+
+ df = DataFrame({"A": [0, 0, 1], "b": range(3)})
+
+ # For simple index structures we check for fast/slow apply using
+ # an identity check on in/output
+ def slow(group):
+ return group
+
+ def fast(group):
+ return group.copy()
+
+ fast_df = df.groupby("A", group_keys=False).apply(fast)
+ slow_df = df.groupby("A", group_keys=False).apply(slow)
+ tm.assert_frame_equal(fast_df, slow_df)
+
+
+def test_apply_fast_slow_identical_index():
+ # GH#44803
+ df = DataFrame(
+ {
+ "name": ["Alice", "Bob", "Carl"],
+ "age": [20, 21, 20],
+ }
+ ).set_index("name")
+
+ grp_by_same_value = df.groupby(["age"], group_keys=False).apply(lambda group: group)
+ grp_by_copy = df.groupby(["age"], group_keys=False).apply(
+ lambda group: group.copy()
+ )
+ tm.assert_frame_equal(grp_by_same_value, grp_by_copy)
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda x: x,
+ lambda x: x[:],
+ lambda x: x.copy(deep=False),
+ lambda x: x.copy(deep=True),
+ ],
+)
+def test_groupby_apply_identity_maybecopy_index_identical(func):
+ # GH 14927
+ # Whether the function returns a copy of the input data or not should not
+ # have an impact on the index structure of the result since this is not
+ # transparent to the user
+
+ df = DataFrame({"g": [1, 2, 2, 2], "a": [1, 2, 3, 4], "b": [5, 6, 7, 8]})
+ result = df.groupby("g", group_keys=False).apply(func)
+ tm.assert_frame_equal(result, df[["a", "b"]])
+
+
+def test_apply_with_mixed_dtype():
+ # GH3480, apply with mixed dtype on axis=1 breaks in 0.11
+ df = DataFrame(
+ {
+ "foo1": np.random.default_rng(2).standard_normal(6),
+ "foo2": ["one", "two", "two", "three", "one", "two"],
+ }
+ )
+ result = df.apply(lambda x: x, axis=1)
+ expected = df
+ tm.assert_frame_equal(result, expected)
+
+ # GH 3610 incorrect dtype conversion with as_index=False
+ df = DataFrame({"c1": [1, 2, 6, 6, 8]})
+ df["c2"] = df.c1 / 2.0
+ result1 = df.groupby("c2").mean().reset_index()
+ result2 = df.groupby("c2", as_index=False).mean()
+ tm.assert_frame_equal(result1, result2)
+
+
+def test_groupby_as_index_apply(as_index):
+ # GH #4648 and #3417
+ df = DataFrame(
+ {
+ "item_id": ["b", "b", "a", "c", "a", "b"],
+ "user_id": [1, 2, 1, 1, 3, 1],
+ "time": range(6),
+ }
+ )
+ gb = df.groupby("user_id", as_index=as_index)
+
+ expected = DataFrame(
+ {
+ "item_id": ["b", "b", "a", "a"],
+ "user_id": [1, 2, 1, 3],
+ "time": [0, 1, 2, 4],
+ },
+ index=[0, 1, 2, 4],
+ )
+ result = gb.head(2)
+ tm.assert_frame_equal(result, expected)
+
+ # apply doesn't maintain the original ordering
+ # changed in GH5610 as the as_index=False returns a MI here
+ if as_index:
+ tp = [(1, 0), (1, 2), (2, 1), (3, 4)]
+ index = MultiIndex.from_tuples(tp, names=["user_id", None])
+ else:
+ index = Index([0, 2, 1, 4])
+ expected = DataFrame(
+ {
+ "item_id": list("baba"),
+ "time": [0, 2, 1, 4],
+ },
+ index=index,
+ )
+ result = gb.apply(lambda x: x.head(2))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_as_index_apply_str():
+ ind = Index(list("abcde"))
+ df = DataFrame([[1, 2], [2, 3], [1, 4], [1, 5], [2, 6]], index=ind)
+ res = df.groupby(0, as_index=False, group_keys=False).apply(lambda x: x).index
+ tm.assert_index_equal(res, ind)
+
+
+def test_apply_concat_preserve_names(three_group):
+ grouped = three_group.groupby(["A", "B"])
+
+ def desc(group):
+ result = group.describe()
+ result.index.name = "stat"
+ return result
+
+ def desc2(group):
+ result = group.describe()
+ result.index.name = "stat"
+ result = result[: len(group)]
+ # weirdo
+ return result
+
+ def desc3(group):
+ result = group.describe()
+
+ # names are different
+ result.index.name = f"stat_{len(group):d}"
+
+ result = result[: len(group)]
+ # weirdo
+ return result
+
+ result = grouped.apply(desc)
+ assert result.index.names == ("A", "B", "stat")
+
+ result2 = grouped.apply(desc2)
+ assert result2.index.names == ("A", "B", "stat")
+
+ result3 = grouped.apply(desc3)
+ assert result3.index.names == ("A", "B", None)
+
+
+def test_apply_series_to_frame():
+ def f(piece):
+ with np.errstate(invalid="ignore"):
+ logged = np.log(piece)
+ return DataFrame(
+ {"value": piece, "demeaned": piece - piece.mean(), "logged": logged}
+ )
+
+ dr = bdate_range("1/1/2000", periods=10)
+ ts = Series(np.random.default_rng(2).standard_normal(10), index=dr)
+
+ grouped = ts.groupby(lambda x: x.month, group_keys=False)
+ result = grouped.apply(f)
+
+ assert isinstance(result, DataFrame)
+ assert not hasattr(result, "name") # GH49907
+ tm.assert_index_equal(result.index, ts.index)
+
+
+def test_apply_series_yield_constant(df):
+ result = df.groupby(["A", "B"])["C"].apply(len)
+ assert result.index.names[:2] == ("A", "B")
+
+
+def test_apply_frame_yield_constant(df):
+ # GH13568
+ result = df.groupby(["A", "B"]).apply(len)
+ assert isinstance(result, Series)
+ assert result.name is None
+
+ result = df.groupby(["A", "B"])[["C", "D"]].apply(len)
+ assert isinstance(result, Series)
+ assert result.name is None
+
+
+def test_apply_frame_to_series(df):
+ grouped = df.groupby(["A", "B"])
+ result = grouped.apply(len)
+ expected = grouped.count()["C"]
+ tm.assert_index_equal(result.index, expected.index)
+ tm.assert_numpy_array_equal(result.values, expected.values)
+
+
+def test_apply_frame_not_as_index_column_name(df):
+ # GH 35964 - path within _wrap_applied_output not hit by a test
+ grouped = df.groupby(["A", "B"], as_index=False)
+ result = grouped.apply(len)
+ expected = grouped.count().rename(columns={"C": np.nan}).drop(columns="D")
+ # TODO(GH#34306): Use assert_frame_equal when column name is not np.nan
+ tm.assert_index_equal(result.index, expected.index)
+ tm.assert_numpy_array_equal(result.values, expected.values)
+
+
+def test_apply_frame_concat_series():
+ def trans(group):
+ return group.groupby("B")["C"].sum().sort_values().iloc[:2]
+
+ def trans2(group):
+ grouped = group.groupby(df.reindex(group.index)["B"])
+ return grouped.sum().sort_values().iloc[:2]
+
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).integers(0, 5, 1000),
+ "B": np.random.default_rng(2).integers(0, 5, 1000),
+ "C": np.random.default_rng(2).standard_normal(1000),
+ }
+ )
+
+ result = df.groupby("A").apply(trans)
+ exp = df.groupby("A")["C"].apply(trans2)
+ tm.assert_series_equal(result, exp, check_names=False)
+ assert result.name == "C"
+
+
+def test_apply_transform(ts):
+ grouped = ts.groupby(lambda x: x.month, group_keys=False)
+ result = grouped.apply(lambda x: x * 2)
+ expected = grouped.transform(lambda x: x * 2)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_multikey_corner(tsframe):
+ grouped = tsframe.groupby([lambda x: x.year, lambda x: x.month])
+
+ def f(group):
+ return group.sort_values("A")[-5:]
+
+ result = grouped.apply(f)
+ for key, group in grouped:
+ tm.assert_frame_equal(result.loc[key], f(group))
+
+
+@pytest.mark.parametrize("group_keys", [True, False])
+def test_apply_chunk_view(group_keys):
+ # Low level tinkering could be unsafe, make sure not
+ df = DataFrame({"key": [1, 1, 1, 2, 2, 2, 3, 3, 3], "value": range(9)})
+
+ result = df.groupby("key", group_keys=group_keys).apply(lambda x: x.iloc[:2])
+ expected = df[["value"]].take([0, 1, 3, 4, 6, 7])
+ if group_keys:
+ expected.index = MultiIndex.from_arrays(
+ [[1, 1, 2, 2, 3, 3], expected.index], names=["key", None]
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_no_name_column_conflict():
+ df = DataFrame(
+ {
+ "name": [1, 1, 1, 1, 1, 1, 2, 2, 2, 2],
+ "name2": [0, 0, 0, 1, 1, 1, 0, 0, 1, 1],
+ "value": range(9, -1, -1),
+ }
+ )
+
+ # it works! #2605
+ grouped = df.groupby(["name", "name2"])
+ grouped.apply(lambda x: x.sort_values("value", inplace=True))
+
+
+def test_apply_typecast_fail():
+ df = DataFrame(
+ {
+ "d": [1.0, 1.0, 1.0, 2.0, 2.0, 2.0],
+ "c": np.tile(["a", "b", "c"], 2),
+ "v": np.arange(1.0, 7.0),
+ }
+ )
+
+ def f(group):
+ v = group["v"]
+ group["v2"] = (v - v.min()) / (v.max() - v.min())
+ return group
+
+ result = df.groupby("d", group_keys=False).apply(f)
+
+ expected = df[["c", "v"]]
+ expected["v2"] = np.tile([0.0, 0.5, 1], 2)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_multiindex_fail():
+ index = MultiIndex.from_arrays([[0, 0, 0, 1, 1, 1], [1, 2, 3, 1, 2, 3]])
+ df = DataFrame(
+ {
+ "d": [1.0, 1.0, 1.0, 2.0, 2.0, 2.0],
+ "c": np.tile(["a", "b", "c"], 2),
+ "v": np.arange(1.0, 7.0),
+ },
+ index=index,
+ )
+
+ def f(group):
+ v = group["v"]
+ group["v2"] = (v - v.min()) / (v.max() - v.min())
+ return group
+
+ result = df.groupby("d", group_keys=False).apply(f)
+
+ expected = df[["c", "v"]]
+ expected["v2"] = np.tile([0.0, 0.5, 1], 2)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_corner(tsframe):
+ result = tsframe.groupby(lambda x: x.year, group_keys=False).apply(lambda x: x * 2)
+ expected = tsframe * 2
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_without_copy():
+ # GH 5545
+ # returning a non-copy in an applied function fails
+
+ data = DataFrame(
+ {
+ "id_field": [100, 100, 200, 300],
+ "category": ["a", "b", "c", "c"],
+ "value": [1, 2, 3, 4],
+ }
+ )
+
+ def filt1(x):
+ if x.shape[0] == 1:
+ return x.copy()
+ else:
+ return x[x.category == "c"]
+
+ def filt2(x):
+ if x.shape[0] == 1:
+ return x
+ else:
+ return x[x.category == "c"]
+
+ expected = data.groupby("id_field").apply(filt1)
+ result = data.groupby("id_field").apply(filt2)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("test_series", [True, False])
+def test_apply_with_duplicated_non_sorted_axis(test_series):
+ # GH 30667
+ df = DataFrame(
+ [["x", "p"], ["x", "p"], ["x", "o"]], columns=["X", "Y"], index=[1, 2, 2]
+ )
+ if test_series:
+ ser = df.set_index("Y")["X"]
+ result = ser.groupby(level=0, group_keys=False).apply(lambda x: x)
+ expected = ser
+ tm.assert_series_equal(result, expected)
+ else:
+ result = df.groupby("Y", group_keys=False).apply(lambda x: x)
+ expected = df[["X"]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_reindex_values():
+ # GH: 26209
+ # reindexing from a single column of a groupby object with duplicate indices caused
+ # a ValueError (cannot reindex from duplicate axis) in 0.24.2, the problem was
+ # solved in #30679
+ values = [1, 2, 3, 4]
+ indices = [1, 1, 2, 2]
+ df = DataFrame({"group": ["Group1", "Group2"] * 2, "value": values}, index=indices)
+ expected = Series(values, index=indices, name="value")
+
+ def reindex_helper(x):
+ return x.reindex(np.arange(x.index.min(), x.index.max() + 1))
+
+ # the following group by raised a ValueError
+ result = df.groupby("group", group_keys=False).value.apply(reindex_helper)
+ tm.assert_series_equal(expected, result)
+
+
+def test_apply_corner_cases():
+ # #535, can't use sliding iterator
+
+ N = 10
+ labels = np.random.default_rng(2).integers(0, 100, size=N)
+ df = DataFrame(
+ {
+ "key": labels,
+ "value1": np.random.default_rng(2).standard_normal(N),
+ "value2": ["foo", "bar", "baz", "qux", "a"] * (N // 5),
+ }
+ )
+
+ grouped = df.groupby("key", group_keys=False)
+
+ def f(g):
+ g["value3"] = g["value1"] * 2
+ return g
+
+ result = grouped.apply(f)
+ assert "value3" in result
+
+
+def test_apply_numeric_coercion_when_datetime():
+ # In the past, group-by/apply operations have been over-eager
+ # in converting dtypes to numeric, in the presence of datetime
+ # columns. Various GH issues were filed, the reproductions
+ # for which are here.
+
+ # GH 15670
+ df = DataFrame(
+ {"Number": [1, 2], "Date": ["2017-03-02"] * 2, "Str": ["foo", "inf"]}
+ )
+ expected = df.groupby(["Number"]).apply(lambda x: x.iloc[0])
+ df.Date = pd.to_datetime(df.Date)
+ result = df.groupby(["Number"]).apply(lambda x: x.iloc[0])
+ tm.assert_series_equal(result["Str"], expected["Str"])
+
+
+def test_apply_numeric_coercion_when_datetime_getitem():
+ # GH 15421
+ df = DataFrame(
+ {"A": [10, 20, 30], "B": ["foo", "3", "4"], "T": [pd.Timestamp("12:31:22")] * 3}
+ )
+
+ def get_B(g):
+ return g.iloc[0][["B"]]
+
+ result = df.groupby("A").apply(get_B)["B"]
+ expected = df.B
+ expected.index = df.A
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_numeric_coercion_when_datetime_with_nat():
+ # GH 14423
+ def predictions(tool):
+ out = Series(index=["p1", "p2", "useTime"], dtype=object)
+ if "step1" in list(tool.State):
+ out["p1"] = str(tool[tool.State == "step1"].Machine.values[0])
+ if "step2" in list(tool.State):
+ out["p2"] = str(tool[tool.State == "step2"].Machine.values[0])
+ out["useTime"] = str(tool[tool.State == "step2"].oTime.values[0])
+ return out
+
+ df1 = DataFrame(
+ {
+ "Key": ["B", "B", "A", "A"],
+ "State": ["step1", "step2", "step1", "step2"],
+ "oTime": ["", "2016-09-19 05:24:33", "", "2016-09-19 23:59:04"],
+ "Machine": ["23", "36L", "36R", "36R"],
+ }
+ )
+ df2 = df1.copy()
+ df2.oTime = pd.to_datetime(df2.oTime)
+ expected = df1.groupby("Key").apply(predictions).p1
+ result = df2.groupby("Key").apply(predictions).p1
+ tm.assert_series_equal(expected, result)
+
+
+def test_apply_aggregating_timedelta_and_datetime():
+ # Regression test for GH 15562
+ # The following groupby caused ValueErrors and IndexErrors pre 0.20.0
+
+ df = DataFrame(
+ {
+ "clientid": ["A", "B", "C"],
+ "datetime": [np.datetime64("2017-02-01 00:00:00")] * 3,
+ }
+ )
+ df["time_delta_zero"] = df.datetime - df.datetime
+ result = df.groupby("clientid").apply(
+ lambda ddf: Series(
+ {"clientid_age": ddf.time_delta_zero.min(), "date": ddf.datetime.min()}
+ )
+ )
+ expected = DataFrame(
+ {
+ "clientid": ["A", "B", "C"],
+ "clientid_age": [np.timedelta64(0, "D")] * 3,
+ "date": [np.datetime64("2017-02-01 00:00:00")] * 3,
+ }
+ ).set_index("clientid")
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_groupby_datetimeindex():
+ # GH 26182
+ # groupby apply failed on dataframe with DatetimeIndex
+
+ data = [["A", 10], ["B", 20], ["B", 30], ["C", 40], ["C", 50]]
+ df = DataFrame(
+ data, columns=["Name", "Value"], index=pd.date_range("2020-09-01", "2020-09-05")
+ )
+
+ result = df.groupby("Name").sum()
+
+ expected = DataFrame({"Name": ["A", "B", "C"], "Value": [10, 50, 90]})
+ expected.set_index("Name", inplace=True)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_time_field_bug():
+ # Test a fix for the following error related to GH issue 11324 When
+ # non-key fields in a group-by dataframe contained time-based fields
+ # that were not returned by the apply function, an exception would be
+ # raised.
+
+ df = DataFrame({"a": 1, "b": [datetime.now() for nn in range(10)]})
+
+ def func_with_no_date(batch):
+ return Series({"c": 2})
+
+ def func_with_date(batch):
+ return Series({"b": datetime(2015, 1, 1), "c": 2})
+
+ dfg_no_conversion = df.groupby(by=["a"]).apply(func_with_no_date)
+ dfg_no_conversion_expected = DataFrame({"c": 2}, index=[1])
+ dfg_no_conversion_expected.index.name = "a"
+
+ dfg_conversion = df.groupby(by=["a"]).apply(func_with_date)
+ dfg_conversion_expected = DataFrame(
+ {"b": pd.Timestamp(2015, 1, 1), "c": 2}, index=[1]
+ )
+ dfg_conversion_expected.index.name = "a"
+
+ tm.assert_frame_equal(dfg_no_conversion, dfg_no_conversion_expected)
+ tm.assert_frame_equal(dfg_conversion, dfg_conversion_expected)
+
+
+def test_gb_apply_list_of_unequal_len_arrays():
+ # GH1738
+ df = DataFrame(
+ {
+ "group1": ["a", "a", "a", "b", "b", "b", "a", "a", "a", "b", "b", "b"],
+ "group2": ["c", "c", "d", "d", "d", "e", "c", "c", "d", "d", "d", "e"],
+ "weight": [1.1, 2, 3, 4, 5, 6, 2, 4, 6, 8, 1, 2],
+ "value": [7.1, 8, 9, 10, 11, 12, 8, 7, 6, 5, 4, 3],
+ }
+ )
+ df = df.set_index(["group1", "group2"])
+ df_grouped = df.groupby(level=["group1", "group2"], sort=True)
+
+ def noddy(value, weight):
+ out = np.array(value * weight).repeat(3)
+ return out
+
+ # the kernel function returns arrays of unequal length
+ # pandas sniffs the first one, sees it's an array and not
+ # a list, and assumed the rest are of equal length
+ # and so tries a vstack
+
+ # don't die
+ df_grouped.apply(lambda x: noddy(x.value, x.weight))
+
+
+def test_groupby_apply_all_none():
+ # Tests to make sure no errors if apply function returns all None
+ # values. Issue 9684.
+ test_df = DataFrame({"groups": [0, 0, 1, 1], "random_vars": [8, 7, 4, 5]})
+
+ def test_func(x):
+ pass
+
+ result = test_df.groupby("groups").apply(test_func)
+ expected = DataFrame(columns=["random_vars"], dtype="int64")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "in_data, out_idx, out_data",
+ [
+ [
+ {"groups": [1, 1, 1, 2], "vars": [0, 1, 2, 3]},
+ [[1, 1], [0, 2]],
+ {"vars": [0, 2]},
+ ],
+ [
+ {"groups": [1, 2, 2, 2], "vars": [0, 1, 2, 3]},
+ [[2, 2], [1, 3]],
+ {"vars": [1, 3]},
+ ],
+ ],
+)
+def test_groupby_apply_none_first(in_data, out_idx, out_data):
+ # GH 12824. Tests if apply returns None first.
+ test_df1 = DataFrame(in_data)
+
+ def test_func(x):
+ if x.shape[0] < 2:
+ return None
+ return x.iloc[[0, -1]]
+
+ result1 = test_df1.groupby("groups").apply(test_func)
+ index1 = MultiIndex.from_arrays(out_idx, names=["groups", None])
+ expected1 = DataFrame(out_data, index=index1)
+ tm.assert_frame_equal(result1, expected1)
+
+
+def test_groupby_apply_return_empty_chunk():
+ # GH 22221: apply filter which returns some empty groups
+ df = DataFrame({"value": [0, 1], "group": ["filled", "empty"]})
+ groups = df.groupby("group")
+ result = groups.apply(lambda group: group[group.value != 1]["value"])
+ expected = Series(
+ [0],
+ name="value",
+ index=MultiIndex.from_product(
+ [["empty", "filled"], [0]], names=["group", None]
+ ).drop("empty"),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("meth", ["apply", "transform"])
+def test_apply_with_mixed_types(meth):
+ # gh-20949
+ df = DataFrame({"A": "a a b".split(), "B": [1, 2, 3], "C": [4, 6, 5]})
+ g = df.groupby("A", group_keys=False)
+
+ result = getattr(g, meth)(lambda x: x / x.sum())
+ expected = DataFrame({"B": [1 / 3.0, 2 / 3.0, 1], "C": [0.4, 0.6, 1.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_func_returns_object():
+ # GH 28652
+ df = DataFrame({"a": [1, 2]}, index=Index([1, 2]))
+ result = df.groupby("a").apply(lambda g: g.index)
+ expected = Series([Index([1]), Index([2])], index=Index([1, 2], name="a"))
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "group_column_dtlike",
+ [datetime.today(), datetime.today().date(), datetime.today().time()],
+)
+def test_apply_datetime_issue(group_column_dtlike):
+ # GH-28247
+ # groupby-apply throws an error if one of the columns in the DataFrame
+ # is a datetime object and the column labels are different from
+ # standard int values in range(len(num_columns))
+
+ df = DataFrame({"a": ["foo"], "b": [group_column_dtlike]})
+ result = df.groupby("a").apply(lambda x: Series(["spam"], index=[42]))
+
+ expected = DataFrame(["spam"], Index(["foo"], dtype="str", name="a"), columns=[42])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_series_return_dataframe_groups():
+ # GH 10078
+ tdf = DataFrame(
+ {
+ "day": {
+ 0: pd.Timestamp("2015-02-24 00:00:00"),
+ 1: pd.Timestamp("2015-02-24 00:00:00"),
+ 2: pd.Timestamp("2015-02-24 00:00:00"),
+ 3: pd.Timestamp("2015-02-24 00:00:00"),
+ 4: pd.Timestamp("2015-02-24 00:00:00"),
+ },
+ "userAgent": {
+ 0: "some UA string",
+ 1: "some UA string",
+ 2: "some UA string",
+ 3: "another UA string",
+ 4: "some UA string",
+ },
+ "userId": {
+ 0: "17661101",
+ 1: "17661101",
+ 2: "17661101",
+ 3: "17661101",
+ 4: "17661101",
+ },
+ }
+ )
+
+ def most_common_values(df):
+ return Series({c: s.value_counts().index[0] for c, s in df.items()})
+
+ result = tdf.groupby("day").apply(most_common_values)["userId"]
+ expected = Series(
+ ["17661101"], index=pd.DatetimeIndex(["2015-02-24"], name="day"), name="userId"
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("category", [False, True])
+def test_apply_multi_level_name(category):
+ # https://github.com/pandas-dev/pandas/issues/31068
+ b = [1, 2] * 5
+ if category:
+ b = pd.Categorical(b, categories=[1, 2, 3])
+ expected_index = pd.CategoricalIndex([1, 2, 3], categories=[1, 2, 3], name="B")
+ expected_values = [20, 25, 0]
+ else:
+ expected_index = Index([1, 2], name="B")
+ expected_values = [20, 25]
+ expected = DataFrame(
+ {"C": expected_values, "D": expected_values}, index=expected_index
+ )
+
+ df = DataFrame(
+ {"A": np.arange(10), "B": b, "C": list(range(10)), "D": list(range(10))}
+ ).set_index(["A", "B"])
+ result = df.groupby("B", observed=False).apply(lambda x: x.sum())
+ tm.assert_frame_equal(result, expected)
+ assert df.index.names == ["A", "B"]
+
+
+def test_groupby_apply_datetime_result_dtypes(using_infer_string):
+ # GH 14849
+ data = DataFrame.from_records(
+ [
+ (pd.Timestamp(2016, 1, 1), "red", "dark", 1, "8"),
+ (pd.Timestamp(2015, 1, 1), "green", "stormy", 2, "9"),
+ (pd.Timestamp(2014, 1, 1), "blue", "bright", 3, "10"),
+ (pd.Timestamp(2013, 1, 1), "blue", "calm", 4, "potato"),
+ ],
+ columns=["observation", "color", "mood", "intensity", "score"],
+ )
+ result = data.groupby("color").apply(lambda g: g.iloc[0]).dtypes
+ dtype = pd.StringDtype(na_value=np.nan) if using_infer_string else object
+ expected = Series(
+ [np.dtype("datetime64[us]"), dtype, np.int64, dtype],
+ index=["observation", "mood", "intensity", "score"],
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ pd.CategoricalIndex(list("abc")),
+ pd.interval_range(0, 3),
+ pd.period_range("2020", periods=3, freq="D"),
+ MultiIndex.from_tuples([("a", 0), ("a", 1), ("b", 0)]),
+ ],
+)
+def test_apply_index_has_complex_internals(index):
+ # GH 31248
+ df = DataFrame({"group": [1, 1, 2], "value": [0, 1, 0]}, index=index)
+ result = df.groupby("group", group_keys=False).apply(lambda x: x)
+ tm.assert_frame_equal(result, df[["value"]])
+
+
+@pytest.mark.parametrize(
+ "function, expected_values",
+ [
+ (lambda x: x.index.to_list(), [[0, 1], [2, 3]]),
+ (lambda x: set(x.index.to_list()), [{0, 1}, {2, 3}]),
+ (lambda x: tuple(x.index.to_list()), [(0, 1), (2, 3)]),
+ (
+ lambda x: dict(enumerate(x.index.to_list())),
+ [{0: 0, 1: 1}, {0: 2, 1: 3}],
+ ),
+ (
+ lambda x: [{n: i} for (n, i) in enumerate(x.index.to_list())],
+ [[{0: 0}, {1: 1}], [{0: 2}, {1: 3}]],
+ ),
+ ],
+)
+def test_apply_function_returns_non_pandas_non_scalar(function, expected_values):
+ # GH 31441
+ df = DataFrame(["A", "A", "B", "B"], columns=["groups"])
+ result = df.groupby("groups").apply(function)
+ expected = Series(expected_values, index=Index(["A", "B"], name="groups"))
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_function_returns_numpy_array():
+ # GH 31605
+ def fct(group):
+ return group["B"].values.flatten()
+
+ df = DataFrame({"A": ["a", "a", "b", "none"], "B": [1, 2, 3, np.nan]})
+
+ result = df.groupby("A").apply(fct)
+ expected = Series(
+ [[1.0, 2.0], [3.0], [np.nan]], index=Index(["a", "b", "none"], name="A")
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("function", [lambda gr: gr.index, lambda gr: gr.index + 1 - 1])
+def test_apply_function_index_return(function):
+ # GH: 22541
+ df = DataFrame([1, 2, 2, 2, 1, 2, 3, 1, 3, 1], columns=["id"])
+ result = df.groupby("id").apply(function)
+ expected = Series(
+ [Index([0, 4, 7, 9]), Index([1, 2, 3, 5]), Index([6, 8])],
+ index=Index([1, 2, 3], name="id"),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_function_with_indexing_return_column():
+ # GH#7002, GH#41480, GH#49256
+ df = DataFrame(
+ {
+ "foo1": ["one", "two", "two", "three", "one", "two"],
+ "foo2": [1, 2, 4, 4, 5, 6],
+ }
+ )
+ result = df.groupby("foo1", as_index=False).apply(lambda x: x.mean())
+ expected = DataFrame(
+ {
+ "foo1": ["one", "three", "two"],
+ "foo2": [3.0, 4.0, 4.0],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "udf",
+ [lambda x: x.copy(), lambda x: x.copy().rename(lambda y: y + 1)],
+)
+@pytest.mark.parametrize("group_keys", [True, False])
+def test_apply_result_type(group_keys, udf):
+ # https://github.com/pandas-dev/pandas/issues/34809
+ # We'd like to control whether the group keys end up in the index
+ # regardless of whether the UDF happens to be a transform.
+ df = DataFrame({"A": ["a", "b"], "B": [1, 2]})
+ df_result = df.groupby("A", group_keys=group_keys).apply(udf)
+ series_result = df.B.groupby(df.A, group_keys=group_keys).apply(udf)
+
+ if group_keys:
+ assert df_result.index.nlevels == 2
+ assert series_result.index.nlevels == 2
+ else:
+ assert df_result.index.nlevels == 1
+ assert series_result.index.nlevels == 1
+
+
+def test_result_order_group_keys_false():
+ # GH 34998
+ # apply result order should not depend on whether index is the same or just equal
+ df = DataFrame({"A": [2, 1, 2], "B": [1, 2, 3]})
+ result = df.groupby("A", group_keys=False).apply(lambda x: x)
+ expected = df.groupby("A", group_keys=False).apply(lambda x: x.copy())
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_with_timezones_aware():
+ # GH: 27212
+ dates = ["2001-01-01"] * 2 + ["2001-01-02"] * 2 + ["2001-01-03"] * 2
+ index_no_tz = pd.DatetimeIndex(dates)
+ index_tz = pd.DatetimeIndex(dates, tz="UTC")
+ df1 = DataFrame({"x": list(range(2)) * 3, "y": range(6), "t": index_no_tz})
+ df2 = DataFrame({"x": list(range(2)) * 3, "y": range(6), "t": index_tz})
+
+ result1 = df1.groupby("x", group_keys=False).apply(lambda df: df[["y"]].copy())
+ result2 = df2.groupby("x", group_keys=False).apply(lambda df: df[["y"]].copy())
+
+ tm.assert_frame_equal(result1, result2)
+
+
+def test_apply_is_unchanged_when_other_methods_are_called_first(reduction_func):
+ # GH #34656
+ # GH #34271
+ df = DataFrame(
+ {
+ "a": [99, 99, 99, 88, 88, 88],
+ "b": [1, 2, 3, 4, 5, 6],
+ "c": [10, 20, 30, 40, 50, 60],
+ }
+ )
+
+ expected = DataFrame(
+ {"b": [15, 6], "c": [150, 60]},
+ index=Index([88, 99], name="a"),
+ )
+
+ # Check output when no other methods are called before .apply()
+ grp = df.groupby(by="a")
+ result = grp.apply(np.sum, axis=0)
+ tm.assert_frame_equal(result, expected)
+
+ # Check output when another method is called before .apply()
+ grp = df.groupby(by="a")
+ args = get_groupby_method_args(reduction_func, df)
+ if reduction_func == "corrwith":
+ warn = Pandas4Warning
+ msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ msg = ""
+ with tm.assert_produces_warning(warn, match=msg):
+ _ = getattr(grp, reduction_func)(*args)
+ result = grp.apply(np.sum, axis=0)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_with_date_in_multiindex_does_not_convert_to_timestamp():
+ # GH 29617
+
+ df = DataFrame(
+ {
+ "A": ["a", "a", "a", "b"],
+ "B": [
+ date(2020, 1, 10),
+ date(2020, 1, 10),
+ date(2020, 2, 10),
+ date(2020, 2, 10),
+ ],
+ "C": [1, 2, 3, 4],
+ },
+ index=Index([100, 101, 102, 103], name="idx"),
+ )
+
+ grp = df.groupby(["A", "B"])
+ result = grp.apply(lambda x: x.head(1))
+
+ expected = df.iloc[[0, 2, 3]]
+ expected = expected.reset_index()
+ expected.index = MultiIndex.from_frame(expected[["A", "B", "idx"]])
+ expected = expected.drop(columns=["A", "B", "idx"])
+
+ tm.assert_frame_equal(result, expected)
+ for val in result.index.levels[1]:
+ assert type(val) is date
+
+
+def test_apply_dropna_with_indexed_same(dropna):
+ # GH 38227
+ # GH#43205
+ df = DataFrame(
+ {
+ "col": [1, 2, 3, 4, 5],
+ "group": ["a", np.nan, np.nan, "b", "b"],
+ },
+ index=list("xxyxz"),
+ )
+ result = df.groupby("group", dropna=dropna, group_keys=False).apply(lambda x: x)
+ expected = df.dropna()[["col"]] if dropna else df[["col"]].iloc[[0, 3, 1, 2, 4]]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "as_index, expected",
+ [
+ [
+ False,
+ DataFrame(
+ [[1, 1, 1], [2, 2, 1]], columns=Index(["a", "b", None], dtype=object)
+ ),
+ ],
+ [
+ True,
+ Series(
+ [1, 1], index=MultiIndex.from_tuples([(1, 1), (2, 2)], names=["a", "b"])
+ ),
+ ],
+ ],
+)
+def test_apply_as_index_constant_lambda(as_index, expected):
+ # GH 13217
+ df = DataFrame({"a": [1, 1, 2, 2], "b": [1, 1, 2, 2], "c": [1, 1, 1, 1]})
+ result = df.groupby(["a", "b"], as_index=as_index).apply(lambda x: 1)
+ tm.assert_equal(result, expected)
+
+
+def test_sort_index_groups():
+ # GH 20420
+ df = DataFrame(
+ {"A": [1, 2, 3, 4, 5], "B": [6, 7, 8, 9, 0], "C": [1, 1, 1, 2, 2]},
+ index=range(5),
+ )
+ result = df.groupby("C").apply(lambda x: x.A.sort_index())
+ expected = Series(
+ range(1, 6),
+ index=MultiIndex.from_tuples(
+ [(1, 0), (1, 1), (1, 2), (2, 3), (2, 4)], names=["C", None]
+ ),
+ name="A",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_positional_slice_groups_datetimelike():
+ # GH 21651
+ expected = DataFrame(
+ {
+ "date": pd.date_range("2010-01-01", freq="12h", periods=5),
+ "vals": range(5),
+ "let": list("abcde"),
+ }
+ )
+ result = expected.groupby(
+ [expected.let, expected.date.dt.date], group_keys=False
+ ).apply(lambda x: x.iloc[0:])
+ tm.assert_frame_equal(result, expected[["date", "vals"]])
+
+
+def test_groupby_apply_shape_cache_safety():
+ # GH#42702 this fails if we cache_readonly Block.shape
+ df = DataFrame({"A": ["a", "a", "b"], "B": [1, 2, 3], "C": [4, 6, 5]})
+ gb = df.groupby("A")
+ result = gb[["B", "C"]].apply(lambda x: x.astype(float).max() - x.min())
+
+ expected = DataFrame(
+ {"B": [1.0, 0.0], "C": [2.0, 0.0]}, index=Index(["a", "b"], name="A")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_apply_to_series_name():
+ # GH52444
+ df = DataFrame.from_dict(
+ {
+ "a": ["a", "b", "a", "b"],
+ "b1": ["aa", "ac", "ac", "ad"],
+ "b2": ["aa", "aa", "aa", "ac"],
+ }
+ )
+ grp = df.groupby("a")[["b1", "b2"]]
+ result = grp.apply(lambda x: x.unstack().value_counts())
+
+ expected_idx = MultiIndex.from_arrays(
+ arrays=[["a", "a", "b", "b", "b"], ["aa", "ac", "ac", "ad", "aa"]],
+ names=["a", None],
+ )
+ expected = Series([3, 1, 2, 1, 1], index=expected_idx, name="count")
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_na(dropna):
+ # GH#28984
+ df = DataFrame(
+ {"grp": [1, 1, 2, 2], "y": [1, 0, 2, 5], "z": [1, 2, np.nan, np.nan]}
+ )
+ dfgrp = df.groupby("grp", dropna=dropna)
+ result = dfgrp.apply(lambda grp_df: grp_df.nlargest(1, "z"))
+ expected = dfgrp.apply(lambda x: x.sort_values("z", ascending=False).head(1))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_empty_string_nan_coerce_bug():
+ # GH#24903
+ result = (
+ DataFrame(
+ {
+ "a": [1, 1, 2, 2],
+ "b": ["", "", "", ""],
+ "c": pd.to_datetime([1, 2, 3, 4], unit="s"),
+ }
+ )
+ .groupby(["a", "b"])
+ .apply(lambda df: df.iloc[-1])
+ )
+ expected = DataFrame(
+ [[pd.to_datetime(2, unit="s")], [pd.to_datetime(4, unit="s")]],
+ columns=["c"],
+ index=MultiIndex.from_tuples([(1, ""), (2, "")], names=["a", "b"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_values", [[1, 2, 3], [1.0, 2.0, 3.0]])
+def test_apply_index_key_error_bug(index_values):
+ # GH 44310
+ result = DataFrame(
+ {
+ "a": ["aa", "a2", "a3"],
+ "b": [1, 2, 3],
+ },
+ index=Index(index_values),
+ )
+ expected = DataFrame(
+ {
+ "b_mean": [2.0, 3.0, 1.0],
+ },
+ index=Index(["a2", "a3", "aa"], name="a"),
+ )
+ result = result.groupby("a").apply(
+ lambda df: Series([df["b"].mean()], index=["b_mean"])
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "arg,idx",
+ [
+ [
+ [
+ 1,
+ 2,
+ 3,
+ ],
+ [
+ 0.1,
+ 0.3,
+ 0.2,
+ ],
+ ],
+ [
+ [
+ 1,
+ 2,
+ 3,
+ ],
+ [
+ 0.1,
+ 0.2,
+ 0.3,
+ ],
+ ],
+ [
+ [
+ 1,
+ 4,
+ 3,
+ ],
+ [
+ 0.1,
+ 0.4,
+ 0.2,
+ ],
+ ],
+ ],
+)
+def test_apply_nonmonotonic_float_index(arg, idx):
+ # GH 34455
+ df = DataFrame({"grp": arg, "col": arg}, index=idx)
+ result = df.groupby("grp", group_keys=False).apply(lambda x: x)
+ expected = df[["col"]]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("args, kwargs", [([True], {}), ([], {"numeric_only": True})])
+def test_apply_str_with_args(df, args, kwargs):
+ # GH#46479
+ gb = df.groupby("A")
+ result = gb.apply("sum", *args, **kwargs)
+ expected = gb.sum(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("name", ["some_name", None])
+def test_result_name_when_one_group(name):
+ # GH 46369
+ ser = Series([1, 2], name=name)
+ result = ser.groupby(["a", "a"], group_keys=False).apply(lambda x: x)
+ expected = Series([1, 2], name=name)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "method, op",
+ [
+ ("apply", lambda gb: gb.values[-1]),
+ ("apply", lambda gb: gb["b"].iloc[0]),
+ ("agg", "skew"),
+ ("agg", "kurt"),
+ ("agg", "prod"),
+ ("agg", "sum"),
+ ],
+)
+def test_empty_df(method, op):
+ # GH 47985
+ empty_df = DataFrame({"a": [], "b": []})
+ gb = empty_df.groupby("a", group_keys=True)
+ group = gb.b
+
+ result = getattr(group, method)(op)
+ expected = Series(
+ [], name="b", dtype="float64", index=Index([], dtype="float64", name="a")
+ )
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_include_groups():
+ # GH#7155
+ df = DataFrame({"a": [1, 1, 2], "b": [3, 4, 5]})
+ gb = df.groupby("a")
+ with pytest.raises(ValueError, match="include_groups=True is no longer allowed"):
+ gb.apply(lambda x: x.sum(), include_groups=True)
+
+
+@pytest.mark.parametrize("func, value", [(max, 2), (min, 1), (sum, 3)])
+def test_builtins_apply(func, value):
+ # GH#8155, GH#53974
+ # Builtins act as e.g. sum(group), which sums the column labels of group
+ df = DataFrame({0: [1, 1, 2], 1: [3, 4, 5], 2: [3, 4, 5]})
+ gb = df.groupby(0)
+ result = gb.apply(func)
+
+ expected = Series([value, value], index=Index([1, 2], name=0))
+ tm.assert_series_equal(result, expected)
+
+
+def test_inconsistent_return_type():
+ # GH5592
+ # inconsistent return type
+ df = DataFrame(
+ {
+ "A": ["Tiger", "Tiger", "Tiger", "Lamb", "Lamb", "Pony", "Pony"],
+ "B": Series(np.arange(7), dtype="int64"),
+ "C": pd.date_range("20130101", periods=7),
+ }
+ )
+
+ def f_0(grp):
+ return grp.iloc[0]
+
+ expected = df.groupby("A").first()[["B"]]
+ result = df.groupby("A").apply(f_0)[["B"]]
+ tm.assert_frame_equal(result, expected)
+
+ def f_1(grp):
+ if grp.name == "Tiger":
+ return None
+ return grp.iloc[0]
+
+ result = df.groupby("A").apply(f_1)[["B"]]
+ e = expected.copy()
+ e.loc["Tiger"] = np.nan
+ tm.assert_frame_equal(result, e)
+
+ def f_2(grp):
+ if grp.name == "Pony":
+ return None
+ return grp.iloc[0]
+
+ result = df.groupby("A").apply(f_2)[["B"]]
+ e = expected.copy()
+ e.loc["Pony"] = np.nan
+ tm.assert_frame_equal(result, e)
+
+ # 5592 revisited, with datetimes
+ def f_3(grp):
+ if grp.name == "Pony":
+ return None
+ return grp.iloc[0]
+
+ result = df.groupby("A").apply(f_3)[["C"]]
+ e = df.groupby("A").first()[["C"]]
+ e.loc["Pony"] = pd.NaT
+ tm.assert_frame_equal(result, e)
+
+ # scalar outputs
+ def f_4(grp):
+ if grp.name == "Pony":
+ return None
+ return grp.iloc[0].loc["C"]
+
+ result = df.groupby("A").apply(f_4)
+ e = df.groupby("A").first()["C"].copy()
+ e.loc["Pony"] = np.nan
+ e.name = None
+ tm.assert_series_equal(result, e)
+
+
+def test_nonreducer_nonstransform():
+ # GH3380, GH60619
+ # Was originally testing mutating in a UDF; now kept as an example
+ # of using apply with a nonreducer and nontransformer.
+ df = DataFrame(
+ {
+ "cat1": ["a"] * 8 + ["b"] * 6,
+ "cat2": ["c"] * 2
+ + ["d"] * 2
+ + ["e"] * 2
+ + ["f"] * 2
+ + ["c"] * 2
+ + ["d"] * 2
+ + ["e"] * 2,
+ "val": np.random.default_rng(2).integers(100, size=14),
+ }
+ )
+
+ def f(x):
+ x = x.copy()
+ x["rank"] = x.val.rank(method="min")
+ return x.groupby("cat2")["rank"].min()
+
+ expected = DataFrame(
+ {
+ "cat1": list("aaaabbb"),
+ "cat2": list("cdefcde"),
+ "rank": [3.0, 2.0, 5.0, 1.0, 2.0, 4.0, 1.0],
+ }
+ ).set_index(["cat1", "cat2"])["rank"]
+ result = df.groupby("cat1").apply(f)
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_apply_store_copy():
+ # GH40673
+ rng = np.random.default_rng(seed=42)
+
+ df = DataFrame(
+ {
+ "A": rng.normal(10, 12, size=(4,)),
+ "B": [1, 2, 1, 2],
+ }
+ )
+
+ store = {}
+
+ def addstore(x):
+ store[len(store)] = x.copy()
+
+ df.groupby("B").apply(addstore)
+
+ expected_out_0 = df.iloc[[0, 2], [0]]
+ expected_out_1 = df.iloc[[1, 3], [0]]
+
+ tm.assert_frame_equal(store[0], expected_out_0)
+ tm.assert_frame_equal(store[1], expected_out_1)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_bin_groupby.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_bin_groupby.py
new file mode 100644
index 0000000000000000000000000000000000000000..07d52308e308ad637fad8df5802b8e79985b8127
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_bin_groupby.py
@@ -0,0 +1,67 @@
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+
+import pandas as pd
+import pandas._testing as tm
+
+
+def assert_block_lengths(x):
+ assert len(x) == len(x._mgr.blocks[0].mgr_locs)
+ return 0
+
+
+def cumsum_max(x):
+ x.cumsum().max()
+ return 0
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ cumsum_max,
+ assert_block_lengths,
+ ],
+)
+def test_mgr_locs_updated(func):
+ # https://github.com/pandas-dev/pandas/issues/31802
+ # Some operations may require creating new blocks, which requires
+ # valid mgr_locs
+ df = pd.DataFrame({"A": ["a", "a", "a"], "B": ["a", "b", "b"], "C": [1, 1, 1]})
+ result = df.groupby(["A", "B"]).agg(func)
+ expected = pd.DataFrame(
+ {"C": [0, 0]},
+ index=pd.MultiIndex.from_product([["a"], ["a", "b"]], names=["A", "B"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "binner,closed,expected",
+ [
+ (
+ [0, 3, 6, 9],
+ "left",
+ [2, 5, 6],
+ ),
+ (
+ [0, 3, 6, 9],
+ "right",
+ [3, 6, 6],
+ ),
+ ([0, 3, 6], "left", [2, 5]),
+ (
+ [0, 3, 6],
+ "right",
+ [3, 6],
+ ),
+ ],
+)
+def test_generate_bins(binner, closed, expected):
+ values = np.array([1, 2, 3, 4, 5, 6], dtype=np.int64)
+ result = lib.generate_bins_dt64(
+ values, np.array(binner, dtype=np.int64), closed=closed
+ )
+ expected = np.array(expected, dtype=np.int64)
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_categorical.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_categorical.py
new file mode 100644
index 0000000000000000000000000000000000000000..e39052e64e3072ef31edb29bb14648c427e8247c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_categorical.py
@@ -0,0 +1,2189 @@
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ CategoricalIndex,
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ qcut,
+)
+import pandas._testing as tm
+from pandas.api.typing import SeriesGroupBy
+from pandas.tests.groupby import get_groupby_method_args
+
+
+def cartesian_product_for_groupers(result, args, names, fill_value=np.nan):
+ """Reindex to a cartesian production for the groupers,
+ preserving the nature (Categorical) of each grouper
+ """
+
+ def f(a):
+ if isinstance(a, (CategoricalIndex, Categorical)):
+ categories = a.categories
+ a = Categorical.from_codes(
+ np.arange(len(categories)), categories=categories, ordered=a.ordered
+ )
+ return a
+
+ index = MultiIndex.from_product(map(f, args), names=names)
+ if isinstance(fill_value, dict):
+ # fill_value is a dict mapping column names to fill values
+ # -> reindex column by column (reindex itself does not support this)
+ res = {}
+ for col in result.columns:
+ res[col] = result[col].reindex(index, fill_value=fill_value[col])
+ return DataFrame(res, index=index).sort_index()
+
+ return result.reindex(index, fill_value=fill_value).sort_index()
+
+
+_results_for_groupbys_with_missing_categories = {
+ # This maps the builtin groupby functions to their expected outputs for
+ # missing categories when they are called on a categorical grouper with
+ # observed=False. Some functions are expected to return NaN, some zero.
+ # These expected values can be used across several tests (i.e. they are
+ # the same for SeriesGroupBy and DataFrameGroupBy) but they should only be
+ # hardcoded in one place.
+ "all": True,
+ "any": False,
+ "count": 0,
+ "corrwith": np.nan,
+ "first": np.nan,
+ "idxmax": np.nan,
+ "idxmin": np.nan,
+ "last": np.nan,
+ "max": np.nan,
+ "mean": np.nan,
+ "median": np.nan,
+ "min": np.nan,
+ "nth": np.nan,
+ "nunique": 0,
+ "prod": 1,
+ "quantile": np.nan,
+ "sem": np.nan,
+ "size": 0,
+ "skew": np.nan,
+ "kurt": np.nan,
+ "std": np.nan,
+ "sum": 0,
+ "var": np.nan,
+}
+
+
+def test_apply_use_categorical_name(df):
+ cats = qcut(df.C, 4)
+
+ def get_stats(group):
+ return {
+ "min": group.min(),
+ "max": group.max(),
+ "count": group.count(),
+ "mean": group.mean(),
+ }
+
+ result = df.groupby(cats, observed=False).D.apply(get_stats)
+ assert result.index.names[0] == "C"
+
+
+def test_basic():
+ cats = Categorical(
+ ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
+ categories=["a", "b", "c", "d"],
+ ordered=True,
+ )
+ data = DataFrame({"a": [1, 1, 1, 2, 2, 2, 3, 4, 5], "b": cats})
+
+ exp_index = CategoricalIndex(list("abcd"), name="b", ordered=True)
+ expected = DataFrame({"a": [1, 2, 4, np.nan]}, index=exp_index)
+ result = data.groupby("b", observed=False).mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_basic_single_grouper():
+ cat1 = Categorical(["a", "a", "b", "b"], categories=["a", "b", "z"], ordered=True)
+ cat2 = Categorical(["c", "d", "c", "d"], categories=["c", "d", "y"], ordered=True)
+ df = DataFrame({"A": cat1, "B": cat2, "values": [1, 2, 3, 4]})
+
+ gb = df.groupby("A", observed=False)
+ exp_idx = CategoricalIndex(["a", "b", "z"], name="A", ordered=True)
+ expected = DataFrame({"values": Series([3, 7, 0], index=exp_idx)})
+ result = gb.sum(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_basic_string(using_infer_string):
+ # GH 8623
+ x = DataFrame(
+ [[1, "John P. Doe"], [2, "Jane Dove"], [1, "John P. Doe"]],
+ columns=["person_id", "person_name"],
+ )
+ x["person_name"] = Categorical(x.person_name)
+
+ g = x.groupby(["person_id"], observed=False)
+ result = g.transform(lambda x: x)
+ tm.assert_frame_equal(result, x[["person_name"]])
+
+ result = x.drop_duplicates("person_name")
+ expected = x.iloc[[0, 1]]
+ tm.assert_frame_equal(result, expected)
+
+ def f(x):
+ return x.drop_duplicates("person_name").iloc[0]
+
+ result = g.apply(f)
+ expected = x[["person_name"]].iloc[[0, 1]]
+ expected.index = Index([1, 2], name="person_id")
+ dtype = "str" if using_infer_string else object
+ expected["person_name"] = expected["person_name"].astype(dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_basic_monotonic():
+ # GH 9921
+ df = DataFrame({"a": [5, 15, 25]})
+ c = pd.cut(df.a, bins=[0, 10, 20, 30, 40])
+
+ result = df.a.groupby(c, observed=False).transform(sum)
+ tm.assert_series_equal(result, df["a"])
+
+ tm.assert_series_equal(
+ df.a.groupby(c, observed=False).transform(lambda xs: np.sum(xs)), df["a"]
+ )
+ result = df.groupby(c, observed=False).transform(sum)
+ expected = df[["a"]]
+ tm.assert_frame_equal(result, expected)
+
+ gbc = df.groupby(c, observed=False)
+ result = gbc.transform(lambda xs: np.max(xs, axis=0))
+ tm.assert_frame_equal(result, df[["a"]])
+
+ result2 = gbc.transform(lambda xs: np.max(xs, axis=0))
+ result3 = gbc.transform(max)
+ result4 = gbc.transform(np.maximum.reduce)
+ result5 = gbc.transform(lambda xs: np.maximum.reduce(xs))
+ tm.assert_frame_equal(result2, df[["a"]], check_dtype=False)
+ tm.assert_frame_equal(result3, df[["a"]], check_dtype=False)
+ tm.assert_frame_equal(result4, df[["a"]])
+ tm.assert_frame_equal(result5, df[["a"]])
+
+ # Filter
+ tm.assert_series_equal(df.a.groupby(c, observed=False).filter(np.all), df["a"])
+ tm.assert_frame_equal(df.groupby(c, observed=False).filter(np.all), df)
+
+
+def test_basic_non_monotonic():
+ df = DataFrame({"a": [5, 15, 25, -5]})
+ c = pd.cut(df.a, bins=[-10, 0, 10, 20, 30, 40])
+
+ result = df.a.groupby(c, observed=False).transform(sum)
+ tm.assert_series_equal(result, df["a"])
+
+ tm.assert_series_equal(
+ df.a.groupby(c, observed=False).transform(lambda xs: np.sum(xs)), df["a"]
+ )
+ result = df.groupby(c, observed=False).transform(sum)
+ expected = df[["a"]]
+ tm.assert_frame_equal(result, expected)
+
+ tm.assert_frame_equal(
+ df.groupby(c, observed=False).transform(lambda xs: np.sum(xs)), df[["a"]]
+ )
+
+
+def test_basic_cut_grouping():
+ # GH 9603
+ df = DataFrame({"a": [1, 0, 0, 0]})
+ c = pd.cut(df.a, [0, 1, 2, 3, 4], labels=Categorical(list("abcd")))
+ result = df.groupby(c, observed=False).apply(len)
+
+ exp_index = CategoricalIndex(c.values.categories, ordered=c.values.ordered)
+ expected = Series([1, 0, 0, 0], index=exp_index)
+ expected.index.name = "a"
+ tm.assert_series_equal(result, expected)
+
+
+def test_more_basic():
+ levels = ["foo", "bar", "baz", "qux"]
+ codes = np.random.default_rng(2).integers(0, 4, size=10)
+
+ cats = Categorical.from_codes(codes, levels, ordered=True)
+
+ data = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+
+ result = data.groupby(cats, observed=False).mean()
+
+ expected = data.groupby(np.asarray(cats), observed=False).mean()
+ exp_idx = CategoricalIndex(levels, categories=cats.categories, ordered=True)
+ expected = expected.reindex(exp_idx)
+
+ tm.assert_frame_equal(result, expected)
+
+ grouped = data.groupby(cats, observed=False)
+ desc_result = grouped.describe()
+
+ idx = cats.codes.argsort()
+ ord_labels = np.asarray(cats).take(idx)
+ ord_data = data.take(idx)
+
+ exp_cats = Categorical(
+ ord_labels, ordered=True, categories=["foo", "bar", "baz", "qux"]
+ )
+ expected = ord_data.groupby(exp_cats, sort=False, observed=False).describe()
+ tm.assert_frame_equal(desc_result, expected)
+
+ # GH 10460
+ expc = Categorical.from_codes(np.arange(4).repeat(8), levels, ordered=True)
+ exp = CategoricalIndex(expc)
+ tm.assert_index_equal(desc_result.stack().index.get_level_values(0), exp)
+ exp = Index(["count", "mean", "std", "min", "25%", "50%", "75%", "max"] * 4)
+ tm.assert_index_equal(desc_result.stack().index.get_level_values(1), exp)
+
+
+def test_level_get_group(observed):
+ # GH15155
+ df = DataFrame(
+ data=np.arange(2, 22, 2),
+ index=MultiIndex(
+ levels=[CategoricalIndex(["a", "b"]), range(10)],
+ codes=[[0] * 5 + [1] * 5, range(10)],
+ names=["Index1", "Index2"],
+ ),
+ )
+ g = df.groupby(level=["Index1"], observed=observed)
+
+ # expected should equal test.loc[["a"]]
+ # GH15166
+ expected = DataFrame(
+ data=np.arange(2, 12, 2),
+ index=MultiIndex(
+ levels=[CategoricalIndex(["a", "b"]), range(5)],
+ codes=[[0] * 5, range(5)],
+ names=["Index1", "Index2"],
+ ),
+ )
+ result = g.get_group(("a",))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_sorting_with_different_categoricals():
+ # GH 24271
+ df = DataFrame(
+ {
+ "group": ["A"] * 6 + ["B"] * 6,
+ "dose": ["high", "med", "low"] * 4,
+ "outcomes": np.arange(12.0),
+ }
+ )
+
+ df.dose = Categorical(df.dose, categories=["low", "med", "high"], ordered=True)
+
+ result = df.groupby("group")["dose"].value_counts()
+ result = result.sort_index(level=0, sort_remaining=True)
+ index = ["low", "med", "high", "low", "med", "high"]
+ index = Categorical(index, categories=["low", "med", "high"], ordered=True)
+ index = [["A", "A", "A", "B", "B", "B"], CategoricalIndex(index)]
+ index = MultiIndex.from_arrays(index, names=["group", "dose"])
+ expected = Series([2] * 6, index=index, name="count")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("ordered", [True, False])
+def test_apply(ordered):
+ # GH 10138
+
+ dense = Categorical(list("abc"), ordered=ordered)
+
+ # 'b' is in the categories but not in the list
+ missing = Categorical(list("aaa"), categories=["a", "b"], ordered=ordered)
+ values = np.arange(len(dense))
+ df = DataFrame({"missing": missing, "dense": dense, "values": values})
+ grouped = df.groupby(["missing", "dense"], observed=True)
+
+ # missing category 'b' should still exist in the output index
+ idx = MultiIndex.from_arrays([missing, dense], names=["missing", "dense"])
+ expected = DataFrame([0, 1, 2.0], index=idx, columns=["values"])
+
+ result = grouped.apply(lambda x: np.mean(x, axis=0))
+ tm.assert_frame_equal(result, expected)
+
+ result = grouped.mean()
+ tm.assert_frame_equal(result, expected)
+
+ result = grouped.agg(np.mean)
+ tm.assert_frame_equal(result, expected)
+
+ # but for transform we should still get back the original index
+ idx = MultiIndex.from_arrays([missing, dense], names=["missing", "dense"])
+ expected = Series(1, index=idx)
+ result = grouped.apply(lambda x: 1)
+ tm.assert_series_equal(result, expected)
+
+
+def test_observed(observed, using_infer_string):
+ # multiple groupers, don't re-expand the output space
+ # of the grouper
+ # gh-14942 (implement)
+ # gh-10132 (back-compat)
+ # gh-8138 (back-compat)
+ # gh-8869
+
+ cat1 = Categorical(["a", "a", "b", "b"], categories=["a", "b", "z"], ordered=True)
+ cat2 = Categorical(["c", "d", "c", "d"], categories=["c", "d", "y"], ordered=True)
+ df = DataFrame({"A": cat1, "B": cat2, "values": [1, 2, 3, 4]})
+ df["C"] = ["foo", "bar"] * 2
+
+ # multiple groupers with a non-cat
+ gb = df.groupby(["A", "B", "C"], observed=observed)
+ exp_index = MultiIndex.from_arrays(
+ [cat1, cat2, ["foo", "bar"] * 2], names=["A", "B", "C"]
+ )
+ expected = DataFrame({"values": Series([1, 2, 3, 4], index=exp_index)}).sort_index()
+ result = gb.sum()
+ if not observed:
+ expected = cartesian_product_for_groupers(
+ expected, [cat1, cat2, ["foo", "bar"]], list("ABC"), fill_value=0
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ gb = df.groupby(["A", "B"], observed=observed)
+ exp_index = MultiIndex.from_arrays([cat1, cat2], names=["A", "B"])
+ expected = DataFrame(
+ {"values": [1, 2, 3, 4], "C": ["foo", "bar", "foo", "bar"]}, index=exp_index
+ )
+ result = gb.sum()
+ if not observed:
+ expected = cartesian_product_for_groupers(
+ expected,
+ [cat1, cat2],
+ list("AB"),
+ fill_value={"values": 0, "C": ""} if using_infer_string else 0,
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_observed_single_column(observed):
+ # https://github.com/pandas-dev/pandas/issues/8138
+ d = {
+ "cat": Categorical(
+ ["a", "b", "a", "b"], categories=["a", "b", "c"], ordered=True
+ ),
+ "ints": [1, 1, 2, 2],
+ "val": [10, 20, 30, 40],
+ }
+ df = DataFrame(d)
+
+ groups_single_key = df.groupby("cat", observed=observed)
+ result = groups_single_key.mean()
+
+ exp_index = CategoricalIndex(
+ list("ab"), name="cat", categories=list("abc"), ordered=True
+ )
+ expected = DataFrame({"ints": [1.5, 1.5], "val": [20.0, 30]}, index=exp_index)
+ if not observed:
+ index = CategoricalIndex(
+ list("abc"), name="cat", categories=list("abc"), ordered=True
+ )
+ expected = expected.reindex(index)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_observed_two_columns(observed):
+ # https://github.com/pandas-dev/pandas/issues/8138
+ d = {
+ "cat": Categorical(
+ ["a", "b", "a", "b"], categories=["a", "b", "c"], ordered=True
+ ),
+ "ints": [1, 1, 2, 2],
+ "val": [10, 20, 30, 40],
+ }
+ df = DataFrame(d)
+ groups_double_key = df.groupby(["cat", "ints"], observed=observed)
+ result = groups_double_key.agg("mean")
+ expected = DataFrame(
+ {
+ "val": [10.0, 30.0, 20.0, 40.0],
+ "cat": Categorical(
+ ["a", "a", "b", "b"], categories=["a", "b", "c"], ordered=True
+ ),
+ "ints": [1, 2, 1, 2],
+ }
+ ).set_index(["cat", "ints"])
+ if not observed:
+ expected = cartesian_product_for_groupers(
+ expected, [df.cat.values, [1, 2]], ["cat", "ints"]
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ # GH 10132
+ for key in [("a", 1), ("b", 2), ("b", 1), ("a", 2)]:
+ c, i = key
+ result = groups_double_key.get_group(key)
+ expected = df[(df.cat == c) & (df.ints == i)]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_observed_with_as_index(observed):
+ # gh-8869
+ # with as_index
+ d = {
+ "foo": [10, 8, 4, 8, 4, 1, 1],
+ "bar": [10, 20, 30, 40, 50, 60, 70],
+ "baz": ["d", "c", "e", "a", "a", "d", "c"],
+ }
+ df = DataFrame(d)
+ cat = pd.cut(df["foo"], np.linspace(0, 10, 3))
+ df["range"] = cat
+ groups = df.groupby(["range", "baz"], as_index=False, observed=observed)
+ result = groups.agg("mean")
+
+ groups2 = df.groupby(["range", "baz"], as_index=True, observed=observed)
+ expected = groups2.agg("mean").reset_index()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_observed_codes_remap(observed):
+ d = {"C1": [3, 3, 4, 5], "C2": [1, 2, 3, 4], "C3": [10, 100, 200, 34]}
+ df = DataFrame(d)
+ values = pd.cut(df["C1"], [1, 2, 3, 6])
+ values.name = "cat"
+ groups_double_key = df.groupby([values, "C2"], observed=observed)
+
+ idx = MultiIndex.from_arrays([values, [1, 2, 3, 4]], names=["cat", "C2"])
+ expected = DataFrame(
+ {"C1": [3.0, 3.0, 4.0, 5.0], "C3": [10.0, 100.0, 200.0, 34.0]}, index=idx
+ )
+ if not observed:
+ expected = cartesian_product_for_groupers(
+ expected, [values.values, [1, 2, 3, 4]], ["cat", "C2"]
+ )
+
+ result = groups_double_key.agg("mean")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_observed_perf():
+ # we create a cartesian product, so this is
+ # non-performant if we don't use observed values
+ # gh-14942
+ df = DataFrame(
+ {
+ "cat": np.random.default_rng(2).integers(0, 255, size=30000),
+ "int_id": np.random.default_rng(2).integers(0, 255, size=30000),
+ "other_id": np.random.default_rng(2).integers(0, 10000, size=30000),
+ "foo": 0,
+ }
+ )
+ df["cat"] = df.cat.astype(str).astype("category")
+
+ grouped = df.groupby(["cat", "int_id", "other_id"], observed=True)
+ result = grouped.count()
+ assert result.index.levels[0].nunique() == df.cat.nunique()
+ assert result.index.levels[1].nunique() == df.int_id.nunique()
+ assert result.index.levels[2].nunique() == df.other_id.nunique()
+
+
+def test_observed_groups(observed):
+ # gh-20583
+ # test that we have the appropriate groups
+
+ cat = Categorical(["a", "c", "a"], categories=["a", "b", "c"])
+ df = DataFrame({"cat": cat, "vals": [1, 2, 3]})
+ g = df.groupby("cat", observed=observed)
+
+ result = g.groups
+ if observed:
+ expected = {"a": Index([0, 2], dtype="int64"), "c": Index([1], dtype="int64")}
+ else:
+ expected = {
+ "a": Index([0, 2], dtype="int64"),
+ "b": Index([], dtype="int64"),
+ "c": Index([1], dtype="int64"),
+ }
+
+ tm.assert_dict_equal(result, expected)
+
+
+def test_groups_na_category(dropna, observed):
+ # https://github.com/pandas-dev/pandas/issues/61356
+ df = DataFrame(
+ {"cat": Categorical(["a", np.nan, "a"], categories=list("adb"))},
+ index=list("xyz"),
+ )
+ g = df.groupby("cat", observed=observed, dropna=dropna)
+
+ result = g.groups
+ expected = {"a": Index(["x", "z"])}
+ if not dropna:
+ expected |= {np.nan: Index(["y"])}
+ if not observed:
+ expected |= {"b": Index([]), "d": Index([])}
+ tm.assert_dict_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "keys, expected_values, expected_index_levels",
+ [
+ ("a", [15, 9, 0], CategoricalIndex([1, 2, 3], name="a")),
+ (
+ ["a", "b"],
+ [7, 8, 0, 0, 0, 9, 0, 0, 0],
+ [CategoricalIndex([1, 2, 3], name="a"), Index([4, 5, 6])],
+ ),
+ (
+ ["a", "a2"],
+ [15, 0, 0, 0, 9, 0, 0, 0, 0],
+ [
+ CategoricalIndex([1, 2, 3], name="a"),
+ CategoricalIndex([1, 2, 3], name="a"),
+ ],
+ ),
+ ],
+)
+@pytest.mark.parametrize("test_series", [True, False])
+def test_unobserved_in_index(keys, expected_values, expected_index_levels, test_series):
+ # GH#49354 - ensure unobserved cats occur when grouping by index levels
+ df = DataFrame(
+ {
+ "a": Categorical([1, 1, 2], categories=[1, 2, 3]),
+ "a2": Categorical([1, 1, 2], categories=[1, 2, 3]),
+ "b": [4, 5, 6],
+ "c": [7, 8, 9],
+ }
+ ).set_index(["a", "a2"])
+ if "b" not in keys:
+ # Only keep b when it is used for grouping for consistent columns in the result
+ df = df.drop(columns="b")
+
+ gb = df.groupby(keys, observed=False)
+ if test_series:
+ gb = gb["c"]
+ result = gb.sum()
+
+ if len(keys) == 1:
+ index = expected_index_levels
+ else:
+ codes = [[0, 0, 0, 1, 1, 1, 2, 2, 2], 3 * [0, 1, 2]]
+ index = MultiIndex(
+ expected_index_levels,
+ codes=codes,
+ names=keys,
+ )
+ expected = DataFrame({"c": expected_values}, index=index)
+ if test_series:
+ expected = expected["c"]
+ tm.assert_equal(result, expected)
+
+
+def test_observed_groups_with_nan(observed):
+ # GH 24740
+ df = DataFrame(
+ {
+ "cat": Categorical(["a", np.nan, "a"], categories=["a", "b", "d"]),
+ "vals": [1, 2, 3],
+ }
+ )
+ g = df.groupby("cat", observed=observed)
+ result = g.groups
+ if observed:
+ expected = {"a": Index([0, 2], dtype="int64")}
+ else:
+ expected = {
+ "a": Index([0, 2], dtype="int64"),
+ "b": Index([], dtype="int64"),
+ "d": Index([], dtype="int64"),
+ }
+ tm.assert_dict_equal(result, expected)
+
+
+def test_observed_nth():
+ # GH 26385
+ cat = Categorical(["a", np.nan, np.nan], categories=["a", "b", "c"])
+ ser = Series([1, 2, 3])
+ df = DataFrame({"cat": cat, "ser": ser})
+
+ result = df.groupby("cat", observed=False)["ser"].nth(0)
+ expected = df["ser"].iloc[[0]]
+ tm.assert_series_equal(result, expected)
+
+
+def test_dataframe_categorical_with_nan(observed):
+ # GH 21151
+ s1 = Categorical([np.nan, "a", np.nan, "a"], categories=["a", "b", "c"])
+ s2 = Series([1, 2, 3, 4])
+ df = DataFrame({"s1": s1, "s2": s2})
+ result = df.groupby("s1", observed=observed).first().reset_index()
+ if observed:
+ expected = DataFrame(
+ {"s1": Categorical(["a"], categories=["a", "b", "c"]), "s2": [2]}
+ )
+ else:
+ expected = DataFrame(
+ {
+ "s1": Categorical(["a", "b", "c"], categories=["a", "b", "c"]),
+ "s2": [2, np.nan, np.nan],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("ordered", [True, False])
+def test_dataframe_categorical_ordered_observed_sort(ordered, observed, sort):
+ # GH 25871: Fix groupby sorting on ordered Categoricals
+ # GH 25167: Groupby with observed=True doesn't sort
+
+ # Build a dataframe with cat having one unobserved category ('missing'),
+ # and a Series with identical values
+ label = Categorical(
+ ["d", "a", "b", "a", "d", "b"],
+ categories=["a", "b", "missing", "d"],
+ ordered=ordered,
+ )
+ val = Series(["d", "a", "b", "a", "d", "b"])
+ df = DataFrame({"label": label, "val": val})
+
+ # aggregate on the Categorical
+ result = df.groupby("label", observed=observed, sort=sort)["val"].aggregate("first")
+
+ # If ordering works, we expect index labels equal to aggregation results,
+ # except for 'observed=False': label 'missing' has aggregation None
+ label = Series(result.index.array, dtype="object")
+ aggr = Series(result.array)
+ if not observed:
+ aggr[aggr.isna()] = "missing"
+ if not all(label == aggr):
+ msg = (
+ "Labels and aggregation results not consistently sorted\n"
+ f"for (ordered={ordered}, observed={observed}, sort={sort})\n"
+ f"Result:\n{result}"
+ )
+ pytest.fail(msg)
+
+
+def test_datetime():
+ # GH9049: ensure backward compatibility
+ levels = pd.date_range("2014-01-01", periods=4)
+ codes = np.random.default_rng(2).integers(0, 4, size=10)
+
+ cats = Categorical.from_codes(codes, levels, ordered=True)
+
+ data = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ result = data.groupby(cats, observed=False).mean()
+
+ expected = data.groupby(np.asarray(cats), observed=False).mean()
+ expected = expected.reindex(levels)
+ expected.index = CategoricalIndex(
+ expected.index, categories=expected.index, ordered=True
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ grouped = data.groupby(cats, observed=False)
+ desc_result = grouped.describe()
+
+ idx = cats.codes.argsort()
+ ord_labels = cats.take(idx)
+ ord_data = data.take(idx)
+ expected = ord_data.groupby(ord_labels, observed=False).describe()
+ tm.assert_frame_equal(desc_result, expected)
+ tm.assert_index_equal(desc_result.index, expected.index)
+ tm.assert_index_equal(
+ desc_result.index.get_level_values(0), expected.index.get_level_values(0)
+ )
+
+ # GH 10460
+ expc = Categorical.from_codes(np.arange(4).repeat(8), levels, ordered=True)
+ exp = CategoricalIndex(expc)
+ tm.assert_index_equal((desc_result.stack().index.get_level_values(0)), exp)
+ exp = Index(["count", "mean", "std", "min", "25%", "50%", "75%", "max"] * 4)
+ tm.assert_index_equal((desc_result.stack().index.get_level_values(1)), exp)
+
+
+def test_categorical_index():
+ s = np.random.default_rng(2)
+ levels = ["foo", "bar", "baz", "qux"]
+ codes = s.integers(0, 4, size=20)
+ cats = Categorical.from_codes(codes, levels, ordered=True)
+ df = DataFrame(np.repeat(np.arange(20), 4).reshape(-1, 4), columns=list("abcd"))
+ df["cats"] = cats
+
+ # with a cat index
+ result = df.set_index("cats").groupby(level=0, observed=False).sum()
+ expected = df[list("abcd")].groupby(cats.codes, observed=False).sum()
+ expected.index = CategoricalIndex(
+ Categorical.from_codes([0, 1, 2, 3], levels, ordered=True), name="cats"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # with a cat column, should produce a cat index
+ result = df.groupby("cats", observed=False).sum()
+ expected = df[list("abcd")].groupby(cats.codes, observed=False).sum()
+ expected.index = CategoricalIndex(
+ Categorical.from_codes([0, 1, 2, 3], levels, ordered=True), name="cats"
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_describe_categorical_columns():
+ # GH 11558
+ cats = CategoricalIndex(
+ ["qux", "foo", "baz", "bar"],
+ categories=["foo", "bar", "baz", "qux"],
+ ordered=True,
+ )
+ df = DataFrame(np.random.default_rng(2).standard_normal((20, 4)), columns=cats)
+ result = df.groupby([1, 2, 3, 4] * 5).describe()
+
+ tm.assert_index_equal(result.stack().columns, cats)
+ tm.assert_categorical_equal(result.stack().columns.values, cats.values)
+
+
+def test_unstack_categorical():
+ # GH11558 (example is taken from the original issue)
+ df = DataFrame(
+ {"a": range(10), "medium": ["A", "B"] * 5, "artist": list("XYXXY") * 2}
+ )
+ df["medium"] = df["medium"].astype("category")
+
+ gcat = df.groupby(["artist", "medium"], observed=False)["a"].count().unstack()
+ result = gcat.describe()
+
+ exp_columns = CategoricalIndex(["A", "B"], ordered=False, name="medium")
+ tm.assert_index_equal(result.columns, exp_columns)
+ tm.assert_categorical_equal(result.columns.values, exp_columns.values)
+
+ result = gcat["A"] + gcat["B"]
+ expected = Series([6, 4], index=Index(["X", "Y"], name="artist"))
+ tm.assert_series_equal(result, expected)
+
+
+def test_bins_unequal_len():
+ # GH3011
+ series = Series([np.nan, np.nan, 1, 1, 2, 2, 3, 3, 4, 4])
+ bins = pd.cut(series.dropna().values, 4)
+
+ # len(bins) != len(series) here
+ with pytest.raises(ValueError, match="Grouper and axis must be same length"):
+ series.groupby(bins).mean()
+
+
+@pytest.mark.parametrize(
+ ["series", "data"],
+ [
+ # Group a series with length and index equal to those of the grouper.
+ (Series(range(4)), {"A": [0, 3], "B": [1, 2]}),
+ # Group a series with length equal to that of the grouper and index unequal to
+ # that of the grouper.
+ (Series(range(4)).rename(lambda idx: idx + 1), {"A": [2], "B": [0, 1]}),
+ # GH44179: Group a series with length unequal to that of the grouper.
+ (Series(range(7)), {"A": [0, 3], "B": [1, 2]}),
+ ],
+)
+def test_categorical_series(series, data):
+ # Group the given series by a series with categorical data type such that group A
+ # takes indices 0 and 3 and group B indices 1 and 2, obtaining the values mapped in
+ # the given data.
+ groupby = series.groupby(Series(list("ABBA"), dtype="category"), observed=False)
+ result = groupby.aggregate(list)
+ expected = Series(data, index=CategoricalIndex(data.keys()))
+ tm.assert_series_equal(result, expected)
+
+
+def test_as_index():
+ # GH13204
+ df = DataFrame(
+ {
+ "cat": Categorical([1, 2, 2], [1, 2, 3]),
+ "A": [10, 11, 11],
+ "B": [101, 102, 103],
+ }
+ )
+ result = df.groupby(["cat", "A"], as_index=False, observed=True).sum()
+ expected = DataFrame(
+ {
+ "cat": Categorical([1, 2], categories=df.cat.cat.categories),
+ "A": [10, 11],
+ "B": [101, 205],
+ },
+ columns=["cat", "A", "B"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # function grouper
+ f = lambda r: df.loc[r, "A"]
+ result = df.groupby(["cat", f], as_index=False, observed=True).sum()
+ expected = DataFrame(
+ {
+ "cat": Categorical([1, 2], categories=df.cat.cat.categories),
+ "level_1": [10, 11],
+ "A": [10, 22],
+ "B": [101, 205],
+ },
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # another not in-axis grouper (conflicting names in index)
+ s = Series(["a", "b", "b"], name="cat")
+ result = df.groupby(["cat", s], as_index=False, observed=True).sum()
+ expected = DataFrame(
+ {
+ "cat": ["a", "b"],
+ "A": [10, 22],
+ "B": [101, 205],
+ },
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # is original index dropped?
+ group_columns = ["cat", "A"]
+ expected = DataFrame(
+ {
+ "cat": Categorical([1, 2], categories=df.cat.cat.categories),
+ "A": [10, 11],
+ "B": [101, 205],
+ },
+ columns=["cat", "A", "B"],
+ )
+
+ for name in [None, "X", "B"]:
+ df.index = Index(list("abc"), name=name)
+ result = df.groupby(group_columns, as_index=False, observed=True).sum()
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_preserve_categories():
+ # GH-13179
+ categories = list("abc")
+
+ # ordered=True
+ df = DataFrame({"A": Categorical(list("ba"), categories=categories, ordered=True)})
+ sort_index = CategoricalIndex(categories, categories, ordered=True, name="A")
+ nosort_index = CategoricalIndex(list("bac"), categories, ordered=True, name="A")
+ tm.assert_index_equal(
+ df.groupby("A", sort=True, observed=False).first().index, sort_index
+ )
+ # GH#42482 - don't sort result when sort=False, even when ordered=True
+ tm.assert_index_equal(
+ df.groupby("A", sort=False, observed=False).first().index, nosort_index
+ )
+
+
+def test_preserve_categories_ordered_false():
+ # GH-13179
+ categories = list("abc")
+ df = DataFrame({"A": Categorical(list("ba"), categories=categories, ordered=False)})
+ sort_index = CategoricalIndex(categories, categories, ordered=False, name="A")
+ # GH#48749 - don't change order of categories
+ # GH#42482 - don't sort result when sort=False, even when ordered=True
+ nosort_index = CategoricalIndex(list("bac"), list("abc"), ordered=False, name="A")
+ tm.assert_index_equal(
+ df.groupby("A", sort=True, observed=False).first().index, sort_index
+ )
+ tm.assert_index_equal(
+ df.groupby("A", sort=False, observed=False).first().index, nosort_index
+ )
+
+
+@pytest.mark.parametrize("col", ["C1", "C2"])
+def test_preserve_categorical_dtype(col):
+ # GH13743, GH13854
+ df = DataFrame(
+ {
+ "A": [1, 2, 1, 1, 2],
+ "B": [10, 16, 22, 28, 34],
+ "C1": Categorical(list("abaab"), categories=list("bac"), ordered=False),
+ "C2": Categorical(list("abaab"), categories=list("bac"), ordered=True),
+ }
+ )
+ # single grouper
+ exp_full = DataFrame(
+ {
+ "A": [2.0, 1.0, np.nan],
+ "B": [25.0, 20.0, np.nan],
+ "C1": Categorical(list("bac"), categories=list("bac"), ordered=False),
+ "C2": Categorical(list("bac"), categories=list("bac"), ordered=True),
+ }
+ )
+ result1 = df.groupby(by=col, as_index=False, observed=False).mean(numeric_only=True)
+ result2 = (
+ df.groupby(by=col, as_index=True, observed=False)
+ .mean(numeric_only=True)
+ .reset_index()
+ )
+ expected = exp_full.reindex(columns=result1.columns)
+ tm.assert_frame_equal(result1, expected)
+ tm.assert_frame_equal(result2, expected)
+
+
+@pytest.mark.parametrize(
+ "func, values",
+ [
+ ("first", ["second", "first"]),
+ ("last", ["fourth", "third"]),
+ ("min", ["fourth", "first"]),
+ ("max", ["second", "third"]),
+ ],
+)
+def test_preserve_on_ordered_ops(func, values):
+ # gh-18502
+ # preserve the categoricals on ops
+ c = Categorical(["first", "second", "third", "fourth"], ordered=True)
+ df = DataFrame({"payload": [-1, -2, -1, -2], "col": c})
+ g = df.groupby("payload")
+ result = getattr(g, func)()
+ expected = DataFrame(
+ {"payload": [-2, -1], "col": Series(values, dtype=c.dtype)}
+ ).set_index("payload")
+ tm.assert_frame_equal(result, expected)
+
+ # we should also preserve categorical for SeriesGroupBy
+ sgb = df.groupby("payload")["col"]
+ result = getattr(sgb, func)()
+ expected = expected["col"]
+ tm.assert_series_equal(result, expected)
+
+
+def test_categorical_no_compress():
+ data = Series(np.random.default_rng(2).standard_normal(9))
+
+ codes = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2])
+ cats = Categorical.from_codes(codes, [0, 1, 2], ordered=True)
+
+ result = data.groupby(cats, observed=False).mean()
+ exp = data.groupby(codes, observed=False).mean()
+
+ exp.index = CategoricalIndex(
+ exp.index, categories=cats.categories, ordered=cats.ordered
+ )
+ tm.assert_series_equal(result, exp)
+
+ codes = np.array([0, 0, 0, 1, 1, 1, 3, 3, 3])
+ cats = Categorical.from_codes(codes, [0, 1, 2, 3], ordered=True)
+
+ result = data.groupby(cats, observed=False).mean()
+ exp = data.groupby(codes, observed=False).mean().reindex(cats.categories)
+ exp.index = CategoricalIndex(
+ exp.index, categories=cats.categories, ordered=cats.ordered
+ )
+ tm.assert_series_equal(result, exp)
+
+
+def test_categorical_no_compress_string():
+ cats = Categorical(
+ ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
+ categories=["a", "b", "c", "d"],
+ ordered=True,
+ )
+ data = DataFrame({"a": [1, 1, 1, 2, 2, 2, 3, 4, 5], "b": cats})
+
+ result = data.groupby("b", observed=False).mean()
+ result = result["a"].values
+ exp = np.array([1, 2, 4, np.nan])
+ tm.assert_numpy_array_equal(result, exp)
+
+
+def test_groupby_empty_with_category():
+ # GH-9614
+ # test fix for when group by on None resulted in
+ # coercion of dtype categorical -> float
+ df = DataFrame({"A": [None] * 3, "B": Categorical(["train", "train", "test"])})
+ result = df.groupby("A").first()["B"]
+ expected = Series(
+ Categorical([], categories=["test", "train"]),
+ index=Series([], dtype="object", name="A"),
+ name="B",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_sort():
+ # https://stackoverflow.com/questions/23814368/sorting-pandas-
+ # categorical-labels-after-groupby
+ # This should result in a properly sorted Series so that the plot
+ # has a sorted x axis
+ # self.cat.groupby(['value_group'])['value_group'].count().plot(kind='bar')
+
+ df = DataFrame({"value": np.random.default_rng(2).integers(0, 10000, 10)})
+ labels = [f"{i} - {i + 499}" for i in range(0, 10000, 500)]
+ cat_labels = Categorical(labels, labels)
+
+ df = df.sort_values(by=["value"], ascending=True)
+ df["value_group"] = pd.cut(
+ df.value, range(0, 10500, 500), right=False, labels=cat_labels
+ )
+
+ res = df.groupby(["value_group"], observed=False)["value_group"].count()
+ exp = res[sorted(res.index, key=lambda x: float(x.split()[0]))]
+ exp.index = CategoricalIndex(exp.index, name=exp.index.name)
+ tm.assert_series_equal(res, exp)
+
+
+@pytest.mark.parametrize("ordered", [True, False])
+def test_sort2(sort, ordered):
+ # dataframe groupby sort was being ignored # GH 8868
+ # GH#48749 - don't change order of categories
+ # GH#42482 - don't sort result when sort=False, even when ordered=True
+ df = DataFrame(
+ [
+ ["(7.5, 10]", 10, 10],
+ ["(7.5, 10]", 8, 20],
+ ["(2.5, 5]", 5, 30],
+ ["(5, 7.5]", 6, 40],
+ ["(2.5, 5]", 4, 50],
+ ["(0, 2.5]", 1, 60],
+ ["(5, 7.5]", 7, 70],
+ ],
+ columns=["range", "foo", "bar"],
+ )
+ df["range"] = Categorical(df["range"], ordered=ordered)
+ result = df.groupby("range", sort=sort, observed=False).first()
+
+ if sort:
+ data_values = [[1, 60], [5, 30], [6, 40], [10, 10]]
+ index_values = ["(0, 2.5]", "(2.5, 5]", "(5, 7.5]", "(7.5, 10]"]
+ else:
+ data_values = [[10, 10], [5, 30], [6, 40], [1, 60]]
+ index_values = ["(7.5, 10]", "(2.5, 5]", "(5, 7.5]", "(0, 2.5]"]
+ expected = DataFrame(
+ data_values,
+ columns=["foo", "bar"],
+ index=CategoricalIndex(index_values, name="range", ordered=ordered),
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("ordered", [True, False])
+def test_sort_datetimelike(sort, ordered):
+ # GH10505
+ # GH#42482 - don't sort result when sort=False, even when ordered=True
+
+ # use same data as test_groupby_sort_categorical, which category is
+ # corresponding to datetime.month
+ df = DataFrame(
+ {
+ "dt": [
+ datetime(2011, 7, 1),
+ datetime(2011, 7, 1),
+ datetime(2011, 2, 1),
+ datetime(2011, 5, 1),
+ datetime(2011, 2, 1),
+ datetime(2011, 1, 1),
+ datetime(2011, 5, 1),
+ ],
+ "foo": [10, 8, 5, 6, 4, 1, 7],
+ "bar": [10, 20, 30, 40, 50, 60, 70],
+ },
+ columns=["dt", "foo", "bar"],
+ )
+
+ # ordered=True
+ df["dt"] = Categorical(df["dt"], ordered=ordered)
+ if sort:
+ data_values = [[1, 60], [5, 30], [6, 40], [10, 10]]
+ index_values = [
+ datetime(2011, 1, 1),
+ datetime(2011, 2, 1),
+ datetime(2011, 5, 1),
+ datetime(2011, 7, 1),
+ ]
+ else:
+ data_values = [[10, 10], [5, 30], [6, 40], [1, 60]]
+ index_values = [
+ datetime(2011, 7, 1),
+ datetime(2011, 2, 1),
+ datetime(2011, 5, 1),
+ datetime(2011, 1, 1),
+ ]
+ expected = DataFrame(
+ data_values,
+ columns=["foo", "bar"],
+ index=CategoricalIndex(index_values, name="dt", ordered=ordered),
+ )
+ result = df.groupby("dt", sort=sort, observed=False).first()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_empty_sum():
+ # https://github.com/pandas-dev/pandas/issues/18678
+ df = DataFrame(
+ {"A": Categorical(["a", "a", "b"], categories=["a", "b", "c"]), "B": [1, 2, 1]}
+ )
+ expected_idx = CategoricalIndex(["a", "b", "c"], name="A")
+
+ # 0 by default
+ result = df.groupby("A", observed=False).B.sum()
+ expected = Series([3, 1, 0], expected_idx, name="B")
+ tm.assert_series_equal(result, expected)
+
+ # min_count=0
+ result = df.groupby("A", observed=False).B.sum(min_count=0)
+ expected = Series([3, 1, 0], expected_idx, name="B")
+ tm.assert_series_equal(result, expected)
+
+ # min_count=1
+ result = df.groupby("A", observed=False).B.sum(min_count=1)
+ expected = Series([3, 1, np.nan], expected_idx, name="B")
+ tm.assert_series_equal(result, expected)
+
+ # min_count>1
+ result = df.groupby("A", observed=False).B.sum(min_count=2)
+ expected = Series([3, np.nan, np.nan], expected_idx, name="B")
+ tm.assert_series_equal(result, expected)
+
+
+def test_empty_prod():
+ # https://github.com/pandas-dev/pandas/issues/18678
+ df = DataFrame(
+ {"A": Categorical(["a", "a", "b"], categories=["a", "b", "c"]), "B": [1, 2, 1]}
+ )
+
+ expected_idx = CategoricalIndex(["a", "b", "c"], name="A")
+
+ # 1 by default
+ result = df.groupby("A", observed=False).B.prod()
+ expected = Series([2, 1, 1], expected_idx, name="B")
+ tm.assert_series_equal(result, expected)
+
+ # min_count=0
+ result = df.groupby("A", observed=False).B.prod(min_count=0)
+ expected = Series([2, 1, 1], expected_idx, name="B")
+ tm.assert_series_equal(result, expected)
+
+ # min_count=1
+ result = df.groupby("A", observed=False).B.prod(min_count=1)
+ expected = Series([2, 1, np.nan], expected_idx, name="B")
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_multiindex_categorical_datetime():
+ # https://github.com/pandas-dev/pandas/issues/21390
+
+ df = DataFrame(
+ {
+ "key1": Categorical(list("abcbabcba")),
+ "key2": Categorical(
+ list(pd.date_range("2018-06-01 00", freq="1min", periods=3)) * 3
+ ),
+ "values": np.arange(9),
+ }
+ )
+ result = df.groupby(["key1", "key2"], observed=False).mean()
+
+ idx = MultiIndex.from_product(
+ [
+ Categorical(["a", "b", "c"]),
+ Categorical(pd.date_range("2018-06-01 00", freq="1min", periods=3)),
+ ],
+ names=["key1", "key2"],
+ )
+ expected = DataFrame({"values": [0, 4, 8, 3, 4, 5, 6, np.nan, 2]}, index=idx)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "as_index, expected",
+ [
+ (
+ True,
+ Series(
+ index=MultiIndex.from_arrays(
+ [Series([1, 1, 2], dtype="category"), [1, 2, 2]], names=["a", "b"]
+ ),
+ data=[1, 2, 3],
+ name="x",
+ ),
+ ),
+ (
+ False,
+ DataFrame(
+ {
+ "a": Series([1, 1, 2], dtype="category"),
+ "b": [1, 2, 2],
+ "x": [1, 2, 3],
+ }
+ ),
+ ),
+ ],
+)
+def test_groupby_agg_observed_true_single_column(as_index, expected):
+ # GH-23970
+ df = DataFrame(
+ {"a": Series([1, 1, 2], dtype="category"), "b": [1, 2, 2], "x": [1, 2, 3]}
+ )
+
+ result = df.groupby(["a", "b"], as_index=as_index, observed=True)["x"].sum()
+
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize("fill_value", [None, np.nan, pd.NaT])
+def test_shift(fill_value):
+ ct = Categorical(
+ ["a", "b", "c", "d"], categories=["a", "b", "c", "d"], ordered=False
+ )
+ expected = Categorical(
+ [None, "a", "b", "c"], categories=["a", "b", "c", "d"], ordered=False
+ )
+ res = ct.shift(1, fill_value=fill_value)
+ tm.assert_equal(res, expected)
+
+
+@pytest.fixture
+def df_cat(df):
+ """
+ DataFrame with multiple categorical columns and a column of integers.
+ Shortened so as not to contain all possible combinations of categories.
+ Useful for testing `observed` kwarg functionality on GroupBy objects.
+
+ Parameters
+ ----------
+ df: DataFrame
+ Non-categorical, longer DataFrame from another fixture, used to derive
+ this one
+
+ Returns
+ -------
+ df_cat: DataFrame
+ """
+ df_cat = df.copy()[:4] # leave out some groups
+ df_cat["A"] = df_cat["A"].astype("category")
+ df_cat["B"] = df_cat["B"].astype("category")
+ df_cat["C"] = Series([1, 2, 3, 4])
+ df_cat = df_cat.drop(["D"], axis=1)
+ return df_cat
+
+
+@pytest.mark.parametrize("operation", ["agg", "apply"])
+def test_seriesgroupby_observed_true(df_cat, operation):
+ # GH#24880
+ # GH#49223 - order of results was wrong when grouping by index levels
+ lev_a = Index(["bar", "bar", "foo", "foo"], dtype=df_cat["A"].dtype, name="A")
+ lev_b = Index(["one", "three", "one", "two"], dtype=df_cat["B"].dtype, name="B")
+ index = MultiIndex.from_arrays([lev_a, lev_b])
+ expected = Series(data=[2, 4, 1, 3], index=index, name="C").sort_index()
+
+ grouped = df_cat.groupby(["A", "B"], observed=True)["C"]
+ result = getattr(grouped, operation)(sum)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("operation", ["agg", "apply"])
+@pytest.mark.parametrize("observed", [False, None])
+def test_seriesgroupby_observed_false_or_none(df_cat, observed, operation):
+ # GH 24880
+ # GH#49223 - order of results was wrong when grouping by index levels
+ index, _ = MultiIndex.from_product(
+ [
+ CategoricalIndex(["bar", "foo"], ordered=False),
+ CategoricalIndex(["one", "three", "two"], ordered=False),
+ ],
+ names=["A", "B"],
+ ).sortlevel()
+
+ expected = Series(data=[2, 4, 0, 1, 0, 3], index=index, name="C")
+ grouped = df_cat.groupby(["A", "B"], observed=observed)["C"]
+ result = getattr(grouped, operation)(sum)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "observed, index, data",
+ [
+ (
+ True,
+ MultiIndex.from_arrays(
+ [
+ Index(["bar"] * 4 + ["foo"] * 4, dtype="category", name="A"),
+ Index(
+ ["one", "one", "three", "three", "one", "one", "two", "two"],
+ dtype="category",
+ name="B",
+ ),
+ Index(["min", "max"] * 4),
+ ]
+ ),
+ [2, 2, 4, 4, 1, 1, 3, 3],
+ ),
+ (
+ False,
+ MultiIndex.from_product(
+ [
+ CategoricalIndex(["bar", "foo"], ordered=False),
+ CategoricalIndex(["one", "three", "two"], ordered=False),
+ Index(["min", "max"]),
+ ],
+ names=["A", "B", None],
+ ),
+ [2, 2, 4, 4, np.nan, np.nan, 1, 1, np.nan, np.nan, 3, 3],
+ ),
+ (
+ None,
+ MultiIndex.from_product(
+ [
+ CategoricalIndex(["bar", "foo"], ordered=False),
+ CategoricalIndex(["one", "three", "two"], ordered=False),
+ Index(["min", "max"]),
+ ],
+ names=["A", "B", None],
+ ),
+ [2, 2, 4, 4, np.nan, np.nan, 1, 1, np.nan, np.nan, 3, 3],
+ ),
+ ],
+)
+def test_seriesgroupby_observed_apply_dict(df_cat, observed, index, data):
+ # GH 24880
+ expected = Series(data=data, index=index, name="C")
+ result = df_cat.groupby(["A", "B"], observed=observed)["C"].apply(
+ lambda x: {"min": x.min(), "max": x.max()}
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_categorical_series_dataframe_consistent(df_cat):
+ # GH 20416
+ expected = df_cat.groupby(["A", "B"], observed=False)["C"].mean()
+ result = df_cat.groupby(["A", "B"], observed=False).mean()["C"]
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_cat_preserves_structure(observed, ordered):
+ # GH 28787
+ df = DataFrame(
+ {"Name": Categorical(["Bob", "Greg"], ordered=ordered), "Item": [1, 2]},
+ columns=["Name", "Item"],
+ )
+ expected = df.copy()
+
+ result = (
+ df.groupby("Name", observed=observed)
+ .agg(DataFrame.sum, skipna=True)
+ .reset_index()
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_get_nonexistent_category():
+ # Accessing a Category that is not in the dataframe
+ df = DataFrame({"var": ["a", "a", "b", "b"], "val": range(4)})
+ with pytest.raises(KeyError, match="'vau'"):
+ df.groupby("var").apply(lambda rows: DataFrame({"val": [rows.iloc[-1]["vau"]]}))
+
+
+def test_series_groupby_on_2_categoricals_unobserved(reduction_func, observed):
+ # GH 17605
+ if reduction_func == "ngroup":
+ pytest.skip("ngroup is not truly a reduction")
+
+ df = DataFrame(
+ {
+ "cat_1": Categorical(list("AABB"), categories=list("ABCD")),
+ "cat_2": Categorical(list("AB") * 2, categories=list("ABCD")),
+ "value": [0.1] * 4,
+ }
+ )
+ args = get_groupby_method_args(reduction_func, df)
+
+ expected_length = 4 if observed else 16
+
+ series_groupby = df.groupby(["cat_1", "cat_2"], observed=observed)["value"]
+
+ if reduction_func == "corrwith":
+ # TODO: implemented SeriesGroupBy.corrwith. See GH 32293
+ assert not hasattr(series_groupby, reduction_func)
+ return
+
+ agg = getattr(series_groupby, reduction_func)
+
+ if not observed and reduction_func in ["idxmin", "idxmax"]:
+ # idxmin and idxmax are designed to fail on empty inputs
+ with pytest.raises(
+ ValueError, match="empty group due to unobserved categories"
+ ):
+ agg(*args)
+ return
+
+ result = agg(*args)
+
+ assert len(result) == expected_length
+
+
+def test_series_groupby_on_2_categoricals_unobserved_zeroes_or_nans(
+ reduction_func, request
+):
+ # GH 17605
+ # Tests whether the unobserved categories in the result contain 0 or NaN
+
+ if reduction_func == "ngroup":
+ pytest.skip("ngroup is not truly a reduction")
+
+ if reduction_func == "corrwith": # GH 32293
+ mark = pytest.mark.xfail(
+ reason="TODO: implemented SeriesGroupBy.corrwith. See GH 32293"
+ )
+ request.applymarker(mark)
+
+ df = DataFrame(
+ {
+ "cat_1": Categorical(list("AABB"), categories=list("ABC")),
+ "cat_2": Categorical(list("AB") * 2, categories=list("ABC")),
+ "value": [0.1] * 4,
+ }
+ )
+ unobserved = [tuple("AC"), tuple("BC"), tuple("CA"), tuple("CB"), tuple("CC")]
+ args = get_groupby_method_args(reduction_func, df)
+
+ series_groupby = df.groupby(["cat_1", "cat_2"], observed=False)["value"]
+ agg = getattr(series_groupby, reduction_func)
+
+ if reduction_func in ["idxmin", "idxmax"]:
+ # idxmin and idxmax are designed to fail on empty inputs
+ with pytest.raises(
+ ValueError, match="empty group due to unobserved categories"
+ ):
+ agg(*args)
+ return
+
+ result = agg(*args)
+
+ missing_fillin = _results_for_groupbys_with_missing_categories[reduction_func]
+
+ for idx in unobserved:
+ val = result.loc[idx]
+ assert (pd.isna(missing_fillin) and pd.isna(val)) or (val == missing_fillin)
+
+ # If we expect unobserved values to be zero, we also expect the dtype to be int.
+ # Except for .sum(). If the observed categories sum to dtype=float (i.e. their
+ # sums have decimals), then the zeros for the missing categories should also be
+ # floats.
+ if missing_fillin == 0:
+ if reduction_func in ["count", "nunique", "size"]:
+ assert np.issubdtype(result.dtype, np.integer)
+ else:
+ assert reduction_func in ["sum", "any"]
+
+
+def test_dataframe_groupby_on_2_categoricals_when_observed_is_true(reduction_func):
+ # GH 23865
+ # GH 27075
+ # Ensure that df.groupby, when 'by' is two Categorical variables,
+ # does not return the categories that are not in df when observed=True
+ if reduction_func == "ngroup":
+ pytest.skip("ngroup does not return the Categories on the index")
+
+ df = DataFrame(
+ {
+ "cat_1": Categorical(list("AABB"), categories=list("ABC")),
+ "cat_2": Categorical(list("1111"), categories=list("12")),
+ "value": [0.1, 0.1, 0.1, 0.1],
+ }
+ )
+ unobserved_cats = [("A", "2"), ("B", "2"), ("C", "1"), ("C", "2")]
+
+ df_grp = df.groupby(["cat_1", "cat_2"], observed=True)
+
+ args = get_groupby_method_args(reduction_func, df)
+ if reduction_func == "corrwith":
+ warn = Pandas4Warning
+ warn_msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ warn_msg = ""
+ with tm.assert_produces_warning(warn, match=warn_msg):
+ res = getattr(df_grp, reduction_func)(*args)
+
+ for cat in unobserved_cats:
+ assert cat not in res.index
+
+
+@pytest.mark.parametrize("observed", [False, None])
+def test_dataframe_groupby_on_2_categoricals_when_observed_is_false(
+ reduction_func, observed, using_python_scalars
+):
+ # GH 23865
+ # GH 27075
+ # Ensure that df.groupby, when 'by' is two Categorical variables,
+ # returns the categories that are not in df when observed=False/None
+
+ if reduction_func == "ngroup":
+ pytest.skip("ngroup does not return the Categories on the index")
+
+ df = DataFrame(
+ {
+ "cat_1": Categorical(list("AABB"), categories=list("ABC")),
+ "cat_2": Categorical(list("1111"), categories=list("12")),
+ "value": [0.1, 0.1, 0.1, 0.1],
+ }
+ )
+ unobserved_cats = [("A", "2"), ("B", "2"), ("C", "1"), ("C", "2")]
+
+ df_grp = df.groupby(["cat_1", "cat_2"], observed=observed)
+
+ args = get_groupby_method_args(reduction_func, df)
+
+ if not observed and reduction_func in ["idxmin", "idxmax"]:
+ # idxmin and idxmax are designed to fail on empty inputs
+ with pytest.raises(
+ ValueError, match="empty group due to unobserved categories"
+ ):
+ getattr(df_grp, reduction_func)(*args)
+ return
+
+ if reduction_func == "corrwith":
+ warn = Pandas4Warning
+ warn_msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ warn_msg = ""
+ with tm.assert_produces_warning(warn, match=warn_msg):
+ res = getattr(df_grp, reduction_func)(*args)
+
+ expected = _results_for_groupbys_with_missing_categories[reduction_func]
+
+ if using_python_scalars and reduction_func == "size":
+ assert (res.loc[unobserved_cats] == expected).all() is True
+ elif expected is np.nan:
+ assert res.loc[unobserved_cats].isnull().all().all()
+ else:
+ assert (res.loc[unobserved_cats] == expected).all().all()
+
+
+def test_series_groupby_categorical_aggregation_getitem():
+ # GH 8870
+ d = {"foo": [10, 8, 4, 1], "bar": [10, 20, 30, 40], "baz": ["d", "c", "d", "c"]}
+ df = DataFrame(d)
+ cat = pd.cut(df["foo"], np.linspace(0, 20, 5))
+ df["range"] = cat
+ groups = df.groupby(["range", "baz"], as_index=True, sort=True, observed=False)
+ result = groups["foo"].agg("mean")
+ expected = groups.agg("mean")["foo"]
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func, expected_values",
+ [(Series.nunique, [1, 1, 2]), (Series.count, [1, 2, 2])],
+)
+def test_groupby_agg_categorical_columns(func, expected_values):
+ # 31256
+ df = DataFrame(
+ {
+ "id": [0, 1, 2, 3, 4],
+ "groups": [0, 1, 1, 2, 2],
+ "value": Categorical([0, 0, 0, 0, 1]),
+ }
+ ).set_index("id")
+ result = df.groupby("groups").agg(func)
+
+ expected = DataFrame(
+ {"value": expected_values}, index=Index([0, 1, 2], name="groups")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_agg_non_numeric():
+ df = DataFrame({"A": Categorical(["a", "a", "b"], categories=["a", "b", "c"])})
+ expected = DataFrame({"A": [2, 1]}, index=np.array([1, 2]))
+
+ result = df.groupby([1, 2, 1]).agg(Series.nunique)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby([1, 2, 1]).nunique()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["first", "last"])
+def test_groupby_first_returned_categorical_instead_of_dataframe(func):
+ # GH 28641: groupby drops index, when grouping over categorical column with
+ # first/last. Renamed Categorical instead of DataFrame previously.
+ df = DataFrame({"A": [1997], "B": Series(["b"], dtype="category").cat.as_ordered()})
+ df_grouped = df.groupby("A")["B"]
+ result = getattr(df_grouped, func)()
+
+ # ordered categorical dtype should be preserved
+ expected = Series(
+ ["b"], index=Index([1997], name="A"), name="B", dtype=df["B"].dtype
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_read_only_category_no_sort():
+ # GH33410
+ cats = np.array([1, 2])
+ cats.flags.writeable = False
+ df = DataFrame(
+ {"a": [1, 3, 5, 7], "b": Categorical([1, 1, 2, 2], categories=Index(cats))}
+ )
+ expected = DataFrame(data={"a": [2.0, 6.0]}, index=CategoricalIndex(cats, name="b"))
+ result = df.groupby("b", sort=False, observed=False).mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_sorted_missing_category_values():
+ # GH 28597
+ df = DataFrame(
+ {
+ "foo": [
+ "small",
+ "large",
+ "large",
+ "large",
+ "medium",
+ "large",
+ "large",
+ "medium",
+ ],
+ "bar": ["C", "A", "A", "C", "A", "C", "A", "C"],
+ }
+ )
+ df["foo"] = (
+ df["foo"]
+ .astype("category")
+ .cat.set_categories(["tiny", "small", "medium", "large"], ordered=True)
+ )
+
+ expected = DataFrame(
+ {
+ "tiny": {"A": 0, "C": 0},
+ "small": {"A": 0, "C": 1},
+ "medium": {"A": 1, "C": 1},
+ "large": {"A": 3, "C": 2},
+ }
+ )
+ expected = expected.rename_axis("bar", axis="index")
+ expected.columns = CategoricalIndex(
+ ["tiny", "small", "medium", "large"],
+ categories=["tiny", "small", "medium", "large"],
+ ordered=True,
+ name="foo",
+ dtype="category",
+ )
+
+ result = df.groupby(["bar", "foo"], observed=False).size().unstack()
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_agg_cython_category_not_implemented_fallback():
+ # https://github.com/pandas-dev/pandas/issues/31450
+ df = DataFrame({"col_num": [1, 1, 2, 3]})
+ df["col_cat"] = df["col_num"].astype("category")
+
+ result = df.groupby("col_num").col_cat.first()
+
+ # ordered categorical dtype should definitely be preserved;
+ # this is unordered, so is less-clear case (if anything, it should raise)
+ expected = Series(
+ [1, 2, 3],
+ index=Index([1, 2, 3], name="col_num"),
+ name="col_cat",
+ dtype=df["col_cat"].dtype,
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = df.groupby("col_num").agg({"col_cat": "first"})
+ expected = expected.to_frame()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_aggregate_categorical_with_isnan():
+ # GH 29837
+ df = DataFrame(
+ {
+ "A": [1, 1, 1, 1],
+ "B": [1, 2, 1, 2],
+ "numerical_col": [0.1, 0.2, np.nan, 0.3],
+ "object_col": ["foo", "bar", "foo", "fee"],
+ "categorical_col": ["foo", "bar", "foo", "fee"],
+ }
+ )
+
+ df = df.astype({"categorical_col": "category"})
+
+ result = df.groupby(["A", "B"]).agg(lambda df: df.isna().sum())
+ index = MultiIndex.from_arrays([[1, 1], [1, 2]], names=("A", "B"))
+ expected = DataFrame(
+ data={
+ "numerical_col": [1, 0],
+ "object_col": [0, 0],
+ "categorical_col": [0, 0],
+ },
+ index=index,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_transform():
+ # GH 29037
+ df = DataFrame(
+ {
+ "package_id": [1, 1, 1, 2, 2, 3],
+ "status": [
+ "Waiting",
+ "OnTheWay",
+ "Delivered",
+ "Waiting",
+ "OnTheWay",
+ "Waiting",
+ ],
+ }
+ )
+
+ delivery_status_type = pd.CategoricalDtype(
+ categories=["Waiting", "OnTheWay", "Delivered"], ordered=True
+ )
+ df["status"] = df["status"].astype(delivery_status_type)
+ df["last_status"] = df.groupby("package_id")["status"].transform(max)
+ result = df.copy()
+
+ expected = DataFrame(
+ {
+ "package_id": [1, 1, 1, 2, 2, 3],
+ "status": [
+ "Waiting",
+ "OnTheWay",
+ "Delivered",
+ "Waiting",
+ "OnTheWay",
+ "Waiting",
+ ],
+ "last_status": [
+ "Waiting",
+ "Waiting",
+ "Waiting",
+ "Waiting",
+ "Waiting",
+ "Waiting",
+ ],
+ }
+ )
+
+ expected["status"] = expected["status"].astype(delivery_status_type)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["first", "last"])
+def test_series_groupby_first_on_categorical_col_grouped_on_2_categoricals(
+ func: str, observed: bool
+):
+ # GH 34951
+ cat = Categorical([0, 0, 1, 1])
+ val = [0, 1, 1, 0]
+ df = DataFrame({"a": cat, "b": cat, "c": val})
+
+ cat2 = Categorical([0, 1])
+ idx = MultiIndex.from_product([cat2, cat2], names=["a", "b"])
+ expected_dict = {
+ "first": Series([0, np.nan, np.nan, 1], idx, name="c"),
+ "last": Series([1, np.nan, np.nan, 0], idx, name="c"),
+ }
+
+ expected = expected_dict[func]
+ if observed:
+ expected = expected.dropna().astype(np.int64)
+
+ srs_grp = df.groupby(["a", "b"], observed=observed)["c"]
+ result = getattr(srs_grp, func)()
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["first", "last"])
+def test_df_groupby_first_on_categorical_col_grouped_on_2_categoricals(
+ func: str, observed: bool
+):
+ # GH 34951
+ cat = Categorical([0, 0, 1, 1])
+ val = [0, 1, 1, 0]
+ df = DataFrame({"a": cat, "b": cat, "c": val})
+
+ cat2 = Categorical([0, 1])
+ idx = MultiIndex.from_product([cat2, cat2], names=["a", "b"])
+ expected_dict = {
+ "first": Series([0, np.nan, np.nan, 1], idx, name="c"),
+ "last": Series([1, np.nan, np.nan, 0], idx, name="c"),
+ }
+
+ expected = expected_dict[func].to_frame()
+ if observed:
+ expected = expected.dropna().astype(np.int64)
+
+ df_grp = df.groupby(["a", "b"], observed=observed)
+ result = getattr(df_grp, func)()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_categorical_indices_unused_categories():
+ # GH#38642
+ df = DataFrame(
+ {
+ "key": Categorical(["b", "b", "a"], categories=["a", "b", "c"]),
+ "col": range(3),
+ }
+ )
+ grouped = df.groupby("key", sort=False, observed=False)
+ result = grouped.indices
+ expected = {
+ "b": np.array([0, 1], dtype="intp"),
+ "a": np.array([2], dtype="intp"),
+ "c": np.array([], dtype="intp"),
+ }
+ assert result.keys() == expected.keys()
+ for key in result.keys():
+ tm.assert_numpy_array_equal(result[key], expected[key])
+
+
+@pytest.mark.parametrize("func", ["first", "last"])
+def test_groupby_last_first_preserve_categoricaldtype(func):
+ # GH#33090
+ df = DataFrame({"a": [1, 2, 3]})
+ df["b"] = df["a"].astype("category")
+ result = getattr(df.groupby("a")["b"], func)()
+ expected = Series(
+ Categorical([1, 2, 3]), name="b", index=Index([1, 2, 3], name="a")
+ )
+ tm.assert_series_equal(expected, result)
+
+
+def test_groupby_categorical_observed_nunique():
+ # GH#45128
+ df = DataFrame({"a": [1, 2], "b": [1, 2], "c": [10, 11]})
+ df = df.astype(dtype={"a": "category", "b": "category"})
+ result = df.groupby(["a", "b"], observed=True).nunique()["c"]
+ expected = Series(
+ [1, 1],
+ index=MultiIndex.from_arrays(
+ [CategoricalIndex([1, 2], name="a"), CategoricalIndex([1, 2], name="b")]
+ ),
+ name="c",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_categorical_aggregate_functions():
+ # GH#37275
+ dtype = pd.CategoricalDtype(categories=["small", "big"], ordered=True)
+ df = DataFrame(
+ [[1, "small"], [1, "big"], [2, "small"]], columns=["grp", "description"]
+ ).astype({"description": dtype})
+
+ result = df.groupby("grp")["description"].max()
+ expected = Series(
+ ["big", "small"],
+ index=Index([1, 2], name="grp"),
+ name="description",
+ dtype=pd.CategoricalDtype(categories=["small", "big"], ordered=True),
+ )
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_categorical_dropna(observed, dropna):
+ # GH#48645 - dropna should have no impact on the result when there are no NA values
+ cat = Categorical([1, 2], categories=[1, 2, 3])
+ df = DataFrame({"x": Categorical([1, 2], categories=[1, 2, 3]), "y": [3, 4]})
+ gb = df.groupby("x", observed=observed, dropna=dropna)
+ result = gb.sum()
+
+ if observed:
+ expected = DataFrame({"y": [3, 4]}, index=cat)
+ else:
+ index = CategoricalIndex([1, 2, 3], [1, 2, 3])
+ expected = DataFrame({"y": [3, 4, 0]}, index=index)
+ expected.index.name = "x"
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_kind", ["range", "single", "multi"])
+@pytest.mark.parametrize("ordered", [True, False])
+def test_category_order_reducer(
+ request, as_index, sort, observed, reduction_func, index_kind, ordered
+):
+ # GH#48749
+ if reduction_func == "corrwith" and not as_index and index_kind != "single":
+ msg = "GH#49950 - corrwith with as_index=False may not have grouping column"
+ request.applymarker(pytest.mark.xfail(reason=msg))
+ elif index_kind != "range" and not as_index:
+ pytest.skip(reason="Result doesn't have categories, nothing to test")
+ df = DataFrame(
+ {
+ "a": Categorical([2, 1, 2, 3], categories=[1, 4, 3, 2], ordered=ordered),
+ "b": range(4),
+ }
+ )
+ if index_kind == "range":
+ keys = ["a"]
+ elif index_kind == "single":
+ keys = ["a"]
+ df = df.set_index(keys)
+ elif index_kind == "multi":
+ keys = ["a", "a2"]
+ df["a2"] = df["a"]
+ df = df.set_index(keys)
+ args = get_groupby_method_args(reduction_func, df)
+ gb = df.groupby(keys, as_index=as_index, sort=sort, observed=observed)
+
+ if not observed and reduction_func in ["idxmin", "idxmax"]:
+ # idxmin and idxmax are designed to fail on empty inputs
+ with pytest.raises(
+ ValueError, match="empty group due to unobserved categories"
+ ):
+ getattr(gb, reduction_func)(*args)
+ return
+ if reduction_func == "corrwith":
+ warn = Pandas4Warning
+ warn_msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ warn_msg = ""
+ with tm.assert_produces_warning(warn, match=warn_msg):
+ op_result = getattr(gb, reduction_func)(*args)
+ if as_index:
+ result = op_result.index.get_level_values("a").categories
+ else:
+ result = op_result["a"].cat.categories
+ expected = Index([1, 4, 3, 2])
+ tm.assert_index_equal(result, expected)
+
+ if index_kind == "multi":
+ result = op_result.index.get_level_values("a2").categories
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_kind", ["single", "multi"])
+@pytest.mark.parametrize("ordered", [True, False])
+def test_category_order_transformer(
+ as_index, sort, observed, transformation_func, index_kind, ordered
+):
+ # GH#48749
+ df = DataFrame(
+ {
+ "a": Categorical([2, 1, 2, 3], categories=[1, 4, 3, 2], ordered=ordered),
+ "b": range(4),
+ }
+ )
+ if index_kind == "single":
+ keys = ["a"]
+ df = df.set_index(keys)
+ elif index_kind == "multi":
+ keys = ["a", "a2"]
+ df["a2"] = df["a"]
+ df = df.set_index(keys)
+ args = get_groupby_method_args(transformation_func, df)
+ gb = df.groupby(keys, as_index=as_index, sort=sort, observed=observed)
+ op_result = getattr(gb, transformation_func)(*args)
+ result = op_result.index.get_level_values("a").categories
+ expected = Index([1, 4, 3, 2])
+ tm.assert_index_equal(result, expected)
+
+ if index_kind == "multi":
+ result = op_result.index.get_level_values("a2").categories
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_kind", ["range", "single", "multi"])
+@pytest.mark.parametrize("method", ["head", "tail"])
+@pytest.mark.parametrize("ordered", [True, False])
+def test_category_order_head_tail(
+ as_index, sort, observed, method, index_kind, ordered
+):
+ # GH#48749
+ df = DataFrame(
+ {
+ "a": Categorical([2, 1, 2, 3], categories=[1, 4, 3, 2], ordered=ordered),
+ "b": range(4),
+ }
+ )
+ if index_kind == "range":
+ keys = ["a"]
+ elif index_kind == "single":
+ keys = ["a"]
+ df = df.set_index(keys)
+ elif index_kind == "multi":
+ keys = ["a", "a2"]
+ df["a2"] = df["a"]
+ df = df.set_index(keys)
+ gb = df.groupby(keys, as_index=as_index, sort=sort, observed=observed)
+ op_result = getattr(gb, method)()
+ if index_kind == "range":
+ result = op_result["a"].cat.categories
+ else:
+ result = op_result.index.get_level_values("a").categories
+ expected = Index([1, 4, 3, 2])
+ tm.assert_index_equal(result, expected)
+
+ if index_kind == "multi":
+ result = op_result.index.get_level_values("a2").categories
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_kind", ["range", "single", "multi"])
+@pytest.mark.parametrize("method", ["apply", "agg", "transform"])
+@pytest.mark.parametrize("ordered", [True, False])
+def test_category_order_apply(as_index, sort, observed, method, index_kind, ordered):
+ # GH#48749
+ if (method == "transform" and index_kind == "range") or (
+ not as_index and index_kind != "range"
+ ):
+ pytest.skip("No categories in result, nothing to test")
+ df = DataFrame(
+ {
+ "a": Categorical([2, 1, 2, 3], categories=[1, 4, 3, 2], ordered=ordered),
+ "b": range(4),
+ }
+ )
+ if index_kind == "range":
+ keys = ["a"]
+ elif index_kind == "single":
+ keys = ["a"]
+ df = df.set_index(keys)
+ elif index_kind == "multi":
+ keys = ["a", "a2"]
+ df["a2"] = df["a"]
+ df = df.set_index(keys)
+ gb = df.groupby(keys, as_index=as_index, sort=sort, observed=observed)
+ op_result = getattr(gb, method)(lambda x: x.sum(numeric_only=True))
+ if (method == "transform" or not as_index) and index_kind == "range":
+ result = op_result["a"].cat.categories
+ else:
+ result = op_result.index.get_level_values("a").categories
+ expected = Index([1, 4, 3, 2])
+ tm.assert_index_equal(result, expected)
+
+ if index_kind == "multi":
+ result = op_result.index.get_level_values("a2").categories
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_kind", ["range", "single", "multi"])
+def test_many_categories(as_index, sort, index_kind, ordered):
+ # GH#48749 - Test when the grouper has many categories
+ if index_kind != "range" and not as_index:
+ pytest.skip(reason="Result doesn't have categories, nothing to test")
+ categories = np.arange(9999, -1, -1)
+ grouper = Categorical([2, 1, 2, 3], categories=categories, ordered=ordered)
+ df = DataFrame({"a": grouper, "b": range(4)})
+ if index_kind == "range":
+ keys = ["a"]
+ elif index_kind == "single":
+ keys = ["a"]
+ df = df.set_index(keys)
+ elif index_kind == "multi":
+ keys = ["a", "a2"]
+ df["a2"] = df["a"]
+ df = df.set_index(keys)
+ gb = df.groupby(keys, as_index=as_index, sort=sort, observed=True)
+ result = gb.sum()
+
+ # Test is setup so that data and index are the same values
+ data = [3, 2, 1] if sort else [2, 1, 3]
+
+ index = CategoricalIndex(
+ data, categories=grouper.categories, ordered=ordered, name="a"
+ )
+ if as_index:
+ expected = DataFrame({"b": data})
+ if index_kind == "multi":
+ expected.index = MultiIndex.from_frame(DataFrame({"a": index, "a2": index}))
+ else:
+ expected.index = index
+ elif index_kind == "multi":
+ expected = DataFrame({"a": Series(index), "a2": Series(index), "b": data})
+ else:
+ expected = DataFrame({"a": Series(index), "b": data})
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("test_series", [True, False])
+@pytest.mark.parametrize("keys", [["a1"], ["a1", "a2"]])
+def test_agg_list(request, as_index, observed, reduction_func, test_series, keys):
+ # GH#52760
+ if test_series and reduction_func == "corrwith":
+ assert not hasattr(SeriesGroupBy, "corrwith")
+ pytest.skip("corrwith not implemented for SeriesGroupBy")
+ elif reduction_func == "corrwith":
+ msg = "GH#32293: attempts to call SeriesGroupBy.corrwith"
+ request.applymarker(pytest.mark.xfail(reason=msg))
+
+ df = DataFrame({"a1": [0, 0, 1], "a2": [2, 3, 3], "b": [4, 5, 6]})
+ df = df.astype({"a1": "category", "a2": "category"})
+ if "a2" not in keys:
+ df = df.drop(columns="a2")
+ gb = df.groupby(by=keys, as_index=as_index, observed=observed)
+ if test_series:
+ gb = gb["b"]
+ args = get_groupby_method_args(reduction_func, df)
+
+ if not observed and reduction_func in ["idxmin", "idxmax"] and keys == ["a1", "a2"]:
+ with pytest.raises(
+ ValueError, match="empty group due to unobserved categories"
+ ):
+ gb.agg([reduction_func], *args)
+ return
+
+ result = gb.agg([reduction_func], *args)
+ expected = getattr(gb, reduction_func)(*args)
+
+ if as_index and (test_series or reduction_func == "size"):
+ expected = expected.to_frame(reduction_func)
+ if not test_series:
+ expected.columns = MultiIndex.from_tuples(
+ [(ind, "") for ind in expected.columns[:-1]] + [("b", reduction_func)]
+ )
+ elif not as_index:
+ expected.columns = [*keys, reduction_func]
+
+ tm.assert_equal(result, expected)
+
+
+def test_categorical_with_noncategorical_na(observed, sort):
+ # https://github.com/pandas-dev/pandas/issues/63920
+ df = DataFrame(
+ {
+ "dates": list("YXXYY"),
+ "sector": Categorical(
+ [2, 1, 2, 1, np.nan], categories=[1, 2, 3], ordered=True
+ ),
+ "metric": [1, 2, 3, 4, 5],
+ }
+ )
+ gb = df.groupby(["dates", "sector"], observed=observed, sort=sort)
+ # Only testing the ids/result_index, okay to just use one kernel
+ result = gb.sum()
+
+ if sort and observed:
+ taker = [0, 1, 2, 3]
+ elif not sort and observed:
+ taker = [3, 0, 1, 2]
+ elif sort and not observed:
+ taker = [0, 1, 4, 2, 3, 5]
+ elif not sort and not observed:
+ taker = [3, 0, 1, 2, 5, 4]
+ expected = (
+ DataFrame(
+ {
+ "dates": list("XXYYXY"),
+ "sector": Categorical(
+ [1, 2, 1, 2, 3, 3], categories=[1, 2, 3], ordered=True
+ ),
+ "metric": [2, 3, 4, 1, 0, 0],
+ }
+ )
+ .set_index(["dates", "sector"])
+ .take(taker)
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_counting.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_counting.py
new file mode 100644
index 0000000000000000000000000000000000000000..679f7eb7f7f11d842dc36c9c6cb83d2096fb66b2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_counting.py
@@ -0,0 +1,394 @@
+from itertools import product
+from string import ascii_lowercase
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Period,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestCounting:
+ def test_cumcount(self):
+ df = DataFrame([["a"], ["a"], ["a"], ["b"], ["a"]], columns=["A"])
+ g = df.groupby("A")
+ sg = g.A
+
+ expected = Series([0, 1, 2, 0, 3])
+
+ tm.assert_series_equal(expected, g.cumcount())
+ tm.assert_series_equal(expected, sg.cumcount())
+
+ def test_cumcount_empty(self):
+ ge = DataFrame().groupby(level=0)
+ se = Series(dtype=object).groupby(level=0)
+
+ # edge case, as this is usually considered float
+ e = Series(dtype="int64")
+
+ tm.assert_series_equal(e, ge.cumcount())
+ tm.assert_series_equal(e, se.cumcount())
+
+ def test_cumcount_dupe_index(self):
+ df = DataFrame(
+ [["a"], ["a"], ["a"], ["b"], ["a"]], columns=["A"], index=[0] * 5
+ )
+ g = df.groupby("A")
+ sg = g.A
+
+ expected = Series([0, 1, 2, 0, 3], index=[0] * 5)
+
+ tm.assert_series_equal(expected, g.cumcount())
+ tm.assert_series_equal(expected, sg.cumcount())
+
+ def test_cumcount_mi(self):
+ mi = MultiIndex.from_tuples([[0, 1], [1, 2], [2, 2], [2, 2], [1, 0]])
+ df = DataFrame([["a"], ["a"], ["a"], ["b"], ["a"]], columns=["A"], index=mi)
+ g = df.groupby("A")
+ sg = g.A
+
+ expected = Series([0, 1, 2, 0, 3], index=mi)
+
+ tm.assert_series_equal(expected, g.cumcount())
+ tm.assert_series_equal(expected, sg.cumcount())
+
+ def test_cumcount_groupby_not_col(self):
+ df = DataFrame(
+ [["a"], ["a"], ["a"], ["b"], ["a"]], columns=["A"], index=[0] * 5
+ )
+ g = df.groupby([0, 0, 0, 1, 0])
+ sg = g.A
+
+ expected = Series([0, 1, 2, 0, 3], index=[0] * 5)
+
+ tm.assert_series_equal(expected, g.cumcount())
+ tm.assert_series_equal(expected, sg.cumcount())
+
+ def test_ngroup(self):
+ df = DataFrame({"A": list("aaaba")})
+ g = df.groupby("A")
+ sg = g.A
+
+ expected = Series([0, 0, 0, 1, 0])
+
+ tm.assert_series_equal(expected, g.ngroup())
+ tm.assert_series_equal(expected, sg.ngroup())
+
+ def test_ngroup_distinct(self):
+ df = DataFrame({"A": list("abcde")})
+ g = df.groupby("A")
+ sg = g.A
+
+ expected = Series(range(5), dtype="int64")
+
+ tm.assert_series_equal(expected, g.ngroup())
+ tm.assert_series_equal(expected, sg.ngroup())
+
+ def test_ngroup_one_group(self):
+ df = DataFrame({"A": [0] * 5})
+ g = df.groupby("A")
+ sg = g.A
+
+ expected = Series([0] * 5)
+
+ tm.assert_series_equal(expected, g.ngroup())
+ tm.assert_series_equal(expected, sg.ngroup())
+
+ def test_ngroup_empty(self):
+ ge = DataFrame().groupby(level=0)
+ se = Series(dtype=object).groupby(level=0)
+
+ # edge case, as this is usually considered float
+ e = Series(dtype="int64")
+
+ tm.assert_series_equal(e, ge.ngroup())
+ tm.assert_series_equal(e, se.ngroup())
+
+ def test_ngroup_series_matches_frame(self):
+ df = DataFrame({"A": list("aaaba")})
+ s = Series(list("aaaba"))
+
+ tm.assert_series_equal(df.groupby(s).ngroup(), s.groupby(s).ngroup())
+
+ def test_ngroup_dupe_index(self):
+ df = DataFrame({"A": list("aaaba")}, index=[0] * 5)
+ g = df.groupby("A")
+ sg = g.A
+
+ expected = Series([0, 0, 0, 1, 0], index=[0] * 5)
+
+ tm.assert_series_equal(expected, g.ngroup())
+ tm.assert_series_equal(expected, sg.ngroup())
+
+ def test_ngroup_mi(self):
+ mi = MultiIndex.from_tuples([[0, 1], [1, 2], [2, 2], [2, 2], [1, 0]])
+ df = DataFrame({"A": list("aaaba")}, index=mi)
+ g = df.groupby("A")
+ sg = g.A
+ expected = Series([0, 0, 0, 1, 0], index=mi)
+
+ tm.assert_series_equal(expected, g.ngroup())
+ tm.assert_series_equal(expected, sg.ngroup())
+
+ def test_ngroup_groupby_not_col(self):
+ df = DataFrame({"A": list("aaaba")}, index=[0] * 5)
+ g = df.groupby([0, 0, 0, 1, 0])
+ sg = g.A
+
+ expected = Series([0, 0, 0, 1, 0], index=[0] * 5)
+
+ tm.assert_series_equal(expected, g.ngroup())
+ tm.assert_series_equal(expected, sg.ngroup())
+
+ def test_ngroup_descending(self):
+ df = DataFrame(["a", "a", "b", "a", "b"], columns=["A"])
+ g = df.groupby(["A"])
+
+ ascending = Series([0, 0, 1, 0, 1])
+ descending = Series([1, 1, 0, 1, 0])
+
+ tm.assert_series_equal(descending, (g.ngroups - 1) - ascending)
+ tm.assert_series_equal(ascending, g.ngroup(ascending=True))
+ tm.assert_series_equal(descending, g.ngroup(ascending=False))
+
+ def test_ngroup_matches_cumcount(self):
+ # verify one manually-worked out case works
+ df = DataFrame(
+ [["a", "x"], ["a", "y"], ["b", "x"], ["a", "x"], ["b", "y"]],
+ columns=["A", "X"],
+ )
+ g = df.groupby(["A", "X"])
+ g_ngroup = g.ngroup()
+ g_cumcount = g.cumcount()
+ expected_ngroup = Series([0, 1, 2, 0, 3])
+ expected_cumcount = Series([0, 0, 0, 1, 0])
+
+ tm.assert_series_equal(g_ngroup, expected_ngroup)
+ tm.assert_series_equal(g_cumcount, expected_cumcount)
+
+ def test_ngroup_cumcount_pair(self):
+ # brute force comparison for all small series
+ for p in product(range(3), repeat=4):
+ df = DataFrame({"a": p})
+ g = df.groupby(["a"])
+
+ order = sorted(set(p))
+ ngroupd = [order.index(val) for val in p]
+ cumcounted = [p[:i].count(val) for i, val in enumerate(p)]
+
+ tm.assert_series_equal(g.ngroup(), Series(ngroupd))
+ tm.assert_series_equal(g.cumcount(), Series(cumcounted))
+
+ def test_ngroup_respects_groupby_order(self, sort):
+ df = DataFrame({"a": np.random.default_rng(2).choice(list("abcdef"), 100)})
+ g = df.groupby("a", sort=sort)
+ df["group_id"] = -1
+ df["group_index"] = -1
+
+ for i, (_, group) in enumerate(g):
+ df.loc[group.index, "group_id"] = i
+ for j, ind in enumerate(group.index):
+ df.loc[ind, "group_index"] = j
+
+ tm.assert_series_equal(Series(df["group_id"].values), g.ngroup())
+ tm.assert_series_equal(Series(df["group_index"].values), g.cumcount())
+
+ @pytest.mark.parametrize(
+ "datetimelike",
+ [
+ [Timestamp(f"2016-05-{i:02d} 20:09:25+00:00") for i in range(1, 4)],
+ [Timestamp(f"2016-05-{i:02d} 20:09:25") for i in range(1, 4)],
+ [Timestamp(f"2016-05-{i:02d} 20:09:25", tz="UTC") for i in range(1, 4)],
+ [Timedelta(x, unit="h") for x in range(1, 4)],
+ [Period(freq="2W", year=2017, month=x) for x in range(1, 4)],
+ ],
+ )
+ def test_count_with_datetimelike(self, datetimelike):
+ # test for #13393, where DataframeGroupBy.count() fails
+ # when counting a datetimelike column.
+
+ df = DataFrame({"x": ["a", "a", "b"], "y": datetimelike})
+ res = df.groupby("x").count()
+ expected = DataFrame({"y": [2, 1]}, index=["a", "b"])
+ expected.index.name = "x"
+ tm.assert_frame_equal(expected, res)
+
+ def test_count_with_only_nans_in_first_group(self):
+ # GH21956
+ df = DataFrame({"A": [np.nan, np.nan], "B": ["a", "b"], "C": [1, 2]})
+ result = df.groupby(["A", "B"]).C.count()
+ mi = MultiIndex(levels=[[], ["a", "b"]], codes=[[], []], names=["A", "B"])
+ expected = Series([], index=mi, dtype=np.int64, name="C")
+ tm.assert_series_equal(result, expected, check_index_type=False)
+
+ def test_count_groupby_column_with_nan_in_groupby_column(self):
+ # https://github.com/pandas-dev/pandas/issues/32841
+ df = DataFrame({"A": [1, 1, 1, 1, 1], "B": [5, 4, np.nan, 3, 0]})
+ res = df.groupby(["B"]).count()
+ expected = DataFrame(
+ index=Index([0.0, 3.0, 4.0, 5.0], name="B"), data={"A": [1, 1, 1, 1]}
+ )
+ tm.assert_frame_equal(expected, res)
+
+ def test_groupby_count_dateparseerror(self):
+ dr = date_range(start="1/1/2012", freq="5min", periods=10)
+
+ # BAD Example, datetimes first
+ ser = Series(np.arange(10), index=[dr, np.arange(10)])
+ grouped = ser.groupby(lambda x: x[1] % 2 == 0)
+ result = grouped.count()
+
+ ser = Series(np.arange(10), index=[np.arange(10), dr])
+ grouped = ser.groupby(lambda x: x[0] % 2 == 0)
+ expected = grouped.count()
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_timedelta_cython_count():
+ df = DataFrame(
+ {"g": list("ab" * 2), "delta": np.arange(4).astype("timedelta64[ns]")}
+ )
+ expected = Series([2, 2], index=Index(["a", "b"], name="g"), name="delta")
+ result = df.groupby("g").delta.count()
+ tm.assert_series_equal(expected, result)
+
+
+def test_count():
+ n = 1 << 15
+ dr = date_range("2015-08-30", periods=n // 10, freq="min")
+
+ df = DataFrame(
+ {
+ "1st": np.random.default_rng(2).choice(list(ascii_lowercase), n),
+ "2nd": np.random.default_rng(2).integers(0, 5, n),
+ "3rd": np.random.default_rng(2).standard_normal(n).round(3),
+ "4th": np.random.default_rng(2).integers(-10, 10, n),
+ "5th": np.random.default_rng(2).choice(dr, n),
+ "6th": np.random.default_rng(2).standard_normal(n).round(3),
+ "7th": np.random.default_rng(2).standard_normal(n).round(3),
+ "8th": np.random.default_rng(2).choice(dr, n)
+ - np.random.default_rng(2).choice(dr, 1),
+ "9th": np.random.default_rng(2).choice(list(ascii_lowercase), n),
+ }
+ )
+
+ for col in df.columns.drop(["1st", "2nd", "4th"]):
+ df.loc[np.random.default_rng(2).choice(n, n // 10), col] = np.nan
+
+ df["9th"] = df["9th"].astype("category")
+
+ for key in ["1st", "2nd", ["1st", "2nd"]]:
+ left = df.groupby(key).count()
+ right = df.groupby(key).apply(DataFrame.count)
+ tm.assert_frame_equal(left, right)
+
+
+def test_count_non_nulls():
+ # GH#5610
+ # count counts non-nulls
+ df = DataFrame(
+ [[1, 2, "foo"], [1, np.nan, "bar"], [3, np.nan, np.nan]],
+ columns=["A", "B", "C"],
+ )
+
+ count_as = df.groupby("A").count()
+ count_not_as = df.groupby("A", as_index=False).count()
+
+ expected = DataFrame([[1, 2], [0, 0]], columns=["B", "C"], index=[1, 3])
+ expected.index.name = "A"
+ tm.assert_frame_equal(count_not_as, expected.reset_index())
+ tm.assert_frame_equal(count_as, expected)
+
+ count_B = df.groupby("A")["B"].count()
+ tm.assert_series_equal(count_B, expected["B"])
+
+
+def test_count_object():
+ df = DataFrame({"a": ["a"] * 3 + ["b"] * 3, "c": [2] * 3 + [3] * 3})
+ result = df.groupby("c").a.count()
+ expected = Series([3, 3], index=Index([2, 3], name="c"), name="a")
+ tm.assert_series_equal(result, expected)
+
+
+def test_count_object_nan():
+ df = DataFrame({"a": ["a", np.nan, np.nan] + ["b"] * 3, "c": [2] * 3 + [3] * 3})
+ result = df.groupby("c").a.count()
+ expected = Series([1, 3], index=Index([2, 3], name="c"), name="a")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("typ", ["object", "float32"])
+def test_count_cross_type(typ):
+ # GH8169
+ # Set float64 dtype to avoid upcast when setting nan below
+ vals = np.hstack(
+ (
+ np.random.default_rng(2).integers(0, 5, (10, 2)),
+ np.random.default_rng(2).integers(0, 2, (10, 2)),
+ )
+ ).astype("float64")
+
+ df = DataFrame(vals, columns=["a", "b", "c", "d"])
+ df[df == 2] = np.nan
+ expected = df.groupby(["c", "d"]).count()
+
+ df["a"] = df["a"].astype(typ)
+ df["b"] = df["b"].astype(typ)
+ result = df.groupby(["c", "d"]).count()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_lower_int_prec_count():
+ df = DataFrame(
+ {
+ "a": np.array([0, 1, 2, 100], np.int8),
+ "b": np.array([1, 2, 3, 6], np.uint32),
+ "c": np.array([4, 5, 6, 8], np.int16),
+ "grp": list("ab" * 2),
+ }
+ )
+ result = df.groupby("grp").count()
+ expected = DataFrame(
+ {"a": [2, 2], "b": [2, 2], "c": [2, 2]}, index=Index(list("ab"), name="grp")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_count_uses_size_on_exception():
+ class RaisingObjectException(Exception):
+ pass
+
+ class RaisingObject:
+ def __init__(self, msg="I will raise inside Cython") -> None:
+ super().__init__()
+ self.msg = msg
+
+ def __eq__(self, other):
+ # gets called in Cython to check that raising calls the method
+ raise RaisingObjectException(self.msg)
+
+ df = DataFrame({"a": [RaisingObject() for _ in range(4)], "grp": list("ab" * 2)})
+ result = df.groupby("grp").count()
+ expected = DataFrame({"a": [2, 2]}, index=Index(list("ab"), name="grp"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_count_arrow_string_array(any_string_dtype):
+ # GH#54751
+ pytest.importorskip("pyarrow")
+ df = DataFrame(
+ {"a": [1, 2, 3], "b": Series(["a", "b", "a"], dtype=any_string_dtype)}
+ )
+ result = df.groupby("a").count()
+ expected = DataFrame({"b": 1}, index=Index([1, 2, 3], name="a"))
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_cumulative.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_cumulative.py
new file mode 100644
index 0000000000000000000000000000000000000000..cca4971e930b416fb5cbbacd704d6f171ebdfeee
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_cumulative.py
@@ -0,0 +1,332 @@
+import numpy as np
+import pytest
+
+from pandas.errors import UnsupportedFunctionCall
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture(
+ params=[np.int32, np.int64, np.float32, np.float64, "Int64", "Float64"],
+ ids=["np.int32", "np.int64", "np.float32", "np.float64", "Int64", "Float64"],
+)
+def dtypes_for_minmax(request):
+ """
+ Fixture of dtypes with min and max values used for testing
+ cummin and cummax
+ """
+ dtype = request.param
+
+ np_type = dtype
+ if dtype == "Int64":
+ np_type = np.int64
+ elif dtype == "Float64":
+ np_type = np.float64
+
+ min_val = (
+ np.iinfo(np_type).min
+ if np.dtype(np_type).kind == "i"
+ else np.finfo(np_type).min
+ )
+ max_val = (
+ np.iinfo(np_type).max
+ if np.dtype(np_type).kind == "i"
+ else np.finfo(np_type).max
+ )
+
+ return (dtype, min_val, max_val)
+
+
+def test_groupby_cumprod():
+ # GH 4095
+ df = DataFrame({"key": ["b"] * 10, "value": 2})
+
+ actual = df.groupby("key")["value"].cumprod()
+ expected = df.groupby("key", group_keys=False)["value"].apply(lambda x: x.cumprod())
+ expected.name = "value"
+ tm.assert_series_equal(actual, expected)
+
+ df = DataFrame({"key": ["b"] * 100, "value": 2})
+ df["value"] = df["value"].astype(float)
+ actual = df.groupby("key")["value"].cumprod()
+ expected = df.groupby("key", group_keys=False)["value"].apply(lambda x: x.cumprod())
+ expected.name = "value"
+ tm.assert_series_equal(actual, expected)
+
+
+def test_groupby_cumprod_overflow():
+ # GH#37493 if we overflow we return garbage consistent with numpy
+ df = DataFrame({"key": ["b"] * 4, "value": 100_000})
+ actual = df.groupby("key")["value"].cumprod()
+ expected = Series(
+ [100_000, 10_000_000_000, 1_000_000_000_000_000, 7766279631452241920],
+ name="value",
+ )
+ tm.assert_series_equal(actual, expected)
+
+ numpy_result = df.groupby("key", group_keys=False)["value"].apply(
+ lambda x: x.cumprod()
+ )
+ numpy_result.name = "value"
+ tm.assert_series_equal(actual, numpy_result)
+
+
+def test_groupby_cumprod_nan_influences_other_columns():
+ # GH#48064
+ df = DataFrame(
+ {
+ "a": 1,
+ "b": [1, np.nan, 2],
+ "c": [1, 2, 3.0],
+ }
+ )
+ result = df.groupby("a").cumprod(numeric_only=True, skipna=False)
+ expected = DataFrame({"b": [1, np.nan, np.nan], "c": [1, 2, 6.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cummin(dtypes_for_minmax):
+ dtype = dtypes_for_minmax[0]
+
+ # GH 15048
+ base_df = DataFrame({"A": [1, 1, 1, 1, 2, 2, 2, 2], "B": [3, 4, 3, 2, 2, 3, 2, 1]})
+ expected_mins = [3, 3, 3, 2, 2, 2, 2, 1]
+
+ df = base_df.astype(dtype)
+ expected = DataFrame({"B": expected_mins}).astype(dtype)
+ result = df.groupby("A").cummin()
+ tm.assert_frame_equal(result, expected)
+ result = df.groupby("A", group_keys=False).B.apply(lambda x: x.cummin()).to_frame()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cummin_min_value_for_dtype(dtypes_for_minmax):
+ dtype = dtypes_for_minmax[0]
+ min_val = dtypes_for_minmax[1]
+
+ # GH 15048
+ base_df = DataFrame({"A": [1, 1, 1, 1, 2, 2, 2, 2], "B": [3, 4, 3, 2, 2, 3, 2, 1]})
+ expected_mins = [3, 3, 3, 2, 2, 2, 2, 1]
+ expected = DataFrame({"B": expected_mins}).astype(dtype)
+ df = base_df.astype(dtype)
+ df.loc[[2, 6], "B"] = min_val
+ df.loc[[1, 5], "B"] = min_val + 1
+ expected.loc[[2, 3, 6, 7], "B"] = min_val
+ expected.loc[[1, 5], "B"] = min_val + 1 # should not be rounded to min_val
+ result = df.groupby("A").cummin()
+ tm.assert_frame_equal(result, expected, check_exact=True)
+ expected = (
+ df.groupby("A", group_keys=False).B.apply(lambda x: x.cummin()).to_frame()
+ )
+ tm.assert_frame_equal(result, expected, check_exact=True)
+
+
+def test_cummin_nan_in_some_values(dtypes_for_minmax):
+ # Explicit cast to float to avoid implicit cast when setting nan
+ base_df = DataFrame({"A": [1, 1, 1, 1, 2, 2, 2, 2], "B": [3, 4, 3, 2, 2, 3, 2, 1]})
+ base_df = base_df.astype({"B": "float"})
+ base_df.loc[[0, 2, 4, 6], "B"] = np.nan
+ expected = DataFrame({"B": [np.nan, 4, np.nan, 2, np.nan, 3, np.nan, 1]})
+ result = base_df.groupby("A").cummin()
+ tm.assert_frame_equal(result, expected)
+ expected = (
+ base_df.groupby("A", group_keys=False).B.apply(lambda x: x.cummin()).to_frame()
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cummin_datetime():
+ # GH 15561
+ df = DataFrame({"a": [1], "b": pd.to_datetime(["2001"])})
+ expected = Series(pd.to_datetime("2001"), index=[0], name="b")
+
+ result = df.groupby("a")["b"].cummin()
+ tm.assert_series_equal(expected, result)
+
+
+def test_cummin_getattr_series():
+ # GH 15635
+ df = DataFrame({"a": [1, 2, 1], "b": [1, 2, 2]})
+ result = df.groupby("a").b.cummin()
+ expected = Series([1, 2, 1], name="b")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["cummin", "cummax"])
+@pytest.mark.parametrize("dtype", ["UInt64", "Int64", "Float64", "float", "boolean"])
+def test_cummin_max_all_nan_column(method, dtype):
+ item = np.nan if dtype == "float" else pd.NA
+ base_df = DataFrame({"A": [1, 1, 1, 1, 2, 2, 2, 2], "B": [item] * 8})
+ base_df["B"] = base_df["B"].astype(dtype)
+ grouped = base_df.groupby("A")
+
+ expected = DataFrame({"B": [item] * 8}, dtype=dtype)
+ result = getattr(grouped, method)()
+ tm.assert_frame_equal(expected, result)
+
+ result = getattr(grouped["B"], method)().to_frame()
+ tm.assert_frame_equal(expected, result)
+
+
+def test_cummax(dtypes_for_minmax):
+ dtype = dtypes_for_minmax[0]
+
+ # GH 15048
+ base_df = DataFrame({"A": [1, 1, 1, 1, 2, 2, 2, 2], "B": [3, 4, 3, 2, 2, 3, 2, 1]})
+ expected_maxs = [3, 4, 4, 4, 2, 3, 3, 3]
+
+ df = base_df.astype(dtype)
+
+ expected = DataFrame({"B": expected_maxs}).astype(dtype)
+ result = df.groupby("A").cummax()
+ tm.assert_frame_equal(result, expected)
+ result = df.groupby("A", group_keys=False).B.apply(lambda x: x.cummax()).to_frame()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cummax_min_value_for_dtype(dtypes_for_minmax):
+ dtype = dtypes_for_minmax[0]
+ max_val = dtypes_for_minmax[2]
+
+ # GH 15048
+ base_df = DataFrame({"A": [1, 1, 1, 1, 2, 2, 2, 2], "B": [3, 4, 3, 2, 2, 3, 2, 1]})
+ expected_maxs = [3, 4, 4, 4, 2, 3, 3, 3]
+
+ df = base_df.astype(dtype)
+ df.loc[[2, 6], "B"] = max_val
+ expected = DataFrame({"B": expected_maxs}).astype(dtype)
+ expected.loc[[2, 3, 6, 7], "B"] = max_val
+ result = df.groupby("A").cummax()
+ tm.assert_frame_equal(result, expected)
+ expected = (
+ df.groupby("A", group_keys=False).B.apply(lambda x: x.cummax()).to_frame()
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cummax_nan_in_some_values(dtypes_for_minmax):
+ # Test nan in some values
+ # Explicit cast to float to avoid implicit cast when setting nan
+ base_df = DataFrame({"A": [1, 1, 1, 1, 2, 2, 2, 2], "B": [3, 4, 3, 2, 2, 3, 2, 1]})
+ base_df = base_df.astype({"B": "float"})
+ base_df.loc[[0, 2, 4, 6], "B"] = np.nan
+ expected = DataFrame({"B": [np.nan, 4, np.nan, 4, np.nan, 3, np.nan, 3]})
+ result = base_df.groupby("A").cummax()
+ tm.assert_frame_equal(result, expected)
+ expected = (
+ base_df.groupby("A", group_keys=False).B.apply(lambda x: x.cummax()).to_frame()
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cummax_datetime():
+ # GH 15561
+ df = DataFrame({"a": [1], "b": pd.to_datetime(["2001"])})
+ expected = Series(pd.to_datetime("2001"), index=[0], name="b")
+
+ result = df.groupby("a")["b"].cummax()
+ tm.assert_series_equal(expected, result)
+
+
+def test_cummax_getattr_series():
+ # GH 15635
+ df = DataFrame({"a": [1, 2, 1], "b": [2, 1, 1]})
+ result = df.groupby("a").b.cummax()
+ expected = Series([2, 1, 2], name="b")
+ tm.assert_series_equal(result, expected)
+
+
+def test_cummax_i8_at_implementation_bound():
+ # the minimum value used to be treated as NPY_NAT+1 instead of NPY_NAT
+ # for int64 dtype GH#46382
+ ser = Series([pd.NaT._value + n for n in range(5)])
+ df = DataFrame({"A": 1, "B": ser, "C": ser._values.view("M8[ns]")})
+ gb = df.groupby("A")
+
+ res = gb.cummax()
+ exp = df[["B", "C"]]
+ tm.assert_frame_equal(res, exp)
+
+
+@pytest.mark.parametrize("method", ["cummin", "cummax"])
+@pytest.mark.parametrize("dtype", ["float", "Int64", "Float64"])
+@pytest.mark.parametrize(
+ "groups,expected_data",
+ [
+ ([1, 1, 1], [1, None, None]),
+ ([1, 2, 3], [1, None, 2]),
+ ([1, 3, 3], [1, None, None]),
+ ],
+)
+def test_cummin_max_skipna(method, dtype, groups, expected_data):
+ # GH-34047
+ df = DataFrame({"a": Series([1, None, 2], dtype=dtype)})
+ orig = df.copy()
+ gb = df.groupby(groups)["a"]
+
+ result = getattr(gb, method)(skipna=False)
+ expected = Series(expected_data, dtype=dtype, name="a")
+
+ # check we didn't accidentally alter df
+ tm.assert_frame_equal(df, orig)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["cummin", "cummax"])
+def test_cummin_max_skipna_multiple_cols(method):
+ # Ensure missing value in "a" doesn't cause "b" to be nan-filled
+ df = DataFrame({"a": [np.nan, 2.0, 2.0], "b": [2.0, 2.0, 2.0]})
+ gb = df.groupby([1, 1, 1])[["a", "b"]]
+
+ result = getattr(gb, method)(skipna=False)
+ expected = DataFrame({"a": [np.nan, np.nan, np.nan], "b": [2.0, 2.0, 2.0]})
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["cumprod", "cumsum"])
+def test_numpy_compat(func):
+ # see gh-12811
+ df = DataFrame({"A": [1, 2, 1], "B": [1, 2, 3]})
+ g = df.groupby("A")
+
+ msg = "numpy operations are not valid with groupby"
+
+ with pytest.raises(UnsupportedFunctionCall, match=msg):
+ getattr(g, func)(1, 2, 3)
+ with pytest.raises(UnsupportedFunctionCall, match=msg):
+ getattr(g, func)(foo=1)
+
+
+@td.skip_if_32bit
+@pytest.mark.parametrize("method", ["cummin", "cummax"])
+@pytest.mark.parametrize(
+ "dtype,val", [("UInt64", np.iinfo("uint64").max), ("Int64", 2**53 + 1)]
+)
+def test_nullable_int_not_cast_as_float(method, dtype, val):
+ data = [val, pd.NA]
+ df = DataFrame({"grp": [1, 1], "b": data}, dtype=dtype)
+ grouped = df.groupby("grp")
+
+ result = grouped.transform(method)
+ expected = DataFrame({"b": data}, dtype=dtype)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cython_api2(as_index):
+ # this takes the fast apply path
+
+ # cumsum (GH5614)
+ # GH 5755 - cumsum is a transformer and should ignore as_index
+ df = DataFrame([[1, 2, np.nan], [1, np.nan, 9], [3, 4, 9]], columns=["A", "B", "C"])
+ expected = DataFrame([[2, np.nan], [np.nan, 9], [4, 9]], columns=["B", "C"])
+ result = df.groupby("A", as_index=as_index).cumsum()
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_filters.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_filters.py
new file mode 100644
index 0000000000000000000000000000000000000000..c20fc9e3d62e77949383e9fd47080d0e23f94875
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_filters.py
@@ -0,0 +1,638 @@
+from string import ascii_lowercase
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+
+
+def test_filter_series():
+ s = Series([1, 3, 20, 5, 22, 24, 7])
+ expected_odd = Series([1, 3, 5, 7], index=[0, 1, 3, 6])
+ expected_even = Series([20, 22, 24], index=[2, 4, 5])
+ grouper = s.apply(lambda x: x % 2)
+ grouped = s.groupby(grouper)
+ tm.assert_series_equal(grouped.filter(lambda x: x.mean() < 10), expected_odd)
+ tm.assert_series_equal(grouped.filter(lambda x: x.mean() > 10), expected_even)
+ # Test dropna=False.
+ tm.assert_series_equal(
+ grouped.filter(lambda x: x.mean() < 10, dropna=False),
+ expected_odd.reindex(s.index),
+ )
+ tm.assert_series_equal(
+ grouped.filter(lambda x: x.mean() > 10, dropna=False),
+ expected_even.reindex(s.index),
+ )
+
+
+def test_filter_single_column_df():
+ df = DataFrame([1, 3, 20, 5, 22, 24, 7])
+ expected_odd = DataFrame([1, 3, 5, 7], index=[0, 1, 3, 6])
+ expected_even = DataFrame([20, 22, 24], index=[2, 4, 5])
+ grouper = df[0].apply(lambda x: x % 2)
+ grouped = df.groupby(grouper)
+ tm.assert_frame_equal(grouped.filter(lambda x: x.mean() < 10), expected_odd)
+ tm.assert_frame_equal(grouped.filter(lambda x: x.mean() > 10), expected_even)
+ # Test dropna=False.
+ tm.assert_frame_equal(
+ grouped.filter(lambda x: x.mean() < 10, dropna=False),
+ expected_odd.reindex(df.index),
+ )
+ tm.assert_frame_equal(
+ grouped.filter(lambda x: x.mean() > 10, dropna=False),
+ expected_even.reindex(df.index),
+ )
+
+
+def test_filter_multi_column_df():
+ df = DataFrame({"A": [1, 12, 12, 1], "B": [1, 1, 1, 1]})
+ grouper = df["A"].apply(lambda x: x % 2)
+ grouped = df.groupby(grouper)
+ expected = DataFrame({"A": [12, 12], "B": [1, 1]}, index=[1, 2])
+ tm.assert_frame_equal(
+ grouped.filter(lambda x: x["A"].sum() - x["B"].sum() > 10), expected
+ )
+
+
+def test_filter_mixed_df():
+ df = DataFrame({"A": [1, 12, 12, 1], "B": "a b c d".split()})
+ grouper = df["A"].apply(lambda x: x % 2)
+ grouped = df.groupby(grouper)
+ expected = DataFrame({"A": [12, 12], "B": ["b", "c"]}, index=[1, 2])
+ tm.assert_frame_equal(grouped.filter(lambda x: x["A"].sum() > 10), expected)
+
+
+def test_filter_out_all_groups():
+ s = Series([1, 3, 20, 5, 22, 24, 7])
+ grouper = s.apply(lambda x: x % 2)
+ grouped = s.groupby(grouper)
+ tm.assert_series_equal(grouped.filter(lambda x: x.mean() > 1000), s[[]])
+ df = DataFrame({"A": [1, 12, 12, 1], "B": "a b c d".split()})
+ grouper = df["A"].apply(lambda x: x % 2)
+ grouped = df.groupby(grouper)
+ tm.assert_frame_equal(grouped.filter(lambda x: x["A"].sum() > 1000), df.loc[[]])
+
+
+def test_filter_out_no_groups():
+ s = Series([1, 3, 20, 5, 22, 24, 7])
+ grouper = s.apply(lambda x: x % 2)
+ grouped = s.groupby(grouper)
+ filtered = grouped.filter(lambda x: x.mean() > 0)
+ tm.assert_series_equal(filtered, s)
+
+
+def test_filter_out_no_groups_dataframe():
+ df = DataFrame({"A": [1, 12, 12, 1], "B": "a b c d".split()})
+ grouper = df["A"].apply(lambda x: x % 2)
+ grouped = df.groupby(grouper)
+ filtered = grouped.filter(lambda x: x["A"].mean() > 0)
+ tm.assert_frame_equal(filtered, df)
+
+
+def test_filter_out_all_groups_in_df():
+ # GH12768
+ df = DataFrame({"a": [1, 1, 2], "b": [1, 2, 0]})
+ res = df.groupby("a")
+ res = res.filter(lambda x: x["b"].sum() > 5, dropna=False)
+ expected = DataFrame({"a": [np.nan] * 3, "b": [np.nan] * 3})
+ tm.assert_frame_equal(expected, res)
+
+
+def test_filter_out_all_groups_in_df_dropna_true():
+ # GH12768
+ df = DataFrame({"a": [1, 1, 2], "b": [1, 2, 0]})
+ res = df.groupby("a")
+ res = res.filter(lambda x: x["b"].sum() > 5, dropna=True)
+ expected = DataFrame({"a": [], "b": []}, dtype="int64")
+ tm.assert_frame_equal(expected, res)
+
+
+def test_filter_condition_raises():
+ def raise_if_sum_is_zero(x):
+ if x.sum() == 0:
+ raise ValueError
+ return x.sum() > 0
+
+ s = Series([-1, 0, 1, 2])
+ grouper = s.apply(lambda x: x % 2)
+ grouped = s.groupby(grouper)
+ msg = "the filter must return a boolean result"
+ with pytest.raises(TypeError, match=msg):
+ grouped.filter(raise_if_sum_is_zero)
+
+
+def test_filter_bad_shapes():
+ df = DataFrame({"A": np.arange(8), "B": list("aabbbbcc"), "C": np.arange(8)})
+ s = df["B"]
+ g_df = df.groupby("B")
+ g_s = s.groupby(s)
+
+ f = lambda x: x
+ msg = "filter function returned a DataFrame, but expected a scalar bool"
+ with pytest.raises(TypeError, match=msg):
+ g_df.filter(f)
+ msg = "the filter must return a boolean result"
+ with pytest.raises(TypeError, match=msg):
+ g_s.filter(f)
+
+ f = lambda x: x == 1
+ msg = "filter function returned a DataFrame, but expected a scalar bool"
+ with pytest.raises(TypeError, match=msg):
+ g_df.filter(f)
+ msg = "the filter must return a boolean result"
+ with pytest.raises(TypeError, match=msg):
+ g_s.filter(f)
+
+ f = lambda x: np.outer(x, x)
+ msg = "can't multiply sequence by non-int of type 'str'"
+ with pytest.raises(TypeError, match=msg):
+ g_df.filter(f)
+ msg = "the filter must return a boolean result"
+ with pytest.raises(TypeError, match=msg):
+ g_s.filter(f)
+
+
+def test_filter_nan_is_false():
+ df = DataFrame({"A": np.arange(8), "B": list("aabbbbcc"), "C": np.arange(8)})
+ s = df["B"]
+ g_df = df.groupby(df["B"])
+ g_s = s.groupby(s)
+
+ f = lambda x: np.nan
+ tm.assert_frame_equal(g_df.filter(f), df.loc[[]])
+ tm.assert_series_equal(g_s.filter(f), s[[]])
+
+
+def test_filter_pdna_is_false():
+ # in particular, dont raise in filter trying to call bool(pd.NA)
+ df = DataFrame({"A": np.arange(8), "B": list("aabbbbcc"), "C": np.arange(8)})
+ ser = df["B"]
+ g_df = df.groupby(df["B"])
+ g_s = ser.groupby(ser)
+
+ func = lambda x: pd.NA
+ res = g_df.filter(func)
+ tm.assert_frame_equal(res, df.loc[[]])
+ res = g_s.filter(func)
+ tm.assert_series_equal(res, ser[[]])
+
+
+def test_filter_against_workaround_ints():
+ # Series of ints
+ s = Series(np.random.default_rng(2).integers(0, 100, 10))
+ grouper = s.apply(lambda x: np.round(x, -1))
+ grouped = s.groupby(grouper)
+ f = lambda x: x.mean() > 10
+
+ old_way = s[grouped.transform(f).astype("bool")]
+ new_way = grouped.filter(f)
+ tm.assert_series_equal(new_way.sort_values(), old_way.sort_values())
+
+
+def test_filter_against_workaround_floats():
+ # Series of floats
+ s = 100 * Series(np.random.default_rng(2).random(10))
+ grouper = s.apply(lambda x: np.round(x, -1))
+ grouped = s.groupby(grouper)
+ f = lambda x: x.mean() > 10
+ old_way = s[grouped.transform(f).astype("bool")]
+ new_way = grouped.filter(f)
+ tm.assert_series_equal(new_way.sort_values(), old_way.sort_values())
+
+
+def test_filter_against_workaround_dataframe():
+ # Set up DataFrame of ints, floats, strings.
+ letters = np.array(list(ascii_lowercase))
+ N = 10
+ random_letters = letters.take(
+ np.random.default_rng(2).integers(0, 26, N, dtype=int)
+ )
+ df = DataFrame(
+ {
+ "ints": Series(np.random.default_rng(2).integers(0, 10, N)),
+ "floats": N / 10 * Series(np.random.default_rng(2).random(N)),
+ "letters": Series(random_letters),
+ }
+ )
+
+ # Group by ints; filter on floats.
+ grouped = df.groupby("ints")
+ old_way = df[grouped.floats.transform(lambda x: x.mean() > N / 2).astype("bool")]
+ new_way = grouped.filter(lambda x: x["floats"].mean() > N / 2)
+ tm.assert_frame_equal(new_way, old_way)
+
+ # Group by floats (rounded); filter on strings.
+ grouper = df.floats.apply(lambda x: np.round(x, -1))
+ grouped = df.groupby(grouper)
+ old_way = df[grouped.letters.transform(lambda x: len(x) < N / 2).astype("bool")]
+ new_way = grouped.filter(lambda x: len(x.letters) < N / 2)
+ tm.assert_frame_equal(new_way, old_way)
+
+ # Group by strings; filter on ints.
+ grouped = df.groupby("letters")
+ old_way = df[grouped.ints.transform(lambda x: x.mean() > N / 2).astype("bool")]
+ new_way = grouped.filter(lambda x: x["ints"].mean() > N / 2)
+ tm.assert_frame_equal(new_way, old_way)
+
+
+def test_filter_using_len():
+ # GH 4447
+ df = DataFrame({"A": np.arange(8), "B": list("aabbbbcc"), "C": np.arange(8)})
+ grouped = df.groupby("B")
+ actual = grouped.filter(lambda x: len(x) > 2)
+ expected = DataFrame(
+ {"A": np.arange(2, 6), "B": list("bbbb"), "C": np.arange(2, 6)},
+ index=range(2, 6),
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ actual = grouped.filter(lambda x: len(x) > 4)
+ expected = df.loc[[]]
+ tm.assert_frame_equal(actual, expected)
+
+
+def test_filter_using_len_series():
+ # GH 4447
+ s = Series(list("aabbbbcc"), name="B")
+ grouped = s.groupby(s)
+ actual = grouped.filter(lambda x: len(x) > 2)
+ expected = Series(4 * ["b"], index=range(2, 6), name="B")
+ tm.assert_series_equal(actual, expected)
+
+ actual = grouped.filter(lambda x: len(x) > 4)
+ expected = s[[]]
+ tm.assert_series_equal(actual, expected)
+
+
+@pytest.mark.parametrize(
+ "index", [range(8), range(7, -1, -1), [0, 2, 1, 3, 4, 6, 5, 7]]
+)
+def test_filter_maintains_ordering(index):
+ # GH 4621
+ df = DataFrame(
+ {"pid": [1, 1, 1, 2, 2, 3, 3, 3], "tag": [23, 45, 62, 24, 45, 34, 25, 62]},
+ index=index,
+ )
+ s = df["pid"]
+ grouped = df.groupby("tag")
+ actual = grouped.filter(lambda x: len(x) > 1)
+ expected = df.iloc[[1, 2, 4, 7]]
+ tm.assert_frame_equal(actual, expected)
+
+ grouped = s.groupby(df["tag"])
+ actual = grouped.filter(lambda x: len(x) > 1)
+ expected = s.iloc[[1, 2, 4, 7]]
+ tm.assert_series_equal(actual, expected)
+
+
+def test_filter_multiple_timestamp():
+ # GH 10114
+ df = DataFrame(
+ {
+ "A": np.arange(5, dtype="int64"),
+ "B": ["foo", "bar", "foo", "bar", "bar"],
+ "C": Timestamp("20130101"),
+ }
+ )
+
+ grouped = df.groupby(["B", "C"])
+
+ result = grouped["A"].filter(lambda x: True)
+ tm.assert_series_equal(df["A"], result)
+
+ result = grouped["A"].transform(len)
+ expected = Series([2, 3, 2, 3, 3], name="A")
+ tm.assert_series_equal(result, expected)
+
+ result = grouped.filter(lambda x: True)
+ tm.assert_frame_equal(df, result)
+
+ result = grouped.transform("sum")
+ expected = DataFrame({"A": [2, 8, 2, 8, 8]})
+ tm.assert_frame_equal(result, expected)
+
+ result = grouped.transform(len)
+ expected = DataFrame({"A": [2, 3, 2, 3, 3]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_filter_and_transform_with_non_unique_int_index():
+ # GH4620
+ index = [1, 1, 1, 2, 1, 1, 0, 1]
+ df = DataFrame(
+ {"pid": [1, 1, 1, 2, 2, 3, 3, 3], "tag": [23, 45, 62, 24, 45, 34, 25, 62]},
+ index=index,
+ )
+ grouped_df = df.groupby("tag")
+ ser = df["pid"]
+ grouped_ser = ser.groupby(df["tag"])
+ expected_indexes = [1, 2, 4, 7]
+
+ # Filter DataFrame
+ actual = grouped_df.filter(lambda x: len(x) > 1)
+ expected = df.iloc[expected_indexes]
+ tm.assert_frame_equal(actual, expected)
+
+ actual = grouped_df.filter(lambda x: len(x) > 1, dropna=False)
+ # Cast to avoid upcast when setting nan below
+ expected = df.copy().astype("float64")
+ expected.iloc[[0, 3, 5, 6]] = np.nan
+ tm.assert_frame_equal(actual, expected)
+
+ # Filter Series
+ actual = grouped_ser.filter(lambda x: len(x) > 1)
+ expected = ser.take(expected_indexes)
+ tm.assert_series_equal(actual, expected)
+
+ actual = grouped_ser.filter(lambda x: len(x) > 1, dropna=False)
+ expected = Series([np.nan, 1, 1, np.nan, 2, np.nan, np.nan, 3], index, name="pid")
+ # ^ made manually because this can get confusing!
+ tm.assert_series_equal(actual, expected)
+
+ # Transform Series
+ actual = grouped_ser.transform(len)
+ expected = Series([1, 2, 2, 1, 2, 1, 1, 2], index, name="pid")
+ tm.assert_series_equal(actual, expected)
+
+ # Transform (a column from) DataFrameGroupBy
+ actual = grouped_df.pid.transform(len)
+ tm.assert_series_equal(actual, expected)
+
+
+def test_filter_and_transform_with_multiple_non_unique_int_index():
+ # GH4620
+ index = [1, 1, 1, 2, 0, 0, 0, 1]
+ df = DataFrame(
+ {"pid": [1, 1, 1, 2, 2, 3, 3, 3], "tag": [23, 45, 62, 24, 45, 34, 25, 62]},
+ index=index,
+ )
+ grouped_df = df.groupby("tag")
+ ser = df["pid"]
+ grouped_ser = ser.groupby(df["tag"])
+ expected_indexes = [1, 2, 4, 7]
+
+ # Filter DataFrame
+ actual = grouped_df.filter(lambda x: len(x) > 1)
+ expected = df.iloc[expected_indexes]
+ tm.assert_frame_equal(actual, expected)
+
+ actual = grouped_df.filter(lambda x: len(x) > 1, dropna=False)
+ # Cast to avoid upcast when setting nan below
+ expected = df.copy().astype("float64")
+ expected.iloc[[0, 3, 5, 6]] = np.nan
+ tm.assert_frame_equal(actual, expected)
+
+ # Filter Series
+ actual = grouped_ser.filter(lambda x: len(x) > 1)
+ expected = ser.take(expected_indexes)
+ tm.assert_series_equal(actual, expected)
+
+ actual = grouped_ser.filter(lambda x: len(x) > 1, dropna=False)
+ expected = Series([np.nan, 1, 1, np.nan, 2, np.nan, np.nan, 3], index, name="pid")
+ # ^ made manually because this can get confusing!
+ tm.assert_series_equal(actual, expected)
+
+ # Transform Series
+ actual = grouped_ser.transform(len)
+ expected = Series([1, 2, 2, 1, 2, 1, 1, 2], index, name="pid")
+ tm.assert_series_equal(actual, expected)
+
+ # Transform (a column from) DataFrameGroupBy
+ actual = grouped_df.pid.transform(len)
+ tm.assert_series_equal(actual, expected)
+
+
+def test_filter_and_transform_with_non_unique_float_index():
+ # GH4620
+ index = np.array([1, 1, 1, 2, 1, 1, 0, 1], dtype=float)
+ df = DataFrame(
+ {"pid": [1, 1, 1, 2, 2, 3, 3, 3], "tag": [23, 45, 62, 24, 45, 34, 25, 62]},
+ index=index,
+ )
+ grouped_df = df.groupby("tag")
+ ser = df["pid"]
+ grouped_ser = ser.groupby(df["tag"])
+ expected_indexes = [1, 2, 4, 7]
+
+ # Filter DataFrame
+ actual = grouped_df.filter(lambda x: len(x) > 1)
+ expected = df.iloc[expected_indexes]
+ tm.assert_frame_equal(actual, expected)
+
+ actual = grouped_df.filter(lambda x: len(x) > 1, dropna=False)
+ # Cast to avoid upcast when setting nan below
+ expected = df.copy().astype("float64")
+ expected.iloc[[0, 3, 5, 6]] = np.nan
+ tm.assert_frame_equal(actual, expected)
+
+ # Filter Series
+ actual = grouped_ser.filter(lambda x: len(x) > 1)
+ expected = ser.take(expected_indexes)
+ tm.assert_series_equal(actual, expected)
+
+ actual = grouped_ser.filter(lambda x: len(x) > 1, dropna=False)
+ expected = Series([np.nan, 1, 1, np.nan, 2, np.nan, np.nan, 3], index, name="pid")
+ # ^ made manually because this can get confusing!
+ tm.assert_series_equal(actual, expected)
+
+ # Transform Series
+ actual = grouped_ser.transform(len)
+ expected = Series([1, 2, 2, 1, 2, 1, 1, 2], index, name="pid")
+ tm.assert_series_equal(actual, expected)
+
+ # Transform (a column from) DataFrameGroupBy
+ actual = grouped_df.pid.transform(len)
+ tm.assert_series_equal(actual, expected)
+
+
+def test_filter_and_transform_with_non_unique_timestamp_index():
+ # GH4620
+ t0 = Timestamp("2013-09-30 00:05:00")
+ t1 = Timestamp("2013-10-30 00:05:00")
+ t2 = Timestamp("2013-11-30 00:05:00")
+ index = [t1, t1, t1, t2, t1, t1, t0, t1]
+ df = DataFrame(
+ {"pid": [1, 1, 1, 2, 2, 3, 3, 3], "tag": [23, 45, 62, 24, 45, 34, 25, 62]},
+ index=index,
+ )
+ grouped_df = df.groupby("tag")
+ ser = df["pid"]
+ grouped_ser = ser.groupby(df["tag"])
+ expected_indexes = [1, 2, 4, 7]
+
+ # Filter DataFrame
+ actual = grouped_df.filter(lambda x: len(x) > 1)
+ expected = df.iloc[expected_indexes]
+ tm.assert_frame_equal(actual, expected)
+
+ actual = grouped_df.filter(lambda x: len(x) > 1, dropna=False)
+ # Cast to avoid upcast when setting nan below
+ expected = df.copy().astype("float64")
+ expected.iloc[[0, 3, 5, 6]] = np.nan
+ tm.assert_frame_equal(actual, expected)
+
+ # Filter Series
+ actual = grouped_ser.filter(lambda x: len(x) > 1)
+ expected = ser.take(expected_indexes)
+ tm.assert_series_equal(actual, expected)
+
+ actual = grouped_ser.filter(lambda x: len(x) > 1, dropna=False)
+ expected = Series([np.nan, 1, 1, np.nan, 2, np.nan, np.nan, 3], index, name="pid")
+ # ^ made manually because this can get confusing!
+ tm.assert_series_equal(actual, expected)
+
+ # Transform Series
+ actual = grouped_ser.transform(len)
+ expected = Series([1, 2, 2, 1, 2, 1, 1, 2], index, name="pid")
+ tm.assert_series_equal(actual, expected)
+
+ # Transform (a column from) DataFrameGroupBy
+ actual = grouped_df.pid.transform(len)
+ tm.assert_series_equal(actual, expected)
+
+
+def test_filter_and_transform_with_non_unique_string_index():
+ # GH4620
+ index = list("bbbcbbab")
+ df = DataFrame(
+ {"pid": [1, 1, 1, 2, 2, 3, 3, 3], "tag": [23, 45, 62, 24, 45, 34, 25, 62]},
+ index=index,
+ )
+ grouped_df = df.groupby("tag")
+ ser = df["pid"]
+ grouped_ser = ser.groupby(df["tag"])
+ expected_indexes = [1, 2, 4, 7]
+
+ # Filter DataFrame
+ actual = grouped_df.filter(lambda x: len(x) > 1)
+ expected = df.iloc[expected_indexes]
+ tm.assert_frame_equal(actual, expected)
+
+ actual = grouped_df.filter(lambda x: len(x) > 1, dropna=False)
+ # Cast to avoid upcast when setting nan below
+ expected = df.copy().astype("float64")
+ expected.iloc[[0, 3, 5, 6]] = np.nan
+ tm.assert_frame_equal(actual, expected)
+
+ # Filter Series
+ actual = grouped_ser.filter(lambda x: len(x) > 1)
+ expected = ser.take(expected_indexes)
+ tm.assert_series_equal(actual, expected)
+
+ actual = grouped_ser.filter(lambda x: len(x) > 1, dropna=False)
+ expected = Series([np.nan, 1, 1, np.nan, 2, np.nan, np.nan, 3], index, name="pid")
+ # ^ made manually because this can get confusing!
+ tm.assert_series_equal(actual, expected)
+
+ # Transform Series
+ actual = grouped_ser.transform(len)
+ expected = Series([1, 2, 2, 1, 2, 1, 1, 2], index, name="pid")
+ tm.assert_series_equal(actual, expected)
+
+ # Transform (a column from) DataFrameGroupBy
+ actual = grouped_df.pid.transform(len)
+ tm.assert_series_equal(actual, expected)
+
+
+def test_filter_has_access_to_grouped_cols():
+ df = DataFrame([[1, 2], [1, 3], [5, 6]], columns=["A", "B"])
+ g = df.groupby("A")
+ # previously didn't have access to col A #????
+ filt = g.filter(lambda x: x["A"].sum() == 2)
+ tm.assert_frame_equal(filt, df.iloc[[0, 1]])
+
+
+def test_filter_enforces_scalarness():
+ df = DataFrame(
+ [
+ ["best", "a", "x"],
+ ["worst", "b", "y"],
+ ["best", "c", "x"],
+ ["best", "d", "y"],
+ ["worst", "d", "y"],
+ ["worst", "d", "y"],
+ ["best", "d", "z"],
+ ],
+ columns=["a", "b", "c"],
+ )
+ with pytest.raises(TypeError, match="filter function returned a.*"):
+ df.groupby("c").filter(lambda g: g["a"] == "best")
+
+
+def test_filter_non_bool_raises():
+ df = DataFrame(
+ [
+ ["best", "a", 1],
+ ["worst", "b", 1],
+ ["best", "c", 1],
+ ["best", "d", 1],
+ ["worst", "d", 1],
+ ["worst", "d", 1],
+ ["best", "d", 1],
+ ],
+ columns=["a", "b", "c"],
+ )
+ with pytest.raises(TypeError, match="filter function returned a.*"):
+ df.groupby("a").filter(lambda g: g.c.mean())
+
+
+def test_filter_dropna_with_empty_groups():
+ # GH 10780
+ data = Series(np.random.default_rng(2).random(9), index=np.repeat([1, 2, 3], 3))
+ grouped = data.groupby(level=0)
+ result_false = grouped.filter(lambda x: x.mean() > 1, dropna=False)
+ expected_false = Series([np.nan] * 9, index=np.repeat([1, 2, 3], 3))
+ tm.assert_series_equal(result_false, expected_false)
+
+ result_true = grouped.filter(lambda x: x.mean() > 1, dropna=True)
+ expected_true = Series(index=pd.Index([], dtype=int), dtype=np.float64)
+ tm.assert_series_equal(result_true, expected_true)
+
+
+def test_filter_consistent_result_before_after_agg_func():
+ # GH 17091
+ df = DataFrame({"data": range(6), "key": list("ABCABC")})
+ grouper = df.groupby("key")
+ result = grouper.filter(lambda x: True)
+ expected = DataFrame({"data": range(6), "key": list("ABCABC")})
+ tm.assert_frame_equal(result, expected)
+
+ grouper.sum()
+ result = grouper.filter(lambda x: True)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_filter_with_non_values():
+ # GH 62501
+ df = DataFrame(
+ [
+ [1],
+ [None],
+ ],
+ columns=["a"],
+ )
+
+ result = df.groupby("a", dropna=False).filter(lambda x: True)
+ tm.assert_frame_equal(result, df)
+
+
+def test_filter_with_non_values_multi_index():
+ # GH 62501
+ df = DataFrame(
+ [
+ [1, 2],
+ [3, None],
+ [None, 4],
+ [None, None],
+ ],
+ columns=["a", "b"],
+ )
+
+ result = df.groupby(["a", "b"], dropna=False).filter(lambda x: True)
+ tm.assert_frame_equal(result, df)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_groupby.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_groupby.py
new file mode 100644
index 0000000000000000000000000000000000000000..54716bfff0fbafae2e354a2066ae6324d05f62bd
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_groupby.py
@@ -0,0 +1,3004 @@
+from datetime import datetime
+import decimal
+from decimal import Decimal
+import re
+
+import numpy as np
+import pytest
+
+from pandas.errors import SpecificationError
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ Grouper,
+ Index,
+ Interval,
+ MultiIndex,
+ RangeIndex,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+ to_datetime,
+)
+import pandas._testing as tm
+from pandas.core.arrays import BooleanArray
+import pandas.core.common as com
+
+pytestmark = pytest.mark.filterwarnings("ignore:Mean of empty slice:RuntimeWarning")
+
+
+def test_repr():
+ # GH18203
+ result = repr(Grouper(key="A", level="B"))
+ expected = "Grouper(key='A', level='B', sort=False, dropna=True)"
+ assert result == expected
+
+
+def test_groupby_nonobject_dtype(multiindex_dataframe_random_data):
+ key = multiindex_dataframe_random_data.index.codes[0]
+ grouped = multiindex_dataframe_random_data.groupby(key)
+ result = grouped.sum()
+
+ expected = multiindex_dataframe_random_data.groupby(key.astype("O")).sum()
+ assert result.index.dtype == np.int8
+ assert expected.index.dtype == np.int64
+ tm.assert_frame_equal(result, expected, check_index_type=False)
+
+
+def test_groupby_nonobject_dtype_mixed():
+ # GH 3911, mixed frame non-conversion
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.array(np.random.default_rng(2).standard_normal(8), dtype="float32"),
+ }
+ )
+ df["value"] = range(len(df))
+
+ def max_value(group):
+ return group.loc[group["value"].idxmax()]
+
+ applied = df.groupby("A").apply(max_value)
+ result = applied.dtypes
+ expected = df.drop(columns="A").dtypes
+ tm.assert_series_equal(result, expected)
+
+
+def test_pass_args_kwargs(ts):
+ def f(x, q=None, axis=0):
+ return np.percentile(x, q, axis=axis)
+
+ g = lambda x: np.percentile(x, 80, axis=0)
+
+ # Series
+ ts_grouped = ts.groupby(lambda x: x.month)
+ agg_result = ts_grouped.agg(np.percentile, 80, axis=0)
+ apply_result = ts_grouped.apply(np.percentile, 80, axis=0)
+ trans_result = ts_grouped.transform(np.percentile, 80, axis=0)
+
+ agg_expected = ts_grouped.quantile(0.8)
+ trans_expected = ts_grouped.transform(g)
+
+ tm.assert_series_equal(apply_result, agg_expected)
+ tm.assert_series_equal(agg_result, agg_expected)
+ tm.assert_series_equal(trans_result, trans_expected)
+
+ agg_result = ts_grouped.agg(f, q=80)
+ apply_result = ts_grouped.apply(f, q=80)
+ trans_result = ts_grouped.transform(f, q=80)
+ tm.assert_series_equal(agg_result, agg_expected)
+ tm.assert_series_equal(apply_result, agg_expected)
+ tm.assert_series_equal(trans_result, trans_expected)
+
+
+def test_pass_args_kwargs_dataframe(tsframe, as_index):
+ def f(x, q=None, axis=0):
+ return np.percentile(x, q, axis=axis)
+
+ df_grouped = tsframe.groupby(lambda x: x.month, as_index=as_index)
+ agg_result = df_grouped.agg(np.percentile, 80, axis=0)
+ apply_result = df_grouped.apply(DataFrame.quantile, 0.8)
+ expected = df_grouped.quantile(0.8)
+ tm.assert_frame_equal(apply_result, expected, check_names=False)
+ tm.assert_frame_equal(agg_result, expected)
+
+ apply_result = df_grouped.apply(DataFrame.quantile, [0.4, 0.8])
+ expected_seq = df_grouped.quantile([0.4, 0.8])
+ if not as_index:
+ # apply treats the op as a transform; .quantile knows it's a reduction
+ apply_result.index = range(4)
+ apply_result.insert(loc=0, column="level_0", value=[1, 1, 2, 2])
+ apply_result.insert(loc=1, column="level_1", value=[0.4, 0.8, 0.4, 0.8])
+ tm.assert_frame_equal(apply_result, expected_seq, check_names=False)
+
+ agg_result = df_grouped.agg(f, q=80)
+ apply_result = df_grouped.apply(DataFrame.quantile, q=0.8)
+ tm.assert_frame_equal(agg_result, expected)
+ tm.assert_frame_equal(apply_result, expected, check_names=False)
+
+
+def test_len():
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ grouped = df.groupby([lambda x: x.year, lambda x: x.month, lambda x: x.day])
+ assert len(grouped) == len(df)
+
+ grouped = df.groupby([lambda x: x.year, lambda x: x.month])
+ expected = len({(x.year, x.month) for x in df.index})
+ assert len(grouped) == expected
+
+
+def test_len_nan_group():
+ # issue 11016
+ df = DataFrame({"a": [np.nan] * 3, "b": [1, 2, 3]})
+ assert len(df.groupby("a")) == 0
+ assert len(df.groupby("b")) == 3
+ assert len(df.groupby(["a", "b"])) == 0
+
+
+def test_groupby_timedelta_median():
+ # issue 57926
+ expected = Series(data=Timedelta("1D"), index=["foo"], dtype="m8[us]")
+ df = DataFrame({"label": ["foo", "foo"], "timedelta": [pd.NaT, Timedelta("1D")]})
+ gb = df.groupby("label")["timedelta"]
+ actual = gb.median()
+ tm.assert_series_equal(actual, expected, check_names=False)
+
+
+@pytest.mark.parametrize("keys", [["a"], ["a", "b"]])
+def test_len_categorical(dropna, observed, keys):
+ # GH#57595
+ df = DataFrame(
+ {
+ "a": Categorical([1, 1, 2, np.nan], categories=[1, 2, 3]),
+ "b": Categorical([1, 1, 2, np.nan], categories=[1, 2, 3]),
+ "c": 1,
+ }
+ )
+ gb = df.groupby(keys, observed=observed, dropna=dropna)
+ result = len(gb)
+ if observed and dropna:
+ expected = 2
+ elif observed and not dropna:
+ expected = 3
+ elif len(keys) == 1:
+ expected = 3 if dropna else 4
+ else:
+ expected = 9 if dropna else 16
+ assert result == expected, f"{result} vs {expected}"
+
+
+def test_basic_regression():
+ # regression
+ result = Series([1.0 * x for x in list(range(1, 10)) * 10])
+
+ data = np.random.default_rng(2).random(1100) * 10.0
+ groupings = Series(data)
+
+ grouped = result.groupby(groupings)
+ grouped.mean()
+
+
+def test_indices_concatenation_order():
+ # GH 2808
+
+ def f1(x):
+ y = x[(x.b % 2) == 1] ** 2
+ if y.empty:
+ multiindex = MultiIndex(levels=[[]] * 2, codes=[[]] * 2, names=["b", "c"])
+ res = DataFrame(columns=["a"], index=multiindex)
+ return res
+ else:
+ y = y.set_index(["b", "c"])
+ return y
+
+ def f2(x):
+ y = x[(x.b % 2) == 1] ** 2
+ if y.empty:
+ return DataFrame()
+ else:
+ y = y.set_index(["b", "c"])
+ return y
+
+ def f3(x):
+ y = x[(x.b % 2) == 1] ** 2
+ if y.empty:
+ multiindex = MultiIndex(
+ levels=[[]] * 2, codes=[[]] * 2, names=["foo", "bar"]
+ )
+ res = DataFrame(columns=["a", "b"], index=multiindex)
+ return res
+ else:
+ return y
+
+ df = DataFrame({"a": [1, 2, 2, 2], "b": range(4), "c": range(5, 9)})
+
+ df2 = DataFrame({"a": [3, 2, 2, 2], "b": range(4), "c": range(5, 9)})
+
+ # correct result
+ result1 = df.groupby("a").apply(f1)
+ result2 = df2.groupby("a").apply(f1)
+ tm.assert_frame_equal(result1, result2)
+
+ # should fail (not the same number of levels)
+ msg = "Cannot concat indices that do not have the same number of levels"
+ with pytest.raises(AssertionError, match=msg):
+ df.groupby("a").apply(f2)
+ with pytest.raises(AssertionError, match=msg):
+ df2.groupby("a").apply(f2)
+
+ # should fail (incorrect shape)
+ with pytest.raises(AssertionError, match=msg):
+ df.groupby("a").apply(f3)
+ with pytest.raises(AssertionError, match=msg):
+ df2.groupby("a").apply(f3)
+
+
+def test_attr_wrapper(ts):
+ grouped = ts.groupby(lambda x: x.weekday())
+
+ result = grouped.std()
+ expected = grouped.agg(lambda x: np.std(x, ddof=1))
+ tm.assert_series_equal(result, expected)
+
+ # this is pretty cool
+ result = grouped.describe()
+ expected = {name: gp.describe() for name, gp in grouped}
+ expected = DataFrame(expected).T
+ tm.assert_frame_equal(result, expected)
+
+ # get attribute
+ result = grouped.dtype
+ expected = grouped.agg(lambda x: x.dtype)
+ tm.assert_series_equal(result, expected)
+
+ # make sure raises error
+ msg = "'SeriesGroupBy' object has no attribute 'foo'"
+ with pytest.raises(AttributeError, match=msg):
+ grouped.foo
+
+
+def test_frame_groupby(tsframe):
+ grouped = tsframe.groupby(lambda x: x.weekday())
+
+ # aggregate
+ aggregated = grouped.aggregate("mean")
+ assert len(aggregated) == 5
+ assert len(aggregated.columns) == 4
+
+ # by string
+ tscopy = tsframe.copy()
+ tscopy["weekday"] = [x.weekday() for x in tscopy.index]
+ stragged = tscopy.groupby("weekday").aggregate("mean")
+ tm.assert_frame_equal(stragged, aggregated, check_names=False)
+
+ # transform
+ grouped = tsframe.head(30).groupby(lambda x: x.weekday())
+ transformed = grouped.transform(lambda x: x - x.mean())
+ assert len(transformed) == 30
+ assert len(transformed.columns) == 4
+
+ # transform propagate
+ transformed = grouped.transform(lambda x: x.mean())
+ for name, group in grouped:
+ mean = group.mean()
+ for idx in group.index:
+ tm.assert_series_equal(transformed.xs(idx), mean, check_names=False)
+
+ # iterate
+ for weekday, group in grouped:
+ assert group.index[0].weekday() == weekday
+
+ # groups / group_indices
+ groups = grouped.groups
+ indices = grouped.indices
+
+ for k, v in groups.items():
+ samething = tsframe.index.take(indices[k])
+ assert (samething == v).all()
+
+
+def test_frame_set_name_single(df):
+ grouped = df.groupby("A")
+
+ result = grouped.mean(numeric_only=True)
+ assert result.index.name == "A"
+
+ result = df.groupby("A", as_index=False).mean(numeric_only=True)
+ assert result.index.name != "A"
+
+ result = grouped[["C", "D"]].agg("mean")
+ assert result.index.name == "A"
+
+ result = grouped.agg({"C": "mean", "D": "std"})
+ assert result.index.name == "A"
+
+ result = grouped["C"].mean()
+ assert result.index.name == "A"
+ result = grouped["C"].agg("mean")
+ assert result.index.name == "A"
+ result = grouped["C"].agg(["mean", "std"])
+ assert result.index.name == "A"
+
+ msg = r"nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ grouped["C"].agg({"foo": "mean", "bar": "std"})
+
+
+def test_multi_func(df):
+ col1 = df["A"]
+ col2 = df["B"]
+
+ grouped = df.groupby([col1.get, col2.get])
+ agged = grouped.mean(numeric_only=True)
+ expected = df.groupby(["A", "B"]).mean()
+
+ # TODO groupby get drops names
+ tm.assert_frame_equal(
+ agged.loc[:, ["C", "D"]], expected.loc[:, ["C", "D"]], check_names=False
+ )
+
+ # some "groups" with no data
+ df = DataFrame(
+ {
+ "v1": np.random.default_rng(2).standard_normal(6),
+ "v2": np.random.default_rng(2).standard_normal(6),
+ "k1": np.array(["b", "b", "b", "a", "a", "a"]),
+ "k2": np.array(["1", "1", "1", "2", "2", "2"]),
+ },
+ index=["one", "two", "three", "four", "five", "six"],
+ )
+ # only verify that it works for now
+ grouped = df.groupby(["k1", "k2"])
+ grouped.agg("sum")
+
+
+def test_multi_key_multiple_functions(df):
+ grouped = df.groupby(["A", "B"])["C"]
+
+ agged = grouped.agg(["mean", "std"])
+ expected = DataFrame({"mean": grouped.agg("mean"), "std": grouped.agg("std")})
+ tm.assert_frame_equal(agged, expected)
+
+
+def test_frame_multi_key_function_list():
+ data = DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "D": np.random.default_rng(2).standard_normal(11),
+ "E": np.random.default_rng(2).standard_normal(11),
+ "F": np.random.default_rng(2).standard_normal(11),
+ }
+ )
+
+ grouped = data.groupby(["A", "B"])
+ funcs = ["mean", "std"]
+ agged = grouped.agg(funcs)
+ expected = pd.concat(
+ [grouped["D"].agg(funcs), grouped["E"].agg(funcs), grouped["F"].agg(funcs)],
+ keys=["D", "E", "F"],
+ axis=1,
+ )
+ assert isinstance(agged.index, MultiIndex)
+ assert isinstance(expected.index, MultiIndex)
+ tm.assert_frame_equal(agged, expected)
+
+
+def test_frame_multi_key_function_list_partial_failure(using_infer_string):
+ data = DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "C": [
+ "dull",
+ "dull",
+ "shiny",
+ "dull",
+ "dull",
+ "shiny",
+ "shiny",
+ "dull",
+ "shiny",
+ "shiny",
+ "shiny",
+ ],
+ "D": np.random.default_rng(2).standard_normal(11),
+ "E": np.random.default_rng(2).standard_normal(11),
+ "F": np.random.default_rng(2).standard_normal(11),
+ }
+ )
+
+ grouped = data.groupby(["A", "B"])
+ funcs = ["mean", "std"]
+ msg = re.escape("agg function failed [how->mean,dtype->")
+ if using_infer_string:
+ msg = "dtype 'str' does not support operation 'mean'"
+ with pytest.raises(TypeError, match=msg):
+ grouped.agg(funcs)
+
+
+@pytest.mark.parametrize("op", [lambda x: x.sum(), lambda x: x.mean()])
+def test_groupby_multiple_columns(df, op):
+ data = df
+ grouped = data.groupby(["A", "B"])
+
+ result1 = op(grouped)
+
+ keys = []
+ values = []
+ for n1, gp1 in data.groupby("A"):
+ for n2, gp2 in gp1.groupby("B"):
+ keys.append((n1, n2))
+ values.append(op(gp2.loc[:, ["C", "D"]]))
+
+ mi = MultiIndex.from_tuples(keys, names=["A", "B"])
+ expected = pd.concat(values, axis=1).T
+ expected.index = mi
+
+ # a little bit crude
+ for col in ["C", "D"]:
+ result_col = op(grouped[col])
+ pivoted = result1[col]
+ exp = expected[col]
+ tm.assert_series_equal(result_col, exp)
+ tm.assert_series_equal(pivoted, exp)
+
+ # test single series works the same
+ result = data["C"].groupby([data["A"], data["B"]]).mean()
+ expected = data.groupby(["A", "B"]).mean()["C"]
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_as_index_select_column():
+ # GH 5764
+ df = DataFrame([[1, 2], [1, 4], [5, 6]], columns=["A", "B"])
+ result = df.groupby("A", as_index=False)["B"].get_group(1)
+ expected = Series([2, 4], name="B")
+ tm.assert_series_equal(result, expected)
+
+ result = df.groupby("A", as_index=False, group_keys=True)["B"].apply(
+ lambda x: x.cumsum()
+ )
+ expected = Series([2, 6, 6], name="B", index=range(3))
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_as_index_select_column_sum_empty_df():
+ # GH 35246
+ df = DataFrame(columns=Index(["A", "B", "C"], name="alpha"))
+ left = df.groupby(by="A", as_index=False)["B"].sum(numeric_only=False)
+
+ expected = DataFrame(columns=df.columns[:2], index=range(0))
+ # GH#50744 - Columns after selection shouldn't retain names
+ expected.columns.names = [None]
+ tm.assert_frame_equal(left, expected)
+
+
+def test_ops_not_as_index(reduction_func):
+ # GH 10355, 21090
+ # Using as_index=False should not modify grouped column
+
+ if reduction_func in ("corrwith", "nth", "ngroup"):
+ pytest.skip(f"GH 5755: Test not applicable for {reduction_func}")
+
+ df = DataFrame(
+ np.random.default_rng(2).integers(0, 5, size=(100, 2)), columns=["a", "b"]
+ )
+ expected = getattr(df.groupby("a"), reduction_func)()
+ if reduction_func == "size":
+ expected = expected.rename("size")
+ expected = expected.reset_index()
+
+ if reduction_func != "size":
+ # 32 bit compat -> groupby preserves dtype whereas reset_index casts to int64
+ expected["a"] = expected["a"].astype(df["a"].dtype)
+
+ g = df.groupby("a", as_index=False)
+
+ result = getattr(g, reduction_func)()
+ tm.assert_frame_equal(result, expected)
+
+ result = g.agg(reduction_func)
+ tm.assert_frame_equal(result, expected)
+
+ result = getattr(g["b"], reduction_func)()
+ tm.assert_frame_equal(result, expected)
+
+ result = g["b"].agg(reduction_func)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_as_index_series_return_frame(df):
+ grouped = df.groupby("A", as_index=False)
+ grouped2 = df.groupby(["A", "B"], as_index=False)
+
+ result = grouped["C"].agg("sum")
+ expected = grouped.agg("sum").loc[:, ["A", "C"]]
+ assert isinstance(result, DataFrame)
+ tm.assert_frame_equal(result, expected)
+
+ result2 = grouped2["C"].agg("sum")
+ expected2 = grouped2.agg("sum").loc[:, ["A", "B", "C"]]
+ assert isinstance(result2, DataFrame)
+ tm.assert_frame_equal(result2, expected2)
+
+ result = grouped["C"].sum()
+ expected = grouped.sum().loc[:, ["A", "C"]]
+ assert isinstance(result, DataFrame)
+ tm.assert_frame_equal(result, expected)
+
+ result2 = grouped2["C"].sum()
+ expected2 = grouped2.sum().loc[:, ["A", "B", "C"]]
+ assert isinstance(result2, DataFrame)
+ tm.assert_frame_equal(result2, expected2)
+
+
+def test_as_index_series_column_slice_raises(df):
+ # GH15072
+ grouped = df.groupby("A", as_index=False)
+ msg = r"Column\(s\) C already selected"
+
+ with pytest.raises(IndexError, match=msg):
+ grouped["C"].__getitem__("D")
+
+
+def test_groupby_as_index_cython(df):
+ data = df
+
+ # single-key
+ grouped = data.groupby("A", as_index=False)
+ result = grouped.mean(numeric_only=True)
+ expected = data.groupby(["A"]).mean(numeric_only=True)
+ expected.insert(0, "A", expected.index)
+ expected.index = RangeIndex(len(expected))
+ tm.assert_frame_equal(result, expected)
+
+ # multi-key
+ grouped = data.groupby(["A", "B"], as_index=False)
+ result = grouped.mean()
+ expected = data.groupby(["A", "B"]).mean()
+
+ arrays = list(zip(*expected.index.values, strict=True))
+ expected.insert(0, "A", arrays[0])
+ expected.insert(1, "B", arrays[1])
+ expected.index = RangeIndex(len(expected))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_as_index_series_scalar(df):
+ grouped = df.groupby(["A", "B"], as_index=False)
+
+ # GH #421
+
+ result = grouped["C"].agg(len)
+ expected = grouped.agg(len).loc[:, ["A", "B", "C"]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_multiple_key():
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ grouped = df.groupby([lambda x: x.year, lambda x: x.month, lambda x: x.day])
+ agged = grouped.sum()
+ tm.assert_almost_equal(df.values, agged.values)
+
+
+def test_groupby_multi_corner(df):
+ # test that having an all-NA column doesn't mess you up
+ df = df.copy()
+ df["bad"] = np.nan
+ agged = df.groupby(["A", "B"]).mean()
+
+ expected = df.groupby(["A", "B"]).mean()
+ expected["bad"] = np.nan
+
+ tm.assert_frame_equal(agged, expected)
+
+
+def test_raises_on_nuisance(df, using_infer_string):
+ grouped = df.groupby("A")
+ msg = re.escape("agg function failed [how->mean,dtype->")
+ if using_infer_string:
+ msg = "dtype 'str' does not support operation 'mean'"
+ with pytest.raises(TypeError, match=msg):
+ grouped.agg("mean")
+ with pytest.raises(TypeError, match=msg):
+ grouped.mean()
+
+ df = df.loc[:, ["A", "C", "D"]]
+ df["E"] = datetime.now()
+ grouped = df.groupby("A")
+ msg = "datetime64 type does not support operation 'sum'"
+ with pytest.raises(TypeError, match=msg):
+ grouped.agg("sum")
+ with pytest.raises(TypeError, match=msg):
+ grouped.sum()
+
+
+@pytest.mark.parametrize(
+ "agg_function",
+ ["max", "min"],
+)
+def test_keep_nuisance_agg(df, agg_function):
+ # GH 38815
+ grouped = df.groupby("A")
+ result = getattr(grouped, agg_function)()
+ expected = result.copy()
+ expected.loc["bar", "B"] = getattr(df.loc[df["A"] == "bar", "B"], agg_function)()
+ expected.loc["foo", "B"] = getattr(df.loc[df["A"] == "foo", "B"], agg_function)()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "agg_function",
+ ["sum", "mean", "prod", "std", "var", "sem", "median"],
+)
+@pytest.mark.parametrize("numeric_only", [True, False])
+def test_omit_nuisance_agg(df, agg_function, numeric_only, using_infer_string):
+ # GH 38774, GH 38815
+ grouped = df.groupby("A")
+
+ no_drop_nuisance = ("var", "std", "sem", "mean", "prod", "median")
+ if agg_function in no_drop_nuisance and not numeric_only:
+ # Added numeric_only as part of GH#46560; these do not drop nuisance
+ # columns when numeric_only is False
+ if using_infer_string:
+ msg = f"dtype 'str' does not support operation '{agg_function}'"
+ klass = TypeError
+ elif agg_function in ("std", "sem"):
+ klass = ValueError
+ msg = "could not convert string to float: 'one'"
+ else:
+ klass = TypeError
+ msg = re.escape(f"agg function failed [how->{agg_function},dtype->")
+ with pytest.raises(klass, match=msg):
+ getattr(grouped, agg_function)(numeric_only=numeric_only)
+ else:
+ result = getattr(grouped, agg_function)(numeric_only=numeric_only)
+ if not numeric_only and agg_function == "sum":
+ # sum is successful on column B
+ columns = ["A", "B", "C", "D"]
+ else:
+ columns = ["A", "C", "D"]
+ expected = getattr(df.loc[:, columns].groupby("A"), agg_function)(
+ numeric_only=numeric_only
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_raise_on_nuisance_python_single(df, using_infer_string):
+ # GH 38815
+ grouped = df.groupby("A")
+
+ err = ValueError
+ msg = "could not convert"
+ if using_infer_string:
+ err = TypeError
+ msg = "dtype 'str' does not support operation 'skew'"
+ with pytest.raises(err, match=msg):
+ grouped.skew()
+
+
+def test_raise_on_nuisance_python_multiple(three_group, using_infer_string):
+ grouped = three_group.groupby(["A", "B"])
+ msg = re.escape("agg function failed [how->mean,dtype->")
+ if using_infer_string:
+ msg = "dtype 'str' does not support operation 'mean'"
+ with pytest.raises(TypeError, match=msg):
+ grouped.agg("mean")
+ with pytest.raises(TypeError, match=msg):
+ grouped.mean()
+
+
+def test_empty_groups_corner(multiindex_dataframe_random_data):
+ # handle empty groups
+ df = DataFrame(
+ {
+ "k1": np.array(["b", "b", "b", "a", "a", "a"]),
+ "k2": np.array(["1", "1", "1", "2", "2", "2"]),
+ "k3": ["foo", "bar"] * 3,
+ "v1": np.random.default_rng(2).standard_normal(6),
+ "v2": np.random.default_rng(2).standard_normal(6),
+ }
+ )
+
+ grouped = df.groupby(["k1", "k2"])
+ result = grouped[["v1", "v2"]].agg("mean")
+ expected = grouped.mean(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+ grouped = multiindex_dataframe_random_data[3:5].groupby(level=0)
+ agged = grouped.apply(lambda x: x.mean())
+ agged_A = grouped["A"].apply("mean")
+ tm.assert_series_equal(agged["A"], agged_A)
+ assert agged.index.name == "first"
+
+
+def test_nonsense_func():
+ df = DataFrame([0])
+ msg = r"unsupported operand type\(s\) for \+: 'int' and 'str'"
+ with pytest.raises(TypeError, match=msg):
+ df.groupby(lambda x: x + "foo")
+
+
+def test_wrap_aggregated_output_multindex(
+ multiindex_dataframe_random_data, using_infer_string
+):
+ df = multiindex_dataframe_random_data.T
+ df["baz", "two"] = "peekaboo"
+
+ keys = [np.array([0, 0, 1]), np.array([0, 0, 1])]
+ msg = re.escape("agg function failed [how->mean,dtype->")
+ if using_infer_string:
+ msg = "dtype 'str' does not support operation 'mean'"
+ with pytest.raises(TypeError, match=msg):
+ df.groupby(keys).agg("mean")
+ agged = df.drop(columns=("baz", "two")).groupby(keys).agg("mean")
+ assert isinstance(agged.columns, MultiIndex)
+
+ def aggfun(ser):
+ if ser.name == ("foo", "one"):
+ raise TypeError("Test error message")
+ return ser.sum()
+
+ with pytest.raises(TypeError, match="Test error message"):
+ df.groupby(keys).aggregate(aggfun)
+
+
+def test_groupby_level_apply(multiindex_dataframe_random_data):
+ result = multiindex_dataframe_random_data.groupby(level=0).count()
+ assert result.index.name == "first"
+ result = multiindex_dataframe_random_data.groupby(level=1).count()
+ assert result.index.name == "second"
+
+ result = multiindex_dataframe_random_data["A"].groupby(level=0).count()
+ assert result.index.name == "first"
+
+
+def test_groupby_level_mapper(multiindex_dataframe_random_data):
+ deleveled = multiindex_dataframe_random_data.reset_index()
+
+ mapper0 = {"foo": 0, "bar": 0, "baz": 1, "qux": 1}
+ mapper1 = {"one": 0, "two": 0, "three": 1}
+
+ result0 = multiindex_dataframe_random_data.groupby(mapper0, level=0).sum()
+ result1 = multiindex_dataframe_random_data.groupby(mapper1, level=1).sum()
+
+ mapped_level0 = np.array(
+ [mapper0.get(x) for x in deleveled["first"]], dtype=np.int64
+ )
+ mapped_level1 = np.array(
+ [mapper1.get(x) for x in deleveled["second"]], dtype=np.int64
+ )
+ expected0 = multiindex_dataframe_random_data.groupby(mapped_level0).sum()
+ expected1 = multiindex_dataframe_random_data.groupby(mapped_level1).sum()
+ expected0.index.name, expected1.index.name = "first", "second"
+
+ tm.assert_frame_equal(result0, expected0)
+ tm.assert_frame_equal(result1, expected1)
+
+
+def test_groupby_level_nonmulti():
+ # GH 1313, GH 13901
+ s = Series([1, 2, 3, 10, 4, 5, 20, 6], Index([1, 2, 3, 1, 4, 5, 2, 6], name="foo"))
+ expected = Series([11, 22, 3, 4, 5, 6], Index(list(range(1, 7)), name="foo"))
+
+ result = s.groupby(level=0).sum()
+ tm.assert_series_equal(result, expected)
+ result = s.groupby(level=[0]).sum()
+ tm.assert_series_equal(result, expected)
+ result = s.groupby(level=-1).sum()
+ tm.assert_series_equal(result, expected)
+ result = s.groupby(level=[-1]).sum()
+ tm.assert_series_equal(result, expected)
+
+ msg = "level > 0 or level < -1 only valid with MultiIndex"
+ with pytest.raises(ValueError, match=msg):
+ s.groupby(level=1)
+ with pytest.raises(ValueError, match=msg):
+ s.groupby(level=-2)
+ msg = "No group keys passed!"
+ with pytest.raises(ValueError, match=msg):
+ s.groupby(level=[])
+ msg = "multiple levels only valid with MultiIndex"
+ with pytest.raises(ValueError, match=msg):
+ s.groupby(level=[0, 0])
+ with pytest.raises(ValueError, match=msg):
+ s.groupby(level=[0, 1])
+ msg = "level > 0 or level < -1 only valid with MultiIndex"
+ with pytest.raises(ValueError, match=msg):
+ s.groupby(level=[1])
+
+
+def test_groupby_complex():
+ # GH 12902
+ a = Series(data=np.arange(4) * (1 + 2j), index=[0, 0, 1, 1])
+ expected = Series((1 + 2j, 5 + 10j), index=Index([0, 1]))
+
+ result = a.groupby(level=0).sum()
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_complex_mean():
+ # GH 26475
+ df = DataFrame(
+ [
+ {"a": 2, "b": 1 + 2j},
+ {"a": 1, "b": 1 + 1j},
+ {"a": 1, "b": 1 + 2j},
+ ]
+ )
+ result = df.groupby("b").mean()
+ expected = DataFrame(
+ [[1.0], [1.5]],
+ index=Index([(1 + 1j), (1 + 2j)], name="b"),
+ columns=Index(["a"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_complex_numbers():
+ # GH 17927
+ df = DataFrame(
+ [
+ {"a": 1, "b": 1 + 1j},
+ {"a": 1, "b": 1 + 2j},
+ {"a": 4, "b": 1},
+ ]
+ )
+ expected = DataFrame(
+ np.array([1, 1, 1], dtype=np.int64),
+ index=Index([(1 + 1j), (1 + 2j), (1 + 0j)], name="b"),
+ columns=Index(["a"]),
+ )
+ result = df.groupby("b", sort=False).count()
+ tm.assert_frame_equal(result, expected)
+
+ # Sorted by the magnitude of the complex numbers
+ expected.index = Index([(1 + 0j), (1 + 1j), (1 + 2j)], name="b")
+ result = df.groupby("b", sort=True).count()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_series_indexed_differently():
+ s1 = Series(
+ [5.0, -9.0, 4.0, 100.0, -5.0, 55.0, 6.7],
+ index=Index(["a", "b", "c", "d", "e", "f", "g"]),
+ )
+ s2 = Series(
+ [1.0, 1.0, 4.0, 5.0, 5.0, 7.0], index=Index(["a", "b", "d", "f", "g", "h"])
+ )
+
+ grouped = s1.groupby(s2)
+ agged = grouped.mean()
+ exp = s1.groupby(s2.reindex(s1.index).get).mean()
+ tm.assert_series_equal(agged, exp)
+
+
+def test_groupby_with_hier_columns():
+ tuples = list(
+ zip(
+ *[
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ],
+ strict=True,
+ )
+ )
+ index = MultiIndex.from_tuples(tuples)
+ columns = MultiIndex.from_tuples(
+ [("A", "cat"), ("B", "dog"), ("B", "cat"), ("A", "dog")]
+ )
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((8, 4)), index=index, columns=columns
+ )
+
+ result = df.groupby(level=0).mean()
+ tm.assert_index_equal(result.columns, columns)
+
+ result = df.groupby(level=0).agg("mean")
+ tm.assert_index_equal(result.columns, columns)
+
+ result = df.groupby(level=0).apply(lambda x: x.mean())
+ tm.assert_index_equal(result.columns, columns)
+
+ # add a nuisance column
+ sorted_columns, _ = columns.sortlevel(0)
+ df["A", "foo"] = "bar"
+ result = df.groupby(level=0).mean(numeric_only=True)
+ tm.assert_index_equal(result.columns, df.columns[:-1])
+
+
+def test_grouping_ndarray(df):
+ grouped = df.groupby(df["A"].values)
+ grouped2 = df.groupby(df["A"].rename(None))
+
+ result = grouped.sum()
+ expected = grouped2.sum()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_wrong_multi_labels():
+ index = Index([0, 1, 2, 3, 4], name="index")
+ data = DataFrame(
+ {
+ "foo": ["foo1", "foo1", "foo2", "foo1", "foo3"],
+ "bar": ["bar1", "bar2", "bar2", "bar1", "bar1"],
+ "baz": ["baz1", "baz1", "baz1", "baz2", "baz2"],
+ "spam": ["spam2", "spam3", "spam2", "spam1", "spam1"],
+ "data": [20, 30, 40, 50, 60],
+ },
+ index=index,
+ )
+
+ grouped = data.groupby(["foo", "bar", "baz", "spam"])
+
+ result = grouped.agg("mean")
+ expected = grouped.mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_series_with_name(df):
+ result = df.groupby(df["A"]).mean(numeric_only=True)
+ result2 = df.groupby(df["A"], as_index=False).mean(numeric_only=True)
+ assert result.index.name == "A"
+ assert "A" in result2
+
+ result = df.groupby([df["A"], df["B"]]).mean()
+ result2 = df.groupby([df["A"], df["B"]], as_index=False).mean()
+ assert result.index.names == ("A", "B")
+ assert "A" in result2
+ assert "B" in result2
+
+
+def test_seriesgroupby_name_attr(df):
+ # GH 6265
+ result = df.groupby("A")["C"]
+ assert result.count().name == "C"
+ assert result.mean().name == "C"
+
+ testFunc = lambda x: np.sum(x) * 2
+ assert result.agg(testFunc).name == "C"
+
+
+def test_consistency_name():
+ # GH 12363
+
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "two", "two", "two", "one", "two"],
+ "C": np.random.default_rng(2).standard_normal(8) + 1.0,
+ "D": np.arange(8),
+ }
+ )
+
+ expected = df.groupby(["A"]).B.count()
+ result = df.B.groupby(df.A).count()
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_name_propagation(df):
+ # GH 6124
+ def summarize(df, name=None):
+ return Series({"count": 1, "mean": 2, "omissions": 3}, name=name)
+
+ def summarize_random_name(df):
+ # Provide a different name for each Series. In this case, groupby
+ # should not attempt to propagate the Series name since they are
+ # inconsistent.
+ return Series({"count": 1, "mean": 2, "omissions": 3}, name=df.iloc[0]["C"])
+
+ metrics = df.groupby("A").apply(summarize)
+ assert metrics.columns.name is None
+ metrics = df.groupby("A").apply(summarize, "metrics")
+ assert metrics.columns.name == "metrics"
+ metrics = df.groupby("A").apply(summarize_random_name)
+ assert metrics.columns.name is None
+
+
+def test_groupby_nonstring_columns():
+ df = DataFrame([np.arange(10) for x in range(10)])
+ grouped = df.groupby(0)
+ result = grouped.mean()
+ expected = df.groupby(df[0]).mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_mixed_type_columns():
+ # GH 13432, unorderable types in py3
+ df = DataFrame([[0, 1, 2]], columns=["A", "B", 0])
+ expected = DataFrame([[1, 2]], columns=["B", 0], index=Index([0], name="A"))
+
+ result = df.groupby("A").first()
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("A").sum()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_cython_grouper_series_bug_noncontig():
+ arr = np.empty((100, 100))
+ arr.fill(np.nan)
+ obj = Series(arr[:, 0])
+ inds = np.tile(range(10), 10)
+
+ result = obj.groupby(inds).agg(Series.median)
+ assert result.isna().all()
+
+
+def test_series_grouper_noncontig_index():
+ index = Index(["a" * 10] * 100)
+
+ values = Series(np.random.default_rng(2).standard_normal(50), index=index[::2])
+ labels = np.random.default_rng(2).integers(0, 5, 50)
+
+ # it works!
+ grouped = values.groupby(labels)
+
+ # accessing the index elements causes segfault
+ f = lambda x: len(set(map(id, x.index)))
+ grouped.agg(f)
+
+
+def test_convert_objects_leave_decimal_alone():
+ s = Series(range(5))
+ labels = np.array(["a", "b", "c", "d", "e"], dtype="O")
+
+ def convert_fast(x):
+ return Decimal(str(x.mean()))
+
+ def convert_force_pure(x):
+ # base will be length 0
+ assert len(x.values.base) > 0
+ return Decimal(str(x.mean()))
+
+ grouped = s.groupby(labels)
+
+ result = grouped.agg(convert_fast)
+ assert result.dtype == np.object_
+ assert isinstance(result.iloc[0], Decimal)
+
+ result = grouped.agg(convert_force_pure)
+ assert result.dtype == np.object_
+ assert isinstance(result.iloc[0], Decimal)
+
+
+def test_groupby_dtype_inference_empty():
+ # GH 6733
+ df = DataFrame({"x": [], "range": np.arange(0, dtype="int64")})
+ assert df["x"].dtype == np.float64
+
+ result = df.groupby("x").first()
+ exp_index = Index([], name="x", dtype=np.float64)
+ expected = DataFrame({"range": Series([], index=exp_index, dtype="int64")})
+ tm.assert_frame_equal(result, expected, by_blocks=True)
+
+
+def test_groupby_unit64_float_conversion():
+ # GH: 30859 groupby converts unit64 to floats sometimes
+ df = DataFrame({"first": [1], "second": [1], "value": [16148277970000000000]})
+ result = df.groupby(["first", "second"])["value"].max()
+ expected = Series(
+ [16148277970000000000],
+ MultiIndex.from_product([[1], [1]], names=["first", "second"]),
+ name="value",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_list_infer_array_like(df):
+ result = df.groupby(list(df["A"])).mean(numeric_only=True)
+ expected = df.groupby(df["A"]).mean(numeric_only=True)
+ tm.assert_frame_equal(result, expected, check_names=False)
+
+ with pytest.raises(KeyError, match=r"^'foo'$"):
+ df.groupby(list(df["A"][:-1]))
+
+ # pathological case of ambiguity
+ df = DataFrame(
+ {
+ "foo": [0, 1],
+ "bar": [3, 4],
+ "val": np.random.default_rng(2).standard_normal(2),
+ }
+ )
+
+ result = df.groupby(["foo", "bar"]).mean()
+ expected = df.groupby([df["foo"], df["bar"]]).mean()[["val"]]
+
+
+def test_groupby_keys_same_size_as_index():
+ # GH 11185
+ freq = "s"
+ index = date_range(
+ start=Timestamp("2015-09-29T11:34:44-0700"), periods=2, freq=freq
+ )
+ df = DataFrame([["A", 10], ["B", 15]], columns=["metric", "values"], index=index)
+ result = df.groupby([Grouper(level=0, freq=freq), "metric"]).mean()
+ expected = df.set_index([df.index, "metric"]).astype(float)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_one_row():
+ # GH 11741
+ msg = r"^'Z'$"
+ df1 = DataFrame(
+ np.random.default_rng(2).standard_normal((1, 4)), columns=list("ABCD")
+ )
+ with pytest.raises(KeyError, match=msg):
+ df1.groupby("Z")
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((2, 4)), columns=list("ABCD")
+ )
+ with pytest.raises(KeyError, match=msg):
+ df2.groupby("Z")
+
+
+def test_groupby_nat_exclude():
+ # GH 6992
+ df = DataFrame(
+ {
+ "values": np.random.default_rng(2).standard_normal(8),
+ "dt": [
+ np.nan,
+ Timestamp("2013-01-01"),
+ np.nan,
+ Timestamp("2013-02-01"),
+ np.nan,
+ Timestamp("2013-02-01"),
+ np.nan,
+ Timestamp("2013-01-01"),
+ ],
+ "str": [np.nan, "a", np.nan, "a", np.nan, "a", np.nan, "b"],
+ }
+ )
+ grouped = df.groupby("dt")
+
+ expected = [
+ RangeIndex(start=1, stop=13, step=6),
+ RangeIndex(start=3, stop=7, step=2),
+ ]
+ keys = sorted(grouped.groups.keys())
+ assert len(keys) == 2
+ for k, e in zip(keys, expected, strict=True):
+ # grouped.groups keys are np.datetime64 with system tz
+ # not to be affected by tz, only compare values
+ tm.assert_index_equal(grouped.groups[k], e)
+
+ # confirm obj is not filtered
+ tm.assert_frame_equal(grouped._grouper.groupings[0].obj, df)
+ assert grouped.ngroups == 2
+
+ expected = {
+ Timestamp("2013-01-01 00:00:00"): np.array([1, 7], dtype=np.intp),
+ Timestamp("2013-02-01 00:00:00"): np.array([3, 5], dtype=np.intp),
+ }
+
+ for k in grouped.indices:
+ tm.assert_numpy_array_equal(grouped.indices[k], expected[k])
+
+ tm.assert_frame_equal(grouped.get_group(Timestamp("2013-01-01")), df.iloc[[1, 7]])
+ tm.assert_frame_equal(grouped.get_group(Timestamp("2013-02-01")), df.iloc[[3, 5]])
+
+ with pytest.raises(KeyError, match=r"^NaT$"):
+ grouped.get_group(pd.NaT)
+
+ nan_df = DataFrame(
+ {"nan": [np.nan, np.nan, np.nan], "nat": [pd.NaT, pd.NaT, pd.NaT]}
+ )
+ assert nan_df["nan"].dtype == "float64"
+ assert nan_df["nat"].dtype == "datetime64[s]"
+
+ for key in ["nan", "nat"]:
+ grouped = nan_df.groupby(key)
+ assert grouped.groups == {}
+ assert grouped.ngroups == 0
+ assert grouped.indices == {}
+ with pytest.raises(KeyError, match=r"^nan$"):
+ grouped.get_group(np.nan)
+ with pytest.raises(KeyError, match=r"^NaT$"):
+ grouped.get_group(pd.NaT)
+
+
+def test_groupby_two_group_keys_all_nan():
+ # GH #36842: Grouping over two group keys shouldn't raise an error
+ df = DataFrame({"a": [np.nan, np.nan], "b": [np.nan, np.nan], "c": [1, 2]})
+ result = df.groupby(["a", "b"]).indices
+ assert result == {}
+
+
+def test_groupby_2d_malformed():
+ d = DataFrame(index=range(2))
+ d["group"] = ["g1", "g2"]
+ d["zeros"] = [0, 0]
+ d["ones"] = [1, 1]
+ d["label"] = ["l1", "l2"]
+ tmp = d.groupby(["group"]).mean(numeric_only=True)
+ res_values = np.array([[0.0, 1.0], [0.0, 1.0]])
+ tm.assert_index_equal(tmp.columns, Index(["zeros", "ones"]))
+ tm.assert_numpy_array_equal(tmp.values, res_values)
+
+
+def test_int32_overflow():
+ B = np.concatenate((np.arange(10000), np.arange(10000), np.arange(5000)))
+ A = np.arange(25000)
+ df = DataFrame(
+ {
+ "A": A,
+ "B": B,
+ "C": A,
+ "D": B,
+ "E": np.random.default_rng(2).standard_normal(25000),
+ }
+ )
+
+ left = df.groupby(["A", "B", "C", "D"]).sum()
+ right = df.groupby(["D", "C", "B", "A"]).sum()
+ assert len(left) == len(right)
+
+
+def test_groupby_sort_multi():
+ df = DataFrame(
+ {
+ "a": ["foo", "bar", "baz"],
+ "b": [3, 2, 1],
+ "c": [0, 1, 2],
+ "d": np.random.default_rng(2).standard_normal(3),
+ }
+ )
+
+ tups = [tuple(row) for row in df[["a", "b", "c"]].values]
+ tups = com.asarray_tuplesafe(tups)
+ result = df.groupby(["a", "b", "c"], sort=True).sum()
+ tm.assert_numpy_array_equal(result.index.values, tups[[1, 2, 0]])
+
+ tups = [tuple(row) for row in df[["c", "a", "b"]].values]
+ tups = com.asarray_tuplesafe(tups)
+ result = df.groupby(["c", "a", "b"], sort=True).sum()
+ tm.assert_numpy_array_equal(result.index.values, tups)
+
+ tups = [tuple(x) for x in df[["b", "c", "a"]].values]
+ tups = com.asarray_tuplesafe(tups)
+ result = df.groupby(["b", "c", "a"], sort=True).sum()
+ tm.assert_numpy_array_equal(result.index.values, tups[[2, 1, 0]])
+
+ df = DataFrame(
+ {
+ "a": [0, 1, 2, 0, 1, 2],
+ "b": [0, 0, 0, 1, 1, 1],
+ "d": np.random.default_rng(2).standard_normal(6),
+ }
+ )
+ grouped = df.groupby(["a", "b"])["d"]
+ result = grouped.sum()
+
+ def _check_groupby(df, result, keys, field, f=lambda x: x.sum()):
+ tups = [tuple(row) for row in df[keys].values]
+ tups = com.asarray_tuplesafe(tups)
+ expected = f(df.groupby(tups)[field])
+ for k, v in expected.items():
+ assert result[k] == v
+
+ _check_groupby(df, result, ["a", "b"], "d")
+
+
+def test_dont_clobber_name_column():
+ df = DataFrame(
+ {"key": ["a", "a", "a", "b", "b", "b"], "name": ["foo", "bar", "baz"] * 2}
+ )
+
+ result = df.groupby("key", group_keys=False).apply(lambda x: x)
+ tm.assert_frame_equal(result, df[["name"]])
+
+
+def test_skip_group_keys():
+ tsf = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+
+ grouped = tsf.groupby(lambda x: x.month, group_keys=False)
+ result = grouped.apply(lambda x: x.sort_values(by="A")[:3])
+
+ pieces = [group.sort_values(by="A")[:3] for key, group in grouped]
+
+ expected = pd.concat(pieces)
+ tm.assert_frame_equal(result, expected)
+
+ grouped = tsf["A"].groupby(lambda x: x.month, group_keys=False)
+ result = grouped.apply(lambda x: x.sort_values()[:3])
+
+ pieces = [group.sort_values()[:3] for key, group in grouped]
+
+ expected = pd.concat(pieces)
+ tm.assert_series_equal(result, expected)
+
+
+def test_no_nonsense_name(float_frame):
+ # GH #995
+ s = float_frame["C"].copy()
+ s.name = None
+
+ result = s.groupby(float_frame["A"]).agg("sum")
+ assert result.name is None
+
+
+def test_multifunc_sum_bug():
+ # GH #1065
+ x = DataFrame(np.arange(9).reshape(3, 3))
+ x["test"] = 0
+ x["fl"] = [1.3, 1.5, 1.6]
+
+ grouped = x.groupby("test")
+ result = grouped.agg({"fl": "sum", 2: "size"})
+ assert result["fl"].dtype == np.float64
+
+
+def test_handle_dict_return_value(df):
+ def f(group):
+ return {"max": group.max(), "min": group.min()}
+
+ def g(group):
+ return Series({"max": group.max(), "min": group.min()})
+
+ result = df.groupby("A")["C"].apply(f)
+ expected = df.groupby("A")["C"].apply(g)
+
+ assert isinstance(result, Series)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("grouper", ["A", ["A", "B"]])
+def test_set_group_name(df, grouper):
+ def f(group):
+ assert group.name is not None
+ return group
+
+ def freduce(group):
+ assert group.name is not None
+ return group.sum()
+
+ def freducex(x):
+ return freduce(x)
+
+ grouped = df.groupby(grouper, group_keys=False)
+
+ # make sure all these work
+ grouped.apply(f)
+ grouped.aggregate(freduce)
+ grouped.aggregate({"C": freduce, "D": freduce})
+ grouped.transform(f)
+
+ grouped["C"].apply(f)
+ grouped["C"].aggregate(freduce)
+ grouped["C"].aggregate([freduce, freducex])
+ grouped["C"].transform(f)
+
+
+def test_group_name_available_in_inference_pass():
+ # gh-15062
+ df = DataFrame({"a": [0, 0, 1, 1, 2, 2], "b": np.arange(6)})
+
+ names = []
+
+ def f(group):
+ names.append(group.name)
+ return group.copy()
+
+ df.groupby("a", sort=False, group_keys=False).apply(f)
+ expected_names = [0, 1, 2]
+ assert names == expected_names
+
+
+def test_no_dummy_key_names(df):
+ # see gh-1291
+ result = df.groupby(df["A"].values).sum()
+ assert result.index.name is None
+
+ result2 = df.groupby([df["A"].values, df["B"].values]).sum()
+ assert result2.index.names == (None, None)
+
+
+def test_groupby_sort_multiindex_series():
+ # series multiindex groupby sort argument was not being passed through
+ # _compress_group_index
+ # GH 9444
+ index = MultiIndex(
+ levels=[[1, 2], [1, 2]],
+ codes=[[0, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 0]],
+ names=["a", "b"],
+ )
+ mseries = Series([0, 1, 2, 3, 4, 5], index=index)
+ index = MultiIndex(
+ levels=[[1, 2], [1, 2]], codes=[[0, 0, 1], [1, 0, 0]], names=["a", "b"]
+ )
+ mseries_result = Series([0, 2, 4], index=index)
+
+ result = mseries.groupby(level=["a", "b"], sort=False).first()
+ tm.assert_series_equal(result, mseries_result)
+ result = mseries.groupby(level=["a", "b"], sort=True).first()
+ tm.assert_series_equal(result, mseries_result.sort_index())
+
+
+def test_groupby_reindex_inside_function():
+ periods = 1000
+ ind = date_range(start="2012/1/1", freq="5min", periods=periods)
+ df = DataFrame({"high": np.arange(periods), "low": np.arange(periods)}, index=ind)
+
+ def agg_before(func, fix=False):
+ """
+ Run an aggregate func on the subset of data.
+ """
+
+ def _func(data):
+ d = data.loc[data.index.map(lambda x: x.hour < 11)].dropna()
+ if fix:
+ data[data.index[0]]
+ if len(d) == 0:
+ return None
+ return func(d)
+
+ return _func
+
+ grouped = df.groupby(lambda x: datetime(x.year, x.month, x.day))
+ closure_bad = grouped.agg({"high": agg_before(np.max)})
+ closure_good = grouped.agg({"high": agg_before(np.max, True)})
+
+ tm.assert_frame_equal(closure_bad, closure_good)
+
+
+def test_groupby_multiindex_missing_pair():
+ # GH9049
+ df = DataFrame(
+ {
+ "group1": ["a", "a", "a", "b"],
+ "group2": ["c", "c", "d", "c"],
+ "value": [1, 1, 1, 5],
+ }
+ )
+ df = df.set_index(["group1", "group2"])
+ df_grouped = df.groupby(level=["group1", "group2"], sort=True)
+
+ res = df_grouped.agg("sum")
+ idx = MultiIndex.from_tuples(
+ [("a", "c"), ("a", "d"), ("b", "c")], names=["group1", "group2"]
+ )
+ exp = DataFrame([[2], [1], [5]], index=idx, columns=["value"])
+
+ tm.assert_frame_equal(res, exp)
+
+
+def test_groupby_multiindex_not_lexsorted(performance_warning):
+ # GH 11640
+
+ # define the lexsorted version
+ lexsorted_mi = MultiIndex.from_tuples(
+ [("a", ""), ("b1", "c1"), ("b2", "c2")], names=["b", "c"]
+ )
+ lexsorted_df = DataFrame([[1, 3, 4]], columns=lexsorted_mi)
+ assert lexsorted_df.columns._is_lexsorted()
+
+ # define the non-lexsorted version
+ not_lexsorted_df = DataFrame(
+ columns=["a", "b", "c", "d"], data=[[1, "b1", "c1", 3], [1, "b2", "c2", 4]]
+ )
+ not_lexsorted_df = not_lexsorted_df.pivot_table(
+ index="a", columns=["b", "c"], values="d"
+ )
+ not_lexsorted_df = not_lexsorted_df.reset_index()
+ assert not not_lexsorted_df.columns._is_lexsorted()
+
+ expected = lexsorted_df.groupby("a").mean()
+ with tm.assert_produces_warning(performance_warning):
+ result = not_lexsorted_df.groupby("a").mean()
+ tm.assert_frame_equal(expected, result)
+
+ # a transforming function should work regardless of sort
+ # GH 14776
+ df = DataFrame(
+ {"x": ["a", "a", "b", "a"], "y": [1, 1, 2, 2], "z": [1, 2, 3, 4]}
+ ).set_index(["x", "y"])
+ assert not df.index._is_lexsorted()
+
+ for level in [0, 1, [0, 1]]:
+ for sort in [False, True]:
+ result = df.groupby(level=level, sort=sort, group_keys=False).apply(
+ DataFrame.drop_duplicates
+ )
+ expected = df
+ tm.assert_frame_equal(expected, result)
+
+ result = (
+ df.sort_index()
+ .groupby(level=level, sort=sort, group_keys=False)
+ .apply(DataFrame.drop_duplicates)
+ )
+ expected = df.sort_index()
+ tm.assert_frame_equal(expected, result)
+
+
+def test_index_label_overlaps_location():
+ # checking we don't have any label/location confusion in the
+ # wake of GH5375
+ df = DataFrame(list("ABCDE"), index=[2, 0, 2, 1, 1])
+ g = df.groupby(list("ababb"))
+ actual = g.filter(lambda x: len(x) > 2)
+ expected = df.iloc[[1, 3, 4]]
+ tm.assert_frame_equal(actual, expected)
+
+ ser = df[0]
+ g = ser.groupby(list("ababb"))
+ actual = g.filter(lambda x: len(x) > 2)
+ expected = ser.take([1, 3, 4])
+ tm.assert_series_equal(actual, expected)
+
+ # and again, with a generic Index of floats
+ df.index = df.index.astype(float)
+ g = df.groupby(list("ababb"))
+ actual = g.filter(lambda x: len(x) > 2)
+ expected = df.iloc[[1, 3, 4]]
+ tm.assert_frame_equal(actual, expected)
+
+ ser = df[0]
+ g = ser.groupby(list("ababb"))
+ actual = g.filter(lambda x: len(x) > 2)
+ expected = ser.take([1, 3, 4])
+ tm.assert_series_equal(actual, expected)
+
+
+def test_transform_doesnt_clobber_ints():
+ # GH 7972
+ n = 6
+ x = np.arange(n)
+ df = DataFrame({"a": x // 2, "b": 2.0 * x, "c": 3.0 * x})
+ df2 = DataFrame({"a": x // 2 * 1.0, "b": 2.0 * x, "c": 3.0 * x})
+
+ gb = df.groupby("a")
+ result = gb.transform("mean")
+
+ gb2 = df2.groupby("a")
+ expected = gb2.transform("mean")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "sort_column",
+ ["ints", "floats", "strings", ["ints", "floats"], ["ints", "strings"]],
+)
+@pytest.mark.parametrize(
+ "group_column", ["int_groups", "string_groups", ["int_groups", "string_groups"]]
+)
+def test_groupby_preserves_sort(sort_column, group_column):
+ # Test to ensure that groupby always preserves sort order of original
+ # object. Issue #8588 and #9651
+
+ df = DataFrame(
+ {
+ "int_groups": [3, 1, 0, 1, 0, 3, 3, 3],
+ "string_groups": ["z", "a", "z", "a", "a", "g", "g", "g"],
+ "ints": [8, 7, 4, 5, 2, 9, 1, 1],
+ "floats": [2.3, 5.3, 6.2, -2.4, 2.2, 1.1, 1.1, 5],
+ "strings": ["z", "d", "a", "e", "word", "word2", "42", "47"],
+ }
+ )
+
+ # Try sorting on different types and with different group types
+
+ df = df.sort_values(by=sort_column)
+ g = df.groupby(group_column)
+
+ def test_sort(x):
+ tm.assert_frame_equal(x, x.sort_values(by=sort_column))
+
+ g.apply(test_sort)
+
+
+def test_pivot_table_values_key_error():
+ # This test is designed to replicate the error in issue #14938
+ df = DataFrame(
+ {
+ "eventDate": date_range(datetime.today(), periods=20, freq="ME").tolist(),
+ "thename": range(20),
+ }
+ )
+
+ df["year"] = df.set_index("eventDate").index.year
+ df["month"] = df.set_index("eventDate").index.month
+
+ with pytest.raises(KeyError, match="'badname'"):
+ df.reset_index().pivot_table(
+ index="year", columns="month", values="badname", aggfunc="count"
+ )
+
+
+@pytest.mark.parametrize("columns", ["C", ["C"]])
+@pytest.mark.parametrize("keys", [["A"], ["A", "B"]])
+@pytest.mark.parametrize(
+ "values",
+ [
+ [True],
+ [0],
+ [0.0],
+ ["a"],
+ Categorical([0]),
+ [to_datetime(0)],
+ date_range(0, 1, 1, tz="US/Eastern"),
+ pd.period_range("2016-01-01", periods=3, freq="D"),
+ pd.array([0], dtype="Int64"),
+ pd.array([0], dtype="Float64"),
+ pd.array([False], dtype="boolean"),
+ ],
+ ids=[
+ "bool",
+ "int",
+ "float",
+ "str",
+ "cat",
+ "dt64",
+ "dt64tz",
+ "period",
+ "Int64",
+ "Float64",
+ "boolean",
+ ],
+)
+@pytest.mark.parametrize("method", ["attr", "agg", "apply"])
+@pytest.mark.parametrize(
+ "op", ["idxmax", "idxmin", "min", "max", "sum", "prod", "skew", "kurt"]
+)
+def test_empty_groupby(columns, keys, values, method, op, dropna, using_infer_string):
+ # GH8093 & GH26411
+ override_dtype = None
+
+ if isinstance(values, BooleanArray) and op in ["sum", "prod"]:
+ # We expect to get Int64 back for these
+ override_dtype = "Int64"
+
+ if isinstance(values[0], bool) and op in ("prod", "sum"):
+ # sum/product of bools is an integer
+ override_dtype = "int64"
+
+ df = DataFrame({"A": values, "B": values, "C": values}, columns=list("ABC"))
+
+ if hasattr(values, "dtype"):
+ # check that we did the construction right
+ assert (df.dtypes == values.dtype).all()
+
+ df = df.iloc[:0]
+
+ gb = df.groupby(keys, group_keys=False, dropna=dropna, observed=False)[columns]
+
+ def get_result(**kwargs):
+ if method == "attr":
+ return getattr(gb, op)(**kwargs)
+ else:
+ return getattr(gb, method)(op, **kwargs)
+
+ def get_categorical_invalid_expected():
+ # Categorical is special without 'observed=True', we get a NaN entry
+ # corresponding to the unobserved group. If we passed observed=True
+ # to groupby, expected would just be 'df.set_index(keys)[columns]'
+ # as below
+ lev = Categorical([0], dtype=values.dtype)
+ if len(keys) != 1:
+ idx = MultiIndex.from_product([lev, lev], names=keys)
+ else:
+ # all columns are dropped, but we end up with one row
+ # Categorical is special without 'observed=True'
+ idx = Index(lev, name=keys[0])
+
+ if using_infer_string:
+ columns = Index([], dtype="str")
+ else:
+ columns = []
+ expected = DataFrame([], columns=columns, index=idx)
+ return expected
+
+ is_per = isinstance(df.dtypes.iloc[0], pd.PeriodDtype)
+ is_dt64 = df.dtypes.iloc[0].kind == "M"
+ is_cat = isinstance(values, Categorical)
+ is_str = isinstance(df.dtypes.iloc[0], pd.StringDtype)
+
+ if (
+ isinstance(values, Categorical)
+ and not values.ordered
+ and op in ["min", "max", "idxmin", "idxmax"]
+ ):
+ if op in ["min", "max"]:
+ msg = f"Cannot perform {op} with non-ordered Categorical"
+ klass = TypeError
+ else:
+ msg = f"Can't get {op} of an empty group due to unobserved categories"
+ klass = ValueError
+ with pytest.raises(klass, match=msg):
+ get_result()
+
+ if op in ["min", "max", "idxmin", "idxmax"] and isinstance(columns, list):
+ # i.e. DataframeGroupBy, not SeriesGroupBy
+ result = get_result(numeric_only=True)
+ expected = get_categorical_invalid_expected()
+ tm.assert_equal(result, expected)
+ return
+
+ if op in ["prod", "sum", "skew", "kurt"]:
+ # ops that require more than just ordered-ness
+ if is_dt64 or is_cat or is_per or (is_str and op != "sum"):
+ # GH#41291
+ # datetime64 -> prod and sum are invalid
+ if is_dt64:
+ msg = "datetime64 type does not support"
+ elif is_per:
+ msg = "Period type does not support"
+ elif is_str:
+ msg = f"dtype 'str' does not support operation '{op}'"
+ else:
+ msg = "category type does not support"
+ if op in ["skew", "kurt"]:
+ msg = "|".join([msg, f"does not support operation '{op}'"])
+ with pytest.raises(TypeError, match=msg):
+ get_result()
+
+ if not isinstance(columns, list):
+ # i.e. SeriesGroupBy
+ return
+ elif op in ["skew", "kurt"]:
+ # TODO: test the numeric_only=True case
+ return
+ else:
+ # i.e. op in ["prod", "sum"]:
+ # i.e. DataFrameGroupBy
+ # ops that require more than just ordered-ness
+ # GH#41291
+ result = get_result(numeric_only=True)
+
+ # with numeric_only=True, these are dropped, and we get
+ # an empty DataFrame back
+ expected = df.set_index(keys)[[]]
+ if is_cat:
+ expected = get_categorical_invalid_expected()
+ tm.assert_equal(result, expected)
+ return
+
+ result = get_result()
+ expected = df.set_index(keys)[columns]
+ if op in ["idxmax", "idxmin"]:
+ expected = expected.astype(df.index.dtype)
+ if override_dtype is not None:
+ expected = expected.astype(override_dtype)
+ if len(keys) == 1:
+ expected.index.name = keys[0]
+ tm.assert_equal(result, expected)
+
+
+def test_empty_groupby_apply_nonunique_columns():
+ # GH#44417
+ df = DataFrame(np.random.default_rng(2).standard_normal((0, 4)))
+ df[3] = df[3].astype(np.int64)
+ df.columns = [0, 1, 2, 0]
+ gb = df.groupby(df[1], group_keys=False)
+ res = gb.apply(lambda x: x)
+ assert (res.dtypes == df.drop(columns=1).dtypes).all()
+
+
+def test_tuple_as_grouping():
+ # https://github.com/pandas-dev/pandas/issues/18314
+ df = DataFrame(
+ {
+ ("a", "b"): [1, 1, 1, 1],
+ "a": [2, 2, 2, 2],
+ "b": [2, 2, 2, 2],
+ "c": [1, 1, 1, 1],
+ }
+ )
+
+ with pytest.raises(KeyError, match=r"('a', 'b')"):
+ df[["a", "b", "c"]].groupby(("a", "b"))
+
+ result = df.groupby(("a", "b"))["c"].sum()
+ expected = Series([4], name="c", index=Index([1], name=("a", "b")))
+ tm.assert_series_equal(result, expected)
+
+
+def test_tuple_correct_keyerror():
+ # https://github.com/pandas-dev/pandas/issues/18798
+ df = DataFrame(1, index=range(3), columns=MultiIndex.from_product([[1, 2], [3, 4]]))
+ with pytest.raises(KeyError, match=r"^\(7, 8\)$"):
+ df.groupby((7, 8)).mean()
+
+
+def test_groupby_agg_ohlc_non_first():
+ # GH 21716
+ df = DataFrame(
+ [[1], [1]],
+ columns=Index(["foo"], name="mycols"),
+ index=date_range("2018-01-01", periods=2, freq="D", name="dti"),
+ )
+
+ expected = DataFrame(
+ [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1]],
+ columns=MultiIndex.from_tuples(
+ (
+ ("foo", "sum", "foo"),
+ ("foo", "ohlc", "open"),
+ ("foo", "ohlc", "high"),
+ ("foo", "ohlc", "low"),
+ ("foo", "ohlc", "close"),
+ ),
+ names=["mycols", None, None],
+ ),
+ index=date_range("2018-01-01", periods=2, freq="D", name="dti"),
+ )
+
+ result = df.groupby(Grouper(freq="D")).agg(["sum", "ohlc"])
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_multiindex_nat():
+ # GH 9236
+ values = [
+ (pd.NaT, "a"),
+ (datetime(2012, 1, 2), "a"),
+ (datetime(2012, 1, 2), "b"),
+ (datetime(2012, 1, 3), "a"),
+ ]
+ mi = MultiIndex.from_tuples(values, names=["date", None])
+ ser = Series([3, 2, 2.5, 4], index=mi)
+
+ result = ser.groupby(level=1).mean()
+ expected = Series([3.0, 2.5], index=["a", "b"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_empty_list_raises():
+ # GH 5289
+ values = zip(range(10), range(10), strict=True)
+ df = DataFrame(values, columns=["apple", "b"])
+ msg = "Grouper and axis must be same length"
+ with pytest.raises(ValueError, match=msg):
+ df.groupby([[]])
+
+
+def test_groupby_multiindex_series_keys_len_equal_group_axis():
+ # GH 25704
+ index_array = [["x", "x"], ["a", "b"], ["k", "k"]]
+ index_names = ["first", "second", "third"]
+ ri = MultiIndex.from_arrays(index_array, names=index_names)
+ s = Series(data=[1, 2], index=ri)
+ result = s.groupby(["first", "third"]).sum()
+
+ index_array = [["x"], ["k"]]
+ index_names = ["first", "third"]
+ ei = MultiIndex.from_arrays(index_array, names=index_names)
+ expected = Series([3], index=ei)
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_groups_in_BaseGrouper():
+ # GH 26326
+ # Test if DataFrame grouped with a pandas.Grouper has correct groups
+ mi = MultiIndex.from_product([["A", "B"], ["C", "D"]], names=["alpha", "beta"])
+ df = DataFrame({"foo": [1, 2, 1, 2], "bar": [1, 2, 3, 4]}, index=mi)
+ result = df.groupby([Grouper(level="alpha"), "beta"])
+ expected = df.groupby(["alpha", "beta"])
+ assert result.groups == expected.groups
+
+ result = df.groupby(["beta", Grouper(level="alpha")])
+ expected = df.groupby(["beta", "alpha"])
+ assert result.groups == expected.groups
+
+
+def test_groups_sort_dropna(sort, dropna):
+ # GH#56966, GH#56851
+ df = DataFrame([[2.0, 1.0], [np.nan, 4.0], [0.0, 3.0]])
+ keys = [(2.0, 1.0), (np.nan, 4.0), (0.0, 3.0)]
+ values = [
+ RangeIndex(0, 1),
+ RangeIndex(1, 2),
+ RangeIndex(2, 3),
+ ]
+ if sort:
+ taker = [2, 0] if dropna else [2, 0, 1]
+ else:
+ taker = [0, 2] if dropna else [0, 1, 2]
+ expected = {keys[idx]: values[idx] for idx in taker}
+
+ gb = df.groupby([0, 1], sort=sort, dropna=dropna)
+ result = gb.groups
+
+ for result_key, expected_key in zip(result.keys(), expected.keys(), strict=True):
+ # Compare as NumPy arrays to handle np.nan
+ result_key = np.array(result_key)
+ expected_key = np.array(expected_key)
+ tm.assert_numpy_array_equal(result_key, expected_key)
+ for result_value, expected_value in zip(
+ result.values(), expected.values(), strict=True
+ ):
+ tm.assert_index_equal(result_value, expected_value)
+
+
+@pytest.mark.parametrize(
+ "op, expected",
+ [
+ (
+ "shift",
+ {
+ "time": [
+ None,
+ None,
+ Timestamp("2019-01-01 12:00:00"),
+ Timestamp("2019-01-01 12:30:00"),
+ None,
+ None,
+ ]
+ },
+ ),
+ (
+ "bfill",
+ {
+ "time": [
+ Timestamp("2019-01-01 12:00:00"),
+ Timestamp("2019-01-01 12:30:00"),
+ Timestamp("2019-01-01 14:00:00"),
+ Timestamp("2019-01-01 14:30:00"),
+ Timestamp("2019-01-01 14:00:00"),
+ Timestamp("2019-01-01 14:30:00"),
+ ]
+ },
+ ),
+ (
+ "ffill",
+ {
+ "time": [
+ Timestamp("2019-01-01 12:00:00"),
+ Timestamp("2019-01-01 12:30:00"),
+ Timestamp("2019-01-01 12:00:00"),
+ Timestamp("2019-01-01 12:30:00"),
+ Timestamp("2019-01-01 14:00:00"),
+ Timestamp("2019-01-01 14:30:00"),
+ ]
+ },
+ ),
+ ],
+)
+def test_shift_bfill_ffill_tz(tz_naive_fixture, op, expected):
+ # GH19995, GH27992: Check that timezone does not drop in shift, bfill, and ffill
+ tz = tz_naive_fixture
+ data = {
+ "id": ["A", "B", "A", "B", "A", "B"],
+ "time": [
+ Timestamp("2019-01-01 12:00:00"),
+ Timestamp("2019-01-01 12:30:00"),
+ None,
+ None,
+ Timestamp("2019-01-01 14:00:00"),
+ Timestamp("2019-01-01 14:30:00"),
+ ],
+ }
+ df = DataFrame(data).assign(time=lambda x: x.time.dt.tz_localize(tz))
+
+ grouped = df.groupby("id")
+ result = getattr(grouped, op)()
+ expected = DataFrame(expected).assign(time=lambda x: x.time.dt.tz_localize(tz))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_only_none_group():
+ # see GH21624
+ # this was crashing with "ValueError: Length of passed values is 1, index implies 0"
+ df = DataFrame({"g": [None], "x": 1})
+ actual = df.groupby("g")["x"].transform("sum")
+ expected = Series([np.nan], name="x")
+
+ tm.assert_series_equal(actual, expected)
+
+
+def test_groupby_duplicate_index():
+ # GH#29189 the groupby call here used to raise
+ ser = Series([2, 5, 6, 8], index=[2.0, 4.0, 4.0, 5.0])
+ gb = ser.groupby(level=0)
+
+ result = gb.mean()
+ expected = Series([2, 5.5, 8], index=[2.0, 4.0, 5.0])
+ tm.assert_series_equal(result, expected)
+
+
+def test_group_on_empty_multiindex(transformation_func, request):
+ # GH 47787
+ # With one row, those are transforms so the schema should be the same
+ df = DataFrame(
+ data=[[1, Timestamp("today"), 3, 4]],
+ columns=["col_1", "col_2", "col_3", "col_4"],
+ )
+ df["col_3"] = df["col_3"].astype(int)
+ df["col_4"] = df["col_4"].astype(int)
+ df = df.set_index(["col_1", "col_2"])
+ result = df.iloc[:0].groupby(["col_1"]).transform(transformation_func)
+ expected = df.groupby(["col_1"]).transform(transformation_func).iloc[:0]
+ if transformation_func in ("diff", "shift"):
+ expected = expected.astype(int)
+ tm.assert_equal(result, expected)
+
+ result = df["col_3"].iloc[:0].groupby(["col_1"]).transform(transformation_func)
+ expected = df["col_3"].groupby(["col_1"]).transform(transformation_func).iloc[:0]
+ if transformation_func in ("diff", "shift"):
+ expected = expected.astype(int)
+ tm.assert_equal(result, expected)
+
+
+def test_groupby_crash_on_nunique():
+ # Fix following 30253
+ dti = date_range("2016-01-01", periods=2, name="foo")
+ df = DataFrame({("A", "B"): [1, 2], ("A", "C"): [1, 3], ("D", "B"): [0, 0]})
+ df.columns.names = ("bar", "baz")
+ df.index = dti
+
+ df = df.T
+ gb = df.groupby(level=0)
+ result = gb.nunique()
+
+ expected = DataFrame({"A": [1, 2], "D": [1, 1]}, index=dti)
+ expected.columns.name = "bar"
+ expected = expected.T
+
+ tm.assert_frame_equal(result, expected)
+
+ # same thing, but empty columns
+ gb2 = df[[]].groupby(level=0)
+ exp = expected[[]]
+
+ res = gb2.nunique()
+ tm.assert_frame_equal(res, exp)
+
+
+def test_groupby_list_level():
+ # GH 9790
+ expected = DataFrame(np.arange(0, 9).reshape(3, 3), dtype=float)
+ result = expected.groupby(level=[0]).mean()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "max_seq_items, expected",
+ [
+ (5, "{0: [0], 1: [1], 2: [2], 3: [3], 4: [4]}"),
+ (4, "{0: [0], 1: [1], 2: [2], 3: [3], ...}"),
+ (1, "{0: [0], ...}"),
+ ],
+)
+def test_groups_repr_truncates(max_seq_items, expected):
+ # GH 1135
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 1)))
+ df["a"] = df.index
+
+ with pd.option_context("display.max_seq_items", max_seq_items):
+ result = df.groupby("a").groups.__repr__()
+ assert result == expected
+
+ result = df.groupby(np.array(df.a)).groups.__repr__()
+ assert result == expected
+
+
+def test_group_on_two_row_multiindex_returns_one_tuple_key():
+ # GH 18451
+ df = DataFrame([{"a": 1, "b": 2, "c": 99}, {"a": 1, "b": 2, "c": 88}])
+ df = df.set_index(["a", "b"])
+
+ grp = df.groupby(["a", "b"])
+ result = grp.indices
+ expected = {(1, 2): np.array([0, 1], dtype=np.int64)}
+
+ assert len(result) == 1
+ key = (1, 2)
+ assert (result[key] == expected[key]).all()
+
+
+@pytest.mark.parametrize(
+ "klass, attr, value",
+ [
+ (DataFrame, "level", "a"),
+ (DataFrame, "as_index", False),
+ (DataFrame, "sort", False),
+ (DataFrame, "group_keys", False),
+ (DataFrame, "observed", True),
+ (DataFrame, "dropna", False),
+ (Series, "level", "a"),
+ (Series, "as_index", False),
+ (Series, "sort", False),
+ (Series, "group_keys", False),
+ (Series, "observed", True),
+ (Series, "dropna", False),
+ ],
+)
+def test_subsetting_columns_keeps_attrs(klass, attr, value):
+ # GH 9959 - When subsetting columns, don't drop attributes
+ df = DataFrame({"a": [1], "b": [2], "c": [3]})
+ if attr != "axis":
+ df = df.set_index("a")
+
+ expected = df.groupby("a", **{attr: value})
+ result = expected[["b"]] if klass is DataFrame else expected["b"]
+ assert getattr(result, attr) == getattr(expected, attr)
+
+
+@pytest.mark.parametrize("func", ["sum", "any", "shift"])
+def test_groupby_column_index_name_lost(func):
+ # GH: 29764 groupby loses index sometimes
+ expected = Index(["a"], name="idx")
+ df = DataFrame([[1]], columns=expected)
+ df_grouped = df.groupby([1])
+ result = getattr(df_grouped, func)().columns
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "infer_string",
+ [
+ False,
+ pytest.param(True, marks=td.skip_if_no("pyarrow")),
+ ],
+)
+def test_groupby_duplicate_columns(infer_string):
+ # GH: 31735
+ if infer_string:
+ pytest.importorskip("pyarrow")
+ df = DataFrame(
+ {"A": ["f", "e", "g", "h"], "B": ["a", "b", "c", "d"], "C": [1, 2, 3, 4]}
+ ).astype(object)
+ df.columns = ["A", "B", "B"]
+ with pd.option_context("future.infer_string", infer_string):
+ result = df.groupby([0, 0, 0, 0]).min()
+ expected = DataFrame(
+ [["e", "a", 1]], index=np.array([0]), columns=["A", "B", "B"], dtype=object
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_series_with_tuple_name():
+ # GH 37755
+ ser = Series([1, 2, 3, 4], index=[1, 1, 2, 2], name=("a", "a"))
+ ser.index.name = ("b", "b")
+ result = ser.groupby(level=0).last()
+ expected = Series([2, 4], index=[1, 2], name=("a", "a"))
+ expected.index.name = ("b", "b")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func, values", [("sum", [97.0, 98.0]), ("mean", [24.25, 24.5])]
+)
+def test_groupby_numerical_stability_sum_mean(func, values):
+ # GH#38778
+ data = [1e16, 1e16, 97, 98, -5e15, -5e15, -5e15, -5e15]
+ df = DataFrame({"group": [1, 2] * 4, "a": data, "b": data})
+ result = getattr(df.groupby("group"), func)()
+ expected = DataFrame({"a": values, "b": values}, index=Index([1, 2], name="group"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_numerical_stability_cumsum():
+ # GH#38934
+ data = [1e16, 1e16, 97, 98, -5e15, -5e15, -5e15, -5e15]
+ df = DataFrame({"group": [1, 2] * 4, "a": data, "b": data})
+ result = df.groupby("group").cumsum()
+ exp_data = (
+ [1e16] * 2 + [1e16 + 96, 1e16 + 98] + [5e15 + 97, 5e15 + 98] + [97.0, 98.0]
+ )
+ expected = DataFrame({"a": exp_data, "b": exp_data})
+ tm.assert_frame_equal(result, expected, check_exact=True)
+
+
+def test_groupby_cumsum_skipna_false():
+ # GH#46216 don't propagate np.nan above the diagonal
+ arr = np.random.default_rng(2).standard_normal((5, 5))
+ df = DataFrame(arr)
+ for i in range(5):
+ df.iloc[i, i] = np.nan
+
+ df["A"] = 1
+ gb = df.groupby("A")
+
+ res = gb.cumsum(skipna=False)
+
+ expected = df[[0, 1, 2, 3, 4]].cumsum(skipna=False)
+ tm.assert_frame_equal(res, expected)
+
+
+def test_groupby_cumsum_timedelta64():
+ # GH#46216 don't ignore is_datetimelike in libgroupby.group_cumsum
+ dti = date_range("2016-01-01", periods=5, unit="ns")
+ ser = Series(dti) - dti[0]
+ ser[2] = pd.NaT
+
+ df = DataFrame({"A": 1, "B": ser})
+ gb = df.groupby("A")
+
+ res = gb.cumsum(numeric_only=False, skipna=True)
+ exp = DataFrame({"B": [ser[0], ser[1], pd.NaT, ser[4], ser[4] * 2]})
+ tm.assert_frame_equal(res, exp)
+
+ res = gb.cumsum(numeric_only=False, skipna=False)
+ exp = DataFrame({"B": [ser[0], ser[1], pd.NaT, pd.NaT, pd.NaT]})
+ tm.assert_frame_equal(res, exp)
+
+
+def test_groupby_mean_duplicate_index(rand_series_with_duplicate_datetimeindex):
+ dups = rand_series_with_duplicate_datetimeindex
+ result = dups.groupby(level=0).mean()
+ expected = dups.groupby(dups.index).mean()
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_all_nan_groups_drop():
+ # GH 15036
+ s = Series([1, 2, 3], [np.nan, np.nan, np.nan])
+ result = s.groupby(s.index).sum()
+ expected = Series([], index=Index([], dtype=np.float64), dtype=np.int64)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("numeric_only", [True, False])
+def test_groupby_empty_multi_column(as_index, numeric_only):
+ # GH 15106 & GH 41998
+ df = DataFrame(data=[], columns=["A", "B", "C"])
+ gb = df.groupby(["A", "B"], as_index=as_index)
+ result = gb.sum(numeric_only=numeric_only)
+ if as_index:
+ index = MultiIndex([[], []], [[], []], names=["A", "B"])
+ columns = ["C"] if not numeric_only else Index([], dtype="str")
+ else:
+ index = RangeIndex(0)
+ columns = ["A", "B", "C"] if not numeric_only else ["A", "B"]
+ expected = DataFrame([], columns=columns, index=index)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregation_non_numeric_dtype():
+ # GH #43108
+ df = DataFrame(
+ [["M", [1]], ["M", [1]], ["W", [10]], ["W", [20]]], columns=["MW", "v"]
+ )
+
+ expected = DataFrame(
+ {
+ "v": [[1, 1], [10, 20]],
+ },
+ index=Index(["M", "W"], name="MW"),
+ )
+
+ gb = df.groupby(by=["MW"])
+ result = gb.sum()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregation_multi_non_numeric_dtype():
+ # GH #42395
+ df = DataFrame(
+ {
+ "x": [1, 0, 1, 1, 0],
+ "y": [Timedelta(i, "days") for i in range(1, 6)],
+ "z": [Timedelta(i * 10, "days") for i in range(1, 6)],
+ }
+ )
+
+ expected = DataFrame(
+ {
+ "y": [Timedelta(i, "days") for i in range(7, 9)],
+ "z": [Timedelta(i * 10, "days") for i in range(7, 9)],
+ },
+ index=Index([0, 1], dtype="int64", name="x"),
+ )
+
+ gb = df.groupby(by=["x"])
+ result = gb.sum()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_aggregation_numeric_with_non_numeric_dtype():
+ # GH #43108
+ df = DataFrame(
+ {
+ "x": [1, 0, 1, 1, 0],
+ "y": [Timedelta(i, "days") for i in range(1, 6)],
+ "z": list(range(1, 6)),
+ }
+ )
+
+ expected = DataFrame(
+ {"y": [Timedelta(7, "days"), Timedelta(8, "days")], "z": [7, 8]},
+ index=Index([0, 1], dtype="int64", name="x"),
+ )
+
+ gb = df.groupby(by=["x"])
+ result = gb.sum()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_filtered_df_std():
+ # GH 16174
+ dicts = [
+ {"filter_col": False, "groupby_col": True, "bool_col": True, "float_col": 10.5},
+ {"filter_col": True, "groupby_col": True, "bool_col": True, "float_col": 20.5},
+ {"filter_col": True, "groupby_col": True, "bool_col": True, "float_col": 30.5},
+ ]
+ df = DataFrame(dicts)
+
+ df_filter = df[df["filter_col"] == True] # noqa: E712
+ dfgb = df_filter.groupby("groupby_col")
+ result = dfgb.std()
+ expected = DataFrame(
+ [[0.0, 0.0, 7.071068]],
+ columns=["filter_col", "bool_col", "float_col"],
+ index=Index([True], name="groupby_col"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_datetime_categorical_multikey_groupby_indices():
+ # GH 26859
+ df = DataFrame(
+ {
+ "a": Series(list("abc")),
+ "b": Series(
+ to_datetime(["2018-01-01", "2018-02-01", "2018-03-01"]),
+ dtype="category",
+ ),
+ "c": Categorical.from_codes([-1, 0, 1], categories=[0, 1]),
+ }
+ )
+ result = df.groupby(["a", "b"], observed=False).indices
+ expected = {
+ ("a", Timestamp("2018-01-01 00:00:00")): np.array([0]),
+ ("b", Timestamp("2018-02-01 00:00:00")): np.array([1]),
+ ("c", Timestamp("2018-03-01 00:00:00")): np.array([2]),
+ }
+ assert result == expected
+
+
+def test_rolling_wrong_param_min_period():
+ # GH34037
+ name_l = ["Alice"] * 5 + ["Bob"] * 5
+ val_l = [np.nan, np.nan, 1, 2, 3, np.nan, 1, 2, 3, 4]
+ test_df = DataFrame([name_l, val_l]).T
+ test_df.columns = ["name", "val"]
+
+ result_error_msg = (
+ r"^[a-zA-Z._]*\(\) got an unexpected keyword argument 'min_period'"
+ )
+ with pytest.raises(TypeError, match=result_error_msg):
+ test_df.groupby("name")["val"].rolling(window=2, min_period=1).sum()
+
+
+def test_by_column_values_with_same_starting_value(any_string_dtype):
+ # GH29635
+ dtype = any_string_dtype
+ df = DataFrame(
+ {
+ "Name": ["Thomas", "Thomas", "Thomas John"],
+ "Credit": [1200, 1300, 900],
+ "Mood": Series(["sad", "happy", "happy"], dtype=dtype),
+ }
+ )
+ aggregate_details = {"Mood": Series.mode, "Credit": "sum"}
+
+ result = df.groupby(["Name"]).agg(aggregate_details)
+ expected = DataFrame(
+ {
+ "Mood": [["happy", "sad"], "happy"],
+ "Credit": [2500, 900],
+ "Name": ["Thomas", "Thomas John"],
+ },
+ ).set_index("Name")
+ if getattr(dtype, "storage", None) == "pyarrow":
+ mood_values = pd.array(["happy", "sad"], dtype=dtype)
+ expected["Mood"] = [mood_values, "happy"]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_none_in_first_mi_level():
+ # GH#47348
+ arr = [[None, 1, 0, 1], [2, 3, 2, 3]]
+ ser = Series(1, index=MultiIndex.from_arrays(arr, names=["a", "b"]))
+ result = ser.groupby(level=[0, 1]).sum()
+ expected = Series(
+ [1, 2], MultiIndex.from_tuples([(0.0, 2), (1.0, 3)], names=["a", "b"])
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_none_column_name(using_infer_string):
+ # GH#47348
+ df = DataFrame({None: [1, 1, 2, 2], "b": [1, 1, 2, 3], "c": [4, 5, 6, 7]})
+ by = [np.nan] if using_infer_string else [None]
+ gb = df.groupby(by=by)
+ result = gb.sum()
+ expected = DataFrame({"b": [2, 5], "c": [9, 13]}, index=Index([1, 2], name=by[0]))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("selection", [None, "a", ["a"]])
+def test_single_element_list_grouping(selection):
+ # GH#42795, GH#53500
+ df = DataFrame({"a": [1, 2], "b": [np.nan, 5], "c": [np.nan, 2]}, index=["x", "y"])
+ grouped = df.groupby(["a"]) if selection is None else df.groupby(["a"])[selection]
+ result = [key for key, _ in grouped]
+
+ expected = [(1,), (2,)]
+ assert result == expected
+
+
+def test_groupby_string_dtype():
+ # GH 40148
+ df = DataFrame({"str_col": ["a", "b", "c", "a"], "num_col": [1, 2, 3, 2]})
+ df["str_col"] = df["str_col"].astype("string")
+ expected = DataFrame(
+ {
+ "str_col": [
+ "a",
+ "b",
+ "c",
+ ],
+ "num_col": [1.5, 2.0, 3.0],
+ }
+ )
+ expected["str_col"] = expected["str_col"].astype("string")
+ grouped = df.groupby("str_col", as_index=False)
+ result = grouped.mean()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "level_arg, multiindex", [([0], False), ((0,), False), ([0], True), ((0,), True)]
+)
+def test_single_element_listlike_level_grouping(level_arg, multiindex):
+ # GH 51583
+ df = DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6]}, index=["x", "y"])
+ if multiindex:
+ df = df.set_index(["a", "b"])
+ result = [key for key, _ in df.groupby(level=level_arg)]
+ expected = [(1,), (2,)] if multiindex else [("x",), ("y",)]
+ assert result == expected
+
+
+@pytest.mark.parametrize("func", ["sum", "cumsum", "cumprod", "prod"])
+def test_groupby_avoid_casting_to_float(func):
+ # GH#37493
+ val = 922337203685477580
+ df = DataFrame({"a": 1, "b": [val]})
+ result = getattr(df.groupby("a"), func)() - val
+ expected = DataFrame({"b": [0]}, index=Index([1], name="a"))
+ if func in ["cumsum", "cumprod"]:
+ expected = expected.reset_index(drop=True)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("func, val", [("sum", 3), ("prod", 2)])
+def test_groupby_sum_support_mask(any_numeric_ea_dtype, func, val):
+ # GH#37493
+ df = DataFrame({"a": 1, "b": [1, 2, pd.NA]}, dtype=any_numeric_ea_dtype)
+ result = getattr(df.groupby("a"), func)()
+ expected = DataFrame(
+ {"b": [val]},
+ index=Index([1], name="a", dtype=any_numeric_ea_dtype),
+ dtype=any_numeric_ea_dtype,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("val, dtype", [(111, "int"), (222, "uint")])
+def test_groupby_overflow(val, dtype):
+ # GH#37493
+ df = DataFrame({"a": 1, "b": [val, val]}, dtype=f"{dtype}8")
+ result = df.groupby("a").sum()
+ expected = DataFrame(
+ {"b": [val * 2]},
+ index=Index([1], name="a", dtype=f"{dtype}8"),
+ dtype=f"{dtype}64",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("a").cumsum()
+ expected = DataFrame({"b": [val, val * 2]}, dtype=f"{dtype}64")
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("a").prod()
+ expected = DataFrame(
+ {"b": [val * val]},
+ index=Index([1], name="a", dtype=f"{dtype}8"),
+ dtype=f"{dtype}64",
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("skipna, val", [(True, 3), (False, pd.NA)])
+def test_groupby_cumsum_mask(any_numeric_ea_dtype, skipna, val):
+ # GH#37493
+ df = DataFrame({"a": 1, "b": [1, pd.NA, 2]}, dtype=any_numeric_ea_dtype)
+ result = df.groupby("a").cumsum(skipna=skipna)
+ expected = DataFrame(
+ {"b": [1, pd.NA, val]},
+ dtype=any_numeric_ea_dtype,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "val_in, index, val_out",
+ [
+ (
+ [1.0, 2.0, 3.0, 4.0, 5.0],
+ ["foo", "foo", "bar", "baz", "blah"],
+ [3.0, 4.0, 5.0, 3.0],
+ ),
+ (
+ [1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
+ ["foo", "foo", "bar", "baz", "blah", "blah"],
+ [3.0, 4.0, 11.0, 3.0],
+ ),
+ ],
+)
+def test_groupby_index_name_in_index_content(val_in, index, val_out):
+ # GH 48567
+ series = Series(data=val_in, name="values", index=Index(index, name="blah"))
+ result = series.groupby("blah").sum()
+ expected = Series(
+ data=val_out,
+ name="values",
+ index=Index(["bar", "baz", "blah", "foo"], name="blah"),
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = series.to_frame().groupby("blah").sum()
+ expected = expected.to_frame()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("n", [1, 10, 32, 100, 1000])
+def test_sum_of_booleans(n):
+ # GH 50347
+ df = DataFrame({"groupby_col": 1, "bool": [True] * n})
+ df["bool"] = df["bool"].eq(True)
+ result = df.groupby("groupby_col").sum()
+ expected = DataFrame({"bool": [n]}, index=Index([1], name="groupby_col"))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in remainder:RuntimeWarning"
+)
+@pytest.mark.parametrize("method", ["head", "tail", "nth", "first", "last"])
+def test_groupby_method_drop_na(method):
+ # GH 21755
+ df = DataFrame({"A": ["a", np.nan, "b", np.nan, "c"], "B": range(5)})
+
+ if method == "nth":
+ result = getattr(df.groupby("A"), method)(n=0)
+ else:
+ result = getattr(df.groupby("A"), method)()
+
+ if method in ["first", "last"]:
+ expected = DataFrame({"B": [0, 2, 4]}).set_index(
+ Series(["a", "b", "c"], name="A")
+ )
+ else:
+ expected = DataFrame(
+ {"A": ["a", "b", "c"], "B": [0, 2, 4]}, index=range(0, 6, 2)
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_reduce_period():
+ # GH#51040
+ pi = pd.period_range("2016-01-01", periods=100, freq="D")
+ grps = list(range(10)) * 10
+ ser = pi.to_series()
+ gb = ser.groupby(grps)
+
+ with pytest.raises(TypeError, match="Period type does not support sum operations"):
+ gb.sum()
+ with pytest.raises(
+ TypeError, match="Period type does not support cumsum operations"
+ ):
+ gb.cumsum()
+ with pytest.raises(TypeError, match="Period type does not support prod operations"):
+ gb.prod()
+ with pytest.raises(
+ TypeError, match="Period type does not support cumprod operations"
+ ):
+ gb.cumprod()
+
+ res = gb.max()
+ expected = ser[-10:]
+ expected.index = Index(range(10), dtype=int)
+ tm.assert_series_equal(res, expected)
+
+ res = gb.min()
+ expected = ser[:10]
+ expected.index = Index(range(10), dtype=int)
+ tm.assert_series_equal(res, expected)
+
+
+def test_obj_with_exclusions_duplicate_columns():
+ # GH#50806
+ df = DataFrame([[0, 1, 2, 3]])
+ df.columns = [0, 1, 2, 0]
+ gb = df.groupby(df[1])
+ result = gb._obj_with_exclusions
+ expected = df.take([0, 2, 3], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("numeric_only", [True, False])
+def test_groupby_numeric_only_std_no_result(numeric_only):
+ # GH 51080
+ dicts_non_numeric = [{"a": "foo", "b": "bar"}, {"a": "car", "b": "dar"}]
+ df = DataFrame(dicts_non_numeric, dtype=object)
+ dfgb = df.groupby("a", as_index=False, sort=False)
+
+ if numeric_only:
+ result = dfgb.std(numeric_only=True)
+ expected_df = DataFrame(["foo", "car"], columns=["a"])
+ tm.assert_frame_equal(result, expected_df)
+ else:
+ with pytest.raises(
+ ValueError, match="could not convert string to float: 'bar'"
+ ):
+ dfgb.std(numeric_only=numeric_only)
+
+
+def test_grouping_with_categorical_interval_columns():
+ # GH#34164
+ df = DataFrame({"x": [0.1, 0.2, 0.3, -0.4, 0.5], "w": ["a", "b", "a", "c", "a"]})
+ qq = pd.qcut(df["x"], q=np.linspace(0, 1, 5))
+ result = df.groupby([qq, "w"], observed=False)["x"].agg("mean")
+ categorical_index_level_1 = Categorical(
+ [
+ Interval(-0.401, 0.1, closed="right"),
+ Interval(0.1, 0.2, closed="right"),
+ Interval(0.2, 0.3, closed="right"),
+ Interval(0.3, 0.5, closed="right"),
+ ],
+ ordered=True,
+ )
+ index_level_2 = ["a", "b", "c"]
+ mi = MultiIndex.from_product(
+ [categorical_index_level_1, index_level_2], names=["x", "w"]
+ )
+ expected = Series(
+ np.array(
+ [
+ 0.1,
+ np.nan,
+ -0.4,
+ np.nan,
+ 0.2,
+ np.nan,
+ 0.3,
+ np.nan,
+ np.nan,
+ 0.5,
+ np.nan,
+ np.nan,
+ ]
+ ),
+ index=mi,
+ name="x",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("bug_var", [1, "a"])
+def test_groupby_sum_on_nan_should_return_nan(bug_var):
+ # GH 24196
+ df = DataFrame({"A": [bug_var, bug_var, bug_var, np.nan]})
+ if isinstance(bug_var, str):
+ df = df.astype(object)
+ dfgb = df.groupby(lambda x: x)
+ result = dfgb.sum(min_count=1)
+
+ expected_df = DataFrame(
+ [bug_var, bug_var, bug_var, None], columns=["A"], dtype=df["A"].dtype
+ )
+ tm.assert_frame_equal(result, expected_df)
+
+
+@pytest.mark.parametrize(
+ "method",
+ [
+ "count",
+ "corr",
+ "cummax",
+ "cummin",
+ "cumprod",
+ "describe",
+ "rank",
+ "quantile",
+ "diff",
+ "shift",
+ "all",
+ "any",
+ "idxmin",
+ "idxmax",
+ "ffill",
+ "bfill",
+ "pct_change",
+ ],
+)
+def test_groupby_selection_with_methods(df, method):
+ # some methods which require DatetimeIndex
+ rng = date_range("2014", periods=len(df))
+ df.index = rng
+
+ g = df.groupby(["A"])[["C"]]
+ g_exp = df[["C"]].groupby(df["A"])
+ # TODO check groupby with > 1 col ?
+
+ res = getattr(g, method)()
+ exp = getattr(g_exp, method)()
+
+ # should always be frames!
+ tm.assert_frame_equal(res, exp)
+
+
+def test_groupby_selection_other_methods(df):
+ # some methods which require DatetimeIndex
+ rng = date_range("2014", periods=len(df))
+ df.columns.name = "foo"
+ df.index = rng
+
+ g = df.groupby(["A"])[["C"]]
+ g_exp = df[["C"]].groupby(df["A"])
+
+ # methods which aren't just .foo()
+ tm.assert_frame_equal(g.apply(lambda x: x.sum()), g_exp.apply(lambda x: x.sum()))
+
+ tm.assert_frame_equal(g.resample("D").mean(), g_exp.resample("D").mean())
+ tm.assert_frame_equal(g.resample("D").ohlc(), g_exp.resample("D").ohlc())
+
+ tm.assert_frame_equal(
+ g.filter(lambda x: len(x) == 3), g_exp.filter(lambda x: len(x) == 3)
+ )
+
+
+def test_groupby_with_Time_Grouper(unit):
+ idx2 = to_datetime(
+ [
+ "2016-08-31 22:08:12.000",
+ "2016-08-31 22:09:12.200",
+ "2016-08-31 22:20:12.400",
+ ]
+ ).as_unit(unit)
+
+ test_data = DataFrame(
+ {"quant": [1.0, 1.0, 3.0], "quant2": [1.0, 1.0, 3.0], "time2": idx2}
+ )
+
+ time2 = date_range("2016-08-31 22:08:00", periods=13, freq="1min", unit=unit)
+ expected_output = DataFrame(
+ {
+ "time2": time2,
+ "quant": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
+ "quant2": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
+ }
+ )
+
+ gb = test_data.groupby(Grouper(key="time2", freq="1min"))
+ result = gb.count().reset_index()
+
+ tm.assert_frame_equal(result, expected_output)
+
+
+def test_groupby_series_with_datetimeindex_month_name():
+ # GH 48509
+ s = Series([0, 1, 0], index=date_range("2022-01-01", periods=3), name="jan")
+ result = s.groupby(s).count()
+ expected = Series([2, 1], name="jan")
+ expected.index.name = "jan"
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("test_series", [True, False])
+@pytest.mark.parametrize(
+ "kwarg, value, name, warn",
+ [
+ ("by", "a", 1, None),
+ ("by", ["a"], (1,), None),
+ ("level", 0, 1, None),
+ ("level", [0], (1,), None),
+ ],
+)
+def test_get_group_len_1_list_likes(test_series, kwarg, value, name, warn):
+ # GH#25971
+ obj = DataFrame({"b": [3, 4, 5]}, index=Index([1, 1, 2], name="a"))
+ if test_series:
+ obj = obj["b"]
+ gb = obj.groupby(**{kwarg: value})
+ result = gb.get_group(name)
+ if test_series:
+ expected = Series([3, 4], index=Index([1, 1], name="a"), name="b")
+ else:
+ expected = DataFrame({"b": [3, 4]}, index=Index([1, 1], name="a"))
+ tm.assert_equal(result, expected)
+
+
+def test_groupby_ngroup_with_nan():
+ # GH#50100
+ df = DataFrame({"a": Categorical([np.nan]), "b": [1]})
+ result = df.groupby(["a", "b"], dropna=False, observed=False).ngroup()
+ expected = Series([0])
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_ffill_with_duplicated_index():
+ # GH#43412
+ df = DataFrame({"a": [1, 2, 3, 4, np.nan, np.nan]}, index=[0, 1, 2, 0, 1, 2])
+
+ result = df.groupby(level=0).ffill()
+ expected = DataFrame({"a": [1, 2, 3, 4, 2, 3]}, index=[0, 1, 2, 0, 1, 2])
+ tm.assert_frame_equal(result, expected, check_dtype=False)
+
+
+@pytest.mark.parametrize("test_series", [True, False])
+def test_decimal_na_sort(test_series):
+ # GH#54847
+ # We catch both TypeError and decimal.InvalidOperation exceptions in safe_sort.
+ # If this next assert raises, we can just catch TypeError
+ assert not isinstance(decimal.InvalidOperation, TypeError)
+ df = DataFrame(
+ {
+ "key": [Decimal(1), Decimal(1), None, None],
+ "value": [Decimal(2), Decimal(3), Decimal(4), Decimal(5)],
+ }
+ )
+ gb = df.groupby("key", dropna=False)
+ if test_series:
+ gb = gb["value"]
+ result = gb._grouper.result_index
+ expected = Index([Decimal(1), None], name="key")
+ tm.assert_index_equal(result, expected)
+
+
+def test_groupby_dropna_with_nunique_unique():
+ # GH#42016
+ df = [[1, 1, 1, "A"], [1, None, 1, "A"], [1, None, 2, "A"], [1, None, 3, "A"]]
+ df_dropna = DataFrame(df, columns=["a", "b", "c", "partner"])
+ result = df_dropna.groupby(["a", "b", "c"], dropna=False).agg(
+ {"partner": ["nunique", "unique"]}
+ )
+
+ index = MultiIndex.from_tuples(
+ [(1, 1.0, 1), (1, np.nan, 1), (1, np.nan, 2), (1, np.nan, 3)],
+ names=["a", "b", "c"],
+ )
+ columns = MultiIndex.from_tuples([("partner", "nunique"), ("partner", "unique")])
+ expected = DataFrame(
+ [(1, ["A"]), (1, ["A"]), (1, ["A"]), (1, ["A"])], index=index, columns=columns
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_agg_namedagg_with_duplicate_columns():
+ # GH#58446
+ df = DataFrame(
+ {
+ "col1": [2, 1, 1, 0, 2, 0],
+ "col2": [4, 5, 36, 7, 4, 5],
+ "col3": [3.1, 8.0, 12, 10, 4, 1.1],
+ "col4": [17, 3, 16, 15, 5, 6],
+ "col5": [-1, 3, -1, 3, -2, -1],
+ }
+ )
+
+ result = df.groupby(by=["col1", "col1", "col2"], as_index=False).agg(
+ new_col=pd.NamedAgg(column="col1", aggfunc="min"),
+ new_col1=pd.NamedAgg(column="col1", aggfunc="max"),
+ new_col2=pd.NamedAgg(column="col2", aggfunc="count"),
+ )
+
+ expected = DataFrame(
+ {
+ "col1": [0, 0, 1, 1, 2],
+ "col2": [5, 7, 5, 36, 4],
+ "new_col": [0, 0, 1, 1, 2],
+ "new_col1": [0, 0, 1, 1, 2],
+ "new_col2": [1, 1, 1, 1, 2],
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_multi_index_codes():
+ # GH#54347
+ df = DataFrame(
+ {"A": [1, 2, 3, 4], "B": [1, float("nan"), 2, float("nan")], "C": [2, 4, 6, 8]}
+ )
+ df_grouped = df.groupby(["A", "B"], dropna=False).sum()
+
+ index = df_grouped.index
+ tm.assert_index_equal(index, MultiIndex.from_frame(index.to_frame()))
+
+
+def test_groupby_datetime_with_nat():
+ # GH##35202
+ df = DataFrame(
+ {
+ "a": [
+ to_datetime("2019-02-12"),
+ to_datetime("2019-02-12"),
+ to_datetime("2019-02-13"),
+ pd.NaT,
+ ],
+ "b": [1, 2, 3, 4],
+ }
+ )
+ grouped = df.groupby("a", dropna=False)
+ result = len(grouped)
+ assert result == 3
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_groupby_dropna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_groupby_dropna.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ddeefedc217ff960b957d92a2f7ca169b2f9ba5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_groupby_dropna.py
@@ -0,0 +1,692 @@
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.missing import na_value_for_dtype
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.tests.groupby import get_groupby_method_args
+
+
+@pytest.mark.parametrize(
+ "dropna, tuples, outputs",
+ [
+ (
+ True,
+ [["A", "B"], ["B", "A"]],
+ {"c": [13.0, 123.23], "d": [13.0, 123.0], "e": [13.0, 1.0]},
+ ),
+ (
+ False,
+ [["A", "B"], ["A", np.nan], ["B", "A"]],
+ {
+ "c": [13.0, 12.3, 123.23],
+ "d": [13.0, 233.0, 123.0],
+ "e": [13.0, 12.0, 1.0],
+ },
+ ),
+ ],
+)
+def test_groupby_dropna_multi_index_dataframe_nan_in_one_group(
+ dropna, tuples, outputs, nulls_fixture
+):
+ # GH 3729 this is to test that NA is in one group
+ df_list = [
+ ["A", "B", 12, 12, 12],
+ ["A", nulls_fixture, 12.3, 233.0, 12],
+ ["B", "A", 123.23, 123, 1],
+ ["A", "B", 1, 1, 1.0],
+ ]
+ df = pd.DataFrame(df_list, columns=["a", "b", "c", "d", "e"])
+ grouped = df.groupby(["a", "b"], dropna=dropna).sum()
+
+ mi = pd.MultiIndex.from_tuples(tuples, names=list("ab"))
+
+ # Since right now, by default MI will drop NA from levels when we create MI
+ # via `from_*`, so we need to add NA for level manually afterwards.
+ if not dropna:
+ mi = mi.set_levels(["A", "B", np.nan], level="b")
+ expected = pd.DataFrame(outputs, index=mi)
+
+ tm.assert_frame_equal(grouped, expected)
+
+
+@pytest.mark.parametrize(
+ "dropna, tuples, outputs",
+ [
+ (
+ True,
+ [["A", "B"], ["B", "A"]],
+ {"c": [12.0, 123.23], "d": [12.0, 123.0], "e": [12.0, 1.0]},
+ ),
+ (
+ False,
+ [["A", "B"], ["A", np.nan], ["B", "A"], [np.nan, "B"]],
+ {
+ "c": [12.0, 13.3, 123.23, 1.0],
+ "d": [12.0, 234.0, 123.0, 1.0],
+ "e": [12.0, 13.0, 1.0, 1.0],
+ },
+ ),
+ ],
+)
+def test_groupby_dropna_multi_index_dataframe_nan_in_two_groups(
+ dropna, tuples, outputs, nulls_fixture, nulls_fixture2
+):
+ # GH 3729 this is to test that NA in different groups with different representations
+ df_list = [
+ ["A", "B", 12, 12, 12],
+ ["A", nulls_fixture, 12.3, 233.0, 12],
+ ["B", "A", 123.23, 123, 1],
+ [nulls_fixture2, "B", 1, 1, 1.0],
+ ["A", nulls_fixture2, 1, 1, 1.0],
+ ]
+ df = pd.DataFrame(df_list, columns=["a", "b", "c", "d", "e"])
+ grouped = df.groupby(["a", "b"], dropna=dropna).sum()
+
+ mi = pd.MultiIndex.from_tuples(tuples, names=list("ab"))
+
+ # Since right now, by default MI will drop NA from levels when we create MI
+ # via `from_*`, so we need to add NA for level manually afterwards.
+ if not dropna:
+ mi = mi.set_levels([["A", "B", np.nan], ["A", "B", np.nan]])
+ expected = pd.DataFrame(outputs, index=mi)
+
+ tm.assert_frame_equal(grouped, expected)
+
+
+@pytest.mark.parametrize(
+ "dropna, idx, outputs",
+ [
+ (True, ["A", "B"], {"b": [123.23, 13.0], "c": [123.0, 13.0], "d": [1.0, 13.0]}),
+ (
+ False,
+ ["A", "B", np.nan],
+ {
+ "b": [123.23, 13.0, 12.3],
+ "c": [123.0, 13.0, 233.0],
+ "d": [1.0, 13.0, 12.0],
+ },
+ ),
+ ],
+)
+def test_groupby_dropna_normal_index_dataframe(dropna, idx, outputs):
+ # GH 3729
+ df_list = [
+ ["B", 12, 12, 12],
+ [None, 12.3, 233.0, 12],
+ ["A", 123.23, 123, 1],
+ ["B", 1, 1, 1.0],
+ ]
+ df = pd.DataFrame(df_list, columns=["a", "b", "c", "d"])
+ grouped = df.groupby("a", dropna=dropna).sum()
+
+ expected = pd.DataFrame(outputs, index=pd.Index(idx, name="a"))
+
+ tm.assert_frame_equal(grouped, expected)
+
+
+@pytest.mark.parametrize(
+ "dropna, idx, expected",
+ [
+ (True, ["a", "a", "b", np.nan], pd.Series([3, 3], index=["a", "b"])),
+ (
+ False,
+ ["a", "a", "b", np.nan],
+ pd.Series([3, 3, 3], index=["a", "b", np.nan]),
+ ),
+ ],
+)
+def test_groupby_dropna_series_level(dropna, idx, expected):
+ ser = pd.Series([1, 2, 3, 3], index=idx)
+
+ result = ser.groupby(level=0, dropna=dropna).sum()
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dropna, expected",
+ [
+ (True, pd.Series([210.0, 350.0], index=["a", "b"], name="Max Speed")),
+ (
+ False,
+ pd.Series([210.0, 350.0, 20.0], index=["a", "b", np.nan], name="Max Speed"),
+ ),
+ ],
+)
+def test_groupby_dropna_series_by(dropna, expected):
+ ser = pd.Series(
+ [390.0, 350.0, 30.0, 20.0],
+ index=["Falcon", "Falcon", "Parrot", "Parrot"],
+ name="Max Speed",
+ )
+
+ result = ser.groupby(["a", "b", "a", np.nan], dropna=dropna).mean()
+ tm.assert_series_equal(result, expected)
+
+
+def test_grouper_dropna_propagation(dropna):
+ # GH 36604
+ df = pd.DataFrame({"A": [0, 0, 1, None], "B": [1, 2, 3, None]})
+ gb = df.groupby("A", dropna=dropna)
+ assert gb._grouper.dropna == dropna
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ pd.RangeIndex(0, 4),
+ list("abcd"),
+ pd.MultiIndex.from_product([(1, 2), ("R", "B")], names=["num", "col"]),
+ ],
+)
+def test_groupby_dataframe_slice_then_transform(dropna, index):
+ # GH35014 & GH35612
+ expected_data = {"B": [2, 2, 1, np.nan if dropna else 1]}
+
+ df = pd.DataFrame({"A": [0, 0, 1, None], "B": [1, 2, 3, None]}, index=index)
+ gb = df.groupby("A", dropna=dropna)
+
+ result = gb.transform(len)
+ expected = pd.DataFrame(expected_data, index=index)
+ tm.assert_frame_equal(result, expected)
+
+ result = gb[["B"]].transform(len)
+ expected = pd.DataFrame(expected_data, index=index)
+ tm.assert_frame_equal(result, expected)
+
+ result = gb["B"].transform(len)
+ expected = pd.Series(expected_data["B"], index=index, name="B")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dropna, tuples, outputs",
+ [
+ (
+ True,
+ [["A", "B"], ["B", "A"]],
+ {"c": [13.0, 123.23], "d": [12.0, 123.0], "e": [1.0, 1.0]},
+ ),
+ (
+ False,
+ [["A", "B"], ["A", np.nan], ["B", "A"]],
+ {
+ "c": [13.0, 12.3, 123.23],
+ "d": [12.0, 233.0, 123.0],
+ "e": [1.0, 12.0, 1.0],
+ },
+ ),
+ ],
+)
+def test_groupby_dropna_multi_index_dataframe_agg(dropna, tuples, outputs):
+ # GH 3729
+ df_list = [
+ ["A", "B", 12, 12, 12],
+ ["A", None, 12.3, 233.0, 12],
+ ["B", "A", 123.23, 123, 1],
+ ["A", "B", 1, 1, 1.0],
+ ]
+ df = pd.DataFrame(df_list, columns=["a", "b", "c", "d", "e"])
+ agg_dict = {"c": "sum", "d": "max", "e": "min"}
+ grouped = df.groupby(["a", "b"], dropna=dropna).agg(agg_dict)
+
+ mi = pd.MultiIndex.from_tuples(tuples, names=list("ab"))
+
+ # Since right now, by default MI will drop NA from levels when we create MI
+ # via `from_*`, so we need to add NA for level manually afterwards.
+ if not dropna:
+ mi = mi.set_levels(["A", "B", np.nan], level="b")
+ expected = pd.DataFrame(outputs, index=mi)
+
+ tm.assert_frame_equal(grouped, expected)
+
+
+@pytest.mark.arm_slow
+@pytest.mark.parametrize(
+ "datetime1, datetime2",
+ [
+ (pd.Timestamp("2020-01-01"), pd.Timestamp("2020-02-01")),
+ (pd.Timedelta("-2 days"), pd.Timedelta("-1 days")),
+ (pd.Period("2020-01-01"), pd.Period("2020-02-01")),
+ ],
+)
+@pytest.mark.parametrize("dropna, values", [(True, [12, 3]), (False, [12, 3, 6])])
+def test_groupby_dropna_datetime_like_data(
+ dropna, values, datetime1, datetime2, unique_nulls_fixture, unique_nulls_fixture2
+):
+ # 3729
+ df = pd.DataFrame(
+ {
+ "values": [1, 2, 3, 4, 5, 6],
+ "dt": [
+ datetime1,
+ unique_nulls_fixture,
+ datetime2,
+ unique_nulls_fixture2,
+ datetime1,
+ datetime1,
+ ],
+ }
+ )
+
+ if dropna:
+ indexes = [datetime1, datetime2]
+ else:
+ indexes = [datetime1, datetime2, np.nan]
+
+ grouped = df.groupby("dt", dropna=dropna).agg({"values": "sum"})
+ expected = pd.DataFrame({"values": values}, index=pd.Index(indexes, name="dt"))
+
+ tm.assert_frame_equal(grouped, expected)
+
+
+@pytest.mark.parametrize(
+ "dropna, data, selected_data, levels",
+ [
+ pytest.param(
+ False,
+ {"groups": ["a", "a", "b", np.nan], "values": [10, 10, 20, 30]},
+ {"values": [0, 1, 0, 0]},
+ ["a", "b", np.nan],
+ id="dropna_false_has_nan",
+ ),
+ pytest.param(
+ True,
+ {"groups": ["a", "a", "b", np.nan], "values": [10, 10, 20, 30]},
+ {"values": [0, 1, 0]},
+ None,
+ id="dropna_true_has_nan",
+ ),
+ pytest.param(
+ # no nan in "groups"; dropna=True|False should be same.
+ False,
+ {"groups": ["a", "a", "b", "c"], "values": [10, 10, 20, 30]},
+ {"values": [0, 1, 0, 0]},
+ None,
+ id="dropna_false_no_nan",
+ ),
+ pytest.param(
+ # no nan in "groups"; dropna=True|False should be same.
+ True,
+ {"groups": ["a", "a", "b", "c"], "values": [10, 10, 20, 30]},
+ {"values": [0, 1, 0, 0]},
+ None,
+ id="dropna_true_no_nan",
+ ),
+ ],
+)
+def test_groupby_apply_with_dropna_for_multi_index(dropna, data, selected_data, levels):
+ # GH 35889
+
+ df = pd.DataFrame(data)
+ gb = df.groupby("groups", dropna=dropna)
+ result = gb.apply(lambda grp: pd.DataFrame({"values": range(len(grp))}))
+
+ mi_tuples = tuple(zip(data["groups"], selected_data["values"], strict=False))
+ mi = pd.MultiIndex.from_tuples(mi_tuples, names=["groups", None])
+ # Since right now, by default MI will drop NA from levels when we create MI
+ # via `from_*`, so we need to add NA for level manually afterwards.
+ if not dropna and levels:
+ mi = mi.set_levels(levels, level="groups")
+
+ expected = pd.DataFrame(selected_data, index=mi)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("input_index", [None, ["a"], ["a", "b"]])
+@pytest.mark.parametrize("keys", [["a"], ["a", "b"]])
+@pytest.mark.parametrize("series", [True, False])
+def test_groupby_dropna_with_multiindex_input(input_index, keys, series):
+ # GH#46783
+ obj = pd.DataFrame(
+ {
+ "a": [1, np.nan],
+ "b": [1, 1],
+ "c": [2, 3],
+ }
+ )
+
+ expected = obj.set_index(keys)
+ if series:
+ expected = expected["c"]
+ elif input_index == ["a", "b"] and keys == ["a"]:
+ # Column b should not be aggregated
+ expected = expected[["c"]]
+
+ if input_index is not None:
+ obj = obj.set_index(input_index)
+ gb = obj.groupby(keys, dropna=False)
+ if series:
+ gb = gb["c"]
+ result = gb.sum()
+
+ tm.assert_equal(result, expected)
+
+
+def test_groupby_nan_included():
+ # GH 35646
+ data = {"group": ["g1", np.nan, "g1", "g2", np.nan], "B": [0, 1, 2, 3, 4]}
+ df = pd.DataFrame(data)
+ grouped = df.groupby("group", dropna=False)
+ result = grouped.indices
+ dtype = np.intp
+ expected = {
+ "g1": np.array([0, 2], dtype=dtype),
+ "g2": np.array([3], dtype=dtype),
+ np.nan: np.array([1, 4], dtype=dtype),
+ }
+ for result_values, expected_values in zip(
+ result.values(), expected.values(), strict=True
+ ):
+ tm.assert_numpy_array_equal(result_values, expected_values)
+ assert np.isnan(list(result.keys())[2])
+ assert list(result.keys())[0:2] == ["g1", "g2"]
+
+
+def test_groupby_drop_nan_with_multi_index():
+ # GH 39895
+ df = pd.DataFrame([[np.nan, 0, 1]], columns=["a", "b", "c"])
+ df = df.set_index(["a", "b"])
+ result = df.groupby(["a", "b"], dropna=False).first()
+ expected = df
+ tm.assert_frame_equal(result, expected)
+
+
+# y >x and z is the missing value
+@pytest.mark.parametrize(
+ "sequence",
+ [
+ "xyzy",
+ "xxyz",
+ "yzxz",
+ "zzzz",
+ "zyzx",
+ "yyyy",
+ "zzxy",
+ "xyxy",
+ ],
+)
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ None,
+ "UInt8",
+ "Int8",
+ "UInt16",
+ "Int16",
+ "UInt32",
+ "Int32",
+ "UInt64",
+ "Int64",
+ "Float32",
+ "Float64",
+ "category",
+ "string",
+ pytest.param("string[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ "datetime64[ns]",
+ "period[D]",
+ "Sparse[float]",
+ ],
+)
+@pytest.mark.parametrize("test_series", [True, False])
+def test_no_sort_keep_na(sequence, dtype, test_series, as_index):
+ # GH#46584, GH#48794
+
+ # Unique values to use for grouper, depends on dtype
+ if dtype in ("string", "string[pyarrow]"):
+ uniques = {"x": "x", "y": "y", "z": pd.NA}
+ elif dtype in ("datetime64[ns]", "period[D]"):
+ uniques = {"x": "2016-01-01", "y": "2017-01-01", "z": pd.NA}
+ elif dtype is not None and dtype.startswith(("I", "U", "F")):
+ uniques = {"x": 1, "y": 2, "z": pd.NA}
+ else:
+ uniques = {"x": 1, "y": 2, "z": np.nan}
+
+ df = pd.DataFrame(
+ {
+ "key": pd.Series([uniques[label] for label in sequence], dtype=dtype),
+ "a": [0, 1, 2, 3],
+ }
+ )
+ gb = df.groupby("key", dropna=False, sort=False, as_index=as_index, observed=False)
+ if test_series:
+ gb = gb["a"]
+ result = gb.sum()
+
+ # Manually compute the groupby sum, use the labels "x", "y", and "z" to avoid
+ # issues with hashing np.nan
+ summed = {}
+ for idx, label in enumerate(sequence):
+ summed[label] = summed.get(label, 0) + idx
+ if dtype == "category":
+ index = pd.CategoricalIndex(
+ [uniques[e] for e in summed],
+ df["key"].cat.categories,
+ name="key",
+ )
+ elif isinstance(dtype, str) and dtype.startswith("Sparse"):
+ index = pd.Index(
+ pd.array([uniques[label] for label in summed], dtype=dtype), name="key"
+ )
+ else:
+ index = pd.Index([uniques[label] for label in summed], dtype=dtype, name="key")
+ expected = pd.Series(summed.values(), index=index, name="a", dtype=None)
+ if not test_series:
+ expected = expected.to_frame()
+ if not as_index:
+ expected = expected.reset_index()
+ if dtype is not None and dtype.startswith("Sparse"):
+ expected["key"] = expected["key"].astype(dtype)
+
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize("test_series", [True, False])
+@pytest.mark.parametrize("dtype", [object, None])
+def test_null_is_null_for_dtype(
+ sort, dtype, nulls_fixture, nulls_fixture2, test_series
+):
+ # GH#48506 - groups should always result in using the null for the dtype
+ df = pd.DataFrame({"a": [1, 2]})
+ groups = pd.Series([nulls_fixture, nulls_fixture2], dtype=dtype)
+ obj = df["a"] if test_series else df
+ gb = obj.groupby(groups, dropna=False, sort=sort)
+ result = gb.sum()
+ index = pd.Index([na_value_for_dtype(groups.dtype)])
+ expected = pd.DataFrame({"a": [3]}, index=index)
+ if test_series:
+ tm.assert_series_equal(result, expected["a"])
+ else:
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_kind", ["range", "single", "multi"])
+def test_categorical_reducers(reduction_func, observed, sort, as_index, index_kind):
+ # Ensure there is at least one null value by appending to the end
+ values = np.append(np.random.default_rng(2).choice([1, 2, None], size=19), None)
+ df = pd.DataFrame(
+ {"x": pd.Categorical(values, categories=[1, 2, 3]), "y": range(20)}
+ )
+
+ # Strategy: Compare to dropna=True by filling null values with a new code
+ df_filled = df.copy()
+ df_filled["x"] = pd.Categorical(values, categories=[1, 2, 3, 4]).fillna(4)
+
+ if index_kind == "range":
+ keys = ["x"]
+ elif index_kind == "single":
+ keys = ["x"]
+ df = df.set_index("x")
+ df_filled = df_filled.set_index("x")
+ else:
+ keys = ["x", "x2"]
+ df["x2"] = df["x"]
+ df = df.set_index(["x", "x2"])
+ df_filled["x2"] = df_filled["x"]
+ df_filled = df_filled.set_index(["x", "x2"])
+ args = get_groupby_method_args(reduction_func, df)
+ args_filled = get_groupby_method_args(reduction_func, df_filled)
+ if reduction_func == "corrwith" and index_kind == "range":
+ # Don't include the grouping columns so we can call reset_index
+ args = (args[0].drop(columns=keys),)
+ args_filled = (args_filled[0].drop(columns=keys),)
+
+ gb_keepna = df.groupby(
+ keys, dropna=False, observed=observed, sort=sort, as_index=as_index
+ )
+
+ if not observed and reduction_func in ["idxmin", "idxmax"]:
+ with pytest.raises(
+ ValueError, match="empty group due to unobserved categories"
+ ):
+ getattr(gb_keepna, reduction_func)(*args)
+ return
+
+ gb_filled = df_filled.groupby(keys, observed=observed, sort=sort, as_index=True)
+ if reduction_func == "corrwith":
+ warn = Pandas4Warning
+ msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ msg = ""
+ with tm.assert_produces_warning(warn, match=msg):
+ expected = getattr(gb_filled, reduction_func)(*args_filled).reset_index()
+ expected["x"] = expected["x"].cat.remove_categories([4])
+ if index_kind == "multi":
+ expected["x2"] = expected["x2"].cat.remove_categories([4])
+ if as_index:
+ if index_kind == "multi":
+ expected = expected.set_index(["x", "x2"])
+ else:
+ expected = expected.set_index("x")
+ if reduction_func in ("idxmax", "idxmin") and index_kind != "range":
+ # expected was computed with a RangeIndex; need to translate to index values
+ values = expected["y"].values.tolist()
+ if index_kind == "single":
+ values = [np.nan if e == 4 else e for e in values]
+ expected["y"] = pd.Categorical(values, categories=[1, 2, 3])
+ else:
+ values = [(np.nan, np.nan) if e == (4, 4) else e for e in values]
+ expected["y"] = values
+ if reduction_func == "size":
+ # size, unlike other methods, has the desired behavior in GH#49519
+ expected = expected.rename(columns={0: "size"})
+ if as_index:
+ expected = expected["size"].rename(None)
+
+ if reduction_func == "corrwith":
+ warn = Pandas4Warning
+ msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ msg = ""
+ with tm.assert_produces_warning(warn, match=msg):
+ result = getattr(gb_keepna, reduction_func)(*args)
+
+ # size will return a Series, others are DataFrame
+ tm.assert_equal(result, expected)
+
+
+def test_categorical_transformers(transformation_func, observed, sort, as_index):
+ # GH#36327
+ values = np.append(np.random.default_rng(2).choice([1, 2, None], size=19), None)
+ df = pd.DataFrame(
+ {"x": pd.Categorical(values, categories=[1, 2, 3]), "y": range(20)}
+ )
+ args = get_groupby_method_args(transformation_func, df)
+
+ # Compute result for null group
+ null_group_values = df[df["x"].isnull()]["y"]
+ if transformation_func == "cumcount":
+ null_group_data = list(range(len(null_group_values)))
+ elif transformation_func == "ngroup":
+ if sort:
+ if observed:
+ na_group = df["x"].nunique(dropna=False) - 1
+ else:
+ # TODO: Should this be 3?
+ na_group = df["x"].nunique(dropna=False) - 1
+ else:
+ na_group = df.iloc[: null_group_values.index[0]]["x"].nunique()
+ null_group_data = len(null_group_values) * [na_group]
+ else:
+ null_group_data = getattr(null_group_values, transformation_func)(*args)
+ null_group_result = pd.DataFrame({"y": null_group_data})
+
+ gb_keepna = df.groupby(
+ "x", dropna=False, observed=observed, sort=sort, as_index=as_index
+ )
+ gb_dropna = df.groupby("x", dropna=True, observed=observed, sort=sort)
+
+ result = getattr(gb_keepna, transformation_func)(*args)
+ expected = getattr(gb_dropna, transformation_func)(*args)
+
+ for iloc, value in zip(
+ df[df["x"].isnull()].index.tolist(),
+ null_group_result.values.ravel(),
+ strict=True,
+ ):
+ if expected.ndim == 1:
+ expected.iloc[iloc] = value
+ else:
+ expected.iloc[iloc, 0] = value
+ if transformation_func == "ngroup":
+ expected[df["x"].notnull() & expected.ge(na_group)] += 1
+ if transformation_func not in ("rank", "diff", "pct_change", "shift"):
+ expected = expected.astype("int64")
+
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["head", "tail"])
+def test_categorical_head_tail(method, observed, sort, as_index):
+ # GH#36327
+ values = np.random.default_rng(2).choice([1, 2, None], 30)
+ df = pd.DataFrame(
+ {"x": pd.Categorical(values, categories=[1, 2, 3]), "y": range(len(values))}
+ )
+ gb = df.groupby("x", dropna=False, observed=observed, sort=sort, as_index=as_index)
+ result = getattr(gb, method)()
+
+ if method == "tail":
+ values = values[::-1]
+ # Take the top 5 values from each group
+ mask = (
+ ((values == 1) & ((values == 1).cumsum() <= 5))
+ | ((values == 2) & ((values == 2).cumsum() <= 5))
+ # flake8 doesn't like the vectorized check for None, thinks we should use `is`
+ | ((values == None) & ((values == None).cumsum() <= 5)) # noqa: E711
+ )
+ if method == "tail":
+ mask = mask[::-1]
+ expected = df[mask]
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_agg():
+ # GH#36327
+ values = np.random.default_rng(2).choice([1, 2, None], 30)
+ df = pd.DataFrame(
+ {"x": pd.Categorical(values, categories=[1, 2, 3]), "y": range(len(values))}
+ )
+ gb = df.groupby("x", dropna=False, observed=False)
+ result = gb.agg(lambda x: x.sum())
+ expected = gb.sum()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_transform():
+ # GH#36327
+ values = np.random.default_rng(2).choice([1, 2, None], 30)
+ df = pd.DataFrame(
+ {"x": pd.Categorical(values, categories=[1, 2, 3]), "y": range(len(values))}
+ )
+ gb = df.groupby("x", dropna=False, observed=False)
+ result = gb.transform(lambda x: x.sum())
+ expected = gb.transform("sum")
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_groupby_subclass.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_groupby_subclass.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1dfb3aabdaf03d120058793e0c9866be2255db6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_groupby_subclass.py
@@ -0,0 +1,152 @@
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+from pandas.tests.groupby import get_groupby_method_args
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
+)
+
+
+@pytest.mark.parametrize(
+ "obj",
+ [
+ tm.SubclassedDataFrame({"A": np.arange(0, 10)}),
+ tm.SubclassedSeries(np.arange(0, 10), name="A"),
+ ],
+)
+def test_groupby_preserves_subclass(obj, groupby_func):
+ # GH28330 -- preserve subclass through groupby operations
+
+ if isinstance(obj, Series) and groupby_func in {"corrwith"}:
+ pytest.skip(f"Not applicable for Series and {groupby_func}")
+
+ grouped = obj.groupby(np.arange(0, 10))
+
+ # Groups should preserve subclass type
+ assert isinstance(grouped.get_group(0), type(obj))
+
+ args = get_groupby_method_args(groupby_func, obj)
+
+ warn = Pandas4Warning if groupby_func == "corrwith" else None
+ msg = f"{type(grouped).__name__}.corrwith is deprecated"
+ with tm.assert_produces_warning(warn, match=msg):
+ result1 = getattr(grouped, groupby_func)(*args)
+ with tm.assert_produces_warning(warn, match=msg):
+ result2 = grouped.agg(groupby_func, *args)
+
+ # Reduction or transformation kernels should preserve type
+ slices = {"ngroup", "cumcount", "size"}
+ if isinstance(obj, DataFrame) and groupby_func in slices:
+ assert isinstance(result1, tm.SubclassedSeries)
+ else:
+ assert isinstance(result1, type(obj))
+
+ # Confirm .agg() groupby operations return same results
+ if isinstance(result1, DataFrame):
+ tm.assert_frame_equal(result1, result2)
+ else:
+ tm.assert_series_equal(result1, result2)
+
+
+def test_groupby_preserves_metadata():
+ # GH-37343
+ custom_df = tm.SubclassedDataFrame({"a": [1, 2, 3], "b": [1, 1, 2], "c": [7, 8, 9]})
+ assert "testattr" in custom_df._metadata
+ custom_df.testattr = "hello"
+ for _, group_df in custom_df.groupby("c"):
+ assert group_df.testattr == "hello"
+
+ # GH-45314
+ def func(group):
+ assert isinstance(group, tm.SubclassedDataFrame)
+ assert hasattr(group, "testattr")
+ assert group.testattr == "hello"
+ return group.testattr
+
+ result = custom_df.groupby("c").apply(func)
+ expected = tm.SubclassedSeries(["hello"] * 3, index=Index([7, 8, 9], name="c"))
+ tm.assert_series_equal(result, expected)
+
+ result = custom_df.groupby("c").apply(func)
+ tm.assert_series_equal(result, expected)
+
+ # https://github.com/pandas-dev/pandas/pull/56761
+ result = custom_df.groupby("c")[["a", "b"]].apply(func)
+ tm.assert_series_equal(result, expected)
+
+ def func2(group):
+ assert isinstance(group, tm.SubclassedSeries)
+ assert hasattr(group, "testattr")
+ return group.testattr
+
+ custom_series = tm.SubclassedSeries([1, 2, 3])
+ custom_series.testattr = "hello"
+ result = custom_series.groupby(custom_df["c"]).apply(func2)
+ tm.assert_series_equal(result, expected)
+ result = custom_series.groupby(custom_df["c"]).agg(func2)
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_apply_preserves_metadata():
+ # GH#62134 - Test that apply() preserves metadata when returning DataFrames/Series
+ custom_df = tm.SubclassedDataFrame({"a": [1, 2, 3], "b": [1, 1, 2], "c": [7, 8, 9]})
+ custom_df.testattr = "hello"
+
+ def sum_func(group):
+ assert isinstance(group, tm.SubclassedDataFrame)
+ assert hasattr(group, "testattr")
+ assert group.testattr == "hello"
+ return group.sum()
+
+ result = custom_df.groupby("c").apply(sum_func)
+ assert hasattr(result, "testattr"), "DataFrame apply() should preserve metadata"
+ assert result.testattr == "hello"
+
+ custom_series = tm.SubclassedSeries([1, 2, 3])
+ custom_series.testattr = "hello"
+
+ def sum_series_func(group):
+ assert isinstance(group, tm.SubclassedSeries)
+ assert hasattr(group, "testattr")
+ assert group.testattr == "hello"
+ return group.sum()
+
+ result = custom_series.groupby(custom_df["c"]).apply(sum_series_func)
+ assert hasattr(result, "testattr"), "Series apply() should preserve metadata"
+ assert result.testattr == "hello"
+
+
+@pytest.mark.parametrize("obj", [DataFrame, tm.SubclassedDataFrame])
+def test_groupby_resample_preserves_subclass(obj):
+ # GH28330 -- preserve subclass through groupby.resample()
+
+ df = obj(
+ {
+ "Buyer": Series("Carl Carl Carl Carl Joe Carl".split(), dtype=object),
+ "Quantity": [18, 3, 5, 1, 9, 3],
+ "Date": [
+ datetime(2013, 9, 1, 13, 0),
+ datetime(2013, 9, 1, 13, 5),
+ datetime(2013, 10, 1, 20, 0),
+ datetime(2013, 10, 3, 10, 0),
+ datetime(2013, 12, 2, 12, 0),
+ datetime(2013, 9, 2, 14, 0),
+ ],
+ }
+ )
+ df = df.set_index("Date")
+
+ # Confirm groupby.resample() preserves dataframe type
+ result = df.groupby("Buyer").resample("5D").sum()
+ assert isinstance(result, obj)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_grouping.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_grouping.py
new file mode 100644
index 0000000000000000000000000000000000000000..6450b1108d240b3eff5a26b6cb33fab8d0a07b90
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_grouping.py
@@ -0,0 +1,1216 @@
+"""
+test where we are determining what we are grouping, or getting groups
+"""
+
+from datetime import (
+ date,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+from pandas.errors import (
+ Pandas4Warning,
+ SpecificationError,
+)
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ DataFrame,
+ Grouper,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+from pandas.core.groupby.grouper import Grouping
+
+# selection
+# --------------------------------
+
+
+class TestSelection:
+ def test_select_bad_cols(self):
+ df = DataFrame([[1, 2]], columns=["A", "B"])
+ g = df.groupby("A")
+ with pytest.raises(KeyError, match="\"Columns not found: 'C'\""):
+ g[["C"]]
+
+ with pytest.raises(KeyError, match="^[^A]+$"):
+ # A should not be referenced as a bad column...
+ # will have to rethink regex if you change message!
+ g[["A", "C"]]
+
+ def test_groupby_duplicated_column_errormsg(self):
+ # GH7511
+ df = DataFrame(
+ columns=["A", "B", "A", "C"], data=[range(4), range(2, 6), range(0, 8, 2)]
+ )
+
+ msg = "Grouper for 'A' not 1-dimensional"
+ with pytest.raises(ValueError, match=msg):
+ df.groupby("A")
+ with pytest.raises(ValueError, match=msg):
+ df.groupby(["A", "B"])
+
+ grouped = df.groupby("B")
+ c = grouped.count()
+ assert c.columns.nlevels == 1
+ assert c.columns.size == 3
+
+ def test_column_select_via_attr(self, df):
+ result = df.groupby("A").C.sum()
+ expected = df.groupby("A")["C"].sum()
+ tm.assert_series_equal(result, expected)
+
+ df["mean"] = 1.5
+ result = df.groupby("A").mean(numeric_only=True)
+ expected = df.groupby("A")[["C", "D", "mean"]].agg("mean")
+ tm.assert_frame_equal(result, expected)
+
+ def test_getitem_list_of_columns(self):
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ "E": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+
+ result = df.groupby("A")[["C", "D"]].mean()
+ result2 = df.groupby("A")[df.columns[2:4]].mean()
+
+ expected = df.loc[:, ["A", "C", "D"]].groupby("A").mean()
+
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(result2, expected)
+
+ def test_getitem_numeric_column_names(self):
+ # GH #13731
+ df = DataFrame(
+ {
+ 0: list("abcd") * 2,
+ 2: np.random.default_rng(2).standard_normal(8),
+ 4: np.random.default_rng(2).standard_normal(8),
+ 6: np.random.default_rng(2).standard_normal(8),
+ }
+ )
+ result = df.groupby(0)[df.columns[1:3]].mean()
+ result2 = df.groupby(0)[[2, 4]].mean()
+
+ expected = df.loc[:, [0, 2, 4]].groupby(0).mean()
+
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(result2, expected)
+
+ # per GH 23566 enforced deprecation raises a ValueError
+ with pytest.raises(ValueError, match="Cannot subset columns with a tuple"):
+ df.groupby(0)[2, 4].mean()
+
+ def test_getitem_single_tuple_of_columns_raises(self, df):
+ # per GH 23566 enforced deprecation raises a ValueError
+ with pytest.raises(ValueError, match="Cannot subset columns with a tuple"):
+ df.groupby("A")["C", "D"].mean()
+
+ def test_getitem_single_column(self):
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ "E": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+
+ result = df.groupby("A")["C"].mean()
+
+ as_frame = df.loc[:, ["A", "C"]].groupby("A").mean()
+ as_series = as_frame.iloc[:, 0]
+ expected = as_series
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "func", [lambda x: x.sum(), lambda x: x.agg(lambda y: y.sum())]
+ )
+ def test_getitem_from_grouper(self, func):
+ # GH 50383
+ df = DataFrame({"a": [1, 1, 2], "b": 3, "c": 4, "d": 5})
+ gb = df.groupby(["a", "b"])[["a", "c"]]
+
+ idx = MultiIndex.from_tuples([(1, 3), (2, 3)], names=["a", "b"])
+ expected = DataFrame({"a": [2, 2], "c": [8, 4]}, index=idx)
+ result = func(gb)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_indices_grouped_by_tuple_with_lambda(self):
+ # GH 36158
+ df = DataFrame(
+ {
+ "Tuples": (
+ (x, y)
+ for x in [0, 1]
+ for y in np.random.default_rng(2).integers(3, 5, 5)
+ )
+ }
+ )
+
+ gb = df.groupby("Tuples")
+ gb_lambda = df.groupby(lambda x: df.iloc[x, 0])
+
+ expected = gb.indices
+ result = gb_lambda.indices
+
+ tm.assert_dict_equal(result, expected)
+
+
+# grouping
+# --------------------------------
+
+
+class TestGrouping:
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(list("abcde")),
+ Index(np.arange(5)),
+ Index(np.arange(5, dtype=float)),
+ date_range("2020-01-01", periods=5),
+ period_range("2020-01-01", periods=5),
+ ],
+ )
+ def test_grouper_index_types(self, index):
+ # related GH5375
+ # groupby misbehaving when using a Floatlike index
+ df = DataFrame(np.arange(10).reshape(5, 2), columns=list("AB"), index=index)
+
+ df.groupby(list("abcde"), group_keys=False).apply(lambda x: x)
+
+ df.index = df.index[::-1]
+ df.groupby(list("abcde"), group_keys=False).apply(lambda x: x)
+
+ def test_grouper_multilevel_freq(self):
+ # GH 7885
+ # with level and freq specified in a Grouper
+ d0 = date.today() - timedelta(days=14)
+ dates = date_range(d0, date.today())
+ date_index = MultiIndex.from_product([dates, dates], names=["foo", "bar"])
+ df = DataFrame(np.random.default_rng(2).integers(0, 100, 225), index=date_index)
+
+ # Check string level
+ expected = (
+ df.reset_index()
+ .groupby([Grouper(key="foo", freq="W"), Grouper(key="bar", freq="W")])
+ .sum()
+ )
+ # reset index changes columns dtype to object
+ expected.columns = Index([0], dtype="int64")
+
+ result = df.groupby(
+ [Grouper(level="foo", freq="W"), Grouper(level="bar", freq="W")]
+ ).sum()
+ tm.assert_frame_equal(result, expected)
+
+ # Check integer level
+ result = df.groupby(
+ [Grouper(level=0, freq="W"), Grouper(level=1, freq="W")]
+ ).sum()
+ tm.assert_frame_equal(result, expected)
+
+ def test_grouper_creation_bug(self):
+ # GH 8795
+ df = DataFrame({"A": [0, 0, 1, 1, 2, 2], "B": [1, 2, 3, 4, 5, 6]})
+ g = df.groupby("A")
+ expected = g.sum()
+
+ g = df.groupby(Grouper(key="A"))
+ result = g.sum()
+ tm.assert_frame_equal(result, expected)
+
+ result = g.apply(lambda x: x.sum())
+ tm.assert_frame_equal(result, expected)
+
+ def test_grouper_creation_bug2(self):
+ # GH14334
+ # Grouper(key=...) may be passed in a list
+ df = DataFrame(
+ {"A": [0, 0, 0, 1, 1, 1], "B": [1, 1, 2, 2, 3, 3], "C": [1, 2, 3, 4, 5, 6]}
+ )
+ # Group by single column
+ expected = df.groupby("A").sum()
+ g = df.groupby([Grouper(key="A")])
+ result = g.sum()
+ tm.assert_frame_equal(result, expected)
+
+ # Group by two columns
+ # using a combination of strings and Grouper objects
+ expected = df.groupby(["A", "B"]).sum()
+
+ # Group with two Grouper objects
+ g = df.groupby([Grouper(key="A"), Grouper(key="B")])
+ result = g.sum()
+ tm.assert_frame_equal(result, expected)
+
+ # Group with a string and a Grouper object
+ g = df.groupby(["A", Grouper(key="B")])
+ result = g.sum()
+ tm.assert_frame_equal(result, expected)
+
+ # Group with a Grouper object and a string
+ g = df.groupby([Grouper(key="A"), "B"])
+ result = g.sum()
+ tm.assert_frame_equal(result, expected)
+
+ def test_grouper_creation_bug3(self, unit):
+ # GH8866
+ dti = date_range("20130101", periods=2, unit=unit)
+ mi = MultiIndex.from_product(
+ [list("ab"), range(2), dti],
+ names=["one", "two", "three"],
+ )
+ ser = Series(
+ np.arange(8, dtype="int64"),
+ index=mi,
+ )
+ result = ser.groupby(Grouper(level="three", freq="ME")).sum()
+ exp_dti = pd.DatetimeIndex(
+ [Timestamp("2013-01-31")], freq="ME", name="three"
+ ).as_unit(unit)
+ expected = Series(
+ [28],
+ index=exp_dti,
+ )
+ tm.assert_series_equal(result, expected)
+
+ # just specifying a level breaks
+ result = ser.groupby(Grouper(level="one")).sum()
+ expected = ser.groupby(level="one").sum()
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("func", [False, True])
+ def test_grouper_returning_tuples(self, func):
+ # GH 22257 , both with dict and with callable
+ df = DataFrame({"X": ["A", "B", "A", "B"], "Y": [1, 4, 3, 2]})
+ mapping = dict(zip(range(4), [("C", 5), ("D", 6)] * 2, strict=True))
+
+ if func:
+ gb = df.groupby(by=lambda idx: mapping[idx], sort=False)
+ else:
+ gb = df.groupby(by=mapping, sort=False)
+
+ name, expected = next(iter(gb))
+ assert name == ("C", 5)
+ result = gb.get_group(name)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_grouper_column_and_index(self):
+ # GH 14327
+
+ # Grouping a multi-index frame by a column and an index level should
+ # be equivalent to resetting the index and grouping by two columns
+ idx = MultiIndex.from_tuples(
+ [("a", 1), ("a", 2), ("a", 3), ("b", 1), ("b", 2), ("b", 3)]
+ )
+ idx.names = ["outer", "inner"]
+ df_multi = DataFrame(
+ {"A": np.arange(6), "B": ["one", "one", "two", "two", "one", "one"]},
+ index=idx,
+ )
+ result = df_multi.groupby(["B", Grouper(level="inner")]).mean(numeric_only=True)
+ expected = (
+ df_multi.reset_index().groupby(["B", "inner"]).mean(numeric_only=True)
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # Test the reverse grouping order
+ result = df_multi.groupby([Grouper(level="inner"), "B"]).mean(numeric_only=True)
+ expected = (
+ df_multi.reset_index().groupby(["inner", "B"]).mean(numeric_only=True)
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # Grouping a single-index frame by a column and the index should
+ # be equivalent to resetting the index and grouping by two columns
+ df_single = df_multi.reset_index("outer")
+ result = df_single.groupby(["B", Grouper(level="inner")]).mean(
+ numeric_only=True
+ )
+ expected = (
+ df_single.reset_index().groupby(["B", "inner"]).mean(numeric_only=True)
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # Test the reverse grouping order
+ result = df_single.groupby([Grouper(level="inner"), "B"]).mean(
+ numeric_only=True
+ )
+ expected = (
+ df_single.reset_index().groupby(["inner", "B"]).mean(numeric_only=True)
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_levels_and_columns(self):
+ # GH9344, GH9049
+ idx_names = ["x", "y"]
+ idx = MultiIndex.from_tuples([(1, 1), (1, 2), (3, 4), (5, 6)], names=idx_names)
+ df = DataFrame(np.arange(12).reshape(-1, 3), index=idx)
+
+ by_levels = df.groupby(level=idx_names).mean()
+ # reset_index changes columns dtype to object
+ by_columns = df.reset_index().groupby(idx_names).mean()
+
+ # without casting, by_columns.columns is object-dtype
+ by_columns.columns = by_columns.columns.astype(np.int64)
+ tm.assert_frame_equal(by_levels, by_columns)
+
+ def test_groupby_categorical_index_and_columns(self, observed):
+ # GH18432, adapted for GH25871
+ columns = ["A", "B", "A", "B"]
+ categories = ["B", "A"]
+ data = np.array(
+ [[1, 2, 1, 2], [1, 2, 1, 2], [1, 2, 1, 2], [1, 2, 1, 2], [1, 2, 1, 2]], int
+ )
+ cat_columns = CategoricalIndex(columns, categories=categories, ordered=True)
+ expected_data = np.array([[4, 2], [4, 2], [4, 2], [4, 2], [4, 2]], int)
+ expected_columns = CategoricalIndex(
+ categories, categories=categories, ordered=True
+ )
+
+ # test transposed version
+ df = DataFrame(data.T, index=cat_columns)
+ result = df.groupby(level=0, observed=observed).sum()
+ expected = DataFrame(data=expected_data.T, index=expected_columns)
+ tm.assert_frame_equal(result, expected)
+
+ def test_grouper_getting_correct_binner(self):
+ # GH 10063
+ # using a non-time-based grouper and a time-based grouper
+ # and specifying levels
+ df = DataFrame(
+ {"A": 1},
+ index=MultiIndex.from_product(
+ [list("ab"), date_range("20130101", periods=80)], names=["one", "two"]
+ ),
+ )
+ result = df.groupby(
+ [Grouper(level="one"), Grouper(level="two", freq="ME")]
+ ).sum()
+ expected = DataFrame(
+ {"A": [31, 28, 21, 31, 28, 21]},
+ index=MultiIndex.from_product(
+ [list("ab"), date_range("20130101", freq="ME", periods=3)],
+ names=["one", "two"],
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_grouper_iter(self, df):
+ gb = df.groupby("A")
+ grouper = gb._grouper
+ result = sorted(grouper)
+ expected = ["bar", "foo"]
+ assert result == expected
+
+ def test_empty_groups(self, df):
+ # see gh-1048
+ with pytest.raises(ValueError, match="No group keys passed!"):
+ df.groupby([])
+
+ def test_groupby_grouper(self, df):
+ grouped = df.groupby("A")
+ grouper = grouped._grouper
+ result = df.groupby(grouper).mean(numeric_only=True)
+ expected = grouped.mean(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_dict_mapping(self):
+ # GH #679
+ s = Series({"T1": 5})
+ result = s.groupby({"T1": "T2"}).agg("sum")
+ expected = s.groupby(["T2"]).agg("sum")
+ tm.assert_series_equal(result, expected)
+
+ s = Series([1.0, 2.0, 3.0, 4.0], index=list("abcd"))
+ mapping = {"a": 0, "b": 0, "c": 1, "d": 1}
+
+ result = s.groupby(mapping).mean()
+ result2 = s.groupby(mapping).agg("mean")
+ exp_key = np.array([0, 0, 1, 1], dtype=np.int64)
+ expected = s.groupby(exp_key).mean()
+ expected2 = s.groupby(exp_key).mean()
+ tm.assert_series_equal(result, expected)
+ tm.assert_series_equal(result, result2)
+ tm.assert_series_equal(result, expected2)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ [0, 1, 2, 3],
+ ["a", "b", "c", "d"],
+ [Timestamp(2021, 7, 28 + i) for i in range(4)],
+ ],
+ )
+ def test_groupby_series_named_with_tuple(self, frame_or_series, index):
+ # GH 42731
+ obj = frame_or_series([1, 2, 3, 4], index=index)
+ groups = Series([1, 0, 1, 0], index=index, name=("a", "a"))
+ result = obj.groupby(groups).last()
+ expected = frame_or_series([4, 3])
+ expected.index.name = ("a", "a")
+ tm.assert_equal(result, expected)
+
+ def test_groupby_grouper_f_sanity_checked(self):
+ dates = date_range("01-Jan-2013", periods=12, freq="MS")
+ ts = Series(np.random.default_rng(2).standard_normal(12), index=dates)
+
+ # GH51979
+ # simple check that the passed function doesn't operates on the whole index
+ msg = "'Timestamp' object is not subscriptable"
+ with pytest.raises(TypeError, match=msg):
+ ts.groupby(lambda key: key[0:6])
+
+ result = ts.groupby(lambda x: x).sum()
+ expected = ts.groupby(ts.index).sum()
+ expected.index.freq = None
+ tm.assert_series_equal(result, expected)
+
+ def test_groupby_with_datetime_key(self):
+ # GH 51158
+ df = DataFrame(
+ {
+ "id": ["a", "b"] * 3,
+ "b": date_range("2000-01-01", "2000-01-03", freq="9h"),
+ }
+ )
+ grouper = Grouper(key="b", freq="D")
+ gb = df.groupby([grouper, "id"])
+
+ # test number of groups
+ expected = {
+ (Timestamp("2000-01-01"), "a"): [0, 2],
+ (Timestamp("2000-01-01"), "b"): [1],
+ (Timestamp("2000-01-02"), "a"): [4],
+ (Timestamp("2000-01-02"), "b"): [3, 5],
+ }
+ tm.assert_dict_equal(gb.groups, expected)
+
+ # test number of group keys
+ assert len(gb.groups.keys()) == 4
+
+ def test_grouping_error_on_multidim_input(self, df):
+ msg = "Grouper for '' not 1-dimensional"
+ with pytest.raises(ValueError, match=msg):
+ Grouping(df.index, df[["A", "A"]])
+
+ def test_multiindex_negative_level(self, multiindex_dataframe_random_data):
+ # GH 13901
+ result = multiindex_dataframe_random_data.groupby(level=-1).sum()
+ expected = multiindex_dataframe_random_data.groupby(level="second").sum()
+ tm.assert_frame_equal(result, expected)
+
+ result = multiindex_dataframe_random_data.groupby(level=-2).sum()
+ expected = multiindex_dataframe_random_data.groupby(level="first").sum()
+ tm.assert_frame_equal(result, expected)
+
+ result = multiindex_dataframe_random_data.groupby(level=[-2, -1]).sum()
+ expected = multiindex_dataframe_random_data.sort_index()
+ tm.assert_frame_equal(result, expected)
+
+ result = multiindex_dataframe_random_data.groupby(level=[-1, "first"]).sum()
+ expected = multiindex_dataframe_random_data.groupby(
+ level=["second", "first"]
+ ).sum()
+ tm.assert_frame_equal(result, expected)
+
+ def test_agg_with_dict_raises(self, df):
+ df.columns = np.arange(len(df.columns))
+ msg = "nested renamer is not supported"
+ with pytest.raises(SpecificationError, match=msg):
+ df.groupby(1, as_index=False)[2].agg({"Q": np.mean})
+
+ def test_multiindex_columns_empty_level(self):
+ lst = [["count", "values"], ["to filter", ""]]
+ midx = MultiIndex.from_tuples(lst)
+
+ df = DataFrame([[1, "A"]], columns=midx)
+
+ msg = "In a future version, the keys"
+ grouped = df.groupby("to filter").groups
+ assert grouped["A"] == [0]
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ grouped = df.groupby([("to filter", "")]).groups
+ assert grouped["A"] == [0]
+
+ df = DataFrame([[1, "A"], [2, "B"]], columns=midx)
+
+ expected = df.groupby("to filter").groups
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = df.groupby([("to filter", "")]).groups
+ assert result == expected
+
+ df = DataFrame([[1, "A"], [2, "A"]], columns=midx)
+
+ expected = df.groupby("to filter").groups
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = df.groupby([("to filter", "")]).groups
+ tm.assert_dict_equal(result, expected)
+
+ def test_groupby_multiindex_tuple(self):
+ # GH 17979, GH#59179
+ df = DataFrame(
+ [[1, 2, 3, 4], [3, 4, 5, 6], [1, 4, 2, 3]],
+ columns=MultiIndex.from_arrays([["a", "b", "b", "c"], [1, 1, 2, 2]]),
+ )
+
+ msg = "In a future version, the keys"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = df.groupby([("b", 1)]).groups
+ result = df.groupby(("b", 1)).groups
+ tm.assert_dict_equal(expected, result)
+
+ df2 = DataFrame(
+ df.values,
+ columns=MultiIndex.from_arrays(
+ [["a", "b", "b", "c"], ["d", "d", "e", "e"]]
+ ),
+ )
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = df2.groupby([("b", "d")]).groups
+ result = df.groupby(("b", 1)).groups
+ tm.assert_dict_equal(expected, result)
+
+ df3 = DataFrame(df.values, columns=[("a", "d"), ("b", "d"), ("b", "e"), "c"])
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = df3.groupby([("b", "d")]).groups
+ result = df.groupby(("b", 1)).groups
+ tm.assert_dict_equal(expected, result)
+
+ def test_groupby_multiindex_partial_indexing_equivalence(self):
+ # GH 17977, GH#59179
+ df = DataFrame(
+ [[1, 2, 3, 4], [3, 4, 5, 6], [1, 4, 2, 3]],
+ columns=MultiIndex.from_arrays([["a", "b", "b", "c"], [1, 1, 2, 2]]),
+ )
+
+ expected_mean = df.groupby([("a", 1)])[[("b", 1), ("b", 2)]].mean()
+ result_mean = df.groupby([("a", 1)])["b"].mean()
+ tm.assert_frame_equal(expected_mean, result_mean)
+
+ expected_sum = df.groupby([("a", 1)])[[("b", 1), ("b", 2)]].sum()
+ result_sum = df.groupby([("a", 1)])["b"].sum()
+ tm.assert_frame_equal(expected_sum, result_sum)
+
+ expected_count = df.groupby([("a", 1)])[[("b", 1), ("b", 2)]].count()
+ result_count = df.groupby([("a", 1)])["b"].count()
+ tm.assert_frame_equal(expected_count, result_count)
+
+ expected_min = df.groupby([("a", 1)])[[("b", 1), ("b", 2)]].min()
+ result_min = df.groupby([("a", 1)])["b"].min()
+ tm.assert_frame_equal(expected_min, result_min)
+
+ expected_max = df.groupby([("a", 1)])[[("b", 1), ("b", 2)]].max()
+ result_max = df.groupby([("a", 1)])["b"].max()
+ tm.assert_frame_equal(expected_max, result_max)
+
+ msg = "In a future version, the keys"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected_groups = df.groupby([("a", 1)])[[("b", 1), ("b", 2)]].groups
+ result_groups = df.groupby([("a", 1)])["b"].groups
+ tm.assert_dict_equal(expected_groups, result_groups)
+
+ def test_groupby_level(self, sort, multiindex_dataframe_random_data, df):
+ # GH 17537
+ frame = multiindex_dataframe_random_data
+ deleveled = frame.reset_index()
+
+ result0 = frame.groupby(level=0, sort=sort).sum()
+ result1 = frame.groupby(level=1, sort=sort).sum()
+
+ expected0 = frame.groupby(deleveled["first"].values, sort=sort).sum()
+ expected1 = frame.groupby(deleveled["second"].values, sort=sort).sum()
+
+ expected0.index.name = "first"
+ expected1.index.name = "second"
+
+ assert result0.index.name == "first"
+ assert result1.index.name == "second"
+
+ tm.assert_frame_equal(result0, expected0)
+ tm.assert_frame_equal(result1, expected1)
+ assert result0.index.name == frame.index.names[0]
+ assert result1.index.name == frame.index.names[1]
+
+ # groupby level name
+ result0 = frame.groupby(level="first", sort=sort).sum()
+ result1 = frame.groupby(level="second", sort=sort).sum()
+ tm.assert_frame_equal(result0, expected0)
+ tm.assert_frame_equal(result1, expected1)
+
+ # raise exception for non-MultiIndex
+ msg = "level > 0 or level < -1 only valid with MultiIndex"
+ with pytest.raises(ValueError, match=msg):
+ df.groupby(level=1)
+
+ def test_groupby_level_index_names(self):
+ # GH4014 this used to raise ValueError since 'exp'>1 (in py2)
+ df = DataFrame({"exp": ["A"] * 3 + ["B"] * 3, "var1": range(6)}).set_index(
+ "exp"
+ )
+ df.groupby(level="exp")
+ msg = "level name foo is not the name of the index"
+ with pytest.raises(ValueError, match=msg):
+ df.groupby(level="foo")
+
+ def test_groupby_level_with_nas(self, sort):
+ # GH 17537
+ index = MultiIndex(
+ levels=[[1, 0], [0, 1, 2, 3]],
+ codes=[[1, 1, 1, 1, 0, 0, 0, 0], [0, 1, 2, 3, 0, 1, 2, 3]],
+ )
+
+ # factorizing doesn't confuse things
+ s = Series(np.arange(8.0), index=index)
+ result = s.groupby(level=0, sort=sort).sum()
+ expected = Series([6.0, 22.0], index=[0, 1])
+ tm.assert_series_equal(result, expected)
+
+ index = MultiIndex(
+ levels=[[1, 0], [0, 1, 2, 3]],
+ codes=[[1, 1, 1, 1, -1, 0, 0, 0], [0, 1, 2, 3, 0, 1, 2, 3]],
+ )
+
+ # factorizing doesn't confuse things
+ s = Series(np.arange(8.0), index=index)
+ result = s.groupby(level=0, sort=sort).sum()
+ expected = Series([6.0, 18.0], index=[0.0, 1.0])
+ tm.assert_series_equal(result, expected)
+
+ def test_groupby_args(self, multiindex_dataframe_random_data):
+ # PR8618 and issue 8015
+ frame = multiindex_dataframe_random_data
+
+ msg = "You have to supply one of 'by' and 'level'"
+ with pytest.raises(TypeError, match=msg):
+ frame.groupby()
+
+ msg = "You have to supply one of 'by' and 'level'"
+ with pytest.raises(TypeError, match=msg):
+ frame.groupby(by=None, level=None)
+
+ @pytest.mark.parametrize(
+ "sort,labels",
+ [
+ [True, [2, 2, 2, 0, 0, 1, 1, 3, 3, 3]],
+ [False, [0, 0, 0, 1, 1, 2, 2, 3, 3, 3]],
+ ],
+ )
+ def test_level_preserve_order(self, sort, labels, multiindex_dataframe_random_data):
+ # GH 17537
+ grouped = multiindex_dataframe_random_data.groupby(level=0, sort=sort)
+ exp_labels = np.array(labels, np.intp)
+ tm.assert_almost_equal(grouped._grouper.ids, exp_labels)
+
+ def test_grouping_labels(self, multiindex_dataframe_random_data):
+ grouped = multiindex_dataframe_random_data.groupby(
+ multiindex_dataframe_random_data.index.get_level_values(0)
+ )
+ exp_labels = np.array([2, 2, 2, 0, 0, 1, 1, 3, 3, 3], dtype=np.intp)
+ tm.assert_almost_equal(grouped._grouper.codes[0], exp_labels)
+
+ def test_list_grouper_with_nat(self):
+ # GH 14715, GH#59179
+ df = DataFrame({"date": date_range("1/1/2011", periods=365, freq="D")})
+ df.iloc[-1] = pd.NaT
+ grouper = Grouper(key="date", freq="YS")
+ msg = "In a future version, the keys"
+
+ # Grouper in a list grouping
+ gb = df.groupby([grouper])
+ expected = {Timestamp("2011-01-01"): Index(list(range(364)))}
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = gb.groups
+ tm.assert_dict_equal(result, expected)
+
+ # Test case without a list
+ result = df.groupby(grouper)
+ expected = {Timestamp("2011-01-01"): 365}
+ tm.assert_dict_equal(result.groups, expected)
+
+ @pytest.mark.parametrize(
+ "func,expected",
+ [
+ (
+ "transform",
+ Series(name=2, dtype=np.float64),
+ ),
+ (
+ "agg",
+ Series(
+ name=2, dtype=np.float64, index=Index([], dtype=np.float64, name=1)
+ ),
+ ),
+ (
+ "apply",
+ Series(
+ name=2, dtype=np.float64, index=Index([], dtype=np.float64, name=1)
+ ),
+ ),
+ ],
+ )
+ def test_evaluate_with_empty_groups(self, func, expected):
+ # 26208
+ # test transform'ing empty groups
+ # (not testing other agg fns, because they return
+ # different index objects.
+ df = DataFrame({1: [], 2: []})
+ g = df.groupby(1, group_keys=True)
+ result = getattr(g[2], func)(lambda x: x)
+ tm.assert_series_equal(result, expected)
+
+ def test_groupby_apply_empty_with_group_keys_false(self):
+ # 60471
+ # test apply'ing empty groups with group_keys False
+ # (not testing other agg fns, because they return
+ # different index objects.
+ df = DataFrame({"A": [], "B": [], "C": []})
+ g = df.groupby("A", group_keys=False)
+ result = g.apply(lambda x: x / x.sum())
+ expected = DataFrame({"B": [], "C": []}, index=None)
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_empty(self):
+ # https://github.com/pandas-dev/pandas/issues/27190
+ s = Series([], name="name", dtype="float64")
+ gr = s.groupby([])
+
+ result = gr.mean()
+ expected = s.set_axis(Index([], dtype=np.intp))
+ tm.assert_series_equal(result, expected)
+
+ # check group properties
+ assert len(gr._grouper.groupings) == 1
+ tm.assert_numpy_array_equal(
+ gr._grouper.ids, np.array([], dtype=np.dtype(np.intp))
+ )
+
+ assert gr._grouper.ngroups == 0
+
+ # check name
+ gb = s.groupby(s)
+ grouper = gb._grouper
+ result = grouper.names
+ expected = ["name"]
+ assert result == expected
+
+ def test_groupby_level_index_value_all_na(self):
+ # issue 20519
+ df = DataFrame(
+ [["x", np.nan, 10], [None, np.nan, 20]], columns=["A", "B", "C"]
+ ).set_index(["A", "B"])
+ result = df.groupby(level=["A", "B"]).sum()
+ expected = DataFrame(
+ data=[],
+ index=MultiIndex(
+ levels=[Index(["x"], dtype="str"), Index([], dtype="float64")],
+ codes=[[], []],
+ names=["A", "B"],
+ ),
+ columns=["C"],
+ dtype="int64",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_multiindex_level_empty(self):
+ # https://github.com/pandas-dev/pandas/issues/31670
+ df = DataFrame(
+ [[123, "a", 1.0], [123, "b", 2.0]], columns=["id", "category", "value"]
+ )
+ df = df.set_index(["id", "category"])
+ empty = df[df.value < 0]
+ result = empty.groupby("id").sum()
+ expected = DataFrame(
+ dtype="float64",
+ columns=["value"],
+ index=Index([], dtype=np.int64, name="id"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_tuple_keys_handle_multiindex(self):
+ # https://github.com/pandas-dev/pandas/issues/21340
+ df = DataFrame(
+ {
+ "num1": [0, 8, 9, 4, 3, 3, 5, 9, 3, 6],
+ "num2": [3, 8, 6, 4, 9, 2, 1, 7, 0, 9],
+ "num3": [6, 5, 7, 8, 5, 1, 1, 10, 7, 8],
+ "category_tuple": [
+ (0, 1),
+ (0, 1),
+ (0, 1),
+ (0, 4),
+ (2, 3),
+ (2, 3),
+ (2, 3),
+ (2, 3),
+ (5,),
+ (6,),
+ ],
+ "category_string": list("aaabbbbcde"),
+ }
+ )
+ expected = df.sort_values(by=["category_tuple", "num1"])
+ result = df.groupby("category_tuple").apply(lambda x: x.sort_values(by="num1"))
+ expected = expected[result.columns]
+ tm.assert_frame_equal(result.reset_index(drop=True), expected)
+
+ def test_groupby_grouper_immutable_list_item(self):
+ # GH 26564 - prevent 'ValueError: all keys need to be the same shape'
+ # when reusing a list of groupers
+ df1 = DataFrame([["05/29/2019"], ["05/28/2019"]], columns=["date"]).assign(
+ date=lambda df: pd.to_datetime(df["date"])
+ )
+ df2 = DataFrame(columns=["date"]).assign(
+ date=lambda df: pd.to_datetime(df["date"])
+ )
+
+ groupers = [Grouper(key="date", freq="1D")]
+
+ df1.groupby(groupers).head()
+ # no error
+ df2.groupby(groupers).head()
+
+
+# get_group
+# --------------------------------
+
+
+class TestGetGroup:
+ def test_get_group(self):
+ # GH 5267
+ # be datelike friendly
+ df = DataFrame(
+ {
+ "DATE": pd.to_datetime(
+ [
+ "10-Oct-2013",
+ "10-Oct-2013",
+ "10-Oct-2013",
+ "11-Oct-2013",
+ "11-Oct-2013",
+ "11-Oct-2013",
+ ]
+ ),
+ "label": ["foo", "foo", "bar", "foo", "foo", "bar"],
+ "VAL": [1, 2, 3, 4, 5, 6],
+ }
+ )
+
+ g = df.groupby("DATE")
+ key = next(iter(g.groups))
+ result1 = g.get_group(key)
+ result2 = g.get_group(Timestamp(key).to_pydatetime())
+ result3 = g.get_group(str(Timestamp(key)))
+ tm.assert_frame_equal(result1, result2)
+ tm.assert_frame_equal(result1, result3)
+
+ g = df.groupby(["DATE", "label"])
+
+ key = next(iter(g.groups))
+ result1 = g.get_group(key)
+ result2 = g.get_group((Timestamp(key[0]).to_pydatetime(), key[1]))
+ result3 = g.get_group((str(Timestamp(key[0])), key[1]))
+ tm.assert_frame_equal(result1, result2)
+ tm.assert_frame_equal(result1, result3)
+
+ # must pass a same-length tuple with multiple keys
+ msg = "must supply a tuple to get_group with multiple grouping keys"
+ with pytest.raises(ValueError, match=msg):
+ g.get_group("foo")
+ with pytest.raises(ValueError, match=msg):
+ g.get_group("foo")
+ msg = "must supply a same-length tuple to get_group with multiple grouping keys"
+ with pytest.raises(ValueError, match=msg):
+ g.get_group(("foo", "bar", "baz"))
+
+ def test_get_group_empty_bins(self, observed):
+ d = DataFrame([3, 1, 7, 6])
+ bins = [0, 5, 10, 15]
+ g = d.groupby(pd.cut(d[0], bins), observed=observed)
+
+ # TODO: should prob allow a str of Interval work as well
+ # IOW '(0, 5]'
+ result = g.get_group(pd.Interval(0, 5))
+ expected = DataFrame([3, 1], index=[0, 1])
+ tm.assert_frame_equal(result, expected)
+
+ msg = r"Interval\(10, 15, closed='right'\)"
+ with pytest.raises(KeyError, match=msg):
+ g.get_group(pd.Interval(10, 15))
+
+ def test_get_group_grouped_by_tuple(self):
+ # GH 8121
+ df = DataFrame([[(1,), (1, 2), (1,), (1, 2)]], index=["ids"]).T
+ gr = df.groupby("ids")
+ expected = DataFrame({"ids": [(1,), (1,)]}, index=[0, 2])
+ result = gr.get_group((1,))
+ tm.assert_frame_equal(result, expected)
+
+ dt = pd.to_datetime(["2010-01-01", "2010-01-02", "2010-01-01", "2010-01-02"])
+ df = DataFrame({"ids": [(x,) for x in dt]})
+ gr = df.groupby("ids")
+ result = gr.get_group(("2010-01-01",))
+ expected = DataFrame({"ids": [(dt[0],), (dt[0],)]}, index=[0, 2])
+ tm.assert_frame_equal(result, expected)
+
+ def test_get_group_grouped_by_tuple_with_lambda(self):
+ # GH 36158
+ df = DataFrame(
+ {
+ "Tuples": (
+ (x, y)
+ for x in [0, 1]
+ for y in np.random.default_rng(2).integers(3, 5, 5)
+ )
+ }
+ )
+
+ gb = df.groupby("Tuples")
+ gb_lambda = df.groupby(lambda x: df.iloc[x, 0])
+
+ expected = gb.get_group(next(iter(gb.groups.keys())))
+ result = gb_lambda.get_group(next(iter(gb_lambda.groups.keys())))
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_with_empty(self):
+ index = pd.DatetimeIndex(())
+ data = ()
+ series = Series(data, index, dtype=object)
+ grouper = Grouper(freq="D")
+ grouped = series.groupby(grouper)
+ assert next(iter(grouped), None) is None
+
+ def test_groupby_with_single_column(self):
+ df = DataFrame({"a": list("abssbab")})
+ tm.assert_frame_equal(df.groupby("a").get_group("a"), df.iloc[[0, 5]])
+ # GH 13530
+ exp = DataFrame(
+ index=Index(["a", "b", "s"], name="a"), columns=Index([], dtype="str")
+ )
+ tm.assert_frame_equal(df.groupby("a").count(), exp)
+ tm.assert_frame_equal(df.groupby("a").sum(), exp)
+
+ exp = df.iloc[[3, 4, 5]]
+ tm.assert_frame_equal(df.groupby("a").nth(1), exp)
+
+ def test_gb_key_len_equal_axis_len(self):
+ # GH16843
+ # test ensures that index and column keys are recognized correctly
+ # when number of keys equals axis length of groupby
+ df = DataFrame(
+ [["foo", "bar", "B", 1], ["foo", "bar", "B", 2], ["foo", "baz", "C", 3]],
+ columns=["first", "second", "third", "one"],
+ )
+ df = df.set_index(["first", "second"])
+ df = df.groupby(["first", "second", "third"]).size()
+ assert df.loc[("foo", "bar", "B")] == 2
+ assert df.loc[("foo", "baz", "C")] == 1
+
+
+# groups & iteration
+# --------------------------------
+
+
+class TestIteration:
+ def test_groups(self, df):
+ grouped = df.groupby(["A"])
+ msg = "In a future version, the keys"
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ groups = grouped.groups
+ assert groups is grouped.groups # caching works
+
+ for k, v in groups.items():
+ assert (df.loc[v]["A"] == k).all()
+
+ grouped = df.groupby(["A", "B"])
+ groups = grouped.groups
+ assert groups is grouped.groups # caching works
+
+ for k, v in groups.items():
+ assert (df.loc[v]["A"] == k[0]).all()
+ assert (df.loc[v]["B"] == k[1]).all()
+
+ def test_grouping_is_iterable(self, tsframe):
+ # this code path isn't used anywhere else
+ # not sure it's useful
+ grouped = tsframe.groupby([lambda x: x.weekday(), lambda x: x.year])
+
+ # test it works
+ for g in grouped._grouper.groupings[0]:
+ pass
+
+ def test_multi_iter(self):
+ s = Series(np.arange(6))
+ k1 = np.array(["a", "a", "a", "b", "b", "b"])
+ k2 = np.array(["1", "2", "1", "2", "1", "2"])
+
+ grouped = s.groupby([k1, k2])
+
+ iterated = list(grouped)
+ expected = [
+ ("a", "1", s[[0, 2]]),
+ ("a", "2", s[[1]]),
+ ("b", "1", s[[4]]),
+ ("b", "2", s[[3, 5]]),
+ ]
+ for i, ((one, two), three) in enumerate(iterated):
+ e1, e2, e3 = expected[i]
+ assert e1 == one
+ assert e2 == two
+ tm.assert_series_equal(three, e3)
+
+ def test_multi_iter_frame(self, three_group):
+ k1 = np.array(["b", "b", "b", "a", "a", "a"])
+ k2 = np.array(["1", "2", "1", "2", "1", "2"])
+ df = DataFrame(
+ {
+ "v1": np.random.default_rng(2).standard_normal(6),
+ "v2": np.random.default_rng(2).standard_normal(6),
+ "k1": k1,
+ "k2": k2,
+ },
+ index=["one", "two", "three", "four", "five", "six"],
+ )
+
+ grouped = df.groupby(["k1", "k2"])
+
+ # things get sorted!
+ iterated = list(grouped)
+ idx = df.index
+ expected = [
+ ("a", "1", df.loc[idx[[4]]]),
+ ("a", "2", df.loc[idx[[3, 5]]]),
+ ("b", "1", df.loc[idx[[0, 2]]]),
+ ("b", "2", df.loc[idx[[1]]]),
+ ]
+ for i, ((one, two), three) in enumerate(iterated):
+ e1, e2, e3 = expected[i]
+ assert e1 == one
+ assert e2 == two
+ tm.assert_frame_equal(three, e3)
+
+ # don't iterate through groups with no data
+ df["k1"] = np.array(["b", "b", "b", "a", "a", "a"])
+ df["k2"] = np.array(["1", "1", "1", "2", "2", "2"])
+ grouped = df.groupby(["k1", "k2"])
+ # calling `dict` on a DataFrameGroupBy leads to a TypeError,
+ # we need to use a dictionary comprehension here
+ groups = {key: gp for key, gp in grouped} # noqa: C416
+ assert len(groups) == 2
+
+ def test_dictify(self, df):
+ dict(iter(df.groupby("A")))
+ dict(iter(df.groupby(["A", "B"])))
+ dict(iter(df["C"].groupby(df["A"])))
+ dict(iter(df["C"].groupby([df["A"], df["B"]])))
+ dict(iter(df.groupby("A")["C"]))
+ dict(iter(df.groupby(["A", "B"])["C"]))
+
+ def test_groupby_with_small_elem(self):
+ # GH 8542
+ # length=2
+ df = DataFrame(
+ {"event": ["start", "start"], "change": [1234, 5678]},
+ index=pd.DatetimeIndex(["2014-09-10", "2013-10-10"]),
+ )
+ grouped = df.groupby([Grouper(freq="ME"), "event"])
+ assert len(grouped.groups) == 2
+ assert grouped.ngroups == 2
+ assert (Timestamp("2014-09-30"), "start") in grouped.groups
+ assert (Timestamp("2013-10-31"), "start") in grouped.groups
+
+ res = grouped.get_group((Timestamp("2014-09-30"), "start"))
+ tm.assert_frame_equal(res, df.iloc[[0], :])
+ res = grouped.get_group((Timestamp("2013-10-31"), "start"))
+ tm.assert_frame_equal(res, df.iloc[[1], :])
+
+ df = DataFrame(
+ {"event": ["start", "start", "start"], "change": [1234, 5678, 9123]},
+ index=pd.DatetimeIndex(["2014-09-10", "2013-10-10", "2014-09-15"]),
+ )
+ grouped = df.groupby([Grouper(freq="ME"), "event"])
+ assert len(grouped.groups) == 2
+ assert grouped.ngroups == 2
+ assert (Timestamp("2014-09-30"), "start") in grouped.groups
+ assert (Timestamp("2013-10-31"), "start") in grouped.groups
+
+ res = grouped.get_group((Timestamp("2014-09-30"), "start"))
+ tm.assert_frame_equal(res, df.iloc[[0, 2], :])
+ res = grouped.get_group((Timestamp("2013-10-31"), "start"))
+ tm.assert_frame_equal(res, df.iloc[[1], :])
+
+ # length=3
+ df = DataFrame(
+ {"event": ["start", "start", "start"], "change": [1234, 5678, 9123]},
+ index=pd.DatetimeIndex(["2014-09-10", "2013-10-10", "2014-08-05"]),
+ )
+ grouped = df.groupby([Grouper(freq="ME"), "event"])
+ assert len(grouped.groups) == 3
+ assert grouped.ngroups == 3
+ assert (Timestamp("2014-09-30"), "start") in grouped.groups
+ assert (Timestamp("2013-10-31"), "start") in grouped.groups
+ assert (Timestamp("2014-08-31"), "start") in grouped.groups
+
+ res = grouped.get_group((Timestamp("2014-09-30"), "start"))
+ tm.assert_frame_equal(res, df.iloc[[0], :])
+ res = grouped.get_group((Timestamp("2013-10-31"), "start"))
+ tm.assert_frame_equal(res, df.iloc[[1], :])
+ res = grouped.get_group((Timestamp("2014-08-31"), "start"))
+ tm.assert_frame_equal(res, df.iloc[[2], :])
+
+ def test_grouping_string_repr(self):
+ # GH 13394
+ mi = MultiIndex.from_arrays([list("AAB"), list("aba")])
+ df = DataFrame([[1, 2, 3]], columns=mi)
+ gr = df.groupby(df[("A", "a")])
+
+ result = gr._grouper.groupings[0].__repr__()
+ expected = "Grouping(('A', 'a'))"
+ assert result == expected
+
+
+def test_grouping_by_key_is_in_axis():
+ # GH#50413 - Groupers specified by key are in-axis
+ df = DataFrame({"a": [1, 1, 2], "b": [1, 1, 2], "c": [3, 4, 5]}).set_index("a")
+ gb = df.groupby([Grouper(level="a"), Grouper(key="b")], as_index=False)
+ assert not gb._grouper.groupings[0].in_axis
+ assert gb._grouper.groupings[1].in_axis
+
+ result = gb.sum()
+ expected = DataFrame({"a": [1, 2], "b": [1, 2], "c": [7, 5]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_any_with_timedelta():
+ # GH#59712
+ df = DataFrame({"value": [pd.Timedelta(1), pd.NaT]})
+
+ result = df.groupby(np.array([0, 1], dtype=np.int64))["value"].any()
+
+ expected = Series({0: True, 1: False}, name="value", dtype=bool)
+ expected.index = expected.index.astype(np.int64)
+
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_index_as_string.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_index_as_string.py
new file mode 100644
index 0000000000000000000000000000000000000000..743db7e70b14b7f8c2d047e403884bdbf1b878a4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_index_as_string.py
@@ -0,0 +1,72 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "key_strs,groupers",
+ [
+ ("inner", pd.Grouper(level="inner")), # Index name
+ (["inner"], [pd.Grouper(level="inner")]), # List of index name
+ (["B", "inner"], ["B", pd.Grouper(level="inner")]), # Column and index
+ (["inner", "B"], [pd.Grouper(level="inner"), "B"]), # Index and column
+ ],
+)
+@pytest.mark.parametrize("levels", [["inner"], ["inner", "outer"]])
+def test_grouper_index_level_as_string(levels, key_strs, groupers):
+ frame = pd.DataFrame(
+ {
+ "outer": ["a", "a", "a", "b", "b", "b"],
+ "inner": [1, 2, 3, 1, 2, 3],
+ "A": np.arange(6),
+ "B": ["one", "one", "two", "two", "one", "one"],
+ }
+ )
+ frame = frame.set_index(levels)
+ if "B" not in key_strs or "outer" in frame.columns:
+ result = frame.groupby(key_strs).mean(numeric_only=True)
+ expected = frame.groupby(groupers).mean(numeric_only=True)
+ else:
+ result = frame.groupby(key_strs).mean()
+ expected = frame.groupby(groupers).mean()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "levels",
+ [
+ "inner",
+ "outer",
+ "B",
+ ["inner"],
+ ["outer"],
+ ["B"],
+ ["inner", "outer"],
+ ["outer", "inner"],
+ ["inner", "outer", "B"],
+ ["B", "outer", "inner"],
+ ],
+)
+def test_grouper_index_level_as_string_series(levels):
+ # Compute expected result
+ df = pd.DataFrame(
+ {
+ "outer": ["a", "a", "a", "b", "b", "b"],
+ "inner": [1, 2, 3, 1, 2, 3],
+ "A": np.arange(6),
+ "B": ["one", "one", "two", "two", "one", "one"],
+ }
+ )
+ series = df.set_index(["outer", "inner", "B"])["A"]
+ if isinstance(levels, list):
+ groupers = [pd.Grouper(level=lv) for lv in levels]
+ else:
+ groupers = pd.Grouper(level=levels)
+
+ expected = series.groupby(groupers).mean()
+
+ # Compute and check result
+ result = series.groupby(levels).mean()
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3d3f509e186ac8099dd5fd1c50e8265753fe16c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_indexing.py
@@ -0,0 +1,310 @@
+# Test GroupBy._positional_selector positional grouped indexing GH#42864
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "arg, expected_rows",
+ [
+ [0, [0, 1, 4]],
+ [2, [5]],
+ [5, []],
+ [-1, [3, 4, 7]],
+ [-2, [1, 6]],
+ [-6, []],
+ ],
+)
+def test_int(slice_test_df, slice_test_grouped, arg, expected_rows):
+ # Test single integer
+ result = slice_test_grouped._positional_selector[arg]
+ expected = slice_test_df.iloc[expected_rows]
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_slice(slice_test_df, slice_test_grouped):
+ # Test single slice
+ result = slice_test_grouped._positional_selector[0:3:2]
+ expected = slice_test_df.iloc[[0, 1, 4, 5]]
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "arg, expected_rows",
+ [
+ [[0, 2], [0, 1, 4, 5]],
+ [[0, 2, -1], [0, 1, 3, 4, 5, 7]],
+ [range(0, 3, 2), [0, 1, 4, 5]],
+ [{0, 2}, [0, 1, 4, 5]],
+ ],
+ ids=[
+ "list",
+ "negative",
+ "range",
+ "set",
+ ],
+)
+def test_list(slice_test_df, slice_test_grouped, arg, expected_rows):
+ # Test lists of integers and integer valued iterables
+ result = slice_test_grouped._positional_selector[arg]
+ expected = slice_test_df.iloc[expected_rows]
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_ints(slice_test_df, slice_test_grouped):
+ # Test tuple of ints
+ result = slice_test_grouped._positional_selector[0, 2, -1]
+ expected = slice_test_df.iloc[[0, 1, 3, 4, 5, 7]]
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_slices(slice_test_df, slice_test_grouped):
+ # Test tuple of slices
+ result = slice_test_grouped._positional_selector[:2, -2:]
+ expected = slice_test_df.iloc[[0, 1, 2, 3, 4, 6, 7]]
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_mix(slice_test_df, slice_test_grouped):
+ # Test mixed tuple of ints and slices
+ result = slice_test_grouped._positional_selector[0, 1, -2:]
+ expected = slice_test_df.iloc[[0, 1, 2, 3, 4, 6, 7]]
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "arg, expected_rows",
+ [
+ [0, [0, 1, 4]],
+ [[0, 2, -1], [0, 1, 3, 4, 5, 7]],
+ [(slice(None, 2), slice(-2, None)), [0, 1, 2, 3, 4, 6, 7]],
+ ],
+)
+def test_as_index(slice_test_df, arg, expected_rows):
+ # Test the default as_index behaviour
+ result = slice_test_df.groupby("Group", sort=False)._positional_selector[arg]
+ expected = slice_test_df.iloc[expected_rows]
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_doc_examples():
+ # Test the examples in the documentation
+ df = pd.DataFrame(
+ [["a", 1], ["a", 2], ["a", 3], ["b", 4], ["b", 5]], columns=["A", "B"]
+ )
+
+ grouped = df.groupby("A", as_index=False)
+
+ result = grouped._positional_selector[1:2]
+ expected = pd.DataFrame([["a", 2], ["b", 5]], columns=["A", "B"], index=[1, 4])
+
+ tm.assert_frame_equal(result, expected)
+
+ result = grouped._positional_selector[1, -1]
+ expected = pd.DataFrame(
+ [["a", 2], ["a", 3], ["b", 5]], columns=["A", "B"], index=[1, 2, 4]
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_multiindex():
+ # Test the multiindex mentioned as the use-case in the documentation
+
+ def _make_df_from_data(data):
+ rows = {}
+ for date in data:
+ for level in data[date]:
+ rows[(date, level[0])] = {"A": level[1], "B": level[2]}
+
+ df = pd.DataFrame.from_dict(rows, orient="index")
+ df.index.names = ("Date", "Item")
+ return df
+
+ rng = np.random.default_rng(2)
+ ndates = 100
+ nitems = 20
+ dates = pd.date_range("20130101", periods=ndates, freq="D")
+ items = [f"item {i}" for i in range(nitems)]
+
+ multiindex_data = {}
+ for date in dates:
+ nitems_for_date = nitems - rng.integers(0, 12)
+ levels = [
+ (item, rng.integers(0, 10000) / 100, rng.integers(0, 10000) / 100)
+ for item in items[:nitems_for_date]
+ ]
+ levels.sort(key=lambda x: x[1])
+ multiindex_data[date] = levels
+
+ df = _make_df_from_data(multiindex_data)
+ result = df.groupby("Date", as_index=False).nth(slice(3, -3))
+
+ sliced = {date: values[3:-3] for date, values in multiindex_data.items()}
+ expected = _make_df_from_data(sliced)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("arg", [1, 5, 30, 1000, -1, -5, -30, -1000])
+@pytest.mark.parametrize("method", ["head", "tail"])
+@pytest.mark.parametrize("simulated", [True, False])
+def test_against_head_and_tail(arg, method, simulated):
+ # Test gives the same results as grouped head and tail
+ n_groups = 100
+ n_rows_per_group = 30
+
+ data = {
+ "group": [
+ f"group {g}" for j in range(n_rows_per_group) for g in range(n_groups)
+ ],
+ "value": [
+ f"group {g} row {j}"
+ for j in range(n_rows_per_group)
+ for g in range(n_groups)
+ ],
+ }
+ df = pd.DataFrame(data)
+ grouped = df.groupby("group", as_index=False)
+ size = arg if arg >= 0 else n_rows_per_group + arg
+
+ if method == "head":
+ result = grouped._positional_selector[:arg]
+
+ if simulated:
+ indices = [
+ j * n_groups + i
+ for j in range(size)
+ for i in range(n_groups)
+ if j * n_groups + i < n_groups * n_rows_per_group
+ ]
+ expected = df.iloc[indices]
+
+ else:
+ expected = grouped.head(arg)
+
+ else:
+ result = grouped._positional_selector[-arg:]
+
+ if simulated:
+ indices = [
+ (n_rows_per_group + j - size) * n_groups + i
+ for j in range(size)
+ for i in range(n_groups)
+ if (n_rows_per_group + j - size) * n_groups + i >= 0
+ ]
+ expected = df.iloc[indices]
+
+ else:
+ expected = grouped.tail(arg)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("start", [None, 0, 1, 10, -1, -10])
+@pytest.mark.parametrize("stop", [None, 0, 1, 10, -1, -10])
+@pytest.mark.parametrize("step", [None, 1, 5])
+def test_against_df_iloc(start, stop, step):
+ # Test that a single group gives the same results as DataFrame.iloc
+ n_rows = 30
+
+ data = {
+ "group": ["group 0"] * n_rows,
+ "value": list(range(n_rows)),
+ }
+ df = pd.DataFrame(data)
+ grouped = df.groupby("group", as_index=False)
+
+ result = grouped._positional_selector[start:stop:step]
+ expected = df.iloc[start:stop:step]
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_series():
+ # Test grouped Series
+ ser = pd.Series([1, 2, 3, 4, 5], index=["a", "a", "a", "b", "b"])
+ grouped = ser.groupby(level=0)
+ result = grouped._positional_selector[1:2]
+ expected = pd.Series([2, 5], index=["a", "b"])
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("step", [1, 2, 3, 4, 5])
+def test_step(step):
+ # Test slice with various step values
+ data = [["x", f"x{i}"] for i in range(5)]
+ data += [["y", f"y{i}"] for i in range(4)]
+ data += [["z", f"z{i}"] for i in range(3)]
+ df = pd.DataFrame(data, columns=["A", "B"])
+
+ grouped = df.groupby("A", as_index=False)
+
+ result = grouped._positional_selector[::step]
+
+ data = [["x", f"x{i}"] for i in range(0, 5, step)]
+ data += [["y", f"y{i}"] for i in range(0, 4, step)]
+ data += [["z", f"z{i}"] for i in range(0, 3, step)]
+
+ index = [0 + i for i in range(0, 5, step)]
+ index += [5 + i for i in range(0, 4, step)]
+ index += [9 + i for i in range(0, 3, step)]
+
+ expected = pd.DataFrame(data, columns=["A", "B"], index=index)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_columns_on_iter():
+ # GitHub issue #44821
+ df = pd.DataFrame({k: range(10) for k in "ABC"})
+
+ # Group-by and select columns
+ cols = ["A", "B"]
+ for _, dg in df.groupby(df.A < 4)[cols]:
+ tm.assert_index_equal(dg.columns, pd.Index(cols))
+ assert "C" not in dg.columns
+
+
+@pytest.mark.parametrize("func", [list, pd.Index, pd.Series, np.array])
+def test_groupby_duplicated_columns(func):
+ # GH#44924
+ df = pd.DataFrame(
+ {
+ "A": [1, 2],
+ "B": [3, 3],
+ "C": ["G", "G"],
+ }
+ )
+ result = df.groupby("C")[func(["A", "B", "A"])].mean()
+ expected = pd.DataFrame(
+ [[1.5, 3.0, 1.5]], columns=["A", "B", "A"], index=pd.Index(["G"], name="C")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_get_nonexisting_groups():
+ # GH#32492
+ df = pd.DataFrame(
+ data={
+ "A": ["a1", "a2", None],
+ "B": ["b1", "b2", "b1"],
+ "val": [1, 2, 3],
+ }
+ )
+ grps = df.groupby(by=["A", "B"])
+
+ msg = "('a2', 'b1')"
+ with pytest.raises(KeyError, match=msg):
+ grps.get_group(("a2", "b1"))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_libgroupby.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_libgroupby.py
new file mode 100644
index 0000000000000000000000000000000000000000..60095663b5a7d0c6fa2896f85b49373c1afff37a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_libgroupby.py
@@ -0,0 +1,344 @@
+import numpy as np
+import pytest
+
+from pandas._libs import groupby as libgroupby
+from pandas._libs.groupby import (
+ group_cumprod,
+ group_cumsum,
+ group_mean,
+ group_sum,
+ group_var,
+)
+
+from pandas.core.dtypes.common import ensure_platform_int
+
+from pandas import isna
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("dtype, rtol", [("float32", 1e-2), ("float64", 1e-5)])
+class TestGroupVar:
+ def test_group_var_generic_1d(self, dtype, rtol):
+ prng = np.random.default_rng(2)
+
+ out = (np.nan * np.ones((5, 1))).astype(dtype)
+ counts = np.zeros(5, dtype="int64")
+ values = 10 * prng.random((15, 1)).astype(dtype)
+ labels = np.tile(np.arange(5), (3,)).astype("intp")
+
+ expected_out = (
+ np.squeeze(values).reshape((5, 3), order="F").std(axis=1, ddof=1) ** 2
+ )[:, np.newaxis]
+ expected_counts = counts + 3
+
+ group_var(out, counts, values, labels)
+ assert np.allclose(out, expected_out, rtol)
+ tm.assert_numpy_array_equal(counts, expected_counts)
+
+ def test_group_var_generic_1d_flat_labels(self, dtype, rtol):
+ prng = np.random.default_rng(2)
+
+ out = (np.nan * np.ones((1, 1))).astype(dtype)
+ counts = np.zeros(1, dtype="int64")
+ values = 10 * prng.random((5, 1)).astype(dtype)
+ labels = np.zeros(5, dtype="intp")
+
+ expected_out = np.array([[values.std(ddof=1) ** 2]])
+ expected_counts = counts + 5
+
+ group_var(out, counts, values, labels)
+
+ assert np.allclose(out, expected_out, rtol)
+ tm.assert_numpy_array_equal(counts, expected_counts)
+
+ def test_group_var_generic_2d_all_finite(self, dtype, rtol):
+ prng = np.random.default_rng(2)
+
+ out = (np.nan * np.ones((5, 2))).astype(dtype)
+ counts = np.zeros(5, dtype="int64")
+ values = 10 * prng.random((10, 2)).astype(dtype)
+ labels = np.tile(np.arange(5), (2,)).astype("intp")
+
+ expected_out = np.std(values.reshape(2, 5, 2), ddof=1, axis=0) ** 2
+ expected_counts = counts + 2
+
+ group_var(out, counts, values, labels)
+ assert np.allclose(out, expected_out, rtol)
+ tm.assert_numpy_array_equal(counts, expected_counts)
+
+ def test_group_var_generic_2d_some_nan(self, dtype, rtol):
+ prng = np.random.default_rng(2)
+
+ out = (np.nan * np.ones((5, 2))).astype(dtype)
+ counts = np.zeros(5, dtype="int64")
+ values = 10 * prng.random((10, 2)).astype(dtype)
+ values[:, 1] = np.nan
+ labels = np.tile(np.arange(5), (2,)).astype("intp")
+
+ expected_out = np.vstack(
+ [
+ values[:, 0].reshape(5, 2, order="F").std(ddof=1, axis=1) ** 2,
+ np.nan * np.ones(5),
+ ]
+ ).T.astype(dtype)
+ expected_counts = counts + 2
+
+ group_var(out, counts, values, labels)
+ tm.assert_almost_equal(out, expected_out, rtol=0.5e-06)
+ tm.assert_numpy_array_equal(counts, expected_counts)
+
+ def test_group_var_constant(self, dtype, rtol):
+ # Regression test from GH 10448.
+
+ out = np.array([[np.nan]], dtype=dtype)
+ counts = np.array([0], dtype="int64")
+ values = 0.832845131556193 * np.ones((3, 1), dtype=dtype)
+ labels = np.zeros(3, dtype="intp")
+
+ group_var(out, counts, values, labels)
+
+ assert counts[0] == 3
+ assert out[0, 0] >= 0
+ tm.assert_almost_equal(out[0, 0], 0.0)
+
+
+def test_group_var_large_inputs():
+ dtype = np.float64
+ prng = np.random.default_rng(2)
+
+ out = np.array([[np.nan]], dtype=dtype)
+ counts = np.array([0], dtype="int64")
+ values = (prng.random((10**6, 1)) + 10**12).astype(dtype)
+ labels = np.zeros(10**6, dtype="intp")
+
+ group_var(out, counts, values, labels)
+
+ assert counts[0] == 10**6
+ tm.assert_almost_equal(out[0, 0], 1.0 / 12, rtol=0.5e-3)
+
+
+@pytest.mark.parametrize("dtype", ["float32", "float64"])
+def test_group_ohlc(dtype):
+ obj = np.array(np.random.default_rng(2).standard_normal(20), dtype=dtype)
+
+ bins = np.array([6, 12, 20])
+ out = np.zeros((3, 4), dtype)
+ counts = np.zeros(len(out), dtype=np.int64)
+ labels = ensure_platform_int(np.repeat(np.arange(3), np.diff(np.r_[0, bins])))
+
+ func = libgroupby.group_ohlc
+ func(out, counts, obj[:, None], labels)
+
+ def _ohlc(group):
+ if isna(group).all():
+ return np.repeat(np.nan, 4)
+ return [group[0], group.max(), group.min(), group[-1]]
+
+ expected = np.array([_ohlc(obj[:6]), _ohlc(obj[6:12]), _ohlc(obj[12:])])
+
+ tm.assert_almost_equal(out, expected)
+ tm.assert_numpy_array_equal(counts, np.array([6, 6, 8], dtype=np.int64))
+
+ obj[:6] = np.nan
+ func(out, counts, obj[:, None], labels)
+ expected[0] = np.nan
+ tm.assert_almost_equal(out, expected)
+
+
+@pytest.mark.parametrize("dtype", [np.int64, np.uint64, np.float32, np.float64])
+@pytest.mark.parametrize(
+ "pd_op, np_op",
+ [
+ (group_cumsum, np.cumsum),
+ (group_cumprod, np.cumprod),
+ ],
+)
+def test_cython_group_transform(dtype, pd_op, np_op):
+ # see gh-4095
+ is_datetimelike = False
+
+ data = np.array([[1], [2], [3], [4]], dtype=dtype)
+ answer = np.zeros_like(data)
+
+ labels = np.array([0, 0, 0, 0], dtype=np.intp)
+ ngroups = 1
+ pd_op(answer, data, labels, ngroups, is_datetimelike)
+
+ tm.assert_numpy_array_equal(np_op(data), answer[:, 0], check_dtype=False)
+
+
+def test_cython_group_transform_algos():
+ # see gh-4095
+ is_datetimelike = False
+
+ # with nans
+ labels = np.array([0, 0, 0, 0, 0], dtype=np.intp)
+ ngroups = 1
+
+ data = np.array([[1], [2], [3], [np.nan], [4]], dtype="float64")
+ actual = np.zeros_like(data)
+ actual.fill(np.nan)
+ group_cumprod(actual, data, labels, ngroups, is_datetimelike)
+ expected = np.array([1, 2, 6, np.nan, 24], dtype="float64")
+ tm.assert_numpy_array_equal(actual[:, 0], expected)
+
+ actual = np.zeros_like(data)
+ actual.fill(np.nan)
+ group_cumsum(actual, data, labels, ngroups, is_datetimelike)
+ expected = np.array([1, 3, 6, np.nan, 10], dtype="float64")
+ tm.assert_numpy_array_equal(actual[:, 0], expected)
+
+ # timedelta
+ is_datetimelike = True
+ data = np.array([np.timedelta64(1, "ns")] * 5, dtype="m8[ns]")[:, None]
+ actual = np.zeros_like(data, dtype="int64")
+ group_cumsum(actual, data.view("int64"), labels, ngroups, is_datetimelike)
+ expected = np.array(
+ [
+ np.timedelta64(1, "ns"),
+ np.timedelta64(2, "ns"),
+ np.timedelta64(3, "ns"),
+ np.timedelta64(4, "ns"),
+ np.timedelta64(5, "ns"),
+ ]
+ )
+ tm.assert_numpy_array_equal(actual[:, 0].view("m8[ns]"), expected)
+
+
+def test_cython_group_mean_datetimelike():
+ actual = np.zeros(shape=(1, 1), dtype="float64")
+ counts = np.array([0], dtype="int64")
+ data = (
+ np.array(
+ [np.timedelta64(2, "ns"), np.timedelta64(4, "ns"), np.timedelta64("NaT")],
+ dtype="m8[ns]",
+ )[:, None]
+ .view("int64")
+ .astype("float64")
+ )
+ labels = np.zeros(len(data), dtype=np.intp)
+
+ group_mean(actual, counts, data, labels, is_datetimelike=True)
+
+ tm.assert_numpy_array_equal(actual[:, 0], np.array([3], dtype="float64"))
+
+
+def test_cython_group_mean_wrong_min_count():
+ actual = np.zeros(shape=(1, 1), dtype="float64")
+ counts = np.zeros(1, dtype="int64")
+ data = np.zeros(1, dtype="float64")[:, None]
+ labels = np.zeros(1, dtype=np.intp)
+
+ with pytest.raises(AssertionError, match="min_count"):
+ group_mean(actual, counts, data, labels, is_datetimelike=True, min_count=0)
+
+
+def test_cython_group_mean_not_datetimelike_but_has_NaT_values():
+ actual = np.zeros(shape=(1, 1), dtype="float64")
+ counts = np.array([0], dtype="int64")
+ data = (
+ np.array(
+ [np.timedelta64("NaT"), np.timedelta64("NaT")],
+ dtype="m8[ns]",
+ )[:, None]
+ .view("int64")
+ .astype("float64")
+ )
+ labels = np.zeros(len(data), dtype=np.intp)
+
+ group_mean(actual, counts, data, labels, is_datetimelike=False)
+
+ tm.assert_numpy_array_equal(
+ actual[:, 0], np.array(np.divide(np.add(data[0], data[1]), 2), dtype="float64")
+ )
+
+
+def test_cython_group_mean_Inf_at_beginning_and_end():
+ # GH 50367
+ actual = np.array([[np.nan, np.nan], [np.nan, np.nan]], dtype="float64")
+ counts = np.array([0, 0], dtype="int64")
+ data = np.array(
+ [[np.inf, 1.0], [1.0, 2.0], [2.0, 3.0], [3.0, 4.0], [4.0, 5.0], [5, np.inf]],
+ dtype="float64",
+ )
+ labels = np.array([0, 1, 0, 1, 0, 1], dtype=np.intp)
+
+ group_mean(actual, counts, data, labels, is_datetimelike=False)
+
+ expected = np.array([[np.inf, 3], [3, np.inf]], dtype="float64")
+
+ tm.assert_numpy_array_equal(
+ actual,
+ expected,
+ )
+
+
+@pytest.mark.parametrize(
+ "values, out",
+ [
+ ([[np.inf], [np.inf], [np.inf]], [[np.inf], [np.inf]]),
+ ([[np.inf], [np.inf], [-np.inf]], [[np.inf], [np.nan]]),
+ ([[np.inf], [-np.inf], [np.inf]], [[np.inf], [np.nan]]),
+ ([[np.inf], [-np.inf], [-np.inf]], [[np.inf], [-np.inf]]),
+ ],
+)
+def test_cython_group_sum_Inf_at_beginning_and_end(values, out):
+ # GH #53606
+ actual = np.array([[np.nan], [np.nan]], dtype="float64")
+ counts = np.array([0, 0], dtype="int64")
+ data = np.array(values, dtype="float64")
+ labels = np.array([0, 1, 1], dtype=np.intp)
+
+ group_sum(actual, counts, data, labels, None, is_datetimelike=False)
+
+ expected = np.array(out, dtype="float64")
+
+ tm.assert_numpy_array_equal(
+ actual,
+ expected,
+ )
+
+
+@pytest.mark.parametrize(
+ "values, expected_values",
+ [
+ (np.finfo(np.float64).max, [[np.inf]]),
+ (np.finfo(np.float64).min, [[-np.inf]]),
+ (
+ np.complex128(np.finfo(np.float64).min + np.finfo(np.float64).max * 1j),
+ [[complex(-np.inf, np.inf)]],
+ ),
+ (
+ np.complex128(np.finfo(np.float64).max + np.finfo(np.float64).min * 1j),
+ [[complex(np.inf, -np.inf)]],
+ ),
+ (
+ np.complex128(np.finfo(np.float64).max + np.finfo(np.float64).max * 1j),
+ [[complex(np.inf, np.inf)]],
+ ),
+ (
+ np.complex128(np.finfo(np.float64).min + np.finfo(np.float64).min * 1j),
+ [[complex(-np.inf, -np.inf)]],
+ ),
+ (
+ np.complex128(3.0 + np.finfo(np.float64).min * 1j),
+ [[complex(9.0, -np.inf)]],
+ ),
+ (
+ np.complex128(np.finfo(np.float64).max + 3 * 1j),
+ [[complex(np.inf, 9.0)]],
+ ),
+ ],
+)
+def test_cython_group_sum_overflow(values, expected_values):
+ # GH-60303
+ data = np.array([[values] for _ in range(3)])
+ labels = np.array([0, 0, 0], dtype=np.intp)
+ counts = np.array([0], dtype="int64")
+
+ expected = np.array(expected_values, dtype=values.dtype)
+ actual = np.zeros_like(expected)
+
+ group_sum(actual, counts, data, labels, None, is_datetimelike=False)
+
+ tm.assert_numpy_array_equal(actual, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_missing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_missing.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b590c50371e9e6f22247c73e94785b16678a2d4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_missing.py
@@ -0,0 +1,89 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("func", ["ffill", "bfill"])
+def test_groupby_column_index_name_lost_fill_funcs(func):
+ # GH: 29764 groupby loses index sometimes
+ df = DataFrame(
+ [[1, 1.0, -1.0], [1, np.nan, np.nan], [1, 2.0, -2.0]],
+ columns=Index(["type", "a", "b"], name="idx"),
+ )
+ df_grouped = df.groupby(["type"])[["a", "b"]]
+ result = getattr(df_grouped, func)().columns
+ expected = Index(["a", "b"], name="idx")
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["ffill", "bfill"])
+def test_groupby_fill_duplicate_column_names(func):
+ # GH: 25610 ValueError with duplicate column names
+ df1 = DataFrame({"field1": [1, 3, 4], "field2": [1, 3, 4]})
+ df2 = DataFrame({"field1": [1, np.nan, 4]})
+ df_grouped = pd.concat([df1, df2], axis=1).groupby(by=["field2"])
+ expected = DataFrame(
+ [[1, 1.0], [3, np.nan], [4, 4.0]], columns=["field1", "field1"]
+ )
+ result = getattr(df_grouped, func)()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("method", ["ffill", "bfill"])
+@pytest.mark.parametrize("has_nan_group", [True, False])
+def test_ffill_handles_nan_groups(dropna, method, has_nan_group):
+ # GH 34725
+
+ df_without_nan_rows = DataFrame([(1, 0.1), (2, 0.2)])
+
+ ridx = [-1, 0, -1, -1, 1, -1]
+ df = df_without_nan_rows.reindex(ridx).reset_index(drop=True)
+
+ group_b = np.nan if has_nan_group else "b"
+ df["group_col"] = pd.Series(["a"] * 3 + [group_b] * 3)
+
+ grouped = df.groupby(by="group_col", dropna=dropna)
+ result = getattr(grouped, method)(limit=None)
+
+ expected_rows = {
+ ("ffill", True, True): [-1, 0, 0, -1, -1, -1],
+ ("ffill", True, False): [-1, 0, 0, -1, 1, 1],
+ ("ffill", False, True): [-1, 0, 0, -1, 1, 1],
+ ("ffill", False, False): [-1, 0, 0, -1, 1, 1],
+ ("bfill", True, True): [0, 0, -1, -1, -1, -1],
+ ("bfill", True, False): [0, 0, -1, 1, 1, -1],
+ ("bfill", False, True): [0, 0, -1, 1, 1, -1],
+ ("bfill", False, False): [0, 0, -1, 1, 1, -1],
+ }
+
+ ridx = expected_rows.get((method, dropna, has_nan_group))
+ expected = df_without_nan_rows.reindex(ridx).reset_index(drop=True)
+ # columns are a 'take' on df.columns, which are object dtype
+ expected.columns = expected.columns.astype(object)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("min_count, value", [(2, np.nan), (-1, 1.0)])
+@pytest.mark.parametrize("func", ["first", "last", "max", "min"])
+def test_min_count(func, min_count, value):
+ # GH#37821
+ df = DataFrame({"a": [1] * 3, "b": [1, np.nan, np.nan], "c": [np.nan] * 3})
+ result = getattr(df.groupby("a"), func)(min_count=min_count)
+ expected = DataFrame({"b": [value], "c": [np.nan]}, index=Index([1], name="a"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_indices_with_missing():
+ # GH 9304
+ df = DataFrame({"a": [1, 1, np.nan], "b": [2, 3, 4], "c": [5, 6, 7]})
+ g = df.groupby(["a", "b"])
+ result = g.indices
+ expected = {(1.0, 2): np.array([0]), (1.0, 3): np.array([1])}
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_numba.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_numba.py
new file mode 100644
index 0000000000000000000000000000000000000000..082319d8479f02b9c61d08042269e77d56156898
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_numba.py
@@ -0,0 +1,82 @@
+import pytest
+
+from pandas.compat import is_platform_arm
+
+from pandas import (
+ DataFrame,
+ Series,
+ option_context,
+)
+import pandas._testing as tm
+from pandas.util.version import Version
+
+pytestmark = [pytest.mark.single_cpu]
+
+numba = pytest.importorskip("numba")
+pytestmark.append(
+ pytest.mark.skipif(
+ Version(numba.__version__) == Version("0.61") and is_platform_arm(),
+ reason=f"Segfaults on ARM platforms with numba {numba.__version__}",
+ )
+)
+
+
+@pytest.mark.filterwarnings("ignore")
+# Filter warnings when parallel=True and the function can't be parallelized by Numba
+class TestEngine:
+ def test_cython_vs_numba_frame(
+ self, sort, nogil, parallel, nopython, numba_supported_reductions
+ ):
+ func, kwargs = numba_supported_reductions
+ df = DataFrame({"a": [3, 2, 3, 2], "b": range(4), "c": range(1, 5)})
+ engine_kwargs = {"nogil": nogil, "parallel": parallel, "nopython": nopython}
+ gb = df.groupby("a", sort=sort)
+ result = getattr(gb, func)(
+ engine="numba", engine_kwargs=engine_kwargs, **kwargs
+ )
+ expected = getattr(gb, func)(**kwargs)
+ tm.assert_frame_equal(result, expected)
+
+ def test_cython_vs_numba_getitem(
+ self, sort, nogil, parallel, nopython, numba_supported_reductions
+ ):
+ func, kwargs = numba_supported_reductions
+ df = DataFrame({"a": [3, 2, 3, 2], "b": range(4), "c": range(1, 5)})
+ engine_kwargs = {"nogil": nogil, "parallel": parallel, "nopython": nopython}
+ gb = df.groupby("a", sort=sort)["c"]
+ result = getattr(gb, func)(
+ engine="numba", engine_kwargs=engine_kwargs, **kwargs
+ )
+ expected = getattr(gb, func)(**kwargs)
+ tm.assert_series_equal(result, expected)
+
+ def test_cython_vs_numba_series(
+ self, sort, nogil, parallel, nopython, numba_supported_reductions
+ ):
+ func, kwargs = numba_supported_reductions
+ ser = Series(range(3), index=[1, 2, 1], name="foo")
+ engine_kwargs = {"nogil": nogil, "parallel": parallel, "nopython": nopython}
+ gb = ser.groupby(level=0, sort=sort)
+ result = getattr(gb, func)(
+ engine="numba", engine_kwargs=engine_kwargs, **kwargs
+ )
+ expected = getattr(gb, func)(**kwargs)
+ tm.assert_series_equal(result, expected)
+
+ def test_as_index_false_unsupported(self, numba_supported_reductions):
+ func, kwargs = numba_supported_reductions
+ df = DataFrame({"a": [3, 2, 3, 2], "b": range(4), "c": range(1, 5)})
+ gb = df.groupby("a", as_index=False)
+ with pytest.raises(NotImplementedError, match="as_index=False"):
+ getattr(gb, func)(engine="numba", **kwargs)
+
+ def test_no_engine_doesnt_raise(self):
+ # GH55520
+ df = DataFrame({"a": [3, 2, 3, 2], "b": range(4), "c": range(1, 5)})
+ gb = df.groupby("a")
+ # Make sure behavior of functions w/out engine argument don't raise
+ # when the global use_numba option is set
+ with option_context("compute.use_numba", True):
+ res = gb.agg({"b": "first"})
+ expected = gb.agg({"b": "first"})
+ tm.assert_frame_equal(res, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_numeric_only.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_numeric_only.py
new file mode 100644
index 0000000000000000000000000000000000000000..b79ca8bf1ee3ae0e62b550d9c4322d7e10d7cf3a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_numeric_only.py
@@ -0,0 +1,445 @@
+import re
+
+import pytest
+
+from pandas._libs import lib
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.tests.groupby import get_groupby_method_args
+
+
+class TestNumericOnly:
+ # make sure that we are passing thru kwargs to our agg functions
+
+ @pytest.fixture
+ def df(self):
+ # GH3668
+ # GH5724
+ df = DataFrame(
+ {
+ "group": [1, 1, 2],
+ "int": [1, 2, 3],
+ "float": [4.0, 5.0, 6.0],
+ "string": Series(["a", "b", "c"], dtype="str"),
+ "object": Series(["a", "b", "c"], dtype=object),
+ "category_string": Series(list("abc")).astype("category"),
+ "category_int": [7, 8, 9],
+ "datetime": date_range("20130101", periods=3),
+ "datetimetz": date_range("20130101", periods=3, tz="US/Eastern"),
+ "timedelta": pd.timedelta_range("1 s", periods=3, freq="s"),
+ },
+ columns=[
+ "group",
+ "int",
+ "float",
+ "string",
+ "object",
+ "category_string",
+ "category_int",
+ "datetime",
+ "datetimetz",
+ "timedelta",
+ ],
+ )
+ return df
+
+ @pytest.mark.parametrize("method", ["mean", "median"])
+ def test_averages(self, df, method):
+ # mean / median
+ expected_columns_numeric = Index(["int", "float", "category_int"])
+
+ gb = df.groupby("group")
+ expected = DataFrame(
+ {
+ "category_int": [7.5, 9],
+ "float": [4.5, 6.0],
+ "timedelta": [pd.Timedelta("1.5s"), pd.Timedelta("3s")],
+ "int": [1.5, 3],
+ "datetime": [
+ Timestamp("2013-01-01 12:00:00"),
+ Timestamp("2013-01-03 00:00:00"),
+ ],
+ "datetimetz": [
+ Timestamp("2013-01-01 12:00:00", tz="US/Eastern"),
+ Timestamp("2013-01-03 00:00:00", tz="US/Eastern"),
+ ],
+ },
+ index=Index([1, 2], name="group"),
+ columns=[
+ "int",
+ "float",
+ "category_int",
+ ],
+ )
+
+ result = getattr(gb, method)(numeric_only=True)
+ tm.assert_frame_equal(result.reindex_like(expected), expected)
+
+ expected_columns = expected.columns
+
+ self._check(df, method, expected_columns, expected_columns_numeric)
+
+ @pytest.mark.parametrize("method", ["min", "max"])
+ def test_extrema(self, df, method):
+ # TODO: min, max *should* handle
+ # categorical (ordered) dtype
+
+ expected_columns = Index(
+ [
+ "int",
+ "float",
+ "string",
+ "category_int",
+ "datetime",
+ "datetimetz",
+ "timedelta",
+ ]
+ )
+ expected_columns_numeric = expected_columns
+
+ self._check(df, method, expected_columns, expected_columns_numeric)
+
+ @pytest.mark.parametrize("method", ["first", "last"])
+ def test_first_last(self, df, method):
+ expected_columns = Index(
+ [
+ "int",
+ "float",
+ "string",
+ "object",
+ "category_string",
+ "category_int",
+ "datetime",
+ "datetimetz",
+ "timedelta",
+ ]
+ )
+ expected_columns_numeric = expected_columns
+
+ self._check(df, method, expected_columns, expected_columns_numeric)
+
+ @pytest.mark.parametrize("method", ["sum", "cumsum"])
+ def test_sum_cumsum(self, df, method):
+ expected_columns_numeric = Index(["int", "float", "category_int"])
+ expected_columns = Index(
+ ["int", "float", "string", "category_int", "timedelta"]
+ )
+ if method == "cumsum":
+ # cumsum loses string
+ expected_columns = Index(["int", "float", "category_int", "timedelta"])
+
+ self._check(df, method, expected_columns, expected_columns_numeric)
+
+ @pytest.mark.parametrize("method", ["prod", "cumprod"])
+ def test_prod_cumprod(self, df, method):
+ expected_columns = Index(["int", "float", "category_int"])
+ expected_columns_numeric = expected_columns
+
+ self._check(df, method, expected_columns, expected_columns_numeric)
+
+ @pytest.mark.parametrize("method", ["cummin", "cummax"])
+ def test_cummin_cummax(self, df, method):
+ # like min, max, but don't include strings
+ expected_columns = Index(
+ ["int", "float", "category_int", "datetime", "datetimetz", "timedelta"]
+ )
+
+ # GH#15561: numeric_only=False set by default like min/max
+ expected_columns_numeric = expected_columns
+
+ self._check(df, method, expected_columns, expected_columns_numeric)
+
+ def _check(self, df, method, expected_columns, expected_columns_numeric):
+ gb = df.groupby("group")
+
+ # object dtypes for transformations are not implemented in Cython and
+ # have no Python fallback
+ exception = (
+ (NotImplementedError, TypeError) if method.startswith("cum") else TypeError
+ )
+
+ if method in ("min", "max", "cummin", "cummax", "cumsum", "cumprod"):
+ # The methods default to numeric_only=False and raise TypeError
+ msg = "|".join(
+ [
+ "Categorical is not ordered",
+ f"Cannot perform {method} with non-ordered Categorical",
+ re.escape(f"agg function failed [how->{method},dtype->object]"),
+ # cumsum/cummin/cummax/cumprod
+ "function is not implemented for this dtype",
+ f"dtype 'str' does not support operation '{method}'",
+ ]
+ )
+ with pytest.raises(exception, match=msg):
+ getattr(gb, method)()
+ elif method in ("sum", "mean", "median", "prod"):
+ msg = "|".join(
+ [
+ "category type does not support sum operations",
+ re.escape(f"agg function failed [how->{method},dtype->object]"),
+ re.escape(f"agg function failed [how->{method},dtype->string]"),
+ f"dtype 'str' does not support operation '{method}'",
+ ]
+ )
+ with pytest.raises(exception, match=msg):
+ getattr(gb, method)()
+ else:
+ result = getattr(gb, method)()
+ tm.assert_index_equal(result.columns, expected_columns_numeric)
+
+ if method not in ("first", "last"):
+ msg = "|".join(
+ [
+ "Categorical is not ordered",
+ "category type does not support",
+ "function is not implemented for this dtype",
+ f"Cannot perform {method} with non-ordered Categorical",
+ re.escape(f"agg function failed [how->{method},dtype->object]"),
+ re.escape(f"agg function failed [how->{method},dtype->string]"),
+ f"dtype 'str' does not support operation '{method}'",
+ ]
+ )
+ with pytest.raises(exception, match=msg):
+ getattr(gb, method)(numeric_only=False)
+ else:
+ result = getattr(gb, method)(numeric_only=False)
+ tm.assert_index_equal(result.columns, expected_columns)
+
+
+@pytest.mark.parametrize(
+ "kernel, has_arg",
+ [
+ ("all", False),
+ ("any", False),
+ ("bfill", False),
+ ("corr", True),
+ ("corrwith", True),
+ ("cov", True),
+ ("cummax", True),
+ ("cummin", True),
+ ("cumprod", True),
+ ("cumsum", True),
+ ("diff", False),
+ ("ffill", False),
+ ("first", True),
+ ("idxmax", True),
+ ("idxmin", True),
+ ("last", True),
+ ("max", True),
+ ("mean", True),
+ ("median", True),
+ ("min", True),
+ ("nth", False),
+ ("nunique", False),
+ ("pct_change", False),
+ ("prod", True),
+ ("quantile", True),
+ ("sem", True),
+ ("skew", True),
+ ("kurt", True),
+ ("std", True),
+ ("sum", True),
+ ("var", True),
+ ],
+)
+@pytest.mark.parametrize("numeric_only", [True, False, lib.no_default])
+@pytest.mark.parametrize("keys", [["a1"], ["a1", "a2"]])
+def test_numeric_only(kernel, has_arg, numeric_only, keys):
+ # GH#46072
+ # drops_nuisance: Whether the op drops nuisance columns even when numeric_only=False
+ # has_arg: Whether the op has a numeric_only arg
+ df = DataFrame({"a1": [1, 1], "a2": [2, 2], "a3": [5, 6], "b": 2 * [object]})
+
+ args = get_groupby_method_args(kernel, df)
+ kwargs = {} if numeric_only is lib.no_default else {"numeric_only": numeric_only}
+
+ gb = df.groupby(keys)
+ method = getattr(gb, kernel)
+ if has_arg and numeric_only is True:
+ # Cases where b does not appear in the result
+ if kernel == "corrwith":
+ warn = Pandas4Warning
+ msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ msg = ""
+ with tm.assert_produces_warning(warn, match=msg):
+ result = method(*args, **kwargs)
+ assert "b" not in result.columns
+ elif (
+ # kernels that work on any dtype and have numeric_only arg
+ kernel in ("first", "last")
+ or (
+ # kernels that work on any dtype and don't have numeric_only arg
+ kernel in ("any", "all", "bfill", "ffill", "nth", "nunique")
+ and numeric_only is lib.no_default
+ )
+ ):
+ result = method(*args, **kwargs)
+ assert "b" in result.columns
+ elif has_arg:
+ assert numeric_only is not True
+ # kernels that are successful on any dtype were above; this will fail
+
+ # object dtypes for transformations are not implemented in Cython and
+ # have no Python fallback
+ exception = NotImplementedError if kernel.startswith("cum") else TypeError
+
+ msg = "|".join(
+ [
+ "not allowed for this dtype",
+ "cannot be performed against 'object' dtypes",
+ "must be a string or a real number",
+ "unsupported operand type",
+ "function is not implemented for this dtype",
+ re.escape(f"agg function failed [how->{kernel},dtype->object]"),
+ ]
+ )
+ if kernel == "quantile":
+ msg = "dtype 'object' does not support operation 'quantile'"
+ elif kernel == "idxmin":
+ msg = "'<' not supported between instances of 'type' and 'type'"
+ elif kernel == "idxmax":
+ msg = "'>' not supported between instances of 'type' and 'type'"
+ with pytest.raises(exception, match=msg):
+ if kernel == "corrwith":
+ warn = Pandas4Warning
+ msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ msg = ""
+ with tm.assert_produces_warning(warn, match=msg):
+ method(*args, **kwargs)
+ elif not has_arg and numeric_only is not lib.no_default:
+ with pytest.raises(
+ TypeError, match="got an unexpected keyword argument 'numeric_only'"
+ ):
+ method(*args, **kwargs)
+ else:
+ assert kernel in ("diff", "pct_change")
+ assert numeric_only is lib.no_default
+ # Doesn't have numeric_only argument and fails on nuisance columns
+ with pytest.raises(TypeError, match=r"unsupported operand type"):
+ method(*args, **kwargs)
+
+
+@pytest.mark.parametrize("dtype", [bool, int, float, object])
+def test_deprecate_numeric_only_series(dtype, groupby_func, request):
+ # GH#46560
+ grouper = [0, 0, 1]
+
+ ser = Series([1, 0, 0], dtype=dtype)
+ gb = ser.groupby(grouper)
+
+ if groupby_func == "corrwith":
+ # corrwith is not implemented on SeriesGroupBy
+ assert not hasattr(gb, groupby_func)
+ return
+
+ method = getattr(gb, groupby_func)
+
+ expected_ser = Series([1, 0, 0])
+ expected_gb = expected_ser.groupby(grouper)
+ expected_method = getattr(expected_gb, groupby_func)
+
+ args = get_groupby_method_args(groupby_func, ser)
+
+ fails_on_numeric_object = (
+ "corr",
+ "cov",
+ "cummax",
+ "cummin",
+ "cumprod",
+ "cumsum",
+ "quantile",
+ )
+ # ops that give an object result on object input
+ obj_result = (
+ "first",
+ "last",
+ "nth",
+ "bfill",
+ "ffill",
+ "shift",
+ "sum",
+ "diff",
+ "pct_change",
+ "var",
+ "mean",
+ "median",
+ "min",
+ "max",
+ "prod",
+ "skew",
+ "kurt",
+ )
+
+ # Test default behavior; kernels that fail may be enabled in the future but kernels
+ # that succeed should not be allowed to fail (without deprecation, at least)
+ if groupby_func in fails_on_numeric_object and dtype is object:
+ if groupby_func == "quantile":
+ msg = "dtype 'object' does not support operation 'quantile'"
+ else:
+ msg = "is not supported for object dtype"
+ with pytest.raises(TypeError, match=msg):
+ method(*args)
+ elif dtype is object:
+ result = method(*args)
+ expected = expected_method(*args)
+ if groupby_func in obj_result:
+ expected = expected.astype(object)
+ tm.assert_series_equal(result, expected)
+
+ has_numeric_only = (
+ "first",
+ "last",
+ "max",
+ "mean",
+ "median",
+ "min",
+ "prod",
+ "quantile",
+ "sem",
+ "skew",
+ "kurt",
+ "std",
+ "sum",
+ "var",
+ "cummax",
+ "cummin",
+ "cumprod",
+ "cumsum",
+ )
+ if groupby_func not in has_numeric_only:
+ msg = "got an unexpected keyword argument 'numeric_only'"
+ with pytest.raises(TypeError, match=msg):
+ method(*args, numeric_only=True)
+ elif dtype is object:
+ msg = "|".join(
+ [
+ "SeriesGroupBy.sem called with numeric_only=True and dtype object",
+ "Series.skew does not allow numeric_only=True with non-numeric",
+ "cum(sum|prod|min|max) is not supported for object dtype",
+ r"Cannot use numeric_only=True with SeriesGroupBy\..* and non-numeric",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ method(*args, numeric_only=True)
+ elif dtype == bool and groupby_func == "quantile":
+ msg = "Cannot use quantile with bool dtype"
+ with pytest.raises(TypeError, match=msg):
+ # GH#51424
+ method(*args, numeric_only=False)
+ else:
+ result = method(*args, numeric_only=True)
+ expected = method(*args, numeric_only=False)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_pipe.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_pipe.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee59a93695bcf84bcfcd8f1add8120e2c04004f5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_pipe.py
@@ -0,0 +1,80 @@
+import numpy as np
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+
+
+def test_pipe():
+ # Test the pipe method of DataFrameGroupBy.
+ # Issue #17871
+
+ random_state = np.random.default_rng(2)
+
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": random_state.standard_normal(8),
+ "C": random_state.standard_normal(8),
+ }
+ )
+
+ def f(dfgb):
+ return dfgb.B.max() - dfgb.C.min().min()
+
+ def square(srs):
+ return srs**2
+
+ # Note that the transformations are
+ # GroupBy -> Series
+ # Series -> Series
+ # This then chains the GroupBy.pipe and the
+ # NDFrame.pipe methods
+ result = df.groupby("A").pipe(f).pipe(square)
+
+ index = Index(["bar", "foo"], name="A")
+ expected = pd.Series([3.749306591013693, 6.717707873081384], name="B", index=index)
+
+ tm.assert_series_equal(expected, result)
+
+
+def test_pipe_args():
+ # Test passing args to the pipe method of DataFrameGroupBy.
+ # Issue #17871
+
+ df = DataFrame(
+ {
+ "group": ["A", "A", "B", "B", "C"],
+ "x": [1.0, 2.0, 3.0, 2.0, 5.0],
+ "y": [10.0, 100.0, 1000.0, -100.0, -1000.0],
+ }
+ )
+
+ def f(dfgb, arg1):
+ filtered = dfgb.filter(lambda grp: grp.y.mean() > arg1, dropna=False)
+ return filtered.groupby("group")
+
+ def g(dfgb, arg2):
+ return dfgb.sum() / dfgb.sum().sum() + arg2
+
+ def h(df, arg3):
+ return df.x + df.y - arg3
+
+ result = df.groupby("group").pipe(f, 0).pipe(g, 10).pipe(h, 100)
+
+ # Assert the results here
+ index = Index(["A", "B"], name="group")
+ expected = pd.Series([-79.5160891089, -78.4839108911], index=index)
+
+ tm.assert_series_equal(result, expected)
+
+ # test SeriesGroupby.pipe
+ ser = pd.Series([1, 1, 2, 2, 3, 3])
+ result = ser.groupby(ser).pipe(lambda grp: grp.sum() * grp.count())
+
+ expected = pd.Series([4, 8, 12], index=Index([1, 2, 3], dtype=np.int64))
+
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_raises.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_raises.py
new file mode 100644
index 0000000000000000000000000000000000000000..652c3808f5f1d13318a577533365ee552151f32d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_raises.py
@@ -0,0 +1,741 @@
+# Only tests that raise an error and have no better location should go here.
+# Tests for specific groupby methods should go in their respective
+# test file.
+
+import datetime
+import re
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ Categorical,
+ DataFrame,
+ Grouper,
+ Series,
+)
+import pandas._testing as tm
+from pandas.tests.groupby import get_groupby_method_args
+
+
+@pytest.fixture(
+ params=[
+ "a",
+ ["a"],
+ ["a", "b"],
+ Grouper(key="a"),
+ lambda x: x % 2,
+ [0, 0, 0, 1, 2, 2, 2, 3, 3],
+ np.array([0, 0, 0, 1, 2, 2, 2, 3, 3]),
+ dict(zip(range(9), [0, 0, 0, 1, 2, 2, 2, 3, 3], strict=True)),
+ Series([1, 1, 1, 1, 1, 2, 2, 2, 2]),
+ [Series([1, 1, 1, 1, 1, 2, 2, 2, 2]), Series([3, 3, 4, 4, 4, 4, 4, 3, 3])],
+ ]
+)
+def by(request):
+ return request.param
+
+
+@pytest.fixture(params=[True, False])
+def groupby_series(request):
+ return request.param
+
+
+@pytest.fixture
+def df_with_string_col():
+ df = DataFrame(
+ {
+ "a": [1, 1, 1, 1, 1, 2, 2, 2, 2],
+ "b": [3, 3, 4, 4, 4, 4, 4, 3, 3],
+ "c": range(9),
+ "d": list("xyzwtyuio"),
+ }
+ )
+ return df
+
+
+@pytest.fixture
+def df_with_datetime_col():
+ df = DataFrame(
+ {
+ "a": [1, 1, 1, 1, 1, 2, 2, 2, 2],
+ "b": [3, 3, 4, 4, 4, 4, 4, 3, 3],
+ "c": range(9),
+ "d": datetime.datetime(2005, 1, 1, 10, 30, 23, 540000),
+ }
+ )
+ return df
+
+
+@pytest.fixture
+def df_with_cat_col():
+ df = DataFrame(
+ {
+ "a": [1, 1, 1, 1, 1, 2, 2, 2, 2],
+ "b": [3, 3, 4, 4, 4, 4, 4, 3, 3],
+ "c": range(9),
+ "d": Categorical(
+ ["a", "a", "a", "a", "b", "b", "b", "b", "c"],
+ categories=["a", "b", "c", "d"],
+ ordered=True,
+ ),
+ }
+ )
+ return df
+
+
+def _call_and_check(
+ klass, msg, how, gb, groupby_func, args, warn_category=None, warn_msg=""
+):
+ with tm.assert_produces_warning(
+ warn_category, match=warn_msg, check_stacklevel=False
+ ):
+ if klass is None:
+ if how == "method":
+ getattr(gb, groupby_func)(*args)
+ elif how == "agg":
+ gb.agg(groupby_func, *args)
+ else:
+ gb.transform(groupby_func, *args)
+ else:
+ with pytest.raises(klass, match=msg):
+ if how == "method":
+ getattr(gb, groupby_func)(*args)
+ elif how == "agg":
+ gb.agg(groupby_func, *args)
+ else:
+ gb.transform(groupby_func, *args)
+
+
+@pytest.mark.parametrize("how", ["method", "agg", "transform"])
+def test_groupby_raises_string(
+ how, by, groupby_series, groupby_func, df_with_string_col, using_infer_string
+):
+ df = df_with_string_col
+ args = get_groupby_method_args(groupby_func, df)
+ gb = df.groupby(by=by)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ if groupby_func == "corrwith":
+ assert not hasattr(gb, "corrwith")
+ return
+
+ klass, msg = {
+ "all": (None, ""),
+ "any": (None, ""),
+ "bfill": (None, ""),
+ "corrwith": (TypeError, "Could not convert"),
+ "count": (None, ""),
+ "cumcount": (None, ""),
+ "cummax": (
+ (NotImplementedError, TypeError),
+ "(function|cummax) is not (implemented|supported) for (this|object) dtype",
+ ),
+ "cummin": (
+ (NotImplementedError, TypeError),
+ "(function|cummin) is not (implemented|supported) for (this|object) dtype",
+ ),
+ "cumprod": (
+ (NotImplementedError, TypeError),
+ "(function|cumprod) is not (implemented|supported) for (this|object) dtype",
+ ),
+ "cumsum": (
+ (NotImplementedError, TypeError),
+ "(function|cumsum) is not (implemented|supported) for (this|object) dtype",
+ ),
+ "diff": (TypeError, "unsupported operand type"),
+ "ffill": (None, ""),
+ "first": (None, ""),
+ "idxmax": (None, ""),
+ "idxmin": (None, ""),
+ "last": (None, ""),
+ "max": (None, ""),
+ "mean": (
+ TypeError,
+ re.escape("agg function failed [how->mean,dtype->object]"),
+ ),
+ "median": (
+ TypeError,
+ re.escape("agg function failed [how->median,dtype->object]"),
+ ),
+ "min": (None, ""),
+ "ngroup": (None, ""),
+ "nunique": (None, ""),
+ "pct_change": (TypeError, "unsupported operand type"),
+ "prod": (
+ TypeError,
+ re.escape("agg function failed [how->prod,dtype->object]"),
+ ),
+ "quantile": (TypeError, "dtype 'object' does not support operation 'quantile'"),
+ "rank": (None, ""),
+ "sem": (ValueError, "could not convert string to float"),
+ "shift": (None, ""),
+ "size": (None, ""),
+ "skew": (ValueError, "could not convert string to float"),
+ "kurt": (ValueError, "could not convert string to float"),
+ "std": (ValueError, "could not convert string to float"),
+ "sum": (None, ""),
+ "var": (
+ TypeError,
+ re.escape("agg function failed [how->var,dtype->"),
+ ),
+ }[groupby_func]
+
+ if using_infer_string:
+ if groupby_func in [
+ "prod",
+ "mean",
+ "median",
+ "cumsum",
+ "cumprod",
+ "std",
+ "sem",
+ "var",
+ "skew",
+ "kurt",
+ "quantile",
+ ]:
+ msg = f"dtype 'str' does not support operation '{groupby_func}'"
+ if groupby_func in ["sem", "std", "skew", "kurt"]:
+ # The object-dtype raises ValueError when trying to convert to numeric.
+ klass = TypeError
+ elif groupby_func == "pct_change" and df["d"].dtype.storage == "pyarrow":
+ # This doesn't go through EA._groupby_op so the message isn't controlled
+ # there.
+ msg = "operation 'truediv' not supported for dtype 'str' with dtype 'str'"
+ elif groupby_func == "diff" and df["d"].dtype.storage == "pyarrow":
+ # This doesn't go through EA._groupby_op so the message isn't controlled
+ # there.
+ msg = "operation 'sub' not supported for dtype 'str' with dtype 'str'"
+
+ elif groupby_func in ["cummin", "cummax"]:
+ msg = msg.replace("object", "str")
+ elif groupby_func == "corrwith":
+ msg = "Cannot perform reduction 'mean' with string dtype"
+
+ if groupby_func == "corrwith":
+ warn_category = Pandas4Warning
+ warn_msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn_category = None
+ warn_msg = ""
+ _call_and_check(klass, msg, how, gb, groupby_func, args, warn_category, warn_msg)
+
+
+@pytest.mark.parametrize("how", ["agg", "transform"])
+def test_groupby_raises_string_udf(how, by, groupby_series, df_with_string_col):
+ df = df_with_string_col
+ gb = df.groupby(by=by)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ def func(x):
+ raise TypeError("Test error message")
+
+ with pytest.raises(TypeError, match="Test error message"):
+ getattr(gb, how)(func)
+
+
+@pytest.mark.parametrize("how", ["agg", "transform"])
+@pytest.mark.parametrize("groupby_func_np", [np.sum, np.mean])
+def test_groupby_raises_string_np(
+ how,
+ by,
+ groupby_series,
+ groupby_func_np,
+ df_with_string_col,
+ using_infer_string,
+):
+ # GH#50749
+ df = df_with_string_col
+ gb = df.groupby(by=by)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ klass, msg = {
+ np.sum: (None, ""),
+ np.mean: (
+ TypeError,
+ "Could not convert string .* to numeric|"
+ "Cannot perform reduction 'mean' with string dtype",
+ ),
+ }[groupby_func_np]
+
+ if using_infer_string:
+ if groupby_func_np is np.mean:
+ klass = TypeError
+ msg = f"Cannot perform reduction '{groupby_func_np.__name__}' with string dtype"
+
+ _call_and_check(klass, msg, how, gb, groupby_func_np, ())
+
+
+@pytest.mark.parametrize("how", ["method", "agg", "transform"])
+def test_groupby_raises_datetime(
+ how, by, groupby_series, groupby_func, df_with_datetime_col
+):
+ df = df_with_datetime_col
+ args = get_groupby_method_args(groupby_func, df)
+ gb = df.groupby(by=by)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ if groupby_func == "corrwith":
+ assert not hasattr(gb, "corrwith")
+ return
+
+ klass, msg = {
+ "all": (TypeError, "'all' with datetime64 dtypes is no longer supported"),
+ "any": (TypeError, "'any' with datetime64 dtypes is no longer supported"),
+ "bfill": (None, ""),
+ "corrwith": (TypeError, "cannot perform __mul__ with this index type"),
+ "count": (None, ""),
+ "cumcount": (None, ""),
+ "cummax": (None, ""),
+ "cummin": (None, ""),
+ "cumprod": (TypeError, "datetime64 type does not support operation 'cumprod'"),
+ "cumsum": (TypeError, "datetime64 type does not support operation 'cumsum'"),
+ "diff": (None, ""),
+ "ffill": (None, ""),
+ "first": (None, ""),
+ "idxmax": (None, ""),
+ "idxmin": (None, ""),
+ "last": (None, ""),
+ "max": (None, ""),
+ "mean": (None, ""),
+ "median": (None, ""),
+ "min": (None, ""),
+ "ngroup": (None, ""),
+ "nunique": (None, ""),
+ "pct_change": (TypeError, "cannot perform __truediv__ with this index type"),
+ "prod": (TypeError, "datetime64 type does not support operation 'prod'"),
+ "quantile": (None, ""),
+ "rank": (None, ""),
+ "sem": (None, ""),
+ "shift": (None, ""),
+ "size": (None, ""),
+ "skew": (
+ TypeError,
+ "|".join(
+ [
+ r"dtype datetime64\[ns\] does not support operation",
+ "datetime64 type does not support operation 'skew'",
+ ]
+ ),
+ ),
+ "kurt": (
+ TypeError,
+ "|".join(
+ [
+ r"dtype datetime64\[ns\] does not support operation",
+ "datetime64 type does not support operation 'kurt'",
+ ]
+ ),
+ ),
+ "std": (None, ""),
+ "sum": (TypeError, "datetime64 type does not support operation 'sum"),
+ "var": (TypeError, "datetime64 type does not support operation 'var'"),
+ }[groupby_func]
+
+ if groupby_func == "corrwith":
+ warn_category = Pandas4Warning
+ warn_msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn_category = None
+ warn_msg = ""
+ _call_and_check(klass, msg, how, gb, groupby_func, args, warn_category, warn_msg)
+
+
+@pytest.mark.parametrize("how", ["agg", "transform"])
+def test_groupby_raises_datetime_udf(how, by, groupby_series, df_with_datetime_col):
+ df = df_with_datetime_col
+ gb = df.groupby(by=by)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ def func(x):
+ raise TypeError("Test error message")
+
+ with pytest.raises(TypeError, match="Test error message"):
+ getattr(gb, how)(func)
+
+
+@pytest.mark.parametrize("how", ["agg", "transform"])
+@pytest.mark.parametrize("groupby_func_np", [np.sum, np.mean])
+def test_groupby_raises_datetime_np(
+ how, by, groupby_series, groupby_func_np, df_with_datetime_col
+):
+ # GH#50749
+ df = df_with_datetime_col
+ gb = df.groupby(by=by)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ klass, msg = {
+ np.sum: (
+ TypeError,
+ re.escape("datetime64[us] does not support operation 'sum'"),
+ ),
+ np.mean: (None, ""),
+ }[groupby_func_np]
+ _call_and_check(klass, msg, how, gb, groupby_func_np, ())
+
+
+@pytest.mark.parametrize("func", ["prod", "cumprod", "skew", "kurt", "var"])
+def test_groupby_raises_timedelta(func):
+ df = DataFrame(
+ {
+ "a": [1, 1, 1, 1, 1, 2, 2, 2, 2],
+ "b": [3, 3, 4, 4, 4, 4, 4, 3, 3],
+ "c": range(9),
+ "d": datetime.timedelta(days=1),
+ }
+ )
+ gb = df.groupby(by="a")
+
+ _call_and_check(
+ TypeError,
+ "timedelta64 type does not support .* operations",
+ "method",
+ gb,
+ func,
+ [],
+ )
+
+
+@pytest.mark.parametrize("how", ["method", "agg", "transform"])
+def test_groupby_raises_category(
+ how, by, groupby_series, groupby_func, df_with_cat_col
+):
+ # GH#50749
+ df = df_with_cat_col
+ args = get_groupby_method_args(groupby_func, df)
+ gb = df.groupby(by=by)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ if groupby_func == "corrwith":
+ assert not hasattr(gb, "corrwith")
+ return
+
+ klass, msg = {
+ "all": (None, ""),
+ "any": (None, ""),
+ "bfill": (None, ""),
+ "corrwith": (
+ TypeError,
+ r"unsupported operand type\(s\) for \*: 'Categorical' and 'int'",
+ ),
+ "count": (None, ""),
+ "cumcount": (None, ""),
+ "cummax": (
+ (NotImplementedError, TypeError),
+ "(category type does not support cummax operations|"
+ "category dtype not supported|"
+ "cummax is not supported for category dtype)",
+ ),
+ "cummin": (
+ (NotImplementedError, TypeError),
+ "(category type does not support cummin operations|"
+ "category dtype not supported|"
+ "cummin is not supported for category dtype)",
+ ),
+ "cumprod": (
+ (NotImplementedError, TypeError),
+ "(category type does not support cumprod operations|"
+ "category dtype not supported|"
+ "cumprod is not supported for category dtype)",
+ ),
+ "cumsum": (
+ (NotImplementedError, TypeError),
+ "(category type does not support cumsum operations|"
+ "category dtype not supported|"
+ "cumsum is not supported for category dtype)",
+ ),
+ "diff": (
+ TypeError,
+ r"unsupported operand type\(s\) for -: 'Categorical' and 'Categorical'",
+ ),
+ "ffill": (None, ""),
+ "first": (None, ""),
+ "idxmax": (None, ""),
+ "idxmin": (None, ""),
+ "last": (None, ""),
+ "max": (None, ""),
+ "mean": (
+ TypeError,
+ "|".join(
+ [
+ "'Categorical' .* does not support operation 'mean'",
+ "category dtype does not support aggregation 'mean'",
+ ]
+ ),
+ ),
+ "median": (
+ TypeError,
+ "|".join(
+ [
+ "'Categorical' .* does not support operation 'median'",
+ "category dtype does not support aggregation 'median'",
+ ]
+ ),
+ ),
+ "min": (None, ""),
+ "ngroup": (None, ""),
+ "nunique": (None, ""),
+ "pct_change": (
+ TypeError,
+ r"unsupported operand type\(s\) for /: 'Categorical' and 'Categorical'",
+ ),
+ "prod": (TypeError, "category type does not support prod operations"),
+ "quantile": (TypeError, "No matching signature found"),
+ "rank": (None, ""),
+ "sem": (
+ TypeError,
+ "|".join(
+ [
+ "'Categorical' .* does not support operation 'sem'",
+ "category dtype does not support aggregation 'sem'",
+ ]
+ ),
+ ),
+ "shift": (None, ""),
+ "size": (None, ""),
+ "skew": (
+ TypeError,
+ "|".join(
+ [
+ "dtype category does not support operation 'skew'",
+ "category type does not support skew operations",
+ ]
+ ),
+ ),
+ "kurt": (
+ TypeError,
+ "|".join(
+ [
+ "dtype category does not support operation 'kurt'",
+ "category type does not support kurt operations",
+ ]
+ ),
+ ),
+ "std": (
+ TypeError,
+ "|".join(
+ [
+ "'Categorical' .* does not support operation 'std'",
+ "category dtype does not support aggregation 'std'",
+ ]
+ ),
+ ),
+ "sum": (TypeError, "category type does not support sum operations"),
+ "var": (
+ TypeError,
+ "|".join(
+ [
+ "'Categorical' .* does not support operation 'var'",
+ "category dtype does not support aggregation 'var'",
+ ]
+ ),
+ ),
+ }[groupby_func]
+
+ if groupby_func == "corrwith":
+ warn_category = Pandas4Warning
+ warn_msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn_category = None
+ warn_msg = ""
+ _call_and_check(klass, msg, how, gb, groupby_func, args, warn_category, warn_msg)
+
+
+@pytest.mark.parametrize("how", ["agg", "transform"])
+def test_groupby_raises_category_udf(how, by, groupby_series, df_with_cat_col):
+ # GH#50749
+ df = df_with_cat_col
+ gb = df.groupby(by=by)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ def func(x):
+ raise TypeError("Test error message")
+
+ with pytest.raises(TypeError, match="Test error message"):
+ getattr(gb, how)(func)
+
+
+@pytest.mark.parametrize("how", ["agg", "transform"])
+@pytest.mark.parametrize("groupby_func_np", [np.sum, np.mean])
+def test_groupby_raises_category_np(
+ how, by, groupby_series, groupby_func_np, df_with_cat_col
+):
+ # GH#50749
+ df = df_with_cat_col
+ gb = df.groupby(by=by)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ klass, msg = {
+ np.sum: (TypeError, "dtype category does not support operation 'sum'"),
+ np.mean: (
+ TypeError,
+ "dtype category does not support operation 'mean'",
+ ),
+ }[groupby_func_np]
+ _call_and_check(klass, msg, how, gb, groupby_func_np, ())
+
+
+@pytest.mark.filterwarnings("ignore:In a future version, the keys")
+@pytest.mark.parametrize("how", ["method", "agg", "transform"])
+def test_groupby_raises_category_on_category(
+ how,
+ by,
+ groupby_series,
+ groupby_func,
+ observed,
+ df_with_cat_col,
+):
+ # GH#50749
+ df = df_with_cat_col
+ df["a"] = Categorical(
+ ["a", "a", "a", "a", "b", "b", "b", "b", "c"],
+ categories=["a", "b", "c", "d"],
+ ordered=True,
+ )
+ args = get_groupby_method_args(groupby_func, df)
+ gb = df.groupby(by=by, observed=observed)
+
+ if groupby_series:
+ gb = gb["d"]
+
+ if groupby_func == "corrwith":
+ assert not hasattr(gb, "corrwith")
+ return
+
+ empty_groups = not observed and any(group.empty for group in gb.groups.values())
+ if how == "transform":
+ # empty groups will be ignored
+ empty_groups = False
+
+ klass, msg = {
+ "all": (None, ""),
+ "any": (None, ""),
+ "bfill": (None, ""),
+ "corrwith": (
+ TypeError,
+ r"unsupported operand type\(s\) for \*: 'Categorical' and 'int'",
+ ),
+ "count": (None, ""),
+ "cumcount": (None, ""),
+ "cummax": (
+ (NotImplementedError, TypeError),
+ "(cummax is not supported for category dtype|"
+ "category dtype not supported|"
+ "category type does not support cummax operations)",
+ ),
+ "cummin": (
+ (NotImplementedError, TypeError),
+ "(cummin is not supported for category dtype|"
+ "category dtype not supported|"
+ "category type does not support cummin operations)",
+ ),
+ "cumprod": (
+ (NotImplementedError, TypeError),
+ "(cumprod is not supported for category dtype|"
+ "category dtype not supported|"
+ "category type does not support cumprod operations)",
+ ),
+ "cumsum": (
+ (NotImplementedError, TypeError),
+ "(cumsum is not supported for category dtype|"
+ "category dtype not supported|"
+ "category type does not support cumsum operations)",
+ ),
+ "diff": (TypeError, "unsupported operand type"),
+ "ffill": (None, ""),
+ "first": (None, ""),
+ "idxmax": (ValueError, "empty group due to unobserved categories")
+ if empty_groups
+ else (None, ""),
+ "idxmin": (ValueError, "empty group due to unobserved categories")
+ if empty_groups
+ else (None, ""),
+ "last": (None, ""),
+ "max": (None, ""),
+ "mean": (TypeError, "category dtype does not support aggregation 'mean'"),
+ "median": (TypeError, "category dtype does not support aggregation 'median'"),
+ "min": (None, ""),
+ "ngroup": (None, ""),
+ "nunique": (None, ""),
+ "pct_change": (TypeError, "unsupported operand type"),
+ "prod": (TypeError, "category type does not support prod operations"),
+ "quantile": (TypeError, "No matching signature found"),
+ "rank": (None, ""),
+ "sem": (
+ TypeError,
+ "|".join(
+ [
+ "'Categorical' .* does not support operation 'sem'",
+ "category dtype does not support aggregation 'sem'",
+ ]
+ ),
+ ),
+ "shift": (None, ""),
+ "size": (None, ""),
+ "skew": (
+ TypeError,
+ "|".join(
+ [
+ "category type does not support skew operations",
+ "dtype category does not support operation 'skew'",
+ ]
+ ),
+ ),
+ "kurt": (
+ TypeError,
+ "|".join(
+ [
+ "category type does not support kurt operations",
+ "dtype category does not support operation 'kurt'",
+ ]
+ ),
+ ),
+ "std": (
+ TypeError,
+ "|".join(
+ [
+ "'Categorical' .* does not support operation 'std'",
+ "category dtype does not support aggregation 'std'",
+ ]
+ ),
+ ),
+ "sum": (TypeError, "category type does not support sum operations"),
+ "var": (
+ TypeError,
+ "|".join(
+ [
+ "'Categorical' .* does not support operation 'var'",
+ "category dtype does not support aggregation 'var'",
+ ]
+ ),
+ ),
+ }[groupby_func]
+
+ if groupby_func == "corrwith":
+ warn_category = Pandas4Warning
+ warn_msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn_category = None
+ warn_msg = ""
+ _call_and_check(klass, msg, how, gb, groupby_func, args, warn_category, warn_msg)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_reductions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_reductions.py
new file mode 100644
index 0000000000000000000000000000000000000000..890ce4e398f0104209799201a6c2a5ea4973edcb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_reductions.py
@@ -0,0 +1,1538 @@
+import builtins
+import datetime as dt
+from string import ascii_lowercase
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import iNaT
+
+from pandas.core.dtypes.common import pandas_dtype
+from pandas.core.dtypes.missing import na_value_for_dtype
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Series,
+ Timestamp,
+ date_range,
+ isna,
+)
+import pandas._testing as tm
+from pandas.tests.groupby import get_groupby_method_args
+from pandas.util import _test_decorators as td
+
+
+@pytest.mark.parametrize("dtype", ["int64", "int32", "float64", "float32"])
+def test_basic_aggregations(dtype):
+ data = Series(np.arange(9) // 3, index=np.arange(9), dtype=dtype)
+
+ index = np.arange(9)
+ np.random.default_rng(2).shuffle(index)
+ data = data.reindex(index)
+
+ grouped = data.groupby(lambda x: x // 3, group_keys=False)
+
+ for k, v in grouped:
+ assert len(v) == 3
+
+ agged = grouped.aggregate(np.mean)
+ assert agged[1] == 1
+
+ expected = grouped.agg(np.mean)
+ tm.assert_series_equal(agged, expected) # shorthand
+ tm.assert_series_equal(agged, grouped.mean())
+ result = grouped.sum()
+ expected = grouped.agg(np.sum)
+ if dtype == "int32":
+ # NumPy's sum returns int64
+ expected = expected.astype("int32")
+ tm.assert_series_equal(result, expected)
+
+ expected = grouped.apply(lambda x: x * x.sum())
+ transformed = grouped.transform(lambda x: x * x.sum())
+ assert transformed[7] == 12
+ tm.assert_series_equal(transformed, expected)
+
+ value_grouped = data.groupby(data)
+ result = value_grouped.aggregate(np.mean)
+ tm.assert_series_equal(result, agged, check_index_type=False)
+
+ # complex agg
+ agged = grouped.aggregate([np.mean, np.std])
+
+ msg = r"nested renamer is not supported"
+ with pytest.raises(pd.errors.SpecificationError, match=msg):
+ grouped.aggregate({"one": np.mean, "two": np.std})
+
+ # corner cases
+ msg = "Must produce aggregated value"
+ # exception raised is type Exception
+ with pytest.raises(Exception, match=msg):
+ grouped.aggregate(lambda x: x * 2)
+
+
+@pytest.mark.parametrize(
+ "vals",
+ [
+ ["foo", "bar", "baz"],
+ ["foo", "", ""],
+ ["", "", ""],
+ [1, 2, 3],
+ [1, 0, 0],
+ [0, 0, 0],
+ [1.0, 2.0, 3.0],
+ [1.0, 0.0, 0.0],
+ [0.0, 0.0, 0.0],
+ [True, True, True],
+ [True, False, False],
+ [False, False, False],
+ [np.nan, np.nan, np.nan],
+ ],
+)
+def test_groupby_bool_aggs(skipna, all_boolean_reductions, vals):
+ df = DataFrame({"key": ["a"] * 3 + ["b"] * 3, "val": vals * 2})
+
+ # Figure out expectation using Python builtin
+ exp = getattr(builtins, all_boolean_reductions)(vals)
+
+ # edge case for missing data with skipna and 'any'
+ if skipna and all(isna(vals)) and all_boolean_reductions == "any":
+ exp = False
+
+ expected = DataFrame(
+ [exp] * 2, columns=["val"], index=pd.Index(["a", "b"], name="key")
+ )
+ result = getattr(df.groupby("key"), all_boolean_reductions)(skipna=skipna)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_any():
+ df = DataFrame(
+ [[1, 2, "foo"], [1, np.nan, "bar"], [3, np.nan, "baz"]],
+ columns=["A", "B", "C"],
+ )
+ expected = DataFrame(
+ [[True, True], [False, True]], columns=["B", "C"], index=[1, 3]
+ )
+ expected.index.name = "A"
+ result = df.groupby("A").any()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_bool_aggs_dup_column_labels(all_boolean_reductions):
+ # GH#21668
+ df = DataFrame([[True, True]], columns=["a", "a"])
+ grp_by = df.groupby([0])
+ result = getattr(grp_by, all_boolean_reductions)()
+
+ expected = df.set_axis(np.array([0]))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ [False, False, False],
+ [True, True, True],
+ [pd.NA, pd.NA, pd.NA],
+ [False, pd.NA, False],
+ [True, pd.NA, True],
+ [True, pd.NA, False],
+ ],
+)
+def test_masked_kleene_logic(all_boolean_reductions, skipna, data):
+ # GH#37506
+ ser = Series(data, dtype="boolean")
+
+ # The result should match aggregating on the whole series. Correctness
+ # there is verified in test_reductions.py::test_any_all_boolean_kleene_logic
+ expected_data = getattr(ser, all_boolean_reductions)(skipna=skipna)
+ expected = Series(expected_data, index=np.array([0]), dtype="boolean")
+
+ result = ser.groupby([0, 0, 0]).agg(all_boolean_reductions, skipna=skipna)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype1,dtype2,exp_col1,exp_col2",
+ [
+ (
+ "float",
+ "Float64",
+ np.array([True], dtype=bool),
+ pd.array([pd.NA], dtype="boolean"),
+ ),
+ (
+ "Int64",
+ "float",
+ pd.array([pd.NA], dtype="boolean"),
+ np.array([True], dtype=bool),
+ ),
+ (
+ "Int64",
+ "Int64",
+ pd.array([pd.NA], dtype="boolean"),
+ pd.array([pd.NA], dtype="boolean"),
+ ),
+ (
+ "Float64",
+ "boolean",
+ pd.array([pd.NA], dtype="boolean"),
+ pd.array([pd.NA], dtype="boolean"),
+ ),
+ ],
+)
+def test_masked_mixed_types(dtype1, dtype2, exp_col1, exp_col2):
+ # GH#37506
+ data1 = [1.0, np.nan] if dtype1.startswith("f") else [1.0, pd.NA]
+ data2 = [1.0, np.nan] if dtype2.startswith("f") else [1.0, pd.NA]
+ df = DataFrame(
+ {"col1": pd.array(data1, dtype=dtype1), "col2": pd.array(data2, dtype=dtype2)}
+ )
+ result = df.groupby([1, 1]).agg("all", skipna=False)
+
+ expected = DataFrame({"col1": exp_col1, "col2": exp_col2}, index=np.array([1]))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["Int64", "Float64", "boolean"])
+def test_masked_bool_aggs_skipna(
+ all_boolean_reductions, dtype, skipna, frame_or_series
+):
+ # GH#40585
+ obj = frame_or_series([pd.NA, 1], dtype=dtype)
+ expected_res = True
+ if not skipna and all_boolean_reductions == "all":
+ expected_res = pd.NA
+ expected = frame_or_series([expected_res], index=np.array([1]), dtype="boolean")
+
+ result = obj.groupby([1, 1]).agg(all_boolean_reductions, skipna=skipna)
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "bool_agg_func,data,expected_res",
+ [
+ ("any", [pd.NA, np.nan], False),
+ ("any", [pd.NA, 1, np.nan], True),
+ ("all", [pd.NA, pd.NaT], True),
+ ("all", [pd.NA, False, pd.NaT], False),
+ ],
+)
+def test_object_type_missing_vals(bool_agg_func, data, expected_res, frame_or_series):
+ # GH#37501
+ obj = frame_or_series(data, dtype=object)
+ result = obj.groupby([1] * len(data)).agg(bool_agg_func)
+ expected = frame_or_series([expected_res], index=np.array([1]), dtype="bool")
+ tm.assert_equal(result, expected)
+
+
+def test_object_NA_raises_with_skipna_false(all_boolean_reductions):
+ # GH#37501
+ ser = Series([pd.NA], dtype=object)
+ with pytest.raises(TypeError, match="boolean value of NA is ambiguous"):
+ ser.groupby([1]).agg(all_boolean_reductions, skipna=False)
+
+
+def test_empty(frame_or_series, all_boolean_reductions):
+ # GH 45231
+ kwargs = {"columns": ["a"]} if frame_or_series is DataFrame else {"name": "a"}
+ obj = frame_or_series(**kwargs, dtype=object)
+ result = getattr(obj.groupby(obj.index), all_boolean_reductions)()
+ expected = frame_or_series(**kwargs, dtype=bool)
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize("how", ["idxmin", "idxmax"])
+def test_idxmin_idxmax_extremes(how, any_real_numpy_dtype):
+ # GH#57040
+ if any_real_numpy_dtype is int or any_real_numpy_dtype is float:
+ # No need to test
+ return
+ info = np.iinfo if "int" in any_real_numpy_dtype else np.finfo
+ min_value = info(any_real_numpy_dtype).min
+ max_value = info(any_real_numpy_dtype).max
+ df = DataFrame(
+ {"a": [2, 1, 1, 2], "b": [min_value, max_value, max_value, min_value]},
+ dtype=any_real_numpy_dtype,
+ )
+ gb = df.groupby("a")
+ result = getattr(gb, how)()
+ expected = DataFrame(
+ {"b": [1, 0]}, index=pd.Index([1, 2], name="a", dtype=any_real_numpy_dtype)
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("how", ["idxmin", "idxmax"])
+def test_idxmin_idxmax_extremes_skipna(skipna, how, float_numpy_dtype):
+ # GH#57040
+ min_value = np.finfo(float_numpy_dtype).min
+ max_value = np.finfo(float_numpy_dtype).max
+ df = DataFrame(
+ {
+ "a": Series(np.repeat(range(1, 5), repeats=2), dtype="intp"),
+ "b": Series(
+ [
+ np.nan,
+ min_value,
+ np.nan,
+ max_value,
+ min_value,
+ np.nan,
+ max_value,
+ np.nan,
+ ],
+ dtype=float_numpy_dtype,
+ ),
+ },
+ )
+ gb = df.groupby("a")
+
+ if not skipna:
+ msg = f"{how} with skipna=False"
+ with pytest.raises(ValueError, match=msg):
+ getattr(gb, how)(skipna=skipna)
+ return
+ result = getattr(gb, how)(skipna=skipna)
+ expected = DataFrame(
+ {"b": [1, 3, 4, 6]}, index=pd.Index(range(1, 5), name="a", dtype="intp")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func, values",
+ [
+ ("idxmin", {"c_int": [0, 2], "c_float": [1, 3], "c_date": [1, 2]}),
+ ("idxmax", {"c_int": [1, 3], "c_float": [0, 2], "c_date": [0, 3]}),
+ ],
+)
+@pytest.mark.parametrize("numeric_only", [True, False])
+def test_idxmin_idxmax_returns_int_types(func, values, numeric_only):
+ # GH 25444
+ df = DataFrame(
+ {
+ "name": ["A", "A", "B", "B"],
+ "c_int": [1, 2, 3, 4],
+ "c_float": [4.02, 3.03, 2.04, 1.05],
+ "c_date": ["2019", "2018", "2016", "2017"],
+ }
+ )
+ df["c_date"] = pd.to_datetime(df["c_date"])
+ df["c_date_tz"] = df["c_date"].dt.tz_localize("US/Pacific")
+ df["c_timedelta"] = df["c_date"] - df["c_date"].iloc[0]
+ df["c_period"] = df["c_date"].dt.to_period("W")
+ df["c_Integer"] = df["c_int"].astype("Int64")
+ df["c_Floating"] = df["c_float"].astype("Float64")
+
+ result = getattr(df.groupby("name"), func)(numeric_only=numeric_only)
+
+ expected = DataFrame(values, index=pd.Index(["A", "B"], name="name"))
+ if numeric_only:
+ expected = expected.drop(columns=["c_date"])
+ else:
+ expected["c_date_tz"] = expected["c_date"]
+ expected["c_timedelta"] = expected["c_date"]
+ expected["c_period"] = expected["c_date"]
+ expected["c_Integer"] = expected["c_int"]
+ expected["c_Floating"] = expected["c_float"]
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ (
+ Timestamp("2011-01-15 12:50:28.502376"),
+ Timestamp("2011-01-20 12:50:28.593448"),
+ ),
+ (24650000000000001, 24650000000000002),
+ ],
+)
+@pytest.mark.parametrize("method", ["count", "min", "max", "first", "last"])
+def test_groupby_non_arithmetic_agg_int_like_precision(method, data):
+ # GH#6620, GH#9311
+ df = DataFrame({"a": [1, 1], "b": data})
+
+ grouped = df.groupby("a")
+ result = getattr(grouped, method)()
+ if method == "count":
+ expected_value = 2
+ elif method == "first":
+ expected_value = data[0]
+ elif method == "last":
+ expected_value = data[1]
+ else:
+ expected_value = getattr(df["b"], method)()
+ expected = DataFrame({"b": [expected_value]}, index=pd.Index([1], name="a"))
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("how", ["first", "last"])
+def test_first_last_skipna(any_real_nullable_dtype, sort, skipna, how):
+ # GH#57019
+ na_value = na_value_for_dtype(pandas_dtype(any_real_nullable_dtype))
+ df = DataFrame(
+ {
+ "a": [2, 1, 1, 2, 3, 3],
+ # TODO: test that has mixed na_value and NaN either working for
+ # float or raising for int?
+ "b": [na_value, 3.0, na_value, 4.0, na_value, na_value],
+ "c": [na_value, 3.0, na_value, 4.0, na_value, na_value],
+ },
+ dtype=any_real_nullable_dtype,
+ )
+ gb = df.groupby("a", sort=sort)
+ method = getattr(gb, how)
+ result = method(skipna=skipna)
+
+ ilocs = {
+ ("first", True): [3, 1, 4],
+ ("first", False): [0, 1, 4],
+ ("last", True): [3, 1, 5],
+ ("last", False): [3, 2, 5],
+ }[how, skipna]
+ expected = df.iloc[ilocs].set_index("a")
+ if sort:
+ expected = expected.sort_index()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_mean_no_overflow():
+ # Regression test for (#22487)
+ df = DataFrame(
+ {
+ "user": ["A", "A", "A", "A", "A"],
+ "connections": [4970, 4749, 4719, 4704, 18446744073699999744],
+ }
+ )
+ assert df.groupby("user")["connections"].mean()["A"] == 3689348814740003840
+
+
+def test_mean_on_timedelta():
+ # GH 17382
+ df = DataFrame({"time": pd.to_timedelta(range(10)), "cat": ["A", "B"] * 5})
+ result = df.groupby("cat")["time"].mean()
+ expected = Series(
+ pd.to_timedelta([4, 5]), name="time", index=pd.Index(["A", "B"], name="cat")
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "values, dtype, result_dtype",
+ [
+ ([0, 1, np.nan, 3, 4, 5, 6, 7, 8, 9], "float64", "float64"),
+ ([0, 1, pd.NA, 3, 4, 5, 6, 7, 8, 9], "Float64", "Float64"),
+ ([0, 1, pd.NA, 3, 4, 5, 6, 7, 8, 9], "Int64", "Float64"),
+ ([0, 1, np.nan, 3, 4, 5, 6, 7, 8, 9], "timedelta64[ns]", "timedelta64[ns]"),
+ (
+ pd.to_datetime(
+ [
+ "2019-05-09",
+ pd.NaT,
+ "2019-05-11",
+ "2019-05-12",
+ "2019-05-13",
+ "2019-05-14",
+ "2019-05-15",
+ "2019-05-16",
+ "2019-05-17",
+ "2019-05-18",
+ ]
+ ),
+ "datetime64[ns]",
+ "datetime64[ns]",
+ ),
+ ],
+)
+def test_mean_skipna(values, dtype, result_dtype, skipna):
+ # GH#15675
+ df = DataFrame(
+ {
+ "val": values,
+ "cat": ["A", "B"] * 5,
+ }
+ ).astype({"val": dtype})
+ # We need to recast the expected values to the result_dtype because
+ # Series.mean() changes the dtype to float64/object depending on the input dtype
+ expected = (
+ df.groupby("cat")["val"]
+ .apply(lambda x: x.mean(skipna=skipna))
+ .astype(result_dtype)
+ )
+ result = df.groupby("cat")["val"].mean(skipna=skipna)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "values, dtype",
+ [
+ ([0, 1, np.nan, 3, 4, 5, 6, 7, 8, 9], "float64"),
+ ([0, 1, pd.NA, 3, 4, 5, 6, 7, 8, 9], "Float64"),
+ ([0, 1, pd.NA, 3, 4, 5, 6, 7, 8, 9], "Int64"),
+ ([0, 1, np.nan, 3, 4, 5, 6, 7, 8, 9], "timedelta64[ns]"),
+ ],
+)
+def test_sum_skipna(values, dtype, skipna):
+ # GH#15675
+ df = DataFrame(
+ {
+ "val": values,
+ "cat": ["A", "B"] * 5,
+ }
+ ).astype({"val": dtype})
+ # We need to recast the expected values to the original dtype because
+ # Series.sum() changes the dtype
+ expected = (
+ df.groupby("cat")["val"].apply(lambda x: x.sum(skipna=skipna)).astype(dtype)
+ )
+ result = df.groupby("cat")["val"].sum(skipna=skipna)
+ tm.assert_series_equal(result, expected)
+
+
+def test_sum_skipna_object(skipna):
+ # GH#15675
+ df = DataFrame(
+ {
+ "val": ["a", "b", np.nan, "d", "e", "f", "g", "h", "i", "j"],
+ "cat": ["A", "B"] * 5,
+ }
+ ).astype({"val": object})
+ if skipna:
+ expected = Series(
+ ["aegi", "bdfhj"], index=pd.Index(["A", "B"], name="cat"), name="val"
+ ).astype(object)
+ else:
+ expected = Series(
+ [np.nan, "bdfhj"], index=pd.Index(["A", "B"], name="cat"), name="val"
+ ).astype(object)
+ result = df.groupby("cat")["val"].sum(skipna=skipna)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func, values, dtype, result_dtype",
+ [
+ ("prod", [0, 1, 3, np.nan, 4, 5, 6, 7, -8, 9], "float64", "float64"),
+ ("prod", [0, -1, 3, 4, 5, pd.NA, 6, 7, 8, 9], "Float64", "Float64"),
+ ("prod", [0, 1, 3, -4, 5, 6, 7, -8, pd.NA, 9], "Int64", "Int64"),
+ ("prod", [np.nan] * 10, "float64", "float64"),
+ ("prod", [pd.NA] * 10, "Float64", "Float64"),
+ ("prod", [pd.NA] * 10, "Int64", "Int64"),
+ ("var", [0, -1, 3, 4, np.nan, 5, 6, 7, 8, 9], "float64", "float64"),
+ ("var", [0, 1, 3, -4, 5, 6, 7, -8, 9, pd.NA], "Float64", "Float64"),
+ ("var", [0, -1, 3, 4, 5, -6, 7, pd.NA, 8, 9], "Int64", "Float64"),
+ ("var", [np.nan] * 10, "float64", "float64"),
+ ("var", [pd.NA] * 10, "Float64", "Float64"),
+ ("var", [pd.NA] * 10, "Int64", "Float64"),
+ ("std", [0, 1, 3, -4, 5, 6, 7, -8, np.nan, 9], "float64", "float64"),
+ ("std", [0, -1, 3, 4, 5, -6, 7, pd.NA, 8, 9], "Float64", "Float64"),
+ ("std", [0, 1, 3, -4, 5, 6, 7, -8, 9, pd.NA], "Int64", "Float64"),
+ ("std", [np.nan] * 10, "float64", "float64"),
+ ("std", [pd.NA] * 10, "Float64", "Float64"),
+ ("std", [pd.NA] * 10, "Int64", "Float64"),
+ ("sem", [0, -1, 3, 4, 5, -6, 7, np.nan, 8, 9], "float64", "float64"),
+ ("sem", [0, 1, 3, -4, 5, 6, 7, -8, pd.NA, 9], "Float64", "Float64"),
+ ("sem", [0, -1, 3, 4, 5, -6, 7, 8, 9, pd.NA], "Int64", "Float64"),
+ ("sem", [np.nan] * 10, "float64", "float64"),
+ ("sem", [pd.NA] * 10, "Float64", "Float64"),
+ ("sem", [pd.NA] * 10, "Int64", "Float64"),
+ ("min", [0, -1, 3, 4, 5, -6, 7, np.nan, 8, 9], "float64", "float64"),
+ ("min", [0, 1, 3, -4, 5, 6, 7, -8, pd.NA, 9], "Float64", "Float64"),
+ ("min", [0, -1, 3, 4, 5, -6, 7, 8, 9, pd.NA], "Int64", "Int64"),
+ (
+ "min",
+ [0, 1, np.nan, 3, 4, 5, 6, 7, 8, 9],
+ "timedelta64[ns]",
+ "timedelta64[ns]",
+ ),
+ (
+ "min",
+ pd.to_datetime(
+ [
+ "2019-05-09",
+ pd.NaT,
+ "2019-05-11",
+ "2019-05-12",
+ "2019-05-13",
+ "2019-05-14",
+ "2019-05-15",
+ "2019-05-16",
+ "2019-05-17",
+ "2019-05-18",
+ ]
+ ),
+ "datetime64[ns]",
+ "datetime64[ns]",
+ ),
+ ("min", [np.nan] * 10, "float64", "float64"),
+ ("min", [pd.NA] * 10, "Float64", "Float64"),
+ ("min", [pd.NA] * 10, "Int64", "Int64"),
+ ("max", [0, -1, 3, 4, 5, -6, 7, np.nan, 8, 9], "float64", "float64"),
+ ("max", [0, 1, 3, -4, 5, 6, 7, -8, pd.NA, 9], "Float64", "Float64"),
+ ("max", [0, -1, 3, 4, 5, -6, 7, 8, 9, pd.NA], "Int64", "Int64"),
+ (
+ "max",
+ [0, 1, np.nan, 3, 4, 5, 6, 7, 8, 9],
+ "timedelta64[ns]",
+ "timedelta64[ns]",
+ ),
+ (
+ "max",
+ pd.to_datetime(
+ [
+ "2019-05-09",
+ pd.NaT,
+ "2019-05-11",
+ "2019-05-12",
+ "2019-05-13",
+ "2019-05-14",
+ "2019-05-15",
+ "2019-05-16",
+ "2019-05-17",
+ "2019-05-18",
+ ]
+ ),
+ "datetime64[ns]",
+ "datetime64[ns]",
+ ),
+ ("max", [np.nan] * 10, "float64", "float64"),
+ ("max", [pd.NA] * 10, "Float64", "Float64"),
+ ("max", [pd.NA] * 10, "Int64", "Int64"),
+ ("median", [0, -1, 3, 4, 5, -6, 7, np.nan, 8, 9], "float64", "float64"),
+ ("median", [0, 1, 3, -4, 5, 6, 7, -8, pd.NA, 9], "Float64", "Float64"),
+ ("median", [0, -1, 3, 4, 5, -6, 7, 8, 9, pd.NA], "Int64", "Float64"),
+ (
+ "median",
+ [0, 1, np.nan, 3, 4, 5, 6, 7, 8, 9],
+ "timedelta64[ns]",
+ "timedelta64[ns]",
+ ),
+ (
+ "median",
+ pd.to_datetime(
+ [
+ "2019-05-09",
+ pd.NaT,
+ "2019-05-11",
+ "2019-05-12",
+ "2019-05-13",
+ "2019-05-14",
+ "2019-05-15",
+ "2019-05-16",
+ "2019-05-17",
+ "2019-05-18",
+ ]
+ ),
+ "datetime64[ns]",
+ "datetime64[ns]",
+ ),
+ ("median", [np.nan] * 10, "float64", "float64"),
+ ("median", [pd.NA] * 10, "Float64", "Float64"),
+ ("median", [pd.NA] * 10, "Int64", "Float64"),
+ ],
+)
+def test_multifunc_skipna(func, values, dtype, result_dtype, skipna):
+ # GH#15675
+ df = DataFrame(
+ {
+ "val": values,
+ "cat": ["A", "B"] * 5,
+ }
+ ).astype({"val": dtype})
+ # We need to recast the expected values to the result_dtype as some operations
+ # change the dtype
+ expected = (
+ df.groupby("cat")["val"]
+ .apply(lambda x: getattr(x, func)(skipna=skipna))
+ .astype(result_dtype)
+ )
+ result = getattr(df.groupby("cat")["val"], func)(skipna=skipna)
+ tm.assert_series_equal(result, expected)
+
+
+def test_cython_median():
+ arr = np.random.default_rng(2).standard_normal(1000)
+ arr[::2] = np.nan
+ df = DataFrame(arr)
+
+ labels = np.random.default_rng(2).integers(0, 50, size=1000).astype(float)
+ labels[::17] = np.nan
+
+ result = df.groupby(labels).median()
+ exp = df.groupby(labels).agg(np.nanmedian)
+ tm.assert_frame_equal(result, exp)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((1000, 5)))
+ rs = df.groupby(labels).agg(np.median)
+ xp = df.groupby(labels).median()
+ tm.assert_frame_equal(rs, xp)
+
+
+def test_median_empty_bins(observed):
+ df = DataFrame(np.random.default_rng(2).integers(0, 44, 500))
+
+ grps = range(0, 55, 5)
+ bins = pd.cut(df[0], grps)
+
+ result = df.groupby(bins, observed=observed).median()
+ expected = df.groupby(bins, observed=observed).agg(lambda x: x.median())
+ tm.assert_frame_equal(result, expected)
+
+
+def test_max_min_non_numeric():
+ # #2700
+ aa = DataFrame({"nn": [11, 11, 22, 22], "ii": [1, 2, 3, 4], "ss": 4 * ["mama"]})
+
+ result = aa.groupby("nn").max()
+ assert "ss" in result
+
+ result = aa.groupby("nn").max(numeric_only=False)
+ assert "ss" in result
+
+ result = aa.groupby("nn").min()
+ assert "ss" in result
+
+ result = aa.groupby("nn").min(numeric_only=False)
+ assert "ss" in result
+
+
+def test_max_min_object_multiple_columns(using_infer_string):
+ # GH#41111 case where the aggregation is valid for some columns but not
+ # others; we split object blocks column-wise, consistent with
+ # DataFrame._reduce
+
+ df = DataFrame(
+ {
+ "A": [1, 1, 2, 2, 3],
+ "B": [1, "foo", 2, "bar", False],
+ "C": ["a", "b", "c", "d", "e"],
+ }
+ )
+ df._consolidate_inplace() # should already be consolidate, but double-check
+ assert len(df._mgr.blocks) == 3 if using_infer_string else 2
+
+ gb = df.groupby("A")
+
+ result = gb[["C"]].max()
+ # "max" is valid for column "C" but not for "B"
+ ei = pd.Index([1, 2, 3], name="A")
+ expected = DataFrame({"C": ["b", "d", "e"]}, index=ei)
+ tm.assert_frame_equal(result, expected)
+
+ result = gb[["C"]].min()
+ # "min" is valid for column "C" but not for "B"
+ ei = pd.Index([1, 2, 3], name="A")
+ expected = DataFrame({"C": ["a", "c", "e"]}, index=ei)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_min_date_with_nans():
+ # GH26321
+ dates = pd.to_datetime(
+ Series(["2019-05-09", "2019-05-09", "2019-05-09"]), format="%Y-%m-%d"
+ ).dt.date
+ df = DataFrame({"a": [np.nan, "1", np.nan], "b": [0, 1, 1], "c": dates})
+
+ result = df.groupby("b", as_index=False)["c"].min()["c"]
+ expected = pd.to_datetime(
+ Series(["2019-05-09", "2019-05-09"], name="c"), format="%Y-%m-%d"
+ ).dt.date
+ tm.assert_series_equal(result, expected)
+
+ result = df.groupby("b")["c"].min()
+ expected.index.name = "b"
+ tm.assert_series_equal(result, expected)
+
+
+def test_max_inat():
+ # GH#40767 dont interpret iNaT as NaN
+ ser = Series([1, iNaT])
+ key = np.array([1, 1], dtype=np.int64)
+ gb = ser.groupby(key)
+
+ result = gb.max(min_count=2)
+ expected = Series({1: 1}, dtype=np.int64)
+ tm.assert_series_equal(result, expected, check_exact=True)
+
+ result = gb.min(min_count=2)
+ expected = Series({1: iNaT}, dtype=np.int64)
+ tm.assert_series_equal(result, expected, check_exact=True)
+
+ # not enough entries -> gets masked to NaN
+ result = gb.min(min_count=3)
+ expected = Series({1: np.nan})
+ tm.assert_series_equal(result, expected, check_exact=True)
+
+
+def test_max_inat_not_all_na():
+ # GH#40767 dont interpret iNaT as NaN
+
+ # make sure we dont round iNaT+1 to iNaT
+ ser = Series([1, iNaT, 2, iNaT + 1])
+ gb = ser.groupby([1, 2, 3, 3])
+ result = gb.min(min_count=2)
+
+ # Note: in converting to float64, the iNaT + 1 maps to iNaT, i.e. is lossy
+ expected = Series({1: np.nan, 2: np.nan, 3: iNaT + 1})
+ expected.index = expected.index.astype(int)
+ tm.assert_series_equal(result, expected, check_exact=True)
+
+
+@pytest.mark.parametrize("func", ["min", "max"])
+def test_groupby_aggregate_period_column(func):
+ # GH 31471
+ groups = [1, 2]
+ periods = pd.period_range("2020", periods=2, freq="Y")
+ df = DataFrame({"a": groups, "b": periods})
+
+ result = getattr(df.groupby("a")["b"], func)()
+ idx = pd.Index([1, 2], name="a")
+ expected = Series(periods, index=idx, name="b")
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["min", "max"])
+def test_groupby_aggregate_period_frame(func):
+ # GH 31471
+ groups = [1, 2]
+ periods = pd.period_range("2020", periods=2, freq="Y")
+ df = DataFrame({"a": groups, "b": periods})
+
+ result = getattr(df.groupby("a"), func)()
+ idx = pd.Index([1, 2], name="a")
+ expected = DataFrame({"b": periods}, index=idx)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_aggregate_numeric_object_dtype():
+ # https://github.com/pandas-dev/pandas/issues/39329
+ # simplified case: multiple object columns where one is all-NaN
+ # -> gets split as the all-NaN is inferred as float
+ df = DataFrame(
+ {"key": ["A", "A", "B", "B"], "col1": list("abcd"), "col2": [np.nan] * 4},
+ ).astype(object)
+ result = df.groupby("key").min()
+ expected = (
+ DataFrame(
+ {"key": ["A", "B"], "col1": ["a", "c"], "col2": [np.nan, np.nan]},
+ )
+ .set_index("key")
+ .astype(object)
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # same but with numbers
+ df = DataFrame(
+ {"key": ["A", "A", "B", "B"], "col1": list("abcd"), "col2": range(4)},
+ ).astype(object)
+ result = df.groupby("key").min()
+ expected = (
+ DataFrame({"key": ["A", "B"], "col1": ["a", "c"], "col2": [0, 2]})
+ .set_index("key")
+ .astype(object)
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["min", "max"])
+def test_aggregate_categorical_lost_index(func: str):
+ # GH: 28641 groupby drops index, when grouping over categorical column with min/max
+ ds = Series(["b"], dtype="category").cat.as_ordered()
+ df = DataFrame({"A": [1997], "B": ds})
+ result = df.groupby("A").agg({"B": func})
+ expected = DataFrame({"B": ["b"]}, index=pd.Index([1997], name="A"))
+
+ # ordered categorical dtype should be preserved
+ expected["B"] = expected["B"].astype(ds.dtype)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["Int64", "Int32", "Float64", "Float32", "boolean"])
+def test_groupby_min_max_nullable(dtype):
+ if dtype == "Int64":
+ # GH#41743 avoid precision loss
+ ts = 1618556707013635762
+ elif dtype == "boolean":
+ ts = 0
+ else:
+ ts = 4.0
+
+ df = DataFrame({"id": [2, 2], "ts": [ts, ts + 1]})
+ df["ts"] = df["ts"].astype(dtype)
+
+ gb = df.groupby("id")
+
+ result = gb.min()
+ expected = df.iloc[:1].set_index("id")
+ tm.assert_frame_equal(result, expected)
+
+ res_max = gb.max()
+ expected_max = df.iloc[1:].set_index("id")
+ tm.assert_frame_equal(res_max, expected_max)
+
+ result2 = gb.min(min_count=3)
+ expected2 = DataFrame({"ts": [pd.NA]}, index=expected.index, dtype=dtype)
+ tm.assert_frame_equal(result2, expected2)
+
+ res_max2 = gb.max(min_count=3)
+ tm.assert_frame_equal(res_max2, expected2)
+
+ # Case with NA values
+ df2 = DataFrame({"id": [2, 2, 2], "ts": [ts, pd.NA, ts + 1]})
+ df2["ts"] = df2["ts"].astype(dtype)
+ gb2 = df2.groupby("id")
+
+ result3 = gb2.min()
+ tm.assert_frame_equal(result3, expected)
+
+ res_max3 = gb2.max()
+ tm.assert_frame_equal(res_max3, expected_max)
+
+ result4 = gb2.min(min_count=100)
+ tm.assert_frame_equal(result4, expected2)
+
+ res_max4 = gb2.max(min_count=100)
+ tm.assert_frame_equal(res_max4, expected2)
+
+
+def test_min_max_nullable_uint64_empty_group():
+ # don't raise NotImplementedError from libgroupby
+ cat = pd.Categorical([0] * 10, categories=[0, 1])
+ df = DataFrame({"A": cat, "B": pd.array(np.arange(10, dtype=np.uint64))})
+ gb = df.groupby("A", observed=False)
+
+ res = gb.min()
+
+ idx = pd.CategoricalIndex([0, 1], dtype=cat.dtype, name="A")
+ expected = DataFrame({"B": pd.array([0, pd.NA], dtype="UInt64")}, index=idx)
+ tm.assert_frame_equal(res, expected)
+
+ res = gb.max()
+ expected.iloc[0, 0] = 9
+ tm.assert_frame_equal(res, expected)
+
+
+@pytest.mark.parametrize("func", ["first", "last", "min", "max"])
+def test_groupby_min_max_categorical(func):
+ # GH: 52151
+ df = DataFrame(
+ {
+ "col1": pd.Categorical(["A"], categories=list("AB"), ordered=True),
+ "col2": pd.Categorical([1], categories=[1, 2], ordered=True),
+ "value": 0.1,
+ }
+ )
+ result = getattr(df.groupby("col1", observed=False), func)()
+
+ idx = pd.CategoricalIndex(data=["A", "B"], name="col1", ordered=True)
+ expected = DataFrame(
+ {
+ "col2": pd.Categorical([1, None], categories=[1, 2], ordered=True),
+ "value": [0.1, None],
+ },
+ index=idx,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["min", "max"])
+def test_min_empty_string_dtype(func, string_dtype_no_object):
+ # GH#55619
+ dtype = string_dtype_no_object
+ df = DataFrame({"a": ["a"], "b": "a", "c": "a"}, dtype=dtype).iloc[:0]
+ result = getattr(df.groupby("a"), func)()
+ expected = DataFrame(
+ columns=["b", "c"], dtype=dtype, index=pd.Index([], dtype=dtype, name="a")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("min_count", [0, 1])
+@pytest.mark.parametrize("test_series", [True, False])
+def test_string_dtype_all_na(
+ string_dtype_no_object, reduction_func, skipna, min_count, test_series
+):
+ # https://github.com/pandas-dev/pandas/issues/60985
+ if reduction_func == "corrwith":
+ # corrwith is deprecated.
+ return
+
+ dtype = string_dtype_no_object
+
+ if reduction_func in [
+ "any",
+ "all",
+ "idxmin",
+ "idxmax",
+ "mean",
+ "median",
+ "std",
+ "var",
+ ]:
+ kwargs = {"skipna": skipna}
+ elif reduction_func in ["kurt"]:
+ kwargs = {"min_count": min_count}
+ elif reduction_func in ["count", "nunique", "quantile", "sem", "size"]:
+ kwargs = {}
+ else:
+ kwargs = {"skipna": skipna, "min_count": min_count}
+
+ expected_dtype, expected_value = dtype, pd.NA
+ if reduction_func in ["all", "any"]:
+ expected_dtype = "bool"
+ # TODO: For skipna=False, bool(pd.NA) raises; should groupby?
+ expected_value = not skipna if reduction_func == "any" else True
+ elif reduction_func in ["count", "nunique", "size"]:
+ # TODO: Should be more consistent - return Int64 when dtype.na_value is pd.NA?
+ if (
+ test_series
+ and reduction_func == "size"
+ and dtype.storage == "pyarrow"
+ and dtype.na_value is pd.NA
+ ):
+ expected_dtype = "Int64"
+ else:
+ expected_dtype = "int64"
+ expected_value = 1 if reduction_func == "size" else 0
+ elif not skipna or min_count > 0:
+ expected_value = pd.NA
+ elif reduction_func == "sum":
+ # https://github.com/pandas-dev/pandas/pull/60936
+ expected_value = ""
+
+ df = DataFrame({"a": ["x"], "b": [pd.NA]}, dtype=dtype)
+ obj = df["b"] if test_series else df
+ args = get_groupby_method_args(reduction_func, obj)
+ gb = obj.groupby(df["a"])
+ method = getattr(gb, reduction_func)
+
+ if reduction_func in [
+ "mean",
+ "median",
+ "kurt",
+ "prod",
+ "quantile",
+ "sem",
+ "skew",
+ "std",
+ "var",
+ ]:
+ msg = f"dtype '{dtype}' does not support operation '{reduction_func}'"
+ with pytest.raises(TypeError, match=msg):
+ method(*args, **kwargs)
+ return
+ elif reduction_func in ["idxmin", "idxmax"]:
+ if skipna:
+ msg = f"{reduction_func} with skipna=True encountered all NA values"
+ else:
+ msg = f"{reduction_func} with skipna=False encountered an NA value."
+ with pytest.raises(ValueError, match=msg):
+ method(*args, **kwargs)
+ return
+
+ result = method(*args, **kwargs)
+ index = pd.Index(["x"], name="a", dtype=dtype)
+ if test_series or reduction_func == "size":
+ name = None if not test_series and reduction_func == "size" else "b"
+ expected = Series(expected_value, index=index, dtype=expected_dtype, name=name)
+ else:
+ expected = DataFrame({"b": expected_value}, index=index, dtype=expected_dtype)
+ tm.assert_equal(result, expected)
+
+
+def test_max_nan_bug():
+ df = DataFrame(
+ {
+ "Unnamed: 0": ["-04-23", "-05-06", "-05-07"],
+ "Date": [
+ "2013-04-23 00:00:00",
+ "2013-05-06 00:00:00",
+ "2013-05-07 00:00:00",
+ ],
+ "app": Series([np.nan, np.nan, "OE"]),
+ "File": ["log080001.log", "log.log", "xlsx"],
+ }
+ )
+ gb = df.groupby("Date")
+ r = gb[["File"]].max()
+ e = gb["File"].max().to_frame()
+ tm.assert_frame_equal(r, e)
+ assert not r["File"].isna().any()
+
+
+@pytest.mark.slow
+@pytest.mark.parametrize("with_nan", [True, False])
+@pytest.mark.parametrize("keys", [["joe"], ["joe", "jim"]])
+def test_series_groupby_nunique(sort, dropna, as_index, with_nan, keys):
+ n = 100
+ m = 10
+ days = date_range("2015-08-23", periods=10)
+ df = DataFrame(
+ {
+ "jim": np.random.default_rng(2).choice(list(ascii_lowercase), n),
+ "joe": np.random.default_rng(2).choice(days, n),
+ "julie": np.random.default_rng(2).integers(0, m, n),
+ }
+ )
+ if with_nan:
+ df = df.astype({"julie": float}) # Explicit cast to avoid implicit cast below
+ df.loc[1::17, "jim"] = None
+ df.loc[3::37, "joe"] = None
+ df.loc[7::19, "julie"] = None
+ df.loc[8::19, "julie"] = None
+ df.loc[9::19, "julie"] = None
+ original_df = df.copy()
+ gr = df.groupby(keys, as_index=as_index, sort=sort)
+ left = gr["julie"].nunique(dropna=dropna)
+
+ gr = df.groupby(keys, as_index=as_index, sort=sort)
+ right = gr["julie"].apply(Series.nunique, dropna=dropna)
+ if not as_index:
+ right = right.reset_index(drop=True)
+
+ if as_index:
+ tm.assert_series_equal(left, right, check_names=False)
+ else:
+ tm.assert_frame_equal(left, right, check_names=False)
+ tm.assert_frame_equal(df, original_df)
+
+
+def test_nunique():
+ df = DataFrame({"A": list("abbacc"), "B": list("abxacc"), "C": list("abbacx")})
+
+ expected = DataFrame({"A": list("abc"), "B": [1, 2, 1], "C": [1, 1, 2]})
+ result = df.groupby("A", as_index=False).nunique()
+ tm.assert_frame_equal(result, expected)
+
+ # as_index
+ expected.index = list("abc")
+ expected.index.name = "A"
+ expected = expected.drop(columns="A")
+ result = df.groupby("A").nunique()
+ tm.assert_frame_equal(result, expected)
+
+ # with na
+ result = df.replace({"x": None}).groupby("A").nunique(dropna=False)
+ tm.assert_frame_equal(result, expected)
+
+ # dropna
+ expected = DataFrame({"B": [1] * 3, "C": [1] * 3}, index=list("abc"))
+ expected.index.name = "A"
+ result = df.replace({"x": None}).groupby("A").nunique()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_nunique_with_object():
+ # GH 11077
+ data = DataFrame(
+ [
+ [100, 1, "Alice"],
+ [200, 2, "Bob"],
+ [300, 3, "Charlie"],
+ [-400, 4, "Dan"],
+ [500, 5, "Edith"],
+ ],
+ columns=["amount", "id", "name"],
+ )
+
+ result = data.groupby(["id", "amount"])["name"].nunique()
+ index = MultiIndex.from_arrays([data.id, data.amount])
+ expected = Series([1] * 5, name="name", index=index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_nunique_with_empty_series():
+ # GH 12553
+ data = Series(name="name", dtype=object)
+ result = data.groupby(level=0).nunique()
+ expected = Series(name="name", dtype="int64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_nunique_with_timegrouper():
+ # GH 13453
+ test = DataFrame(
+ {
+ "time": [
+ Timestamp("2016-06-28 09:35:35"),
+ Timestamp("2016-06-28 16:09:30"),
+ Timestamp("2016-06-28 16:46:28"),
+ ],
+ "data": ["1", "2", "3"],
+ }
+ ).set_index("time")
+ result = test.groupby(pd.Grouper(freq="h"))["data"].nunique()
+ expected = test.groupby(pd.Grouper(freq="h"))["data"].apply(Series.nunique)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "key, data, dropna, expected",
+ [
+ (
+ ["x", "x", "x"],
+ [Timestamp("2019-01-01"), pd.NaT, Timestamp("2019-01-01")],
+ True,
+ Series([1], index=pd.Index(["x"], name="key"), name="data"),
+ ),
+ (
+ ["x", "x", "x"],
+ [dt.date(2019, 1, 1), pd.NaT, dt.date(2019, 1, 1)],
+ True,
+ Series([1], index=pd.Index(["x"], name="key"), name="data"),
+ ),
+ (
+ ["x", "x", "x", "y", "y"],
+ [
+ dt.date(2019, 1, 1),
+ pd.NaT,
+ dt.date(2019, 1, 1),
+ pd.NaT,
+ dt.date(2019, 1, 1),
+ ],
+ False,
+ Series([2, 2], index=pd.Index(["x", "y"], name="key"), name="data"),
+ ),
+ (
+ ["x", "x", "x", "x", "y"],
+ [
+ dt.date(2019, 1, 1),
+ pd.NaT,
+ dt.date(2019, 1, 1),
+ pd.NaT,
+ dt.date(2019, 1, 1),
+ ],
+ False,
+ Series([2, 1], index=pd.Index(["x", "y"], name="key"), name="data"),
+ ),
+ ],
+)
+def test_nunique_with_NaT(key, data, dropna, expected):
+ # GH 27951
+ df = DataFrame({"key": key, "data": data})
+ result = df.groupby(["key"])["data"].nunique(dropna=dropna)
+ tm.assert_series_equal(result, expected)
+
+
+def test_nunique_preserves_column_level_names():
+ # GH 23222
+ test = DataFrame([1, 2, 2], columns=pd.Index(["A"], name="level_0"))
+ result = test.groupby([0, 0, 0]).nunique()
+ expected = DataFrame([2], index=np.array([0]), columns=test.columns)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_nunique_transform_with_datetime():
+ # GH 35109 - transform with nunique on datetimes results in integers
+ df = DataFrame(date_range("2008-12-31", "2009-01-02"), columns=["date"])
+ result = df.groupby([0, 0, 1])["date"].transform("nunique")
+ expected = Series([2, 2, 1], name="date")
+ tm.assert_series_equal(result, expected)
+
+
+def test_empty_categorical(observed):
+ # GH#21334
+ cat = Series([1]).astype("category")
+ ser = cat[:0]
+ gb = ser.groupby(ser, observed=observed)
+ result = gb.nunique()
+ if observed:
+ expected = Series([], index=cat[:0], dtype="int64")
+ else:
+ expected = Series([0], index=cat, dtype="int64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_intercept_builtin_sum():
+ s = Series([1.0, 2.0, np.nan, 3.0])
+ grouped = s.groupby([0, 1, 2, 2])
+
+ # GH#53425
+ result = grouped.agg(builtins.sum)
+ # GH#53425
+ result2 = grouped.apply(builtins.sum)
+ expected = Series([1.0, 2.0, np.nan], index=np.array([0, 1, 2]))
+ tm.assert_series_equal(result, expected)
+ tm.assert_series_equal(result2, expected)
+
+
+@pytest.mark.parametrize("min_count", [0, 10])
+def test_groupby_sum_mincount_boolean(min_count):
+ b = True
+ a = False
+ na = np.nan
+ dfg = pd.array([b, b, na, na, a, a, b], dtype="boolean")
+
+ df = DataFrame({"A": [1, 1, 2, 2, 3, 3, 1], "B": dfg})
+ result = df.groupby("A").sum(min_count=min_count)
+ if min_count == 0:
+ expected = DataFrame(
+ {"B": pd.array([3, 0, 0], dtype="Int64")},
+ index=pd.Index([1, 2, 3], name="A"),
+ )
+ tm.assert_frame_equal(result, expected)
+ else:
+ expected = DataFrame(
+ {"B": pd.array([pd.NA] * 3, dtype="Int64")},
+ index=pd.Index([1, 2, 3], name="A"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_sum_below_mincount_nullable_integer():
+ # https://github.com/pandas-dev/pandas/issues/32861
+ df = DataFrame({"a": [0, 1, 2], "b": [0, 1, 2], "c": [0, 1, 2]}, dtype="Int64")
+ grouped = df.groupby("a")
+ idx = pd.Index([0, 1, 2], name="a", dtype="Int64")
+
+ result = grouped["b"].sum(min_count=2)
+ expected = Series([pd.NA] * 3, dtype="Int64", index=idx, name="b")
+ tm.assert_series_equal(result, expected)
+
+ result = grouped.sum(min_count=2)
+ expected = DataFrame({"b": [pd.NA] * 3, "c": [pd.NA] * 3}, dtype="Int64", index=idx)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_sum_timedelta_with_nat():
+ # GH#42659
+ df = DataFrame(
+ {
+ "a": [1, 1, 2, 2],
+ "b": [pd.Timedelta("1D"), pd.Timedelta("2D"), pd.Timedelta("3D"), pd.NaT],
+ }
+ )
+ td3 = pd.Timedelta(days=3).as_unit("us")
+
+ gb = df.groupby("a")
+
+ res = gb.sum()
+ expected = DataFrame({"b": [td3, td3]}, index=pd.Index([1, 2], name="a"))
+ tm.assert_frame_equal(res, expected)
+
+ res = gb["b"].sum()
+ tm.assert_series_equal(res, expected["b"])
+
+ res = gb["b"].sum(min_count=2)
+ expected = Series([td3, pd.NaT], dtype="m8[us]", name="b", index=expected.index)
+ tm.assert_series_equal(res, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype", ["int8", "int16", "int32", "int64", "float32", "float64", "uint64"]
+)
+@pytest.mark.parametrize(
+ "method,data",
+ [
+ ("first", {"df": [{"a": 1, "b": 1}, {"a": 2, "b": 3}]}),
+ ("last", {"df": [{"a": 1, "b": 2}, {"a": 2, "b": 4}]}),
+ ("min", {"df": [{"a": 1, "b": 1}, {"a": 2, "b": 3}]}),
+ ("max", {"df": [{"a": 1, "b": 2}, {"a": 2, "b": 4}]}),
+ ("count", {"df": [{"a": 1, "b": 2}, {"a": 2, "b": 2}], "out_type": "int64"}),
+ ],
+)
+def test_groupby_non_arithmetic_agg_types(dtype, method, data):
+ # GH9311, GH6620
+ df = DataFrame(
+ [{"a": 1, "b": 1}, {"a": 1, "b": 2}, {"a": 2, "b": 3}, {"a": 2, "b": 4}]
+ )
+
+ df["b"] = df.b.astype(dtype)
+
+ if "args" not in data:
+ data["args"] = []
+
+ if "out_type" in data:
+ out_type = data["out_type"]
+ else:
+ out_type = dtype
+
+ exp = data["df"]
+ df_out = DataFrame(exp)
+
+ df_out["b"] = df_out.b.astype(out_type)
+ df_out.set_index("a", inplace=True)
+
+ grpd = df.groupby("a")
+ t = getattr(grpd, method)(*data["args"])
+ tm.assert_frame_equal(t, df_out)
+
+
+def scipy_sem(*args, **kwargs):
+ from scipy.stats import sem
+
+ return sem(*args, ddof=1, **kwargs)
+
+
+@pytest.mark.parametrize(
+ "op,targop",
+ [
+ ("mean", np.mean),
+ ("median", np.median),
+ ("std", np.std),
+ ("var", np.var),
+ ("sum", np.sum),
+ ("prod", np.prod),
+ ("min", np.min),
+ ("max", np.max),
+ ("first", lambda x: x.iloc[0]),
+ ("last", lambda x: x.iloc[-1]),
+ ("count", np.size),
+ pytest.param("sem", scipy_sem, marks=td.skip_if_no("scipy")),
+ ],
+)
+def test_ops_general(op, targop):
+ df = DataFrame(np.random.default_rng(2).standard_normal(1000))
+ labels = np.random.default_rng(2).integers(0, 50, size=1000).astype(float)
+
+ result = getattr(df.groupby(labels), op)()
+ kwargs = {"ddof": 1, "axis": 0} if op in ["std", "var"] else {}
+ expected = df.groupby(labels).agg(targop, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "values",
+ [
+ {
+ "a": [1, 1, 1, 2, 2, 2, 3, 3, 3],
+ "b": [1, pd.NA, 2, 1, pd.NA, 2, 1, pd.NA, 2],
+ },
+ {"a": [1, 1, 2, 2, 3, 3], "b": [1, 2, 1, 2, 1, 2]},
+ ],
+)
+@pytest.mark.parametrize("function", ["mean", "median", "var"])
+def test_apply_to_nullable_integer_returns_float(values, function):
+ # https://github.com/pandas-dev/pandas/issues/32219
+ output = 0.5 if function == "var" else 1.5
+ arr = np.array([output] * 3, dtype=float)
+ idx = pd.Index([1, 2, 3], name="a", dtype="Int64")
+ expected = DataFrame({"b": arr}, index=idx).astype("Float64")
+
+ groups = DataFrame(values, dtype="Int64").groupby("a")
+
+ result = getattr(groups, function)()
+ tm.assert_frame_equal(result, expected)
+
+ result = groups.agg(function)
+ tm.assert_frame_equal(result, expected)
+
+ result = groups.agg([function])
+ expected.columns = MultiIndex.from_tuples([("b", function)])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "op",
+ [
+ "sum",
+ "prod",
+ "min",
+ "max",
+ "median",
+ "mean",
+ "skew",
+ "kurt",
+ "std",
+ "var",
+ "sem",
+ ],
+)
+def test_regression_allowlist_methods(op, skipna, sort):
+ # GH6944
+ # GH 17537
+ # explicitly test the allowlist methods
+ frame = DataFrame([0])
+
+ grouped = frame.groupby(level=0, sort=sort)
+
+ if op in ["skew", "kurt", "sum", "mean"]:
+ # skew, kurt, sum, mean have skipna
+ result = getattr(grouped, op)(skipna=skipna)
+ expected = frame.groupby(level=0).apply(lambda h: getattr(h, op)(skipna=skipna))
+ if sort:
+ expected = expected.sort_index()
+ tm.assert_frame_equal(result, expected)
+ else:
+ result = getattr(grouped, op)()
+ expected = frame.groupby(level=0).apply(lambda h: getattr(h, op)())
+ if sort:
+ expected = expected.sort_index()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_prod_with_int64_dtype():
+ # GH#46573
+ data = [
+ [1, 11],
+ [1, 41],
+ [1, 17],
+ [1, 37],
+ [1, 7],
+ [1, 29],
+ [1, 31],
+ [1, 2],
+ [1, 3],
+ [1, 43],
+ [1, 5],
+ [1, 47],
+ [1, 19],
+ [1, 88],
+ ]
+ df = DataFrame(data, columns=["A", "B"], dtype="int64")
+ result = df.groupby(["A"]).prod().reset_index()
+ expected = DataFrame({"A": [1], "B": [180970905912331920]}, dtype="int64")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_std_datetimelike():
+ # GH#48481
+ tdi = pd.timedelta_range("1 Day", periods=10000, unit="ns")
+ ser = Series(tdi)
+ ser[::5] *= 2 # get different std for different groups
+
+ df = ser.to_frame("A").copy()
+
+ df["B"] = ser + Timestamp(0)
+ df["C"] = ser + Timestamp(0, tz="UTC")
+ df.iloc[-1] = pd.NaT # last group includes NaTs
+
+ gb = df.groupby(list(range(5)) * 2000)
+
+ result = gb.std()
+
+ # Note: this does not _exactly_ match what we would get if we did
+ # [gb.get_group(i).std() for i in gb.groups]
+ # but it _does_ match the floating point error we get doing the
+ # same operation on int64 data xref GH#51332
+ td1 = pd.Timedelta("2887 days 11:21:02.326710176")
+ td4 = pd.Timedelta("2886 days 00:42:34.664668096")
+ exp_ser = Series([td1 * 2, td1, td1, td1, td4], index=np.arange(5))
+ expected = DataFrame({"A": exp_ser, "B": exp_ser, "C": exp_ser})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_mean_numeric_only_validates_bool():
+ # GH#62778
+
+ df = DataFrame({"A": range(5), "B": range(5)})
+
+ msg = "numeric_only accepts only Boolean values"
+ with pytest.raises(ValueError, match=msg):
+ df.groupby(["A"]).mean(["B"])
+
+ with pytest.raises(ValueError, match=msg):
+ df.groupby(["A"]).mean(numeric_only="True")
+
+ with pytest.raises(ValueError, match=msg):
+ df.groupby(["A"]).mean(numeric_only=1)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_timegrouper.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_timegrouper.py
new file mode 100644
index 0000000000000000000000000000000000000000..b60947e61fb23acf3cd0c3314f0dbfe124c22188
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/test_timegrouper.py
@@ -0,0 +1,984 @@
+"""
+test with the TimeGrouper / grouping with datetimes
+"""
+
+from datetime import (
+ datetime,
+ timedelta,
+ timezone,
+)
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ date_range,
+ offsets,
+)
+import pandas._testing as tm
+from pandas.core.groupby.grouper import Grouper
+from pandas.core.groupby.ops import BinGrouper
+
+
+@pytest.fixture
+def frame_for_truncated_bingrouper():
+ """
+ DataFrame used by groupby_with_truncated_bingrouper, made into
+ a separate fixture for easier reuse in
+ test_groupby_apply_timegrouper_with_nat_apply_squeeze
+ """
+ df = DataFrame(
+ {
+ "Quantity": [18, 3, 5, 1, 9, 3],
+ "Date": [
+ Timestamp(2013, 9, 1, 13, 0),
+ Timestamp(2013, 9, 1, 13, 5),
+ Timestamp(2013, 10, 1, 20, 0),
+ Timestamp(2013, 10, 3, 10, 0),
+ pd.NaT,
+ Timestamp(2013, 9, 2, 14, 0),
+ ],
+ }
+ )
+ return df
+
+
+@pytest.fixture
+def groupby_with_truncated_bingrouper(frame_for_truncated_bingrouper):
+ """
+ GroupBy object such that gb._grouper is a BinGrouper and
+ len(gb._grouper.result_index) < len(gb._grouper.group_keys_seq)
+
+ Aggregations on this groupby should have
+
+ dti = date_range("2013-09-01", "2013-10-01", freq="5D", name="Date")
+
+ As either the index or an index level.
+ """
+ df = frame_for_truncated_bingrouper
+
+ tdg = Grouper(key="Date", freq="5D")
+ gb = df.groupby(tdg)
+
+ # check we're testing the case we're interested in
+ assert len(gb._grouper.result_index) != len(gb._grouper.codes)
+
+ return gb
+
+
+class TestGroupBy:
+ def test_groupby_with_timegrouper(self, using_infer_string):
+ # GH 4161
+ # TimeGrouper requires a sorted index
+ # also verifies that the resultant index has the correct name
+ df_original = DataFrame(
+ {
+ "Buyer": "Carl Carl Carl Carl Joe Carl".split(),
+ "Quantity": [18, 3, 5, 1, 9, 3],
+ "Date": [
+ datetime(2013, 9, 1, 13, 0),
+ datetime(2013, 9, 1, 13, 5),
+ datetime(2013, 10, 1, 20, 0),
+ datetime(2013, 10, 3, 10, 0),
+ datetime(2013, 12, 2, 12, 0),
+ datetime(2013, 9, 2, 14, 0),
+ ],
+ }
+ )
+
+ # GH 6908 change target column's order
+ df_reordered = df_original.sort_values(by="Quantity")
+
+ for df in [df_original, df_reordered]:
+ df = df.set_index(["Date"])
+
+ exp_dti = date_range(
+ "20130901",
+ "20131205",
+ freq="5D",
+ name="Date",
+ inclusive="left",
+ unit=df.index.unit,
+ )
+ expected = DataFrame(
+ {"Buyer": "" if using_infer_string else 0, "Quantity": 0},
+ index=exp_dti,
+ )
+ # Cast to object/str to avoid implicit cast when setting
+ # entry to "CarlCarlCarl"
+ expected = expected.astype({"Buyer": object})
+ if using_infer_string:
+ expected = expected.astype({"Buyer": "str"})
+ expected.iloc[0, 0] = "CarlCarlCarl"
+ expected.iloc[6, 0] = "CarlCarl"
+ expected.iloc[18, 0] = "Joe"
+ expected.iloc[[0, 6, 18], 1] = np.array([24, 6, 9], dtype="int64")
+
+ result1 = df.resample("5D").sum()
+ tm.assert_frame_equal(result1, expected)
+
+ df_sorted = df.sort_index()
+ result2 = df_sorted.groupby(Grouper(freq="5D")).sum()
+ tm.assert_frame_equal(result2, expected)
+
+ result3 = df.groupby(Grouper(freq="5D")).sum()
+ tm.assert_frame_equal(result3, expected)
+
+ @pytest.mark.parametrize("should_sort", [True, False])
+ def test_groupby_with_timegrouper_methods(self, should_sort):
+ # GH 3881
+ # make sure API of timegrouper conforms
+
+ df = DataFrame(
+ {
+ "Branch": "A A A A A B".split(),
+ "Buyer": "Carl Mark Carl Joe Joe Carl".split(),
+ "Quantity": [1, 3, 5, 8, 9, 3],
+ "Date": [
+ datetime(2013, 1, 1, 13, 0),
+ datetime(2013, 1, 1, 13, 5),
+ datetime(2013, 10, 1, 20, 0),
+ datetime(2013, 10, 2, 10, 0),
+ datetime(2013, 12, 2, 12, 0),
+ datetime(2013, 12, 2, 14, 0),
+ ],
+ }
+ )
+
+ if should_sort:
+ df = df.sort_values(by="Quantity", ascending=False)
+
+ df = df.set_index("Date", drop=False)
+ g = df.groupby(Grouper(freq="6ME"))
+ assert g.group_keys
+
+ assert isinstance(g._grouper, BinGrouper)
+ groups = g.groups
+ assert isinstance(groups, dict)
+ assert len(groups) == 3
+
+ def test_timegrouper_with_reg_groups(self):
+ # GH 3794
+ # allow combination of timegrouper/reg groups
+
+ df_original = DataFrame(
+ {
+ "Branch": "A A A A A A A B".split(),
+ "Buyer": "Carl Mark Carl Carl Joe Joe Joe Carl".split(),
+ "Quantity": [1, 3, 5, 1, 8, 1, 9, 3],
+ "Date": [
+ datetime(2013, 1, 1, 13, 0),
+ datetime(2013, 1, 1, 13, 5),
+ datetime(2013, 10, 1, 20, 0),
+ datetime(2013, 10, 2, 10, 0),
+ datetime(2013, 10, 1, 20, 0),
+ datetime(2013, 10, 2, 10, 0),
+ datetime(2013, 12, 2, 12, 0),
+ datetime(2013, 12, 2, 14, 0),
+ ],
+ }
+ ).set_index("Date")
+
+ df_sorted = df_original.sort_values(by="Quantity", ascending=False)
+
+ for df in [df_original, df_sorted]:
+ expected = DataFrame(
+ {
+ "Buyer": "Carl Joe Mark".split(),
+ "Quantity": [10, 18, 3],
+ "Date": [
+ datetime(2013, 12, 31, 0, 0),
+ datetime(2013, 12, 31, 0, 0),
+ datetime(2013, 12, 31, 0, 0),
+ ],
+ }
+ ).set_index(["Date", "Buyer"])
+
+ msg = "The default value of numeric_only"
+ result = df.groupby([Grouper(freq="YE"), "Buyer"]).sum(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(
+ {
+ "Buyer": "Carl Mark Carl Joe".split(),
+ "Quantity": [1, 3, 9, 18],
+ "Date": [
+ datetime(2013, 1, 1, 0, 0),
+ datetime(2013, 1, 1, 0, 0),
+ datetime(2013, 7, 1, 0, 0),
+ datetime(2013, 7, 1, 0, 0),
+ ],
+ }
+ ).set_index(["Date", "Buyer"])
+ result = df.groupby([Grouper(freq="6MS"), "Buyer"]).sum(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+ df_original = DataFrame(
+ {
+ "Branch": "A A A A A A A B".split(),
+ "Buyer": "Carl Mark Carl Carl Joe Joe Joe Carl".split(),
+ "Quantity": [1, 3, 5, 1, 8, 1, 9, 3],
+ "Date": [
+ datetime(2013, 10, 1, 13, 0),
+ datetime(2013, 10, 1, 13, 5),
+ datetime(2013, 10, 1, 20, 0),
+ datetime(2013, 10, 2, 10, 0),
+ datetime(2013, 10, 1, 20, 0),
+ datetime(2013, 10, 2, 10, 0),
+ datetime(2013, 10, 2, 12, 0),
+ datetime(2013, 10, 2, 14, 0),
+ ],
+ }
+ ).set_index("Date")
+
+ df_sorted = df_original.sort_values(by="Quantity", ascending=False)
+ for df in [df_original, df_sorted]:
+ expected = DataFrame(
+ {
+ "Buyer": "Carl Joe Mark Carl Joe".split(),
+ "Quantity": [6, 8, 3, 4, 10],
+ "Date": [
+ datetime(2013, 10, 1, 0, 0),
+ datetime(2013, 10, 1, 0, 0),
+ datetime(2013, 10, 1, 0, 0),
+ datetime(2013, 10, 2, 0, 0),
+ datetime(2013, 10, 2, 0, 0),
+ ],
+ }
+ ).set_index(["Date", "Buyer"])
+
+ result = df.groupby([Grouper(freq="1D"), "Buyer"]).sum(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby([Grouper(freq="1ME"), "Buyer"]).sum(numeric_only=True)
+ expected = DataFrame(
+ {
+ "Buyer": "Carl Joe Mark".split(),
+ "Quantity": [10, 18, 3],
+ "Date": [
+ datetime(2013, 10, 31, 0, 0),
+ datetime(2013, 10, 31, 0, 0),
+ datetime(2013, 10, 31, 0, 0),
+ ],
+ }
+ ).set_index(["Date", "Buyer"])
+ tm.assert_frame_equal(result, expected)
+
+ # passing the name
+ df = df.reset_index()
+ result = df.groupby([Grouper(freq="1ME", key="Date"), "Buyer"]).sum(
+ numeric_only=True
+ )
+ tm.assert_frame_equal(result, expected)
+
+ with pytest.raises(KeyError, match="'The grouper name foo is not found'"):
+ df.groupby([Grouper(freq="1ME", key="foo"), "Buyer"]).sum()
+
+ # passing the level
+ df = df.set_index("Date")
+ result = df.groupby([Grouper(freq="1ME", level="Date"), "Buyer"]).sum(
+ numeric_only=True
+ )
+ tm.assert_frame_equal(result, expected)
+ result = df.groupby([Grouper(freq="1ME", level=0), "Buyer"]).sum(
+ numeric_only=True
+ )
+ tm.assert_frame_equal(result, expected)
+
+ with pytest.raises(ValueError, match="The level foo is not valid"):
+ df.groupby([Grouper(freq="1ME", level="foo"), "Buyer"]).sum()
+
+ # multi names
+ df = df.copy()
+ df["Date"] = df.index + offsets.MonthEnd(2)
+ result = df.groupby([Grouper(freq="1ME", key="Date"), "Buyer"]).sum(
+ numeric_only=True
+ )
+ expected = DataFrame(
+ {
+ "Buyer": "Carl Joe Mark".split(),
+ "Quantity": [10, 18, 3],
+ "Date": [
+ datetime(2013, 11, 30, 0, 0),
+ datetime(2013, 11, 30, 0, 0),
+ datetime(2013, 11, 30, 0, 0),
+ ],
+ }
+ ).set_index(["Date", "Buyer"])
+ tm.assert_frame_equal(result, expected)
+
+ # error as we have both a level and a name!
+ msg = "The Grouper cannot specify both a key and a level!"
+ with pytest.raises(ValueError, match=msg):
+ df.groupby(
+ [Grouper(freq="1ME", key="Date", level="Date"), "Buyer"]
+ ).sum()
+
+ # single groupers
+ expected = DataFrame(
+ [[31]],
+ columns=["Quantity"],
+ index=DatetimeIndex(
+ [datetime(2013, 10, 31, 0, 0)], freq=offsets.MonthEnd(), name="Date"
+ ),
+ )
+ result = df.groupby(Grouper(freq="1ME")).sum(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby([Grouper(freq="1ME")]).sum(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+ expected.index = expected.index.shift(1)
+ assert expected.index.freq == offsets.MonthEnd()
+ result = df.groupby(Grouper(freq="1ME", key="Date")).sum(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby([Grouper(freq="1ME", key="Date")]).sum(
+ numeric_only=True
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("freq", ["D", "ME", "YE", "QE-APR"])
+ def test_timegrouper_with_reg_groups_freq(self, freq):
+ # GH 6764 multiple grouping with/without sort
+ df = DataFrame(
+ {
+ "date": pd.to_datetime(
+ [
+ "20121002",
+ "20121007",
+ "20130130",
+ "20130202",
+ "20130305",
+ "20121002",
+ "20121207",
+ "20130130",
+ "20130202",
+ "20130305",
+ "20130202",
+ "20130305",
+ ]
+ ),
+ "user_id": [1, 1, 1, 1, 1, 3, 3, 3, 5, 5, 5, 5],
+ "whole_cost": [
+ 1790,
+ 364,
+ 280,
+ 259,
+ 201,
+ 623,
+ 90,
+ 312,
+ 359,
+ 301,
+ 359,
+ 801,
+ ],
+ "cost1": [12, 15, 10, 24, 39, 1, 0, 90, 45, 34, 1, 12],
+ }
+ ).set_index("date")
+
+ expected = (
+ df.groupby("user_id")["whole_cost"]
+ .resample(freq)
+ .sum(min_count=1) # XXX
+ .dropna()
+ .reorder_levels(["date", "user_id"])
+ .sort_index()
+ .astype("int64")
+ )
+ expected.name = "whole_cost"
+
+ result1 = (
+ df.sort_index().groupby([Grouper(freq=freq), "user_id"])["whole_cost"].sum()
+ )
+ tm.assert_series_equal(result1, expected)
+
+ result2 = df.groupby([Grouper(freq=freq), "user_id"])["whole_cost"].sum()
+ tm.assert_series_equal(result2, expected)
+
+ def test_timegrouper_get_group(self):
+ # GH 6914
+
+ df_original = DataFrame(
+ {
+ "Buyer": "Carl Joe Joe Carl Joe Carl".split(),
+ "Quantity": [18, 3, 5, 1, 9, 3],
+ "Date": [
+ datetime(2013, 9, 1, 13, 0),
+ datetime(2013, 9, 1, 13, 5),
+ datetime(2013, 10, 1, 20, 0),
+ datetime(2013, 10, 3, 10, 0),
+ datetime(2013, 12, 2, 12, 0),
+ datetime(2013, 9, 2, 14, 0),
+ ],
+ }
+ )
+ df_reordered = df_original.sort_values(by="Quantity")
+
+ # single grouping
+ expected_list = [
+ df_original.iloc[[0, 1, 5]],
+ df_original.iloc[[2, 3]],
+ df_original.iloc[[4]],
+ ]
+ dt_list = ["2013-09-30", "2013-10-31", "2013-12-31"]
+
+ for df in [df_original, df_reordered]:
+ grouped = df.groupby(Grouper(freq="ME", key="Date"))
+ for t, expected in zip(dt_list, expected_list, strict=True):
+ dt = Timestamp(t)
+ result = grouped.get_group(dt)
+ tm.assert_frame_equal(result, expected)
+
+ # multiple grouping
+ expected_list = [
+ df_original.iloc[[1]],
+ df_original.iloc[[3]],
+ df_original.iloc[[4]],
+ ]
+ g_list = [("Joe", "2013-09-30"), ("Carl", "2013-10-31"), ("Joe", "2013-12-31")]
+
+ for df in [df_original, df_reordered]:
+ grouped = df.groupby(["Buyer", Grouper(freq="ME", key="Date")])
+ for (b, t), expected in zip(g_list, expected_list, strict=True):
+ dt = Timestamp(t)
+ result = grouped.get_group((b, dt))
+ tm.assert_frame_equal(result, expected)
+
+ # with index
+ df_original = df_original.set_index("Date")
+ df_reordered = df_original.sort_values(by="Quantity")
+
+ expected_list = [
+ df_original.iloc[[0, 1, 5]],
+ df_original.iloc[[2, 3]],
+ df_original.iloc[[4]],
+ ]
+
+ for df in [df_original, df_reordered]:
+ grouped = df.groupby(Grouper(freq="ME"))
+ for t, expected in zip(dt_list, expected_list, strict=True):
+ dt = Timestamp(t)
+ result = grouped.get_group(dt)
+ tm.assert_frame_equal(result, expected)
+
+ def test_timegrouper_apply_return_type_series(self):
+ # Using `apply` with the `TimeGrouper` should give the
+ # same return type as an `apply` with a `Grouper`.
+ # Issue #11742
+ df = DataFrame({"date": ["10/10/2000", "11/10/2000"], "value": [10, 13]})
+ df_dt = df.copy()
+ df_dt["date"] = pd.to_datetime(df_dt["date"])
+
+ def sumfunc_series(x):
+ return Series([x["value"].sum()], ("sum",))
+
+ expected = df.groupby(Grouper(key="date")).apply(sumfunc_series)
+ result = df_dt.groupby(Grouper(freq="ME", key="date")).apply(sumfunc_series)
+ tm.assert_frame_equal(
+ result.reset_index(drop=True), expected.reset_index(drop=True)
+ )
+
+ def test_timegrouper_apply_return_type_value(self):
+ # Using `apply` with the `TimeGrouper` should give the
+ # same return type as an `apply` with a `Grouper`.
+ # Issue #11742
+ df = DataFrame({"date": ["10/10/2000", "11/10/2000"], "value": [10, 13]})
+ df_dt = df.copy()
+ df_dt["date"] = pd.to_datetime(df_dt["date"])
+
+ def sumfunc_value(x):
+ return x.value.sum()
+
+ expected = df.groupby(Grouper(key="date")).apply(sumfunc_value)
+ result = df_dt.groupby(Grouper(freq="ME", key="date")).apply(sumfunc_value)
+ tm.assert_series_equal(
+ result.reset_index(drop=True), expected.reset_index(drop=True)
+ )
+
+ def test_groupby_groups_datetimeindex(self):
+ # GH#1430
+ periods = 1000
+ ind = date_range(start="2012/1/1", freq="5min", periods=periods)
+ df = DataFrame(
+ {"high": np.arange(periods), "low": np.arange(periods)}, index=ind
+ )
+ grouped = df.groupby(lambda x: datetime(x.year, x.month, x.day))
+
+ # it works!
+ groups = grouped.groups
+ assert isinstance(next(iter(groups.keys())), datetime)
+
+ def test_groupby_groups_datetimeindex2(self):
+ # GH#11442
+ index = date_range("2015/01/01", periods=5, name="date")
+ df = DataFrame({"A": [5, 6, 7, 8, 9], "B": [1, 2, 3, 4, 5]}, index=index)
+ result = df.groupby(level="date").groups
+ dates = ["2015-01-05", "2015-01-04", "2015-01-03", "2015-01-02", "2015-01-01"]
+ expected = {
+ Timestamp(date): DatetimeIndex([date], name="date") for date in dates
+ }
+ tm.assert_dict_equal(result, expected)
+
+ grouped = df.groupby(level="date")
+ for date in dates:
+ result = grouped.get_group(date)
+ data = [[df.loc[date, "A"], df.loc[date, "B"]]]
+ expected_index = DatetimeIndex(
+ [date], name="date", freq="D", dtype=index.dtype
+ )
+ expected = DataFrame(data, columns=list("AB"), index=expected_index)
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_groups_datetimeindex_tz(self):
+ # GH 3950
+ dates = [
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ ]
+ df = DataFrame(
+ {
+ "label": ["a", "a", "a", "b", "b", "b"],
+ "datetime": dates,
+ "value1": np.arange(6, dtype="int64"),
+ "value2": [1, 2] * 3,
+ }
+ )
+ df["datetime"] = df["datetime"].apply(lambda d: Timestamp(d, tz="US/Pacific"))
+
+ exp_idx1 = DatetimeIndex(
+ [
+ "2011-07-19 07:00:00",
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ "2011-07-19 09:00:00",
+ ],
+ tz="US/Pacific",
+ name="datetime",
+ )
+ exp_idx2 = Index(["a", "b"] * 3, name="label")
+ exp_idx = MultiIndex.from_arrays([exp_idx1, exp_idx2])
+ expected = DataFrame(
+ {"value1": [0, 3, 1, 4, 2, 5], "value2": [1, 2, 2, 1, 1, 2]},
+ index=exp_idx,
+ columns=["value1", "value2"],
+ )
+
+ result = df.groupby(["datetime", "label"]).sum()
+ tm.assert_frame_equal(result, expected)
+
+ # by level
+ didx = DatetimeIndex(dates, tz="Asia/Tokyo")
+ df = DataFrame(
+ {"value1": np.arange(6, dtype="int64"), "value2": [1, 2, 3, 1, 2, 3]},
+ index=didx,
+ )
+
+ exp_idx = DatetimeIndex(
+ ["2011-07-19 07:00:00", "2011-07-19 08:00:00", "2011-07-19 09:00:00"],
+ tz="Asia/Tokyo",
+ )
+ expected = DataFrame(
+ {"value1": [3, 5, 7], "value2": [2, 4, 6]},
+ index=exp_idx,
+ columns=["value1", "value2"],
+ )
+
+ result = df.groupby(level=0).sum()
+ tm.assert_frame_equal(result, expected)
+
+ def test_frame_datetime64_handling_groupby(self):
+ # it works!
+ df = DataFrame(
+ [(3, np.datetime64("2012-07-03")), (3, np.datetime64("2012-07-04"))],
+ columns=["a", "date"],
+ )
+ result = df.groupby("a").first()
+ assert result["date"][3] == Timestamp("2012-07-03")
+
+ def test_groupby_multi_timezone(self):
+ # combining multiple / different timezones yields UTC
+ df = DataFrame(
+ {
+ "value": range(5),
+ "date": [
+ "2000-01-28 16:47:00",
+ "2000-01-29 16:48:00",
+ "2000-01-30 16:49:00",
+ "2000-01-31 16:50:00",
+ "2000-01-01 16:50:00",
+ ],
+ "tz": [
+ "America/Chicago",
+ "America/Chicago",
+ "America/Los_Angeles",
+ "America/Chicago",
+ "America/New_York",
+ ],
+ }
+ )
+
+ result = df.groupby("tz", group_keys=False).date.apply(
+ lambda x: pd.to_datetime(x).dt.tz_localize(x.name)
+ )
+
+ expected = Series(
+ [
+ Timestamp("2000-01-28 16:47:00-0600", tz="America/Chicago"),
+ Timestamp("2000-01-29 16:48:00-0600", tz="America/Chicago"),
+ Timestamp("2000-01-30 16:49:00-0800", tz="America/Los_Angeles"),
+ Timestamp("2000-01-31 16:50:00-0600", tz="America/Chicago"),
+ Timestamp("2000-01-01 16:50:00-0500", tz="America/New_York"),
+ ],
+ name="date",
+ dtype=object,
+ )
+ tm.assert_series_equal(result, expected)
+
+ tz = "America/Chicago"
+ res_values = df.groupby("tz").date.get_group(tz)
+ result = pd.to_datetime(res_values).dt.tz_localize(tz)
+ exp_values = Series(
+ ["2000-01-28 16:47:00", "2000-01-29 16:48:00", "2000-01-31 16:50:00"],
+ index=[0, 1, 3],
+ name="date",
+ )
+ expected = pd.to_datetime(exp_values).dt.tz_localize(tz)
+ tm.assert_series_equal(result, expected)
+
+ def test_groupby_groups_periods(self):
+ dates = [
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ ]
+ df = DataFrame(
+ {
+ "label": ["a", "a", "a", "b", "b", "b"],
+ "period": [pd.Period(d, freq="h") for d in dates],
+ "value1": np.arange(6, dtype="int64"),
+ "value2": [1, 2] * 3,
+ }
+ )
+
+ exp_idx1 = pd.PeriodIndex(
+ [
+ "2011-07-19 07:00:00",
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ "2011-07-19 09:00:00",
+ ],
+ freq="h",
+ name="period",
+ )
+ exp_idx2 = Index(["a", "b"] * 3, name="label")
+ exp_idx = MultiIndex.from_arrays([exp_idx1, exp_idx2])
+ expected = DataFrame(
+ {"value1": [0, 3, 1, 4, 2, 5], "value2": [1, 2, 2, 1, 1, 2]},
+ index=exp_idx,
+ columns=["value1", "value2"],
+ )
+
+ result = df.groupby(["period", "label"]).sum()
+ tm.assert_frame_equal(result, expected)
+
+ # by level
+ didx = pd.PeriodIndex(dates, freq="h")
+ df = DataFrame(
+ {"value1": np.arange(6, dtype="int64"), "value2": [1, 2, 3, 1, 2, 3]},
+ index=didx,
+ )
+
+ exp_idx = pd.PeriodIndex(
+ ["2011-07-19 07:00:00", "2011-07-19 08:00:00", "2011-07-19 09:00:00"],
+ freq="h",
+ )
+ expected = DataFrame(
+ {"value1": [3, 5, 7], "value2": [2, 4, 6]},
+ index=exp_idx,
+ columns=["value1", "value2"],
+ )
+
+ result = df.groupby(level=0).sum()
+ tm.assert_frame_equal(result, expected)
+
+ def test_groupby_first_datetime64(self):
+ df = DataFrame([(1, 1351036800000000000), (2, 1351036800000000000)])
+ df[1] = df[1].astype("M8[ns]")
+
+ assert issubclass(df[1].dtype.type, np.datetime64)
+
+ result = df.groupby(level=0).first()
+ got_dt = result[1].dtype
+ assert issubclass(got_dt.type, np.datetime64)
+
+ result = df[1].groupby(level=0).first()
+ got_dt = result.dtype
+ assert issubclass(got_dt.type, np.datetime64)
+
+ def test_groupby_max_datetime64(self):
+ # GH 5869
+ # datetimelike dtype conversion from int
+ df = DataFrame({"A": Timestamp("20130101").as_unit("s"), "B": np.arange(5)})
+ # TODO: can we retain second reso in .apply here?
+ expected = df.groupby("A")["A"].apply(lambda x: x.max()).astype("M8[s]")
+ result = df.groupby("A")["A"].max()
+ tm.assert_series_equal(result, expected)
+
+ def test_groupby_datetime64_32_bit(self):
+ # GH 6410 / numpy 4328
+ # 32-bit under 1.9-dev indexing issue
+
+ df = DataFrame({"A": range(2), "B": [Timestamp("2000-01-1")] * 2})
+ result = df.groupby("A")["B"].transform("min")
+ expected = Series([Timestamp("2000-01-1")] * 2, name="B")
+ tm.assert_series_equal(result, expected)
+
+ def test_groupby_with_timezone_selection(self):
+ # GH 11616
+ # Test that column selection returns output in correct timezone.
+
+ df = DataFrame(
+ {
+ "factor": np.random.default_rng(2).integers(0, 3, size=60),
+ "time": date_range("01/01/2000 00:00", periods=60, freq="s", tz="UTC"),
+ }
+ )
+ df1 = df.groupby("factor").max()["time"]
+ df2 = df.groupby("factor")["time"].max()
+ tm.assert_series_equal(df1, df2)
+
+ def test_timezone_info(self):
+ # see gh-11682: Timezone info lost when broadcasting
+ # scalar datetime to DataFrame
+ utc = timezone.utc
+ df = DataFrame({"a": [1], "b": [datetime.now(utc)]})
+ assert df["b"][0].tzinfo == utc
+ df = DataFrame({"a": [1, 2, 3]})
+ df["b"] = datetime.now(utc)
+ assert df["b"][0].tzinfo == utc
+
+ def test_datetime_count(self):
+ df = DataFrame(
+ {"a": [1, 2, 3] * 2, "dates": date_range("now", periods=6, freq="min")}
+ )
+ result = df.groupby("a").dates.count()
+ expected = Series([2, 2, 2], index=Index([1, 2, 3], name="a"), name="dates")
+ tm.assert_series_equal(result, expected)
+
+ def test_first_last_max_min_on_time_data(self):
+ # GH 10295
+ # Verify that NaT is not in the result of max, min, first and last on
+ # Dataframe with datetime or timedelta values.
+ df_test = DataFrame(
+ {
+ "dt": [
+ np.nan,
+ "2015-07-24 10:10",
+ "2015-07-25 11:11",
+ "2015-07-23 12:12",
+ np.nan,
+ ],
+ "td": [
+ np.nan,
+ timedelta(days=1),
+ timedelta(days=2),
+ timedelta(days=3),
+ np.nan,
+ ],
+ }
+ )
+ df_test.dt = pd.to_datetime(df_test.dt)
+ df_test["group"] = "A"
+ df_ref = df_test[df_test.dt.notna()]
+
+ grouped_test = df_test.groupby("group")
+ grouped_ref = df_ref.groupby("group")
+
+ tm.assert_frame_equal(grouped_ref.max(), grouped_test.max())
+ tm.assert_frame_equal(grouped_ref.min(), grouped_test.min())
+ tm.assert_frame_equal(grouped_ref.first(), grouped_test.first())
+ tm.assert_frame_equal(grouped_ref.last(), grouped_test.last())
+
+ def test_nunique_with_timegrouper_and_nat(self):
+ # GH 17575
+ test = DataFrame(
+ {
+ "time": [
+ Timestamp("2016-06-28 09:35:35"),
+ pd.NaT,
+ Timestamp("2016-06-28 16:46:28"),
+ ],
+ "data": ["1", "2", "3"],
+ }
+ )
+
+ grouper = Grouper(key="time", freq="h")
+ result = test.groupby(grouper)["data"].nunique()
+ expected = test[test.time.notnull()].groupby(grouper)["data"].nunique()
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+
+ def test_scalar_call_versus_list_call(self):
+ # Issue: 17530
+ data_frame = {
+ "location": ["shanghai", "beijing", "shanghai"],
+ "time": Series(
+ ["2017-08-09 13:32:23", "2017-08-11 23:23:15", "2017-08-11 22:23:15"],
+ dtype="datetime64[ns]",
+ ),
+ "value": [1, 2, 3],
+ }
+ data_frame = DataFrame(data_frame).set_index("time")
+ grouper = Grouper(freq="D")
+
+ grouped = data_frame.groupby(grouper)
+ result = grouped.count()
+ grouped = data_frame.groupby([grouper])
+ expected = grouped.count()
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_grouper_period_index(self):
+ # GH 32108
+ periods = 2
+ index = pd.period_range(
+ start="2018-01", periods=periods, freq="M", name="Month"
+ )
+ period_series = Series(range(periods), index=index)
+ result = period_series.groupby(period_series.index.month).sum()
+
+ expected = Series(
+ range(periods), index=Index(range(1, periods + 1), name=index.name)
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_groupby_apply_timegrouper_with_nat_dict_returns(
+ self, groupby_with_truncated_bingrouper
+ ):
+ # GH#43500 case where gb._grouper.result_index and gb._grouper.group_keys_seq
+ # have different lengths that goes through the `isinstance(values[0], dict)`
+ # path
+ gb = groupby_with_truncated_bingrouper
+
+ res = gb["Quantity"].apply(lambda x: {"foo": len(x)})
+
+ df = gb.obj
+ unit = df["Date"]._values.unit
+ dti = date_range("2013-09-01", "2013-10-01", freq="5D", name="Date", unit=unit)
+ mi = MultiIndex.from_arrays([dti, ["foo"] * len(dti)])
+ expected = Series([3, 0, 0, 0, 0, 0, 2], index=mi, name="Quantity")
+ tm.assert_series_equal(res, expected)
+
+ def test_groupby_apply_timegrouper_with_nat_scalar_returns(
+ self, groupby_with_truncated_bingrouper
+ ):
+ # GH#43500 Previously raised ValueError bc used index with incorrect
+ # length in wrap_applied_result
+ gb = groupby_with_truncated_bingrouper
+
+ res = gb["Quantity"].apply(lambda x: x.iloc[0] if len(x) else np.nan)
+
+ df = gb.obj
+ unit = df["Date"]._values.unit
+ dti = date_range("2013-09-01", "2013-10-01", freq="5D", name="Date", unit=unit)
+ expected = Series(
+ [18, np.nan, np.nan, np.nan, np.nan, np.nan, 5],
+ index=dti._with_freq(None),
+ name="Quantity",
+ )
+
+ tm.assert_series_equal(res, expected)
+
+ def test_groupby_apply_timegrouper_with_nat_apply_squeeze(
+ self, frame_for_truncated_bingrouper
+ ):
+ df = frame_for_truncated_bingrouper
+
+ # We need to create a GroupBy object with only one non-NaT group,
+ # so use a huge freq so that all non-NaT dates will be grouped together
+ tdg = Grouper(key="Date", freq="100YE")
+ gb = df.groupby(tdg)
+
+ # check that we will go through the singular_series path
+ # in _wrap_applied_output_series
+ assert gb.ngroups == 1
+ assert gb._selected_obj.index.nlevels == 1
+
+ # function that returns a Series
+ res = gb.apply(lambda x: x["Quantity"] * 2)
+
+ dti = Index([Timestamp("2013-12-31")], dtype=df["Date"].dtype, name="Date")
+ expected = DataFrame(
+ [[36, 6, 6, 10, 2]],
+ index=dti,
+ columns=Index([0, 1, 5, 2, 3], name="Quantity"),
+ )
+ tm.assert_frame_equal(res, expected)
+
+ @pytest.mark.single_cpu
+ def test_groupby_agg_numba_timegrouper_with_nat(
+ self, groupby_with_truncated_bingrouper
+ ):
+ pytest.importorskip("numba")
+
+ # See discussion in GH#43487
+ gb = groupby_with_truncated_bingrouper
+
+ result = gb["Quantity"].aggregate(
+ lambda values, index: np.nanmean(values), engine="numba"
+ )
+
+ expected = gb["Quantity"].aggregate("mean")
+ tm.assert_series_equal(result, expected)
+
+ result_df = gb[["Quantity"]].aggregate(
+ lambda values, index: np.nanmean(values), engine="numba"
+ )
+ expected_df = gb[["Quantity"]].aggregate("mean")
+ tm.assert_frame_equal(result_df, expected_df)
+
+ @td.skip_if_no("pyarrow")
+ def test_pyarrow_index_retention(self):
+ # https://github.com/pandas-dev/pandas/issues/63518
+ df = DataFrame(
+ {
+ "a": [1, 2, 3],
+ },
+ index=Index(
+ [
+ Timestamp("2013-01-01"),
+ Timestamp("2013-01-01"),
+ Timestamp("2013-01-02"),
+ ],
+ dtype="timestamp[ns, America/Denver][pyarrow]",
+ ),
+ )
+ gb = df.groupby(Grouper(freq="D"))
+ result = gb._grouper.result_index
+ expected = Index(
+ [Timestamp("2013-01-01"), Timestamp("2013-01-02")],
+ dtype="timestamp[ns, America/Denver][pyarrow]",
+ )
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/transform/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/transform/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/transform/test_numba.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/transform/test_numba.py
new file mode 100644
index 0000000000000000000000000000000000000000..e19b7592f75b3225044f0d30e3acd04ca69209de
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/transform/test_numba.py
@@ -0,0 +1,331 @@
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_arm
+from pandas.errors import NumbaUtilError
+
+from pandas import (
+ DataFrame,
+ Series,
+ option_context,
+)
+import pandas._testing as tm
+from pandas.util.version import Version
+
+pytestmark = [pytest.mark.single_cpu]
+
+numba = pytest.importorskip("numba")
+pytestmark.append(
+ pytest.mark.skipif(
+ Version(numba.__version__) == Version("0.61") and is_platform_arm(),
+ reason=f"Segfaults on ARM platforms with numba {numba.__version__}",
+ )
+)
+
+
+def test_correct_function_signature():
+ pytest.importorskip("numba")
+
+ def incorrect_function(x):
+ return x + 1
+
+ data = DataFrame(
+ {"key": ["a", "a", "b", "b", "a"], "data": [1.0, 2.0, 3.0, 4.0, 5.0]},
+ columns=["key", "data"],
+ )
+ with pytest.raises(NumbaUtilError, match="The first 2"):
+ data.groupby("key").transform(incorrect_function, engine="numba")
+
+ with pytest.raises(NumbaUtilError, match="The first 2"):
+ data.groupby("key")["data"].transform(incorrect_function, engine="numba")
+
+
+def test_check_nopython_kwargs():
+ pytest.importorskip("numba")
+
+ def incorrect_function(values, index, *, a):
+ return values + a
+
+ def correct_function(values, index, a):
+ return values + a
+
+ data = DataFrame(
+ {"key": ["a", "a", "b", "b", "a"], "data": [1.0, 2.0, 3.0, 4.0, 5.0]},
+ columns=["key", "data"],
+ )
+ # py signature binding
+ with pytest.raises(
+ TypeError, match="missing a required (keyword-only argument|argument): 'a'"
+ ):
+ data.groupby("key").transform(incorrect_function, engine="numba", b=1)
+ with pytest.raises(TypeError, match="missing a required argument: 'a'"):
+ data.groupby("key").transform(correct_function, engine="numba", b=1)
+
+ with pytest.raises(
+ TypeError, match="missing a required (keyword-only argument|argument): 'a'"
+ ):
+ data.groupby("key")["data"].transform(incorrect_function, engine="numba", b=1)
+ with pytest.raises(TypeError, match="missing a required argument: 'a'"):
+ data.groupby("key")["data"].transform(correct_function, engine="numba", b=1)
+
+ # numba signature check after binding
+ with pytest.raises(NumbaUtilError, match="numba does not support"):
+ data.groupby("key").transform(incorrect_function, engine="numba", a=1)
+ actual = data.groupby("key").transform(correct_function, engine="numba", a=1)
+ tm.assert_frame_equal(data[["data"]] + 1, actual)
+
+ with pytest.raises(NumbaUtilError, match="numba does not support"):
+ data.groupby("key")["data"].transform(incorrect_function, engine="numba", a=1)
+ actual = data.groupby("key")["data"].transform(
+ correct_function, engine="numba", a=1
+ )
+ tm.assert_series_equal(data["data"] + 1, actual)
+
+
+@pytest.mark.filterwarnings("ignore")
+# Filter warnings when parallel=True and the function can't be parallelized by Numba
+@pytest.mark.parametrize("jit", [True, False])
+def test_numba_vs_cython(jit, frame_or_series, nogil, parallel, nopython, as_index):
+ pytest.importorskip("numba")
+
+ def func(values, index):
+ return values + 1
+
+ if jit:
+ # Test accepted jitted functions
+ import numba
+
+ func = numba.jit(func)
+
+ data = DataFrame(
+ {0: ["a", "a", "b", "b", "a"], 1: [1.0, 2.0, 3.0, 4.0, 5.0]}, columns=[0, 1]
+ )
+ engine_kwargs = {"nogil": nogil, "parallel": parallel, "nopython": nopython}
+ grouped = data.groupby(0, as_index=as_index)
+ if frame_or_series is Series:
+ grouped = grouped[1]
+
+ result = grouped.transform(func, engine="numba", engine_kwargs=engine_kwargs)
+ expected = grouped.transform(lambda x: x + 1, engine="cython")
+
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.filterwarnings("ignore")
+# Filter warnings when parallel=True and the function can't be parallelized by Numba
+@pytest.mark.parametrize("jit", [True, False])
+def test_cache(jit, frame_or_series, nogil, parallel, nopython):
+ # Test that the functions are cached correctly if we switch functions
+ pytest.importorskip("numba")
+
+ def func_1(values, index):
+ return values + 1
+
+ def func_2(values, index):
+ return values * 5
+
+ if jit:
+ import numba
+
+ func_1 = numba.jit(func_1)
+ func_2 = numba.jit(func_2)
+
+ data = DataFrame(
+ {0: ["a", "a", "b", "b", "a"], 1: [1.0, 2.0, 3.0, 4.0, 5.0]}, columns=[0, 1]
+ )
+ engine_kwargs = {"nogil": nogil, "parallel": parallel, "nopython": nopython}
+ grouped = data.groupby(0)
+ if frame_or_series is Series:
+ grouped = grouped[1]
+
+ result = grouped.transform(func_1, engine="numba", engine_kwargs=engine_kwargs)
+ expected = grouped.transform(lambda x: x + 1, engine="cython")
+ tm.assert_equal(result, expected)
+
+ result = grouped.transform(func_2, engine="numba", engine_kwargs=engine_kwargs)
+ expected = grouped.transform(lambda x: x * 5, engine="cython")
+ tm.assert_equal(result, expected)
+
+ # Retest func_1 which should use the cache
+ result = grouped.transform(func_1, engine="numba", engine_kwargs=engine_kwargs)
+ expected = grouped.transform(lambda x: x + 1, engine="cython")
+ tm.assert_equal(result, expected)
+
+
+def test_use_global_config():
+ pytest.importorskip("numba")
+
+ def func_1(values, index):
+ return values + 1
+
+ data = DataFrame(
+ {0: ["a", "a", "b", "b", "a"], 1: [1.0, 2.0, 3.0, 4.0, 5.0]}, columns=[0, 1]
+ )
+ grouped = data.groupby(0)
+ expected = grouped.transform(func_1, engine="numba")
+ with option_context("compute.use_numba", True):
+ result = grouped.transform(func_1, engine=None)
+ tm.assert_frame_equal(expected, result)
+
+
+# TODO: Test more than just reductions (e.g. actually test transformations once we have
+@pytest.mark.parametrize(
+ "agg_func", [["min", "max"], "min", {"B": ["min", "max"], "C": "sum"}]
+)
+def test_string_cython_vs_numba(agg_func, numba_supported_reductions):
+ pytest.importorskip("numba")
+ agg_func, kwargs = numba_supported_reductions
+ data = DataFrame(
+ {0: ["a", "a", "b", "b", "a"], 1: [1.0, 2.0, 3.0, 4.0, 5.0]}, columns=[0, 1]
+ )
+ grouped = data.groupby(0)
+
+ result = grouped.transform(agg_func, engine="numba", **kwargs)
+ expected = grouped.transform(agg_func, engine="cython", **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+ result = grouped[1].transform(agg_func, engine="numba", **kwargs)
+ expected = grouped[1].transform(agg_func, engine="cython", **kwargs)
+ tm.assert_series_equal(result, expected)
+
+
+def test_args_not_cached():
+ # GH 41647
+ pytest.importorskip("numba")
+
+ def sum_last(values, index, n):
+ return values[-n:].sum()
+
+ df = DataFrame({"id": [0, 0, 1, 1], "x": [1, 1, 1, 1]})
+ grouped_x = df.groupby("id")["x"]
+ result = grouped_x.transform(sum_last, 1, engine="numba")
+ expected = Series([1.0] * 4, name="x")
+ tm.assert_series_equal(result, expected)
+
+ result = grouped_x.transform(sum_last, 2, engine="numba")
+ expected = Series([2.0] * 4, name="x")
+ tm.assert_series_equal(result, expected)
+
+
+def test_index_data_correctly_passed():
+ # GH 43133
+ pytest.importorskip("numba")
+
+ def f(values, index):
+ return index - 1
+
+ df = DataFrame({"group": ["A", "A", "B"], "v": [4, 5, 6]}, index=[-1, -2, -3])
+ result = df.groupby("group").transform(f, engine="numba")
+ expected = DataFrame([-2.0, -3.0, -4.0], columns=["v"], index=[-1, -2, -3])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_index_order_consistency_preserved():
+ # GH 57069
+ pytest.importorskip("numba")
+
+ def f(values, index):
+ return values
+
+ df = DataFrame(
+ {"vals": [0.0, 1.0, 2.0, 3.0], "group": [0, 1, 0, 1]}, index=range(3, -1, -1)
+ )
+ result = df.groupby("group")["vals"].transform(f, engine="numba")
+ expected = Series([0.0, 1.0, 2.0, 3.0], index=range(3, -1, -1), name="vals")
+ tm.assert_series_equal(result, expected)
+
+
+def test_engine_kwargs_not_cached():
+ # If the user passes a different set of engine_kwargs don't return the same
+ # jitted function
+ pytest.importorskip("numba")
+ nogil = True
+ parallel = False
+ nopython = True
+
+ def func_kwargs(values, index):
+ return nogil + parallel + nopython
+
+ engine_kwargs = {"nopython": nopython, "nogil": nogil, "parallel": parallel}
+ df = DataFrame({"value": [0, 0, 0]})
+ result = df.groupby(level=0).transform(
+ func_kwargs, engine="numba", engine_kwargs=engine_kwargs
+ )
+ expected = DataFrame({"value": [2.0, 2.0, 2.0]})
+ tm.assert_frame_equal(result, expected)
+
+ nogil = False
+ engine_kwargs = {"nopython": nopython, "nogil": nogil, "parallel": parallel}
+ result = df.groupby(level=0).transform(
+ func_kwargs, engine="numba", engine_kwargs=engine_kwargs
+ )
+ expected = DataFrame({"value": [1.0, 1.0, 1.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.filterwarnings("ignore")
+def test_multiindex_one_key(nogil, parallel, nopython):
+ pytest.importorskip("numba")
+
+ def numba_func(values, index):
+ return 1
+
+ df = DataFrame([{"A": 1, "B": 2, "C": 3}]).set_index(["A", "B"])
+ engine_kwargs = {"nopython": nopython, "nogil": nogil, "parallel": parallel}
+ result = df.groupby("A").transform(
+ numba_func, engine="numba", engine_kwargs=engine_kwargs
+ )
+ expected = DataFrame([{"A": 1, "B": 2, "C": 1.0}]).set_index(["A", "B"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_multiindex_multi_key_not_supported(nogil, parallel, nopython):
+ pytest.importorskip("numba")
+
+ def numba_func(values, index):
+ return 1
+
+ df = DataFrame([{"A": 1, "B": 2, "C": 3}]).set_index(["A", "B"])
+ engine_kwargs = {"nopython": nopython, "nogil": nogil, "parallel": parallel}
+ with pytest.raises(NotImplementedError, match="more than 1 grouping labels"):
+ df.groupby(["A", "B"]).transform(
+ numba_func, engine="numba", engine_kwargs=engine_kwargs
+ )
+
+
+def test_multilabel_numba_vs_cython(numba_supported_reductions):
+ pytest.importorskip("numba")
+ reduction, kwargs = numba_supported_reductions
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+ gb = df.groupby(["A", "B"])
+ res_agg = gb.transform(reduction, engine="numba", **kwargs)
+ expected_agg = gb.transform(reduction, engine="cython", **kwargs)
+ tm.assert_frame_equal(res_agg, expected_agg)
+
+
+def test_multilabel_udf_numba_vs_cython():
+ pytest.importorskip("numba")
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+ gb = df.groupby(["A", "B"])
+ result = gb.transform(
+ lambda values, index: (values - values.min()) / (values.max() - values.min()),
+ engine="numba",
+ )
+ expected = gb.transform(
+ lambda x: (x - x.min()) / (x.max() - x.min()), engine="cython"
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/transform/test_transform.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/transform/test_transform.py
new file mode 100644
index 0000000000000000000000000000000000000000..59aab02042e75fb996d1ab4530422fa1a6916ad4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/groupby/transform/test_transform.py
@@ -0,0 +1,1600 @@
+"""test with the .transform"""
+
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.common import ensure_platform_int
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ concat,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.tests.groupby import get_groupby_method_args
+
+
+def assert_fp_equal(a, b):
+ assert (np.abs(a - b) < 1e-12).all()
+
+
+def test_transform():
+ data = Series(np.arange(9) // 3, index=np.arange(9))
+
+ index = np.arange(9)
+ np.random.default_rng(2).shuffle(index)
+ data = data.reindex(index)
+
+ grouped = data.groupby(lambda x: x // 3)
+
+ transformed = grouped.transform(lambda x: x * x.sum())
+ assert transformed[7] == 12
+
+ # GH 8046
+ # make sure that we preserve the input order
+
+ df = DataFrame(
+ np.arange(6, dtype="int64").reshape(3, 2), columns=["a", "b"], index=[0, 2, 1]
+ )
+ key = [0, 0, 1]
+ expected = (
+ df.sort_index()
+ .groupby(key)
+ .transform(lambda x: x - x.mean())
+ .groupby(key)
+ .mean()
+ )
+ result = df.groupby(key).transform(lambda x: x - x.mean()).groupby(key).mean()
+ tm.assert_frame_equal(result, expected)
+
+ def demean(arr):
+ return arr - arr.mean(axis=0)
+
+ people = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 5)),
+ columns=["a", "b", "c", "d", "e"],
+ index=["Joe", "Steve", "Wes", "Jim", "Travis"],
+ )
+ key = ["one", "two", "one", "two", "one"]
+ result = people.groupby(key).transform(demean).groupby(key).mean()
+ expected = people.groupby(key, group_keys=False).apply(demean).groupby(key).mean()
+ tm.assert_frame_equal(result, expected)
+
+ # GH 8430
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((50, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=50, freq="B"),
+ )
+ g = df.groupby(pd.Grouper(freq="ME"))
+ g.transform(lambda x: x - 1)
+
+ # GH 9700
+ df = DataFrame({"a": range(5, 10), "b": range(5)})
+ result = df.groupby("a").transform(max)
+ expected = DataFrame({"b": range(5)})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_fast():
+ df = DataFrame(
+ {
+ "id": np.arange(10) / 3,
+ "val": np.random.default_rng(2).standard_normal(10),
+ }
+ )
+
+ grp = df.groupby("id")["val"]
+
+ values = np.repeat(grp.mean().values, ensure_platform_int(grp.count().values))
+ expected = Series(values, index=df.index, name="val")
+
+ result = grp.transform(np.mean)
+ tm.assert_series_equal(result, expected)
+
+ result = grp.transform("mean")
+ tm.assert_series_equal(result, expected)
+
+
+def test_transform_fast2():
+ # GH 12737
+ df = DataFrame(
+ {
+ "grouping": [0, 1, 1, 3],
+ "f": [1.1, 2.1, 3.1, 4.5],
+ "d": date_range("2014-1-1", "2014-1-4", unit="ns"),
+ "i": [1, 2, 3, 4],
+ },
+ columns=["grouping", "f", "i", "d"],
+ )
+ result = df.groupby("grouping").transform("first")
+
+ dates = Index(
+ [
+ Timestamp("2014-1-1"),
+ Timestamp("2014-1-2"),
+ Timestamp("2014-1-2"),
+ Timestamp("2014-1-4"),
+ ],
+ dtype="M8[ns]",
+ )
+ expected = DataFrame(
+ {"f": [1.1, 2.1, 2.1, 4.5], "d": dates, "i": [1, 2, 2, 4]},
+ columns=["f", "i", "d"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # selection
+ result = df.groupby("grouping")[["f", "i"]].transform("first")
+ expected = expected[["f", "i"]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_fast3():
+ # dup columns
+ df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=["g", "a", "a"])
+ result = df.groupby("g").transform("first")
+ expected = df.drop("g", axis=1)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_broadcast(tsframe, ts):
+ grouped = ts.groupby(lambda x: x.month)
+ result = grouped.transform(np.mean)
+
+ tm.assert_index_equal(result.index, ts.index)
+ for _, gp in grouped:
+ assert_fp_equal(result.reindex(gp.index), gp.mean())
+
+ grouped = tsframe.groupby(lambda x: x.month)
+ result = grouped.transform(np.mean)
+ tm.assert_index_equal(result.index, tsframe.index)
+ for _, gp in grouped:
+ agged = gp.mean(axis=0)
+ res = result.reindex(gp.index)
+ for col in tsframe:
+ assert_fp_equal(res[col], agged[col])
+
+
+def test_transform_axis_ts(tsframe):
+ # make sure that we are setting the axes correctly
+ # in the presence of a non-monotonic indexer
+ # GH12713
+
+ base = tsframe.iloc[0:5]
+ r = len(base.index)
+ c = len(base.columns)
+ tso = DataFrame(
+ np.random.default_rng(2).standard_normal((r, c)),
+ index=base.index,
+ columns=base.columns,
+ dtype="float64",
+ )
+ # monotonic
+ ts = tso
+ grouped = ts.groupby(lambda x: x.weekday(), group_keys=False)
+ result = ts - grouped.transform("mean")
+ expected = grouped.apply(lambda x: x - x.mean(axis=0))
+ tm.assert_frame_equal(result, expected)
+
+ # non-monotonic
+ ts = tso.iloc[[1, 0, *list(range(2, len(base)))]]
+ grouped = ts.groupby(lambda x: x.weekday(), group_keys=False)
+ result = ts - grouped.transform("mean")
+ expected = grouped.apply(lambda x: x - x.mean(axis=0))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_dtype():
+ # GH 9807
+ # Check transform dtype output is preserved
+ df = DataFrame([[1, 3], [2, 3]])
+ result = df.groupby(1).transform("mean")
+ expected = DataFrame([[1.5], [1.5]])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_bug():
+ # GH 5712
+ # transforming on a datetime column
+ df = DataFrame({"A": Timestamp("20130101"), "B": np.arange(5)})
+ result = df.groupby("A")["B"].transform(lambda x: x.rank(ascending=False))
+ expected = Series(np.arange(5, 0, step=-1), name="B", dtype="float64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_transform_numeric_to_boolean():
+ # GH 16875
+ # inconsistency in transforming boolean values
+ expected = Series([True, True], name="A")
+
+ df = DataFrame({"A": [1.1, 2.2], "B": [1, 2]})
+ result = df.groupby("B").A.transform(lambda x: True)
+ tm.assert_series_equal(result, expected)
+
+ df = DataFrame({"A": [1, 2], "B": [1, 2]})
+ result = df.groupby("B").A.transform(lambda x: True)
+ tm.assert_series_equal(result, expected)
+
+
+def test_transform_datetime_to_timedelta():
+ # GH 15429
+ # transforming a datetime to timedelta
+ df = DataFrame({"A": Timestamp("20130101"), "B": np.arange(5)})
+ expected = Series(
+ Timestamp("20130101") - Timestamp("20130101"), index=range(5), name="A"
+ )
+
+ # this does date math without changing result type in transform
+ base_time = df["A"][0]
+ result = (
+ df.groupby("A")["A"].transform(lambda x: x.max() - x.min() + base_time)
+ - base_time
+ )
+ tm.assert_series_equal(result, expected)
+
+ # this does date math and causes the transform to return timedelta
+ result = df.groupby("A")["A"].transform(lambda x: x.max() - x.min())
+ tm.assert_series_equal(result, expected)
+
+
+def test_transform_datetime_to_numeric():
+ # GH 10972
+ # convert dt to float
+ df = DataFrame({"a": 1, "b": date_range("2015-01-01", periods=2, freq="D")})
+ result = df.groupby("a").b.transform(
+ lambda x: x.dt.dayofweek - x.dt.dayofweek.mean()
+ )
+
+ expected = Series([-0.5, 0.5], name="b")
+ tm.assert_series_equal(result, expected)
+
+ # convert dt to int
+ df = DataFrame({"a": 1, "b": date_range("2015-01-01", periods=2, freq="D")})
+ result = df.groupby("a").b.transform(
+ lambda x: x.dt.dayofweek - x.dt.dayofweek.min()
+ )
+
+ expected = Series([0, 1], dtype=np.int32, name="b")
+ tm.assert_series_equal(result, expected)
+
+
+def test_transform_casting():
+ # 13046
+ times = [
+ "13:43:27",
+ "14:26:19",
+ "14:29:01",
+ "18:39:34",
+ "18:40:18",
+ "18:44:30",
+ "18:46:00",
+ "18:52:15",
+ "18:59:59",
+ "19:17:48",
+ "19:21:38",
+ ]
+ df = DataFrame(
+ {
+ "A": [f"B-{i}" for i in range(11)],
+ "ID3": np.take(
+ ["a", "b", "c", "d", "e"], [0, 1, 2, 1, 3, 1, 1, 1, 4, 1, 1]
+ ),
+ "DATETIME": pd.to_datetime([f"2014-10-08 {time}" for time in times]),
+ },
+ index=pd.RangeIndex(11, name="idx"),
+ )
+
+ result = df.groupby("ID3")["DATETIME"].transform(lambda x: x.diff())
+ assert lib.is_np_dtype(result.dtype, "m")
+
+ result = df[["ID3", "DATETIME"]].groupby("ID3").transform(lambda x: x.diff())
+ assert lib.is_np_dtype(result.DATETIME.dtype, "m")
+
+
+def test_transform_multiple(ts):
+ grouped = ts.groupby([lambda x: x.year, lambda x: x.month])
+ grouped.transform(lambda x: x * 2)
+ grouped.transform(np.mean)
+
+
+def test_dispatch_transform(tsframe):
+ df = tsframe[::5].reindex(tsframe.index)
+
+ grouped = df.groupby(lambda x: x.month)
+
+ filled = grouped.ffill()
+ fillit = lambda x: x.ffill()
+ expected = df.groupby(lambda x: x.month).transform(fillit)
+ tm.assert_frame_equal(filled, expected)
+
+
+def test_transform_transformation_func(transformation_func):
+ # GH 30918
+ df = DataFrame(
+ {
+ "A": ["foo", "foo", "foo", "foo", "bar", "bar", "baz"],
+ "B": [1, 2, np.nan, 3, 3, np.nan, 4],
+ },
+ index=date_range("2020-01-01", "2020-01-07"),
+ )
+ if transformation_func == "cumcount":
+ test_op = lambda x: x.transform("cumcount")
+ mock_op = lambda x: Series(range(len(x)), x.index)
+ elif transformation_func == "ngroup":
+ test_op = lambda x: x.transform("ngroup")
+ counter = -1
+
+ def mock_op(x):
+ nonlocal counter
+ counter += 1
+ return Series(counter, index=x.index)
+
+ else:
+ test_op = lambda x: x.transform(transformation_func)
+ mock_op = lambda x: getattr(x, transformation_func)()
+
+ result = test_op(df.groupby("A"))
+
+ # pass the group in same order as iterating `for ... in df.groupby(...)`
+ # but reorder to match df's index since this is a transform
+ groups = [df[["B"]].iloc[4:6], df[["B"]].iloc[6:], df[["B"]].iloc[:4]]
+ expected = concat([mock_op(g) for g in groups]).sort_index()
+ # sort_index does not preserve the freq
+ expected = expected.set_axis(df.index)
+
+ if transformation_func in ("cumcount", "ngroup"):
+ tm.assert_series_equal(result, expected)
+ else:
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_select_columns(df):
+ f = lambda x: x.mean()
+ result = df.groupby("A")[["C", "D"]].transform(f)
+
+ selection = df[["C", "D"]]
+ expected = selection.groupby(df["A"]).transform(f)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_nuisance_raises(df, using_infer_string):
+ # case that goes through _transform_item_by_item
+
+ df.columns = ["A", "B", "B", "D"]
+
+ # this also tests orderings in transform between
+ # series/frame to make sure it's consistent
+ grouped = df.groupby("A")
+
+ gbc = grouped["B"]
+ msg = "Could not convert"
+ if using_infer_string:
+ msg = "Cannot perform reduction 'mean' with string dtype"
+ with pytest.raises(TypeError, match=msg):
+ gbc.transform(lambda x: np.mean(x))
+
+ with pytest.raises(TypeError, match=msg):
+ df.groupby("A").transform(lambda x: np.mean(x))
+
+
+def test_transform_function_aliases(df):
+ result = df.groupby("A").transform("mean", numeric_only=True)
+ expected = df.groupby("A")[["C", "D"]].transform(np.mean)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby("A")["C"].transform("mean")
+ expected = df.groupby("A")["C"].transform(np.mean)
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_fast_transform_date():
+ # GH 13191
+ df = DataFrame(
+ {
+ "grouping": [np.nan, 1, 1, 3],
+ "d": date_range("2014-1-1", "2014-1-4", unit="ns"),
+ }
+ )
+ result = df.groupby("grouping")["d"].transform("first")
+ dates = [
+ pd.NaT,
+ Timestamp("2014-1-2"),
+ Timestamp("2014-1-2"),
+ Timestamp("2014-1-4"),
+ ]
+ expected = Series(dates, name="d", dtype="M8[ns]")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", [lambda x: np.nansum(x), sum])
+def test_transform_length(func):
+ # GH 9697
+ df = DataFrame({"col1": [1, 1, 2, 2], "col2": [1, 2, 3, np.nan]})
+ if func is sum:
+ expected = Series([3.0, 3.0, np.nan, np.nan])
+ else:
+ expected = Series([3.0] * 4)
+
+ results = [
+ df.groupby("col1").transform(func)["col2"],
+ df.groupby("col1")["col2"].transform(func),
+ ]
+ for result in results:
+ tm.assert_series_equal(result, expected, check_names=False)
+
+
+def test_transform_coercion():
+ # 14457
+ # when we are transforming be sure to not coerce
+ # via assignment
+ df = DataFrame({"A": ["a", "a", "b", "b"], "B": [0, 1, 3, 4]})
+ g = df.groupby("A")
+
+ expected = g.transform(np.mean)
+ result = g.transform(lambda x: np.mean(x, axis=0))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_transform_with_int(using_infer_string):
+ # GH 3740, make sure that we might upcast on item-by-item transform
+
+ # floats
+ df = DataFrame(
+ {
+ "A": [1, 1, 1, 2, 2, 2],
+ "B": Series(1, dtype="float64"),
+ "C": Series([1, 2, 3, 1, 2, 3], dtype="float64"),
+ "D": "foo",
+ }
+ )
+ with np.errstate(all="ignore"):
+ result = df.groupby("A")[["B", "C"]].transform(
+ lambda x: (x - x.mean()) / x.std()
+ )
+ expected = DataFrame(
+ {"B": np.nan, "C": Series([-1, 0, 1, -1, 0, 1], dtype="float64")}
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # int case
+ df = DataFrame(
+ {
+ "A": [1, 1, 1, 2, 2, 2],
+ "B": 1,
+ "C": [1, 2, 3, 1, 2, 3],
+ "D": "foo",
+ }
+ )
+ msg = "Could not convert"
+ if using_infer_string:
+ msg = "Cannot perform reduction 'mean' with string dtype"
+ with np.errstate(all="ignore"):
+ with pytest.raises(TypeError, match=msg):
+ df.groupby("A").transform(lambda x: (x - x.mean()) / x.std())
+ result = df.groupby("A")[["B", "C"]].transform(
+ lambda x: (x - x.mean()) / x.std()
+ )
+ expected = DataFrame({"B": np.nan, "C": [-1.0, 0.0, 1.0, -1.0, 0.0, 1.0]})
+ tm.assert_frame_equal(result, expected)
+
+ # int that needs float conversion
+ s = Series([2, 3, 4, 10, 5, -1])
+ df = DataFrame({"A": [1, 1, 1, 2, 2, 2], "B": 1, "C": s, "D": "foo"})
+ with np.errstate(all="ignore"):
+ with pytest.raises(TypeError, match=msg):
+ df.groupby("A").transform(lambda x: (x - x.mean()) / x.std())
+ result = df.groupby("A")[["B", "C"]].transform(
+ lambda x: (x - x.mean()) / x.std()
+ )
+
+ s1 = s.iloc[0:3]
+ s1 = (s1 - s1.mean()) / s1.std()
+ s2 = s.iloc[3:6]
+ s2 = (s2 - s2.mean()) / s2.std()
+ expected = DataFrame({"B": np.nan, "C": concat([s1, s2])})
+ tm.assert_frame_equal(result, expected)
+
+ # int doesn't get downcasted
+ result = df.groupby("A")[["B", "C"]].transform(lambda x: x * 2 / 2)
+ expected = DataFrame({"B": 1.0, "C": [2.0, 3.0, 4.0, 10.0, 5.0, -1.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_transform_with_nan_group():
+ # GH 9941
+ df = DataFrame({"a": range(10), "b": [1, 1, 2, 3, np.nan, 4, 4, 5, 5, 5]})
+ result = df.groupby(df.b)["a"].transform(max)
+ expected = Series([1.0, 1.0, 2.0, 3.0, np.nan, 6.0, 6.0, 9.0, 9.0, 9.0], name="a")
+ tm.assert_series_equal(result, expected)
+
+
+def test_transform_mixed_type():
+ index = MultiIndex.from_arrays([[0, 0, 0, 1, 1, 1], [1, 2, 3, 1, 2, 3]])
+ df = DataFrame(
+ {
+ "d": [1.0, 1.0, 1.0, 2.0, 2.0, 2.0],
+ "c": np.tile(["a", "b", "c"], 2),
+ "v": np.arange(1.0, 7.0),
+ },
+ index=index,
+ )
+
+ def f(group):
+ group["g"] = group["d"] * 2
+ return group[:1]
+
+ grouped = df.groupby("c")
+ result = grouped.apply(f)
+
+ assert result["d"].dtype == np.float64
+
+ # this is by definition a mutating operation!
+ for key, group in grouped:
+ res = f(group.drop(columns="c"))
+ tm.assert_frame_equal(res, result.loc[key])
+
+
+@pytest.mark.parametrize(
+ "op, args, targop",
+ [
+ ("cumprod", (), lambda x: x.cumprod()),
+ ("cumsum", (), lambda x: x.cumsum()),
+ ("shift", (-1,), lambda x: x.shift(-1)),
+ ("shift", (1,), lambda x: x.shift()),
+ ],
+)
+def test_cython_transform_series(op, args, targop):
+ # GH 4095
+ s = Series(np.random.default_rng(2).standard_normal(1000))
+ s_missing = s.copy()
+ s_missing.iloc[2:10] = np.nan
+ labels = np.random.default_rng(2).integers(0, 50, size=1000).astype(float)
+
+ # series
+ for data in [s, s_missing]:
+ # print(data.head())
+ expected = data.groupby(labels).transform(targop)
+
+ tm.assert_series_equal(expected, data.groupby(labels).transform(op, *args))
+ tm.assert_series_equal(expected, getattr(data.groupby(labels), op)(*args))
+
+
+@pytest.mark.parametrize("op", ["cumprod", "cumsum"])
+@pytest.mark.parametrize(
+ "input, exp",
+ [
+ # When everything is NaN
+ ({"key": ["b"] * 10, "value": np.nan}, Series([np.nan] * 10, name="value")),
+ # When there is a single NaN
+ (
+ {"key": ["b"] * 10 + ["a"] * 2, "value": [3] * 3 + [np.nan] + [3] * 8},
+ {
+ ("cumprod", False): [3.0, 9.0, 27.0] + [np.nan] * 7 + [3.0, 9.0],
+ ("cumprod", True): [
+ 3.0,
+ 9.0,
+ 27.0,
+ np.nan,
+ 81.0,
+ 243.0,
+ 729.0,
+ 2187.0,
+ 6561.0,
+ 19683.0,
+ 3.0,
+ 9.0,
+ ],
+ ("cumsum", False): [3.0, 6.0, 9.0] + [np.nan] * 7 + [3.0, 6.0],
+ ("cumsum", True): [
+ 3.0,
+ 6.0,
+ 9.0,
+ np.nan,
+ 12.0,
+ 15.0,
+ 18.0,
+ 21.0,
+ 24.0,
+ 27.0,
+ 3.0,
+ 6.0,
+ ],
+ },
+ ),
+ ],
+)
+def test_groupby_cum_skipna(op, skipna, input, exp):
+ df = DataFrame(input)
+ result = df.groupby("key")["value"].transform(op, skipna=skipna)
+ if isinstance(exp, dict):
+ expected = exp[(op, skipna)]
+ else:
+ expected = exp
+ expected = Series(expected, name="value")
+ tm.assert_series_equal(expected, result)
+
+
+@pytest.fixture
+def frame():
+ floating = Series(np.random.default_rng(2).standard_normal(10))
+ floating_missing = floating.copy()
+ floating_missing.iloc[2:7] = np.nan
+ strings = list("abcde") * 2
+ strings_missing = strings[:]
+ strings_missing[5] = np.nan
+
+ df = DataFrame(
+ {
+ "float": floating,
+ "float_missing": floating_missing,
+ "int": [1, 1, 1, 1, 2] * 2,
+ "datetime": date_range("1990-1-1", periods=10),
+ "timedelta": pd.timedelta_range(1, freq="s", periods=10),
+ "string": strings,
+ "string_missing": strings_missing,
+ "cat": Categorical(strings),
+ },
+ )
+ return df
+
+
+@pytest.fixture
+def frame_mi(frame):
+ frame.index = MultiIndex.from_product([range(5), range(2)])
+ return frame
+
+
+@pytest.mark.slow
+@pytest.mark.parametrize(
+ "op, args, targop",
+ [
+ ("cumprod", (), lambda x: x.cumprod()),
+ ("cumsum", (), lambda x: x.cumsum()),
+ ("shift", (-1,), lambda x: x.shift(-1)),
+ ("shift", (1,), lambda x: x.shift()),
+ ],
+)
+@pytest.mark.parametrize("df_fix", ["frame", "frame_mi"])
+@pytest.mark.parametrize(
+ "gb_target",
+ [
+ {"by": np.random.default_rng(2).integers(0, 50, size=10).astype(float)},
+ {"level": 0},
+ {"by": "string"},
+ pytest.param({"by": "string_missing"}, marks=pytest.mark.xfail),
+ {"by": ["int", "string"]},
+ ],
+)
+def test_cython_transform_frame(request, op, args, targop, df_fix, gb_target):
+ df = request.getfixturevalue(df_fix)
+ gb = df.groupby(group_keys=False, **gb_target)
+
+ if op != "shift" and "int" not in gb_target:
+ # numeric apply fastpath promotes dtype so have
+ # to apply separately and concat
+ i = gb[["int"]].apply(targop)
+ f = gb[["float", "float_missing"]].apply(targop)
+ expected = concat([f, i], axis=1)
+ else:
+ expected = gb.apply(targop)
+
+ expected = expected.sort_index(axis=1)
+ if op == "shift":
+ expected["string_missing"] = expected["string_missing"].fillna(np.nan)
+ by = gb_target.get("by")
+ if not isinstance(by, (str, list)) or (by != "string" and "string" not in by):
+ expected["string"] = expected["string"].fillna(np.nan)
+
+ result = gb[expected.columns].transform(op, *args).sort_index(axis=1)
+ tm.assert_frame_equal(result, expected)
+ result = getattr(gb[expected.columns], op)(*args).sort_index(axis=1)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.slow
+@pytest.mark.parametrize(
+ "op, args, targop",
+ [
+ ("cumprod", (), lambda x: x.cumprod()),
+ ("cumsum", (), lambda x: x.cumsum()),
+ ("shift", (-1,), lambda x: x.shift(-1)),
+ ("shift", (1,), lambda x: x.shift()),
+ ],
+)
+@pytest.mark.parametrize("df_fix", ["frame", "frame_mi"])
+@pytest.mark.parametrize(
+ "gb_target",
+ [
+ {"by": np.random.default_rng(2).integers(0, 50, size=10).astype(float)},
+ {"level": 0},
+ {"by": "string"},
+ # TODO: create xfail condition given other params
+ # {"by": 'string_missing'},
+ {"by": ["int", "string"]},
+ ],
+)
+@pytest.mark.parametrize(
+ "column",
+ [
+ "float",
+ "float_missing",
+ "int",
+ "datetime",
+ "timedelta",
+ "string",
+ "string_missing",
+ ],
+)
+def test_cython_transform_frame_column(
+ request, op, args, targop, df_fix, gb_target, column
+):
+ df = request.getfixturevalue(df_fix)
+ gb = df.groupby(group_keys=False, **gb_target)
+ c = column
+ if (
+ c not in ["float", "int", "float_missing"]
+ and op != "shift"
+ and not (c == "timedelta" and op == "cumsum")
+ ):
+ msg = "|".join(
+ [
+ "does not support .* operations",
+ "does not support operation",
+ ".* is not supported for object dtype",
+ "is not implemented for this dtype",
+ ".* is not supported for str dtype",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ gb[c].transform(op)
+ with pytest.raises(TypeError, match=msg):
+ getattr(gb[c], op)()
+ else:
+ expected = gb[c].apply(targop)
+ expected.name = c
+ if c in ["string_missing", "string"]:
+ expected = expected.fillna(np.nan)
+
+ res = gb[c].transform(op, *args)
+ tm.assert_series_equal(expected, res)
+ res2 = getattr(gb[c], op)(*args)
+ tm.assert_series_equal(expected, res2)
+
+
+@pytest.mark.parametrize(
+ "cols,expected",
+ [
+ ("a", Series([1, 1, 1], name="a")),
+ (
+ ["a", "c"],
+ DataFrame({"a": [1, 1, 1], "c": [1, 1, 1]}),
+ ),
+ ],
+)
+@pytest.mark.parametrize("agg_func", ["count", "rank", "size"])
+def test_transform_numeric_ret(cols, expected, agg_func):
+ # GH#19200 and GH#27469
+ df = DataFrame(
+ {"a": date_range("2018-01-01", periods=3), "b": range(3), "c": range(7, 10)}
+ )
+ result = df.groupby("b")[cols].transform(agg_func)
+
+ if agg_func == "rank":
+ expected = expected.astype("float")
+ elif agg_func == "size" and cols == ["a", "c"]:
+ # transform("size") returns a Series
+ expected = expected["a"].rename(None)
+ tm.assert_equal(result, expected)
+
+
+def test_transform_ffill():
+ # GH 24211
+ data = [["a", 0.0], ["a", float("nan")], ["b", 1.0], ["b", float("nan")]]
+ df = DataFrame(data, columns=["key", "values"])
+ result = df.groupby("key").transform("ffill")
+ expected = DataFrame({"values": [0.0, 0.0, 1.0, 1.0]})
+ tm.assert_frame_equal(result, expected)
+ result = df.groupby("key")["values"].transform("ffill")
+ expected = Series([0.0, 0.0, 1.0, 1.0], name="values")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("mix_groupings", [True, False])
+@pytest.mark.parametrize("as_series", [True, False])
+@pytest.mark.parametrize("val1,val2", [("foo", "bar"), (1, 2), (1.0, 2.0)])
+@pytest.mark.parametrize(
+ "fill_method,limit,exp_vals",
+ [
+ (
+ "ffill",
+ None,
+ [np.nan, np.nan, "val1", "val1", "val1", "val2", "val2", "val2"],
+ ),
+ ("ffill", 1, [np.nan, np.nan, "val1", "val1", np.nan, "val2", "val2", np.nan]),
+ (
+ "bfill",
+ None,
+ ["val1", "val1", "val1", "val2", "val2", "val2", np.nan, np.nan],
+ ),
+ ("bfill", 1, [np.nan, "val1", "val1", np.nan, "val2", "val2", np.nan, np.nan]),
+ ],
+)
+def test_group_fill_methods(
+ mix_groupings, as_series, val1, val2, fill_method, limit, exp_vals
+):
+ vals = [np.nan, np.nan, val1, np.nan, np.nan, val2, np.nan, np.nan]
+ _exp_vals = list(exp_vals)
+ # Overwrite placeholder values
+ for index, exp_val in enumerate(_exp_vals):
+ if exp_val == "val1":
+ _exp_vals[index] = val1
+ elif exp_val == "val2":
+ _exp_vals[index] = val2
+
+ # Need to modify values and expectations depending on the
+ # Series / DataFrame that we ultimately want to generate
+ if mix_groupings: # ['a', 'b', 'a, 'b', ...]
+ keys = ["a", "b"] * len(vals)
+
+ def interweave(list_obj):
+ temp = []
+ for x in list_obj:
+ temp.extend([x, x])
+
+ return temp
+
+ _exp_vals = interweave(_exp_vals)
+ vals = interweave(vals)
+ else: # ['a', 'a', 'a', ... 'b', 'b', 'b']
+ keys = ["a"] * len(vals) + ["b"] * len(vals)
+ _exp_vals = _exp_vals * 2
+ vals = vals * 2
+
+ df = DataFrame({"key": keys, "val": vals})
+ if as_series:
+ result = getattr(df.groupby("key")["val"], fill_method)(limit=limit)
+ exp = Series(_exp_vals, name="val")
+ tm.assert_series_equal(result, exp)
+ else:
+ result = getattr(df.groupby("key"), fill_method)(limit=limit)
+ exp = DataFrame({"val": _exp_vals})
+ tm.assert_frame_equal(result, exp)
+
+
+@pytest.mark.parametrize("fill_method", ["ffill", "bfill"])
+def test_pad_stable_sorting(fill_method):
+ # GH 21207
+ x = [0] * 20
+ y = [np.nan] * 10 + [1] * 10
+
+ if fill_method == "bfill":
+ y = y[::-1]
+
+ df = DataFrame({"x": x, "y": y})
+ expected = df.drop("x", axis=1)
+
+ result = getattr(df.groupby("x"), fill_method)()
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "freq",
+ [
+ None,
+ pytest.param(
+ "D",
+ marks=pytest.mark.xfail(
+ reason="GH#23918 before method uses freq in vectorized approach"
+ ),
+ ),
+ ],
+)
+@pytest.mark.parametrize("periods", [1, -1])
+def test_pct_change(frame_or_series, freq, periods):
+ # GH 21200, 21621, 30463
+ vals = [3, np.nan, np.nan, np.nan, 1, 2, 4, 10, np.nan, 4]
+ keys = ["a", "b"]
+ key_v = np.repeat(keys, len(vals))
+ df = DataFrame({"key": key_v, "vals": vals * 2})
+
+ df_g = df
+ grp = df_g.groupby(df.key)
+
+ expected = grp["vals"].obj / grp["vals"].shift(periods) - 1
+
+ gb = df.groupby("key")
+
+ if frame_or_series is Series:
+ gb = gb["vals"]
+ else:
+ expected = expected.to_frame("vals")
+
+ result = gb.pct_change(periods=periods, freq=freq)
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func, expected_status",
+ [
+ ("ffill", ["shrt", "shrt", "lng", np.nan, "shrt", "ntrl", "ntrl"]),
+ ("bfill", ["shrt", "lng", "lng", "shrt", "shrt", "ntrl", np.nan]),
+ ],
+)
+def test_ffill_bfill_non_unique_multilevel(func, expected_status):
+ # GH 19437
+ date = pd.to_datetime(
+ [
+ "2018-01-01",
+ "2018-01-01",
+ "2018-01-01",
+ "2018-01-01",
+ "2018-01-02",
+ "2018-01-01",
+ "2018-01-02",
+ ]
+ )
+ symbol = ["MSFT", "MSFT", "MSFT", "AAPL", "AAPL", "TSLA", "TSLA"]
+ status = ["shrt", np.nan, "lng", np.nan, "shrt", "ntrl", np.nan]
+
+ df = DataFrame({"date": date, "symbol": symbol, "status": status})
+ df = df.set_index(["date", "symbol"])
+ result = getattr(df.groupby("symbol")["status"], func)()
+
+ index = MultiIndex.from_tuples(
+ tuples=list(zip(*[date, symbol], strict=True)), names=["date", "symbol"]
+ )
+ expected = Series(expected_status, index=index, name="status")
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", [np.any, np.all])
+def test_any_all_np_func(func):
+ # GH 20653
+ df = DataFrame(
+ [["foo", True], [np.nan, True], ["foo", True]], columns=["key", "val"]
+ )
+
+ exp = Series([True, np.nan, True], name="val")
+
+ res = df.groupby("key")["val"].transform(func)
+ tm.assert_series_equal(res, exp)
+
+
+def test_groupby_transform_rename():
+ # https://github.com/pandas-dev/pandas/issues/23461
+ def demean_rename(x):
+ result = x - x.mean()
+
+ if isinstance(x, Series):
+ return result
+
+ result = result.rename(columns={c: f"{c}_demeaned" for c in result.columns})
+
+ return result
+
+ df = DataFrame({"group": list("ababa"), "value": [1, 1, 1, 2, 2]})
+ expected = DataFrame({"value": [-1.0 / 3, -0.5, -1.0 / 3, 0.5, 2.0 / 3]})
+
+ result = df.groupby("group").transform(demean_rename)
+ tm.assert_frame_equal(result, expected)
+ result_single = df.groupby("group").value.transform(demean_rename)
+ tm.assert_series_equal(result_single, expected["value"])
+
+
+@pytest.mark.parametrize("func", [min, max, np.min, np.max, "first", "last"])
+def test_groupby_transform_timezone_column(func):
+ # GH 24198
+ ts = pd.to_datetime("now", utc=True).tz_convert("Asia/Singapore")
+ result = DataFrame({"end_time": [ts], "id": [1]})
+ result["max_end_time"] = result.groupby("id").end_time.transform(func)
+ expected = DataFrame([[ts, 1, ts]], columns=["end_time", "id", "max_end_time"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func, values",
+ [
+ ("idxmin", ["1/1/2011"] * 2 + ["1/3/2011"] * 7 + ["1/10/2011"]),
+ ("idxmax", ["1/2/2011"] * 2 + ["1/9/2011"] * 7 + ["1/10/2011"]),
+ ],
+)
+def test_groupby_transform_with_datetimes(func, values):
+ # GH 15306
+ dates = date_range("1/1/2011", periods=10, freq="D", unit="ns")
+
+ stocks = DataFrame({"price": np.arange(10.0)}, index=dates)
+ stocks["week_id"] = dates.isocalendar().week
+
+ result = stocks.groupby(stocks["week_id"])["price"].transform(func)
+
+ expected = Series(
+ data=pd.to_datetime(values).as_unit("ns"), index=dates, name="price"
+ )
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_transform_dtype():
+ # GH 22243
+ df = DataFrame({"a": [1], "val": [1.35]})
+
+ result = df["val"].transform(lambda x: x.map(lambda y: f"+{y}"))
+ expected1 = Series(["+1.35"], name="val")
+ tm.assert_series_equal(result, expected1)
+
+ result = df.groupby("a")["val"].transform(lambda x: x.map(lambda y: f"+{y}"))
+ tm.assert_series_equal(result, expected1)
+
+ result = df.groupby("a")["val"].transform(lambda x: x.map(lambda y: f"+({y})"))
+ expected2 = Series(["+(1.35)"], name="val")
+ tm.assert_series_equal(result, expected2)
+
+ df["val"] = df["val"].astype(object)
+ result = df.groupby("a")["val"].transform(lambda x: x.map(lambda y: f"+{y}"))
+ tm.assert_series_equal(result, expected1)
+
+
+def test_transform_absent_categories(all_numeric_accumulations):
+ # GH 16771
+ # cython transforms with more groups than rows
+ x_vals = [1]
+ x_cats = range(2)
+ y = [1]
+ df = DataFrame({"x": Categorical(x_vals, x_cats), "y": y})
+ result = getattr(df.y.groupby(df.x, observed=False), all_numeric_accumulations)()
+ expected = df.y
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["ffill", "bfill", "shift"])
+@pytest.mark.parametrize("key, val", [("level", 0), ("by", Series([0]))])
+def test_ffill_not_in_axis(func, key, val):
+ # GH 21521
+ df = DataFrame([[np.nan]])
+ result = getattr(df.groupby(**{key: val}), func)()
+ expected = df
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_invalid_name_raises():
+ # GH#27486
+ df = DataFrame({"a": [0, 1, 1, 2]})
+ g = df.groupby(["a", "b", "b", "c"])
+ with pytest.raises(ValueError, match="not a valid function name"):
+ g.transform("some_arbitrary_name")
+
+ # method exists on the object, but is not a valid transformation/agg
+ assert hasattr(g, "aggregate") # make sure the method exists
+ with pytest.raises(ValueError, match="not a valid function name"):
+ g.transform("aggregate")
+
+ # Test SeriesGroupBy
+ g = df["a"].groupby(["a", "b", "b", "c"])
+ with pytest.raises(ValueError, match="not a valid function name"):
+ g.transform("some_arbitrary_name")
+
+
+def test_transform_agg_by_name(request, reduction_func, frame_or_series):
+ func = reduction_func
+
+ obj = DataFrame(
+ {"a": [0, 0, 0, 0, 1, 1, 1, 1], "b": range(8)},
+ index=["A", "B", "C", "D", "E", "F", "G", "H"],
+ )
+ if frame_or_series is Series:
+ obj = obj["a"]
+
+ g = obj.groupby(np.repeat([0, 1], 4))
+
+ if func == "corrwith" and isinstance(obj, Series): # GH#32293
+ # TODO: implement SeriesGroupBy.corrwith
+ assert not hasattr(g, func)
+ return
+
+ args = get_groupby_method_args(reduction_func, obj)
+ if func == "corrwith":
+ warn = Pandas4Warning
+ msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ msg = ""
+ with tm.assert_produces_warning(warn, match=msg):
+ result = g.transform(func, *args)
+
+ # this is the *definition* of a transformation
+ tm.assert_index_equal(result.index, obj.index)
+
+ if func not in ("ngroup", "size") and obj.ndim == 2:
+ # size/ngroup return a Series, unlike other transforms
+ tm.assert_index_equal(result.columns, obj.columns)
+
+ # verify that values were broadcasted across each group
+ assert len(set(DataFrame(result).iloc[-4:, -1])) == 1
+
+
+def test_transform_lambda_with_datetimetz():
+ # GH 27496
+ df = DataFrame(
+ {
+ "time": [
+ Timestamp("2010-07-15 03:14:45"),
+ Timestamp("2010-11-19 18:47:06"),
+ ],
+ "timezone": ["Etc/GMT+4", "US/Eastern"],
+ }
+ )
+ result = df.groupby(["timezone"])["time"].transform(
+ lambda x: x.dt.tz_localize(x.name)
+ )
+ expected = Series(
+ [
+ Timestamp("2010-07-15 03:14:45", tz="Etc/GMT+4"),
+ Timestamp("2010-11-19 18:47:06", tz="US/Eastern"),
+ ],
+ name="time",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_transform_fastpath_raises():
+ # GH#29631 case where fastpath defined in groupby.generic _choose_path
+ # raises, but slow_path does not
+
+ df = DataFrame({"A": [1, 1, 2, 2], "B": [1, -1, 1, 2]})
+ gb = df.groupby("A")
+
+ def func(grp):
+ # we want a function such that func(frame) fails but func.apply(frame)
+ # works
+ if grp.ndim == 2:
+ # Ensure that fast_path fails
+ raise NotImplementedError("Don't cross the streams")
+ return grp * 2
+
+ # Check that the fastpath raises, see _transform_general
+ obj = gb._obj_with_exclusions
+ gen = gb._grouper.get_iterator(obj)
+ fast_path, slow_path = gb._define_paths(func)
+ _, group = next(gen)
+
+ with pytest.raises(NotImplementedError, match="Don't cross the streams"):
+ fast_path(group)
+
+ result = gb.transform(func)
+
+ expected = DataFrame([2, -2, 2, 4], columns=["B"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_lambda_indexing():
+ # GH 7883
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "flux", "foo", "flux"],
+ "B": ["one", "one", "two", "three", "two", "six", "five", "three"],
+ "C": range(8),
+ "D": range(8),
+ "E": range(8),
+ }
+ )
+ df = df.set_index(["A", "B"])
+ df = df.sort_index()
+ result = df.groupby(level="A").transform(lambda x: x.iloc[-1])
+ expected = DataFrame(
+ {
+ "C": [3, 3, 7, 7, 4, 4, 4, 4],
+ "D": [3, 3, 7, 7, 4, 4, 4, 4],
+ "E": [3, 3, 7, 7, 4, 4, 4, 4],
+ },
+ index=MultiIndex.from_tuples(
+ [
+ ("bar", "one"),
+ ("bar", "three"),
+ ("flux", "six"),
+ ("flux", "three"),
+ ("foo", "five"),
+ ("foo", "one"),
+ ("foo", "two"),
+ ("foo", "two"),
+ ],
+ names=["A", "B"],
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_and_not_categorical_key(observed):
+ # Checks that groupby-transform, when grouping by both a categorical
+ # and a non-categorical key, doesn't try to expand the output to include
+ # non-observed categories but instead matches the input shape.
+ # GH 32494
+ df_with_categorical = DataFrame(
+ {
+ "A": Categorical(["a", "b", "a"], categories=["a", "b", "c"]),
+ "B": [1, 2, 3],
+ "C": ["a", "b", "a"],
+ }
+ )
+ df_without_categorical = DataFrame(
+ {"A": ["a", "b", "a"], "B": [1, 2, 3], "C": ["a", "b", "a"]}
+ )
+
+ # DataFrame case
+ result = df_with_categorical.groupby(["A", "C"], observed=observed).transform("sum")
+ expected = df_without_categorical.groupby(["A", "C"]).transform("sum")
+ tm.assert_frame_equal(result, expected)
+ expected_explicit = DataFrame({"B": [4, 2, 4]})
+ tm.assert_frame_equal(result, expected_explicit)
+
+ # Series case
+ gb = df_with_categorical.groupby(["A", "C"], observed=observed)
+ gbp = gb["B"]
+ result = gbp.transform("sum")
+ expected = df_without_categorical.groupby(["A", "C"])["B"].transform("sum")
+ tm.assert_series_equal(result, expected)
+ expected_explicit = Series([4, 2, 4], name="B")
+ tm.assert_series_equal(result, expected_explicit)
+
+
+def test_string_rank_grouping():
+ # GH 19354
+ df = DataFrame({"A": [1, 1, 2], "B": [1, 2, 3]})
+ result = df.groupby("A").transform("rank")
+ expected = DataFrame({"B": [1.0, 2.0, 1.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_cumcount():
+ # GH 27472
+ df = DataFrame({"a": [0, 0, 0, 1, 1, 1], "b": range(6)})
+ grp = df.groupby(np.repeat([0, 1], 3))
+
+ result = grp.cumcount()
+ expected = Series([0, 1, 2, 0, 1, 2])
+ tm.assert_series_equal(result, expected)
+
+ result = grp.transform("cumcount")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("keys", [["A1"], ["A1", "A2"]])
+def test_null_group_lambda_self(sort, dropna, keys):
+ # GH 17093
+ size = 50
+ nulls1 = np.random.default_rng(2).choice([False, True], size)
+ nulls2 = np.random.default_rng(2).choice([False, True], size)
+ # Whether a group contains a null value or not
+ nulls_grouper = nulls1 if len(keys) == 1 else nulls1 | nulls2
+
+ a1 = np.random.default_rng(2).integers(0, 5, size=size).astype(float)
+ a1[nulls1] = np.nan
+ a2 = np.random.default_rng(2).integers(0, 5, size=size).astype(float)
+ a2[nulls2] = np.nan
+ values = np.random.default_rng(2).integers(0, 5, size=a1.shape)
+ df = DataFrame({"A1": a1, "A2": a2, "B": values})
+
+ expected_values = values
+ if dropna and nulls_grouper.any():
+ expected_values = expected_values.astype(float)
+ expected_values[nulls_grouper] = np.nan
+ expected = DataFrame(expected_values, columns=["B"])
+
+ gb = df.groupby(keys, dropna=dropna, sort=sort)
+ result = gb[["B"]].transform(lambda x: x)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_null_group_str_reducer(request, dropna, reduction_func):
+ # GH 17093
+ if reduction_func == "corrwith":
+ msg = "incorrectly raises"
+ request.applymarker(pytest.mark.xfail(reason=msg))
+
+ index = [1, 2, 3, 4] # test transform preserves non-standard index
+ df = DataFrame({"A": [1, 1, np.nan, np.nan], "B": [1, 2, 2, 3]}, index=index)
+ gb = df.groupby("A", dropna=dropna)
+
+ args = get_groupby_method_args(reduction_func, df)
+
+ # Manually handle reducers that don't fit the generic pattern
+ # Set expected with dropna=False, then replace if necessary
+ if reduction_func == "first":
+ expected = DataFrame({"B": [1, 1, 2, 2]}, index=index)
+ elif reduction_func == "last":
+ expected = DataFrame({"B": [2, 2, 3, 3]}, index=index)
+ elif reduction_func == "nth":
+ expected = DataFrame({"B": [1, 1, 2, 2]}, index=index)
+ elif reduction_func == "size":
+ expected = Series([2, 2, 2, 2], index=index)
+ elif reduction_func == "corrwith":
+ expected = DataFrame({"B": [1.0, 1.0, 1.0, 1.0]}, index=index)
+ else:
+ expected_gb = df.groupby("A", dropna=False)
+ buffer = []
+ for idx, group in expected_gb:
+ res = getattr(group["B"], reduction_func)()
+ buffer.append(Series(res, index=group.index))
+ expected = concat(buffer).to_frame("B")
+ if dropna:
+ dtype = object if reduction_func in ("any", "all") else float
+ expected = expected.astype(dtype)
+ if expected.ndim == 2:
+ expected.iloc[[2, 3], 0] = np.nan
+ else:
+ expected.iloc[[2, 3]] = np.nan
+
+ result = gb.transform(reduction_func, *args)
+ tm.assert_equal(result, expected)
+
+
+def test_null_group_str_transformer(dropna, transformation_func):
+ # GH 17093
+ df = DataFrame({"A": [1, 1, np.nan], "B": [1, 2, 2]}, index=[1, 2, 3])
+ args = get_groupby_method_args(transformation_func, df)
+ gb = df.groupby("A", dropna=dropna)
+
+ buffer = []
+ for k, (idx, group) in enumerate(gb):
+ if transformation_func == "cumcount":
+ # DataFrame has no cumcount method
+ res = DataFrame({"B": range(len(group))}, index=group.index)
+ elif transformation_func == "ngroup":
+ res = DataFrame(len(group) * [k], index=group.index, columns=["B"])
+ else:
+ res = getattr(group[["B"]], transformation_func)(*args)
+ buffer.append(res)
+ if dropna:
+ dtype = object if transformation_func in ("any", "all") else None
+ buffer.append(DataFrame([[np.nan]], index=[3], dtype=dtype, columns=["B"]))
+ expected = concat(buffer)
+
+ if transformation_func in ("cumcount", "ngroup"):
+ # ngroup/cumcount always returns a Series as it counts the groups, not values
+ expected = expected["B"].rename(None)
+
+ result = gb.transform(transformation_func, *args)
+ tm.assert_equal(result, expected)
+
+
+def test_null_group_str_reducer_series(request, dropna, reduction_func):
+ # GH 17093
+ index = [1, 2, 3, 4] # test transform preserves non-standard index
+ ser = Series([1, 2, 2, 3], index=index)
+ gb = ser.groupby([1, 1, np.nan, np.nan], dropna=dropna)
+
+ if reduction_func == "corrwith":
+ # corrwith not implemented for SeriesGroupBy
+ assert not hasattr(gb, reduction_func)
+ return
+
+ args = get_groupby_method_args(reduction_func, ser)
+
+ # Manually handle reducers that don't fit the generic pattern
+ # Set expected with dropna=False, then replace if necessary
+ if reduction_func == "first":
+ expected = Series([1, 1, 2, 2], index=index)
+ elif reduction_func == "last":
+ expected = Series([2, 2, 3, 3], index=index)
+ elif reduction_func == "nth":
+ expected = Series([1, 1, 2, 2], index=index)
+ elif reduction_func == "size":
+ expected = Series([2, 2, 2, 2], index=index)
+ elif reduction_func == "corrwith":
+ expected = Series([1, 1, 2, 2], index=index)
+ else:
+ expected_gb = ser.groupby([1, 1, np.nan, np.nan], dropna=False)
+ buffer = []
+ for idx, group in expected_gb:
+ res = getattr(group, reduction_func)()
+ buffer.append(Series(res, index=group.index))
+ expected = concat(buffer)
+ if dropna:
+ dtype = object if reduction_func in ("any", "all") else float
+ expected = expected.astype(dtype)
+ expected.iloc[[2, 3]] = np.nan
+
+ result = gb.transform(reduction_func, *args)
+ tm.assert_series_equal(result, expected)
+
+
+def test_null_group_str_transformer_series(dropna, transformation_func):
+ # GH 17093
+ ser = Series([1, 2, 2], index=[1, 2, 3])
+ args = get_groupby_method_args(transformation_func, ser)
+ gb = ser.groupby([1, 1, np.nan], dropna=dropna)
+
+ buffer = []
+ for k, (idx, group) in enumerate(gb):
+ if transformation_func == "cumcount":
+ # Series has no cumcount method
+ res = Series(range(len(group)), index=group.index)
+ elif transformation_func == "ngroup":
+ res = Series(k, index=group.index)
+ else:
+ res = getattr(group, transformation_func)(*args)
+ buffer.append(res)
+ if dropna:
+ dtype = object if transformation_func in ("any", "all") else None
+ buffer.append(Series([np.nan], index=[3], dtype=dtype))
+ expected = concat(buffer)
+ result = gb.transform(transformation_func, *args)
+
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func, expected_values",
+ [
+ (Series.sort_values, [5, 4, 3, 2, 1]),
+ (lambda x: x.head(1), [5.0, np.nan, 3, 2, np.nan]),
+ ],
+)
+@pytest.mark.parametrize("keys", [["a1"], ["a1", "a2"]])
+@pytest.mark.parametrize("keys_in_index", [True, False])
+def test_transform_aligns(func, frame_or_series, expected_values, keys, keys_in_index):
+ # GH#45648 - transform should align with the input's index
+ df = DataFrame({"a1": [1, 1, 3, 2, 2], "b": [5, 4, 3, 2, 1]})
+ if "a2" in keys:
+ df["a2"] = df["a1"]
+ if keys_in_index:
+ df = df.set_index(keys, append=True)
+
+ gb = df.groupby(keys)
+ if frame_or_series is Series:
+ gb = gb["b"]
+
+ result = gb.transform(func)
+ expected = DataFrame({"b": expected_values}, index=df.index)
+ if frame_or_series is Series:
+ expected = expected["b"]
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize("keys", ["A", ["A", "B"]])
+def test_as_index_no_change(keys, df, groupby_func):
+ # GH#49834 - as_index should have no impact on DataFrameGroupBy.transform
+ if keys == "A":
+ # Column B is string dtype; will fail on some ops
+ df = df.drop(columns="B")
+ args = get_groupby_method_args(groupby_func, df)
+ gb_as_index_true = df.groupby(keys, as_index=True)
+ gb_as_index_false = df.groupby(keys, as_index=False)
+ if groupby_func == "corrwith":
+ warn = Pandas4Warning
+ msg = "DataFrameGroupBy.corrwith is deprecated"
+ else:
+ warn = None
+ msg = ""
+ with tm.assert_produces_warning(warn, match=msg):
+ result = gb_as_index_true.transform(groupby_func, *args)
+ with tm.assert_produces_warning(warn, match=msg):
+ expected = gb_as_index_false.transform(groupby_func, *args)
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize("how", ["idxmax", "idxmin"])
+@pytest.mark.parametrize("numeric_only", [True, False])
+def test_idxmin_idxmax_transform_args(how, skipna, numeric_only):
+ # GH#55268 - ensure *args are passed through when calling transform
+ df = DataFrame({"a": [1, 1, 1, 2], "b": [3.0, 4.0, np.nan, 6.0], "c": list("abcd")})
+ gb = df.groupby("a")
+ if skipna:
+ result = gb.transform(how, skipna, numeric_only)
+ expected = gb.transform(how, skipna=skipna, numeric_only=numeric_only)
+ tm.assert_frame_equal(result, expected)
+ else:
+ msg = f"{how} with skipna=False encountered an NA value"
+ with pytest.raises(ValueError, match=msg):
+ gb.transform(how, skipna, numeric_only)
+
+
+def test_transform_sum_one_column_no_matching_labels():
+ df = DataFrame({"X": [1.0]})
+ series = Series(["Y"])
+ result = df.groupby(series, as_index=False).transform("sum")
+ expected = DataFrame({"X": [1.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_sum_no_matching_labels():
+ df = DataFrame({"X": [1.0, -93204, 4935]})
+ series = Series(["A", "B", "C"])
+
+ result = df.groupby(series, as_index=False).transform("sum")
+ expected = DataFrame({"X": [1.0, -93204, 4935]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_sum_one_column_with_matching_labels():
+ df = DataFrame({"X": [1.0, -93204, 4935]})
+ series = Series(["A", "B", "A"])
+
+ result = df.groupby(series, as_index=False).transform("sum")
+ expected = DataFrame({"X": [4936.0, -93204, 4936.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_sum_one_column_with_missing_labels():
+ df = DataFrame({"X": [1.0, -93204, 4935]})
+ series = Series(["A", "C"])
+
+ result = df.groupby(series, as_index=False).transform("sum")
+ expected = DataFrame({"X": [1.0, -93204, np.nan]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_transform_sum_one_column_with_matching_labels_and_missing_labels():
+ df = DataFrame({"X": [1.0, -93204, 4935]})
+ series = Series(["A", "A"])
+
+ result = df.groupby(series, as_index=False).transform("sum")
+ expected = DataFrame({"X": [-93203.0, -93203.0, np.nan]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["int32", "float32"])
+def test_min_one_unobserved_category_no_type_coercion(dtype):
+ # GH#58084
+ df = DataFrame({"A": Categorical([1, 1, 2], categories=[1, 2, 3]), "B": [3, 4, 5]})
+ df["B"] = df["B"].astype(dtype)
+ gb = df.groupby("A", observed=False)
+ result = gb.transform("min")
+
+ expected = DataFrame({"B": [3, 3, 5]}, dtype=dtype)
+ tm.assert_frame_equal(expected, result)
+
+
+def test_min_all_empty_data_no_type_coercion():
+ # GH#58084
+ df = DataFrame(
+ {
+ "X": Categorical(
+ [],
+ categories=[1, "randomcat", 100],
+ ),
+ "Y": [],
+ }
+ )
+ df["Y"] = df["Y"].astype("int32")
+
+ gb = df.groupby("X", observed=False)
+ result = gb.transform("min")
+
+ expected = DataFrame({"Y": []}, dtype="int32")
+ tm.assert_frame_equal(expected, result)
+
+
+def test_min_one_dim_no_type_coercion():
+ # GH#58084
+ df = DataFrame({"Y": [9435, -5465765, 5055, 0, 954960]})
+ df["Y"] = df["Y"].astype("int32")
+ categories = Categorical([1, 2, 2, 5, 1], categories=[1, 2, 3, 4, 5])
+
+ gb = df.groupby(categories, observed=False)
+ result = gb.transform("min")
+
+ expected = DataFrame({"Y": [9435, -5465765, -5465765, 0, 9435]}, dtype="int32")
+ tm.assert_frame_equal(expected, result)
+
+
+def test_nan_in_cumsum_group_label():
+ # GH#58811
+ df = DataFrame({"A": [1, None], "B": [2, 3]}, dtype="Int16")
+ gb = df.groupby("A")["B"]
+ result = gb.cumsum()
+ expected = Series([2, None], dtype="Int16", name="B")
+ tm.assert_series_equal(expected, result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..0896b97e8a40e56f5c005bf113cddb3a28ab72b4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_constructors.py
@@ -0,0 +1,71 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestIndexConstructor:
+ # Tests for the Index constructor, specifically for cases that do
+ # not return a subclass
+
+ @pytest.mark.parametrize("value", [1, np.int64(1)])
+ def test_constructor_corner(self, value):
+ # corner case
+ msg = (
+ r"Index\(\.\.\.\) must be called with a collection of some "
+ f"kind, {value} was passed"
+ )
+ with pytest.raises(TypeError, match=msg):
+ Index(value)
+
+ @pytest.mark.parametrize("index_vals", [[("A", 1), "B"], ["B", ("A", 1)]])
+ def test_construction_list_mixed_tuples(self, index_vals):
+ # see gh-10697: if we are constructing from a mixed list of tuples,
+ # make sure that we are independent of the sorting order.
+ index = Index(index_vals)
+ assert isinstance(index, Index)
+ assert not isinstance(index, MultiIndex)
+
+ def test_constructor_cast(self):
+ msg = "could not convert string to float"
+ with pytest.raises(ValueError, match=msg):
+ Index(["a", "b", "c"], dtype=float)
+
+ @pytest.mark.parametrize("tuple_list", [[()], [(), ()]])
+ def test_construct_empty_tuples(self, tuple_list):
+ # GH #45608
+ result = Index(tuple_list)
+ expected = MultiIndex.from_tuples(tuple_list)
+
+ tm.assert_index_equal(result, expected)
+
+ def test_index_string_inference(self):
+ # GH#54430
+ expected = Index(["a", "b"], dtype=pd.StringDtype(na_value=np.nan))
+ with pd.option_context("future.infer_string", True):
+ ser = Index(["a", "b"])
+ tm.assert_index_equal(ser, expected)
+
+ expected = Index(["a", 1], dtype="object")
+ with pd.option_context("future.infer_string", True):
+ ser = Index(["a", 1])
+ tm.assert_index_equal(ser, expected)
+
+ @pytest.mark.parametrize("klass", [Series, Index])
+ def test_inference_on_pandas_objects(self, klass):
+ # GH#56012
+ obj = klass([pd.Timestamp("2019-12-31")], dtype=object)
+ result = Index(obj)
+ assert result.dtype == np.object_
+
+ def test_constructor_not_read_only(self):
+ # GH#57130
+ ser = Series([1, 2], dtype=object)
+ idx = Index(ser)
+ assert idx._values.flags.writeable
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_formats.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_formats.py
new file mode 100644
index 0000000000000000000000000000000000000000..2368b8bce2d9ebb324a4cc361499204dcdd0e41d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_formats.py
@@ -0,0 +1,154 @@
+import numpy as np
+import pytest
+
+import pandas._config.config as cf
+
+from pandas import Index
+import pandas._testing as tm
+
+
+class TestIndexRendering:
+ def test_repr_is_valid_construction_code(self):
+ # for the case of Index, where the repr is traditional rather than
+ # stylized
+ idx = Index(["a", "b"])
+ res = eval(repr(idx))
+ tm.assert_index_equal(res, idx)
+
+ @pytest.mark.parametrize(
+ "index,expected",
+ [
+ # ASCII
+ # short
+ (
+ Index(["a", "bb", "ccc"]),
+ """Index(['a', 'bb', 'ccc'], dtype='object')""",
+ ),
+ # multiple lines
+ (
+ Index(["a", "bb", "ccc"] * 10),
+ "Index(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', "
+ "'bb', 'ccc', 'a', 'bb', 'ccc',\n"
+ " 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', "
+ "'bb', 'ccc', 'a', 'bb', 'ccc',\n"
+ " 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],\n"
+ " dtype='object')",
+ ),
+ # truncated
+ (
+ Index(["a", "bb", "ccc"] * 100),
+ "Index(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',\n"
+ " ...\n"
+ " 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],\n"
+ " dtype='object', length=300)",
+ ),
+ # Non-ASCII
+ # short
+ (
+ Index(["あ", "いい", "ううう"]),
+ """Index(['あ', 'いい', 'ううう'], dtype='object')""",
+ ),
+ # multiple lines
+ (
+ Index(["あ", "いい", "ううう"] * 10),
+ (
+ "Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', "
+ "'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',\n"
+ " 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', "
+ "'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',\n"
+ " 'あ', 'いい', 'ううう', 'あ', 'いい', "
+ "'ううう'],\n"
+ " dtype='object')"
+ ),
+ ),
+ # truncated
+ (
+ Index(["あ", "いい", "ううう"] * 100),
+ (
+ "Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', "
+ "'あ', 'いい', 'ううう', 'あ',\n"
+ " ...\n"
+ " 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', "
+ "'ううう', 'あ', 'いい', 'ううう'],\n"
+ " dtype='object', length=300)"
+ ),
+ ),
+ ],
+ )
+ def test_string_index_repr(self, index, expected, using_infer_string):
+ result = repr(index)
+ if using_infer_string:
+ expected = expected.replace("dtype='object'", "dtype='str'")
+
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "index,expected",
+ [
+ # short
+ (
+ Index(["あ", "いい", "ううう"]),
+ ("Index(['あ', 'いい', 'ううう'], dtype='object')"),
+ ),
+ # multiple lines
+ (
+ Index(["あ", "いい", "ううう"] * 10),
+ (
+ "Index(['あ', 'いい', 'ううう', 'あ', 'いい', "
+ "'ううう', 'あ', 'いい', 'ううう',\n"
+ " 'あ', 'いい', 'ううう', 'あ', 'いい', "
+ "'ううう', 'あ', 'いい', 'ううう',\n"
+ " 'あ', 'いい', 'ううう', 'あ', 'いい', "
+ "'ううう', 'あ', 'いい', 'ううう',\n"
+ " 'あ', 'いい', 'ううう'],\n"
+ " dtype='object')"
+ ""
+ ),
+ ),
+ # truncated
+ (
+ Index(["あ", "いい", "ううう"] * 100),
+ (
+ "Index(['あ', 'いい', 'ううう', 'あ', 'いい', "
+ "'ううう', 'あ', 'いい', 'ううう',\n"
+ " 'あ',\n"
+ " ...\n"
+ " 'ううう', 'あ', 'いい', 'ううう', 'あ', "
+ "'いい', 'ううう', 'あ', 'いい',\n"
+ " 'ううう'],\n"
+ " dtype='object', length=300)"
+ ),
+ ),
+ ],
+ )
+ def test_string_index_repr_with_unicode_option(
+ self, index, expected, using_infer_string
+ ):
+ # Enable Unicode option -----------------------------------------
+ with cf.option_context("display.unicode.east_asian_width", True):
+ result = repr(index)
+
+ if using_infer_string:
+ expected = expected.replace("dtype='object'", "dtype='str'")
+ assert result == expected
+
+ def test_repr_summary(self):
+ with cf.option_context("display.max_seq_items", 10):
+ result = repr(Index(np.arange(1000)))
+ assert len(result) < 200
+ assert "..." in result
+
+ def test_summary_bug(self):
+ # GH#3869
+ ind = Index(["{other}%s", "~:{range}:0"], name="A")
+ result = ind._summary()
+ # shouldn't be formatted accidentally.
+ assert "~:{range}:0" in result
+ assert "{other}%s" in result
+
+ def test_index_repr_bool_nan(self):
+ # GH32146
+ arr = Index([True, False, np.nan], dtype=object)
+ exp2 = repr(arr)
+ out2 = "Index([True, False, nan], dtype='object')"
+ assert out2 == exp2
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..2988fa7d1baa1e0bc0f6cc4b6dc32e5d12f332cf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_indexing.py
@@ -0,0 +1,104 @@
+import numpy as np
+import pytest
+
+from pandas._libs import index as libindex
+
+import pandas as pd
+from pandas import (
+ Index,
+ NaT,
+)
+import pandas._testing as tm
+
+
+class TestGetSliceBounds:
+ @pytest.mark.parametrize("side, expected", [("left", 4), ("right", 5)])
+ def test_get_slice_bounds_within(self, side, expected):
+ index = Index(list("abcdef"))
+ result = index.get_slice_bound("e", side=side)
+ assert result == expected
+
+ @pytest.mark.parametrize("side", ["left", "right"])
+ @pytest.mark.parametrize(
+ "data, bound, expected", [(list("abcdef"), "x", 6), (list("bcdefg"), "a", 0)]
+ )
+ def test_get_slice_bounds_outside(self, side, expected, data, bound):
+ index = Index(data)
+ result = index.get_slice_bound(bound, side=side)
+ assert result == expected
+
+ def test_get_slice_bounds_invalid_side(self):
+ with pytest.raises(ValueError, match="Invalid value for side kwarg"):
+ Index([]).get_slice_bound("a", side="middle")
+
+
+class TestGetIndexerNonUnique:
+ def test_get_indexer_non_unique_dtype_mismatch(self):
+ # GH#25459
+ indexes, missing = Index(["A", "B"]).get_indexer_non_unique(Index([0]))
+ tm.assert_numpy_array_equal(np.array([-1], dtype=np.intp), indexes)
+ tm.assert_numpy_array_equal(np.array([0], dtype=np.intp), missing)
+
+ @pytest.mark.parametrize(
+ "idx_values,idx_non_unique",
+ [
+ ([np.nan, 100, 200, 100], [np.nan, 100]),
+ ([np.nan, 100.0, 200.0, 100.0], [np.nan, 100.0]),
+ ],
+ )
+ def test_get_indexer_non_unique_int_index(self, idx_values, idx_non_unique):
+ indexes, missing = Index(idx_values).get_indexer_non_unique(Index([np.nan]))
+ tm.assert_numpy_array_equal(np.array([0], dtype=np.intp), indexes)
+ tm.assert_numpy_array_equal(np.array([], dtype=np.intp), missing)
+
+ indexes, missing = Index(idx_values).get_indexer_non_unique(
+ Index(idx_non_unique)
+ )
+ tm.assert_numpy_array_equal(np.array([0, 1, 3], dtype=np.intp), indexes)
+ tm.assert_numpy_array_equal(np.array([], dtype=np.intp), missing)
+
+
+class TestGetLoc:
+ @pytest.mark.slow # to_flat_index takes a while
+ def test_get_loc_tuple_monotonic_above_size_cutoff(self, monkeypatch):
+ # Go through the libindex path for which using
+ # _bin_search vs ndarray.searchsorted makes a difference
+
+ with monkeypatch.context():
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", 100)
+ lev = list("ABCD")
+ dti = pd.date_range("2016-01-01", periods=10)
+
+ mi = pd.MultiIndex.from_product([lev, range(5), dti])
+ oidx = mi.to_flat_index()
+
+ loc = len(oidx) // 2
+ tup = oidx[loc]
+
+ res = oidx.get_loc(tup)
+ assert res == loc
+
+ def test_get_loc_nan_object_dtype_nonmonotonic_nonunique(self):
+ # case that goes through _maybe_get_bool_indexer
+ idx = Index(["foo", np.nan, None, "foo", 1.0, None], dtype=object)
+
+ # we dont raise KeyError on nan
+ res = idx.get_loc(np.nan)
+ assert res == 1
+
+ # we only match on None, not on np.nan
+ res = idx.get_loc(None)
+ expected = np.array([False, False, True, False, False, True])
+ tm.assert_numpy_array_equal(res, expected)
+
+ # we don't match at all on mismatched NA
+ with pytest.raises(KeyError, match="NaT"):
+ idx.get_loc(NaT)
+
+
+def test_getitem_boolean_ea_indexer():
+ # GH#45806
+ ser = pd.Series([True, False, pd.NA], dtype="boolean")
+ result = ser.index[ser]
+ expected = Index([0])
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_pickle.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_pickle.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d139d58cba6abbe57026faa84683839fd2e8168
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_pickle.py
@@ -0,0 +1,11 @@
+from pandas import Index
+import pandas._testing as tm
+
+
+def test_pickle_preserves_object_dtype(temp_file):
+ # GH#43188, GH#43155 don't infer numeric dtype
+ index = Index([1, 2, 3], dtype=object)
+
+ result = tm.round_trip_pickle(index, temp_file)
+ assert result.dtype == object
+ tm.assert_index_equal(index, result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_reshape.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_reshape.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4932712de1bb73d2ad4067e8874cf2a2fa59a6d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_reshape.py
@@ -0,0 +1,95 @@
+"""
+Tests for ndarray-like method on the base Index class
+"""
+
+import numpy as np
+import pytest
+
+from pandas import Index
+import pandas._testing as tm
+
+
+class TestReshape:
+ def test_repeat(self):
+ repeats = 2
+ index = Index([1, 2, 3])
+ expected = Index([1, 1, 2, 2, 3, 3])
+
+ result = index.repeat(repeats)
+ tm.assert_index_equal(result, expected)
+
+ def test_insert(self):
+ # GH 7256
+ # validate neg/pos inserts
+ result = Index(["b", "c", "d"])
+
+ # test 0th element
+ tm.assert_index_equal(Index(["a", "b", "c", "d"]), result.insert(0, "a"))
+
+ # test Nth element that follows Python list behavior
+ tm.assert_index_equal(Index(["b", "c", "e", "d"]), result.insert(-1, "e"))
+
+ # test loc +/- neq (0, -1)
+ tm.assert_index_equal(result.insert(1, "z"), result.insert(-2, "z"))
+
+ # test empty
+ null_index = Index([])
+ tm.assert_index_equal(Index(["a"]), null_index.insert(0, "a"))
+
+ def test_insert_missing(self, nulls_fixture, using_infer_string):
+ # GH#22295
+ # test there is no mangling of NA values
+ expected = Index(["a", nulls_fixture, "b", "c"], dtype=object)
+ result = Index(list("abc"), dtype=object).insert(
+ 1, Index([nulls_fixture], dtype=object)
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "val", [(1, 2), np.datetime64("2019-12-31"), np.timedelta64(1, "D")]
+ )
+ @pytest.mark.parametrize("loc", [-1, 2])
+ def test_insert_datetime_into_object(self, loc, val):
+ # GH#44509
+ idx = Index(["1", "2", "3"])
+ result = idx.insert(loc, val)
+ expected = Index(["1", "2", val, "3"])
+ tm.assert_index_equal(result, expected)
+ assert type(expected[2]) is type(val)
+
+ def test_insert_none_into_string_numpy(self, string_dtype_no_object):
+ # GH#55365
+ index = Index(["a", "b", "c"], dtype=string_dtype_no_object)
+ result = index.insert(-1, None)
+ expected = Index(["a", "b", None, "c"], dtype=string_dtype_no_object)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "pos,expected",
+ [
+ (0, Index(["b", "c", "d"], name="index")),
+ (-1, Index(["a", "b", "c"], name="index")),
+ ],
+ )
+ def test_delete(self, pos, expected):
+ index = Index(["a", "b", "c", "d"], name="index")
+ result = index.delete(pos)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+
+ def test_delete_raises(self):
+ index = Index(["a", "b", "c", "d"], name="index")
+ msg = "index 5 is out of bounds for axis 0 with size 4"
+ with pytest.raises(IndexError, match=msg):
+ index.delete(5)
+
+ def test_append_multiple(self):
+ index = Index(["a", "b", "c", "d", "e", "f"])
+
+ foos = [index[:2], index[2:4], index[4:]]
+ result = foos[0].append(foos[1:])
+ tm.assert_index_equal(result, index)
+
+ # empty
+ result = index.append([])
+ tm.assert_index_equal(result, index)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..d57df82b2358c6cff0ff8f2d1dbc7be499514238
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_setops.py
@@ -0,0 +1,266 @@
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ Series,
+)
+import pandas._testing as tm
+from pandas.core.algorithms import safe_sort
+
+
+def equal_contents(arr1, arr2) -> bool:
+ """
+ Checks if the set of unique elements of arr1 and arr2 are equivalent.
+ """
+ return frozenset(arr1) == frozenset(arr2)
+
+
+class TestIndexSetOps:
+ @pytest.mark.parametrize(
+ "method", ["union", "intersection", "difference", "symmetric_difference"]
+ )
+ def test_setops_sort_validation(self, method):
+ idx1 = Index(["a", "b"])
+ idx2 = Index(["b", "c"])
+
+ with pytest.raises(ValueError, match="The 'sort' keyword only takes"):
+ getattr(idx1, method)(idx2, sort=2)
+
+ # sort=True is supported as of GH#??
+ getattr(idx1, method)(idx2, sort=True)
+
+ def test_setops_preserve_object_dtype(self):
+ idx = Index([1, 2, 3], dtype=object)
+ result = idx.intersection(idx[1:])
+ expected = idx[1:]
+ tm.assert_index_equal(result, expected)
+
+ # if other is not monotonic increasing, intersection goes through
+ # a different route
+ result = idx.intersection(idx[1:][::-1])
+ tm.assert_index_equal(result, expected)
+
+ result = idx._union(idx[1:], sort=None)
+ expected = idx
+ tm.assert_numpy_array_equal(result, expected.values)
+
+ result = idx.union(idx[1:], sort=None)
+ tm.assert_index_equal(result, expected)
+
+ # if other is not monotonic increasing, _union goes through
+ # a different route
+ result = idx._union(idx[1:][::-1], sort=None)
+ tm.assert_numpy_array_equal(result, expected.values)
+
+ result = idx.union(idx[1:][::-1], sort=None)
+ tm.assert_index_equal(result, expected)
+
+ def test_union_base(self):
+ index = Index([0, "a", 1, "b", 2, "c"])
+ first = index[3:]
+ second = index[:5]
+
+ result = first.union(second)
+
+ expected = Index([0, 1, 2, "a", "b", "c"])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("klass", [np.array, Series, list])
+ def test_union_different_type_base(self, klass):
+ # GH 10149
+ index = Index([0, "a", 1, "b", 2, "c"])
+ first = index[3:]
+ second = index[:5]
+
+ result = first.union(klass(second.values))
+
+ assert equal_contents(result, index)
+
+ def test_union_sort_other_incomparable(self):
+ # https://github.com/pandas-dev/pandas/issues/24959
+ idx = Index([1, pd.Timestamp("2000")])
+ # default (sort=None)
+ with tm.assert_produces_warning(RuntimeWarning, match="not supported between"):
+ result = idx.union(idx[:1])
+
+ tm.assert_index_equal(result, idx)
+
+ # sort=None
+ with tm.assert_produces_warning(RuntimeWarning, match="not supported between"):
+ result = idx.union(idx[:1], sort=None)
+ tm.assert_index_equal(result, idx)
+
+ # sort=False
+ result = idx.union(idx[:1], sort=False)
+ tm.assert_index_equal(result, idx)
+
+ def test_union_sort_other_incomparable_true(self):
+ idx = Index([1, pd.Timestamp("2000")])
+ with pytest.raises(TypeError, match=".*"):
+ idx.union(idx[:1], sort=True)
+
+ def test_intersection_equal_sort_true(self):
+ idx = Index(["c", "a", "b"])
+ sorted_ = Index(["a", "b", "c"])
+ tm.assert_index_equal(idx.intersection(idx, sort=True), sorted_)
+
+ def test_intersection_base(self, sort):
+ # (same results for py2 and py3 but sortedness not tested elsewhere)
+ index = Index([0, "a", 1, "b", 2, "c"])
+ first = index[:5]
+ second = index[:3]
+
+ expected = Index([0, 1, "a"]) if sort is None else Index([0, "a", 1])
+ result = first.intersection(second, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("klass", [np.array, Series, list])
+ def test_intersection_different_type_base(self, klass, sort):
+ # GH 10149
+ index = Index([0, "a", 1, "b", 2, "c"])
+ first = index[:5]
+ second = index[:3]
+
+ result = first.intersection(klass(second.values), sort=sort)
+ assert equal_contents(result, second)
+
+ def test_intersection_nosort(self):
+ result = Index(["c", "b", "a"]).intersection(["b", "a"])
+ expected = Index(["b", "a"])
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection_equal_sort(self):
+ idx = Index(["c", "a", "b"])
+ tm.assert_index_equal(idx.intersection(idx, sort=False), idx)
+ tm.assert_index_equal(idx.intersection(idx, sort=None), idx)
+
+ def test_intersection_str_dates(self, sort):
+ dt_dates = [datetime(2012, 2, 9), datetime(2012, 2, 22)]
+
+ i1 = Index(dt_dates, dtype=object)
+ i2 = Index(["aa"], dtype=object)
+ result = i2.intersection(i1, sort=sort)
+
+ assert len(result) == 0
+
+ @pytest.mark.parametrize(
+ "index2,expected_arr",
+ [(["B", "D"], ["B"]), (["B", "D", "A"], ["A", "B"])],
+ )
+ def test_intersection_non_monotonic_non_unique(self, index2, expected_arr, sort):
+ # non-monotonic non-unique
+ index1 = Index(["A", "B", "A", "C"])
+ expected = Index(expected_arr)
+ result = index1.intersection(Index(index2), sort=sort)
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+
+ def test_difference_base(self, sort):
+ # (same results for py2 and py3 but sortedness not tested elsewhere)
+ index = Index([0, "a", 1, "b", 2, "c"])
+ first = index[:4]
+ second = index[3:]
+
+ result = first.difference(second, sort)
+ expected = Index([0, "a", 1])
+ if sort is None:
+ expected = Index(safe_sort(expected))
+ tm.assert_index_equal(result, expected)
+
+ def test_symmetric_difference(self):
+ # (same results for py2 and py3 but sortedness not tested elsewhere)
+ index = Index([0, "a", 1, "b", 2, "c"])
+ first = index[:4]
+ second = index[3:]
+
+ result = first.symmetric_difference(second)
+ expected = Index([0, 1, 2, "a", "c"])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "method,expected,sort",
+ [
+ (
+ "intersection",
+ np.array(
+ [(1, "A"), (2, "A"), (1, "B"), (2, "B")],
+ dtype=[("num", int), ("let", "S1")],
+ ),
+ False,
+ ),
+ (
+ "intersection",
+ np.array(
+ [(1, "A"), (1, "B"), (2, "A"), (2, "B")],
+ dtype=[("num", int), ("let", "S1")],
+ ),
+ None,
+ ),
+ (
+ "union",
+ np.array(
+ [(1, "A"), (1, "B"), (1, "C"), (2, "A"), (2, "B"), (2, "C")],
+ dtype=[("num", int), ("let", "S1")],
+ ),
+ None,
+ ),
+ ],
+ )
+ def test_tuple_union_bug(self, method, expected, sort):
+ index1 = Index(
+ np.array(
+ [(1, "A"), (2, "A"), (1, "B"), (2, "B")],
+ dtype=[("num", int), ("let", "S1")],
+ )
+ )
+ index2 = Index(
+ np.array(
+ [(1, "A"), (2, "A"), (1, "B"), (2, "B"), (1, "C"), (2, "C")],
+ dtype=[("num", int), ("let", "S1")],
+ )
+ )
+
+ result = getattr(index1, method)(index2, sort=sort)
+ assert result.ndim == 1
+
+ expected = Index(expected)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("first_list", [["b", "a"], []])
+ @pytest.mark.parametrize("second_list", [["a", "b"], []])
+ @pytest.mark.parametrize(
+ "first_name, second_name, expected_name",
+ [("A", "B", None), (None, "B", None), ("A", None, None)],
+ )
+ def test_union_name_preservation(
+ self, first_list, second_list, first_name, second_name, expected_name, sort
+ ):
+ first = Index(first_list, name=first_name)
+ second = Index(second_list, name=second_name)
+ union = first.union(second, sort=sort)
+
+ vals = set(first_list).union(second_list)
+
+ if sort is None and len(first_list) > 0 and len(second_list) > 0:
+ expected = Index(sorted(vals), name=expected_name)
+ tm.assert_index_equal(union, expected)
+ else:
+ expected = Index(vals, name=expected_name)
+ tm.assert_index_equal(union.sort_values(), expected.sort_values())
+
+ @pytest.mark.parametrize(
+ "diff_type, expected",
+ [["difference", [1, "B"]], ["symmetric_difference", [1, 2, "B", "C"]]],
+ )
+ def test_difference_object_type(self, diff_type, expected):
+ # GH 13432
+ idx1 = Index([0, 1, "A", "B"])
+ idx2 = Index([0, 2, "A", "C"])
+ result = getattr(idx1, diff_type)(idx2)
+ expected = Index(expected)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_where.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_where.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c8969735e14e2741bc029b499024af3ec378a92
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/base_class/test_where.py
@@ -0,0 +1,13 @@
+import numpy as np
+
+from pandas import Index
+import pandas._testing as tm
+
+
+class TestWhere:
+ def test_where_intlike_str_doesnt_cast_ints(self):
+ idx = Index(range(3))
+ mask = np.array([True, False, True])
+ res = idx.where(mask, "2")
+ expected = Index([0, "2", 2])
+ tm.assert_index_equal(res, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_append.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_append.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4dbd91815cc1df7878509014a3ecd3ff95133a9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_append.py
@@ -0,0 +1,64 @@
+import pytest
+
+from pandas import (
+ CategoricalIndex,
+ Index,
+)
+import pandas._testing as tm
+
+
+class TestAppend:
+ @pytest.fixture
+ def ci(self):
+ categories = list("cab")
+ return CategoricalIndex(list("aabbca"), categories=categories, ordered=False)
+
+ def test_append(self, ci):
+ # append cats with the same categories
+ result = ci[:3].append(ci[3:])
+ tm.assert_index_equal(result, ci, exact=True)
+
+ foos = [ci[:1], ci[1:3], ci[3:]]
+ result = foos[0].append(foos[1:])
+ tm.assert_index_equal(result, ci, exact=True)
+
+ def test_append_empty(self, ci):
+ # empty
+ result = ci.append([])
+ tm.assert_index_equal(result, ci, exact=True)
+
+ def test_append_mismatched_categories(self, ci):
+ # appending with different categories or reordered is not ok
+ msg = "all inputs must be Index"
+ with pytest.raises(TypeError, match=msg):
+ ci.append(ci.values.set_categories(list("abcd")))
+ with pytest.raises(TypeError, match=msg):
+ ci.append(ci.values.reorder_categories(list("abc")))
+
+ def test_append_category_objects(self, ci):
+ # GH#41626 pre-3.0 this used to cast the object-dtype index to
+ # ci.dtype
+ # with objects
+ result = ci.append(Index(["c", "a"]))
+ expected = Index(list("aabbcaca"))
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_append_non_categories(self, ci):
+ # invalid objects -> cast to object via concat_compat
+ result = ci.append(Index(["a", "d"]))
+ expected = Index(["a", "a", "b", "b", "c", "a", "a", "d"])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_append_object(self, ci):
+ # GH#14298 - if base object is not categorical -> coerce to object
+ result = Index(["c", "a"]).append(ci)
+ expected = Index(list("caaabbca"))
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_append_to_another(self):
+ # hits Index._concat
+ fst = Index(["a", "b"])
+ snd = CategoricalIndex(["d", "e"])
+ result = fst.append(snd)
+ expected = Index(["a", "b", "d", "e"])
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..840004330a4287f31954c5a1fc5887234f74e8ec
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_astype.py
@@ -0,0 +1,95 @@
+from datetime import date
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ Categorical,
+ CategoricalDtype,
+ CategoricalIndex,
+ Index,
+ IntervalIndex,
+)
+import pandas._testing as tm
+
+
+class TestAstype:
+ def test_astype(self):
+ ci = CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
+
+ result = ci.astype(object)
+ tm.assert_index_equal(result, Index(np.array(ci), dtype=object))
+
+ # this IS equal, but not the same class
+ assert result.equals(ci)
+ assert isinstance(result, Index)
+ assert not isinstance(result, CategoricalIndex)
+
+ # interval
+ ii = IntervalIndex.from_arrays(left=[-0.001, 2.0], right=[2, 4], closed="right")
+
+ ci = CategoricalIndex(
+ Categorical.from_codes([0, 1, -1], categories=ii, ordered=True)
+ )
+
+ result = ci.astype("interval")
+ expected = ii.take([0, 1, -1], allow_fill=True, fill_value=np.nan)
+ tm.assert_index_equal(result, expected)
+
+ result = IntervalIndex(result.values)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("name", [None, "foo"])
+ @pytest.mark.parametrize("dtype_ordered", [True, False])
+ @pytest.mark.parametrize("index_ordered", [True, False])
+ def test_astype_category(self, name, dtype_ordered, index_ordered):
+ # GH#18630
+ index = CategoricalIndex(
+ list("aabbca"), categories=list("cab"), ordered=index_ordered
+ )
+ if name:
+ index = index.rename(name)
+
+ # standard categories
+ dtype = CategoricalDtype(ordered=dtype_ordered)
+ result = index.astype(dtype)
+ expected = CategoricalIndex(
+ index.tolist(),
+ name=name,
+ categories=index.categories,
+ ordered=dtype_ordered,
+ )
+ tm.assert_index_equal(result, expected)
+
+ # non-standard categories
+ dtype = CategoricalDtype(index.unique().tolist()[:-1], dtype_ordered)
+ msg = "Constructing a Categorical with a dtype and values containing"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = index.astype(dtype)
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = CategoricalIndex(index.tolist(), name=name, dtype=dtype)
+ tm.assert_index_equal(result, expected)
+
+ if dtype_ordered is False:
+ # dtype='category' can't specify ordered, so only test once
+ result = index.astype("category")
+ expected = index
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("box", [True, False])
+ def test_categorical_date_roundtrip(self, box):
+ # astype to categorical and back should preserve date objects
+ v = date.today()
+
+ obj = Index([v, v])
+ assert obj.dtype == object
+ if box:
+ obj = obj.array
+
+ cat = obj.astype("category")
+
+ rtrip = cat.astype(object)
+ assert rtrip.dtype == object
+ assert type(rtrip[0]) is date
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_category.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_category.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a7aece0c19e5fe1b230b6790e2f847d50d9d475
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_category.py
@@ -0,0 +1,406 @@
+import numpy as np
+import pytest
+
+from pandas._libs import index as libindex
+from pandas._libs.arrays import NDArrayBacked
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ CategoricalDtype,
+)
+import pandas._testing as tm
+from pandas.core.indexes.api import (
+ CategoricalIndex,
+ Index,
+)
+
+
+class TestCategoricalIndex:
+ @pytest.fixture
+ def simple_index(self) -> CategoricalIndex:
+ """
+ Fixture that provides a CategoricalIndex.
+ """
+ return CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
+
+ def test_can_hold_identifiers(self):
+ idx = CategoricalIndex(list("aabbca"), categories=None, ordered=False)
+ key = idx[0]
+ assert idx._can_hold_identifiers_and_holds_name(key) is True
+
+ def test_insert(self, simple_index):
+ ci = simple_index
+ categories = ci.categories
+
+ # test 0th element
+ result = ci.insert(0, "a")
+ expected = CategoricalIndex(list("aaabbca"), categories=categories)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # test Nth element that follows Python list behavior
+ result = ci.insert(-1, "a")
+ expected = CategoricalIndex(list("aabbcaa"), categories=categories)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # test empty
+ result = CategoricalIndex([], categories=categories).insert(0, "a")
+ expected = CategoricalIndex(["a"], categories=categories)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # invalid -> cast to object
+ expected = ci.astype(object).insert(0, "d")
+ result = ci.insert(0, "d").astype(object)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # GH 18295 (test missing)
+ expected = CategoricalIndex(["a", np.nan, "a", "b", "c", "b"])
+ for na in (np.nan, pd.NaT, None):
+ result = CategoricalIndex(list("aabcb")).insert(1, na)
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_na_mismatched_dtype(self):
+ ci = CategoricalIndex([0, 1, 1])
+ result = ci.insert(0, pd.NaT)
+ expected = Index([pd.NaT, 0, 1, 1], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ def test_delete(self, simple_index):
+ ci = simple_index
+ categories = ci.categories
+
+ result = ci.delete(0)
+ expected = CategoricalIndex(list("abbca"), categories=categories)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = ci.delete(-1)
+ expected = CategoricalIndex(list("aabbc"), categories=categories)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ with tm.external_error_raised((IndexError, ValueError)):
+ # Either depending on NumPy version
+ ci.delete(10)
+
+ @pytest.mark.parametrize(
+ "data, non_lexsorted_data",
+ [[[1, 2, 3], [9, 0, 1, 2, 3]], [list("abc"), list("fabcd")]],
+ )
+ def test_is_monotonic(self, data, non_lexsorted_data):
+ c = CategoricalIndex(data)
+ assert c.is_monotonic_increasing is True
+ assert c.is_monotonic_decreasing is False
+
+ c = CategoricalIndex(data, ordered=True)
+ assert c.is_monotonic_increasing is True
+ assert c.is_monotonic_decreasing is False
+
+ c = CategoricalIndex(data, categories=reversed(data))
+ assert c.is_monotonic_increasing is False
+ assert c.is_monotonic_decreasing is True
+
+ c = CategoricalIndex(data, categories=reversed(data), ordered=True)
+ assert c.is_monotonic_increasing is False
+ assert c.is_monotonic_decreasing is True
+
+ # test when data is neither monotonic increasing nor decreasing
+ reordered_data = [data[0], data[2], data[1]]
+ c = CategoricalIndex(reordered_data, categories=reversed(data))
+ assert c.is_monotonic_increasing is False
+ assert c.is_monotonic_decreasing is False
+
+ # non lexsorted categories
+ categories = non_lexsorted_data
+
+ c = CategoricalIndex(categories[:2], categories=categories)
+ assert c.is_monotonic_increasing is True
+ assert c.is_monotonic_decreasing is False
+
+ c = CategoricalIndex(categories[1:3], categories=categories)
+ assert c.is_monotonic_increasing is True
+ assert c.is_monotonic_decreasing is False
+
+ def test_has_duplicates(self):
+ idx = CategoricalIndex([0, 0, 0], name="foo")
+ assert idx.is_unique is False
+ assert idx.has_duplicates is True
+
+ idx = CategoricalIndex([None, None], categories=[2, 3], name="foo")
+ assert idx.is_unique is False
+ assert idx.has_duplicates is True
+
+ idx = CategoricalIndex([None, 1, 2, 3], categories=[1, 2, 3], name="foo")
+ assert idx.is_unique is True
+ assert idx.has_duplicates is False
+
+ @pytest.mark.parametrize(
+ "data, categories, expected",
+ [
+ (
+ [1, 1, 1],
+ [1, 2, 3],
+ {
+ "first": np.array([False, True, True]),
+ "last": np.array([True, True, False]),
+ False: np.array([True, True, True]),
+ },
+ ),
+ (
+ [None, None, None],
+ list("abc"),
+ {
+ "first": np.array([False, True, True]),
+ "last": np.array([True, True, False]),
+ False: np.array([True, True, True]),
+ },
+ ),
+ (
+ [None, "a", "b"],
+ list("abc"),
+ {
+ "first": np.zeros(shape=(3), dtype=np.bool_),
+ "last": np.zeros(shape=(3), dtype=np.bool_),
+ False: np.zeros(shape=(3), dtype=np.bool_),
+ },
+ ),
+ (
+ list("abb"),
+ list("abc"),
+ {
+ "first": np.array([False, False, True]),
+ "last": np.array([False, True, False]),
+ False: np.array([False, True, True]),
+ },
+ ),
+ ],
+ )
+ def test_drop_duplicates(self, data, categories, expected):
+ idx = CategoricalIndex(data, categories=categories, name="foo")
+ for keep, e in expected.items():
+ tm.assert_numpy_array_equal(idx.duplicated(keep=keep), e)
+ e = idx[~e]
+ result = idx.drop_duplicates(keep=keep)
+ tm.assert_index_equal(result, e)
+
+ @pytest.mark.parametrize(
+ "data, categories, expected_data",
+ [
+ ([1, 1, 1], [1, 2, 3], [1]),
+ ([1, 1, 1], list("abc"), [np.nan]),
+ ([1, 2, "a"], [1, 2, 3], [1, 2, np.nan]),
+ ([2, "a", "b"], list("abc"), [np.nan, "a", "b"]),
+ ],
+ )
+ def test_unique(self, data, categories, expected_data, ordered):
+ dtype = CategoricalDtype(categories, ordered=ordered)
+
+ msg = "Constructing a Categorical with a dtype and values containing"
+ warn = None if expected_data == [1] else Pandas4Warning
+ with tm.assert_produces_warning(warn, match=msg):
+ idx = CategoricalIndex(data, dtype=dtype)
+ expected = CategoricalIndex(expected_data, dtype=dtype)
+ tm.assert_index_equal(idx.unique(), expected)
+
+ def test_repr_roundtrip(self):
+ ci = CategoricalIndex(["a", "b"], categories=["a", "b"], ordered=True)
+ str(ci)
+ tm.assert_index_equal(eval(repr(ci)), ci, exact=True)
+
+ # formatting
+ str(ci)
+
+ # long format
+ # this is not reprable
+ ci = CategoricalIndex(np.random.default_rng(2).integers(0, 5, size=100))
+ str(ci)
+
+ def test_isin(self):
+ ci = CategoricalIndex([*list("aabca"), np.nan], categories=["c", "a", "b"])
+ tm.assert_numpy_array_equal(
+ ci.isin(["c"]), np.array([False, False, False, True, False, False])
+ )
+ tm.assert_numpy_array_equal(
+ ci.isin(["c", "a", "b"]), np.array([True] * 5 + [False])
+ )
+ tm.assert_numpy_array_equal(
+ ci.isin(["c", "a", "b", np.nan]), np.array([True] * 6)
+ )
+
+ # mismatched categorical -> coerced to ndarray so doesn't matter
+ result = ci.isin(ci.set_categories(list("abcdefghi")))
+ expected = np.array([True] * 6)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = ci.isin(ci.set_categories(list("defghi")))
+ expected = np.array([False] * 5 + [True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_isin_overlapping_intervals(self):
+ # GH 34974
+ idx = pd.IntervalIndex([pd.Interval(0, 2), pd.Interval(0, 1)])
+ result = CategoricalIndex(idx).isin(idx)
+ expected = np.array([True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_identical(self):
+ ci1 = CategoricalIndex(["a", "b"], categories=["a", "b"], ordered=True)
+ ci2 = CategoricalIndex(["a", "b"], categories=["a", "b", "c"], ordered=True)
+ assert ci1.identical(ci1)
+ assert ci1.identical(ci1.copy())
+ assert not ci1.identical(ci2)
+
+ def test_ensure_copied_data(self):
+ # gh-12309: Check the "copy" argument of each
+ # Index.__new__ is honored.
+ #
+ # Must be tested separately from other indexes because
+ # self.values is not an ndarray.
+ index = CategoricalIndex(list("ab") * 5)
+
+ result = CategoricalIndex(index.values, copy=True)
+ tm.assert_index_equal(index, result)
+ assert not np.shares_memory(result._data._codes, index._data._codes)
+
+ result = CategoricalIndex(index.values, copy=False)
+ assert result._data._codes is index._data._codes
+
+
+class TestCategoricalIndex2:
+ def test_view_i8(self):
+ # GH#25464
+ ci = CategoricalIndex(list("ab") * 50)
+ msg = "When changing to a larger dtype, its size must be a divisor"
+ with pytest.raises(ValueError, match=msg):
+ ci.view("i8")
+ with pytest.raises(ValueError, match=msg):
+ ci._data.view("i8")
+
+ ci = ci[:-4] # length divisible by 8
+
+ res = ci.view("i8")
+ expected = ci._data.codes.view("i8")
+ tm.assert_numpy_array_equal(res, expected)
+
+ cat = ci._data
+ tm.assert_numpy_array_equal(cat.view("i8"), expected)
+
+ @pytest.mark.parametrize(
+ "dtype, engine_type",
+ [
+ (np.int8, libindex.Int8Engine),
+ (np.int16, libindex.Int16Engine),
+ (np.int32, libindex.Int32Engine),
+ (np.int64, libindex.Int64Engine),
+ ],
+ )
+ def test_engine_type(self, dtype, engine_type):
+ if dtype != np.int64:
+ # num. of uniques required to push CategoricalIndex.codes to a
+ # dtype (128 categories required for .codes dtype to be int16 etc.)
+ num_uniques = {np.int8: 1, np.int16: 128, np.int32: 32768}[dtype]
+ ci = CategoricalIndex(range(num_uniques))
+ else:
+ # having 2**32 - 2**31 categories would be very memory-intensive,
+ # so we cheat a bit with the dtype
+ ci = CategoricalIndex(range(32768)) # == 2**16 - 2**(16 - 1)
+ arr = ci.values._ndarray.astype("int64")
+ NDArrayBacked.__init__(ci._data, arr, ci.dtype)
+ assert np.issubdtype(ci.codes.dtype, dtype)
+ assert isinstance(ci._engine, engine_type)
+
+ @pytest.mark.parametrize(
+ "func,op_name",
+ [
+ (lambda idx: idx - idx, "__sub__"),
+ (lambda idx: idx + idx, "__add__"),
+ (lambda idx: idx - ["a", "b"], "__sub__"),
+ (lambda idx: idx + ["a", "b"], "__add__"), # noqa: RUF005
+ (lambda idx: ["a", "b"] - idx, "__rsub__"),
+ (lambda idx: ["a", "b"] + idx, "__radd__"), # noqa: RUF005
+ ],
+ )
+ def test_disallow_addsub_ops(self, func, op_name):
+ # GH 10039
+ # set ops (+/-) raise TypeError
+ idx = Index(Categorical(["a", "b"]))
+ cat_or_list = "'(Categorical|list)' and '(Categorical|list)'"
+ msg = "|".join(
+ [
+ rf"unsupported operand type\(s\) for [\+-]: {cat_or_list}",
+ "Object with dtype category cannot perform the numpy op (add|subtract)",
+ "operation 'r?(add|sub)' not supported for dtype 'str' "
+ "with dtype 'category'",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ func(idx)
+
+ def test_method_delegation(self):
+ ci = CategoricalIndex(list("aabbca"), categories=list("cabdef"))
+ result = ci.set_categories(list("cab"))
+ tm.assert_index_equal(
+ result, CategoricalIndex(list("aabbca"), categories=list("cab"))
+ )
+
+ ci = CategoricalIndex(list("aabbca"), categories=list("cab"))
+ result = ci.rename_categories(list("efg"))
+ tm.assert_index_equal(
+ result, CategoricalIndex(list("ffggef"), categories=list("efg"))
+ )
+
+ # GH18862 (let rename_categories take callables)
+ result = ci.rename_categories(lambda x: x.upper())
+ tm.assert_index_equal(
+ result, CategoricalIndex(list("AABBCA"), categories=list("CAB"))
+ )
+
+ ci = CategoricalIndex(list("aabbca"), categories=list("cab"))
+ result = ci.add_categories(["d"])
+ tm.assert_index_equal(
+ result, CategoricalIndex(list("aabbca"), categories=list("cabd"))
+ )
+
+ ci = CategoricalIndex(list("aabbca"), categories=list("cab"))
+ result = ci.remove_categories(["c"])
+ tm.assert_index_equal(
+ result,
+ CategoricalIndex([*list("aabb"), np.nan, "a"], categories=list("ab")),
+ )
+
+ ci = CategoricalIndex(list("aabbca"), categories=list("cabdef"))
+ result = ci.as_unordered()
+ tm.assert_index_equal(result, ci)
+
+ ci = CategoricalIndex(list("aabbca"), categories=list("cabdef"))
+ result = ci.as_ordered()
+ tm.assert_index_equal(
+ result,
+ CategoricalIndex(list("aabbca"), categories=list("cabdef"), ordered=True),
+ )
+
+ # invalid
+ msg = "cannot use inplace with CategoricalIndex"
+ with pytest.raises(ValueError, match=msg):
+ ci.set_categories(list("cab"), inplace=True)
+
+ def test_remove_maintains_order(self):
+ ci = CategoricalIndex(list("abcdda"), categories=list("abcd"))
+ result = ci.reorder_categories(["d", "c", "b", "a"], ordered=True)
+ tm.assert_index_equal(
+ result,
+ CategoricalIndex(list("abcdda"), categories=list("dcba"), ordered=True),
+ )
+ result = result.remove_categories(["c"])
+ tm.assert_index_equal(
+ result,
+ CategoricalIndex(
+ ["a", "b", np.nan, "d", "d", "a"], categories=list("dba"), ordered=True
+ ),
+ )
+
+
+def test_contains_rangeindex_categories_no_engine():
+ ci = CategoricalIndex(range(3))
+ assert 2 in ci
+ assert 5 not in ci
+ assert "_engine" not in ci._cache
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd38014d9eff76f1c71e3ac5316bef159d72a4fa
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_constructors.py
@@ -0,0 +1,149 @@
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ Categorical,
+ CategoricalDtype,
+ CategoricalIndex,
+ Index,
+)
+import pandas._testing as tm
+
+
+class TestCategoricalIndexConstructors:
+ def test_construction_disallows_scalar(self):
+ msg = "must be called with a collection of some kind"
+ with pytest.raises(TypeError, match=msg):
+ CategoricalIndex(data=1, categories=list("abcd"), ordered=False)
+ with pytest.raises(TypeError, match=msg):
+ CategoricalIndex(categories=list("abcd"), ordered=False)
+
+ def test_construction(self):
+ ci = CategoricalIndex(list("aabbca"), categories=list("abcd"), ordered=False)
+ categories = ci.categories
+
+ result = Index(ci)
+ tm.assert_index_equal(result, ci, exact=True)
+ assert not result.ordered
+
+ result = Index(ci.values)
+ tm.assert_index_equal(result, ci, exact=True)
+ assert not result.ordered
+
+ # empty
+ result = CategoricalIndex([], categories=categories)
+ tm.assert_index_equal(result.categories, Index(categories))
+ tm.assert_numpy_array_equal(result.codes, np.array([], dtype="int8"))
+ assert not result.ordered
+
+ # passing categories
+ result = CategoricalIndex(list("aabbca"), categories=categories)
+ tm.assert_index_equal(result.categories, Index(categories))
+ tm.assert_numpy_array_equal(
+ result.codes, np.array([0, 0, 1, 1, 2, 0], dtype="int8")
+ )
+
+ c = Categorical(list("aabbca"))
+ result = CategoricalIndex(c)
+ tm.assert_index_equal(result.categories, Index(list("abc")))
+ tm.assert_numpy_array_equal(
+ result.codes, np.array([0, 0, 1, 1, 2, 0], dtype="int8")
+ )
+ assert not result.ordered
+
+ result = CategoricalIndex(c, categories=categories)
+ tm.assert_index_equal(result.categories, Index(categories))
+ tm.assert_numpy_array_equal(
+ result.codes, np.array([0, 0, 1, 1, 2, 0], dtype="int8")
+ )
+ assert not result.ordered
+
+ ci = CategoricalIndex(c, categories=list("abcd"))
+ result = CategoricalIndex(ci)
+ tm.assert_index_equal(result.categories, Index(categories))
+ tm.assert_numpy_array_equal(
+ result.codes, np.array([0, 0, 1, 1, 2, 0], dtype="int8")
+ )
+ assert not result.ordered
+
+ msg = "Constructing a Categorical with a dtype and values containing"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = CategoricalIndex(ci, categories=list("ab"))
+ tm.assert_index_equal(result.categories, Index(list("ab")))
+ tm.assert_numpy_array_equal(
+ result.codes, np.array([0, 0, 1, 1, -1, 0], dtype="int8")
+ )
+ assert not result.ordered
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = CategoricalIndex(ci, categories=list("ab"), ordered=True)
+ tm.assert_index_equal(result.categories, Index(list("ab")))
+ tm.assert_numpy_array_equal(
+ result.codes, np.array([0, 0, 1, 1, -1, 0], dtype="int8")
+ )
+ assert result.ordered
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = CategoricalIndex(ci, categories=list("ab"), ordered=True)
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = CategoricalIndex(
+ ci, categories=list("ab"), ordered=True, dtype="category"
+ )
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # turn me to an Index
+ result = Index(np.array(ci))
+ assert isinstance(result, Index)
+ assert not isinstance(result, CategoricalIndex)
+
+ def test_construction_with_dtype(self):
+ # specify dtype
+ ci = CategoricalIndex(list("aabbca"), categories=list("abc"), ordered=False)
+
+ result = Index(np.array(ci), dtype="category")
+ tm.assert_index_equal(result, ci, exact=True)
+
+ result = Index(np.array(ci).tolist(), dtype="category")
+ tm.assert_index_equal(result, ci, exact=True)
+
+ # these are generally only equal when the categories are reordered
+ ci = CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
+
+ result = Index(np.array(ci), dtype="category").reorder_categories(ci.categories)
+ tm.assert_index_equal(result, ci, exact=True)
+
+ # make sure indexes are handled
+ idx = Index(range(3))
+ expected = CategoricalIndex([0, 1, 2], categories=idx, ordered=True)
+ result = CategoricalIndex(idx, categories=idx, ordered=True)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_construction_empty_with_bool_categories(self):
+ # see GH#22702
+ cat = CategoricalIndex([], categories=[True, False])
+ categories = sorted(cat.categories.tolist())
+ assert categories == [False, True]
+
+ def test_construction_with_categorical_dtype(self):
+ # construction with CategoricalDtype
+ # GH#18109
+ data, cats, ordered = "a a b b".split(), "c b a".split(), True
+ dtype = CategoricalDtype(categories=cats, ordered=ordered)
+
+ result = CategoricalIndex(data, dtype=dtype)
+ expected = CategoricalIndex(data, categories=cats, ordered=ordered)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # GH#19032
+ result = Index(data, dtype=dtype)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # error when combining categories/ordered and dtype kwargs
+ msg = "Cannot specify `categories` or `ordered` together with `dtype`."
+ with pytest.raises(ValueError, match=msg):
+ CategoricalIndex(data, categories=cats, dtype=dtype)
+
+ with pytest.raises(ValueError, match=msg):
+ CategoricalIndex(data, ordered=ordered, dtype=dtype)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_equals.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_equals.py
new file mode 100644
index 0000000000000000000000000000000000000000..9893bb4aad81684c9fd8a2a5bb0d1b45cd099549
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_equals.py
@@ -0,0 +1,96 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ CategoricalIndex,
+ Index,
+ MultiIndex,
+)
+
+
+class TestEquals:
+ def test_equals_categorical(self):
+ ci1 = CategoricalIndex(["a", "b"], categories=["a", "b"], ordered=True)
+ ci2 = CategoricalIndex(["a", "b"], categories=["a", "b", "c"], ordered=True)
+
+ assert ci1.equals(ci1)
+ assert not ci1.equals(ci2)
+ assert ci1.equals(ci1.astype(object))
+ assert ci1.astype(object).equals(ci1)
+
+ assert (ci1 == ci1).all()
+ assert not (ci1 != ci1).all()
+ assert not (ci1 > ci1).all()
+ assert not (ci1 < ci1).all()
+ assert (ci1 <= ci1).all()
+ assert (ci1 >= ci1).all()
+
+ assert not (ci1 == 1).all()
+ assert (ci1 == Index(["a", "b"])).all()
+ assert (ci1 == ci1.values).all()
+
+ # invalid comparisons
+ with pytest.raises(ValueError, match="Lengths must match"):
+ ci1 == Index(["a", "b", "c"])
+
+ msg = "Categoricals can only be compared if 'categories' are the same"
+ with pytest.raises(TypeError, match=msg):
+ ci1 == ci2
+ with pytest.raises(TypeError, match=msg):
+ ci1 == Categorical(ci1.values, ordered=False)
+ with pytest.raises(TypeError, match=msg):
+ ci1 == Categorical(ci1.values, categories=list("abc"))
+
+ # tests
+ # make sure that we are testing for category inclusion properly
+ ci = CategoricalIndex(list("aabca"), categories=["c", "a", "b"])
+ assert not ci.equals(list("aabca"))
+ # Same categories, but different order
+ # Unordered
+ assert ci.equals(CategoricalIndex(list("aabca")))
+ # Ordered
+ assert not ci.equals(CategoricalIndex(list("aabca"), ordered=True))
+ assert ci.equals(ci.copy())
+
+ ci = CategoricalIndex([*list("aabca"), np.nan], categories=["c", "a", "b"])
+ assert not ci.equals(list("aabca"))
+ assert not ci.equals(CategoricalIndex(list("aabca")))
+ assert ci.equals(ci.copy())
+
+ ci = CategoricalIndex([*list("aabca"), np.nan], categories=["c", "a", "b"])
+ assert not ci.equals([*list("aabca"), np.nan])
+ assert ci.equals(CategoricalIndex([*list("aabca"), np.nan]))
+ assert not ci.equals(CategoricalIndex([*list("aabca"), np.nan], ordered=True))
+ assert ci.equals(ci.copy())
+
+ def test_equals_categorical_unordered(self):
+ # https://github.com/pandas-dev/pandas/issues/16603
+ a = CategoricalIndex(["A"], categories=["A", "B"])
+ b = CategoricalIndex(["A"], categories=["B", "A"])
+ c = CategoricalIndex([None], categories=["B", "A"])
+ assert a.equals(b)
+ assert not a.equals(c)
+ assert not b.equals(c)
+
+ def test_equals_non_category(self):
+ # GH#37667 Case where other contains a value not among ci's
+ # categories ("D") and also contains np.nan
+ ci = CategoricalIndex(["A", "B", np.nan, np.nan])
+ other = Index(["A", "B", "D", np.nan])
+
+ assert not ci.equals(other)
+
+ def test_equals_multiindex(self):
+ # dont raise NotImplementedError when calling is_dtype_compat
+
+ mi = MultiIndex.from_arrays([["A", "B", "C", "D"], range(4)])
+ ci = mi.to_flat_index().astype("category")
+
+ assert not ci.equals(mi)
+
+ def test_equals_string_dtype(self, any_string_dtype):
+ # GH#55364
+ idx = CategoricalIndex(list("abc"), name="B")
+ other = Index(["a", "b", "c"], name="B", dtype=any_string_dtype)
+ assert idx.equals(other)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_fillna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_fillna.py
new file mode 100644
index 0000000000000000000000000000000000000000..09de578f3c649e5a90278f11b1e3cd5b1d0646d5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_fillna.py
@@ -0,0 +1,54 @@
+import numpy as np
+import pytest
+
+from pandas import CategoricalIndex
+import pandas._testing as tm
+
+
+class TestFillNA:
+ def test_fillna_categorical(self):
+ # GH#11343
+ idx = CategoricalIndex([1.0, np.nan, 3.0, 1.0], name="x")
+ # fill by value in categories
+ exp = CategoricalIndex([1.0, 1.0, 3.0, 1.0], name="x")
+ tm.assert_index_equal(idx.fillna(1.0), exp)
+
+ cat = idx._data
+
+ # fill by value not in categories raises TypeError on EA, casts on CI
+ msg = "Cannot setitem on a Categorical with a new category"
+ with pytest.raises(TypeError, match=msg):
+ cat.fillna(2.0)
+
+ result = idx.fillna(2.0)
+ expected = idx.astype(object).fillna(2.0)
+ tm.assert_index_equal(result, expected)
+
+ def test_fillna_copies_with_no_nas(self):
+ # Nothing to fill, should still get a copy for the Categorical method,
+ # but OK to get a view on CategoricalIndex method
+ ci = CategoricalIndex([0, 1, 1])
+ result = ci.fillna(0)
+ assert result is not ci
+ assert tm.shares_memory(result, ci)
+
+ # But at the EA level we always get a copy.
+ cat = ci._data
+ result = cat.fillna(0)
+ assert result._ndarray is not cat._ndarray
+ assert result._ndarray.base is None
+ assert not tm.shares_memory(result, cat)
+
+ def test_fillna_validates_with_no_nas(self):
+ # We validate the fill value even if fillna is a no-op
+ ci = CategoricalIndex([2, 3, 3])
+ cat = ci._data
+
+ msg = "Cannot setitem on a Categorical with a new category"
+ res = ci.fillna(False)
+ # nothing to fill, so we dont cast
+ tm.assert_index_equal(res, ci)
+
+ # Same check directly on the Categorical
+ with pytest.raises(TypeError, match=msg):
+ cat.fillna(False)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_formats.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_formats.py
new file mode 100644
index 0000000000000000000000000000000000000000..aed1082de88ccaa878c62b63996c3f113d4b1ebc
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_formats.py
@@ -0,0 +1,114 @@
+"""
+Tests for CategoricalIndex.__repr__ and related methods.
+"""
+
+import pandas._config.config as cf
+
+from pandas import CategoricalIndex
+
+
+class TestCategoricalIndexReprStringCategories:
+ def test_string_categorical_index_repr(self):
+ # short
+ idx = CategoricalIndex(["a", "bb", "ccc"])
+ expected = """CategoricalIndex(['a', 'bb', 'ccc'], categories=['a', 'bb', 'ccc'], ordered=False, dtype='category')""" # noqa: E501
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_multiline(self):
+ # multiple lines
+ idx = CategoricalIndex(["a", "bb", "ccc"] * 10)
+ expected = """CategoricalIndex(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
+ 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb',
+ 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
+ categories=['a', 'bb', 'ccc'], ordered=False, dtype='category')""" # noqa: E501
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_truncated(self):
+ # truncated
+ idx = CategoricalIndex(["a", "bb", "ccc"] * 100)
+ expected = """CategoricalIndex(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
+ ...
+ 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
+ categories=['a', 'bb', 'ccc'], ordered=False, dtype='category', length=300)""" # noqa: E501
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_many_categories(self):
+ # larger categories
+ idx = CategoricalIndex(list("abcdefghijklmmo"))
+ expected = """CategoricalIndex(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'm', 'o'],
+ categories=['a', 'b', 'c', 'd', ..., 'k', 'l', 'm', 'o'], ordered=False, dtype='category')""" # noqa: E501
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_unicode(self):
+ # short
+ idx = CategoricalIndex(["あ", "いい", "ううう"])
+ expected = """CategoricalIndex(['あ', 'いい', 'ううう'], categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category')""" # noqa: E501
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_unicode_multiline(self):
+ # multiple lines
+ idx = CategoricalIndex(["あ", "いい", "ううう"] * 10)
+ expected = """CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
+ 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい',
+ 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
+ categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category')""" # noqa: E501
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_unicode_truncated(self):
+ # truncated
+ idx = CategoricalIndex(["あ", "いい", "ううう"] * 100)
+ expected = """CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
+ ...
+ 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
+ categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category', length=300)""" # noqa: E501
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_unicode_many_categories(self):
+ # larger categories
+ idx = CategoricalIndex(list("あいうえおかきくけこさしすせそ"))
+ expected = """CategoricalIndex(['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し',
+ 'す', 'せ', 'そ'],
+ categories=['あ', 'い', 'う', 'え', ..., 'し', 'す', 'せ', 'そ'], ordered=False, dtype='category')""" # noqa: E501
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_east_asian_width(self):
+ with cf.option_context("display.unicode.east_asian_width", True):
+ # short
+ idx = CategoricalIndex(["あ", "いい", "ううう"])
+ expected = """CategoricalIndex(['あ', 'いい', 'ううう'], categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category')""" # noqa: E501
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_east_asian_width_multiline(self):
+ with cf.option_context("display.unicode.east_asian_width", True):
+ # multiple lines
+ idx = CategoricalIndex(["あ", "いい", "ううう"] * 10)
+ expected = """CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい',
+ 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
+ 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい',
+ 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
+ categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category')""" # noqa: E501
+
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_east_asian_width_truncated(self):
+ with cf.option_context("display.unicode.east_asian_width", True):
+ # truncated
+ idx = CategoricalIndex(["あ", "いい", "ううう"] * 100)
+ expected = """CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい',
+ 'ううう', 'あ',
+ ...
+ 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
+ 'あ', 'いい', 'ううう'],
+ categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category', length=300)""" # noqa: E501
+
+ assert repr(idx) == expected
+
+ def test_categorical_index_repr_east_asian_width_many_categories(self):
+ with cf.option_context("display.unicode.east_asian_width", True):
+ idx = CategoricalIndex(list("あいうえおかきくけこさしすせそ"))
+ expected = """CategoricalIndex(['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ',
+ 'さ', 'し', 'す', 'せ', 'そ'],
+ categories=['あ', 'い', 'う', 'え', ..., 'し', 'す', 'せ', 'そ'], ordered=False, dtype='category')""" # noqa: E501
+
+ assert repr(idx) == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7b34f993981d5f11398b860e87c8fc1b1420636
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_indexing.py
@@ -0,0 +1,418 @@
+import numpy as np
+import pytest
+
+from pandas.errors import InvalidIndexError
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ Index,
+ IntervalIndex,
+ Timestamp,
+)
+import pandas._testing as tm
+
+
+class TestTake:
+ def test_take_fill_value(self):
+ # GH 12631
+
+ # numeric category
+ idx = CategoricalIndex([1, 2, 3], name="xxx")
+ result = idx.take(np.array([1, 0, -1]))
+ expected = CategoricalIndex([2, 1, 3], name="xxx")
+ tm.assert_index_equal(result, expected)
+ tm.assert_categorical_equal(result.values, expected.values)
+
+ # fill_value
+ result = idx.take(np.array([1, 0, -1]), fill_value=True)
+ expected = CategoricalIndex([2, 1, np.nan], categories=[1, 2, 3], name="xxx")
+ tm.assert_index_equal(result, expected)
+ tm.assert_categorical_equal(result.values, expected.values)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = CategoricalIndex([2, 1, 3], name="xxx")
+ tm.assert_index_equal(result, expected)
+ tm.assert_categorical_equal(result.values, expected.values)
+
+ # object category
+ idx = CategoricalIndex(
+ list("CBA"), categories=list("ABC"), ordered=True, name="xxx"
+ )
+ result = idx.take(np.array([1, 0, -1]))
+ expected = CategoricalIndex(
+ list("BCA"), categories=list("ABC"), ordered=True, name="xxx"
+ )
+ tm.assert_index_equal(result, expected)
+ tm.assert_categorical_equal(result.values, expected.values)
+
+ # fill_value
+ result = idx.take(np.array([1, 0, -1]), fill_value=True)
+ expected = CategoricalIndex(
+ ["B", "C", np.nan], categories=list("ABC"), ordered=True, name="xxx"
+ )
+ tm.assert_index_equal(result, expected)
+ tm.assert_categorical_equal(result.values, expected.values)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = CategoricalIndex(
+ list("BCA"), categories=list("ABC"), ordered=True, name="xxx"
+ )
+ tm.assert_index_equal(result, expected)
+ tm.assert_categorical_equal(result.values, expected.values)
+
+ msg = (
+ "When allow_fill=True and fill_value is not None, all indices must be >= -1"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ msg = "index -5 is out of bounds for (axis 0 with )?size 3"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
+
+ def test_take_fill_value_datetime(self):
+ # datetime category
+ idx = pd.DatetimeIndex(["2011-01-01", "2011-02-01", "2011-03-01"], name="xxx")
+ idx = CategoricalIndex(idx)
+ result = idx.take(np.array([1, 0, -1]))
+ expected = pd.DatetimeIndex(
+ ["2011-02-01", "2011-01-01", "2011-03-01"], name="xxx"
+ )
+ expected = CategoricalIndex(expected)
+ tm.assert_index_equal(result, expected)
+
+ # fill_value
+ result = idx.take(np.array([1, 0, -1]), fill_value=True)
+ expected = pd.DatetimeIndex(["2011-02-01", "2011-01-01", "NaT"], name="xxx")
+ exp_cats = pd.DatetimeIndex(["2011-01-01", "2011-02-01", "2011-03-01"])
+ expected = CategoricalIndex(expected, categories=exp_cats)
+ tm.assert_index_equal(result, expected)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = pd.DatetimeIndex(
+ ["2011-02-01", "2011-01-01", "2011-03-01"], name="xxx"
+ )
+ expected = CategoricalIndex(expected)
+ tm.assert_index_equal(result, expected)
+
+ msg = (
+ "When allow_fill=True and fill_value is not None, all indices must be >= -1"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ msg = "index -5 is out of bounds for (axis 0 with )?size 3"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
+
+ def test_take_invalid_kwargs(self):
+ idx = CategoricalIndex([1, 2, 3], name="foo")
+ indices = [1, 0, -1]
+
+ msg = r"take\(\) got an unexpected keyword argument 'foo'"
+ with pytest.raises(TypeError, match=msg):
+ idx.take(indices, foo=2)
+
+ msg = "the 'out' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, out=indices)
+
+ msg = "the 'mode' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, mode="clip")
+
+
+class TestGetLoc:
+ def test_get_loc(self):
+ # GH 12531
+ cidx1 = CategoricalIndex(list("abcde"), categories=list("edabc"))
+ idx1 = Index(list("abcde"))
+ assert cidx1.get_loc("a") == idx1.get_loc("a")
+ assert cidx1.get_loc("e") == idx1.get_loc("e")
+
+ for i in [cidx1, idx1]:
+ with pytest.raises(KeyError, match="'NOT-EXIST'"):
+ i.get_loc("NOT-EXIST")
+
+ # non-unique
+ cidx2 = CategoricalIndex(list("aacded"), categories=list("edabc"))
+ idx2 = Index(list("aacded"))
+
+ # results in bool array
+ res = cidx2.get_loc("d")
+ tm.assert_numpy_array_equal(res, idx2.get_loc("d"))
+ tm.assert_numpy_array_equal(
+ res, np.array([False, False, False, True, False, True])
+ )
+ # unique element results in scalar
+ res = cidx2.get_loc("e")
+ assert res == idx2.get_loc("e")
+ assert res == 4
+
+ for i in [cidx2, idx2]:
+ with pytest.raises(KeyError, match="'NOT-EXIST'"):
+ i.get_loc("NOT-EXIST")
+
+ # non-unique, sliceable
+ cidx3 = CategoricalIndex(list("aabbb"), categories=list("abc"))
+ idx3 = Index(list("aabbb"))
+
+ # results in slice
+ res = cidx3.get_loc("a")
+ assert res == idx3.get_loc("a")
+ assert res == slice(0, 2, None)
+
+ res = cidx3.get_loc("b")
+ assert res == idx3.get_loc("b")
+ assert res == slice(2, 5, None)
+
+ for i in [cidx3, idx3]:
+ with pytest.raises(KeyError, match="'c'"):
+ i.get_loc("c")
+
+ def test_get_loc_unique(self):
+ cidx = CategoricalIndex(list("abc"))
+ result = cidx.get_loc("b")
+ assert result == 1
+
+ def test_get_loc_monotonic_nonunique(self):
+ cidx = CategoricalIndex(list("abbc"))
+ result = cidx.get_loc("b")
+ expected = slice(1, 3, None)
+ assert result == expected
+
+ def test_get_loc_nonmonotonic_nonunique(self):
+ cidx = CategoricalIndex(list("abcb"))
+ result = cidx.get_loc("b")
+ expected = np.array([False, True, False, True], dtype=bool)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_loc_nan(self):
+ # GH#41933
+ ci = CategoricalIndex(["A", "B", np.nan])
+ res = ci.get_loc(np.nan)
+
+ assert res == 2
+
+
+class TestGetIndexer:
+ def test_get_indexer_base(self):
+ # Determined by cat ordering.
+ idx = CategoricalIndex(list("cab"), categories=list("cab"))
+ expected = np.arange(len(idx), dtype=np.intp)
+
+ actual = idx.get_indexer(idx)
+ tm.assert_numpy_array_equal(expected, actual)
+
+ with pytest.raises(ValueError, match="Invalid fill method"):
+ idx.get_indexer(idx, method="invalid")
+
+ def test_get_indexer_requires_unique(self):
+ ci = CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
+ oidx = Index(np.array(ci))
+
+ msg = "Reindexing only valid with uniquely valued Index objects"
+
+ for n in [1, 2, 5, len(ci)]:
+ finder = oidx[np.random.default_rng(2).integers(0, len(ci), size=n)]
+
+ with pytest.raises(InvalidIndexError, match=msg):
+ ci.get_indexer(finder)
+
+ # see gh-17323
+ #
+ # Even when indexer is equal to the
+ # members in the index, we should
+ # respect duplicates instead of taking
+ # the fast-track path.
+ for finder in [list("aabbca"), list("aababca")]:
+ with pytest.raises(InvalidIndexError, match=msg):
+ ci.get_indexer(finder)
+
+ def test_get_indexer_non_unique(self):
+ idx1 = CategoricalIndex(list("aabcde"), categories=list("edabc"))
+ idx2 = CategoricalIndex(list("abf"))
+
+ for indexer in [idx2, list("abf"), Index(list("abf"))]:
+ msg = "Reindexing only valid with uniquely valued Index objects"
+ with pytest.raises(InvalidIndexError, match=msg):
+ idx1.get_indexer(indexer)
+
+ r1, _ = idx1.get_indexer_non_unique(indexer)
+ expected = np.array([0, 1, 2, -1], dtype=np.intp)
+ tm.assert_almost_equal(r1, expected)
+
+ def test_get_indexer_method(self):
+ idx1 = CategoricalIndex(list("aabcde"), categories=list("edabc"))
+ idx2 = CategoricalIndex(list("abf"))
+
+ msg = "method pad not yet implemented for CategoricalIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ idx2.get_indexer(idx1, method="pad")
+ msg = "method backfill not yet implemented for CategoricalIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ idx2.get_indexer(idx1, method="backfill")
+
+ msg = "method nearest not yet implemented for CategoricalIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ idx2.get_indexer(idx1, method="nearest")
+
+ def test_get_indexer_array(self):
+ arr = np.array(
+ [Timestamp("1999-12-31 00:00:00"), Timestamp("2000-12-31 00:00:00")],
+ dtype=object,
+ )
+ cats = [Timestamp("1999-12-31 00:00:00"), Timestamp("2000-12-31 00:00:00")]
+ ci = CategoricalIndex(cats, categories=cats, ordered=False, dtype="category")
+ result = ci.get_indexer(arr)
+ expected = np.array([0, 1], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer_same_categories_same_order(self):
+ ci = CategoricalIndex(["a", "b"], categories=["a", "b"])
+
+ result = ci.get_indexer(CategoricalIndex(["b", "b"], categories=["a", "b"]))
+ expected = np.array([1, 1], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer_same_categories_different_order(self):
+ # https://github.com/pandas-dev/pandas/issues/19551
+ ci = CategoricalIndex(["a", "b"], categories=["a", "b"])
+
+ result = ci.get_indexer(CategoricalIndex(["b", "b"], categories=["b", "a"]))
+ expected = np.array([1, 1], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer_nans_in_index_and_target(self):
+ # GH 45361
+ ci = CategoricalIndex([1, 2, np.nan, 3])
+ other1 = [2, 3, 4, np.nan]
+ res1 = ci.get_indexer(other1)
+ expected1 = np.array([1, 3, -1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(res1, expected1)
+ other2 = [1, 4, 2, 3]
+ res2 = ci.get_indexer(other2)
+ expected2 = np.array([0, -1, 1, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(res2, expected2)
+
+
+class TestWhere:
+ def test_where(self, listlike_box):
+ klass = listlike_box
+
+ i = CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
+ cond = [True] * len(i)
+ expected = i
+ result = i.where(klass(cond))
+ tm.assert_index_equal(result, expected)
+
+ cond = [False] + [True] * (len(i) - 1)
+ expected = CategoricalIndex([np.nan, *i[1:].tolist()], categories=i.categories)
+ result = i.where(klass(cond))
+ tm.assert_index_equal(result, expected)
+
+ def test_where_non_categories(self):
+ ci = CategoricalIndex(["a", "b", "c", "d"])
+ mask = np.array([True, False, True, False])
+
+ result = ci.where(mask, 2)
+ expected = Index(["a", 2, "c", 2], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ msg = "Cannot setitem on a Categorical with a new category"
+ with pytest.raises(TypeError, match=msg):
+ # Test the Categorical method directly
+ ci._data._where(mask, 2)
+
+
+class TestContains:
+ def test_contains(self):
+ ci = CategoricalIndex(list("aabbca"), categories=list("cabdef"), ordered=False)
+
+ assert "a" in ci
+ assert "z" not in ci
+ assert "e" not in ci
+ assert np.nan not in ci
+
+ # assert codes NOT in index
+ assert 0 not in ci
+ assert 1 not in ci
+
+ def test_contains_nan(self):
+ ci = CategoricalIndex([*list("aabbca"), np.nan], categories=list("cabdef"))
+ assert np.nan in ci
+
+ @pytest.mark.parametrize("unwrap", [True, False])
+ def test_contains_na_dtype(self, unwrap):
+ dti = pd.date_range("2016-01-01", periods=100).insert(0, pd.NaT)
+ pi = dti.to_period("D")
+ tdi = dti - dti[-1]
+ ci = CategoricalIndex(dti)
+
+ obj = ci
+ if unwrap:
+ obj = ci._data
+
+ assert np.nan in obj
+ assert None in obj
+ assert pd.NaT in obj
+ assert np.datetime64("NaT") in obj
+ assert np.timedelta64("NaT") not in obj
+
+ obj2 = CategoricalIndex(tdi)
+ if unwrap:
+ obj2 = obj2._data
+
+ assert np.nan in obj2
+ assert None in obj2
+ assert pd.NaT in obj2
+ assert np.datetime64("NaT") not in obj2
+ assert np.timedelta64("NaT") in obj2
+
+ obj3 = CategoricalIndex(pi)
+ if unwrap:
+ obj3 = obj3._data
+
+ assert np.nan in obj3
+ assert None in obj3
+ assert pd.NaT in obj3
+ assert np.datetime64("NaT") not in obj3
+ assert np.timedelta64("NaT") not in obj3
+
+ @pytest.mark.parametrize(
+ "item, expected",
+ [
+ (pd.Interval(0, 1), True),
+ (1.5, True),
+ (pd.Interval(0.5, 1.5), False),
+ ("a", False),
+ (Timestamp(1), False),
+ (pd.Timedelta(1), False),
+ ],
+ ids=str,
+ )
+ def test_contains_interval(self, item, expected):
+ # GH 23705
+ ci = CategoricalIndex(IntervalIndex.from_breaks(range(3)))
+ result = item in ci
+ assert result is expected
+
+ def test_contains_list(self):
+ # GH#21729
+ idx = CategoricalIndex([1, 2, 3])
+
+ assert "a" not in idx
+
+ with pytest.raises(TypeError, match="unhashable type"):
+ ["a"] in idx
+
+ with pytest.raises(TypeError, match="unhashable type"):
+ ["a", "b"] in idx
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_map.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_map.py
new file mode 100644
index 0000000000000000000000000000000000000000..baf836594dfb5e03332b57522f39a679ee5b1e40
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_map.py
@@ -0,0 +1,144 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "data, categories",
+ [
+ (list("abcbca"), list("cab")),
+ (pd.interval_range(0, 3).repeat(3), pd.interval_range(0, 3)),
+ ],
+ ids=["string", "interval"],
+)
+def test_map_str(data, categories, ordered):
+ # GH 31202 - override base class since we want to maintain categorical/ordered
+ index = CategoricalIndex(data, categories=categories, ordered=ordered)
+ result = index.map(str)
+ expected = CategoricalIndex(
+ map(str, data), categories=map(str, categories), ordered=ordered
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_map():
+ ci = CategoricalIndex(list("ABABC"), categories=list("CBA"), ordered=True)
+ result = ci.map(lambda x: x.lower())
+ exp = CategoricalIndex(list("ababc"), categories=list("cba"), ordered=True)
+ tm.assert_index_equal(result, exp)
+
+ ci = CategoricalIndex(
+ list("ABABC"), categories=list("BAC"), ordered=False, name="XXX"
+ )
+ result = ci.map(lambda x: x.lower())
+ exp = CategoricalIndex(
+ list("ababc"), categories=list("bac"), ordered=False, name="XXX"
+ )
+ tm.assert_index_equal(result, exp)
+
+ # GH 12766: Return an index not an array
+ tm.assert_index_equal(
+ ci.map(lambda x: 1), Index(np.array([1] * 5, dtype=np.int64), name="XXX")
+ )
+
+ # change categories dtype
+ ci = CategoricalIndex(list("ABABC"), categories=list("BAC"), ordered=False)
+
+ def f(x):
+ return {"A": 10, "B": 20, "C": 30}.get(x)
+
+ result = ci.map(f)
+ exp = CategoricalIndex([10, 20, 10, 20, 30], categories=[20, 10, 30], ordered=False)
+ tm.assert_index_equal(result, exp)
+
+ result = ci.map(Series([10, 20, 30], index=["A", "B", "C"]))
+ tm.assert_index_equal(result, exp)
+
+ result = ci.map({"A": 10, "B": 20, "C": 30})
+ tm.assert_index_equal(result, exp)
+
+
+def test_map_with_categorical_series():
+ # GH 12756
+ a = Index([1, 2, 3, 4])
+ b = Series(["even", "odd", "even", "odd"], dtype="category")
+ c = Series(["even", "odd", "even", "odd"])
+
+ exp = CategoricalIndex(["odd", "even", "odd", np.nan])
+ tm.assert_index_equal(a.map(b), exp)
+ exp = Index(["odd", "even", "odd", np.nan])
+ tm.assert_index_equal(a.map(c), exp)
+
+
+@pytest.mark.parametrize(
+ ("data", "f", "expected"),
+ (
+ ([1, 1, np.nan], pd.isna, CategoricalIndex([False, False, np.nan])),
+ ([1, 2, np.nan], pd.isna, Index([False, False, np.nan])),
+ ([1, 1, np.nan], {1: False}, CategoricalIndex([False, False, np.nan])),
+ ([1, 2, np.nan], {1: False, 2: False}, Index([False, False, np.nan])),
+ (
+ [1, 1, np.nan],
+ Series([False, False]),
+ CategoricalIndex([False, False, np.nan]),
+ ),
+ (
+ [1, 2, np.nan],
+ Series([False, False, False]),
+ Index([False, False, np.nan]),
+ ),
+ ),
+)
+def test_map_with_nan_ignore(data, f, expected): # GH 24241
+ values = CategoricalIndex(data)
+ result = values.map(f, na_action="ignore")
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ ("data", "f", "expected"),
+ (
+ ([1, 1, np.nan], pd.isna, Index([False, False, True])),
+ ([1, 2, np.nan], pd.isna, Index([False, False, True])),
+ ([1, 1, np.nan], {1: False}, CategoricalIndex([False, False, np.nan])),
+ ([1, 2, np.nan], {1: False, 2: False}, Index([False, False, np.nan])),
+ (
+ [1, 1, np.nan],
+ Series([False, False]),
+ CategoricalIndex([False, False, np.nan]),
+ ),
+ (
+ [1, 2, np.nan],
+ Series([False, False, False]),
+ Index([False, False, np.nan]),
+ ),
+ ),
+)
+def test_map_with_nan_none(data, f, expected): # GH 24241
+ values = CategoricalIndex(data)
+ result = values.map(f, na_action=None)
+ tm.assert_index_equal(result, expected)
+
+
+def test_map_with_dict_or_series():
+ orig_values = ["a", "B", 1, "a"]
+ new_values = ["one", 2, 3.0, "one"]
+ cur_index = CategoricalIndex(orig_values, name="XXX")
+ expected = CategoricalIndex(new_values, name="XXX", categories=[3.0, 2, "one"])
+
+ mapper = Series(new_values[:-1], index=orig_values[:-1])
+ result = cur_index.map(mapper)
+ # Order of categories in result can be different
+ tm.assert_index_equal(result, expected)
+
+ mapper = dict(zip(orig_values[:-1], new_values[:-1]))
+ result = cur_index.map(mapper)
+ # Order of categories in result can be different
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_reindex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_reindex.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b1f2b9fb159a6873c83e0a0a4e777913bb99fee
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_reindex.py
@@ -0,0 +1,78 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ CategoricalIndex,
+ Index,
+ Interval,
+)
+import pandas._testing as tm
+
+
+class TestReindex:
+ def test_reindex_list_non_unique(self):
+ # GH#11586
+ msg = "cannot reindex on an axis with duplicate labels"
+ ci = CategoricalIndex(["a", "b", "c", "a"])
+ with pytest.raises(ValueError, match=msg):
+ ci.reindex(["a", "c"])
+
+ def test_reindex_categorical_non_unique(self):
+ msg = "cannot reindex on an axis with duplicate labels"
+ ci = CategoricalIndex(["a", "b", "c", "a"])
+ with pytest.raises(ValueError, match=msg):
+ ci.reindex(Categorical(["a", "c"]))
+
+ def test_reindex_list_non_unique_unused_category(self):
+ msg = "cannot reindex on an axis with duplicate labels"
+ ci = CategoricalIndex(["a", "b", "c", "a"], categories=["a", "b", "c", "d"])
+ with pytest.raises(ValueError, match=msg):
+ ci.reindex(["a", "c"])
+
+ def test_reindex_categorical_non_unique_unused_category(self):
+ msg = "cannot reindex on an axis with duplicate labels"
+ ci = CategoricalIndex(["a", "b", "c", "a"], categories=["a", "b", "c", "d"])
+ with pytest.raises(ValueError, match=msg):
+ ci.reindex(Categorical(["a", "c"]))
+
+ def test_reindex_duplicate_target(self):
+ # See GH25459
+ cat = CategoricalIndex(["a", "b", "c"], categories=["a", "b", "c", "d"])
+ res, indexer = cat.reindex(["a", "c", "c"])
+ exp = Index(["a", "c", "c"])
+ tm.assert_index_equal(res, exp, exact=True)
+ tm.assert_numpy_array_equal(indexer, np.array([0, 2, 2], dtype=np.intp))
+
+ res, indexer = cat.reindex(
+ CategoricalIndex(["a", "c", "c"], categories=["a", "b", "c", "d"])
+ )
+ exp = CategoricalIndex(["a", "c", "c"], categories=["a", "b", "c", "d"])
+ tm.assert_index_equal(res, exp, exact=True)
+ tm.assert_numpy_array_equal(indexer, np.array([0, 2, 2], dtype=np.intp))
+
+ def test_reindex_empty_index(self):
+ # See GH16770
+ c = CategoricalIndex([])
+ res, indexer = c.reindex(["a", "b"])
+ tm.assert_index_equal(res, Index(["a", "b"]), exact=True)
+ tm.assert_numpy_array_equal(indexer, np.array([-1, -1], dtype=np.intp))
+
+ def test_reindex_categorical_added_category(self):
+ # GH 42424
+ ci = CategoricalIndex(
+ [Interval(0, 1, closed="right"), Interval(1, 2, closed="right")],
+ ordered=True,
+ )
+ ci_add = CategoricalIndex(
+ [
+ Interval(0, 1, closed="right"),
+ Interval(1, 2, closed="right"),
+ Interval(2, 3, closed="right"),
+ Interval(3, 4, closed="right"),
+ ],
+ ordered=True,
+ )
+ result, _ = ci.reindex(ci_add)
+ expected = ci_add
+ tm.assert_index_equal(expected, result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e87b90efd54c8fcc4dcab7ec538d461add370de
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/categorical/test_setops.py
@@ -0,0 +1,18 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ CategoricalIndex,
+ Index,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("na_value", [None, np.nan])
+def test_difference_with_na(na_value):
+ # GH 57318
+ ci = CategoricalIndex(["a", "b", "c", None])
+ other = Index(["c", na_value])
+ result = ci.difference(other)
+ expected = CategoricalIndex(["a", "b"], categories=["a", "b", "c"])
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..61f6be000cee88b55fae5b6b657e112df23af1a8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/conftest.py
@@ -0,0 +1,40 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Series,
+ array,
+)
+
+
+@pytest.fixture(params=[None, False])
+def sort(request):
+ """
+ Valid values for the 'sort' parameter used in the Index
+ setops methods (intersection, union, etc.)
+
+ Caution:
+ Don't confuse this one with the "sort" fixture used
+ for concat. That one has parameters [True, False].
+
+ We can't combine them as sort=True is not permitted
+ in the Index setops methods.
+ """
+ return request.param
+
+
+@pytest.fixture(params=["D", "3D", "-3D", "h", "2h", "-2h", "min", "2min", "s", "-3s"])
+def freq_sample(request):
+ """
+ Valid values for 'freq' parameter used to create date_range and
+ timedelta_range..
+ """
+ return request.param
+
+
+@pytest.fixture(params=[list, tuple, np.array, array, Series])
+def listlike_box(request):
+ """
+ Types that may be passed as the indexer to searchsorted.
+ """
+ return request.param
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_drop_duplicates.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_drop_duplicates.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2d76c0bcc8bd4704aaa70f522047efd905dea16
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_drop_duplicates.py
@@ -0,0 +1,101 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ PeriodIndex,
+ Series,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class DropDuplicates:
+ def test_drop_duplicates_metadata(self, idx):
+ # GH#10115
+ result = idx.drop_duplicates()
+ tm.assert_index_equal(idx, result)
+ assert idx.freq == result.freq
+
+ idx_dup = idx.append(idx)
+ result = idx_dup.drop_duplicates()
+
+ expected = idx
+ if not isinstance(idx, PeriodIndex):
+ # freq is reset except for PeriodIndex
+ assert idx_dup.freq is None
+ assert result.freq is None
+ expected = idx._with_freq(None)
+ else:
+ assert result.freq == expected.freq
+
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "keep, expected, index",
+ [
+ (
+ "first",
+ np.concatenate(([False] * 10, [True] * 5)),
+ np.arange(0, 10, dtype=np.int64),
+ ),
+ (
+ "last",
+ np.concatenate(([True] * 5, [False] * 10)),
+ np.arange(5, 15, dtype=np.int64),
+ ),
+ (
+ False,
+ np.concatenate(([True] * 5, [False] * 5, [True] * 5)),
+ np.arange(5, 10, dtype=np.int64),
+ ),
+ ],
+ )
+ def test_drop_duplicates(self, keep, expected, index, idx):
+ # to check Index/Series compat
+ idx = idx.append(idx[:5])
+
+ tm.assert_numpy_array_equal(idx.duplicated(keep=keep), expected)
+ expected = idx[~expected]
+
+ result = idx.drop_duplicates(keep=keep)
+ tm.assert_index_equal(result, expected)
+
+ result = Series(idx).drop_duplicates(keep=keep)
+ expected = Series(expected, index=index)
+ tm.assert_series_equal(result, expected)
+
+
+class TestDropDuplicatesPeriodIndex(DropDuplicates):
+ @pytest.fixture(params=["D", "3D", "h", "2h", "min", "2min", "s", "3s"])
+ def freq(self, request):
+ """
+ Fixture to test for different frequencies for PeriodIndex.
+ """
+ return request.param
+
+ @pytest.fixture
+ def idx(self, freq):
+ """
+ Fixture to get PeriodIndex for 10 periods for different frequencies.
+ """
+ return period_range("2011-01-01", periods=10, freq=freq, name="idx")
+
+
+class TestDropDuplicatesDatetimeIndex(DropDuplicates):
+ @pytest.fixture
+ def idx(self, freq_sample):
+ """
+ Fixture to get DatetimeIndex for 10 periods for different frequencies.
+ """
+ return date_range("2011-01-01", freq=freq_sample, periods=10, name="idx")
+
+
+class TestDropDuplicatesTimedeltaIndex(DropDuplicates):
+ @pytest.fixture
+ def idx(self, freq_sample):
+ """
+ Fixture to get TimedeltaIndex for 10 periods for different frequencies.
+ """
+ return timedelta_range("1 day", periods=10, freq=freq_sample, name="idx")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_equals.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_equals.py
new file mode 100644
index 0000000000000000000000000000000000000000..df9182b2dd1c28281d97770cefc6e4de26e51fa2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_equals.py
@@ -0,0 +1,185 @@
+"""
+Tests shared for DatetimeIndex/TimedeltaIndex/PeriodIndex
+"""
+
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ DatetimeIndex,
+ Index,
+ PeriodIndex,
+ TimedeltaIndex,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class EqualsTests:
+ def test_not_equals_numeric(self, index):
+ assert not index.equals(Index(index.asi8))
+ assert not index.equals(Index(index.asi8.astype("u8")))
+ assert not index.equals(Index(index.asi8).astype("f8"))
+
+ def test_equals(self, index):
+ assert index.equals(index)
+ assert index.equals(index.astype(object))
+ assert index.equals(CategoricalIndex(index))
+ assert index.equals(CategoricalIndex(index.astype(object)))
+
+ def test_not_equals_non_arraylike(self, index):
+ assert not index.equals(list(index))
+
+ def test_not_equals_strings(self, index):
+ other = Index([str(x) for x in index], dtype=object)
+ assert not index.equals(other)
+ assert not index.equals(CategoricalIndex(other))
+
+ def test_not_equals_misc_strs(self, index):
+ other = Index(list("abc"))
+ assert not index.equals(other)
+
+
+class TestPeriodIndexEquals(EqualsTests):
+ @pytest.fixture
+ def index(self):
+ """Fixture for creating a PeriodIndex for use in equality tests."""
+ return period_range("2013-01-01", periods=5, freq="D")
+
+ # TODO: de-duplicate with other test_equals2 methods
+ @pytest.mark.parametrize("freq", ["D", "M"])
+ def test_equals2(self, freq):
+ # GH#13107
+ idx = PeriodIndex(["2011-01-01", "2011-01-02", "NaT"], freq=freq)
+ assert idx.equals(idx)
+ assert idx.equals(idx.copy())
+ assert idx.equals(idx.astype(object))
+ assert idx.astype(object).equals(idx)
+ assert idx.astype(object).equals(idx.astype(object))
+ assert not idx.equals(list(idx))
+ assert not idx.equals(pd.Series(idx))
+
+ idx2 = PeriodIndex(["2011-01-01", "2011-01-02", "NaT"], freq="h")
+ assert not idx.equals(idx2)
+ assert not idx.equals(idx2.copy())
+ assert not idx.equals(idx2.astype(object))
+ assert not idx.astype(object).equals(idx2)
+ assert not idx.equals(list(idx2))
+ assert not idx.equals(pd.Series(idx2))
+
+ # same internal, different tz
+ idx3 = PeriodIndex._simple_new(
+ idx._values._simple_new(idx._values.asi8, dtype=pd.PeriodDtype("h"))
+ )
+ tm.assert_numpy_array_equal(idx.asi8, idx3.asi8)
+ assert not idx.equals(idx3)
+ assert not idx.equals(idx3.copy())
+ assert not idx.equals(idx3.astype(object))
+ assert not idx.astype(object).equals(idx3)
+ assert not idx.equals(list(idx3))
+ assert not idx.equals(pd.Series(idx3))
+
+
+class TestDatetimeIndexEquals(EqualsTests):
+ @pytest.fixture
+ def index(self):
+ """Fixture for creating a DatetimeIndex for use in equality tests."""
+ return date_range("2013-01-01", periods=5)
+
+ def test_equals2(self):
+ # GH#13107
+ idx = DatetimeIndex(["2011-01-01", "2011-01-02", "NaT"])
+ assert idx.equals(idx)
+ assert idx.equals(idx.copy())
+ assert idx.equals(idx.astype(object))
+ assert idx.astype(object).equals(idx)
+ assert idx.astype(object).equals(idx.astype(object))
+ assert not idx.equals(list(idx))
+ assert not idx.equals(pd.Series(idx))
+
+ idx2 = DatetimeIndex(["2011-01-01", "2011-01-02", "NaT"], tz="US/Pacific")
+ assert not idx.equals(idx2)
+ assert not idx.equals(idx2.copy())
+ assert not idx.equals(idx2.astype(object))
+ assert not idx.astype(object).equals(idx2)
+ assert not idx.equals(list(idx2))
+ assert not idx.equals(pd.Series(idx2))
+
+ # same internal, different tz
+ idx3 = DatetimeIndex(idx.asi8, tz="US/Pacific")
+ tm.assert_numpy_array_equal(idx.asi8, idx3.asi8)
+ assert not idx.equals(idx3)
+ assert not idx.equals(idx3.copy())
+ assert not idx.equals(idx3.astype(object))
+ assert not idx.astype(object).equals(idx3)
+ assert not idx.equals(list(idx3))
+ assert not idx.equals(pd.Series(idx3))
+
+ # check that we do not raise when comparing with OutOfBounds objects
+ oob = Index([datetime(2500, 1, 1)] * 3, dtype=object)
+ assert not idx.equals(oob)
+ assert not idx2.equals(oob)
+ assert not idx3.equals(oob)
+
+ # check that we do not raise when comparing with OutOfBounds dt64
+ oob2 = oob.map(np.datetime64)
+ assert not idx.equals(oob2)
+ assert not idx2.equals(oob2)
+ assert not idx3.equals(oob2)
+
+ @pytest.mark.parametrize("freq", ["B", "C"])
+ def test_not_equals_bday(self, freq):
+ rng = date_range("2009-01-01", "2010-01-01", freq=freq)
+ assert not rng.equals(list(rng))
+
+
+class TestTimedeltaIndexEquals(EqualsTests):
+ @pytest.fixture
+ def index(self):
+ """Fixture for creating a TimedeltaIndex for use in equality tests."""
+ return timedelta_range("1 day", periods=10)
+
+ def test_equals2(self):
+ # GH#13107
+ idx = TimedeltaIndex(["1 days", "2 days", "NaT"])
+ assert idx.equals(idx)
+ assert idx.equals(idx.copy())
+ assert idx.equals(idx.astype(object))
+ assert idx.astype(object).equals(idx)
+ assert idx.astype(object).equals(idx.astype(object))
+ assert not idx.equals(list(idx))
+ assert not idx.equals(pd.Series(idx))
+
+ idx2 = TimedeltaIndex(["2 days", "1 days", "NaT"])
+ assert not idx.equals(idx2)
+ assert not idx.equals(idx2.copy())
+ assert not idx.equals(idx2.astype(object))
+ assert not idx.astype(object).equals(idx2)
+ assert not idx.astype(object).equals(idx2.astype(object))
+ assert not idx.equals(list(idx2))
+ assert not idx.equals(pd.Series(idx2))
+
+ # Check that we dont raise OverflowError on comparisons outside the
+ # implementation range GH#28532
+ oob = Index([timedelta(days=10**6)] * 3, dtype=object)
+ assert not idx.equals(oob)
+ assert not idx2.equals(oob)
+
+ oob2 = Index([np.timedelta64(x) for x in oob], dtype=object)
+ assert (oob == oob2).all()
+ assert not idx.equals(oob2)
+ assert not idx2.equals(oob2)
+
+ oob3 = oob.map(np.timedelta64)
+ assert (oob3 == oob).all()
+ assert not idx.equals(oob3)
+ assert not idx2.equals(oob3)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b2c81aaf17de3785e62a2d989394259b2496085
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_indexing.py
@@ -0,0 +1,45 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Index,
+)
+import pandas._testing as tm
+
+dtlike_dtypes = [
+ np.dtype("timedelta64[ns]"),
+ np.dtype("datetime64[ns]"),
+ pd.DatetimeTZDtype("ns", "Asia/Tokyo"),
+ pd.PeriodDtype("ns"),
+]
+
+
+@pytest.mark.parametrize("ldtype", dtlike_dtypes)
+@pytest.mark.parametrize("rdtype", dtlike_dtypes)
+def test_get_indexer_non_unique_wrong_dtype(ldtype, rdtype):
+ vals = np.tile(3600 * 10**9 * np.arange(3, dtype=np.int64), 2)
+
+ def construct(dtype):
+ if dtype is dtlike_dtypes[-1]:
+ # PeriodArray will try to cast ints to strings
+ return DatetimeIndex(vals).astype(dtype)
+ return Index(vals, dtype=dtype)
+
+ left = construct(ldtype)
+ right = construct(rdtype)
+
+ result = left.get_indexer_non_unique(right)
+
+ if ldtype is rdtype:
+ ex1 = np.array([0, 3, 1, 4, 2, 5] * 2, dtype=np.intp)
+ ex2 = np.array([], dtype=np.intp)
+ tm.assert_numpy_array_equal(result[0], ex1)
+ tm.assert_numpy_array_equal(result[1], ex2)
+
+ else:
+ no_matches = np.array([-1] * 6, dtype=np.intp)
+ missing = np.arange(6, dtype=np.intp)
+ tm.assert_numpy_array_equal(result[0], no_matches)
+ tm.assert_numpy_array_equal(result[1], missing)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_is_monotonic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_is_monotonic.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0e42e660b751cddaca74c4574e9588e8ac8c782
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_is_monotonic.py
@@ -0,0 +1,46 @@
+from pandas import (
+ Index,
+ NaT,
+ date_range,
+)
+
+
+def test_is_monotonic_with_nat():
+ # GH#31437
+ # PeriodIndex.is_monotonic_increasing should behave analogously to DatetimeIndex,
+ # in particular never be monotonic when we have NaT
+ dti = date_range("2016-01-01", periods=3)
+ pi = dti.to_period("D")
+ tdi = Index(dti.view("timedelta64[ns]"))
+
+ for obj in [pi, pi._engine, dti, dti._engine, tdi, tdi._engine]:
+ if isinstance(obj, Index):
+ # i.e. not Engines
+ assert obj.is_monotonic_increasing
+ assert obj.is_monotonic_increasing
+ assert not obj.is_monotonic_decreasing
+ assert obj.is_unique
+
+ dti1 = dti.insert(0, NaT)
+ pi1 = dti1.to_period("D")
+ tdi1 = Index(dti1.view("timedelta64[ns]"))
+
+ for obj in [pi1, pi1._engine, dti1, dti1._engine, tdi1, tdi1._engine]:
+ if isinstance(obj, Index):
+ # i.e. not Engines
+ assert not obj.is_monotonic_increasing
+ assert not obj.is_monotonic_increasing
+ assert not obj.is_monotonic_decreasing
+ assert obj.is_unique
+
+ dti2 = dti.insert(3, NaT)
+ pi2 = dti2.to_period("h")
+ tdi2 = Index(dti2.view("timedelta64[ns]"))
+
+ for obj in [pi2, pi2._engine, dti2, dti2._engine, tdi2, tdi2._engine]:
+ if isinstance(obj, Index):
+ # i.e. not Engines
+ assert not obj.is_monotonic_increasing
+ assert not obj.is_monotonic_increasing
+ assert not obj.is_monotonic_decreasing
+ assert obj.is_unique
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_nat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_nat.py
new file mode 100644
index 0000000000000000000000000000000000000000..3dd0ce1cbd637b7d9d3fb8c0eef058ec5e6dc838
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_nat.py
@@ -0,0 +1,42 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ NaT,
+ PeriodIndex,
+ TimedeltaIndex,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "index_without_na",
+ [
+ TimedeltaIndex(["1 days", "2 days"]),
+ PeriodIndex(["2011-01-01", "2011-01-02"], freq="D"),
+ DatetimeIndex(["2011-01-01", "2011-01-02"]),
+ DatetimeIndex(["2011-01-01", "2011-01-02"], tz="UTC"),
+ ],
+)
+def test_nat(index_without_na):
+ empty_index = index_without_na[:0]
+
+ index_with_na = index_without_na.copy(deep=True)
+ index_with_na._data[1] = NaT
+
+ assert empty_index._na_value is NaT
+ assert index_with_na._na_value is NaT
+ assert index_without_na._na_value is NaT
+
+ idx = index_without_na
+ assert idx._can_hold_na
+
+ tm.assert_numpy_array_equal(idx._isnan, np.array([False, False]))
+ assert idx.hasnans is False
+
+ idx = index_with_na
+ assert idx._can_hold_na
+
+ tm.assert_numpy_array_equal(idx._isnan, np.array([False, True]))
+ assert idx.hasnans is True
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_sort_values.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_sort_values.py
new file mode 100644
index 0000000000000000000000000000000000000000..bfef0faebeebf1ce12fc78ab37bc35820ca41d24
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_sort_values.py
@@ -0,0 +1,311 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ Index,
+ NaT,
+ PeriodIndex,
+ TimedeltaIndex,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+def check_freq_ascending(ordered, orig, ascending):
+ """
+ Check the expected freq on a PeriodIndex/DatetimeIndex/TimedeltaIndex
+ when the original index is generated (or generate-able) with
+ period_range/date_range/timedelta_range.
+ """
+ if isinstance(ordered, PeriodIndex):
+ assert ordered.freq == orig.freq
+ elif isinstance(ordered, (DatetimeIndex, TimedeltaIndex)):
+ if ascending:
+ assert ordered.freq.n == orig.freq.n
+ else:
+ assert ordered.freq.n == -1 * orig.freq.n
+
+
+def check_freq_nonmonotonic(ordered, orig):
+ """
+ Check the expected freq on a PeriodIndex/DatetimeIndex/TimedeltaIndex
+ when the original index is _not_ generated (or generate-able) with
+ period_range/date_range//timedelta_range.
+ """
+ if isinstance(ordered, PeriodIndex):
+ assert ordered.freq == orig.freq
+ elif isinstance(ordered, (DatetimeIndex, TimedeltaIndex)):
+ assert ordered.freq is None
+
+
+class TestSortValues:
+ @pytest.fixture(params=[DatetimeIndex, TimedeltaIndex, PeriodIndex])
+ def non_monotonic_idx(self, request):
+ if request.param is DatetimeIndex:
+ return DatetimeIndex(["2000-01-04", "2000-01-01", "2000-01-02"])
+ elif request.param is PeriodIndex:
+ dti = DatetimeIndex(["2000-01-04", "2000-01-01", "2000-01-02"])
+ return dti.to_period("D")
+ else:
+ return TimedeltaIndex(
+ ["1 day 00:00:05", "1 day 00:00:01", "1 day 00:00:02"]
+ )
+
+ def test_argmin_argmax(self, non_monotonic_idx):
+ assert non_monotonic_idx.argmin() == 1
+ assert non_monotonic_idx.argmax() == 0
+
+ def test_sort_values(self, non_monotonic_idx):
+ idx = non_monotonic_idx
+ ordered = idx.sort_values()
+ assert ordered.is_monotonic_increasing
+ ordered = idx.sort_values(ascending=False)
+ assert ordered[::-1].is_monotonic_increasing
+
+ ordered, dexer = idx.sort_values(return_indexer=True)
+ assert ordered.is_monotonic_increasing
+ tm.assert_numpy_array_equal(dexer, np.array([1, 2, 0], dtype=np.intp))
+
+ ordered, dexer = idx.sort_values(return_indexer=True, ascending=False)
+ assert ordered[::-1].is_monotonic_increasing
+ tm.assert_numpy_array_equal(dexer, np.array([0, 2, 1], dtype=np.intp))
+
+ def check_sort_values_with_freq(self, idx):
+ ordered = idx.sort_values()
+ tm.assert_index_equal(ordered, idx)
+ check_freq_ascending(ordered, idx, True)
+
+ ordered = idx.sort_values(ascending=False)
+ expected = idx[::-1]
+ tm.assert_index_equal(ordered, expected)
+ check_freq_ascending(ordered, idx, False)
+
+ ordered, indexer = idx.sort_values(return_indexer=True)
+ tm.assert_index_equal(ordered, idx)
+ tm.assert_numpy_array_equal(indexer, np.array([0, 1, 2], dtype=np.intp))
+ check_freq_ascending(ordered, idx, True)
+
+ ordered, indexer = idx.sort_values(return_indexer=True, ascending=False)
+ expected = idx[::-1]
+ tm.assert_index_equal(ordered, expected)
+ tm.assert_numpy_array_equal(indexer, np.array([2, 1, 0], dtype=np.intp))
+ check_freq_ascending(ordered, idx, False)
+
+ @pytest.mark.parametrize("freq", ["D", "h"])
+ def test_sort_values_with_freq_timedeltaindex(self, freq):
+ # GH#10295
+ idx = timedelta_range(start=f"1{freq}", periods=3, freq=freq).rename("idx")
+
+ self.check_sort_values_with_freq(idx)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [
+ DatetimeIndex(
+ ["2011-01-01", "2011-01-02", "2011-01-03"], freq="D", name="idx"
+ ),
+ DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", "2011-01-01 11:00"],
+ freq="h",
+ name="tzidx",
+ tz="Asia/Tokyo",
+ ),
+ ],
+ )
+ def test_sort_values_with_freq_datetimeindex(self, idx):
+ self.check_sort_values_with_freq(idx)
+
+ @pytest.mark.parametrize("freq", ["D", "2D", "4D"])
+ def test_sort_values_with_freq_periodindex(self, freq):
+ # here with_freq refers to being period_range-like
+ idx = PeriodIndex(
+ ["2011-01-01", "2011-01-02", "2011-01-03"], freq=freq, name="idx"
+ )
+ self.check_sort_values_with_freq(idx)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [
+ PeriodIndex(["2011", "2012", "2013"], name="pidx", freq="Y"),
+ Index([2011, 2012, 2013], name="idx"), # for compatibility check
+ ],
+ )
+ def test_sort_values_with_freq_periodindex2(self, idx):
+ # here with_freq indicates this is period_range-like
+ self.check_sort_values_with_freq(idx)
+
+ def check_sort_values_without_freq(self, idx, expected):
+ ordered = idx.sort_values(na_position="first")
+ tm.assert_index_equal(ordered, expected)
+ check_freq_nonmonotonic(ordered, idx)
+
+ if not idx.isna().any():
+ ordered = idx.sort_values()
+ tm.assert_index_equal(ordered, expected)
+ check_freq_nonmonotonic(ordered, idx)
+
+ ordered = idx.sort_values(ascending=False)
+ tm.assert_index_equal(ordered, expected[::-1])
+ check_freq_nonmonotonic(ordered, idx)
+
+ ordered, indexer = idx.sort_values(return_indexer=True, na_position="first")
+ tm.assert_index_equal(ordered, expected)
+
+ exp = np.array([0, 4, 3, 1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, exp)
+ check_freq_nonmonotonic(ordered, idx)
+
+ if not idx.isna().any():
+ ordered, indexer = idx.sort_values(return_indexer=True)
+ tm.assert_index_equal(ordered, expected)
+
+ exp = np.array([0, 4, 3, 1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, exp)
+ check_freq_nonmonotonic(ordered, idx)
+
+ ordered, indexer = idx.sort_values(return_indexer=True, ascending=False)
+ tm.assert_index_equal(ordered, expected[::-1])
+
+ exp = np.array([2, 1, 3, 0, 4], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, exp)
+ check_freq_nonmonotonic(ordered, idx)
+
+ def test_sort_values_without_freq_timedeltaindex(self):
+ # GH#10295
+
+ idx = TimedeltaIndex(
+ ["1 hour", "3 hour", "5 hour", "2 hour ", "1 hour"], name="idx1"
+ )
+ expected = TimedeltaIndex(
+ ["1 hour", "1 hour", "2 hour", "3 hour", "5 hour"], name="idx1"
+ )
+ self.check_sort_values_without_freq(idx, expected)
+
+ @pytest.mark.parametrize(
+ "index_dates,expected_dates",
+ [
+ (
+ ["2011-01-01", "2011-01-03", "2011-01-05", "2011-01-02", "2011-01-01"],
+ ["2011-01-01", "2011-01-01", "2011-01-02", "2011-01-03", "2011-01-05"],
+ ),
+ (
+ [NaT, "2011-01-03", "2011-01-05", "2011-01-02", NaT],
+ [NaT, NaT, "2011-01-02", "2011-01-03", "2011-01-05"],
+ ),
+ ],
+ )
+ def test_sort_values_without_freq_datetimeindex(
+ self, index_dates, expected_dates, tz_naive_fixture
+ ):
+ tz = tz_naive_fixture
+
+ # without freq
+ idx = DatetimeIndex(index_dates, tz=tz, name="idx")
+ expected = DatetimeIndex(expected_dates, tz=tz, name="idx")
+
+ self.check_sort_values_without_freq(idx, expected)
+
+ @pytest.mark.parametrize(
+ "idx,expected",
+ [
+ (
+ PeriodIndex(
+ [
+ "2011-01-01",
+ "2011-01-03",
+ "2011-01-05",
+ "2011-01-02",
+ "2011-01-01",
+ ],
+ freq="D",
+ name="idx1",
+ ),
+ PeriodIndex(
+ [
+ "2011-01-01",
+ "2011-01-01",
+ "2011-01-02",
+ "2011-01-03",
+ "2011-01-05",
+ ],
+ freq="D",
+ name="idx1",
+ ),
+ ),
+ (
+ PeriodIndex(
+ [
+ "2011-01-01",
+ "2011-01-03",
+ "2011-01-05",
+ "2011-01-02",
+ "2011-01-01",
+ ],
+ freq="D",
+ name="idx2",
+ ),
+ PeriodIndex(
+ [
+ "2011-01-01",
+ "2011-01-01",
+ "2011-01-02",
+ "2011-01-03",
+ "2011-01-05",
+ ],
+ freq="D",
+ name="idx2",
+ ),
+ ),
+ (
+ PeriodIndex(
+ [NaT, "2011-01-03", "2011-01-05", "2011-01-02", NaT],
+ freq="D",
+ name="idx3",
+ ),
+ PeriodIndex(
+ [NaT, NaT, "2011-01-02", "2011-01-03", "2011-01-05"],
+ freq="D",
+ name="idx3",
+ ),
+ ),
+ (
+ PeriodIndex(
+ ["2011", "2013", "2015", "2012", "2011"], name="pidx", freq="Y"
+ ),
+ PeriodIndex(
+ ["2011", "2011", "2012", "2013", "2015"], name="pidx", freq="Y"
+ ),
+ ),
+ (
+ # For compatibility check
+ Index([2011, 2013, 2015, 2012, 2011], name="idx"),
+ Index([2011, 2011, 2012, 2013, 2015], name="idx"),
+ ),
+ ],
+ )
+ def test_sort_values_without_freq_periodindex(self, idx, expected):
+ # here without_freq means not generateable by period_range
+ self.check_sort_values_without_freq(idx, expected)
+
+ def test_sort_values_without_freq_periodindex_nat(self):
+ # doesn't quite fit into check_sort_values_without_freq
+ idx = PeriodIndex(["2011", "2013", "NaT", "2011"], name="pidx", freq="D")
+ expected = PeriodIndex(["NaT", "2011", "2011", "2013"], name="pidx", freq="D")
+
+ ordered = idx.sort_values(na_position="first")
+ tm.assert_index_equal(ordered, expected)
+ check_freq_nonmonotonic(ordered, idx)
+
+ ordered = idx.sort_values(ascending=False)
+ tm.assert_index_equal(ordered, expected[::-1])
+ check_freq_nonmonotonic(ordered, idx)
+
+
+def test_order_stability_compat():
+ # GH#35922. sort_values is stable both for normal and datetime-like Index
+ pidx = PeriodIndex(["2011", "2013", "2015", "2012", "2011"], name="pidx", freq="Y")
+ iidx = Index([2011, 2013, 2015, 2012, 2011], name="idx")
+ ordered1, indexer1 = pidx.sort_values(return_indexer=True, ascending=False)
+ ordered2, indexer2 = iidx.sort_values(return_indexer=True, ascending=False)
+ tm.assert_numpy_array_equal(indexer1, indexer2)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_value_counts.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_value_counts.py
new file mode 100644
index 0000000000000000000000000000000000000000..069e354a364c9343c595da9d18ee7c04eec04f43
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimelike_/test_value_counts.py
@@ -0,0 +1,103 @@
+import numpy as np
+
+from pandas import (
+ DatetimeIndex,
+ NaT,
+ PeriodIndex,
+ Series,
+ TimedeltaIndex,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestValueCounts:
+ # GH#7735
+
+ def test_value_counts_unique_datetimeindex(self, tz_naive_fixture):
+ tz = tz_naive_fixture
+ orig = date_range("2011-01-01 09:00", freq="h", periods=10, tz=tz)
+ self._check_value_counts_with_repeats(orig)
+
+ def test_value_counts_unique_timedeltaindex(self):
+ orig = timedelta_range("1 days 09:00:00", freq="h", periods=10)
+ self._check_value_counts_with_repeats(orig)
+
+ def test_value_counts_unique_periodindex(self):
+ orig = period_range("2011-01-01 09:00", freq="h", periods=10)
+ self._check_value_counts_with_repeats(orig)
+
+ def _check_value_counts_with_repeats(self, orig):
+ # create repeated values, 'n'th element is repeated by n+1 times
+ idx = type(orig)(
+ np.repeat(orig._values, range(1, len(orig) + 1)), dtype=orig.dtype
+ )
+
+ exp_idx = orig[::-1]
+ if not isinstance(exp_idx, PeriodIndex):
+ exp_idx = exp_idx._with_freq(None)
+ expected = Series(range(10, 0, -1), index=exp_idx, dtype="int64", name="count")
+
+ for obj in [idx, Series(idx)]:
+ tm.assert_series_equal(obj.value_counts(), expected)
+
+ tm.assert_index_equal(idx.unique(), orig)
+
+ def test_value_counts_unique_datetimeindex2(self, tz_naive_fixture):
+ tz = tz_naive_fixture
+ idx = DatetimeIndex(
+ [
+ "2013-01-01 09:00",
+ "2013-01-01 09:00",
+ "2013-01-01 09:00",
+ "2013-01-01 08:00",
+ "2013-01-01 08:00",
+ NaT,
+ ],
+ tz=tz,
+ )
+ self._check_value_counts_dropna(idx)
+
+ def test_value_counts_unique_timedeltaindex2(self):
+ idx = TimedeltaIndex(
+ [
+ "1 days 09:00:00",
+ "1 days 09:00:00",
+ "1 days 09:00:00",
+ "1 days 08:00:00",
+ "1 days 08:00:00",
+ NaT,
+ ]
+ )
+ self._check_value_counts_dropna(idx)
+
+ def test_value_counts_unique_periodindex2(self):
+ idx = PeriodIndex(
+ [
+ "2013-01-01 09:00",
+ "2013-01-01 09:00",
+ "2013-01-01 09:00",
+ "2013-01-01 08:00",
+ "2013-01-01 08:00",
+ NaT,
+ ],
+ freq="h",
+ )
+ self._check_value_counts_dropna(idx)
+
+ def _check_value_counts_dropna(self, idx):
+ exp_idx = idx[[2, 3]]
+ expected = Series([3, 2], index=exp_idx, name="count")
+
+ for obj in [idx, Series(idx)]:
+ tm.assert_series_equal(obj.value_counts(), expected)
+
+ exp_idx = idx[[2, 3, -1]]
+ expected = Series([3, 2, 1], index=exp_idx, name="count")
+
+ for obj in [idx, Series(idx)]:
+ tm.assert_series_equal(obj.value_counts(dropna=False), expected)
+
+ tm.assert_index_equal(idx.unique(), exp_idx)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_asof.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_asof.py
new file mode 100644
index 0000000000000000000000000000000000000000..9364e76ade797ca1c2f2eb2186c3f80730b17cee
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_asof.py
@@ -0,0 +1,46 @@
+from datetime import timedelta
+
+from pandas import (
+ DatetimeIndex,
+ Index,
+ Timestamp,
+ date_range,
+ isna,
+)
+
+
+class TestAsOf:
+ def test_asof_partial(self):
+ index = date_range("2010-01-01", periods=2, freq="ME")
+ expected = Timestamp("2010-02-28")
+ result = index.asof("2010-02")
+ assert result == expected
+ assert not isinstance(result, Index)
+
+ def test_asof(self):
+ index = date_range("2020-01-01", periods=10)
+
+ dt = index[0]
+ assert index.asof(dt) == dt
+ assert isna(index.asof(dt - timedelta(1)))
+
+ dt = index[-1]
+ assert index.asof(dt + timedelta(1)) == dt
+
+ dt = index[0].to_pydatetime()
+ assert isinstance(index.asof(dt), Timestamp)
+
+ def test_asof_datetime_string(self):
+ # GH#50946
+
+ dti = date_range("2021-08-05", "2021-08-10", freq="1D")
+
+ key = "2021-08-09"
+ res = dti.asof(key)
+ exp = dti[4]
+ assert res == exp
+
+ # add a non-midnight time caused a bug
+ dti2 = DatetimeIndex([*list(dti), "2021-08-11 00:00:01"])
+ res = dti2.asof(key)
+ assert res == exp
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ed7db4c6d9fb6adae8e9794210d42b8782cbd0d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_astype.py
@@ -0,0 +1,341 @@
+from datetime import datetime
+
+import dateutil
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Index,
+ NaT,
+ PeriodIndex,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeIndex:
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_dti_astype_asobject_around_dst_transition(self, tzstr):
+ # GH#1345
+
+ # dates around a dst transition
+ rng = date_range("2/13/2010", "5/6/2010", tz=tzstr)
+
+ objs = rng.astype(object)
+ for i, x in enumerate(objs):
+ exval = rng[i]
+ assert x == exval
+ assert x.tzinfo == exval.tzinfo
+
+ objs = rng.astype(object)
+ for i, x in enumerate(objs):
+ exval = rng[i]
+ assert x == exval
+ assert x.tzinfo == exval.tzinfo
+
+ def test_astype(self):
+ # GH 13149, GH 13209
+ idx = DatetimeIndex(
+ ["2016-05-16", "NaT", NaT, np.nan], dtype="M8[ns]", name="idx"
+ )
+
+ result = idx.astype(object)
+ expected = Index(
+ [Timestamp("2016-05-16")] + [NaT] * 3, dtype=object, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = idx.astype(np.int64)
+ expected = Index(
+ [1463356800000000000] + [-9223372036854775808] * 3,
+ dtype=np.int64,
+ name="idx",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_astype2(self):
+ rng = date_range("1/1/2000", periods=10, name="idx")
+ result = rng.astype("i8")
+ tm.assert_index_equal(result, Index(rng.asi8, name="idx"))
+ tm.assert_numpy_array_equal(result.values, rng.asi8)
+
+ def test_astype_uint(self):
+ arr = date_range("2000", periods=2, name="idx")
+
+ with pytest.raises(TypeError, match=r"Do obj.astype\('int64'\)"):
+ arr.astype("uint64")
+ with pytest.raises(TypeError, match=r"Do obj.astype\('int64'\)"):
+ arr.astype("uint32")
+
+ def test_astype_with_tz(self):
+ # with tz
+ rng = date_range("1/1/2000", periods=10, tz="US/Eastern")
+ msg = "Cannot use .astype to convert from timezone-aware"
+ with pytest.raises(TypeError, match=msg):
+ # deprecated
+ rng.astype("datetime64[ns]")
+ with pytest.raises(TypeError, match=msg):
+ # check DatetimeArray while we're here deprecated
+ rng._data.astype("datetime64[ns]")
+
+ def test_astype_tzaware_to_tzaware(self):
+ # GH 18951: tz-aware to tz-aware
+ idx = date_range("20170101", periods=4, tz="US/Pacific", unit="ns")
+ result = idx.astype("datetime64[ns, US/Eastern]")
+ expected = date_range(
+ "20170101 03:00:00", periods=4, tz="US/Eastern", unit="ns"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ def test_astype_tznaive_to_tzaware(self):
+ # GH 18951: tz-naive to tz-aware
+ idx = date_range("20170101", periods=4)
+ idx = idx._with_freq(None) # tz_localize does not preserve freq
+ msg = "Cannot use .astype to convert from timezone-naive"
+ with pytest.raises(TypeError, match=msg):
+ # dt64->dt64tz deprecated
+ idx.astype("datetime64[ns, US/Eastern]")
+ with pytest.raises(TypeError, match=msg):
+ # dt64->dt64tz deprecated
+ idx._data.astype("datetime64[ns, US/Eastern]")
+
+ def test_astype_str_nat(self, using_infer_string):
+ # GH 13149, GH 13209
+ # verify that we are returning NaT as a string (and not unicode)
+
+ idx = DatetimeIndex(["2016-05-16", "NaT", NaT, np.nan])
+ result = idx.astype(str)
+ if using_infer_string:
+ expected = Index(["2016-05-16", None, None, None], dtype="str")
+ else:
+ expected = Index(["2016-05-16", "NaT", "NaT", "NaT"], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ def test_astype_str(self):
+ # test astype string - #10442
+ dti = date_range("2012-01-01", periods=4, name="test_name")
+ result = dti.astype(str)
+ expected = Index(
+ ["2012-01-01", "2012-01-02", "2012-01-03", "2012-01-04"],
+ name="test_name",
+ dtype="str",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_astype_str_tz_and_name(self):
+ # test astype string with tz and name
+ dti = date_range("2012-01-01", periods=3, name="test_name", tz="US/Eastern")
+ result = dti.astype(str)
+ expected = Index(
+ [
+ "2012-01-01 00:00:00-05:00",
+ "2012-01-02 00:00:00-05:00",
+ "2012-01-03 00:00:00-05:00",
+ ],
+ name="test_name",
+ dtype="str",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_astype_str_freq_and_name(self):
+ # test astype string with freqH and name
+ dti = date_range("1/1/2011", periods=3, freq="h", name="test_name")
+ result = dti.astype(str)
+ expected = Index(
+ ["2011-01-01 00:00:00", "2011-01-01 01:00:00", "2011-01-01 02:00:00"],
+ name="test_name",
+ dtype="str",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_astype_str_freq_and_tz(self):
+ # test astype string with freqH and timezone
+ dti = date_range(
+ "3/6/2012 00:00", periods=2, freq="h", tz="Europe/London", name="test_name"
+ )
+ result = dti.astype(str)
+ expected = Index(
+ ["2012-03-06 00:00:00+00:00", "2012-03-06 01:00:00+00:00"],
+ dtype="str",
+ name="test_name",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_astype_datetime64(self):
+ # GH 13149, GH 13209
+ idx = DatetimeIndex(
+ ["2016-05-16", "NaT", NaT, np.nan], dtype="M8[ns]", name="idx"
+ )
+
+ result = idx.astype("datetime64[ns]")
+ tm.assert_index_equal(result, idx)
+ assert result is not idx
+
+ result = idx.astype("datetime64[ns]", copy=False)
+ tm.assert_index_equal(result, idx)
+ assert result is idx
+
+ idx_tz = DatetimeIndex(["2016-05-16", "NaT", NaT, np.nan], tz="EST", name="idx")
+ msg = "Cannot use .astype to convert from timezone-aware"
+ with pytest.raises(TypeError, match=msg):
+ # dt64tz->dt64 deprecated
+ result = idx_tz.astype("datetime64[ns]")
+
+ def test_astype_object(self):
+ rng = date_range("1/1/2000", periods=20)
+
+ casted = rng.astype("O")
+ exp_values = list(rng)
+
+ tm.assert_index_equal(casted, Index(exp_values, dtype=np.object_))
+ assert casted.tolist() == exp_values
+
+ @pytest.mark.parametrize("tz", [None, "Asia/Tokyo"])
+ def test_astype_object_tz(self, tz):
+ idx = date_range(start="2013-01-01", periods=4, freq="ME", name="idx", tz=tz)
+ expected_list = [
+ Timestamp("2013-01-31", tz=tz),
+ Timestamp("2013-02-28", tz=tz),
+ Timestamp("2013-03-31", tz=tz),
+ Timestamp("2013-04-30", tz=tz),
+ ]
+ expected = Index(expected_list, dtype=object, name="idx")
+ result = idx.astype(object)
+ tm.assert_index_equal(result, expected)
+ assert idx.tolist() == expected_list
+
+ def test_astype_object_with_nat(self):
+ idx = DatetimeIndex(
+ [datetime(2013, 1, 1), datetime(2013, 1, 2), NaT, datetime(2013, 1, 4)],
+ name="idx",
+ )
+ expected_list = [
+ Timestamp("2013-01-01"),
+ Timestamp("2013-01-02"),
+ NaT,
+ Timestamp("2013-01-04"),
+ ]
+ expected = Index(expected_list, dtype=object, name="idx")
+ result = idx.astype(object)
+ tm.assert_index_equal(result, expected)
+ assert idx.tolist() == expected_list
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [float, "timedelta64", "timedelta64[ns]", "datetime64", "datetime64[D]"],
+ )
+ def test_astype_raises(self, dtype):
+ # GH 13149, GH 13209
+ idx = DatetimeIndex(["2016-05-16", "NaT", NaT, np.nan])
+ msg = "Cannot cast DatetimeIndex to dtype"
+ if dtype == "datetime64":
+ msg = "Casting to unit-less dtype 'datetime64' is not supported"
+ with pytest.raises(TypeError, match=msg):
+ idx.astype(dtype)
+
+ def test_index_convert_to_datetime_array(self):
+ def _check_rng(rng):
+ converted = rng.to_pydatetime()
+ assert isinstance(converted, np.ndarray)
+ for x, stamp in zip(converted, rng):
+ assert isinstance(x, datetime)
+ assert x == stamp.to_pydatetime()
+ assert x.tzinfo == stamp.tzinfo
+
+ rng = date_range("20090415", "20090519")
+ rng_eastern = date_range("20090415", "20090519", tz="US/Eastern")
+ rng_utc = date_range("20090415", "20090519", tz="utc")
+
+ _check_rng(rng)
+ _check_rng(rng_eastern)
+ _check_rng(rng_utc)
+
+ def test_index_convert_to_datetime_array_explicit_pytz(self):
+ pytz = pytest.importorskip("pytz")
+
+ def _check_rng(rng):
+ converted = rng.to_pydatetime()
+ assert isinstance(converted, np.ndarray)
+ for x, stamp in zip(converted, rng):
+ assert isinstance(x, datetime)
+ assert x == stamp.to_pydatetime()
+ assert x.tzinfo == stamp.tzinfo
+
+ rng = date_range("20090415", "20090519")
+ rng_eastern = date_range("20090415", "20090519", tz=pytz.timezone("US/Eastern"))
+ rng_utc = date_range("20090415", "20090519", tz=pytz.utc)
+
+ _check_rng(rng)
+ _check_rng(rng_eastern)
+ _check_rng(rng_utc)
+
+ def test_index_convert_to_datetime_array_dateutil(self):
+ def _check_rng(rng):
+ converted = rng.to_pydatetime()
+ assert isinstance(converted, np.ndarray)
+ for x, stamp in zip(converted, rng):
+ assert isinstance(x, datetime)
+ assert x == stamp.to_pydatetime()
+ assert x.tzinfo == stamp.tzinfo
+
+ rng = date_range("20090415", "20090519")
+ rng_eastern = date_range("20090415", "20090519", tz="dateutil/US/Eastern")
+ rng_utc = date_range("20090415", "20090519", tz=dateutil.tz.tzutc())
+
+ _check_rng(rng)
+ _check_rng(rng_eastern)
+ _check_rng(rng_utc)
+
+ @pytest.mark.parametrize(
+ "tz, dtype",
+ [["US/Pacific", "datetime64[ns, US/Pacific]"], [None, "datetime64[ns]"]],
+ )
+ def test_integer_index_astype_datetime(self, tz, dtype):
+ # GH 20997, 20964, 24559
+ val = [Timestamp("2018-01-01", tz=tz).as_unit("ns")._value]
+ result = Index(val, name="idx").astype(dtype)
+ expected = DatetimeIndex(["2018-01-01"], tz=tz, name="idx").as_unit("ns")
+ tm.assert_index_equal(result, expected)
+
+ def test_dti_astype_period(self):
+ idx = DatetimeIndex([NaT, "2011-01-01", "2011-02-01"], name="idx")
+
+ res = idx.astype("period[M]")
+ exp = PeriodIndex(["NaT", "2011-01", "2011-02"], freq="M", name="idx")
+ tm.assert_index_equal(res, exp)
+
+ res = idx.astype("period[3M]")
+ exp = PeriodIndex(["NaT", "2011-01", "2011-02"], freq="3M", name="idx")
+ tm.assert_index_equal(res, exp)
+
+
+class TestAstype:
+ @pytest.mark.parametrize("tz", [None, "US/Central"])
+ def test_astype_category(self, tz):
+ obj = date_range("2000", periods=2, tz=tz, name="idx", unit="ns")
+ result = obj.astype("category")
+ dti = DatetimeIndex(["2000-01-01", "2000-01-02"], tz=tz).as_unit("ns")
+ expected = pd.CategoricalIndex(
+ dti,
+ name="idx",
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = obj._data.astype("category")
+ expected = expected.values
+ tm.assert_categorical_equal(result, expected)
+
+ @pytest.mark.parametrize("tz", [None, "US/Central"])
+ def test_astype_array_fallback(self, tz):
+ obj = date_range("2000", periods=2, tz=tz, name="idx")
+ result = obj.astype(bool)
+ expected = Index(np.array([True, True]), name="idx")
+ tm.assert_index_equal(result, expected)
+
+ result = obj._data.astype(bool)
+ expected = np.array([True, True])
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_delete.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_delete.py
new file mode 100644
index 0000000000000000000000000000000000000000..2341499977f2247dc42c30470795378515f49dc8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_delete.py
@@ -0,0 +1,141 @@
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestDelete:
+ def test_delete(self, unit):
+ idx = date_range(
+ start="2000-01-01", periods=5, freq="ME", name="idx", unit=unit
+ )
+
+ # preserve freq
+ expected_0 = date_range(
+ start="2000-02-01", periods=4, freq="ME", name="idx", unit=unit
+ )
+ expected_4 = date_range(
+ start="2000-01-01", periods=4, freq="ME", name="idx", unit=unit
+ )
+
+ # reset freq to None
+ expected_1 = DatetimeIndex(
+ ["2000-01-31", "2000-03-31", "2000-04-30", "2000-05-31"],
+ freq=None,
+ name="idx",
+ ).as_unit(unit)
+
+ cases = {
+ 0: expected_0,
+ -5: expected_0,
+ -1: expected_4,
+ 4: expected_4,
+ 1: expected_1,
+ }
+ for n, expected in cases.items():
+ result = idx.delete(n)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ with pytest.raises((IndexError, ValueError), match="out of bounds"):
+ # either depending on numpy version
+ idx.delete(5)
+
+ @pytest.mark.parametrize("tz", [None, "Asia/Tokyo", "US/Pacific"])
+ def test_delete2(self, tz):
+ idx = date_range(
+ start="2000-01-01 09:00", periods=10, freq="h", name="idx", tz=tz
+ )
+
+ expected = date_range(
+ start="2000-01-01 10:00", periods=9, freq="h", name="idx", tz=tz
+ )
+ result = idx.delete(0)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freqstr == "h"
+ assert result.tz == expected.tz
+
+ expected = date_range(
+ start="2000-01-01 09:00", periods=9, freq="h", name="idx", tz=tz
+ )
+ result = idx.delete(-1)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freqstr == "h"
+ assert result.tz == expected.tz
+
+ def test_delete_slice(self, unit):
+ idx = date_range(
+ start="2000-01-01", periods=10, freq="D", name="idx", unit=unit
+ )
+
+ # preserve freq
+ expected_0_2 = date_range(
+ start="2000-01-04", periods=7, freq="D", name="idx", unit=unit
+ )
+ expected_7_9 = date_range(
+ start="2000-01-01", periods=7, freq="D", name="idx", unit=unit
+ )
+
+ # reset freq to None
+ expected_3_5 = DatetimeIndex(
+ [
+ "2000-01-01",
+ "2000-01-02",
+ "2000-01-03",
+ "2000-01-07",
+ "2000-01-08",
+ "2000-01-09",
+ "2000-01-10",
+ ],
+ freq=None,
+ name="idx",
+ ).as_unit(unit)
+
+ cases = {
+ (0, 1, 2): expected_0_2,
+ (7, 8, 9): expected_7_9,
+ (3, 4, 5): expected_3_5,
+ }
+ for n, expected in cases.items():
+ result = idx.delete(n)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ result = idx.delete(slice(n[0], n[-1] + 1))
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ # TODO: belongs in Series.drop tests?
+ @pytest.mark.parametrize("tz", [None, "Asia/Tokyo", "US/Pacific"])
+ def test_delete_slice2(self, tz, unit):
+ dti = date_range(
+ "2000-01-01 09:00", periods=10, freq="h", name="idx", tz=tz, unit=unit
+ )
+ ts = Series(
+ 1,
+ index=dti,
+ )
+ # preserve freq
+ result = ts.drop(ts.index[:5]).index
+ expected = dti[5:]
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+ assert result.tz == expected.tz
+
+ # reset freq to None
+ result = ts.drop(ts.index[[1, 3, 5, 7, 9]]).index
+ expected = dti[::2]._with_freq(None)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+ assert result.tz == expected.tz
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_factorize.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_factorize.py
new file mode 100644
index 0000000000000000000000000000000000000000..41ecf9ee6b82317137b1a6accee14ad8c1b5a35a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_factorize.py
@@ -0,0 +1,125 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ Index,
+ date_range,
+ factorize,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeIndexFactorize:
+ def test_factorize(self):
+ idx1 = DatetimeIndex(
+ ["2014-01", "2014-01", "2014-02", "2014-02", "2014-03", "2014-03"]
+ )
+
+ exp_arr = np.array([0, 0, 1, 1, 2, 2], dtype=np.intp)
+ exp_idx = DatetimeIndex(["2014-01", "2014-02", "2014-03"])
+
+ arr, idx = idx1.factorize()
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+ assert idx.freq == exp_idx.freq
+
+ arr, idx = idx1.factorize(sort=True)
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+ assert idx.freq == exp_idx.freq
+
+ # tz must be preserved
+ idx1 = idx1.tz_localize("Asia/Tokyo")
+ exp_idx = exp_idx.tz_localize("Asia/Tokyo")
+
+ arr, idx = idx1.factorize()
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+ assert idx.freq == exp_idx.freq
+
+ idx2 = DatetimeIndex(
+ ["2014-03", "2014-03", "2014-02", "2014-01", "2014-03", "2014-01"]
+ )
+
+ exp_arr = np.array([2, 2, 1, 0, 2, 0], dtype=np.intp)
+ exp_idx = DatetimeIndex(["2014-01", "2014-02", "2014-03"])
+ arr, idx = idx2.factorize(sort=True)
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+ assert idx.freq == exp_idx.freq
+
+ exp_arr = np.array([0, 0, 1, 2, 0, 2], dtype=np.intp)
+ exp_idx = DatetimeIndex(["2014-03", "2014-02", "2014-01"])
+ arr, idx = idx2.factorize()
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+ assert idx.freq == exp_idx.freq
+
+ def test_factorize_preserves_freq(self):
+ # GH#38120 freq should be preserved
+ idx3 = date_range("2000-01", periods=4, freq="ME", tz="Asia/Tokyo")
+ exp_arr = np.array([0, 1, 2, 3], dtype=np.intp)
+
+ arr, idx = idx3.factorize()
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, idx3)
+ assert idx.freq == idx3.freq
+
+ arr, idx = factorize(idx3)
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, idx3)
+ assert idx.freq == idx3.freq
+
+ def test_factorize_tz(self, tz_naive_fixture, index_or_series):
+ tz = tz_naive_fixture
+ # GH#13750
+ base = date_range("2016-11-05", freq="h", periods=100, tz=tz)
+ idx = base.repeat(5)
+
+ exp_arr = np.arange(100, dtype=np.intp).repeat(5)
+
+ obj = index_or_series(idx)
+
+ arr, res = obj.factorize()
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ expected = base._with_freq(None)
+ tm.assert_index_equal(res, expected)
+ assert res.freq == expected.freq
+
+ def test_factorize_dst(self, index_or_series):
+ # GH#13750
+ idx = date_range("2016-11-06", freq="h", periods=12, tz="US/Eastern")
+ obj = index_or_series(idx)
+
+ arr, res = obj.factorize()
+ tm.assert_numpy_array_equal(arr, np.arange(12, dtype=np.intp))
+ tm.assert_index_equal(res, idx)
+ if index_or_series is Index:
+ assert res.freq == idx.freq
+
+ idx = date_range("2016-06-13", freq="h", periods=12, tz="US/Eastern")
+ obj = index_or_series(idx)
+
+ arr, res = obj.factorize()
+ tm.assert_numpy_array_equal(arr, np.arange(12, dtype=np.intp))
+ tm.assert_index_equal(res, idx)
+ if index_or_series is Index:
+ assert res.freq == idx.freq
+
+ @pytest.mark.parametrize("sort", [True, False])
+ def test_factorize_no_freq_non_nano(self, tz_naive_fixture, sort):
+ # GH#51978 case that does not go through the fastpath based on
+ # non-None freq
+ tz = tz_naive_fixture
+ idx = date_range("2016-11-06", freq="h", periods=5, tz=tz)[[0, 4, 1, 3, 2]]
+ exp_codes, exp_uniques = idx.factorize(sort=sort)
+
+ res_codes, res_uniques = idx.as_unit("s").factorize(sort=sort)
+
+ tm.assert_numpy_array_equal(res_codes, exp_codes)
+ tm.assert_index_equal(res_uniques, exp_uniques.as_unit("s"))
+
+ res_codes, res_uniques = idx.as_unit("s").to_series().factorize(sort=sort)
+ tm.assert_numpy_array_equal(res_codes, exp_codes)
+ tm.assert_index_equal(res_uniques, exp_uniques.as_unit("s"))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_fillna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_fillna.py
new file mode 100644
index 0000000000000000000000000000000000000000..5fbe60bb0c50f0b6ec36eb02b125e9e9bf0f81dd
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_fillna.py
@@ -0,0 +1,62 @@
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class TestDatetimeIndexFillNA:
+ @pytest.mark.parametrize("tz", ["US/Eastern", "Asia/Tokyo"])
+ def test_fillna_datetime64(self, tz):
+ # GH 11343
+ idx = pd.DatetimeIndex(["2011-01-01 09:00", pd.NaT, "2011-01-01 11:00"])
+
+ exp = pd.DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", "2011-01-01 11:00"]
+ )
+ tm.assert_index_equal(idx.fillna(pd.Timestamp("2011-01-01 10:00")), exp)
+
+ # tz mismatch
+ exp = pd.Index(
+ [
+ pd.Timestamp("2011-01-01 09:00"),
+ pd.Timestamp("2011-01-01 10:00", tz=tz),
+ pd.Timestamp("2011-01-01 11:00"),
+ ],
+ dtype=object,
+ )
+ tm.assert_index_equal(idx.fillna(pd.Timestamp("2011-01-01 10:00", tz=tz)), exp)
+
+ # object
+ exp = pd.Index(
+ [pd.Timestamp("2011-01-01 09:00"), "x", pd.Timestamp("2011-01-01 11:00")],
+ dtype=object,
+ )
+ tm.assert_index_equal(idx.fillna("x"), exp)
+
+ idx = pd.DatetimeIndex(["2011-01-01 09:00", pd.NaT, "2011-01-01 11:00"], tz=tz)
+
+ exp = pd.DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", "2011-01-01 11:00"], tz=tz
+ )
+ tm.assert_index_equal(idx.fillna(pd.Timestamp("2011-01-01 10:00", tz=tz)), exp)
+
+ exp = pd.Index(
+ [
+ pd.Timestamp("2011-01-01 09:00", tz=tz),
+ pd.Timestamp("2011-01-01 10:00"),
+ pd.Timestamp("2011-01-01 11:00", tz=tz),
+ ],
+ dtype=object,
+ )
+ tm.assert_index_equal(idx.fillna(pd.Timestamp("2011-01-01 10:00")), exp)
+
+ # object
+ exp = pd.Index(
+ [
+ pd.Timestamp("2011-01-01 09:00", tz=tz),
+ "x",
+ pd.Timestamp("2011-01-01 11:00", tz=tz),
+ ],
+ dtype=object,
+ )
+ tm.assert_index_equal(idx.fillna("x"), exp)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_insert.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_insert.py
new file mode 100644
index 0000000000000000000000000000000000000000..d17c4e81537ea642ac8bff527c30402921a52192
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_insert.py
@@ -0,0 +1,277 @@
+from datetime import datetime
+import zoneinfo
+
+import numpy as np
+import pytest
+
+from pandas import (
+ NA,
+ DatetimeIndex,
+ Index,
+ NaT,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestInsert:
+ @pytest.mark.parametrize("null", [None, np.nan, np.datetime64("NaT"), NaT, NA])
+ @pytest.mark.parametrize("tz", [None, "UTC", "US/Eastern"])
+ def test_insert_nat(self, tz, null):
+ # GH#16537, GH#18295 (test missing)
+
+ idx = DatetimeIndex(["2017-01-01"], tz=tz)
+ expected = DatetimeIndex(["NaT", "2017-01-01"], tz=tz)
+ if tz is not None and isinstance(null, np.datetime64):
+ expected = Index([null, idx[0]], dtype=object)
+
+ res = idx.insert(0, null)
+ tm.assert_index_equal(res, expected)
+
+ @pytest.mark.parametrize("tz", [None, "UTC", "US/Eastern"])
+ def test_insert_invalid_na(self, tz):
+ idx = DatetimeIndex(["2017-01-01"], tz=tz)
+
+ item = np.timedelta64("NaT")
+ result = idx.insert(0, item)
+ expected = Index([item, *list(idx)], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_empty_preserves_freq(self, tz_naive_fixture):
+ # GH#33573
+ tz = tz_naive_fixture
+ dti = DatetimeIndex([], tz=tz, freq="D")
+ item = Timestamp("2017-04-05").tz_localize(tz)
+
+ result = dti.insert(0, item)
+ assert result.freq == dti.freq
+
+ # But not when we insert an item that doesn't conform to freq
+ dti = DatetimeIndex([], tz=tz, freq="W-THU")
+ result = dti.insert(0, item)
+ assert result.freq is None
+
+ def test_insert(self, unit):
+ idx = DatetimeIndex(
+ ["2000-01-04", "2000-01-01", "2000-01-02"], name="idx"
+ ).as_unit(unit)
+
+ result = idx.insert(2, datetime(2000, 1, 5))
+ exp = DatetimeIndex(
+ ["2000-01-04", "2000-01-01", "2000-01-05", "2000-01-02"], name="idx"
+ ).as_unit(unit)
+ tm.assert_index_equal(result, exp)
+
+ # insertion of non-datetime should coerce to object index
+ result = idx.insert(1, "inserted")
+ expected = Index(
+ [
+ datetime(2000, 1, 4),
+ "inserted",
+ datetime(2000, 1, 1),
+ datetime(2000, 1, 2),
+ ],
+ name="idx",
+ )
+ assert not isinstance(result, DatetimeIndex)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+
+ def test_insert2(self, unit):
+ idx = date_range("1/1/2000", periods=3, freq="ME", name="idx", unit=unit)
+
+ # preserve freq
+ expected_0 = DatetimeIndex(
+ ["1999-12-31", "2000-01-31", "2000-02-29", "2000-03-31"],
+ name="idx",
+ freq="ME",
+ ).as_unit(unit)
+ expected_3 = DatetimeIndex(
+ ["2000-01-31", "2000-02-29", "2000-03-31", "2000-04-30"],
+ name="idx",
+ freq="ME",
+ ).as_unit(unit)
+
+ # reset freq to None
+ expected_1_nofreq = DatetimeIndex(
+ ["2000-01-31", "2000-01-31", "2000-02-29", "2000-03-31"],
+ name="idx",
+ freq=None,
+ ).as_unit(unit)
+ expected_3_nofreq = DatetimeIndex(
+ ["2000-01-31", "2000-02-29", "2000-03-31", "2000-01-02"],
+ name="idx",
+ freq=None,
+ ).as_unit(unit)
+
+ cases = [
+ (0, datetime(1999, 12, 31), expected_0),
+ (-3, datetime(1999, 12, 31), expected_0),
+ (3, datetime(2000, 4, 30), expected_3),
+ (1, datetime(2000, 1, 31), expected_1_nofreq),
+ (3, datetime(2000, 1, 2), expected_3_nofreq),
+ ]
+
+ for n, d, expected in cases:
+ result = idx.insert(n, d)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ def test_insert3(self, unit):
+ idx = date_range("1/1/2000", periods=3, freq="ME", name="idx", unit=unit)
+
+ # reset freq to None
+ result = idx.insert(3, datetime(2000, 1, 2))
+ expected = DatetimeIndex(
+ ["2000-01-31", "2000-02-29", "2000-03-31", "2000-01-02"],
+ name="idx",
+ freq=None,
+ ).as_unit(unit)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq is None
+
+ @pytest.mark.parametrize("tz", ["US/Pacific", "Asia/Singapore"])
+ @pytest.mark.parametrize(
+ "to_ts",
+ [lambda x: x, lambda x: x.to_pydatetime()],
+ ids=["Timestamp", "datetime"],
+ )
+ def test_insert4(self, unit, tz, to_ts):
+ idx = date_range(
+ "1/1/2000 09:00", periods=6, freq="h", tz=tz, name="idx", unit=unit
+ )
+ # preserve freq
+ expected = date_range(
+ "1/1/2000 09:00", periods=7, freq="h", tz=tz, name="idx", unit=unit
+ )
+ tz = zoneinfo.ZoneInfo(tz)
+ d = to_ts(Timestamp("2000-01-01 15:00", tz=tz))
+ result = idx.insert(6, d)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+ assert result.tz == expected.tz
+
+ @pytest.mark.parametrize("tz", ["US/Pacific", "Asia/Singapore"])
+ @pytest.mark.parametrize(
+ "to_ts",
+ [lambda x: x, lambda x: x.to_pydatetime()],
+ ids=["Timestamp", "datetime"],
+ )
+ def test_insert4_no_freq(self, unit, tz, to_ts):
+ idx = date_range(
+ "1/1/2000 09:00", periods=6, freq="h", tz=tz, name="idx", unit=unit
+ )
+ expected = DatetimeIndex(
+ [
+ "2000-01-01 09:00",
+ "2000-01-01 10:00",
+ "2000-01-01 11:00",
+ "2000-01-01 12:00",
+ "2000-01-01 13:00",
+ "2000-01-01 14:00",
+ "2000-01-01 10:00",
+ ],
+ name="idx",
+ tz=tz,
+ freq=None,
+ ).as_unit(unit)
+ # reset freq to None
+ d = to_ts(Timestamp("2000-01-01 10:00", tz=tz))
+ result = idx.insert(6, d)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.tz == expected.tz
+ assert result.freq is None
+
+ # TODO: also changes DataFrame.__setitem__ with expansion
+ def test_insert_mismatched_tzawareness(self):
+ # see GH#7299
+ idx = date_range("1/1/2000", periods=3, freq="D", tz="Asia/Tokyo", name="idx")
+
+ # mismatched tz-awareness
+ item = Timestamp("2000-01-04")
+ result = idx.insert(3, item)
+ expected = Index(
+ [*list(idx[:3]), item, *list(idx[3:])], dtype=object, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ # mismatched tz-awareness
+ item = datetime(2000, 1, 4)
+ result = idx.insert(3, item)
+ expected = Index(
+ [*list(idx[:3]), item, *list(idx[3:])], dtype=object, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ # TODO: also changes DataFrame.__setitem__ with expansion
+ def test_insert_mismatched_tz(self):
+ # see GH#7299
+ # pre-2.0 with mismatched tzs we would cast to object
+ idx = date_range(
+ "1/1/2000", periods=3, freq="D", tz="Asia/Tokyo", unit="ns", name="idx"
+ )
+
+ # mismatched tz -> cast to object (could reasonably cast to same tz or UTC)
+ item = Timestamp("2000-01-04", tz="US/Eastern")
+ result = idx.insert(3, item)
+ expected = Index(
+ [*list(idx[:3]), item.tz_convert(idx.tz), *list(idx[3:])],
+ name="idx",
+ )
+ assert expected.dtype == idx.dtype
+ tm.assert_index_equal(result, expected)
+
+ item = datetime(2000, 1, 4, tzinfo=zoneinfo.ZoneInfo("US/Eastern"))
+ result = idx.insert(3, item)
+ expected = Index(
+ [*list(idx[:3]), item.astimezone(idx.tzinfo), *list(idx[3:])],
+ name="idx",
+ )
+ assert expected.dtype == idx.dtype
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "item", [0, np.int64(0), np.float64(0), np.array(0), np.timedelta64(456)]
+ )
+ def test_insert_mismatched_types_raises(self, tz_aware_fixture, item):
+ # GH#33703 dont cast these to dt64
+ tz = tz_aware_fixture
+ dti = date_range("2019-11-04", periods=9, freq="-1D", name=9, tz=tz)
+
+ result = dti.insert(1, item)
+
+ if isinstance(item, np.ndarray):
+ assert item.item() == 0
+ expected = Index([dti[0], 0, *list(dti[1:])], dtype=object, name=9)
+ else:
+ expected = Index([dti[0], item, *list(dti[1:])], dtype=object, name=9)
+
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_castable_str(self, tz_aware_fixture):
+ # GH#33703
+ tz = tz_aware_fixture
+ dti = date_range("2019-11-04", periods=3, freq="-1D", name=9, tz=tz)
+
+ value = "2019-11-05"
+ result = dti.insert(0, value)
+
+ ts = Timestamp(value).tz_localize(tz)
+ expected = DatetimeIndex([ts, *list(dti)], dtype=dti.dtype, name=9)
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_non_castable_str(self, tz_aware_fixture):
+ # GH#33703
+ tz = tz_aware_fixture
+ dti = date_range("2019-11-04", periods=3, freq="-1D", name=9, tz=tz)
+
+ value = "foo"
+ result = dti.insert(0, value)
+
+ expected = Index(["foo", *list(dti)], dtype=object, name=9)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_isocalendar.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_isocalendar.py
new file mode 100644
index 0000000000000000000000000000000000000000..97f1003e0f43f7564434cbc8b3051e870143209c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_isocalendar.py
@@ -0,0 +1,28 @@
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def test_isocalendar_returns_correct_values_close_to_new_year_with_tz():
+ # GH#6538: Check that DatetimeIndex and its TimeStamp elements
+ # return the same weekofyear accessor close to new year w/ tz
+ dates = ["2013/12/29", "2013/12/30", "2013/12/31"]
+ dates = DatetimeIndex(dates, tz="Europe/Brussels")
+ result = dates.isocalendar()
+ expected_data_frame = DataFrame(
+ [[2013, 52, 7], [2014, 1, 1], [2014, 1, 2]],
+ columns=["year", "week", "day"],
+ index=dates,
+ dtype="UInt32",
+ )
+ tm.assert_frame_equal(result, expected_data_frame)
+
+
+def test_dti_timestamp_isocalendar_fields():
+ idx = date_range("2020-01-01", periods=10)
+ expected = tuple(idx.isocalendar().iloc[-1].to_list())
+ result = idx[-1].isocalendar()
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_map.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_map.py
new file mode 100644
index 0000000000000000000000000000000000000000..f35f07bd32068f15fa8c4eb8d1ad8c2a6d43fc72
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_map.py
@@ -0,0 +1,47 @@
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ Index,
+ MultiIndex,
+ Period,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestMap:
+ def test_map(self):
+ rng = date_range("1/1/2000", periods=10)
+
+ f = lambda x: x.strftime("%Y%m%d")
+ result = rng.map(f)
+ exp = Index([f(x) for x in rng])
+ tm.assert_index_equal(result, exp)
+
+ def test_map_fallthrough(self, capsys):
+ # GH#22067, check we don't get warnings about silently ignored errors
+ dti = date_range("2017-01-01", "2018-01-01", freq="B")
+
+ dti.map(lambda x: Period(year=x.year, month=x.month, freq="M"))
+
+ captured = capsys.readouterr()
+ assert captured.err == ""
+
+ def test_map_bug_1677(self):
+ index = DatetimeIndex(["2012-04-25 09:30:00.393000"])
+ f = index.asof
+
+ result = index.map(f)
+ expected = Index([f(index[0])])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("name", [None, "name"])
+ def test_index_map(self, name):
+ # see GH#20990
+ count = 6
+ index = date_range("2018-01-01", periods=count, freq="ME", name=name).map(
+ lambda x: (x.year, x.month)
+ )
+ exp_index = MultiIndex.from_product(((2018,), range(1, 7)), names=[name, name])
+ tm.assert_index_equal(index, exp_index)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_normalize.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_normalize.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ce28d60b53b95365ca52fcae25766c95e81c216
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_normalize.py
@@ -0,0 +1,99 @@
+from dateutil.tz import tzlocal
+import numpy as np
+import pytest
+
+from pandas.compat import WASM
+import pandas.util._test_decorators as td
+
+from pandas import (
+ DatetimeIndex,
+ NaT,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestNormalize:
+ def test_normalize(self):
+ rng = date_range("1/1/2000 9:30", periods=10, freq="D")
+
+ result = rng.normalize()
+ expected = date_range("1/1/2000", periods=10, freq="D")
+ tm.assert_index_equal(result, expected)
+
+ arr_ns = np.array([1380585623454345752, 1380585612343234312]).astype(
+ "datetime64[ns]"
+ )
+ rng_ns = DatetimeIndex(arr_ns)
+ rng_ns_normalized = rng_ns.normalize()
+
+ arr_ns = np.array([1380585600000000000, 1380585600000000000]).astype(
+ "datetime64[ns]"
+ )
+ expected = DatetimeIndex(arr_ns)
+ tm.assert_index_equal(rng_ns_normalized, expected)
+
+ assert result.is_normalized
+ assert not rng.is_normalized
+
+ def test_normalize_nat(self):
+ dti = DatetimeIndex([NaT, Timestamp("2018-01-01 01:00:00")])
+ result = dti.normalize()
+ expected = DatetimeIndex([NaT, Timestamp("2018-01-01")])
+ tm.assert_index_equal(result, expected)
+
+ def test_normalize_tz(self):
+ rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz="US/Eastern")
+
+ result = rng.normalize() # does not preserve freq
+ expected = date_range("1/1/2000", periods=10, freq="D", tz="US/Eastern")
+ tm.assert_index_equal(result, expected._with_freq(None))
+
+ assert result.is_normalized
+ assert not rng.is_normalized
+
+ rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz="UTC")
+
+ result = rng.normalize()
+ expected = date_range("1/1/2000", periods=10, freq="D", tz="UTC")
+ tm.assert_index_equal(result, expected)
+
+ assert result.is_normalized
+ assert not rng.is_normalized
+
+ rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz=tzlocal())
+ result = rng.normalize() # does not preserve freq
+ expected = date_range("1/1/2000", periods=10, freq="D", tz=tzlocal())
+ tm.assert_index_equal(result, expected._with_freq(None))
+
+ assert result.is_normalized
+ assert not rng.is_normalized
+
+ @td.skip_if_windows
+ @pytest.mark.skipif(
+ WASM, reason="tzset is available only on Unix-like systems, not WASM"
+ )
+ @pytest.mark.parametrize(
+ "timezone",
+ [
+ "US/Pacific",
+ "US/Eastern",
+ "UTC",
+ "Asia/Kolkata",
+ "Asia/Shanghai",
+ "Australia/Canberra",
+ ],
+ )
+ def test_normalize_tz_local(self, timezone):
+ # GH#13459
+ with tm.set_timezone(timezone):
+ rng = date_range("1/1/2000 9:30", periods=10, freq="D", tz=tzlocal())
+
+ result = rng.normalize()
+ expected = date_range("1/1/2000", periods=10, freq="D", tz=tzlocal())
+ expected = expected._with_freq(None)
+ tm.assert_index_equal(result, expected)
+
+ assert result.is_normalized
+ assert not rng.is_normalized
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_repeat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_repeat.py
new file mode 100644
index 0000000000000000000000000000000000000000..92501755f8c5b3e943864c76a62cd712edc6dd51
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_repeat.py
@@ -0,0 +1,83 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestRepeat:
+ def test_repeat_range(self, tz_naive_fixture):
+ rng = date_range("1/1/2000", "1/1/2001")
+
+ result = rng.repeat(5)
+ assert result.freq is None
+ assert len(result) == 5 * len(rng)
+
+ def test_repeat_range2(self, tz_naive_fixture, unit):
+ tz = tz_naive_fixture
+ index = date_range("2001-01-01", periods=2, freq="D", tz=tz, unit=unit)
+ exp = DatetimeIndex(
+ ["2001-01-01", "2001-01-01", "2001-01-02", "2001-01-02"], tz=tz
+ ).as_unit(unit)
+ for res in [index.repeat(2), np.repeat(index, 2)]:
+ tm.assert_index_equal(res, exp)
+ assert res.freq is None
+
+ def test_repeat_range3(self, tz_naive_fixture, unit):
+ tz = tz_naive_fixture
+ index = date_range("2001-01-01", periods=2, freq="2D", tz=tz, unit=unit)
+ exp = DatetimeIndex(
+ ["2001-01-01", "2001-01-01", "2001-01-03", "2001-01-03"], tz=tz
+ ).as_unit(unit)
+ for res in [index.repeat(2), np.repeat(index, 2)]:
+ tm.assert_index_equal(res, exp)
+ assert res.freq is None
+
+ def test_repeat_range4(self, tz_naive_fixture, unit):
+ tz = tz_naive_fixture
+ index = DatetimeIndex(["2001-01-01", "NaT", "2003-01-01"], tz=tz).as_unit(unit)
+ exp = DatetimeIndex(
+ [
+ "2001-01-01",
+ "2001-01-01",
+ "2001-01-01",
+ "NaT",
+ "NaT",
+ "NaT",
+ "2003-01-01",
+ "2003-01-01",
+ "2003-01-01",
+ ],
+ tz=tz,
+ ).as_unit(unit)
+ for res in [index.repeat(3), np.repeat(index, 3)]:
+ tm.assert_index_equal(res, exp)
+ assert res.freq is None
+
+ def test_repeat(self, tz_naive_fixture, unit):
+ tz = tz_naive_fixture
+ reps = 2
+ msg = "the 'axis' parameter is not supported"
+
+ rng = date_range(start="2016-01-01", periods=2, freq="30Min", tz=tz, unit=unit)
+
+ expected_rng = DatetimeIndex(
+ [
+ Timestamp("2016-01-01 00:00:00", tz=tz),
+ Timestamp("2016-01-01 00:00:00", tz=tz),
+ Timestamp("2016-01-01 00:30:00", tz=tz),
+ Timestamp("2016-01-01 00:30:00", tz=tz),
+ ]
+ ).as_unit(unit)
+
+ res = rng.repeat(reps)
+ tm.assert_index_equal(res, expected_rng)
+ assert res.freq is None
+
+ tm.assert_index_equal(np.repeat(rng, reps), expected_rng)
+ with pytest.raises(ValueError, match=msg):
+ np.repeat(rng, reps, axis=1)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_resolution.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_resolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..42c3ab0617b7c10a1a32bba312d5bc65014a3f06
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_resolution.py
@@ -0,0 +1,35 @@
+from dateutil.tz import tzlocal
+import pytest
+
+from pandas.compat import (
+ IS64,
+ WASM,
+)
+
+from pandas import date_range
+
+
+@pytest.mark.parametrize(
+ "freq,expected",
+ [
+ ("YE", "day"),
+ ("QE", "day"),
+ ("ME", "day"),
+ ("D", "day"),
+ ("h", "hour"),
+ ("min", "minute"),
+ ("s", "second"),
+ ("ms", "millisecond"),
+ ("us", "microsecond"),
+ ],
+)
+@pytest.mark.skipif(WASM, reason="OverflowError received on WASM")
+def test_dti_resolution(request, tz_naive_fixture, freq, expected):
+ tz = tz_naive_fixture
+ if freq == "YE" and ((not IS64) or WASM) and isinstance(tz, tzlocal):
+ request.applymarker(
+ pytest.mark.xfail(reason="OverflowError inside tzlocal past 2038")
+ )
+
+ idx = date_range(start="2013-04-01", periods=30, freq=freq, tz=tz)
+ assert idx.resolution == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_round.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_round.py
new file mode 100644
index 0000000000000000000000000000000000000000..0570263be5b672e05ff7f77175a3cc0ede6bf68a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_round.py
@@ -0,0 +1,221 @@
+import pytest
+
+from pandas._libs.tslibs import to_offset
+from pandas._libs.tslibs.offsets import INVALID_FREQ_ERR_MSG
+
+from pandas import (
+ DatetimeIndex,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeIndexRound:
+ def test_round_daily(self):
+ dti = date_range("20130101 09:10:11", periods=5)
+ result = dti.round("D")
+ expected = date_range("20130101", periods=5)
+ tm.assert_index_equal(result, expected)
+
+ dti = dti.tz_localize("UTC").tz_convert("US/Eastern")
+ result = dti.round("D")
+ expected = date_range("20130101", periods=5).tz_localize("US/Eastern")
+ tm.assert_index_equal(result, expected)
+
+ result = dti.round("s")
+ tm.assert_index_equal(result, dti)
+
+ @pytest.mark.parametrize(
+ "freq, error_msg",
+ [
+ ("YE", " is a non-fixed frequency"),
+ ("ME", " is a non-fixed frequency"),
+ ("foobar", "Invalid frequency: foobar"),
+ ],
+ )
+ def test_round_invalid(self, freq, error_msg):
+ dti = date_range("20130101 09:10:11", periods=5)
+ dti = dti.tz_localize("UTC").tz_convert("US/Eastern")
+ with pytest.raises(ValueError, match=error_msg):
+ dti.round(freq)
+
+ def test_round(self, tz_naive_fixture, unit):
+ tz = tz_naive_fixture
+ rng = date_range(start="2016-01-01", periods=5, freq="30Min", tz=tz, unit=unit)
+ elt = rng[1]
+
+ expected_rng = DatetimeIndex(
+ [
+ Timestamp("2016-01-01 00:00:00", tz=tz),
+ Timestamp("2016-01-01 00:00:00", tz=tz),
+ Timestamp("2016-01-01 01:00:00", tz=tz),
+ Timestamp("2016-01-01 02:00:00", tz=tz),
+ Timestamp("2016-01-01 02:00:00", tz=tz),
+ ]
+ ).as_unit(unit)
+ expected_elt = expected_rng[1]
+
+ result = rng.round(freq="h")
+ tm.assert_index_equal(result, expected_rng)
+ assert elt.round(freq="h") == expected_elt
+
+ msg = INVALID_FREQ_ERR_MSG
+ with pytest.raises(ValueError, match=msg):
+ rng.round(freq="foo")
+ with pytest.raises(ValueError, match=msg):
+ elt.round(freq="foo")
+
+ msg = " is a non-fixed frequency"
+ with pytest.raises(ValueError, match=msg):
+ rng.round(freq="ME")
+ with pytest.raises(ValueError, match=msg):
+ elt.round(freq="ME")
+
+ def test_round2(self, tz_naive_fixture):
+ tz = tz_naive_fixture
+ # GH#14440 & GH#15578
+ index = DatetimeIndex(["2016-10-17 12:00:00.0015"], tz=tz).as_unit("ns")
+ result = index.round("ms")
+ expected = DatetimeIndex(["2016-10-17 12:00:00.002000"], tz=tz).as_unit("ns")
+ tm.assert_index_equal(result, expected)
+
+ for freq in ["us", "ns"]:
+ tm.assert_index_equal(index, index.round(freq))
+
+ def test_round3(self, tz_naive_fixture):
+ tz = tz_naive_fixture
+ index = DatetimeIndex(["2016-10-17 12:00:00.00149"], tz=tz).as_unit("ns")
+ result = index.round("ms")
+ expected = DatetimeIndex(["2016-10-17 12:00:00.001000"], tz=tz).as_unit("ns")
+ tm.assert_index_equal(result, expected)
+
+ def test_round4(self, tz_naive_fixture):
+ index = DatetimeIndex(["2016-10-17 12:00:00.001501031"], dtype="M8[ns]")
+ result = index.round("10ns")
+ expected = DatetimeIndex(["2016-10-17 12:00:00.001501030"], dtype="M8[ns]")
+ tm.assert_index_equal(result, expected)
+
+ ts = "2016-10-17 12:00:00.001501031"
+ dti = DatetimeIndex([ts], dtype="M8[ns]")
+ with tm.assert_produces_warning(False):
+ dti.round("1010ns")
+
+ def test_no_rounding_occurs(self, tz_naive_fixture):
+ # GH 21262
+ tz = tz_naive_fixture
+ rng = date_range(start="2016-01-01", periods=5, freq="2Min", tz=tz, unit="ns")
+
+ expected_rng = DatetimeIndex(
+ [
+ Timestamp("2016-01-01 00:00:00", tz=tz),
+ Timestamp("2016-01-01 00:02:00", tz=tz),
+ Timestamp("2016-01-01 00:04:00", tz=tz),
+ Timestamp("2016-01-01 00:06:00", tz=tz),
+ Timestamp("2016-01-01 00:08:00", tz=tz),
+ ]
+ ).as_unit("ns")
+
+ result = rng.round(freq="2min")
+ tm.assert_index_equal(result, expected_rng)
+
+ @pytest.mark.parametrize(
+ "test_input, rounder, freq, expected",
+ [
+ (["2117-01-01 00:00:45"], "floor", "15s", ["2117-01-01 00:00:45"]),
+ (["2117-01-01 00:00:45"], "ceil", "15s", ["2117-01-01 00:00:45"]),
+ (
+ ["2117-01-01 00:00:45.000000012"],
+ "floor",
+ "10ns",
+ ["2117-01-01 00:00:45.000000010"],
+ ),
+ (
+ ["1823-01-01 00:00:01.000000012"],
+ "ceil",
+ "10ns",
+ ["1823-01-01 00:00:01.000000020"],
+ ),
+ (["1823-01-01 00:00:01"], "floor", "1s", ["1823-01-01 00:00:01"]),
+ (["1823-01-01 00:00:01"], "ceil", "1s", ["1823-01-01 00:00:01"]),
+ (["2018-01-01 00:15:00"], "ceil", "15min", ["2018-01-01 00:15:00"]),
+ (["2018-01-01 00:15:00"], "floor", "15min", ["2018-01-01 00:15:00"]),
+ (["1823-01-01 03:00:00"], "ceil", "3h", ["1823-01-01 03:00:00"]),
+ (["1823-01-01 03:00:00"], "floor", "3h", ["1823-01-01 03:00:00"]),
+ (
+ ("NaT", "1823-01-01 00:00:01"),
+ "floor",
+ "1s",
+ ("NaT", "1823-01-01 00:00:01"),
+ ),
+ (
+ ("NaT", "1823-01-01 00:00:01"),
+ "ceil",
+ "1s",
+ ("NaT", "1823-01-01 00:00:01"),
+ ),
+ ],
+ )
+ def test_ceil_floor_edge(self, test_input, rounder, freq, expected):
+ dt = DatetimeIndex(list(test_input))
+ func = getattr(dt, rounder)
+ result = func(freq)
+ expected = DatetimeIndex(list(expected))
+ assert expected.equals(result)
+
+ @pytest.mark.parametrize(
+ "start, index_freq, periods",
+ [("2018-01-01", "12h", 25), ("2018-01-01 0:0:0.124999", "1ns", 1000)],
+ )
+ @pytest.mark.parametrize(
+ "round_freq",
+ [
+ "2ns",
+ "3ns",
+ "4ns",
+ "5ns",
+ "6ns",
+ "7ns",
+ "250ns",
+ "500ns",
+ "750ns",
+ "1us",
+ "19us",
+ "250us",
+ "500us",
+ "750us",
+ "1s",
+ "2s",
+ "3s",
+ "12h",
+ "1D",
+ ],
+ )
+ def test_round_int64(self, start, index_freq, periods, round_freq):
+ dt = date_range(start=start, freq=index_freq, periods=periods, unit="ns")
+ unit = to_offset(round_freq).nanos
+
+ # test floor
+ result = dt.floor(round_freq)
+ diff = dt.asi8 - result.asi8
+ mod = result.asi8 % unit
+ assert (mod == 0).all(), f"floor not a {round_freq} multiple"
+ assert (0 <= diff).all() and (diff < unit).all(), "floor error"
+
+ # test ceil
+ result = dt.ceil(round_freq)
+ diff = result.asi8 - dt.asi8
+ mod = result.asi8 % unit
+ assert (mod == 0).all(), f"ceil not a {round_freq} multiple"
+ assert (0 <= diff).all() and (diff < unit).all(), "ceil error"
+
+ # test round
+ result = dt.round(round_freq)
+ diff = abs(result.asi8 - dt.asi8)
+ mod = result.asi8 % unit
+ assert (mod == 0).all(), f"round not a {round_freq} multiple"
+ assert (diff <= unit // 2).all(), "round error"
+ if unit % 2 == 0:
+ assert (result.asi8[diff == unit // 2] % 2 == 0).all(), (
+ "round half to even error"
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_shift.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_shift.py
new file mode 100644
index 0000000000000000000000000000000000000000..a202627550cd2f4ce82dd6e2395778d8d3a2e9ef
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_shift.py
@@ -0,0 +1,181 @@
+from datetime import datetime
+import zoneinfo
+
+import pytest
+
+from pandas.errors import NullFrequencyError
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeIndexShift:
+ # -------------------------------------------------------------
+ # DatetimeIndex.shift is used in integer addition
+
+ def test_dti_shift_tzaware(self, tz_naive_fixture, unit):
+ # GH#9903
+ tz = tz_naive_fixture
+ idx = DatetimeIndex([], name="xxx", tz=tz).as_unit(unit)
+ tm.assert_index_equal(idx.shift(0, freq="h"), idx)
+ tm.assert_index_equal(idx.shift(3, freq="h"), idx)
+
+ idx = DatetimeIndex(
+ ["2011-01-01 10:00", "2011-01-01 11:00", "2011-01-01 12:00"],
+ name="xxx",
+ tz=tz,
+ freq="h",
+ ).as_unit(unit)
+ tm.assert_index_equal(idx.shift(0, freq="h"), idx)
+ exp = DatetimeIndex(
+ ["2011-01-01 13:00", "2011-01-01 14:00", "2011-01-01 15:00"],
+ name="xxx",
+ tz=tz,
+ freq="h",
+ ).as_unit(unit)
+ tm.assert_index_equal(idx.shift(3, freq="h"), exp)
+ exp = DatetimeIndex(
+ ["2011-01-01 07:00", "2011-01-01 08:00", "2011-01-01 09:00"],
+ name="xxx",
+ tz=tz,
+ freq="h",
+ ).as_unit(unit)
+ tm.assert_index_equal(idx.shift(-3, freq="h"), exp)
+
+ def test_dti_shift_freqs(self, unit):
+ # test shift for DatetimeIndex and non DatetimeIndex
+ # GH#8083
+ drange = date_range("20130101", periods=5, unit=unit)
+ result = drange.shift(1)
+ expected = DatetimeIndex(
+ ["2013-01-02", "2013-01-03", "2013-01-04", "2013-01-05", "2013-01-06"],
+ dtype=f"M8[{unit}]",
+ freq="D",
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = drange.shift(-1)
+ expected = DatetimeIndex(
+ ["2012-12-31", "2013-01-01", "2013-01-02", "2013-01-03", "2013-01-04"],
+ dtype=f"M8[{unit}]",
+ freq="D",
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = drange.shift(3, freq="2D")
+ expected = DatetimeIndex(
+ ["2013-01-07", "2013-01-08", "2013-01-09", "2013-01-10", "2013-01-11"],
+ dtype=f"M8[{unit}]",
+ freq="D",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_dti_shift_int(self, unit):
+ rng = date_range("1/1/2000", periods=20, unit=unit)
+
+ result = rng + 5 * rng.freq
+ expected = rng.shift(5)
+ tm.assert_index_equal(result, expected)
+
+ result = rng - 5 * rng.freq
+ expected = rng.shift(-5)
+ tm.assert_index_equal(result, expected)
+
+ def test_dti_shift_no_freq(self, unit):
+ # GH#19147
+ dti = DatetimeIndex(["2011-01-01 10:00", "2011-01-01"], freq=None).as_unit(unit)
+ with pytest.raises(NullFrequencyError, match="Cannot shift with no freq"):
+ dti.shift(2)
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_dti_shift_localized(self, tzstr, unit):
+ dr = date_range("2011/1/1", "2012/1/1", freq="W-FRI", unit=unit)
+ dr_tz = dr.tz_localize(tzstr)
+
+ result = dr_tz.shift(1, "10min")
+ assert result.tz == dr_tz.tz
+
+ def test_dti_shift_across_dst(self, unit):
+ # GH 8616
+ idx = date_range(
+ "2013-11-03", tz="America/Chicago", periods=7, freq="h", unit=unit
+ )
+ ser = Series(index=idx[:-1], dtype=object)
+ result = ser.shift(freq="h")
+ expected = Series(index=idx[1:], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "shift, result_time",
+ [
+ [0, "2014-11-14 00:00:00"],
+ [-1, "2014-11-13 23:00:00"],
+ [1, "2014-11-14 01:00:00"],
+ ],
+ )
+ def test_dti_shift_near_midnight(self, shift, result_time, unit):
+ # GH 8616
+ tz = zoneinfo.ZoneInfo("US/Eastern")
+ dt_est = datetime(2014, 11, 14, 0, tzinfo=tz)
+ idx = DatetimeIndex([dt_est]).as_unit(unit)
+ ser = Series(data=[1], index=idx)
+ result = ser.shift(shift, freq="h")
+ exp_index = DatetimeIndex([result_time], tz=tz).as_unit(unit)
+ expected = Series(1, index=exp_index)
+ tm.assert_series_equal(result, expected)
+
+ def test_shift_periods(self, unit):
+ # GH#22458 : argument 'n' was deprecated in favor of 'periods'
+ idx = date_range(
+ start=datetime(2009, 1, 1), end=datetime(2010, 1, 1), periods=3, unit=unit
+ )
+ tm.assert_index_equal(idx.shift(periods=0), idx)
+ tm.assert_index_equal(idx.shift(0), idx)
+
+ @pytest.mark.parametrize("freq", ["B", "C"])
+ def test_shift_bday(self, freq, unit):
+ rng = date_range(
+ datetime(2009, 1, 1), datetime(2010, 1, 1), freq=freq, unit=unit
+ )
+ shifted = rng.shift(5)
+ assert shifted[0] == rng[5]
+ assert shifted.freq == rng.freq
+
+ shifted = rng.shift(-5)
+ assert shifted[5] == rng[0]
+ assert shifted.freq == rng.freq
+
+ shifted = rng.shift(0)
+ assert shifted[0] == rng[0]
+ assert shifted.freq == rng.freq
+
+ def test_shift_bmonth(self, performance_warning, unit):
+ rng = date_range(
+ datetime(2009, 1, 1),
+ datetime(2010, 1, 1),
+ freq=pd.offsets.BMonthEnd(),
+ unit=unit,
+ )
+ shifted = rng.shift(1, freq=pd.offsets.BDay())
+ assert shifted[0] == rng[0] + pd.offsets.BDay()
+
+ rng = date_range(
+ datetime(2009, 1, 1),
+ datetime(2010, 1, 1),
+ freq=pd.offsets.BMonthEnd(),
+ unit=unit,
+ )
+ with tm.assert_produces_warning(performance_warning):
+ shifted = rng.shift(1, freq=pd.offsets.CDay())
+ assert shifted[0] == rng[0] + pd.offsets.CDay()
+
+ def test_shift_empty(self, unit):
+ # GH#14811
+ dti = date_range(start="2016-10-21", end="2016-10-21", freq="BME", unit=unit)
+ result = dti.shift(1)
+ tm.assert_index_equal(result, dti)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_snap.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_snap.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3d8f76313e7a097fac86f85e54feeba9d7c5f74
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_snap.py
@@ -0,0 +1,54 @@
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ DatetimeIndex,
+ date_range,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+@pytest.mark.filterwarnings("ignore:Period with BDay freq:FutureWarning")
+@pytest.mark.parametrize("tz", [None, "Asia/Shanghai", "Europe/Berlin"])
+@pytest.mark.parametrize("name", [None, "my_dti"])
+def test_dti_snap(name, tz, unit):
+ dti = DatetimeIndex(
+ [
+ "1/1/2002",
+ "1/2/2002",
+ "1/3/2002",
+ "1/4/2002",
+ "1/5/2002",
+ "1/6/2002",
+ "1/7/2002",
+ ],
+ name=name,
+ tz=tz,
+ freq="D",
+ )
+ dti = dti.as_unit(unit)
+
+ result = dti.snap(freq="W-MON")
+ msg = "'w-mon' is deprecated and will be removed in a future version."
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = date_range("12/31/2001", "1/7/2002", name=name, tz=tz, freq="w-mon")
+ expected = expected.repeat([3, 4])
+ expected = expected.as_unit(unit)
+ tm.assert_index_equal(result, expected)
+ assert result.tz == expected.tz
+ assert result.freq is None
+ assert expected.freq is None
+
+ result = dti.snap(freq="B")
+
+ msg = "'b' is deprecated and will be removed in a future version."
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = date_range("1/1/2002", "1/7/2002", name=name, tz=tz, freq="b")
+ expected = expected.repeat([1, 1, 1, 2, 2])
+ expected = expected.as_unit(unit)
+ tm.assert_index_equal(result, expected)
+ assert result.tz == expected.tz
+ assert result.freq is None
+ assert expected.freq is None
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_frame.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_frame.py
new file mode 100644
index 0000000000000000000000000000000000000000..c829109d4e06c14dca160f1de8903432f844f4ef
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_frame.py
@@ -0,0 +1,28 @@
+from pandas import (
+ DataFrame,
+ Index,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestToFrame:
+ def test_to_frame_datetime_tz(self):
+ # GH#25809
+ idx = date_range(start="2019-01-01", end="2019-01-30", freq="D", tz="UTC")
+ result = idx.to_frame()
+ expected = DataFrame(idx, index=idx)
+ tm.assert_frame_equal(result, expected)
+
+ def test_to_frame_respects_none_name(self):
+ # GH#44212 if we explicitly pass name=None, then that should be respected,
+ # not changed to 0
+ # GH-45448 this is first deprecated to only change in the future
+ idx = date_range(start="2019-01-01", end="2019-01-30", freq="D", tz="UTC")
+ result = idx.to_frame(name=None)
+ exp_idx = Index([None], dtype=object)
+ tm.assert_index_equal(exp_idx, result.columns)
+
+ result = idx.rename("foo").to_frame(name=None)
+ exp_idx = Index([None], dtype=object)
+ tm.assert_index_equal(exp_idx, result.columns)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_julian_date.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_julian_date.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc1f0595c21c527816acedf6ef97839ce7d71713
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_julian_date.py
@@ -0,0 +1,45 @@
+import numpy as np
+
+from pandas import (
+ Index,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestDateTimeIndexToJulianDate:
+ def test_1700(self):
+ dr = date_range(start=Timestamp("1710-10-01"), periods=5, freq="D")
+ r1 = Index([x.to_julian_date() for x in dr])
+ r2 = dr.to_julian_date()
+ assert isinstance(r2, Index) and r2.dtype == np.float64
+ tm.assert_index_equal(r1, r2)
+
+ def test_2000(self):
+ dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="D")
+ r1 = Index([x.to_julian_date() for x in dr])
+ r2 = dr.to_julian_date()
+ assert isinstance(r2, Index) and r2.dtype == np.float64
+ tm.assert_index_equal(r1, r2)
+
+ def test_hour(self):
+ dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="h")
+ r1 = Index([x.to_julian_date() for x in dr])
+ r2 = dr.to_julian_date()
+ assert isinstance(r2, Index) and r2.dtype == np.float64
+ tm.assert_index_equal(r1, r2)
+
+ def test_minute(self):
+ dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="min")
+ r1 = Index([x.to_julian_date() for x in dr])
+ r2 = dr.to_julian_date()
+ assert isinstance(r2, Index) and r2.dtype == np.float64
+ tm.assert_index_equal(r1, r2)
+
+ def test_second(self):
+ dr = date_range(start=Timestamp("2000-02-27"), periods=5, freq="s")
+ r1 = Index([x.to_julian_date() for x in dr])
+ r2 = dr.to_julian_date()
+ assert isinstance(r2, Index) and r2.dtype == np.float64
+ tm.assert_index_equal(r1, r2)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_period.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_period.py
new file mode 100644
index 0000000000000000000000000000000000000000..a659a3e05a29e64d3d6d6a40d24666ab0ebea04f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_period.py
@@ -0,0 +1,220 @@
+from datetime import timezone
+
+import dateutil.tz
+from dateutil.tz import tzlocal
+import pytest
+
+from pandas._libs.tslibs.ccalendar import MONTHS
+from pandas._libs.tslibs.offsets import MonthEnd
+from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG
+
+from pandas import (
+ DatetimeIndex,
+ Period,
+ PeriodIndex,
+ Timestamp,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestToPeriod:
+ def test_dti_to_period(self):
+ dti = date_range(start="1/1/2005", end="12/1/2005", freq="ME")
+ pi1 = dti.to_period()
+ pi2 = dti.to_period(freq="D")
+ pi3 = dti.to_period(freq="3D")
+
+ assert pi1[0] == Period("Jan 2005", freq="M")
+ assert pi2[0] == Period("1/31/2005", freq="D")
+ assert pi3[0] == Period("1/31/2005", freq="3D")
+
+ assert pi1[-1] == Period("Nov 2005", freq="M")
+ assert pi2[-1] == Period("11/30/2005", freq="D")
+ assert pi3[-1], Period("11/30/2005", freq="3D")
+
+ tm.assert_index_equal(pi1, period_range("1/1/2005", "11/1/2005", freq="M"))
+ tm.assert_index_equal(
+ pi2, period_range("1/1/2005", "11/1/2005", freq="M").asfreq("D")
+ )
+ tm.assert_index_equal(
+ pi3, period_range("1/1/2005", "11/1/2005", freq="M").asfreq("3D")
+ )
+
+ @pytest.mark.parametrize("month", MONTHS)
+ def test_to_period_quarterly(self, month):
+ # make sure we can make the round trip
+ freq = f"Q-{month}"
+ rng = period_range("1989Q3", "1991Q3", freq=freq)
+ stamps = rng.to_timestamp()
+ result = stamps.to_period(freq)
+ tm.assert_index_equal(rng, result)
+
+ @pytest.mark.parametrize("off", ["BQE", "QS", "BQS"])
+ def test_to_period_quarterlyish(self, off):
+ rng = date_range("01-Jan-2012", periods=8, freq=off)
+ prng = rng.to_period()
+ assert prng.freq == "QE-DEC"
+
+ @pytest.mark.parametrize("off", ["BYE", "YS", "BYS"])
+ def test_to_period_annualish(self, off):
+ rng = date_range("01-Jan-2012", periods=8, freq=off)
+ prng = rng.to_period()
+ assert prng.freq == "YE-DEC"
+
+ def test_to_period_monthish(self):
+ offsets = ["MS", "BME"]
+ for off in offsets:
+ rng = date_range("01-Jan-2012", periods=8, freq=off)
+ prng = rng.to_period()
+ assert prng.freqstr == "M"
+
+ rng = date_range("01-Jan-2012", periods=8, freq="ME")
+ prng = rng.to_period()
+ assert prng.freqstr == "M"
+
+ with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG):
+ date_range("01-Jan-2012", periods=8, freq="EOM")
+
+ @pytest.mark.parametrize(
+ "freq_offset, freq_period",
+ [
+ ("2ME", "2M"),
+ (MonthEnd(2), MonthEnd(2)),
+ ],
+ )
+ def test_dti_to_period_2monthish(self, freq_offset, freq_period):
+ dti = date_range("2020-01-01", periods=3, freq=freq_offset)
+ pi = dti.to_period()
+
+ tm.assert_index_equal(pi, period_range("2020-01", "2020-05", freq=freq_period))
+
+ @pytest.mark.parametrize(
+ "freq", ["2ME", "1me", "2QE", "2QE-SEP", "1YE", "ye", "2YE-MAR"]
+ )
+ def test_to_period_frequency_M_Q_Y_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ rng = date_range("01-Jan-2012", periods=8, freq="ME")
+ with pytest.raises(ValueError, match=msg):
+ rng.to_period(freq)
+
+ def test_to_period_infer(self):
+ # https://github.com/pandas-dev/pandas/issues/33358
+ rng = date_range(
+ start="2019-12-22 06:40:00+00:00",
+ end="2019-12-22 08:45:00+00:00",
+ freq="5min",
+ )
+
+ with tm.assert_produces_warning(UserWarning, match="drop timezone info"):
+ pi1 = rng.to_period("5min")
+
+ with tm.assert_produces_warning(UserWarning, match="drop timezone info"):
+ pi2 = rng.to_period()
+
+ tm.assert_index_equal(pi1, pi2)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_period_dt64_round_trip(self):
+ dti = date_range("1/1/2000", "1/7/2002", freq="B", unit="ns")
+ pi = dti.to_period()
+ tm.assert_index_equal(pi.to_timestamp(), dti.as_unit("us"))
+
+ dti = date_range("1/1/2000", "1/7/2002", freq="B", unit="ns")
+ pi = dti.to_period(freq="h")
+ tm.assert_index_equal(pi.to_timestamp(), dti.as_unit("us"))
+
+ def test_to_period_millisecond(self):
+ index = DatetimeIndex(
+ [
+ Timestamp("2007-01-01 10:11:12.123456Z"),
+ Timestamp("2007-01-01 10:11:13.789123Z"),
+ ]
+ )
+
+ with tm.assert_produces_warning(UserWarning, match="drop timezone info"):
+ period = index.to_period(freq="ms")
+ assert 2 == len(period)
+ assert period[0] == Period("2007-01-01 10:11:12.123Z", "ms")
+ assert period[1] == Period("2007-01-01 10:11:13.789Z", "ms")
+
+ def test_to_period_microsecond(self):
+ index = DatetimeIndex(
+ [
+ Timestamp("2007-01-01 10:11:12.123456Z"),
+ Timestamp("2007-01-01 10:11:13.789123Z"),
+ ]
+ )
+
+ with tm.assert_produces_warning(UserWarning, match="drop timezone info"):
+ period = index.to_period(freq="us")
+ assert 2 == len(period)
+ assert period[0] == Period("2007-01-01 10:11:12.123456Z", "us")
+ assert period[1] == Period("2007-01-01 10:11:13.789123Z", "us")
+
+ @pytest.mark.parametrize(
+ "tz",
+ [
+ "US/Eastern",
+ timezone.utc,
+ tzlocal(),
+ "dateutil/US/Eastern",
+ dateutil.tz.tzutc(),
+ ],
+ )
+ def test_to_period_tz(self, tz):
+ ts = date_range("1/1/2000", "2/1/2000", tz=tz)
+
+ with tm.assert_produces_warning(UserWarning, match="drop timezone info"):
+ result = ts.to_period()[0]
+ expected = ts[0].to_period(ts.freq)
+
+ assert result == expected
+
+ expected = date_range("1/1/2000", "2/1/2000").to_period()
+
+ with tm.assert_produces_warning(UserWarning, match="drop timezone info"):
+ result = ts.to_period(ts.freq)
+
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("tz", ["Etc/GMT-1", "Etc/GMT+1"])
+ def test_to_period_tz_utc_offset_consistency(self, tz):
+ # GH#22905
+ ts = date_range("1/1/2000", "2/1/2000", tz="Etc/GMT-1")
+ with tm.assert_produces_warning(UserWarning, match="drop timezone info"):
+ result = ts.to_period()[0]
+ expected = ts[0].to_period(ts.freq)
+ assert result == expected
+
+ def test_to_period_nofreq(self):
+ idx = DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-04"])
+ msg = "You must pass a freq argument as current index has none."
+ with pytest.raises(ValueError, match=msg):
+ idx.to_period()
+
+ idx = DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-03"], freq="infer")
+ assert idx.freqstr == "D"
+ expected = PeriodIndex(["2000-01-01", "2000-01-02", "2000-01-03"], freq="D")
+ tm.assert_index_equal(idx.to_period(), expected)
+
+ # GH#7606
+ idx = DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-03"])
+ assert idx.freqstr is None
+ tm.assert_index_equal(idx.to_period(), expected)
+
+ @pytest.mark.parametrize("freq", ["2BME", "SME-15", "2BMS"])
+ def test_to_period_offsets_not_supported(self, freq):
+ # GH#56243
+ msg = "|".join(
+ [
+ f"Invalid frequency: {freq}",
+ f"{freq} is not supported as period frequency",
+ ]
+ )
+
+ ts = date_range("1/1/2012", periods=4, freq=freq)
+ with pytest.raises(ValueError, match=msg):
+ ts.to_period()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_pydatetime.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_pydatetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe97ff0cca8ebe6d04ce093077d6ee44d73a7e0b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_pydatetime.py
@@ -0,0 +1,51 @@
+from datetime import (
+ datetime,
+ timezone,
+)
+
+import dateutil.parser
+import dateutil.tz
+from dateutil.tz import tzlocal
+import numpy as np
+
+from pandas import (
+ DatetimeIndex,
+ date_range,
+ to_datetime,
+)
+import pandas._testing as tm
+from pandas.tests.indexes.datetimes.test_timezones import FixedOffset
+
+fixed_off = FixedOffset(-420, "-07:00")
+
+
+class TestToPyDatetime:
+ def test_dti_to_pydatetime(self):
+ dt = dateutil.parser.parse("2012-06-13T01:39:00Z")
+ dt = dt.replace(tzinfo=tzlocal())
+
+ arr = np.array([dt], dtype=object)
+
+ result = to_datetime(arr, utc=True)
+ assert result.tz is timezone.utc
+
+ rng = date_range("2012-11-03 03:00", "2012-11-05 03:00", tz=tzlocal())
+ arr = rng.to_pydatetime()
+ result = to_datetime(arr, utc=True)
+ assert result.tz is timezone.utc
+
+ def test_dti_to_pydatetime_fizedtz(self):
+ dates = np.array(
+ [
+ datetime(2000, 1, 1, tzinfo=fixed_off),
+ datetime(2000, 1, 2, tzinfo=fixed_off),
+ datetime(2000, 1, 3, tzinfo=fixed_off),
+ ]
+ )
+ dti = DatetimeIndex(dates)
+
+ result = dti.to_pydatetime()
+ tm.assert_numpy_array_equal(dates, result)
+
+ result = dti._mpl_repr()
+ tm.assert_numpy_array_equal(dates, result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_series.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_series.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd67775b7a5fcce99b0488ddcc62d88606785168
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_to_series.py
@@ -0,0 +1,18 @@
+import numpy as np
+
+from pandas import (
+ DatetimeIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestToSeries:
+ def test_to_series(self):
+ naive = DatetimeIndex(["2013-1-1 13:00", "2013-1-2 14:00"], name="B")
+ idx = naive.tz_localize("US/Pacific")
+
+ expected = Series(np.array(idx.tolist(), dtype="object"), name="B")
+ result = idx.to_series(index=range(2))
+ assert expected.dtype == idx.dtype
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_tz_convert.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_tz_convert.py
new file mode 100644
index 0000000000000000000000000000000000000000..20dc35f7f8d491493fd2137ffa4cc120cb253b62
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_tz_convert.py
@@ -0,0 +1,292 @@
+from datetime import datetime
+
+import dateutil.tz
+from dateutil.tz import gettz
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import timezones
+
+from pandas import (
+ DatetimeIndex,
+ Index,
+ NaT,
+ Timestamp,
+ date_range,
+ offsets,
+)
+import pandas._testing as tm
+
+
+class TestTZConvert:
+ def test_tz_convert_nat(self):
+ # GH#5546
+ dates = [NaT]
+ idx = DatetimeIndex(dates)
+ idx = idx.tz_localize("US/Pacific")
+ tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Pacific"))
+ idx = idx.tz_convert("US/Eastern")
+ tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Eastern"))
+ idx = idx.tz_convert("UTC")
+ tm.assert_index_equal(idx, DatetimeIndex(dates, tz="UTC"))
+
+ dates = ["2010-12-01 00:00", "2010-12-02 00:00", NaT]
+ idx = DatetimeIndex(dates)
+ idx = idx.tz_localize("US/Pacific")
+ tm.assert_index_equal(idx, DatetimeIndex(dates, tz="US/Pacific"))
+ idx = idx.tz_convert("US/Eastern")
+ expected = ["2010-12-01 03:00", "2010-12-02 03:00", NaT]
+ tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern"))
+
+ idx = idx + offsets.Hour(5)
+ expected = ["2010-12-01 08:00", "2010-12-02 08:00", NaT]
+ tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern"))
+ idx = idx.tz_convert("US/Pacific")
+ expected = ["2010-12-01 05:00", "2010-12-02 05:00", NaT]
+ tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Pacific"))
+
+ idx = idx + np.timedelta64(3, "h")
+ expected = ["2010-12-01 08:00", "2010-12-02 08:00", NaT]
+ tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Pacific"))
+
+ idx = idx.tz_convert("US/Eastern")
+ expected = ["2010-12-01 11:00", "2010-12-02 11:00", NaT]
+ tm.assert_index_equal(idx, DatetimeIndex(expected, tz="US/Eastern"))
+
+ @pytest.mark.parametrize("prefix", ["", "dateutil/"])
+ def test_dti_tz_convert_compat_timestamp(self, prefix):
+ strdates = ["1/1/2012", "3/1/2012", "4/1/2012"]
+ idx = DatetimeIndex(strdates, tz=prefix + "US/Eastern")
+
+ conv = idx[0].tz_convert(prefix + "US/Pacific")
+ expected = idx.tz_convert(prefix + "US/Pacific")[0]
+
+ assert conv == expected
+
+ def test_dti_tz_convert_hour_overflow_dst(self):
+ # Regression test for GH#13306
+
+ # sorted case US/Eastern -> UTC
+ ts = ["2008-05-12 09:50:00", "2008-12-12 09:50:35", "2009-05-12 09:50:32"]
+ tt = DatetimeIndex(ts).tz_localize("US/Eastern")
+ ut = tt.tz_convert("UTC")
+ expected = Index([13, 14, 13], dtype=np.int32)
+ tm.assert_index_equal(ut.hour, expected)
+
+ # sorted case UTC -> US/Eastern
+ ts = ["2008-05-12 13:50:00", "2008-12-12 14:50:35", "2009-05-12 13:50:32"]
+ tt = DatetimeIndex(ts).tz_localize("UTC")
+ ut = tt.tz_convert("US/Eastern")
+ expected = Index([9, 9, 9], dtype=np.int32)
+ tm.assert_index_equal(ut.hour, expected)
+
+ # unsorted case US/Eastern -> UTC
+ ts = ["2008-05-12 09:50:00", "2008-12-12 09:50:35", "2008-05-12 09:50:32"]
+ tt = DatetimeIndex(ts).tz_localize("US/Eastern")
+ ut = tt.tz_convert("UTC")
+ expected = Index([13, 14, 13], dtype=np.int32)
+ tm.assert_index_equal(ut.hour, expected)
+
+ # unsorted case UTC -> US/Eastern
+ ts = ["2008-05-12 13:50:00", "2008-12-12 14:50:35", "2008-05-12 13:50:32"]
+ tt = DatetimeIndex(ts).tz_localize("UTC")
+ ut = tt.tz_convert("US/Eastern")
+ expected = Index([9, 9, 9], dtype=np.int32)
+ tm.assert_index_equal(ut.hour, expected)
+
+ @pytest.mark.parametrize("tz", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_dti_tz_convert_hour_overflow_dst_timestamps(self, tz):
+ # Regression test for GH#13306
+
+ # sorted case US/Eastern -> UTC
+ ts = [
+ Timestamp("2008-05-12 09:50:00", tz=tz),
+ Timestamp("2008-12-12 09:50:35", tz=tz),
+ Timestamp("2009-05-12 09:50:32", tz=tz),
+ ]
+ tt = DatetimeIndex(ts)
+ ut = tt.tz_convert("UTC")
+ expected = Index([13, 14, 13], dtype=np.int32)
+ tm.assert_index_equal(ut.hour, expected)
+
+ # sorted case UTC -> US/Eastern
+ ts = [
+ Timestamp("2008-05-12 13:50:00", tz="UTC"),
+ Timestamp("2008-12-12 14:50:35", tz="UTC"),
+ Timestamp("2009-05-12 13:50:32", tz="UTC"),
+ ]
+ tt = DatetimeIndex(ts)
+ ut = tt.tz_convert("US/Eastern")
+ expected = Index([9, 9, 9], dtype=np.int32)
+ tm.assert_index_equal(ut.hour, expected)
+
+ # unsorted case US/Eastern -> UTC
+ ts = [
+ Timestamp("2008-05-12 09:50:00", tz=tz),
+ Timestamp("2008-12-12 09:50:35", tz=tz),
+ Timestamp("2008-05-12 09:50:32", tz=tz),
+ ]
+ tt = DatetimeIndex(ts)
+ ut = tt.tz_convert("UTC")
+ expected = Index([13, 14, 13], dtype=np.int32)
+ tm.assert_index_equal(ut.hour, expected)
+
+ # unsorted case UTC -> US/Eastern
+ ts = [
+ Timestamp("2008-05-12 13:50:00", tz="UTC"),
+ Timestamp("2008-12-12 14:50:35", tz="UTC"),
+ Timestamp("2008-05-12 13:50:32", tz="UTC"),
+ ]
+ tt = DatetimeIndex(ts)
+ ut = tt.tz_convert("US/Eastern")
+ expected = Index([9, 9, 9], dtype=np.int32)
+ tm.assert_index_equal(ut.hour, expected)
+
+ @pytest.mark.parametrize("freq, n", [("h", 1), ("min", 60), ("s", 3600)])
+ def test_dti_tz_convert_trans_pos_plus_1__bug(self, freq, n):
+ # Regression test for tslib.tz_convert(vals, tz1, tz2).
+ # See GH#4496 for details.
+ idx = date_range(datetime(2011, 3, 26, 23), datetime(2011, 3, 27, 1), freq=freq)
+ idx = idx.tz_localize("UTC")
+ idx = idx.tz_convert("Europe/Moscow")
+
+ expected = np.repeat(np.array([3, 4, 5]), np.array([n, n, 1]))
+ tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32))
+
+ def test_dti_tz_convert_dst(self):
+ for freq, n in [("h", 1), ("min", 60), ("s", 3600)]:
+ # Start DST
+ idx = date_range(
+ "2014-03-08 23:00", "2014-03-09 09:00", freq=freq, tz="UTC"
+ )
+ idx = idx.tz_convert("US/Eastern")
+ expected = np.repeat(
+ np.array([18, 19, 20, 21, 22, 23, 0, 1, 3, 4, 5]),
+ np.array([n, n, n, n, n, n, n, n, n, n, 1]),
+ )
+ tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32))
+
+ idx = date_range(
+ "2014-03-08 18:00", "2014-03-09 05:00", freq=freq, tz="US/Eastern"
+ )
+ idx = idx.tz_convert("UTC")
+ expected = np.repeat(
+ np.array([23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
+ np.array([n, n, n, n, n, n, n, n, n, n, 1]),
+ )
+ tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32))
+
+ # End DST
+ idx = date_range(
+ "2014-11-01 23:00", "2014-11-02 09:00", freq=freq, tz="UTC"
+ )
+ idx = idx.tz_convert("US/Eastern")
+ expected = np.repeat(
+ np.array([19, 20, 21, 22, 23, 0, 1, 1, 2, 3, 4]),
+ np.array([n, n, n, n, n, n, n, n, n, n, 1]),
+ )
+ tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32))
+
+ idx = date_range(
+ "2014-11-01 18:00", "2014-11-02 05:00", freq=freq, tz="US/Eastern"
+ )
+ idx = idx.tz_convert("UTC")
+ expected = np.repeat(
+ np.array([22, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
+ np.array([n, n, n, n, n, n, n, n, n, n, n, n, 1]),
+ )
+ tm.assert_index_equal(idx.hour, Index(expected, dtype=np.int32))
+
+ # daily
+ # Start DST
+ idx = date_range("2014-03-08 00:00", "2014-03-09 00:00", freq="D", tz="UTC")
+ idx = idx.tz_convert("US/Eastern")
+ tm.assert_index_equal(idx.hour, Index([19, 19], dtype=np.int32))
+
+ idx = date_range(
+ "2014-03-08 00:00", "2014-03-09 00:00", freq="D", tz="US/Eastern"
+ )
+ idx = idx.tz_convert("UTC")
+ tm.assert_index_equal(idx.hour, Index([5, 5], dtype=np.int32))
+
+ # End DST
+ idx = date_range("2014-11-01 00:00", "2014-11-02 00:00", freq="D", tz="UTC")
+ idx = idx.tz_convert("US/Eastern")
+ tm.assert_index_equal(idx.hour, Index([20, 20], dtype=np.int32))
+
+ idx = date_range(
+ "2014-11-01 00:00", "2014-11-02 000:00", freq="D", tz="US/Eastern"
+ )
+ idx = idx.tz_convert("UTC")
+ tm.assert_index_equal(idx.hour, Index([4, 4], dtype=np.int32))
+
+ def test_tz_convert_roundtrip(self, tz_aware_fixture):
+ tz = tz_aware_fixture
+ idx1 = date_range(start="2014-01-01", end="2014-12-31", freq="ME", tz="UTC")
+ exp1 = date_range(start="2014-01-01", end="2014-12-31", freq="ME")
+
+ idx2 = date_range(start="2014-01-01", end="2014-12-31", freq="D", tz="UTC")
+ exp2 = date_range(start="2014-01-01", end="2014-12-31", freq="D")
+
+ idx3 = date_range(start="2014-01-01", end="2014-03-01", freq="h", tz="UTC")
+ exp3 = date_range(start="2014-01-01", end="2014-03-01", freq="h")
+
+ idx4 = date_range(start="2014-08-01", end="2014-10-31", freq="min", tz="UTC")
+ exp4 = date_range(start="2014-08-01", end="2014-10-31", freq="min")
+
+ for idx, expected in [(idx1, exp1), (idx2, exp2), (idx3, exp3), (idx4, exp4)]:
+ converted = idx.tz_convert(tz)
+ reset = converted.tz_convert(None)
+ tm.assert_index_equal(reset, expected)
+ assert reset.tzinfo is None
+ expected = converted.tz_convert("UTC").tz_localize(None)
+ expected = expected._with_freq("infer")
+ tm.assert_index_equal(reset, expected)
+
+ def test_dti_tz_convert_tzlocal(self):
+ # GH#13583
+ # tz_convert doesn't affect to internal
+ dti = date_range(start="2001-01-01", end="2001-03-01", tz="UTC")
+ dti2 = dti.tz_convert(dateutil.tz.tzlocal())
+ tm.assert_numpy_array_equal(dti2.asi8, dti.asi8)
+
+ dti = date_range(start="2001-01-01", end="2001-03-01", tz=dateutil.tz.tzlocal())
+ dti2 = dti.tz_convert(None)
+ tm.assert_numpy_array_equal(dti2.asi8, dti.asi8)
+
+ @pytest.mark.parametrize(
+ "tz",
+ [
+ "US/Eastern",
+ "dateutil/US/Eastern",
+ "pytz/US/Eastern",
+ gettz("US/Eastern"),
+ ],
+ )
+ def test_dti_tz_convert_utc_to_local_no_modify(self, tz):
+ if isinstance(tz, str) and tz.startswith("pytz/"):
+ pytz = pytest.importorskip("pytz")
+ tz = pytz.timezone(tz.removeprefix("pytz/"))
+ rng = date_range("3/11/2012", "3/12/2012", freq="h", tz="utc")
+ rng_eastern = rng.tz_convert(tz)
+
+ # Values are unmodified
+ tm.assert_numpy_array_equal(rng.asi8, rng_eastern.asi8)
+
+ assert timezones.tz_compare(rng_eastern.tz, timezones.maybe_get_tz(tz))
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_tz_convert_unsorted(self, tzstr):
+ dr = date_range("2012-03-09", freq="h", periods=100, tz="utc")
+ dr = dr.tz_convert(tzstr)
+
+ result = dr[::-1].hour
+ exp = dr.hour[::-1]
+ tm.assert_almost_equal(result, exp)
+
+ def test_dti_tz_convert_day_freq_not_preserved(self):
+ # GH#51716
+ dti = date_range("2020-3-28", periods=5, freq="D", tz="Europe/London")
+ result = dti.tz_convert("UTC")
+ assert (result == dti).all()
+ assert result.freq is None
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_tz_localize.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_tz_localize.py
new file mode 100644
index 0000000000000000000000000000000000000000..86738b9685839e59169e914af25676ea5d694dd8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_tz_localize.py
@@ -0,0 +1,380 @@
+from datetime import (
+ datetime,
+ timedelta,
+ timezone,
+)
+from zoneinfo import ZoneInfo
+
+import dateutil.tz
+from dateutil.tz import gettz
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ Timestamp,
+ bdate_range,
+ date_range,
+ offsets,
+ to_datetime,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture(params=["pytz/US/Eastern", gettz("US/Eastern"), ZoneInfo("US/Eastern")])
+def tz(request):
+ if isinstance(request.param, str) and request.param.startswith("pytz/"):
+ pytz = pytest.importorskip("pytz")
+ return pytz.timezone(request.param.removeprefix("pytz/"))
+ return request.param
+
+
+class TestTZLocalize:
+ def test_tz_localize_invalidates_freq(self):
+ # we only preserve freq in unambiguous cases
+
+ # if localized to US/Eastern, this crosses a DST transition
+ dti = date_range("2014-03-08 23:00", "2014-03-09 09:00", freq="h")
+ assert dti.freq == "h"
+
+ result = dti.tz_localize(None) # no-op
+ assert result.freq == "h"
+
+ result = dti.tz_localize("UTC") # unambiguous freq preservation
+ assert result.freq == "h"
+
+ result = dti.tz_localize("US/Eastern", nonexistent="shift_forward")
+ assert result.freq is None
+ assert result.inferred_freq is None # i.e. we are not _too_ strict here
+
+ # Case where we _can_ keep freq because we're length==1
+ dti2 = dti[:1]
+ result = dti2.tz_localize("US/Eastern")
+ assert result.freq == "h"
+
+ def test_tz_localize_utc_copies(self, utc_fixture):
+ # GH#46460
+ times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"]
+ index = DatetimeIndex(times)
+
+ res = index.tz_localize(utc_fixture)
+ assert not tm.shares_memory(res, index)
+
+ res2 = index._data.tz_localize(utc_fixture)
+ assert not tm.shares_memory(index._data, res2)
+
+ def test_dti_tz_localize_nonexistent_raise_coerce(self):
+ # GH#13057
+ times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"]
+ index = DatetimeIndex(times)
+ tz = "US/Eastern"
+ with pytest.raises(ValueError, match="|".join(times)):
+ index.tz_localize(tz=tz)
+
+ with pytest.raises(ValueError, match="|".join(times)):
+ index.tz_localize(tz=tz, nonexistent="raise")
+
+ result = index.tz_localize(tz=tz, nonexistent="NaT")
+ test_times = ["2015-03-08 01:00-05:00", "NaT", "2015-03-08 03:00-04:00"]
+ dti = to_datetime(test_times, utc=True)
+ expected = dti.tz_convert("US/Eastern")
+ tm.assert_index_equal(result, expected)
+
+ def test_dti_tz_localize_ambiguous_infer(self, tz):
+ # November 6, 2011, fall back, repeat 2 AM hour
+ # With no repeated hours, we cannot infer the transition
+ dr = date_range(datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour())
+ with pytest.raises(ValueError, match="Cannot infer dst time"):
+ dr.tz_localize(tz)
+
+ def test_dti_tz_localize_ambiguous_infer2(self, tz, unit):
+ # With repeated hours, we can infer the transition
+ dr = date_range(
+ datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour(), tz=tz, unit=unit
+ )
+ times = [
+ "11/06/2011 00:00",
+ "11/06/2011 01:00",
+ "11/06/2011 01:00",
+ "11/06/2011 02:00",
+ "11/06/2011 03:00",
+ ]
+ di = DatetimeIndex(times).as_unit(unit)
+ result = di.tz_localize(tz, ambiguous="infer")
+ expected = dr._with_freq(None)
+ tm.assert_index_equal(result, expected)
+ result2 = DatetimeIndex(times, tz=tz, ambiguous="infer").as_unit(unit)
+ tm.assert_index_equal(result2, expected)
+
+ def test_dti_tz_localize_ambiguous_infer3(self, tz):
+ # When there is no dst transition, nothing special happens
+ dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=offsets.Hour())
+ localized = dr.tz_localize(tz)
+ localized_infer = dr.tz_localize(tz, ambiguous="infer")
+ tm.assert_index_equal(localized, localized_infer)
+
+ def test_dti_tz_localize_ambiguous_times(self, tz):
+ # March 13, 2011, spring forward, skip from 2 AM to 3 AM
+ dr = date_range(datetime(2011, 3, 13, 1, 30), periods=3, freq=offsets.Hour())
+ with pytest.raises(ValueError, match="2011-03-13 02:30:00"):
+ dr.tz_localize(tz)
+
+ # after dst transition, it works
+ dr = date_range(
+ datetime(2011, 3, 13, 3, 30), periods=3, freq=offsets.Hour(), tz=tz
+ )
+
+ # November 6, 2011, fall back, repeat 2 AM hour
+ dr = date_range(datetime(2011, 11, 6, 1, 30), periods=3, freq=offsets.Hour())
+ with pytest.raises(ValueError, match="Cannot infer dst time"):
+ dr.tz_localize(tz)
+
+ # UTC is OK
+ dr = date_range(
+ datetime(2011, 3, 13), periods=48, freq=offsets.Minute(30), tz=timezone.utc
+ )
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_dti_tz_localize_pass_dates_to_utc(self, tzstr):
+ strdates = ["1/1/2012", "3/1/2012", "4/1/2012"]
+
+ idx = DatetimeIndex(strdates)
+ conv = idx.tz_localize(tzstr)
+
+ fromdates = DatetimeIndex(strdates, tz=tzstr)
+
+ assert conv.tz == fromdates.tz
+ tm.assert_numpy_array_equal(conv.values, fromdates.values)
+
+ @pytest.mark.parametrize("prefix", ["", "dateutil/"])
+ def test_dti_tz_localize(self, prefix):
+ tzstr = prefix + "US/Eastern"
+ dti = date_range(start="1/1/2005", end="1/1/2005 0:00:02.256", freq="ms")
+ dti2 = dti.tz_localize(tzstr)
+
+ dti_utc = date_range(
+ start="1/1/2005 05:00", end="1/1/2005 5:00:02.256", freq="ms", tz="utc"
+ )
+
+ tm.assert_numpy_array_equal(dti2.values, dti_utc.values)
+
+ dti3 = dti2.tz_convert(prefix + "US/Pacific")
+ tm.assert_numpy_array_equal(dti3.values, dti_utc.values)
+
+ dti = date_range(start="11/6/2011 1:59:59", end="11/6/2011 2:00", freq="ms")
+ with pytest.raises(ValueError, match="Cannot infer dst time"):
+ dti.tz_localize(tzstr)
+
+ dti = date_range(start="3/13/2011 1:59:59", end="3/13/2011 2:00", freq="ms")
+ with pytest.raises(ValueError, match="2011-03-13 02:00:00"):
+ dti.tz_localize(tzstr)
+
+ def test_dti_tz_localize_utc_conversion(self, tz):
+ # Localizing to time zone should:
+ # 1) check for DST ambiguities
+ # 2) convert to UTC
+
+ rng = date_range("3/10/2012", "3/11/2012", freq="30min")
+
+ converted = rng.tz_localize(tz)
+ expected_naive = rng + offsets.Hour(5)
+ tm.assert_numpy_array_equal(converted.asi8, expected_naive.asi8)
+
+ # DST ambiguity, this should fail
+ rng = date_range("3/11/2012", "3/12/2012", freq="30min")
+ # Is this really how it should fail??
+ with pytest.raises(ValueError, match="2012-03-11 02:00:00"):
+ rng.tz_localize(tz)
+
+ def test_dti_tz_localize_roundtrip(self, tz_aware_fixture):
+ # note: this tz tests that a tz-naive index can be localized
+ # and de-localized successfully, when there are no DST transitions
+ # in the range.
+ idx = date_range(start="2014-06-01", end="2014-08-30", freq="15min")
+ tz = tz_aware_fixture
+ localized = idx.tz_localize(tz)
+ # can't localize a tz-aware object
+ with pytest.raises(
+ TypeError, match="Already tz-aware, use tz_convert to convert"
+ ):
+ localized.tz_localize(tz)
+ reset = localized.tz_localize(None)
+ assert reset.tzinfo is None
+ expected = idx._with_freq(None)
+ tm.assert_index_equal(reset, expected)
+
+ def test_dti_tz_localize_naive(self):
+ rng = date_range("1/1/2011", periods=100, freq="h")
+
+ conv = rng.tz_localize("US/Pacific")
+ exp = date_range("1/1/2011", periods=100, freq="h", tz="US/Pacific")
+
+ tm.assert_index_equal(conv, exp._with_freq(None))
+
+ def test_dti_tz_localize_tzlocal(self):
+ # GH#13583
+ offset = dateutil.tz.tzlocal().utcoffset(datetime(2011, 1, 1))
+ offset = int(offset.total_seconds() * 1000000000)
+
+ dti = date_range(start="2001-01-01", end="2001-03-01", unit="ns")
+ dti2 = dti.tz_localize(dateutil.tz.tzlocal())
+ tm.assert_numpy_array_equal(dti2.asi8 + offset, dti.asi8)
+
+ dti = date_range(
+ start="2001-01-01", end="2001-03-01", tz=dateutil.tz.tzlocal(), unit="ns"
+ )
+ dti2 = dti.tz_localize(None)
+ tm.assert_numpy_array_equal(dti2.asi8 - offset, dti.asi8)
+
+ def test_dti_tz_localize_ambiguous_nat(self, tz):
+ times = [
+ "11/06/2011 00:00",
+ "11/06/2011 01:00",
+ "11/06/2011 01:00",
+ "11/06/2011 02:00",
+ "11/06/2011 03:00",
+ ]
+ di = DatetimeIndex(times)
+ localized = di.tz_localize(tz, ambiguous="NaT")
+
+ times = [
+ "11/06/2011 00:00",
+ np.nan,
+ np.nan,
+ "11/06/2011 02:00",
+ "11/06/2011 03:00",
+ ]
+ di_test = DatetimeIndex(times, tz="US/Eastern")
+
+ # left dtype is datetime64[ns, US/Eastern]
+ # right is datetime64[ns, tzfile('/usr/share/zoneinfo/US/Eastern')]
+ tm.assert_numpy_array_equal(di_test.values, localized.values)
+
+ def test_dti_tz_localize_ambiguous_flags(self, tz, unit):
+ # November 6, 2011, fall back, repeat 2 AM hour
+
+ # Pass in flags to determine right dst transition
+ dr = date_range(
+ datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour(), tz=tz, unit=unit
+ )
+ times = [
+ "11/06/2011 00:00",
+ "11/06/2011 01:00",
+ "11/06/2011 01:00",
+ "11/06/2011 02:00",
+ "11/06/2011 03:00",
+ ]
+
+ # Test tz_localize
+ di = DatetimeIndex(times).as_unit(unit)
+ is_dst = [1, 1, 0, 0, 0]
+ localized = di.tz_localize(tz, ambiguous=is_dst)
+ expected = dr._with_freq(None)
+ tm.assert_index_equal(expected, localized)
+
+ result = DatetimeIndex(times, tz=tz, ambiguous=is_dst).as_unit(unit)
+ tm.assert_index_equal(result, expected)
+
+ localized = di.tz_localize(tz, ambiguous=np.array(is_dst))
+ tm.assert_index_equal(dr, localized)
+
+ localized = di.tz_localize(tz, ambiguous=np.array(is_dst).astype("bool"))
+ tm.assert_index_equal(dr, localized)
+
+ # Test constructor
+ localized = DatetimeIndex(times, tz=tz, ambiguous=is_dst).as_unit(unit)
+ tm.assert_index_equal(dr, localized)
+
+ # Test duplicate times where inferring the dst fails
+ times += times
+ di = DatetimeIndex(times).as_unit(unit)
+
+ # When the sizes are incompatible, make sure error is raised
+ msg = "Length of ambiguous bool-array must be the same size as vals"
+ with pytest.raises(Exception, match=msg):
+ di.tz_localize(tz, ambiguous=is_dst)
+
+ # When sizes are compatible and there are repeats ('infer' won't work)
+ is_dst = np.hstack((is_dst, is_dst))
+ localized = di.tz_localize(tz, ambiguous=is_dst)
+ dr = dr.append(dr)
+ tm.assert_index_equal(dr, localized)
+
+ def test_dti_tz_localize_ambiguous_flags2(self, tz):
+ # When there is no dst transition, nothing special happens
+ dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=offsets.Hour())
+ is_dst = np.array([1] * 10)
+ localized = dr.tz_localize(tz)
+ localized_is_dst = dr.tz_localize(tz, ambiguous=is_dst)
+ tm.assert_index_equal(localized, localized_is_dst)
+
+ def test_dti_tz_localize_bdate_range(self):
+ dr = bdate_range("1/1/2009", "1/1/2010")
+ dr_utc = bdate_range("1/1/2009", "1/1/2010", tz=timezone.utc)
+ localized = dr.tz_localize(timezone.utc)
+ tm.assert_index_equal(dr_utc, localized)
+
+ @pytest.mark.parametrize(
+ "start_ts, tz, end_ts, shift",
+ [
+ ["2015-03-29 02:20:00", "Europe/Warsaw", "2015-03-29 03:00:00", "forward"],
+ [
+ "2015-03-29 02:20:00",
+ "Europe/Warsaw",
+ "2015-03-29 01:59:59.999999999",
+ "backward",
+ ],
+ [
+ "2015-03-29 02:20:00",
+ "Europe/Warsaw",
+ "2015-03-29 03:20:00",
+ timedelta(hours=1),
+ ],
+ [
+ "2015-03-29 02:20:00",
+ "Europe/Warsaw",
+ "2015-03-29 01:20:00",
+ timedelta(hours=-1),
+ ],
+ ["2018-03-11 02:33:00", "US/Pacific", "2018-03-11 03:00:00", "forward"],
+ [
+ "2018-03-11 02:33:00",
+ "US/Pacific",
+ "2018-03-11 01:59:59.999999999",
+ "backward",
+ ],
+ [
+ "2018-03-11 02:33:00",
+ "US/Pacific",
+ "2018-03-11 03:33:00",
+ timedelta(hours=1),
+ ],
+ [
+ "2018-03-11 02:33:00",
+ "US/Pacific",
+ "2018-03-11 01:33:00",
+ timedelta(hours=-1),
+ ],
+ ],
+ )
+ @pytest.mark.parametrize("tz_type", ["", "dateutil/"])
+ def test_dti_tz_localize_nonexistent_shift(
+ self, start_ts, tz, end_ts, shift, tz_type, unit
+ ):
+ # GH#8917
+ tz = tz_type + tz
+ if isinstance(shift, str):
+ shift = "shift_" + shift
+ dti = DatetimeIndex([Timestamp(start_ts)]).as_unit(unit)
+ result = dti.tz_localize(tz, nonexistent=shift)
+ expected = DatetimeIndex([Timestamp(end_ts)]).tz_localize(tz).as_unit(unit)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("offset", [-1, 1])
+ def test_dti_tz_localize_nonexistent_shift_invalid(self, offset, warsaw):
+ # GH#8917
+ tz = warsaw
+ dti = DatetimeIndex([Timestamp("2015-03-29 02:20:00")])
+ msg = "The provided timedelta will relocalize on a nonexistent time"
+ with pytest.raises(ValueError, match=msg):
+ dti.tz_localize(tz, nonexistent=timedelta(seconds=offset))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_unique.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_unique.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c419b23c749a16e66458b334b3aec34521c2241
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/methods/test_unique.py
@@ -0,0 +1,77 @@
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+from pandas import (
+ DatetimeIndex,
+ NaT,
+ Timestamp,
+)
+import pandas._testing as tm
+
+
+def test_unique(tz_naive_fixture):
+ idx = DatetimeIndex(["2017"] * 2, tz=tz_naive_fixture)
+ expected = idx[:1]
+
+ result = idx.unique()
+ tm.assert_index_equal(result, expected)
+ # GH#21737
+ # Ensure the underlying data is consistent
+ assert result[0] == expected[0]
+
+
+def test_index_unique(rand_series_with_duplicate_datetimeindex):
+ dups = rand_series_with_duplicate_datetimeindex
+ index = dups.index
+
+ uniques = index.unique()
+ expected = DatetimeIndex(
+ [
+ datetime(2000, 1, 2),
+ datetime(2000, 1, 3),
+ datetime(2000, 1, 4),
+ datetime(2000, 1, 5),
+ ],
+ dtype=index.dtype,
+ )
+ assert uniques.dtype == index.dtype # sanity
+ tm.assert_index_equal(uniques, expected)
+ assert index.nunique() == 4
+
+ # GH#2563
+ assert isinstance(uniques, DatetimeIndex)
+
+ dups_local = index.tz_localize("US/Eastern")
+ dups_local.name = "foo"
+ result = dups_local.unique()
+ expected = DatetimeIndex(expected, name="foo")
+ expected = expected.tz_localize("US/Eastern")
+ assert result.tz is not None
+ assert result.name == "foo"
+ tm.assert_index_equal(result, expected)
+
+
+def test_index_unique2():
+ # NaT, note this is excluded
+ arr = [1370745748 + t for t in range(20)] + [NaT._value]
+ idx = DatetimeIndex(arr * 3)
+ tm.assert_index_equal(idx.unique(), DatetimeIndex(arr))
+ assert idx.nunique() == 20
+ assert idx.nunique(dropna=False) == 21
+
+
+def test_index_unique3():
+ arr = [
+ Timestamp("2013-06-09 02:42:28") + timedelta(seconds=t) for t in range(20)
+ ] + [NaT]
+ idx = DatetimeIndex(arr * 3)
+ tm.assert_index_equal(idx.unique(), DatetimeIndex(arr))
+ assert idx.nunique() == 20
+ assert idx.nunique(dropna=False) == 21
+
+
+def test_is_unique_monotonic(rand_series_with_duplicate_datetimeindex):
+ index = rand_series_with_duplicate_datetimeindex.index
+ assert not index.is_unique
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_arithmetic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_arithmetic.py
new file mode 100644
index 0000000000000000000000000000000000000000..82ecb84220783300d453cdce428c3ded89a87a8c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_arithmetic.py
@@ -0,0 +1,80 @@
+# Arithmetic tests specific to DatetimeIndex are generally about `freq`
+# retention or inference. Other arithmetic tests belong in
+# tests/arithmetic/test_datetime64.py
+
+from pandas import (
+ Timedelta,
+ TimedeltaIndex,
+ Timestamp,
+ date_range,
+ offsets,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeIndexArithmetic:
+ def test_add_timedelta_preserves_freq(self):
+ # GH#37295 should hold for any DTI with freq=None or Tick freq
+ # GH#41943 as of pandas3 Tick does not include Day
+ tz = "Canada/Eastern"
+ dti = date_range(
+ start=Timestamp("2019-03-26 00:00:00-0400", tz=tz),
+ end=Timestamp("2020-10-17 00:00:00-0400", tz=tz),
+ freq="12h",
+ )
+ result = dti + Timedelta(days=1)
+ assert result.freq == dti.freq
+
+ def test_sub_datetime_preserves_freq(self, tz_naive_fixture):
+ # GH#48818
+ # In pandas3 "D" preserves time-of-day across DST transitions, so
+ # is not preserved by subtraction. Ticks offsets like "24h"
+ # are still preserved
+ dti = date_range(
+ "2016-01-01",
+ periods=12,
+ tz=tz_naive_fixture,
+ freq=offsets.Hour(24),
+ )
+
+ res = dti - dti[0]
+ expected = timedelta_range("0 Days", "11 Days", freq=offsets.Hour(24))
+ tm.assert_index_equal(res, expected)
+ assert res.freq == expected.freq
+
+ def test_sub_datetime_preserves_freq_across_dst(self):
+ # GH#48818
+ ts = Timestamp("2016-03-11", tz="US/Pacific")
+ dti = date_range(ts, periods=4, unit="ns")
+
+ res = dti - dti[0]
+ expected = TimedeltaIndex(
+ [
+ Timedelta(days=0),
+ Timedelta(days=1),
+ Timedelta(days=2),
+ Timedelta(days=2, hours=23),
+ ],
+ dtype="m8[ns]",
+ )
+ tm.assert_index_equal(res, expected)
+ assert res.freq == expected.freq
+
+ def test_add_dti_day(self):
+ # GH#35388
+ dti = date_range("2020-03-28", periods=4, freq="D", tz="Europe/Berlin")
+ result = (dti + dti.freq)[:-1]
+ expected = dti[1:]
+ tm.assert_index_equal(result, expected)
+
+ def test_sub_timestamp_preserves_day_freq(self):
+ # GH#62094
+ dti = date_range("2021-01-01", periods=5, freq="D")
+ ts = Timestamp("2020-01-01")
+
+ result = dti - ts
+
+ # The one crucial assertion:
+ assert isinstance(result, TimedeltaIndex)
+ assert result.freq == dti.freq
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..06ef4c057b5cb238304be723a7ed0f2a699b8b02
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_constructors.py
@@ -0,0 +1,1207 @@
+from __future__ import annotations
+
+from datetime import (
+ datetime,
+ timedelta,
+ timezone,
+)
+from functools import partial
+from operator import attrgetter
+import zoneinfo
+
+import dateutil
+import dateutil.tz
+from dateutil.tz import gettz
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import (
+ astype_overflowsafe,
+ timezones,
+)
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Index,
+ Timestamp,
+ date_range,
+ offsets,
+ to_datetime,
+)
+import pandas._testing as tm
+from pandas.core.arrays import period_array
+
+
+class TestDatetimeIndex:
+ def test_from_dt64_unsupported_unit(self):
+ # GH#49292
+ val = np.datetime64(1, "D")
+ result = DatetimeIndex([val], tz="US/Pacific")
+
+ expected = DatetimeIndex([val.astype("M8[s]")], tz="US/Pacific")
+ tm.assert_index_equal(result, expected)
+
+ def test_explicit_tz_none(self):
+ # GH#48659
+ dti = date_range("2016-01-01", periods=10, tz="UTC", unit="ns")
+
+ msg = "Passed data is timezone-aware, incompatible with 'tz=None'"
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(dti, tz=None)
+
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(np.array(dti), tz=None)
+
+ msg = "Cannot pass both a timezone-aware dtype and tz=None"
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex([], dtype="M8[ns, UTC]", tz=None)
+
+ def test_freq_validation_with_nat(self):
+ # GH#11587 make sure we get a useful error message when generate_range
+ # raises
+ msg = (
+ "Inferred frequency None from passed values does not conform "
+ "to passed frequency D"
+ )
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex([pd.NaT, Timestamp("2011-01-01")], freq="D")
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex([pd.NaT, Timestamp("2011-01-01")._value], freq="D")
+
+ # TODO: better place for tests shared by DTI/TDI?
+ @pytest.mark.parametrize(
+ "index",
+ [
+ date_range("2016-01-01", periods=5, tz="US/Pacific", unit="ns"),
+ pd.timedelta_range("1 Day", periods=5),
+ ],
+ )
+ def test_shallow_copy_inherits_array_freq(self, index):
+ # If we pass a DTA/TDA to shallow_copy and dont specify a freq,
+ # we should inherit the array's freq, not our own.
+ array = index._data
+
+ arr = array[[0, 3, 2, 4, 1]]
+ assert arr.freq is None
+
+ result = index._shallow_copy(arr)
+ assert result.freq is None
+
+ def test_categorical_preserves_tz(self):
+ # GH#18664 retain tz when going DTI-->Categorical-->DTI
+ dti = DatetimeIndex(
+ [pd.NaT, "2015-01-01", "1999-04-06 15:14:13", "2015-01-01"], tz="US/Eastern"
+ )
+
+ for dtobj in [dti, dti._data]:
+ # works for DatetimeIndex or DatetimeArray
+
+ ci = pd.CategoricalIndex(dtobj)
+ carr = pd.Categorical(dtobj)
+ cser = pd.Series(ci)
+
+ for obj in [ci, carr, cser]:
+ result = DatetimeIndex(obj)
+ tm.assert_index_equal(result, dti)
+
+ def test_dti_with_period_data_raises(self):
+ # GH#23675
+ data = pd.PeriodIndex(["2016Q1", "2016Q2"], freq="Q")
+
+ with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
+ DatetimeIndex(data)
+
+ with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
+ to_datetime(data)
+
+ with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
+ DatetimeIndex(period_array(data))
+
+ with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
+ to_datetime(period_array(data))
+
+ def test_dti_with_timedelta64_data_raises(self):
+ # GH#23675 deprecated, enforced in GH#29794
+ data = np.array([0], dtype="m8[ns]")
+ msg = r"timedelta64\[ns\] cannot be converted to datetime64"
+ with pytest.raises(TypeError, match=msg):
+ DatetimeIndex(data)
+
+ with pytest.raises(TypeError, match=msg):
+ to_datetime(data)
+
+ with pytest.raises(TypeError, match=msg):
+ DatetimeIndex(pd.TimedeltaIndex(data))
+
+ with pytest.raises(TypeError, match=msg):
+ to_datetime(pd.TimedeltaIndex(data))
+
+ def test_constructor_from_sparse_array(self):
+ # https://github.com/pandas-dev/pandas/issues/35843
+ values = [
+ Timestamp("2012-05-01T01:00:00.000000"),
+ Timestamp("2016-05-01T01:00:00.000000"),
+ ]
+ arr = pd.arrays.SparseArray(values)
+ result = Index(arr)
+ assert type(result) is Index
+ assert result.dtype == arr.dtype
+
+ def test_construction_caching(self):
+ df = pd.DataFrame(
+ {
+ "dt": date_range("20130101", periods=3, unit="ns"),
+ "dttz": date_range(
+ "20130101", periods=3, tz=zoneinfo.ZoneInfo("US/Eastern"), unit="ns"
+ ),
+ "dt_with_null": [
+ Timestamp("20130101"),
+ pd.NaT,
+ Timestamp("20130103"),
+ ],
+ "dtns": date_range("20130101", periods=3, freq="ns", unit="ns"),
+ }
+ )
+ assert df.dttz.dtype.tz.key == "US/Eastern"
+
+ @pytest.mark.parametrize(
+ "kwargs",
+ [{"tz": "dtype.tz"}, {"dtype": "dtype"}, {"dtype": "dtype", "tz": "dtype.tz"}],
+ )
+ def test_construction_with_alt(self, kwargs, tz_aware_fixture):
+ tz = tz_aware_fixture
+ i = date_range("20130101", periods=5, freq="h", tz=tz, unit="ns")
+ kwargs = {key: attrgetter(val)(i) for key, val in kwargs.items()}
+ result = DatetimeIndex(i, **kwargs)
+ tm.assert_index_equal(i, result)
+
+ @pytest.mark.parametrize(
+ "kwargs",
+ [{"tz": "dtype.tz"}, {"dtype": "dtype"}, {"dtype": "dtype", "tz": "dtype.tz"}],
+ )
+ def test_construction_with_alt_tz_localize(self, kwargs, tz_aware_fixture):
+ tz = tz_aware_fixture
+ i = date_range("20130101", periods=5, freq="h", tz=tz, unit="ns")
+ i = i._with_freq(None)
+ kwargs = {key: attrgetter(val)(i) for key, val in kwargs.items()}
+
+ if "tz" in kwargs:
+ result = DatetimeIndex(i.asi8, tz="UTC").tz_convert(kwargs["tz"])
+
+ expected = DatetimeIndex(i, **kwargs)
+ tm.assert_index_equal(result, expected)
+
+ # localize into the provided tz
+ i2 = DatetimeIndex(i.tz_localize(None).asi8, tz="UTC")
+ expected = i.tz_localize(None).tz_localize("UTC")
+ tm.assert_index_equal(i2, expected)
+
+ # incompat tz/dtype
+ msg = "cannot supply both a tz and a dtype with a tz"
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(
+ i.tz_localize(None).asi8,
+ dtype=i.dtype,
+ tz=zoneinfo.ZoneInfo("US/Hawaii"),
+ )
+
+ def test_construction_index_with_mixed_timezones(self):
+ # gh-11488: no tz results in DatetimeIndex
+ result = Index([Timestamp("2011-01-01"), Timestamp("2011-01-02")], name="idx")
+ exp = DatetimeIndex(
+ [Timestamp("2011-01-01"), Timestamp("2011-01-02")], name="idx"
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is None
+
+ # same tz results in DatetimeIndex
+ result = Index(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ Timestamp("2011-01-02 10:00", tz="Asia/Tokyo"),
+ ],
+ name="idx",
+ )
+ exp = DatetimeIndex(
+ [Timestamp("2011-01-01 10:00"), Timestamp("2011-01-02 10:00")],
+ tz="Asia/Tokyo",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is not None
+ assert result.tz == exp.tz
+
+ # same tz results in DatetimeIndex (DST)
+ result = Index(
+ [
+ Timestamp("2011-01-01 10:00", tz="US/Eastern"),
+ Timestamp("2011-08-01 10:00", tz="US/Eastern"),
+ ],
+ name="idx",
+ )
+ exp = DatetimeIndex(
+ [Timestamp("2011-01-01 10:00"), Timestamp("2011-08-01 10:00")],
+ tz="US/Eastern",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is not None
+ assert result.tz == exp.tz
+
+ # Different tz results in Index(dtype=object)
+ result = Index(
+ [
+ Timestamp("2011-01-01 10:00"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ name="idx",
+ )
+ exp = Index(
+ [
+ Timestamp("2011-01-01 10:00"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ dtype="object",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert not isinstance(result, DatetimeIndex)
+
+ result = Index(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ name="idx",
+ )
+ exp = Index(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ dtype="object",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert not isinstance(result, DatetimeIndex)
+
+ msg = "Mixed timezones detected. Pass utc=True in to_datetime"
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(["2013-11-02 22:00-05:00", "2013-11-03 22:00-06:00"])
+
+ # length = 1
+ result = Index([Timestamp("2011-01-01")], name="idx")
+ exp = DatetimeIndex([Timestamp("2011-01-01")], name="idx")
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is None
+
+ # length = 1 with tz
+ result = Index([Timestamp("2011-01-01 10:00", tz="Asia/Tokyo")], name="idx")
+ exp = DatetimeIndex(
+ [Timestamp("2011-01-01 10:00")], tz="Asia/Tokyo", name="idx"
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is not None
+ assert result.tz == exp.tz
+
+ def test_construction_index_with_mixed_timezones_with_NaT(self):
+ # see gh-11488
+ result = Index(
+ [pd.NaT, Timestamp("2011-01-01"), pd.NaT, Timestamp("2011-01-02")],
+ name="idx",
+ )
+ exp = DatetimeIndex(
+ [pd.NaT, Timestamp("2011-01-01"), pd.NaT, Timestamp("2011-01-02")],
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is None
+
+ # Same tz results in DatetimeIndex
+ result = Index(
+ [
+ pd.NaT,
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ pd.NaT,
+ Timestamp("2011-01-02 10:00", tz="Asia/Tokyo"),
+ ],
+ name="idx",
+ )
+ exp = DatetimeIndex(
+ [
+ pd.NaT,
+ Timestamp("2011-01-01 10:00"),
+ pd.NaT,
+ Timestamp("2011-01-02 10:00"),
+ ],
+ tz="Asia/Tokyo",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is not None
+ assert result.tz == exp.tz
+
+ # same tz results in DatetimeIndex (DST)
+ result = Index(
+ [
+ Timestamp("2011-01-01 10:00", tz="US/Eastern"),
+ pd.NaT,
+ Timestamp("2011-08-01 10:00", tz="US/Eastern"),
+ ],
+ name="idx",
+ )
+ exp = DatetimeIndex(
+ [Timestamp("2011-01-01 10:00"), pd.NaT, Timestamp("2011-08-01 10:00")],
+ tz="US/Eastern",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is not None
+ assert result.tz == exp.tz
+
+ # different tz results in Index(dtype=object)
+ result = Index(
+ [
+ pd.NaT,
+ Timestamp("2011-01-01 10:00"),
+ pd.NaT,
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ name="idx",
+ )
+ exp = Index(
+ [
+ pd.NaT,
+ Timestamp("2011-01-01 10:00"),
+ pd.NaT,
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ dtype="object",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert not isinstance(result, DatetimeIndex)
+
+ result = Index(
+ [
+ pd.NaT,
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ pd.NaT,
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ name="idx",
+ )
+ exp = Index(
+ [
+ pd.NaT,
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ pd.NaT,
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ dtype="object",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert not isinstance(result, DatetimeIndex)
+
+ # all NaT
+ result = Index([pd.NaT, pd.NaT], name="idx")
+ exp = DatetimeIndex([pd.NaT, pd.NaT], name="idx")
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is None
+
+ def test_construction_dti_with_mixed_timezones(self):
+ # GH 11488 (not changed, added explicit tests)
+
+ # no tz results in DatetimeIndex
+ result = DatetimeIndex(
+ [Timestamp("2011-01-01"), Timestamp("2011-01-02")], name="idx"
+ )
+ exp = DatetimeIndex(
+ [Timestamp("2011-01-01"), Timestamp("2011-01-02")], name="idx"
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+
+ # same tz results in DatetimeIndex
+ result = DatetimeIndex(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ Timestamp("2011-01-02 10:00", tz="Asia/Tokyo"),
+ ],
+ name="idx",
+ )
+ exp = DatetimeIndex(
+ [Timestamp("2011-01-01 10:00"), Timestamp("2011-01-02 10:00")],
+ tz="Asia/Tokyo",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+
+ # same tz results in DatetimeIndex (DST)
+ result = DatetimeIndex(
+ [
+ Timestamp("2011-01-01 10:00", tz="US/Eastern"),
+ Timestamp("2011-08-01 10:00", tz="US/Eastern"),
+ ],
+ name="idx",
+ )
+ exp = DatetimeIndex(
+ [Timestamp("2011-01-01 10:00"), Timestamp("2011-08-01 10:00")],
+ tz="US/Eastern",
+ name="idx",
+ )
+ tm.assert_index_equal(result, exp, exact=True)
+ assert isinstance(result, DatetimeIndex)
+
+ # tz mismatch affecting to tz-aware raises TypeError/ValueError
+
+ msg = "cannot be converted to datetime64"
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ name="idx",
+ )
+
+ # pre-2.0 this raised bc of awareness mismatch. in 2.0 with a tz#
+ # specified we behave as if this was called pointwise, so
+ # the naive Timestamp is treated as a wall time.
+ dti = DatetimeIndex(
+ [
+ Timestamp("2011-01-01 10:00"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ tz="Asia/Tokyo",
+ name="idx",
+ )
+ expected = DatetimeIndex(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern").tz_convert("Asia/Tokyo"),
+ ],
+ tz="Asia/Tokyo",
+ name="idx",
+ )
+ tm.assert_index_equal(dti, expected)
+
+ # pre-2.0 mixed-tz scalars raised even if a tz/dtype was specified.
+ # as of 2.0 we successfully return the requested tz/dtype
+ dti = DatetimeIndex(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ tz="US/Eastern",
+ name="idx",
+ )
+ expected = DatetimeIndex(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo").tz_convert("US/Eastern"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ tz="US/Eastern",
+ name="idx",
+ )
+ tm.assert_index_equal(dti, expected)
+
+ # same thing but pass dtype instead of tz
+ dti = DatetimeIndex(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ Timestamp("2011-01-02 10:00", tz="US/Eastern"),
+ ],
+ dtype="M8[us, US/Eastern]",
+ name="idx",
+ )
+ tm.assert_index_equal(dti, expected)
+
+ def test_construction_base_constructor(self):
+ arr = [Timestamp("2011-01-01"), pd.NaT, Timestamp("2011-01-03")]
+ tm.assert_index_equal(Index(arr), DatetimeIndex(arr))
+ tm.assert_index_equal(Index(np.array(arr)), DatetimeIndex(np.array(arr)))
+
+ arr = [np.nan, pd.NaT, Timestamp("2011-01-03")]
+ tm.assert_index_equal(Index(arr), DatetimeIndex(arr))
+ tm.assert_index_equal(Index(np.array(arr)), DatetimeIndex(np.array(arr)))
+
+ def test_construction_outofbounds(self):
+ # GH 13663
+ dates = [
+ datetime(3000, 1, 1),
+ datetime(4000, 1, 1),
+ datetime(5000, 1, 1),
+ datetime(6000, 1, 1),
+ ]
+ exp = Index(dates, dtype="M8[us]")
+ res = Index(dates)
+ tm.assert_index_equal(res, exp)
+
+ DatetimeIndex(dates)
+
+ @pytest.mark.parametrize("data", [["1400-01-01"], [datetime(1400, 1, 1)]])
+ def test_dti_date_out_of_range(self, data):
+ # GH#1475
+ DatetimeIndex(data)
+
+ def test_construction_with_ndarray(self):
+ # GH 5152
+ dates = [datetime(2013, 10, 7), datetime(2013, 10, 8), datetime(2013, 10, 9)]
+ data = DatetimeIndex(dates, freq=offsets.BDay()).values
+ result = DatetimeIndex(data, freq=offsets.BDay())
+ expected = DatetimeIndex(
+ ["2013-10-07", "2013-10-08", "2013-10-09"], dtype="M8[us]", freq="B"
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_integer_values_and_tz_interpreted_as_utc(self):
+ # GH-24559
+ val = np.datetime64("2000-01-01 00:00:00", "ns")
+ values = np.array([val.view("i8")])
+
+ result = DatetimeIndex(values).tz_localize("US/Central")
+
+ expected = DatetimeIndex(["2000-01-01T00:00:00"], dtype="M8[ns, US/Central]")
+ tm.assert_index_equal(result, expected)
+
+ # but UTC is *not* deprecated.
+ with tm.assert_produces_warning(None):
+ result = DatetimeIndex(values, tz="UTC")
+ expected = DatetimeIndex(["2000-01-01T00:00:00"], dtype="M8[ns, UTC]")
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_coverage(self):
+ msg = r"DatetimeIndex\(\.\.\.\) must be called with a collection"
+ with pytest.raises(TypeError, match=msg):
+ DatetimeIndex("1/1/2000")
+
+ # generator expression
+ gen = (datetime(2000, 1, 1) + timedelta(i) for i in range(10))
+ result = DatetimeIndex(gen)
+ expected = DatetimeIndex(
+ [datetime(2000, 1, 1) + timedelta(i) for i in range(10)]
+ )
+ tm.assert_index_equal(result, expected)
+
+ # NumPy string array
+ strings = np.array(["2000-01-01", "2000-01-02", "2000-01-03"])
+ result = DatetimeIndex(strings)
+ expected = DatetimeIndex(strings.astype("O"))
+ tm.assert_index_equal(result, expected)
+
+ from_ints = DatetimeIndex(expected.as_unit("ns").asi8).as_unit("us")
+ tm.assert_index_equal(from_ints, expected)
+
+ # string with NaT
+ strings = np.array(["2000-01-01", "2000-01-02", "NaT"])
+ result = DatetimeIndex(strings)
+ expected = DatetimeIndex(strings.astype("O"))
+ tm.assert_index_equal(result, expected)
+
+ from_ints = DatetimeIndex(expected.as_unit("ns").asi8).as_unit("us")
+ tm.assert_index_equal(from_ints, expected)
+
+ # non-conforming
+ msg = (
+ "Inferred frequency None from passed values does not conform "
+ "to passed frequency D"
+ )
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-04"], freq="D")
+
+ @pytest.mark.parametrize("freq", ["YS", "W-SUN"])
+ def test_constructor_datetime64_tzformat(self, freq):
+ # see GH#6572: ISO 8601 format results in stdlib timezone object
+ idx = date_range(
+ "2013-01-01T00:00:00-05:00", "2016-01-01T23:59:59-05:00", freq=freq
+ )
+ expected = date_range(
+ "2013-01-01T00:00:00",
+ "2016-01-01T23:59:59",
+ freq=freq,
+ tz=timezone(timedelta(minutes=-300)),
+ )
+ tm.assert_index_equal(idx, expected)
+ # Unable to use `US/Eastern` because of DST
+ expected_i8 = date_range(
+ "2013-01-01T00:00:00", "2016-01-01T23:59:59", freq=freq, tz="America/Lima"
+ )
+ tm.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)
+
+ idx = date_range(
+ "2013-01-01T00:00:00+09:00", "2016-01-01T23:59:59+09:00", freq=freq
+ )
+ expected = date_range(
+ "2013-01-01T00:00:00",
+ "2016-01-01T23:59:59",
+ freq=freq,
+ tz=timezone(timedelta(minutes=540)),
+ )
+ tm.assert_index_equal(idx, expected)
+ expected_i8 = date_range(
+ "2013-01-01T00:00:00", "2016-01-01T23:59:59", freq=freq, tz="Asia/Tokyo"
+ )
+ tm.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)
+
+ # Non ISO 8601 format results in dateutil.tz.tzoffset
+ idx = date_range("2013/1/1 0:00:00-5:00", "2016/1/1 23:59:59-5:00", freq=freq)
+ expected = date_range(
+ "2013-01-01T00:00:00",
+ "2016-01-01T23:59:59",
+ freq=freq,
+ tz=timezone(timedelta(minutes=-300)),
+ )
+ tm.assert_index_equal(idx, expected)
+ # Unable to use `US/Eastern` because of DST
+ expected_i8 = date_range(
+ "2013-01-01T00:00:00", "2016-01-01T23:59:59", freq=freq, tz="America/Lima"
+ )
+ tm.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)
+
+ idx = date_range("2013/1/1 0:00:00+9:00", "2016/1/1 23:59:59+09:00", freq=freq)
+ expected = date_range(
+ "2013-01-01T00:00:00",
+ "2016-01-01T23:59:59",
+ freq=freq,
+ tz=timezone(timedelta(minutes=540)),
+ )
+ tm.assert_index_equal(idx, expected)
+ expected_i8 = date_range(
+ "2013-01-01T00:00:00", "2016-01-01T23:59:59", freq=freq, tz="Asia/Tokyo"
+ )
+ tm.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)
+
+ def test_constructor_dtype(self):
+ # passing a dtype with a tz should localize
+ idx = DatetimeIndex(
+ ["2013-01-01", "2013-01-02"], dtype="datetime64[ns, US/Eastern]"
+ )
+ expected = (
+ DatetimeIndex(["2013-01-01", "2013-01-02"])
+ .as_unit("ns")
+ .tz_localize("US/Eastern")
+ )
+ tm.assert_index_equal(idx, expected)
+
+ idx = DatetimeIndex(["2013-01-01", "2013-01-02"], tz="US/Eastern").as_unit("ns")
+ tm.assert_index_equal(idx, expected)
+
+ def test_constructor_dtype_tz_mismatch_raises(self):
+ # if we already have a tz and its not the same, then raise
+ idx = DatetimeIndex(
+ ["2013-01-01", "2013-01-02"], dtype="datetime64[ns, US/Eastern]"
+ )
+
+ msg = (
+ "cannot supply both a tz and a timezone-naive dtype "
+ r"\(i\.e\. datetime64\[ns\]\)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(idx, dtype="datetime64[ns]")
+
+ # this is effectively trying to convert tz's
+ msg = "data is already tz-aware US/Eastern, unable to set specified tz: CET"
+ with pytest.raises(TypeError, match=msg):
+ DatetimeIndex(idx, dtype="datetime64[ns, CET]")
+ msg = "cannot supply both a tz and a dtype with a tz"
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(idx, tz="CET", dtype="datetime64[ns, US/Eastern]")
+
+ result = DatetimeIndex(idx, dtype="datetime64[ns, US/Eastern]")
+ tm.assert_index_equal(idx, result)
+
+ @pytest.mark.parametrize("dtype", [object, np.int32, np.int64])
+ def test_constructor_invalid_dtype_raises(self, dtype):
+ # GH 23986
+ msg = "Unexpected value for 'dtype'"
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex([1, 2], dtype=dtype)
+
+ def test_000constructor_resolution(self):
+ # 2252
+ t1 = Timestamp((1352934390 * 1000000000) + 1000000 + 1000 + 1)
+ idx = DatetimeIndex([t1])
+
+ assert idx.nanosecond[0] == t1.nanosecond
+
+ def test_disallow_setting_tz(self):
+ # GH 3746
+ dti = DatetimeIndex(["2010"], tz="UTC")
+ msg = "Cannot directly set timezone"
+ with pytest.raises(AttributeError, match=msg):
+ dti.tz = zoneinfo.ZoneInfo("US/Pacific")
+
+ @pytest.mark.parametrize(
+ "tz",
+ [
+ None,
+ "America/Los_Angeles",
+ zoneinfo.ZoneInfo("America/Los_Angeles"),
+ Timestamp("2000", tz="America/Los_Angeles").tz,
+ ],
+ )
+ def test_constructor_start_end_with_tz(self, tz):
+ # GH 18595
+ start = Timestamp("2013-01-01 06:00:00", tz="America/Los_Angeles")
+ end = Timestamp("2013-01-02 06:00:00", tz="America/Los_Angeles")
+ result = date_range(freq="D", start=start, end=end, tz=tz, unit="ns")
+ expected = DatetimeIndex(
+ ["2013-01-01 06:00:00", "2013-01-02 06:00:00"],
+ dtype="M8[ns, America/Los_Angeles]",
+ freq="D",
+ )
+ tm.assert_index_equal(result, expected)
+ # Especially assert that the timezone is consistent for zoneinfo
+ assert zoneinfo.ZoneInfo("America/Los_Angeles") is result.tz
+
+ @pytest.mark.parametrize("tz", ["US/Pacific", "US/Eastern", "Asia/Tokyo"])
+ def test_constructor_with_non_normalized_pytz(self, tz):
+ # GH 18595
+ pytz = pytest.importorskip("pytz")
+ tz_in = pytz.timezone(tz)
+ non_norm_tz = Timestamp("2010", tz=tz_in).tz
+ result = DatetimeIndex(["2010"], tz=non_norm_tz)
+ assert pytz.timezone(tz) is result.tz
+
+ def test_constructor_timestamp_near_dst(self):
+ # GH 20854
+ ts = [
+ Timestamp("2016-10-30 03:00:00+0300", tz="Europe/Helsinki"),
+ Timestamp("2016-10-30 03:00:00+0200", tz="Europe/Helsinki"),
+ ]
+ result = DatetimeIndex(ts).as_unit("ns")
+ expected = DatetimeIndex(
+ [ts[0].to_pydatetime(), ts[1].to_pydatetime()]
+ ).as_unit("ns")
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("klass", [Index, DatetimeIndex])
+ @pytest.mark.parametrize("box", [np.array, partial(np.array, dtype=object), list])
+ @pytest.mark.parametrize(
+ "tz, dtype",
+ [("US/Pacific", "datetime64[ns, US/Pacific]"), (None, "datetime64[ns]")],
+ )
+ def test_constructor_with_int_tz(self, klass, box, tz, dtype):
+ # GH 20997, 20964
+ ts = Timestamp("2018-01-01", tz=tz).as_unit("ns")
+ result = klass(box([ts._value]), dtype=dtype)
+ expected = klass([ts])
+ assert result == expected
+
+ def test_construction_int_rountrip(self, tz_naive_fixture):
+ # GH 12619, GH#24559
+ tz = tz_naive_fixture
+
+ result = 1293858000000000000
+ expected = DatetimeIndex([result], tz=tz).asi8[0]
+ assert result == expected
+
+ def test_construction_from_replaced_timestamps_with_dst(self):
+ # GH 18785
+ index = date_range(
+ Timestamp(2000, 12, 31),
+ Timestamp(2005, 12, 31),
+ freq="YE-DEC",
+ tz="Australia/Melbourne",
+ unit="ns",
+ )
+ result = DatetimeIndex([x.replace(month=6, day=1) for x in index])
+ expected = DatetimeIndex(
+ [
+ "2000-06-01 00:00:00",
+ "2001-06-01 00:00:00",
+ "2002-06-01 00:00:00",
+ "2003-06-01 00:00:00",
+ "2004-06-01 00:00:00",
+ "2005-06-01 00:00:00",
+ ],
+ tz="Australia/Melbourne",
+ ).as_unit("ns")
+ tm.assert_index_equal(result, expected)
+
+ def test_construction_with_tz_and_tz_aware_dti(self):
+ # GH 23579
+ dti = date_range("2016-01-01", periods=3, tz="US/Central")
+ msg = "data is already tz-aware US/Central, unable to set specified tz"
+ with pytest.raises(TypeError, match=msg):
+ DatetimeIndex(dti, tz="Asia/Tokyo")
+
+ def test_construction_with_nat_and_tzlocal(self):
+ tz = dateutil.tz.tzlocal()
+ result = DatetimeIndex(["2018", "NaT"], tz=tz).as_unit("ns")
+ expected = DatetimeIndex([Timestamp("2018", tz=tz), pd.NaT]).as_unit("ns")
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_with_ambiguous_keyword_arg(self):
+ # GH 35297
+
+ expected = DatetimeIndex(
+ ["2020-11-01 01:00:00", "2020-11-02 01:00:00"],
+ dtype="datetime64[ns, America/New_York]",
+ freq="D",
+ ambiguous=False,
+ )
+
+ # ambiguous keyword in start
+ timezone = "America/New_York"
+ start = Timestamp(year=2020, month=11, day=1, hour=1).tz_localize(
+ timezone, ambiguous=False
+ )
+ result = date_range(start=start, periods=2, ambiguous=False, unit="ns")
+ tm.assert_index_equal(result, expected)
+
+ # ambiguous keyword in end
+ timezone = "America/New_York"
+ end = Timestamp(year=2020, month=11, day=2, hour=1).tz_localize(
+ timezone, ambiguous=False
+ )
+ result = date_range(end=end, periods=2, ambiguous=False, unit="ns")
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_with_nonexistent_keyword_arg(self, warsaw):
+ # GH 35297
+ timezone = warsaw
+
+ # nonexistent keyword in start
+ start = Timestamp("2015-03-29 02:30:00").tz_localize(
+ timezone, nonexistent="shift_forward"
+ )
+ result = date_range(start=start, periods=2, freq="h", unit="ns")
+ expected = DatetimeIndex(
+ [
+ Timestamp("2015-03-29 03:00:00+02:00", tz=timezone),
+ Timestamp("2015-03-29 04:00:00+02:00", tz=timezone),
+ ]
+ ).as_unit("ns")
+
+ tm.assert_index_equal(result, expected)
+
+ # nonexistent keyword in end
+ end = start
+ result = date_range(end=end, periods=2, freq="h", unit="ns")
+ expected = DatetimeIndex(
+ [
+ Timestamp("2015-03-29 01:00:00+01:00", tz=timezone),
+ Timestamp("2015-03-29 03:00:00+02:00", tz=timezone),
+ ]
+ ).as_unit("ns")
+
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_no_precision_raises(self):
+ # GH-24753, GH-24739
+
+ msg = "with no precision is not allowed"
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(["2000"], dtype="datetime64")
+
+ msg = "The 'datetime64' dtype has no unit. Please pass in"
+ with pytest.raises(ValueError, match=msg):
+ Index(["2000"], dtype="datetime64")
+
+ def test_constructor_wrong_precision_raises(self):
+ dti = DatetimeIndex(["2000"], dtype="datetime64[us]")
+ assert dti.dtype == "M8[us]"
+ assert dti[0] == Timestamp(2000, 1, 1)
+
+ def test_index_constructor_with_numpy_object_array_and_timestamp_tz_with_nan(self):
+ # GH 27011
+ result = Index(np.array([Timestamp("2019", tz="UTC"), np.nan], dtype=object))
+ expected = DatetimeIndex([Timestamp("2019", tz="UTC"), pd.NaT])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "tz", [zoneinfo.ZoneInfo("US/Eastern"), gettz("US/Eastern")]
+ )
+ def test_dti_from_tzaware_datetime(self, tz):
+ d = [datetime(2012, 8, 19, tzinfo=tz)]
+
+ index = DatetimeIndex(d)
+ assert timezones.tz_compare(index.tz, tz)
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_dti_tz_constructors(self, tzstr):
+ """Test different DatetimeIndex constructions with timezone
+ Follow-up of GH#4229
+ """
+ arr = ["11/10/2005 08:00:00", "11/10/2005 09:00:00"]
+
+ idx1 = to_datetime(arr).tz_localize(tzstr)
+ idx2 = date_range(
+ start="2005-11-10 08:00:00", freq="h", periods=2, tz=tzstr, unit="us"
+ )
+ idx2 = idx2._with_freq(None) # the others all have freq=None
+ idx3 = DatetimeIndex(arr, tz=tzstr)
+ idx4 = DatetimeIndex(np.array(arr), tz=tzstr)
+
+ tm.assert_index_equal(idx1, idx2)
+ tm.assert_index_equal(idx1, idx3)
+ tm.assert_index_equal(idx1, idx4)
+
+ def test_dti_construction_idempotent(self, unit):
+ rng = date_range(
+ "03/12/2012 00:00", periods=10, freq="W-FRI", tz="US/Eastern", unit=unit
+ )
+ rng2 = DatetimeIndex(data=rng, tz="US/Eastern")
+ tm.assert_index_equal(rng, rng2)
+
+ @pytest.mark.parametrize("prefix", ["", "dateutil/"])
+ def test_dti_constructor_static_tzinfo(self, prefix):
+ # it works!
+ index = DatetimeIndex([datetime(2012, 1, 1)], tz=prefix + "EST")
+ index.hour
+ index[0]
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_dti_convert_datetime_list(self, tzstr):
+ dr = date_range("2012-06-02", periods=10, tz=tzstr, name="foo")
+ dr2 = DatetimeIndex(list(dr), name="foo", freq="D")
+ tm.assert_index_equal(dr, dr2)
+
+ @pytest.mark.parametrize(
+ "tz",
+ [
+ "pytz/US/Eastern",
+ gettz("US/Eastern"),
+ ],
+ )
+ @pytest.mark.parametrize("use_str", [True, False])
+ @pytest.mark.parametrize("box_cls", [Timestamp, DatetimeIndex])
+ def test_dti_ambiguous_matches_timestamp(self, tz, use_str, box_cls, request):
+ # GH#47471 check that we get the same raising behavior in the DTI
+ # constructor and Timestamp constructor
+ if isinstance(tz, str) and tz.startswith("pytz/"):
+ pytz = pytest.importorskip("pytz")
+ tz = pytz.timezone(tz.removeprefix("pytz/"))
+ dtstr = "2013-11-03 01:59:59.999999"
+ item = dtstr
+ if not use_str:
+ item = Timestamp(dtstr).to_pydatetime()
+ if box_cls is not Timestamp:
+ item = [item]
+
+ if not use_str and isinstance(tz, dateutil.tz.tzfile):
+ # FIXME: The Timestamp constructor here behaves differently than all
+ # the other cases bc with dateutil/zoneinfo tzinfos we implicitly
+ # get fold=0. Having this raise is not important, but having the
+ # behavior be consistent across cases is.
+ mark = pytest.mark.xfail(reason="We implicitly get fold=0.")
+ request.applymarker(mark)
+
+ with pytest.raises(ValueError, match=dtstr):
+ box_cls(item, tz=tz)
+
+ @pytest.mark.parametrize("tz", [None, "UTC", "US/Pacific"])
+ def test_dti_constructor_with_non_nano_dtype(self, tz):
+ # GH#55756, GH#54620
+ ts = Timestamp("2999-01-01")
+ dtype = "M8[us]"
+ if tz is not None:
+ dtype = f"M8[us, {tz}]"
+ vals = [ts, "2999-01-02 03:04:05.678910", 2500]
+ result = DatetimeIndex(vals, dtype=dtype)
+ # The 2500 is interpreted as microseconds, consistent with what
+ # we would get if we created DatetimeIndexes from vals[:2] and vals[2:]
+ # and concated the results.
+ pointwise = [
+ vals[0].tz_localize(tz),
+ Timestamp(vals[1], tz=tz),
+ to_datetime(vals[2], unit="us", utc=True).tz_convert(tz),
+ ]
+ exp_vals = [x.as_unit("us").asm8 for x in pointwise]
+ exp_arr = np.array(exp_vals, dtype="M8[us]")
+ expected = DatetimeIndex(exp_arr, dtype="M8[us]")
+ if tz is not None:
+ expected = expected.tz_localize("UTC").tz_convert(tz)
+ tm.assert_index_equal(result, expected)
+
+ result2 = DatetimeIndex(np.array(vals, dtype=object), dtype=dtype)
+ tm.assert_index_equal(result2, expected)
+
+ def test_dti_constructor_with_non_nano_now_today(self, request):
+ # GH#55756
+ now = Timestamp.now()
+ today = Timestamp.today()
+ result = DatetimeIndex(["now", "today"], dtype="M8[s]")
+ assert result.dtype == "M8[s]"
+
+ diff0 = result[0] - now.as_unit("s")
+ diff1 = result[1] - today.as_unit("s")
+ assert diff1 >= pd.Timedelta(0), f"The difference is {diff0}"
+ assert diff0 >= pd.Timedelta(0), f"The difference is {diff0}"
+
+ # result may not exactly match [now, today] so we'll test it up to a tolerance.
+ # (it *may* match exactly due to rounding)
+ # GH 57535
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="result may not exactly match [now, today]", strict=False
+ )
+ )
+ tolerance = pd.Timedelta(seconds=1)
+ assert diff0 < tolerance, f"The difference is {diff0}"
+ assert diff1 < tolerance, f"The difference is {diff0}"
+
+ def test_dti_constructor_object_float_matches_float_dtype(self):
+ # GH#55780
+ arr = np.array([0, np.nan], dtype=np.float64)
+ arr2 = arr.astype(object)
+
+ dti1 = DatetimeIndex(arr, tz="CET")
+ dti2 = DatetimeIndex(arr2, tz="CET")
+ tm.assert_index_equal(dti1, dti2)
+
+ @pytest.mark.parametrize("dtype", ["M8[us]", "M8[us, US/Pacific]"])
+ def test_dti_constructor_with_dtype_object_int_matches_int_dtype(self, dtype):
+ # Going through the object path should match the non-object path
+
+ vals1 = np.arange(5, dtype="i8") * 1000
+ vals1[0] = pd.NaT.value
+
+ vals2 = vals1.astype(np.float64)
+ vals2[0] = np.nan
+
+ vals3 = vals1.astype(object)
+ # change lib.infer_dtype(vals3) from "integer" so we go through
+ # array_to_datetime in _sequence_to_dt64
+ vals3[0] = pd.NaT
+
+ vals4 = vals2.astype(object)
+
+ res1 = DatetimeIndex(vals1, dtype=dtype)
+ res2 = DatetimeIndex(vals2, dtype=dtype)
+ res3 = DatetimeIndex(vals3, dtype=dtype)
+ res4 = DatetimeIndex(vals4, dtype=dtype)
+
+ expected = DatetimeIndex(vals1.view("M8[us]"))
+ if res1.tz is not None:
+ expected = expected.tz_localize("UTC").tz_convert(res1.tz)
+ tm.assert_index_equal(res1, expected)
+ tm.assert_index_equal(res2, expected)
+ tm.assert_index_equal(res3, expected)
+ tm.assert_index_equal(res4, expected)
+
+
+class TestTimeSeries:
+ def test_dti_constructor_preserve_dti_freq(self):
+ rng = date_range("1/1/2000", "1/2/2000", freq="5min")
+
+ rng2 = DatetimeIndex(rng)
+ assert rng.freq == rng2.freq
+
+ def test_explicit_none_freq(self):
+ # Explicitly passing freq=None is respected
+ rng = date_range("1/1/2000", "1/2/2000", freq="5min")
+
+ result = DatetimeIndex(rng, freq=None)
+ assert result.freq is None
+
+ result = DatetimeIndex(rng._data, freq=None)
+ assert result.freq is None
+
+ def test_dti_constructor_small_int(self, any_int_numpy_dtype):
+ # see gh-13721
+ exp = DatetimeIndex(
+ [
+ "1970-01-01 00:00:00.00000000",
+ "1970-01-01 00:00:00.00000001",
+ "1970-01-01 00:00:00.00000002",
+ ]
+ )
+
+ arr = np.array([0, 10, 20], dtype=any_int_numpy_dtype)
+ tm.assert_index_equal(DatetimeIndex(arr), exp)
+
+ def test_ctor_str_intraday(self):
+ rng = DatetimeIndex(["1-1-2000 00:00:01"])
+ assert rng[0].second == 1
+
+ def test_index_cast_datetime64_other_units(self):
+ arr = np.arange(0, 100, 10, dtype=np.int64).view("M8[D]")
+ idx = Index(arr)
+
+ assert (idx.values == astype_overflowsafe(arr, dtype=np.dtype("M8[ns]"))).all()
+
+ def test_constructor_int64_nocopy(self):
+ # GH#1624
+ arr = np.arange(1000, dtype=np.int64)
+ index = DatetimeIndex(arr, copy=False)
+
+ arr[50:100] = -1
+ assert (index.asi8[50:100] == -1).all()
+
+ arr = np.arange(1000, dtype=np.int64)
+ index = DatetimeIndex(arr, copy=True)
+
+ arr[50:100] = -1
+ assert (index.asi8[50:100] != -1).all()
+
+ @pytest.mark.parametrize(
+ "freq",
+ ["ME", "QE", "YE", "D", "B", "bh", "min", "s", "ms", "us", "h", "ns", "C"],
+ )
+ def test_from_freq_recreate_from_data(self, freq):
+ org = date_range(start="2001/02/01 09:00", freq=freq, periods=1)
+ idx = DatetimeIndex(org, freq=freq)
+ tm.assert_index_equal(idx, org)
+
+ org = date_range(
+ start="2001/02/01 09:00", freq=freq, tz="US/Pacific", periods=1
+ )
+ idx = DatetimeIndex(org, freq=freq, tz="US/Pacific")
+ tm.assert_index_equal(idx, org)
+
+ def test_datetimeindex_constructor_misc(self):
+ arr = ["1/1/2005", "1/2/2005", "Jn 3, 2005", "2005-01-04"]
+ msg = r"(\(')?Unknown datetime string format(:', 'Jn 3, 2005'\))?"
+ with pytest.raises(ValueError, match=msg):
+ DatetimeIndex(arr)
+
+ arr = ["1/1/2005", "1/2/2005", "1/3/2005", "2005-01-04"]
+ idx1 = DatetimeIndex(arr)
+
+ arr = [datetime(2005, 1, 1), "1/2/2005", "1/3/2005", "2005-01-04"]
+ idx2 = DatetimeIndex(arr)
+
+ arr = [Timestamp(datetime(2005, 1, 1)), "1/2/2005", "1/3/2005", "2005-01-04"]
+ idx3 = DatetimeIndex(arr)
+
+ arr = np.array(["1/1/2005", "1/2/2005", "1/3/2005", "2005-01-04"], dtype="O")
+ idx4 = DatetimeIndex(arr)
+
+ idx5 = DatetimeIndex(["12/05/2007", "25/01/2008"], dayfirst=True)
+ idx6 = DatetimeIndex(
+ ["2007/05/12", "2008/01/25"], dayfirst=False, yearfirst=True
+ )
+ tm.assert_index_equal(idx5, idx6)
+
+ for other in [idx2, idx3, idx4]:
+ assert (idx1.values == other.values).all()
+
+ def test_dti_constructor_object_dtype_dayfirst_yearfirst_with_tz(self):
+ # GH#55813
+ val = "5/10/16"
+
+ dfirst = Timestamp(2016, 10, 5, tz="US/Pacific")
+ yfirst = Timestamp(2005, 10, 16, tz="US/Pacific")
+
+ result1 = DatetimeIndex([val], tz="US/Pacific", dayfirst=True)
+ expected1 = DatetimeIndex([dfirst])
+ tm.assert_index_equal(result1, expected1)
+
+ result2 = DatetimeIndex([val], tz="US/Pacific", yearfirst=True)
+ expected2 = DatetimeIndex([yfirst])
+ tm.assert_index_equal(result2, expected2)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_date_range.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_date_range.py
new file mode 100644
index 0000000000000000000000000000000000000000..fd6a20d9274ce1fa96080d2bdfa752e333f3aa16
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_date_range.py
@@ -0,0 +1,1863 @@
+"""
+test date_range, bdate_range construction from the convenience range functions
+"""
+
+from datetime import (
+ datetime,
+ time,
+ timedelta,
+)
+import re
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import timezones
+from pandas._libs.tslibs.offsets import (
+ BDay,
+ CDay,
+ DateOffset,
+ MonthEnd,
+ prefix_mapping,
+)
+from pandas.errors import (
+ OutOfBoundsDatetime,
+ Pandas4Warning,
+)
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Series,
+ Timedelta,
+ Timestamp,
+ bdate_range,
+ date_range,
+ offsets,
+)
+import pandas._testing as tm
+from pandas.core.arrays.datetimes import _generate_range as generate_range
+from pandas.tests.indexes.datetimes.test_timezones import (
+ FixedOffset,
+ fixed_off_no_name,
+)
+
+from pandas.tseries.holiday import USFederalHolidayCalendar
+
+START, END = datetime(2009, 1, 1), datetime(2010, 1, 1)
+
+
+def _get_expected_range(
+ begin_to_match,
+ end_to_match,
+ both_range,
+ inclusive_endpoints,
+):
+ """Helper to get expected range from a both inclusive range"""
+ left_match = begin_to_match == both_range[0]
+ right_match = end_to_match == both_range[-1]
+
+ if inclusive_endpoints == "left" and right_match:
+ expected_range = both_range[:-1]
+ elif inclusive_endpoints == "right" and left_match:
+ expected_range = both_range[1:]
+ elif inclusive_endpoints == "neither" and left_match and right_match:
+ expected_range = both_range[1:-1]
+ elif inclusive_endpoints == "neither" and right_match:
+ expected_range = both_range[:-1]
+ elif inclusive_endpoints == "neither" and left_match:
+ expected_range = both_range[1:]
+ elif inclusive_endpoints == "both":
+ expected_range = both_range[:]
+ else:
+ expected_range = both_range[:]
+
+ return expected_range
+
+
+class TestTimestampEquivDateRange:
+ # Older tests in TestTimeSeries constructed their `stamp` objects
+ # using `date_range` instead of the `Timestamp` constructor.
+ # TestTimestampEquivDateRange checks that these are equivalent in the
+ # pertinent cases.
+
+ def test_date_range_timestamp_equiv(self):
+ rng = date_range("20090415", "20090519", tz="US/Eastern")
+ stamp = rng[0]
+
+ ts = Timestamp("20090415", tz="US/Eastern")
+ assert ts == stamp
+
+ def test_date_range_timestamp_equiv_dateutil(self):
+ rng = date_range("20090415", "20090519", tz="dateutil/US/Eastern")
+ stamp = rng[0]
+
+ ts = Timestamp("20090415", tz="dateutil/US/Eastern")
+ assert ts == stamp
+
+ def test_date_range_timestamp_equiv_explicit_pytz(self):
+ pytz = pytest.importorskip("pytz")
+ rng = date_range("20090415", "20090519", tz=pytz.timezone("US/Eastern"))
+ stamp = rng[0]
+
+ ts = Timestamp("20090415", tz=pytz.timezone("US/Eastern"))
+ assert ts == stamp
+
+ @td.skip_if_windows
+ def test_date_range_timestamp_equiv_explicit_dateutil(self):
+ from pandas._libs.tslibs.timezones import dateutil_gettz as gettz
+
+ rng = date_range("20090415", "20090519", tz=gettz("US/Eastern"))
+ stamp = rng[0]
+
+ ts = Timestamp("20090415", tz=gettz("US/Eastern"))
+ assert ts == stamp
+
+ def test_date_range_timestamp_equiv_from_datetime_instance(self):
+ datetime_instance = datetime(2014, 3, 4)
+ # build a timestamp with a frequency, since then it supports
+ # addition/subtraction of integers
+ timestamp_instance = date_range(datetime_instance, periods=1, freq="D")[0]
+
+ ts = Timestamp(datetime_instance)
+ assert ts == timestamp_instance
+
+ def test_date_range_timestamp_equiv_preserve_frequency(self):
+ timestamp_instance = date_range("2014-03-05", periods=1, freq="D")[0]
+ ts = Timestamp("2014-03-05")
+
+ assert timestamp_instance == ts
+
+
+class TestDateRanges:
+ def test_date_range_name(self):
+ idx = date_range(start="2000-01-01", periods=1, freq="YE", name="TEST")
+ assert idx.name == "TEST"
+
+ def test_date_range_invalid_periods(self):
+ msg = "periods must be an integer, got foo"
+ with pytest.raises(TypeError, match=msg):
+ date_range(start="1/1/2000", periods="foo", freq="D")
+
+ def test_date_range_fractional_period(self):
+ msg = "periods must be an integer"
+ with pytest.raises(TypeError, match=msg):
+ date_range("1/1/2000", periods=10.5)
+
+ @pytest.mark.parametrize("freq", ["2M", "1m", "2SM", "2BQ", "1bq", "2BY"])
+ def test_date_range_frequency_M_SM_BQ_BY_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ date_range("1/1/2000", periods=4, freq=freq)
+
+ def test_date_range_tuple_freq_raises(self):
+ # GH#34703
+ edate = datetime(2000, 1, 1)
+ with pytest.raises(TypeError, match="pass as a string instead"):
+ date_range(end=edate, freq=("D", 5), periods=20)
+
+ @pytest.mark.parametrize("freq", ["ns", "us", "ms", "min", "s", "h", "D"])
+ def test_date_range_edges(self, freq):
+ # GH#13672
+ td = Timedelta(f"1{freq}")
+ ts = Timestamp("1970-01-01")
+ exp_dtype = "M8[us]" if freq != "ns" else "M8[ns]"
+
+ idx = date_range(
+ start=ts + td,
+ end=ts + 4 * td,
+ freq=freq,
+ )
+ exp = DatetimeIndex(
+ [ts + n * td for n in range(1, 5)],
+ dtype=exp_dtype,
+ freq=freq,
+ )
+ tm.assert_index_equal(idx, exp)
+
+ # start after end
+ idx = date_range(
+ start=ts + 4 * td,
+ end=ts + td,
+ freq=freq,
+ )
+ exp = DatetimeIndex([], dtype=exp_dtype, freq=freq)
+ tm.assert_index_equal(idx, exp)
+
+ # start matches end
+ idx = date_range(
+ start=ts + td,
+ end=ts + td,
+ freq=freq,
+ )
+ exp = DatetimeIndex([ts + td], dtype=exp_dtype, freq=freq)
+ tm.assert_index_equal(idx, exp)
+
+ def test_date_range_near_implementation_bound(self):
+ # GH#???
+ freq = Timedelta(1)
+
+ with pytest.raises(OutOfBoundsDatetime, match="Cannot generate range with"):
+ date_range(end=Timestamp.min, periods=2, freq=freq)
+
+ def test_date_range_nat(self):
+ # GH#11587
+ msg = "Neither `start` nor `end` can be NaT"
+ with pytest.raises(ValueError, match=msg):
+ date_range(start="2016-01-01", end=pd.NaT, freq="D")
+ with pytest.raises(ValueError, match=msg):
+ date_range(start=pd.NaT, end="2016-01-01", freq="D")
+
+ def test_date_range_multiplication_overflow(self):
+ # GH#24255
+ # check that overflows in calculating `addend = periods * stride`
+ # are caught
+ with tm.assert_produces_warning(None):
+ # we should _not_ be seeing an overflow RuntimeWarning
+ dti = date_range(start="1677-09-22", periods=213503, freq="D")
+
+ assert dti[0] == Timestamp("1677-09-22")
+ assert len(dti) == 213503
+
+ msg = "Cannot generate range with"
+ with pytest.raises(OutOfBoundsDatetime, match=msg):
+ date_range("1969-05-04", periods=200000000, freq="30000D")
+
+ def test_date_range_unsigned_overflow_handling(self):
+ # GH#24255
+ # case where `addend = periods * stride` overflows int64 bounds
+ # but not uint64 bounds
+ dti = date_range(start="1677-09-22", end="2262-04-11", freq="D")
+
+ dti2 = date_range(start=dti[0], periods=len(dti), freq="D")
+ assert dti2.equals(dti)
+
+ dti3 = date_range(end=dti[-1], periods=len(dti), freq="D")
+ assert dti3.equals(dti)
+
+ def test_date_range_int64_overflow_non_recoverable(self):
+ # GH#24255
+ # case with start later than 1970-01-01, overflow int64 but not uint64
+ msg = "Cannot generate range with"
+ with pytest.raises(OutOfBoundsDatetime, match=msg):
+ date_range(start="1970-02-01", periods=106752 * 24, freq="h", unit="ns")
+
+ # case with end before 1970-01-01, overflow int64 but not uint64
+ with pytest.raises(OutOfBoundsDatetime, match=msg):
+ date_range(end="1969-11-14", periods=106752 * 24, freq="h", unit="ns")
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "s_ts, e_ts", [("2262-02-23", "1969-11-14"), ("1970-02-01", "1677-10-22")]
+ )
+ def test_date_range_int64_overflow_stride_endpoint_different_signs(
+ self, s_ts, e_ts
+ ):
+ # cases where stride * periods overflow int64 and stride/endpoint
+ # have different signs
+ start = Timestamp(s_ts)
+ end = Timestamp(e_ts)
+
+ expected = date_range(start=start, end=end, freq="-1h")
+ assert expected[0] == start
+ assert expected[-1] == end
+
+ dti = date_range(end=end, periods=len(expected), freq="-1h")
+ tm.assert_index_equal(dti, expected)
+
+ def test_date_range_out_of_bounds(self):
+ # GH#14187
+ msg = "Cannot generate range"
+ with pytest.raises(OutOfBoundsDatetime, match=msg):
+ date_range("2016-01-01", periods=100000, freq="D", unit="ns")
+ with pytest.raises(OutOfBoundsDatetime, match=msg):
+ date_range(end="1763-10-12", periods=100000, freq="D", unit="ns")
+
+ def test_date_range_gen_error(self):
+ rng = date_range("1/1/2000 00:00", "1/1/2000 00:18", freq="5min")
+ assert len(rng) == 4
+
+ def test_date_range_normalize(self):
+ snap = datetime.today()
+ n = 50
+
+ rng = date_range(snap, periods=n, normalize=False, freq="2D")
+
+ offset = timedelta(2)
+ expected = DatetimeIndex(
+ [snap + i * offset for i in range(n)], dtype="M8[us]", freq=offset
+ )
+
+ tm.assert_index_equal(rng, expected)
+
+ rng = date_range("1/1/2000 08:15", periods=n, normalize=False, freq="B")
+ the_time = time(8, 15)
+ for val in rng:
+ assert val.time() == the_time
+
+ def test_date_range_ambiguous_arguments(self):
+ # #2538
+ start = datetime(2011, 1, 1, 5, 3, 40)
+ end = datetime(2011, 1, 1, 8, 9, 40)
+
+ msg = (
+ "Of the four parameters: start, end, periods, and "
+ "freq, exactly three must be specified"
+ )
+ with pytest.raises(ValueError, match=msg):
+ date_range(start, end, periods=10, freq="s")
+
+ def test_date_range_convenience_periods(self, unit):
+ # GH 20808
+ result = date_range("2018-04-24", "2018-04-27", periods=3, unit=unit)
+ expected = DatetimeIndex(
+ ["2018-04-24 00:00:00", "2018-04-25 12:00:00", "2018-04-27 00:00:00"],
+ dtype=f"M8[{unit}]",
+ freq=None,
+ )
+
+ tm.assert_index_equal(result, expected)
+
+ # Test if spacing remains linear if tz changes to dst in range
+ result = date_range(
+ "2018-04-01 01:00:00",
+ "2018-04-01 04:00:00",
+ tz="Australia/Sydney",
+ periods=3,
+ unit=unit,
+ )
+ expected = DatetimeIndex(
+ [
+ Timestamp("2018-04-01 01:00:00+1100", tz="Australia/Sydney"),
+ Timestamp("2018-04-01 02:00:00+1000", tz="Australia/Sydney"),
+ Timestamp("2018-04-01 04:00:00+1000", tz="Australia/Sydney"),
+ ]
+ ).as_unit(unit)
+ tm.assert_index_equal(result, expected)
+
+ def test_date_range_index_comparison(self):
+ rng = date_range("2011-01-01", periods=3, tz="US/Eastern")
+ df = Series(rng).to_frame()
+ arr = np.array([rng.to_list()]).T
+ arr2 = np.array([rng]).T
+
+ with pytest.raises(ValueError, match="Unable to coerce to Series"):
+ rng == df
+
+ with pytest.raises(ValueError, match="Unable to coerce to Series"):
+ df == rng
+
+ expected = DataFrame([True, True, True])
+
+ results = df == arr2
+ tm.assert_frame_equal(results, expected)
+
+ expected = Series([True, True, True], name=0)
+
+ results = df[0] == arr2[:, 0]
+ tm.assert_series_equal(results, expected)
+
+ expected = np.array(
+ [[True, False, False], [False, True, False], [False, False, True]]
+ )
+ results = rng == arr
+ tm.assert_numpy_array_equal(results, expected)
+
+ @pytest.mark.parametrize(
+ "start,end,result_tz",
+ [
+ ["20180101", "20180103", "US/Eastern"],
+ [datetime(2018, 1, 1), datetime(2018, 1, 3), "US/Eastern"],
+ [Timestamp("20180101"), Timestamp("20180103"), "US/Eastern"],
+ [
+ Timestamp("20180101", tz="US/Eastern"),
+ Timestamp("20180103", tz="US/Eastern"),
+ "US/Eastern",
+ ],
+ [
+ Timestamp("20180101", tz="US/Eastern"),
+ Timestamp("20180103", tz="US/Eastern"),
+ None,
+ ],
+ ],
+ )
+ def test_date_range_linspacing_tz(self, start, end, result_tz):
+ # GH 20983
+ result = date_range(start, end, periods=3, tz=result_tz)
+ expected = date_range("20180101", periods=3, freq="D", tz="US/Eastern")
+ tm.assert_index_equal(result, expected)
+
+ def test_date_range_timedelta(self):
+ start = "2020-01-01"
+ end = "2020-01-11"
+ rng1 = date_range(start, end, freq="3D")
+ rng2 = date_range(start, end, freq=timedelta(days=3))
+ tm.assert_index_equal(rng1, rng2)
+
+ def test_range_misspecified(self):
+ # GH #1095
+ msg = (
+ "Of the four parameters: start, end, periods, and "
+ "freq, exactly three must be specified"
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ date_range(start="1/1/2000")
+
+ with pytest.raises(ValueError, match=msg):
+ date_range(end="1/1/2000")
+
+ with pytest.raises(ValueError, match=msg):
+ date_range(periods=10)
+
+ with pytest.raises(ValueError, match=msg):
+ date_range(start="1/1/2000", freq="h")
+
+ with pytest.raises(ValueError, match=msg):
+ date_range(end="1/1/2000", freq="h")
+
+ with pytest.raises(ValueError, match=msg):
+ date_range(periods=10, freq="h")
+
+ with pytest.raises(ValueError, match=msg):
+ date_range()
+
+ def test_compat_replace(self):
+ # https://github.com/statsmodels/statsmodels/issues/3349
+ # replace should take ints/longs for compat
+ result = date_range(Timestamp("1960-04-01 00:00:00"), periods=76, freq="QS-JAN")
+ assert len(result) == 76
+
+ def test_catch_infinite_loop(self):
+ offset = offsets.DateOffset(minute=5)
+ # blow up, don't loop forever
+ msg = "Offset did not increment date"
+ with pytest.raises(ValueError, match=msg):
+ date_range(datetime(2011, 11, 11), datetime(2011, 11, 12), freq=offset)
+
+ def test_construct_over_dst(self, unit):
+ # GH 20854
+ pre_dst = Timestamp("2010-11-07 01:00:00").tz_localize(
+ "US/Pacific", ambiguous=True
+ )
+ pst_dst = Timestamp("2010-11-07 01:00:00").tz_localize(
+ "US/Pacific", ambiguous=False
+ )
+ expect_data = [
+ Timestamp("2010-11-07 00:00:00", tz="US/Pacific"),
+ pre_dst,
+ pst_dst,
+ ]
+ expected = DatetimeIndex(expect_data, freq="h").as_unit(unit)
+ result = date_range(
+ start="2010-11-7", periods=3, freq="h", tz="US/Pacific", unit=unit
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_construct_with_different_start_end_string_format(self, unit):
+ # GH 12064
+ result = date_range(
+ "2013-01-01 00:00:00+09:00",
+ "2013/01/01 02:00:00+09:00",
+ freq="h",
+ unit=unit,
+ )
+ expected = DatetimeIndex(
+ [
+ Timestamp("2013-01-01 00:00:00+09:00"),
+ Timestamp("2013-01-01 01:00:00+09:00"),
+ Timestamp("2013-01-01 02:00:00+09:00"),
+ ],
+ freq="h",
+ ).as_unit(unit)
+ tm.assert_index_equal(result, expected)
+
+ def test_error_with_zero_monthends(self):
+ msg = r"Offset <0 \* MonthEnds> did not increment date"
+ with pytest.raises(ValueError, match=msg):
+ date_range("1/1/2000", "1/1/2001", freq=MonthEnd(0))
+
+ def test_range_bug(self, unit):
+ # GH #770
+ offset = DateOffset(months=3)
+ result = date_range("2011-1-1", "2012-1-31", freq=offset, unit=unit)
+
+ start = datetime(2011, 1, 1)
+ expected = DatetimeIndex(
+ [start + i * offset for i in range(5)], dtype=f"M8[{unit}]", freq=offset
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_range_tz_pytz(self):
+ # see gh-2906
+ pytz = pytest.importorskip("pytz")
+ tz = pytz.timezone("US/Eastern")
+ start = tz.localize(datetime(2011, 1, 1))
+ end = tz.localize(datetime(2011, 1, 3))
+
+ dr = date_range(start=start, periods=3)
+ assert dr.tz.zone == tz.zone
+ assert dr[0] == start
+ assert dr[2] == end
+
+ dr = date_range(end=end, periods=3)
+ assert dr.tz.zone == tz.zone
+ assert dr[0] == start
+ assert dr[2] == end
+
+ dr = date_range(start=start, end=end)
+ assert dr.tz.zone == tz.zone
+ assert dr[0] == start
+ assert dr[2] == end
+
+ @pytest.mark.parametrize(
+ "start, end",
+ [
+ [datetime(2014, 3, 6), datetime(2014, 3, 12)],
+ [datetime(2013, 11, 1), datetime(2013, 11, 6)],
+ ],
+ )
+ def test_range_tz_dst_straddle_pytz(self, start, end):
+ pytz = pytest.importorskip("pytz")
+ tz = pytz.timezone("US/Eastern")
+ start = Timestamp(start, tz=tz)
+ end = Timestamp(end, tz=tz)
+ dr = date_range(start, end, freq="D")
+ assert dr[0] == start
+ assert dr[-1] == end
+ assert np.all(dr.hour == 0)
+
+ dr = date_range(start, end, freq="D", tz=tz)
+ assert dr[0] == start
+ assert dr[-1] == end
+ assert np.all(dr.hour == 0)
+
+ dr = date_range(
+ start.replace(tzinfo=None),
+ end.replace(tzinfo=None),
+ freq="D",
+ tz=tz,
+ )
+ assert dr[0] == start
+ assert dr[-1] == end
+ assert np.all(dr.hour == 0)
+
+ def test_range_tz_dateutil(self):
+ # see gh-2906
+
+ # Use maybe_get_tz to fix filename in tz under dateutil.
+ from pandas._libs.tslibs.timezones import maybe_get_tz
+
+ tz = lambda x: maybe_get_tz("dateutil/" + x)
+
+ start = datetime(2011, 1, 1, tzinfo=tz("US/Eastern"))
+ end = datetime(2011, 1, 3, tzinfo=tz("US/Eastern"))
+
+ dr = date_range(start=start, periods=3)
+ assert dr.tz == tz("US/Eastern")
+ assert dr[0] == start
+ assert dr[2] == end
+
+ dr = date_range(end=end, periods=3)
+ assert dr.tz == tz("US/Eastern")
+ assert dr[0] == start
+ assert dr[2] == end
+
+ dr = date_range(start=start, end=end)
+ assert dr.tz == tz("US/Eastern")
+ assert dr[0] == start
+ assert dr[2] == end
+
+ @pytest.mark.parametrize("freq", ["1D", "3D", "2ME", "7W", "3h", "YE"])
+ @pytest.mark.parametrize("tz", [None, "US/Eastern"])
+ def test_range_closed(self, freq, tz, inclusive_endpoints_fixture):
+ # GH#12409, GH#12684
+
+ begin = Timestamp("2011/1/1", tz=tz)
+ end = Timestamp("2014/1/1", tz=tz)
+
+ result_range = date_range(
+ begin, end, inclusive=inclusive_endpoints_fixture, freq=freq
+ )
+ both_range = date_range(begin, end, inclusive="both", freq=freq)
+ expected_range = _get_expected_range(
+ begin, end, both_range, inclusive_endpoints_fixture
+ )
+
+ tm.assert_index_equal(expected_range, result_range)
+
+ @pytest.mark.parametrize("freq", ["1D", "3D", "2ME", "7W", "3h", "YE"])
+ def test_range_with_tz_closed_with_tz_aware_start_end(
+ self, freq, inclusive_endpoints_fixture
+ ):
+ begin = Timestamp("2011/1/1")
+ end = Timestamp("2014/1/1")
+ begintz = Timestamp("2011/1/1", tz="US/Eastern")
+ endtz = Timestamp("2014/1/1", tz="US/Eastern")
+
+ result_range = date_range(
+ begin,
+ end,
+ inclusive=inclusive_endpoints_fixture,
+ freq=freq,
+ tz="US/Eastern",
+ )
+ both_range = date_range(
+ begin, end, inclusive="both", freq=freq, tz="US/Eastern"
+ )
+ expected_range = _get_expected_range(
+ begintz,
+ endtz,
+ both_range,
+ inclusive_endpoints_fixture,
+ )
+
+ tm.assert_index_equal(expected_range, result_range)
+
+ def test_range_closed_boundary(self, inclusive_endpoints_fixture):
+ # GH#11804
+ right_boundary = date_range(
+ "2015-09-12",
+ "2015-12-01",
+ freq="QS-MAR",
+ inclusive=inclusive_endpoints_fixture,
+ )
+ left_boundary = date_range(
+ "2015-09-01",
+ "2015-09-12",
+ freq="QS-MAR",
+ inclusive=inclusive_endpoints_fixture,
+ )
+ both_boundary = date_range(
+ "2015-09-01",
+ "2015-12-01",
+ freq="QS-MAR",
+ inclusive=inclusive_endpoints_fixture,
+ )
+ neither_boundary = date_range(
+ "2015-09-11",
+ "2015-09-12",
+ freq="QS-MAR",
+ inclusive=inclusive_endpoints_fixture,
+ )
+
+ expected_right = both_boundary
+ expected_left = both_boundary
+ expected_both = both_boundary
+
+ if inclusive_endpoints_fixture == "right":
+ expected_left = both_boundary[1:]
+ elif inclusive_endpoints_fixture == "left":
+ expected_right = both_boundary[:-1]
+ elif inclusive_endpoints_fixture == "both":
+ expected_right = both_boundary[1:]
+ expected_left = both_boundary[:-1]
+
+ expected_neither = both_boundary[1:-1]
+
+ tm.assert_index_equal(right_boundary, expected_right)
+ tm.assert_index_equal(left_boundary, expected_left)
+ tm.assert_index_equal(both_boundary, expected_both)
+ tm.assert_index_equal(neither_boundary, expected_neither)
+
+ def test_date_range_years_only(self, tz_naive_fixture):
+ tz = tz_naive_fixture
+ # GH#6961
+ rng1 = date_range("2014", "2015", freq="ME", tz=tz)
+ expected1 = date_range("2014-01-31", "2014-12-31", freq="ME", tz=tz)
+ tm.assert_index_equal(rng1, expected1)
+
+ rng2 = date_range("2014", "2015", freq="MS", tz=tz)
+ expected2 = date_range("2014-01-01", "2015-01-01", freq="MS", tz=tz)
+ tm.assert_index_equal(rng2, expected2)
+
+ rng3 = date_range("2014", "2020", freq="YE", tz=tz)
+ expected3 = date_range("2014-12-31", "2019-12-31", freq="YE", tz=tz)
+ tm.assert_index_equal(rng3, expected3)
+
+ rng4 = date_range("2014", "2020", freq="YS", tz=tz)
+ expected4 = date_range("2014-01-01", "2020-01-01", freq="YS", tz=tz)
+ tm.assert_index_equal(rng4, expected4)
+
+ def test_freq_divides_end_in_nanos(self):
+ # GH 10885
+ result_1 = date_range("2005-01-12 10:00", "2005-01-12 16:00", freq="345min")
+ result_2 = date_range("2005-01-13 10:00", "2005-01-13 16:00", freq="345min")
+ expected_1 = DatetimeIndex(
+ ["2005-01-12 10:00:00", "2005-01-12 15:45:00"],
+ dtype="datetime64[us]",
+ freq="345min",
+ tz=None,
+ )
+ expected_2 = DatetimeIndex(
+ ["2005-01-13 10:00:00", "2005-01-13 15:45:00"],
+ dtype="datetime64[us]",
+ freq="345min",
+ tz=None,
+ )
+ tm.assert_index_equal(result_1, expected_1)
+ tm.assert_index_equal(result_2, expected_2)
+
+ def test_cached_range_bug(self):
+ rng = date_range("2010-09-01 05:00:00", periods=50, freq=DateOffset(hours=6))
+ assert len(rng) == 50
+ assert rng[0] == datetime(2010, 9, 1, 5)
+
+ def test_timezone_comparison_bug(self):
+ # smoke test
+ start = Timestamp("20130220 10:00", tz="US/Eastern")
+ result = date_range(start, periods=2, tz="US/Eastern")
+ assert len(result) == 2
+
+ def test_timezone_comparison_assert(self):
+ start = Timestamp("20130220 10:00", tz="US/Eastern")
+ msg = "Inferred time zone not equal to passed time zone"
+ with pytest.raises(AssertionError, match=msg):
+ date_range(start, periods=2, tz="Europe/Berlin")
+
+ def test_negative_non_tick_frequency_descending_dates(self, tz_aware_fixture):
+ # GH 23270
+ tz = tz_aware_fixture
+ result = date_range(start="2011-06-01", end="2011-01-01", freq="-1MS", tz=tz)
+ expected = date_range(end="2011-06-01", start="2011-01-01", freq="1MS", tz=tz)[
+ ::-1
+ ]
+ tm.assert_index_equal(result, expected)
+
+ def test_range_where_start_equal_end(self, inclusive_endpoints_fixture):
+ # GH 43394
+ start = "2021-09-02"
+ end = "2021-09-02"
+ result = date_range(
+ start=start, end=end, freq="D", inclusive=inclusive_endpoints_fixture
+ )
+
+ both_range = date_range(start=start, end=end, freq="D", inclusive="both")
+ if inclusive_endpoints_fixture == "neither":
+ expected = both_range[1:-1]
+ elif inclusive_endpoints_fixture in ("left", "right", "both"):
+ expected = both_range[:]
+
+ tm.assert_index_equal(result, expected)
+
+ def test_freq_dateoffset_with_relateivedelta_nanos(self):
+ # GH 46877
+ freq = DateOffset(hours=10, days=57, nanoseconds=3)
+ result = date_range(end="1970-01-01 00:00:00", periods=10, freq=freq, name="a")
+ expected = DatetimeIndex(
+ [
+ "1968-08-02T05:59:59.999999973",
+ "1968-09-28T15:59:59.999999976",
+ "1968-11-25T01:59:59.999999979",
+ "1969-01-21T11:59:59.999999982",
+ "1969-03-19T21:59:59.999999985",
+ "1969-05-16T07:59:59.999999988",
+ "1969-07-12T17:59:59.999999991",
+ "1969-09-08T03:59:59.999999994",
+ "1969-11-04T13:59:59.999999997",
+ "1970-01-01T00:00:00.000000000",
+ ],
+ name="a",
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("freq", ["2T", "2L", "1l", "1U", "2N", "2n"])
+ def test_frequency_H_T_S_L_U_N_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+ with pytest.raises(ValueError, match=msg):
+ date_range("1/1/2000", periods=2, freq=freq)
+
+ @pytest.mark.parametrize(
+ "freq_depr", ["m", "bm", "CBM", "SM", "BQ", "q-feb", "y-may", "Y-MAY"]
+ )
+ def test_frequency_raises(self, freq_depr):
+ msg = f"Invalid frequency: {freq_depr}"
+
+ with pytest.raises(ValueError, match=msg):
+ date_range("1/1/2000", periods=2, freq=freq_depr)
+
+ def test_date_range_bday(self):
+ sdate = datetime(1999, 12, 25)
+ idx = date_range(start=sdate, freq="1B", periods=20)
+ assert len(idx) == 20
+ assert idx[0] == sdate + 0 * offsets.BDay()
+ assert idx.freq == "B"
+
+ @pytest.mark.parametrize("freq", ["200A", "2A-MAY"])
+ def test_frequency_A_raises(self, freq):
+ freq_msg = re.split("[0-9]*", freq, maxsplit=1)[1]
+ msg = f"Invalid frequency: {freq_msg}"
+
+ with pytest.raises(ValueError, match=msg):
+ date_range("1/1/2000", periods=2, freq=freq)
+
+ @pytest.mark.parametrize(
+ "freq,freq_depr",
+ [
+ ("2W", "2w"),
+ ("2W-WED", "2w-wed"),
+ ("2B", "2b"),
+ ("2D", "2d"),
+ ("2C", "2c"),
+ ],
+ )
+ def test_date_range_depr_lowercase_frequency(self, freq, freq_depr):
+ # GH#58998
+ depr_msg = (
+ f"'{freq_depr[1:]}' is deprecated and will be removed in a future version."
+ )
+
+ expected = date_range("1/1/2000", periods=4, freq=freq)
+ with tm.assert_produces_warning(Pandas4Warning, match=depr_msg):
+ result = date_range("1/1/2000", periods=4, freq=freq_depr)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "freq_removed,freq",
+ [
+ ("100A", "Y"),
+ ("2A-DEC", "Y-DEC"),
+ ("100AS", "YS"),
+ ("2AS-MAY", "YS-MAY"),
+ ],
+ )
+ def test_error_message_for_removed_year_yearbegin_frequencies(
+ self, freq, freq_removed
+ ):
+ msg = f"Did you mean {freq}"
+ with pytest.raises(ValueError, match=msg):
+ date_range("1/1/2000", periods=2, freq=freq_removed)
+
+
+class TestDateRangeTZ:
+ """Tests for date_range with timezones"""
+
+ def test_hongkong_tz_convert(self):
+ # GH#1673 smoke test
+ dr = date_range("2012-01-01", "2012-01-10", freq="D", tz="Hongkong")
+
+ # it works!
+ dr.hour
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_date_range_span_dst_transition(self, tzstr):
+ # GH#1778
+
+ # Standard -> Daylight Savings Time
+ dr = date_range("03/06/2012 00:00", periods=200, freq="W-FRI", tz="US/Eastern")
+
+ assert (dr.hour == 0).all()
+
+ dr = date_range("2012-11-02", periods=10, tz=tzstr)
+ result = dr.hour
+ expected = pd.Index([0] * 10, dtype="int32")
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_date_range_timezone_str_argument(self, tzstr):
+ tz = timezones.maybe_get_tz(tzstr)
+ result = date_range("1/1/2000", periods=10, tz=tzstr)
+ expected = date_range("1/1/2000", periods=10, tz=tz)
+
+ tm.assert_index_equal(result, expected)
+
+ def test_date_range_with_fixed_tz(self):
+ off = FixedOffset(420, "+07:00")
+ start = datetime(2012, 3, 11, 5, 0, 0, tzinfo=off)
+ end = datetime(2012, 6, 11, 5, 0, 0, tzinfo=off)
+ rng = date_range(start=start, end=end)
+ assert off == rng.tz
+
+ rng2 = date_range(start, periods=len(rng), tz=off)
+ tm.assert_index_equal(rng, rng2)
+
+ rng3 = date_range("3/11/2012 05:00:00+07:00", "6/11/2012 05:00:00+07:00")
+ assert (rng.values == rng3.values).all()
+
+ def test_date_range_with_fixedoffset_noname(self):
+ off = fixed_off_no_name
+ start = datetime(2012, 3, 11, 5, 0, 0, tzinfo=off)
+ end = datetime(2012, 6, 11, 5, 0, 0, tzinfo=off)
+ rng = date_range(start=start, end=end)
+ assert off == rng.tz
+
+ idx = pd.Index([start, end])
+ assert off == idx.tz
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_date_range_with_tz(self, tzstr):
+ stamp = Timestamp("3/11/2012 05:00", tz=tzstr)
+ assert stamp.hour == 5
+
+ rng = date_range("3/11/2012 04:00", periods=10, freq="h", tz=tzstr)
+
+ assert stamp == rng[1]
+
+ @pytest.mark.parametrize("tz", ["Europe/London", "dateutil/Europe/London"])
+ def test_date_range_ambiguous_endpoint(self, tz):
+ # construction with an ambiguous end-point
+ # GH#11626
+
+ with pytest.raises(ValueError, match="Cannot infer dst time"):
+ date_range(
+ "2013-10-26 23:00", "2013-10-27 01:00", tz="Europe/London", freq="h"
+ )
+
+ times = date_range(
+ "2013-10-26 23:00", "2013-10-27 01:00", freq="h", tz=tz, ambiguous="infer"
+ )
+ assert times[0] == Timestamp("2013-10-26 23:00", tz=tz)
+ assert times[-1] == Timestamp("2013-10-27 01:00:00+0000", tz=tz)
+
+ @pytest.mark.parametrize(
+ "tz, option, expected",
+ [
+ ["US/Pacific", "shift_forward", "2019-03-10 03:00"],
+ ["dateutil/US/Pacific", "shift_forward", "2019-03-10 03:00"],
+ ["US/Pacific", "shift_backward", "2019-03-10 01:00"],
+ ["dateutil/US/Pacific", "shift_backward", "2019-03-10 01:00"],
+ ["US/Pacific", timedelta(hours=1), "2019-03-10 03:00"],
+ ],
+ )
+ def test_date_range_nonexistent_endpoint(self, tz, option, expected):
+ # construction with an nonexistent end-point
+
+ with pytest.raises(ValueError, match="2019-03-10 02:00:00"):
+ date_range(
+ "2019-03-10 00:00", "2019-03-10 02:00", tz="US/Pacific", freq="h"
+ )
+
+ times = date_range(
+ "2019-03-10 00:00", "2019-03-10 02:00", freq="h", tz=tz, nonexistent=option
+ )
+ assert times[-1] == Timestamp(expected, tz=tz)
+
+
+class TestGenRangeGeneration:
+ @pytest.mark.parametrize(
+ "freqstr,offset",
+ [
+ ("B", BDay()),
+ ("C", CDay()),
+ ],
+ )
+ def test_generate(self, freqstr, offset):
+ rng1 = list(generate_range(START, END, periods=None, offset=offset, unit="ns"))
+ rng2 = list(generate_range(START, END, periods=None, offset=freqstr, unit="ns"))
+ assert rng1 == rng2
+
+ def test_1(self):
+ rng = list(
+ generate_range(
+ start=datetime(2009, 3, 25),
+ end=None,
+ periods=2,
+ offset=BDay(),
+ unit="ns",
+ )
+ )
+ expected = [datetime(2009, 3, 25), datetime(2009, 3, 26)]
+ assert rng == expected
+
+ def test_2(self):
+ rng = list(
+ generate_range(
+ start=datetime(2008, 1, 1),
+ end=datetime(2008, 1, 3),
+ periods=None,
+ offset=BDay(),
+ unit="ns",
+ )
+ )
+ expected = [datetime(2008, 1, 1), datetime(2008, 1, 2), datetime(2008, 1, 3)]
+ assert rng == expected
+
+ def test_3(self):
+ rng = list(
+ generate_range(
+ start=datetime(2008, 1, 5),
+ end=datetime(2008, 1, 6),
+ periods=None,
+ offset=BDay(),
+ unit="ns",
+ )
+ )
+ expected = []
+ assert rng == expected
+
+ def test_precision_finer_than_offset(self):
+ # GH#9907
+ result1 = date_range(
+ start="2015-04-15 00:00:03", end="2016-04-22 00:00:00", freq="QE"
+ )
+ result2 = date_range(
+ start="2015-04-15 00:00:03", end="2015-06-22 00:00:04", freq="W"
+ )
+ expected1_list = [
+ "2015-06-30 00:00:03",
+ "2015-09-30 00:00:03",
+ "2015-12-31 00:00:03",
+ "2016-03-31 00:00:03",
+ ]
+ expected2_list = [
+ "2015-04-19 00:00:03",
+ "2015-04-26 00:00:03",
+ "2015-05-03 00:00:03",
+ "2015-05-10 00:00:03",
+ "2015-05-17 00:00:03",
+ "2015-05-24 00:00:03",
+ "2015-05-31 00:00:03",
+ "2015-06-07 00:00:03",
+ "2015-06-14 00:00:03",
+ "2015-06-21 00:00:03",
+ ]
+ expected1 = DatetimeIndex(
+ expected1_list, dtype="datetime64[us]", freq="QE-DEC", tz=None
+ )
+ expected2 = DatetimeIndex(
+ expected2_list, dtype="datetime64[us]", freq="W-SUN", tz=None
+ )
+ tm.assert_index_equal(result1, expected1)
+ tm.assert_index_equal(result2, expected2)
+
+ dt1, dt2 = "2017-01-01", "2017-01-01"
+ tz1, tz2 = "US/Eastern", "Europe/London"
+
+ @pytest.mark.parametrize(
+ "start,end",
+ [
+ (Timestamp(dt1, tz=tz1), Timestamp(dt2)),
+ (Timestamp(dt1), Timestamp(dt2, tz=tz2)),
+ (Timestamp(dt1, tz=tz1), Timestamp(dt2, tz=tz2)),
+ (Timestamp(dt1, tz=tz2), Timestamp(dt2, tz=tz1)),
+ ],
+ )
+ def test_mismatching_tz_raises_err(self, start, end):
+ # issue 18488
+ msg = "Start and end cannot both be tz-aware with different timezones"
+ with pytest.raises(TypeError, match=msg):
+ date_range(start, end)
+ with pytest.raises(TypeError, match=msg):
+ date_range(start, end, freq=BDay())
+
+
+class TestBusinessDateRange:
+ def test_constructor(self):
+ bdate_range(START, END, freq=BDay())
+ bdate_range(START, periods=20, freq=BDay())
+ bdate_range(end=START, periods=20, freq=BDay())
+
+ msg = "periods must be an integer, got B"
+ with pytest.raises(TypeError, match=msg):
+ date_range("2011-1-1", "2012-1-1", "B")
+
+ with pytest.raises(TypeError, match=msg):
+ bdate_range("2011-1-1", "2012-1-1", "B")
+
+ msg = "freq must be specified for bdate_range; use date_range instead"
+ with pytest.raises(TypeError, match=msg):
+ bdate_range(START, END, periods=10, freq=None)
+
+ def test_misc(self):
+ end = datetime(2009, 5, 13)
+ dr = bdate_range(end=end, periods=20)
+ firstDate = end - 19 * BDay()
+
+ assert len(dr) == 20
+ assert dr[0] == firstDate
+ assert dr[-1] == end
+
+ def test_date_parse_failure(self):
+ badly_formed_date = "2007/100/1"
+
+ msg = "Unknown datetime string format, unable to parse: 2007/100/1"
+ with pytest.raises(ValueError, match=msg):
+ Timestamp(badly_formed_date)
+
+ with pytest.raises(ValueError, match=msg):
+ bdate_range(start=badly_formed_date, periods=10)
+
+ with pytest.raises(ValueError, match=msg):
+ bdate_range(end=badly_formed_date, periods=10)
+
+ with pytest.raises(ValueError, match=msg):
+ bdate_range(badly_formed_date, badly_formed_date)
+
+ def test_daterange_bug_456(self):
+ # GH #456
+ rng1 = bdate_range("12/5/2011", "12/5/2011")
+ rng2 = bdate_range("12/2/2011", "12/5/2011")
+ assert rng2._data.freq == BDay()
+
+ result = rng1.union(rng2)
+ assert isinstance(result, DatetimeIndex)
+
+ def test_bdays_and_open_boundaries(self, inclusive_endpoints_fixture):
+ # GH 6673
+ start = "2018-07-21" # Saturday
+ end = "2018-07-29" # Sunday
+ result = date_range(start, end, freq="B", inclusive=inclusive_endpoints_fixture)
+
+ bday_start = "2018-07-23" # Monday
+ bday_end = "2018-07-27" # Friday
+ expected = date_range(bday_start, bday_end, freq="D")
+ tm.assert_index_equal(result, expected)
+ # Note: we do _not_ expect the freqs to match here
+
+ def test_bday_near_overflow(self):
+ # GH#24252 avoid doing unnecessary addition that _would_ overflow
+ start = Timestamp.max.floor("D").to_pydatetime()
+ rng = date_range(start, end=None, periods=1, freq="B")
+ expected = DatetimeIndex([start], freq="B")
+ tm.assert_index_equal(rng, expected)
+
+ def test_bday_overflow_error(self):
+ # GH#24252 check that we get OutOfBoundsDatetime and not OverflowError
+ msg = "Out of bounds nanosecond timestamp"
+ start = Timestamp.max.floor("D").to_pydatetime()
+ with pytest.raises(OutOfBoundsDatetime, match=msg):
+ date_range(start, periods=2, freq="B", unit="ns")
+
+
+class TestCustomDateRange:
+ def test_constructor(self):
+ bdate_range(START, END, freq=CDay())
+ bdate_range(START, periods=20, freq=CDay())
+ bdate_range(end=START, periods=20, freq=CDay())
+
+ msg = "periods must be an integer, got C"
+ with pytest.raises(TypeError, match=msg):
+ date_range("2011-1-1", "2012-1-1", "C")
+
+ with pytest.raises(TypeError, match=msg):
+ bdate_range("2011-1-1", "2012-1-1", "C")
+
+ def test_misc(self):
+ end = datetime(2009, 5, 13)
+ dr = bdate_range(end=end, periods=20, freq="C")
+ firstDate = end - 19 * CDay()
+
+ assert len(dr) == 20
+ assert dr[0] == firstDate
+ assert dr[-1] == end
+
+ def test_daterange_bug_456(self):
+ # GH #456
+ rng1 = bdate_range("12/5/2011", "12/5/2011", freq="C")
+ rng2 = bdate_range("12/2/2011", "12/5/2011", freq="C")
+ assert rng2._data.freq == CDay()
+
+ result = rng1.union(rng2)
+ assert isinstance(result, DatetimeIndex)
+
+ def test_cdaterange(self, unit):
+ result = bdate_range("2013-05-01", periods=3, freq="C", unit=unit)
+ expected = DatetimeIndex(
+ ["2013-05-01", "2013-05-02", "2013-05-03"], dtype=f"M8[{unit}]", freq="C"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ def test_cdaterange_weekmask(self, unit):
+ result = bdate_range(
+ "2013-05-01", periods=3, freq="C", weekmask="Sun Mon Tue Wed Thu", unit=unit
+ )
+ expected = DatetimeIndex(
+ ["2013-05-01", "2013-05-02", "2013-05-05"],
+ dtype=f"M8[{unit}]",
+ freq=result.freq,
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ # raise with non-custom freq
+ msg = (
+ "a custom frequency string is required when holidays or "
+ "weekmask are passed, got frequency B"
+ )
+ with pytest.raises(ValueError, match=msg):
+ bdate_range("2013-05-01", periods=3, weekmask="Sun Mon Tue Wed Thu")
+
+ def test_cdaterange_holidays(self, unit):
+ result = bdate_range(
+ "2013-05-01", periods=3, freq="C", holidays=["2013-05-01"], unit=unit
+ )
+ expected = DatetimeIndex(
+ ["2013-05-02", "2013-05-03", "2013-05-06"],
+ dtype=f"M8[{unit}]",
+ freq=result.freq,
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ # raise with non-custom freq
+ msg = (
+ "a custom frequency string is required when holidays or "
+ "weekmask are passed, got frequency B"
+ )
+ with pytest.raises(ValueError, match=msg):
+ bdate_range("2013-05-01", periods=3, holidays=["2013-05-01"])
+
+ def test_cdaterange_weekmask_and_holidays(self, unit):
+ result = bdate_range(
+ "2013-05-01",
+ periods=3,
+ freq="C",
+ weekmask="Sun Mon Tue Wed Thu",
+ holidays=["2013-05-01"],
+ unit=unit,
+ )
+ expected = DatetimeIndex(
+ ["2013-05-02", "2013-05-05", "2013-05-06"],
+ dtype=f"M8[{unit}]",
+ freq=result.freq,
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ def test_cdaterange_holidays_weekmask_requires_freqstr(self):
+ # raise with non-custom freq
+ msg = (
+ "a custom frequency string is required when holidays or "
+ "weekmask are passed, got frequency B"
+ )
+ with pytest.raises(ValueError, match=msg):
+ bdate_range(
+ "2013-05-01",
+ periods=3,
+ weekmask="Sun Mon Tue Wed Thu",
+ holidays=["2013-05-01"],
+ )
+
+ @pytest.mark.parametrize(
+ "freq", [freq for freq in prefix_mapping if freq.upper().startswith("C")]
+ )
+ def test_all_custom_freq(self, freq):
+ # should not raise
+ bdate_range(
+ START, END, freq=freq, weekmask="Mon Wed Fri", holidays=["2009-03-14"]
+ )
+
+ bad_freq = freq + "FOO"
+ msg = f"invalid custom frequency string: {bad_freq}"
+ with pytest.raises(ValueError, match=msg):
+ bdate_range(START, END, freq=bad_freq)
+
+ @pytest.mark.parametrize(
+ "start_end",
+ [
+ ("2018-01-01T00:00:01.000Z", "2018-01-03T00:00:01.000Z"),
+ ("2018-01-01T00:00:00.010Z", "2018-01-03T00:00:00.010Z"),
+ ("2001-01-01T00:00:00.010Z", "2001-01-03T00:00:00.010Z"),
+ ],
+ )
+ def test_range_with_millisecond_resolution(self, start_end):
+ # https://github.com/pandas-dev/pandas/issues/24110
+ start, end = start_end
+ result = date_range(start=start, end=end, periods=2, inclusive="left")
+ expected = DatetimeIndex([start], dtype="M8[us, UTC]")
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "start,period,expected",
+ [
+ ("2022-07-23 00:00:00+02:00", 1, ["2022-07-25 00:00:00+02:00"]),
+ ("2022-07-22 00:00:00+02:00", 1, ["2022-07-22 00:00:00+02:00"]),
+ (
+ "2022-07-22 00:00:00+02:00",
+ 2,
+ ["2022-07-22 00:00:00+02:00", "2022-07-25 00:00:00+02:00"],
+ ),
+ ],
+ )
+ def test_range_with_timezone_and_custombusinessday(self, start, period, expected):
+ # GH49441
+ result = date_range(start=start, periods=period, freq="C")
+ expected = DatetimeIndex(expected).as_unit("us")
+ tm.assert_index_equal(result, expected)
+
+ def test_data_range_custombusinessday_partial_time(self, unit):
+ # GH#57456
+ offset = offsets.CustomBusinessDay(weekmask="Sun Mon Tue")
+ start = datetime(2024, 2, 6, 23)
+ # end datetime is partial and not in the offset
+ end = datetime(2024, 2, 14, 14)
+ result = date_range(start, end, freq=offset, unit=unit)
+ expected = DatetimeIndex(
+ [
+ "2024-02-06 23:00:00",
+ "2024-02-11 23:00:00",
+ "2024-02-12 23:00:00",
+ "2024-02-13 23:00:00",
+ ],
+ dtype=f"M8[{unit}]",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_cdaterange_cbh(self):
+ # GH#62849
+ result = bdate_range(
+ "2009-03-13",
+ "2009-03-15",
+ freq="cbh",
+ weekmask="Mon Wed Fri",
+ holidays=["2009-03-14"],
+ )
+ expected = DatetimeIndex(
+ [
+ "2009-03-13 09:00:00",
+ "2009-03-13 10:00:00",
+ "2009-03-13 11:00:00",
+ "2009-03-13 12:00:00",
+ "2009-03-13 13:00:00",
+ "2009-03-13 14:00:00",
+ "2009-03-13 15:00:00",
+ "2009-03-13 16:00:00",
+ ],
+ dtype="datetime64[us]",
+ freq="cbh",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_cdaterange_deprecated_error_CBH(self):
+ # GH#62849
+ msg = "invalid custom frequency string: CBH, did you mean cbh?"
+ with pytest.raises(ValueError, match=msg):
+ bdate_range(
+ START, END, freq="CBH", weekmask="Mon Wed Fri", holidays=["2009-03-14"]
+ )
+
+
+class TestDateRangeNonNano:
+ def test_date_range_reso_validation(self):
+ msg = "'unit' must be one of 's', 'ms', 'us', 'ns'"
+ with pytest.raises(ValueError, match=msg):
+ date_range("2016-01-01", "2016-03-04", periods=3, unit="h")
+
+ def test_date_range_freq_higher_than_reso(self):
+ # freq being higher-resolution than reso is a problem
+ msg = "Use a lower freq or a higher unit instead"
+ with pytest.raises(ValueError, match=msg):
+ # # TODO give a more useful or informative message?
+ date_range("2016-01-01", "2016-01-02", freq="ns", unit="ms")
+
+ def test_date_range_freq_matches_reso(self):
+ # GH#49106 matching reso is OK
+ dti = date_range("2016-01-01", "2016-01-01 00:00:01", freq="ms", unit="ms")
+ rng = np.arange(1_451_606_400_000, 1_451_606_401_001, dtype=np.int64)
+ expected = DatetimeIndex(rng.view("M8[ms]"), freq="ms")
+ tm.assert_index_equal(dti, expected)
+
+ dti = date_range("2016-01-01", "2016-01-01 00:00:01", freq="us", unit="us")
+ rng = np.arange(1_451_606_400_000_000, 1_451_606_401_000_001, dtype=np.int64)
+ expected = DatetimeIndex(rng.view("M8[us]"), freq="us")
+ tm.assert_index_equal(dti, expected)
+
+ dti = date_range("2016-01-01", "2016-01-01 00:00:00.001", freq="ns", unit="ns")
+ rng = np.arange(
+ 1_451_606_400_000_000_000, 1_451_606_400_001_000_001, dtype=np.int64
+ )
+ expected = DatetimeIndex(rng.view("M8[ns]"), freq="ns")
+ tm.assert_index_equal(dti, expected)
+
+ def test_date_range_freq_lower_than_endpoints(self):
+ start = Timestamp("2022-10-19 11:50:44.719781")
+ end = Timestamp("2022-10-19 11:50:47.066458")
+
+ # start and end cannot be cast to "s" unit without lossy rounding,
+ # so we do not allow this in date_range
+ with pytest.raises(ValueError, match="Cannot losslessly convert units"):
+ date_range(start, end, periods=3, unit="s")
+
+ # but we can losslessly cast to "us"
+ dti = date_range(start, end, periods=2, unit="us")
+ rng = np.array(
+ [start.as_unit("us")._value, end.as_unit("us")._value], dtype=np.int64
+ )
+ expected = DatetimeIndex(rng.view("M8[us]"))
+ tm.assert_index_equal(dti, expected)
+
+ def test_date_range_non_nano(self):
+ start = np.datetime64("1066-10-14") # Battle of Hastings
+ end = np.datetime64("2305-07-13") # Jean-Luc Picard's birthday
+
+ dti = date_range(start, end, freq="D", unit="s")
+ assert dti.freq == "D"
+ assert dti.dtype == "M8[s]"
+
+ exp = np.arange(
+ start.astype("M8[s]").view("i8"),
+ (end + 1).astype("M8[s]").view("i8"),
+ 24 * 3600,
+ ).view("M8[s]")
+
+ tm.assert_numpy_array_equal(dti.to_numpy(), exp)
+
+
+class TestDateRangeNonTickFreq:
+ # Tests revolving around less-common (non-Tick) `freq` keywords.
+
+ def test_date_range_custom_business_month_begin(self, unit):
+ hcal = USFederalHolidayCalendar()
+ freq = offsets.CBMonthBegin(calendar=hcal)
+ dti = date_range(start="20120101", end="20130101", freq=freq, unit=unit)
+ assert all(freq.is_on_offset(x) for x in dti)
+
+ expected = DatetimeIndex(
+ [
+ "2012-01-03",
+ "2012-02-01",
+ "2012-03-01",
+ "2012-04-02",
+ "2012-05-01",
+ "2012-06-01",
+ "2012-07-02",
+ "2012-08-01",
+ "2012-09-04",
+ "2012-10-01",
+ "2012-11-01",
+ "2012-12-03",
+ ],
+ dtype=f"M8[{unit}]",
+ freq=freq,
+ )
+ tm.assert_index_equal(dti, expected)
+
+ def test_date_range_custom_business_month_end(self, unit):
+ hcal = USFederalHolidayCalendar()
+ freq = offsets.CBMonthEnd(calendar=hcal)
+ dti = date_range(start="20120101", end="20130101", freq=freq, unit=unit)
+ assert all(freq.is_on_offset(x) for x in dti)
+
+ expected = DatetimeIndex(
+ [
+ "2012-01-31",
+ "2012-02-29",
+ "2012-03-30",
+ "2012-04-30",
+ "2012-05-31",
+ "2012-06-29",
+ "2012-07-31",
+ "2012-08-31",
+ "2012-09-28",
+ "2012-10-31",
+ "2012-11-30",
+ "2012-12-31",
+ ],
+ dtype=f"M8[{unit}]",
+ freq=freq,
+ )
+ tm.assert_index_equal(dti, expected)
+
+ def test_date_range_with_custom_holidays(self, unit):
+ # GH#30593
+ freq = offsets.CustomBusinessHour(start="15:00", holidays=["2020-11-26"])
+ result = date_range(start="2020-11-25 15:00", periods=4, freq=freq, unit=unit)
+ expected = DatetimeIndex(
+ [
+ "2020-11-25 15:00:00",
+ "2020-11-25 16:00:00",
+ "2020-11-27 15:00:00",
+ "2020-11-27 16:00:00",
+ ],
+ dtype=f"M8[{unit}]",
+ freq=freq,
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_date_range_businesshour(self, unit):
+ idx = DatetimeIndex(
+ [
+ "2014-07-04 09:00",
+ "2014-07-04 10:00",
+ "2014-07-04 11:00",
+ "2014-07-04 12:00",
+ "2014-07-04 13:00",
+ "2014-07-04 14:00",
+ "2014-07-04 15:00",
+ "2014-07-04 16:00",
+ ],
+ dtype=f"M8[{unit}]",
+ freq="bh",
+ )
+ rng = date_range("2014-07-04 09:00", "2014-07-04 16:00", freq="bh", unit=unit)
+ tm.assert_index_equal(idx, rng)
+
+ idx = DatetimeIndex(
+ ["2014-07-04 16:00", "2014-07-07 09:00"], dtype=f"M8[{unit}]", freq="bh"
+ )
+ rng = date_range("2014-07-04 16:00", "2014-07-07 09:00", freq="bh", unit=unit)
+ tm.assert_index_equal(idx, rng)
+
+ idx = DatetimeIndex(
+ [
+ "2014-07-04 09:00",
+ "2014-07-04 10:00",
+ "2014-07-04 11:00",
+ "2014-07-04 12:00",
+ "2014-07-04 13:00",
+ "2014-07-04 14:00",
+ "2014-07-04 15:00",
+ "2014-07-04 16:00",
+ "2014-07-07 09:00",
+ "2014-07-07 10:00",
+ "2014-07-07 11:00",
+ "2014-07-07 12:00",
+ "2014-07-07 13:00",
+ "2014-07-07 14:00",
+ "2014-07-07 15:00",
+ "2014-07-07 16:00",
+ "2014-07-08 09:00",
+ "2014-07-08 10:00",
+ "2014-07-08 11:00",
+ "2014-07-08 12:00",
+ "2014-07-08 13:00",
+ "2014-07-08 14:00",
+ "2014-07-08 15:00",
+ "2014-07-08 16:00",
+ ],
+ dtype=f"M8[{unit}]",
+ freq="bh",
+ )
+ rng = date_range("2014-07-04 09:00", "2014-07-08 16:00", freq="bh", unit=unit)
+ tm.assert_index_equal(idx, rng)
+
+ def test_date_range_business_hour2(self, unit):
+ idx1 = date_range(
+ start="2014-07-04 15:00", end="2014-07-08 10:00", freq="bh", unit=unit
+ )
+ idx2 = date_range(start="2014-07-04 15:00", periods=12, freq="bh", unit=unit)
+ idx3 = date_range(end="2014-07-08 10:00", periods=12, freq="bh", unit=unit)
+ expected = DatetimeIndex(
+ [
+ "2014-07-04 15:00",
+ "2014-07-04 16:00",
+ "2014-07-07 09:00",
+ "2014-07-07 10:00",
+ "2014-07-07 11:00",
+ "2014-07-07 12:00",
+ "2014-07-07 13:00",
+ "2014-07-07 14:00",
+ "2014-07-07 15:00",
+ "2014-07-07 16:00",
+ "2014-07-08 09:00",
+ "2014-07-08 10:00",
+ ],
+ dtype=f"M8[{unit}]",
+ freq="bh",
+ )
+ tm.assert_index_equal(idx1, expected)
+ tm.assert_index_equal(idx2, expected)
+ tm.assert_index_equal(idx3, expected)
+
+ idx4 = date_range(
+ start="2014-07-04 15:45", end="2014-07-08 10:45", freq="bh", unit=unit
+ )
+ idx5 = date_range(start="2014-07-04 15:45", periods=12, freq="bh", unit=unit)
+ idx6 = date_range(end="2014-07-08 10:45", periods=12, freq="bh", unit=unit)
+
+ expected2 = expected + Timedelta(minutes=45).as_unit(unit)
+ expected2.freq = "bh"
+ tm.assert_index_equal(idx4, expected2)
+ tm.assert_index_equal(idx5, expected2)
+ tm.assert_index_equal(idx6, expected2)
+
+ def test_date_range_business_hour_short(self, unit):
+ # GH#49835
+ idx4 = date_range(start="2014-07-01 10:00", freq="bh", periods=1, unit=unit)
+ expected4 = DatetimeIndex(["2014-07-01 10:00"], dtype=f"M8[{unit}]", freq="bh")
+ tm.assert_index_equal(idx4, expected4)
+
+ def test_date_range_year_start(self, unit):
+ # see GH#9313
+ rng = date_range("1/1/2013", "7/1/2017", freq="YS", unit=unit)
+ exp = DatetimeIndex(
+ ["2013-01-01", "2014-01-01", "2015-01-01", "2016-01-01", "2017-01-01"],
+ dtype=f"M8[{unit}]",
+ freq="YS",
+ )
+ tm.assert_index_equal(rng, exp)
+
+ def test_date_range_year_end(self, unit):
+ # see GH#9313
+ rng = date_range("1/1/2013", "7/1/2017", freq="YE", unit=unit)
+ exp = DatetimeIndex(
+ ["2013-12-31", "2014-12-31", "2015-12-31", "2016-12-31"],
+ dtype=f"M8[{unit}]",
+ freq="YE",
+ )
+ tm.assert_index_equal(rng, exp)
+
+ def test_date_range_negative_freq_year_end(self, unit):
+ # GH#11018
+ rng = date_range("2011-12-31", freq="-2YE", periods=3, unit=unit)
+ exp = DatetimeIndex(
+ ["2011-12-31", "2009-12-31", "2007-12-31"], dtype=f"M8[{unit}]", freq="-2YE"
+ )
+ tm.assert_index_equal(rng, exp)
+ assert rng.freq == "-2YE"
+
+ def test_date_range_business_year_end_year(self, unit):
+ # see GH#9313
+ rng = date_range("1/1/2013", "7/1/2017", freq="BYE", unit=unit)
+ exp = DatetimeIndex(
+ ["2013-12-31", "2014-12-31", "2015-12-31", "2016-12-30"],
+ dtype=f"M8[{unit}]",
+ freq="BYE",
+ )
+ tm.assert_index_equal(rng, exp)
+
+ def test_date_range_bms(self, unit):
+ # GH#1645
+ result = date_range("1/1/2000", periods=10, freq="BMS", unit=unit)
+
+ expected = DatetimeIndex(
+ [
+ "2000-01-03",
+ "2000-02-01",
+ "2000-03-01",
+ "2000-04-03",
+ "2000-05-01",
+ "2000-06-01",
+ "2000-07-03",
+ "2000-08-01",
+ "2000-09-01",
+ "2000-10-02",
+ ],
+ dtype=f"M8[{unit}]",
+ freq="BMS",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_date_range_semi_month_begin(self, unit):
+ dates = [
+ datetime(2007, 12, 15),
+ datetime(2008, 1, 1),
+ datetime(2008, 1, 15),
+ datetime(2008, 2, 1),
+ datetime(2008, 2, 15),
+ datetime(2008, 3, 1),
+ datetime(2008, 3, 15),
+ datetime(2008, 4, 1),
+ datetime(2008, 4, 15),
+ datetime(2008, 5, 1),
+ datetime(2008, 5, 15),
+ datetime(2008, 6, 1),
+ datetime(2008, 6, 15),
+ datetime(2008, 7, 1),
+ datetime(2008, 7, 15),
+ datetime(2008, 8, 1),
+ datetime(2008, 8, 15),
+ datetime(2008, 9, 1),
+ datetime(2008, 9, 15),
+ datetime(2008, 10, 1),
+ datetime(2008, 10, 15),
+ datetime(2008, 11, 1),
+ datetime(2008, 11, 15),
+ datetime(2008, 12, 1),
+ datetime(2008, 12, 15),
+ ]
+ # ensure generating a range with DatetimeIndex gives same result
+ result = date_range(start=dates[0], end=dates[-1], freq="SMS", unit=unit)
+ exp = DatetimeIndex(dates, dtype=f"M8[{unit}]", freq="SMS")
+ tm.assert_index_equal(result, exp)
+
+ def test_date_range_semi_month_end(self, unit):
+ dates = [
+ datetime(2007, 12, 31),
+ datetime(2008, 1, 15),
+ datetime(2008, 1, 31),
+ datetime(2008, 2, 15),
+ datetime(2008, 2, 29),
+ datetime(2008, 3, 15),
+ datetime(2008, 3, 31),
+ datetime(2008, 4, 15),
+ datetime(2008, 4, 30),
+ datetime(2008, 5, 15),
+ datetime(2008, 5, 31),
+ datetime(2008, 6, 15),
+ datetime(2008, 6, 30),
+ datetime(2008, 7, 15),
+ datetime(2008, 7, 31),
+ datetime(2008, 8, 15),
+ datetime(2008, 8, 31),
+ datetime(2008, 9, 15),
+ datetime(2008, 9, 30),
+ datetime(2008, 10, 15),
+ datetime(2008, 10, 31),
+ datetime(2008, 11, 15),
+ datetime(2008, 11, 30),
+ datetime(2008, 12, 15),
+ datetime(2008, 12, 31),
+ ]
+ # ensure generating a range with DatetimeIndex gives same result
+ result = date_range(start=dates[0], end=dates[-1], freq="SME", unit=unit)
+ exp = DatetimeIndex(dates, dtype=f"M8[{unit}]", freq="SME")
+ tm.assert_index_equal(result, exp)
+
+ def test_date_range_week_of_month(self, unit):
+ # GH#20517
+ # Note the start here is not on_offset for this freq
+ result = date_range(start="20110101", periods=1, freq="WOM-1MON", unit=unit)
+ expected = DatetimeIndex(["2011-01-03"], dtype=f"M8[{unit}]", freq="WOM-1MON")
+ tm.assert_index_equal(result, expected)
+
+ result2 = date_range(start="20110101", periods=2, freq="WOM-1MON", unit=unit)
+ expected2 = DatetimeIndex(
+ ["2011-01-03", "2011-02-07"], dtype=f"M8[{unit}]", freq="WOM-1MON"
+ )
+ tm.assert_index_equal(result2, expected2)
+
+ def test_date_range_week_of_month2(self, unit):
+ # GH#5115, GH#5348
+ result = date_range("2013-1-1", periods=4, freq="WOM-1SAT", unit=unit)
+ expected = DatetimeIndex(
+ ["2013-01-05", "2013-02-02", "2013-03-02", "2013-04-06"],
+ dtype=f"M8[{unit}]",
+ freq="WOM-1SAT",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_date_range_negative_freq_month_end(self, unit):
+ # GH#11018
+ rng = date_range("2011-01-31", freq="-2ME", periods=3, unit=unit)
+ exp = DatetimeIndex(
+ ["2011-01-31", "2010-11-30", "2010-09-30"], dtype=f"M8[{unit}]", freq="-2ME"
+ )
+ tm.assert_index_equal(rng, exp)
+ assert rng.freq == "-2ME"
+
+ def test_date_range_fy5253(self, unit):
+ freq = offsets.FY5253(startingMonth=1, weekday=3, variation="nearest")
+ dti = date_range(
+ start="2013-01-01",
+ periods=2,
+ freq=freq,
+ unit=unit,
+ )
+ expected = DatetimeIndex(
+ ["2013-01-31", "2014-01-30"], dtype=f"M8[{unit}]", freq=freq
+ )
+
+ tm.assert_index_equal(dti, expected)
+
+ @pytest.mark.parametrize(
+ "freqstr,offset",
+ [
+ ("QS", offsets.QuarterBegin(startingMonth=1)),
+ ("BQE", offsets.BQuarterEnd(startingMonth=12)),
+ ("W-SUN", offsets.Week(weekday=6)),
+ ],
+ )
+ def test_date_range_freqstr_matches_offset(self, freqstr, offset):
+ sdate = datetime(1999, 12, 25)
+ edate = datetime(2000, 1, 1)
+
+ idx1 = date_range(start=sdate, end=edate, freq=freqstr)
+ idx2 = date_range(start=sdate, end=edate, freq=offset)
+ assert len(idx1) == len(idx2)
+ assert idx1.freq == idx2.freq
+
+ def test_date_range_partial_day_year_end(self, unit):
+ # GH#56134
+ rng = date_range(
+ start="2021-12-31 00:00:01",
+ end="2023-10-31 00:00:00",
+ freq="YE",
+ unit=unit,
+ )
+ exp = DatetimeIndex(
+ ["2021-12-31 00:00:01", "2022-12-31 00:00:01"],
+ dtype=f"M8[{unit}]",
+ freq="YE",
+ )
+ tm.assert_index_equal(rng, exp)
+
+ def test_date_range_negative_freq_year_end_inbounds(self, unit):
+ # GH#56147
+ rng = date_range(
+ start="2023-10-31 00:00:00",
+ end="2021-10-31 00:00:00",
+ freq="-1YE",
+ unit=unit,
+ )
+ exp = DatetimeIndex(
+ ["2022-12-31 00:00:00", "2021-12-31 00:00:00"],
+ dtype=f"M8[{unit}]",
+ freq="-1YE",
+ )
+ tm.assert_index_equal(rng, exp)
+
+ def test_date_range_tzaware_endpoints_accept_ambiguous(self):
+ # https://github.com/pandas-dev/pandas/issues/52908
+ start = Timestamp("1916-08-01", tz="Europe/Oslo")
+ end = Timestamp("1916-12-01", tz="Europe/Oslo")
+ res = date_range(start, end, freq="MS", ambiguous=True)
+ exp = date_range(
+ "1916-08-01", "1916-12-01", freq="MS", tz="Europe/Oslo", ambiguous=True
+ )
+ tm.assert_index_equal(res, exp)
+
+ def test_date_range_tzaware_endpoints_accept_nonexistent(self):
+ # Europe/London spring-forward: 2015-03-29 01:30 does not exist.
+ tz = "Europe/London"
+ start = Timestamp("2015-03-28 01:30", tz=tz)
+ end = Timestamp("2015-03-30 01:30", tz=tz)
+
+ result = date_range(start, end, freq="D", nonexistent="shift_forward")
+
+ # Build expected by generating naive daily times, then tz_localize so
+ # the nonexistent handling is applied during localization.
+ expected = date_range(
+ "2015-03-28 01:30", "2015-03-30 01:30", freq="D"
+ ).tz_localize(tz, nonexistent="shift_forward")
+ tm.assert_index_equal(result, expected)
+
+
+class TestDateRangeUnitInference:
+ def test_date_range_unit_inference_matching_unit(self, unit):
+ start = Timestamp("2025-11-25").as_unit(unit)
+ end = Timestamp("2025-11-26").as_unit(unit)
+
+ dti = date_range(start, end, freq="D")
+ assert dti.unit == unit
+
+ def test_date_range_unit_inference_mismatched_unit(self, unit):
+ start = Timestamp("2025-11-25").as_unit(unit)
+ end = Timestamp("2025-11-26").as_unit("s")
+
+ dti = date_range(start, end, freq="D")
+ assert dti.unit == unit
+
+ dti = date_range(start, end.as_unit("ns"), freq="D")
+ assert dti.unit == "ns"
+
+ def test_date_range_unit_inference_tick(self):
+ start = Timestamp("2025-11-25").as_unit("ms")
+ end = Timestamp("2025-11-26").as_unit("s")
+
+ dti = date_range(start, end, freq="2000000us")
+ assert dti.unit == "us"
+
+ dti = date_range(start, end.as_unit("ns"), freq="2000000us")
+ assert dti.unit == "ns"
+
+ def test_date_range_unit_inference_dateoffset_freq(self):
+ start = Timestamp("2025-11-25 09:00:00").as_unit("s")
+ end = Timestamp("2025-11-25 09:00:02").as_unit("s")
+
+ off = DateOffset(microseconds=2_000_000)
+ dti = date_range(start, end, freq=off)
+ assert dti.unit == "us"
+
+ off = DateOffset(milliseconds=2)
+ dti = date_range(start, end, freq=off)
+ assert dti.unit == "ms"
+
+ end2 = start + Timedelta(microseconds=2).as_unit("us")
+ off = DateOffset(nanoseconds=2)
+ dti = date_range(start, end2, freq=off)
+ assert dti.unit == "ns"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_datetime.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1ce0c816152b398e9b74c38d736584d26158e4a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_datetime.py
@@ -0,0 +1,155 @@
+import datetime as dt
+from datetime import date
+
+import numpy as np
+import pytest
+
+from pandas.compat.numpy import np_long
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ Timestamp,
+ date_range,
+ offsets,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeIndex:
+ def test_is_(self):
+ dti = date_range(start="1/1/2005", end="12/1/2005", freq="ME")
+ assert dti.is_(dti)
+ assert dti.is_(dti.view())
+ assert not dti.is_(dti.copy())
+
+ def test_time_overflow_for_32bit_machines(self):
+ # GH8943. On some machines NumPy defaults to np.int32 (for example,
+ # 32-bit Linux machines). In the function _generate_regular_range
+ # found in tseries/index.py, `periods` gets multiplied by `strides`
+ # (which has value 1e9) and since the max value for np.int32 is ~2e9,
+ # and since those machines won't promote np.int32 to np.int64, we get
+ # overflow.
+ periods = np_long(1000)
+
+ idx1 = date_range(start="2000", periods=periods, freq="s")
+ assert len(idx1) == periods
+
+ idx2 = date_range(end="2000", periods=periods, freq="s")
+ assert len(idx2) == periods
+
+ def test_nat(self):
+ assert DatetimeIndex([np.nan])[0] is pd.NaT
+
+ def test_week_of_month_frequency(self):
+ # GH 5348: "ValueError: Could not evaluate WOM-1SUN" shouldn't raise
+ d1 = date(2002, 9, 1)
+ d2 = date(2013, 10, 27)
+ d3 = date(2012, 9, 30)
+ idx1 = DatetimeIndex([d1, d2])
+ idx2 = DatetimeIndex([d3])
+ result_append = idx1.append(idx2)
+ expected = DatetimeIndex([d1, d2, d3])
+ tm.assert_index_equal(result_append, expected)
+ result_union = idx1.union(idx2)
+ expected = DatetimeIndex([d1, d3, d2])
+ tm.assert_index_equal(result_union, expected)
+
+ def test_append_nondatetimeindex(self):
+ rng = date_range("1/1/2000", periods=10)
+ idx = Index(["a", "b", "c", "d"])
+
+ result = rng.append(idx)
+ assert isinstance(result[0], Timestamp)
+
+ def test_misc_coverage(self):
+ rng = date_range("1/1/2000", periods=5)
+ result = rng.groupby(rng.day)
+ assert isinstance(next(iter(result.values()))[0], Timestamp)
+
+ # TODO: belongs in frame groupby tests?
+ def test_groupby_function_tuple_1677(self):
+ df = DataFrame(
+ np.random.default_rng(2).random(100),
+ index=date_range("1/1/2000", periods=100),
+ )
+ monthly_group = df.groupby(lambda x: (x.year, x.month))
+
+ result = monthly_group.mean()
+ assert isinstance(result.index[0], tuple)
+
+ def assert_index_parameters(self, index):
+ assert index.freq == "40960ns"
+ assert index.inferred_freq == "40960ns"
+
+ def test_ns_index(self):
+ nsamples = 400
+ ns = int(1e9 / 24414)
+ dtstart = np.datetime64("2012-09-20T00:00:00")
+
+ dt = dtstart + np.arange(nsamples) * np.timedelta64(ns, "ns")
+ freq = ns * offsets.Nano()
+ index = DatetimeIndex(dt, freq=freq, name="time")
+ self.assert_index_parameters(index)
+
+ new_index = date_range(start=index[0], end=index[-1], freq=index.freq)
+ self.assert_index_parameters(new_index)
+
+ def test_asarray_tz_naive(self):
+ # This shouldn't produce a warning.
+ idx = date_range("2000", periods=2, unit="ns")
+ # M8[ns] by default
+ result = np.asarray(idx)
+
+ expected = np.array(["2000-01-01", "2000-01-02"], dtype="M8[ns]")
+ tm.assert_numpy_array_equal(result, expected)
+
+ # optionally, object
+ result = np.asarray(idx, dtype=object)
+
+ expected = np.array([Timestamp("2000-01-01"), Timestamp("2000-01-02")])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_asarray_tz_aware(self):
+ tz = "US/Central"
+ idx = date_range("2000", periods=2, tz=tz, unit="ns")
+ expected = np.array(["2000-01-01T06", "2000-01-02T06"], dtype="M8[ns]")
+ result = np.asarray(idx, dtype="datetime64[ns]")
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ # Old behavior with no warning
+ result = np.asarray(idx, dtype="M8[ns]")
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ # Future behavior with no warning
+ expected = np.array(
+ [Timestamp("2000-01-01", tz=tz), Timestamp("2000-01-02", tz=tz)]
+ )
+ result = np.asarray(idx, dtype=object)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("freq", ["2H", "2BH", "2S"])
+ def test_CBH_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ date_range(dt.datetime(2022, 12, 11), dt.datetime(2022, 12, 13), freq=freq)
+
+ @pytest.mark.parametrize("freq", ["2BM", "1bm", "2BQ", "1BQ-MAR", "2BY-JUN", "1by"])
+ def test_BM_BQ_BY_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ date_range(start="2016-02-21", end="2016-08-21", freq=freq)
+
+ @pytest.mark.parametrize("freq", ["2BA-MAR", "1BAS-MAY", "2AS-AUG"])
+ def test_BA_BAS_raises(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ date_range(start="2016-02-21", end="2016-08-21", freq=freq)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_formats.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_formats.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4e0a630433352d3fdf169b88ef3814ab0a0a967
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_formats.py
@@ -0,0 +1,281 @@
+from datetime import (
+ datetime,
+ timezone,
+)
+
+import dateutil.tz
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ NaT,
+ Series,
+)
+import pandas._testing as tm
+
+
+def test_get_values_for_csv():
+ index = pd.date_range(freq="1D", periods=3, start="2017-01-01")
+
+ # First, with no arguments.
+ expected = np.array(["2017-01-01", "2017-01-02", "2017-01-03"], dtype=object)
+
+ result = index._get_values_for_csv()
+ tm.assert_numpy_array_equal(result, expected)
+
+ # No NaN values, so na_rep has no effect
+ result = index._get_values_for_csv(na_rep="pandas")
+ tm.assert_numpy_array_equal(result, expected)
+
+ # Make sure date formatting works
+ expected = np.array(["01-2017-01", "01-2017-02", "01-2017-03"], dtype=object)
+
+ result = index._get_values_for_csv(date_format="%m-%Y-%d")
+ tm.assert_numpy_array_equal(result, expected)
+
+ # NULL object handling should work
+ index = DatetimeIndex(["2017-01-01", NaT, "2017-01-03"])
+ expected = np.array(["2017-01-01", "NaT", "2017-01-03"], dtype=object)
+
+ result = index._get_values_for_csv(na_rep="NaT")
+ tm.assert_numpy_array_equal(result, expected)
+
+ expected = np.array(["2017-01-01", "pandas", "2017-01-03"], dtype=object)
+
+ result = index._get_values_for_csv(na_rep="pandas")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = index._get_values_for_csv(na_rep="NaT", date_format="%Y-%m-%d %H:%M:%S.%f")
+ expected = np.array(
+ ["2017-01-01 00:00:00.000000", "NaT", "2017-01-03 00:00:00.000000"],
+ dtype=object,
+ )
+ tm.assert_numpy_array_equal(result, expected)
+
+ # invalid format
+ result = index._get_values_for_csv(na_rep="NaT", date_format="foo")
+ expected = np.array(["foo", "NaT", "foo"], dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestDatetimeIndexRendering:
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_dti_with_timezone_repr(self, tzstr):
+ rng = pd.date_range("4/13/2010", "5/6/2010")
+
+ rng_eastern = rng.tz_localize(tzstr)
+
+ rng_repr = repr(rng_eastern)
+ assert "2010-04-13 00:00:00" in rng_repr
+
+ def test_dti_repr_dates(self):
+ text = str(pd.to_datetime([datetime(2013, 1, 1), datetime(2014, 1, 1)]))
+ assert "['2013-01-01'," in text
+ assert ", '2014-01-01']" in text
+
+ def test_dti_repr_mixed(self):
+ text = str(
+ pd.to_datetime(
+ [datetime(2013, 1, 1), datetime(2014, 1, 1, 12), datetime(2014, 1, 1)]
+ )
+ )
+ assert "'2013-01-01 00:00:00'," in text
+ assert "'2014-01-01 00:00:00']" in text
+
+ def test_dti_repr_short(self):
+ dr = pd.date_range(start="1/1/2012", periods=1)
+ repr(dr)
+
+ dr = pd.date_range(start="1/1/2012", periods=2)
+ repr(dr)
+
+ dr = pd.date_range(start="1/1/2012", periods=3)
+ repr(dr)
+
+ @pytest.mark.parametrize(
+ "dates, freq, expected_repr",
+ [
+ (
+ ["2012-01-01 00:00:00"],
+ "60min",
+ (
+ "DatetimeIndex(['2012-01-01 00:00:00'], "
+ "dtype='datetime64[ns]', freq='60min')"
+ ),
+ ),
+ (
+ ["2012-01-01 00:00:00", "2012-01-01 01:00:00"],
+ "60min",
+ "DatetimeIndex(['2012-01-01 00:00:00', '2012-01-01 01:00:00'], "
+ "dtype='datetime64[ns]', freq='60min')",
+ ),
+ (
+ ["2012-01-01"],
+ "24h",
+ "DatetimeIndex(['2012-01-01'], dtype='datetime64[ns]', freq='24h')",
+ ),
+ ],
+ )
+ def test_dti_repr_time_midnight(self, dates, freq, expected_repr, unit):
+ # GH53634
+ dti = DatetimeIndex(dates, freq).as_unit(unit)
+ actual_repr = repr(dti)
+ assert actual_repr == expected_repr.replace("[ns]", f"[{unit}]")
+
+ def test_dti_representation(self, unit):
+ idxs = []
+ idxs.append(DatetimeIndex([], freq="D"))
+ idxs.append(DatetimeIndex(["2011-01-01"], freq="D"))
+ idxs.append(DatetimeIndex(["2011-01-01", "2011-01-02"], freq="D"))
+ idxs.append(DatetimeIndex(["2011-01-01", "2011-01-02", "2011-01-03"], freq="D"))
+ idxs.append(
+ DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", "2011-01-01 11:00"],
+ freq="h",
+ tz="Asia/Tokyo",
+ )
+ )
+ idxs.append(
+ DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", NaT], tz="US/Eastern"
+ )
+ )
+ idxs.append(
+ DatetimeIndex(["2011-01-01 09:00", "2011-01-01 10:00", NaT], tz="UTC")
+ )
+
+ exp = []
+ exp.append("DatetimeIndex([], dtype='datetime64[ns]', freq='D')")
+ exp.append("DatetimeIndex(['2011-01-01'], dtype='datetime64[ns]', freq='D')")
+ exp.append(
+ "DatetimeIndex(['2011-01-01', '2011-01-02'], "
+ "dtype='datetime64[ns]', freq='D')"
+ )
+ exp.append(
+ "DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03'], "
+ "dtype='datetime64[ns]', freq='D')"
+ )
+ exp.append(
+ "DatetimeIndex(['2011-01-01 09:00:00+09:00', "
+ "'2011-01-01 10:00:00+09:00', '2011-01-01 11:00:00+09:00']"
+ ", dtype='datetime64[ns, Asia/Tokyo]', freq='h')"
+ )
+ exp.append(
+ "DatetimeIndex(['2011-01-01 09:00:00-05:00', "
+ "'2011-01-01 10:00:00-05:00', 'NaT'], "
+ "dtype='datetime64[ns, US/Eastern]', freq=None)"
+ )
+ exp.append(
+ "DatetimeIndex(['2011-01-01 09:00:00+00:00', "
+ "'2011-01-01 10:00:00+00:00', 'NaT'], "
+ "dtype='datetime64[ns, UTC]', freq=None)"
+ ""
+ )
+
+ with pd.option_context("display.width", 300):
+ for index, expected in zip(idxs, exp):
+ index = index.as_unit(unit)
+ expected = expected.replace("[ns", f"[{unit}")
+ result = repr(index)
+ assert result == expected
+ result = str(index)
+ assert result == expected
+
+ # TODO: this is a Series.__repr__ test
+ def test_dti_representation_to_series(self, unit):
+ idx1 = DatetimeIndex([], freq="D")
+ idx2 = DatetimeIndex(["2011-01-01"], freq="D")
+ idx3 = DatetimeIndex(["2011-01-01", "2011-01-02"], freq="D")
+ idx4 = DatetimeIndex(["2011-01-01", "2011-01-02", "2011-01-03"], freq="D")
+ idx5 = DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", "2011-01-01 11:00"],
+ freq="h",
+ tz="Asia/Tokyo",
+ )
+ idx6 = DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", NaT], tz="US/Eastern"
+ )
+ idx7 = DatetimeIndex(["2011-01-01 09:00", "2011-01-02 10:15"])
+
+ exp1 = """Series([], dtype: datetime64[ns])"""
+
+ exp2 = "0 2011-01-01\ndtype: datetime64[ns]"
+
+ exp3 = "0 2011-01-01\n1 2011-01-02\ndtype: datetime64[ns]"
+
+ exp4 = "0 2011-01-01\n1 2011-01-02\n2 2011-01-03\ndtype: datetime64[ns]"
+
+ exp5 = (
+ "0 2011-01-01 09:00:00+09:00\n"
+ "1 2011-01-01 10:00:00+09:00\n"
+ "2 2011-01-01 11:00:00+09:00\n"
+ "dtype: datetime64[ns, Asia/Tokyo]"
+ )
+
+ exp6 = (
+ "0 2011-01-01 09:00:00-05:00\n"
+ "1 2011-01-01 10:00:00-05:00\n"
+ "2 NaT\n"
+ "dtype: datetime64[ns, US/Eastern]"
+ )
+
+ exp7 = "0 2011-01-01 09:00:00\n1 2011-01-02 10:15:00\ndtype: datetime64[ns]"
+
+ with pd.option_context("display.width", 300):
+ for idx, expected in zip(
+ [idx1, idx2, idx3, idx4, idx5, idx6, idx7],
+ [exp1, exp2, exp3, exp4, exp5, exp6, exp7],
+ ):
+ ser = Series(idx.as_unit(unit))
+ result = repr(ser)
+ assert result == expected.replace("[ns", f"[{unit}")
+
+ def test_dti_summary(self):
+ # GH#9116
+ idx1 = DatetimeIndex([], freq="D")
+ idx2 = DatetimeIndex(["2011-01-01"], freq="D")
+ idx3 = DatetimeIndex(["2011-01-01", "2011-01-02"], freq="D")
+ idx4 = DatetimeIndex(["2011-01-01", "2011-01-02", "2011-01-03"], freq="D")
+ idx5 = DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", "2011-01-01 11:00"],
+ freq="h",
+ tz="Asia/Tokyo",
+ )
+ idx6 = DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", NaT], tz="US/Eastern"
+ )
+
+ exp1 = "DatetimeIndex: 0 entries\nFreq: D"
+
+ exp2 = "DatetimeIndex: 1 entries, 2011-01-01 to 2011-01-01\nFreq: D"
+
+ exp3 = "DatetimeIndex: 2 entries, 2011-01-01 to 2011-01-02\nFreq: D"
+
+ exp4 = "DatetimeIndex: 3 entries, 2011-01-01 to 2011-01-03\nFreq: D"
+
+ exp5 = (
+ "DatetimeIndex: 3 entries, 2011-01-01 09:00:00+09:00 "
+ "to 2011-01-01 11:00:00+09:00\n"
+ "Freq: h"
+ )
+
+ exp6 = """DatetimeIndex: 3 entries, 2011-01-01 09:00:00-05:00 to NaT"""
+
+ for idx, expected in zip(
+ [idx1, idx2, idx3, idx4, idx5, idx6], [exp1, exp2, exp3, exp4, exp5, exp6]
+ ):
+ result = idx._summary()
+ assert result == expected
+
+ @pytest.mark.parametrize("tz", [None, timezone.utc, dateutil.tz.tzutc()])
+ @pytest.mark.parametrize("freq", ["B", "C"])
+ def test_dti_business_repr_etc_smoke(self, tz, freq):
+ # only really care that it works
+ dti = pd.bdate_range(
+ datetime(2009, 1, 1), datetime(2010, 1, 1), tz=tz, freq=freq
+ )
+ repr(dti)
+ dti._summary()
+ dti[2:2]._summary()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_freq_attr.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_freq_attr.py
new file mode 100644
index 0000000000000000000000000000000000000000..5cddf56cd1c73b3c00d8b59c6f99095ba9a704fb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_freq_attr.py
@@ -0,0 +1,61 @@
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ date_range,
+)
+
+from pandas.tseries.offsets import (
+ BDay,
+ DateOffset,
+ Day,
+ Hour,
+)
+
+
+class TestFreq:
+ def test_freq_setter_errors(self):
+ # GH#20678
+ idx = DatetimeIndex(["20180101", "20180103", "20180105"])
+
+ # setting with an incompatible freq
+ msg = (
+ "Inferred frequency 2D from passed values does not conform to "
+ "passed frequency 5D"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx._data.freq = "5D"
+
+ # setting with non-freq string
+ with pytest.raises(ValueError, match="Invalid frequency"):
+ idx._data.freq = "foo"
+
+ @pytest.mark.parametrize("values", [["20180101", "20180103", "20180105"], []])
+ @pytest.mark.parametrize("freq", ["2D", Day(2), "2B", BDay(2), "48h", Hour(48)])
+ @pytest.mark.parametrize("tz", [None, "US/Eastern"])
+ def test_freq_setter(self, values, freq, tz):
+ # GH#20678
+ idx = DatetimeIndex(values, tz=tz)
+
+ # can set to an offset, converting from string if necessary
+ idx._data.freq = freq
+ assert idx.freq == freq
+ assert isinstance(idx.freq, DateOffset)
+
+ # can reset to None
+ idx._data.freq = None
+ assert idx.freq is None
+
+ def test_freq_view_safe(self):
+ # Setting the freq for one DatetimeIndex shouldn't alter the freq
+ # for another that views the same data
+
+ dti = date_range("2016-01-01", periods=5)
+ dta = dti._data
+
+ dti2 = DatetimeIndex(dta)._with_freq(None)
+ assert dti2.freq is None
+
+ # Original was not altered
+ assert dti.freq == "D"
+ assert dta.freq == "D"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..f09d582b01d872e6a0799b56a662432cdcfbbe3d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_indexing.py
@@ -0,0 +1,777 @@
+from datetime import (
+ date,
+ datetime,
+ time,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+from pandas._libs import index as libindex
+from pandas.compat.numpy import np_long
+from pandas.errors import Pandas4Warning
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Index,
+ Timestamp,
+ bdate_range,
+ date_range,
+ notna,
+)
+import pandas._testing as tm
+
+from pandas.tseries.frequencies import to_offset
+
+START, END = datetime(2009, 1, 1), datetime(2010, 1, 1)
+
+
+class TestGetItem:
+ def test_getitem_slice_keeps_name(self):
+ # GH4226
+ st = Timestamp("2013-07-01 00:00:00", tz="America/Los_Angeles")
+ et = Timestamp("2013-07-02 00:00:00", tz="America/Los_Angeles")
+ dr = date_range(st, et, freq="h", name="timebucket")
+ assert dr[1:].name == dr.name
+
+ @pytest.mark.parametrize("tz", [None, "Asia/Tokyo"])
+ def test_getitem(self, tz):
+ idx = date_range("2011-01-01", "2011-01-31", freq="D", tz=tz, name="idx")
+
+ result = idx[0]
+ assert result == Timestamp("2011-01-01", tz=idx.tz)
+
+ result = idx[0:5]
+ expected = date_range(
+ "2011-01-01", "2011-01-05", freq="D", tz=idx.tz, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx[0:10:2]
+ expected = date_range(
+ "2011-01-01", "2011-01-09", freq="2D", tz=idx.tz, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx[-20:-5:3]
+ expected = date_range(
+ "2011-01-12", "2011-01-24", freq="3D", tz=idx.tz, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx[4::-1]
+ expected = DatetimeIndex(
+ ["2011-01-05", "2011-01-04", "2011-01-03", "2011-01-02", "2011-01-01"],
+ dtype=idx.dtype,
+ freq="-1D",
+ name="idx",
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ @pytest.mark.parametrize("freq", ["B", "C"])
+ def test_dti_business_getitem(self, freq):
+ rng = bdate_range(START, END, freq=freq)
+ smaller = rng[:5]
+ exp = DatetimeIndex(rng.view(np.ndarray)[:5], freq=freq)
+ tm.assert_index_equal(smaller, exp)
+ assert smaller.freq == exp.freq
+ assert smaller.freq == rng.freq
+
+ sliced = rng[::5]
+ assert sliced.freq == to_offset(freq) * 5
+
+ fancy_indexed = rng[[4, 3, 2, 1, 0]]
+ assert len(fancy_indexed) == 5
+ assert isinstance(fancy_indexed, DatetimeIndex)
+ assert fancy_indexed.freq is None
+
+ # 32-bit vs. 64-bit platforms
+ assert rng[4] == rng[np_long(4)]
+
+ @pytest.mark.parametrize("freq", ["B", "C"])
+ def test_dti_business_getitem_matplotlib_hackaround(self, freq):
+ rng = bdate_range(START, END, freq=freq)
+ with pytest.raises(ValueError, match="Multi-dimensional indexing"):
+ # GH#30588 multi-dimensional indexing deprecated
+ rng[:, None]
+
+ def test_getitem_int_list(self):
+ dti = date_range(start="1/1/2005", end="12/1/2005", freq="ME")
+ dti2 = dti[[1, 3, 5]]
+
+ v1 = dti2[0]
+ v2 = dti2[1]
+ v3 = dti2[2]
+
+ assert v1 == Timestamp("2/28/2005")
+ assert v2 == Timestamp("4/30/2005")
+ assert v3 == Timestamp("6/30/2005")
+
+ # getitem with non-slice drops freq
+ assert dti2.freq is None
+
+
+class TestWhere:
+ @pytest.mark.parametrize("is_td", [True, False])
+ def test_where_freq_invalidation(self, is_td):
+ # GH#24555
+ index = date_range("20130101", periods=3, tz="US/Eastern")
+ if is_td:
+ index = index - index[0]
+ other = Index([pd.NaT, pd.NaT, *index[2:].tolist()])
+
+ result = index.where(notna(other), other)
+ assert result.freq is None
+
+ def test_where_doesnt_retain_freq(self):
+ dti = date_range("20130101", periods=3, freq="D", name="idx")
+ cond = [True, True, False]
+ expected = DatetimeIndex([dti[0], dti[1], dti[0]], freq=None, name="idx")
+
+ result = dti.where(cond, dti[::-1])
+ tm.assert_index_equal(result, expected)
+
+ def test_where_other(self):
+ # other is ndarray or Index
+ i = date_range("20130101", periods=3, tz="US/Eastern")
+
+ for arr in [np.nan, pd.NaT]:
+ result = i.where(notna(i), other=arr)
+ expected = i
+ tm.assert_index_equal(result, expected)
+
+ i2 = i.copy()
+ i2 = Index([pd.NaT, pd.NaT, *i[2:].tolist()])
+ result = i.where(notna(i2), i2)
+ tm.assert_index_equal(result, i2)
+
+ i2 = i.copy()
+ i2 = Index([pd.NaT, pd.NaT, *i[2:].tolist()])
+ result = i.where(notna(i2), i2._values)
+ tm.assert_index_equal(result, i2)
+
+ def test_where_invalid_dtypes(self):
+ dti = date_range("20130101", periods=3, tz="US/Eastern", unit="ns")
+
+ tail = dti[2:].tolist()
+ i2 = Index([pd.NaT, pd.NaT, *tail])
+
+ mask = notna(i2)
+
+ # passing tz-naive ndarray to tzaware DTI
+ result = dti.where(mask, i2.values)
+ expected = Index([pd.NaT.asm8, pd.NaT.asm8, *tail], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ # passing tz-aware DTI to tznaive DTI
+ naive = dti.tz_localize(None)
+ result = naive.where(mask, i2)
+ expected = Index([i2[0], i2[1], *naive[2:].tolist()], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ pi = i2.tz_localize(None).to_period("D")
+ result = dti.where(mask, pi)
+ expected = Index([pi[0], pi[1], *tail], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ tda = i2.asi8.view("timedelta64[ns]")
+ result = dti.where(mask, tda)
+ expected = Index([tda[0], tda[1], *tail], dtype=object)
+ assert isinstance(expected[0], np.timedelta64)
+ tm.assert_index_equal(result, expected)
+
+ result = dti.where(mask, i2.asi8)
+ expected = Index([pd.NaT._value, pd.NaT._value, *tail], dtype=object)
+ assert isinstance(expected[0], int)
+ tm.assert_index_equal(result, expected)
+
+ # non-matching scalar
+ td = pd.Timedelta(days=4)
+ result = dti.where(mask, td)
+ expected = Index([td, td, *tail], dtype=object)
+ assert expected[0] is td
+ tm.assert_index_equal(result, expected)
+
+ def test_where_mismatched_nat(self, tz_aware_fixture):
+ tz = tz_aware_fixture
+ dti = date_range("2013-01-01", periods=3, tz=tz)
+ cond = np.array([True, False, True])
+
+ tdnat = np.timedelta64("NaT", "ns")
+ expected = Index([dti[0], tdnat, dti[2]], dtype=object)
+ assert expected[1] is tdnat
+
+ result = dti.where(cond, tdnat)
+ tm.assert_index_equal(result, expected)
+
+ def test_where_tz(self):
+ i = date_range("20130101", periods=3, tz="US/Eastern")
+ result = i.where(notna(i))
+ expected = i
+ tm.assert_index_equal(result, expected)
+
+ i2 = i.copy()
+ i2 = Index([pd.NaT, pd.NaT, *i[2:].tolist()])
+ result = i.where(notna(i2))
+ expected = i2
+ tm.assert_index_equal(result, expected)
+
+
+class TestTake:
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_dti_take_dont_lose_meta(self, tzstr):
+ rng = date_range("1/1/2000", periods=20, tz=tzstr)
+
+ result = rng.take(range(5))
+ assert result.tz == rng.tz
+ assert result.freq == rng.freq
+
+ def test_take_nan_first_datetime(self):
+ index = DatetimeIndex([pd.NaT, Timestamp("20130101"), Timestamp("20130102")])
+ result = index.take([-1, 0, 1])
+ expected = DatetimeIndex([index[-1], index[0], index[1]])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("tz", [None, "Asia/Tokyo"])
+ def test_take(self, tz):
+ # GH#10295
+ idx = date_range("2011-01-01", "2011-01-31", freq="D", name="idx", tz=tz)
+
+ result = idx.take([0])
+ assert result == Timestamp("2011-01-01", tz=idx.tz)
+
+ result = idx.take([0, 1, 2])
+ expected = date_range(
+ "2011-01-01", "2011-01-03", freq="D", tz=idx.tz, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx.take([0, 2, 4])
+ expected = date_range(
+ "2011-01-01", "2011-01-05", freq="2D", tz=idx.tz, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx.take([7, 4, 1])
+ expected = date_range(
+ "2011-01-08", "2011-01-02", freq="-3D", tz=idx.tz, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx.take([3, 2, 5])
+ expected = DatetimeIndex(
+ ["2011-01-04", "2011-01-03", "2011-01-06"],
+ dtype=idx.dtype,
+ freq=None,
+ name="idx",
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq is None
+
+ result = idx.take([-3, 2, 5])
+ expected = DatetimeIndex(
+ ["2011-01-29", "2011-01-03", "2011-01-06"],
+ dtype=idx.dtype,
+ freq=None,
+ name="idx",
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq is None
+
+ def test_take_invalid_kwargs(self):
+ idx = date_range("2011-01-01", "2011-01-31", freq="D", name="idx")
+ indices = [1, 6, 5, 9, 10, 13, 15, 3]
+
+ msg = r"take\(\) got an unexpected keyword argument 'foo'"
+ with pytest.raises(TypeError, match=msg):
+ idx.take(indices, foo=2)
+
+ msg = "the 'out' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, out=indices)
+
+ msg = "the 'mode' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, mode="clip")
+
+ # TODO: This method came from test_datetime; de-dup with version above
+ @pytest.mark.parametrize("tz", [None, "US/Eastern", "Asia/Tokyo"])
+ def test_take2(self, tz):
+ dates = [
+ datetime(2010, 1, 1, 14),
+ datetime(2010, 1, 1, 15),
+ datetime(2010, 1, 1, 17),
+ datetime(2010, 1, 1, 21),
+ ]
+
+ idx = date_range(
+ start="2010-01-01 09:00",
+ end="2010-02-01 09:00",
+ freq="h",
+ tz=tz,
+ name="idx",
+ )
+ expected = DatetimeIndex(dates, freq=None, name="idx", dtype=idx.dtype)
+
+ taken1 = idx.take([5, 6, 8, 12])
+ taken2 = idx[[5, 6, 8, 12]]
+
+ for taken in [taken1, taken2]:
+ tm.assert_index_equal(taken, expected)
+ assert isinstance(taken, DatetimeIndex)
+ assert taken.freq is None
+ assert taken.tz == expected.tz
+ assert taken.name == expected.name
+
+ def test_take_fill_value(self):
+ # GH#12631
+ idx = DatetimeIndex(["2011-01-01", "2011-02-01", "2011-03-01"], name="xxx")
+ result = idx.take(np.array([1, 0, -1]))
+ expected = DatetimeIndex(["2011-02-01", "2011-01-01", "2011-03-01"], name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ # fill_value
+ result = idx.take(np.array([1, 0, -1]), fill_value=True)
+ expected = DatetimeIndex(["2011-02-01", "2011-01-01", "NaT"], name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = DatetimeIndex(["2011-02-01", "2011-01-01", "2011-03-01"], name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ msg = (
+ "When allow_fill=True and fill_value is not None, all indices must be >= -1"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ msg = "out of bounds"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
+
+ def test_take_fill_value_with_timezone(self):
+ idx = DatetimeIndex(
+ ["2011-01-01", "2011-02-01", "2011-03-01"], name="xxx", tz="US/Eastern"
+ )
+ result = idx.take(np.array([1, 0, -1]))
+ expected = DatetimeIndex(
+ ["2011-02-01", "2011-01-01", "2011-03-01"], name="xxx", tz="US/Eastern"
+ )
+ tm.assert_index_equal(result, expected)
+
+ # fill_value
+ result = idx.take(np.array([1, 0, -1]), fill_value=True)
+ expected = DatetimeIndex(
+ ["2011-02-01", "2011-01-01", "NaT"], name="xxx", tz="US/Eastern"
+ )
+ tm.assert_index_equal(result, expected)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = DatetimeIndex(
+ ["2011-02-01", "2011-01-01", "2011-03-01"], name="xxx", tz="US/Eastern"
+ )
+ tm.assert_index_equal(result, expected)
+
+ msg = (
+ "When allow_fill=True and fill_value is not None, all indices must be >= -1"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ msg = "out of bounds"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
+
+
+class TestGetLoc:
+ def test_get_loc_key_unit_mismatch(self):
+ idx = date_range("2000-01-01", periods=3)
+ key = idx[1].as_unit("ms")
+ loc = idx.get_loc(key)
+ assert loc == 1
+ assert key in idx
+
+ def test_get_loc_key_unit_mismatch_not_castable(self):
+ dta = date_range("2000-01-01", periods=3)._data.astype("M8[s]")
+ dti = DatetimeIndex(dta)
+ key = dta[0].as_unit("ns") + pd.Timedelta(1)
+
+ with pytest.raises(
+ KeyError, match=r"Timestamp\('2000-01-01 00:00:00.000000001'\)"
+ ):
+ dti.get_loc(key)
+
+ assert key not in dti
+
+ def test_get_loc_time_obj(self):
+ # time indexing
+ idx = date_range("2000-01-01", periods=24, freq="h")
+
+ result = idx.get_loc(time(12))
+ expected = np.array([12])
+ tm.assert_numpy_array_equal(result, expected, check_dtype=False)
+
+ result = idx.get_loc(time(12, 30))
+ expected = np.array([])
+ tm.assert_numpy_array_equal(result, expected, check_dtype=False)
+
+ @pytest.mark.parametrize("offset", [-10, 10])
+ def test_get_loc_time_obj2(self, monkeypatch, offset):
+ # GH#8667
+ size_cutoff = 50
+ n = size_cutoff + offset
+ key = time(15, 11, 30)
+ start = key.hour * 3600 + key.minute * 60 + key.second
+ step = 24 * 3600
+
+ with monkeypatch.context():
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", size_cutoff)
+ idx = date_range("2014-11-26", periods=n, freq="s")
+ ts = pd.Series(np.random.default_rng(2).standard_normal(n), index=idx)
+ locs = np.arange(start, n, step, dtype=np.intp)
+
+ result = ts.index.get_loc(key)
+ tm.assert_numpy_array_equal(result, locs)
+ tm.assert_series_equal(ts[key], ts.iloc[locs])
+
+ left, right = ts.copy(), ts.copy()
+ left[key] *= -10
+ right.iloc[locs] *= -10
+ tm.assert_series_equal(left, right)
+
+ def test_get_loc_time_nat(self):
+ # GH#35114
+ # Case where key's total microseconds happens to match iNaT % 1e6 // 1000
+ tic = time(minute=12, second=43, microsecond=145224)
+ dti = DatetimeIndex([pd.NaT])
+
+ loc = dti.get_loc(tic)
+ expected = np.array([], dtype=np.intp)
+ tm.assert_numpy_array_equal(loc, expected)
+
+ def test_get_loc_nat(self):
+ # GH#20464
+ index = DatetimeIndex(["1/3/2000", "NaT"])
+ assert index.get_loc(pd.NaT) == 1
+
+ assert index.get_loc(None) == 1
+
+ assert index.get_loc(np.nan) == 1
+
+ assert index.get_loc(pd.NA) == 1
+
+ assert index.get_loc(np.datetime64("NaT")) == 1
+
+ with pytest.raises(KeyError, match="NaT"):
+ index.get_loc(np.timedelta64("NaT"))
+
+ @pytest.mark.parametrize("key", [pd.Timedelta(0), pd.Timedelta(1), timedelta(0)])
+ def test_get_loc_timedelta_invalid_key(self, key):
+ # GH#20464
+ dti = date_range("1970-01-01", periods=10)
+ msg = "Cannot index DatetimeIndex with [Tt]imedelta"
+ with pytest.raises(TypeError, match=msg):
+ dti.get_loc(key)
+
+ def test_get_loc_reasonable_key_error(self):
+ # GH#1062
+ index = DatetimeIndex(["1/3/2000"])
+ with pytest.raises(KeyError, match="2000"):
+ index.get_loc("1/1/2000")
+
+ def test_get_loc_year_str(self):
+ rng = date_range("1/1/2000", "1/1/2010")
+
+ result = rng.get_loc("2009")
+ expected = slice(3288, 3653)
+ assert result == expected
+
+
+class TestContains:
+ def test_dti_contains_with_duplicates(self):
+ d = datetime(2011, 12, 5, 20, 30)
+ ix = DatetimeIndex([d, d])
+ assert d in ix
+
+ @pytest.mark.parametrize(
+ "vals",
+ [
+ [0, 1, 0],
+ [0, 0, -1],
+ [0, -1, -1],
+ ["2015", "2015", "2016"],
+ ["2015", "2015", "2014"],
+ ],
+ )
+ def test_contains_nonunique(self, vals):
+ # GH#9512
+ idx = DatetimeIndex(vals)
+ assert idx[0] in idx
+
+
+class TestGetIndexer:
+ @td.skip_if_no("pyarrow")
+ @pytest.mark.parametrize("as_td", [True, False])
+ def test_get_indexer_pyarrow(self, as_td):
+ # GH#62277
+ index = date_range("2016-01-01", periods=3)
+ target = index.astype("timestamp[ns][pyarrow]")[::-1]
+ if as_td:
+ # Test duration dtypes while we're here
+ index = index - index[0]
+ target = target - target[-1]
+
+ result = index.get_indexer(target)
+
+ expected = np.array([2, 1, 0], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # Reversed op should work the same
+ result2 = target.get_indexer(index)
+ tm.assert_numpy_array_equal(result2, expected)
+
+ def test_get_indexer_date_objs(self):
+ rng = date_range("1/1/2000", periods=20)
+
+ result = rng.get_indexer(rng.map(lambda x: x.date()))
+ expected = rng.get_indexer(rng)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer(self):
+ idx = date_range("2000-01-01", periods=3)
+ exp = np.array([0, 1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(idx.get_indexer(idx), exp)
+
+ target = idx[0] + pd.to_timedelta(["-1 hour", "12 hours", "1 day 1 hour"])
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "pad"), np.array([-1, 0, 1], dtype=np.intp)
+ )
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "backfill"), np.array([0, 1, 2], dtype=np.intp)
+ )
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "nearest"), np.array([0, 1, 1], dtype=np.intp)
+ )
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "nearest", tolerance=pd.Timedelta("1 hour")),
+ np.array([0, -1, 1], dtype=np.intp),
+ )
+ tol_raw = [
+ pd.Timedelta("1 hour"),
+ pd.Timedelta("1 hour"),
+ pd.Timedelta("1 hour").to_timedelta64(),
+ ]
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(
+ target, "nearest", tolerance=[np.timedelta64(x) for x in tol_raw]
+ ),
+ np.array([0, -1, 1], dtype=np.intp),
+ )
+ tol_bad = [
+ pd.Timedelta("2 hour").to_timedelta64(),
+ pd.Timedelta("1 hour").to_timedelta64(),
+ "foo",
+ ]
+ msg = "Could not convert 'foo' to NumPy timedelta"
+ with pytest.raises(ValueError, match=msg):
+ idx.get_indexer(target, "nearest", tolerance=tol_bad)
+ with pytest.raises(ValueError, match="abbreviation w/o a number"):
+ idx.get_indexer(idx[[0]], method="nearest", tolerance="foo")
+
+ @pytest.mark.parametrize(
+ "target",
+ [
+ [date(2020, 1, 1), Timestamp("2020-01-02")],
+ [Timestamp("2020-01-01"), date(2020, 1, 2)],
+ ],
+ )
+ def test_get_indexer_mixed_dtypes(self, target):
+ # https://github.com/pandas-dev/pandas/issues/33741
+ values = DatetimeIndex([Timestamp("2020-01-01"), Timestamp("2020-01-02")])
+ result = values.get_indexer(target)
+ expected = np.array([0, 1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "target, positions",
+ [
+ ([date(9999, 1, 1), Timestamp("2020-01-01")], [-1, 0]),
+ ([Timestamp("2020-01-01"), date(9999, 1, 1)], [0, -1]),
+ ([date(9999, 1, 1), date(9999, 1, 1)], [-1, -1]),
+ ],
+ )
+ def test_get_indexer_out_of_bounds_date(self, target, positions):
+ values = DatetimeIndex([Timestamp("2020-01-01"), Timestamp("2020-01-02")])
+
+ result = values.get_indexer(target)
+ expected = np.array(positions, dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer_pad_requires_monotonicity(self):
+ rng = date_range("1/1/2000", "3/1/2000", freq="B")
+
+ # neither monotonic increasing or decreasing
+ rng2 = rng[[1, 0, 2]]
+
+ msg = "index must be monotonic increasing or decreasing"
+ with pytest.raises(ValueError, match=msg):
+ rng2.get_indexer(rng, method="pad")
+
+
+class TestMaybeCastSliceBound:
+ def test_maybe_cast_slice_bounds_empty(self):
+ # GH#14354
+ empty_idx = date_range(freq="1h", periods=0, end="2015", unit="ns")
+
+ right = empty_idx._maybe_cast_slice_bound("2015-01-02", "right")
+ exp = Timestamp("2015-01-02 23:59:59.999999999")
+ assert right == exp
+
+ left = empty_idx._maybe_cast_slice_bound("2015-01-02", "left")
+ exp = Timestamp("2015-01-02 00:00:00")
+ assert left == exp
+
+ def test_maybe_cast_slice_duplicate_monotonic(self):
+ # https://github.com/pandas-dev/pandas/issues/16515
+ idx = DatetimeIndex(["2017", "2017"])
+ result = idx._maybe_cast_slice_bound("2017-01-01", "left")
+ expected = Timestamp("2017-01-01")
+ assert result == expected
+
+
+class TestGetSliceBounds:
+ @pytest.mark.parametrize("as_td", [True, False])
+ def test_get_slice_bound_mismatched_unit(self, as_td):
+ # GH#63262
+ index = date_range(start="2000-01-01", freq="h", periods=8)
+
+ td = pd.Timedelta(1)
+ ts = Timestamp("2000-01-01 01:00:00")
+ start = ts - td
+ stop = ts + td
+ if as_td:
+ index = index - Timestamp(0).as_unit("us")
+ start = start - Timestamp(0).as_unit("us")
+ stop = stop - Timestamp(0).as_unit("us")
+
+ left = index.get_slice_bound(start, side="left")
+ assert left == 1
+ right = index.get_slice_bound(stop, side="right")
+ assert right == 2
+
+ # The user-facing behavior is slicing with .loc, so let's test that
+ # explicitly while we're here.
+ ser = pd.Series(1, index=index)
+ result = ser.loc[start:stop]
+ expected = ser.iloc[1:2]
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("box", [date, datetime, Timestamp])
+ @pytest.mark.parametrize("side, expected", [("left", 4), ("right", 5)])
+ def test_get_slice_bounds_datetime_within(
+ self, box, side, expected, tz_aware_fixture
+ ):
+ # GH 35690
+ tz = tz_aware_fixture
+ index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz)
+ key = box(year=2000, month=1, day=7)
+
+ warn = None if box is not date else Pandas4Warning
+ msg = "Slicing with a datetime.date object is deprecated"
+ with tm.assert_produces_warning(warn, match=msg):
+ if tz is not None:
+ with pytest.raises(TypeError, match="Cannot compare tz-naive"):
+ # GH#36148 we require tzawareness-compat as of 2.0
+ index.get_slice_bound(key, side=side)
+ else:
+ result = index.get_slice_bound(key, side=side)
+ assert result == expected
+
+ @pytest.mark.parametrize("box", [datetime, Timestamp])
+ @pytest.mark.parametrize("side", ["left", "right"])
+ @pytest.mark.parametrize("year, expected", [(1999, 0), (2020, 30)])
+ def test_get_slice_bounds_datetime_outside(
+ self, box, side, year, expected, tz_aware_fixture
+ ):
+ # GH 35690
+ tz = tz_aware_fixture
+ index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz)
+ key = box(year=year, month=1, day=7)
+
+ if tz is not None:
+ with pytest.raises(TypeError, match="Cannot compare tz-naive"):
+ # GH#36148 we require tzawareness-compat as of 2.0
+ index.get_slice_bound(key, side=side)
+ else:
+ result = index.get_slice_bound(key, side=side)
+ assert result == expected
+
+ @pytest.mark.parametrize("box", [datetime, Timestamp])
+ def test_slice_datetime_locs(self, box, tz_aware_fixture):
+ # GH 34077
+ tz = tz_aware_fixture
+ index = DatetimeIndex(["2010-01-01", "2010-01-03"]).tz_localize(tz)
+ key = box(2010, 1, 1)
+
+ if tz is not None:
+ with pytest.raises(TypeError, match="Cannot compare tz-naive"):
+ # GH#36148 we require tzawareness-compat as of 2.0
+ index.slice_locs(key, box(2010, 1, 2))
+ else:
+ result = index.slice_locs(key, box(2010, 1, 2))
+ expected = (0, 1)
+ assert result == expected
+
+
+class TestIndexerBetweenTime:
+ def test_indexer_between_time(self):
+ # GH#11818
+ rng = date_range("1/1/2000", "1/5/2000", freq="5min")
+ msg = r"Cannot convert arg \[datetime\.datetime\(2010, 1, 2, 1, 0\)\] to a time"
+ with pytest.raises(ValueError, match=msg):
+ rng.indexer_between_time(datetime(2010, 1, 2, 1), datetime(2010, 1, 2, 5))
+
+ @pytest.mark.parametrize("unit", ["us", "ms", "s"])
+ def test_indexer_between_time_non_nano(self, unit):
+ # For simple cases like this, the non-nano indexer_between_time
+ # should match the nano result
+
+ rng = date_range("1/1/2000", "1/5/2000", freq="5min")
+ arr_nano = rng._data._ndarray
+
+ arr = arr_nano.astype(f"M8[{unit}]")
+
+ dta = type(rng._data)._simple_new(arr, dtype=arr.dtype)
+ dti = DatetimeIndex(dta)
+ assert dti.dtype == arr.dtype
+
+ tic = time(1, 25)
+ toc = time(2, 29)
+
+ result = dti.indexer_between_time(tic, toc)
+ expected = rng.indexer_between_time(tic, toc)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # case with non-zero micros in arguments
+ tic = time(1, 25, 0, 45678)
+ toc = time(2, 29, 0, 1234)
+
+ result = dti.indexer_between_time(tic, toc)
+ expected = rng.indexer_between_time(tic, toc)
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_iter.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_iter.py
new file mode 100644
index 0000000000000000000000000000000000000000..34c763eacaf35713ce9de96859c8f07081c18a03
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_iter.py
@@ -0,0 +1,76 @@
+import dateutil.tz
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ date_range,
+ to_datetime,
+)
+from pandas.core.arrays import datetimes
+
+
+class TestDatetimeIndexIteration:
+ @pytest.mark.parametrize(
+ "tz", [None, "UTC", "US/Central", dateutil.tz.tzoffset(None, -28800)]
+ )
+ def test_iteration_preserves_nanoseconds(self, tz):
+ # GH#19603
+ index = DatetimeIndex(
+ ["2018-02-08 15:00:00.168456358", "2018-02-08 15:00:00.168456359"], tz=tz
+ )
+ for i, ts in enumerate(index):
+ assert ts == index[i] # noqa: PLR1736 (unnecessary-list-index-lookup)
+
+ def test_iter_readonly(self):
+ # GH#28055 ints_to_pydatetime with readonly array
+ arr = np.array([np.datetime64("2012-02-15T12:00:00.000000000")])
+ arr.setflags(write=False)
+ dti = to_datetime(arr)
+ list(dti)
+
+ def test_iteration_preserves_tz(self):
+ # see GH#8890
+ index = date_range("2012-01-01", periods=3, freq="h", tz="US/Eastern")
+
+ for i, ts in enumerate(index):
+ result = ts
+ expected = index[i] # noqa: PLR1736 (unnecessary-list-index-lookup)
+ assert result == expected
+
+ def test_iteration_preserves_tz2(self):
+ index = date_range(
+ "2012-01-01", periods=3, freq="h", tz=dateutil.tz.tzoffset(None, -28800)
+ )
+
+ for i, ts in enumerate(index):
+ result = ts
+ expected = index[i] # noqa: PLR1736 (unnecessary-list-index-lookup)
+ assert result._repr_base == expected._repr_base
+ assert result == expected
+
+ def test_iteration_preserves_tz3(self):
+ # GH#9100
+ index = DatetimeIndex(
+ ["2014-12-01 03:32:39.987000-08:00", "2014-12-01 04:12:34.987000-08:00"]
+ )
+ for i, ts in enumerate(index):
+ result = ts
+ expected = index[i] # noqa: PLR1736 (unnecessary-list-index-lookup)
+ assert result._repr_base == expected._repr_base
+ assert result == expected
+
+ @pytest.mark.parametrize("offset", [-5, -1, 0, 1])
+ def test_iteration_over_chunksize(self, offset, monkeypatch):
+ # GH#21012
+ chunksize = 5
+ index = date_range(
+ "2000-01-01 00:00:00", periods=chunksize - offset, freq="min"
+ )
+ num = 0
+ with monkeypatch.context() as m:
+ m.setattr(datetimes, "_ITER_CHUNKSIZE", chunksize)
+ for stamp in index:
+ assert index[num] == stamp
+ num += 1
+ assert num == len(index)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..abf6809d67f9cd2178c45544186edc71bc7126b9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_join.py
@@ -0,0 +1,153 @@
+from datetime import (
+ datetime,
+ timezone,
+)
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ Timestamp,
+ date_range,
+ period_range,
+ to_datetime,
+)
+import pandas._testing as tm
+
+from pandas.tseries.offsets import (
+ BDay,
+ BMonthEnd,
+)
+
+
+class TestJoin:
+ def test_does_not_convert_mixed_integer(self):
+ df = DataFrame(np.ones((3, 2)), columns=date_range("2020-01-01", periods=2))
+ cols = df.columns.join(df.index, how="outer")
+ joined = cols.join(df.columns)
+ assert cols.dtype == np.dtype("O")
+ assert cols.dtype == joined.dtype
+ tm.assert_numpy_array_equal(cols.values, joined.values)
+
+ def test_join_self(self, join_type):
+ index = date_range("1/1/2000", periods=10)
+ joined = index.join(index, how=join_type)
+ assert index is joined
+
+ def test_join_with_period_index(self, join_type):
+ df = DataFrame(
+ np.ones((10, 2)),
+ index=date_range("2020-01-01", periods=10),
+ columns=period_range("2020-01-01", periods=2),
+ )
+ s = df.iloc[:5, 0]
+
+ expected = df.columns.astype("O").join(s.index, how=join_type)
+ result = df.columns.join(s.index, how=join_type)
+ tm.assert_index_equal(expected, result)
+
+ def test_join_object_index(self):
+ rng = date_range("1/1/2000", periods=10)
+ idx = Index(["a", "b", "c", "d"])
+
+ result = rng.join(idx, how="outer")
+ assert isinstance(result[0], Timestamp)
+
+ def test_join_utc_convert(self, join_type):
+ rng = date_range("1/1/2011", periods=100, freq="h", tz="utc")
+
+ left = rng.tz_convert("US/Eastern")
+ right = rng.tz_convert("Europe/Berlin")
+
+ result = left.join(left[:-5], how=join_type)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz == left.tz
+
+ result = left.join(right[:-5], how=join_type)
+ assert isinstance(result, DatetimeIndex)
+ assert result.tz is timezone.utc
+
+ def test_datetimeindex_union_join_empty(self, sort, using_infer_string):
+ dti = date_range(start="1/1/2001", end="2/1/2001", freq="D")
+ empty = Index([])
+
+ result = dti.union(empty, sort=sort)
+ if using_infer_string:
+ assert isinstance(result, DatetimeIndex)
+ tm.assert_index_equal(result, dti)
+ else:
+ expected = dti.astype("O")
+ tm.assert_index_equal(result, expected)
+
+ result = dti.join(empty)
+ assert isinstance(result, DatetimeIndex)
+ tm.assert_index_equal(result, dti)
+
+ def test_join_nonunique(self):
+ idx1 = to_datetime(["2012-11-06 16:00:11.477563", "2012-11-06 16:00:11.477563"])
+ idx2 = to_datetime(["2012-11-06 15:11:09.006507", "2012-11-06 15:11:09.006507"])
+ rs = idx1.join(idx2, how="outer")
+ assert rs.is_monotonic_increasing
+
+ @pytest.mark.parametrize("freq", ["B", "C"])
+ def test_outer_join(self, freq):
+ # should just behave as union
+ start, end = datetime(2009, 1, 1), datetime(2010, 1, 1)
+ rng = date_range(start=start, end=end, freq=freq)
+
+ # overlapping
+ left = rng[:10]
+ right = rng[5:10]
+
+ the_join = left.join(right, how="outer")
+ assert isinstance(the_join, DatetimeIndex)
+
+ # non-overlapping, gap in middle
+ left = rng[:5]
+ right = rng[10:]
+
+ the_join = left.join(right, how="outer")
+ assert isinstance(the_join, DatetimeIndex)
+ assert the_join.freq is None
+
+ # non-overlapping, no gap
+ left = rng[:5]
+ right = rng[5:10]
+
+ the_join = left.join(right, how="outer")
+ assert isinstance(the_join, DatetimeIndex)
+
+ # overlapping, but different offset
+ other = date_range(start, end, freq=BMonthEnd())
+
+ the_join = rng.join(other, how="outer")
+ assert isinstance(the_join, DatetimeIndex)
+ assert the_join.freq is None
+
+ def test_naive_aware_conflicts(self):
+ start, end = datetime(2009, 1, 1), datetime(2010, 1, 1)
+ naive = date_range(start, end, freq=BDay(), tz=None)
+ aware = date_range(start, end, freq=BDay(), tz="Asia/Hong_Kong")
+
+ msg = "tz-naive.*tz-aware"
+ with pytest.raises(TypeError, match=msg):
+ naive.join(aware)
+
+ with pytest.raises(TypeError, match=msg):
+ aware.join(naive)
+
+ @pytest.mark.parametrize("tz", [None, "US/Pacific"])
+ def test_join_preserves_freq(self, tz):
+ # GH#32157
+ dti = date_range("2016-01-01", periods=10, tz=tz)
+ result = dti[:5].join(dti[5:], how="outer")
+ assert result.freq == dti.freq
+ tm.assert_index_equal(result, dti)
+
+ result = dti[:5].join(dti[6:], how="outer")
+ assert result.freq is None
+ expected = dti.delete(5)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_npfuncs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_npfuncs.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c3e44c2a5db1ebc4f02686d19d34ae3caf1e9ad
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_npfuncs.py
@@ -0,0 +1,13 @@
+import numpy as np
+
+from pandas import date_range
+import pandas._testing as tm
+
+
+class TestSplit:
+ def test_split_non_utc(self):
+ # GH#14042
+ indices = date_range("2016-01-01 00:00:00+0200", freq="s", periods=10)
+ result = np.split(indices, indices_or_sections=[])[0]
+ expected = indices._with_freq(None)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_ops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_ops.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a1c0a136a82851cad648c86595dc0960ad0131e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_ops.py
@@ -0,0 +1,56 @@
+from datetime import datetime
+
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ Index,
+ bdate_range,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeIndexOps:
+ def test_infer_freq(self, freq_sample):
+ # GH 11018
+ idx = date_range("2011-01-01 09:00:00", freq=freq_sample, periods=10, unit="ns")
+ result = DatetimeIndex(idx.asi8, freq="infer")
+ tm.assert_index_equal(idx, result)
+ assert result.freq == freq_sample
+
+
+@pytest.mark.parametrize("freq", ["B", "C"])
+class TestBusinessDatetimeIndex:
+ @pytest.fixture
+ def rng(self, freq):
+ START, END = datetime(2009, 1, 1), datetime(2010, 1, 1)
+ return bdate_range(START, END, freq=freq)
+
+ def test_comparison(self, rng):
+ d = rng[10]
+
+ comp = rng > d
+ assert comp[11]
+ assert not comp[9]
+
+ def test_copy(self, rng):
+ cp = rng.copy()
+ tm.assert_index_equal(cp, rng)
+
+ def test_identical(self, rng):
+ t1 = rng.copy()
+ t2 = rng.copy()
+ assert t1.identical(t2)
+
+ # name
+ t1 = t1.rename("foo")
+ assert t1.equals(t2)
+ assert not t1.identical(t2)
+ t2 = t2.rename("foo")
+ assert t1.identical(t2)
+
+ # freq
+ t2v = Index(t2.values)
+ assert t1.equals(t2v)
+ assert not t1.identical(t2v)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_partial_slicing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_partial_slicing.py
new file mode 100644
index 0000000000000000000000000000000000000000..94175a56f1c4a764b693f620b59e125c978e4802
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_partial_slicing.py
@@ -0,0 +1,466 @@
+"""test partial slicing on Series/Frame"""
+
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ MultiIndex,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestSlicing:
+ def test_string_index_series_name_converted(self):
+ # GH#1644
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ index=date_range("1/1/2000", periods=10),
+ )
+
+ result = df.loc["1/3/2000"]
+ assert result.name == df.index[2]
+
+ result = df.T["1/3/2000"]
+ assert result.name == df.index[2]
+
+ def test_stringified_slice_with_tz(self):
+ # GH#2658
+ start = "2013-01-07"
+ idx = date_range(start=start, freq="1D", periods=10, tz="US/Eastern")
+ df = DataFrame(np.arange(10), index=idx)
+ df["2013-01-14 23:44:34.437768-05:00":] # no exception here
+
+ def test_return_type_doesnt_depend_on_monotonicity(self):
+ # GH#24892 we get Series back regardless of whether our DTI is monotonic
+ dti = date_range(start="2015-5-13 23:59:00", freq="min", periods=3)
+ ser = Series(range(3), index=dti)
+
+ # non-monotonic index
+ ser2 = Series(range(3), index=[dti[1], dti[0], dti[2]])
+
+ # key with resolution strictly lower than "min"
+ key = "2015-5-14 00"
+
+ # monotonic increasing index
+ result = ser.loc[key]
+ expected = ser.iloc[1:]
+ tm.assert_series_equal(result, expected)
+
+ # monotonic decreasing index
+ result = ser.iloc[::-1].loc[key]
+ expected = ser.iloc[::-1][:-1]
+ tm.assert_series_equal(result, expected)
+
+ # non-monotonic index
+ result2 = ser2.loc[key]
+ expected2 = ser2.iloc[::2]
+ tm.assert_series_equal(result2, expected2)
+
+ def test_return_type_doesnt_depend_on_monotonicity_higher_reso(self):
+ # GH#24892 we get Series back regardless of whether our DTI is monotonic
+ dti = date_range(start="2015-5-13 23:59:00", freq="min", periods=3)
+ ser = Series(range(3), index=dti)
+
+ # non-monotonic index
+ ser2 = Series(range(3), index=[dti[1], dti[0], dti[2]])
+
+ # key with resolution strictly *higher) than "min"
+ key = "2015-5-14 00:00:00"
+
+ # monotonic increasing index
+ result = ser.loc[key]
+ assert result == 1
+
+ # monotonic decreasing index
+ result = ser.iloc[::-1].loc[key]
+ assert result == 1
+
+ # non-monotonic index
+ result2 = ser2.loc[key]
+ assert result2 == 0
+
+ def test_monotone_DTI_indexing_bug(self):
+ # GH 19362
+ # Testing accessing the first element in a monotonic descending
+ # partial string indexing.
+
+ df = DataFrame(list(range(5)))
+ date_list = [
+ "2018-01-02",
+ "2017-02-10",
+ "2016-03-10",
+ "2015-03-15",
+ "2014-03-16",
+ ]
+ date_index = DatetimeIndex(date_list)
+ df["date"] = date_index
+ expected = DataFrame({0: list(range(5)), "date": date_index})
+ tm.assert_frame_equal(df, expected)
+
+ # We get a slice because df.index's resolution is hourly and we
+ # are slicing with a daily-resolution string. If both were daily,
+ # we would get a single item back
+ dti = date_range("20170101 01:00:00", periods=3)
+ df = DataFrame({"A": [1, 2, 3]}, index=dti[::-1])
+
+ expected = DataFrame({"A": 1}, index=dti[-1:][::-1])
+ result = df.loc["2017-01-03"]
+ tm.assert_frame_equal(result, expected)
+
+ result2 = df.iloc[::-1].loc["2017-01-03"]
+ expected2 = expected.iloc[::-1]
+ tm.assert_frame_equal(result2, expected2)
+
+ def test_slice_year(self):
+ dti = date_range(freq="B", start=datetime(2005, 1, 1), periods=500)
+
+ s = Series(np.arange(len(dti)), index=dti)
+ result = s["2005"]
+ expected = s[s.index.year == 2005]
+ tm.assert_series_equal(result, expected)
+
+ df = DataFrame(np.random.default_rng(2).random((len(dti), 5)), index=dti)
+ result = df.loc["2005"]
+ expected = df[df.index.year == 2005]
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "partial_dtime",
+ [
+ "2019",
+ "2019Q4",
+ "Dec 2019",
+ "2019-12-31",
+ "2019-12-31 23",
+ "2019-12-31 23:59",
+ ],
+ )
+ def test_slice_end_of_period_resolution(self, partial_dtime):
+ # GH#31064
+ dti = date_range("2019-12-31 23:59:55.999999999", periods=10, freq="s")
+
+ ser = Series(range(10), index=dti)
+ result = ser[partial_dtime]
+ expected = ser.iloc[:5]
+ tm.assert_series_equal(result, expected)
+
+ def test_slice_quarter(self):
+ dti = date_range(freq="D", start=datetime(2000, 6, 1), periods=500)
+
+ s = Series(np.arange(len(dti)), index=dti)
+ assert len(s["2001Q1"]) == 90
+
+ df = DataFrame(np.random.default_rng(2).random((len(dti), 5)), index=dti)
+ assert len(df.loc["1Q01"]) == 90
+
+ def test_slice_month(self):
+ dti = date_range(freq="D", start=datetime(2005, 1, 1), periods=500)
+ s = Series(np.arange(len(dti)), index=dti)
+ assert len(s["2005-11"]) == 30
+
+ df = DataFrame(np.random.default_rng(2).random((len(dti), 5)), index=dti)
+ assert len(df.loc["2005-11"]) == 30
+
+ tm.assert_series_equal(s["2005-11"], s["11-2005"])
+
+ def test_partial_slice(self):
+ rng = date_range(freq="D", start=datetime(2005, 1, 1), periods=500)
+ s = Series(np.arange(len(rng)), index=rng)
+
+ result = s["2005-05":"2006-02"]
+ expected = s["20050501":"20060228"]
+ tm.assert_series_equal(result, expected)
+
+ result = s["2005-05":]
+ expected = s["20050501":]
+ tm.assert_series_equal(result, expected)
+
+ result = s[:"2006-02"]
+ expected = s[:"20060228"]
+ tm.assert_series_equal(result, expected)
+
+ result = s["2005-1-1"]
+ assert result == s.iloc[0]
+
+ with pytest.raises(KeyError, match=r"^'2004-12-31'$"):
+ s["2004-12-31"]
+
+ def test_partial_slice_daily(self):
+ rng = date_range(freq="h", start=datetime(2005, 1, 31), periods=500)
+ s = Series(np.arange(len(rng)), index=rng)
+
+ result = s["2005-1-31"]
+ tm.assert_series_equal(result, s.iloc[:24])
+
+ with pytest.raises(KeyError, match=r"^'2004-12-31 00'$"):
+ s["2004-12-31 00"]
+
+ def test_partial_slice_hourly(self):
+ rng = date_range(freq="min", start=datetime(2005, 1, 1, 20, 0, 0), periods=500)
+ s = Series(np.arange(len(rng)), index=rng)
+
+ result = s["2005-1-1"]
+ tm.assert_series_equal(result, s.iloc[: 60 * 4])
+
+ result = s["2005-1-1 20"]
+ tm.assert_series_equal(result, s.iloc[:60])
+
+ assert s["2005-1-1 20:00"] == s.iloc[0]
+ with pytest.raises(KeyError, match=r"^'2004-12-31 00:15'$"):
+ s["2004-12-31 00:15"]
+
+ def test_partial_slice_minutely(self):
+ rng = date_range(freq="s", start=datetime(2005, 1, 1, 23, 59, 0), periods=500)
+ s = Series(np.arange(len(rng)), index=rng)
+
+ result = s["2005-1-1 23:59"]
+ tm.assert_series_equal(result, s.iloc[:60])
+
+ result = s["2005-1-1"]
+ tm.assert_series_equal(result, s.iloc[:60])
+
+ assert s[Timestamp("2005-1-1 23:59:00")] == s.iloc[0]
+ with pytest.raises(KeyError, match=r"^'2004-12-31 00:00:00'$"):
+ s["2004-12-31 00:00:00"]
+
+ def test_partial_slice_second_precision(self):
+ rng = date_range(
+ start=datetime(2005, 1, 1, 0, 0, 59, microsecond=999990),
+ periods=20,
+ freq="us",
+ )
+ s = Series(np.arange(20), rng)
+
+ tm.assert_series_equal(s["2005-1-1 00:00"], s.iloc[:10])
+ tm.assert_series_equal(s["2005-1-1 00:00:59"], s.iloc[:10])
+
+ tm.assert_series_equal(s["2005-1-1 00:01"], s.iloc[10:])
+ tm.assert_series_equal(s["2005-1-1 00:01:00"], s.iloc[10:])
+
+ assert s[Timestamp("2005-1-1 00:00:59.999990")] == s.iloc[0]
+ with pytest.raises(KeyError, match="2005-1-1 00:00:00"):
+ s["2005-1-1 00:00:00"]
+
+ def test_partial_slicing_dataframe(self):
+ # GH14856
+ # Test various combinations of string slicing resolution vs.
+ # index resolution
+ # - If string resolution is less precise than index resolution,
+ # string is considered a slice
+ # - If string resolution is equal to or more precise than index
+ # resolution, string is considered an exact match
+ formats = [
+ "%Y",
+ "%Y-%m",
+ "%Y-%m-%d",
+ "%Y-%m-%d %H",
+ "%Y-%m-%d %H:%M",
+ "%Y-%m-%d %H:%M:%S",
+ ]
+ resolutions = ["year", "month", "day", "hour", "minute", "second"]
+ for rnum, resolution in enumerate(resolutions[2:], 2):
+ # we check only 'day', 'hour', 'minute' and 'second'
+ unit = Timedelta("1 " + resolution)
+ middate = datetime(2012, 1, 1, 0, 0, 0)
+ index = DatetimeIndex([middate - unit, middate, middate + unit])
+ values = [1, 2, 3]
+ df = DataFrame({"a": values}, index, dtype=np.int64)
+ assert df.index.resolution == resolution
+
+ # Timestamp with the same resolution as index
+ # Should be exact match for Series (return scalar)
+ # and raise KeyError for Frame
+ for timestamp, expected in zip(index, values):
+ ts_string = timestamp.strftime(formats[rnum])
+ # make ts_string as precise as index
+ result = df["a"][ts_string]
+ assert isinstance(result, np.int64)
+ assert result == expected
+ msg = rf"^'{ts_string}'$"
+ with pytest.raises(KeyError, match=msg):
+ df[ts_string]
+
+ # Timestamp with resolution less precise than index
+ for fmt in formats[:rnum]:
+ for element, theslice in [[0, slice(None, 1)], [1, slice(1, None)]]:
+ ts_string = index[element].strftime(fmt)
+
+ # Series should return slice
+ result = df["a"][ts_string]
+ expected = df["a"][theslice]
+ tm.assert_series_equal(result, expected)
+
+ # pre-2.0 df[ts_string] was overloaded to interpret this
+ # as slicing along index
+ with pytest.raises(KeyError, match=ts_string):
+ df[ts_string]
+
+ # Timestamp with resolution more precise than index
+ # Compatible with existing key
+ # Should return scalar for Series
+ # and raise KeyError for Frame
+ for fmt in formats[rnum + 1 :]:
+ ts_string = index[1].strftime(fmt)
+ result = df["a"][ts_string]
+ assert isinstance(result, np.int64)
+ assert result == 2
+ msg = rf"^'{ts_string}'$"
+ with pytest.raises(KeyError, match=msg):
+ df[ts_string]
+
+ # Not compatible with existing key
+ # Should raise KeyError
+ for fmt, res in list(zip(formats, resolutions))[rnum + 1 :]:
+ ts = index[1] + Timedelta("1 " + res)
+ ts_string = ts.strftime(fmt)
+ msg = rf"^'{ts_string}'$"
+ with pytest.raises(KeyError, match=msg):
+ df["a"][ts_string]
+ with pytest.raises(KeyError, match=msg):
+ df[ts_string]
+
+ def test_partial_slicing_with_multiindex(self):
+ # GH 4758
+ # partial string indexing with a multi-index buggy
+ df = DataFrame(
+ {
+ "ACCOUNT": ["ACCT1", "ACCT1", "ACCT1", "ACCT2"],
+ "TICKER": ["ABC", "MNP", "XYZ", "XYZ"],
+ "val": [1, 2, 3, 4],
+ },
+ index=date_range("2013-06-19 09:30:00", periods=4, freq="5min"),
+ )
+ df_multi = df.set_index(["ACCOUNT", "TICKER"], append=True)
+
+ expected = DataFrame(
+ [[1]], index=Index(["ABC"], name="TICKER"), columns=["val"]
+ )
+ result = df_multi.loc[("2013-06-19 09:30:00", "ACCT1")]
+ tm.assert_frame_equal(result, expected)
+
+ expected = df_multi.loc[
+ (Timestamp("2013-06-19 09:30:00", tz=None), "ACCT1", "ABC")
+ ]
+ result = df_multi.loc[("2013-06-19 09:30:00", "ACCT1", "ABC")]
+ tm.assert_series_equal(result, expected)
+
+ # partial string indexing on first level, scalar indexing on the other two
+ result = df_multi.loc[("2013-06-19", "ACCT1", "ABC")]
+ expected = df_multi.iloc[:1].droplevel([1, 2])
+ tm.assert_frame_equal(result, expected)
+
+ def test_partial_slicing_with_multiindex_series(self):
+ # GH 4294
+ # partial slice on a series mi
+ ser = Series(
+ range(250),
+ index=MultiIndex.from_product(
+ [date_range("2000-1-1", periods=50), range(5)]
+ ),
+ )
+
+ s2 = ser[:-1].copy()
+ expected = s2["2000-1-4"]
+ result = s2[Timestamp("2000-1-4")]
+ tm.assert_series_equal(result, expected)
+
+ result = ser[Timestamp("2000-1-4")]
+ expected = ser["2000-1-4"]
+ tm.assert_series_equal(result, expected)
+
+ df2 = DataFrame(ser)
+ expected = df2.xs("2000-1-4")
+ result = df2.loc[Timestamp("2000-1-4")]
+ tm.assert_frame_equal(result, expected)
+
+ def test_partial_slice_requires_monotonicity(self):
+ # Disallowed since 2.0 (GH 37819)
+ ser = Series(np.arange(10), date_range("2014-01-01", periods=10))
+
+ nonmonotonic = ser.iloc[[3, 5, 4]]
+ timestamp = Timestamp("2014-01-10")
+ with pytest.raises(
+ KeyError, match="Value based partial slicing on non-monotonic"
+ ):
+ nonmonotonic["2014-01-10":]
+
+ with pytest.raises(KeyError, match=r"Timestamp\('2014-01-10 00:00:00'\)"):
+ nonmonotonic[timestamp:]
+
+ with pytest.raises(
+ KeyError, match="Value based partial slicing on non-monotonic"
+ ):
+ nonmonotonic.loc["2014-01-10":]
+
+ with pytest.raises(KeyError, match=r"Timestamp\('2014-01-10 00:00:00'\)"):
+ nonmonotonic.loc[timestamp:]
+
+ def test_loc_datetime_length_one(self):
+ # GH16071
+ df = DataFrame(
+ columns=["1"],
+ index=date_range("2016-10-01T00:00:00", "2016-10-01T23:59:59"),
+ )
+ result = df.loc[datetime(2016, 10, 1) :]
+ tm.assert_frame_equal(result, df)
+
+ result = df.loc["2016-10-01T00:00:00":]
+ tm.assert_frame_equal(result, df)
+
+ @pytest.mark.parametrize(
+ "start",
+ [
+ "2018-12-02 21:50:00+00:00",
+ Timestamp("2018-12-02 21:50:00+00:00"),
+ Timestamp("2018-12-02 21:50:00+00:00").to_pydatetime(),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "end",
+ [
+ "2018-12-02 21:52:00+00:00",
+ Timestamp("2018-12-02 21:52:00+00:00"),
+ Timestamp("2018-12-02 21:52:00+00:00").to_pydatetime(),
+ ],
+ )
+ def test_getitem_with_datestring_with_UTC_offset(self, start, end):
+ # GH 24076
+ idx = date_range(
+ start="2018-12-02 14:50:00-07:00",
+ end="2018-12-02 14:50:00-07:00",
+ freq="1min",
+ )
+ df = DataFrame(1, index=idx, columns=["A"])
+ result = df[start:end]
+ expected = df.iloc[0:3, :]
+ tm.assert_frame_equal(result, expected)
+
+ # GH 16785
+ start = str(start)
+ end = str(end)
+ with pytest.raises(ValueError, match="Both dates must"):
+ df[start : end[:-4] + "1:00"]
+
+ df = df.tz_localize(None)
+ with pytest.raises(ValueError, match="The index must be timezone"):
+ df[start:end]
+
+ def test_slice_reduce_to_series(self):
+ # GH 27516
+ df = DataFrame(
+ {"A": range(24)}, index=date_range("2000", periods=24, freq="ME")
+ )
+ expected = Series(
+ range(12), index=date_range("2000", periods=12, freq="ME"), name="A"
+ )
+ result = df.loc["2000", "A"]
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_pickle.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_pickle.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d9a2b0c20c108567082cd0e7c97b935be3c6f2e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_pickle.py
@@ -0,0 +1,45 @@
+import pytest
+
+from pandas import (
+ NaT,
+ date_range,
+ to_datetime,
+)
+import pandas._testing as tm
+
+
+class TestPickle:
+ def test_pickle(self, temp_file):
+ # GH#4606
+ idx = to_datetime(["2013-01-01", NaT, "2014-01-06"])
+ idx_p = tm.round_trip_pickle(idx, temp_file)
+ assert idx_p[0] == idx[0]
+ assert idx_p[1] is NaT
+ assert idx_p[2] == idx[2]
+
+ def test_pickle_dont_infer_freq(self, temp_file):
+ # GH#11002
+ # don't infer freq
+ idx = date_range("1750-1-1", "2050-1-1", freq="7D")
+ idx_p = tm.round_trip_pickle(idx, temp_file)
+ tm.assert_index_equal(idx, idx_p)
+
+ def test_pickle_after_set_freq(self, temp_file):
+ dti = date_range("20130101", periods=3, tz="US/Eastern", name="foo")
+ dti = dti._with_freq(None)
+
+ res = tm.round_trip_pickle(dti, temp_file)
+ tm.assert_index_equal(res, dti)
+
+ def test_roundtrip_pickle_with_tz(self, temp_file):
+ # GH#8367
+ # round-trip of timezone
+ index = date_range("20130101", periods=3, tz="US/Eastern", name="foo")
+ unpickled = tm.round_trip_pickle(index, temp_file)
+ tm.assert_index_equal(index, unpickled)
+
+ @pytest.mark.parametrize("freq", ["B", "C"])
+ def test_pickle_unpickle(self, freq, temp_file):
+ rng = date_range("2009-01-01", "2010-01-01", freq=freq)
+ unpickled = tm.round_trip_pickle(rng, temp_file)
+ assert unpickled.freq == freq
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_reindex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_reindex.py
new file mode 100644
index 0000000000000000000000000000000000000000..6bc445e8c69a3e587e97e0c6849246e4cd91d97d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_reindex.py
@@ -0,0 +1,56 @@
+from datetime import timedelta
+
+import numpy as np
+
+from pandas import (
+ DatetimeIndex,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeIndexReindex:
+ def test_reindex_preserves_tz_if_target_is_empty_list_or_array(self):
+ # GH#7774
+ index = date_range("2013-01-01", periods=3, tz="US/Eastern", unit="ns")
+ assert str(index.reindex([])[0].tz) == "US/Eastern"
+ assert str(index.reindex(np.array([]))[0].tz) == "US/Eastern"
+
+ def test_reindex_with_same_tz_nearest(self):
+ # GH#32740
+ rng_a = date_range("2010-01-01", "2010-01-02", periods=24, tz="utc", unit="ns")
+ rng_b = date_range("2010-01-01", "2010-01-02", periods=23, tz="utc", unit="ns")
+ result1, result2 = rng_a.reindex(
+ rng_b, method="nearest", tolerance=timedelta(seconds=20)
+ )
+ expected_list1 = [
+ "2010-01-01 00:00:00",
+ "2010-01-01 01:05:27.272727272",
+ "2010-01-01 02:10:54.545454545",
+ "2010-01-01 03:16:21.818181818",
+ "2010-01-01 04:21:49.090909090",
+ "2010-01-01 05:27:16.363636363",
+ "2010-01-01 06:32:43.636363636",
+ "2010-01-01 07:38:10.909090909",
+ "2010-01-01 08:43:38.181818181",
+ "2010-01-01 09:49:05.454545454",
+ "2010-01-01 10:54:32.727272727",
+ "2010-01-01 12:00:00",
+ "2010-01-01 13:05:27.272727272",
+ "2010-01-01 14:10:54.545454545",
+ "2010-01-01 15:16:21.818181818",
+ "2010-01-01 16:21:49.090909090",
+ "2010-01-01 17:27:16.363636363",
+ "2010-01-01 18:32:43.636363636",
+ "2010-01-01 19:38:10.909090909",
+ "2010-01-01 20:43:38.181818181",
+ "2010-01-01 21:49:05.454545454",
+ "2010-01-01 22:54:32.727272727",
+ "2010-01-02 00:00:00",
+ ]
+ expected1 = DatetimeIndex(
+ expected_list1, dtype="datetime64[ns, UTC]", freq=None
+ )
+ expected2 = np.array([0] + [-1] * 21 + [23], dtype=np.dtype("intp"))
+ tm.assert_index_equal(result1, expected1)
+ tm.assert_numpy_array_equal(result2, expected2)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_scalar_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_scalar_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..d640447a4c0e0f1de053166509bba9dd27e63090
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_scalar_compat.py
@@ -0,0 +1,459 @@
+"""
+Tests for DatetimeIndex methods behaving like their Timestamp counterparts
+"""
+
+import calendar
+from datetime import (
+ date,
+ datetime,
+ time,
+)
+import locale
+import unicodedata
+
+from hypothesis import given
+import hypothesis.strategies as st
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import timezones
+
+from pandas import (
+ DatetimeIndex,
+ Index,
+ NaT,
+ Timestamp,
+ date_range,
+ offsets,
+)
+import pandas._testing as tm
+from pandas.core.arrays import DatetimeArray
+
+
+class TestDatetimeIndexOps:
+ def test_dti_no_millisecond_field(self):
+ msg = "type object 'DatetimeIndex' has no attribute 'millisecond'"
+ with pytest.raises(AttributeError, match=msg):
+ DatetimeIndex.millisecond
+
+ msg = "'DatetimeIndex' object has no attribute 'millisecond'"
+ with pytest.raises(AttributeError, match=msg):
+ DatetimeIndex([]).millisecond
+
+ def test_dti_time(self):
+ rng = date_range("1/1/2000", freq="12min", periods=10)
+ result = Index(rng).time
+ expected = [t.time() for t in rng]
+ assert (result == expected).all()
+
+ def test_dti_date(self):
+ rng = date_range("1/1/2000", freq="12h", periods=10)
+ result = Index(rng).date
+ expected = [t.date() for t in rng]
+ assert (result == expected).all()
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [None, "datetime64[ns, CET]", "datetime64[ns, EST]", "datetime64[ns, UTC]"],
+ )
+ def test_dti_date2(self, dtype):
+ # Regression test for GH#21230
+ expected = np.array([date(2018, 6, 4), NaT])
+
+ index = DatetimeIndex(["2018-06-04 10:00:00", NaT], dtype=dtype)
+ result = index.date
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [None, "datetime64[ns, CET]", "datetime64[ns, EST]", "datetime64[ns, UTC]"],
+ )
+ def test_dti_time2(self, dtype):
+ # Regression test for GH#21267
+ expected = np.array([time(10, 20, 30), NaT])
+
+ index = DatetimeIndex(["2018-06-04 10:20:30", NaT], dtype=dtype)
+ result = index.time
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_dti_timetz(self, tz_naive_fixture):
+ # GH#21358
+ tz = timezones.maybe_get_tz(tz_naive_fixture)
+
+ expected = np.array([time(10, 20, 30, tzinfo=tz), NaT])
+
+ index = DatetimeIndex(["2018-06-04 10:20:30", NaT], tz=tz)
+ result = index.timetz
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "field",
+ [
+ "dayofweek",
+ "day_of_week",
+ "dayofyear",
+ "day_of_year",
+ "quarter",
+ "days_in_month",
+ "is_month_start",
+ "is_month_end",
+ "is_quarter_start",
+ "is_quarter_end",
+ "is_year_start",
+ "is_year_end",
+ ],
+ )
+ def test_dti_timestamp_fields(self, field):
+ # extra fields from DatetimeIndex like quarter and week
+ idx = date_range("2020-01-01", periods=10)
+ expected = getattr(idx, field)[-1]
+
+ result = getattr(Timestamp(idx[-1]), field)
+ assert result == expected
+
+ def test_dti_nanosecond(self):
+ dti = DatetimeIndex(np.arange(10))
+ expected = Index(np.arange(10, dtype=np.int32))
+
+ tm.assert_index_equal(dti.nanosecond, expected)
+
+ @pytest.mark.parametrize("prefix", ["", "dateutil/"])
+ def test_dti_hour_tzaware(self, prefix):
+ strdates = ["1/1/2012", "3/1/2012", "4/1/2012"]
+ rng = DatetimeIndex(strdates, tz=prefix + "US/Eastern")
+ assert (rng.hour == 0).all()
+
+ # a more unusual time zone, GH#1946
+ dr = date_range(
+ "2011-10-02 00:00", freq="h", periods=10, tz=prefix + "America/Atikokan"
+ )
+
+ expected = Index(np.arange(10, dtype=np.int32))
+ tm.assert_index_equal(dr.hour, expected)
+
+ # GH#12806
+ @pytest.mark.parametrize(
+ "time_locale",
+ [None, *tm.get_locales()],
+ )
+ def test_day_name_month_name(self, time_locale):
+ # Test Monday -> Sunday and January -> December, in that sequence
+ if time_locale is None:
+ # If the time_locale is None, day-name and month_name should
+ # return the english attributes
+ expected_days = [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]
+ expected_months = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ ]
+ else:
+ with tm.set_locale(time_locale, locale.LC_TIME):
+ expected_days = calendar.day_name[:]
+ expected_months = calendar.month_name[1:]
+
+ # GH#11128
+ dti = date_range(freq="D", start=datetime(1998, 1, 1), periods=365)
+ english_days = [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]
+ for day, name, eng_name in zip(range(4, 11), expected_days, english_days):
+ name = name.capitalize()
+ assert dti.day_name(locale=time_locale)[day] == name
+ assert dti.day_name(locale=None)[day] == eng_name
+ ts = Timestamp(datetime(2016, 4, day))
+ assert ts.day_name(locale=time_locale) == name
+ dti = dti.append(DatetimeIndex([NaT]))
+ assert np.isnan(dti.day_name(locale=time_locale)[-1])
+ ts = Timestamp(NaT)
+ assert np.isnan(ts.day_name(locale=time_locale))
+
+ # GH#12805
+ dti = date_range(freq="ME", start="2012", end="2013")
+ result = dti.month_name(locale=time_locale)
+ expected = Index([month.capitalize() for month in expected_months])
+
+ # work around different normalization schemes GH#22342
+ result = result.str.normalize("NFD")
+ expected = expected.str.normalize("NFD")
+
+ tm.assert_index_equal(result, expected)
+
+ for item, expected in zip(dti, expected_months):
+ result = item.month_name(locale=time_locale)
+ expected = expected.capitalize()
+
+ result = unicodedata.normalize("NFD", result)
+ expected = unicodedata.normalize("NFD", result)
+
+ assert result == expected
+ dti = dti.append(DatetimeIndex([NaT]))
+ assert np.isnan(dti.month_name(locale=time_locale)[-1])
+
+ def test_dti_week(self):
+ # GH#6538: Check that DatetimeIndex and its TimeStamp elements
+ # return the same weekofyear accessor close to new year w/ tz
+ dates = ["2013/12/29", "2013/12/30", "2013/12/31"]
+ dates = DatetimeIndex(dates, tz="Europe/Brussels")
+ expected = [52, 1, 1]
+ assert dates.isocalendar().week.tolist() == expected
+ assert [d.weekofyear for d in dates] == expected
+
+ @pytest.mark.parametrize("tz", [None, "US/Eastern"])
+ def test_dti_fields(self, tz):
+ # GH#13303
+ dti = date_range(
+ freq="D", start=datetime(1998, 1, 1), periods=365, tz=tz, unit="ns"
+ )
+ assert dti.year[0] == 1998
+ assert dti.month[0] == 1
+ assert dti.day[0] == 1
+ assert dti.hour[0] == 0
+ assert dti.minute[0] == 0
+ assert dti.second[0] == 0
+ assert dti.microsecond[0] == 0
+ assert dti.dayofweek[0] == 3
+
+ assert dti.dayofyear[0] == 1
+ assert dti.dayofyear[120] == 121
+
+ assert dti.isocalendar().week.iloc[0] == 1
+ assert dti.isocalendar().week.iloc[120] == 18
+
+ assert dti.quarter[0] == 1
+ assert dti.quarter[120] == 2
+
+ assert dti.days_in_month[0] == 31
+ assert dti.days_in_month[90] == 30
+
+ assert dti.is_month_start[0]
+ assert not dti.is_month_start[1]
+ assert dti.is_month_start[31]
+ assert dti.is_quarter_start[0]
+ assert dti.is_quarter_start[90]
+ assert dti.is_year_start[0]
+ assert not dti.is_year_start[364]
+ assert not dti.is_month_end[0]
+ assert dti.is_month_end[30]
+ assert not dti.is_month_end[31]
+ assert dti.is_month_end[364]
+ assert not dti.is_quarter_end[0]
+ assert not dti.is_quarter_end[30]
+ assert dti.is_quarter_end[89]
+ assert dti.is_quarter_end[364]
+ assert not dti.is_year_end[0]
+ assert dti.is_year_end[364]
+
+ assert len(dti.year) == 365
+ assert len(dti.month) == 365
+ assert len(dti.day) == 365
+ assert len(dti.hour) == 365
+ assert len(dti.minute) == 365
+ assert len(dti.second) == 365
+ assert len(dti.microsecond) == 365
+ assert len(dti.dayofweek) == 365
+ assert len(dti.dayofyear) == 365
+ assert len(dti.isocalendar()) == 365
+ assert len(dti.quarter) == 365
+ assert len(dti.is_month_start) == 365
+ assert len(dti.is_month_end) == 365
+ assert len(dti.is_quarter_start) == 365
+ assert len(dti.is_quarter_end) == 365
+ assert len(dti.is_year_start) == 365
+ assert len(dti.is_year_end) == 365
+
+ dti.name = "name"
+
+ # non boolean accessors -> return Index
+ for accessor in DatetimeArray._field_ops:
+ res = getattr(dti, accessor)
+ assert len(res) == 365
+ assert isinstance(res, Index)
+ assert res.name == "name"
+
+ # boolean accessors -> return array
+ for accessor in DatetimeArray._bool_ops:
+ res = getattr(dti, accessor)
+ assert len(res) == 365
+ assert isinstance(res, np.ndarray)
+
+ # test boolean indexing
+ res = dti[dti.is_quarter_start]
+ exp = dti[[0, 90, 181, 273]]
+ tm.assert_index_equal(res, exp)
+ res = dti[dti.is_leap_year]
+ exp = DatetimeIndex([], freq="D", tz=dti.tz, name="name").as_unit("ns")
+ tm.assert_index_equal(res, exp)
+
+ def test_dti_is_year_quarter_start(self):
+ dti = date_range(freq="BQE-FEB", start=datetime(1998, 1, 1), periods=4)
+
+ assert sum(dti.is_quarter_start) == 0
+ assert sum(dti.is_quarter_end) == 4
+ assert sum(dti.is_year_start) == 0
+ assert sum(dti.is_year_end) == 1
+
+ def test_dti_is_month_start(self):
+ dti = DatetimeIndex(["2000-01-01", "2000-01-02", "2000-01-03"])
+
+ assert dti.is_month_start[0] == 1
+
+ def test_dti_is_month_start_custom(self):
+ # Ensure is_start/end accessors throw ValueError for CustomBusinessDay,
+ bday_egypt = offsets.CustomBusinessDay(weekmask="Sun Mon Tue Wed Thu")
+ dti = date_range(datetime(2013, 4, 30), periods=5, freq=bday_egypt)
+ msg = "Custom business days is not supported by is_month_start"
+ with pytest.raises(ValueError, match=msg):
+ dti.is_month_start
+
+ @pytest.mark.parametrize(
+ "timestamp, freq, periods, expected_values",
+ [
+ ("2017-12-01", "MS", 3, np.array([False, True, False])),
+ ("2017-12-01", "QS", 3, np.array([True, False, False])),
+ ("2017-12-01", "YS", 3, np.array([True, True, True])),
+ ],
+ )
+ def test_dti_dr_is_year_start(self, timestamp, freq, periods, expected_values):
+ # GH57377
+ result = date_range(timestamp, freq=freq, periods=periods).is_year_start
+ tm.assert_numpy_array_equal(result, expected_values)
+
+ @pytest.mark.parametrize(
+ "timestamp, freq, periods, expected_values",
+ [
+ ("2017-12-01", "ME", 3, np.array([True, False, False])),
+ ("2017-12-01", "QE", 3, np.array([True, False, False])),
+ ("2017-12-01", "YE", 3, np.array([True, True, True])),
+ ],
+ )
+ def test_dti_dr_is_year_end(self, timestamp, freq, periods, expected_values):
+ # GH57377
+ result = date_range(timestamp, freq=freq, periods=periods).is_year_end
+ tm.assert_numpy_array_equal(result, expected_values)
+
+ @pytest.mark.parametrize(
+ "timestamp, freq, periods, expected_values",
+ [
+ ("2017-12-01", "MS", 3, np.array([False, True, False])),
+ ("2017-12-01", "QS", 3, np.array([True, True, True])),
+ ("2017-12-01", "YS", 3, np.array([True, True, True])),
+ ],
+ )
+ def test_dti_dr_is_quarter_start(self, timestamp, freq, periods, expected_values):
+ # GH57377
+ result = date_range(timestamp, freq=freq, periods=periods).is_quarter_start
+ tm.assert_numpy_array_equal(result, expected_values)
+
+ @pytest.mark.parametrize(
+ "timestamp, freq, periods, expected_values",
+ [
+ ("2017-12-01", "ME", 3, np.array([True, False, False])),
+ ("2017-12-01", "QE", 3, np.array([True, True, True])),
+ ("2017-12-01", "YE", 3, np.array([True, True, True])),
+ ],
+ )
+ def test_dti_dr_is_quarter_end(self, timestamp, freq, periods, expected_values):
+ # GH57377
+ result = date_range(timestamp, freq=freq, periods=periods).is_quarter_end
+ tm.assert_numpy_array_equal(result, expected_values)
+
+ @pytest.mark.parametrize(
+ "timestamp, freq, periods, expected_values",
+ [
+ ("2017-12-01", "MS", 3, np.array([True, True, True])),
+ ("2017-12-01", "QS", 3, np.array([True, True, True])),
+ ("2017-12-01", "YS", 3, np.array([True, True, True])),
+ ],
+ )
+ def test_dti_dr_is_month_start(self, timestamp, freq, periods, expected_values):
+ # GH57377
+ result = date_range(timestamp, freq=freq, periods=periods).is_month_start
+ tm.assert_numpy_array_equal(result, expected_values)
+
+ @pytest.mark.parametrize(
+ "timestamp, freq, periods, expected_values",
+ [
+ ("2017-12-01", "ME", 3, np.array([True, True, True])),
+ ("2017-12-01", "QE", 3, np.array([True, True, True])),
+ ("2017-12-01", "YE", 3, np.array([True, True, True])),
+ ],
+ )
+ def test_dti_dr_is_month_end(self, timestamp, freq, periods, expected_values):
+ # GH57377
+ result = date_range(timestamp, freq=freq, periods=periods).is_month_end
+ tm.assert_numpy_array_equal(result, expected_values)
+
+ def test_dti_is_year_quarter_start_doubledigit_freq(self):
+ # GH#58523
+ dr = date_range("2017-01-01", periods=2, freq="10YS")
+ assert all(dr.is_year_start)
+
+ dr = date_range("2017-01-01", periods=2, freq="10QS")
+ assert all(dr.is_quarter_start)
+
+ def test_dti_is_year_start_freq_custom_business_day_with_digit(self):
+ # GH#58664
+ dr = date_range("2020-01-01", periods=2, freq="2C")
+ msg = "Custom business days is not supported by is_year_start"
+ with pytest.raises(ValueError, match=msg):
+ dr.is_year_start
+
+ @pytest.mark.parametrize("freq", ["3BMS", offsets.BusinessMonthBegin(3)])
+ def test_dti_is_year_quarter_start_freq_business_month_begin(self, freq):
+ # GH#58729
+ dr = date_range("2020-01-01", periods=5, freq=freq)
+ result = [x.is_year_start for x in dr]
+ assert result == [True, False, False, False, True]
+
+ dr = date_range("2020-01-01", periods=4, freq=freq)
+ result = [x.is_quarter_start for x in dr]
+ assert all(dr.is_quarter_start)
+
+ def test_dti_is_year_start_freq_two_business_days(self):
+ # GH#58524
+ dr = date_range("2017-01-01", periods=2, freq="2B")
+ result = dr.is_year_start
+ expected = np.array([True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@given(
+ dt=st.datetimes(min_value=datetime(1960, 1, 1), max_value=datetime(1980, 1, 1)),
+ n=st.integers(min_value=1, max_value=10),
+ freq=st.sampled_from(["MS", "QS", "YS"]),
+)
+@pytest.mark.slow
+def test_against_scalar_parametric(freq, dt, n):
+ # https://github.com/pandas-dev/pandas/issues/49606
+ freq = f"{n}{freq}"
+ d = date_range(dt, periods=3, freq=freq)
+ result = list(d.is_year_start)
+ expected = [x.is_year_start for x in d]
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d3307191938ef8dc23de9a4ed058a1ce82e15ee
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_setops.py
@@ -0,0 +1,775 @@
+from datetime import (
+ datetime,
+ timedelta,
+ timezone,
+)
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ Series,
+ Timestamp,
+ bdate_range,
+ date_range,
+)
+import pandas._testing as tm
+
+from pandas.tseries.offsets import (
+ BMonthEnd,
+ Minute,
+ MonthEnd,
+)
+
+START, END = datetime(2009, 1, 1), datetime(2010, 1, 1)
+
+
+class TestDatetimeIndexSetOps:
+ tz = [
+ None,
+ "UTC",
+ "Asia/Tokyo",
+ "US/Eastern",
+ "dateutil/Asia/Singapore",
+ "dateutil/US/Pacific",
+ ]
+
+ # TODO: moved from test_datetimelike; dedup with version below
+ def test_union2(self, sort):
+ everything = date_range("2020-01-01", periods=10)
+ first = everything[:5]
+ second = everything[5:]
+ union = first.union(second, sort=sort)
+ tm.assert_index_equal(union, everything)
+
+ @pytest.mark.parametrize("box", [np.array, Series, list])
+ def test_union3(self, sort, box):
+ everything = date_range("2020-01-01", periods=10)
+ first = everything[:5]
+ second = everything[5:]
+
+ # GH 10149 support listlike inputs other than Index objects
+ expected = first.union(second, sort=sort)
+ case = box(second.values)
+ result = first.union(case, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("tz", tz)
+ def test_union(self, tz, sort):
+ rng1 = date_range("1/1/2000", freq="D", periods=5, tz=tz, unit="ns")
+ other1 = date_range("1/6/2000", freq="D", periods=5, tz=tz, unit="ns")
+ expected1 = date_range("1/1/2000", freq="D", periods=10, tz=tz, unit="ns")
+ expected1_notsorted = DatetimeIndex(list(other1) + list(rng1))
+
+ rng2 = date_range("1/1/2000", freq="D", periods=5, tz=tz, unit="ns")
+ other2 = date_range("1/4/2000", freq="D", periods=5, tz=tz, unit="ns")
+ expected2 = date_range("1/1/2000", freq="D", periods=8, tz=tz, unit="ns")
+ expected2_notsorted = DatetimeIndex(list(other2) + list(rng2[:3]))
+
+ rng3 = date_range("1/1/2000", freq="D", periods=5, tz=tz, unit="ns")
+ other3 = DatetimeIndex([], tz=tz).as_unit("ns")
+ expected3 = date_range("1/1/2000", freq="D", periods=5, tz=tz, unit="ns")
+ expected3_notsorted = rng3
+
+ for rng, other, exp, exp_notsorted in [
+ (rng1, other1, expected1, expected1_notsorted),
+ (rng2, other2, expected2, expected2_notsorted),
+ (rng3, other3, expected3, expected3_notsorted),
+ ]:
+ result_union = rng.union(other, sort=sort)
+ tm.assert_index_equal(result_union, exp)
+
+ result_union = other.union(rng, sort=sort)
+ if sort is None:
+ tm.assert_index_equal(result_union, exp)
+ else:
+ tm.assert_index_equal(result_union, exp_notsorted)
+
+ def test_union_coverage(self, sort):
+ idx = DatetimeIndex(["2000-01-03", "2000-01-01", "2000-01-02"])
+ ordered = DatetimeIndex(idx.sort_values(), freq="infer")
+ result = ordered.union(idx, sort=sort)
+ tm.assert_index_equal(result, ordered)
+
+ result = ordered[:0].union(ordered, sort=sort)
+ tm.assert_index_equal(result, ordered)
+ assert result.freq == ordered.freq
+
+ def test_union_bug_1730(self, sort):
+ rng_a = date_range("1/1/2012", periods=4, freq="3h")
+ rng_b = date_range("1/1/2012", periods=4, freq="4h")
+
+ result = rng_a.union(rng_b, sort=sort)
+ exp = list(rng_a) + list(rng_b[1:])
+ if sort is None:
+ exp = DatetimeIndex(sorted(exp))
+ else:
+ exp = DatetimeIndex(exp)
+ tm.assert_index_equal(result, exp)
+
+ def test_union_bug_1745(self, sort):
+ left = DatetimeIndex(["2012-05-11 15:19:49.695000"])
+ right = DatetimeIndex(
+ [
+ "2012-05-29 13:04:21.322000",
+ "2012-05-11 15:27:24.873000",
+ "2012-05-11 15:31:05.350000",
+ ]
+ )
+
+ result = left.union(right, sort=sort)
+ exp = DatetimeIndex(
+ [
+ "2012-05-11 15:19:49.695000",
+ "2012-05-29 13:04:21.322000",
+ "2012-05-11 15:27:24.873000",
+ "2012-05-11 15:31:05.350000",
+ ]
+ )
+ if sort is None:
+ exp = exp.sort_values()
+ tm.assert_index_equal(result, exp)
+
+ def test_union_bug_4564(self, sort):
+ from pandas import DateOffset
+
+ left = date_range("2013-01-01", "2013-02-01")
+ right = left + DateOffset(minutes=15)
+
+ result = left.union(right, sort=sort)
+ exp = list(left) + list(right)
+ if sort is None:
+ exp = DatetimeIndex(sorted(exp))
+ else:
+ exp = DatetimeIndex(exp)
+ tm.assert_index_equal(result, exp)
+
+ def test_union_freq_both_none(self, sort):
+ # GH11086
+ expected = bdate_range("20150101", periods=10)
+ expected._data.freq = None
+
+ result = expected.union(expected, sort=sort)
+ tm.assert_index_equal(result, expected)
+ assert result.freq is None
+
+ def test_union_freq_infer(self):
+ # When taking the union of two DatetimeIndexes, we infer
+ # a freq even if the arguments don't have freq. This matches
+ # TimedeltaIndex behavior.
+ dti = date_range("2016-01-01", periods=5)
+ left = dti[[0, 1, 3, 4]]
+ right = dti[[2, 3, 1]]
+
+ assert left.freq is None
+ assert right.freq is None
+
+ result = left.union(right)
+ tm.assert_index_equal(result, dti)
+ assert result.freq == "D"
+
+ def test_union_dataframe_index(self):
+ rng1 = date_range("1/1/1999", "1/1/2012", freq="MS")
+ s1 = Series(np.random.default_rng(2).standard_normal(len(rng1)), rng1)
+
+ rng2 = date_range("1/1/1980", "12/1/2001", freq="MS")
+ s2 = Series(np.random.default_rng(2).standard_normal(len(rng2)), rng2)
+ df = DataFrame({"s1": s1, "s2": s2})
+
+ exp = date_range("1/1/1980", "1/1/2012", freq="MS")
+ tm.assert_index_equal(df.index, exp)
+
+ def test_union_with_DatetimeIndex(self, sort):
+ i1 = Index(np.arange(0, 20, 2, dtype=np.int64))
+ i2 = date_range(start="2012-01-03 00:00:00", periods=10, freq="D")
+ # Works
+ i1.union(i2, sort=sort)
+ # Fails with "AttributeError: can't set attribute"
+ i2.union(i1, sort=sort)
+
+ def test_union_same_timezone_different_units(self):
+ # GH 55238
+ idx1 = date_range("2000-01-01", periods=3, tz="UTC").as_unit("ms")
+ idx2 = date_range("2000-01-01", periods=3, tz="UTC").as_unit("us")
+ result = idx1.union(idx2)
+ expected = date_range("2000-01-01", periods=3, tz="UTC").as_unit("us")
+ tm.assert_index_equal(result, expected)
+
+ def test_union_same_nonzero_timezone_different_units(self):
+ # GH 60080 - fix timezone being changed to UTC when units differ
+ # but timezone is the same
+ tz = "UTC+05:00"
+ idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
+ idx2 = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
+
+ # Check pre-conditions
+ assert idx1.tz == idx2.tz
+ assert idx1.dtype != idx2.dtype # Different units
+
+ # Test union preserves timezone when units differ
+ result = idx1.union(idx2)
+ expected = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
+ tm.assert_index_equal(result, expected)
+
+ def test_union_different_dates_same_timezone_different_units(self):
+ # GH 60080 - fix timezone being changed to UTC when units differ
+ # but timezone is the same
+ tz = "UTC+05:00"
+ idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
+ idx3 = date_range("2000-01-03", periods=3, tz=tz).as_unit("us")
+
+ # Test with different dates to ensure it's not just returning one of the inputs
+ result = idx1.union(idx3)
+ expected = DatetimeIndex(
+ ["2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05"],
+ tz=tz,
+ ).as_unit("us")
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection_same_timezone_different_units(self):
+ # GH 60080 - fix timezone being changed to UTC when units differ
+ # but timezone is the same
+ tz = "UTC+05:00"
+ idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
+ idx2 = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
+
+ # Check pre-conditions
+ assert idx1.tz == idx2.tz
+ assert idx1.dtype != idx2.dtype # Different units
+
+ # Test intersection
+ result = idx1.intersection(idx2)
+ expected = date_range("2000-01-01", periods=3, tz=tz)
+ tm.assert_index_equal(result, expected)
+
+ # Intersection dtype is not commutative
+ result2 = idx2.intersection(idx1)
+ tm.assert_index_equal(result2, expected.as_unit("ns"))
+
+ def test_symmetric_difference_same_timezone_different_units(self):
+ # GH 60080 - fix timezone being changed to UTC when units differ
+ # but timezone is the same
+ tz = "UTC+05:00"
+ idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
+ idx4 = date_range("2000-01-02", periods=3, tz=tz).as_unit("ns")
+
+ # Check pre-conditions
+ assert idx1.tz == idx4.tz
+ assert idx1.dtype != idx4.dtype # Different units
+
+ # Test symmetric_difference
+ result = idx1.symmetric_difference(idx4)
+ expected = DatetimeIndex(["2000-01-01", "2000-01-04"], tz=tz).as_unit("ns")
+ tm.assert_index_equal(result, expected)
+
+ # TODO: moved from test_datetimelike; de-duplicate with version below
+ def test_intersection2(self):
+ first = date_range("2020-01-01", periods=10)
+ second = first[5:]
+ intersect = first.intersection(second)
+ tm.assert_index_equal(intersect, second)
+
+ # GH 10149
+ cases = [klass(second.values) for klass in [np.array, Series, list]]
+ for case in cases:
+ result = first.intersection(case)
+ tm.assert_index_equal(result, second)
+
+ third = Index(["a", "b", "c"])
+ result = first.intersection(third)
+ expected = Index([], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "tz", [None, "Asia/Tokyo", "US/Eastern", "dateutil/US/Pacific"]
+ )
+ def test_intersection(self, tz, sort):
+ # GH 4690 (with tz)
+ base = date_range("6/1/2000", "6/30/2000", freq="D", name="idx", unit="ns")
+
+ # if target has the same name, it is preserved
+ rng2 = date_range("5/15/2000", "6/20/2000", freq="D", name="idx", unit="ns")
+ expected2 = date_range("6/1/2000", "6/20/2000", freq="D", name="idx", unit="ns")
+
+ # if target name is different, it will be reset
+ rng3 = date_range("5/15/2000", "6/20/2000", freq="D", name="other", unit="ns")
+ expected3 = date_range("6/1/2000", "6/20/2000", freq="D", name=None, unit="ns")
+
+ rng4 = date_range("7/1/2000", "7/31/2000", freq="D", name="idx", unit="ns")
+ expected4 = DatetimeIndex([], freq="D", name="idx", dtype="M8[ns]")
+
+ for rng, expected in [
+ (rng2, expected2),
+ (rng3, expected3),
+ (rng4, expected4),
+ ]:
+ result = base.intersection(rng)
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ # non-monotonic
+ base = DatetimeIndex(
+ ["2011-01-05", "2011-01-04", "2011-01-02", "2011-01-03"], tz=tz, name="idx"
+ ).as_unit("ns")
+
+ rng2 = DatetimeIndex(
+ ["2011-01-04", "2011-01-02", "2011-02-02", "2011-02-03"], tz=tz, name="idx"
+ ).as_unit("ns")
+ expected2 = DatetimeIndex(
+ ["2011-01-04", "2011-01-02"], tz=tz, name="idx"
+ ).as_unit("ns")
+
+ rng3 = DatetimeIndex(
+ ["2011-01-04", "2011-01-02", "2011-02-02", "2011-02-03"],
+ tz=tz,
+ name="other",
+ ).as_unit("ns")
+ expected3 = DatetimeIndex(
+ ["2011-01-04", "2011-01-02"], tz=tz, name=None
+ ).as_unit("ns")
+
+ # GH 7880
+ rng4 = date_range(
+ "7/1/2000", "7/31/2000", freq="D", tz=tz, unit="ns", name="idx"
+ )
+ expected4 = DatetimeIndex([], tz=tz, name="idx").as_unit("ns")
+ assert expected4.freq is None
+
+ for rng, expected in [
+ (rng2, expected2),
+ (rng3, expected3),
+ (rng4, expected4),
+ ]:
+ result = base.intersection(rng, sort=sort)
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ # parametrize over both anchored and non-anchored freqs, as they
+ # have different code paths
+ @pytest.mark.parametrize("freq", ["min", "B"])
+ def test_intersection_empty(self, tz_aware_fixture, freq):
+ # empty same freq GH2129
+ tz = tz_aware_fixture
+ rng = date_range("6/1/2000", "6/15/2000", freq=freq, tz=tz)
+ result = rng[0:0].intersection(rng)
+ assert len(result) == 0
+ assert result.freq == rng.freq
+
+ result = rng.intersection(rng[0:0])
+ assert len(result) == 0
+ assert result.freq == rng.freq
+
+ # no overlap GH#33604
+ check_freq = freq != "min" # We don't preserve freq on non-anchored offsets
+ result = rng[:3].intersection(rng[-3:])
+ tm.assert_index_equal(result, rng[:0])
+ if check_freq:
+ # We don't preserve freq on non-anchored offsets
+ assert result.freq == rng.freq
+
+ # swapped left and right
+ result = rng[-3:].intersection(rng[:3])
+ tm.assert_index_equal(result, rng[:0])
+ if check_freq:
+ # We don't preserve freq on non-anchored offsets
+ assert result.freq == rng.freq
+
+ def test_intersection_bug_1708(self):
+ from pandas import DateOffset
+
+ index_1 = date_range("1/1/2012", periods=4, freq="12h")
+ index_2 = index_1 + DateOffset(hours=1)
+
+ result = index_1.intersection(index_2)
+ assert len(result) == 0
+
+ @pytest.mark.parametrize("tz", tz)
+ def test_difference(self, tz, sort):
+ rng_dates = ["1/2/2000", "1/3/2000", "1/1/2000", "1/4/2000", "1/5/2000"]
+
+ rng1 = DatetimeIndex(rng_dates, tz=tz)
+ other1 = date_range("1/6/2000", freq="D", periods=5, tz=tz)
+ expected1 = DatetimeIndex(rng_dates, tz=tz)
+
+ rng2 = DatetimeIndex(rng_dates, tz=tz)
+ other2 = date_range("1/4/2000", freq="D", periods=5, tz=tz)
+ expected2 = DatetimeIndex(rng_dates[:3], tz=tz)
+
+ rng3 = DatetimeIndex(rng_dates, tz=tz)
+ other3 = DatetimeIndex([], tz=tz)
+ expected3 = DatetimeIndex(rng_dates, tz=tz)
+
+ for rng, other, expected in [
+ (rng1, other1, expected1),
+ (rng2, other2, expected2),
+ (rng3, other3, expected3),
+ ]:
+ result_diff = rng.difference(other, sort)
+ if sort is None and len(other):
+ # We dont sort (yet?) when empty GH#24959
+ expected = expected.sort_values()
+ tm.assert_index_equal(result_diff, expected)
+
+ def test_difference_freq(self, sort):
+ # GH14323: difference of DatetimeIndex should not preserve frequency
+
+ index = date_range("20160920", "20160925", freq="D", unit="ns")
+ other = date_range("20160921", "20160924", freq="D", unit="ns")
+ expected = DatetimeIndex(["20160920", "20160925"], dtype="M8[ns]", freq=None)
+ idx_diff = index.difference(other, sort)
+ tm.assert_index_equal(idx_diff, expected)
+ tm.assert_attr_equal("freq", idx_diff, expected)
+
+ # preserve frequency when the difference is a contiguous
+ # subset of the original range
+ other = date_range("20160922", "20160925", freq="D", unit="ns")
+ idx_diff = index.difference(other, sort)
+ expected = DatetimeIndex(["20160920", "20160921"], dtype="M8[ns]", freq="D")
+ tm.assert_index_equal(idx_diff, expected)
+ tm.assert_attr_equal("freq", idx_diff, expected)
+
+ def test_datetimeindex_diff(self, sort):
+ dti1 = date_range(freq="QE-JAN", start=datetime(1997, 12, 31), periods=100)
+ dti2 = date_range(freq="QE-JAN", start=datetime(1997, 12, 31), periods=98)
+ assert len(dti1.difference(dti2, sort)) == 2
+
+ @pytest.mark.parametrize("tz", [None, "Asia/Tokyo", "US/Eastern"])
+ def test_setops_preserve_freq(self, tz):
+ rng = date_range("1/1/2000", "1/1/2002", name="idx", tz=tz)
+
+ result = rng[:50].union(rng[50:100])
+ assert result.name == rng.name
+ assert result.freq == rng.freq
+ assert result.tz == rng.tz
+
+ result = rng[:50].union(rng[30:100])
+ assert result.name == rng.name
+ assert result.freq == rng.freq
+ assert result.tz == rng.tz
+
+ result = rng[:50].union(rng[60:100])
+ assert result.name == rng.name
+ assert result.freq is None
+ assert result.tz == rng.tz
+
+ result = rng[:50].intersection(rng[25:75])
+ assert result.name == rng.name
+ assert result.freqstr == "D"
+ assert result.tz == rng.tz
+
+ nofreq = DatetimeIndex(list(rng[25:75]), name="other")
+ result = rng[:50].union(nofreq)
+ assert result.name is None
+ assert result.freq == rng.freq
+ assert result.tz == rng.tz
+
+ result = rng[:50].intersection(nofreq)
+ assert result.name is None
+ assert result.freq == rng.freq
+ assert result.tz == rng.tz
+
+ def test_intersection_non_tick_no_fastpath(self):
+ # GH#42104
+ dti = DatetimeIndex(
+ [
+ "2018-12-31",
+ "2019-03-31",
+ "2019-06-30",
+ "2019-09-30",
+ "2019-12-31",
+ "2020-03-31",
+ ],
+ freq="QE-DEC",
+ )
+ result = dti[::2].intersection(dti[1::2])
+ expected = dti[:0]
+ tm.assert_index_equal(result, expected)
+
+ def test_dti_intersection(self):
+ rng = date_range("1/1/2011", periods=100, freq="h", tz="utc")
+
+ left = rng[10:90][::-1]
+ right = rng[20:80][::-1]
+
+ assert left.tz == rng.tz
+ result = left.intersection(right)
+ assert result.tz == left.tz
+
+ # Note: not difference, as there is no symmetry requirement there
+ @pytest.mark.parametrize("setop", ["union", "intersection", "symmetric_difference"])
+ def test_dti_setop_aware(self, setop):
+ # non-overlapping
+ # GH#39328 as of 2.0 we cast these to UTC instead of object
+ rng = date_range("2012-11-15 00:00:00", periods=6, freq="h", tz="US/Central")
+
+ rng2 = date_range("2012-11-15 12:00:00", periods=6, freq="h", tz="US/Eastern")
+
+ result = getattr(rng, setop)(rng2)
+
+ left = rng.tz_convert("UTC")
+ right = rng2.tz_convert("UTC")
+ expected = getattr(left, setop)(right)
+ tm.assert_index_equal(result, expected)
+ assert result.tz == left.tz
+ if len(result):
+ assert result[0].tz is timezone.utc
+ assert result[-1].tz is timezone.utc
+
+ def test_dti_union_mixed(self):
+ # GH#21671
+ rng = DatetimeIndex([Timestamp("2011-01-01"), pd.NaT])
+ rng2 = DatetimeIndex(["2012-01-01", "2012-01-02"], tz="Asia/Tokyo")
+ result = rng.union(rng2)
+ expected = Index(
+ [
+ Timestamp("2011-01-01"),
+ pd.NaT,
+ Timestamp("2012-01-01", tz="Asia/Tokyo"),
+ Timestamp("2012-01-02", tz="Asia/Tokyo"),
+ ],
+ dtype=object,
+ )
+ tm.assert_index_equal(result, expected)
+
+
+class TestBusinessDatetimeIndex:
+ def test_union(self, sort):
+ rng = bdate_range(START, END)
+ # overlapping
+ left = rng[:10]
+ right = rng[5:10]
+
+ the_union = left.union(right, sort=sort)
+ assert isinstance(the_union, DatetimeIndex)
+
+ # non-overlapping, gap in middle
+ left = rng[:5]
+ right = rng[10:]
+
+ the_union = left.union(right, sort=sort)
+ assert isinstance(the_union, Index)
+
+ # non-overlapping, no gap
+ left = rng[:5]
+ right = rng[5:10]
+
+ the_union = left.union(right, sort=sort)
+ assert isinstance(the_union, DatetimeIndex)
+
+ # order does not matter
+ if sort is None:
+ tm.assert_index_equal(right.union(left, sort=sort), the_union)
+ else:
+ expected = DatetimeIndex(list(right) + list(left))
+ tm.assert_index_equal(right.union(left, sort=sort), expected)
+
+ # overlapping, but different offset
+ rng = date_range(START, END, freq=BMonthEnd())
+
+ the_union = rng.union(rng, sort=sort)
+ assert isinstance(the_union, DatetimeIndex)
+
+ def test_union_not_cacheable(self, sort):
+ rng = date_range("1/1/2000", periods=50, freq=Minute())
+ rng1 = rng[10:]
+ rng2 = rng[:25]
+ the_union = rng1.union(rng2, sort=sort)
+ if sort is None:
+ tm.assert_index_equal(the_union, rng)
+ else:
+ expected = DatetimeIndex(list(rng[10:]) + list(rng[:10]))
+ tm.assert_index_equal(the_union, expected)
+
+ rng1 = rng[10:]
+ rng2 = rng[15:35]
+ the_union = rng1.union(rng2, sort=sort)
+ expected = rng[10:]
+ tm.assert_index_equal(the_union, expected)
+
+ def test_intersection(self):
+ rng = date_range("1/1/2000", periods=50, freq=Minute(), unit="ns")
+ rng1 = rng[10:]
+ rng2 = rng[:25]
+ the_int = rng1.intersection(rng2)
+ expected = rng[10:25]
+ tm.assert_index_equal(the_int, expected)
+ assert isinstance(the_int, DatetimeIndex)
+ assert the_int.freq == rng.freq
+
+ the_int = rng1.intersection(rng2)
+ tm.assert_index_equal(the_int, expected)
+
+ # non-overlapping
+ the_int = rng[:10].intersection(rng[10:])
+ expected = DatetimeIndex([]).as_unit("ns")
+ tm.assert_index_equal(the_int, expected)
+
+ def test_intersection_bug(self):
+ # GH #771
+ a = bdate_range("11/30/2011", "12/31/2011")
+ b = bdate_range("12/10/2011", "12/20/2011")
+ result = a.intersection(b)
+ tm.assert_index_equal(result, b)
+ assert result.freq == b.freq
+
+ def test_intersection_list(self):
+ # GH#35876
+ # values is not an Index -> no name -> retain "a"
+ values = [Timestamp("2020-01-01"), Timestamp("2020-02-01")]
+ idx = DatetimeIndex(values, name="a")
+ res = idx.intersection(values)
+ tm.assert_index_equal(res, idx)
+
+ def test_month_range_union_tz_pytz(self, sort):
+ pytz = pytest.importorskip("pytz")
+ tz = pytz.timezone("US/Eastern")
+
+ early_start = datetime(2011, 1, 1)
+ early_end = datetime(2011, 3, 1)
+
+ late_start = datetime(2011, 3, 1)
+ late_end = datetime(2011, 5, 1)
+
+ early_dr = date_range(start=early_start, end=early_end, tz=tz, freq=MonthEnd())
+ late_dr = date_range(start=late_start, end=late_end, tz=tz, freq=MonthEnd())
+
+ early_dr.union(late_dr, sort=sort)
+
+ @td.skip_if_windows
+ def test_month_range_union_tz_dateutil(self, sort):
+ from pandas._libs.tslibs.timezones import dateutil_gettz
+
+ tz = dateutil_gettz("US/Eastern")
+
+ early_start = datetime(2011, 1, 1)
+ early_end = datetime(2011, 3, 1)
+
+ late_start = datetime(2011, 3, 1)
+ late_end = datetime(2011, 5, 1)
+
+ early_dr = date_range(start=early_start, end=early_end, tz=tz, freq=MonthEnd())
+ late_dr = date_range(start=late_start, end=late_end, tz=tz, freq=MonthEnd())
+
+ early_dr.union(late_dr, sort=sort)
+
+ @pytest.mark.parametrize("sort", [False, None])
+ def test_intersection_duplicates(self, sort):
+ # GH#38196
+ idx1 = Index(
+ [
+ Timestamp("2019-12-13"),
+ Timestamp("2019-12-12"),
+ Timestamp("2019-12-12"),
+ ]
+ )
+ result = idx1.intersection(idx1, sort=sort)
+ expected = Index([Timestamp("2019-12-13"), Timestamp("2019-12-12")])
+ tm.assert_index_equal(result, expected)
+
+
+class TestCustomDatetimeIndex:
+ def test_union(self, sort):
+ # overlapping
+ rng = bdate_range(START, END, freq="C")
+ left = rng[:10]
+ right = rng[5:10]
+
+ the_union = left.union(right, sort=sort)
+ assert isinstance(the_union, DatetimeIndex)
+
+ # non-overlapping, gap in middle
+ left = rng[:5]
+ right = rng[10:]
+
+ the_union = left.union(right, sort)
+ assert isinstance(the_union, Index)
+
+ # non-overlapping, no gap
+ left = rng[:5]
+ right = rng[5:10]
+
+ the_union = left.union(right, sort=sort)
+ assert isinstance(the_union, DatetimeIndex)
+
+ # order does not matter
+ if sort is None:
+ tm.assert_index_equal(right.union(left, sort=sort), the_union)
+
+ # overlapping, but different offset
+ rng = date_range(START, END, freq=BMonthEnd())
+
+ the_union = rng.union(rng, sort=sort)
+ assert isinstance(the_union, DatetimeIndex)
+
+ def test_intersection_bug(self):
+ # GH #771
+ a = bdate_range("11/30/2011", "12/31/2011", freq="C")
+ b = bdate_range("12/10/2011", "12/20/2011", freq="C")
+ result = a.intersection(b)
+ tm.assert_index_equal(result, b)
+ assert result.freq == b.freq
+
+ @pytest.mark.parametrize(
+ "tz", [None, "UTC", "Europe/Berlin", timezone(timedelta(hours=-1))]
+ )
+ def test_intersection_dst_transition(self, tz):
+ # GH 46702: Europe/Berlin has DST transition
+ idx1 = date_range("2020-03-27", periods=5, freq="D", tz=tz)
+ idx2 = date_range("2020-03-30", periods=5, freq="D", tz=tz)
+ result = idx1.intersection(idx2)
+ expected = date_range("2020-03-30", periods=2, freq="D", tz=tz)
+ tm.assert_index_equal(result, expected)
+
+ # GH#45863 same problem for union
+ index1 = date_range("2021-10-28", periods=3, freq="D", tz="Europe/London")
+ index2 = date_range("2021-10-30", periods=4, freq="D", tz="Europe/London")
+ result = index1.union(index2)
+ expected = date_range("2021-10-28", periods=6, freq="D", tz="Europe/London")
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_non_nano_rangelike():
+ # GH 59036
+ l1 = DatetimeIndex(
+ ["2024-05-11", "2024-05-12"], dtype="datetime64[us]", name="Date", freq="D"
+ )
+ l2 = DatetimeIndex(["2024-05-13"], dtype="datetime64[us]", name="Date", freq="D")
+ result = l1.union(l2)
+ expected = DatetimeIndex(
+ ["2024-05-11", "2024-05-12", "2024-05-13"],
+ dtype="datetime64[us]",
+ name="Date",
+ freq="D",
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_intersection_non_nano_rangelike():
+ # GH 59271
+ l1 = date_range("2024-01-01", "2024-01-03", unit="s")
+ l2 = date_range("2024-01-02", "2024-01-04", unit="s")
+ result = l1.intersection(l2)
+ expected = DatetimeIndex(
+ ["2024-01-02", "2024-01-03"],
+ dtype="datetime64[s]",
+ freq="D",
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_across_dst_boundary():
+ # GH#62915 union should work when one index ends at DST boundary
+ # and the other extends past it
+ index1 = date_range("2025-10-25", "2025-10-26", freq="D", tz="Europe/Helsinki")
+ index2 = date_range("2025-10-25", "2025-10-28", freq="D", tz="Europe/Helsinki")
+ result = index1.union(index2)
+ expected = date_range("2025-10-25", "2025-10-28", freq="D", tz="Europe/Helsinki")
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_timezones.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_timezones.py
new file mode 100644
index 0000000000000000000000000000000000000000..897b12d4686f538f6c722895529a5d902db907f7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/datetimes/test_timezones.py
@@ -0,0 +1,256 @@
+"""
+Tests for DatetimeIndex timezone-related methods
+"""
+
+from datetime import (
+ datetime,
+ timedelta,
+ timezone,
+ tzinfo,
+)
+import zoneinfo
+
+from dateutil.tz import gettz
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import (
+ conversion,
+ timezones,
+)
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Timestamp,
+ bdate_range,
+ date_range,
+ isna,
+ to_datetime,
+)
+import pandas._testing as tm
+
+
+class FixedOffset(tzinfo):
+ """Fixed offset in minutes east from UTC."""
+
+ def __init__(self, offset, name) -> None:
+ self.__offset = timedelta(minutes=offset)
+ self.__name = name
+
+ def utcoffset(self, dt):
+ return self.__offset
+
+ def tzname(self, dt):
+ return self.__name
+
+ def dst(self, dt):
+ return timedelta(0)
+
+
+fixed_off_no_name = FixedOffset(-330, None)
+
+
+class TestDatetimeIndexTimezones:
+ # -------------------------------------------------------------
+ # Unsorted
+
+ def test_dti_drop_dont_lose_tz(self):
+ # GH#2621
+ ind = date_range("2012-12-01", periods=10, tz="utc")
+ ind = ind.drop(ind[-1])
+
+ assert ind.tz is not None
+
+ def test_dti_tz_conversion_freq(self, tz_naive_fixture):
+ # GH25241
+ t3 = DatetimeIndex(["2019-01-01 10:00"], freq="h")
+ assert t3.tz_localize(tz=tz_naive_fixture).freq == t3.freq
+ t4 = DatetimeIndex(["2019-01-02 12:00"], tz="UTC", freq="min")
+ assert t4.tz_convert(tz="UTC").freq == t4.freq
+
+ def test_drop_dst_boundary(self):
+ # see gh-18031
+ tz = "Europe/Brussels"
+ freq = "15min"
+
+ start = Timestamp("201710290100", tz=tz)
+ end = Timestamp("201710290300", tz=tz)
+ index = date_range(start=start, end=end, freq=freq, unit="ns")
+
+ expected = DatetimeIndex(
+ [
+ "201710290115",
+ "201710290130",
+ "201710290145",
+ "201710290200",
+ "201710290215",
+ "201710290230",
+ "201710290245",
+ "201710290200",
+ "201710290215",
+ "201710290230",
+ "201710290245",
+ "201710290300",
+ ],
+ dtype="M8[ns, Europe/Brussels]",
+ freq=freq,
+ ambiguous=[
+ True,
+ True,
+ True,
+ True,
+ True,
+ True,
+ True,
+ False,
+ False,
+ False,
+ False,
+ False,
+ ],
+ )
+ result = index.drop(index[0])
+ tm.assert_index_equal(result, expected)
+
+ def test_date_range_localize(self, unit):
+ rng = date_range(
+ "3/11/2012 03:00", periods=15, freq="h", tz="US/Eastern", unit=unit
+ )
+ rng2 = DatetimeIndex(
+ ["3/11/2012 03:00", "3/11/2012 04:00"], dtype=f"M8[{unit}, US/Eastern]"
+ )
+ rng3 = date_range("3/11/2012 03:00", periods=15, freq="h", unit=unit)
+ rng3 = rng3.tz_localize("US/Eastern")
+
+ tm.assert_index_equal(rng._with_freq(None), rng3)
+
+ # DST transition time
+ val = rng[0]
+ exp = Timestamp("3/11/2012 03:00", tz="US/Eastern")
+
+ assert val.hour == 3
+ assert exp.hour == 3
+ assert val == exp # same UTC value
+ tm.assert_index_equal(rng[:2], rng2)
+
+ def test_date_range_localize2(self, unit):
+ # Right before the DST transition
+ rng = date_range(
+ "3/11/2012 00:00", periods=2, freq="h", tz="US/Eastern", unit=unit
+ )
+ rng2 = DatetimeIndex(
+ ["3/11/2012 00:00", "3/11/2012 01:00"],
+ dtype=f"M8[{unit}, US/Eastern]",
+ freq="h",
+ )
+ tm.assert_index_equal(rng, rng2)
+ exp = Timestamp("3/11/2012 00:00", tz="US/Eastern")
+ assert exp.hour == 0
+ assert rng[0] == exp
+ exp = Timestamp("3/11/2012 01:00", tz="US/Eastern")
+ assert exp.hour == 1
+ assert rng[1] == exp
+
+ rng = date_range(
+ "3/11/2012 00:00", periods=10, freq="h", tz="US/Eastern", unit=unit
+ )
+ assert rng[2].hour == 3
+
+ def test_timestamp_equality_different_timezones(self):
+ utc_range = date_range("1/1/2000", periods=20, tz="UTC")
+ eastern_range = utc_range.tz_convert("US/Eastern")
+ berlin_range = utc_range.tz_convert("Europe/Berlin")
+
+ for a, b, c in zip(utc_range, eastern_range, berlin_range):
+ assert a == b
+ assert b == c
+ assert a == c
+
+ assert (utc_range == eastern_range).all()
+ assert (utc_range == berlin_range).all()
+ assert (berlin_range == eastern_range).all()
+
+ def test_dti_equals_with_tz(self):
+ left = date_range("1/1/2011", periods=100, freq="h", tz="utc")
+ right = date_range("1/1/2011", periods=100, freq="h", tz="US/Eastern")
+
+ assert not left.equals(right)
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_dti_tz_nat(self, tzstr):
+ idx = DatetimeIndex([Timestamp("2013-1-1", tz=tzstr), pd.NaT])
+
+ assert isna(idx[1])
+ assert idx[0].tzinfo is not None
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_utc_box_timestamp_and_localize(self, tzstr):
+ tz = timezones.maybe_get_tz(tzstr)
+
+ rng = date_range("3/11/2012", "3/12/2012", freq="h", tz="utc")
+ rng_eastern = rng.tz_convert(tzstr)
+
+ expected = rng[-1].astimezone(tz)
+
+ stamp = rng_eastern[-1]
+ assert stamp == expected
+ assert stamp.tzinfo == expected.tzinfo
+
+ # right tzinfo
+ rng = date_range("3/13/2012", "3/14/2012", freq="h", tz="utc")
+ rng_eastern = rng.tz_convert(tzstr)
+ if "dateutil" in tzstr:
+ assert "EDT" in repr(rng_eastern[0].tzinfo) or "tzfile" in repr(
+ rng_eastern[0].tzinfo
+ )
+
+ @pytest.mark.parametrize(
+ "tz", [zoneinfo.ZoneInfo("US/Central"), gettz("US/Central")]
+ )
+ def test_with_tz(self, tz):
+ # just want it to work
+ start = datetime(2011, 3, 12, tzinfo=timezone.utc)
+ dr = bdate_range(start, periods=50, freq=pd.offsets.Hour())
+ assert dr.tz is timezone.utc
+
+ # DateRange with naive datetimes
+ dr = bdate_range("1/1/2005", "1/1/2009", tz=timezone.utc)
+ dr = bdate_range("1/1/2005", "1/1/2009", tz=tz)
+
+ # normalized
+ central = dr.tz_convert(tz)
+ assert central.tz is tz
+ naive = central[0].to_pydatetime().replace(tzinfo=None)
+ comp = conversion.localize_pydatetime(naive, tz).tzinfo
+ assert central[0].tz is comp
+
+ # compare vs a localized tz
+ naive = dr[0].to_pydatetime().replace(tzinfo=None)
+ comp = conversion.localize_pydatetime(naive, tz).tzinfo
+ assert central[0].tz is comp
+
+ # datetimes with tzinfo set
+ dr = bdate_range(
+ datetime(2005, 1, 1, tzinfo=timezone.utc),
+ datetime(2009, 1, 1, tzinfo=timezone.utc),
+ )
+ msg = "Start and end cannot both be tz-aware with different timezones"
+ with pytest.raises(Exception, match=msg):
+ bdate_range(datetime(2005, 1, 1, tzinfo=timezone.utc), "1/1/2009", tz=tz)
+
+ @pytest.mark.parametrize(
+ "tz", [zoneinfo.ZoneInfo("US/Eastern"), gettz("US/Eastern")]
+ )
+ def test_dti_convert_tz_aware_datetime_datetime(self, tz):
+ # GH#1581
+ dates = [datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)]
+
+ dates_aware = [conversion.localize_pydatetime(x, tz) for x in dates]
+ result = DatetimeIndex(dates_aware).as_unit("ns")
+ assert timezones.tz_compare(result.tz, tz)
+
+ converted = to_datetime(dates_aware, utc=True).as_unit("ns")
+ ex_vals = np.array([Timestamp(x).as_unit("ns")._value for x in dates_aware])
+ tm.assert_numpy_array_equal(converted.asi8, ex_vals)
+ assert converted.tz is timezone.utc
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e6c67d8610d8ff68560e41131fed225028d6de1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_astype.py
@@ -0,0 +1,262 @@
+import re
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.dtypes import (
+ CategoricalDtype,
+ IntervalDtype,
+)
+
+from pandas import (
+ CategoricalIndex,
+ Index,
+ IntervalIndex,
+ NaT,
+ Timedelta,
+ Timestamp,
+ interval_range,
+)
+import pandas._testing as tm
+
+
+class AstypeTests:
+ """Tests common to IntervalIndex with any subtype"""
+
+ def test_astype_idempotent(self, index):
+ result = index.astype("interval")
+ tm.assert_index_equal(result, index)
+
+ result = index.astype(index.dtype)
+ tm.assert_index_equal(result, index)
+
+ def test_astype_object(self, index):
+ result = index.astype(object)
+ expected = Index(index.values, dtype="object")
+ tm.assert_index_equal(result, expected)
+ assert not result.equals(index)
+
+ def test_astype_category(self, index):
+ result = index.astype("category")
+ expected = CategoricalIndex(index.values)
+ tm.assert_index_equal(result, expected)
+
+ result = index.astype(CategoricalDtype())
+ tm.assert_index_equal(result, expected)
+
+ # non-default params
+ categories = index.dropna().unique().values[:-1]
+ dtype = CategoricalDtype(categories=categories, ordered=True)
+ msg = "Constructing a Categorical with a dtype and values containing"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = index.astype(dtype)
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = CategoricalIndex(
+ index.values, categories=categories, ordered=True
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ "int64",
+ "uint64",
+ "float64",
+ "complex128",
+ "period[M]",
+ "timedelta64",
+ "timedelta64[ns]",
+ "datetime64",
+ "datetime64[ns]",
+ "datetime64[ns, US/Eastern]",
+ ],
+ )
+ def test_astype_cannot_cast(self, index, dtype):
+ msg = "Cannot cast IntervalIndex to dtype"
+ with pytest.raises(TypeError, match=msg):
+ index.astype(dtype)
+
+ def test_astype_invalid_dtype(self, index):
+ msg = "data type [\"']fake_dtype[\"'] not understood"
+ with pytest.raises(TypeError, match=msg):
+ index.astype("fake_dtype")
+
+
+class TestIntSubtype(AstypeTests):
+ """Tests specific to IntervalIndex with integer-like subtype"""
+
+ indexes = [
+ IntervalIndex.from_breaks(np.arange(-10, 11, dtype="int64")),
+ IntervalIndex.from_breaks(np.arange(100, dtype="uint64"), closed="left"),
+ ]
+
+ @pytest.fixture(params=indexes)
+ def index(self, request):
+ return request.param
+
+ @pytest.mark.parametrize(
+ "subtype", ["float64", "datetime64[ns]", "timedelta64[ns]"]
+ )
+ def test_subtype_conversion(self, index, subtype):
+ dtype = IntervalDtype(subtype, index.closed)
+ result = index.astype(dtype)
+ expected = IntervalIndex.from_arrays(
+ index.left.astype(subtype), index.right.astype(subtype), closed=index.closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "subtype_start, subtype_end", [("int64", "uint64"), ("uint64", "int64")]
+ )
+ def test_subtype_integer(self, subtype_start, subtype_end):
+ index = IntervalIndex.from_breaks(np.arange(100, dtype=subtype_start))
+ dtype = IntervalDtype(subtype_end, index.closed)
+ result = index.astype(dtype)
+ expected = IntervalIndex.from_arrays(
+ index.left.astype(subtype_end),
+ index.right.astype(subtype_end),
+ closed=index.closed,
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.xfail(reason="GH#15832")
+ def test_subtype_integer_errors(self):
+ # int64 -> uint64 fails with negative values
+ index = interval_range(-10, 10)
+ dtype = IntervalDtype("uint64", "right")
+
+ # Until we decide what the exception message _should_ be, we
+ # assert something that it should _not_ be.
+ # We should _not_ be getting a message suggesting that the -10
+ # has been wrapped around to a large-positive integer
+ msg = "^(?!(left side of interval must be <= right side))"
+ with pytest.raises(ValueError, match=msg):
+ index.astype(dtype)
+
+
+class TestFloatSubtype(AstypeTests):
+ """Tests specific to IntervalIndex with float subtype"""
+
+ indexes = [
+ interval_range(-10.0, 10.0, closed="neither"),
+ IntervalIndex.from_arrays(
+ [-1.5, np.nan, 0.0, 0.0, 1.5], [-0.5, np.nan, 1.0, 1.0, 3.0], closed="both"
+ ),
+ ]
+
+ @pytest.fixture(params=indexes)
+ def index(self, request):
+ return request.param
+
+ @pytest.mark.parametrize("subtype", ["int64", "uint64"])
+ def test_subtype_integer(self, subtype):
+ index = interval_range(0.0, 10.0)
+ dtype = IntervalDtype(subtype, "right")
+ result = index.astype(dtype)
+ expected = IntervalIndex.from_arrays(
+ index.left.astype(subtype), index.right.astype(subtype), closed=index.closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # raises with NA
+ msg = r"Cannot convert non-finite values \(NA or inf\) to integer"
+ with pytest.raises(ValueError, match=msg):
+ index.insert(0, np.nan).astype(dtype)
+
+ @pytest.mark.parametrize("subtype", ["int64", "uint64"])
+ def test_subtype_integer_with_non_integer_borders(self, subtype):
+ index = interval_range(0.0, 3.0, freq=0.25)
+ dtype = IntervalDtype(subtype, "right")
+ result = index.astype(dtype)
+ expected = IntervalIndex.from_arrays(
+ index.left.astype(subtype), index.right.astype(subtype), closed=index.closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_subtype_integer_errors(self):
+ # float64 -> uint64 fails with negative values
+ index = interval_range(-10.0, 10.0)
+ dtype = IntervalDtype("uint64", "right")
+ msg = re.escape(
+ "Cannot convert interval[float64, right] to interval[uint64, right]; "
+ "subtypes are incompatible"
+ )
+ with pytest.raises(TypeError, match=msg):
+ index.astype(dtype)
+
+ @pytest.mark.parametrize("subtype", ["datetime64[ns]", "timedelta64[ns]"])
+ def test_subtype_datetimelike(self, index, subtype):
+ dtype = IntervalDtype(subtype, "right")
+ msg = "Cannot convert .* to .*; subtypes are incompatible"
+ with pytest.raises(TypeError, match=msg):
+ index.astype(dtype)
+
+ @pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in cast:RuntimeWarning"
+ )
+ def test_astype_category(self, index):
+ super().test_astype_category(index)
+
+
+class TestDatetimelikeSubtype(AstypeTests):
+ """Tests specific to IntervalIndex with datetime-like subtype"""
+
+ indexes = [
+ interval_range(Timestamp("2018-01-01"), periods=10, closed="neither"),
+ interval_range(Timestamp("2018-01-01"), periods=10).insert(2, NaT),
+ interval_range(Timestamp("2018-01-01", tz="US/Eastern"), periods=10),
+ interval_range(Timedelta("0 days"), periods=10, closed="both"),
+ interval_range(Timedelta("0 days"), periods=10).insert(2, NaT),
+ ]
+
+ @pytest.fixture(params=indexes)
+ def index(self, request):
+ return request.param
+
+ @pytest.mark.parametrize("subtype", ["int64", "uint64"])
+ def test_subtype_integer(self, index, subtype):
+ dtype = IntervalDtype(subtype, "right")
+
+ if subtype != "int64":
+ msg = (
+ r"Cannot convert interval"
+ r"\[(timedelta64\[us\]|datetime64\[us(, US/Eastern)?\]), .*\] "
+ r"to interval\[uint64, .*\]"
+ )
+ with pytest.raises(TypeError, match=msg):
+ index.astype(dtype)
+ return
+
+ result = index.astype(dtype)
+ new_left = index.left.astype(subtype)
+ new_right = index.right.astype(subtype)
+
+ expected = IntervalIndex.from_arrays(new_left, new_right, closed=index.closed)
+ tm.assert_index_equal(result, expected)
+
+ def test_subtype_float(self, index):
+ dtype = IntervalDtype("float64", "right")
+ msg = "Cannot convert .* to .*; subtypes are incompatible"
+ with pytest.raises(TypeError, match=msg):
+ index.astype(dtype)
+
+ def test_subtype_datetimelike(self):
+ # datetime -> timedelta raises
+ dtype = IntervalDtype("timedelta64[ns]", "right")
+ msg = "Cannot convert .* to .*; subtypes are incompatible"
+
+ index = interval_range(Timestamp("2018-01-01"), periods=10)
+ with pytest.raises(TypeError, match=msg):
+ index.astype(dtype)
+
+ index = interval_range(Timestamp("2018-01-01", tz="CET"), periods=10)
+ with pytest.raises(TypeError, match=msg):
+ index.astype(dtype)
+
+ # timedelta -> datetime raises
+ dtype = IntervalDtype("datetime64[ns]", "right")
+ index = interval_range(Timedelta("0 days"), periods=10)
+ with pytest.raises(TypeError, match=msg):
+ index.astype(dtype)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..b1ea1460c67d4ef974348eefc9d2ee37f8d41b1e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_constructors.py
@@ -0,0 +1,527 @@
+from functools import partial
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.common import is_unsigned_integer_dtype
+from pandas.core.dtypes.dtypes import IntervalDtype
+
+from pandas import (
+ Categorical,
+ CategoricalDtype,
+ CategoricalIndex,
+ Index,
+ Interval,
+ IntervalIndex,
+ date_range,
+ notna,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import IntervalArray
+import pandas.core.common as com
+
+
+class ConstructorTests:
+ """
+ Common tests for all variations of IntervalIndex construction. Input data
+ to be supplied in breaks format, then converted by the subclass method
+ get_kwargs_from_breaks to the expected format.
+ """
+
+ @pytest.mark.parametrize(
+ "breaks_and_expected_subtype",
+ [
+ ([3, 14, 15, 92, 653], np.int64),
+ (np.arange(10, dtype="int64"), np.int64),
+ (Index(np.arange(-10, 11, dtype=np.int64)), np.int64),
+ (Index(np.arange(10, 31, dtype=np.uint64)), np.uint64),
+ (Index(np.arange(20, 30, 0.5), dtype=np.float64), np.float64),
+ (date_range("20180101", periods=10, unit="ns"), "M8[ns]"),
+ (
+ date_range("20180101", periods=10, tz="US/Eastern", unit="ns"),
+ "datetime64[ns, US/Eastern]",
+ ),
+ (timedelta_range("1 day", periods=10), "m8[us]"),
+ ],
+ )
+ @pytest.mark.parametrize("name", [None, "foo"])
+ def test_constructor(self, constructor, breaks_and_expected_subtype, closed, name):
+ breaks, expected_subtype = breaks_and_expected_subtype
+
+ result_kwargs = self.get_kwargs_from_breaks(breaks, closed)
+
+ result = constructor(closed=closed, name=name, **result_kwargs)
+
+ assert result.closed == closed
+ assert result.name == name
+ assert result.dtype.subtype == expected_subtype
+ tm.assert_index_equal(result.left, Index(breaks[:-1], dtype=expected_subtype))
+ tm.assert_index_equal(result.right, Index(breaks[1:], dtype=expected_subtype))
+
+ @pytest.mark.parametrize(
+ "breaks, subtype",
+ [
+ (Index([0, 1, 2, 3, 4], dtype=np.int64), "float64"),
+ (Index([0, 1, 2, 3, 4], dtype=np.int64), "datetime64[ns]"),
+ (Index([0, 1, 2, 3, 4], dtype=np.int64), "timedelta64[ns]"),
+ (Index([0, 1, 2, 3, 4], dtype=np.float64), "int64"),
+ (date_range("2017-01-01", periods=5, unit="ns"), "int64"),
+ (timedelta_range("1 day", periods=5), "int64"),
+ ],
+ )
+ def test_constructor_dtype(self, constructor, breaks, subtype):
+ # GH 19262: conversion via dtype parameter
+ expected_kwargs = self.get_kwargs_from_breaks(breaks.astype(subtype))
+ expected = constructor(**expected_kwargs)
+
+ result_kwargs = self.get_kwargs_from_breaks(breaks)
+ iv_dtype = IntervalDtype(subtype, "right")
+ for dtype in (iv_dtype, str(iv_dtype)):
+ result = constructor(dtype=dtype, **result_kwargs)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "breaks",
+ [
+ Index([0, 1, 2, 3, 4], dtype=np.int64),
+ Index([0, 1, 2, 3, 4], dtype=np.uint64),
+ Index([0, 1, 2, 3, 4], dtype=np.float64),
+ date_range("2017-01-01", periods=5, unit="ns"),
+ timedelta_range("1 day", periods=5),
+ ],
+ )
+ def test_constructor_pass_closed(self, constructor, breaks):
+ # not passing closed to IntervalDtype, but to IntervalArray constructor
+ iv_dtype = IntervalDtype(breaks.dtype)
+
+ result_kwargs = self.get_kwargs_from_breaks(breaks)
+
+ for dtype in (iv_dtype, str(iv_dtype)):
+ with tm.assert_produces_warning(None):
+ result = constructor(dtype=dtype, closed="left", **result_kwargs)
+ assert result.dtype.closed == "left"
+
+ @pytest.mark.parametrize("breaks", [[np.nan] * 2, [np.nan] * 4, [np.nan] * 50])
+ def test_constructor_nan(self, constructor, breaks, closed):
+ # GH 18421
+ result_kwargs = self.get_kwargs_from_breaks(breaks)
+ result = constructor(closed=closed, **result_kwargs)
+
+ expected_subtype = np.float64
+ expected_values = np.array(breaks[:-1], dtype=object)
+
+ assert result.closed == closed
+ assert result.dtype.subtype == expected_subtype
+ tm.assert_numpy_array_equal(np.array(result), expected_values)
+
+ @pytest.mark.parametrize(
+ "breaks",
+ [
+ [],
+ np.array([], dtype="int64"),
+ np.array([], dtype="uint64"),
+ np.array([], dtype="float64"),
+ np.array([], dtype="datetime64[ns]"),
+ np.array([], dtype="timedelta64[ns]"),
+ ],
+ )
+ def test_constructor_empty(self, constructor, breaks, closed):
+ # GH 18421
+ result_kwargs = self.get_kwargs_from_breaks(breaks)
+ result = constructor(closed=closed, **result_kwargs)
+
+ expected_values = np.array([], dtype=object)
+ expected_subtype = getattr(breaks, "dtype", np.int64)
+
+ assert result.empty
+ assert result.closed == closed
+ assert result.dtype.subtype == expected_subtype
+ tm.assert_numpy_array_equal(np.array(result), expected_values)
+
+ @pytest.mark.parametrize(
+ "breaks",
+ [
+ tuple("0123456789"),
+ list("abcdefghij"),
+ np.array(list("abcdefghij"), dtype=object),
+ np.array(list("abcdefghij"), dtype=" Interval(0.5, 1.5)
+ tm.assert_numpy_array_equal(actual, expected)
+
+ actual = self.index == self.index
+ expected = np.array([True, True])
+ tm.assert_numpy_array_equal(actual, expected)
+ actual = self.index <= self.index
+ tm.assert_numpy_array_equal(actual, expected)
+ actual = self.index >= self.index
+ tm.assert_numpy_array_equal(actual, expected)
+
+ actual = self.index < self.index
+ expected = np.array([False, False])
+ tm.assert_numpy_array_equal(actual, expected)
+ actual = self.index > self.index
+ tm.assert_numpy_array_equal(actual, expected)
+
+ actual = self.index == IntervalIndex.from_breaks([0, 1, 2], "left")
+ tm.assert_numpy_array_equal(actual, expected)
+
+ actual = self.index == self.index.values
+ tm.assert_numpy_array_equal(actual, np.array([True, True]))
+ actual = self.index.values == self.index
+ tm.assert_numpy_array_equal(actual, np.array([True, True]))
+ actual = self.index <= self.index.values
+ tm.assert_numpy_array_equal(actual, np.array([True, True]))
+ actual = self.index != self.index.values
+ tm.assert_numpy_array_equal(actual, np.array([False, False]))
+ actual = self.index > self.index.values
+ tm.assert_numpy_array_equal(actual, np.array([False, False]))
+ actual = self.index.values > self.index
+ tm.assert_numpy_array_equal(actual, np.array([False, False]))
+
+ # invalid comparisons
+ actual = self.index == 0
+ tm.assert_numpy_array_equal(actual, np.array([False, False]))
+ actual = self.index == self.index.left
+ tm.assert_numpy_array_equal(actual, np.array([False, False]))
+
+ msg = "|".join(
+ [
+ "not supported between instances of 'int' and '.*.Interval'",
+ r"Invalid comparison between dtype=interval\[int64, right\] and ",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ self.index > 0
+ with pytest.raises(TypeError, match=msg):
+ self.index <= 0
+ with pytest.raises(TypeError, match=msg):
+ self.index > np.arange(2)
+
+ msg = "Lengths must match to compare"
+ with pytest.raises(ValueError, match=msg):
+ self.index > np.arange(3)
+
+ def test_missing_values(self, closed):
+ idx = Index(
+ [np.nan, Interval(0, 1, closed=closed), Interval(1, 2, closed=closed)]
+ )
+ idx2 = IntervalIndex.from_arrays([np.nan, 0, 1], [np.nan, 1, 2], closed=closed)
+ assert idx.equals(idx2)
+
+ msg = (
+ "missing values must be missing in the same location both left "
+ "and right sides"
+ )
+ with pytest.raises(ValueError, match=msg):
+ IntervalIndex.from_arrays(
+ [np.nan, 0, 1], np.array([0, 1, 2]), closed=closed
+ )
+
+ tm.assert_numpy_array_equal(isna(idx), np.array([True, False, False]))
+
+ def test_sort_values(self, closed):
+ index = self.create_index(closed=closed)
+
+ result = index.sort_values()
+ tm.assert_index_equal(result, index)
+
+ result = index.sort_values(ascending=False)
+ tm.assert_index_equal(result, index[::-1])
+
+ # with nan
+ index = IntervalIndex([Interval(1, 2), np.nan, Interval(0, 1)])
+
+ result = index.sort_values()
+ expected = IntervalIndex([Interval(0, 1), Interval(1, 2), np.nan])
+ tm.assert_index_equal(result, expected)
+
+ result = index.sort_values(ascending=False, na_position="first")
+ expected = IntervalIndex([np.nan, Interval(1, 2), Interval(0, 1)])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("tz", [None, "US/Eastern"])
+ def test_datetime(self, tz):
+ start = Timestamp("2000-01-01", tz=tz)
+ dates = date_range(start=start, periods=10)
+ index = IntervalIndex.from_breaks(dates)
+
+ # test mid
+ start = Timestamp("2000-01-01T12:00", tz=tz)
+ expected = date_range(start=start, periods=9)
+ tm.assert_index_equal(index.mid, expected)
+
+ # __contains__ doesn't check individual points
+ assert Timestamp("2000-01-01", tz=tz) not in index
+ assert Timestamp("2000-01-01T12", tz=tz) not in index
+ assert Timestamp("2000-01-02", tz=tz) not in index
+ iv_true = Interval(
+ Timestamp("2000-01-02", tz=tz), Timestamp("2000-01-03", tz=tz)
+ )
+ iv_false = Interval(
+ Timestamp("1999-12-31", tz=tz), Timestamp("2000-01-01", tz=tz)
+ )
+ assert iv_true in index
+ assert iv_false not in index
+
+ # .contains does check individual points
+ assert not index.contains(Timestamp("2000-01-01", tz=tz)).any()
+ assert index.contains(Timestamp("2000-01-01T12", tz=tz)).any()
+ assert index.contains(Timestamp("2000-01-02", tz=tz)).any()
+
+ # test get_indexer
+ start = Timestamp("1999-12-31T12:00", tz=tz)
+ target = date_range(start=start, periods=7, freq="12h")
+ actual = index.get_indexer(target)
+ expected = np.array([-1, -1, 0, 0, 1, 1, 2], dtype="intp")
+ tm.assert_numpy_array_equal(actual, expected)
+
+ start = Timestamp("2000-01-08T18:00", tz=tz)
+ target = date_range(start=start, periods=7, freq="6h")
+ actual = index.get_indexer(target)
+ expected = np.array([7, 7, 8, 8, 8, 8, -1], dtype="intp")
+ tm.assert_numpy_array_equal(actual, expected)
+
+ def test_append(self, closed):
+ index1 = IntervalIndex.from_arrays([0, 1], [1, 2], closed=closed)
+ index2 = IntervalIndex.from_arrays([1, 2], [2, 3], closed=closed)
+
+ result = index1.append(index2)
+ expected = IntervalIndex.from_arrays([0, 1, 1, 2], [1, 2, 2, 3], closed=closed)
+ tm.assert_index_equal(result, expected)
+
+ result = index1.append([index1, index2])
+ expected = IntervalIndex.from_arrays(
+ [0, 1, 0, 1, 1, 2], [1, 2, 1, 2, 2, 3], closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ for other_closed in {"left", "right", "both", "neither"} - {closed}:
+ index_other_closed = IntervalIndex.from_arrays(
+ [0, 1], [1, 2], closed=other_closed
+ )
+ result = index1.append(index_other_closed)
+ expected = index1.astype(object).append(index_other_closed.astype(object))
+ tm.assert_index_equal(result, expected)
+
+ def test_is_non_overlapping_monotonic(self, closed):
+ # Should be True in all cases
+ tpls = [(0, 1), (2, 3), (4, 5), (6, 7)]
+ idx = IntervalIndex.from_tuples(tpls, closed=closed)
+ assert idx.is_non_overlapping_monotonic is True
+
+ idx = IntervalIndex.from_tuples(tpls[::-1], closed=closed)
+ assert idx.is_non_overlapping_monotonic is True
+
+ # Should be False in all cases (overlapping)
+ tpls = [(0, 2), (1, 3), (4, 5), (6, 7)]
+ idx = IntervalIndex.from_tuples(tpls, closed=closed)
+ assert idx.is_non_overlapping_monotonic is False
+
+ idx = IntervalIndex.from_tuples(tpls[::-1], closed=closed)
+ assert idx.is_non_overlapping_monotonic is False
+
+ # Should be False in all cases (non-monotonic)
+ tpls = [(0, 1), (2, 3), (6, 7), (4, 5)]
+ idx = IntervalIndex.from_tuples(tpls, closed=closed)
+ assert idx.is_non_overlapping_monotonic is False
+
+ idx = IntervalIndex.from_tuples(tpls[::-1], closed=closed)
+ assert idx.is_non_overlapping_monotonic is False
+
+ # Should be False for closed='both', otherwise True (GH16560)
+ if closed == "both":
+ idx = IntervalIndex.from_breaks(range(4), closed=closed)
+ assert idx.is_non_overlapping_monotonic is False
+ else:
+ idx = IntervalIndex.from_breaks(range(4), closed=closed)
+ assert idx.is_non_overlapping_monotonic is True
+
+ @pytest.mark.parametrize(
+ "start, shift, na_value",
+ [
+ (0, 1, np.nan),
+ (Timestamp("2018-01-01"), Timedelta("1 day"), pd.NaT),
+ (Timedelta("0 days"), Timedelta("1 day"), pd.NaT),
+ ],
+ )
+ def test_is_overlapping(self, start, shift, na_value, closed):
+ # GH 23309
+ # see test_interval_tree.py for extensive tests; interface tests here
+
+ # non-overlapping
+ tuples = [(start + n * shift, start + (n + 1) * shift) for n in (0, 2, 4)]
+ index = IntervalIndex.from_tuples(tuples, closed=closed)
+ assert index.is_overlapping is False
+
+ # non-overlapping with NA
+ tuples = [(na_value, na_value), *tuples, (na_value, na_value)]
+ index = IntervalIndex.from_tuples(tuples, closed=closed)
+ assert index.is_overlapping is False
+
+ # overlapping
+ tuples = [(start + n * shift, start + (n + 2) * shift) for n in range(3)]
+ index = IntervalIndex.from_tuples(tuples, closed=closed)
+ assert index.is_overlapping is True
+
+ # overlapping with NA
+ tuples = [(na_value, na_value), *tuples, (na_value, na_value)]
+ index = IntervalIndex.from_tuples(tuples, closed=closed)
+ assert index.is_overlapping is True
+
+ # common endpoints
+ tuples = [(start + n * shift, start + (n + 1) * shift) for n in range(3)]
+ index = IntervalIndex.from_tuples(tuples, closed=closed)
+ result = index.is_overlapping
+ expected = closed == "both"
+ assert result is expected
+
+ # common endpoints with NA
+ tuples = [(na_value, na_value), *tuples, (na_value, na_value)]
+ index = IntervalIndex.from_tuples(tuples, closed=closed)
+ result = index.is_overlapping
+ assert result is expected
+
+ # intervals with duplicate left values
+ a = [10, 15, 20, 25, 30, 35, 40, 45, 45, 50, 55, 60, 65, 70, 75, 80, 85]
+ b = [15, 20, 25, 30, 35, 40, 45, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90]
+ index = IntervalIndex.from_arrays(a, b, closed="right")
+ result = index.is_overlapping
+ assert result is False
+
+ @pytest.mark.parametrize(
+ "tuples",
+ [
+ zip(range(10), range(1, 11)),
+ zip(
+ date_range("20170101", periods=10),
+ date_range("20170101", periods=10),
+ ),
+ zip(
+ timedelta_range("0 days", periods=10),
+ timedelta_range("1 day", periods=10),
+ ),
+ ],
+ )
+ def test_to_tuples(self, tuples):
+ # GH 18756
+ tuples = list(tuples)
+ idx = IntervalIndex.from_tuples(tuples)
+ result = idx.to_tuples()
+ expected = Index(com.asarray_tuplesafe(tuples))
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "tuples",
+ [
+ [*list(zip(range(10), range(1, 11))), np.nan],
+ [
+ *list(
+ zip(
+ date_range("20170101", periods=10),
+ date_range("20170101", periods=10),
+ )
+ ),
+ np.nan,
+ ],
+ [
+ *list(
+ zip(
+ timedelta_range("0 days", periods=10),
+ timedelta_range("1 day", periods=10),
+ )
+ ),
+ np.nan,
+ ],
+ ],
+ )
+ @pytest.mark.parametrize("na_tuple", [True, False])
+ def test_to_tuples_na(self, tuples, na_tuple):
+ # GH 18756
+ idx = IntervalIndex.from_tuples(tuples)
+ result = idx.to_tuples(na_tuple=na_tuple)
+
+ # check the non-NA portion
+ expected_notna = Index(com.asarray_tuplesafe(tuples[:-1]))
+ result_notna = result[:-1]
+ tm.assert_index_equal(result_notna, expected_notna)
+
+ # check the NA portion
+ result_na = result[-1]
+ if na_tuple:
+ assert isinstance(result_na, tuple)
+ assert len(result_na) == 2
+ assert all(isna(x) for x in result_na)
+ else:
+ assert isna(result_na)
+
+ def test_nbytes(self):
+ # GH 19209
+ left = np.arange(0, 4, dtype="i8")
+ right = np.arange(1, 5, dtype="i8")
+
+ result = IntervalIndex.from_arrays(left, right).nbytes
+ expected = 64 # 4 * 8 * 2
+ assert result == expected
+
+ @pytest.mark.parametrize("name", [None, "foo"])
+ def test_set_closed(self, name, closed, other_closed):
+ # GH 21670
+ index = interval_range(0, 5, closed=closed, name=name)
+ result = index.set_closed(other_closed)
+ expected = interval_range(0, 5, closed=other_closed, name=name)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("bad_closed", ["foo", 10, "LEFT", True, False])
+ def test_set_closed_errors(self, bad_closed):
+ # GH 21670
+ index = interval_range(0, 5)
+ msg = f"invalid option for 'closed': {bad_closed}"
+ with pytest.raises(ValueError, match=msg):
+ index.set_closed(bad_closed)
+
+ def test_is_all_dates(self):
+ # GH 23576
+ year_2017 = Interval(
+ Timestamp("2017-01-01 00:00:00"), Timestamp("2018-01-01 00:00:00")
+ )
+ year_2017_index = IntervalIndex([year_2017])
+ assert not year_2017_index._is_all_dates
+
+
+def test_from_arrays_mismatched_signedness_raises():
+ # GH 55715
+ left = np.array([0, 1, 2], dtype="int64")
+ right = np.array([1, 2, 3], dtype="uint64")
+ with pytest.raises(TypeError, match="matching signedness"):
+ IntervalIndex.from_arrays(left, right)
+
+
+def test_dir():
+ # GH#27571 dir(interval_index) should not raise
+ index = IntervalIndex.from_arrays([0, 1], [1, 2])
+ result = dir(index)
+ assert "str" not in result
+
+
+def test_searchsorted_different_argument_classes(listlike_box):
+ # https://github.com/pandas-dev/pandas/issues/32762
+ values = IntervalIndex([Interval(0, 1), Interval(1, 2)])
+ result = values.searchsorted(listlike_box(values))
+ expected = np.array([0, 1], dtype=result.dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = values._data.searchsorted(listlike_box(values))
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "arg", [[1, 2], ["a", "b"], [Timestamp("2020-01-01", tz="Europe/London")] * 2]
+)
+def test_searchsorted_invalid_argument(arg):
+ values = IntervalIndex([Interval(0, 1), Interval(1, 2)])
+ msg = "'<' not supported between instances of 'pandas._libs.interval.Interval' and "
+ with pytest.raises(TypeError, match=msg):
+ values.searchsorted(arg)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_interval_range.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_interval_range.py
new file mode 100644
index 0000000000000000000000000000000000000000..dbb01b28d23fbdba855b1db69683edfa0361930f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_interval_range.py
@@ -0,0 +1,390 @@
+from datetime import timedelta
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import is_integer
+
+from pandas import (
+ DateOffset,
+ Interval,
+ IntervalIndex,
+ Timedelta,
+ Timestamp,
+ date_range,
+ interval_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+from pandas.tseries.offsets import Day
+
+
+@pytest.fixture(params=[None, "foo"])
+def name(request):
+ return request.param
+
+
+class TestIntervalRange:
+ @pytest.mark.parametrize("freq, periods", [(1, 100), (2.5, 40), (5, 20), (25, 4)])
+ def test_constructor_numeric(self, closed, name, freq, periods):
+ start, end = 0, 100
+ breaks = np.arange(101, step=freq)
+ expected = IntervalIndex.from_breaks(breaks, name=name, closed=closed)
+
+ # defined from start/end/freq
+ result = interval_range(
+ start=start, end=end, freq=freq, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # defined from start/periods/freq
+ result = interval_range(
+ start=start, periods=periods, freq=freq, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # defined from end/periods/freq
+ result = interval_range(
+ end=end, periods=periods, freq=freq, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # GH 20976: linspace behavior defined from start/end/periods
+ result = interval_range(
+ start=start, end=end, periods=periods, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("tz", [None, "US/Eastern"])
+ @pytest.mark.parametrize(
+ "freq, periods", [("D", 364), ("2D", 182), ("22D18h", 16), ("ME", 11)]
+ )
+ def test_constructor_timestamp(self, closed, name, freq, periods, tz):
+ start, end = Timestamp("20180101", tz=tz), Timestamp("20181231", tz=tz)
+ breaks = date_range(start=start, end=end, freq=freq)
+ expected = IntervalIndex.from_breaks(breaks, name=name, closed=closed)
+
+ # defined from start/end/freq
+ result = interval_range(
+ start=start, end=end, freq=freq, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # defined from start/periods/freq
+ result = interval_range(
+ start=start, periods=periods, freq=freq, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # defined from end/periods/freq
+ result = interval_range(
+ end=end, periods=periods, freq=freq, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # GH 20976: linspace behavior defined from start/end/periods
+ if not breaks.freq.n == 1 and tz is None:
+ result = interval_range(
+ start=start, end=end, periods=periods, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "freq, periods", [("D", 100), ("2D12h", 40), ("5D", 20), ("25D", 4)]
+ )
+ def test_constructor_timedelta(self, closed, name, freq, periods):
+ start, end = Timedelta("0 days"), Timedelta("100 days")
+ breaks = timedelta_range(start=start, end=end, freq=freq)
+ expected = IntervalIndex.from_breaks(breaks, name=name, closed=closed)
+
+ # defined from start/end/freq
+ result = interval_range(
+ start=start, end=end, freq=freq, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # defined from start/periods/freq
+ result = interval_range(
+ start=start, periods=periods, freq=freq, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # defined from end/periods/freq
+ result = interval_range(
+ end=end, periods=periods, freq=freq, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ # GH 20976: linspace behavior defined from start/end/periods
+ result = interval_range(
+ start=start, end=end, periods=periods, name=name, closed=closed
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "start, end, freq, expected_endpoint",
+ [
+ (0, 10, 3, 9),
+ (0, 10, 1.5, 9),
+ (0.5, 10, 3, 9.5),
+ (Timedelta("0D"), Timedelta("10D"), "2D4h", Timedelta("8D16h")),
+ (
+ Timestamp("2018-01-01"),
+ Timestamp("2018-02-09"),
+ "MS",
+ Timestamp("2018-02-01"),
+ ),
+ (
+ Timestamp("2018-01-01", tz="US/Eastern"),
+ Timestamp("2018-01-20", tz="US/Eastern"),
+ "5D12h",
+ Timestamp("2018-01-17 12:00:00", tz="US/Eastern"),
+ ),
+ ],
+ )
+ def test_early_truncation(self, start, end, freq, expected_endpoint):
+ # index truncates early if freq causes end to be skipped
+ result = interval_range(start=start, end=end, freq=freq)
+ result_endpoint = result.right[-1]
+ assert result_endpoint == expected_endpoint
+
+ @pytest.mark.parametrize(
+ "start, end, freq",
+ [(0.5, None, None), (None, 4.5, None), (0.5, None, 1.5), (None, 6.5, 1.5)],
+ )
+ def test_no_invalid_float_truncation(self, start, end, freq):
+ # GH 21161
+ if freq is None:
+ breaks = [0.5, 1.5, 2.5, 3.5, 4.5]
+ else:
+ breaks = [0.5, 2.0, 3.5, 5.0, 6.5]
+ expected = IntervalIndex.from_breaks(breaks)
+
+ result = interval_range(start=start, end=end, periods=4, freq=freq)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "start, mid, end",
+ [
+ (
+ Timestamp("2018-03-10", tz="US/Eastern"),
+ Timestamp("2018-03-10 23:30:00", tz="US/Eastern"),
+ Timestamp("2018-03-12", tz="US/Eastern"),
+ ),
+ (
+ Timestamp("2018-11-03", tz="US/Eastern"),
+ Timestamp("2018-11-04 00:30:00", tz="US/Eastern"),
+ Timestamp("2018-11-05", tz="US/Eastern"),
+ ),
+ ],
+ )
+ def test_linspace_dst_transition(self, start, mid, end):
+ # GH 20976: linspace behavior defined from start/end/periods
+ # accounts for the hour gained/lost during DST transition
+ start = start.as_unit("ns")
+ mid = mid.as_unit("ns")
+ end = end.as_unit("ns")
+ result = interval_range(start=start, end=end, periods=2)
+ expected = IntervalIndex.from_breaks([start, mid, end])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("freq", [2, 2.0])
+ @pytest.mark.parametrize("end", [10, 10.0])
+ @pytest.mark.parametrize("start", [0, 0.0])
+ def test_float_subtype(self, start, end, freq):
+ # Has float subtype if any of start/end/freq are float, even if all
+ # resulting endpoints can safely be upcast to integers
+
+ # defined from start/end/freq
+ index = interval_range(start=start, end=end, freq=freq)
+ result = index.dtype.subtype
+ expected = "int64" if is_integer(start + end + freq) else "float64"
+ assert result == expected
+
+ # defined from start/periods/freq
+ index = interval_range(start=start, periods=5, freq=freq)
+ result = index.dtype.subtype
+ expected = "int64" if is_integer(start + freq) else "float64"
+ assert result == expected
+
+ # defined from end/periods/freq
+ index = interval_range(end=end, periods=5, freq=freq)
+ result = index.dtype.subtype
+ expected = "int64" if is_integer(end + freq) else "float64"
+ assert result == expected
+
+ # GH 20976: linspace behavior defined from start/end/periods
+ index = interval_range(start=start, end=end, periods=5)
+ result = index.dtype.subtype
+ expected = "int64" if is_integer(start + end) else "float64"
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "start, end, expected",
+ [
+ (np.int8(1), np.int8(10), np.dtype("int8")),
+ (np.int8(1), np.float16(10), np.dtype("float64")),
+ (np.float32(1), np.float32(10), np.dtype("float32")),
+ (1, 10, np.dtype("int64")),
+ (1, 10.0, np.dtype("float64")),
+ ],
+ )
+ def test_interval_dtype(self, start, end, expected):
+ result = interval_range(start=start, end=end).dtype.subtype
+ assert result == expected
+
+ def test_interval_range_fractional_period(self):
+ # float value for periods
+ msg = "periods must be an integer, got 10.5"
+ ts = Timestamp("2024-03-25")
+ with pytest.raises(TypeError, match=msg):
+ interval_range(ts, periods=10.5)
+
+ def test_constructor_coverage(self):
+ # equivalent timestamp-like start/end
+ start, end = Timestamp("2017-01-01"), Timestamp("2017-01-15")
+ expected = interval_range(start=start, end=end)
+
+ result = interval_range(start=start.to_pydatetime(), end=end.to_pydatetime())
+ tm.assert_index_equal(result, expected)
+
+ result = interval_range(start=start.asm8, end=end.asm8)
+ tm.assert_index_equal(result, expected)
+
+ # equivalent freq with timestamp
+ equiv_freq = [
+ "D",
+ Day(),
+ Timedelta(days=1),
+ timedelta(days=1),
+ DateOffset(days=1),
+ ]
+ for freq in equiv_freq:
+ result = interval_range(start=start, end=end, freq=freq)
+ tm.assert_index_equal(result, expected)
+
+ # equivalent timedelta-like start/end
+ start, end = Timedelta(days=1).as_unit("us"), Timedelta(days=10).as_unit("us")
+ expected = interval_range(start=start, end=end)
+
+ result = interval_range(start=start.to_pytimedelta(), end=end.to_pytimedelta())
+ tm.assert_index_equal(result, expected)
+
+ result = interval_range(start=start.asm8, end=end.asm8)
+ tm.assert_index_equal(result, expected)
+
+ # equivalent freq with timedelta
+ equiv_freq = ["D", Day(), Timedelta(days=1), timedelta(days=1)]
+ for freq in equiv_freq:
+ result = interval_range(start=start, end=end, freq=freq)
+ tm.assert_index_equal(result, expected)
+
+ def test_errors(self):
+ # not enough params
+ msg = (
+ "Of the four parameters: start, end, periods, and freq, "
+ "exactly three must be specified"
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ interval_range(start=0)
+
+ with pytest.raises(ValueError, match=msg):
+ interval_range(end=5)
+
+ with pytest.raises(ValueError, match=msg):
+ interval_range(periods=2)
+
+ with pytest.raises(ValueError, match=msg):
+ interval_range()
+
+ # too many params
+ with pytest.raises(ValueError, match=msg):
+ interval_range(start=0, end=5, periods=6, freq=1.5)
+
+ # mixed units
+ msg = "start, end, freq need to be type compatible"
+ with pytest.raises(TypeError, match=msg):
+ interval_range(start=0, end=Timestamp("20130101"), freq=2)
+
+ with pytest.raises(TypeError, match=msg):
+ interval_range(start=0, end=Timedelta("1 day"), freq=2)
+
+ with pytest.raises(TypeError, match=msg):
+ interval_range(start=0, end=10, freq="D")
+
+ with pytest.raises(TypeError, match=msg):
+ interval_range(start=Timestamp("20130101"), end=10, freq="D")
+
+ with pytest.raises(TypeError, match=msg):
+ interval_range(
+ start=Timestamp("20130101"), end=Timedelta("1 day"), freq="D"
+ )
+
+ with pytest.raises(TypeError, match=msg):
+ interval_range(
+ start=Timestamp("20130101"), end=Timestamp("20130110"), freq=2
+ )
+
+ with pytest.raises(TypeError, match=msg):
+ interval_range(start=Timedelta("1 day"), end=10, freq="D")
+
+ with pytest.raises(TypeError, match=msg):
+ interval_range(
+ start=Timedelta("1 day"), end=Timestamp("20130110"), freq="D"
+ )
+
+ with pytest.raises(TypeError, match=msg):
+ interval_range(start=Timedelta("1 day"), end=Timedelta("10 days"), freq=2)
+
+ # invalid periods
+ msg = "periods must be an integer, got foo"
+ with pytest.raises(TypeError, match=msg):
+ interval_range(start=0, periods="foo")
+
+ # invalid start
+ msg = "start must be numeric or datetime-like, got foo"
+ with pytest.raises(ValueError, match=msg):
+ interval_range(start="foo", periods=10)
+
+ # invalid end
+ msg = r"end must be numeric or datetime-like, got \(0, 1\]"
+ with pytest.raises(ValueError, match=msg):
+ interval_range(end=Interval(0, 1), periods=10)
+
+ # invalid freq for datetime-like
+ msg = "freq must be numeric or convertible to DateOffset, got foo"
+ with pytest.raises(ValueError, match=msg):
+ interval_range(start=0, end=10, freq="foo")
+
+ with pytest.raises(ValueError, match=msg):
+ interval_range(start=Timestamp("20130101"), periods=10, freq="foo")
+
+ with pytest.raises(ValueError, match=msg):
+ interval_range(end=Timedelta("1 day"), periods=10, freq="foo")
+
+ # mixed tz
+ start = Timestamp("2017-01-01", tz="US/Eastern")
+ end = Timestamp("2017-01-07", tz="US/Pacific")
+ msg = "Start and end cannot both be tz-aware with different timezones"
+ with pytest.raises(TypeError, match=msg):
+ interval_range(start=start, end=end)
+
+ def test_float_freq(self):
+ # GH 54477
+ result = interval_range(0, 1, freq=0.1)
+ expected = IntervalIndex.from_breaks([0 + 0.1 * n for n in range(11)])
+ tm.assert_index_equal(result, expected)
+
+ result = interval_range(0, 1, freq=0.6)
+ expected = IntervalIndex.from_breaks([0, 0.6])
+ tm.assert_index_equal(result, expected)
+
+ def test_interval_range_float32_start_int_freq(self):
+ # GH 58964
+ result = interval_range(start=np.float32(0), end=2, freq=1)
+ expected = IntervalIndex.from_tuples(
+ [(0.0, 1.0), (1.0, 2.0)], dtype="interval[float64, right]"
+ )
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_interval_tree.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_interval_tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..df9c3b390f66011eccfcce67702cc8c011af3dc2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_interval_tree.py
@@ -0,0 +1,208 @@
+from itertools import permutations
+
+import numpy as np
+import pytest
+
+from pandas._libs.interval import IntervalTree
+from pandas.compat import (
+ IS64,
+ WASM,
+)
+
+import pandas._testing as tm
+
+
+def skipif_32bit(param):
+ """
+ Skip parameters in a parametrize on 32bit systems. Specifically used
+ here to skip leaf_size parameters related to GH 23440.
+ """
+ marks = pytest.mark.skipif(not IS64, reason="GH 23440: int type mismatch on 32bit")
+ return pytest.param(param, marks=marks)
+
+
+@pytest.fixture(params=[skipif_32bit(1), skipif_32bit(2), 10])
+def leaf_size(request):
+ """
+ Fixture to specify IntervalTree leaf_size parameter; to be used with the
+ tree fixture.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ np.arange(5, dtype="int64"),
+ np.arange(5, dtype="uint64"),
+ np.arange(5, dtype="float64"),
+ np.array([0, 1, 2, 3, 4, np.nan], dtype="float64"),
+ ]
+)
+def tree(request, leaf_size):
+ left = request.param
+ return IntervalTree(left, left + 2, leaf_size=leaf_size)
+
+
+class TestIntervalTree:
+ def test_get_indexer(self, tree):
+ result = tree.get_indexer(np.array([1.0, 5.5, 6.5]))
+ expected = np.array([0, 4, -1], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ with pytest.raises(
+ KeyError, match="'indexer does not intersect a unique set of intervals'"
+ ):
+ tree.get_indexer(np.array([3.0]))
+
+ @pytest.mark.parametrize(
+ "dtype, target_value, target_dtype",
+ [("int64", 2**63 + 1, "uint64"), ("uint64", -1, "int64")],
+ )
+ def test_get_indexer_overflow(self, dtype, target_value, target_dtype):
+ left, right = np.array([0, 1], dtype=dtype), np.array([1, 2], dtype=dtype)
+ tree = IntervalTree(left, right)
+
+ result = tree.get_indexer(np.array([target_value], dtype=target_dtype))
+ expected = np.array([-1], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer_non_unique(self, tree):
+ indexer, missing = tree.get_indexer_non_unique(np.array([1.0, 2.0, 6.5]))
+
+ result = indexer[:1]
+ expected = np.array([0], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.sort(indexer[1:3])
+ expected = np.array([0, 1], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.sort(indexer[3:])
+ expected = np.array([-1], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = missing
+ expected = np.array([2], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype, target_value, target_dtype",
+ [("int64", 2**63 + 1, "uint64"), ("uint64", -1, "int64")],
+ )
+ def test_get_indexer_non_unique_overflow(self, dtype, target_value, target_dtype):
+ left, right = np.array([0, 2], dtype=dtype), np.array([1, 3], dtype=dtype)
+ tree = IntervalTree(left, right)
+ target = np.array([target_value], dtype=target_dtype)
+
+ result_indexer, result_missing = tree.get_indexer_non_unique(target)
+ expected_indexer = np.array([-1], dtype="intp")
+ tm.assert_numpy_array_equal(result_indexer, expected_indexer)
+
+ expected_missing = np.array([0], dtype="intp")
+ tm.assert_numpy_array_equal(result_missing, expected_missing)
+
+ @pytest.mark.parametrize("dtype", ["int64", "float64", "uint64"])
+ def test_duplicates(self, dtype):
+ left = np.array([0, 0, 0], dtype=dtype)
+ tree = IntervalTree(left, left + 1)
+
+ with pytest.raises(
+ KeyError, match="'indexer does not intersect a unique set of intervals'"
+ ):
+ tree.get_indexer(np.array([0.5]))
+
+ indexer, missing = tree.get_indexer_non_unique(np.array([0.5]))
+ result = np.sort(indexer)
+ expected = np.array([0, 1, 2], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = missing
+ expected = np.array([], dtype="intp")
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "leaf_size", [skipif_32bit(1), skipif_32bit(10), skipif_32bit(100), 10000]
+ )
+ def test_get_indexer_closed(self, closed, leaf_size):
+ x = np.arange(1000, dtype="float64")
+ found = x.astype("intp")
+ not_found = (-1 * np.ones(1000)).astype("intp")
+
+ tree = IntervalTree(x, x + 0.5, closed=closed, leaf_size=leaf_size)
+ tm.assert_numpy_array_equal(found, tree.get_indexer(x + 0.25))
+
+ expected = found if tree.closed_left else not_found
+ tm.assert_numpy_array_equal(expected, tree.get_indexer(x + 0.0))
+
+ expected = found if tree.closed_right else not_found
+ tm.assert_numpy_array_equal(expected, tree.get_indexer(x + 0.5))
+
+ @pytest.mark.parametrize(
+ "left, right, expected",
+ [
+ (np.array([0, 1, 4], dtype="int64"), np.array([2, 3, 5]), True),
+ (np.array([0, 1, 2], dtype="int64"), np.array([5, 4, 3]), True),
+ (np.array([0, 1, np.nan]), np.array([5, 4, np.nan]), True),
+ (np.array([0, 2, 4], dtype="int64"), np.array([1, 3, 5]), False),
+ (np.array([0, 2, np.nan]), np.array([1, 3, np.nan]), False),
+ ],
+ )
+ @pytest.mark.parametrize("order", (list(x) for x in permutations(range(3))))
+ def test_is_overlapping(self, closed, order, left, right, expected):
+ # GH 23309
+ tree = IntervalTree(left[order], right[order], closed=closed)
+ result = tree.is_overlapping
+ assert result is expected
+
+ @pytest.mark.parametrize("order", (list(x) for x in permutations(range(3))))
+ def test_is_overlapping_endpoints(self, closed, order):
+ """shared endpoints are marked as overlapping"""
+ # GH 23309
+ left, right = np.arange(3, dtype="int64"), np.arange(1, 4)
+ tree = IntervalTree(left[order], right[order], closed=closed)
+ result = tree.is_overlapping
+ expected = closed == "both"
+ assert result is expected
+
+ @pytest.mark.parametrize(
+ "left, right",
+ [
+ (np.array([], dtype="int64"), np.array([], dtype="int64")),
+ (np.array([0], dtype="int64"), np.array([1], dtype="int64")),
+ (np.array([np.nan]), np.array([np.nan])),
+ (np.array([np.nan] * 3), np.array([np.nan] * 3)),
+ ],
+ )
+ def test_is_overlapping_trivial(self, closed, left, right):
+ # GH 23309
+ tree = IntervalTree(left, right, closed=closed)
+ assert tree.is_overlapping is False
+
+ @pytest.mark.skipif(not IS64, reason="GH 23440")
+ def test_construction_overflow(self):
+ # GH 25485
+ left, right = np.arange(101, dtype="int64"), [np.iinfo(np.int64).max] * 101
+ tree = IntervalTree(left, right)
+
+ # pivot should be average of left/right medians
+ result = tree.root.pivot
+ expected = (50 + np.iinfo(np.int64).max) / 2
+ assert result == expected
+
+ @pytest.mark.xfail(WASM, reason="GH 23440")
+ @pytest.mark.parametrize(
+ "left, right, expected",
+ [
+ ([-np.inf, 1.0], [1.0, 2.0], 0.0),
+ ([-np.inf, -2.0], [-2.0, -1.0], -2.0),
+ ([-2.0, -1.0], [-1.0, np.inf], 0.0),
+ ([1.0, 2.0], [2.0, np.inf], 2.0),
+ ],
+ )
+ def test_inf_bound_infinite_recursion(self, left, right, expected):
+ # GH 46658
+
+ tree = IntervalTree(left * 101, right * 101)
+
+ result = tree.root.pivot
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f42c530a66868fa69b1d449e75f84d42592bb77
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_join.py
@@ -0,0 +1,44 @@
+import pytest
+
+from pandas import (
+ IntervalIndex,
+ MultiIndex,
+ RangeIndex,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def range_index():
+ return RangeIndex(3, name="range_index")
+
+
+@pytest.fixture
+def interval_index():
+ return IntervalIndex.from_tuples(
+ [(0.0, 1.0), (1.0, 2.0), (1.5, 2.5)], name="interval_index"
+ )
+
+
+def test_join_overlapping_in_mi_to_same_intervalindex(range_index, interval_index):
+ # GH-45661
+ multi_index = MultiIndex.from_product([interval_index, range_index])
+ result = multi_index.join(interval_index)
+
+ tm.assert_index_equal(result, multi_index)
+
+
+def test_join_overlapping_to_multiindex_with_same_interval(range_index, interval_index):
+ # GH-45661
+ multi_index = MultiIndex.from_product([interval_index, range_index])
+ result = interval_index.join(multi_index)
+
+ tm.assert_index_equal(result, multi_index)
+
+
+def test_join_overlapping_interval_to_another_intervalindex(interval_index):
+ # GH-45661
+ flipped_interval_index = interval_index[::-1]
+ result = interval_index.join(flipped_interval_index)
+
+ tm.assert_index_equal(result, interval_index)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_pickle.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_pickle.py
new file mode 100644
index 0000000000000000000000000000000000000000..1eb2599b038ddbcb2e779f7ab96aaacf0577ae80
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_pickle.py
@@ -0,0 +1,10 @@
+from pandas import IntervalIndex
+import pandas._testing as tm
+
+
+class TestPickle:
+ def test_pickle_round_trip_closed(self, closed, temp_file):
+ # https://github.com/pandas-dev/pandas/issues/35658
+ idx = IntervalIndex.from_tuples([(1, 2), (2, 3)], closed=closed)
+ result = tm.round_trip_pickle(idx, temp_file)
+ tm.assert_index_equal(result, idx)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b0816a9405cb9dd6ed81691e72012c948b898a2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/interval/test_setops.py
@@ -0,0 +1,208 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ IntervalIndex,
+ Timestamp,
+ interval_range,
+)
+import pandas._testing as tm
+
+
+def monotonic_index(start, end, dtype="int64", closed="right"):
+ return IntervalIndex.from_breaks(np.arange(start, end, dtype=dtype), closed=closed)
+
+
+def empty_index(dtype="int64", closed="right"):
+ return IntervalIndex(np.array([], dtype=dtype), closed=closed)
+
+
+class TestIntervalIndex:
+ def test_union(self, closed, sort):
+ index = monotonic_index(0, 11, closed=closed)
+ other = monotonic_index(5, 13, closed=closed)
+
+ expected = monotonic_index(0, 13, closed=closed)
+ result = index[::-1].union(other, sort=sort)
+ if sort in (None, True):
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_index_equal(result.sort_values(), expected)
+
+ result = other[::-1].union(index, sort=sort)
+ if sort in (None, True):
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_index_equal(result.sort_values(), expected)
+
+ tm.assert_index_equal(index.union(index, sort=sort), index)
+ tm.assert_index_equal(index.union(index[:1], sort=sort), index)
+
+ def test_union_empty_result(self, closed, sort):
+ # GH 19101: empty result, same dtype
+ index = empty_index(dtype="int64", closed=closed)
+ result = index.union(index, sort=sort)
+ tm.assert_index_equal(result, index)
+
+ # GH 19101: empty result, different numeric dtypes -> common dtype is f8
+ other = empty_index(dtype="float64", closed=closed)
+ result = index.union(other, sort=sort)
+ expected = other
+ tm.assert_index_equal(result, expected)
+
+ other = index.union(index, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ other = empty_index(dtype="uint64", closed=closed)
+ result = index.union(other, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ result = other.union(index, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection(self, closed, sort):
+ index = monotonic_index(0, 11, closed=closed)
+ other = monotonic_index(5, 13, closed=closed)
+
+ expected = monotonic_index(5, 11, closed=closed)
+ result = index[::-1].intersection(other, sort=sort)
+ if sort in (None, True):
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_index_equal(result.sort_values(), expected)
+
+ result = other[::-1].intersection(index, sort=sort)
+ if sort in (None, True):
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_index_equal(result.sort_values(), expected)
+
+ tm.assert_index_equal(index.intersection(index, sort=sort), index)
+
+ # GH 26225: nested intervals
+ index = IntervalIndex.from_tuples([(1, 2), (1, 3), (1, 4), (0, 2)])
+ other = IntervalIndex.from_tuples([(1, 2), (1, 3)])
+ expected = IntervalIndex.from_tuples([(1, 2), (1, 3)])
+ result = index.intersection(other)
+ tm.assert_index_equal(result, expected)
+
+ # GH 26225
+ index = IntervalIndex.from_tuples([(0, 3), (0, 2)])
+ other = IntervalIndex.from_tuples([(0, 2), (1, 3)])
+ expected = IntervalIndex.from_tuples([(0, 2)])
+ result = index.intersection(other)
+ tm.assert_index_equal(result, expected)
+
+ # GH 26225: duplicate nan element
+ index = IntervalIndex([np.nan, np.nan])
+ other = IntervalIndex([np.nan])
+ expected = IntervalIndex([np.nan])
+ result = index.intersection(other)
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection_empty_result(self, closed, sort):
+ index = monotonic_index(0, 11, closed=closed)
+
+ # GH 19101: empty result, same dtype
+ other = monotonic_index(300, 314, closed=closed)
+ expected = empty_index(dtype="int64", closed=closed)
+ result = index.intersection(other, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ # GH 19101: empty result, different numeric dtypes -> common dtype is float64
+ other = monotonic_index(300, 314, dtype="float64", closed=closed)
+ result = index.intersection(other, sort=sort)
+ expected = other[:0]
+ tm.assert_index_equal(result, expected)
+
+ other = monotonic_index(300, 314, dtype="uint64", closed=closed)
+ result = index.intersection(other, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection_duplicates(self):
+ # GH#38743
+ index = IntervalIndex.from_tuples([(1, 2), (1, 2), (2, 3), (3, 4)])
+ other = IntervalIndex.from_tuples([(1, 2), (2, 3)])
+ expected = IntervalIndex.from_tuples([(1, 2), (2, 3)])
+ result = index.intersection(other)
+ tm.assert_index_equal(result, expected)
+
+ def test_difference(self, closed, sort):
+ index = IntervalIndex.from_arrays([1, 0, 3, 2], [1, 2, 3, 4], closed=closed)
+ result = index.difference(index[:1], sort=sort)
+ expected = index[1:]
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+
+ # GH 19101: empty result, same dtype
+ result = index.difference(index, sort=sort)
+ expected = empty_index(dtype="int64", closed=closed)
+ tm.assert_index_equal(result, expected)
+
+ # GH 19101: empty result, different dtypes
+ other = IntervalIndex.from_arrays(
+ index.left.astype("float64"), index.right, closed=closed
+ )
+ result = index.difference(other, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ def test_symmetric_difference(self, closed, sort):
+ index = monotonic_index(0, 11, closed=closed)
+ result = index[1:].symmetric_difference(index[:-1], sort=sort)
+ expected = IntervalIndex([index[0], index[-1]])
+ if sort in (None, True):
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_index_equal(result.sort_values(), expected)
+
+ # GH 19101: empty result, same dtype
+ result = index.symmetric_difference(index, sort=sort)
+ expected = empty_index(dtype="int64", closed=closed)
+ if sort in (None, True):
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_index_equal(result.sort_values(), expected)
+
+ # GH 19101: empty result, different dtypes
+ other = IntervalIndex.from_arrays(
+ index.left.astype("float64"), index.right, closed=closed
+ )
+ result = index.symmetric_difference(other, sort=sort)
+ expected = empty_index(dtype="float64", closed=closed)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.filterwarnings("ignore:'<' not supported between:RuntimeWarning")
+ @pytest.mark.parametrize(
+ "op_name", ["union", "intersection", "difference", "symmetric_difference"]
+ )
+ def test_set_incompatible_types(self, closed, op_name, sort):
+ index = monotonic_index(0, 11, closed=closed)
+ set_op = getattr(index, op_name)
+
+ # TODO: standardize return type of non-union setops type(self vs other)
+ # non-IntervalIndex
+ if op_name == "difference":
+ expected = index
+ else:
+ expected = getattr(index.astype("O"), op_name)(Index([1, 2, 3]))
+ result = set_op(Index([1, 2, 3]), sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ # mixed closed -> cast to object
+ for other_closed in {"right", "left", "both", "neither"} - {closed}:
+ other = monotonic_index(0, 11, closed=other_closed)
+ expected = getattr(index.astype(object), op_name)(other, sort=sort)
+ if op_name == "difference":
+ expected = index
+ result = set_op(other, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ # GH 19016: incompatible dtypes -> cast to object
+ other = interval_range(Timestamp("20180101"), periods=9, closed=closed)
+ expected = getattr(index.astype(object), op_name)(other, sort=sort)
+ if op_name == "difference":
+ expected = index
+ result = set_op(other, sort=sort)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..15062aee56e3a1b91d1f6eb76a4f86e381e0ad44
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/conftest.py
@@ -0,0 +1,27 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ MultiIndex,
+)
+
+
+# Note: identical the "multi" entry in the top-level "index" fixture
+@pytest.fixture
+def idx():
+ # a MultiIndex used to test the general functionality of the
+ # general functionality of this object
+ major_axis = Index(["foo", "bar", "baz", "qux"])
+ minor_axis = Index(["one", "two"])
+
+ major_codes = np.array([0, 0, 1, 2, 3, 3])
+ minor_codes = np.array([0, 1, 0, 1, 0, 1])
+ index_names = ["first", "second"]
+ mi = MultiIndex(
+ levels=[major_axis, minor_axis],
+ codes=[major_codes, minor_codes],
+ names=index_names,
+ verify_integrity=False,
+ )
+ return mi
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_analytics.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_analytics.py
new file mode 100644
index 0000000000000000000000000000000000000000..c16130390230864b6940fedd58ff3876bf5da055
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_analytics.py
@@ -0,0 +1,263 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+def test_infer_objects(idx):
+ with pytest.raises(NotImplementedError, match="to_frame"):
+ idx.infer_objects()
+
+
+def test_shift(idx):
+ # GH8083 test the base class for shift
+ msg = (
+ "This method is only implemented for DatetimeIndex, PeriodIndex and "
+ "TimedeltaIndex; Got type MultiIndex"
+ )
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.shift(1)
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.shift(1, 2)
+
+
+def test_groupby(idx):
+ groups = idx.groupby(np.array([1, 1, 1, 2, 2, 2]))
+ labels = idx.tolist()
+ exp = {1: labels[:3], 2: labels[3:]}
+ tm.assert_dict_equal(groups, exp)
+
+ # GH5620
+ groups = idx.groupby(idx)
+ exp = {key: [key] for key in idx}
+ tm.assert_dict_equal(groups, exp)
+
+
+def test_truncate_multiindex():
+ # GH 34564 for MultiIndex level names check
+ major_axis = Index(list(range(4)))
+ minor_axis = Index(list(range(2)))
+
+ major_codes = np.array([0, 0, 1, 2, 3, 3])
+ minor_codes = np.array([0, 1, 0, 1, 0, 1])
+
+ index = MultiIndex(
+ levels=[major_axis, minor_axis],
+ codes=[major_codes, minor_codes],
+ names=["L1", "L2"],
+ )
+
+ result = index.truncate(before=1)
+ assert "foo" not in result.levels[0]
+ assert 1 in result.levels[0]
+ assert index.names == result.names
+
+ result = index.truncate(after=1)
+ assert 2 not in result.levels[0]
+ assert 1 in result.levels[0]
+ assert index.names == result.names
+
+ result = index.truncate(before=1, after=2)
+ assert len(result.levels[0]) == 2
+ assert index.names == result.names
+
+ msg = "after < before"
+ with pytest.raises(ValueError, match=msg):
+ index.truncate(3, 1)
+
+
+# TODO: reshape
+
+
+def test_reorder_levels(idx):
+ # this blows up
+ with pytest.raises(IndexError, match="^Too many levels"):
+ idx.reorder_levels([2, 1, 0])
+
+
+def test_numpy_repeat():
+ reps = 2
+ numbers = [1, 2, 3]
+ names = np.array(["foo", "bar"])
+
+ m = MultiIndex.from_product([numbers, names], names=names)
+ expected = MultiIndex.from_product([numbers, names.repeat(reps)], names=names)
+ tm.assert_index_equal(np.repeat(m, reps), expected)
+
+ msg = "the 'axis' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.repeat(m, reps, axis=1)
+
+
+def test_append_mixed_dtypes():
+ # GH 13660
+ dti = date_range("2011-01-01", freq="ME", periods=3)
+ dti_tz = date_range("2011-01-01", freq="ME", periods=3, tz="US/Eastern")
+ pi = period_range("2011-01", freq="M", periods=3)
+
+ mi = MultiIndex.from_arrays(
+ [[1, 2, 3], [1.1, np.nan, 3.3], ["a", "b", "c"], dti, dti_tz, pi]
+ )
+ assert mi.nlevels == 6
+
+ res = mi.append(mi)
+ exp = MultiIndex.from_arrays(
+ [
+ [1, 2, 3, 1, 2, 3],
+ [1.1, np.nan, 3.3, 1.1, np.nan, 3.3],
+ ["a", "b", "c", "a", "b", "c"],
+ dti.append(dti),
+ dti_tz.append(dti_tz),
+ pi.append(pi),
+ ]
+ )
+ tm.assert_index_equal(res, exp)
+
+ other = MultiIndex.from_arrays(
+ [
+ ["x", "y", "z"],
+ ["x", "y", "z"],
+ ["x", "y", "z"],
+ ["x", "y", "z"],
+ ["x", "y", "z"],
+ ["x", "y", "z"],
+ ]
+ )
+
+ res = mi.append(other)
+ exp = MultiIndex.from_arrays(
+ [
+ [1, 2, 3, "x", "y", "z"],
+ [1.1, np.nan, 3.3, "x", "y", "z"],
+ ["a", "b", "c", "x", "y", "z"],
+ dti.append(Index(["x", "y", "z"])),
+ dti_tz.append(Index(["x", "y", "z"])),
+ pi.append(Index(["x", "y", "z"])),
+ ]
+ )
+ tm.assert_index_equal(res, exp)
+
+
+def test_iter(idx):
+ result = list(idx)
+ expected = [
+ ("foo", "one"),
+ ("foo", "two"),
+ ("bar", "one"),
+ ("baz", "two"),
+ ("qux", "one"),
+ ("qux", "two"),
+ ]
+ assert result == expected
+
+
+def test_sub(idx):
+ first = idx
+
+ # - now raises (previously was set op difference)
+ msg = "cannot perform __sub__ with this index type: MultiIndex"
+ with pytest.raises(TypeError, match=msg):
+ first - idx[-3:]
+ with pytest.raises(TypeError, match=msg):
+ idx[-3:] - first
+ with pytest.raises(TypeError, match=msg):
+ idx[-3:] - first.tolist()
+ msg = "cannot perform __rsub__ with this index type: MultiIndex"
+ with pytest.raises(TypeError, match=msg):
+ first.tolist() - idx[-3:]
+
+
+def test_map(idx):
+ # callable
+ index = idx
+
+ result = index.map(lambda x: x)
+ tm.assert_index_equal(result, index)
+
+
+@pytest.mark.parametrize(
+ "mapper",
+ [
+ lambda values, idx: {i: e for e, i in zip(values, idx, strict=True)},
+ lambda values, idx: pd.Series(values, idx),
+ ],
+)
+def test_map_dictlike(idx, mapper):
+ identity = mapper(idx.values, idx)
+
+ # we don't infer to uint64 dtype for a dict
+ if idx.dtype == np.uint64 and isinstance(identity, dict):
+ expected = idx.astype("int64")
+ else:
+ expected = idx
+
+ result = idx.map(identity)
+ tm.assert_index_equal(result, expected)
+
+ # empty mappable
+ expected = Index([np.nan] * len(idx))
+ result = idx.map(mapper(expected, idx))
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ np.exp,
+ np.exp2,
+ np.expm1,
+ np.log,
+ np.log2,
+ np.log10,
+ np.log1p,
+ np.sqrt,
+ np.sin,
+ np.cos,
+ np.tan,
+ np.arcsin,
+ np.arccos,
+ np.arctan,
+ np.sinh,
+ np.cosh,
+ np.tanh,
+ np.arcsinh,
+ np.arccosh,
+ np.arctanh,
+ np.deg2rad,
+ np.rad2deg,
+ ],
+ ids=lambda func: func.__name__,
+)
+def test_numpy_ufuncs(idx, func):
+ # test ufuncs of numpy. see:
+ # https://numpy.org/doc/stable/reference/ufuncs.html
+
+ expected_exception = TypeError
+ msg = (
+ "loop of ufunc does not support argument 0 of type tuple which "
+ f"has no callable {func.__name__} method"
+ )
+ with pytest.raises(expected_exception, match=msg):
+ func(idx)
+
+
+@pytest.mark.parametrize(
+ "func",
+ [np.isfinite, np.isinf, np.isnan, np.signbit],
+ ids=lambda func: func.__name__,
+)
+def test_numpy_type_funcs(idx, func):
+ msg = (
+ f"ufunc '{func.__name__}' not supported for the input types, and the inputs "
+ "could not be safely coerced to any supported types according to "
+ "the casting rule ''safe''"
+ )
+ with pytest.raises(TypeError, match=msg):
+ func(idx)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..29908537fbe590328ac586e05f90f3cc24cab9ab
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_astype.py
@@ -0,0 +1,30 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.dtypes import CategoricalDtype
+
+import pandas._testing as tm
+
+
+def test_astype(idx):
+ expected = idx.copy()
+ actual = idx.astype("O")
+ tm.assert_copy(actual.levels, expected.levels)
+ tm.assert_copy(actual.codes, expected.codes)
+ assert actual.names == list(expected.names)
+
+ with pytest.raises(TypeError, match="^Setting.*dtype.*object"):
+ idx.astype(np.dtype(int))
+
+
+@pytest.mark.parametrize("ordered", [True, False])
+def test_astype_category(idx, ordered):
+ # GH 18630
+ msg = "> 1 ndim Categorical are not supported at this time"
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.astype(CategoricalDtype(ordered=ordered))
+
+ if ordered is False:
+ # dtype='category' defaults to ordered=False, so only test once
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.astype("category")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..27a8c6e9b715880a57e711e8eab457ae553a4867
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_compat.py
@@ -0,0 +1,122 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import MultiIndex
+import pandas._testing as tm
+
+
+def test_numeric_compat(idx):
+ with pytest.raises(TypeError, match="cannot perform __mul__"):
+ idx * 1
+
+ with pytest.raises(TypeError, match="cannot perform __rmul__"):
+ 1 * idx
+
+ div_err = "cannot perform __truediv__"
+ with pytest.raises(TypeError, match=div_err):
+ idx / 1
+
+ div_err = div_err.replace(" __", " __r")
+ with pytest.raises(TypeError, match=div_err):
+ 1 / idx
+
+ with pytest.raises(TypeError, match="cannot perform __floordiv__"):
+ idx // 1
+
+ with pytest.raises(TypeError, match="cannot perform __rfloordiv__"):
+ 1 // idx
+
+
+@pytest.mark.parametrize("method", ["all", "any", "__invert__"])
+def test_logical_compat(idx, method):
+ msg = f"cannot perform {method}"
+
+ with pytest.raises(TypeError, match=msg):
+ getattr(idx, method)()
+
+
+def test_inplace_mutation_resets_values():
+ levels = [["a", "b", "c"], [4]]
+ levels2 = [[1, 2, 3], ["a"]]
+ codes = [[0, 1, 0, 2, 2, 0], [0, 0, 0, 0, 0, 0]]
+
+ mi1 = MultiIndex(levels=levels, codes=codes)
+ mi2 = MultiIndex(levels=levels2, codes=codes)
+
+ # instantiating MultiIndex should not access/cache _.values
+ assert "_values" not in mi1._cache
+ assert "_values" not in mi2._cache
+
+ vals = mi1.values.copy()
+ vals2 = mi2.values.copy()
+
+ # accessing .values should cache ._values
+ assert mi1._values is mi1._cache["_values"]
+ assert mi1.values is mi1._cache["_values"]
+ assert isinstance(mi1._cache["_values"], np.ndarray)
+
+ # Make sure level setting works
+ new_vals = mi1.set_levels(levels2).values
+ tm.assert_almost_equal(vals2, new_vals)
+
+ # Doesn't drop _values from _cache [implementation detail]
+ tm.assert_almost_equal(mi1._cache["_values"], vals)
+
+ # ...and values is still same too
+ tm.assert_almost_equal(mi1.values, vals)
+
+ # Make sure label setting works too
+ codes2 = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]
+ exp_values = np.empty((6,), dtype=object)
+ exp_values[:] = [(1, "a")] * 6
+
+ # Must be 1d array of tuples
+ assert exp_values.shape == (6,)
+
+ new_mi = mi2.set_codes(codes2)
+ assert "_values" not in new_mi._cache
+ new_values = new_mi.values
+ assert "_values" in new_mi._cache
+
+ # Shouldn't change cache
+ tm.assert_almost_equal(mi2._cache["_values"], vals2)
+
+ # Should have correct values
+ tm.assert_almost_equal(exp_values, new_values)
+
+
+def test_boxable_categorical_values():
+ cat = pd.Categorical(pd.date_range("2012-01-01", periods=3, freq="h"))
+ result = MultiIndex.from_product([["a", "b", "c"], cat]).values
+ expected = pd.Series(
+ [
+ ("a", pd.Timestamp("2012-01-01 00:00:00")),
+ ("a", pd.Timestamp("2012-01-01 01:00:00")),
+ ("a", pd.Timestamp("2012-01-01 02:00:00")),
+ ("b", pd.Timestamp("2012-01-01 00:00:00")),
+ ("b", pd.Timestamp("2012-01-01 01:00:00")),
+ ("b", pd.Timestamp("2012-01-01 02:00:00")),
+ ("c", pd.Timestamp("2012-01-01 00:00:00")),
+ ("c", pd.Timestamp("2012-01-01 01:00:00")),
+ ("c", pd.Timestamp("2012-01-01 02:00:00")),
+ ]
+ ).values
+ tm.assert_numpy_array_equal(result, expected)
+ result = pd.DataFrame({"a": ["a", "b", "c"], "b": cat, "c": np.array(cat)}).values
+ expected = pd.DataFrame(
+ {
+ "a": ["a", "b", "c"],
+ "b": [
+ pd.Timestamp("2012-01-01 00:00:00"),
+ pd.Timestamp("2012-01-01 01:00:00"),
+ pd.Timestamp("2012-01-01 02:00:00"),
+ ],
+ "c": [
+ pd.Timestamp("2012-01-01 00:00:00"),
+ pd.Timestamp("2012-01-01 01:00:00"),
+ pd.Timestamp("2012-01-01 02:00:00"),
+ ],
+ }
+ ).values
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..a76b3443a079e0a7e02b57573fc6131237505f28
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_constructors.py
@@ -0,0 +1,872 @@
+from datetime import (
+ date,
+ datetime,
+)
+import itertools
+
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def test_constructor_single_level():
+ result = MultiIndex(
+ levels=[["foo", "bar", "baz", "qux"]], codes=[[0, 1, 2, 3]], names=["first"]
+ )
+ assert isinstance(result, MultiIndex)
+ expected = Index(["foo", "bar", "baz", "qux"], name="first")
+ tm.assert_index_equal(result.levels[0], expected)
+ assert result.names == ["first"]
+
+
+def test_constructor_no_levels():
+ msg = "non-zero number of levels/codes"
+ with pytest.raises(ValueError, match=msg):
+ MultiIndex(levels=[], codes=[])
+
+ msg = "Must pass both levels and codes"
+ with pytest.raises(TypeError, match=msg):
+ MultiIndex(levels=[])
+ with pytest.raises(TypeError, match=msg):
+ MultiIndex(codes=[])
+
+
+def test_constructor_nonhashable_names():
+ # GH 20527
+ levels = [[1, 2], ["one", "two"]]
+ codes = [[0, 0, 1, 1], [0, 1, 0, 1]]
+ names = (["foo"], ["bar"])
+ msg = r"MultiIndex\.name must be a hashable type"
+ with pytest.raises(TypeError, match=msg):
+ MultiIndex(levels=levels, codes=codes, names=names)
+
+ # With .rename()
+ mi = MultiIndex(
+ levels=[[1, 2], ["one", "two"]],
+ codes=[[0, 0, 1, 1], [0, 1, 0, 1]],
+ names=("foo", "bar"),
+ )
+ renamed = [["fooo"], ["barr"]]
+ with pytest.raises(TypeError, match=msg):
+ mi.rename(names=renamed)
+
+ # With .set_names()
+ with pytest.raises(TypeError, match=msg):
+ mi.set_names(names=renamed)
+
+
+def test_constructor_mismatched_codes_levels(idx):
+ codes = [np.array([1]), np.array([2]), np.array([3])]
+ levels = ["a"]
+
+ msg = "Length of levels and codes must be the same"
+ with pytest.raises(ValueError, match=msg):
+ MultiIndex(levels=levels, codes=codes)
+
+ length_error = (
+ r"On level 0, code max \(3\) >= length of level \(1\)\. "
+ "NOTE: this index is in an inconsistent state"
+ )
+ label_error = r"Unequal code lengths: \[4, 2\]"
+ code_value_error = r"On level 0, code value \(-2\) < -1"
+
+ # important to check that it's looking at the right thing.
+ with pytest.raises(ValueError, match=length_error):
+ MultiIndex(levels=[["a"], ["b"]], codes=[[0, 1, 2, 3], [0, 3, 4, 1]])
+
+ with pytest.raises(ValueError, match=label_error):
+ MultiIndex(levels=[["a"], ["b"]], codes=[[0, 0, 0, 0], [0, 0]])
+
+ # external API
+ with pytest.raises(ValueError, match=length_error):
+ idx.copy().set_levels([["a"], ["b"]])
+
+ with pytest.raises(ValueError, match=label_error):
+ idx.copy().set_codes([[0, 0, 0, 0], [0, 0]])
+
+ # test set_codes with verify_integrity=False
+ # the setting should not raise any value error
+ idx.copy().set_codes(codes=[[0, 0, 0, 0], [0, 0]], verify_integrity=False)
+
+ # code value smaller than -1
+ with pytest.raises(ValueError, match=code_value_error):
+ MultiIndex(levels=[["a"], ["b"]], codes=[[0, -2], [0, 0]])
+
+
+def test_na_levels():
+ # GH26408
+ # test if codes are re-assigned value -1 for levels
+ # with missing values (NaN, NaT, None)
+ result = MultiIndex(
+ levels=[[np.nan, None, pd.NaT, 128, 2]], codes=[[0, -1, 1, 2, 3, 4]]
+ )
+ expected = MultiIndex(
+ levels=[[np.nan, None, pd.NaT, 128, 2]], codes=[[-1, -1, -1, -1, 3, 4]]
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = MultiIndex(
+ levels=[[np.nan, "s", pd.NaT, 128, None]], codes=[[0, -1, 1, 2, 3, 4]]
+ )
+ expected = MultiIndex(
+ levels=[[np.nan, "s", pd.NaT, 128, None]], codes=[[-1, -1, 1, -1, 3, -1]]
+ )
+ tm.assert_index_equal(result, expected)
+
+ # verify set_levels and set_codes
+ result = MultiIndex(
+ levels=[[1, 2, 3, 4, 5]], codes=[[0, -1, 1, 2, 3, 4]]
+ ).set_levels([[np.nan, "s", pd.NaT, 128, None]])
+ tm.assert_index_equal(result, expected)
+
+ result = MultiIndex(
+ levels=[[np.nan, "s", pd.NaT, 128, None]], codes=[[1, 2, 2, 2, 2, 2]]
+ ).set_codes([[0, -1, 1, 2, 3, 4]])
+ tm.assert_index_equal(result, expected)
+
+
+def test_copy_in_constructor():
+ levels = np.array(["a", "b", "c"])
+ codes = np.array([1, 1, 2, 0, 0, 1, 1])
+ val = codes[0]
+ mi = MultiIndex(levels=[levels, levels], codes=[codes, codes], copy=True)
+ assert mi.codes[0][0] == val
+ codes[0] = 15
+ assert mi.codes[0][0] == val
+ val = levels[0]
+ levels[0] = "PANDA"
+ assert mi.levels[0][0] == val
+
+
+# ----------------------------------------------------------------------------
+# from_arrays
+# ----------------------------------------------------------------------------
+def test_from_arrays(idx):
+ arrays = [
+ np.asarray(lev).take(level_codes)
+ for lev, level_codes in zip(idx.levels, idx.codes, strict=True)
+ ]
+
+ # list of arrays as input
+ result = MultiIndex.from_arrays(arrays, names=idx.names)
+ tm.assert_index_equal(result, idx)
+
+ # infer correctly
+ result = MultiIndex.from_arrays([[pd.NaT, Timestamp("20130101")], ["a", "b"]])
+ assert result.levels[0].equals(Index([Timestamp("20130101")]))
+ assert result.levels[1].equals(Index(["a", "b"]))
+
+
+def test_from_arrays_iterator(idx):
+ # GH 18434
+ arrays = [
+ np.asarray(lev).take(level_codes)
+ for lev, level_codes in zip(idx.levels, idx.codes, strict=True)
+ ]
+
+ # iterator as input
+ result = MultiIndex.from_arrays(iter(arrays), names=idx.names)
+ tm.assert_index_equal(result, idx)
+
+ # invalid iterator input
+ msg = "Input must be a list / sequence of array-likes."
+ with pytest.raises(TypeError, match=msg):
+ MultiIndex.from_arrays(0)
+
+
+def test_from_arrays_tuples(idx):
+ arrays = tuple(
+ tuple(np.asarray(lev).take(level_codes))
+ for lev, level_codes in zip(idx.levels, idx.codes, strict=True)
+ )
+
+ # tuple of tuples as input
+ result = MultiIndex.from_arrays(arrays, names=idx.names)
+ tm.assert_index_equal(result, idx)
+
+
+@pytest.mark.parametrize(
+ ("idx1", "idx2"),
+ [
+ (
+ pd.period_range("2011-01-01", freq="D", periods=3),
+ pd.period_range("2015-01-01", freq="h", periods=3),
+ ),
+ (
+ date_range("2015-01-01 10:00", freq="D", periods=3, tz="US/Eastern"),
+ date_range("2015-01-01 10:00", freq="h", periods=3, tz="Asia/Tokyo"),
+ ),
+ (
+ pd.timedelta_range("1 days", freq="D", periods=3),
+ pd.timedelta_range("2 hours", freq="h", periods=3),
+ ),
+ ],
+)
+def test_from_arrays_index_series_period_datetimetz_and_timedelta(idx1, idx2):
+ result = MultiIndex.from_arrays([idx1, idx2])
+ tm.assert_index_equal(result.get_level_values(0), idx1)
+ tm.assert_index_equal(result.get_level_values(1), idx2)
+
+ result2 = MultiIndex.from_arrays([Series(idx1), Series(idx2)])
+ tm.assert_index_equal(result2.get_level_values(0), idx1)
+ tm.assert_index_equal(result2.get_level_values(1), idx2)
+
+ tm.assert_index_equal(result, result2)
+
+
+def test_from_arrays_index_datetimelike_mixed():
+ idx1 = date_range("2015-01-01 10:00", freq="D", periods=3, tz="US/Eastern")
+ idx2 = date_range("2015-01-01 10:00", freq="h", periods=3)
+ idx3 = pd.timedelta_range("1 days", freq="D", periods=3)
+ idx4 = pd.period_range("2011-01-01", freq="D", periods=3)
+
+ result = MultiIndex.from_arrays([idx1, idx2, idx3, idx4])
+ tm.assert_index_equal(result.get_level_values(0), idx1)
+ tm.assert_index_equal(result.get_level_values(1), idx2)
+ tm.assert_index_equal(result.get_level_values(2), idx3)
+ tm.assert_index_equal(result.get_level_values(3), idx4)
+
+ result2 = MultiIndex.from_arrays(
+ [Series(idx1), Series(idx2), Series(idx3), Series(idx4)]
+ )
+ tm.assert_index_equal(result2.get_level_values(0), idx1)
+ tm.assert_index_equal(result2.get_level_values(1), idx2)
+ tm.assert_index_equal(result2.get_level_values(2), idx3)
+ tm.assert_index_equal(result2.get_level_values(3), idx4)
+
+ tm.assert_index_equal(result, result2)
+
+
+def test_from_arrays_index_series_categorical():
+ # GH13743
+ idx1 = pd.CategoricalIndex(list("abcaab"), categories=list("bac"), ordered=False)
+ idx2 = pd.CategoricalIndex(list("abcaab"), categories=list("bac"), ordered=True)
+
+ result = MultiIndex.from_arrays([idx1, idx2])
+ tm.assert_index_equal(result.get_level_values(0), idx1)
+ tm.assert_index_equal(result.get_level_values(1), idx2)
+
+ result2 = MultiIndex.from_arrays([Series(idx1), Series(idx2)])
+ tm.assert_index_equal(result2.get_level_values(0), idx1)
+ tm.assert_index_equal(result2.get_level_values(1), idx2)
+
+ result3 = MultiIndex.from_arrays([idx1.values, idx2.values])
+ tm.assert_index_equal(result3.get_level_values(0), idx1)
+ tm.assert_index_equal(result3.get_level_values(1), idx2)
+
+
+def test_from_arrays_empty():
+ # 0 levels
+ msg = "Must pass non-zero number of levels/codes"
+ with pytest.raises(ValueError, match=msg):
+ MultiIndex.from_arrays(arrays=[])
+
+ # 1 level
+ result = MultiIndex.from_arrays(arrays=[[]], names=["A"])
+ assert isinstance(result, MultiIndex)
+ expected = Index([], name="A")
+ tm.assert_index_equal(result.levels[0], expected)
+ assert result.names == ["A"]
+
+ # N levels
+ for N in [2, 3]:
+ arrays = [[]] * N
+ names = list("ABC")[:N]
+ result = MultiIndex.from_arrays(arrays=arrays, names=names)
+ expected = MultiIndex(levels=[[]] * N, codes=[[]] * N, names=names)
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "invalid_sequence_of_arrays",
+ [
+ 1,
+ [1],
+ [1, 2],
+ [[1], 2],
+ [1, [2]],
+ "a",
+ ["a"],
+ ["a", "b"],
+ [["a"], "b"],
+ (1,),
+ (1, 2),
+ ([1], 2),
+ (1, [2]),
+ ("a",),
+ ("a", "b"),
+ (["a"], "b"),
+ [(1,), 2],
+ [1, (2,)],
+ [("a",), "b"],
+ ((1,), 2),
+ (1, (2,)),
+ (("a",), "b"),
+ ],
+)
+def test_from_arrays_invalid_input(invalid_sequence_of_arrays):
+ msg = "Input must be a list / sequence of array-likes"
+ with pytest.raises(TypeError, match=msg):
+ MultiIndex.from_arrays(arrays=invalid_sequence_of_arrays)
+
+
+@pytest.mark.parametrize(
+ "idx1, idx2", [([1, 2, 3], ["a", "b"]), ([], ["a", "b"]), ([1, 2, 3], [])]
+)
+def test_from_arrays_different_lengths(idx1, idx2):
+ # see gh-13599
+ msg = "^all arrays must be same length$"
+ with pytest.raises(ValueError, match=msg):
+ MultiIndex.from_arrays([idx1, idx2])
+
+
+def test_from_arrays_respects_none_names():
+ # GH27292
+ a = Series([1, 2, 3], name="foo")
+ b = Series(["a", "b", "c"], name="bar")
+
+ result = MultiIndex.from_arrays([a, b], names=None)
+ expected = MultiIndex(
+ levels=[[1, 2, 3], ["a", "b", "c"]], codes=[[0, 1, 2], [0, 1, 2]], names=None
+ )
+
+ tm.assert_index_equal(result, expected)
+
+
+# ----------------------------------------------------------------------------
+# from_tuples
+# ----------------------------------------------------------------------------
+def test_from_tuples():
+ msg = "Cannot infer number of levels from empty list"
+ with pytest.raises(TypeError, match=msg):
+ MultiIndex.from_tuples([])
+
+ expected = MultiIndex(
+ levels=[[1, 3], [2, 4]], codes=[[0, 1], [0, 1]], names=["a", "b"]
+ )
+
+ # input tuples
+ result = MultiIndex.from_tuples(((1, 2), (3, 4)), names=["a", "b"])
+ tm.assert_index_equal(result, expected)
+
+
+def test_from_tuples_iterator():
+ # GH 18434
+ # input iterator for tuples
+ expected = MultiIndex(
+ levels=[[1, 3], [2, 4]], codes=[[0, 1], [0, 1]], names=["a", "b"]
+ )
+
+ result = MultiIndex.from_tuples(zip([1, 3], [2, 4], strict=True), names=["a", "b"])
+ tm.assert_index_equal(result, expected)
+
+ # input non-iterables
+ msg = "Input must be a list / sequence of tuple-likes."
+ with pytest.raises(TypeError, match=msg):
+ MultiIndex.from_tuples(0)
+
+
+def test_from_tuples_empty():
+ # GH 16777
+ result = MultiIndex.from_tuples([], names=["a", "b"])
+ expected = MultiIndex.from_arrays(arrays=[[], []], names=["a", "b"])
+ tm.assert_index_equal(result, expected)
+
+
+def test_from_tuples_index_values(idx):
+ result = MultiIndex.from_tuples(idx)
+ assert (result.values == idx.values).all()
+
+
+def test_tuples_with_name_string():
+ # GH 15110 and GH 14848
+
+ li = [(0, 0, 1), (0, 1, 0), (1, 0, 0)]
+ msg = "Names should be list-like for a MultiIndex"
+ with pytest.raises(ValueError, match=msg):
+ Index(li, name="abc")
+ with pytest.raises(ValueError, match=msg):
+ Index(li, name="a")
+
+
+def test_from_tuples_with_tuple_label():
+ # GH 15457
+ expected = pd.DataFrame(
+ [[2, 1, 2], [4, (1, 2), 3]], columns=["a", "b", "c"]
+ ).set_index(["a", "b"])
+ idx = MultiIndex.from_tuples([(2, 1), (4, (1, 2))], names=("a", "b"))
+ result = pd.DataFrame([2, 3], columns=["c"], index=idx)
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize(
+ "keys, expected",
+ [
+ ((("l1",), ("l1", "l2")), (("l1", np.nan), ("l1", "l2"))),
+ ((("l1", "l2"), ("l1",)), (("l1", "l2"), ("l1", np.nan))),
+ ],
+)
+def test_from_tuples_with_various_tuple_lengths(keys, expected):
+ # GH 60695
+ idx = MultiIndex.from_tuples(keys)
+ assert tuple(idx) == expected
+
+
+# ----------------------------------------------------------------------------
+# from_product
+# ----------------------------------------------------------------------------
+def test_from_product_empty_zero_levels():
+ # 0 levels
+ msg = "Must pass non-zero number of levels/codes"
+ with pytest.raises(ValueError, match=msg):
+ MultiIndex.from_product([])
+
+
+def test_from_product_empty_one_level():
+ result = MultiIndex.from_product([[]], names=["A"])
+ expected = Index([], name="A")
+ tm.assert_index_equal(result.levels[0], expected)
+ assert result.names == ["A"]
+
+
+@pytest.mark.parametrize(
+ "first, second", [([], []), (["foo", "bar", "baz"], []), ([], ["a", "b", "c"])]
+)
+def test_from_product_empty_two_levels(first, second):
+ names = ["A", "B"]
+ result = MultiIndex.from_product([first, second], names=names)
+ expected = MultiIndex(levels=[first, second], codes=[[], []], names=names)
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("N", list(range(4)))
+def test_from_product_empty_three_levels(N):
+ # GH12258
+ names = ["A", "B", "C"]
+ lvl2 = list(range(N))
+ result = MultiIndex.from_product([[], lvl2, []], names=names)
+ expected = MultiIndex(levels=[[], lvl2, []], codes=[[], [], []], names=names)
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "invalid_input", [1, [1], [1, 2], [[1], 2], "a", ["a"], ["a", "b"], [["a"], "b"]]
+)
+def test_from_product_invalid_input(invalid_input):
+ msg = r"Input must be a list / sequence of iterables|Input must be list-like"
+ with pytest.raises(TypeError, match=msg):
+ MultiIndex.from_product(iterables=invalid_input)
+
+
+def test_from_product_datetimeindex():
+ dt_index = date_range("2000-01-01", periods=2)
+ mi = MultiIndex.from_product([[1, 2], dt_index])
+ etalon = construct_1d_object_array_from_listlike(
+ [
+ (1, Timestamp("2000-01-01")),
+ (1, Timestamp("2000-01-02")),
+ (2, Timestamp("2000-01-01")),
+ (2, Timestamp("2000-01-02")),
+ ]
+ )
+ tm.assert_numpy_array_equal(mi.values, etalon)
+
+
+def test_from_product_rangeindex():
+ # RangeIndex is preserved by factorize, so preserved in levels
+ rng = Index(range(5))
+ other = ["a", "b"]
+ mi = MultiIndex.from_product([rng, other])
+ tm.assert_index_equal(mi._levels[0], rng, exact=True)
+
+
+@pytest.mark.parametrize("ordered", [False, True])
+@pytest.mark.parametrize("f", [lambda x: x, lambda x: Series(x), lambda x: x.values])
+def test_from_product_index_series_categorical(ordered, f):
+ # GH13743
+ first = ["foo", "bar"]
+
+ idx = pd.CategoricalIndex(list("abcaab"), categories=list("bac"), ordered=ordered)
+ expected = pd.CategoricalIndex(
+ list("abcaab") + list("abcaab"), categories=list("bac"), ordered=ordered
+ )
+
+ result = MultiIndex.from_product([first, f(idx)])
+ tm.assert_index_equal(result.get_level_values(1), expected)
+
+
+def test_from_product():
+ first = ["foo", "bar", "buz"]
+ second = ["a", "b", "c"]
+ names = ["first", "second"]
+ result = MultiIndex.from_product([first, second], names=names)
+
+ tuples = [
+ ("foo", "a"),
+ ("foo", "b"),
+ ("foo", "c"),
+ ("bar", "a"),
+ ("bar", "b"),
+ ("bar", "c"),
+ ("buz", "a"),
+ ("buz", "b"),
+ ("buz", "c"),
+ ]
+ expected = MultiIndex.from_tuples(tuples, names=names)
+
+ tm.assert_index_equal(result, expected)
+
+
+def test_from_product_iterator():
+ # GH 18434
+ first = ["foo", "bar", "buz"]
+ second = ["a", "b", "c"]
+ names = ["first", "second"]
+ tuples = [
+ ("foo", "a"),
+ ("foo", "b"),
+ ("foo", "c"),
+ ("bar", "a"),
+ ("bar", "b"),
+ ("bar", "c"),
+ ("buz", "a"),
+ ("buz", "b"),
+ ("buz", "c"),
+ ]
+ expected = MultiIndex.from_tuples(tuples, names=names)
+
+ # iterator as input
+ result = MultiIndex.from_product(iter([first, second]), names=names)
+ tm.assert_index_equal(result, expected)
+
+ # Invalid non-iterable input
+ msg = "Input must be a list / sequence of iterables."
+ with pytest.raises(TypeError, match=msg):
+ MultiIndex.from_product(0)
+
+
+@pytest.mark.parametrize(
+ "a, b, expected_names",
+ [
+ (
+ Series([1, 2, 3], name="foo"),
+ Series(["a", "b"], name="bar"),
+ ["foo", "bar"],
+ ),
+ (Series([1, 2, 3], name="foo"), ["a", "b"], ["foo", None]),
+ ([1, 2, 3], ["a", "b"], None),
+ ],
+)
+def test_from_product_infer_names(a, b, expected_names):
+ # GH27292
+ result = MultiIndex.from_product([a, b])
+ expected = MultiIndex(
+ levels=[[1, 2, 3], ["a", "b"]],
+ codes=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]],
+ names=expected_names,
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_from_product_respects_none_names():
+ # GH27292
+ a = Series([1, 2, 3], name="foo")
+ b = Series(["a", "b"], name="bar")
+
+ result = MultiIndex.from_product([a, b], names=None)
+ expected = MultiIndex(
+ levels=[[1, 2, 3], ["a", "b"]],
+ codes=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]],
+ names=None,
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_from_product_readonly():
+ # GH#15286 passing read-only array to from_product
+ a = np.array(range(3))
+ b = ["a", "b"]
+ expected = MultiIndex.from_product([a, b])
+
+ a.setflags(write=False)
+ result = MultiIndex.from_product([a, b])
+ tm.assert_index_equal(result, expected)
+
+
+def test_create_index_existing_name(idx):
+ # GH11193, when an existing index is passed, and a new name is not
+ # specified, the new index should inherit the previous object name
+ index = idx
+ index.names = ["foo", "bar"]
+ result = Index(index)
+ expected = Index(
+ Index(
+ [
+ ("foo", "one"),
+ ("foo", "two"),
+ ("bar", "one"),
+ ("baz", "two"),
+ ("qux", "one"),
+ ("qux", "two"),
+ ],
+ dtype="object",
+ )
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = Index(index, name="A")
+ expected = Index(
+ Index(
+ [
+ ("foo", "one"),
+ ("foo", "two"),
+ ("bar", "one"),
+ ("baz", "two"),
+ ("qux", "one"),
+ ("qux", "two"),
+ ],
+ dtype="object",
+ ),
+ name="A",
+ )
+ tm.assert_index_equal(result, expected)
+
+
+# ----------------------------------------------------------------------------
+# from_frame
+# ----------------------------------------------------------------------------
+def test_from_frame():
+ # GH 22420
+ df = pd.DataFrame(
+ [["a", "a"], ["a", "b"], ["b", "a"], ["b", "b"]], columns=["L1", "L2"]
+ )
+ expected = MultiIndex.from_tuples(
+ [("a", "a"), ("a", "b"), ("b", "a"), ("b", "b")], names=["L1", "L2"]
+ )
+ result = MultiIndex.from_frame(df)
+ tm.assert_index_equal(expected, result)
+
+
+def test_from_frame_missing_values_multiIndex():
+ # GH 39984
+ pa = pytest.importorskip("pyarrow")
+
+ df = pd.DataFrame(
+ {
+ "a": Series([1, 2, None], dtype="Int64"),
+ "b": pd.Float64Dtype().__from_arrow__(pa.array([0.2, None, None])),
+ }
+ )
+ multi_indexed = MultiIndex.from_frame(df)
+ expected = MultiIndex.from_arrays(
+ [
+ Series([1, 2, None], dtype="Int64"),
+ pd.Float64Dtype().__from_arrow__(pa.array([0.2, None, None])),
+ ],
+ names=["a", "b"],
+ )
+ tm.assert_index_equal(multi_indexed, expected)
+
+
+@pytest.mark.parametrize(
+ "non_frame",
+ [
+ Series([1, 2, 3, 4]),
+ [1, 2, 3, 4],
+ [[1, 2], [3, 4], [5, 6]],
+ Index([1, 2, 3, 4]),
+ np.array([[1, 2], [3, 4], [5, 6]]),
+ 27,
+ ],
+)
+def test_from_frame_error(non_frame):
+ # GH 22420
+ with pytest.raises(TypeError, match="Input must be a DataFrame"):
+ MultiIndex.from_frame(non_frame)
+
+
+def test_from_frame_dtype_fidelity():
+ # GH 22420
+ df = pd.DataFrame(
+ {
+ "dates": date_range("19910905", periods=6, tz="US/Eastern"),
+ "a": [1, 1, 1, 2, 2, 2],
+ "b": pd.Categorical(["a", "a", "b", "b", "c", "c"], ordered=True),
+ "c": ["x", "x", "y", "z", "x", "y"],
+ }
+ )
+ original_dtypes = df.dtypes.to_dict()
+
+ expected_mi = MultiIndex.from_arrays(
+ [
+ date_range("19910905", periods=6, tz="US/Eastern"),
+ [1, 1, 1, 2, 2, 2],
+ pd.Categorical(["a", "a", "b", "b", "c", "c"], ordered=True),
+ ["x", "x", "y", "z", "x", "y"],
+ ],
+ names=["dates", "a", "b", "c"],
+ )
+ mi = MultiIndex.from_frame(df)
+ mi_dtypes = {name: mi.levels[i].dtype for i, name in enumerate(mi.names)}
+
+ tm.assert_index_equal(expected_mi, mi)
+ assert original_dtypes == mi_dtypes
+
+
+@pytest.mark.parametrize(
+ "names_in,names_out", [(None, [("L1", "x"), ("L2", "y")]), (["x", "y"], ["x", "y"])]
+)
+def test_from_frame_valid_names(names_in, names_out):
+ # GH 22420
+ df = pd.DataFrame(
+ [["a", "a"], ["a", "b"], ["b", "a"], ["b", "b"]],
+ columns=MultiIndex.from_tuples([("L1", "x"), ("L2", "y")]),
+ )
+ mi = MultiIndex.from_frame(df, names=names_in)
+ assert mi.names == names_out
+
+
+@pytest.mark.parametrize(
+ "names,expected_error_msg",
+ [
+ ("bad_input", "Names should be list-like for a MultiIndex"),
+ (["a", "b", "c"], "Length of names must match number of levels in MultiIndex"),
+ ],
+)
+def test_from_frame_invalid_names(names, expected_error_msg):
+ # GH 22420
+ df = pd.DataFrame(
+ [["a", "a"], ["a", "b"], ["b", "a"], ["b", "b"]],
+ columns=MultiIndex.from_tuples([("L1", "x"), ("L2", "y")]),
+ )
+ with pytest.raises(ValueError, match=expected_error_msg):
+ MultiIndex.from_frame(df, names=names)
+
+
+def test_index_equal_empty_iterable():
+ # #16844
+ a = MultiIndex(levels=[[], []], codes=[[], []], names=["a", "b"])
+ b = MultiIndex.from_arrays(arrays=[[], []], names=["a", "b"])
+ tm.assert_index_equal(a, b)
+
+
+def test_raise_invalid_sortorder():
+ # Test that the MultiIndex constructor raise when an incorrect sortorder is given
+ # GH#28518
+
+ levels = [[0, 1], [0, 1, 2]]
+
+ # Correct sortorder
+ MultiIndex(
+ levels=levels, codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]], sortorder=2
+ )
+
+ with pytest.raises(ValueError, match=r".* sortorder 2 with lexsort_depth 1.*"):
+ MultiIndex(
+ levels=levels, codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 2, 1]], sortorder=2
+ )
+
+ with pytest.raises(ValueError, match=r".* sortorder 1 with lexsort_depth 0.*"):
+ MultiIndex(
+ levels=levels, codes=[[0, 0, 1, 0, 1, 1], [0, 1, 0, 2, 2, 1]], sortorder=1
+ )
+
+
+def test_datetimeindex():
+ idx1 = pd.DatetimeIndex(
+ ["2013-04-01 9:00", "2013-04-02 9:00", "2013-04-03 9:00"] * 2, tz="Asia/Tokyo"
+ )
+ idx2 = date_range("2010/01/01", periods=6, freq="ME", tz="US/Eastern")
+ idx = MultiIndex.from_arrays([idx1, idx2])
+
+ expected1 = pd.DatetimeIndex(
+ ["2013-04-01 9:00", "2013-04-02 9:00", "2013-04-03 9:00"], tz="Asia/Tokyo"
+ )
+
+ tm.assert_index_equal(idx.levels[0], expected1)
+ tm.assert_index_equal(idx.levels[1], idx2)
+
+ # from datetime combos
+ # GH 7888
+ date1 = np.datetime64("today")
+ date2 = datetime.today()
+ date3 = Timestamp.today()
+
+ for d1, d2 in itertools.product([date1, date2, date3], [date1, date2, date3]):
+ index = MultiIndex.from_product([[d1], [d2]])
+ assert isinstance(index.levels[0], pd.DatetimeIndex)
+ assert isinstance(index.levels[1], pd.DatetimeIndex)
+
+ # but NOT date objects, matching Index behavior
+ date4 = date.today()
+ index = MultiIndex.from_product([[date4], [date2]])
+ assert not isinstance(index.levels[0], pd.DatetimeIndex)
+ assert isinstance(index.levels[1], pd.DatetimeIndex)
+
+
+def test_constructor_with_tz():
+ index = pd.DatetimeIndex(
+ ["2013/01/01 09:00", "2013/01/02 09:00"], name="dt1", tz="US/Pacific"
+ )
+ columns = pd.DatetimeIndex(
+ ["2014/01/01 09:00", "2014/01/02 09:00"], name="dt2", tz="Asia/Tokyo"
+ )
+
+ result = MultiIndex.from_arrays([index, columns])
+
+ assert result.names == ["dt1", "dt2"]
+ tm.assert_index_equal(result.levels[0], index)
+ tm.assert_index_equal(result.levels[1], columns)
+
+ result = MultiIndex.from_arrays([Series(index), Series(columns)])
+
+ assert result.names == ["dt1", "dt2"]
+ tm.assert_index_equal(result.levels[0], index)
+ tm.assert_index_equal(result.levels[1], columns)
+
+
+def test_multiindex_inference_consistency():
+ # check that inference behavior matches the base class
+
+ v = date.today()
+
+ arr = [v, v]
+
+ idx = Index(arr)
+ assert idx.dtype == object
+
+ mi = MultiIndex.from_arrays([arr])
+ lev = mi.levels[0]
+ assert lev.dtype == object
+
+ mi = MultiIndex.from_product([arr])
+ lev = mi.levels[0]
+ assert lev.dtype == object
+
+ mi = MultiIndex.from_tuples([(x,) for x in arr])
+ lev = mi.levels[0]
+ assert lev.dtype == object
+
+
+def test_dtype_representation(using_infer_string):
+ # GH#46900
+ pmidx = MultiIndex.from_arrays([[1], ["a"]], names=[("a", "b"), ("c", "d")])
+ result = pmidx.dtypes
+ exp = "object" if not using_infer_string else pd.StringDtype(na_value=np.nan)
+ expected = Series(
+ ["int64", exp],
+ index=MultiIndex.from_tuples([("a", "b"), ("c", "d")]),
+ dtype=object,
+ )
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_conversion.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_conversion.py
new file mode 100644
index 0000000000000000000000000000000000000000..347d6b206e3b94deb2c8f57fbce0571d7ab14bbe
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_conversion.py
@@ -0,0 +1,208 @@
+import numpy as np
+import pytest
+
+from pandas.compat.numpy import np_version_gt2
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ RangeIndex,
+)
+import pandas._testing as tm
+
+
+def test_to_numpy(idx):
+ result = idx.to_numpy()
+ exp = idx.values
+ tm.assert_numpy_array_equal(result, exp)
+
+
+def test_array_interface(idx):
+ # https://github.com/pandas-dev/pandas/pull/60046
+ result = np.asarray(idx)
+ expected = np.empty((6,), dtype=object)
+ expected[:] = [
+ ("foo", "one"),
+ ("foo", "two"),
+ ("bar", "one"),
+ ("baz", "two"),
+ ("qux", "one"),
+ ("qux", "two"),
+ ]
+ tm.assert_numpy_array_equal(result, expected)
+
+ # it always gives a copy by default, but the values are cached, so results
+ # are still sharing memory
+ result_copy1 = np.asarray(idx)
+ result_copy2 = np.asarray(idx)
+ assert np.may_share_memory(result_copy1, result_copy2)
+
+ # with explicit copy=True, then it is an actual copy
+ result_copy1 = np.array(idx, copy=True)
+ result_copy2 = np.array(idx, copy=True)
+ assert not np.may_share_memory(result_copy1, result_copy2)
+
+ if not np_version_gt2:
+ # copy=False semantics are only supported in NumPy>=2.
+ return
+
+ # for MultiIndex, copy=False is never allowed
+ with pytest.raises(ValueError, match="Unable to avoid copy while creating"):
+ np.array(idx, copy=False)
+
+
+def test_to_frame():
+ tuples = [(1, "one"), (1, "two"), (2, "one"), (2, "two")]
+
+ index = MultiIndex.from_tuples(tuples)
+ result = index.to_frame(index=False)
+ expected = DataFrame(tuples)
+ tm.assert_frame_equal(result, expected)
+
+ result = index.to_frame()
+ expected.index = index
+ tm.assert_frame_equal(result, expected)
+
+ tuples = [(1, "one"), (1, "two"), (2, "one"), (2, "two")]
+ index = MultiIndex.from_tuples(tuples, names=["first", "second"])
+ result = index.to_frame(index=False)
+ expected = DataFrame(tuples)
+ expected.columns = ["first", "second"]
+ tm.assert_frame_equal(result, expected)
+
+ result = index.to_frame()
+ expected.index = index
+ tm.assert_frame_equal(result, expected)
+
+ # See GH-22580
+ index = MultiIndex.from_tuples(tuples)
+ result = index.to_frame(index=False, name=["first", "second"])
+ expected = DataFrame(tuples)
+ expected.columns = ["first", "second"]
+ tm.assert_frame_equal(result, expected)
+
+ result = index.to_frame(name=["first", "second"])
+ expected.index = index
+ expected.columns = ["first", "second"]
+ tm.assert_frame_equal(result, expected)
+
+ msg = "'name' must be a list / sequence of column names."
+ with pytest.raises(TypeError, match=msg):
+ index.to_frame(name="first")
+
+ msg = "'name' should have same length as number of levels on index."
+ with pytest.raises(ValueError, match=msg):
+ index.to_frame(name=["first"])
+
+ # Tests for datetime index
+ index = MultiIndex.from_product([range(5), pd.date_range("20130101", periods=3)])
+ result = index.to_frame(index=False)
+ expected = DataFrame(
+ {
+ 0: np.repeat(np.arange(5, dtype="int64"), 3),
+ 1: np.tile(pd.date_range("20130101", periods=3), 5),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = index.to_frame()
+ expected.index = index
+ tm.assert_frame_equal(result, expected)
+
+ # See GH-22580
+ result = index.to_frame(index=False, name=["first", "second"])
+ expected = DataFrame(
+ {
+ "first": np.repeat(np.arange(5, dtype="int64"), 3),
+ "second": np.tile(pd.date_range("20130101", periods=3), 5),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = index.to_frame(name=["first", "second"])
+ expected.index = index
+ tm.assert_frame_equal(result, expected)
+
+
+def test_to_frame_dtype_fidelity():
+ # GH 22420
+ mi = MultiIndex.from_arrays(
+ [
+ pd.date_range("19910905", periods=6, tz="US/Eastern"),
+ [1, 1, 1, 2, 2, 2],
+ pd.Categorical(["a", "a", "b", "b", "c", "c"], ordered=True),
+ ["x", "x", "y", "z", "x", "y"],
+ ],
+ names=["dates", "a", "b", "c"],
+ )
+ original_dtypes = {name: mi.levels[i].dtype for i, name in enumerate(mi.names)}
+
+ expected_df = DataFrame(
+ {
+ "dates": pd.date_range("19910905", periods=6, tz="US/Eastern"),
+ "a": [1, 1, 1, 2, 2, 2],
+ "b": pd.Categorical(["a", "a", "b", "b", "c", "c"], ordered=True),
+ "c": ["x", "x", "y", "z", "x", "y"],
+ }
+ )
+ df = mi.to_frame(index=False)
+ df_dtypes = df.dtypes.to_dict()
+
+ tm.assert_frame_equal(df, expected_df)
+ assert original_dtypes == df_dtypes
+
+
+def test_to_frame_resulting_column_order():
+ # GH 22420
+ expected = ["z", 0, "a"]
+ mi = MultiIndex.from_arrays(
+ [["a", "b", "c"], ["x", "y", "z"], ["q", "w", "e"]], names=expected
+ )
+ result = mi.to_frame().columns.tolist()
+ assert result == expected
+
+
+def test_to_frame_duplicate_labels():
+ # GH 45245
+ data = [(1, 2), (3, 4)]
+ names = ["a", "a"]
+ index = MultiIndex.from_tuples(data, names=names)
+ with pytest.raises(ValueError, match="Cannot create duplicate column labels"):
+ index.to_frame()
+
+ result = index.to_frame(allow_duplicates=True)
+ expected = DataFrame(data, index=index, columns=names)
+ tm.assert_frame_equal(result, expected)
+
+ names = [None, 0]
+ index = MultiIndex.from_tuples(data, names=names)
+ with pytest.raises(ValueError, match="Cannot create duplicate column labels"):
+ index.to_frame()
+
+ result = index.to_frame(allow_duplicates=True)
+ expected = DataFrame(data, index=index, columns=[0, 0])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_to_frame_column_rangeindex():
+ mi = MultiIndex.from_arrays([[1, 2], ["a", "b"]])
+ result = mi.to_frame().columns
+ expected = RangeIndex(2)
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_to_flat_index(idx):
+ expected = pd.Index(
+ (
+ ("foo", "one"),
+ ("foo", "two"),
+ ("bar", "one"),
+ ("baz", "two"),
+ ("qux", "one"),
+ ("qux", "two"),
+ ),
+ tupleize_cols=False,
+ )
+ result = idx.to_flat_index()
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_copy.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_copy.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e09a580f9528bc8197d55c6a7533098e0129fa2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_copy.py
@@ -0,0 +1,96 @@
+from copy import (
+ copy,
+ deepcopy,
+)
+
+import pytest
+
+from pandas import MultiIndex
+import pandas._testing as tm
+
+
+def assert_multiindex_copied(copy, original):
+ # Levels should be (at least, shallow copied)
+ tm.assert_copy(copy.levels, original.levels)
+ tm.assert_almost_equal(copy.codes, original.codes)
+
+ # Labels doesn't matter which way copied
+ tm.assert_almost_equal(copy.codes, original.codes)
+ assert copy.codes is not original.codes
+
+ # Names doesn't matter which way copied
+ assert copy.names == original.names
+ assert copy.names is not original.names
+
+ # Sort order should be copied
+ assert copy.sortorder == original.sortorder
+
+
+def test_copy(idx):
+ i_copy = idx.copy()
+
+ assert_multiindex_copied(i_copy, idx)
+
+
+def test_shallow_copy(idx):
+ i_copy = idx._view()
+
+ assert_multiindex_copied(i_copy, idx)
+
+
+def test_view(idx):
+ i_view = idx.view()
+ assert_multiindex_copied(i_view, idx)
+
+
+@pytest.mark.parametrize("func", [copy, deepcopy])
+def test_copy_and_deepcopy(func):
+ idx = MultiIndex(
+ levels=[["foo", "bar"], ["fizz", "buzz"]],
+ codes=[[0, 0, 0, 1], [0, 0, 1, 1]],
+ names=["first", "second"],
+ )
+ idx_copy = func(idx)
+ assert idx_copy is not idx
+ assert idx_copy.equals(idx)
+
+
+@pytest.mark.parametrize("deep", [True, False])
+def test_copy_method(deep):
+ idx = MultiIndex(
+ levels=[["foo", "bar"], ["fizz", "buzz"]],
+ codes=[[0, 0, 0, 1], [0, 0, 1, 1]],
+ names=["first", "second"],
+ )
+ idx_copy = idx.copy(deep=deep)
+ assert idx_copy.equals(idx)
+
+
+@pytest.mark.parametrize("deep", [True, False])
+@pytest.mark.parametrize(
+ "kwarg, value",
+ [
+ ("names", ["third", "fourth"]),
+ ],
+)
+def test_copy_method_kwargs(deep, kwarg, value):
+ # gh-12309: Check that the "name" argument as well other kwargs are honored
+ idx = MultiIndex(
+ levels=[["foo", "bar"], ["fizz", "buzz"]],
+ codes=[[0, 0, 0, 1], [0, 0, 1, 1]],
+ names=["first", "second"],
+ )
+ idx_copy = idx.copy(**{kwarg: value, "deep": deep})
+ assert getattr(idx_copy, kwarg) == value
+
+
+def test_copy_deep_false_retains_id():
+ # GH#47878
+ idx = MultiIndex(
+ levels=[["foo", "bar"], ["fizz", "buzz"]],
+ codes=[[0, 0, 0, 1], [0, 0, 1, 1]],
+ names=["first", "second"],
+ )
+
+ res = idx.copy(deep=False)
+ assert res._id is idx._id
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_drop.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_drop.py
new file mode 100644
index 0000000000000000000000000000000000000000..b83680be6a5cec756f2a4bb2a201622a0c010bef
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_drop.py
@@ -0,0 +1,188 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+
+def test_drop(idx):
+ dropped = idx.drop([("foo", "two"), ("qux", "one")])
+
+ index = MultiIndex.from_tuples([("foo", "two"), ("qux", "one")])
+ dropped2 = idx.drop(index)
+
+ expected = idx[[0, 2, 3, 5]]
+ tm.assert_index_equal(dropped, expected)
+ tm.assert_index_equal(dropped2, expected)
+
+ dropped = idx.drop(["bar"])
+ expected = idx[[0, 1, 3, 4, 5]]
+ tm.assert_index_equal(dropped, expected)
+
+ dropped = idx.drop("foo")
+ expected = idx[[2, 3, 4, 5]]
+ tm.assert_index_equal(dropped, expected)
+
+ index = MultiIndex.from_tuples([("bar", "two")])
+ with pytest.raises(KeyError, match=r"^\('bar', 'two'\)$"):
+ idx.drop([("bar", "two")])
+ with pytest.raises(KeyError, match=r"^\('bar', 'two'\)$"):
+ idx.drop(index)
+ with pytest.raises(KeyError, match=r"^'two'$"):
+ idx.drop(["foo", "two"])
+
+ # partially correct argument
+ mixed_index = MultiIndex.from_tuples([("qux", "one"), ("bar", "two")])
+ with pytest.raises(KeyError, match=r"^\('bar', 'two'\)$"):
+ idx.drop(mixed_index)
+
+ # error='ignore'
+ dropped = idx.drop(index, errors="ignore")
+ expected = idx[[0, 1, 2, 3, 4, 5]]
+ tm.assert_index_equal(dropped, expected)
+
+ dropped = idx.drop(mixed_index, errors="ignore")
+ expected = idx[[0, 1, 2, 3, 5]]
+ tm.assert_index_equal(dropped, expected)
+
+ dropped = idx.drop(["foo", "two"], errors="ignore")
+ expected = idx[[2, 3, 4, 5]]
+ tm.assert_index_equal(dropped, expected)
+
+ # mixed partial / full drop
+ dropped = idx.drop(["foo", ("qux", "one")])
+ expected = idx[[2, 3, 5]]
+ tm.assert_index_equal(dropped, expected)
+
+ # mixed partial / full drop / error='ignore'
+ mixed_index = ["foo", ("qux", "one"), "two"]
+ with pytest.raises(KeyError, match=r"^'two'$"):
+ idx.drop(mixed_index)
+ dropped = idx.drop(mixed_index, errors="ignore")
+ expected = idx[[2, 3, 5]]
+ tm.assert_index_equal(dropped, expected)
+
+
+def test_droplevel_with_names(idx):
+ index = idx[idx.get_loc("foo")]
+ dropped = index.droplevel(0)
+ assert dropped.name == "second"
+
+ index = MultiIndex(
+ levels=[Index(range(4)), Index(range(4)), Index(range(4))],
+ codes=[
+ np.array([0, 0, 1, 2, 2, 2, 3, 3]),
+ np.array([0, 1, 0, 0, 0, 1, 0, 1]),
+ np.array([1, 0, 1, 1, 0, 0, 1, 0]),
+ ],
+ names=["one", "two", "three"],
+ )
+ dropped = index.droplevel(0)
+ assert dropped.names == ("two", "three")
+
+ dropped = index.droplevel("two")
+ expected = index.droplevel(1)
+ assert dropped.equals(expected)
+
+
+def test_droplevel_list():
+ index = MultiIndex(
+ levels=[Index(range(4)), Index(range(4)), Index(range(4))],
+ codes=[
+ np.array([0, 0, 1, 2, 2, 2, 3, 3]),
+ np.array([0, 1, 0, 0, 0, 1, 0, 1]),
+ np.array([1, 0, 1, 1, 0, 0, 1, 0]),
+ ],
+ names=["one", "two", "three"],
+ )
+
+ dropped = index[:2].droplevel(["three", "one"])
+ expected = index[:2].droplevel(2).droplevel(0)
+ assert dropped.equals(expected)
+
+ dropped = index[:2].droplevel([])
+ expected = index[:2]
+ assert dropped.equals(expected)
+
+ msg = (
+ "Cannot remove 3 levels from an index with 3 levels: "
+ "at least one level must be left"
+ )
+ with pytest.raises(ValueError, match=msg):
+ index[:2].droplevel(["one", "two", "three"])
+
+ with pytest.raises(KeyError, match="'Level four not found'"):
+ index[:2].droplevel(["one", "four"])
+
+
+def test_drop_not_lexsorted(performance_warning):
+ # GH 12078
+
+ # define the lexsorted version of the multi-index
+ tuples = [("a", ""), ("b1", "c1"), ("b2", "c2")]
+ lexsorted_mi = MultiIndex.from_tuples(tuples, names=["b", "c"])
+ assert lexsorted_mi._is_lexsorted()
+
+ # and the not-lexsorted version
+ df = pd.DataFrame(
+ columns=["a", "b", "c", "d"], data=[[1, "b1", "c1", 3], [1, "b2", "c2", 4]]
+ )
+ df = df.pivot_table(index="a", columns=["b", "c"], values="d")
+ df = df.reset_index()
+ not_lexsorted_mi = df.columns
+ assert not not_lexsorted_mi._is_lexsorted()
+
+ # compare the results
+ tm.assert_index_equal(lexsorted_mi, not_lexsorted_mi)
+ with tm.assert_produces_warning(performance_warning):
+ tm.assert_index_equal(lexsorted_mi.drop("a"), not_lexsorted_mi.drop("a"))
+
+
+def test_drop_with_nan_in_index(nulls_fixture):
+ # GH#18853
+ mi = MultiIndex.from_tuples([("blah", nulls_fixture)], names=["name", "date"])
+ msg = r"labels \[Timestamp\('2001-01-01 00:00:00'\)\] not found in level"
+ with pytest.raises(KeyError, match=msg):
+ mi.drop(pd.Timestamp("2001"), level="date")
+
+
+@pytest.mark.filterwarnings("ignore::pandas.errors.PerformanceWarning")
+def test_drop_with_non_monotonic_duplicates():
+ # GH#33494
+ mi = MultiIndex.from_tuples([(1, 2), (2, 3), (1, 2)])
+ result = mi.drop((1, 2))
+ expected = MultiIndex.from_tuples([(2, 3)])
+ tm.assert_index_equal(result, expected)
+
+
+def test_single_level_drop_partially_missing_elements():
+ # GH 37820
+
+ mi = MultiIndex.from_tuples([(1, 2), (2, 2), (3, 2)])
+ msg = r"labels \[4\] not found in level"
+ with pytest.raises(KeyError, match=msg):
+ mi.drop(4, level=0)
+ with pytest.raises(KeyError, match=msg):
+ mi.drop([1, 4], level=0)
+ msg = r"labels \[nan\] not found in level"
+ with pytest.raises(KeyError, match=msg):
+ mi.drop([np.nan], level=0)
+ with pytest.raises(KeyError, match=msg):
+ mi.drop([np.nan, 1, 2, 3], level=0)
+
+ mi = MultiIndex.from_tuples([(np.nan, 1), (1, 2)])
+ msg = r"labels \['a'\] not found in level"
+ with pytest.raises(KeyError, match=msg):
+ mi.drop([np.nan, 1, "a"], level=0)
+
+
+def test_droplevel_multiindex_one_level():
+ # GH#37208
+ index = MultiIndex.from_tuples([(2,)], names=("b",))
+ result = index.droplevel([])
+ expected = Index([2], name="b")
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_duplicates.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_duplicates.py
new file mode 100644
index 0000000000000000000000000000000000000000..babacbe557efdc775dde19dbbd43db8405ae4ddd
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_duplicates.py
@@ -0,0 +1,357 @@
+from itertools import product
+
+import numpy as np
+import pytest
+
+from pandas._libs import (
+ hashtable,
+ index as libindex,
+)
+
+from pandas import (
+ NA,
+ DatetimeIndex,
+ Index,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def idx_dup():
+ # compare tests/indexes/multi/conftest.py
+ major_axis = Index(["foo", "bar", "baz", "qux"])
+ minor_axis = Index(["one", "two"])
+
+ major_codes = np.array([0, 0, 1, 0, 1, 1])
+ minor_codes = np.array([0, 1, 0, 1, 0, 1])
+ index_names = ["first", "second"]
+ mi = MultiIndex(
+ levels=[major_axis, minor_axis],
+ codes=[major_codes, minor_codes],
+ names=index_names,
+ verify_integrity=False,
+ )
+ return mi
+
+
+@pytest.mark.parametrize("names", [None, ["first", "second"]])
+def test_unique(names):
+ mi = MultiIndex.from_arrays([[1, 2, 1, 2], [1, 1, 1, 2]], names=names)
+
+ res = mi.unique()
+ exp = MultiIndex.from_arrays([[1, 2, 2], [1, 1, 2]], names=mi.names)
+ tm.assert_index_equal(res, exp)
+
+ mi = MultiIndex.from_arrays([list("aaaa"), list("abab")], names=names)
+ res = mi.unique()
+ exp = MultiIndex.from_arrays([list("aa"), list("ab")], names=mi.names)
+ tm.assert_index_equal(res, exp)
+
+ mi = MultiIndex.from_arrays([list("aaaa"), list("aaaa")], names=names)
+ res = mi.unique()
+ exp = MultiIndex.from_arrays([["a"], ["a"]], names=mi.names)
+ tm.assert_index_equal(res, exp)
+
+ # GH #20568 - empty MI
+ mi = MultiIndex.from_arrays([[], []], names=names)
+ res = mi.unique()
+ tm.assert_index_equal(mi, res)
+
+
+def test_unique_datetimelike():
+ idx1 = DatetimeIndex(
+ ["2015-01-01", "2015-01-01", "2015-01-01", "2015-01-01", "NaT", "NaT"]
+ )
+ idx2 = DatetimeIndex(
+ ["2015-01-01", "2015-01-01", "2015-01-02", "2015-01-02", "NaT", "2015-01-01"],
+ tz="Asia/Tokyo",
+ )
+ result = MultiIndex.from_arrays([idx1, idx2]).unique()
+
+ eidx1 = DatetimeIndex(["2015-01-01", "2015-01-01", "NaT", "NaT"])
+ eidx2 = DatetimeIndex(
+ ["2015-01-01", "2015-01-02", "NaT", "2015-01-01"], tz="Asia/Tokyo"
+ )
+ exp = MultiIndex.from_arrays([eidx1, eidx2])
+ tm.assert_index_equal(result, exp)
+
+
+@pytest.mark.parametrize("level", [0, "first", 1, "second"])
+def test_unique_level(idx, level):
+ # GH #17896 - with level= argument
+ result = idx.unique(level=level)
+ expected = idx.get_level_values(level).unique()
+ tm.assert_index_equal(result, expected)
+
+ # With already unique level
+ mi = MultiIndex.from_arrays([[1, 3, 2, 4], [1, 3, 2, 5]], names=["first", "second"])
+ result = mi.unique(level=level)
+ expected = mi.get_level_values(level)
+ tm.assert_index_equal(result, expected)
+
+ # With empty MI
+ mi = MultiIndex.from_arrays([[], []], names=["first", "second"])
+ result = mi.unique(level=level)
+ expected = mi.get_level_values(level)
+ tm.assert_index_equal(result, expected)
+
+
+def test_duplicate_multiindex_codes():
+ # GH 17464
+ # Make sure that a MultiIndex with duplicate levels throws a ValueError
+ msg = r"Level values must be unique: \[[A', ]+\] on level 0"
+ with pytest.raises(ValueError, match=msg):
+ mi = MultiIndex([["A"] * 10, range(10)], [[0] * 10, range(10)])
+
+ # And that using set_levels with duplicate levels fails
+ mi = MultiIndex.from_arrays([["A", "A", "B", "B", "B"], [1, 2, 1, 2, 3]])
+ msg = r"Level values must be unique: \[[AB', ]+\] on level 0"
+ with pytest.raises(ValueError, match=msg):
+ mi.set_levels([["A", "B", "A", "A", "B"], [2, 1, 3, -2, 5]])
+
+
+@pytest.mark.parametrize("names", [["a", "b", "a"], [1, 1, 2], [1, "a", 1]])
+def test_duplicate_level_names(names):
+ # GH18872, GH19029
+ mi = MultiIndex.from_product([[0, 1]] * 3, names=names)
+ assert mi.names == names
+
+ # With .rename()
+ mi = MultiIndex.from_product([[0, 1]] * 3)
+ mi = mi.rename(names)
+ assert mi.names == names
+
+ # With .rename(., level=)
+ mi.rename(names[1], level=1, inplace=True)
+ mi = mi.rename([names[0], names[2]], level=[0, 2])
+ assert mi.names == names
+
+
+def test_duplicate_meta_data():
+ # GH 10115
+ mi = MultiIndex(
+ levels=[[0, 1], [0, 1, 2]], codes=[[0, 0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 0, 1, 2]]
+ )
+
+ for idx in [
+ mi,
+ mi.set_names([None, None]),
+ mi.set_names([None, "Num"]),
+ mi.set_names(["Upper", "Num"]),
+ ]:
+ assert idx.has_duplicates
+ assert idx.drop_duplicates().names == idx.names
+
+
+def test_has_duplicates(idx, idx_dup):
+ # see fixtures
+ assert idx.is_unique is True
+ assert idx.has_duplicates is False
+ assert idx_dup.is_unique is False
+ assert idx_dup.has_duplicates is True
+
+ mi = MultiIndex(
+ levels=[[0, 1], [0, 1, 2]], codes=[[0, 0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 0, 1, 2]]
+ )
+ assert mi.is_unique is False
+ assert mi.has_duplicates is True
+
+ # single instance of NaN
+ mi_nan = MultiIndex(
+ levels=[["a", "b"], [0, 1]], codes=[[-1, 0, 0, 1, 1], [-1, 0, 1, 0, 1]]
+ )
+ assert mi_nan.is_unique is True
+ assert mi_nan.has_duplicates is False
+
+ # multiple instances of NaN
+ mi_nan_dup = MultiIndex(
+ levels=[["a", "b"], [0, 1]], codes=[[-1, -1, 0, 0, 1, 1], [-1, -1, 0, 1, 0, 1]]
+ )
+ assert mi_nan_dup.is_unique is False
+ assert mi_nan_dup.has_duplicates is True
+
+
+def test_has_duplicates_from_tuples():
+ # GH 9075
+ t = [
+ ("x", "out", "z", 5, "y", "in", "z", 169),
+ ("x", "out", "z", 7, "y", "in", "z", 119),
+ ("x", "out", "z", 9, "y", "in", "z", 135),
+ ("x", "out", "z", 13, "y", "in", "z", 145),
+ ("x", "out", "z", 14, "y", "in", "z", 158),
+ ("x", "out", "z", 16, "y", "in", "z", 122),
+ ("x", "out", "z", 17, "y", "in", "z", 160),
+ ("x", "out", "z", 18, "y", "in", "z", 180),
+ ("x", "out", "z", 20, "y", "in", "z", 143),
+ ("x", "out", "z", 21, "y", "in", "z", 128),
+ ("x", "out", "z", 22, "y", "in", "z", 129),
+ ("x", "out", "z", 25, "y", "in", "z", 111),
+ ("x", "out", "z", 28, "y", "in", "z", 114),
+ ("x", "out", "z", 29, "y", "in", "z", 121),
+ ("x", "out", "z", 31, "y", "in", "z", 126),
+ ("x", "out", "z", 32, "y", "in", "z", 155),
+ ("x", "out", "z", 33, "y", "in", "z", 123),
+ ("x", "out", "z", 12, "y", "in", "z", 144),
+ ]
+
+ mi = MultiIndex.from_tuples(t)
+ assert not mi.has_duplicates
+
+
+@pytest.mark.parametrize("nlevels", [4, 8])
+@pytest.mark.parametrize("with_nulls", [True, False])
+def test_has_duplicates_overflow(nlevels, with_nulls):
+ # handle int64 overflow if possible
+ # no overflow with 4
+ # overflow possible with 8
+ codes = np.tile(np.arange(500), 2)
+ level = np.arange(500)
+
+ if with_nulls: # inject some null values
+ codes[500] = -1 # common nan value
+ codes = [codes.copy() for i in range(nlevels)]
+ for i in range(nlevels):
+ codes[i][500 + i - nlevels // 2] = -1
+
+ codes += [np.array([-1, 1]).repeat(500)]
+ else:
+ codes = [codes] * nlevels + [np.arange(2).repeat(500)]
+
+ levels = [level] * nlevels + [[0, 1]]
+
+ # no dups
+ mi = MultiIndex(levels=levels, codes=codes)
+ assert not mi.has_duplicates
+
+ # with a dup
+ if with_nulls:
+
+ def f(a):
+ return np.insert(a, 1000, a[0])
+
+ codes = list(map(f, codes))
+ mi = MultiIndex(levels=levels, codes=codes)
+ else:
+ values = mi.values.tolist()
+ mi = MultiIndex.from_tuples([*values, values[0]])
+
+ assert mi.has_duplicates
+
+
+@pytest.mark.parametrize(
+ "keep, expected",
+ [
+ ("first", [False, False, False, True, True, False]),
+ ("last", [False, True, True, False, False, False]),
+ (False, [False, True, True, True, True, False]),
+ ],
+)
+def test_duplicated(idx_dup, keep, expected):
+ result = idx_dup.duplicated(keep=keep)
+ expected = np.array(expected)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.arm_slow
+def test_duplicated_hashtable_impl(keep, monkeypatch):
+ # GH 9125
+ n, k = 6, 10
+ levels = [np.arange(n), [str(i) for i in range(n)], 1000 + np.arange(n)]
+ codes = [np.random.default_rng(2).choice(n, k * n) for _ in levels]
+ with monkeypatch.context() as m:
+ m.setattr(libindex, "_SIZE_CUTOFF", 50)
+ mi = MultiIndex(levels=levels, codes=codes)
+
+ result = mi.duplicated(keep=keep)
+ expected = hashtable.duplicated(mi.values, keep=keep)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("val", [101, 102])
+def test_duplicated_with_nan(val):
+ # GH5873
+ mi = MultiIndex.from_arrays([[101, val], [3.5, np.nan]])
+ assert not mi.has_duplicates
+
+ tm.assert_numpy_array_equal(mi.duplicated(), np.zeros(2, dtype="bool"))
+
+
+@pytest.mark.parametrize("n", range(1, 6))
+@pytest.mark.parametrize("m", range(1, 5))
+def test_duplicated_with_nan_multi_shape(n, m):
+ # GH5873
+ # all possible unique combinations, including nan
+ codes = product(range(-1, n), range(-1, m))
+ mi = MultiIndex(
+ levels=[list("abcde")[:n], list("WXYZ")[:m]],
+ codes=np.random.default_rng(2).permutation(list(codes)).T,
+ )
+ assert len(mi) == (n + 1) * (m + 1)
+ assert not mi.has_duplicates
+
+ tm.assert_numpy_array_equal(mi.duplicated(), np.zeros(len(mi), dtype="bool"))
+
+
+def test_duplicated_drop_duplicates():
+ # GH#4060
+ idx = MultiIndex.from_arrays(([1, 2, 3, 1, 2, 3], [1, 1, 1, 1, 2, 2]))
+
+ expected = np.array([False, False, False, True, False, False], dtype=bool)
+ duplicated = idx.duplicated()
+ tm.assert_numpy_array_equal(duplicated, expected)
+ assert duplicated.dtype == bool
+ expected = MultiIndex.from_arrays(([1, 2, 3, 2, 3], [1, 1, 1, 2, 2]))
+ tm.assert_index_equal(idx.drop_duplicates(), expected)
+
+ expected = np.array([True, False, False, False, False, False])
+ duplicated = idx.duplicated(keep="last")
+ tm.assert_numpy_array_equal(duplicated, expected)
+ assert duplicated.dtype == bool
+ expected = MultiIndex.from_arrays(([2, 3, 1, 2, 3], [1, 1, 1, 2, 2]))
+ tm.assert_index_equal(idx.drop_duplicates(keep="last"), expected)
+
+ expected = np.array([True, False, False, True, False, False])
+ duplicated = idx.duplicated(keep=False)
+ tm.assert_numpy_array_equal(duplicated, expected)
+ assert duplicated.dtype == bool
+ expected = MultiIndex.from_arrays(([2, 3, 2, 3], [1, 1, 2, 2]))
+ tm.assert_index_equal(idx.drop_duplicates(keep=False), expected)
+
+
+def test_duplicated_series_complex_numbers(complex_dtype):
+ # GH 17927
+ expected = Series(
+ [False, False, False, True, False, False, False, True, False, True],
+ dtype=bool,
+ )
+ result = Series(
+ [
+ np.nan + np.nan * 1j,
+ 0,
+ 1j,
+ 1j,
+ 1,
+ 1 + 1j,
+ 1 + 2j,
+ 1 + 1j,
+ np.nan,
+ np.nan + np.nan * 1j,
+ ],
+ dtype=complex_dtype,
+ ).duplicated()
+ tm.assert_series_equal(result, expected)
+
+
+def test_midx_unique_ea_dtype():
+ # GH#48335
+ vals_a = Series([1, 2, NA, NA], dtype="Int64")
+ vals_b = np.array([1, 2, 3, 3])
+ midx = MultiIndex.from_arrays([vals_a, vals_b], names=["a", "b"])
+ result = midx.unique()
+
+ exp_vals_a = Series([1, 2, NA], dtype="Int64")
+ exp_vals_b = np.array([1, 2, 3])
+ expected = MultiIndex.from_arrays([exp_vals_a, exp_vals_b], names=["a", "b"])
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_equivalence.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_equivalence.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d6c638fd8e11a78aa6f7ebc189af9e8a8d4cf33
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_equivalence.py
@@ -0,0 +1,284 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import is_any_real_numeric_dtype
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+def test_equals(idx):
+ assert idx.equals(idx)
+ assert idx.equals(idx.copy())
+ assert idx.equals(idx.astype(object))
+ assert idx.equals(idx.to_flat_index())
+ assert idx.equals(idx.to_flat_index().astype("category"))
+
+ assert not idx.equals(list(idx))
+ assert not idx.equals(np.array(idx))
+
+ same_values = Index(idx, dtype=object)
+ assert idx.equals(same_values)
+ assert same_values.equals(idx)
+
+ if idx.nlevels == 1:
+ # do not test MultiIndex
+ assert not idx.equals(Series(idx))
+
+
+def test_equals_op(idx):
+ # GH9947, GH10637
+ index_a = idx
+
+ n = len(index_a)
+ index_b = index_a[0:-1]
+ index_c = index_a[0:-1].append(index_a[-2:-1])
+ index_d = index_a[0:1]
+ with pytest.raises(ValueError, match="Lengths must match"):
+ index_a == index_b
+ expected1 = np.array([True] * n)
+ expected2 = np.array([True] * (n - 1) + [False])
+ tm.assert_numpy_array_equal(index_a == index_a, expected1)
+ tm.assert_numpy_array_equal(index_a == index_c, expected2)
+
+ # test comparisons with numpy arrays
+ array_a = np.array(index_a)
+ array_b = np.array(index_a[0:-1])
+ array_c = np.array(index_a[0:-1].append(index_a[-2:-1]))
+ array_d = np.array(index_a[0:1])
+ with pytest.raises(ValueError, match="Lengths must match"):
+ index_a == array_b
+ tm.assert_numpy_array_equal(index_a == array_a, expected1)
+ tm.assert_numpy_array_equal(index_a == array_c, expected2)
+
+ # test comparisons with Series
+ series_a = Series(array_a)
+ series_b = Series(array_b)
+ series_c = Series(array_c)
+ series_d = Series(array_d)
+ with pytest.raises(ValueError, match="Lengths must match"):
+ index_a == series_b
+
+ tm.assert_series_equal(index_a == series_a, Series(expected1))
+ tm.assert_series_equal(index_a == series_c, Series(expected2))
+
+ # cases where length is 1 for one of them
+ with pytest.raises(ValueError, match="Lengths must match"):
+ index_a == index_d
+ with pytest.raises(ValueError, match="Lengths must match"):
+ index_a == series_d
+ with pytest.raises(ValueError, match="Lengths must match"):
+ index_a == array_d
+ msg = "Can only compare identically-labeled Series objects"
+ with pytest.raises(ValueError, match=msg):
+ series_a == series_d
+ with pytest.raises(ValueError, match="Lengths must match"):
+ series_a == array_d
+
+ # comparing with a scalar should broadcast; note that we are excluding
+ # MultiIndex because in this case each item in the index is a tuple of
+ # length 2, and therefore is considered an array of length 2 in the
+ # comparison instead of a scalar
+ if not isinstance(index_a, MultiIndex):
+ expected3 = np.array([False] * (len(index_a) - 2) + [True, False])
+ # assuming the 2nd to last item is unique in the data
+ item = index_a[-2]
+ tm.assert_numpy_array_equal(index_a == item, expected3)
+ tm.assert_series_equal(series_a == item, Series(expected3))
+
+
+def test_compare_tuple():
+ # GH#21517
+ mi = MultiIndex.from_product([[1, 2]] * 2)
+
+ all_false = np.array([False, False, False, False])
+
+ result = mi == mi[0]
+ expected = np.array([True, False, False, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = mi != mi[0]
+ tm.assert_numpy_array_equal(result, ~expected)
+
+ result = mi < mi[0]
+ tm.assert_numpy_array_equal(result, all_false)
+
+ result = mi <= mi[0]
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = mi > mi[0]
+ tm.assert_numpy_array_equal(result, ~expected)
+
+ result = mi >= mi[0]
+ tm.assert_numpy_array_equal(result, ~all_false)
+
+
+def test_compare_tuple_strs():
+ # GH#34180
+
+ mi = MultiIndex.from_tuples([("a", "b"), ("b", "c"), ("c", "a")])
+
+ result = mi == ("c", "a")
+ expected = np.array([False, False, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = mi == ("c",)
+ expected = np.array([False, False, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_equals_multi(idx):
+ assert idx.equals(idx)
+ assert not idx.equals(idx.values)
+ assert idx.equals(Index(idx.values))
+
+ assert idx.equal_levels(idx)
+ assert not idx.equals(idx[:-1])
+ assert not idx.equals(idx[-1])
+
+ # different number of levels
+ index = MultiIndex(
+ levels=[Index(list(range(4))), Index(list(range(4))), Index(list(range(4)))],
+ codes=[
+ np.array([0, 0, 1, 2, 2, 2, 3, 3]),
+ np.array([0, 1, 0, 0, 0, 1, 0, 1]),
+ np.array([1, 0, 1, 1, 0, 0, 1, 0]),
+ ],
+ )
+
+ index2 = MultiIndex(levels=index.levels[:-1], codes=index.codes[:-1])
+ assert not index.equals(index2)
+ assert not index.equal_levels(index2)
+
+ # levels are different
+ major_axis = Index(list(range(4)))
+ minor_axis = Index(list(range(2)))
+
+ major_codes = np.array([0, 0, 1, 2, 2, 3])
+ minor_codes = np.array([0, 1, 0, 0, 1, 0])
+
+ index = MultiIndex(
+ levels=[major_axis, minor_axis], codes=[major_codes, minor_codes]
+ )
+ assert not idx.equals(index)
+ assert not idx.equal_levels(index)
+
+ # some of the labels are different
+ major_axis = Index(["foo", "bar", "baz", "qux"])
+ minor_axis = Index(["one", "two"])
+
+ major_codes = np.array([0, 0, 2, 2, 3, 3])
+ minor_codes = np.array([0, 1, 0, 1, 0, 1])
+
+ index = MultiIndex(
+ levels=[major_axis, minor_axis], codes=[major_codes, minor_codes]
+ )
+ assert not idx.equals(index)
+
+
+def test_identical(idx):
+ mi = idx.copy()
+ mi2 = idx.copy()
+ assert mi.identical(mi2)
+
+ mi = mi.set_names(["new1", "new2"])
+ assert mi.equals(mi2)
+ assert not mi.identical(mi2)
+
+ mi2 = mi2.set_names(["new1", "new2"])
+ assert mi.identical(mi2)
+
+ mi4 = Index(mi.tolist(), tupleize_cols=False)
+ assert not mi.identical(mi4)
+ assert mi.equals(mi4)
+
+
+def test_equals_operator(idx):
+ # GH9785
+ assert (idx == idx).all()
+
+
+def test_equals_missing_values():
+ # make sure take is not using -1
+ i = MultiIndex.from_tuples([(0, pd.NaT), (0, pd.Timestamp("20130101"))])
+ result = i[0:1].equals(i[0])
+ assert not result
+ result = i[1:2].equals(i[1])
+ assert not result
+
+
+def test_equals_missing_values_differently_sorted():
+ # GH#38439
+ mi1 = MultiIndex.from_tuples([(81.0, np.nan), (np.nan, np.nan)])
+ mi2 = MultiIndex.from_tuples([(np.nan, np.nan), (81.0, np.nan)])
+ assert not mi1.equals(mi2)
+
+ mi2 = MultiIndex.from_tuples([(81.0, np.nan), (np.nan, np.nan)])
+ assert mi1.equals(mi2)
+
+
+def test_is_():
+ mi = MultiIndex.from_tuples(zip(range(10), range(10), strict=True))
+ assert mi.is_(mi)
+ assert mi.is_(mi.view())
+ assert mi.is_(mi.view().view().view().view())
+ mi2 = mi.view()
+ # names are metadata, they don't change id
+ mi2.names = ["A", "B"]
+ assert mi2.is_(mi)
+ assert mi.is_(mi2)
+
+ assert not mi.is_(mi.set_names(["C", "D"]))
+ # levels are inherent properties, they change identity
+ mi3 = mi2.set_levels([list(range(10)), list(range(10))])
+ assert not mi3.is_(mi2)
+ # shouldn't change
+ assert mi2.is_(mi)
+ mi4 = mi3.view()
+
+ # GH 17464 - Remove duplicate MultiIndex levels
+ mi4 = mi4.set_levels([list(range(10)), list(range(10))])
+ assert not mi4.is_(mi3)
+ mi5 = mi.view()
+ mi5 = mi5.set_levels(mi5.levels)
+ assert not mi5.is_(mi)
+
+
+def test_is_all_dates(idx):
+ assert not idx._is_all_dates
+
+
+def test_is_numeric(idx):
+ # MultiIndex is never numeric
+ assert not is_any_real_numeric_dtype(idx)
+
+
+def test_multiindex_compare():
+ # GH 21149
+ # Ensure comparison operations for MultiIndex with nlevels == 1
+ # behave consistently with those for MultiIndex with nlevels > 1
+
+ midx = MultiIndex.from_product([[0, 1]])
+
+ # Equality self-test: MultiIndex object vs self
+ expected = Series([True, True])
+ result = Series(midx == midx)
+ tm.assert_series_equal(result, expected)
+
+ # Greater than comparison: MultiIndex object vs self
+ expected = Series([False, False])
+ result = Series(midx > midx)
+ tm.assert_series_equal(result, expected)
+
+
+def test_equals_ea_int_regular_int():
+ # GH#46026
+ mi1 = MultiIndex.from_arrays([Index([1, 2], dtype="Int64"), [3, 4]])
+ mi2 = MultiIndex.from_arrays([[1, 2], [3, 4]])
+ assert not mi1.equals(mi2)
+ assert not mi2.equals(mi1)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_formats.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_formats.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ea42349bd04a28be83e76eaf4110a4eb16e70dd
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_formats.py
@@ -0,0 +1,207 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+)
+
+
+def test_repr_with_unicode_data():
+ with pd.option_context("display.encoding", "UTF-8"):
+ d = {"a": ["\u05d0", 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]}
+ index = pd.DataFrame(d).set_index(["a", "b"]).index
+ assert "\\" not in repr(index) # we don't want unicode-escaped
+
+
+def test_repr_roundtrip_raises():
+ mi = MultiIndex.from_product([list("ab"), range(3)], names=["first", "second"])
+ msg = "Must pass both levels and codes"
+ with pytest.raises(TypeError, match=msg):
+ eval(repr(mi))
+
+
+def test_unicode_string_with_unicode():
+ d = {"a": ["\u05d0", 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]}
+ idx = pd.DataFrame(d).set_index(["a", "b"]).index
+ str(idx)
+
+
+def test_repr_max_seq_item_setting(idx):
+ # GH10182
+ idx = idx.repeat(50)
+ with pd.option_context("display.max_seq_items", None):
+ repr(idx)
+ assert "..." not in str(idx)
+
+
+class TestRepr:
+ def test_unicode_repr_issues(self):
+ levels = [Index(["a/\u03c3", "b/\u03c3", "c/\u03c3"]), Index([0, 1])]
+ codes = [np.arange(3).repeat(2), np.tile(np.arange(2), 3)]
+ index = MultiIndex(levels=levels, codes=codes)
+
+ repr(index.levels)
+ repr(index.get_level_values(1))
+
+ def test_repr_max_seq_items_equal_to_n(self, idx):
+ # display.max_seq_items == n
+ with pd.option_context("display.max_seq_items", 6):
+ result = idx.__repr__()
+ expected = """\
+MultiIndex([('foo', 'one'),
+ ('foo', 'two'),
+ ('bar', 'one'),
+ ('baz', 'two'),
+ ('qux', 'one'),
+ ('qux', 'two')],
+ names=['first', 'second'])"""
+ assert result == expected
+
+ def test_repr(self, idx):
+ result = idx[:1].__repr__()
+ expected = """\
+MultiIndex([('foo', 'one')],
+ names=['first', 'second'])"""
+ assert result == expected
+
+ result = idx.__repr__()
+ expected = """\
+MultiIndex([('foo', 'one'),
+ ('foo', 'two'),
+ ('bar', 'one'),
+ ('baz', 'two'),
+ ('qux', 'one'),
+ ('qux', 'two')],
+ names=['first', 'second'])"""
+ assert result == expected
+
+ with pd.option_context("display.max_seq_items", 5):
+ result = idx.__repr__()
+ expected = """\
+MultiIndex([('foo', 'one'),
+ ('foo', 'two'),
+ ...
+ ('qux', 'one'),
+ ('qux', 'two')],
+ names=['first', 'second'], length=6)"""
+ assert result == expected
+
+ # display.max_seq_items == 1
+ with pd.option_context("display.max_seq_items", 1):
+ result = idx.__repr__()
+ expected = """\
+MultiIndex([...
+ ('qux', 'two')],
+ names=['first', ...], length=6)"""
+ assert result == expected
+
+ def test_rjust(self):
+ n = 1000
+ ci = pd.CategoricalIndex(list("a" * n) + (["abc"] * n))
+ dti = pd.date_range("2000-01-01", freq="s", periods=n * 2)
+ mi = MultiIndex.from_arrays([ci, ci.codes + 9, dti], names=["a", "b", "dti"])
+ result = mi[:1].__repr__()
+ expected = """\
+MultiIndex([('a', 9, '2000-01-01 00:00:00')],
+ names=['a', 'b', 'dti'])"""
+ assert result == expected
+
+ result = mi[::500].__repr__()
+ expected = """\
+MultiIndex([( 'a', 9, '2000-01-01 00:00:00'),
+ ( 'a', 9, '2000-01-01 00:08:20'),
+ ('abc', 10, '2000-01-01 00:16:40'),
+ ('abc', 10, '2000-01-01 00:25:00')],
+ names=['a', 'b', 'dti'])"""
+ assert result == expected
+
+ result = mi.__repr__()
+ expected = """\
+MultiIndex([( 'a', 9, '2000-01-01 00:00:00'),
+ ( 'a', 9, '2000-01-01 00:00:01'),
+ ( 'a', 9, '2000-01-01 00:00:02'),
+ ( 'a', 9, '2000-01-01 00:00:03'),
+ ( 'a', 9, '2000-01-01 00:00:04'),
+ ( 'a', 9, '2000-01-01 00:00:05'),
+ ( 'a', 9, '2000-01-01 00:00:06'),
+ ( 'a', 9, '2000-01-01 00:00:07'),
+ ( 'a', 9, '2000-01-01 00:00:08'),
+ ( 'a', 9, '2000-01-01 00:00:09'),
+ ...
+ ('abc', 10, '2000-01-01 00:33:10'),
+ ('abc', 10, '2000-01-01 00:33:11'),
+ ('abc', 10, '2000-01-01 00:33:12'),
+ ('abc', 10, '2000-01-01 00:33:13'),
+ ('abc', 10, '2000-01-01 00:33:14'),
+ ('abc', 10, '2000-01-01 00:33:15'),
+ ('abc', 10, '2000-01-01 00:33:16'),
+ ('abc', 10, '2000-01-01 00:33:17'),
+ ('abc', 10, '2000-01-01 00:33:18'),
+ ('abc', 10, '2000-01-01 00:33:19')],
+ names=['a', 'b', 'dti'], length=2000)"""
+ assert result == expected
+
+ def test_tuple_width(self):
+ n = 1000
+ ci = pd.CategoricalIndex(list("a" * n) + (["abc"] * n))
+ dti = pd.date_range("2000-01-01", freq="s", periods=n * 2)
+ levels = [ci, ci.codes + 9, dti, dti, dti]
+ names = ["a", "b", "dti_1", "dti_2", "dti_3"]
+ mi = MultiIndex.from_arrays(levels, names=names)
+ result = mi[:1].__repr__()
+ expected = """MultiIndex([('a', 9, '2000-01-01 00:00:00', '2000-01-01 00:00:00', ...)],
+ names=['a', 'b', 'dti_1', 'dti_2', 'dti_3'])""" # noqa: E501
+ assert result == expected
+
+ result = mi[:10].__repr__()
+ expected = """\
+MultiIndex([('a', 9, '2000-01-01 00:00:00', '2000-01-01 00:00:00', ...),
+ ('a', 9, '2000-01-01 00:00:01', '2000-01-01 00:00:01', ...),
+ ('a', 9, '2000-01-01 00:00:02', '2000-01-01 00:00:02', ...),
+ ('a', 9, '2000-01-01 00:00:03', '2000-01-01 00:00:03', ...),
+ ('a', 9, '2000-01-01 00:00:04', '2000-01-01 00:00:04', ...),
+ ('a', 9, '2000-01-01 00:00:05', '2000-01-01 00:00:05', ...),
+ ('a', 9, '2000-01-01 00:00:06', '2000-01-01 00:00:06', ...),
+ ('a', 9, '2000-01-01 00:00:07', '2000-01-01 00:00:07', ...),
+ ('a', 9, '2000-01-01 00:00:08', '2000-01-01 00:00:08', ...),
+ ('a', 9, '2000-01-01 00:00:09', '2000-01-01 00:00:09', ...)],
+ names=['a', 'b', 'dti_1', 'dti_2', 'dti_3'])"""
+ assert result == expected
+
+ result = mi.__repr__()
+ expected = """\
+MultiIndex([( 'a', 9, '2000-01-01 00:00:00', '2000-01-01 00:00:00', ...),
+ ( 'a', 9, '2000-01-01 00:00:01', '2000-01-01 00:00:01', ...),
+ ( 'a', 9, '2000-01-01 00:00:02', '2000-01-01 00:00:02', ...),
+ ( 'a', 9, '2000-01-01 00:00:03', '2000-01-01 00:00:03', ...),
+ ( 'a', 9, '2000-01-01 00:00:04', '2000-01-01 00:00:04', ...),
+ ( 'a', 9, '2000-01-01 00:00:05', '2000-01-01 00:00:05', ...),
+ ( 'a', 9, '2000-01-01 00:00:06', '2000-01-01 00:00:06', ...),
+ ( 'a', 9, '2000-01-01 00:00:07', '2000-01-01 00:00:07', ...),
+ ( 'a', 9, '2000-01-01 00:00:08', '2000-01-01 00:00:08', ...),
+ ( 'a', 9, '2000-01-01 00:00:09', '2000-01-01 00:00:09', ...),
+ ...
+ ('abc', 10, '2000-01-01 00:33:10', '2000-01-01 00:33:10', ...),
+ ('abc', 10, '2000-01-01 00:33:11', '2000-01-01 00:33:11', ...),
+ ('abc', 10, '2000-01-01 00:33:12', '2000-01-01 00:33:12', ...),
+ ('abc', 10, '2000-01-01 00:33:13', '2000-01-01 00:33:13', ...),
+ ('abc', 10, '2000-01-01 00:33:14', '2000-01-01 00:33:14', ...),
+ ('abc', 10, '2000-01-01 00:33:15', '2000-01-01 00:33:15', ...),
+ ('abc', 10, '2000-01-01 00:33:16', '2000-01-01 00:33:16', ...),
+ ('abc', 10, '2000-01-01 00:33:17', '2000-01-01 00:33:17', ...),
+ ('abc', 10, '2000-01-01 00:33:18', '2000-01-01 00:33:18', ...),
+ ('abc', 10, '2000-01-01 00:33:19', '2000-01-01 00:33:19', ...)],
+ names=['a', 'b', 'dti_1', 'dti_2', 'dti_3'], length=2000)"""
+ assert result == expected
+
+ def test_multiindex_long_element(self):
+ # Non-regression test towards GH#52960
+ data = MultiIndex.from_tuples([("c" * 62,)])
+
+ expected = (
+ "MultiIndex([('cccccccccccccccccccccccccccccccccccccccc"
+ "cccccccccccccccccccccc',)],\n )"
+ )
+ assert str(data) == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_get_level_values.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_get_level_values.py
new file mode 100644
index 0000000000000000000000000000000000000000..4db74a716c514f972cf85714e62143d668149a72
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_get_level_values.py
@@ -0,0 +1,133 @@
+import numpy as np
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ Index,
+ MultiIndex,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestGetLevelValues:
+ def test_get_level_values_box_datetime64(self):
+ dates = date_range("1/1/2000", periods=4)
+ levels = [dates, [0, 1]]
+ codes = [[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]]
+
+ index = MultiIndex(levels=levels, codes=codes)
+
+ assert isinstance(index.get_level_values(0)[0], Timestamp)
+
+
+def test_get_level_values(idx):
+ result = idx.get_level_values(0)
+ expected = Index(["foo", "foo", "bar", "baz", "qux", "qux"], name="first")
+ tm.assert_index_equal(result, expected)
+ assert result.name == "first"
+
+ result = idx.get_level_values("first")
+ expected = idx.get_level_values(0)
+ tm.assert_index_equal(result, expected)
+
+ # GH 10460
+ index = MultiIndex(
+ levels=[CategoricalIndex(["A", "B"]), CategoricalIndex([1, 2, 3])],
+ codes=[np.array([0, 0, 0, 1, 1, 1]), np.array([0, 1, 2, 0, 1, 2])],
+ )
+
+ exp = CategoricalIndex(["A", "A", "A", "B", "B", "B"])
+ tm.assert_index_equal(index.get_level_values(0), exp)
+ exp = CategoricalIndex([1, 2, 3, 1, 2, 3])
+ tm.assert_index_equal(index.get_level_values(1), exp)
+
+
+def test_get_level_values_all_na():
+ # GH#17924 when level entirely consists of nan
+ arrays = [[np.nan, np.nan, np.nan], ["a", np.nan, 1]]
+ index = MultiIndex.from_arrays(arrays)
+ result = index.get_level_values(0)
+ expected = Index([np.nan, np.nan, np.nan], dtype=np.float64)
+ tm.assert_index_equal(result, expected)
+
+ result = index.get_level_values(1)
+ expected = Index(["a", np.nan, 1], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+
+def test_get_level_values_int_with_na():
+ # GH#17924
+ arrays = [["a", "b", "b"], [1, np.nan, 2]]
+ index = MultiIndex.from_arrays(arrays)
+ result = index.get_level_values(1)
+ expected = Index([1, np.nan, 2])
+ tm.assert_index_equal(result, expected)
+
+ arrays = [["a", "b", "b"], [np.nan, np.nan, 2]]
+ index = MultiIndex.from_arrays(arrays)
+ result = index.get_level_values(1)
+ expected = Index([np.nan, np.nan, 2])
+ tm.assert_index_equal(result, expected)
+
+
+def test_get_level_values_na():
+ arrays = [[np.nan, np.nan, np.nan], ["a", np.nan, 1]]
+ index = MultiIndex.from_arrays(arrays)
+ result = index.get_level_values(0)
+ expected = Index([np.nan, np.nan, np.nan])
+ tm.assert_index_equal(result, expected)
+
+ result = index.get_level_values(1)
+ expected = Index(["a", np.nan, 1])
+ tm.assert_index_equal(result, expected)
+
+ arrays = [["a", "b", "b"], pd.DatetimeIndex([0, 1, pd.NaT])]
+ index = MultiIndex.from_arrays(arrays)
+ result = index.get_level_values(1)
+ expected = pd.DatetimeIndex([0, 1, pd.NaT])
+ tm.assert_index_equal(result, expected)
+
+ arrays = [[], []]
+ index = MultiIndex.from_arrays(arrays)
+ result = index.get_level_values(0)
+ expected = Index([], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+
+def test_get_level_values_when_periods():
+ # GH33131. See also discussion in GH32669.
+ # This test can probably be removed when PeriodIndex._engine is removed.
+ from pandas import (
+ Period,
+ PeriodIndex,
+ )
+
+ idx = MultiIndex.from_arrays(
+ [PeriodIndex([Period("2019Q1"), Period("2019Q2")], name="b")]
+ )
+ idx2 = MultiIndex.from_arrays(
+ [idx._get_level_values(level) for level in range(idx.nlevels)]
+ )
+ assert all(x.is_monotonic_increasing for x in idx2.levels)
+
+
+def test_values_loses_freq_of_underlying_index():
+ # GH#49054
+ idx = pd.DatetimeIndex(date_range("20200101", periods=3, freq="BME"))
+ expected = idx.copy(deep=True)
+ idx2 = Index([1, 2, 3])
+ midx = MultiIndex(levels=[idx, idx2], codes=[[0, 1, 2], [0, 1, 2]])
+ midx.values
+ assert idx.freq is not None
+ tm.assert_index_equal(idx, expected)
+
+
+def test_get_level_values_gets_frequency_correctly():
+ # GH#57949 GH#58327
+ datetime_index = date_range(start=pd.to_datetime("1/1/2018"), periods=4, freq="YS")
+ other_index = ["A"]
+ multi_index = MultiIndex.from_product([datetime_index, other_index])
+
+ assert multi_index.get_level_values(0).freq == datetime_index.freq
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_get_set.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_get_set.py
new file mode 100644
index 0000000000000000000000000000000000000000..b794a49b5f85f11e387f6e2ed16d0fb0e56b0be5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_get_set.py
@@ -0,0 +1,380 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.dtypes import DatetimeTZDtype
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+
+def assert_matching(actual, expected, check_dtype=False):
+ # avoid specifying internal representation
+ # as much as possible
+ assert len(actual) == len(expected)
+ for act, exp in zip(actual, expected, strict=True):
+ act = np.asarray(act)
+ exp = np.asarray(exp)
+ tm.assert_numpy_array_equal(act, exp, check_dtype=check_dtype)
+
+
+def test_get_level_number_integer(idx):
+ idx.names = [1, 0]
+ assert idx._get_level_number(1) == 0
+ assert idx._get_level_number(0) == 1
+ msg = "Too many levels: Index has only 2 levels, not 3"
+ with pytest.raises(IndexError, match=msg):
+ idx._get_level_number(2)
+ with pytest.raises(KeyError, match="Level fourth not found"):
+ idx._get_level_number("fourth")
+
+
+def test_get_dtypes(using_infer_string):
+ # Test MultiIndex.dtypes (# Gh37062)
+ idx_multitype = MultiIndex.from_product(
+ [
+ [1, 2, 3],
+ ["a", "b", "c"],
+ pd.date_range("20200101", periods=2, tz="UTC", unit="ns"),
+ ],
+ names=["int", "string", "dt"],
+ )
+
+ exp = "object" if not using_infer_string else pd.StringDtype(na_value=np.nan)
+ expected = pd.Series(
+ {
+ "int": np.dtype("int64"),
+ "string": exp,
+ "dt": DatetimeTZDtype(tz="utc"),
+ }
+ )
+ tm.assert_series_equal(expected, idx_multitype.dtypes)
+
+
+def test_get_dtypes_no_level_name(using_infer_string):
+ # Test MultiIndex.dtypes (# GH38580 )
+ idx_multitype = MultiIndex.from_product(
+ [
+ [1, 2, 3],
+ ["a", "b", "c"],
+ pd.date_range("20200101", periods=2, tz="UTC", unit="ns"),
+ ],
+ )
+ exp = "object" if not using_infer_string else pd.StringDtype(na_value=np.nan)
+ expected = pd.Series(
+ {
+ "level_0": np.dtype("int64"),
+ "level_1": exp,
+ "level_2": DatetimeTZDtype(tz="utc"),
+ }
+ )
+ tm.assert_series_equal(expected, idx_multitype.dtypes)
+
+
+def test_get_dtypes_duplicate_level_names(using_infer_string):
+ # Test MultiIndex.dtypes with non-unique level names (# GH45174)
+ result = MultiIndex.from_product(
+ [
+ [1, 2, 3],
+ ["a", "b", "c"],
+ pd.date_range("20200101", periods=2, tz="UTC", unit="ns"),
+ ],
+ names=["A", "A", "A"],
+ ).dtypes
+ exp = "object" if not using_infer_string else pd.StringDtype(na_value=np.nan)
+ expected = pd.Series(
+ [np.dtype("int64"), exp, DatetimeTZDtype(tz="utc")],
+ index=["A", "A", "A"],
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_get_level_number_out_of_bounds(multiindex_dataframe_random_data):
+ frame = multiindex_dataframe_random_data
+
+ with pytest.raises(IndexError, match="Too many levels"):
+ frame.index._get_level_number(2)
+ with pytest.raises(IndexError, match="not a valid level number"):
+ frame.index._get_level_number(-3)
+
+
+def test_set_name_methods(idx):
+ # so long as these are synonyms, we don't need to test set_names
+ index_names = ["first", "second"]
+ assert idx.rename == idx.set_names
+ new_names = [name + "SUFFIX" for name in index_names]
+ ind = idx.set_names(new_names)
+ assert idx.names == index_names
+ assert ind.names == new_names
+ msg = "Length of names must match number of levels in MultiIndex"
+ with pytest.raises(ValueError, match=msg):
+ ind.set_names(new_names + new_names)
+ new_names2 = [name + "SUFFIX2" for name in new_names]
+ res = ind.set_names(new_names2, inplace=True)
+ assert res is None
+ assert ind.names == new_names2
+
+ # set names for specific level (# GH7792)
+ ind = idx.set_names(new_names[0], level=0)
+ assert idx.names == index_names
+ assert ind.names == [new_names[0], index_names[1]]
+
+ res = ind.set_names(new_names2[0], level=0, inplace=True)
+ assert res is None
+ assert ind.names == [new_names2[0], index_names[1]]
+
+ # set names for multiple levels
+ ind = idx.set_names(new_names, level=[0, 1])
+ assert idx.names == index_names
+ assert ind.names == new_names
+
+ res = ind.set_names(new_names2, level=[0, 1], inplace=True)
+ assert res is None
+ assert ind.names == new_names2
+
+
+def test_set_levels_codes_directly(idx):
+ # setting levels/codes directly raises AttributeError
+
+ levels = idx.levels
+ new_levels = [[lev + "a" for lev in level] for level in levels]
+
+ codes = idx.codes
+ major_codes, minor_codes = codes
+ major_codes = [(x + 1) % 3 for x in major_codes]
+ minor_codes = [(x + 1) % 1 for x in minor_codes]
+ new_codes = [major_codes, minor_codes]
+
+ msg = "Can't set attribute"
+ with pytest.raises(AttributeError, match=msg):
+ idx.levels = new_levels
+
+ msg = "property 'codes' of 'MultiIndex' object has no setter"
+ with pytest.raises(AttributeError, match=msg):
+ idx.codes = new_codes
+
+
+def test_set_levels(idx):
+ # side note - you probably wouldn't want to use levels and codes
+ # directly like this - but it is possible.
+ levels = idx.levels
+ new_levels = [[lev + "a" for lev in level] for level in levels]
+
+ # level changing [w/o mutation]
+ ind2 = idx.set_levels(new_levels)
+ assert_matching(ind2.levels, new_levels)
+ assert_matching(idx.levels, levels)
+
+ # level changing specific level [w/o mutation]
+ ind2 = idx.set_levels(new_levels[0], level=0)
+ assert_matching(ind2.levels, [new_levels[0], levels[1]])
+ assert_matching(idx.levels, levels)
+
+ ind2 = idx.set_levels(new_levels[1], level=1)
+ assert_matching(ind2.levels, [levels[0], new_levels[1]])
+ assert_matching(idx.levels, levels)
+
+ # level changing multiple levels [w/o mutation]
+ ind2 = idx.set_levels(new_levels, level=[0, 1])
+ assert_matching(ind2.levels, new_levels)
+ assert_matching(idx.levels, levels)
+
+ # illegal level changing should not change levels
+ # GH 13754
+ original_index = idx.copy()
+ with pytest.raises(ValueError, match="^On"):
+ idx.set_levels(["c"], level=0)
+ assert_matching(idx.levels, original_index.levels, check_dtype=True)
+
+ with pytest.raises(ValueError, match="^On"):
+ idx.set_codes([0, 1, 2, 3, 4, 5], level=0)
+ assert_matching(idx.codes, original_index.codes, check_dtype=True)
+
+ with pytest.raises(TypeError, match="^Levels"):
+ idx.set_levels("c", level=0)
+ assert_matching(idx.levels, original_index.levels, check_dtype=True)
+
+ with pytest.raises(TypeError, match="^Codes"):
+ idx.set_codes(1, level=0)
+ assert_matching(idx.codes, original_index.codes, check_dtype=True)
+
+
+def test_set_codes(idx):
+ # side note - you probably wouldn't want to use levels and codes
+ # directly like this - but it is possible.
+ codes = idx.codes
+ major_codes, minor_codes = codes
+ major_codes = [(x + 1) % 3 for x in major_codes]
+ minor_codes = [(x + 1) % 1 for x in minor_codes]
+ new_codes = [major_codes, minor_codes]
+
+ # changing codes w/o mutation
+ ind2 = idx.set_codes(new_codes)
+ assert_matching(ind2.codes, new_codes)
+ assert_matching(idx.codes, codes)
+
+ # codes changing specific level w/o mutation
+ ind2 = idx.set_codes(new_codes[0], level=0)
+ assert_matching(ind2.codes, [new_codes[0], codes[1]])
+ assert_matching(idx.codes, codes)
+
+ ind2 = idx.set_codes(new_codes[1], level=1)
+ assert_matching(ind2.codes, [codes[0], new_codes[1]])
+ assert_matching(idx.codes, codes)
+
+ # codes changing multiple levels w/o mutation
+ ind2 = idx.set_codes(new_codes, level=[0, 1])
+ assert_matching(ind2.codes, new_codes)
+ assert_matching(idx.codes, codes)
+
+ # label changing for levels of different magnitude of categories
+ ind = MultiIndex.from_tuples([(0, i) for i in range(130)])
+ new_codes = range(129, -1, -1)
+ expected = MultiIndex.from_tuples([(0, i) for i in new_codes])
+
+ # [w/o mutation]
+ result = ind.set_codes(codes=new_codes, level=1)
+ assert result.equals(expected)
+
+
+def test_set_levels_codes_names_bad_input(idx):
+ levels, codes = idx.levels, idx.codes
+ names = idx.names
+
+ with pytest.raises(ValueError, match="Length of levels"):
+ idx.set_levels([levels[0]])
+
+ with pytest.raises(ValueError, match="Length of codes"):
+ idx.set_codes([codes[0]])
+
+ with pytest.raises(ValueError, match="Length of names"):
+ idx.set_names([names[0]])
+
+ # shouldn't scalar data error, instead should demand list-like
+ with pytest.raises(TypeError, match="list of lists-like"):
+ idx.set_levels(levels[0])
+
+ # shouldn't scalar data error, instead should demand list-like
+ with pytest.raises(TypeError, match="list of lists-like"):
+ idx.set_codes(codes[0])
+
+ # shouldn't scalar data error, instead should demand list-like
+ with pytest.raises(TypeError, match="list-like"):
+ idx.set_names(names[0])
+
+ # should have equal lengths
+ with pytest.raises(TypeError, match="list of lists-like"):
+ idx.set_levels(levels[0], level=[0, 1])
+
+ with pytest.raises(TypeError, match="list-like"):
+ idx.set_levels(levels, level=0)
+
+ # should have equal lengths
+ with pytest.raises(TypeError, match="list of lists-like"):
+ idx.set_codes(codes[0], level=[0, 1])
+
+ with pytest.raises(TypeError, match="list-like"):
+ idx.set_codes(codes, level=0)
+
+ # should have equal lengths
+ with pytest.raises(ValueError, match="Length of names"):
+ idx.set_names(names[0], level=[0, 1])
+
+ with pytest.raises(TypeError, match="Names must be a"):
+ idx.set_names(names, level=0)
+
+
+@pytest.mark.parametrize("inplace", [True, False])
+def test_set_names_with_nlevel_1(inplace):
+ # GH 21149
+ # Ensure that .set_names for MultiIndex with
+ # nlevels == 1 does not raise any errors
+ expected = MultiIndex(levels=[[0, 1]], codes=[[0, 1]], names=["first"])
+ m = MultiIndex.from_product([[0, 1]])
+ result = m.set_names("first", level=0, inplace=inplace)
+
+ if inplace:
+ result = m
+
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("ordered", [True, False])
+def test_set_levels_categorical(ordered):
+ # GH13854
+ index = MultiIndex.from_arrays([list("xyzx"), [0, 1, 2, 3]])
+
+ cidx = CategoricalIndex(list("bac"), ordered=ordered)
+ result = index.set_levels(cidx, level=0)
+ expected = MultiIndex(levels=[cidx, [0, 1, 2, 3]], codes=index.codes)
+ tm.assert_index_equal(result, expected)
+
+ result_lvl = result.get_level_values(0)
+ expected_lvl = CategoricalIndex(
+ list("bacb"), categories=cidx.categories, ordered=cidx.ordered
+ )
+ tm.assert_index_equal(result_lvl, expected_lvl)
+
+
+def test_set_value_keeps_names():
+ # motivating example from #3742
+ lev1 = ["hans", "hans", "hans", "grethe", "grethe", "grethe"]
+ lev2 = ["1", "2", "3"] * 2
+ idx = MultiIndex.from_arrays([lev1, lev2], names=["Name", "Number"])
+ df = pd.DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ columns=["one", "two", "three", "four"],
+ index=idx,
+ )
+ df = df.sort_index()
+ assert df.index.names == ("Name", "Number")
+ df.at[("grethe", "4"), "one"] = 99.34
+ assert df.index.names == ("Name", "Number")
+
+
+def test_set_levels_with_iterable():
+ # GH23273
+ sizes = [1, 2, 3]
+ colors = ["black"] * 3
+ index = MultiIndex.from_arrays([sizes, colors], names=["size", "color"])
+
+ result = index.set_levels(map(int, ["3", "2", "1"]), level="size")
+
+ expected_sizes = [3, 2, 1]
+ expected = MultiIndex.from_arrays([expected_sizes, colors], names=["size", "color"])
+ tm.assert_index_equal(result, expected)
+
+
+def test_set_empty_level():
+ # GH#48636
+ midx = MultiIndex.from_arrays([[]], names=["A"])
+ result = midx.set_levels(pd.DatetimeIndex([]), level=0)
+ expected = MultiIndex.from_arrays([pd.DatetimeIndex([])], names=["A"])
+ tm.assert_index_equal(result, expected)
+
+
+def test_set_levels_pos_args_removal():
+ # https://github.com/pandas-dev/pandas/issues/41485
+ idx = MultiIndex.from_tuples(
+ [
+ (1, "one"),
+ (3, "one"),
+ ],
+ names=["foo", "bar"],
+ )
+ with pytest.raises(TypeError, match="positional arguments"):
+ idx.set_levels(["a", "b", "c"], 0)
+
+ with pytest.raises(TypeError, match="positional arguments"):
+ idx.set_codes([[0, 1], [1, 0]], 0)
+
+
+def test_set_levels_categorical_keep_dtype():
+ # GH#52125
+ midx = MultiIndex.from_arrays([[5, 6]])
+ result = midx.set_levels(levels=pd.Categorical([1, 2]), level=0)
+ expected = MultiIndex.from_arrays([pd.Categorical([1, 2])])
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5760ceb33153dbb5522b6e2b4b0d9fca4ea2ad2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_indexing.py
@@ -0,0 +1,1031 @@
+from collections import namedtuple
+from datetime import timedelta
+import re
+
+import numpy as np
+import pytest
+
+from pandas._libs import index as libindex
+from pandas.errors import InvalidIndexError
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ Index,
+ MultiIndex,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestSliceLocs:
+ def test_slice_locs_partial(self, idx):
+ sorted_idx, _ = idx.sortlevel(0)
+
+ result = sorted_idx.slice_locs(("foo", "two"), ("qux", "one"))
+ assert result == (1, 5)
+
+ result = sorted_idx.slice_locs(None, ("qux", "one"))
+ assert result == (0, 5)
+
+ result = sorted_idx.slice_locs(("foo", "two"), None)
+ assert result == (1, len(sorted_idx))
+
+ result = sorted_idx.slice_locs("bar", "baz")
+ assert result == (2, 4)
+
+ def test_slice_locs(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((50, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=50, freq="B"),
+ )
+ stacked = df.stack()
+ idx = stacked.index
+
+ slob = slice(*idx.slice_locs(df.index[5], df.index[15]))
+ sliced = stacked[slob]
+ expected = df[5:16].stack()
+ tm.assert_almost_equal(sliced.values, expected.values)
+
+ slob = slice(
+ *idx.slice_locs(
+ df.index[5] + timedelta(seconds=30),
+ df.index[15] - timedelta(seconds=30),
+ )
+ )
+ sliced = stacked[slob]
+ expected = df[6:15].stack()
+ tm.assert_almost_equal(sliced.values, expected.values)
+
+ def test_slice_locs_with_type_mismatch(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ stacked = df.stack()
+ idx = stacked.index
+ with pytest.raises(TypeError, match="^Level type mismatch"):
+ idx.slice_locs((1, 3))
+ with pytest.raises(TypeError, match="^Level type mismatch"):
+ idx.slice_locs(df.index[5] + timedelta(seconds=30), (5, 2))
+ df = DataFrame(
+ np.ones((5, 5)),
+ index=Index([f"i-{i}" for i in range(5)], name="a"),
+ columns=Index([f"i-{i}" for i in range(5)], name="a"),
+ )
+ stacked = df.stack()
+ idx = stacked.index
+ with pytest.raises(TypeError, match="^Level type mismatch"):
+ idx.slice_locs(timedelta(seconds=30))
+ # TODO: Try creating a UnicodeDecodeError in exception message
+ with pytest.raises(TypeError, match="^Level type mismatch"):
+ idx.slice_locs(df.index[1], (16, "a"))
+
+ def test_slice_locs_not_sorted(self):
+ index = MultiIndex(
+ levels=[Index(np.arange(4)), Index(np.arange(4)), Index(np.arange(4))],
+ codes=[
+ np.array([0, 0, 1, 2, 2, 2, 3, 3]),
+ np.array([0, 1, 0, 0, 0, 1, 0, 1]),
+ np.array([1, 0, 1, 1, 0, 0, 1, 0]),
+ ],
+ )
+ msg = "[Kk]ey length.*greater than MultiIndex lexsort depth"
+ with pytest.raises(KeyError, match=msg):
+ index.slice_locs((1, 0, 1), (2, 1, 0))
+
+ # works
+ sorted_index, _ = index.sortlevel(0)
+ # should there be a test case here???
+ sorted_index.slice_locs((1, 0, 1), (2, 1, 0))
+
+ def test_slice_locs_not_contained(self):
+ # some searchsorted action
+
+ index = MultiIndex(
+ levels=[[0, 2, 4, 6], [0, 2, 4]],
+ codes=[[0, 0, 0, 1, 1, 2, 3, 3, 3], [0, 1, 2, 1, 2, 2, 0, 1, 2]],
+ )
+
+ result = index.slice_locs((1, 0), (5, 2))
+ assert result == (3, 6)
+
+ result = index.slice_locs(1, 5)
+ assert result == (3, 6)
+
+ result = index.slice_locs((2, 2), (5, 2))
+ assert result == (3, 6)
+
+ result = index.slice_locs(2, 5)
+ assert result == (3, 6)
+
+ result = index.slice_locs((1, 0), (6, 3))
+ assert result == (3, 8)
+
+ result = index.slice_locs(-1, 10)
+ assert result == (0, len(index))
+
+ @pytest.mark.parametrize(
+ "index_arr,expected,start_idx,end_idx",
+ [
+ ([[np.nan, "a", "b"], ["c", "d", "e"]], (0, 3), np.nan, None),
+ ([[np.nan, "a", "b"], ["c", "d", "e"]], (0, 3), np.nan, "b"),
+ ([[np.nan, "a", "b"], ["c", "d", "e"]], (0, 3), np.nan, ("b", "e")),
+ ([["a", "b", "c"], ["d", np.nan, "e"]], (1, 3), ("b", np.nan), None),
+ ([["a", "b", "c"], ["d", np.nan, "e"]], (1, 3), ("b", np.nan), "c"),
+ ([["a", "b", "c"], ["d", np.nan, "e"]], (1, 3), ("b", np.nan), ("c", "e")),
+ ],
+ )
+ def test_slice_locs_with_missing_value(
+ self, index_arr, expected, start_idx, end_idx
+ ):
+ # issue 19132
+ idx = MultiIndex.from_arrays(index_arr)
+ result = idx.slice_locs(start=start_idx, end=end_idx)
+ assert result == expected
+
+
+class TestPutmask:
+ def test_putmask_with_wrong_mask(self, idx):
+ # GH18368
+
+ msg = "putmask: mask and data must be the same size"
+ with pytest.raises(ValueError, match=msg):
+ idx.putmask(np.ones(len(idx) + 1, np.bool_), 1)
+
+ with pytest.raises(ValueError, match=msg):
+ idx.putmask(np.ones(len(idx) - 1, np.bool_), 1)
+
+ with pytest.raises(ValueError, match=msg):
+ idx.putmask("foo", 1)
+
+ def test_putmask_multiindex_other(self):
+ # GH#43212 `value` is also a MultiIndex
+
+ left = MultiIndex.from_tuples([(np.nan, 6), (np.nan, 6), ("a", 4)])
+ right = MultiIndex.from_tuples([("a", 1), ("a", 1), ("d", 1)])
+ mask = np.array([True, True, False])
+
+ result = left.putmask(mask, right)
+
+ expected = MultiIndex.from_tuples([right[0], right[1], left[2]])
+ tm.assert_index_equal(result, expected)
+
+ def test_putmask_keep_dtype(self, any_numeric_ea_dtype):
+ # GH#49830
+ midx = MultiIndex.from_arrays(
+ [pd.Series([1, 2, 3], dtype=any_numeric_ea_dtype), [10, 11, 12]]
+ )
+ midx2 = MultiIndex.from_arrays(
+ [pd.Series([5, 6, 7], dtype=any_numeric_ea_dtype), [-1, -2, -3]]
+ )
+ result = midx.putmask([True, False, False], midx2)
+ expected = MultiIndex.from_arrays(
+ [pd.Series([5, 2, 3], dtype=any_numeric_ea_dtype), [-1, 11, 12]]
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_putmask_keep_dtype_shorter_value(self, any_numeric_ea_dtype):
+ # GH#49830
+ midx = MultiIndex.from_arrays(
+ [pd.Series([1, 2, 3], dtype=any_numeric_ea_dtype), [10, 11, 12]]
+ )
+ midx2 = MultiIndex.from_arrays(
+ [pd.Series([5], dtype=any_numeric_ea_dtype), [-1]]
+ )
+ result = midx.putmask([True, False, False], midx2)
+ expected = MultiIndex.from_arrays(
+ [pd.Series([5, 2, 3], dtype=any_numeric_ea_dtype), [-1, 11, 12]]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+class TestGetIndexer:
+ def test_get_indexer(self):
+ major_axis = Index(np.arange(4))
+ minor_axis = Index(np.arange(2))
+
+ major_codes = np.array([0, 0, 1, 2, 2, 3, 3], dtype=np.intp)
+ minor_codes = np.array([0, 1, 0, 0, 1, 0, 1], dtype=np.intp)
+
+ index = MultiIndex(
+ levels=[major_axis, minor_axis], codes=[major_codes, minor_codes]
+ )
+ idx1 = index[:5]
+ idx2 = index[[1, 3, 5]]
+
+ r1 = idx1.get_indexer(idx2)
+ tm.assert_almost_equal(r1, np.array([1, 3, -1], dtype=np.intp))
+
+ r1 = idx2.get_indexer(idx1, method="pad")
+ e1 = np.array([-1, 0, 0, 1, 1], dtype=np.intp)
+ tm.assert_almost_equal(r1, e1)
+
+ r2 = idx2.get_indexer(idx1[::-1], method="pad")
+ tm.assert_almost_equal(r2, e1[::-1])
+
+ rffill1 = idx2.get_indexer(idx1, method="ffill")
+ tm.assert_almost_equal(r1, rffill1)
+
+ r1 = idx2.get_indexer(idx1, method="backfill")
+ e1 = np.array([0, 0, 1, 1, 2], dtype=np.intp)
+ tm.assert_almost_equal(r1, e1)
+
+ r2 = idx2.get_indexer(idx1[::-1], method="backfill")
+ tm.assert_almost_equal(r2, e1[::-1])
+
+ rbfill1 = idx2.get_indexer(idx1, method="bfill")
+ tm.assert_almost_equal(r1, rbfill1)
+
+ # pass non-MultiIndex
+ r1 = idx1.get_indexer(idx2.values)
+ rexp1 = idx1.get_indexer(idx2)
+ tm.assert_almost_equal(r1, rexp1)
+
+ r1 = idx1.get_indexer([1, 2, 3])
+ assert (r1 == [-1, -1, -1]).all()
+
+ # create index with duplicates
+ idx1 = Index(list(range(10)) + list(range(10)))
+ idx2 = Index(list(range(20)))
+
+ msg = "Reindexing only valid with uniquely valued Index objects"
+ with pytest.raises(InvalidIndexError, match=msg):
+ idx1.get_indexer(idx2)
+
+ def test_get_indexer_nearest(self):
+ midx = MultiIndex.from_tuples([("a", 1), ("b", 2)])
+ msg = (
+ "method='nearest' not implemented yet for MultiIndex; see GitHub issue 9365"
+ )
+ with pytest.raises(NotImplementedError, match=msg):
+ midx.get_indexer(["a"], method="nearest")
+ msg = "tolerance not implemented yet for MultiIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ midx.get_indexer(["a"], method="pad", tolerance=2)
+
+ def test_get_indexer_categorical_time(self):
+ # https://github.com/pandas-dev/pandas/issues/21390
+ midx = MultiIndex.from_product(
+ [
+ Categorical(["a", "b", "c"]),
+ Categorical(date_range("2012-01-01", periods=3, freq="h")),
+ ]
+ )
+ result = midx.get_indexer(midx)
+ tm.assert_numpy_array_equal(result, np.arange(9, dtype=np.intp))
+
+ @pytest.mark.parametrize(
+ "index_arr,labels,expected",
+ [
+ (
+ [[1, np.nan, 2], [3, 4, 5]],
+ [1, np.nan, 2],
+ np.array([-1, -1, -1], dtype=np.intp),
+ ),
+ ([[1, np.nan, 2], [3, 4, 5]], [(np.nan, 4)], np.array([1], dtype=np.intp)),
+ ([[1, 2, 3], [np.nan, 4, 5]], [(1, np.nan)], np.array([0], dtype=np.intp)),
+ (
+ [[1, 2, 3], [np.nan, 4, 5]],
+ [np.nan, 4, 5],
+ np.array([-1, -1, -1], dtype=np.intp),
+ ),
+ ],
+ )
+ def test_get_indexer_with_missing_value(self, index_arr, labels, expected):
+ # issue 19132
+ idx = MultiIndex.from_arrays(index_arr)
+ result = idx.get_indexer(labels)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer_methods(self):
+ # https://github.com/pandas-dev/pandas/issues/29896
+ # test getting an indexer for another index with different methods
+ # confirms that getting an indexer without a filling method, getting an
+ # indexer and backfilling, and getting an indexer and padding all behave
+ # correctly in the case where all of the target values fall in between
+ # several levels in the MultiIndex into which they are getting an indexer
+ #
+ # visually, the MultiIndexes used in this test are:
+ # mult_idx_1:
+ # 0: -1 0
+ # 1: 2
+ # 2: 3
+ # 3: 4
+ # 4: 0 0
+ # 5: 2
+ # 6: 3
+ # 7: 4
+ # 8: 1 0
+ # 9: 2
+ # 10: 3
+ # 11: 4
+ #
+ # mult_idx_2:
+ # 0: 0 1
+ # 1: 3
+ # 2: 4
+ mult_idx_1 = MultiIndex.from_product([[-1, 0, 1], [0, 2, 3, 4]])
+ mult_idx_2 = MultiIndex.from_product([[0], [1, 3, 4]])
+
+ indexer = mult_idx_1.get_indexer(mult_idx_2)
+ expected = np.array([-1, 6, 7], dtype=indexer.dtype)
+ tm.assert_almost_equal(expected, indexer)
+
+ backfill_indexer = mult_idx_1.get_indexer(mult_idx_2, method="backfill")
+ expected = np.array([5, 6, 7], dtype=backfill_indexer.dtype)
+ tm.assert_almost_equal(expected, backfill_indexer)
+
+ # ensure the legacy "bfill" option functions identically to "backfill"
+ backfill_indexer = mult_idx_1.get_indexer(mult_idx_2, method="bfill")
+ expected = np.array([5, 6, 7], dtype=backfill_indexer.dtype)
+ tm.assert_almost_equal(expected, backfill_indexer)
+
+ pad_indexer = mult_idx_1.get_indexer(mult_idx_2, method="pad")
+ expected = np.array([4, 6, 7], dtype=pad_indexer.dtype)
+ tm.assert_almost_equal(expected, pad_indexer)
+
+ # ensure the legacy "ffill" option functions identically to "pad"
+ pad_indexer = mult_idx_1.get_indexer(mult_idx_2, method="ffill")
+ expected = np.array([4, 6, 7], dtype=pad_indexer.dtype)
+ tm.assert_almost_equal(expected, pad_indexer)
+
+ @pytest.mark.parametrize("method", ["pad", "ffill", "backfill", "bfill", "nearest"])
+ def test_get_indexer_methods_raise_for_non_monotonic(self, method):
+ # 53452
+ mi = MultiIndex.from_arrays([[0, 4, 2], [0, 4, 2]])
+ if method == "nearest":
+ err = NotImplementedError
+ msg = "not implemented yet for MultiIndex"
+ else:
+ err = ValueError
+ msg = "index must be monotonic increasing or decreasing"
+ with pytest.raises(err, match=msg):
+ mi.get_indexer([(1, 1)], method=method)
+
+ def test_get_indexer_three_or_more_levels(self):
+ # https://github.com/pandas-dev/pandas/issues/29896
+ # tests get_indexer() on MultiIndexes with 3+ levels
+ # visually, these are
+ # mult_idx_1:
+ # 0: 1 2 5
+ # 1: 7
+ # 2: 4 5
+ # 3: 7
+ # 4: 6 5
+ # 5: 7
+ # 6: 3 2 5
+ # 7: 7
+ # 8: 4 5
+ # 9: 7
+ # 10: 6 5
+ # 11: 7
+ #
+ # mult_idx_2:
+ # 0: 1 1 8
+ # 1: 1 5 9
+ # 2: 1 6 7
+ # 3: 2 1 6
+ # 4: 2 7 6
+ # 5: 2 7 8
+ # 6: 3 6 8
+ mult_idx_1 = MultiIndex.from_product([[1, 3], [2, 4, 6], [5, 7]])
+ mult_idx_2 = MultiIndex.from_tuples(
+ [
+ (1, 1, 8),
+ (1, 5, 9),
+ (1, 6, 7),
+ (2, 1, 6),
+ (2, 7, 7),
+ (2, 7, 8),
+ (3, 6, 8),
+ ]
+ )
+ # sanity check
+ assert mult_idx_1.is_monotonic_increasing
+ assert mult_idx_1.is_unique
+ assert mult_idx_2.is_monotonic_increasing
+ assert mult_idx_2.is_unique
+
+ # show the relationships between the two
+ assert mult_idx_2[0] < mult_idx_1[0]
+ assert mult_idx_1[3] < mult_idx_2[1] < mult_idx_1[4]
+ assert mult_idx_1[5] == mult_idx_2[2]
+ assert mult_idx_1[5] < mult_idx_2[3] < mult_idx_1[6]
+ assert mult_idx_1[5] < mult_idx_2[4] < mult_idx_1[6]
+ assert mult_idx_1[5] < mult_idx_2[5] < mult_idx_1[6]
+ assert mult_idx_1[-1] < mult_idx_2[6]
+
+ indexer_no_fill = mult_idx_1.get_indexer(mult_idx_2)
+ expected = np.array([-1, -1, 5, -1, -1, -1, -1], dtype=indexer_no_fill.dtype)
+ tm.assert_almost_equal(expected, indexer_no_fill)
+
+ # test with backfilling
+ indexer_backfilled = mult_idx_1.get_indexer(mult_idx_2, method="backfill")
+ expected = np.array([0, 4, 5, 6, 6, 6, -1], dtype=indexer_backfilled.dtype)
+ tm.assert_almost_equal(expected, indexer_backfilled)
+
+ # now, the same thing, but forward-filled (aka "padded")
+ indexer_padded = mult_idx_1.get_indexer(mult_idx_2, method="pad")
+ expected = np.array([-1, 3, 5, 5, 5, 5, 11], dtype=indexer_padded.dtype)
+ tm.assert_almost_equal(expected, indexer_padded)
+
+ # now, do the indexing in the other direction
+ assert mult_idx_2[0] < mult_idx_1[0] < mult_idx_2[1]
+ assert mult_idx_2[0] < mult_idx_1[1] < mult_idx_2[1]
+ assert mult_idx_2[0] < mult_idx_1[2] < mult_idx_2[1]
+ assert mult_idx_2[0] < mult_idx_1[3] < mult_idx_2[1]
+ assert mult_idx_2[1] < mult_idx_1[4] < mult_idx_2[2]
+ assert mult_idx_2[2] == mult_idx_1[5]
+ assert mult_idx_2[5] < mult_idx_1[6] < mult_idx_2[6]
+ assert mult_idx_2[5] < mult_idx_1[7] < mult_idx_2[6]
+ assert mult_idx_2[5] < mult_idx_1[8] < mult_idx_2[6]
+ assert mult_idx_2[5] < mult_idx_1[9] < mult_idx_2[6]
+ assert mult_idx_2[5] < mult_idx_1[10] < mult_idx_2[6]
+ assert mult_idx_2[5] < mult_idx_1[11] < mult_idx_2[6]
+
+ indexer = mult_idx_2.get_indexer(mult_idx_1)
+ expected = np.array(
+ [-1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1], dtype=indexer.dtype
+ )
+ tm.assert_almost_equal(expected, indexer)
+
+ backfill_indexer = mult_idx_2.get_indexer(mult_idx_1, method="bfill")
+ expected = np.array(
+ [1, 1, 1, 1, 2, 2, 6, 6, 6, 6, 6, 6], dtype=backfill_indexer.dtype
+ )
+ tm.assert_almost_equal(expected, backfill_indexer)
+
+ pad_indexer = mult_idx_2.get_indexer(mult_idx_1, method="pad")
+ expected = np.array(
+ [0, 0, 0, 0, 1, 2, 5, 5, 5, 5, 5, 5], dtype=pad_indexer.dtype
+ )
+ tm.assert_almost_equal(expected, pad_indexer)
+
+ def test_get_indexer_crossing_levels(self):
+ # https://github.com/pandas-dev/pandas/issues/29896
+ # tests a corner case with get_indexer() with MultiIndexes where, when we
+ # need to "carry" across levels, proper tuple ordering is respected
+ #
+ # the MultiIndexes used in this test, visually, are:
+ # mult_idx_1:
+ # 0: 1 1 1 1
+ # 1: 2
+ # 2: 2 1
+ # 3: 2
+ # 4: 1 2 1 1
+ # 5: 2
+ # 6: 2 1
+ # 7: 2
+ # 8: 2 1 1 1
+ # 9: 2
+ # 10: 2 1
+ # 11: 2
+ # 12: 2 2 1 1
+ # 13: 2
+ # 14: 2 1
+ # 15: 2
+ #
+ # mult_idx_2:
+ # 0: 1 3 2 2
+ # 1: 2 3 2 2
+ mult_idx_1 = MultiIndex.from_product([[1, 2]] * 4)
+ mult_idx_2 = MultiIndex.from_tuples([(1, 3, 2, 2), (2, 3, 2, 2)])
+
+ # show the tuple orderings, which get_indexer() should respect
+ assert mult_idx_1[7] < mult_idx_2[0] < mult_idx_1[8]
+ assert mult_idx_1[-1] < mult_idx_2[1]
+
+ indexer = mult_idx_1.get_indexer(mult_idx_2)
+ expected = np.array([-1, -1], dtype=indexer.dtype)
+ tm.assert_almost_equal(expected, indexer)
+
+ backfill_indexer = mult_idx_1.get_indexer(mult_idx_2, method="bfill")
+ expected = np.array([8, -1], dtype=backfill_indexer.dtype)
+ tm.assert_almost_equal(expected, backfill_indexer)
+
+ pad_indexer = mult_idx_1.get_indexer(mult_idx_2, method="ffill")
+ expected = np.array([7, 15], dtype=pad_indexer.dtype)
+ tm.assert_almost_equal(expected, pad_indexer)
+
+ def test_get_indexer_kwarg_validation(self):
+ # GH#41918
+ mi = MultiIndex.from_product([range(3), ["A", "B"]])
+
+ msg = "limit argument only valid if doing pad, backfill or nearest"
+ with pytest.raises(ValueError, match=msg):
+ mi.get_indexer(mi[:-1], limit=4)
+
+ msg = "tolerance argument only valid if doing pad, backfill or nearest"
+ with pytest.raises(ValueError, match=msg):
+ mi.get_indexer(mi[:-1], tolerance="piano")
+
+ def test_get_indexer_nan(self):
+ # GH#37222
+ idx1 = MultiIndex.from_product([["A"], [1.0, 2.0]], names=["id1", "id2"])
+ idx2 = MultiIndex.from_product([["A"], [np.nan, 2.0]], names=["id1", "id2"])
+ expected = np.array([-1, 1])
+ result = idx2.get_indexer(idx1)
+ tm.assert_numpy_array_equal(result, expected, check_dtype=False)
+ result = idx1.get_indexer(idx2)
+ tm.assert_numpy_array_equal(result, expected, check_dtype=False)
+
+
+def test_getitem(idx):
+ # scalar
+ assert idx[2] == ("bar", "one")
+
+ # slice
+ result = idx[2:5]
+ expected = idx[[2, 3, 4]]
+ assert result.equals(expected)
+
+ # boolean
+ result = idx[[True, False, True, False, True, True]]
+ result2 = idx[np.array([True, False, True, False, True, True])]
+ expected = idx[[0, 2, 4, 5]]
+ assert result.equals(expected)
+ assert result2.equals(expected)
+
+
+def test_getitem_group_select(idx):
+ sorted_idx, _ = idx.sortlevel(0)
+ assert sorted_idx.get_loc("baz") == slice(3, 4)
+ assert sorted_idx.get_loc("foo") == slice(0, 2)
+
+
+@pytest.mark.parametrize("box", [list, Index])
+def test_getitem_bool_index_all(box):
+ # GH#22533
+ ind1 = box([True] * 5)
+ idx = MultiIndex.from_tuples([(10, 1), (20, 2), (30, 3), (40, 4), (50, 5)])
+ tm.assert_index_equal(idx[ind1], idx)
+
+ ind2 = box([True, False, True, False, False])
+ expected = MultiIndex.from_tuples([(10, 1), (30, 3)])
+ tm.assert_index_equal(idx[ind2], expected)
+
+
+@pytest.mark.parametrize("box", [list, Index])
+def test_getitem_bool_index_single(box):
+ # GH#22533
+ ind1 = box([True])
+ idx = MultiIndex.from_tuples([(10, 1)])
+ tm.assert_index_equal(idx[ind1], idx)
+
+ ind2 = box([False])
+ expected = MultiIndex(
+ levels=[np.array([], dtype=np.int64), np.array([], dtype=np.int64)],
+ codes=[[], []],
+ )
+ tm.assert_index_equal(idx[ind2], expected)
+
+
+class TestGetLoc:
+ def test_get_loc(self, idx):
+ assert idx.get_loc(("foo", "two")) == 1
+ assert idx.get_loc(("baz", "two")) == 3
+ with pytest.raises(KeyError, match=r"^\('bar', 'two'\)$"):
+ idx.get_loc(("bar", "two"))
+ with pytest.raises(KeyError, match=r"^'quux'$"):
+ idx.get_loc("quux")
+
+ # 3 levels
+ index = MultiIndex(
+ levels=[Index(np.arange(4)), Index(np.arange(4)), Index(np.arange(4))],
+ codes=[
+ np.array([0, 0, 1, 2, 2, 2, 3, 3]),
+ np.array([0, 1, 0, 0, 0, 1, 0, 1]),
+ np.array([1, 0, 1, 1, 0, 0, 1, 0]),
+ ],
+ )
+ with pytest.raises(KeyError, match=r"^\(1, 1\)$"):
+ index.get_loc((1, 1))
+ assert index.get_loc((2, 0)) == slice(3, 5)
+
+ def test_get_loc_duplicates(self):
+ index = Index([2, 2, 2, 2])
+ result = index.get_loc(2)
+ expected = slice(0, 4)
+ assert result == expected
+
+ index = Index(["c", "a", "a", "b", "b"])
+ rs = index.get_loc("c")
+ xp = 0
+ assert rs == xp
+
+ with pytest.raises(KeyError, match="2"):
+ index.get_loc(2)
+
+ def test_get_loc_level(self):
+ index = MultiIndex(
+ levels=[Index(np.arange(4)), Index(np.arange(4)), Index(np.arange(4))],
+ codes=[
+ np.array([0, 0, 1, 2, 2, 2, 3, 3]),
+ np.array([0, 1, 0, 0, 0, 1, 0, 1]),
+ np.array([1, 0, 1, 1, 0, 0, 1, 0]),
+ ],
+ )
+ loc, new_index = index.get_loc_level((0, 1))
+ expected = slice(1, 2)
+ exp_index = index[expected].droplevel(0).droplevel(0)
+ assert loc == expected
+ assert new_index.equals(exp_index)
+
+ loc, new_index = index.get_loc_level((0, 1, 0))
+ expected = 1
+ assert loc == expected
+ assert new_index is None
+
+ with pytest.raises(KeyError, match=r"^\(2, 2\)$"):
+ index.get_loc_level((2, 2))
+ # GH 22221: unused label
+ with pytest.raises(KeyError, match=r"^2$"):
+ index.drop(2).get_loc_level(2)
+ # Unused label on unsorted level:
+ with pytest.raises(KeyError, match=r"^2$"):
+ index.drop(1, level=2).get_loc_level(2, level=2)
+
+ index = MultiIndex(
+ levels=[[2000], list(range(4))],
+ codes=[np.array([0, 0, 0, 0]), np.array([0, 1, 2, 3])],
+ )
+ result, new_index = index.get_loc_level((2000, slice(None, None)))
+ expected = slice(None, None)
+ assert result == expected
+ assert new_index.equals(index.droplevel(0))
+
+ @pytest.mark.parametrize("dtype1", [int, float, bool, str])
+ @pytest.mark.parametrize("dtype2", [int, float, bool, str])
+ def test_get_loc_multiple_dtypes(self, dtype1, dtype2):
+ # GH 18520
+ levels = [np.array([0, 1]).astype(dtype1), np.array([0, 1]).astype(dtype2)]
+ idx = MultiIndex.from_product(levels)
+ assert idx.get_loc(idx[2]) == 2
+
+ @pytest.mark.parametrize("level", [0, 1])
+ @pytest.mark.parametrize("dtypes", [[int, float], [float, int]])
+ def test_get_loc_implicit_cast(self, level, dtypes):
+ # GH 18818, GH 15994 : as flat index, cast int to float and vice-versa
+ levels = [["a", "b"], ["c", "d"]]
+ key = ["b", "d"]
+ lev_dtype, key_dtype = dtypes
+ levels[level] = np.array([0, 1], dtype=lev_dtype)
+ key[level] = key_dtype(1)
+ idx = MultiIndex.from_product(levels)
+ assert idx.get_loc(tuple(key)) == 3
+
+ @pytest.mark.parametrize("dtype", [bool, object])
+ def test_get_loc_cast_bool(self, dtype):
+ # GH 19086 : int is casted to bool, but not vice-versa (for object dtype)
+ # With bool dtype, we don't cast in either direction.
+ levels = [Index([False, True], dtype=dtype), np.arange(2, dtype="int64")]
+ idx = MultiIndex.from_product(levels)
+
+ if dtype is bool:
+ with pytest.raises(KeyError, match=r"^\(0, 1\)$"):
+ assert idx.get_loc((0, 1)) == 1
+ with pytest.raises(KeyError, match=r"^\(1, 0\)$"):
+ assert idx.get_loc((1, 0)) == 2
+ else:
+ # We use python object comparisons, which treat 0 == False and 1 == True
+ assert idx.get_loc((0, 1)) == 1
+ assert idx.get_loc((1, 0)) == 2
+
+ with pytest.raises(KeyError, match=r"^\(False, True\)$"):
+ idx.get_loc((False, True))
+ with pytest.raises(KeyError, match=r"^\(True, False\)$"):
+ idx.get_loc((True, False))
+
+ @pytest.mark.parametrize("level", [0, 1])
+ def test_get_loc_nan(self, level, nulls_fixture):
+ # GH 18485 : NaN in MultiIndex
+ levels = [["a", "b"], ["c", "d"]]
+ key = ["b", "d"]
+ levels[level] = np.array([0, nulls_fixture], dtype=type(nulls_fixture))
+ key[level] = nulls_fixture
+ idx = MultiIndex.from_product(levels)
+ assert idx.get_loc(tuple(key)) == 3
+
+ def test_get_loc_missing_nan(self):
+ # GH 8569
+ idx = MultiIndex.from_arrays([[1.0, 2.0], [3.0, 4.0]])
+ assert isinstance(idx.get_loc(1), slice)
+ with pytest.raises(KeyError, match=r"^3$"):
+ idx.get_loc(3)
+ with pytest.raises(KeyError, match=r"^nan$"):
+ idx.get_loc(np.nan)
+ with pytest.raises(InvalidIndexError, match=r"\[nan\]"):
+ # listlike/non-hashable raises TypeError
+ idx.get_loc([np.nan])
+
+ def test_get_loc_with_values_including_missing_values(self):
+ # issue 19132
+ idx = MultiIndex.from_product([[np.nan, 1]] * 2)
+ expected = slice(0, 2, None)
+ assert idx.get_loc(np.nan) == expected
+
+ idx = MultiIndex.from_arrays([[np.nan, 1, 2, np.nan]])
+ expected = np.array([True, False, False, True])
+ tm.assert_numpy_array_equal(idx.get_loc(np.nan), expected)
+
+ idx = MultiIndex.from_product([[np.nan, 1]] * 3)
+ expected = slice(2, 4, None)
+ assert idx.get_loc((np.nan, 1)) == expected
+
+ def test_get_loc_duplicates2(self):
+ # TODO: de-duplicate with test_get_loc_duplicates above?
+ index = MultiIndex(
+ levels=[["D", "B", "C"], [0, 26, 27, 37, 57, 67, 75, 82]],
+ codes=[[0, 0, 0, 1, 2, 2, 2, 2, 2, 2], [1, 3, 4, 6, 0, 2, 2, 3, 5, 7]],
+ names=["tag", "day"],
+ )
+
+ assert index.get_loc("D") == slice(0, 3)
+
+ def test_get_loc_past_lexsort_depth(self, performance_warning):
+ # GH#30053
+ idx = MultiIndex(
+ levels=[["a"], [0, 7], [1]],
+ codes=[[0, 0], [1, 0], [0, 0]],
+ names=["x", "y", "z"],
+ sortorder=0,
+ )
+ key = ("a", 7)
+
+ with tm.assert_produces_warning(performance_warning):
+ # PerformanceWarning: indexing past lexsort depth may impact performance
+ result = idx.get_loc(key)
+
+ assert result == slice(0, 1, None)
+
+ def test_multiindex_get_loc_list_raises(self):
+ # GH#35878
+ idx = MultiIndex.from_tuples([("a", 1), ("b", 2)])
+ msg = r"\[\]"
+ with pytest.raises(InvalidIndexError, match=msg):
+ idx.get_loc([])
+
+ def test_get_loc_nested_tuple_raises_keyerror(self):
+ # raise KeyError, not TypeError
+ mi = MultiIndex.from_product([range(3), range(4), range(5), range(6)])
+ key = ((2, 3, 4), "foo")
+
+ with pytest.raises(KeyError, match=re.escape(str(key))):
+ mi.get_loc(key)
+
+
+class TestWhere:
+ def test_where(self):
+ i = MultiIndex.from_tuples([("A", 1), ("A", 2)])
+
+ msg = r"\.where is not supported for MultiIndex operations"
+ with pytest.raises(NotImplementedError, match=msg):
+ i.where(True)
+
+ def test_where_array_like(self, listlike_box):
+ mi = MultiIndex.from_tuples([("A", 1), ("A", 2)])
+ cond = [False, True]
+ msg = r"\.where is not supported for MultiIndex operations"
+ with pytest.raises(NotImplementedError, match=msg):
+ mi.where(listlike_box(cond))
+
+
+class TestContains:
+ def test_contains_top_level(self):
+ midx = MultiIndex.from_product([["A", "B"], [1, 2]])
+ assert "A" in midx
+ assert "A" not in midx._engine
+
+ def test_contains_with_nat(self):
+ # MI with a NaT
+ mi = MultiIndex(
+ levels=[["C"], date_range("2012-01-01", periods=5)],
+ codes=[[0, 0, 0, 0, 0, 0], [-1, 0, 1, 2, 3, 4]],
+ names=[None, "B"],
+ )
+ assert ("C", pd.Timestamp("2012-01-01")) in mi
+ for val in mi.values:
+ assert val in mi
+
+ def test_contains(self, idx):
+ assert ("foo", "two") in idx
+ assert ("bar", "two") not in idx
+ assert None not in idx
+
+ def test_contains_with_missing_value(self):
+ # GH#19132
+ idx = MultiIndex.from_arrays([[1, np.nan, 2]])
+ assert np.nan in idx
+
+ idx = MultiIndex.from_arrays([[1, 2], [np.nan, 3]])
+ assert np.nan not in idx
+ assert (1, np.nan) in idx
+
+ def test_multiindex_contains_dropped(self):
+ # GH#19027
+ # test that dropped MultiIndex levels are not in the MultiIndex
+ # despite continuing to be in the MultiIndex's levels
+ idx = MultiIndex.from_product([[1, 2], [3, 4]])
+ assert 2 in idx
+ idx = idx.drop(2)
+
+ # drop implementation keeps 2 in the levels
+ assert 2 in idx.levels[0]
+ # but it should no longer be in the index itself
+ assert 2 not in idx
+
+ # also applies to strings
+ idx = MultiIndex.from_product([["a", "b"], ["c", "d"]])
+ assert "a" in idx
+ idx = idx.drop("a")
+ assert "a" in idx.levels[0]
+ assert "a" not in idx
+
+ def test_contains_td64_level(self):
+ # GH#24570
+ tx = pd.timedelta_range("09:30:00", "16:00:00", freq="30 min")
+ idx = MultiIndex.from_arrays([tx, np.arange(len(tx))])
+ assert tx[0] in idx
+ assert "element_not_exit" not in idx
+ assert "0 day 09:30:00" in idx
+
+ def test_large_mi_contains(self, monkeypatch):
+ # GH#10645
+ with monkeypatch.context():
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", 10)
+ result = MultiIndex.from_arrays([range(10), range(10)])
+ assert (10, 0) not in result
+
+
+def test_timestamp_multiindex_indexer():
+ # https://github.com/pandas-dev/pandas/issues/26944
+ idx = MultiIndex.from_product(
+ [
+ date_range("2019-01-01T00:15:33", periods=100, freq="h", name="date"),
+ ["x"],
+ [3],
+ ]
+ )
+ df = DataFrame({"foo": np.arange(len(idx))}, idx)
+ result = df.loc[pd.IndexSlice["2019-1-2":, "x", :], "foo"]
+ qidx = MultiIndex.from_product(
+ [
+ date_range(
+ start="2019-01-02T00:15:33",
+ end="2019-01-05T03:15:33",
+ freq="h",
+ name="date",
+ ),
+ ["x"],
+ [3],
+ ]
+ )
+ should_be = pd.Series(data=np.arange(24, len(qidx) + 24), index=qidx, name="foo")
+ tm.assert_series_equal(result, should_be)
+
+
+@pytest.mark.parametrize(
+ "index_arr,expected,target,algo",
+ [
+ ([[np.nan, "a", "b"], ["c", "d", "e"]], 0, np.nan, "left"),
+ ([[np.nan, "a", "b"], ["c", "d", "e"]], 1, (np.nan, "c"), "right"),
+ ([["a", "b", "c"], ["d", np.nan, "d"]], 1, ("b", np.nan), "left"),
+ ],
+)
+def test_get_slice_bound_with_missing_value(index_arr, expected, target, algo):
+ # issue 19132
+ idx = MultiIndex.from_arrays(index_arr)
+ result = idx.get_slice_bound(target, side=algo)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "index_arr,expected,start_idx,end_idx",
+ [
+ ([[np.nan, 1, 2], [3, 4, 5]], slice(0, 2, None), np.nan, 1),
+ ([[np.nan, 1, 2], [3, 4, 5]], slice(0, 3, None), np.nan, (2, 5)),
+ ([[1, 2, 3], [4, np.nan, 5]], slice(1, 3, None), (2, np.nan), 3),
+ ([[1, 2, 3], [4, np.nan, 5]], slice(1, 3, None), (2, np.nan), (3, 5)),
+ ],
+)
+def test_slice_indexer_with_missing_value(index_arr, expected, start_idx, end_idx):
+ # issue 19132
+ idx = MultiIndex.from_arrays(index_arr)
+ result = idx.slice_indexer(start=start_idx, end=end_idx)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "N, expected_dtype",
+ [
+ (1, "uint8"), # 2*4*N = 8
+ (2, "uint16"), # 2*4*N = 16
+ (4, "uint32"), # 2*4*N = 32
+ (8, "uint64"), # 2*4*N = 64
+ (10, "object"), # 2*4*N = 80
+ ],
+)
+def test_pyint_engine(N, expected_dtype):
+ # GH#18519 : when combinations of codes cannot be represented in 64
+ # bits, the index underlying the MultiIndex engine works with Python
+ # integers, rather than uint64.
+ keys = [
+ tuple(arr)
+ for arr in [
+ [0] * 4 * N,
+ [1] * 4 * N,
+ [np.nan] * N + [0] * 3 * N,
+ [0] * N + [1] * 3 * N,
+ [np.nan] * N + [1] * 2 * N + [0] * N,
+ ]
+ ]
+ # Each level contains 3 elements (NaN, 0, 1), and it's represented
+ # in 2 bits to store 4 possible values (0=notfound, 1=NaN, 2=0, 3=1), for
+ # a total of 2*N*4 = 80 > 64 bits where N=10 and the number of levels is N*4.
+ # If we were using a 64 bit engine and truncating the first levels, the
+ # fourth and fifth keys would collide; if truncating the last levels, the
+ # fifth and sixth; if rotating bits rather than shifting, the third and fifth.
+
+ index = MultiIndex.from_tuples(keys)
+ assert index._engine.values.dtype == expected_dtype
+
+ for idx, key_value in enumerate(keys):
+ assert index.get_loc(key_value) == idx
+
+ expected = np.arange(idx + 1, dtype=np.intp)
+ result = index.get_indexer([keys[i] for i in expected])
+ tm.assert_numpy_array_equal(result, expected)
+
+ # With missing key:
+ idces = range(len(keys))
+ expected = np.array([-1, *list(idces)], dtype=np.intp)
+ missing = tuple([0, 1, 0, 1] * N)
+ result = index.get_indexer([missing] + [keys[i] for i in idces])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "keys,expected",
+ [
+ ((slice(None), [5, 4]), [1, 0]),
+ ((slice(None), [4, 5]), [0, 1]),
+ (([True, False, True], [4, 6]), [0, 2]),
+ (([True, False, True], [6, 4]), [0, 2]),
+ ((2, [4, 5]), [0, 1]),
+ ((2, [5, 4]), [1, 0]),
+ (([2], [4, 5]), [0, 1]),
+ (([2], [5, 4]), [1, 0]),
+ ],
+)
+def test_get_locs_reordering(keys, expected):
+ # GH48384
+ idx = MultiIndex.from_arrays(
+ [
+ [2, 2, 1],
+ [4, 5, 6],
+ ]
+ )
+ result = idx.get_locs(keys)
+ expected = np.array(expected, dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_get_indexer_for_multiindex_with_nans(nulls_fixture):
+ # GH37222
+ idx1 = MultiIndex.from_product([["A"], [1.0, 2.0]], names=["id1", "id2"])
+ idx2 = MultiIndex.from_product([["A"], [nulls_fixture, 2.0]], names=["id1", "id2"])
+
+ result = idx2.get_indexer(idx1)
+ expected = np.array([-1, 1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx1.get_indexer(idx2)
+ expected = np.array([-1, 1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_get_loc_namedtuple_behaves_like_tuple():
+ # GH57922
+ NamedIndex = namedtuple("NamedIndex", ("a", "b"))
+ multi_idx = MultiIndex.from_tuples(
+ [NamedIndex("i1", "i2"), NamedIndex("i3", "i4"), NamedIndex("i5", "i6")]
+ )
+ for idx in (multi_idx, multi_idx.to_flat_index()):
+ assert idx.get_loc(NamedIndex("i1", "i2")) == 0
+ assert idx.get_loc(NamedIndex("i3", "i4")) == 1
+ assert idx.get_loc(NamedIndex("i5", "i6")) == 2
+ assert idx.get_loc(("i1", "i2")) == 0
+ assert idx.get_loc(("i3", "i4")) == 1
+ assert idx.get_loc(("i5", "i6")) == 2
+ multi_idx = MultiIndex.from_tuples([("i1", "i2"), ("i3", "i4"), ("i5", "i6")])
+ for idx in (multi_idx, multi_idx.to_flat_index()):
+ assert idx.get_loc(NamedIndex("i1", "i2")) == 0
+ assert idx.get_loc(NamedIndex("i3", "i4")) == 1
+ assert idx.get_loc(NamedIndex("i5", "i6")) == 2
+ assert idx.get_loc(("i1", "i2")) == 0
+ assert idx.get_loc(("i3", "i4")) == 1
+ assert idx.get_loc(("i5", "i6")) == 2
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_integrity.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_integrity.py
new file mode 100644
index 0000000000000000000000000000000000000000..d570e911bf584dd4552aafdbc8ec37aac091973a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_integrity.py
@@ -0,0 +1,289 @@
+import re
+
+import numpy as np
+import pytest
+
+from pandas._libs import index as libindex
+
+from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
+
+import pandas as pd
+from pandas import (
+ Index,
+ IntervalIndex,
+ MultiIndex,
+ RangeIndex,
+)
+import pandas._testing as tm
+
+
+def test_labels_dtypes():
+ # GH 8456
+ i = MultiIndex.from_tuples([("A", 1), ("A", 2)])
+ assert i.codes[0].dtype == "int8"
+ assert i.codes[1].dtype == "int8"
+
+ i = MultiIndex.from_product([["a"], range(40)])
+ assert i.codes[1].dtype == "int8"
+ i = MultiIndex.from_product([["a"], range(400)])
+ assert i.codes[1].dtype == "int16"
+ i = MultiIndex.from_product([["a"], range(40000)])
+ assert i.codes[1].dtype == "int32"
+
+ i = MultiIndex.from_product([["a"], range(1000)])
+ assert (i.codes[0] >= 0).all()
+ assert (i.codes[1] >= 0).all()
+
+
+def test_values_boxed():
+ tuples = [
+ (1, pd.Timestamp("2000-01-01")),
+ (2, pd.NaT),
+ (3, pd.Timestamp("2000-01-03")),
+ (1, pd.Timestamp("2000-01-04")),
+ (2, pd.Timestamp("2000-01-02")),
+ (3, pd.Timestamp("2000-01-03")),
+ ]
+ result = MultiIndex.from_tuples(tuples)
+ expected = construct_1d_object_array_from_listlike(tuples)
+ tm.assert_numpy_array_equal(result.values, expected)
+ # Check that code branches for boxed values produce identical results
+ tm.assert_numpy_array_equal(result.values[:4], result[:4].values)
+
+
+def test_values_multiindex_datetimeindex():
+ # Test to ensure we hit the boxing / nobox part of MI.values
+ ints = np.arange(10**18, 10**18 + 5)
+ naive = pd.DatetimeIndex(ints)
+
+ aware = pd.DatetimeIndex(ints, tz="US/Central")
+
+ idx = MultiIndex.from_arrays([naive, aware])
+ result = idx.values
+
+ outer = pd.DatetimeIndex([x[0] for x in result])
+ tm.assert_index_equal(outer, naive)
+
+ inner = pd.DatetimeIndex([x[1] for x in result])
+ tm.assert_index_equal(inner, aware)
+
+ # n_lev > n_lab
+ result = idx[:2].values
+
+ outer = pd.DatetimeIndex([x[0] for x in result])
+ tm.assert_index_equal(outer, naive[:2])
+
+ inner = pd.DatetimeIndex([x[1] for x in result])
+ tm.assert_index_equal(inner, aware[:2])
+
+
+def test_values_multiindex_periodindex():
+ # Test to ensure we hit the boxing / nobox part of MI.values
+ ints = np.arange(2007, 2012)
+ pidx = pd.PeriodIndex(ints, freq="D")
+
+ idx = MultiIndex.from_arrays([ints, pidx])
+ result = idx.values
+
+ outer = Index([x[0] for x in result])
+ tm.assert_index_equal(outer, Index(ints, dtype=np.int64))
+
+ inner = pd.PeriodIndex([x[1] for x in result])
+ tm.assert_index_equal(inner, pidx)
+
+ # n_lev > n_lab
+ result = idx[:2].values
+
+ outer = Index([x[0] for x in result])
+ tm.assert_index_equal(outer, Index(ints[:2], dtype=np.int64))
+
+ inner = pd.PeriodIndex([x[1] for x in result])
+ tm.assert_index_equal(inner, pidx[:2])
+
+
+def test_consistency():
+ # need to construct an overflow
+ major_axis = list(range(70000))
+ minor_axis = list(range(10))
+
+ major_codes = np.arange(70000)
+ minor_codes = np.repeat(range(10), 7000)
+
+ # the fact that is works means it's consistent
+ index = MultiIndex(
+ levels=[major_axis, minor_axis], codes=[major_codes, minor_codes]
+ )
+
+ # inconsistent
+ major_codes = np.array([0, 0, 1, 1, 1, 2, 2, 3, 3])
+ minor_codes = np.array([0, 1, 0, 1, 1, 0, 1, 0, 1])
+ index = MultiIndex(
+ levels=[major_axis, minor_axis], codes=[major_codes, minor_codes]
+ )
+
+ assert index.is_unique is False
+
+
+@pytest.mark.slow
+def test_hash_collisions(monkeypatch):
+ # non-smoke test that we don't get hash collisions
+ size_cutoff = 50
+ with monkeypatch.context() as m:
+ m.setattr(libindex, "_SIZE_CUTOFF", size_cutoff)
+ index = MultiIndex.from_product(
+ [np.arange(8), np.arange(8)], names=["one", "two"]
+ )
+ result = index.get_indexer(index.values)
+ tm.assert_numpy_array_equal(result, np.arange(len(index), dtype="intp"))
+
+ for i in [0, 1, len(index) - 2, len(index) - 1]:
+ result = index.get_loc(index[i])
+ assert result == i
+
+
+def test_dims():
+ pass
+
+
+def test_take_invalid_kwargs():
+ vals = [["A", "B"], [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")]]
+ idx = MultiIndex.from_product(vals, names=["str", "dt"])
+ indices = [1, 2]
+
+ msg = r"take\(\) got an unexpected keyword argument 'foo'"
+ with pytest.raises(TypeError, match=msg):
+ idx.take(indices, foo=2)
+
+ msg = "the 'out' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, out=indices)
+
+ msg = "the 'mode' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, mode="clip")
+
+
+def test_isna_behavior(idx):
+ # should not segfault GH5123
+ # NOTE: if MI representation changes, may make sense to allow
+ # isna(MI)
+ msg = "isna is not defined for MultiIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ pd.isna(idx)
+
+
+def test_large_multiindex_error(monkeypatch):
+ # GH12527
+ size_cutoff = 50
+ with monkeypatch.context() as m:
+ m.setattr(libindex, "_SIZE_CUTOFF", size_cutoff)
+ df_below_cutoff = pd.DataFrame(
+ 1,
+ index=MultiIndex.from_product([[1, 2], range(size_cutoff - 1)]),
+ columns=["dest"],
+ )
+ with pytest.raises(KeyError, match=r"^\(-1, 0\)$"):
+ df_below_cutoff.loc[(-1, 0), "dest"]
+ with pytest.raises(KeyError, match=r"^\(3, 0\)$"):
+ df_below_cutoff.loc[(3, 0), "dest"]
+ df_above_cutoff = pd.DataFrame(
+ 1,
+ index=MultiIndex.from_product([[1, 2], range(size_cutoff + 1)]),
+ columns=["dest"],
+ )
+ with pytest.raises(KeyError, match=r"^\(-1, 0\)$"):
+ df_above_cutoff.loc[(-1, 0), "dest"]
+ with pytest.raises(KeyError, match=r"^\(3, 0\)$"):
+ df_above_cutoff.loc[(3, 0), "dest"]
+
+
+def test_mi_hashtable_populated_attribute_error(monkeypatch):
+ # GH 18165
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", 50)
+ r = range(50)
+ df = pd.DataFrame({"a": r, "b": r}, index=MultiIndex.from_arrays([r, r]))
+
+ msg = "'Series' object has no attribute 'foo'"
+ with pytest.raises(AttributeError, match=msg):
+ df["a"].foo()
+
+
+def test_can_hold_identifiers(idx):
+ key = idx[0]
+ assert idx._can_hold_identifiers_and_holds_name(key) is True
+
+
+def test_metadata_immutable(idx):
+ levels, codes = idx.levels, idx.codes
+ # shouldn't be able to set at either the top level or base level
+ mutable_regex = re.compile("does not support mutable operations")
+ with pytest.raises(TypeError, match=mutable_regex):
+ levels[0] = levels[0]
+ with pytest.raises(TypeError, match=mutable_regex):
+ levels[0][0] = levels[0][0]
+ # ditto for labels
+ with pytest.raises(TypeError, match=mutable_regex):
+ codes[0] = codes[0]
+ with pytest.raises(ValueError, match="assignment destination is read-only"):
+ codes[0][0] = codes[0][0]
+ # and for names
+ names = idx.names
+ with pytest.raises(TypeError, match=mutable_regex):
+ names[0] = names[0]
+
+
+def test_level_setting_resets_attributes():
+ ind = MultiIndex.from_arrays([["A", "A", "B", "B", "B"], [1, 2, 1, 2, 3]])
+ assert ind.is_monotonic_increasing
+ ind = ind.set_levels([["A", "B"], [1, 3, 2]])
+ # if this fails, probably didn't reset the cache correctly.
+ assert not ind.is_monotonic_increasing
+
+
+def test_rangeindex_fallback_coercion_bug():
+ # GH 12893
+ df1 = pd.DataFrame(np.arange(100).reshape((10, 10)))
+ df2 = pd.DataFrame(np.arange(100).reshape((10, 10)))
+ df = pd.concat(
+ {"df1": df1.stack(), "df2": df2.stack()},
+ axis=1,
+ )
+ df.index.names = ["fizz", "buzz"]
+
+ expected = pd.DataFrame(
+ {"df2": np.arange(100), "df1": np.arange(100)},
+ index=MultiIndex.from_product([range(10), range(10)], names=["fizz", "buzz"]),
+ )
+ tm.assert_frame_equal(df, expected, check_like=True)
+
+ result = df.index.get_level_values("fizz")
+ expected = Index(np.arange(10, dtype=np.int64), name="fizz").repeat(10)
+ tm.assert_index_equal(result, expected)
+
+ result = df.index.get_level_values("buzz")
+ expected = Index(np.tile(np.arange(10, dtype=np.int64), 10), name="buzz")
+ tm.assert_index_equal(result, expected)
+
+
+def test_memory_usage(idx):
+ result = idx.memory_usage()
+ if len(idx):
+ idx.get_loc(idx[0])
+ result2 = idx.memory_usage()
+ result3 = idx.memory_usage(deep=True)
+
+ # RangeIndex, IntervalIndex
+ # don't have engines
+ if not isinstance(idx, (RangeIndex, IntervalIndex)):
+ assert result2 > result
+
+ if idx.inferred_type == "object":
+ assert result3 > result2
+
+ else:
+ # we report 0 for no-length
+ assert result == 0
+
+
+def test_nlevels(idx):
+ assert idx.nlevels == 2
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_isin.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_isin.py
new file mode 100644
index 0000000000000000000000000000000000000000..92ac2468d599373c1f3fc14d3019d065bc03ec08
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_isin.py
@@ -0,0 +1,104 @@
+import numpy as np
+import pytest
+
+from pandas import MultiIndex
+import pandas._testing as tm
+
+
+def test_isin_nan():
+ idx = MultiIndex.from_arrays([["foo", "bar"], [1.0, np.nan]])
+ tm.assert_numpy_array_equal(idx.isin([("bar", np.nan)]), np.array([False, True]))
+ tm.assert_numpy_array_equal(
+ idx.isin([("bar", float("nan"))]), np.array([False, True])
+ )
+
+
+def test_isin_missing(nulls_fixture):
+ # GH48905
+ mi1 = MultiIndex.from_tuples([(1, nulls_fixture)])
+ mi2 = MultiIndex.from_tuples([(1, 1), (1, 2)])
+ result = mi2.isin(mi1)
+ expected = np.array([False, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_isin():
+ values = [("foo", 2), ("bar", 3), ("quux", 4)]
+
+ idx = MultiIndex.from_arrays([["qux", "baz", "foo", "bar"], np.arange(4)])
+ result = idx.isin(values)
+ expected = np.array([False, False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ # empty, return dtype bool
+ idx = MultiIndex.from_arrays([[], []])
+ result = idx.isin(values)
+ assert len(result) == 0
+ assert result.dtype == np.bool_
+
+
+def test_isin_level_kwarg():
+ idx = MultiIndex.from_arrays([["qux", "baz", "foo", "bar"], np.arange(4)])
+
+ vals_0 = ["foo", "bar", "quux"]
+ vals_1 = [2, 3, 10]
+
+ expected = np.array([False, False, True, True])
+ tm.assert_numpy_array_equal(expected, idx.isin(vals_0, level=0))
+ tm.assert_numpy_array_equal(expected, idx.isin(vals_0, level=-2))
+
+ tm.assert_numpy_array_equal(expected, idx.isin(vals_1, level=1))
+ tm.assert_numpy_array_equal(expected, idx.isin(vals_1, level=-1))
+
+ msg = "Too many levels: Index has only 2 levels, not 6"
+ with pytest.raises(IndexError, match=msg):
+ idx.isin(vals_0, level=5)
+ msg = "Too many levels: Index has only 2 levels, -5 is not a valid level number"
+ with pytest.raises(IndexError, match=msg):
+ idx.isin(vals_0, level=-5)
+
+ with pytest.raises(KeyError, match=r"'Level 1\.0 not found'"):
+ idx.isin(vals_0, level=1.0)
+ with pytest.raises(KeyError, match=r"'Level -1\.0 not found'"):
+ idx.isin(vals_1, level=-1.0)
+ with pytest.raises(KeyError, match="'Level A not found'"):
+ idx.isin(vals_1, level="A")
+
+ idx.names = ["A", "B"]
+ tm.assert_numpy_array_equal(expected, idx.isin(vals_0, level="A"))
+ tm.assert_numpy_array_equal(expected, idx.isin(vals_1, level="B"))
+
+ with pytest.raises(KeyError, match="'Level C not found'"):
+ idx.isin(vals_1, level="C")
+
+
+@pytest.mark.parametrize(
+ "labels,expected,level",
+ [
+ ([("b", np.nan)], [False, False, True], None),
+ ([np.nan, "a"], [True, True, False], 0),
+ (["d", np.nan], [False, True, True], 1),
+ ],
+)
+def test_isin_multi_index_with_missing_value(labels, expected, level):
+ # GH 19132
+ midx = MultiIndex.from_arrays([[np.nan, "a", "b"], ["c", "d", np.nan]])
+ result = midx.isin(labels, level=level)
+ expected = np.array(expected)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_isin_empty():
+ # GH#51599
+ midx = MultiIndex.from_arrays([[1, 2], [3, 4]])
+ result = midx.isin([])
+ expected = np.array([False, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_isin_generator():
+ # GH#52568
+ midx = MultiIndex.from_tuples([(1, 2)])
+ result = midx.isin(x for x in [(1, 2)])
+ expected = np.array([True])
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..2be6bba475af7af0269dcdfece0d42c9e0110ec1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_join.py
@@ -0,0 +1,270 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Interval,
+ MultiIndex,
+ Series,
+ StringDtype,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("other", [["three", "one", "two"], ["one"], ["one", "three"]])
+def test_join_level(idx, other, join_type):
+ other = Index(other)
+ join_index, lidx, ridx = other.join(
+ idx, how=join_type, level="second", return_indexers=True
+ )
+
+ exp_level = other.join(idx.levels[1], how=join_type)
+ assert join_index.levels[0].equals(idx.levels[0])
+ assert join_index.levels[1].equals(exp_level)
+
+ # pare down levels
+ mask = np.array([x[1] in exp_level for x in idx], dtype=bool)
+ exp_values = idx.values[mask]
+ tm.assert_numpy_array_equal(join_index.values, exp_values)
+
+ if join_type in ("outer", "inner"):
+ join_index2, ridx2, lidx2 = idx.join(
+ other, how=join_type, level="second", return_indexers=True
+ )
+
+ assert join_index.equals(join_index2)
+ tm.assert_numpy_array_equal(lidx, lidx2)
+ if ridx is None:
+ assert ridx == ridx2
+ else:
+ tm.assert_numpy_array_equal(ridx, ridx2)
+ tm.assert_numpy_array_equal(join_index2.values, exp_values)
+
+
+def test_join_level_corner_case(idx):
+ # some corner cases
+ index = Index(["three", "one", "two"])
+ result = index.join(idx, level="second")
+ assert isinstance(result, MultiIndex)
+
+ with pytest.raises(TypeError, match="Join.*MultiIndex.*ambiguous"):
+ idx.join(idx, level=1)
+
+
+def test_join_self(idx, join_type):
+ result = idx.join(idx, how=join_type)
+ expected = idx
+ if join_type == "outer":
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+
+
+def test_join_multi():
+ # GH 10665
+ midx = MultiIndex.from_product([np.arange(4), np.arange(4)], names=["a", "b"])
+ idx = Index([1, 2, 5], name="b")
+
+ # inner
+ jidx, lidx, ridx = midx.join(idx, how="inner", return_indexers=True)
+ exp_idx = MultiIndex.from_product([np.arange(4), [1, 2]], names=["a", "b"])
+ exp_lidx = np.array([1, 2, 5, 6, 9, 10, 13, 14], dtype=np.intp)
+ exp_ridx = np.array([0, 1, 0, 1, 0, 1, 0, 1], dtype=np.intp)
+ tm.assert_index_equal(jidx, exp_idx)
+ tm.assert_numpy_array_equal(lidx, exp_lidx)
+ tm.assert_numpy_array_equal(ridx, exp_ridx)
+ # flip
+ jidx, ridx, lidx = idx.join(midx, how="inner", return_indexers=True)
+ tm.assert_index_equal(jidx, exp_idx)
+ tm.assert_numpy_array_equal(lidx, exp_lidx)
+ tm.assert_numpy_array_equal(ridx, exp_ridx)
+
+ # keep MultiIndex
+ jidx, lidx, ridx = midx.join(idx, how="left", return_indexers=True)
+ exp_ridx = np.array(
+ [-1, 0, 1, -1, -1, 0, 1, -1, -1, 0, 1, -1, -1, 0, 1, -1], dtype=np.intp
+ )
+ tm.assert_index_equal(jidx, midx)
+ assert lidx is None
+ tm.assert_numpy_array_equal(ridx, exp_ridx)
+ # flip
+ jidx, ridx, lidx = idx.join(midx, how="right", return_indexers=True)
+ tm.assert_index_equal(jidx, midx)
+ assert lidx is None
+ tm.assert_numpy_array_equal(ridx, exp_ridx)
+
+
+def test_join_multi_wrong_order():
+ # GH 25760
+ # GH 28956
+
+ midx1 = MultiIndex.from_product([[1, 2], [3, 4]], names=["a", "b"])
+ midx2 = MultiIndex.from_product([[1, 2], [3, 4]], names=["b", "a"])
+
+ join_idx, lidx, ridx = midx1.join(midx2, return_indexers=True)
+
+ exp_ridx = np.array([-1, -1, -1, -1], dtype=np.intp)
+
+ tm.assert_index_equal(midx1, join_idx)
+ assert lidx is None
+ tm.assert_numpy_array_equal(ridx, exp_ridx)
+
+
+def test_join_multi_return_indexers():
+ # GH 34074
+
+ midx1 = MultiIndex.from_product([[1, 2], [3, 4], [5, 6]], names=["a", "b", "c"])
+ midx2 = MultiIndex.from_product([[1, 2], [3, 4]], names=["a", "b"])
+
+ result = midx1.join(midx2, return_indexers=False)
+ tm.assert_index_equal(result, midx1)
+
+
+def test_join_overlapping_interval_level():
+ # GH 44096
+ idx_1 = MultiIndex.from_tuples(
+ [
+ (1, Interval(0.0, 1.0)),
+ (1, Interval(1.0, 2.0)),
+ (1, Interval(2.0, 5.0)),
+ (2, Interval(0.0, 1.0)),
+ (2, Interval(1.0, 3.0)), # interval limit is here at 3.0, not at 2.0
+ (2, Interval(3.0, 5.0)),
+ ],
+ names=["num", "interval"],
+ )
+
+ idx_2 = MultiIndex.from_tuples(
+ [
+ (1, Interval(2.0, 5.0)),
+ (1, Interval(0.0, 1.0)),
+ (1, Interval(1.0, 2.0)),
+ (2, Interval(3.0, 5.0)),
+ (2, Interval(0.0, 1.0)),
+ (2, Interval(1.0, 3.0)),
+ ],
+ names=["num", "interval"],
+ )
+
+ expected = MultiIndex.from_tuples(
+ [
+ (1, Interval(0.0, 1.0)),
+ (1, Interval(1.0, 2.0)),
+ (1, Interval(2.0, 5.0)),
+ (2, Interval(0.0, 1.0)),
+ (2, Interval(1.0, 3.0)),
+ (2, Interval(3.0, 5.0)),
+ ],
+ names=["num", "interval"],
+ )
+ result = idx_1.join(idx_2, how="outer")
+
+ tm.assert_index_equal(result, expected)
+
+
+def test_join_midx_ea():
+ # GH#49277
+ midx = MultiIndex.from_arrays(
+ [Series([1, 1, 3], dtype="Int64"), Series([1, 2, 3], dtype="Int64")],
+ names=["a", "b"],
+ )
+ midx2 = MultiIndex.from_arrays(
+ [Series([1], dtype="Int64"), Series([3], dtype="Int64")], names=["a", "c"]
+ )
+ result = midx.join(midx2, how="inner")
+ expected = MultiIndex.from_arrays(
+ [
+ Series([1, 1], dtype="Int64"),
+ Series([1, 2], dtype="Int64"),
+ Series([3, 3], dtype="Int64"),
+ ],
+ names=["a", "b", "c"],
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_join_midx_string():
+ # GH#49277
+ midx = MultiIndex.from_arrays(
+ [
+ Series(["a", "a", "c"], dtype=StringDtype()),
+ Series(["a", "b", "c"], dtype=StringDtype()),
+ ],
+ names=["a", "b"],
+ )
+ midx2 = MultiIndex.from_arrays(
+ [Series(["a"], dtype=StringDtype()), Series(["c"], dtype=StringDtype())],
+ names=["a", "c"],
+ )
+ result = midx.join(midx2, how="inner")
+ expected = MultiIndex.from_arrays(
+ [
+ Series(["a", "a"], dtype=StringDtype()),
+ Series(["a", "b"], dtype=StringDtype()),
+ Series(["c", "c"], dtype=StringDtype()),
+ ],
+ names=["a", "b", "c"],
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_join_multi_with_nan():
+ # GH29252
+ df1 = DataFrame(
+ data={"col1": [1.1, 1.2]},
+ index=MultiIndex.from_product([["A"], [1.0, 2.0]], names=["id1", "id2"]),
+ )
+ df2 = DataFrame(
+ data={"col2": [2.1, 2.2]},
+ index=MultiIndex.from_product([["A"], [np.nan, 2.0]], names=["id1", "id2"]),
+ )
+ result = df1.join(df2)
+ expected = DataFrame(
+ data={"col1": [1.1, 1.2], "col2": [np.nan, 2.2]},
+ index=MultiIndex.from_product([["A"], [1.0, 2.0]], names=["id1", "id2"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("val", [0, 5])
+def test_join_dtypes(any_numeric_ea_dtype, val):
+ # GH#49830
+ midx = MultiIndex.from_arrays([Series([1, 2], dtype=any_numeric_ea_dtype), [3, 4]])
+ midx2 = MultiIndex.from_arrays(
+ [Series([1, val, val], dtype=any_numeric_ea_dtype), [3, 4, 4]]
+ )
+ result = midx.join(midx2, how="outer")
+ expected = MultiIndex.from_arrays(
+ [Series([val, val, 1, 2], dtype=any_numeric_ea_dtype), [4, 4, 3, 4]]
+ ).sort_values()
+ tm.assert_index_equal(result, expected)
+
+
+def test_join_dtypes_all_nan(any_numeric_ea_dtype):
+ # GH#49830
+ midx = MultiIndex.from_arrays(
+ [Series([1, 2], dtype=any_numeric_ea_dtype), [np.nan, np.nan]]
+ )
+ midx2 = MultiIndex.from_arrays(
+ [Series([1, 0, 0], dtype=any_numeric_ea_dtype), [np.nan, np.nan, np.nan]]
+ )
+ result = midx.join(midx2, how="outer")
+ expected = MultiIndex.from_arrays(
+ [
+ Series([0, 0, 1, 2], dtype=any_numeric_ea_dtype),
+ [np.nan, np.nan, np.nan, np.nan],
+ ]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_join_index_levels():
+ # GH#53093
+ midx = MultiIndex.from_tuples([("a", "2019-02-01"), ("a", "2019-02-01")])
+ midx2 = MultiIndex.from_tuples([("a", "2019-01-31")])
+ result = midx.join(midx2, how="outer")
+ expected = MultiIndex.from_tuples(
+ [("a", "2019-01-31"), ("a", "2019-02-01"), ("a", "2019-02-01")]
+ )
+ tm.assert_index_equal(result.levels[1], expected.levels[1])
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_lexsort.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_lexsort.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc16a4197a3a4daf65de6f58d85d13883d535d41
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_lexsort.py
@@ -0,0 +1,46 @@
+from pandas import MultiIndex
+
+
+class TestIsLexsorted:
+ def test_is_lexsorted(self):
+ levels = [[0, 1], [0, 1, 2]]
+
+ index = MultiIndex(
+ levels=levels, codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]]
+ )
+ assert index._is_lexsorted()
+
+ index = MultiIndex(
+ levels=levels, codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 2, 1]]
+ )
+ assert not index._is_lexsorted()
+
+ index = MultiIndex(
+ levels=levels, codes=[[0, 0, 1, 0, 1, 1], [0, 1, 0, 2, 2, 1]]
+ )
+ assert not index._is_lexsorted()
+ assert index._lexsort_depth == 0
+
+
+class TestLexsortDepth:
+ def test_lexsort_depth(self):
+ # Test that lexsort_depth return the correct sortorder
+ # when it was given to the MultiIndex const.
+ # GH#28518
+
+ levels = [[0, 1], [0, 1, 2]]
+
+ index = MultiIndex(
+ levels=levels, codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]], sortorder=2
+ )
+ assert index._lexsort_depth == 2
+
+ index = MultiIndex(
+ levels=levels, codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 2, 1]], sortorder=1
+ )
+ assert index._lexsort_depth == 1
+
+ index = MultiIndex(
+ levels=levels, codes=[[0, 0, 1, 0, 1, 1], [0, 1, 0, 2, 2, 1]], sortorder=0
+ )
+ assert index._lexsort_depth == 0
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_missing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_missing.py
new file mode 100644
index 0000000000000000000000000000000000000000..41cfa093ae53c3215dbb883a5094dd2c2d0495b7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_missing.py
@@ -0,0 +1,111 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import MultiIndex
+import pandas._testing as tm
+
+
+def test_fillna(idx):
+ # GH 11343
+ msg = "fillna is not defined for MultiIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.fillna(idx[0])
+
+
+def test_dropna():
+ # GH 6194
+ idx = MultiIndex.from_arrays(
+ [
+ [1, np.nan, 3, np.nan, 5],
+ [1, 2, np.nan, np.nan, 5],
+ ["a", "b", "c", np.nan, "e"],
+ ]
+ )
+
+ exp = MultiIndex.from_arrays([[1, 5], [1, 5], ["a", "e"]])
+ tm.assert_index_equal(idx.dropna(), exp)
+ tm.assert_index_equal(idx.dropna(how="any"), exp)
+
+ exp = MultiIndex.from_arrays(
+ [[1, np.nan, 3, 5], [1, 2, np.nan, 5], ["a", "b", "c", "e"]]
+ )
+ tm.assert_index_equal(idx.dropna(how="all"), exp)
+
+ msg = "invalid how option: xxx"
+ with pytest.raises(ValueError, match=msg):
+ idx.dropna(how="xxx")
+
+ # GH26408
+ # test if missing values are dropped for multiindex constructed
+ # from codes and values
+ idx = MultiIndex(
+ levels=[[np.nan, None, pd.NaT, "128", 2], [np.nan, None, pd.NaT, "128", 2]],
+ codes=[[0, -1, 1, 2, 3, 4], [0, -1, 3, 3, 3, 4]],
+ )
+ expected = MultiIndex.from_arrays([["128", 2], ["128", 2]])
+ tm.assert_index_equal(idx.dropna(), expected)
+ tm.assert_index_equal(idx.dropna(how="any"), expected)
+
+ expected = MultiIndex.from_arrays(
+ [[np.nan, np.nan, "128", 2], ["128", "128", "128", 2]]
+ )
+ tm.assert_index_equal(idx.dropna(how="all"), expected)
+
+
+def test_nulls(idx):
+ # this is really a smoke test for the methods
+ # as these are adequately tested for function elsewhere
+
+ msg = "isna is not defined for MultiIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.isna()
+
+
+@pytest.mark.xfail(reason="isna is not defined for MultiIndex")
+def test_hasnans_isnans(idx):
+ # GH 11343, added tests for hasnans / isnans
+ index = idx.copy()
+
+ # cases in indices doesn't include NaN
+ expected = np.array([False] * len(index), dtype=bool)
+ tm.assert_numpy_array_equal(index._isnan, expected)
+ assert index.hasnans is False
+
+ index = idx.copy()
+ values = index.values
+ values[1] = np.nan
+
+ index = type(idx)(values)
+
+ expected = np.array([False] * len(index), dtype=bool)
+ expected[1] = True
+ tm.assert_numpy_array_equal(index._isnan, expected)
+ assert index.hasnans is True
+
+
+def test_nan_stays_float():
+ # GH 7031
+ idx0 = MultiIndex(levels=[["A", "B"], []], codes=[[1, 0], [-1, -1]], names=[0, 1])
+ idx1 = MultiIndex(levels=[["C"], ["D"]], codes=[[0], [0]], names=[0, 1])
+ idxm = idx0.join(idx1, how="outer")
+ assert pd.isna(idx0.get_level_values(1)).all()
+ # the following failed in 0.14.1
+ assert pd.isna(idxm.get_level_values(1)[:-1]).all()
+
+ df0 = pd.DataFrame([[1, 2]], index=idx0)
+ df1 = pd.DataFrame([[3, 4]], index=idx1)
+ dfm = df0 - df1
+ assert pd.isna(df0.index.get_level_values(1)).all()
+ # the following failed in 0.14.1
+ assert pd.isna(dfm.index.get_level_values(1)[:-1]).all()
+
+
+def test_tuples_have_na():
+ index = MultiIndex(
+ levels=[[1, 0], [0, 1, 2, 3]],
+ codes=[[1, 1, 1, 1, -1, 0, 0, 0], [0, 1, 2, 3, 0, 1, 2, 3]],
+ )
+
+ assert pd.isna(index[4][0])
+ assert pd.isna(index.values[4][0])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_monotonic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_monotonic.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b0b3f7cb36d72abedc538eda9e6a85eb45067e2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_monotonic.py
@@ -0,0 +1,188 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ MultiIndex,
+)
+
+
+def test_is_monotonic_increasing_lexsorted(lexsorted_two_level_string_multiindex):
+ # string ordering
+ mi = lexsorted_two_level_string_multiindex
+ assert mi.is_monotonic_increasing is False
+ assert Index(mi.values).is_monotonic_increasing is False
+ assert mi._is_strictly_monotonic_increasing is False
+ assert Index(mi.values)._is_strictly_monotonic_increasing is False
+
+
+def test_is_monotonic_increasing():
+ i = MultiIndex.from_product([np.arange(10), np.arange(10)], names=["one", "two"])
+ assert i.is_monotonic_increasing is True
+ assert i._is_strictly_monotonic_increasing is True
+ assert Index(i.values).is_monotonic_increasing is True
+ assert i._is_strictly_monotonic_increasing is True
+
+ i = MultiIndex.from_product(
+ [np.arange(10, 0, -1), np.arange(10)], names=["one", "two"]
+ )
+ assert i.is_monotonic_increasing is False
+ assert i._is_strictly_monotonic_increasing is False
+ assert Index(i.values).is_monotonic_increasing is False
+ assert Index(i.values)._is_strictly_monotonic_increasing is False
+
+ i = MultiIndex.from_product(
+ [np.arange(10), np.arange(10, 0, -1)], names=["one", "two"]
+ )
+ assert i.is_monotonic_increasing is False
+ assert i._is_strictly_monotonic_increasing is False
+ assert Index(i.values).is_monotonic_increasing is False
+ assert Index(i.values)._is_strictly_monotonic_increasing is False
+
+ i = MultiIndex.from_product([[1.0, np.nan, 2.0], ["a", "b", "c"]])
+ assert i.is_monotonic_increasing is False
+ assert i._is_strictly_monotonic_increasing is False
+ assert Index(i.values).is_monotonic_increasing is False
+ assert Index(i.values)._is_strictly_monotonic_increasing is False
+
+ i = MultiIndex(
+ levels=[["bar", "baz", "foo", "qux"], ["mom", "next", "zenith"]],
+ codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
+ names=["first", "second"],
+ )
+ assert i.is_monotonic_increasing is True
+ assert Index(i.values).is_monotonic_increasing is True
+ assert i._is_strictly_monotonic_increasing is True
+ assert Index(i.values)._is_strictly_monotonic_increasing is True
+
+ # mixed levels, hits the TypeError
+ i = MultiIndex(
+ levels=[
+ [1, 2, 3, 4],
+ [
+ "gb00b03mlx29",
+ "lu0197800237",
+ "nl0000289783",
+ "nl0000289965",
+ "nl0000301109",
+ ],
+ ],
+ codes=[[0, 1, 1, 2, 2, 2, 3], [4, 2, 0, 0, 1, 3, -1]],
+ names=["household_id", "asset_id"],
+ )
+
+ assert i.is_monotonic_increasing is False
+ assert i._is_strictly_monotonic_increasing is False
+
+ # empty
+ i = MultiIndex.from_arrays([[], []])
+ assert i.is_monotonic_increasing is True
+ assert Index(i.values).is_monotonic_increasing is True
+ assert i._is_strictly_monotonic_increasing is True
+ assert Index(i.values)._is_strictly_monotonic_increasing is True
+
+
+def test_is_monotonic_decreasing():
+ i = MultiIndex.from_product(
+ [np.arange(9, -1, -1), np.arange(9, -1, -1)], names=["one", "two"]
+ )
+ assert i.is_monotonic_decreasing is True
+ assert i._is_strictly_monotonic_decreasing is True
+ assert Index(i.values).is_monotonic_decreasing is True
+ assert i._is_strictly_monotonic_decreasing is True
+
+ i = MultiIndex.from_product(
+ [np.arange(10), np.arange(10, 0, -1)], names=["one", "two"]
+ )
+ assert i.is_monotonic_decreasing is False
+ assert i._is_strictly_monotonic_decreasing is False
+ assert Index(i.values).is_monotonic_decreasing is False
+ assert Index(i.values)._is_strictly_monotonic_decreasing is False
+
+ i = MultiIndex.from_product(
+ [np.arange(10, 0, -1), np.arange(10)], names=["one", "two"]
+ )
+ assert i.is_monotonic_decreasing is False
+ assert i._is_strictly_monotonic_decreasing is False
+ assert Index(i.values).is_monotonic_decreasing is False
+ assert Index(i.values)._is_strictly_monotonic_decreasing is False
+
+ i = MultiIndex.from_product([[2.0, np.nan, 1.0], ["c", "b", "a"]])
+ assert i.is_monotonic_decreasing is False
+ assert i._is_strictly_monotonic_decreasing is False
+ assert Index(i.values).is_monotonic_decreasing is False
+ assert Index(i.values)._is_strictly_monotonic_decreasing is False
+
+ # string ordering
+ i = MultiIndex(
+ levels=[["qux", "foo", "baz", "bar"], ["three", "two", "one"]],
+ codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
+ names=["first", "second"],
+ )
+ assert i.is_monotonic_decreasing is False
+ assert Index(i.values).is_monotonic_decreasing is False
+ assert i._is_strictly_monotonic_decreasing is False
+ assert Index(i.values)._is_strictly_monotonic_decreasing is False
+
+ i = MultiIndex(
+ levels=[["qux", "foo", "baz", "bar"], ["zenith", "next", "mom"]],
+ codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
+ names=["first", "second"],
+ )
+ assert i.is_monotonic_decreasing is True
+ assert Index(i.values).is_monotonic_decreasing is True
+ assert i._is_strictly_monotonic_decreasing is True
+ assert Index(i.values)._is_strictly_monotonic_decreasing is True
+
+ # mixed levels, hits the TypeError
+ i = MultiIndex(
+ levels=[
+ [4, 3, 2, 1],
+ [
+ "nl0000301109",
+ "nl0000289965",
+ "nl0000289783",
+ "lu0197800237",
+ "gb00b03mlx29",
+ ],
+ ],
+ codes=[[0, 1, 1, 2, 2, 2, 3], [4, 2, 0, 0, 1, 3, -1]],
+ names=["household_id", "asset_id"],
+ )
+
+ assert i.is_monotonic_decreasing is False
+ assert i._is_strictly_monotonic_decreasing is False
+
+ # empty
+ i = MultiIndex.from_arrays([[], []])
+ assert i.is_monotonic_decreasing is True
+ assert Index(i.values).is_monotonic_decreasing is True
+ assert i._is_strictly_monotonic_decreasing is True
+ assert Index(i.values)._is_strictly_monotonic_decreasing is True
+
+
+def test_is_strictly_monotonic_increasing():
+ idx = MultiIndex(
+ levels=[["bar", "baz"], ["mom", "next"]], codes=[[0, 0, 1, 1], [0, 0, 0, 1]]
+ )
+ assert idx.is_monotonic_increasing is True
+ assert idx._is_strictly_monotonic_increasing is False
+
+
+def test_is_strictly_monotonic_decreasing():
+ idx = MultiIndex(
+ levels=[["baz", "bar"], ["next", "mom"]], codes=[[0, 0, 1, 1], [0, 0, 0, 1]]
+ )
+ assert idx.is_monotonic_decreasing is True
+ assert idx._is_strictly_monotonic_decreasing is False
+
+
+@pytest.mark.parametrize("attr", ["is_monotonic_increasing", "is_monotonic_decreasing"])
+@pytest.mark.parametrize(
+ "values",
+ [[(np.nan,), (1,), (2,)], [(1,), (np.nan,), (2,)], [(1,), (2,), (np.nan,)]],
+)
+def test_is_monotonic_with_nans(values, attr):
+ # GH: 37220
+ idx = MultiIndex.from_tuples(values, names=["test"])
+ assert getattr(idx, attr) is False
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_names.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_names.py
new file mode 100644
index 0000000000000000000000000000000000000000..6131d62e10ca5c9e773c6927e169d46e621f0269
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_names.py
@@ -0,0 +1,201 @@
+import pytest
+
+import pandas as pd
+from pandas import MultiIndex
+import pandas._testing as tm
+
+
+def check_level_names(index, names):
+ assert [level.name for level in index.levels] == list(names)
+
+
+def test_slice_keep_name():
+ x = MultiIndex.from_tuples([("a", "b"), (1, 2), ("c", "d")], names=["x", "y"])
+ assert x[1:].names == x.names
+
+
+def test_index_name_retained():
+ # GH9857
+ result = pd.DataFrame({"x": [1, 2, 6], "y": [2, 2, 8], "z": [-5, 0, 5]})
+ result = result.set_index("z")
+ result.loc[10] = [9, 10]
+ df_expected = pd.DataFrame(
+ {"x": [1, 2, 6, 9], "y": [2, 2, 8, 10], "z": [-5, 0, 5, 10]}
+ )
+ df_expected = df_expected.set_index("z")
+ tm.assert_frame_equal(result, df_expected)
+
+
+def test_changing_names(idx):
+ assert [level.name for level in idx.levels] == ["first", "second"]
+
+ view = idx.view()
+ copy = idx.copy()
+ shallow_copy = idx._view()
+
+ # changing names should not change level names on object
+ new_names = [name + "a" for name in idx.names]
+ idx.names = new_names
+ check_level_names(idx, ["firsta", "seconda"])
+
+ # and not on copies
+ check_level_names(view, ["first", "second"])
+ check_level_names(copy, ["first", "second"])
+ check_level_names(shallow_copy, ["first", "second"])
+
+ # and copies shouldn't change original
+ shallow_copy.names = [name + "c" for name in shallow_copy.names]
+ check_level_names(idx, ["firsta", "seconda"])
+
+
+def test_take_preserve_name(idx):
+ taken = idx.take([3, 0, 1])
+ assert taken.names == idx.names
+
+
+def test_copy_names():
+ # Check that adding a "names" parameter to the copy is honored
+ # GH14302
+ multi_idx = MultiIndex.from_tuples([(1, 2), (3, 4)], names=["MyName1", "MyName2"])
+ multi_idx1 = multi_idx.copy()
+
+ assert multi_idx.equals(multi_idx1)
+ assert multi_idx.names == ["MyName1", "MyName2"]
+ assert multi_idx1.names == ["MyName1", "MyName2"]
+
+ multi_idx2 = multi_idx.copy(names=["NewName1", "NewName2"])
+
+ assert multi_idx.equals(multi_idx2)
+ assert multi_idx.names == ["MyName1", "MyName2"]
+ assert multi_idx2.names == ["NewName1", "NewName2"]
+
+ multi_idx3 = multi_idx.copy(name=["NewName1", "NewName2"])
+
+ assert multi_idx.equals(multi_idx3)
+ assert multi_idx.names == ["MyName1", "MyName2"]
+ assert multi_idx3.names == ["NewName1", "NewName2"]
+
+ # gh-35592
+ with pytest.raises(ValueError, match="Length of new names must be 2, got 1"):
+ multi_idx.copy(names=["mario"])
+
+ with pytest.raises(TypeError, match="MultiIndex.name must be a hashable type"):
+ multi_idx.copy(names=[["mario"], ["luigi"]])
+
+
+def test_names(idx):
+ # names are assigned in setup
+ assert idx.names == ["first", "second"]
+ level_names = [level.name for level in idx.levels]
+ assert level_names == idx.names
+
+ # setting bad names on existing
+ index = idx
+ with pytest.raises(ValueError, match="^Length of names"):
+ setattr(index, "names", [*list(index.names), "third"])
+ with pytest.raises(ValueError, match="^Length of names"):
+ setattr(index, "names", [])
+
+ # initializing with bad names (should always be equivalent)
+ major_axis, minor_axis = idx.levels
+ major_codes, minor_codes = idx.codes
+ with pytest.raises(ValueError, match="^Length of names"):
+ MultiIndex(
+ levels=[major_axis, minor_axis],
+ codes=[major_codes, minor_codes],
+ names=["first"],
+ )
+ with pytest.raises(ValueError, match="^Length of names"):
+ MultiIndex(
+ levels=[major_axis, minor_axis],
+ codes=[major_codes, minor_codes],
+ names=["first", "second", "third"],
+ )
+
+ # names are assigned on index, but not transferred to the levels
+ index.names = ["a", "b"]
+ level_names = [level.name for level in index.levels]
+ assert level_names == ["a", "b"]
+
+
+def test_duplicate_level_names_access_raises(idx):
+ # GH19029
+ idx.names = ["foo", "foo"]
+ with pytest.raises(ValueError, match="name foo occurs multiple times"):
+ idx._get_level_number("foo")
+
+
+def test_get_names_from_levels():
+ idx = MultiIndex.from_product([["a"], [1, 2]], names=["a", "b"])
+
+ assert idx.levels[0].name == "a"
+ assert idx.levels[1].name == "b"
+
+
+def test_setting_names_from_levels_raises():
+ idx = MultiIndex.from_product([["a"], [1, 2]], names=["a", "b"])
+ with pytest.raises(RuntimeError, match="set_names"):
+ idx.levels[0].name = "foo"
+
+ with pytest.raises(RuntimeError, match="set_names"):
+ idx.levels[1].name = "foo"
+
+ new = pd.Series(1, index=idx.levels[0])
+ with pytest.raises(RuntimeError, match="set_names"):
+ new.index.name = "bar"
+
+ assert pd.Index._no_setting_name is False
+ assert pd.RangeIndex._no_setting_name is False
+
+
+@pytest.mark.parametrize("func", ["rename", "set_names"])
+@pytest.mark.parametrize(
+ "rename_dict, exp_names",
+ [
+ ({"x": "z"}, ["z", "y", "z"]),
+ ({"x": "z", "y": "x"}, ["z", "x", "z"]),
+ ({"y": "z"}, ["x", "z", "x"]),
+ ({}, ["x", "y", "x"]),
+ ({"z": "a"}, ["x", "y", "x"]),
+ ({"y": "z", "a": "b"}, ["x", "z", "x"]),
+ ],
+)
+def test_name_mi_with_dict_like_duplicate_names(func, rename_dict, exp_names):
+ # GH#20421
+ mi = MultiIndex.from_arrays([[1, 2], [3, 4], [5, 6]], names=["x", "y", "x"])
+ result = getattr(mi, func)(rename_dict)
+ expected = MultiIndex.from_arrays([[1, 2], [3, 4], [5, 6]], names=exp_names)
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["rename", "set_names"])
+@pytest.mark.parametrize(
+ "rename_dict, exp_names",
+ [
+ ({"x": "z"}, ["z", "y"]),
+ ({"x": "z", "y": "x"}, ["z", "x"]),
+ ({"a": "z"}, ["x", "y"]),
+ ({}, ["x", "y"]),
+ ],
+)
+def test_name_mi_with_dict_like(func, rename_dict, exp_names):
+ # GH#20421
+ mi = MultiIndex.from_arrays([[1, 2], [3, 4]], names=["x", "y"])
+ result = getattr(mi, func)(rename_dict)
+ expected = MultiIndex.from_arrays([[1, 2], [3, 4]], names=exp_names)
+ tm.assert_index_equal(result, expected)
+
+
+def test_index_name_with_dict_like_raising():
+ # GH#20421
+ ix = pd.Index([1, 2])
+ msg = "Can only pass dict-like as `names` for MultiIndex."
+ with pytest.raises(TypeError, match=msg):
+ ix.set_names({"x": "z"})
+
+
+def test_multiindex_name_and_level_raising():
+ # GH#20421
+ mi = MultiIndex.from_arrays([[1, 2], [3, 4]], names=["x", "y"])
+ with pytest.raises(TypeError, match="Can not pass level for dictlike `names`."):
+ mi.set_names(names={"x": "z"}, level={"x": "z"})
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_partial_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_partial_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..64cc1fa621b3195727cbfb3e62a8b6a6acf4dfaf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_partial_indexing.py
@@ -0,0 +1,148 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ IndexSlice,
+ MultiIndex,
+ date_range,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def df():
+ # c1
+ # 2016-01-01 00:00:00 a 0
+ # b 1
+ # c 2
+ # 2016-01-01 12:00:00 a 3
+ # b 4
+ # c 5
+ # 2016-01-02 00:00:00 a 6
+ # b 7
+ # c 8
+ # 2016-01-02 12:00:00 a 9
+ # b 10
+ # c 11
+ # 2016-01-03 00:00:00 a 12
+ # b 13
+ # c 14
+ dr = date_range("2016-01-01", "2016-01-03", freq="12h")
+ abc = ["a", "b", "c"]
+ mi = MultiIndex.from_product([dr, abc])
+ frame = DataFrame({"c1": range(15)}, index=mi)
+ return frame
+
+
+def test_partial_string_matching_single_index(df):
+ # partial string matching on a single index
+ for df_swap in [df.swaplevel(), df.swaplevel(0), df.swaplevel(0, 1)]:
+ df_swap = df_swap.sort_index()
+ just_a = df_swap.loc["a"]
+ result = just_a.loc["2016-01-01"]
+ expected = df.loc[IndexSlice[:, "a"], :].iloc[0:2]
+ expected.index = expected.index.droplevel(1)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_get_loc_partial_timestamp_multiindex(df):
+ mi = df.index
+ key = ("2016-01-01", "a")
+ loc = mi.get_loc(key)
+
+ expected = np.zeros(len(mi), dtype=bool)
+ expected[[0, 3]] = True
+ tm.assert_numpy_array_equal(loc, expected)
+
+ key2 = ("2016-01-02", "a")
+ loc2 = mi.get_loc(key2)
+ expected2 = np.zeros(len(mi), dtype=bool)
+ expected2[[6, 9]] = True
+ tm.assert_numpy_array_equal(loc2, expected2)
+
+ key3 = ("2016-01", "a")
+ loc3 = mi.get_loc(key3)
+ expected3 = np.zeros(len(mi), dtype=bool)
+ expected3[mi.get_level_values(1).get_loc("a")] = True
+ tm.assert_numpy_array_equal(loc3, expected3)
+
+ key4 = ("2016", "a")
+ loc4 = mi.get_loc(key4)
+ expected4 = expected3
+ tm.assert_numpy_array_equal(loc4, expected4)
+
+ # non-monotonic
+ taker = np.arange(len(mi), dtype=np.intp)
+ taker[::2] = taker[::-2]
+ mi2 = mi.take(taker)
+ loc5 = mi2.get_loc(key)
+ expected5 = np.zeros(len(mi2), dtype=bool)
+ expected5[[3, 14]] = True
+ tm.assert_numpy_array_equal(loc5, expected5)
+
+
+def test_partial_string_timestamp_multiindex(df):
+ # GH10331
+ df_swap = df.swaplevel(0, 1).sort_index()
+ SLC = IndexSlice
+
+ # indexing with IndexSlice
+ result = df.loc[SLC["2016-01-01":"2016-02-01", :], :]
+ expected = df
+ tm.assert_frame_equal(result, expected)
+
+ # match on secondary index
+ result = df_swap.loc[SLC[:, "2016-01-01":"2016-01-01"], :]
+ expected = df_swap.iloc[[0, 1, 5, 6, 10, 11]]
+ tm.assert_frame_equal(result, expected)
+
+ # partial string match on year only
+ result = df.loc["2016"]
+ expected = df
+ tm.assert_frame_equal(result, expected)
+
+ # partial string match on date
+ result = df.loc["2016-01-01"]
+ expected = df.iloc[0:6]
+ tm.assert_frame_equal(result, expected)
+
+ # partial string match on date and hour, from middle
+ result = df.loc["2016-01-02 12"]
+ # hourly resolution, same as index.levels[0], so we are _not_ slicing on
+ # that level, so that level gets dropped
+ expected = df.iloc[9:12].droplevel(0)
+ tm.assert_frame_equal(result, expected)
+
+ # partial string match on secondary index
+ result = df_swap.loc[SLC[:, "2016-01-02"], :]
+ expected = df_swap.iloc[[2, 3, 7, 8, 12, 13]]
+ tm.assert_frame_equal(result, expected)
+
+ # tuple selector with partial string match on date
+ # "2016-01-01" has daily resolution, so _is_ a slice on the first level.
+ result = df.loc[("2016-01-01", "a"), :]
+ expected = df.iloc[[0, 3]]
+ expected = df.iloc[[0, 3]].droplevel(1)
+ tm.assert_frame_equal(result, expected)
+
+ # Slicing date on first level should break (of course) bc the DTI is the
+ # second level on df_swap
+ with pytest.raises(KeyError, match="'2016-01-01'"):
+ df_swap.loc["2016-01-01"]
+
+
+def test_partial_string_timestamp_multiindex_str_key_raises(df):
+ # Even though this syntax works on a single index, this is somewhat
+ # ambiguous and we don't want to extend this behavior forward to work
+ # in multi-indexes. This would amount to selecting a scalar from a
+ # column.
+ with pytest.raises(KeyError, match="'2016-01-01'"):
+ df["2016-01-01"]
+
+
+def test_partial_string_timestamp_multiindex_daily_resolution(df):
+ # GH12685 (partial string with daily resolution or below)
+ result = df.loc[IndexSlice["2013-03":"2013-03", :], :]
+ expected = df.iloc[118:180]
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_pickle.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_pickle.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d8b72140442159fa0b8c608022d167bddd95db4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_pickle.py
@@ -0,0 +1,10 @@
+import pytest
+
+from pandas import MultiIndex
+
+
+def test_pickle_compat_construction():
+ # this is testing for pickle compat
+ # need an object to create with
+ with pytest.raises(TypeError, match="Must pass both levels and codes"):
+ MultiIndex()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_reindex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_reindex.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1b4fe8b98760a0b776c5d81d471a7745e8407de
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_reindex.py
@@ -0,0 +1,174 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+
+def test_reindex(idx):
+ result, indexer = idx.reindex(list(idx[:4]))
+ assert isinstance(result, MultiIndex)
+ assert result.names == ["first", "second"]
+ assert [level.name for level in result.levels] == ["first", "second"]
+
+ result, indexer = idx.reindex(list(idx))
+ assert isinstance(result, MultiIndex)
+ assert indexer is None
+ assert result.names == ["first", "second"]
+ assert [level.name for level in result.levels] == ["first", "second"]
+
+
+def test_reindex_level(idx):
+ index = Index(["one"])
+
+ target, indexer = idx.reindex(index, level="second")
+ target2, indexer2 = index.reindex(idx, level="second")
+
+ exp_index = idx.join(index, level="second", how="right")
+ exp_index2 = idx.join(index, level="second", how="left")
+
+ assert target.equals(exp_index)
+ exp_indexer = np.array([0, 2, 4])
+ tm.assert_numpy_array_equal(indexer, exp_indexer, check_dtype=False)
+
+ assert target2.equals(exp_index2)
+ exp_indexer2 = np.array([0, -1, 0, -1, 0, -1])
+ tm.assert_numpy_array_equal(indexer2, exp_indexer2, check_dtype=False)
+
+ with pytest.raises(TypeError, match="Fill method not supported"):
+ idx.reindex(idx, method="pad", level="second")
+
+
+def test_reindex_preserves_names_when_target_is_list_or_ndarray(idx):
+ # GH6552
+ idx = idx.copy()
+ target = idx.copy()
+ idx.names = target.names = [None, None]
+
+ other_dtype = MultiIndex.from_product([[1, 2], [3, 4]])
+
+ # list & ndarray cases
+ assert idx.reindex([])[0].names == [None, None]
+ assert idx.reindex(np.array([]))[0].names == [None, None]
+ assert idx.reindex(target.tolist())[0].names == [None, None]
+ assert idx.reindex(target.values)[0].names == [None, None]
+ assert idx.reindex(other_dtype.tolist())[0].names == [None, None]
+ assert idx.reindex(other_dtype.values)[0].names == [None, None]
+
+ idx.names = ["foo", "bar"]
+ assert idx.reindex([])[0].names == ["foo", "bar"]
+ assert idx.reindex(np.array([]))[0].names == ["foo", "bar"]
+ assert idx.reindex(target.tolist())[0].names == ["foo", "bar"]
+ assert idx.reindex(target.values)[0].names == ["foo", "bar"]
+ assert idx.reindex(other_dtype.tolist())[0].names == ["foo", "bar"]
+ assert idx.reindex(other_dtype.values)[0].names == ["foo", "bar"]
+
+
+def test_reindex_lvl_preserves_names_when_target_is_list_or_array():
+ # GH7774
+ idx = MultiIndex.from_product([[0, 1], ["a", "b"]], names=["foo", "bar"])
+ assert idx.reindex([], level=0)[0].names == ["foo", "bar"]
+ assert idx.reindex([], level=1)[0].names == ["foo", "bar"]
+
+
+def test_reindex_lvl_preserves_type_if_target_is_empty_list_or_array(
+ using_infer_string,
+):
+ # GH7774
+ idx = MultiIndex.from_product([[0, 1], ["a", "b"]])
+ assert idx.reindex([], level=0)[0].levels[0].dtype.type == np.int64
+ exp = np.object_ if not using_infer_string else str
+ assert idx.reindex([], level=1)[0].levels[1].dtype.type == exp
+
+ # case with EA levels
+ cat = pd.Categorical(["foo", "bar"])
+ dti = pd.date_range("2016-01-01", periods=2, tz="US/Pacific")
+ mi = MultiIndex.from_product([cat, dti])
+ assert mi.reindex([], level=0)[0].levels[0].dtype == cat.dtype
+ assert mi.reindex([], level=1)[0].levels[1].dtype == dti.dtype
+
+
+def test_reindex_base(idx):
+ expected = np.arange(idx.size, dtype=np.intp)
+
+ actual = idx.get_indexer(idx)
+ tm.assert_numpy_array_equal(expected, actual)
+
+ with pytest.raises(ValueError, match="Invalid fill method"):
+ idx.get_indexer(idx, method="invalid")
+
+
+def test_reindex_non_unique():
+ idx = MultiIndex.from_tuples([(0, 0), (1, 1), (1, 1), (2, 2)])
+ a = pd.Series(np.arange(4), index=idx)
+ new_idx = MultiIndex.from_tuples([(0, 0), (1, 1), (2, 2)])
+
+ msg = "cannot handle a non-unique multi-index!"
+ with pytest.raises(ValueError, match=msg):
+ a.reindex(new_idx)
+
+
+@pytest.mark.parametrize("values", [[["a"], ["x"]], [[], []]])
+def test_reindex_empty_with_level(values):
+ # GH41170
+ idx = MultiIndex.from_arrays(values)
+ result, result_indexer = idx.reindex(np.array(["b"]), level=0)
+ expected = MultiIndex(levels=[["b"], values[1]], codes=[[], []])
+ expected_indexer = np.array([], dtype=result_indexer.dtype)
+ tm.assert_index_equal(result, expected)
+ tm.assert_numpy_array_equal(result_indexer, expected_indexer)
+
+
+def test_reindex_not_all_tuples():
+ keys = [("i", "i"), ("i", "j"), ("j", "i"), "j"]
+ mi = MultiIndex.from_tuples(keys[:-1])
+ idx = Index(keys)
+ res, indexer = mi.reindex(idx)
+
+ tm.assert_index_equal(res, idx)
+ expected = np.array([0, 1, 2, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+
+def test_reindex_limit_arg_with_multiindex():
+ # GH21247
+
+ idx = MultiIndex.from_tuples([(3, "A"), (4, "A"), (4, "B")])
+
+ df = pd.Series([0.02, 0.01, 0.012], index=idx)
+
+ new_idx = MultiIndex.from_tuples(
+ [
+ (3, "A"),
+ (3, "B"),
+ (4, "A"),
+ (4, "B"),
+ (4, "C"),
+ (5, "B"),
+ (5, "C"),
+ (6, "B"),
+ (6, "C"),
+ ]
+ )
+
+ with pytest.raises(
+ ValueError,
+ match="limit argument only valid if doing pad, backfill or nearest reindexing",
+ ):
+ df.reindex(new_idx, fill_value=0, limit=1)
+
+
+def test_reindex_with_none_in_nested_multiindex():
+ # GH42883
+ index = MultiIndex.from_tuples([(("a", None), 1), (("b", None), 2)])
+ index2 = MultiIndex.from_tuples([(("b", None), 2), (("a", None), 1)])
+ df1_dtype = pd.DataFrame([1, 2], index=index)
+ df2_dtype = pd.DataFrame([2, 1], index=index2)
+
+ result = df1_dtype.reindex_like(df2_dtype)
+ expected = df2_dtype
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_reshape.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_reshape.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce411ea3970823e0a62821588542fce769258f98
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_reshape.py
@@ -0,0 +1,224 @@
+from datetime import datetime
+import zoneinfo
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+
+def test_insert(idx):
+ # key contained in all levels
+ new_index = idx.insert(0, ("bar", "two"))
+ assert new_index.equal_levels(idx)
+ assert new_index[0] == ("bar", "two")
+
+ # key not contained in all levels
+ new_index = idx.insert(0, ("abc", "three"))
+
+ exp0 = Index([*list(idx.levels[0]), "abc"], name="first")
+ tm.assert_index_equal(new_index.levels[0], exp0)
+ assert new_index.names == ["first", "second"]
+
+ exp1 = Index([*list(idx.levels[1]), "three"], name="second")
+ tm.assert_index_equal(new_index.levels[1], exp1)
+ assert new_index[0] == ("abc", "three")
+
+ # key wrong length
+ msg = "Item must have length equal to number of levels"
+ with pytest.raises(ValueError, match=msg):
+ idx.insert(0, ("foo2",))
+
+ left = pd.DataFrame([["a", "b", 0], ["b", "d", 1]], columns=["1st", "2nd", "3rd"])
+ left.set_index(["1st", "2nd"], inplace=True)
+ ts = left["3rd"].copy(deep=True)
+
+ left.loc[("b", "x"), "3rd"] = 2
+ left.loc[("b", "a"), "3rd"] = -1
+ left.loc[("b", "b"), "3rd"] = 3
+ left.loc[("a", "x"), "3rd"] = 4
+ left.loc[("a", "w"), "3rd"] = 5
+ left.loc[("a", "a"), "3rd"] = 6
+
+ ts.loc[("b", "x")] = 2
+ ts.loc["b", "a"] = -1
+ ts.loc[("b", "b")] = 3
+ ts.loc["a", "x"] = 4
+ ts.loc[("a", "w")] = 5
+ ts.loc["a", "a"] = 6
+
+ right = pd.DataFrame(
+ [
+ ["a", "b", 0],
+ ["b", "d", 1],
+ ["b", "x", 2],
+ ["b", "a", -1],
+ ["b", "b", 3],
+ ["a", "x", 4],
+ ["a", "w", 5],
+ ["a", "a", 6],
+ ],
+ columns=["1st", "2nd", "3rd"],
+ )
+ right.set_index(["1st", "2nd"], inplace=True)
+ # FIXME data types changes to float because
+ # of intermediate nan insertion;
+ tm.assert_frame_equal(left, right, check_dtype=False)
+ tm.assert_series_equal(ts, right["3rd"])
+
+
+def test_insert2():
+ # GH9250
+ idx = (
+ [("test1", i) for i in range(5)]
+ + [("test2", i) for i in range(6)]
+ + [("test", 17), ("test", 18)]
+ )
+
+ left = pd.Series(np.linspace(0, 10, 11), MultiIndex.from_tuples(idx[:-2]))
+
+ left.loc[("test", 17)] = 11
+ left.loc[("test", 18)] = 12
+
+ right = pd.Series(np.linspace(0, 12, 13), MultiIndex.from_tuples(idx))
+
+ tm.assert_series_equal(left, right)
+
+
+def test_append(idx):
+ result = idx[:3].append(idx[3:])
+ assert result.equals(idx)
+
+ foos = [idx[:1], idx[1:3], idx[3:]]
+ result = foos[0].append(foos[1:])
+ assert result.equals(idx)
+
+ # empty
+ result = idx.append([])
+ assert result.equals(idx)
+
+
+def test_append_index():
+ idx1 = Index([1.1, 1.2, 1.3])
+ idx2 = pd.date_range("2011-01-01", freq="D", periods=3, tz="Asia/Tokyo")
+ idx3 = Index(["A", "B", "C"])
+
+ midx_lv2 = MultiIndex.from_arrays([idx1, idx2])
+ midx_lv3 = MultiIndex.from_arrays([idx1, idx2, idx3])
+
+ result = idx1.append(midx_lv2)
+
+ # see gh-7112
+ tz = zoneinfo.ZoneInfo("Asia/Tokyo")
+ expected_tuples = [
+ (1.1, datetime(2011, 1, 1, tzinfo=tz)),
+ (1.2, datetime(2011, 1, 2, tzinfo=tz)),
+ (1.3, datetime(2011, 1, 3, tzinfo=tz)),
+ ]
+ expected = Index([1.1, 1.2, 1.3, *expected_tuples])
+ tm.assert_index_equal(result, expected)
+
+ result = midx_lv2.append(idx1)
+ expected = Index([*expected_tuples, 1.1, 1.2, 1.3])
+ tm.assert_index_equal(result, expected)
+
+ result = midx_lv2.append(midx_lv2)
+ expected = MultiIndex.from_arrays([idx1.append(idx1), idx2.append(idx2)])
+ tm.assert_index_equal(result, expected)
+
+ result = midx_lv2.append(midx_lv3)
+ tm.assert_index_equal(result, expected)
+
+ result = midx_lv3.append(midx_lv2)
+ expected = Index._simple_new(
+ np.array(
+ [
+ (1.1, datetime(2011, 1, 1, tzinfo=tz), "A"),
+ (1.2, datetime(2011, 1, 2, tzinfo=tz), "B"),
+ (1.3, datetime(2011, 1, 3, tzinfo=tz), "C"),
+ *expected_tuples,
+ ],
+ dtype=object,
+ ),
+ None,
+ )
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("name, exp", [("b", "b"), ("c", None)])
+def test_append_names_match(name, exp):
+ # GH#48288
+ midx = MultiIndex.from_arrays([[1, 2], [3, 4]], names=["a", "b"])
+ midx2 = MultiIndex.from_arrays([[3], [5]], names=["a", name])
+ result = midx.append(midx2)
+ expected = MultiIndex.from_arrays([[1, 2, 3], [3, 4, 5]], names=["a", exp])
+ tm.assert_index_equal(result, expected)
+
+
+def test_append_names_dont_match():
+ # GH#48288
+ midx = MultiIndex.from_arrays([[1, 2], [3, 4]], names=["a", "b"])
+ midx2 = MultiIndex.from_arrays([[3], [5]], names=["x", "y"])
+ result = midx.append(midx2)
+ expected = MultiIndex.from_arrays([[1, 2, 3], [3, 4, 5]], names=None)
+ tm.assert_index_equal(result, expected)
+
+
+def test_append_overlapping_interval_levels():
+ # GH 54934
+ ivl1 = pd.IntervalIndex.from_breaks([0.0, 1.0, 2.0])
+ ivl2 = pd.IntervalIndex.from_breaks([0.5, 1.5, 2.5])
+ mi1 = MultiIndex.from_product([ivl1, ivl1])
+ mi2 = MultiIndex.from_product([ivl2, ivl2])
+ result = mi1.append(mi2)
+ expected = MultiIndex.from_tuples(
+ [
+ (pd.Interval(0.0, 1.0), pd.Interval(0.0, 1.0)),
+ (pd.Interval(0.0, 1.0), pd.Interval(1.0, 2.0)),
+ (pd.Interval(1.0, 2.0), pd.Interval(0.0, 1.0)),
+ (pd.Interval(1.0, 2.0), pd.Interval(1.0, 2.0)),
+ (pd.Interval(0.5, 1.5), pd.Interval(0.5, 1.5)),
+ (pd.Interval(0.5, 1.5), pd.Interval(1.5, 2.5)),
+ (pd.Interval(1.5, 2.5), pd.Interval(0.5, 1.5)),
+ (pd.Interval(1.5, 2.5), pd.Interval(1.5, 2.5)),
+ ]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_repeat():
+ reps = 2
+ numbers = [1, 2, 3]
+ names = np.array(["foo", "bar"])
+
+ m = MultiIndex.from_product([numbers, names], names=names)
+ expected = MultiIndex.from_product([numbers, names.repeat(reps)], names=names)
+ tm.assert_index_equal(m.repeat(reps), expected)
+
+
+def test_insert_base(idx):
+ result = idx[1:4]
+
+ # test 0th element
+ assert idx[0:4].equals(result.insert(0, idx[0]))
+
+
+def test_delete_base(idx):
+ expected = idx[1:]
+ result = idx.delete(0)
+ assert result.equals(expected)
+ assert result.name == expected.name
+
+ expected = idx[:-1]
+ result = idx.delete(-1)
+ assert result.equals(expected)
+ assert result.name == expected.name
+
+ msg = "index 6 is out of bounds for axis 0 with size 6"
+ with pytest.raises(IndexError, match=msg):
+ idx.delete(len(idx))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..7149daa3ac57333170d6ad094d3942854d3a49c7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_setops.py
@@ -0,0 +1,778 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ DataFrame,
+ Index,
+ IntervalIndex,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+from pandas.api.types import (
+ is_float_dtype,
+ is_unsigned_integer_dtype,
+)
+
+
+@pytest.mark.parametrize("case", [0.5, "xxx"])
+@pytest.mark.parametrize(
+ "method", ["intersection", "union", "difference", "symmetric_difference"]
+)
+def test_set_ops_error_cases(idx, case, sort, method):
+ # non-iterable input
+ msg = "Input must be Index or array-like"
+ with pytest.raises(TypeError, match=msg):
+ getattr(idx, method)(case, sort=sort)
+
+
+@pytest.mark.parametrize("klass", [MultiIndex, np.array, Series, list])
+def test_intersection_base(idx, sort, klass):
+ first = idx[2::-1] # first 3 elements reversed
+ second = idx[:5]
+
+ if klass is not MultiIndex:
+ second = klass(second.values)
+
+ intersect = first.intersection(second, sort=sort)
+ if sort is None:
+ expected = first.sort_values()
+ else:
+ expected = first
+ tm.assert_index_equal(intersect, expected)
+
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ first.intersection([1, 2, 3], sort=sort)
+
+
+@pytest.mark.arm_slow
+@pytest.mark.parametrize("klass", [MultiIndex, np.array, Series, list])
+def test_union_base(idx, sort, klass):
+ first = idx[::-1]
+ second = idx[:5]
+
+ if klass is not MultiIndex:
+ second = klass(second.values)
+
+ union = first.union(second, sort=sort)
+ if sort is None:
+ expected = first.sort_values()
+ else:
+ expected = first
+ tm.assert_index_equal(union, expected)
+
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ first.union([1, 2, 3], sort=sort)
+
+
+def test_difference_base(idx, sort):
+ second = idx[4:]
+ answer = idx[:4]
+ result = idx.difference(second, sort=sort)
+
+ if sort is None:
+ answer = answer.sort_values()
+
+ assert result.equals(answer)
+ tm.assert_index_equal(result, answer)
+
+ # GH 10149
+ cases = [klass(second.values) for klass in [np.array, Series, list]]
+ for case in cases:
+ result = idx.difference(case, sort=sort)
+ tm.assert_index_equal(result, answer)
+
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ idx.difference([1, 2, 3], sort=sort)
+
+
+def test_symmetric_difference(idx, sort):
+ first = idx[1:]
+ second = idx[:-1]
+ answer = idx[[-1, 0]]
+ result = first.symmetric_difference(second, sort=sort)
+
+ if sort is None:
+ answer = answer.sort_values()
+
+ tm.assert_index_equal(result, answer)
+
+ # GH 10149
+ cases = [klass(second.values) for klass in [np.array, Series, list]]
+ for case in cases:
+ result = first.symmetric_difference(case, sort=sort)
+ tm.assert_index_equal(result, answer)
+
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ first.symmetric_difference([1, 2, 3], sort=sort)
+
+
+def test_multiindex_symmetric_difference():
+ # GH 13490
+ idx = MultiIndex.from_product([["a", "b"], ["A", "B"]], names=["a", "b"])
+ result = idx.symmetric_difference(idx)
+ assert result.names == idx.names
+
+ idx2 = idx.copy().rename(["A", "B"])
+ result = idx.symmetric_difference(idx2)
+ assert result.names == [None, None]
+
+
+def test_empty(idx):
+ # GH 15270
+ assert not idx.empty
+ assert idx[:0].empty
+
+
+def test_difference(idx, sort):
+ first = idx
+ result = first.difference(idx[-3:], sort=sort)
+ vals = idx[:-3].values
+
+ if sort is None:
+ vals = sorted(vals)
+
+ expected = MultiIndex.from_tuples(vals, sortorder=0, names=idx.names)
+
+ assert isinstance(result, MultiIndex)
+ assert result.equals(expected)
+ assert result.names == idx.names
+ tm.assert_index_equal(result, expected)
+
+ # empty difference: reflexive
+ result = idx.difference(idx, sort=sort)
+ expected = idx[:0]
+ assert result.equals(expected)
+ assert result.names == idx.names
+
+ # empty difference: superset
+ result = idx[-3:].difference(idx, sort=sort)
+ expected = idx[:0]
+ assert result.equals(expected)
+ assert result.names == idx.names
+
+ # empty difference: degenerate
+ result = idx[:0].difference(idx, sort=sort)
+ expected = idx[:0]
+ assert result.equals(expected)
+ assert result.names == idx.names
+
+ # names not the same
+ chunklet = idx[-3:]
+ chunklet.names = ["foo", "baz"]
+ result = first.difference(chunklet, sort=sort)
+ assert result.names == (None, None)
+
+ # empty, but non-equal
+ result = idx.difference(idx.sortlevel(1)[0], sort=sort)
+ assert len(result) == 0
+
+ # raise Exception called with non-MultiIndex
+ result = first.difference(first.values, sort=sort)
+ assert result.equals(first[:0])
+
+ # name from empty array
+ result = first.difference([], sort=sort)
+ assert first.equals(result)
+ assert first.names == result.names
+
+ # name from non-empty array
+ result = first.difference([("foo", "one")], sort=sort)
+ expected = MultiIndex.from_tuples(
+ [("bar", "one"), ("baz", "two"), ("foo", "two"), ("qux", "one"), ("qux", "two")]
+ )
+ expected.names = first.names
+ assert first.names == result.names
+
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ first.difference([1, 2, 3, 4, 5], sort=sort)
+
+
+def test_difference_sort_special():
+ # GH-24959
+ idx = MultiIndex.from_product([[1, 0], ["a", "b"]])
+ # sort=None, the default
+ result = idx.difference([])
+ tm.assert_index_equal(result, idx)
+
+
+def test_difference_sort_special_true():
+ idx = MultiIndex.from_product([[1, 0], ["a", "b"]])
+ result = idx.difference([], sort=True)
+ expected = MultiIndex.from_product([[0, 1], ["a", "b"]])
+ tm.assert_index_equal(result, expected)
+
+
+def test_difference_sort_incomparable():
+ # GH-24959
+ idx = MultiIndex.from_product([[1, pd.Timestamp("2000"), 2], ["a", "b"]])
+
+ other = MultiIndex.from_product([[3, pd.Timestamp("2000"), 4], ["c", "d"]])
+ # sort=None, the default
+ msg = "sort order is undefined for incomparable objects"
+ with tm.assert_produces_warning(RuntimeWarning, match=msg):
+ result = idx.difference(other)
+ tm.assert_index_equal(result, idx)
+
+ # sort=False
+ result = idx.difference(other, sort=False)
+ tm.assert_index_equal(result, idx)
+
+
+def test_difference_sort_incomparable_true():
+ idx = MultiIndex.from_product([[1, pd.Timestamp("2000"), 2], ["a", "b"]])
+ other = MultiIndex.from_product([[3, pd.Timestamp("2000"), 4], ["c", "d"]])
+
+ # TODO: this is raising in constructing a Categorical when calling
+ # algos.safe_sort. Should we catch and re-raise with a better message?
+ msg = "'values' is not ordered, please explicitly specify the categories order "
+ with pytest.raises(TypeError, match=msg):
+ idx.difference(other, sort=True)
+
+
+def test_union(idx, sort):
+ piece1 = idx[:5][::-1]
+ piece2 = idx[3:]
+
+ the_union = piece1.union(piece2, sort=sort)
+
+ if sort in (None, False):
+ tm.assert_index_equal(the_union.sort_values(), idx.sort_values())
+ else:
+ tm.assert_index_equal(the_union, idx)
+
+ # corner case, pass self or empty thing:
+ the_union = idx.union(idx, sort=sort)
+ tm.assert_index_equal(the_union, idx)
+
+ the_union = idx.union(idx[:0], sort=sort)
+ tm.assert_index_equal(the_union, idx)
+
+ tuples = idx.values
+ result = idx[:4].union(tuples[4:], sort=sort)
+ if sort is None:
+ tm.assert_index_equal(result.sort_values(), idx.sort_values())
+ else:
+ assert result.equals(idx)
+
+
+def test_union_with_regular_index(idx, using_infer_string):
+ other = Index(["A", "B", "C"])
+
+ result = other.union(idx)
+ assert ("foo", "one") in result
+ assert "B" in result
+
+ if using_infer_string:
+ with pytest.raises(NotImplementedError, match="Can only union"):
+ idx.union(other)
+ else:
+ msg = "The values in the array are unorderable"
+ with tm.assert_produces_warning(RuntimeWarning, match=msg):
+ result2 = idx.union(other)
+ # This is more consistent now, if sorting fails then we don't sort at all
+ # in the MultiIndex case.
+ assert not result.equals(result2)
+
+
+def test_intersection(idx, sort):
+ piece1 = idx[:5][::-1]
+ piece2 = idx[3:]
+
+ the_int = piece1.intersection(piece2, sort=sort)
+
+ if sort in (None, True):
+ tm.assert_index_equal(the_int, idx[3:5])
+ else:
+ tm.assert_index_equal(the_int.sort_values(), idx[3:5])
+
+ # corner case, pass self
+ the_int = idx.intersection(idx, sort=sort)
+ tm.assert_index_equal(the_int, idx)
+
+ # empty intersection: disjoint
+ empty = idx[:2].intersection(idx[2:], sort=sort)
+ expected = idx[:0]
+ assert empty.equals(expected)
+
+ tuples = idx.values
+ result = idx.intersection(tuples)
+ assert result.equals(idx)
+
+
+@pytest.mark.parametrize(
+ "method", ["intersection", "union", "difference", "symmetric_difference"]
+)
+def test_setop_with_categorical(idx, sort, method):
+ other = idx.to_flat_index().astype("category")
+ res_names = [None] * idx.nlevels
+
+ result = getattr(idx, method)(other, sort=sort)
+ expected = getattr(idx, method)(idx, sort=sort).rename(res_names)
+ tm.assert_index_equal(result, expected)
+
+ result = getattr(idx, method)(other[:5], sort=sort)
+ expected = getattr(idx, method)(idx[:5], sort=sort).rename(res_names)
+ tm.assert_index_equal(result, expected)
+
+
+def test_intersection_non_object(idx, sort):
+ other = Index(range(3), name="foo")
+
+ result = idx.intersection(other, sort=sort)
+ expected = MultiIndex(levels=idx.levels, codes=[[]] * idx.nlevels, names=None)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # if we pass a length-0 ndarray (i.e. no name, we retain our idx.name)
+ result = idx.intersection(np.asarray(other)[:0], sort=sort)
+ expected = MultiIndex(levels=idx.levels, codes=[[]] * idx.nlevels, names=idx.names)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ # With non-zero length non-index, we try and fail to convert to tuples
+ idx.intersection(np.asarray(other), sort=sort)
+
+
+def test_intersect_equal_sort():
+ # GH-24959
+ idx = MultiIndex.from_product([[1, 0], ["a", "b"]])
+ tm.assert_index_equal(idx.intersection(idx, sort=False), idx)
+ tm.assert_index_equal(idx.intersection(idx, sort=None), idx)
+
+
+def test_intersect_equal_sort_true():
+ idx = MultiIndex.from_product([[1, 0], ["a", "b"]])
+ expected = MultiIndex.from_product([[0, 1], ["a", "b"]])
+ result = idx.intersection(idx, sort=True)
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("slice_", [slice(None), slice(0)])
+def test_union_sort_other_empty(slice_):
+ # https://github.com/pandas-dev/pandas/issues/24959
+ idx = MultiIndex.from_product([[1, 0], ["a", "b"]])
+
+ # default, sort=None
+ other = idx[slice_]
+ tm.assert_index_equal(idx.union(other), idx)
+ tm.assert_index_equal(other.union(idx), idx)
+
+ # sort=False
+ tm.assert_index_equal(idx.union(other, sort=False), idx)
+
+
+def test_union_sort_other_empty_sort():
+ idx = MultiIndex.from_product([[1, 0], ["a", "b"]])
+ other = idx[:0]
+ result = idx.union(other, sort=True)
+ expected = MultiIndex.from_product([[0, 1], ["a", "b"]])
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_sort_other_incomparable():
+ # https://github.com/pandas-dev/pandas/issues/24959
+ idx = MultiIndex.from_product([[1, pd.Timestamp("2000")], ["a", "b"]])
+
+ # default, sort=None
+ with tm.assert_produces_warning(RuntimeWarning, match="are unorderable"):
+ result = idx.union(idx[:1])
+ tm.assert_index_equal(result, idx)
+
+ # sort=False
+ result = idx.union(idx[:1], sort=False)
+ tm.assert_index_equal(result, idx)
+
+
+def test_union_sort_other_incomparable_sort():
+ idx = MultiIndex.from_product([[1, pd.Timestamp("2000")], ["a", "b"]])
+ msg = "'<' not supported between instances of 'Timestamp' and 'int'"
+ with pytest.raises(TypeError, match=msg):
+ idx.union(idx[:1], sort=True)
+
+
+def test_union_non_object_dtype_raises():
+ # GH#32646 raise NotImplementedError instead of less-informative error
+ mi = MultiIndex.from_product([["a", "b"], [1, 2]])
+
+ idx = mi.levels[1]
+
+ msg = "Can only union MultiIndex with MultiIndex or Index of tuples"
+ with pytest.raises(NotImplementedError, match=msg):
+ mi.union(idx)
+
+
+def test_union_empty_self_different_names():
+ # GH#38423
+ mi = MultiIndex.from_arrays([[]])
+ mi2 = MultiIndex.from_arrays([[1, 2], [3, 4]], names=["a", "b"])
+ result = mi.union(mi2)
+ expected = MultiIndex.from_arrays([[1, 2], [3, 4]])
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_multiindex_empty_rangeindex():
+ # GH#41234
+ mi = MultiIndex.from_arrays([[1, 2], [3, 4]], names=["a", "b"])
+ ri = pd.RangeIndex(0)
+
+ result_left = mi.union(ri)
+ tm.assert_index_equal(mi, result_left, check_names=False)
+
+ result_right = ri.union(mi)
+ tm.assert_index_equal(mi, result_right, check_names=False)
+
+
+@pytest.mark.parametrize(
+ "method", ["union", "intersection", "difference", "symmetric_difference"]
+)
+def test_setops_sort_validation(method):
+ idx1 = MultiIndex.from_product([["a", "b"], [1, 2]])
+ idx2 = MultiIndex.from_product([["b", "c"], [1, 2]])
+
+ with pytest.raises(ValueError, match="The 'sort' keyword only takes"):
+ getattr(idx1, method)(idx2, sort=2)
+
+ # sort=True is supported as of GH#?
+ getattr(idx1, method)(idx2, sort=True)
+
+
+@pytest.mark.parametrize("val", [pd.NA, 100])
+def test_difference_keep_ea_dtypes(any_numeric_ea_dtype, val):
+ # GH#48606
+ midx = MultiIndex.from_arrays(
+ [Series([1, 2], dtype=any_numeric_ea_dtype), [2, 1]], names=["a", None]
+ )
+ midx2 = MultiIndex.from_arrays(
+ [Series([1, 2, val], dtype=any_numeric_ea_dtype), [1, 1, 3]]
+ )
+ result = midx.difference(midx2)
+ expected = MultiIndex.from_arrays([Series([1], dtype=any_numeric_ea_dtype), [2]])
+ tm.assert_index_equal(result, expected)
+
+ result = midx.difference(midx.sort_values(ascending=False))
+ expected = MultiIndex.from_arrays(
+ [Series([], dtype=any_numeric_ea_dtype), Series([], dtype=np.int64)],
+ names=["a", None],
+ )
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("val", [pd.NA, 5])
+def test_symmetric_difference_keeping_ea_dtype(any_numeric_ea_dtype, val):
+ # GH#48607
+ midx = MultiIndex.from_arrays(
+ [Series([1, 2], dtype=any_numeric_ea_dtype), [2, 1]], names=["a", None]
+ )
+ midx2 = MultiIndex.from_arrays(
+ [Series([1, 2, val], dtype=any_numeric_ea_dtype), [1, 1, 3]]
+ )
+ result = midx.symmetric_difference(midx2)
+ expected = MultiIndex.from_arrays(
+ [Series([1, 1, val], dtype=any_numeric_ea_dtype), [1, 2, 3]]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ ("tuples", "exp_tuples"),
+ [
+ ([("val1", "test1")], [("val1", "test1")]),
+ ([("val1", "test1"), ("val1", "test1")], [("val1", "test1")]),
+ (
+ [("val2", "test2"), ("val1", "test1")],
+ [("val2", "test2"), ("val1", "test1")],
+ ),
+ ],
+)
+def test_intersect_with_duplicates(tuples, exp_tuples):
+ # GH#36915
+ left = MultiIndex.from_tuples(tuples, names=["first", "second"])
+ right = MultiIndex.from_tuples(
+ [("val1", "test1"), ("val1", "test1"), ("val2", "test2")],
+ names=["first", "second"],
+ )
+ result = left.intersection(right)
+ expected = MultiIndex.from_tuples(exp_tuples, names=["first", "second"])
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data, names, expected",
+ [
+ ((1,), None, [None, None]),
+ ((1,), ["a"], [None, None]),
+ ((1,), ["b"], [None, None]),
+ ((1, 2), ["c", "d"], [None, None]),
+ ((1, 2), ["b", "a"], [None, None]),
+ ((1, 2, 3), ["a", "b", "c"], [None, None]),
+ ((1, 2), ["a", "c"], ["a", None]),
+ ((1, 2), ["c", "b"], [None, "b"]),
+ ((1, 2), ["a", "b"], ["a", "b"]),
+ ((1, 2), [None, "b"], [None, "b"]),
+ ],
+)
+def test_maybe_match_names(data, names, expected):
+ # GH#38323
+ mi = MultiIndex.from_tuples([], names=["a", "b"])
+ mi2 = MultiIndex.from_tuples([data], names=names)
+ result = mi._maybe_match_names(mi2)
+ assert result == expected
+
+
+def test_intersection_equal_different_names():
+ # GH#30302
+ mi1 = MultiIndex.from_arrays([[1, 2], [3, 4]], names=["c", "b"])
+ mi2 = MultiIndex.from_arrays([[1, 2], [3, 4]], names=["a", "b"])
+
+ result = mi1.intersection(mi2)
+ expected = MultiIndex.from_arrays([[1, 2], [3, 4]], names=[None, "b"])
+ tm.assert_index_equal(result, expected)
+
+
+def test_intersection_different_names():
+ # GH#38323
+ mi = MultiIndex.from_arrays([[1], [3]], names=["c", "b"])
+ mi2 = MultiIndex.from_arrays([[1], [3]])
+ result = mi.intersection(mi2)
+ tm.assert_index_equal(result, mi2)
+
+
+def test_intersection_with_missing_values_on_both_sides(nulls_fixture):
+ # GH#38623
+ mi1 = MultiIndex.from_arrays([[3, nulls_fixture, 4, nulls_fixture], [1, 2, 4, 2]])
+ mi2 = MultiIndex.from_arrays([[3, nulls_fixture, 3], [1, 2, 4]])
+ result = mi1.intersection(mi2)
+ expected = MultiIndex.from_arrays([[3, nulls_fixture], [1, 2]])
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_with_missing_values_on_both_sides(nulls_fixture):
+ # GH#38623
+ mi1 = MultiIndex.from_arrays([[1, nulls_fixture]])
+ mi2 = MultiIndex.from_arrays([[1, nulls_fixture, 3]])
+ result = mi1.union(mi2)
+ expected = MultiIndex.from_arrays([[1, 3, nulls_fixture]])
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["float64", "Float64"])
+@pytest.mark.parametrize("sort", [None, False])
+def test_union_nan_got_duplicated(dtype, sort):
+ # GH#38977, GH#49010
+ mi1 = MultiIndex.from_arrays([pd.array([1.0, np.nan], dtype=dtype), [2, 3]])
+ mi2 = MultiIndex.from_arrays([pd.array([1.0, np.nan, 3.0], dtype=dtype), [2, 3, 4]])
+ result = mi1.union(mi2, sort=sort)
+ if sort is None:
+ expected = MultiIndex.from_arrays(
+ [pd.array([1.0, 3.0, np.nan], dtype=dtype), [2, 4, 3]]
+ )
+ else:
+ expected = mi2
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("val", [4, 1])
+def test_union_keep_ea_dtype(any_numeric_ea_dtype, val):
+ # GH#48505
+
+ arr1 = Series([val, 2], dtype=any_numeric_ea_dtype)
+ arr2 = Series([2, 1], dtype=any_numeric_ea_dtype)
+ midx = MultiIndex.from_arrays([arr1, [1, 2]], names=["a", None])
+ midx2 = MultiIndex.from_arrays([arr2, [2, 1]])
+ result = midx.union(midx2)
+ if val == 4:
+ expected = MultiIndex.from_arrays(
+ [Series([1, 2, 4], dtype=any_numeric_ea_dtype), [1, 2, 1]]
+ )
+ else:
+ expected = MultiIndex.from_arrays(
+ [Series([1, 2], dtype=any_numeric_ea_dtype), [1, 2]]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("dupe_val", [3, pd.NA])
+def test_union_with_duplicates_keep_ea_dtype(dupe_val, any_numeric_ea_dtype):
+ # GH48900
+ mi1 = MultiIndex.from_arrays(
+ [
+ Series([1, dupe_val, 2], dtype=any_numeric_ea_dtype),
+ Series([1, dupe_val, 2], dtype=any_numeric_ea_dtype),
+ ]
+ )
+ mi2 = MultiIndex.from_arrays(
+ [
+ Series([2, dupe_val, dupe_val], dtype=any_numeric_ea_dtype),
+ Series([2, dupe_val, dupe_val], dtype=any_numeric_ea_dtype),
+ ]
+ )
+ result = mi1.union(mi2)
+ expected = MultiIndex.from_arrays(
+ [
+ Series([1, 2, dupe_val, dupe_val], dtype=any_numeric_ea_dtype),
+ Series([1, 2, dupe_val, dupe_val], dtype=any_numeric_ea_dtype),
+ ]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+def test_union_duplicates(index, request):
+ # GH#38977
+ if index.empty or isinstance(index, (IntervalIndex, CategoricalIndex)):
+ pytest.skip(f"No duplicates in an empty {type(index).__name__}")
+
+ values = index.unique().values.tolist()
+ mi1 = MultiIndex.from_arrays([values, [1] * len(values)])
+ mi2 = MultiIndex.from_arrays([[values[0], *values], [1] * (len(values) + 1)])
+ result = mi2.union(mi1)
+ expected = mi2.sort_values()
+ tm.assert_index_equal(result, expected)
+
+ if (
+ is_unsigned_integer_dtype(mi2.levels[0])
+ and (mi2.get_level_values(0) < 2**63).all()
+ ):
+ # GH#47294 - union uses lib.fast_zip, converting data to Python integers
+ # and loses type information. Result is then unsigned only when values are
+ # sufficiently large to require unsigned dtype. This happens only if other
+ # has dups or one of both have missing values
+ expected = expected.set_levels(
+ [expected.levels[0].astype(np.int64), expected.levels[1]]
+ )
+ elif is_float_dtype(mi2.levels[0]):
+ # mi2 has duplicates witch is a different path than above, Fix that path
+ # to use correct float dtype?
+ expected = expected.set_levels(
+ [expected.levels[0].astype(float), expected.levels[1]]
+ )
+
+ result = mi1.union(mi2)
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_keep_dtype_precision(any_real_numeric_dtype):
+ # GH#48498
+ arr1 = Series([4, 1, 1], dtype=any_real_numeric_dtype)
+ arr2 = Series([1, 4], dtype=any_real_numeric_dtype)
+ midx = MultiIndex.from_arrays([arr1, [2, 1, 1]], names=["a", None])
+ midx2 = MultiIndex.from_arrays([arr2, [1, 2]], names=["a", None])
+
+ result = midx.union(midx2)
+ expected = MultiIndex.from_arrays(
+ ([Series([1, 1, 4], dtype=any_real_numeric_dtype), [1, 1, 2]]),
+ names=["a", None],
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_keep_ea_dtype_with_na(any_numeric_ea_dtype):
+ # GH#48498
+ arr1 = Series([4, pd.NA], dtype=any_numeric_ea_dtype)
+ arr2 = Series([1, pd.NA], dtype=any_numeric_ea_dtype)
+ midx = MultiIndex.from_arrays([arr1, [2, 1]], names=["a", None])
+ midx2 = MultiIndex.from_arrays([arr2, [1, 2]])
+ result = midx.union(midx2)
+ expected = MultiIndex.from_arrays(
+ [Series([1, 4, pd.NA, pd.NA], dtype=any_numeric_ea_dtype), [1, 2, 1, 2]]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_duplicates_different_names():
+ # GH#62059
+ mi1 = MultiIndex.from_tuples([(1, "a"), (2, "b")], names=["x", "y"])
+ mi2 = MultiIndex.from_tuples([(2, "b"), (3, "c"), (2, "b")])
+
+ result = mi1.union(mi2)
+ expected = MultiIndex.from_tuples(
+ [(1, "a"), (2, "b"), (2, "b"), (3, "c")], names=[None, None]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "levels1, levels2, codes1, codes2, names",
+ [
+ (
+ [["a", "b", "c"], [0, ""]],
+ [["c", "d", "b"], [""]],
+ [[0, 1, 2], [1, 1, 1]],
+ [[0, 1, 2], [0, 0, 0]],
+ ["name1", "name2"],
+ ),
+ ],
+)
+def test_intersection_lexsort_depth(levels1, levels2, codes1, codes2, names):
+ # GH#25169
+ mi1 = MultiIndex(levels=levels1, codes=codes1, names=names)
+ mi2 = MultiIndex(levels=levels2, codes=codes2, names=names)
+ mi_int = mi1.intersection(mi2)
+ assert mi_int._lexsort_depth == 2
+
+
+@pytest.mark.parametrize(
+ "a",
+ [pd.Categorical(["a", "b"], categories=["a", "b"]), ["a", "b"]],
+)
+@pytest.mark.parametrize("b_ordered", [True, False])
+def test_intersection_with_non_lex_sorted_categories(a, b_ordered):
+ # GH#49974
+ other = ["1", "2"]
+ b = pd.Categorical(["a", "b"], categories=["b", "a"], ordered=b_ordered)
+ df1 = DataFrame({"x": a, "y": other})
+ df2 = DataFrame({"x": b, "y": other})
+
+ expected = MultiIndex.from_arrays([a, other], names=["x", "y"])
+
+ res1 = MultiIndex.from_frame(df1).intersection(
+ MultiIndex.from_frame(df2.sort_values(["x", "y"]))
+ )
+ res2 = MultiIndex.from_frame(df1).intersection(MultiIndex.from_frame(df2))
+ res3 = MultiIndex.from_frame(df1.sort_values(["x", "y"])).intersection(
+ MultiIndex.from_frame(df2)
+ )
+ res4 = MultiIndex.from_frame(df1.sort_values(["x", "y"])).intersection(
+ MultiIndex.from_frame(df2.sort_values(["x", "y"]))
+ )
+
+ tm.assert_index_equal(res1, expected)
+ tm.assert_index_equal(res2, expected)
+ tm.assert_index_equal(res3, expected)
+ tm.assert_index_equal(res4, expected)
+
+
+@pytest.mark.parametrize("val", [pd.NA, 100])
+def test_intersection_keep_ea_dtypes(val, any_numeric_ea_dtype):
+ # GH#48604
+ midx = MultiIndex.from_arrays(
+ [Series([1, 2], dtype=any_numeric_ea_dtype), [2, 1]], names=["a", None]
+ )
+ midx2 = MultiIndex.from_arrays(
+ [Series([1, 2, val], dtype=any_numeric_ea_dtype), [1, 1, 3]]
+ )
+ result = midx.intersection(midx2)
+ expected = MultiIndex.from_arrays([Series([2], dtype=any_numeric_ea_dtype), [1]])
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_with_na_when_constructing_dataframe():
+ # GH43222
+ series1 = Series(
+ (1,),
+ index=MultiIndex.from_arrays(
+ [Series([None], dtype="str"), Series([None], dtype="str")]
+ ),
+ )
+ series2 = Series((10, 20), index=MultiIndex.from_tuples(((None, None), ("a", "b"))))
+ result = DataFrame([series1, series2])
+ expected = DataFrame({(np.nan, np.nan): [1.0, 10.0], ("a", "b"): [np.nan, 20.0]})
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_sorting.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_sorting.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d21ee8a5771642407b73180940a95df55153d0f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_sorting.py
@@ -0,0 +1,346 @@
+import numpy as np
+import pytest
+
+from pandas.errors import UnsortedIndexError
+
+from pandas import (
+ CategoricalIndex,
+ DataFrame,
+ Index,
+ MultiIndex,
+ RangeIndex,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.core.indexes.frozen import FrozenList
+
+
+def test_sortlevel(idx):
+ tuples = list(idx)
+ np.random.default_rng(2).shuffle(tuples)
+
+ index = MultiIndex.from_tuples(tuples)
+
+ sorted_idx, _ = index.sortlevel(0)
+ expected = MultiIndex.from_tuples(sorted(tuples))
+ assert sorted_idx.equals(expected)
+
+ sorted_idx, _ = index.sortlevel(0, ascending=False)
+ assert sorted_idx.equals(expected[::-1])
+
+ sorted_idx, _ = index.sortlevel(1)
+ by1 = sorted(tuples, key=lambda x: (x[1], x[0]))
+ expected = MultiIndex.from_tuples(by1)
+ assert sorted_idx.equals(expected)
+
+ sorted_idx, _ = index.sortlevel(1, ascending=False)
+ assert sorted_idx.equals(expected[::-1])
+
+
+def test_sortlevel_not_sort_remaining():
+ mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list("ABC"))
+ sorted_idx, _ = mi.sortlevel("A", sort_remaining=False)
+ assert sorted_idx.equals(mi)
+
+
+def test_sortlevel_deterministic():
+ tuples = [
+ ("bar", "one"),
+ ("foo", "two"),
+ ("qux", "two"),
+ ("foo", "one"),
+ ("baz", "two"),
+ ("qux", "one"),
+ ]
+
+ index = MultiIndex.from_tuples(tuples)
+
+ sorted_idx, _ = index.sortlevel(0)
+ expected = MultiIndex.from_tuples(sorted(tuples))
+ assert sorted_idx.equals(expected)
+
+ sorted_idx, _ = index.sortlevel(0, ascending=False)
+ assert sorted_idx.equals(expected[::-1])
+
+ sorted_idx, _ = index.sortlevel(1)
+ by1 = sorted(tuples, key=lambda x: (x[1], x[0]))
+ expected = MultiIndex.from_tuples(by1)
+ assert sorted_idx.equals(expected)
+
+ sorted_idx, _ = index.sortlevel(1, ascending=False)
+ assert sorted_idx.equals(expected[::-1])
+
+
+def test_sortlevel_na_position():
+ # GH#51612
+ midx = MultiIndex.from_tuples([(1, np.nan), (1, 1)])
+ result = midx.sortlevel(level=[0, 1], na_position="last")[0]
+ expected = MultiIndex.from_tuples([(1, 1), (1, np.nan)])
+ tm.assert_index_equal(result, expected)
+
+
+def test_numpy_argsort(idx):
+ result = np.argsort(idx)
+ expected = idx.argsort()
+ tm.assert_numpy_array_equal(result, expected)
+
+ # these are the only two types that perform
+ # pandas compatibility input validation - the
+ # rest already perform separate (or no) such
+ # validation via their 'values' attribute as
+ # defined in pandas.core.indexes/base.py - they
+ # cannot be changed at the moment due to
+ # backwards compatibility concerns
+ if isinstance(type(idx), (CategoricalIndex, RangeIndex)):
+ msg = "the 'axis' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.argsort(idx, axis=1)
+
+ msg = "the 'kind' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.argsort(idx, kind="mergesort")
+
+ msg = "the 'order' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.argsort(idx, order=("a", "b"))
+
+
+def test_unsortedindex():
+ # GH 11897
+ mi = MultiIndex.from_tuples(
+ [("z", "a"), ("x", "a"), ("y", "b"), ("x", "b"), ("y", "a"), ("z", "b")],
+ names=["one", "two"],
+ )
+ df = DataFrame([[i, 10 * i] for i in range(6)], index=mi, columns=["one", "two"])
+
+ # GH 16734: not sorted, but no real slicing
+ result = df.loc(axis=0)["z", "a"]
+ expected = df.iloc[0]
+ tm.assert_series_equal(result, expected)
+
+ msg = (
+ "MultiIndex slicing requires the index to be lexsorted: "
+ r"slicing on levels \[1\], lexsort depth 0"
+ )
+ with pytest.raises(UnsortedIndexError, match=msg):
+ df.loc(axis=0)["z", slice("a")]
+ df.sort_index(inplace=True)
+ assert len(df.loc(axis=0)["z", :]) == 2
+
+ with pytest.raises(KeyError, match="'q'"):
+ df.loc(axis=0)["q", :]
+
+
+def test_unsortedindex_doc_examples(performance_warning):
+ # https://pandas.pydata.org/pandas-docs/stable/advanced.html#sorting-a-multiindex
+ dfm = DataFrame(
+ {
+ "jim": [0, 0, 1, 1],
+ "joe": ["x", "x", "z", "y"],
+ "jolie": np.random.default_rng(2).random(4),
+ }
+ )
+
+ dfm = dfm.set_index(["jim", "joe"])
+ with tm.assert_produces_warning(performance_warning):
+ dfm.loc[(1, "z")]
+
+ msg = r"Key length \(2\) was greater than MultiIndex lexsort depth \(1\)"
+ with pytest.raises(UnsortedIndexError, match=msg):
+ dfm.loc[(0, "y") : (1, "z")]
+
+ assert not dfm.index._is_lexsorted()
+ assert dfm.index._lexsort_depth == 1
+
+ # sort it
+ dfm = dfm.sort_index()
+ dfm.loc[(1, "z")]
+ dfm.loc[(0, "y") : (1, "z")]
+
+ assert dfm.index._is_lexsorted()
+ assert dfm.index._lexsort_depth == 2
+
+
+def test_reconstruct_sort():
+ # starts off lexsorted & monotonic
+ mi = MultiIndex.from_arrays([["A", "A", "B", "B", "B"], [1, 2, 1, 2, 3]])
+ assert mi.is_monotonic_increasing
+ recons = mi._sort_levels_monotonic()
+ assert recons.is_monotonic_increasing
+ assert mi is recons
+
+ assert mi.equals(recons)
+ assert Index(mi.values).equals(Index(recons.values))
+
+ # cannot convert to lexsorted
+ mi = MultiIndex.from_tuples(
+ [("z", "a"), ("x", "a"), ("y", "b"), ("x", "b"), ("y", "a"), ("z", "b")],
+ names=["one", "two"],
+ )
+ assert not mi.is_monotonic_increasing
+ recons = mi._sort_levels_monotonic()
+ assert not recons.is_monotonic_increasing
+ assert mi.equals(recons)
+ assert Index(mi.values).equals(Index(recons.values))
+
+ # cannot convert to lexsorted
+ mi = MultiIndex(
+ levels=[["b", "d", "a"], [1, 2, 3]],
+ codes=[[0, 1, 0, 2], [2, 0, 0, 1]],
+ names=["col1", "col2"],
+ )
+ assert not mi.is_monotonic_increasing
+ recons = mi._sort_levels_monotonic()
+ assert not recons.is_monotonic_increasing
+ assert mi.equals(recons)
+ assert Index(mi.values).equals(Index(recons.values))
+
+
+def test_reconstruct_remove_unused():
+ # xref to GH 2770
+ df = DataFrame(
+ [["deleteMe", 1, 9], ["keepMe", 2, 9], ["keepMeToo", 3, 9]],
+ columns=["first", "second", "third"],
+ )
+ df2 = df.set_index(["first", "second"], drop=False)
+ df2 = df2[df2["first"] != "deleteMe"]
+
+ # removed levels are there
+ expected = MultiIndex(
+ levels=[["deleteMe", "keepMe", "keepMeToo"], [1, 2, 3]],
+ codes=[[1, 2], [1, 2]],
+ names=["first", "second"],
+ )
+ result = df2.index
+ tm.assert_index_equal(result, expected)
+
+ expected = MultiIndex(
+ levels=[["keepMe", "keepMeToo"], [2, 3]],
+ codes=[[0, 1], [0, 1]],
+ names=["first", "second"],
+ )
+ result = df2.index.remove_unused_levels()
+ tm.assert_index_equal(result, expected)
+
+ # idempotent
+ result2 = result.remove_unused_levels()
+ tm.assert_index_equal(result2, expected)
+ assert result2.is_(result)
+
+
+@pytest.mark.parametrize(
+ "first_type,second_type", [("int64", "int64"), ("datetime64[D]", "str")]
+)
+def test_remove_unused_levels_large(first_type, second_type):
+ # GH16556
+
+ # because tests should be deterministic (and this test in particular
+ # checks that levels are removed, which is not the case for every
+ # random input):
+ rng = np.random.default_rng(10) # seed is arbitrary value that works
+
+ size = 1 << 16
+ df = DataFrame(
+ {
+ "first": rng.integers(0, 1 << 13, size).astype(first_type),
+ "second": rng.integers(0, 1 << 10, size).astype(second_type),
+ "third": rng.random(size),
+ }
+ )
+ df = df.groupby(["first", "second"]).sum()
+ df = df[df.third < 0.1]
+
+ result = df.index.remove_unused_levels()
+ assert len(result.levels[0]) < len(df.index.levels[0])
+ assert len(result.levels[1]) < len(df.index.levels[1])
+ assert result.equals(df.index)
+
+ expected = df.reset_index().set_index(["first", "second"]).index
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("level0", [["a", "d", "b"], ["a", "d", "b", "unused"]])
+@pytest.mark.parametrize(
+ "level1", [["w", "x", "y", "z"], ["w", "x", "y", "z", "unused"]]
+)
+def test_remove_unused_nan(level0, level1):
+ # GH 18417
+ mi = MultiIndex(levels=[level0, level1], codes=[[0, 2, -1, 1, -1], [0, 1, 2, 3, 2]])
+
+ result = mi.remove_unused_levels()
+ tm.assert_index_equal(result, mi)
+ for level in 0, 1:
+ assert "unused" not in result.levels[level]
+
+
+def test_argsort(idx):
+ result = idx.argsort()
+ expected = idx.values.argsort()
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_remove_unused_levels_with_nan():
+ # GH 37510
+ idx = Index([(1, np.nan), (3, 4)]).rename(["id1", "id2"])
+ idx = idx.set_levels(["a", np.nan], level="id1")
+ idx = idx.remove_unused_levels()
+ result = idx.levels
+ expected = FrozenList([["a", np.nan], [4]])
+ assert str(result) == str(expected)
+
+
+def test_sort_values_nan():
+ # GH48495, GH48626
+ midx = MultiIndex(levels=[["A", "B", "C"], ["D"]], codes=[[1, 0, 2], [-1, -1, 0]])
+ result = midx.sort_values()
+ expected = MultiIndex(
+ levels=[["A", "B", "C"], ["D"]], codes=[[0, 1, 2], [-1, -1, 0]]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_sort_values_incomparable():
+ # GH48495
+ mi = MultiIndex.from_arrays(
+ [
+ [1, Timestamp("2000-01-01")],
+ [3, 4],
+ ]
+ )
+ match = "'<' not supported between instances of 'Timestamp' and 'int'"
+ with pytest.raises(TypeError, match=match):
+ mi.sort_values()
+
+
+@pytest.mark.parametrize("na_position", ["first", "last"])
+@pytest.mark.parametrize("dtype", ["float64", "Int64", "Float64"])
+def test_sort_values_with_na_na_position(dtype, na_position):
+ # 51612
+ arrays = [
+ Series([1, 1, 2], dtype=dtype),
+ Series([1, None, 3], dtype=dtype),
+ ]
+ index = MultiIndex.from_arrays(arrays)
+ result = index.sort_values(na_position=na_position)
+ if na_position == "first":
+ arrays = [
+ Series([1, 1, 2], dtype=dtype),
+ Series([None, 1, 3], dtype=dtype),
+ ]
+ else:
+ arrays = [
+ Series([1, 1, 2], dtype=dtype),
+ Series([1, None, 3], dtype=dtype),
+ ]
+ expected = MultiIndex.from_arrays(arrays)
+ tm.assert_index_equal(result, expected)
+
+
+def test_sort_unnecessary_warning():
+ # GH#55386
+ midx = MultiIndex.from_tuples([(1.5, 2), (3.5, 3), (0, 1)])
+ midx = midx.set_levels([2.5, np.nan, 1], level=0)
+ result = midx.sort_values()
+ expected = MultiIndex.from_tuples([(1, 3), (2.5, 1), (np.nan, 2)])
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_take.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_take.py
new file mode 100644
index 0000000000000000000000000000000000000000..543cba25c373b71b8c79c7fe0ea5ae2fb7f40b18
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_take.py
@@ -0,0 +1,78 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+def test_take(idx):
+ indexer = [4, 3, 0, 2]
+ result = idx.take(indexer)
+ expected = idx[indexer]
+ assert result.equals(expected)
+
+ # GH 10791
+ msg = "'MultiIndex' object has no attribute 'freq'"
+ with pytest.raises(AttributeError, match=msg):
+ idx.freq
+
+
+def test_take_invalid_kwargs(idx):
+ indices = [1, 2]
+
+ msg = r"take\(\) got an unexpected keyword argument 'foo'"
+ with pytest.raises(TypeError, match=msg):
+ idx.take(indices, foo=2)
+
+ msg = "the 'out' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, out=indices)
+
+ msg = "the 'mode' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, mode="clip")
+
+
+def test_take_fill_value():
+ # GH 12631
+ vals = [["A", "B"], [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")]]
+ idx = pd.MultiIndex.from_product(vals, names=["str", "dt"])
+
+ result = idx.take(np.array([1, 0, -1]))
+ exp_vals = [
+ ("A", pd.Timestamp("2011-01-02")),
+ ("A", pd.Timestamp("2011-01-01")),
+ ("B", pd.Timestamp("2011-01-02")),
+ ]
+ expected = pd.MultiIndex.from_tuples(exp_vals, names=["str", "dt"])
+ tm.assert_index_equal(result, expected)
+
+ # fill_value
+ result = idx.take(np.array([1, 0, -1]), fill_value=True)
+ exp_vals = [
+ ("A", pd.Timestamp("2011-01-02")),
+ ("A", pd.Timestamp("2011-01-01")),
+ (np.nan, pd.NaT),
+ ]
+ expected = pd.MultiIndex.from_tuples(exp_vals, names=["str", "dt"])
+ tm.assert_index_equal(result, expected)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ exp_vals = [
+ ("A", pd.Timestamp("2011-01-02")),
+ ("A", pd.Timestamp("2011-01-01")),
+ ("B", pd.Timestamp("2011-01-02")),
+ ]
+ expected = pd.MultiIndex.from_tuples(exp_vals, names=["str", "dt"])
+ tm.assert_index_equal(result, expected)
+
+ msg = "When allow_fill=True and fill_value is not None, all indices must be >= -1"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ msg = "index -5 is out of bounds for( axis 0 with)? size 4"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_util.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_util.py
new file mode 100644
index 0000000000000000000000000000000000000000..68792ce53f04e62b3b1f11cb3fa921987a5ef162
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/multi/test_util.py
@@ -0,0 +1,63 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.core.indexes.multi import cartesian_product
+
+
+class TestCartesianProduct:
+ def test_simple(self):
+ x, y = list("ABC"), [1, 22]
+ result1, result2 = cartesian_product([x, y])
+ expected1 = np.array(["A", "A", "B", "B", "C", "C"])
+ expected2 = np.array([1, 22, 1, 22, 1, 22])
+ tm.assert_numpy_array_equal(result1, expected1)
+ tm.assert_numpy_array_equal(result2, expected2)
+
+ def test_datetimeindex(self):
+ # regression test for GitHub issue #6439
+ # make sure that the ordering on datetimeindex is consistent
+ x = date_range("2000-01-01", periods=2)
+ result1, result2 = (Index(y).day for y in cartesian_product([x, x]))
+ expected1 = Index([1, 1, 2, 2], dtype=np.int32)
+ expected2 = Index([1, 2, 1, 2], dtype=np.int32)
+ tm.assert_index_equal(result1, expected1)
+ tm.assert_index_equal(result2, expected2)
+
+ @pytest.mark.parametrize("x, y", [[[], []], [[0, 1], []], [[], ["a", "b", "c"]]])
+ def test_empty(self, x, y):
+ # product of empty factors
+ expected1 = np.array([], dtype=np.asarray(x).dtype)
+ expected2 = np.array([], dtype=np.asarray(y).dtype)
+ result1, result2 = cartesian_product([x, y])
+ tm.assert_numpy_array_equal(result1, expected1)
+ tm.assert_numpy_array_equal(result2, expected2)
+
+ def test_empty_input(self):
+ # empty product (empty input):
+ result = cartesian_product([])
+ expected = []
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "X", [1, [1], [1, 2], [[1], 2], "a", ["a"], ["a", "b"], [["a"], "b"]]
+ )
+ def test_invalid_input(self, X):
+ msg = "Input must be a list-like of list-likes"
+
+ with pytest.raises(TypeError, match=msg):
+ cartesian_product(X=X)
+
+ def test_exceed_product_space(self):
+ # GH31355: raise useful error when produce space is too large
+ msg = "Product space too large to allocate arrays!"
+
+ dims = [np.arange(0, 22, dtype=np.int16) for i in range(12)] + [
+ (np.arange(15128, dtype=np.int16)),
+ ]
+ with pytest.raises(ValueError, match=msg):
+ cartesian_product(X=dims)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c2df6008de5d85789b026e947ac27a8036a9be7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_astype.py
@@ -0,0 +1,95 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ to_datetime,
+ to_timedelta,
+)
+import pandas._testing as tm
+
+
+class TestAstype:
+ def test_astype_float64_to_uint64(self):
+ # GH#45309 used to incorrectly return Index with int64 dtype
+ idx = Index([0.0, 5.0, 10.0, 15.0, 20.0], dtype=np.float64)
+ result = idx.astype("u8")
+ expected = Index([0, 5, 10, 15, 20], dtype=np.uint64)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ idx_with_negatives = idx - 10
+ with pytest.raises(ValueError, match="losslessly"):
+ idx_with_negatives.astype(np.uint64)
+
+ def test_astype_float64_to_object(self):
+ float_index = Index([0.0, 2.5, 5.0, 7.5, 10.0], dtype=np.float64)
+ result = float_index.astype(object)
+ assert result.equals(float_index)
+ assert float_index.equals(result)
+ assert isinstance(result, Index) and result.dtype == object
+
+ def test_astype_float64_mixed_to_object(self):
+ # mixed int-float
+ idx = Index([1.5, 2, 3, 4, 5], dtype=np.float64)
+ idx.name = "foo"
+ result = idx.astype(object)
+ assert result.equals(idx)
+ assert idx.equals(result)
+ assert isinstance(result, Index) and result.dtype == object
+
+ @pytest.mark.parametrize("dtype", ["int16", "int32", "int64"])
+ def test_astype_float64_to_int_dtype(self, dtype):
+ # GH#12881
+ # a float astype int
+ idx = Index([0, 1, 2], dtype=np.float64)
+ result = idx.astype(dtype)
+ expected = Index([0, 1, 2], dtype=dtype)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ idx = Index([0, 1.1, 2], dtype=np.float64)
+ result = idx.astype(dtype)
+ expected = Index([0, 1, 2], dtype=dtype)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ @pytest.mark.parametrize("dtype", ["float32", "float64"])
+ def test_astype_float64_to_float_dtype(self, dtype):
+ # GH#12881
+ # a float astype int
+ idx = Index([0, 1, 2], dtype=np.float64)
+ result = idx.astype(dtype)
+ assert isinstance(result, Index) and result.dtype == dtype
+
+ @pytest.mark.parametrize("dtype", ["M8[ns]", "m8[ns]"])
+ def test_astype_float_to_datetimelike(self, dtype):
+ # GH#49660 pre-2.0 Index.astype from floating to M8/m8/Period raised,
+ # inconsistent with Series.astype
+ idx = Index([0, 1.1, 2], dtype=np.float64)
+
+ result = idx.astype(dtype)
+ if dtype[0] == "M":
+ expected = to_datetime(idx.values)
+ else:
+ expected = to_timedelta(idx.values)
+ tm.assert_index_equal(result, expected)
+
+ # check that we match Series behavior
+ result = idx.to_series().set_axis(range(3)).astype(dtype)
+ expected = expected.to_series().set_axis(range(3))
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", [int, "int16", "int32", "int64"])
+ @pytest.mark.parametrize("non_finite", [np.inf, np.nan])
+ def test_cannot_cast_inf_to_int(self, non_finite, dtype):
+ # GH#13149
+ idx = Index([1, 2, non_finite], dtype=np.float64)
+
+ msg = r"Cannot convert non-finite values \(NA or inf\) to integer"
+ with pytest.raises(ValueError, match=msg):
+ idx.astype(dtype)
+
+ def test_astype_from_object(self):
+ index = Index([1.0, np.nan, 0.2], dtype="object")
+ result = index.astype(float)
+ expected = Index([1.0, np.nan, 0.2], dtype=np.float64)
+ assert result.dtype == expected.dtype
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..7858ce6f71b493581cbf4b94eacc8890fa552911
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_indexing.py
@@ -0,0 +1,623 @@
+import numpy as np
+import pytest
+
+from pandas.errors import InvalidIndexError
+
+from pandas import (
+ NA,
+ Index,
+ RangeIndex,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.core.arrays import (
+ ArrowExtensionArray,
+ FloatingArray,
+)
+
+
+class TestGetLoc:
+ def test_get_loc(self):
+ index = Index([0, 1, 2])
+ assert index.get_loc(1) == 1
+
+ def test_get_loc_raises_bad_label(self):
+ index = Index([0, 1, 2])
+ with pytest.raises(InvalidIndexError, match=r"\[1, 2\]"):
+ index.get_loc([1, 2])
+
+ def test_get_loc_float64(self):
+ idx = Index([0.0, 1.0, 2.0], dtype=np.float64)
+
+ with pytest.raises(KeyError, match="^'foo'$"):
+ idx.get_loc("foo")
+ with pytest.raises(KeyError, match=r"^1\.5$"):
+ idx.get_loc(1.5)
+ with pytest.raises(KeyError, match="^True$"):
+ idx.get_loc(True)
+ with pytest.raises(KeyError, match="^False$"):
+ idx.get_loc(False)
+
+ def test_get_loc_na(self):
+ idx = Index([np.nan, 1, 2], dtype=np.float64)
+ assert idx.get_loc(1) == 1
+ assert idx.get_loc(np.nan) == 0
+
+ idx = Index([np.nan, 1, np.nan], dtype=np.float64)
+ assert idx.get_loc(1) == 1
+
+ # representable by slice [0:2:2]
+ msg = "'Cannot get left slice bound for non-unique label: nan'"
+ with pytest.raises(KeyError, match=msg):
+ idx.slice_locs(np.nan)
+ # not representable by slice
+ idx = Index([np.nan, 1, np.nan, np.nan], dtype=np.float64)
+ assert idx.get_loc(1) == 1
+ msg = "'Cannot get left slice bound for non-unique label: nan"
+ with pytest.raises(KeyError, match=msg):
+ idx.slice_locs(np.nan)
+
+ def test_get_loc_missing_nan(self):
+ # GH#8569
+ idx = Index([1, 2], dtype=np.float64)
+ assert idx.get_loc(1) == 0
+ with pytest.raises(KeyError, match=r"^3$"):
+ idx.get_loc(3)
+ with pytest.raises(KeyError, match="^nan$"):
+ idx.get_loc(np.nan)
+ with pytest.raises(InvalidIndexError, match=r"\[nan\]"):
+ # listlike/non-hashable raises TypeError
+ idx.get_loc([np.nan])
+
+ @pytest.mark.parametrize("vals", [[1], [1.0], [Timestamp("2019-12-31")], ["test"]])
+ def test_get_loc_float_index_nan_with_method(self, vals):
+ # GH#39382
+ idx = Index(vals)
+ with pytest.raises(KeyError, match="nan"):
+ idx.get_loc(np.nan)
+
+ @pytest.mark.parametrize("dtype", ["f8", "i8", "u8"])
+ def test_get_loc_numericindex_none_raises(self, dtype):
+ # case that goes through searchsorted and key is non-comparable to values
+ arr = np.arange(10**7, dtype=dtype)
+ idx = Index(arr)
+ with pytest.raises(KeyError, match="None"):
+ idx.get_loc(None)
+
+ def test_get_loc_overflows(self):
+ # unique but non-monotonic goes through IndexEngine.mapping.get_item
+ idx = Index([0, 2, 1])
+
+ val = np.iinfo(np.int64).max + 1
+
+ with pytest.raises(KeyError, match=str(val)):
+ idx.get_loc(val)
+ with pytest.raises(KeyError, match=str(val)):
+ idx._engine.get_loc(val)
+
+
+class TestGetIndexer:
+ def test_get_indexer(self):
+ index1 = Index([1, 2, 3, 4, 5])
+ index2 = Index([2, 4, 6])
+
+ r1 = index1.get_indexer(index2)
+ e1 = np.array([1, 3, -1], dtype=np.intp)
+ tm.assert_almost_equal(r1, e1)
+
+ @pytest.mark.parametrize("reverse", [True, False])
+ @pytest.mark.parametrize(
+ "expected,method",
+ [
+ ([-1, 0, 0, 1, 1], "pad"),
+ ([-1, 0, 0, 1, 1], "ffill"),
+ ([0, 0, 1, 1, 2], "backfill"),
+ ([0, 0, 1, 1, 2], "bfill"),
+ ],
+ )
+ def test_get_indexer_methods(self, reverse, expected, method):
+ index1 = Index([1, 2, 3, 4, 5])
+ index2 = Index([2, 4, 6])
+ expected = np.array(expected, dtype=np.intp)
+ if reverse:
+ index1 = index1[::-1]
+ expected = expected[::-1]
+
+ result = index2.get_indexer(index1, method=method)
+ tm.assert_almost_equal(result, expected)
+
+ def test_get_indexer_invalid(self):
+ # GH10411
+ index = Index(np.arange(10))
+
+ with pytest.raises(ValueError, match="tolerance argument"):
+ index.get_indexer([1, 0], tolerance=1)
+
+ with pytest.raises(ValueError, match="limit argument"):
+ index.get_indexer([1, 0], limit=1)
+
+ @pytest.mark.parametrize(
+ "method, tolerance, indexer, expected",
+ [
+ ("pad", None, [0, 5, 9], [0, 5, 9]),
+ ("backfill", None, [0, 5, 9], [0, 5, 9]),
+ ("nearest", None, [0, 5, 9], [0, 5, 9]),
+ ("pad", 0, [0, 5, 9], [0, 5, 9]),
+ ("backfill", 0, [0, 5, 9], [0, 5, 9]),
+ ("nearest", 0, [0, 5, 9], [0, 5, 9]),
+ ("pad", None, [0.2, 1.8, 8.5], [0, 1, 8]),
+ ("backfill", None, [0.2, 1.8, 8.5], [1, 2, 9]),
+ ("nearest", None, [0.2, 1.8, 8.5], [0, 2, 9]),
+ ("pad", 1, [0.2, 1.8, 8.5], [0, 1, 8]),
+ ("backfill", 1, [0.2, 1.8, 8.5], [1, 2, 9]),
+ ("nearest", 1, [0.2, 1.8, 8.5], [0, 2, 9]),
+ ("pad", 0.2, [0.2, 1.8, 8.5], [0, -1, -1]),
+ ("backfill", 0.2, [0.2, 1.8, 8.5], [-1, 2, -1]),
+ ("nearest", 0.2, [0.2, 1.8, 8.5], [0, 2, -1]),
+ ],
+ )
+ def test_get_indexer_nearest(self, method, tolerance, indexer, expected):
+ index = Index(np.arange(10))
+
+ actual = index.get_indexer(indexer, method=method, tolerance=tolerance)
+ tm.assert_numpy_array_equal(actual, np.array(expected, dtype=np.intp))
+
+ @pytest.mark.parametrize("listtype", [list, tuple, Series, np.array])
+ @pytest.mark.parametrize(
+ "tolerance, expected",
+ [
+ [[0.3, 0.3, 0.1], [0, 2, -1]],
+ [[0.2, 0.1, 0.1], [0, -1, -1]],
+ [[0.1, 0.5, 0.5], [-1, 2, 9]],
+ ],
+ )
+ def test_get_indexer_nearest_listlike_tolerance(
+ self, tolerance, expected, listtype
+ ):
+ index = Index(np.arange(10))
+
+ actual = index.get_indexer(
+ [0.2, 1.8, 8.5], method="nearest", tolerance=listtype(tolerance)
+ )
+ tm.assert_numpy_array_equal(actual, np.array(expected, dtype=np.intp))
+
+ def test_get_indexer_nearest_error(self):
+ index = Index(np.arange(10))
+ with pytest.raises(ValueError, match="limit argument"):
+ index.get_indexer([1, 0], method="nearest", limit=1)
+
+ with pytest.raises(ValueError, match="tolerance size must match"):
+ index.get_indexer([1, 0], method="nearest", tolerance=[1, 2, 3])
+
+ @pytest.mark.parametrize(
+ "method,expected",
+ [("pad", [8, 7, 0]), ("backfill", [9, 8, 1]), ("nearest", [9, 7, 0])],
+ )
+ def test_get_indexer_nearest_decreasing(self, method, expected):
+ index = Index(np.arange(10))[::-1]
+
+ actual = index.get_indexer([0, 5, 9], method=method)
+ tm.assert_numpy_array_equal(actual, np.array([9, 4, 0], dtype=np.intp))
+
+ actual = index.get_indexer([0.2, 1.8, 8.5], method=method)
+ tm.assert_numpy_array_equal(actual, np.array(expected, dtype=np.intp))
+
+ @pytest.mark.parametrize("idx_dtype", ["int64", "float64", "uint64", "range"])
+ @pytest.mark.parametrize("method", ["get_indexer", "get_indexer_non_unique"])
+ def test_get_indexer_numeric_index_boolean_target(self, method, idx_dtype):
+ # GH 16877
+
+ if idx_dtype == "range":
+ numeric_index = RangeIndex(4)
+ else:
+ numeric_index = Index(np.arange(4, dtype=idx_dtype))
+
+ other = Index([True, False, True])
+
+ result = getattr(numeric_index, method)(other)
+ expected = np.array([-1, -1, -1], dtype=np.intp)
+ if method == "get_indexer":
+ tm.assert_numpy_array_equal(result, expected)
+ else:
+ missing = np.arange(3, dtype=np.intp)
+ tm.assert_numpy_array_equal(result[0], expected)
+ tm.assert_numpy_array_equal(result[1], missing)
+
+ @pytest.mark.parametrize("method", ["pad", "backfill", "nearest"])
+ def test_get_indexer_with_method_numeric_vs_bool(self, method):
+ left = Index([1, 2, 3])
+ right = Index([True, False])
+
+ with pytest.raises(TypeError, match="Cannot compare"):
+ left.get_indexer(right, method=method)
+
+ with pytest.raises(TypeError, match="Cannot compare"):
+ right.get_indexer(left, method=method)
+
+ def test_get_indexer_numeric_vs_bool(self):
+ left = Index([1, 2, 3])
+ right = Index([True, False])
+
+ res = left.get_indexer(right)
+ expected = -1 * np.ones(len(right), dtype=np.intp)
+ tm.assert_numpy_array_equal(res, expected)
+
+ res = right.get_indexer(left)
+ expected = -1 * np.ones(len(left), dtype=np.intp)
+ tm.assert_numpy_array_equal(res, expected)
+
+ res = left.get_indexer_non_unique(right)[0]
+ expected = -1 * np.ones(len(right), dtype=np.intp)
+ tm.assert_numpy_array_equal(res, expected)
+
+ res = right.get_indexer_non_unique(left)[0]
+ expected = -1 * np.ones(len(left), dtype=np.intp)
+ tm.assert_numpy_array_equal(res, expected)
+
+ def test_get_indexer_float64(self):
+ idx = Index([0.0, 1.0, 2.0], dtype=np.float64)
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(idx), np.array([0, 1, 2], dtype=np.intp)
+ )
+
+ target = [-0.1, 0.5, 1.1]
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "pad"), np.array([-1, 0, 1], dtype=np.intp)
+ )
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "backfill"), np.array([0, 1, 2], dtype=np.intp)
+ )
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "nearest"), np.array([0, 1, 1], dtype=np.intp)
+ )
+
+ def test_get_indexer_nan(self):
+ # GH#7820
+ result = Index([1, 2, np.nan], dtype=np.float64).get_indexer([np.nan])
+ expected = np.array([2], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer_int64(self):
+ index = Index(range(0, 20, 2), dtype=np.int64)
+ target = Index(np.arange(10), dtype=np.int64)
+ indexer = index.get_indexer(target)
+ expected = np.array([0, -1, 1, -1, 2, -1, 3, -1, 4, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+ target = Index(np.arange(10), dtype=np.int64)
+ indexer = index.get_indexer(target, method="pad")
+ expected = np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+ target = Index(np.arange(10), dtype=np.int64)
+ indexer = index.get_indexer(target, method="backfill")
+ expected = np.array([0, 1, 1, 2, 2, 3, 3, 4, 4, 5], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+ def test_get_indexer_uint64(self):
+ index_large = Index(
+ [2**63, 2**63 + 10, 2**63 + 15, 2**63 + 20, 2**63 + 25],
+ dtype=np.uint64,
+ )
+ target = Index(np.arange(10).astype("uint64") * 5 + 2**63)
+ indexer = index_large.get_indexer(target)
+ expected = np.array([0, -1, 1, 2, 3, 4, -1, -1, -1, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+ target = Index(np.arange(10).astype("uint64") * 5 + 2**63)
+ indexer = index_large.get_indexer(target, method="pad")
+ expected = np.array([0, 0, 1, 2, 3, 4, 4, 4, 4, 4], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+ target = Index(np.arange(10).astype("uint64") * 5 + 2**63)
+ indexer = index_large.get_indexer(target, method="backfill")
+ expected = np.array([0, 1, 1, 2, 3, 4, -1, -1, -1, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+ @pytest.mark.parametrize("val, val2", [(4, 5), (4, 4), (4, NA), (NA, NA)])
+ def test_get_loc_masked(self, val, val2, any_numeric_ea_and_arrow_dtype):
+ # GH#39133
+ idx = Index([1, 2, 3, val, val2], dtype=any_numeric_ea_and_arrow_dtype)
+ result = idx.get_loc(2)
+ assert result == 1
+
+ with pytest.raises(KeyError, match="9"):
+ idx.get_loc(9)
+
+ def test_get_loc_masked_na(self, any_numeric_ea_and_arrow_dtype):
+ # GH#39133
+ idx = Index([1, 2, NA], dtype=any_numeric_ea_and_arrow_dtype)
+ result = idx.get_loc(NA)
+ assert result == 2
+
+ idx = Index([1, 2, NA, NA], dtype=any_numeric_ea_and_arrow_dtype)
+ result = idx.get_loc(NA)
+ tm.assert_numpy_array_equal(result, np.array([False, False, True, True]))
+
+ idx = Index([1, 2, 3], dtype=any_numeric_ea_and_arrow_dtype)
+ with pytest.raises(KeyError, match="NA"):
+ idx.get_loc(NA)
+
+ def test_get_loc_masked_na_and_nan(self, using_nan_is_na):
+ # GH#39133
+ mask = np.array([False, False, True, False])
+ if using_nan_is_na:
+ mask[-1] = True
+
+ idx = Index(FloatingArray(np.array([1, 2, 1, np.nan]), mask=mask))
+ if using_nan_is_na:
+ # NaN and NA are consistently treated as the same
+ result = idx.get_loc(NA)
+ expected = np.array([False, False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+ result = idx.get_loc(np.nan)
+ tm.assert_numpy_array_equal(result, expected)
+ else:
+ result = idx.get_loc(NA)
+ assert result == 2
+ result = idx.get_loc(np.nan)
+ assert result == 3
+
+ idx = Index(
+ FloatingArray(np.array([1, 2, 1.0]), mask=np.array([False, False, True]))
+ )
+ result = idx.get_loc(NA)
+ assert result == 2
+ if using_nan_is_na:
+ result = idx.get_loc(np.nan)
+ assert result == 2
+ else:
+ with pytest.raises(KeyError, match="nan"):
+ idx.get_loc(np.nan)
+
+ mask = np.array([False, False, False])
+ if using_nan_is_na:
+ mask[-1] = True
+ idx = Index(FloatingArray(np.array([1, 2, np.nan]), mask=mask))
+ result = idx.get_loc(np.nan)
+ assert result == 2
+ if using_nan_is_na:
+ result = idx.get_loc(NA)
+ assert result == 2
+ else:
+ with pytest.raises(KeyError, match="NA"):
+ idx.get_loc(NA)
+
+ @pytest.mark.parametrize("val", [4, 2])
+ def test_get_indexer_masked_na(self, any_numeric_ea_and_arrow_dtype, val):
+ # GH#39133
+ idx = Index([1, 2, NA, 3, val], dtype=any_numeric_ea_and_arrow_dtype)
+ result = idx.get_indexer_for([1, NA, 5])
+ expected = np.array([0, 2, -1])
+ tm.assert_numpy_array_equal(result, expected, check_dtype=False)
+
+ @pytest.mark.parametrize("dtype", ["boolean", "bool[pyarrow]"])
+ def test_get_indexer_masked_na_boolean(self, dtype):
+ # GH#39133
+ if dtype == "bool[pyarrow]":
+ pytest.importorskip("pyarrow")
+ idx = Index([True, False, NA], dtype=dtype)
+ result = idx.get_loc(False)
+ assert result == 1
+ result = idx.get_loc(NA)
+ assert result == 2
+
+ def test_get_indexer_arrow_dictionary_target(self):
+ pa = pytest.importorskip("pyarrow")
+ target = Index(
+ ArrowExtensionArray(
+ pa.array([1, 2], type=pa.dictionary(pa.int8(), pa.int8()))
+ )
+ )
+ idx = Index([1])
+
+ result = idx.get_indexer(target)
+ expected = np.array([0, -1], dtype=np.int64)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result_1, result_2 = idx.get_indexer_non_unique(target)
+ expected_1, expected_2 = (
+ np.array([0, -1], dtype=np.int64),
+ np.array([1], dtype=np.int64),
+ )
+ tm.assert_numpy_array_equal(result_1, expected_1)
+ tm.assert_numpy_array_equal(result_2, expected_2)
+
+
+class TestWhere:
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(np.arange(5, dtype="float64")),
+ Index(range(0, 20, 2), dtype=np.int64),
+ Index(np.arange(5, dtype="uint64")),
+ ],
+ )
+ def test_where(self, listlike_box, index):
+ cond = [True] * len(index)
+ expected = index
+ result = index.where(listlike_box(cond))
+
+ cond = [False] + [True] * (len(index) - 1)
+ expected = Index([index._na_value, *index[1:].tolist()], dtype=np.float64)
+ result = index.where(listlike_box(cond))
+ tm.assert_index_equal(result, expected)
+
+ def test_where_uint64(self):
+ idx = Index([0, 6, 2], dtype=np.uint64)
+ mask = np.array([False, True, False])
+ other = np.array([1], dtype=np.int64)
+
+ expected = Index([1, 6, 1], dtype=np.uint64)
+
+ result = idx.where(mask, other)
+ tm.assert_index_equal(result, expected)
+
+ result = idx.putmask(~mask, other)
+ tm.assert_index_equal(result, expected)
+
+ def test_where_infers_type_instead_of_trying_to_convert_string_to_float(self):
+ # GH 32413
+ index = Index([1, np.nan])
+ cond = index.notna()
+ other = Index(["a", "b"], dtype="string")
+
+ expected = Index([1.0, "b"])
+ result = index.where(cond, other)
+
+ tm.assert_index_equal(result, expected)
+
+
+class TestTake:
+ @pytest.mark.parametrize("idx_dtype", [np.float64, np.int64, np.uint64])
+ def test_take_preserve_name(self, idx_dtype):
+ index = Index([1, 2, 3, 4], dtype=idx_dtype, name="foo")
+ taken = index.take([3, 0, 1])
+ assert index.name == taken.name
+
+ def test_take_fill_value_float64(self):
+ # GH 12631
+ idx = Index([1.0, 2.0, 3.0], name="xxx", dtype=np.float64)
+ result = idx.take(np.array([1, 0, -1]))
+ expected = Index([2.0, 1.0, 3.0], dtype=np.float64, name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ # fill_value
+ result = idx.take(np.array([1, 0, -1]), fill_value=True)
+ expected = Index([2.0, 1.0, np.nan], dtype=np.float64, name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = Index([2.0, 1.0, 3.0], dtype=np.float64, name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ msg = (
+ "When allow_fill=True and fill_value is not None, all indices must be >= -1"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ msg = "index -5 is out of bounds for (axis 0 with )?size 3"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
+
+ @pytest.mark.parametrize("dtype", [np.int64, np.uint64])
+ def test_take_fill_value_ints(self, dtype):
+ # see gh-12631
+ idx = Index([1, 2, 3], dtype=dtype, name="xxx")
+ result = idx.take(np.array([1, 0, -1]))
+ expected = Index([2, 1, 3], dtype=dtype, name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ name = type(idx).__name__
+ msg = f"Unable to fill values because {name} cannot contain NA"
+
+ # fill_value=True
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -1]), fill_value=True)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = Index([2, 1, 3], dtype=dtype, name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ msg = "index -5 is out of bounds for (axis 0 with )?size 3"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
+
+
+class TestContains:
+ @pytest.mark.parametrize("dtype", [np.float64, np.int64, np.uint64])
+ def test_contains_none(self, dtype):
+ # GH#35788 should return False, not raise TypeError
+ index = Index([0, 1, 2, 3, 4], dtype=dtype)
+ assert None not in index
+
+ def test_contains_float64_nans(self):
+ index = Index([1.0, 2.0, np.nan], dtype=np.float64)
+ assert np.nan in index
+
+ def test_contains_float64_not_nans(self):
+ index = Index([1.0, 2.0, np.nan], dtype=np.float64)
+ assert 1.0 in index
+
+
+class TestSliceLocs:
+ @pytest.mark.parametrize("dtype", [int, float])
+ def test_slice_locs(self, dtype):
+ index = Index(np.array([0, 1, 2, 5, 6, 7, 9, 10], dtype=dtype))
+ n = len(index)
+
+ assert index.slice_locs(start=2) == (2, n)
+ assert index.slice_locs(start=3) == (3, n)
+ assert index.slice_locs(3, 8) == (3, 6)
+ assert index.slice_locs(5, 10) == (3, n)
+ assert index.slice_locs(end=8) == (0, 6)
+ assert index.slice_locs(end=9) == (0, 7)
+
+ # reversed
+ index2 = index[::-1]
+ assert index2.slice_locs(8, 2) == (2, 6)
+ assert index2.slice_locs(7, 3) == (2, 5)
+
+ @pytest.mark.parametrize("dtype", [int, float])
+ def test_slice_locs_float_locs(self, dtype):
+ index = Index(np.array([0, 1, 2, 5, 6, 7, 9, 10], dtype=dtype))
+ n = len(index)
+ assert index.slice_locs(5.0, 10.0) == (3, n)
+ assert index.slice_locs(4.5, 10.5) == (3, 8)
+
+ index2 = index[::-1]
+ assert index2.slice_locs(8.5, 1.5) == (2, 6)
+ assert index2.slice_locs(10.5, -1) == (0, n)
+
+ @pytest.mark.parametrize("dtype", [int, float])
+ def test_slice_locs_dup_numeric(self, dtype):
+ index = Index(np.array([10, 12, 12, 14], dtype=dtype))
+ assert index.slice_locs(12, 12) == (1, 3)
+ assert index.slice_locs(11, 13) == (1, 3)
+
+ index2 = index[::-1]
+ assert index2.slice_locs(12, 12) == (1, 3)
+ assert index2.slice_locs(13, 11) == (1, 3)
+
+ def test_slice_locs_na(self):
+ index = Index([np.nan, 1, 2])
+ assert index.slice_locs(1) == (1, 3)
+ assert index.slice_locs(np.nan) == (0, 3)
+
+ index = Index([0, np.nan, np.nan, 1, 2])
+ assert index.slice_locs(np.nan) == (1, 5)
+
+ def test_slice_locs_na_raises(self):
+ index = Index([np.nan, 1, 2])
+ msg = "non-monotonic index with a missing label 1.5"
+ with pytest.raises(KeyError, match=msg):
+ index.slice_locs(start=1.5)
+
+ with pytest.raises(KeyError, match=msg):
+ index.slice_locs(end=1.5)
+
+
+class TestGetSliceBounds:
+ @pytest.mark.parametrize("side, expected", [("left", 4), ("right", 5)])
+ def test_get_slice_bounds_within(self, side, expected):
+ index = Index(range(6))
+ result = index.get_slice_bound(4, side=side)
+ assert result == expected
+
+ @pytest.mark.parametrize("side", ["left", "right"])
+ @pytest.mark.parametrize("bound, expected", [(-1, 0), (10, 6)])
+ def test_get_slice_bounds_outside(self, side, expected, bound):
+ index = Index(range(6))
+ result = index.get_slice_bound(bound, side=side)
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..4737af987a04efec98f87152ea1e671bcf078af3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_join.py
@@ -0,0 +1,368 @@
+import numpy as np
+import pytest
+
+import pandas._testing as tm
+from pandas.core.indexes.api import Index
+
+
+class TestJoinInt64Index:
+ def test_join_non_unique(self):
+ left = Index([4, 4, 3, 3])
+
+ joined, lidx, ridx = left.join(left, return_indexers=True)
+
+ exp_joined = Index([4, 4, 4, 4, 3, 3, 3, 3])
+ tm.assert_index_equal(joined, exp_joined)
+
+ exp_lidx = np.array([0, 0, 1, 1, 2, 2, 3, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(lidx, exp_lidx)
+
+ exp_ridx = np.array([0, 1, 0, 1, 2, 3, 2, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(ridx, exp_ridx)
+
+ def test_join_inner(self):
+ index = Index(range(0, 20, 2), dtype=np.int64, name="lhs")
+ other = Index([7, 12, 25, 1, 2, 5], dtype=np.int64, name="rhs")
+ other_mono = Index([1, 2, 5, 7, 12, 25], dtype=np.int64, name="rhs")
+
+ # not monotonic
+ res, lidx, ridx = index.join(other, how="inner", return_indexers=True)
+
+ eres = Index([2, 12], dtype=np.int64, name="lhs")
+ elidx = np.array([1, 6], dtype=np.intp)
+ eridx = np.array([4, 1], dtype=np.intp)
+
+ assert isinstance(res, Index) and res.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # monotonic
+ res, lidx, ridx = index.join(other_mono, how="inner", return_indexers=True)
+
+ res2 = index.intersection(other_mono).set_names(["lhs"])
+ tm.assert_index_equal(res, res2)
+
+ elidx = np.array([1, 6], dtype=np.intp)
+ eridx = np.array([1, 4], dtype=np.intp)
+ assert isinstance(res, Index) and res.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_left(self):
+ index = Index(range(0, 20, 2), dtype=np.int64, name="lhs")
+ other = Index([7, 12, 25, 1, 2, 5], dtype=np.int64, name="rhs")
+ other_mono = Index([1, 2, 5, 7, 12, 25], dtype=np.int64, name="rhs")
+
+ # not monotonic
+ res, lidx, ridx = index.join(other, how="left", return_indexers=True)
+ eres = index
+ eridx = np.array([-1, 4, -1, -1, -1, -1, 1, -1, -1, -1], dtype=np.intp)
+
+ assert isinstance(res, Index) and res.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ assert lidx is None
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # monotonic
+ res, lidx, ridx = index.join(other_mono, how="left", return_indexers=True)
+ eridx = np.array([-1, 1, -1, -1, -1, -1, 4, -1, -1, -1], dtype=np.intp)
+ assert isinstance(res, Index) and res.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ assert lidx is None
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # non-unique
+ idx = Index([1, 1, 2, 5], name="rhs")
+ idx2 = Index([1, 2, 5, 7, 9], name="lhs")
+ res, lidx, ridx = idx2.join(idx, how="left", return_indexers=True)
+ eres = Index([1, 1, 2, 5, 7, 9], name="lhs") # 1 is in idx2, so it should be x2
+ eridx = np.array([0, 1, 2, 3, -1, -1], dtype=np.intp)
+ elidx = np.array([0, 0, 1, 2, 3, 4], dtype=np.intp)
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_right(self):
+ index = Index(range(0, 20, 2), dtype=np.int64, name="lhs")
+ other = Index([7, 12, 25, 1, 2, 5], dtype=np.int64, name="rhs")
+ other_mono = Index([1, 2, 5, 7, 12, 25], dtype=np.int64, name="rhs")
+
+ # not monotonic
+ res, lidx, ridx = index.join(other, how="right", return_indexers=True)
+ eres = other
+ elidx = np.array([-1, 6, -1, -1, 1, -1], dtype=np.intp)
+
+ assert isinstance(other, Index) and other.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ assert ridx is None
+
+ # monotonic
+ res, lidx, ridx = index.join(other_mono, how="right", return_indexers=True)
+ eres = other_mono
+ elidx = np.array([-1, 1, -1, -1, 6, -1], dtype=np.intp)
+ assert isinstance(other, Index) and other.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ assert ridx is None
+
+ # non-unique
+ idx = Index([1, 1, 2, 5], name="lhs")
+ idx2 = Index([1, 2, 5, 7, 9], name="rhs")
+ res, lidx, ridx = idx.join(idx2, how="right", return_indexers=True)
+ eres = Index([1, 1, 2, 5, 7, 9], name="rhs") # 1 is in idx2, so it should be x2
+ elidx = np.array([0, 1, 2, 3, -1, -1], dtype=np.intp)
+ eridx = np.array([0, 0, 1, 2, 3, 4], dtype=np.intp)
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_non_int_index(self):
+ index = Index(range(0, 20, 2), dtype=np.int64)
+ other = Index([3, 6, 7, 8, 10], dtype=object)
+
+ outer = index.join(other, how="outer")
+ outer2 = other.join(index, how="outer")
+ expected = Index([0, 2, 3, 4, 6, 7, 8, 10, 12, 14, 16, 18])
+ tm.assert_index_equal(outer, outer2)
+ tm.assert_index_equal(outer, expected)
+
+ inner = index.join(other, how="inner")
+ inner2 = other.join(index, how="inner")
+ expected = Index([6, 8, 10])
+ tm.assert_index_equal(inner, inner2)
+ tm.assert_index_equal(inner, expected)
+
+ left = index.join(other, how="left")
+ tm.assert_index_equal(left, index.astype(object))
+
+ left2 = other.join(index, how="left")
+ tm.assert_index_equal(left2, other)
+
+ right = index.join(other, how="right")
+ tm.assert_index_equal(right, other)
+
+ right2 = other.join(index, how="right")
+ tm.assert_index_equal(right2, index.astype(object))
+
+ def test_join_outer(self):
+ index = Index(range(0, 20, 2), dtype=np.int64)
+ other = Index([7, 12, 25, 1, 2, 5], dtype=np.int64)
+ other_mono = Index([1, 2, 5, 7, 12, 25], dtype=np.int64)
+
+ # not monotonic
+ # guarantee of sortedness
+ res, lidx, ridx = index.join(other, how="outer", return_indexers=True)
+ noidx_res = index.join(other, how="outer")
+ tm.assert_index_equal(res, noidx_res)
+
+ eres = Index([0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 25], dtype=np.int64)
+ elidx = np.array([0, -1, 1, 2, -1, 3, -1, 4, 5, 6, 7, 8, 9, -1], dtype=np.intp)
+ eridx = np.array(
+ [-1, 3, 4, -1, 5, -1, 0, -1, -1, 1, -1, -1, -1, 2], dtype=np.intp
+ )
+
+ assert isinstance(res, Index) and res.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # monotonic
+ res, lidx, ridx = index.join(other_mono, how="outer", return_indexers=True)
+ noidx_res = index.join(other_mono, how="outer")
+ tm.assert_index_equal(res, noidx_res)
+
+ elidx = np.array([0, -1, 1, 2, -1, 3, -1, 4, 5, 6, 7, 8, 9, -1], dtype=np.intp)
+ eridx = np.array(
+ [-1, 0, 1, -1, 2, -1, 3, -1, -1, 4, -1, -1, -1, 5], dtype=np.intp
+ )
+ assert isinstance(res, Index) and res.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+
+class TestJoinUInt64Index:
+ @pytest.fixture
+ def index_large(self):
+ # large values used in TestUInt64Index where no compat needed with int64/float64
+ large = [2**63, 2**63 + 10, 2**63 + 15, 2**63 + 20, 2**63 + 25]
+ return Index(large, dtype=np.uint64)
+
+ def test_join_inner(self, index_large):
+ other = Index(2**63 + np.array([7, 12, 25, 1, 2, 10], dtype="uint64"))
+ other_mono = Index(2**63 + np.array([1, 2, 7, 10, 12, 25], dtype="uint64"))
+
+ # not monotonic
+ res, lidx, ridx = index_large.join(other, how="inner", return_indexers=True)
+
+ # no guarantee of sortedness, so sort for comparison purposes
+ ind = res.argsort()
+ res = res.take(ind)
+ lidx = lidx.take(ind)
+ ridx = ridx.take(ind)
+
+ eres = Index(2**63 + np.array([10, 25], dtype="uint64"))
+ elidx = np.array([1, 4], dtype=np.intp)
+ eridx = np.array([5, 2], dtype=np.intp)
+
+ assert isinstance(res, Index) and res.dtype == np.uint64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # monotonic
+ res, lidx, ridx = index_large.join(
+ other_mono, how="inner", return_indexers=True
+ )
+
+ res2 = index_large.intersection(other_mono)
+ tm.assert_index_equal(res, res2)
+
+ elidx = np.array([1, 4], dtype=np.intp)
+ eridx = np.array([3, 5], dtype=np.intp)
+
+ assert isinstance(res, Index) and res.dtype == np.uint64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_left(self, index_large):
+ other = Index(2**63 + np.array([7, 12, 25, 1, 2, 10], dtype="uint64"))
+ other_mono = Index(2**63 + np.array([1, 2, 7, 10, 12, 25], dtype="uint64"))
+
+ # not monotonic
+ res, lidx, ridx = index_large.join(other, how="left", return_indexers=True)
+ eres = index_large
+ eridx = np.array([-1, 5, -1, -1, 2], dtype=np.intp)
+
+ assert isinstance(res, Index) and res.dtype == np.uint64
+ tm.assert_index_equal(res, eres)
+ assert lidx is None
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # monotonic
+ res, lidx, ridx = index_large.join(other_mono, how="left", return_indexers=True)
+ eridx = np.array([-1, 3, -1, -1, 5], dtype=np.intp)
+
+ assert isinstance(res, Index) and res.dtype == np.uint64
+ tm.assert_index_equal(res, eres)
+ assert lidx is None
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # non-unique
+ idx = Index(2**63 + np.array([1, 1, 2, 5], dtype="uint64"))
+ idx2 = Index(2**63 + np.array([1, 2, 5, 7, 9], dtype="uint64"))
+ res, lidx, ridx = idx2.join(idx, how="left", return_indexers=True)
+
+ # 1 is in idx2, so it should be x2
+ eres = Index(2**63 + np.array([1, 1, 2, 5, 7, 9], dtype="uint64"))
+ eridx = np.array([0, 1, 2, 3, -1, -1], dtype=np.intp)
+ elidx = np.array([0, 0, 1, 2, 3, 4], dtype=np.intp)
+
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_right(self, index_large):
+ other = Index(2**63 + np.array([7, 12, 25, 1, 2, 10], dtype="uint64"))
+ other_mono = Index(2**63 + np.array([1, 2, 7, 10, 12, 25], dtype="uint64"))
+
+ # not monotonic
+ res, lidx, ridx = index_large.join(other, how="right", return_indexers=True)
+ eres = other
+ elidx = np.array([-1, -1, 4, -1, -1, 1], dtype=np.intp)
+
+ tm.assert_numpy_array_equal(lidx, elidx)
+ assert isinstance(other, Index) and other.dtype == np.uint64
+ tm.assert_index_equal(res, eres)
+ assert ridx is None
+
+ # monotonic
+ res, lidx, ridx = index_large.join(
+ other_mono, how="right", return_indexers=True
+ )
+ eres = other_mono
+ elidx = np.array([-1, -1, -1, 1, -1, 4], dtype=np.intp)
+
+ assert isinstance(other, Index) and other.dtype == np.uint64
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_index_equal(res, eres)
+ assert ridx is None
+
+ # non-unique
+ idx = Index(2**63 + np.array([1, 1, 2, 5], dtype="uint64"))
+ idx2 = Index(2**63 + np.array([1, 2, 5, 7, 9], dtype="uint64"))
+ res, lidx, ridx = idx.join(idx2, how="right", return_indexers=True)
+
+ # 1 is in idx2, so it should be x2
+ eres = Index(2**63 + np.array([1, 1, 2, 5, 7, 9], dtype="uint64"))
+ elidx = np.array([0, 1, 2, 3, -1, -1], dtype=np.intp)
+ eridx = np.array([0, 0, 1, 2, 3, 4], dtype=np.intp)
+
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_non_int_index(self, index_large):
+ other = Index(2**63 + np.array([1, 5, 7, 10, 20], dtype="uint64"), dtype=object)
+
+ outer = index_large.join(other, how="outer")
+ outer2 = other.join(index_large, how="outer")
+ expected = Index(2**63 + np.array([0, 1, 5, 7, 10, 15, 20, 25], dtype="uint64"))
+ tm.assert_index_equal(outer, outer2)
+ tm.assert_index_equal(outer, expected)
+
+ inner = index_large.join(other, how="inner")
+ inner2 = other.join(index_large, how="inner")
+ expected = Index(2**63 + np.array([10, 20], dtype="uint64"))
+ tm.assert_index_equal(inner, inner2)
+ tm.assert_index_equal(inner, expected)
+
+ left = index_large.join(other, how="left")
+ tm.assert_index_equal(left, index_large.astype(object))
+
+ left2 = other.join(index_large, how="left")
+ tm.assert_index_equal(left2, other)
+
+ right = index_large.join(other, how="right")
+ tm.assert_index_equal(right, other)
+
+ right2 = other.join(index_large, how="right")
+ tm.assert_index_equal(right2, index_large.astype(object))
+
+ def test_join_outer(self, index_large):
+ other = Index(2**63 + np.array([7, 12, 25, 1, 2, 10], dtype="uint64"))
+ other_mono = Index(2**63 + np.array([1, 2, 7, 10, 12, 25], dtype="uint64"))
+
+ # not monotonic
+ # guarantee of sortedness
+ res, lidx, ridx = index_large.join(other, how="outer", return_indexers=True)
+ noidx_res = index_large.join(other, how="outer")
+ tm.assert_index_equal(res, noidx_res)
+
+ eres = Index(2**63 + np.array([0, 1, 2, 7, 10, 12, 15, 20, 25], dtype="uint64"))
+ elidx = np.array([0, -1, -1, -1, 1, -1, 2, 3, 4], dtype=np.intp)
+ eridx = np.array([-1, 3, 4, 0, 5, 1, -1, -1, 2], dtype=np.intp)
+
+ assert isinstance(res, Index) and res.dtype == np.uint64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # monotonic
+ res, lidx, ridx = index_large.join(
+ other_mono, how="outer", return_indexers=True
+ )
+ noidx_res = index_large.join(other_mono, how="outer")
+ tm.assert_index_equal(res, noidx_res)
+
+ elidx = np.array([0, -1, -1, -1, 1, -1, 2, 3, 4], dtype=np.intp)
+ eridx = np.array([-1, 0, 1, 2, 3, 4, -1, -1, 5], dtype=np.intp)
+
+ assert isinstance(res, Index) and res.dtype == np.uint64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_numeric.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_numeric.py
new file mode 100644
index 0000000000000000000000000000000000000000..8dde5febe810d1a72a1592575df44fb71d1150ee
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_numeric.py
@@ -0,0 +1,550 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestFloatNumericIndex:
+ @pytest.fixture(params=[np.float64, np.float32])
+ def dtype(self, request):
+ return request.param
+
+ @pytest.fixture
+ def mixed_index(self, dtype):
+ return Index([1.5, 2, 3, 4, 5], dtype=dtype)
+
+ @pytest.fixture
+ def float_index(self, dtype):
+ return Index([0.0, 2.5, 5.0, 7.5, 10.0], dtype=dtype)
+
+ @pytest.mark.parametrize(
+ "index_data",
+ [
+ [1.5, 2, 3, 4, 5],
+ [0.0, 2.5, 5.0, 7.5, 10.0],
+ [5, 4, 3, 2, 1.5],
+ [10.0, 7.5, 5.0, 2.5, 0.0],
+ ],
+ ids=["mixed", "float", "mixed_dec", "float_dec"],
+ )
+ def test_repr_roundtrip(self, index_data, dtype):
+ index = Index(index_data, dtype=dtype)
+ tm.assert_index_equal(eval(repr(index)), index, exact=True)
+
+ def check_coerce(self, a, b, is_float_index=True):
+ assert a.equals(b)
+ tm.assert_index_equal(a, b, exact=False)
+ if is_float_index:
+ assert isinstance(b, Index)
+ else:
+ assert type(b) is Index
+
+ def test_constructor_from_list_no_dtype(self):
+ index = Index([1.5, 2.5, 3.5])
+ assert index.dtype == np.float64
+
+ def test_constructor(self, dtype):
+ index_cls = Index
+
+ # explicit construction
+ index = index_cls([1, 2, 3, 4, 5], dtype=dtype)
+
+ assert isinstance(index, index_cls)
+ assert index.dtype == dtype
+
+ expected = np.array([1, 2, 3, 4, 5], dtype=dtype)
+ tm.assert_numpy_array_equal(index.values, expected)
+
+ index = index_cls(np.array([1, 2, 3, 4, 5]), dtype=dtype)
+ assert isinstance(index, index_cls)
+ assert index.dtype == dtype
+
+ index = index_cls([1.0, 2, 3, 4, 5], dtype=dtype)
+ assert isinstance(index, index_cls)
+ assert index.dtype == dtype
+
+ index = index_cls(np.array([1.0, 2, 3, 4, 5]), dtype=dtype)
+ assert isinstance(index, index_cls)
+ assert index.dtype == dtype
+
+ index = index_cls([1.0, 2, 3, 4, 5], dtype=dtype)
+ assert isinstance(index, index_cls)
+ assert index.dtype == dtype
+
+ index = index_cls(np.array([1.0, 2, 3, 4, 5]), dtype=dtype)
+ assert isinstance(index, index_cls)
+ assert index.dtype == dtype
+
+ # nan handling
+ result = index_cls([np.nan, np.nan], dtype=dtype)
+ assert pd.isna(result.values).all()
+
+ result = index_cls(np.array([np.nan]), dtype=dtype)
+ assert pd.isna(result.values).all()
+
+ def test_constructor_invalid(self):
+ index_cls = Index
+ cls_name = index_cls.__name__
+ # invalid
+ msg = (
+ rf"{cls_name}\(\.\.\.\) must be called with a collection of "
+ r"some kind, 0\.0 was passed"
+ )
+ with pytest.raises(TypeError, match=msg):
+ index_cls(0.0)
+
+ def test_constructor_coerce(self, mixed_index, float_index):
+ self.check_coerce(mixed_index, Index([1.5, 2, 3, 4, 5]))
+ self.check_coerce(float_index, Index(np.arange(5) * 2.5))
+
+ result = Index(np.array(np.arange(5) * 2.5, dtype=object))
+ assert result.dtype == object # as of 2.0 to match Series
+ self.check_coerce(float_index, result.astype("float64"))
+
+ def test_constructor_explicit(self, mixed_index, float_index):
+ # these don't auto convert
+ self.check_coerce(
+ float_index, Index((np.arange(5) * 2.5), dtype=object), is_float_index=False
+ )
+ self.check_coerce(
+ mixed_index, Index([1.5, 2, 3, 4, 5], dtype=object), is_float_index=False
+ )
+
+ def test_type_coercion_fail(self, any_int_numpy_dtype):
+ # see gh-15832
+ msg = "Trying to coerce float values to integers"
+ with pytest.raises(ValueError, match=msg):
+ Index([1, 2, 3.5], dtype=any_int_numpy_dtype)
+
+ def test_equals_numeric(self):
+ index_cls = Index
+
+ idx = index_cls([1.0, 2.0])
+ assert idx.equals(idx)
+ assert idx.identical(idx)
+
+ idx2 = index_cls([1.0, 2.0])
+ assert idx.equals(idx2)
+
+ idx = index_cls([1.0, np.nan])
+ assert idx.equals(idx)
+ assert idx.identical(idx)
+
+ idx2 = index_cls([1.0, np.nan])
+ assert idx.equals(idx2)
+
+ @pytest.mark.parametrize(
+ "other",
+ (
+ Index([1, 2], dtype=np.int64),
+ Index([1.0, 2.0], dtype=object),
+ Index([1, 2], dtype=object),
+ ),
+ )
+ def test_equals_numeric_other_index_type(self, other):
+ idx = Index([1.0, 2.0])
+ assert idx.equals(other)
+ assert other.equals(idx)
+
+ @pytest.mark.parametrize(
+ "vals",
+ [
+ pd.date_range("2016-01-01", periods=3),
+ pd.timedelta_range("1 Day", periods=3),
+ ],
+ )
+ def test_lookups_datetimelike_values(self, vals, dtype):
+ # If we have datetime64 or timedelta64 values, make sure they are
+ # wrapped correctly GH#31163
+ ser = Series(vals, index=range(3, 6))
+ ser.index = ser.index.astype(dtype)
+
+ expected = vals[1]
+
+ result = ser[4.0]
+ assert isinstance(result, type(expected)) and result == expected
+ result = ser[4]
+ assert isinstance(result, type(expected)) and result == expected
+
+ result = ser.loc[4.0]
+ assert isinstance(result, type(expected)) and result == expected
+ result = ser.loc[4]
+ assert isinstance(result, type(expected)) and result == expected
+
+ result = ser.at[4.0]
+ assert isinstance(result, type(expected)) and result == expected
+ # GH#31329 .at[4] should cast to 4.0, matching .loc behavior
+ result = ser.at[4]
+ assert isinstance(result, type(expected)) and result == expected
+
+ result = ser.iloc[1]
+ assert isinstance(result, type(expected)) and result == expected
+
+ result = ser.iat[1]
+ assert isinstance(result, type(expected)) and result == expected
+
+ def test_doesnt_contain_all_the_things(self):
+ idx = Index([np.nan])
+ assert not idx.isin([0]).item()
+ assert not idx.isin([1]).item()
+ assert idx.isin([np.nan]).item()
+
+ def test_nan_multiple_containment(self):
+ index_cls = Index
+
+ idx = index_cls([1.0, np.nan])
+ tm.assert_numpy_array_equal(idx.isin([1.0]), np.array([True, False]))
+ tm.assert_numpy_array_equal(idx.isin([2.0, np.pi]), np.array([False, False]))
+ tm.assert_numpy_array_equal(idx.isin([np.nan]), np.array([False, True]))
+ tm.assert_numpy_array_equal(idx.isin([1.0, np.nan]), np.array([True, True]))
+ idx = index_cls([1.0, 2.0])
+ tm.assert_numpy_array_equal(idx.isin([np.nan]), np.array([False, False]))
+
+ def test_fillna_float64(self):
+ index_cls = Index
+ # GH 11343
+ idx = Index([1.0, np.nan, 3.0], dtype=float, name="x")
+ # can't downcast
+ exp = Index([1.0, 0.1, 3.0], name="x")
+ tm.assert_index_equal(idx.fillna(0.1), exp, exact=True)
+
+ # downcast
+ exp = index_cls([1.0, 2.0, 3.0], name="x")
+ tm.assert_index_equal(idx.fillna(2), exp)
+
+ # object
+ exp = Index([1.0, "obj", 3.0], name="x")
+ tm.assert_index_equal(idx.fillna("obj"), exp, exact=True)
+
+ def test_logical_compat(self, dtype):
+ idx = Index(np.arange(5, dtype=dtype))
+ assert idx.all() == idx.values.all()
+ assert idx.any() == idx.values.any()
+
+ assert idx.all() == idx.to_series().all()
+ assert idx.any() == idx.to_series().any()
+
+
+class TestNumericInt:
+ @pytest.fixture
+ def dtype(self, any_int_numpy_dtype):
+ return np.dtype(any_int_numpy_dtype)
+
+ @pytest.fixture
+ def simple_index(self, dtype):
+ return Index(range(0, 20, 2), dtype=dtype)
+
+ def test_is_monotonic(self):
+ index_cls = Index
+
+ index = index_cls([1, 2, 3, 4])
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_increasing is True
+ assert index._is_strictly_monotonic_increasing is True
+ assert index.is_monotonic_decreasing is False
+ assert index._is_strictly_monotonic_decreasing is False
+
+ index = index_cls([4, 3, 2, 1])
+ assert index.is_monotonic_increasing is False
+ assert index._is_strictly_monotonic_increasing is False
+ assert index._is_strictly_monotonic_decreasing is True
+
+ index = index_cls([1])
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_decreasing is True
+ assert index._is_strictly_monotonic_increasing is True
+ assert index._is_strictly_monotonic_decreasing is True
+
+ def test_is_strictly_monotonic(self):
+ index_cls = Index
+
+ index = index_cls([1, 1, 2, 3])
+ assert index.is_monotonic_increasing is True
+ assert index._is_strictly_monotonic_increasing is False
+
+ index = index_cls([3, 2, 1, 1])
+ assert index.is_monotonic_decreasing is True
+ assert index._is_strictly_monotonic_decreasing is False
+
+ index = index_cls([1, 1])
+ assert index.is_monotonic_increasing
+ assert index.is_monotonic_decreasing
+ assert not index._is_strictly_monotonic_increasing
+ assert not index._is_strictly_monotonic_decreasing
+
+ def test_logical_compat(self, simple_index):
+ idx = simple_index
+ assert idx.all() == idx.values.all()
+ assert idx.any() == idx.values.any()
+
+ def test_identical(self, simple_index, dtype):
+ index = simple_index
+
+ idx = Index(index.copy())
+ assert idx.identical(index)
+
+ same_values_different_type = Index(idx, dtype=object)
+ assert not idx.identical(same_values_different_type)
+
+ idx = index.astype(dtype=object)
+ idx = idx.rename("foo")
+ same_values = Index(idx, dtype=object)
+ assert same_values.identical(idx)
+
+ assert not idx.identical(index)
+ assert Index(same_values, name="foo", dtype=object).identical(idx)
+
+ assert not index.astype(dtype=object).identical(index.astype(dtype=dtype))
+
+ def test_cant_or_shouldnt_cast(self, dtype):
+ msg = r"invalid literal for int\(\) with base 10: 'foo'"
+
+ # can't
+ data = ["foo", "bar", "baz"]
+ with pytest.raises(ValueError, match=msg):
+ Index(data, dtype=dtype)
+
+ def test_view_index(self, simple_index):
+ index = simple_index
+ msg = (
+ "Cannot change data-type for array of references.|"
+ "Cannot change data-type for object array.|"
+ )
+ with pytest.raises(TypeError, match=msg):
+ index.view(Index)
+
+ def test_prevent_casting(self, simple_index):
+ index = simple_index
+ result = index.astype("O")
+ assert result.dtype == np.object_
+
+
+class TestIntNumericIndex:
+ @pytest.fixture(params=[np.int64, np.int32, np.int16, np.int8])
+ def dtype(self, request):
+ return request.param
+
+ def test_constructor_from_list_no_dtype(self):
+ index = Index([1, 2, 3])
+ assert index.dtype == np.int64
+
+ def test_constructor(self, dtype):
+ index_cls = Index
+
+ # scalar raise Exception
+ msg = (
+ rf"{index_cls.__name__}\(\.\.\.\) must be called with a collection of some "
+ "kind, 5 was passed"
+ )
+ with pytest.raises(TypeError, match=msg):
+ index_cls(5)
+
+ # copy
+ # pass list, coerce fine
+ index = index_cls([-5, 0, 1, 2], dtype=dtype)
+ arr = index.values.copy()
+ new_index = index_cls(arr, copy=True)
+ tm.assert_index_equal(new_index, index, exact=True)
+ val = int(arr[0]) + 3000
+
+ # this should not change index
+ if dtype != np.int8:
+ # NEP 50 won't allow assignment that would overflow
+ arr[0] = val
+ assert new_index[0] != val
+
+ if dtype == np.int64:
+ # pass list, coerce fine
+ index = index_cls([-5, 0, 1, 2], dtype=dtype)
+ expected = Index([-5, 0, 1, 2], dtype=dtype)
+ tm.assert_index_equal(index, expected)
+
+ # from iterable
+ index = index_cls(iter([-5, 0, 1, 2]), dtype=dtype)
+ expected = index_cls([-5, 0, 1, 2], dtype=dtype)
+ tm.assert_index_equal(index, expected, exact=True)
+
+ # interpret list-like
+ expected = index_cls([5, 0], dtype=dtype)
+ for cls in [Index, index_cls]:
+ for idx in [
+ cls([5, 0], dtype=dtype),
+ cls(np.array([5, 0]), dtype=dtype),
+ cls(Series([5, 0]), dtype=dtype),
+ ]:
+ tm.assert_index_equal(idx, expected)
+
+ def test_constructor_corner(self, dtype):
+ index_cls = Index
+
+ arr = np.array([1, 2, 3, 4], dtype=object)
+
+ index = index_cls(arr, dtype=dtype)
+ assert index.values.dtype == index.dtype
+ if dtype == np.int64:
+ without_dtype = Index(arr)
+ # as of 2.0 we do not infer a dtype when we get an object-dtype
+ # ndarray of numbers, matching Series behavior
+ assert without_dtype.dtype == object
+
+ tm.assert_index_equal(index, without_dtype.astype(np.int64))
+
+ # preventing casting
+ arr = np.array([1, "2", 3, "4"], dtype=object)
+ msg = "Trying to coerce object values to integers"
+ with pytest.raises(ValueError, match=msg):
+ index_cls(arr, dtype=dtype)
+
+ def test_constructor_coercion_signed_to_unsigned(
+ self,
+ any_unsigned_int_numpy_dtype,
+ ):
+ # see gh-15832
+ msg = "|".join(
+ [
+ "Trying to coerce negative values to unsigned integers",
+ "The elements provided in the data cannot all be casted",
+ ]
+ )
+ with pytest.raises(OverflowError, match=msg):
+ Index([-1], dtype=any_unsigned_int_numpy_dtype)
+
+ def test_constructor_np_signed(self, any_signed_int_numpy_dtype):
+ # GH#47475
+ scalar = np.dtype(any_signed_int_numpy_dtype).type(1)
+ result = Index([scalar])
+ expected = Index([1], dtype=any_signed_int_numpy_dtype)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_constructor_np_unsigned(self, any_unsigned_int_numpy_dtype):
+ # GH#47475
+ scalar = np.dtype(any_unsigned_int_numpy_dtype).type(1)
+ result = Index([scalar])
+ expected = Index([1], dtype=any_unsigned_int_numpy_dtype)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_coerce_list(self):
+ # coerce things
+ arr = Index([1, 2, 3, 4])
+ assert isinstance(arr, Index)
+
+ # but not if explicit dtype passed
+ arr = Index([1, 2, 3, 4], dtype=object)
+ assert type(arr) is Index
+
+
+class TestFloat16Index:
+ # float 16 indexes not supported
+ # GH 49535
+ def test_constructor(self):
+ index_cls = Index
+ dtype = np.float16
+
+ msg = "float16 indexes are not supported"
+
+ # explicit construction
+ with pytest.raises(NotImplementedError, match=msg):
+ index_cls([1, 2, 3, 4, 5], dtype=dtype)
+
+ with pytest.raises(NotImplementedError, match=msg):
+ index_cls(np.array([1, 2, 3, 4, 5]), dtype=dtype)
+
+ with pytest.raises(NotImplementedError, match=msg):
+ index_cls([1.0, 2, 3, 4, 5], dtype=dtype)
+
+ with pytest.raises(NotImplementedError, match=msg):
+ index_cls(np.array([1.0, 2, 3, 4, 5]), dtype=dtype)
+
+ with pytest.raises(NotImplementedError, match=msg):
+ index_cls([1.0, 2, 3, 4, 5], dtype=dtype)
+
+ with pytest.raises(NotImplementedError, match=msg):
+ index_cls(np.array([1.0, 2, 3, 4, 5]), dtype=dtype)
+
+ # nan handling
+ with pytest.raises(NotImplementedError, match=msg):
+ index_cls([np.nan, np.nan], dtype=dtype)
+
+ with pytest.raises(NotImplementedError, match=msg):
+ index_cls(np.array([np.nan]), dtype=dtype)
+
+
+@pytest.mark.parametrize(
+ "box",
+ [list, lambda x: np.array(x, dtype=object), lambda x: Index(x, dtype=object)],
+)
+def test_uint_index_does_not_convert_to_float64(box):
+ # https://github.com/pandas-dev/pandas/issues/28279
+ # https://github.com/pandas-dev/pandas/issues/28023
+ series = Series(
+ [0, 1, 2, 3, 4, 5],
+ index=[
+ 7606741985629028552,
+ 17876870360202815256,
+ 17876870360202815256,
+ 13106359306506049338,
+ 8991270399732411471,
+ 8991270399732411472,
+ ],
+ )
+
+ result = series.loc[box([7606741985629028552, 17876870360202815256])]
+
+ expected = Index(
+ [7606741985629028552, 17876870360202815256, 17876870360202815256],
+ dtype="uint64",
+ )
+ tm.assert_index_equal(result.index, expected)
+
+ tm.assert_equal(result, series.iloc[:3])
+
+
+def test_float64_index_equals():
+ # https://github.com/pandas-dev/pandas/issues/35217
+ float_index = Index([1.0, 2, 3])
+ string_index = Index(["1", "2", "3"])
+
+ result = float_index.equals(string_index)
+ assert result is False
+
+ result = string_index.equals(float_index)
+ assert result is False
+
+
+def test_map_dtype_inference_unsigned_to_signed():
+ # GH#44609 cases where we don't retain dtype
+ idx = Index([1, 2, 3], dtype=np.uint64)
+ result = idx.map(lambda x: -x)
+ expected = Index([-1, -2, -3], dtype=np.int64)
+ tm.assert_index_equal(result, expected)
+
+
+def test_map_dtype_inference_overflows():
+ # GH#44609 case where we have to upcast
+ idx = Index(np.array([1, 2, 3], dtype=np.int8))
+ result = idx.map(lambda x: x * 1000)
+ # TODO: we could plausibly try to infer down to int16 here
+ expected = Index([1000, 2000, 3000], dtype=np.int64)
+ tm.assert_index_equal(result, expected)
+
+
+def test_view_to_datetimelike():
+ # GH#55710
+ idx = Index([1, 2, 3])
+ res = idx.view("m8[s]")
+ expected = pd.TimedeltaIndex(idx.values.view("m8[s]"))
+ tm.assert_index_equal(res, expected)
+
+ res2 = idx.view("m8[D]")
+ expected2 = idx.values.view("m8[D]")
+ tm.assert_numpy_array_equal(res2, expected2)
+
+ res3 = idx.view("M8[h]")
+ expected3 = idx.values.view("M8[h]")
+ tm.assert_numpy_array_equal(res3, expected3)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d3981dbf93d07ab244f7ee4318424d3885b134d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/numeric/test_setops.py
@@ -0,0 +1,173 @@
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+import pandas._testing as tm
+from pandas.core.indexes.api import (
+ Index,
+ RangeIndex,
+)
+
+
+@pytest.fixture
+def index_large():
+ # large values used in TestUInt64Index where no compat needed with int64/float64
+ large = [2**63, 2**63 + 10, 2**63 + 15, 2**63 + 20, 2**63 + 25]
+ return Index(large, dtype=np.uint64)
+
+
+class TestSetOps:
+ @pytest.mark.parametrize("dtype", ["f8", "u8", "i8"])
+ def test_union_non_numeric(self, dtype):
+ # corner case, non-numeric
+ index = Index(np.arange(5, dtype=dtype), dtype=dtype)
+ assert index.dtype == dtype
+
+ other = Index([datetime.now() + timedelta(i) for i in range(4)], dtype=object)
+ result = index.union(other)
+ expected = Index(np.concatenate((index, other)))
+ tm.assert_index_equal(result, expected)
+
+ result = other.union(index)
+ expected = Index(np.concatenate((other, index)))
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection(self):
+ index = Index(range(5), dtype=np.int64)
+
+ other = Index([1, 2, 3, 4, 5])
+ result = index.intersection(other)
+ expected = Index(range(1, 5))
+ tm.assert_index_equal(result, expected)
+
+ result = other.intersection(index)
+ expected = Index(
+ np.sort(np.asarray(np.intersect1d(index.values, other.values)))
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["int64", "uint64"])
+ def test_int_float_union_dtype(self, dtype):
+ # https://github.com/pandas-dev/pandas/issues/26778
+ # [u]int | float -> float
+ index = Index([0, 2, 3], dtype=dtype)
+ other = Index([0.5, 1.5], dtype=np.float64)
+ expected = Index([0.0, 0.5, 1.5, 2.0, 3.0], dtype=np.float64)
+ result = index.union(other)
+ tm.assert_index_equal(result, expected)
+
+ result = other.union(index)
+ tm.assert_index_equal(result, expected)
+
+ def test_range_float_union_dtype(self):
+ # https://github.com/pandas-dev/pandas/issues/26778
+ index = RangeIndex(start=0, stop=3)
+ other = Index([0.5, 1.5], dtype=np.float64)
+ result = index.union(other)
+ expected = Index([0.0, 0.5, 1, 1.5, 2.0], dtype=np.float64)
+ tm.assert_index_equal(result, expected)
+
+ result = other.union(index)
+ tm.assert_index_equal(result, expected)
+
+ def test_range_uint64_union_dtype(self):
+ # https://github.com/pandas-dev/pandas/issues/26778
+ index = RangeIndex(start=0, stop=3)
+ other = Index([0, 10], dtype=np.uint64)
+ result = index.union(other)
+ expected = Index([0, 1, 2, 10], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ result = other.union(index)
+ tm.assert_index_equal(result, expected)
+
+ def test_float64_index_difference(self):
+ # https://github.com/pandas-dev/pandas/issues/35217
+ float_index = Index([1.0, 2, 3])
+ string_index = Index(["1", "2", "3"])
+
+ result = float_index.difference(string_index)
+ tm.assert_index_equal(result, float_index)
+
+ result = string_index.difference(float_index)
+ tm.assert_index_equal(result, string_index)
+
+ def test_intersection_uint64_outside_int64_range(self):
+ index_large = Index(
+ [2**63, 2**63 + 10, 2**63 + 15, 2**63 + 20, 2**63 + 25],
+ dtype=np.uint64,
+ )
+ other = Index([2**63, 2**63 + 5, 2**63 + 10, 2**63 + 15, 2**63 + 20])
+ result = index_large.intersection(other)
+ expected = Index(np.sort(np.intersect1d(index_large.values, other.values)))
+ tm.assert_index_equal(result, expected)
+
+ result = other.intersection(index_large)
+ expected = Index(
+ np.sort(np.asarray(np.intersect1d(index_large.values, other.values)))
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "index2_name,keeps_name",
+ [
+ ("index", True),
+ ("other", False),
+ ],
+ )
+ def test_intersection_monotonic(self, index2_name, keeps_name, sort):
+ index2 = Index([4, 7, 6, 5, 3], name=index2_name)
+ index1 = Index([5, 3, 2, 4, 1], name="index")
+ expected = Index([5, 3, 4])
+
+ if keeps_name:
+ expected.name = "index"
+
+ result = index1.intersection(index2, sort=sort)
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+
+ def test_symmetric_difference(self, sort):
+ # smoke
+ index1 = Index([5, 2, 3, 4], name="index1")
+ index2 = Index([2, 3, 4, 1])
+ result = index1.symmetric_difference(index2, sort=sort)
+ expected = Index([5, 1])
+ if sort is not None:
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_index_equal(result, expected.sort_values())
+ assert result.name is None
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+
+
+class TestSetOpsSort:
+ @pytest.mark.parametrize("slice_", [slice(None), slice(0)])
+ def test_union_sort_other_special(self, slice_):
+ # https://github.com/pandas-dev/pandas/issues/24959
+
+ idx = Index([1, 0, 2])
+ # default, sort=None
+ other = idx[slice_]
+ tm.assert_index_equal(idx.union(other), idx)
+ tm.assert_index_equal(other.union(idx), idx)
+
+ # sort=False
+ tm.assert_index_equal(idx.union(other, sort=False), idx)
+
+ @pytest.mark.parametrize("slice_", [slice(None), slice(0)])
+ def test_union_sort_special_true(self, slice_):
+ idx = Index([1, 0, 2])
+ # default, sort=None
+ other = idx[slice_]
+
+ result = idx.union(other, sort=True)
+ expected = Index([0, 1, 2])
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/object/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/object/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/object/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/object/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e0de138aacfbf89ef6800669383e39f466104b3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/object/test_astype.py
@@ -0,0 +1,15 @@
+import pytest
+
+from pandas import (
+ Index,
+ NaT,
+)
+
+
+def test_astype_invalid_nas_to_tdt64_raises():
+ # GH#45722 don't cast np.datetime64 NaTs to timedelta64 NaT
+ idx = Index([NaT.asm8] * 2, dtype=object)
+
+ msg = r"Invalid type for timedelta scalar: "
+ with pytest.raises(TypeError, match=msg):
+ idx.astype("m8[ns]")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/object/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/object/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c5968314e5cf16ebb6b07ebe6936717d68a602f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/object/test_indexing.py
@@ -0,0 +1,159 @@
+from decimal import Decimal
+
+import numpy as np
+import pytest
+
+from pandas._libs.missing import is_matching_na
+
+from pandas import Index
+import pandas._testing as tm
+
+
+class TestGetIndexer:
+ @pytest.mark.parametrize(
+ "method,expected",
+ [
+ ("pad", [-1, 0, 1, 1]),
+ ("backfill", [0, 0, 1, -1]),
+ ],
+ )
+ def test_get_indexer_strings(self, method, expected):
+ expected = np.array(expected, dtype=np.intp)
+ index = Index(["b", "c"], dtype=object)
+ actual = index.get_indexer(["a", "b", "c", "d"], method=method)
+
+ tm.assert_numpy_array_equal(actual, expected)
+
+ def test_get_indexer_strings_raises(self):
+ index = Index(["b", "c"], dtype=object)
+
+ msg = "|".join(
+ [
+ "operation 'sub' not supported for dtype 'str'",
+ r"unsupported operand type\(s\) for -: 'str' and 'str'",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ index.get_indexer(["a", "b", "c", "d"], method="nearest")
+
+ with pytest.raises(TypeError, match=msg):
+ index.get_indexer(["a", "b", "c", "d"], method="pad", tolerance=2)
+
+ with pytest.raises(TypeError, match=msg):
+ index.get_indexer(
+ ["a", "b", "c", "d"], method="pad", tolerance=[2, 2, 2, 2]
+ )
+
+ def test_get_indexer_with_NA_values(
+ self, unique_nulls_fixture, unique_nulls_fixture2
+ ):
+ # GH#22332
+ # check pairwise, that no pair of na values
+ # is mangled
+ if unique_nulls_fixture is unique_nulls_fixture2:
+ return # skip it, values are not unique
+ arr = np.array([unique_nulls_fixture, unique_nulls_fixture2], dtype=object)
+ index = Index(arr, dtype=object)
+ result = index.get_indexer(
+ Index(
+ [unique_nulls_fixture, unique_nulls_fixture2, "Unknown"], dtype=object
+ )
+ )
+ expected = np.array([0, 1, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer_infer_string_missing_values(self):
+ # ensure the passed list is not cast to string but to object so that
+ # the None value is matched in the index
+ # https://github.com/pandas-dev/pandas/issues/55834
+ idx = Index(["a", "b", None], dtype="object")
+ result = idx.get_indexer([None, "x"])
+ expected = np.array([2, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestGetIndexerNonUnique:
+ def test_get_indexer_non_unique_nas(self, nulls_fixture):
+ # even though this isn't non-unique, this should still work
+ index = Index(["a", "b", nulls_fixture], dtype=object)
+ indexer, missing = index.get_indexer_non_unique([nulls_fixture])
+
+ expected_indexer = np.array([2], dtype=np.intp)
+ expected_missing = np.array([], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected_indexer)
+ tm.assert_numpy_array_equal(missing, expected_missing)
+
+ # actually non-unique
+ index = Index(["a", nulls_fixture, "b", nulls_fixture], dtype=object)
+ indexer, missing = index.get_indexer_non_unique([nulls_fixture])
+
+ expected_indexer = np.array([1, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected_indexer)
+ tm.assert_numpy_array_equal(missing, expected_missing)
+
+ # matching-but-not-identical nans
+ if is_matching_na(nulls_fixture, float("NaN")):
+ index = Index(["a", float("NaN"), "b", float("NaN")], dtype=object)
+ match_but_not_identical = True
+ elif is_matching_na(nulls_fixture, Decimal("NaN")):
+ index = Index(["a", Decimal("NaN"), "b", Decimal("NaN")], dtype=object)
+ match_but_not_identical = True
+ else:
+ match_but_not_identical = False
+
+ if match_but_not_identical:
+ indexer, missing = index.get_indexer_non_unique([nulls_fixture])
+
+ expected_indexer = np.array([1, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected_indexer)
+ tm.assert_numpy_array_equal(missing, expected_missing)
+
+ @pytest.mark.filterwarnings("ignore:elementwise comp:DeprecationWarning")
+ def test_get_indexer_non_unique_np_nats(self, np_nat_fixture, np_nat_fixture2):
+ expected_missing = np.array([], dtype=np.intp)
+ # matching-but-not-identical nats
+ if is_matching_na(np_nat_fixture, np_nat_fixture2):
+ # ensure nats are different objects
+ index = Index(
+ np.array(
+ ["2021-10-02", np_nat_fixture.copy(), np_nat_fixture2.copy()],
+ dtype=object,
+ ),
+ dtype=object,
+ )
+ # pass as index to prevent target from being casted to DatetimeIndex
+ indexer, missing = index.get_indexer_non_unique(
+ Index([np_nat_fixture], dtype=object)
+ )
+ expected_indexer = np.array([1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected_indexer)
+ tm.assert_numpy_array_equal(missing, expected_missing)
+ # dt64nat vs td64nat
+ else:
+ try:
+ np_nat_fixture == np_nat_fixture2
+ except (TypeError, OverflowError):
+ # Numpy will raise on uncomparable types, like
+ # np.datetime64('NaT', 'Y') and np.datetime64('NaT', 'ps')
+ # https://github.com/numpy/numpy/issues/22762
+ return
+ index = Index(
+ np.array(
+ [
+ "2021-10-02",
+ np_nat_fixture,
+ np_nat_fixture2,
+ np_nat_fixture,
+ np_nat_fixture2,
+ ],
+ dtype=object,
+ ),
+ dtype=object,
+ )
+ # pass as index to prevent target from being casted to DatetimeIndex
+ indexer, missing = index.get_indexer_non_unique(
+ Index([np_nat_fixture], dtype=object)
+ )
+ expected_indexer = np.array([1, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected_indexer)
+ tm.assert_numpy_array_equal(missing, expected_missing)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_asfreq.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_asfreq.py
new file mode 100644
index 0000000000000000000000000000000000000000..8fca53c28a036d5ce3a39bcdf24e238400c28045
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_asfreq.py
@@ -0,0 +1,182 @@
+import re
+
+import pytest
+
+from pandas import (
+ PeriodIndex,
+ Series,
+ period_range,
+)
+import pandas._testing as tm
+
+from pandas.tseries import offsets
+
+
+class TestPeriodIndex:
+ def test_asfreq(self):
+ pi1 = period_range(freq="Y", start="1/1/2001", end="1/1/2001")
+ pi2 = period_range(freq="Q", start="1/1/2001", end="1/1/2001")
+ pi3 = period_range(freq="M", start="1/1/2001", end="1/1/2001")
+ pi4 = period_range(freq="D", start="1/1/2001", end="1/1/2001")
+ pi5 = period_range(freq="h", start="1/1/2001", end="1/1/2001 00:00")
+ pi6 = period_range(freq="Min", start="1/1/2001", end="1/1/2001 00:00")
+ pi7 = period_range(freq="s", start="1/1/2001", end="1/1/2001 00:00:00")
+
+ assert pi1.asfreq("Q", "s") == pi2
+ assert pi1.asfreq("Q", "s") == pi2
+ assert pi1.asfreq("M", "start") == pi3
+ assert pi1.asfreq("D", "StarT") == pi4
+ assert pi1.asfreq("h", "beGIN") == pi5
+ assert pi1.asfreq("Min", "s") == pi6
+ assert pi1.asfreq("s", "s") == pi7
+
+ assert pi2.asfreq("Y", "s") == pi1
+ assert pi2.asfreq("M", "s") == pi3
+ assert pi2.asfreq("D", "s") == pi4
+ assert pi2.asfreq("h", "s") == pi5
+ assert pi2.asfreq("Min", "s") == pi6
+ assert pi2.asfreq("s", "s") == pi7
+
+ assert pi3.asfreq("Y", "s") == pi1
+ assert pi3.asfreq("Q", "s") == pi2
+ assert pi3.asfreq("D", "s") == pi4
+ assert pi3.asfreq("h", "s") == pi5
+ assert pi3.asfreq("Min", "s") == pi6
+ assert pi3.asfreq("s", "s") == pi7
+
+ assert pi4.asfreq("Y", "s") == pi1
+ assert pi4.asfreq("Q", "s") == pi2
+ assert pi4.asfreq("M", "s") == pi3
+ assert pi4.asfreq("h", "s") == pi5
+ assert pi4.asfreq("Min", "s") == pi6
+ assert pi4.asfreq("s", "s") == pi7
+
+ assert pi5.asfreq("Y", "s") == pi1
+ assert pi5.asfreq("Q", "s") == pi2
+ assert pi5.asfreq("M", "s") == pi3
+ assert pi5.asfreq("D", "s") == pi4
+ assert pi5.asfreq("Min", "s") == pi6
+ assert pi5.asfreq("s", "s") == pi7
+
+ assert pi6.asfreq("Y", "s") == pi1
+ assert pi6.asfreq("Q", "s") == pi2
+ assert pi6.asfreq("M", "s") == pi3
+ assert pi6.asfreq("D", "s") == pi4
+ assert pi6.asfreq("h", "s") == pi5
+ assert pi6.asfreq("s", "s") == pi7
+
+ assert pi7.asfreq("Y", "s") == pi1
+ assert pi7.asfreq("Q", "s") == pi2
+ assert pi7.asfreq("M", "s") == pi3
+ assert pi7.asfreq("D", "s") == pi4
+ assert pi7.asfreq("h", "s") == pi5
+ assert pi7.asfreq("Min", "s") == pi6
+
+ msg = "How must be one of S or E"
+ with pytest.raises(ValueError, match=msg):
+ pi7.asfreq("T", "foo")
+ result1 = pi1.asfreq("3M")
+ result2 = pi1.asfreq("M")
+ expected = period_range(freq="M", start="2001-12", end="2001-12")
+ tm.assert_numpy_array_equal(result1.asi8, expected.asi8)
+ assert result1.freqstr == "3M"
+ tm.assert_numpy_array_equal(result2.asi8, expected.asi8)
+ assert result2.freqstr == "M"
+
+ def test_asfreq_nat(self):
+ idx = PeriodIndex(["2011-01", "2011-02", "NaT", "2011-04"], freq="M")
+ result = idx.asfreq(freq="Q")
+ expected = PeriodIndex(["2011Q1", "2011Q1", "NaT", "2011Q2"], freq="Q")
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("freq", ["D", "3D"])
+ def test_asfreq_mult_pi(self, freq):
+ pi = PeriodIndex(["2001-01", "2001-02", "NaT", "2001-03"], freq="2M")
+
+ result = pi.asfreq(freq)
+ exp = PeriodIndex(["2001-02-28", "2001-03-31", "NaT", "2001-04-30"], freq=freq)
+ tm.assert_index_equal(result, exp)
+ assert result.freq == exp.freq
+
+ result = pi.asfreq(freq, how="S")
+ exp = PeriodIndex(["2001-01-01", "2001-02-01", "NaT", "2001-03-01"], freq=freq)
+ tm.assert_index_equal(result, exp)
+ assert result.freq == exp.freq
+
+ def test_asfreq_combined_pi(self):
+ pi = PeriodIndex(["2001-01-01 00:00", "2001-01-02 02:00", "NaT"], freq="h")
+ exp = PeriodIndex(["2001-01-01 00:00", "2001-01-02 02:00", "NaT"], freq="25h")
+ for freq, how in zip(["1D1h", "1h1D"], ["S", "E"]):
+ result = pi.asfreq(freq, how=how)
+ tm.assert_index_equal(result, exp)
+ assert result.freq == exp.freq
+
+ for freq in ["1D1h", "1h1D"]:
+ pi = PeriodIndex(["2001-01-01 00:00", "2001-01-02 02:00", "NaT"], freq=freq)
+ result = pi.asfreq("h")
+ exp = PeriodIndex(["2001-01-02 00:00", "2001-01-03 02:00", "NaT"], freq="h")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == exp.freq
+
+ pi = PeriodIndex(["2001-01-01 00:00", "2001-01-02 02:00", "NaT"], freq=freq)
+ result = pi.asfreq("h", how="S")
+ exp = PeriodIndex(["2001-01-01 00:00", "2001-01-02 02:00", "NaT"], freq="h")
+ tm.assert_index_equal(result, exp)
+ assert result.freq == exp.freq
+
+ def test_astype_asfreq(self):
+ pi1 = PeriodIndex(["2011-01-01", "2011-02-01", "2011-03-01"], freq="D")
+ exp = PeriodIndex(["2011-01", "2011-02", "2011-03"], freq="M")
+ tm.assert_index_equal(pi1.asfreq("M"), exp)
+ tm.assert_index_equal(pi1.astype("period[M]"), exp)
+
+ exp = PeriodIndex(["2011-01", "2011-02", "2011-03"], freq="3M")
+ tm.assert_index_equal(pi1.asfreq("3M"), exp)
+ tm.assert_index_equal(pi1.astype("period[3M]"), exp)
+
+ def test_asfreq_with_different_n(self):
+ ser = Series([1, 2], index=PeriodIndex(["2020-01", "2020-03"], freq="2M"))
+ result = ser.asfreq("M")
+
+ excepted = Series([1, 2], index=PeriodIndex(["2020-02", "2020-04"], freq="M"))
+ tm.assert_series_equal(result, excepted)
+
+ @pytest.mark.parametrize(
+ "freq",
+ [
+ "2BMS",
+ "2YS-MAR",
+ "2bh",
+ offsets.MonthBegin(2),
+ offsets.BusinessMonthEnd(2),
+ ],
+ )
+ def test_pi_asfreq_not_supported_frequency(self, freq):
+ # GH#55785, GH#56945
+ msg = "|".join(
+ [
+ f"Invalid frequency: {freq}",
+ re.escape(f"{freq} is not supported as period frequency"),
+ "bh is not supported as period frequency",
+ ]
+ )
+
+ pi = PeriodIndex(["2020-01-01", "2021-01-01"], freq="M")
+ with pytest.raises(ValueError, match=msg):
+ pi.asfreq(freq=freq)
+
+ @pytest.mark.parametrize(
+ "freq",
+ [
+ "2BME",
+ "2YE-MAR",
+ "2QE",
+ ],
+ )
+ def test_pi_asfreq_invalid_frequency(self, freq):
+ # GH#55785
+ msg = f"Invalid frequency: {freq}"
+
+ pi = PeriodIndex(["2020-01-01", "2021-01-01"], freq="M")
+ with pytest.raises(ValueError, match=msg):
+ pi.asfreq(freq=freq)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..af3c2667f51b4387c5d5c089f186952deec68af1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_astype.py
@@ -0,0 +1,156 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ CategoricalIndex,
+ DatetimeIndex,
+ Index,
+ NaT,
+ Period,
+ PeriodIndex,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestPeriodIndexAsType:
+ @pytest.mark.parametrize("dtype", [float, "timedelta64", "timedelta64[ns]"])
+ def test_astype_raises(self, dtype):
+ # GH#13149, GH#13209
+ idx = PeriodIndex(["2016-05-16", "NaT", NaT, np.nan], freq="D")
+ msg = "Cannot cast PeriodIndex to dtype"
+ with pytest.raises(TypeError, match=msg):
+ idx.astype(dtype)
+
+ def test_astype_conversion(self, using_infer_string):
+ # GH#13149, GH#13209
+ idx = PeriodIndex(["2016-05-16", "NaT", NaT, np.nan], freq="D", name="idx")
+
+ result = idx.astype(object)
+ expected = Index(
+ [Period("2016-05-16", freq="D")] + [Period(NaT, freq="D")] * 3,
+ dtype="object",
+ name="idx",
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = idx.astype(np.int64)
+ expected = Index(
+ [16937] + [-9223372036854775808] * 3, dtype=np.int64, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = idx.astype(str)
+ if using_infer_string:
+ expected = Index(
+ [str(x) if x is not NaT else None for x in idx], name="idx", dtype="str"
+ )
+ else:
+ expected = Index([str(x) for x in idx], name="idx", dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ idx = period_range("1990", "2009", freq="Y", name="idx")
+ result = idx.astype("i8")
+ tm.assert_index_equal(result, Index(idx.asi8, name="idx"))
+ tm.assert_numpy_array_equal(result.values, idx.asi8)
+
+ def test_astype_uint(self):
+ arr = period_range("2000", periods=2, name="idx")
+
+ with pytest.raises(TypeError, match=r"Do obj.astype\('int64'\)"):
+ arr.astype("uint64")
+ with pytest.raises(TypeError, match=r"Do obj.astype\('int64'\)"):
+ arr.astype("uint32")
+
+ def test_astype_object(self):
+ idx = PeriodIndex([], freq="M")
+
+ exp = np.array([], dtype=object)
+ tm.assert_numpy_array_equal(idx.astype(object).values, exp)
+ tm.assert_numpy_array_equal(idx._mpl_repr(), exp)
+
+ idx = PeriodIndex(["2011-01", NaT], freq="M")
+
+ exp = np.array([Period("2011-01", freq="M"), NaT], dtype=object)
+ tm.assert_numpy_array_equal(idx.astype(object).values, exp)
+ tm.assert_numpy_array_equal(idx._mpl_repr(), exp)
+
+ exp = np.array([Period("2011-01-01", freq="D"), NaT], dtype=object)
+ idx = PeriodIndex(["2011-01-01", NaT], freq="D")
+ tm.assert_numpy_array_equal(idx.astype(object).values, exp)
+ tm.assert_numpy_array_equal(idx._mpl_repr(), exp)
+
+ # TODO: de-duplicate this version (from test_ops) with the one above
+ # (from test_period)
+ def test_astype_object2(self):
+ idx = period_range(start="2013-01-01", periods=4, freq="M", name="idx")
+ expected_list = [
+ Period("2013-01-31", freq="M"),
+ Period("2013-02-28", freq="M"),
+ Period("2013-03-31", freq="M"),
+ Period("2013-04-30", freq="M"),
+ ]
+ expected = Index(expected_list, dtype=object, name="idx")
+ result = idx.astype(object)
+ assert isinstance(result, Index)
+ assert result.dtype == object
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert idx.tolist() == expected_list
+
+ idx = PeriodIndex(
+ ["2013-01-01", "2013-01-02", "NaT", "2013-01-04"], freq="D", name="idx"
+ )
+ expected_list = [
+ Period("2013-01-01", freq="D"),
+ Period("2013-01-02", freq="D"),
+ Period("NaT", freq="D"),
+ Period("2013-01-04", freq="D"),
+ ]
+ expected = Index(expected_list, dtype=object, name="idx")
+ result = idx.astype(object)
+ assert isinstance(result, Index)
+ assert result.dtype == object
+ tm.assert_index_equal(result, expected)
+ for i in [0, 1, 3]:
+ assert result[i] == expected[i]
+ assert result[2] is NaT
+ assert result.name == expected.name
+
+ result_list = idx.tolist()
+ for i in [0, 1, 3]:
+ assert result_list[i] == expected_list[i]
+ assert result_list[2] is NaT
+
+ def test_astype_category(self):
+ obj = period_range("2000", periods=2, name="idx")
+ result = obj.astype("category")
+ expected = CategoricalIndex(
+ [Period("2000-01-01", freq="D"), Period("2000-01-02", freq="D")], name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = obj._data.astype("category")
+ expected = expected.values
+ tm.assert_categorical_equal(result, expected)
+
+ def test_astype_array_fallback(self):
+ obj = period_range("2000", periods=2, name="idx")
+ result = obj.astype(bool)
+ expected = Index(np.array([True, True]), name="idx")
+ tm.assert_index_equal(result, expected)
+
+ result = obj._data.astype(bool)
+ expected = np.array([True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_period_astype_to_timestamp(self, unit):
+ # GH#55958
+ pi = PeriodIndex(["2011-01", "2011-02", "2011-03"], freq="M")
+
+ exp = DatetimeIndex(
+ ["2011-01-01", "2011-02-01", "2011-03-01"], tz="US/Eastern"
+ ).as_unit(unit)
+ res = pi.astype(f"datetime64[{unit}, US/Eastern]")
+ tm.assert_index_equal(res, exp)
+ assert res.freq == exp.freq
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_factorize.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_factorize.py
new file mode 100644
index 0000000000000000000000000000000000000000..1239eae6091b81dfcc1ac049996296f6af565df8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_factorize.py
@@ -0,0 +1,41 @@
+import numpy as np
+
+from pandas import PeriodIndex
+import pandas._testing as tm
+
+
+class TestFactorize:
+ def test_factorize_period(self):
+ idx1 = PeriodIndex(
+ ["2014-01", "2014-01", "2014-02", "2014-02", "2014-03", "2014-03"],
+ freq="M",
+ )
+
+ exp_arr = np.array([0, 0, 1, 1, 2, 2], dtype=np.intp)
+ exp_idx = PeriodIndex(["2014-01", "2014-02", "2014-03"], freq="M")
+
+ arr, idx = idx1.factorize()
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+
+ arr, idx = idx1.factorize(sort=True)
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+
+ def test_factorize_period_nonmonotonic(self):
+ idx2 = PeriodIndex(
+ ["2014-03", "2014-03", "2014-02", "2014-01", "2014-03", "2014-01"],
+ freq="M",
+ )
+ exp_idx = PeriodIndex(["2014-01", "2014-02", "2014-03"], freq="M")
+
+ exp_arr = np.array([2, 2, 1, 0, 2, 0], dtype=np.intp)
+ arr, idx = idx2.factorize(sort=True)
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+
+ exp_arr = np.array([0, 0, 1, 2, 0, 2], dtype=np.intp)
+ exp_idx = PeriodIndex(["2014-03", "2014-02", "2014-01"], freq="M")
+ arr, idx = idx2.factorize()
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_fillna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_fillna.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed6b4686a06defdc3eac4e1f6427fb0569c2d48d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_fillna.py
@@ -0,0 +1,41 @@
+from pandas import (
+ Index,
+ NaT,
+ Period,
+ PeriodIndex,
+)
+import pandas._testing as tm
+
+
+class TestFillNA:
+ def test_fillna_period(self):
+ # GH#11343
+ idx = PeriodIndex(["2011-01-01 09:00", NaT, "2011-01-01 11:00"], freq="h")
+
+ exp = PeriodIndex(
+ ["2011-01-01 09:00", "2011-01-01 10:00", "2011-01-01 11:00"], freq="h"
+ )
+ result = idx.fillna(Period("2011-01-01 10:00", freq="h"))
+ tm.assert_index_equal(result, exp)
+
+ exp = Index(
+ [
+ Period("2011-01-01 09:00", freq="h"),
+ "x",
+ Period("2011-01-01 11:00", freq="h"),
+ ],
+ dtype=object,
+ )
+ result = idx.fillna("x")
+ tm.assert_index_equal(result, exp)
+
+ exp = Index(
+ [
+ Period("2011-01-01 09:00", freq="h"),
+ Period("2011-01-01", freq="D"),
+ Period("2011-01-01 11:00", freq="h"),
+ ],
+ dtype=object,
+ )
+ result = idx.fillna(Period("2011-01-01", freq="D"))
+ tm.assert_index_equal(result, exp)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_insert.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_insert.py
new file mode 100644
index 0000000000000000000000000000000000000000..32bbe09d925679579c1ec015b435870d1282e6b3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_insert.py
@@ -0,0 +1,18 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ NaT,
+ PeriodIndex,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestInsert:
+ @pytest.mark.parametrize("na", [np.nan, NaT, None])
+ def test_insert(self, na):
+ # GH#18295 (test missing)
+ expected = PeriodIndex(["2017Q1", NaT, "2017Q2", "2017Q3", "2017Q4"], freq="Q")
+ result = period_range("2017Q1", periods=4, freq="Q").insert(1, na)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_is_full.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_is_full.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4105bedbe21d6dc85379f1a6eefb298db954056
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_is_full.py
@@ -0,0 +1,23 @@
+import pytest
+
+from pandas import PeriodIndex
+
+
+def test_is_full():
+ index = PeriodIndex([2005, 2007, 2009], freq="Y")
+ assert not index.is_full
+
+ index = PeriodIndex([2005, 2006, 2007], freq="Y")
+ assert index.is_full
+
+ index = PeriodIndex([2005, 2005, 2007], freq="Y")
+ assert not index.is_full
+
+ index = PeriodIndex([2005, 2005, 2006], freq="Y")
+ assert index.is_full
+
+ index = PeriodIndex([2006, 2005, 2005], freq="Y")
+ with pytest.raises(ValueError, match="Index is not monotonic"):
+ index.is_full
+
+ assert index[:0].is_full
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_repeat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_repeat.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc344b06420d16a436c84a70f45a292cf6045856
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_repeat.py
@@ -0,0 +1,26 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ PeriodIndex,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestRepeat:
+ @pytest.mark.parametrize("use_numpy", [True, False])
+ @pytest.mark.parametrize(
+ "index",
+ [
+ period_range("2000-01-01", periods=3, freq="D"),
+ period_range("2001-01-01", periods=3, freq="2D"),
+ PeriodIndex(["2001-01", "NaT", "2003-01"], freq="M"),
+ ],
+ )
+ def test_repeat_freqstr(self, index, use_numpy):
+ # GH#10183
+ expected = PeriodIndex([per for per in index for _ in range(3)])
+ result = np.repeat(index, 3) if use_numpy else index.repeat(3)
+ tm.assert_index_equal(result, expected)
+ assert result.freqstr == index.freqstr
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_shift.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_shift.py
new file mode 100644
index 0000000000000000000000000000000000000000..fca3e3a559e1fe2e53571f5af919e9a0c49c4e68
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_shift.py
@@ -0,0 +1,122 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ PeriodIndex,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestPeriodIndexShift:
+ # ---------------------------------------------------------------
+ # PeriodIndex.shift is used by __add__ and __sub__
+
+ def test_pi_shift_ndarray(self):
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "NaT", "2011-04"], freq="M", name="idx"
+ )
+ result = idx.shift(np.array([1, 2, 3, 4]))
+ expected = PeriodIndex(
+ ["2011-02", "2011-04", "NaT", "2011-08"], freq="M", name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = idx.shift(np.array([1, -2, 3, -4]))
+ expected = PeriodIndex(
+ ["2011-02", "2010-12", "NaT", "2010-12"], freq="M", name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_shift(self):
+ pi1 = period_range(freq="Y", start="1/1/2001", end="12/1/2009")
+ pi2 = period_range(freq="Y", start="1/1/2002", end="12/1/2010")
+
+ tm.assert_index_equal(pi1.shift(0), pi1)
+
+ assert len(pi1) == len(pi2)
+ tm.assert_index_equal(pi1.shift(1), pi2)
+
+ pi1 = period_range(freq="Y", start="1/1/2001", end="12/1/2009")
+ pi2 = period_range(freq="Y", start="1/1/2000", end="12/1/2008")
+ assert len(pi1) == len(pi2)
+ tm.assert_index_equal(pi1.shift(-1), pi2)
+
+ pi1 = period_range(freq="M", start="1/1/2001", end="12/1/2009")
+ pi2 = period_range(freq="M", start="2/1/2001", end="1/1/2010")
+ assert len(pi1) == len(pi2)
+ tm.assert_index_equal(pi1.shift(1), pi2)
+
+ pi1 = period_range(freq="M", start="1/1/2001", end="12/1/2009")
+ pi2 = period_range(freq="M", start="12/1/2000", end="11/1/2009")
+ assert len(pi1) == len(pi2)
+ tm.assert_index_equal(pi1.shift(-1), pi2)
+
+ pi1 = period_range(freq="D", start="1/1/2001", end="12/1/2009")
+ pi2 = period_range(freq="D", start="1/2/2001", end="12/2/2009")
+ assert len(pi1) == len(pi2)
+ tm.assert_index_equal(pi1.shift(1), pi2)
+
+ pi1 = period_range(freq="D", start="1/1/2001", end="12/1/2009")
+ pi2 = period_range(freq="D", start="12/31/2000", end="11/30/2009")
+ assert len(pi1) == len(pi2)
+ tm.assert_index_equal(pi1.shift(-1), pi2)
+
+ def test_shift_corner_cases(self):
+ # GH#9903
+ idx = PeriodIndex([], name="xxx", freq="h")
+
+ msg = "`freq` argument is not supported for PeriodIndex.shift"
+ with pytest.raises(TypeError, match=msg):
+ # period shift doesn't accept freq
+ idx.shift(1, freq="h")
+
+ tm.assert_index_equal(idx.shift(0), idx)
+ tm.assert_index_equal(idx.shift(3), idx)
+
+ idx = PeriodIndex(
+ ["2011-01-01 10:00", "2011-01-01 11:00", "2011-01-01 12:00"],
+ name="xxx",
+ freq="h",
+ )
+ tm.assert_index_equal(idx.shift(0), idx)
+ exp = PeriodIndex(
+ ["2011-01-01 13:00", "2011-01-01 14:00", "2011-01-01 15:00"],
+ name="xxx",
+ freq="h",
+ )
+ tm.assert_index_equal(idx.shift(3), exp)
+ exp = PeriodIndex(
+ ["2011-01-01 07:00", "2011-01-01 08:00", "2011-01-01 09:00"],
+ name="xxx",
+ freq="h",
+ )
+ tm.assert_index_equal(idx.shift(-3), exp)
+
+ def test_shift_nat(self):
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "NaT", "2011-04"], freq="M", name="idx"
+ )
+ result = idx.shift(1)
+ expected = PeriodIndex(
+ ["2011-02", "2011-03", "NaT", "2011-05"], freq="M", name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+
+ def test_shift_gh8083(self):
+ # test shift for PeriodIndex
+ # GH#8083
+ drange = period_range("20130101", periods=5, freq="D")
+ result = drange.shift(1)
+ expected = PeriodIndex(
+ ["2013-01-02", "2013-01-03", "2013-01-04", "2013-01-05", "2013-01-06"],
+ freq="D",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_shift_periods(self):
+ # GH #22458 : argument 'n' was deprecated in favor of 'periods'
+ idx = period_range(freq="Y", start="1/1/2001", end="12/1/2009")
+ tm.assert_index_equal(idx.shift(periods=0), idx)
+ tm.assert_index_equal(idx.shift(0), idx)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_to_timestamp.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_to_timestamp.py
new file mode 100644
index 0000000000000000000000000000000000000000..619a8afe4fc21fb2edc7a08701669b53f6e2d9d6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/methods/test_to_timestamp.py
@@ -0,0 +1,149 @@
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ NaT,
+ PeriodIndex,
+ Timedelta,
+ Timestamp,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestToTimestamp:
+ def test_to_timestamp_non_contiguous(self):
+ # GH#44100
+ dti = date_range("2021-10-18", periods=9, freq="D", unit="ns")
+ pi = dti.to_period()
+
+ result = pi[::2].to_timestamp()
+ expected = dti[::2].as_unit("us")
+ tm.assert_index_equal(result, expected)
+
+ result = pi._data[::2].to_timestamp()
+ expected = dti._data[::2].as_unit("us")
+ # TODO: can we get the freq to round-trip?
+ tm.assert_datetime_array_equal(result, expected, check_freq=False)
+
+ result = pi[::-1].to_timestamp()
+ expected = dti[::-1].as_unit("us")
+ tm.assert_index_equal(result, expected)
+
+ result = pi._data[::-1].to_timestamp()
+ expected = dti._data[::-1].as_unit("us")
+ tm.assert_datetime_array_equal(result, expected, check_freq=False)
+
+ result = pi[::2][::-1].to_timestamp()
+ expected = dti[::2][::-1].as_unit("us")
+ tm.assert_index_equal(result, expected)
+
+ result = pi._data[::2][::-1].to_timestamp()
+ expected = dti._data[::2][::-1].as_unit("us")
+ tm.assert_datetime_array_equal(result, expected, check_freq=False)
+
+ def test_to_timestamp_freq(self):
+ idx = period_range("2017", periods=12, freq="Y-DEC")
+ result = idx.to_timestamp()
+ expected = date_range("2017", periods=12, freq="YS-JAN")
+ tm.assert_index_equal(result, expected)
+
+ def test_to_timestamp_pi_nat(self):
+ # GH#7228
+ index = PeriodIndex(["NaT", "2011-01", "2011-02"], freq="M", name="idx")
+
+ result = index.to_timestamp("D")
+ expected = DatetimeIndex(
+ [NaT, datetime(2011, 1, 1), datetime(2011, 2, 1)],
+ dtype="M8[us]",
+ name="idx",
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.name == "idx"
+
+ result2 = result.to_period(freq="M")
+ tm.assert_index_equal(result2, index)
+ assert result2.name == "idx"
+
+ result3 = result.to_period(freq="3M")
+ exp = PeriodIndex(["NaT", "2011-01", "2011-02"], freq="3M", name="idx")
+ tm.assert_index_equal(result3, exp)
+ assert result3.freqstr == "3M"
+
+ msg = "Frequency must be positive, because it represents span: -2Y"
+ with pytest.raises(ValueError, match=msg):
+ result.to_period(freq="-2Y")
+
+ def test_to_timestamp_preserve_name(self):
+ index = period_range(freq="Y", start="1/1/2001", end="12/1/2009", name="foo")
+ assert index.name == "foo"
+
+ conv = index.to_timestamp("D")
+ assert conv.name == "foo"
+
+ def test_to_timestamp_quarterly_bug(self):
+ years = np.arange(1960, 2000).repeat(4)
+ quarters = np.tile(list(range(1, 5)), 40)
+
+ pindex = PeriodIndex.from_fields(year=years, quarter=quarters)
+
+ stamps = pindex.to_timestamp("D", "end")
+ expected = DatetimeIndex([x.to_timestamp("D", "end") for x in pindex])
+ tm.assert_index_equal(stamps, expected)
+ assert stamps.freq == expected.freq
+
+ def test_to_timestamp_pi_mult(self):
+ idx = PeriodIndex(["2011-01", "NaT", "2011-02"], freq="2M", name="idx")
+
+ result = idx.to_timestamp()
+ expected = DatetimeIndex(
+ ["2011-01-01", "NaT", "2011-02-01"], dtype="M8[us]", name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = idx.to_timestamp(how="E")
+ expected = DatetimeIndex(
+ ["2011-02-28", "NaT", "2011-03-31"], dtype="M8[us]", name="idx"
+ )
+ expected = expected + Timedelta(1, "D") - Timedelta(1, "us")
+ tm.assert_index_equal(result, expected)
+
+ def test_to_timestamp_pi_combined(self):
+ idx = period_range(start="2011", periods=2, freq="1D1h", name="idx")
+
+ result = idx.to_timestamp()
+ expected = DatetimeIndex(
+ ["2011-01-01 00:00", "2011-01-02 01:00"], dtype="M8[us]", name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = idx.to_timestamp(how="E")
+ expected = DatetimeIndex(
+ ["2011-01-02 00:59:59", "2011-01-03 01:59:59"], name="idx", dtype="M8[us]"
+ )
+ expected = expected + Timedelta(1, "s") - Timedelta(1, "us")
+ tm.assert_index_equal(result, expected)
+
+ result = idx.to_timestamp(how="E", freq="h")
+ expected = DatetimeIndex(
+ ["2011-01-02 00:00", "2011-01-03 01:00"], dtype="M8[us]", name="idx"
+ )
+ expected = expected + Timedelta(1, "h") - Timedelta(1, "us")
+ tm.assert_index_equal(result, expected)
+
+ def test_to_timestamp_1703(self):
+ index = period_range("1/1/2012", periods=4, freq="D")
+
+ result = index.to_timestamp()
+ assert result[0] == Timestamp("1/1/2012")
+
+
+def test_ms_to_timestamp_error_message():
+ # https://github.com/pandas-dev/pandas/issues/58974#issuecomment-2164265446
+ ix = period_range("2000", periods=3, freq="M")
+ with pytest.raises(ValueError, match="for Period, please use 'M' instead of 'MS'"):
+ ix.to_timestamp("MS")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..310f6e5628a40f57c1d2b4ca638b5f819ab29d8f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_constructors.py
@@ -0,0 +1,682 @@
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs.period import IncompatibleFrequency
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.dtypes import PeriodDtype
+
+from pandas import (
+ Index,
+ NaT,
+ Period,
+ PeriodIndex,
+ Series,
+ date_range,
+ offsets,
+ period_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import PeriodArray
+
+
+class TestPeriodIndexDisallowedFreqs:
+ @pytest.mark.parametrize(
+ "freq,freq_depr",
+ [
+ ("2M", "2ME"),
+ ("2Q-MAR", "2QE-MAR"),
+ ("2Y-FEB", "2YE-FEB"),
+ ("2M", "2me"),
+ ("2Q-MAR", "2qe-MAR"),
+ ("2Y-FEB", "2yE-feb"),
+ ],
+ )
+ def test_period_index_offsets_frequency_error_message(self, freq, freq_depr):
+ # GH#52064
+ msg = f"Invalid frequency: {freq_depr}"
+
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex(["2020-01-01", "2020-01-02"], freq=freq_depr)
+
+ with pytest.raises(ValueError, match=msg):
+ period_range(start="2020-01-01", end="2020-01-02", freq=freq_depr)
+
+ @pytest.mark.parametrize(
+ "freq",
+ ["2SME", "2sme", "2BYE", "2Bye", "2CBME"],
+ )
+ def test_period_index_frequency_invalid_freq(self, freq):
+ # GH#9586
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ period_range("2020-01", "2020-05", freq=freq)
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex(["2020-01", "2020-05"], freq=freq)
+
+ @pytest.mark.parametrize("freq", ["2BQE-SEP", "2BYE-MAR", "2BME"])
+ def test_period_index_from_datetime_index_invalid_freq(self, freq):
+ # GH#56899
+ msg = f"Invalid frequency: {freq}"
+
+ rng = date_range("01-Jan-2012", periods=8, freq=freq)
+ with pytest.raises(ValueError, match=msg):
+ rng.to_period()
+
+ @pytest.mark.parametrize("freq_depr", ["2T", "1l", "2U", "n"])
+ def test_period_index_T_L_U_N_raises(self, freq_depr):
+ # GH#9586
+ msg = f"Invalid frequency: {freq_depr}"
+
+ with pytest.raises(ValueError, match=msg):
+ period_range("2020-01", "2020-05", freq=freq_depr)
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex(["2020-01", "2020-05"], freq=freq_depr)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ @pytest.mark.filterwarnings("ignore:Period with BDay freq:FutureWarning")
+ @pytest.mark.parametrize(
+ "freq,freq_depr",
+ [("2W", "2w"), ("2W-FRI", "2w-fri"), ("2D", "2d"), ("2B", "2b")],
+ )
+ def test_period_index_depr_lowercase_frequency(self, freq, freq_depr):
+ # GH#58998
+ msg = (
+ f"'{freq_depr[1:]}' is deprecated and will be removed in a future version."
+ )
+
+ with tm.assert_produces_warning(
+ Pandas4Warning, match=msg, raise_on_extra_warnings=False
+ ):
+ result = PeriodIndex(["2020-01-01", "2020-01-02"], freq=freq_depr)
+
+ expected = PeriodIndex(["2020-01-01", "2020-01-02"], freq=freq)
+ tm.assert_index_equal(result, expected)
+
+ with tm.assert_produces_warning(
+ Pandas4Warning, match=msg, raise_on_extra_warnings=False
+ ):
+ result = period_range(start="2020-01-01", end="2020-01-02", freq=freq_depr)
+
+ expected = period_range(start="2020-01-01", end="2020-01-02", freq=freq)
+ tm.assert_index_equal(result, expected)
+
+
+class TestPeriodIndex:
+ def test_from_ordinals(self):
+ Period(ordinal=-1000, freq="Y")
+ Period(ordinal=0, freq="Y")
+
+ idx1 = PeriodIndex.from_ordinals(ordinals=[-1, 0, 1], freq="Y")
+ idx2 = PeriodIndex.from_ordinals(ordinals=np.array([-1, 0, 1]), freq="Y")
+ tm.assert_index_equal(idx1, idx2)
+
+ def test_construction_base_constructor(self):
+ # GH 13664
+ arr = [Period("2011-01", freq="M"), NaT, Period("2011-03", freq="M")]
+ tm.assert_index_equal(Index(arr), PeriodIndex(arr))
+ tm.assert_index_equal(Index(np.array(arr)), PeriodIndex(np.array(arr)))
+
+ arr = [np.nan, NaT, Period("2011-03", freq="M")]
+ tm.assert_index_equal(Index(arr), PeriodIndex(arr))
+ tm.assert_index_equal(Index(np.array(arr)), PeriodIndex(np.array(arr)))
+
+ arr = [Period("2011-01", freq="M"), NaT, Period("2011-03", freq="D")]
+ tm.assert_index_equal(Index(arr), Index(arr, dtype=object))
+
+ tm.assert_index_equal(Index(np.array(arr)), Index(np.array(arr), dtype=object))
+
+ def test_base_constructor_with_period_dtype(self):
+ dtype = PeriodDtype("D")
+ values = ["2011-01-01", "2012-03-04", "2014-05-01"]
+ result = Index(values, dtype=dtype)
+
+ expected = PeriodIndex(values, dtype=dtype)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "values_constructor", [list, np.array, PeriodIndex, PeriodArray._from_sequence]
+ )
+ def test_index_object_dtype(self, values_constructor):
+ # Index(periods, dtype=object) is an Index (not a PeriodIndex)
+ periods = [
+ Period("2011-01", freq="M"),
+ NaT,
+ Period("2011-03", freq="M"),
+ ]
+ values = values_constructor(periods)
+ result = Index(values, dtype=object)
+
+ assert type(result) is Index
+ tm.assert_numpy_array_equal(result.values, np.array(values))
+
+ def test_constructor_use_start_freq(self):
+ # GH #1118
+ msg1 = "Period with BDay freq is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg1):
+ p = Period("4/2/2012", freq="B")
+ msg2 = r"PeriodDtype\[B\] is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg2):
+ expected = period_range(start="4/2/2012", periods=10, freq="B")
+
+ with tm.assert_produces_warning(FutureWarning, match=msg2):
+ index = period_range(start=p, periods=10)
+ tm.assert_index_equal(index, expected)
+
+ def test_constructor_field_arrays(self):
+ # GH #1264
+
+ years = np.arange(1990, 2010).repeat(4)[2:-2]
+ quarters = np.tile(np.arange(1, 5), 20)[2:-2]
+
+ index = PeriodIndex.from_fields(year=years, quarter=quarters, freq="Q-DEC")
+ expected = period_range("1990Q3", "2009Q2", freq="Q-DEC")
+ tm.assert_index_equal(index, expected)
+
+ index2 = PeriodIndex.from_fields(year=years, quarter=quarters, freq="2Q-DEC")
+ tm.assert_numpy_array_equal(index.asi8, index2.asi8)
+
+ index = PeriodIndex.from_fields(year=years, quarter=quarters)
+ tm.assert_index_equal(index, expected)
+
+ years = [2007, 2007, 2007]
+ months = [1, 2]
+
+ msg = "Mismatched Period array lengths"
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex.from_fields(year=years, month=months, freq="M")
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex.from_fields(year=years, month=months, freq="2M")
+
+ years = [2007, 2007, 2007]
+ months = [1, 2, 3]
+ idx = PeriodIndex.from_fields(year=years, month=months, freq="M")
+ exp = period_range("2007-01", periods=3, freq="M")
+ tm.assert_index_equal(idx, exp)
+
+ def test_constructor_nano(self):
+ idx = period_range(
+ start=Period(ordinal=1, freq="ns"),
+ end=Period(ordinal=4, freq="ns"),
+ freq="ns",
+ )
+ exp = PeriodIndex(
+ [
+ Period(ordinal=1, freq="ns"),
+ Period(ordinal=2, freq="ns"),
+ Period(ordinal=3, freq="ns"),
+ Period(ordinal=4, freq="ns"),
+ ],
+ freq="ns",
+ )
+ tm.assert_index_equal(idx, exp)
+
+ def test_constructor_arrays_negative_year(self):
+ years = np.arange(1960, 2000, dtype=np.int64).repeat(4)
+ quarters = np.tile(np.array([1, 2, 3, 4], dtype=np.int64), 40)
+ pindex = PeriodIndex.from_fields(year=years, quarter=quarters)
+
+ tm.assert_index_equal(pindex.year, Index(years))
+ tm.assert_index_equal(pindex.quarter, Index(quarters))
+
+ def test_constructor_invalid_quarters(self):
+ msg = "Quarter must be 1 <= q <= 4"
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex.from_fields(
+ year=range(2000, 2004), quarter=list(range(4)), freq="Q-DEC"
+ )
+
+ def test_period_range_fractional_period(self):
+ msg = "periods must be an integer, got 10.5"
+ with pytest.raises(TypeError, match=msg):
+ period_range("2007-01", periods=10.5, freq="M")
+
+ def test_constructor_with_without_freq(self):
+ # GH53687
+ start = Period("2002-01-01 00:00", freq="30min")
+ exp = period_range(start=start, periods=5, freq=start.freq)
+ result = period_range(start=start, periods=5)
+ tm.assert_index_equal(exp, result)
+
+ def test_constructor_fromarraylike(self):
+ idx = period_range("2007-01", periods=20, freq="M")
+
+ # values is an array of Period, thus can retrieve freq
+ tm.assert_index_equal(PeriodIndex(idx.values), idx)
+ tm.assert_index_equal(PeriodIndex(list(idx.values)), idx)
+
+ msg = "freq not specified and cannot be inferred"
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex(idx.asi8)
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex(list(idx.asi8))
+
+ msg = "'Period' object is not iterable"
+ with pytest.raises(TypeError, match=msg):
+ PeriodIndex(data=Period("2007", freq="Y"))
+
+ result = PeriodIndex(iter(idx))
+ tm.assert_index_equal(result, idx)
+
+ result = PeriodIndex(idx)
+ tm.assert_index_equal(result, idx)
+
+ result = PeriodIndex(idx, freq="M")
+ tm.assert_index_equal(result, idx)
+
+ result = PeriodIndex(idx, freq=offsets.MonthEnd())
+ tm.assert_index_equal(result, idx)
+ assert result.freq == "ME"
+
+ result = PeriodIndex(idx, freq="2M")
+ tm.assert_index_equal(result, idx.asfreq("2M"))
+ assert result.freq == "2ME"
+
+ result = PeriodIndex(idx, freq=offsets.MonthEnd(2))
+ tm.assert_index_equal(result, idx.asfreq("2M"))
+ assert result.freq == "2ME"
+
+ result = PeriodIndex(idx, freq="D")
+ exp = idx.asfreq("D", "e")
+ tm.assert_index_equal(result, exp)
+
+ def test_constructor_datetime64arr(self):
+ vals = np.arange(100000, 100000 + 10000, 100, dtype=np.int64)
+ vals = vals.view(np.dtype("M8[us]"))
+
+ pi = PeriodIndex(vals, freq="D")
+
+ expected = PeriodIndex(vals.astype("M8[ns]"), freq="D")
+ tm.assert_index_equal(pi, expected)
+
+ @pytest.mark.parametrize("box", [None, "series", "index"])
+ def test_constructor_datetime64arr_ok(self, box):
+ # https://github.com/pandas-dev/pandas/issues/23438
+ data = date_range("2017", periods=4, freq="ME")
+ if box is None:
+ data = data._values
+ elif box == "series":
+ data = Series(data)
+
+ result = PeriodIndex(data, freq="D")
+ expected = PeriodIndex(
+ ["2017-01-31", "2017-02-28", "2017-03-31", "2017-04-30"], freq="D"
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_dtype(self):
+ # passing a dtype with a tz should localize
+ idx = PeriodIndex(["2013-01", "2013-03"], dtype="period[M]")
+ exp = PeriodIndex(["2013-01", "2013-03"], freq="M")
+ tm.assert_index_equal(idx, exp)
+ assert idx.dtype == "period[M]"
+
+ idx = PeriodIndex(["2013-01-05", "2013-03-05"], dtype="period[3D]")
+ exp = PeriodIndex(["2013-01-05", "2013-03-05"], freq="3D")
+ tm.assert_index_equal(idx, exp)
+ assert idx.dtype == "period[3D]"
+
+ # if we already have a freq and its not the same, then asfreq
+ # (not changed)
+ idx = PeriodIndex(["2013-01-01", "2013-01-02"], freq="D")
+
+ res = PeriodIndex(idx, dtype="period[M]")
+ exp = PeriodIndex(["2013-01", "2013-01"], freq="M")
+ tm.assert_index_equal(res, exp)
+ assert res.dtype == "period[M]"
+
+ res = PeriodIndex(idx, freq="M")
+ tm.assert_index_equal(res, exp)
+ assert res.dtype == "period[M]"
+
+ msg = "specified freq and dtype are different"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ PeriodIndex(["2011-01"], freq="M", dtype="period[D]")
+
+ def test_constructor_empty(self):
+ idx = PeriodIndex([], freq="M")
+ assert isinstance(idx, PeriodIndex)
+ assert len(idx) == 0
+ assert idx.freq == "ME"
+
+ with pytest.raises(ValueError, match="freq not specified"):
+ PeriodIndex([])
+
+ def test_constructor_pi_nat(self):
+ idx = PeriodIndex(
+ [Period("2011-01", freq="M"), NaT, Period("2011-01", freq="M")]
+ )
+ exp = PeriodIndex(["2011-01", "NaT", "2011-01"], freq="M")
+ tm.assert_index_equal(idx, exp)
+
+ idx = PeriodIndex(
+ np.array([Period("2011-01", freq="M"), NaT, Period("2011-01", freq="M")])
+ )
+ tm.assert_index_equal(idx, exp)
+
+ idx = PeriodIndex(
+ [NaT, NaT, Period("2011-01", freq="M"), Period("2011-01", freq="M")]
+ )
+ exp = PeriodIndex(["NaT", "NaT", "2011-01", "2011-01"], freq="M")
+ tm.assert_index_equal(idx, exp)
+
+ idx = PeriodIndex(
+ np.array(
+ [NaT, NaT, Period("2011-01", freq="M"), Period("2011-01", freq="M")]
+ )
+ )
+ tm.assert_index_equal(idx, exp)
+
+ idx = PeriodIndex([NaT, NaT, "2011-01", "2011-01"], freq="M")
+ tm.assert_index_equal(idx, exp)
+
+ with pytest.raises(ValueError, match="freq not specified"):
+ PeriodIndex([NaT, NaT])
+
+ with pytest.raises(ValueError, match="freq not specified"):
+ PeriodIndex(np.array([NaT, NaT]))
+
+ with pytest.raises(ValueError, match="freq not specified"):
+ PeriodIndex(["NaT", "NaT"])
+
+ with pytest.raises(ValueError, match="freq not specified"):
+ PeriodIndex(np.array(["NaT", "NaT"]))
+
+ def test_constructor_incompat_freq(self):
+ msg = "Input has different freq=D from PeriodIndex\\(freq=M\\)"
+
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ PeriodIndex([Period("2011-01", freq="M"), NaT, Period("2011-01", freq="D")])
+
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ PeriodIndex(
+ np.array(
+ [Period("2011-01", freq="M"), NaT, Period("2011-01", freq="D")]
+ )
+ )
+
+ # first element is NaT
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ PeriodIndex([NaT, Period("2011-01", freq="M"), Period("2011-01", freq="D")])
+
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ PeriodIndex(
+ np.array(
+ [NaT, Period("2011-01", freq="M"), Period("2011-01", freq="D")]
+ )
+ )
+
+ def test_constructor_mixed(self):
+ idx = PeriodIndex(["2011-01", NaT, Period("2011-01", freq="M")])
+ exp = PeriodIndex(["2011-01", "NaT", "2011-01"], freq="M")
+ tm.assert_index_equal(idx, exp)
+
+ idx = PeriodIndex(["NaT", NaT, Period("2011-01", freq="M")])
+ exp = PeriodIndex(["NaT", "NaT", "2011-01"], freq="M")
+ tm.assert_index_equal(idx, exp)
+
+ idx = PeriodIndex([Period("2011-01-01", freq="D"), NaT, "2012-01-01"])
+ exp = PeriodIndex(["2011-01-01", "NaT", "2012-01-01"], freq="D")
+ tm.assert_index_equal(idx, exp)
+
+ @pytest.mark.parametrize("floats", [[1.1, 2.1], np.array([1.1, 2.1])])
+ def test_constructor_floats(self, floats):
+ msg = "PeriodIndex does not allow floating point in construction"
+ with pytest.raises(TypeError, match=msg):
+ PeriodIndex(floats)
+
+ def test_constructor_year_and_quarter(self):
+ year = Series([2001, 2002, 2003])
+ quarter = year - 2000
+ idx = PeriodIndex.from_fields(year=year, quarter=quarter)
+ strs = [f"{t[0]:d}Q{t[1]:d}" for t in zip(quarter, year)]
+ lops = list(map(Period, strs))
+ p = PeriodIndex(lops)
+ tm.assert_index_equal(p, idx)
+
+ def test_constructor_freq_mult(self):
+ # GH #7811
+ pidx = period_range(start="2014-01", freq="2M", periods=4)
+ expected = PeriodIndex(["2014-01", "2014-03", "2014-05", "2014-07"], freq="2M")
+ tm.assert_index_equal(pidx, expected)
+
+ pidx = period_range(start="2014-01-02", end="2014-01-15", freq="3D")
+ expected = PeriodIndex(
+ ["2014-01-02", "2014-01-05", "2014-01-08", "2014-01-11", "2014-01-14"],
+ freq="3D",
+ )
+ tm.assert_index_equal(pidx, expected)
+
+ pidx = period_range(end="2014-01-01 17:00", freq="4h", periods=3)
+ expected = PeriodIndex(
+ ["2014-01-01 09:00", "2014-01-01 13:00", "2014-01-01 17:00"], freq="4h"
+ )
+ tm.assert_index_equal(pidx, expected)
+
+ msg = "Frequency must be positive, because it represents span: -1M"
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex(["2011-01"], freq="-1M")
+
+ msg = "Frequency must be positive, because it represents span: 0M"
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex(["2011-01"], freq="0M")
+
+ msg = "Frequency must be positive, because it represents span: 0M"
+ with pytest.raises(ValueError, match=msg):
+ period_range("2011-01", periods=3, freq="0M")
+
+ @pytest.mark.parametrize(
+ "freq_offset, freq_period",
+ [
+ ("YE", "Y"),
+ ("ME", "M"),
+ ("D", "D"),
+ ("min", "min"),
+ ("s", "s"),
+ ],
+ )
+ @pytest.mark.parametrize("mult", [1, 2, 3, 4, 5])
+ def test_constructor_freq_mult_dti_compat(self, mult, freq_offset, freq_period):
+ freqstr_offset = str(mult) + freq_offset
+ freqstr_period = str(mult) + freq_period
+ pidx = period_range(start="2014-04-01", freq=freqstr_period, periods=10)
+ expected = date_range(
+ start="2014-04-01", freq=freqstr_offset, periods=10
+ ).to_period(freqstr_period)
+ tm.assert_index_equal(pidx, expected)
+
+ @pytest.mark.parametrize("mult", [1, 2, 3, 4, 5])
+ def test_constructor_freq_mult_dti_compat_month(self, mult):
+ pidx = period_range(start="2014-04-01", freq=f"{mult}M", periods=10)
+ expected = date_range(
+ start="2014-04-01", freq=f"{mult}ME", periods=10
+ ).to_period(f"{mult}M")
+ tm.assert_index_equal(pidx, expected)
+
+ def test_constructor_freq_combined(self):
+ for freq in ["1D1h", "1h1D"]:
+ pidx = PeriodIndex(["2016-01-01", "2016-01-02"], freq=freq)
+ expected = PeriodIndex(["2016-01-01 00:00", "2016-01-02 00:00"], freq="25h")
+ for freq in ["1D1h", "1h1D"]:
+ pidx = period_range(start="2016-01-01", periods=2, freq=freq)
+ expected = PeriodIndex(["2016-01-01 00:00", "2016-01-02 01:00"], freq="25h")
+ tm.assert_index_equal(pidx, expected)
+
+ def test_period_range_length(self):
+ pi = period_range(freq="Y", start="1/1/2001", end="12/1/2009")
+ assert len(pi) == 9
+
+ pi = period_range(freq="Q", start="1/1/2001", end="12/1/2009")
+ assert len(pi) == 4 * 9
+
+ pi = period_range(freq="M", start="1/1/2001", end="12/1/2009")
+ assert len(pi) == 12 * 9
+
+ pi = period_range(freq="D", start="1/1/2001", end="12/31/2009")
+ assert len(pi) == 365 * 9 + 2
+
+ msg = "Period with BDay freq is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ pi = period_range(freq="B", start="1/1/2001", end="12/31/2009")
+ assert len(pi) == 261 * 9
+
+ pi = period_range(freq="h", start="1/1/2001", end="12/31/2001 23:00")
+ assert len(pi) == 365 * 24
+
+ pi = period_range(freq="Min", start="1/1/2001", end="1/1/2001 23:59")
+ assert len(pi) == 24 * 60
+
+ pi = period_range(freq="s", start="1/1/2001", end="1/1/2001 23:59:59")
+ assert len(pi) == 24 * 60 * 60
+
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ start = Period("02-Apr-2005", "B")
+ i1 = period_range(start=start, periods=20)
+ assert len(i1) == 20
+ assert i1.freq == start.freq
+ assert i1[0] == start
+
+ end_intv = Period("2006-12-31", "W")
+ i1 = period_range(end=end_intv, periods=10)
+ assert len(i1) == 10
+ assert i1.freq == end_intv.freq
+ assert i1[-1] == end_intv
+
+ msg = "'w' is deprecated and will be removed in a future version."
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ end_intv = Period("2006-12-31", "1w")
+ i2 = period_range(end=end_intv, periods=10)
+ assert len(i1) == len(i2)
+ assert (i1 == i2).all()
+ assert i1.freq == i2.freq
+
+ def test_infer_freq_from_first_element(self):
+ msg = "Period with BDay freq is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ start = Period("02-Apr-2005", "B")
+ end_intv = Period("2005-05-01", "B")
+ period_range(start=start, end=end_intv)
+
+ # infer freq from first element
+ i2 = PeriodIndex([end_intv, Period("2005-05-05", "B")])
+ assert len(i2) == 2
+ assert i2[0] == end_intv
+
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ i2 = PeriodIndex(np.array([end_intv, Period("2005-05-05", "B")]))
+ assert len(i2) == 2
+ assert i2[0] == end_intv
+
+ def test_mixed_freq_raises(self):
+ # Mixed freq should fail
+ msg = "Period with BDay freq is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ end_intv = Period("2005-05-01", "B")
+
+ vals = [end_intv, Period("2006-12-31", "W")]
+ msg = r"Input has different freq=W-SUN from PeriodIndex\(freq=B\)"
+ depr_msg = r"PeriodDtype\[B\] is deprecated"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ with tm.assert_produces_warning(FutureWarning, match=depr_msg):
+ PeriodIndex(vals)
+ vals = np.array(vals)
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ with tm.assert_produces_warning(FutureWarning, match=depr_msg):
+ PeriodIndex(vals)
+
+ @pytest.mark.parametrize(
+ "freq", ["M", "Q", "Y", "D", "B", "min", "s", "ms", "us", "ns", "h"]
+ )
+ @pytest.mark.filterwarnings(
+ r"ignore:Period with BDay freq is deprecated:FutureWarning"
+ )
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_recreate_from_data(self, freq):
+ org = period_range(start="2001/04/01", freq=freq, periods=1)
+ idx = PeriodIndex(org.values, freq=freq)
+ tm.assert_index_equal(idx, org)
+
+ def test_map_with_string_constructor(self):
+ raw = [2005, 2007, 2009]
+ index = PeriodIndex(raw, freq="Y")
+
+ expected = Index([str(num) for num in raw])
+ res = index.map(str)
+
+ # should return an Index
+ assert isinstance(res, Index)
+
+ # preserve element types
+ assert all(isinstance(resi, str) for resi in res)
+
+ # lastly, values should compare equal
+ tm.assert_index_equal(res, expected)
+
+
+class TestSimpleNew:
+ def test_constructor_simple_new(self):
+ idx = period_range("2007-01", name="p", periods=2, freq="M")
+
+ with pytest.raises(AssertionError, match=""):
+ idx._simple_new(idx, name="p")
+
+ result = idx._simple_new(idx._data, name="p")
+ tm.assert_index_equal(result, idx)
+
+ msg = "Should be numpy array of type i8"
+ with pytest.raises(AssertionError, match=msg):
+ # Need ndarray, not int64 Index
+ type(idx._data)._simple_new(Index(idx.asi8), dtype=idx.dtype)
+
+ arr = type(idx._data)._simple_new(idx.asi8, dtype=idx.dtype)
+ result = idx._simple_new(arr, name="p")
+ tm.assert_index_equal(result, idx)
+
+ def test_constructor_simple_new_empty(self):
+ # GH13079
+ idx = PeriodIndex([], freq="M", name="p")
+ with pytest.raises(AssertionError, match=""):
+ idx._simple_new(idx, name="p")
+
+ result = idx._simple_new(idx._data, name="p")
+ tm.assert_index_equal(result, idx)
+
+ @pytest.mark.parametrize("floats", [[1.1, 2.1], np.array([1.1, 2.1])])
+ def test_period_index_simple_new_disallows_floats(self, floats):
+ with pytest.raises(AssertionError, match="= -1"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ msg = "index -5 is out of bounds for( axis 0 with)? size 3"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
+
+
+class TestGetValue:
+ @pytest.mark.parametrize("freq", ["h", "D"])
+ def test_get_value_datetime_hourly(self, freq):
+ # get_loc and get_value should treat datetime objects symmetrically
+ # TODO: this test used to test get_value, which is removed in 2.0.
+ # should this test be moved somewhere, or is what's left redundant?
+ dti = date_range("2016-01-01", periods=3, freq="MS")
+ pi = dti.to_period(freq)
+ ser = Series(range(7, 10), index=pi)
+
+ ts = dti[0]
+
+ assert pi.get_loc(ts) == 0
+ assert ser[ts] == 7
+ assert ser.loc[ts] == 7
+
+ ts2 = ts + Timedelta(hours=3)
+ if freq == "h":
+ with pytest.raises(KeyError, match="2016-01-01 03:00"):
+ pi.get_loc(ts2)
+ with pytest.raises(KeyError, match="2016-01-01 03:00"):
+ ser[ts2]
+ with pytest.raises(KeyError, match="2016-01-01 03:00"):
+ ser.loc[ts2]
+ else:
+ assert pi.get_loc(ts2) == 0
+ assert ser[ts2] == 7
+ assert ser.loc[ts2] == 7
+
+
+class TestContains:
+ def test_contains(self):
+ # GH 17717
+ p0 = Period("2017-09-01")
+ p1 = Period("2017-09-02")
+ p2 = Period("2017-09-03")
+ p3 = Period("2017-09-04")
+
+ ps0 = [p0, p1, p2]
+ idx0 = PeriodIndex(ps0)
+
+ for p in ps0:
+ assert p in idx0
+ assert str(p) in idx0
+
+ # GH#31172
+ # Higher-resolution period-like are _not_ considered as contained
+ key = "2017-09-01 00:00:01"
+ assert key not in idx0
+ with pytest.raises(KeyError, match=key):
+ idx0.get_loc(key)
+
+ assert "2017-09" in idx0
+
+ assert p3 not in idx0
+
+ def test_contains_freq_mismatch(self):
+ rng = period_range("2007-01", freq="M", periods=10)
+
+ assert Period("2007-01", freq="M") in rng
+ assert Period("2007-01", freq="D") not in rng
+ assert Period("2007-01", freq="2M") not in rng
+
+ def test_contains_nat(self):
+ # see gh-13582
+ idx = period_range("2007-01", freq="M", periods=10)
+ assert NaT not in idx
+ assert None not in idx
+ assert float("nan") not in idx
+ assert np.nan not in idx
+
+ idx = PeriodIndex(["2011-01", "NaT", "2011-02"], freq="M")
+ assert NaT in idx
+ assert None in idx
+ assert float("nan") in idx
+ assert np.nan in idx
+
+
+class TestAsOfLocs:
+ def test_asof_locs_mismatched_type(self):
+ dti = date_range("2016-01-01", periods=3)
+ pi = dti.to_period("D")
+ pi2 = dti.to_period("h")
+
+ mask = np.array([0, 1, 0], dtype=bool)
+
+ msg = "must be DatetimeIndex or PeriodIndex"
+ with pytest.raises(TypeError, match=msg):
+ pi.asof_locs(pd.Index(pi.asi8, dtype=np.int64), mask)
+
+ with pytest.raises(TypeError, match=msg):
+ pi.asof_locs(pd.Index(pi.asi8, dtype=np.float64), mask)
+
+ with pytest.raises(TypeError, match=msg):
+ # TimedeltaIndex
+ pi.asof_locs(dti - dti, mask)
+
+ msg = "Input has different freq=h"
+ with pytest.raises(libperiod.IncompatibleFrequency, match=msg):
+ pi.asof_locs(pi2, mask)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f733b358f772f5b779aad9ba4837b78172eb16f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_join.py
@@ -0,0 +1,56 @@
+import numpy as np
+
+from pandas import (
+ DataFrame,
+ Index,
+ PeriodIndex,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestJoin:
+ def test_join_outer_indexer(self):
+ pi = period_range("1/1/2000", "1/20/2000", freq="D")
+
+ result = pi._outer_indexer(pi)
+ tm.assert_extension_array_equal(result[0], pi._values)
+ tm.assert_numpy_array_equal(result[1], np.arange(len(pi), dtype=np.intp))
+ tm.assert_numpy_array_equal(result[2], np.arange(len(pi), dtype=np.intp))
+
+ def test_joins(self, join_type):
+ index = period_range("1/1/2000", "1/20/2000", freq="D")
+
+ joined = index.join(index[:-5], how=join_type)
+
+ assert isinstance(joined, PeriodIndex)
+ assert joined.freq == index.freq
+
+ def test_join_self(self, join_type):
+ index = period_range("1/1/2000", "1/20/2000", freq="D")
+
+ res = index.join(index, how=join_type)
+ assert index is res
+
+ def test_join_does_not_recur(self):
+ df = DataFrame(
+ np.ones((3, 2)),
+ index=date_range("2020-01-01", periods=3),
+ columns=period_range("2020-01-01", periods=2),
+ )
+ ser = df.iloc[:2, 0]
+
+ res = ser.index.join(df.columns, how="outer")
+ expected = Index(
+ [ser.index[0], ser.index[1], df.columns[0], df.columns[1]], object
+ )
+ tm.assert_index_equal(res, expected)
+
+ def test_join_mismatched_freq_raises(self):
+ # pre-GH#55782 this raises IncompatibleFrequency
+ index = period_range("1/1/2000", "1/20/2000", freq="D")
+ index3 = period_range("1/1/2000", "1/20/2000", freq="2D")
+ result = index.join(index3)
+ expected = index.astype(object).join(index3.astype(object))
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_monotonic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_monotonic.py
new file mode 100644
index 0000000000000000000000000000000000000000..15cb8f71cdcf3221800e6dca43390ae79114a9df
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_monotonic.py
@@ -0,0 +1,42 @@
+from pandas import (
+ Period,
+ PeriodIndex,
+)
+
+
+def test_is_monotonic_increasing():
+ # GH#17717
+ p0 = Period("2017-09-01")
+ p1 = Period("2017-09-02")
+ p2 = Period("2017-09-03")
+
+ idx_inc0 = PeriodIndex([p0, p1, p2])
+ idx_inc1 = PeriodIndex([p0, p1, p1])
+ idx_dec0 = PeriodIndex([p2, p1, p0])
+ idx_dec1 = PeriodIndex([p2, p1, p1])
+ idx = PeriodIndex([p1, p2, p0])
+
+ assert idx_inc0.is_monotonic_increasing is True
+ assert idx_inc1.is_monotonic_increasing is True
+ assert idx_dec0.is_monotonic_increasing is False
+ assert idx_dec1.is_monotonic_increasing is False
+ assert idx.is_monotonic_increasing is False
+
+
+def test_is_monotonic_decreasing():
+ # GH#17717
+ p0 = Period("2017-09-01")
+ p1 = Period("2017-09-02")
+ p2 = Period("2017-09-03")
+
+ idx_inc0 = PeriodIndex([p0, p1, p2])
+ idx_inc1 = PeriodIndex([p0, p1, p1])
+ idx_dec0 = PeriodIndex([p2, p1, p0])
+ idx_dec1 = PeriodIndex([p2, p1, p1])
+ idx = PeriodIndex([p1, p2, p0])
+
+ assert idx_inc0.is_monotonic_decreasing is False
+ assert idx_inc1.is_monotonic_decreasing is False
+ assert idx_dec0.is_monotonic_decreasing is True
+ assert idx_dec1.is_monotonic_decreasing is True
+ assert idx.is_monotonic_decreasing is False
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_partial_slicing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_partial_slicing.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d173d850583f13333b081caa7e2e398ff910e70
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_partial_slicing.py
@@ -0,0 +1,192 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ PeriodIndex,
+ Series,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestPeriodIndex:
+ def test_getitem_periodindex_duplicates_string_slice(self):
+ # monotonic
+ idx = PeriodIndex([2000, 2007, 2007, 2009, 2009], freq="Y-JUN")
+ ts = Series(np.random.default_rng(2).standard_normal(len(idx)), index=idx)
+ original = ts.copy()
+
+ result = ts["2007"]
+ expected = ts[1:3]
+ tm.assert_series_equal(result, expected)
+ result[:] = 1
+ tm.assert_series_equal(ts, original)
+
+ # not monotonic
+ idx = PeriodIndex([2000, 2007, 2007, 2009, 2007], freq="Y-JUN")
+ ts = Series(np.random.default_rng(2).standard_normal(len(idx)), index=idx)
+
+ result = ts["2007"]
+ expected = ts[idx == "2007"]
+ tm.assert_series_equal(result, expected)
+
+ def test_getitem_periodindex_quarter_string(self):
+ pi = PeriodIndex(["2Q05", "3Q05", "4Q05", "1Q06", "2Q06"], freq="Q")
+ ser = Series(np.random.default_rng(2).random(len(pi)), index=pi).cumsum()
+ # Todo: fix these accessors!
+ assert ser["05Q4"] == ser.iloc[2]
+
+ def test_pindex_slice_index(self):
+ pi = period_range(start="1/1/10", end="12/31/12", freq="M")
+ s = Series(np.random.default_rng(2).random(len(pi)), index=pi)
+ res = s["2010"]
+ exp = s[0:12]
+ tm.assert_series_equal(res, exp)
+ res = s["2011"]
+ exp = s[12:24]
+ tm.assert_series_equal(res, exp)
+
+ @pytest.mark.parametrize("make_range", [date_range, period_range])
+ def test_range_slice_day(self, make_range):
+ # GH#6716
+ idx = make_range(start="2013/01/01", freq="D", periods=400)
+
+ msg = "slice indices must be integers or None or have an __index__ method"
+ # slices against index should raise IndexError
+ values = [
+ "2014",
+ "2013/02",
+ "2013/01/02",
+ "2013/02/01 9H",
+ "2013/02/01 09:00",
+ ]
+ for v in values:
+ with pytest.raises(TypeError, match=msg):
+ idx[v:]
+
+ s = Series(np.random.default_rng(2).random(len(idx)), index=idx)
+
+ tm.assert_series_equal(s["2013/01/02":], s[1:])
+ tm.assert_series_equal(s["2013/01/02":"2013/01/05"], s[1:5])
+ tm.assert_series_equal(s["2013/02":], s[31:])
+ tm.assert_series_equal(s["2014":], s[365:])
+
+ invalid = ["2013/02/01 9H", "2013/02/01 09:00"]
+ for v in invalid:
+ with pytest.raises(TypeError, match=msg):
+ idx[v:]
+
+ @pytest.mark.parametrize("make_range", [date_range, period_range])
+ def test_range_slice_seconds(self, make_range):
+ # GH#6716
+ idx = make_range(start="2013/01/01 09:00:00", freq="s", periods=4000)
+ msg = "slice indices must be integers or None or have an __index__ method"
+
+ # slices against index should raise IndexError
+ values = [
+ "2014",
+ "2013/02",
+ "2013/01/02",
+ "2013/02/01 9H",
+ "2013/02/01 09:00",
+ ]
+ for v in values:
+ with pytest.raises(TypeError, match=msg):
+ idx[v:]
+
+ s = Series(np.random.default_rng(2).random(len(idx)), index=idx)
+
+ tm.assert_series_equal(s["2013/01/01 09:05":"2013/01/01 09:10"], s[300:660])
+ tm.assert_series_equal(s["2013/01/01 10:00":"2013/01/01 10:05"], s[3600:3960])
+ tm.assert_series_equal(s["2013/01/01 10H":], s[3600:])
+ tm.assert_series_equal(s[:"2013/01/01 09:30"], s[:1860])
+ for d in ["2013/01/01", "2013/01", "2013"]:
+ tm.assert_series_equal(s[d:], s)
+
+ @pytest.mark.parametrize("make_range", [date_range, period_range])
+ def test_range_slice_outofbounds(self, make_range):
+ # GH#5407
+ idx = make_range(start="2013/10/01", freq="D", periods=10)
+
+ df = DataFrame({"units": [100 + i for i in range(10)]}, index=idx)
+ empty = DataFrame(index=idx[:0], columns=["units"])
+ empty["units"] = empty["units"].astype("int64")
+
+ tm.assert_frame_equal(df["2013/09/01":"2013/09/30"], empty)
+ tm.assert_frame_equal(df["2013/09/30":"2013/10/02"], df.iloc[:2])
+ tm.assert_frame_equal(df["2013/10/01":"2013/10/02"], df.iloc[:2])
+ tm.assert_frame_equal(df["2013/10/02":"2013/09/30"], empty)
+ tm.assert_frame_equal(df["2013/10/15":"2013/10/17"], empty)
+ tm.assert_frame_equal(df["2013-06":"2013-09"], empty)
+ tm.assert_frame_equal(df["2013-11":"2013-12"], empty)
+
+ @pytest.mark.parametrize("make_range", [date_range, period_range])
+ def test_maybe_cast_slice_bound(self, make_range, frame_or_series):
+ idx = make_range(start="2013/10/01", freq="D", periods=10)
+
+ obj = DataFrame({"units": [100 + i for i in range(10)]}, index=idx)
+ obj = tm.get_obj(obj, frame_or_series)
+
+ msg = (
+ f"cannot do slice indexing on {type(idx).__name__} with "
+ r"these indexers \[foo\] of type str"
+ )
+
+ # Check the lower-level calls are raising where expected.
+ with pytest.raises(TypeError, match=msg):
+ idx._maybe_cast_slice_bound("foo", "left")
+ with pytest.raises(TypeError, match=msg):
+ idx.get_slice_bound("foo", "left")
+
+ with pytest.raises(TypeError, match=msg):
+ obj["2013/09/30":"foo"]
+ with pytest.raises(TypeError, match=msg):
+ obj["foo":"2013/09/30"]
+ with pytest.raises(TypeError, match=msg):
+ obj.loc["2013/09/30":"foo"]
+ with pytest.raises(TypeError, match=msg):
+ obj.loc["foo":"2013/09/30"]
+
+ def test_partial_slice_doesnt_require_monotonicity(self):
+ # See also: DatetimeIndex test ofm the same name
+ dti = date_range("2014-01-01", periods=30, freq="30D")
+ pi = dti.to_period("D")
+
+ ser_montonic = Series(np.arange(30), index=pi)
+
+ shuffler = list(range(0, 30, 2)) + list(range(1, 31, 2))
+ ser = ser_montonic.iloc[shuffler]
+ nidx = ser.index
+
+ # Manually identified locations of year==2014
+ indexer_2014 = np.array(
+ [0, 1, 2, 3, 4, 5, 6, 15, 16, 17, 18, 19, 20], dtype=np.intp
+ )
+ assert (nidx[indexer_2014].year == 2014).all()
+ assert not (nidx[~indexer_2014].year == 2014).any()
+
+ result = nidx.get_loc("2014")
+ tm.assert_numpy_array_equal(result, indexer_2014)
+
+ expected = ser.iloc[indexer_2014]
+ result = ser.loc["2014"]
+ tm.assert_series_equal(result, expected)
+
+ result = ser["2014"]
+ tm.assert_series_equal(result, expected)
+
+ # Manually identified locations where ser.index is within Mat 2015
+ indexer_may2015 = np.array([23], dtype=np.intp)
+ assert nidx[23].year == 2015 and nidx[23].month == 5
+
+ result = nidx.get_loc("May 2015")
+ tm.assert_numpy_array_equal(result, indexer_may2015)
+
+ expected = ser.iloc[indexer_may2015]
+ result = ser.loc["May 2015"]
+ tm.assert_series_equal(result, expected)
+
+ result = ser["May 2015"]
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_period.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_period.py
new file mode 100644
index 0000000000000000000000000000000000000000..d465225da7f2411d6a05b90c06240b8c3a1486a2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_period.py
@@ -0,0 +1,233 @@
+import numpy as np
+import pytest
+
+from pandas.errors import IncompatibleFrequency
+
+from pandas import (
+ Index,
+ NaT,
+ Period,
+ PeriodIndex,
+ Series,
+ date_range,
+ offsets,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestPeriodIndex:
+ def test_view_asi8(self):
+ idx = PeriodIndex([], freq="M")
+
+ exp = np.array([], dtype=np.int64)
+ tm.assert_numpy_array_equal(idx.view("i8"), exp)
+ tm.assert_numpy_array_equal(idx.asi8, exp)
+
+ idx = PeriodIndex(["2011-01", NaT], freq="M")
+
+ exp = np.array([492, -9223372036854775808], dtype=np.int64)
+ tm.assert_numpy_array_equal(idx.view("i8"), exp)
+ tm.assert_numpy_array_equal(idx.asi8, exp)
+
+ exp = np.array([14975, -9223372036854775808], dtype=np.int64)
+ idx = PeriodIndex(["2011-01-01", NaT], freq="D")
+ tm.assert_numpy_array_equal(idx.view("i8"), exp)
+ tm.assert_numpy_array_equal(idx.asi8, exp)
+
+ def test_values(self):
+ idx = PeriodIndex([], freq="M")
+
+ exp = np.array([], dtype=object)
+ tm.assert_numpy_array_equal(idx.values, exp)
+ tm.assert_numpy_array_equal(idx.to_numpy(), exp)
+
+ exp = np.array([], dtype=np.int64)
+ tm.assert_numpy_array_equal(idx.asi8, exp)
+
+ idx = PeriodIndex(["2011-01", NaT], freq="M")
+
+ exp = np.array([Period("2011-01", freq="M"), NaT], dtype=object)
+ tm.assert_numpy_array_equal(idx.values, exp)
+ tm.assert_numpy_array_equal(idx.to_numpy(), exp)
+ exp = np.array([492, -9223372036854775808], dtype=np.int64)
+ tm.assert_numpy_array_equal(idx.asi8, exp)
+
+ idx = PeriodIndex(["2011-01-01", NaT], freq="D")
+
+ exp = np.array([Period("2011-01-01", freq="D"), NaT], dtype=object)
+ tm.assert_numpy_array_equal(idx.values, exp)
+ tm.assert_numpy_array_equal(idx.to_numpy(), exp)
+ exp = np.array([14975, -9223372036854775808], dtype=np.int64)
+ tm.assert_numpy_array_equal(idx.asi8, exp)
+
+ @pytest.mark.parametrize(
+ "field",
+ [
+ "year",
+ "month",
+ "day",
+ "hour",
+ "minute",
+ "second",
+ "weekofyear",
+ "week",
+ "dayofweek",
+ "day_of_week",
+ "dayofyear",
+ "day_of_year",
+ "quarter",
+ "qyear",
+ "days_in_month",
+ ],
+ )
+ @pytest.mark.parametrize(
+ "periodindex",
+ [
+ period_range(freq="Y", start="1/1/2001", end="12/1/2005"),
+ period_range(freq="Q", start="1/1/2001", end="12/1/2002"),
+ period_range(freq="M", start="1/1/2001", end="1/1/2002"),
+ period_range(freq="D", start="12/1/2001", end="6/1/2001"),
+ period_range(freq="h", start="12/31/2001", end="1/1/2002 23:00"),
+ period_range(freq="Min", start="12/31/2001", end="1/1/2002 00:20"),
+ period_range(
+ freq="s", start="12/31/2001 00:00:00", end="12/31/2001 00:05:00"
+ ),
+ period_range(end=Period("2006-12-31", "W"), periods=10),
+ ],
+ )
+ def test_fields(self, periodindex, field):
+ periods = list(periodindex)
+ ser = Series(periodindex)
+
+ field_idx = getattr(periodindex, field)
+ assert len(periodindex) == len(field_idx)
+ for x, val in zip(periods, field_idx):
+ assert getattr(x, field) == val
+
+ if len(ser) == 0:
+ return
+
+ field_s = getattr(ser.dt, field)
+ assert len(periodindex) == len(field_s)
+ for x, val in zip(periods, field_s):
+ assert getattr(x, field) == val
+
+ def test_is_(self):
+ create_index = lambda: period_range(freq="Y", start="1/1/2001", end="12/1/2009")
+ index = create_index()
+ assert index.is_(index)
+ assert not index.is_(create_index())
+ assert index.is_(index.view())
+ assert index.is_(index.view().view().view().view().view())
+ assert index.view().is_(index)
+ ind2 = index.view()
+ index.name = "Apple"
+ assert ind2.is_(index)
+ assert not index.is_(index[:])
+ assert not index.is_(index.asfreq("M"))
+ assert not index.is_(index.asfreq("Y"))
+
+ assert not index.is_(index - 2)
+ assert not index.is_(index - 0)
+
+ def test_index_unique(self):
+ idx = PeriodIndex([2000, 2007, 2007, 2009, 2009], freq="Y-JUN")
+ expected = PeriodIndex([2000, 2007, 2009], freq="Y-JUN")
+ tm.assert_index_equal(idx.unique(), expected)
+ assert idx.nunique() == 3
+
+ def test_pindex_fieldaccessor_nat(self):
+ idx = PeriodIndex(
+ ["2011-01", "2011-02", "NaT", "2012-03", "2012-04"], freq="D", name="name"
+ )
+
+ exp = Index([2011, 2011, -1, 2012, 2012], dtype=np.int64, name="name")
+ tm.assert_index_equal(idx.year, exp)
+ exp = Index([1, 2, -1, 3, 4], dtype=np.int64, name="name")
+ tm.assert_index_equal(idx.month, exp)
+
+ def test_pindex_multiples(self):
+ expected = PeriodIndex(
+ ["2011-01", "2011-03", "2011-05", "2011-07", "2011-09", "2011-11"],
+ freq="2M",
+ )
+
+ pi = period_range(start="1/1/11", end="12/31/11", freq="2M")
+ tm.assert_index_equal(pi, expected)
+ assert pi.freq == offsets.MonthEnd(2)
+ assert pi.freqstr == "2M"
+
+ pi = period_range(start="1/1/11", periods=6, freq="2M")
+ tm.assert_index_equal(pi, expected)
+ assert pi.freq == offsets.MonthEnd(2)
+ assert pi.freqstr == "2M"
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ @pytest.mark.filterwarnings("ignore:Period with BDay freq:FutureWarning")
+ def test_iteration(self):
+ index = period_range(start="1/1/10", periods=4, freq="B")
+
+ result = list(index)
+ assert isinstance(result[0], Period)
+ assert result[0].freq == index.freq
+
+ def test_with_multi_index(self):
+ # #1705
+ index = date_range("1/1/2012", periods=4, freq="12h")
+ index_as_arrays = [index.to_period(freq="D"), index.hour]
+
+ s = Series([0, 1, 2, 3], index_as_arrays)
+
+ assert isinstance(s.index.levels[0], PeriodIndex)
+
+ assert isinstance(s.index.values[0][0], Period)
+
+ def test_map(self):
+ # test_map_dictlike generally tests
+
+ index = PeriodIndex([2005, 2007, 2009], freq="Y")
+ result = index.map(lambda x: x.ordinal)
+ exp = Index([x.ordinal for x in index])
+ tm.assert_index_equal(result, exp)
+
+
+def test_maybe_convert_timedelta():
+ pi = PeriodIndex(["2000", "2001"], freq="D")
+ offset = offsets.Day(2)
+ assert pi._maybe_convert_timedelta(offset) == 2
+ assert pi._maybe_convert_timedelta(2) == 2
+
+ offset = offsets.BusinessDay()
+ msg = r"Input has different freq=B from PeriodIndex\(freq=D\)"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ pi._maybe_convert_timedelta(offset)
+
+
+@pytest.mark.parametrize("array", [True, False])
+def test_dunder_array(array):
+ obj = PeriodIndex(["2000-01-01", "2001-01-01"], freq="D")
+ if array:
+ obj = obj._data
+
+ expected = np.array([obj[0], obj[1]], dtype=object)
+ result = np.array(obj)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.asarray(obj)
+ tm.assert_numpy_array_equal(result, expected)
+
+ expected = obj.asi8
+ for dtype in ["i8", "int64", np.int64]:
+ result = np.array(obj, dtype=dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.asarray(obj, dtype=dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+ for dtype in ["float64", "int32", "uint64"]:
+ msg = "argument must be"
+ with pytest.raises(TypeError, match=msg):
+ np.array(obj, dtype=dtype)
+ with pytest.raises(TypeError, match=msg):
+ np.array(obj, dtype=getattr(np, dtype))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_period_range.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_period_range.py
new file mode 100644
index 0000000000000000000000000000000000000000..07e7b4461f203fd07e81c4159db064b43e8c15a1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_period_range.py
@@ -0,0 +1,243 @@
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ NaT,
+ Period,
+ PeriodIndex,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestPeriodRangeKeywords:
+ def test_required_arguments(self):
+ msg = (
+ "Of the three parameters: start, end, and periods, exactly two "
+ "must be specified"
+ )
+ with pytest.raises(ValueError, match=msg):
+ period_range("2011-1-1", "2012-1-1", "B")
+
+ def test_required_arguments2(self):
+ start = Period("02-Apr-2005", "D")
+ msg = (
+ "Of the three parameters: start, end, and periods, exactly two "
+ "must be specified"
+ )
+ with pytest.raises(ValueError, match=msg):
+ period_range(start=start)
+
+ def test_required_arguments3(self):
+ # not enough params
+ msg = (
+ "Of the three parameters: start, end, and periods, "
+ "exactly two must be specified"
+ )
+ with pytest.raises(ValueError, match=msg):
+ period_range(start="2017Q1")
+
+ with pytest.raises(ValueError, match=msg):
+ period_range(end="2017Q1")
+
+ with pytest.raises(ValueError, match=msg):
+ period_range(periods=5)
+
+ with pytest.raises(ValueError, match=msg):
+ period_range()
+
+ def test_required_arguments_too_many(self):
+ msg = (
+ "Of the three parameters: start, end, and periods, "
+ "exactly two must be specified"
+ )
+ with pytest.raises(ValueError, match=msg):
+ period_range(start="2017Q1", end="2018Q1", periods=8, freq="Q")
+
+ def test_start_end_non_nat(self):
+ # start/end NaT
+ msg = "start and end must not be NaT"
+ with pytest.raises(ValueError, match=msg):
+ period_range(start=NaT, end="2018Q1")
+ with pytest.raises(ValueError, match=msg):
+ period_range(start=NaT, end="2018Q1", freq="Q")
+
+ with pytest.raises(ValueError, match=msg):
+ period_range(start="2017Q1", end=NaT)
+ with pytest.raises(ValueError, match=msg):
+ period_range(start="2017Q1", end=NaT, freq="Q")
+
+ def test_periods_requires_integer(self):
+ # invalid periods param
+ msg = "periods must be an integer, got foo"
+ with pytest.raises(TypeError, match=msg):
+ period_range(start="2017Q1", periods="foo")
+
+
+class TestPeriodRange:
+ @pytest.mark.parametrize(
+ "freq_offset, freq_period",
+ [
+ ("D", "D"),
+ ("W", "W"),
+ ("QE", "Q"),
+ ("YE", "Y"),
+ ],
+ )
+ def test_construction_from_string(self, freq_offset, freq_period):
+ # non-empty
+ expected = date_range(
+ start="2017-01-01", periods=5, freq=freq_offset, name="foo"
+ ).to_period()
+ start, end = str(expected[0]), str(expected[-1])
+
+ result = period_range(start=start, end=end, freq=freq_period, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(start=start, periods=5, freq=freq_period, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(end=end, periods=5, freq=freq_period, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ # empty
+ expected = PeriodIndex([], freq=freq_period, name="foo")
+
+ result = period_range(start=start, periods=0, freq=freq_period, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(end=end, periods=0, freq=freq_period, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(start=end, end=start, freq=freq_period, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ def test_construction_from_string_monthly(self):
+ # non-empty
+ expected = date_range(
+ start="2017-01-01", periods=5, freq="ME", name="foo"
+ ).to_period()
+ start, end = str(expected[0]), str(expected[-1])
+
+ result = period_range(start=start, end=end, freq="M", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(start=start, periods=5, freq="M", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(end=end, periods=5, freq="M", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ # empty
+ expected = PeriodIndex([], freq="M", name="foo")
+
+ result = period_range(start=start, periods=0, freq="M", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(end=end, periods=0, freq="M", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(start=end, end=start, freq="M", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ def test_construction_from_period(self):
+ # upsampling
+ start, end = Period("2017Q1", freq="Q"), Period("2018Q1", freq="Q")
+ expected = date_range(
+ start="2017-03-31", end="2018-03-31", freq="ME", name="foo"
+ ).to_period()
+ result = period_range(start=start, end=end, freq="M", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ # downsampling
+ start = Period("2017-1", freq="M")
+ end = Period("2019-12", freq="M")
+ expected = date_range(
+ start="2017-01-31", end="2019-12-31", freq="QE", name="foo"
+ ).to_period()
+ result = period_range(start=start, end=end, freq="Q", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ # test for issue # 21793
+ start = Period("2017Q1", freq="Q")
+ end = Period("2018Q1", freq="Q")
+ idx = period_range(start=start, end=end, freq="Q", name="foo")
+ result = idx == idx.values
+ expected = np.array([True, True, True, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ # empty
+ expected = PeriodIndex([], freq="W", name="foo")
+
+ result = period_range(start=start, periods=0, freq="W", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(end=end, periods=0, freq="W", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = period_range(start=end, end=start, freq="W", name="foo")
+ tm.assert_index_equal(result, expected)
+
+ def test_mismatched_start_end_freq_raises(self):
+ depr_msg = "Period with BDay freq is deprecated"
+
+ end_w = Period("2006-12-31", "1W")
+ with tm.assert_produces_warning(FutureWarning, match=depr_msg):
+ start_b = Period("02-Apr-2005", "B")
+ end_b = Period("2005-05-01", "B")
+
+ msg = "start and end must have same freq"
+ with pytest.raises(ValueError, match=msg):
+ with tm.assert_produces_warning(FutureWarning, match=depr_msg):
+ period_range(start=start_b, end=end_w)
+
+ # without mismatch we are OK
+ with tm.assert_produces_warning(FutureWarning, match=depr_msg):
+ period_range(start=start_b, end=end_b)
+
+
+class TestPeriodRangeDisallowedFreqs:
+ def test_constructor_U(self):
+ # U was used as undefined period
+ with pytest.raises(ValueError, match="Invalid frequency: X"):
+ period_range("2007-1-1", periods=500, freq="X")
+
+ @pytest.mark.parametrize("freq_depr", ["2MIN", "2US", "2NS"])
+ def test_uppercase_freq_deprecated_from_time_series(self, freq_depr):
+ # GH#52536, GH#54939
+ msg = (
+ f"'{freq_depr[1:]}' is deprecated and will be removed in a "
+ f"future version, please use '{freq_depr.lower()[1:]}' instead."
+ )
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ period_range("2020-01-01 00:00:00 00:00", periods=2, freq=freq_depr)
+
+ @pytest.mark.parametrize("freq", ["2m", "2q-sep", "2y", "2H", "2S"])
+ def test_incorrect_case_freq_from_time_series_raises(self, freq):
+ # GH#52536, GH#54939, GH#59143
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ period_range(freq=freq, start="1/1/2001", end="12/1/2009")
+
+ @pytest.mark.parametrize("freq", ["2A", "2a", "2A-AUG", "2A-aug"])
+ def test_A_raises_from_time_series(self, freq):
+ msg = f"Invalid frequency: {freq}"
+
+ with pytest.raises(ValueError, match=msg):
+ period_range(freq=freq, start="1/1/2001", end="12/1/2009")
+
+ @pytest.mark.parametrize("freq", ["2w"])
+ def test_lowercase_freq_from_time_series_deprecated(self, freq):
+ # GH#52536, GH#54939
+ msg = (
+ f"'{freq[1:]}' is deprecated and will be removed in a "
+ f"future version, please use '{freq.upper()[1:]}' instead."
+ )
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ period_range(freq=freq, start="1/1/2001", end="12/1/2009")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_pickle.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_pickle.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d763aa36807179045e4520ae4d52f7658b1ca0f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_pickle.py
@@ -0,0 +1,26 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ NaT,
+ PeriodIndex,
+ period_range,
+)
+import pandas._testing as tm
+
+from pandas.tseries import offsets
+
+
+class TestPickle:
+ @pytest.mark.parametrize("freq", ["D", "M", "Y"])
+ def test_pickle_round_trip(self, freq, temp_file):
+ idx = PeriodIndex(["2016-05-16", "NaT", NaT, np.nan], freq=freq)
+ result = tm.round_trip_pickle(idx, temp_file)
+ tm.assert_index_equal(result, idx)
+
+ def test_pickle_freq(self, temp_file):
+ # GH#2891
+ prng = period_range("1/1/2011", "1/1/2012", freq="M")
+ new_prng = tm.round_trip_pickle(prng, temp_file)
+ assert new_prng.freq == offsets.MonthEnd()
+ assert new_prng.freqstr == "M"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_resolution.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_resolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..680bdaa2e2a44c9603c6465274e4f4cea35e8701
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_resolution.py
@@ -0,0 +1,23 @@
+import pytest
+
+import pandas as pd
+
+
+class TestResolution:
+ @pytest.mark.parametrize(
+ "freq,expected",
+ [
+ ("Y", "year"),
+ ("Q", "quarter"),
+ ("M", "month"),
+ ("D", "day"),
+ ("h", "hour"),
+ ("min", "minute"),
+ ("s", "second"),
+ ("ms", "millisecond"),
+ ("us", "microsecond"),
+ ],
+ )
+ def test_resolution(self, freq, expected):
+ idx = pd.period_range(start="2013-04-01", periods=30, freq=freq)
+ assert idx.resolution == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_scalar_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_scalar_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac0cbf6f860961b1fba73c4ace93c4c8fb5bfdc3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_scalar_compat.py
@@ -0,0 +1,38 @@
+"""Tests for PeriodIndex behaving like a vectorized Period scalar"""
+
+import pytest
+
+from pandas import (
+ Timedelta,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestPeriodIndexOps:
+ def test_start_time(self):
+ # GH#17157
+ index = period_range(freq="M", start="2016-01-01", end="2016-05-31")
+ expected_index = date_range("2016-01-01", end="2016-05-31", freq="MS")
+ tm.assert_index_equal(index.start_time, expected_index)
+
+ def test_end_time(self):
+ # GH#17157
+ index = period_range(freq="M", start="2016-01-01", end="2016-05-31")
+ expected_index = date_range("2016-01-01", end="2016-05-31", freq="ME")
+ expected_index += Timedelta(1, "D") - Timedelta(1, "us")
+ tm.assert_index_equal(index.end_time, expected_index)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ @pytest.mark.filterwarnings(
+ "ignore:Period with BDay freq is deprecated:FutureWarning"
+ )
+ def test_end_time_business_friday(self):
+ # GH#34449
+ pi = period_range("1990-01-05", freq="B", periods=1)
+ result = pi.end_time
+
+ dti = date_range("1990-01-05", freq="D", periods=1)._with_freq(None)
+ expected = dti + Timedelta(1, "D") - Timedelta(1, "us")
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_searchsorted.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_searchsorted.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b02a2f35fd0193bbc8133373299a0ac2cea38ea
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_searchsorted.py
@@ -0,0 +1,80 @@
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import IncompatibleFrequency
+
+from pandas import (
+ NaT,
+ Period,
+ PeriodIndex,
+)
+import pandas._testing as tm
+
+
+class TestSearchsorted:
+ @pytest.mark.parametrize("freq", ["D", "2D"])
+ def test_searchsorted(self, freq):
+ pidx = PeriodIndex(
+ ["2014-01-01", "2014-01-02", "2014-01-03", "2014-01-04", "2014-01-05"],
+ freq=freq,
+ )
+
+ p1 = Period("2014-01-01", freq=freq)
+ assert pidx.searchsorted(p1) == 0
+
+ p2 = Period("2014-01-04", freq=freq)
+ assert pidx.searchsorted(p2) == 3
+
+ assert pidx.searchsorted(NaT) == 5
+
+ msg = "Input has different freq=h from PeriodArray"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ pidx.searchsorted(Period("2014-01-01", freq="h"))
+
+ msg = "Input has different freq=5D from PeriodArray"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ pidx.searchsorted(Period("2014-01-01", freq="5D"))
+
+ def test_searchsorted_different_argument_classes(self, listlike_box):
+ pidx = PeriodIndex(
+ ["2014-01-01", "2014-01-02", "2014-01-03", "2014-01-04", "2014-01-05"],
+ freq="D",
+ )
+ result = pidx.searchsorted(listlike_box(pidx))
+ expected = np.arange(len(pidx), dtype=result.dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = pidx._data.searchsorted(listlike_box(pidx))
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_searchsorted_invalid(self):
+ pidx = PeriodIndex(
+ ["2014-01-01", "2014-01-02", "2014-01-03", "2014-01-04", "2014-01-05"],
+ freq="D",
+ )
+
+ other = np.array([0, 1], dtype=np.int64)
+
+ msg = "|".join(
+ [
+ "searchsorted requires compatible dtype or scalar",
+ "value should be a 'Period', 'NaT', or array of those. Got",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ pidx.searchsorted(other)
+
+ with pytest.raises(TypeError, match=msg):
+ pidx.searchsorted(other.astype("timedelta64[ns]"))
+
+ with pytest.raises(TypeError, match=msg):
+ pidx.searchsorted(np.timedelta64(4))
+
+ with pytest.raises(TypeError, match=msg):
+ pidx.searchsorted(np.timedelta64("NaT", "ms"))
+
+ with pytest.raises(TypeError, match=msg):
+ pidx.searchsorted(np.datetime64(4, "ns"))
+
+ with pytest.raises(TypeError, match=msg):
+ pidx.searchsorted(np.datetime64("NaT", "ns"))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fa7e8cd0d2df5982cc0c798fbfba4e0230df367
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_setops.py
@@ -0,0 +1,363 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ PeriodIndex,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+def _permute(obj):
+ return obj.take(np.random.default_rng(2).permutation(len(obj)))
+
+
+class TestPeriodIndex:
+ def test_union(self, sort):
+ # union
+ other1 = period_range("1/1/2000", freq="D", periods=5)
+ rng1 = period_range("1/6/2000", freq="D", periods=5)
+ expected1 = PeriodIndex(
+ [
+ "2000-01-06",
+ "2000-01-07",
+ "2000-01-08",
+ "2000-01-09",
+ "2000-01-10",
+ "2000-01-01",
+ "2000-01-02",
+ "2000-01-03",
+ "2000-01-04",
+ "2000-01-05",
+ ],
+ freq="D",
+ )
+
+ rng2 = period_range("1/1/2000", freq="D", periods=5)
+ other2 = period_range("1/4/2000", freq="D", periods=5)
+ expected2 = period_range("1/1/2000", freq="D", periods=8)
+
+ rng3 = period_range("1/1/2000", freq="D", periods=5)
+ other3 = PeriodIndex([], freq="D")
+ expected3 = period_range("1/1/2000", freq="D", periods=5)
+
+ rng4 = period_range("2000-01-01 09:00", freq="h", periods=5)
+ other4 = period_range("2000-01-02 09:00", freq="h", periods=5)
+ expected4 = PeriodIndex(
+ [
+ "2000-01-01 09:00",
+ "2000-01-01 10:00",
+ "2000-01-01 11:00",
+ "2000-01-01 12:00",
+ "2000-01-01 13:00",
+ "2000-01-02 09:00",
+ "2000-01-02 10:00",
+ "2000-01-02 11:00",
+ "2000-01-02 12:00",
+ "2000-01-02 13:00",
+ ],
+ freq="h",
+ )
+
+ rng5 = PeriodIndex(
+ ["2000-01-01 09:01", "2000-01-01 09:03", "2000-01-01 09:05"], freq="min"
+ )
+ other5 = PeriodIndex(
+ ["2000-01-01 09:01", "2000-01-01 09:05", "2000-01-01 09:08"], freq="min"
+ )
+ expected5 = PeriodIndex(
+ [
+ "2000-01-01 09:01",
+ "2000-01-01 09:03",
+ "2000-01-01 09:05",
+ "2000-01-01 09:08",
+ ],
+ freq="min",
+ )
+
+ rng6 = period_range("2000-01-01", freq="M", periods=7)
+ other6 = period_range("2000-04-01", freq="M", periods=7)
+ expected6 = period_range("2000-01-01", freq="M", periods=10)
+
+ rng7 = period_range("2003-01-01", freq="Y", periods=5)
+ other7 = period_range("1998-01-01", freq="Y", periods=8)
+ expected7 = PeriodIndex(
+ [
+ "2003",
+ "2004",
+ "2005",
+ "2006",
+ "2007",
+ "1998",
+ "1999",
+ "2000",
+ "2001",
+ "2002",
+ ],
+ freq="Y",
+ )
+
+ rng8 = PeriodIndex(
+ ["1/3/2000", "1/2/2000", "1/1/2000", "1/5/2000", "1/4/2000"], freq="D"
+ )
+ other8 = period_range("1/6/2000", freq="D", periods=5)
+ expected8 = PeriodIndex(
+ [
+ "1/3/2000",
+ "1/2/2000",
+ "1/1/2000",
+ "1/5/2000",
+ "1/4/2000",
+ "1/6/2000",
+ "1/7/2000",
+ "1/8/2000",
+ "1/9/2000",
+ "1/10/2000",
+ ],
+ freq="D",
+ )
+
+ for rng, other, expected in [
+ (rng1, other1, expected1),
+ (rng2, other2, expected2),
+ (rng3, other3, expected3),
+ (rng4, other4, expected4),
+ (rng5, other5, expected5),
+ (rng6, other6, expected6),
+ (rng7, other7, expected7),
+ (rng8, other8, expected8),
+ ]:
+ result_union = rng.union(other, sort=sort)
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result_union, expected)
+
+ def test_union_misc(self, sort):
+ index = period_range("1/1/2000", "1/20/2000", freq="D")
+
+ result = index[:-5].union(index[10:], sort=sort)
+ tm.assert_index_equal(result, index)
+
+ # not in order
+ result = _permute(index[:-5]).union(_permute(index[10:]), sort=sort)
+ if sort is False:
+ tm.assert_index_equal(result.sort_values(), index)
+ else:
+ tm.assert_index_equal(result, index)
+
+ # cast if different frequencies
+ index = period_range("1/1/2000", "1/20/2000", freq="D")
+ index2 = period_range("1/1/2000", "1/20/2000", freq="W-WED")
+ result = index.union(index2, sort=sort)
+ expected = index.astype(object).union(index2.astype(object), sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection(self, sort):
+ index = period_range("1/1/2000", "1/20/2000", freq="D")
+
+ result = index[:-5].intersection(index[10:], sort=sort)
+ tm.assert_index_equal(result, index[10:-5])
+
+ # not in order
+ left = _permute(index[:-5])
+ right = _permute(index[10:])
+ result = left.intersection(right, sort=sort)
+ if sort is False:
+ tm.assert_index_equal(result.sort_values(), index[10:-5])
+ else:
+ tm.assert_index_equal(result, index[10:-5])
+
+ # cast if different frequencies
+ index = period_range("1/1/2000", "1/20/2000", freq="D")
+ index2 = period_range("1/1/2000", "1/20/2000", freq="W-WED")
+
+ result = index.intersection(index2, sort=sort)
+ expected = pd.Index([], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ index3 = period_range("1/1/2000", "1/20/2000", freq="2D")
+ result = index.intersection(index3, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection_cases(self, sort):
+ base = period_range("6/1/2000", "6/30/2000", freq="D", name="idx")
+
+ # if target has the same name, it is preserved
+ rng2 = period_range("5/15/2000", "6/20/2000", freq="D", name="idx")
+ expected2 = period_range("6/1/2000", "6/20/2000", freq="D", name="idx")
+
+ # if target name is different, it will be reset
+ rng3 = period_range("5/15/2000", "6/20/2000", freq="D", name="other")
+ expected3 = period_range("6/1/2000", "6/20/2000", freq="D", name=None)
+
+ rng4 = period_range("7/1/2000", "7/31/2000", freq="D", name="idx")
+ expected4 = PeriodIndex([], name="idx", freq="D")
+
+ for rng, expected in [
+ (rng2, expected2),
+ (rng3, expected3),
+ (rng4, expected4),
+ ]:
+ result = base.intersection(rng, sort=sort)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ # non-monotonic
+ base = PeriodIndex(
+ ["2011-01-05", "2011-01-04", "2011-01-02", "2011-01-03"],
+ freq="D",
+ name="idx",
+ )
+
+ rng2 = PeriodIndex(
+ ["2011-01-04", "2011-01-02", "2011-02-02", "2011-02-03"],
+ freq="D",
+ name="idx",
+ )
+ expected2 = PeriodIndex(["2011-01-04", "2011-01-02"], freq="D", name="idx")
+
+ rng3 = PeriodIndex(
+ ["2011-01-04", "2011-01-02", "2011-02-02", "2011-02-03"],
+ freq="D",
+ name="other",
+ )
+ expected3 = PeriodIndex(["2011-01-04", "2011-01-02"], freq="D", name=None)
+
+ rng4 = period_range("7/1/2000", "7/31/2000", freq="D", name="idx")
+ expected4 = PeriodIndex([], freq="D", name="idx")
+
+ for rng, expected in [
+ (rng2, expected2),
+ (rng3, expected3),
+ (rng4, expected4),
+ ]:
+ result = base.intersection(rng, sort=sort)
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == "D"
+
+ # empty same freq
+ rng = date_range("6/1/2000", "6/15/2000", freq="min")
+ result = rng[0:0].intersection(rng)
+ assert len(result) == 0
+
+ result = rng.intersection(rng[0:0])
+ assert len(result) == 0
+
+ def test_difference(self, sort):
+ # diff
+ period_rng = ["1/3/2000", "1/2/2000", "1/1/2000", "1/5/2000", "1/4/2000"]
+ rng1 = PeriodIndex(period_rng, freq="D")
+ other1 = period_range("1/6/2000", freq="D", periods=5)
+ expected1 = rng1
+
+ rng2 = PeriodIndex(period_rng, freq="D")
+ other2 = period_range("1/4/2000", freq="D", periods=5)
+ expected2 = PeriodIndex(["1/3/2000", "1/2/2000", "1/1/2000"], freq="D")
+
+ rng3 = PeriodIndex(period_rng, freq="D")
+ other3 = PeriodIndex([], freq="D")
+ expected3 = rng3
+
+ period_rng = [
+ "2000-01-01 10:00",
+ "2000-01-01 09:00",
+ "2000-01-01 12:00",
+ "2000-01-01 11:00",
+ "2000-01-01 13:00",
+ ]
+ rng4 = PeriodIndex(period_rng, freq="h")
+ other4 = period_range("2000-01-02 09:00", freq="h", periods=5)
+ expected4 = rng4
+
+ rng5 = PeriodIndex(
+ ["2000-01-01 09:03", "2000-01-01 09:01", "2000-01-01 09:05"], freq="min"
+ )
+ other5 = PeriodIndex(["2000-01-01 09:01", "2000-01-01 09:05"], freq="min")
+ expected5 = PeriodIndex(["2000-01-01 09:03"], freq="min")
+
+ period_rng = [
+ "2000-02-01",
+ "2000-01-01",
+ "2000-06-01",
+ "2000-07-01",
+ "2000-05-01",
+ "2000-03-01",
+ "2000-04-01",
+ ]
+ rng6 = PeriodIndex(period_rng, freq="M")
+ other6 = period_range("2000-04-01", freq="M", periods=7)
+ expected6 = PeriodIndex(["2000-02-01", "2000-01-01", "2000-03-01"], freq="M")
+
+ period_rng = ["2003", "2007", "2006", "2005", "2004"]
+ rng7 = PeriodIndex(period_rng, freq="Y")
+ other7 = period_range("1998-01-01", freq="Y", periods=8)
+ expected7 = PeriodIndex(["2007", "2006"], freq="Y")
+
+ for rng, other, expected in [
+ (rng1, other1, expected1),
+ (rng2, other2, expected2),
+ (rng3, other3, expected3),
+ (rng4, other4, expected4),
+ (rng5, other5, expected5),
+ (rng6, other6, expected6),
+ (rng7, other7, expected7),
+ ]:
+ result_difference = rng.difference(other, sort=sort)
+ if sort is None and len(other):
+ # We dont sort (yet?) when empty GH#24959
+ expected = expected.sort_values()
+ tm.assert_index_equal(result_difference, expected)
+
+ def test_difference_freq(self, sort):
+ # GH14323: difference of Period MUST preserve frequency
+ # but the ability to union results must be preserved
+
+ index = period_range("20160920", "20160925", freq="D")
+
+ other = period_range("20160921", "20160924", freq="D")
+ expected = PeriodIndex(["20160920", "20160925"], freq="D")
+ idx_diff = index.difference(other, sort)
+ tm.assert_index_equal(idx_diff, expected)
+ tm.assert_attr_equal("freq", idx_diff, expected)
+
+ other = period_range("20160922", "20160925", freq="D")
+ idx_diff = index.difference(other, sort)
+ expected = PeriodIndex(["20160920", "20160921"], freq="D")
+ tm.assert_index_equal(idx_diff, expected)
+ tm.assert_attr_equal("freq", idx_diff, expected)
+
+ def test_intersection_equal_duplicates(self):
+ # GH#38302
+ idx = period_range("2011-01-01", periods=2)
+ idx_dup = idx.append(idx)
+ result = idx_dup.intersection(idx_dup)
+ tm.assert_index_equal(result, idx)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_union_duplicates(self):
+ # GH#36289
+ idx = period_range("2011-01-01", periods=2)
+ idx_dup = idx.append(idx)
+
+ idx2 = period_range("2011-01-02", periods=2)
+ idx2_dup = idx2.append(idx2)
+ result = idx_dup.union(idx2_dup)
+
+ expected = PeriodIndex(
+ [
+ "2011-01-01",
+ "2011-01-01",
+ "2011-01-02",
+ "2011-01-02",
+ "2011-01-03",
+ "2011-01-03",
+ ],
+ freq="D",
+ )
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_tools.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..f507e64d88b06b5862de3e98c693ab9f85306116
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/period/test_tools.py
@@ -0,0 +1,52 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Period,
+ PeriodIndex,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestPeriodRepresentation:
+ """
+ Wish to match NumPy units
+ """
+
+ @pytest.mark.parametrize(
+ "freq, base_date",
+ [
+ ("W-THU", "1970-01-01"),
+ ("D", "1970-01-01"),
+ ("B", "1970-01-01"),
+ ("h", "1970-01-01"),
+ ("min", "1970-01-01"),
+ ("s", "1970-01-01"),
+ ("ms", "1970-01-01"),
+ ("us", "1970-01-01"),
+ ("ns", "1970-01-01"),
+ ("M", "1970-01"),
+ ("Y", 1970),
+ ],
+ )
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ @pytest.mark.filterwarnings(
+ "ignore:Period with BDay freq is deprecated:FutureWarning"
+ )
+ def test_freq(self, freq, base_date):
+ rng = period_range(start=base_date, periods=10, freq=freq)
+ exp = np.arange(10, dtype=np.int64)
+
+ tm.assert_numpy_array_equal(rng.asi8, exp)
+
+
+class TestPeriodIndexConversion:
+ def test_tolist(self):
+ index = period_range(freq="Y", start="1/1/2001", end="12/1/2009")
+ rs = index.tolist()
+ for x in rs:
+ assert isinstance(x, Period)
+
+ recon = PeriodIndex(rs)
+ tm.assert_index_equal(index, recon)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e6f16075ae636a3aa14e7443097f426bd6f998a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_constructors.py
@@ -0,0 +1,164 @@
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ RangeIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestRangeIndexConstructors:
+ @pytest.mark.parametrize("name", [None, "foo"])
+ @pytest.mark.parametrize(
+ "args, kwargs, start, stop, step",
+ [
+ ((5,), {}, 0, 5, 1),
+ ((1, 5), {}, 1, 5, 1),
+ ((1, 5, 2), {}, 1, 5, 2),
+ ((0,), {}, 0, 0, 1),
+ ((0, 0), {}, 0, 0, 1),
+ ((), {"start": 0}, 0, 0, 1),
+ ((), {"stop": 0}, 0, 0, 1),
+ ],
+ )
+ def test_constructor(self, args, kwargs, start, stop, step, name):
+ result = RangeIndex(*args, name=name, **kwargs)
+ expected = Index(np.arange(start, stop, step, dtype=np.int64), name=name)
+ assert isinstance(result, RangeIndex)
+ assert result.name is name
+ assert result._range == range(start, stop, step)
+ tm.assert_index_equal(result, expected, exact="equiv")
+
+ def test_constructor_invalid_args(self):
+ msg = "RangeIndex\\(\\.\\.\\.\\) must be called with integers"
+ with pytest.raises(TypeError, match=msg):
+ RangeIndex()
+
+ with pytest.raises(TypeError, match=msg):
+ RangeIndex(name="Foo")
+
+ # we don't allow on a bare Index
+ msg = (
+ r"Index\(\.\.\.\) must be called with a collection of some "
+ r"kind, 0 was passed"
+ )
+ with pytest.raises(TypeError, match=msg):
+ Index(0)
+
+ @pytest.mark.parametrize(
+ "args",
+ [
+ Index(["a", "b"]),
+ Series(["a", "b"]),
+ np.array(["a", "b"]),
+ [],
+ np.arange(0, 10),
+ np.array([1]),
+ [1],
+ ],
+ )
+ def test_constructor_additional_invalid_args(self, args):
+ msg = f"Value needs to be a scalar value, was type {type(args).__name__}"
+ with pytest.raises(TypeError, match=msg):
+ RangeIndex(args)
+
+ @pytest.mark.parametrize("args", ["foo", datetime(2000, 1, 1, 0, 0)])
+ def test_constructor_invalid_args_wrong_type(self, args):
+ msg = f"Wrong type {type(args)} for value {args}"
+ with pytest.raises(TypeError, match=msg):
+ RangeIndex(args)
+
+ def test_constructor_same(self):
+ # pass thru w and w/o copy
+ index = RangeIndex(1, 5, 2)
+ result = RangeIndex(index, copy=False)
+ assert result.identical(index)
+
+ result = RangeIndex(index, copy=True)
+ tm.assert_index_equal(result, index, exact=True)
+
+ result = RangeIndex(index)
+ tm.assert_index_equal(result, index, exact=True)
+
+ with pytest.raises(
+ ValueError,
+ match="Incorrect `dtype` passed: expected signed integer, received float64",
+ ):
+ RangeIndex(index, dtype="float64")
+
+ def test_constructor_range_object(self):
+ result = RangeIndex(range(1, 5, 2))
+ expected = RangeIndex(1, 5, 2)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_constructor_range(self):
+ result = RangeIndex.from_range(range(1, 5, 2))
+ expected = RangeIndex(1, 5, 2)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = RangeIndex.from_range(range(5, 6))
+ expected = RangeIndex(5, 6, 1)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # an invalid range
+ result = RangeIndex.from_range(range(5, 1))
+ expected = RangeIndex(0, 0, 1)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = RangeIndex.from_range(range(5))
+ expected = RangeIndex(0, 5, 1)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = Index(range(1, 5, 2))
+ expected = RangeIndex(1, 5, 2)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ msg = (
+ r"(RangeIndex.)?from_range\(\) got an unexpected keyword argument( 'copy')?"
+ )
+ with pytest.raises(TypeError, match=msg):
+ RangeIndex.from_range(range(10), copy=True)
+
+ def test_constructor_name(self):
+ # GH#12288
+ orig = RangeIndex(10)
+ orig.name = "original"
+
+ copy = RangeIndex(orig)
+ copy.name = "copy"
+
+ assert orig.name == "original"
+ assert copy.name == "copy"
+
+ new = Index(copy)
+ assert new.name == "copy"
+
+ new.name = "new"
+ assert orig.name == "original"
+ assert copy.name == "copy"
+ assert new.name == "new"
+
+ def test_constructor_corner(self):
+ arr = np.array([1, 2, 3, 4], dtype=object)
+ index = RangeIndex(1, 5)
+ assert index.values.dtype == np.int64
+ expected = Index(arr).astype("int64")
+
+ tm.assert_index_equal(index, expected, exact="equiv")
+
+ # non-int raise Exception
+ with pytest.raises(TypeError, match=r"Wrong type \"):
+ RangeIndex("1", "10", "1")
+ with pytest.raises(TypeError, match=r"Wrong type \"):
+ RangeIndex(1.1, 10.2, 1.3)
+
+ # invalid passed type
+ with pytest.raises(
+ ValueError,
+ match="Incorrect `dtype` passed: expected signed integer, received float64",
+ ):
+ RangeIndex(1, 5, dtype="float64")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..695be18043da97c40b76f58715dde9b327bf73ef
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_indexing.py
@@ -0,0 +1,148 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ RangeIndex,
+)
+import pandas._testing as tm
+
+
+class TestGetIndexer:
+ def test_get_indexer(self):
+ index = RangeIndex(start=0, stop=20, step=2)
+ target = RangeIndex(10)
+ indexer = index.get_indexer(target)
+ expected = np.array([0, -1, 1, -1, 2, -1, 3, -1, 4, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+ def test_get_indexer_pad(self):
+ index = RangeIndex(start=0, stop=20, step=2)
+ target = RangeIndex(10)
+ indexer = index.get_indexer(target, method="pad")
+ expected = np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+ def test_get_indexer_backfill(self):
+ index = RangeIndex(start=0, stop=20, step=2)
+ target = RangeIndex(10)
+ indexer = index.get_indexer(target, method="backfill")
+ expected = np.array([0, 1, 1, 2, 2, 3, 3, 4, 4, 5], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected)
+
+ def test_get_indexer_limit(self):
+ # GH#28631
+ idx = RangeIndex(4)
+ target = RangeIndex(6)
+ result = idx.get_indexer(target, method="pad", limit=1)
+ expected = np.array([0, 1, 2, 3, 3, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("stop", [0, -1, -2])
+ def test_get_indexer_decreasing(self, stop):
+ # GH#28678
+ index = RangeIndex(7, stop, -3)
+ result = index.get_indexer(range(9))
+ expected = np.array([-1, 2, -1, -1, 1, -1, -1, 0, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_indexer_missing_value_casting_string_dtype(self):
+ # GH#55833
+ idx = Index(["a", "b", None])
+ result = idx.get_indexer([None])
+ expected = np.array([2], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx.get_indexer([None, True])
+ expected = np.array([2, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestTake:
+ def test_take_preserve_name(self):
+ index = RangeIndex(1, 5, name="foo")
+ taken = index.take([3, 0, 1])
+ assert index.name == taken.name
+
+ def test_take_fill_value(self):
+ # GH#12631
+ idx = RangeIndex(1, 4, name="xxx")
+ result = idx.take(np.array([1, 0, -1]))
+ expected = Index([2, 1, 3], dtype=np.int64, name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ # fill_value
+ msg = "Unable to fill values because RangeIndex cannot contain NA"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -1]), fill_value=True)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = Index([2, 1, 3], dtype=np.int64, name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ msg = "Unable to fill values because RangeIndex cannot contain NA"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ def test_take_raises_index_error(self):
+ idx = RangeIndex(1, 4, name="xxx")
+
+ msg = "index -5 is out of bounds for (axis 0 with )?size 3"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
+
+ msg = "index -4 is out of bounds for (axis 0 with )?size 3"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -4]))
+
+ # no errors
+ result = idx.take(np.array([1, -3]))
+ expected = Index([2, 1], dtype=np.int64, name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ def test_take_accepts_empty_array(self):
+ idx = RangeIndex(1, 4, name="foo")
+ result = idx.take(np.array([]))
+ expected = Index([], dtype=np.int64, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ # empty index
+ idx = RangeIndex(0, name="foo")
+ result = idx.take(np.array([]))
+ expected = Index([], dtype=np.int64, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ def test_take_accepts_non_int64_array(self):
+ idx = RangeIndex(1, 4, name="foo")
+ result = idx.take(np.array([2, 1], dtype=np.uint32))
+ expected = Index([3, 2], dtype=np.int64, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ def test_take_when_index_has_step(self):
+ idx = RangeIndex(1, 11, 3, name="foo") # [1, 4, 7, 10]
+ result = idx.take(np.array([1, 0, -1, -4]))
+ expected = Index([4, 1, 10, 1], dtype=np.int64, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ def test_take_when_index_has_negative_step(self):
+ idx = RangeIndex(11, -4, -2, name="foo") # [11, 9, 7, 5, 3, 1, -1, -3]
+ result = idx.take(np.array([1, 0, -1, -8]))
+ expected = Index([9, 11, -3, 11], dtype=np.int64, name="foo")
+ tm.assert_index_equal(result, expected)
+
+
+class TestWhere:
+ def test_where_putmask_range_cast(self):
+ # GH#43240
+ idx = RangeIndex(0, 5, name="test")
+
+ mask = np.array([True, True, False, False, False])
+ result = idx.putmask(mask, 10)
+ expected = Index([10, 10, 2, 3, 4], dtype=np.int64, name="test")
+ tm.assert_index_equal(result, expected)
+
+ result = idx.where(~mask, 10)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..09db30b1d4c51f73ae26c556b56cf75a4da07fb1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_join.py
@@ -0,0 +1,235 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ RangeIndex,
+)
+import pandas._testing as tm
+
+
+class TestJoin:
+ def test_join_outer(self):
+ # join with Index[int64]
+ index = RangeIndex(start=0, stop=20, step=2)
+ other = Index(np.arange(25, 14, -1, dtype=np.int64))
+
+ res, lidx, ridx = index.join(other, how="outer", return_indexers=True)
+ noidx_res = index.join(other, how="outer")
+ tm.assert_index_equal(res, noidx_res)
+
+ eres = Index(
+ [0, 2, 4, 6, 8, 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
+ )
+ elidx = np.array(
+ [0, 1, 2, 3, 4, 5, 6, 7, -1, 8, -1, 9, -1, -1, -1, -1, -1, -1, -1],
+ dtype=np.intp,
+ )
+ eridx = np.array(
+ [-1, -1, -1, -1, -1, -1, -1, -1, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
+ dtype=np.intp,
+ )
+
+ assert isinstance(res, Index) and res.dtype == np.dtype(np.int64)
+ assert not isinstance(res, RangeIndex)
+ tm.assert_index_equal(res, eres, exact=True)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # join with RangeIndex
+ other = RangeIndex(25, 14, -1)
+
+ res, lidx, ridx = index.join(other, how="outer", return_indexers=True)
+ noidx_res = index.join(other, how="outer")
+ tm.assert_index_equal(res, noidx_res)
+
+ assert isinstance(res, Index) and res.dtype == np.int64
+ assert not isinstance(res, RangeIndex)
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_inner(self):
+ # Join with non-RangeIndex
+ index = RangeIndex(start=0, stop=20, step=2)
+ other = Index(np.arange(25, 14, -1, dtype=np.int64))
+
+ res, lidx, ridx = index.join(other, how="inner", return_indexers=True)
+
+ # no guarantee of sortedness, so sort for comparison purposes
+ ind = res.argsort()
+ res = res.take(ind)
+ lidx = lidx.take(ind)
+ ridx = ridx.take(ind)
+
+ eres = Index([16, 18])
+ elidx = np.array([8, 9], dtype=np.intp)
+ eridx = np.array([9, 7], dtype=np.intp)
+
+ assert isinstance(res, Index) and res.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # Join two RangeIndex
+ other = RangeIndex(25, 14, -1)
+
+ res, lidx, ridx = index.join(other, how="inner", return_indexers=True)
+
+ assert isinstance(res, RangeIndex)
+ tm.assert_index_equal(res, eres, exact="equiv")
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_left(self):
+ # Join with Index[int64]
+ index = RangeIndex(start=0, stop=20, step=2)
+ other = Index(np.arange(25, 14, -1, dtype=np.int64))
+
+ res, lidx, ridx = index.join(other, how="left", return_indexers=True)
+ eres = index
+ eridx = np.array([-1, -1, -1, -1, -1, -1, -1, -1, 9, 7], dtype=np.intp)
+
+ assert isinstance(res, RangeIndex)
+ tm.assert_index_equal(res, eres)
+ assert lidx is None
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ # Join withRangeIndex
+ other = Index(np.arange(25, 14, -1, dtype=np.int64))
+
+ res, lidx, ridx = index.join(other, how="left", return_indexers=True)
+
+ assert isinstance(res, RangeIndex)
+ tm.assert_index_equal(res, eres)
+ assert lidx is None
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_right(self):
+ # Join with Index[int64]
+ index = RangeIndex(start=0, stop=20, step=2)
+ other = Index(np.arange(25, 14, -1, dtype=np.int64))
+
+ res, lidx, ridx = index.join(other, how="right", return_indexers=True)
+ eres = other
+ elidx = np.array([-1, -1, -1, -1, -1, -1, -1, 9, -1, 8, -1], dtype=np.intp)
+
+ assert isinstance(other, Index) and other.dtype == np.int64
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ assert ridx is None
+
+ # Join withRangeIndex
+ other = RangeIndex(25, 14, -1)
+
+ res, lidx, ridx = index.join(other, how="right", return_indexers=True)
+ eres = other
+
+ assert isinstance(other, RangeIndex)
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ assert ridx is None
+
+ def test_join_non_int_index(self):
+ index = RangeIndex(start=0, stop=20, step=2)
+ other = Index([3, 6, 7, 8, 10], dtype=object)
+
+ outer = index.join(other, how="outer")
+ outer2 = other.join(index, how="outer")
+ expected = Index([0, 2, 3, 4, 6, 7, 8, 10, 12, 14, 16, 18])
+ tm.assert_index_equal(outer, outer2)
+ tm.assert_index_equal(outer, expected)
+
+ inner = index.join(other, how="inner")
+ inner2 = other.join(index, how="inner")
+ expected = Index([6, 8, 10])
+ tm.assert_index_equal(inner, inner2)
+ tm.assert_index_equal(inner, expected)
+
+ left = index.join(other, how="left")
+ tm.assert_index_equal(left, index.astype(object))
+
+ left2 = other.join(index, how="left")
+ tm.assert_index_equal(left2, other)
+
+ right = index.join(other, how="right")
+ tm.assert_index_equal(right, other)
+
+ right2 = other.join(index, how="right")
+ tm.assert_index_equal(right2, index.astype(object))
+
+ def test_join_non_unique(self):
+ index = RangeIndex(start=0, stop=20, step=2)
+ other = Index([4, 4, 3, 3])
+
+ res, lidx, ridx = index.join(other, return_indexers=True)
+
+ eres = Index([0, 2, 4, 4, 6, 8, 10, 12, 14, 16, 18])
+ elidx = np.array([0, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.intp)
+ eridx = np.array([-1, -1, 0, 1, -1, -1, -1, -1, -1, -1, -1], dtype=np.intp)
+
+ tm.assert_index_equal(res, eres)
+ tm.assert_numpy_array_equal(lidx, elidx)
+ tm.assert_numpy_array_equal(ridx, eridx)
+
+ def test_join_self(self, join_type):
+ index = RangeIndex(start=0, stop=20, step=2)
+ joined = index.join(index, how=join_type)
+ assert index is joined
+
+
+@pytest.mark.parametrize(
+ "left, right, expected, expected_lidx, expected_ridx, how",
+ [
+ [RangeIndex(2), RangeIndex(3), RangeIndex(2), None, [0, 1], "left"],
+ [RangeIndex(2), RangeIndex(2), RangeIndex(2), None, None, "left"],
+ [RangeIndex(2), RangeIndex(20, 22), RangeIndex(2), None, [-1, -1], "left"],
+ [RangeIndex(2), RangeIndex(3), RangeIndex(3), [0, 1, -1], None, "right"],
+ [RangeIndex(2), RangeIndex(2), RangeIndex(2), None, None, "right"],
+ [
+ RangeIndex(2),
+ RangeIndex(20, 22),
+ RangeIndex(20, 22),
+ [-1, -1],
+ None,
+ "right",
+ ],
+ [RangeIndex(2), RangeIndex(3), RangeIndex(2), [0, 1], [0, 1], "inner"],
+ [RangeIndex(2), RangeIndex(2), RangeIndex(2), None, None, "inner"],
+ [RangeIndex(2), RangeIndex(1, 3), RangeIndex(1, 2), [1], [0], "inner"],
+ [RangeIndex(2), RangeIndex(3), RangeIndex(3), [0, 1, -1], [0, 1, 2], "outer"],
+ [RangeIndex(2), RangeIndex(2), RangeIndex(2), None, None, "outer"],
+ [
+ RangeIndex(2),
+ RangeIndex(2, 4),
+ RangeIndex(4),
+ [0, 1, -1, -1],
+ [-1, -1, 0, 1],
+ "outer",
+ ],
+ [RangeIndex(2), RangeIndex(0), RangeIndex(2), None, [-1, -1], "left"],
+ [RangeIndex(2), RangeIndex(0), RangeIndex(0), [], None, "right"],
+ [RangeIndex(2), RangeIndex(0), RangeIndex(0), [], None, "inner"],
+ [RangeIndex(2), RangeIndex(0), RangeIndex(2), None, [-1, -1], "outer"],
+ ],
+)
+@pytest.mark.parametrize(
+ "right_type", [RangeIndex, lambda x: Index(list(x), dtype=x.dtype)]
+)
+def test_join_preserves_rangeindex(
+ left, right, expected, expected_lidx, expected_ridx, how, right_type
+):
+ result, lidx, ridx = left.join(right_type(right), how=how, return_indexers=True)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ if expected_lidx is None:
+ assert lidx is expected_lidx
+ else:
+ exp_lidx = np.array(expected_lidx, dtype=np.intp)
+ tm.assert_numpy_array_equal(lidx, exp_lidx)
+
+ if expected_ridx is None:
+ assert ridx is expected_ridx
+ else:
+ exp_ridx = np.array(expected_ridx, dtype=np.intp)
+ tm.assert_numpy_array_equal(ridx, exp_ridx)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_range.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_range.py
new file mode 100644
index 0000000000000000000000000000000000000000..2cf8febc416712b7c027b71028ca3916fa6e5018
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_range.py
@@ -0,0 +1,927 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import ensure_platform_int
+
+import pandas as pd
+from pandas import (
+ Index,
+ RangeIndex,
+)
+import pandas._testing as tm
+from pandas.core.indexes.range import min_fitting_element
+
+
+class TestRangeIndex:
+ @pytest.fixture
+ def simple_index(self):
+ return RangeIndex(start=0, stop=20, step=2)
+
+ def test_constructor_unwraps_index(self):
+ result = RangeIndex(1, 3)
+ expected = np.array([1, 2], dtype=np.int64)
+ tm.assert_numpy_array_equal(result._data, expected)
+
+ def test_can_hold_identifiers(self, simple_index):
+ idx = simple_index
+ key = idx[0]
+ assert idx._can_hold_identifiers_and_holds_name(key) is False
+
+ def test_too_many_names(self, simple_index):
+ index = simple_index
+ with pytest.raises(ValueError, match="^Length"):
+ index.names = ["roger", "harold"]
+
+ @pytest.mark.parametrize(
+ "index, start, stop, step",
+ [
+ (RangeIndex(5), 0, 5, 1),
+ (RangeIndex(0, 5), 0, 5, 1),
+ (RangeIndex(5, step=2), 0, 5, 2),
+ (RangeIndex(1, 5, 2), 1, 5, 2),
+ ],
+ )
+ def test_start_stop_step_attrs(self, index, start, stop, step):
+ # GH 25710
+ assert index.start == start
+ assert index.stop == stop
+ assert index.step == step
+
+ def test_copy(self):
+ i = RangeIndex(5, name="Foo")
+ i_copy = i.copy()
+ assert i_copy is not i
+ assert i_copy.identical(i)
+ assert i_copy._range == range(0, 5, 1)
+ assert i_copy.name == "Foo"
+
+ def test_repr(self):
+ i = RangeIndex(5, name="Foo")
+ result = repr(i)
+ expected = "RangeIndex(start=0, stop=5, step=1, name='Foo')"
+ assert result == expected
+
+ result = eval(result)
+ tm.assert_index_equal(result, i, exact=True)
+
+ i = RangeIndex(5, 0, -1)
+ result = repr(i)
+ expected = "RangeIndex(start=5, stop=0, step=-1)"
+ assert result == expected
+
+ result = eval(result)
+ tm.assert_index_equal(result, i, exact=True)
+
+ def test_insert(self):
+ idx = RangeIndex(5, name="Foo")
+ result = idx[1:4]
+
+ # test 0th element
+ tm.assert_index_equal(idx[0:4], result.insert(0, idx[0]), exact="equiv")
+
+ # GH 18295 (test missing)
+ expected = Index([0, np.nan, 1, 2, 3, 4], dtype=np.float64)
+ for na in [np.nan, None, pd.NA]:
+ result = RangeIndex(5).insert(1, na)
+ tm.assert_index_equal(result, expected)
+
+ result = RangeIndex(5).insert(1, pd.NaT)
+ expected = Index([0, pd.NaT, 1, 2, 3, 4], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_edges_preserves_rangeindex(self):
+ idx = Index(range(4, 9, 2))
+
+ result = idx.insert(0, 2)
+ expected = Index(range(2, 9, 2))
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx.insert(3, 10)
+ expected = Index(range(4, 11, 2))
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_insert_middle_preserves_rangeindex(self):
+ # insert in the middle
+ idx = Index(range(0, 3, 2))
+ result = idx.insert(1, 1)
+ expected = Index(range(3))
+ tm.assert_index_equal(result, expected, exact=True)
+
+ idx = idx * 2
+ result = idx.insert(1, 2)
+ expected = expected * 2
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_delete(self):
+ idx = RangeIndex(5, name="Foo")
+ expected = idx[1:]
+ result = idx.delete(0)
+ tm.assert_index_equal(result, expected, exact=True)
+ assert result.name == expected.name
+
+ expected = idx[:-1]
+ result = idx.delete(-1)
+ tm.assert_index_equal(result, expected, exact=True)
+ assert result.name == expected.name
+
+ msg = "index 5 is out of bounds for axis 0 with size 5"
+ with pytest.raises((IndexError, ValueError), match=msg):
+ # either depending on numpy version
+ result = idx.delete(len(idx))
+
+ def test_delete_preserves_rangeindex(self):
+ idx = Index(range(2), name="foo")
+
+ result = idx.delete([1])
+ expected = Index(range(1), name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx.delete(1)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_delete_preserves_rangeindex_middle(self):
+ idx = Index(range(3), name="foo")
+ result = idx.delete(1)
+ expected = idx[::2]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx.delete(-2)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_delete_preserves_rangeindex_list_at_end(self):
+ idx = RangeIndex(0, 6, 1)
+
+ loc = [2, 3, 4, 5]
+ result = idx.delete(loc)
+ expected = idx[:2]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx.delete(loc[::-1])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_delete_preserves_rangeindex_list_middle(self):
+ idx = RangeIndex(0, 6, 1)
+
+ loc = [1, 2, 3, 4]
+ result = idx.delete(loc)
+ expected = RangeIndex(0, 6, 5)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx.delete(loc[::-1])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_delete_all_preserves_rangeindex(self):
+ idx = RangeIndex(0, 6, 1)
+
+ loc = [0, 1, 2, 3, 4, 5]
+ result = idx.delete(loc)
+ expected = idx[:0]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx.delete(loc[::-1])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_delete_not_preserving_rangeindex(self):
+ idx = RangeIndex(0, 6, 1)
+
+ loc = [0, 3, 5]
+ result = idx.delete(loc)
+ expected = Index([1, 2, 4])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx.delete(loc[::-1])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_view(self):
+ i = RangeIndex(0, name="Foo")
+ i_view = i.view()
+ assert i_view.name == "Foo"
+
+ i_view = i.view("i8")
+ tm.assert_numpy_array_equal(i.values, i_view)
+
+ def test_dtype(self, simple_index):
+ index = simple_index
+ assert index.dtype == np.int64
+
+ def test_cache(self):
+ # GH 26565, GH26617, GH35432, GH53387
+ # This test checks whether _cache has been set.
+ # Calling RangeIndex._cache["_data"] creates an int64 array of the same length
+ # as the RangeIndex and stores it in _cache.
+ idx = RangeIndex(0, 100, 10)
+
+ assert idx._cache == {}
+
+ repr(idx)
+ assert idx._cache == {}
+
+ str(idx)
+ assert idx._cache == {}
+
+ idx.get_loc(20)
+ assert idx._cache == {}
+
+ 90 in idx # True
+ assert idx._cache == {}
+
+ 91 in idx # False
+ assert idx._cache == {}
+
+ idx.all()
+ assert idx._cache == {}
+
+ idx.any()
+ assert idx._cache == {}
+
+ for _ in idx:
+ pass
+ assert idx._cache == {}
+
+ df = pd.DataFrame({"a": range(10)}, index=idx)
+
+ # df.__repr__ should not populate index cache
+ str(df)
+ assert idx._cache == {}
+
+ df.loc[50]
+ assert idx._cache == {}
+
+ with pytest.raises(KeyError, match="51"):
+ df.loc[51]
+ assert idx._cache == {}
+
+ df.loc[10:50]
+ assert idx._cache == {}
+
+ df.iloc[5:10]
+ assert idx._cache == {}
+
+ # after calling take, _cache may contain other keys, but not "_data"
+ idx.take([3, 0, 1])
+ assert "_data" not in idx._cache
+
+ df.loc[[50]]
+ assert "_data" not in idx._cache
+
+ df.iloc[[5, 6, 7, 8, 9]]
+ assert "_data" not in idx._cache
+
+ # idx._cache should contain a _data entry after call to idx._data
+ idx._data
+ assert isinstance(idx._data, np.ndarray)
+ assert idx._data is idx._data # check cached value is reused
+ assert "_data" in idx._cache
+ expected = np.arange(0, 100, 10, dtype="int64")
+ tm.assert_numpy_array_equal(idx._cache["_data"], expected)
+
+ def test_is_monotonic(self):
+ index = RangeIndex(0, 20, 2)
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_decreasing is False
+ assert index._is_strictly_monotonic_increasing is True
+ assert index._is_strictly_monotonic_decreasing is False
+
+ index = RangeIndex(4, 0, -1)
+ assert index.is_monotonic_increasing is False
+ assert index._is_strictly_monotonic_increasing is False
+ assert index.is_monotonic_decreasing is True
+ assert index._is_strictly_monotonic_decreasing is True
+
+ index = RangeIndex(1, 2)
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_decreasing is True
+ assert index._is_strictly_monotonic_increasing is True
+ assert index._is_strictly_monotonic_decreasing is True
+
+ index = RangeIndex(2, 1)
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_decreasing is True
+ assert index._is_strictly_monotonic_increasing is True
+ assert index._is_strictly_monotonic_decreasing is True
+
+ index = RangeIndex(1, 1)
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_increasing is True
+ assert index.is_monotonic_decreasing is True
+ assert index._is_strictly_monotonic_increasing is True
+ assert index._is_strictly_monotonic_decreasing is True
+
+ @pytest.mark.parametrize(
+ "left,right",
+ [
+ (RangeIndex(0, 9, 2), RangeIndex(0, 10, 2)),
+ (RangeIndex(0), RangeIndex(1, -1, 3)),
+ (RangeIndex(1, 2, 3), RangeIndex(1, 3, 4)),
+ (RangeIndex(0, -9, -2), RangeIndex(0, -10, -2)),
+ ],
+ )
+ def test_equals_range(self, left, right):
+ assert left.equals(right)
+ assert right.equals(left)
+
+ def test_logical_compat(self, simple_index):
+ idx = simple_index
+ assert idx.all() == idx.values.all()
+ assert idx.any() == idx.values.any()
+
+ def test_identical(self, simple_index):
+ index = simple_index
+ i = Index(index.copy())
+ assert i.identical(index)
+
+ # we don't allow object dtype for RangeIndex
+ if isinstance(index, RangeIndex):
+ return
+
+ same_values_different_type = Index(i, dtype=object)
+ assert not i.identical(same_values_different_type)
+
+ i = index.copy(dtype=object)
+ i = i.rename("foo")
+ same_values = Index(i, dtype=object)
+ assert same_values.identical(index.copy(dtype=object))
+
+ assert not i.identical(index)
+ assert Index(same_values, name="foo", dtype=object).identical(i)
+
+ assert not index.copy(dtype=object).identical(index.copy(dtype="int64"))
+
+ def test_nbytes(self):
+ # memory savings vs int index
+ idx = RangeIndex(0, 1000)
+ assert idx.nbytes < Index(idx._values).nbytes / 10
+
+ # constant memory usage
+ i2 = RangeIndex(0, 10)
+ assert idx.nbytes == i2.nbytes
+
+ @pytest.mark.parametrize(
+ "start,stop,step",
+ [
+ # can't
+ ("foo", "bar", "baz"),
+ # shouldn't
+ ("0", "1", "2"),
+ ],
+ )
+ def test_cant_or_shouldnt_cast(self, start, stop, step):
+ msg = f"Wrong type {type(start)} for value {start}"
+ with pytest.raises(TypeError, match=msg):
+ RangeIndex(start, stop, step)
+
+ def test_view_index(self, simple_index):
+ index = simple_index
+ msg = (
+ "Cannot change data-type for array of references.|"
+ "Cannot change data-type for object array.|"
+ )
+ with pytest.raises(TypeError, match=msg):
+ index.view(Index)
+
+ def test_prevent_casting(self, simple_index):
+ index = simple_index
+ result = index.astype("O")
+ assert result.dtype == np.object_
+
+ def test_repr_roundtrip(self, simple_index):
+ index = simple_index
+ tm.assert_index_equal(eval(repr(index)), index)
+
+ def test_slice_keep_name(self):
+ idx = RangeIndex(1, 2, name="asdf")
+ assert idx.name == idx[1:].name
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ RangeIndex(start=0, stop=20, step=2, name="foo"),
+ RangeIndex(start=18, stop=-1, step=-2, name="bar"),
+ ],
+ ids=["index_inc", "index_dec"],
+ )
+ def test_has_duplicates(self, index):
+ assert index.is_unique
+ assert not index.has_duplicates
+
+ def test_extended_gcd(self, simple_index):
+ index = simple_index
+ result = index._extended_gcd(6, 10)
+ assert result[0] == result[1] * 6 + result[2] * 10
+ assert 2 == result[0]
+
+ result = index._extended_gcd(10, 6)
+ assert 2 == result[1] * 10 + result[2] * 6
+ assert 2 == result[0]
+
+ def test_min_fitting_element(self):
+ result = min_fitting_element(0, 2, 1)
+ assert 2 == result
+
+ result = min_fitting_element(1, 1, 1)
+ assert 1 == result
+
+ result = min_fitting_element(18, -2, 1)
+ assert 2 == result
+
+ result = min_fitting_element(5, -1, 1)
+ assert 1 == result
+
+ big_num = 500000000000000000000000
+
+ result = min_fitting_element(5, 1, big_num)
+ assert big_num == result
+
+ def test_slice_specialised(self, simple_index):
+ index = simple_index
+ index.name = "foo"
+
+ # scalar indexing
+ res = index[1]
+ expected = 2
+ assert res == expected
+
+ res = index[-1]
+ expected = 18
+ assert res == expected
+
+ # slicing
+ # slice value completion
+ index_slice = index[:]
+ expected = index
+ tm.assert_index_equal(index_slice, expected)
+
+ # positive slice values
+ index_slice = index[7:10:2]
+ expected = Index([14, 18], name="foo")
+ tm.assert_index_equal(index_slice, expected, exact="equiv")
+
+ # negative slice values
+ index_slice = index[-1:-5:-2]
+ expected = Index([18, 14], name="foo")
+ tm.assert_index_equal(index_slice, expected, exact="equiv")
+
+ # stop overshoot
+ index_slice = index[2:100:4]
+ expected = Index([4, 12], name="foo")
+ tm.assert_index_equal(index_slice, expected, exact="equiv")
+
+ # reverse
+ index_slice = index[::-1]
+ expected = Index(index.values[::-1], name="foo")
+ tm.assert_index_equal(index_slice, expected, exact="equiv")
+
+ index_slice = index[-8::-1]
+ expected = Index([4, 2, 0], name="foo")
+ tm.assert_index_equal(index_slice, expected, exact="equiv")
+
+ index_slice = index[-40::-1]
+ expected = Index(np.array([], dtype=np.int64), name="foo")
+ tm.assert_index_equal(index_slice, expected, exact="equiv")
+
+ index_slice = index[40::-1]
+ expected = Index(index.values[40::-1], name="foo")
+ tm.assert_index_equal(index_slice, expected, exact="equiv")
+
+ index_slice = index[10::-1]
+ expected = Index(index.values[::-1], name="foo")
+ tm.assert_index_equal(index_slice, expected, exact="equiv")
+
+ @pytest.mark.parametrize("step", set(range(-5, 6)) - {0})
+ def test_len_specialised(self, step):
+ # make sure that our len is the same as np.arange calc
+ start, stop = (0, 5) if step > 0 else (5, 0)
+
+ arr = np.arange(start, stop, step)
+ index = RangeIndex(start, stop, step)
+ assert len(index) == len(arr)
+
+ index = RangeIndex(stop, start, step)
+ assert len(index) == 0
+
+ @pytest.mark.parametrize(
+ "indices, expected",
+ [
+ ([RangeIndex(1, 12, 5)], RangeIndex(1, 12, 5)),
+ ([RangeIndex(0, 6, 4)], RangeIndex(0, 6, 4)),
+ ([RangeIndex(1, 3), RangeIndex(3, 7)], RangeIndex(1, 7)),
+ ([RangeIndex(1, 5, 2), RangeIndex(5, 6)], RangeIndex(1, 6, 2)),
+ ([RangeIndex(1, 3, 2), RangeIndex(4, 7, 3)], RangeIndex(1, 7, 3)),
+ ([RangeIndex(-4, 3, 2), RangeIndex(4, 7, 2)], RangeIndex(-4, 7, 2)),
+ ([RangeIndex(-4, -8), RangeIndex(-8, -12)], RangeIndex(0, 0)),
+ ([RangeIndex(-4, -8), RangeIndex(3, -4)], RangeIndex(0, 0)),
+ ([RangeIndex(-4, -8), RangeIndex(3, 5)], RangeIndex(3, 5)),
+ ([RangeIndex(-4, -2), RangeIndex(3, 5)], Index([-4, -3, 3, 4])),
+ ([RangeIndex(-2), RangeIndex(3, 5)], RangeIndex(3, 5)),
+ ([RangeIndex(2), RangeIndex(2)], Index([0, 1, 0, 1])),
+ ([RangeIndex(2), RangeIndex(2, 5), RangeIndex(5, 8, 4)], RangeIndex(0, 6)),
+ (
+ [RangeIndex(2), RangeIndex(3, 5), RangeIndex(5, 8, 4)],
+ Index([0, 1, 3, 4, 5]),
+ ),
+ (
+ [RangeIndex(-2, 2), RangeIndex(2, 5), RangeIndex(5, 8, 4)],
+ RangeIndex(-2, 6),
+ ),
+ ([RangeIndex(3), Index([-1, 3, 15])], Index([0, 1, 2, -1, 3, 15])),
+ ([RangeIndex(3), Index([-1, 3.1, 15.0])], Index([0, 1, 2, -1, 3.1, 15.0])),
+ ([RangeIndex(3), Index(["a", None, 14])], Index([0, 1, 2, "a", None, 14])),
+ ([RangeIndex(3, 1), Index(["a", None, 14])], Index(["a", None, 14])),
+ ],
+ )
+ def test_append(self, indices, expected):
+ # GH16212
+ result = indices[0].append(indices[1:])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ if len(indices) == 2:
+ # Append single item rather than list
+ result2 = indices[0].append(indices[1])
+ tm.assert_index_equal(result2, expected, exact=True)
+
+ def test_engineless_lookup(self):
+ # GH 16685
+ # Standard lookup on RangeIndex should not require the engine to be
+ # created
+ idx = RangeIndex(2, 10, 3)
+
+ assert idx.get_loc(5) == 1
+ tm.assert_numpy_array_equal(
+ idx.get_indexer([2, 8]), ensure_platform_int(np.array([0, 2]))
+ )
+ with pytest.raises(KeyError, match="3"):
+ idx.get_loc(3)
+
+ assert "_engine" not in idx._cache
+
+ # Different types of scalars can be excluded immediately, no need to
+ # use the _engine
+ with pytest.raises(KeyError, match="'a'"):
+ idx.get_loc("a")
+
+ assert "_engine" not in idx._cache
+
+ @pytest.mark.parametrize(
+ "ri",
+ [
+ RangeIndex(0, -1, -1),
+ RangeIndex(0, 1, 1),
+ RangeIndex(1, 3, 2),
+ RangeIndex(0, -1, -2),
+ RangeIndex(-3, -5, -2),
+ ],
+ )
+ def test_append_len_one(self, ri):
+ # GH39401
+ result = ri.append([])
+ tm.assert_index_equal(result, ri, exact=True)
+
+ @pytest.mark.parametrize("base", [RangeIndex(0, 2), Index([0, 1])])
+ def test_isin_range(self, base):
+ # GH#41151
+ values = RangeIndex(0, 1)
+ result = base.isin(values)
+ expected = np.array([True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_sort_values_key(self):
+ # GH#43666, GH#52764
+ sort_order = {8: 2, 6: 0, 4: 8, 2: 10, 0: 12}
+ values = RangeIndex(0, 10, 2)
+ result = values.sort_values(key=lambda x: x.map(sort_order))
+ expected = Index([6, 8, 4, 2, 0], dtype="int64")
+ tm.assert_index_equal(result, expected, check_exact=True)
+
+ # check this matches the Series.sort_values behavior
+ ser = values.to_series()
+ result2 = ser.sort_values(key=lambda x: x.map(sort_order))
+ tm.assert_series_equal(result2, expected.to_series(), check_exact=True)
+
+ def test_range_index_rsub_by_const(self):
+ # GH#53255
+ result = 3 - RangeIndex(0, 4, 1)
+ expected = RangeIndex(3, -1, -1)
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "rng, decimals",
+ [
+ [range(5), 0],
+ [range(5), 2],
+ [range(10, 30, 10), -1],
+ [range(30, 10, -10), -1],
+ ],
+)
+def test_range_round_returns_rangeindex(rng, decimals):
+ ri = RangeIndex(rng)
+ expected = ri.copy()
+ result = ri.round(decimals=decimals)
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+@pytest.mark.parametrize(
+ "rng, decimals",
+ [
+ [range(10, 30, 1), -1],
+ [range(30, 10, -1), -1],
+ [range(11, 14), -10],
+ ],
+)
+def test_range_round_returns_index(rng, decimals):
+ ri = RangeIndex(rng)
+ expected = Index(list(rng)).round(decimals=decimals)
+ result = ri.round(decimals=decimals)
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_reindex_1_value_returns_rangeindex():
+ ri = RangeIndex(0, 10, 2, name="foo")
+ result, result_indexer = ri.reindex([2])
+ expected = RangeIndex(2, 4, 2, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ expected_indexer = np.array([1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result_indexer, expected_indexer)
+
+
+def test_reindex_empty_returns_rangeindex():
+ ri = RangeIndex(0, 10, 2, name="foo")
+ result, result_indexer = ri.reindex([])
+ expected = RangeIndex(0, 0, 2, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ expected_indexer = np.array([], dtype=np.intp)
+ tm.assert_numpy_array_equal(result_indexer, expected_indexer)
+
+
+def test_insert_empty_0_loc():
+ ri = RangeIndex(0, step=10, name="foo")
+ result = ri.insert(0, 5)
+ expected = RangeIndex(5, 15, 10, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_append_non_rangeindex_return_rangeindex():
+ ri = RangeIndex(1)
+ result = ri.append(Index([1]))
+ expected = RangeIndex(2)
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_append_non_rangeindex_return_index():
+ ri = RangeIndex(1)
+ result = ri.append(Index([1, 3, 4]))
+ expected = Index([0, 1, 3, 4])
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_reindex_returns_rangeindex():
+ ri = RangeIndex(2, name="foo")
+ result, result_indexer = ri.reindex([1, 2, 3])
+ expected = RangeIndex(1, 4, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ expected_indexer = np.array([1, -1, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result_indexer, expected_indexer)
+
+
+def test_reindex_returns_index():
+ ri = RangeIndex(4, name="foo")
+ result, result_indexer = ri.reindex([0, 1, 3])
+ expected = Index([0, 1, 3], name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ expected_indexer = np.array([0, 1, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(result_indexer, expected_indexer)
+
+
+def test_take_return_rangeindex():
+ ri = RangeIndex(5, name="foo")
+ result = ri.take([])
+ expected = RangeIndex(0, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = ri.take([3, 4])
+ expected = RangeIndex(3, 5, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test__getitem__boolean_numpyextensionarray():
+ ri = RangeIndex(1)
+ result = ri[pd.arrays.NumpyExtensionArray(np.array([True]))]
+ tm.assert_index_equal(ri, result)
+
+
+@pytest.mark.parametrize(
+ "container",
+ [np.array, pd.Series, lambda x: pd.arrays.NumpyExtensionArray(np.array(x))],
+ ids=["numpy-array", "series", "numpy-extension-array"],
+)
+def test__getitem__boolean_arraylike(container):
+ ri = RangeIndex(5)
+ result = ri[container([True, True, False, False, True])]
+ expected = Index([0, 1, 4], dtype="int64")
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "rng, exp_rng",
+ [
+ [range(5), range(3, 4)],
+ [range(0, -10, -2), range(-6, -8, -2)],
+ [range(0, 10, 2), range(6, 8, 2)],
+ ],
+)
+def test_take_1_value_returns_rangeindex(rng, exp_rng):
+ ri = RangeIndex(rng, name="foo")
+ result = ri.take([3])
+ expected = RangeIndex(exp_rng, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_append_one_nonempty_preserve_step():
+ expected = RangeIndex(0, -1, -1)
+ result = RangeIndex(0).append([expected])
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_getitem_boolmask_all_true():
+ ri = RangeIndex(3, name="foo")
+ expected = ri.copy()
+ result = ri[[True] * 3]
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_getitem_boolmask_all_false():
+ ri = RangeIndex(3, name="foo")
+ result = ri[[False] * 3]
+ expected = RangeIndex(0, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_getitem_boolmask_returns_rangeindex():
+ ri = RangeIndex(3, name="foo")
+ result = ri[[False, True, True]]
+ expected = RangeIndex(1, 3, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = ri[[True, False, True]]
+ expected = RangeIndex(0, 3, 2, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_getitem_boolmask_returns_index():
+ ri = RangeIndex(4, name="foo")
+ result = ri[[True, True, False, True]]
+ expected = Index([0, 1, 3], name="foo")
+ tm.assert_index_equal(result, expected)
+
+
+def test_getitem_boolmask_wrong_length():
+ ri = RangeIndex(4, name="foo")
+ with pytest.raises(IndexError, match="Boolean index has wrong length"):
+ ri[[True]]
+
+
+def test_pos_returns_rangeindex():
+ ri = RangeIndex(2, name="foo")
+ expected = ri.copy()
+ result = +ri
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_neg_returns_rangeindex():
+ ri = RangeIndex(2, name="foo")
+ result = -ri
+ expected = RangeIndex(0, -2, -1, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ ri = RangeIndex(-2, 2, name="foo")
+ result = -ri
+ expected = RangeIndex(2, -2, -1, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+@pytest.mark.parametrize(
+ "rng, exp_rng",
+ [
+ [range(0), range(0)],
+ [range(10), range(10)],
+ [range(-2, 1, 1), range(2, -1, -1)],
+ [range(0, -10, -1), range(0, 10, 1)],
+ ],
+)
+def test_abs_returns_rangeindex(rng, exp_rng):
+ ri = RangeIndex(rng, name="foo")
+ expected = RangeIndex(exp_rng, name="foo")
+ result = abs(ri)
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_abs_returns_index():
+ ri = RangeIndex(-2, 2, name="foo")
+ result = abs(ri)
+ expected = Index([2, 1, 0, 1], name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+@pytest.mark.parametrize(
+ "rng",
+ [
+ range(0),
+ range(5),
+ range(0, -5, -1),
+ range(-2, 2, 1),
+ range(2, -2, -2),
+ range(0, 5, 2),
+ ],
+)
+def test_invert_returns_rangeindex(rng):
+ ri = RangeIndex(rng, name="foo")
+ result = ~ri
+ assert isinstance(result, RangeIndex)
+ expected = ~Index(list(rng), name="foo")
+ tm.assert_index_equal(result, expected, exact=False)
+
+
+@pytest.mark.parametrize(
+ "rng",
+ [
+ range(0, 5, 1),
+ range(0, 5, 2),
+ range(10, 15, 1),
+ range(10, 5, -1),
+ range(10, 5, -2),
+ range(5, 0, -1),
+ ],
+)
+@pytest.mark.parametrize("meth", ["argmax", "argmin"])
+def test_arg_min_max(rng, meth):
+ ri = RangeIndex(rng)
+ idx = Index(list(rng))
+ assert getattr(ri, meth)() == getattr(idx, meth)()
+
+
+@pytest.mark.parametrize("meth", ["argmin", "argmax"])
+def test_empty_argmin_argmax_raises(meth):
+ with pytest.raises(ValueError, match=f"attempt to get {meth} of an empty sequence"):
+ getattr(RangeIndex(0), meth)()
+
+
+def test_getitem_integers_return_rangeindex():
+ result = RangeIndex(0, 10, 2, name="foo")[[0, -1]]
+ expected = RangeIndex(start=0, stop=16, step=8, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = RangeIndex(0, 10, 2, name="foo")[[3]]
+ expected = RangeIndex(start=6, stop=8, step=2, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_getitem_empty_return_rangeindex():
+ result = RangeIndex(0, 10, 2, name="foo")[[]]
+ expected = RangeIndex(start=0, stop=0, step=1, name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_getitem_integers_return_index():
+ result = RangeIndex(0, 10, 2, name="foo")[[0, 1, -1]]
+ expected = Index([0, 2, 8], dtype="int64", name="foo")
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("normalize", [True, False])
+@pytest.mark.parametrize(
+ "rng",
+ [
+ range(3),
+ range(0),
+ range(0, 3, 2),
+ range(3, -3, -2),
+ ],
+)
+def test_value_counts(sort, dropna, ascending, normalize, rng):
+ ri = RangeIndex(rng, name="A")
+ result = ri.value_counts(
+ normalize=normalize, sort=sort, ascending=ascending, dropna=dropna
+ )
+ expected = Index(list(rng), name="A").value_counts(
+ normalize=normalize, sort=sort, ascending=ascending, dropna=dropna
+ )
+ tm.assert_series_equal(result, expected, check_index_type=False)
+
+
+@pytest.mark.parametrize("side", ["left", "right"])
+@pytest.mark.parametrize("value", [0, -5, 5, -3, np.array([-5, -3, 0, 5])])
+def test_searchsorted(side, value):
+ ri = RangeIndex(-3, 3, 2)
+ result = ri.searchsorted(value=value, side=side)
+ expected = Index(list(ri)).searchsorted(value=value, side=side)
+ if isinstance(value, int):
+ assert result == expected
+ else:
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..55ae84fdbc23b0a9a032bf976321f2e12281d6a1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/ranges/test_setops.py
@@ -0,0 +1,494 @@
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+from hypothesis import (
+ assume,
+ given,
+ strategies as st,
+)
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ RangeIndex,
+)
+import pandas._testing as tm
+
+
+class TestRangeIndexSetOps:
+ @pytest.mark.parametrize("dtype", [None, "int64", "uint64"])
+ def test_intersection_mismatched_dtype(self, dtype):
+ # check that we cast to float, not object
+ index = RangeIndex(start=0, stop=20, step=2, name="foo")
+ index = Index(index, dtype=dtype)
+
+ flt = index.astype(np.float64)
+
+ # bc index.equals(flt), we go through fastpath and get RangeIndex back
+ result = index.intersection(flt)
+ tm.assert_index_equal(result, index, exact=True)
+
+ result = flt.intersection(index)
+ tm.assert_index_equal(result, flt, exact=True)
+
+ # neither empty, not-equals
+ result = index.intersection(flt[1:])
+ tm.assert_index_equal(result, flt[1:], exact=True)
+
+ result = flt[1:].intersection(index)
+ tm.assert_index_equal(result, flt[1:], exact=True)
+
+ # empty other
+ result = index.intersection(flt[:0])
+ tm.assert_index_equal(result, flt[:0], exact=True)
+
+ result = flt[:0].intersection(index)
+ tm.assert_index_equal(result, flt[:0], exact=True)
+
+ def test_intersection_empty(self, sort, names):
+ # name retention on empty intersections
+ index = RangeIndex(start=0, stop=20, step=2, name=names[0])
+
+ # empty other
+ result = index.intersection(index[:0].rename(names[1]), sort=sort)
+ tm.assert_index_equal(result, index[:0].rename(names[2]), exact=True)
+
+ # empty self
+ result = index[:0].intersection(index.rename(names[1]), sort=sort)
+ tm.assert_index_equal(result, index[:0].rename(names[2]), exact=True)
+
+ def test_intersection(self, sort):
+ # intersect with Index with dtype int64
+ index = RangeIndex(start=0, stop=20, step=2)
+ other = Index(np.arange(1, 6))
+ result = index.intersection(other, sort=sort)
+ expected = Index(np.sort(np.intersect1d(index.values, other.values)))
+ tm.assert_index_equal(result, expected)
+
+ result = other.intersection(index, sort=sort)
+ expected = Index(
+ np.sort(np.asarray(np.intersect1d(index.values, other.values)))
+ )
+ tm.assert_index_equal(result, expected)
+
+ # intersect with increasing RangeIndex
+ other = RangeIndex(1, 6)
+ result = index.intersection(other, sort=sort)
+ expected = Index(np.sort(np.intersect1d(index.values, other.values)))
+ tm.assert_index_equal(result, expected, exact="equiv")
+
+ # intersect with decreasing RangeIndex
+ other = RangeIndex(5, 0, -1)
+ result = index.intersection(other, sort=sort)
+ expected = Index(np.sort(np.intersect1d(index.values, other.values)))
+ tm.assert_index_equal(result, expected, exact="equiv")
+
+ # reversed (GH 17296)
+ result = other.intersection(index, sort=sort)
+ tm.assert_index_equal(result, expected, exact="equiv")
+
+ # GH 17296: intersect two decreasing RangeIndexes
+ first = RangeIndex(10, -2, -2)
+ other = RangeIndex(5, -4, -1)
+ expected = RangeIndex(start=4, stop=-2, step=-2)
+ result = first.intersection(other, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ # reversed
+ result = other.intersection(first, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ index = RangeIndex(5, name="foo")
+
+ # intersect of non-overlapping indices
+ other = RangeIndex(5, 10, 1, name="foo")
+ result = index.intersection(other, sort=sort)
+ expected = RangeIndex(0, 0, 1, name="foo")
+ tm.assert_index_equal(result, expected)
+
+ other = RangeIndex(-1, -5, -1)
+ result = index.intersection(other, sort=sort)
+ expected = RangeIndex(0, 0, 1)
+ tm.assert_index_equal(result, expected)
+
+ # intersection of empty indices
+ other = RangeIndex(0, 0, 1)
+ result = index.intersection(other, sort=sort)
+ expected = RangeIndex(0, 0, 1)
+ tm.assert_index_equal(result, expected)
+
+ result = other.intersection(index, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection_non_overlapping_gcd(self, sort, names):
+ # intersection of non-overlapping values based on start value and gcd
+ index = RangeIndex(1, 10, 2, name=names[0])
+ other = RangeIndex(0, 10, 4, name=names[1])
+ result = index.intersection(other, sort=sort)
+ expected = RangeIndex(0, 0, 1, name=names[2])
+ tm.assert_index_equal(result, expected)
+
+ def test_union_noncomparable(self, sort):
+ # corner case, Index with non-int64 dtype
+ index = RangeIndex(start=0, stop=20, step=2)
+ other = Index([datetime.now() + timedelta(i) for i in range(4)], dtype=object)
+ result = index.union(other, sort=sort)
+ expected = Index(np.concatenate((index, other)))
+ tm.assert_index_equal(result, expected)
+
+ result = other.union(index, sort=sort)
+ expected = Index(np.concatenate((other, index)))
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "idx1, idx2, expected_sorted, expected_notsorted",
+ [
+ (
+ RangeIndex(0, 10, 1),
+ RangeIndex(0, 10, 1),
+ RangeIndex(0, 10, 1),
+ RangeIndex(0, 10, 1),
+ ),
+ (
+ RangeIndex(0, 10, 1),
+ RangeIndex(5, 20, 1),
+ RangeIndex(0, 20, 1),
+ RangeIndex(0, 20, 1),
+ ),
+ (
+ RangeIndex(0, 10, 1),
+ RangeIndex(10, 20, 1),
+ RangeIndex(0, 20, 1),
+ RangeIndex(0, 20, 1),
+ ),
+ (
+ RangeIndex(0, -10, -1),
+ RangeIndex(0, -10, -1),
+ RangeIndex(0, -10, -1),
+ RangeIndex(0, -10, -1),
+ ),
+ (
+ RangeIndex(0, -10, -1),
+ RangeIndex(-10, -20, -1),
+ RangeIndex(-19, 1, 1),
+ RangeIndex(0, -20, -1),
+ ),
+ (
+ RangeIndex(0, 10, 2),
+ RangeIndex(1, 10, 2),
+ RangeIndex(0, 10, 1),
+ Index(list(range(0, 10, 2)) + list(range(1, 10, 2))),
+ ),
+ (
+ RangeIndex(0, 11, 2),
+ RangeIndex(1, 12, 2),
+ RangeIndex(0, 12, 1),
+ Index(list(range(0, 11, 2)) + list(range(1, 12, 2))),
+ ),
+ (
+ RangeIndex(0, 21, 4),
+ RangeIndex(-2, 24, 4),
+ RangeIndex(-2, 24, 2),
+ Index(list(range(0, 21, 4)) + list(range(-2, 24, 4))),
+ ),
+ (
+ RangeIndex(0, -20, -2),
+ RangeIndex(-1, -21, -2),
+ RangeIndex(-19, 1, 1),
+ Index(list(range(0, -20, -2)) + list(range(-1, -21, -2))),
+ ),
+ (
+ RangeIndex(0, 100, 5),
+ RangeIndex(0, 100, 20),
+ RangeIndex(0, 100, 5),
+ RangeIndex(0, 100, 5),
+ ),
+ (
+ RangeIndex(0, -100, -5),
+ RangeIndex(5, -100, -20),
+ RangeIndex(-95, 10, 5),
+ Index([*list(range(0, -100, -5)), 5]),
+ ),
+ (
+ RangeIndex(0, -11, -1),
+ RangeIndex(1, -12, -4),
+ RangeIndex(-11, 2, 1),
+ Index([*list(range(0, -11, -1)), 1, -11]),
+ ),
+ (RangeIndex(0), RangeIndex(0), RangeIndex(0), RangeIndex(0)),
+ (
+ RangeIndex(0, -10, -2),
+ RangeIndex(0),
+ RangeIndex(0, -10, -2),
+ RangeIndex(0, -10, -2),
+ ),
+ (
+ RangeIndex(0, 100, 2),
+ RangeIndex(100, 150, 200),
+ RangeIndex(0, 102, 2),
+ RangeIndex(0, 102, 2),
+ ),
+ (
+ RangeIndex(0, -100, -2),
+ RangeIndex(-100, 50, 102),
+ RangeIndex(-100, 4, 2),
+ Index([*list(range(0, -100, -2)), -100, 2]),
+ ),
+ (
+ RangeIndex(0, -100, -1),
+ RangeIndex(0, -50, -3),
+ RangeIndex(-99, 1, 1),
+ RangeIndex(0, -100, -1),
+ ),
+ (
+ RangeIndex(0, 1, 1),
+ RangeIndex(5, 6, 10),
+ RangeIndex(0, 6, 5),
+ RangeIndex(0, 10, 5),
+ ),
+ (
+ RangeIndex(0, 10, 5),
+ RangeIndex(-5, -6, -20),
+ RangeIndex(-5, 10, 5),
+ Index([0, 5, -5]),
+ ),
+ (
+ RangeIndex(0, 3, 1),
+ RangeIndex(4, 5, 1),
+ Index([0, 1, 2, 4]),
+ Index([0, 1, 2, 4]),
+ ),
+ (
+ RangeIndex(0, 10, 1),
+ Index([], dtype=np.int64),
+ RangeIndex(0, 10, 1),
+ RangeIndex(0, 10, 1),
+ ),
+ (
+ RangeIndex(0),
+ Index([1, 5, 6]),
+ Index([1, 5, 6]),
+ Index([1, 5, 6]),
+ ),
+ # GH 43885
+ (
+ RangeIndex(0, 10),
+ RangeIndex(0, 5),
+ RangeIndex(0, 10),
+ RangeIndex(0, 10),
+ ),
+ ],
+ ids=lambda x: repr(x) if isinstance(x, RangeIndex) else x,
+ )
+ def test_union_sorted(self, idx1, idx2, expected_sorted, expected_notsorted):
+ res1 = idx1.union(idx2, sort=None)
+ tm.assert_index_equal(res1, expected_sorted, exact=True)
+
+ res1 = idx1.union(idx2, sort=False)
+ tm.assert_index_equal(res1, expected_notsorted, exact=True)
+
+ res2 = idx2.union(idx1, sort=None)
+ res3 = Index(idx1._values, name=idx1.name).union(idx2, sort=None)
+ tm.assert_index_equal(res2, expected_sorted, exact=True)
+ tm.assert_index_equal(res3, expected_sorted, exact="equiv")
+
+ def test_union_same_step_misaligned(self):
+ # GH#44019
+ left = RangeIndex(range(0, 20, 4))
+ right = RangeIndex(range(1, 21, 4))
+
+ result = left.union(right)
+ expected = Index([0, 1, 4, 5, 8, 9, 12, 13, 16, 17])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_difference(self):
+ # GH#12034 Cases where we operate against another RangeIndex and may
+ # get back another RangeIndex
+ obj = RangeIndex.from_range(range(1, 10), name="foo")
+
+ result = obj.difference(obj)
+ expected = RangeIndex.from_range(range(0), name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = obj.difference(expected.rename("bar"))
+ tm.assert_index_equal(result, obj.rename(None), exact=True)
+
+ result = obj.difference(obj[:3])
+ tm.assert_index_equal(result, obj[3:], exact=True)
+
+ result = obj.difference(obj[-3:])
+ tm.assert_index_equal(result, obj[:-3], exact=True)
+
+ # Flipping the step of 'other' doesn't affect the result, but
+ # flipping the stepof 'self' does when sort=None
+ result = obj[::-1].difference(obj[-3:])
+ tm.assert_index_equal(result, obj[:-3], exact=True)
+
+ result = obj[::-1].difference(obj[-3:], sort=False)
+ tm.assert_index_equal(result, obj[:-3][::-1], exact=True)
+
+ result = obj[::-1].difference(obj[-3:][::-1])
+ tm.assert_index_equal(result, obj[:-3], exact=True)
+
+ result = obj[::-1].difference(obj[-3:][::-1], sort=False)
+ tm.assert_index_equal(result, obj[:-3][::-1], exact=True)
+
+ result = obj.difference(obj[2:6])
+ expected = Index([1, 2, 7, 8, 9], name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_difference_sort(self):
+ # GH#44085 ensure we respect the sort keyword
+
+ idx = Index(range(4))[::-1]
+ other = Index(range(3, 4))
+
+ result = idx.difference(other)
+ expected = Index(range(3))
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = idx.difference(other, sort=False)
+ expected = expected[::-1]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # case where the intersection is empty
+ other = range(10, 12)
+ result = idx.difference(other, sort=None)
+ expected = idx[::-1]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_difference_mismatched_step(self):
+ obj = RangeIndex.from_range(range(1, 10), name="foo")
+
+ result = obj.difference(obj[::2])
+ expected = obj[1::2]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = obj[::-1].difference(obj[::2], sort=False)
+ tm.assert_index_equal(result, expected[::-1], exact=True)
+
+ result = obj.difference(obj[1::2])
+ expected = obj[::2]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ result = obj[::-1].difference(obj[1::2], sort=False)
+ tm.assert_index_equal(result, expected[::-1], exact=True)
+
+ def test_difference_interior_overlap_endpoints_preserved(self):
+ left = RangeIndex(range(4))
+ right = RangeIndex(range(1, 3))
+
+ result = left.difference(right)
+ expected = RangeIndex(0, 4, 3)
+ assert expected.tolist() == [0, 3]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_difference_endpoints_overlap_interior_preserved(self):
+ left = RangeIndex(-8, 20, 7)
+ right = RangeIndex(13, -9, -3)
+
+ result = left.difference(right)
+ expected = RangeIndex(-1, 13, 7)
+ assert expected.tolist() == [-1, 6]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_difference_interior_non_preserving(self):
+ # case with intersection of length 1 but RangeIndex is not preserved
+ idx = Index(range(10))
+
+ other = idx[3:4]
+ result = idx.difference(other)
+ expected = Index([0, 1, 2, 4, 5, 6, 7, 8, 9])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # case with other.step / self.step > 2
+ other = idx[::3]
+ result = idx.difference(other)
+ expected = Index([1, 2, 4, 5, 7, 8])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # cases with only reaching one end of left
+ obj = Index(range(20))
+ other = obj[:10:2]
+ result = obj.difference(other)
+ expected = Index([1, 3, 5, 7, 9, *list(range(10, 20))])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ other = obj[1:11:2]
+ result = obj.difference(other)
+ expected = Index([0, 2, 4, 6, 8, 10, *list(range(11, 20))])
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_symmetric_difference(self):
+ # GH#12034 Cases where we operate against another RangeIndex and may
+ # get back another RangeIndex
+ left = RangeIndex.from_range(range(1, 10), name="foo")
+
+ result = left.symmetric_difference(left)
+ expected = RangeIndex.from_range(range(0), name="foo")
+ tm.assert_index_equal(result, expected)
+
+ result = left.symmetric_difference(expected.rename("bar"))
+ tm.assert_index_equal(result, left.rename(None))
+
+ result = left[:-2].symmetric_difference(left[2:])
+ expected = Index([1, 2, 8, 9], name="foo")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ right = RangeIndex.from_range(range(10, 15))
+
+ result = left.symmetric_difference(right)
+ expected = RangeIndex.from_range(range(1, 15))
+ tm.assert_index_equal(result, expected)
+
+ result = left.symmetric_difference(right[1:])
+ expected = Index([1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14])
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def assert_range_or_not_is_rangelike(index):
+ """
+ Check that we either have a RangeIndex or that this index *cannot*
+ be represented as a RangeIndex.
+ """
+ if not isinstance(index, RangeIndex) and len(index) > 0:
+ diff = index[:-1] - index[1:]
+ assert not (diff == diff[0]).all()
+
+
+@pytest.mark.slow
+@given(
+ st.integers(-20, 20),
+ st.integers(-20, 20),
+ st.integers(-20, 20),
+ st.integers(-20, 20),
+ st.integers(-20, 20),
+ st.integers(-20, 20),
+)
+def test_range_difference(start1, stop1, step1, start2, stop2, step2):
+ # test that
+ # a) we match Index[int64].difference and
+ # b) we return RangeIndex whenever it is possible to do so.
+ assume(step1 != 0)
+ assume(step2 != 0)
+
+ left = RangeIndex(start1, stop1, step1)
+ right = RangeIndex(start2, stop2, step2)
+
+ result = left.difference(right, sort=None)
+ assert_range_or_not_is_rangelike(result)
+
+ left_int64 = Index(left.to_numpy())
+ right_int64 = Index(right.to_numpy())
+
+ alt = left_int64.difference(right_int64, sort=None)
+ tm.assert_index_equal(result, alt, exact="equiv")
+
+ result = left.difference(right, sort=False)
+ assert_range_or_not_is_rangelike(result)
+
+ alt = left_int64.difference(right_int64, sort=False)
+ tm.assert_index_equal(result, alt, exact="equiv")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/string/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/string/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/string/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/string/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..0349d85f2316707d6ecba2c2289fde49930cbbac
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/string/test_astype.py
@@ -0,0 +1,21 @@
+from pandas import (
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+
+def test_astype_str_from_bytes():
+ # https://github.com/pandas-dev/pandas/issues/38607
+ # GH#49658 pre-2.0 Index called .values.astype(str) here, which effectively
+ # did a .decode() on the bytes object. In 2.0 we go through
+ # ensure_string_array which does f"{val}"
+ idx = Index(["あ", b"a"], dtype="object")
+ result = idx.astype(str)
+ expected = Index(["あ", "a"], dtype="str")
+ tm.assert_index_equal(result, expected)
+
+ # while we're here, check that Series.astype behaves the same
+ result = Series(idx).astype(str)
+ expected = Series(expected, dtype="str")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/string/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/string/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..648ee47ddc34c1d4ae90bd986a283880743ac415
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/string/test_indexing.py
@@ -0,0 +1,199 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import Index
+import pandas._testing as tm
+
+
+def _isnan(val):
+ try:
+ return val is not pd.NA and np.isnan(val)
+ except TypeError:
+ return False
+
+
+def _equivalent_na(dtype, null):
+ if dtype.na_value is pd.NA and null is pd.NA:
+ return True
+ elif _isnan(dtype.na_value) and _isnan(null):
+ return True
+ else:
+ return False
+
+
+class TestGetLoc:
+ def test_get_loc(self, any_string_dtype):
+ index = Index(["a", "b", "c"], dtype=any_string_dtype)
+ assert index.get_loc("b") == 1
+
+ def test_get_loc_raises(self, any_string_dtype):
+ index = Index(["a", "b", "c"], dtype=any_string_dtype)
+ with pytest.raises(KeyError, match="d"):
+ index.get_loc("d")
+
+ def test_get_loc_invalid_value(self, any_string_dtype):
+ index = Index(["a", "b", "c"], dtype=any_string_dtype)
+ with pytest.raises(KeyError, match="1"):
+ index.get_loc(1)
+
+ def test_get_loc_non_unique(self, any_string_dtype):
+ index = Index(["a", "b", "a"], dtype=any_string_dtype)
+ result = index.get_loc("a")
+ expected = np.array([True, False, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_get_loc_non_missing(self, any_string_dtype, nulls_fixture):
+ index = Index(["a", "b", "c"], dtype=any_string_dtype)
+ with pytest.raises(KeyError):
+ index.get_loc(nulls_fixture)
+
+ def test_get_loc_missing(self, any_string_dtype, nulls_fixture):
+ index = Index(["a", "b", nulls_fixture], dtype=any_string_dtype)
+ assert index.get_loc(nulls_fixture) == 2
+
+
+class TestGetIndexer:
+ @pytest.mark.parametrize(
+ "method,expected",
+ [
+ ("pad", [-1, 0, 1, 1]),
+ ("backfill", [0, 0, 1, -1]),
+ ],
+ )
+ def test_get_indexer_strings(self, any_string_dtype, method, expected):
+ expected = np.array(expected, dtype=np.intp)
+ index = Index(["b", "c"], dtype=any_string_dtype)
+ actual = index.get_indexer(["a", "b", "c", "d"], method=method)
+
+ tm.assert_numpy_array_equal(actual, expected)
+
+ def test_get_indexer_strings_raises(self, any_string_dtype):
+ index = Index(["b", "c"], dtype=any_string_dtype)
+
+ msg = "|".join(
+ [
+ "operation 'sub' not supported for dtype 'str",
+ r"unsupported operand type\(s\) for -: 'str' and 'str'",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ index.get_indexer(["a", "b", "c", "d"], method="nearest")
+
+ with pytest.raises(TypeError, match=msg):
+ index.get_indexer(["a", "b", "c", "d"], method="pad", tolerance=2)
+
+ with pytest.raises(TypeError, match=msg):
+ index.get_indexer(
+ ["a", "b", "c", "d"], method="pad", tolerance=[2, 2, 2, 2]
+ )
+
+ @pytest.mark.parametrize("null", [None, np.nan, float("nan"), pd.NA])
+ def test_get_indexer_missing(self, any_string_dtype, null, using_infer_string):
+ # NaT and Decimal("NaN") from null_fixture are not supported for string dtype
+ index = Index(["a", "b", null], dtype=any_string_dtype)
+ result = index.get_indexer(["a", null, "c"])
+ if using_infer_string:
+ expected = np.array([0, 2, -1], dtype=np.intp)
+ elif any_string_dtype == "string" and not _equivalent_na(
+ any_string_dtype, null
+ ):
+ expected = np.array([0, -1, -1], dtype=np.intp)
+ else:
+ expected = np.array([0, 2, -1], dtype=np.intp)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestGetIndexerNonUnique:
+ @pytest.mark.parametrize("null", [None, np.nan, float("nan"), pd.NA])
+ def test_get_indexer_non_unique_nas(
+ self, any_string_dtype, null, using_infer_string
+ ):
+ index = Index(["a", "b", null], dtype=any_string_dtype)
+ indexer, missing = index.get_indexer_non_unique(["a", null])
+
+ if using_infer_string:
+ expected_indexer = np.array([0, 2], dtype=np.intp)
+ expected_missing = np.array([], dtype=np.intp)
+ elif any_string_dtype == "string" and not _equivalent_na(
+ any_string_dtype, null
+ ):
+ expected_indexer = np.array([0, -1], dtype=np.intp)
+ expected_missing = np.array([1], dtype=np.intp)
+ else:
+ expected_indexer = np.array([0, 2], dtype=np.intp)
+ expected_missing = np.array([], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected_indexer)
+ tm.assert_numpy_array_equal(missing, expected_missing)
+
+ # actually non-unique
+ index = Index(["a", null, "b", null], dtype=any_string_dtype)
+ indexer, missing = index.get_indexer_non_unique(["a", null])
+
+ if using_infer_string:
+ expected_indexer = np.array([0, 1, 3], dtype=np.intp)
+ elif any_string_dtype == "string" and not _equivalent_na(
+ any_string_dtype, null
+ ):
+ pass
+ else:
+ expected_indexer = np.array([0, 1, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(indexer, expected_indexer)
+ tm.assert_numpy_array_equal(missing, expected_missing)
+
+
+class TestSliceLocs:
+ @pytest.mark.parametrize(
+ "in_slice,expected",
+ [
+ # error: Slice index must be an integer or None
+ (pd.IndexSlice[::-1], "yxdcb"),
+ (pd.IndexSlice["b":"y":-1], ""), # type: ignore[misc]
+ (pd.IndexSlice["b"::-1], "b"), # type: ignore[misc]
+ (pd.IndexSlice[:"b":-1], "yxdcb"), # type: ignore[misc]
+ (pd.IndexSlice[:"y":-1], "y"), # type: ignore[misc]
+ (pd.IndexSlice["y"::-1], "yxdcb"), # type: ignore[misc]
+ (pd.IndexSlice["y"::-4], "yb"), # type: ignore[misc]
+ # absent labels
+ (pd.IndexSlice[:"a":-1], "yxdcb"), # type: ignore[misc]
+ (pd.IndexSlice[:"a":-2], "ydb"), # type: ignore[misc]
+ (pd.IndexSlice["z"::-1], "yxdcb"), # type: ignore[misc]
+ (pd.IndexSlice["z"::-3], "yc"), # type: ignore[misc]
+ (pd.IndexSlice["m"::-1], "dcb"), # type: ignore[misc]
+ (pd.IndexSlice[:"m":-1], "yx"), # type: ignore[misc]
+ (pd.IndexSlice["a":"a":-1], ""), # type: ignore[misc]
+ (pd.IndexSlice["z":"z":-1], ""), # type: ignore[misc]
+ (pd.IndexSlice["m":"m":-1], ""), # type: ignore[misc]
+ ],
+ )
+ def test_slice_locs_negative_step(self, in_slice, expected, any_string_dtype):
+ index = Index(list("bcdxy"), dtype=any_string_dtype)
+
+ s_start, s_stop = index.slice_locs(in_slice.start, in_slice.stop, in_slice.step)
+ result = index[s_start : s_stop : in_slice.step]
+ expected = Index(list(expected), dtype=any_string_dtype)
+ tm.assert_index_equal(result, expected)
+
+ def test_slice_locs_negative_step_oob(self, any_string_dtype):
+ index = Index(list("bcdxy"), dtype=any_string_dtype)
+
+ result = index[-10:5:1]
+ tm.assert_index_equal(result, index)
+
+ result = index[4:-10:-1]
+ expected = Index(list("yxdcb"), dtype=any_string_dtype)
+ tm.assert_index_equal(result, expected)
+
+ def test_slice_locs_dup(self, any_string_dtype):
+ index = Index(["a", "a", "b", "c", "d", "d"], dtype=any_string_dtype)
+ assert index.slice_locs("a", "d") == (0, 6)
+ assert index.slice_locs(end="d") == (0, 6)
+ assert index.slice_locs("a", "c") == (0, 4)
+ assert index.slice_locs("b", "d") == (2, 6)
+
+ index2 = index[::-1]
+ assert index2.slice_locs("d", "a") == (0, 6)
+ assert index2.slice_locs(end="a") == (0, 6)
+ assert index2.slice_locs("d", "b") == (0, 4)
+ assert index2.slice_locs("c", "a") == (2, 6)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_any_index.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_any_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7e32be98bbefad2ad6bc22e682bfe2e9f682664
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_any_index.py
@@ -0,0 +1,184 @@
+"""
+Tests that can be parametrized over _any_ Index object.
+"""
+
+import re
+
+import numpy as np
+import pytest
+
+from pandas.errors import InvalidIndexError
+
+from pandas import StringDtype
+import pandas._testing as tm
+
+
+def test_boolean_context_compat(index):
+ # GH#7897
+ with pytest.raises(ValueError, match="The truth value of a"):
+ if index:
+ pass
+
+ with pytest.raises(ValueError, match="The truth value of a"):
+ bool(index)
+
+
+def test_hash_error(index):
+ with pytest.raises(TypeError, match=f"unhashable type: '{type(index).__name__}'"):
+ hash(index)
+
+
+def test_mutability(index):
+ if not len(index):
+ pytest.skip("Test doesn't make sense for empty index")
+ msg = "Index does not support mutable operations"
+ with pytest.raises(TypeError, match=msg):
+ index[0] = index[0]
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+def test_map_identity_mapping(index, request, using_infer_string):
+ # GH#12766
+ if (
+ not using_infer_string
+ and isinstance(index.dtype, StringDtype)
+ and index.dtype.storage == "python"
+ ):
+ mark = pytest.mark.xfail(reason="Does not preserve dtype")
+ request.applymarker(mark)
+
+ result = index.map(lambda x: x)
+ if index.dtype == object and (result.dtype in (bool, "string")):
+ assert (index == result).all()
+ # TODO: could work that into the 'exact="equiv"'?
+ return # FIXME: doesn't belong in this file anymore!
+ tm.assert_index_equal(result, index, exact="equiv")
+
+
+def test_wrong_number_names(index):
+ names = index.nlevels * ["apple", "banana", "carrot"]
+ with pytest.raises(ValueError, match="^Length"):
+ index.names = names
+
+
+def test_view_preserves_name(index):
+ assert index.view().name == index.name
+
+
+def test_ravel(index):
+ # GH#19956 ravel returning ndarray is deprecated, in 2.0 returns a view on self
+ res = index.ravel()
+ tm.assert_index_equal(res, index)
+
+
+class TestConversion:
+ def test_to_series(self, index):
+ # assert that we are creating a copy of the index
+
+ ser = index.to_series()
+ assert ser.values is not index.values
+ assert ser.index is not index
+ assert ser.name == index.name
+
+ def test_to_series_with_arguments(self, index):
+ # GH#18699
+
+ # index kwarg
+ ser = index.to_series(index=index)
+
+ assert ser.values is not index.values
+ assert ser.index is index
+ assert ser.name == index.name
+
+ # name kwarg
+ ser = index.to_series(name="__test")
+
+ assert ser.values is not index.values
+ assert ser.index is not index
+ assert ser.name != index.name
+
+ def test_tolist_matches_list(self, index):
+ assert index.tolist() == list(index)
+
+
+class TestRoundTrips:
+ def test_pickle_roundtrip(self, index, temp_file):
+ result = tm.round_trip_pickle(index, temp_file)
+ tm.assert_index_equal(result, index, exact=True)
+ if result.nlevels > 1:
+ # GH#8367 round-trip with timezone
+ assert index.equal_levels(result)
+
+ def test_pickle_preserves_name(self, index, temp_file):
+ original_name, index.name = index.name, "foo"
+ unpickled = tm.round_trip_pickle(index, temp_file)
+ assert index.equals(unpickled)
+ index.name = original_name
+
+
+class TestIndexing:
+ def test_getitem_0d_ndarray(self, index):
+ # GH#55601
+ if len(index) == 0:
+ pytest.skip(reason="Test assumes non-empty index")
+ key = np.array(0)
+ result = index[key]
+
+ assert result == index[0]
+
+ def test_get_loc_listlike_raises_invalid_index_error(self, index):
+ # and never TypeError
+ key = np.array([0, 1], dtype=np.intp)
+
+ with pytest.raises(InvalidIndexError, match=r"\[0 1\]"):
+ index.get_loc(key)
+
+ with pytest.raises(InvalidIndexError, match=r"\[False True\]"):
+ index.get_loc(key.astype(bool))
+
+ def test_getitem_ellipsis(self, index):
+ # GH#21282
+ result = index[...]
+ assert result.equals(index)
+ assert result is not index
+
+ def test_slice_keeps_name(self, index):
+ assert index.name == index[1:].name
+
+ @pytest.mark.parametrize("item", [101, "no_int", 2.5])
+ def test_getitem_error(self, index, item):
+ msg = "|".join(
+ [
+ r"index 101 is out of bounds for axis 0 with size [\d]+",
+ re.escape(
+ "only integers, slices (`:`), ellipsis (`...`), "
+ "numpy.newaxis (`None`) and integer or boolean arrays "
+ "are valid indices"
+ ),
+ "index out of bounds", # string[pyarrow]
+ ]
+ )
+ with pytest.raises(IndexError, match=msg):
+ index[item]
+
+
+class TestRendering:
+ def test_str(self, index):
+ # test the string repr
+ index.name = "foo"
+ assert "'foo'" in str(index)
+ assert type(index).__name__ in str(index)
+
+
+class TestReductions:
+ def test_argmax_axis_invalid(self, index):
+ # GH#23081
+ msg = r"`axis` must be fewer than the number of dimensions \(1\)"
+ with pytest.raises(ValueError, match=msg):
+ index.argmax(axis=1)
+ with pytest.raises(ValueError, match=msg):
+ index.argmin(axis=2)
+ with pytest.raises(ValueError, match=msg):
+ index.min(axis=-2)
+ with pytest.raises(ValueError, match=msg):
+ index.max(axis=-3)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_base.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..e892277d4d116426e51cc018ab5ccd6229f71082
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_base.py
@@ -0,0 +1,1751 @@
+from collections import defaultdict
+from datetime import datetime
+from functools import partial
+import math
+import operator
+import re
+
+import numpy as np
+import pytest
+
+from pandas.compat import IS64
+from pandas.errors import InvalidIndexError
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.common import (
+ is_any_real_numeric_dtype,
+ is_numeric_dtype,
+ is_object_dtype,
+)
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ DataFrame,
+ DatetimeIndex,
+ IntervalIndex,
+ PeriodIndex,
+ RangeIndex,
+ Series,
+ TimedeltaIndex,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.indexes.api import (
+ Index,
+ MultiIndex,
+ _get_combined_index,
+ ensure_index,
+ ensure_index_from_sequences,
+)
+from pandas.testing import assert_series_equal
+
+
+class TestIndex:
+ @pytest.fixture
+ def simple_index(self) -> Index:
+ return Index(list("abcde"))
+
+ def test_can_hold_identifiers(self, simple_index):
+ index = simple_index
+ key = index[0]
+ assert index._can_hold_identifiers_and_holds_name(key) is True
+
+ @pytest.mark.parametrize("index", ["datetime"], indirect=True)
+ def test_new_axis(self, index):
+ # TODO: a bunch of scattered tests check this deprecation is enforced.
+ # de-duplicate/centralize them.
+ with pytest.raises(ValueError, match="Multi-dimensional indexing"):
+ # GH#30588 multi-dimensional indexing deprecated
+ index[None, :]
+
+ def test_constructor_regular(self, index):
+ tm.assert_contains_all(index, index)
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ def test_constructor_casting(self, index):
+ # casting
+ arr = np.array(index)
+ new_index = Index(arr)
+ tm.assert_contains_all(arr, new_index)
+ tm.assert_index_equal(index, new_index)
+
+ def test_constructor_copy(self, using_infer_string):
+ index = Index(list("abc"), name="name")
+ arr = np.array(index)
+ new_index = Index(arr, copy=True, name="name")
+ assert isinstance(new_index, Index)
+ assert new_index.name == "name"
+ if using_infer_string:
+ tm.assert_extension_array_equal(
+ new_index.values, pd.array(arr, dtype="str")
+ )
+ else:
+ tm.assert_numpy_array_equal(arr, new_index.values)
+ arr[0] = "SOMEBIGLONGSTRING"
+ assert new_index[0] != "SOMEBIGLONGSTRING"
+
+ @pytest.mark.parametrize("cast_as_obj", [True, False])
+ @pytest.mark.parametrize(
+ "index",
+ [
+ date_range(
+ "2015-01-01 10:00",
+ freq="D",
+ periods=3,
+ tz="US/Eastern",
+ name="Green Eggs & Ham",
+ ), # DTI with tz
+ date_range("2015-01-01 10:00", freq="D", periods=3), # DTI no tz
+ timedelta_range("1 days", freq="D", periods=3), # td
+ period_range("2015-01-01", freq="D", periods=3), # period
+ ],
+ )
+ def test_constructor_from_index_dtlike(self, cast_as_obj, index):
+ if cast_as_obj:
+ result = Index(index.astype(object))
+ assert result.dtype == np.dtype(object)
+ if isinstance(index, DatetimeIndex):
+ # GH#23524 check that Index(dti, dtype=object) does not
+ # incorrectly raise ValueError, and that nanoseconds are not
+ # dropped
+ index += pd.Timedelta(nanoseconds=50)
+ result = Index(index, dtype=object)
+ assert result.dtype == np.object_
+ assert list(result) == list(index)
+ else:
+ result = Index(index)
+
+ tm.assert_index_equal(result, index)
+
+ @pytest.mark.parametrize(
+ "index,has_tz",
+ [
+ (
+ date_range("2015-01-01 10:00", freq="D", periods=3, tz="US/Eastern"),
+ True,
+ ), # datetimetz
+ (timedelta_range("1 days", freq="D", periods=3), False), # td
+ (period_range("2015-01-01", freq="D", periods=3), False), # period
+ ],
+ )
+ def test_constructor_from_series_dtlike(self, index, has_tz):
+ result = Index(Series(index))
+ tm.assert_index_equal(result, index)
+
+ if has_tz:
+ assert result.tz == index.tz
+
+ def test_constructor_from_series_freq(self):
+ # GH 6273
+ # create from a series, passing a freq
+ dts = ["1-1-1990", "2-1-1990", "3-1-1990", "4-1-1990", "5-1-1990"]
+ expected = DatetimeIndex(dts, freq="MS")
+
+ s = Series(pd.to_datetime(dts))
+ result = DatetimeIndex(s, freq="MS")
+
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_from_frame_series_freq(self, using_infer_string):
+ # GH 6273
+ # create from a series, passing a freq
+ dts = ["1-1-1990", "2-1-1990", "3-1-1990", "4-1-1990", "5-1-1990"]
+ expected = DatetimeIndex(dts, freq="MS")
+
+ df = DataFrame(np.random.default_rng(2).random((5, 3)))
+ df["date"] = dts
+ result = DatetimeIndex(df["date"], freq="MS")
+ dtype = object if not using_infer_string else "str"
+ assert df["date"].dtype == dtype
+ expected.name = "date"
+ tm.assert_index_equal(result, expected)
+
+ expected = Series(dts, name="date")
+ tm.assert_series_equal(df["date"], expected)
+
+ # GH 6274
+ # infer freq of same
+ if not using_infer_string:
+ # Doesn't work with arrow strings
+ freq = pd.infer_freq(df["date"])
+ assert freq == "MS"
+
+ def test_constructor_int_dtype_nan(self):
+ # see gh-15187
+ data = [np.nan]
+ expected = Index(data, dtype=np.float64)
+ result = Index(data, dtype="float")
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "klass,dtype,na_val",
+ [
+ (Index, np.float64, np.nan),
+ (DatetimeIndex, "datetime64[s]", pd.NaT),
+ ],
+ )
+ def test_index_ctor_infer_nan_nat(self, klass, dtype, na_val):
+ # GH 13467
+ na_list = [na_val, na_val]
+ expected = klass(na_list)
+ assert expected.dtype == dtype
+
+ result = Index(na_list)
+ tm.assert_index_equal(result, expected)
+
+ result = Index(np.array(na_list))
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "vals,dtype",
+ [
+ ([1, 2, 3, 4, 5], "int"),
+ ([1.1, np.nan, 2.2, 3.0], "float"),
+ (["A", "B", "C", np.nan], "obj"),
+ ],
+ )
+ def test_constructor_simple_new(self, vals, dtype):
+ index = Index(vals, name=dtype)
+ result = index._simple_new(index.values, dtype)
+ tm.assert_index_equal(result, index)
+
+ @pytest.mark.parametrize("attr", ["values", "asi8"])
+ @pytest.mark.parametrize("klass", [Index, DatetimeIndex])
+ def test_constructor_dtypes_datetime(self, tz_naive_fixture, attr, klass):
+ # Test constructing with a datetimetz dtype
+ # .values produces numpy datetimes, so these are considered naive
+ # .asi8 produces integers, so these are considered epoch timestamps
+ # ^the above will be true in a later version. Right now we `.view`
+ # the i8 values as NS_DTYPE, effectively treating them as wall times.
+ index = date_range("2011-01-01", periods=5, unit="ns")
+ arg = getattr(index, attr)
+ index = index.tz_localize(tz_naive_fixture)
+ dtype = index.dtype
+
+ # As of 2.0 astype raises on dt64.astype(dt64tz)
+ err = tz_naive_fixture is not None
+ msg = "Cannot use .astype to convert from timezone-naive dtype to"
+
+ if attr == "asi8":
+ result = DatetimeIndex(arg).tz_localize(tz_naive_fixture)
+ tm.assert_index_equal(result, index)
+ elif klass is Index:
+ with pytest.raises(TypeError, match="unexpected keyword"):
+ klass(arg, tz=tz_naive_fixture)
+ else:
+ result = klass(arg, tz=tz_naive_fixture)
+ tm.assert_index_equal(result, index)
+
+ if attr == "asi8":
+ if err:
+ with pytest.raises(TypeError, match=msg):
+ DatetimeIndex(arg).astype(dtype)
+ else:
+ result = DatetimeIndex(arg).astype(dtype)
+ tm.assert_index_equal(result, index)
+ else:
+ result = klass(arg, dtype=dtype)
+ tm.assert_index_equal(result, index)
+
+ if attr == "asi8":
+ result = DatetimeIndex(list(arg)).tz_localize(tz_naive_fixture)
+ tm.assert_index_equal(result, index)
+ elif klass is Index:
+ with pytest.raises(TypeError, match="unexpected keyword"):
+ klass(arg, tz=tz_naive_fixture)
+ else:
+ result = klass(list(arg), tz=tz_naive_fixture)
+ tm.assert_index_equal(result, index)
+
+ if attr == "asi8":
+ if err:
+ with pytest.raises(TypeError, match=msg):
+ DatetimeIndex(list(arg)).astype(dtype)
+ else:
+ result = DatetimeIndex(list(arg)).astype(dtype)
+ tm.assert_index_equal(result, index)
+ else:
+ result = klass(list(arg), dtype=dtype)
+ tm.assert_index_equal(result, index)
+
+ @pytest.mark.parametrize("attr", ["values", "asi8"])
+ @pytest.mark.parametrize("klass", [Index, TimedeltaIndex])
+ def test_constructor_dtypes_timedelta(self, attr, klass):
+ index = timedelta_range("1 days", periods=5, unit="ns")
+ index = index._with_freq(None) # won't be preserved by constructors
+ dtype = index.dtype
+
+ values = getattr(index, attr)
+
+ result = klass(values, dtype=dtype)
+ tm.assert_index_equal(result, index)
+
+ result = klass(list(values), dtype=dtype)
+ tm.assert_index_equal(result, index)
+
+ @pytest.mark.parametrize("value", [[], iter([]), (_ for _ in [])])
+ @pytest.mark.parametrize(
+ "klass",
+ [
+ Index,
+ CategoricalIndex,
+ DatetimeIndex,
+ TimedeltaIndex,
+ ],
+ )
+ def test_constructor_empty(self, value, klass):
+ empty = klass(value)
+ assert isinstance(empty, klass)
+ assert not len(empty)
+
+ @pytest.mark.parametrize(
+ "empty,klass",
+ [
+ (PeriodIndex([], freq="D"), PeriodIndex),
+ (PeriodIndex(iter([]), freq="D"), PeriodIndex),
+ (PeriodIndex((_ for _ in []), freq="D"), PeriodIndex),
+ (RangeIndex(step=1), RangeIndex),
+ (MultiIndex(levels=[[1, 2], ["blue", "red"]], codes=[[], []]), MultiIndex),
+ ],
+ )
+ def test_constructor_empty_special(self, empty, klass):
+ assert isinstance(empty, klass)
+ assert not len(empty)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ "datetime",
+ "float64",
+ "float32",
+ "int64",
+ "int32",
+ "period",
+ "range",
+ "repeats",
+ "timedelta",
+ "tuples",
+ "uint64",
+ "uint32",
+ ],
+ indirect=True,
+ )
+ def test_view_with_args(self, index):
+ index.view("i8")
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ "string",
+ pytest.param("categorical", marks=pytest.mark.xfail(reason="gh-25464")),
+ "bool-object",
+ "bool-dtype",
+ "empty",
+ ],
+ indirect=True,
+ )
+ def test_view_with_args_object_array_raises(self, index):
+ if index.dtype == bool:
+ msg = "When changing to a larger dtype"
+ with pytest.raises(ValueError, match=msg):
+ index.view("i8")
+ else:
+ msg = (
+ r"Cannot change data-type for array of references\.|"
+ r"Cannot change data-type for object array\.|"
+ r"Cannot change data-type for array of strings\.|"
+ )
+ with pytest.raises(TypeError, match=msg):
+ index.view("i8")
+
+ @pytest.mark.parametrize(
+ "index",
+ ["int64", "int32", "range"],
+ indirect=True,
+ )
+ def test_astype(self, index):
+ casted = index.astype("i8")
+
+ # it works!
+ casted.get_loc(5)
+
+ # pass on name
+ index.name = "foobar"
+ casted = index.astype("i8")
+ assert casted.name == "foobar"
+
+ def test_equals_object(self):
+ # same
+ assert Index(["a", "b", "c"]).equals(Index(["a", "b", "c"]))
+
+ @pytest.mark.parametrize(
+ "comp", [Index(["a", "b"]), Index(["a", "b", "d"]), ["a", "b", "c"]]
+ )
+ def test_not_equals_object(self, comp):
+ assert not Index(["a", "b", "c"]).equals(comp)
+
+ def test_identical(self):
+ # index
+ i1 = Index(["a", "b", "c"])
+ i2 = Index(["a", "b", "c"])
+
+ assert i1.identical(i2)
+
+ i1 = i1.rename("foo")
+ assert i1.equals(i2)
+ assert not i1.identical(i2)
+
+ i2 = i2.rename("foo")
+ assert i1.identical(i2)
+
+ i3 = Index([("a", "a"), ("a", "b"), ("b", "a")])
+ i4 = Index([("a", "a"), ("a", "b"), ("b", "a")], tupleize_cols=False)
+ assert not i3.identical(i4)
+
+ def test_is_(self):
+ ind = Index(range(10))
+ assert ind.is_(ind)
+ assert ind.is_(ind.view().view().view().view())
+ assert not ind.is_(Index(range(10)))
+ assert not ind.is_(ind.copy())
+ assert not ind.is_(ind.copy(deep=False))
+ assert not ind.is_(ind[:])
+ assert not ind.is_(np.array(range(10)))
+
+ # quasi-implementation dependent
+ assert ind.is_(ind.view())
+ ind2 = ind.view()
+ ind2.name = "bob"
+ assert ind.is_(ind2)
+ assert ind2.is_(ind)
+ # doesn't matter if Indices are *actually* views of underlying data,
+ assert not ind.is_(Index(ind.values))
+ arr = np.array(range(1, 11))
+ ind1 = Index(arr, copy=False)
+ ind2 = Index(arr, copy=False)
+ assert not ind1.is_(ind2)
+
+ def test_asof_numeric_vs_bool_raises(self):
+ left = Index([1, 2, 3])
+ right = Index([True, False], dtype=object)
+
+ msg = "Cannot compare dtypes int64 and bool"
+ with pytest.raises(TypeError, match=msg):
+ left.asof(right[0])
+ # TODO: should right.asof(left[0]) also raise?
+
+ with pytest.raises(InvalidIndexError, match=re.escape(str(right))):
+ left.asof(right)
+
+ with pytest.raises(InvalidIndexError, match=re.escape(str(left))):
+ right.asof(left)
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ def test_booleanindex(self, index):
+ bool_index = np.ones(len(index), dtype=bool)
+ bool_index[5:30:2] = False
+
+ sub_index = index[bool_index]
+
+ for i, val in enumerate(sub_index):
+ assert sub_index.get_loc(val) == i
+
+ sub_index = index[list(bool_index)]
+ for i, val in enumerate(sub_index):
+ assert sub_index.get_loc(val) == i
+
+ def test_fancy(self, simple_index):
+ index = simple_index
+ sl = index[[1, 2, 3]]
+ for i in sl:
+ assert i == sl[sl.get_loc(i)]
+
+ @pytest.mark.parametrize(
+ "index",
+ ["string", "int64", "int32", "uint64", "uint32", "float64", "float32"],
+ indirect=True,
+ )
+ @pytest.mark.parametrize("dtype", [int, np.bool_])
+ def test_empty_fancy(self, index, dtype, request, using_infer_string):
+ if dtype is np.bool_ and using_infer_string and index.dtype == "string":
+ request.applymarker(pytest.mark.xfail(reason="numpy behavior is buggy"))
+ empty_arr = np.array([], dtype=dtype)
+ empty_index = type(index)([], dtype=index.dtype)
+
+ assert index[[]].identical(empty_index)
+ if dtype == np.bool_:
+ with pytest.raises(ValueError, match="length of the boolean indexer"):
+ assert index[empty_arr].identical(empty_index)
+ else:
+ assert index[empty_arr].identical(empty_index)
+
+ @pytest.mark.parametrize(
+ "index",
+ ["string", "int64", "int32", "uint64", "uint32", "float64", "float32"],
+ indirect=True,
+ )
+ def test_empty_fancy_raises(self, index):
+ # DatetimeIndex is excluded, because it overrides getitem and should
+ # be tested separately.
+ empty_farr = np.array([], dtype=np.float64)
+ empty_index = type(index)([], dtype=index.dtype)
+
+ assert index[[]].identical(empty_index)
+ # np.ndarray only accepts ndarray of int & bool dtypes, so should Index
+ msg = r"arrays used as indices must be of integer"
+ with pytest.raises(IndexError, match=msg):
+ index[empty_farr]
+
+ def test_union_dt_as_obj(self, simple_index):
+ # TODO: Replace with fixturesult
+ index = simple_index
+ date_index = date_range("2019-01-01", periods=10)
+ first_cat = index.union(date_index)
+ second_cat = index.union(index)
+
+ appended = Index(np.append(index, date_index.astype("O")))
+
+ tm.assert_index_equal(first_cat, appended)
+ tm.assert_index_equal(second_cat, index)
+ tm.assert_contains_all(index, first_cat)
+ tm.assert_contains_all(index, second_cat)
+ tm.assert_contains_all(date_index, first_cat)
+
+ def test_map_with_tuples(self):
+ # GH 12766
+
+ # Test that returning a single tuple from an Index
+ # returns an Index.
+ index = Index(np.arange(3), dtype=np.int64)
+ result = index.map(lambda x: (x,))
+ expected = Index([(i,) for i in index])
+ tm.assert_index_equal(result, expected)
+
+ # Test that returning a tuple from a map of a single index
+ # returns a MultiIndex object.
+ result = index.map(lambda x: (x, x == 1))
+ expected = MultiIndex.from_tuples([(i, i == 1) for i in index])
+ tm.assert_index_equal(result, expected)
+
+ def test_map_with_tuples_mi(self):
+ # Test that returning a single object from a MultiIndex
+ # returns an Index.
+ first_level = ["foo", "bar", "baz"]
+ multi_index = MultiIndex.from_tuples(zip(first_level, [1, 2, 3]))
+ reduced_index = multi_index.map(lambda x: x[0])
+ tm.assert_index_equal(reduced_index, Index(first_level))
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ date_range("2020-01-01", freq="D", periods=10),
+ period_range("2020-01-01", freq="D", periods=10),
+ timedelta_range("1 day", periods=10),
+ ],
+ )
+ def test_map_tseries_indices_return_index(self, index):
+ expected = Index([1] * 10)
+ result = index.map(lambda x: 1)
+ tm.assert_index_equal(expected, result)
+
+ def test_map_tseries_indices_accsr_return_index(self):
+ date_index = DatetimeIndex(
+ date_range("2020-01-01", periods=24, freq="h"), name="hourly"
+ )
+ result = date_index.map(lambda x: x.hour)
+ expected = Index(np.arange(24, dtype="int64"), name="hourly")
+ tm.assert_index_equal(result, expected, exact=True)
+
+ @pytest.mark.parametrize(
+ "mapper",
+ [
+ lambda values, index: {i: e for e, i in zip(values, index)},
+ lambda values, index: Series(values, index),
+ ],
+ )
+ def test_map_dictlike_simple(self, mapper):
+ # GH 12756
+ expected = Index(["foo", "bar", "baz"])
+ index = Index(np.arange(3), dtype=np.int64)
+ result = index.map(mapper(expected.values, index))
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "mapper",
+ [
+ lambda values, index: {i: e for e, i in zip(values, index)},
+ lambda values, index: Series(values, index),
+ ],
+ )
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_map_dictlike(self, index, mapper, request, using_infer_string):
+ # GH 12756
+ if isinstance(index, CategoricalIndex):
+ pytest.skip("Tested in test_categorical")
+ elif not index.is_unique:
+ pytest.skip("Cannot map duplicated index")
+ if (
+ not using_infer_string
+ and isinstance(index.dtype, pd.StringDtype)
+ and index.dtype.storage == "python"
+ ):
+ mark = pytest.mark.xfail(reason="map does not retain dtype")
+ request.applymarker(mark)
+
+ rng = np.arange(len(index), 0, -1, dtype=np.int64)
+
+ if index.empty:
+ # to match proper result coercion for uints
+ expected = Index([])
+ elif is_numeric_dtype(index.dtype):
+ expected = index._constructor(rng, dtype=index.dtype)
+ elif type(index) is Index and index.dtype != object:
+ # i.e. EA-backed, for now just Nullable
+ expected = Index(rng, dtype=index.dtype)
+ else:
+ expected = Index(rng)
+
+ result = index.map(mapper(expected, index))
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "mapper",
+ [Series(["foo", 2.0, "baz"], index=[0, 2, -1]), {0: "foo", 2: 2.0, -1: "baz"}],
+ )
+ def test_map_with_non_function_missing_values(self, mapper):
+ # GH 12756
+ expected = Index([2.0, np.nan, "foo"])
+ result = Index([2, 1, 0]).map(mapper)
+
+ tm.assert_index_equal(expected, result)
+
+ def test_map_na_exclusion(self):
+ index = Index([1.5, np.nan, 3, np.nan, 5])
+
+ result = index.map(lambda x: x * 2, na_action="ignore")
+ expected = index * 2
+ tm.assert_index_equal(result, expected)
+
+ def test_map_defaultdict(self):
+ index = Index([1, 2, 3])
+ default_dict = defaultdict(lambda: "blank")
+ default_dict[1] = "stuff"
+ result = index.map(default_dict)
+ expected = Index(["stuff", "blank", "blank"])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("name,expected", [("foo", "foo"), ("bar", None)])
+ def test_append_empty_preserve_name(self, name, expected):
+ left = Index([], name="foo")
+ right = Index([1, 2, 3], name=name)
+
+ result = left.append(right)
+ assert result.name == expected
+
+ @pytest.mark.parametrize(
+ "index, expected",
+ [
+ ("string", False),
+ ("bool-object", False),
+ ("bool-dtype", False),
+ ("categorical", False),
+ ("int64", True),
+ ("int32", True),
+ ("uint64", True),
+ ("uint32", True),
+ ("datetime", False),
+ ("float64", True),
+ ("float32", True),
+ ],
+ indirect=["index"],
+ )
+ def test_is_numeric(self, index, expected):
+ assert is_any_real_numeric_dtype(index) is expected
+
+ @pytest.mark.parametrize(
+ "index, expected",
+ [
+ ("string", True),
+ ("bool-object", True),
+ ("bool-dtype", False),
+ ("categorical", False),
+ ("int64", False),
+ ("int32", False),
+ ("uint64", False),
+ ("uint32", False),
+ ("datetime", False),
+ ("float64", False),
+ ("float32", False),
+ ],
+ indirect=["index"],
+ )
+ def test_is_object(self, index, expected, using_infer_string):
+ if using_infer_string and index.dtype == "string" and expected:
+ expected = False
+ assert is_object_dtype(index) is expected
+
+ def test_summary(self, index):
+ index._summary()
+
+ def test_logical_compat(self, all_boolean_reductions, simple_index):
+ index = simple_index
+ left = getattr(index, all_boolean_reductions)()
+ assert left == getattr(index.values, all_boolean_reductions)()
+ right = getattr(index.to_series(), all_boolean_reductions)()
+ # left might not match right exactly in e.g. string cases where the
+ # because we use np.any/all instead of .any/all
+ assert bool(left) == bool(right)
+
+ @pytest.mark.parametrize(
+ "index", ["string", "int64", "int32", "float64", "float32"], indirect=True
+ )
+ def test_drop_by_str_label(self, index):
+ n = len(index)
+ drop = index[list(range(5, 10))]
+ dropped = index.drop(drop)
+
+ expected = index[list(range(5)) + list(range(10, n))]
+ tm.assert_index_equal(dropped, expected)
+
+ dropped = index.drop(index[0])
+ expected = index[1:]
+ tm.assert_index_equal(dropped, expected)
+
+ @pytest.mark.parametrize(
+ "index", ["string", "int64", "int32", "float64", "float32"], indirect=True
+ )
+ @pytest.mark.parametrize("keys", [["foo", "bar"], ["1", "bar"]])
+ def test_drop_by_str_label_raises_missing_keys(self, index, keys):
+ with pytest.raises(KeyError, match=".* not found in axis"):
+ index.drop(keys)
+
+ @pytest.mark.parametrize(
+ "index", ["string", "int64", "int32", "float64", "float32"], indirect=True
+ )
+ def test_drop_by_str_label_errors_ignore(self, index):
+ n = len(index)
+ drop = index[list(range(5, 10))]
+ mixed = [*drop.tolist(), "foo"]
+ dropped = index.drop(mixed, errors="ignore")
+
+ expected = index[list(range(5)) + list(range(10, n))]
+ tm.assert_index_equal(dropped, expected)
+
+ dropped = index.drop(["foo", "bar"], errors="ignore")
+ expected = index[list(range(n))]
+ tm.assert_index_equal(dropped, expected)
+
+ def test_drop_by_numeric_label_loc(self):
+ # TODO: Parametrize numeric and str tests after self.strIndex fixture
+ index = Index([1, 2, 3])
+ dropped = index.drop(1)
+ expected = Index([2, 3])
+
+ tm.assert_index_equal(dropped, expected)
+
+ def test_drop_by_numeric_label_raises_missing_keys(self):
+ index = Index([1, 2, 3])
+ with pytest.raises(KeyError, match=re.escape("[4] not found in axis")):
+ index.drop([3, 4])
+
+ @pytest.mark.parametrize(
+ "key,expected", [(4, Index([1, 2, 3])), ([3, 4, 5], Index([1, 2]))]
+ )
+ def test_drop_by_numeric_label_errors_ignore(self, key, expected):
+ index = Index([1, 2, 3])
+ dropped = index.drop(key, errors="ignore")
+
+ tm.assert_index_equal(dropped, expected)
+
+ @pytest.mark.parametrize(
+ "values",
+ [["a", "b", ("c", "d")], ["a", ("c", "d"), "b"], [("c", "d"), "a", "b"]],
+ )
+ @pytest.mark.parametrize("to_drop", [[("c", "d"), "a"], ["a", ("c", "d")]])
+ def test_drop_tuple(self, values, to_drop):
+ # GH 18304
+ index = Index(values)
+ expected = Index(["b"], dtype=object)
+
+ result = index.drop(to_drop)
+ tm.assert_index_equal(result, expected)
+
+ removed = index.drop(to_drop[0])
+ for drop_me in to_drop[1], [to_drop[1]]:
+ result = removed.drop(drop_me)
+ tm.assert_index_equal(result, expected)
+
+ removed = index.drop(to_drop[1])
+ msg = rf"\"\[{re.escape(to_drop[1].__repr__())}\] not found in axis\""
+ for drop_me in to_drop[1], [to_drop[1]]:
+ with pytest.raises(KeyError, match=msg):
+ removed.drop(drop_me)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_drop_with_duplicates_in_index(self, index):
+ # GH38051
+ if len(index) == 0 or isinstance(index, MultiIndex):
+ pytest.skip("Test doesn't make sense for empty MultiIndex")
+ if isinstance(index, IntervalIndex) and not IS64:
+ pytest.skip("Cannot test IntervalIndex with int64 dtype on 32 bit platform")
+ index = index.unique().repeat(2)
+ expected = index[2:]
+ result = index.drop(index[0])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "attr",
+ [
+ "is_monotonic_increasing",
+ "is_monotonic_decreasing",
+ "_is_strictly_monotonic_increasing",
+ "_is_strictly_monotonic_decreasing",
+ ],
+ )
+ def test_is_monotonic_incomparable(self, attr):
+ index = Index([5, datetime.now(), 7])
+ assert not getattr(index, attr)
+
+ @pytest.mark.parametrize("values", [["foo", "bar", "quux"], {"foo", "bar", "quux"}])
+ @pytest.mark.parametrize(
+ "index,expected",
+ [
+ (["qux", "baz", "foo", "bar"], [False, False, True, True]),
+ ([], []), # empty
+ ],
+ )
+ def test_isin(self, values, index, expected):
+ index = Index(index)
+ result = index.isin(values)
+ expected = np.array(expected, dtype=bool)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_isin_nan_common_object(
+ self, nulls_fixture, nulls_fixture2, using_infer_string
+ ):
+ # Test cartesian product of null fixtures and ensure that we don't
+ # mangle the various types (save a corner case with PyPy)
+ idx = Index(["a", nulls_fixture])
+
+ # all nans are the same
+ if (
+ isinstance(nulls_fixture, float)
+ and isinstance(nulls_fixture2, float)
+ and math.isnan(nulls_fixture)
+ and math.isnan(nulls_fixture2)
+ ):
+ tm.assert_numpy_array_equal(
+ idx.isin([nulls_fixture2]),
+ np.array([False, True]),
+ )
+
+ elif nulls_fixture is nulls_fixture2: # should preserve NA type
+ tm.assert_numpy_array_equal(
+ idx.isin([nulls_fixture2]),
+ np.array([False, True]),
+ )
+
+ elif using_infer_string and idx.dtype == "string":
+ tm.assert_numpy_array_equal(
+ idx.isin([nulls_fixture2]),
+ np.array([False, True]),
+ )
+
+ else:
+ tm.assert_numpy_array_equal(
+ idx.isin([nulls_fixture2]),
+ np.array([False, False]),
+ )
+
+ def test_isin_nan_common_float64(self, nulls_fixture, float_numpy_dtype):
+ dtype = float_numpy_dtype
+
+ if nulls_fixture is pd.NaT or nulls_fixture is pd.NA:
+ # Check 1) that we cannot construct a float64 Index with this value
+ # and 2) that with a NaN we do not have .isin(nulls_fixture)
+ msg = (
+ r"float\(\) argument must be a string or a (real )?number, "
+ f"not {type(nulls_fixture).__name__!r}"
+ )
+ with pytest.raises(TypeError, match=msg):
+ Index([1.0, nulls_fixture], dtype=dtype)
+
+ idx = Index([1.0, np.nan], dtype=dtype)
+ assert not idx.isin([nulls_fixture]).any()
+ return
+
+ idx = Index([1.0, nulls_fixture], dtype=dtype)
+ res = idx.isin([np.nan])
+ tm.assert_numpy_array_equal(res, np.array([False, True]))
+
+ # we cannot compare NaT with NaN
+ res = idx.isin([pd.NaT])
+ tm.assert_numpy_array_equal(res, np.array([False, False]))
+
+ @pytest.mark.parametrize("level", [0, -1])
+ @pytest.mark.parametrize(
+ "index",
+ [
+ ["qux", "baz", "foo", "bar"],
+ np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float64),
+ ],
+ )
+ def test_isin_level_kwarg(self, level, index):
+ index = Index(index)
+ values = [*index.tolist()[-2:], "nonexisting"]
+
+ expected = np.array([False, False, True, True])
+ tm.assert_numpy_array_equal(expected, index.isin(values, level=level))
+
+ index.name = "foobar"
+ tm.assert_numpy_array_equal(expected, index.isin(values, level="foobar"))
+
+ def test_isin_level_kwarg_bad_level_raises(self, index):
+ for level in [10, index.nlevels, -(index.nlevels + 1)]:
+ with pytest.raises(IndexError, match="Too many levels"):
+ index.isin([], level=level)
+
+ @pytest.mark.parametrize("label", [1.0, "foobar", "xyzzy", np.nan])
+ def test_isin_level_kwarg_bad_label_raises(self, label, index):
+ if isinstance(index, MultiIndex):
+ index = index.rename(["foo", "bar", *index.names[2:]])
+ msg = f"'Level {label} not found'"
+ else:
+ index = index.rename("foo")
+ msg = rf"Requested level \({label}\) does not match index name \(foo\)"
+ with pytest.raises(KeyError, match=msg):
+ index.isin([], level=label)
+
+ @pytest.mark.parametrize("empty", [[], Series(dtype=object), np.array([])])
+ def test_isin_empty(self, empty):
+ # see gh-16991
+ index = Index(["a", "b"])
+ expected = np.array([False, False])
+
+ result = index.isin(empty)
+ tm.assert_numpy_array_equal(expected, result)
+
+ def test_isin_string_null(self, string_dtype_no_object):
+ # GH#55821
+ index = Index(["a", "b"], dtype=string_dtype_no_object)
+ result = index.isin([None])
+ expected = np.array([False, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "values",
+ [
+ [1, 2, 3, 4],
+ [1.0, 2.0, 3.0, 4.0],
+ [True, True, True, True],
+ ["foo", "bar", "baz", "qux"],
+ date_range("2018-01-01", freq="D", periods=4),
+ ],
+ )
+ def test_boolean_cmp(self, values):
+ index = Index(values)
+ result = index == values
+ expected = np.array([True, True, True, True], dtype=bool)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ @pytest.mark.parametrize("name,level", [(None, 0), ("a", "a")])
+ def test_get_level_values(self, index, name, level):
+ expected = index.copy()
+ if name:
+ expected.name = name
+
+ result = expected.get_level_values(level)
+ tm.assert_index_equal(result, expected)
+
+ def test_slice_keep_name(self):
+ index = Index(["a", "b"], name="asdf")
+ assert index.name == index[1:].name
+
+ def test_slice_is_unique(self):
+ # GH 57911
+ index = Index([1, 1, 2, 3, 4])
+ assert not index.is_unique
+ filtered_index = index[2:].copy()
+ assert filtered_index.is_unique
+
+ def test_slice_is_montonic(self):
+ """Test that is_monotonic_decreasing is correct on slices."""
+ # GH 57911
+ index = Index([1, 2, 3, 3])
+ assert not index.is_monotonic_decreasing
+
+ filtered_index = index[2:].copy()
+ assert filtered_index.is_monotonic_decreasing
+ assert filtered_index.is_monotonic_increasing
+
+ filtered_index = index[1:].copy()
+ assert not filtered_index.is_monotonic_decreasing
+ assert filtered_index.is_monotonic_increasing
+
+ filtered_index = index[:].copy()
+ assert not filtered_index.is_monotonic_decreasing
+ assert filtered_index.is_monotonic_increasing
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ "string",
+ "datetime",
+ "int64",
+ "int32",
+ "uint64",
+ "uint32",
+ "float64",
+ "float32",
+ ],
+ indirect=True,
+ )
+ def test_join_self(self, index, join_type):
+ result = index.join(index, how=join_type)
+ expected = index
+ if join_type == "outer":
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("method", ["strip", "rstrip", "lstrip"])
+ def test_str_attribute(self, method):
+ # GH9068
+ index = Index([" jack", "jill ", " jesse ", "frank"])
+ expected = Index([getattr(str, method)(x) for x in index.values])
+
+ result = getattr(index.str, method)()
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(range(5)),
+ date_range("2020-01-01", periods=10),
+ MultiIndex.from_tuples([("foo", "1"), ("bar", "3")]),
+ period_range(start="2000", end="2010", freq="Y"),
+ ],
+ )
+ def test_str_attribute_raises(self, index):
+ with pytest.raises(AttributeError, match="only use .str accessor"):
+ index.str.repeat(2)
+
+ @pytest.mark.parametrize(
+ "expand,expected",
+ [
+ (None, Index([["a", "b", "c"], ["d", "e"], ["f"]])),
+ (False, Index([["a", "b", "c"], ["d", "e"], ["f"]])),
+ (
+ True,
+ MultiIndex.from_tuples(
+ [("a", "b", "c"), ("d", "e", np.nan), ("f", np.nan, np.nan)]
+ ),
+ ),
+ ],
+ )
+ def test_str_split(self, expand, expected):
+ index = Index(["a b c", "d e", "f"])
+ if expand is not None:
+ result = index.str.split(expand=expand)
+ else:
+ result = index.str.split()
+
+ tm.assert_index_equal(result, expected)
+
+ def test_str_bool_return(self):
+ # test boolean case, should return np.array instead of boolean Index
+ index = Index(["a1", "a2", "b1", "b2"])
+ result = index.str.startswith("a")
+ expected = np.array([True, True, False, False])
+
+ tm.assert_numpy_array_equal(result, expected)
+ assert isinstance(result, np.ndarray)
+
+ def test_str_bool_series_indexing(self):
+ index = Index(["a1", "a2", "b1", "b2"])
+ s = Series(range(4), index=index)
+
+ result = s[s.index.str.startswith("a")]
+ expected = Series(range(2), index=["a1", "a2"])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "index,expected", [(list("abcd"), True), (range(4), False)]
+ )
+ def test_tab_completion(self, index, expected):
+ # GH 9910
+ index = Index(index)
+ result = "str" in dir(index)
+ assert result == expected
+
+ def test_indexing_doesnt_change_class(self):
+ index = Index([1, 2, 3, "a", "b", "c"])
+
+ assert index[1:3].identical(Index([2, 3], dtype=np.object_))
+ assert index[[0, 1]].identical(Index([1, 2], dtype=np.object_))
+
+ def test_outer_join_sort(self):
+ left_index = Index(np.random.default_rng(2).permutation(15))
+ right_index = date_range("2020-01-01", periods=10)
+
+ with tm.assert_produces_warning(RuntimeWarning, match="not supported between"):
+ result = left_index.join(right_index, how="outer")
+
+ with tm.assert_produces_warning(RuntimeWarning, match="not supported between"):
+ expected = left_index.astype(object).union(right_index.astype(object))
+
+ tm.assert_index_equal(result, expected)
+
+ def test_take_fill_value(self):
+ # GH 12631
+ index = Index(list("ABC"), name="xxx")
+ result = index.take(np.array([1, 0, -1]))
+ expected = Index(list("BAC"), name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ # fill_value
+ result = index.take(np.array([1, 0, -1]), fill_value=True)
+ expected = Index(["B", "A", np.nan], name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ # allow_fill=False
+ result = index.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = Index(["B", "A", "C"], name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ def test_take_fill_value_none_raises(self):
+ index = Index(list("ABC"), name="xxx")
+ msg = (
+ "When allow_fill=True and fill_value is not None, all indices must be >= -1"
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ index.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ index.take(np.array([1, 0, -5]), fill_value=True)
+
+ def test_take_bad_bounds_raises(self):
+ index = Index(list("ABC"), name="xxx")
+ with pytest.raises(IndexError, match="out of bounds"):
+ index.take(np.array([1, -5]))
+
+ @pytest.mark.parametrize("name", [None, "foobar"])
+ @pytest.mark.parametrize(
+ "labels",
+ [
+ [],
+ np.array([]),
+ ["A", "B", "C"],
+ ["C", "B", "A"],
+ np.array(["A", "B", "C"]),
+ np.array(["C", "B", "A"]),
+ # Must preserve name even if dtype changes
+ date_range("20130101", periods=3).values,
+ date_range("20130101", periods=3).tolist(),
+ ],
+ )
+ def test_reindex_preserves_name_if_target_is_list_or_ndarray(self, name, labels):
+ # GH6552
+ index = Index([0, 1, 2])
+ index.name = name
+ assert index.reindex(labels)[0].name == name
+
+ @pytest.mark.parametrize("labels", [[], np.array([]), np.array([], dtype=np.int64)])
+ def test_reindex_preserves_type_if_target_is_empty_list_or_array(self, labels):
+ # GH7774
+ index = Index(list("abc"))
+ assert index.reindex(labels)[0].dtype.type == index.dtype.type
+
+ def test_reindex_doesnt_preserve_type_if_target_is_empty_index(self):
+ # GH7774
+ index = Index(list("abc"))
+ labels = DatetimeIndex([])
+ dtype = np.datetime64
+ assert index.reindex(labels)[0].dtype.type == dtype
+
+ def test_reindex_doesnt_preserve_type_if_target_is_empty_index_numeric(
+ self, any_real_numpy_dtype
+ ):
+ # GH7774
+ dtype = any_real_numpy_dtype
+ index = Index(list("abc"))
+ labels = Index([], dtype=dtype)
+ assert index.reindex(labels)[0].dtype == dtype
+
+ def test_reindex_no_type_preserve_target_empty_mi(self):
+ index = Index(list("abc"))
+ result = index.reindex(
+ MultiIndex([Index([], np.int64), Index([], np.float64)], [[], []])
+ )[0]
+ assert result.levels[0].dtype.type == np.int64
+ assert result.levels[1].dtype.type == np.float64
+
+ def test_reindex_ignoring_level(self):
+ # GH#35132
+ idx = Index([1, 2, 3], name="x")
+ idx2 = Index([1, 2, 3, 4], name="x")
+ expected = Index([1, 2, 3, 4], name="x")
+ result, _ = idx.reindex(idx2, level="x")
+ tm.assert_index_equal(result, expected)
+
+ def test_groupby(self):
+ index = Index(range(5))
+ result = index.groupby(np.array([1, 1, 2, 2, 2]))
+ expected = {1: Index([0, 1]), 2: Index([2, 3, 4])}
+
+ tm.assert_dict_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "mi,expected",
+ [
+ (MultiIndex.from_tuples([(1, 2), (4, 5)]), np.array([True, True])),
+ (MultiIndex.from_tuples([(1, 2), (4, 6)]), np.array([True, False])),
+ ],
+ )
+ def test_equals_op_multiindex(self, mi, expected):
+ # GH9785
+ # test comparisons of multiindex
+ df = DataFrame(
+ [3, 6],
+ columns=["c"],
+ index=MultiIndex.from_arrays([[1, 4], [2, 5]], names=["a", "b"]),
+ )
+
+ result = df.index == mi
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_equals_op_multiindex_identify(self):
+ df = DataFrame(
+ [3, 6],
+ columns=["c"],
+ index=MultiIndex.from_arrays([[1, 4], [2, 5]], names=["a", "b"]),
+ )
+
+ result = df.index == df.index
+ expected = np.array([True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ MultiIndex.from_tuples([(1, 2), (4, 5), (8, 9)]),
+ Index(["foo", "bar", "baz"]),
+ ],
+ )
+ def test_equals_op_mismatched_multiindex_raises(self, index):
+ df = DataFrame(
+ [3, 6],
+ columns=["c"],
+ index=MultiIndex.from_arrays([[1, 4], [2, 5]], names=["a", "b"]),
+ )
+
+ with pytest.raises(ValueError, match="Lengths must match"):
+ df.index == index
+
+ def test_equals_op_index_vs_mi_same_length(self, using_infer_string):
+ mi = MultiIndex.from_tuples([(1, 2), (4, 5), (8, 9)])
+ index = Index(["foo", "bar", "baz"])
+
+ result = mi == index
+ expected = np.array([False, False, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dt_conv, arg",
+ [
+ (pd.to_datetime, ["2000-01-01", "2000-01-02"]),
+ (pd.to_timedelta, ["01:02:03", "01:02:04"]),
+ ],
+ )
+ def test_dt_conversion_preserves_name(self, dt_conv, arg):
+ # GH 10875
+ index = Index(arg, name="label")
+ assert index.name == dt_conv(index).name
+
+ def test_cached_properties_not_settable(self):
+ index = Index([1, 2, 3])
+ with pytest.raises(AttributeError, match="Can't set attribute"):
+ index.is_unique = False
+
+ def test_tab_complete_warning(self, ip):
+ # https://github.com/pandas-dev/pandas/issues/16409
+ pytest.importorskip("IPython", minversion="6.0.0")
+ from IPython.core.completer import provisionalcompleter
+
+ code = "import pandas as pd; idx = pd.Index([1, 2])"
+ ip.run_cell(code)
+
+ # GH 31324 newer jedi version raises Deprecation warning;
+ # appears resolved 2021-02-02
+ with tm.assert_produces_warning(None, raise_on_extra_warnings=False):
+ with provisionalcompleter("ignore"):
+ list(ip.Completer.completions("idx.", 4))
+
+ def test_contains_method_removed(self, index):
+ # GH#30103 method removed for all types except IntervalIndex
+ if isinstance(index, IntervalIndex):
+ index.contains(1)
+ else:
+ msg = f"'{type(index).__name__}' object has no attribute 'contains'"
+ with pytest.raises(AttributeError, match=msg):
+ index.contains(1)
+
+ def test_sortlevel(self):
+ index = Index([5, 4, 3, 2, 1])
+ with pytest.raises(Exception, match="ascending must be a single bool value or"):
+ index.sortlevel(ascending="True")
+
+ with pytest.raises(
+ Exception, match="ascending must be a list of bool values of length 1"
+ ):
+ index.sortlevel(ascending=[True, True])
+
+ with pytest.raises(Exception, match="ascending must be a bool value"):
+ index.sortlevel(ascending=["True"])
+
+ expected = Index([1, 2, 3, 4, 5])
+ result = index.sortlevel(ascending=[True])
+ tm.assert_index_equal(result[0], expected)
+
+ expected = Index([1, 2, 3, 4, 5])
+ result = index.sortlevel(ascending=True)
+ tm.assert_index_equal(result[0], expected)
+
+ expected = Index([5, 4, 3, 2, 1])
+ result = index.sortlevel(ascending=False)
+ tm.assert_index_equal(result[0], expected)
+
+ def test_sortlevel_na_position(self):
+ # GH#51612
+ idx = Index([1, np.nan])
+ result = idx.sortlevel(na_position="first")[0]
+ expected = Index([np.nan, 1])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "periods, expected_results",
+ [
+ (1, [np.nan, 10, 10, 10, 10]),
+ (2, [np.nan, np.nan, 20, 20, 20]),
+ (3, [np.nan, np.nan, np.nan, 30, 30]),
+ ],
+ )
+ def test_index_diff(self, periods, expected_results):
+ # GH#19708
+ idx = Index([10, 20, 30, 40, 50])
+ result = idx.diff(periods)
+ expected = Index(expected_results)
+
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "decimals, expected_results",
+ [
+ (0, [1.0, 2.0, 3.0]),
+ (1, [1.2, 2.3, 3.5]),
+ (2, [1.23, 2.35, 3.46]),
+ ],
+ )
+ def test_index_round(self, decimals, expected_results):
+ # GH#19708
+ idx = Index([1.234, 2.345, 3.456])
+ result = idx.round(decimals)
+ expected = Index(expected_results)
+
+ tm.assert_index_equal(result, expected)
+
+
+class TestMixedIntIndex:
+ # Mostly the tests from common.py for which the results differ
+ # in py2 and py3 because ints and strings are uncomparable in py3
+ # (GH 13514)
+ @pytest.fixture
+ def simple_index(self) -> Index:
+ return Index([0, "a", 1, "b", 2, "c"])
+
+ def test_argsort(self, simple_index):
+ index = simple_index
+ with pytest.raises(TypeError, match="'>|<' not supported"):
+ index.argsort()
+
+ def test_numpy_argsort(self, simple_index):
+ index = simple_index
+ with pytest.raises(TypeError, match="'>|<' not supported"):
+ np.argsort(index)
+
+ def test_copy_name(self, simple_index):
+ # Check that "name" argument passed at initialization is honoured
+ # GH12309
+ index = simple_index
+
+ first = type(index)(index, copy=True, name="mario")
+ second = type(first)(first, copy=False)
+
+ # Even though "copy=False", we want a new object.
+ assert first is not second
+ tm.assert_index_equal(first, second)
+
+ assert first.name == "mario"
+ assert second.name == "mario"
+
+ s1 = Series(2, index=first)
+ s2 = Series(3, index=second[:-1])
+
+ s3 = s1 * s2
+
+ assert s3.index.name == "mario"
+
+ def test_copy_name2(self):
+ # Check that adding a "name" parameter to the copy is honored
+ # GH14302
+ index = Index([1, 2], name="MyName")
+ index1 = index.copy()
+
+ tm.assert_index_equal(index, index1)
+
+ index2 = index.copy(name="NewName")
+ tm.assert_index_equal(index, index2, check_names=False)
+ assert index.name == "MyName"
+ assert index2.name == "NewName"
+
+ def test_unique_na(self):
+ idx = Index([2, np.nan, 2, 1], name="my_index")
+ expected = Index([2, np.nan, 1], name="my_index")
+ result = idx.unique()
+ tm.assert_index_equal(result, expected)
+
+ def test_logical_compat(self, simple_index):
+ index = simple_index
+ assert index.all() == index.values.all()
+ assert index.any() == index.values.any()
+
+ @pytest.mark.parametrize("how", ["any", "all"])
+ @pytest.mark.parametrize("dtype", [None, object, "category"])
+ @pytest.mark.parametrize(
+ "vals,expected",
+ [
+ ([1, 2, 3], [1, 2, 3]),
+ ([1.0, 2.0, 3.0], [1.0, 2.0, 3.0]),
+ ([1.0, 2.0, np.nan, 3.0], [1.0, 2.0, 3.0]),
+ (["A", "B", "C"], ["A", "B", "C"]),
+ (["A", np.nan, "B", "C"], ["A", "B", "C"]),
+ ],
+ )
+ def test_dropna(self, how, dtype, vals, expected):
+ # GH 6194
+ index = Index(vals, dtype=dtype)
+ result = index.dropna(how=how)
+ expected = Index(expected, dtype=dtype)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("how", ["any", "all"])
+ @pytest.mark.parametrize(
+ "index,expected",
+ [
+ (
+ DatetimeIndex(["2011-01-01", "2011-01-02", "2011-01-03"]),
+ DatetimeIndex(["2011-01-01", "2011-01-02", "2011-01-03"]),
+ ),
+ (
+ DatetimeIndex(["2011-01-01", "2011-01-02", "2011-01-03", pd.NaT]),
+ DatetimeIndex(["2011-01-01", "2011-01-02", "2011-01-03"]),
+ ),
+ (
+ TimedeltaIndex(["1 days", "2 days", "3 days"]),
+ TimedeltaIndex(["1 days", "2 days", "3 days"]),
+ ),
+ (
+ TimedeltaIndex([pd.NaT, "1 days", "2 days", "3 days", pd.NaT]),
+ TimedeltaIndex(["1 days", "2 days", "3 days"]),
+ ),
+ (
+ PeriodIndex(["2012-02", "2012-04", "2012-05"], freq="M"),
+ PeriodIndex(["2012-02", "2012-04", "2012-05"], freq="M"),
+ ),
+ (
+ PeriodIndex(["2012-02", "2012-04", "NaT", "2012-05"], freq="M"),
+ PeriodIndex(["2012-02", "2012-04", "2012-05"], freq="M"),
+ ),
+ ],
+ )
+ def test_dropna_dt_like(self, how, index, expected):
+ result = index.dropna(how=how)
+ tm.assert_index_equal(result, expected)
+
+ def test_dropna_invalid_how_raises(self):
+ msg = "invalid how option: xxx"
+ with pytest.raises(ValueError, match=msg):
+ Index([1, 2, 3]).dropna(how="xxx")
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index([np.nan]),
+ Index([np.nan, 1]),
+ Index([1, 2, np.nan]),
+ Index(["a", "b", np.nan]),
+ pd.to_datetime(["NaT"]),
+ pd.to_datetime(["NaT", "2000-01-01"]),
+ pd.to_datetime(["2000-01-01", "NaT", "2000-01-02"]),
+ pd.to_timedelta(["1 day", "NaT"]),
+ ],
+ )
+ def test_is_monotonic_na(self, index):
+ assert index.is_monotonic_increasing is False
+ assert index.is_monotonic_decreasing is False
+ assert index._is_strictly_monotonic_increasing is False
+ assert index._is_strictly_monotonic_decreasing is False
+
+ @pytest.mark.parametrize("dtype", ["f8", "m8[ns]", "M8[us]"])
+ @pytest.mark.parametrize("unique_first", [True, False])
+ def test_is_monotonic_unique_na(self, dtype, unique_first):
+ # GH 55755
+ index = Index([None, 1, 1], dtype=dtype)
+ if unique_first:
+ assert index.is_unique is False
+ assert index.is_monotonic_increasing is False
+ assert index.is_monotonic_decreasing is False
+ else:
+ assert index.is_monotonic_increasing is False
+ assert index.is_monotonic_decreasing is False
+ assert index.is_unique is False
+
+ def test_int_name_format(self, frame_or_series):
+ index = Index(["a", "b", "c"], name=0)
+ result = frame_or_series(list(range(3)), index=index)
+ assert "0" in repr(result)
+
+ def test_str_to_bytes_raises(self):
+ # GH 26447
+ index = Index([str(x) for x in range(10)])
+ msg = "^'str' object cannot be interpreted as an integer$"
+ with pytest.raises(TypeError, match=msg):
+ bytes(index)
+
+ @pytest.mark.filterwarnings("ignore:elementwise comparison failed:FutureWarning")
+ def test_index_with_tuple_bool(self):
+ # GH34123
+ # TODO: also this op right now produces FutureWarning from numpy
+ # https://github.com/numpy/numpy/issues/11521
+ idx = Index([("a", "b"), ("b", "c"), ("c", "a")])
+ result = idx == ("c", "a")
+ expected = np.array([False, False, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestIndexUtils:
+ @pytest.mark.parametrize(
+ "data, names, expected",
+ [
+ ([[1, 2, 4]], None, Index([1, 2, 4])),
+ ([[1, 2, 4]], ["name"], Index([1, 2, 4], name="name")),
+ ([[1, 2, 3]], None, RangeIndex(1, 4)),
+ ([[1, 2, 3]], ["name"], RangeIndex(1, 4, name="name")),
+ (
+ [["a", "a"], ["c", "d"]],
+ None,
+ MultiIndex([["a"], ["c", "d"]], [[0, 0], [0, 1]]),
+ ),
+ (
+ [["a", "a"], ["c", "d"]],
+ ["L1", "L2"],
+ MultiIndex([["a"], ["c", "d"]], [[0, 0], [0, 1]], names=["L1", "L2"]),
+ ),
+ ],
+ )
+ def test_ensure_index_from_sequences(self, data, names, expected):
+ result = ensure_index_from_sequences(data, names)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_ensure_index_mixed_closed_intervals(self):
+ # GH27172
+ intervals = [
+ pd.Interval(0, 1, closed="left"),
+ pd.Interval(1, 2, closed="right"),
+ pd.Interval(2, 3, closed="neither"),
+ pd.Interval(3, 4, closed="both"),
+ ]
+ result = ensure_index(intervals)
+ expected = Index(intervals, dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ def test_ensure_index_uint64(self):
+ # with both 0 and a large-uint64, np.array will infer to float64
+ # https://github.com/numpy/numpy/issues/19146
+ # but a more accurate choice would be uint64
+ values = [0, np.iinfo(np.uint64).max]
+
+ result = ensure_index(values)
+ assert list(result) == values
+
+ expected = Index(values, dtype="uint64")
+ tm.assert_index_equal(result, expected)
+
+ def test_get_combined_index(self):
+ result = _get_combined_index([])
+ expected = RangeIndex(0)
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "opname",
+ [
+ "eq",
+ "ne",
+ "le",
+ "lt",
+ "ge",
+ "gt",
+ "add",
+ "radd",
+ "sub",
+ "rsub",
+ "mul",
+ "rmul",
+ "truediv",
+ "rtruediv",
+ "floordiv",
+ "rfloordiv",
+ "pow",
+ "rpow",
+ "mod",
+ "divmod",
+ ],
+)
+def test_generated_op_names(opname, index):
+ opname = f"__{opname}__"
+ method = getattr(index, opname)
+ assert method.__name__ == opname
+
+
+@pytest.mark.parametrize(
+ "klass",
+ [
+ partial(CategoricalIndex, data=[1]),
+ partial(DatetimeIndex, data=["2020-01-01"]),
+ partial(PeriodIndex, data=["2020-01-01"]),
+ partial(TimedeltaIndex, data=["1 day"]),
+ partial(RangeIndex, start=range(1)),
+ partial(IntervalIndex, data=[pd.Interval(0, 1)]),
+ partial(Index, data=["a"], dtype=object),
+ partial(MultiIndex, levels=[1], codes=[0]),
+ ],
+)
+def test_index_subclass_constructor_wrong_kwargs(klass):
+ # GH #19348
+ with pytest.raises(TypeError, match="unexpected keyword argument"):
+ klass(foo="bar")
+
+
+def test_deprecated_fastpath():
+ msg = "[Uu]nexpected keyword argument"
+ with pytest.raises(TypeError, match=msg):
+ Index(np.array(["a", "b"], dtype=object), name="test", fastpath=True)
+
+ with pytest.raises(TypeError, match=msg):
+ Index(np.array([1, 2, 3], dtype="int64"), name="test", fastpath=True)
+
+ with pytest.raises(TypeError, match=msg):
+ RangeIndex(0, 5, 2, name="test", fastpath=True)
+
+ with pytest.raises(TypeError, match=msg):
+ CategoricalIndex(["a", "b", "c"], name="test", fastpath=True)
+
+
+def test_shape_of_invalid_index():
+ # Pre-2.0, it was possible to create "invalid" index objects backed by
+ # a multi-dimensional array (see https://github.com/pandas-dev/pandas/issues/27125
+ # about this). However, as long as this is not solved in general,this test ensures
+ # that the returned shape is consistent with this underlying array for
+ # compat with matplotlib (see https://github.com/pandas-dev/pandas/issues/27775)
+ idx = Index([0, 1, 2, 3])
+ with pytest.raises(ValueError, match="Multi-dimensional indexing"):
+ # GH#30588 multi-dimensional indexing deprecated
+ idx[:, None]
+
+
+@pytest.mark.parametrize("dtype", [None, np.int64, np.uint64, np.float64])
+def test_validate_1d_input(dtype):
+ # GH#27125 check that we do not have >1-dimensional input
+ msg = "Index data must be 1-dimensional"
+
+ arr = np.arange(8).reshape(2, 2, 2)
+ with pytest.raises(ValueError, match=msg):
+ Index(arr, dtype=dtype)
+
+ df = DataFrame(arr.reshape(4, 2))
+ with pytest.raises(ValueError, match=msg):
+ Index(df, dtype=dtype)
+
+ # GH#13601 trying to assign a multi-dimensional array to an index is not allowed
+ ser = Series(0, range(4))
+ with pytest.raises(ValueError, match=msg):
+ ser.index = np.array([[2, 3]] * 4, dtype=dtype)
+
+
+@pytest.mark.parametrize(
+ "klass, extra_kwargs",
+ [
+ [Index, {}],
+ *[[lambda x: Index(x, dtype=dtyp), {}] for dtyp in tm.ALL_REAL_NUMPY_DTYPES],
+ [DatetimeIndex, {}],
+ [TimedeltaIndex, {}],
+ [PeriodIndex, {"freq": "Y"}],
+ ],
+)
+def test_construct_from_memoryview(klass, extra_kwargs):
+ # GH 13120
+ result = klass(memoryview(np.arange(2000, 2005)), **extra_kwargs)
+ expected = klass(list(range(2000, 2005)), **extra_kwargs)
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+@pytest.mark.parametrize("op", [operator.lt, operator.gt])
+def test_nan_comparison_same_object(op):
+ # GH#47105
+ idx = Index([np.nan])
+ expected = np.array([False])
+
+ result = op(idx, idx)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = op(idx, idx.copy())
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_is_monotonic_pyarrow_list_type():
+ # GH 57333
+ import pyarrow as pa
+
+ idx = Index([[1], [2, 3]], dtype=pd.ArrowDtype(pa.list_(pa.int64())))
+ assert not idx.is_monotonic_increasing
+ assert not idx.is_monotonic_decreasing
+
+
+def test_index_equals_different_string_dtype(string_dtype_no_object):
+ # GH 61099
+ idx_obj = Index(["a", "b", "c"])
+ idx_str = Index(["a", "b", "c"], dtype=string_dtype_no_object)
+
+ assert idx_obj.equals(idx_str)
+ assert idx_str.equals(idx_obj)
+
+
+def test_index_comparison_different_string_dtype(string_dtype_no_object):
+ # GH 61099
+ idx = Index(["a", "b", "c"])
+ s_obj = Series([1, 2, 3], index=idx)
+ s_str = Series([4, 5, 6], index=idx.astype(string_dtype_no_object))
+
+ expected = Series([True, True, True], index=["a", "b", "c"])
+ result = s_obj < s_str
+ assert_series_equal(result, expected)
+
+ result = s_str > s_obj
+ expected.index = idx.astype(string_dtype_no_object)
+ assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..77153dc3cc392a4f4eeaf8130e18f33688c52e49
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_common.py
@@ -0,0 +1,536 @@
+"""
+Collection of tests asserting things that should be true for
+any index subclass except for MultiIndex. Makes use of the `index_flat`
+fixture defined in pandas/conftest.py.
+"""
+
+from copy import (
+ copy,
+ deepcopy,
+)
+import re
+
+import numpy as np
+import pytest
+
+from pandas.compat import IS64
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.common import (
+ is_integer_dtype,
+ is_numeric_dtype,
+)
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ MultiIndex,
+ PeriodIndex,
+ RangeIndex,
+)
+import pandas._testing as tm
+
+
+class TestCommon:
+ @pytest.mark.parametrize("name", [None, "new_name"])
+ def test_to_frame(self, name, index_flat):
+ # see GH#15230, GH#22580
+ idx = index_flat
+
+ if name:
+ idx_name = name
+ else:
+ idx_name = idx.name or 0
+
+ df = idx.to_frame(name=idx_name)
+
+ assert df.index is idx
+ assert len(df.columns) == 1
+ assert df.columns[0] == idx_name
+
+ df = idx.to_frame(index=False, name=idx_name)
+ assert df.index is not idx
+
+ def test_droplevel(self, index_flat):
+ # GH 21115
+ # MultiIndex is tested separately in test_multi.py
+ index = index_flat
+
+ assert index.droplevel([]).equals(index)
+
+ for level in [index.name, [index.name]]:
+ if isinstance(index.name, tuple) and level is index.name:
+ # GH 21121 : droplevel with tuple name
+ continue
+ msg = (
+ "Cannot remove 1 levels from an index with 1 levels: at least one "
+ "level must be left."
+ )
+ with pytest.raises(ValueError, match=msg):
+ index.droplevel(level)
+
+ for level in "wrong", ["wrong"]:
+ with pytest.raises(
+ KeyError,
+ match=r"'Requested level \(wrong\) does not match index name \(None\)'",
+ ):
+ index.droplevel(level)
+
+ def test_constructor_non_hashable_name(self, index_flat):
+ # GH 20527
+ index = index_flat
+
+ message = "Index.name must be a hashable type"
+ renamed = [["1"]]
+
+ # With .rename()
+ with pytest.raises(TypeError, match=message):
+ index.rename(name=renamed)
+
+ # With .set_names()
+ with pytest.raises(TypeError, match=message):
+ index.set_names(names=renamed)
+
+ def test_constructor_unwraps_index(self, index_flat):
+ a = index_flat
+ # Passing dtype is necessary for Index([True, False], dtype=object)
+ # case.
+ b = type(a)(a, dtype=a.dtype)
+ tm.assert_equal(a._data, b._data)
+
+ def test_to_flat_index(self, index_flat):
+ # 22866
+ index = index_flat
+
+ result = index.to_flat_index()
+ tm.assert_index_equal(result, index)
+
+ def test_set_name_methods(self, index_flat):
+ # MultiIndex tested separately
+ index = index_flat
+ new_name = "This is the new name for this index"
+
+ original_name = index.name
+ new_ind = index.set_names([new_name])
+ assert new_ind.name == new_name
+ assert index.name == original_name
+ res = index.rename(new_name, inplace=True)
+
+ # should return None
+ assert res is None
+ assert index.name == new_name
+ assert index.names == [new_name]
+ with pytest.raises(ValueError, match="Level must be None"):
+ index.set_names("a", level=0)
+
+ # rename in place just leaves tuples and other containers alone
+ name = ("A", "B")
+ index.rename(name, inplace=True)
+ assert index.name == name
+ assert index.names == [name]
+
+ @pytest.mark.xfail
+ def test_set_names_single_label_no_level(self, index_flat):
+ with pytest.raises(TypeError, match="list-like"):
+ # should still fail even if it would be the right length
+ index_flat.set_names("a")
+
+ def test_copy_and_deepcopy(self, index_flat):
+ index = index_flat
+
+ for func in (copy, deepcopy):
+ idx_copy = func(index)
+ assert idx_copy is not index
+ assert idx_copy.equals(index)
+
+ new_copy = index.copy(deep=True, name="banana")
+ assert new_copy.name == "banana"
+
+ def test_copy_name(self, index_flat):
+ # GH#12309: Check that the "name" argument
+ # passed at initialization is honored.
+ index = index_flat
+
+ first = type(index)(index, copy=True, name="mario")
+ second = type(first)(first, copy=False)
+
+ # Even though "copy=False", we want a new object.
+ assert first is not second
+ tm.assert_index_equal(first, second)
+
+ # Not using tm.assert_index_equal() since names differ.
+ assert index.equals(first)
+
+ assert first.name == "mario"
+ assert second.name == "mario"
+
+ # TODO: belongs in series arithmetic tests?
+ s1 = pd.Series(2, index=first)
+ s2 = pd.Series(3, index=second[:-1])
+ # See GH#13365
+ s3 = s1 * s2
+ assert s3.index.name == "mario"
+
+ def test_copy_name2(self, index_flat):
+ # GH#35592
+ index = index_flat
+
+ assert index.copy(name="mario").name == "mario"
+
+ with pytest.raises(ValueError, match="Length of new names must be 1, got 2"):
+ index.copy(name=["mario", "luigi"])
+
+ msg = f"{type(index).__name__}.name must be a hashable type"
+ with pytest.raises(TypeError, match=msg):
+ index.copy(name=[["mario"]])
+
+ def test_unique_level(self, index_flat):
+ # don't test a MultiIndex here (as its tested separated)
+ index = index_flat
+
+ # GH 17896
+ expected = index.drop_duplicates()
+ for level in [0, index.name, None]:
+ result = index.unique(level=level)
+ tm.assert_index_equal(result, expected)
+
+ msg = "Too many levels: Index has only 1 level, not 4"
+ with pytest.raises(IndexError, match=msg):
+ index.unique(level=3)
+
+ msg = (
+ rf"Requested level \(wrong\) does not match index name "
+ rf"\({re.escape(index.name.__repr__())}\)"
+ )
+ with pytest.raises(KeyError, match=msg):
+ index.unique(level="wrong")
+
+ def test_unique(self, index_flat):
+ # MultiIndex tested separately
+ index = index_flat
+ if not len(index):
+ pytest.skip("Skip check for empty Index and MultiIndex")
+
+ idx = index[[0] * 5]
+ idx_unique = index[[0]]
+
+ # We test against `idx_unique`, so first we make sure it's unique
+ # and doesn't contain nans.
+ assert idx_unique.is_unique is True
+ try:
+ assert idx_unique.hasnans is False
+ except NotImplementedError:
+ pass
+
+ result = idx.unique()
+ tm.assert_index_equal(
+ result, idx_unique, exact=not isinstance(index, RangeIndex)
+ )
+
+ # nans:
+ if not index._can_hold_na:
+ pytest.skip("Skip na-check if index cannot hold na")
+
+ vals = index._values.copy()[[0] * 5]
+ vals[0] = np.nan
+
+ vals_unique = vals[:2]
+ idx_nan = index._shallow_copy(vals)
+ idx_unique_nan = index._shallow_copy(vals_unique)
+ assert idx_unique_nan.is_unique is True
+
+ assert idx_nan.dtype == index.dtype
+ assert idx_unique_nan.dtype == index.dtype
+
+ expected = idx_unique_nan
+ for pos, i in enumerate([idx_nan, idx_unique_nan]):
+ result = i.unique()
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.filterwarnings("ignore:Period with BDay freq:FutureWarning")
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_searchsorted_monotonic(self, index_flat, request):
+ # GH17271
+ index = index_flat
+ # not implemented for tuple searches in MultiIndex
+ # or Intervals searches in IntervalIndex
+ if isinstance(index, pd.IntervalIndex):
+ mark = pytest.mark.xfail(
+ reason="IntervalIndex.searchsorted does not support Interval arg",
+ raises=NotImplementedError,
+ )
+ request.applymarker(mark)
+
+ # nothing to test if the index is empty
+ if index.empty:
+ pytest.skip("Skip check for empty Index")
+ value = index[0]
+
+ # determine the expected results (handle dupes for 'right')
+ expected_left, expected_right = 0, (index == value).argmin()
+ if expected_right == 0:
+ # all values are the same, expected_right should be length
+ expected_right = len(index)
+
+ if index.is_monotonic_increasing or index.is_monotonic_decreasing:
+ ssm_left = index._searchsorted_monotonic(value, side="left")
+ assert expected_left == ssm_left
+
+ ssm_right = index._searchsorted_monotonic(value, side="right")
+ assert expected_right == ssm_right
+
+ ss_left = index.searchsorted(value, side="left")
+ assert expected_left == ss_left
+
+ ss_right = index.searchsorted(value, side="right")
+ assert expected_right == ss_right
+ else:
+ # non-monotonic should raise.
+ msg = "index must be monotonic increasing or decreasing"
+ with pytest.raises(ValueError, match=msg):
+ index._searchsorted_monotonic(value, side="left")
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_drop_duplicates(self, index_flat, keep):
+ # MultiIndex is tested separately
+ index = index_flat
+ if isinstance(index, RangeIndex):
+ pytest.skip(
+ "RangeIndex is tested in test_drop_duplicates_no_duplicates "
+ "as it cannot hold duplicates"
+ )
+ if len(index) == 0:
+ pytest.skip(
+ "empty index is tested in test_drop_duplicates_no_duplicates "
+ "as it cannot hold duplicates"
+ )
+
+ # make unique index
+ holder = type(index)
+ unique_values = list(set(index))
+ dtype = index.dtype if is_numeric_dtype(index) else None
+ unique_idx = holder(unique_values, dtype=dtype)
+
+ # make duplicated index
+ n = len(unique_idx)
+ duplicated_selection = np.random.default_rng(2).choice(n, int(n * 1.5))
+ idx = holder(unique_idx.values[duplicated_selection])
+
+ # Series.duplicated is tested separately
+ expected_duplicated = (
+ pd.Series(duplicated_selection).duplicated(keep=keep).values
+ )
+ tm.assert_numpy_array_equal(idx.duplicated(keep=keep), expected_duplicated)
+
+ # Series.drop_duplicates is tested separately
+ expected_dropped = holder(pd.Series(idx).drop_duplicates(keep=keep))
+ tm.assert_index_equal(idx.drop_duplicates(keep=keep), expected_dropped)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_drop_duplicates_no_duplicates(self, index_flat):
+ # MultiIndex is tested separately
+ index = index_flat
+
+ # make unique index
+ if isinstance(index, RangeIndex):
+ # RangeIndex cannot have duplicates
+ unique_idx = index
+ else:
+ holder = type(index)
+ unique_values = list(set(index))
+ dtype = index.dtype if is_numeric_dtype(index) else None
+ unique_idx = holder(unique_values, dtype=dtype)
+
+ # check on unique index
+ expected_duplicated = np.array([False] * len(unique_idx), dtype="bool")
+ tm.assert_numpy_array_equal(unique_idx.duplicated(), expected_duplicated)
+ result_dropped = unique_idx.drop_duplicates()
+ tm.assert_index_equal(result_dropped, unique_idx)
+ # validate shallow copy
+ assert result_dropped is not unique_idx
+
+ def test_drop_duplicates_inplace(self, index):
+ msg = r"drop_duplicates\(\) got an unexpected keyword argument"
+ with pytest.raises(TypeError, match=msg):
+ index.drop_duplicates(inplace=True)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_has_duplicates(self, index_flat):
+ # MultiIndex tested separately in:
+ # tests/indexes/multi/test_unique_and_duplicates.
+ index = index_flat
+ holder = type(index)
+ if not len(index) or isinstance(index, RangeIndex):
+ # MultiIndex tested separately in:
+ # tests/indexes/multi/test_unique_and_duplicates.
+ # RangeIndex is unique by definition.
+ pytest.skip("Skip check for empty Index, MultiIndex, and RangeIndex")
+
+ idx = holder([index[0]] * 5)
+ assert idx.is_unique is False
+ assert idx.has_duplicates is True
+
+ @pytest.mark.parametrize(
+ "dtype",
+ ["int64", "uint64", "float64", "category", "datetime64[ns]", "timedelta64[ns]"],
+ )
+ def test_astype_preserves_name(self, index, dtype):
+ # https://github.com/pandas-dev/pandas/issues/32013
+ if isinstance(index, MultiIndex):
+ index.names = ["idx" + str(i) for i in range(index.nlevels)]
+ else:
+ index.name = "idx"
+
+ warn = (
+ np.exceptions.ComplexWarning
+ if index.dtype.kind == "c" and dtype in ["float64", "int64", "uint64"]
+ else None
+ )
+
+ is_pyarrow_str = str(index.dtype) == "string[pyarrow]" and dtype == "category"
+ try:
+ # Some of these conversions cannot succeed so we use a try / except
+ with tm.assert_produces_warning(
+ warn,
+ raise_on_extra_warnings=is_pyarrow_str,
+ check_stacklevel=False,
+ ):
+ result = index.astype(dtype)
+ except (ValueError, TypeError, NotImplementedError, SystemError):
+ return
+
+ if isinstance(index, MultiIndex):
+ assert result.names == index.names
+ else:
+ assert result.name == index.name
+
+ def test_hasnans_isnans(self, index_flat):
+ # GH#11343, added tests for hasnans / isnans
+ index = index_flat
+
+ # cases in indices doesn't include NaN
+ idx = index.copy(deep=True)
+ expected = np.array([False] * len(idx), dtype=bool)
+ tm.assert_numpy_array_equal(idx._isnan, expected)
+ assert idx.hasnans is False
+
+ idx = index.copy(deep=True)
+ values = idx._values
+
+ if len(index) == 0:
+ return
+ elif is_integer_dtype(index.dtype):
+ return
+ elif index.dtype == bool:
+ # values[1] = np.nan below casts to True!
+ return
+
+ values[1] = np.nan
+
+ idx = type(index)(values)
+
+ expected = np.array([False] * len(idx), dtype=bool)
+ expected[1] = True
+ tm.assert_numpy_array_equal(idx._isnan, expected)
+ assert idx.hasnans is True
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+@pytest.mark.parametrize("na_position", [None, "middle"])
+def test_sort_values_invalid_na_position(index_with_missing, na_position):
+ with pytest.raises(ValueError, match=f"invalid na_position: {na_position}"):
+ index_with_missing.sort_values(na_position=na_position)
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+@pytest.mark.parametrize("na_position", ["first", "last"])
+def test_sort_values_with_missing(index_with_missing, na_position, request):
+ # GH 35584. Test that sort_values works with missing values,
+ # sort non-missing and place missing according to na_position
+
+ if isinstance(index_with_missing, CategoricalIndex):
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="missing value sorting order not well-defined", strict=False
+ )
+ )
+
+ missing_count = np.sum(index_with_missing.isna())
+ not_na_vals = index_with_missing[index_with_missing.notna()].values
+ sorted_values = np.sort(not_na_vals)
+ if na_position == "first":
+ sorted_values = np.concatenate([[None] * missing_count, sorted_values])
+ else:
+ sorted_values = np.concatenate([sorted_values, [None] * missing_count])
+
+ # Explicitly pass dtype needed for Index backed by EA e.g. IntegerArray
+ expected = type(index_with_missing)(sorted_values, dtype=index_with_missing.dtype)
+
+ result = index_with_missing.sort_values(na_position=na_position)
+ tm.assert_index_equal(result, expected)
+
+
+def test_sort_values_natsort_key():
+ # GH#56081
+ def split_convert(s):
+ return tuple(int(x) for x in s.split("."))
+
+ idx = pd.Index(["1.9", "2.0", "1.11", "1.10"])
+ expected = pd.Index(["1.9", "1.10", "1.11", "2.0"])
+ result = idx.sort_values(key=lambda x: tuple(map(split_convert, x)))
+ tm.assert_index_equal(result, expected)
+
+
+def test_ndarray_compat_properties(index):
+ if isinstance(index, PeriodIndex) and not IS64:
+ pytest.skip("Overflow")
+ idx = index
+ assert idx.T.equals(idx)
+ assert idx.transpose().equals(idx)
+
+ values = idx.values
+
+ assert idx.shape == values.shape
+ assert idx.ndim == values.ndim
+ assert idx.size == values.size
+
+ if not isinstance(index, (RangeIndex, MultiIndex)):
+ # These two are not backed by an ndarray
+ assert idx.nbytes == values.nbytes
+
+ # test for validity
+ idx.nbytes
+ idx.values.nbytes
+
+
+def test_compare_read_only_array():
+ # GH#57130
+ arr = np.array([], dtype=object)
+ arr.flags.writeable = False
+ idx = pd.Index(arr)
+ result = idx > 69
+ assert result.dtype == bool
+
+
+def test_to_frame_column_rangeindex():
+ idx = pd.Index([1])
+ result = idx.to_frame().columns
+ expected = RangeIndex(1)
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_to_frame_name_tuple_multiindex():
+ idx = pd.Index([1])
+ result = idx.to_frame(name=(1, 2))
+ expected = pd.DataFrame([1], columns=MultiIndex.from_arrays([[1], [2]]), index=idx)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_join_series_deprecated():
+ # GH#62897
+ idx = pd.Index([1, 2])
+ ser = pd.Series([1, 2, 2])
+ with tm.assert_produces_warning(
+ Pandas4Warning, match="Passing .* to .* is deprecated"
+ ):
+ idx.join(ser)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_datetimelike.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_datetimelike.py
new file mode 100644
index 0000000000000000000000000000000000000000..280d959579f7c47fe4e2aa092f551c66be18678f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_datetimelike.py
@@ -0,0 +1,170 @@
+"""generic datetimelike tests"""
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class TestDatetimeLike:
+ @pytest.fixture(
+ params=[
+ pd.period_range("20130101", periods=5, freq="D"),
+ pd.TimedeltaIndex(
+ [
+ "0 days 01:00:00",
+ "1 days 01:00:00",
+ "2 days 01:00:00",
+ "3 days 01:00:00",
+ "4 days 01:00:00",
+ ],
+ dtype="timedelta64[ns]",
+ freq="D",
+ ),
+ pd.DatetimeIndex(
+ ["2013-01-01", "2013-01-02", "2013-01-03", "2013-01-04", "2013-01-05"],
+ dtype="datetime64[ns]",
+ freq="D",
+ ),
+ ]
+ )
+ def simple_index(self, request):
+ return request.param
+
+ def test_isin(self, simple_index):
+ index = simple_index[:4]
+ result = index.isin(index)
+ assert result.all()
+
+ result = index.isin(list(index))
+ assert result.all()
+
+ result = index.isin([index[2], 5])
+ expected = np.array([False, False, True, False])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_argsort_matches_array(self, simple_index):
+ idx = simple_index
+ idx = idx.insert(1, pd.NaT)
+
+ result = idx.argsort()
+ expected = idx._data.argsort()
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_can_hold_identifiers(self, simple_index):
+ idx = simple_index
+ key = idx[0]
+ assert idx._can_hold_identifiers_and_holds_name(key) is False
+
+ def test_shift_identity(self, simple_index):
+ idx = simple_index
+ tm.assert_index_equal(idx, idx.shift(0))
+
+ def test_shift_empty(self, simple_index):
+ # GH#14811
+ idx = simple_index[:0]
+ tm.assert_index_equal(idx, idx.shift(1))
+
+ def test_str(self, simple_index):
+ # test the string repr
+ idx = simple_index.copy()
+ idx.name = "foo"
+ assert f"length={len(idx)}" not in str(idx)
+ assert "'foo'" in str(idx)
+ assert type(idx).__name__ in str(idx)
+
+ if hasattr(idx, "tz"):
+ if idx.tz is not None:
+ assert idx.tz in str(idx)
+ if isinstance(idx, pd.PeriodIndex):
+ assert f"dtype='period[{idx.freqstr}]'" in str(idx)
+ else:
+ assert f"freq='{idx.freqstr}'" in str(idx)
+
+ def test_view(self, simple_index):
+ idx = simple_index
+
+ result = type(simple_index)(idx)
+ tm.assert_index_equal(result, idx)
+
+ msg = (
+ "Cannot change data-type for array of references.|"
+ "Cannot change data-type for object array.|"
+ )
+ with pytest.raises(TypeError, match=msg):
+ idx.view(type(simple_index))
+
+ def test_map_callable(self, simple_index):
+ index = simple_index
+ expected = index + index.freq
+ result = index.map(lambda x: x + index.freq)
+ tm.assert_index_equal(result, expected)
+
+ # map to NaT
+ result = index.map(lambda x: pd.NaT if x == index[0] else x)
+ expected = pd.Index([pd.NaT, *index[1:].tolist()])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "mapper",
+ [
+ lambda values, index: {i: e for e, i in zip(values, index)},
+ lambda values, index: pd.Series(values, index, dtype=object),
+ ],
+ )
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_map_dictlike(self, mapper, simple_index):
+ index = simple_index
+ expected = index + index.freq
+
+ # don't compare the freqs
+ if isinstance(expected, (pd.DatetimeIndex, pd.TimedeltaIndex)):
+ expected = expected._with_freq(None)
+
+ result = index.map(mapper(expected, index))
+ tm.assert_index_equal(result, expected)
+
+ expected = pd.Index([pd.NaT, *index[1:].tolist()])
+ result = index.map(mapper(expected, index))
+ tm.assert_index_equal(result, expected)
+
+ # empty map; these map to np.nan because we cannot know
+ # to re-infer things
+ expected = pd.Index([np.nan] * len(index))
+ result = index.map(mapper([], []))
+ tm.assert_index_equal(result, expected)
+
+ def test_getitem_preserves_freq(self, simple_index):
+ index = simple_index
+ assert index.freq is not None
+
+ result = index[:]
+ assert result.freq == index.freq
+
+ def test_where_cast_str(self, simple_index):
+ index = simple_index
+
+ mask = np.ones(len(index), dtype=bool)
+ mask[-1] = False
+
+ result = index.where(mask, str(index[0]))
+ expected = index.where(mask, index[0])
+ tm.assert_index_equal(result, expected)
+
+ result = index.where(mask, [str(index[0])])
+ tm.assert_index_equal(result, expected)
+
+ expected = index.astype(object).where(mask, "foo")
+ result = index.where(mask, "foo")
+ tm.assert_index_equal(result, expected)
+
+ result = index.where(mask, ["foo"])
+ tm.assert_index_equal(result, expected)
+
+ def test_diff(self, unit):
+ # GH 55080
+ dti = pd.to_datetime([10, 20, 30], unit=unit).as_unit(unit)
+ result = dti.diff(1)
+ expected = pd.to_timedelta([pd.NaT, 10, 10], unit=unit).as_unit(unit)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_engines.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_engines.py
new file mode 100644
index 0000000000000000000000000000000000000000..468c2240c8192098a6ff75a5a2d0210c8108a176
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_engines.py
@@ -0,0 +1,192 @@
+import re
+
+import numpy as np
+import pytest
+
+from pandas._libs import index as libindex
+
+import pandas as pd
+
+
+@pytest.fixture(
+ params=[
+ (libindex.Int64Engine, np.int64),
+ (libindex.Int32Engine, np.int32),
+ (libindex.Int16Engine, np.int16),
+ (libindex.Int8Engine, np.int8),
+ (libindex.UInt64Engine, np.uint64),
+ (libindex.UInt32Engine, np.uint32),
+ (libindex.UInt16Engine, np.uint16),
+ (libindex.UInt8Engine, np.uint8),
+ (libindex.Float64Engine, np.float64),
+ (libindex.Float32Engine, np.float32),
+ ],
+ ids=lambda x: x[0].__name__,
+)
+def numeric_indexing_engine_type_and_dtype(request):
+ return request.param
+
+
+class TestDatetimeEngine:
+ @pytest.mark.parametrize(
+ "scalar",
+ [
+ pd.Timedelta(pd.Timestamp("2016-01-01").asm8.view("m8[ns]")),
+ pd.Timestamp("2016-01-01")._value,
+ pd.Timestamp("2016-01-01").to_pydatetime(),
+ pd.Timestamp("2016-01-01").to_datetime64(),
+ ],
+ )
+ def test_not_contains_requires_timestamp(self, scalar):
+ dti1 = pd.date_range("2016-01-01", periods=3)
+ dti2 = dti1.insert(1, pd.NaT) # non-monotonic
+ dti3 = dti1.insert(3, dti1[0]) # non-unique
+ dti4 = pd.date_range("2016-01-01", freq="ns", periods=2_000_000)
+ dti5 = dti4.insert(0, dti4[0]) # over size threshold, not unique
+
+ msg = "|".join([re.escape(str(scalar)), re.escape(repr(scalar))])
+ for dti in [dti1, dti2, dti3, dti4, dti5]:
+ with pytest.raises(TypeError, match=msg):
+ scalar in dti._engine
+
+ with pytest.raises(KeyError, match=msg):
+ dti._engine.get_loc(scalar)
+
+
+class TestTimedeltaEngine:
+ @pytest.mark.parametrize(
+ "scalar",
+ [
+ pd.Timestamp(pd.Timedelta(days=42).asm8.view("datetime64[ns]")),
+ pd.Timedelta(days=42)._value,
+ pd.Timedelta(days=42).to_pytimedelta(),
+ pd.Timedelta(days=42).to_timedelta64(),
+ ],
+ )
+ def test_not_contains_requires_timedelta(self, scalar):
+ tdi1 = pd.timedelta_range("42 days", freq="9h", periods=1234)
+ tdi2 = tdi1.insert(1, pd.NaT) # non-monotonic
+ tdi3 = tdi1.insert(3, tdi1[0]) # non-unique
+ tdi4 = pd.timedelta_range("42 days", freq="ns", periods=2_000_000)
+ tdi5 = tdi4.insert(0, tdi4[0]) # over size threshold, not unique
+
+ msg = "|".join([re.escape(str(scalar)), re.escape(repr(scalar))])
+ for tdi in [tdi1, tdi2, tdi3, tdi4, tdi5]:
+ with pytest.raises(TypeError, match=msg):
+ scalar in tdi._engine
+
+ with pytest.raises(KeyError, match=msg):
+ tdi._engine.get_loc(scalar)
+
+
+class TestNumericEngine:
+ def test_is_monotonic(self, numeric_indexing_engine_type_and_dtype):
+ engine_type, dtype = numeric_indexing_engine_type_and_dtype
+ num = 1000
+ arr = np.array([1] * num + [2] * num + [3] * num, dtype=dtype)
+
+ # monotonic increasing
+ engine = engine_type(arr)
+ assert engine.is_monotonic_increasing is True
+ assert engine.is_monotonic_decreasing is False
+
+ # monotonic decreasing
+ engine = engine_type(arr[::-1])
+ assert engine.is_monotonic_increasing is False
+ assert engine.is_monotonic_decreasing is True
+
+ # neither monotonic increasing or decreasing
+ arr = np.array([1] * num + [2] * num + [1] * num, dtype=dtype)
+ engine = engine_type(arr[::-1])
+ assert engine.is_monotonic_increasing is False
+ assert engine.is_monotonic_decreasing is False
+
+ def test_is_unique(self, numeric_indexing_engine_type_and_dtype):
+ engine_type, dtype = numeric_indexing_engine_type_and_dtype
+
+ # unique
+ arr = np.array([1, 3, 2], dtype=dtype)
+ engine = engine_type(arr)
+ assert engine.is_unique is True
+
+ # not unique
+ arr = np.array([1, 2, 1], dtype=dtype)
+ engine = engine_type(arr)
+ assert engine.is_unique is False
+
+ def test_get_loc(self, numeric_indexing_engine_type_and_dtype):
+ engine_type, dtype = numeric_indexing_engine_type_and_dtype
+
+ # unique
+ arr = np.array([1, 2, 3], dtype=dtype)
+ engine = engine_type(arr)
+ assert engine.get_loc(2) == 1
+
+ # monotonic
+ num = 1000
+ arr = np.array([1] * num + [2] * num + [3] * num, dtype=dtype)
+ engine = engine_type(arr)
+ assert engine.get_loc(2) == slice(1000, 2000)
+
+ # not monotonic
+ arr = np.array([1, 2, 3] * num, dtype=dtype)
+ engine = engine_type(arr)
+ expected = np.array([False, True, False] * num, dtype=bool)
+ result = engine.get_loc(2)
+ assert (result == expected).all()
+
+
+class TestObjectEngine:
+ engine_type = libindex.ObjectEngine
+ dtype = np.object_
+ values = list("abc")
+
+ def test_is_monotonic(self):
+ num = 1000
+ arr = np.array(["a"] * num + ["a"] * num + ["c"] * num, dtype=self.dtype)
+
+ # monotonic increasing
+ engine = self.engine_type(arr)
+ assert engine.is_monotonic_increasing is True
+ assert engine.is_monotonic_decreasing is False
+
+ # monotonic decreasing
+ engine = self.engine_type(arr[::-1])
+ assert engine.is_monotonic_increasing is False
+ assert engine.is_monotonic_decreasing is True
+
+ # neither monotonic increasing or decreasing
+ arr = np.array(["a"] * num + ["b"] * num + ["a"] * num, dtype=self.dtype)
+ engine = self.engine_type(arr[::-1])
+ assert engine.is_monotonic_increasing is False
+ assert engine.is_monotonic_decreasing is False
+
+ def test_is_unique(self):
+ # unique
+ arr = np.array(self.values, dtype=self.dtype)
+ engine = self.engine_type(arr)
+ assert engine.is_unique is True
+
+ # not unique
+ arr = np.array(["a", "b", "a"], dtype=self.dtype)
+ engine = self.engine_type(arr)
+ assert engine.is_unique is False
+
+ def test_get_loc(self):
+ # unique
+ arr = np.array(self.values, dtype=self.dtype)
+ engine = self.engine_type(arr)
+ assert engine.get_loc("b") == 1
+
+ # monotonic
+ num = 1000
+ arr = np.array(["a"] * num + ["b"] * num + ["c"] * num, dtype=self.dtype)
+ engine = self.engine_type(arr)
+ assert engine.get_loc("b") == slice(1000, 2000)
+
+ # not monotonic
+ arr = np.array(self.values * num, dtype=self.dtype)
+ engine = self.engine_type(arr)
+ expected = np.array([False, True, False] * num, dtype=bool)
+ result = engine.get_loc("b")
+ assert (result == expected).all()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_frozen.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_frozen.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5e3744fe9aef0024fb8d615fdd87db8d2f63e60
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_frozen.py
@@ -0,0 +1,113 @@
+import re
+
+import pytest
+
+from pandas.core.indexes.frozen import FrozenList
+
+
+@pytest.fixture
+def lst():
+ return [1, 2, 3, 4, 5]
+
+
+@pytest.fixture
+def container(lst):
+ return FrozenList(lst)
+
+
+@pytest.fixture
+def unicode_container():
+ return FrozenList(["\u05d0", "\u05d1", "c"])
+
+
+class TestFrozenList:
+ def check_mutable_error(self, *args, **kwargs):
+ # Pass whatever function you normally would to pytest.raises
+ # (after the Exception kind).
+ mutable_regex = re.compile("does not support mutable operations")
+ msg = "'(_s)?re.(SRE_)?Pattern' object is not callable"
+ with pytest.raises(TypeError, match=msg):
+ mutable_regex(*args, **kwargs)
+
+ def test_no_mutable_funcs(self, container):
+ def setitem():
+ container[0] = 5
+
+ self.check_mutable_error(setitem)
+
+ def setslice():
+ container[1:2] = 3
+
+ self.check_mutable_error(setslice)
+
+ def delitem():
+ del container[0]
+
+ self.check_mutable_error(delitem)
+
+ def delslice():
+ del container[0:3]
+
+ self.check_mutable_error(delslice)
+
+ mutable_methods = ("extend", "pop", "remove", "insert")
+
+ for meth in mutable_methods:
+ self.check_mutable_error(getattr(container, meth))
+
+ def test_slicing_maintains_type(self, container, lst):
+ result = container[1:2]
+ expected = lst[1:2]
+ self.check_result(result, expected)
+
+ def check_result(self, result, expected):
+ assert isinstance(result, FrozenList)
+ assert result == expected
+
+ def test_string_methods_dont_fail(self, container):
+ repr(container)
+ str(container)
+ bytes(container)
+
+ def test_tricky_container(self, unicode_container):
+ repr(unicode_container)
+ str(unicode_container)
+
+ def test_add(self, container, lst):
+ result = container + (1, 2, 3) # noqa: RUF005
+ expected = FrozenList(lst + [1, 2, 3]) # noqa: RUF005
+ self.check_result(result, expected)
+
+ result = (1, 2, 3) + container # noqa: RUF005
+ expected = FrozenList([1, 2, 3] + lst) # noqa: RUF005
+ self.check_result(result, expected)
+
+ def test_iadd(self, container, lst):
+ q = r = container
+
+ q += [5]
+ self.check_result(q, lst + [5]) # noqa: RUF005
+
+ # Other shouldn't be mutated.
+ self.check_result(r, lst)
+
+ def test_union(self, container, lst):
+ result = container.union((1, 2, 3))
+ expected = FrozenList(lst + [1, 2, 3]) # noqa: RUF005
+ self.check_result(result, expected)
+
+ def test_difference(self, container):
+ result = container.difference([2])
+ expected = FrozenList([1, 3, 4, 5])
+ self.check_result(result, expected)
+
+ def test_difference_dupe(self):
+ result = FrozenList([1, 2, 3, 2]).difference([2])
+ expected = FrozenList([1, 3])
+ self.check_result(result, expected)
+
+ def test_tricky_container_to_bytes_raises(self, unicode_container):
+ # GH 26447
+ msg = "^'str' object cannot be interpreted as an integer$"
+ with pytest.raises(TypeError, match=msg):
+ bytes(unicode_container)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_index_new.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_index_new.py
new file mode 100644
index 0000000000000000000000000000000000000000..3687fe921924f84dac0be903ddc3f1967d0ece20
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_index_new.py
@@ -0,0 +1,428 @@
+"""
+Tests for the Index constructor conducting inference.
+"""
+
+from datetime import (
+ datetime,
+ timedelta,
+ timezone,
+)
+from decimal import Decimal
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs.timezones import maybe_get_tz
+
+from pandas import (
+ NA,
+ Categorical,
+ CategoricalIndex,
+ DatetimeIndex,
+ Index,
+ IntervalIndex,
+ MultiIndex,
+ NaT,
+ PeriodIndex,
+ Series,
+ TimedeltaIndex,
+ Timestamp,
+ array,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestIndexConstructorInference:
+ def test_object_all_bools(self):
+ # GH#49594 match Series behavior on ndarray[object] of all bools
+ arr = np.array([True, False], dtype=object)
+ res = Index(arr)
+ assert res.dtype == object
+
+ # since the point is matching Series behavior, let's double check
+ assert Series(arr).dtype == object
+
+ def test_object_all_complex(self):
+ # GH#49594 match Series behavior on ndarray[object] of all complex
+ arr = np.array([complex(1), complex(2)], dtype=object)
+ res = Index(arr)
+ assert res.dtype == object
+
+ # since the point is matching Series behavior, let's double check
+ assert Series(arr).dtype == object
+
+ @pytest.mark.parametrize("val", [NaT, None, np.nan, float("nan")])
+ def test_infer_nat(self, val):
+ # GH#49340 all NaT/None/nan and at least 1 NaT -> datetime64[ns],
+ # matching Series behavior
+ values = [NaT, val]
+
+ idx = Index(values)
+ assert idx.dtype == "datetime64[s]" and idx.isna().all()
+
+ idx = Index(values[::-1])
+ assert idx.dtype == "datetime64[s]" and idx.isna().all()
+
+ idx = Index(np.array(values, dtype=object))
+ assert idx.dtype == "datetime64[s]" and idx.isna().all()
+
+ idx = Index(np.array(values, dtype=object)[::-1])
+ assert idx.dtype == "datetime64[s]" and idx.isna().all()
+
+ @pytest.mark.parametrize("na_value", [None, np.nan])
+ @pytest.mark.parametrize("vtype", [list, tuple, iter])
+ def test_construction_list_tuples_nan(self, na_value, vtype):
+ # GH#18505 : valid tuples containing NaN
+ values = [(1, "two"), (3.0, na_value)]
+ result = Index(vtype(values))
+ expected = MultiIndex.from_tuples(values)
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_int_dtype_float(self, any_int_numpy_dtype):
+ # GH#18400
+ expected = Index([0, 1, 2, 3], dtype=any_int_numpy_dtype)
+ result = Index([0.0, 1.0, 2.0, 3.0], dtype=any_int_numpy_dtype)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("cast_index", [True, False])
+ @pytest.mark.parametrize(
+ "vals", [[True, False, True], np.array([True, False, True], dtype=bool)]
+ )
+ def test_constructor_dtypes_to_object(self, cast_index, vals):
+ if cast_index:
+ index = Index(vals, dtype=bool)
+ else:
+ index = Index(vals)
+
+ assert type(index) is Index
+ assert index.dtype == bool
+
+ def test_constructor_categorical_to_object(self):
+ # GH#32167 Categorical data and dtype=object should return object-dtype
+ ci = CategoricalIndex(range(5))
+ result = Index(ci, dtype=object)
+ assert not isinstance(result, CategoricalIndex)
+
+ def test_constructor_infer_periodindex(self):
+ xp = period_range("2012-1-1", freq="M", periods=3)
+ rs = Index(xp)
+ tm.assert_index_equal(rs, xp)
+ assert isinstance(rs, PeriodIndex)
+
+ def test_from_list_of_periods(self):
+ rng = period_range("1/1/2000", periods=20, freq="D")
+ periods = list(rng)
+
+ result = Index(periods)
+ assert isinstance(result, PeriodIndex)
+
+ @pytest.mark.parametrize("pos", [0, 1])
+ @pytest.mark.parametrize(
+ "klass,dtype,ctor",
+ [
+ (DatetimeIndex, "datetime64[ns]", np.datetime64("nat")),
+ (TimedeltaIndex, "timedelta64[ns]", np.timedelta64("nat")),
+ ],
+ )
+ def test_constructor_infer_nat_dt_like(
+ self, pos, klass, dtype, ctor, nulls_fixture, request
+ ):
+ if isinstance(nulls_fixture, Decimal):
+ # We dont cast these to datetime64/timedelta64
+ pytest.skip(
+ f"We don't cast {type(nulls_fixture).__name__} to "
+ "datetime64/timedelta64"
+ )
+
+ expected = klass([NaT, NaT])
+ if dtype[0] == "d":
+ # we infer all-NaT as second resolution
+ expected = expected.astype("M8[ns]")
+ if dtype[0] == "t":
+ # we infer all-NaT as second resolution
+ expected = expected.astype("m8[ns]")
+ assert expected.dtype == dtype
+ data = [ctor]
+ data.insert(pos, nulls_fixture)
+
+ if nulls_fixture is NA:
+ expected = Index([NA, NaT])
+ mark = pytest.mark.xfail(reason="Broken with np.NaT ctor; see GH 31884")
+ request.applymarker(mark)
+
+ result = Index(data)
+
+ tm.assert_index_equal(result, expected)
+
+ result = Index(np.array(data, dtype=object))
+
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("swap_objs", [True, False])
+ def test_constructor_mixed_nat_objs_infers_object(self, swap_objs):
+ # mixed np.datetime64/timedelta64 nat results in object
+ data = [np.datetime64("nat"), np.timedelta64("nat")]
+ if swap_objs:
+ data = data[::-1]
+
+ expected = Index(data, dtype=object)
+ tm.assert_index_equal(Index(data), expected)
+ tm.assert_index_equal(Index(np.array(data, dtype=object)), expected)
+
+ @pytest.mark.parametrize("swap_objs", [True, False])
+ def test_constructor_datetime_and_datetime64(self, swap_objs):
+ data = [Timestamp(2021, 6, 8, 9, 42), np.datetime64("now")]
+ if swap_objs:
+ data = data[::-1]
+ expected = DatetimeIndex(data)
+
+ tm.assert_index_equal(Index(data), expected)
+ tm.assert_index_equal(Index(np.array(data, dtype=object)), expected)
+
+ def test_constructor_datetimes_mixed_tzs(self):
+ # https://github.com/pandas-dev/pandas/pull/55793/files#r1383719998
+ tz = maybe_get_tz("US/Central")
+ dt1 = datetime(2020, 1, 1, tzinfo=tz)
+ dt2 = datetime(2020, 1, 1, tzinfo=timezone.utc)
+ result = Index([dt1, dt2])
+ expected = Index([dt1, dt2], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+
+class TestDtypeEnforced:
+ # check we don't silently ignore the dtype keyword
+
+ def test_constructor_object_dtype_with_ea_data(self, any_numeric_ea_dtype):
+ # GH#45206
+ arr = array([0], dtype=any_numeric_ea_dtype)
+
+ idx = Index(arr, dtype=object)
+ assert idx.dtype == object
+
+ @pytest.mark.parametrize("dtype", [object, "float64", "uint64", "category"])
+ def test_constructor_range_values_mismatched_dtype(self, dtype):
+ rng = Index(range(5))
+
+ result = Index(rng, dtype=dtype)
+ assert result.dtype == dtype
+
+ result = Index(range(5), dtype=dtype)
+ assert result.dtype == dtype
+
+ @pytest.mark.parametrize("dtype", [object, "float64", "uint64", "category"])
+ def test_constructor_categorical_values_mismatched_non_ea_dtype(self, dtype):
+ cat = Categorical([1, 2, 3])
+
+ result = Index(cat, dtype=dtype)
+ assert result.dtype == dtype
+
+ def test_constructor_categorical_values_mismatched_dtype(self):
+ dti = date_range("2016-01-01", periods=3)
+ cat = Categorical(dti)
+ result = Index(cat, dti.dtype)
+ tm.assert_index_equal(result, dti)
+
+ dti2 = dti.tz_localize("Asia/Tokyo")
+ cat2 = Categorical(dti2)
+ result = Index(cat2, dti2.dtype)
+ tm.assert_index_equal(result, dti2)
+
+ ii = IntervalIndex.from_breaks(range(5))
+ cat3 = Categorical(ii)
+ result = Index(cat3, dtype=ii.dtype)
+ tm.assert_index_equal(result, ii)
+
+ def test_constructor_ea_values_mismatched_categorical_dtype(self):
+ dti = date_range("2016-01-01", periods=3)
+ result = Index(dti, dtype="category")
+ expected = CategoricalIndex(dti)
+ tm.assert_index_equal(result, expected)
+
+ dti2 = date_range("2016-01-01", periods=3, tz="US/Pacific")
+ result = Index(dti2, dtype="category")
+ expected = CategoricalIndex(dti2)
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_period_values_mismatched_dtype(self):
+ pi = period_range("2016-01-01", periods=3, freq="D")
+ result = Index(pi, dtype="category")
+ expected = CategoricalIndex(pi)
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_timedelta64_values_mismatched_dtype(self):
+ # check we don't silently ignore the dtype keyword
+ tdi = timedelta_range("4 Days", periods=5)
+ result = Index(tdi, dtype="category")
+ expected = CategoricalIndex(tdi)
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_interval_values_mismatched_dtype(self):
+ dti = date_range("2016-01-01", periods=3)
+ ii = IntervalIndex.from_breaks(dti)
+ result = Index(ii, dtype="category")
+ expected = CategoricalIndex(ii)
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_datetime64_values_mismatched_period_dtype(self):
+ dti = date_range("2016-01-01", periods=3)
+ result = Index(dti, dtype="Period[D]")
+ expected = dti.to_period("D")
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["int64", "uint64"])
+ def test_constructor_int_dtype_nan_raises(self, dtype):
+ # see GH#15187
+ data = [np.nan]
+ msg = "cannot convert"
+ with pytest.raises(ValueError, match=msg):
+ Index(data, dtype=dtype)
+
+ @pytest.mark.parametrize(
+ "vals",
+ [
+ [1, 2, 3],
+ np.array([1, 2, 3]),
+ np.array([1, 2, 3], dtype=int),
+ # below should coerce
+ [1.0, 2.0, 3.0],
+ np.array([1.0, 2.0, 3.0], dtype=float),
+ ],
+ )
+ def test_constructor_dtypes_to_int(self, vals, any_int_numpy_dtype):
+ dtype = any_int_numpy_dtype
+ index = Index(vals, dtype=dtype)
+ assert index.dtype == dtype
+
+ @pytest.mark.parametrize(
+ "vals",
+ [
+ [1, 2, 3],
+ [1.0, 2.0, 3.0],
+ np.array([1.0, 2.0, 3.0]),
+ np.array([1, 2, 3], dtype=int),
+ np.array([1.0, 2.0, 3.0], dtype=float),
+ ],
+ )
+ def test_constructor_dtypes_to_float(self, vals, float_numpy_dtype):
+ dtype = float_numpy_dtype
+ index = Index(vals, dtype=dtype)
+ assert index.dtype == dtype
+
+ @pytest.mark.parametrize(
+ "vals",
+ [
+ [1, 2, 3],
+ np.array([1, 2, 3], dtype=int),
+ np.array(["2011-01-01", "2011-01-02"], dtype="datetime64[ns]"),
+ [datetime(2011, 1, 1), datetime(2011, 1, 2)],
+ ],
+ )
+ def test_constructor_dtypes_to_categorical(self, vals):
+ index = Index(vals, dtype="category")
+ assert isinstance(index, CategoricalIndex)
+
+ @pytest.mark.parametrize("cast_index", [True, False])
+ @pytest.mark.parametrize(
+ "vals",
+ [
+ np.array([np.datetime64("2011-01-01"), np.datetime64("2011-01-02")]),
+ [datetime(2011, 1, 1), datetime(2011, 1, 2)],
+ ],
+ )
+ def test_constructor_dtypes_to_datetime(self, cast_index, vals):
+ vals = Index(vals)
+ if cast_index:
+ index = Index(vals, dtype=object)
+ assert isinstance(index, Index)
+ assert index.dtype == object
+ else:
+ index = Index(vals)
+ assert isinstance(index, DatetimeIndex)
+
+ @pytest.mark.parametrize("cast_index", [True, False])
+ @pytest.mark.parametrize(
+ "vals",
+ [
+ np.array([np.timedelta64(1, "D"), np.timedelta64(1, "D")]),
+ [timedelta(1), timedelta(1)],
+ ],
+ )
+ def test_constructor_dtypes_to_timedelta(self, cast_index, vals):
+ if cast_index:
+ index = Index(vals, dtype=object)
+ assert isinstance(index, Index)
+ assert index.dtype == object
+ else:
+ index = Index(vals)
+ assert isinstance(index, TimedeltaIndex)
+
+ def test_pass_timedeltaindex_to_index(self):
+ rng = timedelta_range("1 days", "10 days")
+ idx = Index(rng, dtype=object)
+
+ expected = Index(rng.to_pytimedelta(), dtype=object)
+
+ tm.assert_numpy_array_equal(idx.values, expected.values)
+
+ def test_pass_datetimeindex_to_index(self):
+ # GH#1396
+ rng = date_range("1/1/2000", "3/1/2000")
+ idx = Index(rng, dtype=object)
+
+ expected = Index(rng.to_pydatetime(), dtype=object)
+
+ tm.assert_numpy_array_equal(idx.values, expected.values)
+
+
+class TestIndexConstructorUnwrapping:
+ # Test passing different arraylike values to pd.Index
+
+ @pytest.mark.parametrize("klass", [Index, DatetimeIndex])
+ def test_constructor_from_series_dt64(self, klass):
+ stamps = [Timestamp("20110101"), Timestamp("20120101"), Timestamp("20130101")]
+ expected = DatetimeIndex(stamps)
+ ser = Series(stamps)
+ result = klass(ser)
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_no_pandas_array(self):
+ ser = Series([1, 2, 3])
+ result = Index(ser.array)
+ expected = Index([1, 2, 3])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "array",
+ [
+ np.arange(5),
+ np.array(["a", "b", "c"]),
+ date_range("2000-01-01", periods=3).values,
+ ],
+ )
+ def test_constructor_ndarray_like(self, array):
+ # GH#5460#issuecomment-44474502
+ # it should be possible to convert any object that satisfies the numpy
+ # ndarray interface directly into an Index
+ class ArrayLike:
+ def __init__(self, array) -> None:
+ self.array = array
+
+ def __array__(self, dtype=None, copy=None) -> np.ndarray:
+ return self.array
+
+ expected = Index(array)
+ result = Index(ArrayLike(array))
+ tm.assert_index_equal(result, expected)
+
+
+class TestIndexConstructionErrors:
+ def test_constructor_overflow_int64(self):
+ # see GH#15832
+ msg = (
+ "The elements provided in the data cannot all be casted to the dtype int64"
+ )
+ with pytest.raises(OverflowError, match=msg):
+ Index([np.iinfo(np.uint64).max - 1], dtype="int64")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1c9f0fde863dc92eba3cf36b4474849a42965cf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_indexing.py
@@ -0,0 +1,362 @@
+"""
+test_indexing tests the following Index methods:
+ __getitem__
+ get_loc
+ get_value
+ __contains__
+ take
+ where
+ get_indexer
+ get_indexer_for
+ slice_locs
+ asof_locs
+
+The corresponding tests.indexes.[index_type].test_indexing files
+contain tests for the corresponding methods specific to those Index subclasses.
+"""
+
+import numpy as np
+import pytest
+
+from pandas.compat import PY314
+from pandas.errors import InvalidIndexError
+
+from pandas.core.dtypes.common import (
+ is_float_dtype,
+ is_scalar,
+)
+
+from pandas import (
+ NA,
+ DatetimeIndex,
+ Index,
+ IntervalIndex,
+ MultiIndex,
+ NaT,
+ PeriodIndex,
+ TimedeltaIndex,
+)
+import pandas._testing as tm
+
+
+class TestTake:
+ def test_take_invalid_kwargs(self, index):
+ indices = [1, 2]
+
+ msg = r"take\(\) got an unexpected keyword argument 'foo'"
+ with pytest.raises(TypeError, match=msg):
+ index.take(indices, foo=2)
+
+ msg = "the 'out' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ index.take(indices, out=indices)
+
+ msg = "the 'mode' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ index.take(indices, mode="clip")
+
+ def test_take(self, index):
+ indexer = [4, 3, 0, 2]
+ if len(index) < 5:
+ pytest.skip("Test doesn't make sense since not enough elements")
+
+ result = index.take(indexer)
+ expected = index[indexer]
+ assert result.equals(expected)
+
+ if not isinstance(index, (DatetimeIndex, PeriodIndex, TimedeltaIndex)):
+ # GH 10791
+ msg = r"'(.*Index)' object has no attribute 'freq'"
+ with pytest.raises(AttributeError, match=msg):
+ index.freq
+
+ def test_take_indexer_type(self):
+ # GH#42875
+ integer_index = Index([0, 1, 2, 3])
+ scalar_index = 1
+ msg = "Expected indices to be array-like"
+ with pytest.raises(TypeError, match=msg):
+ integer_index.take(scalar_index)
+
+ def test_take_minus1_without_fill(self, index):
+ # -1 does not get treated as NA unless allow_fill=True is passed
+ if len(index) == 0:
+ # Test is not applicable
+ pytest.skip("Test doesn't make sense for empty index")
+
+ result = index.take([0, 0, -1])
+
+ expected = index.take([0, 0, len(index) - 1])
+ tm.assert_index_equal(result, expected)
+
+
+class TestContains:
+ @pytest.mark.parametrize(
+ "index,val",
+ [
+ ([0, 1, 2], 2),
+ ([0, 1, "2"], "2"),
+ ([0, 1, 2, np.inf, 4], 4),
+ ([0, 1, 2, np.nan, 4], 4),
+ ([0, 1, 2, np.inf], np.inf),
+ ([0, 1, 2, np.nan], np.nan),
+ ],
+ )
+ def test_index_contains(self, index, val):
+ index = Index(index)
+ assert val in index
+
+ @pytest.mark.parametrize(
+ "index,val",
+ [
+ (Index([0, 1, 2]), "2"),
+ (Index([0, 1, "2"]), 2),
+ (Index([0, 1, 2, np.inf]), 4),
+ (Index([0, 1, 2, np.nan]), 4),
+ (Index([0, 1, 2, np.inf]), np.nan),
+ (Index([0, 1, 2, np.nan]), np.inf),
+ # Checking if np.inf in int64 Index should not cause an OverflowError
+ # Related to GH 16957
+ (Index([0, 1, 2], dtype=np.int64), np.inf),
+ (Index([0, 1, 2], dtype=np.int64), np.nan),
+ (Index([0, 1, 2], dtype=np.uint64), np.inf),
+ (Index([0, 1, 2], dtype=np.uint64), np.nan),
+ ],
+ )
+ def test_index_not_contains(self, index, val):
+ assert val not in index
+
+ @pytest.mark.parametrize("val", [0, "2"])
+ def test_mixed_index_contains(self, val):
+ # GH#19860
+ index = Index([0, 1, "2"])
+ assert val in index
+
+ @pytest.mark.parametrize("val", ["1", 2])
+ def test_mixed_index_not_contains(self, index, val):
+ # GH#19860
+ index = Index([0, 1, "2"])
+ assert val not in index
+
+ def test_contains_with_float_index(self, any_real_numpy_dtype):
+ # GH#22085
+ dtype = any_real_numpy_dtype
+ data = [0, 1, 2, 3] if not is_float_dtype(dtype) else [0.1, 1.1, 2.2, 3.3]
+ index = Index(data, dtype=dtype)
+
+ if not is_float_dtype(index.dtype):
+ assert 1.1 not in index
+ assert 1.0 in index
+ assert 1 in index
+ else:
+ assert 1.1 in index
+ assert 1.0 not in index
+ assert 1 not in index
+
+ def test_contains_requires_hashable_raises(self, index):
+ if isinstance(index, MultiIndex):
+ return # TODO: do we want this to raise?
+
+ msg = "unhashable type: 'list'"
+ with pytest.raises(TypeError, match=msg):
+ [] in index
+
+ if PY314:
+ container_or_iterable = "a container or iterable"
+ else:
+ container_or_iterable = "iterable"
+
+ msg = "|".join(
+ [
+ r"unhashable type: 'dict'",
+ r"must be real number, not dict",
+ r"an integer is required",
+ r"\{\}",
+ r"pandas\._libs\.interval\.IntervalTree' is not "
+ f"{container_or_iterable}",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ {} in index._engine
+
+
+class TestGetLoc:
+ def test_get_loc_non_hashable(self, index):
+ with pytest.raises(InvalidIndexError, match="[0, 1]"):
+ index.get_loc([0, 1])
+
+ def test_get_loc_non_scalar_hashable(self, index):
+ # GH52877
+ from enum import Enum
+
+ class E(Enum):
+ X1 = "x1"
+
+ assert not is_scalar(E.X1)
+
+ exc = KeyError
+ msg = ""
+ if isinstance(
+ index,
+ (
+ DatetimeIndex,
+ TimedeltaIndex,
+ PeriodIndex,
+ IntervalIndex,
+ ),
+ ):
+ # TODO: make these more consistent?
+ exc = InvalidIndexError
+ msg = "E.X1"
+ with pytest.raises(exc, match=msg):
+ index.get_loc(E.X1)
+
+ def test_get_loc_generator(self, index):
+ exc = KeyError
+ if isinstance(
+ index,
+ (
+ DatetimeIndex,
+ TimedeltaIndex,
+ PeriodIndex,
+ IntervalIndex,
+ MultiIndex,
+ ),
+ ):
+ # TODO: make these more consistent?
+ exc = InvalidIndexError
+ with pytest.raises(exc, match="generator object"):
+ # MultiIndex specifically checks for generator; others for scalar
+ index.get_loc(x for x in range(5))
+
+ def test_get_loc_masked_duplicated_na(self):
+ # GH#48411
+ idx = Index([1, 2, NA, NA], dtype="Int64")
+ result = idx.get_loc(NA)
+ expected = np.array([False, False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestGetIndexer:
+ def test_get_indexer_base(self, index):
+ if index._index_as_unique:
+ expected = np.arange(index.size, dtype=np.intp)
+ actual = index.get_indexer(index)
+ tm.assert_numpy_array_equal(expected, actual)
+ else:
+ msg = "Reindexing only valid with uniquely valued Index objects"
+ with pytest.raises(InvalidIndexError, match=msg):
+ index.get_indexer(index)
+
+ with pytest.raises(ValueError, match="Invalid fill method"):
+ index.get_indexer(index, method="invalid")
+
+ def test_get_indexer_consistency(self, index):
+ # See GH#16819
+
+ if index._index_as_unique:
+ indexer = index.get_indexer(index[0:2])
+ assert isinstance(indexer, np.ndarray)
+ assert indexer.dtype == np.intp
+ else:
+ msg = "Reindexing only valid with uniquely valued Index objects"
+ with pytest.raises(InvalidIndexError, match=msg):
+ index.get_indexer(index[0:2])
+
+ indexer, _ = index.get_indexer_non_unique(index[0:2])
+ assert isinstance(indexer, np.ndarray)
+ assert indexer.dtype == np.intp
+
+ def test_get_indexer_masked_duplicated_na(self):
+ # GH#48411
+ idx = Index([1, 2, NA, NA], dtype="Int64")
+ result = idx.get_indexer_for(Index([1, NA], dtype="Int64"))
+ expected = np.array([0, 2, 3], dtype=result.dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestConvertSliceIndexer:
+ def test_convert_almost_null_slice(self, index):
+ # slice with None at both ends, but not step
+
+ key = slice(None, None, "foo")
+
+ if isinstance(index, IntervalIndex):
+ msg = "label-based slicing with step!=1 is not supported for IntervalIndex"
+ with pytest.raises(ValueError, match=msg):
+ index._convert_slice_indexer(key, "loc")
+ else:
+ msg = "'>=' not supported between instances of 'str' and 'int'"
+ with pytest.raises(TypeError, match=msg):
+ index._convert_slice_indexer(key, "loc")
+
+
+class TestPutmask:
+ def test_putmask_with_wrong_mask(self, index):
+ # GH#18368
+ if not len(index):
+ pytest.skip("Test doesn't make sense for empty index")
+
+ fill = index[0]
+
+ msg = "putmask: mask and data must be the same size"
+ with pytest.raises(ValueError, match=msg):
+ index.putmask(np.ones(len(index) + 1, np.bool_), fill)
+
+ with pytest.raises(ValueError, match=msg):
+ index.putmask(np.ones(len(index) - 1, np.bool_), fill)
+
+ with pytest.raises(ValueError, match=msg):
+ index.putmask("foo", fill)
+
+
+@pytest.mark.parametrize("idx", [[1, 2, 3], [0.1, 0.2, 0.3], ["a", "b", "c"]])
+def test_getitem_deprecated_float(idx):
+ # https://github.com/pandas-dev/pandas/issues/34191
+ idx = Index(idx)
+ msg = "Indexing with a float is no longer supported"
+ with pytest.raises(IndexError, match=msg):
+ idx[1.0]
+
+
+@pytest.mark.parametrize(
+ "idx,target,expected",
+ [
+ ([np.nan, "var1", np.nan], [np.nan], np.array([0, 2], dtype=np.intp)),
+ (
+ [np.nan, "var1", np.nan],
+ [np.nan, "var1"],
+ np.array([0, 2, 1], dtype=np.intp),
+ ),
+ (
+ np.array([np.nan, "var1", np.nan], dtype=object),
+ [np.nan],
+ np.array([0, 2], dtype=np.intp),
+ ),
+ (
+ DatetimeIndex(["2020-08-05", NaT, NaT]),
+ [NaT],
+ np.array([1, 2], dtype=np.intp),
+ ),
+ (["a", "b", "a", np.nan], [np.nan], np.array([3], dtype=np.intp)),
+ (
+ np.array(["b", np.nan, float("NaN"), "b"], dtype=object),
+ Index([np.nan], dtype=object),
+ np.array([1, 2], dtype=np.intp),
+ ),
+ ],
+)
+def test_get_indexer_non_unique_multiple_nans(idx, target, expected):
+ # GH 35392
+ axis = Index(idx)
+ actual = axis.get_indexer_for(target)
+ tm.assert_numpy_array_equal(actual, expected)
+
+
+def test_get_indexer_non_unique_nans_in_object_dtype_target(nulls_fixture):
+ idx = Index([1.0, 2.0])
+ target = Index([1, nulls_fixture], dtype="object")
+
+ result_idx, result_missing = idx.get_indexer_non_unique(target)
+ tm.assert_numpy_array_equal(result_idx, np.array([0, -1], dtype=np.intp))
+ tm.assert_numpy_array_equal(result_missing, np.array([1], dtype=np.intp))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_numpy_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_numpy_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..8004e97698b675f2e6898ef3f095e18faf89bf31
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_numpy_compat.py
@@ -0,0 +1,189 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ BooleanDtype,
+ CategoricalIndex,
+ DatetimeIndex,
+ Index,
+ PeriodIndex,
+ TimedeltaIndex,
+ isna,
+)
+import pandas._testing as tm
+from pandas.api.types import (
+ is_complex_dtype,
+ is_numeric_dtype,
+)
+from pandas.core.indexes.datetimelike import DatetimeIndexOpsMixin
+
+
+def test_numpy_ufuncs_out(index):
+ result = index == index
+
+ out = np.empty(index.shape, dtype=bool)
+ np.equal(index, index, out=out)
+ tm.assert_numpy_array_equal(out, result)
+
+ if not index._is_multi:
+ # same thing on the ExtensionArray
+ out = np.empty(index.shape, dtype=bool)
+ np.equal(index.array, index.array, out=out)
+ tm.assert_numpy_array_equal(out, result)
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ np.exp,
+ np.exp2,
+ np.expm1,
+ np.log,
+ np.log2,
+ np.log10,
+ np.log1p,
+ np.sqrt,
+ np.sin,
+ np.cos,
+ np.tan,
+ np.arcsin,
+ np.arccos,
+ np.arctan,
+ np.sinh,
+ np.cosh,
+ np.tanh,
+ np.arcsinh,
+ np.arccosh,
+ np.arctanh,
+ np.deg2rad,
+ np.rad2deg,
+ ],
+ ids=lambda x: x.__name__,
+)
+def test_numpy_ufuncs_basic(index, func):
+ # test ufuncs of numpy, see:
+ # https://numpy.org/doc/stable/reference/ufuncs.html
+
+ if isinstance(index, DatetimeIndexOpsMixin):
+ with tm.external_error_raised((TypeError, AttributeError)):
+ with np.errstate(all="ignore"):
+ func(index)
+ elif is_numeric_dtype(index) and not (
+ is_complex_dtype(index) and func in [np.deg2rad, np.rad2deg]
+ ):
+ # coerces to float (e.g. np.sin)
+ with np.errstate(all="ignore"):
+ result = func(index)
+ arr_result = func(index.values)
+ if arr_result.dtype == np.float16:
+ arr_result = arr_result.astype(np.float32)
+ exp = Index(arr_result, name=index.name)
+
+ tm.assert_index_equal(result, exp)
+ if isinstance(index.dtype, np.dtype) and is_numeric_dtype(index):
+ if is_complex_dtype(index):
+ assert result.dtype == index.dtype
+ elif index.dtype in ["bool", "int8", "uint8"]:
+ assert result.dtype in ["float16", "float32"]
+ elif index.dtype in ["int16", "uint16", "float32"]:
+ assert result.dtype == "float32"
+ else:
+ assert result.dtype == "float64"
+ else:
+ # e.g. np.exp with Int64 -> Float64
+ assert type(result) is Index
+ # raise AttributeError or TypeError
+ elif len(index) == 0:
+ pass
+ else:
+ with tm.external_error_raised((TypeError, AttributeError)):
+ with np.errstate(all="ignore"):
+ func(index)
+
+
+@pytest.mark.parametrize(
+ "func", [np.isfinite, np.isinf, np.isnan, np.signbit], ids=lambda x: x.__name__
+)
+def test_numpy_ufuncs_other(index, func):
+ # test ufuncs of numpy, see:
+ # https://numpy.org/doc/stable/reference/ufuncs.html
+ if isinstance(index, (DatetimeIndex, TimedeltaIndex)):
+ if func in (np.isfinite, np.isinf, np.isnan):
+ # numpy 1.18 changed isinf and isnan to not raise on dt64/td64
+ result = func(index)
+
+ out = np.empty(index.shape, dtype=bool)
+ func(index, out=out)
+ tm.assert_index_equal(Index(out), result)
+ else:
+ with tm.external_error_raised(TypeError):
+ func(index)
+
+ elif isinstance(index, PeriodIndex):
+ with tm.external_error_raised(TypeError):
+ func(index)
+
+ elif is_numeric_dtype(index) and not (
+ is_complex_dtype(index) and func is np.signbit
+ ):
+ # Results in bool array
+ result = func(index)
+ assert isinstance(result, Index)
+ if not isinstance(index.dtype, np.dtype):
+ # e.g. Int64 we expect to get BooleanArray back
+ assert isinstance(result.dtype, BooleanDtype)
+ else:
+ assert isinstance(result.dtype, np.dtype)
+
+ out = np.empty(index.shape, dtype=bool)
+ func(index, out=out)
+
+ if not isinstance(index.dtype, np.dtype):
+ tm.assert_index_equal(result, Index(out, dtype="boolean"))
+ else:
+ tm.assert_index_equal(result, Index(out))
+
+ elif len(index) == 0:
+ pass
+ else:
+ with tm.external_error_raised(TypeError):
+ func(index)
+
+
+@pytest.mark.parametrize("func", [np.maximum, np.minimum])
+def test_numpy_ufuncs_reductions(index, func):
+ # TODO: overlap with tests.series.test_ufunc.test_reductions
+ if len(index) == 0:
+ pytest.skip("Test doesn't make sense for empty index.")
+
+ if isinstance(index, CategoricalIndex) and index.dtype.ordered is False:
+ with pytest.raises(TypeError, match="is not ordered for"):
+ func.reduce(index)
+ return
+ else:
+ result = func.reduce(index)
+
+ if func is np.maximum:
+ expected = index.max(skipna=False)
+ else:
+ expected = index.min(skipna=False)
+ # TODO: do we have cases both with and without NAs?
+
+ assert type(result) is type(expected)
+ if isna(result):
+ assert isna(expected)
+ else:
+ assert result == expected
+
+
+@pytest.mark.parametrize("func", [np.bitwise_and, np.bitwise_or, np.bitwise_xor])
+def test_numpy_ufuncs_bitwise(func):
+ # https://github.com/pandas-dev/pandas/issues/46769
+ idx1 = Index([1, 2, 3, 4], dtype="int64")
+ idx2 = Index([3, 4, 5, 6], dtype="int64")
+
+ with tm.assert_produces_warning(None):
+ result = func(idx1, idx2)
+
+ expected = Index(func(idx1.values, idx2.values))
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_old_base.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_old_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..216088c595f87325992a860435c09cb83d856ff7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_old_base.py
@@ -0,0 +1,986 @@
+from __future__ import annotations
+
+from datetime import datetime
+import weakref
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import Timestamp
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.common import (
+ is_integer_dtype,
+ is_numeric_dtype,
+)
+from pandas.core.dtypes.dtypes import CategoricalDtype
+
+import pandas as pd
+from pandas import (
+ CategoricalIndex,
+ DatetimeIndex,
+ DatetimeTZDtype,
+ Index,
+ IntervalIndex,
+ MultiIndex,
+ PeriodIndex,
+ RangeIndex,
+ Series,
+ StringDtype,
+ TimedeltaIndex,
+ isna,
+ period_range,
+)
+import pandas._testing as tm
+import pandas.core.algorithms as algos
+from pandas.core.arrays import BaseMaskedArray
+
+
+class TestBase:
+ @pytest.fixture(
+ params=[
+ RangeIndex(start=0, stop=20, step=2),
+ Index(np.arange(5, dtype=np.float64)),
+ Index(np.arange(5, dtype=np.float32)),
+ Index(np.arange(5, dtype=np.uint64)),
+ Index(range(0, 20, 2), dtype=np.int64),
+ Index(range(0, 20, 2), dtype=np.int32),
+ Index(range(0, 20, 2), dtype=np.int16),
+ Index(range(0, 20, 2), dtype=np.int8),
+ Index(list("abcde")),
+ Index([0, "a", 1, "b", 2, "c"]),
+ period_range("20130101", periods=5, freq="D"),
+ TimedeltaIndex(
+ [
+ "0 days 01:00:00",
+ "1 days 01:00:00",
+ "2 days 01:00:00",
+ "3 days 01:00:00",
+ "4 days 01:00:00",
+ ],
+ dtype="timedelta64[ns]",
+ freq="D",
+ ),
+ DatetimeIndex(
+ ["2013-01-01", "2013-01-02", "2013-01-03", "2013-01-04", "2013-01-05"],
+ dtype="datetime64[ns]",
+ freq="D",
+ ),
+ IntervalIndex.from_breaks(range(11), closed="right"),
+ ]
+ )
+ def simple_index(self, request):
+ return request.param
+
+ def test_pickle_compat_construction(self, simple_index):
+ # need an object to create with
+ if isinstance(simple_index, RangeIndex):
+ pytest.skip("RangeIndex() is a valid constructor")
+ msg = "|".join(
+ [
+ r"Index\(\.\.\.\) must be called with a collection of some "
+ r"kind, None was passed",
+ r"DatetimeIndex\(\) must be called with a collection of some "
+ r"kind, None was passed",
+ r"TimedeltaIndex\(\) must be called with a collection of some "
+ r"kind, None was passed",
+ r"__new__\(\) missing 1 required positional argument: 'data'",
+ r"__new__\(\) takes at least 2 arguments \(1 given\)",
+ r"'NoneType' object is not iterable",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ type(simple_index)()
+
+ def test_shift(self, simple_index):
+ # GH8083 test the base class for shift
+ if isinstance(simple_index, (DatetimeIndex, TimedeltaIndex, PeriodIndex)):
+ pytest.skip("Tested in test_ops/test_arithmetic")
+ idx = simple_index
+ msg = (
+ f"This method is only implemented for DatetimeIndex, PeriodIndex and "
+ f"TimedeltaIndex; Got type {type(idx).__name__}"
+ )
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.shift(1)
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.shift(1, 2)
+
+ def test_constructor_name_unhashable(self, simple_index):
+ # GH#29069 check that name is hashable
+ # See also same-named test in tests.series.test_constructors
+ idx = simple_index
+ with pytest.raises(TypeError, match="Index.name must be a hashable type"):
+ type(idx)(idx, name=[])
+
+ def test_create_index_existing_name(self, simple_index):
+ # GH11193, when an existing index is passed, and a new name is not
+ # specified, the new index should inherit the previous object name
+ expected = simple_index.copy()
+ if not isinstance(expected, MultiIndex):
+ expected.name = "foo"
+ result = Index(expected)
+ tm.assert_index_equal(result, expected)
+
+ result = Index(expected, name="bar")
+ expected.name = "bar"
+ tm.assert_index_equal(result, expected)
+ else:
+ expected.names = ["foo", "bar"]
+ result = Index(expected)
+ tm.assert_index_equal(
+ result,
+ Index(
+ Index(
+ [
+ ("foo", "one"),
+ ("foo", "two"),
+ ("bar", "one"),
+ ("baz", "two"),
+ ("qux", "one"),
+ ("qux", "two"),
+ ],
+ dtype="object",
+ ),
+ names=["foo", "bar"],
+ ),
+ )
+
+ result = Index(expected, names=["A", "B"])
+ tm.assert_index_equal(
+ result,
+ Index(
+ Index(
+ [
+ ("foo", "one"),
+ ("foo", "two"),
+ ("bar", "one"),
+ ("baz", "two"),
+ ("qux", "one"),
+ ("qux", "two"),
+ ],
+ dtype="object",
+ ),
+ names=["A", "B"],
+ ),
+ )
+
+ def test_numeric_compat(self, simple_index):
+ idx = simple_index
+ # Check that this doesn't cover MultiIndex case, if/when it does,
+ # we can remove multi.test_compat.test_numeric_compat
+ assert not isinstance(idx, MultiIndex)
+ if type(idx) is Index:
+ pytest.skip("Not applicable for Index")
+ if is_numeric_dtype(simple_index.dtype) or isinstance(
+ simple_index, TimedeltaIndex
+ ):
+ pytest.skip("Tested elsewhere.")
+
+ typ = type(idx._data).__name__
+ cls = type(idx).__name__
+ lmsg = "|".join(
+ [
+ rf"unsupported operand type\(s\) for \*: '{typ}' and 'int'",
+ "cannot perform (__mul__|__truediv__|__floordiv__) with "
+ f"this index type: ({cls}|{typ})",
+ ]
+ )
+ with pytest.raises(TypeError, match=lmsg):
+ idx * 1
+ rmsg = "|".join(
+ [
+ rf"unsupported operand type\(s\) for \*: 'int' and '{typ}'",
+ "cannot perform (__rmul__|__rtruediv__|__rfloordiv__) with "
+ f"this index type: ({cls}|{typ})",
+ ]
+ )
+ with pytest.raises(TypeError, match=rmsg):
+ 1 * idx
+
+ div_err = lmsg.replace("*", "/")
+ with pytest.raises(TypeError, match=div_err):
+ idx / 1
+ div_err = rmsg.replace("*", "/")
+ with pytest.raises(TypeError, match=div_err):
+ 1 / idx
+
+ floordiv_err = lmsg.replace("*", "//")
+ with pytest.raises(TypeError, match=floordiv_err):
+ idx // 1
+ floordiv_err = rmsg.replace("*", "//")
+ with pytest.raises(TypeError, match=floordiv_err):
+ 1 // idx
+
+ def test_logical_compat(self, simple_index):
+ if simple_index.dtype in (object, "string"):
+ pytest.skip("Tested elsewhere.")
+ idx = simple_index
+ if idx.dtype.kind in "iufcbm":
+ assert idx.all() == idx._values.all()
+ assert idx.all() == idx.to_series().all()
+ assert idx.any() == idx._values.any()
+ assert idx.any() == idx.to_series().any()
+ else:
+ msg = "does not support operation '(any|all)'"
+ with pytest.raises(TypeError, match=msg):
+ idx.all()
+ with pytest.raises(TypeError, match=msg):
+ idx.any()
+
+ def test_repr_roundtrip(self, simple_index):
+ if isinstance(simple_index, IntervalIndex):
+ pytest.skip(f"Not a valid repr for {type(simple_index).__name__}")
+ idx = simple_index
+ tm.assert_index_equal(eval(repr(idx)), idx)
+
+ def test_repr_max_seq_item_setting(self, simple_index):
+ # GH10182
+ if isinstance(simple_index, IntervalIndex):
+ pytest.skip(f"Not a valid repr for {type(simple_index).__name__}")
+ idx = simple_index
+ idx = idx.repeat(50)
+ with pd.option_context("display.max_seq_items", None):
+ repr(idx)
+ assert "..." not in str(idx)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_ensure_copied_data(self, index):
+ # Check the "copy" argument of each Index.__new__ is honoured
+ # GH12309
+ init_kwargs = {}
+ if isinstance(index, PeriodIndex):
+ # Needs "freq" specification:
+ init_kwargs["freq"] = index.freq
+ elif isinstance(index, (RangeIndex, MultiIndex, CategoricalIndex)):
+ pytest.skip(
+ "RangeIndex cannot be initialized from data, "
+ "MultiIndex and CategoricalIndex are tested separately"
+ )
+ elif index.dtype == object and index.inferred_type in ["boolean", "string"]:
+ init_kwargs["dtype"] = index.dtype
+
+ index_type = type(index)
+ result = index_type(index.values, copy=True, **init_kwargs)
+ if isinstance(index.dtype, DatetimeTZDtype):
+ result = result.tz_localize("UTC").tz_convert(index.tz)
+ if isinstance(index, (DatetimeIndex, TimedeltaIndex)):
+ index = index._with_freq(None)
+
+ tm.assert_index_equal(index, result)
+
+ if isinstance(index, PeriodIndex):
+ # .values an object array of Period, thus copied
+ result = index_type.from_ordinals(ordinals=index.asi8, **init_kwargs)
+ tm.assert_numpy_array_equal(index.asi8, result.asi8, check_same="same")
+ elif isinstance(index, IntervalIndex):
+ # checked in test_interval.py
+ pass
+ elif type(index) is Index and not isinstance(index.dtype, np.dtype):
+ result = index_type(index.values, copy=False, **init_kwargs)
+ tm.assert_index_equal(result, index)
+
+ if isinstance(index._values, BaseMaskedArray):
+ assert np.shares_memory(index._values._data, result._values._data)
+ tm.assert_numpy_array_equal(
+ index._values._data, result._values._data, check_same="same"
+ )
+ assert np.shares_memory(index._values._mask, result._values._mask)
+ tm.assert_numpy_array_equal(
+ index._values._mask, result._values._mask, check_same="same"
+ )
+ elif (
+ isinstance(index.dtype, StringDtype) and index.dtype.storage == "python"
+ ):
+ assert np.shares_memory(index._values._ndarray, result._values._ndarray)
+ tm.assert_numpy_array_equal(
+ index._values._ndarray, result._values._ndarray, check_same="same"
+ )
+ elif (
+ isinstance(index.dtype, StringDtype)
+ and index.dtype.storage == "pyarrow"
+ ):
+ assert tm.shares_memory(result._values, index._values)
+ else:
+ raise NotImplementedError(index.dtype)
+ else:
+ result = index_type(index.values, copy=False, **init_kwargs)
+ tm.assert_numpy_array_equal(index.values, result.values, check_same="same")
+
+ def test_memory_usage(self, index):
+ index._engine.clear_mapping()
+ result = index.memory_usage()
+ if index.empty:
+ # we report 0 for no-length
+ assert result == 0
+ return
+
+ # non-zero length
+ index.get_loc(index[0])
+ result2 = index.memory_usage()
+ result3 = index.memory_usage(deep=True)
+
+ # RangeIndex, IntervalIndex
+ # don't have engines
+ # Index[EA] has engine but it does not have a Hashtable .mapping
+ if not isinstance(index, (RangeIndex, IntervalIndex)) and not (
+ type(index) is Index and not isinstance(index.dtype, np.dtype)
+ ):
+ assert result2 > result
+
+ if index.inferred_type == "object":
+ assert result3 > result2
+
+ def test_memory_usage_doesnt_trigger_engine(self, index):
+ index._cache.clear()
+ assert "_engine" not in index._cache
+
+ res_without_engine = index.memory_usage()
+ assert "_engine" not in index._cache
+
+ # explicitly load and cache the engine
+ _ = index._engine
+ assert "_engine" in index._cache
+
+ res_with_engine = index.memory_usage()
+
+ # the empty engine doesn't affect the result even when initialized with values,
+ # because engine.sizeof() doesn't consider the content of engine.values
+ assert res_with_engine == res_without_engine
+
+ if len(index) == 0:
+ assert res_without_engine == 0
+ assert res_with_engine == 0
+ else:
+ assert res_without_engine > 0
+ assert res_with_engine > 0
+
+ def test_argsort(self, index):
+ if isinstance(index, CategoricalIndex):
+ pytest.skip(f"{type(self).__name__} separately tested")
+
+ result = index.argsort()
+ expected = np.array(index).argsort()
+ tm.assert_numpy_array_equal(result, expected, check_dtype=False)
+
+ def test_numpy_argsort(self, index):
+ result = np.argsort(index)
+ expected = index.argsort()
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = np.argsort(index, kind="mergesort")
+ expected = index.argsort(kind="mergesort")
+ tm.assert_numpy_array_equal(result, expected)
+
+ # these are the only two types that perform
+ # pandas compatibility input validation - the
+ # rest already perform separate (or no) such
+ # validation via their 'values' attribute as
+ # defined in pandas.core.indexes/base.py - they
+ # cannot be changed at the moment due to
+ # backwards compatibility concerns
+ if isinstance(index, (CategoricalIndex, RangeIndex)):
+ msg = "the 'axis' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.argsort(index, axis=1)
+
+ msg = "the 'order' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.argsort(index, order=("a", "b"))
+
+ def test_repeat(self, simple_index):
+ rep = 2
+ idx = simple_index.copy()
+ new_index_cls = idx._constructor
+ expected = new_index_cls(idx.values.repeat(rep), name=idx.name)
+ tm.assert_index_equal(idx.repeat(rep), expected)
+
+ idx = simple_index
+ rep = np.arange(len(idx))
+ expected = new_index_cls(idx.values.repeat(rep), name=idx.name)
+ tm.assert_index_equal(idx.repeat(rep), expected)
+
+ def test_numpy_repeat(self, simple_index):
+ rep = 2
+ idx = simple_index
+ expected = idx.repeat(rep)
+ tm.assert_index_equal(np.repeat(idx, rep), expected)
+
+ msg = "the 'axis' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.repeat(idx, rep, axis=0)
+
+ def test_where(self, listlike_box, simple_index):
+ if isinstance(simple_index, (IntervalIndex, PeriodIndex)) or is_numeric_dtype(
+ simple_index.dtype
+ ):
+ pytest.skip("Tested elsewhere.")
+ klass = listlike_box
+
+ idx = simple_index
+ if isinstance(idx, (DatetimeIndex, TimedeltaIndex)):
+ # where does not preserve freq
+ idx = idx._with_freq(None)
+
+ cond = [True] * len(idx)
+ result = idx.where(klass(cond))
+ expected = idx
+ tm.assert_index_equal(result, expected)
+
+ cond = [False] + [True] * len(idx[1:])
+ expected = Index([idx._na_value, *idx[1:].tolist()], dtype=idx.dtype)
+ result = idx.where(klass(cond))
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_base(self, index):
+ # GH#51363
+ trimmed = index[1:4]
+
+ if not len(index):
+ pytest.skip("Not applicable for empty index")
+
+ result = trimmed.insert(0, index[0])
+ assert index[0:4].equals(result)
+
+ def test_insert_out_of_bounds(self, index, using_infer_string):
+ # TypeError/IndexError matches what np.insert raises in these cases
+
+ if len(index) > 0:
+ err = TypeError
+ else:
+ err = IndexError
+ if len(index) == 0:
+ # 0 vs 0.5 in error message varies with numpy version
+ msg = "index (0|0.5) is out of bounds for axis 0 with size 0"
+ else:
+ msg = "slice indices must be integers or None or have an __index__ method"
+
+ if using_infer_string:
+ if str(index.dtype) in {"str", "string", "category"}:
+ msg = "loc must be an integer between"
+ elif index.dtype == "object" and len(index) == 0:
+ msg = "loc must be an integer between"
+ err = TypeError
+
+ with pytest.raises(err, match=msg):
+ index.insert(0.5, "foo")
+
+ msg = "|".join(
+ [
+ r"index -?\d+ is out of bounds for axis 0 with size \d+",
+ "loc must be an integer between",
+ ]
+ )
+ with pytest.raises(IndexError, match=msg):
+ index.insert(len(index) + 1, 1)
+
+ with pytest.raises(IndexError, match=msg):
+ index.insert(-len(index) - 1, 1)
+
+ def test_delete_base(self, index):
+ if not len(index):
+ pytest.skip("Not applicable for empty index")
+
+ if isinstance(index, RangeIndex):
+ # tested in class
+ pytest.skip(f"{type(self).__name__} tested elsewhere")
+
+ expected = index[1:]
+ result = index.delete(0)
+ assert result.equals(expected)
+ assert result.name == expected.name
+
+ expected = index[:-1]
+ result = index.delete(-1)
+ assert result.equals(expected)
+ assert result.name == expected.name
+
+ length = len(index)
+ msg = f"index {length} is out of bounds for axis 0 with size {length}"
+ with pytest.raises(IndexError, match=msg):
+ index.delete(length)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_equals(self, index):
+ if isinstance(index, IntervalIndex):
+ pytest.skip(f"{type(index).__name__} tested elsewhere")
+
+ is_ea_idx = type(index) is Index and not isinstance(index.dtype, np.dtype)
+
+ assert index.equals(index)
+ assert index.equals(index.copy())
+ if not is_ea_idx:
+ # doesn't hold for e.g. IntegerDtype
+ assert index.equals(index.astype(object))
+
+ assert not index.equals(list(index))
+ assert not index.equals(np.array(index))
+
+ # Cannot pass in non-int64 dtype to RangeIndex
+ if not isinstance(index, RangeIndex) and not is_ea_idx:
+ same_values = Index(index, dtype=object)
+ assert index.equals(same_values)
+ assert same_values.equals(index)
+
+ if index.nlevels == 1:
+ # do not test MultiIndex
+ assert not index.equals(Series(index))
+
+ def test_equals_op(self, simple_index):
+ # GH9947, GH10637
+ index_a = simple_index
+
+ n = len(index_a)
+ index_b = index_a[0:-1]
+ index_c = index_a[0:-1].append(index_a[-2:-1])
+ index_d = index_a[0:1]
+
+ msg = "Lengths must match|could not be broadcast"
+ with pytest.raises(ValueError, match=msg):
+ index_a == index_b
+ expected1 = np.array([True] * n)
+ expected2 = np.array([True] * (n - 1) + [False])
+ tm.assert_numpy_array_equal(index_a == index_a, expected1)
+ tm.assert_numpy_array_equal(index_a == index_c, expected2)
+
+ # test comparisons with numpy arrays
+ array_a = np.array(index_a)
+ array_b = np.array(index_a[0:-1])
+ array_c = np.array(index_a[0:-1].append(index_a[-2:-1]))
+ array_d = np.array(index_a[0:1])
+ with pytest.raises(ValueError, match=msg):
+ index_a == array_b
+ tm.assert_numpy_array_equal(index_a == array_a, expected1)
+ tm.assert_numpy_array_equal(index_a == array_c, expected2)
+
+ # test comparisons with Series
+ series_a = Series(array_a)
+ series_b = Series(array_b)
+ series_c = Series(array_c)
+ series_d = Series(array_d)
+ with pytest.raises(ValueError, match=msg):
+ index_a == series_b
+
+ tm.assert_series_equal(index_a == series_a, Series(expected1))
+ tm.assert_series_equal(index_a == series_c, Series(expected2))
+
+ # cases where length is 1 for one of them
+ with pytest.raises(ValueError, match="Lengths must match"):
+ index_a == index_d
+ with pytest.raises(ValueError, match="Lengths must match"):
+ index_a == series_d
+ with pytest.raises(ValueError, match="Lengths must match"):
+ index_a == array_d
+ msg = "Can only compare identically-labeled Series objects"
+ with pytest.raises(ValueError, match=msg):
+ series_a == series_d
+ with pytest.raises(ValueError, match="Lengths must match"):
+ series_a == array_d
+
+ # comparing with a scalar should broadcast; note that we are excluding
+ # MultiIndex because in this case each item in the index is a tuple of
+ # length 2, and therefore is considered an array of length 2 in the
+ # comparison instead of a scalar
+ if not isinstance(index_a, MultiIndex):
+ expected3 = np.array([False] * (len(index_a) - 2) + [True, False])
+ # assuming the 2nd to last item is unique in the data
+ item = index_a[-2]
+ tm.assert_numpy_array_equal(index_a == item, expected3)
+ tm.assert_series_equal(series_a == item, Series(expected3))
+
+ def test_fillna(self, index):
+ # GH 11343
+ if len(index) == 0:
+ pytest.skip("Not relevant for empty index")
+ elif index.dtype == bool:
+ pytest.skip(f"{index.dtype} cannot hold NAs")
+ elif isinstance(index, Index) and is_integer_dtype(index.dtype):
+ pytest.skip(f"Not relevant for Index with {index.dtype}")
+ elif isinstance(index, MultiIndex):
+ idx = index.copy(deep=True)
+ msg = "fillna is not defined for MultiIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.fillna(idx[0])
+ else:
+ idx = index.copy(deep=True)
+ result = idx.fillna(idx[0])
+ tm.assert_index_equal(result, idx)
+ assert result is not idx
+
+ msg = "'value' must be a scalar, passed: "
+ with pytest.raises(TypeError, match=msg):
+ idx.fillna([idx[0]])
+
+ idx = index.copy(deep=True)
+ values = idx._values
+
+ values[1] = np.nan
+
+ idx = type(index)(values)
+
+ expected = np.array([False] * len(idx), dtype=bool)
+ expected[1] = True
+ tm.assert_numpy_array_equal(idx._isnan, expected)
+ assert idx.hasnans is True
+
+ def test_nulls(self, index):
+ # this is really a smoke test for the methods
+ # as these are adequately tested for function elsewhere
+ if len(index) == 0:
+ tm.assert_numpy_array_equal(index.isna(), np.array([], dtype=bool))
+ elif isinstance(index, MultiIndex):
+ idx = index.copy()
+ msg = "isna is not defined for MultiIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ idx.isna()
+ elif not index.hasnans:
+ tm.assert_numpy_array_equal(index.isna(), np.zeros(len(index), dtype=bool))
+ tm.assert_numpy_array_equal(index.notna(), np.ones(len(index), dtype=bool))
+ else:
+ result = isna(index)
+ tm.assert_numpy_array_equal(index.isna(), result)
+ tm.assert_numpy_array_equal(index.notna(), ~result)
+
+ def test_empty(self, simple_index):
+ # GH 15270
+ idx = simple_index
+ assert not idx.empty
+ assert idx[:0].empty
+
+ def test_join_self_unique(self, join_type, simple_index):
+ idx = simple_index
+ if idx.is_unique:
+ joined = idx.join(idx, how=join_type)
+ expected = simple_index
+ if join_type == "outer":
+ expected = algos.safe_sort(expected)
+ tm.assert_index_equal(joined, expected)
+
+ def test_map(self, simple_index):
+ # callable
+ if isinstance(simple_index, (TimedeltaIndex, PeriodIndex)):
+ pytest.skip("Tested elsewhere.")
+ idx = simple_index
+
+ result = idx.map(lambda x: x)
+ # RangeIndex are equivalent to the similar Index with int64 dtype
+ tm.assert_index_equal(result, idx, exact="equiv")
+
+ @pytest.mark.parametrize(
+ "mapper",
+ [
+ lambda values, index: {i: e for e, i in zip(values, index)},
+ lambda values, index: Series(values, index),
+ ],
+ )
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_map_dictlike(self, mapper, simple_index, request):
+ idx = simple_index
+ if isinstance(idx, (DatetimeIndex, TimedeltaIndex, PeriodIndex)):
+ pytest.skip("Tested elsewhere.")
+
+ identity = mapper(idx.values, idx)
+
+ result = idx.map(identity)
+ # RangeIndex are equivalent to the similar Index with int64 dtype
+ tm.assert_index_equal(result, idx, exact="equiv")
+
+ # empty mappable
+ dtype = None
+ if idx.dtype.kind == "f":
+ dtype = idx.dtype
+
+ expected = Index([np.nan] * len(idx), dtype=dtype)
+ result = idx.map(mapper(expected, idx))
+ tm.assert_index_equal(result, expected)
+
+ def test_map_str(self, simple_index):
+ # GH 31202
+ if isinstance(simple_index, CategoricalIndex):
+ pytest.skip("See test_map.py")
+ idx = simple_index
+ result = idx.map(str)
+ expected = Index([str(x) for x in idx])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("copy", [True, False])
+ @pytest.mark.parametrize("name", [None, "foo"])
+ @pytest.mark.parametrize("ordered", [True, False])
+ def test_astype_category(self, copy, name, ordered, simple_index):
+ # GH 18630
+ idx = simple_index
+ if name:
+ idx = idx.rename(name)
+
+ # standard categories
+ dtype = CategoricalDtype(ordered=ordered)
+ result = idx.astype(dtype, copy=copy)
+ expected = CategoricalIndex(idx, name=name, ordered=ordered)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ # non-standard categories
+ dtype = CategoricalDtype(idx.unique().tolist()[:-1], ordered)
+ msg = "Constructing a Categorical with a dtype and values containing"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = idx.astype(dtype, copy=copy)
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = CategoricalIndex(idx, name=name, dtype=dtype)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ if ordered is False:
+ # dtype='category' defaults to ordered=False, so only test once
+ result = idx.astype("category", copy=copy)
+ expected = CategoricalIndex(idx, name=name)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_is_unique(self, simple_index):
+ # initialize a unique index
+ index = simple_index.drop_duplicates()
+ assert index.is_unique is True
+
+ # empty index should be unique
+ index_empty = index[:0]
+ assert index_empty.is_unique is True
+
+ # test basic dupes
+ index_dup = index.insert(0, index[0])
+ assert index_dup.is_unique is False
+
+ # single NA should be unique
+ index_na = index.insert(0, np.nan)
+ assert index_na.is_unique is True
+
+ # multiple NA should not be unique
+ index_na_dup = index_na.insert(0, np.nan)
+ assert index_na_dup.is_unique is False
+
+ @pytest.mark.arm_slow
+ def test_engine_reference_cycle(self, simple_index):
+ # GH27585
+ index = simple_index.copy()
+ ref = weakref.ref(index)
+ index._engine
+ del index
+ assert ref() is None
+
+ def test_getitem_2d_deprecated(self, simple_index):
+ # GH#30588, GH#31479
+ if isinstance(simple_index, IntervalIndex):
+ pytest.skip("Tested elsewhere")
+ idx = simple_index
+ msg = "Multi-dimensional indexing|too many|only"
+ with pytest.raises((ValueError, IndexError), match=msg):
+ idx[:, None]
+
+ if not isinstance(idx, RangeIndex):
+ # GH#44051 RangeIndex already raised pre-2.0 with a different message
+ with pytest.raises((ValueError, IndexError), match=msg):
+ idx[True]
+ with pytest.raises((ValueError, IndexError), match=msg):
+ idx[False]
+ else:
+ msg = "only integers, slices"
+ with pytest.raises(IndexError, match=msg):
+ idx[True]
+ with pytest.raises(IndexError, match=msg):
+ idx[False]
+
+ def test_copy_shares_cache(self, simple_index):
+ # GH32898, GH36840
+ idx = simple_index
+ idx.get_loc(idx[0]) # populates the _cache.
+ copy = idx.copy()
+
+ assert copy._cache is idx._cache
+
+ def test_shallow_copy_shares_cache(self, simple_index):
+ # GH32669, GH36840
+ idx = simple_index
+ idx.get_loc(idx[0]) # populates the _cache.
+ shallow_copy = idx._view()
+
+ assert shallow_copy._cache is idx._cache
+
+ shallow_copy = idx._shallow_copy(idx._data)
+ assert shallow_copy._cache is not idx._cache
+ assert shallow_copy._cache == {}
+
+ def test_index_groupby(self, simple_index):
+ idx = simple_index[:5]
+ to_groupby = np.array([1, 2, np.nan, 2, 1])
+ tm.assert_dict_equal(
+ idx.groupby(to_groupby), {1.0: idx[[0, 4]], 2.0: idx[[1, 3]]}
+ )
+
+ to_groupby = DatetimeIndex(
+ [
+ datetime(2011, 11, 1),
+ datetime(2011, 12, 1),
+ pd.NaT,
+ datetime(2011, 12, 1),
+ datetime(2011, 11, 1),
+ ],
+ tz="UTC",
+ ).values
+
+ ex_keys = [Timestamp("2011-11-01"), Timestamp("2011-12-01")]
+ expected = {ex_keys[0]: idx[[0, 4]], ex_keys[1]: idx[[1, 3]]}
+ tm.assert_dict_equal(idx.groupby(to_groupby), expected)
+
+ def test_append_preserves_dtype(self, simple_index):
+ # In particular Index with dtype float32
+ index = simple_index
+ N = len(index)
+
+ result = index.append(index)
+ assert result.dtype == index.dtype
+
+ tm.assert_index_equal(result[:N], index, exact=False, check_exact=True)
+ tm.assert_index_equal(result[N:], index, exact=False, check_exact=True)
+
+ alt = index.take(list(range(N)) * 2)
+ tm.assert_index_equal(result, alt, check_exact=True)
+
+ def test_inv(self, simple_index, using_infer_string):
+ idx = simple_index
+
+ if idx.dtype.kind in ["i", "u"]:
+ res = ~idx
+ expected = Index(~idx.values, name=idx.name)
+ tm.assert_index_equal(res, expected)
+
+ # check that we are matching Series behavior
+ res2 = ~Series(idx)
+ tm.assert_series_equal(res2, Series(expected))
+ else:
+ if idx.dtype.kind == "f":
+ msg = "ufunc 'invert' not supported for the input types"
+ else:
+ msg = "bad operand|__invert__ is not supported for string dtype"
+ with pytest.raises(TypeError, match=msg):
+ ~idx
+
+ # check that we get the same behavior with Series
+ with pytest.raises(TypeError, match=msg):
+ ~Series(idx)
+
+
+class TestNumericBase:
+ @pytest.fixture(
+ params=[
+ RangeIndex(start=0, stop=20, step=2),
+ Index(np.arange(5, dtype=np.float64)),
+ Index(np.arange(5, dtype=np.float32)),
+ Index(np.arange(5, dtype=np.uint64)),
+ Index(range(0, 20, 2), dtype=np.int64),
+ Index(range(0, 20, 2), dtype=np.int32),
+ Index(range(0, 20, 2), dtype=np.int16),
+ Index(range(0, 20, 2), dtype=np.int8),
+ ]
+ )
+ def simple_index(self, request):
+ return request.param
+
+ def test_constructor_unwraps_index(self, simple_index):
+ if isinstance(simple_index, RangeIndex):
+ pytest.skip("Tested elsewhere.")
+ index_cls = type(simple_index)
+ dtype = simple_index.dtype
+
+ idx = Index([1, 2], dtype=dtype)
+ result = index_cls(idx)
+ expected = np.array([1, 2], dtype=idx.dtype)
+ tm.assert_numpy_array_equal(result._data, expected)
+
+ def test_can_hold_identifiers(self, simple_index):
+ idx = simple_index
+ key = idx[0]
+ assert idx._can_hold_identifiers_and_holds_name(key) is False
+
+ def test_view(self, simple_index):
+ if isinstance(simple_index, RangeIndex):
+ pytest.skip("Tested elsewhere.")
+ index_cls = type(simple_index)
+ dtype = simple_index.dtype
+
+ idx = index_cls([], dtype=dtype, name="Foo")
+ idx_view = idx.view()
+ assert idx_view.name == "Foo"
+
+ idx_view = idx.view(dtype)
+ tm.assert_index_equal(idx, index_cls(idx_view, name="Foo"), exact=True)
+
+ msg = (
+ "Cannot change data-type for array of references.|"
+ "Cannot change data-type for object array.|"
+ )
+ with pytest.raises(TypeError, match=msg):
+ # GH#55709
+ idx.view(index_cls)
+
+ def test_insert_non_na(self, simple_index):
+ # GH#43921 inserting an element that we know we can hold should
+ # not change dtype or type (except for RangeIndex)
+ index = simple_index
+
+ result = index.insert(0, index[0])
+
+ expected = Index([index[0], *list(index)], dtype=index.dtype)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_insert_na(self, nulls_fixture, simple_index):
+ # GH 18295 (test missing)
+ index = simple_index
+ na_val = nulls_fixture
+
+ if na_val is pd.NaT:
+ expected = Index([index[0], pd.NaT, *list(index[1:])], dtype=object)
+ else:
+ expected = Index([index[0], np.nan, *list(index[1:])])
+ # GH#43921 we preserve float dtype
+ if index.dtype.kind == "f":
+ expected = Index(expected, dtype=index.dtype)
+
+ result = index.insert(1, na_val)
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_arithmetic_explicit_conversions(self, simple_index):
+ # GH 8608
+ # add/sub are overridden explicitly for Float/Int Index
+ index_cls = type(simple_index)
+ if index_cls is RangeIndex:
+ idx = RangeIndex(5)
+ else:
+ idx = index_cls(np.arange(5, dtype="int64"))
+
+ # float conversions
+ arr = np.arange(5, dtype="int64") * 3.2
+ expected = Index(arr, dtype=np.float64)
+ fidx = idx * 3.2
+ tm.assert_index_equal(fidx, expected)
+ fidx = 3.2 * idx
+ tm.assert_index_equal(fidx, expected)
+
+ # interops with numpy arrays
+ expected = Index(arr, dtype=np.float64)
+ a = np.zeros(5, dtype="float64")
+ result = fidx - a
+ tm.assert_index_equal(result, expected)
+
+ expected = Index(-arr, dtype=np.float64)
+ a = np.zeros(5, dtype="float64")
+ result = a - fidx
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("complex_dtype", [np.complex64, np.complex128])
+ def test_astype_to_complex(self, complex_dtype, simple_index):
+ result = simple_index.astype(complex_dtype)
+
+ assert type(result) is Index and result.dtype == complex_dtype
+
+ def test_cast_string(self, simple_index):
+ if isinstance(simple_index, RangeIndex):
+ pytest.skip("casting of strings not relevant for RangeIndex")
+ result = type(simple_index)(["0", "1", "2"], dtype=simple_index.dtype)
+ expected = type(simple_index)([0, 1, 2], dtype=simple_index.dtype)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f089eae7abe7ae5b86aface9d83ca4ae8e81113
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_setops.py
@@ -0,0 +1,1066 @@
+"""
+The tests in this package are to ensure the proper resultant dtypes of
+set operations.
+"""
+
+from datetime import datetime
+import operator
+
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.cast import find_common_type
+
+from pandas import (
+ CategoricalDtype,
+ CategoricalIndex,
+ DatetimeTZDtype,
+ Index,
+ MultiIndex,
+ PeriodDtype,
+ RangeIndex,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.api.types import (
+ is_signed_integer_dtype,
+ pandas_dtype,
+)
+
+
+def equal_contents(arr1, arr2) -> bool:
+ """
+ Checks if the set of unique elements of arr1 and arr2 are equivalent.
+ """
+ return frozenset(arr1) == frozenset(arr2)
+
+
+@pytest.fixture(
+ params=[
+ *tm.ALL_REAL_NUMPY_DTYPES,
+ "object",
+ "category",
+ "datetime64[ns]",
+ "timedelta64[ns]",
+ ]
+)
+def any_dtype_for_small_pos_integer_indexes(request):
+ """
+ Dtypes that can be given to an Index with small positive integers.
+
+ This means that for any dtype `x` in the params list, `Index([1, 2, 3], dtype=x)` is
+ valid and gives the correct Index (sub-)class.
+ """
+ return request.param
+
+
+@pytest.fixture
+def index_flat2(index_flat):
+ return index_flat
+
+
+def test_union_same_types(index):
+ # Union with a non-unique, non-monotonic index raises error
+ # Only needed for bool index factory
+ idx1 = index.sort_values()
+ idx2 = index.sort_values()
+ assert idx1.union(idx2).dtype == idx1.dtype
+
+
+def test_union_different_types(index_flat, index_flat2, request):
+ # This test only considers combinations of indices
+ # GH 23525
+ idx1 = index_flat
+ idx2 = index_flat2
+
+ if (
+ not idx1.is_unique
+ and not idx2.is_unique
+ and idx1.dtype.kind == "i"
+ and idx2.dtype.kind == "b"
+ ) or (
+ not idx2.is_unique
+ and not idx1.is_unique
+ and idx2.dtype.kind == "i"
+ and idx1.dtype.kind == "b"
+ ):
+ # Each condition had idx[1|2].is_monotonic_decreasing
+ # but failed when e.g.
+ # idx1 = Index(
+ # [True, True, True, True, True, True, True, True, False, False], dtype='bool'
+ # )
+ # idx2 = Index([0, 0, 1, 1, 2, 2], dtype='int64')
+ mark = pytest.mark.xfail(
+ reason="GH#44000 True==1", raises=ValueError, strict=False
+ )
+ request.applymarker(mark)
+
+ common_dtype = find_common_type([idx1.dtype, idx2.dtype])
+
+ warn = None
+ msg = "'<' not supported between"
+ if not len(idx1) or not len(idx2):
+ pass
+ elif (idx1.dtype.kind == "c" and (not lib.is_np_dtype(idx2.dtype, "iufc"))) or (
+ idx2.dtype.kind == "c" and (not lib.is_np_dtype(idx1.dtype, "iufc"))
+ ):
+ # complex objects non-sortable
+ warn = RuntimeWarning
+ elif (
+ isinstance(idx1.dtype, PeriodDtype) and isinstance(idx2.dtype, CategoricalDtype)
+ ) or (
+ isinstance(idx2.dtype, PeriodDtype) and isinstance(idx1.dtype, CategoricalDtype)
+ ):
+ warn = FutureWarning
+ msg = r"PeriodDtype\[B\] is deprecated"
+ mark = pytest.mark.xfail(
+ reason="Warning not produced on all builds",
+ raises=AssertionError,
+ strict=False,
+ )
+ request.applymarker(mark)
+
+ any_uint64 = np.uint64 in (idx1.dtype, idx2.dtype)
+ idx1_signed = is_signed_integer_dtype(idx1.dtype)
+ idx2_signed = is_signed_integer_dtype(idx2.dtype)
+
+ # Union with a non-unique, non-monotonic index raises error
+ # This applies to the boolean index
+ idx1 = idx1.sort_values()
+ idx2 = idx2.sort_values()
+
+ with tm.assert_produces_warning(warn, match=msg):
+ res1 = idx1.union(idx2)
+ res2 = idx2.union(idx1)
+
+ if any_uint64 and (idx1_signed or idx2_signed):
+ assert res1.dtype == np.dtype("O")
+ assert res2.dtype == np.dtype("O")
+ else:
+ assert res1.dtype == common_dtype
+ assert res2.dtype == common_dtype
+
+
+@pytest.mark.parametrize(
+ "idx1,idx2",
+ [
+ (Index(np.arange(5), dtype=np.int64), RangeIndex(5)),
+ (Index(np.arange(5), dtype=np.float64), Index(np.arange(5), dtype=np.int64)),
+ (Index(np.arange(5), dtype=np.float64), RangeIndex(5)),
+ (Index(np.arange(5), dtype=np.float64), Index(np.arange(5), dtype=np.uint64)),
+ ],
+)
+def test_compatible_inconsistent_pairs(idx1, idx2):
+ # GH 23525
+ res1 = idx1.union(idx2)
+ res2 = idx2.union(idx1)
+
+ assert res1.dtype in (idx1.dtype, idx2.dtype)
+ assert res2.dtype in (idx1.dtype, idx2.dtype)
+
+
+@pytest.mark.parametrize(
+ "left, right, expected",
+ [
+ ("int64", "int64", "int64"),
+ ("int64", "uint64", "object"),
+ ("int64", "float64", "float64"),
+ ("uint64", "float64", "float64"),
+ ("uint64", "uint64", "uint64"),
+ ("float64", "float64", "float64"),
+ ("datetime64[ns]", "int64", "object"),
+ ("datetime64[ns]", "uint64", "object"),
+ ("datetime64[ns]", "float64", "object"),
+ ("datetime64[ns, CET]", "int64", "object"),
+ ("datetime64[ns, CET]", "uint64", "object"),
+ ("datetime64[ns, CET]", "float64", "object"),
+ ("Period[D]", "int64", "object"),
+ ("Period[D]", "uint64", "object"),
+ ("Period[D]", "float64", "object"),
+ ],
+)
+@pytest.mark.parametrize("names", [("foo", "foo", "foo"), ("foo", "bar", None)])
+def test_union_dtypes(left, right, expected, names):
+ left = pandas_dtype(left)
+ right = pandas_dtype(right)
+ a = Index([], dtype=left, name=names[0])
+ b = Index([], dtype=right, name=names[1])
+ result = a.union(b)
+ assert result.dtype == expected
+ assert result.name == names[2]
+
+ # Testing name retention
+ # TODO: pin down desired dtype; do we want it to be commutative?
+ result = a.intersection(b)
+ assert result.name == names[2]
+
+
+@pytest.mark.parametrize("values", [[1, 2, 2, 3], [3, 3]])
+def test_intersection_duplicates(values):
+ # GH#31326
+ a = Index(values)
+ b = Index([3, 3])
+ result = a.intersection(b)
+ expected = Index([3])
+ tm.assert_index_equal(result, expected)
+
+
+class TestSetOps:
+ # Set operation tests shared by all indexes in the `index` fixture
+ @pytest.mark.parametrize("case", [0.5, "xxx"])
+ @pytest.mark.parametrize(
+ "method", ["intersection", "union", "difference", "symmetric_difference"]
+ )
+ def test_set_ops_error_cases(self, case, method, index):
+ # non-iterable input
+ msg = "Input must be Index or array-like"
+ with pytest.raises(TypeError, match=msg):
+ getattr(index, method)(case)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_intersection_base(self, index):
+ if isinstance(index, CategoricalIndex):
+ pytest.skip(f"Not relevant for {type(index).__name__}")
+
+ first = index[:5].unique()
+ second = index[:3].unique()
+ intersect = first.intersection(second)
+ tm.assert_index_equal(intersect, second)
+
+ if isinstance(index.dtype, DatetimeTZDtype):
+ # The second.values below will drop tz, so the rest of this test
+ # is not applicable.
+ return
+
+ # GH#10149
+ cases = [second.to_numpy(), second.to_series(), second.to_list()]
+ for case in cases:
+ result = first.intersection(case)
+ assert equal_contents(result, second)
+
+ if isinstance(index, MultiIndex):
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ first.intersection([1, 2, 3])
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_union_base(self, index):
+ index = index.unique()
+ first = index[3:]
+ second = index[:5]
+ everything = index
+
+ union = first.union(second)
+ tm.assert_index_equal(union.sort_values(), everything.sort_values())
+
+ if isinstance(index.dtype, DatetimeTZDtype):
+ # The second.values below will drop tz, so the rest of this test
+ # is not applicable.
+ return
+
+ # GH#10149
+ cases = [second.to_numpy(), second.to_series(), second.to_list()]
+ for case in cases:
+ result = first.union(case)
+ assert equal_contents(result, everything)
+
+ if isinstance(index, MultiIndex):
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ first.union([1, 2, 3])
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_difference_base(self, sort, index):
+ first = index[2:]
+ second = index[:4]
+ if index.inferred_type == "boolean":
+ # i think (TODO: be sure) there assumptions baked in about
+ # the index fixture that don't hold here?
+ answer = set(first).difference(set(second))
+ elif isinstance(index, CategoricalIndex):
+ answer = []
+ else:
+ answer = index[4:]
+ result = first.difference(second, sort)
+ assert equal_contents(result, answer)
+
+ # GH#10149
+ cases = [second.to_numpy(), second.to_series(), second.to_list()]
+ for case in cases:
+ result = first.difference(case, sort)
+ assert equal_contents(result, answer)
+
+ if isinstance(index, MultiIndex):
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ first.difference([1, 2, 3], sort)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_symmetric_difference(self, index, using_infer_string, request):
+ if (
+ using_infer_string
+ and index.dtype == "object"
+ and index.inferred_type == "string"
+ ):
+ request.applymarker(pytest.mark.xfail(reason="TODO: infer_string"))
+ if isinstance(index, CategoricalIndex):
+ pytest.skip(f"Not relevant for {type(index).__name__}")
+ if len(index) < 2:
+ pytest.skip("Too few values for test")
+ if index[0] in index[1:] or index[-1] in index[:-1]:
+ # index fixture has e.g. an index of bools that does not satisfy this,
+ # another with [0, 0, 1, 1, 2, 2]
+ pytest.skip("Index values no not satisfy test condition.")
+
+ first = index[1:]
+ second = index[:-1]
+ answer = index[[0, -1]]
+ result = first.symmetric_difference(second)
+ tm.assert_index_equal(result.sort_values(), answer.sort_values())
+
+ # GH#10149
+ cases = [second.to_numpy(), second.to_series(), second.to_list()]
+ for case in cases:
+ result = first.symmetric_difference(case)
+ assert equal_contents(result, answer)
+
+ if isinstance(index, MultiIndex):
+ msg = "other must be a MultiIndex or a list of tuples"
+ with pytest.raises(TypeError, match=msg):
+ first.symmetric_difference([1, 2, 3])
+
+ @pytest.mark.parametrize(
+ "fname, sname, expected_name",
+ [
+ ("A", "A", "A"),
+ ("A", "B", None),
+ ("A", None, None),
+ (None, "B", None),
+ (None, None, None),
+ ],
+ )
+ def test_corner_union(self, index_flat, fname, sname, expected_name):
+ # GH#9943, GH#9862
+ # Test unions with various name combinations
+ # Do not test MultiIndex or repeats
+ if not index_flat.is_unique:
+ index = index_flat.unique()
+ else:
+ index = index_flat
+
+ # Test copy.union(copy)
+ first = index.copy().set_names(fname)
+ second = index.copy().set_names(sname)
+ union = first.union(second)
+ expected = index.copy().set_names(expected_name)
+ tm.assert_index_equal(union, expected)
+
+ # Test copy.union(empty)
+ first = index.copy().set_names(fname)
+ second = index.drop(index).set_names(sname)
+ union = first.union(second)
+ expected = index.copy().set_names(expected_name)
+ tm.assert_index_equal(union, expected)
+
+ # Test empty.union(copy)
+ first = index.drop(index).set_names(fname)
+ second = index.copy().set_names(sname)
+ union = first.union(second)
+ expected = index.copy().set_names(expected_name)
+ tm.assert_index_equal(union, expected)
+
+ # Test empty.union(empty)
+ first = index.drop(index).set_names(fname)
+ second = index.drop(index).set_names(sname)
+ union = first.union(second)
+ expected = index.drop(index).set_names(expected_name)
+ tm.assert_index_equal(union, expected)
+
+ @pytest.mark.parametrize(
+ "fname, sname, expected_name",
+ [
+ ("A", "A", "A"),
+ ("A", "B", None),
+ ("A", None, None),
+ (None, "B", None),
+ (None, None, None),
+ ],
+ )
+ def test_union_unequal(self, index_flat, fname, sname, expected_name):
+ if not index_flat.is_unique:
+ index = index_flat.unique()
+ else:
+ index = index_flat
+
+ # test copy.union(subset) - need sort for unicode and string
+ first = index.copy().set_names(fname)
+ second = index[1:].set_names(sname)
+ union = first.union(second).sort_values()
+ expected = index.set_names(expected_name).sort_values()
+ tm.assert_index_equal(union, expected)
+
+ @pytest.mark.parametrize(
+ "fname, sname, expected_name",
+ [
+ ("A", "A", "A"),
+ ("A", "B", None),
+ ("A", None, None),
+ (None, "B", None),
+ (None, None, None),
+ ],
+ )
+ def test_corner_intersect(self, index_flat, fname, sname, expected_name):
+ # GH#35847
+ # Test intersections with various name combinations
+ if not index_flat.is_unique:
+ index = index_flat.unique()
+ else:
+ index = index_flat
+
+ # Test copy.intersection(copy)
+ first = index.copy().set_names(fname)
+ second = index.copy().set_names(sname)
+ intersect = first.intersection(second)
+ expected = index.copy().set_names(expected_name)
+ tm.assert_index_equal(intersect, expected)
+
+ # Test copy.intersection(empty)
+ first = index.copy().set_names(fname)
+ second = index.drop(index).set_names(sname)
+ intersect = first.intersection(second)
+ expected = index.drop(index).set_names(expected_name)
+ tm.assert_index_equal(intersect, expected)
+
+ # Test empty.intersection(copy)
+ first = index.drop(index).set_names(fname)
+ second = index.copy().set_names(sname)
+ intersect = first.intersection(second)
+ expected = index.drop(index).set_names(expected_name)
+ tm.assert_index_equal(intersect, expected)
+
+ # Test empty.intersection(empty)
+ first = index.drop(index).set_names(fname)
+ second = index.drop(index).set_names(sname)
+ intersect = first.intersection(second)
+ expected = index.drop(index).set_names(expected_name)
+ tm.assert_index_equal(intersect, expected)
+
+ @pytest.mark.parametrize(
+ "fname, sname, expected_name",
+ [
+ ("A", "A", "A"),
+ ("A", "B", None),
+ ("A", None, None),
+ (None, "B", None),
+ (None, None, None),
+ ],
+ )
+ def test_intersect_unequal(self, index_flat, fname, sname, expected_name):
+ if not index_flat.is_unique:
+ index = index_flat.unique()
+ else:
+ index = index_flat
+
+ # test copy.intersection(subset) - need sort for unicode and string
+ first = index.copy().set_names(fname)
+ second = index[1:].set_names(sname)
+ intersect = first.intersection(second).sort_values()
+ expected = index[1:].set_names(expected_name).sort_values()
+ tm.assert_index_equal(intersect, expected)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_intersection_name_retention_with_nameless(self, index):
+ if isinstance(index, MultiIndex):
+ index = index.rename(list(range(index.nlevels)))
+ else:
+ index = index.rename("foo")
+
+ other = np.asarray(index)
+
+ result = index.intersection(other)
+ assert result.name == index.name
+
+ # empty other, same dtype
+ result = index.intersection(other[:0])
+ assert result.name == index.name
+
+ # empty `self`
+ result = index[:0].intersection(other)
+ assert result.name == index.name
+
+ def test_difference_preserves_type_empty(self, index, sort):
+ # GH#20040
+ # If taking difference of a set and itself, it
+ # needs to preserve the type of the index
+ if not index.is_unique:
+ pytest.skip("Not relevant since index is not unique")
+ result = index.difference(index, sort=sort)
+ expected = index[:0]
+ tm.assert_index_equal(result, expected, exact=True)
+
+ def test_difference_name_retention_equals(self, index, names):
+ if isinstance(index, MultiIndex):
+ names = [[x] * index.nlevels for x in names]
+ index = index.rename(names[0])
+ other = index.rename(names[1])
+
+ assert index.equals(other)
+
+ result = index.difference(other)
+ expected = index[:0].rename(names[2])
+ tm.assert_index_equal(result, expected)
+
+ def test_intersection_difference_match_empty(self, index, sort):
+ # GH#20040
+ # Test that the intersection of an index with an
+ # empty index produces the same index as the difference
+ # of an index with itself. Test for all types
+ if not index.is_unique:
+ pytest.skip("Not relevant because index is not unique")
+ inter = index.intersection(index[:0])
+ diff = index.difference(index, sort=sort)
+ tm.assert_index_equal(inter, diff, exact=True)
+
+
+@pytest.mark.filterwarnings("ignore:invalid value encountered in cast:RuntimeWarning")
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+@pytest.mark.parametrize(
+ "method", ["intersection", "union", "difference", "symmetric_difference"]
+)
+def test_setop_with_categorical(index_flat, sort, method, using_infer_string):
+ # MultiIndex tested separately in tests.indexes.multi.test_setops
+ index = index_flat
+
+ other = index.astype("category")
+ exact = "equiv" if isinstance(index, RangeIndex) else True
+
+ result = getattr(index, method)(other, sort=sort)
+ expected = getattr(index, method)(index, sort=sort)
+ if (
+ using_infer_string
+ and index.empty
+ and method in ("union", "symmetric_difference")
+ ):
+ expected = expected.astype("category")
+ tm.assert_index_equal(result, expected, exact=exact)
+
+ result = getattr(index, method)(other[:5], sort=sort)
+ expected = getattr(index, method)(index[:5], sort=sort)
+ if (
+ using_infer_string
+ and index.empty
+ and method in ("union", "symmetric_difference")
+ ):
+ expected = expected.astype("category")
+ tm.assert_index_equal(result, expected, exact=exact)
+
+
+def test_intersection_duplicates_all_indexes(index):
+ # GH#38743
+ if index.empty:
+ # No duplicates in empty indexes
+ pytest.skip("Not relevant for empty Index")
+
+ idx = index
+ idx_non_unique = idx[[0, 0, 1, 2]]
+
+ assert idx.intersection(idx_non_unique).equals(idx_non_unique.intersection(idx))
+ assert idx.intersection(idx_non_unique).is_unique
+
+
+def test_union_duplicate_index_subsets_of_each_other(
+ any_dtype_for_small_pos_integer_indexes,
+):
+ # GH#31326
+ dtype = any_dtype_for_small_pos_integer_indexes
+ a = Index([1, 2, 2, 3], dtype=dtype)
+ b = Index([3, 3, 4], dtype=dtype)
+
+ expected = Index([1, 2, 2, 3, 3, 4], dtype=dtype)
+ if isinstance(a, CategoricalIndex):
+ expected = Index([1, 2, 2, 3, 3, 4])
+ result = a.union(b)
+ tm.assert_index_equal(result, expected)
+ result = a.union(b, sort=False)
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_with_duplicate_index_and_non_monotonic(
+ any_dtype_for_small_pos_integer_indexes,
+):
+ # GH#36289
+ dtype = any_dtype_for_small_pos_integer_indexes
+ a = Index([1, 0, 0], dtype=dtype)
+ b = Index([0, 1], dtype=dtype)
+ expected = Index([0, 0, 1], dtype=dtype)
+
+ result = a.union(b)
+ tm.assert_index_equal(result, expected)
+
+ result = b.union(a)
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_duplicate_index_different_dtypes():
+ # GH#36289
+ a = Index([1, 2, 2, 3])
+ b = Index(["1", "0", "0"])
+ expected = Index([1, 2, 2, 3, "1", "0", "0"])
+ result = a.union(b, sort=False)
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_same_value_duplicated_in_both():
+ # GH#36289
+ a = Index([0, 0, 1])
+ b = Index([0, 0, 1, 2])
+ result = a.union(b)
+ expected = Index([0, 0, 1, 2])
+ tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize("dup", [1, np.nan])
+def test_union_nan_in_both(dup):
+ # GH#36289
+ a = Index([np.nan, 1, 2, 2])
+ b = Index([np.nan, dup, 1, 2])
+ result = a.union(b, sort=False)
+ expected = Index([np.nan, dup, 1.0, 2.0, 2.0])
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_rangeindex_sort_true():
+ # GH 53490
+ idx1 = RangeIndex(1, 100, 6)
+ idx2 = RangeIndex(1, 50, 3)
+ result = idx1.union(idx2, sort=True)
+ expected = Index(
+ [
+ 1,
+ 4,
+ 7,
+ 10,
+ 13,
+ 16,
+ 19,
+ 22,
+ 25,
+ 28,
+ 31,
+ 34,
+ 37,
+ 40,
+ 43,
+ 46,
+ 49,
+ 55,
+ 61,
+ 67,
+ 73,
+ 79,
+ 85,
+ 91,
+ 97,
+ ]
+ )
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_with_duplicate_index_not_subset_and_non_monotonic(
+ any_dtype_for_small_pos_integer_indexes,
+):
+ # GH#36289
+ dtype = any_dtype_for_small_pos_integer_indexes
+ a = Index([1, 0, 2], dtype=dtype)
+ b = Index([0, 0, 1], dtype=dtype)
+ expected = Index([0, 0, 1, 2], dtype=dtype)
+ if isinstance(a, CategoricalIndex):
+ expected = Index([0, 0, 1, 2])
+
+ result = a.union(b)
+ tm.assert_index_equal(result, expected)
+
+ result = b.union(a)
+ tm.assert_index_equal(result, expected)
+
+
+def test_union_int_categorical_with_nan():
+ ci = CategoricalIndex([1, 2, np.nan])
+ assert ci.categories.dtype.kind == "i"
+
+ idx = Index([1, 2])
+
+ result = idx.union(ci)
+ expected = Index([1, 2, np.nan], dtype=np.float64)
+ tm.assert_index_equal(result, expected)
+
+ result = ci.union(idx)
+ tm.assert_index_equal(result, expected)
+
+
+class TestSetOpsUnsorted:
+ # These may eventually belong in a dtype-specific test_setops, or
+ # parametrized over a more general fixture
+ def test_intersect_str_dates(self):
+ dt_dates = [datetime(2012, 2, 9), datetime(2012, 2, 22)]
+
+ index1 = Index(dt_dates, dtype=object)
+ index2 = Index(["aa"], dtype=object)
+ result = index2.intersection(index1)
+
+ expected = Index([], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ def test_intersection(self, index, sort):
+ first = index[:20]
+ second = index[:10]
+ intersect = first.intersection(second, sort=sort)
+ if sort in (None, False):
+ tm.assert_index_equal(intersect.sort_values(), second.sort_values())
+ else:
+ tm.assert_index_equal(intersect, second)
+
+ # Corner cases
+ inter = first.intersection(first, sort=sort)
+ assert inter is not first
+
+ @pytest.mark.parametrize(
+ "index2_name,keeps_name",
+ [
+ ("index", True), # preserve same name
+ ("other", False), # drop diff names
+ (None, False),
+ ],
+ )
+ def test_intersection_name_preservation(self, index2_name, keeps_name, sort):
+ index2 = Index([3, 4, 5, 6, 7], name=index2_name)
+ index1 = Index([1, 2, 3, 4, 5], name="index")
+ expected = Index([3, 4, 5])
+ result = index1.intersection(index2, sort)
+
+ if keeps_name:
+ expected.name = "index"
+
+ assert result.name == expected.name
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ @pytest.mark.parametrize(
+ "first_name,second_name,expected_name",
+ [("A", "A", "A"), ("A", "B", None), (None, "B", None)],
+ )
+ def test_intersection_name_preservation2(
+ self, index, first_name, second_name, expected_name, sort
+ ):
+ first = index[5:20]
+ second = index[:10]
+ first.name = first_name
+ second.name = second_name
+ intersect = first.intersection(second, sort=sort)
+ assert intersect.name == expected_name
+
+ def test_chained_union(self, sort):
+ # Chained unions handles names correctly
+ i1 = Index([1, 2], name="i1")
+ i2 = Index([5, 6], name="i2")
+ i3 = Index([3, 4], name="i3")
+ union = i1.union(i2.union(i3, sort=sort), sort=sort)
+ expected = i1.union(i2, sort=sort).union(i3, sort=sort)
+ tm.assert_index_equal(union, expected)
+
+ j1 = Index([1, 2], name="j1")
+ j2 = Index([], name="j2")
+ j3 = Index([], name="j3")
+ union = j1.union(j2.union(j3, sort=sort), sort=sort)
+ expected = j1.union(j2, sort=sort).union(j3, sort=sort)
+ tm.assert_index_equal(union, expected)
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ def test_union(self, index, sort):
+ first = index[5:20]
+ second = index[:10]
+ everything = index[:20]
+
+ union = first.union(second, sort=sort)
+ if sort in (None, False):
+ tm.assert_index_equal(union.sort_values(), everything.sort_values())
+ else:
+ tm.assert_index_equal(union, everything)
+
+ @pytest.mark.parametrize("klass", [np.array, Series, list])
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ def test_union_from_iterables(self, index, klass, sort):
+ # GH#10149
+ first = index[5:20]
+ second = index[:10]
+ everything = index[:20]
+
+ case = klass(second.values)
+ result = first.union(case, sort=sort)
+ if sort in (None, False):
+ tm.assert_index_equal(result.sort_values(), everything.sort_values())
+ else:
+ tm.assert_index_equal(result, everything)
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ def test_union_identity(self, index, sort):
+ first = index[5:20]
+
+ union = first.union(first, sort=sort)
+ # GH#63169 - identity is not preserved to prevent shared mutable state
+ assert union is not first
+
+ # This should no longer be the same object, since [] is not consistent,
+ # both objects will be recast to dtype('O')
+ union = first.union(Index([], dtype=first.dtype), sort=sort)
+ assert union is not first
+
+ union = Index([], dtype=first.dtype).union(first, sort=sort)
+ assert union is not first
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ @pytest.mark.parametrize("second_name,expected", [(None, None), ("name", "name")])
+ def test_difference_name_preservation(self, index, second_name, expected, sort):
+ first = index[5:20]
+ second = index[:10]
+ answer = index[10:20]
+
+ first.name = "name"
+ second.name = second_name
+ result = first.difference(second, sort=sort)
+
+ if sort is True:
+ tm.assert_index_equal(result, answer)
+ else:
+ answer.name = second_name
+ tm.assert_index_equal(result.sort_values(), answer.sort_values())
+
+ if expected is None:
+ assert result.name is None
+ else:
+ assert result.name == expected
+
+ def test_difference_empty_arg(self, index, sort):
+ first = index.copy()
+ first = first[5:20]
+ first.name = "name"
+ result = first.difference([], sort)
+ expected = index[5:20].unique()
+ expected.name = "name"
+ tm.assert_index_equal(result, expected)
+
+ def test_difference_should_not_compare(self):
+ # GH 55113
+ left = Index([1, 1])
+ right = Index([True])
+ result = left.difference(right)
+ expected = Index([1])
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ def test_difference_identity(self, index, sort):
+ first = index[5:20]
+ first.name = "name"
+ result = first.difference(first, sort)
+
+ assert len(result) == 0
+ assert result.name == first.name
+
+ @pytest.mark.parametrize("index", ["string"], indirect=True)
+ def test_difference_sort(self, index, sort):
+ first = index[5:20]
+ second = index[:10]
+
+ result = first.difference(second, sort)
+ expected = index[10:20]
+
+ if sort is None:
+ expected = expected.sort_values()
+
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("opname", ["difference", "symmetric_difference"])
+ def test_difference_incomparable(self, opname):
+ a = Index([3, Timestamp("2000"), 1])
+ b = Index([2, Timestamp("1999"), 1])
+ op = operator.methodcaller(opname, b)
+
+ with tm.assert_produces_warning(RuntimeWarning, match="not supported between"):
+ # sort=None, the default
+ result = op(a)
+ expected = Index([3, Timestamp("2000"), 2, Timestamp("1999")])
+ if opname == "difference":
+ expected = expected[:2]
+ tm.assert_index_equal(result, expected)
+
+ # sort=False
+ op = operator.methodcaller(opname, b, sort=False)
+ result = op(a)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("opname", ["difference", "symmetric_difference"])
+ def test_difference_incomparable_true(self, opname):
+ a = Index([3, Timestamp("2000"), 1])
+ b = Index([2, Timestamp("1999"), 1])
+ op = operator.methodcaller(opname, b, sort=True)
+
+ msg = "'<' not supported between instances of 'Timestamp' and 'int'"
+ with pytest.raises(TypeError, match=msg):
+ op(a)
+
+ def test_symmetric_difference_mi(self, sort):
+ index1 = MultiIndex.from_tuples(zip(["foo", "bar", "baz"], [1, 2, 3]))
+ index2 = MultiIndex.from_tuples([("foo", 1), ("bar", 3)])
+ result = index1.symmetric_difference(index2, sort=sort)
+ expected = MultiIndex.from_tuples([("bar", 2), ("baz", 3), ("bar", 3)])
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "index2,expected",
+ [
+ ([0, 1, np.nan], [2.0, 3.0, 0.0]),
+ ([0, 1], [np.nan, 2.0, 3.0, 0.0]),
+ ],
+ )
+ def test_symmetric_difference_missing(self, index2, expected, sort):
+ index2 = Index(index2)
+ expected = Index(expected)
+ # GH#13514 change: {nan} - {nan} == {}
+ # (GH#6444, sorting of nans, is no longer an issue)
+ index1 = Index([1, np.nan, 2, 3])
+
+ result = index1.symmetric_difference(index2, sort=sort)
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+
+ def test_symmetric_difference_non_index(self, sort):
+ index1 = Index([1, 2, 3, 4], name="index1")
+ index2 = np.array([2, 3, 4, 5])
+ expected = Index([1, 5], name="index1")
+ result = index1.symmetric_difference(index2, sort=sort)
+ if sort in (None, True):
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_index_equal(result.sort_values(), expected)
+ assert result.name == "index1"
+
+ result = index1.symmetric_difference(index2, result_name="new_name", sort=sort)
+ expected.name = "new_name"
+ if sort in (None, True):
+ tm.assert_index_equal(result, expected)
+ else:
+ tm.assert_index_equal(result.sort_values(), expected)
+ assert result.name == "new_name"
+
+ def test_union_ea_dtypes(self, any_numeric_ea_and_arrow_dtype):
+ # GH#51365
+ idx = Index([1, 2, 3], dtype=any_numeric_ea_and_arrow_dtype)
+ idx2 = Index([3, 4, 5], dtype=any_numeric_ea_and_arrow_dtype)
+ result = idx.union(idx2)
+ expected = Index([1, 2, 3, 4, 5], dtype=any_numeric_ea_and_arrow_dtype)
+ tm.assert_index_equal(result, expected)
+
+ def test_union_string_array(self, any_string_dtype):
+ idx1 = Index(["a"], dtype=any_string_dtype)
+ idx2 = Index(["b"], dtype=any_string_dtype)
+ result = idx1.union(idx2)
+ expected = Index(["a", "b"], dtype=any_string_dtype)
+ tm.assert_index_equal(result, expected)
+
+ @td.skip_if_no("pyarrow")
+ def test_union_pyarrow_timestamp(self):
+ # GH#58421
+ left = Index(["2020-01-01"], dtype="timestamp[s][pyarrow]")
+ right = Index(["2020-01-02"], dtype="timestamp[s][pyarrow]")
+
+ res = left.union(right)
+ expected = Index(["2020-01-01", "2020-01-02"], dtype=left.dtype)
+ tm.assert_index_equal(res, expected)
+
+
+def test_intersection_mutation_safety():
+ # GH#63169
+ index1 = Index([0, 1], name="original")
+ index2 = Index([0, 1], name="original")
+
+ result = index1.intersection(index2)
+
+ assert result is not index1
+ assert result is not index2
+
+ tm.assert_index_equal(result, index1)
+ assert result.name == "original"
+
+ index1.name = "changed"
+
+ assert result.name == "original"
+ assert index1.name == "changed"
+
+
+def test_union_mutation_safety():
+ # GH#63169
+ index1 = Index([0, 1], name="original")
+ index2 = Index([0, 1], name="original")
+
+ result = index1.union(index2)
+
+ assert result is not index1
+ assert result is not index2
+
+ tm.assert_index_equal(result, index1)
+ assert result.name == "original"
+
+ index1.name = "changed"
+
+ assert result.name == "original"
+ assert index1.name == "changed"
+
+
+def test_union_mutation_safety_other():
+ # GH#63169
+ index1 = Index([0, 1], name="original")
+ index2 = Index([0, 1], name="original")
+
+ result = index1.union(index2)
+
+ assert result is not index2
+
+ tm.assert_index_equal(result, index2)
+ assert result.name == "original"
+
+ index2.name = "changed"
+
+ assert result.name == "original"
+ assert index2.name == "changed"
+
+
+def test_multiindex_intersection_mutation_safety():
+ # GH#63169
+ mi1 = MultiIndex.from_tuples([("a", 1), ("b", 2)], names=["x", "y"])
+ mi2 = MultiIndex.from_tuples([("a", 1), ("b", 2)], names=["x", "y"])
+
+ result = mi1.intersection(mi2)
+ assert result is not mi1
+
+ mi1.names = ["changed1", "changed2"]
+ assert result.names == ["x", "y"]
+
+
+def test_multiindex_union_mutation_safety():
+ # GH#63169
+ mi1 = MultiIndex.from_tuples([("a", 1), ("b", 2)], names=["x", "y"])
+ mi2 = MultiIndex.from_tuples([("a", 1), ("b", 2)], names=["x", "y"])
+
+ result = mi1.union(mi2)
+ assert result is not mi1
+
+ mi1.names = ["changed1", "changed2"]
+ assert result.names == ["x", "y"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_subclass.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_subclass.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8ba8c3090cf24d4b2298aeb9cf0bb9a42e1bb58
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/test_subclass.py
@@ -0,0 +1,41 @@
+"""
+Tests involving custom Index subclasses
+"""
+
+import numpy as np
+
+from pandas import (
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+
+
+class CustomIndex(Index):
+ def __new__(cls, data, name=None):
+ # assert that this index class cannot hold strings
+ if any(isinstance(val, str) for val in data):
+ raise TypeError("CustomIndex cannot hold strings")
+
+ if name is None and hasattr(data, "name"):
+ name = data.name
+ data = np.array(data, dtype="O")
+
+ return cls._simple_new(data, name)
+
+
+def test_insert_fallback_to_base_index():
+ # https://github.com/pandas-dev/pandas/issues/47071
+
+ idx = CustomIndex([1, 2, 3])
+ result = idx.insert(0, "string")
+ expected = Index(["string", 1, 2, 3], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((2, 3)),
+ columns=idx,
+ index=Index([1, 2], name="string"),
+ )
+ result = df.reset_index()
+ tm.assert_index_equal(result.columns, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..5166cadae499e44a6dff420580c96043569b839b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_astype.py
@@ -0,0 +1,181 @@
+from datetime import timedelta
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Index,
+ NaT,
+ Timedelta,
+ TimedeltaIndex,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import TimedeltaArray
+
+
+class TestTimedeltaIndex:
+ def test_astype_object(self):
+ idx = timedelta_range(start="1 days", periods=4, freq="D", name="idx")
+ expected_list = [
+ Timedelta("1 days"),
+ Timedelta("2 days"),
+ Timedelta("3 days"),
+ Timedelta("4 days"),
+ ]
+ result = idx.astype(object)
+ expected = Index(expected_list, dtype=object, name="idx")
+ tm.assert_index_equal(result, expected)
+ assert idx.tolist() == expected_list
+
+ def test_astype_object_with_nat(self):
+ idx = TimedeltaIndex(
+ [timedelta(days=1), timedelta(days=2), NaT, timedelta(days=4)], name="idx"
+ )
+ expected_list = [
+ Timedelta("1 days"),
+ Timedelta("2 days"),
+ NaT,
+ Timedelta("4 days"),
+ ]
+ result = idx.astype(object)
+ expected = Index(expected_list, dtype=object, name="idx")
+ tm.assert_index_equal(result, expected)
+ assert idx.tolist() == expected_list
+
+ def test_astype(self, using_infer_string):
+ # GH 13149, GH 13209
+ idx = TimedeltaIndex([1e14, "NaT", NaT, np.nan], name="idx")
+
+ result = idx.astype(object)
+ expected = Index(
+ [Timedelta("1 days 03:46:40")] + [NaT] * 3, dtype=object, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = idx.astype(np.int64)
+ expected = Index(
+ [100000000000000] + [-9223372036854775808] * 3, dtype=np.int64, name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+
+ result = idx.astype(str)
+ if using_infer_string:
+ expected = Index(
+ [str(x) if x is not NaT else None for x in idx], name="idx", dtype="str"
+ )
+ else:
+ expected = Index([str(x) for x in idx], name="idx", dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ rng = timedelta_range("1 days", periods=10)
+ result = rng.astype("i8")
+ tm.assert_index_equal(result, Index(rng.asi8))
+ tm.assert_numpy_array_equal(rng.asi8, result.values)
+
+ def test_astype_uint(self):
+ arr = timedelta_range("1h", periods=2)
+
+ with pytest.raises(TypeError, match=r"Do obj.astype\('int64'\)"):
+ arr.astype("uint64")
+ with pytest.raises(TypeError, match=r"Do obj.astype\('int64'\)"):
+ arr.astype("uint32")
+
+ def test_astype_timedelta64(self):
+ # GH 13149, GH 13209
+ idx = TimedeltaIndex([1e14, "NaT", NaT, np.nan])
+
+ msg = (
+ r"Cannot convert from timedelta64\[ns\] to timedelta64. "
+ "Supported resolutions are 's', 'ms', 'us', 'ns'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx.astype("timedelta64")
+
+ result = idx.astype("timedelta64[ns]")
+ tm.assert_index_equal(result, idx)
+ assert result is not idx
+
+ result = idx.astype("timedelta64[ns]", copy=False)
+ tm.assert_index_equal(result, idx)
+ assert result is idx
+
+ def test_astype_to_td64d_raises(self, index_or_series):
+ # We don't support "D" reso
+ scalar = Timedelta(days=31)
+ td = index_or_series(
+ [scalar, scalar, scalar + timedelta(minutes=5, seconds=3), NaT],
+ dtype="m8[ns]",
+ )
+ msg = (
+ r"Cannot convert from timedelta64\[ns\] to timedelta64\[D\]. "
+ "Supported resolutions are 's', 'ms', 'us', 'ns'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ td.astype("timedelta64[D]")
+
+ def test_astype_ms_to_s(self, index_or_series):
+ scalar = Timedelta(days=31)
+ td = index_or_series(
+ [scalar, scalar, scalar + timedelta(minutes=5, seconds=3), NaT],
+ dtype="m8[ns]",
+ )
+
+ exp_values = np.asarray(td).astype("m8[s]")
+ exp_tda = TimedeltaArray._simple_new(exp_values, dtype=exp_values.dtype)
+ expected = index_or_series(exp_tda)
+ assert expected.dtype == "m8[s]"
+ result = td.astype("timedelta64[s]")
+ tm.assert_equal(result, expected)
+
+ def test_astype_freq_conversion(self):
+ # pre-2.0 td64 astype converted to float64. now for supported units
+ # (s, ms, us, ns) this converts to the requested dtype.
+ # This matches TDA and Series
+ tdi = timedelta_range("1 Day", periods=30)
+
+ res = tdi.astype("m8[s]")
+ exp_values = np.asarray(tdi).astype("m8[s]")
+ exp_tda = TimedeltaArray._simple_new(
+ exp_values, dtype=exp_values.dtype, freq=tdi.freq
+ )
+ expected = Index(exp_tda)
+ assert expected.dtype == "m8[s]"
+ tm.assert_index_equal(res, expected)
+
+ # check this matches Series and TimedeltaArray
+ res = tdi._data.astype("m8[s]")
+ tm.assert_equal(res, expected._values)
+
+ res = tdi.to_series().astype("m8[s]")
+ tm.assert_equal(res._values, expected._values._with_freq(None))
+
+ @pytest.mark.parametrize("dtype", [float, "datetime64", "datetime64[ns]"])
+ def test_astype_raises(self, dtype):
+ # GH 13149, GH 13209
+ idx = TimedeltaIndex([1e14, "NaT", NaT, np.nan])
+ msg = "Cannot cast TimedeltaIndex to dtype"
+ with pytest.raises(TypeError, match=msg):
+ idx.astype(dtype)
+
+ def test_astype_category(self):
+ obj = timedelta_range("1h", periods=2, freq="h")
+
+ result = obj.astype("category")
+ expected = pd.CategoricalIndex([Timedelta("1h"), Timedelta("2h")])
+ tm.assert_index_equal(result, expected)
+
+ result = obj._data.astype("category")
+ expected = expected.values
+ tm.assert_categorical_equal(result, expected)
+
+ def test_astype_array_fallback(self):
+ obj = timedelta_range("1h", periods=2)
+ result = obj.astype(bool)
+ expected = Index(np.array([True, True]))
+ tm.assert_index_equal(result, expected)
+
+ result = obj._data.astype(bool)
+ expected = np.array([True, True])
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_factorize.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_factorize.py
new file mode 100644
index 0000000000000000000000000000000000000000..24ab3888412d08b54543ed22910c67ce9bdf328f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_factorize.py
@@ -0,0 +1,40 @@
+import numpy as np
+
+from pandas import (
+ TimedeltaIndex,
+ factorize,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestTimedeltaIndexFactorize:
+ def test_factorize(self):
+ idx1 = TimedeltaIndex(["1 day", "1 day", "2 day", "2 day", "3 day", "3 day"])
+
+ exp_arr = np.array([0, 0, 1, 1, 2, 2], dtype=np.intp)
+ exp_idx = TimedeltaIndex(["1 day", "2 day", "3 day"])
+
+ arr, idx = idx1.factorize()
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+ assert idx.freq == exp_idx.freq
+
+ arr, idx = idx1.factorize(sort=True)
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, exp_idx)
+ assert idx.freq == exp_idx.freq
+
+ def test_factorize_preserves_freq(self):
+ # GH#38120 freq should be preserved
+ idx3 = timedelta_range("1 day", periods=4, freq="s")
+ exp_arr = np.array([0, 1, 2, 3], dtype=np.intp)
+ arr, idx = idx3.factorize()
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, idx3)
+ assert idx.freq == idx3.freq
+
+ arr, idx = factorize(idx3)
+ tm.assert_numpy_array_equal(arr, exp_arr)
+ tm.assert_index_equal(idx, idx3)
+ assert idx.freq == idx3.freq
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_fillna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_fillna.py
new file mode 100644
index 0000000000000000000000000000000000000000..40aa95d0a46058d2dc3fc5208ca39328d96b23fb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_fillna.py
@@ -0,0 +1,22 @@
+from pandas import (
+ Index,
+ NaT,
+ Timedelta,
+ TimedeltaIndex,
+)
+import pandas._testing as tm
+
+
+class TestFillNA:
+ def test_fillna_timedelta(self):
+ # GH#11343
+ idx = TimedeltaIndex(["1 day", NaT, "3 day"])
+
+ exp = TimedeltaIndex(["1 day", "2 day", "3 day"])
+ tm.assert_index_equal(idx.fillna(Timedelta("2 day")), exp)
+
+ exp = TimedeltaIndex(["1 day", "3 hour", "3 day"])
+ idx.fillna(Timedelta("3 hour"))
+
+ exp = Index([Timedelta("1 day"), "x", Timedelta("3 day")], dtype=object)
+ tm.assert_index_equal(idx.fillna("x"), exp)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_insert.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_insert.py
new file mode 100644
index 0000000000000000000000000000000000000000..88d6333475f064db5ef77319916d0062a18567c2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_insert.py
@@ -0,0 +1,145 @@
+from datetime import timedelta
+
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+
+import pandas as pd
+from pandas import (
+ Index,
+ Timedelta,
+ TimedeltaIndex,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestTimedeltaIndexInsert:
+ def test_insert(self):
+ idx = TimedeltaIndex(["4day", "1day", "2day"], name="idx")
+
+ result = idx.insert(2, timedelta(days=5))
+ exp = TimedeltaIndex(["4day", "1day", "5day", "2day"], name="idx")
+ tm.assert_index_equal(result, exp)
+
+ # insertion of non-datetime should coerce to object index
+ result = idx.insert(1, "inserted")
+ expected = Index(
+ [Timedelta("4day"), "inserted", Timedelta("1day"), Timedelta("2day")],
+ name="idx",
+ )
+ assert not isinstance(result, TimedeltaIndex)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+
+ idx = timedelta_range("1day 00:00:01", periods=3, freq="s", name="idx")
+
+ # preserve freq
+ expected_0 = TimedeltaIndex(
+ ["1day", "1day 00:00:01", "1day 00:00:02", "1day 00:00:03"],
+ name="idx",
+ freq="s",
+ )
+ expected_3 = TimedeltaIndex(
+ ["1day 00:00:01", "1day 00:00:02", "1day 00:00:03", "1day 00:00:04"],
+ name="idx",
+ freq="s",
+ )
+
+ # reset freq to None
+ expected_1_nofreq = TimedeltaIndex(
+ ["1day 00:00:01", "1day 00:00:01", "1day 00:00:02", "1day 00:00:03"],
+ name="idx",
+ freq=None,
+ )
+ expected_3_nofreq = TimedeltaIndex(
+ ["1day 00:00:01", "1day 00:00:02", "1day 00:00:03", "1day 00:00:05"],
+ name="idx",
+ freq=None,
+ )
+
+ cases = [
+ (0, Timedelta("1day"), expected_0),
+ (-3, Timedelta("1day"), expected_0),
+ (3, Timedelta("1day 00:00:04"), expected_3),
+ (1, Timedelta("1day 00:00:01"), expected_1_nofreq),
+ (3, Timedelta("1day 00:00:05"), expected_3_nofreq),
+ ]
+
+ for n, d, expected in cases:
+ result = idx.insert(n, d)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ @pytest.mark.parametrize(
+ "null", [None, np.nan, np.timedelta64("NaT"), pd.NaT, pd.NA]
+ )
+ def test_insert_nat(self, null):
+ # GH 18295 (test missing)
+ idx = timedelta_range("1day", "3day")
+ result = idx.insert(1, null)
+ expected = TimedeltaIndex(["1day", pd.NaT, "2day", "3day"])
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_invalid_na(self):
+ idx = TimedeltaIndex(["4day", "1day", "2day"], name="idx")
+
+ item = np.datetime64("NaT")
+ result = idx.insert(0, item)
+
+ expected = Index([item, *list(idx)], dtype=object, name="idx")
+ tm.assert_index_equal(result, expected)
+
+ # Also works if we pass a different dt64nat object
+ item2 = np.datetime64("NaT")
+ result = idx.insert(0, item2)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "item", [0, np.int64(0), np.float64(0), np.array(0), np.datetime64(456, "us")]
+ )
+ def test_insert_mismatched_types_raises(self, item):
+ # GH#33703 dont cast these to td64
+ tdi = TimedeltaIndex(["4day", "1day", "2day"], name="idx")
+
+ result = tdi.insert(1, item)
+
+ expected = Index(
+ [tdi[0], lib.item_from_zerodim(item), *list(tdi[1:])],
+ dtype=object,
+ name="idx",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_castable_str(self):
+ idx = timedelta_range("1day", "3day")
+
+ result = idx.insert(0, "1 Day")
+
+ expected = TimedeltaIndex([idx[0], *list(idx)])
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_non_castable_str(self):
+ idx = timedelta_range("1day", "3day")
+
+ result = idx.insert(0, "foo")
+
+ expected = Index(["foo", *list(idx)], dtype=object)
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_empty(self):
+ # Corner case inserting with length zero doesn't raise IndexError
+ # GH#33573 for freq preservation
+ idx = timedelta_range("1 Day", periods=3)
+ td = idx[0]
+
+ result = idx[:0].insert(0, td)
+ assert result.freq == "D"
+
+ with pytest.raises(IndexError, match="loc must be an integer between"):
+ result = idx[:0].insert(1, td)
+
+ with pytest.raises(IndexError, match="loc must be an integer between"):
+ result = idx[:0].insert(-1, td)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_repeat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_repeat.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a9b58d1bf322938e9344d0cbacfaa79674fcf0e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_repeat.py
@@ -0,0 +1,34 @@
+import numpy as np
+
+from pandas import (
+ TimedeltaIndex,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestRepeat:
+ def test_repeat(self):
+ index = timedelta_range("1 days", periods=2, freq="D")
+ exp = TimedeltaIndex(["1 days", "1 days", "2 days", "2 days"])
+ for res in [index.repeat(2), np.repeat(index, 2)]:
+ tm.assert_index_equal(res, exp)
+ assert res.freq is None
+
+ index = TimedeltaIndex(["1 days", "NaT", "3 days"])
+ exp = TimedeltaIndex(
+ [
+ "1 days",
+ "1 days",
+ "1 days",
+ "NaT",
+ "NaT",
+ "NaT",
+ "3 days",
+ "3 days",
+ "3 days",
+ ]
+ )
+ for res in [index.repeat(3), np.repeat(index, 3)]:
+ tm.assert_index_equal(res, exp)
+ assert res.freq is None
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_shift.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_shift.py
new file mode 100644
index 0000000000000000000000000000000000000000..166eeb671867f79be62c31423a3ec8bddd70209a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/methods/test_shift.py
@@ -0,0 +1,78 @@
+import pytest
+
+from pandas.errors import NullFrequencyError
+
+import pandas as pd
+from pandas import TimedeltaIndex
+import pandas._testing as tm
+
+
+class TestTimedeltaIndexShift:
+ # -------------------------------------------------------------
+ # TimedeltaIndex.shift is used by __add__/__sub__
+
+ def test_tdi_shift_empty(self):
+ # GH#9903
+ idx = TimedeltaIndex([], name="xxx")
+ tm.assert_index_equal(idx.shift(0, freq="h"), idx)
+ tm.assert_index_equal(idx.shift(3, freq="h"), idx)
+
+ def test_tdi_shift_hours(self):
+ # GH#9903
+ idx = TimedeltaIndex(["5 hours", "6 hours", "9 hours"], name="xxx")
+ tm.assert_index_equal(idx.shift(0, freq="h"), idx)
+ exp = TimedeltaIndex(["8 hours", "9 hours", "12 hours"], name="xxx")
+ tm.assert_index_equal(idx.shift(3, freq="h"), exp)
+ exp = TimedeltaIndex(["2 hours", "3 hours", "6 hours"], name="xxx")
+ tm.assert_index_equal(idx.shift(-3, freq="h"), exp)
+
+ def test_tdi_shift_minutes(self):
+ # GH#9903
+ idx = TimedeltaIndex(["5 hours", "6 hours", "9 hours"], name="xxx")
+ tm.assert_index_equal(idx.shift(0, freq="min"), idx)
+ exp = TimedeltaIndex(["05:03:00", "06:03:00", "9:03:00"], name="xxx")
+ tm.assert_index_equal(idx.shift(3, freq="min"), exp)
+ exp = TimedeltaIndex(["04:57:00", "05:57:00", "8:57:00"], name="xxx")
+ tm.assert_index_equal(idx.shift(-3, freq="min"), exp)
+
+ def test_tdi_shift_int(self):
+ # GH#8083
+ tdi = pd.to_timedelta(range(5), unit="D")
+ trange = tdi._with_freq("infer") + pd.offsets.Hour(1)
+ result = trange.shift(1)
+ expected = TimedeltaIndex(
+ [
+ "1 days 01:00:00",
+ "2 days 01:00:00",
+ "3 days 01:00:00",
+ "4 days 01:00:00",
+ "5 days 01:00:00",
+ ],
+ freq="D",
+ dtype="m8[s]",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_tdi_shift_nonstandard_freq(self):
+ # GH#8083
+ tdi = pd.to_timedelta(range(5), unit="D")
+ trange = tdi._with_freq("infer") + pd.offsets.Hour(1)
+ result = trange.shift(3, freq="2D 1s")
+ expected = TimedeltaIndex(
+ [
+ "6 days 01:00:03",
+ "7 days 01:00:03",
+ "8 days 01:00:03",
+ "9 days 01:00:03",
+ "10 days 01:00:03",
+ ],
+ freq="D",
+ dtype="m8[s]",
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_shift_no_freq(self):
+ # GH#19147
+ tdi = TimedeltaIndex(["1 days 01:00:00", "2 days 01:00:00"], freq=None)
+ with pytest.raises(NullFrequencyError, match="Cannot shift with no freq"):
+ tdi.shift(2)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_arithmetic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_arithmetic.py
new file mode 100644
index 0000000000000000000000000000000000000000..a431e10dc18ab15da0fd07f798d54b6dead26073
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_arithmetic.py
@@ -0,0 +1,51 @@
+# Arithmetic tests for TimedeltaIndex are generally about the result's `freq` attribute.
+# Other cases can be shared in tests.arithmetic.test_timedelta64
+import numpy as np
+
+from pandas import (
+ NaT,
+ Timedelta,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestTimedeltaIndexArithmetic:
+ def test_arithmetic_zero_freq(self):
+ # GH#51575 don't get a .freq with freq.n = 0
+ tdi = timedelta_range(0, periods=100, freq="ns")
+ result = tdi / 2
+ assert result.freq is None
+ expected = tdi[:50].repeat(2)
+ tm.assert_index_equal(result, expected)
+
+ result2 = tdi // 2
+ assert result2.freq is None
+ expected2 = expected
+ tm.assert_index_equal(result2, expected2)
+
+ result3 = tdi * 0
+ assert result3.freq is None
+ expected3 = tdi[:1].repeat(100)
+ tm.assert_index_equal(result3, expected3)
+
+ def test_tdi_division(self, index_or_series):
+ # doc example
+
+ scalar = Timedelta(days=31)
+ td = index_or_series(
+ [scalar, scalar, scalar + Timedelta(minutes=5, seconds=3), NaT],
+ dtype="m8[ns]",
+ )
+
+ result = td / np.timedelta64(1, "D")
+ expected = index_or_series(
+ [31, 31, (31 * 86400 + 5 * 60 + 3) / 86400.0, np.nan]
+ )
+ tm.assert_equal(result, expected)
+
+ result = td / np.timedelta64(1, "s")
+ expected = index_or_series(
+ [31 * 86400, 31 * 86400, 31 * 86400 + 5 * 60 + 3, np.nan]
+ )
+ tm.assert_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0be8f4b3c74c26fd67e7da4dc8fa5850e6596a3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_constructors.py
@@ -0,0 +1,271 @@
+from datetime import timedelta
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ Timedelta,
+ TimedeltaIndex,
+ timedelta_range,
+ to_timedelta,
+)
+import pandas._testing as tm
+from pandas.core.arrays.timedeltas import TimedeltaArray
+
+
+class TestTimedeltaIndex:
+ def test_array_of_dt64_nat_raises(self):
+ # GH#39462
+ nat = np.datetime64("NaT", "ns")
+ arr = np.array([nat], dtype=object)
+
+ msg = "Invalid type for timedelta scalar"
+ with pytest.raises(TypeError, match=msg):
+ TimedeltaIndex(arr)
+
+ with pytest.raises(TypeError, match=msg):
+ TimedeltaArray._from_sequence(arr, dtype="m8[ns]")
+
+ with pytest.raises(TypeError, match=msg):
+ to_timedelta(arr)
+
+ def test_int64_nocopy(self):
+ # GH#23539 check that a copy isn't made when we pass int64 data
+ # and copy=False
+ arr = np.arange(10, dtype=np.int64)
+ tdi = TimedeltaIndex(arr, copy=False)
+ assert tdi._data._ndarray.base is arr
+
+ def test_infer_from_tdi(self):
+ # GH#23539
+ # fast-path for inferring a frequency if the passed data already
+ # has one
+ tdi = timedelta_range("1 second", periods=10**7, freq="1s")
+
+ result = TimedeltaIndex(tdi, freq="infer")
+ assert result.freq == tdi.freq
+
+ # check that inferred_freq was not called by checking that the
+ # value has not been cached
+ assert "inferred_freq" not in getattr(result, "_cache", {})
+
+ def test_infer_from_tdi_mismatch(self):
+ # GH#23539
+ # fast-path for invalidating a frequency if the passed data already
+ # has one and it does not match the `freq` input
+ tdi = timedelta_range("1 second", periods=100, freq="1s")
+
+ msg = (
+ "Inferred frequency .* from passed values does "
+ "not conform to passed frequency"
+ )
+ with pytest.raises(ValueError, match=msg):
+ TimedeltaIndex(tdi, freq="D")
+
+ with pytest.raises(ValueError, match=msg):
+ TimedeltaIndex(tdi._data, freq="D")
+
+ def test_dt64_data_invalid(self):
+ # GH#23539
+ # passing tz-aware DatetimeIndex raises, naive or ndarray[datetime64]
+ # raise as of GH#29794
+ dti = pd.date_range("2016-01-01", periods=3)
+
+ msg = "cannot be converted to timedelta64"
+ with pytest.raises(TypeError, match=msg):
+ TimedeltaIndex(dti.tz_localize("Europe/Brussels"))
+
+ with pytest.raises(TypeError, match=msg):
+ TimedeltaIndex(dti)
+
+ with pytest.raises(TypeError, match=msg):
+ TimedeltaIndex(np.asarray(dti))
+
+ def test_float64_ns_rounded(self):
+ # GH#23539 without specifying a unit, floats are regarded as nanos,
+ # and fractional portions are truncated
+ tdi = TimedeltaIndex([2.3, 9.7])
+ expected = TimedeltaIndex([2, 9])
+ tm.assert_index_equal(tdi, expected)
+
+ # integral floats are non-lossy
+ tdi = TimedeltaIndex([2.0, 9.0])
+ expected = TimedeltaIndex([2, 9])
+ tm.assert_index_equal(tdi, expected)
+
+ # NaNs get converted to NaT
+ tdi = TimedeltaIndex([2.0, np.nan])
+ expected = TimedeltaIndex([Timedelta(nanoseconds=2), pd.NaT])
+ tm.assert_index_equal(tdi, expected)
+
+ def test_float64_unit_conversion(self):
+ # GH#23539
+ tdi = to_timedelta([1.5, 2.25], unit="D")
+ expected = TimedeltaIndex(
+ [Timedelta(days=1.5), Timedelta(days=2.25)], dtype="m8[ns]"
+ )
+ tm.assert_index_equal(tdi, expected)
+
+ def test_construction_base_constructor(self):
+ arr = [Timedelta("1 days"), pd.NaT, Timedelta("3 days")]
+ tm.assert_index_equal(pd.Index(arr), TimedeltaIndex(arr))
+ tm.assert_index_equal(pd.Index(np.array(arr)), TimedeltaIndex(np.array(arr)))
+
+ arr = [np.nan, pd.NaT, Timedelta("1 days")]
+ tm.assert_index_equal(pd.Index(arr), TimedeltaIndex(arr))
+ tm.assert_index_equal(pd.Index(np.array(arr)), TimedeltaIndex(np.array(arr)))
+
+ def test_constructor(self):
+ expected = TimedeltaIndex(
+ [
+ "1 days",
+ "1 days 00:00:05",
+ "2 days",
+ "2 days 00:00:02",
+ "0 days 00:00:03",
+ ]
+ )
+ result = TimedeltaIndex(
+ [
+ "1 days",
+ "1 days, 00:00:05",
+ np.timedelta64(2, "D"),
+ timedelta(days=2, seconds=2),
+ pd.offsets.Second(3),
+ ]
+ )
+ tm.assert_index_equal(result, expected)
+
+ def test_constructor_iso(self):
+ # GH #21877
+ expected = timedelta_range("1s", periods=9, freq="s")
+ durations = [f"P0DT0H0M{i}S" for i in range(1, 10)]
+ result = to_timedelta(durations)
+ tm.assert_index_equal(result, expected)
+
+ def test_timedelta_range_fractional_period(self):
+ msg = "periods must be an integer"
+ with pytest.raises(TypeError, match=msg):
+ timedelta_range("1 days", periods=10.5)
+
+ def test_constructor_coverage(self):
+ msg = "periods must be an integer, got foo"
+ with pytest.raises(TypeError, match=msg):
+ timedelta_range(start="1 days", periods="foo", freq="D")
+
+ msg = (
+ r"TimedeltaIndex\(\.\.\.\) must be called with a collection of some kind, "
+ "'1 days' was passed"
+ )
+ with pytest.raises(TypeError, match=msg):
+ TimedeltaIndex("1 days")
+
+ # generator expression
+ gen = (timedelta(i) for i in range(10))
+ result = TimedeltaIndex(gen)
+ expected = TimedeltaIndex([timedelta(i) for i in range(10)])
+ tm.assert_index_equal(result, expected)
+
+ # NumPy string array
+ strings = np.array(["1 days", "2 days", "3 days"])
+ result = TimedeltaIndex(strings)
+ expected = to_timedelta([1, 2, 3], unit="D").as_unit("us")
+ tm.assert_index_equal(result, expected)
+
+ from_ints = TimedeltaIndex(expected.as_unit("ns").asi8)
+ tm.assert_index_equal(from_ints, expected.as_unit("ns"))
+
+ # non-conforming freq
+ msg = (
+ "Inferred frequency None from passed values does not conform to "
+ "passed frequency D"
+ )
+ with pytest.raises(ValueError, match=msg):
+ TimedeltaIndex(["1 days", "2 days", "4 days"], freq="D")
+
+ msg = (
+ "Of the four parameters: start, end, periods, and freq, exactly "
+ "three must be specified"
+ )
+ with pytest.raises(ValueError, match=msg):
+ timedelta_range(periods=10, freq="D")
+
+ def test_constructor_name(self):
+ idx = timedelta_range(start="1 days", periods=1, freq="D", name="TEST")
+ assert idx.name == "TEST"
+
+ # GH10025
+ idx2 = TimedeltaIndex(idx, name="something else")
+ assert idx2.name == "something else"
+
+ def test_constructor_no_precision_raises(self):
+ # GH-24753, GH-24739
+
+ msg = "with no precision is not allowed"
+ with pytest.raises(ValueError, match=msg):
+ TimedeltaIndex(["2000"], dtype="timedelta64")
+
+ msg = "The 'timedelta64' dtype has no unit. Please pass in"
+ with pytest.raises(ValueError, match=msg):
+ pd.Index(["2000"], dtype="timedelta64")
+
+ def test_constructor_wrong_precision_raises(self):
+ msg = "Supported timedelta64 resolutions are 's', 'ms', 'us', 'ns'"
+ with pytest.raises(ValueError, match=msg):
+ TimedeltaIndex(["2000"], dtype="timedelta64[D]")
+
+ # "timedelta64[us]" was unsupported pre-2.0, but now this works.
+ tdi = TimedeltaIndex(["2000"], dtype="timedelta64[us]")
+ assert tdi.dtype == "m8[us]"
+
+ def test_explicit_none_freq(self):
+ # Explicitly passing freq=None is respected
+ tdi = timedelta_range(1, periods=5)
+ assert tdi.freq is not None
+
+ result = TimedeltaIndex(tdi, freq=None)
+ assert result.freq is None
+
+ result = TimedeltaIndex(tdi._data, freq=None)
+ assert result.freq is None
+
+ def test_from_categorical(self):
+ tdi = timedelta_range(1, periods=5)
+
+ cat = pd.Categorical(tdi)
+
+ result = TimedeltaIndex(cat)
+ tm.assert_index_equal(result, tdi)
+
+ ci = pd.CategoricalIndex(tdi)
+ result = TimedeltaIndex(ci)
+ tm.assert_index_equal(result, tdi)
+
+ @pytest.mark.parametrize(
+ "unit,unit_depr",
+ [
+ ("W", "w"),
+ ("D", "d"),
+ ("min", "MIN"),
+ ("s", "S"),
+ ("h", "H"),
+ ("ms", "MS"),
+ ("us", "US"),
+ ],
+ )
+ def test_unit_deprecated(self, unit, unit_depr):
+ # GH#52536, GH#59051
+ msg = f"'{unit_depr}' is deprecated and will be removed in a future version."
+
+ expected = TimedeltaIndex([f"1{unit}", f"2{unit}"])
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = TimedeltaIndex([f"1{unit_depr}", f"2{unit_depr}"])
+ tm.assert_index_equal(result, expected)
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ tdi = to_timedelta([1, 2], unit=unit_depr)
+ exp_unit = unit if unit in ["s", "ms", "us"] else "s"
+ tm.assert_index_equal(tdi, expected.as_unit(exp_unit))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_delete.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_delete.py
new file mode 100644
index 0000000000000000000000000000000000000000..f49af7cd0befda87b591ce3f1d12c00f5b012635
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_delete.py
@@ -0,0 +1,71 @@
+from pandas import (
+ TimedeltaIndex,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestTimedeltaIndexDelete:
+ def test_delete(self):
+ idx = timedelta_range(start="1 Days", periods=5, freq="D", name="idx")
+
+ # preserve freq
+ expected_0 = timedelta_range(start="2 Days", periods=4, freq="D", name="idx")
+ expected_4 = timedelta_range(start="1 Days", periods=4, freq="D", name="idx")
+
+ # reset freq to None
+ expected_1 = TimedeltaIndex(
+ ["1 day", "3 day", "4 day", "5 day"], freq=None, name="idx"
+ )
+
+ cases = {
+ 0: expected_0,
+ -5: expected_0,
+ -1: expected_4,
+ 4: expected_4,
+ 1: expected_1,
+ }
+ for n, expected in cases.items():
+ result = idx.delete(n)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ with tm.external_error_raised((IndexError, ValueError)):
+ # either depending on numpy version
+ idx.delete(5)
+
+ def test_delete_slice(self):
+ idx = timedelta_range(start="1 days", periods=10, freq="D", name="idx")
+
+ # preserve freq
+ expected_0_2 = timedelta_range(start="4 days", periods=7, freq="D", name="idx")
+ expected_7_9 = timedelta_range(start="1 days", periods=7, freq="D", name="idx")
+
+ # reset freq to None
+ expected_3_5 = TimedeltaIndex(
+ ["1 D", "2 D", "3 D", "7 D", "8 D", "9 D", "10D"], freq=None, name="idx"
+ )
+
+ cases = {
+ (0, 1, 2): expected_0_2,
+ (7, 8, 9): expected_7_9,
+ (3, 4, 5): expected_3_5,
+ }
+ for n, expected in cases.items():
+ result = idx.delete(n)
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ result = idx.delete(slice(n[0], n[-1] + 1))
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ def test_delete_doesnt_infer_freq(self):
+ # GH#30655 behavior matches DatetimeIndex
+
+ tdi = TimedeltaIndex(["1 Day", "2 Days", None, "3 Days", "4 Days"])
+ result = tdi.delete(2)
+ assert result.freq is None
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_formats.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_formats.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ec4c999aa838c0b9550b14fcd877a64efd6404d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_formats.py
@@ -0,0 +1,106 @@
+import pytest
+
+import pandas as pd
+from pandas import (
+ Series,
+ TimedeltaIndex,
+)
+
+
+class TestTimedeltaIndexRendering:
+ def test_repr_round_days_non_nano(self):
+ # GH#55405
+ # we should get "1 days", not "1 days 00:00:00" with non-nano
+ tdi = TimedeltaIndex(["1 days"], freq="D").as_unit("s")
+ result = repr(tdi)
+ expected = "TimedeltaIndex(['1 days'], dtype='timedelta64[s]', freq='D')"
+ assert result == expected
+
+ result2 = repr(Series(tdi))
+ expected2 = "0 1 days\ndtype: timedelta64[s]"
+ assert result2 == expected2
+
+ @pytest.mark.parametrize("method", ["__repr__", "__str__"])
+ def test_representation(self, method):
+ idx1 = TimedeltaIndex([], freq="D", dtype="m8[ns]")
+ idx2 = TimedeltaIndex(["1 days"], freq="D")
+ idx3 = TimedeltaIndex(["1 days", "2 days"], freq="D")
+ idx4 = TimedeltaIndex(["1 days", "2 days", "3 days"], freq="D")
+ idx5 = TimedeltaIndex(["1 days 00:00:01", "2 days", "3 days"])
+
+ exp1 = "TimedeltaIndex([], dtype='timedelta64[ns]', freq='D')"
+
+ exp2 = "TimedeltaIndex(['1 days'], dtype='timedelta64[us]', freq='D')"
+
+ exp3 = "TimedeltaIndex(['1 days', '2 days'], dtype='timedelta64[us]', freq='D')"
+
+ exp4 = (
+ "TimedeltaIndex(['1 days', '2 days', '3 days'], "
+ "dtype='timedelta64[us]', freq='D')"
+ )
+
+ exp5 = (
+ "TimedeltaIndex(['1 days 00:00:01', '2 days 00:00:00', "
+ "'3 days 00:00:00'], dtype='timedelta64[us]', freq=None)"
+ )
+
+ with pd.option_context("display.width", 300):
+ for idx, expected in zip(
+ [idx1, idx2, idx3, idx4, idx5], [exp1, exp2, exp3, exp4, exp5]
+ ):
+ result = getattr(idx, method)()
+ assert result == expected
+
+ # TODO: this is a Series.__repr__ test
+ def test_representation_to_series(self):
+ idx1 = TimedeltaIndex([], freq="D", dtype="m8[ns]")
+ idx2 = TimedeltaIndex(["1 days"], freq="D")
+ idx3 = TimedeltaIndex(["1 days", "2 days"], freq="D")
+ idx4 = TimedeltaIndex(["1 days", "2 days", "3 days"], freq="D")
+ idx5 = TimedeltaIndex(["1 days 00:00:01", "2 days", "3 days"])
+
+ exp1 = """Series([], dtype: timedelta64[ns])"""
+
+ exp2 = "0 1 days\ndtype: timedelta64[us]"
+
+ exp3 = "0 1 days\n1 2 days\ndtype: timedelta64[us]"
+
+ exp4 = "0 1 days\n1 2 days\n2 3 days\ndtype: timedelta64[us]"
+
+ exp5 = (
+ "0 1 days 00:00:01\n"
+ "1 2 days 00:00:00\n"
+ "2 3 days 00:00:00\n"
+ "dtype: timedelta64[us]"
+ )
+
+ with pd.option_context("display.width", 300):
+ for idx, expected in zip(
+ [idx1, idx2, idx3, idx4, idx5], [exp1, exp2, exp3, exp4, exp5]
+ ):
+ result = repr(Series(idx))
+ assert result == expected
+
+ def test_summary(self):
+ # GH#9116
+ idx1 = TimedeltaIndex([], freq="D", dtype="m8[ns]")
+ idx2 = TimedeltaIndex(["1 days"], freq="D")
+ idx3 = TimedeltaIndex(["1 days", "2 days"], freq="D")
+ idx4 = TimedeltaIndex(["1 days", "2 days", "3 days"], freq="D")
+ idx5 = TimedeltaIndex(["1 days 00:00:01", "2 days", "3 days"])
+
+ exp1 = "TimedeltaIndex: 0 entries\nFreq: D"
+
+ exp2 = "TimedeltaIndex: 1 entries, 1 days to 1 days\nFreq: D"
+
+ exp3 = "TimedeltaIndex: 2 entries, 1 days to 2 days\nFreq: D"
+
+ exp4 = "TimedeltaIndex: 3 entries, 1 days to 3 days\nFreq: D"
+
+ exp5 = "TimedeltaIndex: 3 entries, 1 days 00:00:01 to 3 days 00:00:00"
+
+ for idx, expected in zip(
+ [idx1, idx2, idx3, idx4, idx5], [exp1, exp2, exp3, exp4, exp5]
+ ):
+ result = idx._summary()
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_freq_attr.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_freq_attr.py
new file mode 100644
index 0000000000000000000000000000000000000000..1912c49d3000fcbef45dd081213778bfb387e38e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_freq_attr.py
@@ -0,0 +1,72 @@
+import pytest
+
+from pandas import TimedeltaIndex
+
+from pandas.tseries.offsets import (
+ DateOffset,
+ Day,
+ Hour,
+ MonthEnd,
+)
+
+
+class TestFreq:
+ @pytest.mark.parametrize("values", [["0 days", "2 days", "4 days"], []])
+ @pytest.mark.parametrize("freq", ["2D", Day(2), "48h", Hour(48)])
+ def test_freq_setter(self, values, freq):
+ # GH#20678
+ idx = TimedeltaIndex(values)
+
+ # can set to an offset, converting from string if necessary
+ idx._data.freq = freq
+ assert idx.freq == freq
+ assert isinstance(idx.freq, DateOffset)
+
+ # can reset to None
+ idx._data.freq = None
+ assert idx.freq is None
+
+ def test_with_freq_empty_requires_tick(self):
+ idx = TimedeltaIndex([])
+
+ off = MonthEnd(1)
+ msg = "TimedeltaArray/Index freq must be a Tick"
+ with pytest.raises(TypeError, match=msg):
+ idx._with_freq(off)
+ with pytest.raises(TypeError, match=msg):
+ idx._data._with_freq(off)
+
+ def test_freq_setter_errors(self):
+ # GH#20678
+ idx = TimedeltaIndex(["0 days", "2 days", "4 days"])
+
+ # setting with an incompatible freq
+ msg = (
+ "Inferred frequency 2D from passed values does not conform to "
+ "passed frequency 5D"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx._data.freq = "5D"
+
+ # setting with a non-fixed frequency
+ msg = r"<2 \* BusinessDays> is a non-fixed frequency"
+ with pytest.raises(ValueError, match=msg):
+ idx._data.freq = "2B"
+
+ # setting with non-freq string
+ with pytest.raises(ValueError, match="Invalid frequency"):
+ idx._data.freq = "foo"
+
+ def test_freq_view_safe(self):
+ # Setting the freq for one TimedeltaIndex shouldn't alter the freq
+ # for another that views the same data
+
+ tdi = TimedeltaIndex(["0 days", "2 days", "4 days"], freq="2D")
+ tda = tdi._data
+
+ tdi2 = TimedeltaIndex(tda)._with_freq(None)
+ assert tdi2.freq is None
+
+ # Original was not altered
+ assert tdi.freq == "2D"
+ assert tda.freq == "2D"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..88c316e8aabfe6e057885edc2f5a8a3b325620f4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_indexing.py
@@ -0,0 +1,352 @@
+from datetime import datetime
+import re
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ Index,
+ NaT,
+ Timedelta,
+ TimedeltaIndex,
+ Timestamp,
+ notna,
+ offsets,
+ timedelta_range,
+ to_timedelta,
+)
+import pandas._testing as tm
+
+
+class TestGetItem:
+ def test_getitem_slice_keeps_name(self):
+ # GH#4226, GH#59051
+ msg = "'d' is deprecated and will be removed in a future version."
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ tdi = timedelta_range("1d", "5d", freq="h", name="timebucket")
+ assert tdi[1:].name == tdi.name
+
+ def test_getitem(self):
+ idx1 = timedelta_range("1 day", "31 day", freq="D", name="idx")
+
+ for idx in [idx1]:
+ result = idx[0]
+ assert result == Timedelta("1 day")
+
+ result = idx[0:5]
+ expected = timedelta_range("1 day", "5 day", freq="D", name="idx")
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx[0:10:2]
+ expected = timedelta_range("1 day", "9 day", freq="2D", name="idx")
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx[-20:-5:3]
+ expected = timedelta_range("12 day", "24 day", freq="3D", name="idx")
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx[4::-1]
+ expected = TimedeltaIndex(
+ ["5 day", "4 day", "3 day", "2 day", "1 day"], freq="-1D", name="idx"
+ )
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ @pytest.mark.parametrize(
+ "key",
+ [
+ Timestamp("1970-01-01"),
+ Timestamp("1970-01-02"),
+ datetime(1970, 1, 1),
+ Timestamp("1970-01-03").to_datetime64(),
+ # non-matching NA values
+ np.datetime64("NaT"),
+ ],
+ )
+ def test_timestamp_invalid_key(self, key):
+ # GH#20464
+ tdi = timedelta_range(0, periods=10)
+ with pytest.raises(KeyError, match=re.escape(repr(key))):
+ tdi.get_loc(key)
+
+
+class TestGetLoc:
+ def test_get_loc_key_unit_mismatch(self):
+ idx = to_timedelta(["0 days", "1 days", "2 days"])
+ key = idx[1].as_unit("ms")
+ loc = idx.get_loc(key)
+ assert loc == 1
+
+ def test_get_loc_key_unit_mismatch_not_castable(self):
+ tdi = to_timedelta(["0 days", "1 days", "2 days"]).astype("m8[s]")
+ assert tdi.dtype == "m8[s]"
+ key = tdi[0].as_unit("ns") + Timedelta(1)
+
+ with pytest.raises(KeyError, match=r"Timedelta\('0 days 00:00:00.000000001'\)"):
+ tdi.get_loc(key)
+
+ assert key not in tdi
+
+ def test_get_loc(self):
+ idx = to_timedelta(["0 days", "1 days", "2 days"])
+
+ # GH 16909
+ assert idx.get_loc(idx[1].to_timedelta64()) == 1
+
+ # GH 16896
+ assert idx.get_loc("0 days") == 0
+
+ def test_get_loc_nat(self):
+ tidx = TimedeltaIndex(["1 days 01:00:00", "NaT", "2 days 01:00:00"])
+
+ assert tidx.get_loc(NaT) == 1
+ assert tidx.get_loc(None) == 1
+ assert tidx.get_loc(float("nan")) == 1
+ assert tidx.get_loc(np.nan) == 1
+
+
+class TestGetIndexer:
+ def test_get_indexer(self):
+ idx = to_timedelta(["0 days", "1 days", "2 days"])
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(idx), np.array([0, 1, 2], dtype=np.intp)
+ )
+
+ target = to_timedelta(["-1 hour", "12 hours", "1 day 1 hour"])
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "pad"), np.array([-1, 0, 1], dtype=np.intp)
+ )
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "backfill"), np.array([0, 1, 2], dtype=np.intp)
+ )
+ tm.assert_numpy_array_equal(
+ idx.get_indexer(target, "nearest"), np.array([0, 1, 1], dtype=np.intp)
+ )
+
+ res = idx.get_indexer(target, "nearest", tolerance=Timedelta("1 hour"))
+ tm.assert_numpy_array_equal(res, np.array([0, -1, 1], dtype=np.intp))
+
+
+class TestWhere:
+ def test_where_doesnt_retain_freq(self):
+ tdi = timedelta_range("1 day", periods=3, freq="D", name="idx")
+ cond = [True, True, False]
+ expected = TimedeltaIndex([tdi[0], tdi[1], tdi[0]], freq=None, name="idx")
+
+ result = tdi.where(cond, tdi[::-1])
+ tm.assert_index_equal(result, expected)
+
+ def test_where_invalid_dtypes(self, fixed_now_ts):
+ tdi = timedelta_range("1 day", periods=3, freq="D", name="idx")
+
+ tail = tdi[2:].tolist()
+ i2 = Index([NaT, NaT, *tail])
+ mask = notna(i2)
+
+ expected = Index([NaT._value, NaT._value, *tail], dtype=object, name="idx")
+ assert isinstance(expected[0], int)
+ result = tdi.where(mask, i2.asi8)
+ tm.assert_index_equal(result, expected)
+
+ ts = i2 + fixed_now_ts
+ expected = Index([ts[0], ts[1], *tail], dtype=object, name="idx")
+ result = tdi.where(mask, ts)
+ tm.assert_index_equal(result, expected)
+
+ per = (i2 + fixed_now_ts).to_period("D")
+ expected = Index([per[0], per[1], *tail], dtype=object, name="idx")
+ result = tdi.where(mask, per)
+ tm.assert_index_equal(result, expected)
+
+ ts = fixed_now_ts
+ expected = Index([ts, ts, *tail], dtype=object, name="idx")
+ result = tdi.where(mask, ts)
+ tm.assert_index_equal(result, expected)
+
+ def test_where_mismatched_nat(self):
+ tdi = timedelta_range("1 day", periods=3, freq="D", name="idx")
+ cond = np.array([True, False, False])
+
+ dtnat = np.datetime64("NaT", "ns")
+ expected = Index([tdi[0], dtnat, dtnat], dtype=object, name="idx")
+ assert expected[2] is dtnat
+ result = tdi.where(cond, dtnat)
+ tm.assert_index_equal(result, expected)
+
+
+class TestTake:
+ def test_take(self):
+ # GH 10295
+ idx1 = timedelta_range("1 day", "31 day", freq="D", name="idx")
+
+ for idx in [idx1]:
+ result = idx.take([0])
+ assert result == Timedelta("1 day")
+
+ result = idx.take([-1])
+ assert result == Timedelta("31 day")
+
+ result = idx.take([0, 1, 2])
+ expected = timedelta_range("1 day", "3 day", freq="D", name="idx")
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx.take([0, 2, 4])
+ expected = timedelta_range("1 day", "5 day", freq="2D", name="idx")
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx.take([7, 4, 1])
+ expected = timedelta_range("8 day", "2 day", freq="-3D", name="idx")
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ result = idx.take([3, 2, 5])
+ expected = TimedeltaIndex(["4 day", "3 day", "6 day"], name="idx")
+ tm.assert_index_equal(result, expected)
+ assert result.freq is None
+
+ result = idx.take([-3, 2, 5])
+ expected = TimedeltaIndex(["29 day", "3 day", "6 day"], name="idx")
+ tm.assert_index_equal(result, expected)
+ assert result.freq is None
+
+ def test_take_invalid_kwargs(self):
+ idx = timedelta_range("1 day", "31 day", freq="D", name="idx")
+ indices = [1, 6, 5, 9, 10, 13, 15, 3]
+
+ msg = r"take\(\) got an unexpected keyword argument 'foo'"
+ with pytest.raises(TypeError, match=msg):
+ idx.take(indices, foo=2)
+
+ msg = "the 'out' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, out=indices)
+
+ msg = "the 'mode' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ idx.take(indices, mode="clip")
+
+ def test_take_equiv_getitem(self):
+ tds = ["1day 02:00:00", "1 day 04:00:00", "1 day 10:00:00"]
+ idx = timedelta_range(start="1D", end="2D", freq="h", name="idx")
+ expected = TimedeltaIndex(tds, freq=None, name="idx")
+
+ taken1 = idx.take([2, 4, 10])
+ taken2 = idx[[2, 4, 10]]
+
+ for taken in [taken1, taken2]:
+ tm.assert_index_equal(taken, expected)
+ assert isinstance(taken, TimedeltaIndex)
+ assert taken.freq is None
+ assert taken.name == expected.name
+
+ def test_take_fill_value(self):
+ # GH 12631
+ idx = TimedeltaIndex(["1 days", "2 days", "3 days"], name="xxx")
+ result = idx.take(np.array([1, 0, -1]))
+ expected = TimedeltaIndex(["2 days", "1 days", "3 days"], name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ # fill_value
+ result = idx.take(np.array([1, 0, -1]), fill_value=True)
+ expected = TimedeltaIndex(["2 days", "1 days", "NaT"], name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ # allow_fill=False
+ result = idx.take(np.array([1, 0, -1]), allow_fill=False, fill_value=True)
+ expected = TimedeltaIndex(["2 days", "1 days", "3 days"], name="xxx")
+ tm.assert_index_equal(result, expected)
+
+ msg = (
+ "When allow_fill=True and fill_value is not None, all indices must be >= -1"
+ )
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -2]), fill_value=True)
+ with pytest.raises(ValueError, match=msg):
+ idx.take(np.array([1, 0, -5]), fill_value=True)
+
+ msg = "index -5 is out of bounds for (axis 0 with )?size 3"
+ with pytest.raises(IndexError, match=msg):
+ idx.take(np.array([1, -5]))
+
+
+class TestMaybeCastSliceBound:
+ @pytest.fixture(params=["increasing", "decreasing", None])
+ def monotonic(self, request):
+ return request.param
+
+ @pytest.fixture
+ def tdi(self, monotonic):
+ tdi = timedelta_range("1 Day", periods=10)
+ if monotonic == "decreasing":
+ tdi = tdi[::-1]
+ elif monotonic is None:
+ taker = np.arange(10, dtype=np.intp)
+ np.random.default_rng(2).shuffle(taker)
+ tdi = tdi.take(taker)
+ return tdi
+
+ def test_maybe_cast_slice_bound_invalid_str(self, tdi):
+ # test the low-level _maybe_cast_slice_bound and that we get the
+ # expected exception+message all the way up the stack
+ msg = (
+ "cannot do slice indexing on TimedeltaIndex with these "
+ r"indexers \[foo\] of type str"
+ )
+ with pytest.raises(TypeError, match=msg):
+ tdi._maybe_cast_slice_bound("foo", side="left")
+ with pytest.raises(TypeError, match=msg):
+ tdi.get_slice_bound("foo", side="left")
+ with pytest.raises(TypeError, match=msg):
+ tdi.slice_locs("foo", None, None)
+
+ def test_slice_invalid_str_with_timedeltaindex(
+ self, tdi, frame_or_series, indexer_sl
+ ):
+ obj = frame_or_series(range(10), index=tdi)
+
+ msg = (
+ "cannot do slice indexing on TimedeltaIndex with these "
+ r"indexers \[foo\] of type str"
+ )
+ with pytest.raises(TypeError, match=msg):
+ indexer_sl(obj)["foo":]
+ with pytest.raises(TypeError, match=msg):
+ indexer_sl(obj)["foo":-1]
+ with pytest.raises(TypeError, match=msg):
+ indexer_sl(obj)[:"foo"]
+ with pytest.raises(TypeError, match=msg):
+ indexer_sl(obj)[tdi[0] : "foo"]
+
+
+class TestContains:
+ def test_contains_nonunique(self):
+ # GH#9512
+ for vals in (
+ [0, 1, 0],
+ [0, 0, -1],
+ [0, -1, -1],
+ ["00:01:00", "00:01:00", "00:02:00"],
+ ["00:01:00", "00:01:00", "00:00:01"],
+ ):
+ idx = TimedeltaIndex(vals)
+ assert idx[0] in idx
+
+ def test_contains(self):
+ # Checking for any NaT-like objects
+ # GH#13603, GH#59051
+ msg = "'d' is deprecated and will be removed in a future version."
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ td = to_timedelta(range(5), unit="d") + offsets.Hour(1)
+ for v in [NaT, None, float("nan"), np.nan]:
+ assert v not in td
+
+ td = to_timedelta([NaT])
+ for v in [NaT, None, float("nan"), np.nan]:
+ assert v in td
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..cbd7a5de71b10a6004cd7a3f798fecd8e7631750
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_join.py
@@ -0,0 +1,47 @@
+import numpy as np
+
+from pandas import (
+ DataFrame,
+ Index,
+ Timedelta,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestJoin:
+ def test_append_join_nondatetimeindex(self):
+ rng = timedelta_range("1 days", periods=10)
+ idx = Index(["a", "b", "c", "d"])
+
+ result = rng.append(idx)
+ assert isinstance(result[0], Timedelta)
+
+ # it works
+ rng.join(idx, how="outer")
+
+ def test_join_self(self, join_type):
+ index = timedelta_range("1 day", periods=10)
+ joined = index.join(index, how=join_type)
+ tm.assert_index_equal(index, joined)
+
+ def test_does_not_convert_mixed_integer(self):
+ df = DataFrame(np.ones((5, 5)), columns=timedelta_range("1 day", periods=5))
+
+ cols = df.columns.join(df.index, how="outer")
+ joined = cols.join(df.columns)
+ assert cols.dtype == np.dtype("O")
+ assert cols.dtype == joined.dtype
+ tm.assert_index_equal(cols, joined)
+
+ def test_join_preserves_freq(self):
+ # GH#32157
+ tdi = timedelta_range("1 day", periods=10)
+ result = tdi[:5].join(tdi[5:], how="outer")
+ assert result.freq == tdi.freq
+ tm.assert_index_equal(result, tdi)
+
+ result = tdi[:5].join(tdi[6:], how="outer")
+ assert result.freq is None
+ expected = tdi.delete(5)
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_ops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_ops.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6013baf86edcd566a17cd3127467a7443ac475a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_ops.py
@@ -0,0 +1,14 @@
+from pandas import (
+ TimedeltaIndex,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestTimedeltaIndexOps:
+ def test_infer_freq(self, freq_sample):
+ # GH#11018
+ idx = timedelta_range("1", freq=freq_sample, periods=10)
+ result = TimedeltaIndex(idx.asi8, freq="infer")
+ tm.assert_index_equal(idx, result)
+ assert result.freq == freq_sample
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_pickle.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_pickle.py
new file mode 100644
index 0000000000000000000000000000000000000000..d58e15960ebf1ea75c2e6ddb1e7ecafca2bb5e86
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_pickle.py
@@ -0,0 +1,11 @@
+from pandas import timedelta_range
+import pandas._testing as tm
+
+
+class TestPickle:
+ def test_pickle_after_set_freq(self, temp_file):
+ tdi = timedelta_range("1 day", periods=4, freq="s")
+ tdi = tdi._with_freq(None)
+
+ res = tm.round_trip_pickle(tdi, temp_file)
+ tm.assert_index_equal(res, tdi)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_scalar_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_scalar_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..80c02f9b1bd38430b17dc1b1cb2c71597879a4a9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_scalar_compat.py
@@ -0,0 +1,147 @@
+"""
+Tests for TimedeltaIndex methods behaving like their Timedelta counterparts
+"""
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs.offsets import INVALID_FREQ_ERR_MSG
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ Index,
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestVectorizedTimedelta:
+ def test_tdi_total_seconds(self):
+ # GH#10939
+ # test index
+ rng = timedelta_range("1 days, 10:11:12.100123456", periods=2, freq="s")
+ expt = [
+ 1 * 86400 + 10 * 3600 + 11 * 60 + 12 + 100123456.0 / 1e9,
+ 1 * 86400 + 10 * 3600 + 11 * 60 + 13 + 100123456.0 / 1e9,
+ ]
+ tm.assert_almost_equal(rng.total_seconds(), Index(expt))
+
+ # test Series
+ ser = Series(rng)
+ s_expt = Series(expt, index=[0, 1])
+ tm.assert_series_equal(ser.dt.total_seconds(), s_expt)
+
+ # with nat
+ ser[1] = np.nan
+ s_expt = Series(
+ [1 * 86400 + 10 * 3600 + 11 * 60 + 12 + 100123456.0 / 1e9, np.nan],
+ index=[0, 1],
+ )
+ tm.assert_series_equal(ser.dt.total_seconds(), s_expt)
+
+ def test_tdi_total_seconds_all_nat(self):
+ # with both nat
+ ser = Series([np.nan, np.nan], dtype="timedelta64[ns]")
+ result = ser.dt.total_seconds()
+ expected = Series([np.nan, np.nan])
+ tm.assert_series_equal(result, expected)
+
+ def test_tdi_round(self):
+ td = timedelta_range(start="16801 days", periods=5, freq="30Min")
+ elt = td[1]
+
+ expected_rng = TimedeltaIndex(
+ [
+ Timedelta("16801 days 00:00:00"),
+ Timedelta("16801 days 00:00:00"),
+ Timedelta("16801 days 01:00:00"),
+ Timedelta("16801 days 02:00:00"),
+ Timedelta("16801 days 02:00:00"),
+ ]
+ )
+ expected_elt = expected_rng[1]
+
+ tm.assert_index_equal(td.round(freq="h"), expected_rng)
+ assert elt.round(freq="h") == expected_elt
+
+ msg = INVALID_FREQ_ERR_MSG
+ with pytest.raises(ValueError, match=msg):
+ td.round(freq="foo")
+ with pytest.raises(ValueError, match=msg):
+ elt.round(freq="foo")
+
+ msg = " is a non-fixed frequency"
+ with pytest.raises(ValueError, match=msg):
+ td.round(freq="ME")
+ with pytest.raises(ValueError, match=msg):
+ elt.round(freq="ME")
+
+ @pytest.mark.parametrize(
+ "freq,msg",
+ [
+ ("YE", " is a non-fixed frequency"),
+ ("ME", " is a non-fixed frequency"),
+ ("foobar", "Invalid frequency: foobar"),
+ ],
+ )
+ def test_tdi_round_invalid(self, freq, msg):
+ t1 = timedelta_range("1 days", periods=3, freq="1 min 2 s 3 us")
+
+ with pytest.raises(ValueError, match=msg):
+ t1.round(freq)
+ with pytest.raises(ValueError, match=msg):
+ # Same test for TimedeltaArray
+ t1._data.round(freq)
+
+ # TODO: de-duplicate with test_tdi_round
+ def test_round(self):
+ t1 = timedelta_range("1 days", periods=3, freq="1 min 2 s 3 us")
+ t2 = -1 * t1
+ t1a = timedelta_range("1 days", periods=3, freq="1 min 2 s")
+ t1c = TimedeltaIndex(np.array([1, 1, 1], "m8[D]")).as_unit("us")
+
+ # note that negative times round DOWN! so don't give whole numbers
+ msg = "'d' is deprecated and will be removed in a future version."
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ for freq, s1, s2 in [
+ ("ns", t1, t2),
+ ("us", t1, t2),
+ (
+ "ms",
+ t1a,
+ TimedeltaIndex(
+ ["-1 days +00:00:00", "-2 days +23:58:58", "-2 days +23:57:56"]
+ ),
+ ),
+ (
+ "s",
+ t1a,
+ TimedeltaIndex(
+ ["-1 days +00:00:00", "-2 days +23:58:58", "-2 days +23:57:56"]
+ ),
+ ),
+ ("12min", t1c, TimedeltaIndex(["-1 days", "-1 days", "-1 days"])),
+ ("h", t1c, TimedeltaIndex(["-1 days", "-1 days", "-1 days"])),
+ ("d", t1c, -1 * t1c),
+ ]:
+ r1 = t1.round(freq)
+ r2 = t2.round(freq)
+
+ tm.assert_index_equal(r1, s1)
+ tm.assert_index_equal(r2, s2)
+
+ def test_components(self):
+ rng = timedelta_range("1 days, 10:11:12", periods=2, freq="s")
+ rng.components
+
+ # with nat
+ s = Series(rng)
+ s[1] = np.nan
+
+ result = s.dt.components
+ assert not result.iloc[0].isna().all()
+ assert result.iloc[1].isna().all()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_searchsorted.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_searchsorted.py
new file mode 100644
index 0000000000000000000000000000000000000000..710571ef383970097985f44e09ecba77fcf63f74
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_searchsorted.py
@@ -0,0 +1,28 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ TimedeltaIndex,
+ Timestamp,
+)
+import pandas._testing as tm
+
+
+class TestSearchSorted:
+ def test_searchsorted_different_argument_classes(self, listlike_box):
+ idx = TimedeltaIndex(["1 day", "2 days", "3 days"])
+ result = idx.searchsorted(listlike_box(idx))
+ expected = np.arange(len(idx), dtype=result.dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = idx._data.searchsorted(listlike_box(idx))
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "arg", [[1, 2], ["a", "b"], [Timestamp("2020-01-01", tz="Europe/London")] * 2]
+ )
+ def test_searchsorted_invalid_argument_dtype(self, arg):
+ idx = TimedeltaIndex(["1 day", "2 days", "3 days"])
+ msg = "value should be a 'Timedelta', 'NaT', or array of those. Got"
+ with pytest.raises(TypeError, match=msg):
+ idx.searchsorted(arg)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_setops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_setops.py
new file mode 100644
index 0000000000000000000000000000000000000000..424961a4b652bd00874f408f36a9f28b0f880d9a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_setops.py
@@ -0,0 +1,260 @@
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ Index,
+ TimedeltaIndex,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+from pandas.tseries.offsets import Hour
+
+
+class TestTimedeltaIndex:
+ def test_union(self):
+ i1 = timedelta_range("1day", periods=5)
+ i2 = timedelta_range("3day", periods=5)
+ result = i1.union(i2)
+ expected = timedelta_range("1day", periods=7)
+ tm.assert_index_equal(result, expected)
+
+ i1 = Index(np.arange(0, 20, 2, dtype=np.int64))
+ i2 = timedelta_range(start="1 day", periods=10, freq="D")
+ i1.union(i2) # Works
+ i2.union(i1) # Fails with "AttributeError: can't set attribute"
+
+ def test_union_sort_false(self):
+ tdi = timedelta_range("1day", periods=5)
+
+ left = tdi[3:]
+ right = tdi[:3]
+
+ # Check that we are testing the desired code path
+ assert left._can_fast_union(right)
+
+ result = left.union(right)
+ tm.assert_index_equal(result, tdi)
+
+ result = left.union(right, sort=False)
+ expected = TimedeltaIndex(["4 Days", "5 Days", "1 Days", "2 Day", "3 Days"])
+ tm.assert_index_equal(result, expected)
+
+ def test_union_coverage(self):
+ # GH#59051
+ msg = "'d' is deprecated and will be removed in a future version."
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ idx = TimedeltaIndex(["3d", "1d", "2d"])
+ ordered = TimedeltaIndex(idx.sort_values(), freq="infer")
+ result = ordered.union(idx)
+ tm.assert_index_equal(result, ordered)
+
+ result = ordered[:0].union(ordered)
+ tm.assert_index_equal(result, ordered)
+ assert result.freq == ordered.freq
+
+ def test_union_bug_1730(self):
+ rng_a = timedelta_range("1 day", periods=4, freq="3h")
+ rng_b = timedelta_range("1 day", periods=4, freq="4h")
+
+ result = rng_a.union(rng_b)
+ exp = TimedeltaIndex(sorted(set(rng_a) | set(rng_b)))
+ tm.assert_index_equal(result, exp)
+
+ def test_union_bug_1745(self):
+ left = TimedeltaIndex(["1 day 15:19:49.695000"])
+ right = TimedeltaIndex(
+ ["2 day 13:04:21.322000", "1 day 15:27:24.873000", "1 day 15:31:05.350000"]
+ )
+
+ result = left.union(right)
+ exp = TimedeltaIndex(sorted(set(left) | set(right)))
+ tm.assert_index_equal(result, exp)
+
+ def test_union_bug_4564(self):
+ left = timedelta_range("1 day", "30D")
+ right = left + pd.offsets.Minute(15)
+
+ result = left.union(right)
+ exp = TimedeltaIndex(sorted(set(left) | set(right)))
+ tm.assert_index_equal(result, exp)
+
+ def test_union_freq_infer(self):
+ # When taking the union of two TimedeltaIndexes, we infer
+ # a freq even if the arguments don't have freq. This matches
+ # DatetimeIndex behavior.
+ tdi = timedelta_range("1 Day", periods=5)
+ left = tdi[[0, 1, 3, 4]]
+ right = tdi[[2, 3, 1]]
+
+ assert left.freq is None
+ assert right.freq is None
+
+ result = left.union(right)
+ tm.assert_index_equal(result, tdi)
+ assert result.freq == "D"
+
+ def test_intersection_bug_1708(self):
+ index_1 = timedelta_range("1 day", periods=4, freq="h")
+ index_2 = index_1 + pd.offsets.Hour(5)
+
+ result = index_1.intersection(index_2)
+ assert len(result) == 0
+
+ index_1 = timedelta_range("1 day", periods=4, freq="h")
+ index_2 = index_1 + pd.offsets.Hour(1)
+
+ result = index_1.intersection(index_2)
+ expected = timedelta_range("1 day 01:00:00", periods=3, freq="h")
+ tm.assert_index_equal(result, expected)
+ assert result.freq == expected.freq
+
+ def test_intersection_equal(self, sort):
+ # GH 24471 Test intersection outcome given the sort keyword
+ # GH#63169 intersection returns a copy to prevent shared mutable state
+ first = timedelta_range("1 day", periods=4, freq="h")
+ second = timedelta_range("1 day", periods=4, freq="h")
+ intersect = first.intersection(second, sort=sort)
+ if sort is None:
+ tm.assert_index_equal(intersect, second.sort_values())
+ tm.assert_index_equal(intersect, second)
+
+ # Corner cases
+ inter = first.intersection(first, sort=sort)
+ assert inter is not first
+ tm.assert_index_equal(inter, first)
+
+ @pytest.mark.parametrize("period_1, period_2", [(0, 4), (4, 0)])
+ def test_intersection_zero_length(self, period_1, period_2, sort):
+ # GH 24471 test for non overlap the intersection should be zero length
+ index_1 = timedelta_range("1 day", periods=period_1, freq="h")
+ index_2 = timedelta_range("1 day", periods=period_2, freq="h")
+ expected = timedelta_range("1 day", periods=0, freq="h")
+ result = index_1.intersection(index_2, sort=sort)
+ tm.assert_index_equal(result, expected)
+
+ def test_zero_length_input_index(self, sort):
+ # GH 24966 test for 0-len intersections are copied
+ index_1 = timedelta_range("1 day", periods=0, freq="h")
+ index_2 = timedelta_range("1 day", periods=3, freq="h")
+ result = index_1.intersection(index_2, sort=sort)
+ assert index_1 is not result
+ assert index_2 is not result
+ tm.assert_copy(result, index_1)
+
+ @pytest.mark.parametrize(
+ "rng, expected",
+ # if target has the same name, it is preserved
+ [
+ (
+ timedelta_range("1 day", periods=5, freq="h", name="idx"),
+ timedelta_range("1 day", periods=4, freq="h", name="idx"),
+ ),
+ # if target name is different, it will be reset
+ (
+ timedelta_range("1 day", periods=5, freq="h", name="other"),
+ timedelta_range("1 day", periods=4, freq="h", name=None),
+ ),
+ # if no overlap exists return empty index
+ (
+ timedelta_range("1 day", periods=10, freq="h", name="idx")[5:],
+ TimedeltaIndex([], freq="h", name="idx", dtype="m8[us]"),
+ ),
+ ],
+ )
+ def test_intersection(self, rng, expected, sort):
+ # GH 4690 (with tz)
+ base = timedelta_range("1 day", periods=4, freq="h", name="idx")
+ result = base.intersection(rng, sort=sort)
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+ assert result.freq == expected.freq
+
+ @pytest.mark.parametrize(
+ "rng, expected",
+ # part intersection works
+ [
+ (
+ TimedeltaIndex(["5 hour", "2 hour", "4 hour", "9 hour"], name="idx"),
+ TimedeltaIndex(["2 hour", "4 hour"], name="idx"),
+ ),
+ # reordered part intersection
+ (
+ TimedeltaIndex(["2 hour", "5 hour", "5 hour", "1 hour"], name="other"),
+ TimedeltaIndex(["1 hour", "2 hour"], name=None),
+ ),
+ # reversed index
+ (
+ TimedeltaIndex(["1 hour", "2 hour", "4 hour", "3 hour"], name="idx")[
+ ::-1
+ ],
+ TimedeltaIndex(["1 hour", "2 hour", "4 hour", "3 hour"], name="idx"),
+ ),
+ ],
+ )
+ def test_intersection_non_monotonic(self, rng, expected, sort):
+ # 24471 non-monotonic
+ base = TimedeltaIndex(["1 hour", "2 hour", "4 hour", "3 hour"], name="idx")
+ result = base.intersection(rng, sort=sort)
+ if sort is None:
+ expected = expected.sort_values()
+ tm.assert_index_equal(result, expected)
+ assert result.name == expected.name
+
+ # if reversed order, frequency is still the same
+ if all(base == rng[::-1]) and sort is None:
+ assert isinstance(result.freq, Hour)
+ else:
+ assert result.freq is None
+
+
+class TestTimedeltaIndexDifference:
+ def test_difference_freq(self, sort):
+ # GH14323: Difference of TimedeltaIndex should not preserve frequency
+
+ index = timedelta_range("0 days", "5 days", freq="D")
+
+ other = timedelta_range("1 days", "4 days", freq="D")
+ expected = TimedeltaIndex(["0 days", "5 days"], freq=None)
+ idx_diff = index.difference(other, sort)
+ tm.assert_index_equal(idx_diff, expected)
+ tm.assert_attr_equal("freq", idx_diff, expected)
+
+ # preserve frequency when the difference is a contiguous
+ # subset of the original range
+ other = timedelta_range("2 days", "5 days", freq="D")
+ idx_diff = index.difference(other, sort)
+ expected = TimedeltaIndex(["0 days", "1 days"], freq="D")
+ tm.assert_index_equal(idx_diff, expected)
+ tm.assert_attr_equal("freq", idx_diff, expected)
+
+ def test_difference_sort(self, sort):
+ index = TimedeltaIndex(
+ ["5 days", "3 days", "2 days", "4 days", "1 days", "0 days"]
+ )
+
+ other = timedelta_range("1 days", "4 days", freq="D")
+ idx_diff = index.difference(other, sort)
+
+ expected = TimedeltaIndex(["5 days", "0 days"], freq=None)
+
+ if sort is None:
+ expected = expected.sort_values()
+
+ tm.assert_index_equal(idx_diff, expected)
+ tm.assert_attr_equal("freq", idx_diff, expected)
+
+ other = timedelta_range("2 days", "5 days", freq="D")
+ idx_diff = index.difference(other, sort)
+ expected = TimedeltaIndex(["1 days", "0 days"], freq=None)
+
+ if sort is None:
+ expected = expected.sort_values()
+
+ tm.assert_index_equal(idx_diff, expected)
+ tm.assert_attr_equal("freq", idx_diff, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_timedelta.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_timedelta.py
new file mode 100644
index 0000000000000000000000000000000000000000..2066be8976e7fa33c68c70a8b79a3dc65587de32
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_timedelta.py
@@ -0,0 +1,61 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ Series,
+ Timedelta,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestTimedeltaIndex:
+ def test_misc_coverage(self):
+ rng = timedelta_range("1 day", periods=5)
+ result = rng.groupby(rng.days)
+ assert isinstance(next(iter(result.values()))[0], Timedelta)
+
+ def test_map(self):
+ # test_map_dictlike generally tests
+
+ rng = timedelta_range("1 day", periods=10)
+
+ f = lambda x: x.days
+ result = rng.map(f)
+ exp = Index([f(x) for x in rng], dtype=np.int64)
+ tm.assert_index_equal(result, exp)
+
+ def test_fields(self):
+ rng = timedelta_range("1 days, 10:11:12.100123456", periods=2, freq="s")
+ tm.assert_index_equal(rng.days, Index([1, 1], dtype=np.int64))
+ tm.assert_index_equal(
+ rng.seconds,
+ Index([10 * 3600 + 11 * 60 + 12, 10 * 3600 + 11 * 60 + 13], dtype=np.int32),
+ )
+ tm.assert_index_equal(
+ rng.microseconds,
+ Index([100 * 1000 + 123, 100 * 1000 + 123], dtype=np.int32),
+ )
+ tm.assert_index_equal(rng.nanoseconds, Index([456, 456], dtype=np.int32))
+
+ msg = "'TimedeltaIndex' object has no attribute '{}'"
+ with pytest.raises(AttributeError, match=msg.format("hours")):
+ rng.hours
+ with pytest.raises(AttributeError, match=msg.format("minutes")):
+ rng.minutes
+ with pytest.raises(AttributeError, match=msg.format("milliseconds")):
+ rng.milliseconds
+
+ # with nat
+ s = Series(rng)
+ s[1] = np.nan
+
+ tm.assert_series_equal(s.dt.days, Series([1, np.nan], index=range(2)))
+ tm.assert_series_equal(
+ s.dt.seconds, Series([10 * 3600 + 11 * 60 + 12, np.nan], index=range(2))
+ )
+
+ # preserve name (GH15589)
+ rng.name = "name"
+ assert rng.days.name == "name"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_timedelta_range.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_timedelta_range.py
new file mode 100644
index 0000000000000000000000000000000000000000..1cca77e7e44c9ca5e5ab9d8f4baafb047967dd7f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexes/timedeltas/test_timedelta_range.py
@@ -0,0 +1,187 @@
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ Timedelta,
+ timedelta_range,
+ to_timedelta,
+)
+import pandas._testing as tm
+
+from pandas.tseries.offsets import (
+ Day,
+ Second,
+)
+
+
+class TestTimedeltas:
+ def test_timedelta_range_unit(self):
+ # GH#49824
+ tdi = timedelta_range("0 Days", periods=10, freq="100000D", unit="s")
+ exp_arr = (np.arange(10, dtype="i8") * 100_000).view("m8[D]").astype("m8[s]")
+ tm.assert_numpy_array_equal(tdi.to_numpy(), exp_arr)
+
+ def test_timedelta_range(self):
+ expected = to_timedelta(np.arange(5), unit="D").as_unit("us")
+ result = timedelta_range("0 days", periods=5, freq="D")
+ tm.assert_index_equal(result, expected)
+
+ expected = to_timedelta(np.arange(11), unit="D").as_unit("us")
+ result = timedelta_range("0 days", "10 days", freq="D")
+ tm.assert_index_equal(result, expected)
+
+ expected = (
+ to_timedelta(np.arange(5), unit="D").as_unit("us") + Second(2) + Day()
+ )
+ result = timedelta_range("1 days, 00:00:02", "5 days, 00:00:02", freq="D")
+ tm.assert_index_equal(result, expected)
+
+ expected = to_timedelta([1, 3, 5, 7, 9], unit="D").as_unit("us") + Second(2)
+ result = timedelta_range("1 days, 00:00:02", periods=5, freq="2D")
+ tm.assert_index_equal(result, expected)
+
+ expected = to_timedelta(np.arange(50), unit="min").as_unit("us") * 30
+ result = timedelta_range("0 days", freq="30min", periods=50)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("depr_unit, unit", [("H", "hour"), ("S", "second")])
+ def test_timedelta_units_H_S_deprecated(self, depr_unit, unit):
+ # GH#52536
+ depr_msg = (
+ f"'{depr_unit}' is deprecated and will be removed in a future version."
+ )
+ expected = to_timedelta(np.arange(5), unit=unit)
+ with tm.assert_produces_warning(Pandas4Warning, match=depr_msg):
+ result = to_timedelta(np.arange(5), unit=depr_unit)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("unit", ["T", "t", "L", "l", "U", "u", "N", "n"])
+ def test_timedelta_unit_T_L_U_N_raises(self, unit):
+ msg = f"invalid unit abbreviation: {unit}"
+
+ with pytest.raises(ValueError, match=msg):
+ to_timedelta(np.arange(5), unit=unit)
+
+ @pytest.mark.parametrize(
+ "periods, freq", [(3, "2D"), (5, "D"), (6, "19h12min"), (7, "16h"), (9, "12h")]
+ )
+ def test_linspace_behavior(self, periods, freq):
+ # GH 20976
+ result = timedelta_range(start="0 days", end="4 days", periods=periods)
+ expected = timedelta_range(start="0 days", end="4 days", freq=freq)
+ tm.assert_index_equal(result, expected)
+
+ def test_timedelta_range_H_raises(self):
+ # GH#52536
+ msg = "Invalid frequency: H"
+
+ with pytest.raises(ValueError, match=msg):
+ timedelta_range(start="0 days", end="4 days", freq="19H12min")
+
+ def test_timedelta_range_T_raises(self):
+ msg = "Invalid frequency: T"
+
+ with pytest.raises(ValueError, match=msg):
+ timedelta_range(start="0 days", end="4 days", freq="19h12T")
+
+ def test_errors(self):
+ # not enough params
+ msg = (
+ "Of the four parameters: start, end, periods, and freq, "
+ "exactly three must be specified"
+ )
+ with pytest.raises(ValueError, match=msg):
+ timedelta_range(start="0 days")
+
+ with pytest.raises(ValueError, match=msg):
+ timedelta_range(end="5 days")
+
+ with pytest.raises(ValueError, match=msg):
+ timedelta_range(periods=2)
+
+ with pytest.raises(ValueError, match=msg):
+ timedelta_range()
+
+ # too many params
+ with pytest.raises(ValueError, match=msg):
+ timedelta_range(start="0 days", end="5 days", periods=10, freq="h")
+
+ @pytest.mark.parametrize(
+ "start, end, freq, expected_periods",
+ [
+ ("1D", "10D", "2D", (10 - 1) // 2 + 1),
+ ("2D", "30D", "3D", (30 - 2) // 3 + 1),
+ ("2s", "50s", "5s", (50 - 2) // 5 + 1),
+ # tests that worked before GH 33498:
+ ("4D", "16D", "3D", (16 - 4) // 3 + 1),
+ ("8D", "16D", "40s", (16 * 3600 * 24 - 8 * 3600 * 24) // 40 + 1),
+ ],
+ )
+ def test_timedelta_range_freq_divide_end(self, start, end, freq, expected_periods):
+ # GH 33498 only the cases where `(end % freq) == 0` used to fail
+ res = timedelta_range(start=start, end=end, freq=freq)
+ assert Timedelta(start) == res[0]
+ assert Timedelta(end) >= res[-1]
+ assert len(res) == expected_periods
+
+ def test_timedelta_range_infer_freq(self):
+ # https://github.com/pandas-dev/pandas/issues/35897
+ result = timedelta_range("0s", "1s", periods=31)
+ assert result.freq is None
+
+ @pytest.mark.parametrize(
+ "freq_depr, start, end",
+ [
+ (
+ "3.5l",
+ "05:03:01",
+ "05:03:10",
+ ),
+ (
+ "2.5T",
+ "5 hours",
+ "5 hours 8 minutes",
+ ),
+ (
+ "3.5S",
+ "05:03:01",
+ "05:03:10",
+ ),
+ ],
+ )
+ def test_timedelta_range_removed_freq(self, freq_depr, start, end):
+ # GH#59143
+ msg = f"Invalid frequency: {freq_depr}"
+ with pytest.raises(ValueError, match=msg):
+ timedelta_range(start=start, end=end, freq=freq_depr)
+
+
+class TestTimedeltaRangeUnitInference:
+ def test_timedelta_range_unit_inference_matching_unit(self, unit):
+ start = Timedelta(0).as_unit(unit)
+ end = Timedelta(days=1).as_unit(unit)
+
+ tdi = timedelta_range(start, end, freq="D")
+ assert tdi.unit == unit
+
+ def test_timedelta_range_unit_inference_mismatched_unit(self, unit):
+ start = Timedelta(0).as_unit(unit)
+ end = Timedelta(days=1).as_unit("s")
+
+ tdi = timedelta_range(start, end, freq="D")
+ assert tdi.unit == unit
+
+ tdi = timedelta_range(start, end.as_unit("ns"), freq="D")
+ assert tdi.unit == "ns"
+
+ def test_timedelta_range_unit_inference_tick(self):
+ start = Timedelta(0).as_unit("ms")
+ end = Timedelta(days=1).as_unit("s")
+
+ tdi = timedelta_range(start, end, freq="2000000us")
+ assert tdi.unit == "us"
+
+ tdi = timedelta_range(start, end.as_unit("ns"), freq="2000000us")
+ assert tdi.unit == "ns"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..a33fb1e6979ec48625d0c4bcd451fe2e64333e16
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/common.py
@@ -0,0 +1,41 @@
+"""common utilities"""
+
+from __future__ import annotations
+
+from typing import (
+ Any,
+ Literal,
+)
+
+
+def _mklbl(prefix: str, n: int):
+ return [f"{prefix}{i}" for i in range(n)]
+
+
+def check_indexing_smoketest_or_raises(
+ obj,
+ method: Literal["iloc", "loc"],
+ key: Any,
+ axes: Literal[0, 1] | None = None,
+ fails=None,
+) -> None:
+ if axes is None:
+ axes_list = [0, 1]
+ else:
+ assert axes in [0, 1]
+ axes_list = [axes]
+
+ for ax in axes_list:
+ if ax < obj.ndim:
+ # create a tuple accessor
+ new_axes = [slice(None)] * obj.ndim
+ new_axes[ax] = key
+ axified = tuple(new_axes)
+ try:
+ getattr(obj, method).__getitem__(axified)
+ except (IndexError, TypeError, KeyError) as detail:
+ # if we are in fails, the ok, otherwise raise it
+ if fails is not None:
+ if isinstance(detail, fails):
+ return
+ raise
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/interval/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/interval/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/interval/test_interval.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/interval/test_interval.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c6d1e025b2ccc5d68795b6bb2b1088f63bd1174
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/interval/test_interval.py
@@ -0,0 +1,234 @@
+import numpy as np
+import pytest
+
+from pandas._libs import index as libindex
+from pandas.compat import WASM
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ IntervalIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestIntervalIndex:
+ @pytest.fixture
+ def series_with_interval_index(self):
+ return Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6)))
+
+ def test_getitem_with_scalar(self, series_with_interval_index, indexer_sl):
+ ser = series_with_interval_index.copy()
+
+ expected = ser.iloc[:3]
+ tm.assert_series_equal(expected, indexer_sl(ser)[:3])
+ tm.assert_series_equal(expected, indexer_sl(ser)[:2.5])
+ tm.assert_series_equal(expected, indexer_sl(ser)[0.1:2.5])
+ if indexer_sl is tm.loc:
+ tm.assert_series_equal(expected, ser.loc[-1:3])
+
+ expected = ser.iloc[1:4]
+ tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 2.5, 3.5]])
+ tm.assert_series_equal(expected, indexer_sl(ser)[[2, 3, 4]])
+ tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 3, 4]])
+
+ expected = ser.iloc[2:5]
+ tm.assert_series_equal(expected, indexer_sl(ser)[ser >= 2])
+
+ @pytest.mark.parametrize("direction", ["increasing", "decreasing"])
+ def test_getitem_nonoverlapping_monotonic(self, direction, closed, indexer_sl):
+ tpls = [(0, 1), (2, 3), (4, 5)]
+ if direction == "decreasing":
+ tpls = tpls[::-1]
+
+ idx = IntervalIndex.from_tuples(tpls, closed=closed)
+ ser = Series(list("abc"), idx)
+
+ for key, expected in zip(idx.left, ser):
+ if idx.closed_left:
+ assert indexer_sl(ser)[key] == expected
+ else:
+ with pytest.raises(KeyError, match=str(key)):
+ indexer_sl(ser)[key]
+
+ for key, expected in zip(idx.right, ser):
+ if idx.closed_right:
+ assert indexer_sl(ser)[key] == expected
+ else:
+ with pytest.raises(KeyError, match=str(key)):
+ indexer_sl(ser)[key]
+
+ for key, expected in zip(idx.mid, ser):
+ assert indexer_sl(ser)[key] == expected
+
+ def test_getitem_non_matching(self, series_with_interval_index, indexer_sl):
+ ser = series_with_interval_index.copy()
+
+ # this is a departure from our current
+ # indexing scheme, but simpler
+ with pytest.raises(KeyError, match=r"\[-1\] not in index"):
+ indexer_sl(ser)[[-1, 3, 4, 5]]
+
+ with pytest.raises(KeyError, match=r"\[-1\] not in index"):
+ indexer_sl(ser)[[-1, 3]]
+
+ def test_loc_getitem_large_series(self, monkeypatch):
+ size_cutoff = 20
+ with monkeypatch.context():
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", size_cutoff)
+ ser = Series(
+ np.arange(size_cutoff),
+ index=IntervalIndex.from_breaks(np.arange(size_cutoff + 1)),
+ )
+
+ result1 = ser.loc[:8]
+ result2 = ser.loc[0:8]
+ result3 = ser.loc[0:8:1]
+ tm.assert_series_equal(result1, result2)
+ tm.assert_series_equal(result1, result3)
+
+ def test_loc_getitem_frame(self):
+ # CategoricalIndex with IntervalIndex categories
+ df = DataFrame({"A": range(10)})
+ ser = pd.cut(df.A, 5)
+ df["B"] = ser
+ df = df.set_index("B")
+
+ result = df.loc[4]
+ expected = df.iloc[4:6]
+ tm.assert_frame_equal(result, expected)
+
+ with pytest.raises(KeyError, match="10"):
+ df.loc[10]
+
+ # single list-like
+ result = df.loc[[4]]
+ expected = df.iloc[4:6]
+ tm.assert_frame_equal(result, expected)
+
+ # non-unique
+ result = df.loc[[4, 5]]
+ expected = df.take([4, 5, 4, 5])
+ tm.assert_frame_equal(result, expected)
+
+ msg = (
+ r"None of \[Index\(\[10\], dtype='object', name='B'\)\] "
+ r"are in the \[index\]"
+ )
+ with pytest.raises(KeyError, match=msg):
+ df.loc[[10]]
+
+ # partial missing
+ with pytest.raises(KeyError, match=r"\[10\] not in index"):
+ df.loc[[10, 4]]
+
+ def test_getitem_interval_with_nans(self, frame_or_series, indexer_sl):
+ # GH#41831
+
+ index = IntervalIndex([np.nan, np.nan])
+ key = index[:-1]
+
+ obj = frame_or_series(range(2), index=index)
+ if frame_or_series is DataFrame and indexer_sl is tm.setitem:
+ obj = obj.T
+
+ result = indexer_sl(obj)[key]
+ expected = obj
+
+ tm.assert_equal(result, expected)
+
+ def test_setitem_interval_with_slice(self):
+ # GH#54722
+ ii = IntervalIndex.from_breaks(range(4, 15))
+ ser = Series(range(10), index=ii)
+
+ orig = ser.copy()
+
+ # This should be a no-op (used to raise)
+ ser.loc[1:3] = 20
+ tm.assert_series_equal(ser, orig)
+
+ ser.loc[6:8] = 19
+ orig.iloc[1:4] = 19
+ tm.assert_series_equal(ser, orig)
+
+ ser2 = Series(range(5), index=ii[::2])
+ orig2 = ser2.copy()
+
+ # this used to raise
+ ser2.loc[6:8] = 22 # <- raises on main, sets on branch
+ orig2.iloc[1] = 22
+ tm.assert_series_equal(ser2, orig2)
+
+ ser2.loc[5:7] = 21
+ orig2.iloc[:2] = 21
+ tm.assert_series_equal(ser2, orig2)
+
+
+class TestIntervalIndexInsideMultiIndex:
+ def test_mi_intervalindex_slicing_with_scalar(self):
+ # GH#27456
+ ii = IntervalIndex.from_arrays(
+ [0, 1, 10, 11, 0, 1, 10, 11], [1, 2, 11, 12, 1, 2, 11, 12], name="MP"
+ )
+ idx = pd.MultiIndex.from_arrays(
+ [
+ pd.Index(["FC", "FC", "FC", "FC", "OWNER", "OWNER", "OWNER", "OWNER"]),
+ pd.Index(
+ ["RID1", "RID1", "RID2", "RID2", "RID1", "RID1", "RID2", "RID2"]
+ ),
+ ii,
+ ]
+ )
+
+ idx.names = ["Item", "RID", "MP"]
+ df = DataFrame({"value": [1, 2, 3, 4, 5, 6, 7, 8]})
+ df.index = idx
+
+ query_df = DataFrame(
+ {
+ "Item": ["FC", "OWNER", "FC", "OWNER", "OWNER"],
+ "RID": ["RID1", "RID1", "RID1", "RID2", "RID2"],
+ "MP": [0.2, 1.5, 1.6, 11.1, 10.9],
+ }
+ )
+
+ query_df = query_df.sort_index()
+
+ idx = pd.MultiIndex.from_arrays([query_df.Item, query_df.RID, query_df.MP])
+ query_df.index = idx
+ result = df.value.loc[query_df.index]
+
+ # the IntervalIndex level is indexed with floats, which map to
+ # the intervals containing them. Matching the behavior we would get
+ # with _only_ an IntervalIndex, we get an IntervalIndex level back.
+ sliced_level = ii.take([0, 1, 1, 3, 2])
+ expected_index = pd.MultiIndex.from_arrays(
+ [idx.get_level_values(0), idx.get_level_values(1), sliced_level]
+ )
+ expected = Series([1, 6, 2, 8, 7], index=expected_index, name="value")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.xfail(WASM, reason="GH 23440")
+ @pytest.mark.parametrize("base", [101, 1010])
+ def test_reindex_behavior_with_interval_index(self, base):
+ # GH 51826
+
+ ser = Series(
+ range(base),
+ index=IntervalIndex.from_arrays(range(base), range(1, base + 1)),
+ )
+ expected_result = Series([np.nan, 0], index=[np.nan, 1.0], dtype=float)
+ result = ser.reindex(index=[np.nan, 1.0])
+ tm.assert_series_equal(result, expected_result)
+
+ def test_multiindex_with_interval_index(self):
+ # for GH#25298
+ intIndex = IntervalIndex.from_arrays([1, 5, 8, 13, 16], [4, 9, 12, 17, 20])
+ multiIndex = pd.MultiIndex.from_arrays([["a", "a", "b", "b", "c"], intIndex])
+ data = [(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
+ df = DataFrame(data, index=multiIndex)
+ result = df.loc[("b", 16)]
+ expected = Series([7, 8], name=("b", pd.Interval(13, 17, closed="right")))
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/interval/test_interval_new.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/interval/test_interval_new.py
new file mode 100644
index 0000000000000000000000000000000000000000..051dc7b98f2aa3c8294c715bc5dbb2637d99552a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/interval/test_interval_new.py
@@ -0,0 +1,235 @@
+import re
+
+import numpy as np
+import pytest
+
+from pandas.compat import WASM
+
+from pandas import (
+ Index,
+ Interval,
+ IntervalIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestIntervalIndex:
+ @pytest.fixture
+ def series_with_interval_index(self):
+ """
+ Fixture providing a Series with an IntervalIndex.
+ """
+ return Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6)))
+
+ def test_loc_with_interval(self, series_with_interval_index, indexer_sl):
+ # loc with single label / list of labels:
+ # - Intervals: only exact matches
+ # - scalars: those that contain it
+
+ ser = series_with_interval_index.copy()
+
+ expected = 0
+ result = indexer_sl(ser)[Interval(0, 1)]
+ assert result == expected
+
+ expected = ser.iloc[3:5]
+ result = indexer_sl(ser)[[Interval(3, 4), Interval(4, 5)]]
+ tm.assert_series_equal(expected, result)
+
+ # missing or not exact
+ with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='left')")):
+ indexer_sl(ser)[Interval(3, 5, closed="left")]
+
+ with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")):
+ indexer_sl(ser)[Interval(3, 5)]
+
+ with pytest.raises(
+ KeyError, match=re.escape("Interval(-2, 0, closed='right')")
+ ):
+ indexer_sl(ser)[Interval(-2, 0)]
+
+ with pytest.raises(KeyError, match=re.escape("Interval(5, 6, closed='right')")):
+ indexer_sl(ser)[Interval(5, 6)]
+
+ def test_loc_with_scalar(self, series_with_interval_index, indexer_sl):
+ # loc with single label / list of labels:
+ # - Intervals: only exact matches
+ # - scalars: those that contain it
+
+ ser = series_with_interval_index.copy()
+
+ assert indexer_sl(ser)[1] == 0
+ assert indexer_sl(ser)[1.5] == 1
+ assert indexer_sl(ser)[2] == 1
+
+ expected = ser.iloc[1:4]
+ tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 2.5, 3.5]])
+ tm.assert_series_equal(expected, indexer_sl(ser)[[2, 3, 4]])
+ tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 3, 4]])
+
+ expected = ser.iloc[[1, 1, 2, 1]]
+ tm.assert_series_equal(expected, indexer_sl(ser)[[1.5, 2, 2.5, 1.5]])
+
+ expected = ser.iloc[2:5]
+ tm.assert_series_equal(expected, indexer_sl(ser)[ser >= 2])
+
+ def test_loc_with_slices(self, series_with_interval_index, indexer_sl):
+ # loc with slices:
+ # - Interval objects: only works with exact matches
+ # - scalars: only works for non-overlapping, monotonic intervals,
+ # and start/stop select location based on the interval that
+ # contains them:
+ # (slice_loc(start, stop) == (idx.get_loc(start), idx.get_loc(stop))
+
+ ser = series_with_interval_index.copy()
+
+ # slice of interval
+
+ expected = ser.iloc[:3]
+ result = indexer_sl(ser)[Interval(0, 1) : Interval(2, 3)]
+ tm.assert_series_equal(expected, result)
+
+ expected = ser.iloc[3:]
+ result = indexer_sl(ser)[Interval(3, 4) :]
+ tm.assert_series_equal(expected, result)
+
+ msg = "Interval objects are not currently supported"
+ with pytest.raises(NotImplementedError, match=msg):
+ indexer_sl(ser)[Interval(3, 6) :]
+
+ with pytest.raises(NotImplementedError, match=msg):
+ indexer_sl(ser)[Interval(3, 4, closed="left") :]
+
+ def test_slice_step_ne1(self, series_with_interval_index):
+ # GH#31658 slice of scalar with step != 1
+ ser = series_with_interval_index.copy()
+ expected = ser.iloc[0:4:2]
+
+ result = ser[0:4:2]
+ tm.assert_series_equal(result, expected)
+
+ result2 = ser[0:4][::2]
+ tm.assert_series_equal(result2, expected)
+
+ def test_slice_float_start_stop(self, series_with_interval_index):
+ # GH#31658 slicing with integers is positional, with floats is not
+ # supported
+ ser = series_with_interval_index.copy()
+
+ msg = "label-based slicing with step!=1 is not supported for IntervalIndex"
+ with pytest.raises(ValueError, match=msg):
+ ser[1.5:9.5:2]
+
+ def test_slice_interval_step(self, series_with_interval_index):
+ # GH#31658 allows for integer step!=1, not Interval step
+ ser = series_with_interval_index.copy()
+ msg = "label-based slicing with step!=1 is not supported for IntervalIndex"
+ with pytest.raises(ValueError, match=msg):
+ ser[0 : 4 : Interval(0, 1)]
+
+ def test_loc_with_overlap(self, indexer_sl):
+ idx = IntervalIndex.from_tuples([(1, 5), (3, 7)])
+ ser = Series(range(len(idx)), index=idx)
+
+ # scalar
+ expected = ser
+ result = indexer_sl(ser)[4]
+ tm.assert_series_equal(expected, result)
+
+ result = indexer_sl(ser)[[4]]
+ tm.assert_series_equal(expected, result)
+
+ # interval
+ expected = 0
+ result = indexer_sl(ser)[Interval(1, 5)]
+ assert expected == result
+
+ expected = ser
+ result = indexer_sl(ser)[[Interval(1, 5), Interval(3, 7)]]
+ tm.assert_series_equal(expected, result)
+
+ with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")):
+ indexer_sl(ser)[Interval(3, 5)]
+
+ msg = (
+ r"None of \[IntervalIndex\(\[\(3, 5\]\], "
+ r"dtype='interval\[int64, right\]'\)\] are in the \[index\]"
+ )
+ with pytest.raises(KeyError, match=msg):
+ indexer_sl(ser)[[Interval(3, 5)]]
+
+ # slices with interval (only exact matches)
+ expected = ser
+ result = indexer_sl(ser)[Interval(1, 5) : Interval(3, 7)]
+ tm.assert_series_equal(expected, result)
+
+ msg = (
+ "'can only get slices from an IntervalIndex if bounds are "
+ "non-overlapping and all monotonic increasing or decreasing'"
+ )
+ with pytest.raises(KeyError, match=msg):
+ indexer_sl(ser)[Interval(1, 6) : Interval(3, 8)]
+
+ if indexer_sl is tm.loc:
+ # slices with scalar raise for overlapping intervals
+ # TODO KeyError is the appropriate error?
+ with pytest.raises(KeyError, match=msg):
+ ser.loc[1:4]
+
+ def test_non_unique(self, indexer_sl):
+ idx = IntervalIndex.from_tuples([(1, 3), (3, 7)])
+ ser = Series(range(len(idx)), index=idx)
+
+ result = indexer_sl(ser)[Interval(1, 3)]
+ assert result == 0
+
+ result = indexer_sl(ser)[[Interval(1, 3)]]
+ expected = ser.iloc[0:1]
+ tm.assert_series_equal(expected, result)
+
+ def test_non_unique_moar(self, indexer_sl):
+ idx = IntervalIndex.from_tuples([(1, 3), (1, 3), (3, 7)])
+ ser = Series(range(len(idx)), index=idx)
+
+ expected = ser.iloc[[0, 1]]
+ result = indexer_sl(ser)[Interval(1, 3)]
+ tm.assert_series_equal(expected, result)
+
+ expected = ser
+ result = indexer_sl(ser)[Interval(1, 3) :]
+ tm.assert_series_equal(expected, result)
+
+ expected = ser.iloc[[0, 1]]
+ result = indexer_sl(ser)[[Interval(1, 3)]]
+ tm.assert_series_equal(expected, result)
+
+ def test_loc_getitem_missing_key_error_message(
+ self, frame_or_series, series_with_interval_index
+ ):
+ # GH#27365
+ ser = series_with_interval_index.copy()
+ obj = frame_or_series(ser)
+ with pytest.raises(KeyError, match=r"\[6\]"):
+ obj.loc[[4, 5, 6]]
+
+
+@pytest.mark.xfail(WASM, reason="GH 23440")
+@pytest.mark.parametrize(
+ "intervals",
+ [
+ ([Interval(-np.inf, 0.0), Interval(0.0, 1.0)]),
+ ([Interval(-np.inf, -2.0), Interval(-2.0, -1.0)]),
+ ([Interval(-1.0, 0.0), Interval(0.0, np.inf)]),
+ ([Interval(1.0, 2.0), Interval(2.0, np.inf)]),
+ ],
+)
+def test_repeating_interval_index_with_infs(intervals):
+ # GH 46658
+
+ interval_index = Index(intervals * 51)
+
+ expected = np.arange(1, 102, 2, dtype=np.intp)
+ result = interval_index.get_indexer_for([intervals[1]])
+
+ tm.assert_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_chaining_and_caching.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_chaining_and_caching.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7ed21a2cc001ff523b328bacee953c05d278099
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_chaining_and_caching.py
@@ -0,0 +1,87 @@
+import numpy as np
+
+from pandas._libs import index as libindex
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ RangeIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+def test_detect_chained_assignment():
+ # Inplace ops, originally from:
+ # https://stackoverflow.com/questions/20508968/series-fillna-in-a-multiindex-dataframe-does-not-fill-is-this-a-bug
+ a = [12, 23]
+ b = [123, None]
+ c = [1234, 2345]
+ d = [12345, 23456]
+ tuples = [("eyes", "left"), ("eyes", "right"), ("ears", "left"), ("ears", "right")]
+ events = {
+ ("eyes", "left"): a,
+ ("eyes", "right"): b,
+ ("ears", "left"): c,
+ ("ears", "right"): d,
+ }
+ multiind = MultiIndex.from_tuples(tuples, names=["part", "side"])
+ zed = DataFrame(events, index=["a", "b"], columns=multiind)
+
+ with tm.raises_chained_assignment_error():
+ zed["eyes"]["right"].fillna(value=555, inplace=True)
+
+
+def test_cache_updating():
+ # 5216
+ # make sure that we don't try to set a dead cache
+ a = np.random.default_rng(2).random((10, 3))
+ df = DataFrame(a, columns=["x", "y", "z"])
+ df_original = df.copy()
+ tuples = [(i, j) for i in range(5) for j in range(2)]
+ index = MultiIndex.from_tuples(tuples)
+ df.index = index
+
+ # setting via chained assignment
+ # but actually works, since everything is a view
+
+ with tm.raises_chained_assignment_error():
+ df.loc[0]["z"].iloc[0] = 1.0
+
+ assert df.loc[(0, 0), "z"] == df_original.loc[0, "z"]
+
+ # correct setting
+ df.loc[(0, 0), "z"] = 2
+ result = df.loc[(0, 0), "z"]
+ assert result == 2
+
+
+def test_indexer_caching(monkeypatch):
+ # GH5727
+ # make sure that indexers are in the _internal_names_set
+ size_cutoff = 20
+ with monkeypatch.context():
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", size_cutoff)
+ index = MultiIndex.from_arrays([np.arange(size_cutoff), np.arange(size_cutoff)])
+ s = Series(np.zeros(size_cutoff), index=index)
+
+ # setitem
+ s[s == 0] = 1
+ expected = Series(np.ones(size_cutoff), index=index)
+ tm.assert_series_equal(s, expected)
+
+
+def test_set_names_only_clears_level_cache():
+ mi = MultiIndex.from_arrays([range(4), range(4)], names=["a", "b"])
+ mi.dtypes
+ mi.is_monotonic_increasing
+ mi._engine
+ mi.levels
+ old_cache_keys = sorted(mi._cache.keys())
+ assert old_cache_keys == ["_engine", "dtypes", "is_monotonic_increasing", "levels"]
+ mi.names = ["A", "B"]
+ new_cache_keys = sorted(mi._cache.keys())
+ assert new_cache_keys == ["_engine", "dtypes", "is_monotonic_increasing"]
+ new_levels = mi.levels
+ tm.assert_index_equal(new_levels[0], RangeIndex(4, name="A"))
+ tm.assert_index_equal(new_levels[1], RangeIndex(4, name="B"))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_datetime.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..d325971e7baf69fb3119afc018c6f90da93e0d3b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_datetime.py
@@ -0,0 +1,50 @@
+from datetime import datetime
+
+import numpy as np
+
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Period,
+ Series,
+ period_range,
+ to_datetime,
+)
+import pandas._testing as tm
+
+
+def test_multiindex_period_datetime():
+ # GH4861, using datetime in period of multiindex raises exception
+
+ idx1 = Index(["a", "a", "a", "b", "b"])
+ idx2 = period_range("2012-01", periods=len(idx1), freq="M")
+ s = Series(np.random.default_rng(2).standard_normal(len(idx1)), [idx1, idx2])
+
+ # try Period as index
+ expected = s.iloc[0]
+ result = s.loc["a", Period("2012-01")]
+ assert result == expected
+
+ # try datetime as index
+ result = s.loc["a", datetime(2012, 1, 1)]
+ assert result == expected
+
+
+def test_multiindex_datetime_columns():
+ # GH35015, using datetime as column indices raises exception
+
+ mi = MultiIndex.from_tuples(
+ [(to_datetime("02/29/2020"), to_datetime("03/01/2020"))], names=["a", "b"]
+ )
+
+ df = DataFrame([], columns=mi)
+
+ expected_df = DataFrame(
+ [],
+ columns=MultiIndex.from_arrays(
+ [[to_datetime("02/29/2020")], [to_datetime("03/01/2020")]], names=["a", "b"]
+ ),
+ )
+
+ tm.assert_frame_equal(df, expected_df)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_getitem.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_getitem.py
new file mode 100644
index 0000000000000000000000000000000000000000..41e90fca281e1ac950771c523c65198f390851b6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_getitem.py
@@ -0,0 +1,414 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+from pandas.core.indexing import IndexingError
+
+# ----------------------------------------------------------------------------
+# test indexing of Series with multi-level Index
+# ----------------------------------------------------------------------------
+
+
+@pytest.mark.parametrize(
+ "access_method",
+ [lambda s, x: s[:, x], lambda s, x: s.loc[:, x], lambda s, x: s.xs(x, level=1)],
+)
+@pytest.mark.parametrize(
+ "level1_value, expected",
+ [(0, Series([1], index=[0])), (1, Series([2, 3], index=[1, 2]))],
+)
+def test_series_getitem_multiindex(access_method, level1_value, expected):
+ # GH 6018
+ # series regression getitem with a multi-index
+
+ mi = MultiIndex.from_tuples([(0, 0), (1, 1), (2, 1)], names=["A", "B"])
+ ser = Series([1, 2, 3], index=mi)
+ expected.index.name = "A"
+
+ result = access_method(ser, level1_value)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("level0_value", ["D", "A"])
+def test_series_getitem_duplicates_multiindex(level0_value):
+ # GH 5725 the 'A' happens to be a valid Timestamp so the doesn't raise
+ # the appropriate error, only in PY3 of course!
+
+ index = MultiIndex(
+ levels=[[level0_value, "B", "C"], [0, 26, 27, 37, 57, 67, 75, 82]],
+ codes=[[0, 0, 0, 1, 2, 2, 2, 2, 2, 2], [1, 3, 4, 6, 0, 2, 2, 3, 5, 7]],
+ names=["tag", "day"],
+ )
+ arr = np.random.default_rng(2).standard_normal((len(index), 1))
+ df = DataFrame(arr, index=index, columns=["val"])
+
+ # confirm indexing on missing value raises KeyError
+ if level0_value != "A":
+ with pytest.raises(KeyError, match=r"^'A'$"):
+ df.val["A"]
+
+ with pytest.raises(KeyError, match=r"^'X'$"):
+ df.val["X"]
+
+ result = df.val[level0_value]
+ expected = Series(
+ arr.ravel()[0:3], name="val", index=Index([26, 37, 57], name="day")
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_getitem(multiindex_year_month_day_dataframe_random_data, indexer_sl):
+ s = multiindex_year_month_day_dataframe_random_data["A"]
+ expected = s.reindex(s.index[42:65])
+ expected.index = expected.index.droplevel(0).droplevel(0)
+
+ result = indexer_sl(s)[2000, 3]
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_getitem_returns_scalar(
+ multiindex_year_month_day_dataframe_random_data, indexer_sl
+):
+ s = multiindex_year_month_day_dataframe_random_data["A"]
+ expected = s.iloc[49]
+
+ result = indexer_sl(s)[2000, 3, 10]
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "indexer,expected_error,expected_error_msg",
+ [
+ (lambda s: s.__getitem__((2000, 3, 4)), KeyError, r"^\(2000, 3, 4\)$"),
+ (lambda s: s[(2000, 3, 4)], KeyError, r"^\(2000, 3, 4\)$"),
+ (lambda s: s.loc[(2000, 3, 4)], KeyError, r"^\(2000, 3, 4\)$"),
+ (lambda s: s.loc[(2000, 3, 4, 5)], IndexingError, "Too many indexers"),
+ (
+ lambda s: s.__getitem__(len(s)),
+ KeyError,
+ "100",
+ ), # match should include len(s)
+ (lambda s: s[len(s)], KeyError, "100"), # match should include len(s)
+ (
+ lambda s: s.iloc[len(s)],
+ IndexError,
+ "single positional indexer is out-of-bounds",
+ ),
+ ],
+)
+def test_series_getitem_indexing_errors(
+ multiindex_year_month_day_dataframe_random_data,
+ indexer,
+ expected_error,
+ expected_error_msg,
+):
+ s = multiindex_year_month_day_dataframe_random_data["A"]
+ with pytest.raises(expected_error, match=expected_error_msg):
+ indexer(s)
+
+
+def test_series_getitem_corner_generator(
+ multiindex_year_month_day_dataframe_random_data,
+):
+ s = multiindex_year_month_day_dataframe_random_data["A"]
+ result = s[(x > 0 for x in s)]
+ expected = s[s > 0]
+ tm.assert_series_equal(result, expected)
+
+
+# ----------------------------------------------------------------------------
+# test indexing of DataFrame with multi-level Index
+# ----------------------------------------------------------------------------
+
+
+def test_getitem_simple(multiindex_dataframe_random_data):
+ df = multiindex_dataframe_random_data.T
+ expected = df.values[:, 0]
+ result = df["foo", "one"].values
+ tm.assert_almost_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "indexer,expected_error_msg",
+ [
+ (lambda df: df[("foo", "four")], r"^\('foo', 'four'\)$"),
+ (lambda df: df["foobar"], r"^'foobar'$"),
+ ],
+)
+def test_frame_getitem_simple_key_error(
+ multiindex_dataframe_random_data, indexer, expected_error_msg
+):
+ df = multiindex_dataframe_random_data.T
+ with pytest.raises(KeyError, match=expected_error_msg):
+ indexer(df)
+
+
+def test_tuple_string_column_names():
+ # GH#50372
+ mi = MultiIndex.from_tuples([("a", "aa"), ("a", "ab"), ("b", "ba"), ("b", "bb")])
+ df = DataFrame([range(4), range(1, 5), range(2, 6)], columns=mi)
+ df["single_index"] = 0
+
+ df_flat = df.copy()
+ df_flat.columns = df_flat.columns.to_flat_index()
+ df_flat["new_single_index"] = 0
+
+ result = df_flat[[("a", "aa"), "new_single_index"]]
+ expected = DataFrame(
+ [[0, 0], [1, 0], [2, 0]], columns=Index([("a", "aa"), "new_single_index"])
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_frame_getitem_multicolumn_empty_level():
+ df = DataFrame({"a": ["1", "2", "3"], "b": ["2", "3", "4"]})
+ df.columns = [
+ ["level1 item1", "level1 item2"],
+ ["", "level2 item2"],
+ ["level3 item1", "level3 item2"],
+ ]
+
+ result = df["level1 item1"]
+ expected = DataFrame(
+ [["1"], ["2"], ["3"]], index=df.index, columns=["level3 item1"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "indexer,expected_slice",
+ [
+ (lambda df: df["foo"], slice(3)),
+ (lambda df: df["bar"], slice(3, 5)),
+ (lambda df: df.loc[:, "bar"], slice(3, 5)),
+ ],
+)
+def test_frame_getitem_toplevel(
+ multiindex_dataframe_random_data, indexer, expected_slice
+):
+ df = multiindex_dataframe_random_data.T
+ expected = df.reindex(columns=df.columns[expected_slice])
+ expected.columns = expected.columns.droplevel(0)
+ result = indexer(df)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_frame_mixed_depth_get():
+ arrays = [
+ ["a", "top", "top", "routine1", "routine1", "routine2"],
+ ["", "OD", "OD", "result1", "result2", "result1"],
+ ["", "wx", "wy", "", "", ""],
+ ]
+
+ tuples = sorted(zip(*arrays))
+ index = MultiIndex.from_tuples(tuples)
+ df = DataFrame(np.random.default_rng(2).standard_normal((4, 6)), columns=index)
+
+ result = df["a"]
+ expected = df["a", "", ""].rename("a")
+ tm.assert_series_equal(result, expected)
+
+ result = df["routine1", "result1"]
+ expected = df["routine1", "result1", ""]
+ expected = expected.rename(("routine1", "result1"))
+ tm.assert_series_equal(result, expected)
+
+
+def test_frame_getitem_nan_multiindex(nulls_fixture):
+ # GH#29751
+ # loc on a multiindex containing nan values
+ n = nulls_fixture # for code readability
+ cols = ["a", "b", "c"]
+ df = DataFrame(
+ [[11, n, 13], [21, n, 23], [31, n, 33], [41, n, 43]],
+ columns=cols,
+ ).set_index(["a", "b"])
+ df["c"] = df["c"].astype("int64")
+
+ idx = (21, n)
+ result = df.loc[:idx]
+ expected = DataFrame([[11, n, 13], [21, n, 23]], columns=cols).set_index(["a", "b"])
+ expected["c"] = expected["c"].astype("int64")
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[idx:]
+ expected = DataFrame(
+ [[21, n, 23], [31, n, 33], [41, n, 43]], columns=cols
+ ).set_index(["a", "b"])
+ expected["c"] = expected["c"].astype("int64")
+ tm.assert_frame_equal(result, expected)
+
+ idx1, idx2 = (21, n), (31, n)
+ result = df.loc[idx1:idx2]
+ expected = DataFrame([[21, n, 23], [31, n, 33]], columns=cols).set_index(["a", "b"])
+ expected["c"] = expected["c"].astype("int64")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "indexer,expected",
+ [
+ (
+ (["b"], ["bar", np.nan]),
+ (
+ DataFrame(
+ [[2, 3], [5, 6]],
+ columns=MultiIndex.from_tuples([("b", "bar"), ("b", np.nan)]),
+ dtype="int64",
+ )
+ ),
+ ),
+ (
+ (["a", "b"]),
+ (
+ DataFrame(
+ [[1, 2, 3], [4, 5, 6]],
+ columns=MultiIndex.from_tuples(
+ [("a", "foo"), ("b", "bar"), ("b", np.nan)]
+ ),
+ dtype="int64",
+ )
+ ),
+ ),
+ (
+ (["b"]),
+ (
+ DataFrame(
+ [[2, 3], [5, 6]],
+ columns=MultiIndex.from_tuples([("b", "bar"), ("b", np.nan)]),
+ dtype="int64",
+ )
+ ),
+ ),
+ (
+ (["b"], ["bar"]),
+ (
+ DataFrame(
+ [[2], [5]],
+ columns=MultiIndex.from_tuples([("b", "bar")]),
+ dtype="int64",
+ )
+ ),
+ ),
+ (
+ (["b"], [np.nan]),
+ (
+ DataFrame(
+ [[3], [6]],
+ columns=MultiIndex(
+ codes=[[1], [-1]], levels=[["a", "b"], ["bar", "foo"]]
+ ),
+ dtype="int64",
+ )
+ ),
+ ),
+ (("b", np.nan), Series([3, 6], dtype="int64", name=("b", np.nan))),
+ ],
+)
+def test_frame_getitem_nan_cols_multiindex(
+ indexer,
+ expected,
+ nulls_fixture,
+):
+ # Slicing MultiIndex including levels with nan values, for more information
+ # see GH#25154
+ df = DataFrame(
+ [[1, 2, 3], [4, 5, 6]],
+ columns=MultiIndex.from_tuples(
+ [("a", "foo"), ("b", "bar"), ("b", nulls_fixture)]
+ ),
+ dtype="int64",
+ )
+
+ result = df.loc[:, indexer]
+ tm.assert_equal(result, expected)
+
+
+# ----------------------------------------------------------------------------
+# test indexing of DataFrame with multi-level Index with duplicates
+# ----------------------------------------------------------------------------
+
+
+@pytest.fixture
+def dataframe_with_duplicate_index():
+ """Fixture for DataFrame used in tests for gh-4145 and gh-4146"""
+ data = [["a", "d", "e", "c", "f", "b"], [1, 4, 5, 3, 6, 2], [1, 4, 5, 3, 6, 2]]
+ index = ["h1", "h3", "h5"]
+ columns = MultiIndex(
+ levels=[["A", "B"], ["A1", "A2", "B1", "B2"]],
+ codes=[[0, 0, 0, 1, 1, 1], [0, 3, 3, 0, 1, 2]],
+ names=["main", "sub"],
+ )
+ return DataFrame(data, index=index, columns=columns)
+
+
+@pytest.mark.parametrize(
+ "indexer", [lambda df: df[("A", "A1")], lambda df: df.loc[:, ("A", "A1")]]
+)
+def test_frame_mi_access(dataframe_with_duplicate_index, indexer):
+ # GH 4145
+ df = dataframe_with_duplicate_index
+ index = Index(["h1", "h3", "h5"])
+ columns = MultiIndex.from_tuples([("A", "A1")], names=["main", "sub"])
+ expected = DataFrame([["a", 1, 1]], index=columns, columns=index).T
+
+ result = indexer(df)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_frame_mi_access_returns_series(dataframe_with_duplicate_index):
+ # GH 4146, not returning a block manager when selecting a unique index
+ # from a duplicate index
+ # as of 4879, this returns a Series (which is similar to what happens
+ # with a non-unique)
+ df = dataframe_with_duplicate_index
+ expected = Series(["a", 1, 1], index=["h1", "h3", "h5"], name="A1")
+ result = df["A"]["A1"]
+ tm.assert_series_equal(result, expected)
+
+
+def test_frame_mi_access_returns_frame(dataframe_with_duplicate_index):
+ # selecting a non_unique from the 2nd level
+ df = dataframe_with_duplicate_index
+ expected = DataFrame(
+ [["d", 4, 4], ["e", 5, 5]],
+ index=Index(["B2", "B2"], name="sub"),
+ columns=["h1", "h3", "h5"],
+ ).T
+ result = df["A"]["B2"]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_frame_mi_empty_slice():
+ # GH 15454
+ df = DataFrame(0, index=range(2), columns=MultiIndex.from_product([[1], [2]]))
+ result = df[[]]
+ expected = DataFrame(
+ index=[0, 1], columns=MultiIndex(levels=[[1], [2]], codes=[[], []])
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_empty_multiindex():
+ # GH#36936
+ arrays = [["a", "a", "b", "a"], ["a", "a", "b", "b"]]
+ index = MultiIndex.from_arrays(arrays, names=("idx1", "idx2"))
+ df = DataFrame([1, 2, 3, 4], index=index, columns=["value"])
+
+ # loc on empty multiindex == loc with False mask
+ empty_multiindex = df.loc[df.loc[:, "value"] == 0, :].index
+ result = df.loc[empty_multiindex, :]
+ expected = df.loc[[False] * len(df.index), :]
+ tm.assert_frame_equal(result, expected)
+
+ # replacing value with loc on empty multiindex
+ df.loc[df.loc[df.loc[:, "value"] == 0].index, "value"] = 5
+ result = df
+ expected = DataFrame([1, 2, 3, 4], index=index, columns=["value"])
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_iloc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_iloc.py
new file mode 100644
index 0000000000000000000000000000000000000000..8939ecc78000be08812afb702358e7eee1ae9499
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_iloc.py
@@ -0,0 +1,171 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def simple_multiindex_dataframe():
+ """
+ Factory function to create simple 3 x 3 dataframe with
+ both columns and row MultiIndex using supplied data or
+ random data by default.
+ """
+
+ data = np.random.default_rng(2).standard_normal((3, 3))
+ return DataFrame(
+ data, columns=[[2, 2, 4], [6, 8, 10]], index=[[4, 4, 8], [8, 10, 12]]
+ )
+
+
+@pytest.mark.parametrize(
+ "indexer, expected",
+ [
+ (
+ lambda df: df.iloc[0],
+ lambda arr: Series(arr[0], index=[[2, 2, 4], [6, 8, 10]], name=(4, 8)),
+ ),
+ (
+ lambda df: df.iloc[2],
+ lambda arr: Series(arr[2], index=[[2, 2, 4], [6, 8, 10]], name=(8, 12)),
+ ),
+ (
+ lambda df: df.iloc[:, 2],
+ lambda arr: Series(arr[:, 2], index=[[4, 4, 8], [8, 10, 12]], name=(4, 10)),
+ ),
+ ],
+)
+def test_iloc_returns_series(indexer, expected, simple_multiindex_dataframe):
+ df = simple_multiindex_dataframe
+ arr = df.values
+ result = indexer(df)
+ expected = expected(arr)
+ tm.assert_series_equal(result, expected)
+
+
+def test_iloc_returns_dataframe(simple_multiindex_dataframe):
+ df = simple_multiindex_dataframe
+ result = df.iloc[[0, 1]]
+ expected = df.xs(4, drop_level=False)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_iloc_returns_scalar(simple_multiindex_dataframe):
+ df = simple_multiindex_dataframe
+ arr = df.values
+ result = df.iloc[2, 2]
+ expected = arr[2, 2]
+ assert result == expected
+
+
+def test_iloc_getitem_multiple_items():
+ # GH 5528
+ tup = zip(*[["a", "a", "b", "b"], ["x", "y", "x", "y"]])
+ index = MultiIndex.from_tuples(tup)
+ df = DataFrame(np.random.default_rng(2).standard_normal((4, 4)), index=index)
+ result = df.iloc[[2, 3]]
+ expected = df.xs("b", drop_level=False)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_iloc_getitem_labels():
+ # this is basically regular indexing
+ arr = np.random.default_rng(2).standard_normal((4, 3))
+ df = DataFrame(
+ arr,
+ columns=[["i", "i", "j"], ["A", "A", "B"]],
+ index=[["i", "i", "j", "k"], ["X", "X", "Y", "Y"]],
+ )
+ result = df.iloc[2, 2]
+ expected = arr[2, 2]
+ assert result == expected
+
+
+def test_frame_getitem_slice(multiindex_dataframe_random_data):
+ df = multiindex_dataframe_random_data
+ result = df.iloc[:4]
+ expected = df[:4]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_frame_setitem_slice(multiindex_dataframe_random_data):
+ df = multiindex_dataframe_random_data
+ df.iloc[:4] = 0
+
+ assert (df.values[:4] == 0).all()
+ assert (df.values[4:] != 0).all()
+
+
+def test_indexing_ambiguity_bug_1678():
+ # GH 1678
+ columns = MultiIndex.from_tuples(
+ [("Ohio", "Green"), ("Ohio", "Red"), ("Colorado", "Green")]
+ )
+ index = MultiIndex.from_tuples([("a", 1), ("a", 2), ("b", 1), ("b", 2)])
+
+ df = DataFrame(np.arange(12).reshape((4, 3)), index=index, columns=columns)
+
+ result = df.iloc[:, 1]
+ expected = df.loc[:, ("Ohio", "Red")]
+ tm.assert_series_equal(result, expected)
+
+
+def test_iloc_integer_locations():
+ # GH 13797
+ data = [
+ ["str00", "str01"],
+ ["str10", "str11"],
+ ["str20", "srt21"],
+ ["str30", "str31"],
+ ["str40", "str41"],
+ ]
+
+ index = MultiIndex.from_tuples(
+ [("CC", "A"), ("CC", "B"), ("CC", "B"), ("BB", "a"), ("BB", "b")]
+ )
+
+ expected = DataFrame(data)
+ df = DataFrame(data, index=index)
+
+ result = DataFrame([[df.iloc[r, c] for c in range(2)] for r in range(5)])
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data, indexes, values, expected_k",
+ [
+ # test without indexer value in first level of MultiIndex
+ ([[2, 22, 5], [2, 33, 6]], [0, -1, 1], [2, 3, 1], [7, 10]),
+ # test like code sample 1 in the issue
+ ([[1, 22, 555], [1, 33, 666]], [0, -1, 1], [200, 300, 100], [755, 1066]),
+ # test like code sample 2 in the issue
+ ([[1, 3, 7], [2, 4, 8]], [0, -1, 1], [10, 10, 1000], [17, 1018]),
+ # test like code sample 3 in the issue
+ ([[1, 11, 4], [2, 22, 5], [3, 33, 6]], [0, -1, 1], [4, 7, 10], [8, 15, 13]),
+ ],
+)
+def test_iloc_setitem_int_multiindex_series(data, indexes, values, expected_k):
+ # GH17148
+ df = DataFrame(data=data, columns=["i", "j", "k"])
+ df = df.set_index(["i", "j"])
+
+ series = df.k.copy()
+ for i, v in zip(indexes, values):
+ series.iloc[i] += v
+
+ df["k"] = expected_k
+ expected = df.k
+ tm.assert_series_equal(series, expected)
+
+
+def test_getitem_iloc(multiindex_dataframe_random_data):
+ df = multiindex_dataframe_random_data
+ result = df.iloc[2]
+ expected = df.xs(df.index[2])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_indexing_slow.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_indexing_slow.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c7f7f3bb05acca8acef5cda78438ded431800e0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_indexing_slow.py
@@ -0,0 +1,119 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def m():
+ return 5
+
+
+@pytest.fixture
+def n():
+ return 100
+
+
+@pytest.fixture
+def cols():
+ return ["jim", "joe", "jolie", "joline", "jolia"]
+
+
+@pytest.fixture
+def vals(n):
+ vals = [
+ np.random.default_rng(2).integers(0, 10, n),
+ np.random.default_rng(2).choice(list("abcdefghij"), n),
+ np.random.default_rng(2).choice(
+ pd.date_range("20141009", periods=10).tolist(), n
+ ),
+ np.random.default_rng(2).choice(list("ZYXWVUTSRQ"), n),
+ np.random.default_rng(2).standard_normal(n),
+ ]
+ vals = list(map(tuple, zip(*vals)))
+ return vals
+
+
+@pytest.fixture
+def keys(n, m, vals):
+ # bunch of keys for testing
+ keys = [
+ np.random.default_rng(2).integers(0, 11, m),
+ np.random.default_rng(2).choice(list("abcdefghijk"), m),
+ np.random.default_rng(2).choice(
+ pd.date_range("20141009", periods=11).tolist(), m
+ ),
+ np.random.default_rng(2).choice(list("ZYXWVUTSRQP"), m),
+ ]
+ keys = list(map(tuple, zip(*keys)))
+ keys += [t[:-1] for t in vals[:: n // m]]
+ return keys
+
+
+# covers both unique index and non-unique index
+@pytest.fixture
+def df(vals, cols):
+ return DataFrame(vals, columns=cols)
+
+
+@pytest.fixture
+def a(df):
+ return pd.concat([df, df])
+
+
+@pytest.fixture
+def b(df, cols):
+ return df.drop_duplicates(subset=cols[:-1])
+
+
+@pytest.mark.slow
+@pytest.mark.filterwarnings("ignore::pandas.errors.PerformanceWarning")
+@pytest.mark.parametrize("lexsort_depth", list(range(5)))
+@pytest.mark.parametrize("frame_fixture", ["a", "b"])
+def test_multiindex_get_loc(request, lexsort_depth, keys, frame_fixture, cols):
+ # GH7724, GH2646
+
+ frame = request.getfixturevalue(frame_fixture)
+ if lexsort_depth == 0:
+ df = frame.copy(deep=False)
+ else:
+ df = frame.sort_values(by=cols[:lexsort_depth])
+
+ mi = df.set_index(cols[:-1])
+ assert not mi.index._lexsort_depth < lexsort_depth
+ for key in keys:
+ mask = np.ones(len(df), dtype=bool)
+
+ # test for all partials of this key
+ for i, k in enumerate(key):
+ mask &= df.iloc[:, i] == k
+
+ if not mask.any():
+ assert key[: i + 1] not in mi.index
+ continue
+
+ assert key[: i + 1] in mi.index
+ right = df[mask].copy(deep=False)
+
+ if i + 1 != len(key): # partial key
+ return_value = right.drop(cols[: i + 1], axis=1, inplace=True)
+ assert return_value is None
+ return_value = right.set_index(cols[i + 1 : -1], inplace=True)
+ assert return_value is None
+ tm.assert_frame_equal(mi.loc[key[: i + 1]], right)
+
+ else: # full key
+ return_value = right.set_index(cols[:-1], inplace=True)
+ assert return_value is None
+ if len(right) == 1: # single hit
+ right = Series(
+ right["jolia"].values, name=right.index[0], index=["jolia"]
+ )
+ tm.assert_series_equal(mi.loc[key[: i + 1]], right)
+ else: # multi hit
+ tm.assert_frame_equal(mi.loc[key[: i + 1]], right)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_loc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_loc.py
new file mode 100644
index 0000000000000000000000000000000000000000..51c8d5b3569f561db53fae65da66ad34eb674ea3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_loc.py
@@ -0,0 +1,1012 @@
+import numpy as np
+import pytest
+
+from pandas.errors import IndexingError
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def frame_random_data_integer_multi_index():
+ levels = [[0, 1], [0, 1, 2]]
+ codes = [[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]]
+ index = MultiIndex(levels=levels, codes=codes)
+ return DataFrame(np.random.default_rng(2).standard_normal((6, 2)), index=index)
+
+
+class TestMultiIndexLoc:
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_loc_setitem_frame_with_multiindex(
+ self, multiindex_dataframe_random_data, has_ref
+ ):
+ frame = multiindex_dataframe_random_data
+ if has_ref:
+ view = frame[:]
+ frame.loc[("bar", "two"), "B"] = 5
+ assert frame.loc[("bar", "two"), "B"] == 5
+
+ # with integer labels
+ df = frame.copy()
+ df.columns = list(range(3))
+ if has_ref:
+ view = df[:] # noqa: F841
+ df.loc[("bar", "two"), 1] = 7
+ assert df.loc[("bar", "two"), 1] == 7
+
+ def test_loc_getitem_general(self, performance_warning, any_real_numpy_dtype):
+ # GH#2817
+ dtype = any_real_numpy_dtype
+ data = {
+ "amount": {0: 700, 1: 600, 2: 222, 3: 333, 4: 444},
+ "col": {0: 3.5, 1: 3.5, 2: 4.0, 3: 4.0, 4: 4.0},
+ "num": {0: 12, 1: 11, 2: 12, 3: 12, 4: 12},
+ }
+ df = DataFrame(data)
+ df = df.astype({"col": dtype, "num": dtype})
+ df = df.set_index(keys=["col", "num"])
+ key = 4.0, 12
+
+ with tm.assert_produces_warning(performance_warning):
+ tm.assert_frame_equal(df.loc[key], df.iloc[2:])
+
+ # this is ok
+ return_value = df.sort_index(inplace=True)
+ assert return_value is None
+ res = df.loc[key]
+
+ # col has float dtype, result should be float64 Index
+ col_arr = np.array([4.0] * 3, dtype=dtype)
+ year_arr = np.array([12] * 3, dtype=dtype)
+ index = MultiIndex.from_arrays([col_arr, year_arr], names=["col", "num"])
+ expected = DataFrame({"amount": [222, 333, 444]}, index=index)
+ tm.assert_frame_equal(res, expected)
+
+ def test_loc_getitem_multiindex_missing_label_raises(self):
+ # GH#21593
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=[[2, 2, 4], [6, 8, 10]],
+ index=[[4, 4, 8], [8, 10, 12]],
+ )
+
+ with pytest.raises(KeyError, match=r"^2$"):
+ df.loc[2]
+
+ def test_loc_getitem_list_of_tuples_with_multiindex(
+ self, multiindex_year_month_day_dataframe_random_data
+ ):
+ ser = multiindex_year_month_day_dataframe_random_data["A"]
+ expected = ser.reindex(ser.index[49:51])
+ result = ser.loc[[(2000, 3, 10), (2000, 3, 13)]]
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_getitem_series(self):
+ # GH14730
+ # passing a series as a key with a MultiIndex
+ index = MultiIndex.from_product([[1, 2, 3], ["A", "B", "C"]])
+ x = Series(index=index, data=range(9), dtype=np.float64)
+ y = Series([1, 3])
+ expected = Series(
+ data=[0, 1, 2, 6, 7, 8],
+ index=MultiIndex.from_product([[1, 3], ["A", "B", "C"]]),
+ dtype=np.float64,
+ )
+ result = x.loc[y]
+ tm.assert_series_equal(result, expected)
+
+ result = x.loc[[1, 3]]
+ tm.assert_series_equal(result, expected)
+
+ # GH15424
+ y1 = Series([1, 3], index=[1, 2])
+ result = x.loc[y1]
+ tm.assert_series_equal(result, expected)
+
+ empty = Series(data=[], dtype=np.float64)
+ expected = Series(
+ [],
+ index=MultiIndex(levels=index.levels, codes=[[], []]),
+ dtype=np.float64,
+ )
+ result = x.loc[empty]
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_getitem_array(self):
+ # GH15434
+ # passing an array as a key with a MultiIndex
+ index = MultiIndex.from_product([[1, 2, 3], ["A", "B", "C"]])
+ x = Series(index=index, data=range(9), dtype=np.float64)
+ y = np.array([1, 3])
+ expected = Series(
+ data=[0, 1, 2, 6, 7, 8],
+ index=MultiIndex.from_product([[1, 3], ["A", "B", "C"]]),
+ dtype=np.float64,
+ )
+ result = x.loc[y]
+ tm.assert_series_equal(result, expected)
+
+ # empty array:
+ empty = np.array([])
+ expected = Series(
+ [],
+ index=MultiIndex(levels=index.levels, codes=[[], []]),
+ dtype="float64",
+ )
+ result = x.loc[empty]
+ tm.assert_series_equal(result, expected)
+
+ # 0-dim array (scalar):
+ scalar = np.int64(1)
+ expected = Series(data=[0, 1, 2], index=["A", "B", "C"], dtype=np.float64)
+ result = x.loc[scalar]
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_multiindex_labels(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=[["i", "i", "j"], ["A", "A", "B"]],
+ index=[["i", "i", "j"], ["X", "X", "Y"]],
+ )
+
+ # the first 2 rows
+ expected = df.iloc[[0, 1]].droplevel(0)
+ result = df.loc["i"]
+ tm.assert_frame_equal(result, expected)
+
+ # 2nd (last) column
+ expected = df.iloc[:, [2]].droplevel(0, axis=1)
+ result = df.loc[:, "j"]
+ tm.assert_frame_equal(result, expected)
+
+ # bottom right corner
+ expected = df.iloc[[2], [2]].droplevel(0).droplevel(0, axis=1)
+ result = df.loc["j"].loc[:, "j"]
+ tm.assert_frame_equal(result, expected)
+
+ # with a tuple
+ expected = df.iloc[[0, 1]]
+ result = df.loc[("i", "X")]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_multiindex_ints(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=[[2, 2, 4], [6, 8, 10]],
+ index=[[4, 4, 8], [8, 10, 12]],
+ )
+ expected = df.iloc[[0, 1]].droplevel(0)
+ result = df.loc[4]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_multiindex_missing_label_raises(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=[[2, 2, 4], [6, 8, 10]],
+ index=[[4, 4, 8], [8, 10, 12]],
+ )
+
+ with pytest.raises(KeyError, match=r"^2$"):
+ df.loc[2]
+
+ @pytest.mark.parametrize("key, pos", [([2, 4], [0, 1]), ([2], []), ([2, 3], [])])
+ def test_loc_multiindex_list_missing_label(self, key, pos):
+ # GH 27148 - lists with missing labels _do_ raise
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=[[2, 2, 4], [6, 8, 10]],
+ index=[[4, 4, 8], [8, 10, 12]],
+ )
+
+ with pytest.raises(KeyError, match="not in index"):
+ df.loc[key]
+
+ def test_loc_multiindex_too_many_dims_raises(self):
+ # GH 14885
+ s = Series(
+ range(8),
+ index=MultiIndex.from_product([["a", "b"], ["c", "d"], ["e", "f"]]),
+ )
+
+ with pytest.raises(KeyError, match=r"^\('a', 'b'\)$"):
+ s.loc["a", "b"]
+ with pytest.raises(KeyError, match=r"^\('a', 'd', 'g'\)$"):
+ s.loc["a", "d", "g"]
+ with pytest.raises(IndexingError, match="Too many indexers"):
+ s.loc["a", "d", "g", "j"]
+
+ def test_loc_multiindex_indexer_none(self):
+ # GH6788
+ # multi-index indexer is None (meaning take all)
+ attributes = ["Attribute" + str(i) for i in range(1)]
+ attribute_values = ["Value" + str(i) for i in range(5)]
+
+ index = MultiIndex.from_product([attributes, attribute_values])
+ df = 0.1 * np.random.default_rng(2).standard_normal((10, 1 * 5)) + 0.5
+ df = DataFrame(df, columns=index)
+ result = df[attributes]
+ tm.assert_frame_equal(result, df)
+
+ # GH 7349
+ # loc with a multi-index seems to be doing fallback
+ df = DataFrame(
+ np.arange(12).reshape(-1, 1),
+ index=MultiIndex.from_product([[1, 2, 3, 4], [1, 2, 3]]),
+ )
+
+ expected = df.loc[([1, 2],), :]
+ result = df.loc[[1, 2]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_multiindex_incomplete(self):
+ # GH 7399
+ # incomplete indexers
+ s = Series(
+ np.arange(15, dtype="int64"),
+ MultiIndex.from_product([range(5), ["a", "b", "c"]]),
+ )
+ expected = s.loc[:, "a":"c"]
+
+ result = s.loc[0:4, "a":"c"]
+ tm.assert_series_equal(result, expected)
+
+ result = s.loc[:4, "a":"c"]
+ tm.assert_series_equal(result, expected)
+
+ result = s.loc[0:, "a":"c"]
+ tm.assert_series_equal(result, expected)
+
+ # GH 7400
+ # multiindexer getitem with list of indexers skips wrong element
+ s = Series(
+ np.arange(15, dtype="int64"),
+ MultiIndex.from_product([range(5), ["a", "b", "c"]]),
+ )
+ expected = s.iloc[[6, 7, 8, 12, 13, 14]]
+ result = s.loc[2:4:2, "a":"c"]
+ tm.assert_series_equal(result, expected)
+
+ def test_get_loc_single_level(self):
+ single_level = MultiIndex(
+ levels=[["foo", "bar", "baz", "qux"]], codes=[[0, 1, 2, 3]], names=["first"]
+ )
+ s = Series(
+ np.random.default_rng(2).standard_normal(len(single_level)),
+ index=single_level,
+ )
+ for k in single_level.values:
+ s[k]
+
+ def test_loc_getitem_int_slice(self):
+ # GH 3053
+ # loc should treat integer slices like label slices
+
+ index = MultiIndex.from_product([[6, 7, 8], ["a", "b"]])
+ df = DataFrame(np.random.default_rng(2).standard_normal((6, 6)), index, index)
+ result = df.loc[6:8, :]
+ expected = df
+ tm.assert_frame_equal(result, expected)
+
+ index = MultiIndex.from_product([[10, 20, 30], ["a", "b"]])
+ df = DataFrame(np.random.default_rng(2).standard_normal((6, 6)), index, index)
+ result = df.loc[20:30, :]
+ expected = df.iloc[2:]
+ tm.assert_frame_equal(result, expected)
+
+ # doc examples
+ result = df.loc[10, :]
+ expected = df.iloc[0:2]
+ expected.index = ["a", "b"]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[:, 10]
+ expected = df[10]
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "indexer_type_1", (list, tuple, set, slice, np.ndarray, Series, Index)
+ )
+ @pytest.mark.parametrize(
+ "indexer_type_2", (list, tuple, set, slice, np.ndarray, Series, Index)
+ )
+ def test_loc_getitem_nested_indexer(self, indexer_type_1, indexer_type_2):
+ # GH #19686
+ # .loc should work with nested indexers which can be
+ # any list-like objects (see `is_list_like` (`pandas.api.types`)) or slices
+
+ def convert_nested_indexer(indexer_type, keys):
+ if indexer_type == np.ndarray:
+ return np.array(keys)
+ if indexer_type == slice:
+ return slice(*keys)
+ return indexer_type(keys)
+
+ a = [10, 20, 30]
+ b = [1, 2, 3]
+ index = MultiIndex.from_product([a, b])
+ df = DataFrame(
+ np.arange(len(index), dtype="int64"), index=index, columns=["Data"]
+ )
+
+ keys = ([10, 20], [2, 3])
+ types = (indexer_type_1, indexer_type_2)
+
+ # check indexers with all the combinations of nested objects
+ # of all the valid types
+ indexer = tuple(
+ convert_nested_indexer(indexer_type, k)
+ for indexer_type, k in zip(types, keys)
+ )
+ if indexer_type_1 is set or indexer_type_2 is set:
+ with pytest.raises(TypeError, match="as an indexer is not supported"):
+ df.loc[indexer, "Data"]
+
+ return
+ else:
+ result = df.loc[indexer, "Data"]
+ expected = Series(
+ [1, 2, 4, 5], name="Data", index=MultiIndex.from_product(keys)
+ )
+
+ tm.assert_series_equal(result, expected)
+
+ def test_multiindex_loc_one_dimensional_tuple(self, frame_or_series):
+ # GH#37711
+ mi = MultiIndex.from_tuples([("a", "A"), ("b", "A")])
+ obj = frame_or_series([1, 2], index=mi)
+ obj.loc[("a",)] = 0
+ expected = frame_or_series([0, 2], index=mi)
+ tm.assert_equal(obj, expected)
+
+ @pytest.mark.parametrize("indexer", [("a",), ("a")])
+ def test_multiindex_one_dimensional_tuple_columns(self, indexer):
+ # GH#37711
+ mi = MultiIndex.from_tuples([("a", "A"), ("b", "A")])
+ obj = DataFrame([1, 2], index=mi)
+ obj.loc[indexer, :] = 0
+ expected = DataFrame([0, 2], index=mi)
+ tm.assert_frame_equal(obj, expected)
+
+ @pytest.mark.parametrize(
+ "indexer, exp_value", [(slice(None), 1.0), ((1, 2), np.nan)]
+ )
+ def test_multiindex_setitem_columns_enlarging(self, indexer, exp_value):
+ # GH#39147
+ mi = MultiIndex.from_tuples([(1, 2), (3, 4)])
+ df = DataFrame([[1, 2], [3, 4]], index=mi, columns=["a", "b"])
+ df.loc[indexer, ["c", "d"]] = 1.0
+ expected = DataFrame(
+ [[1, 2, 1.0, 1.0], [3, 4, exp_value, exp_value]],
+ index=mi,
+ columns=["a", "b", "c", "d"],
+ )
+ tm.assert_frame_equal(df, expected)
+
+ def test_multiindex_setitem_axis_set(self):
+ # GH#58116
+ dates = pd.date_range("2001-01-01", freq="D", periods=2)
+ ids = ["i1", "i2", "i3"]
+ index = MultiIndex.from_product([dates, ids], names=["date", "identifier"])
+ df = DataFrame(0.0, index=index, columns=["A", "B"])
+ df.loc(axis=0)["2001-01-01", ["i1", "i3"]] = None
+
+ expected = DataFrame(
+ [
+ [None, None],
+ [0.0, 0.0],
+ [None, None],
+ [0.0, 0.0],
+ [0.0, 0.0],
+ [0.0, 0.0],
+ ],
+ index=index,
+ columns=["A", "B"],
+ )
+
+ tm.assert_frame_equal(df, expected)
+
+ def test_sorted_multiindex_after_union(self):
+ # GH#44752
+ midx = MultiIndex.from_product(
+ [pd.date_range("20110101", periods=2), Index(["a", "b"])]
+ )
+ ser1 = Series(1, index=midx)
+ ser2 = Series(1, index=midx[:2])
+ df = pd.concat([ser1, ser2], axis=1)
+ expected = df.copy()
+ result = df.loc["2011-01-01":"2011-01-02"]
+ tm.assert_frame_equal(result, expected)
+
+ df = DataFrame({0: ser1, 1: ser2})
+ result = df.loc["2011-01-01":"2011-01-02"]
+ tm.assert_frame_equal(result, expected)
+
+ df = pd.concat([ser1, ser2.reindex(ser1.index)], axis=1)
+ result = df.loc["2011-01-01":"2011-01-02"]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_no_second_level_index(self):
+ # GH#43599
+ df = DataFrame(
+ index=MultiIndex.from_product([list("ab"), list("cd"), list("e")]),
+ columns=["Val"],
+ )
+ res = df.loc[np.s_[:, "c", :]]
+ expected = DataFrame(
+ index=MultiIndex.from_product([list("ab"), list("e")]), columns=["Val"]
+ )
+ tm.assert_frame_equal(res, expected)
+
+ def test_loc_multi_index_key_error(self):
+ # GH 51892
+ df = DataFrame(
+ {
+ (1, 2): ["a", "b", "c"],
+ (1, 3): ["d", "e", "f"],
+ (2, 2): ["g", "h", "i"],
+ (2, 4): ["j", "k", "l"],
+ }
+ )
+ with pytest.raises(KeyError, match=r"(1, 4)"):
+ df.loc[0, (1, 4)]
+
+
+@pytest.mark.parametrize(
+ "indexer, pos",
+ [
+ ([], []), # empty ok
+ (["A"], slice(3)),
+ (["A", "D"], []), # "D" isn't present -> raise
+ (["D", "E"], []), # no values found -> raise
+ (["D"], []), # same, with single item list: GH 27148
+ (pd.IndexSlice[:, ["foo"]], slice(2, None, 3)),
+ (pd.IndexSlice[:, ["foo", "bah"]], slice(2, None, 3)),
+ ],
+)
+def test_loc_getitem_duplicates_multiindex_missing_indexers(indexer, pos):
+ # GH 7866
+ # multi-index slicing with missing indexers
+ idx = MultiIndex.from_product(
+ [["A", "B", "C"], ["foo", "bar", "baz"]], names=["one", "two"]
+ )
+ ser = Series(np.arange(9, dtype="int64"), index=idx).sort_index()
+ expected = ser.iloc[pos]
+
+ if expected.size == 0 and indexer != []:
+ with pytest.raises(KeyError, match=str(indexer)):
+ ser.loc[indexer]
+ elif indexer == (slice(None), ["foo", "bah"]):
+ # "bah" is not in idx.levels[1], raising KeyError enforced in 2.0
+ with pytest.raises(KeyError, match="'bah'"):
+ ser.loc[indexer]
+ else:
+ result = ser.loc[indexer]
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("columns_indexer", [([], slice(None)), (["foo"], [])])
+def test_loc_getitem_duplicates_multiindex_empty_indexer(columns_indexer):
+ # GH 8737
+ # empty indexer
+ multi_index = MultiIndex.from_product((["foo", "bar", "baz"], ["alpha", "beta"]))
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 6)),
+ index=range(5),
+ columns=multi_index,
+ )
+ df = df.sort_index(level=0, axis=1)
+
+ expected = DataFrame(index=range(5), columns=multi_index.reindex([])[0])
+ result = df.loc[:, columns_indexer]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_getitem_duplicates_multiindex_non_scalar_type_object():
+ # regression from < 0.14.0
+ # GH 7914
+ df = DataFrame(
+ [[np.mean, np.median], ["mean", "median"]],
+ columns=MultiIndex.from_tuples([("functs", "mean"), ("functs", "median")]),
+ index=["function", "name"],
+ )
+ result = df.loc["function", ("functs", "mean")]
+ expected = np.mean
+ assert result == expected
+
+
+def test_loc_getitem_tuple_plus_slice():
+ # GH 671
+ df = DataFrame(
+ {
+ "a": np.arange(10),
+ "b": np.arange(10),
+ "c": np.random.default_rng(2).standard_normal(10),
+ "d": np.random.default_rng(2).standard_normal(10),
+ }
+ ).set_index(["a", "b"])
+ expected = df.loc[0, 0]
+ result = df.loc[(0, 0), :]
+ tm.assert_series_equal(result, expected)
+
+
+def test_loc_getitem_int(frame_random_data_integer_multi_index):
+ df = frame_random_data_integer_multi_index
+ result = df.loc[1]
+ expected = df[-3:]
+ expected.index = expected.index.droplevel(0)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_getitem_int_raises_exception(frame_random_data_integer_multi_index):
+ df = frame_random_data_integer_multi_index
+ with pytest.raises(KeyError, match=r"^3$"):
+ df.loc[3]
+
+
+def test_loc_getitem_lowerdim_corner(multiindex_dataframe_random_data):
+ df = multiindex_dataframe_random_data
+
+ # test setup - check key not in dataframe
+ with pytest.raises(KeyError, match=r"^\('bar', 'three'\)$"):
+ df.loc[("bar", "three"), "B"]
+
+ # in theory should be inserting in a sorted space????
+ df.loc[("bar", "three"), "B"] = 0
+ expected = 0
+ result = df.sort_index().loc[("bar", "three"), "B"]
+ assert result == expected
+
+
+def test_loc_setitem_single_column_slice():
+ # case from https://github.com/pandas-dev/pandas/issues/27841
+ df = DataFrame(
+ "string",
+ index=list("abcd"),
+ columns=MultiIndex.from_product([["Main"], ("another", "one")]),
+ )
+ df["labels"] = "a"
+ df.loc[:, "labels"] = df.index
+ tm.assert_numpy_array_equal(np.asarray(df["labels"]), np.asarray(df.index))
+
+ # test with non-object block
+ df = DataFrame(
+ np.nan,
+ index=range(4),
+ columns=MultiIndex.from_tuples([("A", "1"), ("A", "2"), ("B", "1")]),
+ )
+ expected = df.copy()
+ df.loc[:, "B"] = np.arange(4)
+ expected.iloc[:, 2] = np.arange(4)
+ tm.assert_frame_equal(df, expected)
+
+
+def test_loc_nan_multiindex(using_infer_string):
+ # GH 5286
+ tups = [
+ ("Good Things", "C", np.nan),
+ ("Good Things", "R", np.nan),
+ ("Bad Things", "C", np.nan),
+ ("Bad Things", "T", np.nan),
+ ("Okay Things", "N", "B"),
+ ("Okay Things", "N", "D"),
+ ("Okay Things", "B", np.nan),
+ ("Okay Things", "D", np.nan),
+ ]
+ df = DataFrame(
+ np.ones((8, 4)),
+ columns=Index(["d1", "d2", "d3", "d4"]),
+ index=MultiIndex.from_tuples(tups, names=["u1", "u2", "u3"]),
+ )
+ result = df.loc["Good Things"].loc["C"]
+ expected = DataFrame(
+ np.ones((1, 4)),
+ index=Index(
+ [np.nan],
+ dtype="object" if not using_infer_string else "str",
+ name="u3",
+ ),
+ columns=Index(["d1", "d2", "d3", "d4"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_period_string_indexing():
+ # GH 9892
+ a = pd.period_range("2013Q1", "2013Q4", freq="Q")
+ i = (1111, 2222, 3333)
+ idx = MultiIndex.from_product((a, i), names=("Period", "CVR"))
+ df = DataFrame(
+ index=idx,
+ columns=(
+ "OMS",
+ "OMK",
+ "RES",
+ "DRIFT_IND",
+ "OEVRIG_IND",
+ "FIN_IND",
+ "VARE_UD",
+ "LOEN_UD",
+ "FIN_UD",
+ ),
+ )
+ result = df.loc[("2013Q1", 1111), "OMS"]
+
+ alt = df.loc[(a[0], 1111), "OMS"]
+ assert np.isnan(alt)
+
+ # Because the resolution of the string matches, it is an exact lookup,
+ # not a slice
+ assert np.isnan(result)
+
+ alt = df.loc[("2013Q1", 1111), "OMS"]
+ assert np.isnan(alt)
+
+
+def test_loc_datetime_mask_slicing():
+ # GH 16699
+ dt_idx = pd.to_datetime(["2017-05-04", "2017-05-05"])
+ m_idx = MultiIndex.from_product([dt_idx, dt_idx], names=["Idx1", "Idx2"])
+ df = DataFrame(
+ data=[[1, 2], [3, 4], [5, 6], [7, 6]], index=m_idx, columns=["C1", "C2"]
+ )
+ result = df.loc[(dt_idx[0], (df.index.get_level_values(1) > "2017-05-04")), "C1"]
+ expected = Series(
+ [3],
+ name="C1",
+ index=MultiIndex.from_tuples(
+ [(pd.Timestamp("2017-05-04"), pd.Timestamp("2017-05-05"))],
+ names=["Idx1", "Idx2"],
+ ),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_loc_datetime_series_tuple_slicing():
+ # https://github.com/pandas-dev/pandas/issues/35858
+ date = pd.Timestamp("2000")
+ ser = Series(
+ 1,
+ index=MultiIndex.from_tuples([("a", date)], names=["a", "b"]),
+ name="c",
+ )
+ result = ser.loc[:, [date]]
+ tm.assert_series_equal(result, ser)
+
+
+def test_loc_with_mi_indexer():
+ # https://github.com/pandas-dev/pandas/issues/35351
+ df = DataFrame(
+ data=[["a", 1], ["a", 0], ["b", 1], ["c", 2]],
+ index=MultiIndex.from_tuples(
+ [(0, 1), (1, 0), (1, 1), (1, 1)], names=["index", "date"]
+ ),
+ columns=["author", "price"],
+ )
+ idx = MultiIndex.from_tuples([(0, 1), (1, 1)], names=["index", "date"])
+ result = df.loc[idx, :]
+ expected = DataFrame(
+ [["a", 1], ["b", 1], ["c", 2]],
+ index=MultiIndex.from_tuples([(0, 1), (1, 1), (1, 1)], names=["index", "date"]),
+ columns=["author", "price"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_mi_with_level1_named_0():
+ # GH#37194
+ dti = pd.date_range("2016-01-01", periods=3, tz="US/Pacific")
+
+ ser = Series(range(3), index=dti)
+ df = ser.to_frame()
+ df[1] = dti
+
+ df2 = df.set_index(0, append=True)
+ assert df2.index.names == (None, 0)
+ df2.index.get_loc(dti[0]) # smoke test
+
+ result = df2.loc[dti[0]]
+ expected = df2.iloc[[0]].droplevel(None)
+ tm.assert_frame_equal(result, expected)
+
+ ser2 = df2[1]
+ assert ser2.index.names == (None, 0)
+
+ result = ser2.loc[dti[0]]
+ expected = ser2.iloc[[0]].droplevel(None)
+ tm.assert_series_equal(result, expected)
+
+
+def test_getitem_str_slice():
+ # GH#15928
+ df = DataFrame(
+ [
+ ["20160525 13:30:00.023", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.048", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.076", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.131", "AAPL", "98.61", "98.62"],
+ ["20160525 13:30:00.135", "MSFT", "51.92", "51.95"],
+ ["20160525 13:30:00.135", "AAPL", "98.61", "98.62"],
+ ],
+ columns="time,ticker,bid,ask".split(","),
+ )
+ df2 = df.set_index(["ticker", "time"]).sort_index()
+
+ res = df2.loc[("AAPL", slice("2016-05-25 13:30:00")), :].droplevel(0)
+ expected = df2.loc["AAPL"].loc[slice("2016-05-25 13:30:00"), :]
+ tm.assert_frame_equal(res, expected)
+
+
+def test_3levels_leading_period_index():
+ # GH#24091
+ pi = pd.PeriodIndex(
+ ["20181101 1100", "20181101 1200", "20181102 1300", "20181102 1400"],
+ name="datetime",
+ freq="D",
+ )
+ lev2 = ["A", "A", "Z", "W"]
+ lev3 = ["B", "C", "Q", "F"]
+ mi = MultiIndex.from_arrays([pi, lev2, lev3])
+
+ ser = Series(range(4), index=mi, dtype=np.float64)
+ result = ser.loc[(pi[0], "A", "B")]
+ assert result == 0.0
+
+
+class TestKeyErrorsWithMultiIndex:
+ def test_missing_keys_raises_keyerror(self):
+ # GH#27420 KeyError, not TypeError
+ df = DataFrame(np.arange(12).reshape(4, 3), columns=["A", "B", "C"])
+ df2 = df.set_index(["A", "B"])
+
+ with pytest.raises(KeyError, match="6"):
+ df2.loc[(1, 6)]
+
+ def test_missing_key_raises_keyerror2(self):
+ # GH#21168 KeyError, not "IndexingError: Too many indexers"
+ ser = Series(-1, index=MultiIndex.from_product([[0, 1]] * 2))
+
+ with pytest.raises(KeyError, match=r"\(0, 3\)"):
+ ser.loc[0, 3]
+
+ def test_missing_key_combination(self):
+ # GH: 19556
+ mi = MultiIndex.from_arrays(
+ [
+ np.array(["a", "a", "b", "b"]),
+ np.array(["1", "2", "2", "3"]),
+ np.array(["c", "d", "c", "d"]),
+ ],
+ names=["one", "two", "three"],
+ )
+ df = DataFrame(np.random.default_rng(2).random((4, 3)), index=mi)
+ msg = r"\('b', '1', slice\(None, None, None\)\)"
+ with pytest.raises(KeyError, match=msg):
+ df.loc[("b", "1", slice(None)), :]
+ with pytest.raises(KeyError, match=msg):
+ df.index.get_locs(("b", "1", slice(None)))
+ with pytest.raises(KeyError, match=r"\('b', '1'\)"):
+ df.loc[("b", "1"), :]
+
+
+def test_getitem_loc_commutability(multiindex_year_month_day_dataframe_random_data):
+ df = multiindex_year_month_day_dataframe_random_data
+ ser = df["A"]
+ result = ser[2000, 5]
+ expected = df.loc[2000, 5]["A"]
+ tm.assert_series_equal(result, expected)
+
+
+def test_loc_with_nan():
+ # GH: 27104
+ df = DataFrame(
+ {"col": [1, 2, 5], "ind1": ["a", "d", np.nan], "ind2": [1, 4, 5]}
+ ).set_index(["ind1", "ind2"])
+ result = df.loc[["a"]]
+ expected = DataFrame(
+ {"col": [1]}, index=MultiIndex.from_tuples([("a", 1)], names=["ind1", "ind2"])
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc["a"]
+ expected = DataFrame({"col": [1]}, index=Index([1], name="ind2"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_getitem_non_found_tuple():
+ # GH: 25236
+ df = DataFrame([[1, 2, 3, 4]], columns=["a", "b", "c", "d"]).set_index(
+ ["a", "b", "c"]
+ )
+ with pytest.raises(KeyError, match=r"\(2\.0, 2\.0, 3\.0\)"):
+ df.loc[(2.0, 2.0, 3.0)]
+
+
+def test_get_loc_datetime_index():
+ # GH#24263
+ index = pd.date_range("2001-01-01", periods=100)
+ mi = MultiIndex.from_arrays([index])
+ # Check if get_loc matches for Index and MultiIndex
+ assert mi.get_loc("2001-01") == slice(0, 31, None)
+ assert index.get_loc("2001-01") == slice(0, 31, None)
+
+ loc = mi[::2].get_loc("2001-01")
+ expected = index[::2].get_loc("2001-01")
+ assert loc == expected
+
+ loc = mi.repeat(2).get_loc("2001-01")
+ expected = index.repeat(2).get_loc("2001-01")
+ assert loc == expected
+
+ loc = mi.append(mi).get_loc("2001-01")
+ expected = index.append(index).get_loc("2001-01")
+ # TODO: standardize return type for MultiIndex.get_loc
+ tm.assert_numpy_array_equal(loc.nonzero()[0], expected)
+
+
+def test_loc_setitem_indexer_differently_ordered():
+ # GH#34603
+ mi = MultiIndex.from_product([["a", "b"], [0, 1]])
+ df = DataFrame([[1, 2], [3, 4], [5, 6], [7, 8]], index=mi)
+
+ indexer = ("a", [1, 0])
+ df.loc[indexer, :] = np.array([[9, 10], [11, 12]])
+ expected = DataFrame([[11, 12], [9, 10], [5, 6], [7, 8]], index=mi)
+ tm.assert_frame_equal(df, expected)
+
+
+def test_loc_getitem_index_differently_ordered_slice_none():
+ # GH#31330
+ df = DataFrame(
+ [[1, 2], [3, 4], [5, 6], [7, 8]],
+ index=[["a", "a", "b", "b"], [1, 2, 1, 2]],
+ columns=["a", "b"],
+ )
+ result = df.loc[(slice(None), [2, 1]), :]
+ expected = DataFrame(
+ [[3, 4], [7, 8], [1, 2], [5, 6]],
+ index=[["a", "b", "a", "b"], [2, 2, 1, 1]],
+ columns=["a", "b"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("indexer", [[1, 2, 7, 6, 2, 3, 8, 7], [1, 2, 7, 6, 3, 8]])
+def test_loc_getitem_index_differently_ordered_slice_none_duplicates(indexer):
+ # GH#40978
+ df = DataFrame(
+ [1] * 8,
+ index=MultiIndex.from_tuples(
+ [(1, 1), (1, 2), (1, 7), (1, 6), (2, 2), (2, 3), (2, 8), (2, 7)]
+ ),
+ columns=["a"],
+ )
+ result = df.loc[(slice(None), indexer), :]
+ expected = DataFrame(
+ [1] * 8,
+ index=[[1, 1, 2, 1, 2, 1, 2, 2], [1, 2, 2, 7, 7, 6, 3, 8]],
+ columns=["a"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[df.index.isin(indexer, level=1), :]
+ tm.assert_frame_equal(result, df)
+
+
+def test_loc_getitem_drops_levels_for_one_row_dataframe():
+ # GH#10521 "x" and "z" are both scalar indexing, so those levels are dropped
+ mi = MultiIndex.from_arrays([["x"], ["y"], ["z"]], names=["a", "b", "c"])
+ df = DataFrame({"d": [0]}, index=mi)
+ expected = df.droplevel([0, 2])
+ result = df.loc["x", :, "z"]
+ tm.assert_frame_equal(result, expected)
+
+ ser = Series([0], index=mi)
+ result = ser.loc["x", :, "z"]
+ expected = Series([0], index=Index(["y"], name="b"))
+ tm.assert_series_equal(result, expected)
+
+
+def test_mi_columns_loc_list_label_order():
+ # GH 10710
+ cols = MultiIndex.from_product([["A", "B", "C"], [1, 2]])
+ df = DataFrame(np.zeros((5, 6)), columns=cols)
+ result = df.loc[:, ["B", "A"]]
+ expected = DataFrame(
+ np.zeros((5, 4)),
+ columns=MultiIndex.from_tuples([("B", 1), ("B", 2), ("A", 1), ("A", 2)]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_mi_partial_indexing_list_raises():
+ # GH 13501
+ frame = DataFrame(
+ np.arange(12).reshape((4, 3)),
+ index=[["a", "a", "b", "b"], [1, 2, 1, 2]],
+ columns=[["Ohio", "Ohio", "Colorado"], ["Green", "Red", "Green"]],
+ )
+ frame.index.names = ["key1", "key2"]
+ frame.columns.names = ["state", "color"]
+ with pytest.raises(KeyError, match="\\[2\\] not in index"):
+ frame.loc[["b", 2], "Colorado"]
+
+
+def test_mi_indexing_list_nonexistent_raises():
+ # GH 15452
+ s = Series(range(4), index=MultiIndex.from_product([[1, 2], ["a", "b"]]))
+ with pytest.raises(KeyError, match="\\['not' 'found'\\] not in index"):
+ s.loc[["not", "found"]]
+
+
+def test_mi_add_cell_missing_row_non_unique():
+ # GH 16018
+ result = DataFrame(
+ [[1, 2, 5, 6], [3, 4, 7, 8]],
+ index=["a", "a"],
+ columns=MultiIndex.from_product([[1, 2], ["A", "B"]]),
+ )
+ result.loc["c"] = -1
+ result.loc["c", (1, "A")] = 3
+ result.loc["d", (1, "A")] = 3
+ expected = DataFrame(
+ [
+ [1.0, 2.0, 5.0, 6.0],
+ [3.0, 4.0, 7.0, 8.0],
+ [3.0, -1.0, -1, -1],
+ [3.0, np.nan, np.nan, np.nan],
+ ],
+ index=["a", "a", "c", "d"],
+ columns=MultiIndex.from_product([[1, 2], ["A", "B"]]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_get_scalar_casting_to_float():
+ # GH#41369
+ df = DataFrame(
+ {"a": 1.0, "b": 2}, index=MultiIndex.from_arrays([[3], [4]], names=["c", "d"])
+ )
+ result = df.loc[(3, 4), "b"]
+ assert result == 2
+ assert isinstance(result, np.int64)
+ result = df.loc[[(3, 4)], "b"].iloc[0]
+ assert result == 2
+ assert isinstance(result, np.int64)
+
+
+def test_loc_empty_single_selector_with_names():
+ # GH 19517
+ idx = MultiIndex.from_product([["a", "b"], ["A", "B"]], names=[1, 0])
+ s2 = Series(index=idx, dtype=np.float64)
+ result = s2.loc["a"]
+ expected = Series([np.nan, np.nan], index=Index(["A", "B"], name=0))
+ tm.assert_series_equal(result, expected)
+
+
+def test_loc_keyerror_rightmost_key_missing():
+ # GH 20951
+
+ df = DataFrame(
+ {
+ "A": [100, 100, 200, 200, 300, 300],
+ "B": [10, 10, 20, 21, 31, 33],
+ "C": range(6),
+ }
+ )
+ df = df.set_index(["A", "B"])
+ with pytest.raises(KeyError, match="^1$"):
+ df.loc[(100, 1)]
+
+
+def test_multindex_series_loc_with_tuple_label():
+ # GH#43908
+ mi = MultiIndex.from_tuples([(1, 2), (3, (4, 5))])
+ ser = Series([1, 2], index=mi)
+ result = ser.loc[(3, (4, 5))]
+ assert result == 2
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_multiindex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_multiindex.py
new file mode 100644
index 0000000000000000000000000000000000000000..35ef7625b6e76f6baa9fc7a0059463a33e5cc54e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_multiindex.py
@@ -0,0 +1,294 @@
+import numpy as np
+import pytest
+
+import pandas._libs.index as libindex
+
+import pandas as pd
+from pandas import (
+ CategoricalDtype,
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+from pandas.core.arrays.boolean import BooleanDtype
+
+
+class TestMultiIndexBasic:
+ def test_multiindex_perf_warn(self, performance_warning):
+ df = DataFrame(
+ {
+ "jim": [0, 0, 1, 1],
+ "joe": ["x", "x", "z", "y"],
+ "jolie": np.random.default_rng(2).random(4),
+ }
+ ).set_index(["jim", "joe"])
+
+ with tm.assert_produces_warning(performance_warning):
+ df.loc[(1, "z")]
+
+ df = df.iloc[[2, 1, 3, 0]]
+ with tm.assert_produces_warning(performance_warning):
+ df.loc[(0,)]
+
+ @pytest.mark.parametrize("offset", [-5, 5])
+ def test_indexing_over_hashtable_size_cutoff(self, monkeypatch, offset):
+ size_cutoff = 20
+ n = size_cutoff + offset
+
+ with monkeypatch.context():
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", size_cutoff)
+ s = Series(np.arange(n), MultiIndex.from_arrays((["a"] * n, np.arange(n))))
+
+ # hai it works!
+ assert s[("a", 5)] == 5
+ assert s[("a", 6)] == 6
+ assert s[("a", 7)] == 7
+
+ def test_multi_nan_indexing(self):
+ # GH 3588
+ df = DataFrame(
+ {
+ "a": ["R1", "R2", np.nan, "R4"],
+ "b": ["C1", "C2", "C3", "C4"],
+ "c": [10, 15, np.nan, 20],
+ }
+ )
+ result = df.set_index(["a", "b"], drop=False)
+ expected = DataFrame(
+ {
+ "a": ["R1", "R2", np.nan, "R4"],
+ "b": ["C1", "C2", "C3", "C4"],
+ "c": [10, 15, np.nan, 20],
+ },
+ index=[
+ Index(["R1", "R2", np.nan, "R4"], name="a"),
+ Index(["C1", "C2", "C3", "C4"], name="b"),
+ ],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_exclusive_nat_column_indexing(self):
+ # GH 38025
+ # test multi indexing when one column exclusively contains NaT values
+ df = DataFrame(
+ {
+ "a": [pd.NaT, pd.NaT, pd.NaT, pd.NaT],
+ "b": ["C1", "C2", "C3", "C4"],
+ "c": [10, 15, np.nan, 20],
+ }
+ )
+ df = df.set_index(["a", "b"])
+ expected = DataFrame(
+ {
+ "c": [10, 15, np.nan, 20],
+ },
+ index=[
+ Index([pd.NaT, pd.NaT, pd.NaT, pd.NaT], name="a"),
+ Index(["C1", "C2", "C3", "C4"], name="b"),
+ ],
+ )
+ tm.assert_frame_equal(df, expected)
+
+ def test_nested_tuples_duplicates(self):
+ # GH#30892
+
+ dti = pd.to_datetime(["20190101", "20190101", "20190102"])
+ idx = Index(["a", "a", "c"])
+ mi = MultiIndex.from_arrays([dti, idx], names=["index1", "index2"])
+
+ df = DataFrame({"c1": [1, 2, 3], "c2": [np.nan, np.nan, np.nan]}, index=mi)
+
+ expected = DataFrame({"c1": df["c1"], "c2": [1.0, 1.0, np.nan]}, index=mi)
+
+ df2 = df.copy(deep=True)
+ df2.loc[(dti[0], "a"), "c2"] = 1.0
+ tm.assert_frame_equal(df2, expected)
+
+ df3 = df.copy(deep=True)
+ df3.loc[[(dti[0], "a")], "c2"] = 1.0
+ tm.assert_frame_equal(df3, expected)
+
+ def test_multiindex_with_datatime_level_preserves_freq(self):
+ # https://github.com/pandas-dev/pandas/issues/35563
+ idx = Index(range(2), name="A")
+ dti = pd.date_range("2020-01-01", periods=7, freq="D", name="B")
+ mi = MultiIndex.from_product([idx, dti])
+ df = DataFrame(np.random.default_rng(2).standard_normal((14, 2)), index=mi)
+ result = df.loc[0].index
+ tm.assert_index_equal(result, dti)
+ assert result.freq == dti.freq
+
+ def test_multiindex_complex(self):
+ # GH#42145
+ complex_data = [1 + 2j, 4 - 3j, 10 - 1j]
+ non_complex_data = [3, 4, 5]
+ result = DataFrame(
+ {
+ "x": complex_data,
+ "y": non_complex_data,
+ "z": non_complex_data,
+ }
+ )
+ result.set_index(["x", "y"], inplace=True)
+ expected = DataFrame(
+ {"z": non_complex_data},
+ index=MultiIndex.from_arrays(
+ [complex_data, non_complex_data],
+ names=("x", "y"),
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_rename_multiindex_with_duplicates(self):
+ # GH 38015
+ mi = MultiIndex.from_tuples([("A", "cat"), ("B", "cat"), ("B", "cat")])
+ df = DataFrame(index=mi)
+ df = df.rename(index={"A": "Apple"}, level=0)
+
+ mi2 = MultiIndex.from_tuples([("Apple", "cat"), ("B", "cat"), ("B", "cat")])
+ expected = DataFrame(index=mi2)
+ tm.assert_frame_equal(df, expected)
+
+ def test_series_align_multiindex_with_nan_overlap_only(self):
+ # GH 38439
+ mi1 = MultiIndex.from_arrays([[81.0, np.nan], [np.nan, np.nan]])
+ mi2 = MultiIndex.from_arrays([[np.nan, 82.0], [np.nan, np.nan]])
+ ser1 = Series([1, 2], index=mi1)
+ ser2 = Series([1, 2], index=mi2)
+ result1, result2 = ser1.align(ser2)
+
+ mi = MultiIndex.from_arrays([[81.0, 82.0, np.nan], [np.nan, np.nan, np.nan]])
+ expected1 = Series([1.0, np.nan, 2.0], index=mi)
+ expected2 = Series([np.nan, 2.0, 1.0], index=mi)
+
+ tm.assert_series_equal(result1, expected1)
+ tm.assert_series_equal(result2, expected2)
+
+ def test_series_align_multiindex_with_nan(self):
+ # GH 38439
+ mi1 = MultiIndex.from_arrays([[81.0, np.nan], [np.nan, np.nan]])
+ mi2 = MultiIndex.from_arrays([[np.nan, 81.0], [np.nan, np.nan]])
+ ser1 = Series([1, 2], index=mi1)
+ ser2 = Series([1, 2], index=mi2)
+ result1, result2 = ser1.align(ser2)
+
+ mi = MultiIndex.from_arrays([[81.0, np.nan], [np.nan, np.nan]])
+ expected1 = Series([1, 2], index=mi)
+ expected2 = Series([2, 1], index=mi)
+
+ tm.assert_series_equal(result1, expected1)
+ tm.assert_series_equal(result2, expected2)
+
+ def test_nunique_smoke(self):
+ # GH 34019
+ n = DataFrame([[1, 2], [1, 2]]).set_index([0, 1]).index.nunique()
+ assert n == 1
+
+ def test_multiindex_repeated_keys(self):
+ # GH19414
+ tm.assert_series_equal(
+ Series([1, 2], MultiIndex.from_arrays([["a", "b"]])).loc[
+ ["a", "a", "b", "b"]
+ ],
+ Series([1, 1, 2, 2], MultiIndex.from_arrays([["a", "a", "b", "b"]])),
+ )
+
+ def test_multiindex_with_na_missing_key(self):
+ # GH46173
+ df = DataFrame.from_dict(
+ {
+ ("foo",): [1, 2, 3],
+ ("bar",): [5, 6, 7],
+ (None,): [8, 9, 0],
+ }
+ )
+ with pytest.raises(KeyError, match="missing_key"):
+ df[[("missing_key",)]]
+
+ def test_multiindex_dtype_preservation(self):
+ # GH51261
+ columns = MultiIndex.from_tuples([("A", "B")], names=["lvl1", "lvl2"])
+ df = DataFrame(["value"], columns=columns).astype("category")
+ df_no_multiindex = df["A"]
+ assert isinstance(df_no_multiindex["B"].dtype, CategoricalDtype)
+
+ # geopandas 1763 analogue
+ df = DataFrame(
+ [[1, 0], [0, 1]],
+ columns=[
+ ["foo", "foo"],
+ ["location", "location"],
+ ["x", "y"],
+ ],
+ ).assign(bools=Series([True, False], dtype="boolean"))
+ assert isinstance(df["bools"].dtype, BooleanDtype)
+
+ def test_multiindex_from_tuples_with_nan(self):
+ # GH#23578
+ result = MultiIndex.from_tuples([("a", "b", "c"), np.nan, ("d", "", "")])
+ expected = MultiIndex.from_tuples(
+ [("a", "b", "c"), (np.nan, np.nan, np.nan), ("d", "", "")]
+ )
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("operation", ["div", "mul", "add", "sub"])
+ def test_groupyby_rename_categories_operation_with_multiindex(self, operation):
+ # GH#51500
+ data = DataFrame(
+ [["C", "B", "B"], ["B", "A", "A"], ["B", "A", "B"]], columns=["0", "1", "2"]
+ )
+ data["0"] = data["0"].astype("category")
+ data["0"] = data["0"].cat.rename_categories({"C": "B", "B": "C"})
+
+ a = data.groupby(by=["0", "1"])["2"].value_counts()
+ b = data.groupby(by=["0", "1"]).size()
+
+ result = getattr(a, operation)(b)
+ expected = getattr(a, operation)(b.sort_index(ascending=False))
+
+ tm.assert_series_equal(result, expected)
+
+ def test_multiindex_assign_aligns_as_implicit_tuple(self):
+ # GH 61841
+ cols = MultiIndex.from_tuples([("A", "B")])
+ df1 = DataFrame([[i] for i in range(3)], columns=cols)
+ df2 = df1.copy()
+ df3 = df1.copy()
+ s1 = df1["A"].rolling(2).mean()
+ s2 = s1.copy()
+ s3 = s1.copy()
+
+ df2["C"] = s2
+ df3[("C", "")] = s3
+ tm.assert_frame_equal(df2, df3)
+
+ df1["C"] = s1
+ tm.assert_frame_equal(df1, df2)
+ tm.assert_frame_equal(df1, df3)
+
+ df1["C"] = s1
+ tm.assert_frame_equal(df1, df2)
+ tm.assert_frame_equal(df1, df3)
+
+ def test_multiindex_assign_alignment_with_non_string_dtype(self):
+ # GH 62518
+ columns = MultiIndex.from_arrays(
+ [["a", "a", "z", "z"], pd.Categorical([1, 2, 1, 2])]
+ )
+
+ meta = DataFrame(columns=columns, dtype=object)
+ meta["z"] = meta["z"].astype("int64")
+
+ result = DataFrame(
+ data={
+ ("a", 1): Series([], dtype=object),
+ ("a", 2): Series([], dtype=object),
+ ("z", 1): Series([], dtype="int64"),
+ ("z", 2): Series([], dtype="int64"),
+ },
+ columns=columns,
+ )
+
+ tm.assert_frame_equal(meta, result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_partial.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_partial.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdb3e6bb1f9518b9688e5fc77efded30f606bb08
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_partial.py
@@ -0,0 +1,255 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ MultiIndex,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestMultiIndexPartial:
+ def test_getitem_partial_int(self):
+ # GH 12416
+ # with single item
+ l1 = [10, 20]
+ l2 = ["a", "b"]
+ df = DataFrame(index=range(2), columns=MultiIndex.from_product([l1, l2]))
+ expected = DataFrame(index=range(2), columns=l2)
+ result = df[20]
+ tm.assert_frame_equal(result, expected)
+
+ # with list
+ expected = DataFrame(
+ index=range(2), columns=MultiIndex.from_product([l1[1:], l2])
+ )
+ result = df[[20]]
+ tm.assert_frame_equal(result, expected)
+
+ # missing item:
+ with pytest.raises(KeyError, match="1"):
+ df[1]
+ with pytest.raises(KeyError, match=r"'\[1\] not in index'"):
+ df[[1]]
+
+ def test_series_slice_partial(self):
+ pass
+
+ def test_xs_partial(
+ self,
+ multiindex_dataframe_random_data,
+ multiindex_year_month_day_dataframe_random_data,
+ ):
+ frame = multiindex_dataframe_random_data
+ ymd = multiindex_year_month_day_dataframe_random_data
+ result = frame.xs("foo")
+ result2 = frame.loc["foo"]
+ expected = frame.T["foo"].T
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(result, result2)
+
+ result = ymd.xs((2000, 4))
+ expected = ymd.loc[2000, 4]
+ tm.assert_frame_equal(result, expected)
+
+ # ex from #1796
+ index = MultiIndex(
+ levels=[["foo", "bar"], ["one", "two"], [-1, 1]],
+ codes=[
+ [0, 0, 0, 0, 1, 1, 1, 1],
+ [0, 0, 1, 1, 0, 0, 1, 1],
+ [0, 1, 0, 1, 0, 1, 0, 1],
+ ],
+ )
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((8, 4)),
+ index=index,
+ columns=list("abcd"),
+ )
+
+ result = df.xs(("foo", "one"))
+ expected = df.loc["foo", "one"]
+ tm.assert_frame_equal(result, expected)
+
+ def test_getitem_partial(self, multiindex_year_month_day_dataframe_random_data):
+ ymd = multiindex_year_month_day_dataframe_random_data
+ ymd = ymd.T
+ result = ymd[2000, 2]
+
+ expected = ymd.reindex(columns=ymd.columns[ymd.columns.codes[1] == 1])
+ expected.columns = expected.columns.droplevel(0).droplevel(0)
+ tm.assert_frame_equal(result, expected)
+
+ def test_fancy_slice_partial(
+ self,
+ multiindex_dataframe_random_data,
+ multiindex_year_month_day_dataframe_random_data,
+ ):
+ frame = multiindex_dataframe_random_data
+ result = frame.loc["bar":"baz"]
+ expected = frame[3:7]
+ tm.assert_frame_equal(result, expected)
+
+ ymd = multiindex_year_month_day_dataframe_random_data
+ result = ymd.loc[(2000, 2) : (2000, 4)]
+ lev = ymd.index.codes[1]
+ expected = ymd[(lev >= 1) & (lev <= 3)]
+ tm.assert_frame_equal(result, expected)
+
+ def test_getitem_partial_column_select(self):
+ idx = MultiIndex(
+ codes=[[0, 0, 0], [0, 1, 1], [1, 0, 1]],
+ levels=[["a", "b"], ["x", "y"], ["p", "q"]],
+ )
+ df = DataFrame(np.random.default_rng(2).random((3, 2)), index=idx)
+
+ result = df.loc[("a", "y"), :]
+ expected = df.loc[("a", "y")]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[("a", "y"), [1, 0]]
+ expected = df.loc[("a", "y")][[1, 0]]
+ tm.assert_frame_equal(result, expected)
+
+ with pytest.raises(KeyError, match=r"\('a', 'foo'\)"):
+ df.loc[("a", "foo"), :]
+
+ def test_partial_set(
+ self,
+ multiindex_year_month_day_dataframe_random_data,
+ ):
+ # GH #397
+ ymd = multiindex_year_month_day_dataframe_random_data
+ df = ymd.copy()
+ exp = ymd.copy()
+ df.loc[2000, 4] = 0
+ exp.iloc[65:85] = 0
+ tm.assert_frame_equal(df, exp)
+
+ with tm.raises_chained_assignment_error():
+ df["A"].loc[2000, 4] = 1
+ df.loc[(2000, 4), "A"] = 1
+ exp.iloc[65:85, 0] = 1
+ tm.assert_frame_equal(df, exp)
+
+ df.loc[2000] = 5
+ exp.iloc[:100] = 5
+ tm.assert_frame_equal(df, exp)
+
+ # this works...for now
+ with tm.raises_chained_assignment_error():
+ df["A"].iloc[14] = 5
+ assert df["A"].iloc[14] == exp["A"].iloc[14]
+
+ @pytest.mark.parametrize("dtype", [int, float])
+ def test_getitem_intkey_leading_level(
+ self, multiindex_year_month_day_dataframe_random_data, dtype
+ ):
+ # GH#33355 dont fall-back to positional when leading level is int
+ ymd = multiindex_year_month_day_dataframe_random_data
+ levels = ymd.index.levels
+ ymd.index = ymd.index.set_levels([levels[0].astype(dtype), *levels[1:]])
+ ser = ymd["A"]
+ mi = ser.index
+ assert isinstance(mi, MultiIndex)
+ if dtype is int:
+ assert mi.levels[0].dtype == np.dtype(int)
+ else:
+ assert mi.levels[0].dtype == np.float64
+
+ assert 14 not in mi.levels[0]
+ assert not mi.levels[0]._should_fallback_to_positional
+ assert not mi._should_fallback_to_positional
+
+ with pytest.raises(KeyError, match="14"):
+ ser[14]
+
+ # ---------------------------------------------------------------------
+
+ def test_setitem_multiple_partial(self, multiindex_dataframe_random_data):
+ frame = multiindex_dataframe_random_data
+ expected = frame.copy()
+ result = frame.copy()
+ result.loc[["foo", "bar"]] = 0
+ expected.loc["foo"] = 0
+ expected.loc["bar"] = 0
+ tm.assert_frame_equal(result, expected)
+
+ expected = frame.copy()
+ result = frame.copy()
+ result.loc["foo":"bar"] = 0
+ expected.loc["foo"] = 0
+ expected.loc["bar"] = 0
+ tm.assert_frame_equal(result, expected)
+
+ expected = frame["A"].copy()
+ result = frame["A"].copy()
+ result.loc[["foo", "bar"]] = 0
+ expected.loc["foo"] = 0
+ expected.loc["bar"] = 0
+ tm.assert_series_equal(result, expected)
+
+ expected = frame["A"].copy()
+ result = frame["A"].copy()
+ result.loc["foo":"bar"] = 0
+ expected.loc["foo"] = 0
+ expected.loc["bar"] = 0
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "indexer, exp_idx, exp_values",
+ [
+ (
+ slice("2019-2", None),
+ DatetimeIndex(["2019-02-01"], dtype="M8[ns]"),
+ [2, 3],
+ ),
+ (
+ slice(None, "2019-2"),
+ date_range("2019", periods=2, freq="MS", unit="ns"),
+ [0, 1, 2, 3],
+ ),
+ ],
+ )
+ def test_partial_getitem_loc_datetime(self, indexer, exp_idx, exp_values):
+ # GH: 25165
+ date_idx = date_range("2019", periods=2, freq="MS", unit="ns")
+ df = DataFrame(
+ list(range(4)),
+ index=MultiIndex.from_product([date_idx, [0, 1]], names=["x", "y"]),
+ )
+ expected = DataFrame(
+ exp_values,
+ index=MultiIndex.from_product([exp_idx, [0, 1]], names=["x", "y"]),
+ )
+ result = df[indexer]
+ tm.assert_frame_equal(result, expected)
+ result = df.loc[indexer]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc(axis=0)[indexer]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[indexer, :]
+ tm.assert_frame_equal(result, expected)
+
+ df2 = df.swaplevel(0, 1).sort_index()
+ expected = expected.swaplevel(0, 1).sort_index()
+
+ result = df2.loc[:, indexer, :]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_getitem_partial_both_axis():
+ # gh-12660
+ iterables = [["a", "b"], [2, 1]]
+ columns = MultiIndex.from_product(iterables, names=["col1", "col2"])
+ rows = MultiIndex.from_product(iterables, names=["row1", "row2"])
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 4)), index=rows, columns=columns
+ )
+ expected = df.iloc[:2, 2:].droplevel("row1").droplevel("col1", axis=1)
+ result = df.loc["a", "b"]
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_setitem.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_setitem.py
new file mode 100644
index 0000000000000000000000000000000000000000..615a0eb6e1af19cf3135edff68e06def06a95987
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_setitem.py
@@ -0,0 +1,553 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Series,
+ date_range,
+ isna,
+ notna,
+)
+import pandas._testing as tm
+
+
+def assert_equal(a, b):
+ assert a == b
+
+
+class TestMultiIndexSetItem:
+ def check(self, target, indexers, value, compare_fn=assert_equal, expected=None):
+ target.loc[indexers] = value
+ result = target.loc[indexers]
+ if expected is None:
+ expected = value
+ compare_fn(result, expected)
+
+ def test_setitem_multiindex(self):
+ # GH#7190
+ cols = ["A", "w", "l", "a", "x", "X", "d", "profit"]
+ index = MultiIndex.from_product(
+ [np.arange(0, 100), np.arange(0, 80)], names=["time", "firm"]
+ )
+ t, n = 0, 2
+
+ df = DataFrame(
+ np.nan,
+ columns=cols,
+ index=index,
+ )
+ self.check(target=df, indexers=((t, n), "X"), value=0)
+
+ df = DataFrame(-999, columns=cols, index=index)
+ self.check(target=df, indexers=((t, n), "X"), value=1)
+
+ df = DataFrame(columns=cols, index=index)
+ self.check(target=df, indexers=((t, n), "X"), value=2)
+
+ # gh-7218: assigning with 0-dim arrays
+ df = DataFrame(-999, columns=cols, index=index)
+ self.check(
+ target=df,
+ indexers=((t, n), "X"),
+ value=np.array(3),
+ expected=3,
+ )
+
+ def test_setitem_multiindex2(self):
+ # GH#5206
+ df = DataFrame(
+ np.arange(25).reshape(5, 5), columns="A,B,C,D,E".split(","), dtype=float
+ )
+ df["F"] = 99
+ row_selection = df["A"] % 2 == 0
+ col_selection = ["B", "C"]
+ df.loc[row_selection, col_selection] = df["F"]
+ output = DataFrame(99.0, index=[0, 2, 4], columns=["B", "C"])
+ tm.assert_frame_equal(df.loc[row_selection, col_selection], output)
+ self.check(
+ target=df,
+ indexers=(row_selection, col_selection),
+ value=df["F"],
+ compare_fn=tm.assert_frame_equal,
+ expected=output,
+ )
+
+ def test_setitem_multiindex3(self):
+ # GH#11372
+ idx = MultiIndex.from_product(
+ [["A", "B", "C"], date_range("2015-01-01", "2015-04-01", freq="MS")]
+ )
+ cols = MultiIndex.from_product(
+ [["foo", "bar"], date_range("2016-01-01", "2016-02-01", freq="MS")]
+ )
+
+ df = DataFrame(
+ np.random.default_rng(2).random((12, 4)), index=idx, columns=cols
+ )
+
+ subidx = MultiIndex.from_arrays(
+ [["A", "A"], date_range("2015-01-01", "2015-02-01", freq="MS")]
+ )
+ subcols = MultiIndex.from_arrays(
+ [["foo", "foo"], date_range("2016-01-01", "2016-02-01", freq="MS")]
+ )
+
+ vals = DataFrame(
+ np.random.default_rng(2).random((2, 2)), index=subidx, columns=subcols
+ )
+ self.check(
+ target=df,
+ indexers=(subidx, subcols),
+ value=vals,
+ compare_fn=tm.assert_frame_equal,
+ )
+ # set all columns
+ vals = DataFrame(
+ np.random.default_rng(2).random((2, 4)), index=subidx, columns=cols
+ )
+ self.check(
+ target=df,
+ indexers=(subidx, slice(None, None, None)),
+ value=vals,
+ compare_fn=tm.assert_frame_equal,
+ )
+ # identity
+ copy = df.copy()
+ self.check(
+ target=df,
+ indexers=(df.index, df.columns),
+ value=df,
+ compare_fn=tm.assert_frame_equal,
+ expected=copy,
+ )
+
+ def test_multiindex_setitem(self):
+ # GH 3738
+ # setting with a multi-index right hand side
+ arrays = [
+ np.array(["bar", "bar", "baz", "qux", "qux", "bar"]),
+ np.array(["one", "two", "one", "one", "two", "one"]),
+ np.arange(0, 6, 1),
+ ]
+
+ df_orig = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 3)),
+ index=arrays,
+ columns=["A", "B", "C"],
+ ).sort_index()
+
+ expected = df_orig.loc[["bar"]] * 2
+ df = df_orig.copy()
+ df.loc[["bar"]] *= 2
+ tm.assert_frame_equal(df.loc[["bar"]], expected)
+
+ # raise because these have differing levels
+ msg = "cannot align on a multi-index with out specifying the join levels"
+ with pytest.raises(TypeError, match=msg):
+ df.loc["bar"] *= 2
+
+ def test_multiindex_setitem2(self):
+ # from SO
+ # https://stackoverflow.com/questions/24572040/pandas-access-the-level-of-multiindex-for-inplace-operation
+ df_orig = DataFrame.from_dict(
+ {
+ "price": {
+ ("DE", "Coal", "Stock"): 2,
+ ("DE", "Gas", "Stock"): 4,
+ ("DE", "Elec", "Demand"): 1,
+ ("FR", "Gas", "Stock"): 5,
+ ("FR", "Solar", "SupIm"): 0,
+ ("FR", "Wind", "SupIm"): 0,
+ }
+ }
+ )
+ df_orig.index = MultiIndex.from_tuples(
+ df_orig.index, names=["Sit", "Com", "Type"]
+ )
+
+ expected = df_orig.copy()
+ expected.iloc[[0, 1, 3]] *= 2
+
+ idx = pd.IndexSlice
+ df = df_orig.copy()
+ df.loc[idx[:, :, "Stock"], :] *= 2
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+ df.loc[idx[:, :, "Stock"], "price"] *= 2
+ tm.assert_frame_equal(df, expected)
+
+ def test_multiindex_assignment(self):
+ # GH3777 part 2
+
+ # mixed dtype
+ df = DataFrame(
+ np.random.default_rng(2).integers(5, 10, size=9).reshape(3, 3),
+ columns=list("abc"),
+ index=[[4, 4, 8], [8, 10, 12]],
+ )
+ df["d"] = np.nan
+ arr = np.array([0.0, 1.0])
+
+ df.loc[4, "d"] = arr
+ tm.assert_series_equal(df.loc[4, "d"], Series(arr, index=[8, 10], name="d"))
+
+ def test_multiindex_assignment_single_dtype(self):
+ # GH3777 part 2b
+ # single dtype
+ arr = np.array([0.0, 1.0])
+
+ df = DataFrame(
+ np.random.default_rng(2).integers(5, 10, size=9).reshape(3, 3),
+ columns=list("abc"),
+ index=[[4, 4, 8], [8, 10, 12]],
+ dtype=np.int64,
+ )
+
+ # arr can be losslessly cast to int, so this setitem is inplace
+ df.loc[4, "c"] = arr
+ exp = Series(arr, index=[8, 10], name="c", dtype="int64")
+ result = df.loc[4, "c"]
+ tm.assert_series_equal(result, exp)
+
+ # arr + 0.5 cannot be cast losslessly to int, so we upcast
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[4, "c"] = arr + 0.5
+ # Upcast so that we can add .5
+ df = df.astype({"c": "float64"})
+ df.loc[4, "c"] = arr + 0.5
+
+ # scalar ok
+ df.loc[4, "c"] = 10
+ exp = Series(10, index=[8, 10], name="c", dtype="float64")
+ tm.assert_series_equal(df.loc[4, "c"], exp)
+
+ # invalid assignments
+ msg = "Must have equal len keys and value when setting with an iterable"
+ with pytest.raises(ValueError, match=msg):
+ df.loc[4, "c"] = [0, 1, 2, 3]
+
+ with pytest.raises(ValueError, match=msg):
+ df.loc[4, "c"] = [0]
+
+ # But with a length-1 listlike column indexer this behaves like
+ # `df.loc[4, "c"] = 0
+ df.loc[4, ["c"]] = [0]
+ assert (df.loc[4, "c"] == 0).all()
+
+ def test_groupby_example(self):
+ # groupby example
+ NUM_ROWS = 100
+ NUM_COLS = 10
+ col_names = ["A" + num for num in map(str, np.arange(NUM_COLS).tolist())]
+ index_cols = col_names[:5]
+
+ df = DataFrame(
+ np.random.default_rng(2).integers(5, size=(NUM_ROWS, NUM_COLS)),
+ dtype=np.int64,
+ columns=col_names,
+ )
+ df = df.set_index(index_cols).sort_index()
+ grp = df.groupby(level=index_cols[:4])
+ df["new_col"] = np.nan
+
+ # we are actually operating on a copy here
+ # but in this case, that's ok
+ for name, df2 in grp:
+ new_vals = np.arange(df2.shape[0])
+ df.loc[name, "new_col"] = new_vals
+
+ def test_series_setitem(self, multiindex_year_month_day_dataframe_random_data):
+ ymd = multiindex_year_month_day_dataframe_random_data
+ s = ymd["A"]
+
+ s[2000, 3] = np.nan
+ assert isna(s.values[42:65]).all()
+ assert notna(s.values[:42]).all()
+ assert notna(s.values[65:]).all()
+
+ s[2000, 3, 10] = np.nan
+ assert isna(s.iloc[49])
+
+ with pytest.raises(KeyError, match="49"):
+ # GH#33355 dont fall-back to positional when leading level is int
+ s[49]
+
+ def test_frame_getitem_setitem_boolean(self, multiindex_dataframe_random_data):
+ frame = multiindex_dataframe_random_data
+ df = frame.T.copy()
+ values = df.values.copy()
+
+ result = df[df > 0]
+ expected = df.where(df > 0)
+ tm.assert_frame_equal(result, expected)
+
+ df[df > 0] = 5
+ values[values > 0] = 5
+ tm.assert_almost_equal(df.values, values)
+
+ df[df == 5] = 0
+ values[values == 5] = 0
+ tm.assert_almost_equal(df.values, values)
+
+ # a df that needs alignment first
+ df[df[:-1] < 0] = 2
+ np.putmask(values[:-1], values[:-1] < 0, 2)
+ tm.assert_almost_equal(df.values, values)
+
+ with pytest.raises(TypeError, match="boolean values only"):
+ df[df * 0] = 2
+
+ def test_frame_getitem_setitem_multislice(self):
+ levels = [["t1", "t2"], ["a", "b", "c"]]
+ codes = [[0, 0, 0, 1, 1], [0, 1, 2, 0, 1]]
+ midx = MultiIndex(codes=codes, levels=levels, names=[None, "id"])
+ df = DataFrame({"value": [1, 2, 3, 7, 8]}, index=midx)
+
+ result = df.loc[:, "value"]
+ tm.assert_series_equal(df["value"], result)
+
+ result = df.loc[df.index[1:3], "value"]
+ tm.assert_series_equal(df["value"][1:3], result)
+
+ result = df.loc[:, :]
+ tm.assert_frame_equal(df, result)
+
+ result = df
+ df.loc[:, "value"] = 10
+ result["value"] = 10
+ tm.assert_frame_equal(df, result)
+
+ df.loc[:, :] = 10
+ tm.assert_frame_equal(df, result)
+
+ def test_frame_setitem_multi_column(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=[["a", "a", "b", "b"], [0, 1, 0, 1]],
+ )
+
+ cp = df.copy()
+ cp["a"] = cp["b"]
+ tm.assert_frame_equal(cp["a"], cp["b"])
+
+ # set with ndarray
+ cp = df.copy()
+ cp["a"] = cp["b"].values
+ tm.assert_frame_equal(cp["a"], cp["b"])
+
+ def test_frame_setitem_multi_column2(self):
+ # ---------------------------------------
+ # GH#1803
+ columns = MultiIndex.from_tuples([("A", "1"), ("A", "2"), ("B", "1")])
+ df = DataFrame(index=[1, 3, 5], columns=columns)
+
+ # Works, but adds a column instead of updating the two existing ones
+ df["A"] = 0.0 # Doesn't work
+ assert (df["A"].values == 0).all()
+
+ # it broadcasts
+ df["B", "1"] = [1, 2, 3]
+ df["A"] = df["B", "1"]
+
+ sliced_a1 = df["A", "1"]
+ sliced_a2 = df["A", "2"]
+ sliced_b1 = df["B", "1"]
+ tm.assert_series_equal(sliced_a1, sliced_b1, check_names=False)
+ tm.assert_series_equal(sliced_a2, sliced_b1, check_names=False)
+ assert sliced_a1.name == ("A", "1")
+ assert sliced_a2.name == ("A", "2")
+ assert sliced_b1.name == ("B", "1")
+
+ def test_loc_getitem_tuple_plus_columns(
+ self, multiindex_year_month_day_dataframe_random_data
+ ):
+ # GH #1013
+ ymd = multiindex_year_month_day_dataframe_random_data
+ df = ymd[:5]
+
+ result = df.loc[(2000, 1, 6), ["A", "B", "C"]]
+ expected = df.loc[2000, 1, 6][["A", "B", "C"]]
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.filterwarnings("ignore:Setting a value on a view:FutureWarning")
+ def test_loc_getitem_setitem_slice_integers(self, frame_or_series):
+ index = MultiIndex(
+ levels=[[0, 1, 2], [0, 2]], codes=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]]
+ )
+
+ obj = DataFrame(
+ np.random.default_rng(2).standard_normal((len(index), 4)),
+ index=index,
+ columns=["a", "b", "c", "d"],
+ )
+ obj = tm.get_obj(obj, frame_or_series)
+
+ res = obj.loc[1:2]
+ exp = obj.reindex(obj.index[2:])
+ tm.assert_equal(res, exp)
+
+ obj.loc[1:2] = 7
+ assert (obj.loc[1:2] == 7).values.all()
+
+ def test_setitem_change_dtype(self, multiindex_dataframe_random_data):
+ frame = multiindex_dataframe_random_data
+ dft = frame.T
+ s = dft["foo", "two"]
+ dft["foo", "two"] = s > s.median()
+ tm.assert_series_equal(dft["foo", "two"], s > s.median())
+ # assert isinstance(dft._data.blocks[1].items, MultiIndex)
+
+ reindexed = dft.reindex(columns=[("foo", "two")])
+ tm.assert_series_equal(reindexed["foo", "two"], s > s.median())
+
+ def test_set_column_scalar_with_loc(self, multiindex_dataframe_random_data):
+ frame = multiindex_dataframe_random_data
+ subset = frame.index[[1, 4, 5]]
+
+ frame.loc[subset] = 99
+ assert (frame.loc[subset].values == 99).all()
+
+ frame_original = frame.copy()
+ col = frame["B"]
+ col[subset] = 97
+ # chained setitem doesn't work with CoW
+ tm.assert_frame_equal(frame, frame_original)
+
+ def test_nonunique_assignment_1750(self):
+ df = DataFrame(
+ [[1, 1, "x", "X"], [1, 1, "y", "Y"], [1, 2, "z", "Z"]], columns=list("ABCD")
+ )
+
+ df = df.set_index(["A", "B"])
+ mi = MultiIndex.from_tuples([(1, 1)])
+
+ df.loc[mi, "C"] = "_"
+
+ assert (df.xs((1, 1))["C"] == "_").all()
+
+ def test_astype_assignment_with_dups(self):
+ # GH 4686
+ # assignment with dups that has a dtype change
+ cols = MultiIndex.from_tuples([("A", "1"), ("B", "1"), ("A", "2")])
+ df = DataFrame(np.arange(3).reshape((1, 3)), columns=cols, dtype=object)
+ index = df.index.copy()
+
+ df["A"] = df["A"].astype(np.float64)
+ tm.assert_index_equal(df.index, index)
+
+ def test_setitem_nonmonotonic(self):
+ # https://github.com/pandas-dev/pandas/issues/31449
+ index = MultiIndex.from_tuples(
+ [("a", "c"), ("b", "x"), ("a", "d")], names=["l1", "l2"]
+ )
+ df = DataFrame(data=[0, 1, 2], index=index, columns=["e"])
+ df.loc["a", "e"] = np.arange(99, 101, dtype="int64")
+ expected = DataFrame({"e": [99, 1, 100]}, index=index)
+ tm.assert_frame_equal(df, expected)
+
+
+class TestSetitemWithExpansionMultiIndex:
+ def test_setitem_new_column_mixed_depth(self):
+ arrays = [
+ ["a", "top", "top", "routine1", "routine1", "routine2"],
+ ["", "OD", "OD", "result1", "result2", "result1"],
+ ["", "wx", "wy", "", "", ""],
+ ]
+
+ tuples = sorted(zip(*arrays))
+ index = MultiIndex.from_tuples(tuples)
+ df = DataFrame(np.random.default_rng(2).standard_normal((4, 6)), columns=index)
+
+ result = df.copy()
+ expected = df.copy()
+ result["b"] = [1, 2, 3, 4]
+ expected["b", "", ""] = [1, 2, 3, 4]
+ tm.assert_frame_equal(result, expected)
+
+ def test_setitem_new_column_all_na(self):
+ # GH#1534
+ mix = MultiIndex.from_tuples([("1a", "2a"), ("1a", "2b"), ("1a", "2c")])
+ df = DataFrame([[1, 2], [3, 4], [5, 6]], index=mix)
+ s = Series({(1, 1): 1, (1, 2): 2})
+ df["new"] = s
+ assert df["new"].isna().all()
+
+ def test_setitem_enlargement_keep_index_names(self):
+ # GH#53053
+ mi = MultiIndex.from_tuples([(1, 2, 3)], names=["i1", "i2", "i3"])
+ df = DataFrame(data=[[10, 20, 30]], index=mi, columns=["A", "B", "C"])
+ df.loc[(0, 0, 0)] = df.loc[(1, 2, 3)]
+ mi_expected = MultiIndex.from_tuples(
+ [(1, 2, 3), (0, 0, 0)], names=["i1", "i2", "i3"]
+ )
+ expected = DataFrame(
+ data=[[10, 20, 30], [10, 20, 30]],
+ index=mi_expected,
+ columns=["A", "B", "C"],
+ )
+ tm.assert_frame_equal(df, expected)
+
+ def test_setitem_enlargement_multiindex_with_none(self):
+ # GH#59153
+ # enlarging a DataFrame with a MultiIndex containing None values
+ index = MultiIndex.from_tuples(
+ [("A", "a1"), ("A", "a2"), ("B", "b1"), ("B", None)]
+ )
+ df = DataFrame([(0.0, 6.0), (1.0, 5.0), (2.0, 4.0), (3.0, 7.0)], index=index)
+ df.loc[("A", None), :] = [12.0, 13.0]
+ expected_index = MultiIndex.from_tuples(
+ [("A", "a1"), ("A", "a2"), ("B", "b1"), ("B", None), ("A", None)]
+ )
+ expected = DataFrame(
+ [[0.0, 6.0], [1.0, 5.0], [2.0, 4.0], [3.0, 7.0], [12.0, 13.0]],
+ index=expected_index,
+ columns=[0, 1],
+ )
+ tm.assert_frame_equal(df, expected, check_index_type=False)
+
+
+def test_frame_setitem_view_direct(multiindex_dataframe_random_data):
+ # this works because we are modifying the underlying array
+ # really a no-no
+ df = multiindex_dataframe_random_data.T
+ with pytest.raises(ValueError, match="read-only"):
+ df["foo"].values[:] = 0
+ assert (df["foo"].values != 0).all()
+
+
+def test_frame_setitem_copy_raises(multiindex_dataframe_random_data):
+ # will raise/warn as its chained assignment
+ df = multiindex_dataframe_random_data.T
+ with tm.raises_chained_assignment_error():
+ df["foo"]["one"] = 2
+
+
+def test_frame_setitem_copy_no_write(multiindex_dataframe_random_data):
+ frame = multiindex_dataframe_random_data.T
+ expected = frame
+ df = frame.copy()
+ with tm.raises_chained_assignment_error():
+ df["foo"]["one"] = 2
+
+ tm.assert_frame_equal(df, expected)
+
+
+def test_frame_setitem_partial_multiindex():
+ # GH 54875
+ df = DataFrame(
+ {
+ "a": [1, 2, 3],
+ "b": [3, 4, 5],
+ "c": 6,
+ "d": 7,
+ }
+ ).set_index(["a", "b", "c"])
+ ser = Series(8, index=df.index.droplevel("c"))
+ result = df.copy()
+ result["d"] = ser
+ expected = df.copy()
+ expected["d"] = 8
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_slice.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_slice.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f298e9bdd375d226b295455a6db1443f2ffaf77
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_slice.py
@@ -0,0 +1,798 @@
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+from pandas.errors import UnsortedIndexError
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.tests.indexing.common import _mklbl
+
+
+class TestMultiIndexSlicers:
+ def test_per_axis_per_level_getitem(self):
+ # GH6134
+ # example test case
+ ix = MultiIndex.from_product(
+ [_mklbl("A", 5), _mklbl("B", 7), _mklbl("C", 4), _mklbl("D", 2)]
+ )
+ df = DataFrame(np.arange(len(ix.to_numpy())), index=ix)
+
+ result = df.loc[(slice("A1", "A3"), slice(None), ["C1", "C3"]), :]
+ expected = df.loc[
+ [
+ (
+ a,
+ b,
+ c,
+ d,
+ )
+ for a, b, c, d in df.index.values
+ if a in ("A1", "A2", "A3") and c in ("C1", "C3")
+ ]
+ ]
+ tm.assert_frame_equal(result, expected)
+
+ expected = df.loc[
+ [
+ (
+ a,
+ b,
+ c,
+ d,
+ )
+ for a, b, c, d in df.index.values
+ if a in ("A1", "A2", "A3") and c in ("C1", "C2", "C3")
+ ]
+ ]
+ result = df.loc[(slice("A1", "A3"), slice(None), slice("C1", "C3")), :]
+ tm.assert_frame_equal(result, expected)
+
+ # test multi-index slicing with per axis and per index controls
+ index = MultiIndex.from_tuples(
+ [("A", 1), ("A", 2), ("A", 3), ("B", 1)], names=["one", "two"]
+ )
+ columns = MultiIndex.from_tuples(
+ [("a", "foo"), ("a", "bar"), ("b", "foo"), ("b", "bah")],
+ names=["lvl0", "lvl1"],
+ )
+
+ df = DataFrame(
+ np.arange(16, dtype="int64").reshape(4, 4), index=index, columns=columns
+ )
+ df = df.sort_index(axis=0).sort_index(axis=1)
+
+ # identity
+ result = df.loc[(slice(None), slice(None)), :]
+ tm.assert_frame_equal(result, df)
+ result = df.loc[(slice(None), slice(None)), (slice(None), slice(None))]
+ tm.assert_frame_equal(result, df)
+ result = df.loc[:, (slice(None), slice(None))]
+ tm.assert_frame_equal(result, df)
+
+ # index
+ result = df.loc[(slice(None), [1]), :]
+ expected = df.iloc[[0, 3]]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[(slice(None), 1), :]
+ expected = df.iloc[[0, 3]]
+ tm.assert_frame_equal(result, expected)
+
+ # columns
+ result = df.loc[:, (slice(None), ["foo"])]
+ expected = df.iloc[:, [1, 3]]
+ tm.assert_frame_equal(result, expected)
+
+ # both
+ result = df.loc[(slice(None), 1), (slice(None), ["foo"])]
+ expected = df.iloc[[0, 3], [1, 3]]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc["A", "a"]
+ expected = DataFrame(
+ {"bar": [1, 5, 9], "foo": [0, 4, 8]},
+ index=Index([1, 2, 3], name="two"),
+ columns=Index(["bar", "foo"], name="lvl1"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[(slice(None), [1, 2]), :]
+ expected = df.iloc[[0, 1, 3]]
+ tm.assert_frame_equal(result, expected)
+
+ # multi-level series
+ s = Series(np.arange(len(ix.to_numpy())), index=ix)
+ result = s.loc["A1":"A3", :, ["C1", "C3"]]
+ expected = s.loc[
+ [
+ (
+ a,
+ b,
+ c,
+ d,
+ )
+ for a, b, c, d in s.index.values
+ if a in ("A1", "A2", "A3") and c in ("C1", "C3")
+ ]
+ ]
+ tm.assert_series_equal(result, expected)
+
+ # boolean indexers
+ result = df.loc[(slice(None), df.loc[:, ("a", "bar")] > 5), :]
+ expected = df.iloc[[2, 3]]
+ tm.assert_frame_equal(result, expected)
+
+ msg = (
+ "cannot index with a boolean indexer "
+ "that is not the same length as the index"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.loc[(slice(None), np.array([True, False])), :]
+
+ with pytest.raises(KeyError, match=r"\[1\] not in index"):
+ # slice(None) is on the index, [1] is on the columns, but 1 is
+ # not in the columns, so we raise
+ # This used to treat [1] as positional GH#16396
+ df.loc[slice(None), [1]]
+
+ # not lexsorted
+ assert df.index._lexsort_depth == 2
+ df = df.sort_index(level=1, axis=0)
+ assert df.index._lexsort_depth == 0
+
+ msg = (
+ "MultiIndex slicing requires the index to be "
+ r"lexsorted: slicing on levels \[1\], lexsort depth 0"
+ )
+ with pytest.raises(UnsortedIndexError, match=msg):
+ df.loc[(slice(None), slice("bar")), :]
+
+ # GH 16734: not sorted, but no real slicing
+ result = df.loc[(slice(None), df.loc[:, ("a", "bar")] > 5), :]
+ tm.assert_frame_equal(result, df.iloc[[1, 3], :])
+
+ def test_multiindex_slicers_non_unique(self):
+ # GH 7106
+ # non-unique mi index support
+ df = (
+ DataFrame(
+ {
+ "A": ["foo", "foo", "foo", "foo"],
+ "B": ["a", "a", "a", "a"],
+ "C": [1, 2, 1, 3],
+ "D": [1, 2, 3, 4],
+ }
+ )
+ .set_index(["A", "B", "C"])
+ .sort_index()
+ )
+ assert not df.index.is_unique
+ expected = (
+ DataFrame({"A": ["foo", "foo"], "B": ["a", "a"], "C": [1, 1], "D": [1, 3]})
+ .set_index(["A", "B", "C"])
+ .sort_index()
+ )
+ result = df.loc[(slice(None), slice(None), 1), :]
+ tm.assert_frame_equal(result, expected)
+
+ # this is equivalent of an xs expression
+ result = df.xs(1, level=2, drop_level=False)
+ tm.assert_frame_equal(result, expected)
+
+ df = (
+ DataFrame(
+ {
+ "A": ["foo", "foo", "foo", "foo"],
+ "B": ["a", "a", "a", "a"],
+ "C": [1, 2, 1, 2],
+ "D": [1, 2, 3, 4],
+ }
+ )
+ .set_index(["A", "B", "C"])
+ .sort_index()
+ )
+ assert not df.index.is_unique
+ expected = (
+ DataFrame({"A": ["foo", "foo"], "B": ["a", "a"], "C": [1, 1], "D": [1, 3]})
+ .set_index(["A", "B", "C"])
+ .sort_index()
+ )
+ result = df.loc[(slice(None), slice(None), 1), :]
+ assert not result.index.is_unique
+ tm.assert_frame_equal(result, expected)
+
+ # GH12896
+ # numpy-implementation dependent bug
+ ints = [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 12,
+ 13,
+ 14,
+ 14,
+ 16,
+ 17,
+ 18,
+ 19,
+ 200000,
+ 200000,
+ ]
+ n = len(ints)
+ idx = MultiIndex.from_arrays([["a"] * n, ints])
+ result = Series([1] * n, index=idx)
+ result = result.sort_index()
+ result = result.loc[(slice(None), slice(100000))]
+ expected = Series([1] * (n - 2), index=idx[:-2]).sort_index()
+ tm.assert_series_equal(result, expected)
+
+ def test_multiindex_slicers_datetimelike(self):
+ # GH 7429
+ # buggy/inconsistent behavior when slicing with datetime-like
+ dates = [datetime(2012, 1, 1, 12, 12, 12) + timedelta(days=i) for i in range(6)]
+ freq = [1, 2]
+ index = MultiIndex.from_product([dates, freq], names=["date", "frequency"])
+
+ df = DataFrame(
+ np.arange(6 * 2 * 4, dtype="int64").reshape(-1, 4),
+ index=index,
+ columns=list("ABCD"),
+ )
+
+ # multi-axis slicing
+ idx = pd.IndexSlice
+ expected = df.iloc[[0, 2, 4], [0, 1]]
+ result = df.loc[
+ (
+ slice(
+ Timestamp("2012-01-01 12:12:12"), Timestamp("2012-01-03 12:12:12")
+ ),
+ slice(1, 1),
+ ),
+ slice("A", "B"),
+ ]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[
+ (
+ idx[
+ Timestamp("2012-01-01 12:12:12") : Timestamp("2012-01-03 12:12:12")
+ ],
+ idx[1:1],
+ ),
+ slice("A", "B"),
+ ]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[
+ (
+ slice(
+ Timestamp("2012-01-01 12:12:12"), Timestamp("2012-01-03 12:12:12")
+ ),
+ 1,
+ ),
+ slice("A", "B"),
+ ]
+ tm.assert_frame_equal(result, expected)
+
+ # with strings
+ result = df.loc[
+ (slice("2012-01-01 12:12:12", "2012-01-03 12:12:12"), slice(1, 1)),
+ slice("A", "B"),
+ ]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[
+ (idx["2012-01-01 12:12:12":"2012-01-03 12:12:12"], 1), idx["A", "B"]
+ ]
+ tm.assert_frame_equal(result, expected)
+
+ def test_multiindex_slicers_edges(self):
+ # GH 8132
+ # various edge cases
+ df = DataFrame(
+ {
+ "A": ["A0"] * 5 + ["A1"] * 5 + ["A2"] * 5,
+ "B": ["B0", "B0", "B1", "B1", "B2"] * 3,
+ "DATE": [
+ "2013-06-11",
+ "2013-07-02",
+ "2013-07-09",
+ "2013-07-30",
+ "2013-08-06",
+ "2013-06-11",
+ "2013-07-02",
+ "2013-07-09",
+ "2013-07-30",
+ "2013-08-06",
+ "2013-09-03",
+ "2013-10-01",
+ "2013-07-09",
+ "2013-08-06",
+ "2013-09-03",
+ ],
+ "VALUES": [22, 35, 14, 9, 4, 40, 18, 4, 2, 5, 1, 2, 3, 4, 2],
+ }
+ )
+
+ df["DATE"] = pd.to_datetime(df["DATE"])
+ df1 = df.set_index(["A", "B", "DATE"])
+ df1 = df1.sort_index()
+
+ # A1 - Get all values under "A0" and "A1"
+ result = df1.loc[(slice("A1")), :]
+ expected = df1.iloc[0:10]
+ tm.assert_frame_equal(result, expected)
+
+ # A2 - Get all values from the start to "A2"
+ result = df1.loc[(slice("A2")), :]
+ expected = df1
+ tm.assert_frame_equal(result, expected)
+
+ # A3 - Get all values under "B1" or "B2"
+ result = df1.loc[(slice(None), slice("B1", "B2")), :]
+ expected = df1.iloc[[2, 3, 4, 7, 8, 9, 12, 13, 14]]
+ tm.assert_frame_equal(result, expected)
+
+ # A4 - Get all values between 2013-07-02 and 2013-07-09
+ result = df1.loc[(slice(None), slice(None), slice("20130702", "20130709")), :]
+ expected = df1.iloc[[1, 2, 6, 7, 12]]
+ tm.assert_frame_equal(result, expected)
+
+ # B1 - Get all values in B0 that are also under A0, A1 and A2
+ result = df1.loc[(slice("A2"), slice("B0")), :]
+ expected = df1.iloc[[0, 1, 5, 6, 10, 11]]
+ tm.assert_frame_equal(result, expected)
+
+ # B2 - Get all values in B0, B1 and B2 (similar to what #2 is doing for
+ # the As)
+ result = df1.loc[(slice(None), slice("B2")), :]
+ expected = df1
+ tm.assert_frame_equal(result, expected)
+
+ # B3 - Get all values from B1 to B2 and up to 2013-08-06
+ result = df1.loc[(slice(None), slice("B1", "B2"), slice("2013-08-06")), :]
+ expected = df1.iloc[[2, 3, 4, 7, 8, 9, 12, 13]]
+ tm.assert_frame_equal(result, expected)
+
+ # B4 - Same as A4 but the start of the date slice is not a key.
+ # shows indexing on a partial selection slice
+ result = df1.loc[(slice(None), slice(None), slice("20130701", "20130709")), :]
+ expected = df1.iloc[[1, 2, 6, 7, 12]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_per_axis_per_level_doc_examples(self):
+ # test index maker
+ idx = pd.IndexSlice
+
+ # from indexing.rst / advanced
+ index = MultiIndex.from_product(
+ [_mklbl("A", 4), _mklbl("B", 2), _mklbl("C", 4), _mklbl("D", 2)]
+ )
+ columns = MultiIndex.from_tuples(
+ [("a", "foo"), ("a", "bar"), ("b", "foo"), ("b", "bah")],
+ names=["lvl0", "lvl1"],
+ )
+ df = DataFrame(
+ np.arange(len(index) * len(columns), dtype="int64").reshape(
+ (len(index), len(columns))
+ ),
+ index=index,
+ columns=columns,
+ )
+ result = df.loc[(slice("A1", "A3"), slice(None), ["C1", "C3"]), :]
+ expected = df.loc[
+ [
+ (
+ a,
+ b,
+ c,
+ d,
+ )
+ for a, b, c, d in df.index.values
+ if a in ("A1", "A2", "A3") and c in ("C1", "C3")
+ ]
+ ]
+ tm.assert_frame_equal(result, expected)
+ result = df.loc[idx["A1":"A3", :, ["C1", "C3"]], :]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[(slice(None), slice(None), ["C1", "C3"]), :]
+ expected = df.loc[
+ [
+ (
+ a,
+ b,
+ c,
+ d,
+ )
+ for a, b, c, d in df.index.values
+ if c in ("C1", "C3")
+ ]
+ ]
+ tm.assert_frame_equal(result, expected)
+ result = df.loc[idx[:, :, ["C1", "C3"]], :]
+ tm.assert_frame_equal(result, expected)
+
+ # not sorted
+ msg = (
+ "MultiIndex slicing requires the index to be lexsorted: "
+ r"slicing on levels \[1\], lexsort depth 1"
+ )
+ with pytest.raises(UnsortedIndexError, match=msg):
+ df.loc["A1", ("a", slice("foo"))]
+
+ # GH 16734: not sorted, but no real slicing
+ tm.assert_frame_equal(
+ df.loc["A1", (slice(None), "foo")], df.loc["A1"].iloc[:, [0, 2]]
+ )
+
+ df = df.sort_index(axis=1)
+
+ # slicing
+ df.loc["A1", (slice(None), "foo")]
+ df.loc[(slice(None), slice(None), ["C1", "C3"]), (slice(None), "foo")]
+
+ # setitem
+ df.loc(axis=0)[:, :, ["C1", "C3"]] = -10
+
+ def test_loc_axis_arguments(self):
+ index = MultiIndex.from_product(
+ [_mklbl("A", 4), _mklbl("B", 2), _mklbl("C", 4), _mklbl("D", 2)]
+ )
+ columns = MultiIndex.from_tuples(
+ [("a", "foo"), ("a", "bar"), ("b", "foo"), ("b", "bah")],
+ names=["lvl0", "lvl1"],
+ )
+ df = (
+ DataFrame(
+ np.arange(len(index) * len(columns), dtype="int64").reshape(
+ (len(index), len(columns))
+ ),
+ index=index,
+ columns=columns,
+ )
+ .sort_index()
+ .sort_index(axis=1)
+ )
+
+ # axis 0
+ result = df.loc(axis=0)["A1":"A3", :, ["C1", "C3"]]
+ expected = df.loc[
+ [
+ (
+ a,
+ b,
+ c,
+ d,
+ )
+ for a, b, c, d in df.index.values
+ if a in ("A1", "A2", "A3") and c in ("C1", "C3")
+ ]
+ ]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc(axis="index")[:, :, ["C1", "C3"]]
+ expected = df.loc[
+ [
+ (
+ a,
+ b,
+ c,
+ d,
+ )
+ for a, b, c, d in df.index.values
+ if c in ("C1", "C3")
+ ]
+ ]
+ tm.assert_frame_equal(result, expected)
+
+ # axis 1
+ result = df.loc(axis=1)[:, "foo"]
+ expected = df.loc[:, (slice(None), "foo")]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc(axis="columns")[:, "foo"]
+ expected = df.loc[:, (slice(None), "foo")]
+ tm.assert_frame_equal(result, expected)
+
+ # invalid axis
+ for i in [-1, 2, "foo"]:
+ msg = f"No axis named {i} for object type DataFrame"
+ with pytest.raises(ValueError, match=msg):
+ df.loc(axis=i)[:, :, ["C1", "C3"]]
+
+ def test_loc_axis_single_level_multi_col_indexing_multiindex_col_df(self):
+ # GH29519
+ df = DataFrame(
+ np.arange(27).reshape(3, 9),
+ columns=MultiIndex.from_product([["a1", "a2", "a3"], ["b1", "b2", "b3"]]),
+ )
+ result = df.loc(axis=1)["a1":"a2"]
+ expected = df.iloc[:, :-3]
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_axis_single_level_single_col_indexing_multiindex_col_df(self):
+ # GH29519
+ df = DataFrame(
+ np.arange(27).reshape(3, 9),
+ columns=MultiIndex.from_product([["a1", "a2", "a3"], ["b1", "b2", "b3"]]),
+ )
+ result = df.loc(axis=1)["a1"]
+ expected = df.iloc[:, :3]
+ expected.columns = ["b1", "b2", "b3"]
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_ax_single_level_indexer_simple_df(self):
+ # GH29519
+ # test single level indexing on single index column data frame
+ df = DataFrame(np.arange(9).reshape(3, 3), columns=["a", "b", "c"])
+ result = df.loc(axis=1)["a"]
+ expected = Series(np.array([0, 3, 6]), name="a")
+ tm.assert_series_equal(result, expected)
+
+ def test_per_axis_per_level_setitem(self):
+ # test index maker
+ idx = pd.IndexSlice
+
+ # test multi-index slicing with per axis and per index controls
+ index = MultiIndex.from_tuples(
+ [("A", 1), ("A", 2), ("A", 3), ("B", 1)], names=["one", "two"]
+ )
+ columns = MultiIndex.from_tuples(
+ [("a", "foo"), ("a", "bar"), ("b", "foo"), ("b", "bah")],
+ names=["lvl0", "lvl1"],
+ )
+
+ df_orig = DataFrame(
+ np.arange(16, dtype="int64").reshape(4, 4), index=index, columns=columns
+ )
+ df_orig = df_orig.sort_index(axis=0).sort_index(axis=1)
+
+ # identity
+ df = df_orig.copy()
+ df.loc[(slice(None), slice(None)), :] = 100
+ expected = df_orig.copy()
+ expected.iloc[:, :] = 100
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+ df.loc(axis=0)[:, :] = 100
+ expected = df_orig.copy()
+ expected.iloc[:, :] = 100
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+ df.loc[(slice(None), slice(None)), (slice(None), slice(None))] = 100
+ expected = df_orig.copy()
+ expected.iloc[:, :] = 100
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+ df.loc[:, (slice(None), slice(None))] = 100
+ expected = df_orig.copy()
+ expected.iloc[:, :] = 100
+ tm.assert_frame_equal(df, expected)
+
+ # index
+ df = df_orig.copy()
+ df.loc[(slice(None), [1]), :] = 100
+ expected = df_orig.copy()
+ expected.iloc[[0, 3]] = 100
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+ df.loc[(slice(None), 1), :] = 100
+ expected = df_orig.copy()
+ expected.iloc[[0, 3]] = 100
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+ df.loc(axis=0)[:, 1] = 100
+ expected = df_orig.copy()
+ expected.iloc[[0, 3]] = 100
+ tm.assert_frame_equal(df, expected)
+
+ # columns
+ df = df_orig.copy()
+ df.loc[:, (slice(None), ["foo"])] = 100
+ expected = df_orig.copy()
+ expected.iloc[:, [1, 3]] = 100
+ tm.assert_frame_equal(df, expected)
+
+ # both
+ df = df_orig.copy()
+ df.loc[(slice(None), 1), (slice(None), ["foo"])] = 100
+ expected = df_orig.copy()
+ expected.iloc[[0, 3], [1, 3]] = 100
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+ df.loc[idx[:, 1], idx[:, ["foo"]]] = 100
+ expected = df_orig.copy()
+ expected.iloc[[0, 3], [1, 3]] = 100
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+ df.loc["A", "a"] = 100
+ expected = df_orig.copy()
+ expected.iloc[0:3, 0:2] = 100
+ tm.assert_frame_equal(df, expected)
+
+ # setting with a list-like
+ df = df_orig.copy()
+ df.loc[(slice(None), 1), (slice(None), ["foo"])] = np.array(
+ [[100, 100], [100, 100]], dtype="int64"
+ )
+ expected = df_orig.copy()
+ expected.iloc[[0, 3], [1, 3]] = 100
+ tm.assert_frame_equal(df, expected)
+
+ # not enough values
+ df = df_orig.copy()
+
+ msg = "setting an array element with a sequence."
+ with pytest.raises(ValueError, match=msg):
+ df.loc[(slice(None), 1), (slice(None), ["foo"])] = np.array(
+ [[100], [100, 100]], dtype="int64"
+ )
+
+ msg = "Must have equal len keys and value when setting with an iterable"
+ with pytest.raises(ValueError, match=msg):
+ df.loc[(slice(None), 1), (slice(None), ["foo"])] = np.array(
+ [100, 100, 100, 100], dtype="int64"
+ )
+
+ # with an alignable rhs
+ df = df_orig.copy()
+ df.loc[(slice(None), 1), (slice(None), ["foo"])] = (
+ df.loc[(slice(None), 1), (slice(None), ["foo"])] * 5
+ )
+ expected = df_orig.copy()
+ expected.iloc[[0, 3], [1, 3]] = expected.iloc[[0, 3], [1, 3]] * 5
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+ df.loc[(slice(None), 1), (slice(None), ["foo"])] *= df.loc[
+ (slice(None), 1), (slice(None), ["foo"])
+ ]
+ expected = df_orig.copy()
+ expected.iloc[[0, 3], [1, 3]] *= expected.iloc[[0, 3], [1, 3]]
+ tm.assert_frame_equal(df, expected)
+
+ rhs = df_orig.loc[(slice(None), 1), (slice(None), ["foo"])].copy()
+ rhs.loc[:, ("c", "bah")] = 10
+ df = df_orig.copy()
+ df.loc[(slice(None), 1), (slice(None), ["foo"])] *= rhs
+ expected = df_orig.copy()
+ expected.iloc[[0, 3], [1, 3]] *= expected.iloc[[0, 3], [1, 3]]
+ tm.assert_frame_equal(df, expected)
+
+ def test_multiindex_label_slicing_with_negative_step(self):
+ ser = Series(
+ np.arange(20), MultiIndex.from_product([list("abcde"), np.arange(4)])
+ )
+ SLC = pd.IndexSlice
+
+ tm.assert_indexing_slices_equivalent(ser, SLC[::-1], SLC[::-1])
+
+ tm.assert_indexing_slices_equivalent(ser, SLC["d"::-1], SLC[15::-1])
+ tm.assert_indexing_slices_equivalent(ser, SLC[("d",) :: -1], SLC[15::-1])
+
+ tm.assert_indexing_slices_equivalent(ser, SLC[:"d":-1], SLC[:11:-1])
+ tm.assert_indexing_slices_equivalent(ser, SLC[: ("d",) : -1], SLC[:11:-1])
+
+ tm.assert_indexing_slices_equivalent(ser, SLC["d":"b":-1], SLC[15:3:-1])
+ tm.assert_indexing_slices_equivalent(ser, SLC[("d",) : "b" : -1], SLC[15:3:-1])
+ tm.assert_indexing_slices_equivalent(ser, SLC["d" : ("b",) : -1], SLC[15:3:-1])
+ tm.assert_indexing_slices_equivalent(
+ ser, SLC[("d",) : ("b",) : -1], SLC[15:3:-1]
+ )
+ tm.assert_indexing_slices_equivalent(ser, SLC["b":"d":-1], SLC[:0])
+
+ tm.assert_indexing_slices_equivalent(ser, SLC[("c", 2) :: -1], SLC[10::-1])
+ tm.assert_indexing_slices_equivalent(ser, SLC[: ("c", 2) : -1], SLC[:9:-1])
+ tm.assert_indexing_slices_equivalent(
+ ser, SLC[("e", 0) : ("c", 2) : -1], SLC[16:9:-1]
+ )
+
+ def test_multiindex_slice_first_level(self):
+ # GH 12697
+ freq = ["a", "b", "c", "d"]
+ idx = MultiIndex.from_product([freq, range(500)])
+ df = DataFrame(list(range(2000)), index=idx, columns=["Test"])
+ df_slice = df.loc[pd.IndexSlice[:, 30:70], :]
+ result = df_slice.loc["a"]
+ expected = DataFrame(list(range(30, 71)), columns=["Test"], index=range(30, 71))
+ tm.assert_frame_equal(result, expected)
+ result = df_slice.loc["d"]
+ expected = DataFrame(
+ list(range(1530, 1571)), columns=["Test"], index=range(30, 71)
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_int_series_slicing(self, multiindex_year_month_day_dataframe_random_data):
+ ymd = multiindex_year_month_day_dataframe_random_data
+ s = ymd["A"]
+ result = s[5:]
+ expected = s.reindex(s.index[5:])
+ tm.assert_series_equal(result, expected)
+
+ s = ymd["A"].copy()
+ exp = ymd["A"].copy()
+ s[5:] = 0
+ exp.iloc[5:] = 0
+ tm.assert_numpy_array_equal(s.values, exp.values)
+
+ result = ymd[5:]
+ expected = ymd.reindex(s.index[5:])
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype, loc, iloc",
+ [
+ # dtype = int, step = -1
+ ("int", slice(None, None, -1), slice(None, None, -1)),
+ ("int", slice(3, None, -1), slice(3, None, -1)),
+ ("int", slice(None, 1, -1), slice(None, 0, -1)),
+ ("int", slice(3, 1, -1), slice(3, 0, -1)),
+ # dtype = int, step = -2
+ ("int", slice(None, None, -2), slice(None, None, -2)),
+ ("int", slice(3, None, -2), slice(3, None, -2)),
+ ("int", slice(None, 1, -2), slice(None, 0, -2)),
+ ("int", slice(3, 1, -2), slice(3, 0, -2)),
+ # dtype = str, step = -1
+ ("str", slice(None, None, -1), slice(None, None, -1)),
+ ("str", slice("d", None, -1), slice(3, None, -1)),
+ ("str", slice(None, "b", -1), slice(None, 0, -1)),
+ ("str", slice("d", "b", -1), slice(3, 0, -1)),
+ # dtype = str, step = -2
+ ("str", slice(None, None, -2), slice(None, None, -2)),
+ ("str", slice("d", None, -2), slice(3, None, -2)),
+ ("str", slice(None, "b", -2), slice(None, 0, -2)),
+ ("str", slice("d", "b", -2), slice(3, 0, -2)),
+ ],
+ )
+ def test_loc_slice_negative_stepsize(self, dtype, loc, iloc):
+ # GH#38071
+ labels = {
+ "str": list("abcde"),
+ "int": range(5),
+ }[dtype]
+
+ mi = MultiIndex.from_arrays([labels] * 2)
+ df = DataFrame(1.0, index=mi, columns=["A"])
+
+ SLC = pd.IndexSlice
+
+ expected = df.iloc[iloc, :]
+ result_get_loc = df.loc[SLC[loc], :]
+ result_get_locs_level_0 = df.loc[SLC[loc, :], :]
+ result_get_locs_level_1 = df.loc[SLC[:, loc], :]
+
+ tm.assert_frame_equal(result_get_loc, expected)
+ tm.assert_frame_equal(result_get_locs_level_0, expected)
+ tm.assert_frame_equal(result_get_locs_level_1, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_sorted.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_sorted.py
new file mode 100644
index 0000000000000000000000000000000000000000..53a2e1ce05d2a1c48f6df009b1d6ad7187d0ae7d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/multiindex/test_sorted.py
@@ -0,0 +1,153 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ NA,
+ DataFrame,
+ MultiIndex,
+ Series,
+ array,
+)
+import pandas._testing as tm
+
+
+class TestMultiIndexSorted:
+ def test_getitem_multilevel_index_tuple_not_sorted(self):
+ index_columns = list("abc")
+ df = DataFrame(
+ [[0, 1, 0, "x"], [0, 0, 1, "y"]], columns=[*index_columns, "data"]
+ )
+ df = df.set_index(index_columns)
+ query_index = df.index[:1]
+ rs = df.loc[query_index, "data"]
+
+ xp_idx = MultiIndex.from_tuples([(0, 1, 0)], names=["a", "b", "c"])
+ xp = Series(["x"], index=xp_idx, name="data")
+ tm.assert_series_equal(rs, xp)
+
+ def test_getitem_slice_not_sorted(self, multiindex_dataframe_random_data):
+ frame = multiindex_dataframe_random_data
+ df = frame.sort_index(level=1).T
+
+ # buglet with int typechecking
+ result = df.iloc[:, : np.int32(3)]
+ expected = df.reindex(columns=df.columns[:3])
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("key", [None, lambda x: x])
+ def test_frame_getitem_not_sorted2(self, key):
+ # 13431
+ df = DataFrame(
+ {
+ "col1": ["b", "d", "b", "a"],
+ "col2": [3, 1, 1, 2],
+ "data": ["one", "two", "three", "four"],
+ }
+ )
+
+ df2 = df.set_index(["col1", "col2"])
+ df2_original = df2.copy()
+
+ df2.index = df2.index.set_levels(["b", "d", "a"], level="col1")
+ df2.index = df2.index.set_codes([0, 1, 0, 2], level="col1")
+ assert not df2.index.is_monotonic_increasing
+
+ assert df2_original.index.equals(df2.index)
+ expected = df2.sort_index(key=key)
+ assert expected.index.is_monotonic_increasing
+
+ result = df2.sort_index(level=0, key=key)
+ assert result.index.is_monotonic_increasing
+ tm.assert_frame_equal(result, expected)
+
+ def test_sort_values_key(self):
+ arrays = [
+ ["bar", "bar", "baz", "baz", "qux", "qux", "foo", "foo"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ tuples = zip(*arrays)
+ index = MultiIndex.from_tuples(tuples)
+ index = index.sort_values( # sort by third letter
+ key=lambda x: x.map(lambda entry: entry[2])
+ )
+ result = DataFrame(range(8), index=index)
+
+ arrays = [
+ ["foo", "foo", "bar", "bar", "qux", "qux", "baz", "baz"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ tuples = zip(*arrays)
+ index = MultiIndex.from_tuples(tuples)
+ expected = DataFrame(range(8), index=index)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_argsort_with_na(self):
+ # GH48495
+ arrays = [
+ array([2, NA, 1], dtype="Int64"),
+ array([1, 2, 3], dtype="Int64"),
+ ]
+ index = MultiIndex.from_arrays(arrays)
+ result = index.argsort()
+ expected = np.array([2, 0, 1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_sort_values_with_na(self):
+ # GH48495
+ arrays = [
+ array([2, NA, 1], dtype="Int64"),
+ array([1, 2, 3], dtype="Int64"),
+ ]
+ index = MultiIndex.from_arrays(arrays)
+ index = index.sort_values()
+ result = DataFrame(range(3), index=index)
+
+ arrays = [
+ array([1, 2, NA], dtype="Int64"),
+ array([3, 1, 2], dtype="Int64"),
+ ]
+ index = MultiIndex.from_arrays(arrays)
+ expected = DataFrame(range(3), index=index)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_frame_getitem_not_sorted(self, multiindex_dataframe_random_data):
+ frame = multiindex_dataframe_random_data
+ df = frame.T
+ df["foo", "four"] = "foo"
+
+ arrays = [np.array(x) for x in zip(*df.columns.values)]
+
+ result = df["foo"]
+ result2 = df.loc[:, "foo"]
+ expected = df.reindex(columns=df.columns[arrays[0] == "foo"])
+ expected.columns = expected.columns.droplevel(0)
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(result2, expected)
+
+ df = df.T
+ result = df.xs("foo")
+ result2 = df.loc["foo"]
+ expected = df.reindex(df.index[arrays[0] == "foo"])
+ expected.index = expected.index.droplevel(0)
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(result2, expected)
+
+ def test_series_getitem_not_sorted(self):
+ arrays = [
+ ["bar", "bar", "baz", "baz", "qux", "qux", "foo", "foo"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ tuples = zip(*arrays)
+ index = MultiIndex.from_tuples(tuples)
+ s = Series(np.random.default_rng(2).standard_normal(8), index=index)
+
+ arrays = [np.array(x) for x in zip(*index.values)]
+
+ result = s["qux"]
+ result2 = s.loc["qux"]
+ expected = s[arrays[0] == "qux"]
+ expected.index = expected.index.droplevel(0)
+ tm.assert_series_equal(result, expected)
+ tm.assert_series_equal(result2, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_at.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_at.py
new file mode 100644
index 0000000000000000000000000000000000000000..d24d3433326693e61ad18141339b769d4eaf0f61
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_at.py
@@ -0,0 +1,229 @@
+from datetime import (
+ datetime,
+ timezone,
+)
+
+import numpy as np
+import pytest
+
+from pandas.errors import InvalidIndexError
+
+from pandas import (
+ CategoricalDtype,
+ CategoricalIndex,
+ DataFrame,
+ DatetimeIndex,
+ MultiIndex,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+
+
+def test_at_timezone():
+ # https://github.com/pandas-dev/pandas/issues/33544
+ result = DataFrame({"foo": [datetime(2000, 1, 1)]})
+ with pytest.raises(TypeError, match="Invalid value"):
+ result.at[0, "foo"] = datetime(2000, 1, 2, tzinfo=timezone.utc)
+
+
+def test_selection_methods_of_assigned_col():
+ # GH 29282
+ df = DataFrame(data={"a": [1, 2, 3], "b": [4, 5, 6]})
+ df2 = DataFrame(data={"c": [7, 8, 9]}, index=[2, 1, 0])
+ df["c"] = df2["c"]
+ df.at[1, "c"] = 11
+ result = df
+ expected = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [9, 11, 7]})
+ tm.assert_frame_equal(result, expected)
+ result = df.at[1, "c"]
+ assert result == 11
+
+ result = df["c"]
+ expected = Series([9, 11, 7], name="c")
+ tm.assert_series_equal(result, expected)
+
+ result = df[["c"]]
+ expected = DataFrame({"c": [9, 11, 7]})
+ tm.assert_frame_equal(result, expected)
+
+
+class TestAtSetItem:
+ def test_at_setitem_mixed_index_assignment(self):
+ # GH#19860
+ ser = Series([1, 2, 3, 4, 5], index=["a", "b", "c", 1, 2])
+ ser.at["a"] = 11
+ assert ser.iat[0] == 11
+ ser.at[1] = 22
+ assert ser.iat[3] == 22
+
+ def test_at_setitem_categorical_missing(self):
+ df = DataFrame(
+ index=range(3), columns=range(3), dtype=CategoricalDtype(["foo", "bar"])
+ )
+ df.at[1, 1] = "foo"
+
+ expected = DataFrame(
+ [
+ [np.nan, np.nan, np.nan],
+ [np.nan, "foo", np.nan],
+ [np.nan, np.nan, np.nan],
+ ],
+ dtype=CategoricalDtype(["foo", "bar"]),
+ )
+
+ tm.assert_frame_equal(df, expected)
+
+ def test_at_setitem_multiindex(self):
+ df = DataFrame(
+ np.zeros((3, 2), dtype="int64"),
+ columns=MultiIndex.from_tuples([("a", 0), ("a", 1)]),
+ )
+ df.at[0, "a"] = 10
+ expected = DataFrame(
+ [[10, 10], [0, 0], [0, 0]],
+ columns=MultiIndex.from_tuples([("a", 0), ("a", 1)]),
+ )
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("row", (Timestamp("2019-01-01"), "2019-01-01"))
+ def test_at_datetime_index(self, row):
+ # Set float64 dtype to avoid upcast when setting .5
+ df = DataFrame(
+ data=[[1] * 2], index=DatetimeIndex(data=["2019-01-01", "2019-01-02"])
+ ).astype({0: "float64"})
+ expected = DataFrame(
+ data=[[0.5, 1], [1.0, 1]],
+ index=DatetimeIndex(data=["2019-01-01", "2019-01-02"]),
+ )
+
+ df.at[row, 0] = 0.5
+ tm.assert_frame_equal(df, expected)
+
+
+class TestAtSetItemWithExpansion:
+ def test_at_setitem_expansion_series_dt64tz_value(self, tz_naive_fixture):
+ # GH#25506
+ ts = (
+ Timestamp("2017-08-05 00:00:00+0100", tz=tz_naive_fixture)
+ if tz_naive_fixture is not None
+ else Timestamp("2017-08-05 00:00:00+0100")
+ )
+ result = Series(ts)
+ result.at[1] = ts
+ expected = Series([ts, ts])
+ tm.assert_series_equal(result, expected)
+
+
+class TestAtWithDuplicates:
+ def test_at_with_duplicate_axes_requires_scalar_lookup(self):
+ # GH#33041 check that falling back to loc doesn't allow non-scalar
+ # args to slip in
+
+ arr = np.random.default_rng(2).standard_normal(6).reshape(3, 2)
+ df = DataFrame(arr, columns=["A", "A"])
+
+ msg = "Invalid call for scalar access"
+ with pytest.raises(ValueError, match=msg):
+ df.at[[1, 2]]
+ with pytest.raises(ValueError, match=msg):
+ df.at[1, ["A"]]
+ with pytest.raises(ValueError, match=msg):
+ df.at[:, "A"]
+
+ with pytest.raises(ValueError, match=msg):
+ df.at[[1, 2]] = 1
+ with pytest.raises(ValueError, match=msg):
+ df.at[1, ["A"]] = 1
+ with pytest.raises(ValueError, match=msg):
+ df.at[:, "A"] = 1
+
+
+class TestAtErrors:
+ # TODO: De-duplicate/parametrize
+ # test_at_series_raises_key_error2, test_at_frame_raises_key_error2
+
+ def test_at_series_raises_key_error(self, indexer_al):
+ # GH#31724 .at should match .loc
+
+ ser = Series([1, 2, 3], index=[3, 2, 1])
+ result = indexer_al(ser)[1]
+ assert result == 3
+
+ with pytest.raises(KeyError, match="a"):
+ indexer_al(ser)["a"]
+
+ def test_at_frame_raises_key_error(self, indexer_al):
+ # GH#31724 .at should match .loc
+
+ df = DataFrame({0: [1, 2, 3]}, index=[3, 2, 1])
+
+ result = indexer_al(df)[1, 0]
+ assert result == 3
+
+ with pytest.raises(KeyError, match="a"):
+ indexer_al(df)["a", 0]
+
+ with pytest.raises(KeyError, match="a"):
+ indexer_al(df)[1, "a"]
+
+ def test_at_series_raises_key_error2(self, indexer_al):
+ # at should not fallback
+ # GH#7814
+ # GH#31724 .at should match .loc
+ ser = Series([1, 2, 3], index=list("abc"))
+ result = indexer_al(ser)["a"]
+ assert result == 1
+
+ with pytest.raises(KeyError, match="^0$"):
+ indexer_al(ser)[0]
+
+ def test_at_frame_raises_key_error2(self, indexer_al):
+ # GH#31724 .at should match .loc
+ df = DataFrame({"A": [1, 2, 3]}, index=list("abc"))
+ result = indexer_al(df)["a", "A"]
+ assert result == 1
+
+ with pytest.raises(KeyError, match="^0$"):
+ indexer_al(df)["a", 0]
+
+ def test_at_frame_multiple_columns(self):
+ # GH#48296 - at shouldn't modify multiple columns
+ df = DataFrame({"a": [1, 2], "b": [3, 4]})
+ new_row = [6, 7]
+ with pytest.raises(
+ InvalidIndexError,
+ match=f"You can only assign a scalar value not a \\{type(new_row)}",
+ ):
+ df.at[5] = new_row
+
+ def test_at_getitem_mixed_index_no_fallback(self):
+ # GH#19860
+ ser = Series([1, 2, 3, 4, 5], index=["a", "b", "c", 1, 2])
+ with pytest.raises(KeyError, match="^0$"):
+ ser.at[0]
+ with pytest.raises(KeyError, match="^4$"):
+ ser.at[4]
+
+ def test_at_categorical_integers(self):
+ # CategoricalIndex with integer categories that don't happen to match
+ # the Categorical's codes
+ ci = CategoricalIndex([3, 4])
+
+ arr = np.arange(4).reshape(2, 2)
+ frame = DataFrame(arr, index=ci)
+
+ for df in [frame, frame.T]:
+ for key in [0, 1]:
+ with pytest.raises(KeyError, match=str(key)):
+ df.at[key, key]
+
+ def test_at_applied_for_rows(self):
+ # GH#48729 .at should raise InvalidIndexError when assigning rows
+ df = DataFrame(index=["a"], columns=["col1", "col2"])
+ new_row = [123, 15]
+ with pytest.raises(
+ InvalidIndexError,
+ match=f"You can only assign a scalar value not a \\{type(new_row)}",
+ ):
+ df.at["a"] = new_row
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_categorical.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_categorical.py
new file mode 100644
index 0000000000000000000000000000000000000000..a31f463d0b17e9ca6f47279456b1f9f843b6b44e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_categorical.py
@@ -0,0 +1,595 @@
+import re
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ CategoricalDtype,
+ CategoricalIndex,
+ DataFrame,
+ Index,
+ Interval,
+ Series,
+ Timedelta,
+ Timestamp,
+ option_context,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def df():
+ return DataFrame(
+ {
+ "A": np.arange(6, dtype="int64"),
+ },
+ index=CategoricalIndex(
+ list("aabbca"), dtype=CategoricalDtype(list("cab")), name="B"
+ ),
+ )
+
+
+@pytest.fixture
+def df2():
+ return DataFrame(
+ {
+ "A": np.arange(6, dtype="int64"),
+ },
+ index=CategoricalIndex(
+ list("aabbca"), dtype=CategoricalDtype(list("cabe")), name="B"
+ ),
+ )
+
+
+class TestCategoricalIndex:
+ def test_loc_scalar(self, df):
+ dtype = CategoricalDtype(list("cab"))
+ result = df.loc["a"]
+ bidx = Series(list("aaa"), name="B").astype(dtype)
+ assert bidx.dtype == dtype
+
+ expected = DataFrame({"A": [0, 1, 5]}, index=Index(bidx))
+ tm.assert_frame_equal(result, expected)
+
+ df = df.copy()
+ df.loc["a"] = 20
+ bidx2 = Series(list("aabbca"), name="B").astype(dtype)
+ assert bidx2.dtype == dtype
+ expected = DataFrame(
+ {
+ "A": [20, 20, 2, 3, 4, 20],
+ },
+ index=Index(bidx2),
+ )
+ tm.assert_frame_equal(df, expected)
+
+ # value not in the categories
+ with pytest.raises(KeyError, match=r"^'d'$"):
+ df.loc["d"]
+
+ df2 = df.copy()
+ expected = df2.copy()
+ expected.index = expected.index.astype(object)
+ expected.loc["d"] = 10
+ df2.loc["d"] = 10
+ tm.assert_frame_equal(df2, expected)
+
+ def test_loc_setitem_with_expansion_non_category(self, df):
+ # Setting-with-expansion with a new key "d" that is not among caegories
+ df.loc["a"] = 20
+
+ # Setting a new row on an existing column
+ df3 = df.copy()
+ df3.loc["d", "A"] = 10
+ bidx3 = Index(list("aabbcad"), name="B")
+ expected3 = DataFrame(
+ {
+ "A": [20, 20, 2, 3, 4, 20, 10.0],
+ },
+ index=Index(bidx3),
+ )
+ tm.assert_frame_equal(df3, expected3)
+
+ # Setting a new row _and_ new column
+ df4 = df.copy()
+ df4.loc["d", "C"] = 10
+ expected3 = DataFrame(
+ {
+ "A": [20, 20, 2, 3, 4, 20, np.nan],
+ "C": [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 10],
+ },
+ index=Index(bidx3),
+ )
+ tm.assert_frame_equal(df4, expected3)
+
+ def test_loc_getitem_scalar_non_category(self, df):
+ with pytest.raises(KeyError, match="^1$"):
+ df.loc[1]
+
+ def test_slicing(self):
+ cat = Series(Categorical([1, 2, 3, 4]))
+ reverse = cat[::-1]
+ exp = np.array([4, 3, 2, 1], dtype=np.int64)
+ tm.assert_numpy_array_equal(reverse.__array__(), exp)
+
+ df = DataFrame({"value": (np.arange(100) + 1).astype("int64")})
+ df["D"] = pd.cut(df.value, bins=[0, 25, 50, 75, 100])
+
+ expected = Series([11, Interval(0, 25)], index=["value", "D"], name=10)
+ result = df.iloc[10]
+ tm.assert_series_equal(result, expected)
+
+ expected = DataFrame(
+ {"value": np.arange(11, 21).astype("int64")},
+ index=np.arange(10, 20).astype("int64"),
+ )
+ expected["D"] = pd.cut(expected.value, bins=[0, 25, 50, 75, 100])
+ result = df.iloc[10:20]
+ tm.assert_frame_equal(result, expected)
+
+ expected = Series([9, Interval(0, 25)], index=["value", "D"], name=8)
+ result = df.loc[8]
+ tm.assert_series_equal(result, expected)
+
+ def test_slicing_and_getting_ops(self):
+ # systematically test the slicing operations:
+ # for all slicing ops:
+ # - returning a dataframe
+ # - returning a column
+ # - returning a row
+ # - returning a single value
+
+ cats = Categorical(
+ ["a", "c", "b", "c", "c", "c", "c"], categories=["a", "b", "c"]
+ )
+ idx = Index(["h", "i", "j", "k", "l", "m", "n"])
+ values = [1, 2, 3, 4, 5, 6, 7]
+ df = DataFrame({"cats": cats, "values": values}, index=idx)
+
+ # the expected values
+ cats2 = Categorical(["b", "c"], categories=["a", "b", "c"])
+ idx2 = Index(["j", "k"])
+ values2 = [3, 4]
+
+ # 2:4,: | "j":"k",:
+ exp_df = DataFrame({"cats": cats2, "values": values2}, index=idx2)
+
+ # :,"cats" | :,0
+ exp_col = Series(cats, index=idx, name="cats")
+
+ # "j",: | 2,:
+ exp_row = Series(["b", 3], index=["cats", "values"], dtype="object", name="j")
+
+ # "j","cats | 2,0
+ exp_val = "b"
+
+ # iloc
+ # frame
+ res_df = df.iloc[2:4, :]
+ tm.assert_frame_equal(res_df, exp_df)
+ assert isinstance(res_df["cats"].dtype, CategoricalDtype)
+
+ # row
+ res_row = df.iloc[2, :]
+ tm.assert_series_equal(res_row, exp_row)
+ assert isinstance(res_row["cats"], str)
+
+ # col
+ res_col = df.iloc[:, 0]
+ tm.assert_series_equal(res_col, exp_col)
+ assert isinstance(res_col.dtype, CategoricalDtype)
+
+ # single value
+ res_val = df.iloc[2, 0]
+ assert res_val == exp_val
+
+ # loc
+ # frame
+ res_df = df.loc["j":"k", :]
+ tm.assert_frame_equal(res_df, exp_df)
+ assert isinstance(res_df["cats"].dtype, CategoricalDtype)
+
+ # row
+ res_row = df.loc["j", :]
+ tm.assert_series_equal(res_row, exp_row)
+ assert isinstance(res_row["cats"], str)
+
+ # col
+ res_col = df.loc[:, "cats"]
+ tm.assert_series_equal(res_col, exp_col)
+ assert isinstance(res_col.dtype, CategoricalDtype)
+
+ # single value
+ res_val = df.loc["j", "cats"]
+ assert res_val == exp_val
+
+ # single value
+ res_val = df.loc["j", df.columns[0]]
+ assert res_val == exp_val
+
+ # iat
+ res_val = df.iat[2, 0]
+ assert res_val == exp_val
+
+ # at
+ res_val = df.at["j", "cats"]
+ assert res_val == exp_val
+
+ # fancy indexing
+ exp_fancy = df.iloc[[2]]
+
+ res_fancy = df[df["cats"] == "b"]
+ tm.assert_frame_equal(res_fancy, exp_fancy)
+ res_fancy = df[df["values"] == 3]
+ tm.assert_frame_equal(res_fancy, exp_fancy)
+
+ # get_value
+ res_val = df.at["j", "cats"]
+ assert res_val == exp_val
+
+ # i : int, slice, or sequence of integers
+ res_row = df.iloc[2]
+ tm.assert_series_equal(res_row, exp_row)
+ assert isinstance(res_row["cats"], str)
+
+ res_df = df.iloc[slice(2, 4)]
+ tm.assert_frame_equal(res_df, exp_df)
+ assert isinstance(res_df["cats"].dtype, CategoricalDtype)
+
+ res_df = df.iloc[[2, 3]]
+ tm.assert_frame_equal(res_df, exp_df)
+ assert isinstance(res_df["cats"].dtype, CategoricalDtype)
+
+ res_col = df.iloc[:, 0]
+ tm.assert_series_equal(res_col, exp_col)
+ assert isinstance(res_col.dtype, CategoricalDtype)
+
+ res_df = df.iloc[:, slice(0, 2)]
+ tm.assert_frame_equal(res_df, df)
+ assert isinstance(res_df["cats"].dtype, CategoricalDtype)
+
+ res_df = df.iloc[:, [0, 1]]
+ tm.assert_frame_equal(res_df, df)
+ assert isinstance(res_df["cats"].dtype, CategoricalDtype)
+
+ def test_slicing_doc_examples(self):
+ # GH 7918
+ cats = Categorical(
+ ["a", "b", "b", "b", "c", "c", "c"], categories=["a", "b", "c"]
+ )
+ idx = Index(["h", "i", "j", "k", "l", "m", "n"])
+ values = [1, 2, 2, 2, 3, 4, 5]
+ df = DataFrame({"cats": cats, "values": values}, index=idx)
+
+ result = df.iloc[2:4, :]
+ expected = DataFrame(
+ {
+ "cats": Categorical(["b", "b"], categories=["a", "b", "c"]),
+ "values": [2, 2],
+ },
+ index=["j", "k"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[2:4, :].dtypes
+ expected = Series(["category", "int64"], ["cats", "values"], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ result = df.loc["h":"j", "cats"]
+ expected = Series(
+ Categorical(["a", "b", "b"], categories=["a", "b", "c"]),
+ index=["h", "i", "j"],
+ name="cats",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = df.loc["h":"j", df.columns[0:1]]
+ expected = DataFrame(
+ {"cats": Categorical(["a", "b", "b"], categories=["a", "b", "c"])},
+ index=["h", "i", "j"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_getitem_listlike_labels(self, df):
+ # list of labels
+ result = df.loc[["c", "a"]]
+ expected = df.iloc[[4, 0, 1, 5]]
+ tm.assert_frame_equal(result, expected, check_index_type=True)
+
+ def test_loc_getitem_listlike_unused_category(self, df2):
+ # GH#37901 a label that is in index.categories but not in index
+ # listlike containing an element in the categories but not in the values
+ with pytest.raises(KeyError, match=re.escape("['e'] not in index")):
+ df2.loc[["a", "b", "e"]]
+
+ def test_loc_getitem_label_unused_category(self, df2):
+ # element in the categories but not in the values
+ with pytest.raises(KeyError, match=r"^'e'$"):
+ df2.loc["e"]
+
+ def test_loc_getitem_non_category(self, df2):
+ # not all labels in the categories
+ with pytest.raises(KeyError, match=re.escape("['d'] not in index")):
+ df2.loc[["a", "d"]]
+
+ def test_loc_setitem_expansion_label_unused_category(self, df2):
+ # assigning with a label that is in the categories but not in the index
+ df = df2.copy()
+ df.loc["e"] = 20
+ result = df.loc[["a", "b", "e"]]
+ exp_index = CategoricalIndex(list("aaabbe"), categories=list("cabe"), name="B")
+ expected = DataFrame({"A": [0, 1, 5, 2, 3, 20]}, index=exp_index)
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_listlike_dtypes(self):
+ # GH 11586
+
+ # unique categories and codes
+ index = CategoricalIndex(["a", "b", "c"])
+ df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}, index=index)
+
+ # unique slice
+ res = df.loc[["a", "b"]]
+ exp_index = CategoricalIndex(["a", "b"], categories=index.categories)
+ exp = DataFrame({"A": [1, 2], "B": [4, 5]}, index=exp_index)
+ tm.assert_frame_equal(res, exp, check_index_type=True)
+
+ # duplicated slice
+ res = df.loc[["a", "a", "b"]]
+
+ exp_index = CategoricalIndex(["a", "a", "b"], categories=index.categories)
+ exp = DataFrame({"A": [1, 1, 2], "B": [4, 4, 5]}, index=exp_index)
+ tm.assert_frame_equal(res, exp, check_index_type=True)
+
+ with pytest.raises(KeyError, match=re.escape("['x'] not in index")):
+ df.loc[["a", "x"]]
+
+ def test_loc_listlike_dtypes_duplicated_categories_and_codes(self):
+ # duplicated categories and codes
+ index = CategoricalIndex(["a", "b", "a"])
+ df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}, index=index)
+
+ # unique slice
+ res = df.loc[["a", "b"]]
+ exp = DataFrame(
+ {"A": [1, 3, 2], "B": [4, 6, 5]}, index=CategoricalIndex(["a", "a", "b"])
+ )
+ tm.assert_frame_equal(res, exp, check_index_type=True)
+
+ # duplicated slice
+ res = df.loc[["a", "a", "b"]]
+ exp = DataFrame(
+ {"A": [1, 3, 1, 3, 2], "B": [4, 6, 4, 6, 5]},
+ index=CategoricalIndex(["a", "a", "a", "a", "b"]),
+ )
+ tm.assert_frame_equal(res, exp, check_index_type=True)
+
+ with pytest.raises(KeyError, match=re.escape("['x'] not in index")):
+ df.loc[["a", "x"]]
+
+ def test_loc_listlike_dtypes_unused_category(self):
+ # contains unused category
+ index = CategoricalIndex(["a", "b", "a", "c"], categories=list("abcde"))
+ df = DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8]}, index=index)
+
+ res = df.loc[["a", "b"]]
+ exp = DataFrame(
+ {"A": [1, 3, 2], "B": [5, 7, 6]},
+ index=CategoricalIndex(["a", "a", "b"], categories=list("abcde")),
+ )
+ tm.assert_frame_equal(res, exp, check_index_type=True)
+
+ # duplicated slice
+ res = df.loc[["a", "a", "b"]]
+ exp = DataFrame(
+ {"A": [1, 3, 1, 3, 2], "B": [5, 7, 5, 7, 6]},
+ index=CategoricalIndex(["a", "a", "a", "a", "b"], categories=list("abcde")),
+ )
+ tm.assert_frame_equal(res, exp, check_index_type=True)
+
+ with pytest.raises(KeyError, match=re.escape("['x'] not in index")):
+ df.loc[["a", "x"]]
+
+ def test_loc_getitem_listlike_unused_category_raises_keyerror(self):
+ # key that is an *unused* category raises
+ index = CategoricalIndex(["a", "b", "a", "c"], categories=list("abcde"))
+ df = DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8]}, index=index)
+
+ with pytest.raises(KeyError, match="e"):
+ # For comparison, check the scalar behavior
+ df.loc["e"]
+
+ with pytest.raises(KeyError, match=re.escape("['e'] not in index")):
+ df.loc[["a", "e"]]
+
+ def test_ix_categorical_index(self):
+ # GH 12531
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ index=list("ABC"),
+ columns=list("XYZ"),
+ )
+ cdf = df.copy()
+ cdf.index = CategoricalIndex(df.index)
+ cdf.columns = CategoricalIndex(df.columns)
+
+ expect = Series(df.loc["A", :], index=cdf.columns, name="A")
+ tm.assert_series_equal(cdf.loc["A", :], expect)
+
+ expect = Series(df.loc[:, "X"], index=cdf.index, name="X")
+ tm.assert_series_equal(cdf.loc[:, "X"], expect)
+
+ exp_index = CategoricalIndex(list("AB"), categories=["A", "B", "C"])
+ expect = DataFrame(df.loc[["A", "B"], :], columns=cdf.columns, index=exp_index)
+ tm.assert_frame_equal(cdf.loc[["A", "B"], :], expect)
+
+ exp_columns = CategoricalIndex(list("XY"), categories=["X", "Y", "Z"])
+ expect = DataFrame(df.loc[:, ["X", "Y"]], index=cdf.index, columns=exp_columns)
+ tm.assert_frame_equal(cdf.loc[:, ["X", "Y"]], expect)
+
+ @pytest.mark.parametrize(
+ "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))]
+ )
+ def test_ix_categorical_index_non_unique(self, infer_string):
+ # non-unique
+ with option_context("future.infer_string", infer_string):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ index=list("ABA"),
+ columns=list("XYX"),
+ )
+ cdf = df.copy()
+ cdf.index = CategoricalIndex(df.index)
+ cdf.columns = CategoricalIndex(df.columns)
+
+ exp_index = CategoricalIndex(list("AA"), categories=["A", "B"])
+ expect = DataFrame(df.loc["A", :], columns=cdf.columns, index=exp_index)
+ tm.assert_frame_equal(cdf.loc["A", :], expect)
+
+ exp_columns = CategoricalIndex(list("XX"), categories=["X", "Y"])
+ expect = DataFrame(df.loc[:, "X"], index=cdf.index, columns=exp_columns)
+ tm.assert_frame_equal(cdf.loc[:, "X"], expect)
+
+ expect = DataFrame(
+ df.loc[["A", "B"], :],
+ columns=cdf.columns,
+ index=CategoricalIndex(list("AAB")),
+ )
+ tm.assert_frame_equal(cdf.loc[["A", "B"], :], expect)
+
+ expect = DataFrame(
+ df.loc[:, ["X", "Y"]],
+ index=cdf.index,
+ columns=CategoricalIndex(list("XXY")),
+ )
+ tm.assert_frame_equal(cdf.loc[:, ["X", "Y"]], expect)
+
+ def test_loc_slice(self, df):
+ # GH9748
+ msg = (
+ "cannot do slice indexing on CategoricalIndex with these "
+ r"indexers \[1\] of type int"
+ )
+ with pytest.raises(TypeError, match=msg):
+ df.loc[1:5]
+
+ result = df.loc["b":"c"]
+ expected = df.iloc[[2, 3, 4]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_and_at_with_categorical_index(self):
+ # GH 20629
+ df = DataFrame(
+ [[1, 2], [3, 4], [5, 6]], index=CategoricalIndex(["A", "B", "C"])
+ )
+
+ s = df[0]
+ assert s.loc["A"] == 1
+ assert s.at["A"] == 1
+
+ assert df.loc["B", 1] == 4
+ assert df.at["B", 1] == 4
+
+ @pytest.mark.parametrize(
+ "idx_values",
+ [
+ # python types
+ [1, 2, 3],
+ [-1, -2, -3],
+ [1.5, 2.5, 3.5],
+ [-1.5, -2.5, -3.5],
+ # numpy int/uint
+ *(np.array([1, 2, 3], dtype=dtype) for dtype in tm.ALL_INT_NUMPY_DTYPES),
+ # numpy floats
+ *(np.array([1.5, 2.5, 3.5], dtype=dtyp) for dtyp in tm.FLOAT_NUMPY_DTYPES),
+ # numpy object
+ np.array([1, "b", 3.5], dtype=object),
+ # pandas scalars
+ [Interval(1, 4), Interval(4, 6), Interval(6, 9)],
+ [Timestamp(2019, 1, 1), Timestamp(2019, 2, 1), Timestamp(2019, 3, 1)],
+ [Timedelta(1, "D"), Timedelta(2, "D"), Timedelta(3, "D")],
+ # pandas Integer arrays
+ *(pd.array([1, 2, 3], dtype=dtype) for dtype in tm.ALL_INT_EA_DTYPES),
+ # other pandas arrays
+ pd.IntervalIndex.from_breaks([1, 4, 6, 9]).array,
+ pd.date_range("2019-01-01", periods=3).array,
+ pd.timedelta_range(start="1D", periods=3).array,
+ ],
+ )
+ def test_loc_getitem_with_non_string_categories(self, idx_values, ordered):
+ # GH-17569
+ cat_idx = CategoricalIndex(idx_values, ordered=ordered)
+ df = DataFrame({"A": ["foo", "bar", "baz"]}, index=cat_idx)
+ sl = slice(idx_values[0], idx_values[1])
+
+ # scalar selection
+ result = df.loc[idx_values[0]]
+ expected = Series(["foo"], index=["A"], name=idx_values[0])
+ tm.assert_series_equal(result, expected)
+
+ # list selection
+ result = df.loc[idx_values[:2]]
+ expected = DataFrame(["foo", "bar"], index=cat_idx[:2], columns=["A"])
+ tm.assert_frame_equal(result, expected)
+
+ # slice selection
+ result = df.loc[sl]
+ expected = DataFrame(["foo", "bar"], index=cat_idx[:2], columns=["A"])
+ tm.assert_frame_equal(result, expected)
+
+ # scalar assignment
+ result = df.copy()
+ result.loc[idx_values[0]] = "qux"
+ expected = DataFrame({"A": ["qux", "bar", "baz"]}, index=cat_idx)
+ tm.assert_frame_equal(result, expected)
+
+ # list assignment
+ result = df.copy()
+ result.loc[idx_values[:2], "A"] = ["qux", "qux2"]
+ expected = DataFrame({"A": ["qux", "qux2", "baz"]}, index=cat_idx)
+ tm.assert_frame_equal(result, expected)
+
+ # slice assignment
+ result = df.copy()
+ result.loc[sl, "A"] = ["qux", "qux2"]
+ expected = DataFrame({"A": ["qux", "qux2", "baz"]}, index=cat_idx)
+ tm.assert_frame_equal(result, expected)
+
+ def test_getitem_categorical_with_nan(self):
+ # GH#41933
+ ci = CategoricalIndex(["A", "B", np.nan])
+
+ ser = Series(range(3), index=ci)
+
+ assert ser[np.nan] == 2
+ assert ser.loc[np.nan] == 2
+
+ df = DataFrame(ser)
+ assert df.loc[np.nan, 0] == 2
+ assert df.loc[np.nan][0] == 2
+
+ def test_getitem_row_categorical_with_nan(self):
+ # GH#58954
+ df = DataFrame({"a": [1, 2], "b": CategoricalIndex([1, None])})
+
+ res = df.iloc[1]
+ expected = Series([2, np.nan], index=df.columns, name=1)
+ tm.assert_series_equal(res, expected)
+
+ res = df.loc[1]
+ tm.assert_series_equal(res, expected)
+
+ def test_getitem_row_categorical_with_nan_bool(self):
+ # GH#58954
+ df = DataFrame({"a": [True, False], "b": CategoricalIndex([False, None])})
+
+ res = df.iloc[1]
+ expected = Series([False, np.nan], index=df.columns, dtype=object, name=1)
+ tm.assert_series_equal(res, expected)
+
+ res = df.loc[1]
+ tm.assert_series_equal(res, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_chaining_and_caching.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_chaining_and_caching.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1f8bf8ae9bf8dd4fba6b10abebca88ea8792b82
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_chaining_and_caching.py
@@ -0,0 +1,361 @@
+from string import ascii_letters
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ Timestamp,
+ date_range,
+ option_context,
+)
+import pandas._testing as tm
+
+
+class TestCaching:
+ @pytest.mark.parametrize("do_ref", [True, False])
+ def test_setitem_cache_updating(self, do_ref):
+ # GH 5424
+ cont = ["one", "two", "three", "four", "five", "six", "seven"]
+
+ df = DataFrame({"a": cont, "b": cont[3:] + cont[:3], "c": np.arange(7)})
+
+ # ref the cache
+ if do_ref:
+ df.loc[0, "c"]
+
+ # set it
+ df.loc[7, "c"] = 1
+
+ assert df.loc[0, "c"] == 0.0
+ assert df.loc[7, "c"] == 1.0
+
+ def test_setitem_cache_updating_slices(self):
+ # GH 7084
+ # not updating cache on series setting with slices
+ expected = DataFrame(
+ {"A": [600, 600, 600]}, index=date_range("5/7/2014", "5/9/2014")
+ )
+ out = DataFrame({"A": [0, 0, 0]}, index=date_range("5/7/2014", "5/9/2014"))
+ df = DataFrame({"C": ["A", "A", "A"], "D": [100, 200, 300]})
+
+ # loop through df to update out
+ six = Timestamp("5/7/2014")
+ eix = Timestamp("5/9/2014")
+ for ix, row in df.iterrows():
+ out.loc[six:eix, row["C"]] = out.loc[six:eix, row["C"]] + row["D"]
+
+ tm.assert_frame_equal(out, expected)
+ tm.assert_series_equal(out["A"], expected["A"])
+
+ # try via a chain indexing
+ # this actually works
+ out = DataFrame({"A": [0, 0, 0]}, index=date_range("5/7/2014", "5/9/2014"))
+ out_original = out.copy()
+ for ix, row in df.iterrows():
+ v = out[row["C"]][six:eix] + row["D"]
+ with tm.raises_chained_assignment_error():
+ out[row["C"]][six:eix] = v
+
+ tm.assert_frame_equal(out, out_original)
+ tm.assert_series_equal(out["A"], out_original["A"])
+
+ out = DataFrame({"A": [0, 0, 0]}, index=date_range("5/7/2014", "5/9/2014"))
+ for ix, row in df.iterrows():
+ out.loc[six:eix, row["C"]] += row["D"]
+
+ tm.assert_frame_equal(out, expected)
+ tm.assert_series_equal(out["A"], expected["A"])
+
+
+class TestChaining:
+ def test_setitem_chained_setfault(self):
+ # GH6026
+ data = ["right", "left", "left", "left", "right", "left", "timeout"]
+
+ df = DataFrame({"response": np.array(data)})
+ mask = df.response == "timeout"
+ with tm.raises_chained_assignment_error():
+ df.response[mask] = "none"
+ tm.assert_frame_equal(df, DataFrame({"response": data}))
+
+ recarray = np.rec.fromarrays([data], names=["response"])
+ df = DataFrame(recarray)
+ mask = df.response == "timeout"
+ with tm.raises_chained_assignment_error():
+ df.response[mask] = "none"
+ tm.assert_frame_equal(df, DataFrame({"response": data}))
+
+ df = DataFrame({"response": data, "response1": data})
+ df_original = df.copy()
+ mask = df.response == "timeout"
+ with tm.raises_chained_assignment_error():
+ df.response[mask] = "none"
+ tm.assert_frame_equal(df, df_original)
+
+ # GH 6056
+ expected = DataFrame({"A": [np.nan, "bar", "bah", "foo", "bar"]})
+ df = DataFrame({"A": np.array(["foo", "bar", "bah", "foo", "bar"])})
+ with tm.raises_chained_assignment_error():
+ df["A"].iloc[0] = np.nan
+ expected = DataFrame({"A": ["foo", "bar", "bah", "foo", "bar"]})
+ result = df.head()
+ tm.assert_frame_equal(result, expected)
+
+ df = DataFrame({"A": np.array(["foo", "bar", "bah", "foo", "bar"])})
+ with tm.raises_chained_assignment_error():
+ df.A.iloc[0] = np.nan
+ result = df.head()
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment(self):
+ with option_context("chained_assignment", "raise"):
+ # work with the chain
+ df = DataFrame(
+ np.arange(4).reshape(2, 2), columns=list("AB"), dtype="int64"
+ )
+ df_original = df.copy()
+
+ with tm.raises_chained_assignment_error():
+ df["A"][0] = -5
+ with tm.raises_chained_assignment_error():
+ df["A"][1] = -6
+ tm.assert_frame_equal(df, df_original)
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_raises(self):
+ # test with the chaining
+ df = DataFrame(
+ {
+ "A": Series(range(2), dtype="int64"),
+ "B": np.array(np.arange(2, 4), dtype=np.float64),
+ }
+ )
+ df_original = df.copy()
+ with tm.raises_chained_assignment_error():
+ df["A"][0] = -5
+ with tm.raises_chained_assignment_error():
+ df["A"][1] = -6
+ tm.assert_frame_equal(df, df_original)
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_fails(self):
+ # Using a copy (the chain), fails
+ df = DataFrame(
+ {
+ "A": Series(range(2), dtype="int64"),
+ "B": np.array(np.arange(2, 4), dtype=np.float64),
+ }
+ )
+
+ with tm.raises_chained_assignment_error():
+ df.loc[0]["A"] = -5
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_doc_example(self):
+ # Doc example
+ df = DataFrame(
+ {
+ "a": ["one", "one", "two", "three", "two", "one", "six"],
+ "c": Series(range(7), dtype="int64"),
+ }
+ )
+
+ indexer = df.a.str.startswith("o")
+ with tm.raises_chained_assignment_error():
+ df[indexer]["c"] = 42
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_object_dtype(self):
+ df = DataFrame(
+ {"A": Series(["aaa", "bbb", "ccc"], dtype=object), "B": [1, 2, 3]}
+ )
+ df_original = df.copy()
+
+ with tm.raises_chained_assignment_error():
+ df["A"][0] = 111
+ tm.assert_frame_equal(df, df_original)
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_is_copy_pickle(self, temp_file):
+ # gh-5475: Make sure that is_copy is picked up reconstruction
+ df = DataFrame({"A": [1, 2]})
+
+ path = str(temp_file)
+ df.to_pickle(path)
+ df2 = pd.read_pickle(path)
+ df2["B"] = df2["A"]
+ df2["B"] = df2["A"]
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_str(self):
+ idxs = np.random.default_rng(2).integers(len(ascii_letters), size=(100, 2))
+ idxs.sort(axis=1)
+ strings = [ascii_letters[x[0] : x[1]] for x in idxs]
+
+ df = DataFrame(strings, columns=["letters"])
+ indexer = df.letters.apply(lambda x: len(x) > 10)
+ df.loc[indexer, "letters"] = df.loc[indexer, "letters"].apply(str.lower)
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_sorting(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ ser = df.iloc[:, 0].sort_values()
+
+ tm.assert_series_equal(ser, df.iloc[:, 0].sort_values())
+ tm.assert_series_equal(ser, df[0].sort_values())
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_false_positives(self):
+ # see gh-6025: false positives
+ df = DataFrame({"column1": ["a", "a", "a"], "column2": [4, 8, 9]})
+ str(df)
+
+ df["column1"] = df["column1"] + "b"
+ str(df)
+
+ df = df[df["column2"] != 8]
+ str(df)
+
+ df["column1"] = df["column1"] + "c"
+ str(df)
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_undefined_column(self):
+ # from SO:
+ # https://stackoverflow.com/questions/24054495/potential-bug-setting-value-for-undefined-column-using-iloc
+ df = DataFrame(np.arange(0, 9), columns=["count"])
+ df["group"] = "b"
+ df_original = df.copy()
+ with tm.raises_chained_assignment_error():
+ df.iloc[0:5]["group"] = "a"
+ tm.assert_frame_equal(df, df_original)
+
+ @pytest.mark.arm_slow
+ def test_detect_chained_assignment_changing_dtype(self):
+ # Mixed type setting but same dtype & changing dtype
+ df = DataFrame(
+ {
+ "A": date_range("20130101", periods=5),
+ "B": np.random.default_rng(2).standard_normal(5),
+ "C": np.arange(5, dtype="int64"),
+ "D": ["a", "b", "c", "d", "e"],
+ }
+ )
+ df_original = df.copy()
+
+ with tm.raises_chained_assignment_error():
+ df.loc[2]["D"] = "foo"
+ with tm.raises_chained_assignment_error():
+ df.loc[2]["C"] = "foo"
+ tm.assert_frame_equal(df, df_original)
+ # TODO: Use tm.raises_chained_assignment_error() when PDEP-6 is enforced
+ with pytest.raises(TypeError, match="Invalid value"):
+ with tm.raises_chained_assignment_error():
+ df["C"][2] = "foo"
+
+ def test_setting_with_copy_bug(self):
+ # operating on a copy
+ df = DataFrame(
+ {"a": list(range(4)), "b": list("ab.."), "c": ["a", "b", np.nan, "d"]}
+ )
+ df_original = df.copy()
+ mask = pd.isna(df.c)
+ with tm.raises_chained_assignment_error():
+ df[["c"]][mask] = df[["b"]][mask]
+ tm.assert_frame_equal(df, df_original)
+
+ def test_setting_with_copy_bug_no_warning(self):
+ # invalid warning as we are returning a new object
+ # GH 8730
+ df1 = DataFrame({"x": Series(["a", "b", "c"]), "y": Series(["d", "e", "f"])})
+ df2 = df1[["x"]]
+
+ # this should not raise
+ df2["y"] = ["g", "h", "i"]
+
+ def test_detect_chained_assignment_warnings_errors(self):
+ df = DataFrame({"A": ["aaa", "bbb", "ccc"], "B": [1, 2, 3]})
+ with tm.raises_chained_assignment_error():
+ df.loc[0]["A"] = 111
+
+ @pytest.mark.parametrize("rhs", [3, DataFrame({0: [1, 2, 3, 4]})])
+ def test_detect_chained_assignment_warning_stacklevel(self, rhs):
+ # GH#42570
+ df = DataFrame(np.arange(25).reshape(5, 5))
+ df_original = df.copy()
+ chained = df.loc[:3]
+ chained[2] = rhs
+ tm.assert_frame_equal(df, df_original)
+
+ def test_chained_getitem_with_lists(self):
+ # GH6394
+ # Regression in chained getitem indexing with embedded list-like from
+ # 0.12
+
+ df = DataFrame({"A": 5 * [np.zeros(3)], "B": 5 * [np.ones(3)]})
+ expected = df["A"].iloc[2]
+ result = df.loc[2, "A"]
+ tm.assert_numpy_array_equal(result, expected)
+ result2 = df.iloc[2]["A"]
+ tm.assert_numpy_array_equal(result2, expected)
+ result3 = df["A"].loc[2]
+ tm.assert_numpy_array_equal(result3, expected)
+ result4 = df["A"].iloc[2]
+ tm.assert_numpy_array_equal(result4, expected)
+
+ def test_cache_updating(self):
+ # GH 4939, make sure to update the cache on setitem
+
+ df = DataFrame(
+ np.zeros((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ )
+ df["A"] # cache series
+ df.loc["Hello Friend"] = df.iloc[0]
+ assert "Hello Friend" in df["A"].index
+ assert "Hello Friend" in df["B"].index
+
+ def test_cache_updating2(self):
+ # 10264
+ df = DataFrame(
+ np.zeros((5, 5), dtype="int64"),
+ columns=["a", "b", "c", "d", "e"],
+ index=range(5),
+ )
+ df["f"] = 0
+ df_orig = df.copy()
+ with pytest.raises(ValueError, match="read-only"):
+ df.f.values[3] = 1
+ tm.assert_frame_equal(df, df_orig)
+
+ def test_iloc_setitem_chained_assignment(self):
+ # GH#3970
+ with option_context("chained_assignment", None):
+ df = DataFrame({"aa": range(5), "bb": [2.2] * 5})
+ df["cc"] = 0.0
+
+ ck = [True] * len(df)
+
+ with tm.raises_chained_assignment_error():
+ df["bb"].iloc[0] = 0.13
+
+ # GH#3970 this lookup used to break the chained setting to 0.15
+ df.iloc[ck]
+
+ with tm.raises_chained_assignment_error():
+ df["bb"].iloc[0] = 0.15
+
+ assert df["bb"].iloc[0] == 2.2
+
+ def test_getitem_loc_assignment_slice_state(self):
+ # GH 13569
+ df = DataFrame({"a": [10, 20, 30]})
+ with tm.raises_chained_assignment_error():
+ df["a"].loc[4] = 40
+ tm.assert_frame_equal(df, DataFrame({"a": [10, 20, 30]}))
+ tm.assert_series_equal(df["a"], Series([10, 20, 30], name="a"))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_check_indexer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_check_indexer.py
new file mode 100644
index 0000000000000000000000000000000000000000..975a31b873792c6afe59a23e5fef43b56ce7e46e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_check_indexer.py
@@ -0,0 +1,105 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.indexers import check_array_indexer
+
+
+@pytest.mark.parametrize(
+ "indexer, expected",
+ [
+ # integer
+ ([1, 2], np.array([1, 2], dtype=np.intp)),
+ (np.array([1, 2], dtype="int64"), np.array([1, 2], dtype=np.intp)),
+ (pd.array([1, 2], dtype="Int32"), np.array([1, 2], dtype=np.intp)),
+ (pd.Index([1, 2]), np.array([1, 2], dtype=np.intp)),
+ # boolean
+ ([True, False, True], np.array([True, False, True], dtype=np.bool_)),
+ (np.array([True, False, True]), np.array([True, False, True], dtype=np.bool_)),
+ (
+ pd.array([True, False, True], dtype="boolean"),
+ np.array([True, False, True], dtype=np.bool_),
+ ),
+ # other
+ ([], np.array([], dtype=np.intp)),
+ ],
+)
+def test_valid_input(indexer, expected):
+ arr = np.array([1, 2, 3])
+ result = check_array_indexer(arr, indexer)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "indexer", [[True, False, None], pd.array([True, False, None], dtype="boolean")]
+)
+def test_boolean_na_returns_indexer(indexer):
+ # https://github.com/pandas-dev/pandas/issues/31503
+ arr = np.array([1, 2, 3])
+
+ result = check_array_indexer(arr, indexer)
+ expected = np.array([True, False, False], dtype=bool)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "indexer",
+ [
+ [True, False],
+ pd.array([True, False], dtype="boolean"),
+ np.array([True, False], dtype=np.bool_),
+ ],
+)
+def test_bool_raise_length(indexer):
+ arr = np.array([1, 2, 3])
+
+ msg = "Boolean index has wrong length"
+ with pytest.raises(IndexError, match=msg):
+ check_array_indexer(arr, indexer)
+
+
+@pytest.mark.parametrize(
+ "indexer", [[0, 1, None], pd.array([0, 1, pd.NA], dtype="Int64")]
+)
+def test_int_raise_missing_values(indexer):
+ arr = np.array([1, 2, 3])
+
+ msg = "Cannot index with an integer indexer containing NA values"
+ with pytest.raises(ValueError, match=msg):
+ check_array_indexer(arr, indexer)
+
+
+@pytest.mark.parametrize(
+ "indexer",
+ [
+ [0.0, 1.0],
+ np.array([1.0, 2.0], dtype="float64"),
+ np.array([True, False], dtype=object),
+ pd.Index([True, False], dtype=object),
+ ],
+)
+def test_raise_invalid_array_dtypes(indexer):
+ arr = np.array([1, 2, 3])
+
+ msg = "arrays used as indices must be of integer or boolean type"
+ with pytest.raises(IndexError, match=msg):
+ check_array_indexer(arr, indexer)
+
+
+def test_raise_nullable_string_dtype(nullable_string_dtype):
+ indexer = pd.array(["a", "b"], dtype=nullable_string_dtype)
+ arr = np.array([1, 2, 3])
+
+ msg = "arrays used as indices must be of integer or boolean type"
+ with pytest.raises(IndexError, match=msg):
+ check_array_indexer(arr, indexer)
+
+
+@pytest.mark.parametrize("indexer", [None, Ellipsis, slice(0, 3), (None,)])
+def test_pass_through_non_array_likes(indexer):
+ arr = np.array([1, 2, 3])
+
+ result = check_array_indexer(arr, indexer)
+ assert result == indexer
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_coercion.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_coercion.py
new file mode 100644
index 0000000000000000000000000000000000000000..662028bb246ba0126b4af0a947933f664ea1225c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_coercion.py
@@ -0,0 +1,895 @@
+from __future__ import annotations
+
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+from pandas.compat import (
+ IS64,
+ is_platform_windows,
+)
+from pandas.compat.numpy import np_version_gt2
+
+import pandas as pd
+import pandas._testing as tm
+
+###############################################################
+# Index / Series common tests which may trigger dtype coercions
+###############################################################
+
+
+class CoercionBase:
+ klasses = ["index", "series"]
+ dtypes = [
+ "object",
+ "int64",
+ "float64",
+ "complex128",
+ "bool",
+ "datetime64",
+ "datetime64tz",
+ "timedelta64",
+ "period",
+ ]
+
+ @property
+ def method(self):
+ raise NotImplementedError(self)
+
+
+class TestSetitemCoercion(CoercionBase):
+ method = "setitem"
+
+ # disable comprehensiveness tests, as most of these have been moved to
+ # tests.series.indexing.test_setitem in SetitemCastingEquivalents subclasses.
+ klasses: list[str] = []
+
+ def test_setitem_series_no_coercion_from_values_list(self):
+ # GH35865 - int casted to str when internally calling np.array(ser.values)
+ ser = pd.Series(["a", 1])
+ ser[:] = list(ser.values)
+
+ expected = pd.Series(["a", 1])
+
+ tm.assert_series_equal(ser, expected)
+
+ def _assert_setitem_index_conversion(
+ self, original_series, loc_key, expected_index, expected_dtype
+ ):
+ """test index's coercion triggered by assign key"""
+ temp = original_series.copy()
+ # GH#33469 pre-2.0 with int loc_key and temp.index.dtype == np.float64
+ # `temp[loc_key] = 5` treated loc_key as positional
+ temp[loc_key] = 5
+ exp = pd.Series([1, 2, 3, 4, 5], index=expected_index)
+ tm.assert_series_equal(temp, exp)
+ # check dtype explicitly for sure
+ assert temp.index.dtype == expected_dtype
+
+ temp = original_series.copy()
+ temp.loc[loc_key] = 5
+ exp = pd.Series([1, 2, 3, 4, 5], index=expected_index)
+ tm.assert_series_equal(temp, exp)
+ # check dtype explicitly for sure
+ assert temp.index.dtype == expected_dtype
+
+ @pytest.mark.parametrize(
+ "val,exp_dtype", [("x", object), (5, IndexError), (1.1, object)]
+ )
+ def test_setitem_index_object(self, val, exp_dtype):
+ obj = pd.Series([1, 2, 3, 4], index=pd.Index(list("abcd"), dtype=object))
+ assert obj.index.dtype == object
+
+ exp_index = pd.Index([*list("abcd"), val], dtype=object)
+ self._assert_setitem_index_conversion(obj, val, exp_index, exp_dtype)
+
+ @pytest.mark.parametrize(
+ "val,exp_dtype", [(5, np.int64), (1.1, np.float64), ("x", object)]
+ )
+ def test_setitem_index_int64(self, val, exp_dtype):
+ obj = pd.Series([1, 2, 3, 4])
+ assert obj.index.dtype == np.int64
+
+ exp_index = pd.Index([0, 1, 2, 3, val])
+ self._assert_setitem_index_conversion(obj, val, exp_index, exp_dtype)
+
+ @pytest.mark.parametrize(
+ "val,exp_dtype", [(5, np.float64), (5.1, np.float64), ("x", object)]
+ )
+ def test_setitem_index_float64(self, val, exp_dtype, request):
+ obj = pd.Series([1, 2, 3, 4], index=[1.1, 2.1, 3.1, 4.1])
+ assert obj.index.dtype == np.float64
+
+ exp_index = pd.Index([1.1, 2.1, 3.1, 4.1, val])
+ self._assert_setitem_index_conversion(obj, val, exp_index, exp_dtype)
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_setitem_series_period(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_setitem_index_complex128(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_setitem_index_bool(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_setitem_index_datetime64(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_setitem_index_datetime64tz(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_setitem_index_timedelta64(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_setitem_index_period(self):
+ raise NotImplementedError
+
+
+class TestInsertIndexCoercion(CoercionBase):
+ klasses = ["index"]
+ method = "insert"
+
+ def _assert_insert_conversion(self, original, value, expected, expected_dtype):
+ """test coercion triggered by insert"""
+ target = original.copy()
+ res = target.insert(1, value)
+ tm.assert_index_equal(res, expected)
+ assert res.dtype == expected_dtype
+
+ @pytest.mark.parametrize(
+ "insert, coerced_val, coerced_dtype",
+ [
+ (1, 1, object),
+ (1.1, 1.1, object),
+ (False, False, object),
+ ("x", "x", object),
+ ],
+ )
+ def test_insert_index_object(self, insert, coerced_val, coerced_dtype):
+ obj = pd.Index(list("abcd"), dtype=object)
+ assert obj.dtype == object
+
+ exp = pd.Index(["a", coerced_val, "b", "c", "d"], dtype=object)
+ self._assert_insert_conversion(obj, insert, exp, coerced_dtype)
+
+ @pytest.mark.parametrize(
+ "insert, coerced_val, coerced_dtype",
+ [
+ (1, 1, None),
+ (1.1, 1.1, np.float64),
+ (False, False, object), # GH#36319
+ ("x", "x", object),
+ ],
+ )
+ def test_insert_int_index(
+ self, any_int_numpy_dtype, insert, coerced_val, coerced_dtype
+ ):
+ dtype = any_int_numpy_dtype
+ obj = pd.Index([1, 2, 3, 4], dtype=dtype)
+ coerced_dtype = coerced_dtype if coerced_dtype is not None else dtype
+
+ exp = pd.Index([1, coerced_val, 2, 3, 4], dtype=coerced_dtype)
+ self._assert_insert_conversion(obj, insert, exp, coerced_dtype)
+
+ @pytest.mark.parametrize(
+ "insert, coerced_val, coerced_dtype",
+ [
+ (1, 1.0, None),
+ # When float_numpy_dtype=float32, this is not the case
+ # see the correction below
+ (1.1, 1.1, np.float64),
+ (False, False, object), # GH#36319
+ ("x", "x", object),
+ ],
+ )
+ def test_insert_float_index(
+ self, float_numpy_dtype, insert, coerced_val, coerced_dtype
+ ):
+ dtype = float_numpy_dtype
+ obj = pd.Index([1.0, 2.0, 3.0, 4.0], dtype=dtype)
+ coerced_dtype = coerced_dtype if coerced_dtype is not None else dtype
+
+ if np_version_gt2 and dtype == "float32" and coerced_val == 1.1:
+ # Hack, in the 2nd test case, since 1.1 can be losslessly cast to float32
+ # the expected dtype will be float32 if the original dtype was float32
+ coerced_dtype = np.float32
+ exp = pd.Index([1.0, coerced_val, 2.0, 3.0, 4.0], dtype=coerced_dtype)
+ self._assert_insert_conversion(obj, insert, exp, coerced_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,exp_dtype",
+ [
+ (pd.Timestamp("2012-01-01").as_unit("s"), "datetime64[ns]"),
+ (
+ pd.Timestamp("2012-01-01", tz="US/Eastern").as_unit("s"),
+ "datetime64[ns, US/Eastern]",
+ ),
+ ],
+ ids=["datetime64", "datetime64tz"],
+ )
+ @pytest.mark.parametrize(
+ "insert_value",
+ [
+ pd.Timestamp("2012-01-01").as_unit("s"),
+ pd.Timestamp("2012-01-01", tz="Asia/Tokyo").as_unit("s"),
+ 1,
+ ],
+ )
+ def test_insert_index_datetimes(self, fill_val, exp_dtype, insert_value):
+ obj = pd.DatetimeIndex(
+ ["2011-01-01", "2011-01-02", "2011-01-03", "2011-01-04"], tz=fill_val.tz
+ ).as_unit("ns")
+ assert obj.dtype == exp_dtype
+
+ exp = pd.DatetimeIndex(
+ ["2011-01-01", fill_val.date(), "2011-01-02", "2011-01-03", "2011-01-04"],
+ tz=fill_val.tz,
+ ).as_unit("ns")
+ self._assert_insert_conversion(obj, fill_val, exp, exp_dtype)
+
+ if fill_val.tz:
+ # mismatched tzawareness
+ ts = pd.Timestamp("2012-01-01").as_unit("s")
+ result = obj.insert(1, ts)
+ expected = obj.astype(object).insert(1, ts)
+ assert expected.dtype == object
+ tm.assert_index_equal(result, expected)
+
+ ts = pd.Timestamp("2012-01-01", tz="Asia/Tokyo").as_unit("s")
+ result = obj.insert(1, ts)
+ # once deprecation is enforced:
+ expected = obj.insert(1, ts.tz_convert(obj.dtype.tz))
+ assert expected.dtype == obj.dtype
+ tm.assert_index_equal(result, expected)
+
+ else:
+ # mismatched tzawareness
+ ts = pd.Timestamp("2012-01-01", tz="Asia/Tokyo").as_unit("s")
+ result = obj.insert(1, ts)
+ expected = obj.astype(object).insert(1, ts)
+ assert expected.dtype == object
+ tm.assert_index_equal(result, expected)
+
+ item = 1
+ result = obj.insert(1, item)
+ expected = obj.astype(object).insert(1, item)
+ assert expected[1] == item
+ assert expected.dtype == object
+ tm.assert_index_equal(result, expected)
+
+ def test_insert_index_timedelta64(self):
+ obj = pd.TimedeltaIndex(["1 day", "2 day", "3 day", "4 day"])
+ assert obj.dtype == "timedelta64[us]"
+
+ # timedelta64 + timedelta64 => timedelta64
+ exp = pd.TimedeltaIndex(["1 day", "10 day", "2 day", "3 day", "4 day"])
+ self._assert_insert_conversion(
+ obj, pd.Timedelta("10 day"), exp, "timedelta64[us]"
+ )
+
+ for item in [pd.Timestamp("2012-01-01").as_unit("s"), 1]:
+ result = obj.insert(1, item)
+ expected = obj.astype(object).insert(1, item)
+ assert expected.dtype == object
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "insert, coerced_val, coerced_dtype",
+ [
+ (pd.Period("2012-01", freq="M"), "2012-01", "period[M]"),
+ (
+ pd.Timestamp("2012-01-01").as_unit("s"),
+ pd.Timestamp("2012-01-01").as_unit("s"),
+ object,
+ ),
+ (1, 1, object),
+ ("x", "x", object),
+ ],
+ )
+ def test_insert_index_period(self, insert, coerced_val, coerced_dtype):
+ obj = pd.PeriodIndex(["2011-01", "2011-02", "2011-03", "2011-04"], freq="M")
+ assert obj.dtype == "period[M]"
+
+ data = [
+ pd.Period("2011-01", freq="M"),
+ coerced_val,
+ pd.Period("2011-02", freq="M"),
+ pd.Period("2011-03", freq="M"),
+ pd.Period("2011-04", freq="M"),
+ ]
+ if isinstance(insert, pd.Period):
+ exp = pd.PeriodIndex(data, freq="M")
+ self._assert_insert_conversion(obj, insert, exp, coerced_dtype)
+
+ # string that can be parsed to appropriate PeriodDtype
+ self._assert_insert_conversion(obj, str(insert), exp, coerced_dtype)
+
+ else:
+ result = obj.insert(0, insert)
+ expected = obj.astype(object).insert(0, insert)
+ tm.assert_index_equal(result, expected)
+
+ # TODO: ATM inserting '2012-01-01 00:00:00' when we have obj.freq=="M"
+ # casts that string to Period[M], not clear that is desirable
+ if not isinstance(insert, pd.Timestamp):
+ # non-castable string
+ result = obj.insert(0, str(insert))
+ expected = obj.astype(object).insert(0, str(insert))
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_insert_index_complex128(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_insert_index_bool(self):
+ raise NotImplementedError
+
+
+class TestWhereCoercion(CoercionBase):
+ method = "where"
+ _cond = np.array([True, False, True, False])
+
+ def _assert_where_conversion(
+ self, original, cond, values, expected, expected_dtype
+ ):
+ """test coercion triggered by where"""
+ target = original.copy()
+ res = target.where(cond, values)
+ tm.assert_equal(res, expected)
+ assert res.dtype == expected_dtype
+
+ def _construct_exp(self, obj, klass, fill_val, exp_dtype):
+ if fill_val is True:
+ values = klass([True, False, True, True])
+ elif isinstance(fill_val, (datetime, np.datetime64)):
+ values = pd.date_range(fill_val, periods=4)
+ else:
+ values = klass(x * fill_val for x in [5, 6, 7, 8])
+
+ exp = klass([obj[0], values[1], obj[2], values[3]], dtype=exp_dtype)
+ return values, exp
+
+ def _run_test(self, obj, fill_val, klass, exp_dtype):
+ cond = klass(self._cond)
+
+ exp = klass([obj[0], fill_val, obj[2], fill_val], dtype=exp_dtype)
+ self._assert_where_conversion(obj, cond, fill_val, exp, exp_dtype)
+
+ values, exp = self._construct_exp(obj, klass, fill_val, exp_dtype)
+ self._assert_where_conversion(obj, cond, values, exp, exp_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,exp_dtype",
+ [(1, object), (1.1, object), (1 + 1j, object), (True, object)],
+ )
+ def test_where_object(self, index_or_series, fill_val, exp_dtype):
+ klass = index_or_series
+ obj = klass(list("abcd"), dtype=object)
+ assert obj.dtype == object
+ self._run_test(obj, fill_val, klass, exp_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,exp_dtype",
+ [(1, np.int64), (1.1, np.float64), (1 + 1j, np.complex128), (True, object)],
+ )
+ def test_where_int64(self, index_or_series, fill_val, exp_dtype, request):
+ klass = index_or_series
+
+ obj = klass([1, 2, 3, 4])
+ assert obj.dtype == np.int64
+ self._run_test(obj, fill_val, klass, exp_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val, exp_dtype",
+ [(1, np.float64), (1.1, np.float64), (1 + 1j, np.complex128), (True, object)],
+ )
+ def test_where_float64(self, index_or_series, fill_val, exp_dtype, request):
+ klass = index_or_series
+
+ obj = klass([1.1, 2.2, 3.3, 4.4])
+ assert obj.dtype == np.float64
+ self._run_test(obj, fill_val, klass, exp_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,exp_dtype",
+ [
+ (1, np.complex128),
+ (1.1, np.complex128),
+ (1 + 1j, np.complex128),
+ (True, object),
+ ],
+ )
+ def test_where_complex128(self, index_or_series, fill_val, exp_dtype):
+ klass = index_or_series
+ obj = klass([1 + 1j, 2 + 2j, 3 + 3j, 4 + 4j], dtype=np.complex128)
+ assert obj.dtype == np.complex128
+ self._run_test(obj, fill_val, klass, exp_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,exp_dtype",
+ [(1, object), (1.1, object), (1 + 1j, object), (True, np.bool_)],
+ )
+ def test_where_series_bool(self, index_or_series, fill_val, exp_dtype):
+ klass = index_or_series
+
+ obj = klass([True, False, True, False])
+ assert obj.dtype == np.bool_
+ self._run_test(obj, fill_val, klass, exp_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,exp_dtype",
+ [
+ (pd.Timestamp("2012-01-01").as_unit("s"), "datetime64[ns]"),
+ (pd.Timestamp("2012-01-01", tz="US/Eastern").as_unit("s"), object),
+ ],
+ ids=["datetime64", "datetime64tz"],
+ )
+ def test_where_datetime64(self, index_or_series, fill_val, exp_dtype):
+ klass = index_or_series
+
+ obj = klass(
+ pd.date_range("2011-01-01", periods=4, freq="D", unit="ns")._with_freq(None)
+ )
+ assert obj.dtype == "datetime64[ns]"
+
+ fv = fill_val
+ # do the check with each of the available datetime scalars
+ if exp_dtype == "datetime64[ns]":
+ for scalar in [fv, fv.to_pydatetime(), fv.to_datetime64()]:
+ self._run_test(obj, scalar, klass, exp_dtype)
+ else:
+ for scalar in [fv, fv.to_pydatetime()]:
+ self._run_test(obj, fill_val, klass, exp_dtype)
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_where_index_complex128(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_where_index_bool(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_where_series_timedelta64(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_where_series_period(self):
+ raise NotImplementedError
+
+ @pytest.mark.parametrize(
+ "value",
+ [pd.Timedelta(days=9).as_unit("us"), timedelta(days=9), np.timedelta64(9, "D")],
+ )
+ def test_where_index_timedelta64(self, value):
+ tdi = pd.timedelta_range("1 Day", periods=4)
+ cond = np.array([True, False, False, True])
+
+ expected = pd.TimedeltaIndex(["1 Day", value, value, "4 Days"])
+ result = tdi.where(cond, value)
+ tm.assert_index_equal(result, expected)
+
+ # wrong-dtyped NaT
+ dtnat = np.datetime64("NaT", "ns")
+ expected = pd.Index([tdi[0], dtnat, dtnat, tdi[3]], dtype=object)
+ assert expected[1] is dtnat
+
+ result = tdi.where(cond, dtnat)
+ tm.assert_index_equal(result, expected)
+
+ def test_where_index_period(self):
+ dti = pd.date_range("2016-01-01", periods=3, freq="QS")
+ pi = dti.to_period("Q")
+
+ cond = np.array([False, True, False])
+
+ # Passing a valid scalar
+ value = pi[-1] + pi.freq * 10
+ expected = pd.PeriodIndex([value, pi[1], value])
+ result = pi.where(cond, value)
+ tm.assert_index_equal(result, expected)
+
+ # Case passing ndarray[object] of Periods
+ other = np.asarray(pi + pi.freq * 10, dtype=object)
+ result = pi.where(cond, other)
+ expected = pd.PeriodIndex([other[0], pi[1], other[2]])
+ tm.assert_index_equal(result, expected)
+
+ # Passing a mismatched scalar -> casts to object
+ td = pd.Timedelta(days=4)
+ expected = pd.Index([td, pi[1], td], dtype=object)
+ result = pi.where(cond, td)
+ tm.assert_index_equal(result, expected)
+
+ per = pd.Period("2020-04-21", "D")
+ expected = pd.Index([per, pi[1], per], dtype=object)
+ result = pi.where(cond, per)
+ tm.assert_index_equal(result, expected)
+
+
+class TestFillnaSeriesCoercion(CoercionBase):
+ # not indexing, but place here for consistency
+
+ method = "fillna"
+
+ def _assert_fillna_conversion(self, original, value, expected, expected_dtype):
+ """test coercion triggered by fillna"""
+ target = original.copy()
+ res = target.fillna(value)
+ tm.assert_equal(res, expected)
+ assert res.dtype == expected_dtype
+
+ @pytest.mark.parametrize(
+ "fill_val, fill_dtype",
+ [(1, object), (1.1, object), (1 + 1j, object), (True, object)],
+ )
+ def test_fillna_object(self, index_or_series, fill_val, fill_dtype):
+ klass = index_or_series
+ obj = klass(["a", np.nan, "c", "d"], dtype=object)
+ assert obj.dtype == object
+
+ exp = klass(["a", fill_val, "c", "d"], dtype=object)
+ self._assert_fillna_conversion(obj, fill_val, exp, fill_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,fill_dtype",
+ [(1, np.float64), (1.1, np.float64), (1 + 1j, np.complex128), (True, object)],
+ )
+ def test_fillna_float64(self, index_or_series, fill_val, fill_dtype):
+ klass = index_or_series
+ obj = klass([1.1, np.nan, 3.3, 4.4])
+ assert obj.dtype == np.float64
+
+ exp = klass([1.1, fill_val, 3.3, 4.4])
+ self._assert_fillna_conversion(obj, fill_val, exp, fill_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,fill_dtype",
+ [
+ (1, np.complex128),
+ (1.1, np.complex128),
+ (1 + 1j, np.complex128),
+ (True, object),
+ ],
+ )
+ def test_fillna_complex128(self, index_or_series, fill_val, fill_dtype):
+ klass = index_or_series
+ obj = klass([1 + 1j, np.nan, 3 + 3j, 4 + 4j], dtype=np.complex128)
+ assert obj.dtype == np.complex128
+
+ exp = klass([1 + 1j, fill_val, 3 + 3j, 4 + 4j])
+ self._assert_fillna_conversion(obj, fill_val, exp, fill_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,fill_dtype",
+ [
+ (pd.Timestamp("2012-01-01").as_unit("s"), "datetime64[s]"),
+ (pd.Timestamp("2012-01-01", tz="US/Eastern").as_unit("s"), object),
+ (1, object),
+ ("x", object),
+ ],
+ ids=["datetime64", "datetime64tz", "object", "object"],
+ )
+ def test_fillna_datetime(self, index_or_series, fill_val, fill_dtype):
+ klass = index_or_series
+ obj = klass(
+ [
+ pd.Timestamp("2011-01-01").as_unit("s"),
+ pd.NaT,
+ pd.Timestamp("2011-01-03").as_unit("s"),
+ pd.Timestamp("2011-01-04").as_unit("s"),
+ ]
+ )
+ assert obj.dtype == "datetime64[s]"
+
+ exp = klass(
+ [
+ pd.Timestamp("2011-01-01").as_unit("s"),
+ fill_val,
+ pd.Timestamp("2011-01-03").as_unit("s"),
+ pd.Timestamp("2011-01-04").as_unit("s"),
+ ]
+ )
+ self._assert_fillna_conversion(obj, fill_val, exp, fill_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val,fill_dtype",
+ [
+ (
+ pd.Timestamp("2012-01-01", tz="US/Eastern").as_unit("s"),
+ "datetime64[s, US/Eastern]",
+ ),
+ (pd.Timestamp("2012-01-01").as_unit("s"), object),
+ # pre-2.0 with a mismatched tz we would get object result
+ (
+ pd.Timestamp("2012-01-01", tz="Asia/Tokyo").as_unit("s"),
+ "datetime64[s, US/Eastern]",
+ ),
+ (1, object),
+ ("x", object),
+ ],
+ )
+ def test_fillna_datetime64tz(self, index_or_series, fill_val, fill_dtype):
+ klass = index_or_series
+ tz = "US/Eastern"
+
+ obj = klass(
+ [
+ pd.Timestamp("2011-01-01", tz=tz).as_unit("s"),
+ pd.NaT,
+ pd.Timestamp("2011-01-03", tz=tz).as_unit("s"),
+ pd.Timestamp("2011-01-04", tz=tz).as_unit("s"),
+ ]
+ )
+ assert obj.dtype == "datetime64[s, US/Eastern]"
+
+ if getattr(fill_val, "tz", None) is None:
+ fv = fill_val
+ else:
+ fv = fill_val.tz_convert(tz)
+ exp = klass(
+ [
+ pd.Timestamp("2011-01-01", tz=tz).as_unit("s"),
+ fv,
+ pd.Timestamp("2011-01-03", tz=tz).as_unit("s"),
+ pd.Timestamp("2011-01-04", tz=tz).as_unit("s"),
+ ]
+ )
+ self._assert_fillna_conversion(obj, fill_val, exp, fill_dtype)
+
+ @pytest.mark.parametrize(
+ "fill_val",
+ [
+ 1,
+ 1.1,
+ 1 + 1j,
+ True,
+ pd.Interval(1, 2, closed="left"),
+ pd.Timestamp("2012-01-01", tz="US/Eastern").as_unit("s"),
+ pd.Timestamp("2012-01-01").as_unit("s"),
+ pd.Timedelta(days=1),
+ pd.Period("2016-01-01", "D"),
+ ],
+ )
+ def test_fillna_interval(self, index_or_series, fill_val):
+ ii = pd.interval_range(1.0, 5.0, closed="right").insert(1, np.nan)
+ assert isinstance(ii.dtype, pd.IntervalDtype)
+ obj = index_or_series(ii)
+
+ exp = index_or_series([ii[0], fill_val, ii[2], ii[3], ii[4]], dtype=object)
+
+ fill_dtype = object
+ self._assert_fillna_conversion(obj, fill_val, exp, fill_dtype)
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_fillna_series_int64(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_fillna_index_int64(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_fillna_series_bool(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_fillna_index_bool(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_fillna_series_timedelta64(self):
+ raise NotImplementedError
+
+ @pytest.mark.parametrize(
+ "fill_val",
+ [
+ 1,
+ 1.1,
+ 1 + 1j,
+ True,
+ pd.Interval(1, 2, closed="left"),
+ pd.Timestamp("2012-01-01", tz="US/Eastern").as_unit("s"),
+ pd.Timestamp("2012-01-01").as_unit("s"),
+ pd.Timedelta(days=1),
+ pd.Period("2016-01-01", "W"),
+ ],
+ )
+ def test_fillna_series_period(self, index_or_series, fill_val):
+ pi = pd.period_range("2016-01-01", periods=4, freq="D").insert(1, pd.NaT)
+ assert isinstance(pi.dtype, pd.PeriodDtype)
+ obj = index_or_series(pi)
+
+ exp = index_or_series([pi[0], fill_val, pi[2], pi[3], pi[4]], dtype=object)
+
+ fill_dtype = object
+ self._assert_fillna_conversion(obj, fill_val, exp, fill_dtype)
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_fillna_index_timedelta64(self):
+ raise NotImplementedError
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_fillna_index_period(self):
+ raise NotImplementedError
+
+
+class TestReplaceSeriesCoercion(CoercionBase):
+ klasses = ["series"]
+ method = "replace"
+
+ rep: dict[str, list] = {}
+ rep["object"] = ["a", "b"]
+ rep["int64"] = [4, 5]
+ rep["float64"] = [1.1, 2.2]
+ rep["complex128"] = [1 + 1j, 2 + 2j]
+ rep["bool"] = [True, False]
+ rep["datetime64[ns]"] = [
+ pd.Timestamp("2011-01-01").as_unit("s"),
+ pd.Timestamp("2011-01-03").as_unit("s"),
+ ]
+
+ for tz in ["UTC", "US/Eastern"]:
+ # to test tz => different tz replacement
+ key = f"datetime64[ns, {tz}]"
+ rep[key] = [
+ pd.Timestamp("2011-01-01", tz=tz).as_unit("s"),
+ pd.Timestamp("2011-01-03", tz=tz).as_unit("s"),
+ ]
+
+ rep["timedelta64[us]"] = [pd.Timedelta("1 day"), pd.Timedelta("2 day")]
+
+ @pytest.fixture(params=["dict", "series"])
+ def how(self, request):
+ return request.param
+
+ @pytest.fixture(
+ params=[
+ "object",
+ "int64",
+ "float64",
+ "complex128",
+ "bool",
+ "datetime64[ns]",
+ "datetime64[ns, UTC]",
+ "datetime64[ns, US/Eastern]",
+ "timedelta64[us]",
+ ]
+ )
+ def from_key(self, request):
+ return request.param
+
+ @pytest.fixture(
+ params=[
+ "object",
+ "int64",
+ "float64",
+ "complex128",
+ "bool",
+ "datetime64[ns]",
+ "datetime64[ns, UTC]",
+ "datetime64[ns, US/Eastern]",
+ "timedelta64[us]",
+ ],
+ ids=[
+ "object",
+ "int64",
+ "float64",
+ "complex128",
+ "bool",
+ "datetime64",
+ "datetime64tz",
+ "datetime64tz",
+ "timedelta64",
+ ],
+ )
+ def to_key(self, request):
+ return request.param
+
+ @pytest.fixture
+ def replacer(self, how, from_key, to_key):
+ """
+ Object we will pass to `Series.replace`
+ """
+ if how == "dict":
+ replacer = dict(zip(self.rep[from_key], self.rep[to_key]))
+ elif how == "series":
+ replacer = pd.Series(self.rep[to_key], index=self.rep[from_key])
+ else:
+ raise ValueError
+ return replacer
+
+ def test_replace_series(self, to_key, from_key, replacer):
+ index = pd.Index([3, 4], name="xxx")
+ obj = pd.Series(self.rep[from_key], index=index, name="yyy")
+ obj = obj.astype(from_key)
+ assert obj.dtype == from_key
+
+ if from_key.startswith("datetime") and to_key.startswith("datetime"):
+ # tested below
+ return
+ elif from_key in ["datetime64[ns, US/Eastern]", "datetime64[ns, UTC]"]:
+ # tested below
+ return
+
+ if (from_key == "float64" and to_key in ("int64")) or (
+ from_key == "complex128" and to_key in ("int64", "float64")
+ ):
+ if not IS64 or is_platform_windows():
+ pytest.skip(f"32-bit platform buggy: {from_key} -> {to_key}")
+
+ # Expected: do not downcast by replacement
+ exp = pd.Series(self.rep[to_key], index=index, name="yyy", dtype=from_key)
+
+ else:
+ exp = pd.Series(self.rep[to_key], index=index, name="yyy")
+
+ result = obj.replace(replacer)
+ tm.assert_series_equal(result, exp, check_dtype=False)
+
+ @pytest.mark.parametrize(
+ "to_key",
+ ["timedelta64[us]", "bool", "object", "complex128", "float64", "int64"],
+ indirect=True,
+ )
+ @pytest.mark.parametrize(
+ "from_key", ["datetime64[ns, UTC]", "datetime64[ns, US/Eastern]"], indirect=True
+ )
+ def test_replace_series_datetime_tz(
+ self, to_key, from_key, replacer, using_infer_string
+ ):
+ index = pd.Index([3, 4], name="xyz")
+ obj = pd.Series(self.rep[from_key], index=index, name="yyy").dt.as_unit("ns")
+ assert obj.dtype == from_key
+
+ exp = pd.Series(self.rep[to_key], index=index, name="yyy")
+ if using_infer_string and to_key == "object":
+ assert exp.dtype == "string"
+ else:
+ assert exp.dtype == to_key
+
+ result = obj.replace(replacer)
+ tm.assert_series_equal(result, exp, check_dtype=False)
+
+ @pytest.mark.parametrize(
+ "to_key",
+ ["datetime64[ns]", "datetime64[ns, UTC]", "datetime64[ns, US/Eastern]"],
+ indirect=True,
+ )
+ @pytest.mark.parametrize(
+ "from_key",
+ ["datetime64[ns]", "datetime64[ns, UTC]", "datetime64[ns, US/Eastern]"],
+ indirect=True,
+ )
+ def test_replace_series_datetime_datetime(self, to_key, from_key, replacer):
+ index = pd.Index([3, 4], name="xyz")
+ obj = pd.Series(self.rep[from_key], index=index, name="yyy").dt.as_unit("ns")
+ assert obj.dtype == from_key
+
+ exp = pd.Series(self.rep[to_key], index=index, name="yyy")
+ if isinstance(obj.dtype, pd.DatetimeTZDtype) and isinstance(
+ exp.dtype, pd.DatetimeTZDtype
+ ):
+ # with mismatched tzs, we retain the original dtype as of 2.0
+ exp = exp.astype(obj.dtype)
+ elif to_key == from_key:
+ exp = exp.dt.as_unit("ns")
+
+ result = obj.replace(replacer)
+ tm.assert_series_equal(result, exp, check_dtype=False)
+
+ @pytest.mark.xfail(reason="Test not implemented")
+ def test_replace_series_period(self):
+ raise NotImplementedError
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_datetime.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..a890de3b977ea2e739cf6c574f789763e4401c98
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_datetime.py
@@ -0,0 +1,191 @@
+import re
+
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeIndex:
+ def test_get_loc_naive_dti_aware_str_deprecated(self):
+ # GH#46903
+ ts = Timestamp("20130101")._value
+ dti = pd.DatetimeIndex([ts + 50 + i for i in range(100)])
+ ser = Series(range(100), index=dti)
+
+ key = "2013-01-01 00:00:00.000000050+0000"
+ msg = re.escape(repr(key))
+ with pytest.raises(KeyError, match=msg):
+ ser[key]
+
+ with pytest.raises(KeyError, match=msg):
+ dti.get_loc(key)
+
+ def test_indexing_with_datetime_tz(self):
+ # GH#8260
+ # support datetime64 with tz
+
+ idx = Index(date_range("20130101", periods=3, tz="US/Eastern"), name="foo")
+ dr = date_range("20130110", periods=3)
+ df = DataFrame({"A": idx, "B": dr})
+ df["C"] = idx
+ df.iloc[1, 1] = pd.NaT
+ df.iloc[1, 2] = pd.NaT
+
+ expected = Series(
+ [Timestamp("2013-01-02 00:00:00-0500", tz="US/Eastern"), pd.NaT, pd.NaT],
+ index=list("ABC"),
+ dtype="object",
+ name=1,
+ )
+
+ # indexing
+ result = df.iloc[1]
+ tm.assert_series_equal(result, expected)
+ result = df.loc[1]
+ tm.assert_series_equal(result, expected)
+
+ def test_indexing_fast_xs(self):
+ # indexing - fast_xs
+ df = DataFrame({"a": date_range("2014-01-01", periods=10, tz="UTC", unit="ns")})
+ result = df.iloc[5]
+ expected = Series(
+ [Timestamp("2014-01-06 00:00:00+0000", tz="UTC")],
+ index=["a"],
+ name=5,
+ dtype="M8[ns, UTC]",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = df.loc[5]
+ tm.assert_series_equal(result, expected)
+
+ # indexing - boolean
+ result = df[df.a > df.a[3]]
+ expected = df.iloc[4:]
+ tm.assert_frame_equal(result, expected)
+
+ def test_consistency_with_tz_aware_scalar(self):
+ # xef gh-12938
+ # various ways of indexing the same tz-aware scalar
+ df = Series([Timestamp("2016-03-30 14:35:25", tz="Europe/Brussels")]).to_frame()
+
+ df = pd.concat([df, df]).reset_index(drop=True)
+ expected = Timestamp("2016-03-30 14:35:25+0200", tz="Europe/Brussels")
+
+ result = df[0][0]
+ assert result == expected
+
+ result = df.iloc[0, 0]
+ assert result == expected
+
+ result = df.loc[0, 0]
+ assert result == expected
+
+ result = df.iat[0, 0]
+ assert result == expected
+
+ result = df.at[0, 0]
+ assert result == expected
+
+ result = df[0].loc[0]
+ assert result == expected
+
+ result = df[0].at[0]
+ assert result == expected
+
+ def test_indexing_with_datetimeindex_tz(self, indexer_sl):
+ # GH 12050
+ # indexing on a series with a datetimeindex with tz
+ index = date_range("2015-01-01", periods=2, tz="utc")
+
+ ser = Series(range(2), index=index, dtype="int64")
+
+ # list-like indexing
+
+ for sel in (index, list(index)):
+ # getitem
+ result = indexer_sl(ser)[sel]
+ expected = ser.copy()
+ if sel is not index:
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+
+ # setitem
+ result = ser.copy()
+ indexer_sl(result)[sel] = 1
+ expected = Series(1, index=index)
+ tm.assert_series_equal(result, expected)
+
+ # single element indexing
+
+ # getitem
+ assert indexer_sl(ser)[index[1]] == 1
+
+ # setitem
+ result = ser.copy()
+ indexer_sl(result)[index[1]] = 5
+ expected = Series([0, 5], index=index)
+ tm.assert_series_equal(result, expected)
+
+ def test_nanosecond_getitem_setitem_with_tz(self):
+ # GH 11679
+ data = ["2016-06-28 08:30:00.123456789"]
+ index = pd.DatetimeIndex(data, dtype="datetime64[ns, America/Chicago]")
+ df = DataFrame({"a": [10]}, index=index)
+ result = df.loc[df.index[0]]
+ expected = Series(10, index=["a"], name=df.index[0])
+ tm.assert_series_equal(result, expected)
+
+ result = df.copy()
+ result.loc[df.index[0], "a"] = -1
+ expected = DataFrame(-1, index=index, columns=["a"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_getitem_str_slice_millisecond_resolution(self, frame_or_series):
+ # GH#33589
+
+ keys = [
+ "2017-10-25T16:25:04.151",
+ "2017-10-25T16:25:04.252",
+ "2017-10-25T16:50:05.237",
+ "2017-10-25T16:50:05.238",
+ ]
+ obj = frame_or_series(
+ [1, 2, 3, 4],
+ index=[Timestamp(x) for x in keys],
+ )
+ result = obj[keys[1] : keys[2]]
+ expected = frame_or_series(
+ [2, 3],
+ index=[
+ Timestamp(keys[1]),
+ Timestamp(keys[2]),
+ ],
+ )
+ tm.assert_equal(result, expected)
+
+ def test_getitem_pyarrow_index(self, frame_or_series):
+ # GH 53644
+ pytest.importorskip("pyarrow")
+ obj = frame_or_series(
+ range(5),
+ index=date_range("2020", freq="D", periods=5).astype(
+ "timestamp[us][pyarrow]"
+ ),
+ )
+ result = obj.loc[obj.index[:-3]]
+ expected = frame_or_series(
+ range(2),
+ index=date_range("2020", freq="D", periods=2).astype(
+ "timestamp[us][pyarrow]"
+ ),
+ )
+ tm.assert_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_floats.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_floats.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9b9e8a6c7c28760593abe5f7464904fb4a0c56c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_floats.py
@@ -0,0 +1,674 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ RangeIndex,
+ Series,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+def gen_obj(klass, index):
+ if klass is Series:
+ obj = Series(np.arange(len(index)), index=index)
+ else:
+ obj = DataFrame(
+ np.random.default_rng(2).standard_normal((len(index), len(index))),
+ index=index,
+ columns=index,
+ )
+ return obj
+
+
+class TestFloatIndexers:
+ def check(self, result, original, indexer, getitem):
+ """
+ comparator for results
+ we need to take care if we are indexing on a
+ Series or a frame
+ """
+ if isinstance(original, Series):
+ expected = original.iloc[indexer]
+ elif getitem:
+ expected = original.iloc[:, indexer]
+ else:
+ expected = original.iloc[indexer]
+
+ tm.assert_almost_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(list("abcde")),
+ Index(list("abcde"), dtype="category"),
+ date_range("2020-01-01", periods=5),
+ timedelta_range("1 day", periods=5),
+ period_range("2020-01-01", periods=5),
+ ],
+ )
+ def test_scalar_non_numeric(self, index, frame_or_series, indexer_sl):
+ # GH 4892
+ # float_indexers should raise exceptions
+ # on appropriate Index types & accessors
+
+ s = gen_obj(frame_or_series, index)
+
+ # getting
+ with pytest.raises(KeyError, match="^3.0$"):
+ indexer_sl(s)[3.0]
+
+ # contains
+ assert 3.0 not in s
+
+ s2 = s.copy()
+ indexer_sl(s2)[3.0] = 10
+
+ if indexer_sl is tm.setitem:
+ assert 3.0 in s2.axes[-1]
+ elif indexer_sl is tm.loc:
+ assert 3.0 in s2.axes[0]
+ else:
+ assert 3.0 not in s2.axes[0]
+ assert 3.0 not in s2.axes[-1]
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(list("abcde")),
+ Index(list("abcde"), dtype="category"),
+ date_range("2020-01-01", periods=5),
+ timedelta_range("1 day", periods=5),
+ period_range("2020-01-01", periods=5),
+ ],
+ )
+ def test_scalar_non_numeric_series_fallback(self, index):
+ # starting in 3.0, integer keys are always treated as labels, no longer
+ # fall back to positional.
+ s = Series(np.arange(len(index)), index=index)
+
+ with pytest.raises(KeyError, match="3"):
+ s[3]
+ with pytest.raises(KeyError, match="^3.0$"):
+ s[3.0]
+
+ def test_scalar_with_mixed(self, indexer_sl):
+ s2 = Series([1, 2, 3], index=["a", "b", "c"])
+ s3 = Series([1, 2, 3], index=["a", "b", 1.5])
+
+ # lookup in a pure string index with an invalid indexer
+
+ with pytest.raises(KeyError, match="^1.0$"):
+ indexer_sl(s2)[1.0]
+
+ with pytest.raises(KeyError, match=r"^1\.0$"):
+ indexer_sl(s2)[1.0]
+
+ result = indexer_sl(s2)["b"]
+ expected = 2
+ assert result == expected
+
+ # mixed index so we have label
+ # indexing
+ with pytest.raises(KeyError, match="^1.0$"):
+ indexer_sl(s3)[1.0]
+
+ if indexer_sl is not tm.loc:
+ # as of 3.0, __getitem__ no longer falls back to positional
+ with pytest.raises(KeyError, match="^1$"):
+ s3[1]
+
+ with pytest.raises(KeyError, match=r"^1\.0$"):
+ indexer_sl(s3)[1.0]
+
+ result = indexer_sl(s3)[1.5]
+ expected = 3
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "index", [Index(np.arange(5), dtype=np.int64), RangeIndex(5)]
+ )
+ def test_scalar_integer(self, index, frame_or_series, indexer_sl):
+ getitem = indexer_sl is not tm.loc
+
+ # test how scalar float indexers work on int indexes
+
+ # integer index
+ i = index
+ obj = gen_obj(frame_or_series, i)
+
+ # coerce to equal int
+
+ result = indexer_sl(obj)[3.0]
+ self.check(result, obj, 3, getitem)
+
+ if isinstance(obj, Series):
+
+ def compare(x, y):
+ assert x == y
+
+ expected = 100
+ else:
+ compare = tm.assert_series_equal
+ if getitem:
+ expected = Series(100, index=range(len(obj)), name=3)
+ else:
+ expected = Series(100.0, index=range(len(obj)), name=3)
+
+ s2 = obj.copy()
+ indexer_sl(s2)[3.0] = 100
+
+ result = indexer_sl(s2)[3.0]
+ compare(result, expected)
+
+ result = indexer_sl(s2)[3]
+ compare(result, expected)
+
+ @pytest.mark.parametrize(
+ "index", [Index(np.arange(5), dtype=np.int64), RangeIndex(5)]
+ )
+ def test_scalar_integer_contains_float(self, index, frame_or_series):
+ # contains
+ # integer index
+ obj = gen_obj(frame_or_series, index)
+
+ # coerce to equal int
+ assert 3.0 in obj
+
+ def test_scalar_float(self, frame_or_series):
+ # scalar float indexers work on a float index
+ index = Index(np.arange(5.0))
+ s = gen_obj(frame_or_series, index)
+
+ # assert all operations except for iloc are ok
+ indexer = index[3]
+ for idxr in [tm.loc, tm.setitem]:
+ getitem = idxr is not tm.loc
+
+ # getting
+ result = idxr(s)[indexer]
+ self.check(result, s, 3, getitem)
+
+ # setting
+ s2 = s.copy()
+
+ result = idxr(s2)[indexer]
+ self.check(result, s, 3, getitem)
+
+ # random float is a KeyError
+ with pytest.raises(KeyError, match=r"^3\.5$"):
+ idxr(s)[3.5]
+
+ # contains
+ assert 3.0 in s
+
+ # iloc succeeds with an integer
+ expected = s.iloc[3]
+ s2 = s.copy()
+
+ s2.iloc[3] = expected
+ result = s2.iloc[3]
+ self.check(result, s, 3, False)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(list("abcde"), dtype=object),
+ date_range("2020-01-01", periods=5),
+ timedelta_range("1 day", periods=5),
+ period_range("2020-01-01", periods=5),
+ ],
+ )
+ @pytest.mark.parametrize("idx", [slice(3.0, 4), slice(3, 4.0), slice(3.0, 4.0)])
+ def test_slice_non_numeric(self, index, idx, frame_or_series, indexer_sli):
+ # GH 4892
+ # float_indexers should raise exceptions
+ # on appropriate Index types & accessors
+
+ s = gen_obj(frame_or_series, index)
+
+ # getitem
+ if indexer_sli is tm.iloc:
+ msg = (
+ "cannot do positional indexing "
+ rf"on {type(index).__name__} with these indexers \[(3|4)\.0\] of "
+ "type float"
+ )
+ else:
+ msg = (
+ "cannot do slice indexing "
+ rf"on {type(index).__name__} with these indexers "
+ r"\[(3|4)(\.0)?\] "
+ r"of type (float|int)"
+ )
+ with pytest.raises(TypeError, match=msg):
+ indexer_sli(s)[idx]
+
+ # setitem
+ if indexer_sli is tm.iloc:
+ # otherwise we keep the same message as above
+ msg = "slice indices must be integers or None or have an __index__ method"
+ with pytest.raises(TypeError, match=msg):
+ indexer_sli(s)[idx] = 0
+
+ def test_slice_integer(self):
+ # same as above, but for Integer based indexes
+ # these coerce to a like integer
+ # oob indicates if we are out of bounds
+ # of positional indexing
+ for index, oob in [
+ (Index(np.arange(5, dtype=np.int64)), False),
+ (RangeIndex(5), False),
+ (Index(np.arange(5, dtype=np.int64) + 10), True),
+ ]:
+ # s is an in-range index
+ s = Series(range(5), index=index)
+
+ # getitem
+ for idx in [slice(3.0, 4), slice(3, 4.0), slice(3.0, 4.0)]:
+ result = s.loc[idx]
+
+ # these are all label indexing
+ # except getitem which is positional
+ # empty
+ if oob:
+ indexer = slice(0, 0)
+ else:
+ indexer = slice(3, 5)
+ self.check(result, s, indexer, False)
+
+ # getitem out-of-bounds
+ for idx in [slice(-6, 6), slice(-6.0, 6.0)]:
+ result = s.loc[idx]
+
+ # these are all label indexing
+ # except getitem which is positional
+ # empty
+ if oob:
+ indexer = slice(0, 0)
+ else:
+ indexer = slice(-6, 6)
+ self.check(result, s, indexer, False)
+
+ # positional indexing
+ msg = (
+ "cannot do slice indexing "
+ rf"on {type(index).__name__} with these indexers \[-6\.0\] of "
+ "type float"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s[slice(-6.0, 6.0)]
+
+ # getitem odd floats
+ for idx, res1 in [
+ (slice(2.5, 4), slice(3, 5)),
+ (slice(2, 3.5), slice(2, 4)),
+ (slice(2.5, 3.5), slice(3, 4)),
+ ]:
+ result = s.loc[idx]
+ if oob:
+ res = slice(0, 0)
+ else:
+ res = res1
+
+ self.check(result, s, res, False)
+
+ # positional indexing
+ msg = (
+ "cannot do slice indexing "
+ rf"on {type(index).__name__} with these indexers \[(2|3)\.5\] of "
+ "type float"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s[idx]
+
+ @pytest.mark.parametrize("idx", [slice(2, 4.0), slice(2.0, 4), slice(2.0, 4.0)])
+ def test_integer_positional_indexing(self, idx):
+ """make sure that we are raising on positional indexing
+ w.r.t. an integer index
+ """
+ s = Series(range(2, 6), index=range(2, 6))
+
+ result = s[2:4]
+ expected = s.iloc[2:4]
+ tm.assert_series_equal(result, expected)
+
+ klass = RangeIndex
+ msg = (
+ "cannot do (slice|positional) indexing "
+ rf"on {klass.__name__} with these indexers \[(2|4)\.0\] of "
+ "type float"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s[idx]
+ with pytest.raises(TypeError, match=msg):
+ s.iloc[idx]
+
+ @pytest.mark.parametrize(
+ "index", [Index(np.arange(5), dtype=np.int64), RangeIndex(5)]
+ )
+ def test_slice_integer_frame_getitem(self, index):
+ # similar to above, but on the getitem dim (of a DataFrame)
+ s = DataFrame(np.random.default_rng(2).standard_normal((5, 2)), index=index)
+
+ # getitem
+ for idx in [slice(0.0, 1), slice(0, 1.0), slice(0.0, 1.0)]:
+ result = s.loc[idx]
+ indexer = slice(0, 2)
+ self.check(result, s, indexer, False)
+
+ # positional indexing
+ msg = (
+ "cannot do slice indexing "
+ rf"on {type(index).__name__} with these indexers \[(0|1)\.0\] of "
+ "type float"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s[idx]
+
+ # getitem out-of-bounds
+ for idx in [slice(-10, 10), slice(-10.0, 10.0)]:
+ result = s.loc[idx]
+ self.check(result, s, slice(-10, 10), True)
+
+ # positional indexing
+ msg = (
+ "cannot do slice indexing "
+ rf"on {type(index).__name__} with these indexers \[-10\.0\] of "
+ "type float"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s[slice(-10.0, 10.0)]
+
+ # getitem odd floats
+ for idx, res in [
+ (slice(0.5, 1), slice(1, 2)),
+ (slice(0, 0.5), slice(0, 1)),
+ (slice(0.5, 1.5), slice(1, 2)),
+ ]:
+ result = s.loc[idx]
+ self.check(result, s, res, False)
+
+ # positional indexing
+ msg = (
+ "cannot do slice indexing "
+ rf"on {type(index).__name__} with these indexers \[0\.5\] of "
+ "type float"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s[idx]
+
+ @pytest.mark.parametrize("idx", [slice(3.0, 4), slice(3, 4.0), slice(3.0, 4.0)])
+ @pytest.mark.parametrize(
+ "index", [Index(np.arange(5), dtype=np.int64), RangeIndex(5)]
+ )
+ def test_float_slice_getitem_with_integer_index_raises(self, idx, index):
+ # similar to above, but on the getitem dim (of a DataFrame)
+ s = DataFrame(np.random.default_rng(2).standard_normal((5, 2)), index=index)
+
+ # setitem
+ sc = s.copy()
+ sc.loc[idx] = 0
+ result = sc.loc[idx].values.ravel()
+ assert (result == 0).all()
+
+ # positional indexing
+ msg = (
+ "cannot do slice indexing "
+ rf"on {type(index).__name__} with these indexers \[(3|4)\.0\] of "
+ "type float"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s[idx] = 0
+
+ with pytest.raises(TypeError, match=msg):
+ s[idx]
+
+ @pytest.mark.parametrize("idx", [slice(3.0, 4), slice(3, 4.0), slice(3.0, 4.0)])
+ def test_slice_float(self, idx, frame_or_series, indexer_sl):
+ # same as above, but for floats
+ index = Index(np.arange(5.0)) + 0.1
+ s = gen_obj(frame_or_series, index)
+
+ expected = s.iloc[3:4]
+
+ # getitem
+ result = indexer_sl(s)[idx]
+ assert isinstance(result, type(s))
+ tm.assert_equal(result, expected)
+
+ # setitem
+ s2 = s.copy()
+ indexer_sl(s2)[idx] = 0
+ result = indexer_sl(s2)[idx].values.ravel()
+ assert (result == 0).all()
+
+ def test_floating_index_doc_example(self):
+ index = Index([1.5, 2, 3, 4.5, 5])
+ s = Series(range(5), index=index)
+ assert s[3] == 2
+ assert s.loc[3] == 2
+ assert s.iloc[3] == 3
+
+ def test_floating_misc(self, indexer_sl):
+ # related 236
+ # scalar/slicing of a float index
+ s = Series(np.arange(5), index=np.arange(5) * 2.5, dtype=np.int64)
+
+ # label based slicing
+ result = indexer_sl(s)[1.0:3.0]
+ expected = Series(1, index=[2.5])
+ tm.assert_series_equal(result, expected)
+
+ # exact indexing when found
+
+ result = indexer_sl(s)[5.0]
+ assert result == 2
+
+ result = indexer_sl(s)[5]
+ assert result == 2
+
+ # value not found (and no fallbacking at all)
+
+ # scalar integers
+ with pytest.raises(KeyError, match=r"^4$"):
+ indexer_sl(s)[4]
+
+ # fancy floats/integers create the correct entry (as nan)
+ # fancy tests
+ expected = Series([2, 0], index=Index([5.0, 0.0], dtype=np.float64))
+ for fancy_idx in [[5.0, 0.0], np.array([5.0, 0.0])]: # float
+ tm.assert_series_equal(indexer_sl(s)[fancy_idx], expected)
+
+ expected = Series([2, 0], index=Index([5, 0], dtype="float64"))
+ for fancy_idx in [[5, 0], np.array([5, 0])]:
+ tm.assert_series_equal(indexer_sl(s)[fancy_idx], expected)
+
+ # all should return the same as we are slicing 'the same'
+ result2 = indexer_sl(s)[2.0:5.0]
+ result3 = indexer_sl(s)[2.0:5]
+ result4 = indexer_sl(s)[2.1:5]
+ tm.assert_series_equal(result2, result3)
+ tm.assert_series_equal(result2, result4)
+
+ # list selection
+ result1 = indexer_sl(s)[[0.0, 5, 10]]
+ result2 = s.iloc[[0, 2, 4]]
+ tm.assert_series_equal(result1, result2)
+
+ with pytest.raises(KeyError, match="not in index"):
+ indexer_sl(s)[[1.6, 5, 10]]
+
+ with pytest.raises(KeyError, match="not in index"):
+ indexer_sl(s)[[0, 1, 2]]
+
+ result = indexer_sl(s)[[2.5, 5]]
+ tm.assert_series_equal(result, Series([1, 2], index=[2.5, 5.0]))
+
+ result = indexer_sl(s)[[2.5]]
+ tm.assert_series_equal(result, Series([1], index=[2.5]))
+
+ def test_floatindex_slicing_bug(self, float_numpy_dtype):
+ # GH 5557, related to slicing a float index
+ dtype = float_numpy_dtype
+ ser = {
+ 256: 2321.0,
+ 1: 78.0,
+ 2: 2716.0,
+ 3: 0.0,
+ 4: 369.0,
+ 5: 0.0,
+ 6: 269.0,
+ 7: 0.0,
+ 8: 0.0,
+ 9: 0.0,
+ 10: 3536.0,
+ 11: 0.0,
+ 12: 24.0,
+ 13: 0.0,
+ 14: 931.0,
+ 15: 0.0,
+ 16: 101.0,
+ 17: 78.0,
+ 18: 9643.0,
+ 19: 0.0,
+ 20: 0.0,
+ 21: 0.0,
+ 22: 63761.0,
+ 23: 0.0,
+ 24: 446.0,
+ 25: 0.0,
+ 26: 34773.0,
+ 27: 0.0,
+ 28: 729.0,
+ 29: 78.0,
+ 30: 0.0,
+ 31: 0.0,
+ 32: 3374.0,
+ 33: 0.0,
+ 34: 1391.0,
+ 35: 0.0,
+ 36: 361.0,
+ 37: 0.0,
+ 38: 61808.0,
+ 39: 0.0,
+ 40: 0.0,
+ 41: 0.0,
+ 42: 6677.0,
+ 43: 0.0,
+ 44: 802.0,
+ 45: 0.0,
+ 46: 2691.0,
+ 47: 0.0,
+ 48: 3582.0,
+ 49: 0.0,
+ 50: 734.0,
+ 51: 0.0,
+ 52: 627.0,
+ 53: 70.0,
+ 54: 2584.0,
+ 55: 0.0,
+ 56: 324.0,
+ 57: 0.0,
+ 58: 605.0,
+ 59: 0.0,
+ 60: 0.0,
+ 61: 0.0,
+ 62: 3989.0,
+ 63: 10.0,
+ 64: 42.0,
+ 65: 0.0,
+ 66: 904.0,
+ 67: 0.0,
+ 68: 88.0,
+ 69: 70.0,
+ 70: 8172.0,
+ 71: 0.0,
+ 72: 0.0,
+ 73: 0.0,
+ 74: 64902.0,
+ 75: 0.0,
+ 76: 347.0,
+ 77: 0.0,
+ 78: 36605.0,
+ 79: 0.0,
+ 80: 379.0,
+ 81: 70.0,
+ 82: 0.0,
+ 83: 0.0,
+ 84: 3001.0,
+ 85: 0.0,
+ 86: 1630.0,
+ 87: 7.0,
+ 88: 364.0,
+ 89: 0.0,
+ 90: 67404.0,
+ 91: 9.0,
+ 92: 0.0,
+ 93: 0.0,
+ 94: 7685.0,
+ 95: 0.0,
+ 96: 1017.0,
+ 97: 0.0,
+ 98: 2831.0,
+ 99: 0.0,
+ 100: 2963.0,
+ 101: 0.0,
+ 102: 854.0,
+ 103: 0.0,
+ 104: 0.0,
+ 105: 0.0,
+ 106: 0.0,
+ 107: 0.0,
+ 108: 0.0,
+ 109: 0.0,
+ 110: 0.0,
+ 111: 0.0,
+ 112: 0.0,
+ 113: 0.0,
+ 114: 0.0,
+ 115: 0.0,
+ 116: 0.0,
+ 117: 0.0,
+ 118: 0.0,
+ 119: 0.0,
+ 120: 0.0,
+ 121: 0.0,
+ 122: 0.0,
+ 123: 0.0,
+ 124: 0.0,
+ 125: 0.0,
+ 126: 67744.0,
+ 127: 22.0,
+ 128: 264.0,
+ 129: 0.0,
+ 260: 197.0,
+ 268: 0.0,
+ 265: 0.0,
+ 269: 0.0,
+ 261: 0.0,
+ 266: 1198.0,
+ 267: 0.0,
+ 262: 2629.0,
+ 258: 775.0,
+ 257: 0.0,
+ 263: 0.0,
+ 259: 0.0,
+ 264: 163.0,
+ 250: 10326.0,
+ 251: 0.0,
+ 252: 1228.0,
+ 253: 0.0,
+ 254: 2769.0,
+ 255: 0.0,
+ }
+
+ # smoke test for the repr
+ s = Series(ser, dtype=dtype)
+ result = s.value_counts()
+ assert result.index.dtype == dtype
+ str(result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_iat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_iat.py
new file mode 100644
index 0000000000000000000000000000000000000000..bb9252a50eb2aded723170f8bddecf8811c1ee76
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_iat.py
@@ -0,0 +1,30 @@
+import numpy as np
+
+from pandas import (
+ DataFrame,
+ Series,
+ period_range,
+)
+
+
+def test_iat(float_frame):
+ for i, row in enumerate(float_frame.index):
+ for j, col in enumerate(float_frame.columns):
+ result = float_frame.iat[i, j]
+ expected = float_frame.at[row, col]
+ assert result == expected
+
+
+def test_iat_duplicate_columns():
+ # https://github.com/pandas-dev/pandas/issues/11754
+ df = DataFrame([[1, 2]], columns=["x", "x"])
+ assert df.iat[0, 0] == 1
+
+
+def test_iat_getitem_series_with_period_index():
+ # GH#4390, iat incorrectly indexing
+ index = period_range("1/1/2001", periods=10)
+ ser = Series(np.random.default_rng(2).standard_normal(10), index=index)
+ expected = ser[index[0]]
+ result = ser.iat[0]
+ assert expected == result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_iloc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_iloc.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb67a2fe64533cf6480543fc5b8d56117e6fc76d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_iloc.py
@@ -0,0 +1,1550 @@
+"""test positional based indexing with iloc"""
+
+from datetime import datetime
+import re
+
+import numpy as np
+import pytest
+
+from pandas.compat import pa_version_under16p0
+from pandas.errors import IndexingError
+import pandas.util._test_decorators as td
+
+from pandas import (
+ NA,
+ Categorical,
+ CategoricalDtype,
+ DataFrame,
+ Index,
+ Interval,
+ NaT,
+ Series,
+ Timestamp,
+ array,
+ concat,
+ date_range,
+ interval_range,
+ isna,
+ to_datetime,
+)
+import pandas._testing as tm
+from pandas.api.types import is_scalar
+from pandas.tests.indexing.common import check_indexing_smoketest_or_raises
+
+# We pass through the error message from numpy
+_slice_iloc_msg = re.escape(
+ "only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) "
+ "and integer or boolean arrays are valid indices"
+)
+
+
+class TestiLoc:
+ @pytest.mark.parametrize("key", [2, -1, [0, 1, 2]])
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(list("abcd"), dtype=object),
+ Index([2, 4, "null", 8], dtype=object),
+ date_range("20130101", periods=4),
+ Index(range(0, 8, 2), dtype=np.float64),
+ Index([]),
+ ],
+ )
+ def test_iloc_getitem_int_and_list_int(self, key, frame_or_series, index, request):
+ obj = frame_or_series(range(len(index)), index=index)
+ check_indexing_smoketest_or_raises(
+ obj,
+ "iloc",
+ key,
+ fails=IndexError,
+ )
+
+ # array of ints (GH5006), make sure that a single indexer is returning
+ # the correct type
+
+
+class TestiLocBaseIndependent:
+ """Tests Independent Of Base Class"""
+
+ @pytest.mark.parametrize(
+ "key",
+ [
+ slice(None),
+ slice(3),
+ range(3),
+ [0, 1, 2],
+ Index(range(3)),
+ np.asarray([0, 1, 2]),
+ ],
+ )
+ def test_iloc_setitem_fullcol_categorical(self, indexer_li, key):
+ frame = DataFrame({0: range(3)}, dtype=object)
+
+ cat = Categorical(["alpha", "beta", "gamma"])
+
+ assert frame._mgr.blocks[0]._can_hold_element(cat)
+
+ df = frame.copy()
+ orig_vals = df.values
+
+ indexer_li(df)[key, 0] = cat
+
+ expected = DataFrame({0: cat}).astype(object)
+ assert np.shares_memory(df[0].values, orig_vals)
+
+ tm.assert_frame_equal(df, expected)
+
+ # check we dont have a view on cat (may be undesired GH#39986)
+ df.iloc[0, 0] = "gamma"
+ assert cat[0] != "gamma"
+
+ # pre-2.0 with mixed dataframe ("split" path) we always overwrote the
+ # column. as of 2.0 we correctly write "into" the column, so
+ # we retain the object dtype.
+ frame = DataFrame({0: np.array([0, 1, 2], dtype=object), 1: range(3)})
+ df = frame.copy()
+ indexer_li(df)[key, 0] = cat
+ expected = DataFrame({0: Series(cat.astype(object), dtype=object), 1: range(3)})
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_iloc_setitem_ea_inplace(
+ self, frame_or_series, index_or_series_or_array, has_ref
+ ):
+ # GH#38952 Case with not setting a full column
+ # IntegerArray without NAs
+ arr = array([1, 2, 3, 4])
+ obj = frame_or_series(arr.to_numpy("i8"))
+ if has_ref:
+ view = obj[:] # noqa: F841
+
+ if frame_or_series is Series:
+ values = obj.values
+ else:
+ values = obj._mgr.blocks[0].values
+
+ if frame_or_series is Series:
+ obj.iloc[:2] = index_or_series_or_array(arr[2:])
+ else:
+ obj.iloc[:2, 0] = index_or_series_or_array(arr[2:])
+
+ expected = frame_or_series(np.array([3, 4, 3, 4], dtype="i8"))
+ tm.assert_equal(obj, expected)
+
+ # Check that we are actually in-place
+ if not has_ref:
+ if frame_or_series is Series:
+ assert obj.values is not values
+ assert np.shares_memory(obj.values, values)
+ else:
+ assert np.shares_memory(obj[0].values, values)
+
+ def test_is_scalar_access(self):
+ # GH#32085 index with duplicates doesn't matter for _is_scalar_access
+ index = Index([1, 2, 1])
+ ser = Series(range(3), index=index)
+
+ assert ser.iloc._is_scalar_access((1,))
+
+ df = ser.to_frame()
+ assert df.iloc._is_scalar_access((1, 0))
+
+ @pytest.mark.skipif(
+ pa_version_under16p0, reason="https://github.com/apache/arrow/issues/40642"
+ )
+ def test_iloc_exceeds_bounds(self):
+ # GH6296
+ # iloc should allow indexers that exceed the bounds
+ df = DataFrame(np.random.default_rng(2).random((20, 5)), columns=list("ABCDE"))
+
+ # lists of positions should raise IndexError!
+ msg = "positional indexers are out-of-bounds"
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[:, [0, 1, 2, 3, 4, 5]]
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[[1, 30]]
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[[1, -30]]
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[[100]]
+
+ s = df["A"]
+ with pytest.raises(IndexError, match=msg):
+ s.iloc[[100]]
+ with pytest.raises(IndexError, match=msg):
+ s.iloc[[-100]]
+
+ # still raise on a single indexer
+ msg = "single positional indexer is out-of-bounds"
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[30]
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[-30]
+
+ # GH10779
+ # single positive/negative indexer exceeding Series bounds should raise
+ # an IndexError
+ with pytest.raises(IndexError, match=msg):
+ s.iloc[30]
+ with pytest.raises(IndexError, match=msg):
+ s.iloc[-30]
+
+ # slices are ok
+ result = df.iloc[:, 4:10] # 0 < start < len < stop
+ expected = df.iloc[:, 4:]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[:, -4:-10] # stop < 0 < start < len
+ expected = df.iloc[:, :0]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[:, 10:4:-1] # 0 < stop < len < start (down)
+ expected = df.iloc[:, :4:-1]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[:, 4:-10:-1] # stop < 0 < start < len (down)
+ expected = df.iloc[:, 4::-1]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[:, -10:4] # start < 0 < stop < len
+ expected = df.iloc[:, :4]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[:, 10:4] # 0 < stop < len < start
+ expected = df.iloc[:, :0]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[:, -10:-11:-1] # stop < start < 0 < len (down)
+ expected = df.iloc[:, :0]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[:, 10:11] # 0 < len < start < stop
+ expected = df.iloc[:, :0]
+ tm.assert_frame_equal(result, expected)
+
+ # slice bounds exceeding is ok
+ result = s.iloc[18:30]
+ expected = s.iloc[18:]
+ tm.assert_series_equal(result, expected)
+
+ result = s.iloc[30:]
+ expected = s.iloc[:0]
+ tm.assert_series_equal(result, expected)
+
+ result = s.iloc[30::-1]
+ expected = s.iloc[::-1]
+ tm.assert_series_equal(result, expected)
+
+ # doc example
+ dfl = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("AB")
+ )
+ tm.assert_frame_equal(
+ dfl.iloc[:, 2:3],
+ DataFrame(index=dfl.index, columns=Index([], dtype=dfl.columns.dtype)),
+ )
+ tm.assert_frame_equal(dfl.iloc[:, 1:3], dfl.iloc[:, [1]])
+ tm.assert_frame_equal(dfl.iloc[4:6], dfl.iloc[[4]])
+
+ msg = "positional indexers are out-of-bounds"
+ with pytest.raises(IndexError, match=msg):
+ dfl.iloc[[4, 5, 6]]
+ msg = "single positional indexer is out-of-bounds"
+ with pytest.raises(IndexError, match=msg):
+ dfl.iloc[:, 4]
+
+ @pytest.mark.parametrize("index,columns", [(np.arange(20), list("ABCDE"))])
+ @pytest.mark.parametrize(
+ "index_vals,column_vals",
+ [
+ ([slice(None), ["A", "D"]]),
+ (["1", "2"], slice(None)),
+ ([datetime(2019, 1, 1)], slice(None)),
+ ],
+ )
+ def test_iloc_non_integer_raises(self, index, columns, index_vals, column_vals):
+ # GH 25753
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(index), len(columns))),
+ index=index,
+ columns=columns,
+ )
+ msg = ".iloc requires numeric indexers, got"
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[index_vals, column_vals]
+
+ def test_iloc_getitem_invalid_scalar(self, frame_or_series):
+ # GH 21982
+
+ obj = DataFrame(np.arange(100).reshape(10, 10))
+ obj = tm.get_obj(obj, frame_or_series)
+
+ with pytest.raises(TypeError, match="Cannot index by location index"):
+ obj.iloc["a"]
+
+ def test_iloc_array_not_mutating_negative_indices(self):
+ # GH 21867
+ array_with_neg_numbers = np.array([1, 2, -1])
+ array_copy = array_with_neg_numbers.copy()
+ df = DataFrame(
+ {"A": [100, 101, 102], "B": [103, 104, 105], "C": [106, 107, 108]},
+ index=[1, 2, 3],
+ )
+ df.iloc[array_with_neg_numbers]
+ tm.assert_numpy_array_equal(array_with_neg_numbers, array_copy)
+ df.iloc[:, array_with_neg_numbers]
+ tm.assert_numpy_array_equal(array_with_neg_numbers, array_copy)
+
+ def test_iloc_getitem_neg_int_can_reach_first_index(self):
+ # GH10547 and GH10779
+ # negative integers should be able to reach index 0
+ df = DataFrame({"A": [2, 3, 5], "B": [7, 11, 13]})
+ s = df["A"]
+
+ expected = df.iloc[0]
+ result = df.iloc[-3]
+ tm.assert_series_equal(result, expected)
+
+ expected = df.iloc[[0]]
+ result = df.iloc[[-3]]
+ tm.assert_frame_equal(result, expected)
+
+ expected = s.iloc[0]
+ result = s.iloc[-3]
+ assert result == expected
+
+ expected = s.iloc[[0]]
+ result = s.iloc[[-3]]
+ tm.assert_series_equal(result, expected)
+
+ # check the length 1 Series case highlighted in GH10547
+ expected = Series(["a"], index=["A"])
+ result = expected.iloc[[-1]]
+ tm.assert_series_equal(result, expected)
+
+ def test_iloc_getitem_dups(self):
+ # GH 6766
+ df1 = DataFrame([{"A": None, "B": 1}, {"A": 2, "B": 2}])
+ df2 = DataFrame([{"A": 3, "B": 3}, {"A": 4, "B": 4}])
+ df = concat([df1, df2], axis=1)
+
+ # cross-sectional indexing
+ result = df.iloc[0, 0]
+ assert isna(result)
+
+ result = df.iloc[0, :]
+ expected = Series([np.nan, 1, 3, 3], index=["A", "B", "A", "B"], name=0)
+ tm.assert_series_equal(result, expected)
+
+ def test_iloc_getitem_array(self):
+ df = DataFrame(
+ [
+ {"A": 1, "B": 2, "C": 3},
+ {"A": 100, "B": 200, "C": 300},
+ {"A": 1000, "B": 2000, "C": 3000},
+ ]
+ )
+
+ expected = DataFrame([{"A": 1, "B": 2, "C": 3}])
+ tm.assert_frame_equal(df.iloc[[0]], expected)
+
+ expected = DataFrame([{"A": 1, "B": 2, "C": 3}, {"A": 100, "B": 200, "C": 300}])
+ tm.assert_frame_equal(df.iloc[[0, 1]], expected)
+
+ expected = DataFrame([{"B": 2, "C": 3}, {"B": 2000, "C": 3000}], index=[0, 2])
+ result = df.iloc[[0, 2], [1, 2]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_iloc_getitem_bool(self):
+ df = DataFrame(
+ [
+ {"A": 1, "B": 2, "C": 3},
+ {"A": 100, "B": 200, "C": 300},
+ {"A": 1000, "B": 2000, "C": 3000},
+ ]
+ )
+
+ expected = DataFrame([{"A": 1, "B": 2, "C": 3}, {"A": 100, "B": 200, "C": 300}])
+ result = df.iloc[[True, True, False]]
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(
+ [{"A": 1, "B": 2, "C": 3}, {"A": 1000, "B": 2000, "C": 3000}], index=[0, 2]
+ )
+ result = df.iloc[lambda x: x.index % 2 == 0]
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("index", [[True, False], [True, False, True, False]])
+ def test_iloc_getitem_bool_diff_len(self, index):
+ # GH26658
+ s = Series([1, 2, 3])
+ msg = f"Boolean index has wrong length: {len(index)} instead of {len(s)}"
+ with pytest.raises(IndexError, match=msg):
+ s.iloc[index]
+
+ def test_iloc_getitem_slice(self):
+ df = DataFrame(
+ [
+ {"A": 1, "B": 2, "C": 3},
+ {"A": 100, "B": 200, "C": 300},
+ {"A": 1000, "B": 2000, "C": 3000},
+ ]
+ )
+
+ expected = DataFrame([{"A": 1, "B": 2, "C": 3}, {"A": 100, "B": 200, "C": 300}])
+ result = df.iloc[:2]
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame([{"A": 100, "B": 200}], index=[1])
+ result = df.iloc[1:2, 0:2]
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(
+ [{"A": 1, "C": 3}, {"A": 100, "C": 300}, {"A": 1000, "C": 3000}]
+ )
+ result = df.iloc[:, lambda df: [0, 2]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_iloc_getitem_slice_dups(self):
+ df1 = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=["A", "A", "B", "B"],
+ )
+ df2 = DataFrame(
+ np.random.default_rng(2).integers(0, 10, size=20).reshape(10, 2),
+ columns=["A", "C"],
+ )
+
+ # axis=1
+ df = concat([df1, df2], axis=1)
+ tm.assert_frame_equal(df.iloc[:, :4], df1)
+ tm.assert_frame_equal(df.iloc[:, 4:], df2)
+
+ df = concat([df2, df1], axis=1)
+ tm.assert_frame_equal(df.iloc[:, :2], df2)
+ tm.assert_frame_equal(df.iloc[:, 2:], df1)
+
+ exp = concat([df2, df1.iloc[:, [0]]], axis=1)
+ tm.assert_frame_equal(df.iloc[:, 0:3], exp)
+
+ # axis=0
+ df = concat([df, df], axis=0)
+ tm.assert_frame_equal(df.iloc[0:10, :2], df2)
+ tm.assert_frame_equal(df.iloc[0:10, 2:], df1)
+ tm.assert_frame_equal(df.iloc[10:, :2], df2)
+ tm.assert_frame_equal(df.iloc[10:, 2:], df1)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_iloc_setitem(sel, has_ref):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 4)),
+ index=np.arange(0, 8, 2),
+ columns=np.arange(0, 12, 3),
+ )
+ if has_ref:
+ view = df[:] # noqa: F841
+
+ df.iloc[1, 1] = 1
+ result = df.iloc[1, 1]
+ assert result == 1
+
+ df.iloc[:, 2:3] = 0
+ expected = df.iloc[:, 2:3]
+ result = df.iloc[:, 2:3]
+ tm.assert_frame_equal(result, expected)
+
+ # GH5771
+ s = Series(0, index=[4, 5, 6])
+ s.iloc[1:2] += 1
+ expected = Series([0, 1, 0], index=[4, 5, 6])
+ tm.assert_series_equal(s, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_iloc_setitem_axis_argument(self, has_ref):
+ # GH45032
+ df = DataFrame([[6, "c", 10], [7, "d", 11], [8, "e", 12]])
+ df[1] = df[1].astype(object)
+ if has_ref:
+ view = df[:]
+ expected = DataFrame([[6, "c", 10], [7, "d", 11], [5, 5, 5]])
+ expected[1] = expected[1].astype(object)
+ df.iloc(axis=0)[2] = 5
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame([[6, "c", 10], [7, "d", 11], [8, "e", 12]])
+ df[1] = df[1].astype(object)
+ if has_ref:
+ view = df[:] # noqa: F841
+ expected = DataFrame([[6, "c", 5], [7, "d", 5], [8, "e", 5]])
+ expected[1] = expected[1].astype(object)
+ df.iloc(axis=1)[2] = 5
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_iloc_setitem_list(self, has_ref):
+ # setitem with an iloc list
+ df = DataFrame(
+ np.arange(9).reshape((3, 3)), index=["A", "B", "C"], columns=["A", "B", "C"]
+ )
+ if has_ref:
+ view = df[:] # noqa: F841
+ df.iloc[[0, 1], [1, 2]]
+ df.iloc[[0, 1], [1, 2]] += 100
+
+ expected = DataFrame(
+ np.array([0, 101, 102, 3, 104, 105, 6, 7, 8]).reshape((3, 3)),
+ index=["A", "B", "C"],
+ columns=["A", "B", "C"],
+ )
+ tm.assert_frame_equal(df, expected)
+
+ def test_iloc_setitem_pandas_object(self):
+ # GH 17193
+ s_orig = Series([0, 1, 2, 3])
+ expected = Series([0, -1, -2, 3])
+
+ s = s_orig.copy()
+ s.iloc[Series([1, 2])] = [-1, -2]
+ tm.assert_series_equal(s, expected)
+
+ s = s_orig.copy()
+ s.iloc[Index([1, 2])] = [-1, -2]
+ tm.assert_series_equal(s, expected)
+
+ def test_iloc_setitem_dups(self):
+ # GH 6766
+ # iloc with a mask aligning from another iloc
+ df1 = DataFrame([{"A": None, "B": 1}, {"A": 2, "B": 2}])
+ df2 = DataFrame([{"A": 3, "B": 3}, {"A": 4, "B": 4}])
+ df = concat([df1, df2], axis=1)
+
+ expected = df.fillna(3)
+ inds = np.isnan(df.iloc[:, 0])
+ mask = inds[inds].index
+ df.iloc[mask, 0] = df.iloc[mask, 2]
+ tm.assert_frame_equal(df, expected)
+
+ # del a dup column across blocks
+ expected = DataFrame({0: [1, 2], 1: [3, 4]})
+ expected.columns = ["B", "B"]
+ del df["A"]
+ tm.assert_frame_equal(df, expected)
+
+ # assign back to self
+ df.iloc[[0, 1], [0, 1]] = df.iloc[[0, 1], [0, 1]]
+ tm.assert_frame_equal(df, expected)
+
+ # reversed x 2
+ df.iloc[[1, 0], [0, 1]] = df.iloc[[1, 0], [0, 1]].reset_index(drop=True)
+ df.iloc[[1, 0], [0, 1]] = df.iloc[[1, 0], [0, 1]].reset_index(drop=True)
+ tm.assert_frame_equal(df, expected)
+
+ def test_iloc_setitem_frame_duplicate_columns_multiple_blocks(self):
+ # Same as the "assign back to self" check in test_iloc_setitem_dups
+ # but on a DataFrame with multiple blocks
+ df = DataFrame([[0, 1], [2, 3]], columns=["B", "B"])
+
+ # setting float values that can be held by existing integer arrays
+ # is inplace
+ df.iloc[:, 0] = df.iloc[:, 0].astype("f8")
+ assert len(df._mgr.blocks) == 1
+
+ # if the assigned values cannot be held by existing integer arrays,
+ # we raise
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.iloc[:, 0] = df.iloc[:, 0] + 0.5
+
+ expected = df.copy()
+
+ # assign back to self
+ df.iloc[[0, 1], [0, 1]] = df.iloc[[0, 1], [0, 1]]
+
+ tm.assert_frame_equal(df, expected)
+
+ # TODO: GH#27620 this test used to compare iloc against ix; check if this
+ # is redundant with another test comparing iloc against loc
+ def test_iloc_getitem_frame(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ index=range(0, 20, 2),
+ columns=range(0, 8, 2),
+ )
+
+ result = df.iloc[2]
+ exp = df.loc[4]
+ tm.assert_series_equal(result, exp)
+
+ result = df.iloc[2, 2]
+ exp = df.loc[4, 4]
+ assert result == exp
+
+ # slice
+ result = df.iloc[4:8]
+ expected = df.loc[8:14]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[:, 2:3]
+ expected = df.loc[:, 4:5]
+ tm.assert_frame_equal(result, expected)
+
+ # list of integers
+ result = df.iloc[[0, 1, 3]]
+ expected = df.loc[[0, 2, 6]]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.iloc[[0, 1, 3], [0, 1]]
+ expected = df.loc[[0, 2, 6], [0, 2]]
+ tm.assert_frame_equal(result, expected)
+
+ # neg indices
+ result = df.iloc[[-1, 1, 3], [-1, 1]]
+ expected = df.loc[[18, 2, 6], [6, 2]]
+ tm.assert_frame_equal(result, expected)
+
+ # dups indices
+ result = df.iloc[[-1, -1, 1, 3], [-1, 1]]
+ expected = df.loc[[18, 18, 2, 6], [6, 2]]
+ tm.assert_frame_equal(result, expected)
+
+ # with index-like
+ s = Series(index=range(1, 5), dtype=object)
+ result = df.iloc[s.index]
+ expected = df.loc[[2, 4, 6, 8]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_iloc_getitem_labelled_frame(self):
+ # try with labelled frame
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ index=list("abcdefghij"),
+ columns=list("ABCD"),
+ )
+
+ result = df.iloc[1, 1]
+ exp = df.loc["b", "B"]
+ assert result == exp
+
+ result = df.iloc[:, 2:3]
+ expected = df.loc[:, ["C"]]
+ tm.assert_frame_equal(result, expected)
+
+ # negative indexing
+ result = df.iloc[-1, -1]
+ exp = df.loc["j", "D"]
+ assert result == exp
+
+ # out-of-bounds exception
+ msg = "index 5 is out of bounds for axis 0 with size 4|index out of bounds"
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[10, 5]
+
+ # trying to use a label
+ msg = (
+ r"Location based indexing can only have \[integer, integer "
+ r"slice \(START point is INCLUDED, END point is EXCLUDED\), "
+ r"listlike of integers, boolean array\] types"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.iloc["j", "D"]
+
+ def test_iloc_getitem_doc_issue(self):
+ # multi axis slicing issue with single block
+ # surfaced in GH 6059
+
+ arr = np.random.default_rng(2).standard_normal((6, 4))
+ index = date_range("20130101", periods=6)
+ columns = list("ABCD")
+ df = DataFrame(arr, index=index, columns=columns)
+
+ # defines ref_locs
+ df.describe()
+
+ result = df.iloc[3:5, 0:2]
+
+ expected = DataFrame(arr[3:5, 0:2], index=index[3:5], columns=columns[0:2])
+ tm.assert_frame_equal(result, expected)
+
+ # for dups
+ df.columns = list("aaaa")
+ result = df.iloc[3:5, 0:2]
+
+ expected = DataFrame(arr[3:5, 0:2], index=index[3:5], columns=list("aa"))
+ tm.assert_frame_equal(result, expected)
+
+ # related
+ arr = np.random.default_rng(2).standard_normal((6, 4))
+ index = list(range(0, 12, 2))
+ columns = list(range(0, 8, 2))
+ df = DataFrame(arr, index=index, columns=columns)
+
+ df._mgr.blocks[0].mgr_locs
+ result = df.iloc[1:5, 2:4]
+ expected = DataFrame(arr[1:5, 2:4], index=index[1:5], columns=columns[2:4])
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_iloc_setitem_series(self, has_ref):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ index=list("abcdefghij"),
+ columns=list("ABCD"),
+ )
+ if has_ref:
+ view = df[:] # noqa: F841
+
+ df.iloc[1, 1] = 1
+ result = df.iloc[1, 1]
+ assert result == 1
+
+ df.iloc[:, 2:3] = 0
+ expected = df.iloc[:, 2:3]
+ result = df.iloc[:, 2:3]
+ tm.assert_frame_equal(result, expected)
+
+ s = Series(np.random.default_rng(2).standard_normal(10), index=range(0, 20, 2))
+
+ s.iloc[1] = 1
+ result = s.iloc[1]
+ assert result == 1
+
+ s.iloc[:4] = 0
+ expected = s.iloc[:4]
+ result = s.iloc[:4]
+ tm.assert_series_equal(result, expected)
+
+ s = Series([-1] * 6)
+ s.iloc[0::2] = [0, 2, 4]
+ s.iloc[1::2] = [1, 3, 5]
+ result = s
+ expected = Series([0, 1, 2, 3, 4, 5])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_iloc_setitem_list_of_lists(self, has_ref):
+ # GH 7551
+ # list-of-list is set incorrectly in mixed vs. single dtyped frames
+ df = DataFrame(
+ {"A": np.arange(5, dtype="int64"), "B": np.arange(5, 10, dtype="int64")}
+ )
+ if has_ref:
+ view = df[:]
+ df.iloc[2:4] = [[10, 11], [12, 13]]
+ expected = DataFrame({"A": [0, 1, 10, 12, 4], "B": [5, 6, 11, 13, 9]})
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame(
+ {"A": ["a", "b", "c", "d", "e"], "B": np.arange(5, 10, dtype="int64")}
+ )
+ if has_ref:
+ view = df[:] # noqa: F841
+ df.iloc[2:4] = [["x", 11], ["y", 13]]
+ expected = DataFrame({"A": ["a", "b", "x", "y", "e"], "B": [5, 6, 11, 13, 9]})
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ @pytest.mark.parametrize("indexer", [[0], slice(None, 1, None), np.array([0])])
+ @pytest.mark.parametrize("value", [["Z"], np.array(["Z"])])
+ def test_iloc_setitem_with_scalar_index(self, has_ref, indexer, value):
+ # GH #19474
+ # assigning like "df.iloc[0, [0]] = ['Z']" should be evaluated
+ # elementwisely, not using "setter('A', ['Z'])".
+
+ # Set object type to avoid upcast when setting "Z"
+ df = DataFrame([[1, 2], [3, 4]], columns=["A", "B"]).astype({"A": object})
+ if has_ref:
+ view = df[:] # noqa: F841
+ df.iloc[0, indexer] = value
+ result = df.iloc[0, 0]
+
+ assert is_scalar(result) and result == "Z"
+
+ @pytest.mark.filterwarnings("ignore::UserWarning")
+ def test_iloc_mask(self):
+ # GH 60994, iloc with a mask (of a series) should return accordingly
+ df = DataFrame(list(range(5)), index=list("ABCDE"), columns=["a"])
+ mask = df.a % 2 == 0
+ msg = "iLocation based boolean indexing cannot use an indexable as a mask"
+ with pytest.raises(ValueError, match=msg):
+ df.iloc[mask]
+
+ mask.index = range(len(mask))
+ msg = "Unalignable boolean Series provided as indexer"
+ with pytest.raises(IndexingError, match=msg):
+ df.iloc[mask]
+
+ # ndarray ok
+ result = df.iloc[np.array([True] * len(mask), dtype=bool)]
+ tm.assert_frame_equal(result, df)
+
+ # the possibilities
+ locs = np.arange(4)
+ nums = 2**locs
+ reps = [bin(num) for num in nums]
+ df = DataFrame({"locs": locs, "nums": nums}, reps)
+
+ expected = {
+ (None, ""): "0b1100",
+ (None, ".loc"): "0b1100",
+ (None, ".iloc"): "0b1100",
+ ("index", ""): "0b11",
+ ("index", ".loc"): "0b11",
+ (
+ "index",
+ ".iloc",
+ ): "iLocation based boolean indexing cannot use an indexable as a mask",
+ ("locs", ""): "Unalignable boolean Series provided as indexer",
+ ("locs", ".loc"): "Unalignable boolean Series provided as indexer",
+ ("locs", ".iloc"): "Unalignable boolean Series provided as indexer",
+ }
+
+ # UserWarnings from reindex of a boolean mask
+ for idx in [None, "index", "locs"]:
+ mask = (df.nums > 2).values
+ if idx:
+ mask_index = getattr(df, idx)[::-1]
+ mask = Series(mask, list(mask_index))
+ for method in ["", ".loc", ".iloc"]:
+ try:
+ if method:
+ accessor = getattr(df, method[1:])
+ else:
+ accessor = df
+ answer = str(bin(accessor[mask]["nums"].sum()))
+ except (ValueError, IndexingError) as err:
+ answer = str(err)
+
+ key = (
+ idx,
+ method,
+ )
+ expected_result = expected.get(key)
+
+ # Fix the assertion to check for substring match
+ if (
+ idx is None or (idx == "index" and method != ".iloc")
+ ) and "0b" in expected_result:
+ # For successful numeric results, exact match is needed
+ assert expected_result == answer, (
+ f"[{key}] does not match [{answer}]"
+ )
+ else:
+ # For error messages, substring match is sufficient
+ assert expected_result in answer, f"[{key}] not found in [{answer}]"
+
+ def test_iloc_with_numpy_bool_array(self):
+ df = DataFrame(list(range(5)), index=list("ABCDE"), columns=["a"])
+ result = df.iloc[np.array([True, False, True, False, True], dtype=bool)]
+ expected = DataFrame({"a": [0, 2, 4]}, index=["A", "C", "E"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_iloc_series_mask_with_index_mismatch_raises(self):
+ df = DataFrame(list(range(5)), index=list("ABCDE"), columns=["a"])
+ mask = df.a % 2 == 0
+ msg = "Unalignable boolean Series provided as indexer"
+ with pytest.raises(IndexingError, match=msg):
+ df.iloc[Series([True] * len(mask), dtype=bool)]
+
+ def test_iloc_series_mask_all_true(self):
+ df = DataFrame(list(range(5)), columns=["a"])
+ mask = Series([True] * len(df), dtype=bool)
+ result = df.iloc[mask]
+ tm.assert_frame_equal(result, df)
+
+ def test_iloc_series_mask_alternate_true(self):
+ df = DataFrame(list(range(5)), columns=["a"])
+ mask = Series([True, False, True, False, True], dtype=bool)
+ result = df.iloc[mask]
+ expected = DataFrame({"a": [0, 2, 4]}, index=[0, 2, 4])
+ tm.assert_frame_equal(result, expected)
+
+ def test_iloc_non_unique_indexing(self):
+ # GH 4017, non-unique indexing (on the axis)
+ df = DataFrame({"A": [0.1] * 3000, "B": [1] * 3000})
+ idx = np.arange(30) * 99
+ expected = df.iloc[idx]
+
+ df3 = concat([df, 2 * df, 3 * df])
+ result = df3.iloc[idx]
+
+ tm.assert_frame_equal(result, expected)
+
+ df2 = DataFrame({"A": [0.1] * 1000, "B": [1] * 1000})
+ df2 = concat([df2, 2 * df2, 3 * df2])
+
+ with pytest.raises(KeyError, match="not in index"):
+ df2.loc[idx]
+
+ def test_iloc_empty_list_indexer_is_ok(self):
+ df = DataFrame(
+ np.ones((5, 2)),
+ index=Index([f"i-{i}" for i in range(5)], name="a"),
+ columns=Index([f"i-{i}" for i in range(2)], name="a"),
+ )
+ # vertical empty
+ tm.assert_frame_equal(
+ df.iloc[:, []],
+ df.iloc[:, :0],
+ check_index_type=True,
+ check_column_type=True,
+ )
+ # horizontal empty
+ tm.assert_frame_equal(
+ df.iloc[[], :],
+ df.iloc[:0, :],
+ check_index_type=True,
+ check_column_type=True,
+ )
+ # horizontal empty
+ tm.assert_frame_equal(
+ df.iloc[[]], df.iloc[:0, :], check_index_type=True, check_column_type=True
+ )
+
+ def test_identity_slice_returns_new_object(self):
+ # GH13873
+ original_df = DataFrame({"a": [1, 2, 3]})
+ sliced_df = original_df.iloc[:]
+ assert sliced_df is not original_df
+
+ # should be a shallow copy
+ assert np.shares_memory(original_df["a"], sliced_df["a"])
+
+ # Setting using .loc[:, "a"] sets inplace so alters both sliced and orig
+ # depending on CoW
+ original_df.loc[:, "a"] = [4, 4, 4]
+ assert (sliced_df["a"] == [1, 2, 3]).all()
+
+ original_series = Series([1, 2, 3, 4, 5, 6])
+ sliced_series = original_series.iloc[:]
+ assert sliced_series is not original_series
+
+ # should also be a shallow copy
+ original_series[:3] = [7, 8, 9]
+ # shallow copy not updated (CoW)
+ assert all(sliced_series[:3] == [1, 2, 3])
+
+ def test_indexing_zerodim_np_array(self):
+ # GH24919
+ df = DataFrame([[1, 2], [3, 4]])
+ result = df.iloc[np.array(0)]
+ s = Series([1, 2], name=0)
+ tm.assert_series_equal(result, s)
+
+ def test_series_indexing_zerodim_np_array(self):
+ # GH24919
+ s = Series([1, 2])
+ result = s.iloc[np.array(0)]
+ assert result == 1
+
+ def test_iloc_setitem_categorical_updates_inplace(self):
+ # Mixed dtype ensures we go through take_split_path in setitem_with_indexer
+ cat = Categorical(["A", "B", "C"])
+ df = DataFrame({1: cat, 2: [1, 2, 3]}, copy=False)
+
+ assert tm.shares_memory(df[1], cat)
+
+ # With the enforcement of GH#45333 in 2.0, this modifies original
+ # values inplace
+ df.iloc[:, 0] = cat[::-1]
+
+ assert tm.shares_memory(df[1], cat)
+ expected = Categorical(["C", "B", "A"], categories=["A", "B", "C"])
+ tm.assert_categorical_equal(cat, expected)
+
+ def test_iloc_with_boolean_operation(self):
+ # GH 20627
+ result = DataFrame([[0, 1], [2, 3], [4, 5], [6, np.nan]])
+ result.iloc[result.index <= 2] *= 2
+ expected = DataFrame([[0, 2], [4, 6], [8, 10], [6, np.nan]])
+ tm.assert_frame_equal(result, expected)
+
+ result.iloc[result.index > 2] *= 2
+ expected = DataFrame([[0, 2], [4, 6], [8, 10], [12, np.nan]])
+ tm.assert_frame_equal(result, expected)
+
+ result.iloc[[True, True, False, False]] *= 2
+ expected = DataFrame([[0, 4], [8, 12], [8, 10], [12, np.nan]])
+ tm.assert_frame_equal(result, expected)
+
+ result.iloc[[False, False, True, True]] /= 2
+ expected = DataFrame([[0, 4.0], [8, 12.0], [4, 5.0], [6, np.nan]])
+ tm.assert_frame_equal(result, expected)
+
+ def test_iloc_getitem_singlerow_slice_categoricaldtype_gives_series(self):
+ # GH#29521
+ df = DataFrame({"x": Categorical("a b c d e".split())})
+ result = df.iloc[0]
+ raw_cat = Categorical(["a"], categories=["a", "b", "c", "d", "e"])
+ expected = Series(raw_cat, index=["x"], name=0, dtype="category")
+
+ tm.assert_series_equal(result, expected)
+
+ def test_iloc_getitem_categorical_values(self):
+ # GH#14580
+ # test iloc() on Series with Categorical data
+
+ ser = Series([1, 2, 3]).astype("category")
+
+ # get slice
+ result = ser.iloc[0:2]
+ expected = Series([1, 2]).astype(CategoricalDtype([1, 2, 3]))
+ tm.assert_series_equal(result, expected)
+
+ # get list of indexes
+ result = ser.iloc[[0, 1]]
+ expected = Series([1, 2]).astype(CategoricalDtype([1, 2, 3]))
+ tm.assert_series_equal(result, expected)
+
+ # get boolean array
+ result = ser.iloc[[True, False, False]]
+ expected = Series([1]).astype(CategoricalDtype([1, 2, 3]))
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("value", [None, NaT, np.nan])
+ def test_iloc_setitem_td64_values_cast_na(self, value):
+ # GH#18586
+ series = Series([0, 1, 2], dtype="timedelta64[ns]")
+ series.iloc[0] = value
+ expected = Series([NaT, 1, 2], dtype="timedelta64[ns]")
+ tm.assert_series_equal(series, expected)
+
+ @pytest.mark.parametrize("not_na", [Interval(0, 1), "a", 1.0])
+ def test_setitem_mix_of_nan_and_interval(self, not_na, nulls_fixture):
+ # GH#27937
+ dtype = CategoricalDtype(categories=[not_na])
+ ser = Series(
+ [nulls_fixture, nulls_fixture, nulls_fixture, nulls_fixture], dtype=dtype
+ )
+ ser.iloc[:3] = [nulls_fixture, not_na, nulls_fixture]
+ exp = Series([nulls_fixture, not_na, nulls_fixture, nulls_fixture], dtype=dtype)
+ tm.assert_series_equal(ser, exp)
+
+ def test_iloc_setitem_empty_frame_raises_with_3d_ndarray(self):
+ idx = Index([])
+ obj = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), len(idx))),
+ index=idx,
+ columns=idx,
+ )
+ nd3 = np.random.default_rng(2).integers(5, size=(2, 2, 2))
+
+ msg = f"Cannot set values with ndim > {obj.ndim}"
+ with pytest.raises(ValueError, match=msg):
+ obj.iloc[nd3] = 0
+
+ def test_iloc_getitem_read_only_values(self, indexer_li):
+ # GH#10043 this is fundamentally a test for iloc, but test loc while
+ # we're here
+ rw_array = np.eye(10)
+ rw_df = DataFrame(rw_array)
+
+ ro_array = np.eye(10)
+ ro_array.setflags(write=False)
+ ro_df = DataFrame(ro_array)
+
+ tm.assert_frame_equal(
+ indexer_li(rw_df)[[1, 2, 3]], indexer_li(ro_df)[[1, 2, 3]]
+ )
+ tm.assert_frame_equal(indexer_li(rw_df)[[1]], indexer_li(ro_df)[[1]])
+ tm.assert_series_equal(indexer_li(rw_df)[1], indexer_li(ro_df)[1])
+ tm.assert_frame_equal(indexer_li(rw_df)[1:3], indexer_li(ro_df)[1:3])
+
+ def test_iloc_getitem_readonly_key(self):
+ # GH#17192 iloc with read-only array raising TypeError
+ df = DataFrame({"data": np.ones(100, dtype="float64")})
+ indices = np.array([1, 3, 6])
+ indices.flags.writeable = False
+
+ result = df.iloc[indices]
+ expected = df.loc[[1, 3, 6]]
+ tm.assert_frame_equal(result, expected)
+
+ result = df["data"].iloc[indices]
+ expected = df["data"].loc[[1, 3, 6]]
+ tm.assert_series_equal(result, expected)
+
+ def test_iloc_assign_series_to_df_cell(self):
+ # GH 37593
+ df = DataFrame(columns=["a"], index=[0])
+ df.iloc[0, 0] = Series([1, 2, 3])
+ expected = DataFrame({"a": [Series([1, 2, 3])]}, columns=["a"], index=[0])
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("klass", [list, np.array])
+ def test_iloc_setitem_bool_indexer(self, klass):
+ # GH#36741
+ df = DataFrame({"flag": ["x", "y", "z"], "value": [1, 3, 4]})
+ indexer = klass([True, False, False])
+ df.iloc[indexer, 1] = df.iloc[indexer, 1] * 2
+ expected = DataFrame({"flag": ["x", "y", "z"], "value": [2, 3, 4]})
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ @pytest.mark.parametrize("indexer", [[1], slice(1, 2)])
+ def test_iloc_setitem_pure_position_based(self, indexer, has_ref):
+ # GH#22046
+ df1 = DataFrame({"a2": [11, 12, 13], "b2": [14, 15, 16]})
+ df2 = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})
+ if has_ref:
+ view = df2[:] # noqa: F841
+ df2.iloc[:, indexer] = df1.iloc[:, [0]]
+ expected = DataFrame({"a": [1, 2, 3], "b": [11, 12, 13], "c": [7, 8, 9]})
+ tm.assert_frame_equal(df2, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_iloc_setitem_dictionary_value(self, has_ref):
+ # GH#37728
+ df = DataFrame({"x": [1, 2], "y": [2, 2]})
+ if has_ref:
+ view = df[:]
+ rhs = {"x": 9, "y": 99}
+ df.iloc[1] = rhs
+ expected = DataFrame({"x": [1, 9], "y": [2, 99]})
+ tm.assert_frame_equal(df, expected)
+
+ # GH#38335 same thing, mixed dtypes
+ df = DataFrame({"x": [1, 2], "y": [2.0, 2.0]})
+ if has_ref:
+ view = df[:] # noqa: F841
+ df.iloc[1] = rhs
+ expected = DataFrame({"x": [1, 9], "y": [2.0, 99.0]})
+ tm.assert_frame_equal(df, expected)
+
+ def test_iloc_getitem_float_duplicates(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ index=[0.1, 0.2, 0.2],
+ columns=list("abc"),
+ )
+ expect = df.iloc[1:]
+ tm.assert_frame_equal(df.loc[0.2], expect)
+
+ expect = df.iloc[1:, 0]
+ tm.assert_series_equal(df.loc[0.2, "a"], expect)
+
+ df.index = [1, 0.2, 0.2]
+ expect = df.iloc[1:]
+ tm.assert_frame_equal(df.loc[0.2], expect)
+
+ expect = df.iloc[1:, 0]
+ tm.assert_series_equal(df.loc[0.2, "a"], expect)
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 3)),
+ index=[1, 0.2, 0.2, 1],
+ columns=list("abc"),
+ )
+ expect = df.iloc[1:-1]
+ tm.assert_frame_equal(df.loc[0.2], expect)
+
+ expect = df.iloc[1:-1, 0]
+ tm.assert_series_equal(df.loc[0.2, "a"], expect)
+
+ df.index = [0.1, 0.2, 2, 0.2]
+ expect = df.iloc[[1, -1]]
+ tm.assert_frame_equal(df.loc[0.2], expect)
+
+ expect = df.iloc[[1, -1], 0]
+ tm.assert_series_equal(df.loc[0.2, "a"], expect)
+
+ def test_iloc_setitem_custom_object(self):
+ # iloc with an object
+ class TO:
+ def __init__(self, value) -> None:
+ self.value = value
+
+ def __str__(self) -> str:
+ return f"[{self.value}]"
+
+ __repr__ = __str__
+
+ def __eq__(self, other) -> bool:
+ return self.value == other.value
+
+ def view(self):
+ return self
+
+ df = DataFrame(index=[0, 1], columns=[0])
+ df.iloc[1, 0] = TO(1)
+ df.iloc[1, 0] = TO(2)
+
+ result = DataFrame(index=[0, 1], columns=[0])
+ result.iloc[1, 0] = TO(2)
+
+ tm.assert_frame_equal(result, df)
+
+ # remains object dtype even after setting it back
+ df = DataFrame(index=[0, 1], columns=[0])
+ df.iloc[1, 0] = TO(1)
+ df.iloc[1, 0] = np.nan
+ result = DataFrame(index=[0, 1], columns=[0])
+
+ tm.assert_frame_equal(result, df)
+
+ def test_iloc_getitem_with_duplicates(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((3, 3)),
+ columns=list("ABC"),
+ index=list("aab"),
+ )
+
+ result = df.iloc[0]
+ assert isinstance(result, Series)
+ tm.assert_almost_equal(result.values, df.values[0])
+
+ result = df.T.iloc[:, 0]
+ assert isinstance(result, Series)
+ tm.assert_almost_equal(result.values, df.values[0])
+
+ def test_iloc_getitem_with_duplicates2(self):
+ # GH#2259
+ df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=[1, 1, 2])
+ result = df.iloc[:, [0]]
+ expected = df.take([0], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ def test_iloc_interval(self):
+ # GH#17130
+ df = DataFrame({Interval(1, 2): [1, 2]})
+
+ result = df.iloc[0]
+ expected = Series({Interval(1, 2): 1}, name=0)
+ tm.assert_series_equal(result, expected)
+
+ result = df.iloc[:, 0]
+ expected = Series([1, 2], name=Interval(1, 2))
+ tm.assert_series_equal(result, expected)
+
+ result = df.copy()
+ result.iloc[:, 0] += 1
+ expected = DataFrame({Interval(1, 2): [2, 3]})
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("indexing_func", [list, np.array])
+ @pytest.mark.parametrize("rhs_func", [list, np.array])
+ def test_loc_setitem_boolean_list(self, rhs_func, indexing_func):
+ # GH#20438 testing specifically list key, not arraylike
+ ser = Series([0, 1, 2])
+ ser.iloc[indexing_func([True, False, True])] = rhs_func([5, 10])
+ expected = Series([5, 1, 10])
+ tm.assert_series_equal(ser, expected)
+
+ df = DataFrame({"a": [0, 1, 2]})
+ df.iloc[indexing_func([True, False, True])] = rhs_func([[5], [10]])
+ expected = DataFrame({"a": [5, 1, 10]})
+ tm.assert_frame_equal(df, expected)
+
+ def test_iloc_getitem_slice_negative_step_ea_block(self):
+ # GH#44551
+ df = DataFrame({"A": [1, 2, 3]}, dtype="Int64")
+
+ res = df.iloc[:, ::-1]
+ tm.assert_frame_equal(res, df)
+
+ df["B"] = "foo"
+ res = df.iloc[:, ::-1]
+ expected = DataFrame({"B": df["B"], "A": df["A"]})
+ tm.assert_frame_equal(res, expected)
+
+ def test_iloc_setitem_2d_ndarray_into_ea_block(self):
+ # GH#44703
+ df = DataFrame({"status": ["a", "b", "c"]}, dtype="category")
+ df.iloc[np.array([0, 1]), np.array([0])] = np.array([["a"], ["a"]])
+
+ expected = DataFrame({"status": ["a", "a", "c"]}, dtype=df["status"].dtype)
+ tm.assert_frame_equal(df, expected)
+
+ def test_iloc_getitem_int_single_ea_block_view(self):
+ # GH#45241
+ # TODO: make an extension interface test for this?
+ arr = interval_range(1, 10.0)._values
+ df = DataFrame(arr, copy=False)
+
+ # ser should be a *view* on the DataFrame data
+ ser = df.iloc[2]
+
+ # if we have a view, then changing arr[2] should also change ser[0]
+ assert arr[2] != arr[-1] # otherwise the rest isn't meaningful
+ arr[2] = arr[-1]
+ assert ser[0] == arr[-1]
+
+ def test_iloc_setitem_multicolumn_to_datetime(self, using_infer_string):
+ # GH#20511
+ df = DataFrame({"A": ["2022-01-01", "2022-01-02"], "B": ["2021", "2022"]})
+
+ if using_infer_string:
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.iloc[:, [0]] = DataFrame({"A": to_datetime(["2021", "2022"])})
+ else:
+ df.iloc[:, [0]] = DataFrame({"A": to_datetime(["2021", "2022"])})
+ expected = DataFrame(
+ {
+ "A": [
+ Timestamp("2021-01-01 00:00:00"),
+ Timestamp("2022-01-01 00:00:00"),
+ ],
+ "B": ["2021", "2022"],
+ }
+ )
+ tm.assert_frame_equal(df, expected, check_dtype=False)
+
+
+class TestILocErrors:
+ # NB: this test should work for _any_ Series we can pass as
+ # series_with_simple_index
+ def test_iloc_float_raises(self, series_with_simple_index, frame_or_series):
+ # GH#4892
+ # float_indexers should raise exceptions
+ # on appropriate Index types & accessors
+ # this duplicates the code below
+ # but is specifically testing for the error
+ # message
+
+ obj = series_with_simple_index
+ if frame_or_series is DataFrame:
+ obj = obj.to_frame()
+
+ msg = "Cannot index by location index with a non-integer key"
+ with pytest.raises(TypeError, match=msg):
+ obj.iloc[3.0]
+
+ with pytest.raises(IndexError, match=_slice_iloc_msg):
+ obj.iloc[3.0] = 0
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_iloc_getitem_setitem_fancy_exceptions(self, float_frame, has_ref):
+ with pytest.raises(IndexingError, match="Too many indexers"):
+ float_frame.iloc[:, :, :]
+
+ if has_ref:
+ view = float_frame[:] # noqa: F841
+ with pytest.raises(IndexError, match="too many indices for array"):
+ # GH#32257 we let numpy do validation, get their exception
+ float_frame.iloc[:, :, :] = 1
+
+ def test_iloc_frame_indexer(self):
+ # GH#39004
+ df = DataFrame({"a": [1, 2, 3]})
+ indexer = DataFrame({"a": [True, False, True]})
+ msg = "DataFrame indexer for .iloc is not supported. Consider using .loc"
+ with pytest.raises(TypeError, match=msg):
+ df.iloc[indexer] = 1
+
+ msg = (
+ "DataFrame indexer is not allowed for .iloc\n"
+ "Consider using .loc for automatic alignment."
+ )
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[indexer]
+
+
+class TestILocSetItemDuplicateColumns:
+ def test_iloc_setitem_scalar_duplicate_columns(self):
+ # GH#15686, duplicate columns and mixed dtype
+ df1 = DataFrame([{"A": None, "B": 1}, {"A": 2, "B": 2}])
+ df2 = DataFrame([{"A": 3, "B": 3}, {"A": 4, "B": 4}])
+ df = concat([df1, df2], axis=1)
+ df.iloc[0, 0] = -1
+
+ assert df.iloc[0, 0] == -1
+ assert df.iloc[0, 2] == 3
+ assert df.dtypes.iloc[2] == np.int64
+
+ def test_iloc_setitem_list_duplicate_columns(self):
+ # GH#22036 setting with same-sized list
+ df = DataFrame([[0, "str", "str2"]], columns=["a", "b", "b"])
+
+ df.iloc[:, 2] = ["str3"]
+
+ expected = DataFrame([[0, "str", "str3"]], columns=["a", "b", "b"])
+ tm.assert_frame_equal(df, expected)
+
+ def test_iloc_setitem_series_duplicate_columns(self):
+ df = DataFrame(
+ np.arange(8, dtype=np.int64).reshape(2, 4), columns=["A", "B", "A", "B"]
+ )
+ df.iloc[:, 0] = df.iloc[:, 0].astype(np.float64)
+ assert df.dtypes.iloc[2] == np.int64
+
+ @pytest.mark.parametrize(
+ ["dtypes", "init_value", "expected_value"],
+ [("int64", "0", 0), ("float", "1.2", 1.2)],
+ )
+ def test_iloc_setitem_dtypes_duplicate_columns(
+ self, dtypes, init_value, expected_value
+ ):
+ # GH#22035
+ df = DataFrame(
+ [[init_value, "str", "str2"]], columns=["a", "b", "b"], dtype=object
+ )
+
+ # with the enforcement of GH#45333 in 2.0, this sets values inplace,
+ # so we retain object dtype
+ df.iloc[:, 0] = df.iloc[:, 0].astype(dtypes)
+
+ expected_df = DataFrame(
+ [[expected_value, "str", "str2"]],
+ columns=["a", "b", "b"],
+ dtype=object,
+ )
+ tm.assert_frame_equal(df, expected_df)
+
+
+class TestILocCallable:
+ def test_frame_iloc_getitem_callable(self):
+ # GH#11485
+ df = DataFrame({"X": [1, 2, 3, 4], "Y": list("aabb")}, index=list("ABCD"))
+
+ # return location
+ res = df.iloc[lambda x: [1, 3]]
+ tm.assert_frame_equal(res, df.iloc[[1, 3]])
+
+ res = df.iloc[lambda x: [1, 3], :]
+ tm.assert_frame_equal(res, df.iloc[[1, 3], :])
+
+ res = df.iloc[lambda x: [1, 3], lambda x: 0]
+ tm.assert_series_equal(res, df.iloc[[1, 3], 0])
+
+ res = df.iloc[lambda x: [1, 3], lambda x: [0]]
+ tm.assert_frame_equal(res, df.iloc[[1, 3], [0]])
+
+ # mixture
+ res = df.iloc[[1, 3], lambda x: 0]
+ tm.assert_series_equal(res, df.iloc[[1, 3], 0])
+
+ res = df.iloc[[1, 3], lambda x: [0]]
+ tm.assert_frame_equal(res, df.iloc[[1, 3], [0]])
+
+ res = df.iloc[lambda x: [1, 3], 0]
+ tm.assert_series_equal(res, df.iloc[[1, 3], 0])
+
+ res = df.iloc[lambda x: [1, 3], [0]]
+ tm.assert_frame_equal(res, df.iloc[[1, 3], [0]])
+
+ def test_frame_iloc_setitem_callable(self):
+ # GH#11485
+ df = DataFrame(
+ {"X": [1, 2, 3, 4], "Y": Series(list("aabb"), dtype=object)},
+ index=list("ABCD"),
+ )
+
+ # return location
+ res = df.copy()
+ res.iloc[lambda x: [1, 3]] = 0
+ exp = df.copy()
+ exp.iloc[[1, 3]] = 0
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.iloc[lambda x: [1, 3], :] = -1
+ exp = df.copy()
+ exp.iloc[[1, 3], :] = -1
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.iloc[lambda x: [1, 3], lambda x: 0] = 5
+ exp = df.copy()
+ exp.iloc[[1, 3], 0] = 5
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.iloc[lambda x: [1, 3], lambda x: [0]] = 25
+ exp = df.copy()
+ exp.iloc[[1, 3], [0]] = 25
+ tm.assert_frame_equal(res, exp)
+
+ # mixture
+ res = df.copy()
+ res.iloc[[1, 3], lambda x: 0] = -3
+ exp = df.copy()
+ exp.iloc[[1, 3], 0] = -3
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.iloc[[1, 3], lambda x: [0]] = -5
+ exp = df.copy()
+ exp.iloc[[1, 3], [0]] = -5
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.iloc[lambda x: [1, 3], 0] = 10
+ exp = df.copy()
+ exp.iloc[[1, 3], 0] = 10
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.iloc[lambda x: [1, 3], [0]] = [-5, -5]
+ exp = df.copy()
+ exp.iloc[[1, 3], [0]] = [-5, -5]
+ tm.assert_frame_equal(res, exp)
+
+
+class TestILocSeries:
+ def test_iloc(self):
+ ser = Series(
+ np.random.default_rng(2).standard_normal(10), index=list(range(0, 20, 2))
+ )
+ ser_original = ser.copy()
+
+ for i in range(len(ser)):
+ result = ser.iloc[i]
+ exp = ser[ser.index[i]]
+ tm.assert_almost_equal(result, exp)
+
+ # pass a slice
+ result = ser.iloc[slice(1, 3)]
+ expected = ser.loc[2:4]
+ tm.assert_series_equal(result, expected)
+
+ # test slice is a view
+ with tm.assert_produces_warning(None):
+ # GH#45324 make sure we aren't giving a spurious FutureWarning
+ result[:] = 0
+ tm.assert_series_equal(ser, ser_original)
+
+ # list of integers
+ result = ser.iloc[[0, 2, 3, 4, 5]]
+ expected = ser.reindex(ser.index[[0, 2, 3, 4, 5]])
+ tm.assert_series_equal(result, expected)
+
+ def test_iloc_getitem_nonunique(self):
+ ser = Series([0, 1, 2], index=[0, 1, 0])
+ assert ser.iloc[2] == 2
+
+ def test_iloc_setitem_pure_position_based(self):
+ # GH#22046
+ ser1 = Series([1, 2, 3])
+ ser2 = Series([4, 5, 6], index=[1, 0, 2])
+ ser1.iloc[1:3] = ser2.iloc[1:3]
+ expected = Series([1, 5, 6])
+ tm.assert_series_equal(ser1, expected)
+
+ def test_iloc_nullable_int64_size_1_nan(self):
+ # GH 31861
+ result = DataFrame({"a": ["test"], "b": [np.nan]})
+
+ ser = Series([NA], name="b", dtype="Int64")
+ with pytest.raises(TypeError, match="Invalid value"):
+ result.loc[:, "b"] = ser
+
+ def test_iloc_arrow_extension_array(self):
+ # GH#61311
+ pytest.importorskip("pyarrow")
+ df = DataFrame({"a": [1, 2], "c": [0, 2], "d": ["c", "a"]})
+ df_arrow = DataFrame(
+ {"a": [1, 2], "c": [0, 2], "d": ["c", "a"]}
+ ).convert_dtypes(dtype_backend="pyarrow")
+ expected = df.iloc[:, df["c"]]
+ result = df_arrow.iloc[:, df_arrow["c"]]
+ tm.assert_frame_equal(result, expected, check_dtype=False)
+
+ @td.skip_if_no("pyarrow")
+ def test_setitem_pyarrow_int_series(self):
+ # GH#62462
+ ser = Series([1, 2, 3], dtype="int64[pyarrow]")
+ idx = Index([0, 1])
+ vals = Series([7, 8], dtype="int64[pyarrow]")
+
+ ser.iloc[idx] = vals
+
+ expected = Series([7, 8, 3], dtype="int64[pyarrow]")
+ tm.assert_series_equal(ser, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_indexers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_indexers.py
new file mode 100644
index 0000000000000000000000000000000000000000..ddc5c039160d5ada6c6dccb62514590a4ce9f620
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_indexers.py
@@ -0,0 +1,61 @@
+# Tests aimed at pandas.core.indexers
+import numpy as np
+import pytest
+
+from pandas.core.indexers import (
+ is_scalar_indexer,
+ length_of_indexer,
+ validate_indices,
+)
+
+
+def test_length_of_indexer():
+ arr = np.zeros(4, dtype=bool)
+ arr[0] = 1
+ result = length_of_indexer(arr)
+ assert result == 1
+
+
+def test_is_scalar_indexer():
+ indexer = (0, 1)
+ assert is_scalar_indexer(indexer, 2)
+ assert not is_scalar_indexer(indexer[0], 2)
+
+ indexer = (np.array([2]), 1)
+ assert not is_scalar_indexer(indexer, 2)
+
+ indexer = (np.array([2]), np.array([3]))
+ assert not is_scalar_indexer(indexer, 2)
+
+ indexer = (np.array([2]), np.array([3, 4]))
+ assert not is_scalar_indexer(indexer, 2)
+
+ assert not is_scalar_indexer(slice(None), 1)
+
+ indexer = 0
+ assert is_scalar_indexer(indexer, 1)
+
+ indexer = (0,)
+ assert is_scalar_indexer(indexer, 1)
+
+
+class TestValidateIndices:
+ def test_validate_indices_ok(self):
+ indices = np.asarray([0, 1])
+ validate_indices(indices, 2)
+ validate_indices(indices[:0], 0)
+ validate_indices(np.array([-1, -1]), 0)
+
+ def test_validate_indices_low(self):
+ indices = np.asarray([0, -2])
+ with pytest.raises(ValueError, match="'indices' contains"):
+ validate_indices(indices, 2)
+
+ def test_validate_indices_high(self):
+ indices = np.asarray([0, 1, 2])
+ with pytest.raises(IndexError, match="indices are out"):
+ validate_indices(indices, 2)
+
+ def test_validate_indices_empty(self):
+ with pytest.raises(IndexError, match="indices are out"):
+ validate_indices(np.array([0, 1]), 0)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..3dffb9b04ea1a359ee0a96eb94f9edfb6067aaf0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_indexing.py
@@ -0,0 +1,1136 @@
+"""test fancy indexing & misc"""
+
+import array
+from datetime import datetime
+import re
+import weakref
+
+import numpy as np
+import pytest
+
+from pandas.errors import IndexingError
+
+from pandas.core.dtypes.common import (
+ is_float_dtype,
+ is_integer_dtype,
+ is_object_dtype,
+)
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ NaT,
+ Series,
+ date_range,
+ offsets,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.tests.indexing.common import _mklbl
+from pandas.tests.indexing.test_floats import gen_obj
+
+# ------------------------------------------------------------------------
+# Indexing test cases
+
+
+class TestFancy:
+ """pure get/set item & fancy indexing"""
+
+ def test_setitem_ndarray_1d(self):
+ # GH5508
+
+ # len of indexer vs length of the 1d ndarray
+ df = DataFrame(index=Index(np.arange(1, 11), dtype=np.int64))
+ df["foo"] = np.zeros(10, dtype=np.float64)
+ df["bar"] = np.zeros(10, dtype=complex)
+
+ # invalid
+ msg = "Must have equal len keys and value when setting with an iterable"
+ with pytest.raises(ValueError, match=msg):
+ df.loc[df.index[2:5], "bar"] = np.array([2.33j, 1.23 + 0.1j, 2.2, 1.0])
+
+ # valid
+ df.loc[df.index[2:6], "bar"] = np.array([2.33j, 1.23 + 0.1j, 2.2, 1.0])
+
+ result = df.loc[df.index[2:6], "bar"]
+ expected = Series(
+ [2.33j, 1.23 + 0.1j, 2.2, 1.0], index=[3, 4, 5, 6], name="bar"
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_setitem_ndarray_1d_2(self):
+ # GH5508
+
+ # dtype getting changed?
+ df = DataFrame(index=Index(np.arange(1, 11)))
+ df["foo"] = np.zeros(10, dtype=np.float64)
+ df["bar"] = np.zeros(10, dtype=complex)
+
+ msg = "Must have equal len keys and value when setting with an iterable"
+ with pytest.raises(ValueError, match=msg):
+ df[2:5] = np.arange(1, 4) * 1j
+
+ @pytest.mark.filterwarnings(
+ "ignore:Series.__getitem__ treating keys as positions is deprecated:"
+ "FutureWarning"
+ )
+ def test_getitem_ndarray_3d(self, index, frame_or_series, indexer_sli):
+ # GH 25567
+ obj = gen_obj(frame_or_series, index)
+ idxr = indexer_sli(obj)
+ nd3 = np.random.default_rng(2).integers(5, size=(2, 2, 2))
+
+ msgs = []
+ if frame_or_series is Series and indexer_sli in [tm.setitem, tm.iloc]:
+ msgs.append(r"Wrong number of dimensions. values.ndim > ndim \[3 > 1\]")
+ if frame_or_series is Series or indexer_sli is tm.iloc:
+ msgs.append(r"Buffer has wrong number of dimensions \(expected 1, got 3\)")
+ if indexer_sli is tm.loc or (
+ frame_or_series is Series and indexer_sli is tm.setitem
+ ):
+ msgs.append("Cannot index with multidimensional key")
+ if frame_or_series is DataFrame and indexer_sli is tm.setitem:
+ msgs.append("Index data must be 1-dimensional")
+ if isinstance(index, pd.IntervalIndex) and indexer_sli is tm.iloc:
+ msgs.append("Index data must be 1-dimensional")
+ if isinstance(index, (pd.TimedeltaIndex, pd.DatetimeIndex, pd.PeriodIndex)):
+ msgs.append("Data must be 1-dimensional")
+ if len(index) == 0 or isinstance(index, pd.MultiIndex):
+ msgs.append("positional indexers are out-of-bounds")
+ if type(index) is Index and not isinstance(index._values, np.ndarray):
+ # e.g. Int64
+ msgs.append("values must be a 1D array")
+
+ # string[pyarrow]
+ msgs.append("only handle 1-dimensional arrays")
+
+ msg = "|".join(msgs)
+
+ potential_errors = (IndexError, ValueError, NotImplementedError)
+ with pytest.raises(potential_errors, match=msg):
+ idxr[nd3]
+
+ @pytest.mark.filterwarnings(
+ "ignore:Series.__setitem__ treating keys as positions is deprecated:"
+ "FutureWarning"
+ )
+ def test_setitem_ndarray_3d(self, index, frame_or_series, indexer_sli):
+ # GH 25567
+ obj = gen_obj(frame_or_series, index)
+ idxr = indexer_sli(obj)
+ nd3 = np.random.default_rng(2).integers(5, size=(2, 2, 2))
+
+ if indexer_sli is tm.iloc:
+ err = ValueError
+ msg = f"Cannot set values with ndim > {obj.ndim}"
+ else:
+ err = ValueError
+ msg = "|".join(
+ [
+ r"Buffer has wrong number of dimensions \(expected 1, got 3\)",
+ "Cannot set values with ndim > 1",
+ "Index data must be 1-dimensional",
+ "Data must be 1-dimensional",
+ "Array conditional must be same shape as self",
+ ]
+ )
+
+ with pytest.raises(err, match=msg):
+ idxr[nd3] = 0
+
+ def test_getitem_ndarray_0d(self):
+ # GH#24924
+ key = np.array(0)
+
+ # dataframe __getitem__
+ df = DataFrame([[1, 2], [3, 4]])
+ result = df[key]
+ expected = Series([1, 3], name=0)
+ tm.assert_series_equal(result, expected)
+
+ # series __getitem__
+ ser = Series([1, 2])
+ result = ser[key]
+ assert result == 1
+
+ def test_inf_upcast(self):
+ # GH 16957
+ # We should be able to use np.inf as a key
+ # np.inf should cause an index to convert to float
+
+ # Test with np.inf in rows
+ df = DataFrame(columns=[0])
+ df.loc[1] = 1
+ df.loc[2] = 2
+ df.loc[np.inf] = 3
+
+ # make sure we can look up the value
+ assert df.loc[np.inf, 0] == 3
+
+ result = df.index
+ expected = Index([1, 2, np.inf], dtype=np.float64)
+ tm.assert_index_equal(result, expected)
+
+ def test_setitem_dtype_upcast(self):
+ # GH3216
+ df = DataFrame([{"a": 1}, {"a": 3, "b": 2}])
+ df["c"] = np.nan
+ assert df["c"].dtype == np.float64
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[0, "c"] = "foo"
+
+ @pytest.mark.parametrize("val", [3.14, "wxyz"])
+ def test_setitem_dtype_upcast2(self, val):
+ # GH10280
+ df = DataFrame(
+ np.arange(6, dtype="int64").reshape(2, 3),
+ index=list("ab"),
+ columns=["foo", "bar", "baz"],
+ )
+
+ left = df.copy()
+ with pytest.raises(TypeError, match="Invalid value"):
+ left.loc["a", "bar"] = val
+
+ def test_setitem_dtype_upcast3(self):
+ left = DataFrame(
+ np.arange(6, dtype="int64").reshape(2, 3) / 10.0,
+ index=list("ab"),
+ columns=["foo", "bar", "baz"],
+ )
+ with pytest.raises(TypeError, match="Invalid value"):
+ left.loc["a", "bar"] = "wxyz"
+
+ def test_dups_fancy_indexing(self):
+ # GH 3455
+
+ df = DataFrame(np.eye(3), columns=["a", "a", "b"])
+ result = df[["b", "a"]].columns
+ expected = Index(["b", "a", "a"])
+ tm.assert_index_equal(result, expected)
+
+ def test_dups_fancy_indexing_across_dtypes(self):
+ # across dtypes
+ df = DataFrame([[1, 2, 1.0, 2.0, 3.0, "foo", "bar"]], columns=list("aaaaaaa"))
+ result = DataFrame([[1, 2, 1.0, 2.0, 3.0, "foo", "bar"]])
+ result.columns = list("aaaaaaa") # GH#3468
+
+ # GH#3509 smoke tests for indexing with duplicate columns
+ df.iloc[:, 4]
+ result.iloc[:, 4]
+
+ tm.assert_frame_equal(df, result)
+
+ def test_dups_fancy_indexing_not_in_order(self):
+ # GH 3561, dups not in selected order
+ df = DataFrame(
+ {"test": [5, 7, 9, 11], "test1": [4.0, 5, 6, 7], "other": list("abcd")},
+ index=["A", "A", "B", "C"],
+ )
+ rows = ["C", "B"]
+ expected = DataFrame(
+ {"test": [11, 9], "test1": [7.0, 6], "other": ["d", "c"]}, index=rows
+ )
+ result = df.loc[rows]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[Index(rows)]
+ tm.assert_frame_equal(result, expected)
+
+ rows = ["C", "B", "E"]
+ with pytest.raises(KeyError, match="not in index"):
+ df.loc[rows]
+
+ # see GH5553, make sure we use the right indexer
+ rows = ["F", "G", "H", "C", "B", "E"]
+ with pytest.raises(KeyError, match="not in index"):
+ df.loc[rows]
+
+ def test_dups_fancy_indexing_only_missing_label(self, using_infer_string):
+ # List containing only missing label
+ dfnu = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 3)), index=list("AABCD")
+ )
+ if using_infer_string:
+ with pytest.raises(
+ KeyError,
+ match=re.escape(
+ "\"None of [Index(['E'], dtype='str')] are in the [index]\""
+ ),
+ ):
+ dfnu.loc[["E"]]
+ else:
+ with pytest.raises(
+ KeyError,
+ match=re.escape(
+ "\"None of [Index(['E'], dtype='object')] are in the [index]\""
+ ),
+ ):
+ dfnu.loc[["E"]]
+
+ @pytest.mark.parametrize("vals", [[0, 1, 2], list("abc")])
+ def test_dups_fancy_indexing_missing_label(self, vals):
+ # GH 4619; duplicate indexer with missing label
+ df = DataFrame({"A": vals})
+ with pytest.raises(KeyError, match="not in index"):
+ df.loc[[0, 8, 0]]
+
+ def test_dups_fancy_indexing_non_unique(self):
+ # non unique with non unique selector
+ df = DataFrame({"test": [5, 7, 9, 11]}, index=["A", "A", "B", "C"])
+ with pytest.raises(KeyError, match="not in index"):
+ df.loc[["A", "A", "E"]]
+
+ def test_dups_fancy_indexing2(self):
+ # GH 5835
+ # dups on index and missing values
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 5)),
+ columns=["A", "B", "B", "B", "A"],
+ )
+
+ with pytest.raises(KeyError, match="not in index"):
+ df.loc[:, ["A", "B", "C"]]
+
+ def test_dups_fancy_indexing3(self):
+ # GH 6504, multi-axis indexing
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((9, 2)),
+ index=[1, 1, 1, 2, 2, 2, 3, 3, 3],
+ columns=["a", "b"],
+ )
+
+ expected = df.iloc[0:6]
+ result = df.loc[[1, 2]]
+ tm.assert_frame_equal(result, expected)
+
+ expected = df
+ result = df.loc[:, ["a", "b"]]
+ tm.assert_frame_equal(result, expected)
+
+ expected = df.iloc[0:6, :]
+ result = df.loc[[1, 2], ["a", "b"]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_duplicate_int_indexing(self, indexer_sl):
+ # GH 17347
+ ser = Series(range(3), index=[1, 1, 3])
+ expected = Series(range(2), index=[1, 1])
+ result = indexer_sl(ser)[[1]]
+ tm.assert_series_equal(result, expected)
+
+ def test_indexing_mixed_frame_bug(self):
+ # GH3492
+ df = DataFrame(
+ {"a": {1: "aaa", 2: "bbb", 3: "ccc"}, "b": {1: 111, 2: 222, 3: 333}}
+ )
+
+ # this works, new column is created correctly
+ df["test"] = df["a"].apply(lambda x: "_" if x == "aaa" else x)
+
+ # this does not work, ie column test is not changed
+ idx = df["test"] == "_"
+ temp = df.loc[idx, "a"].apply(lambda x: "-----" if x == "aaa" else x)
+ df.loc[idx, "test"] = temp
+ assert df.iloc[0, 2] == "-----"
+
+ def test_multitype_list_index_access(self):
+ # GH 10610
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 5)), columns=["a", 20, 21, 22, 23]
+ )
+
+ with pytest.raises(KeyError, match=re.escape("'[26, -8] not in index'")):
+ df[[22, 26, -8]]
+ assert df[21].shape[0] == df.shape[0]
+
+ def test_set_index_nan(self):
+ # GH 3586
+ df = DataFrame(
+ {
+ "PRuid": {
+ 17: "nonQC",
+ 18: "nonQC",
+ 19: "nonQC",
+ 20: "10",
+ 21: "11",
+ 22: "12",
+ 23: "13",
+ 24: "24",
+ 25: "35",
+ 26: "46",
+ 27: "47",
+ 28: "48",
+ 29: "59",
+ 30: "10",
+ },
+ "QC": {
+ 17: 0.0,
+ 18: 0.0,
+ 19: 0.0,
+ 20: np.nan,
+ 21: np.nan,
+ 22: np.nan,
+ 23: np.nan,
+ 24: 1.0,
+ 25: np.nan,
+ 26: np.nan,
+ 27: np.nan,
+ 28: np.nan,
+ 29: np.nan,
+ 30: np.nan,
+ },
+ "data": {
+ 17: 7.9544899999999998,
+ 18: 8.0142609999999994,
+ 19: 7.8591520000000008,
+ 20: 0.86140349999999999,
+ 21: 0.87853110000000001,
+ 22: 0.8427041999999999,
+ 23: 0.78587700000000005,
+ 24: 0.73062459999999996,
+ 25: 0.81668560000000001,
+ 26: 0.81927080000000008,
+ 27: 0.80705009999999999,
+ 28: 0.81440240000000008,
+ 29: 0.80140849999999997,
+ 30: 0.81307740000000006,
+ },
+ "year": {
+ 17: 2006,
+ 18: 2007,
+ 19: 2008,
+ 20: 1985,
+ 21: 1985,
+ 22: 1985,
+ 23: 1985,
+ 24: 1985,
+ 25: 1985,
+ 26: 1985,
+ 27: 1985,
+ 28: 1985,
+ 29: 1985,
+ 30: 1986,
+ },
+ }
+ ).reset_index()
+
+ result = (
+ df.set_index(["year", "PRuid", "QC"])
+ .reset_index()
+ .reindex(columns=df.columns)
+ )
+ tm.assert_frame_equal(result, df)
+
+ def test_multi_assign(self):
+ # GH 3626, an assignment of a sub-df to a df
+ # set float64 to avoid upcast when setting nan
+ df = DataFrame(
+ {
+ "FC": ["a", "b", "a", "b", "a", "b"],
+ "PF": [0, 0, 0, 0, 1, 1],
+ "col1": list(range(6)),
+ "col2": list(range(6, 12)),
+ }
+ ).astype({"col2": "float64"})
+ df.iloc[1, 0] = np.nan
+ df2 = df.copy()
+
+ mask = ~df2.FC.isna()
+ cols = ["col1", "col2"]
+
+ dft = df2 * 2
+ dft.iloc[3, 3] = np.nan
+
+ expected = DataFrame(
+ {
+ "FC": ["a", np.nan, "a", "b", "a", "b"],
+ "PF": [0, 0, 0, 0, 1, 1],
+ "col1": Series([0, 1, 4, 6, 8, 10]),
+ "col2": [12, 7, 16, np.nan, 20, 22],
+ }
+ )
+
+ # frame on rhs
+ df2.loc[mask, cols] = dft.loc[mask, cols]
+ tm.assert_frame_equal(df2, expected)
+
+ # with an ndarray on rhs
+ # coerces to float64 because values has float64 dtype
+ # GH 14001
+ expected = DataFrame(
+ {
+ "FC": ["a", np.nan, "a", "b", "a", "b"],
+ "PF": [0, 0, 0, 0, 1, 1],
+ "col1": [0, 1, 4, 6, 8, 10],
+ "col2": [12, 7, 16, np.nan, 20, 22],
+ }
+ )
+ df2 = df.copy()
+ df2.loc[mask, cols] = dft.loc[mask, cols].values
+ tm.assert_frame_equal(df2, expected)
+
+ def test_multi_assign_broadcasting_rhs(self):
+ # broadcasting on the rhs is required
+ df = DataFrame(
+ {
+ "A": [1, 2, 0, 0, 0],
+ "B": [0, 0, 0, 10, 11],
+ "C": [0, 0, 0, 10, 11],
+ "D": [3, 4, 5, 6, 7],
+ }
+ )
+
+ expected = df.copy()
+ mask = expected["A"] == 0
+ for col in ["A", "B"]:
+ expected.loc[mask, col] = df["D"]
+
+ df.loc[df["A"] == 0, ["A", "B"]] = df["D"].copy()
+ tm.assert_frame_equal(df, expected)
+
+ def test_setitem_list(self):
+ # GH 6043
+ # iloc with a list
+ df = DataFrame(index=[0, 1], columns=[0])
+ df.iloc[1, 0] = [1, 2, 3]
+ df.iloc[1, 0] = [1, 2]
+
+ result = DataFrame(index=[0, 1], columns=[0])
+ result.iloc[1, 0] = [1, 2]
+
+ tm.assert_frame_equal(result, df)
+
+ def test_string_slice(self):
+ # GH 14424
+ # string indexing against datetimelike with object
+ # dtype should properly raises KeyError
+ df = DataFrame([1], Index([pd.Timestamp("2011-01-01")], dtype=object))
+ assert df.index._is_all_dates
+ with pytest.raises(KeyError, match="'2011'"):
+ df["2011"]
+
+ with pytest.raises(KeyError, match="'2011'"):
+ df.loc["2011", 0]
+
+ def test_string_slice_empty(self):
+ # GH 14424
+
+ df = DataFrame()
+ assert not df.index._is_all_dates
+ with pytest.raises(KeyError, match="'2011'"):
+ df["2011"]
+
+ with pytest.raises(KeyError, match="^0$"):
+ df.loc["2011", 0]
+
+ def test_astype_assignment(self, using_infer_string):
+ # GH4312 (iloc)
+ df_orig = DataFrame(
+ [["1", "2", "3", ".4", 5, 6.0, "foo"]], columns=list("ABCDEFG")
+ )
+ df_orig[list("ABCDG")] = df_orig[list("ABCDG")].astype(object)
+
+ df = df_orig.copy()
+
+ # with the enforcement of GH#45333 in 2.0, this setting is attempted inplace,
+ # so object dtype is retained
+ df.iloc[:, 0:2] = df.iloc[:, 0:2].astype(np.int64)
+ expected = DataFrame(
+ [[1, 2, "3", ".4", 5, 6.0, "foo"]], columns=list("ABCDEFG")
+ )
+ expected[list("CDG")] = expected[list("CDG")].astype(object)
+ expected["A"] = expected["A"].astype(object)
+ expected["B"] = expected["B"].astype(object)
+ tm.assert_frame_equal(df, expected)
+
+ # GH5702 (loc)
+ df = df_orig.copy()
+ df.loc[:, "A"] = df.loc[:, "A"].astype(np.int64)
+ expected = DataFrame(
+ [[1, "2", "3", ".4", 5, 6.0, "foo"]], columns=list("ABCDEFG")
+ )
+ expected[list("ABCDG")] = expected[list("ABCDG")].astype(object)
+ tm.assert_frame_equal(df, expected)
+
+ df = df_orig.copy()
+
+ df.loc[:, ["B", "C"]] = df.loc[:, ["B", "C"]].astype(np.int64)
+ expected = DataFrame(
+ [["1", 2, 3, ".4", 5, 6.0, "foo"]], columns=list("ABCDEFG")
+ )
+ expected[list("ABCDG")] = expected[list("ABCDG")].astype(object)
+ tm.assert_frame_equal(df, expected)
+
+ def test_astype_assignment_full_replacements(self):
+ # full replacements / no nans
+ df = DataFrame({"A": [1.0, 2.0, 3.0, 4.0]})
+
+ # With the enforcement of GH#45333 in 2.0, this assignment occurs inplace,
+ # so float64 is retained
+ df.iloc[:, 0] = df["A"].astype(np.int64)
+ expected = DataFrame({"A": [1.0, 2.0, 3.0, 4.0]})
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame({"A": [1.0, 2.0, 3.0, 4.0]})
+ df.loc[:, "A"] = df["A"].astype(np.int64)
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("indexer", [tm.getitem, tm.loc])
+ def test_index_type_coercion(self, indexer):
+ # GH 11836
+ # if we have an index type and set it with something that looks
+ # to numpy like the same, but is actually, not
+ # (e.g. setting with a float or string '0')
+ # then we need to coerce to object
+
+ # integer indexes
+ for s in [Series(range(5)), Series(range(5), index=range(1, 6))]:
+ assert is_integer_dtype(s.index)
+
+ s2 = s.copy()
+ indexer(s2)[0.1] = 0
+ assert is_float_dtype(s2.index)
+ assert indexer(s2)[0.1] == 0
+
+ s2 = s.copy()
+ indexer(s2)[0.0] = 0
+ exp = s.index
+ if 0 not in s:
+ exp = Index([*s.index.tolist(), 0])
+ tm.assert_index_equal(s2.index, exp)
+
+ s2 = s.copy()
+ indexer(s2)["0"] = 0
+ assert is_object_dtype(s2.index)
+
+ for s in [Series(range(5), index=np.arange(5.0))]:
+ assert is_float_dtype(s.index)
+
+ s2 = s.copy()
+ indexer(s2)[0.1] = 0
+ assert is_float_dtype(s2.index)
+ assert indexer(s2)[0.1] == 0
+
+ s2 = s.copy()
+ indexer(s2)[0.0] = 0
+ tm.assert_index_equal(s2.index, s.index)
+
+ s2 = s.copy()
+ indexer(s2)["0"] = 0
+ assert is_object_dtype(s2.index)
+
+
+class TestMisc:
+ def test_float_index_to_mixed(self):
+ df = DataFrame(
+ {
+ 0.0: np.random.default_rng(2).random(10),
+ 1.0: np.random.default_rng(2).random(10),
+ }
+ )
+ df["a"] = 10
+
+ expected = DataFrame({0.0: df[0.0], 1.0: df[1.0], "a": [10] * 10})
+ tm.assert_frame_equal(expected, df)
+
+ def test_float_index_non_scalar_assignment(self):
+ df = DataFrame({"a": [1, 2, 3], "b": [3, 4, 5]}, index=[1.0, 2.0, 3.0])
+ df.loc[df.index[:2]] = 1
+ expected = DataFrame({"a": [1, 1, 3], "b": [1, 1, 5]}, index=df.index)
+ tm.assert_frame_equal(expected, df)
+
+ def test_loc_setitem_fullindex_views(self):
+ df = DataFrame({"a": [1, 2, 3], "b": [3, 4, 5]}, index=[1.0, 2.0, 3.0])
+ df2 = df.copy()
+ df.loc[df.index] = df.loc[df.index]
+ tm.assert_frame_equal(df, df2)
+
+ def test_rhs_alignment(self):
+ # GH8258, tests that both rows & columns are aligned to what is
+ # assigned to. covers both uniform data-type & multi-type cases
+ def run_tests(df, rhs, right_loc, right_iloc):
+ # label, index, slice
+ lbl_one, idx_one, slice_one = list("bcd"), [1, 2, 3], slice(1, 4)
+ lbl_two, idx_two, slice_two = ["joe", "jolie"], [1, 2], slice(1, 3)
+
+ left = df.copy()
+ left.loc[lbl_one, lbl_two] = rhs
+ tm.assert_frame_equal(left, right_loc)
+
+ left = df.copy()
+ left.iloc[idx_one, idx_two] = rhs
+ tm.assert_frame_equal(left, right_iloc)
+
+ left = df.copy()
+ left.iloc[slice_one, slice_two] = rhs
+ tm.assert_frame_equal(left, right_iloc)
+
+ xs = np.arange(20).reshape(5, 4)
+ cols = ["jim", "joe", "jolie", "joline"]
+ df = DataFrame(xs, columns=cols, index=list("abcde"), dtype="int64")
+
+ # right hand side; permute the indices and multiply by -2
+ rhs = -2 * df.iloc[3:0:-1, 2:0:-1]
+
+ # expected `right` result; just multiply by -2
+ right_iloc = df.copy()
+ right_iloc["joe"] = [1, 14, 10, 6, 17]
+ right_iloc["jolie"] = [2, 13, 9, 5, 18]
+ right_iloc.iloc[1:4, 1:3] *= -2
+ right_loc = df.copy()
+ right_loc.iloc[1:4, 1:3] *= -2
+
+ # run tests with uniform dtypes
+ run_tests(df, rhs, right_loc, right_iloc)
+
+ # make frames multi-type & re-run tests
+ for frame in [df, rhs, right_loc, right_iloc]:
+ frame["joe"] = frame["joe"].astype("float64")
+ frame["jolie"] = frame["jolie"].map(lambda x: f"@{x}")
+ right_iloc["joe"] = [1.0, "@-28", "@-20", "@-12", 17.0]
+ right_iloc["jolie"] = ["@2", -26.0, -18.0, -10.0, "@18"]
+ with pytest.raises(TypeError, match="Invalid value"):
+ run_tests(df, rhs, right_loc, right_iloc)
+
+ @pytest.mark.parametrize(
+ "idx", [_mklbl("A", 20), np.arange(20) + 100, np.linspace(100, 150, 20)]
+ )
+ def test_str_label_slicing_with_negative_step(self, idx):
+ SLC = pd.IndexSlice
+
+ idx = Index(idx)
+ ser = Series(np.arange(20), index=idx)
+ tm.assert_indexing_slices_equivalent(ser, SLC[idx[9] :: -1], SLC[9::-1])
+ tm.assert_indexing_slices_equivalent(ser, SLC[: idx[9] : -1], SLC[:8:-1])
+ tm.assert_indexing_slices_equivalent(
+ ser, SLC[idx[13] : idx[9] : -1], SLC[13:8:-1]
+ )
+ tm.assert_indexing_slices_equivalent(ser, SLC[idx[9] : idx[13] : -1], SLC[:0])
+
+ def test_slice_with_zero_step_raises(self, index, indexer_sl, frame_or_series):
+ obj = frame_or_series(np.arange(len(index)), index=index)
+ with pytest.raises(ValueError, match="slice step cannot be zero"):
+ indexer_sl(obj)[::0]
+
+ def test_loc_setitem_indexing_assignment_dict_already_exists(self):
+ index = Index([-5, 0, 5], name="z")
+ df = DataFrame({"x": [1, 2, 6], "y": [2, 2, 8]}, index=index)
+ expected = df.copy()
+ rhs = {"x": 9, "y": 99}
+ df.loc[5] = rhs
+ expected.loc[5] = [9, 99]
+ tm.assert_frame_equal(df, expected)
+
+ # GH#38335 same thing, mixed dtypes
+ df = DataFrame({"x": [1, 2, 6], "y": [2.0, 2.0, 8.0]}, index=index)
+ df.loc[5] = rhs
+ expected = DataFrame({"x": [1, 2, 9], "y": [2.0, 2.0, 99.0]}, index=index)
+ tm.assert_frame_equal(df, expected)
+
+ def test_iloc_getitem_indexing_dtypes_on_empty(self):
+ # Check that .iloc returns correct dtypes GH9983
+ df = DataFrame({"a": [1, 2, 3], "b": ["b", "b2", "b3"]})
+ df2 = df.iloc[[], :]
+
+ assert df2.loc[:, "a"].dtype == np.int64
+ tm.assert_series_equal(df2.loc[:, "a"], df2.iloc[:, 0])
+
+ @pytest.mark.parametrize("size", [5, 999999, 1000000])
+ def test_loc_range_in_series_indexing(self, size):
+ # range can cause an indexing error
+ # GH 11652
+ s = Series(index=range(size), dtype=np.float64)
+ s.loc[range(1)] = 42
+ tm.assert_series_equal(s.loc[range(1)], Series(42.0, index=range(1)))
+
+ s.loc[range(2)] = 43
+ tm.assert_series_equal(s.loc[range(2)], Series(43.0, index=range(2)))
+
+ def test_partial_boolean_frame_indexing(self):
+ # GH 17170
+ df = DataFrame(
+ np.arange(9.0).reshape(3, 3), index=list("abc"), columns=list("ABC")
+ )
+ index_df = DataFrame(1, index=list("ab"), columns=list("AB"))
+ result = df[index_df.notnull()]
+ expected = DataFrame(
+ np.array([[0.0, 1.0, np.nan], [3.0, 4.0, np.nan], [np.nan] * 3]),
+ index=list("abc"),
+ columns=list("ABC"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_period_column_slicing(self):
+ # GH#60273 The transpose operation creates a single 5x1 block of PeriodDtype
+ # Make sure it is reindexed correctly
+ df = DataFrame(
+ pd.period_range("2021-01-01", periods=5, freq="D"),
+ columns=["A"],
+ ).T
+ result = df[[0, 1, 2]]
+ expected = DataFrame(
+ [
+ [
+ pd.Period("2021-01-01", freq="D"),
+ pd.Period("2021-01-02", freq="D"),
+ pd.Period("2021-01-03", freq="D"),
+ ]
+ ],
+ index=["A"],
+ columns=[0, 1, 2],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_no_reference_cycle(self):
+ df = DataFrame({"a": [0, 1], "b": [2, 3]})
+ for name in ("loc", "iloc", "at", "iat"):
+ getattr(df, name)
+ wr = weakref.ref(df)
+ del df
+ assert wr() is None
+
+ def test_label_indexing_on_nan(self, nulls_fixture):
+ # GH 32431
+ df = Series([1, "{1,2}", 1, nulls_fixture])
+ vc = df.value_counts(dropna=False)
+ result1 = vc.loc[nulls_fixture]
+ result2 = vc[nulls_fixture]
+
+ expected = 1
+ assert result1 == expected
+ assert result2 == expected
+
+
+class TestDataframeNoneCoercion:
+ EXPECTED_SINGLE_ROW_RESULTS = [
+ # For numeric series, we should coerce to NaN.
+ ([1, 2, 3], [np.nan, 2, 3], FutureWarning),
+ ([1.0, 2.0, 3.0], [np.nan, 2.0, 3.0], None),
+ # For datetime series, we should coerce to NaT.
+ (
+ [datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)],
+ [NaT, datetime(2000, 1, 2), datetime(2000, 1, 3)],
+ None,
+ ),
+ # For objects, we should preserve the None value.
+ (["foo", "bar", "baz"], [None, "bar", "baz"], None),
+ ]
+
+ @pytest.mark.parametrize("expected", EXPECTED_SINGLE_ROW_RESULTS)
+ def test_coercion_with_loc(self, expected):
+ start_data, expected_result, warn = expected
+
+ start_dataframe = DataFrame({"foo": start_data})
+ start_dataframe.loc[0, ["foo"]] = None
+
+ expected_dataframe = DataFrame({"foo": expected_result})
+ tm.assert_frame_equal(start_dataframe, expected_dataframe)
+
+ @pytest.mark.parametrize("expected", EXPECTED_SINGLE_ROW_RESULTS)
+ def test_coercion_with_setitem_and_dataframe(self, expected):
+ start_data, expected_result, warn = expected
+
+ start_dataframe = DataFrame({"foo": start_data})
+ start_dataframe[start_dataframe["foo"] == start_dataframe["foo"][0]] = None
+
+ expected_dataframe = DataFrame({"foo": expected_result})
+ tm.assert_frame_equal(start_dataframe, expected_dataframe)
+
+ @pytest.mark.parametrize("expected", EXPECTED_SINGLE_ROW_RESULTS)
+ def test_none_coercion_loc_and_dataframe(self, expected):
+ start_data, expected_result, warn = expected
+
+ start_dataframe = DataFrame({"foo": start_data})
+ start_dataframe.loc[start_dataframe["foo"] == start_dataframe["foo"][0]] = None
+
+ expected_dataframe = DataFrame({"foo": expected_result})
+ tm.assert_frame_equal(start_dataframe, expected_dataframe)
+
+ def test_none_coercion_mixed_dtypes(self):
+ start_dataframe = DataFrame(
+ {
+ "a": [1, 2, 3],
+ "b": [1.0, 2.0, 3.0],
+ "c": [datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)],
+ "d": ["a", "b", "c"],
+ }
+ )
+ start_dataframe.iloc[0] = None
+
+ exp = DataFrame(
+ {
+ "a": [np.nan, 2, 3],
+ "b": [np.nan, 2.0, 3.0],
+ "c": [NaT, datetime(2000, 1, 2), datetime(2000, 1, 3)],
+ "d": [None, "b", "c"],
+ }
+ )
+ tm.assert_frame_equal(start_dataframe, exp)
+
+
+class TestDatetimelikeCoercion:
+ def test_setitem_dt64_string_scalar(self, tz_naive_fixture, indexer_sli):
+ # dispatching _can_hold_element to underlying DatetimeArray
+ tz = tz_naive_fixture
+
+ dti = date_range("2016-01-01", periods=3, tz=tz)
+ ser = Series(dti.copy(deep=True))
+
+ values = ser._values
+
+ newval = "2018-01-01"
+ values._validate_setitem_value(newval)
+
+ indexer_sli(ser)[0] = newval
+
+ if tz is None:
+ # TODO(EA2D): we can make this no-copy in tz-naive case too
+ assert ser.dtype == dti.dtype
+ assert ser._values._ndarray is values._ndarray
+ else:
+ assert ser._values is values
+
+ @pytest.mark.parametrize("box", [list, np.array, pd.array, pd.Categorical, Index])
+ @pytest.mark.parametrize(
+ "key", [[0, 1], slice(0, 2), np.array([True, True, False])]
+ )
+ def test_setitem_dt64_string_values(self, tz_naive_fixture, indexer_sli, key, box):
+ # dispatching _can_hold_element to underling DatetimeArray
+ tz = tz_naive_fixture
+
+ if isinstance(key, slice) and indexer_sli is tm.loc:
+ key = slice(0, 1)
+
+ dti = date_range("2016-01-01", periods=3, tz=tz)
+ ser = Series(dti.copy(deep=True))
+
+ values = ser._values
+
+ newvals = box(["2019-01-01", "2010-01-02"])
+ values._validate_setitem_value(newvals)
+
+ indexer_sli(ser)[key] = newvals
+
+ if tz is None:
+ # TODO(EA2D): we can make this no-copy in tz-naive case too
+ assert ser.dtype == dti.dtype
+ assert ser._values._ndarray is values._ndarray
+ else:
+ assert ser._values is values
+
+ @pytest.mark.parametrize("scalar", ["3 Days", offsets.Hour(4)])
+ def test_setitem_td64_scalar(self, indexer_sli, scalar):
+ # dispatching _can_hold_element to underling TimedeltaArray
+ tdi = timedelta_range("1 Day", periods=3)
+ ser = Series(tdi.copy(deep=True))
+
+ values = ser._values
+ values._validate_setitem_value(scalar)
+
+ indexer_sli(ser)[0] = scalar
+ assert ser._values._ndarray is values._ndarray
+
+ @pytest.mark.parametrize("box", [list, np.array, pd.array, pd.Categorical, Index])
+ @pytest.mark.parametrize(
+ "key", [[0, 1], slice(0, 2), np.array([True, True, False])]
+ )
+ def test_setitem_td64_string_values(self, indexer_sli, key, box):
+ # dispatching _can_hold_element to underling TimedeltaArray
+ if isinstance(key, slice) and indexer_sli is tm.loc:
+ key = slice(0, 1)
+
+ tdi = timedelta_range("1 Day", periods=3)
+ ser = Series(tdi.copy(deep=True))
+
+ values = ser._values
+
+ newvals = box(["10 Days", "44 hours"])
+ values._validate_setitem_value(newvals)
+
+ indexer_sli(ser)[key] = newvals
+ assert ser._values._ndarray is values._ndarray
+
+
+def test_extension_array_cross_section():
+ # A cross-section of a homogeneous EA should be an EA
+ df = DataFrame(
+ {
+ "A": pd.array([1, 2], dtype="Int64"),
+ "B": pd.array([3, 4], dtype="Int64"),
+ },
+ index=["a", "b"],
+ )
+ expected = Series(pd.array([1, 3], dtype="Int64"), index=["A", "B"], name="a")
+ result = df.loc["a"]
+ tm.assert_series_equal(result, expected)
+
+ result = df.iloc[0]
+ tm.assert_series_equal(result, expected)
+
+
+def test_extension_array_cross_section_converts():
+ # all numeric columns -> numeric series
+ df = DataFrame(
+ {
+ "A": pd.array([1, 2], dtype="Int64"),
+ "B": np.array([1, 2], dtype="int64"),
+ },
+ index=["a", "b"],
+ )
+ result = df.loc["a"]
+ expected = Series([1, 1], dtype="Int64", index=["A", "B"], name="a")
+ tm.assert_series_equal(result, expected)
+
+ result = df.iloc[0]
+ tm.assert_series_equal(result, expected)
+
+ # mixed columns -> object series
+ df = DataFrame(
+ {"A": pd.array([1, 2], dtype="Int64"), "B": np.array(["a", "b"])},
+ index=["a", "b"],
+ )
+ result = df.loc["a"]
+ expected = Series([1, "a"], dtype=object, index=["A", "B"], name="a")
+ tm.assert_series_equal(result, expected)
+
+ result = df.iloc[0]
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ser, keys",
+ [(Series([10]), (0, 0)), (Series([1, 2, 3], index=list("abc")), (0, 1))],
+)
+def test_ser_tup_indexer_exceeds_dimensions(ser, keys, indexer_li):
+ # GH#13831
+ exp_err, exp_msg = IndexingError, "Too many indexers"
+ with pytest.raises(exp_err, match=exp_msg):
+ indexer_li(ser)[keys]
+
+ if indexer_li == tm.iloc:
+ # For iloc.__setitem__ we let numpy handle the error reporting.
+ exp_err, exp_msg = IndexError, "too many indices for array"
+
+ with pytest.raises(exp_err, match=exp_msg):
+ indexer_li(ser)[keys] = 0
+
+
+def test_ser_list_indexer_exceeds_dimensions(indexer_li):
+ # GH#13831
+ # Make sure an exception is raised when a tuple exceeds the dimension of the series,
+ # but not list when a list is used.
+ ser = Series([10])
+ res = indexer_li(ser)[[0, 0]]
+ exp = Series([10, 10], index=Index([0, 0]))
+ tm.assert_series_equal(res, exp)
+
+
+@pytest.mark.parametrize(
+ "value", [(0, 1), [0, 1], np.array([0, 1]), array.array("b", [0, 1])]
+)
+def test_scalar_setitem_with_nested_value(value):
+ # For numeric data, we try to unpack and thus raise for mismatching length
+ df = DataFrame({"A": [1, 2, 3]})
+ msg = "|".join(
+ [
+ "Must have equal len keys and value",
+ "setting an array element with a sequence",
+ ]
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.loc[0, "B"] = value
+
+ # TODO For object dtype this happens as well, but should we rather preserve
+ # the nested data and set as such?
+ df = DataFrame({"A": [1, 2, 3], "B": np.array([1, "a", "b"], dtype=object)})
+ with pytest.raises(ValueError, match="Must have equal len keys and value"):
+ df.loc[0, "B"] = value
+ # if isinstance(value, np.ndarray):
+ # assert (df.loc[0, "B"] == value).all()
+ # else:
+ # assert df.loc[0, "B"] == value
+
+
+@pytest.mark.parametrize(
+ "value", [(0, 1), [0, 1], np.array([0, 1]), array.array("b", [0, 1])]
+)
+def test_scalar_setitem_series_with_nested_value(value, indexer_sli):
+ # For numeric data, we try to unpack and thus raise for mismatching length
+ ser = Series([1, 2, 3])
+ with pytest.raises(ValueError, match="setting an array element with a sequence"):
+ indexer_sli(ser)[0] = value
+
+ # but for object dtype we preserve the nested data and set as such
+ ser = Series([1, "a", "b"], dtype=object)
+ indexer_sli(ser)[0] = value
+ if isinstance(value, np.ndarray):
+ assert (ser.loc[0] == value).all()
+ else:
+ assert ser.loc[0] == value
+
+
+@pytest.mark.parametrize(
+ "value", [(0.0,), [0.0], np.array([0.0]), array.array("d", [0.0])]
+)
+def test_scalar_setitem_with_nested_value_length1(value):
+ # https://github.com/pandas-dev/pandas/issues/46268
+
+ # For numeric data, assigning length-1 array to scalar position gets unpacked
+ df = DataFrame({"A": [1, 2, 3]})
+ df.loc[0, "B"] = value
+ expected = DataFrame({"A": [1, 2, 3], "B": [0.0, np.nan, np.nan]})
+ tm.assert_frame_equal(df, expected)
+
+ # but for object dtype we preserve the nested data
+ df = DataFrame({"A": [1, 2, 3], "B": np.array([1, "a", "b"], dtype=object)})
+ df.loc[0, "B"] = value
+ if isinstance(value, np.ndarray):
+ assert (df.loc[0, "B"] == value).all()
+ else:
+ assert df.loc[0, "B"] == value
+
+
+@pytest.mark.parametrize(
+ "value", [(0.0,), [0.0], np.array([0.0]), array.array("d", [0.0])]
+)
+def test_scalar_setitem_series_with_nested_value_length1(value, indexer_sli):
+ # For numeric data, assigning length-1 array to scalar position gets unpacked
+ # TODO this only happens in case of ndarray, should we make this consistent
+ # for all list-likes? (as happens for DataFrame.(i)loc, see test above)
+ ser = Series([1.0, 2.0, 3.0])
+ if isinstance(value, np.ndarray):
+ indexer_sli(ser)[0] = value
+ expected = Series([0.0, 2.0, 3.0])
+ tm.assert_series_equal(ser, expected)
+ else:
+ with pytest.raises(
+ ValueError, match="setting an array element with a sequence"
+ ):
+ indexer_sli(ser)[0] = value
+
+ # but for object dtype we preserve the nested data
+ ser = Series([1, "a", "b"], dtype=object)
+ indexer_sli(ser)[0] = value
+ if isinstance(value, np.ndarray):
+ assert (ser.loc[0] == value).all()
+ else:
+ assert ser.loc[0] == value
+
+
+def test_object_dtype_series_set_series_element():
+ # GH 48933
+ s1 = Series(dtype="O", index=["a", "b"])
+
+ s1["a"] = Series()
+ s1.loc["b"] = Series()
+
+ tm.assert_series_equal(s1.loc["a"], Series())
+ tm.assert_series_equal(s1.loc["b"], Series())
+
+ s2 = Series(dtype="O", index=["a", "b"])
+
+ s2.iloc[1] = Series()
+ tm.assert_series_equal(s2.iloc[1], Series())
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_loc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_loc.py
new file mode 100644
index 0000000000000000000000000000000000000000..9559b610452a57c49cf3d72cf708c001cbc9614c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_loc.py
@@ -0,0 +1,3480 @@
+"""test label based indexing with loc"""
+
+from collections import namedtuple
+import contextlib
+from datetime import (
+ date,
+ datetime,
+ time,
+ timedelta,
+)
+import re
+
+from dateutil.tz import gettz
+import numpy as np
+import pytest
+
+from pandas._libs import index as libindex
+from pandas.errors import IndexingError
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ CategoricalDtype,
+ CategoricalIndex,
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ IndexSlice,
+ MultiIndex,
+ Period,
+ PeriodIndex,
+ Series,
+ SparseDtype,
+ Timedelta,
+ Timestamp,
+ date_range,
+ timedelta_range,
+ to_datetime,
+ to_timedelta,
+)
+import pandas._testing as tm
+from pandas.api.types import is_scalar
+from pandas.core.indexing import _one_ellipsis_message
+from pandas.tests.indexing.common import check_indexing_smoketest_or_raises
+
+
+@pytest.mark.parametrize(
+ "series, new_series, expected_ser",
+ [
+ [[np.nan, np.nan, "b"], ["a", np.nan, np.nan], [False, True, True]],
+ [[np.nan, "b"], ["a", np.nan], [False, True]],
+ ],
+)
+def test_not_change_nan_loc(series, new_series, expected_ser):
+ # GH 28403
+ df = DataFrame({"A": series})
+ df.loc[:, "A"] = new_series
+ expected = DataFrame({"A": expected_ser})
+ tm.assert_frame_equal(df.isna(), expected)
+ tm.assert_frame_equal(df.notna(), ~expected)
+
+
+def test_loc_dtype():
+ # GH 60600
+ df = DataFrame([["a", 1.0, 2.0], ["b", 3.0, 4.0]])
+ result = df.loc[0, [1, 2]]
+ expected = Series([1.0, 2.0], index=[1, 2], dtype=float, name=0)
+ tm.assert_series_equal(result, expected)
+
+
+class TestLoc:
+ def test_none_values_on_string_columns(self, using_infer_string):
+ # Issue #32218
+ df = DataFrame(["1", "2", None], columns=["a"], dtype=object)
+ assert df.loc[2, "a"] is None
+
+ df = DataFrame(["1", "2", None], columns=["a"], dtype="str")
+ if using_infer_string:
+ assert np.isnan(df.loc[2, "a"])
+ else:
+ assert df.loc[2, "a"] is None
+
+ def test_loc_getitem_int(self, frame_or_series):
+ # int label
+ obj = frame_or_series(range(3), index=Index(list("abc"), dtype=object))
+ check_indexing_smoketest_or_raises(obj, "loc", 2, fails=KeyError)
+
+ def test_loc_getitem_label(self, frame_or_series):
+ # label
+ obj = frame_or_series()
+ check_indexing_smoketest_or_raises(obj, "loc", "c", fails=KeyError)
+
+ @pytest.mark.parametrize("key", ["f", 20])
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(list("abcd"), dtype=object),
+ Index([2, 4, "null", 8], dtype=object),
+ date_range("20130101", periods=4),
+ Index(range(0, 8, 2), dtype=np.float64),
+ Index([]),
+ ],
+ )
+ def test_loc_getitem_label_out_of_range(self, key, index, frame_or_series):
+ obj = frame_or_series(range(len(index)), index=index)
+ # out of range label
+ check_indexing_smoketest_or_raises(obj, "loc", key, fails=KeyError)
+
+ @pytest.mark.parametrize("key", [[0, 1, 2], [1, 3.0, "A"]])
+ @pytest.mark.parametrize("dtype", [np.int64, np.uint64, np.float64])
+ def test_loc_getitem_label_list(self, key, dtype, frame_or_series):
+ obj = frame_or_series(range(3), index=Index([0, 1, 2], dtype=dtype))
+ # list of labels
+ check_indexing_smoketest_or_raises(obj, "loc", key, fails=KeyError)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ None,
+ Index([0, 1, 2], dtype=np.int64),
+ Index([0, 1, 2], dtype=np.uint64),
+ Index([0, 1, 2], dtype=np.float64),
+ MultiIndex.from_arrays([range(3), range(3)]),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "key", [[0, 1, 2], [0, 2, 10], [3, 6, 7], [(1, 3), (1, 4), (2, 5)]]
+ )
+ def test_loc_getitem_label_list_with_missing(self, key, index, frame_or_series):
+ if index is None:
+ obj = frame_or_series()
+ else:
+ obj = frame_or_series(range(len(index)), index=index)
+ check_indexing_smoketest_or_raises(obj, "loc", key, fails=KeyError)
+
+ @pytest.mark.parametrize("dtype", [np.int64, np.uint64])
+ def test_loc_getitem_label_list_fails(self, dtype, frame_or_series):
+ # fails
+ obj = frame_or_series(range(3), Index([0, 1, 2], dtype=dtype))
+ check_indexing_smoketest_or_raises(
+ obj, "loc", [20, 30, 40], axes=1, fails=KeyError
+ )
+
+ def test_loc_getitem_bool(self, frame_or_series):
+ obj = frame_or_series()
+ # boolean indexers
+ b = [True, False, True, False]
+
+ check_indexing_smoketest_or_raises(obj, "loc", b, fails=IndexError)
+
+ @pytest.mark.parametrize(
+ "slc, indexes, axes, fails",
+ [
+ [
+ slice(1, 3),
+ [
+ Index(list("abcd"), dtype=object),
+ Index([2, 4, "null", 8], dtype=object),
+ None,
+ date_range("20130101", periods=4),
+ Index(range(0, 12, 3), dtype=np.float64),
+ ],
+ None,
+ TypeError,
+ ],
+ [
+ slice("20130102", "20130104"),
+ [date_range("20130101", periods=4)],
+ 1,
+ TypeError,
+ ],
+ [slice(2, 8), [Index([2, 4, "null", 8], dtype=object)], 0, TypeError],
+ [slice(2, 8), [Index([2, 4, "null", 8], dtype=object)], 1, KeyError],
+ [slice(2, 4, 2), [Index([2, 4, "null", 8], dtype=object)], 0, TypeError],
+ ],
+ )
+ def test_loc_getitem_label_slice(self, slc, indexes, axes, fails, frame_or_series):
+ # label slices (with ints)
+
+ # real label slices
+
+ # GH 14316
+ for index in indexes:
+ if index is None:
+ obj = frame_or_series()
+ else:
+ obj = frame_or_series(range(len(index)), index=index)
+ check_indexing_smoketest_or_raises(
+ obj,
+ "loc",
+ slc,
+ axes=axes,
+ fails=fails,
+ )
+
+ def test_setitem_from_duplicate_axis(self):
+ # GH#34034
+ df = DataFrame(
+ [[20, "a"], [200, "a"], [200, "a"]],
+ columns=["col1", "col2"],
+ index=[10, 1, 1],
+ )
+ df.loc[1, "col1"] = np.arange(2)
+ expected = DataFrame(
+ [[20, "a"], [0, "a"], [1, "a"]], columns=["col1", "col2"], index=[10, 1, 1]
+ )
+ tm.assert_frame_equal(df, expected)
+
+ def test_column_types_consistent(self):
+ # GH 26779
+ df = DataFrame(
+ data={
+ "channel": [1, 2, 3],
+ "A": ["String 1", np.nan, "String 2"],
+ "B": [
+ Timestamp("2019-06-11 11:00:00"),
+ pd.NaT,
+ Timestamp("2019-06-11 12:00:00"),
+ ],
+ }
+ )
+ df2 = DataFrame(
+ data={"A": ["String 3"], "B": [Timestamp("2019-06-11 12:00:00")]}
+ )
+ # Change Columns A and B to df2.values wherever Column A is NaN
+ df.loc[df["A"].isna(), ["A", "B"]] = df2.values
+ expected = DataFrame(
+ data={
+ "channel": [1, 2, 3],
+ "A": ["String 1", "String 3", "String 2"],
+ "B": [
+ Timestamp("2019-06-11 11:00:00"),
+ Timestamp("2019-06-11 12:00:00"),
+ Timestamp("2019-06-11 12:00:00"),
+ ],
+ }
+ )
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize(
+ "obj, key, exp",
+ [
+ (
+ DataFrame([[1]], columns=Index([False])),
+ IndexSlice[:, False],
+ Series([1], name=False),
+ ),
+ (Series([1], index=Index([False])), False, [1]),
+ (DataFrame([[1]], index=Index([False])), False, Series([1], name=False)),
+ ],
+ )
+ def test_loc_getitem_single_boolean_arg(self, obj, key, exp):
+ # GH 44322
+ res = obj.loc[key]
+ if isinstance(exp, (DataFrame, Series)):
+ tm.assert_equal(res, exp)
+ else:
+ assert res == exp
+
+ @pytest.mark.parametrize(
+ "data",
+ [
+ [datetime(2025, 10, 30)],
+ [Timestamp(2025, 10, 30)] * 2,
+ [Timedelta(1)],
+ [Timedelta(1), Timedelta(2)],
+ ],
+ )
+ def test_loc_empty_slice_assignment_with_datetime(self, data):
+ # issue #50942
+ # empty slice assignment with datetime or timedelta should not raise exceptions
+ mask = [False] * len(data)
+
+ df = DataFrame(data=data, columns=["A"])
+ expected = df.copy()
+ df.loc[mask] = df
+ tm.assert_frame_equal(df, expected)
+
+
+class TestLocBaseIndependent:
+ # Tests for loc that do not depend on subclassing Base
+ def test_loc_npstr(self):
+ # GH#45580
+ df = DataFrame(index=date_range("2021", "2022"))
+ result = df.loc[np.array(["2021/6/1"])[0] :]
+ expected = df.iloc[151:]
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "msg, key",
+ [
+ (r"Period\('2019', 'Y-DEC'\), 'foo', 'bar'", (Period(2019), "foo", "bar")),
+ (r"Period\('2019', 'Y-DEC'\), 'y1', 'bar'", (Period(2019), "y1", "bar")),
+ (r"Period\('2019', 'Y-DEC'\), 'foo', 'z1'", (Period(2019), "foo", "z1")),
+ (
+ r"Period\('2018', 'Y-DEC'\), Period\('2016', 'Y-DEC'\), 'bar'",
+ (Period(2018), Period(2016), "bar"),
+ ),
+ (r"Period\('2018', 'Y-DEC'\), 'foo', 'y1'", (Period(2018), "foo", "y1")),
+ (
+ r"Period\('2017', 'Y-DEC'\), 'foo', Period\('2015', 'Y-DEC'\)",
+ (Period(2017), "foo", Period(2015)),
+ ),
+ (r"Period\('2017', 'Y-DEC'\), 'z1', 'bar'", (Period(2017), "z1", "bar")),
+ ],
+ )
+ def test_contains_raise_error_if_period_index_is_in_multi_index(self, msg, key):
+ # GH#20684
+ """
+ parse_datetime_string_with_reso return parameter if type not matched.
+ PeriodIndex.get_loc takes returned value from parse_datetime_string_with_reso
+ as a tuple.
+ If first argument is Period and a tuple has 3 items,
+ process go on not raise exception
+ """
+ df = DataFrame(
+ {
+ "A": [Period(2019), "x1", "x2"],
+ "B": [Period(2018), Period(2016), "y1"],
+ "C": [Period(2017), "z1", Period(2015)],
+ "V1": [1, 2, 3],
+ "V2": [10, 20, 30],
+ }
+ ).set_index(["A", "B", "C"])
+ with pytest.raises(KeyError, match=msg):
+ df.loc[key]
+
+ def test_loc_getitem_missing_unicode_key(self):
+ df = DataFrame({"a": [1]})
+ with pytest.raises(KeyError, match="\u05d0"):
+ df.loc[:, "\u05d0"] # should not raise UnicodeEncodeError
+
+ def test_loc_getitem_dups(self):
+ # GH 5678
+ # repeated getitems on a dup index returning an ndarray
+ df = DataFrame(
+ np.random.default_rng(2).random((20, 5)),
+ index=["ABCDE"[x % 5] for x in range(20)],
+ )
+ expected = df.loc["A", 0]
+ result = df.loc[:, 0].loc["A"]
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_getitem_dups2(self):
+ # GH4726
+ # dup indexing with iloc/loc
+ df = DataFrame(
+ [[1, 2, "foo", "bar", Timestamp("20130101")]],
+ columns=["a", "a", "a", "a", "a"],
+ index=[1],
+ )
+ expected = Series(
+ [1, 2, "foo", "bar", Timestamp("20130101")],
+ index=["a", "a", "a", "a", "a"],
+ name=1,
+ )
+
+ result = df.iloc[0]
+ tm.assert_series_equal(result, expected)
+
+ result = df.loc[1]
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_setitem_dups(self):
+ # GH 6541
+ df_orig = DataFrame(
+ {
+ "me": list("rttti"),
+ "foo": list("aaade"),
+ "bar": np.arange(5, dtype="float64") * 1.34 + 2,
+ "bar2": np.arange(5, dtype="float64") * -0.34 + 2,
+ }
+ ).set_index("me")
+
+ indexer = (
+ "r",
+ ["bar", "bar2"],
+ )
+ df = df_orig.copy()
+ df.loc[indexer] *= 2.0
+ tm.assert_series_equal(df.loc[indexer], 2.0 * df_orig.loc[indexer])
+
+ indexer = (
+ "r",
+ "bar",
+ )
+ df = df_orig.copy()
+ df.loc[indexer] *= 2.0
+ assert df.loc[indexer] == 2.0 * df_orig.loc[indexer]
+
+ indexer = (
+ "t",
+ ["bar", "bar2"],
+ )
+ df = df_orig.copy()
+ df.loc[indexer] *= 2.0
+ tm.assert_frame_equal(df.loc[indexer], 2.0 * df_orig.loc[indexer])
+
+ def test_loc_setitem_slice(self):
+ # GH10503
+
+ # assigning the same type should not change the type
+ df1 = DataFrame({"a": [0, 1, 1], "b": Series([100, 200, 300], dtype="uint32")})
+ ix = df1["a"] == 1
+ newb1 = df1.loc[ix, "b"] + 1
+ df1.loc[ix, "b"] = newb1
+ expected = DataFrame(
+ {"a": [0, 1, 1], "b": Series([100, 201, 301], dtype="uint32")}
+ )
+ tm.assert_frame_equal(df1, expected)
+
+ # assigning a new type should get the inferred type
+ df2 = DataFrame({"a": [0, 1, 1], "b": [100, 200, 300]}, dtype="uint64")
+ ix = df1["a"] == 1
+ newb2 = df2.loc[ix, "b"]
+ with pytest.raises(TypeError, match="Invalid value"):
+ df1.loc[ix, "b"] = newb2
+
+ def test_loc_setitem_dtype(self):
+ # GH31340
+ df = DataFrame({"id": ["A"], "a": [1.2], "b": [0.0], "c": [-2.5]})
+ cols = ["a", "b", "c"]
+ df.loc[:, cols] = df.loc[:, cols].astype("float32")
+
+ # pre-2.0 this setting would swap in new arrays, in 2.0 it is correctly
+ # in-place, consistent with non-split-path
+ expected = DataFrame(
+ {
+ "id": ["A"],
+ "a": np.array([1.2], dtype="float64"),
+ "b": np.array([0.0], dtype="float64"),
+ "c": np.array([-2.5], dtype="float64"),
+ }
+ ) # id is inferred as object
+
+ tm.assert_frame_equal(df, expected)
+
+ def test_getitem_label_list_with_missing(self):
+ s = Series(range(3), index=["a", "b", "c"])
+
+ # consistency
+ with pytest.raises(KeyError, match="not in index"):
+ s[["a", "d"]]
+
+ s = Series(range(3))
+ with pytest.raises(KeyError, match="not in index"):
+ s[[0, 3]]
+
+ @pytest.mark.parametrize("index", [[True, False], [True, False, True, False]])
+ def test_loc_getitem_bool_diff_len(self, index):
+ # GH26658
+ s = Series([1, 2, 3])
+ msg = f"Boolean index has wrong length: {len(index)} instead of {len(s)}"
+ with pytest.raises(IndexError, match=msg):
+ s.loc[index]
+
+ def test_loc_getitem_int_slice(self):
+ # TODO: test something here?
+ pass
+
+ def test_loc_to_fail(self):
+ # GH3449
+ df = DataFrame(
+ np.random.default_rng(2).random((3, 3)),
+ index=["a", "b", "c"],
+ columns=["e", "f", "g"],
+ )
+
+ msg = (
+ rf"\"None of \[Index\(\[1, 2\], dtype='{np.dtype(int)}'\)\] are "
+ r"in the \[columns\]\""
+ )
+ with pytest.raises(KeyError, match=msg):
+ df.loc[[1, 2], [1, 2]]
+
+ def test_loc_to_fail2(self):
+ # GH 7496
+ # loc should not fallback
+
+ s = Series(dtype=object)
+ s.loc[1] = 1
+ s.loc["a"] = 2
+
+ with pytest.raises(KeyError, match=r"^-1$"):
+ s.loc[-1]
+
+ msg = (
+ rf"\"None of \[Index\(\[-1, -2\], dtype='{np.dtype(int)}'\)\] are "
+ r"in the \[index\]\""
+ )
+ with pytest.raises(KeyError, match=msg):
+ s.loc[[-1, -2]]
+
+ msg = r"\"None of \[Index\(\['4'\], dtype='object'\)\] are in the \[index\]\""
+ with pytest.raises(KeyError, match=msg):
+ s.loc[Index(["4"], dtype=object)]
+
+ s.loc[-1] = 3
+ with pytest.raises(KeyError, match="not in index"):
+ s.loc[[-1, -2]]
+
+ s["a"] = 2
+ msg = (
+ rf"\"None of \[Index\(\[-2\], dtype='{np.dtype(int)}'\)\] are "
+ r"in the \[index\]\""
+ )
+ with pytest.raises(KeyError, match=msg):
+ s.loc[[-2]]
+
+ del s["a"]
+
+ with pytest.raises(KeyError, match=msg):
+ s.loc[[-2]] = 0
+
+ def test_loc_to_fail3(self):
+ # inconsistency between .loc[values] and .loc[values,:]
+ # GH 7999
+ df = DataFrame([["a"], ["b"]], index=[1, 2], columns=["value"])
+
+ msg = (
+ rf"\"None of \[Index\(\[3\], dtype='{np.dtype(int)}'\)\] are "
+ r"in the \[index\]\""
+ )
+ with pytest.raises(KeyError, match=msg):
+ df.loc[[3], :]
+
+ with pytest.raises(KeyError, match=msg):
+ df.loc[[3]]
+
+ def test_loc_getitem_list_with_fail(self):
+ # 15747
+ # should KeyError if *any* missing labels
+
+ s = Series([1, 2, 3])
+
+ s.loc[[2]]
+
+ msg = "None of [RangeIndex(start=3, stop=4, step=1)] are in the [index]"
+ with pytest.raises(KeyError, match=re.escape(msg)):
+ s.loc[[3]]
+
+ # a non-match and a match
+ with pytest.raises(KeyError, match="not in index"):
+ s.loc[[2, 3]]
+
+ def test_loc_index(self):
+ # gh-17131
+ # a boolean index should index like a boolean numpy array
+
+ df = DataFrame(
+ np.random.default_rng(2).random(size=(5, 10)),
+ index=["alpha_0", "alpha_1", "alpha_2", "beta_0", "beta_1"],
+ )
+
+ mask = df.index.map(lambda x: "alpha" in x)
+ expected = df.loc[np.array(mask)]
+
+ result = df.loc[mask]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[mask.values]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[pd.array(mask, dtype="boolean")]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_general(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 4)),
+ columns=["A", "B", "C", "D"],
+ index=["A", "B", "C", "D"],
+ )
+
+ # want this to work
+ result = df.loc[:, "A":"B"].iloc[0:2, :]
+ assert (result.columns == ["A", "B"]).all()
+ assert (result.index == ["A", "B"]).all()
+
+ # mixed type
+ result = DataFrame({"a": [Timestamp("20130101")], "b": [1]}).iloc[0]
+ expected = Series([Timestamp("20130101"), 1], index=["a", "b"], name=0)
+ tm.assert_series_equal(result, expected)
+ assert result.dtype == object
+
+ @pytest.fixture
+ def frame_for_consistency(self):
+ return DataFrame(
+ {
+ "date": date_range("2000-01-01", "2000-01-5"),
+ "val": Series(range(5), dtype=np.int64),
+ }
+ )
+
+ @pytest.mark.parametrize(
+ "val",
+ [0, np.array(0, dtype=np.int64), np.array([0, 0, 0, 0, 0], dtype=np.int64)],
+ )
+ def test_loc_setitem_consistency(self, frame_for_consistency, val):
+ # GH 6149
+ # coerce similarly for setitem and loc when rows have a null-slice
+ df = frame_for_consistency.copy()
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[:, "date"] = val
+
+ def test_loc_setitem_consistency_dt64_to_str(self, frame_for_consistency):
+ # GH 6149
+ # coerce similarly for setitem and loc when rows have a null-slice
+
+ df = frame_for_consistency.copy()
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[:, "date"] = "foo"
+
+ def test_loc_setitem_consistency_dt64_to_float(self, frame_for_consistency):
+ # GH 6149
+ # coerce similarly for setitem and loc when rows have a null-slice
+ df = frame_for_consistency.copy()
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[:, "date"] = 1.0
+
+ def test_loc_setitem_consistency_single_row(self):
+ # GH 15494
+ # setting on frame with single row
+ df = DataFrame({"date": Series([Timestamp("20180101")])})
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[:, "date"] = "string"
+
+ def test_loc_setitem_consistency_empty(self):
+ # empty (essentially noops)
+ # before the enforcement of #45333 in 2.0, the loc.setitem here would
+ # change the dtype of df.x to int64
+ expected = DataFrame(columns=["x", "y"])
+ df = DataFrame(columns=["x", "y"])
+ with tm.assert_produces_warning(None):
+ df.loc[:, "x"] = 1
+ tm.assert_frame_equal(df, expected)
+
+ # setting with setitem swaps in a new array, so changes the dtype
+ df = DataFrame(columns=["x", "y"])
+ df["x"] = 1
+ expected["x"] = expected["x"].astype(np.int64)
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_consistency_slice_column_len(self, using_infer_string):
+ # .loc[:,column] setting with slice == len of the column
+ # GH10408
+ levels = [
+ ["Region_1"] * 4,
+ ["Site_1", "Site_1", "Site_2", "Site_2"],
+ [3987227376, 3980680971, 3977723249, 3977723089],
+ ]
+ mi = MultiIndex.from_arrays(levels, names=["Region", "Site", "RespondentID"])
+
+ clevels = [
+ ["Respondent", "Respondent", "Respondent", "OtherCat", "OtherCat"],
+ ["Something", "StartDate", "EndDate", "Yes/No", "SomethingElse"],
+ ]
+ cols = MultiIndex.from_arrays(clevels, names=["Level_0", "Level_1"])
+
+ values = [
+ ["A", "5/25/2015 10:59", "5/25/2015 11:22", "Yes", np.nan],
+ ["A", "5/21/2015 9:40", "5/21/2015 9:52", "Yes", "Yes"],
+ ["A", "5/20/2015 8:27", "5/20/2015 8:41", "Yes", np.nan],
+ ["A", "5/20/2015 8:33", "5/20/2015 9:09", "Yes", "No"],
+ ]
+ df = DataFrame(values, index=mi, columns=cols)
+
+ ctx = contextlib.nullcontext()
+ if using_infer_string:
+ ctx = pytest.raises(TypeError, match="Invalid value")
+
+ with ctx:
+ df.loc[:, ("Respondent", "StartDate")] = to_datetime(
+ df.loc[:, ("Respondent", "StartDate")]
+ )
+ with ctx:
+ df.loc[:, ("Respondent", "EndDate")] = to_datetime(
+ df.loc[:, ("Respondent", "EndDate")]
+ )
+
+ if using_infer_string:
+ # infer-objects won't infer stuff anymore
+ return
+
+ df = df.infer_objects()
+
+ # Adding a new key
+ df.loc[:, ("Respondent", "Duration")] = (
+ df.loc[:, ("Respondent", "EndDate")]
+ - df.loc[:, ("Respondent", "StartDate")]
+ )
+
+ # timedelta64[m] -> float, so this cannot be done inplace, so
+ # no warning
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[:, ("Respondent", "Duration")] = df.loc[
+ :, ("Respondent", "Duration")
+ ] / Timedelta(60_000_000_000)
+
+ @pytest.mark.parametrize("unit", ["Y", "M", "D", "h", "m", "s", "ms", "us"])
+ def test_loc_assign_non_ns_datetime(self, unit):
+ # GH 27395, non-ns dtype assignment via .loc should work
+ # and return the same result when using simple assignment
+ df = DataFrame(
+ {
+ "timestamp": [
+ np.datetime64("2017-02-11 12:41:29"),
+ np.datetime64("1991-11-07 04:22:37"),
+ ]
+ }
+ )
+
+ df.loc[:, unit] = df.loc[:, "timestamp"].values.astype(f"datetime64[{unit}]")
+ df["expected"] = df.loc[:, "timestamp"].values.astype(f"datetime64[{unit}]")
+ expected = Series(df.loc[:, "expected"], name=unit)
+ tm.assert_series_equal(df.loc[:, unit], expected)
+
+ def test_loc_modify_datetime(self):
+ # see gh-28837
+ df = DataFrame.from_dict(
+ {"date": [1485264372711, 1485265925110, 1540215845888, 1540282121025]}
+ )
+
+ df["date_dt"] = to_datetime(df["date"], unit="ms", cache=True).dt.as_unit("us")
+
+ df.loc[:, "date_dt_cp"] = df.loc[:, "date_dt"]
+ df.loc[[2, 3], "date_dt_cp"] = df.loc[[2, 3], "date_dt"]
+
+ expected = DataFrame(
+ [
+ [1485264372711, "2017-01-24 13:26:12.711", "2017-01-24 13:26:12.711"],
+ [1485265925110, "2017-01-24 13:52:05.110", "2017-01-24 13:52:05.110"],
+ [1540215845888, "2018-10-22 13:44:05.888", "2018-10-22 13:44:05.888"],
+ [1540282121025, "2018-10-23 08:08:41.025", "2018-10-23 08:08:41.025"],
+ ],
+ columns=["date", "date_dt", "date_dt_cp"],
+ )
+
+ columns = ["date_dt", "date_dt_cp"]
+ expected[columns] = expected[columns].apply(to_datetime)
+
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_loc_setitem_frame_with_reindex(self, has_ref):
+ # GH#6254 setting issue
+ df = DataFrame(index=[3, 5, 4], columns=["A"], dtype=float)
+ if has_ref:
+ view = df[:] # noqa: F841
+ df.loc[[4, 3, 5], "A"] = np.array([1, 2, 3], dtype="int64")
+
+ # setting integer values into a float dataframe with loc is inplace,
+ # so we retain float dtype
+ ser = Series([2, 3, 1], index=[3, 5, 4], dtype=float)
+ expected = DataFrame({"A": ser})
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_frame_with_reindex_mixed(self):
+ # GH#40480
+ df = DataFrame(index=[3, 5, 4], columns=["A", "B"], dtype=float)
+ df["B"] = "string"
+ df.loc[[4, 3, 5], "A"] = np.array([1, 2, 3], dtype="int64")
+ ser = Series([2, 3, 1], index=[3, 5, 4], dtype="int64")
+ # pre-2.0 this setting swapped in a new array, now it is inplace
+ # consistent with non-split-path
+ expected = DataFrame({"A": ser.astype(float)})
+ expected["B"] = "string"
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_frame_with_inverted_slice(self):
+ # GH#40480
+ df = DataFrame(index=[1, 2, 3], columns=["A", "B"], dtype=float)
+ df["B"] = "string"
+ df.loc[slice(3, 0, -1), "A"] = np.array([1, 2, 3], dtype="int64")
+ # pre-2.0 this setting swapped in a new array, now it is inplace
+ # consistent with non-split-path
+ expected = DataFrame({"A": [3.0, 2.0, 1.0], "B": "string"}, index=[1, 2, 3])
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_loc_setitem_empty_frame(self, has_ref):
+ # GH#6252 setting with an empty frame
+ keys1 = ["@" + str(i) for i in range(5)]
+ val1 = np.arange(5, dtype="int64")
+
+ keys2 = ["@" + str(i) for i in range(4)]
+ val2 = np.arange(4, dtype="int64")
+
+ index = list(set(keys1).union(keys2))
+ df = DataFrame(index=index)
+ df["A"] = np.nan
+ if has_ref:
+ view = df[:] # noqa: F841
+ df.loc[keys1, "A"] = val1
+
+ df["B"] = np.nan
+ df.loc[keys2, "B"] = val2
+
+ # Because df["A"] was initialized as float64, setting values into it
+ # is inplace, so that dtype is retained
+ sera = Series(val1, index=keys1, dtype=np.float64)
+ serb = Series(val2, index=keys2)
+ expected = DataFrame({"A": sera, "B": serb}, columns=Index(["A", "B"])).reindex(
+ index=index
+ )
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_loc_setitem_frame(self, has_ref):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 4)),
+ index=list("abcd"),
+ columns=list("ABCD"),
+ )
+ if has_ref:
+ view = df[:] # noqa: F841
+
+ result = df.iloc[0, 0]
+
+ df.loc["a", "A"] = 1
+ result = df.loc["a", "A"]
+ assert result == 1
+
+ result = df.iloc[0, 0]
+ assert result == 1
+
+ df.loc[:, "B":"D"] = 0
+ expected = df.loc[:, "B":"D"]
+ result = df.iloc[:, 1:]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_setitem_frame_nan_int_coercion_invalid(self):
+ # GH 8669
+ # invalid coercion of nan -> int
+ df = DataFrame({"A": [1, 2, 3], "B": np.nan})
+ df.loc[df.B > df.A, "B"] = df.A
+ expected = DataFrame({"A": [1, 2, 3], "B": np.nan})
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_frame_mixed_labels(self):
+ # GH 6546
+ # setting with mixed labels
+ df = DataFrame({1: [1, 2], 2: [3, 4], "a": ["a", "b"]})
+
+ result = df.loc[0, [1, 2]]
+ expected = Series(
+ [1, 3], index=Index([1, 2], dtype=object), dtype="int64", name=0
+ )
+ tm.assert_series_equal(result, expected)
+
+ expected = DataFrame({1: [5, 2], 2: [6, 4], "a": ["a", "b"]})
+ df.loc[0, [1, 2]] = [5, 6]
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_frame_multiples(self):
+ # multiple setting
+ df = DataFrame(
+ {"A": ["foo", "bar", "baz"], "B": Series(range(3), dtype=np.int64)}
+ )
+ rhs = df.loc[1:2]
+ rhs.index = df.index[0:2]
+ df.loc[0:1] = rhs
+ expected = DataFrame(
+ {"A": ["bar", "baz", "baz"], "B": Series([1, 2, 2], dtype=np.int64)}
+ )
+ tm.assert_frame_equal(df, expected)
+
+ # multiple setting with frame on rhs (with M8)
+ df = DataFrame(
+ {
+ "date": date_range("2000-01-01", "2000-01-5", unit="ns"),
+ "val": Series(range(5), dtype=np.int64),
+ }
+ )
+ expected = DataFrame(
+ {
+ "date": [
+ Timestamp("20000101"),
+ Timestamp("20000102"),
+ Timestamp("20000101"),
+ Timestamp("20000102"),
+ Timestamp("20000103"),
+ ],
+ "val": Series([0, 1, 0, 1, 2], dtype=np.int64),
+ }
+ )
+ expected["date"] = expected["date"].astype("M8[ns]")
+ rhs = df.loc[0:2]
+ rhs.index = df.index[2:5]
+ df.loc[2:4] = rhs
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize(
+ "indexer", [["A"], slice(None, "A", None), np.array(["A"])]
+ )
+ @pytest.mark.parametrize("value", [["Z"], np.array(["Z"])])
+ def test_loc_setitem_with_scalar_index(self, indexer, value):
+ # GH #19474
+ # assigning like "df.loc[0, ['A']] = ['Z']" should be evaluated
+ # elementwisely, not using "setter('A', ['Z'])".
+
+ # Set object dtype to avoid upcast when setting 'Z'
+ df = DataFrame([[1, 2], [3, 4]], columns=["A", "B"]).astype({"A": object})
+ df.loc[0, indexer] = value
+ result = df.loc[0, "A"]
+
+ assert is_scalar(result) and result == "Z"
+
+ @pytest.mark.parametrize(
+ "index,box,expected",
+ [
+ (
+ ([0, 2], ["A", "B", "C", "D"]),
+ 7,
+ DataFrame(
+ [[7, 7, 7, 7], [3, 4, np.nan, np.nan], [7, 7, 7, 7]],
+ columns=["A", "B", "C", "D"],
+ ),
+ ),
+ (
+ (1, ["C", "D"]),
+ [7, 8],
+ DataFrame(
+ [[1, 2, np.nan, np.nan], [3, 4, 7, 8], [5, 6, np.nan, np.nan]],
+ columns=["A", "B", "C", "D"],
+ ),
+ ),
+ (
+ (1, ["A", "B", "C"]),
+ np.array([7, 8, 9], dtype=np.int64),
+ DataFrame(
+ [[1, 2, np.nan], [7, 8, 9], [5, 6, np.nan]], columns=["A", "B", "C"]
+ ),
+ ),
+ (
+ (slice(1, 3, None), ["B", "C", "D"]),
+ [[7, 8, 9], [10, 11, 12]],
+ DataFrame(
+ [[1, 2, np.nan, np.nan], [3, 7, 8, 9], [5, 10, 11, 12]],
+ columns=["A", "B", "C", "D"],
+ ),
+ ),
+ (
+ (slice(1, 3, None), ["C", "A", "D"]),
+ np.array([[7, 8, 9], [10, 11, 12]], dtype=np.int64),
+ DataFrame(
+ [[1, 2, np.nan, np.nan], [8, 4, 7, 9], [11, 6, 10, 12]],
+ columns=["A", "B", "C", "D"],
+ ),
+ ),
+ (
+ (slice(None, None, None), ["A", "C"]),
+ DataFrame([[7, 8], [9, 10], [11, 12]], columns=["A", "C"]),
+ DataFrame(
+ [[7, 2, 8], [9, 4, 10], [11, 6, 12]], columns=["A", "B", "C"]
+ ),
+ ),
+ ],
+ )
+ def test_loc_setitem_missing_columns(self, index, box, expected):
+ # GH 29334
+ df = DataFrame([[1, 2], [3, 4], [5, 6]], columns=["A", "B"])
+
+ df.loc[index] = box
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_coercion(self):
+ # GH#12411
+ df = DataFrame({"date": [Timestamp("20130101").tz_localize("UTC"), pd.NaT]})
+ expected = df.dtypes
+
+ result = df.iloc[[0]]
+ tm.assert_series_equal(result.dtypes, expected)
+
+ result = df.iloc[[1]]
+ tm.assert_series_equal(result.dtypes, expected)
+
+ def test_loc_coercion2(self):
+ # GH#12045
+ df = DataFrame({"date": [datetime(2012, 1, 1), datetime(1012, 1, 2)]})
+ expected = df.dtypes
+
+ result = df.iloc[[0]]
+ tm.assert_series_equal(result.dtypes, expected)
+
+ result = df.iloc[[1]]
+ tm.assert_series_equal(result.dtypes, expected)
+
+ def test_loc_coercion3(self):
+ # GH#11594
+ df = DataFrame({"text": ["some words"] + [None] * 9})
+ expected = df.dtypes
+
+ result = df.iloc[0:2]
+ tm.assert_series_equal(result.dtypes, expected)
+
+ result = df.iloc[3:]
+ tm.assert_series_equal(result.dtypes, expected)
+
+ def test_setitem_new_key_tz(self, indexer_sl):
+ # GH#12862 should not raise on assigning the second value
+ vals = [
+ to_datetime(42).tz_localize("UTC"),
+ to_datetime(666).tz_localize("UTC"),
+ ]
+ expected = Series(vals, index=Index(["foo", "bar"]))
+
+ ser = Series(dtype=object)
+ indexer_sl(ser)["foo"] = vals[0]
+ indexer_sl(ser)["bar"] = vals[1]
+
+ tm.assert_series_equal(ser, expected)
+
+ def test_loc_non_unique(self):
+ # GH3659
+ # non-unique indexer with loc slice
+ # https://groups.google.com/forum/?fromgroups#!topic/pydata/zTm2No0crYs
+
+ # these are going to raise because the we are non monotonic
+ df = DataFrame(
+ {"A": [1, 2, 3, 4, 5, 6], "B": [3, 4, 5, 6, 7, 8]}, index=[0, 1, 0, 1, 2, 3]
+ )
+ msg = "'Cannot get left slice bound for non-unique label: 1'"
+ with pytest.raises(KeyError, match=msg):
+ df.loc[1:]
+ msg = "'Cannot get left slice bound for non-unique label: 0'"
+ with pytest.raises(KeyError, match=msg):
+ df.loc[0:]
+ msg = "'Cannot get left slice bound for non-unique label: 1'"
+ with pytest.raises(KeyError, match=msg):
+ df.loc[1:2]
+
+ # monotonic are ok
+ df = DataFrame(
+ {"A": [1, 2, 3, 4, 5, 6], "B": [3, 4, 5, 6, 7, 8]}, index=[0, 1, 0, 1, 2, 3]
+ ).sort_index(axis=0)
+ result = df.loc[1:]
+ expected = DataFrame({"A": [2, 4, 5, 6], "B": [4, 6, 7, 8]}, index=[1, 1, 2, 3])
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[0:]
+ tm.assert_frame_equal(result, df)
+
+ result = df.loc[1:2]
+ expected = DataFrame({"A": [2, 4, 5], "B": [4, 6, 7]}, index=[1, 1, 2])
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.arm_slow
+ @pytest.mark.slow
+ @pytest.mark.parametrize("length, l2", [[900, 100], [900000, 100000]])
+ def test_loc_non_unique_memory_error(self, length, l2):
+ # GH 4280
+ # non_unique index with a large selection triggers a memory error
+
+ columns = list("ABCDEFG")
+
+ df = pd.concat(
+ [
+ DataFrame(
+ np.random.default_rng(2).standard_normal((length, len(columns))),
+ index=np.arange(length),
+ columns=columns,
+ ),
+ DataFrame(np.ones((l2, len(columns))), index=[0] * l2, columns=columns),
+ ]
+ )
+
+ assert df.index.is_unique is False
+
+ mask = np.arange(l2)
+ result = df.loc[mask]
+ expected = pd.concat(
+ [
+ df.take([0]),
+ DataFrame(
+ np.ones((len(mask), len(columns))),
+ index=[0] * len(mask),
+ columns=columns,
+ ),
+ df.take(mask[1:]),
+ ]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_name(self):
+ # GH 3880
+ df = DataFrame([[1, 1], [1, 1]])
+ df.index.name = "index_name"
+ result = df.iloc[[0, 1]].index.name
+ assert result == "index_name"
+
+ result = df.loc[[0, 1]].index.name
+ assert result == "index_name"
+
+ def test_loc_empty_list_indexer_is_ok(self):
+ df = DataFrame(
+ np.ones((5, 2)),
+ index=Index([f"i-{i}" for i in range(5)], name="a"),
+ columns=Index([f"i-{i}" for i in range(2)], name="a"),
+ )
+ # vertical empty
+ tm.assert_frame_equal(
+ df.loc[:, []], df.iloc[:, :0], check_index_type=True, check_column_type=True
+ )
+ # horizontal empty
+ tm.assert_frame_equal(
+ df.loc[[], :], df.iloc[:0, :], check_index_type=True, check_column_type=True
+ )
+ # horizontal empty
+ tm.assert_frame_equal(
+ df.loc[[]], df.iloc[:0, :], check_index_type=True, check_column_type=True
+ )
+
+ def test_identity_slice_returns_new_object(self):
+ # GH13873
+
+ original_df = DataFrame({"a": [1, 2, 3]})
+ sliced_df = original_df.loc[:]
+ assert sliced_df is not original_df
+ assert original_df[:] is not original_df
+ assert original_df.loc[:, :] is not original_df
+
+ # should be a shallow copy
+ assert np.shares_memory(original_df["a"]._values, sliced_df["a"]._values)
+
+ # Setting using .loc[:, "a"] sets inplace so alters both sliced and orig
+ # depending on CoW
+ original_df.loc[:, "a"] = [4, 4, 4]
+ assert (sliced_df["a"] == [1, 2, 3]).all()
+
+ # These should not return copies
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ assert df[0] is not df.loc[:, 0]
+
+ # Same tests for Series
+ original_series = Series([1, 2, 3, 4, 5, 6])
+ sliced_series = original_series.loc[:]
+ assert sliced_series is not original_series
+ assert original_series[:] is not original_series
+
+ original_series[:3] = [7, 8, 9]
+ assert all(sliced_series[:3] == [1, 2, 3])
+
+ def test_loc_copy_vs_view(self, request):
+ # GH 15631
+ x = DataFrame(zip(range(3), range(3)), columns=["a", "b"])
+
+ y = x.copy()
+ q = y.loc[:, "a"]
+ q += 2
+
+ tm.assert_frame_equal(x, y)
+
+ z = x.copy()
+ q = z.loc[x.index, "a"]
+ q += 2
+
+ tm.assert_frame_equal(x, z)
+
+ def test_loc_uint64(self):
+ # GH20722
+ # Test whether loc accept uint64 max value as index.
+ umax = np.iinfo("uint64").max
+ ser = Series([1, 2], index=[umax - 1, umax])
+
+ result = ser.loc[umax - 1]
+ expected = ser.iloc[0]
+ assert result == expected
+
+ result = ser.loc[[umax - 1]]
+ expected = ser.iloc[[0]]
+ tm.assert_series_equal(result, expected)
+
+ result = ser.loc[[umax - 1, umax]]
+ tm.assert_series_equal(result, ser)
+
+ def test_loc_uint64_disallow_negative(self):
+ # GH#41775
+ umax = np.iinfo("uint64").max
+ ser = Series([1, 2], index=[umax - 1, umax])
+
+ with pytest.raises(KeyError, match="-1"):
+ # don't wrap around
+ ser.loc[-1]
+
+ with pytest.raises(KeyError, match="-1"):
+ # don't wrap around
+ ser.loc[[-1]]
+
+ def test_loc_setitem_empty_append_expands_rows(self):
+ # GH6173, various appends to an empty dataframe
+
+ data = [1, 2, 3]
+ expected = DataFrame(
+ {"x": data, "y": np.array([np.nan] * len(data), dtype=object)}
+ )
+
+ # appends to fit length of data
+ df = DataFrame(columns=["x", "y"])
+ df.loc[:, "x"] = data
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_empty_append_expands_rows_mixed_dtype(self):
+ # GH#37932 same as test_loc_setitem_empty_append_expands_rows
+ # but with mixed dtype so we go through take_split_path
+ data = [1, 2, 3]
+ expected = DataFrame(
+ {"x": data, "y": np.array([np.nan] * len(data), dtype=object)}
+ )
+
+ df = DataFrame(columns=["x", "y"])
+ df["x"] = df["x"].astype(np.int64)
+ df.loc[:, "x"] = data
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_empty_append_single_value(self):
+ # only appends one value
+ expected = DataFrame({"x": [1.0], "y": [np.nan]})
+ df = DataFrame(columns=["x", "y"], dtype=float)
+ df.loc[0, "x"] = expected.loc[0, "x"]
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_empty_append_raises(self):
+ # GH6173, various appends to an empty dataframe
+
+ data = [1, 2]
+ df = DataFrame(columns=["x", "y"])
+ df.index = df.index.astype(np.int64)
+ msg = r"None of .*Index.* are in the \[index\]"
+ with pytest.raises(KeyError, match=msg):
+ df.loc[[0, 1], "x"] = data
+
+ msg = "setting an array element with a sequence."
+ with pytest.raises(ValueError, match=msg):
+ df.loc[0:2, "x"] = data
+
+ def test_indexing_zerodim_np_array(self):
+ # GH24924
+ df = DataFrame([[1, 2], [3, 4]])
+ result = df.loc[np.array(0)]
+ s = Series([1, 2], name=0)
+ tm.assert_series_equal(result, s)
+
+ def test_series_indexing_zerodim_np_array(self):
+ # GH24924
+ s = Series([1, 2])
+ result = s.loc[np.array(0)]
+ assert result == 1
+
+ def test_loc_reverse_assignment(self):
+ # GH26939
+ data = [1, 2, 3, 4, 5, 6] + [None] * 4
+ expected = Series(data, index=range(2010, 2020))
+
+ result = Series(index=range(2010, 2020), dtype=np.float64)
+ result.loc[2015:2010:-1] = [6, 5, 4, 3, 2, 1]
+
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_setitem_str_to_small_float_conversion_type(self, using_infer_string):
+ # GH#20388
+
+ col_data = [str(np.random.default_rng(2).random() * 1e-12) for _ in range(5)]
+ result = DataFrame(col_data, columns=["A"])
+ expected = DataFrame(col_data, columns=["A"])
+ tm.assert_frame_equal(result, expected)
+
+ # assigning with loc/iloc attempts to set the values inplace, which
+ # in this case is successful
+ if using_infer_string:
+ with pytest.raises(TypeError, match="Invalid value"):
+ result.loc[result.index, "A"] = [float(x) for x in col_data]
+ else:
+ result.loc[result.index, "A"] = [float(x) for x in col_data]
+ expected = DataFrame(col_data, columns=["A"], dtype=float).astype(object)
+ tm.assert_frame_equal(result, expected)
+
+ # assigning the entire column using __setitem__ swaps in the new array
+ # GH#???
+ result["A"] = [float(x) for x in col_data]
+ expected = DataFrame(col_data, columns=["A"], dtype=float)
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_getitem_time_object(self, frame_or_series):
+ rng = date_range("1/1/2000", "1/5/2000", freq="5min")
+ mask = (rng.hour == 9) & (rng.minute == 30)
+
+ obj = DataFrame(
+ np.random.default_rng(2).standard_normal((len(rng), 3)), index=rng
+ )
+ obj = tm.get_obj(obj, frame_or_series)
+
+ result = obj.loc[time(9, 30)]
+ exp = obj.loc[mask]
+ tm.assert_equal(result, exp)
+
+ chunk = obj.loc["1/4/2000":]
+ result = chunk.loc[time(9, 30)]
+ expected = result[-1:]
+
+ # Without resetting the freqs, these are 5 min and 1440 min, respectively
+ result.index = result.index._with_freq(None)
+ expected.index = expected.index._with_freq(None)
+ tm.assert_equal(result, expected)
+
+ @pytest.mark.parametrize("spmatrix_t", ["coo_matrix", "csc_matrix", "csr_matrix"])
+ @pytest.mark.parametrize("dtype", [np.complex128, np.float64, np.int64, bool])
+ def test_loc_getitem_range_from_spmatrix(self, spmatrix_t, dtype):
+ sp_sparse = pytest.importorskip("scipy.sparse")
+
+ spmatrix_t = getattr(sp_sparse, spmatrix_t)
+
+ # The bug is triggered by a sparse matrix with purely sparse columns. So the
+ # recipe below generates a rectangular matrix of dimension (5, 7) where all the
+ # diagonal cells are ones, meaning the last two columns are purely sparse.
+ rows, cols = 5, 7
+ spmatrix = spmatrix_t(np.eye(rows, cols, dtype=dtype), dtype=dtype)
+ df = DataFrame.sparse.from_spmatrix(spmatrix)
+
+ # regression test for GH#34526
+ itr_idx = range(2, rows)
+ result = np.nan_to_num(df.loc[itr_idx].values)
+ expected = spmatrix.toarray()[itr_idx]
+ tm.assert_numpy_array_equal(result, expected)
+
+ # regression test for GH#34540
+ result = df.loc[itr_idx].dtypes.values
+ expected = np.full(cols, SparseDtype(dtype))
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_loc_getitem_listlike_all_retains_sparse(self):
+ df = DataFrame({"A": pd.array([0, 0], dtype=SparseDtype("int64"))})
+ result = df.loc[[0, 1]]
+ tm.assert_frame_equal(result, df)
+
+ def test_loc_getitem_sparse_frame(self):
+ # GH34687
+ sp_sparse = pytest.importorskip("scipy.sparse")
+
+ df = DataFrame.sparse.from_spmatrix(sp_sparse.eye(5, dtype=np.int64))
+ result = df.loc[range(2)]
+ expected = DataFrame(
+ [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0]],
+ dtype=SparseDtype(np.int64),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.loc[range(2)].loc[range(1)]
+ expected = DataFrame([[1, 0, 0, 0, 0]], dtype=SparseDtype(np.int64))
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_getitem_sparse_series(self):
+ # GH34687
+ s = Series([1.0, 0.0, 0.0, 0.0, 0.0], dtype=SparseDtype("float64", 0.0))
+
+ result = s.loc[range(2)]
+ expected = Series([1.0, 0.0], dtype=SparseDtype("float64", 0.0))
+ tm.assert_series_equal(result, expected)
+
+ result = s.loc[range(3)].loc[range(2)]
+ expected = Series([1.0, 0.0], dtype=SparseDtype("float64", 0.0))
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("indexer", ["loc", "iloc"])
+ def test_getitem_single_row_sparse_df(self, indexer):
+ # GH#46406
+ df = DataFrame([[1.0, 0.0, 1.5], [0.0, 2.0, 0.0]], dtype=SparseDtype(float))
+ result = getattr(df, indexer)[0]
+ expected = Series([1.0, 0.0, 1.5], dtype=SparseDtype(float), name=0)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("key_type", [iter, np.array, Series, Index])
+ def test_loc_getitem_iterable(self, float_frame, key_type):
+ idx = key_type(["A", "B", "C"])
+ result = float_frame.loc[:, idx]
+ expected = float_frame.loc[:, ["A", "B", "C"]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_getitem_timedelta_0seconds(self):
+ # GH#10583
+ df = DataFrame(np.random.default_rng(2).normal(size=(10, 4)))
+ df.index = timedelta_range(start="0s", periods=10, freq="s")
+ expected = df.loc[Timedelta("0s") :, :]
+ result = df.loc["0s":, :]
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("val,expected", [(2**63 - 1, 1), (2**63, 2)])
+ def test_loc_getitem_uint64_scalar(self, val, expected):
+ # see GH#19399
+ df = DataFrame([1, 2], index=[2**63 - 1, 2**63])
+ result = df.loc[val]
+ expected = Series([expected])
+ expected.name = val
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_setitem_int_label_with_float_index(self, float_numpy_dtype):
+ # note labels are floats
+ dtype = float_numpy_dtype
+ ser = Series(["a", "b", "c"], index=Index([0, 0.5, 1], dtype=dtype))
+ expected = ser.copy()
+
+ ser.loc[1] = "zoo"
+ expected.iloc[2] = "zoo"
+
+ tm.assert_series_equal(ser, expected)
+
+ @pytest.mark.parametrize(
+ "indexer, expected",
+ [
+ # The test name is a misnomer in the 0 case as df.index[indexer]
+ # is a scalar.
+ (0, [20, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
+ (slice(4, 8), [0, 1, 2, 3, 20, 20, 20, 20, 8, 9]),
+ ([3, 5], [0, 1, 2, 20, 4, 20, 6, 7, 8, 9]),
+ ],
+ )
+ def test_loc_setitem_listlike_with_timedelta64index(self, indexer, expected):
+ # GH#16637
+ tdi = to_timedelta(range(10), unit="s")
+ df = DataFrame({"x": range(10)}, dtype="int64", index=tdi)
+
+ df.loc[df.index[indexer], "x"] = 20
+
+ expected = DataFrame(
+ expected,
+ index=tdi,
+ columns=["x"],
+ dtype="int64",
+ )
+
+ tm.assert_frame_equal(expected, df)
+
+ def test_loc_setitem_categorical_values_partial_column_slice(self):
+ # Assigning a Category to parts of an int/... column uses the values of
+ # the Categorical
+ df = DataFrame({"a": [1, 1, 1, 1, 1], "b": list("aaaaa")})
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[1:2, "a"] = Categorical(["b", "b"], categories=["a", "b"])
+ df.loc[2:3, "b"] = Categorical(["b", "b"], categories=["a", "b"])
+
+ def test_loc_setitem_single_row_categorical(self, using_infer_string):
+ # GH#25495
+ df = DataFrame({"Alpha": ["a"], "Numeric": [0]})
+ categories = Categorical(df["Alpha"], categories=["a", "b", "c"])
+
+ # pre-2.0 this swapped in a new array, in 2.0 it operates inplace,
+ # consistent with non-split-path
+ df.loc[:, "Alpha"] = categories
+
+ result = df["Alpha"]
+ expected = Series(categories, index=df.index, name="Alpha").astype(
+ object if not using_infer_string else "str"
+ )
+ tm.assert_series_equal(result, expected)
+
+ # double-check that the non-loc setting retains categoricalness
+ df["Alpha"] = categories
+ tm.assert_series_equal(df["Alpha"], Series(categories, name="Alpha"))
+
+ def test_loc_setitem_datetime_coercion(self):
+ # GH#1048
+ df = DataFrame({"c": [Timestamp("2010-10-01")] * 3})
+ df.loc[0:1, "c"] = np.datetime64("2008-08-08")
+ assert Timestamp("2008-08-08") == df.loc[0, "c"]
+ assert Timestamp("2008-08-08") == df.loc[1, "c"]
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[2, "c"] = date(2005, 5, 5)
+
+ @pytest.mark.parametrize("idxer", ["var", ["var"]])
+ def test_loc_setitem_datetimeindex_tz(self, idxer, tz_naive_fixture):
+ # GH#11365
+ tz = tz_naive_fixture
+ idx = date_range(start="2015-07-12", periods=3, freq="h", tz=tz)
+ expected = DataFrame(1.2, index=idx, columns=["var"])
+ # if result started off with object dtype, then the .loc.__setitem__
+ # below would retain object dtype
+ result = DataFrame(index=idx, columns=["var"], dtype=np.float64)
+ if idxer == "var":
+ with pytest.raises(TypeError, match="Invalid value"):
+ result.loc[:, idxer] = expected
+ else:
+ # See https://github.com/pandas-dev/pandas/issues/56223
+ result.loc[:, idxer] = expected
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_setitem_time_key(self):
+ index = date_range("2012-01-01", "2012-01-05", freq="30min")
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(index), 5)), index=index
+ )
+ akey = time(12, 0, 0)
+ bkey = slice(time(13, 0, 0), time(14, 0, 0))
+ ainds = [24, 72, 120, 168]
+ binds = [26, 27, 28, 74, 75, 76, 122, 123, 124, 170, 171, 172]
+
+ result = df.copy()
+ result.loc[akey] = 0
+ result = result.loc[akey]
+ expected = df.loc[akey].copy()
+ expected.loc[:] = 0
+ tm.assert_frame_equal(result, expected)
+
+ result = df.copy()
+ result.loc[akey] = 0
+ result.loc[akey] = df.iloc[ainds]
+ tm.assert_frame_equal(result, df)
+
+ result = df.copy()
+ result.loc[bkey] = 0
+ result = result.loc[bkey]
+ expected = df.loc[bkey].copy()
+ expected.loc[:] = 0
+ tm.assert_frame_equal(result, expected)
+
+ result = df.copy()
+ result.loc[bkey] = 0
+ result.loc[bkey] = df.iloc[binds]
+ tm.assert_frame_equal(result, df)
+
+ @pytest.mark.parametrize("key", ["A", ["A"], ("A", slice(None))])
+ def test_loc_setitem_unsorted_multiindex_columns(self, key):
+ # GH#38601
+ mi = MultiIndex.from_tuples([("A", 4), ("B", "3"), ("A", "2")])
+ df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=mi)
+ obj = df.copy()
+ obj.loc[:, key] = np.zeros((2, 2), dtype="int64")
+ expected = DataFrame([[0, 2, 0], [0, 5, 0]], columns=mi)
+ tm.assert_frame_equal(obj, expected)
+
+ df = df.sort_index(axis=1)
+ df.loc[:, key] = np.zeros((2, 2), dtype="int64")
+ expected = expected.sort_index(axis=1)
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_uint_drop(self, any_int_numpy_dtype):
+ # see GH#18311
+ # assigning series.loc[0] = 4 changed series.dtype to int
+ series = Series([1, 2, 3], dtype=any_int_numpy_dtype)
+ series.loc[0] = 4
+ expected = Series([4, 2, 3], dtype=any_int_numpy_dtype)
+ tm.assert_series_equal(series, expected)
+
+ def test_loc_setitem_td64_non_nano(self):
+ # GH#14155
+ ser = Series(10 * [np.timedelta64(10, "m")])
+ ser.loc[[1, 2, 3]] = np.timedelta64(20, "m")
+ expected = Series(10 * [np.timedelta64(10, "m")])
+ expected.loc[[1, 2, 3]] = Timedelta(np.timedelta64(20, "m"))
+ tm.assert_series_equal(ser, expected)
+
+ def test_loc_setitem_2d_to_1d_raises(self):
+ data = np.random.default_rng(2).standard_normal((2, 2))
+ # float64 dtype to avoid upcast when trying to set float data
+ ser = Series(range(2), dtype="float64")
+
+ msg = "setting an array element with a sequence."
+ with pytest.raises(ValueError, match=msg):
+ ser.loc[range(2)] = data
+
+ with pytest.raises(ValueError, match=msg):
+ ser.loc[:] = data
+
+ def test_loc_getitem_interval_index(self):
+ # GH#19977
+ index = pd.interval_range(start=0, periods=3)
+ df = DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=index, columns=["A", "B", "C"]
+ )
+
+ expected = 1
+ result = df.loc[0.5, "A"]
+ tm.assert_almost_equal(result, expected)
+
+ def test_loc_getitem_interval_index2(self):
+ # GH#19977
+ index = pd.interval_range(start=0, periods=3, closed="both")
+ df = DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=index, columns=["A", "B", "C"]
+ )
+
+ index_exp = pd.interval_range(start=0, periods=2, freq=1, closed="both")
+ expected = Series([1, 4], index=index_exp, name="A")
+ result = df.loc[1, "A"]
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("tpl", [(1,), (1, 2)])
+ def test_loc_getitem_index_single_double_tuples(self, tpl):
+ # GH#20991
+ idx = Index(
+ [(1,), (1, 2)],
+ name="A",
+ tupleize_cols=False,
+ )
+ df = DataFrame(index=idx)
+
+ result = df.loc[[tpl]]
+ idx = Index([tpl], name="A", tupleize_cols=False)
+ expected = DataFrame(index=idx)
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_getitem_index_namedtuple(self):
+ IndexType = namedtuple("IndexType", ["a", "b"])
+ idx1 = IndexType("foo", "bar")
+ idx2 = IndexType("baz", "bof")
+ index = Index([idx1, idx2], name="composite_index", tupleize_cols=False)
+ df = DataFrame([(1, 2), (3, 4)], index=index, columns=["A", "B"])
+
+ result = df.loc[IndexType("foo", "bar")]["A"]
+ assert result == 1
+
+ def test_loc_setitem_single_column_mixed(self, using_infer_string):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 3)),
+ index=["a", "b", "c", "d", "e"],
+ columns=["foo", "bar", "baz"],
+ )
+ df["str"] = "qux"
+ df.loc[df.index[::2], "str"] = np.nan
+ expected = Series(
+ [np.nan, "qux", np.nan, "qux", np.nan],
+ dtype=object if not using_infer_string else "str",
+ ).values
+ tm.assert_almost_equal(df["str"].values, expected)
+
+ def test_loc_setitem_cast2(self):
+ # GH#7704
+ # dtype conversion on setting
+ df = DataFrame(np.random.default_rng(2).random((30, 3)), columns=tuple("ABC"))
+ df["event"] = np.nan
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[10, "event"] = "foo"
+
+ def test_loc_setitem_cast3(self):
+ # Test that data type is preserved . GH#5782
+ df = DataFrame({"one": np.arange(6, dtype=np.int8)})
+ df.loc[1, "one"] = 6
+ assert df.dtypes.one == np.dtype(np.int8)
+ df.one = np.int8(7)
+ assert df.dtypes.one == np.dtype(np.int8)
+
+ def test_loc_setitem_range_key(self, frame_or_series):
+ # GH#45479 don't treat range key as positional
+ obj = frame_or_series(range(5), index=[3, 4, 1, 0, 2])
+
+ values = [9, 10, 11]
+ if obj.ndim == 2:
+ values = [[9], [10], [11]]
+
+ obj.loc[range(3)] = values
+
+ expected = frame_or_series([0, 1, 10, 9, 11], index=obj.index)
+ tm.assert_equal(obj, expected)
+
+ def test_loc_setitem_numpy_frame_categorical_value(self):
+ # GH#52927
+ df = DataFrame({"a": [1, 1, 1, 1, 1], "b": ["a", "a", "a", "a", "a"]})
+ df.loc[1:2, "a"] = Categorical([2, 2], categories=[1, 2])
+
+ expected = DataFrame({"a": [1, 2, 2, 1, 1], "b": ["a", "a", "a", "a", "a"]})
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_with_nat_in_tzaware_index(self):
+ # GH#54409
+ timestamp = to_datetime("2023-01-01", utc=True)
+ df = DataFrame(
+ {
+ "index": Series([pd.NaT, timestamp]),
+ "value": Series([0, 1]),
+ }
+ ).set_index("index")
+
+ # Works fine when mixing NaT and valid values
+ result = df.loc[
+ Series([pd.NaT, timestamp, timestamp], dtype=df.index.dtype),
+ "value",
+ ]
+ expected = [0, 1, 1]
+ assert result.tolist() == expected
+
+ # Regression check: all-NaT lookup should return [0], not raise
+ result = df.loc[
+ Series([pd.NaT], dtype=df.index.dtype),
+ "value",
+ ]
+ assert result.tolist() == [0]
+
+
+class TestLocWithEllipsis:
+ @pytest.fixture
+ def indexer(self, indexer_li):
+ # Test iloc while we're here
+ return indexer_li
+
+ @pytest.fixture
+ def obj(self, series_with_simple_index, frame_or_series):
+ obj = series_with_simple_index
+ if frame_or_series is not Series:
+ obj = obj.to_frame()
+ return obj
+
+ def test_loc_iloc_getitem_ellipsis(self, obj, indexer):
+ result = indexer(obj)[...]
+ tm.assert_equal(result, obj)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_loc_iloc_getitem_leading_ellipses(self, series_with_simple_index, indexer):
+ obj = series_with_simple_index
+ key = 0 if (indexer is tm.iloc or len(obj) == 0) else obj.index[0]
+
+ if indexer is tm.loc and obj.index.inferred_type == "boolean":
+ # passing [False] will get interpreted as a boolean mask
+ # TODO: should it? unambiguous when lengths dont match?
+ return
+ if indexer is tm.loc and isinstance(obj.index, MultiIndex):
+ msg = "MultiIndex does not support indexing with Ellipsis"
+ with pytest.raises(NotImplementedError, match=msg):
+ result = indexer(obj)[..., [key]]
+
+ elif len(obj) != 0:
+ result = indexer(obj)[..., [key]]
+ expected = indexer(obj)[[key]]
+ tm.assert_series_equal(result, expected)
+
+ key2 = 0 if indexer is tm.iloc else obj.name
+ df = obj.to_frame()
+ result = indexer(df)[..., [key2]]
+ expected = indexer(df)[:, [key2]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_iloc_getitem_ellipses_only_one_ellipsis(self, obj, indexer):
+ # GH37750
+ key = 0 if (indexer is tm.iloc or len(obj) == 0) else obj.index[0]
+
+ with pytest.raises(IndexingError, match=_one_ellipsis_message):
+ indexer(obj)[..., ...]
+
+ with pytest.raises(IndexingError, match=_one_ellipsis_message):
+ indexer(obj)[..., [key], ...]
+
+ with pytest.raises(IndexingError, match=_one_ellipsis_message):
+ indexer(obj)[..., ..., key]
+
+ # one_ellipsis_message takes precedence over "Too many indexers"
+ # only when the first key is Ellipsis
+ with pytest.raises(IndexingError, match="Too many indexers"):
+ indexer(obj)[key, ..., ...]
+
+
+class TestLocWithMultiIndex:
+ @pytest.mark.parametrize(
+ "keys, expected",
+ [
+ (["b", "a"], [["b", "b", "a", "a"], [1, 2, 1, 2]]),
+ (["a", "b"], [["a", "a", "b", "b"], [1, 2, 1, 2]]),
+ ((["a", "b"], [1, 2]), [["a", "a", "b", "b"], [1, 2, 1, 2]]),
+ ((["a", "b"], [2, 1]), [["a", "a", "b", "b"], [2, 1, 2, 1]]),
+ ((["b", "a"], [2, 1]), [["b", "b", "a", "a"], [2, 1, 2, 1]]),
+ ((["b", "a"], [1, 2]), [["b", "b", "a", "a"], [1, 2, 1, 2]]),
+ ((["c", "a"], [2, 1]), [["c", "a", "a"], [1, 2, 1]]),
+ ],
+ )
+ @pytest.mark.parametrize("dim", ["index", "columns"])
+ def test_loc_getitem_multilevel_index_order(self, dim, keys, expected):
+ # GH#22797
+ # Try to respect order of keys given for MultiIndex.loc
+ kwargs = {dim: [["c", "a", "a", "b", "b"], [1, 1, 2, 1, 2]]}
+ df = DataFrame(np.arange(25).reshape(5, 5), **kwargs)
+ exp_index = MultiIndex.from_arrays(expected)
+ if dim == "index":
+ res = df.loc[keys, :]
+ tm.assert_index_equal(res.index, exp_index)
+ elif dim == "columns":
+ res = df.loc[:, keys]
+ tm.assert_index_equal(res.columns, exp_index)
+
+ def test_loc_preserve_names(self, multiindex_year_month_day_dataframe_random_data):
+ ymd = multiindex_year_month_day_dataframe_random_data
+
+ result = ymd.loc[2000]
+ result2 = ymd["A"].loc[2000]
+ assert result.index.names == ymd.index.names[1:]
+ assert result2.index.names == ymd.index.names[1:]
+
+ result = ymd.loc[2000, 2]
+ result2 = ymd["A"].loc[2000, 2]
+ assert result.index.name == ymd.index.names[2]
+ assert result2.index.name == ymd.index.names[2]
+
+ def test_loc_getitem_multiindex_nonunique_len_zero(self):
+ # GH#13691
+ mi = MultiIndex.from_product([[0], [1, 1]])
+ ser = Series(0, index=mi)
+
+ res = ser.loc[[]]
+
+ expected = ser[:0]
+ tm.assert_series_equal(res, expected)
+
+ res2 = ser.loc[ser.iloc[0:0]]
+ tm.assert_series_equal(res2, expected)
+
+ def test_loc_getitem_access_none_value_in_multiindex(self):
+ # GH#34318: test that you can access a None value using .loc
+ # through a Multiindex
+
+ ser = Series([None], MultiIndex.from_arrays([["Level1"], ["Level2"]]))
+ result = ser.loc[("Level1", "Level2")]
+ assert result is None
+
+ midx = MultiIndex.from_product([["Level1"], ["Level2_a", "Level2_b"]])
+ ser = Series([None] * len(midx), dtype=object, index=midx)
+ result = ser.loc[("Level1", "Level2_a")]
+ assert result is None
+
+ ser = Series([1] * len(midx), dtype=object, index=midx)
+ result = ser.loc[("Level1", "Level2_a")]
+ assert result == 1
+
+ def test_loc_setitem_multiindex_slice(self):
+ # GH 34870
+
+ index = MultiIndex.from_tuples(
+ zip(
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ),
+ names=["first", "second"],
+ )
+
+ result = Series([1, 1, 1, 1, 1, 1, 1, 1], index=index)
+ result.loc[("baz", "one") : ("foo", "two")] = 100
+
+ expected = Series([1, 1, 100, 100, 100, 100, 1, 1], index=index)
+
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_getitem_slice_datetime_objs_with_datetimeindex(self):
+ times = date_range("2000-01-01", freq="10min", periods=100000)
+ ser = Series(range(100000), times)
+ result = ser.loc[datetime(1900, 1, 1) : datetime(2100, 1, 1)]
+ tm.assert_series_equal(result, ser)
+
+ def test_loc_getitem_datetime_string_with_datetimeindex(self):
+ # GH 16710
+ df = DataFrame(
+ {"a": range(10), "b": range(10)},
+ index=date_range("2010-01-01", "2010-01-10", unit="ns"),
+ )
+ result = df.loc[["2010-01-01", "2010-01-05"], ["a", "b"]]
+ expected = DataFrame(
+ {"a": [0, 4], "b": [0, 4]},
+ index=DatetimeIndex(["2010-01-01", "2010-01-05"]).as_unit("ns"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_getitem_sorted_index_level_with_duplicates(self):
+ # GH#4516 sorting a MultiIndex with duplicates and multiple dtypes
+ mi = MultiIndex.from_tuples(
+ [
+ ("foo", "bar"),
+ ("foo", "bar"),
+ ("bah", "bam"),
+ ("bah", "bam"),
+ ("foo", "bar"),
+ ("bah", "bam"),
+ ],
+ names=["A", "B"],
+ )
+ df = DataFrame(
+ [
+ [1.0, 1],
+ [2.0, 2],
+ [3.0, 3],
+ [4.0, 4],
+ [5.0, 5],
+ [6.0, 6],
+ ],
+ index=mi,
+ columns=["C", "D"],
+ )
+ df = df.sort_index(level=0)
+
+ expected = DataFrame(
+ [[1.0, 1], [2.0, 2], [5.0, 5]], columns=["C", "D"], index=mi.take([0, 1, 4])
+ )
+
+ result = df.loc[("foo", "bar")]
+ tm.assert_frame_equal(result, expected)
+
+ def test_additional_element_to_categorical_series_loc(self):
+ # GH#47677
+ result = Series(["a", "b", "c"], dtype="category")
+ result.loc[3] = 0
+ expected = Series(["a", "b", "c", 0], dtype="object")
+ tm.assert_series_equal(result, expected)
+
+ def test_additional_categorical_element_loc(self):
+ # GH#47677
+ result = Series(["a", "b", "c"], dtype="category")
+ result.loc[3] = "a"
+ expected = Series(["a", "b", "c", "a"], dtype="category")
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_set_nan_in_categorical_series(self, any_numeric_ea_dtype):
+ # GH#47677
+ srs = Series(
+ [1, 2, 3],
+ dtype=CategoricalDtype(Index([1, 2, 3], dtype=any_numeric_ea_dtype)),
+ )
+ # enlarge
+ srs.loc[3] = np.nan
+ expected = Series(
+ [1, 2, 3, np.nan],
+ dtype=CategoricalDtype(Index([1, 2, 3], dtype=any_numeric_ea_dtype)),
+ )
+ tm.assert_series_equal(srs, expected)
+ # set into
+ srs.loc[1] = np.nan
+ expected = Series(
+ [1, np.nan, 3, np.nan],
+ dtype=CategoricalDtype(Index([1, 2, 3], dtype=any_numeric_ea_dtype)),
+ )
+ tm.assert_series_equal(srs, expected)
+
+ @pytest.mark.parametrize("na", (np.nan, pd.NA, None, pd.NaT))
+ def test_loc_consistency_series_enlarge_set_into(self, na):
+ # GH#47677
+ srs_enlarge = Series(["a", "b", "c"], dtype="category")
+ srs_enlarge.loc[3] = na
+
+ srs_setinto = Series(["a", "b", "c", "a"], dtype="category")
+ srs_setinto.loc[3] = na
+
+ tm.assert_series_equal(srs_enlarge, srs_setinto)
+ expected = Series(["a", "b", "c", na], dtype="category")
+ tm.assert_series_equal(srs_enlarge, expected)
+
+ def test_loc_getitem_preserves_index_level_category_dtype(self):
+ # GH#15166
+ df = DataFrame(
+ data=np.arange(2, 22, 2),
+ index=MultiIndex(
+ levels=[CategoricalIndex(["a", "b"]), range(10)],
+ codes=[[0] * 5 + [1] * 5, range(10)],
+ names=["Index1", "Index2"],
+ ),
+ )
+
+ expected = CategoricalIndex(
+ ["a", "b"],
+ categories=["a", "b"],
+ ordered=False,
+ name="Index1",
+ dtype="category",
+ )
+
+ result = df.index.levels[0]
+ tm.assert_index_equal(result, expected)
+
+ result = df.loc[["a"]].index.levels[0]
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.parametrize("lt_value", [30, 10])
+ def test_loc_multiindex_levels_contain_values_not_in_index_anymore(self, lt_value):
+ # GH#41170
+ df = DataFrame({"a": [12, 23, 34, 45]}, index=[list("aabb"), [0, 1, 2, 3]])
+ with pytest.raises(KeyError, match=r"\['b'\] not in index"):
+ df.loc[df["a"] < lt_value, :].loc[["b"], :]
+
+ def test_loc_multiindex_null_slice_na_level(self):
+ # GH#42055
+ lev1 = np.array([np.nan, np.nan])
+ lev2 = ["bar", "baz"]
+ mi = MultiIndex.from_arrays([lev1, lev2])
+ ser = Series([0, 1], index=mi)
+ result = ser.loc[:, "bar"]
+
+ # TODO: should we have name="bar"?
+ expected = Series([0], index=[np.nan])
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_drops_level(self):
+ # Based on test_series_varied_multiindex_alignment, where
+ # this used to fail to drop the first level
+ mi = MultiIndex.from_product(
+ [list("ab"), list("xy"), [1, 2]], names=["ab", "xy", "num"]
+ )
+ ser = Series(range(8), index=mi)
+
+ loc_result = ser.loc["a", :, :]
+ expected = ser.index.droplevel(0)[:4]
+ tm.assert_index_equal(loc_result.index, expected)
+
+
+class TestLocSetitemWithExpansion:
+ @td.skip_if_no("pyarrow")
+ def test_loc_setitem_with_expansion_preserves_ea_dtype(self):
+ # GH#41626 retain index.dtype in setitem-with-expansion
+ idx = Index([Timestamp(0).date()], dtype="date32[pyarrow]")
+ df = DataFrame({"A": range(1)}, index=idx)
+ item = Timestamp("1970-01-02").date()
+
+ df.loc[item] = 1
+
+ exp_index = Index([idx[0], item], dtype=idx.dtype)
+ tm.assert_index_equal(df.index, exp_index)
+
+ ser = df["A"].iloc[:-1]
+ ser.loc[item] = 1
+ tm.assert_index_equal(ser.index, exp_index)
+
+ def test_loc_setitem_with_expansion_large_dataframe(self, monkeypatch):
+ # GH#10692
+ size_cutoff = 50
+ with monkeypatch.context():
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", size_cutoff)
+ result = DataFrame({"x": range(size_cutoff)}, dtype="int64")
+ result.loc[size_cutoff] = size_cutoff
+ expected = DataFrame({"x": range(size_cutoff + 1)}, dtype="int64")
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_setitem_empty_series(self):
+ # GH#5226
+
+ # partially set with an empty object series
+ ser = Series(dtype=object)
+ ser.loc[1] = 1
+ tm.assert_series_equal(ser, Series([1], index=range(1, 2)))
+ ser.loc[3] = 3
+ tm.assert_series_equal(ser, Series([1, 3], index=[1, 3]))
+
+ def test_loc_setitem_empty_series_float(self):
+ # GH#5226
+
+ # partially set with an empty object series
+ ser = Series(dtype=object)
+ ser.loc[1] = 1.0
+ tm.assert_series_equal(ser, Series([1.0], index=range(1, 2)))
+ ser.loc[3] = 3.0
+ tm.assert_series_equal(ser, Series([1.0, 3.0], index=[1, 3]))
+
+ def test_loc_setitem_empty_series_str_idx(self):
+ # GH#5226
+
+ # partially set with an empty object series
+ ser = Series(dtype=object)
+ ser.loc["foo"] = 1
+ tm.assert_series_equal(ser, Series([1], index=Index(["foo"])))
+ ser.loc["bar"] = 3
+ tm.assert_series_equal(ser, Series([1, 3], index=Index(["foo", "bar"])))
+ ser.loc[3] = 4
+ tm.assert_series_equal(ser, Series([1, 3, 4], index=Index(["foo", "bar", 3])))
+
+ def test_loc_setitem_incremental_with_dst(self):
+ # GH#20724
+ base = datetime(2015, 11, 1, tzinfo=gettz("US/Pacific"))
+ idxs = [base + timedelta(seconds=i * 900) for i in range(16)]
+ result = Series([0], index=[idxs[0]])
+ for ts in idxs:
+ result.loc[ts] = 1
+ expected = Series(1, index=idxs)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "conv",
+ [
+ lambda x: x,
+ lambda x: x.to_datetime64(),
+ lambda x: x.to_pydatetime(),
+ lambda x: np.datetime64(x),
+ ],
+ ids=["self", "to_datetime64", "to_pydatetime", "np.datetime64"],
+ )
+ def test_loc_setitem_datetime_keys_cast(self, conv, using_infer_string):
+ # GH#9516, GH#51363 changed in 3.0 to not cast on Index.insert
+ dt1 = Timestamp("20130101 09:00:00")
+ dt2 = Timestamp("20130101 10:00:00")
+ df = DataFrame()
+ df.loc[conv(dt1), "one"] = 100
+ df.loc[conv(dt2), "one"] = 200
+
+ expected = DataFrame(
+ {"one": [100.0, 200.0]},
+ index=Index(
+ [conv(dt1), conv(dt2)], dtype=None if using_infer_string else object
+ ),
+ columns=Index(["one"]),
+ )
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_categorical_column_retains_dtype(self, ordered):
+ # GH16360
+ result = DataFrame({"A": [1]})
+ result.loc[:, "B"] = Categorical(["b"], ordered=ordered)
+ expected = DataFrame({"A": [1], "B": Categorical(["b"], ordered=ordered)})
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_setitem_with_expansion_and_existing_dst(self):
+ # GH#18308
+ start = Timestamp("2017-10-29 00:00:00+0200", tz="Europe/Madrid")
+ end = Timestamp("2017-10-29 03:00:00+0100", tz="Europe/Madrid")
+ ts = Timestamp("2016-10-10 03:00:00", tz="Europe/Madrid")
+ idx = date_range(start, end, inclusive="left", freq="h")
+ assert ts not in idx # i.e. result.loc setitem is with-expansion
+
+ result = DataFrame(index=idx, columns=["value"])
+ result.loc[ts, "value"] = 12
+ expected = DataFrame(
+ [np.nan] * len(idx) + [12],
+ index=idx.append(DatetimeIndex([ts])),
+ columns=["value"],
+ dtype=object,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_setitem_with_expansion(self):
+ # indexing - setting an element
+ df = DataFrame(
+ data=to_datetime(["2015-03-30 20:12:32", "2015-03-12 00:11:11"]),
+ columns=["time"],
+ )
+ df["new_col"] = ["new", "old"]
+ df.time = df.set_index("time").index.tz_localize("UTC")
+ v = df[df.new_col == "new"].set_index("time").index.tz_convert("US/Pacific")
+
+ # pre-2.0 trying to set a single element on a part of a different
+ # timezone converted to object; in 2.0 it retains dtype
+ df2 = df.copy()
+ df2.loc[df2.new_col == "new", "time"] = v
+
+ expected = Series([v[0].tz_convert("UTC"), df.loc[1, "time"]], name="time")
+ tm.assert_series_equal(df2.time, expected)
+
+ v = df.loc[df.new_col == "new", "time"] + Timedelta("1s").as_unit("s")
+ df.loc[df.new_col == "new", "time"] = v
+ tm.assert_series_equal(df.loc[df.new_col == "new", "time"], v)
+
+ def test_loc_setitem_with_expansion_inf_upcast_empty(self):
+ # Test with np.inf in columns
+ df = DataFrame()
+ df.loc[0, 0] = 1
+ df.loc[1, 1] = 2
+ df.loc[0, np.inf] = 3
+
+ result = df.columns
+ expected = Index([0, 1, np.inf], dtype=np.float64)
+ tm.assert_index_equal(result, expected)
+
+ @pytest.mark.filterwarnings("ignore:indexing past lexsort depth")
+ @pytest.mark.parametrize("has_ref", [True, False])
+ def test_loc_setitem_with_expansion_nonunique_index(self, index, has_ref):
+ # GH#40096
+ if not len(index):
+ pytest.skip("Not relevant for empty Index")
+
+ index = index.repeat(2) # ensure non-unique
+ N = len(index)
+ arr = np.arange(N).astype(np.int64)
+
+ orig = DataFrame(arr, index=index)
+
+ # key that will requiring object-dtype casting in the index
+ key = "kapow"
+ assert key not in index # otherwise test is invalid
+ # TODO: using a tuple key breaks here in many cases
+
+ exp_index = index.insert(len(index), key)
+ if isinstance(index, MultiIndex):
+ assert exp_index[-1][0] == key
+ else:
+ assert exp_index[-1] == key
+ exp_data = np.arange(N + 1).astype(np.float64)
+ expected = DataFrame(exp_data, index=exp_index)
+
+ # Add new row, but no new columns
+ df = orig.copy()
+ if has_ref:
+ view = df[:]
+ df.loc[key, 0] = N
+ tm.assert_frame_equal(df, expected)
+
+ # add new row on a Series
+ ser = orig.copy()[0]
+ if has_ref:
+ view = ser[:]
+ ser.loc[key] = N
+ # the series machinery lets us preserve int dtype instead of float
+ expected = expected[0].astype(np.int64)
+ tm.assert_series_equal(ser, expected)
+
+ # add new row and new column
+ df = orig.copy()
+ if has_ref:
+ view = df[:] # noqa: F841
+ df.loc[key, 1] = N
+ expected = DataFrame(
+ {0: [*list(arr), np.nan], 1: [np.nan] * N + [float(N)]},
+ index=exp_index,
+ )
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_with_expansion_preserves_nullable_int(
+ self, any_numeric_ea_dtype
+ ):
+ # GH#42099
+ ser = Series([0, 1, 2, 3], dtype=any_numeric_ea_dtype)
+ df = DataFrame({"data": ser})
+
+ result = DataFrame(index=df.index)
+ result.loc[df.index, "data"] = ser
+
+ tm.assert_frame_equal(result, df, check_column_type=False)
+
+ result = DataFrame(index=df.index)
+ result.loc[df.index, "data"] = ser._values
+ tm.assert_frame_equal(result, df, check_column_type=False)
+
+ def test_loc_setitem_ea_not_full_column(self):
+ # GH#39163
+ df = DataFrame({"A": range(5)})
+
+ val = date_range("2016-01-01", periods=3, tz="US/Pacific")
+
+ df.loc[[0, 1, 2], "B"] = val
+
+ bex = val.append(DatetimeIndex([pd.NaT, pd.NaT], dtype=val.dtype))
+ expected = DataFrame({"A": range(5), "B": bex})
+ assert expected.dtypes["B"] == val.dtype
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_with_expansion_new_row_and_new_columns(self):
+ # GH#58316
+ df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
+ df.loc["x", ["C", "D"]] = 91
+ expected = DataFrame(
+ {
+ "A": [1.0, 2.0, 3.0, np.nan],
+ "B": [4.0, 5.0, 6.0, np.nan],
+ "C": [np.nan, np.nan, np.nan, 91.0],
+ "D": [np.nan, np.nan, np.nan, 91.0],
+ },
+ index=Index([0, 1, 2, "x"]),
+ )
+ tm.assert_frame_equal(df, expected)
+
+
+class TestLocCallable:
+ def test_frame_loc_getitem_callable(self):
+ # GH#11485
+ df = DataFrame({"A": [1, 2, 3, 4], "B": list("aabb"), "C": [1, 2, 3, 4]})
+ # iloc cannot use boolean Series (see GH3635)
+
+ # return bool indexer
+ res = df.loc[lambda x: x.A > 2]
+ tm.assert_frame_equal(res, df.loc[df.A > 2])
+
+ res = df.loc[lambda x: x.B == "b", :]
+ tm.assert_frame_equal(res, df.loc[df.B == "b", :])
+
+ res = df.loc[lambda x: x.A > 2, lambda x: x.columns == "B"]
+ tm.assert_frame_equal(res, df.loc[df.A > 2, [False, True, False]])
+
+ res = df.loc[lambda x: x.A > 2, lambda x: "B"]
+ tm.assert_series_equal(res, df.loc[df.A > 2, "B"])
+
+ res = df.loc[lambda x: x.A > 2, lambda x: ["A", "B"]]
+ tm.assert_frame_equal(res, df.loc[df.A > 2, ["A", "B"]])
+
+ res = df.loc[lambda x: x.A == 2, lambda x: ["A", "B"]]
+ tm.assert_frame_equal(res, df.loc[df.A == 2, ["A", "B"]])
+
+ # scalar
+ res = df.loc[lambda x: 1, lambda x: "A"]
+ assert res == df.loc[1, "A"]
+
+ def test_frame_loc_getitem_callable_mixture(self):
+ # GH#11485
+ df = DataFrame({"A": [1, 2, 3, 4], "B": list("aabb"), "C": [1, 2, 3, 4]})
+
+ res = df.loc[lambda x: x.A > 2, ["A", "B"]]
+ tm.assert_frame_equal(res, df.loc[df.A > 2, ["A", "B"]])
+
+ res = df.loc[[2, 3], lambda x: ["A", "B"]]
+ tm.assert_frame_equal(res, df.loc[[2, 3], ["A", "B"]])
+
+ res = df.loc[3, lambda x: ["A", "B"]]
+ tm.assert_series_equal(res, df.loc[3, ["A", "B"]])
+
+ def test_frame_loc_getitem_callable_labels(self):
+ # GH#11485
+ df = DataFrame({"X": [1, 2, 3, 4], "Y": list("aabb")}, index=list("ABCD"))
+
+ # return label
+ res = df.loc[lambda x: ["A", "C"]]
+ tm.assert_frame_equal(res, df.loc[["A", "C"]])
+
+ res = df.loc[lambda x: ["A", "C"], :]
+ tm.assert_frame_equal(res, df.loc[["A", "C"], :])
+
+ res = df.loc[lambda x: ["A", "C"], lambda x: "X"]
+ tm.assert_series_equal(res, df.loc[["A", "C"], "X"])
+
+ res = df.loc[lambda x: ["A", "C"], lambda x: ["X"]]
+ tm.assert_frame_equal(res, df.loc[["A", "C"], ["X"]])
+
+ # mixture
+ res = df.loc[["A", "C"], lambda x: "X"]
+ tm.assert_series_equal(res, df.loc[["A", "C"], "X"])
+
+ res = df.loc[["A", "C"], lambda x: ["X"]]
+ tm.assert_frame_equal(res, df.loc[["A", "C"], ["X"]])
+
+ res = df.loc[lambda x: ["A", "C"], "X"]
+ tm.assert_series_equal(res, df.loc[["A", "C"], "X"])
+
+ res = df.loc[lambda x: ["A", "C"], ["X"]]
+ tm.assert_frame_equal(res, df.loc[["A", "C"], ["X"]])
+
+ def test_frame_loc_setitem_callable(self):
+ # GH#11485
+ df = DataFrame(
+ {"X": [1, 2, 3, 4], "Y": Series(list("aabb"), dtype=object)},
+ index=list("ABCD"),
+ )
+
+ # return label
+ res = df.copy()
+ res.loc[lambda x: ["A", "C"]] = -20
+ exp = df.copy()
+ exp.loc[["A", "C"]] = -20
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.loc[lambda x: ["A", "C"], :] = 20
+ exp = df.copy()
+ exp.loc[["A", "C"], :] = 20
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.loc[lambda x: ["A", "C"], lambda x: "X"] = -1
+ exp = df.copy()
+ exp.loc[["A", "C"], "X"] = -1
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.loc[lambda x: ["A", "C"], lambda x: ["X"]] = [5, 10]
+ exp = df.copy()
+ exp.loc[["A", "C"], ["X"]] = [5, 10]
+ tm.assert_frame_equal(res, exp)
+
+ # mixture
+ res = df.copy()
+ res.loc[["A", "C"], lambda x: "X"] = np.array([-1, -2])
+ exp = df.copy()
+ exp.loc[["A", "C"], "X"] = np.array([-1, -2])
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.loc[["A", "C"], lambda x: ["X"]] = 10
+ exp = df.copy()
+ exp.loc[["A", "C"], ["X"]] = 10
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.loc[lambda x: ["A", "C"], "X"] = -2
+ exp = df.copy()
+ exp.loc[["A", "C"], "X"] = -2
+ tm.assert_frame_equal(res, exp)
+
+ res = df.copy()
+ res.loc[lambda x: ["A", "C"], ["X"]] = -4
+ exp = df.copy()
+ exp.loc[["A", "C"], ["X"]] = -4
+ tm.assert_frame_equal(res, exp)
+
+
+class TestPartialStringSlicing:
+ def test_loc_getitem_partial_string_slicing_datetimeindex(self):
+ # GH#35509
+ df = DataFrame(
+ {"col1": ["a", "b", "c"], "col2": [1, 2, 3]},
+ index=to_datetime(["2020-08-01", "2020-07-02", "2020-08-05"]),
+ )
+ expected = DataFrame(
+ {"col1": ["a", "c"], "col2": [1, 3]},
+ index=to_datetime(["2020-08-01", "2020-08-05"]),
+ )
+ result = df.loc["2020-08"]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_getitem_partial_string_slicing_with_periodindex(self):
+ pi = pd.period_range(start="2017-01-01", end="2018-01-01", freq="M")
+ ser = pi.to_series()
+ result = ser.loc[:"2017-12"]
+ expected = ser.iloc[:-1]
+
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_getitem_partial_string_slicing_with_timedeltaindex(self):
+ ix = timedelta_range(start="1 day", end="2 days", freq="1h")
+ ser = ix.to_series()
+ result = ser.loc[:"1 days"]
+ expected = ser.iloc[:-1]
+
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_getitem_str_timedeltaindex(self):
+ # GH#16896
+ df = DataFrame({"x": range(3)}, index=to_timedelta(range(3), unit="days"))
+ expected = df.iloc[0]
+ sliced = df.loc["0 days"]
+ tm.assert_series_equal(sliced, expected)
+
+ @pytest.mark.parametrize("indexer_end", [None, "2020-01-02 23:59:59.999999999"])
+ def test_loc_getitem_partial_slice_non_monotonicity(
+ self, tz_aware_fixture, indexer_end, frame_or_series
+ ):
+ # GH#33146
+ obj = frame_or_series(
+ [1] * 5,
+ index=DatetimeIndex(
+ [
+ Timestamp("2019-12-30"),
+ Timestamp("2020-01-01"),
+ Timestamp("2019-12-25"),
+ Timestamp("2020-01-02 23:59:59.999999999"),
+ Timestamp("2019-12-19"),
+ ],
+ tz=tz_aware_fixture,
+ ),
+ )
+ expected = frame_or_series(
+ [1] * 2,
+ index=DatetimeIndex(
+ [
+ Timestamp("2020-01-01"),
+ Timestamp("2020-01-02 23:59:59.999999999"),
+ ],
+ tz=tz_aware_fixture,
+ ),
+ )
+ indexer = slice("2020-01-01", indexer_end)
+
+ result = obj[indexer]
+ tm.assert_equal(result, expected)
+
+ result = obj.loc[indexer]
+ tm.assert_equal(result, expected)
+
+
+class TestLabelSlicing:
+ def test_loc_getitem_slicing_datetimes_frame(self):
+ # GH#7523
+
+ # unique
+ df_unique = DataFrame(
+ np.arange(4.0, dtype="float64"),
+ index=[datetime(2001, 1, i, 10, 00) for i in [1, 2, 3, 4]],
+ )
+
+ # duplicates
+ df_dups = DataFrame(
+ np.arange(5.0, dtype="float64"),
+ index=[datetime(2001, 1, i, 10, 00) for i in [1, 2, 2, 3, 4]],
+ )
+
+ for df in [df_unique, df_dups]:
+ result = df.loc[datetime(2001, 1, 1, 10) :]
+ tm.assert_frame_equal(result, df)
+ result = df.loc[: datetime(2001, 1, 4, 10)]
+ tm.assert_frame_equal(result, df)
+ result = df.loc[datetime(2001, 1, 1, 10) : datetime(2001, 1, 4, 10)]
+ tm.assert_frame_equal(result, df)
+
+ result = df.loc[datetime(2001, 1, 1, 11) :]
+ expected = df.iloc[1:]
+ tm.assert_frame_equal(result, expected)
+ result = df.loc["20010101 11":]
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_getitem_label_slice_across_dst(self):
+ # GH#21846
+ idx = date_range(
+ "2017-10-29 01:30:00", tz="Europe/Berlin", periods=5, freq="30 min"
+ )
+ series2 = Series([0, 1, 2, 3, 4], index=idx)
+
+ t_1 = Timestamp("2017-10-29 02:30:00+02:00", tz="Europe/Berlin")
+ t_2 = Timestamp("2017-10-29 02:00:00+01:00", tz="Europe/Berlin")
+ result = series2.loc[t_1:t_2]
+ expected = Series([2, 3], index=idx[2:4])
+ tm.assert_series_equal(result, expected)
+
+ result = series2[t_1]
+ expected = 2
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ pd.period_range(start="2017-01-01", end="2018-01-01", freq="M"),
+ timedelta_range(start="1 day", end="2 days", freq="1h"),
+ ],
+ )
+ def test_loc_getitem_label_slice_period_timedelta(self, index):
+ ser = index.to_series()
+ result = ser.loc[: index[-2]]
+ expected = ser.iloc[:-1]
+
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_getitem_slice_floats_inexact(self):
+ index = [52195.504153, 52196.303147, 52198.369883]
+ df = DataFrame(np.random.default_rng(2).random((3, 2)), index=index)
+
+ s1 = df.loc[52195.1:52196.5]
+ assert len(s1) == 2
+
+ s1 = df.loc[52195.1:52196.6]
+ assert len(s1) == 2
+
+ s1 = df.loc[52195.1:52198.9]
+ assert len(s1) == 3
+
+ def test_loc_getitem_float_slice_floatindex(self, float_numpy_dtype):
+ dtype = float_numpy_dtype
+ ser = Series(
+ np.random.default_rng(2).random(10), index=np.arange(10, 20, dtype=dtype)
+ )
+
+ assert len(ser.loc[12.0:]) == 8
+ assert len(ser.loc[12.5:]) == 7
+
+ idx = np.arange(10, 20, dtype=dtype)
+ idx[2] = 12.2
+ ser.index = idx
+ assert len(ser.loc[12.0:]) == 8
+ assert len(ser.loc[12.5:]) == 7
+
+ @pytest.mark.parametrize(
+ "start,stop, expected_slice",
+ [
+ [np.timedelta64(0, "ns"), None, slice(0, 11)],
+ [np.timedelta64(1, "D"), np.timedelta64(6, "D"), slice(1, 7)],
+ [None, np.timedelta64(4, "D"), slice(0, 5)],
+ ],
+ )
+ def test_loc_getitem_slice_label_td64obj(self, start, stop, expected_slice):
+ # GH#20393
+ ser = Series(range(11), timedelta_range("0 days", "10 days"))
+ result = ser.loc[slice(start, stop)]
+ expected = ser.iloc[expected_slice]
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("start", ["2018", "2020"])
+ def test_loc_getitem_slice_unordered_dt_index(self, frame_or_series, start):
+ obj = frame_or_series(
+ [1, 2, 3],
+ index=[Timestamp("2016"), Timestamp("2019"), Timestamp("2017")],
+ )
+ with pytest.raises(
+ KeyError, match="Value based partial slicing on non-monotonic"
+ ):
+ obj.loc[start:"2022"]
+
+ @pytest.mark.parametrize("value", [1, 1.5])
+ def test_loc_getitem_slice_labels_int_in_object_index(self, frame_or_series, value):
+ # GH: 26491
+ obj = frame_or_series(range(4), index=[value, "first", 2, "third"])
+ result = obj.loc[value:"third"]
+ expected = frame_or_series(range(4), index=[value, "first", 2, "third"])
+ tm.assert_equal(result, expected)
+
+ def test_loc_getitem_slice_columns_mixed_dtype(self):
+ # GH: 20975
+ df = DataFrame({"test": 1, 1: 2, 2: 3}, index=[0])
+ expected = DataFrame(
+ data=[[2, 3]], index=[0], columns=Index([1, 2], dtype=object)
+ )
+ tm.assert_frame_equal(df.loc[:, 1:], expected)
+
+
+class TestLocBooleanLabelsAndSlices:
+ @pytest.mark.parametrize("bool_value", [True, False])
+ def test_loc_bool_incompatible_index_raises(
+ self, index, frame_or_series, bool_value
+ ):
+ # GH20432
+ message = f"{bool_value}: boolean label can not be used without a boolean index"
+ if index.inferred_type != "boolean":
+ obj = frame_or_series(index=index, dtype="object")
+ with pytest.raises(KeyError, match=message):
+ obj.loc[bool_value]
+
+ @pytest.mark.parametrize("bool_value", [True, False])
+ def test_loc_bool_should_not_raise(self, frame_or_series, bool_value):
+ obj = frame_or_series(
+ index=Index([True, False], dtype="boolean"), dtype="object"
+ )
+ obj.loc[bool_value]
+
+ def test_loc_bool_slice_raises(self, index, frame_or_series):
+ # GH20432
+ message = (
+ r"slice\(True, False, None\): boolean values can not be used in a slice"
+ )
+ obj = frame_or_series(index=index, dtype="object")
+ with pytest.raises(TypeError, match=message):
+ obj.loc[True:False]
+
+
+class TestLocBooleanMask:
+ def test_loc_setitem_bool_mask_timedeltaindex(self):
+ # GH#14946
+ df = DataFrame({"x": range(10)})
+ df.index = to_timedelta(range(10), unit="s")
+ conditions = [df["x"] > 3, df["x"] == 3, df["x"] < 3]
+ expected_data = [
+ [0, 1, 2, 3, 10, 10, 10, 10, 10, 10],
+ [0, 1, 2, 10, 4, 5, 6, 7, 8, 9],
+ [10, 10, 10, 3, 4, 5, 6, 7, 8, 9],
+ ]
+ for cond, data in zip(conditions, expected_data):
+ result = df.copy()
+ result.loc[cond, "x"] = 10
+
+ expected = DataFrame(
+ data,
+ index=to_timedelta(range(10), unit="s"),
+ columns=["x"],
+ dtype="int64",
+ )
+ tm.assert_frame_equal(expected, result)
+
+ @pytest.mark.parametrize("tz", [None, "UTC"])
+ def test_loc_setitem_mask_with_datetimeindex_tz(self, tz):
+ # GH#16889
+ # support .loc with alignment and tz-aware DatetimeIndex
+ mask = np.array([True, False, True, False])
+
+ idx = date_range("20010101", periods=4, tz=tz)
+ df = DataFrame({"a": np.arange(4)}, index=idx).astype("float64")
+
+ result = df.copy()
+ result.loc[mask, :] = df.loc[mask, :]
+ tm.assert_frame_equal(result, df)
+
+ result = df.copy()
+ result.loc[mask] = df.loc[mask]
+ tm.assert_frame_equal(result, df)
+
+ def test_loc_setitem_mask_and_label_with_datetimeindex(self):
+ # GH#9478
+ # a datetimeindex alignment issue with partial setting
+ df = DataFrame(
+ np.arange(6.0).reshape(3, 2),
+ columns=list("AB"),
+ index=date_range("1/1/2000", periods=3, freq="1h"),
+ )
+ expected = df.copy()
+ expected["C"] = [expected.index[0], pd.NaT, pd.NaT]
+
+ mask = df.A < 1
+ df.loc[mask, "C"] = df.loc[mask].index
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_mask_td64_series_value(self):
+ # GH#23462 key list of bools, value is a Series
+ td1 = Timedelta(0)
+ td2 = Timedelta(28767471428571405)
+ df = DataFrame({"col": Series([td1, td2])})
+ df_copy = df.copy()
+ ser = Series([td1])
+
+ expected = df["col"].iloc[1]._value
+ df.loc[[True, False]] = ser
+ result = df["col"].iloc[1]._value
+
+ assert expected == result
+ tm.assert_frame_equal(df, df_copy)
+
+ def test_loc_setitem_boolean_and_column(self, float_frame):
+ expected = float_frame.copy()
+ mask = float_frame["A"] > 0
+
+ float_frame.loc[mask, "B"] = 0
+
+ values = expected.values.copy()
+ values[mask.values, 1] = 0
+ expected = DataFrame(values, index=expected.index, columns=expected.columns)
+ tm.assert_frame_equal(float_frame, expected)
+
+ def test_loc_setitem_ndframe_values_alignment(self):
+ # GH#45501
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ df.loc[[False, False, True], ["a"]] = DataFrame(
+ {"a": [10, 20, 30]}, index=[2, 1, 0]
+ )
+
+ expected = DataFrame({"a": [1, 2, 10], "b": [4, 5, 6]})
+ tm.assert_frame_equal(df, expected)
+
+ # same thing with Series RHS
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ df.loc[[False, False, True], ["a"]] = Series([10, 11, 12], index=[2, 1, 0])
+ tm.assert_frame_equal(df, expected)
+
+ # same thing but setting "a" instead of ["a"]
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ df.loc[[False, False, True], "a"] = Series([10, 11, 12], index=[2, 1, 0])
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ df_orig = df.copy()
+ ser = df["a"]
+ ser.loc[[False, False, True]] = Series([10, 11, 12], index=[2, 1, 0])
+ tm.assert_frame_equal(df, df_orig)
+
+ def test_loc_indexer_empty_broadcast(self):
+ # GH#51450
+ df = DataFrame({"a": [], "b": []}, dtype=object)
+ expected = df.copy()
+ df.loc[np.array([], dtype=np.bool_), ["a"]] = df["a"].copy()
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_indexer_all_false_broadcast(self):
+ # GH#51450
+ df = DataFrame({"a": ["x"], "b": ["y"]}, dtype=object)
+ expected = df.copy()
+ df.loc[np.array([False], dtype=np.bool_), ["a"]] = df["b"].copy()
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_indexer_length_one(self):
+ # GH#51435
+ df = DataFrame({"a": ["x"], "b": ["y"]}, dtype=object)
+ expected = DataFrame({"a": ["y"], "b": ["y"]}, dtype=object)
+ df.loc[np.array([True], dtype=np.bool_), ["a"]] = df["b"].copy()
+ tm.assert_frame_equal(df, expected)
+
+
+class TestLocListlike:
+ @pytest.mark.parametrize("box", [lambda x: x, np.asarray, list])
+ def test_loc_getitem_list_of_labels_categoricalindex_with_na(self, box):
+ # passing a list can include valid categories _or_ NA values
+ ci = CategoricalIndex(["A", "B", np.nan])
+ ser = Series(range(3), index=ci)
+
+ result = ser.loc[box(ci)]
+ tm.assert_series_equal(result, ser)
+
+ result = ser[box(ci)]
+ tm.assert_series_equal(result, ser)
+
+ result = ser.to_frame().loc[box(ci)]
+ tm.assert_frame_equal(result, ser.to_frame())
+
+ ser2 = ser[:-1]
+ ci2 = ci[1:]
+ # but if there are no NAs present, this should raise KeyError
+ msg = "not in index"
+ with pytest.raises(KeyError, match=msg):
+ ser2.loc[box(ci2)]
+
+ with pytest.raises(KeyError, match=msg):
+ ser2[box(ci2)]
+
+ with pytest.raises(KeyError, match=msg):
+ ser2.to_frame().loc[box(ci2)]
+
+ def test_loc_getitem_series_label_list_missing_values(self):
+ # gh-11428
+ key = np.array(
+ ["2001-01-04", "2001-01-02", "2001-01-04", "2001-01-14"], dtype="datetime64"
+ )
+ ser = Series([2, 5, 8, 11], date_range("2001-01-01", freq="D", periods=4))
+ with pytest.raises(KeyError, match="not in index"):
+ ser.loc[key]
+
+ def test_loc_getitem_series_label_list_missing_integer_values(self):
+ # GH: 25927
+ ser = Series(
+ index=np.array([9730701000001104, 10049011000001109]),
+ data=np.array([999000011000001104, 999000011000001104]),
+ )
+ with pytest.raises(KeyError, match="not in index"):
+ ser.loc[np.array([9730701000001104, 10047311000001102])]
+
+ @pytest.mark.parametrize("to_period", [True, False])
+ def test_loc_getitem_listlike_of_datetimelike_keys(self, to_period):
+ # GH#11497
+
+ idx = date_range("2011-01-01", "2011-01-02", freq="D", name="idx")
+ if to_period:
+ idx = idx.to_period("D")
+ ser = Series([0.1, 0.2], index=idx, name="s")
+
+ keys = [Timestamp("2011-01-01"), Timestamp("2011-01-02")]
+ if to_period:
+ keys = [x.to_period("D") for x in keys]
+ result = ser.loc[keys]
+ exp = Series([0.1, 0.2], index=idx, name="s")
+ if not to_period:
+ exp.index = exp.index._with_freq(None)
+ tm.assert_series_equal(result, exp, check_index_type=True)
+
+ keys = [
+ Timestamp("2011-01-02"),
+ Timestamp("2011-01-02"),
+ Timestamp("2011-01-01"),
+ ]
+ if to_period:
+ keys = [x.to_period("D") for x in keys]
+ exp = Series(
+ [0.2, 0.2, 0.1], index=Index(keys, name="idx", dtype=idx.dtype), name="s"
+ )
+ result = ser.loc[keys]
+ tm.assert_series_equal(result, exp, check_index_type=True)
+
+ keys = [
+ Timestamp("2011-01-03"),
+ Timestamp("2011-01-02"),
+ Timestamp("2011-01-03"),
+ ]
+ if to_period:
+ keys = [x.to_period("D") for x in keys]
+
+ with pytest.raises(KeyError, match="not in index"):
+ ser.loc[keys]
+
+ def test_loc_named_index(self):
+ # GH 42790
+ df = DataFrame(
+ [[1, 2], [4, 5], [7, 8]],
+ index=["cobra", "viper", "sidewinder"],
+ columns=["max_speed", "shield"],
+ )
+ expected = df.iloc[:2]
+ expected.index.name = "foo"
+ result = df.loc[Index(["cobra", "viper"], name="foo")]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "columns, column_key, expected_columns",
+ [
+ ([2011, 2012, 2013], [2011, 2012], [0, 1]),
+ ([2011, 2012, "All"], [2011, 2012], [0, 1]),
+ ([2011, 2012, "All"], [2011, "All"], [0, 2]),
+ ],
+)
+def test_loc_getitem_label_list_integer_labels(columns, column_key, expected_columns):
+ # gh-14836
+ df = DataFrame(
+ np.random.default_rng(2).random((3, 3)), columns=columns, index=list("ABC")
+ )
+ expected = df.iloc[:, expected_columns]
+ result = df.loc[["A", "B", "C"], column_key]
+
+ tm.assert_frame_equal(result, expected, check_column_type=True)
+
+
+def test_loc_setitem_float_intindex():
+ # GH 8720
+ rand_data = np.random.default_rng(2).standard_normal((8, 4))
+ result = DataFrame(rand_data)
+ result.loc[:, 0.5] = np.nan
+ expected_data = np.hstack((rand_data, np.array([np.nan] * 8).reshape(8, 1)))
+ expected = DataFrame(expected_data, columns=[0.0, 1.0, 2.0, 3.0, 0.5])
+ tm.assert_frame_equal(result, expected)
+
+ result = DataFrame(rand_data)
+ result.loc[:, 0.5] = np.nan
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_axis_1_slice():
+ # GH 10586
+ cols = [(yr, m) for yr in [2014, 2015] for m in [7, 8, 9, 10]]
+ df = DataFrame(
+ np.ones((10, 8)),
+ index=tuple("ABCDEFGHIJ"),
+ columns=MultiIndex.from_tuples(cols),
+ )
+ result = df.loc(axis=1)[(2014, 9) : (2015, 8)]
+ expected = DataFrame(
+ np.ones((10, 4)),
+ index=tuple("ABCDEFGHIJ"),
+ columns=MultiIndex.from_tuples([(2014, 9), (2014, 10), (2015, 7), (2015, 8)]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_set_dataframe_multiindex():
+ # GH 14592
+ expected = DataFrame(
+ "a", index=range(2), columns=MultiIndex.from_product([range(2), range(2)])
+ )
+ result = expected.copy()
+ result.loc[0, [(0, 1)]] = result.loc[0, [(0, 1)]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_loc_mixed_int_float():
+ # GH#19456
+ ser = Series(range(2), Index([1, 2.0], dtype=object))
+
+ result = ser.loc[1]
+ assert result == 0
+
+
+def test_loc_with_positional_slice_raises():
+ # GH#31840
+ ser = Series(range(4), index=["A", "B", "C", "D"])
+
+ with pytest.raises(TypeError, match="Slicing a positional slice with .loc"):
+ ser.loc[:3] = 2
+
+
+def test_loc_slice_disallows_positional():
+ # GH#16121, GH#24612, GH#31810
+ dti = date_range("2016-01-01", periods=3)
+ df = DataFrame(np.random.default_rng(2).random((3, 2)), index=dti)
+
+ ser = df[0]
+
+ msg = (
+ "cannot do slice indexing on DatetimeIndex with these "
+ r"indexers \[1\] of type int"
+ )
+
+ for obj in [df, ser]:
+ with pytest.raises(TypeError, match=msg):
+ obj.loc[1:3]
+
+ with pytest.raises(TypeError, match="Slicing a positional slice with .loc"):
+ # GH#31840 enforce incorrect behavior
+ obj.loc[1:3] = 1
+
+ with pytest.raises(TypeError, match=msg):
+ df.loc[1:3, 1]
+
+ with pytest.raises(TypeError, match="Slicing a positional slice with .loc"):
+ # GH#31840 enforce incorrect behavior
+ df.loc[1:3, 1] = 2
+
+
+def test_loc_datetimelike_mismatched_dtypes():
+ # GH#32650 dont mix and match datetime/timedelta/period dtypes
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 3)),
+ columns=["a", "b", "c"],
+ index=date_range("2012", freq="h", periods=5),
+ )
+ # create dataframe with non-unique DatetimeIndex
+ df = df.iloc[[0, 2, 2, 3]].copy()
+
+ dti = df.index
+ tdi = pd.TimedeltaIndex(dti.asi8) # matching i8 values
+
+ msg = r"None of \[TimedeltaIndex.* are in the \[index\]"
+ with pytest.raises(KeyError, match=msg):
+ df.loc[tdi]
+
+ with pytest.raises(KeyError, match=msg):
+ df["a"].loc[tdi]
+
+
+def test_loc_with_period_index_indexer():
+ # GH#4125
+ idx = pd.period_range("2002-01", "2003-12", freq="M")
+ df = DataFrame(np.random.default_rng(2).standard_normal((24, 10)), index=idx)
+ tm.assert_frame_equal(df, df.loc[idx])
+ tm.assert_frame_equal(df, df.loc[list(idx)])
+ tm.assert_frame_equal(df, df.loc[list(idx)])
+ tm.assert_frame_equal(df.iloc[0:5], df.loc[idx[0:5]])
+ tm.assert_frame_equal(df, df.loc[list(idx)])
+
+
+def test_loc_setitem_multiindex_timestamp():
+ # GH#13831
+ vals = np.random.default_rng(2).standard_normal((8, 6))
+ idx = date_range("1/1/2000", periods=8)
+ cols = ["A", "B", "C", "D", "E", "F"]
+ exp = DataFrame(vals, index=idx, columns=cols)
+ exp.loc[exp.index[1], ("A", "B")] = np.nan
+ vals[1][0:2] = np.nan
+ res = DataFrame(vals, index=idx, columns=cols)
+ tm.assert_frame_equal(res, exp)
+
+
+def test_loc_getitem_multiindex_tuple_level():
+ # GH#27591
+ lev1 = ["a", "b", "c"]
+ lev2 = [(0, 1), (1, 0)]
+ lev3 = [0, 1]
+ cols = MultiIndex.from_product([lev1, lev2, lev3], names=["x", "y", "z"])
+ df = DataFrame(6, index=range(5), columns=cols)
+
+ # the lev2[0] here should be treated as a single label, not as a sequence
+ # of labels
+ result = df.loc[:, (lev1[0], lev2[0], lev3[0])]
+
+ # TODO: i think this actually should drop levels
+ expected = df.iloc[:, :1]
+ tm.assert_frame_equal(result, expected)
+
+ alt = df.xs((lev1[0], lev2[0], lev3[0]), level=[0, 1, 2], axis=1)
+ tm.assert_frame_equal(alt, expected)
+
+ # same thing on a Series
+ ser = df.iloc[0]
+ expected2 = ser.iloc[:1]
+
+ alt2 = ser.xs((lev1[0], lev2[0], lev3[0]), level=[0, 1, 2], axis=0)
+ tm.assert_series_equal(alt2, expected2)
+
+ result2 = ser.loc[lev1[0], lev2[0], lev3[0]]
+ assert result2 == 6
+
+
+def test_loc_getitem_nullable_index_with_duplicates():
+ # GH#34497
+ df = DataFrame(
+ data=np.array([[1, 2, 3, 4], [5, 6, 7, 8], [1, 2, pd.NA, pd.NA]]).T,
+ columns=["a", "b", "c"],
+ dtype="Int64",
+ )
+ df2 = df.set_index("c")
+ assert df2.index.dtype == "Int64"
+
+ res = df2.loc[1]
+ expected = Series([1, 5], index=df2.columns, dtype="Int64", name=1)
+ tm.assert_series_equal(res, expected)
+
+ # pd.NA and duplicates in an object-dtype Index
+ df2.index = df2.index.astype(object)
+ res = df2.loc[1]
+ tm.assert_series_equal(res, expected)
+
+
+@pytest.mark.parametrize("value", [300, np.uint16(300), np.int16(300)])
+def test_loc_setitem_uint8_upcast(value):
+ # GH#26049
+
+ df = DataFrame([1, 2, 3, 4], columns=["col1"], dtype="uint8")
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc[2, "col1"] = value # value that can't be held in uint8
+
+
+@pytest.mark.parametrize(
+ "fill_val,exp_dtype",
+ [
+ (Timestamp("2022-01-06"), "datetime64[ns]"),
+ (Timestamp("2022-01-07", tz="US/Eastern"), "datetime64[ns, US/Eastern]"),
+ ],
+)
+def test_loc_setitem_using_datetimelike_str_as_index(fill_val, exp_dtype):
+ data = ["2022-01-02", "2022-01-03", "2022-01-04", fill_val.date()]
+ index = DatetimeIndex(data, tz=fill_val.tz, dtype=exp_dtype)
+ df = DataFrame([10, 11, 12, 14], columns=["a"], index=index)
+ # adding new row using an unexisting datetime-like str index
+ df.loc["2022-01-08", "a"] = 13
+
+ data.append("2022-01-08")
+ expected_index = DatetimeIndex(data, dtype=exp_dtype)
+ tm.assert_index_equal(df.index, expected_index, exact=True)
+
+
+def test_loc_set_int_dtype():
+ # GH#23326
+ df = DataFrame([list("abc")])
+ df.loc[:, "col1"] = 5
+
+ expected = DataFrame({0: ["a"], 1: ["b"], 2: ["c"], "col1": [5]})
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.filterwarnings(r"ignore:Period with BDay freq is deprecated:FutureWarning")
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+def test_loc_periodindex_3_levels():
+ # GH#24091
+ p_index = PeriodIndex(
+ ["20181101 1100", "20181101 1200", "20181102 1300", "20181102 1400"],
+ name="datetime",
+ freq="B",
+ )
+ mi_series = DataFrame(
+ [["A", "B", 1.0], ["A", "C", 2.0], ["Z", "Q", 3.0], ["W", "F", 4.0]],
+ index=p_index,
+ columns=["ONE", "TWO", "VALUES"],
+ )
+ mi_series = mi_series.set_index(["ONE", "TWO"], append=True)["VALUES"]
+ assert mi_series.loc[(p_index[0], "A", "B")] == 1.0
+
+
+def test_loc_setitem_pyarrow_strings():
+ # GH#52319
+ pytest.importorskip("pyarrow")
+ df = DataFrame(
+ {
+ "strings": Series(["A", "B", "C"], dtype="string[pyarrow]"),
+ "ids": Series([True, True, False]),
+ }
+ )
+ new_value = Series(["X", "Y"])
+ df.loc[df.ids, "strings"] = new_value
+
+ expected_df = DataFrame(
+ {
+ "strings": Series(["X", "Y", "C"], dtype="string[pyarrow]"),
+ "ids": Series([True, True, False]),
+ }
+ )
+
+ tm.assert_frame_equal(df, expected_df)
+
+
+class TestLocSeries:
+ @pytest.mark.parametrize("val,expected", [(2**63 - 1, 3), (2**63, 4)])
+ def test_loc_uint64(self, val, expected):
+ # see GH#19399
+ ser = Series({2**63 - 1: 3, 2**63: 4})
+ assert ser.loc[val] == expected
+
+ def test_loc_getitem(self, string_series, datetime_series):
+ inds = string_series.index[[3, 4, 7]]
+ tm.assert_series_equal(string_series.loc[inds], string_series.reindex(inds))
+ tm.assert_series_equal(string_series.iloc[5::2], string_series[5::2])
+
+ # slice with indices
+ d1, d2 = datetime_series.index[[5, 15]]
+ result = datetime_series.loc[d1:d2]
+ expected = datetime_series.truncate(d1, d2)
+ tm.assert_series_equal(result, expected)
+
+ # boolean
+ mask = string_series > string_series.median()
+ tm.assert_series_equal(string_series.loc[mask], string_series[mask])
+
+ # ask for index value
+ assert datetime_series.loc[d1] == datetime_series[d1]
+ assert datetime_series.loc[d2] == datetime_series[d2]
+
+ def test_loc_getitem_not_monotonic(self, datetime_series):
+ d1, d2 = datetime_series.index[[5, 15]]
+
+ ts2 = datetime_series[::2].iloc[[1, 2, 0]]
+
+ msg = r"Timestamp\('2000-01-10 00:00:00'\)"
+ with pytest.raises(KeyError, match=msg):
+ ts2.loc[d1:d2]
+ with pytest.raises(KeyError, match=msg):
+ ts2.loc[d1:d2] = 0
+
+ def test_loc_getitem_setitem_integer_slice_keyerrors(self):
+ ser = Series(
+ np.random.default_rng(2).standard_normal(10), index=list(range(0, 20, 2))
+ )
+
+ # this is OK
+ cp = ser.copy()
+ cp.iloc[4:10] = 0
+ assert (cp.iloc[4:10] == 0).all()
+
+ # so is this
+ cp = ser.copy()
+ cp.iloc[3:11] = 0
+ assert (cp.iloc[3:11] == 0).values.all()
+
+ result = ser.iloc[2:6]
+ result2 = ser.loc[3:11]
+ expected = ser.reindex([4, 6, 8, 10])
+
+ tm.assert_series_equal(result, expected)
+ tm.assert_series_equal(result2, expected)
+
+ # non-monotonic, raise KeyError
+ s2 = ser.iloc[list(range(5)) + list(range(9, 4, -1))]
+ msg = "non-monotonic index with a missing label 3"
+ with pytest.raises(KeyError, match=msg):
+ s2.loc[3:11]
+ with pytest.raises(KeyError, match=msg):
+ s2.loc[3:11] = 0
+
+ def test_loc_getitem_iterator(self, string_series):
+ idx = iter(string_series.index[:10])
+ result = string_series.loc[idx]
+ tm.assert_series_equal(result, string_series[:10])
+
+ def test_loc_setitem_boolean(self, string_series):
+ mask = string_series > string_series.median()
+
+ result = string_series.copy()
+ result.loc[mask] = 0
+ expected = string_series
+ expected[mask] = 0
+ tm.assert_series_equal(result, expected)
+
+ def test_loc_setitem_corner(self, string_series):
+ inds = list(string_series.index[[5, 8, 12]])
+ string_series.loc[inds] = 5
+ msg = r"\['foo'\] not in index"
+ with pytest.raises(KeyError, match=msg):
+ string_series.loc[[*inds, "foo"]] = 5
+
+ def test_basic_setitem_with_labels(self, datetime_series):
+ indices = datetime_series.index[[5, 10, 15]]
+
+ cp = datetime_series.copy()
+ exp = datetime_series.copy()
+ cp[indices] = 0
+ exp.loc[indices] = 0
+ tm.assert_series_equal(cp, exp)
+
+ cp = datetime_series.copy()
+ exp = datetime_series.copy()
+ cp[indices[0] : indices[2]] = 0
+ exp.loc[indices[0] : indices[2]] = 0
+ tm.assert_series_equal(cp, exp)
+
+ def test_loc_setitem_listlike_of_ints(self):
+ # integer indexes, be careful
+ ser = Series(
+ np.random.default_rng(2).standard_normal(10), index=list(range(0, 20, 2))
+ )
+ inds = [0, 4, 6]
+ arr_inds = np.array([0, 4, 6])
+
+ cp = ser.copy()
+ exp = ser.copy()
+ ser[inds] = 0
+ ser.loc[inds] = 0
+ tm.assert_series_equal(cp, exp)
+
+ cp = ser.copy()
+ exp = ser.copy()
+ ser[arr_inds] = 0
+ ser.loc[arr_inds] = 0
+ tm.assert_series_equal(cp, exp)
+
+ inds_notfound = [0, 4, 5, 6]
+ arr_inds_notfound = np.array([0, 4, 5, 6])
+ msg = r"\[5\] not in index"
+ with pytest.raises(KeyError, match=msg):
+ ser[inds_notfound] = 0
+ with pytest.raises(Exception, match=msg):
+ ser[arr_inds_notfound] = 0
+
+ def test_loc_setitem_dt64tz_values(self):
+ # GH#12089
+ ser = Series(
+ date_range("2011-01-01", periods=3, tz="US/Eastern"),
+ index=["a", "b", "c"],
+ )
+ s2 = ser.copy()
+ expected = Timestamp("2011-01-03", tz="US/Eastern")
+ s2.loc["a"] = expected
+ result = s2.loc["a"]
+ assert result == expected
+
+ s2 = ser.copy()
+ s2.iloc[0] = expected
+ result = s2.iloc[0]
+ assert result == expected
+
+ s2 = ser.copy()
+ s2["a"] = expected
+ result = s2["a"]
+ assert result == expected
+
+ @pytest.mark.parametrize("array_fn", [np.array, pd.array, list, tuple])
+ @pytest.mark.parametrize("size", [0, 4, 5, 6])
+ def test_loc_iloc_setitem_with_listlike(self, size, array_fn):
+ # GH37748
+ # testing insertion, in a Series of size N (here 5), of a listlike object
+ # of size 0, N-1, N, N+1
+
+ arr = array_fn([0] * size)
+ expected = Series([arr, 0, 0, 0, 0], index=list("abcde"), dtype=object)
+
+ ser = Series(0, index=list("abcde"), dtype=object)
+ ser.loc["a"] = arr
+ tm.assert_series_equal(ser, expected)
+
+ ser = Series(0, index=list("abcde"), dtype=object)
+ ser.iloc[0] = arr
+ tm.assert_series_equal(ser, expected)
+
+ @pytest.mark.parametrize("indexer", [IndexSlice["A", :], ("A", slice(None))])
+ def test_loc_series_getitem_too_many_dimensions(self, indexer):
+ # GH#35349
+ ser = Series(
+ index=MultiIndex.from_tuples([("A", "0"), ("A", "1"), ("B", "0")]),
+ data=[21, 22, 23],
+ )
+ msg = "Too many indexers"
+ with pytest.raises(IndexingError, match=msg):
+ ser.loc[indexer, :]
+
+ with pytest.raises(IndexingError, match=msg):
+ ser.loc[indexer, :] = 1
+
+ def test_loc_setitem(self, string_series):
+ inds = string_series.index[[3, 4, 7]]
+
+ result = string_series.copy()
+ result.loc[inds] = 5
+
+ expected = string_series.copy()
+ expected.iloc[[3, 4, 7]] = 5
+ tm.assert_series_equal(result, expected)
+
+ result.iloc[5:10] = 10
+ expected[5:10] = 10
+ tm.assert_series_equal(result, expected)
+
+ # set slice with indices
+ d1, d2 = string_series.index[[5, 15]]
+ result.loc[d1:d2] = 6
+ expected[5:16] = 6 # because it's inclusive
+ tm.assert_series_equal(result, expected)
+
+ # set index value
+ string_series.loc[d1] = 4
+ string_series.loc[d2] = 6
+ assert string_series[d1] == 4
+ assert string_series[d2] == 6
+
+ @pytest.mark.parametrize("dtype", ["object", "string"])
+ def test_loc_assign_dict_to_row(self, dtype):
+ # GH41044
+ df = DataFrame({"A": ["abc", "def"], "B": ["ghi", "jkl"]}, dtype=dtype)
+ df.loc[0, :] = {"A": "newA", "B": "newB"}
+
+ expected = DataFrame({"A": ["newA", "def"], "B": ["newB", "jkl"]}, dtype=dtype)
+
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_dict_timedelta_multiple_set(self):
+ # GH 16309
+ result = DataFrame(columns=["time", "value"])
+ result.loc[1] = {"time": Timedelta(6, unit="s"), "value": "foo"}
+ result.loc[1] = {"time": Timedelta(6, unit="s"), "value": "foo"}
+ expected = DataFrame(
+ [[Timedelta(6, unit="s"), "foo"]], columns=["time", "value"], index=[1]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_set_multiple_items_in_multiple_new_columns(self):
+ # GH 25594
+ df = DataFrame(index=[1, 2], columns=["a"])
+ df.loc[1, ["b", "c"]] = [6, 7]
+
+ expected = DataFrame(
+ {
+ "a": Series([np.nan, np.nan], dtype="object"),
+ "b": [6, np.nan],
+ "c": [7, np.nan],
+ },
+ index=[1, 2],
+ )
+
+ tm.assert_frame_equal(df, expected)
+
+ def test_getitem_loc_str_periodindex(self):
+ # GH#33964
+ msg = "Period with BDay freq is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ index = pd.period_range(start="2000", periods=20, freq="B")
+ series = Series(range(20), index=index)
+ assert series.loc["2000-01-14"] == 9
+
+ def test_loc_nonunique_masked_index(self):
+ # GH 57027
+ ids = list(range(11))
+ index = Index(ids * 1000, dtype="Int64")
+ df = DataFrame({"val": np.arange(len(index), dtype=np.intp)}, index=index)
+ result = df.loc[ids]
+ expected = DataFrame(
+ {"val": index.argsort(kind="stable").astype(np.intp)},
+ index=Index(np.array(ids).repeat(1000), dtype="Int64"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_loc_index_alignment_for_series(self):
+ # GH #56024
+ df = DataFrame({"a": [1, 2], "b": [3, 4]})
+ other = Series([200, 999], index=[1, 0])
+ df.loc[:, "a"] = other
+ expected = DataFrame({"a": [999, 200], "b": [3, 4]})
+ tm.assert_frame_equal(expected, df)
+
+ @pytest.mark.parametrize("dtype", ["str", object])
+ def test_loc_reindexing_of_empty_index(self, dtype):
+ # GH 57735
+ df = DataFrame(index=[1, 1, 2, 2], data=["1", "1", "2", "2"], dtype=dtype)
+ df.loc[Series([False] * 4, index=df.index, name=0), 0] = df[0]
+ expected = DataFrame(index=[1, 1, 2, 2], data=["1", "1", "2", "2"], dtype=dtype)
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize(
+ "df, row_index, col_index, expected_df",
+ [
+ [
+ DataFrame([[1, 2, 3], [4, 5, 6]], columns=list("ABC")),
+ slice(0, 3),
+ ["A", "B", "C"],
+ DataFrame([[10, 10, 10], [20, 20, 20]], columns=list("ABC")),
+ ],
+ [
+ DataFrame([[1, 2, 3], [4, 5, 6]], columns=list("ABC")),
+ slice(None),
+ ["A", "B", "C"],
+ DataFrame([[10, 10, 10], [20, 20, 20]], columns=list("ABC")),
+ ],
+ [
+ DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=list("ABC")),
+ [True, True, True],
+ ["A", "B", "C"],
+ DataFrame(
+ [[10, 10, 10], [20, 20, 20], [30, 30, 30]], columns=list("ABC")
+ ),
+ ],
+ [
+ DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=list("ABC")),
+ slice(0, 4),
+ ["A", "B", "C"],
+ DataFrame(
+ [[10, 10, 10], [20, 20, 20], [30, 30, 30]], columns=list("ABC")
+ ),
+ ],
+ [
+ DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=list("ABC")),
+ slice(None),
+ slice("A", "C"),
+ DataFrame(
+ [[10, 10, 10], [20, 20, 20], [30, 30, 30]], columns=list("ABC")
+ ),
+ ],
+ [
+ DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=list("ABC")),
+ slice(None),
+ Series(
+ {
+ "A": True,
+ "C": False,
+ "B": True,
+ }
+ ),
+ DataFrame([[10, 10, 3], [20, 20, 6], [30, 30, 9]], columns=list("ABC")),
+ ],
+ ],
+ )
+ def test_loc_set_series_to_multiple_columns(
+ self, df, row_index, col_index, expected_df
+ ):
+ # GH 59933
+ df.loc[row_index, col_index] = Series([10, 20, 30])
+ tm.assert_frame_equal(df, expected_df)
+
+ def test_loc_setitem_matching_index(self):
+ # GH 25548
+ s = Series(0.0, index=list("abcd"))
+ s1 = Series(1.0, index=list("ab"))
+ s2 = Series(2.0, index=list("xy"))
+
+ # Test matching indices
+ s.loc[["a", "b"]] = s1
+
+ result = s[["a", "b"]]
+ expected = s1
+ tm.assert_series_equal(result, expected)
+
+ # Test unmatched indices
+ s.loc[["a", "b"]] = s2
+
+ result = s[["a", "b"]]
+ expected = Series([np.nan, np.nan], index=["a", "b"])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_na_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_na_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4ad350a64e4dcd3310ddcdbdf17892c5220af24
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_na_indexing.py
@@ -0,0 +1,74 @@
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "values, dtype",
+ [
+ ([], "object"),
+ ([1, 2, 3], "int64"),
+ ([1.0, 2.0, 3.0], "float64"),
+ (["a", "b", "c"], "object"),
+ (["a", "b", "c"], "string"),
+ ([1, 2, 3], "datetime64[ns]"),
+ ([1, 2, 3], "datetime64[ns, CET]"),
+ ([1, 2, 3], "timedelta64[ns]"),
+ (["2000", "2001", "2002"], "Period[D]"),
+ ([1, 0, 3], "Sparse"),
+ ([pd.Interval(0, 1), pd.Interval(1, 2), pd.Interval(3, 4)], "interval"),
+ ],
+)
+@pytest.mark.parametrize(
+ "mask", [[True, False, False], [True, True, True], [False, False, False]]
+)
+@pytest.mark.parametrize("indexer_class", [list, pd.array, pd.Index, pd.Series])
+@pytest.mark.parametrize("frame", [True, False])
+def test_series_mask_boolean(values, dtype, mask, indexer_class, frame):
+ # In case len(values) < 3
+ index = ["a", "b", "c"][: len(values)]
+ mask = mask[: len(values)]
+
+ obj = pd.Series(values, dtype=dtype, index=index)
+ if frame:
+ if len(values) == 0:
+ # Otherwise obj is an empty DataFrame with shape (0, 1)
+ obj = pd.DataFrame(dtype=dtype, index=index)
+ else:
+ obj = obj.to_frame()
+
+ if indexer_class is pd.array:
+ mask = pd.array(mask, dtype="boolean")
+ elif indexer_class is pd.Series:
+ mask = pd.Series(mask, index=obj.index, dtype="boolean")
+ else:
+ mask = indexer_class(mask)
+
+ expected = obj[mask]
+
+ result = obj[mask]
+ tm.assert_equal(result, expected)
+
+ if indexer_class is pd.Series:
+ msg = "iLocation based boolean indexing cannot use an indexable as a mask"
+ with pytest.raises(ValueError, match=msg):
+ result = obj.iloc[mask]
+ else:
+ result = obj.iloc[mask]
+ tm.assert_equal(result, expected)
+
+ result = obj.loc[mask]
+ tm.assert_equal(result, expected)
+
+
+def test_na_treated_as_false(frame_or_series, indexer_sli):
+ # https://github.com/pandas-dev/pandas/issues/31503
+ obj = frame_or_series([1, 2, 3])
+
+ mask = pd.array([True, False, None], dtype="boolean")
+
+ result = indexer_sli(obj)[mask]
+ expected = indexer_sli(obj)[mask.fillna(False)]
+
+ tm.assert_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_partial.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_partial.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b0666b3be5c125c0c7177d789a9fdfaa59a0a28
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_partial.py
@@ -0,0 +1,694 @@
+"""
+test setting *parts* of objects both positionally and label based
+
+TODO: these should be split among the indexer tests
+"""
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Period,
+ Series,
+ Timestamp,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestEmptyFrameSetitemExpansion:
+ def test_empty_frame_setitem_index_name_retained(self):
+ # GH#31368 empty frame has non-None index.name -> retained
+ df = DataFrame({}, index=pd.RangeIndex(0, name="df_index"))
+ series = Series(1.23, index=pd.RangeIndex(4, name="series_index"))
+
+ df["series"] = series
+ expected = DataFrame(
+ {"series": [1.23] * 4},
+ index=pd.RangeIndex(4, name="df_index"),
+ columns=Index(["series"]),
+ )
+
+ tm.assert_frame_equal(df, expected)
+
+ def test_empty_frame_setitem_index_name_inherited(self):
+ # GH#36527 empty frame has None index.name -> not retained
+ df = DataFrame()
+ series = Series(1.23, index=pd.RangeIndex(4, name="series_index"))
+ df["series"] = series
+ expected = DataFrame(
+ {"series": [1.23] * 4},
+ index=pd.RangeIndex(4, name="series_index"),
+ columns=Index(["series"]),
+ )
+ tm.assert_frame_equal(df, expected)
+
+ def test_loc_setitem_zerolen_series_columns_align(self):
+ # columns will align
+ df = DataFrame(columns=["A", "B"])
+ df.loc[0] = Series(1, index=range(4))
+ expected = DataFrame(columns=["A", "B"], index=[0], dtype=np.float64)
+ tm.assert_frame_equal(df, expected)
+
+ # columns will align
+ df = DataFrame(columns=["A", "B"])
+ df.loc[0] = Series(1, index=["B"])
+
+ exp = DataFrame([[np.nan, 1]], columns=["A", "B"], index=[0], dtype="float64")
+ tm.assert_frame_equal(df, exp)
+
+ def test_loc_setitem_zerolen_list_length_must_match_columns(self):
+ # list-like must conform
+ df = DataFrame(columns=["A", "B"])
+
+ msg = "cannot set a row with mismatched columns"
+ with pytest.raises(ValueError, match=msg):
+ df.loc[0] = [1, 2, 3]
+
+ df = DataFrame(columns=["A", "B"])
+ df.loc[3] = [6, 7] # length matches len(df.columns) --> OK!
+
+ exp = DataFrame([[6, 7]], index=[3], columns=["A", "B"], dtype=np.int64)
+ tm.assert_frame_equal(df, exp)
+
+ def test_partial_set_empty_frame(self):
+ # partially set with an empty object
+ # frame
+ df = DataFrame()
+
+ msg = "cannot set a frame with no defined columns"
+
+ with pytest.raises(ValueError, match=msg):
+ df.loc[1] = 1
+
+ with pytest.raises(ValueError, match=msg):
+ df.loc[1] = Series([1], index=["foo"])
+
+ msg = "cannot set a frame with no defined index and a scalar"
+ with pytest.raises(ValueError, match=msg):
+ df.loc[:, 1] = 1
+
+ def test_partial_set_empty_frame2(self):
+ # these work as they don't really change
+ # anything but the index
+ # GH#5632
+ expected = DataFrame(columns=Index(["foo"]), index=Index([], dtype="object"))
+
+ df = DataFrame(index=Index([], dtype="object"))
+ df["foo"] = Series([], dtype="object")
+
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame(index=Index([]))
+ df["foo"] = Series(df.index)
+
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame(index=Index([]))
+ df["foo"] = df.index
+
+ tm.assert_frame_equal(df, expected)
+
+ def test_partial_set_empty_frame3(self):
+ expected = DataFrame(columns=Index(["foo"]), index=Index([], dtype="int64"))
+ expected["foo"] = expected["foo"].astype("float64")
+
+ df = DataFrame(index=Index([], dtype="int64"))
+ df["foo"] = []
+
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame(index=Index([], dtype="int64"))
+ df["foo"] = Series(np.arange(len(df)), dtype="float64")
+
+ tm.assert_frame_equal(df, expected)
+
+ def test_partial_set_empty_frame4(self):
+ df = DataFrame(index=Index([], dtype="int64"))
+ df["foo"] = range(len(df))
+
+ expected = DataFrame(columns=Index(["foo"]), index=Index([], dtype="int64"))
+ # range is int-dtype-like, so we get int64 dtype
+ expected["foo"] = expected["foo"].astype("int64")
+ tm.assert_frame_equal(df, expected)
+
+ def test_partial_set_empty_frame5(self):
+ df = DataFrame()
+ tm.assert_index_equal(df.columns, pd.RangeIndex(0))
+ df2 = DataFrame()
+ df2[1] = Series([1], index=["foo"])
+ df.loc[:, 1] = Series([1], index=["foo"])
+ tm.assert_frame_equal(df, DataFrame([[1]], index=["foo"], columns=[1]))
+ tm.assert_frame_equal(df, df2)
+
+ def test_partial_set_empty_frame_no_index(self):
+ # no index to start
+ expected = DataFrame({0: Series(1, index=range(4))}, columns=["A", "B", 0])
+
+ df = DataFrame(columns=["A", "B"])
+ df[0] = Series(1, index=range(4))
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame(columns=["A", "B"])
+ df.loc[:, 0] = Series(1, index=range(4))
+ tm.assert_frame_equal(df, expected)
+
+ def test_partial_set_empty_frame_row(self):
+ # GH#5720, GH#5744
+ # don't create rows when empty
+ expected = DataFrame(columns=["A", "B", "New"], index=Index([], dtype="int64"))
+ expected["A"] = expected["A"].astype("int64")
+ expected["B"] = expected["B"].astype("float64")
+ expected["New"] = expected["New"].astype("float64")
+
+ df = DataFrame({"A": [1, 2, 3], "B": [1.2, 4.2, 5.2]})
+ y = df[df.A > 5]
+ y["New"] = np.nan
+ tm.assert_frame_equal(y, expected)
+
+ expected = DataFrame(columns=["a", "b", "c c", "d"])
+ expected["d"] = expected["d"].astype("int64")
+ df = DataFrame(columns=["a", "b", "c c"])
+ df["d"] = 3
+ tm.assert_frame_equal(df, expected)
+ tm.assert_series_equal(df["c c"], Series(name="c c", dtype=object))
+
+ # reindex columns is ok
+ df = DataFrame({"A": [1, 2, 3], "B": [1.2, 4.2, 5.2]})
+ y = df[df.A > 5]
+ result = y.reindex(columns=["A", "B", "C"])
+ expected = DataFrame(columns=["A", "B", "C"])
+ expected["A"] = expected["A"].astype("int64")
+ expected["B"] = expected["B"].astype("float64")
+ expected["C"] = expected["C"].astype("float64")
+ tm.assert_frame_equal(result, expected)
+
+ def test_partial_set_empty_frame_set_series(self):
+ # GH#5756
+ # setting with empty Series
+ df = DataFrame(Series(dtype=object))
+ expected = DataFrame({0: Series(dtype=object)})
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame(Series(name="foo", dtype=object))
+ expected = DataFrame({"foo": Series(dtype=object)})
+ tm.assert_frame_equal(df, expected)
+
+ def test_partial_set_empty_frame_empty_copy_assignment(self):
+ # GH#5932
+ # copy on empty with assignment fails
+ df = DataFrame(index=[0])
+ df = df.copy()
+ df["a"] = 0
+ expected = DataFrame(0, index=[0], columns=Index(["a"]))
+ tm.assert_frame_equal(df, expected)
+
+ def test_partial_set_empty_frame_empty_consistencies(self, using_infer_string):
+ # GH#6171
+ # consistency on empty frames
+ df = DataFrame(columns=["x", "y"])
+ df["x"] = [1, 2]
+ expected = DataFrame({"x": [1, 2], "y": [np.nan, np.nan]})
+ tm.assert_frame_equal(df, expected, check_dtype=False)
+
+ df = DataFrame(columns=["x", "y"])
+ df["x"] = ["1", "2"]
+ expected = DataFrame(
+ {
+ "x": Series(
+ ["1", "2"],
+ dtype=object if not using_infer_string else "str",
+ ),
+ "y": Series([np.nan, np.nan], dtype=object),
+ }
+ )
+ tm.assert_frame_equal(df, expected)
+
+ df = DataFrame(columns=["x", "y"])
+ df.loc[0, "x"] = 1
+ expected = DataFrame({"x": [1], "y": [np.nan]})
+ tm.assert_frame_equal(df, expected, check_dtype=False)
+
+
+class TestPartialSetting:
+ def test_partial_setting(self):
+ # GH2578, allow ix and friends to partially set
+
+ # series
+ s_orig = Series([1, 2, 3])
+
+ s = s_orig.copy()
+ s[5] = 5
+ expected = Series([1, 2, 3, 5], index=[0, 1, 2, 5])
+ tm.assert_series_equal(s, expected)
+
+ s = s_orig.copy()
+ s.loc[5] = 5
+ expected = Series([1, 2, 3, 5], index=[0, 1, 2, 5])
+ tm.assert_series_equal(s, expected)
+
+ s = s_orig.copy()
+ s[5] = 5.0
+ expected = Series([1, 2, 3, 5.0], index=[0, 1, 2, 5])
+ tm.assert_series_equal(s, expected)
+
+ s = s_orig.copy()
+ s.loc[5] = 5.0
+ expected = Series([1, 2, 3, 5.0], index=[0, 1, 2, 5])
+ tm.assert_series_equal(s, expected)
+
+ # iloc/iat raise
+ s = s_orig.copy()
+
+ msg = "iloc cannot enlarge its target object"
+ with pytest.raises(IndexError, match=msg):
+ s.iloc[3] = 5.0
+
+ msg = "index 3 is out of bounds for axis 0 with size 3"
+ with pytest.raises(IndexError, match=msg):
+ s.iat[3] = 5.0
+
+ @pytest.mark.filterwarnings("ignore:Setting a value on a view:FutureWarning")
+ def test_partial_setting_frame(self):
+ df_orig = DataFrame(
+ np.arange(6).reshape(3, 2), columns=["A", "B"], dtype="int64"
+ )
+
+ # iloc/iat raise
+ df = df_orig.copy()
+
+ msg = "iloc cannot enlarge its target object"
+ with pytest.raises(IndexError, match=msg):
+ df.iloc[4, 2] = 5.0
+
+ msg = "index 2 is out of bounds for axis 0 with size 2"
+ with pytest.raises(IndexError, match=msg):
+ df.iat[4, 2] = 5.0
+
+ # row setting where it exists
+ expected = DataFrame({"A": [0, 4, 4], "B": [1, 5, 5]})
+ df = df_orig.copy()
+ df.iloc[1] = df.iloc[2]
+ tm.assert_frame_equal(df, expected)
+
+ expected = DataFrame({"A": [0, 4, 4], "B": [1, 5, 5]})
+ df = df_orig.copy()
+ df.loc[1] = df.loc[2]
+ tm.assert_frame_equal(df, expected)
+
+ # like 2578, partial setting with dtype preservation
+ expected = DataFrame({"A": [0, 2, 4, 4], "B": [1, 3, 5, 5]})
+ df = df_orig.copy()
+ df.loc[3] = df.loc[2]
+ tm.assert_frame_equal(df, expected)
+
+ # single dtype frame, overwrite
+ expected = DataFrame({"A": [0, 2, 4], "B": [0, 2, 4]})
+ df = df_orig.copy()
+ df.loc[:, "B"] = df.loc[:, "A"]
+ tm.assert_frame_equal(df, expected)
+
+ # mixed dtype frame, overwrite
+ expected = DataFrame({"A": [0, 2, 4], "B": Series([0.0, 2.0, 4.0])})
+ df = df_orig.copy()
+ df["B"] = df["B"].astype(np.float64)
+ # as of 2.0, df.loc[:, "B"] = ... attempts (and here succeeds) at
+ # setting inplace
+ df.loc[:, "B"] = df.loc[:, "A"]
+ tm.assert_frame_equal(df, expected)
+
+ # single dtype frame, partial setting
+ expected = df_orig.copy()
+ expected["C"] = df["A"]
+ df = df_orig.copy()
+ df.loc[:, "C"] = df.loc[:, "A"]
+ tm.assert_frame_equal(df, expected)
+
+ # mixed frame, partial setting
+ expected = df_orig.copy()
+ expected["C"] = df["A"]
+ df = df_orig.copy()
+ df.loc[:, "C"] = df.loc[:, "A"]
+ tm.assert_frame_equal(df, expected)
+
+ def test_partial_setting2(self):
+ # GH 8473
+ dates = date_range("1/1/2000", periods=8)
+ df_orig = DataFrame(
+ np.random.default_rng(2).standard_normal((8, 4)),
+ index=dates,
+ columns=["A", "B", "C", "D"],
+ )
+
+ expected = pd.concat(
+ [df_orig, DataFrame({"A": 7}, index=dates[-1:] + dates.freq)], sort=True
+ )
+ df = df_orig.copy()
+ df.loc[dates[-1] + dates.freq, "A"] = 7
+ tm.assert_frame_equal(df, expected)
+ df = df_orig.copy()
+ df.at[dates[-1] + dates.freq, "A"] = 7
+ tm.assert_frame_equal(df, expected)
+
+ exp_other = DataFrame({0: 7}, index=dates[-1:] + dates.freq)
+ expected = pd.concat([df_orig, exp_other], axis=1)
+
+ df = df_orig.copy()
+ df.loc[dates[-1] + dates.freq, 0] = 7
+ tm.assert_frame_equal(df, expected)
+ df = df_orig.copy()
+ df.at[dates[-1] + dates.freq, 0] = 7
+ tm.assert_frame_equal(df, expected)
+
+ def test_partial_setting_mixed_dtype(self):
+ # in a mixed dtype environment, try to preserve dtypes
+ # by appending
+ df = DataFrame([[True, 1], [False, 2]], columns=["female", "fitness"])
+
+ s = df.loc[1].copy()
+ s.name = 2
+ expected = pd.concat([df, DataFrame(s).T.infer_objects()])
+
+ df.loc[2] = df.loc[1]
+ tm.assert_frame_equal(df, expected)
+
+ def test_series_partial_set(self):
+ # partial set with new index
+ # Regression from GH4825
+ ser = Series([0.1, 0.2], index=[1, 2])
+
+ # loc equiv to .reindex
+ expected = Series([np.nan, 0.2, np.nan], index=[3, 2, 3])
+ with pytest.raises(KeyError, match=r"not in index"):
+ ser.loc[[3, 2, 3]]
+
+ result = ser.reindex([3, 2, 3])
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ expected = Series([np.nan, 0.2, np.nan, np.nan], index=[3, 2, 3, "x"])
+ with pytest.raises(KeyError, match="not in index"):
+ ser.loc[[3, 2, 3, "x"]]
+
+ result = ser.reindex([3, 2, 3, "x"])
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ expected = Series([0.2, 0.2, 0.1], index=[2, 2, 1])
+ result = ser.loc[[2, 2, 1]]
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ expected = Series([0.2, 0.2, np.nan, 0.1], index=[2, 2, "x", 1])
+ with pytest.raises(KeyError, match="not in index"):
+ ser.loc[[2, 2, "x", 1]]
+
+ result = ser.reindex([2, 2, "x", 1])
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ # raises as nothing is in the index
+ msg = (
+ rf"\"None of \[Index\(\[3, 3, 3\], dtype='{np.dtype(int)}'\)\] "
+ r"are in the \[index\]\""
+ )
+ with pytest.raises(KeyError, match=msg):
+ ser.loc[[3, 3, 3]]
+
+ expected = Series([0.2, 0.2, np.nan], index=[2, 2, 3])
+ with pytest.raises(KeyError, match="not in index"):
+ ser.loc[[2, 2, 3]]
+
+ result = ser.reindex([2, 2, 3])
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ s = Series([0.1, 0.2, 0.3], index=[1, 2, 3])
+ expected = Series([0.3, np.nan, np.nan], index=[3, 4, 4])
+ with pytest.raises(KeyError, match="not in index"):
+ s.loc[[3, 4, 4]]
+
+ result = s.reindex([3, 4, 4])
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ s = Series([0.1, 0.2, 0.3, 0.4], index=[1, 2, 3, 4])
+ expected = Series([np.nan, 0.3, 0.3], index=[5, 3, 3])
+ with pytest.raises(KeyError, match="not in index"):
+ s.loc[[5, 3, 3]]
+
+ result = s.reindex([5, 3, 3])
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ s = Series([0.1, 0.2, 0.3, 0.4], index=[1, 2, 3, 4])
+ expected = Series([np.nan, 0.4, 0.4], index=[5, 4, 4])
+ with pytest.raises(KeyError, match="not in index"):
+ s.loc[[5, 4, 4]]
+
+ result = s.reindex([5, 4, 4])
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ s = Series([0.1, 0.2, 0.3, 0.4], index=[4, 5, 6, 7])
+ expected = Series([0.4, np.nan, np.nan], index=[7, 2, 2])
+ with pytest.raises(KeyError, match="not in index"):
+ s.loc[[7, 2, 2]]
+
+ result = s.reindex([7, 2, 2])
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ s = Series([0.1, 0.2, 0.3, 0.4], index=[1, 2, 3, 4])
+ expected = Series([0.4, np.nan, np.nan], index=[4, 5, 5])
+ with pytest.raises(KeyError, match="not in index"):
+ s.loc[[4, 5, 5]]
+
+ result = s.reindex([4, 5, 5])
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ # iloc
+ expected = Series([0.2, 0.2, 0.1, 0.1], index=[2, 2, 1, 1])
+ result = ser.iloc[[1, 1, 0, 0]]
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ def test_series_partial_set_with_name(self):
+ # GH 11497
+
+ idx = Index([1, 2], dtype="int64", name="idx")
+ ser = Series([0.1, 0.2], index=idx, name="s")
+
+ # loc
+ with pytest.raises(KeyError, match=r"\[3\] not in index"):
+ ser.loc[[3, 2, 3]]
+
+ with pytest.raises(KeyError, match=r"not in index"):
+ ser.loc[[3, 2, 3, "x"]]
+
+ exp_idx = Index([2, 2, 1], dtype="int64", name="idx")
+ expected = Series([0.2, 0.2, 0.1], index=exp_idx, name="s")
+ result = ser.loc[[2, 2, 1]]
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ with pytest.raises(KeyError, match=r"\['x'\] not in index"):
+ ser.loc[[2, 2, "x", 1]]
+
+ # raises as nothing is in the index
+ msg = (
+ rf"\"None of \[Index\(\[3, 3, 3\], dtype='{np.dtype(int)}', "
+ r"name='idx'\)\] are in the \[index\]\""
+ )
+ with pytest.raises(KeyError, match=msg):
+ ser.loc[[3, 3, 3]]
+
+ with pytest.raises(KeyError, match="not in index"):
+ ser.loc[[2, 2, 3]]
+
+ idx = Index([1, 2, 3], dtype="int64", name="idx")
+ with pytest.raises(KeyError, match="not in index"):
+ Series([0.1, 0.2, 0.3], index=idx, name="s").loc[[3, 4, 4]]
+
+ idx = Index([1, 2, 3, 4], dtype="int64", name="idx")
+ with pytest.raises(KeyError, match="not in index"):
+ Series([0.1, 0.2, 0.3, 0.4], index=idx, name="s").loc[[5, 3, 3]]
+
+ idx = Index([1, 2, 3, 4], dtype="int64", name="idx")
+ with pytest.raises(KeyError, match="not in index"):
+ Series([0.1, 0.2, 0.3, 0.4], index=idx, name="s").loc[[5, 4, 4]]
+
+ idx = Index([4, 5, 6, 7], dtype="int64", name="idx")
+ with pytest.raises(KeyError, match="not in index"):
+ Series([0.1, 0.2, 0.3, 0.4], index=idx, name="s").loc[[7, 2, 2]]
+
+ idx = Index([1, 2, 3, 4], dtype="int64", name="idx")
+ with pytest.raises(KeyError, match="not in index"):
+ Series([0.1, 0.2, 0.3, 0.4], index=idx, name="s").loc[[4, 5, 5]]
+
+ # iloc
+ exp_idx = Index([2, 2, 1, 1], dtype="int64", name="idx")
+ expected = Series([0.2, 0.2, 0.1, 0.1], index=exp_idx, name="s")
+ result = ser.iloc[[1, 1, 0, 0]]
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ @pytest.mark.parametrize("key", [100, 100.0])
+ def test_setitem_with_expansion_numeric_into_datetimeindex(self, key):
+ # GH#4940 inserting non-strings
+ orig = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df = orig.copy()
+
+ df.loc[key, :] = df.iloc[0]
+ ex_index = Index([*list(orig.index), key], dtype=object, name=orig.index.name)
+ ex_data = np.concatenate([orig.values, df.iloc[[0]].values], axis=0)
+ expected = DataFrame(ex_data, index=ex_index, columns=orig.columns)
+
+ tm.assert_frame_equal(df, expected)
+
+ def test_partial_set_invalid(self):
+ # GH 4940
+ # allow only setting of 'valid' values
+
+ orig = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+
+ # allow object conversion here
+ df = orig.copy()
+ df.loc["a", :] = df.iloc[0]
+ ser = Series(df.iloc[0], name="a")
+ exp = pd.concat([orig, DataFrame(ser).T.infer_objects()])
+ tm.assert_frame_equal(df, exp)
+ tm.assert_index_equal(df.index, Index([*orig.index.tolist(), "a"]))
+ assert df.index.dtype == "object"
+
+ @pytest.mark.parametrize(
+ "idx,labels,expected_idx",
+ [
+ (
+ period_range(start="2000", periods=20, freq="D"),
+ ["2000-01-04", "2000-01-08", "2000-01-12"],
+ [
+ Period("2000-01-04", freq="D"),
+ Period("2000-01-08", freq="D"),
+ Period("2000-01-12", freq="D"),
+ ],
+ ),
+ (
+ date_range(start="2000", periods=20, freq="D", unit="s"),
+ ["2000-01-04", "2000-01-08", "2000-01-12"],
+ [
+ Timestamp("2000-01-04").as_unit("s"),
+ Timestamp("2000-01-08").as_unit("s"),
+ Timestamp("2000-01-12").as_unit("s"),
+ ],
+ ),
+ (
+ pd.timedelta_range(start="1 day", periods=20),
+ ["4D", "8D", "12D"],
+ [pd.Timedelta("4 day"), pd.Timedelta("8 day"), pd.Timedelta("12 day")],
+ ),
+ ],
+ )
+ def test_loc_with_list_of_strings_representing_datetimes(
+ self, idx, labels, expected_idx, frame_or_series
+ ):
+ # GH 11278
+ obj = frame_or_series(range(20), index=idx)
+
+ expected_value = [3, 7, 11]
+ expected = frame_or_series(expected_value, expected_idx)
+
+ tm.assert_equal(expected, obj.loc[labels])
+ if frame_or_series is Series:
+ tm.assert_series_equal(expected, obj[labels])
+
+ @pytest.mark.parametrize(
+ "idx,labels",
+ [
+ (
+ period_range(start="2000", periods=20, freq="D"),
+ ["2000-01-04", "2000-01-30"],
+ ),
+ (
+ date_range(start="2000", periods=20, freq="D"),
+ ["2000-01-04", "2000-01-30"],
+ ),
+ (pd.timedelta_range(start="1 day", periods=20), ["3 day", "30 day"]),
+ ],
+ )
+ def test_loc_with_list_of_strings_representing_datetimes_missing_value(
+ self, idx, labels
+ ):
+ # GH 11278
+ ser = Series(range(20), index=idx)
+ df = DataFrame(range(20), index=idx)
+ msg = r"not in index"
+
+ with pytest.raises(KeyError, match=msg):
+ ser.loc[labels]
+ with pytest.raises(KeyError, match=msg):
+ ser[labels]
+ with pytest.raises(KeyError, match=msg):
+ df.loc[labels]
+
+ @pytest.mark.parametrize(
+ "idx,labels,msg",
+ [
+ (
+ period_range(start="2000", periods=20, freq="D"),
+ Index(["4D", "8D"], dtype=object),
+ (
+ r"None of \[Index\(\['4D', '8D'\], dtype='object'\)\] "
+ r"are in the \[index\]"
+ ),
+ ),
+ (
+ date_range(start="2000", periods=20, freq="D"),
+ Index(["4D", "8D"], dtype=object),
+ (
+ r"None of \[Index\(\['4D', '8D'\], dtype='object'\)\] "
+ r"are in the \[index\]"
+ ),
+ ),
+ (
+ pd.timedelta_range(start="1 day", periods=20),
+ Index(["2000-01-04", "2000-01-08"], dtype=object),
+ (
+ r"None of \[Index\(\['2000-01-04', '2000-01-08'\], "
+ r"dtype='object'\)\] are in the \[index\]"
+ ),
+ ),
+ ],
+ )
+ def test_loc_with_list_of_strings_representing_datetimes_not_matched_type(
+ self, idx, labels, msg
+ ):
+ # GH 11278
+ ser = Series(range(20), index=idx)
+ df = DataFrame(range(20), index=idx)
+
+ with pytest.raises(KeyError, match=msg):
+ ser.loc[labels]
+ with pytest.raises(KeyError, match=msg):
+ ser[labels]
+ with pytest.raises(KeyError, match=msg):
+ df.loc[labels]
+
+
+class TestStringSlicing:
+ def test_slice_irregular_datetime_index_with_nan(self):
+ # GH36953
+ index = pd.to_datetime(["2012-01-01", "2012-01-02", "2012-01-03", None])
+ df = DataFrame(range(len(index)), index=index)
+ expected = DataFrame(range(len(index[:3])), index=index[:3])
+ with pytest.raises(KeyError, match="non-existing keys is not allowed"):
+ # Upper bound is not in index (which is unordered)
+ # GH53983
+ # GH37819
+ df["2012-01-01":"2012-01-04"]
+ # Need this precision for right bound since the right slice
+ # bound is "rounded" up to the largest timepoint smaller than
+ # the next "resolution"-step of the provided point.
+ # e.g. 2012-01-03 is rounded up to 2012-01-04 - 1ns
+ result = df["2012-01-01":"2012-01-03 00:00:00.000000000"]
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_scalar.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_scalar.py
new file mode 100644
index 0000000000000000000000000000000000000000..730fe584d7f0772c4b3b505f788cf997606ca298
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/indexing/test_scalar.py
@@ -0,0 +1,318 @@
+"""test scalar indexing, including at and iat"""
+
+from datetime import (
+ datetime,
+ timedelta,
+)
+import itertools
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def generate_indices(f, values=False):
+ """
+ generate the indices
+ if values is True , use the axis values
+ is False, use the range
+ """
+ axes = f.axes
+ if values:
+ axes = (list(range(len(ax))) for ax in axes)
+
+ return itertools.product(*axes)
+
+
+class TestScalar:
+ @pytest.mark.parametrize("dtype", [np.int64, np.uint64])
+ def test_iat_set_ints(self, dtype, frame_or_series):
+ f = frame_or_series(range(3), index=Index([0, 1, 2], dtype=dtype))
+ indices = generate_indices(f, True)
+ for i in indices:
+ f.iat[i] = 1
+ expected = f.values[i]
+ tm.assert_almost_equal(expected, 1)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(list("abcd"), dtype=object),
+ date_range("20130101", periods=4),
+ Index(range(0, 8, 2), dtype=np.float64),
+ ],
+ )
+ def test_iat_set_other(self, index, frame_or_series):
+ f = frame_or_series(range(len(index)), index=index)
+ msg = "iAt based indexing can only have integer indexers"
+ idx = next(generate_indices(f, False))
+ with pytest.raises(ValueError, match=msg):
+ f.iat[idx] = 1
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(list("abcd"), dtype=object),
+ date_range("20130101", periods=4),
+ Index(range(0, 8, 2), dtype=np.float64),
+ Index(range(0, 8, 2), dtype=np.uint64),
+ Index(range(0, 8, 2), dtype=np.int64),
+ ],
+ )
+ def test_at_set_ints_other(self, index, frame_or_series):
+ f = frame_or_series(range(len(index)), index=index)
+ indices = generate_indices(f, False)
+ for i in indices:
+ f.at[i] = 1
+ expected = f.loc[i]
+ tm.assert_almost_equal(expected, 1)
+
+
+class TestAtAndiAT:
+ # at and iat tests that don't need Base class
+
+ def test_float_index_at_iat(self):
+ ser = Series([1, 2, 3], index=[0.1, 0.2, 0.3])
+ for el, item in ser.items():
+ assert ser.at[el] == item
+ for i in range(len(ser)):
+ assert ser.iat[i] == i + 1
+
+ def test_at_iat_coercion(self):
+ # as timestamp is not a tuple!
+ dates = date_range("1/1/2000", periods=8)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((8, 4)),
+ index=dates,
+ columns=["A", "B", "C", "D"],
+ )
+ s = df["A"]
+
+ result = s.at[dates[5]]
+ xp = s.values[5]
+ assert result == xp
+
+ @pytest.mark.parametrize(
+ "ser, expected",
+ [
+ [
+ Series(["2014-01-01", "2014-02-02"], dtype="datetime64[ns]"),
+ Timestamp("2014-02-02"),
+ ],
+ [
+ Series(["1 days", "2 days"], dtype="timedelta64[ns]"),
+ Timedelta("2 days"),
+ ],
+ ],
+ )
+ def test_iloc_iat_coercion_datelike(self, indexer_ial, ser, expected):
+ # GH 7729
+ # make sure we are boxing the returns
+ result = indexer_ial(ser)[1]
+ assert result == expected
+
+ def test_imethods_with_dups(self):
+ # GH6493
+ # iat/iloc with dups
+
+ s = Series(range(5), index=[1, 1, 2, 2, 3], dtype="int64")
+ result = s.iloc[2]
+ assert result == 2
+ result = s.iat[2]
+ assert result == 2
+
+ msg = "index 10 is out of bounds for axis 0 with size 5"
+ with pytest.raises(IndexError, match=msg):
+ s.iat[10]
+ msg = "index -10 is out of bounds for axis 0 with size 5"
+ with pytest.raises(IndexError, match=msg):
+ s.iat[-10]
+
+ result = s.iloc[[2, 3]]
+ expected = Series([2, 3], [2, 2], dtype="int64")
+ tm.assert_series_equal(result, expected)
+
+ df = s.to_frame()
+ result = df.iloc[2]
+ expected = Series(2, index=[0], name=2)
+ tm.assert_series_equal(result, expected)
+
+ result = df.iat[2, 0]
+ assert result == 2
+
+ def test_frame_at_with_duplicate_axes(self):
+ # GH#33041
+ arr = np.random.default_rng(2).standard_normal(6).reshape(3, 2)
+ df = DataFrame(arr, columns=["A", "A"])
+
+ result = df.at[0, "A"]
+ expected = df.iloc[0].copy()
+
+ tm.assert_series_equal(result, expected)
+
+ result = df.T.at["A", 0]
+ tm.assert_series_equal(result, expected)
+
+ # setter
+ df.at[1, "A"] = 2
+ expected = Series([2.0, 2.0], index=["A", "A"], name=1)
+ tm.assert_series_equal(df.iloc[1], expected)
+
+ def test_at_getitem_dt64tz_values(self):
+ # gh-15822
+ df = DataFrame(
+ {
+ "name": ["John", "Anderson"],
+ "date": [
+ Timestamp(2017, 3, 13, 13, 32, 56),
+ Timestamp(2017, 2, 16, 12, 10, 3),
+ ],
+ }
+ )
+ df["date"] = df["date"].dt.tz_localize("Asia/Shanghai")
+
+ expected = Timestamp("2017-03-13 13:32:56+0800", tz="Asia/Shanghai")
+
+ result = df.loc[0, "date"]
+ assert result == expected
+
+ result = df.at[0, "date"]
+ assert result == expected
+
+ def test_mixed_index_at_iat_loc_iloc_series(self):
+ # GH 19860
+ s = Series([1, 2, 3, 4, 5], index=["a", "b", "c", 1, 2])
+ for el, item in s.items():
+ assert s.at[el] == s.loc[el] == item
+ for i in range(len(s)):
+ assert s.iat[i] == s.iloc[i] == i + 1
+
+ with pytest.raises(KeyError, match="^4$"):
+ s.at[4]
+ with pytest.raises(KeyError, match="^4$"):
+ s.loc[4]
+
+ def test_mixed_index_at_iat_loc_iloc_dataframe(self):
+ # GH 19860
+ df = DataFrame(
+ [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], columns=["a", "b", "c", 1, 2]
+ )
+ for rowIdx, row in df.iterrows():
+ for el, item in row.items():
+ assert df.at[rowIdx, el] == df.loc[rowIdx, el] == item
+
+ for row in range(2):
+ for i in range(5):
+ assert df.iat[row, i] == df.iloc[row, i] == row * 5 + i
+
+ with pytest.raises(KeyError, match="^3$"):
+ df.at[0, 3]
+ with pytest.raises(KeyError, match="^3$"):
+ df.loc[0, 3]
+
+ def test_iat_setter_incompatible_assignment(self):
+ # GH 23236
+ result = DataFrame({"a": [0.0, 1.0], "b": [4, 5]})
+ result.iat[0, 0] = None
+ expected = DataFrame({"a": [None, 1], "b": [4, 5]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_iat_dont_wrap_object_datetimelike():
+ # GH#32809 .iat calls go through DataFrame._get_value, should not
+ # call maybe_box_datetimelike
+ dti = date_range("2016-01-01", periods=3)
+ tdi = dti - dti
+ ser = Series(dti.to_pydatetime(), dtype=object)
+ ser2 = Series(tdi.to_pytimedelta(), dtype=object)
+ df = DataFrame({"A": ser, "B": ser2})
+ assert (df.dtypes == object).all()
+
+ for result in [df.at[0, "A"], df.iat[0, 0], df.loc[0, "A"], df.iloc[0, 0]]:
+ assert result is ser[0]
+ assert isinstance(result, datetime)
+ assert not isinstance(result, Timestamp)
+
+ for result in [df.at[1, "B"], df.iat[1, 1], df.loc[1, "B"], df.iloc[1, 1]]:
+ assert result is ser2[1]
+ assert isinstance(result, timedelta)
+ assert not isinstance(result, Timedelta)
+
+
+def test_at_with_tuple_index_get():
+ # GH 26989
+ # DataFrame.at getter works with Index of tuples
+ df = DataFrame({"a": [1, 2]}, index=[(1, 2), (3, 4)])
+ assert df.index.nlevels == 1
+ assert df.at[(1, 2), "a"] == 1
+
+ # Series.at getter works with Index of tuples
+ series = df["a"]
+ assert series.index.nlevels == 1
+ assert series.at[(1, 2)] == 1
+
+
+@pytest.mark.filterwarnings("ignore:Setting a value on a view:FutureWarning")
+def test_at_with_tuple_index_set():
+ # GH 26989
+ # DataFrame.at setter works with Index of tuples
+ df = DataFrame({"a": [1, 2]}, index=[(1, 2), (3, 4)])
+ assert df.index.nlevels == 1
+ df.at[(1, 2), "a"] = 2
+ assert df.at[(1, 2), "a"] == 2
+
+ # Series.at setter works with Index of tuples
+ series = df["a"]
+ assert series.index.nlevels == 1
+ series.at[1, 2] = 3
+ assert series.at[1, 2] == 3
+
+
+class TestMultiIndexScalar:
+ def test_multiindex_at_get(self):
+ # GH 26989
+ # DataFrame.at and DataFrame.loc getter works with MultiIndex
+ df = DataFrame({"a": [1, 2]}, index=[[1, 2], [3, 4]])
+ assert df.index.nlevels == 2
+ assert df.at[(1, 3), "a"] == 1
+ assert df.loc[(1, 3), "a"] == 1
+
+ # Series.at and Series.loc getter works with MultiIndex
+ series = df["a"]
+ assert series.index.nlevels == 2
+ assert series.at[1, 3] == 1
+ assert series.loc[1, 3] == 1
+
+ @pytest.mark.filterwarnings("ignore:Setting a value on a view:FutureWarning")
+ def test_multiindex_at_set(self):
+ # GH 26989
+ # DataFrame.at and DataFrame.loc setter works with MultiIndex
+ df = DataFrame({"a": [1, 2]}, index=[[1, 2], [3, 4]])
+ assert df.index.nlevels == 2
+ df.at[(1, 3), "a"] = 3
+ assert df.at[(1, 3), "a"] == 3
+ df.loc[(1, 3), "a"] = 4
+ assert df.loc[(1, 3), "a"] == 4
+
+ # Series.at and Series.loc setter works with MultiIndex
+ series = df["a"]
+ assert series.index.nlevels == 2
+ series.at[1, 3] = 5
+ assert series.at[1, 3] == 5
+ series.loc[1, 3] = 6
+ assert series.loc[1, 3] == 6
+
+ def test_multiindex_at_get_one_level(self):
+ # GH#38053
+ s2 = Series((0, 1), index=[[False, True]])
+ result = s2.at[False]
+ assert result == 0
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/test_impl.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/test_impl.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e6fa4e2c83e1e539a638044684c0f9f4060d2b5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/test_impl.py
@@ -0,0 +1,699 @@
+from datetime import (
+ datetime,
+ timezone,
+)
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import iNaT
+from pandas.compat import (
+ is_ci_environment,
+ is_platform_windows,
+)
+from pandas.compat.pyarrow import pa_version_under22p0
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.interchange.column import PandasColumn
+from pandas.core.interchange.dataframe_protocol import (
+ ColumnNullType,
+ DtypeKind,
+)
+from pandas.core.interchange.from_dataframe import from_dataframe
+from pandas.core.interchange.utils import ArrowCTypes
+
+
+@pytest.mark.parametrize("data", [("ordered", True), ("unordered", False)])
+def test_categorical_dtype(data):
+ data_categorical = {
+ "ordered": pd.Categorical(list("testdata") * 30, ordered=True),
+ "unordered": pd.Categorical(list("testdata") * 30, ordered=False),
+ }
+ df = pd.DataFrame({"A": (data_categorical[data[0]])})
+
+ with tm.assert_produces_warning(match="Interchange"):
+ col = df.__dataframe__().get_column_by_name("A")
+ assert col.dtype[0] == DtypeKind.CATEGORICAL
+ assert col.null_count == 0
+ assert col.describe_null == (ColumnNullType.USE_SENTINEL, -1)
+ assert col.num_chunks() == 1
+ desc_cat = col.describe_categorical
+ assert desc_cat["is_ordered"] == data[1]
+ assert desc_cat["is_dictionary"] is True
+ assert isinstance(desc_cat["categories"], PandasColumn)
+ tm.assert_series_equal(
+ desc_cat["categories"]._col, pd.Series(["a", "d", "e", "s", "t"])
+ )
+
+ with tm.assert_produces_warning(match="Interchange"):
+ tm.assert_frame_equal(df, from_dataframe(df.__dataframe__()))
+
+
+def test_categorical_pyarrow():
+ # GH 49889
+ pa = pytest.importorskip("pyarrow", "11.0.0")
+
+ arr = ["Mon", "Tue", "Mon", "Wed", "Mon", "Thu", "Fri", "Sat", "Sun"]
+ table = pa.table({"weekday": pa.array(arr).dictionary_encode()})
+ exchange_df = table.__dataframe__()
+ with tm.assert_produces_warning(match="Interchange"):
+ result = from_dataframe(exchange_df)
+ weekday = pd.Categorical(
+ arr, categories=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
+ )
+ expected = pd.DataFrame({"weekday": weekday})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.filterwarnings(
+ "ignore:Constructing a Categorical with a dtype and values containing"
+)
+def test_empty_categorical_pyarrow():
+ # https://github.com/pandas-dev/pandas/issues/53077
+ pa = pytest.importorskip("pyarrow", "11.0.0")
+
+ arr = [None]
+ table = pa.table({"arr": pa.array(arr, "float64").dictionary_encode()})
+ exchange_df = table.__dataframe__()
+ with tm.assert_produces_warning(match="Interchange"):
+ result = pd.api.interchange.from_dataframe(exchange_df)
+ expected = pd.DataFrame({"arr": pd.Categorical([np.nan])})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_large_string_pyarrow():
+ # GH 52795
+ pa = pytest.importorskip("pyarrow", "11.0.0")
+
+ arr = ["Mon", "Tue"]
+ table = pa.table({"weekday": pa.array(arr, "large_string")})
+ exchange_df = table.__dataframe__()
+ with tm.assert_produces_warning(match="Interchange"):
+ result = from_dataframe(exchange_df)
+ expected = pd.DataFrame({"weekday": ["Mon", "Tue"]})
+ tm.assert_frame_equal(result, expected)
+
+ # check round-trip
+ # Don't check stacklevel as PyArrow calls the deprecated `__dataframe__` method.
+ with tm.assert_produces_warning(match="Interchange", check_stacklevel=False):
+ assert pa.Table.equals(pa.interchange.from_dataframe(result), table)
+
+
+@pytest.mark.parametrize(
+ ("offset", "length", "expected_values"),
+ [
+ (0, None, [3.3, float("nan"), 2.1]),
+ (1, None, [float("nan"), 2.1]),
+ (2, None, [2.1]),
+ (0, 2, [3.3, float("nan")]),
+ (0, 1, [3.3]),
+ (1, 1, [float("nan")]),
+ ],
+)
+def test_bitmasks_pyarrow(offset, length, expected_values):
+ # GH 52795
+ pa = pytest.importorskip("pyarrow", "11.0.0")
+
+ arr = [3.3, None, 2.1]
+ table = pa.table({"arr": arr}).slice(offset, length)
+ exchange_df = table.__dataframe__()
+ with tm.assert_produces_warning(match="Interchange"):
+ result = from_dataframe(exchange_df)
+ expected = pd.DataFrame({"arr": expected_values})
+ tm.assert_frame_equal(result, expected)
+
+ # check round-trip
+ # Don't check stacklevel as PyArrow calls the deprecated `__dataframe__` method.
+ with tm.assert_produces_warning(match="Interchange", check_stacklevel=False):
+ assert pa.Table.equals(pa.interchange.from_dataframe(result), table)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ lambda: np.random.default_rng(2).integers(-100, 100),
+ lambda: np.random.default_rng(2).integers(1, 100),
+ lambda: np.random.default_rng(2).random(),
+ lambda: np.random.default_rng(2).choice([True, False]),
+ lambda: datetime(
+ year=np.random.default_rng(2).integers(1900, 2100),
+ month=np.random.default_rng(2).integers(1, 12),
+ day=np.random.default_rng(2).integers(1, 20),
+ ),
+ ],
+)
+def test_dataframe(data):
+ NCOLS, NROWS = 10, 20
+ data = {
+ f"col{int((i - NCOLS / 2) % NCOLS + 1)}": [data() for _ in range(NROWS)]
+ for i in range(NCOLS)
+ }
+ df = pd.DataFrame(data)
+
+ with tm.assert_produces_warning(match="Interchange"):
+ df2 = df.__dataframe__()
+
+ assert df2.num_columns() == NCOLS
+ assert df2.num_rows() == NROWS
+
+ assert list(df2.column_names()) == list(data.keys())
+
+ indices = (0, 2)
+ names = tuple(list(data.keys())[idx] for idx in indices)
+
+ with tm.assert_produces_warning(match="Interchange"):
+ result = from_dataframe(df2.select_columns(indices))
+ expected = from_dataframe(df2.select_columns_by_name(names))
+ tm.assert_frame_equal(result, expected)
+
+ assert isinstance(result.attrs["_INTERCHANGE_PROTOCOL_BUFFERS"], list)
+ assert isinstance(expected.attrs["_INTERCHANGE_PROTOCOL_BUFFERS"], list)
+
+
+def test_missing_from_masked():
+ df = pd.DataFrame(
+ {
+ "x": np.array([1.0, 2.0, 3.0, 4.0, 0.0]),
+ "y": np.array([1.5, 2.5, 3.5, 4.5, 0]),
+ "z": np.array([1.0, 0.0, 1.0, 1.0, 1.0]),
+ }
+ )
+
+ rng = np.random.default_rng(2)
+ dict_null = {col: rng.integers(low=0, high=len(df)) for col in df.columns}
+ for col, num_nulls in dict_null.items():
+ null_idx = df.index[
+ rng.choice(np.arange(len(df)), size=num_nulls, replace=False)
+ ]
+ df.loc[null_idx, col] = None
+
+ with tm.assert_produces_warning(match="Interchange"):
+ df2 = df.__dataframe__()
+
+ assert df2.get_column_by_name("x").null_count == dict_null["x"]
+ assert df2.get_column_by_name("y").null_count == dict_null["y"]
+ assert df2.get_column_by_name("z").null_count == dict_null["z"]
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ {"x": [1.5, 2.5, 3.5], "y": [9.2, 10.5, 11.8]},
+ {"x": [1, 2, 0], "y": [9.2, 10.5, 11.8]},
+ {
+ "x": np.array([True, True, False]),
+ "y": np.array([1, 2, 0]),
+ "z": np.array([9.2, 10.5, 11.8]),
+ },
+ ],
+)
+def test_mixed_data(data):
+ df = pd.DataFrame(data)
+ with tm.assert_produces_warning(match="Interchange"):
+ df2 = df.__dataframe__()
+
+ for col_name in df.columns:
+ assert df2.get_column_by_name(col_name).null_count == 0
+
+
+def test_mixed_missing():
+ df = pd.DataFrame(
+ {
+ "x": np.array([True, None, False, None, True]),
+ "y": np.array([None, 2, None, 1, 2]),
+ "z": np.array([9.2, 10.5, None, 11.8, None]),
+ }
+ )
+
+ with tm.assert_produces_warning(match="Interchange"):
+ df2 = df.__dataframe__()
+
+ for col_name in df.columns:
+ assert df2.get_column_by_name(col_name).null_count == 2
+
+
+def test_string():
+ string_data = {
+ "separator data": [
+ "abC|DeF,Hik",
+ "234,3245.67",
+ "gSaf,qWer|Gre",
+ "asd3,4sad|",
+ np.nan,
+ ]
+ }
+ test_str_data = string_data["separator data"] + [""]
+ df = pd.DataFrame({"A": test_str_data})
+ with tm.assert_produces_warning(match="Interchange"):
+ col = df.__dataframe__().get_column_by_name("A")
+
+ assert col.size() == 6
+ assert col.null_count == 1
+ assert col.dtype[0] == DtypeKind.STRING
+ assert col.describe_null == (ColumnNullType.USE_BYTEMASK, 0)
+
+ df_sliced = df[1:]
+ with tm.assert_produces_warning(match="Interchange"):
+ col = df_sliced.__dataframe__().get_column_by_name("A")
+ assert col.size() == 5
+ assert col.null_count == 1
+ assert col.dtype[0] == DtypeKind.STRING
+ assert col.describe_null == (ColumnNullType.USE_BYTEMASK, 0)
+
+
+def test_nonstring_object():
+ df = pd.DataFrame({"A": ["a", 10, 1.0, ()]})
+ with tm.assert_produces_warning(match="Interchange"):
+ col = df.__dataframe__().get_column_by_name("A")
+ with pytest.raises(NotImplementedError, match="not supported yet"):
+ col.dtype
+
+
+def test_datetime():
+ df = pd.DataFrame({"A": [pd.Timestamp("2022-01-01"), pd.NaT]})
+ with tm.assert_produces_warning(match="Interchange"):
+ col = df.__dataframe__().get_column_by_name("A")
+
+ assert col.size() == 2
+ assert col.null_count == 1
+ assert col.dtype[0] == DtypeKind.DATETIME
+ assert col.describe_null == (ColumnNullType.USE_SENTINEL, iNaT)
+
+ with tm.assert_produces_warning(match="Interchange"):
+ tm.assert_frame_equal(df, from_dataframe(df.__dataframe__()))
+
+
+def test_categorical_to_numpy_dlpack():
+ # https://github.com/pandas-dev/pandas/issues/48393
+ df = pd.DataFrame({"A": pd.Categorical(["a", "b", "a"])})
+ with tm.assert_produces_warning(match="Interchange"):
+ col = df.__dataframe__().get_column_by_name("A")
+ result = np.from_dlpack(col.get_buffers()["data"][0])
+ expected = np.array([0, 1, 0], dtype="int8")
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("data", [{}, {"a": []}])
+def test_empty_pyarrow(data):
+ # GH 53155
+ pytest.importorskip("pyarrow", "14.0.0")
+ from pyarrow.interchange import from_dataframe as pa_from_dataframe
+
+ expected = pd.DataFrame(data)
+ # Don't check stacklevel as PyArrow calls the deprecated `__dataframe__` method.
+ with tm.assert_produces_warning(match="Interchange", check_stacklevel=False):
+ arrow_df = pa_from_dataframe(expected)
+ result = from_dataframe(arrow_df)
+ tm.assert_frame_equal(result, expected, check_column_type=False)
+
+
+def test_multi_chunk_pyarrow() -> None:
+ pa = pytest.importorskip("pyarrow", "14.0.0")
+ n_legs = pa.chunked_array([[2, 2, 4], [4, 5, 100]])
+ names = ["n_legs"]
+ table = pa.table([n_legs], names=names)
+ with pytest.raises(
+ RuntimeError,
+ match="Cannot do zero copy conversion into multi-column DataFrame block",
+ ):
+ pd.api.interchange.from_dataframe(table, allow_copy=False)
+
+
+def test_multi_chunk_column() -> None:
+ pytest.importorskip("pyarrow", "11.0.0")
+ ser = pd.Series([1, 2, None], dtype="Int64[pyarrow]")
+ df = pd.concat([ser, ser], ignore_index=True).to_frame("a")
+ df_orig = df.copy()
+
+ with tm.assert_produces_warning(match="Interchange"):
+ with pytest.raises(
+ RuntimeError,
+ match="Found multi-chunk pyarrow array, but `allow_copy` is False",
+ ):
+ pd.api.interchange.from_dataframe(df.__dataframe__(allow_copy=False))
+ with tm.assert_produces_warning(match="Interchange"):
+ result = pd.api.interchange.from_dataframe(df.__dataframe__(allow_copy=True))
+ # Interchange protocol defaults to creating numpy-backed columns, so currently this
+ # is 'float64'.
+ expected = pd.DataFrame({"a": [1.0, 2.0, None, 1.0, 2.0, None]}, dtype="float64")
+ tm.assert_frame_equal(result, expected)
+
+ # Check that the rechunking we did didn't modify the original DataFrame.
+ tm.assert_frame_equal(df, df_orig)
+ assert len(df["a"].array._pa_array.chunks) == 2
+ assert len(df_orig["a"].array._pa_array.chunks) == 2
+
+
+def test_timestamp_ns_pyarrow():
+ # GH 56712
+ pytest.importorskip("pyarrow", "11.0.0")
+ timestamp_args = {
+ "year": 2000,
+ "month": 1,
+ "day": 1,
+ "hour": 1,
+ "minute": 1,
+ "second": 1,
+ }
+ df = pd.Series(
+ [datetime(**timestamp_args)],
+ dtype="timestamp[ns][pyarrow]",
+ name="col0",
+ ).to_frame()
+
+ with tm.assert_produces_warning(match="Interchange"):
+ dfi = df.__dataframe__()
+ result = pd.api.interchange.from_dataframe(dfi)["col0"].item()
+
+ expected = pd.Timestamp(**timestamp_args)
+ assert result == expected
+
+
+@pytest.mark.parametrize("tz", ["UTC", "US/Pacific"])
+def test_datetimetzdtype(tz, unit):
+ # GH 54239
+ tz_data = (
+ pd.date_range("2018-01-01", periods=5, freq="D").tz_localize(tz).as_unit(unit)
+ )
+ df = pd.DataFrame({"ts_tz": tz_data})
+ with tm.assert_produces_warning(match="Interchange"):
+ tm.assert_frame_equal(df, from_dataframe(df.__dataframe__()))
+
+
+def test_interchange_from_non_pandas_tz_aware(request):
+ # GH 54239, 54287
+ pa = pytest.importorskip("pyarrow", "11.0.0")
+ import pyarrow.compute as pc
+
+ if is_platform_windows() and is_ci_environment() and pa_version_under22p0:
+ mark = pytest.mark.xfail(
+ raises=pa.ArrowInvalid,
+ reason=(
+ "TODO: Set ARROW_TIMEZONE_DATABASE environment variable "
+ "on CI to path to the tzdata for pyarrow."
+ ),
+ )
+ request.applymarker(mark)
+
+ arr = pa.array([datetime(2020, 1, 1), None, datetime(2020, 1, 2)])
+ arr = pc.assume_timezone(arr, "Asia/Kathmandu")
+ table = pa.table({"arr": arr})
+ exchange_df = table.__dataframe__()
+ with tm.assert_produces_warning(match="Interchange"):
+ result = from_dataframe(exchange_df)
+
+ expected = pd.DataFrame(
+ ["2020-01-01 00:00:00+05:45", "NaT", "2020-01-02 00:00:00+05:45"],
+ columns=["arr"],
+ dtype="datetime64[us, Asia/Kathmandu]",
+ )
+ tm.assert_frame_equal(expected, result)
+
+
+def test_interchange_from_corrected_buffer_dtypes(monkeypatch) -> None:
+ # https://github.com/pandas-dev/pandas/issues/54781
+ with tm.assert_produces_warning(match="Interchange"):
+ df = pd.DataFrame({"a": ["foo", "bar"]}).__dataframe__()
+ interchange = df.__dataframe__()
+ column = interchange.get_column_by_name("a")
+ buffers = column.get_buffers()
+ buffers_data = buffers["data"]
+ buffer_dtype = buffers_data[1]
+ buffer_dtype = (
+ DtypeKind.UINT,
+ 8,
+ ArrowCTypes.UINT8,
+ buffer_dtype[3],
+ )
+ buffers["data"] = (buffers_data[0], buffer_dtype)
+ column.get_buffers = lambda: buffers
+ interchange.get_column_by_name = lambda _: column
+ monkeypatch.setattr(df, "__dataframe__", lambda allow_copy: interchange)
+ with tm.assert_produces_warning(match="Interchange"):
+ pd.api.interchange.from_dataframe(df)
+
+
+def test_empty_string_column():
+ # https://github.com/pandas-dev/pandas/issues/56703
+ df = pd.DataFrame({"a": []}, dtype=str)
+ with tm.assert_produces_warning(match="Interchange"):
+ df2 = df.__dataframe__()
+ result = pd.api.interchange.from_dataframe(df2)
+ tm.assert_frame_equal(df, result)
+
+
+def test_large_string():
+ # GH#56702
+ pytest.importorskip("pyarrow")
+ df = pd.DataFrame({"a": ["x"]}, dtype="large_string[pyarrow]")
+ # Don't check stacklevel as PyArrow calls the deprecated `__dataframe__` method.
+ with tm.assert_produces_warning(match="Interchange", check_stacklevel=False):
+ result = pd.api.interchange.from_dataframe(df.__dataframe__())
+ expected = pd.DataFrame({"a": ["x"]}, dtype="str")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_non_str_names():
+ # https://github.com/pandas-dev/pandas/issues/56701
+ df = pd.Series([1, 2, 3], name=0).to_frame()
+ with tm.assert_produces_warning(match="Interchange"):
+ names = df.__dataframe__().column_names()
+ assert names == ["0"]
+
+
+def test_non_str_names_w_duplicates():
+ # https://github.com/pandas-dev/pandas/issues/56701
+ df = pd.DataFrame({"0": [1, 2, 3], 0: [4, 5, 6]})
+ with tm.assert_produces_warning(match="Interchange"):
+ dfi = df.__dataframe__()
+ with tm.assert_produces_warning(match="Interchange"):
+ with pytest.raises(
+ TypeError,
+ match=(
+ "Expected a Series, got a DataFrame. This likely happened because you "
+ "called __dataframe__ on a DataFrame which, after converting column "
+ r"names to string, resulted in duplicated names: Index\(\['0', '0'\], "
+ r"dtype='(str|object)'\). Please rename these columns before using the "
+ "interchange protocol."
+ ),
+ ):
+ pd.api.interchange.from_dataframe(dfi, allow_copy=False)
+
+
+@pytest.mark.parametrize(
+ ("data", "dtype", "expected_dtype"),
+ [
+ ([1, 2, None], "Int64", "int64"),
+ ([1, 2, None], "Int64[pyarrow]", "int64"),
+ ([1, 2, None], "Int8", "int8"),
+ ([1, 2, None], "Int8[pyarrow]", "int8"),
+ (
+ [1, 2, None],
+ "UInt64",
+ "uint64",
+ ),
+ (
+ [1, 2, None],
+ "UInt64[pyarrow]",
+ "uint64",
+ ),
+ ([1.0, 2.25, None], "Float32", "float32"),
+ ([1.0, 2.25, None], "Float32[pyarrow]", "float32"),
+ ([True, False, None], "boolean", "bool"),
+ ([True, False, None], "boolean[pyarrow]", "bool"),
+ (["much ado", "about", None], pd.StringDtype(na_value=np.nan), "large_string"),
+ (["much ado", "about", None], "string[pyarrow]", "large_string"),
+ (
+ [datetime(2020, 1, 1), datetime(2020, 1, 2), None],
+ "timestamp[ns][pyarrow]",
+ "timestamp[ns]",
+ ),
+ (
+ [datetime(2020, 1, 1), datetime(2020, 1, 2), None],
+ "timestamp[us][pyarrow]",
+ "timestamp[us]",
+ ),
+ (
+ [
+ datetime(2020, 1, 1, tzinfo=timezone.utc),
+ datetime(2020, 1, 2, tzinfo=timezone.utc),
+ None,
+ ],
+ "timestamp[us, Asia/Kathmandu][pyarrow]",
+ "timestamp[us, tz=Asia/Kathmandu]",
+ ),
+ ],
+)
+def test_pandas_nullable_with_missing_values(
+ data: list, dtype: str, expected_dtype: str
+) -> None:
+ # https://github.com/pandas-dev/pandas/issues/57643
+ # https://github.com/pandas-dev/pandas/issues/57664
+ pa = pytest.importorskip("pyarrow", "14.0.0")
+ import pyarrow.interchange as pai
+
+ if expected_dtype == "timestamp[us, tz=Asia/Kathmandu]":
+ expected_dtype = pa.timestamp("us", "Asia/Kathmandu")
+
+ df = pd.DataFrame({"a": data}, dtype=dtype)
+ with tm.assert_produces_warning(match="Interchange"):
+ result = pai.from_dataframe(df.__dataframe__())["a"]
+ assert result.type == expected_dtype
+ assert result[0].as_py() == data[0]
+ assert result[1].as_py() == data[1]
+ assert result[2].as_py() is None
+
+
+@pytest.mark.parametrize(
+ ("data", "dtype", "expected_dtype"),
+ [
+ ([1, 2, 3], "Int64", "int64"),
+ ([1, 2, 3], "Int64[pyarrow]", "int64"),
+ ([1, 2, 3], "Int8", "int8"),
+ ([1, 2, 3], "Int8[pyarrow]", "int8"),
+ (
+ [1, 2, 3],
+ "UInt64",
+ "uint64",
+ ),
+ (
+ [1, 2, 3],
+ "UInt64[pyarrow]",
+ "uint64",
+ ),
+ ([1.0, 2.25, 5.0], "Float32", "float32"),
+ ([1.0, 2.25, 5.0], "Float32[pyarrow]", "float32"),
+ ([True, False, False], "boolean", "bool"),
+ ([True, False, False], "boolean[pyarrow]", "bool"),
+ (
+ ["much ado", "about", "nothing"],
+ pd.StringDtype(na_value=np.nan),
+ "large_string",
+ ),
+ (["much ado", "about", "nothing"], "string[pyarrow]", "large_string"),
+ (
+ [datetime(2020, 1, 1), datetime(2020, 1, 2), datetime(2020, 1, 3)],
+ "timestamp[ns][pyarrow]",
+ "timestamp[ns]",
+ ),
+ (
+ [datetime(2020, 1, 1), datetime(2020, 1, 2), datetime(2020, 1, 3)],
+ "timestamp[us][pyarrow]",
+ "timestamp[us]",
+ ),
+ (
+ [
+ datetime(2020, 1, 1, tzinfo=timezone.utc),
+ datetime(2020, 1, 2, tzinfo=timezone.utc),
+ datetime(2020, 1, 3, tzinfo=timezone.utc),
+ ],
+ "timestamp[us, Asia/Kathmandu][pyarrow]",
+ "timestamp[us, tz=Asia/Kathmandu]",
+ ),
+ ],
+)
+def test_pandas_nullable_without_missing_values(
+ data: list, dtype: str, expected_dtype: str
+) -> None:
+ # https://github.com/pandas-dev/pandas/issues/57643
+ pa = pytest.importorskip("pyarrow", "14.0.0")
+ import pyarrow.interchange as pai
+
+ if expected_dtype == "timestamp[us, tz=Asia/Kathmandu]":
+ expected_dtype = pa.timestamp("us", "Asia/Kathmandu")
+
+ df = pd.DataFrame({"a": data}, dtype=dtype)
+ with tm.assert_produces_warning(match="Interchange"):
+ result = pai.from_dataframe(df.__dataframe__())["a"]
+ assert result.type == expected_dtype
+ assert result[0].as_py() == data[0]
+ assert result[1].as_py() == data[1]
+ assert result[2].as_py() == data[2]
+
+
+def test_string_validity_buffer() -> None:
+ # https://github.com/pandas-dev/pandas/issues/57761
+ pytest.importorskip("pyarrow", "11.0.0")
+ df = pd.DataFrame({"a": ["x"]}, dtype="large_string[pyarrow]")
+ with tm.assert_produces_warning(match="Interchange"):
+ result = df.__dataframe__().get_column_by_name("a").get_buffers()["validity"]
+ assert result is None
+
+
+def test_string_validity_buffer_no_missing() -> None:
+ # https://github.com/pandas-dev/pandas/issues/57762
+ pytest.importorskip("pyarrow", "11.0.0")
+ df = pd.DataFrame({"a": ["x", None]}, dtype="large_string[pyarrow]")
+ with tm.assert_produces_warning(match="Interchange"):
+ validity = df.__dataframe__().get_column_by_name("a").get_buffers()["validity"]
+ assert validity is not None
+ result = validity[1]
+ expected = (DtypeKind.BOOL, 1, ArrowCTypes.BOOL, "=")
+ assert result == expected
+
+
+def test_empty_dataframe():
+ # https://github.com/pandas-dev/pandas/issues/56700
+ df = pd.DataFrame({"a": []}, dtype="int8")
+ with tm.assert_produces_warning(match="Interchange"):
+ dfi = df.__dataframe__()
+ result = pd.api.interchange.from_dataframe(dfi, allow_copy=False)
+ expected = pd.DataFrame({"a": []}, dtype="int8")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ ("data", "expected_dtype", "expected_buffer_dtype"),
+ [
+ (
+ pd.Series(["a", "b", "a"], dtype="category"),
+ (DtypeKind.CATEGORICAL, 8, "c", "="),
+ (DtypeKind.INT, 8, "c", "|"),
+ ),
+ (
+ pd.Series(
+ [datetime(2022, 1, 1), datetime(2022, 1, 2), datetime(2022, 1, 3)],
+ dtype="M8[ns]",
+ ),
+ (DtypeKind.DATETIME, 64, "tsn:", "="),
+ (DtypeKind.INT, 64, ArrowCTypes.INT64, "="),
+ ),
+ (
+ pd.Series(["a", "bc", None]),
+ (DtypeKind.STRING, 8, ArrowCTypes.STRING, "="),
+ (DtypeKind.UINT, 8, ArrowCTypes.UINT8, "="),
+ ),
+ (
+ pd.Series([1, 2, 3]),
+ (DtypeKind.INT, 64, ArrowCTypes.INT64, "="),
+ (DtypeKind.INT, 64, ArrowCTypes.INT64, "="),
+ ),
+ (
+ pd.Series([1.5, 2, 3]),
+ (DtypeKind.FLOAT, 64, ArrowCTypes.FLOAT64, "="),
+ (DtypeKind.FLOAT, 64, ArrowCTypes.FLOAT64, "="),
+ ),
+ ],
+)
+def test_buffer_dtype_categorical(
+ data: pd.Series,
+ expected_dtype: tuple[DtypeKind, int, str, str],
+ expected_buffer_dtype: tuple[DtypeKind, int, str, str],
+) -> None:
+ # https://github.com/pandas-dev/pandas/issues/54781
+ df = pd.DataFrame({"data": data})
+ with tm.assert_produces_warning(match="Interchange"):
+ dfi = df.__dataframe__()
+ col = dfi.get_column_by_name("data")
+ assert col.dtype == expected_dtype
+ assert col.get_buffers()["data"][1] == expected_buffer_dtype
+
+
+def test_from_dataframe_list_dtype():
+ pa = pytest.importorskip("pyarrow", "14.0.0")
+ data = {"a": [[1, 2], [4, 5, 6]]}
+ tbl = pa.table(data)
+ result = from_dataframe(tbl)
+ expected = pd.DataFrame(data)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/test_spec_conformance.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/test_spec_conformance.py
new file mode 100644
index 0000000000000000000000000000000000000000..04e19b290f886a4cd37e8c41ef2f2e65d7e678e8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/test_spec_conformance.py
@@ -0,0 +1,187 @@
+"""
+A verbatim copy (vendored) of the spec tests.
+Taken from https://github.com/data-apis/dataframe-api
+"""
+
+import ctypes
+import math
+
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+@pytest.fixture
+def df_from_dict():
+ def maker(dct, is_categorical=False):
+ df = pd.DataFrame(dct)
+ return df.astype("category") if is_categorical else df
+
+ return maker
+
+
+@pytest.mark.parametrize(
+ "test_data",
+ [
+ {"a": ["foo", "bar"], "b": ["baz", "qux"]},
+ {"a": [1.5, 2.5, 3.5], "b": [9.2, 10.5, 11.8]},
+ {"A": [1, 2, 3, 4], "B": [1, 2, 3, 4]},
+ ],
+ ids=["str_data", "float_data", "int_data"],
+)
+def test_only_one_dtype(test_data, df_from_dict):
+ columns = list(test_data.keys())
+ df = df_from_dict(test_data)
+ with tm.assert_produces_warning(match="Interchange"):
+ dfX = df.__dataframe__()
+
+ column_size = len(test_data[columns[0]])
+ for column in columns:
+ null_count = dfX.get_column_by_name(column).null_count
+ assert null_count == 0
+ assert isinstance(null_count, int)
+ assert dfX.get_column_by_name(column).size() == column_size
+ assert dfX.get_column_by_name(column).offset == 0
+
+
+def test_mixed_dtypes(df_from_dict):
+ df = df_from_dict(
+ {
+ "a": [1, 2, 3], # dtype kind INT = 0
+ "b": [3, 4, 5], # dtype kind INT = 0
+ "c": [1.5, 2.5, 3.5], # dtype kind FLOAT = 2
+ "d": [9, 10, 11], # dtype kind INT = 0
+ "e": [True, False, True], # dtype kind BOOLEAN = 20
+ "f": ["a", "", "c"], # dtype kind STRING = 21
+ }
+ )
+ with tm.assert_produces_warning(match="Interchange"):
+ dfX = df.__dataframe__()
+ # for meanings of dtype[0] see the spec; we cannot import the spec here as this
+ # file is expected to be vendored *anywhere*;
+ # values for dtype[0] are explained above
+ columns = {"a": 0, "b": 0, "c": 2, "d": 0, "e": 20, "f": 21}
+
+ for column, kind in columns.items():
+ colX = dfX.get_column_by_name(column)
+ assert colX.null_count == 0
+ assert isinstance(colX.null_count, int)
+ assert colX.size() == 3
+ assert colX.offset == 0
+
+ assert colX.dtype[0] == kind
+
+ assert dfX.get_column_by_name("c").dtype[1] == 64
+
+
+def test_na_float(df_from_dict):
+ df = df_from_dict({"a": [1.0, math.nan, 2.0]})
+ with tm.assert_produces_warning(match="Interchange"):
+ dfX = df.__dataframe__()
+ colX = dfX.get_column_by_name("a")
+ assert colX.null_count == 1
+ assert isinstance(colX.null_count, int)
+
+
+def test_noncategorical(df_from_dict):
+ df = df_from_dict({"a": [1, 2, 3]})
+ with tm.assert_produces_warning(match="Interchange"):
+ dfX = df.__dataframe__()
+ colX = dfX.get_column_by_name("a")
+ with pytest.raises(TypeError, match=".*categorical.*"):
+ colX.describe_categorical
+
+
+def test_categorical(df_from_dict):
+ df = df_from_dict(
+ {"weekday": ["Mon", "Tue", "Mon", "Wed", "Mon", "Thu", "Fri", "Sat", "Sun"]},
+ is_categorical=True,
+ )
+
+ with tm.assert_produces_warning(match="Interchange"):
+ colX = df.__dataframe__().get_column_by_name("weekday")
+ categorical = colX.describe_categorical
+ assert isinstance(categorical["is_ordered"], bool)
+ assert isinstance(categorical["is_dictionary"], bool)
+
+
+def test_dataframe(df_from_dict):
+ df = df_from_dict(
+ {"x": [True, True, False], "y": [1, 2, 0], "z": [9.2, 10.5, 11.8]}
+ )
+ with tm.assert_produces_warning(match="Interchange"):
+ dfX = df.__dataframe__()
+
+ assert dfX.num_columns() == 3
+ assert dfX.num_rows() == 3
+ assert dfX.num_chunks() == 1
+ assert list(dfX.column_names()) == ["x", "y", "z"]
+ assert list(dfX.select_columns((0, 2)).column_names()) == list(
+ dfX.select_columns_by_name(("x", "z")).column_names()
+ )
+
+
+@pytest.mark.parametrize(["size", "n_chunks"], [(10, 3), (12, 3), (12, 5)])
+def test_df_get_chunks(size, n_chunks, df_from_dict):
+ df = df_from_dict({"x": list(range(size))})
+ with tm.assert_produces_warning(match="Interchange"):
+ dfX = df.__dataframe__()
+ chunks = list(dfX.get_chunks(n_chunks))
+ assert len(chunks) == n_chunks
+ assert sum(chunk.num_rows() for chunk in chunks) == size
+
+
+@pytest.mark.parametrize(["size", "n_chunks"], [(10, 3), (12, 3), (12, 5)])
+def test_column_get_chunks(size, n_chunks, df_from_dict):
+ df = df_from_dict({"x": list(range(size))})
+ with tm.assert_produces_warning(match="Interchange"):
+ dfX = df.__dataframe__()
+ chunks = list(dfX.get_column(0).get_chunks(n_chunks))
+ assert len(chunks) == n_chunks
+ assert sum(chunk.size() for chunk in chunks) == size
+
+
+def test_get_columns(df_from_dict):
+ df = df_from_dict({"a": [0, 1], "b": [2.5, 3.5]})
+ with tm.assert_produces_warning(match="Interchange"):
+ dfX = df.__dataframe__()
+ for colX in dfX.get_columns():
+ assert colX.size() == 2
+ assert colX.num_chunks() == 1
+ # for meanings of dtype[0] see the spec; we cannot import the spec here as this
+ # file is expected to be vendored *anywhere*
+ assert dfX.get_column(0).dtype[0] == 0 # INT
+ assert dfX.get_column(1).dtype[0] == 2 # FLOAT
+
+
+def test_buffer(df_from_dict):
+ arr = [0, 1, -1]
+ df = df_from_dict({"a": arr})
+ with tm.assert_produces_warning(match="Interchange"):
+ dfX = df.__dataframe__()
+ colX = dfX.get_column(0)
+ bufX = colX.get_buffers()
+
+ dataBuf, dataDtype = bufX["data"]
+
+ assert dataBuf.bufsize > 0
+ assert dataBuf.ptr != 0
+ device, _ = dataBuf.__dlpack_device__()
+
+ # for meanings of dtype[0] see the spec; we cannot import the spec here as this
+ # file is expected to be vendored *anywhere*
+ assert dataDtype[0] == 0 # INT
+
+ if device == 1: # CPU-only as we're going to directly read memory here
+ bitwidth = dataDtype[1]
+ ctype = {
+ 8: ctypes.c_int8,
+ 16: ctypes.c_int16,
+ 32: ctypes.c_int32,
+ 64: ctypes.c_int64,
+ }[bitwidth]
+
+ for idx, truth in enumerate(arr):
+ val = ctype.from_address(dataBuf.ptr + idx * (bitwidth // 8)).value
+ assert val == truth, f"Buffer at index {idx} mismatch"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/test_utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/test_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..a47bc2752ff32f5eb7630a3960e7611242cb73e3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/interchange/test_utils.py
@@ -0,0 +1,89 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas.core.interchange.utils import dtype_to_arrow_c_fmt
+
+# TODO: use ArrowSchema to get reference C-string.
+# At the time, there is no way to access ArrowSchema holding a type format string
+# from python. The only way to access it is to export the structure to a C-pointer,
+# see DataType._export_to_c() method defined in
+# https://github.com/apache/arrow/blob/master/python/pyarrow/types.pxi
+
+
+@pytest.mark.parametrize(
+ "pandas_dtype, c_string",
+ [
+ (np.dtype("bool"), "b"),
+ (np.dtype("int8"), "c"),
+ (np.dtype("uint8"), "C"),
+ (np.dtype("int16"), "s"),
+ (np.dtype("uint16"), "S"),
+ (np.dtype("int32"), "i"),
+ (np.dtype("uint32"), "I"),
+ (np.dtype("int64"), "l"),
+ (np.dtype("uint64"), "L"),
+ (np.dtype("float16"), "e"),
+ (np.dtype("float32"), "f"),
+ (np.dtype("float64"), "g"),
+ (pd.Series(["a"]).dtype, "u"),
+ (
+ pd.Series([0]).astype("datetime64[ns]").dtype,
+ "tsn:",
+ ),
+ (pd.CategoricalDtype(["a"]), "l"),
+ (np.dtype("O"), "u"),
+ ],
+)
+def test_dtype_to_arrow_c_fmt(pandas_dtype, c_string): # PR01
+ """Test ``dtype_to_arrow_c_fmt`` utility function."""
+ assert dtype_to_arrow_c_fmt(pandas_dtype) == c_string
+
+
+@pytest.mark.parametrize(
+ "pa_dtype, args_kwargs, c_string",
+ [
+ ["null", {}, "n"],
+ ["bool_", {}, "b"],
+ ["uint8", {}, "C"],
+ ["uint16", {}, "S"],
+ ["uint32", {}, "I"],
+ ["uint64", {}, "L"],
+ ["int8", {}, "c"],
+ ["int16", {}, "S"],
+ ["int32", {}, "i"],
+ ["int64", {}, "l"],
+ ["float16", {}, "e"],
+ ["float32", {}, "f"],
+ ["float64", {}, "g"],
+ ["string", {}, "u"],
+ ["binary", {}, "z"],
+ ["time32", ("s",), "tts"],
+ ["time32", ("ms",), "ttm"],
+ ["time64", ("us",), "ttu"],
+ ["time64", ("ns",), "ttn"],
+ ["date32", {}, "tdD"],
+ ["date64", {}, "tdm"],
+ ["timestamp", {"unit": "s"}, "tss:"],
+ ["timestamp", {"unit": "ms"}, "tsm:"],
+ ["timestamp", {"unit": "us"}, "tsu:"],
+ ["timestamp", {"unit": "ns"}, "tsn:"],
+ ["timestamp", {"unit": "ns", "tz": "UTC"}, "tsn:UTC"],
+ ["duration", ("s",), "tDs"],
+ ["duration", ("ms",), "tDm"],
+ ["duration", ("us",), "tDu"],
+ ["duration", ("ns",), "tDn"],
+ ["decimal128", {"precision": 4, "scale": 2}, "d:4,2"],
+ ],
+)
+def test_dtype_to_arrow_c_fmt_arrowdtype(pa_dtype, args_kwargs, c_string):
+ # GH 52323
+ pa = pytest.importorskip("pyarrow")
+ if not args_kwargs:
+ pa_type = getattr(pa, pa_dtype)()
+ elif isinstance(args_kwargs, tuple):
+ pa_type = getattr(pa, pa_dtype)(*args_kwargs)
+ else:
+ pa_type = getattr(pa, pa_dtype)(**args_kwargs)
+ arrow_type = pd.ArrowDtype(pa_type)
+ assert dtype_to_arrow_c_fmt(arrow_type) == c_string
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/internals/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/internals/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/internals/test_api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/internals/test_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..9368105a1fa5b192e7b223529e527390b9d6d7bf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/internals/test_api.py
@@ -0,0 +1,178 @@
+"""
+Tests for the pseudo-public API implemented in internals/api.py and exposed
+in core.internals
+"""
+
+import datetime
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.api.internals import create_dataframe_from_blocks
+from pandas.core import internals
+from pandas.core.internals import api
+
+
+def test_internals_api():
+ assert internals.make_block is api.make_block
+
+
+def test_namespace():
+ # SUBJECT TO CHANGE
+
+ modules = [
+ "blocks",
+ "concat",
+ "managers",
+ "construction",
+ "api",
+ "ops",
+ ]
+ expected = [
+ "make_block",
+ "BlockManager",
+ "SingleBlockManager",
+ "concatenate_managers",
+ ]
+
+ result = [x for x in dir(internals) if not x.startswith("__")]
+ assert set(result) == set(expected + modules), set(result) ^ set(expected + modules)
+
+
+@pytest.mark.parametrize(
+ "name",
+ [
+ "Block",
+ "ExtensionBlock",
+ ],
+)
+def test_deprecations(name):
+ # GH#55139
+ msg = f"{name} is deprecated.* Use public APIs instead"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ getattr(internals, name)
+
+
+def test_make_block_2d_with_dti():
+ # GH#41168
+ dti = pd.date_range("2012", periods=3, tz="UTC")
+
+ msg = "make_block is deprecated"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ blk = api.make_block(dti, placement=[0])
+
+ assert blk.shape == (1, 3)
+ assert blk.values.shape == (1, 3)
+
+
+def test_create_block_manager_from_blocks_deprecated():
+ # GH#33892
+ # If they must, downstream packages should get this from internals.api,
+ # not internals.
+ msg = (
+ "create_block_manager_from_blocks is deprecated and will be "
+ "removed in a future version. Use public APIs instead"
+ )
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ internals.create_block_manager_from_blocks
+
+
+def test_maybe_infer_ndim_deprecated():
+ # GH#40226
+ msg = "maybe_infer_ndim is deprecated and will be removed in a future version."
+ arr = np.arange(5)
+ bp = pd._libs.internals.BlockPlacement([1])
+ with tm.assert_produces_warning(DeprecationWarning, match=msg):
+ internals.api.maybe_infer_ndim(arr, bp, 1)
+
+
+def test_create_dataframe_from_blocks(float_frame):
+ block = float_frame._mgr.blocks[0]
+ index = float_frame.index.copy()
+ columns = float_frame.columns.copy()
+
+ result = create_dataframe_from_blocks(
+ [(block.values, block.mgr_locs.as_array)], index=index, columns=columns
+ )
+ tm.assert_frame_equal(result, float_frame)
+
+
+def test_create_dataframe_from_blocks_types():
+ df = pd.DataFrame(
+ {
+ "int": list(range(1, 4)),
+ "uint": np.arange(3, 6).astype("uint8"),
+ "float": [2.0, np.nan, 3.0],
+ "bool": np.array([True, False, True]),
+ "boolean": pd.array([True, False, None], dtype="boolean"),
+ "string": list("abc"),
+ "datetime": pd.date_range("20130101", periods=3),
+ "datetimetz": pd.date_range("20130101", periods=3).tz_localize(
+ "Europe/Brussels"
+ ),
+ "timedelta": pd.timedelta_range("1 day", periods=3),
+ "period": pd.period_range("2012-01-01", periods=3, freq="D"),
+ "categorical": pd.Categorical(["a", "b", "a"]),
+ "interval": pd.IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)]),
+ }
+ )
+
+ result = create_dataframe_from_blocks(
+ [(block.values, block.mgr_locs.as_array) for block in df._mgr.blocks],
+ index=df.index,
+ columns=df.columns,
+ )
+ tm.assert_frame_equal(result, df)
+
+
+def test_create_dataframe_from_blocks_datetimelike():
+ # extension dtypes that have an exact matching numpy dtype can also be
+ # be passed as a numpy array
+ index, columns = pd.RangeIndex(3), pd.Index(["a", "b", "c", "d"])
+
+ block_array1 = np.arange(
+ datetime.datetime(2020, 1, 1),
+ datetime.datetime(2020, 1, 7),
+ step=datetime.timedelta(1),
+ ).reshape((2, 3))
+ block_array2 = np.arange(
+ datetime.timedelta(1), datetime.timedelta(7), step=datetime.timedelta(1)
+ ).reshape((2, 3))
+ result = create_dataframe_from_blocks(
+ [(block_array1, np.array([0, 2])), (block_array2, np.array([1, 3]))],
+ index=index,
+ columns=columns,
+ )
+ expected = pd.DataFrame(
+ {
+ "a": pd.date_range("2020-01-01", periods=3, unit="us"),
+ "b": pd.timedelta_range("1 days", periods=3, unit="us"),
+ "c": pd.date_range("2020-01-04", periods=3, unit="us"),
+ "d": pd.timedelta_range("4 days", periods=3, unit="us"),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "array",
+ [
+ pd.date_range("2020-01-01", periods=3),
+ pd.date_range("2020-01-01", periods=3, tz="UTC"),
+ pd.period_range("2012-01-01", periods=3, freq="D"),
+ pd.timedelta_range("1 day", periods=3),
+ ],
+)
+def test_create_dataframe_from_blocks_1dEA(array):
+ # ExtensionArrays can be passed as 1D even if stored under the hood as 2D
+ df = pd.DataFrame({"a": array})
+
+ block = df._mgr.blocks[0]
+ result = create_dataframe_from_blocks(
+ [(block.values[0], block.mgr_locs.as_array)], index=df.index, columns=df.columns
+ )
+ tm.assert_frame_equal(result, df)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/internals/test_internals.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/internals/test_internals.py
new file mode 100644
index 0000000000000000000000000000000000000000..8852ae81bddf2032f3cf03105917dde9fb6637bf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/internals/test_internals.py
@@ -0,0 +1,1421 @@
+from datetime import (
+ date,
+ datetime,
+)
+import itertools
+import re
+
+import numpy as np
+import pytest
+
+from pandas._libs.internals import BlockPlacement
+from pandas.compat import IS64
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.common import is_scalar
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ IntervalIndex,
+ Series,
+ Timedelta,
+ Timestamp,
+ period_range,
+)
+import pandas._testing as tm
+import pandas.core.algorithms as algos
+from pandas.core.arrays import (
+ DatetimeArray,
+ SparseArray,
+ TimedeltaArray,
+)
+from pandas.core.internals import (
+ BlockManager,
+ SingleBlockManager,
+ make_block,
+)
+from pandas.core.internals.blocks import (
+ ensure_block_shape,
+ maybe_coerce_values,
+ new_block,
+)
+
+
+@pytest.fixture(params=[new_block, make_block])
+def block_maker(request):
+ """
+ Fixture to test both the internal new_block and pseudo-public make_block.
+ """
+ return request.param
+
+
+@pytest.fixture
+def mgr():
+ return create_mgr(
+ "a: f8; b: object; c: f8; d: object; e: f8;"
+ "f: bool; g: i8; h: complex; i: datetime-1; j: datetime-2;"
+ "k: M8[ns, US/Eastern]; l: M8[ns, CET];"
+ )
+
+
+def assert_block_equal(left, right):
+ tm.assert_numpy_array_equal(left.values, right.values)
+ assert left.dtype == right.dtype
+ assert isinstance(left.mgr_locs, BlockPlacement)
+ assert isinstance(right.mgr_locs, BlockPlacement)
+ tm.assert_numpy_array_equal(left.mgr_locs.as_array, right.mgr_locs.as_array)
+
+
+def get_numeric_mat(shape):
+ arr = np.arange(shape[0])
+ return np.lib.stride_tricks.as_strided(
+ x=arr, shape=shape, strides=(arr.itemsize,) + (0,) * (len(shape) - 1)
+ ).copy()
+
+
+N = 10
+
+
+def create_block(typestr, placement, item_shape=None, num_offset=0, maker=new_block):
+ """
+ Supported typestr:
+
+ * float, f8, f4, f2
+ * int, i8, i4, i2, i1
+ * uint, u8, u4, u2, u1
+ * complex, c16, c8
+ * bool
+ * object, string, O
+ * datetime, dt, M8[ns], M8[ns, tz]
+ * timedelta, td, m8[ns]
+ * sparse (SparseArray with fill_value=0.0)
+ * sparse_na (SparseArray with fill_value=np.nan)
+ * category, category2
+
+ """
+ placement = BlockPlacement(placement)
+ num_items = len(placement)
+
+ if item_shape is None:
+ item_shape = (N,)
+
+ shape = (num_items, *item_shape)
+
+ mat = get_numeric_mat(shape)
+
+ if typestr in (
+ "float",
+ "f8",
+ "f4",
+ "f2",
+ "int",
+ "i8",
+ "i4",
+ "i2",
+ "i1",
+ "uint",
+ "u8",
+ "u4",
+ "u2",
+ "u1",
+ ):
+ values = mat.astype(typestr) + num_offset
+ elif typestr in ("complex", "c16", "c8"):
+ values = 1.0j * (mat.astype(typestr) + num_offset)
+ elif typestr in ("object", "string", "O"):
+ values = np.reshape([f"A{i:d}" for i in mat.ravel() + num_offset], shape)
+ elif typestr in ("b", "bool"):
+ values = np.ones(shape, dtype=np.bool_)
+ elif typestr in ("datetime", "dt", "M8[ns]"):
+ values = (mat * 1e9).astype("M8[ns]")
+ elif typestr.startswith("M8[ns"):
+ # datetime with tz
+ m = re.search(r"M8\[ns,\s*(\w+\/?\w*)\]", typestr)
+ assert m is not None, f"incompatible typestr -> {typestr}"
+ tz = m.groups()[0]
+ assert num_items == 1, "must have only 1 num items for a tz-aware"
+ values = DatetimeIndex(np.arange(N) * 10**9, tz=tz)._data
+ values = ensure_block_shape(values, ndim=len(shape))
+ elif typestr in ("timedelta", "td", "m8[ns]"):
+ values = (mat * 1).astype("m8[ns]")
+ elif typestr in ("category",):
+ values = Categorical([1, 1, 2, 2, 3, 3, 3, 3, 4, 4])
+ elif typestr in ("category2",):
+ values = Categorical(["a", "a", "a", "a", "b", "b", "c", "c", "c", "d"])
+ elif typestr in ("sparse", "sparse_na"):
+ if shape[-1] != 10:
+ # We also are implicitly assuming this in the category cases above
+ raise NotImplementedError
+
+ assert all(s == 1 for s in shape[:-1])
+ if typestr.endswith("_na"):
+ fill_value = np.nan
+ else:
+ fill_value = 0.0
+ values = SparseArray(
+ [fill_value, fill_value, 1, 2, 3, fill_value, 4, 5, fill_value, 6],
+ fill_value=fill_value,
+ )
+ arr = values.sp_values.view()
+ arr += num_offset - 1
+ else:
+ raise ValueError(f'Unsupported typestr: "{typestr}"')
+
+ values = maybe_coerce_values(values)
+ return maker(values, placement=placement, ndim=len(shape))
+
+
+def create_single_mgr(typestr, num_rows=None):
+ if num_rows is None:
+ num_rows = N
+
+ return SingleBlockManager(
+ create_block(typestr, placement=slice(0, num_rows), item_shape=()),
+ Index(np.arange(num_rows)),
+ )
+
+
+def create_mgr(descr, item_shape=None):
+ """
+ Construct BlockManager from string description.
+
+ String description syntax looks similar to np.matrix initializer. It looks
+ like this::
+
+ a,b,c: f8; d,e,f: i8
+
+ Rules are rather simple:
+
+ * see list of supported datatypes in `create_block` method
+ * components are semicolon-separated
+ * each component is `NAME,NAME,NAME: DTYPE_ID`
+ * whitespace around colons & semicolons are removed
+ * components with same DTYPE_ID are combined into single block
+ * to force multiple blocks with same dtype, use '-SUFFIX'::
+
+ "a:f8-1; b:f8-2; c:f8-foobar"
+
+ """
+ if item_shape is None:
+ item_shape = (N,)
+
+ offset = 0
+ mgr_items = []
+ block_placements = {}
+ for d in descr.split(";"):
+ d = d.strip()
+ if not len(d):
+ continue
+ names, blockstr = d.partition(":")[::2]
+ blockstr = blockstr.strip()
+ names = names.strip().split(",")
+
+ mgr_items.extend(names)
+ placement = list(np.arange(len(names)) + offset)
+ try:
+ block_placements[blockstr].extend(placement)
+ except KeyError:
+ block_placements[blockstr] = placement
+ offset += len(names)
+
+ mgr_items = Index(mgr_items)
+
+ blocks = []
+ num_offset = 0
+ for blockstr, placement in block_placements.items():
+ typestr = blockstr.split("-")[0]
+ blocks.append(
+ create_block(
+ typestr, placement, item_shape=item_shape, num_offset=num_offset
+ )
+ )
+ num_offset += len(placement)
+
+ sblocks = sorted(blocks, key=lambda b: b.mgr_locs[0])
+ return BlockManager(
+ tuple(sblocks),
+ [mgr_items] + [Index(np.arange(n)) for n in item_shape],
+ )
+
+
+@pytest.fixture
+def fblock():
+ return create_block("float", [0, 2, 4])
+
+
+class TestBlock:
+ def test_constructor(self):
+ int32block = create_block("i4", [0])
+ assert int32block.dtype == np.int32
+
+ @pytest.mark.parametrize(
+ "typ, data",
+ [
+ ["float", [0, 2, 4]],
+ ["complex", [7]],
+ ["object", [1, 3]],
+ ["bool", [5]],
+ ],
+ )
+ def test_pickle(self, typ, data, temp_file):
+ blk = create_block(typ, data)
+ assert_block_equal(tm.round_trip_pickle(blk, temp_file), blk)
+
+ def test_mgr_locs(self, fblock):
+ assert isinstance(fblock.mgr_locs, BlockPlacement)
+ tm.assert_numpy_array_equal(
+ fblock.mgr_locs.as_array, np.array([0, 2, 4], dtype=np.intp)
+ )
+
+ def test_attrs(self, fblock):
+ assert fblock.shape == fblock.values.shape
+ assert fblock.dtype == fblock.values.dtype
+ assert len(fblock) == len(fblock.values)
+
+ def test_copy(self, fblock):
+ cop = fblock.copy(deep=True)
+ assert cop is not fblock
+ assert_block_equal(fblock, cop)
+
+ def test_delete(self, fblock):
+ newb = fblock.copy(deep=True)
+ locs = newb.mgr_locs
+ nb = newb.delete(0)[0]
+ assert newb.mgr_locs is locs
+
+ assert nb is not newb
+
+ tm.assert_numpy_array_equal(
+ nb.mgr_locs.as_array, np.array([2, 4], dtype=np.intp)
+ )
+ assert not (newb.values[0] == 1).all()
+ assert (nb.values[0] == 1).all()
+
+ newb = fblock.copy(deep=True)
+ locs = newb.mgr_locs
+ nb = newb.delete(1)
+ assert len(nb) == 2
+ assert newb.mgr_locs is locs
+
+ tm.assert_numpy_array_equal(
+ nb[0].mgr_locs.as_array, np.array([0], dtype=np.intp)
+ )
+ tm.assert_numpy_array_equal(
+ nb[1].mgr_locs.as_array, np.array([4], dtype=np.intp)
+ )
+ assert not (newb.values[1] == 2).all()
+ assert (nb[1].values[0] == 2).all()
+
+ newb = fblock.copy(deep=True)
+ nb = newb.delete(2)
+ assert len(nb) == 1
+ tm.assert_numpy_array_equal(
+ nb[0].mgr_locs.as_array, np.array([0, 2], dtype=np.intp)
+ )
+ assert (nb[0].values[1] == 1).all()
+
+ newb = fblock.copy(deep=True)
+
+ with pytest.raises(IndexError, match=None):
+ newb.delete(3)
+
+ def test_delete_datetimelike(self):
+ # dont use np.delete on values, as that will coerce from DTA/TDA to ndarray
+ arr = np.arange(20, dtype="i8").reshape(5, 4).view("m8[ns]")
+ df = DataFrame(arr)
+ blk = df._mgr.blocks[0]
+ assert isinstance(blk.values, TimedeltaArray)
+
+ nb = blk.delete(1)
+ assert len(nb) == 2
+ assert isinstance(nb[0].values, TimedeltaArray)
+ assert isinstance(nb[1].values, TimedeltaArray)
+
+ df = DataFrame(arr.view("M8[ns]"))
+ blk = df._mgr.blocks[0]
+ assert isinstance(blk.values, DatetimeArray)
+
+ nb = blk.delete([1, 3])
+ assert len(nb) == 2
+ assert isinstance(nb[0].values, DatetimeArray)
+ assert isinstance(nb[1].values, DatetimeArray)
+
+ def test_split(self):
+ # GH#37799
+ values = np.random.default_rng(2).standard_normal((3, 4))
+ blk = new_block(values, placement=BlockPlacement([3, 1, 6]), ndim=2)
+ result = list(blk._split())
+
+ # check that we get views, not copies
+ values[:] = -9999
+ assert (blk.values == -9999).all()
+
+ assert len(result) == 3
+ expected = [
+ new_block(values[[0]], placement=BlockPlacement([3]), ndim=2),
+ new_block(values[[1]], placement=BlockPlacement([1]), ndim=2),
+ new_block(values[[2]], placement=BlockPlacement([6]), ndim=2),
+ ]
+ for res, exp in zip(result, expected):
+ assert_block_equal(res, exp)
+
+
+class TestBlockManager:
+ def test_attrs(self):
+ mgr = create_mgr("a,b,c: f8-1; d,e,f: f8-2")
+ assert mgr.nblocks == 2
+ assert len(mgr) == 6
+
+ def test_duplicate_ref_loc_failure(self):
+ tmp_mgr = create_mgr("a:bool; a: f8")
+
+ axes, blocks = tmp_mgr.axes, tmp_mgr.blocks
+
+ blocks[0].mgr_locs = BlockPlacement(np.array([0]))
+ blocks[1].mgr_locs = BlockPlacement(np.array([0]))
+
+ # test trying to create block manager with overlapping ref locs
+
+ msg = "Gaps in blk ref_locs"
+
+ mgr = BlockManager(blocks, axes)
+ with pytest.raises(AssertionError, match=msg):
+ mgr._rebuild_blknos_and_blklocs()
+
+ blocks[0].mgr_locs = BlockPlacement(np.array([0]))
+ blocks[1].mgr_locs = BlockPlacement(np.array([1]))
+ mgr = BlockManager(blocks, axes)
+ mgr.iget(1)
+
+ def test_pickle(self, mgr, temp_file):
+ mgr2 = tm.round_trip_pickle(mgr, temp_file)
+ tm.assert_frame_equal(
+ DataFrame._from_mgr(mgr, axes=mgr.axes),
+ DataFrame._from_mgr(mgr2, axes=mgr2.axes),
+ )
+
+ # GH2431
+ assert hasattr(mgr2, "_is_consolidated")
+ assert hasattr(mgr2, "_known_consolidated")
+
+ # reset to False on load
+ assert not mgr2._is_consolidated
+ assert not mgr2._known_consolidated
+
+ @pytest.mark.parametrize("mgr_string", ["a,a,a:f8", "a: f8; a: i8"])
+ def test_non_unique_pickle(self, mgr_string, temp_file):
+ mgr = create_mgr(mgr_string)
+ mgr2 = tm.round_trip_pickle(mgr, temp_file)
+ tm.assert_frame_equal(
+ DataFrame._from_mgr(mgr, axes=mgr.axes),
+ DataFrame._from_mgr(mgr2, axes=mgr2.axes),
+ )
+
+ def test_categorical_block_pickle(self, temp_file):
+ mgr = create_mgr("a: category")
+ mgr2 = tm.round_trip_pickle(mgr, temp_file)
+ tm.assert_frame_equal(
+ DataFrame._from_mgr(mgr, axes=mgr.axes),
+ DataFrame._from_mgr(mgr2, axes=mgr2.axes),
+ )
+
+ smgr = create_single_mgr("category")
+ smgr2 = tm.round_trip_pickle(smgr, temp_file)
+ tm.assert_series_equal(
+ Series()._constructor_from_mgr(smgr, axes=smgr.axes),
+ Series()._constructor_from_mgr(smgr2, axes=smgr2.axes),
+ )
+
+ def test_iget(self):
+ cols = Index(list("abc"))
+ values = np.random.default_rng(2).random((3, 3))
+ block = new_block(
+ values=values.copy(),
+ placement=BlockPlacement(np.arange(3, dtype=np.intp)),
+ ndim=values.ndim,
+ )
+ mgr = BlockManager(blocks=(block,), axes=[cols, Index(np.arange(3))])
+
+ tm.assert_almost_equal(mgr.iget(0).internal_values(), values[0])
+ tm.assert_almost_equal(mgr.iget(1).internal_values(), values[1])
+ tm.assert_almost_equal(mgr.iget(2).internal_values(), values[2])
+
+ def test_set(self):
+ mgr = create_mgr("a,b,c: int", item_shape=(3,))
+
+ mgr.insert(len(mgr.items), "d", np.array(["foo"] * 3))
+ mgr.iset(1, np.array(["bar"] * 3))
+ tm.assert_numpy_array_equal(mgr.iget(0).internal_values(), np.array([0] * 3))
+ tm.assert_numpy_array_equal(
+ mgr.iget(1).internal_values(), np.array(["bar"] * 3, dtype=np.object_)
+ )
+ tm.assert_numpy_array_equal(mgr.iget(2).internal_values(), np.array([2] * 3))
+ tm.assert_numpy_array_equal(
+ mgr.iget(3).internal_values(), np.array(["foo"] * 3, dtype=np.object_)
+ )
+
+ def test_set_change_dtype(self, mgr):
+ mgr.insert(len(mgr.items), "baz", np.zeros(N, dtype=bool))
+
+ mgr.iset(mgr.items.get_loc("baz"), np.repeat("foo", N))
+ idx = mgr.items.get_loc("baz")
+ assert mgr.iget(idx).dtype == np.object_
+
+ mgr2 = mgr.consolidate()
+ mgr2.iset(mgr2.items.get_loc("baz"), np.repeat("foo", N))
+ idx = mgr2.items.get_loc("baz")
+ assert mgr2.iget(idx).dtype == np.object_
+
+ mgr2.insert(
+ len(mgr2.items),
+ "quux",
+ np.random.default_rng(2).standard_normal(N).astype(int),
+ )
+ idx = mgr2.items.get_loc("quux")
+ assert mgr2.iget(idx).dtype == np.dtype(int)
+
+ mgr2.iset(
+ mgr2.items.get_loc("quux"), np.random.default_rng(2).standard_normal(N)
+ )
+ assert mgr2.iget(idx).dtype == np.float64
+
+ def test_copy(self, mgr):
+ cp = mgr.copy(deep=False)
+ for blk, cp_blk in zip(mgr.blocks, cp.blocks):
+ # view assertion
+ tm.assert_equal(cp_blk.values, blk.values)
+ if isinstance(blk.values, np.ndarray):
+ assert cp_blk.values.base.base is blk.values.base
+ else:
+ # DatetimeTZBlock has DatetimeIndex values
+ assert cp_blk.values._ndarray.base is blk.values._ndarray.base
+
+ # copy(deep=True) consolidates, so the block-wise assertions will
+ # fail is mgr is not consolidated
+ mgr._consolidate_inplace()
+ cp = mgr.copy(deep=True)
+ for blk, cp_blk in zip(mgr.blocks, cp.blocks):
+ bvals = blk.values
+ cpvals = cp_blk.values
+
+ tm.assert_equal(cpvals, bvals)
+
+ if isinstance(cpvals, np.ndarray):
+ lbase = cpvals.base
+ rbase = bvals.base
+ else:
+ lbase = cpvals._ndarray.base
+ rbase = bvals._ndarray.base
+
+ # copy assertion we either have a None for a base or in case of
+ # some blocks it is an array (e.g. datetimetz), but was copied
+ if isinstance(cpvals, DatetimeArray):
+ assert (lbase is None and rbase is None) or (lbase is not rbase)
+ elif not isinstance(cpvals, np.ndarray):
+ assert lbase is not rbase
+ else:
+ assert lbase is None and rbase is None
+
+ def test_sparse(self):
+ mgr = create_mgr("a: sparse-1; b: sparse-2")
+ assert mgr.as_array().dtype == np.float64
+
+ def test_sparse_mixed(self):
+ mgr = create_mgr("a: sparse-1; b: sparse-2; c: f8")
+ assert len(mgr.blocks) == 3
+ assert isinstance(mgr, BlockManager)
+
+ @pytest.mark.parametrize(
+ "mgr_string, dtype",
+ [("c: f4; d: f2", np.float32), ("c: f4; d: f2; e: f8", np.float64)],
+ )
+ def test_as_array_float(self, mgr_string, dtype):
+ mgr = create_mgr(mgr_string)
+ assert mgr.as_array().dtype == dtype
+
+ @pytest.mark.parametrize(
+ "mgr_string, dtype",
+ [
+ ("a: bool-1; b: bool-2", np.bool_),
+ ("a: i8-1; b: i8-2; c: i4; d: i2; e: u1", np.int64),
+ ("c: i4; d: i2; e: u1", np.int32),
+ ],
+ )
+ def test_as_array_int_bool(self, mgr_string, dtype):
+ mgr = create_mgr(mgr_string)
+ assert mgr.as_array().dtype == dtype
+
+ def test_as_array_datetime(self):
+ mgr = create_mgr("h: datetime-1; g: datetime-2")
+ assert mgr.as_array().dtype == "M8[ns]"
+
+ def test_as_array_datetime_tz(self):
+ mgr = create_mgr("h: M8[ns, US/Eastern]; g: M8[ns, CET]")
+ assert mgr.iget(0).dtype == "datetime64[ns, US/Eastern]"
+ assert mgr.iget(1).dtype == "datetime64[ns, CET]"
+ assert mgr.as_array().dtype == "object"
+
+ @pytest.mark.parametrize("t", ["float16", "float32", "float64", "int32", "int64"])
+ def test_astype(self, t):
+ # coerce all
+ mgr = create_mgr("c: f4; d: f2; e: f8")
+
+ t = np.dtype(t)
+ tmgr = mgr.astype(t)
+ assert tmgr.iget(0).dtype.type == t
+ assert tmgr.iget(1).dtype.type == t
+ assert tmgr.iget(2).dtype.type == t
+
+ # mixed
+ mgr = create_mgr("a,b: object; c: bool; d: datetime; e: f4; f: f2; g: f8")
+
+ t = np.dtype(t)
+ tmgr = mgr.astype(t, errors="ignore")
+ assert tmgr.iget(2).dtype.type == t
+ assert tmgr.iget(4).dtype.type == t
+ assert tmgr.iget(5).dtype.type == t
+ assert tmgr.iget(6).dtype.type == t
+
+ assert tmgr.iget(0).dtype.type == np.object_
+ assert tmgr.iget(1).dtype.type == np.object_
+ if t != np.int64:
+ assert tmgr.iget(3).dtype.type == np.datetime64
+ else:
+ assert tmgr.iget(3).dtype.type == t
+
+ def test_convert(self, using_infer_string):
+ def _compare(old_mgr, new_mgr):
+ """compare the blocks, numeric compare ==, object don't"""
+ old_blocks = set(old_mgr.blocks)
+ new_blocks = set(new_mgr.blocks)
+ assert len(old_blocks) == len(new_blocks)
+
+ # compare non-numeric
+ for b in old_blocks:
+ found = False
+ for nb in new_blocks:
+ if (b.values == nb.values).all():
+ found = True
+ break
+ assert found
+
+ for b in new_blocks:
+ found = False
+ for ob in old_blocks:
+ if (b.values == ob.values).all():
+ found = True
+ break
+ assert found
+
+ # noops
+ mgr = create_mgr("f: i8; g: f8")
+ new_mgr = mgr.convert()
+ _compare(mgr, new_mgr)
+
+ # convert
+ mgr = create_mgr("a,b,foo: object; f: i8; g: f8")
+ mgr.iset(0, np.array(["1"] * N, dtype=np.object_))
+ mgr.iset(1, np.array(["2."] * N, dtype=np.object_))
+ mgr.iset(2, np.array(["foo."] * N, dtype=np.object_))
+ new_mgr = mgr.convert()
+ dtype = "str" if using_infer_string else np.object_
+ assert new_mgr.iget(0).dtype == dtype
+ assert new_mgr.iget(1).dtype == dtype
+ assert new_mgr.iget(2).dtype == dtype
+ assert new_mgr.iget(3).dtype == np.int64
+ assert new_mgr.iget(4).dtype == np.float64
+
+ mgr = create_mgr(
+ "a,b,foo: object; f: i4; bool: bool; dt: datetime; i: i8; g: f8; h: f2"
+ )
+ mgr.iset(0, np.array(["1"] * N, dtype=np.object_))
+ mgr.iset(1, np.array(["2."] * N, dtype=np.object_))
+ mgr.iset(2, np.array(["foo."] * N, dtype=np.object_))
+ new_mgr = mgr.convert()
+ assert new_mgr.iget(0).dtype == dtype
+ assert new_mgr.iget(1).dtype == dtype
+ assert new_mgr.iget(2).dtype == dtype
+ assert new_mgr.iget(3).dtype == np.int32
+ assert new_mgr.iget(4).dtype == np.bool_
+ assert new_mgr.iget(5).dtype.type, np.datetime64
+ assert new_mgr.iget(6).dtype == np.int64
+ assert new_mgr.iget(7).dtype == np.float64
+ assert new_mgr.iget(8).dtype == np.float16
+
+ def test_interleave(self):
+ # self
+ for dtype in ["f8", "i8", "object", "bool", "complex", "M8[ns]", "m8[ns]"]:
+ mgr = create_mgr(f"a: {dtype}")
+ assert mgr.as_array().dtype == dtype
+ mgr = create_mgr(f"a: {dtype}; b: {dtype}")
+ assert mgr.as_array().dtype == dtype
+
+ @pytest.mark.parametrize(
+ "mgr_string, dtype",
+ [
+ ("a: category", "i8"),
+ ("a: category; b: category", "i8"),
+ ("a: category; b: category2", "object"),
+ ("a: category2", "object"),
+ ("a: category2; b: category2", "object"),
+ ("a: f8", "f8"),
+ ("a: f8; b: i8", "f8"),
+ ("a: f4; b: i8", "f8"),
+ ("a: f4; b: i8; d: object", "object"),
+ ("a: bool; b: i8", "object"),
+ ("a: complex", "complex"),
+ ("a: f8; b: category", "object"),
+ ("a: M8[ns]; b: category", "object"),
+ ("a: M8[ns]; b: bool", "object"),
+ ("a: M8[ns]; b: i8", "object"),
+ ("a: m8[ns]; b: bool", "object"),
+ ("a: m8[ns]; b: i8", "object"),
+ ("a: M8[ns]; b: m8[ns]", "object"),
+ ],
+ )
+ def test_interleave_dtype(self, mgr_string, dtype):
+ # will be converted according the actual dtype of the underlying
+ mgr = create_mgr("a: category")
+ assert mgr.as_array().dtype == "i8"
+ mgr = create_mgr("a: category; b: category2")
+ assert mgr.as_array().dtype == "object"
+ mgr = create_mgr("a: category2")
+ assert mgr.as_array().dtype == "object"
+
+ # combinations
+ mgr = create_mgr("a: f8")
+ assert mgr.as_array().dtype == "f8"
+ mgr = create_mgr("a: f8; b: i8")
+ assert mgr.as_array().dtype == "f8"
+ mgr = create_mgr("a: f4; b: i8")
+ assert mgr.as_array().dtype == "f8"
+ mgr = create_mgr("a: f4; b: i8; d: object")
+ assert mgr.as_array().dtype == "object"
+ mgr = create_mgr("a: bool; b: i8")
+ assert mgr.as_array().dtype == "object"
+ mgr = create_mgr("a: complex")
+ assert mgr.as_array().dtype == "complex"
+ mgr = create_mgr("a: f8; b: category")
+ assert mgr.as_array().dtype == "f8"
+ mgr = create_mgr("a: M8[ns]; b: category")
+ assert mgr.as_array().dtype == "object"
+ mgr = create_mgr("a: M8[ns]; b: bool")
+ assert mgr.as_array().dtype == "object"
+ mgr = create_mgr("a: M8[ns]; b: i8")
+ assert mgr.as_array().dtype == "object"
+ mgr = create_mgr("a: m8[ns]; b: bool")
+ assert mgr.as_array().dtype == "object"
+ mgr = create_mgr("a: m8[ns]; b: i8")
+ assert mgr.as_array().dtype == "object"
+ mgr = create_mgr("a: M8[ns]; b: m8[ns]")
+ assert mgr.as_array().dtype == "object"
+
+ def test_consolidate_ordering_issues(self, mgr):
+ mgr.iset(mgr.items.get_loc("f"), np.random.default_rng(2).standard_normal(N))
+ mgr.iset(mgr.items.get_loc("d"), np.random.default_rng(2).standard_normal(N))
+ mgr.iset(mgr.items.get_loc("b"), np.random.default_rng(2).standard_normal(N))
+ mgr.iset(mgr.items.get_loc("g"), np.random.default_rng(2).standard_normal(N))
+ mgr.iset(mgr.items.get_loc("h"), np.random.default_rng(2).standard_normal(N))
+
+ # we have datetime/tz blocks in mgr
+ cons = mgr.consolidate()
+ assert cons.nblocks == 4
+ cons = mgr.consolidate().get_numeric_data()
+ assert cons.nblocks == 1
+ assert isinstance(cons.blocks[0].mgr_locs, BlockPlacement)
+ tm.assert_numpy_array_equal(
+ cons.blocks[0].mgr_locs.as_array, np.arange(len(cons.items), dtype=np.intp)
+ )
+
+ def test_reindex_items(self):
+ # mgr is not consolidated, f8 & f8-2 blocks
+ mgr = create_mgr("a: f8; b: i8; c: f8; d: i8; e: f8; f: bool; g: f8-2")
+
+ reindexed = mgr.reindex_axis(["g", "c", "a", "d"], axis=0)
+ assert not reindexed.is_consolidated()
+
+ tm.assert_index_equal(reindexed.items, Index(["g", "c", "a", "d"]))
+ tm.assert_almost_equal(
+ mgr.iget(6).internal_values(), reindexed.iget(0).internal_values()
+ )
+ tm.assert_almost_equal(
+ mgr.iget(2).internal_values(), reindexed.iget(1).internal_values()
+ )
+ tm.assert_almost_equal(
+ mgr.iget(0).internal_values(), reindexed.iget(2).internal_values()
+ )
+ tm.assert_almost_equal(
+ mgr.iget(3).internal_values(), reindexed.iget(3).internal_values()
+ )
+
+ def test_get_numeric_data(self):
+ mgr = create_mgr(
+ "int: int; float: float; complex: complex;"
+ "str: object; bool: bool; obj: object; dt: datetime",
+ item_shape=(3,),
+ )
+ mgr.iset(5, np.array([1, 2, 3], dtype=np.object_))
+
+ numeric = mgr.get_numeric_data()
+ tm.assert_index_equal(numeric.items, Index(["int", "float", "complex", "bool"]))
+ tm.assert_almost_equal(
+ mgr.iget(mgr.items.get_loc("float")).internal_values(),
+ numeric.iget(numeric.items.get_loc("float")).internal_values(),
+ )
+
+ # Check sharing
+ numeric.iset(
+ numeric.items.get_loc("float"),
+ np.array([100.0, 200.0, 300.0]),
+ inplace=True,
+ )
+ tm.assert_almost_equal(
+ mgr.iget(mgr.items.get_loc("float")).internal_values(),
+ np.array([1.0, 1.0, 1.0]),
+ )
+
+ def test_get_bool_data(self):
+ mgr = create_mgr(
+ "int: int; float: float; complex: complex;"
+ "str: object; bool: bool; obj: object; dt: datetime",
+ item_shape=(3,),
+ )
+ mgr.iset(6, np.array([True, False, True], dtype=np.object_))
+
+ bools = mgr.get_bool_data()
+ tm.assert_index_equal(bools.items, Index(["bool"]))
+ tm.assert_almost_equal(
+ mgr.iget(mgr.items.get_loc("bool")).internal_values(),
+ bools.iget(bools.items.get_loc("bool")).internal_values(),
+ )
+
+ bools.iset(0, np.array([True, False, True]), inplace=True)
+ tm.assert_numpy_array_equal(
+ mgr.iget(mgr.items.get_loc("bool")).internal_values(),
+ np.array([True, True, True]),
+ )
+
+ def test_unicode_repr_doesnt_raise(self):
+ repr(create_mgr("b,\u05d0: object"))
+
+ @pytest.mark.parametrize(
+ "mgr_string", ["a,b,c: i8-1; d,e,f: i8-2", "a,a,a: i8-1; b,b,b: i8-2"]
+ )
+ def test_equals(self, mgr_string):
+ # unique items
+ bm1 = create_mgr(mgr_string)
+ bm2 = BlockManager(bm1.blocks[::-1], bm1.axes)
+ assert bm1.equals(bm2)
+
+ @pytest.mark.parametrize(
+ "mgr_string",
+ [
+ "a:i8;b:f8", # basic case
+ "a:i8;b:f8;c:c8;d:b", # many types
+ "a:i8;e:dt;f:td;g:string", # more types
+ "a:i8;b:category;c:category2", # categories
+ "c:sparse;d:sparse_na;b:f8", # sparse
+ ],
+ )
+ def test_equals_block_order_different_dtypes(self, mgr_string):
+ # GH 9330
+ bm = create_mgr(mgr_string)
+ block_perms = itertools.permutations(bm.blocks)
+ for bm_perm in block_perms:
+ bm_this = BlockManager(tuple(bm_perm), bm.axes)
+ assert bm.equals(bm_this)
+ assert bm_this.equals(bm)
+
+ def test_single_mgr_ctor(self):
+ mgr = create_single_mgr("f8", num_rows=5)
+ assert mgr.external_values().tolist() == [0.0, 1.0, 2.0, 3.0, 4.0]
+
+ @pytest.mark.parametrize("value", [1, "True", [1, 2, 3], 5.0])
+ def test_validate_bool_args(self, value):
+ bm1 = create_mgr("a,b,c: i8-1; d,e,f: i8-2")
+
+ msg = (
+ 'For argument "inplace" expected type bool, '
+ f"received type {type(value).__name__}."
+ )
+ with pytest.raises(ValueError, match=msg):
+ bm1.replace_list([1], [2], inplace=value)
+
+ def test_iset_split_block(self):
+ bm = create_mgr("a,b,c: i8; d: f8")
+ bm._iset_split_block(0, np.array([0]))
+ tm.assert_numpy_array_equal(
+ bm.blklocs, np.array([0, 0, 1, 0], dtype="int64" if IS64 else "int32")
+ )
+ # First indexer currently does not have a block associated with it in case
+ tm.assert_numpy_array_equal(
+ bm.blknos, np.array([0, 0, 0, 1], dtype="int64" if IS64 else "int32")
+ )
+ assert len(bm.blocks) == 2
+
+ def test_iset_split_block_values(self):
+ bm = create_mgr("a,b,c: i8; d: f8")
+ bm._iset_split_block(0, np.array([0]), np.array([list(range(10))]))
+ tm.assert_numpy_array_equal(
+ bm.blklocs, np.array([0, 0, 1, 0], dtype="int64" if IS64 else "int32")
+ )
+ # First indexer currently does not have a block associated with it in case
+ tm.assert_numpy_array_equal(
+ bm.blknos, np.array([0, 2, 2, 1], dtype="int64" if IS64 else "int32")
+ )
+ assert len(bm.blocks) == 3
+
+
+def _as_array(mgr):
+ if mgr.ndim == 1:
+ return mgr.external_values()
+ return mgr.as_array().T
+
+
+class TestIndexing:
+ # Nosetests-style data-driven tests.
+ #
+ # This test applies different indexing routines to block managers and
+ # compares the outcome to the result of same operations on np.ndarray.
+ #
+ # NOTE: sparse (SparseBlock with fill_value != np.nan) fail a lot of tests
+ # and are disabled.
+
+ MANAGERS = [
+ create_single_mgr("f8", N),
+ create_single_mgr("i8", N),
+ # 2-dim
+ create_mgr("a,b,c,d,e,f: f8", item_shape=(N,)),
+ create_mgr("a,b,c,d,e,f: i8", item_shape=(N,)),
+ create_mgr("a,b: f8; c,d: i8; e,f: string", item_shape=(N,)),
+ create_mgr("a,b: f8; c,d: i8; e,f: f8", item_shape=(N,)),
+ ]
+
+ @pytest.mark.parametrize("mgr", MANAGERS)
+ def test_get_slice(self, mgr):
+ def assert_slice_ok(mgr, axis, slobj):
+ mat = _as_array(mgr)
+
+ # we maybe using an ndarray to test slicing and
+ # might not be the full length of the axis
+ if isinstance(slobj, np.ndarray):
+ ax = mgr.axes[axis]
+ if len(ax) and len(slobj) and len(slobj) != len(ax):
+ slobj = np.concatenate(
+ [slobj, np.zeros(len(ax) - len(slobj), dtype=bool)]
+ )
+
+ if isinstance(slobj, slice):
+ sliced = mgr.get_slice(slobj, axis=axis)
+ elif (
+ mgr.ndim == 1
+ and axis == 0
+ and isinstance(slobj, np.ndarray)
+ and slobj.dtype == bool
+ ):
+ sliced = mgr.get_rows_with_mask(slobj)
+ else:
+ # BlockManager doesn't support non-slice, SingleBlockManager
+ # doesn't support axis > 0
+ raise TypeError(slobj)
+
+ mat_slobj = (slice(None),) * axis + (slobj,)
+ tm.assert_numpy_array_equal(
+ mat[mat_slobj], _as_array(sliced), check_dtype=False
+ )
+ tm.assert_index_equal(mgr.axes[axis][slobj], sliced.axes[axis])
+
+ assert mgr.ndim <= 2, mgr.ndim
+ for ax in range(mgr.ndim):
+ # slice
+ assert_slice_ok(mgr, ax, slice(None))
+ assert_slice_ok(mgr, ax, slice(3))
+ assert_slice_ok(mgr, ax, slice(100))
+ assert_slice_ok(mgr, ax, slice(1, 4))
+ assert_slice_ok(mgr, ax, slice(3, 0, -2))
+
+ if mgr.ndim < 2:
+ # 2D only support slice objects
+
+ # boolean mask
+ assert_slice_ok(mgr, ax, np.ones(mgr.shape[ax], dtype=np.bool_))
+ assert_slice_ok(mgr, ax, np.zeros(mgr.shape[ax], dtype=np.bool_))
+
+ if mgr.shape[ax] >= 3:
+ assert_slice_ok(mgr, ax, np.arange(mgr.shape[ax]) % 3 == 0)
+ assert_slice_ok(
+ mgr, ax, np.array([True, True, False], dtype=np.bool_)
+ )
+
+ @pytest.mark.parametrize("mgr", MANAGERS)
+ def test_take(self, mgr):
+ def assert_take_ok(mgr, axis, indexer):
+ mat = _as_array(mgr)
+ taken = mgr.take(indexer, axis)
+ tm.assert_numpy_array_equal(
+ np.take(mat, indexer, axis), _as_array(taken), check_dtype=False
+ )
+ tm.assert_index_equal(mgr.axes[axis].take(indexer), taken.axes[axis])
+
+ for ax in range(mgr.ndim):
+ # take/fancy indexer
+ assert_take_ok(mgr, ax, indexer=np.array([], dtype=np.intp))
+ assert_take_ok(mgr, ax, indexer=np.array([0, 0, 0], dtype=np.intp))
+ assert_take_ok(
+ mgr, ax, indexer=np.array(list(range(mgr.shape[ax])), dtype=np.intp)
+ )
+
+ if mgr.shape[ax] >= 3:
+ assert_take_ok(mgr, ax, indexer=np.array([0, 1, 2], dtype=np.intp))
+ assert_take_ok(mgr, ax, indexer=np.array([-1, -2, -3], dtype=np.intp))
+
+ @pytest.mark.parametrize("mgr", MANAGERS)
+ @pytest.mark.parametrize("fill_value", [None, np.nan, 100.0])
+ def test_reindex_axis(self, fill_value, mgr):
+ def assert_reindex_axis_is_ok(mgr, axis, new_labels, fill_value):
+ mat = _as_array(mgr)
+ indexer = mgr.axes[axis].get_indexer_for(new_labels)
+
+ reindexed = mgr.reindex_axis(new_labels, axis, fill_value=fill_value)
+ tm.assert_numpy_array_equal(
+ algos.take_nd(mat, indexer, axis, fill_value=fill_value),
+ _as_array(reindexed),
+ check_dtype=False,
+ )
+ tm.assert_index_equal(reindexed.axes[axis], new_labels)
+
+ for ax in range(mgr.ndim):
+ assert_reindex_axis_is_ok(mgr, ax, Index([]), fill_value)
+ assert_reindex_axis_is_ok(mgr, ax, mgr.axes[ax], fill_value)
+ assert_reindex_axis_is_ok(mgr, ax, mgr.axes[ax][[0, 0, 0]], fill_value)
+ assert_reindex_axis_is_ok(mgr, ax, Index(["foo", "bar", "baz"]), fill_value)
+ assert_reindex_axis_is_ok(
+ mgr, ax, Index(["foo", mgr.axes[ax][0], "baz"]), fill_value
+ )
+
+ if mgr.shape[ax] >= 3:
+ assert_reindex_axis_is_ok(mgr, ax, mgr.axes[ax][:-3], fill_value)
+ assert_reindex_axis_is_ok(mgr, ax, mgr.axes[ax][-3::-1], fill_value)
+ assert_reindex_axis_is_ok(
+ mgr, ax, mgr.axes[ax][[0, 1, 2, 0, 1, 2]], fill_value
+ )
+
+ @pytest.mark.parametrize("mgr", MANAGERS)
+ @pytest.mark.parametrize("fill_value", [None, np.nan, 100.0])
+ def test_reindex_indexer(self, fill_value, mgr):
+ def assert_reindex_indexer_is_ok(mgr, axis, new_labels, indexer, fill_value):
+ mat = _as_array(mgr)
+ reindexed_mat = algos.take_nd(mat, indexer, axis, fill_value=fill_value)
+ reindexed = mgr.reindex_indexer(
+ new_labels, indexer, axis, fill_value=fill_value
+ )
+ tm.assert_numpy_array_equal(
+ reindexed_mat, _as_array(reindexed), check_dtype=False
+ )
+ tm.assert_index_equal(reindexed.axes[axis], new_labels)
+
+ for ax in range(mgr.ndim):
+ assert_reindex_indexer_is_ok(
+ mgr, ax, Index([]), np.array([], dtype=np.intp), fill_value
+ )
+ assert_reindex_indexer_is_ok(
+ mgr, ax, mgr.axes[ax], np.arange(mgr.shape[ax]), fill_value
+ )
+ assert_reindex_indexer_is_ok(
+ mgr,
+ ax,
+ Index(["foo"] * mgr.shape[ax]),
+ np.arange(mgr.shape[ax]),
+ fill_value,
+ )
+ assert_reindex_indexer_is_ok(
+ mgr, ax, mgr.axes[ax][::-1], np.arange(mgr.shape[ax]), fill_value
+ )
+ assert_reindex_indexer_is_ok(
+ mgr, ax, mgr.axes[ax], np.arange(mgr.shape[ax])[::-1], fill_value
+ )
+ assert_reindex_indexer_is_ok(
+ mgr, ax, Index(["foo", "bar", "baz"]), np.array([0, 0, 0]), fill_value
+ )
+ assert_reindex_indexer_is_ok(
+ mgr, ax, Index(["foo", "bar", "baz"]), np.array([-1, 0, -1]), fill_value
+ )
+ assert_reindex_indexer_is_ok(
+ mgr,
+ ax,
+ Index(["foo", mgr.axes[ax][0], "baz"]),
+ np.array([-1, -1, -1]),
+ fill_value,
+ )
+
+ if mgr.shape[ax] >= 3:
+ assert_reindex_indexer_is_ok(
+ mgr,
+ ax,
+ Index(["foo", "bar", "baz"]),
+ np.array([0, 1, 2]),
+ fill_value,
+ )
+
+
+class TestBlockPlacement:
+ @pytest.mark.parametrize(
+ "slc, expected",
+ [
+ (slice(0, 4), 4),
+ (slice(0, 4, 2), 2),
+ (slice(0, 3, 2), 2),
+ (slice(0, 1, 2), 1),
+ (slice(1, 0, -1), 1),
+ ],
+ )
+ def test_slice_len(self, slc, expected):
+ assert len(BlockPlacement(slc)) == expected
+
+ @pytest.mark.parametrize("slc", [slice(1, 1, 0), slice(1, 2, 0)])
+ def test_zero_step_raises(self, slc):
+ msg = "slice step cannot be zero"
+ with pytest.raises(ValueError, match=msg):
+ BlockPlacement(slc)
+
+ def test_slice_canonize_negative_stop(self):
+ # GH#37524 negative stop is OK with negative step and positive start
+ slc = slice(3, -1, -2)
+
+ bp = BlockPlacement(slc)
+ assert bp.indexer == slice(3, None, -2)
+
+ @pytest.mark.parametrize(
+ "slc",
+ [
+ slice(None, None),
+ slice(10, None),
+ slice(None, None, -1),
+ slice(None, 10, -1),
+ # These are "unbounded" because negative index will
+ # change depending on container shape.
+ slice(-1, None),
+ slice(None, -1),
+ slice(-1, -1),
+ slice(-1, None, -1),
+ slice(None, -1, -1),
+ slice(-1, -1, -1),
+ ],
+ )
+ def test_unbounded_slice_raises(self, slc):
+ msg = "unbounded slice"
+ with pytest.raises(ValueError, match=msg):
+ BlockPlacement(slc)
+
+ @pytest.mark.parametrize(
+ "slc",
+ [
+ slice(0, 0),
+ slice(100, 0),
+ slice(100, 100),
+ slice(100, 100, -1),
+ slice(0, 100, -1),
+ ],
+ )
+ def test_not_slice_like_slices(self, slc):
+ assert not BlockPlacement(slc).is_slice_like
+
+ @pytest.mark.parametrize(
+ "arr, slc",
+ [
+ ([0], slice(0, 1, 1)),
+ ([100], slice(100, 101, 1)),
+ ([0, 1, 2], slice(0, 3, 1)),
+ ([0, 5, 10], slice(0, 15, 5)),
+ ([0, 100], slice(0, 200, 100)),
+ ([2, 1], slice(2, 0, -1)),
+ ],
+ )
+ def test_array_to_slice_conversion(self, arr, slc):
+ assert BlockPlacement(arr).as_slice == slc
+
+ @pytest.mark.parametrize(
+ "arr",
+ [
+ [],
+ [-1],
+ [-1, -2, -3],
+ [-10],
+ [-1, 0, 1, 2],
+ [-2, 0, 2, 4],
+ [1, 0, -1],
+ [1, 1, 1],
+ ],
+ )
+ def test_not_slice_like_arrays(self, arr):
+ assert not BlockPlacement(arr).is_slice_like
+
+ @pytest.mark.parametrize(
+ "slc, expected",
+ [(slice(0, 3), [0, 1, 2]), (slice(0, 0), []), (slice(3, 0), [])],
+ )
+ def test_slice_iter(self, slc, expected):
+ assert list(BlockPlacement(slc)) == expected
+
+ @pytest.mark.parametrize(
+ "slc, arr",
+ [
+ (slice(0, 3), [0, 1, 2]),
+ (slice(0, 0), []),
+ (slice(3, 0), []),
+ (slice(3, 0, -1), [3, 2, 1]),
+ ],
+ )
+ def test_slice_to_array_conversion(self, slc, arr):
+ tm.assert_numpy_array_equal(
+ BlockPlacement(slc).as_array, np.asarray(arr, dtype=np.intp)
+ )
+
+ def test_blockplacement_add(self):
+ bpl = BlockPlacement(slice(0, 5))
+ assert bpl.add(1).as_slice == slice(1, 6, 1)
+ assert bpl.add(np.arange(5)).as_slice == slice(0, 10, 2)
+ assert list(bpl.add(np.arange(5, 0, -1))) == [5, 5, 5, 5, 5]
+
+ @pytest.mark.parametrize(
+ "val, inc, expected",
+ [
+ (slice(0, 0), 0, []),
+ (slice(1, 4), 0, [1, 2, 3]),
+ (slice(3, 0, -1), 0, [3, 2, 1]),
+ ([1, 2, 4], 0, [1, 2, 4]),
+ (slice(0, 0), 10, []),
+ (slice(1, 4), 10, [11, 12, 13]),
+ (slice(3, 0, -1), 10, [13, 12, 11]),
+ ([1, 2, 4], 10, [11, 12, 14]),
+ (slice(0, 0), -1, []),
+ (slice(1, 4), -1, [0, 1, 2]),
+ ([1, 2, 4], -1, [0, 1, 3]),
+ ],
+ )
+ def test_blockplacement_add_int(self, val, inc, expected):
+ assert list(BlockPlacement(val).add(inc)) == expected
+
+ @pytest.mark.parametrize("val", [slice(1, 4), [1, 2, 4]])
+ def test_blockplacement_add_int_raises(self, val):
+ msg = "iadd causes length change"
+ with pytest.raises(ValueError, match=msg):
+ BlockPlacement(val).add(-10)
+
+
+class TestCanHoldElement:
+ @pytest.fixture(
+ params=[
+ lambda x: x,
+ lambda x: x.to_series(),
+ lambda x: x._data,
+ lambda x: list(x),
+ lambda x: x.astype(object),
+ lambda x: np.asarray(x),
+ lambda x: x[0],
+ lambda x: x[:0],
+ ]
+ )
+ def element(self, request):
+ """
+ Functions that take an Index and return an element that should have
+ blk._can_hold_element(element) for a Block with this index's dtype.
+ """
+ return request.param
+
+ def test_datetime_block_can_hold_element(self):
+ block = create_block("datetime", [0])
+
+ assert block._can_hold_element([])
+
+ # We will check that block._can_hold_element iff arr.__setitem__ works
+ arr = pd.array(block.values.ravel())
+
+ # coerce None
+ assert block._can_hold_element(None)
+ arr[0] = None
+ assert arr[0] is pd.NaT
+
+ # coerce different types of datetime objects
+ vals = [np.datetime64("2010-10-10"), datetime(2010, 10, 10)]
+ for val in vals:
+ assert block._can_hold_element(val)
+ arr[0] = val
+
+ val = date(2010, 10, 10)
+ assert not block._can_hold_element(val)
+
+ msg = (
+ "value should be a 'Timestamp', 'NaT', "
+ "or array of those. Got 'date' instead."
+ )
+ with pytest.raises(TypeError, match=msg):
+ arr[0] = val
+
+ @pytest.mark.parametrize("dtype", [np.int64, np.uint64, np.float64])
+ def test_interval_can_hold_element_emptylist(self, dtype, element):
+ arr = np.array([1, 3, 4], dtype=dtype)
+ ii = IntervalIndex.from_breaks(arr)
+ blk = new_block(ii._data, BlockPlacement([1]), ndim=2)
+
+ assert blk._can_hold_element([])
+ # TODO: check this holds for all blocks
+
+ @pytest.mark.parametrize("dtype", [np.int64, np.uint64, np.float64])
+ def test_interval_can_hold_element(self, dtype, element):
+ arr = np.array([1, 3, 4, 9], dtype=dtype)
+ ii = IntervalIndex.from_breaks(arr)
+ blk = new_block(ii._data, BlockPlacement([1]), ndim=2)
+
+ elem = element(ii)
+ self.check_series_setitem(elem, ii, True)
+ assert blk._can_hold_element(elem)
+
+ # Careful: to get the expected Series-inplace behavior we need
+ # `elem` to not have the same length as `arr`
+ ii2 = IntervalIndex.from_breaks(arr[:-1], closed="neither")
+ elem = element(ii2)
+ with pytest.raises(TypeError, match="Invalid value"):
+ self.check_series_setitem(elem, ii, False)
+ assert not blk._can_hold_element(elem)
+
+ ii3 = IntervalIndex.from_breaks([Timestamp(1), Timestamp(3), Timestamp(4)])
+ elem = element(ii3)
+ with pytest.raises(TypeError, match="Invalid value"):
+ self.check_series_setitem(elem, ii, False)
+ assert not blk._can_hold_element(elem)
+
+ ii4 = IntervalIndex.from_breaks([Timedelta(1), Timedelta(3), Timedelta(4)])
+ elem = element(ii4)
+ with pytest.raises(TypeError, match="Invalid value"):
+ self.check_series_setitem(elem, ii, False)
+ assert not blk._can_hold_element(elem)
+
+ def test_period_can_hold_element_emptylist(self):
+ pi = period_range("2016", periods=3, freq="Y")
+ blk = new_block(pi._data.reshape(1, 3), BlockPlacement([1]), ndim=2)
+
+ assert blk._can_hold_element([])
+
+ def test_period_can_hold_element(self, element):
+ pi = period_range("2016", periods=3, freq="Y")
+
+ elem = element(pi)
+ self.check_series_setitem(elem, pi, True)
+
+ # Careful: to get the expected Series-inplace behavior we need
+ # `elem` to not have the same length as `arr`
+ pi2 = pi.asfreq("D")[:-1]
+ elem = element(pi2)
+ with pytest.raises(TypeError, match="Invalid value"):
+ self.check_series_setitem(elem, pi, False)
+
+ dti = pi.to_timestamp("s")[:-1]
+ elem = element(dti)
+ with pytest.raises(TypeError, match="Invalid value"):
+ self.check_series_setitem(elem, pi, False)
+
+ def test_period_reindex_axis(self):
+ # GH#60273 Test reindexing of block with PeriodDtype
+ pi = period_range("2020", periods=5, freq="Y")
+ blk = new_block(pi._data.reshape(5, 1), BlockPlacement(slice(5)), ndim=2)
+ mgr = BlockManager(blocks=(blk,), axes=[Index(np.arange(5)), Index(["a"])])
+ reindexed = mgr.reindex_axis(Index([0, 2, 4]), axis=0)
+ result = DataFrame._from_mgr(reindexed, axes=reindexed.axes)
+ expected = DataFrame([[pi[0], pi[2], pi[4]]], columns=[0, 2, 4], index=["a"])
+ tm.assert_frame_equal(result, expected)
+
+ def check_can_hold_element(self, obj, elem, inplace: bool):
+ blk = obj._mgr.blocks[0]
+ if inplace:
+ assert blk._can_hold_element(elem)
+ else:
+ assert not blk._can_hold_element(elem)
+
+ def check_series_setitem(self, elem, index: Index, inplace: bool):
+ arr = index._data.copy()
+ ser = Series(arr, copy=False)
+
+ self.check_can_hold_element(ser, elem, inplace)
+
+ if is_scalar(elem):
+ ser[0] = elem
+ else:
+ ser[: len(elem)] = elem
+
+ if inplace:
+ assert ser._values is arr # i.e. setting was done inplace
+ else:
+ assert ser.dtype == object
+
+
+class TestShouldStore:
+ def test_should_store_categorical(self):
+ cat = Categorical(["A", "B", "C"])
+ df = DataFrame(cat)
+ blk = df._mgr.blocks[0]
+
+ # matching dtype
+ assert blk.should_store(cat)
+ assert blk.should_store(cat[:-1])
+
+ # different dtype
+ assert not blk.should_store(cat.as_ordered())
+
+ # ndarray instead of Categorical
+ assert not blk.should_store(np.asarray(cat))
+
+
+def test_validate_ndim():
+ values = np.array([1.0, 2.0])
+ placement = BlockPlacement(slice(2))
+ msg = r"Wrong number of dimensions. values.ndim != ndim \[1 != 2\]"
+
+ depr_msg = "make_block is deprecated"
+ with pytest.raises(ValueError, match=msg):
+ with tm.assert_produces_warning(Pandas4Warning, match=depr_msg):
+ make_block(values, placement, ndim=2)
+
+
+def test_block_shape():
+ idx = Index([0, 1, 2, 3, 4])
+ a = Series([1, 2, 3]).reindex(idx)
+ b = Series(Categorical([1, 2, 3])).reindex(idx)
+
+ assert a._mgr.blocks[0].mgr_locs.indexer == b._mgr.blocks[0].mgr_locs.indexer
+
+
+def test_make_block_no_pandas_array(block_maker):
+ # https://github.com/pandas-dev/pandas/pull/24866
+ arr = pd.arrays.NumpyExtensionArray(np.array([1, 2]))
+
+ depr_msg = "make_block is deprecated"
+ warn = DeprecationWarning if block_maker is make_block else None
+
+ # NumpyExtensionArray, no dtype
+ with tm.assert_produces_warning(warn, match=depr_msg):
+ result = block_maker(arr, BlockPlacement(slice(len(arr))), ndim=arr.ndim)
+ assert result.dtype.kind in ["i", "u"]
+
+ if block_maker is make_block:
+ # new_block requires caller to unwrap NumpyExtensionArray
+ assert result.is_extension is False
+
+ # NumpyExtensionArray, NumpyEADtype
+ with tm.assert_produces_warning(warn, match=depr_msg):
+ result = block_maker(arr, slice(len(arr)), dtype=arr.dtype, ndim=arr.ndim)
+ assert result.dtype.kind in ["i", "u"]
+ assert result.is_extension is False
+
+ # new_block no longer accepts dtype keyword
+ # ndarray, NumpyEADtype
+ with tm.assert_produces_warning(warn, match=depr_msg):
+ result = block_maker(
+ arr.to_numpy(), slice(len(arr)), dtype=arr.dtype, ndim=arr.ndim
+ )
+ assert result.dtype.kind in ["i", "u"]
+ assert result.is_extension is False
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ce44e87570a77aa31b2cc534ced2c2797e66c74
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/conftest.py
@@ -0,0 +1,197 @@
+import uuid
+
+import pytest
+
+from pandas.compat import (
+ is_ci_environment,
+ is_platform_arm,
+ is_platform_mac,
+ is_platform_windows,
+)
+import pandas.util._test_decorators as td
+
+import pandas.io.common as icom
+from pandas.io.parsers import read_csv
+
+
+@pytest.fixture
+def compression_to_extension():
+ return {value: key for key, value in icom.extension_to_compression.items()}
+
+
+@pytest.fixture
+def tips_file(datapath):
+ """Path to the tips dataset"""
+ return datapath("io", "data", "csv", "tips.csv")
+
+
+@pytest.fixture
+def jsonl_file(datapath):
+ """Path to a JSONL dataset"""
+ return datapath("io", "parser", "data", "items.jsonl")
+
+
+@pytest.fixture
+def salaries_table(datapath):
+ """DataFrame with the salaries dataset"""
+ return read_csv(datapath("io", "parser", "data", "salaries.csv"), sep="\t")
+
+
+@pytest.fixture
+def feather_file(datapath):
+ return datapath("io", "data", "feather", "feather-0_3_1.feather")
+
+
+@pytest.fixture
+def xml_file(datapath):
+ return datapath("io", "data", "xml", "books.xml")
+
+
+@pytest.fixture(scope="session")
+def aws_credentials(monkeysession):
+ """Mocked AWS Credentials for moto."""
+ monkeysession.setenv("AWS_ACCESS_KEY_ID", "testing")
+ monkeysession.setenv("AWS_SECRET_ACCESS_KEY", "testing")
+ monkeysession.setenv("AWS_SECURITY_TOKEN", "testing")
+ monkeysession.setenv("AWS_SESSION_AWS_SESSION_TOKEN", "testing")
+ monkeysession.setenv("AWS_DEFAULT_REGION", "us-east-1")
+
+
+@pytest.fixture(scope="session")
+def moto_server(aws_credentials):
+ # use service container for Linux on GitHub Actions
+ if is_ci_environment() and not (
+ is_platform_mac() or is_platform_arm() or is_platform_windows()
+ ):
+ yield "http://localhost:5000"
+ else:
+ moto_server = pytest.importorskip("moto.server")
+ server = moto_server.ThreadedMotoServer(port=0)
+ server.start()
+ host, port = server.get_host_and_port()
+ yield f"http://{host}:{port}"
+ server.stop()
+
+
+@pytest.fixture
+def moto_s3_resource(moto_server):
+ boto3 = pytest.importorskip("boto3")
+ s3 = boto3.resource("s3", endpoint_url=moto_server)
+ return s3
+
+
+@pytest.fixture(scope="session")
+def s3so(moto_server):
+ return {
+ "client_kwargs": {
+ "endpoint_url": moto_server,
+ }
+ }
+
+
+@pytest.fixture
+def s3_bucket_public(moto_s3_resource):
+ """
+ Create a public S3 bucket using moto.
+ """
+ bucket_name = f"pandas-test-{uuid.uuid4()}"
+ bucket = moto_s3_resource.Bucket(bucket_name)
+ bucket.create(ACL="public-read")
+ yield bucket
+ bucket.objects.delete()
+ bucket.delete()
+
+
+@pytest.fixture
+def s3_bucket_private(moto_s3_resource):
+ """
+ Create a private S3 bucket using moto.
+ """
+ bucket_name = f"cant_get_it-{uuid.uuid4()}"
+ bucket = moto_s3_resource.Bucket(bucket_name)
+ bucket.create(ACL="private")
+ yield bucket
+ bucket.objects.delete()
+ bucket.delete()
+
+
+@pytest.fixture
+def s3_bucket_public_with_data(
+ s3_bucket_public, tips_file, jsonl_file, feather_file, xml_file
+):
+ """
+ The following datasets
+ are loaded.
+
+ - tips.csv
+ - tips.csv.gz
+ - tips.csv.bz2
+ - items.jsonl
+ """
+ test_s3_files = [
+ ("tips#1.csv", tips_file),
+ ("tips.csv", tips_file),
+ ("tips.csv.gz", tips_file + ".gz"),
+ ("tips.csv.bz2", tips_file + ".bz2"),
+ ("items.jsonl", jsonl_file),
+ ("simple_dataset.feather", feather_file),
+ ("books.xml", xml_file),
+ ]
+ for s3_key, file_name in test_s3_files:
+ with open(file_name, "rb") as f:
+ s3_bucket_public.put_object(Key=s3_key, Body=f)
+ return s3_bucket_public
+
+
+@pytest.fixture
+def s3_bucket_private_with_data(
+ s3_bucket_private, tips_file, jsonl_file, feather_file, xml_file
+):
+ """
+ The following datasets
+ are loaded.
+
+ - tips.csv
+ - tips.csv.gz
+ - tips.csv.bz2
+ - items.jsonl
+ """
+ test_s3_files = [
+ ("tips#1.csv", tips_file),
+ ("tips.csv", tips_file),
+ ("tips.csv.gz", tips_file + ".gz"),
+ ("tips.csv.bz2", tips_file + ".bz2"),
+ ("items.jsonl", jsonl_file),
+ ("simple_dataset.feather", feather_file),
+ ("books.xml", xml_file),
+ ]
+ for s3_key, file_name in test_s3_files:
+ with open(file_name, "rb") as f:
+ s3_bucket_private.put_object(Key=s3_key, Body=f)
+ return s3_bucket_private
+
+
+_compression_formats_params = [
+ (".no_compress", None),
+ ("", None),
+ (".gz", "gzip"),
+ (".GZ", "gzip"),
+ (".bz2", "bz2"),
+ (".BZ2", "bz2"),
+ (".zip", "zip"),
+ (".ZIP", "zip"),
+ (".xz", "xz"),
+ (".XZ", "xz"),
+ pytest.param((".zst", "zstd"), marks=td.skip_if_no("zstandard")),
+ pytest.param((".ZST", "zstd"), marks=td.skip_if_no("zstandard")),
+]
+
+
+@pytest.fixture(params=_compression_formats_params[1:])
+def compression_format(request):
+ return request.param
+
+
+@pytest.fixture(params=_compression_formats_params)
+def compression_ext(request):
+ return request.param[0]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_odf.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_odf.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce720c9e1345722f222d46de4e8b103c4a7ede0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_odf.py
@@ -0,0 +1,72 @@
+import functools
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+pytest.importorskip("odf")
+
+
+@pytest.fixture(autouse=True)
+def cd_and_set_engine(monkeypatch, datapath):
+ func = functools.partial(pd.read_excel, engine="odf")
+ monkeypatch.setattr(pd, "read_excel", func)
+ monkeypatch.chdir(datapath("io", "data", "excel"))
+
+
+def test_read_invalid_types_raises():
+ # the invalid_value_type.ods required manually editing
+ # of the included content.xml file
+ with pytest.raises(ValueError, match="Unrecognized type awesome_new_type"):
+ pd.read_excel("invalid_value_type.ods")
+
+
+def test_read_writer_table():
+ # Also test reading tables from a text OpenDocument file
+ # (.odt)
+ index = pd.Index(["Row 1", "Row 2", "Row 3"], name="Header")
+ expected = pd.DataFrame(
+ [[1, np.nan, 7], [2, np.nan, 8], [3, np.nan, 9]],
+ index=index,
+ columns=["Column 1", "Unnamed: 2", "Column 3"],
+ )
+
+ result = pd.read_excel("writertable.odt", sheet_name="Table1", index_col=0)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_newlines_between_xml_elements_table():
+ # GH#45598
+ expected = pd.DataFrame(
+ [[1.0, 4.0, 7], [np.nan, np.nan, 8], [3.0, 6.0, 9]],
+ columns=["Column 1", "Column 2", "Column 3"],
+ )
+
+ result = pd.read_excel("test_newlines.ods")
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_unempty_cells():
+ expected = pd.DataFrame(
+ [1, np.nan, 3, np.nan, 5],
+ columns=["Column 1"],
+ )
+
+ result = pd.read_excel("test_unempty_cells.ods")
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_cell_annotation():
+ expected = pd.DataFrame(
+ ["test", np.nan, "test 3"],
+ columns=["Column 1"],
+ )
+
+ result = pd.read_excel("test_cell_annotation.ods")
+
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_odswriter.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_odswriter.py
new file mode 100644
index 0000000000000000000000000000000000000000..7843bb59f97cf6e95903c2869d33460843aa9169
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_odswriter.py
@@ -0,0 +1,106 @@
+from datetime import (
+ date,
+ datetime,
+)
+import re
+import uuid
+
+import pytest
+
+import pandas as pd
+
+from pandas.io.excel import ExcelWriter
+
+odf = pytest.importorskip("odf")
+
+
+@pytest.fixture
+def ext():
+ return ".ods"
+
+
+@pytest.fixture
+def tmp_excel(ext, tmp_path):
+ tmp = tmp_path / f"{uuid.uuid4()}{ext}"
+ tmp.touch()
+ return str(tmp)
+
+
+def test_write_append_mode_raises(tmp_excel):
+ msg = "Append mode is not supported with odf!"
+
+ with pytest.raises(ValueError, match=msg):
+ ExcelWriter(tmp_excel, engine="odf", mode="a")
+
+
+@pytest.mark.parametrize("engine_kwargs", [None, {"kwarg": 1}])
+def test_engine_kwargs(tmp_excel, engine_kwargs):
+ # GH 42286
+ # GH 43445
+ # test for error: OpenDocumentSpreadsheet does not accept any arguments
+ if engine_kwargs is not None:
+ error = re.escape(
+ "OpenDocumentSpreadsheet() got an unexpected keyword argument 'kwarg'"
+ )
+ with pytest.raises(
+ TypeError,
+ match=error,
+ ):
+ ExcelWriter(tmp_excel, engine="odf", engine_kwargs=engine_kwargs)
+ else:
+ with ExcelWriter(tmp_excel, engine="odf", engine_kwargs=engine_kwargs) as _:
+ pass
+
+
+def test_book_and_sheets_consistent(tmp_excel):
+ # GH#45687 - Ensure sheets is updated if user modifies book
+ with ExcelWriter(tmp_excel) as writer:
+ assert writer.sheets == {}
+ table = odf.table.Table(name="test_name")
+ writer.book.spreadsheet.addElement(table)
+ assert writer.sheets == {"test_name": table}
+
+
+@pytest.mark.parametrize(
+ ["value", "cell_value_type", "cell_value_attribute", "cell_value"],
+ argvalues=[
+ (True, "boolean", "boolean-value", "true"),
+ ("test string", "string", "string-value", "test string"),
+ (1, "float", "value", "1"),
+ (1.5, "float", "value", "1.5"),
+ (
+ datetime(2010, 10, 10, 10, 10, 10),
+ "date",
+ "date-value",
+ "2010-10-10T10:10:10",
+ ),
+ (date(2010, 10, 10), "date", "date-value", "2010-10-10"),
+ ],
+)
+def test_cell_value_type(
+ tmp_excel, value, cell_value_type, cell_value_attribute, cell_value
+):
+ # GH#54994 ODS: cell attributes should follow specification
+ # http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#refTable13
+ from odf.namespaces import OFFICENS
+ from odf.table import (
+ TableCell,
+ TableRow,
+ )
+
+ table_cell_name = TableCell().qname
+
+ pd.DataFrame([[value]]).to_excel(tmp_excel, header=False, index=False)
+
+ with pd.ExcelFile(tmp_excel) as wb:
+ sheet = wb._reader.get_sheet_by_index(0)
+ sheet_rows = sheet.getElementsByType(TableRow)
+ sheet_cells = [
+ x
+ for x in sheet_rows[0].childNodes
+ if hasattr(x, "qname") and x.qname == table_cell_name
+ ]
+
+ cell = sheet_cells[0]
+ assert cell.attributes.get((OFFICENS, "value-type")) == cell_value_type
+ assert cell.attributes.get((OFFICENS, cell_value_attribute)) == cell_value
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_openpyxl.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_openpyxl.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b4bbb9e686d3308b5fa698ab7e2607c29872efe
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_openpyxl.py
@@ -0,0 +1,431 @@
+import contextlib
+from pathlib import Path
+import re
+import uuid
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import DataFrame
+import pandas._testing as tm
+
+from pandas.io.excel import (
+ ExcelWriter,
+ _OpenpyxlWriter,
+)
+from pandas.io.excel._openpyxl import OpenpyxlReader
+
+openpyxl = pytest.importorskip("openpyxl")
+
+
+@pytest.fixture
+def ext():
+ return ".xlsx"
+
+
+@pytest.fixture
+def tmp_excel(ext, tmp_path):
+ tmp = tmp_path / f"{uuid.uuid4()}{ext}"
+ tmp.touch()
+ return str(tmp)
+
+
+def test_to_excel_styleconverter():
+ from openpyxl import styles
+
+ hstyle = {
+ "font": {"color": "00FF0000", "bold": True},
+ "borders": {"top": "thin", "right": "thin", "bottom": "thin", "left": "thin"},
+ "alignment": {"horizontal": "center", "vertical": "top"},
+ "fill": {"patternType": "solid", "fgColor": {"rgb": "006666FF", "tint": 0.3}},
+ "number_format": {"format_code": "0.00"},
+ "protection": {"locked": True, "hidden": False},
+ }
+
+ font_color = styles.Color("00FF0000")
+ font = styles.Font(bold=True, color=font_color)
+ side = styles.Side(style=styles.borders.BORDER_THIN)
+ border = styles.Border(top=side, right=side, bottom=side, left=side)
+ alignment = styles.Alignment(horizontal="center", vertical="top")
+ fill_color = styles.Color(rgb="006666FF", tint=0.3)
+ fill = styles.PatternFill(patternType="solid", fgColor=fill_color)
+
+ number_format = "0.00"
+
+ protection = styles.Protection(locked=True, hidden=False)
+
+ kw = _OpenpyxlWriter._convert_to_style_kwargs(hstyle)
+ assert kw["font"] == font
+ assert kw["border"] == border
+ assert kw["alignment"] == alignment
+ assert kw["fill"] == fill
+ assert kw["number_format"] == number_format
+ assert kw["protection"] == protection
+
+
+def test_write_cells_merge_styled(tmp_excel):
+ from pandas.io.formats.excel import ExcelCell
+
+ sheet_name = "merge_styled"
+
+ sty_b1 = {"font": {"color": "00FF0000"}}
+ sty_a2 = {"font": {"color": "0000FF00"}}
+
+ initial_cells = [
+ ExcelCell(col=1, row=0, val=42, style=sty_b1),
+ ExcelCell(col=0, row=1, val=99, style=sty_a2),
+ ]
+
+ sty_merged = {"font": {"color": "000000FF", "bold": True}}
+ sty_kwargs = _OpenpyxlWriter._convert_to_style_kwargs(sty_merged)
+ openpyxl_sty_merged = sty_kwargs["font"]
+ merge_cells = [
+ ExcelCell(
+ col=0, row=0, val="pandas", mergestart=1, mergeend=1, style=sty_merged
+ )
+ ]
+
+ with _OpenpyxlWriter(tmp_excel) as writer:
+ writer._write_cells(initial_cells, sheet_name=sheet_name)
+ writer._write_cells(merge_cells, sheet_name=sheet_name)
+
+ wks = writer.sheets[sheet_name]
+ xcell_b1 = wks["B1"]
+ xcell_a2 = wks["A2"]
+ assert xcell_b1.font == openpyxl_sty_merged
+ assert xcell_a2.font == openpyxl_sty_merged
+
+
+@pytest.mark.parametrize("iso_dates", [True, False])
+def test_engine_kwargs_write(tmp_excel, iso_dates):
+ # GH 42286 GH 43445
+ engine_kwargs = {"iso_dates": iso_dates}
+ with ExcelWriter(
+ tmp_excel, engine="openpyxl", engine_kwargs=engine_kwargs
+ ) as writer:
+ assert writer.book.iso_dates == iso_dates
+ # ExcelWriter won't allow us to close without writing something
+ DataFrame().to_excel(writer)
+
+
+def test_engine_kwargs_append_invalid(tmp_excel):
+ # GH 43445
+ # test whether an invalid engine kwargs actually raises
+ DataFrame(["hello", "world"]).to_excel(tmp_excel)
+ with pytest.raises(
+ TypeError,
+ match=re.escape(
+ "load_workbook() got an unexpected keyword argument 'apple_banana'"
+ ),
+ ):
+ with ExcelWriter(
+ tmp_excel,
+ engine="openpyxl",
+ mode="a",
+ engine_kwargs={"apple_banana": "fruit"},
+ ) as writer:
+ # ExcelWriter needs us to write something to close properly
+ DataFrame(["good"]).to_excel(writer, sheet_name="Sheet2")
+
+
+@pytest.mark.parametrize("data_only, expected", [(True, 0), (False, "=1+1")])
+def test_engine_kwargs_append_data_only(tmp_excel, data_only, expected):
+ # GH 43445
+ # tests whether the data_only engine_kwarg actually works well for
+ # openpyxl's load_workbook
+ DataFrame(["=1+1"]).to_excel(tmp_excel)
+ with ExcelWriter(
+ tmp_excel, engine="openpyxl", mode="a", engine_kwargs={"data_only": data_only}
+ ) as writer:
+ assert writer.sheets["Sheet1"]["B2"].value == expected
+ # ExcelWriter needs us to writer something to close properly?
+ DataFrame().to_excel(writer, sheet_name="Sheet2")
+
+ # ensure that data_only also works for reading
+ # and that formulas/values roundtrip
+ assert (
+ pd.read_excel(
+ tmp_excel,
+ sheet_name="Sheet1",
+ engine="openpyxl",
+ engine_kwargs={"data_only": data_only},
+ ).iloc[0, 1]
+ == expected
+ )
+
+
+@pytest.mark.parametrize("kwarg_name", ["read_only", "data_only"])
+@pytest.mark.parametrize("kwarg_value", [True, False])
+def test_engine_kwargs_append_reader(datapath, ext, kwarg_name, kwarg_value):
+ # GH 55027
+ # test that `read_only` and `data_only` can be passed to
+ # `openpyxl.reader.excel.load_workbook` via `engine_kwargs`
+ filename = datapath("io", "data", "excel", "test1" + ext)
+ with contextlib.closing(
+ OpenpyxlReader(filename, engine_kwargs={kwarg_name: kwarg_value})
+ ) as reader:
+ assert getattr(reader.book, kwarg_name) == kwarg_value
+
+
+@pytest.mark.parametrize(
+ "mode,expected", [("w", ["baz"]), ("a", ["foo", "bar", "baz"])]
+)
+def test_write_append_mode(tmp_excel, mode, expected):
+ df = DataFrame([1], columns=["baz"])
+
+ wb = openpyxl.Workbook()
+ wb.worksheets[0].title = "foo"
+ wb.worksheets[0]["A1"].value = "foo"
+ wb.create_sheet("bar")
+ wb.worksheets[1]["A1"].value = "bar"
+ wb.save(tmp_excel)
+
+ with ExcelWriter(tmp_excel, engine="openpyxl", mode=mode) as writer:
+ df.to_excel(writer, sheet_name="baz", index=False)
+
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb2:
+ result = [sheet.title for sheet in wb2.worksheets]
+ assert result == expected
+
+ for index, cell_value in enumerate(expected):
+ assert wb2.worksheets[index]["A1"].value == cell_value
+
+
+@pytest.mark.parametrize(
+ "if_sheet_exists,num_sheets,expected",
+ [
+ ("new", 2, ["apple", "banana"]),
+ ("replace", 1, ["pear"]),
+ ("overlay", 1, ["pear", "banana"]),
+ ],
+)
+def test_if_sheet_exists_append_modes(tmp_excel, if_sheet_exists, num_sheets, expected):
+ # GH 40230
+ df1 = DataFrame({"fruit": ["apple", "banana"]})
+ df2 = DataFrame({"fruit": ["pear"]})
+
+ df1.to_excel(tmp_excel, engine="openpyxl", sheet_name="foo", index=False)
+ with ExcelWriter(
+ tmp_excel, engine="openpyxl", mode="a", if_sheet_exists=if_sheet_exists
+ ) as writer:
+ df2.to_excel(writer, sheet_name="foo", index=False)
+
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ assert len(wb.sheetnames) == num_sheets
+ assert wb.sheetnames[0] == "foo"
+ result = pd.read_excel(wb, "foo", engine="openpyxl")
+ assert list(result["fruit"]) == expected
+ if len(wb.sheetnames) == 2:
+ result = pd.read_excel(wb, wb.sheetnames[1], engine="openpyxl")
+ tm.assert_frame_equal(result, df2)
+
+
+@pytest.mark.parametrize(
+ "startrow, startcol, greeting, goodbye",
+ [
+ (0, 0, ["poop", "world"], ["goodbye", "people"]),
+ (0, 1, ["hello", "world"], ["poop", "people"]),
+ (1, 0, ["hello", "poop"], ["goodbye", "people"]),
+ (1, 1, ["hello", "world"], ["goodbye", "poop"]),
+ ],
+)
+def test_append_overlay_startrow_startcol(
+ tmp_excel, startrow, startcol, greeting, goodbye
+):
+ df1 = DataFrame({"greeting": ["hello", "world"], "goodbye": ["goodbye", "people"]})
+ df2 = DataFrame(["poop"])
+
+ df1.to_excel(tmp_excel, engine="openpyxl", sheet_name="poo", index=False)
+ with ExcelWriter(
+ tmp_excel, engine="openpyxl", mode="a", if_sheet_exists="overlay"
+ ) as writer:
+ # use startrow+1 because we don't have a header
+ df2.to_excel(
+ writer,
+ index=False,
+ header=False,
+ startrow=startrow + 1,
+ startcol=startcol,
+ sheet_name="poo",
+ )
+
+ result = pd.read_excel(tmp_excel, sheet_name="poo", engine="openpyxl")
+ expected = DataFrame({"greeting": greeting, "goodbye": goodbye})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "if_sheet_exists,msg",
+ [
+ (
+ "invalid",
+ "'invalid' is not valid for if_sheet_exists. Valid options "
+ "are 'error', 'new', 'replace' and 'overlay'.",
+ ),
+ (
+ "error",
+ "Sheet 'foo' already exists and if_sheet_exists is set to 'error'.",
+ ),
+ (
+ None,
+ "Sheet 'foo' already exists and if_sheet_exists is set to 'error'.",
+ ),
+ ],
+)
+def test_if_sheet_exists_raises(tmp_excel, if_sheet_exists, msg):
+ # GH 40230
+ df = DataFrame({"fruit": ["pear"]})
+ df.to_excel(tmp_excel, sheet_name="foo", engine="openpyxl")
+ with pytest.raises(ValueError, match=re.escape(msg)):
+ with ExcelWriter(
+ tmp_excel, engine="openpyxl", mode="a", if_sheet_exists=if_sheet_exists
+ ) as writer:
+ df.to_excel(writer, sheet_name="foo")
+
+
+def test_to_excel_with_openpyxl_engine(tmp_excel):
+ # GH 29854
+ df1 = DataFrame({"A": np.linspace(1, 10, 10)})
+ df2 = DataFrame({"B": np.linspace(1, 20, 10)})
+ df = pd.concat([df1, df2], axis=1)
+ styled = df.style.map(
+ lambda val: f"color: {'red' if val < 0 else 'black'}"
+ ).highlight_max()
+
+ styled.to_excel(tmp_excel, engine="openpyxl")
+
+
+@pytest.mark.parametrize("read_only", [True, False])
+def test_read_workbook(datapath, ext, read_only):
+ # GH 39528
+ filename = datapath("io", "data", "excel", "test1" + ext)
+ with contextlib.closing(
+ openpyxl.load_workbook(filename, read_only=read_only)
+ ) as wb:
+ result = pd.read_excel(wb, engine="openpyxl")
+ expected = pd.read_excel(filename)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "header, expected_data",
+ [
+ (
+ 0,
+ {
+ "Title": [np.nan, "A", 1, 2, 3],
+ "Unnamed: 1": [np.nan, "B", 4, 5, 6],
+ "Unnamed: 2": [np.nan, "C", 7, 8, 9],
+ },
+ ),
+ (2, {"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]}),
+ ],
+)
+@pytest.mark.parametrize(
+ "filename", ["dimension_missing", "dimension_small", "dimension_large"]
+)
+# When read_only is None, use read_excel instead of a workbook
+@pytest.mark.parametrize("read_only", [True, False, None])
+def test_read_with_bad_dimension(
+ datapath, ext, header, expected_data, filename, read_only
+):
+ # GH 38956, 39001 - no/incorrect dimension information
+ path = datapath("io", "data", "excel", f"{filename}{ext}")
+ if read_only is None:
+ result = pd.read_excel(path, header=header)
+ else:
+ with contextlib.closing(
+ openpyxl.load_workbook(path, read_only=read_only)
+ ) as wb:
+ result = pd.read_excel(wb, engine="openpyxl", header=header)
+ expected = DataFrame(expected_data)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_append_mode_file(tmp_excel):
+ # GH 39576
+ df = DataFrame()
+
+ df.to_excel(tmp_excel, engine="openpyxl")
+
+ with ExcelWriter(
+ tmp_excel, mode="a", engine="openpyxl", if_sheet_exists="new"
+ ) as writer:
+ df.to_excel(writer)
+
+ # make sure that zip files are not concatenated by making sure that
+ # "docProps/app.xml" only occurs twice in the file
+ data = Path(tmp_excel).read_bytes()
+ first = data.find(b"docProps/app.xml")
+ second = data.find(b"docProps/app.xml", first + 1)
+ third = data.find(b"docProps/app.xml", second + 1)
+ assert second != -1 and third == -1
+
+
+# When read_only is None, use read_excel instead of a workbook
+@pytest.mark.parametrize("read_only", [True, False, None])
+def test_read_with_empty_trailing_rows(datapath, ext, read_only):
+ # GH 39181
+ path = datapath("io", "data", "excel", f"empty_trailing_rows{ext}")
+ if read_only is None:
+ result = pd.read_excel(path)
+ else:
+ with contextlib.closing(
+ openpyxl.load_workbook(path, read_only=read_only)
+ ) as wb:
+ result = pd.read_excel(wb, engine="openpyxl")
+ expected = DataFrame(
+ {
+ "Title": [np.nan, "A", 1, 2, 3],
+ "Unnamed: 1": [np.nan, "B", 4, 5, 6],
+ "Unnamed: 2": [np.nan, "C", 7, 8, 9],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+# When read_only is None, use read_excel instead of a workbook
+@pytest.mark.parametrize("read_only", [True, False, None])
+def test_read_empty_with_blank_row(datapath, ext, read_only):
+ # GH 39547 - empty excel file with a row that has no data
+ path = datapath("io", "data", "excel", f"empty_with_blank_row{ext}")
+ if read_only is None:
+ result = pd.read_excel(path)
+ else:
+ with contextlib.closing(
+ openpyxl.load_workbook(path, read_only=read_only)
+ ) as wb:
+ result = pd.read_excel(wb, engine="openpyxl")
+ expected = DataFrame()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_book_and_sheets_consistent(tmp_excel):
+ # GH#45687 - Ensure sheets is updated if user modifies book
+ with ExcelWriter(tmp_excel, engine="openpyxl") as writer:
+ assert writer.sheets == {}
+ sheet = writer.book.create_sheet("test_name", 0)
+ assert writer.sheets == {"test_name": sheet}
+
+
+def test_ints_spelled_with_decimals(datapath, ext):
+ # GH 46988 - openpyxl returns this sheet with floats
+ path = datapath("io", "data", "excel", f"ints_spelled_with_decimals{ext}")
+ result = pd.read_excel(path)
+ expected = DataFrame(range(2, 12), columns=[1])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_multiindex_header_no_index_names(datapath, ext):
+ # GH#47487
+ path = datapath("io", "data", "excel", f"multiindex_no_index_names{ext}")
+ result = pd.read_excel(path, index_col=[0, 1, 2], header=[0, 1, 2])
+ expected = DataFrame(
+ [[np.nan, "x", "x", "x"], ["x", np.nan, np.nan, np.nan]],
+ columns=pd.MultiIndex.from_tuples(
+ [("X", "Y", "A1"), ("X", "Y", "A2"), ("XX", "YY", "B1"), ("XX", "YY", "B2")]
+ ),
+ index=pd.MultiIndex.from_tuples([("A", "AA", "AAA"), ("A", "BB", "BBB")]),
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_readers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_readers.py
new file mode 100644
index 0000000000000000000000000000000000000000..830c2fb40ffbea96c4f00d09b39282cd10e9da01
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_readers.py
@@ -0,0 +1,1743 @@
+from __future__ import annotations
+
+from datetime import (
+ datetime,
+ time,
+)
+from functools import partial
+from io import BytesIO
+import os
+from pathlib import Path
+import platform
+import re
+from urllib.error import URLError
+import uuid
+from zipfile import BadZipFile
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ read_csv,
+)
+import pandas._testing as tm
+
+read_ext_params = [".xls", ".xlsx", ".xlsm", ".xlsb", ".ods"]
+engine_params = [
+ # Add any engines to test here
+ # When defusedxml is installed it triggers deprecation warnings for
+ # xlrd and openpyxl, so catch those here
+ pytest.param(
+ "xlrd",
+ marks=[
+ td.skip_if_no("xlrd"),
+ ],
+ ),
+ pytest.param(
+ "openpyxl",
+ marks=[
+ td.skip_if_no("openpyxl"),
+ ],
+ ),
+ pytest.param(
+ None,
+ marks=[
+ td.skip_if_no("xlrd"),
+ ],
+ ),
+ pytest.param("pyxlsb", marks=td.skip_if_no("pyxlsb")),
+ pytest.param("odf", marks=td.skip_if_no("odf")),
+ pytest.param("calamine", marks=td.skip_if_no("python_calamine")),
+]
+
+
+def _is_valid_engine_ext_pair(engine, read_ext: str) -> bool:
+ """
+ Filter out invalid (engine, ext) pairs instead of skipping, as that
+ produces 500+ pytest.skips.
+ """
+ engine = engine.values[0]
+ if engine == "openpyxl" and read_ext == ".xls":
+ return False
+ if engine == "odf" and read_ext != ".ods":
+ return False
+ if read_ext == ".ods" and engine not in {"odf", "calamine"}:
+ return False
+ if engine == "pyxlsb" and read_ext != ".xlsb":
+ return False
+ if read_ext == ".xlsb" and engine not in {"pyxlsb", "calamine"}:
+ return False
+ if engine == "xlrd" and read_ext != ".xls":
+ return False
+ return True
+
+
+def _transfer_marks(engine, read_ext):
+ """
+ engine gives us a pytest.param object with some marks, read_ext is just
+ a string. We need to generate a new pytest.param inheriting the marks.
+ """
+ values = (*engine.values, read_ext)
+ new_param = pytest.param(values, marks=engine.marks)
+ return new_param
+
+
+@pytest.fixture(
+ params=[
+ _transfer_marks(eng, ext)
+ for eng in engine_params
+ for ext in read_ext_params
+ if _is_valid_engine_ext_pair(eng, ext)
+ ],
+ ids=str,
+)
+def engine_and_read_ext(request):
+ """
+ Fixture for Excel reader engine and read_ext, only including valid pairs.
+ """
+ return request.param
+
+
+@pytest.fixture
+def engine(engine_and_read_ext):
+ engine, read_ext = engine_and_read_ext
+ return engine
+
+
+@pytest.fixture
+def read_ext(engine_and_read_ext):
+ engine, read_ext = engine_and_read_ext
+ return read_ext
+
+
+@pytest.fixture
+def tmp_excel(read_ext, tmp_path):
+ tmp = tmp_path / f"{uuid.uuid4()}{read_ext}"
+ tmp.touch()
+ return str(tmp)
+
+
+@pytest.fixture
+def df_ref(datapath):
+ """
+ Obtain the reference data from read_csv with the Python engine.
+ """
+ filepath = datapath("io", "data", "csv", "test1.csv")
+ df_ref = read_csv(filepath, index_col=0, parse_dates=True, engine="python")
+ return df_ref
+
+
+def get_exp_unit(read_ext: str, engine: str | None) -> str:
+ return "us"
+
+
+def adjust_expected(expected: DataFrame, read_ext: str, engine: str | None) -> None:
+ expected.index.name = None
+ unit = get_exp_unit(read_ext, engine)
+ # error: "Index" has no attribute "as_unit"
+ expected.index = expected.index.as_unit(unit) # type: ignore[attr-defined]
+
+
+def xfail_datetimes_with_pyxlsb(engine, request):
+ if engine == "pyxlsb":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="Sheets containing datetimes not supported by pyxlsb"
+ )
+ )
+
+
+class TestReaders:
+ @pytest.mark.parametrize("col", [[True, None, False], [True], [True, False]])
+ def test_read_excel_type_check(self, col, tmp_excel, read_ext):
+ # GH 58159
+ if read_ext in (".xlsb", ".xls"):
+ pytest.skip(f"No engine for filetype: '{read_ext}'")
+ df = DataFrame({"bool_column": col}, dtype="boolean")
+ df.to_excel(tmp_excel, index=False)
+ df2 = pd.read_excel(tmp_excel, dtype={"bool_column": "boolean"})
+ tm.assert_frame_equal(df, df2)
+
+ def test_pass_none_type(self, datapath):
+ # GH 58159
+ f_path = datapath("io", "data", "excel", "test_none_type.xlsx")
+
+ with pd.ExcelFile(f_path) as excel:
+ parsed = pd.read_excel(
+ excel,
+ sheet_name="Sheet1",
+ keep_default_na=True,
+ na_values=["nan", "None", "abcd"],
+ dtype="boolean",
+ engine="openpyxl",
+ )
+ expected = DataFrame(
+ {"Test": [True, None, False, None, False, None, True]},
+ dtype="boolean",
+ )
+
+ tm.assert_frame_equal(parsed, expected)
+
+ @pytest.fixture(autouse=True)
+ def cd_and_set_engine(self, engine, datapath, monkeypatch):
+ """
+ Change directory and set engine for read_excel calls.
+ """
+ func = partial(pd.read_excel, engine=engine)
+ monkeypatch.chdir(datapath("io", "data", "excel"))
+ monkeypatch.setattr(pd, "read_excel", func)
+
+ def test_engine_used(self, read_ext, engine, monkeypatch):
+ # GH 38884
+ def parser(self, *args, **kwargs):
+ return self.engine
+
+ monkeypatch.setattr(pd.ExcelFile, "parse", parser)
+
+ expected_defaults = {
+ "xlsx": "openpyxl",
+ "xlsm": "openpyxl",
+ "xlsb": "pyxlsb",
+ "xls": "xlrd",
+ "ods": "odf",
+ }
+
+ with open("test1" + read_ext, "rb") as f:
+ result = pd.read_excel(f)
+
+ if engine is not None:
+ expected = engine
+ else:
+ expected = expected_defaults[read_ext[1:]]
+ assert result == expected
+
+ def test_engine_kwargs(self, read_ext, engine):
+ # GH#52214
+ expected_defaults = {
+ "xlsx": {"foo": "abcd"},
+ "xlsm": {"foo": 123},
+ "xlsb": {"foo": "True"},
+ "xls": {"foo": True},
+ "ods": {"foo": "abcd"},
+ }
+
+ if engine in {"xlrd", "pyxlsb"}:
+ msg = re.escape(r"open_workbook() got an unexpected keyword argument 'foo'")
+ elif engine == "odf":
+ msg = re.escape(r"load() got an unexpected keyword argument 'foo'")
+ else:
+ msg = re.escape(r"load_workbook() got an unexpected keyword argument 'foo'")
+
+ if engine is not None:
+ with pytest.raises(TypeError, match=msg):
+ pd.read_excel(
+ "test1" + read_ext,
+ sheet_name="Sheet1",
+ index_col=0,
+ engine_kwargs=expected_defaults[read_ext[1:]],
+ )
+
+ def test_usecols_int(self, read_ext):
+ # usecols as int
+ msg = "Passing an integer for `usecols`"
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet1", index_col=0, usecols=3
+ )
+
+ # usecols as int
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel(
+ "test1" + read_ext,
+ sheet_name="Sheet2",
+ skiprows=[1],
+ index_col=0,
+ usecols=3,
+ )
+
+ def test_usecols_list(self, request, engine, read_ext, df_ref):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ expected = df_ref[["B", "C"]]
+ adjust_expected(expected, read_ext, engine)
+
+ df1 = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet1", index_col=0, usecols=[0, 2, 3]
+ )
+ df2 = pd.read_excel(
+ "test1" + read_ext,
+ sheet_name="Sheet2",
+ skiprows=[1],
+ index_col=0,
+ usecols=[0, 2, 3],
+ )
+
+ # TODO add index to xls file)
+ tm.assert_frame_equal(df1, expected)
+ tm.assert_frame_equal(df2, expected)
+
+ def test_usecols_str(self, request, engine, read_ext, df_ref):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ expected = df_ref[["A", "B", "C"]]
+ adjust_expected(expected, read_ext, engine)
+
+ df2 = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet1", index_col=0, usecols="A:D"
+ )
+ df3 = pd.read_excel(
+ "test1" + read_ext,
+ sheet_name="Sheet2",
+ skiprows=[1],
+ index_col=0,
+ usecols="A:D",
+ )
+
+ # TODO add index to xls, read xls ignores index name ?
+ tm.assert_frame_equal(df2, expected)
+ tm.assert_frame_equal(df3, expected)
+
+ expected = df_ref[["B", "C"]]
+ adjust_expected(expected, read_ext, engine)
+
+ df2 = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet1", index_col=0, usecols="A,C,D"
+ )
+ df3 = pd.read_excel(
+ "test1" + read_ext,
+ sheet_name="Sheet2",
+ skiprows=[1],
+ index_col=0,
+ usecols="A,C,D",
+ )
+ # TODO add index to xls file
+ tm.assert_frame_equal(df2, expected)
+ tm.assert_frame_equal(df3, expected)
+
+ df2 = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet1", index_col=0, usecols="A,C:D"
+ )
+ df3 = pd.read_excel(
+ "test1" + read_ext,
+ sheet_name="Sheet2",
+ skiprows=[1],
+ index_col=0,
+ usecols="A,C:D",
+ )
+ tm.assert_frame_equal(df2, expected)
+ tm.assert_frame_equal(df3, expected)
+
+ @pytest.mark.parametrize(
+ "usecols", [[0, 1, 3], [0, 3, 1], [1, 0, 3], [1, 3, 0], [3, 0, 1], [3, 1, 0]]
+ )
+ def test_usecols_diff_positional_int_columns_order(
+ self, request, engine, read_ext, usecols, df_ref
+ ):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ expected = df_ref[["A", "C"]]
+ adjust_expected(expected, read_ext, engine)
+
+ result = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet1", index_col=0, usecols=usecols
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("usecols", [["B", "D"], ["D", "B"]])
+ def test_usecols_diff_positional_str_columns_order(self, read_ext, usecols, df_ref):
+ expected = df_ref[["B", "D"]]
+ expected.index = range(len(expected))
+
+ result = pd.read_excel("test1" + read_ext, sheet_name="Sheet1", usecols=usecols)
+ tm.assert_frame_equal(result, expected)
+
+ def test_read_excel_without_slicing(self, request, engine, read_ext, df_ref):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ expected = df_ref
+ adjust_expected(expected, read_ext, engine)
+
+ result = pd.read_excel("test1" + read_ext, sheet_name="Sheet1", index_col=0)
+ tm.assert_frame_equal(result, expected)
+
+ def test_usecols_excel_range_str(self, request, engine, read_ext, df_ref):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ expected = df_ref[["C", "D"]]
+ adjust_expected(expected, read_ext, engine)
+
+ result = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet1", index_col=0, usecols="A,D:E"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_usecols_excel_range_str_invalid(self, read_ext):
+ msg = "Invalid column name: E1"
+
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel("test1" + read_ext, sheet_name="Sheet1", usecols="D:E1")
+
+ def test_index_col_label_error(self, read_ext):
+ msg = "list indices must be integers.*, not str"
+
+ with pytest.raises(TypeError, match=msg):
+ pd.read_excel(
+ "test1" + read_ext,
+ sheet_name="Sheet1",
+ index_col=["A"],
+ usecols=["A", "C"],
+ )
+
+ def test_index_col_str(self, read_ext):
+ # see gh-52716
+ result = pd.read_excel("test1" + read_ext, sheet_name="Sheet3", index_col="A")
+ expected = DataFrame(
+ columns=["B", "C", "D", "E", "F"], index=Index([], name="A")
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_index_col_empty(self, read_ext):
+ # see gh-9208
+ result = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet3", index_col=["A", "B", "C"]
+ )
+ expected = DataFrame(
+ columns=["D", "E", "F"],
+ index=MultiIndex(levels=[[]] * 3, codes=[[]] * 3, names=["A", "B", "C"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("index_col", [None, 2])
+ def test_index_col_with_unnamed(self, read_ext, index_col):
+ # see gh-18792
+ result = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet4", index_col=index_col
+ )
+ expected = DataFrame(
+ [["i1", "a", "x"], ["i2", "b", "y"]], columns=["Unnamed: 0", "col1", "col2"]
+ )
+ if index_col:
+ expected = expected.set_index(expected.columns[index_col])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_usecols_pass_non_existent_column(self, read_ext):
+ msg = (
+ "Usecols do not match columns, "
+ "columns expected but not found: "
+ r"\['E'\]"
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel("test1" + read_ext, usecols=["E"])
+
+ def test_usecols_wrong_type(self, read_ext):
+ msg = (
+ "'usecols' must either be list-like of "
+ "all strings, all unicode, all integers or a callable."
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel("test1" + read_ext, usecols=["E1", 0])
+
+ def test_excel_stop_iterator(self, read_ext):
+ parsed = pd.read_excel("test2" + read_ext, sheet_name="Sheet1")
+ expected = DataFrame([["aaaa", "bbbbb"]], columns=["Test", "Test1"])
+ tm.assert_frame_equal(parsed, expected)
+
+ def test_excel_cell_error_na(self, request, engine, read_ext):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ # https://github.com/tafia/calamine/issues/355
+ if engine == "calamine" and read_ext == ".ods":
+ request.applymarker(
+ pytest.mark.xfail(reason="Calamine can't extract error from ods files")
+ )
+
+ parsed = pd.read_excel("test3" + read_ext, sheet_name="Sheet1")
+ expected = DataFrame([[np.nan]], columns=["Test"])
+ tm.assert_frame_equal(parsed, expected)
+
+ def test_excel_table(self, request, engine, read_ext, df_ref):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ expected = df_ref
+ adjust_expected(expected, read_ext, engine)
+
+ df1 = pd.read_excel("test1" + read_ext, sheet_name="Sheet1", index_col=0)
+ df2 = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet2", skiprows=[1], index_col=0
+ )
+ # TODO add index to file
+ tm.assert_frame_equal(df1, expected)
+ tm.assert_frame_equal(df2, expected)
+
+ df3 = pd.read_excel(
+ "test1" + read_ext, sheet_name="Sheet1", index_col=0, skipfooter=1
+ )
+ tm.assert_frame_equal(df3, df1.iloc[:-1])
+
+ def test_reader_special_dtypes(self, request, engine, read_ext):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ unit = get_exp_unit(read_ext, engine)
+ expected = DataFrame.from_dict(
+ {
+ "IntCol": [1, 2, -3, 4, 0],
+ "FloatCol": [1.25, 2.25, 1.83, 1.92, 0.0000000005],
+ "BoolCol": [True, False, True, True, False],
+ "StrCol": [1, 2, 3, 4, 5],
+ "Str2Col": ["a", 3, "c", "d", "e"],
+ "DateCol": Index(
+ [
+ datetime(2013, 10, 30),
+ datetime(2013, 10, 31),
+ datetime(1905, 1, 1),
+ datetime(2013, 12, 14),
+ datetime(2015, 3, 14),
+ ],
+ dtype=f"M8[{unit}]",
+ ),
+ },
+ )
+ basename = "test_types"
+
+ # should read in correctly and infer types
+ actual = pd.read_excel(basename + read_ext, sheet_name="Sheet1")
+ tm.assert_frame_equal(actual, expected)
+
+ # if not coercing number, then int comes in as float
+ float_expected = expected.copy()
+ float_expected.loc[float_expected.index[1], "Str2Col"] = 3.0
+ actual = pd.read_excel(basename + read_ext, sheet_name="Sheet1")
+ tm.assert_frame_equal(actual, float_expected)
+
+ # check setting Index (assuming xls and xlsx are the same here)
+ for icol, name in enumerate(expected.columns):
+ actual = pd.read_excel(
+ basename + read_ext, sheet_name="Sheet1", index_col=icol
+ )
+ exp = expected.set_index(name)
+ tm.assert_frame_equal(actual, exp)
+
+ expected["StrCol"] = expected["StrCol"].apply(str)
+ actual = pd.read_excel(
+ basename + read_ext, sheet_name="Sheet1", converters={"StrCol": str}
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ # GH8212 - support for converters and missing values
+ def test_reader_converters(self, read_ext):
+ basename = "test_converters"
+
+ expected = DataFrame.from_dict(
+ {
+ "IntCol": [1, 2, -3, -1000, 0],
+ "FloatCol": [12.5, np.nan, 18.3, 19.2, 0.000000005],
+ "BoolCol": ["Found", "Found", "Found", "Not found", "Found"],
+ "StrCol": ["1", np.nan, "3", "4", "5"],
+ }
+ )
+
+ converters = {
+ "IntCol": lambda x: int(x) if x != "" else -1000,
+ "FloatCol": lambda x: 10 * x if x else np.nan,
+ 2: lambda x: "Found" if x != "" else "Not found",
+ 3: lambda x: str(x) if x else "",
+ }
+
+ # should read in correctly and set types of single cells (not array
+ # dtypes)
+ actual = pd.read_excel(
+ basename + read_ext, sheet_name="Sheet1", converters=converters
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ def test_reader_dtype(self, read_ext):
+ # GH 8212
+ basename = "testdtype"
+ actual = pd.read_excel(basename + read_ext)
+
+ expected = DataFrame(
+ {
+ "a": [1, 2, 3, 4],
+ "b": [2.5, 3.5, 4.5, 5.5],
+ "c": [1, 2, 3, 4],
+ "d": [1.0, 2.0, np.nan, 4.0],
+ }
+ )
+
+ tm.assert_frame_equal(actual, expected)
+
+ actual = pd.read_excel(
+ basename + read_ext, dtype={"a": "float64", "b": "float32", "c": str}
+ )
+
+ expected["a"] = expected["a"].astype("float64")
+ expected["b"] = expected["b"].astype("float32")
+ expected["c"] = Series(["001", "002", "003", "004"], dtype="str")
+ tm.assert_frame_equal(actual, expected)
+
+ msg = "Unable to convert column d to type int64"
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel(basename + read_ext, dtype={"d": "int64"})
+
+ @pytest.mark.parametrize(
+ "dtype,expected",
+ [
+ (
+ None,
+ {
+ "a": [1, 2, 3, 4],
+ "b": [2.5, 3.5, 4.5, 5.5],
+ "c": [1, 2, 3, 4],
+ "d": [1.0, 2.0, np.nan, 4.0],
+ },
+ ),
+ (
+ {"a": "float64", "b": "float32", "c": str, "d": str},
+ {
+ "a": Series([1, 2, 3, 4], dtype="float64"),
+ "b": Series([2.5, 3.5, 4.5, 5.5], dtype="float32"),
+ "c": Series(["001", "002", "003", "004"], dtype="str"),
+ "d": Series(["1", "2", np.nan, "4"], dtype="str"),
+ },
+ ),
+ ],
+ )
+ def test_reader_dtype_str(self, read_ext, dtype, expected):
+ # see gh-20377
+ basename = "testdtype"
+
+ actual = pd.read_excel(basename + read_ext, dtype=dtype)
+ expected = DataFrame(expected)
+ tm.assert_frame_equal(actual, expected)
+
+ def test_dtype_backend(self, read_ext, dtype_backend, engine, tmp_excel):
+ # GH#36712
+ if read_ext in (".xlsb", ".xls"):
+ pytest.skip(f"No engine for filetype: '{read_ext}'")
+
+ df = DataFrame(
+ {
+ "a": Series([1, 3], dtype="Int64"),
+ "b": Series([2.5, 4.5], dtype="Float64"),
+ "c": Series([True, False], dtype="boolean"),
+ "d": Series(["a", "b"], dtype="string"),
+ "e": Series([pd.NA, 6], dtype="Int64"),
+ "f": Series([pd.NA, 7.5], dtype="Float64"),
+ "g": Series([pd.NA, True], dtype="boolean"),
+ "h": Series([pd.NA, "a"], dtype="string"),
+ "i": Series([pd.Timestamp("2019-12-31")] * 2),
+ "j": Series([pd.NA, pd.NA], dtype="Int64"),
+ }
+ )
+ df.to_excel(tmp_excel, sheet_name="test", index=False)
+ result = pd.read_excel(
+ tmp_excel, sheet_name="test", dtype_backend=dtype_backend
+ )
+ if dtype_backend == "pyarrow":
+ import pyarrow as pa
+
+ from pandas.arrays import ArrowExtensionArray
+
+ expected = DataFrame(
+ {
+ col: ArrowExtensionArray(pa.array(df[col], from_pandas=True))
+ for col in df.columns
+ }
+ )
+
+ # pandas uses large_string by default, but pyarrow infers string
+ expected["d"] = expected["d"].astype(pd.ArrowDtype(pa.string()))
+ expected["h"] = expected["h"].astype(pd.ArrowDtype(pa.string()))
+ # pyarrow by default infers timestamp resolution as us, not ns
+ expected["i"] = ArrowExtensionArray(
+ expected["i"].array._pa_array.cast(pa.timestamp(unit="us"))
+ )
+ # pyarrow supports a null type, so don't have to default to Int64
+ expected["j"] = ArrowExtensionArray(pa.array([None, None]))
+ else:
+ expected = df
+ unit = get_exp_unit(read_ext, engine)
+ expected["i"] = expected["i"].astype(f"M8[{unit}]")
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_dtype_backend_and_dtype(self, read_ext, tmp_excel):
+ # GH#36712
+ if read_ext in (".xlsb", ".xls"):
+ pytest.skip(f"No engine for filetype: '{read_ext}'")
+
+ df = DataFrame({"a": [np.nan, 1.0], "b": [2.5, np.nan]})
+ df.to_excel(tmp_excel, sheet_name="test", index=False)
+ result = pd.read_excel(
+ tmp_excel,
+ sheet_name="test",
+ dtype_backend="numpy_nullable",
+ dtype="float64",
+ )
+ tm.assert_frame_equal(result, df)
+
+ def test_dtype_backend_string(self, read_ext, string_storage, tmp_excel):
+ # GH#36712
+ if read_ext in (".xlsb", ".xls"):
+ pytest.skip(f"No engine for filetype: '{read_ext}'")
+
+ df = DataFrame(
+ {
+ "a": np.array(["a", "b"], dtype=np.object_),
+ "b": np.array(["x", pd.NA], dtype=np.object_),
+ }
+ )
+ df.to_excel(tmp_excel, sheet_name="test", index=False)
+
+ with pd.option_context("mode.string_storage", string_storage):
+ result = pd.read_excel(
+ tmp_excel, sheet_name="test", dtype_backend="numpy_nullable"
+ )
+
+ expected = DataFrame(
+ {
+ "a": Series(["a", "b"], dtype=pd.StringDtype(string_storage)),
+ "b": Series(["x", None], dtype=pd.StringDtype(string_storage)),
+ }
+ )
+ # the storage of the str columns' Index is also affected by the
+ # string_storage setting -> ignore that for checking the result
+ tm.assert_frame_equal(result, expected, check_column_type=False)
+
+ @pytest.mark.parametrize("dtypes, exp_value", [({}, 1), ({"a.1": "int64"}, 1)])
+ def test_dtype_mangle_dup_cols(self, read_ext, dtypes, exp_value):
+ # GH#35211
+ basename = "df_mangle_dup_col_dtypes"
+ dtype_dict = {"a": object, **dtypes}
+ dtype_dict_copy = dtype_dict.copy()
+ # GH#42462
+ result = pd.read_excel(basename + read_ext, dtype=dtype_dict)
+ expected = DataFrame(
+ {
+ "a": Series([1], dtype=object),
+ "a.1": Series([exp_value], dtype=object if not dtypes else None),
+ }
+ )
+ assert dtype_dict == dtype_dict_copy, "dtype dict changed"
+ tm.assert_frame_equal(result, expected)
+
+ def test_reader_spaces(self, read_ext):
+ # see gh-32207
+ basename = "test_spaces"
+
+ actual = pd.read_excel(basename + read_ext)
+ expected = DataFrame(
+ {
+ "testcol": [
+ "this is great",
+ "4 spaces",
+ "1 trailing ",
+ " 1 leading",
+ "2 spaces multiple times",
+ ]
+ }
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ # gh-36122, gh-35802
+ @pytest.mark.parametrize(
+ "basename,expected",
+ [
+ ("gh-35802", DataFrame({"COLUMN": ["Test (1)"]})),
+ ("gh-36122", DataFrame(columns=["got 2nd sa"])),
+ ],
+ )
+ def test_read_excel_ods_nested_xml(self, engine, read_ext, basename, expected):
+ # see gh-35802
+ if engine != "odf":
+ pytest.skip(f"Skipped for engine: {engine}")
+
+ actual = pd.read_excel(basename + read_ext)
+ tm.assert_frame_equal(actual, expected)
+
+ def test_reading_all_sheets(self, read_ext):
+ # Test reading all sheet names by setting sheet_name to None,
+ # Ensure a dict is returned.
+ # See PR #9450
+ basename = "test_multisheet"
+ dfs = pd.read_excel(basename + read_ext, sheet_name=None)
+ # ensure this is not alphabetical to test order preservation
+ expected_keys = ["Charlie", "Alpha", "Beta"]
+ tm.assert_contains_all(expected_keys, dfs.keys())
+ # Issue 9930
+ # Ensure sheet order is preserved
+ assert expected_keys == list(dfs.keys())
+
+ def test_reading_multiple_specific_sheets(self, read_ext):
+ # Test reading specific sheet names by specifying a mixed list
+ # of integers and strings, and confirm that duplicated sheet
+ # references (positions/names) are removed properly.
+ # Ensure a dict is returned
+ # See PR #9450
+ basename = "test_multisheet"
+ # Explicitly request duplicates. Only the set should be returned.
+ expected_keys = [2, "Charlie", "Charlie"]
+ dfs = pd.read_excel(basename + read_ext, sheet_name=expected_keys)
+ expected_keys = list(set(expected_keys))
+ tm.assert_contains_all(expected_keys, dfs.keys())
+ assert len(expected_keys) == len(dfs.keys())
+
+ def test_reading_all_sheets_with_blank(self, read_ext):
+ # Test reading all sheet names by setting sheet_name to None,
+ # In the case where some sheets are blank.
+ # Issue #11711
+ basename = "blank_with_header"
+ dfs = pd.read_excel(basename + read_ext, sheet_name=None)
+ expected_keys = ["Sheet1", "Sheet2", "Sheet3"]
+ tm.assert_contains_all(expected_keys, dfs.keys())
+
+ # GH6403
+ def test_read_excel_blank(self, read_ext):
+ actual = pd.read_excel("blank" + read_ext, sheet_name="Sheet1")
+ tm.assert_frame_equal(actual, DataFrame())
+
+ def test_read_excel_blank_with_header(self, read_ext):
+ expected = DataFrame(columns=["col_1", "col_2"])
+ actual = pd.read_excel("blank_with_header" + read_ext, sheet_name="Sheet1")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_exception_message_includes_sheet_name(self, read_ext):
+ # GH 48706
+ with pytest.raises(ValueError, match=r" \(sheet: Sheet1\)$"):
+ pd.read_excel("blank_with_header" + read_ext, header=[1], sheet_name=None)
+ with pytest.raises(ZeroDivisionError, match=r" \(sheet: Sheet1\)$"):
+ pd.read_excel("test1" + read_ext, usecols=lambda x: 1 / 0, sheet_name=None)
+
+ @pytest.mark.filterwarnings("ignore:Cell A4 is marked:UserWarning:openpyxl")
+ def test_date_conversion_overflow(self, request, engine, read_ext):
+ # GH 10001 : pandas.ExcelFile ignore parse_dates=False
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ expected = DataFrame(
+ [
+ [pd.Timestamp("2016-03-12"), "Marc Johnson"],
+ [pd.Timestamp("2016-03-16"), "Jack Black"],
+ [1e20, "Timothy Brown"],
+ ],
+ columns=["DateColWithBigInt", "StringCol"],
+ )
+
+ if engine == "openpyxl":
+ request.applymarker(
+ pytest.mark.xfail(reason="Maybe not supported by openpyxl")
+ )
+
+ if engine is None and read_ext in (".xlsx", ".xlsm"):
+ # GH 35029
+ request.applymarker(
+ pytest.mark.xfail(reason="Defaults to openpyxl, maybe not supported")
+ )
+
+ result = pd.read_excel("testdateoverflow" + read_ext)
+ tm.assert_frame_equal(result, expected)
+
+ def test_sheet_name(self, request, read_ext, engine, df_ref):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ filename = "test1"
+ sheet_name = "Sheet1"
+
+ expected = df_ref
+ adjust_expected(expected, read_ext, engine)
+
+ df1 = pd.read_excel(
+ filename + read_ext, sheet_name=sheet_name, index_col=0
+ ) # doc
+ df2 = pd.read_excel(filename + read_ext, index_col=0, sheet_name=sheet_name)
+
+ tm.assert_frame_equal(df1, expected)
+ tm.assert_frame_equal(df2, expected)
+
+ def test_excel_read_buffer(self, read_ext):
+ pth = "test1" + read_ext
+ expected = pd.read_excel(pth, sheet_name="Sheet1", index_col=0)
+ with open(pth, "rb") as f:
+ actual = pd.read_excel(f, sheet_name="Sheet1", index_col=0)
+ tm.assert_frame_equal(expected, actual)
+
+ def test_bad_engine_raises(self):
+ bad_engine = "foo"
+ with pytest.raises(ValueError, match="Unknown engine: foo"):
+ pd.read_excel("", engine=bad_engine)
+
+ @pytest.mark.parametrize(
+ "sheet_name",
+ [3, [0, 3], [3, 0], "Sheet4", ["Sheet1", "Sheet4"], ["Sheet4", "Sheet1"]],
+ )
+ def test_bad_sheetname_raises(self, read_ext, sheet_name):
+ # GH 39250
+ msg = "Worksheet index 3 is invalid|Worksheet named 'Sheet4' not found"
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel("blank" + read_ext, sheet_name=sheet_name)
+
+ def test_missing_file_raises(self, read_ext):
+ bad_file = f"foo{read_ext}"
+ # CI tests with other languages, translates to "No such file or directory"
+ match = "|".join(
+ [
+ "(No such file or directory",
+ "没有那个文件或目录",
+ "File o directory non esistente)",
+ ]
+ )
+ with pytest.raises(FileNotFoundError, match=match):
+ pd.read_excel(bad_file)
+
+ def test_corrupt_bytes_raises(self, engine):
+ bad_stream = b"foo"
+ if engine is None:
+ error = ValueError
+ msg = (
+ "Excel file format cannot be determined, you must "
+ "specify an engine manually."
+ )
+ elif engine == "xlrd":
+ from xlrd import XLRDError
+
+ error = XLRDError
+ msg = (
+ "Unsupported format, or corrupt file: Expected BOF record; found b'foo'"
+ )
+ elif engine == "calamine":
+ from python_calamine import CalamineError
+
+ error = CalamineError
+ msg = "Cannot detect file format"
+ else:
+ error = BadZipFile
+ msg = "File is not a zip file"
+ with pytest.raises(error, match=msg):
+ pd.read_excel(BytesIO(bad_stream))
+
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_read_from_http_url(self, httpserver, read_ext):
+ with open("test1" + read_ext, "rb") as f:
+ httpserver.serve_content(content=f.read())
+ url_table = pd.read_excel(httpserver.url)
+ local_table = pd.read_excel("test1" + read_ext)
+ tm.assert_frame_equal(url_table, local_table)
+
+ @td.skip_if_not_us_locale
+ @pytest.mark.single_cpu
+ def test_read_from_s3_url(self, read_ext, s3_bucket_public, s3so):
+ with open("test1" + read_ext, "rb") as f:
+ s3_bucket_public.put_object(Key="test1" + read_ext, Body=f)
+
+ url = f"s3://{s3_bucket_public.name}/test1" + read_ext
+
+ url_table = pd.read_excel(url, storage_options=s3so)
+ local_table = pd.read_excel("test1" + read_ext)
+ tm.assert_frame_equal(url_table, local_table)
+
+ @pytest.mark.single_cpu
+ def test_read_from_s3_object(self, read_ext, s3_bucket_public, s3so):
+ # GH 38788
+ with open("test1" + read_ext, "rb") as f:
+ s3_bucket_public.put_object(Key="test1" + read_ext, Body=f)
+
+ import s3fs
+
+ s3 = s3fs.S3FileSystem(**s3so)
+
+ with s3.open(f"s3://{s3_bucket_public.name}/test1" + read_ext) as f:
+ url_table = pd.read_excel(f)
+
+ local_table = pd.read_excel("test1" + read_ext)
+ tm.assert_frame_equal(url_table, local_table)
+
+ @pytest.mark.slow
+ def test_read_from_file_url(self, read_ext, datapath):
+ # FILE
+ localtable = os.path.join(datapath("io", "data", "excel"), "test1" + read_ext)
+ local_table = pd.read_excel(localtable)
+
+ try:
+ url_table = pd.read_excel("file://localhost/" + localtable)
+ except URLError:
+ # fails on some systems
+ platform_info = " ".join(platform.uname()).strip()
+ pytest.skip(f"failing on {platform_info}")
+
+ tm.assert_frame_equal(url_table, local_table)
+
+ def test_read_from_pathlib_path(self, read_ext):
+ # GH12655
+ str_path = "test1" + read_ext
+ expected = pd.read_excel(str_path, sheet_name="Sheet1", index_col=0)
+
+ path_obj = Path("test1" + read_ext)
+ actual = pd.read_excel(path_obj, sheet_name="Sheet1", index_col=0)
+
+ tm.assert_frame_equal(expected, actual)
+
+ def test_close_from_py_localpath(self, read_ext):
+ # GH31467
+ str_path = os.path.join("test1" + read_ext)
+ with open(str_path, "rb") as f:
+ x = pd.read_excel(f, sheet_name="Sheet1", index_col=0)
+ del x
+ # should not throw an exception because the passed file was closed
+ f.read()
+
+ def test_reader_seconds(self, request, engine, read_ext):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ # GH 55045
+ if engine == "calamine" and read_ext == ".ods":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="ODS file contains bad datetime (seconds as text)"
+ )
+ )
+
+ # Test reading times with and without milliseconds. GH5945.
+ expected = DataFrame.from_dict(
+ {
+ "Time": [
+ time(1, 2, 3),
+ time(2, 45, 56, 100000),
+ time(4, 29, 49, 200000),
+ time(6, 13, 42, 300000),
+ time(7, 57, 35, 400000),
+ time(9, 41, 28, 500000),
+ time(11, 25, 21, 600000),
+ time(13, 9, 14, 700000),
+ time(14, 53, 7, 800000),
+ time(16, 37, 0, 900000),
+ time(18, 20, 54),
+ ]
+ }
+ )
+
+ actual = pd.read_excel("times_1900" + read_ext, sheet_name="Sheet1")
+ tm.assert_frame_equal(actual, expected)
+
+ actual = pd.read_excel("times_1904" + read_ext, sheet_name="Sheet1")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_read_excel_multiindex(self, request, engine, read_ext):
+ # see gh-4679
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ unit = get_exp_unit(read_ext, engine)
+
+ mi = MultiIndex.from_product([["foo", "bar"], ["a", "b"]])
+ mi_file = "testmultiindex" + read_ext
+
+ # "mi_column" sheet
+ expected = DataFrame(
+ [
+ [1, 2.5, pd.Timestamp("2015-01-01"), True],
+ [2, 3.5, pd.Timestamp("2015-01-02"), False],
+ [3, 4.5, pd.Timestamp("2015-01-03"), False],
+ [4, 5.5, pd.Timestamp("2015-01-04"), True],
+ ],
+ columns=mi,
+ )
+ expected[mi[2]] = expected[mi[2]].astype(f"M8[{unit}]")
+
+ actual = pd.read_excel(
+ mi_file, sheet_name="mi_column", header=[0, 1], index_col=0
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ # "mi_index" sheet
+ expected.index = mi
+ expected.columns = ["a", "b", "c", "d"]
+
+ actual = pd.read_excel(mi_file, sheet_name="mi_index", index_col=[0, 1])
+ tm.assert_frame_equal(actual, expected)
+
+ # "both" sheet
+ expected.columns = mi
+
+ actual = pd.read_excel(
+ mi_file, sheet_name="both", index_col=[0, 1], header=[0, 1]
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ # "mi_index_name" sheet
+ expected.columns = ["a", "b", "c", "d"]
+ expected.index = mi.set_names(["ilvl1", "ilvl2"])
+
+ actual = pd.read_excel(mi_file, sheet_name="mi_index_name", index_col=[0, 1])
+ tm.assert_frame_equal(actual, expected)
+
+ # "mi_column_name" sheet
+ expected.index = range(4)
+ expected.columns = mi.set_names(["c1", "c2"])
+ actual = pd.read_excel(
+ mi_file, sheet_name="mi_column_name", header=[0, 1], index_col=0
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ # see gh-11317
+ # "name_with_int" sheet
+ expected.columns = mi.set_levels([1, 2], level=1).set_names(["c1", "c2"])
+
+ actual = pd.read_excel(
+ mi_file, sheet_name="name_with_int", index_col=0, header=[0, 1]
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ # "both_name" sheet
+ expected.columns = mi.set_names(["c1", "c2"])
+ expected.index = mi.set_names(["ilvl1", "ilvl2"])
+
+ actual = pd.read_excel(
+ mi_file, sheet_name="both_name", index_col=[0, 1], header=[0, 1]
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ # "both_skiprows" sheet
+ actual = pd.read_excel(
+ mi_file,
+ sheet_name="both_name_skiprows",
+ index_col=[0, 1],
+ header=[0, 1],
+ skiprows=2,
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ @pytest.mark.parametrize(
+ "sheet_name,idx_lvl2",
+ [
+ ("both_name_blank_after_mi_name", [np.nan, "b", "a", "b"]),
+ ("both_name_multiple_blanks", [np.nan] * 4),
+ ],
+ )
+ def test_read_excel_multiindex_blank_after_name(
+ self, request, engine, read_ext, sheet_name, idx_lvl2
+ ):
+ # GH34673
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ mi_file = "testmultiindex" + read_ext
+ mi = MultiIndex.from_product([["foo", "bar"], ["a", "b"]], names=["c1", "c2"])
+
+ unit = get_exp_unit(read_ext, engine)
+ expected = DataFrame(
+ [
+ [1, 2.5, pd.Timestamp("2015-01-01"), True],
+ [2, 3.5, pd.Timestamp("2015-01-02"), False],
+ [3, 4.5, pd.Timestamp("2015-01-03"), False],
+ [4, 5.5, pd.Timestamp("2015-01-04"), True],
+ ],
+ columns=mi,
+ index=MultiIndex.from_arrays(
+ (["foo", "foo", "bar", "bar"], idx_lvl2),
+ names=["ilvl1", "ilvl2"],
+ ),
+ )
+ expected[mi[2]] = expected[mi[2]].astype(f"M8[{unit}]")
+ result = pd.read_excel(
+ mi_file,
+ sheet_name=sheet_name,
+ index_col=[0, 1],
+ header=[0, 1],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_read_excel_multiindex_header_only(self, read_ext):
+ # see gh-11733.
+ #
+ # Don't try to parse a header name if there isn't one.
+ mi_file = "testmultiindex" + read_ext
+ result = pd.read_excel(mi_file, sheet_name="index_col_none", header=[0, 1])
+
+ exp_columns = MultiIndex.from_product([("A", "B"), ("key", "val")])
+ expected = DataFrame([[1, 2, 3, 4]] * 2, columns=exp_columns)
+ tm.assert_frame_equal(result, expected)
+
+ def test_excel_old_index_format(self, read_ext):
+ # see gh-4679
+ filename = "test_index_name_pre17" + read_ext
+
+ # We detect headers to determine if index names exist, so
+ # that "index" name in the "names" version of the data will
+ # now be interpreted as rows that include null data.
+ data = np.array(
+ [
+ [np.nan, np.nan, np.nan, np.nan, np.nan],
+ ["R0C0", "R0C1", "R0C2", "R0C3", "R0C4"],
+ ["R1C0", "R1C1", "R1C2", "R1C3", "R1C4"],
+ ["R2C0", "R2C1", "R2C2", "R2C3", "R2C4"],
+ ["R3C0", "R3C1", "R3C2", "R3C3", "R3C4"],
+ ["R4C0", "R4C1", "R4C2", "R4C3", "R4C4"],
+ ],
+ dtype=object,
+ )
+ columns = ["C_l0_g0", "C_l0_g1", "C_l0_g2", "C_l0_g3", "C_l0_g4"]
+ mi = MultiIndex(
+ levels=[
+ ["R0", "R_l0_g0", "R_l0_g1", "R_l0_g2", "R_l0_g3", "R_l0_g4"],
+ ["R1", "R_l1_g0", "R_l1_g1", "R_l1_g2", "R_l1_g3", "R_l1_g4"],
+ ],
+ codes=[[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]],
+ names=[None, None],
+ )
+ si = Index(
+ ["R0", "R_l0_g0", "R_l0_g1", "R_l0_g2", "R_l0_g3", "R_l0_g4"], name=None
+ )
+
+ expected = DataFrame(data, index=si, columns=columns)
+
+ actual = pd.read_excel(filename, sheet_name="single_names", index_col=0)
+ tm.assert_frame_equal(actual, expected)
+
+ expected.index = mi
+
+ actual = pd.read_excel(filename, sheet_name="multi_names", index_col=[0, 1])
+ tm.assert_frame_equal(actual, expected)
+
+ # The analogous versions of the "names" version data
+ # where there are explicitly no names for the indices.
+ data = np.array(
+ [
+ ["R0C0", "R0C1", "R0C2", "R0C3", "R0C4"],
+ ["R1C0", "R1C1", "R1C2", "R1C3", "R1C4"],
+ ["R2C0", "R2C1", "R2C2", "R2C3", "R2C4"],
+ ["R3C0", "R3C1", "R3C2", "R3C3", "R3C4"],
+ ["R4C0", "R4C1", "R4C2", "R4C3", "R4C4"],
+ ]
+ )
+ columns = ["C_l0_g0", "C_l0_g1", "C_l0_g2", "C_l0_g3", "C_l0_g4"]
+ mi = MultiIndex(
+ levels=[
+ ["R_l0_g0", "R_l0_g1", "R_l0_g2", "R_l0_g3", "R_l0_g4"],
+ ["R_l1_g0", "R_l1_g1", "R_l1_g2", "R_l1_g3", "R_l1_g4"],
+ ],
+ codes=[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]],
+ names=[None, None],
+ )
+ si = Index(["R_l0_g0", "R_l0_g1", "R_l0_g2", "R_l0_g3", "R_l0_g4"], name=None)
+
+ expected = DataFrame(data, index=si, columns=columns)
+
+ actual = pd.read_excel(filename, sheet_name="single_no_names", index_col=0)
+ tm.assert_frame_equal(actual, expected)
+
+ expected.index = mi
+
+ actual = pd.read_excel(filename, sheet_name="multi_no_names", index_col=[0, 1])
+ tm.assert_frame_equal(actual, expected)
+
+ def test_read_excel_bool_header_arg(self, read_ext):
+ # GH 6114
+ msg = "Passing a bool to header is invalid"
+ for arg in [True, False]:
+ with pytest.raises(TypeError, match=msg):
+ pd.read_excel("test1" + read_ext, header=arg)
+
+ def test_read_excel_skiprows(self, request, engine, read_ext):
+ # GH 4903
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ unit = get_exp_unit(read_ext, engine)
+
+ actual = pd.read_excel(
+ "testskiprows" + read_ext, sheet_name="skiprows_list", skiprows=[0, 2]
+ )
+ expected = DataFrame(
+ [
+ [1, 2.5, pd.Timestamp("2015-01-01"), True],
+ [2, 3.5, pd.Timestamp("2015-01-02"), False],
+ [3, 4.5, pd.Timestamp("2015-01-03"), False],
+ [4, 5.5, pd.Timestamp("2015-01-04"), True],
+ ],
+ columns=["a", "b", "c", "d"],
+ )
+ expected["c"] = expected["c"].astype(f"M8[{unit}]")
+ tm.assert_frame_equal(actual, expected)
+
+ actual = pd.read_excel(
+ "testskiprows" + read_ext,
+ sheet_name="skiprows_list",
+ skiprows=np.array([0, 2]),
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ # GH36435
+ actual = pd.read_excel(
+ "testskiprows" + read_ext,
+ sheet_name="skiprows_list",
+ skiprows=lambda x: x in [0, 2],
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ actual = pd.read_excel(
+ "testskiprows" + read_ext,
+ sheet_name="skiprows_list",
+ skiprows=3,
+ names=["a", "b", "c", "d"],
+ )
+ expected = DataFrame(
+ [
+ # [1, 2.5, pd.Timestamp("2015-01-01"), True],
+ [2, 3.5, pd.Timestamp("2015-01-02"), False],
+ [3, 4.5, pd.Timestamp("2015-01-03"), False],
+ [4, 5.5, pd.Timestamp("2015-01-04"), True],
+ ],
+ columns=["a", "b", "c", "d"],
+ )
+ expected["c"] = expected["c"].astype(f"M8[{unit}]")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_read_excel_skiprows_callable_not_in(self, request, engine, read_ext):
+ # GH 4903
+ xfail_datetimes_with_pyxlsb(engine, request)
+ unit = get_exp_unit(read_ext, engine)
+
+ actual = pd.read_excel(
+ "testskiprows" + read_ext,
+ sheet_name="skiprows_list",
+ skiprows=lambda x: x not in [1, 3, 5],
+ )
+ expected = DataFrame(
+ [
+ [1, 2.5, pd.Timestamp("2015-01-01"), True],
+ # [2, 3.5, pd.Timestamp("2015-01-02"), False],
+ [3, 4.5, pd.Timestamp("2015-01-03"), False],
+ # [4, 5.5, pd.Timestamp("2015-01-04"), True],
+ ],
+ columns=["a", "b", "c", "d"],
+ )
+ expected["c"] = expected["c"].astype(f"M8[{unit}]")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_read_excel_nrows(self, read_ext):
+ # GH 16645
+ num_rows_to_pull = 5
+ actual = pd.read_excel("test1" + read_ext, nrows=num_rows_to_pull)
+ expected = pd.read_excel("test1" + read_ext)
+ expected = expected[:num_rows_to_pull]
+ tm.assert_frame_equal(actual, expected)
+
+ def test_read_excel_nrows_greater_than_nrows_in_file(self, read_ext):
+ # GH 16645
+ expected = pd.read_excel("test1" + read_ext)
+ num_records_in_file = len(expected)
+ num_rows_to_pull = num_records_in_file + 10
+ actual = pd.read_excel("test1" + read_ext, nrows=num_rows_to_pull)
+ tm.assert_frame_equal(actual, expected)
+
+ def test_read_excel_nrows_non_integer_parameter(self, read_ext):
+ # GH 16645
+ msg = "'nrows' must be an integer >=0"
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel("test1" + read_ext, nrows="5")
+
+ @pytest.mark.parametrize(
+ "filename,sheet_name,header,index_col,skiprows",
+ [
+ ("testmultiindex", "mi_column", [0, 1], 0, None),
+ ("testmultiindex", "mi_index", None, [0, 1], None),
+ ("testmultiindex", "both", [0, 1], [0, 1], None),
+ ("testmultiindex", "mi_column_name", [0, 1], 0, None),
+ ("testskiprows", "skiprows_list", None, None, [0, 2]),
+ ("testskiprows", "skiprows_list", None, None, lambda x: x in (0, 2)),
+ ],
+ )
+ def test_read_excel_nrows_params(
+ self, read_ext, filename, sheet_name, header, index_col, skiprows
+ ):
+ """
+ For various parameters, we should get the same result whether we
+ limit the rows during load (nrows=3) or after (df.iloc[:3]).
+ """
+ # GH 46894
+ expected = pd.read_excel(
+ filename + read_ext,
+ sheet_name=sheet_name,
+ header=header,
+ index_col=index_col,
+ skiprows=skiprows,
+ ).iloc[:3]
+ actual = pd.read_excel(
+ filename + read_ext,
+ sheet_name=sheet_name,
+ header=header,
+ index_col=index_col,
+ skiprows=skiprows,
+ nrows=3,
+ )
+ tm.assert_frame_equal(actual, expected)
+
+ def test_deprecated_kwargs(self, read_ext):
+ with pytest.raises(TypeError, match="but 3 positional arguments"):
+ pd.read_excel("test1" + read_ext, "Sheet1", 0)
+
+ def test_no_header_with_list_index_col(self, read_ext):
+ # GH 31783
+ file_name = "testmultiindex" + read_ext
+ data = [("B", "B"), ("key", "val"), (3, 4), (3, 4)]
+ idx = MultiIndex.from_tuples(
+ [("A", "A"), ("key", "val"), (1, 2), (1, 2)], names=(0, 1)
+ )
+ expected = DataFrame(data, index=idx, columns=(2, 3))
+ result = pd.read_excel(
+ file_name, sheet_name="index_col_none", index_col=[0, 1], header=None
+ )
+ tm.assert_frame_equal(expected, result)
+
+ def test_one_col_noskip_blank_line(self, read_ext):
+ # GH 39808
+ file_name = "one_col_blank_line" + read_ext
+ data = [0.5, np.nan, 1, 2]
+ expected = DataFrame(data, columns=["numbers"])
+ result = pd.read_excel(file_name)
+ tm.assert_frame_equal(result, expected)
+
+ def test_multiheader_two_blank_lines(self, read_ext):
+ # GH 40442
+ file_name = "testmultiindex" + read_ext
+ columns = MultiIndex.from_tuples([("a", "A"), ("b", "B")])
+ data = [[np.nan, np.nan], [np.nan, np.nan], [1, 3], [2, 4]]
+ expected = DataFrame(data, columns=columns)
+ result = pd.read_excel(
+ file_name, sheet_name="mi_column_empty_rows", header=[0, 1]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_trailing_blanks(self, read_ext):
+ """
+ Sheets can contain blank cells with no data. Some of our readers
+ were including those cells, creating many empty rows and columns
+ """
+ file_name = "trailing_blanks" + read_ext
+ result = pd.read_excel(file_name)
+ assert result.shape == (3, 3)
+
+ def test_ignore_chartsheets_by_str(self, request, engine, read_ext):
+ # GH 41448
+ if read_ext == ".ods":
+ pytest.skip("chartsheets do not exist in the ODF format")
+ if engine == "pyxlsb":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="pyxlsb can't distinguish chartsheets from worksheets"
+ )
+ )
+ with pytest.raises(ValueError, match="Worksheet named 'Chart1' not found"):
+ pd.read_excel("chartsheet" + read_ext, sheet_name="Chart1")
+
+ def test_ignore_chartsheets_by_int(self, request, engine, read_ext):
+ # GH 41448
+ if read_ext == ".ods":
+ pytest.skip("chartsheets do not exist in the ODF format")
+ if engine == "pyxlsb":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="pyxlsb can't distinguish chartsheets from worksheets"
+ )
+ )
+ with pytest.raises(
+ ValueError, match="Worksheet index 1 is invalid, 1 worksheets found"
+ ):
+ pd.read_excel("chartsheet" + read_ext, sheet_name=1)
+
+ def test_euro_decimal_format(self, read_ext):
+ # copied from read_csv
+ result = pd.read_excel("test_decimal" + read_ext, decimal=",", skiprows=1)
+ expected = DataFrame(
+ [
+ [1, 1521.1541, 187101.9543, "ABC", "poi", 4.738797819],
+ [2, 121.12, 14897.76, "DEF", "uyt", 0.377320872],
+ [3, 878.158, 108013.434, "GHI", "rez", 2.735694704],
+ ],
+ columns=["Id", "Number1", "Number2", "Text1", "Text2", "Number3"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+class TestExcelFileRead:
+ def test_raises_bytes_input(self, engine, read_ext):
+ # GH 53830
+ msg = "Expected file path name or file-like object"
+ with pytest.raises(TypeError, match=msg):
+ with open("test1" + read_ext, "rb") as f:
+ pd.read_excel(f.read(), engine=engine)
+
+ @pytest.fixture(autouse=True)
+ def cd_and_set_engine(self, engine, datapath, monkeypatch):
+ """
+ Change directory and set engine for ExcelFile objects.
+ """
+ func = partial(pd.ExcelFile, engine=engine)
+ monkeypatch.chdir(datapath("io", "data", "excel"))
+ monkeypatch.setattr(pd, "ExcelFile", func)
+
+ def test_engine_used(self, read_ext, engine):
+ expected_defaults = {
+ "xlsx": "openpyxl",
+ "xlsm": "openpyxl",
+ "xlsb": "pyxlsb",
+ "xls": "xlrd",
+ "ods": "odf",
+ }
+
+ with pd.ExcelFile("test1" + read_ext) as excel:
+ result = excel.engine
+
+ if engine is not None:
+ expected = engine
+ else:
+ expected = expected_defaults[read_ext[1:]]
+ assert result == expected
+
+ def test_excel_passes_na(self, read_ext):
+ with pd.ExcelFile("test4" + read_ext) as excel:
+ parsed = pd.read_excel(
+ excel, sheet_name="Sheet1", keep_default_na=False, na_values=["apple"]
+ )
+ expected = DataFrame(
+ [["NA"], [1], ["NA"], [np.nan], ["rabbit"]], columns=["Test"]
+ )
+ tm.assert_frame_equal(parsed, expected)
+
+ with pd.ExcelFile("test4" + read_ext) as excel:
+ parsed = pd.read_excel(
+ excel, sheet_name="Sheet1", keep_default_na=True, na_values=["apple"]
+ )
+ expected = DataFrame(
+ [[np.nan], [1], [np.nan], [np.nan], ["rabbit"]], columns=["Test"]
+ )
+ tm.assert_frame_equal(parsed, expected)
+
+ # 13967
+ with pd.ExcelFile("test5" + read_ext) as excel:
+ parsed = pd.read_excel(
+ excel, sheet_name="Sheet1", keep_default_na=False, na_values=["apple"]
+ )
+ expected = DataFrame(
+ [["1.#QNAN"], [1], ["nan"], [np.nan], ["rabbit"]], columns=["Test"]
+ )
+ tm.assert_frame_equal(parsed, expected)
+
+ with pd.ExcelFile("test5" + read_ext) as excel:
+ parsed = pd.read_excel(
+ excel, sheet_name="Sheet1", keep_default_na=True, na_values=["apple"]
+ )
+ expected = DataFrame(
+ [[np.nan], [1], [np.nan], [np.nan], ["rabbit"]], columns=["Test"]
+ )
+ tm.assert_frame_equal(parsed, expected)
+
+ @pytest.mark.parametrize("na_filter", [None, True, False])
+ def test_excel_passes_na_filter(self, read_ext, na_filter):
+ # gh-25453
+ kwargs = {}
+
+ if na_filter is not None:
+ kwargs["na_filter"] = na_filter
+
+ with pd.ExcelFile("test5" + read_ext) as excel:
+ parsed = pd.read_excel(
+ excel,
+ sheet_name="Sheet1",
+ keep_default_na=True,
+ na_values=["apple"],
+ **kwargs,
+ )
+
+ if na_filter is False:
+ expected = [["1.#QNAN"], [1], ["nan"], ["apple"], ["rabbit"]]
+ else:
+ expected = [[np.nan], [1], [np.nan], [np.nan], ["rabbit"]]
+
+ expected = DataFrame(expected, columns=["Test"])
+ tm.assert_frame_equal(parsed, expected)
+
+ def test_excel_table_sheet_by_index(self, request, engine, read_ext, df_ref):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ expected = df_ref
+ adjust_expected(expected, read_ext, engine)
+
+ with pd.ExcelFile("test1" + read_ext) as excel:
+ df1 = pd.read_excel(excel, sheet_name=0, index_col=0)
+ df2 = pd.read_excel(excel, sheet_name=1, skiprows=[1], index_col=0)
+ tm.assert_frame_equal(df1, expected)
+ tm.assert_frame_equal(df2, expected)
+
+ with pd.ExcelFile("test1" + read_ext) as excel:
+ df1 = excel.parse(0, index_col=0)
+ df2 = excel.parse(1, skiprows=[1], index_col=0)
+ tm.assert_frame_equal(df1, expected)
+ tm.assert_frame_equal(df2, expected)
+
+ with pd.ExcelFile("test1" + read_ext) as excel:
+ df3 = pd.read_excel(excel, sheet_name=0, index_col=0, skipfooter=1)
+ tm.assert_frame_equal(df3, df1.iloc[:-1])
+
+ with pd.ExcelFile("test1" + read_ext) as excel:
+ df3 = excel.parse(0, index_col=0, skipfooter=1)
+
+ tm.assert_frame_equal(df3, df1.iloc[:-1])
+
+ def test_sheet_name(self, request, engine, read_ext, df_ref):
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ expected = df_ref
+ adjust_expected(expected, read_ext, engine)
+
+ filename = "test1"
+ sheet_name = "Sheet1"
+
+ with pd.ExcelFile(filename + read_ext) as excel:
+ df1_parse = excel.parse(sheet_name=sheet_name, index_col=0) # doc
+
+ with pd.ExcelFile(filename + read_ext) as excel:
+ df2_parse = excel.parse(index_col=0, sheet_name=sheet_name)
+
+ tm.assert_frame_equal(df1_parse, expected)
+ tm.assert_frame_equal(df2_parse, expected)
+
+ @pytest.mark.parametrize(
+ "sheet_name",
+ [3, [0, 3], [3, 0], "Sheet4", ["Sheet1", "Sheet4"], ["Sheet4", "Sheet1"]],
+ )
+ def test_bad_sheetname_raises(self, read_ext, sheet_name):
+ # GH 39250
+ msg = "Worksheet index 3 is invalid|Worksheet named 'Sheet4' not found"
+ with pytest.raises(ValueError, match=msg):
+ with pd.ExcelFile("blank" + read_ext) as excel:
+ excel.parse(sheet_name=sheet_name)
+
+ def test_excel_read_buffer(self, engine, read_ext):
+ pth = "test1" + read_ext
+ expected = pd.read_excel(pth, sheet_name="Sheet1", index_col=0, engine=engine)
+
+ with open(pth, "rb") as f:
+ with pd.ExcelFile(f) as xls:
+ actual = pd.read_excel(xls, sheet_name="Sheet1", index_col=0)
+
+ tm.assert_frame_equal(expected, actual)
+
+ def test_reader_closes_file(self, engine, read_ext):
+ with open("test1" + read_ext, "rb") as f:
+ with pd.ExcelFile(f) as xlsx:
+ # parses okay
+ pd.read_excel(xlsx, sheet_name="Sheet1", index_col=0, engine=engine)
+
+ assert f.closed
+
+ def test_conflicting_excel_engines(self, read_ext):
+ # GH 26566
+ msg = "Engine should not be specified when passing an ExcelFile"
+
+ with pd.ExcelFile("test1" + read_ext) as xl:
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel(xl, engine="foo")
+
+ def test_excel_read_binary(self, engine, read_ext):
+ # GH 15914
+ expected = pd.read_excel("test1" + read_ext, engine=engine)
+
+ with open("test1" + read_ext, "rb") as f:
+ data = f.read()
+
+ actual = pd.read_excel(BytesIO(data), engine=engine)
+ tm.assert_frame_equal(expected, actual)
+
+ def test_excel_read_binary_via_read_excel(self, read_ext, engine):
+ # GH 38424
+ with open("test1" + read_ext, "rb") as f:
+ result = pd.read_excel(f, engine=engine)
+ expected = pd.read_excel("test1" + read_ext, engine=engine)
+ tm.assert_frame_equal(result, expected)
+
+ def test_read_excel_header_index_out_of_range(self, engine):
+ # GH#43143
+ with open("df_header_oob.xlsx", "rb") as f:
+ with pytest.raises(ValueError, match="exceeds maximum"):
+ pd.read_excel(f, header=[0, 1])
+
+ @pytest.mark.parametrize("filename", ["df_empty.xlsx", "df_equals.xlsx"])
+ def test_header_with_index_col(self, filename):
+ # GH 33476
+ idx = Index(["Z"], name="I2")
+ cols = MultiIndex.from_tuples([("A", "B"), ("A", "B.1")], names=["I11", "I12"])
+ expected = DataFrame([[1, 3]], index=idx, columns=cols, dtype="int64")
+ result = pd.read_excel(
+ filename, sheet_name="Sheet1", index_col=0, header=[0, 1]
+ )
+ tm.assert_frame_equal(expected, result)
+
+ def test_read_datetime_multiindex(self, request, engine, read_ext):
+ # GH 34748
+ xfail_datetimes_with_pyxlsb(engine, request)
+
+ f = "test_datetime_mi" + read_ext
+ with pd.ExcelFile(f) as excel:
+ actual = pd.read_excel(excel, header=[0, 1], index_col=0, engine=engine)
+
+ unit = get_exp_unit(read_ext, engine)
+
+ dti = pd.DatetimeIndex(["2020-02-29", "2020-03-01"], dtype=f"M8[{unit}]")
+ expected_column_index = MultiIndex.from_arrays(
+ [dti[:1], dti[1:]],
+ names=[
+ dti[0].to_pydatetime(),
+ dti[1].to_pydatetime(),
+ ],
+ )
+ expected = DataFrame([], index=[], columns=expected_column_index)
+
+ tm.assert_frame_equal(expected, actual)
+
+ def test_engine_invalid_option(self, read_ext):
+ # read_ext includes the '.' hence the weird formatting
+ with pytest.raises(ValueError, match="Value must be one of *"):
+ with pd.option_context(f"io.excel{read_ext}.reader", "abc"):
+ pass
+
+ def test_ignore_chartsheets(self, request, engine, read_ext):
+ # GH 41448
+ if read_ext == ".ods":
+ pytest.skip("chartsheets do not exist in the ODF format")
+ if engine == "pyxlsb":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="pyxlsb can't distinguish chartsheets from worksheets"
+ )
+ )
+ with pd.ExcelFile("chartsheet" + read_ext) as excel:
+ assert excel.sheet_names == ["Sheet1"]
+
+ def test_corrupt_files_closed(self, engine, tmp_excel):
+ # GH41778
+ errors = (BadZipFile,)
+ if engine is None:
+ pytest.skip(f"Invalid test for engine={engine}")
+ elif engine == "xlrd":
+ import xlrd
+
+ errors = (BadZipFile, xlrd.biffh.XLRDError)
+ elif engine == "calamine":
+ from python_calamine import CalamineError
+
+ errors = (CalamineError,)
+
+ Path(tmp_excel).write_text("corrupt", encoding="utf-8")
+ with tm.assert_produces_warning(False):
+ try:
+ pd.ExcelFile(tmp_excel, engine=engine)
+ except errors:
+ pass
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_style.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_style.py
new file mode 100644
index 0000000000000000000000000000000000000000..5afa2cae6a56cb87879d218a616f62788b50c103
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_style.py
@@ -0,0 +1,356 @@
+import contextlib
+import uuid
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Timestamp,
+ period_range,
+ read_excel,
+)
+import pandas._testing as tm
+
+from pandas.io.excel import ExcelWriter
+from pandas.io.formats.excel import ExcelFormatter
+
+pytest.importorskip("jinja2")
+# jinja2 is currently required for Styler.__init__(). Technically Styler.to_excel
+# could compute styles and render to excel without jinja2, since there is no
+# 'template' file, but this needs the import error to delayed until render time.
+
+
+@pytest.fixture
+def tmp_excel(tmp_path):
+ tmp = tmp_path / f"{uuid.uuid4()}.xlsx"
+ tmp.touch()
+ return str(tmp)
+
+
+def assert_equal_cell_styles(cell1, cell2):
+ # TODO: should find a better way to check equality
+ assert cell1.alignment.__dict__ == cell2.alignment.__dict__
+ assert cell1.border.__dict__ == cell2.border.__dict__
+ assert cell1.fill.__dict__ == cell2.fill.__dict__
+ assert cell1.font.__dict__ == cell2.font.__dict__
+ assert cell1.number_format == cell2.number_format
+ assert cell1.protection.__dict__ == cell2.protection.__dict__
+
+
+def test_styler_default_values(tmp_excel):
+ # GH 54154
+ openpyxl = pytest.importorskip("openpyxl")
+ df = DataFrame([{"A": 1, "B": 2, "C": 3}, {"A": 1, "B": 2, "C": 3}])
+
+ with ExcelWriter(tmp_excel, engine="openpyxl") as writer:
+ df.to_excel(writer, sheet_name="custom")
+
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ # Check font, spacing, indentation
+ assert wb["custom"].cell(1, 1).font.bold is False
+ assert wb["custom"].cell(1, 1).alignment.horizontal is None
+ assert wb["custom"].cell(1, 1).alignment.vertical is None
+
+ # Check border
+ assert wb["custom"].cell(1, 1).border.bottom.color is None
+ assert wb["custom"].cell(1, 1).border.top.color is None
+ assert wb["custom"].cell(1, 1).border.left.color is None
+ assert wb["custom"].cell(1, 1).border.right.color is None
+
+
+@pytest.mark.parametrize("engine", ["xlsxwriter", "openpyxl"])
+def test_styler_to_excel_unstyled(engine, tmp_excel):
+ # compare DataFrame.to_excel and Styler.to_excel when no styles applied
+ pytest.importorskip(engine)
+ df = DataFrame(np.random.default_rng(2).standard_normal((2, 2)))
+ with ExcelWriter(tmp_excel, engine=engine) as writer:
+ df.to_excel(writer, sheet_name="dataframe")
+ df.style.to_excel(writer, sheet_name="unstyled")
+
+ openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ for col1, col2 in zip(
+ wb["dataframe"].columns,
+ wb["unstyled"].columns,
+ strict=True,
+ ):
+ assert len(col1) == len(col2)
+ for cell1, cell2 in zip(col1, col2, strict=True):
+ assert cell1.value == cell2.value
+ assert_equal_cell_styles(cell1, cell2)
+
+
+shared_style_params = [
+ (
+ "background-color: #111222",
+ ["fill", "fgColor", "rgb"],
+ {"xlsxwriter": "FF111222", "openpyxl": "00111222"},
+ ),
+ (
+ "color: #111222",
+ ["font", "color", "value"],
+ {"xlsxwriter": "FF111222", "openpyxl": "00111222"},
+ ),
+ ("font-family: Arial;", ["font", "name"], "arial"),
+ ("font-weight: bold;", ["font", "b"], True),
+ ("font-style: italic;", ["font", "i"], True),
+ ("text-decoration: underline;", ["font", "u"], "single"),
+ ("number-format: $??,???.00;", ["number_format"], "$??,???.00"),
+ ("text-align: left;", ["alignment", "horizontal"], "left"),
+ (
+ "vertical-align: bottom;",
+ ["alignment", "vertical"],
+ {"xlsxwriter": None, "openpyxl": "bottom"}, # xlsxwriter Fails
+ ),
+ ("vertical-align: middle;", ["alignment", "vertical"], "center"),
+ # Border widths
+ ("border-left: 2pt solid red", ["border", "left", "style"], "medium"),
+ ("border-left: 1pt dotted red", ["border", "left", "style"], "dotted"),
+ ("border-left: 2pt dotted red", ["border", "left", "style"], "mediumDashDotDot"),
+ ("border-left: 1pt dashed red", ["border", "left", "style"], "dashed"),
+ ("border-left: 2pt dashed red", ["border", "left", "style"], "mediumDashed"),
+ ("border-left: 1pt solid red", ["border", "left", "style"], "thin"),
+ ("border-left: 3pt solid red", ["border", "left", "style"], "thick"),
+ # Border expansion
+ (
+ "border-left: 2pt solid #111222",
+ ["border", "left", "color", "rgb"],
+ {"xlsxwriter": "FF111222", "openpyxl": "00111222"},
+ ),
+ ("border: 1pt solid red", ["border", "top", "style"], "thin"),
+ (
+ "border: 1pt solid #111222",
+ ["border", "top", "color", "rgb"],
+ {"xlsxwriter": "FF111222", "openpyxl": "00111222"},
+ ),
+ ("border: 1pt solid red", ["border", "right", "style"], "thin"),
+ (
+ "border: 1pt solid #111222",
+ ["border", "right", "color", "rgb"],
+ {"xlsxwriter": "FF111222", "openpyxl": "00111222"},
+ ),
+ ("border: 1pt solid red", ["border", "bottom", "style"], "thin"),
+ (
+ "border: 1pt solid #111222",
+ ["border", "bottom", "color", "rgb"],
+ {"xlsxwriter": "FF111222", "openpyxl": "00111222"},
+ ),
+ ("border: 1pt solid red", ["border", "left", "style"], "thin"),
+ (
+ "border: 1pt solid #111222",
+ ["border", "left", "color", "rgb"],
+ {"xlsxwriter": "FF111222", "openpyxl": "00111222"},
+ ),
+ # Border styles
+ (
+ "border-left-style: hair; border-left-color: black",
+ ["border", "left", "style"],
+ "hair",
+ ),
+]
+
+
+def test_styler_custom_style(tmp_excel):
+ # GH 54154
+ css_style = "background-color: #111222"
+ openpyxl = pytest.importorskip("openpyxl")
+ df = DataFrame([{"A": 1, "B": 2}, {"A": 1, "B": 2}])
+
+ with ExcelWriter(tmp_excel, engine="openpyxl") as writer:
+ styler = df.style.map(lambda x: css_style)
+ styler.to_excel(writer, sheet_name="custom", index=False)
+
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ # Check font, spacing, indentation
+ assert wb["custom"].cell(1, 1).font.bold is False
+ assert wb["custom"].cell(1, 1).alignment.horizontal is None
+ assert wb["custom"].cell(1, 1).alignment.vertical is None
+
+ # Check border
+ assert wb["custom"].cell(1, 1).border.bottom.color is None
+ assert wb["custom"].cell(1, 1).border.top.color is None
+ assert wb["custom"].cell(1, 1).border.left.color is None
+ assert wb["custom"].cell(1, 1).border.right.color is None
+
+ # Check background color
+ assert wb["custom"].cell(2, 1).fill.fgColor.index == "00111222"
+ assert wb["custom"].cell(3, 1).fill.fgColor.index == "00111222"
+ assert wb["custom"].cell(2, 2).fill.fgColor.index == "00111222"
+ assert wb["custom"].cell(3, 2).fill.fgColor.index == "00111222"
+
+
+@pytest.mark.parametrize("engine", ["xlsxwriter", "openpyxl"])
+@pytest.mark.parametrize("css, attrs, expected", shared_style_params)
+def test_styler_to_excel_basic(engine, css, attrs, expected, tmp_excel):
+ pytest.importorskip(engine)
+ df = DataFrame(np.random.default_rng(2).standard_normal((1, 1)))
+ styler = df.style.map(lambda x: css)
+
+ with ExcelWriter(tmp_excel, engine=engine) as writer:
+ df.to_excel(writer, sheet_name="dataframe")
+ styler.to_excel(writer, sheet_name="styled")
+
+ openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ # test unstyled data cell does not have expected styles
+ # test styled cell has expected styles
+ u_cell, s_cell = wb["dataframe"].cell(2, 2), wb["styled"].cell(2, 2)
+ for attr in attrs:
+ u_cell, s_cell = getattr(u_cell, attr, None), getattr(s_cell, attr)
+
+ if isinstance(expected, dict):
+ assert u_cell is None or u_cell != expected[engine]
+ assert s_cell == expected[engine]
+ else:
+ assert u_cell is None or u_cell != expected
+ assert s_cell == expected
+
+
+@pytest.mark.parametrize("engine", ["xlsxwriter", "openpyxl"])
+@pytest.mark.parametrize("css, attrs, expected", shared_style_params)
+def test_styler_to_excel_basic_indexes(engine, css, attrs, expected, tmp_excel):
+ pytest.importorskip(engine)
+ df = DataFrame(np.random.default_rng(2).standard_normal((1, 1)))
+
+ styler = df.style
+ styler.map_index(lambda x: css, axis=0)
+ styler.map_index(lambda x: css, axis=1)
+
+ null_styler = df.style
+ null_styler.map(lambda x: "null: css;")
+ null_styler.map_index(lambda x: "null: css;", axis=0)
+ null_styler.map_index(lambda x: "null: css;", axis=1)
+
+ with ExcelWriter(tmp_excel, engine=engine) as writer:
+ null_styler.to_excel(writer, sheet_name="null_styled")
+ styler.to_excel(writer, sheet_name="styled")
+
+ openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ # test null styled index cells does not have expected styles
+ # test styled cell has expected styles
+ ui_cell, si_cell = wb["null_styled"].cell(2, 1), wb["styled"].cell(2, 1)
+ uc_cell, sc_cell = wb["null_styled"].cell(1, 2), wb["styled"].cell(1, 2)
+ for attr in attrs:
+ ui_cell, si_cell = getattr(ui_cell, attr, None), getattr(si_cell, attr)
+ uc_cell, sc_cell = getattr(uc_cell, attr, None), getattr(sc_cell, attr)
+
+ if isinstance(expected, dict):
+ assert ui_cell is None or ui_cell != expected[engine]
+ assert si_cell == expected[engine]
+ assert uc_cell is None or uc_cell != expected[engine]
+ assert sc_cell == expected[engine]
+ else:
+ assert ui_cell is None or ui_cell != expected
+ assert si_cell == expected
+ assert uc_cell is None or uc_cell != expected
+ assert sc_cell == expected
+
+
+# From https://openpyxl.readthedocs.io/en/stable/api/openpyxl.styles.borders.html
+# Note: Leaving behavior of "width"-type styles undefined; user should use border-width
+# instead
+excel_border_styles = [
+ # "thin",
+ "dashed",
+ "mediumDashDot",
+ "dashDotDot",
+ "hair",
+ "dotted",
+ "mediumDashDotDot",
+ # "medium",
+ "double",
+ "dashDot",
+ "slantDashDot",
+ # "thick",
+ "mediumDashed",
+]
+
+
+@pytest.mark.parametrize("engine", ["xlsxwriter", "openpyxl"])
+@pytest.mark.parametrize("border_style", excel_border_styles)
+def test_styler_to_excel_border_style(engine, border_style, tmp_excel):
+ css = f"border-left: {border_style} black thin"
+ attrs = ["border", "left", "style"]
+ expected = border_style
+
+ pytest.importorskip(engine)
+ df = DataFrame(np.random.default_rng(2).standard_normal((1, 1)))
+ styler = df.style.map(lambda x: css)
+
+ with ExcelWriter(tmp_excel, engine=engine) as writer:
+ df.to_excel(writer, sheet_name="dataframe")
+ styler.to_excel(writer, sheet_name="styled")
+
+ openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ # test unstyled data cell does not have expected styles
+ # test styled cell has expected styles
+ u_cell, s_cell = wb["dataframe"].cell(2, 2), wb["styled"].cell(2, 2)
+ for attr in attrs:
+ u_cell, s_cell = getattr(u_cell, attr, None), getattr(s_cell, attr)
+
+ if isinstance(expected, dict):
+ assert u_cell is None or u_cell != expected[engine]
+ assert s_cell == expected[engine]
+ else:
+ assert u_cell is None or u_cell != expected
+ assert s_cell == expected
+
+
+def test_styler_custom_converter(tmp_excel):
+ openpyxl = pytest.importorskip("openpyxl")
+
+ def custom_converter(css):
+ return {"font": {"color": {"rgb": "111222"}}}
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((1, 1)))
+ styler = df.style.map(lambda x: "color: #888999")
+ with ExcelWriter(tmp_excel, engine="openpyxl") as writer:
+ ExcelFormatter(styler, style_converter=custom_converter).write(
+ writer, sheet_name="custom"
+ )
+
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ assert wb["custom"].cell(2, 2).font.color.value == "00111222"
+
+
+@pytest.mark.single_cpu
+@td.skip_if_not_us_locale
+def test_styler_to_s3(s3_bucket_public, s3so):
+ # GH#46381
+ mock_bucket_name = s3_bucket_public.name
+ target_file = f"{uuid.uuid4()}.xlsx"
+ df = DataFrame({"x": [1, 2, 3], "y": [2, 4, 6]})
+ styler = df.style.set_sticky(axis="index")
+ uri = f"s3://{mock_bucket_name}/{target_file}"
+ styler.to_excel(uri, storage_options=s3so)
+ result = read_excel(uri, index_col=0, storage_options=s3so)
+ tm.assert_frame_equal(result, df)
+
+
+@pytest.mark.parametrize("merge_cells", [True, False, "columns"])
+def test_format_hierarchical_rows_periodindex(merge_cells):
+ # GH#60099
+ df = DataFrame(
+ {"A": [1, 2]},
+ index=MultiIndex.from_arrays(
+ [
+ period_range(start="2006-10-06", end="2006-10-07", freq="D"),
+ ["X", "Y"],
+ ],
+ names=["date", "category"],
+ ),
+ )
+ formatter = ExcelFormatter(df, merge_cells=merge_cells)
+ formatted_cells = formatter._format_hierarchical_rows()
+
+ for cell in formatted_cells:
+ if cell.row != 0 and cell.col == 0:
+ assert isinstance(cell.val, Timestamp), (
+ "Period should be converted to Timestamp"
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_writers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_writers.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6a26f0ca027c673c16d7263103d57736880f94c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_writers.py
@@ -0,0 +1,1765 @@
+import contextlib
+from datetime import (
+ date,
+ datetime,
+ timedelta,
+)
+from decimal import Decimal
+from functools import partial
+from io import BytesIO
+import os
+import pathlib
+import re
+import uuid
+
+import numpy as np
+import pytest
+
+from pandas.compat._optional import import_optional_dependency
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ date_range,
+ option_context,
+ period_range,
+)
+import pandas._testing as tm
+
+from pandas.io.excel import (
+ ExcelFile,
+ ExcelWriter,
+ _OpenpyxlWriter,
+ _XlsxWriter,
+ register_writer,
+)
+from pandas.io.excel._util import _writers
+
+
+def get_exp_unit(path: str) -> str:
+ return "us"
+
+
+@pytest.fixture
+def frame(float_frame):
+ """
+ Returns the first ten items in fixture "float_frame".
+ """
+ return float_frame[:10]
+
+
+@pytest.fixture(params=[True, False, "columns"])
+def merge_cells(request):
+ return request.param
+
+
+@pytest.fixture
+def tmp_excel(ext, tmp_path):
+ """
+ Fixture to open file for use in each test case.
+ """
+ tmp = tmp_path / f"{uuid.uuid4()}{ext}"
+ tmp.touch()
+ return str(tmp)
+
+
+@pytest.fixture
+def set_engine(engine, ext):
+ """
+ Fixture to set engine for use in each test case.
+
+ Rather than requiring `engine=...` to be provided explicitly as an
+ argument in each test, this fixture sets a global option to dictate
+ which engine should be used to write Excel files. After executing
+ the test it rolls back said change to the global option.
+ """
+ option_name = f"io.excel.{ext.strip('.')}.writer"
+ with option_context(option_name, engine):
+ yield
+
+
+@pytest.mark.parametrize(
+ "ext",
+ [
+ pytest.param(".xlsx", marks=[td.skip_if_no("openpyxl"), td.skip_if_no("xlrd")]),
+ pytest.param(".xlsm", marks=[td.skip_if_no("openpyxl"), td.skip_if_no("xlrd")]),
+ pytest.param(
+ ".xlsx", marks=[td.skip_if_no("xlsxwriter"), td.skip_if_no("xlrd")]
+ ),
+ pytest.param(".ods", marks=td.skip_if_no("odf")),
+ ],
+)
+class TestRoundTrip:
+ @pytest.mark.parametrize(
+ "header,expected",
+ [(None, [np.nan] * 4), (0, {"Unnamed: 0": [np.nan] * 3})],
+ )
+ def test_read_one_empty_col_no_header(self, tmp_excel, header, expected):
+ # xref gh-12292
+ filename = "no_header"
+ df = DataFrame([["", 1, 100], ["", 2, 200], ["", 3, 300], ["", 4, 400]])
+
+ df.to_excel(tmp_excel, sheet_name=filename, index=False, header=False)
+ result = pd.read_excel(
+ tmp_excel, sheet_name=filename, usecols=[0], header=header
+ )
+ expected = DataFrame(expected)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "header,expected_extra",
+ [(None, [0]), (0, [])],
+ )
+ def test_read_one_empty_col_with_header(self, tmp_excel, header, expected_extra):
+ filename = "with_header"
+ df = DataFrame([["", 1, 100], ["", 2, 200], ["", 3, 300], ["", 4, 400]])
+
+ df.to_excel(tmp_excel, sheet_name="with_header", index=False, header=True)
+ result = pd.read_excel(
+ tmp_excel, sheet_name=filename, usecols=[0], header=header
+ )
+ expected = DataFrame(expected_extra + [np.nan] * 4)
+ tm.assert_frame_equal(result, expected)
+
+ def test_set_column_names_in_parameter(self, tmp_excel):
+ # GH 12870 : pass down column names associated with
+ # keyword argument names
+ refdf = DataFrame([[1, "foo"], [2, "bar"], [3, "baz"]], columns=["a", "b"])
+
+ with ExcelWriter(tmp_excel) as writer:
+ refdf.to_excel(writer, sheet_name="Data_no_head", header=False, index=False)
+ refdf.to_excel(writer, sheet_name="Data_with_head", index=False)
+
+ refdf.columns = ["A", "B"]
+
+ with ExcelFile(tmp_excel) as reader:
+ xlsdf_no_head = pd.read_excel(
+ reader, sheet_name="Data_no_head", header=None, names=["A", "B"]
+ )
+ xlsdf_with_head = pd.read_excel(
+ reader,
+ sheet_name="Data_with_head",
+ index_col=None,
+ names=["A", "B"],
+ )
+
+ tm.assert_frame_equal(xlsdf_no_head, refdf)
+ tm.assert_frame_equal(xlsdf_with_head, refdf)
+
+ def test_creating_and_reading_multiple_sheets(self, tmp_excel):
+ # see gh-9450
+ #
+ # Test reading multiple sheets, from a runtime
+ # created Excel file with multiple sheets.
+ def tdf(col_sheet_name):
+ d, i = [11, 22, 33], [1, 2, 3]
+ return DataFrame(d, i, columns=[col_sheet_name])
+
+ sheets = ["AAA", "BBB", "CCC"]
+
+ dfs = [tdf(s) for s in sheets]
+ dfs = dict(zip(sheets, dfs))
+
+ with ExcelWriter(tmp_excel) as ew:
+ for sheetname, df in dfs.items():
+ df.to_excel(ew, sheet_name=sheetname)
+
+ dfs_returned = pd.read_excel(tmp_excel, sheet_name=sheets, index_col=0)
+
+ for s in sheets:
+ tm.assert_frame_equal(dfs[s], dfs_returned[s])
+
+ def test_read_excel_multiindex_empty_level(self, tmp_excel):
+ # see gh-12453
+ df = DataFrame(
+ {
+ ("One", "x"): {0: 1},
+ ("Two", "X"): {0: 3},
+ ("Two", "Y"): {0: 7},
+ ("Zero", ""): {0: 0},
+ }
+ )
+
+ expected = DataFrame(
+ {
+ ("One", "x"): {0: 1},
+ ("Two", "X"): {0: 3},
+ ("Two", "Y"): {0: 7},
+ ("Zero", "Unnamed: 4_level_1"): {0: 0},
+ }
+ )
+
+ df.to_excel(tmp_excel)
+ actual = pd.read_excel(tmp_excel, header=[0, 1], index_col=0)
+ tm.assert_frame_equal(actual, expected)
+
+ df = DataFrame(
+ {
+ ("Beg", ""): {0: 0},
+ ("Middle", "x"): {0: 1},
+ ("Tail", "X"): {0: 3},
+ ("Tail", "Y"): {0: 7},
+ }
+ )
+
+ expected = DataFrame(
+ {
+ ("Beg", "Unnamed: 1_level_1"): {0: 0},
+ ("Middle", "x"): {0: 1},
+ ("Tail", "X"): {0: 3},
+ ("Tail", "Y"): {0: 7},
+ }
+ )
+
+ df.to_excel(tmp_excel)
+ actual = pd.read_excel(tmp_excel, header=[0, 1], index_col=0)
+ tm.assert_frame_equal(actual, expected)
+
+ @pytest.mark.parametrize("c_idx_names", ["a", None])
+ @pytest.mark.parametrize("r_idx_names", ["b", None])
+ @pytest.mark.parametrize("c_idx_levels", [1, 3])
+ @pytest.mark.parametrize("r_idx_levels", [1, 3])
+ def test_excel_multindex_roundtrip(
+ self,
+ tmp_excel,
+ c_idx_names,
+ r_idx_names,
+ c_idx_levels,
+ r_idx_levels,
+ ):
+ # see gh-4679
+ # Empty name case current read in as
+ # unnamed levels, not Nones.
+ check_names = bool(r_idx_names) or r_idx_levels <= 1
+
+ if c_idx_levels == 1:
+ columns = Index(list("abcde"))
+ else:
+ columns = MultiIndex.from_arrays(
+ [range(5) for _ in range(c_idx_levels)],
+ names=[f"{c_idx_names}-{i}" for i in range(c_idx_levels)],
+ )
+ if r_idx_levels == 1:
+ index = Index(list("ghijk"))
+ else:
+ index = MultiIndex.from_arrays(
+ [range(5) for _ in range(r_idx_levels)],
+ names=[f"{r_idx_names}-{i}" for i in range(r_idx_levels)],
+ )
+ df = DataFrame(
+ 1.1 * np.ones((5, 5)),
+ columns=columns,
+ index=index,
+ )
+ df.to_excel(tmp_excel)
+
+ act = pd.read_excel(
+ tmp_excel,
+ index_col=list(range(r_idx_levels)),
+ header=list(range(c_idx_levels)),
+ )
+ tm.assert_frame_equal(df, act, check_names=check_names)
+
+ df.iloc[0, :] = np.nan
+ df.to_excel(tmp_excel)
+
+ act = pd.read_excel(
+ tmp_excel,
+ index_col=list(range(r_idx_levels)),
+ header=list(range(c_idx_levels)),
+ )
+ tm.assert_frame_equal(df, act, check_names=check_names)
+
+ df.iloc[-1, :] = np.nan
+ df.to_excel(tmp_excel)
+ act = pd.read_excel(
+ tmp_excel,
+ index_col=list(range(r_idx_levels)),
+ header=list(range(c_idx_levels)),
+ )
+ tm.assert_frame_equal(df, act, check_names=check_names)
+
+ def test_read_excel_parse_dates(self, tmp_excel):
+ # see gh-11544, gh-12051
+ df = DataFrame(
+ {"col": [1, 2, 3], "date_strings": date_range("2012-01-01", periods=3)}
+ )
+ df2 = df.copy()
+ df2["date_strings"] = df2["date_strings"].dt.strftime("%m/%d/%Y")
+
+ df2.to_excel(tmp_excel)
+
+ res = pd.read_excel(tmp_excel, index_col=0)
+ tm.assert_frame_equal(df2, res)
+
+ res = pd.read_excel(tmp_excel, parse_dates=["date_strings"], index_col=0)
+ expected = df[:]
+ expected["date_strings"] = expected["date_strings"].astype("M8[us]")
+ tm.assert_frame_equal(res, expected)
+
+ res = pd.read_excel(
+ tmp_excel, parse_dates=["date_strings"], date_format="%m/%d/%Y", index_col=0
+ )
+ expected["date_strings"] = expected["date_strings"].astype("M8[us]")
+ tm.assert_frame_equal(expected, res)
+
+ def test_multiindex_interval_datetimes(self, tmp_excel):
+ # GH 30986
+ midx = MultiIndex.from_arrays(
+ [
+ range(4),
+ pd.interval_range(
+ start=pd.Timestamp("2020-01-01"), periods=4, freq="6ME"
+ ),
+ ]
+ )
+ df = DataFrame(range(4), index=midx)
+ df.to_excel(tmp_excel)
+ result = pd.read_excel(tmp_excel, index_col=[0, 1])
+ expected = DataFrame(
+ range(4),
+ MultiIndex.from_arrays(
+ [
+ range(4),
+ [
+ "(2020-01-31 00:00:00, 2020-07-31 00:00:00]",
+ "(2020-07-31 00:00:00, 2021-01-31 00:00:00]",
+ "(2021-01-31 00:00:00, 2021-07-31 00:00:00]",
+ "(2021-07-31 00:00:00, 2022-01-31 00:00:00]",
+ ],
+ ]
+ ),
+ columns=Index([0]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("merge_cells", [True, False, "columns"])
+ def test_excel_round_trip_with_periodindex(self, tmp_excel, merge_cells):
+ # GH#60099
+ df = DataFrame(
+ {"A": [1, 2]},
+ index=MultiIndex.from_arrays(
+ [
+ period_range(start="2006-10-06", end="2006-10-07", freq="D"),
+ ["X", "Y"],
+ ],
+ names=["date", "category"],
+ ),
+ )
+ df.to_excel(tmp_excel, merge_cells=merge_cells)
+ result = pd.read_excel(tmp_excel, index_col=[0, 1])
+ expected = DataFrame(
+ {"A": [1, 2]},
+ MultiIndex.from_arrays(
+ [
+ [
+ pd.to_datetime("2006-10-06 00:00:00").as_unit("s"),
+ pd.to_datetime("2006-10-07 00:00:00").as_unit("s"),
+ ],
+ ["X", "Y"],
+ ],
+ names=["date", "category"],
+ ),
+ )
+ time_format = "datetime64[us]"
+ expected.index = expected.index.set_levels(
+ expected.index.levels[0].astype(time_format), level=0
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "engine,ext",
+ [
+ pytest.param(
+ "openpyxl",
+ ".xlsx",
+ marks=[td.skip_if_no("openpyxl"), td.skip_if_no("xlrd")],
+ ),
+ pytest.param(
+ "openpyxl",
+ ".xlsm",
+ marks=[td.skip_if_no("openpyxl"), td.skip_if_no("xlrd")],
+ ),
+ pytest.param(
+ "xlsxwriter",
+ ".xlsx",
+ marks=[td.skip_if_no("xlsxwriter"), td.skip_if_no("xlrd")],
+ ),
+ pytest.param("odf", ".ods", marks=td.skip_if_no("odf")),
+ ],
+)
+@pytest.mark.usefixtures("set_engine")
+class TestExcelWriter:
+ def test_excel_sheet_size(self, tmp_excel):
+ # GH 26080
+ breaking_row_count = 2**20 + 1
+ breaking_col_count = 2**14 + 1
+ # purposely using two arrays to prevent memory issues while testing
+ row_arr = np.zeros(shape=(breaking_row_count, 1))
+ col_arr = np.zeros(shape=(1, breaking_col_count))
+ row_df = DataFrame(row_arr)
+ col_df = DataFrame(col_arr)
+
+ msg = "sheet is too large"
+ with pytest.raises(ValueError, match=msg):
+ row_df.to_excel(tmp_excel)
+
+ with pytest.raises(ValueError, match=msg):
+ col_df.to_excel(tmp_excel)
+
+ def test_excel_sheet_by_name_raise(self, tmp_excel):
+ gt = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ index=Index(list(range(10))),
+ )
+ gt.to_excel(tmp_excel)
+
+ with ExcelFile(tmp_excel) as xl:
+ df = pd.read_excel(xl, sheet_name=0, index_col=0)
+
+ tm.assert_frame_equal(gt, df)
+
+ msg = "Worksheet named '0' not found"
+ with pytest.raises(ValueError, match=msg):
+ pd.read_excel(xl, "0")
+
+ def test_excel_writer_context_manager(self, frame, tmp_excel):
+ with ExcelWriter(tmp_excel) as writer:
+ frame.to_excel(writer, sheet_name="Data1")
+ frame2 = frame.copy()
+ frame2.columns = frame.columns[::-1]
+ frame2.to_excel(writer, sheet_name="Data2")
+
+ with ExcelFile(tmp_excel) as reader:
+ found_df = pd.read_excel(reader, sheet_name="Data1", index_col=0)
+ found_df2 = pd.read_excel(reader, sheet_name="Data2", index_col=0)
+
+ tm.assert_frame_equal(found_df, frame)
+ tm.assert_frame_equal(found_df2, frame2)
+
+ def test_roundtrip(self, frame, tmp_excel):
+ frame = frame.copy()
+ frame.iloc[:5, frame.columns.get_loc("A")] = np.nan
+
+ frame.to_excel(tmp_excel, sheet_name="test1")
+ frame.to_excel(tmp_excel, sheet_name="test1", columns=["A", "B"])
+ frame.to_excel(tmp_excel, sheet_name="test1", header=False)
+ frame.to_excel(tmp_excel, sheet_name="test1", index=False)
+
+ # test roundtrip
+ frame.to_excel(tmp_excel, sheet_name="test1")
+ recons = pd.read_excel(tmp_excel, sheet_name="test1", index_col=0)
+ tm.assert_frame_equal(frame, recons)
+
+ frame.to_excel(tmp_excel, sheet_name="test1", index=False)
+ recons = pd.read_excel(tmp_excel, sheet_name="test1", index_col=None)
+ recons.index = frame.index
+ tm.assert_frame_equal(frame, recons)
+
+ frame.to_excel(tmp_excel, sheet_name="test1", na_rep="NA")
+ recons = pd.read_excel(
+ tmp_excel, sheet_name="test1", index_col=0, na_values=["NA"]
+ )
+ tm.assert_frame_equal(frame, recons)
+
+ # GH 3611
+ frame.to_excel(tmp_excel, sheet_name="test1", na_rep="88")
+ recons = pd.read_excel(
+ tmp_excel, sheet_name="test1", index_col=0, na_values=["88"]
+ )
+ tm.assert_frame_equal(frame, recons)
+
+ frame.to_excel(tmp_excel, sheet_name="test1", na_rep="88")
+ recons = pd.read_excel(
+ tmp_excel, sheet_name="test1", index_col=0, na_values=[88, 88.0]
+ )
+ tm.assert_frame_equal(frame, recons)
+
+ # GH 6573
+ frame.to_excel(tmp_excel, sheet_name="Sheet1")
+ recons = pd.read_excel(tmp_excel, index_col=0)
+ tm.assert_frame_equal(frame, recons)
+
+ frame.to_excel(tmp_excel, sheet_name="0")
+ recons = pd.read_excel(tmp_excel, index_col=0)
+ tm.assert_frame_equal(frame, recons)
+
+ # GH 8825 Pandas Series should provide to_excel method
+ s = frame["A"]
+ s.to_excel(tmp_excel)
+ recons = pd.read_excel(tmp_excel, index_col=0)
+ tm.assert_frame_equal(s.to_frame(), recons)
+
+ def test_mixed(self, frame, tmp_excel):
+ mixed_frame = frame.copy()
+ mixed_frame["foo"] = "bar"
+
+ mixed_frame.to_excel(tmp_excel, sheet_name="test1")
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
+ tm.assert_frame_equal(mixed_frame, recons)
+
+ def test_ts_frame(self, tmp_excel):
+ unit = get_exp_unit(tmp_excel)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=5, freq="B"),
+ )
+
+ # freq doesn't round-trip
+ index = pd.DatetimeIndex(np.asarray(df.index), freq=None)
+ df.index = index
+
+ expected = df[:]
+ expected.index = expected.index.as_unit(unit)
+
+ df.to_excel(tmp_excel, sheet_name="test1")
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
+ tm.assert_frame_equal(expected, recons)
+
+ def test_basics_with_nan(self, frame, tmp_excel):
+ frame = frame.copy()
+ frame.iloc[:5, frame.columns.get_loc("A")] = np.nan
+ frame.to_excel(tmp_excel, sheet_name="test1")
+ frame.to_excel(tmp_excel, sheet_name="test1", columns=["A", "B"])
+ frame.to_excel(tmp_excel, sheet_name="test1", header=False)
+ frame.to_excel(tmp_excel, sheet_name="test1", index=False)
+
+ @pytest.mark.parametrize("np_type", [np.int8, np.int16, np.int32, np.int64])
+ def test_int_types(self, np_type, tmp_excel):
+ # Test np.int values read come back as int
+ # (rather than float which is Excel's format).
+ df = DataFrame(
+ np.random.default_rng(2).integers(-10, 10, size=(10, 2)),
+ dtype=np_type,
+ index=Index(list(range(10))),
+ )
+ df.to_excel(tmp_excel, sheet_name="test1")
+
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
+
+ int_frame = df.astype(np.int64)
+ tm.assert_frame_equal(int_frame, recons)
+
+ recons2 = pd.read_excel(tmp_excel, sheet_name="test1", index_col=0)
+ tm.assert_frame_equal(int_frame, recons2)
+
+ @pytest.mark.parametrize("np_type", [np.float16, np.float32, np.float64])
+ def test_float_types(self, np_type, tmp_excel):
+ # Test np.float values read come back as float.
+ df = DataFrame(
+ np.random.default_rng(2).random(10),
+ dtype=np_type,
+ index=Index(list(range(10))),
+ )
+ df.to_excel(tmp_excel, sheet_name="test1")
+
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0).astype(
+ np_type
+ )
+
+ tm.assert_frame_equal(df, recons)
+
+ def test_bool_types(self, tmp_excel):
+ # Test np.bool_ values read come back as float.
+ df = DataFrame([1, 0, True, False], dtype=np.bool_, index=Index(list(range(4))))
+ df.to_excel(tmp_excel, sheet_name="test1")
+
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0).astype(
+ np.bool_
+ )
+
+ tm.assert_frame_equal(df, recons)
+
+ def test_inf_roundtrip(self, tmp_excel):
+ df = DataFrame([(1, np.inf), (2, 3), (5, -np.inf)], index=Index(list(range(3))))
+ df.to_excel(tmp_excel, sheet_name="test1")
+
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
+
+ tm.assert_frame_equal(df, recons)
+
+ def test_sheets(self, frame, tmp_excel):
+ # freq doesn't round-trip
+ unit = get_exp_unit(tmp_excel)
+ tsframe = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=5, freq="B"),
+ )
+
+ index = pd.DatetimeIndex(np.asarray(tsframe.index), freq=None)
+ tsframe.index = index
+
+ expected = tsframe[:]
+ expected.index = expected.index.as_unit(unit)
+
+ frame = frame.copy()
+ frame.iloc[:5, frame.columns.get_loc("A")] = np.nan
+
+ frame.to_excel(tmp_excel, sheet_name="test1")
+ frame.to_excel(tmp_excel, sheet_name="test1", columns=["A", "B"])
+ frame.to_excel(tmp_excel, sheet_name="test1", header=False)
+ frame.to_excel(tmp_excel, sheet_name="test1", index=False)
+
+ # Test writing to separate sheets
+ with ExcelWriter(tmp_excel) as writer:
+ frame.to_excel(writer, sheet_name="test1")
+ tsframe.to_excel(writer, sheet_name="test2")
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
+ tm.assert_frame_equal(frame, recons)
+ recons = pd.read_excel(reader, sheet_name="test2", index_col=0)
+ tm.assert_frame_equal(expected, recons)
+ assert 2 == len(reader.sheet_names)
+ assert "test1" == reader.sheet_names[0]
+ assert "test2" == reader.sheet_names[1]
+
+ def test_colaliases(self, frame, tmp_excel):
+ frame = frame.copy()
+ frame.iloc[:5, frame.columns.get_loc("A")] = np.nan
+
+ frame.to_excel(tmp_excel, sheet_name="test1")
+ frame.to_excel(tmp_excel, sheet_name="test1", columns=["A", "B"])
+ frame.to_excel(tmp_excel, sheet_name="test1", header=False)
+ frame.to_excel(tmp_excel, sheet_name="test1", index=False)
+
+ # column aliases
+ col_aliases = Index(["AA", "X", "Y", "Z"])
+ frame.to_excel(tmp_excel, sheet_name="test1", header=col_aliases)
+ with ExcelFile(tmp_excel) as reader:
+ rs = pd.read_excel(reader, sheet_name="test1", index_col=0)
+ xp = frame.copy()
+ xp.columns = col_aliases
+ tm.assert_frame_equal(xp, rs)
+
+ def test_roundtrip_indexlabels(self, merge_cells, frame, tmp_excel):
+ frame = frame.copy()
+ frame.iloc[:5, frame.columns.get_loc("A")] = np.nan
+
+ frame.to_excel(tmp_excel, sheet_name="test1")
+ frame.to_excel(tmp_excel, sheet_name="test1", columns=["A", "B"])
+ frame.to_excel(tmp_excel, sheet_name="test1", header=False)
+ frame.to_excel(tmp_excel, sheet_name="test1", index=False)
+
+ # test index_label
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2))) >= 0
+ df.to_excel(
+ tmp_excel, sheet_name="test1", index_label=["test"], merge_cells=merge_cells
+ )
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0).astype(
+ np.int64
+ )
+ df.index.names = ["test"]
+ assert df.index.names == recons.index.names
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2))) >= 0
+ df.to_excel(
+ tmp_excel,
+ sheet_name="test1",
+ index_label=["test", "dummy", "dummy2"],
+ merge_cells=merge_cells,
+ )
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0).astype(
+ np.int64
+ )
+ df.index.names = ["test"]
+ assert df.index.names == recons.index.names
+
+ df = (
+ DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ index=Index(list(range(10))),
+ )
+ >= 0
+ )
+ df.to_excel(
+ tmp_excel, sheet_name="test1", index_label="test", merge_cells=merge_cells
+ )
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0).astype(
+ np.int64
+ )
+ df.index.names = ["test"]
+ tm.assert_frame_equal(df, recons.astype(bool))
+
+ frame.to_excel(
+ tmp_excel,
+ sheet_name="test1",
+ columns=["A", "B", "C", "D"],
+ index=False,
+ merge_cells=merge_cells,
+ )
+ # take 'A' and 'B' as indexes (same row as cols 'C', 'D')
+ df = frame.copy()
+ df = df.set_index(["A", "B"])
+
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=[0, 1])
+ tm.assert_frame_equal(df, recons)
+
+ def test_excel_roundtrip_indexname(self, merge_cells, tmp_excel):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ df.index.name = "foo"
+
+ df.to_excel(tmp_excel, merge_cells=merge_cells)
+
+ with ExcelFile(tmp_excel) as xf:
+ result = pd.read_excel(xf, sheet_name=xf.sheet_names[0], index_col=0)
+
+ tm.assert_frame_equal(result, df)
+ assert result.index.name == "foo"
+
+ def test_excel_roundtrip_datetime(self, merge_cells, tmp_excel):
+ # datetime.date, not sure what to test here exactly
+ unit = get_exp_unit(tmp_excel)
+
+ # freq does not round-trip
+ tsframe = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=5, freq="B"),
+ )
+ index = pd.DatetimeIndex(np.asarray(tsframe.index), freq=None)
+ tsframe.index = index
+
+ tsf = tsframe.copy()
+
+ tsf.index = [x.date() for x in tsframe.index]
+ tsf.to_excel(tmp_excel, sheet_name="test1", merge_cells=merge_cells)
+
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
+
+ expected = tsframe[:]
+ expected.index = expected.index.as_unit(unit)
+ tm.assert_frame_equal(expected, recons)
+
+ def test_excel_date_datetime_format(self, ext, tmp_excel, tmp_path):
+ # see gh-4133
+ #
+ # Excel output format strings
+ unit = get_exp_unit(tmp_excel)
+ df = DataFrame(
+ [
+ [date(2014, 1, 31), date(1999, 9, 24)],
+ [datetime(1998, 5, 26, 23, 33, 4), datetime(2014, 2, 28, 13, 5, 13)],
+ ],
+ index=["DATE", "DATETIME"],
+ columns=["X", "Y"],
+ )
+ df_expected = DataFrame(
+ [
+ [datetime(2014, 1, 31), datetime(1999, 9, 24)],
+ [datetime(1998, 5, 26, 23, 33, 4), datetime(2014, 2, 28, 13, 5, 13)],
+ ],
+ index=["DATE", "DATETIME"],
+ columns=["X", "Y"],
+ )
+ df_expected = df_expected.astype(f"M8[{unit}]")
+
+ filename2 = tmp_path / f"tmp2{ext}"
+ filename2.touch()
+ with ExcelWriter(tmp_excel) as writer1:
+ df.to_excel(writer1, sheet_name="test1")
+
+ with ExcelWriter(
+ filename2,
+ date_format="DD.MM.YYYY",
+ datetime_format="DD.MM.YYYY HH-MM-SS",
+ ) as writer2:
+ df.to_excel(writer2, sheet_name="test1")
+
+ with ExcelFile(tmp_excel) as reader1:
+ rs1 = pd.read_excel(reader1, sheet_name="test1", index_col=0)
+
+ with ExcelFile(filename2) as reader2:
+ rs2 = pd.read_excel(reader2, sheet_name="test1", index_col=0)
+
+ # TODO: why do we get different units?
+ rs2 = rs2.astype(f"M8[{unit}]")
+
+ tm.assert_frame_equal(rs1, rs2)
+
+ # Since the reader returns a datetime object for dates,
+ # we need to use df_expected to check the result.
+ tm.assert_frame_equal(rs2, df_expected)
+
+ @pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in cast:RuntimeWarning"
+ )
+ def test_to_excel_interval_no_labels(self, tmp_excel, using_infer_string):
+ # see gh-19242
+ #
+ # Test writing Interval without labels.
+ df = DataFrame(
+ np.random.default_rng(2).integers(-10, 10, size=(20, 1)), dtype=np.int64
+ )
+ expected = df.copy()
+
+ df["new"] = pd.cut(df[0], 10)
+ expected["new"] = pd.cut(expected[0], 10).astype(
+ str if not using_infer_string else "str"
+ )
+
+ df.to_excel(tmp_excel, sheet_name="test1")
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
+ tm.assert_frame_equal(expected, recons)
+
+ def test_to_excel_interval_labels(self, tmp_excel):
+ # see gh-19242
+ #
+ # Test writing Interval with labels.
+ df = DataFrame(
+ np.random.default_rng(2).integers(-10, 10, size=(20, 1)), dtype=np.int64
+ )
+ expected = df.copy()
+ intervals = pd.cut(
+ df[0], 10, labels=["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
+ )
+ df["new"] = intervals
+ expected["new"] = pd.Series(list(intervals))
+
+ df.to_excel(tmp_excel, sheet_name="test1")
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
+ tm.assert_frame_equal(expected, recons)
+
+ def test_to_excel_timedelta(self, tmp_excel):
+ # see gh-19242, gh-9155
+ #
+ # Test writing timedelta to xls.
+ df = DataFrame(
+ np.random.default_rng(2).integers(-10, 10, size=(20, 1)),
+ columns=["A"],
+ dtype=np.int64,
+ )
+ expected = df.copy()
+
+ df["new"] = df["A"].apply(lambda x: timedelta(seconds=x))
+ expected["new"] = expected["A"].apply(
+ lambda x: timedelta(seconds=x).total_seconds() / 86400
+ )
+
+ df.to_excel(tmp_excel, sheet_name="test1")
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
+ tm.assert_frame_equal(expected, recons)
+
+ def test_to_excel_periodindex(self, tmp_excel):
+ # xp has a PeriodIndex
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=5, freq="B"),
+ )
+ xp = df.resample("ME").mean().to_period("M")
+
+ xp.to_excel(tmp_excel, sheet_name="sht1")
+
+ with ExcelFile(tmp_excel) as reader:
+ rs = pd.read_excel(reader, sheet_name="sht1", index_col=0)
+ tm.assert_frame_equal(xp, rs.to_period("M"))
+
+ def test_to_excel_multiindex(self, merge_cells, frame, tmp_excel):
+ arrays = np.arange(len(frame.index) * 2, dtype=np.int64).reshape(2, -1)
+ new_index = MultiIndex.from_arrays(arrays, names=["first", "second"])
+ frame.index = new_index
+
+ frame.to_excel(tmp_excel, sheet_name="test1", header=False)
+ frame.to_excel(tmp_excel, sheet_name="test1", columns=["A", "B"])
+
+ # round trip
+ frame.to_excel(tmp_excel, sheet_name="test1", merge_cells=merge_cells)
+ with ExcelFile(tmp_excel) as reader:
+ df = pd.read_excel(reader, sheet_name="test1", index_col=[0, 1])
+ tm.assert_frame_equal(frame, df)
+
+ # GH13511
+ def test_to_excel_multiindex_nan_label(self, merge_cells, tmp_excel):
+ df = DataFrame(
+ {
+ "A": [None, 2, 3],
+ "B": [10, 20, 30],
+ "C": np.random.default_rng(2).random(3),
+ }
+ )
+ df = df.set_index(["A", "B"])
+
+ df.to_excel(tmp_excel, merge_cells=merge_cells)
+ df1 = pd.read_excel(tmp_excel, index_col=[0, 1])
+ tm.assert_frame_equal(df, df1)
+
+ # Test for Issue 11328. If column indices are integers, make
+ # sure they are handled correctly for either setting of
+ # merge_cells
+ def test_to_excel_multiindex_cols(self, merge_cells, tmp_excel):
+ # GH#11328
+ frame = DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [4, 5, 6],
+ "C": [7, 8, 9],
+ }
+ )
+ arrays = np.arange(len(frame.index) * 2, dtype=np.int64).reshape(2, -1)
+ new_index = MultiIndex.from_arrays(arrays, names=["first", "second"])
+ frame.index = new_index
+
+ new_cols_index = MultiIndex.from_tuples([(40, 1), (40, 2), (50, 1)])
+ frame.columns = new_cols_index
+ frame.to_excel(tmp_excel, sheet_name="test1", merge_cells=merge_cells)
+
+ # Check round trip
+ with ExcelFile(tmp_excel) as reader:
+ result = pd.read_excel(
+ reader, sheet_name="test1", header=[0, 1], index_col=[0, 1]
+ )
+ tm.assert_frame_equal(result, frame)
+
+ # GH#60274
+ # Check with header/index_col None to determine which cells were merged
+ with ExcelFile(tmp_excel) as reader:
+ result = pd.read_excel(
+ reader, sheet_name="test1", header=None, index_col=None
+ )
+ expected = DataFrame(
+ {
+ 0: [np.nan, np.nan, "first", 0, 1, 2],
+ 1: [np.nan, np.nan, "second", 3, 4, 5],
+ 2: [40.0, 1.0, np.nan, 1.0, 2.0, 3.0],
+ 3: [np.nan, 2.0, np.nan, 4.0, 5.0, 6.0],
+ 4: [50.0, 1.0, np.nan, 7.0, 8.0, 9.0],
+ }
+ )
+ if not merge_cells:
+ # MultiIndex column value is repeated
+ expected.loc[0, 3] = 40.0
+ tm.assert_frame_equal(result, expected)
+
+ def test_to_excel_multiindex_dates(self, merge_cells, tmp_excel):
+ # try multiindex with dates
+ unit = get_exp_unit(tmp_excel)
+ tsframe = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=5, freq="B"),
+ )
+ tsframe.index = MultiIndex.from_arrays(
+ [
+ tsframe.index.as_unit(unit),
+ np.arange(len(tsframe.index), dtype=np.int64),
+ ],
+ names=["time", "foo"],
+ )
+
+ tsframe.to_excel(tmp_excel, sheet_name="test1", merge_cells=merge_cells)
+ with ExcelFile(tmp_excel) as reader:
+ recons = pd.read_excel(reader, sheet_name="test1", index_col=[0, 1])
+
+ tm.assert_frame_equal(tsframe, recons)
+ assert recons.index.names == ("time", "foo")
+
+ def test_to_excel_multiindex_no_write_index(self, tmp_excel):
+ # Test writing and re-reading a MI without the index. GH 5616.
+
+ # Initial non-MI frame.
+ frame1 = DataFrame({"a": [10, 20], "b": [30, 40], "c": [50, 60]})
+
+ # Add a MI.
+ frame2 = frame1.copy()
+ multi_index = MultiIndex.from_tuples([(70, 80), (90, 100)])
+ frame2.index = multi_index
+
+ # Write out to Excel without the index.
+ frame2.to_excel(tmp_excel, sheet_name="test1", index=False)
+
+ # Read it back in.
+ with ExcelFile(tmp_excel) as reader:
+ frame3 = pd.read_excel(reader, sheet_name="test1")
+
+ # Test that it is the same as the initial frame.
+ tm.assert_frame_equal(frame1, frame3)
+
+ def test_to_excel_empty_multiindex(self, tmp_excel):
+ # GH 19543.
+ expected = DataFrame([], columns=[0, 1, 2])
+
+ df = DataFrame([], index=MultiIndex.from_tuples([], names=[0, 1]), columns=[2])
+ df.to_excel(tmp_excel, sheet_name="test1")
+
+ with ExcelFile(tmp_excel) as reader:
+ result = pd.read_excel(reader, sheet_name="test1")
+ tm.assert_frame_equal(
+ result, expected, check_index_type=False, check_dtype=False
+ )
+
+ def test_to_excel_empty_multiindex_both_axes(self, tmp_excel):
+ # GH 57696
+ df = DataFrame(
+ [],
+ index=MultiIndex.from_tuples([], names=[0, 1]),
+ columns=MultiIndex.from_tuples([("A", "B")]),
+ )
+ df.to_excel(tmp_excel)
+ result = pd.read_excel(tmp_excel, header=[0, 1], index_col=[0, 1])
+ tm.assert_frame_equal(result, df)
+
+ def test_to_excel_float_format(self, tmp_excel):
+ df = DataFrame(
+ [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ index=["A", "B"],
+ columns=["X", "Y", "Z"],
+ )
+ df.to_excel(tmp_excel, sheet_name="test1", float_format="%.2f")
+
+ with ExcelFile(tmp_excel) as reader:
+ result = pd.read_excel(reader, sheet_name="test1", index_col=0)
+
+ expected = DataFrame(
+ [[0.12, 0.23, 0.57], [12.32, 123123.20, 321321.20]],
+ index=["A", "B"],
+ columns=["X", "Y", "Z"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_to_excel_datatypes_preserved(self, tmp_excel):
+ # Test that when writing and reading Excel with dtype=object,
+ # datatypes are preserved, except Decimals which should be
+ # stored as floats
+
+ # see gh-49598
+ df = DataFrame(
+ [
+ [1.23, "1.23", Decimal("1.23")],
+ [4.56, "4.56", Decimal("4.56")],
+ ],
+ index=["A", "B"],
+ columns=["X", "Y", "Z"],
+ )
+ df.to_excel(tmp_excel)
+
+ with ExcelFile(tmp_excel) as reader:
+ result = pd.read_excel(reader, index_col=0, dtype=object)
+
+ expected = DataFrame(
+ [
+ [1.23, "1.23", 1.23],
+ [4.56, "4.56", 4.56],
+ ],
+ index=["A", "B"],
+ columns=["X", "Y", "Z"],
+ dtype=object,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_to_excel_output_encoding(self, tmp_excel):
+ # Avoid mixed inferred_type.
+ df = DataFrame(
+ [["\u0192", "\u0193", "\u0194"], ["\u0195", "\u0196", "\u0197"]],
+ index=["A\u0192", "B"],
+ columns=["X\u0193", "Y", "Z"],
+ )
+
+ df.to_excel(tmp_excel, sheet_name="TestSheet")
+ result = pd.read_excel(tmp_excel, sheet_name="TestSheet", index_col=0)
+ tm.assert_frame_equal(result, df)
+
+ def test_to_excel_unicode_filename(self, ext, tmp_path):
+ filename = tmp_path / f"\u0192u.{ext}"
+ filename.touch()
+ df = DataFrame(
+ [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ index=["A", "B"],
+ columns=["X", "Y", "Z"],
+ )
+ df.to_excel(filename, sheet_name="test1", float_format="%.2f")
+
+ with ExcelFile(filename) as reader:
+ result = pd.read_excel(reader, sheet_name="test1", index_col=0)
+
+ expected = DataFrame(
+ [[0.12, 0.23, 0.57], [12.32, 123123.20, 321321.20]],
+ index=["A", "B"],
+ columns=["X", "Y", "Z"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("use_headers", [True, False])
+ @pytest.mark.parametrize("r_idx_nlevels", [1, 2, 3])
+ @pytest.mark.parametrize("c_idx_nlevels", [1, 2, 3])
+ def test_excel_010_hemstring(
+ self, merge_cells, c_idx_nlevels, r_idx_nlevels, use_headers, tmp_excel
+ ):
+ def roundtrip(data, header=True, parser_hdr=0, index=True):
+ data.to_excel(
+ tmp_excel, header=header, merge_cells=merge_cells, index=index
+ )
+
+ with ExcelFile(tmp_excel) as xf:
+ return pd.read_excel(
+ xf, sheet_name=xf.sheet_names[0], header=parser_hdr
+ )
+
+ # Basic test.
+ parser_header = 0 if use_headers else None
+ res = roundtrip(DataFrame([0]), use_headers, parser_header)
+
+ assert res.shape == (1, 2)
+ assert res.iloc[0, 0] is not np.nan
+
+ # More complex tests with multi-index.
+ nrows = 5
+ ncols = 3
+
+ # ensure limited functionality in 0.10
+ # override of gh-2370 until sorted out in 0.11
+
+ if c_idx_nlevels == 1:
+ columns = Index([f"a-{i}" for i in range(ncols)], dtype=object)
+ else:
+ columns = MultiIndex.from_arrays(
+ [range(ncols) for _ in range(c_idx_nlevels)],
+ names=[f"i-{i}" for i in range(c_idx_nlevels)],
+ )
+ if r_idx_nlevels == 1:
+ index = Index([f"b-{i}" for i in range(nrows)], dtype=object)
+ else:
+ index = MultiIndex.from_arrays(
+ [range(nrows) for _ in range(r_idx_nlevels)],
+ names=[f"j-{i}" for i in range(r_idx_nlevels)],
+ )
+
+ df = DataFrame(
+ np.ones((nrows, ncols)),
+ columns=columns,
+ index=index,
+ )
+
+ # This if will be removed once multi-column Excel writing
+ # is implemented. For now fixing gh-9794.
+ if c_idx_nlevels > 1:
+ msg = (
+ "Writing to Excel with MultiIndex columns and no index "
+ "\\('index'=False\\) is not yet implemented."
+ )
+ with pytest.raises(NotImplementedError, match=msg):
+ roundtrip(df, use_headers, index=False)
+ else:
+ res = roundtrip(df, use_headers)
+
+ if use_headers:
+ assert res.shape == (nrows, ncols + r_idx_nlevels)
+ else:
+ # First row taken as columns.
+ assert res.shape == (nrows - 1, ncols + r_idx_nlevels)
+
+ # No NaNs.
+ for r in range(len(res.index)):
+ for c in range(len(res.columns)):
+ assert res.iloc[r, c] is not np.nan
+
+ def test_duplicated_columns(self, tmp_excel):
+ # see gh-5235
+ df = DataFrame([[1, 2, 3], [1, 2, 3], [1, 2, 3]], columns=["A", "B", "B"])
+ df.to_excel(tmp_excel, sheet_name="test1")
+ expected = DataFrame(
+ [[1, 2, 3], [1, 2, 3], [1, 2, 3]], columns=["A", "B", "B.1"]
+ )
+
+ # By default, we mangle.
+ result = pd.read_excel(tmp_excel, sheet_name="test1", index_col=0)
+ tm.assert_frame_equal(result, expected)
+
+ # see gh-11007, gh-10970
+ df = DataFrame([[1, 2, 3, 4], [5, 6, 7, 8]], columns=["A", "B", "A", "B"])
+ df.to_excel(tmp_excel, sheet_name="test1")
+
+ result = pd.read_excel(tmp_excel, sheet_name="test1", index_col=0)
+ expected = DataFrame(
+ [[1, 2, 3, 4], [5, 6, 7, 8]], columns=["A", "B", "A.1", "B.1"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # see gh-10982
+ df.to_excel(tmp_excel, sheet_name="test1", index=False, header=False)
+ result = pd.read_excel(tmp_excel, sheet_name="test1", header=None)
+
+ expected = DataFrame([[1, 2, 3, 4], [5, 6, 7, 8]])
+ tm.assert_frame_equal(result, expected)
+
+ def test_swapped_columns(self, tmp_excel):
+ # Test for issue #5427.
+ write_frame = DataFrame({"A": [1, 1, 1], "B": [2, 2, 2]})
+ write_frame.to_excel(tmp_excel, sheet_name="test1", columns=["B", "A"])
+
+ read_frame = pd.read_excel(tmp_excel, sheet_name="test1", header=0)
+
+ tm.assert_series_equal(write_frame["A"], read_frame["A"])
+ tm.assert_series_equal(write_frame["B"], read_frame["B"])
+
+ def test_invalid_columns(self, tmp_excel):
+ # see gh-10982
+ write_frame = DataFrame({"A": [1, 1, 1], "B": [2, 2, 2]})
+
+ with pytest.raises(KeyError, match="Not all names specified"):
+ write_frame.to_excel(tmp_excel, sheet_name="test1", columns=["B", "C"])
+
+ with pytest.raises(
+ KeyError, match="'passes columns are not ALL present dataframe'"
+ ):
+ write_frame.to_excel(tmp_excel, sheet_name="test1", columns=["C", "D"])
+
+ @pytest.mark.parametrize(
+ "to_excel_index,read_excel_index_col",
+ [
+ (True, 0), # Include index in write to file
+ (False, None), # Dont include index in write to file
+ ],
+ )
+ def test_write_subset_columns(
+ self, tmp_excel, to_excel_index, read_excel_index_col
+ ):
+ # GH 31677
+ write_frame = DataFrame({"A": [1, 1, 1], "B": [2, 2, 2], "C": [3, 3, 3]})
+ write_frame.to_excel(
+ tmp_excel,
+ sheet_name="col_subset_bug",
+ columns=["A", "B"],
+ index=to_excel_index,
+ )
+
+ expected = write_frame[["A", "B"]]
+ read_frame = pd.read_excel(
+ tmp_excel, sheet_name="col_subset_bug", index_col=read_excel_index_col
+ )
+
+ tm.assert_frame_equal(expected, read_frame)
+
+ def test_comment_arg(self, tmp_excel):
+ # see gh-18735
+ #
+ # Test the comment argument functionality to pd.read_excel.
+
+ # Create file to read in.
+ df = DataFrame({"A": ["one", "#one", "one"], "B": ["two", "two", "#two"]})
+ df.to_excel(tmp_excel, sheet_name="test_c")
+
+ # Read file without comment arg.
+ result1 = pd.read_excel(tmp_excel, sheet_name="test_c", index_col=0)
+
+ result1.iloc[1, 0] = None
+ result1.iloc[1, 1] = None
+ result1.iloc[2, 1] = None
+
+ result2 = pd.read_excel(
+ tmp_excel, sheet_name="test_c", comment="#", index_col=0
+ )
+ tm.assert_frame_equal(result1, result2)
+
+ def test_comment_default(self, tmp_excel):
+ # Re issue #18735
+ # Test the comment argument default to pd.read_excel
+
+ # Create file to read in
+ df = DataFrame({"A": ["one", "#one", "one"], "B": ["two", "two", "#two"]})
+ df.to_excel(tmp_excel, sheet_name="test_c")
+
+ # Read file with default and explicit comment=None
+ result1 = pd.read_excel(tmp_excel, sheet_name="test_c")
+ result2 = pd.read_excel(tmp_excel, sheet_name="test_c", comment=None)
+ tm.assert_frame_equal(result1, result2)
+
+ def test_comment_used(self, tmp_excel):
+ # see gh-18735
+ #
+ # Test the comment argument is working as expected when used.
+
+ # Create file to read in.
+ df = DataFrame({"A": ["one", "#one", "one"], "B": ["two", "two", "#two"]})
+ df.to_excel(tmp_excel, sheet_name="test_c")
+
+ # Test read_frame_comment against manually produced expected output.
+ expected = DataFrame({"A": ["one", None, "one"], "B": ["two", None, None]})
+ result = pd.read_excel(tmp_excel, sheet_name="test_c", comment="#", index_col=0)
+ tm.assert_frame_equal(result, expected)
+
+ def test_comment_empty_line(self, tmp_excel):
+ # Re issue #18735
+ # Test that pd.read_excel ignores commented lines at the end of file
+
+ df = DataFrame({"a": ["1", "#2"], "b": ["2", "3"]})
+ df.to_excel(tmp_excel, index=False)
+
+ # Test that all-comment lines at EoF are ignored
+ expected = DataFrame({"a": [1], "b": [2]})
+ result = pd.read_excel(tmp_excel, comment="#")
+ tm.assert_frame_equal(result, expected)
+
+ def test_datetimes(self, tmp_excel):
+ # Test writing and reading datetimes. For issue #9139. (xref #9185)
+ unit = get_exp_unit(tmp_excel)
+ datetimes = [
+ datetime(2013, 1, 13, 1, 2, 3),
+ datetime(2013, 1, 13, 2, 45, 56),
+ datetime(2013, 1, 13, 4, 29, 49),
+ datetime(2013, 1, 13, 6, 13, 42),
+ datetime(2013, 1, 13, 7, 57, 35),
+ datetime(2013, 1, 13, 9, 41, 28),
+ datetime(2013, 1, 13, 11, 25, 21),
+ datetime(2013, 1, 13, 13, 9, 14),
+ datetime(2013, 1, 13, 14, 53, 7),
+ datetime(2013, 1, 13, 16, 37, 0),
+ datetime(2013, 1, 13, 18, 20, 52),
+ ]
+
+ write_frame = DataFrame({"A": datetimes})
+ write_frame.to_excel(tmp_excel, sheet_name="Sheet1")
+ read_frame = pd.read_excel(tmp_excel, sheet_name="Sheet1", header=0)
+
+ expected = write_frame.astype(f"M8[{unit}]")
+ tm.assert_series_equal(expected["A"], read_frame["A"])
+
+ def test_bytes_io(self, engine):
+ # see gh-7074
+ with BytesIO() as bio:
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+
+ # Pass engine explicitly, as there is no file path to infer from.
+ with ExcelWriter(bio, engine=engine) as writer:
+ df.to_excel(writer)
+
+ bio.seek(0)
+ reread_df = pd.read_excel(bio, index_col=0)
+ tm.assert_frame_equal(df, reread_df)
+
+ def test_engine_kwargs(self, engine, tmp_excel):
+ # GH#52368
+ df = DataFrame([{"A": 1, "B": 2}, {"A": 3, "B": 4}])
+
+ msgs = {
+ "odf": r"OpenDocumentSpreadsheet() got an unexpected keyword "
+ r"argument 'foo'",
+ "openpyxl": r"__init__() got an unexpected keyword argument 'foo'",
+ "xlsxwriter": r"__init__() got an unexpected keyword argument 'foo'",
+ }
+
+ msgs["openpyxl"] = (
+ "Workbook.__init__() got an unexpected keyword argument 'foo'"
+ )
+ msgs["xlsxwriter"] = (
+ "Workbook.__init__() got an unexpected keyword argument 'foo'"
+ )
+
+ # Handle change in error message for openpyxl (write and append mode)
+ if engine == "openpyxl" and not os.path.exists(tmp_excel):
+ msgs["openpyxl"] = (
+ r"load_workbook() got an unexpected keyword argument 'foo'"
+ )
+
+ with pytest.raises(TypeError, match=re.escape(msgs[engine])):
+ df.to_excel(
+ tmp_excel,
+ engine=engine,
+ engine_kwargs={"foo": "bar"},
+ )
+
+ def test_write_lists_dict(self, tmp_excel):
+ # see gh-8188.
+ df = DataFrame(
+ {
+ "mixed": ["a", ["b", "c"], {"d": "e", "f": 2}],
+ "numeric": [1, 2, 3.0],
+ "str": ["apple", "banana", "cherry"],
+ }
+ )
+ df.to_excel(tmp_excel, sheet_name="Sheet1")
+ read = pd.read_excel(tmp_excel, sheet_name="Sheet1", header=0, index_col=0)
+
+ expected = df.copy()
+ expected.mixed = expected.mixed.apply(str)
+ expected.numeric = expected.numeric.astype("int64")
+
+ tm.assert_frame_equal(read, expected)
+
+ def test_render_as_column_name(self, tmp_excel):
+ # see gh-34331
+ df = DataFrame({"render": [1, 2], "data": [3, 4]})
+ df.to_excel(tmp_excel, sheet_name="Sheet1")
+ read = pd.read_excel(tmp_excel, "Sheet1", index_col=0)
+ expected = df
+ tm.assert_frame_equal(read, expected)
+
+ def test_true_and_false_value_options(self, tmp_excel):
+ # see gh-13347
+ df = DataFrame([["foo", "bar"]], columns=["col1", "col2"], dtype=object)
+ expected = df.replace({"foo": True, "bar": False}).astype("bool")
+
+ df.to_excel(tmp_excel)
+ read_frame = pd.read_excel(
+ tmp_excel, true_values=["foo"], false_values=["bar"], index_col=0
+ )
+ tm.assert_frame_equal(read_frame, expected)
+
+ def test_freeze_panes(self, tmp_excel):
+ # see gh-15160
+ expected = DataFrame([[1, 2], [3, 4]], columns=["col1", "col2"])
+ expected.to_excel(tmp_excel, sheet_name="Sheet1", freeze_panes=(1, 1))
+
+ result = pd.read_excel(tmp_excel, index_col=0)
+ tm.assert_frame_equal(result, expected)
+
+ def test_path_path_lib(self, engine, tmp_excel):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ writer = partial(df.to_excel, engine=engine)
+
+ reader = partial(pd.read_excel, index_col=0)
+ result = tm.round_trip_pathlib(writer, reader, pathlib.Path(tmp_excel))
+ tm.assert_frame_equal(result, df)
+
+ def test_merged_cell_custom_objects(self, tmp_excel):
+ # see GH-27006
+ mi = MultiIndex.from_tuples(
+ [
+ (pd.Period("2018"), pd.Period("2018Q1")),
+ (pd.Period("2018"), pd.Period("2018Q2")),
+ ]
+ )
+ expected = DataFrame(np.ones((2, 2), dtype="int64"), columns=mi)
+ expected.to_excel(tmp_excel)
+ result = pd.read_excel(tmp_excel, header=[0, 1], index_col=0)
+ # need to convert PeriodIndexes to standard Indexes for assert equal
+ expected.columns = expected.columns.set_levels(
+ [[str(i) for i in mi.levels[0]], [str(i) for i in mi.levels[1]]],
+ level=[0, 1],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", [None, object])
+ def test_raise_when_saving_timezones(self, dtype, tz_aware_fixture, tmp_excel):
+ # GH 27008, GH 7056
+ tz = tz_aware_fixture
+ data = pd.Timestamp("2019", tz=tz)
+ df = DataFrame([data], dtype=dtype)
+ with pytest.raises(ValueError, match="Excel does not support"):
+ df.to_excel(tmp_excel)
+
+ data = data.to_pydatetime()
+ df = DataFrame([data], dtype=dtype)
+ with pytest.raises(ValueError, match="Excel does not support"):
+ df.to_excel(tmp_excel)
+
+ def test_excel_duplicate_columns_with_names(self, tmp_excel):
+ # GH#39695
+ df = DataFrame({"A": [0, 1], "B": [10, 11]})
+ df.to_excel(tmp_excel, columns=["A", "B", "A"], index=False)
+
+ result = pd.read_excel(tmp_excel)
+ expected = DataFrame([[0, 10, 0], [1, 11, 1]], columns=["A", "B", "A.1"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_if_sheet_exists_raises(self, tmp_excel):
+ # GH 40230
+ msg = "if_sheet_exists is only valid in append mode (mode='a')"
+
+ with pytest.raises(ValueError, match=re.escape(msg)):
+ ExcelWriter(tmp_excel, if_sheet_exists="replace")
+
+ def test_excel_writer_empty_frame(self, engine, tmp_excel):
+ # GH#45793
+ with ExcelWriter(tmp_excel, engine=engine) as writer:
+ DataFrame().to_excel(writer)
+ result = pd.read_excel(tmp_excel)
+ expected = DataFrame()
+ tm.assert_frame_equal(result, expected)
+
+ def test_to_excel_empty_frame(self, engine, tmp_excel):
+ # GH#45793
+ DataFrame().to_excel(tmp_excel, engine=engine)
+ result = pd.read_excel(tmp_excel)
+ expected = DataFrame()
+ tm.assert_frame_equal(result, expected)
+
+ def test_to_excel_raising_warning_when_cell_character_exceed_limit(self):
+ # GH#56954
+ df = DataFrame({"A": ["a" * 32768]})
+ msg = r"Cell contents too long \(32768\), truncated to 32767 characters"
+ with tm.assert_produces_warning(
+ UserWarning, match=msg, raise_on_extra_warnings=False
+ ):
+ buf = BytesIO()
+ df.to_excel(buf)
+
+ @pytest.mark.parametrize("with_index", [True, False])
+ def test_autofilter(self, engine, with_index, tmp_excel):
+ # GH 61194
+ df = DataFrame.from_dict([{"A": 1, "B": 2, "C": 3}, {"A": 4, "B": 5, "C": 6}])
+
+ if engine in ["odf"]:
+ with pytest.raises(
+ ValueError, match="Autofilter is not supported with odf!"
+ ):
+ df.to_excel(tmp_excel, engine=engine, autofilter=True, index=False)
+ else:
+ df.to_excel(tmp_excel, engine=engine, autofilter=True, index=with_index)
+
+ openpyxl = pytest.importorskip(
+ "openpyxl"
+ ) # test loading only with openpyxl
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ ws = wb.active
+
+ assert ws.auto_filter.ref is not None
+ assert ws.auto_filter.ref == "A1:D3" if with_index else "A1:C3"
+
+ def test_autofilter_with_startrow_startcol(self, engine, tmp_excel):
+ # GH 61194
+ df = DataFrame.from_dict([{"A": 1, "B": 2, "C": 3}, {"A": 4, "B": 5, "C": 6}])
+
+ if engine in ["odf"]:
+ # odf does not support autofilter
+ with pytest.raises(
+ ValueError, match="Autofilter is not supported with odf!"
+ ):
+ df.to_excel(tmp_excel, engine=engine, autofilter=True, index=False)
+ else:
+ df.to_excel(
+ tmp_excel, engine=engine, autofilter=True, startrow=10, startcol=10
+ )
+
+ openpyxl = pytest.importorskip(
+ "openpyxl"
+ ) # test loading only with openpyxl
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ ws = wb.active
+ assert ws.auto_filter.ref is not None
+ # Autofiler range moved by 10x10 cells
+ assert ws.auto_filter.ref == "K11:N13"
+
+ @pytest.mark.parametrize("merge_cells", [True, False])
+ def test_autofilter_with_multiindex_index(self, engine, tmp_excel, merge_cells):
+ # GH 61194
+ df = DataFrame(
+ {
+ "animal": ("horse", "horse", "dog", "dog"),
+ "color of fur": ("black", "white", "grey", "black"),
+ "name": ("Blacky", "Wendy", "Rufus", "Catchy"),
+ }
+ )
+ # setup hierarchical index
+ mi_df = df.set_index(["animal", "color of fur"])
+ if engine in ["odf"]:
+ # odf does not support autofilter
+ with pytest.raises(
+ ValueError, match="Autofilter is not supported with odf!"
+ ):
+ mi_df.to_excel(
+ tmp_excel,
+ engine=engine,
+ autofilter=True,
+ index=False,
+ merge_cells=merge_cells,
+ )
+ elif merge_cells:
+ # multiindex and merge cells cannot be used simultaneously
+ with pytest.raises(
+ ValueError,
+ match="Excel filters merged cells by showing only the first row. "
+ "'autofilter' and 'merge_cells' cannot be used simultaneously.",
+ ):
+ mi_df.to_excel(
+ tmp_excel,
+ engine=engine,
+ autofilter=True,
+ index=True,
+ merge_cells=merge_cells,
+ )
+ else:
+ mi_df.to_excel(
+ tmp_excel,
+ engine=engine,
+ autofilter=True,
+ index=True,
+ merge_cells=merge_cells,
+ )
+
+ # validate autofilter range
+ openpyxl = pytest.importorskip(
+ "openpyxl"
+ ) # test loading only with openpyxl
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ ws = wb.active
+
+ assert ws.auto_filter.ref is not None
+ assert ws.auto_filter.ref == "A1:C5"
+
+ @pytest.mark.parametrize("merge_cells", [True, False])
+ def test_autofilter_with_multiindex_columns(self, engine, tmp_excel, merge_cells):
+ # GH 61194
+ columns = MultiIndex(
+ levels=[["x", "y"], ["w", "t"]],
+ codes=[[0, 0, 1], [0, 1, 0]],
+ )
+ df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=columns)
+
+ if engine in ["odf"]:
+ # odf does not support autofilter
+ with pytest.raises(
+ ValueError, match="Autofilter is not supported with odf!"
+ ):
+ df.to_excel(
+ tmp_excel,
+ engine=engine,
+ autofilter=True,
+ index=False,
+ merge_cells=merge_cells,
+ )
+ elif merge_cells:
+ # multiindex and merge cells cannot be used simultaneously
+ with pytest.raises(
+ ValueError,
+ match="Excel filters merged cells by showing only the first row. "
+ "'autofilter' and 'merge_cells' cannot be used simultaneously.",
+ ):
+ df.to_excel(
+ tmp_excel,
+ engine=engine,
+ autofilter=True,
+ index=True,
+ merge_cells=merge_cells,
+ )
+ else:
+ df.to_excel(
+ tmp_excel,
+ engine=engine,
+ autofilter=True,
+ index=True,
+ merge_cells=merge_cells,
+ )
+
+ # validate autofilter range
+ openpyxl = pytest.importorskip(
+ "openpyxl"
+ ) # test loading only with openpyxl
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
+ ws = wb.active
+
+ assert ws.auto_filter.ref is not None
+ assert ws.auto_filter.ref == "A2:D5"
+
+
+class TestExcelWriterEngineTests:
+ @pytest.mark.parametrize(
+ "klass,ext",
+ [
+ pytest.param(_XlsxWriter, ".xlsx", marks=td.skip_if_no("xlsxwriter")),
+ pytest.param(_OpenpyxlWriter, ".xlsx", marks=td.skip_if_no("openpyxl")),
+ ],
+ )
+ def test_ExcelWriter_dispatch(self, klass, ext, tmp_excel):
+ with ExcelWriter(tmp_excel) as writer:
+ if ext == ".xlsx" and bool(
+ import_optional_dependency("xlsxwriter", errors="ignore")
+ ):
+ # xlsxwriter has preference over openpyxl if both installed
+ assert isinstance(writer, _XlsxWriter)
+ else:
+ assert isinstance(writer, klass)
+
+ def test_ExcelWriter_dispatch_raises(self):
+ with pytest.raises(ValueError, match="No engine"):
+ ExcelWriter("nothing")
+
+ def test_register_writer(self, tmp_path):
+ class DummyClass(ExcelWriter):
+ called_save = False
+ called_write_cells = False
+ called_sheets = False
+ _supported_extensions = ("xlsx", "xls")
+ _engine = "dummy"
+
+ def book(self):
+ pass
+
+ def _save(self):
+ type(self).called_save = True
+
+ def _write_cells(self, *args, **kwargs):
+ type(self).called_write_cells = True
+
+ @property
+ def sheets(self):
+ type(self).called_sheets = True
+
+ @classmethod
+ def assert_called_and_reset(cls):
+ assert cls.called_save
+ assert cls.called_write_cells
+ assert not cls.called_sheets
+ cls.called_save = False
+ cls.called_write_cells = False
+
+ register_writer(DummyClass)
+
+ with option_context("io.excel.xlsx.writer", "dummy"):
+ filepath = tmp_path / "something.xlsx"
+ filepath.touch()
+ with ExcelWriter(filepath) as writer:
+ assert isinstance(writer, DummyClass)
+ df = DataFrame(
+ ["a"],
+ columns=Index(["b"], name="foo"),
+ index=Index(["c"], name="bar"),
+ )
+ df.to_excel(filepath)
+ DummyClass.assert_called_and_reset()
+
+ filepath2 = tmp_path / "something2.xlsx"
+ filepath2.touch()
+ df.to_excel(filepath2, engine="dummy")
+ DummyClass.assert_called_and_reset()
+
+
+@td.skip_if_no("xlrd")
+@td.skip_if_no("openpyxl")
+class TestFSPath:
+ def test_excelfile_fspath(self, tmp_path):
+ path = tmp_path / "foo.xlsx"
+ path.touch()
+ df = DataFrame({"A": [1, 2]})
+ df.to_excel(path)
+ with ExcelFile(path) as xl:
+ result = os.fspath(xl)
+ assert result == str(path)
+
+ def test_excelwriter_fspath(self, tmp_path):
+ path = tmp_path / "foo.xlsx"
+ path.touch()
+ with ExcelWriter(path) as writer:
+ assert os.fspath(writer) == str(path)
+
+
+@pytest.mark.parametrize("klass", _writers.values())
+def test_subclass_attr(klass):
+ # testing that subclasses of ExcelWriter don't have public attributes (issue 49602)
+ attrs_base = {name for name in dir(ExcelWriter) if not name.startswith("_")}
+ attrs_klass = {name for name in dir(klass) if not name.startswith("_")}
+ assert not attrs_base.symmetric_difference(attrs_klass)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_xlrd.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_xlrd.py
new file mode 100644
index 0000000000000000000000000000000000000000..c49cbaf7fb26c8824329d9f9907df8cce8ddbd29
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_xlrd.py
@@ -0,0 +1,71 @@
+import io
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+from pandas.io.excel import ExcelFile
+from pandas.io.excel._base import inspect_excel_format
+
+xlrd = pytest.importorskip("xlrd")
+
+
+@pytest.fixture
+def read_ext_xlrd():
+ """
+ Valid extensions for reading Excel files with xlrd.
+
+ Similar to read_ext, but excludes .ods, .xlsb, and for xlrd>2 .xlsx, .xlsm
+ """
+ return ".xls"
+
+
+def test_read_xlrd_book(read_ext_xlrd, datapath):
+ engine = "xlrd"
+ sheet_name = "Sheet1"
+ pth = datapath("io", "data", "excel", "test1.xls")
+ with xlrd.open_workbook(pth) as book:
+ with ExcelFile(book, engine=engine) as xl:
+ result = pd.read_excel(xl, sheet_name=sheet_name, index_col=0)
+
+ expected = pd.read_excel(
+ book, sheet_name=sheet_name, engine=engine, index_col=0
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_xlsx_fails(datapath):
+ # GH 29375
+ from xlrd.biffh import XLRDError
+
+ path = datapath("io", "data", "excel", "test1.xlsx")
+ with pytest.raises(XLRDError, match="Excel xlsx file; not supported"):
+ pd.read_excel(path, engine="xlrd")
+
+
+def test_nan_in_xls(datapath):
+ # GH 54564
+ path = datapath("io", "data", "excel", "test6.xls")
+
+ expected = pd.DataFrame({0: np.r_[0, 2].astype("int64"), 1: np.r_[1, np.nan]})
+
+ result = pd.read_excel(path, header=None)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "file_header",
+ [
+ b"\x09\x00\x04\x00\x07\x00\x10\x00",
+ b"\x09\x02\x06\x00\x00\x00\x10\x00",
+ b"\x09\x04\x06\x00\x00\x00\x10\x00",
+ b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1",
+ ],
+)
+def test_read_old_xls_files(file_header):
+ # GH 41226
+ f = io.BytesIO(file_header)
+ assert inspect_excel_format(f) == "xls"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_xlsxwriter.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_xlsxwriter.py
new file mode 100644
index 0000000000000000000000000000000000000000..b2e6c845e50199ec7ca7d336de3f1cd3478a8bbf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/excel/test_xlsxwriter.py
@@ -0,0 +1,86 @@
+import contextlib
+import uuid
+
+import pytest
+
+from pandas import DataFrame
+
+from pandas.io.excel import ExcelWriter
+
+xlsxwriter = pytest.importorskip("xlsxwriter")
+
+
+@pytest.fixture
+def ext():
+ return ".xlsx"
+
+
+@pytest.fixture
+def tmp_excel(ext, tmp_path):
+ tmp = tmp_path / f"{uuid.uuid4()}{ext}"
+ tmp.touch()
+ return str(tmp)
+
+
+def test_column_format(tmp_excel):
+ # Test that column formats are applied to cells. Test for issue #9167.
+ # Applicable to xlsxwriter only.
+ openpyxl = pytest.importorskip("openpyxl")
+
+ frame = DataFrame({"A": [123456, 123456], "B": [123456, 123456]})
+
+ with ExcelWriter(tmp_excel) as writer:
+ frame.to_excel(writer)
+
+ # Add a number format to col B and ensure it is applied to cells.
+ num_format = "#,##0"
+ write_workbook = writer.book
+ write_worksheet = write_workbook.worksheets()[0]
+ col_format = write_workbook.add_format({"num_format": num_format})
+ write_worksheet.set_column("B:B", None, col_format)
+
+ with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as read_workbook:
+ try:
+ read_worksheet = read_workbook["Sheet1"]
+ except TypeError:
+ # compat
+ read_worksheet = read_workbook.get_sheet_by_name(name="Sheet1")
+
+ # Get the number format from the cell.
+ try:
+ cell = read_worksheet["B2"]
+ except TypeError:
+ # compat
+ cell = read_worksheet.cell("B2")
+
+ try:
+ read_num_format = cell.number_format
+ except AttributeError:
+ read_num_format = cell.style.number_format._format_code
+
+ assert read_num_format == num_format
+
+
+def test_write_append_mode_raises(tmp_excel):
+ msg = "Append mode is not supported with xlsxwriter!"
+
+ with pytest.raises(ValueError, match=msg):
+ ExcelWriter(tmp_excel, engine="xlsxwriter", mode="a")
+
+
+@pytest.mark.parametrize("nan_inf_to_errors", [True, False])
+def test_engine_kwargs(tmp_excel, nan_inf_to_errors):
+ # GH 42286
+ engine_kwargs = {"options": {"nan_inf_to_errors": nan_inf_to_errors}}
+ with ExcelWriter(
+ tmp_excel, engine="xlsxwriter", engine_kwargs=engine_kwargs
+ ) as writer:
+ assert writer.book.nan_inf_to_errors == nan_inf_to_errors
+
+
+def test_book_and_sheets_consistent(tmp_excel):
+ # GH#45687 - Ensure sheets is updated if user modifies book
+ with ExcelWriter(tmp_excel, engine="xlsxwriter") as writer:
+ assert writer.sheets == {}
+ sheet = writer.book.add_worksheet("test_name")
+ assert writer.sheets == {"test_name": sheet}
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_bar.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_bar.py
new file mode 100644
index 0000000000000000000000000000000000000000..4de83fd1436e0d8f1aae150b92d4cc11ed62466d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_bar.py
@@ -0,0 +1,360 @@
+import io
+
+import numpy as np
+import pytest
+
+from pandas import (
+ NA,
+ DataFrame,
+ read_csv,
+)
+
+pytest.importorskip("jinja2")
+
+
+def bar_grad(a=None, b=None, c=None, d=None):
+ """Used in multiple tests to simplify formatting of expected result"""
+ ret = [("width", "10em")]
+ if all(x is None for x in [a, b, c, d]):
+ return ret
+ return [
+ *ret,
+ (
+ "background",
+ f"linear-gradient(90deg,{','.join([x for x in [a, b, c, d] if x])})",
+ ),
+ ]
+
+
+def no_bar():
+ return bar_grad()
+
+
+def bar_to(x, color="#d65f5f"):
+ return bar_grad(f" {color} {x:.1f}%", f" transparent {x:.1f}%")
+
+
+def bar_from_to(x, y, color="#d65f5f"):
+ return bar_grad(
+ f" transparent {x:.1f}%",
+ f" {color} {x:.1f}%",
+ f" {color} {y:.1f}%",
+ f" transparent {y:.1f}%",
+ )
+
+
+@pytest.fixture
+def df_pos():
+ return DataFrame([[1], [2], [3]])
+
+
+@pytest.fixture
+def df_neg():
+ return DataFrame([[-1], [-2], [-3]])
+
+
+@pytest.fixture
+def df_mix():
+ return DataFrame([[-3], [1], [2]])
+
+
+@pytest.mark.parametrize(
+ "align, exp",
+ [
+ ("left", [no_bar(), bar_to(50), bar_to(100)]),
+ ("right", [bar_to(100), bar_from_to(50, 100), no_bar()]),
+ ("mid", [bar_to(33.33), bar_to(66.66), bar_to(100)]),
+ ("zero", [bar_from_to(50, 66.7), bar_from_to(50, 83.3), bar_from_to(50, 100)]),
+ ("mean", [bar_to(50), no_bar(), bar_from_to(50, 100)]),
+ (2.0, [bar_to(50), no_bar(), bar_from_to(50, 100)]),
+ (np.median, [bar_to(50), no_bar(), bar_from_to(50, 100)]),
+ ],
+)
+def test_align_positive_cases(df_pos, align, exp):
+ # test different align cases for all positive values
+ result = df_pos.style.bar(align=align)._compute().ctx
+ expected = {(0, 0): exp[0], (1, 0): exp[1], (2, 0): exp[2]}
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "align, exp",
+ [
+ ("left", [bar_to(100), bar_to(50), no_bar()]),
+ ("right", [no_bar(), bar_from_to(50, 100), bar_to(100)]),
+ ("mid", [bar_from_to(66.66, 100), bar_from_to(33.33, 100), bar_to(100)]),
+ ("zero", [bar_from_to(33.33, 50), bar_from_to(16.66, 50), bar_to(50)]),
+ ("mean", [bar_from_to(50, 100), no_bar(), bar_to(50)]),
+ (-2.0, [bar_from_to(50, 100), no_bar(), bar_to(50)]),
+ (np.median, [bar_from_to(50, 100), no_bar(), bar_to(50)]),
+ ],
+)
+def test_align_negative_cases(df_neg, align, exp):
+ # test different align cases for all negative values
+ result = df_neg.style.bar(align=align)._compute().ctx
+ expected = {(0, 0): exp[0], (1, 0): exp[1], (2, 0): exp[2]}
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "align, exp",
+ [
+ ("left", [no_bar(), bar_to(80), bar_to(100)]),
+ ("right", [bar_to(100), bar_from_to(80, 100), no_bar()]),
+ ("mid", [bar_to(60), bar_from_to(60, 80), bar_from_to(60, 100)]),
+ ("zero", [bar_to(50), bar_from_to(50, 66.66), bar_from_to(50, 83.33)]),
+ ("mean", [bar_to(50), bar_from_to(50, 66.66), bar_from_to(50, 83.33)]),
+ (-0.0, [bar_to(50), bar_from_to(50, 66.66), bar_from_to(50, 83.33)]),
+ (np.nanmedian, [bar_to(50), no_bar(), bar_from_to(50, 62.5)]),
+ ],
+)
+@pytest.mark.parametrize("nans", [True, False])
+def test_align_mixed_cases(df_mix, align, exp, nans):
+ # test different align cases for mixed positive and negative values
+ # also test no impact of NaNs and no_bar
+ expected = {(0, 0): exp[0], (1, 0): exp[1], (2, 0): exp[2]}
+ if nans:
+ df_mix.loc[3, :] = np.nan
+ expected.update({(3, 0): no_bar()})
+ result = df_mix.style.bar(align=align)._compute().ctx
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "align, exp",
+ [
+ (
+ "left",
+ {
+ "index": [[no_bar(), no_bar()], [bar_to(100), bar_to(100)]],
+ "columns": [[no_bar(), bar_to(100)], [no_bar(), bar_to(100)]],
+ "none": [[no_bar(), bar_to(33.33)], [bar_to(66.66), bar_to(100)]],
+ },
+ ),
+ (
+ "mid",
+ {
+ "index": [[bar_to(33.33), bar_to(50)], [bar_to(100), bar_to(100)]],
+ "columns": [[bar_to(50), bar_to(100)], [bar_to(75), bar_to(100)]],
+ "none": [[bar_to(25), bar_to(50)], [bar_to(75), bar_to(100)]],
+ },
+ ),
+ (
+ "zero",
+ {
+ "index": [
+ [bar_from_to(50, 66.66), bar_from_to(50, 75)],
+ [bar_from_to(50, 100), bar_from_to(50, 100)],
+ ],
+ "columns": [
+ [bar_from_to(50, 75), bar_from_to(50, 100)],
+ [bar_from_to(50, 87.5), bar_from_to(50, 100)],
+ ],
+ "none": [
+ [bar_from_to(50, 62.5), bar_from_to(50, 75)],
+ [bar_from_to(50, 87.5), bar_from_to(50, 100)],
+ ],
+ },
+ ),
+ (
+ 2,
+ {
+ "index": [
+ [bar_to(50), no_bar()],
+ [bar_from_to(50, 100), bar_from_to(50, 100)],
+ ],
+ "columns": [
+ [bar_to(50), no_bar()],
+ [bar_from_to(50, 75), bar_from_to(50, 100)],
+ ],
+ "none": [
+ [bar_from_to(25, 50), no_bar()],
+ [bar_from_to(50, 75), bar_from_to(50, 100)],
+ ],
+ },
+ ),
+ ],
+)
+@pytest.mark.parametrize("axis", ["index", "columns", "none"])
+def test_align_axis(align, exp, axis):
+ # test all axis combinations with positive values and different aligns
+ data = DataFrame([[1, 2], [3, 4]])
+ result = (
+ data.style.bar(align=align, axis=None if axis == "none" else axis)
+ ._compute()
+ .ctx
+ )
+ expected = {
+ (0, 0): exp[axis][0][0],
+ (0, 1): exp[axis][0][1],
+ (1, 0): exp[axis][1][0],
+ (1, 1): exp[axis][1][1],
+ }
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "values, vmin, vmax",
+ [
+ ("positive", 1.5, 2.5),
+ ("negative", -2.5, -1.5),
+ ("mixed", -2.5, 1.5),
+ ],
+)
+@pytest.mark.parametrize("nullify", [None, "vmin", "vmax"]) # test min/max separately
+@pytest.mark.parametrize("align", ["left", "right", "zero", "mid"])
+def test_vmin_vmax_clipping(df_pos, df_neg, df_mix, values, vmin, vmax, nullify, align):
+ # test that clipping occurs if any vmin > data_values or vmax < data_values
+ if align == "mid": # mid acts as left or right in each case
+ if values == "positive":
+ align = "left"
+ elif values == "negative":
+ align = "right"
+ df = {"positive": df_pos, "negative": df_neg, "mixed": df_mix}[values]
+ vmin = None if nullify == "vmin" else vmin
+ vmax = None if nullify == "vmax" else vmax
+
+ clip_df = df.where(df <= (vmax if vmax else 999), other=vmax)
+ clip_df = clip_df.where(clip_df >= (vmin if vmin else -999), other=vmin)
+
+ result = (
+ df.style.bar(align=align, vmin=vmin, vmax=vmax, color=["red", "green"])
+ ._compute()
+ .ctx
+ )
+ expected = clip_df.style.bar(align=align, color=["red", "green"])._compute().ctx
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "values, vmin, vmax",
+ [
+ ("positive", 0.5, 4.5),
+ ("negative", -4.5, -0.5),
+ ("mixed", -4.5, 4.5),
+ ],
+)
+@pytest.mark.parametrize("nullify", [None, "vmin", "vmax"]) # test min/max separately
+@pytest.mark.parametrize("align", ["left", "right", "zero", "mid"])
+def test_vmin_vmax_widening(df_pos, df_neg, df_mix, values, vmin, vmax, nullify, align):
+ # test that widening occurs if any vmax > data_values or vmin < data_values
+ if align == "mid": # mid acts as left or right in each case
+ if values == "positive":
+ align = "left"
+ elif values == "negative":
+ align = "right"
+ df = {"positive": df_pos, "negative": df_neg, "mixed": df_mix}[values]
+ vmin = None if nullify == "vmin" else vmin
+ vmax = None if nullify == "vmax" else vmax
+
+ expand_df = df.copy()
+ expand_df.loc[3, :], expand_df.loc[4, :] = vmin, vmax
+
+ result = (
+ df.style.bar(align=align, vmin=vmin, vmax=vmax, color=["red", "green"])
+ ._compute()
+ .ctx
+ )
+ expected = expand_df.style.bar(align=align, color=["red", "green"])._compute().ctx
+ assert result.items() <= expected.items()
+
+
+def test_numerics():
+ # test data is pre-selected for numeric values
+ data = DataFrame([[1, "a"], [2, "b"]])
+ result = data.style.bar()._compute().ctx
+ assert (0, 1) not in result
+ assert (1, 1) not in result
+
+
+@pytest.mark.parametrize(
+ "align, exp",
+ [
+ ("left", [no_bar(), bar_to(100, "green")]),
+ ("right", [bar_to(100, "red"), no_bar()]),
+ ("mid", [bar_to(25, "red"), bar_from_to(25, 100, "green")]),
+ ("zero", [bar_from_to(33.33, 50, "red"), bar_from_to(50, 100, "green")]),
+ ],
+)
+def test_colors_mixed(align, exp):
+ data = DataFrame([[-1], [3]])
+ result = data.style.bar(align=align, color=["red", "green"])._compute().ctx
+ assert result == {(0, 0): exp[0], (1, 0): exp[1]}
+
+
+def test_bar_align_height():
+ # test when keyword height is used 'no-repeat center' and 'background-size' present
+ data = DataFrame([[1], [2]])
+ result = data.style.bar(align="left", height=50)._compute().ctx
+ bg_s = "linear-gradient(90deg, #d65f5f 100.0%, transparent 100.0%) no-repeat center"
+ expected = {
+ (0, 0): [("width", "10em")],
+ (1, 0): [
+ ("width", "10em"),
+ ("background", bg_s),
+ ("background-size", "100% 50.0%"),
+ ],
+ }
+ assert result == expected
+
+
+def test_bar_value_error_raises():
+ df = DataFrame({"A": [-100, -60, -30, -20]})
+
+ msg = "`align` should be in {'left', 'right', 'mid', 'mean', 'zero'} or"
+ with pytest.raises(ValueError, match=msg):
+ df.style.bar(align="poorly", color=["#d65f5f", "#5fba7d"]).to_html()
+
+ msg = r"`width` must be a value in \[0, 100\]"
+ with pytest.raises(ValueError, match=msg):
+ df.style.bar(width=200).to_html()
+
+ msg = r"`height` must be a value in \[0, 100\]"
+ with pytest.raises(ValueError, match=msg):
+ df.style.bar(height=200).to_html()
+
+
+def test_bar_color_and_cmap_error_raises():
+ df = DataFrame({"A": [1, 2, 3, 4]})
+ msg = "`color` and `cmap` cannot both be given"
+ # Test that providing both color and cmap raises a ValueError
+ with pytest.raises(ValueError, match=msg):
+ df.style.bar(color="#d65f5f", cmap="viridis").to_html()
+
+
+def test_bar_invalid_color_type_error_raises():
+ df = DataFrame({"A": [1, 2, 3, 4]})
+ msg = (
+ r"`color` must be string or list or tuple of 2 strings,"
+ r"\(eg: color=\['#d65f5f', '#5fba7d'\]\)"
+ )
+ # Test that providing an invalid color type raises a ValueError
+ with pytest.raises(ValueError, match=msg):
+ df.style.bar(color=123).to_html()
+
+ # Test that providing a color list with more than two elements raises a ValueError
+ with pytest.raises(ValueError, match=msg):
+ df.style.bar(color=["#d65f5f", "#5fba7d", "#abcdef"]).to_html()
+
+
+def test_styler_bar_with_NA_values():
+ df1 = DataFrame({"A": [1, 2, NA, 4]})
+ df2 = DataFrame([[NA, NA], [NA, NA]])
+ expected_substring = "style type="
+ html_output1 = df1.style.bar(subset="A").to_html()
+ html_output2 = df2.style.bar(align="left", axis=None).to_html()
+ assert expected_substring in html_output1
+ assert expected_substring in html_output2
+
+
+def test_style_bar_with_pyarrow_NA_values():
+ pytest.importorskip("pyarrow")
+ data = """name,age,test1,test2,teacher
+ Adam,15,95.0,80,Ashby
+ Bob,16,81.0,82,Ashby
+ Dave,16,89.0,84,Jones
+ Fred,15,,88,Jones"""
+ df = read_csv(io.StringIO(data), dtype_backend="pyarrow")
+ expected_substring = "style type="
+ html_output = df.style.bar(subset="test1").to_html()
+ assert expected_substring in html_output
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_exceptions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_exceptions.py
new file mode 100644
index 0000000000000000000000000000000000000000..d52e3a37e7693dadce34f73fc03a0790c7a0b4d3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_exceptions.py
@@ -0,0 +1,44 @@
+import pytest
+
+jinja2 = pytest.importorskip("jinja2")
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+)
+
+from pandas.io.formats.style import Styler
+
+
+@pytest.fixture
+def df():
+ return DataFrame(
+ data=[[0, -0.609], [1, -1.228]],
+ columns=["A", "B"],
+ index=["x", "y"],
+ )
+
+
+@pytest.fixture
+def styler(df):
+ return Styler(df, uuid_len=0)
+
+
+def test_concat_bad_columns(styler):
+ msg = "`other.data` must have same columns as `Styler.data"
+ with pytest.raises(ValueError, match=msg):
+ styler.concat(DataFrame([[1, 2]]).style)
+
+
+def test_concat_bad_type(styler):
+ msg = "`other` must be of type `Styler`"
+ with pytest.raises(TypeError, match=msg):
+ styler.concat(DataFrame([[1, 2]]))
+
+
+def test_concat_bad_index_levels(styler, df):
+ df = df.copy()
+ df.index = MultiIndex.from_tuples([(0, 0), (1, 1)])
+ msg = "number of index levels must be same in `other`"
+ with pytest.raises(ValueError, match=msg):
+ styler.concat(df.style)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_format.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_format.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae68fcf9ef1fc99fa1e114265b0f8e58650e0170
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_format.py
@@ -0,0 +1,661 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ NA,
+ DataFrame,
+ IndexSlice,
+ MultiIndex,
+ NaT,
+ Timestamp,
+ option_context,
+)
+
+pytest.importorskip("jinja2")
+from pandas.io.formats.style import Styler
+from pandas.io.formats.style_render import _str_escape
+
+
+@pytest.fixture
+def df():
+ return DataFrame(
+ data=[[0, -0.609], [1, -1.228]],
+ columns=["A", "B"],
+ index=["x", "y"],
+ )
+
+
+@pytest.fixture
+def styler(df):
+ return Styler(df, uuid_len=0)
+
+
+@pytest.fixture
+def df_multi():
+ return (
+ DataFrame(
+ data=np.arange(16).reshape(4, 4),
+ columns=MultiIndex.from_product([["A", "B"], ["a", "b"]]),
+ index=MultiIndex.from_product([["X", "Y"], ["x", "y"]]),
+ )
+ .rename_axis(["0_0", "0_1"], axis=0)
+ .rename_axis(["1_0", "1_1"], axis=1)
+ )
+
+
+@pytest.fixture
+def styler_multi(df_multi):
+ return Styler(df_multi, uuid_len=0)
+
+
+def test_display_format(styler):
+ ctx = styler.format("{:0.1f}")._translate(True, True)
+ assert all(["display_value" in c for c in row] for row in ctx["body"])
+ assert all([len(c["display_value"]) <= 3 for c in row[1:]] for row in ctx["body"])
+ assert len(ctx["body"][0][1]["display_value"].lstrip("-")) <= 3
+
+
+@pytest.mark.parametrize("index", [True, False])
+@pytest.mark.parametrize("columns", [True, False])
+def test_display_format_index(styler, index, columns):
+ exp_index = ["x", "y"]
+ if index:
+ styler.format_index(lambda v: v.upper(), axis=0) # test callable
+ exp_index = ["X", "Y"]
+
+ exp_columns = ["A", "B"]
+ if columns:
+ styler.format_index("*{}*", axis=1) # test string
+ exp_columns = ["*A*", "*B*"]
+
+ ctx = styler._translate(True, True)
+
+ for r, row in enumerate(ctx["body"]):
+ assert row[0]["display_value"] == exp_index[r]
+
+ for c, col in enumerate(ctx["head"][1:]):
+ assert col["display_value"] == exp_columns[c]
+
+
+def test_format_dict(styler):
+ ctx = styler.format({"A": "{:0.1f}", "B": "{0:.2%}"})._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == "0.0"
+ assert ctx["body"][0][2]["display_value"] == "-60.90%"
+
+
+def test_format_index_dict(styler):
+ ctx = styler.format_index({0: lambda v: v.upper()})._translate(True, True)
+ for i, val in enumerate(["X", "Y"]):
+ assert ctx["body"][i][0]["display_value"] == val
+
+
+def test_format_string(styler):
+ ctx = styler.format("{:.2f}")._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == "0.00"
+ assert ctx["body"][0][2]["display_value"] == "-0.61"
+ assert ctx["body"][1][1]["display_value"] == "1.00"
+ assert ctx["body"][1][2]["display_value"] == "-1.23"
+
+
+def test_format_callable(styler):
+ ctx = styler.format(lambda v: "neg" if v < 0 else "pos")._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == "pos"
+ assert ctx["body"][0][2]["display_value"] == "neg"
+ assert ctx["body"][1][1]["display_value"] == "pos"
+ assert ctx["body"][1][2]["display_value"] == "neg"
+
+
+def test_format_with_na_rep():
+ # GH 21527 28358
+ df = DataFrame([[None, None], [1.1, 1.2]], columns=["A", "B"])
+
+ ctx = df.style.format(None, na_rep="-")._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == "-"
+ assert ctx["body"][0][2]["display_value"] == "-"
+
+ ctx = df.style.format("{:.2%}", na_rep="-")._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == "-"
+ assert ctx["body"][0][2]["display_value"] == "-"
+ assert ctx["body"][1][1]["display_value"] == "110.00%"
+ assert ctx["body"][1][2]["display_value"] == "120.00%"
+
+ ctx = df.style.format("{:.2%}", na_rep="-", subset=["B"])._translate(True, True)
+ assert ctx["body"][0][2]["display_value"] == "-"
+ assert ctx["body"][1][2]["display_value"] == "120.00%"
+
+
+def test_format_index_with_na_rep():
+ df = DataFrame([[1, 2, 3, 4, 5]], columns=["A", None, np.nan, NaT, NA])
+ ctx = df.style.format_index(None, na_rep="--", axis=1)._translate(True, True)
+ assert ctx["head"][0][1]["display_value"] == "A"
+ for i in [2, 3, 4, 5]:
+ assert ctx["head"][0][i]["display_value"] == "--"
+
+
+def test_format_non_numeric_na():
+ # GH 21527 28358
+ df = DataFrame(
+ {
+ "object": [None, np.nan, "foo"],
+ "datetime": [None, NaT, Timestamp("20120101")],
+ }
+ )
+ ctx = df.style.format(None, na_rep="-")._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == "-"
+ assert ctx["body"][0][2]["display_value"] == "-"
+ assert ctx["body"][1][1]["display_value"] == "-"
+ assert ctx["body"][1][2]["display_value"] == "-"
+
+
+@pytest.mark.parametrize(
+ "func, attr, kwargs",
+ [
+ ("format", "_display_funcs", {}),
+ ("format_index", "_display_funcs_index", {"axis": 0}),
+ ("format_index", "_display_funcs_columns", {"axis": 1}),
+ ],
+)
+def test_format_clear(styler, func, attr, kwargs):
+ assert (0, 0) not in getattr(styler, attr) # using default
+ getattr(styler, func)("{:.2f}", **kwargs)
+ assert (0, 0) in getattr(styler, attr) # formatter is specified
+ getattr(styler, func)(**kwargs)
+ assert (0, 0) not in getattr(styler, attr) # formatter cleared to default
+
+
+@pytest.mark.parametrize(
+ "escape, exp",
+ [
+ ("html", "<>&"%$#_{}~^\\~ ^ \\ "),
+ (
+ "latex",
+ '<>\\&"\\%\\$\\#\\_\\{\\}\\textasciitilde \\textasciicircum '
+ "\\textbackslash \\textasciitilde \\space \\textasciicircum \\space "
+ "\\textbackslash \\space ",
+ ),
+ ],
+)
+def test_format_escape_html(escape, exp):
+ chars = '<>&"%$#_{}~^\\~ ^ \\ '
+ df = DataFrame([[chars]])
+
+ s = Styler(df, uuid_len=0).format("&{0}&", escape=None)
+ expected = f'&{chars}& '
+ assert expected in s.to_html()
+
+ # only the value should be escaped before passing to the formatter
+ s = Styler(df, uuid_len=0).format("&{0}&", escape=escape)
+ expected = f'&{exp}& '
+ assert expected in s.to_html()
+
+ # also test format_index()
+ styler = Styler(DataFrame(columns=[chars]), uuid_len=0)
+ styler.format_index("&{0}&", escape=None, axis=1)
+ assert styler._translate(True, True)["head"][0][1]["display_value"] == f"&{chars}&"
+ styler.format_index("&{0}&", escape=escape, axis=1)
+ assert styler._translate(True, True)["head"][0][1]["display_value"] == f"&{exp}&"
+
+
+@pytest.mark.parametrize(
+ "chars, expected",
+ [
+ (
+ r"$ \$&%#_{}~^\ $ &%#_{}~^\ $",
+ "".join(
+ [
+ r"$ \$&%#_{}~^\ $ ",
+ r"\&\%\#\_\{\}\textasciitilde \textasciicircum ",
+ r"\textbackslash \space \$",
+ ]
+ ),
+ ),
+ (
+ r"\( &%#_{}~^\ \) &%#_{}~^\ \(",
+ "".join(
+ [
+ r"\( &%#_{}~^\ \) ",
+ r"\&\%\#\_\{\}\textasciitilde \textasciicircum ",
+ r"\textbackslash \space \textbackslash (",
+ ]
+ ),
+ ),
+ (
+ r"$\&%#_{}^\$",
+ r"\$\textbackslash \&\%\#\_\{\}\textasciicircum \textbackslash \$",
+ ),
+ (
+ r"$ \frac{1}{2} $ \( \frac{1}{2} \)",
+ "".join(
+ [
+ r"$ \frac{1}{2} $",
+ r" \textbackslash ( \textbackslash frac\{1\}\{2\} \textbackslash )",
+ ]
+ ),
+ ),
+ ],
+)
+def test_format_escape_latex_math(chars, expected):
+ # GH 51903
+ # latex-math escape works for each DataFrame cell separately. If we have
+ # a combination of dollar signs and brackets, the dollar sign would apply.
+ df = DataFrame([[chars]])
+ s = df.style.format("{0}", escape="latex-math")
+ assert s._translate(True, True)["body"][0][1]["display_value"] == expected
+
+
+def test_format_escape_na_rep():
+ # tests the na_rep is not escaped
+ df = DataFrame([['<>&"', None]])
+ s = Styler(df, uuid_len=0).format("X&{0}>X", escape="html", na_rep="&")
+ ex = 'X&<>&">X '
+ expected2 = '& '
+ assert ex in s.to_html()
+ assert expected2 in s.to_html()
+
+ # also test for format_index()
+ df = DataFrame(columns=['<>&"', None])
+ styler = Styler(df, uuid_len=0)
+ styler.format_index("X&{0}>X", escape="html", na_rep="&", axis=1)
+ ctx = styler._translate(True, True)
+ assert ctx["head"][0][1]["display_value"] == "X&<>&">X"
+ assert ctx["head"][0][2]["display_value"] == "&"
+
+
+def test_format_escape_floats(styler):
+ # test given formatter for number format is not impacted by escape
+ s = styler.format("{:.1f}", escape="html")
+ for expected in [">0.0<", ">1.0<", ">-1.2<", ">-0.6<"]:
+ assert expected in s.to_html()
+ # tests precision of floats is not impacted by escape
+ s = styler.format(precision=1, escape="html")
+ for expected in [">0<", ">1<", ">-1.2<", ">-0.6<"]:
+ assert expected in s.to_html()
+
+
+@pytest.mark.parametrize("formatter", [5, True, [2.0]])
+@pytest.mark.parametrize("func", ["format", "format_index"])
+def test_format_raises(styler, formatter, func):
+ with pytest.raises(TypeError, match="expected str or callable"):
+ getattr(styler, func)(formatter)
+
+
+@pytest.mark.parametrize(
+ "precision, expected",
+ [
+ (1, ["1.0", "2.0", "3.2", "4.6"]),
+ (2, ["1.00", "2.01", "3.21", "4.57"]),
+ (3, ["1.000", "2.009", "3.212", "4.566"]),
+ ],
+)
+def test_format_with_precision(precision, expected):
+ # Issue #13257
+ df = DataFrame([[1.0, 2.0090, 3.2121, 4.566]], columns=[1.0, 2.0090, 3.2121, 4.566])
+ styler = Styler(df)
+ styler.format(precision=precision)
+ styler.format_index(precision=precision, axis=1)
+
+ ctx = styler._translate(True, True)
+ for col, exp in enumerate(expected):
+ assert ctx["body"][0][col + 1]["display_value"] == exp # format test
+ assert ctx["head"][0][col + 1]["display_value"] == exp # format_index test
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+@pytest.mark.parametrize(
+ "level, expected",
+ [
+ (0, ["X", "X", "_", "_"]), # level int
+ ("zero", ["X", "X", "_", "_"]), # level name
+ (1, ["_", "_", "X", "X"]), # other level int
+ ("one", ["_", "_", "X", "X"]), # other level name
+ ([0, 1], ["X", "X", "X", "X"]), # both levels
+ ([0, "zero"], ["X", "X", "_", "_"]), # level int and name simultaneous
+ ([0, "one"], ["X", "X", "X", "X"]), # both levels as int and name
+ (["one", "zero"], ["X", "X", "X", "X"]), # both level names, reversed
+ ],
+)
+def test_format_index_level(axis, level, expected):
+ midx = MultiIndex.from_arrays([["_", "_"], ["_", "_"]], names=["zero", "one"])
+ df = DataFrame([[1, 2], [3, 4]])
+ if axis == 0:
+ df.index = midx
+ else:
+ df.columns = midx
+
+ styler = df.style.format_index(lambda v: "X", level=level, axis=axis)
+ ctx = styler._translate(True, True)
+
+ if axis == 0: # compare index
+ result = [ctx["body"][s][0]["display_value"] for s in range(2)]
+ result += [ctx["body"][s][1]["display_value"] for s in range(2)]
+ else: # compare columns
+ result = [ctx["head"][0][s + 1]["display_value"] for s in range(2)]
+ result += [ctx["head"][1][s + 1]["display_value"] for s in range(2)]
+
+ assert expected == result
+
+
+def test_format_subset():
+ df = DataFrame([[0.1234, 0.1234], [1.1234, 1.1234]], columns=["a", "b"])
+ ctx = df.style.format(
+ {"a": "{:0.1f}", "b": "{0:.2%}"}, subset=IndexSlice[0, :]
+ )._translate(True, True)
+ expected = "0.1"
+ raw_11 = "1.123400"
+ assert ctx["body"][0][1]["display_value"] == expected
+ assert ctx["body"][1][1]["display_value"] == raw_11
+ assert ctx["body"][0][2]["display_value"] == "12.34%"
+
+ ctx = df.style.format("{:0.1f}", subset=IndexSlice[0, :])._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == expected
+ assert ctx["body"][1][1]["display_value"] == raw_11
+
+ ctx = df.style.format("{:0.1f}", subset=IndexSlice["a"])._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == expected
+ assert ctx["body"][0][2]["display_value"] == "0.123400"
+
+ ctx = df.style.format("{:0.1f}", subset=IndexSlice[0, "a"])._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == expected
+ assert ctx["body"][1][1]["display_value"] == raw_11
+
+ ctx = df.style.format("{:0.1f}", subset=IndexSlice[[0, 1], ["a"]])._translate(
+ True, True
+ )
+ assert ctx["body"][0][1]["display_value"] == expected
+ assert ctx["body"][1][1]["display_value"] == "1.1"
+ assert ctx["body"][0][2]["display_value"] == "0.123400"
+ assert ctx["body"][1][2]["display_value"] == raw_11
+
+
+@pytest.mark.parametrize("formatter", [None, "{:,.1f}"])
+@pytest.mark.parametrize("decimal", [".", "*"])
+@pytest.mark.parametrize("precision", [None, 2])
+@pytest.mark.parametrize("func, col", [("format", 1), ("format_index", 0)])
+def test_format_thousands(formatter, decimal, precision, func, col):
+ styler = DataFrame([[1000000.123456789]], index=[1000000.123456789]).style
+ result = getattr(styler, func)( # testing float
+ thousands="_", formatter=formatter, decimal=decimal, precision=precision
+ )._translate(True, True)
+ assert "1_000_000" in result["body"][0][col]["display_value"]
+
+ styler = DataFrame([[1000000]], index=[1000000]).style
+ result = getattr(styler, func)( # testing int
+ thousands="_", formatter=formatter, decimal=decimal, precision=precision
+ )._translate(True, True)
+ assert "1_000_000" in result["body"][0][col]["display_value"]
+
+ styler = DataFrame([[1 + 1000000.123456789j]], index=[1 + 1000000.123456789j]).style
+ result = getattr(styler, func)( # testing complex
+ thousands="_", formatter=formatter, decimal=decimal, precision=precision
+ )._translate(True, True)
+ assert "1_000_000" in result["body"][0][col]["display_value"]
+
+
+@pytest.mark.parametrize("formatter", [None, "{:,.4f}"])
+@pytest.mark.parametrize("thousands", [None, ",", "*"])
+@pytest.mark.parametrize("precision", [None, 4])
+@pytest.mark.parametrize("func, col", [("format", 1), ("format_index", 0)])
+def test_format_decimal(formatter, thousands, precision, func, col):
+ styler = DataFrame([[1000000.123456789]], index=[1000000.123456789]).style
+ result = getattr(styler, func)( # testing float
+ decimal="_", formatter=formatter, thousands=thousands, precision=precision
+ )._translate(True, True)
+ assert "000_123" in result["body"][0][col]["display_value"]
+
+ styler = DataFrame([[1 + 1000000.123456789j]], index=[1 + 1000000.123456789j]).style
+ result = getattr(styler, func)( # testing complex
+ decimal="_", formatter=formatter, thousands=thousands, precision=precision
+ )._translate(True, True)
+ assert "000_123" in result["body"][0][col]["display_value"]
+
+
+def test_str_escape_error():
+ msg = "`escape` only permitted in {'html', 'latex', 'latex-math'}, got "
+ with pytest.raises(ValueError, match=msg):
+ _str_escape("text", "bad_escape")
+
+ with pytest.raises(ValueError, match=msg):
+ _str_escape("text", [])
+
+ _str_escape(2.00, "bad_escape") # OK since dtype is float
+
+
+def test_long_int_formatting():
+ df = DataFrame(data=[[1234567890123456789]], columns=["test"])
+ styler = df.style
+ ctx = styler._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == "1234567890123456789"
+
+ styler = df.style.format(thousands="_")
+ ctx = styler._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == "1_234_567_890_123_456_789"
+
+
+def test_format_options():
+ df = DataFrame({"int": [2000, 1], "float": [1.009, None], "str": ["&<", "&~"]})
+ ctx = df.style._translate(True, True)
+
+ # test option: na_rep
+ assert ctx["body"][1][2]["display_value"] == "nan"
+ with option_context("styler.format.na_rep", "MISSING"):
+ ctx_with_op = df.style._translate(True, True)
+ assert ctx_with_op["body"][1][2]["display_value"] == "MISSING"
+
+ # test option: decimal and precision
+ assert ctx["body"][0][2]["display_value"] == "1.009000"
+ with option_context("styler.format.decimal", "_"):
+ ctx_with_op = df.style._translate(True, True)
+ assert ctx_with_op["body"][0][2]["display_value"] == "1_009000"
+ with option_context("styler.format.precision", 2):
+ ctx_with_op = df.style._translate(True, True)
+ assert ctx_with_op["body"][0][2]["display_value"] == "1.01"
+
+ # test option: thousands
+ assert ctx["body"][0][1]["display_value"] == "2000"
+ with option_context("styler.format.thousands", "_"):
+ ctx_with_op = df.style._translate(True, True)
+ assert ctx_with_op["body"][0][1]["display_value"] == "2_000"
+
+ # test option: escape
+ assert ctx["body"][0][3]["display_value"] == "&<"
+ assert ctx["body"][1][3]["display_value"] == "&~"
+ with option_context("styler.format.escape", "html"):
+ ctx_with_op = df.style._translate(True, True)
+ assert ctx_with_op["body"][0][3]["display_value"] == "&<"
+ with option_context("styler.format.escape", "latex"):
+ ctx_with_op = df.style._translate(True, True)
+ assert ctx_with_op["body"][1][3]["display_value"] == "\\&\\textasciitilde "
+ with option_context("styler.format.escape", "latex-math"):
+ ctx_with_op = df.style._translate(True, True)
+ assert ctx_with_op["body"][1][3]["display_value"] == "\\&\\textasciitilde "
+
+ # test option: formatter
+ with option_context("styler.format.formatter", {"int": "{:,.2f}"}):
+ ctx_with_op = df.style._translate(True, True)
+ assert ctx_with_op["body"][0][1]["display_value"] == "2,000.00"
+
+
+def test_precision_zero(df):
+ styler = Styler(df, precision=0)
+ ctx = styler._translate(True, True)
+ assert ctx["body"][0][2]["display_value"] == "-1"
+ assert ctx["body"][1][2]["display_value"] == "-1"
+
+
+@pytest.mark.parametrize(
+ "formatter, exp",
+ [
+ (lambda x: f"{x:.3f}", "9.000"),
+ ("{:.2f}", "9.00"),
+ ({0: "{:.1f}"}, "9.0"),
+ (None, "9"),
+ ],
+)
+def test_formatter_options_validator(formatter, exp):
+ df = DataFrame([[9]])
+ with option_context("styler.format.formatter", formatter):
+ assert f" {exp} " in df.style.to_latex()
+
+
+def test_formatter_options_raises():
+ msg = "Value must be an instance of"
+ with pytest.raises(ValueError, match=msg):
+ with option_context("styler.format.formatter", ["bad", "type"]):
+ DataFrame().style.to_latex()
+
+
+def test_1level_multiindex():
+ # GH 43383
+ midx = MultiIndex.from_product([[1, 2]], names=[""])
+ df = DataFrame(-1, index=midx, columns=[0, 1])
+ ctx = df.style._translate(True, True)
+ assert ctx["body"][0][0]["display_value"] == "1"
+ assert ctx["body"][0][0]["is_visible"] is True
+ assert ctx["body"][1][0]["display_value"] == "2"
+ assert ctx["body"][1][0]["is_visible"] is True
+
+
+def test_boolean_format():
+ # gh 46384: booleans do not collapse to integer representation on display
+ df = DataFrame([[True, False]])
+ ctx = df.style._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] is True
+ assert ctx["body"][0][2]["display_value"] is False
+
+
+@pytest.mark.parametrize(
+ "hide, labels",
+ [
+ (False, [1, 2]),
+ (True, [1, 2, 3, 4]),
+ ],
+)
+def test_relabel_raise_length(styler_multi, hide, labels):
+ if hide:
+ styler_multi.hide(axis=0, subset=[("X", "x"), ("Y", "y")])
+ with pytest.raises(ValueError, match="``labels`` must be of length equal"):
+ styler_multi.relabel_index(labels=labels)
+
+
+def test_relabel_index(styler_multi):
+ labels = [(1, 2), (3, 4)]
+ styler_multi.hide(axis=0, subset=[("X", "x"), ("Y", "y")])
+ styler_multi.relabel_index(labels=labels)
+ ctx = styler_multi._translate(True, True)
+ assert {"value": "X", "display_value": 1}.items() <= ctx["body"][0][0].items()
+ assert {"value": "y", "display_value": 2}.items() <= ctx["body"][0][1].items()
+ assert {"value": "Y", "display_value": 3}.items() <= ctx["body"][1][0].items()
+ assert {"value": "x", "display_value": 4}.items() <= ctx["body"][1][1].items()
+
+
+def test_relabel_columns(styler_multi):
+ labels = [(1, 2), (3, 4)]
+ styler_multi.hide(axis=1, subset=[("A", "a"), ("B", "b")])
+ styler_multi.relabel_index(axis=1, labels=labels)
+ ctx = styler_multi._translate(True, True)
+ assert {"value": "A", "display_value": 1}.items() <= ctx["head"][0][3].items()
+ assert {"value": "B", "display_value": 3}.items() <= ctx["head"][0][4].items()
+ assert {"value": "b", "display_value": 2}.items() <= ctx["head"][1][3].items()
+ assert {"value": "a", "display_value": 4}.items() <= ctx["head"][1][4].items()
+
+
+def test_relabel_roundtrip(styler):
+ styler.relabel_index(["{}", "{}"])
+ ctx = styler._translate(True, True)
+ assert {"value": "x", "display_value": "x"}.items() <= ctx["body"][0][0].items()
+ assert {"value": "y", "display_value": "y"}.items() <= ctx["body"][1][0].items()
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+@pytest.mark.parametrize(
+ "level, expected",
+ [
+ (0, ["X", "one"]), # level int
+ ("zero", ["X", "one"]), # level name
+ (1, ["zero", "X"]), # other level int
+ ("one", ["zero", "X"]), # other level name
+ ([0, 1], ["X", "X"]), # both levels
+ ([0, "zero"], ["X", "one"]), # level int and name simultaneous
+ ([0, "one"], ["X", "X"]), # both levels as int and name
+ (["one", "zero"], ["X", "X"]), # both level names, reversed
+ ],
+)
+def test_format_index_names_level(axis, level, expected):
+ midx = MultiIndex.from_arrays([["_", "_"], ["_", "_"]], names=["zero", "one"])
+ df = DataFrame([[1, 2], [3, 4]])
+ if axis == 0:
+ df.index = midx
+ else:
+ df.columns = midx
+
+ styler = df.style.format_index_names(lambda v: "X", level=level, axis=axis)
+ ctx = styler._translate(True, True)
+
+ if axis == 0: # compare index
+ result = [ctx["head"][1][s]["display_value"] for s in range(2)]
+ else: # compare columns
+ result = [ctx["head"][s][0]["display_value"] for s in range(2)]
+ assert expected == result
+
+
+@pytest.mark.parametrize(
+ "attr, kwargs",
+ [
+ ("_display_funcs_index_names", {"axis": 0}),
+ ("_display_funcs_column_names", {"axis": 1}),
+ ],
+)
+def test_format_index_names_clear(styler, attr, kwargs):
+ assert 0 not in getattr(styler, attr) # using default
+ styler.format_index_names("{:.2f}", **kwargs)
+ assert 0 in getattr(styler, attr) # formatter is specified
+ styler.format_index_names(**kwargs)
+ assert 0 not in getattr(styler, attr) # formatter cleared to default
+
+
+@pytest.mark.parametrize("axis", [0, 1])
+def test_format_index_names_callable(styler_multi, axis):
+ ctx = styler_multi.format_index_names(
+ lambda v: v.replace("_", "A"), axis=axis
+ )._translate(True, True)
+ result = [
+ ctx["head"][2][0]["display_value"],
+ ctx["head"][2][1]["display_value"],
+ ctx["head"][0][1]["display_value"],
+ ctx["head"][1][1]["display_value"],
+ ]
+ if axis == 0:
+ expected = ["0A0", "0A1", "1_0", "1_1"]
+ else:
+ expected = ["0_0", "0_1", "1A0", "1A1"]
+ assert result == expected
+
+
+def test_format_index_names_dict(styler_multi):
+ ctx = (
+ styler_multi.format_index_names({"0_0": "{:<<5}"})
+ .format_index_names({"1_1": "{:>>4}"}, axis=1)
+ ._translate(True, True)
+ )
+ assert ctx["head"][2][0]["display_value"] == "0_0<<"
+ assert ctx["head"][1][1]["display_value"] == ">1_1"
+
+
+def test_format_index_names_with_hidden_levels(styler_multi):
+ ctx = styler_multi._translate(True, True)
+ full_head_height = len(ctx["head"])
+ full_head_width = len(ctx["head"][0])
+ assert full_head_height == 3
+ assert full_head_width == 6
+
+ ctx = (
+ styler_multi.hide(axis=0, level=1)
+ .hide(axis=1, level=1)
+ .format_index_names("{:>>4}", axis=1)
+ .format_index_names("{:!<5}")
+ ._translate(True, True)
+ )
+ assert len(ctx["head"]) == full_head_height - 1
+ assert len(ctx["head"][0]) == full_head_width - 1
+ assert ctx["head"][0][0]["display_value"] == ">1_0"
+ assert ctx["head"][1][0]["display_value"] == "0_0!!"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_highlight.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_highlight.py
new file mode 100644
index 0000000000000000000000000000000000000000..98c1f70f08e897bfc7b37d9efe0130ef1708bf2c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_highlight.py
@@ -0,0 +1,221 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ NA,
+ DataFrame,
+ IndexSlice,
+)
+
+pytest.importorskip("jinja2")
+
+from pandas.io.formats.style import Styler
+
+
+@pytest.fixture(params=[(None, "float64"), (NA, "Int64")])
+def df(request):
+ # GH 45804
+ dtype = request.param[1]
+ item = np.nan if dtype == "float64" else NA
+ return DataFrame(
+ {"A": [0, item, 10], "B": [1, request.param[0], 2]}, dtype=request.param[1]
+ )
+
+
+@pytest.fixture
+def styler(df):
+ return Styler(df, uuid_len=0)
+
+
+def test_highlight_null(styler):
+ result = styler.highlight_null()._compute().ctx
+ expected = {
+ (1, 0): [("background-color", "red")],
+ (1, 1): [("background-color", "red")],
+ }
+ assert result == expected
+
+
+def test_highlight_null_subset(styler):
+ # GH 31345
+ result = (
+ styler.highlight_null(color="red", subset=["A"])
+ .highlight_null(color="green", subset=["B"])
+ ._compute()
+ .ctx
+ )
+ expected = {
+ (1, 0): [("background-color", "red")],
+ (1, 1): [("background-color", "green")],
+ }
+ assert result == expected
+
+
+@pytest.mark.parametrize("f", ["highlight_min", "highlight_max"])
+def test_highlight_minmax_basic(df, f):
+ expected = {
+ (0, 1): [("background-color", "red")],
+ # ignores NaN row,
+ (2, 0): [("background-color", "red")],
+ }
+ if f == "highlight_min":
+ df = -df
+ result = getattr(df.style, f)(axis=1, color="red")._compute().ctx
+ assert result == expected
+
+
+@pytest.mark.parametrize("f", ["highlight_min", "highlight_max"])
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"axis": None, "color": "red"}, # test axis
+ {"axis": 0, "subset": ["A"], "color": "red"}, # test subset and ignores NaN
+ {"axis": None, "props": "background-color: red"}, # test props
+ ],
+)
+def test_highlight_minmax_ext(df, f, kwargs):
+ expected = {(2, 0): [("background-color", "red")]}
+ if f == "highlight_min":
+ df = -df
+ result = getattr(df.style, f)(**kwargs)._compute().ctx
+ assert result == expected
+
+
+@pytest.mark.parametrize("f", ["highlight_min", "highlight_max"])
+@pytest.mark.parametrize("axis", [None, 0, 1])
+def test_highlight_minmax_nulls(f, axis):
+ # GH 42750
+ expected = {
+ (1, 0): [("background-color", "yellow")],
+ (1, 1): [("background-color", "yellow")],
+ }
+ if axis == 1:
+ expected.update({(2, 1): [("background-color", "yellow")]})
+
+ if f == "highlight_max":
+ df = DataFrame({"a": [NA, 1, None], "b": [np.nan, 1, -1]})
+ else:
+ df = DataFrame({"a": [NA, -1, None], "b": [np.nan, -1, 1]})
+
+ result = getattr(df.style, f)(axis=axis)._compute().ctx
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"left": 0, "right": 1}, # test basic range
+ {"left": 0, "right": 1, "props": "background-color: yellow"}, # test props
+ {"left": -100, "right": 100, "subset": IndexSlice[[0, 1], :]}, # test subset
+ {"left": 0, "subset": IndexSlice[[0, 1], :]}, # test no right
+ {"right": 1}, # test no left
+ {"left": [0, 0, 11], "axis": 0}, # test left as sequence
+ {"left": DataFrame({"A": [0, 0, 11], "B": [1, 1, 11]}), "axis": None}, # axis
+ {"left": 0, "right": [0, 1], "axis": 1}, # test sequence right
+ ],
+)
+def test_highlight_between(styler, kwargs):
+ expected = {
+ (0, 0): [("background-color", "yellow")],
+ (0, 1): [("background-color", "yellow")],
+ }
+ result = styler.highlight_between(**kwargs)._compute().ctx
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "arg, map, axis",
+ [
+ ("left", [1, 2], 0), # 0 axis has 3 elements not 2
+ ("left", [1, 2, 3], 1), # 1 axis has 2 elements not 3
+ ("left", np.array([[1, 2], [1, 2]]), None), # df is (2,3) not (2,2)
+ ("right", [1, 2], 0), # same tests as above for 'right' not 'left'
+ ("right", [1, 2, 3], 1), # ..
+ ("right", np.array([[1, 2], [1, 2]]), None), # ..
+ ],
+)
+def test_highlight_between_raises(arg, styler, map, axis):
+ msg = f"supplied '{arg}' is not correct shape"
+ with pytest.raises(ValueError, match=msg):
+ styler.highlight_between(**{arg: map, "axis": axis})._compute()
+
+
+def test_highlight_between_raises2(styler):
+ msg = "values can be 'both', 'left', 'right', or 'neither'"
+ with pytest.raises(ValueError, match=msg):
+ styler.highlight_between(inclusive="badstring")._compute()
+
+ with pytest.raises(ValueError, match=msg):
+ styler.highlight_between(inclusive=1)._compute()
+
+
+@pytest.mark.parametrize(
+ "inclusive, expected",
+ [
+ (
+ "both",
+ {
+ (0, 0): [("background-color", "yellow")],
+ (0, 1): [("background-color", "yellow")],
+ },
+ ),
+ ("neither", {}),
+ ("left", {(0, 0): [("background-color", "yellow")]}),
+ ("right", {(0, 1): [("background-color", "yellow")]}),
+ ],
+)
+def test_highlight_between_inclusive(styler, inclusive, expected):
+ kwargs = {"left": 0, "right": 1, "subset": IndexSlice[[0, 1], :]}
+ result = styler.highlight_between(**kwargs, inclusive=inclusive)._compute()
+ assert result.ctx == expected
+
+
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"q_left": 0.5, "q_right": 1, "axis": 0}, # base case
+ {"q_left": 0.5, "q_right": 1, "axis": None}, # test axis
+ {"q_left": 0, "q_right": 1, "subset": IndexSlice[2, :]}, # test subset
+ {"q_left": 0.5, "axis": 0}, # test no high
+ {"q_right": 1, "subset": IndexSlice[2, :], "axis": 1}, # test no low
+ {"q_left": 0.5, "axis": 0, "props": "background-color: yellow"}, # tst prop
+ ],
+)
+def test_highlight_quantile(styler, kwargs):
+ expected = {
+ (2, 0): [("background-color", "yellow")],
+ (2, 1): [("background-color", "yellow")],
+ }
+ result = styler.highlight_quantile(**kwargs)._compute().ctx
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "f,kwargs",
+ [
+ ("highlight_min", {"axis": 1, "subset": IndexSlice[1, :]}),
+ ("highlight_max", {"axis": 0, "subset": [0]}),
+ ("highlight_quantile", {"axis": None, "q_left": 0.6, "q_right": 0.8}),
+ ("highlight_between", {"subset": [0]}),
+ ],
+)
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ int,
+ float,
+ "datetime64[ns]",
+ str,
+ "timedelta64[ns]",
+ ],
+)
+def test_all_highlight_dtypes(f, kwargs, dtype):
+ df = DataFrame([[0, 10], [20, 30]], dtype=dtype)
+ if f == "highlight_quantile" and isinstance(df.iloc[0, 0], (str)):
+ return None # quantile incompatible with str
+ if f == "highlight_between":
+ kwargs["left"] = df.iloc[1, 0] # set the range low for testing
+
+ expected = {(1, 0): [("background-color", "yellow")]}
+ result = getattr(df.style, f)(**kwargs)._compute().ctx
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_html.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_html.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b225126b0a70d9eeca414dc0c11a7e9ca80befc
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_html.py
@@ -0,0 +1,1038 @@
+import pathlib
+from textwrap import (
+ dedent,
+ indent,
+)
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ option_context,
+)
+
+jinja2 = pytest.importorskip("jinja2")
+from pandas.io.formats.style import Styler
+
+
+@pytest.fixture
+def env():
+ project_dir = pathlib.Path(__file__).parent.parent.parent.parent.parent.resolve()
+ template_dir = project_dir / "io" / "formats" / "templates"
+ loader = jinja2.FileSystemLoader(template_dir)
+ env = jinja2.Environment(loader=loader, trim_blocks=True)
+ return env
+
+
+@pytest.fixture
+def styler():
+ return Styler(DataFrame([[2.61], [2.69]], index=["a", "b"], columns=["A"]))
+
+
+@pytest.fixture
+def styler_mi():
+ midx = MultiIndex.from_product([["a", "b"], ["c", "d"]])
+ return Styler(DataFrame(np.arange(16).reshape(4, 4), index=midx, columns=midx))
+
+
+@pytest.fixture
+def styler_multi():
+ df = DataFrame(
+ data=np.arange(16).reshape(4, 4),
+ columns=MultiIndex.from_product([["A", "B"], ["a", "b"]], names=["A&", "b&"]),
+ index=MultiIndex.from_product([["X", "Y"], ["x", "y"]], names=["X>", "y_"]),
+ )
+ return Styler(df)
+
+
+@pytest.fixture
+def tpl_style(env):
+ return env.get_template("html_style.tpl")
+
+
+@pytest.fixture
+def tpl_table(env):
+ return env.get_template("html_table.tpl")
+
+
+def test_html_template_extends_options(datapath):
+ # make sure if templates are edited tests are updated as are setup fixtures
+ # to understand the dependency
+ path = datapath("..", "io", "formats", "templates", "html.tpl")
+ with open(path, encoding="utf-8") as file:
+ result = file.read()
+ assert "{% include html_style_tpl %}" in result
+ assert "{% include html_table_tpl %}" in result
+
+
+def test_exclude_styles(styler):
+ result = styler.to_html(exclude_styles=True, doctype_html=True)
+ expected = dedent(
+ """\
+
+
+
+
+
+
+
+
+
+
+ A
+
+
+
+
+ a
+ 2.610000
+
+
+ b
+ 2.690000
+
+
+
+
+
+ """
+ )
+ assert result == expected
+
+
+def test_w3_html_format(styler):
+ styler.set_uuid("").set_table_styles([{"selector": "th", "props": "att2:v2;"}]).map(
+ lambda x: "att1:v1;"
+ ).set_table_attributes('class="my-cls1" style="attr3:v3;"').set_td_classes(
+ DataFrame(["my-cls2"], index=["a"], columns=["A"])
+ ).format("{:.1f}").set_caption("A comprehensive test")
+ expected = dedent(
+ """\
+
+
+ A comprehensive test
+
+
+
+ A
+
+
+
+
+ a
+ 2.6
+
+
+ b
+ 2.7
+
+
+
+ """
+ )
+ assert expected == styler.to_html()
+
+
+def test_colspan_w3():
+ # GH 36223
+ df = DataFrame(data=[[1, 2]], columns=[["l0", "l0"], ["l1a", "l1b"]])
+ styler = Styler(df, uuid="_", cell_ids=False)
+ assert 'l0 ' in styler.to_html()
+
+
+def test_rowspan_w3():
+ # GH 38533
+ df = DataFrame(data=[[1, 2]], index=[["l0", "l0"], ["l1a", "l1b"]])
+ styler = Styler(df, uuid="_", cell_ids=False)
+ assert 'l0 ' in styler.to_html()
+
+
+def test_styles(styler):
+ styler.set_uuid("abc")
+ styler.set_table_styles([{"selector": "td", "props": "color: red;"}])
+ result = styler.to_html(doctype_html=True)
+ expected = dedent(
+ """\
+
+
+
+
+
+
+
+
+
+
+
+ A
+
+
+
+
+ a
+ 2.610000
+
+
+ b
+ 2.690000
+
+
+
+
+
+ """
+ )
+ assert result == expected
+
+
+def test_doctype(styler):
+ result = styler.to_html(doctype_html=False)
+ assert "" not in result
+ assert "" not in result
+ assert "" not in result
+ assert "" not in result
+
+
+def test_doctype_encoding(styler):
+ with option_context("styler.render.encoding", "ASCII"):
+ result = styler.to_html(doctype_html=True)
+ assert '' in result
+ result = styler.to_html(doctype_html=True, encoding="ANSI")
+ assert '' in result
+
+
+def test_bold_headers_arg(styler):
+ result = styler.to_html(bold_headers=True)
+ assert "th {\n font-weight: bold;\n}" in result
+ result = styler.to_html()
+ assert "th {\n font-weight: bold;\n}" not in result
+
+
+def test_caption_arg(styler):
+ result = styler.to_html(caption="foo bar")
+ assert "foo bar " in result
+ result = styler.to_html()
+ assert "foo bar " not in result
+
+
+def test_block_names(tpl_style, tpl_table):
+ # catch accidental removal of a block
+ expected_style = {
+ "before_style",
+ "style",
+ "table_styles",
+ "before_cellstyle",
+ "cellstyle",
+ }
+ expected_table = {
+ "before_table",
+ "table",
+ "caption",
+ "thead",
+ "tbody",
+ "after_table",
+ "before_head_rows",
+ "head_tr",
+ "after_head_rows",
+ "before_rows",
+ "tr",
+ "after_rows",
+ }
+ result1 = set(tpl_style.blocks)
+ assert result1 == expected_style
+
+ result2 = set(tpl_table.blocks)
+ assert result2 == expected_table
+
+
+def test_from_custom_template_table(tmpdir):
+ p = tmpdir.mkdir("tpl").join("myhtml_table.tpl")
+ p.write(
+ dedent(
+ """\
+ {% extends "html_table.tpl" %}
+ {% block table %}
+ {{custom_title}}
+ {{ super() }}
+ {% endblock table %}"""
+ )
+ )
+ result = Styler.from_custom_template(str(tmpdir.join("tpl")), "myhtml_table.tpl")
+ assert issubclass(result, Styler)
+ assert result.env is not Styler.env
+ assert result.template_html_table is not Styler.template_html_table
+ styler = result(DataFrame({"A": [1, 2]}))
+ assert "My Title
\n\n\n
+ {{ super() }}
+ {% endblock style %}"""
+ )
+ )
+ result = Styler.from_custom_template(
+ str(tmpdir.join("tpl")), html_style="myhtml_style.tpl"
+ )
+ assert issubclass(result, Styler)
+ assert result.env is not Styler.env
+ assert result.template_html_style is not Styler.template_html_style
+ styler = result(DataFrame({"A": [1, 2]}))
+ assert '\n\n
+
+
+
+
+ n1
+ a
+
+
+
+ n2
+ c
+
+
+ n1
+ n2
+
+
+
+
+
+ a
+ c
+ 0
+
+
+
+ """
+ )
+ result = styler_mi.to_html()
+ assert result == expected
+
+
+def test_include_css_style_rules_only_for_visible_cells(styler_mi):
+ # GH 43619
+ result = (
+ styler_mi.set_uuid("")
+ .map(lambda v: "color: blue;")
+ .hide(styler_mi.data.columns[1:], axis="columns")
+ .hide(styler_mi.data.index[1:], axis="index")
+ .to_html()
+ )
+ expected_styles = dedent(
+ """\
+
+ """
+ )
+ assert expected_styles in result
+
+
+def test_include_css_style_rules_only_for_visible_index_labels(styler_mi):
+ # GH 43619
+ result = (
+ styler_mi.set_uuid("")
+ .map_index(lambda v: "color: blue;", axis="index")
+ .hide(styler_mi.data.columns, axis="columns")
+ .hide(styler_mi.data.index[1:], axis="index")
+ .to_html()
+ )
+ expected_styles = dedent(
+ """\
+
+ """
+ )
+ assert expected_styles in result
+
+
+def test_include_css_style_rules_only_for_visible_column_labels(styler_mi):
+ # GH 43619
+ result = (
+ styler_mi.set_uuid("")
+ .map_index(lambda v: "color: blue;", axis="columns")
+ .hide(styler_mi.data.columns[1:], axis="columns")
+ .hide(styler_mi.data.index, axis="index")
+ .to_html()
+ )
+ expected_styles = dedent(
+ """\
+
+ """
+ )
+ assert expected_styles in result
+
+
+def test_hiding_index_columns_multiindex_alignment():
+ # gh 43644
+ midx = MultiIndex.from_product(
+ [["i0", "j0"], ["i1"], ["i2", "j2"]], names=["i-0", "i-1", "i-2"]
+ )
+ cidx = MultiIndex.from_product(
+ [["c0"], ["c1", "d1"], ["c2", "d2"]], names=["c-0", "c-1", "c-2"]
+ )
+ df = DataFrame(np.arange(16).reshape(4, 4), index=midx, columns=cidx)
+ styler = Styler(df, uuid_len=0)
+ styler.hide(level=1, axis=0).hide(level=0, axis=1)
+ styler.hide([("j0", "i1", "j2")], axis=0)
+ styler.hide([("c0", "d1", "d2")], axis=1)
+ result = styler.to_html()
+ expected = dedent(
+ """\
+
+
+
+
+
+ c-1
+ c1
+ d1
+
+
+
+ c-2
+ c2
+ d2
+ c2
+
+
+ i-0
+ i-2
+
+
+
+
+
+
+
+ i0
+ i2
+ 0
+ 1
+ 2
+
+
+ j2
+ 4
+ 5
+ 6
+
+
+ j0
+ i2
+ 8
+ 9
+ 10
+
+
+
+ """
+ )
+ assert result == expected
+
+
+def test_hiding_index_columns_multiindex_trimming():
+ # gh 44272
+ df = DataFrame(np.arange(64).reshape(8, 8))
+ df.columns = MultiIndex.from_product([[0, 1, 2, 3], [0, 1]])
+ df.index = MultiIndex.from_product([[0, 1, 2, 3], [0, 1]])
+ df.index.names, df.columns.names = ["a", "b"], ["c", "d"]
+ styler = Styler(df, cell_ids=False, uuid_len=0)
+ styler.hide([(0, 0), (0, 1), (1, 0)], axis=1).hide([(0, 0), (0, 1), (1, 0)], axis=0)
+ with option_context("styler.render.max_rows", 4, "styler.render.max_columns", 4):
+ result = styler.to_html()
+
+ expected = dedent(
+ """\
+
+
+
+
+
+ c
+ 1
+ 2
+ 3
+
+
+
+ d
+ 1
+ 0
+ 1
+ 0
+ ...
+
+
+ a
+ b
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+ 27
+ 28
+ 29
+ 30
+ ...
+
+
+ 2
+ 0
+ 35
+ 36
+ 37
+ 38
+ ...
+
+
+ 1
+ 43
+ 44
+ 45
+ 46
+ ...
+
+
+ 3
+ 0
+ 51
+ 52
+ 53
+ 54
+ ...
+
+
+ ...
+ ...
+ ...
+ ...
+ ...
+ ...
+ ...
+
+
+
+ """
+ )
+
+ assert result == expected
+
+
+@pytest.mark.parametrize("type", ["data", "index"])
+@pytest.mark.parametrize(
+ "text, exp, found",
+ [
+ ("no link, just text", False, ""),
+ ("subdomain not www: sub.web.com", False, ""),
+ ("www subdomain: www.web.com other", True, "www.web.com"),
+ ("scheme full structure: http://www.web.com", True, "http://www.web.com"),
+ ("scheme no top-level: http://www.web", True, "http://www.web"),
+ ("no scheme, no top-level: www.web", False, "www.web"),
+ ("https scheme: https://www.web.com", True, "https://www.web.com"),
+ ("ftp scheme: ftp://www.web", True, "ftp://www.web"),
+ ("ftps scheme: ftps://www.web", True, "ftps://www.web"),
+ ("subdirectories: www.web.com/directory", True, "www.web.com/directory"),
+ ("Multiple domains: www.1.2.3.4", True, "www.1.2.3.4"),
+ ("with port: http://web.com:80", True, "http://web.com:80"),
+ (
+ "full net_loc scheme: http://user:pass@web.com",
+ True,
+ "http://user:pass@web.com",
+ ),
+ (
+ "with valid special chars: http://web.com/,.':;~!@#$*()[]",
+ True,
+ "http://web.com/,.':;~!@#$*()[]",
+ ),
+ ],
+)
+def test_rendered_links(type, text, exp, found):
+ if type == "data":
+ df = DataFrame([text])
+ styler = df.style.format(hyperlinks="html")
+ else:
+ df = DataFrame([0], index=[text])
+ styler = df.style.format_index(hyperlinks="html")
+
+ rendered = f'{found}'
+ result = styler.to_html()
+ assert (rendered in result) is exp
+ assert (text in result) is not exp # test conversion done when expected and not
+
+
+def test_multiple_rendered_links():
+ links = ("www.a.b", "http://a.c", "https://a.d", "ftp://a.e")
+ df = DataFrame(["text {} {} text {} {}".format(*links)])
+ result = df.style.format(hyperlinks="html").to_html()
+ href = '{0}'
+ for link in links:
+ assert href.format(link) in result
+ assert href.format("text") not in result
+
+
+def test_concat(styler):
+ other = styler.data.agg(["mean"]).style
+ styler.concat(other).set_uuid("X")
+ result = styler.to_html()
+ fp = "foot0_"
+ expected = dedent(
+ f"""\
+
+ b
+ 2.690000
+
+
+ mean
+ 2.650000
+
+
+
+ """
+ )
+ assert expected in result
+
+
+def test_concat_recursion(styler):
+ df = styler.data
+ styler1 = styler
+ styler2 = Styler(df.agg(["mean"]), precision=3)
+ styler3 = Styler(df.agg(["mean"]), precision=4)
+ styler1.concat(styler2.concat(styler3)).set_uuid("X")
+ result = styler.to_html()
+ # notice that the second concat (last of the output html),
+ # there are two `foot_` in the id and class
+ fp1 = "foot0_"
+ fp2 = "foot0_foot0_"
+ expected = dedent(
+ f"""\
+
+ b
+ 2.690000
+
+
+ mean
+ 2.650
+
+
+ mean
+ 2.6500
+
+
+
+ """
+ )
+ assert expected in result
+
+
+def test_concat_chain(styler):
+ df = styler.data
+ styler1 = styler
+ styler2 = Styler(df.agg(["mean"]), precision=3)
+ styler3 = Styler(df.agg(["mean"]), precision=4)
+ styler1.concat(styler2).concat(styler3).set_uuid("X")
+ result = styler.to_html()
+ fp1 = "foot0_"
+ fp2 = "foot1_"
+ expected = dedent(
+ f"""\
+
+ b
+ 2.690000
+
+
+ mean
+ 2.650
+
+
+ mean
+ 2.6500
+
+
+
+ """
+ )
+ assert expected in result
+
+
+def test_concat_combined():
+ def html_lines(foot_prefix: str):
+ assert foot_prefix.endswith("_") or foot_prefix == ""
+ fp = foot_prefix
+ return indent(
+ dedent(
+ f"""\
+
+ a
+ 2.610000
+
+
+ b
+ 2.690000
+
+ """
+ ),
+ prefix=" " * 4,
+ )
+
+ df = DataFrame([[2.61], [2.69]], index=["a", "b"], columns=["A"])
+ s1 = df.style.highlight_max(color="red")
+ s2 = df.style.highlight_max(color="green")
+ s3 = df.style.highlight_max(color="blue")
+ s4 = df.style.highlight_max(color="yellow")
+
+ result = s1.concat(s2).concat(s3.concat(s4)).set_uuid("X").to_html()
+ expected_css = dedent(
+ """\
+
+ """
+ )
+ expected_table = (
+ dedent(
+ """\
+
+
+
+
+ A
+
+
+
+ """
+ )
+ + html_lines("")
+ + html_lines("foot0_")
+ + html_lines("foot1_")
+ + html_lines("foot1_foot0_")
+ + dedent(
+ """\
+
+
+ """
+ )
+ )
+ assert expected_css + expected_table == result
+
+
+def test_to_html_na_rep_non_scalar_data(datapath):
+ # GH47103
+ df = DataFrame([{"a": 1, "b": [1, 2, 3], "c": np.nan}])
+ result = df.style.format(na_rep="-").to_html(table_uuid="test")
+ expected = """\
+
+
+
+
+
+ a
+ b
+ c
+
+
+
+
+ 0
+ 1
+ [1, 2, 3]
+ -
+
+
+
+"""
+ assert result == expected
+
+
+@pytest.mark.parametrize("escape_axis_0", [True, False])
+@pytest.mark.parametrize("escape_axis_1", [True, False])
+def test_format_index_names(styler_multi, escape_axis_0, escape_axis_1):
+ if escape_axis_0:
+ styler_multi.format_index_names(axis=0, escape="html")
+ expected_index = ["X>", "y_"]
+ else:
+ expected_index = ["X>", "y_"]
+
+ if escape_axis_1:
+ styler_multi.format_index_names(axis=1, escape="html")
+ expected_columns = ["A&", "b&"]
+ else:
+ expected_columns = ["A&", "b&"]
+
+ result = styler_multi.to_html(table_uuid="test")
+ for expected_str in expected_index + expected_columns:
+ assert f"{expected_str}" in result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_matplotlib.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_matplotlib.py
new file mode 100644
index 0000000000000000000000000000000000000000..490bd45bfb2eea246f3c997a8a9e8298b2a02bf4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_matplotlib.py
@@ -0,0 +1,305 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ IndexSlice,
+ Series,
+)
+
+mpl = pytest.importorskip("matplotlib")
+pytest.importorskip("jinja2")
+
+from pandas.io.formats.style import Styler
+
+pytestmark = pytest.mark.usefixtures("mpl_cleanup")
+
+
+@pytest.fixture
+def df():
+ return DataFrame([[1, 2], [2, 4]], columns=["A", "B"])
+
+
+@pytest.fixture
+def styler(df):
+ return Styler(df, uuid_len=0)
+
+
+@pytest.fixture
+def df_blank():
+ return DataFrame([[0, 0], [0, 0]], columns=["A", "B"], index=["X", "Y"])
+
+
+@pytest.fixture
+def styler_blank(df_blank):
+ return Styler(df_blank, uuid_len=0)
+
+
+@pytest.mark.parametrize("f", ["background_gradient", "text_gradient"])
+def test_function_gradient(styler, f):
+ for c_map in [None, "YlOrRd"]:
+ result = getattr(styler, f)(cmap=c_map)._compute().ctx
+ assert all("#" in x[0][1] for x in result.values())
+ assert result[(0, 0)] == result[(0, 1)]
+ assert result[(1, 0)] == result[(1, 1)]
+
+
+@pytest.mark.parametrize("f", ["background_gradient", "text_gradient"])
+def test_background_gradient_color(styler, f):
+ result = getattr(styler, f)(subset=IndexSlice[1, "A"])._compute().ctx
+ if f == "background_gradient":
+ assert result[(1, 0)] == [("background-color", "#fff7fb"), ("color", "#000000")]
+ elif f == "text_gradient":
+ assert result[(1, 0)] == [("color", "#fff7fb")]
+
+
+@pytest.mark.parametrize(
+ "axis, expected",
+ [
+ (0, ["low", "low", "high", "high"]),
+ (1, ["low", "high", "low", "high"]),
+ (None, ["low", "mid", "mid", "high"]),
+ ],
+)
+@pytest.mark.parametrize("f", ["background_gradient", "text_gradient"])
+def test_background_gradient_axis(styler, axis, expected, f):
+ if f == "background_gradient":
+ colors = {
+ "low": [("background-color", "#f7fbff"), ("color", "#000000")],
+ "mid": [("background-color", "#abd0e6"), ("color", "#000000")],
+ "high": [("background-color", "#08306b"), ("color", "#f1f1f1")],
+ }
+ elif f == "text_gradient":
+ colors = {
+ "low": [("color", "#f7fbff")],
+ "mid": [("color", "#abd0e6")],
+ "high": [("color", "#08306b")],
+ }
+ result = getattr(styler, f)(cmap="Blues", axis=axis)._compute().ctx
+ for i, cell in enumerate([(0, 0), (0, 1), (1, 0), (1, 1)]):
+ assert result[cell] == colors[expected[i]]
+
+
+@pytest.mark.parametrize(
+ "cmap, expected",
+ [
+ (
+ "PuBu",
+ {
+ (4, 5): [("background-color", "#86b0d3"), ("color", "#000000")],
+ (4, 6): [("background-color", "#83afd3"), ("color", "#f1f1f1")],
+ },
+ ),
+ (
+ "YlOrRd",
+ {
+ (4, 8): [("background-color", "#fd913e"), ("color", "#000000")],
+ (4, 9): [("background-color", "#fd8f3d"), ("color", "#f1f1f1")],
+ },
+ ),
+ (
+ None,
+ {
+ (7, 0): [("background-color", "#48c16e"), ("color", "#f1f1f1")],
+ (7, 1): [("background-color", "#4cc26c"), ("color", "#000000")],
+ },
+ ),
+ ],
+)
+def test_text_color_threshold(cmap, expected):
+ # GH 39888
+ df = DataFrame(np.arange(100).reshape(10, 10))
+ result = df.style.background_gradient(cmap=cmap, axis=None)._compute().ctx
+ for k in expected.keys():
+ assert result[k] == expected[k]
+
+
+def test_background_gradient_vmin_vmax():
+ # GH 12145
+ df = DataFrame(range(5))
+ ctx = df.style.background_gradient(vmin=1, vmax=3)._compute().ctx
+ assert ctx[(0, 0)] == ctx[(1, 0)]
+ assert ctx[(4, 0)] == ctx[(3, 0)]
+
+
+def test_background_gradient_int64():
+ # GH 28869
+ df1 = Series(range(3)).to_frame()
+ df2 = Series(range(3), dtype="Int64").to_frame()
+ ctx1 = df1.style.background_gradient()._compute().ctx
+ ctx2 = df2.style.background_gradient()._compute().ctx
+ assert ctx2[(0, 0)] == ctx1[(0, 0)]
+ assert ctx2[(1, 0)] == ctx1[(1, 0)]
+ assert ctx2[(2, 0)] == ctx1[(2, 0)]
+
+
+@pytest.mark.parametrize(
+ "axis, gmap, expected",
+ [
+ (
+ 0,
+ [1, 2],
+ {
+ (0, 0): [("background-color", "#fff7fb"), ("color", "#000000")],
+ (1, 0): [("background-color", "#023858"), ("color", "#f1f1f1")],
+ (0, 1): [("background-color", "#fff7fb"), ("color", "#000000")],
+ (1, 1): [("background-color", "#023858"), ("color", "#f1f1f1")],
+ },
+ ),
+ (
+ 1,
+ [1, 2],
+ {
+ (0, 0): [("background-color", "#fff7fb"), ("color", "#000000")],
+ (1, 0): [("background-color", "#fff7fb"), ("color", "#000000")],
+ (0, 1): [("background-color", "#023858"), ("color", "#f1f1f1")],
+ (1, 1): [("background-color", "#023858"), ("color", "#f1f1f1")],
+ },
+ ),
+ (
+ None,
+ np.array([[2, 1], [1, 2]]),
+ {
+ (0, 0): [("background-color", "#023858"), ("color", "#f1f1f1")],
+ (1, 0): [("background-color", "#fff7fb"), ("color", "#000000")],
+ (0, 1): [("background-color", "#fff7fb"), ("color", "#000000")],
+ (1, 1): [("background-color", "#023858"), ("color", "#f1f1f1")],
+ },
+ ),
+ ],
+)
+def test_background_gradient_gmap_array(styler_blank, axis, gmap, expected):
+ # tests when gmap is given as a sequence and converted to ndarray
+ result = styler_blank.background_gradient(axis=axis, gmap=gmap)._compute().ctx
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "gmap, axis", [([1, 2, 3], 0), ([1, 2], 1), (np.array([[1, 2], [1, 2]]), None)]
+)
+def test_background_gradient_gmap_array_raises(gmap, axis):
+ # test when gmap as converted ndarray is bad shape
+ df = DataFrame([[0, 0, 0], [0, 0, 0]])
+ msg = "supplied 'gmap' is not correct shape"
+ with pytest.raises(ValueError, match=msg):
+ df.style.background_gradient(gmap=gmap, axis=axis)._compute()
+
+
+@pytest.mark.parametrize(
+ "gmap",
+ [
+ DataFrame( # reverse the columns
+ [[2, 1], [1, 2]], columns=["B", "A"], index=["X", "Y"]
+ ),
+ DataFrame( # reverse the index
+ [[2, 1], [1, 2]], columns=["A", "B"], index=["Y", "X"]
+ ),
+ DataFrame( # reverse the index and columns
+ [[1, 2], [2, 1]], columns=["B", "A"], index=["Y", "X"]
+ ),
+ DataFrame( # add unnecessary columns
+ [[1, 2, 3], [2, 1, 3]], columns=["A", "B", "C"], index=["X", "Y"]
+ ),
+ DataFrame( # add unnecessary index
+ [[1, 2], [2, 1], [3, 3]], columns=["A", "B"], index=["X", "Y", "Z"]
+ ),
+ ],
+)
+@pytest.mark.parametrize(
+ "subset, exp_gmap", # exp_gmap is underlying map DataFrame should conform to
+ [
+ (None, [[1, 2], [2, 1]]),
+ (["A"], [[1], [2]]), # slice only column "A" in data and gmap
+ (["B", "A"], [[2, 1], [1, 2]]), # reverse the columns in data
+ (IndexSlice["X", :], [[1, 2]]), # slice only index "X" in data and gmap
+ (IndexSlice[["Y", "X"], :], [[2, 1], [1, 2]]), # reverse the index in data
+ ],
+)
+def test_background_gradient_gmap_dataframe_align(styler_blank, gmap, subset, exp_gmap):
+ # test gmap given as DataFrame that it aligns to the data including subset
+ expected = styler_blank.background_gradient(axis=None, gmap=exp_gmap, subset=subset)
+ result = styler_blank.background_gradient(axis=None, gmap=gmap, subset=subset)
+ assert expected._compute().ctx == result._compute().ctx
+
+
+@pytest.mark.parametrize(
+ "gmap, axis, exp_gmap",
+ [
+ (Series([2, 1], index=["Y", "X"]), 0, [[1, 1], [2, 2]]), # reverse the index
+ (Series([2, 1], index=["B", "A"]), 1, [[1, 2], [1, 2]]), # reverse the cols
+ (Series([1, 2, 3], index=["X", "Y", "Z"]), 0, [[1, 1], [2, 2]]), # add idx
+ (Series([1, 2, 3], index=["A", "B", "C"]), 1, [[1, 2], [1, 2]]), # add col
+ ],
+)
+def test_background_gradient_gmap_series_align(styler_blank, gmap, axis, exp_gmap):
+ # test gmap given as Series that it aligns to the data including subset
+ expected = styler_blank.background_gradient(axis=None, gmap=exp_gmap)._compute()
+ result = styler_blank.background_gradient(axis=axis, gmap=gmap)._compute()
+ assert expected.ctx == result.ctx
+
+
+@pytest.mark.parametrize("axis", [1, 0])
+def test_background_gradient_gmap_wrong_dataframe(styler_blank, axis):
+ # test giving a gmap in DataFrame but with wrong axis
+ gmap = DataFrame([[1, 2], [2, 1]], columns=["A", "B"], index=["X", "Y"])
+ msg = "'gmap' is a DataFrame but underlying data for operations is a Series"
+ with pytest.raises(ValueError, match=msg):
+ styler_blank.background_gradient(gmap=gmap, axis=axis)._compute()
+
+
+def test_background_gradient_gmap_wrong_series(styler_blank):
+ # test giving a gmap in Series form but with wrong axis
+ msg = "'gmap' is a Series but underlying data for operations is a DataFrame"
+ gmap = Series([1, 2], index=["X", "Y"])
+ with pytest.raises(ValueError, match=msg):
+ styler_blank.background_gradient(gmap=gmap, axis=None)._compute()
+
+
+def test_background_gradient_nullable_dtypes():
+ # GH 50712
+ df1 = DataFrame([[1], [0], [np.nan]], dtype=float)
+ df2 = DataFrame([[1], [0], [None]], dtype="Int64")
+
+ ctx1 = df1.style.background_gradient()._compute().ctx
+ ctx2 = df2.style.background_gradient()._compute().ctx
+ assert ctx1 == ctx2
+
+
+@pytest.mark.parametrize(
+ "cmap",
+ ["PuBu", mpl.colormaps["PuBu"]],
+)
+def test_bar_colormap(cmap):
+ data = DataFrame([[1, 2], [3, 4]])
+ ctx = data.style.bar(cmap=cmap, axis=None)._compute().ctx
+ pubu_colors = {
+ (0, 0): "#d0d1e6",
+ (1, 0): "#056faf",
+ (0, 1): "#73a9cf",
+ (1, 1): "#023858",
+ }
+ for k, v in pubu_colors.items():
+ assert v in ctx[k][1][1]
+
+
+def test_bar_color_raises(df):
+ msg = "`color` must be string or list or tuple of 2 strings"
+ with pytest.raises(ValueError, match=msg):
+ df.style.bar(color={"a", "b"}).to_html()
+ with pytest.raises(ValueError, match=msg):
+ df.style.bar(color=["a", "b", "c"]).to_html()
+
+ msg = "`color` and `cmap` cannot both be given"
+ with pytest.raises(ValueError, match=msg):
+ df.style.bar(color="something", cmap="something else").to_html()
+
+
+@pytest.mark.parametrize("plot_method", ["scatter", "hexbin"])
+def test_pass_colormap_instance(df, plot_method):
+ # https://github.com/pandas-dev/pandas/issues/49374
+ cmap = mpl.colors.ListedColormap([[1, 1, 1], [0, 0, 0]])
+ df["c"] = df.A + df.B
+ kwargs = {"x": "A", "y": "B", "c": "c", "colormap": cmap}
+ if plot_method == "hexbin":
+ kwargs["C"] = kwargs.pop("c")
+ getattr(df.plot, plot_method)(**kwargs)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_non_unique.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_non_unique.py
new file mode 100644
index 0000000000000000000000000000000000000000..e4d31fe21f2c9cf3454a67f8c7443382f7f1c0ef
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_non_unique.py
@@ -0,0 +1,140 @@
+from textwrap import dedent
+
+import pytest
+
+from pandas import (
+ DataFrame,
+ IndexSlice,
+)
+
+pytest.importorskip("jinja2")
+
+from pandas.io.formats.style import Styler
+
+
+@pytest.fixture
+def df():
+ return DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ index=["i", "j", "j"],
+ columns=["c", "d", "d"],
+ dtype=float,
+ )
+
+
+@pytest.fixture
+def styler(df):
+ return Styler(df, uuid_len=0)
+
+
+def test_format_non_unique(df):
+ # GH 41269
+
+ # test dict
+ html = df.style.format({"d": "{:.1f}"}).to_html()
+ for val in ["1.000000<", "4.000000<", "7.000000<"]:
+ assert val in html
+ for val in ["2.0<", "3.0<", "5.0<", "6.0<", "8.0<", "9.0<"]:
+ assert val in html
+
+ # test subset
+ html = df.style.format(precision=1, subset=IndexSlice["j", "d"]).to_html()
+ for val in ["1.000000<", "4.000000<", "7.000000<", "2.000000<", "3.000000<"]:
+ assert val in html
+ for val in ["5.0<", "6.0<", "8.0<", "9.0<"]:
+ assert val in html
+
+
+@pytest.mark.parametrize("func", ["apply", "map"])
+def test_apply_map_non_unique_raises(df, func):
+ # GH 41269
+ if func == "apply":
+ op = lambda s: ["color: red;"] * len(s)
+ else:
+ op = lambda v: "color: red;"
+
+ with pytest.raises(KeyError, match="`Styler.apply` and `.map` are not"):
+ getattr(df.style, func)(op)._compute()
+
+
+def test_table_styles_dict_non_unique_index(styler):
+ styles = styler.set_table_styles(
+ {"j": [{"selector": "td", "props": "a: v;"}]}, axis=1
+ ).table_styles
+ assert styles == [
+ {"selector": "td.row1", "props": [("a", "v")]},
+ {"selector": "td.row2", "props": [("a", "v")]},
+ ]
+
+
+def test_table_styles_dict_non_unique_columns(styler):
+ styles = styler.set_table_styles(
+ {"d": [{"selector": "td", "props": "a: v;"}]}, axis=0
+ ).table_styles
+ assert styles == [
+ {"selector": "td.col1", "props": [("a", "v")]},
+ {"selector": "td.col2", "props": [("a", "v")]},
+ ]
+
+
+def test_tooltips_non_unique_raises(styler):
+ # ttips has unique keys
+ ttips = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "d"], index=["a", "b"])
+ styler.set_tooltips(ttips=ttips) # OK
+
+ # ttips has non-unique columns
+ ttips = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "c"], index=["a", "b"])
+ with pytest.raises(KeyError, match="Tooltips render only if `ttips` has unique"):
+ styler.set_tooltips(ttips=ttips)
+
+ # ttips has non-unique index
+ ttips = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "d"], index=["a", "a"])
+ with pytest.raises(KeyError, match="Tooltips render only if `ttips` has unique"):
+ styler.set_tooltips(ttips=ttips)
+
+
+def test_set_td_classes_non_unique_raises(styler):
+ # classes has unique keys
+ classes = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "d"], index=["a", "b"])
+ styler.set_td_classes(classes=classes) # OK
+
+ # classes has non-unique columns
+ classes = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "c"], index=["a", "b"])
+ with pytest.raises(KeyError, match="Classes render only if `classes` has unique"):
+ styler.set_td_classes(classes=classes)
+
+ # classes has non-unique index
+ classes = DataFrame([["1", "2"], ["3", "4"]], columns=["c", "d"], index=["a", "a"])
+ with pytest.raises(KeyError, match="Classes render only if `classes` has unique"):
+ styler.set_td_classes(classes=classes)
+
+
+def test_hide_columns_non_unique(styler):
+ ctx = styler.hide(["d"], axis="columns")._translate(True, True)
+
+ assert ctx["head"][0][1]["display_value"] == "c"
+ assert ctx["head"][0][1]["is_visible"] is True
+
+ assert ctx["head"][0][2]["display_value"] == "d"
+ assert ctx["head"][0][2]["is_visible"] is False
+
+ assert ctx["head"][0][3]["display_value"] == "d"
+ assert ctx["head"][0][3]["is_visible"] is False
+
+ assert ctx["body"][0][1]["is_visible"] is True
+ assert ctx["body"][0][2]["is_visible"] is False
+ assert ctx["body"][0][3]["is_visible"] is False
+
+
+def test_latex_non_unique(styler):
+ result = styler.to_latex()
+ assert result == dedent(
+ """\
+ \\begin{tabular}{lrrr}
+ & c & d & d \\\\
+ i & 1.000000 & 2.000000 & 3.000000 \\\\
+ j & 4.000000 & 5.000000 & 6.000000 \\\\
+ j & 7.000000 & 8.000000 & 9.000000 \\\\
+ \\end{tabular}
+ """
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_style.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_style.py
new file mode 100644
index 0000000000000000000000000000000000000000..d78403771e227dd97eba2a130b4e60a0303c4400
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_style.py
@@ -0,0 +1,1602 @@
+import contextlib
+import copy
+import re
+from textwrap import dedent
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ IndexSlice,
+ MultiIndex,
+ Series,
+ option_context,
+)
+import pandas._testing as tm
+
+jinja2 = pytest.importorskip("jinja2")
+from pandas.io.formats.style import ( # isort:skip
+ Styler,
+)
+from pandas.io.formats.style_render import (
+ _get_level_lengths,
+ _get_trimming_maximums,
+ maybe_convert_css_to_tuples,
+ non_reducing_slice,
+)
+
+
+@pytest.fixture
+def mi_df():
+ return DataFrame(
+ [[1, 2], [3, 4]],
+ index=MultiIndex.from_product([["i0"], ["i1_a", "i1_b"]]),
+ columns=MultiIndex.from_product([["c0"], ["c1_a", "c1_b"]]),
+ dtype=int,
+ )
+
+
+@pytest.fixture
+def mi_styler(mi_df):
+ return Styler(mi_df, uuid_len=0)
+
+
+@pytest.fixture
+def mi_styler_comp(mi_styler):
+ # comprehensively add features to mi_styler
+ mi_styler = mi_styler._copy(deepcopy=True)
+ mi_styler.css = {**mi_styler.css, "row": "ROW", "col": "COL"}
+ mi_styler.uuid_len = 5
+ mi_styler.uuid = "abcde"
+ mi_styler.set_caption("capt")
+ mi_styler.set_table_styles([{"selector": "a", "props": "a:v;"}])
+ mi_styler.hide(axis="columns")
+ mi_styler.hide([("c0", "c1_a")], axis="columns", names=True)
+ mi_styler.hide(axis="index")
+ mi_styler.hide([("i0", "i1_a")], axis="index", names=True)
+ mi_styler.set_table_attributes('class="box"')
+ other = mi_styler.data.agg(["mean"])
+ other.index = MultiIndex.from_product([[""], other.index])
+ mi_styler.concat(other.style)
+ mi_styler.format(na_rep="MISSING", precision=3)
+ mi_styler.format_index(precision=2, axis=0)
+ mi_styler.format_index(precision=4, axis=1)
+ mi_styler.highlight_max(axis=None)
+ mi_styler.map_index(lambda x: "color: white;", axis=0)
+ mi_styler.map_index(lambda x: "color: black;", axis=1)
+ mi_styler.set_td_classes(
+ DataFrame(
+ [["a", "b"], ["a", "c"]], index=mi_styler.index, columns=mi_styler.columns
+ )
+ )
+ mi_styler.set_tooltips(
+ DataFrame(
+ [["a2", "b2"], ["a2", "c2"]],
+ index=mi_styler.index,
+ columns=mi_styler.columns,
+ )
+ )
+ mi_styler.format_index_names(escape="html", axis=0)
+ mi_styler.format_index_names(escape="html", axis=1)
+ return mi_styler
+
+
+@pytest.fixture
+def blank_value():
+ return " "
+
+
+@pytest.fixture
+def df():
+ df = DataFrame({"A": [0, 1], "B": np.random.default_rng(2).standard_normal(2)})
+ return df
+
+
+@pytest.fixture
+def styler(df):
+ df = DataFrame({"A": [0, 1], "B": np.random.default_rng(2).standard_normal(2)})
+ return Styler(df)
+
+
+@pytest.mark.parametrize(
+ "sparse_columns, exp_cols",
+ [
+ (
+ True,
+ [
+ {"is_visible": True, "attributes": 'colspan="2"', "value": "c0"},
+ {"is_visible": False, "attributes": "", "value": "c0"},
+ ],
+ ),
+ (
+ False,
+ [
+ {"is_visible": True, "attributes": "", "value": "c0"},
+ {"is_visible": True, "attributes": "", "value": "c0"},
+ ],
+ ),
+ ],
+)
+def test_mi_styler_sparsify_columns(mi_styler, sparse_columns, exp_cols):
+ exp_l1_c0 = {"is_visible": True, "attributes": "", "display_value": "c1_a"}
+ exp_l1_c1 = {"is_visible": True, "attributes": "", "display_value": "c1_b"}
+
+ ctx = mi_styler._translate(True, sparse_columns)
+
+ assert exp_cols[0].items() <= ctx["head"][0][2].items()
+ assert exp_cols[1].items() <= ctx["head"][0][3].items()
+ assert exp_l1_c0.items() <= ctx["head"][1][2].items()
+ assert exp_l1_c1.items() <= ctx["head"][1][3].items()
+
+
+@pytest.mark.parametrize(
+ "sparse_index, exp_rows",
+ [
+ (
+ True,
+ [
+ {"is_visible": True, "attributes": 'rowspan="2"', "value": "i0"},
+ {"is_visible": False, "attributes": "", "value": "i0"},
+ ],
+ ),
+ (
+ False,
+ [
+ {"is_visible": True, "attributes": "", "value": "i0"},
+ {"is_visible": True, "attributes": "", "value": "i0"},
+ ],
+ ),
+ ],
+)
+def test_mi_styler_sparsify_index(mi_styler, sparse_index, exp_rows):
+ exp_l1_r0 = {"is_visible": True, "attributes": "", "display_value": "i1_a"}
+ exp_l1_r1 = {"is_visible": True, "attributes": "", "display_value": "i1_b"}
+
+ ctx = mi_styler._translate(sparse_index, True)
+
+ assert exp_rows[0].items() <= ctx["body"][0][0].items()
+ assert exp_rows[1].items() <= ctx["body"][1][0].items()
+ assert exp_l1_r0.items() <= ctx["body"][0][1].items()
+ assert exp_l1_r1.items() <= ctx["body"][1][1].items()
+
+
+def test_mi_styler_sparsify_options(mi_styler):
+ with option_context("styler.sparse.index", False):
+ html1 = mi_styler.to_html()
+ with option_context("styler.sparse.index", True):
+ html2 = mi_styler.to_html()
+
+ assert html1 != html2
+
+ with option_context("styler.sparse.columns", False):
+ html1 = mi_styler.to_html()
+ with option_context("styler.sparse.columns", True):
+ html2 = mi_styler.to_html()
+
+ assert html1 != html2
+
+
+@pytest.mark.parametrize(
+ "rn, cn, max_els, max_rows, max_cols, exp_rn, exp_cn",
+ [
+ (100, 100, 100, None, None, 12, 6), # reduce to (12, 6) < 100 elements
+ (1000, 3, 750, None, None, 250, 3), # dynamically reduce rows to 250, keep cols
+ (4, 1000, 500, None, None, 4, 125), # dynamically reduce cols to 125, keep rows
+ (1000, 3, 750, 10, None, 10, 3), # overwrite above dynamics with max_row
+ (4, 1000, 500, None, 5, 4, 5), # overwrite above dynamics with max_col
+ (100, 100, 700, 50, 50, 25, 25), # rows cols below given maxes so < 700 elmts
+ ],
+)
+def test_trimming_maximum(rn, cn, max_els, max_rows, max_cols, exp_rn, exp_cn):
+ rn, cn = _get_trimming_maximums(
+ rn, cn, max_els, max_rows, max_cols, scaling_factor=0.5
+ )
+ assert (rn, cn) == (exp_rn, exp_cn)
+
+
+@pytest.mark.parametrize(
+ "option, val",
+ [
+ ("styler.render.max_elements", 6),
+ ("styler.render.max_rows", 3),
+ ],
+)
+def test_render_trimming_rows(option, val):
+ # test auto and specific trimming of rows
+ df = DataFrame(np.arange(120).reshape(60, 2))
+ with option_context(option, val):
+ ctx = df.style._translate(True, True)
+ assert len(ctx["head"][0]) == 3 # index + 2 data cols
+ assert len(ctx["body"]) == 4 # 3 data rows + trimming row
+ assert len(ctx["body"][0]) == 3 # index + 2 data cols
+
+
+@pytest.mark.parametrize(
+ "option, val",
+ [
+ ("styler.render.max_elements", 6),
+ ("styler.render.max_columns", 2),
+ ],
+)
+def test_render_trimming_cols(option, val):
+ # test auto and specific trimming of cols
+ df = DataFrame(np.arange(30).reshape(3, 10))
+ with option_context(option, val):
+ ctx = df.style._translate(True, True)
+ assert len(ctx["head"][0]) == 4 # index + 2 data cols + trimming col
+ assert len(ctx["body"]) == 3 # 3 data rows
+ assert len(ctx["body"][0]) == 4 # index + 2 data cols + trimming col
+
+
+def test_render_trimming_mi():
+ midx = MultiIndex.from_product([[1, 2], [1, 2, 3]])
+ df = DataFrame(np.arange(36).reshape(6, 6), columns=midx, index=midx)
+ with option_context("styler.render.max_elements", 4):
+ ctx = df.style._translate(True, True)
+
+ assert len(ctx["body"][0]) == 5 # 2 indexes + 2 data cols + trimming row
+ assert {"attributes": 'rowspan="2"'}.items() <= ctx["body"][0][0].items()
+ assert {"class": "data row0 col_trim"}.items() <= ctx["body"][0][4].items()
+ assert {"class": "data row_trim col_trim"}.items() <= ctx["body"][2][4].items()
+ assert len(ctx["body"]) == 3 # 2 data rows + trimming row
+
+
+def test_render_empty_mi():
+ # GH 43305
+ df = DataFrame(index=MultiIndex.from_product([["A"], [0, 1]], names=[None, "one"]))
+ expected = dedent(
+ """\
+ >
+
+
+
+ one
+
+
+ """
+ )
+ assert expected in df.style.to_html()
+
+
+@pytest.mark.parametrize("comprehensive", [True, False])
+@pytest.mark.parametrize("render", [True, False])
+@pytest.mark.parametrize("deepcopy", [True, False])
+def test_copy(comprehensive, render, deepcopy, mi_styler, mi_styler_comp):
+ styler = mi_styler_comp if comprehensive else mi_styler
+ styler.uuid_len = 5
+
+ s2 = copy.deepcopy(styler) if deepcopy else copy.copy(styler) # make copy and check
+ assert s2 is not styler
+
+ if render:
+ styler.to_html()
+
+ excl = [
+ "cellstyle_map", # render time vars..
+ "cellstyle_map_columns",
+ "cellstyle_map_index",
+ "template_latex", # render templates are class level
+ "template_html",
+ "template_html_style",
+ "template_html_table",
+ ]
+ if not deepcopy: # check memory locations are equal for all included attributes
+ for attr in [a for a in styler.__dict__ if (not callable(a) and a not in excl)]:
+ assert id(getattr(s2, attr)) == id(getattr(styler, attr))
+ else: # check memory locations are different for nested or mutable vars
+ shallow = [
+ "data",
+ "columns",
+ "index",
+ "uuid_len",
+ "uuid",
+ "caption",
+ "cell_ids",
+ "hide_index_",
+ "hide_columns_",
+ "hide_index_names",
+ "hide_column_names",
+ "table_attributes",
+ ]
+ for attr in shallow:
+ assert id(getattr(s2, attr)) == id(getattr(styler, attr))
+
+ for attr in [
+ a
+ for a in styler.__dict__
+ if (not callable(a) and a not in excl and a not in shallow)
+ ]:
+ if getattr(s2, attr) is None:
+ assert id(getattr(s2, attr)) == id(getattr(styler, attr))
+ else:
+ assert id(getattr(s2, attr)) != id(getattr(styler, attr))
+
+
+@pytest.mark.parametrize("deepcopy", [True, False])
+def test_inherited_copy(mi_styler, deepcopy):
+ # Ensure that the inherited class is preserved when a Styler object is copied.
+ # GH 52728
+ class CustomStyler(Styler):
+ pass
+
+ custom_styler = CustomStyler(mi_styler.data)
+ custom_styler_copy = (
+ copy.deepcopy(custom_styler) if deepcopy else copy.copy(custom_styler)
+ )
+ assert isinstance(custom_styler_copy, CustomStyler)
+
+
+def test_clear(mi_styler_comp):
+ # NOTE: if this test fails for new features then 'mi_styler_comp' should be updated
+ # to ensure proper testing of the 'copy', 'clear', 'export' methods with new feature
+ # GH 40675
+ styler = mi_styler_comp
+ styler._compute() # execute applied methods
+
+ clean_copy = Styler(styler.data, uuid=styler.uuid)
+
+ excl = [
+ "data",
+ "index",
+ "columns",
+ "uuid",
+ "uuid_len", # uuid is set to be the same on styler and clean_copy
+ "cell_ids",
+ "cellstyle_map", # execution time only
+ "cellstyle_map_columns", # execution time only
+ "cellstyle_map_index", # execution time only
+ "template_latex", # render templates are class level
+ "template_html",
+ "template_html_style",
+ "template_html_table",
+ ]
+ # tests vars are not same vals on obj and clean copy before clear (except for excl)
+ for attr in [a for a in styler.__dict__ if not (callable(a) or a in excl)]:
+ res = getattr(styler, attr) == getattr(clean_copy, attr)
+ if hasattr(res, "__iter__") and len(res) > 0:
+ assert not all(res) # some element in iterable differs
+ elif hasattr(res, "__iter__") and len(res) == 0:
+ pass # empty array
+ else:
+ assert not res # explicit var differs
+
+ # test vars have same vales on obj and clean copy after clearing
+ styler.clear()
+ for attr in [a for a in styler.__dict__ if not callable(a)]:
+ res = getattr(styler, attr) == getattr(clean_copy, attr)
+ assert all(res) if hasattr(res, "__iter__") else res
+
+
+def test_export(mi_styler_comp, mi_styler):
+ exp_attrs = [
+ "_todo",
+ "hide_index_",
+ "hide_index_names",
+ "hide_columns_",
+ "hide_column_names",
+ "table_attributes",
+ "table_styles",
+ "css",
+ ]
+ for attr in exp_attrs:
+ check = getattr(mi_styler, attr) == getattr(mi_styler_comp, attr)
+ assert not (
+ all(check) if (hasattr(check, "__iter__") and len(check) > 0) else check
+ )
+
+ export = mi_styler_comp.export()
+ used = mi_styler.use(export)
+ for attr in exp_attrs:
+ check = getattr(used, attr) == getattr(mi_styler_comp, attr)
+ assert all(check) if (hasattr(check, "__iter__") and len(check) > 0) else check
+
+ used.to_html()
+
+
+def test_hide_raises(mi_styler):
+ msg = "`subset` and `level` cannot be passed simultaneously"
+ with pytest.raises(ValueError, match=msg):
+ mi_styler.hide(axis="index", subset="something", level="something else")
+
+ msg = "`level` must be of type `int`, `str` or list of such"
+ with pytest.raises(ValueError, match=msg):
+ mi_styler.hide(axis="index", level={"bad": 1, "type": 2})
+
+
+@pytest.mark.parametrize("level", [1, "one", [1], ["one"]])
+def test_hide_index_level(mi_styler, level):
+ mi_styler.index.names, mi_styler.columns.names = ["zero", "one"], ["zero", "one"]
+ ctx = mi_styler.hide(axis="index", level=level)._translate(False, True)
+ assert len(ctx["head"][0]) == 3
+ assert len(ctx["head"][1]) == 3
+ assert len(ctx["head"][2]) == 4
+ assert ctx["head"][2][0]["is_visible"]
+ assert not ctx["head"][2][1]["is_visible"]
+
+ assert ctx["body"][0][0]["is_visible"]
+ assert not ctx["body"][0][1]["is_visible"]
+ assert ctx["body"][1][0]["is_visible"]
+ assert not ctx["body"][1][1]["is_visible"]
+
+
+@pytest.mark.parametrize("level", [1, "one", [1], ["one"]])
+@pytest.mark.parametrize("names", [True, False])
+def test_hide_columns_level(mi_styler, level, names):
+ mi_styler.columns.names = ["zero", "one"]
+ if names:
+ mi_styler.index.names = ["zero", "one"]
+ ctx = mi_styler.hide(axis="columns", level=level)._translate(True, False)
+ assert len(ctx["head"]) == (2 if names else 1)
+
+
+@pytest.mark.parametrize("method", ["map", "apply"])
+@pytest.mark.parametrize("axis", ["index", "columns"])
+def test_apply_map_header(method, axis):
+ # GH 41893
+ df = DataFrame({"A": [0, 0], "B": [1, 1]}, index=["C", "D"])
+ func = {
+ "apply": lambda s: ["attr: val" if ("A" in v or "C" in v) else "" for v in s],
+ "map": lambda v: "attr: val" if ("A" in v or "C" in v) else "",
+ }
+
+ # test execution added to todo
+ result = getattr(df.style, f"{method}_index")(func[method], axis=axis)
+ assert len(result._todo) == 1
+ assert len(getattr(result, f"ctx_{axis}")) == 0
+
+ # test ctx object on compute
+ result._compute()
+ expected = {
+ (0, 0): [("attr", "val")],
+ }
+ assert getattr(result, f"ctx_{axis}") == expected
+
+
+@pytest.mark.parametrize("method", ["apply", "map"])
+@pytest.mark.parametrize("axis", ["index", "columns"])
+def test_apply_map_header_mi(mi_styler, method, axis):
+ # GH 41893
+ func = {
+ "apply": lambda s: ["attr: val;" if "b" in v else "" for v in s],
+ "map": lambda v: "attr: val" if "b" in v else "",
+ }
+ result = getattr(mi_styler, f"{method}_index")(func[method], axis=axis)._compute()
+ expected = {(1, 1): [("attr", "val")]}
+ assert getattr(result, f"ctx_{axis}") == expected
+
+
+def test_apply_map_header_raises(mi_styler):
+ # GH 41893
+ with pytest.raises(ValueError, match="No axis named bad for object type DataFrame"):
+ mi_styler.map_index(lambda v: "attr: val;", axis="bad")._compute()
+
+
+class TestStyler:
+ def test_init_non_pandas(self):
+ msg = "``data`` must be a Series or DataFrame"
+ with pytest.raises(TypeError, match=msg):
+ Styler([1, 2, 3])
+
+ def test_init_series(self):
+ result = Styler(Series([1, 2]))
+ assert result.data.ndim == 2
+
+ def test_repr_html_ok(self, styler):
+ styler._repr_html_()
+
+ def test_repr_html_mathjax(self, styler):
+ # gh-19824 / 41395
+ assert "tex2jax_ignore" not in styler._repr_html_()
+ assert "mathjax_ignore" not in styler._repr_html_()
+
+ with option_context("styler.html.mathjax", False):
+ assert "tex2jax_ignore" in styler._repr_html_()
+ assert "mathjax_ignore" in styler._repr_html_()
+
+ def test_update_ctx(self, styler):
+ styler._update_ctx(DataFrame({"A": ["color: red", "color: blue"]}))
+ expected = {(0, 0): [("color", "red")], (1, 0): [("color", "blue")]}
+ assert styler.ctx == expected
+
+ def test_update_ctx_flatten_multi_and_trailing_semi(self, styler):
+ attrs = DataFrame({"A": ["color: red; foo: bar", "color:blue ; foo: baz;"]})
+ styler._update_ctx(attrs)
+ expected = {
+ (0, 0): [("color", "red"), ("foo", "bar")],
+ (1, 0): [("color", "blue"), ("foo", "baz")],
+ }
+ assert styler.ctx == expected
+
+ def test_render(self):
+ df = DataFrame({"A": [0, 1]})
+ style = lambda x: Series(["color: red", "color: blue"], name=x.name)
+ s = Styler(df, uuid="AB").apply(style)
+ s.to_html()
+ # it worked?
+
+ def test_multiple_render(self, df):
+ # GH 39396
+ s = Styler(df, uuid_len=0).map(lambda x: "color: red;", subset=["A"])
+ s.to_html() # do 2 renders to ensure css styles not duplicated
+ assert (
+ '" in s.to_html()
+ )
+
+ def test_render_empty_dfs(self):
+ empty_df = DataFrame()
+ es = Styler(empty_df)
+ es.to_html()
+ # An index but no columns
+ DataFrame(columns=["a"]).style.to_html()
+ # A column but no index
+ DataFrame(index=["a"]).style.to_html()
+ # No IndexError raised?
+
+ def test_render_double(self):
+ df = DataFrame({"A": [0, 1]})
+ style = lambda x: Series(
+ ["color: red; border: 1px", "color: blue; border: 2px"], name=x.name
+ )
+ s = Styler(df, uuid="AB").apply(style)
+ s.to_html()
+ # it worked?
+
+ def test_set_properties(self):
+ df = DataFrame({"A": [0, 1]})
+ result = df.style.set_properties(color="white", size="10px")._compute().ctx
+ # order is deterministic
+ v = [("color", "white"), ("size", "10px")]
+ expected = {(0, 0): v, (1, 0): v}
+ assert result.keys() == expected.keys()
+ for v1, v2 in zip(result.values(), expected.values()):
+ assert sorted(v1) == sorted(v2)
+
+ def test_set_properties_subset(self):
+ df = DataFrame({"A": [0, 1]})
+ result = (
+ df.style.set_properties(subset=IndexSlice[0, "A"], color="white")
+ ._compute()
+ .ctx
+ )
+ expected = {(0, 0): [("color", "white")]}
+ assert result == expected
+
+ def test_empty_index_name_doesnt_display(self, blank_value):
+ # https://github.com/pandas-dev/pandas/pull/12090#issuecomment-180695902
+ df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]})
+ result = df.style._translate(True, True)
+ assert len(result["head"]) == 1
+ expected = {
+ "class": "blank level0",
+ "type": "th",
+ "value": blank_value,
+ "is_visible": True,
+ "display_value": blank_value,
+ }
+ assert expected.items() <= result["head"][0][0].items()
+
+ def test_index_name(self):
+ # https://github.com/pandas-dev/pandas/issues/11655
+ df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]})
+ result = df.set_index("A").style._translate(True, True)
+ expected = {
+ "class": "index_name level0",
+ "type": "th",
+ "value": "A",
+ "is_visible": True,
+ "display_value": "A",
+ }
+ assert expected.items() <= result["head"][1][0].items()
+
+ def test_numeric_columns(self):
+ # https://github.com/pandas-dev/pandas/issues/12125
+ # smoke test for _translate
+ df = DataFrame({0: [1, 2, 3]})
+ df.style._translate(True, True)
+
+ def test_apply_axis(self):
+ df = DataFrame({"A": [0, 0], "B": [1, 1]})
+ f = lambda x: [f"val: {x.max()}" for v in x]
+ result = df.style.apply(f, axis=1)
+ assert len(result._todo) == 1
+ assert len(result.ctx) == 0
+ result._compute()
+ expected = {
+ (0, 0): [("val", "1")],
+ (0, 1): [("val", "1")],
+ (1, 0): [("val", "1")],
+ (1, 1): [("val", "1")],
+ }
+ assert result.ctx == expected
+
+ result = df.style.apply(f, axis=0)
+ expected = {
+ (0, 0): [("val", "0")],
+ (0, 1): [("val", "1")],
+ (1, 0): [("val", "0")],
+ (1, 1): [("val", "1")],
+ }
+ result._compute()
+ assert result.ctx == expected
+ result = df.style.apply(f) # default
+ result._compute()
+ assert result.ctx == expected
+
+ @pytest.mark.parametrize("axis", [0, 1])
+ def test_apply_series_return(self, axis):
+ # GH 42014
+ df = DataFrame([[1, 2], [3, 4]], index=["X", "Y"], columns=["X", "Y"])
+
+ # test Series return where len(Series) < df.index or df.columns but labels OK
+ func = lambda s: Series(["color: red;"], index=["Y"])
+ result = df.style.apply(func, axis=axis)._compute().ctx
+ assert result[(1, 1)] == [("color", "red")]
+ assert result[(1 - axis, axis)] == [("color", "red")]
+
+ # test Series return where labels align but different order
+ func = lambda s: Series(["color: red;", "color: blue;"], index=["Y", "X"])
+ result = df.style.apply(func, axis=axis)._compute().ctx
+ assert result[(0, 0)] == [("color", "blue")]
+ assert result[(1, 1)] == [("color", "red")]
+ assert result[(1 - axis, axis)] == [("color", "red")]
+ assert result[(axis, 1 - axis)] == [("color", "blue")]
+
+ @pytest.mark.parametrize("index", [False, True])
+ @pytest.mark.parametrize("columns", [False, True])
+ def test_apply_dataframe_return(self, index, columns):
+ # GH 42014
+ df = DataFrame([[1, 2], [3, 4]], index=["X", "Y"], columns=["X", "Y"])
+ idxs = ["X", "Y"] if index else ["Y"]
+ cols = ["X", "Y"] if columns else ["Y"]
+ df_styles = DataFrame("color: red;", index=idxs, columns=cols)
+ result = df.style.apply(lambda x: df_styles, axis=None)._compute().ctx
+
+ assert result[(1, 1)] == [("color", "red")] # (Y,Y) styles always present
+ assert (result[(0, 1)] == [("color", "red")]) is index # (X,Y) only if index
+ assert (result[(1, 0)] == [("color", "red")]) is columns # (Y,X) only if cols
+ assert (result[(0, 0)] == [("color", "red")]) is (index and columns) # (X,X)
+
+ @pytest.mark.parametrize(
+ "slice_",
+ [
+ IndexSlice[:],
+ IndexSlice[:, ["A"]],
+ IndexSlice[[1], :],
+ IndexSlice[[1], ["A"]],
+ IndexSlice[:2, ["A", "B"]],
+ ],
+ )
+ @pytest.mark.parametrize("axis", [0, 1])
+ def test_apply_subset(self, slice_, axis, df):
+ def h(x, color="bar"):
+ return Series(f"color: {color}", index=x.index, name=x.name)
+
+ result = df.style.apply(h, axis=axis, subset=slice_, color="baz")._compute().ctx
+ expected = {
+ (r, c): [("color", "baz")]
+ for r, row in enumerate(df.index)
+ for c, col in enumerate(df.columns)
+ if row in df.loc[slice_].index and col in df.loc[slice_].columns
+ }
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "slice_",
+ [
+ IndexSlice[:],
+ IndexSlice[:, ["A"]],
+ IndexSlice[[1], :],
+ IndexSlice[[1], ["A"]],
+ IndexSlice[:2, ["A", "B"]],
+ ],
+ )
+ def test_map_subset(self, slice_, df):
+ result = df.style.map(lambda x: "color:baz;", subset=slice_)._compute().ctx
+ expected = {
+ (r, c): [("color", "baz")]
+ for r, row in enumerate(df.index)
+ for c, col in enumerate(df.columns)
+ if row in df.loc[slice_].index and col in df.loc[slice_].columns
+ }
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "slice_",
+ [
+ IndexSlice[:, IndexSlice["x", "A"]],
+ IndexSlice[:, IndexSlice[:, "A"]],
+ IndexSlice[:, IndexSlice[:, ["A", "C"]]], # missing col element
+ IndexSlice[IndexSlice["a", 1], :],
+ IndexSlice[IndexSlice[:, 1], :],
+ IndexSlice[IndexSlice[:, [1, 3]], :], # missing row element
+ IndexSlice[:, ("x", "A")],
+ IndexSlice[("a", 1), :],
+ ],
+ )
+ def test_map_subset_multiindex(self, slice_):
+ # GH 19861
+ # edited for GH 33562
+ if (
+ isinstance(slice_[-1], tuple)
+ and isinstance(slice_[-1][-1], list)
+ and "C" in slice_[-1][-1]
+ ):
+ ctx = pytest.raises(KeyError, match="C")
+ elif (
+ isinstance(slice_[0], tuple)
+ and isinstance(slice_[0][1], list)
+ and 3 in slice_[0][1]
+ ):
+ ctx = pytest.raises(KeyError, match="3")
+ else:
+ ctx = contextlib.nullcontext()
+
+ idx = MultiIndex.from_product([["a", "b"], [1, 2]])
+ col = MultiIndex.from_product([["x", "y"], ["A", "B"]])
+ df = DataFrame(np.random.default_rng(2).random((4, 4)), columns=col, index=idx)
+
+ with ctx:
+ df.style.map(lambda x: "color: red;", subset=slice_).to_html()
+
+ def test_map_subset_multiindex_code(self):
+ # https://github.com/pandas-dev/pandas/issues/25858
+ # Checks styler.map works with multindex when codes are provided
+ codes = np.array([[0, 0, 1, 1], [0, 1, 0, 1]])
+ columns = MultiIndex(
+ levels=[["a", "b"], ["%", "#"]], codes=codes, names=["", ""]
+ )
+ df = DataFrame(
+ [[1, -1, 1, 1], [-1, 1, 1, 1]], index=["hello", "world"], columns=columns
+ )
+ pct_subset = IndexSlice[:, IndexSlice[:, "%":"%"]]
+
+ def color_negative_red(val):
+ color = "red" if val < 0 else "black"
+ return f"color: {color}"
+
+ df.loc[pct_subset]
+ df.style.map(color_negative_red, subset=pct_subset)
+
+ @pytest.mark.parametrize(
+ "stylefunc", ["background_gradient", "bar", "text_gradient"]
+ )
+ def test_subset_for_boolean_cols(self, stylefunc):
+ # GH47838
+ df = DataFrame(
+ [
+ [1, 2],
+ [3, 4],
+ ],
+ columns=[False, True],
+ )
+ styled = getattr(df.style, stylefunc)()
+ styled._compute()
+ assert set(styled.ctx) == {(0, 0), (0, 1), (1, 0), (1, 1)}
+
+ def test_empty(self):
+ df = DataFrame({"A": [1, 0]})
+ s = df.style
+ s.ctx = {(0, 0): [("color", "red")], (1, 0): [("", "")]}
+
+ result = s._translate(True, True)["cellstyle"]
+ expected = [
+ {"props": [("color", "red")], "selectors": ["row0_col0"]},
+ {"props": [("", "")], "selectors": ["row1_col0"]},
+ ]
+ assert result == expected
+
+ def test_duplicate(self):
+ df = DataFrame({"A": [1, 0]})
+ s = df.style
+ s.ctx = {(0, 0): [("color", "red")], (1, 0): [("color", "red")]}
+
+ result = s._translate(True, True)["cellstyle"]
+ expected = [
+ {"props": [("color", "red")], "selectors": ["row0_col0", "row1_col0"]}
+ ]
+ assert result == expected
+
+ def test_init_with_na_rep(self):
+ # GH 21527 28358
+ df = DataFrame([[None, None], [1.1, 1.2]], columns=["A", "B"])
+
+ ctx = Styler(df, na_rep="NA")._translate(True, True)
+ assert ctx["body"][0][1]["display_value"] == "NA"
+ assert ctx["body"][0][2]["display_value"] == "NA"
+
+ def test_caption(self, df):
+ styler = Styler(df, caption="foo")
+ result = styler.to_html()
+ assert all(["caption" in result, "foo" in result])
+
+ styler = df.style
+ result = styler.set_caption("baz")
+ assert styler is result
+ assert styler.caption == "baz"
+
+ def test_uuid(self, df):
+ styler = Styler(df, uuid="abc123")
+ result = styler.to_html()
+ assert "abc123" in result
+
+ styler = df.style
+ result = styler.set_uuid("aaa")
+ assert result is styler
+ assert result.uuid == "aaa"
+
+ def test_unique_id(self):
+ # See https://github.com/pandas-dev/pandas/issues/16780
+ df = DataFrame({"a": [1, 3, 5, 6], "b": [2, 4, 12, 21]})
+ result = df.style.to_html(uuid="test")
+ assert "test" in result
+ ids = re.findall('id="(.*?)"', result)
+ assert np.unique(ids).size == len(ids)
+
+ def test_table_styles(self, df):
+ style = [{"selector": "th", "props": [("foo", "bar")]}] # default format
+ styler = Styler(df, table_styles=style)
+ result = " ".join(styler.to_html().split())
+ assert "th { foo: bar; }" in result
+
+ styler = df.style
+ result = styler.set_table_styles(style)
+ assert styler is result
+ assert styler.table_styles == style
+
+ # GH 39563
+ style = [{"selector": "th", "props": "foo:bar;"}] # css string format
+ styler = df.style.set_table_styles(style)
+ result = " ".join(styler.to_html().split())
+ assert "th { foo: bar; }" in result
+
+ def test_table_styles_multiple(self, df):
+ ctx = df.style.set_table_styles(
+ [
+ {"selector": "th,td", "props": "color:red;"},
+ {"selector": "tr", "props": "color:green;"},
+ ]
+ )._translate(True, True)["table_styles"]
+ assert ctx == [
+ {"selector": "th", "props": [("color", "red")]},
+ {"selector": "td", "props": [("color", "red")]},
+ {"selector": "tr", "props": [("color", "green")]},
+ ]
+
+ def test_table_styles_dict_multiple_selectors(self, df):
+ # GH 44011
+ result = df.style.set_table_styles(
+ {
+ "B": [
+ {"selector": "th,td", "props": [("border-left", "2px solid black")]}
+ ]
+ }
+ )._translate(True, True)["table_styles"]
+
+ expected = [
+ {"selector": "th.col1", "props": [("border-left", "2px solid black")]},
+ {"selector": "td.col1", "props": [("border-left", "2px solid black")]},
+ ]
+
+ assert result == expected
+
+ def test_maybe_convert_css_to_tuples(self):
+ expected = [("a", "b"), ("c", "d e")]
+ assert maybe_convert_css_to_tuples("a:b;c:d e;") == expected
+ assert maybe_convert_css_to_tuples("a: b ;c: d e ") == expected
+ expected = []
+ assert maybe_convert_css_to_tuples("") == expected
+
+ # issue #59623
+ expected = [("a", "b"), ("c", "url('data:123')")]
+ assert maybe_convert_css_to_tuples("a:b;c: url('data:123');") == expected
+
+ # if no value, return attr and empty string
+ expected = [("a", ""), ("c", "")]
+ assert maybe_convert_css_to_tuples("a:;c: ") == expected
+
+ def test_maybe_convert_css_to_tuples_err(self):
+ msg = (
+ "Styles supplied as string must follow CSS rule formats, "
+ "for example 'attr: val;'. 'err' was given."
+ )
+ with pytest.raises(ValueError, match=msg):
+ maybe_convert_css_to_tuples("err")
+
+ def test_table_attributes(self, df):
+ attributes = 'class="foo" data-bar'
+ styler = Styler(df, table_attributes=attributes)
+ result = styler.to_html()
+ assert 'class="foo" data-bar' in result
+
+ result = df.style.set_table_attributes(attributes).to_html()
+ assert 'class="foo" data-bar' in result
+
+ def test_apply_none(self):
+ def f(x):
+ return DataFrame(
+ np.where(x == x.max(), "color: red", ""),
+ index=x.index,
+ columns=x.columns,
+ )
+
+ result = DataFrame([[1, 2], [3, 4]]).style.apply(f, axis=None)._compute().ctx
+ assert result[(1, 1)] == [("color", "red")]
+
+ def test_trim(self, df):
+ result = df.style.to_html() # trim=True
+ assert result.count("#") == 0
+
+ result = df.style.highlight_max().to_html()
+ assert result.count("#") == len(df.columns)
+
+ def test_export(self, df, styler):
+ f = lambda x: "color: red" if x > 0 else "color: blue"
+ g = lambda x, z: f"color: {z}"
+ style1 = styler
+ style1.map(f).map(g, z="b").highlight_max()._compute() # = render
+ result = style1.export()
+ style2 = df.style
+ style2.use(result)
+ assert style1._todo == style2._todo
+ style2.to_html()
+
+ def test_bad_apply_shape(self):
+ df = DataFrame([[1, 2], [3, 4]], index=["A", "B"], columns=["X", "Y"])
+
+ msg = "resulted in the apply method collapsing to a Series."
+ with pytest.raises(ValueError, match=msg):
+ df.style._apply(lambda x: "x")
+
+ msg = "created invalid {} labels"
+ with pytest.raises(ValueError, match=msg.format("index")):
+ df.style._apply(lambda x: [""])
+
+ with pytest.raises(ValueError, match=msg.format("index")):
+ df.style._apply(lambda x: ["", "", "", ""])
+
+ with pytest.raises(ValueError, match=msg.format("index")):
+ df.style._apply(lambda x: Series(["a:v;", ""], index=["A", "C"]), axis=0)
+
+ with pytest.raises(ValueError, match=msg.format("columns")):
+ df.style._apply(lambda x: ["", "", ""], axis=1)
+
+ with pytest.raises(ValueError, match=msg.format("columns")):
+ df.style._apply(lambda x: Series(["a:v;", ""], index=["X", "Z"]), axis=1)
+
+ msg = "returned ndarray with wrong shape"
+ with pytest.raises(ValueError, match=msg):
+ df.style._apply(lambda x: np.array([[""], [""]]), axis=None)
+
+ def test_apply_bad_return(self):
+ def f(x):
+ return ""
+
+ df = DataFrame([[1, 2], [3, 4]])
+ msg = (
+ "must return a DataFrame or ndarray when passed to `Styler.apply` "
+ "with axis=None"
+ )
+ with pytest.raises(TypeError, match=msg):
+ df.style._apply(f, axis=None)
+
+ @pytest.mark.parametrize("axis", ["index", "columns"])
+ def test_apply_bad_labels(self, axis):
+ def f(x):
+ return DataFrame(**{axis: ["bad", "labels"]})
+
+ df = DataFrame([[1, 2], [3, 4]])
+ msg = f"created invalid {axis} labels."
+ with pytest.raises(ValueError, match=msg):
+ df.style._apply(f, axis=None)
+
+ def test_get_level_lengths(self):
+ index = MultiIndex.from_product([["a", "b"], [0, 1, 2]])
+ expected = {
+ (0, 0): 3,
+ (0, 3): 3,
+ (1, 0): 1,
+ (1, 1): 1,
+ (1, 2): 1,
+ (1, 3): 1,
+ (1, 4): 1,
+ (1, 5): 1,
+ }
+ result = _get_level_lengths(index, sparsify=True, max_index=100)
+ tm.assert_dict_equal(result, expected)
+
+ expected = {
+ (0, 0): 1,
+ (0, 1): 1,
+ (0, 2): 1,
+ (0, 3): 1,
+ (0, 4): 1,
+ (0, 5): 1,
+ (1, 0): 1,
+ (1, 1): 1,
+ (1, 2): 1,
+ (1, 3): 1,
+ (1, 4): 1,
+ (1, 5): 1,
+ }
+ result = _get_level_lengths(index, sparsify=False, max_index=100)
+ tm.assert_dict_equal(result, expected)
+
+ def test_get_level_lengths_un_sorted(self):
+ index = MultiIndex.from_arrays([[1, 1, 2, 1], ["a", "b", "b", "d"]])
+ expected = {
+ (0, 0): 2,
+ (0, 2): 1,
+ (0, 3): 1,
+ (1, 0): 1,
+ (1, 1): 1,
+ (1, 2): 1,
+ (1, 3): 1,
+ }
+ result = _get_level_lengths(index, sparsify=True, max_index=100)
+ tm.assert_dict_equal(result, expected)
+
+ expected = {
+ (0, 0): 1,
+ (0, 1): 1,
+ (0, 2): 1,
+ (0, 3): 1,
+ (1, 0): 1,
+ (1, 1): 1,
+ (1, 2): 1,
+ (1, 3): 1,
+ }
+ result = _get_level_lengths(index, sparsify=False, max_index=100)
+ tm.assert_dict_equal(result, expected)
+
+ def test_mi_sparse_index_names(self, blank_value):
+ # Test the class names and displayed value are correct on rendering MI names
+ df = DataFrame(
+ {"A": [1, 2]},
+ index=MultiIndex.from_arrays(
+ [["a", "a"], [0, 1]], names=["idx_level_0", "idx_level_1"]
+ ),
+ )
+ result = df.style._translate(True, True)
+ head = result["head"][1]
+ expected = [
+ {
+ "class": "index_name level0",
+ "display_value": "idx_level_0",
+ "is_visible": True,
+ },
+ {
+ "class": "index_name level1",
+ "display_value": "idx_level_1",
+ "is_visible": True,
+ },
+ {
+ "class": "blank col0",
+ "display_value": blank_value,
+ "is_visible": True,
+ },
+ ]
+ for i, expected_dict in enumerate(expected):
+ assert expected_dict.items() <= head[i].items()
+
+ def test_mi_sparse_column_names(self, blank_value):
+ df = DataFrame(
+ np.arange(16).reshape(4, 4),
+ index=MultiIndex.from_arrays(
+ [["a", "a", "b", "a"], [0, 1, 1, 2]],
+ names=["idx_level_0", "idx_level_1"],
+ ),
+ columns=MultiIndex.from_arrays(
+ [["C1", "C1", "C2", "C2"], [1, 0, 1, 0]], names=["colnam_0", "colnam_1"]
+ ),
+ )
+ result = Styler(df, cell_ids=False)._translate(True, True)
+
+ for level in [0, 1]:
+ head = result["head"][level]
+ expected = [
+ {
+ "class": "blank",
+ "display_value": blank_value,
+ "is_visible": True,
+ },
+ {
+ "class": f"index_name level{level}",
+ "display_value": f"colnam_{level}",
+ "is_visible": True,
+ },
+ ]
+ for i, expected_dict in enumerate(expected):
+ assert expected_dict.items() <= head[i].items()
+
+ def test_hide_column_headers(self, df, styler):
+ ctx = styler.hide(axis="columns")._translate(True, True)
+ assert len(ctx["head"]) == 0 # no header entries with an unnamed index
+
+ df.index.name = "some_name"
+ ctx = df.style.hide(axis="columns")._translate(True, True)
+ assert len(ctx["head"]) == 1
+ # index names still visible, changed in #42101, reverted in 43404
+
+ def test_hide_single_index(self, df):
+ # GH 14194
+ # single unnamed index
+ ctx = df.style._translate(True, True)
+ assert ctx["body"][0][0]["is_visible"]
+ assert ctx["head"][0][0]["is_visible"]
+ ctx2 = df.style.hide(axis="index")._translate(True, True)
+ assert not ctx2["body"][0][0]["is_visible"]
+ assert not ctx2["head"][0][0]["is_visible"]
+
+ # single named index
+ ctx3 = df.set_index("A").style._translate(True, True)
+ assert ctx3["body"][0][0]["is_visible"]
+ assert len(ctx3["head"]) == 2 # 2 header levels
+ assert ctx3["head"][0][0]["is_visible"]
+
+ ctx4 = df.set_index("A").style.hide(axis="index")._translate(True, True)
+ assert not ctx4["body"][0][0]["is_visible"]
+ assert len(ctx4["head"]) == 1 # only 1 header levels
+ assert not ctx4["head"][0][0]["is_visible"]
+
+ def test_hide_multiindex(self):
+ # GH 14194
+ df = DataFrame(
+ {"A": [1, 2], "B": [1, 2]},
+ index=MultiIndex.from_arrays(
+ [["a", "a"], [0, 1]], names=["idx_level_0", "idx_level_1"]
+ ),
+ )
+ ctx1 = df.style._translate(True, True)
+ # tests for 'a' and '0'
+ assert ctx1["body"][0][0]["is_visible"]
+ assert ctx1["body"][0][1]["is_visible"]
+ # check for blank header rows
+ assert len(ctx1["head"][0]) == 4 # two visible indexes and two data columns
+
+ ctx2 = df.style.hide(axis="index")._translate(True, True)
+ # tests for 'a' and '0'
+ assert not ctx2["body"][0][0]["is_visible"]
+ assert not ctx2["body"][0][1]["is_visible"]
+ # check for blank header rows
+ assert len(ctx2["head"][0]) == 3 # one hidden (col name) and two data columns
+ assert not ctx2["head"][0][0]["is_visible"]
+
+ def test_hide_columns_single_level(self, df):
+ # GH 14194
+ # test hiding single column
+ ctx = df.style._translate(True, True)
+ assert ctx["head"][0][1]["is_visible"]
+ assert ctx["head"][0][1]["display_value"] == "A"
+ assert ctx["head"][0][2]["is_visible"]
+ assert ctx["head"][0][2]["display_value"] == "B"
+ assert ctx["body"][0][1]["is_visible"] # col A, row 1
+ assert ctx["body"][1][2]["is_visible"] # col B, row 1
+
+ ctx = df.style.hide("A", axis="columns")._translate(True, True)
+ assert not ctx["head"][0][1]["is_visible"]
+ assert not ctx["body"][0][1]["is_visible"] # col A, row 1
+ assert ctx["body"][1][2]["is_visible"] # col B, row 1
+
+ # test hiding multiple columns
+ ctx = df.style.hide(["A", "B"], axis="columns")._translate(True, True)
+ assert not ctx["head"][0][1]["is_visible"]
+ assert not ctx["head"][0][2]["is_visible"]
+ assert not ctx["body"][0][1]["is_visible"] # col A, row 1
+ assert not ctx["body"][1][2]["is_visible"] # col B, row 1
+
+ def test_hide_columns_index_mult_levels(self):
+ # GH 14194
+ # setup dataframe with multiple column levels and indices
+ i1 = MultiIndex.from_arrays(
+ [["a", "a"], [0, 1]], names=["idx_level_0", "idx_level_1"]
+ )
+ i2 = MultiIndex.from_arrays(
+ [["b", "b"], [0, 1]], names=["col_level_0", "col_level_1"]
+ )
+ df = DataFrame([[1, 2], [3, 4]], index=i1, columns=i2)
+ ctx = df.style._translate(True, True)
+ # column headers
+ assert ctx["head"][0][2]["is_visible"]
+ assert ctx["head"][1][2]["is_visible"]
+ assert ctx["head"][1][3]["display_value"] == "1"
+ # indices
+ assert ctx["body"][0][0]["is_visible"]
+ # data
+ assert ctx["body"][1][2]["is_visible"]
+ assert ctx["body"][1][2]["display_value"] == "3"
+ assert ctx["body"][1][3]["is_visible"]
+ assert ctx["body"][1][3]["display_value"] == "4"
+
+ # hide top column level, which hides both columns
+ ctx = df.style.hide("b", axis="columns")._translate(True, True)
+ assert not ctx["head"][0][2]["is_visible"] # b
+ assert not ctx["head"][1][2]["is_visible"] # 0
+ assert not ctx["body"][1][2]["is_visible"] # 3
+ assert ctx["body"][0][0]["is_visible"] # index
+
+ # hide first column only
+ ctx = df.style.hide([("b", 0)], axis="columns")._translate(True, True)
+ assert not ctx["head"][0][2]["is_visible"] # b
+ assert ctx["head"][0][3]["is_visible"] # b
+ assert not ctx["head"][1][2]["is_visible"] # 0
+ assert not ctx["body"][1][2]["is_visible"] # 3
+ assert ctx["body"][1][3]["is_visible"]
+ assert ctx["body"][1][3]["display_value"] == "4"
+
+ # hide second column and index
+ ctx = df.style.hide([("b", 1)], axis=1).hide(axis=0)._translate(True, True)
+ assert not ctx["body"][0][0]["is_visible"] # index
+ assert len(ctx["head"][0]) == 3
+ assert ctx["head"][0][1]["is_visible"] # b
+ assert ctx["head"][1][1]["is_visible"] # 0
+ assert not ctx["head"][1][2]["is_visible"] # 1
+ assert not ctx["body"][1][3]["is_visible"] # 4
+ assert ctx["body"][1][2]["is_visible"]
+ assert ctx["body"][1][2]["display_value"] == "3"
+
+ # hide top row level, which hides both rows so body empty
+ ctx = df.style.hide("a", axis="index")._translate(True, True)
+ assert ctx["body"] == []
+
+ # hide first row only
+ ctx = df.style.hide(("a", 0), axis="index")._translate(True, True)
+ for i in [0, 1, 2, 3]:
+ assert "row1" in ctx["body"][0][i]["class"] # row0 not included in body
+ assert ctx["body"][0][i]["is_visible"]
+
+ def test_pipe(self, df):
+ def set_caption_from_template(styler, a, b):
+ return styler.set_caption(f"Dataframe with a = {a} and b = {b}")
+
+ styler = df.style.pipe(set_caption_from_template, "A", b="B")
+ assert "Dataframe with a = A and b = B" in styler.to_html()
+
+ # Test with an argument that is a (callable, keyword_name) pair.
+ def f(a, b, styler):
+ return (a, b, styler)
+
+ styler = df.style
+ result = styler.pipe((f, "styler"), a=1, b=2)
+ assert result == (1, 2, styler)
+
+ def test_no_cell_ids(self):
+ # GH 35588
+ # GH 35663
+ df = DataFrame(data=[[0]])
+ styler = Styler(df, uuid="_", cell_ids=False)
+ styler.to_html()
+ s = styler.to_html() # render twice to ensure ctx is not updated
+ assert s.find('') != -1
+
+ @pytest.mark.parametrize(
+ "classes",
+ [
+ DataFrame(
+ data=[["", "test-class"], [np.nan, None]],
+ columns=["A", "B"],
+ index=["a", "b"],
+ ),
+ DataFrame(data=[["test-class"]], columns=["B"], index=["a"]),
+ DataFrame(data=[["test-class", "unused"]], columns=["B", "C"], index=["a"]),
+ ],
+ )
+ def test_set_data_classes(self, classes):
+ # GH 36159
+ df = DataFrame(data=[[0, 1], [2, 3]], columns=["A", "B"], index=["a", "b"])
+ s = Styler(df, uuid_len=0, cell_ids=False).set_td_classes(classes).to_html()
+ assert ' 0 ' in s
+ assert '1 ' in s
+ assert '2 ' in s
+ assert '3 ' in s
+ # GH 39317
+ s = Styler(df, uuid_len=0, cell_ids=True).set_td_classes(classes).to_html()
+ assert '0 ' in s
+ assert '1 ' in s
+ assert '2 ' in s
+ assert '3 ' in s
+
+ def test_set_data_classes_reindex(self):
+ # GH 39317
+ df = DataFrame(
+ data=[[0, 1, 2], [3, 4, 5], [6, 7, 8]], columns=[0, 1, 2], index=[0, 1, 2]
+ )
+ classes = DataFrame(
+ data=[["mi", "ma"], ["mu", "mo"]],
+ columns=[0, 2],
+ index=[0, 2],
+ )
+ s = Styler(df, uuid_len=0).set_td_classes(classes).to_html()
+ assert '0 ' in s
+ assert '2 ' in s
+ assert '4 ' in s
+ assert '6 ' in s
+ assert '8 ' in s
+
+ def test_chaining_table_styles(self):
+ # GH 35607
+ df = DataFrame(data=[[0, 1], [1, 2]], columns=["A", "B"])
+ styler = df.style.set_table_styles(
+ [{"selector": "", "props": [("background-color", "yellow")]}]
+ ).set_table_styles(
+ [{"selector": ".col0", "props": [("background-color", "blue")]}],
+ overwrite=False,
+ )
+ assert len(styler.table_styles) == 2
+
+ def test_column_and_row_styling(self):
+ # GH 35607
+ df = DataFrame(data=[[0, 1], [1, 2]], columns=["A", "B"])
+ s = Styler(df, uuid_len=0)
+ s = s.set_table_styles({"A": [{"selector": "", "props": [("color", "blue")]}]})
+ assert "#T_ .col0 {\n color: blue;\n}" in s.to_html()
+ s = s.set_table_styles(
+ {0: [{"selector": "", "props": [("color", "blue")]}]}, axis=1
+ )
+ assert "#T_ .row0 {\n color: blue;\n}" in s.to_html()
+
+ @pytest.mark.parametrize("len_", [1, 5, 32, 33, 100])
+ def test_uuid_len(self, len_):
+ # GH 36345
+ df = DataFrame(data=[["A"]])
+ s = Styler(df, uuid_len=len_, cell_ids=False).to_html()
+ strt = s.find('id="T_')
+ end = s[strt + 6 :].find('"')
+ if len_ > 32:
+ assert end == 32
+ else:
+ assert end == len_
+
+ @pytest.mark.parametrize("len_", [-2, "bad", None])
+ def test_uuid_len_raises(self, len_):
+ # GH 36345
+ df = DataFrame(data=[["A"]])
+ msg = "``uuid_len`` must be an integer in range \\[0, 32\\]."
+ with pytest.raises(TypeError, match=msg):
+ Styler(df, uuid_len=len_, cell_ids=False).to_html()
+
+ @pytest.mark.parametrize(
+ "slc",
+ [
+ IndexSlice[:, :],
+ IndexSlice[:, 1],
+ IndexSlice[1, :],
+ IndexSlice[[1], [1]],
+ IndexSlice[1, [1]],
+ IndexSlice[[1], 1],
+ IndexSlice[1],
+ IndexSlice[1, 1],
+ slice(None, None, None),
+ [0, 1],
+ np.array([0, 1]),
+ Series([0, 1]),
+ ],
+ )
+ def test_non_reducing_slice(self, slc):
+ df = DataFrame([[0, 1], [2, 3]])
+
+ tslice_ = non_reducing_slice(slc)
+ assert isinstance(df.loc[tslice_], DataFrame)
+
+ @pytest.mark.parametrize("box", [list, Series, np.array])
+ def test_list_slice(self, box):
+ # like dataframe getitem
+ subset = box(["A"])
+
+ df = DataFrame({"A": [1, 2], "B": [3, 4]}, index=["A", "B"])
+ expected = IndexSlice[:, ["A"]]
+
+ result = non_reducing_slice(subset)
+ tm.assert_frame_equal(df.loc[result], df.loc[expected])
+
+ def test_non_reducing_slice_on_multiindex(self):
+ # GH 19861
+ dic = {
+ ("a", "d"): [1, 4],
+ ("a", "c"): [2, 3],
+ ("b", "c"): [3, 2],
+ ("b", "d"): [4, 1],
+ }
+ df = DataFrame(dic, index=[0, 1])
+ idx = IndexSlice
+ slice_ = idx[:, idx["b", "d"]]
+ tslice_ = non_reducing_slice(slice_)
+
+ result = df.loc[tslice_]
+ expected = DataFrame({("b", "d"): [4, 1]})
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "slice_",
+ [
+ IndexSlice[:, :],
+ # check cols
+ IndexSlice[:, IndexSlice[["a"]]], # inferred deeper need list
+ IndexSlice[:, IndexSlice[["a"], ["c"]]], # inferred deeper need list
+ IndexSlice[:, IndexSlice["a", "c", :]],
+ IndexSlice[:, IndexSlice["a", :, "e"]],
+ IndexSlice[:, IndexSlice[:, "c", "e"]],
+ IndexSlice[:, IndexSlice["a", ["c", "d"], :]], # check list
+ IndexSlice[:, IndexSlice["a", ["c", "d", "-"], :]], # don't allow missing
+ IndexSlice[:, IndexSlice["a", ["c", "d", "-"], "e"]], # no slice
+ # check rows
+ IndexSlice[IndexSlice[["U"]], :], # inferred deeper need list
+ IndexSlice[IndexSlice[["U"], ["W"]], :], # inferred deeper need list
+ IndexSlice[IndexSlice["U", "W", :], :],
+ IndexSlice[IndexSlice["U", :, "Y"], :],
+ IndexSlice[IndexSlice[:, "W", "Y"], :],
+ IndexSlice[IndexSlice[:, "W", ["Y", "Z"]], :], # check list
+ IndexSlice[IndexSlice[:, "W", ["Y", "Z", "-"]], :], # don't allow missing
+ IndexSlice[IndexSlice["U", "W", ["Y", "Z", "-"]], :], # no slice
+ # check simultaneous
+ IndexSlice[IndexSlice[:, "W", "Y"], IndexSlice["a", "c", :]],
+ ],
+ )
+ def test_non_reducing_multi_slice_on_multiindex(self, slice_):
+ # GH 33562
+ cols = MultiIndex.from_product([["a", "b"], ["c", "d"], ["e", "f"]])
+ idxs = MultiIndex.from_product([["U", "V"], ["W", "X"], ["Y", "Z"]])
+ df = DataFrame(np.arange(64).reshape(8, 8), columns=cols, index=idxs)
+
+ for lvl in [0, 1]:
+ key = slice_[lvl]
+ if isinstance(key, tuple):
+ for subkey in key:
+ if isinstance(subkey, list) and "-" in subkey:
+ # not present in the index level, raises KeyError since 2.0
+ with pytest.raises(KeyError, match="-"):
+ df.loc[slice_]
+ return
+
+ expected = df.loc[slice_]
+ result = df.loc[non_reducing_slice(slice_)]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_hidden_index_names(mi_df):
+ mi_df.index.names = ["Lev0", "Lev1"]
+ mi_styler = mi_df.style
+ ctx = mi_styler._translate(True, True)
+ assert len(ctx["head"]) == 3 # 2 column index levels + 1 index names row
+
+ mi_styler.hide(axis="index", names=True)
+ ctx = mi_styler._translate(True, True)
+ assert len(ctx["head"]) == 2 # index names row is unparsed
+ for i in range(4):
+ assert ctx["body"][0][i]["is_visible"] # 2 index levels + 2 data values visible
+
+ mi_styler.hide(axis="index", level=1)
+ ctx = mi_styler._translate(True, True)
+ assert len(ctx["head"]) == 2 # index names row is still hidden
+ assert ctx["body"][0][0]["is_visible"] is True
+ assert ctx["body"][0][1]["is_visible"] is False
+
+
+def test_hidden_column_names(mi_df):
+ mi_df.columns.names = ["Lev0", "Lev1"]
+ mi_styler = mi_df.style
+ ctx = mi_styler._translate(True, True)
+ assert ctx["head"][0][1]["display_value"] == "Lev0"
+ assert ctx["head"][1][1]["display_value"] == "Lev1"
+
+ mi_styler.hide(names=True, axis="columns")
+ ctx = mi_styler._translate(True, True)
+ assert ctx["head"][0][1]["display_value"] == " "
+ assert ctx["head"][1][1]["display_value"] == " "
+
+ mi_styler.hide(level=0, axis="columns")
+ ctx = mi_styler._translate(True, True)
+ assert len(ctx["head"]) == 1 # no index names and only one visible column headers
+ assert ctx["head"][0][1]["display_value"] == " "
+
+
+@pytest.mark.parametrize("caption", [1, ("a", "b", "c"), (1, "s")])
+def test_caption_raises(mi_styler, caption):
+ msg = "`caption` must be either a string or 2-tuple of strings."
+ with pytest.raises(ValueError, match=msg):
+ mi_styler.set_caption(caption)
+
+
+def test_hiding_headers_over_index_no_sparsify():
+ # GH 43464
+ midx = MultiIndex.from_product([[1, 2], ["a", "a", "b"]])
+ df = DataFrame(9, index=midx, columns=[0])
+ ctx = df.style._translate(False, False)
+ assert len(ctx["body"]) == 6
+ ctx = df.style.hide((1, "a"), axis=0)._translate(False, False)
+ assert len(ctx["body"]) == 4
+ assert "row2" in ctx["body"][0][0]["class"]
+
+
+def test_hiding_headers_over_columns_no_sparsify():
+ # GH 43464
+ midx = MultiIndex.from_product([[1, 2], ["a", "a", "b"]])
+ df = DataFrame(9, columns=midx, index=[0])
+ ctx = df.style._translate(False, False)
+ for ix in [(0, 1), (0, 2), (1, 1), (1, 2)]:
+ assert ctx["head"][ix[0]][ix[1]]["is_visible"] is True
+ ctx = df.style.hide((1, "a"), axis="columns")._translate(False, False)
+ for ix in [(0, 1), (0, 2), (1, 1), (1, 2)]:
+ assert ctx["head"][ix[0]][ix[1]]["is_visible"] is False
+
+
+def test_get_level_lengths_mi_hidden():
+ # GH 43464
+ index = MultiIndex.from_arrays([[1, 1, 1, 2, 2, 2], ["a", "a", "b", "a", "a", "b"]])
+ expected = {
+ (0, 2): 1,
+ (0, 3): 1,
+ (0, 4): 1,
+ (0, 5): 1,
+ (1, 2): 1,
+ (1, 3): 1,
+ (1, 4): 1,
+ (1, 5): 1,
+ }
+ result = _get_level_lengths(
+ index,
+ sparsify=False,
+ max_index=100,
+ hidden_elements=[0, 1, 0, 1], # hidden element can repeat if duplicated index
+ )
+ tm.assert_dict_equal(result, expected)
+
+
+def test_row_trimming_hide_index():
+ # gh 43703
+ df = DataFrame([[1], [2], [3], [4], [5]])
+ with option_context("styler.render.max_rows", 2):
+ ctx = df.style.hide([0, 1], axis="index")._translate(True, True)
+ assert len(ctx["body"]) == 3
+ for r, val in enumerate(["3", "4", "..."]):
+ assert ctx["body"][r][1]["display_value"] == val
+
+
+def test_row_trimming_hide_index_mi():
+ # gh 44247
+ df = DataFrame([[1], [2], [3], [4], [5]])
+ df.index = MultiIndex.from_product([[0], [0, 1, 2, 3, 4]])
+ with option_context("styler.render.max_rows", 2):
+ ctx = df.style.hide([(0, 0), (0, 1)], axis="index")._translate(True, True)
+ assert len(ctx["body"]) == 3
+
+ # level 0 index headers (sparsified)
+ assert {"value": 0, "attributes": 'rowspan="2"', "is_visible": True}.items() <= ctx[
+ "body"
+ ][0][0].items()
+ assert {"value": 0, "attributes": "", "is_visible": False}.items() <= ctx["body"][
+ 1
+ ][0].items()
+ assert {"value": "...", "is_visible": True}.items() <= ctx["body"][2][0].items()
+
+ for r, val in enumerate(["2", "3", "..."]):
+ assert ctx["body"][r][1]["display_value"] == val # level 1 index headers
+ for r, val in enumerate(["3", "4", "..."]):
+ assert ctx["body"][r][2]["display_value"] == val # data values
+
+
+def test_col_trimming_hide_columns():
+ # gh 44272
+ df = DataFrame([[1, 2, 3, 4, 5]])
+ with option_context("styler.render.max_columns", 2):
+ ctx = df.style.hide([0, 1], axis="columns")._translate(True, True)
+
+ assert len(ctx["head"][0]) == 6 # blank, [0, 1 (hidden)], [2 ,3 (visible)], + trim
+ for c, vals in enumerate([(1, False), (2, True), (3, True), ("...", True)]):
+ assert ctx["head"][0][c + 2]["value"] == vals[0]
+ assert ctx["head"][0][c + 2]["is_visible"] == vals[1]
+
+ assert len(ctx["body"][0]) == 6 # index + 2 hidden + 2 visible + trimming col
+
+
+def test_no_empty_apply(mi_styler):
+ # 45313
+ mi_styler.apply(lambda s: ["a:v;"] * 2, subset=[False, False])
+ mi_styler._compute()
+
+
+@pytest.mark.parametrize("format", ["html", "latex", "string"])
+def test_output_buffer(mi_styler, format, temp_file):
+ # gh 47053
+ getattr(mi_styler, f"to_{format}")(temp_file)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_to_latex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_to_latex.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb221686dd165be0dd55dac77c25f11c047f1f23
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/style/test_to_latex.py
@@ -0,0 +1,1091 @@
+from textwrap import dedent
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Series,
+ option_context,
+)
+
+pytest.importorskip("jinja2")
+from pandas.io.formats.style import Styler
+from pandas.io.formats.style_render import (
+ _parse_latex_cell_styles,
+ _parse_latex_css_conversion,
+ _parse_latex_header_span,
+ _parse_latex_table_styles,
+ _parse_latex_table_wrapping,
+)
+
+
+@pytest.fixture
+def df():
+ return DataFrame(
+ {"A": [0, 1], "B": [-0.61, -1.22], "C": Series(["ab", "cd"], dtype=object)}
+ )
+
+
+@pytest.fixture
+def df_ext():
+ return DataFrame(
+ {"A": [0, 1, 2], "B": [-0.61, -1.22, -2.22], "C": ["ab", "cd", "de"]}
+ )
+
+
+@pytest.fixture
+def styler(df):
+ return Styler(df, uuid_len=0, precision=2)
+
+
+def test_minimal_latex_tabular(styler):
+ expected = dedent(
+ """\
+ \\begin{tabular}{lrrl}
+ & A & B & C \\\\
+ 0 & 0 & -0.61 & ab \\\\
+ 1 & 1 & -1.22 & cd \\\\
+ \\end{tabular}
+ """
+ )
+ assert styler.to_latex() == expected
+
+
+def test_tabular_hrules(styler):
+ expected = dedent(
+ """\
+ \\begin{tabular}{lrrl}
+ \\toprule
+ & A & B & C \\\\
+ \\midrule
+ 0 & 0 & -0.61 & ab \\\\
+ 1 & 1 & -1.22 & cd \\\\
+ \\bottomrule
+ \\end{tabular}
+ """
+ )
+ assert styler.to_latex(hrules=True) == expected
+
+
+def test_tabular_custom_hrules(styler):
+ styler.set_table_styles(
+ [
+ {"selector": "toprule", "props": ":hline"},
+ {"selector": "bottomrule", "props": ":otherline"},
+ ]
+ ) # no midrule
+ expected = dedent(
+ """\
+ \\begin{tabular}{lrrl}
+ \\hline
+ & A & B & C \\\\
+ 0 & 0 & -0.61 & ab \\\\
+ 1 & 1 & -1.22 & cd \\\\
+ \\otherline
+ \\end{tabular}
+ """
+ )
+ assert styler.to_latex() == expected
+
+
+def test_column_format(styler):
+ # default setting is already tested in `test_latex_minimal_tabular`
+ styler.set_table_styles([{"selector": "column_format", "props": ":cccc"}])
+
+ assert "\\begin{tabular}{rrrr}" in styler.to_latex(column_format="rrrr")
+ styler.set_table_styles([{"selector": "column_format", "props": ":r|r|cc"}])
+ assert "\\begin{tabular}{r|r|cc}" in styler.to_latex()
+
+
+def test_siunitx_cols(styler):
+ expected = dedent(
+ """\
+ \\begin{tabular}{lSSl}
+ {} & {A} & {B} & {C} \\\\
+ 0 & 0 & -0.61 & ab \\\\
+ 1 & 1 & -1.22 & cd \\\\
+ \\end{tabular}
+ """
+ )
+ assert styler.to_latex(siunitx=True) == expected
+
+
+def test_position(styler):
+ assert "\\begin{table}[h!]" in styler.to_latex(position="h!")
+ assert "\\end{table}" in styler.to_latex(position="h!")
+ styler.set_table_styles([{"selector": "position", "props": ":b!"}])
+ assert "\\begin{table}[b!]" in styler.to_latex()
+ assert "\\end{table}" in styler.to_latex()
+
+
+@pytest.mark.parametrize("env", [None, "longtable"])
+def test_label(styler, env):
+ assert "\n\\label{text}" in styler.to_latex(label="text", environment=env)
+ styler.set_table_styles([{"selector": "label", "props": ":{more §text}"}])
+ assert "\n\\label{more :text}" in styler.to_latex(environment=env)
+
+
+def test_position_float_raises(styler):
+ msg = "`position_float` should be one of 'raggedright', 'raggedleft', 'centering',"
+ with pytest.raises(ValueError, match=msg):
+ styler.to_latex(position_float="bad_string")
+
+ msg = "`position_float` cannot be used in 'longtable' `environment`"
+ with pytest.raises(ValueError, match=msg):
+ styler.to_latex(position_float="centering", environment="longtable")
+
+
+@pytest.mark.parametrize("label", [(None, ""), ("text", "\\label{text}")])
+@pytest.mark.parametrize("position", [(None, ""), ("h!", "{table}[h!]")])
+@pytest.mark.parametrize("caption", [(None, ""), ("text", "\\caption{text}")])
+@pytest.mark.parametrize("column_format", [(None, ""), ("rcrl", "{tabular}{rcrl}")])
+@pytest.mark.parametrize("position_float", [(None, ""), ("centering", "\\centering")])
+def test_kwargs_combinations(
+ styler, label, position, caption, column_format, position_float
+):
+ result = styler.to_latex(
+ label=label[0],
+ position=position[0],
+ caption=caption[0],
+ column_format=column_format[0],
+ position_float=position_float[0],
+ )
+ assert label[1] in result
+ assert position[1] in result
+ assert caption[1] in result
+ assert column_format[1] in result
+ assert position_float[1] in result
+
+
+def test_custom_table_styles(styler):
+ styler.set_table_styles(
+ [
+ {"selector": "mycommand", "props": ":{myoptions}"},
+ {"selector": "mycommand2", "props": ":{myoptions2}"},
+ ]
+ )
+ expected = dedent(
+ """\
+ \\begin{table}
+ \\mycommand{myoptions}
+ \\mycommand2{myoptions2}
+ """
+ )
+ assert expected in styler.to_latex()
+
+
+def test_cell_styling(styler):
+ styler.highlight_max(props="itshape:;Huge:--wrap;")
+ expected = dedent(
+ """\
+ \\begin{tabular}{lrrl}
+ & A & B & C \\\\
+ 0 & 0 & \\itshape {\\Huge -0.61} & ab \\\\
+ 1 & \\itshape {\\Huge 1} & -1.22 & \\itshape {\\Huge cd} \\\\
+ \\end{tabular}
+ """
+ )
+ assert expected == styler.to_latex()
+
+
+def test_multiindex_columns(df):
+ cidx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
+ df.columns = cidx
+ expected = dedent(
+ """\
+ \\begin{tabular}{lrrl}
+ & \\multicolumn{2}{r}{A} & B \\\\
+ & a & b & c \\\\
+ 0 & 0 & -0.61 & ab \\\\
+ 1 & 1 & -1.22 & cd \\\\
+ \\end{tabular}
+ """
+ )
+ s = df.style.format(precision=2)
+ assert expected == s.to_latex()
+
+ # non-sparse
+ expected = dedent(
+ """\
+ \\begin{tabular}{lrrl}
+ & A & A & B \\\\
+ & a & b & c \\\\
+ 0 & 0 & -0.61 & ab \\\\
+ 1 & 1 & -1.22 & cd \\\\
+ \\end{tabular}
+ """
+ )
+ s = df.style.format(precision=2)
+ assert expected == s.to_latex(sparse_columns=False)
+
+
+def test_multiindex_row(df_ext):
+ ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
+ df_ext.index = ridx
+ expected = dedent(
+ """\
+ \\begin{tabular}{llrrl}
+ & & A & B & C \\\\
+ \\multirow[c]{2}{*}{A} & a & 0 & -0.61 & ab \\\\
+ & b & 1 & -1.22 & cd \\\\
+ B & c & 2 & -2.22 & de \\\\
+ \\end{tabular}
+ """
+ )
+ styler = df_ext.style.format(precision=2)
+ result = styler.to_latex()
+ assert expected == result
+
+ # non-sparse
+ expected = dedent(
+ """\
+ \\begin{tabular}{llrrl}
+ & & A & B & C \\\\
+ A & a & 0 & -0.61 & ab \\\\
+ A & b & 1 & -1.22 & cd \\\\
+ B & c & 2 & -2.22 & de \\\\
+ \\end{tabular}
+ """
+ )
+ result = styler.to_latex(sparse_index=False)
+ assert expected == result
+
+
+def test_multirow_naive(df_ext):
+ ridx = MultiIndex.from_tuples([("X", "x"), ("X", "y"), ("Y", "z")])
+ df_ext.index = ridx
+ expected = dedent(
+ """\
+ \\begin{tabular}{llrrl}
+ & & A & B & C \\\\
+ X & x & 0 & -0.61 & ab \\\\
+ & y & 1 & -1.22 & cd \\\\
+ Y & z & 2 & -2.22 & de \\\\
+ \\end{tabular}
+ """
+ )
+ styler = df_ext.style.format(precision=2)
+ result = styler.to_latex(multirow_align="naive")
+ assert expected == result
+
+
+def test_multiindex_row_and_col(df_ext):
+ cidx = MultiIndex.from_tuples([("Z", "a"), ("Z", "b"), ("Y", "c")])
+ ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
+ df_ext.index, df_ext.columns = ridx, cidx
+ expected = dedent(
+ """\
+ \\begin{tabular}{llrrl}
+ & & \\multicolumn{2}{l}{Z} & Y \\\\
+ & & a & b & c \\\\
+ \\multirow[b]{2}{*}{A} & a & 0 & -0.61 & ab \\\\
+ & b & 1 & -1.22 & cd \\\\
+ B & c & 2 & -2.22 & de \\\\
+ \\end{tabular}
+ """
+ )
+ styler = df_ext.style.format(precision=2)
+ result = styler.to_latex(multirow_align="b", multicol_align="l")
+ assert result == expected
+
+ # non-sparse
+ expected = dedent(
+ """\
+ \\begin{tabular}{llrrl}
+ & & Z & Z & Y \\\\
+ & & a & b & c \\\\
+ A & a & 0 & -0.61 & ab \\\\
+ A & b & 1 & -1.22 & cd \\\\
+ B & c & 2 & -2.22 & de \\\\
+ \\end{tabular}
+ """
+ )
+ result = styler.to_latex(sparse_index=False, sparse_columns=False)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "multicol_align, siunitx, header",
+ [
+ ("naive-l", False, " & A & &"),
+ ("naive-r", False, " & & & A"),
+ ("naive-l", True, "{} & {A} & {} & {}"),
+ ("naive-r", True, "{} & {} & {} & {A}"),
+ ],
+)
+def test_multicol_naive(df, multicol_align, siunitx, header):
+ ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("A", "c")])
+ df.columns = ridx
+ level1 = " & a & b & c" if not siunitx else "{} & {a} & {b} & {c}"
+ col_format = "lrrl" if not siunitx else "lSSl"
+ expected = dedent(
+ f"""\
+ \\begin{{tabular}}{{{col_format}}}
+ {header} \\\\
+ {level1} \\\\
+ 0 & 0 & -0.61 & ab \\\\
+ 1 & 1 & -1.22 & cd \\\\
+ \\end{{tabular}}
+ """
+ )
+ styler = df.style.format(precision=2)
+ result = styler.to_latex(multicol_align=multicol_align, siunitx=siunitx)
+ assert expected == result
+
+
+def test_multi_options(df_ext):
+ cidx = MultiIndex.from_tuples([("Z", "a"), ("Z", "b"), ("Y", "c")])
+ ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
+ df_ext.index, df_ext.columns = ridx, cidx
+ styler = df_ext.style.format(precision=2)
+
+ expected = dedent(
+ """\
+ & & \\multicolumn{2}{r}{Z} & Y \\\\
+ & & a & b & c \\\\
+ \\multirow[c]{2}{*}{A} & a & 0 & -0.61 & ab \\\\
+ """
+ )
+ result = styler.to_latex()
+ assert expected in result
+
+ with option_context("styler.latex.multicol_align", "l"):
+ assert " & & \\multicolumn{2}{l}{Z} & Y \\\\" in styler.to_latex()
+
+ with option_context("styler.latex.multirow_align", "b"):
+ assert "\\multirow[b]{2}{*}{A} & a & 0 & -0.61 & ab \\\\" in styler.to_latex()
+
+
+def test_multiindex_columns_hidden():
+ df = DataFrame([[1, 2, 3, 4]])
+ df.columns = MultiIndex.from_tuples([("A", 1), ("A", 2), ("A", 3), ("B", 1)])
+ s = df.style
+ assert "{tabular}{lrrrr}" in s.to_latex()
+ s.set_table_styles([]) # reset the position command
+ s.hide([("A", 2)], axis="columns")
+ assert "{tabular}{lrrr}" in s.to_latex()
+
+
+@pytest.mark.parametrize(
+ "option, value",
+ [
+ ("styler.sparse.index", True),
+ ("styler.sparse.index", False),
+ ("styler.sparse.columns", True),
+ ("styler.sparse.columns", False),
+ ],
+)
+def test_sparse_options(df_ext, option, value):
+ cidx = MultiIndex.from_tuples([("Z", "a"), ("Z", "b"), ("Y", "c")])
+ ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
+ df_ext.index, df_ext.columns = ridx, cidx
+ styler = df_ext.style
+
+ latex1 = styler.to_latex()
+ with option_context(option, value):
+ latex2 = styler.to_latex()
+ assert (latex1 == latex2) is value
+
+
+def test_hidden_index(styler):
+ styler.hide(axis="index")
+ expected = dedent(
+ """\
+ \\begin{tabular}{rrl}
+ A & B & C \\\\
+ 0 & -0.61 & ab \\\\
+ 1 & -1.22 & cd \\\\
+ \\end{tabular}
+ """
+ )
+ assert styler.to_latex() == expected
+
+
+@pytest.mark.parametrize("environment", ["table", "figure*", None])
+def test_comprehensive(df_ext, environment):
+ # test as many low level features simultaneously as possible
+ cidx = MultiIndex.from_tuples([("Z", "a"), ("Z", "b"), ("Y", "c")])
+ ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
+ df_ext.index, df_ext.columns = ridx, cidx
+ stlr = df_ext.style
+ stlr.set_caption("mycap")
+ stlr.set_table_styles(
+ [
+ {"selector": "label", "props": ":{fig§item}"},
+ {"selector": "position", "props": ":h!"},
+ {"selector": "position_float", "props": ":centering"},
+ {"selector": "column_format", "props": ":rlrlr"},
+ {"selector": "toprule", "props": ":toprule"},
+ {"selector": "midrule", "props": ":midrule"},
+ {"selector": "bottomrule", "props": ":bottomrule"},
+ {"selector": "rowcolors", "props": ":{3}{pink}{}"}, # custom command
+ ]
+ )
+ stlr.highlight_max(axis=0, props="textbf:--rwrap;cellcolor:[rgb]{1,1,0.6}--rwrap")
+ stlr.highlight_max(axis=None, props="Huge:--wrap;", subset=[("Z", "a"), ("Z", "b")])
+
+ expected = (
+ """\
+\\begin{table}[h!]
+\\centering
+\\caption{mycap}
+\\label{fig:item}
+\\rowcolors{3}{pink}{}
+\\begin{tabular}{rlrlr}
+\\toprule
+ & & \\multicolumn{2}{r}{Z} & Y \\\\
+ & & a & b & c \\\\
+\\midrule
+\\multirow[c]{2}{*}{A} & a & 0 & \\textbf{\\cellcolor[rgb]{1,1,0.6}{-0.61}} & ab \\\\
+ & b & 1 & -1.22 & cd \\\\
+B & c & \\textbf{\\cellcolor[rgb]{1,1,0.6}{{\\Huge 2}}} & -2.22 & """
+ """\
+\\textbf{\\cellcolor[rgb]{1,1,0.6}{de}} \\\\
+\\bottomrule
+\\end{tabular}
+\\end{table}
+"""
+ ).replace("table", environment if environment else "table")
+ result = stlr.format(precision=2).to_latex(environment=environment)
+ assert result == expected
+
+
+def test_environment_option(styler):
+ with option_context("styler.latex.environment", "bar-env"):
+ assert "\\begin{bar-env}" in styler.to_latex()
+ assert "\\begin{foo-env}" in styler.to_latex(environment="foo-env")
+
+
+def test_parse_latex_table_styles(styler):
+ styler.set_table_styles(
+ [
+ {"selector": "foo", "props": [("attr", "value")]},
+ {"selector": "bar", "props": [("attr", "overwritten")]},
+ {"selector": "bar", "props": [("attr", "baz"), ("attr2", "ignored")]},
+ {"selector": "label", "props": [("", "{fig§item}")]},
+ ]
+ )
+ assert _parse_latex_table_styles(styler.table_styles, "bar") == "baz"
+
+ # test '§' replaced by ':' [for CSS compatibility]
+ assert _parse_latex_table_styles(styler.table_styles, "label") == "{fig:item}"
+
+
+def test_parse_latex_cell_styles_basic(): # test nesting
+ cell_style = [("itshape", "--rwrap"), ("cellcolor", "[rgb]{0,1,1}--rwrap")]
+ expected = "\\itshape{\\cellcolor[rgb]{0,1,1}{text}}"
+ assert _parse_latex_cell_styles(cell_style, "text") == expected
+
+
+@pytest.mark.parametrize(
+ "wrap_arg, expected",
+ [ # test wrapping
+ ("", "\\ "),
+ ("--wrap", "{\\ }"),
+ ("--nowrap", "\\ "),
+ ("--lwrap", "{\\} "),
+ ("--dwrap", "{\\}{}"),
+ ("--rwrap", "\\{}"),
+ ],
+)
+def test_parse_latex_cell_styles_braces(wrap_arg, expected):
+ cell_style = [("", f"{wrap_arg}")]
+ assert _parse_latex_cell_styles(cell_style, "") == expected
+
+
+def test_parse_latex_header_span():
+ cell = {"attributes": 'colspan="3"', "display_value": "text", "cellstyle": []}
+ expected = "\\multicolumn{3}{Y}{text}"
+ assert _parse_latex_header_span(cell, "X", "Y") == expected
+
+ cell = {"attributes": 'rowspan="5"', "display_value": "text", "cellstyle": []}
+ expected = "\\multirow[X]{5}{*}{text}"
+ assert _parse_latex_header_span(cell, "X", "Y") == expected
+
+ cell = {"display_value": "text", "cellstyle": []}
+ assert _parse_latex_header_span(cell, "X", "Y") == "text"
+
+ cell = {"display_value": "text", "cellstyle": [("bfseries", "--rwrap")]}
+ assert _parse_latex_header_span(cell, "X", "Y") == "\\bfseries{text}"
+
+
+def test_parse_latex_table_wrapping(styler):
+ styler.set_table_styles(
+ [
+ {"selector": "toprule", "props": ":value"},
+ {"selector": "bottomrule", "props": ":value"},
+ {"selector": "midrule", "props": ":value"},
+ {"selector": "column_format", "props": ":value"},
+ ]
+ )
+ assert _parse_latex_table_wrapping(styler.table_styles, styler.caption) is False
+ assert _parse_latex_table_wrapping(styler.table_styles, "some caption") is True
+ styler.set_table_styles(
+ [
+ {"selector": "not-ignored", "props": ":value"},
+ ],
+ overwrite=False,
+ )
+ assert _parse_latex_table_wrapping(styler.table_styles, None) is True
+
+
+def test_short_caption(styler):
+ result = styler.to_latex(caption=("full cap", "short cap"))
+ assert "\\caption[short cap]{full cap}" in result
+
+
+@pytest.mark.parametrize(
+ "css, expected",
+ [
+ ([("color", "red")], [("color", "{red}")]), # test color and input format types
+ (
+ [("color", "rgb(128, 128, 128 )")],
+ [("color", "[rgb]{0.502, 0.502, 0.502}")],
+ ),
+ (
+ [("color", "rgb(128, 50%, 25% )")],
+ [("color", "[rgb]{0.502, 0.500, 0.250}")],
+ ),
+ (
+ [("color", "rgba(128,128,128,1)")],
+ [("color", "[rgb]{0.502, 0.502, 0.502}")],
+ ),
+ ([("color", "#FF00FF")], [("color", "[HTML]{FF00FF}")]),
+ ([("color", "#F0F")], [("color", "[HTML]{FF00FF}")]),
+ ([("font-weight", "bold")], [("bfseries", "")]), # test font-weight and types
+ ([("font-weight", "bolder")], [("bfseries", "")]),
+ ([("font-weight", "normal")], []),
+ ([("background-color", "red")], [("cellcolor", "{red}--lwrap")]),
+ (
+ [("background-color", "#FF00FF")], # test background-color command and wrap
+ [("cellcolor", "[HTML]{FF00FF}--lwrap")],
+ ),
+ ([("font-style", "italic")], [("itshape", "")]), # test font-style and types
+ ([("font-style", "oblique")], [("slshape", "")]),
+ ([("font-style", "normal")], []),
+ ([("color", "red /*--dwrap*/")], [("color", "{red}--dwrap")]), # css comments
+ ([("background-color", "red /* --dwrap */")], [("cellcolor", "{red}--dwrap")]),
+ ],
+)
+def test_parse_latex_css_conversion(css, expected):
+ result = _parse_latex_css_conversion(css)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "env, inner_env",
+ [
+ (None, "tabular"),
+ ("table", "tabular"),
+ ("longtable", "longtable"),
+ ],
+)
+@pytest.mark.parametrize(
+ "convert, exp", [(True, "bfseries"), (False, "font-weightbold")]
+)
+def test_parse_latex_css_convert_minimal(styler, env, inner_env, convert, exp):
+ # parameters ensure longtable template is also tested
+ styler.highlight_max(props="font-weight:bold;")
+ result = styler.to_latex(convert_css=convert, environment=env)
+ expected = dedent(
+ f"""\
+ 0 & 0 & \\{exp} -0.61 & ab \\\\
+ 1 & \\{exp} 1 & -1.22 & \\{exp} cd \\\\
+ \\end{{{inner_env}}}
+ """
+ )
+ assert expected in result
+
+
+def test_parse_latex_css_conversion_option():
+ css = [("command", "option--latex--wrap")]
+ expected = [("command", "option--wrap")]
+ result = _parse_latex_css_conversion(css)
+ assert result == expected
+
+
+def test_styler_object_after_render(styler):
+ # GH 42320
+ pre_render = styler._copy(deepcopy=True)
+ styler.to_latex(
+ column_format="rllr",
+ position="h",
+ position_float="centering",
+ hrules=True,
+ label="my lab",
+ caption="my cap",
+ )
+
+ assert pre_render.table_styles == styler.table_styles
+ assert pre_render.caption == styler.caption
+
+
+def test_longtable_comprehensive(styler):
+ result = styler.to_latex(
+ environment="longtable", hrules=True, label="fig:A", caption=("full", "short")
+ )
+ expected = dedent(
+ """\
+ \\begin{longtable}{lrrl}
+ \\caption[short]{full} \\label{fig:A} \\\\
+ \\toprule
+ & A & B & C \\\\
+ \\midrule
+ \\endfirsthead
+ \\caption[]{full} \\\\
+ \\toprule
+ & A & B & C \\\\
+ \\midrule
+ \\endhead
+ \\midrule
+ \\multicolumn{4}{r}{Continued on next page} \\\\
+ \\midrule
+ \\endfoot
+ \\bottomrule
+ \\endlastfoot
+ 0 & 0 & -0.61 & ab \\\\
+ 1 & 1 & -1.22 & cd \\\\
+ \\end{longtable}
+ """
+ )
+ assert result == expected
+
+
+def test_longtable_minimal(styler):
+ result = styler.to_latex(environment="longtable")
+ expected = dedent(
+ """\
+ \\begin{longtable}{lrrl}
+ & A & B & C \\\\
+ \\endfirsthead
+ & A & B & C \\\\
+ \\endhead
+ \\multicolumn{4}{r}{Continued on next page} \\\\
+ \\endfoot
+ \\endlastfoot
+ 0 & 0 & -0.61 & ab \\\\
+ 1 & 1 & -1.22 & cd \\\\
+ \\end{longtable}
+ """
+ )
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "sparse, exp, siunitx",
+ [
+ (True, "{} & \\multicolumn{2}{r}{A} & {B}", True),
+ (False, "{} & {A} & {A} & {B}", True),
+ (True, " & \\multicolumn{2}{r}{A} & B", False),
+ (False, " & A & A & B", False),
+ ],
+)
+def test_longtable_multiindex_columns(df, sparse, exp, siunitx):
+ cidx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
+ df.columns = cidx
+ with_si = "{} & {a} & {b} & {c} \\\\"
+ without_si = " & a & b & c \\\\"
+ expected = dedent(
+ f"""\
+ \\begin{{longtable}}{{l{"SS" if siunitx else "rr"}l}}
+ {exp} \\\\
+ {with_si if siunitx else without_si}
+ \\endfirsthead
+ {exp} \\\\
+ {with_si if siunitx else without_si}
+ \\endhead
+ """
+ )
+ result = df.style.to_latex(
+ environment="longtable", sparse_columns=sparse, siunitx=siunitx
+ )
+ assert expected in result
+
+
+@pytest.mark.parametrize(
+ "caption, cap_exp",
+ [
+ ("full", ("{full}", "")),
+ (("full", "short"), ("{full}", "[short]")),
+ ],
+)
+@pytest.mark.parametrize("label, lab_exp", [(None, ""), ("tab:A", " \\label{tab:A}")])
+def test_longtable_caption_label(styler, caption, cap_exp, label, lab_exp):
+ cap_exp1 = f"\\caption{cap_exp[1]}{cap_exp[0]}"
+ cap_exp2 = f"\\caption[]{cap_exp[0]}"
+
+ expected = dedent(
+ f"""\
+ {cap_exp1}{lab_exp} \\\\
+ & A & B & C \\\\
+ \\endfirsthead
+ {cap_exp2} \\\\
+ """
+ )
+ assert expected in styler.to_latex(
+ environment="longtable", caption=caption, label=label
+ )
+
+
+@pytest.mark.parametrize("index", [True, False])
+@pytest.mark.parametrize(
+ "columns, siunitx",
+ [
+ (True, True),
+ (True, False),
+ (False, False),
+ ],
+)
+def test_apply_map_header_render_mi(df_ext, index, columns, siunitx):
+ cidx = MultiIndex.from_tuples([("Z", "a"), ("Z", "b"), ("Y", "c")])
+ ridx = MultiIndex.from_tuples([("A", "a"), ("A", "b"), ("B", "c")])
+ df_ext.index, df_ext.columns = ridx, cidx
+ styler = df_ext.style
+
+ func = lambda v: "bfseries: --rwrap" if "A" in v or "Z" in v or "c" in v else None
+
+ if index:
+ styler.map_index(func, axis="index")
+ if columns:
+ styler.map_index(func, axis="columns")
+
+ result = styler.to_latex(siunitx=siunitx)
+
+ expected_index = dedent(
+ """\
+ \\multirow[c]{2}{*}{\\bfseries{A}} & a & 0 & -0.610000 & ab \\\\
+ \\bfseries{} & b & 1 & -1.220000 & cd \\\\
+ B & \\bfseries{c} & 2 & -2.220000 & de \\\\
+ """
+ )
+ assert (expected_index in result) is index
+
+ exp_cols_si = dedent(
+ """\
+ {} & {} & \\multicolumn{2}{r}{\\bfseries{Z}} & {Y} \\\\
+ {} & {} & {a} & {b} & {\\bfseries{c}} \\\\
+ """
+ )
+ exp_cols_no_si = """\
+ & & \\multicolumn{2}{r}{\\bfseries{Z}} & Y \\\\
+ & & a & b & \\bfseries{c} \\\\
+"""
+ assert ((exp_cols_si if siunitx else exp_cols_no_si) in result) is columns
+
+
+def test_repr_option(styler):
+ assert "' in result
+ assert '' not in result
+ assert 'title="' not in result
+
+
+def test_tooltip_css_class(styler):
+ # GH 21266
+ result = styler.set_tooltips(
+ DataFrame([["tooltip"]], index=["x"], columns=["A"]),
+ css_class="other-class",
+ props=[("color", "green")],
+ ).to_html()
+ assert "#T_ .other-class {\n color: green;\n" in result
+ assert '#T_ #T__row0_col0 .other-class::after {\n content: "tooltip";\n' in result
+
+ # GH 39563
+ result = styler.set_tooltips( # set_tooltips overwrites previous
+ DataFrame([["tooltip"]], index=["x"], columns=["A"]),
+ css_class="another-class",
+ props="color:green;color:red;",
+ ).to_html()
+ assert "#T_ .another-class {\n color: green;\n color: red;\n}" in result
+
+
+@pytest.mark.parametrize(
+ "data, columns, index",
+ [
+ # Test basic reindex and ignoring blank
+ ([["Min", "Max"], [np.nan, ""]], ["A", "C"], ["x", "y"]),
+ # Test non-referenced columns, reversed col names, short index
+ ([["Max", "Min", "Bad-Col"]], ["C", "A", "D"], ["x"]),
+ ],
+)
+def test_tooltip_render_as_title(data, columns, index, styler):
+ ttips = DataFrame(data=data, columns=columns, index=index)
+ # GH 56605
+ result = styler.set_tooltips(ttips, as_title_attribute=True).to_html()
+
+ # test css not added
+ assert "#T_ .pd-t {\n visibility: hidden;\n" not in result
+
+ # test 'Min' tooltip added as title attribute and css does not exist
+ assert "#T_ #T__row0_col0:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert '#T_ #T__row0_col0 .pd-t::after {\n content: "Min";\n}' not in result
+ assert 'class="data row0 col0" title="Min">0' in result
+
+ # test 'Max' tooltip added as title attribute and css does not exist
+ assert "#T_ #T__row0_col2:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert '#T_ #T__row0_col2 .pd-t::after {\n content: "Max";\n}' not in result
+ assert 'class="data row0 col2" title="Max">2' in result
+
+ # test Nan, empty string and bad column ignored
+ assert "#T_ #T__row1_col0:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert "#T_ #T__row1_col1:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert "#T_ #T__row0_col1:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert "#T_ #T__row1_col2:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert "Bad-Col" not in result
+ assert 'class="data row0 col1" >1' in result
+ assert 'class="data row1 col0" >3' in result
+ assert 'class="data row1 col1" >4' in result
+ assert 'class="data row1 col2" >5' in result
+ assert 'class="data row2 col0" >6' in result
+ assert 'class="data row2 col1" >7' in result
+ assert 'class="data row2 col2" >8' in result
+
+
+def test_tooltip_render_as_title_with_hidden_index_level():
+ df = DataFrame(
+ data=[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
+ columns=["A", "B", "C"],
+ index=MultiIndex.from_arrays(
+ [["x", "y", "z"], [1, 2, 3], ["aa", "bb", "cc"]],
+ names=["alpha", "num", "char"],
+ ),
+ )
+ ttips = DataFrame(
+ # Test basic reindex and ignoring blank, and hide level 2 (num) from index
+ data=[["Min", "Max"], [np.nan, ""]],
+ columns=["A", "C"],
+ index=MultiIndex.from_arrays(
+ [["x", "y"], [1, 2], ["aa", "bb"]], names=["alpha", "num", "char"]
+ ),
+ )
+ styler = Styler(df, uuid_len=0)
+ styler = styler.hide(axis=0, level=-1, names=True)
+ # GH 56605
+ result = styler.set_tooltips(ttips, as_title_attribute=True).to_html()
+
+ # test css not added
+ assert "#T_ .pd-t {\n visibility: hidden;\n" not in result
+
+ # test 'Min' tooltip added as title attribute and css does not exist
+ assert "#T_ #T__row0_col0:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert '#T_ #T__row0_col0 .pd-t::after {\n content: "Min";\n}' not in result
+ assert 'class="data row0 col0" title="Min">0' in result
+
+ # test 'Max' tooltip added as title attribute and css does not exist
+ assert "#T_ #T__row0_col2:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert '#T_ #T__row0_col2 .pd-t::after {\n content: "Max";\n}' not in result
+ assert 'class="data row0 col2" title="Max">2' in result
+
+ # test Nan, empty string and bad column ignored
+ assert "#T_ #T__row1_col0:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert "#T_ #T__row1_col1:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert "#T_ #T__row0_col1:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert "#T_ #T__row1_col2:hover .pd-t {\n visibility: visible;\n}" not in result
+ assert "Bad-Col" not in result
+ assert 'class="data row0 col1" >1' in result
+ assert 'class="data row1 col0" >3' in result
+ assert 'class="data row1 col1" >4' in result
+ assert 'class="data row1 col2" >5' in result
+ assert 'class="data row2 col0" >6' in result
+ assert 'class="data row2 col1" >7' in result
+ assert 'class="data row2 col2" >8' in result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_console.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_console.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd7b57df9baed18b172dc8398a61a49e9435f82a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_console.py
@@ -0,0 +1,72 @@
+import locale
+
+import pytest
+
+from pandas._config import detect_console_encoding
+
+
+class MockEncoding:
+ """
+ Used to add a side effect when accessing the 'encoding' property. If the
+ side effect is a str in nature, the value will be returned. Otherwise, the
+ side effect should be an exception that will be raised.
+ """
+
+ def __init__(self, encoding) -> None:
+ super().__init__()
+ self.val = encoding
+
+ @property
+ def encoding(self):
+ return self.raise_or_return(self.val)
+
+ @staticmethod
+ def raise_or_return(val):
+ if isinstance(val, str):
+ return val
+ else:
+ raise val
+
+
+@pytest.mark.parametrize("empty,filled", [["stdin", "stdout"], ["stdout", "stdin"]])
+def test_detect_console_encoding_from_stdout_stdin(monkeypatch, empty, filled):
+ # Ensures that when sys.stdout.encoding or sys.stdin.encoding is used when
+ # they have values filled.
+ # GH 21552
+ with monkeypatch.context() as context:
+ context.setattr(f"sys.{empty}", MockEncoding(""))
+ context.setattr(f"sys.{filled}", MockEncoding(filled))
+ assert detect_console_encoding() == filled
+
+
+@pytest.mark.parametrize("encoding", [AttributeError, OSError, "ascii"])
+def test_detect_console_encoding_fallback_to_locale(monkeypatch, encoding):
+ # GH 21552
+ with monkeypatch.context() as context:
+ context.setattr("locale.getpreferredencoding", lambda: "foo")
+ context.setattr("sys.stdout", MockEncoding(encoding))
+ assert detect_console_encoding() == "foo"
+
+
+@pytest.mark.parametrize(
+ "std,locale",
+ [
+ ["ascii", "ascii"],
+ ["ascii", locale.Error],
+ [AttributeError, "ascii"],
+ [AttributeError, locale.Error],
+ [OSError, "ascii"],
+ [OSError, locale.Error],
+ ],
+)
+def test_detect_console_encoding_fallback_to_default(monkeypatch, std, locale):
+ # When both the stdout/stdin encoding and locale preferred encoding checks
+ # fail (or return 'ascii', we should default to the sys default encoding.
+ # GH 21552
+ with monkeypatch.context() as context:
+ context.setattr(
+ "locale.getpreferredencoding", lambda: MockEncoding.raise_or_return(locale)
+ )
+ context.setattr("sys.stdout", MockEncoding(std))
+ context.setattr("sys.getdefaultencoding", lambda: "sysDefaultEncoding")
+ assert detect_console_encoding() == "sysDefaultEncoding"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_css.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_css.py
new file mode 100644
index 0000000000000000000000000000000000000000..642a56270434424ac2347cff55bb37eaf4608e50
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_css.py
@@ -0,0 +1,288 @@
+import pytest
+
+from pandas.errors import CSSWarning
+
+import pandas._testing as tm
+
+from pandas.io.formats.css import CSSResolver
+
+
+def assert_resolves(css, props, inherited=None):
+ resolve = CSSResolver()
+ actual = resolve(css, inherited=inherited)
+ assert props == actual
+
+
+def assert_same_resolution(css1, css2, inherited=None):
+ resolve = CSSResolver()
+ resolved1 = resolve(css1, inherited=inherited)
+ resolved2 = resolve(css2, inherited=inherited)
+ assert resolved1 == resolved2
+
+
+@pytest.mark.parametrize(
+ "name,norm,abnorm",
+ [
+ (
+ "whitespace",
+ "hello: world; foo: bar",
+ " \t hello \t :\n world \n ; \n foo: \tbar\n\n",
+ ),
+ ("case", "hello: world; foo: bar", "Hello: WORLD; foO: bar"),
+ ("empty-decl", "hello: world; foo: bar", "; hello: world;; foo: bar;\n; ;"),
+ ("empty-list", "", ";"),
+ ],
+)
+def test_css_parse_normalisation(name, norm, abnorm):
+ assert_same_resolution(norm, abnorm)
+
+
+@pytest.mark.parametrize(
+ "invalid_css,remainder,msg",
+ [
+ # No colon
+ ("hello-world", "", "expected a colon"),
+ ("border-style: solid; hello-world", "border-style: solid", "expected a colon"),
+ (
+ "border-style: solid; hello-world; font-weight: bold",
+ "border-style: solid; font-weight: bold",
+ "expected a colon",
+ ),
+ # Unclosed string fail
+ # Invalid size
+ ("font-size: blah", "font-size: 1em", "Unhandled size"),
+ ("font-size: 1a2b", "font-size: 1em", "Unhandled size"),
+ ("font-size: 1e5pt", "font-size: 1em", "Unhandled size"),
+ ("font-size: 1+6pt", "font-size: 1em", "Unhandled size"),
+ ("font-size: 1unknownunit", "font-size: 1em", "Unhandled size"),
+ ("font-size: 10", "font-size: 1em", "Unhandled size"),
+ ("font-size: 10 pt", "font-size: 1em", "Unhandled size"),
+ # Too many args
+ ("border-top: 1pt solid red green", "border-top: 1pt solid green", "Too many"),
+ ],
+)
+def test_css_parse_invalid(invalid_css, remainder, msg):
+ with tm.assert_produces_warning(CSSWarning, match=msg):
+ assert_same_resolution(invalid_css, remainder)
+
+
+@pytest.mark.parametrize(
+ "shorthand,expansions",
+ [
+ ("margin", ["margin-top", "margin-right", "margin-bottom", "margin-left"]),
+ ("padding", ["padding-top", "padding-right", "padding-bottom", "padding-left"]),
+ (
+ "border-width",
+ [
+ "border-top-width",
+ "border-right-width",
+ "border-bottom-width",
+ "border-left-width",
+ ],
+ ),
+ (
+ "border-color",
+ [
+ "border-top-color",
+ "border-right-color",
+ "border-bottom-color",
+ "border-left-color",
+ ],
+ ),
+ (
+ "border-style",
+ [
+ "border-top-style",
+ "border-right-style",
+ "border-bottom-style",
+ "border-left-style",
+ ],
+ ),
+ ],
+)
+def test_css_side_shorthands(shorthand, expansions):
+ top, right, bottom, left = expansions
+
+ assert_resolves(
+ f"{shorthand}: 1pt", {top: "1pt", right: "1pt", bottom: "1pt", left: "1pt"}
+ )
+
+ assert_resolves(
+ f"{shorthand}: 1pt 4pt", {top: "1pt", right: "4pt", bottom: "1pt", left: "4pt"}
+ )
+
+ assert_resolves(
+ f"{shorthand}: 1pt 4pt 2pt",
+ {top: "1pt", right: "4pt", bottom: "2pt", left: "4pt"},
+ )
+
+ assert_resolves(
+ f"{shorthand}: 1pt 4pt 2pt 0pt",
+ {top: "1pt", right: "4pt", bottom: "2pt", left: "0pt"},
+ )
+
+ with tm.assert_produces_warning(CSSWarning, match="Could not expand"):
+ assert_resolves(f"{shorthand}: 1pt 1pt 1pt 1pt 1pt", {})
+
+
+@pytest.mark.parametrize(
+ "shorthand,sides",
+ [
+ ("border-top", ["top"]),
+ ("border-right", ["right"]),
+ ("border-bottom", ["bottom"]),
+ ("border-left", ["left"]),
+ ("border", ["top", "right", "bottom", "left"]),
+ ],
+)
+def test_css_border_shorthand_sides(shorthand, sides):
+ def create_border_dict(sides, color=None, style=None, width=None):
+ resolved = {}
+ for side in sides:
+ if color:
+ resolved[f"border-{side}-color"] = color
+ if style:
+ resolved[f"border-{side}-style"] = style
+ if width:
+ resolved[f"border-{side}-width"] = width
+ return resolved
+
+ assert_resolves(
+ f"{shorthand}: 1pt red solid", create_border_dict(sides, "red", "solid", "1pt")
+ )
+
+
+@pytest.mark.parametrize(
+ "prop, expected",
+ [
+ ("1pt red solid", ("red", "solid", "1pt")),
+ ("red 1pt solid", ("red", "solid", "1pt")),
+ ("red solid 1pt", ("red", "solid", "1pt")),
+ ("solid 1pt red", ("red", "solid", "1pt")),
+ ("red solid", ("red", "solid", "1.500000pt")),
+ # Note: color=black is not CSS conforming
+ # (See https://drafts.csswg.org/css-backgrounds/#border-shorthands)
+ ("1pt solid", ("black", "solid", "1pt")),
+ ("1pt red", ("red", "none", "1pt")),
+ ("red", ("red", "none", "1.500000pt")),
+ ("1pt", ("black", "none", "1pt")),
+ ("solid", ("black", "solid", "1.500000pt")),
+ # Sizes
+ ("1em", ("black", "none", "12pt")),
+ ],
+)
+def test_css_border_shorthands(prop, expected):
+ color, style, width = expected
+
+ assert_resolves(
+ f"border-left: {prop}",
+ {
+ "border-left-color": color,
+ "border-left-style": style,
+ "border-left-width": width,
+ },
+ )
+
+
+@pytest.mark.parametrize(
+ "style,inherited,equiv",
+ [
+ ("margin: 1px; margin: 2px", "", "margin: 2px"),
+ ("margin: 1px", "margin: 2px", "margin: 1px"),
+ ("margin: 1px; margin: inherit", "margin: 2px", "margin: 2px"),
+ (
+ "margin: 1px; margin-top: 2px",
+ "",
+ "margin-left: 1px; margin-right: 1px; margin-bottom: 1px; margin-top: 2px",
+ ),
+ ("margin-top: 2px", "margin: 1px", "margin: 1px; margin-top: 2px"),
+ ("margin: 1px", "margin-top: 2px", "margin: 1px"),
+ (
+ "margin: 1px; margin-top: inherit",
+ "margin: 2px",
+ "margin: 1px; margin-top: 2px",
+ ),
+ ],
+)
+def test_css_precedence(style, inherited, equiv):
+ resolve = CSSResolver()
+ inherited_props = resolve(inherited)
+ style_props = resolve(style, inherited=inherited_props)
+ equiv_props = resolve(equiv)
+ assert style_props == equiv_props
+
+
+@pytest.mark.parametrize(
+ "style,equiv",
+ [
+ (
+ "margin: 1px; margin-top: inherit",
+ "margin-bottom: 1px; margin-right: 1px; margin-left: 1px",
+ ),
+ ("margin-top: inherit", ""),
+ ("margin-top: initial", ""),
+ ],
+)
+def test_css_none_absent(style, equiv):
+ assert_same_resolution(style, equiv)
+
+
+@pytest.mark.parametrize(
+ "size,resolved",
+ [
+ ("xx-small", "6pt"),
+ ("x-small", f"{7.5:f}pt"),
+ ("small", f"{9.6:f}pt"),
+ ("medium", "12pt"),
+ ("large", f"{13.5:f}pt"),
+ ("x-large", "18pt"),
+ ("xx-large", "24pt"),
+ ("8px", "6pt"),
+ ("1.25pc", "15pt"),
+ (".25in", "18pt"),
+ ("02.54cm", "72pt"),
+ ("25.4mm", "72pt"),
+ ("101.6q", "72pt"),
+ ],
+)
+@pytest.mark.parametrize("relative_to", [None, "16pt"]) # invariant to inherited size
+def test_css_absolute_font_size(size, relative_to, resolved):
+ if relative_to is None:
+ inherited = None
+ else:
+ inherited = {"font-size": relative_to}
+ assert_resolves(f"font-size: {size}", {"font-size": resolved}, inherited=inherited)
+
+
+@pytest.mark.parametrize(
+ "size,relative_to,resolved",
+ [
+ ("1em", None, "12pt"),
+ ("1.0em", None, "12pt"),
+ ("1.25em", None, "15pt"),
+ ("1em", "16pt", "16pt"),
+ ("1.0em", "16pt", "16pt"),
+ ("1.25em", "16pt", "20pt"),
+ ("1rem", "16pt", "12pt"),
+ ("1.0rem", "16pt", "12pt"),
+ ("1.25rem", "16pt", "15pt"),
+ ("100%", None, "12pt"),
+ ("125%", None, "15pt"),
+ ("100%", "16pt", "16pt"),
+ ("125%", "16pt", "20pt"),
+ ("2ex", None, "12pt"),
+ ("2.0ex", None, "12pt"),
+ ("2.50ex", None, "15pt"),
+ ("inherit", "16pt", "16pt"),
+ ("smaller", None, "10pt"),
+ ("smaller", "18pt", "15pt"),
+ ("larger", None, f"{14.4:f}pt"),
+ ("larger", "15pt", "18pt"),
+ ],
+)
+def test_css_relative_font_size(size, relative_to, resolved):
+ if relative_to is None:
+ inherited = None
+ else:
+ inherited = {"font-size": relative_to}
+ assert_resolves(f"font-size: {size}", {"font-size": resolved}, inherited=inherited)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_eng_formatting.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_eng_formatting.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d581b5b92e0c8cbcfe21dbbdfb0f99ca05c1a4e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_eng_formatting.py
@@ -0,0 +1,254 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ reset_option,
+ set_eng_float_format,
+)
+
+from pandas.io.formats.format import EngFormatter
+
+
+@pytest.fixture(autouse=True)
+def reset_float_format():
+ yield
+ reset_option("display.float_format")
+
+
+class TestEngFormatter:
+ def test_eng_float_formatter2(self, float_frame):
+ df = float_frame
+ df.loc[5] = 0
+
+ set_eng_float_format()
+ repr(df)
+
+ set_eng_float_format(use_eng_prefix=True)
+ repr(df)
+
+ set_eng_float_format(accuracy=0)
+ repr(df)
+
+ def test_eng_float_formatter(self):
+ df = DataFrame({"A": [1.41, 141.0, 14100, 1410000.0]})
+
+ set_eng_float_format()
+ result = df.to_string()
+ expected = (
+ " A\n"
+ "0 1.410E+00\n"
+ "1 141.000E+00\n"
+ "2 14.100E+03\n"
+ "3 1.410E+06"
+ )
+ assert result == expected
+
+ set_eng_float_format(use_eng_prefix=True)
+ result = df.to_string()
+ expected = " A\n0 1.410\n1 141.000\n2 14.100k\n3 1.410M"
+ assert result == expected
+
+ set_eng_float_format(accuracy=0)
+ result = df.to_string()
+ expected = " A\n0 1E+00\n1 141E+00\n2 14E+03\n3 1E+06"
+ assert result == expected
+
+ def compare(self, formatter, input, output):
+ formatted_input = formatter(input)
+ assert formatted_input == output
+
+ def compare_all(self, formatter, in_out):
+ """
+ Parameters:
+ -----------
+ formatter: EngFormatter under test
+ in_out: list of tuples. Each tuple = (number, expected_formatting)
+
+ It is tested if 'formatter(number) == expected_formatting'.
+ *number* should be >= 0 because formatter(-number) == fmt is also
+ tested. *fmt* is derived from *expected_formatting*
+ """
+ for input, output in in_out:
+ self.compare(formatter, input, output)
+ self.compare(formatter, -input, "-" + output[1:])
+
+ def test_exponents_with_eng_prefix(self):
+ formatter = EngFormatter(accuracy=3, use_eng_prefix=True)
+ f = np.sqrt(2)
+ in_out = [
+ (f * 10**-24, " 1.414y"),
+ (f * 10**-23, " 14.142y"),
+ (f * 10**-22, " 141.421y"),
+ (f * 10**-21, " 1.414z"),
+ (f * 10**-20, " 14.142z"),
+ (f * 10**-19, " 141.421z"),
+ (f * 10**-18, " 1.414a"),
+ (f * 10**-17, " 14.142a"),
+ (f * 10**-16, " 141.421a"),
+ (f * 10**-15, " 1.414f"),
+ (f * 10**-14, " 14.142f"),
+ (f * 10**-13, " 141.421f"),
+ (f * 10**-12, " 1.414p"),
+ (f * 10**-11, " 14.142p"),
+ (f * 10**-10, " 141.421p"),
+ (f * 10**-9, " 1.414n"),
+ (f * 10**-8, " 14.142n"),
+ (f * 10**-7, " 141.421n"),
+ (f * 10**-6, " 1.414u"),
+ (f * 10**-5, " 14.142u"),
+ (f * 10**-4, " 141.421u"),
+ (f * 10**-3, " 1.414m"),
+ (f * 10**-2, " 14.142m"),
+ (f * 10**-1, " 141.421m"),
+ (f * 10**0, " 1.414"),
+ (f * 10**1, " 14.142"),
+ (f * 10**2, " 141.421"),
+ (f * 10**3, " 1.414k"),
+ (f * 10**4, " 14.142k"),
+ (f * 10**5, " 141.421k"),
+ (f * 10**6, " 1.414M"),
+ (f * 10**7, " 14.142M"),
+ (f * 10**8, " 141.421M"),
+ (f * 10**9, " 1.414G"),
+ (f * 10**10, " 14.142G"),
+ (f * 10**11, " 141.421G"),
+ (f * 10**12, " 1.414T"),
+ (f * 10**13, " 14.142T"),
+ (f * 10**14, " 141.421T"),
+ (f * 10**15, " 1.414P"),
+ (f * 10**16, " 14.142P"),
+ (f * 10**17, " 141.421P"),
+ (f * 10**18, " 1.414E"),
+ (f * 10**19, " 14.142E"),
+ (f * 10**20, " 141.421E"),
+ (f * 10**21, " 1.414Z"),
+ (f * 10**22, " 14.142Z"),
+ (f * 10**23, " 141.421Z"),
+ (f * 10**24, " 1.414Y"),
+ (f * 10**25, " 14.142Y"),
+ (f * 10**26, " 141.421Y"),
+ ]
+ self.compare_all(formatter, in_out)
+
+ def test_exponents_without_eng_prefix(self):
+ formatter = EngFormatter(accuracy=4, use_eng_prefix=False)
+ f = np.pi
+ in_out = [
+ (f * 10**-24, " 3.1416E-24"),
+ (f * 10**-23, " 31.4159E-24"),
+ (f * 10**-22, " 314.1593E-24"),
+ (f * 10**-21, " 3.1416E-21"),
+ (f * 10**-20, " 31.4159E-21"),
+ (f * 10**-19, " 314.1593E-21"),
+ (f * 10**-18, " 3.1416E-18"),
+ (f * 10**-17, " 31.4159E-18"),
+ (f * 10**-16, " 314.1593E-18"),
+ (f * 10**-15, " 3.1416E-15"),
+ (f * 10**-14, " 31.4159E-15"),
+ (f * 10**-13, " 314.1593E-15"),
+ (f * 10**-12, " 3.1416E-12"),
+ (f * 10**-11, " 31.4159E-12"),
+ (f * 10**-10, " 314.1593E-12"),
+ (f * 10**-9, " 3.1416E-09"),
+ (f * 10**-8, " 31.4159E-09"),
+ (f * 10**-7, " 314.1593E-09"),
+ (f * 10**-6, " 3.1416E-06"),
+ (f * 10**-5, " 31.4159E-06"),
+ (f * 10**-4, " 314.1593E-06"),
+ (f * 10**-3, " 3.1416E-03"),
+ (f * 10**-2, " 31.4159E-03"),
+ (f * 10**-1, " 314.1593E-03"),
+ (f * 10**0, " 3.1416E+00"),
+ (f * 10**1, " 31.4159E+00"),
+ (f * 10**2, " 314.1593E+00"),
+ (f * 10**3, " 3.1416E+03"),
+ (f * 10**4, " 31.4159E+03"),
+ (f * 10**5, " 314.1593E+03"),
+ (f * 10**6, " 3.1416E+06"),
+ (f * 10**7, " 31.4159E+06"),
+ (f * 10**8, " 314.1593E+06"),
+ (f * 10**9, " 3.1416E+09"),
+ (f * 10**10, " 31.4159E+09"),
+ (f * 10**11, " 314.1593E+09"),
+ (f * 10**12, " 3.1416E+12"),
+ (f * 10**13, " 31.4159E+12"),
+ (f * 10**14, " 314.1593E+12"),
+ (f * 10**15, " 3.1416E+15"),
+ (f * 10**16, " 31.4159E+15"),
+ (f * 10**17, " 314.1593E+15"),
+ (f * 10**18, " 3.1416E+18"),
+ (f * 10**19, " 31.4159E+18"),
+ (f * 10**20, " 314.1593E+18"),
+ (f * 10**21, " 3.1416E+21"),
+ (f * 10**22, " 31.4159E+21"),
+ (f * 10**23, " 314.1593E+21"),
+ (f * 10**24, " 3.1416E+24"),
+ (f * 10**25, " 31.4159E+24"),
+ (f * 10**26, " 314.1593E+24"),
+ ]
+ self.compare_all(formatter, in_out)
+
+ def test_rounding(self):
+ formatter = EngFormatter(accuracy=3, use_eng_prefix=True)
+ in_out = [
+ (5.55555, " 5.556"),
+ (55.5555, " 55.556"),
+ (555.555, " 555.555"),
+ (5555.55, " 5.556k"),
+ (55555.5, " 55.556k"),
+ (555555, " 555.555k"),
+ ]
+ self.compare_all(formatter, in_out)
+
+ formatter = EngFormatter(accuracy=1, use_eng_prefix=True)
+ in_out = [
+ (5.55555, " 5.6"),
+ (55.5555, " 55.6"),
+ (555.555, " 555.6"),
+ (5555.55, " 5.6k"),
+ (55555.5, " 55.6k"),
+ (555555, " 555.6k"),
+ ]
+ self.compare_all(formatter, in_out)
+
+ formatter = EngFormatter(accuracy=0, use_eng_prefix=True)
+ in_out = [
+ (5.55555, " 6"),
+ (55.5555, " 56"),
+ (555.555, " 556"),
+ (5555.55, " 6k"),
+ (55555.5, " 56k"),
+ (555555, " 556k"),
+ ]
+ self.compare_all(formatter, in_out)
+
+ formatter = EngFormatter(accuracy=3, use_eng_prefix=True)
+ result = formatter(0)
+ assert result == " 0.000"
+
+ def test_nan(self):
+ # Issue #11981
+
+ formatter = EngFormatter(accuracy=1, use_eng_prefix=True)
+ result = formatter(np.nan)
+ assert result == "NaN"
+
+ df = DataFrame(
+ {
+ "a": [1.5, 10.3, 20.5],
+ "b": [50.3, 60.67, 70.12],
+ "c": [100.2, 101.33, 120.33],
+ }
+ )
+ pt = df.pivot_table(values="a", index="b", columns="c")
+ set_eng_float_format(accuracy=1)
+ result = pt.to_string()
+ assert "NaN" in result
+
+ def test_inf(self):
+ # Issue #11981
+
+ formatter = EngFormatter(accuracy=1, use_eng_prefix=True)
+ result = formatter(np.inf)
+ assert result == "inf"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_format.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_format.py
new file mode 100644
index 0000000000000000000000000000000000000000..9602aa723e4ed75fe3ac7f2a4f00b8dbfa46ae53
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_format.py
@@ -0,0 +1,2302 @@
+"""
+Tests for the file pandas.io.formats.format, *not* tests for general formatting
+of pandas objects.
+"""
+
+from datetime import datetime
+from io import StringIO
+import re
+from shutil import get_terminal_size
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ NaT,
+ Series,
+ Timestamp,
+ date_range,
+ get_option,
+ option_context,
+ read_csv,
+ reset_option,
+)
+
+from pandas.io.formats import printing
+import pandas.io.formats.format as fmt
+
+
+def has_info_repr(df):
+ r = repr(df)
+ c1 = r.split("\n")[0].startswith("
+ # 2. Index
+ # 3. Columns
+ # 4. dtype
+ # 5. memory usage
+ # 6. trailing newline
+ nv = len(r.split("\n")) == 6
+ return has_info and nv
+
+
+def has_horizontally_truncated_repr(df):
+ try: # Check header row
+ fst_line = np.array(repr(df).splitlines()[0].split())
+ cand_col = np.where(fst_line == "...")[0][0]
+ except IndexError:
+ return False
+ # Make sure each row has this ... in the same place
+ r = repr(df)
+ for ix, _ in enumerate(r.splitlines()):
+ if not r.split()[cand_col] == "...":
+ return False
+ return True
+
+
+def has_vertically_truncated_repr(df):
+ r = repr(df)
+ only_dot_row = False
+ for row in r.splitlines():
+ if re.match(r"^[\.\ ]+$", row):
+ only_dot_row = True
+ return only_dot_row
+
+
+def has_truncated_repr(df):
+ return has_horizontally_truncated_repr(df) or has_vertically_truncated_repr(df)
+
+
+def has_doubly_truncated_repr(df):
+ return has_horizontally_truncated_repr(df) and has_vertically_truncated_repr(df)
+
+
+def has_expanded_repr(df):
+ r = repr(df)
+ for line in r.split("\n"):
+ if line.endswith("\\"):
+ return True
+ return False
+
+
+class TestDataFrameFormatting:
+ def test_repr_truncation(self):
+ max_len = 20
+ with option_context("display.max_colwidth", max_len):
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).standard_normal(10),
+ "B": [
+ "a"
+ * np.random.default_rng(2).integers(max_len - 1, max_len + 1)
+ for _ in range(10)
+ ],
+ }
+ )
+ r = repr(df)
+ r = r[r.find("\n") + 1 :]
+
+ adj = printing.get_adjustment()
+
+ for line, value in zip(r.split("\n"), df["B"]):
+ if adj.len(value) + 1 > max_len:
+ assert "..." in line
+ else:
+ assert "..." not in line
+
+ with option_context("display.max_colwidth", 999999):
+ assert "..." not in repr(df)
+
+ with option_context("display.max_colwidth", max_len + 2):
+ assert "..." not in repr(df)
+
+ def test_repr_truncation_preserves_na(self):
+ # https://github.com/pandas-dev/pandas/issues/55630
+ df = DataFrame({"a": [pd.NA for _ in range(10)]})
+ with option_context("display.max_rows", 2, "display.show_dimensions", False):
+ assert repr(df) == " a\n0 \n.. ...\n9 "
+
+ def test_repr_truncation_dataframe_attrs(self):
+ # GH#60455
+ df = DataFrame([[0] * 10])
+ df.attrs["b"] = DataFrame([])
+ with option_context("display.max_columns", 2, "display.show_dimensions", False):
+ assert repr(df) == " 0 ... 9\n0 0 ... 0"
+
+ def test_repr_truncation_series_with_dataframe_attrs(self):
+ # GH#60568
+ ser = Series([0] * 10)
+ ser.attrs["b"] = DataFrame([])
+ with option_context("display.max_rows", 2, "display.show_dimensions", False):
+ assert repr(ser) == "0 0\n ..\n9 0\ndtype: int64"
+
+ def test_max_colwidth_negative_int_raises(self):
+ # Deprecation enforced from:
+ # https://github.com/pandas-dev/pandas/issues/31532
+ with pytest.raises(
+ ValueError, match="Value must be a nonnegative integer or None"
+ ):
+ with option_context("display.max_colwidth", -1):
+ pass
+
+ def test_repr_chop_threshold(self):
+ df = DataFrame([[0.1, 0.5], [0.5, -0.1]])
+ reset_option("display.chop_threshold") # default None
+ assert repr(df) == " 0 1\n0 0.1 0.5\n1 0.5 -0.1"
+
+ with option_context("display.chop_threshold", 0.2):
+ assert repr(df) == " 0 1\n0 0.0 0.5\n1 0.5 0.0"
+
+ with option_context("display.chop_threshold", 0.6):
+ assert repr(df) == " 0 1\n0 0.0 0.0\n1 0.0 0.0"
+
+ with option_context("display.chop_threshold", None):
+ assert repr(df) == " 0 1\n0 0.1 0.5\n1 0.5 -0.1"
+
+ def test_repr_chop_threshold_column_below(self):
+ # GH 6839: validation case
+
+ df = DataFrame([[10, 20, 30, 40], [8e-10, -1e-11, 2e-9, -2e-11]]).T
+
+ with option_context("display.chop_threshold", 0):
+ assert repr(df) == (
+ " 0 1\n"
+ "0 10.0 8.000000e-10\n"
+ "1 20.0 -1.000000e-11\n"
+ "2 30.0 2.000000e-09\n"
+ "3 40.0 -2.000000e-11"
+ )
+
+ with option_context("display.chop_threshold", 1e-8):
+ assert repr(df) == (
+ " 0 1\n"
+ "0 10.0 0.000000e+00\n"
+ "1 20.0 0.000000e+00\n"
+ "2 30.0 0.000000e+00\n"
+ "3 40.0 0.000000e+00"
+ )
+
+ with option_context("display.chop_threshold", 5e-11):
+ assert repr(df) == (
+ " 0 1\n"
+ "0 10.0 8.000000e-10\n"
+ "1 20.0 0.000000e+00\n"
+ "2 30.0 2.000000e-09\n"
+ "3 40.0 0.000000e+00"
+ )
+
+ def test_repr_no_backslash(self):
+ with option_context("mode.sim_interactive", True):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ assert "\\" not in repr(df)
+
+ def test_expand_frame_repr(self):
+ df_small = DataFrame("hello", index=[0], columns=[0])
+ df_wide = DataFrame("hello", index=[0], columns=range(10))
+ df_tall = DataFrame("hello", index=range(30), columns=range(5))
+
+ with option_context("mode.sim_interactive", True):
+ with option_context(
+ "display.max_columns",
+ 10,
+ "display.width",
+ 20,
+ "display.max_rows",
+ 20,
+ "display.show_dimensions",
+ True,
+ ):
+ with option_context("display.expand_frame_repr", True):
+ assert not has_truncated_repr(df_small)
+ assert not has_expanded_repr(df_small)
+ assert not has_truncated_repr(df_wide)
+ assert has_expanded_repr(df_wide)
+ assert has_vertically_truncated_repr(df_tall)
+ assert has_expanded_repr(df_tall)
+
+ with option_context("display.expand_frame_repr", False):
+ assert not has_truncated_repr(df_small)
+ assert not has_expanded_repr(df_small)
+ assert not has_horizontally_truncated_repr(df_wide)
+ assert not has_expanded_repr(df_wide)
+ assert has_vertically_truncated_repr(df_tall)
+ assert not has_expanded_repr(df_tall)
+
+ def test_repr_non_interactive(self):
+ # in non interactive mode, there can be no dependency on the
+ # result of terminal auto size detection
+ df = DataFrame("hello", index=range(1000), columns=range(5))
+
+ with option_context(
+ "mode.sim_interactive", False, "display.width", 0, "display.max_rows", 5000
+ ):
+ assert not has_truncated_repr(df)
+ assert not has_expanded_repr(df)
+
+ def test_repr_truncates_terminal_size(self, monkeypatch):
+ # see gh-21180
+
+ terminal_size = (118, 96)
+ monkeypatch.setattr(
+ "pandas.io.formats.format.get_terminal_size", lambda: terminal_size
+ )
+
+ index = range(5)
+ columns = MultiIndex.from_tuples(
+ [
+ ("This is a long title with > 37 chars.", "cat"),
+ ("This is a loooooonger title with > 43 chars.", "dog"),
+ ]
+ )
+ df = DataFrame(1, index=index, columns=columns)
+
+ result = repr(df)
+
+ h1, h2 = result.split("\n")[:2]
+ assert "long" in h1
+ assert "loooooonger" in h1
+ assert "cat" in h2
+ assert "dog" in h2
+
+ # regular columns
+ df2 = DataFrame({"A" * 41: [1, 2], "B" * 41: [1, 2]})
+ result = repr(df2)
+
+ assert df2.columns[0] in result.split("\n")[0]
+
+ def test_repr_truncates_terminal_size_full(self, monkeypatch):
+ # GH 22984 ensure entire window is filled
+ terminal_size = (80, 24)
+ df = DataFrame(np.random.default_rng(2).random((1, 7)))
+
+ monkeypatch.setattr(
+ "pandas.io.formats.format.get_terminal_size", lambda: terminal_size
+ )
+ assert "..." not in str(df)
+
+ def test_repr_truncation_column_size(self):
+ # dataframe with last column very wide -> check it is not used to
+ # determine size of truncation (...) column
+ df = DataFrame(
+ {
+ "a": [108480, 30830],
+ "b": [12345, 12345],
+ "c": [12345, 12345],
+ "d": [12345, 12345],
+ "e": ["a" * 50] * 2,
+ }
+ )
+ assert "..." in str(df)
+ assert " ... " not in str(df)
+
+ def test_repr_max_columns_max_rows(self):
+ term_width, term_height = get_terminal_size()
+ if term_width < 10 or term_height < 10:
+ pytest.skip(f"terminal size too small, {term_width} x {term_height}")
+
+ def mkframe(n):
+ index = [f"{i:05d}" for i in range(n)]
+ return DataFrame(0, index, index)
+
+ df6 = mkframe(6)
+ df10 = mkframe(10)
+ with option_context("mode.sim_interactive", True):
+ with option_context("display.width", term_width * 2):
+ with option_context("display.max_rows", 5, "display.max_columns", 5):
+ assert not has_expanded_repr(mkframe(4))
+ assert not has_expanded_repr(mkframe(5))
+ assert not has_expanded_repr(df6)
+ assert has_doubly_truncated_repr(df6)
+
+ with option_context("display.max_rows", 20, "display.max_columns", 10):
+ # Out off max_columns boundary, but no extending
+ # since not exceeding width
+ assert not has_expanded_repr(df6)
+ assert not has_truncated_repr(df6)
+
+ with option_context("display.max_rows", 9, "display.max_columns", 10):
+ # out vertical bounds can not result in expanded repr
+ assert not has_expanded_repr(df10)
+ assert has_vertically_truncated_repr(df10)
+
+ # width=None in terminal, auto detection
+ with option_context(
+ "display.max_columns",
+ 100,
+ "display.max_rows",
+ term_width * 20,
+ "display.width",
+ None,
+ ):
+ df = mkframe((term_width // 7) - 2)
+ assert not has_expanded_repr(df)
+ df = mkframe((term_width // 7) + 2)
+ printing.pprint_thing(df._repr_fits_horizontal_())
+ assert has_expanded_repr(df)
+
+ def test_repr_min_rows(self):
+ df = DataFrame({"a": range(20)})
+
+ # default setting no truncation even if above min_rows
+ assert ".." not in repr(df)
+ assert ".." not in df._repr_html_()
+
+ df = DataFrame({"a": range(61)})
+
+ # default of max_rows 60 triggers truncation if above
+ assert ".." in repr(df)
+ assert ".." in df._repr_html_()
+
+ with option_context("display.max_rows", 10, "display.min_rows", 4):
+ # truncated after first two rows
+ assert ".." in repr(df)
+ assert "2 " not in repr(df)
+ assert "..." in df._repr_html_()
+ assert "2 " not in df._repr_html_()
+
+ with option_context("display.max_rows", 12, "display.min_rows", None):
+ # when set to None, follow value of max_rows
+ assert "5 5" in repr(df)
+ assert "5 " in df._repr_html_()
+
+ with option_context("display.max_rows", 10, "display.min_rows", 12):
+ # when set value higher as max_rows, use the minimum
+ assert "5 5" not in repr(df)
+ assert "5 " not in df._repr_html_()
+
+ with option_context("display.max_rows", None, "display.min_rows", 12):
+ # max_rows of None -> never truncate
+ assert ".." not in repr(df)
+ assert ".." not in df._repr_html_()
+
+ @pytest.mark.parametrize(
+ "data, format_option, expected_values",
+ [
+ (12345.6789, "{:12.3f}", "12345.679"),
+ (None, "{:.3f}", "None"),
+ ("", "{:.2f}", ""),
+ (112345.6789, "{:6.3f}", "112345.679"),
+ ("foo foo", None, "foo foo"),
+ (" foo", None, "foo"),
+ (
+ "foo foo foo",
+ None,
+ "foo foo foo",
+ ), # odd no.of spaces
+ (
+ "foo foo foo",
+ None,
+ "foo foo foo",
+ ), # even no.of spaces
+ ],
+ )
+ def test_repr_float_formatting_html_output(
+ self, data, format_option, expected_values
+ ):
+ if format_option is not None:
+ with option_context("display.float_format", format_option.format):
+ df = DataFrame({"A": [data]})
+ html_output = df._repr_html_()
+ assert expected_values in html_output
+ else:
+ df = DataFrame({"A": [data]})
+ html_output = df._repr_html_()
+ assert expected_values in html_output
+
+ def test_str_max_colwidth(self):
+ # GH 7856
+ df = DataFrame(
+ [
+ {
+ "a": "foo",
+ "b": "bar",
+ "c": "uncomfortably long line with lots of stuff",
+ "d": 1,
+ },
+ {"a": "foo", "b": "bar", "c": "stuff", "d": 1},
+ ]
+ )
+ df.set_index(["a", "b", "c"])
+ assert str(df) == (
+ " a b c d\n"
+ "0 foo bar uncomfortably long line with lots of stuff 1\n"
+ "1 foo bar stuff 1"
+ )
+ with option_context("max_colwidth", 20):
+ assert str(df) == (
+ " a b c d\n"
+ "0 foo bar uncomfortably lo... 1\n"
+ "1 foo bar stuff 1"
+ )
+
+ def test_auto_detect(self):
+ term_width, term_height = get_terminal_size()
+ fac = 1.05 # Arbitrary large factor to exceed term width
+ cols = range(int(term_width * fac))
+ index = range(10)
+ df = DataFrame(index=index, columns=cols)
+ with option_context("mode.sim_interactive", True):
+ with option_context("display.max_rows", None):
+ with option_context("display.max_columns", None):
+ # Wrap around with None
+ assert has_expanded_repr(df)
+ with option_context("display.max_rows", 0):
+ with option_context("display.max_columns", 0):
+ # Truncate with auto detection.
+ assert has_horizontally_truncated_repr(df)
+
+ index = range(int(term_height * fac))
+ df = DataFrame(index=index, columns=cols)
+ with option_context("display.max_rows", 0):
+ with option_context("display.max_columns", None):
+ # Wrap around with None
+ assert has_expanded_repr(df)
+ # Truncate vertically
+ assert has_vertically_truncated_repr(df)
+
+ with option_context("display.max_rows", None):
+ with option_context("display.max_columns", 0):
+ assert has_horizontally_truncated_repr(df)
+
+ def test_to_string_repr_unicode2(self):
+ idx = Index(["abc", "\u03c3a", "aegdvg"])
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ rs = repr(ser).split("\n")
+ line_len = len(rs[0])
+ for line in rs[1:]:
+ try:
+ line = line.decode(get_option("display.encoding"))
+ except AttributeError:
+ pass
+ if not line.startswith("dtype:"):
+ assert len(line) == line_len
+
+ def test_east_asian_unicode_false(self):
+ # not aligned properly because of east asian width
+
+ # mid col
+ df = DataFrame(
+ {"a": ["あ", "いいい", "う", "ええええええ"], "b": [1, 222, 33333, 4]},
+ index=["a", "bb", "c", "ddd"],
+ )
+ expected = (
+ " a b\na あ 1\n"
+ "bb いいい 222\nc う 33333\n"
+ "ddd ええええええ 4"
+ )
+ assert repr(df) == expected
+
+ # last col
+ df = DataFrame(
+ {"a": [1, 222, 33333, 4], "b": ["あ", "いいい", "う", "ええええええ"]},
+ index=["a", "bb", "c", "ddd"],
+ )
+ expected = (
+ " a b\na 1 あ\n"
+ "bb 222 いいい\nc 33333 う\n"
+ "ddd 4 ええええええ"
+ )
+ assert repr(df) == expected
+
+ # all col
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ },
+ index=["a", "bb", "c", "ddd"],
+ )
+ expected = (
+ " a b\na あああああ あ\n"
+ "bb い いいい\nc う う\n"
+ "ddd えええ ええええええ"
+ )
+ assert repr(df) == expected
+
+ # column name
+ df = DataFrame(
+ {
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ "あああああ": [1, 222, 33333, 4],
+ },
+ index=["a", "bb", "c", "ddd"],
+ )
+ expected = (
+ " b あああああ\na あ 1\n"
+ "bb いいい 222\nc う 33333\n"
+ "ddd ええええええ 4"
+ )
+ assert repr(df) == expected
+
+ # index
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ },
+ index=["あああ", "いいいいいい", "うう", "え"],
+ )
+ expected = (
+ " a b\nあああ あああああ あ\n"
+ "いいいいいい い いいい\nうう う う\n"
+ "え えええ ええええええ"
+ )
+ assert repr(df) == expected
+
+ # index name
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ },
+ index=Index(["あ", "い", "うう", "え"], name="おおおお"),
+ )
+ expected = (
+ " a b\n"
+ "おおおお \n"
+ "あ あああああ あ\n"
+ "い い いいい\n"
+ "うう う う\n"
+ "え えええ ええええええ"
+ )
+ assert repr(df) == expected
+
+ # all
+ df = DataFrame(
+ {
+ "あああ": ["あああ", "い", "う", "えええええ"],
+ "いいいいい": ["あ", "いいい", "う", "ええ"],
+ },
+ index=Index(["あ", "いいい", "うう", "え"], name="お"),
+ )
+ expected = (
+ " あああ いいいいい\n"
+ "お \n"
+ "あ あああ あ\n"
+ "いいい い いいい\n"
+ "うう う う\n"
+ "え えええええ ええ"
+ )
+ assert repr(df) == expected
+
+ # MultiIndex
+ idx = MultiIndex.from_tuples(
+ [("あ", "いい"), ("う", "え"), ("おおお", "かかかか"), ("き", "くく")]
+ )
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ },
+ index=idx,
+ )
+ expected = (
+ " a b\n"
+ "あ いい あああああ あ\n"
+ "う え い いいい\n"
+ "おおお かかかか う う\n"
+ "き くく えええ ええええええ"
+ )
+ assert repr(df) == expected
+
+ # truncate
+ with option_context("display.max_rows", 3, "display.max_columns", 3):
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ "c": ["お", "か", "ききき", "くくくくくく"],
+ "ああああ": ["さ", "し", "す", "せ"],
+ },
+ columns=["a", "b", "c", "ああああ"],
+ )
+
+ expected = (
+ " a ... ああああ\n0 あああああ ... さ\n"
+ ".. ... ... ...\n3 えええ ... せ\n"
+ "\n[4 rows x 4 columns]"
+ )
+ assert repr(df) == expected
+
+ df.index = ["あああ", "いいいい", "う", "aaa"]
+ expected = (
+ " a ... ああああ\nあああ あああああ ... さ\n"
+ ".. ... ... ...\naaa えええ ... せ\n"
+ "\n[4 rows x 4 columns]"
+ )
+ assert repr(df) == expected
+
+ def test_east_asian_unicode_true(self):
+ # Enable Unicode option -----------------------------------------
+ with option_context("display.unicode.east_asian_width", True):
+ # mid col
+ df = DataFrame(
+ {"a": ["あ", "いいい", "う", "ええええええ"], "b": [1, 222, 33333, 4]},
+ index=["a", "bb", "c", "ddd"],
+ )
+ expected = (
+ " a b\na あ 1\n"
+ "bb いいい 222\nc う 33333\n"
+ "ddd ええええええ 4"
+ )
+ assert repr(df) == expected
+
+ # last col
+ df = DataFrame(
+ {"a": [1, 222, 33333, 4], "b": ["あ", "いいい", "う", "ええええええ"]},
+ index=["a", "bb", "c", "ddd"],
+ )
+ expected = (
+ " a b\na 1 あ\n"
+ "bb 222 いいい\nc 33333 う\n"
+ "ddd 4 ええええええ"
+ )
+ assert repr(df) == expected
+
+ # all col
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ },
+ index=["a", "bb", "c", "ddd"],
+ )
+ expected = (
+ " a b\n"
+ "a あああああ あ\n"
+ "bb い いいい\n"
+ "c う う\n"
+ "ddd えええ ええええええ"
+ )
+ assert repr(df) == expected
+
+ # column name
+ df = DataFrame(
+ {
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ "あああああ": [1, 222, 33333, 4],
+ },
+ index=["a", "bb", "c", "ddd"],
+ )
+ expected = (
+ " b あああああ\n"
+ "a あ 1\n"
+ "bb いいい 222\n"
+ "c う 33333\n"
+ "ddd ええええええ 4"
+ )
+ assert repr(df) == expected
+
+ # index
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ },
+ index=["あああ", "いいいいいい", "うう", "え"],
+ )
+ expected = (
+ " a b\n"
+ "あああ あああああ あ\n"
+ "いいいいいい い いいい\n"
+ "うう う う\n"
+ "え えええ ええええええ"
+ )
+ assert repr(df) == expected
+
+ # index name
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ },
+ index=Index(["あ", "い", "うう", "え"], name="おおおお"),
+ )
+ expected = (
+ " a b\n"
+ "おおおお \n"
+ "あ あああああ あ\n"
+ "い い いいい\n"
+ "うう う う\n"
+ "え えええ ええええええ"
+ )
+ assert repr(df) == expected
+
+ # all
+ df = DataFrame(
+ {
+ "あああ": ["あああ", "い", "う", "えええええ"],
+ "いいいいい": ["あ", "いいい", "う", "ええ"],
+ },
+ index=Index(["あ", "いいい", "うう", "え"], name="お"),
+ )
+ expected = (
+ " あああ いいいいい\n"
+ "お \n"
+ "あ あああ あ\n"
+ "いいい い いいい\n"
+ "うう う う\n"
+ "え えええええ ええ"
+ )
+ assert repr(df) == expected
+
+ # MultiIndex
+ idx = MultiIndex.from_tuples(
+ [("あ", "いい"), ("う", "え"), ("おおお", "かかかか"), ("き", "くく")]
+ )
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ },
+ index=idx,
+ )
+ expected = (
+ " a b\n"
+ "あ いい あああああ あ\n"
+ "う え い いいい\n"
+ "おおお かかかか う う\n"
+ "き くく えええ ええええええ"
+ )
+ assert repr(df) == expected
+
+ # truncate
+ with option_context("display.max_rows", 3, "display.max_columns", 3):
+ df = DataFrame(
+ {
+ "a": ["あああああ", "い", "う", "えええ"],
+ "b": ["あ", "いいい", "う", "ええええええ"],
+ "c": ["お", "か", "ききき", "くくくくくく"],
+ "ああああ": ["さ", "し", "す", "せ"],
+ },
+ columns=["a", "b", "c", "ああああ"],
+ )
+
+ expected = (
+ " a ... ああああ\n"
+ "0 あああああ ... さ\n"
+ ".. ... ... ...\n"
+ "3 えええ ... せ\n"
+ "\n[4 rows x 4 columns]"
+ )
+ assert repr(df) == expected
+
+ df.index = ["あああ", "いいいい", "う", "aaa"]
+ expected = (
+ " a ... ああああ\n"
+ "あああ あああああ ... さ\n"
+ "... ... ... ...\n"
+ "aaa えええ ... せ\n"
+ "\n[4 rows x 4 columns]"
+ )
+ assert repr(df) == expected
+
+ # ambiguous unicode
+ df = DataFrame(
+ {
+ "b": ["あ", "いいい", "¡¡", "ええええええ"],
+ "あああああ": [1, 222, 33333, 4],
+ },
+ index=["a", "bb", "c", "¡¡¡"],
+ )
+ expected = (
+ " b あああああ\n"
+ "a あ 1\n"
+ "bb いいい 222\n"
+ "c ¡¡ 33333\n"
+ "¡¡¡ ええええええ 4"
+ )
+ assert repr(df) == expected
+
+ def test_to_string_buffer_all_unicode(self):
+ buf = StringIO()
+
+ empty = DataFrame({"c/\u03c3": Series(dtype=object)})
+ nonempty = DataFrame({"c/\u03c3": Series([1, 2, 3])})
+
+ print(empty, file=buf)
+ print(nonempty, file=buf)
+
+ # this should work
+ buf.getvalue()
+
+ @pytest.mark.parametrize(
+ "index_scalar",
+ [
+ "a" * 10,
+ 1,
+ Timestamp(2020, 1, 1),
+ pd.Period("2020-01-01"),
+ ],
+ )
+ @pytest.mark.parametrize("h", [10, 20])
+ @pytest.mark.parametrize("w", [10, 20])
+ def test_to_string_truncate_indices(self, index_scalar, h, w):
+ with option_context("display.expand_frame_repr", False):
+ df = DataFrame(
+ index=[index_scalar] * h, columns=[str(i) * 10 for i in range(w)]
+ )
+ with option_context("display.max_rows", 15):
+ if h == 20:
+ assert has_vertically_truncated_repr(df)
+ else:
+ assert not has_vertically_truncated_repr(df)
+ with option_context("display.max_columns", 15):
+ if w == 20:
+ assert has_horizontally_truncated_repr(df)
+ else:
+ assert not has_horizontally_truncated_repr(df)
+ with option_context("display.max_rows", 15, "display.max_columns", 15):
+ if h == 20 and w == 20:
+ assert has_doubly_truncated_repr(df)
+ else:
+ assert not has_doubly_truncated_repr(df)
+
+ def test_to_string_truncate_multilevel(self):
+ arrays = [
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ df = DataFrame(index=arrays, columns=arrays)
+ with option_context("display.max_rows", 7, "display.max_columns", 7):
+ assert has_doubly_truncated_repr(df)
+
+ @pytest.mark.parametrize("dtype", ["object", "datetime64[us]"])
+ def test_truncate_with_different_dtypes(self, dtype):
+ # 11594, 12045
+ # when truncated the dtypes of the splits can differ
+
+ # 11594
+ ser = Series(
+ [datetime(2012, 1, 1)] * 10
+ + [datetime(1012, 1, 2)]
+ + [datetime(2012, 1, 3)] * 10,
+ dtype=dtype,
+ )
+
+ with option_context("display.max_rows", 8):
+ result = str(ser)
+ assert dtype in result
+
+ def test_truncate_with_different_dtypes2(self):
+ # 12045
+ df = DataFrame({"text": ["some words"] + [None] * 9}, dtype=object)
+
+ with option_context("display.max_rows", 8, "display.max_columns", 3):
+ result = str(df)
+ assert "None" in result
+ assert "NaN" not in result
+
+ def test_truncate_with_different_dtypes_multiindex(self):
+ # GH#13000
+ df = DataFrame({"Vals": range(100)})
+ frame = pd.concat([df], keys=["Sweep"], names=["Sweep", "Index"])
+ result = repr(frame)
+
+ result2 = repr(frame.iloc[:5])
+ assert result.startswith(result2)
+
+ def test_datetimelike_frame(self):
+ # GH 12211
+ df = DataFrame({"date": [Timestamp("20130101").tz_localize("UTC")] + [NaT] * 5})
+
+ with option_context("display.max_rows", 5):
+ result = str(df)
+ assert "2013-01-01 00:00:00+00:00" in result
+ assert "NaT" in result
+ assert "..." in result
+ assert "[6 rows x 1 columns]" in result
+
+ dts = [Timestamp("2011-01-01", tz="US/Eastern")] * 5 + [NaT] * 5
+ df = DataFrame({"dt": dts, "x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})
+ with option_context("display.max_rows", 5):
+ expected = (
+ " dt x\n"
+ "0 2011-01-01 00:00:00-05:00 1\n"
+ "1 2011-01-01 00:00:00-05:00 2\n"
+ ".. ... ..\n"
+ "8 NaT 9\n"
+ "9 NaT 10\n\n"
+ "[10 rows x 2 columns]"
+ )
+ assert repr(df) == expected
+
+ dts = [NaT] * 5 + [Timestamp("2011-01-01", tz="US/Eastern")] * 5
+ df = DataFrame({"dt": dts, "x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})
+ with option_context("display.max_rows", 5):
+ expected = (
+ " dt x\n"
+ "0 NaT 1\n"
+ "1 NaT 2\n"
+ ".. ... ..\n"
+ "8 2011-01-01 00:00:00-05:00 9\n"
+ "9 2011-01-01 00:00:00-05:00 10\n\n"
+ "[10 rows x 2 columns]"
+ )
+ assert repr(df) == expected
+
+ dts = [Timestamp("2011-01-01", tz="Asia/Tokyo")] * 5 + [
+ Timestamp("2011-01-01", tz="US/Eastern")
+ ] * 5
+ df = DataFrame({"dt": dts, "x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})
+ with option_context("display.max_rows", 5):
+ expected = (
+ " dt x\n"
+ "0 2011-01-01 00:00:00+09:00 1\n"
+ "1 2011-01-01 00:00:00+09:00 2\n"
+ ".. ... ..\n"
+ "8 2011-01-01 00:00:00-05:00 9\n"
+ "9 2011-01-01 00:00:00-05:00 10\n\n"
+ "[10 rows x 2 columns]"
+ )
+ assert repr(df) == expected
+
+ @pytest.mark.parametrize(
+ "start_date",
+ [
+ "2017-01-01 23:59:59.999999999",
+ "2017-01-01 23:59:59.99999999",
+ "2017-01-01 23:59:59.9999999",
+ "2017-01-01 23:59:59.999999",
+ "2017-01-01 23:59:59.99999",
+ "2017-01-01 23:59:59.9999",
+ ],
+ )
+ def test_datetimeindex_highprecision(self, start_date):
+ # GH19030
+ # Check that high-precision time values for the end of day are
+ # included in repr for DatetimeIndex
+ df = DataFrame({"A": date_range(start=start_date, freq="D", periods=5)})
+ result = str(df)
+ assert start_date in result
+
+ dti = date_range(start=start_date, freq="D", periods=5)
+ df = DataFrame({"A": range(5)}, index=dti)
+ result = str(df.index)
+ assert start_date in result
+
+ def test_string_repr_encoding(self, datapath):
+ filepath = datapath("io", "parser", "data", "unicode_series.csv")
+ df = read_csv(filepath, header=None, encoding="latin1")
+ repr(df)
+ repr(df[1])
+
+ def test_repr_corner(self):
+ # representing infs poses no problems
+ df = DataFrame({"foo": [-np.inf, np.inf]})
+ repr(df)
+
+ def test_frame_info_encoding(self):
+ index = ["'Til There Was You (1997)", "ldum klaka (Cold Fever) (1994)"]
+ with option_context("display.max_rows", 1):
+ df = DataFrame(columns=["a", "b", "c"], index=index)
+ repr(df)
+ repr(df.T)
+
+ def test_wide_repr(self):
+ with option_context(
+ "mode.sim_interactive",
+ True,
+ "display.show_dimensions",
+ True,
+ "display.max_columns",
+ 20,
+ ):
+ max_cols = get_option("display.max_columns")
+ df = DataFrame([["a" * 25] * (max_cols - 1)] * 10)
+ with option_context("display.expand_frame_repr", False):
+ rep_str = repr(df)
+
+ assert f"10 rows x {max_cols - 1} columns" in rep_str
+ with option_context("display.expand_frame_repr", True):
+ wide_repr = repr(df)
+ assert rep_str != wide_repr
+
+ with option_context("display.width", 120):
+ wider_repr = repr(df)
+ assert len(wider_repr) < len(wide_repr)
+
+ def test_wide_repr_wide_columns(self):
+ with option_context("mode.sim_interactive", True, "display.max_columns", 20):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 3)),
+ columns=["a" * 90, "b" * 90, "c" * 90],
+ )
+ rep_str = repr(df)
+
+ assert len(rep_str.splitlines()) == 20
+
+ def test_wide_repr_named(self):
+ with option_context("mode.sim_interactive", True, "display.max_columns", 20):
+ max_cols = get_option("display.max_columns")
+ df = DataFrame([["a" * 25] * (max_cols - 1)] * 10)
+ df.index.name = "DataFrame Index"
+ with option_context("display.expand_frame_repr", False):
+ rep_str = repr(df)
+ with option_context("display.expand_frame_repr", True):
+ wide_repr = repr(df)
+ assert rep_str != wide_repr
+
+ with option_context("display.width", 150):
+ wider_repr = repr(df)
+ assert len(wider_repr) < len(wide_repr)
+
+ for line in wide_repr.splitlines()[1::13]:
+ assert "DataFrame Index" in line
+
+ def test_wide_repr_multiindex(self):
+ with option_context("mode.sim_interactive", True, "display.max_columns", 20):
+ midx = MultiIndex.from_arrays([["a" * 5] * 10] * 2)
+ max_cols = get_option("display.max_columns")
+ df = DataFrame([["a" * 25] * (max_cols - 1)] * 10, index=midx)
+ df.index.names = ["Level 0", "Level 1"]
+ with option_context("display.expand_frame_repr", False):
+ rep_str = repr(df)
+ with option_context("display.expand_frame_repr", True):
+ wide_repr = repr(df)
+ assert rep_str != wide_repr
+
+ with option_context("display.width", 150):
+ wider_repr = repr(df)
+ assert len(wider_repr) < len(wide_repr)
+
+ for line in wide_repr.splitlines()[1::13]:
+ assert "Level 0 Level 1" in line
+
+ def test_wide_repr_multiindex_cols(self):
+ with option_context("mode.sim_interactive", True, "display.max_columns", 20):
+ max_cols = get_option("display.max_columns")
+ midx = MultiIndex.from_arrays([["a" * 5] * 10] * 2)
+ mcols = MultiIndex.from_arrays([["b" * 3] * (max_cols - 1)] * 2)
+ df = DataFrame(
+ [["c" * 25] * (max_cols - 1)] * 10, index=midx, columns=mcols
+ )
+ df.index.names = ["Level 0", "Level 1"]
+ with option_context("display.expand_frame_repr", False):
+ rep_str = repr(df)
+ with option_context("display.expand_frame_repr", True):
+ wide_repr = repr(df)
+ assert rep_str != wide_repr
+
+ with option_context("display.width", 150, "display.max_columns", 20):
+ wider_repr = repr(df)
+ assert len(wider_repr) < len(wide_repr)
+
+ def test_wide_repr_unicode(self):
+ with option_context("mode.sim_interactive", True, "display.max_columns", 20):
+ max_cols = 20
+ df = DataFrame([["a" * 25] * 10] * (max_cols - 1))
+ with option_context("display.expand_frame_repr", False):
+ rep_str = repr(df)
+ with option_context("display.expand_frame_repr", True):
+ wide_repr = repr(df)
+ assert rep_str != wide_repr
+
+ with option_context("display.width", 150):
+ wider_repr = repr(df)
+ assert len(wider_repr) < len(wide_repr)
+
+ def test_wide_repr_wide_long_columns(self):
+ with option_context("mode.sim_interactive", True):
+ df = DataFrame({"a": ["a" * 30, "b" * 30], "b": ["c" * 70, "d" * 80]})
+
+ result = repr(df)
+ assert "ccccc" in result
+ assert "ddddd" in result
+
+ def test_long_series(self):
+ n = 1000
+ s = Series(
+ np.random.default_rng(2).integers(-50, 50, n),
+ index=[f"s{x:04d}" for x in range(n)],
+ dtype="int64",
+ )
+
+ str_rep = str(s)
+ nmatches = len(re.findall("dtype", str_rep))
+ assert nmatches == 1
+
+ def test_to_string_ascii_error(self):
+ data = [
+ (
+ "0 ",
+ " .gitignore ",
+ " 5 ",
+ " \xe2\x80\xa2\xe2\x80\xa2\xe2\x80\xa2\xe2\x80\xa2\xe2\x80\xa2",
+ )
+ ]
+ df = DataFrame(data)
+
+ # it works!
+ repr(df)
+
+ def test_show_dimensions(self):
+ df = DataFrame(123, index=range(10, 15), columns=range(30))
+
+ with option_context(
+ "display.max_rows",
+ 10,
+ "display.max_columns",
+ 40,
+ "display.width",
+ 500,
+ "display.expand_frame_repr",
+ "info",
+ "display.show_dimensions",
+ True,
+ ):
+ assert "5 rows" in str(df)
+ assert "5 rows" in df._repr_html_()
+ with option_context(
+ "display.max_rows",
+ 10,
+ "display.max_columns",
+ 40,
+ "display.width",
+ 500,
+ "display.expand_frame_repr",
+ "info",
+ "display.show_dimensions",
+ False,
+ ):
+ assert "5 rows" not in str(df)
+ assert "5 rows" not in df._repr_html_()
+ with option_context(
+ "display.max_rows",
+ 2,
+ "display.max_columns",
+ 2,
+ "display.width",
+ 500,
+ "display.expand_frame_repr",
+ "info",
+ "display.show_dimensions",
+ "truncate",
+ ):
+ assert "5 rows" in str(df)
+ assert "5 rows" in df._repr_html_()
+ with option_context(
+ "display.max_rows",
+ 10,
+ "display.max_columns",
+ 40,
+ "display.width",
+ 500,
+ "display.expand_frame_repr",
+ "info",
+ "display.show_dimensions",
+ "truncate",
+ ):
+ assert "5 rows" not in str(df)
+ assert "5 rows" not in df._repr_html_()
+
+ def test_info_repr(self):
+ # GH#21746 For tests inside a terminal (i.e. not CI) we need to detect
+ # the terminal size to ensure that we try to print something "too big"
+ term_width, term_height = get_terminal_size()
+
+ max_rows = 60
+ max_cols = 20 + (max(term_width, 80) - 80) // 4
+ # Long
+ h, w = max_rows + 1, max_cols - 1
+ df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
+ assert has_vertically_truncated_repr(df)
+ with option_context("display.large_repr", "info"):
+ assert has_info_repr(df)
+
+ # Wide
+ h, w = max_rows - 1, max_cols + 1
+ df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
+ assert has_horizontally_truncated_repr(df)
+ with option_context(
+ "display.large_repr", "info", "display.max_columns", max_cols
+ ):
+ assert has_info_repr(df)
+
+ def test_info_repr_max_cols(self):
+ # GH #6939
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 5)))
+ with option_context(
+ "display.large_repr",
+ "info",
+ "display.max_columns",
+ 1,
+ "display.max_info_columns",
+ 4,
+ ):
+ assert has_non_verbose_info_repr(df)
+
+ with option_context(
+ "display.large_repr",
+ "info",
+ "display.max_columns",
+ 1,
+ "display.max_info_columns",
+ 5,
+ ):
+ assert not has_non_verbose_info_repr(df)
+
+ # FIXME: don't leave commented-out
+ # test verbose overrides
+ # set_option('display.max_info_columns', 4) # exceeded
+
+ def test_pprint_pathological_object(self):
+ """
+ If the test fails, it at least won't hang.
+ """
+
+ class A:
+ def __getitem__(self, key):
+ return 3 # obviously simplified
+
+ df = DataFrame([A()])
+ repr(df) # just don't die
+
+ def test_float_trim_zeros(self):
+ vals = [
+ 2.08430917305e10,
+ 3.52205017305e10,
+ 2.30674817305e10,
+ 2.03954217305e10,
+ 5.59897817305e10,
+ ]
+ skip = True
+ for line in repr(DataFrame({"A": vals})).split("\n")[:-2]:
+ if line.startswith("dtype:"):
+ continue
+ if _three_digit_exp():
+ assert ("+010" in line) or skip
+ else:
+ assert ("+10" in line) or skip
+ skip = False
+
+ @pytest.mark.parametrize(
+ "data, expected",
+ [
+ (["3.50"], "0 3.50\ndtype: object"),
+ ([1.20, "1.00"], "0 1.2\n1 1.00\ndtype: object"),
+ ([np.nan], "0 NaN\ndtype: float64"),
+ ([None], "0 None\ndtype: object"),
+ (["3.50", np.nan], "0 3.50\n1 NaN\ndtype: object"),
+ ([3.50, np.nan], "0 3.5\n1 NaN\ndtype: float64"),
+ ([3.50, np.nan, "3.50"], "0 3.5\n1 NaN\n2 3.50\ndtype: object"),
+ ([3.50, None, "3.50"], "0 3.5\n1 None\n2 3.50\ndtype: object"),
+ ],
+ )
+ def test_repr_str_float_truncation(self, data, expected, using_infer_string):
+ # GH#38708
+ series = Series(data, dtype=object if "3.50" in data else None)
+ result = repr(series)
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "float_format,expected",
+ [
+ ("{:,.0f}".format, "0 1,000\n1 test\ndtype: object"),
+ ("{:.4f}".format, "0 1000.0000\n1 test\ndtype: object"),
+ ],
+ )
+ def test_repr_float_format_in_object_col(self, float_format, expected):
+ # GH#40024
+ df = Series([1000.0, "test"])
+ with option_context("display.float_format", float_format):
+ result = repr(df)
+
+ assert result == expected
+
+ def test_period(self):
+ # GH 12615
+ df = DataFrame(
+ {
+ "A": pd.period_range("2013-01", periods=4, freq="M"),
+ "B": [
+ pd.Period("2011-01", freq="M"),
+ pd.Period("2011-02-01", freq="D"),
+ pd.Period("2011-03-01 09:00", freq="h"),
+ pd.Period("2011-04", freq="M"),
+ ],
+ "C": list("abcd"),
+ }
+ )
+ exp = (
+ " A B C\n"
+ "0 2013-01 2011-01 a\n"
+ "1 2013-02 2011-02-01 b\n"
+ "2 2013-03 2011-03-01 09:00 c\n"
+ "3 2013-04 2011-04 d"
+ )
+ assert str(df) == exp
+
+ @pytest.mark.parametrize(
+ "length, max_rows, min_rows, expected",
+ [
+ (10, 10, 10, 10),
+ (10, 10, None, 10),
+ (10, 8, None, 8),
+ (20, 30, 10, 30), # max_rows > len(frame), hence max_rows
+ (50, 30, 10, 10), # max_rows < len(frame), hence min_rows
+ (100, 60, 10, 10), # same
+ (60, 60, 10, 60), # edge case
+ (61, 60, 10, 10), # edge case
+ ],
+ )
+ def test_max_rows_fitted(self, length, min_rows, max_rows, expected):
+ """Check that display logic is correct.
+
+ GH #37359
+
+ See description here:
+ https://pandas.pydata.org/docs/dev/user_guide/options.html#frequently-used-options
+ """
+ formatter = fmt.DataFrameFormatter(
+ DataFrame(np.random.default_rng(2).random((length, 3))),
+ max_rows=max_rows,
+ min_rows=min_rows,
+ )
+ result = formatter.max_rows_fitted
+ assert result == expected
+
+
+def gen_series_formatting():
+ s1 = Series(["a"] * 100)
+ s2 = Series(["ab"] * 100)
+ s3 = Series(["a", "ab", "abc", "abcd", "abcde", "abcdef"])
+ s4 = s3[::-1]
+ test_sers = {"onel": s1, "twol": s2, "asc": s3, "desc": s4}
+ return test_sers
+
+
+class TestSeriesFormatting:
+ def test_freq_name_separation(self):
+ s = Series(
+ np.random.default_rng(2).standard_normal(10),
+ index=date_range("1/1/2000", periods=10),
+ name=0,
+ )
+
+ result = repr(s)
+ assert "Freq: D, Name: 0" in result
+
+ def test_unicode_name_in_footer(self):
+ s = Series([1, 2], name="\u05e2\u05d1\u05e8\u05d9\u05ea")
+ sf = fmt.SeriesFormatter(s, name="\u05e2\u05d1\u05e8\u05d9\u05ea")
+ sf._get_footer() # should not raise exception
+
+ def test_east_asian_unicode_series(self, using_infer_string):
+ # not aligned properly because of east asian width
+
+ # unicode index
+ s = Series(["a", "bb", "CCC", "D"], index=["あ", "いい", "ううう", "ええええ"])
+ expected = "".join(
+ [
+ "あ a\n",
+ "いい bb\n",
+ "ううう CCC\n",
+ "ええええ D\ndtype: object",
+ ]
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ # unicode values
+ s = Series(["あ", "いい", "ううう", "ええええ"], index=["a", "bb", "c", "ddd"])
+ expected = "".join(
+ [
+ "a あ\n",
+ "bb いい\n",
+ "c ううう\n",
+ "ddd ええええ\n",
+ "dtype: object",
+ ]
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ # both
+ s = Series(
+ ["あ", "いい", "ううう", "ええええ"],
+ index=["ああ", "いいいい", "う", "えええ"],
+ )
+ expected = "".join(
+ [
+ "ああ あ\n",
+ "いいいい いい\n",
+ "う ううう\n",
+ "えええ ええええ\n",
+ "dtype: object",
+ ]
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ # unicode footer
+ s = Series(
+ ["あ", "いい", "ううう", "ええええ"],
+ index=["ああ", "いいいい", "う", "えええ"],
+ name="おおおおおおお",
+ )
+ expected = (
+ "ああ あ\nいいいい いい\nう ううう\n"
+ "えええ ええええ\nName: おおおおおおお, dtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ # MultiIndex
+ idx = MultiIndex.from_tuples(
+ [("あ", "いい"), ("う", "え"), ("おおお", "かかかか"), ("き", "くく")]
+ )
+ s = Series([1, 22, 3333, 44444], index=idx)
+ expected = (
+ "あ いい 1\n"
+ "う え 22\n"
+ "おおお かかかか 3333\n"
+ "き くく 44444\ndtype: int64"
+ )
+ assert repr(s) == expected
+
+ # object dtype, shorter than unicode repr
+ s = Series([1, 22, 3333, 44444], index=[1, "AB", np.nan, "あああ"])
+ expected = (
+ "1 1\nAB 22\nNaN 3333\nあああ 44444\ndtype: int64"
+ )
+ assert repr(s) == expected
+
+ # object dtype, longer than unicode repr
+ s = Series(
+ [1, 22, 3333, 44444], index=[1, "AB", Timestamp("2011-01-01"), "あああ"]
+ )
+ expected = (
+ "1 1\n"
+ "AB 22\n"
+ "2011-01-01 00:00:00 3333\n"
+ "あああ 44444\ndtype: int64"
+ )
+ assert repr(s) == expected
+
+ # truncate
+ with option_context("display.max_rows", 3):
+ s = Series(["あ", "いい", "ううう", "ええええ"], name="おおおおおおお")
+
+ expected = (
+ "0 あ\n ... \n"
+ "3 ええええ\n"
+ "Name: おおおおおおお, Length: 4, dtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ s.index = ["ああ", "いいいい", "う", "えええ"]
+ expected = (
+ "ああ あ\n ... \n"
+ "えええ ええええ\n"
+ "Name: おおおおおおお, Length: 4, dtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ # Enable Unicode option -----------------------------------------
+ with option_context("display.unicode.east_asian_width", True):
+ # unicode index
+ s = Series(
+ ["a", "bb", "CCC", "D"],
+ index=["あ", "いい", "ううう", "ええええ"],
+ )
+ expected = (
+ "あ a\nいい bb\nううう CCC\n"
+ "ええええ D\ndtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ # unicode values
+ s = Series(
+ ["あ", "いい", "ううう", "ええええ"],
+ index=["a", "bb", "c", "ddd"],
+ )
+ expected = (
+ "a あ\nbb いい\nc ううう\n"
+ "ddd ええええ\ndtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+ # both
+ s = Series(
+ ["あ", "いい", "ううう", "ええええ"],
+ index=["ああ", "いいいい", "う", "えええ"],
+ )
+ expected = (
+ "ああ あ\n"
+ "いいいい いい\n"
+ "う ううう\n"
+ "えええ ええええ\ndtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ # unicode footer
+ s = Series(
+ ["あ", "いい", "ううう", "ええええ"],
+ index=["ああ", "いいいい", "う", "えええ"],
+ name="おおおおおおお",
+ )
+ expected = (
+ "ああ あ\n"
+ "いいいい いい\n"
+ "う ううう\n"
+ "えええ ええええ\n"
+ "Name: おおおおおおお, dtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ # MultiIndex
+ idx = MultiIndex.from_tuples(
+ [("あ", "いい"), ("う", "え"), ("おおお", "かかかか"), ("き", "くく")]
+ )
+ s = Series([1, 22, 3333, 44444], index=idx)
+ expected = (
+ "あ いい 1\n"
+ "う え 22\n"
+ "おおお かかかか 3333\n"
+ "き くく 44444\n"
+ "dtype: int64"
+ )
+ assert repr(s) == expected
+
+ # object dtype, shorter than unicode repr
+ s = Series([1, 22, 3333, 44444], index=[1, "AB", np.nan, "あああ"])
+ expected = (
+ "1 1\nAB 22\nNaN 3333\n"
+ "あああ 44444\ndtype: int64"
+ )
+ assert repr(s) == expected
+
+ # object dtype, longer than unicode repr
+ s = Series(
+ [1, 22, 3333, 44444],
+ index=[1, "AB", Timestamp("2011-01-01"), "あああ"],
+ )
+ expected = (
+ "1 1\n"
+ "AB 22\n"
+ "2011-01-01 00:00:00 3333\n"
+ "あああ 44444\ndtype: int64"
+ )
+ assert repr(s) == expected
+
+ # truncate
+ with option_context("display.max_rows", 3):
+ s = Series(["あ", "いい", "ううう", "ええええ"], name="おおおおおおお")
+ expected = (
+ "0 あ\n ... \n"
+ "3 ええええ\n"
+ "Name: おおおおおおお, Length: 4, dtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ s.index = ["ああ", "いいいい", "う", "えええ"]
+ expected = (
+ "ああ あ\n"
+ " ... \n"
+ "えええ ええええ\n"
+ "Name: おおおおおおお, Length: 4, dtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ # ambiguous unicode
+ s = Series(
+ ["¡¡", "い¡¡", "ううう", "ええええ"],
+ index=["ああ", "¡¡¡¡いい", "¡¡", "えええ"],
+ )
+ expected = (
+ "ああ ¡¡\n"
+ "¡¡¡¡いい い¡¡\n"
+ "¡¡ ううう\n"
+ "えええ ええええ\ndtype: object"
+ )
+ if using_infer_string:
+ expected = expected.replace("dtype: object", "dtype: str")
+ assert repr(s) == expected
+
+ def test_float_trim_zeros(self):
+ vals = [
+ 2.08430917305e10,
+ 3.52205017305e10,
+ 2.30674817305e10,
+ 2.03954217305e10,
+ 5.59897817305e10,
+ ]
+ for line in repr(Series(vals)).split("\n"):
+ if line.startswith("dtype:"):
+ continue
+ if _three_digit_exp():
+ assert "+010" in line
+ else:
+ assert "+10" in line
+
+ @pytest.mark.parametrize(
+ "start_date",
+ [
+ "2017-01-01 23:59:59.999999999",
+ "2017-01-01 23:59:59.99999999",
+ "2017-01-01 23:59:59.9999999",
+ "2017-01-01 23:59:59.999999",
+ "2017-01-01 23:59:59.99999",
+ "2017-01-01 23:59:59.9999",
+ ],
+ )
+ def test_datetimeindex_highprecision(self, start_date):
+ # GH19030
+ # Check that high-precision time values for the end of day are
+ # included in repr for DatetimeIndex
+ s1 = Series(date_range(start=start_date, freq="D", periods=5))
+ result = str(s1)
+ assert start_date in result
+
+ dti = date_range(start=start_date, freq="D", periods=5)
+ s2 = Series(3, index=dti)
+ result = str(s2.index)
+ assert start_date in result
+
+ def test_mixed_datetime64(self):
+ df = DataFrame({"A": [1, 2], "B": ["2012-01-01", "2012-01-02"]})
+ df["B"] = pd.to_datetime(df.B)
+
+ result = repr(df.loc[0])
+ assert "2012-01-01" in result
+
+ def test_period(self):
+ # GH 12615
+ index = pd.period_range("2013-01", periods=6, freq="M")
+ s = Series(np.arange(6, dtype="int64"), index=index)
+ exp = (
+ "2013-01 0\n"
+ "2013-02 1\n"
+ "2013-03 2\n"
+ "2013-04 3\n"
+ "2013-05 4\n"
+ "2013-06 5\n"
+ "Freq: M, dtype: int64"
+ )
+ assert str(s) == exp
+
+ s = Series(index)
+ exp = (
+ "0 2013-01\n"
+ "1 2013-02\n"
+ "2 2013-03\n"
+ "3 2013-04\n"
+ "4 2013-05\n"
+ "5 2013-06\n"
+ "dtype: period[M]"
+ )
+ assert str(s) == exp
+
+ # periods with mixed freq
+ s = Series(
+ [
+ pd.Period("2011-01", freq="M"),
+ pd.Period("2011-02-01", freq="D"),
+ pd.Period("2011-03-01 09:00", freq="h"),
+ ]
+ )
+ exp = (
+ "0 2011-01\n1 2011-02-01\n"
+ "2 2011-03-01 09:00\ndtype: object"
+ )
+ assert str(s) == exp
+
+ def test_max_multi_index_display(self):
+ # GH 7101
+
+ # doc example (indexing.rst)
+
+ # multi-index
+ arrays = [
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ tuples = list(zip(*arrays))
+ index = MultiIndex.from_tuples(tuples, names=["first", "second"])
+ s = Series(np.random.default_rng(2).standard_normal(8), index=index)
+
+ with option_context("display.max_rows", 10):
+ assert len(str(s).split("\n")) == 10
+ with option_context("display.max_rows", 3):
+ assert len(str(s).split("\n")) == 5
+ with option_context("display.max_rows", 2):
+ assert len(str(s).split("\n")) == 5
+ with option_context("display.max_rows", 1):
+ assert len(str(s).split("\n")) == 4
+ with option_context("display.max_rows", 0):
+ assert len(str(s).split("\n")) == 10
+
+ # index
+ s = Series(np.random.default_rng(2).standard_normal(8), None)
+
+ with option_context("display.max_rows", 10):
+ assert len(str(s).split("\n")) == 9
+ with option_context("display.max_rows", 3):
+ assert len(str(s).split("\n")) == 4
+ with option_context("display.max_rows", 2):
+ assert len(str(s).split("\n")) == 4
+ with option_context("display.max_rows", 1):
+ assert len(str(s).split("\n")) == 3
+ with option_context("display.max_rows", 0):
+ assert len(str(s).split("\n")) == 9
+
+ # Make sure #8532 is fixed
+ def test_consistent_format(self):
+ s = Series([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9999, 1, 1] * 10)
+ with option_context("display.max_rows", 10, "display.show_dimensions", False):
+ res = repr(s)
+ exp = (
+ "0 1.0000\n1 1.0000\n2 1.0000\n3 "
+ "1.0000\n4 1.0000\n ... \n125 "
+ "1.0000\n126 1.0000\n127 0.9999\n128 "
+ "1.0000\n129 1.0000\ndtype: float64"
+ )
+ assert res == exp
+
+ def chck_ncols(self, s):
+ lines = [
+ line for line in repr(s).split("\n") if not re.match(r"[^\.]*\.+", line)
+ ][:-1]
+ ncolsizes = len({len(line.strip()) for line in lines})
+ assert ncolsizes == 1
+
+ def test_format_explicit(self, using_infer_string):
+ test_sers = gen_series_formatting()
+ with option_context("display.max_rows", 4, "display.show_dimensions", False):
+ res = repr(test_sers["onel"])
+ exp = "0 a\n1 a\n ..\n98 a\n99 a\ndtype: object"
+ if using_infer_string:
+ exp = exp.replace("dtype: object", "dtype: str")
+ assert exp == res
+ res = repr(test_sers["twol"])
+ exp = "0 ab\n1 ab\n ..\n98 ab\n99 ab\ndtype: object"
+ if using_infer_string:
+ exp = exp.replace("dtype: object", "dtype: str")
+ assert exp == res
+ res = repr(test_sers["asc"])
+ exp = (
+ "0 a\n1 ab\n ... \n4 abcde\n5 "
+ "abcdef\ndtype: object"
+ )
+ if using_infer_string:
+ exp = exp.replace("dtype: object", "dtype: str")
+ assert exp == res
+ res = repr(test_sers["desc"])
+ exp = (
+ "5 abcdef\n4 abcde\n ... \n1 ab\n0 "
+ "a\ndtype: object"
+ )
+ if using_infer_string:
+ exp = exp.replace("dtype: object", "dtype: str")
+ assert exp == res
+
+ def test_ncols(self):
+ test_sers = gen_series_formatting()
+ for s in test_sers.values():
+ self.chck_ncols(s)
+
+ def test_max_rows_eq_one(self):
+ s = Series(range(10), dtype="int64")
+ with option_context("display.max_rows", 1):
+ strrepr = repr(s).split("\n")
+ exp1 = ["0", "0"]
+ res1 = strrepr[0].split()
+ assert exp1 == res1
+ exp2 = [".."]
+ res2 = strrepr[1].split()
+ assert exp2 == res2
+
+ def test_truncate_ndots(self):
+ def getndots(s):
+ return len(re.match(r"[^\.]*(\.*)", s).groups()[0])
+
+ s = Series([0, 2, 3, 6])
+ with option_context("display.max_rows", 2):
+ strrepr = repr(s).replace("\n", "")
+ assert getndots(strrepr) == 2
+
+ s = Series([0, 100, 200, 400])
+ with option_context("display.max_rows", 2):
+ strrepr = repr(s).replace("\n", "")
+ assert getndots(strrepr) == 3
+
+ def test_show_dimensions(self):
+ # gh-7117
+ s = Series(range(5))
+
+ assert "Length" not in repr(s)
+
+ with option_context("display.max_rows", 4):
+ assert "Length" in repr(s)
+
+ with option_context("display.show_dimensions", True):
+ assert "Length" in repr(s)
+
+ with option_context("display.max_rows", 4, "display.show_dimensions", False):
+ assert "Length" not in repr(s)
+
+ def test_repr_min_rows(self):
+ s = Series(range(20))
+
+ # default setting no truncation even if above min_rows
+ assert ".." not in repr(s)
+
+ s = Series(range(61))
+
+ # default of max_rows 60 triggers truncation if above
+ assert ".." in repr(s)
+
+ with option_context("display.max_rows", 10, "display.min_rows", 4):
+ # truncated after first two rows
+ assert ".." in repr(s)
+ assert "2 " not in repr(s)
+
+ with option_context("display.max_rows", 12, "display.min_rows", None):
+ # when set to None, follow value of max_rows
+ assert "5 5" in repr(s)
+
+ with option_context("display.max_rows", 10, "display.min_rows", 12):
+ # when set value higher as max_rows, use the minimum
+ assert "5 5" not in repr(s)
+
+ with option_context("display.max_rows", None, "display.min_rows", 12):
+ # max_rows of None -> never truncate
+ assert ".." not in repr(s)
+
+
+class TestGenericArrayFormatter:
+ def test_1d_array(self):
+ # _GenericArrayFormatter is used on types for which there isn't a dedicated
+ # formatter. np.bool_ is one of those types.
+ obj = fmt._GenericArrayFormatter(np.array([True, False]))
+ res = obj.get_result()
+ assert len(res) == 2
+ # Results should be right-justified.
+ assert res[0] == " True"
+ assert res[1] == " False"
+
+ def test_2d_array(self):
+ obj = fmt._GenericArrayFormatter(np.array([[True, False], [False, True]]))
+ res = obj.get_result()
+ assert len(res) == 2
+ assert res[0] == " [True, False]"
+ assert res[1] == " [False, True]"
+
+ def test_3d_array(self):
+ obj = fmt._GenericArrayFormatter(
+ np.array([[[True, True], [False, False]], [[False, True], [True, False]]])
+ )
+ res = obj.get_result()
+ assert len(res) == 2
+ assert res[0] == " [[True, True], [False, False]]"
+ assert res[1] == " [[False, True], [True, False]]"
+
+
+def _three_digit_exp():
+ return f"{1.7e8:.4g}" == "1.7e+008"
+
+
+class TestFloatArrayFormatter:
+ def test_misc(self):
+ obj = fmt.FloatArrayFormatter(np.array([], dtype=np.float64))
+ result = obj.get_result()
+ assert len(result) == 0
+
+ def test_format(self):
+ obj = fmt.FloatArrayFormatter(np.array([12, 0], dtype=np.float64))
+ result = obj.get_result()
+ assert result[0] == " 12.0"
+ assert result[1] == " 0.0"
+
+ def test_output_display_precision_trailing_zeroes(self):
+ # Issue #20359: trimming zeros while there is no decimal point
+
+ # Happens when display precision is set to zero
+ with option_context("display.precision", 0):
+ s = Series([840.0, 4200.0])
+ expected_output = "0 840\n1 4200\ndtype: float64"
+ assert str(s) == expected_output
+
+ @pytest.mark.parametrize(
+ "value,expected",
+ [
+ ([9.4444], " 0\n0 9"),
+ ([0.49], " 0\n0 5e-01"),
+ ([10.9999], " 0\n0 11"),
+ ([9.5444, 9.6], " 0\n0 10\n1 10"),
+ ([0.46, 0.78, -9.9999], " 0\n0 5e-01\n1 8e-01\n2 -1e+01"),
+ ],
+ )
+ def test_set_option_precision(self, value, expected):
+ # Issue #30122
+ # Precision was incorrectly shown
+
+ with option_context("display.precision", 0):
+ df_value = DataFrame(value)
+ assert str(df_value) == expected
+
+ def test_output_significant_digits(self):
+ # Issue #9764
+
+ # In case default display precision changes:
+ with option_context("display.precision", 6):
+ # DataFrame example from issue #9764
+ d = DataFrame(
+ {
+ "col1": [
+ 9.999e-8,
+ 1e-7,
+ 1.0001e-7,
+ 2e-7,
+ 4.999e-7,
+ 5e-7,
+ 5.0001e-7,
+ 6e-7,
+ 9.999e-7,
+ 1e-6,
+ 1.0001e-6,
+ 2e-6,
+ 4.999e-6,
+ 5e-6,
+ 5.0001e-6,
+ 6e-6,
+ ]
+ }
+ )
+
+ expected_output = {
+ (0, 6): " col1\n"
+ "0 9.999000e-08\n"
+ "1 1.000000e-07\n"
+ "2 1.000100e-07\n"
+ "3 2.000000e-07\n"
+ "4 4.999000e-07\n"
+ "5 5.000000e-07",
+ (1, 6): " col1\n"
+ "1 1.000000e-07\n"
+ "2 1.000100e-07\n"
+ "3 2.000000e-07\n"
+ "4 4.999000e-07\n"
+ "5 5.000000e-07",
+ (1, 8): " col1\n"
+ "1 1.000000e-07\n"
+ "2 1.000100e-07\n"
+ "3 2.000000e-07\n"
+ "4 4.999000e-07\n"
+ "5 5.000000e-07\n"
+ "6 5.000100e-07\n"
+ "7 6.000000e-07",
+ (8, 16): " col1\n"
+ "8 9.999000e-07\n"
+ "9 1.000000e-06\n"
+ "10 1.000100e-06\n"
+ "11 2.000000e-06\n"
+ "12 4.999000e-06\n"
+ "13 5.000000e-06\n"
+ "14 5.000100e-06\n"
+ "15 6.000000e-06",
+ (9, 16): " col1\n"
+ "9 0.000001\n"
+ "10 0.000001\n"
+ "11 0.000002\n"
+ "12 0.000005\n"
+ "13 0.000005\n"
+ "14 0.000005\n"
+ "15 0.000006",
+ }
+
+ for (start, stop), v in expected_output.items():
+ assert str(d[start:stop]) == v
+
+ def test_too_long(self):
+ # GH 10451
+ with option_context("display.precision", 4):
+ # need both a number > 1e6 and something that normally formats to
+ # having length > display.precision + 6
+ df = DataFrame({"x": [12345.6789]})
+ assert str(df) == " x\n0 12345.6789"
+ df = DataFrame({"x": [2e6]})
+ assert str(df) == " x\n0 2000000.0"
+ df = DataFrame({"x": [12345.6789, 2e6]})
+ assert str(df) == " x\n0 1.2346e+04\n1 2.0000e+06"
+
+
+class TestTimedelta64Formatter:
+ def test_days(self):
+ x = pd.to_timedelta([*list(range(5)), NaT], unit="D")._values
+ result = fmt._Timedelta64Formatter(x).get_result()
+ assert result[0].strip() == "0 days"
+ assert result[1].strip() == "1 days"
+
+ result = fmt._Timedelta64Formatter(x[1:2]).get_result()
+ assert result[0].strip() == "1 days"
+
+ result = fmt._Timedelta64Formatter(x).get_result()
+ assert result[0].strip() == "0 days"
+ assert result[1].strip() == "1 days"
+
+ result = fmt._Timedelta64Formatter(x[1:2]).get_result()
+ assert result[0].strip() == "1 days"
+
+ def test_days_neg(self):
+ x = pd.to_timedelta([*list(range(5)), NaT], unit="D")._values
+ result = fmt._Timedelta64Formatter(-x).get_result()
+ assert result[0].strip() == "0 days"
+ assert result[1].strip() == "-1 days"
+
+ def test_subdays(self):
+ y = pd.to_timedelta([*list(range(5)), NaT], unit="s")._values
+ result = fmt._Timedelta64Formatter(y).get_result()
+ assert result[0].strip() == "0 days 00:00:00"
+ assert result[1].strip() == "0 days 00:00:01"
+
+ def test_subdays_neg(self):
+ y = pd.to_timedelta([*list(range(5)), NaT], unit="s")._values
+ result = fmt._Timedelta64Formatter(-y).get_result()
+ assert result[0].strip() == "0 days 00:00:00"
+ assert result[1].strip() == "-1 days +23:59:59"
+
+ def test_zero(self):
+ x = pd.to_timedelta([*list(range(1)), NaT], unit="D")._values
+ result = fmt._Timedelta64Formatter(x).get_result()
+ assert result[0].strip() == "0 days"
+
+ x = pd.to_timedelta(list(range(1)), unit="D")._values
+ result = fmt._Timedelta64Formatter(x).get_result()
+ assert result[0].strip() == "0 days"
+
+
+class TestDatetime64Formatter:
+ def test_mixed(self):
+ x = Series([datetime(2013, 1, 1), datetime(2013, 1, 1, 12), NaT])._values
+ result = fmt._Datetime64Formatter(x).get_result()
+ assert result[0].strip() == "2013-01-01 00:00:00"
+ assert result[1].strip() == "2013-01-01 12:00:00"
+
+ def test_dates(self):
+ x = Series([datetime(2013, 1, 1), datetime(2013, 1, 2), NaT])._values
+ result = fmt._Datetime64Formatter(x).get_result()
+ assert result[0].strip() == "2013-01-01"
+ assert result[1].strip() == "2013-01-02"
+
+ def test_date_nanos(self):
+ x = Series([Timestamp(200)])._values
+ result = fmt._Datetime64Formatter(x).get_result()
+ assert result[0].strip() == "1970-01-01 00:00:00.000000200"
+
+ def test_dates_display(self):
+ # 10170
+ # make sure that we are consistently display date formatting
+ x = Series(date_range("20130101 09:00:00", periods=5, freq="D"))
+ x.iloc[1] = np.nan
+ result = fmt._Datetime64Formatter(x._values).get_result()
+ assert result[0].strip() == "2013-01-01 09:00:00"
+ assert result[1].strip() == "NaT"
+ assert result[4].strip() == "2013-01-05 09:00:00"
+
+ x = Series(date_range("20130101 09:00:00", periods=5, freq="s"))
+ x.iloc[1] = np.nan
+ result = fmt._Datetime64Formatter(x._values).get_result()
+ assert result[0].strip() == "2013-01-01 09:00:00"
+ assert result[1].strip() == "NaT"
+ assert result[4].strip() == "2013-01-01 09:00:04"
+
+ x = Series(date_range("20130101 09:00:00", periods=5, freq="ms"))
+ x.iloc[1] = np.nan
+ result = fmt._Datetime64Formatter(x._values).get_result()
+ assert result[0].strip() == "2013-01-01 09:00:00.000"
+ assert result[1].strip() == "NaT"
+ assert result[4].strip() == "2013-01-01 09:00:00.004"
+
+ x = Series(date_range("20130101 09:00:00", periods=5, freq="us"))
+ x.iloc[1] = np.nan
+ result = fmt._Datetime64Formatter(x._values).get_result()
+ assert result[0].strip() == "2013-01-01 09:00:00.000000"
+ assert result[1].strip() == "NaT"
+ assert result[4].strip() == "2013-01-01 09:00:00.000004"
+
+ x = Series(date_range("20130101 09:00:00", periods=5, freq="ns"))
+ x.iloc[1] = np.nan
+ result = fmt._Datetime64Formatter(x._values).get_result()
+ assert result[0].strip() == "2013-01-01 09:00:00.000000000"
+ assert result[1].strip() == "NaT"
+ assert result[4].strip() == "2013-01-01 09:00:00.000000004"
+
+ def test_datetime64formatter_yearmonth(self):
+ x = Series([datetime(2016, 1, 1), datetime(2016, 2, 2)])._values
+
+ def format_func(x):
+ return x.strftime("%Y-%m")
+
+ formatter = fmt._Datetime64Formatter(x, formatter=format_func)
+ result = formatter.get_result()
+ assert result == ["2016-01", "2016-02"]
+
+ def test_datetime64formatter_hoursecond(self):
+ x = Series(
+ pd.to_datetime(["10:10:10.100", "12:12:12.120"], format="%H:%M:%S.%f")
+ )._values
+
+ def format_func(x):
+ return x.strftime("%H:%M")
+
+ formatter = fmt._Datetime64Formatter(x, formatter=format_func)
+ result = formatter.get_result()
+ assert result == ["10:10", "12:12"]
+
+ def test_datetime64formatter_tz_ms(self):
+ x = (
+ Series(
+ np.array(["2999-01-01", "2999-01-02", "NaT"], dtype="datetime64[ms]")
+ )
+ .dt.tz_localize("US/Pacific")
+ ._values
+ )
+ result = fmt._Datetime64TZFormatter(x).get_result()
+ assert result[0].strip() == "2999-01-01 00:00:00-08:00"
+ assert result[1].strip() == "2999-01-02 00:00:00-08:00"
+
+
+class TestFormatPercentiles:
+ @pytest.mark.parametrize(
+ "percentiles, expected",
+ [
+ (
+ [0.01999, 0.02001, 0.5, 0.666666, 0.9999],
+ ["1.999%", "2.001%", "50%", "66.667%", "99.99%"],
+ ),
+ (
+ [0, 0.5, 0.02001, 0.5, 0.666666, 0.9999],
+ ["0%", "50%", "2.0%", "50%", "66.67%", "99.99%"],
+ ),
+ ([0.281, 0.29, 0.57, 0.58], ["28.1%", "29%", "57%", "58%"]),
+ ([0.28, 0.29, 0.57, 0.58], ["28%", "29%", "57%", "58%"]),
+ (
+ [0.9, 0.99, 0.999, 0.9999, 0.99999],
+ ["90%", "99%", "99.9%", "99.99%", "99.999%"],
+ ),
+ ],
+ )
+ def test_format_percentiles(self, percentiles, expected):
+ result = fmt.format_percentiles(percentiles)
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "percentiles",
+ [
+ ([0.1, np.nan, 0.5]),
+ ([-0.001, 0.1, 0.5]),
+ ([2, 0.1, 0.5]),
+ ([0.1, 0.5, "a"]),
+ ],
+ )
+ def test_error_format_percentiles(self, percentiles):
+ msg = r"percentiles should all be in the interval \[0,1\]"
+ with pytest.raises(ValueError, match=msg):
+ fmt.format_percentiles(percentiles)
+
+ def test_format_percentiles_integer_idx(self):
+ # Issue #26660
+ result = fmt.format_percentiles(np.linspace(0, 1, 10 + 1))
+ expected = [
+ "0%",
+ "10%",
+ "20%",
+ "30%",
+ "40%",
+ "50%",
+ "60%",
+ "70%",
+ "80%",
+ "90%",
+ "100%",
+ ]
+ assert result == expected
+
+
+@pytest.mark.parametrize("method", ["to_string", "to_html", "to_latex"])
+@pytest.mark.parametrize(
+ "encoding, data",
+ [(None, "abc"), ("utf-8", "abc"), ("gbk", "造成输出中文显示乱码"), ("foo", "abc")],
+)
+@pytest.mark.parametrize("filepath_or_buffer_id", ["string", "pathlike", "buffer"])
+def test_filepath_or_buffer_arg(
+ method,
+ tmp_path,
+ encoding,
+ data,
+ filepath_or_buffer_id,
+):
+ if filepath_or_buffer_id == "buffer":
+ filepath_or_buffer = StringIO()
+ elif filepath_or_buffer_id == "pathlike":
+ filepath_or_buffer = tmp_path / "foo"
+ else:
+ filepath_or_buffer = str(tmp_path / "foo")
+
+ df = DataFrame([data])
+ if method in ["to_latex"]: # uses styler implementation
+ pytest.importorskip("jinja2")
+
+ if filepath_or_buffer_id not in ["string", "pathlike"] and encoding is not None:
+ with pytest.raises(
+ ValueError, match="buf is not a file name and encoding is specified."
+ ):
+ getattr(df, method)(buf=filepath_or_buffer, encoding=encoding)
+ elif encoding == "foo":
+ with pytest.raises(LookupError, match="unknown encoding"):
+ getattr(df, method)(buf=filepath_or_buffer, encoding=encoding)
+ else:
+ expected = getattr(df, method)()
+ getattr(df, method)(buf=filepath_or_buffer, encoding=encoding)
+ encoding = encoding or "utf-8"
+ if filepath_or_buffer_id == "string":
+ with open(filepath_or_buffer, encoding=encoding) as f:
+ result = f.read()
+ elif filepath_or_buffer_id == "pathlike":
+ result = filepath_or_buffer.read_text(encoding=encoding)
+ elif filepath_or_buffer_id == "buffer":
+ result = filepath_or_buffer.getvalue()
+ assert result == expected
+ if filepath_or_buffer_id == "buffer":
+ assert not filepath_or_buffer.closed
+
+
+@pytest.mark.parametrize("method", ["to_string", "to_html", "to_latex"])
+def test_filepath_or_buffer_bad_arg_raises(float_frame, method):
+ if method in ["to_latex"]: # uses styler implementation
+ pytest.importorskip("jinja2")
+ msg = "buf is not a file name and it has no write method"
+ with pytest.raises(TypeError, match=msg):
+ getattr(float_frame, method)(buf=object())
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_ipython_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_ipython_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..df202dbd7d9fb9d754af234643c1d27ed0c78414
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_ipython_compat.py
@@ -0,0 +1,91 @@
+import numpy as np
+
+import pandas._config.config as cf
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+)
+
+
+class TestTableSchemaRepr:
+ def test_publishes(self, ip):
+ ipython = ip.instance(config=ip.config)
+ df = DataFrame({"A": [1, 2]})
+ objects = [df["A"], df] # dataframe / series
+ expected_keys = [
+ {"text/plain", "application/vnd.dataresource+json"},
+ {"text/plain", "text/html", "application/vnd.dataresource+json"},
+ ]
+
+ opt = cf.option_context("display.html.table_schema", True)
+ last_obj = None
+ for obj, expected in zip(objects, expected_keys):
+ last_obj = obj
+ with cf.option_context("display.html.table_schema", True):
+ # Can't reuse opt on all systems GH#58055
+ formatted = ipython.display_formatter.format(obj)
+ assert set(formatted[0].keys()) == expected
+
+ with_latex = cf.option_context("styler.render.repr", "latex")
+
+ with opt, with_latex:
+ formatted = ipython.display_formatter.format(last_obj)
+
+ expected = {
+ "text/plain",
+ "text/html",
+ "text/latex",
+ "application/vnd.dataresource+json",
+ }
+ assert set(formatted[0].keys()) == expected
+
+ def test_publishes_not_implemented(self, ip):
+ # column MultiIndex
+ # GH#15996
+ midx = MultiIndex.from_product([["A", "B"], ["a", "b", "c"]])
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, len(midx))), columns=midx
+ )
+
+ opt = cf.option_context("display.html.table_schema", True)
+
+ with opt:
+ formatted = ip.instance(config=ip.config).display_formatter.format(df)
+
+ expected = {"text/plain", "text/html"}
+ assert set(formatted[0].keys()) == expected
+
+ def test_config_on(self):
+ df = DataFrame({"A": [1, 2]})
+ with cf.option_context("display.html.table_schema", True):
+ result = df._repr_data_resource_()
+
+ assert result is not None
+
+ def test_config_default_off(self):
+ df = DataFrame({"A": [1, 2]})
+ with cf.option_context("display.html.table_schema", False):
+ result = df._repr_data_resource_()
+
+ assert result is None
+
+ def test_enable_data_resource_formatter(self, ip):
+ # GH#10491
+ formatters = ip.instance(config=ip.config).display_formatter.formatters
+ mimetype = "application/vnd.dataresource+json"
+
+ with cf.option_context("display.html.table_schema", True):
+ assert "application/vnd.dataresource+json" in formatters
+ assert formatters[mimetype].enabled
+
+ # still there, just disabled
+ assert "application/vnd.dataresource+json" in formatters
+ assert not formatters[mimetype].enabled
+
+ # able to re-set
+ with cf.option_context("display.html.table_schema", True):
+ assert "application/vnd.dataresource+json" in formatters
+ assert formatters[mimetype].enabled
+ # smoke test that it works
+ ip.instance(config=ip.config).display_formatter.format(cf)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_printing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_printing.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d154235d2c4a6d3cb5ee481127221017b5e1bdc
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_printing.py
@@ -0,0 +1,172 @@
+# Note! This file is aimed specifically at pandas.io.formats.printing utility
+# functions, not the general printing of pandas objects.
+from collections.abc import Mapping
+import string
+
+import pytest
+
+import pandas._config.config as cf
+
+import pandas as pd
+
+from pandas.io.formats import printing
+
+
+@pytest.mark.parametrize(
+ "input_names, expected_names",
+ [
+ (["'a b"], "['\\'a b']"), # Escape leading quote
+ (["test's b"], "['test\\'s b']"), # Escape apostrophe
+ (["'test' b"], "['\\'test\\' b']"), # Escape surrounding quotes
+ (["test b'"], "['test b\\'']"), # Escape single quote
+ (["test\n' b"], "['test\\n\\' b']"), # Escape quotes, preserve newline
+ ],
+)
+def test_formatted_index_names(input_names, expected_names):
+ # GH#60190
+ df = pd.DataFrame({name: [1, 2, 3] for name in input_names}).set_index(input_names)
+ formatted_names = str(df.index.names)
+
+ assert formatted_names == expected_names
+
+
+def test_adjoin():
+ data = [["a", "b", "c"], ["dd", "ee", "ff"], ["ggg", "hhh", "iii"]]
+ expected = "a dd ggg\nb ee hhh\nc ff iii"
+
+ adjoined = printing.adjoin(2, *data)
+
+ assert adjoined == expected
+
+
+class MyMapping(Mapping):
+ def __getitem__(self, key):
+ return 4
+
+ def __iter__(self):
+ return iter(["a", "b"])
+
+ def __len__(self):
+ return 2
+
+
+class TestPPrintThing:
+ def test_repr_binary_type(self):
+ letters = string.ascii_letters
+ try:
+ raw = bytes(letters, encoding=cf.get_option("display.encoding"))
+ except TypeError:
+ raw = bytes(letters)
+ b = str(raw.decode("utf-8"))
+ res = printing.pprint_thing(b, quote_strings=True)
+ assert res == repr(b)
+ res = printing.pprint_thing(b, quote_strings=False)
+ assert res == b
+
+ def test_repr_obeys_max_seq_limit(self):
+ with cf.option_context("display.max_seq_items", 2000):
+ assert len(printing.pprint_thing(list(range(1000)))) > 1000
+
+ with cf.option_context("display.max_seq_items", 5):
+ assert len(printing.pprint_thing(list(range(1000)))) < 100
+
+ with cf.option_context("display.max_seq_items", 1):
+ assert len(printing.pprint_thing(list(range(1000)))) < 9
+
+ def test_repr_set(self):
+ assert printing.pprint_thing({1}) == "{1}"
+
+ def test_repr_dict(self):
+ assert printing.pprint_thing({"a": 4, "b": 4}) == "{'a': 4, 'b': 4}"
+
+ def test_repr_mapping(self):
+ assert printing.pprint_thing(MyMapping()) == "{'a': 4, 'b': 4}"
+
+ def test_repr_frozenset(self):
+ assert printing.pprint_thing(frozenset([1, 2])) == "frozenset({1, 2})"
+
+
+class TestFormatBase:
+ def test_adjoin(self):
+ data = [["a", "b", "c"], ["dd", "ee", "ff"], ["ggg", "hhh", "iii"]]
+ expected = "a dd ggg\nb ee hhh\nc ff iii"
+
+ adjoined = printing.adjoin(2, *data)
+
+ assert adjoined == expected
+
+ def test_adjoin_unicode(self):
+ data = [["あ", "b", "c"], ["dd", "ええ", "ff"], ["ggg", "hhh", "いいい"]]
+ expected = "あ dd ggg\nb ええ hhh\nc ff いいい"
+ adjoined = printing.adjoin(2, *data)
+ assert adjoined == expected
+
+ adj = printing._EastAsianTextAdjustment()
+
+ expected = """あ dd ggg
+b ええ hhh
+c ff いいい"""
+
+ adjoined = adj.adjoin(2, *data)
+ assert adjoined == expected
+ cols = adjoined.split("\n")
+ assert adj.len(cols[0]) == 13
+ assert adj.len(cols[1]) == 13
+ assert adj.len(cols[2]) == 16
+
+ expected = """あ dd ggg
+b ええ hhh
+c ff いいい"""
+
+ adjoined = adj.adjoin(7, *data)
+ assert adjoined == expected
+ cols = adjoined.split("\n")
+ assert adj.len(cols[0]) == 23
+ assert adj.len(cols[1]) == 23
+ assert adj.len(cols[2]) == 26
+
+ def test_justify(self):
+ adj = printing._EastAsianTextAdjustment()
+
+ def just(x, *args, **kwargs):
+ # wrapper to test single str
+ return adj.justify([x], *args, **kwargs)[0]
+
+ assert just("abc", 5, mode="left") == "abc "
+ assert just("abc", 5, mode="center") == " abc "
+ assert just("abc", 5, mode="right") == " abc"
+ assert just("abc", 5, mode="left") == "abc "
+ assert just("abc", 5, mode="center") == " abc "
+ assert just("abc", 5, mode="right") == " abc"
+
+ assert just("パンダ", 5, mode="left") == "パンダ"
+ assert just("パンダ", 5, mode="center") == "パンダ"
+ assert just("パンダ", 5, mode="right") == "パンダ"
+
+ assert just("パンダ", 10, mode="left") == "パンダ "
+ assert just("パンダ", 10, mode="center") == " パンダ "
+ assert just("パンダ", 10, mode="right") == " パンダ"
+
+ def test_east_asian_len(self):
+ adj = printing._EastAsianTextAdjustment()
+
+ assert adj.len("abc") == 3
+ assert adj.len("abc") == 3
+
+ assert adj.len("パンダ") == 6
+ assert adj.len("パンダ") == 5
+ assert adj.len("パンダpanda") == 11
+ assert adj.len("パンダpanda") == 10
+
+ def test_ambiguous_width(self):
+ adj = printing._EastAsianTextAdjustment()
+ assert adj.len("¡¡ab") == 4
+
+ with cf.option_context("display.unicode.ambiguous_as_wide", True):
+ adj = printing._EastAsianTextAdjustment()
+ assert adj.len("¡¡ab") == 6
+
+ data = [["あ", "b", "c"], ["dd", "ええ", "ff"], ["ggg", "¡¡ab", "いいい"]]
+ expected = "あ dd ggg \nb ええ ¡¡ab\nc ff いいい"
+ adjoined = adj.adjoin(2, *data)
+ assert adjoined == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_csv.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_csv.py
new file mode 100644
index 0000000000000000000000000000000000000000..f70875172ccc8f16f2e66ed7cf7594487e5c192f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_csv.py
@@ -0,0 +1,864 @@
+import io
+import os
+import sys
+from zipfile import ZipFile
+
+from _csv import Error
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ compat,
+)
+import pandas._testing as tm
+
+
+class TestToCSV:
+ def test_to_csv_with_single_column(self, temp_file):
+ # see gh-18676, https://bugs.python.org/issue32255
+ #
+ # Python's CSV library adds an extraneous '""'
+ # before the newline when the NaN-value is in
+ # the first row. Otherwise, only the newline
+ # character is added. This behavior is inconsistent
+ # and was patched in https://bugs.python.org/pull_request4672.
+ df1 = DataFrame([None, 1])
+ expected1 = """\
+""
+1.0
+"""
+ df1.to_csv(temp_file, header=None, index=None)
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected1
+
+ df2 = DataFrame([1, None])
+ expected2 = """\
+1.0
+""
+"""
+ df2.to_csv(temp_file, header=None, index=None)
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected2
+
+ def test_to_csv_default_encoding(self, temp_file):
+ # GH17097
+ df = DataFrame({"col": ["AAAAA", "ÄÄÄÄÄ", "ßßßßß", "聞聞聞聞聞"]})
+
+ # the default to_csv encoding is uft-8.
+ df.to_csv(temp_file)
+ tm.assert_frame_equal(pd.read_csv(temp_file, index_col=0), df)
+
+ def test_to_csv_quotechar(self, temp_file):
+ df = DataFrame({"col": [1, 2]})
+ expected = """\
+"","col"
+"0","1"
+"1","2"
+"""
+
+ df.to_csv(temp_file, quoting=1) # 1=QUOTE_ALL
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected
+
+ expected = """\
+$$,$col$
+$0$,$1$
+$1$,$2$
+"""
+
+ df.to_csv(temp_file, quoting=1, quotechar="$")
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected
+
+ with pytest.raises(TypeError, match="quotechar"):
+ df.to_csv(temp_file, quoting=1, quotechar=None)
+
+ def test_to_csv_doublequote(self, temp_file):
+ df = DataFrame({"col": ['a"a', '"bb"']})
+ expected = '''\
+"","col"
+"0","a""a"
+"1","""bb"""
+'''
+
+ df.to_csv(temp_file, quoting=1, doublequote=True) # QUOTE_ALL
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected
+
+ with pytest.raises(Error, match="escapechar"):
+ df.to_csv(temp_file, doublequote=False) # no escapechar set
+
+ def test_to_csv_escapechar(self, temp_file):
+ df = DataFrame({"col": ['a"a', '"bb"']})
+ expected = """\
+"","col"
+"0","a\\"a"
+"1","\\"bb\\""
+"""
+
+ df.to_csv(temp_file, quoting=1, doublequote=False, escapechar="\\")
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected
+
+ df = DataFrame({"col": ["a,a", ",bb,"]})
+ expected = """\
+,col
+0,a\\,a
+1,\\,bb\\,
+"""
+
+ df.to_csv(temp_file, quoting=3, escapechar="\\") # QUOTE_NONE
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected
+
+ def test_csv_to_string(self):
+ df = DataFrame({"col": [1, 2]})
+ expected_rows = [",col", "0,1", "1,2"]
+ expected = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df.to_csv() == expected
+
+ def test_to_csv_decimal(self):
+ # see gh-781
+ df = DataFrame({"col1": [1], "col2": ["a"], "col3": [10.1]})
+
+ expected_rows = [",col1,col2,col3", "0,1,a,10.1"]
+ expected_default = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df.to_csv() == expected_default
+
+ expected_rows = [";col1;col2;col3", "0;1;a;10,1"]
+ expected_european_excel = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df.to_csv(decimal=",", sep=";") == expected_european_excel
+
+ expected_rows = [",col1,col2,col3", "0,1,a,10.10"]
+ expected_float_format_default = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df.to_csv(float_format="%.2f") == expected_float_format_default
+
+ expected_rows = [";col1;col2;col3", "0;1;a;10,10"]
+ expected_float_format = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert (
+ df.to_csv(decimal=",", sep=";", float_format="%.2f")
+ == expected_float_format
+ )
+
+ # see gh-11553: testing if decimal is taken into account for '0.0'
+ df = DataFrame({"a": [0, 1.1], "b": [2.2, 3.3], "c": 1})
+
+ expected_rows = ["a,b,c", "0^0,2^2,1", "1^1,3^3,1"]
+ expected = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df.to_csv(index=False, decimal="^") == expected
+
+ # same but for an index
+ assert df.set_index("a").to_csv(decimal="^") == expected
+
+ # same for a multi-index
+ assert df.set_index(["a", "b"]).to_csv(decimal="^") == expected
+
+ def test_to_csv_float_format(self):
+ # testing if float_format is taken into account for the index
+ # GH 11553
+ df = DataFrame({"a": [0, 1], "b": [2.2, 3.3], "c": 1})
+
+ expected_rows = ["a,b,c", "0,2.20,1", "1,3.30,1"]
+ expected = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df.set_index("a").to_csv(float_format="%.2f") == expected
+
+ # same for a multi-index
+ assert df.set_index(["a", "b"]).to_csv(float_format="%.2f") == expected
+
+ def test_to_csv_na_rep(self):
+ # see gh-11553
+ #
+ # Testing if NaN values are correctly represented in the index.
+ df = DataFrame({"a": [0, np.nan], "b": [0, 1], "c": [2, 3]})
+ expected_rows = ["a,b,c", "0.0,0,2", "_,1,3"]
+ expected = tm.convert_rows_list_to_csv_str(expected_rows)
+
+ assert df.set_index("a").to_csv(na_rep="_") == expected
+ assert df.set_index(["a", "b"]).to_csv(na_rep="_") == expected
+
+ # now with an index containing only NaNs
+ df = DataFrame({"a": np.nan, "b": [0, 1], "c": [2, 3]})
+ expected_rows = ["a,b,c", "_,0,2", "_,1,3"]
+ expected = tm.convert_rows_list_to_csv_str(expected_rows)
+
+ assert df.set_index("a").to_csv(na_rep="_") == expected
+ assert df.set_index(["a", "b"]).to_csv(na_rep="_") == expected
+
+ # check if na_rep parameter does not break anything when no NaN
+ df = DataFrame({"a": 0, "b": [0, 1], "c": [2, 3]})
+ expected_rows = ["a,b,c", "0,0,2", "0,1,3"]
+ expected = tm.convert_rows_list_to_csv_str(expected_rows)
+
+ assert df.set_index("a").to_csv(na_rep="_") == expected
+ assert df.set_index(["a", "b"]).to_csv(na_rep="_") == expected
+
+ csv = pd.Series(["a", pd.NA, "c"]).to_csv(na_rep="ZZZZZ")
+ expected = tm.convert_rows_list_to_csv_str([",0", "0,a", "1,ZZZZZ", "2,c"])
+ assert expected == csv
+
+ def test_to_csv_na_rep_nullable_string(self, nullable_string_dtype):
+ # GH 29975
+ # Make sure full na_rep shows up when a dtype is provided
+ expected = tm.convert_rows_list_to_csv_str([",0", "0,a", "1,ZZZZZ", "2,c"])
+ csv = pd.Series(["a", pd.NA, "c"], dtype=nullable_string_dtype).to_csv(
+ na_rep="ZZZZZ"
+ )
+ assert expected == csv
+
+ def test_to_csv_date_format(self):
+ # GH 10209
+ df_sec = DataFrame({"A": pd.date_range("20130101", periods=5, freq="s")})
+ df_day = DataFrame({"A": pd.date_range("20130101", periods=5, freq="D")})
+
+ expected_rows = [
+ ",A",
+ "0,2013-01-01 00:00:00",
+ "1,2013-01-01 00:00:01",
+ "2,2013-01-01 00:00:02",
+ "3,2013-01-01 00:00:03",
+ "4,2013-01-01 00:00:04",
+ ]
+ expected_default_sec = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df_sec.to_csv() == expected_default_sec
+
+ expected_rows = [
+ ",A",
+ "0,2013-01-01 00:00:00",
+ "1,2013-01-02 00:00:00",
+ "2,2013-01-03 00:00:00",
+ "3,2013-01-04 00:00:00",
+ "4,2013-01-05 00:00:00",
+ ]
+ expected_ymdhms_day = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df_day.to_csv(date_format="%Y-%m-%d %H:%M:%S") == expected_ymdhms_day
+
+ expected_rows = [
+ ",A",
+ "0,2013-01-01",
+ "1,2013-01-01",
+ "2,2013-01-01",
+ "3,2013-01-01",
+ "4,2013-01-01",
+ ]
+ expected_ymd_sec = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df_sec.to_csv(date_format="%Y-%m-%d") == expected_ymd_sec
+
+ expected_rows = [
+ ",A",
+ "0,2013-01-01",
+ "1,2013-01-02",
+ "2,2013-01-03",
+ "3,2013-01-04",
+ "4,2013-01-05",
+ ]
+ expected_default_day = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df_day.to_csv() == expected_default_day
+ assert df_day.to_csv(date_format="%Y-%m-%d") == expected_default_day
+
+ # see gh-7791
+ #
+ # Testing if date_format parameter is taken into account
+ # for multi-indexed DataFrames.
+ df_sec["B"] = 0
+ df_sec["C"] = 1
+
+ expected_rows = ["A,B,C", "2013-01-01,0,1.0"]
+ expected_ymd_sec = tm.convert_rows_list_to_csv_str(expected_rows)
+
+ df_sec_grouped = df_sec.groupby([pd.Grouper(key="A", freq="1h"), "B"])
+ assert df_sec_grouped.mean().to_csv(date_format="%Y-%m-%d") == expected_ymd_sec
+
+ def test_to_csv_different_datetime_formats(self):
+ # GH#21734
+ df = DataFrame(
+ {
+ "date": pd.to_datetime("1970-01-01"),
+ "datetime": pd.date_range("1970-01-01", periods=2, freq="h"),
+ }
+ )
+ expected_rows = [
+ "date,datetime",
+ "1970-01-01,1970-01-01 00:00:00",
+ "1970-01-01,1970-01-01 01:00:00",
+ ]
+ expected = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert df.to_csv(index=False) == expected
+
+ def test_to_csv_date_format_in_categorical(self):
+ # GH#40754
+ ser = pd.Series(pd.to_datetime(["2021-03-27", pd.NaT], format="%Y-%m-%d"))
+ ser = ser.astype("category")
+ expected = tm.convert_rows_list_to_csv_str(["0", "2021-03-27", '""'])
+ assert ser.to_csv(index=False) == expected
+
+ ser = pd.Series(
+ pd.date_range(
+ start="2021-03-27", freq="D", periods=1, tz="Europe/Berlin"
+ ).append(pd.DatetimeIndex([pd.NaT]))
+ )
+ ser = ser.astype("category")
+ assert ser.to_csv(index=False, date_format="%Y-%m-%d") == expected
+
+ def test_to_csv_float_ea_float_format(self):
+ # GH#45991
+ df = DataFrame({"a": [1.1, 2.02, pd.NA, 6.000006], "b": "c"})
+ df["a"] = df["a"].astype("Float64")
+ result = df.to_csv(index=False, float_format="%.5f")
+ expected = tm.convert_rows_list_to_csv_str(
+ ["a,b", "1.10000,c", "2.02000,c", ",c", "6.00001,c"]
+ )
+ assert result == expected
+
+ def test_to_csv_float_ea_no_float_format(self):
+ # GH#45991
+ df = DataFrame({"a": [1.1, 2.02, pd.NA, 6.000006], "b": "c"})
+ df["a"] = df["a"].astype("Float64")
+ result = df.to_csv(index=False)
+ expected = tm.convert_rows_list_to_csv_str(
+ ["a,b", "1.1,c", "2.02,c", ",c", "6.000006,c"]
+ )
+ assert result == expected
+
+ def test_to_csv_multi_index(self):
+ # see gh-6618
+ df = DataFrame([1], columns=pd.MultiIndex.from_arrays([[1], [2]]))
+
+ exp_rows = [",1", ",2", "0,1"]
+ exp = tm.convert_rows_list_to_csv_str(exp_rows)
+ assert df.to_csv() == exp
+
+ exp_rows = ["1", "2", "1"]
+ exp = tm.convert_rows_list_to_csv_str(exp_rows)
+ assert df.to_csv(index=False) == exp
+
+ df = DataFrame(
+ [1],
+ columns=pd.MultiIndex.from_arrays([[1], [2]]),
+ index=pd.MultiIndex.from_arrays([[1], [2]]),
+ )
+
+ exp_rows = [",,1", ",,2", "1,2,1"]
+ exp = tm.convert_rows_list_to_csv_str(exp_rows)
+ assert df.to_csv() == exp
+
+ exp_rows = ["1", "2", "1"]
+ exp = tm.convert_rows_list_to_csv_str(exp_rows)
+ assert df.to_csv(index=False) == exp
+
+ df = DataFrame([1], columns=pd.MultiIndex.from_arrays([["foo"], ["bar"]]))
+
+ exp_rows = [",foo", ",bar", "0,1"]
+ exp = tm.convert_rows_list_to_csv_str(exp_rows)
+ assert df.to_csv() == exp
+
+ exp_rows = ["foo", "bar", "1"]
+ exp = tm.convert_rows_list_to_csv_str(exp_rows)
+ assert df.to_csv(index=False) == exp
+
+ @pytest.mark.parametrize(
+ "ind,expected",
+ [
+ (
+ pd.MultiIndex(levels=[[1.0]], codes=[[0]], names=["x"]),
+ "x,data\n1.0,1\n",
+ ),
+ (
+ pd.MultiIndex(
+ levels=[[1.0], [2.0]], codes=[[0], [0]], names=["x", "y"]
+ ),
+ "x,y,data\n1.0,2.0,1\n",
+ ),
+ ],
+ )
+ def test_to_csv_single_level_multi_index(self, ind, expected, frame_or_series):
+ # see gh-19589
+ obj = frame_or_series(pd.Series([1], ind, name="data"))
+
+ result = obj.to_csv(lineterminator="\n", header=True)
+ assert result == expected
+
+ def test_to_csv_string_array_ascii(self, temp_file):
+ # GH 10813
+ str_array = [{"names": ["foo", "bar"]}, {"names": ["baz", "qux"]}]
+ df = DataFrame(str_array)
+ expected_ascii = """\
+,names
+0,"['foo', 'bar']"
+1,"['baz', 'qux']"
+"""
+ df.to_csv(temp_file, encoding="ascii")
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected_ascii
+
+ def test_to_csv_string_array_utf8(self, temp_file):
+ # GH 10813
+ str_array = [{"names": ["foo", "bar"]}, {"names": ["baz", "qux"]}]
+ df = DataFrame(str_array)
+ expected_utf8 = """\
+,names
+0,"['foo', 'bar']"
+1,"['baz', 'qux']"
+"""
+ df.to_csv(temp_file, encoding="utf-8")
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected_utf8
+
+ def test_to_csv_string_with_lf(self, temp_file):
+ # GH 20353
+ data = {"int": [1, 2, 3], "str_lf": ["abc", "d\nef", "g\nh\n\ni"]}
+ df = DataFrame(data)
+
+ # case 1: The default line terminator(=os.linesep)(PR 21406)
+ os_linesep = os.linesep.encode("utf-8")
+ expected_noarg = (
+ b"int,str_lf"
+ + os_linesep
+ + b"1,abc"
+ + os_linesep
+ + b'2,"d\nef"'
+ + os_linesep
+ + b'3,"g\nh\n\ni"'
+ + os_linesep
+ )
+ df.to_csv(temp_file, index=False)
+ with open(temp_file, "rb") as f:
+ assert f.read() == expected_noarg
+
+ # case 2: LF as line terminator
+ expected_lf = b'int,str_lf\n1,abc\n2,"d\nef"\n3,"g\nh\n\ni"\n'
+ df.to_csv(temp_file, lineterminator="\n", index=False)
+ with open(temp_file, "rb") as f:
+ assert f.read() == expected_lf
+
+ # case 3: CRLF as line terminator
+ # 'lineterminator' should not change inner element
+ expected_crlf = b'int,str_lf\r\n1,abc\r\n2,"d\nef"\r\n3,"g\nh\n\ni"\r\n'
+ df.to_csv(temp_file, lineterminator="\r\n", index=False)
+ with open(temp_file, "rb") as f:
+ assert f.read() == expected_crlf
+
+ def test_to_csv_string_with_crlf(self, temp_file):
+ # GH 20353
+ data = {"int": [1, 2, 3], "str_crlf": ["abc", "d\r\nef", "g\r\nh\r\n\r\ni"]}
+ df = DataFrame(data)
+ # case 1: The default line terminator(=os.linesep)(PR 21406)
+ os_linesep = os.linesep.encode("utf-8")
+ expected_noarg = (
+ b"int,str_crlf"
+ + os_linesep
+ + b"1,abc"
+ + os_linesep
+ + b'2,"d\r\nef"'
+ + os_linesep
+ + b'3,"g\r\nh\r\n\r\ni"'
+ + os_linesep
+ )
+ df.to_csv(temp_file, index=False)
+ with open(temp_file, "rb") as f:
+ assert f.read() == expected_noarg
+
+ # case 2: LF as line terminator
+ expected_lf = b'int,str_crlf\n1,abc\n2,"d\r\nef"\n3,"g\r\nh\r\n\r\ni"\n'
+ df.to_csv(temp_file, lineterminator="\n", index=False)
+ with open(temp_file, "rb") as f:
+ assert f.read() == expected_lf
+
+ # case 3: CRLF as line terminator
+ # 'lineterminator' should not change inner element
+ expected_crlf = (
+ b'int,str_crlf\r\n1,abc\r\n2,"d\r\nef"\r\n3,"g\r\nh\r\n\r\ni"\r\n'
+ )
+ df.to_csv(temp_file, lineterminator="\r\n", index=False)
+ with open(temp_file, "rb") as f:
+ assert f.read() == expected_crlf
+
+ def test_to_csv_stdout_file(self, capsys):
+ # GH 21561
+ df = DataFrame([["foo", "bar"], ["baz", "qux"]], columns=["name_1", "name_2"])
+ expected_rows = [",name_1,name_2", "0,foo,bar", "1,baz,qux"]
+ expected_ascii = tm.convert_rows_list_to_csv_str(expected_rows)
+
+ df.to_csv(sys.stdout, encoding="ascii")
+ captured = capsys.readouterr()
+
+ assert captured.out == expected_ascii
+ assert not sys.stdout.closed
+
+ @pytest.mark.xfail(
+ compat.is_platform_windows(),
+ reason=(
+ "Especially in Windows, file stream should not be passed"
+ "to csv writer without newline='' option."
+ "(https://docs.python.org/3/library/csv.html#csv.writer)"
+ ),
+ )
+ def test_to_csv_write_to_open_file(self, temp_file):
+ # GH 21696
+ df = DataFrame({"a": ["x", "y", "z"]})
+ expected = """\
+manual header
+x
+y
+z
+"""
+ with open(temp_file, "w", encoding="utf-8") as f:
+ f.write("manual header\n")
+ df.to_csv(f, header=None, index=None)
+ with open(temp_file, encoding="utf-8") as f:
+ assert f.read() == expected
+
+ def test_to_csv_write_to_open_file_with_newline_py3(self, temp_file):
+ # see gh-21696
+ # see gh-20353
+ df = DataFrame({"a": ["x", "y", "z"]})
+ expected_rows = ["x", "y", "z"]
+ expected = "manual header\n" + tm.convert_rows_list_to_csv_str(expected_rows)
+
+ with open(temp_file, "w", newline="", encoding="utf-8") as f:
+ f.write("manual header\n")
+ df.to_csv(f, header=None, index=None)
+
+ with open(temp_file, "rb") as f:
+ assert f.read() == bytes(expected, "utf-8")
+
+ @pytest.mark.parametrize("to_infer", [True, False])
+ @pytest.mark.parametrize("read_infer", [True, False])
+ def test_to_csv_compression(
+ self,
+ compression_only,
+ read_infer,
+ to_infer,
+ compression_to_extension,
+ temp_file,
+ ):
+ # see gh-15008
+ compression = compression_only
+
+ df = DataFrame({"A": [1]})
+
+ to_compression = "infer" if to_infer else compression
+ read_compression = "infer" if read_infer else compression
+
+ path_ext = str(temp_file) + "." + compression_to_extension[compression]
+ df.to_csv(path_ext, compression=to_compression)
+ result = pd.read_csv(path_ext, index_col=0, compression=read_compression)
+ tm.assert_frame_equal(result, df)
+
+ def test_to_csv_compression_dict(self, compression_only, temp_file):
+ # GH 26023
+ method = compression_only
+ df = DataFrame({"ABC": [1]})
+ extension = {
+ "gzip": "gz",
+ "zstd": "zst",
+ }.get(method, method)
+
+ path = str(temp_file) + "." + extension
+ df.to_csv(path, compression={"method": method})
+ read_df = pd.read_csv(path, index_col=0)
+ tm.assert_frame_equal(read_df, df)
+
+ def test_to_csv_compression_dict_no_method_raises(self, temp_file):
+ # GH 26023
+ df = DataFrame({"ABC": [1]})
+ compression = {"some_option": True}
+ msg = "must have key 'method'"
+
+ with pytest.raises(ValueError, match=msg):
+ df.to_csv(temp_file, compression=compression)
+
+ @pytest.mark.parametrize("compression", ["zip", "infer"])
+ @pytest.mark.parametrize("archive_name", ["test_to_csv.csv", "test_to_csv.zip"])
+ def test_to_csv_zip_arguments(self, compression, archive_name, temp_file):
+ # GH 26023
+ df = DataFrame({"ABC": [1]})
+
+ path = str(temp_file) + ".zip"
+ df.to_csv(
+ path, compression={"method": compression, "archive_name": archive_name}
+ )
+ with ZipFile(path) as zp:
+ assert len(zp.filelist) == 1
+ archived_file = zp.filelist[0].filename
+ assert archived_file == archive_name
+
+ @pytest.mark.parametrize(
+ "filename,expected_arcname",
+ [
+ ("archive.csv", "archive.csv"),
+ ("archive.tsv", "archive.tsv"),
+ ("archive.csv.zip", "archive.csv"),
+ ("archive.tsv.zip", "archive.tsv"),
+ ("archive.zip", "archive"),
+ ],
+ )
+ def test_to_csv_zip_infer_name(self, tmp_path, filename, expected_arcname):
+ # GH 39465
+ df = DataFrame({"ABC": [1]})
+ path = tmp_path / filename
+ df.to_csv(path, compression="zip")
+ with ZipFile(path) as zp:
+ assert len(zp.filelist) == 1
+ archived_file = zp.filelist[0].filename
+ assert archived_file == expected_arcname
+
+ @pytest.mark.parametrize("df_new_type", ["Int64"])
+ def test_to_csv_na_rep_long_string(self, df_new_type):
+ # see gh-25099
+ df = DataFrame({"c": [pd.NA] * 3})
+ df = df.astype(df_new_type)
+ expected_rows = ["c", "mynull", "mynull", "mynull"]
+ expected = tm.convert_rows_list_to_csv_str(expected_rows)
+
+ result = df.to_csv(index=False, na_rep="mynull", encoding="ascii")
+
+ assert expected == result
+
+ def test_to_csv_timedelta_precision(self):
+ # GH 6783
+ s = pd.Series([1, 1]).astype("timedelta64[ns]")
+ buf = io.StringIO()
+ s.to_csv(buf)
+ result = buf.getvalue()
+ expected_rows = [
+ ",0",
+ "0,0 days 00:00:00.000000001",
+ "1,0 days 00:00:00.000000001",
+ ]
+ expected = tm.convert_rows_list_to_csv_str(expected_rows)
+ assert result == expected
+
+ def test_na_rep_truncated(self):
+ # https://github.com/pandas-dev/pandas/issues/31447
+ result = pd.Series(range(8, 12)).to_csv(na_rep="-")
+ expected = tm.convert_rows_list_to_csv_str([",0", "0,8", "1,9", "2,10", "3,11"])
+ assert result == expected
+
+ result = pd.Series([True, False]).to_csv(na_rep="nan")
+ expected = tm.convert_rows_list_to_csv_str([",0", "0,True", "1,False"])
+ assert result == expected
+
+ result = pd.Series([1.1, 2.2]).to_csv(na_rep=".")
+ expected = tm.convert_rows_list_to_csv_str([",0", "0,1.1", "1,2.2"])
+ assert result == expected
+
+ @pytest.mark.parametrize("errors", ["surrogatepass", "ignore", "replace"])
+ def test_to_csv_errors(self, errors, temp_file):
+ # GH 22610
+ data = ["\ud800foo"]
+ ser = pd.Series(data, index=Index(data, dtype=object), dtype=object)
+
+ ser.to_csv(temp_file, errors=errors)
+ # No use in reading back the data as it is not the same anymore
+ # due to the error handling
+
+ @pytest.mark.parametrize("mode", ["wb", "w"])
+ def test_to_csv_binary_handle(self, mode, temp_file):
+ """
+ Binary file objects should work (if 'mode' contains a 'b') or even without
+ it in most cases.
+
+ GH 35058 and GH 19827
+ """
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ with open(temp_file, mode="w+b") as handle:
+ df.to_csv(handle, mode=mode)
+ tm.assert_frame_equal(df, pd.read_csv(temp_file, index_col=0))
+
+ @pytest.mark.parametrize("mode", ["wb", "w"])
+ def test_to_csv_encoding_binary_handle(self, mode, temp_file):
+ """
+ Binary file objects should honor a specified encoding.
+
+ GH 23854 and GH 13068 with binary handles
+ """
+ # example from GH 23854
+ content = "a, b, 🐟".encode("utf-8-sig")
+ buffer = io.BytesIO(content)
+ df = pd.read_csv(buffer, encoding="utf-8-sig")
+
+ buffer = io.BytesIO()
+ df.to_csv(buffer, mode=mode, encoding="utf-8-sig", index=False)
+ buffer.seek(0) # tests whether file handle wasn't closed
+ assert buffer.getvalue().startswith(content)
+
+ # example from GH 13068
+ with open(temp_file, "w+b") as handle:
+ DataFrame().to_csv(handle, mode=mode, encoding="utf-8-sig")
+
+ handle.seek(0)
+ assert handle.read().startswith(b'\xef\xbb\xbf""')
+
+
+def test_to_csv_iterative_compression_name(compression, temp_file):
+ # GH 38714
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ df.to_csv(temp_file, compression=compression, chunksize=1)
+ tm.assert_frame_equal(
+ pd.read_csv(temp_file, compression=compression, index_col=0), df
+ )
+
+
+def test_to_csv_iterative_compression_buffer(compression):
+ # GH 38714
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ with io.BytesIO() as buffer:
+ df.to_csv(buffer, compression=compression, chunksize=1)
+ buffer.seek(0)
+ tm.assert_frame_equal(
+ pd.read_csv(buffer, compression=compression, index_col=0), df
+ )
+ assert not buffer.closed
+
+
+def test_new_style_float_format_basic():
+ df = DataFrame({"A": [1234.56789, 9876.54321]})
+ result = df.to_csv(float_format="{:.2f}", lineterminator="\n")
+ expected = ",A\n0,1234.57\n1,9876.54\n"
+ assert result == expected
+
+
+def test_new_style_float_format_thousands():
+ df = DataFrame({"A": [1234.56789, 9876.54321]})
+ result = df.to_csv(float_format="{:,.2f}", lineterminator="\n")
+ expected = ',A\n0,"1,234.57"\n1,"9,876.54"\n'
+ assert result == expected
+
+
+def test_new_style_scientific_format():
+ df = DataFrame({"A": [0.000123, 0.000456]})
+ result = df.to_csv(float_format="{:.2e}", lineterminator="\n")
+ expected = ",A\n0,1.23e-04\n1,4.56e-04\n"
+ assert result == expected
+
+
+def test_new_style_with_nan():
+ df = DataFrame({"A": [1.23, np.nan, 4.56]})
+ result = df.to_csv(float_format="{:.2f}", na_rep="NA", lineterminator="\n")
+ expected = ",A\n0,1.23\n1,NA\n2,4.56\n"
+ assert result == expected
+
+
+def test_new_style_with_mixed_types():
+ df = DataFrame({"A": [1.23, 4.56], "B": ["x", "y"]})
+ result = df.to_csv(float_format="{:.2f}", lineterminator="\n")
+ expected = ",A,B\n0,1.23,x\n1,4.56,y\n"
+ assert result == expected
+
+
+def test_new_style_with_mixed_types_in_column():
+ df = DataFrame({"A": [1.23, "text", 4.56]})
+ result = df.to_csv(float_format="{:.2f}", lineterminator="\n")
+ expected = ",A\n0,1.23\n1,text\n2,4.56\n"
+ assert result == expected
+
+
+def test_invalid_new_style_format_missing_brace():
+ df = DataFrame({"A": [1.23]})
+ with pytest.raises(ValueError, match="Invalid new-style format string '{:.2f"):
+ df.to_csv(float_format="{:.2f")
+
+
+def test_invalid_new_style_format_specifier():
+ df = DataFrame({"A": [1.23]})
+ with pytest.raises(ValueError, match="Invalid new-style format string '{:.2z}'"):
+ df.to_csv(float_format="{:.2z}")
+
+
+def test_old_style_format_compatibility():
+ df = DataFrame({"A": [1234.56789, 9876.54321]})
+ result = df.to_csv(float_format="%.2f", lineterminator="\n")
+ expected = ",A\n0,1234.57\n1,9876.54\n"
+ assert result == expected
+
+
+def test_callable_float_format_compatibility():
+ df = DataFrame({"A": [1234.56789, 9876.54321]})
+ result = df.to_csv(float_format=lambda x: f"{x:,.2f}", lineterminator="\n")
+ expected = ',A\n0,"1,234.57"\n1,"9,876.54"\n'
+ assert result == expected
+
+
+def test_no_float_format():
+ df = DataFrame({"A": [1.23, 4.56]})
+ result = df.to_csv(float_format=None, lineterminator="\n")
+ expected = ",A\n0,1.23\n1,4.56\n"
+ assert result == expected
+
+
+def test_large_numbers():
+ df = DataFrame({"A": [1e308, 2e308]})
+ result = df.to_csv(float_format="{:.2e}", lineterminator="\n")
+ expected = ",A\n0,1.00e+308\n1,inf\n"
+ assert result == expected
+
+
+def test_zero_and_negative():
+ df = DataFrame({"A": [0.0, -1.23456]})
+ result = df.to_csv(float_format="{:+.2f}", lineterminator="\n")
+ expected = ",A\n0,+0.00\n1,-1.23\n"
+ assert result == expected
+
+
+def test_unicode_format():
+ df = DataFrame({"A": [1.23, 4.56]})
+ result = df.to_csv(float_format="{:.2f}€", encoding="utf-8", lineterminator="\n")
+ expected = ",A\n0,1.23€\n1,4.56€\n"
+ assert result == expected
+
+
+def test_empty_dataframe():
+ df = DataFrame({"A": []})
+ result = df.to_csv(float_format="{:.2f}", lineterminator="\n")
+ expected = ",A\n"
+ assert result == expected
+
+
+def test_multi_column_float():
+ df = DataFrame({"A": [1.23, 4.56], "B": [7.89, 0.12]})
+ result = df.to_csv(float_format="{:.2f}", lineterminator="\n")
+ expected = ",A,B\n0,1.23,7.89\n1,4.56,0.12\n"
+ assert result == expected
+
+
+def test_invalid_float_format_type():
+ df = DataFrame({"A": [1.23]})
+ with pytest.raises(ValueError, match="float_format must be a string or callable"):
+ df.to_csv(float_format=123)
+
+
+def test_new_style_with_inf():
+ df = DataFrame({"A": [1.23, np.inf, -np.inf]})
+ result = df.to_csv(float_format="{:.2f}", na_rep="NA", lineterminator="\n")
+ expected = ",A\n0,1.23\n1,inf\n2,-inf\n"
+ assert result == expected
+
+
+def test_new_style_with_precision_edge():
+ df = DataFrame({"A": [1.23456789]})
+ result = df.to_csv(float_format="{:.10f}", lineterminator="\n")
+ expected = ",A\n0,1.2345678900\n"
+ assert result == expected
+
+
+def test_new_style_with_template():
+ df = DataFrame({"A": [1234.56789]})
+ result = df.to_csv(float_format="Value: {:,.2f}", lineterminator="\n")
+ expected = ',A\n0,"Value: 1,234.57"\n'
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_excel.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_excel.py
new file mode 100644
index 0000000000000000000000000000000000000000..babbfcb46a6fc3969ee48372da5d1ab528d89d91
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_excel.py
@@ -0,0 +1,473 @@
+"""Tests formatting as writer-agnostic ExcelCells
+
+ExcelFormatter is tested implicitly in pandas/tests/io/excel
+"""
+
+import string
+
+import pytest
+
+from pandas.errors import CSSWarning
+
+import pandas._testing as tm
+
+from pandas.io.formats.excel import (
+ CssExcelCell,
+ CSSToExcelConverter,
+)
+
+
+@pytest.mark.parametrize(
+ "css,expected",
+ [
+ # FONT
+ # - name
+ ("font-family: foo,bar", {"font": {"name": "foo"}}),
+ ('font-family: "foo bar",baz', {"font": {"name": "foo bar"}}),
+ ("font-family: foo,\nbar", {"font": {"name": "foo"}}),
+ ("font-family: foo, bar, baz", {"font": {"name": "foo"}}),
+ ("font-family: bar, foo", {"font": {"name": "bar"}}),
+ ("font-family: 'foo bar', baz", {"font": {"name": "foo bar"}}),
+ ("font-family: 'foo \\'bar', baz", {"font": {"name": "foo 'bar"}}),
+ ('font-family: "foo \\"bar", baz', {"font": {"name": 'foo "bar'}}),
+ ('font-family: "foo ,bar", baz', {"font": {"name": "foo ,bar"}}),
+ # - family
+ ("font-family: serif", {"font": {"name": "serif", "family": 1}}),
+ ("font-family: Serif", {"font": {"name": "serif", "family": 1}}),
+ ("font-family: roman, serif", {"font": {"name": "roman", "family": 1}}),
+ ("font-family: roman, sans-serif", {"font": {"name": "roman", "family": 2}}),
+ ("font-family: roman, sans serif", {"font": {"name": "roman"}}),
+ ("font-family: roman, sansserif", {"font": {"name": "roman"}}),
+ ("font-family: roman, cursive", {"font": {"name": "roman", "family": 4}}),
+ ("font-family: roman, fantasy", {"font": {"name": "roman", "family": 5}}),
+ # - size
+ ("font-size: 1em", {"font": {"size": 12}}),
+ ("font-size: xx-small", {"font": {"size": 6}}),
+ ("font-size: x-small", {"font": {"size": 7.5}}),
+ ("font-size: small", {"font": {"size": 9.6}}),
+ ("font-size: medium", {"font": {"size": 12}}),
+ ("font-size: large", {"font": {"size": 13.5}}),
+ ("font-size: x-large", {"font": {"size": 18}}),
+ ("font-size: xx-large", {"font": {"size": 24}}),
+ ("font-size: 50%", {"font": {"size": 6}}),
+ # - bold
+ ("font-weight: 100", {"font": {"bold": False}}),
+ ("font-weight: 200", {"font": {"bold": False}}),
+ ("font-weight: 300", {"font": {"bold": False}}),
+ ("font-weight: 400", {"font": {"bold": False}}),
+ ("font-weight: normal", {"font": {"bold": False}}),
+ ("font-weight: lighter", {"font": {"bold": False}}),
+ ("font-weight: bold", {"font": {"bold": True}}),
+ ("font-weight: bolder", {"font": {"bold": True}}),
+ ("font-weight: 700", {"font": {"bold": True}}),
+ ("font-weight: 800", {"font": {"bold": True}}),
+ ("font-weight: 900", {"font": {"bold": True}}),
+ # - italic
+ ("font-style: italic", {"font": {"italic": True}}),
+ ("font-style: oblique", {"font": {"italic": True}}),
+ # - underline
+ ("text-decoration: underline", {"font": {"underline": "single"}}),
+ ("text-decoration: overline", {}),
+ ("text-decoration: none", {}),
+ # - strike
+ ("text-decoration: line-through", {"font": {"strike": True}}),
+ (
+ "text-decoration: underline line-through",
+ {"font": {"strike": True, "underline": "single"}},
+ ),
+ (
+ "text-decoration: underline; text-decoration: line-through",
+ {"font": {"strike": True}},
+ ),
+ # - color
+ ("color: red", {"font": {"color": "FF0000"}}),
+ ("color: #ff0000", {"font": {"color": "FF0000"}}),
+ ("color: #f0a", {"font": {"color": "FF00AA"}}),
+ # - shadow
+ ("text-shadow: none", {"font": {"shadow": False}}),
+ ("text-shadow: 0px -0em 0px #CCC", {"font": {"shadow": False}}),
+ ("text-shadow: 0px -0em 0px #999", {"font": {"shadow": False}}),
+ ("text-shadow: 0px -0em 0px", {"font": {"shadow": False}}),
+ ("text-shadow: 2px -0em 0px #CCC", {"font": {"shadow": True}}),
+ ("text-shadow: 0px -2em 0px #CCC", {"font": {"shadow": True}}),
+ ("text-shadow: 0px -0em 2px #CCC", {"font": {"shadow": True}}),
+ ("text-shadow: 0px -0em 2px", {"font": {"shadow": True}}),
+ ("text-shadow: 0px -2em", {"font": {"shadow": True}}),
+ # FILL
+ # - color, fillType
+ (
+ "background-color: red",
+ {"fill": {"fgColor": "FF0000", "patternType": "solid"}},
+ ),
+ (
+ "background-color: #ff0000",
+ {"fill": {"fgColor": "FF0000", "patternType": "solid"}},
+ ),
+ (
+ "background-color: #f0a",
+ {"fill": {"fgColor": "FF00AA", "patternType": "solid"}},
+ ),
+ # BORDER
+ # - style
+ (
+ "border-style: solid",
+ {
+ "border": {
+ "top": {"style": "medium"},
+ "bottom": {"style": "medium"},
+ "left": {"style": "medium"},
+ "right": {"style": "medium"},
+ }
+ },
+ ),
+ (
+ "border-style: solid; border-width: thin",
+ {
+ "border": {
+ "top": {"style": "thin"},
+ "bottom": {"style": "thin"},
+ "left": {"style": "thin"},
+ "right": {"style": "thin"},
+ }
+ },
+ ),
+ (
+ "border-top-style: solid; border-top-width: thin",
+ {"border": {"top": {"style": "thin"}}},
+ ),
+ (
+ "border-top-style: solid; border-top-width: 1pt",
+ {"border": {"top": {"style": "thin"}}},
+ ),
+ ("border-top-style: solid", {"border": {"top": {"style": "medium"}}}),
+ (
+ "border-top-style: solid; border-top-width: medium",
+ {"border": {"top": {"style": "medium"}}},
+ ),
+ (
+ "border-top-style: solid; border-top-width: 2pt",
+ {"border": {"top": {"style": "medium"}}},
+ ),
+ (
+ "border-top-style: solid; border-top-width: thick",
+ {"border": {"top": {"style": "thick"}}},
+ ),
+ (
+ "border-top-style: solid; border-top-width: 4pt",
+ {"border": {"top": {"style": "thick"}}},
+ ),
+ (
+ "border-top-style: solid; border-top-width: none",
+ {"border": {"top": {"style": "none"}}},
+ ),
+ (
+ "border-top-style: solid; border-top-width: 0.000001pt",
+ {"border": {"top": {"style": "none"}}},
+ ),
+ (
+ "border-top-style: dotted",
+ {"border": {"top": {"style": "mediumDashDotDot"}}},
+ ),
+ (
+ "border-top-style: dotted; border-top-width: thin",
+ {"border": {"top": {"style": "dotted"}}},
+ ),
+ ("border-top-style: dashed", {"border": {"top": {"style": "mediumDashed"}}}),
+ (
+ "border-top-style: dashed; border-top-width: thin",
+ {"border": {"top": {"style": "dashed"}}},
+ ),
+ ("border-top-style: double", {"border": {"top": {"style": "double"}}}),
+ # - color
+ (
+ "border-style: solid; border-color: #0000ff",
+ {
+ "border": {
+ "top": {"style": "medium", "color": "0000FF"},
+ "right": {"style": "medium", "color": "0000FF"},
+ "bottom": {"style": "medium", "color": "0000FF"},
+ "left": {"style": "medium", "color": "0000FF"},
+ }
+ },
+ ),
+ (
+ "border-top-style: double; border-top-color: blue",
+ {"border": {"top": {"style": "double", "color": "0000FF"}}},
+ ),
+ (
+ "border-top-style: solid; border-top-color: #06c",
+ {"border": {"top": {"style": "medium", "color": "0066CC"}}},
+ ),
+ (
+ "border-top-color: blue",
+ {"border": {"top": {"color": "0000FF", "style": "none"}}},
+ ),
+ (
+ "border-top-style: slantDashDot; border-top-color: blue",
+ {"border": {"top": {"style": "slantDashDot", "color": "0000FF"}}},
+ ),
+ # ALIGNMENT
+ # - horizontal
+ ("text-align: center", {"alignment": {"horizontal": "center"}}),
+ ("text-align: left", {"alignment": {"horizontal": "left"}}),
+ ("text-align: right", {"alignment": {"horizontal": "right"}}),
+ ("text-align: justify", {"alignment": {"horizontal": "justify"}}),
+ # - vertical
+ ("vertical-align: top", {"alignment": {"vertical": "top"}}),
+ ("vertical-align: text-top", {"alignment": {"vertical": "top"}}),
+ ("vertical-align: middle", {"alignment": {"vertical": "center"}}),
+ ("vertical-align: bottom", {"alignment": {"vertical": "bottom"}}),
+ ("vertical-align: text-bottom", {"alignment": {"vertical": "bottom"}}),
+ # - wrap_text
+ ("white-space: nowrap", {"alignment": {"wrap_text": False}}),
+ ("white-space: pre", {"alignment": {"wrap_text": False}}),
+ ("white-space: pre-line", {"alignment": {"wrap_text": False}}),
+ ("white-space: normal", {"alignment": {"wrap_text": True}}),
+ # NUMBER FORMAT
+ ("number-format: 0%", {"number_format": {"format_code": "0%"}}),
+ (
+ "number-format: 0§[Red](0)§-§@;",
+ {"number_format": {"format_code": "0;[red](0);-;@"}}, # GH 46152
+ ),
+ ],
+)
+def test_css_to_excel(css, expected):
+ convert = CSSToExcelConverter()
+ assert expected == convert(css)
+
+
+def test_css_to_excel_multiple():
+ convert = CSSToExcelConverter()
+ actual = convert(
+ """
+ font-weight: bold;
+ text-decoration: underline;
+ color: red;
+ border-width: thin;
+ text-align: center;
+ vertical-align: top;
+ unused: something;
+ """
+ )
+ assert {
+ "font": {"bold": True, "underline": "single", "color": "FF0000"},
+ "border": {
+ "top": {"style": "thin"},
+ "right": {"style": "thin"},
+ "bottom": {"style": "thin"},
+ "left": {"style": "thin"},
+ },
+ "alignment": {"horizontal": "center", "vertical": "top"},
+ } == actual
+
+
+@pytest.mark.parametrize(
+ "css",
+ [
+ "border-top-style: unhandled-border-style",
+ "border-style: another-unhandled-style",
+ ],
+)
+def test_css_to_excel_unhandled_border_style_warns(css):
+ """Test that unhandled border styles raise a CSSWarning."""
+ convert = CSSToExcelConverter()
+ with tm.assert_produces_warning(CSSWarning, match="Unhandled border style format"):
+ convert(css)
+
+
+@pytest.mark.parametrize(
+ "css,inherited,expected",
+ [
+ ("font-weight: bold", "", {"font": {"bold": True}}),
+ ("", "font-weight: bold", {"font": {"bold": True}}),
+ (
+ "font-weight: bold",
+ "font-style: italic",
+ {"font": {"bold": True, "italic": True}},
+ ),
+ ("font-style: normal", "font-style: italic", {"font": {"italic": False}}),
+ ("font-style: inherit", "", {}),
+ (
+ "font-style: normal; font-style: inherit",
+ "font-style: italic",
+ {"font": {"italic": True}},
+ ),
+ ],
+)
+def test_css_to_excel_inherited(css, inherited, expected):
+ convert = CSSToExcelConverter(inherited)
+ assert expected == convert(css)
+
+
+@pytest.mark.parametrize(
+ "input_color,output_color",
+ (
+ list(CSSToExcelConverter.NAMED_COLORS.items())
+ + [("#" + rgb, rgb) for rgb in CSSToExcelConverter.NAMED_COLORS.values()]
+ + [("#F0F", "FF00FF"), ("#ABC", "AABBCC")]
+ ),
+)
+def test_css_to_excel_good_colors(input_color, output_color):
+ # see gh-18392
+ css = (
+ f"border-top-color: {input_color}; "
+ f"border-right-color: {input_color}; "
+ f"border-bottom-color: {input_color}; "
+ f"border-left-color: {input_color}; "
+ f"background-color: {input_color}; "
+ f"color: {input_color}"
+ )
+
+ expected = {}
+
+ expected["fill"] = {"patternType": "solid", "fgColor": output_color}
+
+ expected["font"] = {"color": output_color}
+
+ expected["border"] = {
+ k: {"color": output_color, "style": "none"}
+ for k in ("top", "right", "bottom", "left")
+ }
+
+ with tm.assert_produces_warning(None):
+ convert = CSSToExcelConverter()
+ assert expected == convert(css)
+
+
+@pytest.mark.parametrize("input_color", [None, "not-a-color"])
+def test_css_to_excel_bad_colors(input_color):
+ # see gh-18392
+ css = (
+ f"border-top-color: {input_color}; "
+ f"border-right-color: {input_color}; "
+ f"border-bottom-color: {input_color}; "
+ f"border-left-color: {input_color}; "
+ f"background-color: {input_color}; "
+ f"color: {input_color}"
+ )
+
+ expected = {}
+
+ if input_color is not None:
+ expected["fill"] = {"patternType": "solid"}
+
+ with tm.assert_produces_warning(CSSWarning, match="Unhandled color format"):
+ convert = CSSToExcelConverter()
+ assert expected == convert(css)
+
+
+@pytest.mark.parametrize("input_color", ["#", "#1234567"])
+def test_css_to_excel_invalid_color_raises(input_color):
+ """Test that invalid colors raise a ValueError."""
+ css = (
+ f"border-top-color: {input_color}; "
+ f"border-right-color: {input_color}; "
+ f"border-bottom-color: {input_color}; "
+ f"border-left-color: {input_color}; "
+ f"background-color: {input_color}; "
+ f"color: {input_color}"
+ )
+
+ convert = CSSToExcelConverter()
+ with pytest.raises(ValueError, match=f"Unexpected color {input_color}"):
+ convert(css)
+
+
+def tests_css_named_colors_valid():
+ upper_hexs = set(map(str.upper, string.hexdigits))
+ for color in CSSToExcelConverter.NAMED_COLORS.values():
+ assert len(color) == 6 and all(c in upper_hexs for c in color)
+
+
+def test_css_named_colors_from_mpl_present():
+ mpl_colors = pytest.importorskip("matplotlib.colors")
+
+ pd_colors = CSSToExcelConverter.NAMED_COLORS
+ for name, color in mpl_colors.CSS4_COLORS.items():
+ assert name in pd_colors and pd_colors[name] == color[1:]
+
+
+@pytest.mark.parametrize(
+ "styles,expected",
+ [
+ ([("color", "green"), ("color", "red")], "color: red;"),
+ ([("font-weight", "bold"), ("font-weight", "normal")], "font-weight: normal;"),
+ ([("text-align", "center"), ("TEXT-ALIGN", "right")], "text-align: right;"),
+ ],
+)
+def test_css_excel_cell_precedence(styles, expected):
+ """It applies favors latter declarations over former declarations"""
+ # See GH 47371
+ converter = CSSToExcelConverter()
+ converter._call_cached.cache_clear()
+ css_styles = {(0, 0): styles}
+ cell = CssExcelCell(
+ row=0,
+ col=0,
+ val="",
+ style=None,
+ css_styles=css_styles,
+ css_row=0,
+ css_col=0,
+ css_converter=converter,
+ )
+ converter._call_cached.cache_clear()
+
+ assert cell.style == converter(expected)
+
+
+@pytest.mark.parametrize(
+ "styles,cache_hits,cache_misses",
+ [
+ ([[("color", "green"), ("color", "red"), ("color", "green")]], 0, 1),
+ (
+ [
+ [("font-weight", "bold")],
+ [("font-weight", "normal"), ("font-weight", "bold")],
+ ],
+ 1,
+ 1,
+ ),
+ ([[("text-align", "center")], [("TEXT-ALIGN", "center")]], 1, 1),
+ (
+ [
+ [("font-weight", "bold"), ("text-align", "center")],
+ [("font-weight", "bold"), ("text-align", "left")],
+ ],
+ 0,
+ 2,
+ ),
+ (
+ [
+ [("font-weight", "bold"), ("text-align", "center")],
+ [("font-weight", "bold"), ("text-align", "left")],
+ [("font-weight", "bold"), ("text-align", "center")],
+ ],
+ 1,
+ 2,
+ ),
+ ],
+)
+def test_css_excel_cell_cache(styles, cache_hits, cache_misses):
+ """It caches unique cell styles"""
+ # See GH 47371
+ converter = CSSToExcelConverter()
+ converter._call_cached.cache_clear()
+
+ css_styles = {(0, i): _style for i, _style in enumerate(styles)}
+ for css_row, css_col in css_styles:
+ CssExcelCell(
+ row=0,
+ col=0,
+ val="",
+ style=None,
+ css_styles=css_styles,
+ css_row=css_row,
+ css_col=css_col,
+ css_converter=converter,
+ )
+ cache_info = converter._call_cached.cache_info()
+ converter._call_cached.cache_clear()
+
+ assert cache_info.hits == cache_hits
+ assert cache_info.misses == cache_misses
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_html.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_html.py
new file mode 100644
index 0000000000000000000000000000000000000000..11f6d1282bf7b1e97404d8e73aa282c2f2a04a6f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_html.py
@@ -0,0 +1,1166 @@
+from datetime import datetime
+from io import StringIO
+import itertools
+import re
+import textwrap
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ get_option,
+ option_context,
+)
+
+import pandas.io.formats.format as fmt
+
+lorem_ipsum = (
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
+ "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
+ "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex "
+ "ea commodo consequat. Duis aute irure dolor in reprehenderit in "
+ "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur "
+ "sint occaecat cupidatat non proident, sunt in culpa qui officia "
+ "deserunt mollit anim id est laborum."
+)
+
+
+def expected_html(datapath, name):
+ """
+ Read HTML file from formats data directory.
+
+ Parameters
+ ----------
+ datapath : pytest fixture
+ The datapath fixture injected into a test by pytest.
+ name : str
+ The name of the HTML file without the suffix.
+
+ Returns
+ -------
+ str : contents of HTML file.
+ """
+ filename = ".".join([name, "html"])
+ filepath = datapath("io", "formats", "data", "html", filename)
+ with open(filepath, encoding="utf-8") as f:
+ html = f.read()
+ return html.rstrip()
+
+
+@pytest.fixture(params=["mixed", "empty"])
+def biggie_df_fixture(request):
+ """Fixture for a big mixed Dataframe and an empty Dataframe"""
+ if request.param == "mixed":
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).standard_normal(200),
+ "B": Index([f"{i}?!" for i in range(200)]),
+ },
+ index=np.arange(200),
+ )
+ df.loc[:20, "A"] = np.nan
+ df.loc[:20, "B"] = np.nan
+ return df
+ elif request.param == "empty":
+ df = DataFrame(index=np.arange(200))
+ return df
+
+
+@pytest.fixture(params=fmt.VALID_JUSTIFY_PARAMETERS)
+def justify(request):
+ return request.param
+
+
+@pytest.mark.parametrize("col_space", [30, 50])
+def test_to_html_with_col_space(col_space):
+ df = DataFrame(np.random.default_rng(2).random(size=(1, 3)))
+ # check that col_space affects HTML generation
+ # and be very brittle about it.
+ result = df.to_html(col_space=col_space)
+ hdrs = [x for x in result.split(r"\n") if re.search(r"\s]", x)]
+ assert len(hdrs) > 0
+ for h in hdrs:
+ assert "min-width" in h
+ assert str(col_space) in h
+
+
+def test_to_html_with_column_specific_col_space_raises():
+ df = DataFrame(
+ np.random.default_rng(2).random(size=(3, 3)), columns=["a", "b", "c"]
+ )
+
+ msg = (
+ "Col_space length\\(\\d+\\) should match DataFrame number of columns\\(\\d+\\)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.to_html(col_space=[30, 40])
+
+ with pytest.raises(ValueError, match=msg):
+ df.to_html(col_space=[30, 40, 50, 60])
+
+ msg = "unknown column"
+ with pytest.raises(ValueError, match=msg):
+ df.to_html(col_space={"a": "foo", "b": 23, "d": 34})
+
+
+def test_to_html_with_column_specific_col_space():
+ df = DataFrame(
+ np.random.default_rng(2).random(size=(3, 3)), columns=["a", "b", "c"]
+ )
+
+ result = df.to_html(col_space={"a": "2em", "b": 23})
+ hdrs = [x for x in result.split("\n") if re.search(r" \s]", x)]
+ assert 'min-width: 2em;">a ' in hdrs[1]
+ assert 'min-width: 23px;">b' in hdrs[2]
+ assert "c " in hdrs[3]
+
+ result = df.to_html(col_space=["1em", 2, 3])
+ hdrs = [x for x in result.split("\n") if re.search(r"\s]", x)]
+ assert 'min-width: 1em;">a ' in hdrs[1]
+ assert 'min-width: 2px;">b' in hdrs[2]
+ assert 'min-width: 3px;">c' in hdrs[3]
+
+
+def test_to_html_with_empty_string_label():
+ # GH 3547, to_html regards empty string labels as repeated labels
+ data = {"c1": ["a", "b"], "c2": ["a", ""], "data": [1, 2]}
+ df = DataFrame(data).set_index(["c1", "c2"])
+ result = df.to_html()
+ assert "rowspan" not in result
+
+
+@pytest.mark.parametrize(
+ "df_data,expected",
+ [
+ ({"\u03c3": np.arange(10.0)}, "unicode_1"),
+ ({"A": ["\u03c3"]}, "unicode_2"),
+ ],
+)
+def test_to_html_unicode(df_data, expected, datapath):
+ df = DataFrame(df_data)
+ expected = expected_html(datapath, expected)
+ result = df.to_html()
+ assert result == expected
+
+
+def test_to_html_encoding(float_frame, temp_file):
+ # GH 28663
+ float_frame.to_html(temp_file, encoding="gbk")
+ expected = temp_file.read_text(encoding="gbk")
+ assert float_frame.to_html() == expected
+
+
+def test_to_html_decimal(datapath):
+ # GH 12031
+ df = DataFrame({"A": [6.0, 3.1, 2.2]})
+ result = df.to_html(decimal=",")
+ expected = expected_html(datapath, "gh12031_expected_output")
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "kwargs,string,expected",
+ [
+ ({}, "", "escaped"),
+ ({"escape": False}, "bold", "escape_disabled"),
+ ],
+)
+def test_to_html_escaped(kwargs, string, expected, datapath):
+ a = "strl2": {a: string, b: string}}
+ result = DataFrame(test_dict).to_html(**kwargs)
+ expected = expected_html(datapath, expected)
+ assert result == expected
+
+
+@pytest.mark.parametrize("index_is_named", [True, False])
+def test_to_html_multiindex_index_false(index_is_named, datapath):
+ # GH 8452
+ df = DataFrame(
+ {"a": range(2), "b": range(3, 5), "c": range(5, 7), "d": range(3, 5)}
+ )
+ df.columns = MultiIndex.from_product([["a", "b"], ["c", "d"]])
+ if index_is_named:
+ df.index = Index(df.index.values, name="idx")
+ result = df.to_html(index=False)
+ expected = expected_html(datapath, "gh8452_expected_output")
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "multi_sparse,expected",
+ [
+ (False, "multiindex_sparsify_false_multi_sparse_1"),
+ (False, "multiindex_sparsify_false_multi_sparse_2"),
+ (True, "multiindex_sparsify_1"),
+ (True, "multiindex_sparsify_2"),
+ ],
+)
+def test_to_html_multiindex_sparsify(multi_sparse, expected, datapath):
+ index = MultiIndex.from_arrays([[0, 0, 1, 1], [0, 1, 0, 1]], names=["foo", None])
+ df = DataFrame([[0, 1], [2, 3], [4, 5], [6, 7]], index=index)
+ if expected.endswith("2"):
+ df.columns = index[::2]
+ with option_context("display.multi_sparse", multi_sparse):
+ result = df.to_html()
+ expected = expected_html(datapath, expected)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "max_rows,expected",
+ [
+ (60, "gh14882_expected_output_1"),
+ # Test that ... appears in a middle level
+ (56, "gh14882_expected_output_2"),
+ ],
+)
+def test_to_html_multiindex_odd_even_truncate(max_rows, expected, datapath):
+ # GH 14882 - Issue on truncation with odd length DataFrame
+ index = MultiIndex.from_product(
+ [[100, 200, 300], [10, 20, 30], [1, 2, 3, 4, 5, 6, 7]], names=["a", "b", "c"]
+ )
+ df = DataFrame({"n": range(len(index))}, index=index)
+ result = df.to_html(max_rows=max_rows)
+ expected = expected_html(datapath, expected)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "df,formatters,expected",
+ [
+ (
+ DataFrame(
+ [[0, 1], [2, 3], [4, 5], [6, 7]],
+ columns=Index(["foo", None], dtype=object),
+ index=np.arange(4),
+ ),
+ {"__index__": lambda x: "abcd"[x]},
+ "index_formatter",
+ ),
+ (
+ DataFrame({"months": [datetime(2016, 1, 1), datetime(2016, 2, 2)]}),
+ {"months": lambda x: x.strftime("%Y-%m")},
+ "datetime64_monthformatter",
+ ),
+ (
+ DataFrame(
+ {
+ "hod": pd.to_datetime(
+ ["10:10:10.100", "12:12:12.120"], format="%H:%M:%S.%f"
+ )
+ }
+ ),
+ {"hod": lambda x: x.strftime("%H:%M")},
+ "datetime64_hourformatter",
+ ),
+ (
+ DataFrame(
+ {
+ "i": pd.Series([1, 2], dtype="int64"),
+ "f": pd.Series([1, 2], dtype="float64"),
+ "I": pd.Series([1, 2], dtype="Int64"),
+ "s": pd.Series([1, 2], dtype="string"),
+ "b": pd.Series([True, False], dtype="boolean"),
+ "c": pd.Series(["a", "b"], dtype=pd.CategoricalDtype(["a", "b"])),
+ "o": pd.Series([1, "2"], dtype=object),
+ }
+ ),
+ [lambda x: "formatted"] * 7,
+ "various_dtypes_formatted",
+ ),
+ ],
+)
+def test_to_html_formatters(df, formatters, expected, datapath):
+ expected = expected_html(datapath, expected)
+ result = df.to_html(formatters=formatters)
+ assert result == expected
+
+
+def test_to_html_regression_GH6098():
+ df = DataFrame(
+ {
+ "clé1": ["a", "a", "b", "b", "a"],
+ "clé2": ["1er", "2ème", "1er", "2ème", "1er"],
+ "données1": np.random.default_rng(2).standard_normal(5),
+ "données2": np.random.default_rng(2).standard_normal(5),
+ }
+ )
+
+ # it works
+ df.pivot_table(index=["clé1"], columns=["clé2"])._repr_html_()
+
+
+def test_to_html_truncate(datapath):
+ index = pd.date_range(start="20010101", freq="D", periods=20)
+ df = DataFrame(index=index, columns=range(20))
+ result = df.to_html(max_rows=8, max_cols=4)
+ expected = expected_html(datapath, "truncate")
+ assert result == expected
+
+
+@pytest.mark.parametrize("size", [1, 5])
+def test_html_invalid_formatters_arg_raises(size):
+ # issue-28469
+ df = DataFrame(columns=["a", "b", "c"])
+ msg = "Formatters length({}) should match DataFrame number of columns(3)"
+ with pytest.raises(ValueError, match=re.escape(msg.format(size))):
+ df.to_html(formatters=["{}".format] * size)
+
+
+def test_to_html_truncate_formatter(datapath):
+ # issue-25955
+ data = [
+ {"A": 1, "B": 2, "C": 3, "D": 4},
+ {"A": 5, "B": 6, "C": 7, "D": 8},
+ {"A": 9, "B": 10, "C": 11, "D": 12},
+ {"A": 13, "B": 14, "C": 15, "D": 16},
+ ]
+
+ df = DataFrame(data)
+ fmt = lambda x: str(x) + "_mod"
+ formatters = [fmt, fmt, None, None]
+ result = df.to_html(formatters=formatters, max_cols=3)
+ expected = expected_html(datapath, "truncate_formatter")
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "sparsify,expected",
+ [(True, "truncate_multi_index"), (False, "truncate_multi_index_sparse_off")],
+)
+def test_to_html_truncate_multi_index(sparsify, expected, datapath):
+ arrays = [
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ df = DataFrame(index=arrays, columns=arrays)
+ result = df.to_html(max_rows=7, max_cols=7, sparsify=sparsify)
+ expected = expected_html(datapath, expected)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "option,result,expected",
+ [
+ (None, lambda df: df.to_html(), "1"),
+ (None, lambda df: df.to_html(border=2), "2"),
+ (2, lambda df: df.to_html(), "2"),
+ (2, lambda df: df._repr_html_(), "2"),
+ ],
+)
+def test_to_html_border(option, result, expected):
+ df = DataFrame({"A": [1, 2]})
+ if option is None:
+ result = result(df)
+ else:
+ with option_context("display.html.border", option):
+ result = result(df)
+ expected = f'border="{expected}"'
+ assert expected in result
+
+
+@pytest.mark.parametrize("biggie_df_fixture", ["mixed"], indirect=True)
+def test_to_html(biggie_df_fixture):
+ # TODO: split this test
+ df = biggie_df_fixture
+ s = df.to_html()
+
+ buf = StringIO()
+ retval = df.to_html(buf=buf)
+ assert retval is None
+ assert buf.getvalue() == s
+
+ assert isinstance(s, str)
+
+ df.to_html(columns=["B", "A"], col_space=17)
+ df.to_html(columns=["B", "A"], formatters={"A": lambda x: f"{x:.1f}"})
+
+ df.to_html(columns=["B", "A"], float_format=str)
+ df.to_html(columns=["B", "A"], col_space=12, float_format=str)
+
+
+@pytest.mark.parametrize("biggie_df_fixture", ["empty"], indirect=True)
+def test_to_html_empty_dataframe(biggie_df_fixture):
+ df = biggie_df_fixture
+ df.to_html()
+
+
+def test_to_html_filename(biggie_df_fixture, tmpdir):
+ df = biggie_df_fixture
+ expected = df.to_html()
+ path = tmpdir.join("test.html")
+ df.to_html(path)
+ result = path.read()
+ assert result == expected
+
+
+def test_to_html_with_no_bold():
+ df = DataFrame({"x": np.random.default_rng(2).standard_normal(5)})
+ html = df.to_html(bold_rows=False)
+ result = html[html.find("")]
+ assert "B" not in result
+
+
+@pytest.mark.parametrize(
+ "columns,justify,expected",
+ [
+ (
+ MultiIndex.from_arrays(
+ [np.arange(2).repeat(2), np.mod(range(4), 2)],
+ names=["CL0", "CL1"],
+ ),
+ "left",
+ "multiindex_1",
+ ),
+ (
+ MultiIndex.from_arrays([np.arange(4), np.mod(range(4), 2)]),
+ "right",
+ "multiindex_2",
+ ),
+ ],
+)
+def test_to_html_multiindex(columns, justify, expected, datapath):
+ df = DataFrame([list("abcd"), list("efgh")], columns=columns)
+ result = df.to_html(justify=justify)
+ expected = expected_html(datapath, expected)
+ assert result == expected
+
+
+def test_to_html_justify(justify, datapath):
+ df = DataFrame(
+ {"A": [6, 30000, 2], "B": [1, 2, 70000], "C": [223442, 0, 1]},
+ columns=["A", "B", "C"],
+ )
+ result = df.to_html(justify=justify)
+ expected = expected_html(datapath, "justify").format(justify=justify)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "justify", ["super-right", "small-left", "noinherit", "tiny", "pandas"]
+)
+def test_to_html_invalid_justify(justify):
+ # GH 17527
+ df = DataFrame()
+ msg = "Invalid value for justify parameter"
+
+ with pytest.raises(ValueError, match=msg):
+ df.to_html(justify=justify)
+
+
+class TestHTMLIndex:
+ @pytest.fixture
+ def df(self):
+ index = ["foo", "bar", "baz"]
+ df = DataFrame(
+ {"A": [1, 2, 3], "B": [1.2, 3.4, 5.6], "C": ["one", "two", np.nan]},
+ columns=["A", "B", "C"],
+ index=index,
+ )
+ return df
+
+ @pytest.fixture
+ def expected_without_index(self, datapath):
+ return expected_html(datapath, "index_2")
+
+ def test_to_html_flat_index_without_name(
+ self, datapath, df, expected_without_index
+ ):
+ expected_with_index = expected_html(datapath, "index_1")
+ assert df.to_html() == expected_with_index
+
+ result = df.to_html(index=False)
+ for i in df.index:
+ assert i not in result
+ assert result == expected_without_index
+
+ def test_to_html_flat_index_with_name(self, datapath, df, expected_without_index):
+ df.index = Index(["foo", "bar", "baz"], name="idx")
+ expected_with_index = expected_html(datapath, "index_3")
+ assert df.to_html() == expected_with_index
+ assert df.to_html(index=False) == expected_without_index
+
+ def test_to_html_multiindex_without_names(
+ self, datapath, df, expected_without_index
+ ):
+ tuples = [("foo", "car"), ("foo", "bike"), ("bar", "car")]
+ df.index = MultiIndex.from_tuples(tuples)
+
+ expected_with_index = expected_html(datapath, "index_4")
+ assert df.to_html() == expected_with_index
+
+ result = df.to_html(index=False)
+ for i in ["foo", "bar", "car", "bike"]:
+ assert i not in result
+ # must be the same result as normal index
+ assert result == expected_without_index
+
+ def test_to_html_multiindex_with_names(self, datapath, df, expected_without_index):
+ tuples = [("foo", "car"), ("foo", "bike"), ("bar", "car")]
+ df.index = MultiIndex.from_tuples(tuples, names=["idx1", "idx2"])
+ expected_with_index = expected_html(datapath, "index_5")
+ assert df.to_html() == expected_with_index
+ assert df.to_html(index=False) == expected_without_index
+
+
+@pytest.mark.parametrize("classes", ["sortable draggable", ["sortable", "draggable"]])
+def test_to_html_with_classes(classes, datapath):
+ df = DataFrame()
+ expected = expected_html(datapath, "with_classes")
+ result = df.to_html(classes=classes)
+ assert result == expected
+
+
+def test_to_html_no_index_max_rows(datapath):
+ # GH 14998
+ df = DataFrame({"A": [1, 2, 3, 4]})
+ result = df.to_html(index=False, max_rows=1)
+ expected = expected_html(datapath, "gh14998_expected_output")
+ assert result == expected
+
+
+def test_to_html_multiindex_max_cols(datapath):
+ # GH 6131
+ index = MultiIndex(
+ levels=[["ba", "bb", "bc"], ["ca", "cb", "cc"]],
+ codes=[[0, 1, 2], [0, 1, 2]],
+ names=["b", "c"],
+ )
+ columns = MultiIndex(
+ levels=[["d"], ["aa", "ab", "ac"]],
+ codes=[[0, 0, 0], [0, 1, 2]],
+ names=[None, "a"],
+ )
+ data = np.array(
+ [[1.0, np.nan, np.nan], [np.nan, 2.0, np.nan], [np.nan, np.nan, 3.0]]
+ )
+ df = DataFrame(data, index, columns)
+ result = df.to_html(max_cols=2)
+ expected = expected_html(datapath, "gh6131_expected_output")
+ assert result == expected
+
+
+def test_to_html_multi_indexes_index_false(datapath):
+ # GH 22579
+ df = DataFrame(
+ {"a": range(10), "b": range(10, 20), "c": range(10, 20), "d": range(10, 20)}
+ )
+ df.columns = MultiIndex.from_product([["a", "b"], ["c", "d"]])
+ df.index = MultiIndex.from_product([["a", "b"], ["c", "d", "e", "f", "g"]])
+ result = df.to_html(index=False)
+ expected = expected_html(datapath, "gh22579_expected_output")
+ assert result == expected
+
+
+@pytest.mark.parametrize("index_names", [True, False])
+@pytest.mark.parametrize("header", [True, False])
+@pytest.mark.parametrize("index", [True, False])
+@pytest.mark.parametrize(
+ "column_index, column_type",
+ [
+ (Index([0, 1]), "unnamed_standard"),
+ (Index([0, 1], name="columns.name"), "named_standard"),
+ (MultiIndex.from_product([["a"], ["b", "c"]]), "unnamed_multi"),
+ (
+ MultiIndex.from_product(
+ [["a"], ["b", "c"]], names=["columns.name.0", "columns.name.1"]
+ ),
+ "named_multi",
+ ),
+ ],
+)
+@pytest.mark.parametrize(
+ "row_index, row_type",
+ [
+ (Index([0, 1]), "unnamed_standard"),
+ (Index([0, 1], name="index.name"), "named_standard"),
+ (MultiIndex.from_product([["a"], ["b", "c"]]), "unnamed_multi"),
+ (
+ MultiIndex.from_product(
+ [["a"], ["b", "c"]], names=["index.name.0", "index.name.1"]
+ ),
+ "named_multi",
+ ),
+ ],
+)
+def test_to_html_basic_alignment(
+ datapath, row_index, row_type, column_index, column_type, index, header, index_names
+):
+ # GH 22747, GH 22579
+ df = DataFrame(np.zeros((2, 2), dtype=int), index=row_index, columns=column_index)
+ result = df.to_html(index=index, header=header, index_names=index_names)
+
+ if not index:
+ row_type = "none"
+ elif not index_names and row_type.startswith("named"):
+ row_type = "un" + row_type
+
+ if not header:
+ column_type = "none"
+ elif not index_names and column_type.startswith("named"):
+ column_type = "un" + column_type
+
+ filename = "index_" + row_type + "_columns_" + column_type
+ expected = expected_html(datapath, filename)
+ assert result == expected
+
+
+@pytest.mark.parametrize("index_names", [True, False])
+@pytest.mark.parametrize("header", [True, False])
+@pytest.mark.parametrize("index", [True, False])
+@pytest.mark.parametrize(
+ "column_index, column_type",
+ [
+ (Index(np.arange(8)), "unnamed_standard"),
+ (Index(np.arange(8), name="columns.name"), "named_standard"),
+ (
+ MultiIndex.from_product([["a", "b"], ["c", "d"], ["e", "f"]]),
+ "unnamed_multi",
+ ),
+ (
+ MultiIndex.from_product(
+ [["a", "b"], ["c", "d"], ["e", "f"]], names=["foo", None, "baz"]
+ ),
+ "named_multi",
+ ),
+ ],
+)
+@pytest.mark.parametrize(
+ "row_index, row_type",
+ [
+ (Index(np.arange(8)), "unnamed_standard"),
+ (Index(np.arange(8), name="index.name"), "named_standard"),
+ (
+ MultiIndex.from_product([["a", "b"], ["c", "d"], ["e", "f"]]),
+ "unnamed_multi",
+ ),
+ (
+ MultiIndex.from_product(
+ [["a", "b"], ["c", "d"], ["e", "f"]], names=["foo", None, "baz"]
+ ),
+ "named_multi",
+ ),
+ ],
+)
+def test_to_html_alignment_with_truncation(
+ datapath, row_index, row_type, column_index, column_type, index, header, index_names
+):
+ # GH 22747, GH 22579
+ df = DataFrame(np.arange(64).reshape(8, 8), index=row_index, columns=column_index)
+ result = df.to_html(
+ max_rows=4, max_cols=4, index=index, header=header, index_names=index_names
+ )
+
+ if not index:
+ row_type = "none"
+ elif not index_names and row_type.startswith("named"):
+ row_type = "un" + row_type
+
+ if not header:
+ column_type = "none"
+ elif not index_names and column_type.startswith("named"):
+ column_type = "un" + column_type
+
+ filename = "trunc_df_index_" + row_type + "_columns_" + column_type
+ expected = expected_html(datapath, filename)
+ assert result == expected
+
+
+@pytest.mark.parametrize("index", [False, 0])
+def test_to_html_truncation_index_false_max_rows(datapath, index):
+ # GH 15019
+ data = [
+ [1.764052, 0.400157],
+ [0.978738, 2.240893],
+ [1.867558, -0.977278],
+ [0.950088, -0.151357],
+ [-0.103219, 0.410599],
+ ]
+ df = DataFrame(data)
+ result = df.to_html(max_rows=4, index=index)
+ expected = expected_html(datapath, "gh15019_expected_output")
+ assert result == expected
+
+
+@pytest.mark.parametrize("index", [False, 0])
+@pytest.mark.parametrize(
+ "col_index_named, expected_output",
+ [(False, "gh22783_expected_output"), (True, "gh22783_named_columns_index")],
+)
+def test_to_html_truncation_index_false_max_cols(
+ datapath, index, col_index_named, expected_output
+):
+ # GH 22783
+ data = [
+ [1.764052, 0.400157, 0.978738, 2.240893, 1.867558],
+ [-0.977278, 0.950088, -0.151357, -0.103219, 0.410599],
+ ]
+ df = DataFrame(data)
+ if col_index_named:
+ df.columns.rename("columns.name", inplace=True)
+ result = df.to_html(max_cols=4, index=index)
+ expected = expected_html(datapath, expected_output)
+ assert result == expected
+
+
+@pytest.mark.parametrize("notebook", [True, False])
+def test_to_html_notebook_has_style(notebook):
+ df = DataFrame({"A": [1, 2, 3]})
+ result = df.to_html(notebook=notebook)
+
+ if notebook:
+ assert "tbody tr th:only-of-type" in result
+ assert "vertical-align: middle;" in result
+ assert "thead th" in result
+ else:
+ assert "tbody tr th:only-of-type" not in result
+ assert "vertical-align: middle;" not in result
+ assert "thead th" not in result
+
+
+def test_to_html_with_index_names_false():
+ # GH 16493
+ df = DataFrame({"A": [1, 2]}, index=Index(["a", "b"], name="myindexname"))
+ result = df.to_html(index_names=False)
+ assert "myindexname" not in result
+
+
+def test_to_html_with_id():
+ # GH 8496
+ df = DataFrame({"A": [1, 2]}, index=Index(["a", "b"], name="myindexname"))
+ result = df.to_html(index_names=False, table_id="TEST_ID")
+ assert ' id="TEST_ID"' in result
+
+
+@pytest.mark.parametrize(
+ "value,float_format,expected",
+ [
+ (0.19999, "%.3f", "gh21625_expected_output"),
+ (100.0, "%.0f", "gh22270_expected_output"),
+ ],
+)
+def test_to_html_float_format_no_fixed_width(value, float_format, expected, datapath):
+ # GH 21625, GH 22270
+ df = DataFrame({"x": [value]})
+ expected = expected_html(datapath, expected)
+ result = df.to_html(float_format=float_format)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "render_links,expected",
+ [(True, "render_links_true"), (False, "render_links_false")],
+)
+def test_to_html_render_links(render_links, expected, datapath):
+ # GH 2679
+ data = [
+ [0, "https://pandas.pydata.org/?q1=a&q2=b", "pydata.org"],
+ [0, "www.pydata.org", "pydata.org"],
+ ]
+ df = DataFrame(data, columns=Index(["foo", "bar", None], dtype=object))
+
+ result = df.to_html(render_links=render_links)
+ expected = expected_html(datapath, expected)
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "method,expected",
+ [
+ ("to_html", lambda x: lorem_ipsum),
+ ("_repr_html_", lambda x: lorem_ipsum[: x - 4] + "..."), # regression case
+ ],
+)
+@pytest.mark.parametrize("max_colwidth", [10, 20, 50, 100])
+def test_ignore_display_max_colwidth(method, expected, max_colwidth):
+ # see gh-17004
+ df = DataFrame([lorem_ipsum])
+ with option_context("display.max_colwidth", max_colwidth):
+ result = getattr(df, method)()
+ expected = expected(max_colwidth)
+ assert expected in result
+
+
+@pytest.mark.parametrize("classes", [True, 0])
+def test_to_html_invalid_classes_type(classes):
+ # GH 25608
+ df = DataFrame()
+ msg = "classes must be a string, list, or tuple"
+
+ with pytest.raises(TypeError, match=msg):
+ df.to_html(classes=classes)
+
+
+def test_to_html_round_column_headers():
+ # GH 17280
+ df = DataFrame([1], columns=[0.55555])
+ with option_context("display.precision", 3):
+ html = df.to_html(notebook=False)
+ notebook = df.to_html(notebook=True)
+ assert "0.55555" in html
+ assert "0.556" in notebook
+
+
+@pytest.mark.parametrize("unit", ["100px", "10%", "5em", 150])
+def test_to_html_with_col_space_units(unit):
+ # GH 25941
+ df = DataFrame(np.random.default_rng(2).random(size=(1, 3)))
+ result = df.to_html(col_space=unit)
+ result = result.split("tbody")[0]
+ hdrs = [x for x in result.split("\n") if re.search(r"\s]", x)]
+ if isinstance(unit, int):
+ unit = str(unit) + "px"
+ for h in hdrs:
+ expected = f' '
+ assert expected in h
+
+
+class TestReprHTML:
+ def test_html_repr_min_rows_default(self, datapath):
+ # gh-27991
+
+ # default setting no truncation even if above min_rows
+ df = DataFrame({"a": range(20)})
+ result = df._repr_html_()
+ expected = expected_html(datapath, "html_repr_min_rows_default_no_truncation")
+ assert result == expected
+
+ # default of max_rows 60 triggers truncation if above
+ df = DataFrame({"a": range(61)})
+ result = df._repr_html_()
+ expected = expected_html(datapath, "html_repr_min_rows_default_truncated")
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "max_rows,min_rows,expected",
+ [
+ # truncated after first two rows
+ (10, 4, "html_repr_max_rows_10_min_rows_4"),
+ # when set to None, follow value of max_rows
+ (12, None, "html_repr_max_rows_12_min_rows_None"),
+ # when set value higher as max_rows, use the minimum
+ (10, 12, "html_repr_max_rows_10_min_rows_12"),
+ # max_rows of None -> never truncate
+ (None, 12, "html_repr_max_rows_None_min_rows_12"),
+ ],
+ )
+ def test_html_repr_min_rows(self, datapath, max_rows, min_rows, expected):
+ # gh-27991
+
+ df = DataFrame({"a": range(61)})
+ expected = expected_html(datapath, expected)
+ with option_context("display.max_rows", max_rows, "display.min_rows", min_rows):
+ result = df._repr_html_()
+ assert result == expected
+
+ def test_repr_html_ipython_config(self, ip):
+ code = textwrap.dedent(
+ """\
+ from pandas import DataFrame
+ df = DataFrame({"A": [1, 2]})
+ df._repr_html_()
+
+ cfg = get_ipython().config
+ cfg['IPKernelApp']['parent_appname']
+ df._repr_html_()
+ """
+ )
+ result = ip.run_cell(code, silent=True)
+ assert not result.error_in_exec
+
+ def test_info_repr_html(self):
+ max_rows = 60
+ max_cols = 20
+ # Long
+ h, w = max_rows + 1, max_cols - 1
+ df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
+ assert r"<class" not in df._repr_html_()
+ with option_context("display.large_repr", "info"):
+ assert r"<class" in df._repr_html_()
+
+ # Wide
+ h, w = max_rows - 1, max_cols + 1
+ df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
+ assert "{40 + h}" in reg_repr
+
+ h = max_rows + 1
+ df = DataFrame(
+ {
+ "idx": np.linspace(-10, 10, h),
+ "A": np.arange(1, 1 + h),
+ "B": np.arange(41, 41 + h),
+ }
+ ).set_index("idx")
+ long_repr = df._repr_html_()
+ assert ".." in long_repr
+ assert "31 " not in long_repr
+ assert f"{h} rows " in long_repr
+ assert "2 columns" in long_repr
+
+ def test_repr_html_long_multiindex(self):
+ max_rows = 60
+ max_L1 = max_rows // 2
+
+ tuples = list(itertools.product(np.arange(max_L1), ["foo", "bar"]))
+ idx = MultiIndex.from_tuples(tuples, names=["first", "second"])
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((max_L1 * 2, 2)),
+ index=idx,
+ columns=["A", "B"],
+ )
+ with option_context("display.max_rows", 60, "display.max_columns", 20):
+ reg_repr = df._repr_html_()
+ assert "..." not in reg_repr
+
+ tuples = list(itertools.product(np.arange(max_L1 + 1), ["foo", "bar"]))
+ idx = MultiIndex.from_tuples(tuples, names=["first", "second"])
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal(((max_L1 + 1) * 2, 2)),
+ index=idx,
+ columns=["A", "B"],
+ )
+ long_repr = df._repr_html_()
+ assert "..." in long_repr
+
+ def test_repr_html_long_and_wide(self):
+ max_cols = 20
+ max_rows = 60
+
+ h, w = max_rows - 1, max_cols - 1
+ df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
+ with option_context("display.max_rows", 60, "display.max_columns", 20):
+ assert "..." not in df._repr_html_()
+
+ h, w = max_rows + 1, max_cols + 1
+ df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
+ with option_context("display.max_rows", 60, "display.max_columns", 20):
+ assert "..." in df._repr_html_()
+
+
+def test_to_html_multilevel(multiindex_year_month_day_dataframe_random_data):
+ ymd = multiindex_year_month_day_dataframe_random_data
+
+ ymd.columns.name = "foo"
+ ymd.to_html()
+ ymd.T.to_html()
+
+
+@pytest.mark.parametrize("na_rep", ["NaN", "Ted"])
+def test_to_html_na_rep_and_float_format(na_rep, datapath):
+ # https://github.com/pandas-dev/pandas/issues/13828
+ df = DataFrame(
+ [
+ ["A", 1.2225],
+ ["A", None],
+ ],
+ columns=["Group", "Data"],
+ )
+ result = df.to_html(na_rep=na_rep, float_format="{:.2f}".format)
+ expected = expected_html(datapath, "gh13828_expected_output")
+ expected = expected.format(na_rep=na_rep)
+ assert result == expected
+
+
+def test_to_html_na_rep_non_scalar_data(datapath):
+ # GH47103
+ df = DataFrame([{"a": 1, "b": [1, 2, 3]}])
+ result = df.to_html(na_rep="-")
+ expected = expected_html(datapath, "gh47103_expected_output")
+ assert result == expected
+
+
+def test_to_html_float_format_object_col(datapath):
+ # GH#40024
+ df = DataFrame(data={"x": [1000.0, "test"]})
+ result = df.to_html(float_format=lambda x: f"{x:,.0f}")
+ expected = expected_html(datapath, "gh40024_expected_output")
+ assert result == expected
+
+
+def test_to_html_multiindex_col_with_colspace():
+ # GH#53885
+ df = DataFrame([[1, 2]])
+ df.columns = MultiIndex.from_tuples([(1, 1), (2, 1)])
+ result = df.to_html(col_space=100)
+ expected = (
+ '\n'
+ " \n"
+ " \n"
+ ' \n'
+ ' 1 \n'
+ ' 2 \n'
+ " \n"
+ " \n"
+ ' \n'
+ ' 1 \n'
+ ' 1 \n'
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " 0 \n"
+ " 1 \n"
+ " 2 \n"
+ " \n"
+ " \n"
+ "
"
+ )
+ assert result == expected
+
+
+def test_to_html_tuple_col_with_colspace():
+ # GH#53885
+ df = DataFrame({("a", "b"): [1], "b": [2]})
+ result = df.to_html(col_space=100)
+ expected = (
+ '\n'
+ " \n"
+ ' \n'
+ ' \n'
+ ' (a, b) \n'
+ ' b \n'
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " 0 \n"
+ " 1 \n"
+ " 2 \n"
+ " \n"
+ " \n"
+ "
"
+ )
+ assert result == expected
+
+
+def test_to_html_empty_complex_array():
+ # GH#54167
+ df = DataFrame({"x": np.array([], dtype="complex")})
+ result = df.to_html(col_space=100)
+ expected = (
+ '\n'
+ " \n"
+ ' \n'
+ ' \n'
+ ' x \n'
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ "
"
+ )
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_latex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_latex.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e44b42dc88a88e2fe3faf4924788a485df26b60
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_latex.py
@@ -0,0 +1,1527 @@
+from datetime import datetime
+from textwrap import dedent
+
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+)
+
+pytest.importorskip("jinja2")
+
+
+def _dedent(string):
+ """Dedent without new line in the beginning.
+
+ Built-in textwrap.dedent would keep new line character in the beginning
+ of multi-line string starting from the new line.
+ This version drops the leading new line character.
+ """
+ return dedent(string).lstrip()
+
+
+@pytest.fixture
+def df_short():
+ """Short dataframe for testing table/tabular/longtable LaTeX env."""
+ return DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+
+
+class TestToLatex:
+ def test_to_latex_to_file(self, float_frame, temp_file):
+ float_frame.to_latex(temp_file)
+ with open(temp_file, encoding="utf-8") as f:
+ assert float_frame.to_latex() == f.read()
+
+ def test_to_latex_to_file_utf8_with_encoding(self, temp_file):
+ # test with utf-8 and encoding option (GH 7061)
+ df = DataFrame([["au\xdfgangen"]])
+ df.to_latex(temp_file, encoding="utf-8")
+ with open(temp_file, encoding="utf-8") as f:
+ assert df.to_latex() == f.read()
+
+ def test_to_latex_to_file_utf8_without_encoding(self, temp_file):
+ # test with utf-8 without encoding option
+ df = DataFrame([["au\xdfgangen"]])
+ df.to_latex(temp_file)
+ with open(temp_file, encoding="utf-8") as f:
+ assert df.to_latex() == f.read()
+
+ def test_to_latex_tabular_with_index(self):
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex()
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_tabular_without_index(self):
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(index=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{rl}
+ \toprule
+ a & b \\
+ \midrule
+ 1 & b1 \\
+ 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "bad_column_format",
+ [5, 1.2, ["l", "r"], ("r", "c"), {"r", "c", "l"}, {"a": "r", "b": "l"}],
+ )
+ def test_to_latex_bad_column_format(self, bad_column_format):
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ msg = r"`column_format` must be str or unicode"
+ with pytest.raises(ValueError, match=msg):
+ df.to_latex(column_format=bad_column_format)
+
+ def test_to_latex_column_format_just_works(self, float_frame):
+ # GH Bug #9402
+ float_frame.to_latex(column_format="lcr")
+
+ def test_to_latex_column_format(self):
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(column_format="lcr")
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lcr}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_float_format_object_col(self):
+ # GH#40024
+ ser = Series([1000.0, "test"])
+ result = ser.to_latex(float_format="{:,.0f}".format)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{ll}
+ \toprule
+ & 0 \\
+ \midrule
+ 0 & 1,000 \\
+ 1 & test \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_empty_tabular(self):
+ df = DataFrame()
+ result = df.to_latex()
+ expected = _dedent(
+ r"""
+ \begin{tabular}{l}
+ \toprule
+ \midrule
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_series(self):
+ s = Series(["a", "b", "c"])
+ result = s.to_latex()
+ expected = _dedent(
+ r"""
+ \begin{tabular}{ll}
+ \toprule
+ & 0 \\
+ \midrule
+ 0 & a \\
+ 1 & b \\
+ 2 & c \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_midrule_location(self):
+ # GH 18326
+ df = DataFrame({"a": [1, 2]})
+ df.index.name = "foo"
+ result = df.to_latex(index_names=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lr}
+ \toprule
+ & a \\
+ \midrule
+ 0 & 1 \\
+ 1 & 2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+
+class TestToLatexLongtable:
+ def test_to_latex_empty_longtable(self):
+ df = DataFrame()
+ result = df.to_latex(longtable=True)
+ expected = _dedent(
+ r"""
+ \begin{longtable}{l}
+ \toprule
+ \midrule
+ \endfirsthead
+ \toprule
+ \midrule
+ \endhead
+ \midrule
+ \multicolumn{0}{r}{Continued on next page} \\
+ \midrule
+ \endfoot
+ \bottomrule
+ \endlastfoot
+ \end{longtable}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_longtable_with_index(self):
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(longtable=True)
+ expected = _dedent(
+ r"""
+ \begin{longtable}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ \endfirsthead
+ \toprule
+ & a & b \\
+ \midrule
+ \endhead
+ \midrule
+ \multicolumn{3}{r}{Continued on next page} \\
+ \midrule
+ \endfoot
+ \bottomrule
+ \endlastfoot
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \end{longtable}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_longtable_without_index(self):
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(index=False, longtable=True)
+ expected = _dedent(
+ r"""
+ \begin{longtable}{rl}
+ \toprule
+ a & b \\
+ \midrule
+ \endfirsthead
+ \toprule
+ a & b \\
+ \midrule
+ \endhead
+ \midrule
+ \multicolumn{2}{r}{Continued on next page} \\
+ \midrule
+ \endfoot
+ \bottomrule
+ \endlastfoot
+ 1 & b1 \\
+ 2 & b2 \\
+ \end{longtable}
+ """
+ )
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "df_data, expected_number",
+ [
+ ({"a": [1, 2]}, 1),
+ ({"a": [1, 2], "b": [3, 4]}, 2),
+ ({"a": [1, 2], "b": [3, 4], "c": [5, 6]}, 3),
+ ],
+ )
+ def test_to_latex_longtable_continued_on_next_page(self, df_data, expected_number):
+ df = DataFrame(df_data)
+ result = df.to_latex(index=False, longtable=True)
+ assert rf"\multicolumn{{{expected_number}}}" in result
+
+
+class TestToLatexHeader:
+ def test_to_latex_no_header_with_index(self):
+ # GH 7124
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(header=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrl}
+ \toprule
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_no_header_without_index(self):
+ # GH 7124
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(index=False, header=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{rl}
+ \toprule
+ \midrule
+ 1 & b1 \\
+ 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_specified_header_with_index(self):
+ # GH 7124
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(header=["AA", "BB"])
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrl}
+ \toprule
+ & AA & BB \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_specified_header_without_index(self):
+ # GH 7124
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(header=["AA", "BB"], index=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{rl}
+ \toprule
+ AA & BB \\
+ \midrule
+ 1 & b1 \\
+ 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "header, num_aliases",
+ [
+ (["A"], 1),
+ (("B",), 1),
+ (("Col1", "Col2", "Col3"), 3),
+ (("Col1", "Col2", "Col3", "Col4"), 4),
+ ],
+ )
+ def test_to_latex_number_of_items_in_header_missmatch_raises(
+ self,
+ header,
+ num_aliases,
+ ):
+ # GH 7124
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ msg = f"Writing 2 cols but got {num_aliases} aliases"
+ with pytest.raises(ValueError, match=msg):
+ df.to_latex(header=header)
+
+ def test_to_latex_decimal(self):
+ # GH 12031
+ df = DataFrame({"a": [1.0, 2.1], "b": ["b1", "b2"]})
+ result = df.to_latex(decimal=",")
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1,000000 & b1 \\
+ 1 & 2,100000 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+
+class TestToLatexBold:
+ def test_to_latex_bold_rows(self):
+ # GH 16707
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(bold_rows=True)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ \textbf{0} & 1 & b1 \\
+ \textbf{1} & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_no_bold_rows(self):
+ # GH 16707
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(bold_rows=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+
+class TestToLatexCaptionLabel:
+ @pytest.fixture
+ def caption_table(self):
+ """Caption for table/tabular LaTeX environment."""
+ return "a table in a \\texttt{table/tabular} environment"
+
+ @pytest.fixture
+ def short_caption(self):
+ """Short caption for testing \\caption[short_caption]{full_caption}."""
+ return "a table"
+
+ @pytest.fixture
+ def label_table(self):
+ """Label for table/tabular LaTeX environment."""
+ return "tab:table_tabular"
+
+ @pytest.fixture
+ def caption_longtable(self):
+ """Caption for longtable LaTeX environment."""
+ return "a table in a \\texttt{longtable} environment"
+
+ @pytest.fixture
+ def label_longtable(self):
+ """Label for longtable LaTeX environment."""
+ return "tab:longtable"
+
+ def test_to_latex_caption_only(self, df_short, caption_table):
+ # GH 25436
+ result = df_short.to_latex(caption=caption_table)
+ expected = _dedent(
+ r"""
+ \begin{table}
+ \caption{a table in a \texttt{table/tabular} environment}
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ \end{table}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_label_only(self, df_short, label_table):
+ # GH 25436
+ result = df_short.to_latex(label=label_table)
+ expected = _dedent(
+ r"""
+ \begin{table}
+ \label{tab:table_tabular}
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ \end{table}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_caption_and_label(self, df_short, caption_table, label_table):
+ # GH 25436
+ result = df_short.to_latex(caption=caption_table, label=label_table)
+ expected = _dedent(
+ r"""
+ \begin{table}
+ \caption{a table in a \texttt{table/tabular} environment}
+ \label{tab:table_tabular}
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ \end{table}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_caption_and_shortcaption(
+ self,
+ df_short,
+ caption_table,
+ short_caption,
+ ):
+ result = df_short.to_latex(caption=(caption_table, short_caption))
+ expected = _dedent(
+ r"""
+ \begin{table}
+ \caption[a table]{a table in a \texttt{table/tabular} environment}
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ \end{table}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_caption_and_shortcaption_list_is_ok(self, df_short):
+ caption = ("Long-long-caption", "Short")
+ result_tuple = df_short.to_latex(caption=caption)
+ result_list = df_short.to_latex(caption=list(caption))
+ assert result_tuple == result_list
+
+ def test_to_latex_caption_shortcaption_and_label(
+ self,
+ df_short,
+ caption_table,
+ short_caption,
+ label_table,
+ ):
+ # test when the short_caption is provided alongside caption and label
+ result = df_short.to_latex(
+ caption=(caption_table, short_caption),
+ label=label_table,
+ )
+ expected = _dedent(
+ r"""
+ \begin{table}
+ \caption[a table]{a table in a \texttt{table/tabular} environment}
+ \label{tab:table_tabular}
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ \end{table}
+ """
+ )
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "bad_caption",
+ [
+ ("full_caption", "short_caption", "extra_string"),
+ ("full_caption", "short_caption", 1),
+ ("full_caption", "short_caption", None),
+ ("full_caption",),
+ (None,),
+ ],
+ )
+ def test_to_latex_bad_caption_raises(self, bad_caption):
+ # test that wrong number of params is raised
+ df = DataFrame({"a": [1]})
+ msg = "`caption` must be either a string or 2-tuple of strings"
+ with pytest.raises(ValueError, match=msg):
+ df.to_latex(caption=bad_caption)
+
+ def test_to_latex_two_chars_caption(self, df_short):
+ # test that two chars caption is handled correctly
+ # it must not be unpacked into long_caption, short_caption.
+ result = df_short.to_latex(caption="xy")
+ expected = _dedent(
+ r"""
+ \begin{table}
+ \caption{xy}
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ \end{table}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_longtable_caption_only(self, df_short, caption_longtable):
+ # GH 25436
+ # test when no caption and no label is provided
+ # is performed by test_to_latex_longtable()
+ result = df_short.to_latex(longtable=True, caption=caption_longtable)
+ expected = _dedent(
+ r"""
+ \begin{longtable}{lrl}
+ \caption{a table in a \texttt{longtable} environment} \\
+ \toprule
+ & a & b \\
+ \midrule
+ \endfirsthead
+ \caption[]{a table in a \texttt{longtable} environment} \\
+ \toprule
+ & a & b \\
+ \midrule
+ \endhead
+ \midrule
+ \multicolumn{3}{r}{Continued on next page} \\
+ \midrule
+ \endfoot
+ \bottomrule
+ \endlastfoot
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \end{longtable}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_longtable_label_only(self, df_short, label_longtable):
+ # GH 25436
+ result = df_short.to_latex(longtable=True, label=label_longtable)
+ expected = _dedent(
+ r"""
+ \begin{longtable}{lrl}
+ \label{tab:longtable} \\
+ \toprule
+ & a & b \\
+ \midrule
+ \endfirsthead
+ \toprule
+ & a & b \\
+ \midrule
+ \endhead
+ \midrule
+ \multicolumn{3}{r}{Continued on next page} \\
+ \midrule
+ \endfoot
+ \bottomrule
+ \endlastfoot
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \end{longtable}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_longtable_caption_and_label(
+ self,
+ df_short,
+ caption_longtable,
+ label_longtable,
+ ):
+ # GH 25436
+ result = df_short.to_latex(
+ longtable=True,
+ caption=caption_longtable,
+ label=label_longtable,
+ )
+ expected = _dedent(
+ r"""
+ \begin{longtable}{lrl}
+ \caption{a table in a \texttt{longtable} environment} \label{tab:longtable} \\
+ \toprule
+ & a & b \\
+ \midrule
+ \endfirsthead
+ \caption[]{a table in a \texttt{longtable} environment} \\
+ \toprule
+ & a & b \\
+ \midrule
+ \endhead
+ \midrule
+ \multicolumn{3}{r}{Continued on next page} \\
+ \midrule
+ \endfoot
+ \bottomrule
+ \endlastfoot
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \end{longtable}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_longtable_caption_shortcaption_and_label(
+ self,
+ df_short,
+ caption_longtable,
+ short_caption,
+ label_longtable,
+ ):
+ # test when the caption, the short_caption and the label are provided
+ result = df_short.to_latex(
+ longtable=True,
+ caption=(caption_longtable, short_caption),
+ label=label_longtable,
+ )
+ expected = _dedent(
+ r"""
+\begin{longtable}{lrl}
+\caption[a table]{a table in a \texttt{longtable} environment} \label{tab:longtable} \\
+\toprule
+ & a & b \\
+\midrule
+\endfirsthead
+\caption[]{a table in a \texttt{longtable} environment} \\
+\toprule
+ & a & b \\
+\midrule
+\endhead
+\midrule
+\multicolumn{3}{r}{Continued on next page} \\
+\midrule
+\endfoot
+\bottomrule
+\endlastfoot
+0 & 1 & b1 \\
+1 & 2 & b2 \\
+\end{longtable}
+"""
+ )
+ assert result == expected
+
+
+class TestToLatexEscape:
+ @pytest.fixture
+ def df_with_symbols(self):
+ """Dataframe with special characters for testing chars escaping."""
+ a = "a"
+ b = "b"
+ return DataFrame({"co$e^x$": {a: "a", b: "b"}, "co^l1": {a: "a", b: "b"}})
+
+ def test_to_latex_escape_false(self, df_with_symbols):
+ result = df_with_symbols.to_latex(escape=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lll}
+ \toprule
+ & co$e^x$ & co^l1 \\
+ \midrule
+ a & a & a \\
+ b & b & b \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_escape_default(self, df_with_symbols):
+ # gh50871: in v2.0 escape is False by default (styler.format.escape=None)
+ default = df_with_symbols.to_latex()
+ specified_true = df_with_symbols.to_latex(escape=True)
+ assert default != specified_true
+
+ def test_to_latex_special_escape(self):
+ df = DataFrame([r"a\b\c", r"^a^b^c", r"~a~b~c"])
+ result = df.to_latex(escape=True)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{ll}
+ \toprule
+ & 0 \\
+ \midrule
+ 0 & a\textbackslash b\textbackslash c \\
+ 1 & \textasciicircum a\textasciicircum b\textasciicircum c \\
+ 2 & \textasciitilde a\textasciitilde b\textasciitilde c \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_escape_special_chars(self):
+ special_characters = ["&", "%", "$", "#", "_", "{", "}", "~", "^", "\\"]
+ df = DataFrame(data=special_characters)
+ result = df.to_latex(escape=True)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{ll}
+ \toprule
+ & 0 \\
+ \midrule
+ 0 & \& \\
+ 1 & \% \\
+ 2 & \$ \\
+ 3 & \# \\
+ 4 & \_ \\
+ 5 & \{ \\
+ 6 & \} \\
+ 7 & \textasciitilde \\
+ 8 & \textasciicircum \\
+ 9 & \textbackslash \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_escape_special_chars_in_index_names(self):
+ # https://github.com/pandas-dev/pandas/issues/61309
+ # https://github.com/pandas-dev/pandas/issues/57362
+ index = "&%$#_{}}~^\\"
+ df = DataFrame({index: [1, 2, 3]}).set_index(index)
+ result = df.to_latex(escape=True)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{l}
+ \toprule
+ \&\%\$\#\_\{\}\}\textasciitilde \textasciicircum \textbackslash \\
+ \midrule
+ 1 \\
+ 2 \\
+ 3 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_escape_special_chars_in_column_name(self):
+ df = DataFrame({"A": [1, 2, 3], "B": ["a", "b", "c"]})
+ df.columns.name = "_^~"
+ result = df.to_latex(escape=True)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrl}
+ \toprule
+ \_\textasciicircum \textasciitilde & A & B \\
+ \midrule
+ 0 & 1 & a \\
+ 1 & 2 & b \\
+ 2 & 3 & c \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_specified_header_special_chars_without_escape(self):
+ # GH 7124
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(header=["$A$", "$B$"], escape=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrl}
+ \toprule
+ & $A$ & $B$ \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+
+class TestToLatexPosition:
+ def test_to_latex_position(self):
+ the_position = "h"
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(position=the_position)
+ expected = _dedent(
+ r"""
+ \begin{table}[h]
+ \begin{tabular}{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \bottomrule
+ \end{tabular}
+ \end{table}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_longtable_position(self):
+ the_position = "t"
+ df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
+ result = df.to_latex(longtable=True, position=the_position)
+ expected = _dedent(
+ r"""
+ \begin{longtable}[t]{lrl}
+ \toprule
+ & a & b \\
+ \midrule
+ \endfirsthead
+ \toprule
+ & a & b \\
+ \midrule
+ \endhead
+ \midrule
+ \multicolumn{3}{r}{Continued on next page} \\
+ \midrule
+ \endfoot
+ \bottomrule
+ \endlastfoot
+ 0 & 1 & b1 \\
+ 1 & 2 & b2 \\
+ \end{longtable}
+ """
+ )
+ assert result == expected
+
+
+class TestToLatexFormatters:
+ def test_to_latex_with_formatters(self):
+ df = DataFrame(
+ {
+ "datetime64": [
+ datetime(2016, 1, 1),
+ datetime(2016, 2, 5),
+ datetime(2016, 3, 3),
+ ],
+ "float": [1.0, 2.0, 3.0],
+ "int": [1, 2, 3],
+ "object": [(1, 2), True, False],
+ }
+ )
+
+ formatters = {
+ "datetime64": lambda x: x.strftime("%Y-%m"),
+ "float": lambda x: f"[{x: 4.1f}]",
+ "int": lambda x: f"0x{x:x}",
+ "object": lambda x: f"-{x!s}-",
+ "__index__": lambda x: f"index: {x}",
+ }
+ result = df.to_latex(formatters=dict(formatters))
+
+ expected = _dedent(
+ r"""
+ \begin{tabular}{llrrl}
+ \toprule
+ & datetime64 & float & int & object \\
+ \midrule
+ index: 0 & 2016-01 & [ 1.0] & 0x1 & -(1, 2)- \\
+ index: 1 & 2016-02 & [ 2.0] & 0x2 & -True- \\
+ index: 2 & 2016-03 & [ 3.0] & 0x3 & -False- \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_float_format_no_fixed_width_3decimals(self):
+ # GH 21625
+ df = DataFrame({"x": [0.19999]})
+ result = df.to_latex(float_format="%.3f")
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lr}
+ \toprule
+ & x \\
+ \midrule
+ 0 & 0.200 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_float_format_no_fixed_width_integer(self):
+ # GH 22270
+ df = DataFrame({"x": [100.0]})
+ result = df.to_latex(float_format="%.0f")
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lr}
+ \toprule
+ & x \\
+ \midrule
+ 0 & 100 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ @pytest.mark.parametrize("na_rep", ["NaN", "Ted"])
+ def test_to_latex_na_rep_and_float_format(self, na_rep):
+ df = DataFrame(
+ [
+ ["A", 1.2225],
+ ["A", None],
+ ],
+ columns=["Group", "Data"],
+ )
+ result = df.to_latex(na_rep=na_rep, float_format="{:.2f}".format)
+ expected = _dedent(
+ rf"""
+ \begin{{tabular}}{{llr}}
+ \toprule
+ & Group & Data \\
+ \midrule
+ 0 & A & 1.22 \\
+ 1 & A & {na_rep} \\
+ \bottomrule
+ \end{{tabular}}
+ """
+ )
+ assert result == expected
+
+
+class TestToLatexMultiindex:
+ @pytest.fixture
+ def multiindex_frame(self):
+ """Multiindex dataframe for testing multirow LaTeX macros."""
+ return DataFrame.from_dict(
+ {
+ ("c1", 0): Series({x: x for x in range(4)}),
+ ("c1", 1): Series({x: x + 4 for x in range(4)}),
+ ("c2", 0): Series({x: x for x in range(4)}),
+ ("c2", 1): Series({x: x + 4 for x in range(4)}),
+ ("c3", 0): Series({x: x for x in range(4)}),
+ }
+ ).T
+
+ @pytest.fixture
+ def multicolumn_frame(self):
+ """Multicolumn dataframe for testing multicolumn LaTeX macros."""
+ return DataFrame(
+ {
+ ("c1", 0): {x: x for x in range(5)},
+ ("c1", 1): {x: x + 5 for x in range(5)},
+ ("c2", 0): {x: x for x in range(5)},
+ ("c2", 1): {x: x + 5 for x in range(5)},
+ ("c3", 0): {x: x for x in range(5)},
+ }
+ )
+
+ def test_to_latex_multindex_header(self):
+ # GH 16718
+ df = DataFrame({"a": [0], "b": [1], "c": [2], "d": [3]})
+ df = df.set_index(["a", "b"])
+ observed = df.to_latex(header=["r1", "r2"], multirow=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{llrr}
+ \toprule
+ & & r1 & r2 \\
+ a & b & & \\
+ \midrule
+ 0 & 1 & 2 & 3 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert observed == expected
+
+ def test_to_latex_multiindex_empty_name(self):
+ # GH 18669
+ mi = pd.MultiIndex.from_product([[1, 2]], names=[""])
+ df = DataFrame(-1, index=mi, columns=range(4))
+ observed = df.to_latex()
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrrrr}
+ \toprule
+ & 0 & 1 & 2 & 3 \\
+ & & & & \\
+ \midrule
+ 1 & -1 & -1 & -1 & -1 \\
+ 2 & -1 & -1 & -1 & -1 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert observed == expected
+
+ def test_to_latex_multiindex_column_tabular(self):
+ df = DataFrame({("x", "y"): ["a"]})
+ result = df.to_latex()
+ expected = _dedent(
+ r"""
+ \begin{tabular}{ll}
+ \toprule
+ & x \\
+ & y \\
+ \midrule
+ 0 & a \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multiindex_small_tabular(self):
+ df = DataFrame({("x", "y"): ["a"]}).T
+ result = df.to_latex(multirow=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lll}
+ \toprule
+ & & 0 \\
+ \midrule
+ x & y & a \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multiindex_tabular(self, multiindex_frame):
+ result = multiindex_frame.to_latex(multirow=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{llrrrr}
+ \toprule
+ & & 0 & 1 & 2 & 3 \\
+ \midrule
+ c1 & 0 & 0 & 1 & 2 & 3 \\
+ & 1 & 4 & 5 & 6 & 7 \\
+ c2 & 0 & 0 & 1 & 2 & 3 \\
+ & 1 & 4 & 5 & 6 & 7 \\
+ c3 & 0 & 0 & 1 & 2 & 3 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multicolumn_tabular(self, multiindex_frame):
+ # GH 14184
+ df = multiindex_frame.T
+ df.columns.names = ["a", "b"]
+ result = df.to_latex(multirow=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrrrrr}
+ \toprule
+ a & \multicolumn{2}{r}{c1} & \multicolumn{2}{r}{c2} & c3 \\
+ b & 0 & 1 & 0 & 1 & 0 \\
+ \midrule
+ 0 & 0 & 4 & 0 & 4 & 0 \\
+ 1 & 1 & 5 & 1 & 5 & 1 \\
+ 2 & 2 & 6 & 2 & 6 & 2 \\
+ 3 & 3 & 7 & 3 & 7 & 3 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_index_has_name_tabular(self):
+ # GH 10660
+ df = DataFrame({"a": [0, 0, 1, 1], "b": list("abab"), "c": [1, 2, 3, 4]})
+ result = df.set_index(["a", "b"]).to_latex(multirow=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{llr}
+ \toprule
+ & & c \\
+ a & b & \\
+ \midrule
+ 0 & a & 1 \\
+ & b & 2 \\
+ 1 & a & 3 \\
+ & b & 4 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_groupby_tabular(self):
+ # GH 10660
+ df = DataFrame({"a": [0, 0, 1, 1], "b": list("abab"), "c": [1, 2, 3, 4]})
+ result = (
+ df.groupby("a")
+ .describe()
+ .to_latex(float_format="{:.1f}".format, escape=True)
+ )
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrrrrrrrr}
+ \toprule
+ & \multicolumn{8}{r}{c} \\
+ & count & mean & std & min & 25\% & 50\% & 75\% & max \\
+ a & & & & & & & & \\
+ \midrule
+ 0 & 2.0 & 1.5 & 0.7 & 1.0 & 1.2 & 1.5 & 1.8 & 2.0 \\
+ 1 & 2.0 & 3.5 & 0.7 & 3.0 & 3.2 & 3.5 & 3.8 & 4.0 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multiindex_dupe_level(self):
+ # see gh-14484
+ #
+ # If an index is repeated in subsequent rows, it should be
+ # replaced with a blank in the created table. This should
+ # ONLY happen if all higher order indices (to the left) are
+ # equal too. In this test, 'c' has to be printed both times
+ # because the higher order index 'A' != 'B'.
+ df = DataFrame(
+ index=pd.MultiIndex.from_tuples([("A", "c"), ("B", "c")]), columns=["col"]
+ )
+ result = df.to_latex(multirow=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lll}
+ \toprule
+ & & col \\
+ \midrule
+ A & c & NaN \\
+ B & c & NaN \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multicolumn_default(self, multicolumn_frame):
+ result = multicolumn_frame.to_latex()
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrrrrr}
+ \toprule
+ & \multicolumn{2}{r}{c1} & \multicolumn{2}{r}{c2} & c3 \\
+ & 0 & 1 & 0 & 1 & 0 \\
+ \midrule
+ 0 & 0 & 5 & 0 & 5 & 0 \\
+ 1 & 1 & 6 & 1 & 6 & 1 \\
+ 2 & 2 & 7 & 2 & 7 & 2 \\
+ 3 & 3 & 8 & 3 & 8 & 3 \\
+ 4 & 4 & 9 & 4 & 9 & 4 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multicolumn_false(self, multicolumn_frame):
+ result = multicolumn_frame.to_latex(multicolumn=False, multicolumn_format="l")
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lrrrrr}
+ \toprule
+ & c1 & & c2 & & c3 \\
+ & 0 & 1 & 0 & 1 & 0 \\
+ \midrule
+ 0 & 0 & 5 & 0 & 5 & 0 \\
+ 1 & 1 & 6 & 1 & 6 & 1 \\
+ 2 & 2 & 7 & 2 & 7 & 2 \\
+ 3 & 3 & 8 & 3 & 8 & 3 \\
+ 4 & 4 & 9 & 4 & 9 & 4 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multirow_true(self, multicolumn_frame):
+ result = multicolumn_frame.T.to_latex(multirow=True)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{llrrrrr}
+ \toprule
+ & & 0 & 1 & 2 & 3 & 4 \\
+ \midrule
+ \multirow[t]{2}{*}{c1} & 0 & 0 & 1 & 2 & 3 & 4 \\
+ & 1 & 5 & 6 & 7 & 8 & 9 \\
+ \cline{1-7}
+ \multirow[t]{2}{*}{c2} & 0 & 0 & 1 & 2 & 3 & 4 \\
+ & 1 & 5 & 6 & 7 & 8 & 9 \\
+ \cline{1-7}
+ c3 & 0 & 0 & 1 & 2 & 3 & 4 \\
+ \cline{1-7}
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multicolumnrow_with_multicol_format(self, multicolumn_frame):
+ multicolumn_frame.index = multicolumn_frame.T.index
+ result = multicolumn_frame.T.to_latex(
+ multirow=True,
+ multicolumn=True,
+ multicolumn_format="c",
+ )
+ expected = _dedent(
+ r"""
+ \begin{tabular}{llrrrrr}
+ \toprule
+ & & \multicolumn{2}{c}{c1} & \multicolumn{2}{c}{c2} & c3 \\
+ & & 0 & 1 & 0 & 1 & 0 \\
+ \midrule
+ \multirow[t]{2}{*}{c1} & 0 & 0 & 1 & 2 & 3 & 4 \\
+ & 1 & 5 & 6 & 7 & 8 & 9 \\
+ \cline{1-7}
+ \multirow[t]{2}{*}{c2} & 0 & 0 & 1 & 2 & 3 & 4 \\
+ & 1 & 5 & 6 & 7 & 8 & 9 \\
+ \cline{1-7}
+ c3 & 0 & 0 & 1 & 2 & 3 & 4 \\
+ \cline{1-7}
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ @pytest.mark.parametrize("name0", [None, "named0"])
+ @pytest.mark.parametrize("name1", [None, "named1"])
+ @pytest.mark.parametrize("axes", [[0], [1], [0, 1]])
+ def test_to_latex_multiindex_names(self, name0, name1, axes):
+ # GH 18667
+ names = [name0, name1]
+ mi = pd.MultiIndex.from_product([[1, 2], [3, 4]])
+ df = DataFrame(-1, index=mi.copy(), columns=mi.copy())
+ for idx in axes:
+ df.axes[idx].names = names
+
+ idx_names = tuple(n or "" for n in names)
+ idx_names_row = (
+ f"{idx_names[0]} & {idx_names[1]} & & & & \\\\\n"
+ if (0 in axes and any(names))
+ else ""
+ )
+ col_names = [n if (bool(n) and 1 in axes) else "" for n in names]
+ observed = df.to_latex(multirow=False)
+ expected = r"""\begin{{tabular}}{{llrrrr}}
+\toprule
+ & {} & \multicolumn{{2}}{{r}}{{1}} & \multicolumn{{2}}{{r}}{{2}} \\
+ & {} & 3 & 4 & 3 & 4 \\
+{}\midrule
+1 & 3 & -1 & -1 & -1 & -1 \\
+ & 4 & -1 & -1 & -1 & -1 \\
+2 & 3 & -1 & -1 & -1 & -1 \\
+ & 4 & -1 & -1 & -1 & -1 \\
+\bottomrule
+\end{{tabular}}
+""".format(*col_names, idx_names_row)
+ assert observed == expected
+
+ @pytest.mark.parametrize("one_row", [True, False])
+ def test_to_latex_multiindex_nans(self, one_row):
+ # GH 14249
+ df = DataFrame({"a": [None, 1], "b": [2, 3], "c": [4, 5]})
+ if one_row:
+ df = df.iloc[[0]]
+ observed = df.set_index(["a", "b"]).to_latex(multirow=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{llr}
+ \toprule
+ & & c \\
+ a & b & \\
+ \midrule
+ NaN & 2 & 4 \\
+ """
+ )
+ if not one_row:
+ expected += r"""1.000000 & 3 & 5 \\
+"""
+ expected += r"""\bottomrule
+\end{tabular}
+"""
+ assert observed == expected
+
+ def test_to_latex_non_string_index(self):
+ # GH 19981
+ df = DataFrame([[1, 2, 3]] * 2).set_index([0, 1])
+ result = df.to_latex(multirow=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{llr}
+ \toprule
+ & & 2 \\
+ 0 & 1 & \\
+ \midrule
+ 1 & 2 & 3 \\
+ & 2 & 3 \\
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multiindex_multirow(self):
+ # GH 16719
+ mi = pd.MultiIndex.from_product(
+ [[0.0, 1.0], [3.0, 2.0, 1.0], ["0", "1"]], names=["i", "val0", "val1"]
+ )
+ df = DataFrame(index=mi)
+ result = df.to_latex(multirow=True, escape=False)
+ expected = _dedent(
+ r"""
+ \begin{tabular}{lll}
+ \toprule
+ i & val0 & val1 \\
+ \midrule
+ \multirow[t]{6}{*}{0.000000} & \multirow[t]{2}{*}{3.000000} & 0 \\
+ & & 1 \\
+ \cline{2-3}
+ & \multirow[t]{2}{*}{2.000000} & 0 \\
+ & & 1 \\
+ \cline{2-3}
+ & \multirow[t]{2}{*}{1.000000} & 0 \\
+ & & 1 \\
+ \cline{1-3} \cline{2-3}
+ \multirow[t]{6}{*}{1.000000} & \multirow[t]{2}{*}{3.000000} & 0 \\
+ & & 1 \\
+ \cline{2-3}
+ & \multirow[t]{2}{*}{2.000000} & 0 \\
+ & & 1 \\
+ \cline{2-3}
+ & \multirow[t]{2}{*}{1.000000} & 0 \\
+ & & 1 \\
+ \cline{1-3} \cline{2-3}
+ \bottomrule
+ \end{tabular}
+ """
+ )
+ assert result == expected
+
+ def test_to_latex_multiindex_format_single_index_hidden(self):
+ # GH 52218
+ df = DataFrame(
+ {
+ "A": [1, 2],
+ "B": [4, 5],
+ }
+ )
+ result = (
+ df.style.hide(axis="index")
+ .map_index(lambda v: "textbf:--rwrap;", axis="columns")
+ .to_latex()
+ )
+ expected = _dedent(r"""
+ \begin{tabular}{rr}
+ \textbf{A} & \textbf{B} \\
+ 1 & 4 \\
+ 2 & 5 \\
+ \end{tabular}
+ """)
+ assert result == expected
+
+ def test_to_latex_multiindex_format_triple_index_two_hidden(self):
+ # GH 52218
+ arrays = [
+ ["A", "A", "B", "B"],
+ ["one", "two", "one", "two"],
+ ["x", "x", "y", "y"],
+ ]
+ index = pd.MultiIndex.from_arrays(
+ arrays, names=["Level 0", "Level 1", "Level 2"]
+ )
+ df = DataFrame(
+ [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
+ index=index,
+ columns=["C1", "C2", "C3"],
+ )
+ result = (
+ df.style.hide(axis="index", level=[0, 1])
+ .map_index(lambda v: "textbf:--rwrap;", axis="columns")
+ .to_latex()
+ )
+ expected = _dedent(r"""
+ \begin{tabular}{lrrr}
+ & \textbf{C1} & \textbf{C2} & \textbf{C3} \\
+ Level 2 & & & \\
+ x & 0 & 0 & 0 \\
+ x & 0 & 0 & 0 \\
+ y & 0 & 0 & 0 \\
+ y & 0 & 0 & 0 \\
+ \end{tabular}
+ """)
+ assert result == expected
+
+ def test_to_latex_multiindex_format_triple_index_all_hidden(self):
+ # GH 52218
+ arrays = [
+ ["A", "A", "B", "B"],
+ ["one", "two", "one", "two"],
+ ["x", "x", "y", "y"],
+ ]
+ index = pd.MultiIndex.from_arrays(
+ arrays, names=["Level 0", "Level 1", "Level 2"]
+ )
+ df = DataFrame(
+ [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
+ index=index,
+ columns=["C1", "C2", "C3"],
+ )
+ result = (
+ df.style.hide(axis="index", level=[0, 1, 2])
+ .map_index(lambda v: "textbf:--rwrap;", axis="columns")
+ .to_latex()
+ )
+ expected = _dedent(r"""
+ \begin{tabular}{rrr}
+ \textbf{C1} & \textbf{C2} & \textbf{C3} \\
+ 0 & 0 & 0 \\
+ 0 & 0 & 0 \\
+ 0 & 0 & 0 \\
+ 0 & 0 & 0 \\
+ \end{tabular}
+ """)
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_markdown.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_markdown.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b1e22ce1bb06108eeb13ff9076531ea96f08f47
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_markdown.py
@@ -0,0 +1,102 @@
+from io import StringIO
+
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+import pandas._testing as tm
+
+pytest.importorskip("tabulate")
+
+
+def test_keyword_deprecation():
+ # GH 57280
+ msg = (
+ "Starting with pandas version 4.0 all arguments of to_markdown "
+ "except for the argument 'buf' will be keyword-only."
+ )
+ s = pd.Series()
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ s.to_markdown(None, "wt")
+
+
+def test_simple():
+ buf = StringIO()
+ df = pd.DataFrame([1, 2, 3])
+ df.to_markdown(buf=buf)
+ result = buf.getvalue()
+ assert (
+ result == "| | 0 |\n|---:|----:|\n| 0 | 1 |\n| 1 | 2 |\n| 2 | 3 |"
+ )
+
+
+def test_empty_frame():
+ buf = StringIO()
+ df = pd.DataFrame({"id": [], "first_name": [], "last_name": []}).set_index("id")
+ df.to_markdown(buf=buf)
+ result = buf.getvalue()
+ assert result == (
+ "| id | first_name | last_name |\n|------|--------------|-------------|"
+ )
+
+
+def test_other_tablefmt():
+ buf = StringIO()
+ df = pd.DataFrame([1, 2, 3])
+ df.to_markdown(buf=buf, tablefmt="jira")
+ result = buf.getvalue()
+ assert result == "|| || 0 ||\n| 0 | 1 |\n| 1 | 2 |\n| 2 | 3 |"
+
+
+def test_other_headers():
+ buf = StringIO()
+ df = pd.DataFrame([1, 2, 3])
+ df.to_markdown(buf=buf, headers=["foo", "bar"])
+ result = buf.getvalue()
+ assert result == (
+ "| foo | bar |\n|------:|------:|\n| 0 "
+ "| 1 |\n| 1 | 2 |\n| 2 | 3 |"
+ )
+
+
+def test_series():
+ buf = StringIO()
+ s = pd.Series([1, 2, 3], name="foo")
+ s.to_markdown(buf=buf)
+ result = buf.getvalue()
+ assert result == (
+ "| | foo |\n|---:|------:|\n| 0 | 1 |\n| 1 | 2 |\n| 2 | 3 |"
+ )
+
+
+def test_no_buf():
+ df = pd.DataFrame([1, 2, 3])
+ result = df.to_markdown()
+ assert (
+ result == "| | 0 |\n|---:|----:|\n| 0 | 1 |\n| 1 | 2 |\n| 2 | 3 |"
+ )
+
+
+@pytest.mark.parametrize("index", [True, False])
+def test_index(index):
+ # GH 32667
+
+ df = pd.DataFrame([1, 2, 3])
+
+ result = df.to_markdown(index=index)
+
+ if index:
+ expected = (
+ "| | 0 |\n|---:|----:|\n| 0 | 1 |\n| 1 | 2 |\n| 2 | 3 |"
+ )
+ else:
+ expected = "| 0 |\n|----:|\n| 1 |\n| 2 |\n| 3 |"
+ assert result == expected
+
+
+def test_showindex_disallowed_in_kwargs():
+ # GH 32667; disallowing showindex in kwargs enforced in 2.0
+ df = pd.DataFrame([1, 2, 3])
+ with pytest.raises(ValueError, match="Pass 'index' instead of 'showindex"):
+ df.to_markdown(index=True, showindex=True)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_string.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_string.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d414cef152c71ea35e10f8c910a1c39a5a051ae
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/formats/test_to_string.py
@@ -0,0 +1,1226 @@
+from datetime import (
+ datetime,
+ timedelta,
+)
+from io import StringIO
+import re
+import sys
+from textwrap import dedent
+
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ CategoricalIndex,
+ DataFrame,
+ Index,
+ NaT,
+ Series,
+ Timestamp,
+ concat,
+ date_range,
+ get_option,
+ option_context,
+ read_csv,
+ timedelta_range,
+ to_datetime,
+)
+import pandas._testing as tm
+
+
+def _three_digit_exp():
+ return f"{1.7e8:.4g}" == "1.7e+008"
+
+
+class TestDataFrameToStringFormatters:
+ def test_keyword_deprecation(self):
+ # GH 57280
+ msg = (
+ "Starting with pandas version 4.0 all arguments of to_string "
+ "except for the argument 'buf' will be keyword-only."
+ )
+ s = Series(["a", "b"])
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ s.to_string(None, "NaN")
+
+ def test_to_string_masked_ea_with_formatter(self):
+ # GH#39336
+ df = DataFrame(
+ {
+ "a": Series([0.123456789, 1.123456789], dtype="Float64"),
+ "b": Series([1, 2], dtype="Int64"),
+ }
+ )
+ result = df.to_string(formatters=["{:.2f}".format, "{:.2f}".format])
+ expected = dedent(
+ """\
+ a b
+ 0 0.12 1.00
+ 1 1.12 2.00"""
+ )
+ assert result == expected
+
+ def test_to_string_with_formatters(self):
+ df = DataFrame(
+ {
+ "int": [1, 2, 3],
+ "float": [1.0, 2.0, 3.0],
+ "object": [(1, 2), True, False],
+ },
+ columns=["int", "float", "object"],
+ )
+
+ formatters = [
+ ("int", lambda x: f"0x{x:x}"),
+ ("float", lambda x: f"[{x: 4.1f}]"),
+ ("object", lambda x: f"-{x!s}-"),
+ ]
+ result = df.to_string(formatters=dict(formatters))
+ result2 = df.to_string(formatters=list(zip(*formatters))[1])
+ assert result == (
+ " int float object\n"
+ "0 0x1 [ 1.0] -(1, 2)-\n"
+ "1 0x2 [ 2.0] -True-\n"
+ "2 0x3 [ 3.0] -False-"
+ )
+ assert result == result2
+
+ def test_to_string_with_datetime64_monthformatter(self):
+ months = [datetime(2016, 1, 1), datetime(2016, 2, 2)]
+ x = DataFrame({"months": months})
+
+ def format_func(x):
+ return x.strftime("%Y-%m")
+
+ result = x.to_string(formatters={"months": format_func})
+ expected = dedent(
+ """\
+ months
+ 0 2016-01
+ 1 2016-02"""
+ )
+ assert result.strip() == expected
+
+ def test_to_string_with_datetime64_hourformatter(self):
+ x = DataFrame(
+ {"hod": to_datetime(["10:10:10.100", "12:12:12.120"], format="%H:%M:%S.%f")}
+ )
+
+ def format_func(x):
+ return x.strftime("%H:%M")
+
+ result = x.to_string(formatters={"hod": format_func})
+ expected = dedent(
+ """\
+ hod
+ 0 10:10
+ 1 12:12"""
+ )
+ assert result.strip() == expected
+
+ def test_to_string_with_formatters_unicode(self):
+ df = DataFrame({"c/\u03c3": [1, 2, 3]})
+ result = df.to_string(formatters={"c/\u03c3": str})
+ expected = dedent(
+ """\
+ c/\u03c3
+ 0 1
+ 1 2
+ 2 3"""
+ )
+ assert result == expected
+
+ def test_to_string_index_formatter(self):
+ df = DataFrame([range(5), range(5, 10), range(10, 15)])
+ rs = df.to_string(formatters={"__index__": lambda x: "abc"[x]})
+ xp = dedent(
+ """\
+ 0 1 2 3 4
+ a 0 1 2 3 4
+ b 5 6 7 8 9
+ c 10 11 12 13 14"""
+ )
+ assert rs == xp
+
+ def test_no_extra_space(self):
+ # GH#52690: Check that no extra space is given
+ col1 = "TEST"
+ col2 = "PANDAS"
+ col3 = "to_string"
+ expected = f"{col1:<6s} {col2:<7s} {col3:<10s}"
+ df = DataFrame([{"col1": "TEST", "col2": "PANDAS", "col3": "to_string"}])
+ d = {"col1": "{:<6s}".format, "col2": "{:<7s}".format, "col3": "{:<10s}".format}
+ result = df.to_string(index=False, header=False, formatters=d)
+ assert result == expected
+
+
+class TestDataFrameToStringColSpace:
+ def test_to_string_with_column_specific_col_space_raises(self):
+ df = DataFrame(
+ np.random.default_rng(2).random(size=(3, 3)), columns=["a", "b", "c"]
+ )
+
+ msg = (
+ "Col_space length\\(\\d+\\) should match "
+ "DataFrame number of columns\\(\\d+\\)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.to_string(col_space=[30, 40])
+
+ with pytest.raises(ValueError, match=msg):
+ df.to_string(col_space=[30, 40, 50, 60])
+
+ msg = "unknown column"
+ with pytest.raises(ValueError, match=msg):
+ df.to_string(col_space={"a": "foo", "b": 23, "d": 34})
+
+ def test_to_string_with_column_specific_col_space(self):
+ df = DataFrame(
+ np.random.default_rng(2).random(size=(3, 3)), columns=["a", "b", "c"]
+ )
+
+ result = df.to_string(col_space={"a": 10, "b": 11, "c": 12})
+ # 3 separating space + each col_space for (id, a, b, c)
+ assert len(result.split("\n")[1]) == (3 + 1 + 10 + 11 + 12)
+
+ result = df.to_string(col_space=[10, 11, 12])
+ assert len(result.split("\n")[1]) == (3 + 1 + 10 + 11 + 12)
+
+ def test_to_string_with_col_space(self):
+ df = DataFrame(np.random.default_rng(2).random(size=(1, 3)))
+ c10 = len(df.to_string(col_space=10).split("\n")[1])
+ c20 = len(df.to_string(col_space=20).split("\n")[1])
+ c30 = len(df.to_string(col_space=30).split("\n")[1])
+ assert c10 < c20 < c30
+
+ # GH#8230
+ # col_space wasn't being applied with header=False
+ with_header = df.to_string(col_space=20)
+ with_header_row1 = with_header.splitlines()[1]
+ no_header = df.to_string(col_space=20, header=False)
+ assert len(with_header_row1) == len(no_header)
+
+ def test_to_string_repr_tuples(self):
+ buf = StringIO()
+
+ df = DataFrame({"tups": list(zip(range(10), range(10)))})
+ repr(df)
+ df.to_string(col_space=10, buf=buf)
+
+
+class TestDataFrameToStringHeader:
+ def test_to_string_header_false(self):
+ # GH#49230
+ df = DataFrame([1, 2])
+ df.index.name = "a"
+ s = df.to_string(header=False)
+ expected = "a \n0 1\n1 2"
+ assert s == expected
+
+ df = DataFrame([[1, 2], [3, 4]])
+ df.index.name = "a"
+ s = df.to_string(header=False)
+ expected = "a \n0 1 2\n1 3 4"
+ assert s == expected
+
+ def test_to_string_multindex_header(self):
+ # GH#16718
+ df = DataFrame({"a": [0], "b": [1], "c": [2], "d": [3]}).set_index(["a", "b"])
+ res = df.to_string(header=["r1", "r2"])
+ exp = " r1 r2\na b \n0 1 2 3"
+ assert res == exp
+
+ def test_to_string_no_header(self):
+ df = DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
+
+ df_s = df.to_string(header=False)
+ expected = "0 1 4\n1 2 5\n2 3 6"
+
+ assert df_s == expected
+
+ def test_to_string_specified_header(self):
+ df = DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
+
+ df_s = df.to_string(header=["X", "Y"])
+ expected = " X Y\n0 1 4\n1 2 5\n2 3 6"
+
+ assert df_s == expected
+
+ msg = "Writing 2 cols but got 1 aliases"
+ with pytest.raises(ValueError, match=msg):
+ df.to_string(header=["X"])
+
+
+class TestDataFrameToStringLineWidth:
+ def test_to_string_line_width(self):
+ df = DataFrame(123, index=range(10, 15), columns=range(30))
+ lines = df.to_string(line_width=80)
+ assert max(len(line) for line in lines.split("\n")) == 80
+
+ def test_to_string_line_width_no_index(self):
+ # GH#13998, GH#22505
+ df = DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
+
+ df_s = df.to_string(line_width=1, index=False)
+ expected = " x \\\n 1 \n 2 \n 3 \n\n y \n 4 \n 5 \n 6 "
+
+ assert df_s == expected
+
+ df = DataFrame({"x": [11, 22, 33], "y": [4, 5, 6]})
+
+ df_s = df.to_string(line_width=1, index=False)
+ expected = " x \\\n11 \n22 \n33 \n\n y \n 4 \n 5 \n 6 "
+
+ assert df_s == expected
+
+ df = DataFrame({"x": [11, 22, -33], "y": [4, 5, -6]})
+
+ df_s = df.to_string(line_width=1, index=False)
+ expected = " x \\\n 11 \n 22 \n-33 \n\n y \n 4 \n 5 \n-6 "
+
+ assert df_s == expected
+
+ def test_to_string_line_width_no_header(self):
+ # GH#53054
+ df = DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
+
+ df_s = df.to_string(line_width=1, header=False)
+ expected = "0 1 \\\n1 2 \n2 3 \n\n0 4 \n1 5 \n2 6 "
+
+ assert df_s == expected
+
+ df = DataFrame({"x": [11, 22, 33], "y": [4, 5, 6]})
+
+ df_s = df.to_string(line_width=1, header=False)
+ expected = "0 11 \\\n1 22 \n2 33 \n\n0 4 \n1 5 \n2 6 "
+
+ assert df_s == expected
+
+ df = DataFrame({"x": [11, 22, -33], "y": [4, 5, -6]})
+
+ df_s = df.to_string(line_width=1, header=False)
+ expected = "0 11 \\\n1 22 \n2 -33 \n\n0 4 \n1 5 \n2 -6 "
+
+ assert df_s == expected
+
+ def test_to_string_line_width_with_both_index_and_header(self):
+ # GH#53054
+ df = DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
+
+ df_s = df.to_string(line_width=1)
+ expected = (
+ " x \\\n0 1 \n1 2 \n2 3 \n\n y \n0 4 \n1 5 \n2 6 "
+ )
+
+ assert df_s == expected
+
+ df = DataFrame({"x": [11, 22, 33], "y": [4, 5, 6]})
+
+ df_s = df.to_string(line_width=1)
+ expected = (
+ " x \\\n0 11 \n1 22 \n2 33 \n\n y \n0 4 \n1 5 \n2 6 "
+ )
+
+ assert df_s == expected
+
+ df = DataFrame({"x": [11, 22, -33], "y": [4, 5, -6]})
+
+ df_s = df.to_string(line_width=1)
+ expected = (
+ " x \\\n0 11 \n1 22 \n2 -33 \n\n y \n0 4 \n1 5 \n2 -6 "
+ )
+
+ assert df_s == expected
+
+ def test_to_string_line_width_no_index_no_header(self):
+ # GH#53054
+ df = DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
+
+ df_s = df.to_string(line_width=1, index=False, header=False)
+ expected = "1 \\\n2 \n3 \n\n4 \n5 \n6 "
+
+ assert df_s == expected
+
+ df = DataFrame({"x": [11, 22, 33], "y": [4, 5, 6]})
+
+ df_s = df.to_string(line_width=1, index=False, header=False)
+ expected = "11 \\\n22 \n33 \n\n4 \n5 \n6 "
+
+ assert df_s == expected
+
+ df = DataFrame({"x": [11, 22, -33], "y": [4, 5, -6]})
+
+ df_s = df.to_string(line_width=1, index=False, header=False)
+ expected = " 11 \\\n 22 \n-33 \n\n 4 \n 5 \n-6 "
+
+ assert df_s == expected
+
+
+class TestToStringNumericFormatting:
+ def test_to_string_float_format_no_fixed_width(self):
+ # GH#21625
+ df = DataFrame({"x": [0.19999]})
+ expected = " x\n0 0.200"
+ assert df.to_string(float_format="%.3f") == expected
+
+ # GH#22270
+ df = DataFrame({"x": [100.0]})
+ expected = " x\n0 100"
+ assert df.to_string(float_format="%.0f") == expected
+
+ def test_to_string_small_float_values(self):
+ df = DataFrame({"a": [1.5, 1e-17, -5.5e-7]})
+
+ result = df.to_string()
+ # sadness per above
+ if _three_digit_exp():
+ expected = (
+ " a\n0 1.500000e+000\n1 1.000000e-017\n2 -5.500000e-007"
+ )
+ else:
+ expected = (
+ " a\n0 1.500000e+00\n1 1.000000e-17\n2 -5.500000e-07"
+ )
+ assert result == expected
+
+ # but not all exactly zero
+ df = df * 0
+ result = df.to_string()
+ expected = " 0\n0 0\n1 0\n2 -0"
+ # TODO: assert that these match??
+
+ def test_to_string_complex_float_formatting(self):
+ # GH #25514, 25745
+ with option_context("display.precision", 5):
+ df = DataFrame(
+ {
+ "x": [
+ (0.4467846931321966 + 0.0715185102060818j),
+ (0.2739442392974528 + 0.23515228785438969j),
+ (0.26974928742135185 + 0.3250604054898979j),
+ (-1j),
+ ]
+ }
+ )
+ result = df.to_string()
+ expected = (
+ " x\n0 0.44678+0.07152j\n"
+ "1 0.27394+0.23515j\n"
+ "2 0.26975+0.32506j\n"
+ "3 -0.00000-1.00000j"
+ )
+ assert result == expected
+
+ def test_to_string_complex_float_formatting_with_exponents(self):
+ # GH #60393
+ with option_context("display.precision", 6):
+ df = DataFrame(
+ {
+ "x": [
+ (1.8816e-09 + 0j),
+ (1.8816e-09 + 3.39676e-09j),
+ ]
+ }
+ )
+ result = df.to_string()
+ expected = (
+ " x\n0 1.881600e-09+0.000000e+00j\n"
+ "1 1.881600e-09+3.396760e-09j"
+ )
+ assert result == expected
+
+ def test_to_string_format_inf(self):
+ # GH#24861
+ df = DataFrame(
+ {
+ "A": [-np.inf, np.inf, -1, -2.1234, 3, 4],
+ "B": [-np.inf, np.inf, "foo", "foooo", "fooooo", "bar"],
+ }
+ )
+ result = df.to_string()
+
+ expected = (
+ " A B\n"
+ "0 -inf -inf\n"
+ "1 inf inf\n"
+ "2 -1.0000 foo\n"
+ "3 -2.1234 foooo\n"
+ "4 3.0000 fooooo\n"
+ "5 4.0000 bar"
+ )
+ assert result == expected
+
+ df = DataFrame(
+ {
+ "A": [-np.inf, np.inf, -1.0, -2.0, 3.0, 4.0],
+ "B": [-np.inf, np.inf, "foo", "foooo", "fooooo", "bar"],
+ }
+ )
+ result = df.to_string()
+
+ expected = (
+ " A B\n"
+ "0 -inf -inf\n"
+ "1 inf inf\n"
+ "2 -1.0 foo\n"
+ "3 -2.0 foooo\n"
+ "4 3.0 fooooo\n"
+ "5 4.0 bar"
+ )
+ assert result == expected
+
+ def test_to_string_int_formatting(self):
+ df = DataFrame({"x": [-15, 20, 25, -35]})
+ assert issubclass(df["x"].dtype.type, np.integer)
+
+ output = df.to_string()
+ expected = " x\n0 -15\n1 20\n2 25\n3 -35"
+ assert output == expected
+
+ def test_to_string_float_formatting(self):
+ with option_context(
+ "display.precision",
+ 5,
+ "display.notebook_repr_html",
+ False,
+ ):
+ df = DataFrame(
+ {"x": [0, 0.25, 3456.000, 12e45, 1.64e6, 1.7e8, 1.253456, np.pi, -1e6]}
+ )
+
+ df_s = df.to_string()
+
+ if _three_digit_exp():
+ expected = (
+ " x\n0 0.00000e+000\n1 2.50000e-001\n"
+ "2 3.45600e+003\n3 1.20000e+046\n4 1.64000e+006\n"
+ "5 1.70000e+008\n6 1.25346e+000\n7 3.14159e+000\n"
+ "8 -1.00000e+006"
+ )
+ else:
+ expected = (
+ " x\n0 0.00000e+00\n1 2.50000e-01\n"
+ "2 3.45600e+03\n3 1.20000e+46\n4 1.64000e+06\n"
+ "5 1.70000e+08\n6 1.25346e+00\n7 3.14159e+00\n"
+ "8 -1.00000e+06"
+ )
+ assert df_s == expected
+
+ df = DataFrame({"x": [3234, 0.253]})
+ df_s = df.to_string()
+
+ expected = " x\n0 3234.000\n1 0.253"
+ assert df_s == expected
+
+ assert get_option("display.precision") == 6
+
+ df = DataFrame({"x": [1e9, 0.2512]})
+ df_s = df.to_string()
+
+ if _three_digit_exp():
+ expected = " x\n0 1.000000e+009\n1 2.512000e-001"
+ else:
+ expected = " x\n0 1.000000e+09\n1 2.512000e-01"
+ assert df_s == expected
+
+
+class TestDataFrameToString:
+ def test_to_string_decimal(self):
+ # GH#23614
+ df = DataFrame({"A": [6.0, 3.1, 2.2]})
+ expected = " A\n0 6,0\n1 3,1\n2 2,2"
+ assert df.to_string(decimal=",") == expected
+
+ def test_to_string_left_justify_cols(self):
+ df = DataFrame({"x": [3234, 0.253]})
+ df_s = df.to_string(justify="left")
+ expected = " x \n0 3234.000\n1 0.253"
+ assert df_s == expected
+
+ def test_to_string_format_na(self):
+ df = DataFrame(
+ {
+ "A": [np.nan, -1, -2.1234, 3, 4],
+ "B": [np.nan, "foo", "foooo", "fooooo", "bar"],
+ }
+ )
+ result = df.to_string()
+
+ expected = (
+ " A B\n"
+ "0 NaN NaN\n"
+ "1 -1.0000 foo\n"
+ "2 -2.1234 foooo\n"
+ "3 3.0000 fooooo\n"
+ "4 4.0000 bar"
+ )
+ assert result == expected
+
+ df = DataFrame(
+ {
+ "A": [np.nan, -1.0, -2.0, 3.0, 4.0],
+ "B": [np.nan, "foo", "foooo", "fooooo", "bar"],
+ }
+ )
+ result = df.to_string()
+
+ expected = (
+ " A B\n"
+ "0 NaN NaN\n"
+ "1 -1.0 foo\n"
+ "2 -2.0 foooo\n"
+ "3 3.0 fooooo\n"
+ "4 4.0 bar"
+ )
+ assert result == expected
+
+ def test_to_string_with_dict_entries(self):
+ df = DataFrame({"A": [{"a": 1, "b": 2}]})
+
+ val = df.to_string()
+ assert "'a': 1" in val
+ assert "'b': 2" in val
+
+ def test_to_string_with_categorical_columns(self):
+ # GH#35439
+ data = [[4, 2], [3, 2], [4, 3]]
+ cols = ["aaaaaaaaa", "b"]
+ df = DataFrame(data, columns=cols)
+ df_cat_cols = DataFrame(data, columns=CategoricalIndex(cols))
+
+ assert df.to_string() == df_cat_cols.to_string()
+
+ def test_repr_embedded_ndarray(self):
+ arr = np.empty(10, dtype=[("err", object)])
+ for i in range(len(arr)):
+ arr["err"][i] = np.random.default_rng(2).standard_normal(i)
+
+ df = DataFrame(arr)
+ repr(df["err"])
+ repr(df)
+ df.to_string()
+
+ def test_to_string_truncate(self):
+ # GH 9784 - dont truncate when calling DataFrame.to_string
+ df = DataFrame(
+ [
+ {
+ "a": "foo",
+ "b": "bar",
+ "c": "let's make this a very VERY long line that is longer "
+ "than the default 50 character limit",
+ "d": 1,
+ },
+ {"a": "foo", "b": "bar", "c": "stuff", "d": 1},
+ ]
+ )
+ df.set_index(["a", "b", "c"])
+ assert df.to_string() == (
+ " a b "
+ " c d\n"
+ "0 foo bar let's make this a very VERY long line t"
+ "hat is longer than the default 50 character limit 1\n"
+ "1 foo bar "
+ " stuff 1"
+ )
+ with option_context("max_colwidth", 20):
+ # the display option has no effect on the to_string method
+ assert df.to_string() == (
+ " a b "
+ " c d\n"
+ "0 foo bar let's make this a very VERY long line t"
+ "hat is longer than the default 50 character limit 1\n"
+ "1 foo bar "
+ " stuff 1"
+ )
+ assert df.to_string(max_colwidth=20) == (
+ " a b c d\n"
+ "0 foo bar let's make this ... 1\n"
+ "1 foo bar stuff 1"
+ )
+
+ @pytest.mark.parametrize(
+ "input_array, expected",
+ [
+ ({"A": ["a"]}, "A\na"),
+ ({"A": ["a", "b"], "B": ["c", "dd"]}, "A B\na c\nb dd"),
+ ({"A": ["a", 1], "B": ["aa", 1]}, "A B\na aa\n1 1"),
+ ],
+ )
+ def test_format_remove_leading_space_dataframe(self, input_array, expected):
+ # GH#24980
+ df = DataFrame(input_array).to_string(index=False)
+ assert df == expected
+
+ @pytest.mark.parametrize(
+ "data,expected",
+ [
+ (
+ {"col1": [1, 2], "col2": [3, 4]},
+ " col1 col2\n0 1 3\n1 2 4",
+ ),
+ (
+ {"col1": ["Abc", 0.756], "col2": [np.nan, 4.5435]},
+ " col1 col2\n0 Abc NaN\n1 0.756 4.5435",
+ ),
+ (
+ {"col1": [np.nan, "a"], "col2": [0.009, 3.543], "col3": ["Abc", 23]},
+ " col1 col2 col3\n0 NaN 0.009 Abc\n1 a 3.543 23",
+ ),
+ ],
+ )
+ def test_to_string_max_rows_zero(self, data, expected):
+ # GH#35394
+ result = DataFrame(data=data).to_string(max_rows=0)
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "max_cols, max_rows, expected",
+ [
+ (
+ 10,
+ None,
+ " 0 1 2 3 4 ... 6 7 8 9 10\n"
+ " 0 0 0 0 0 ... 0 0 0 0 0\n"
+ " 0 0 0 0 0 ... 0 0 0 0 0\n"
+ " 0 0 0 0 0 ... 0 0 0 0 0\n"
+ " 0 0 0 0 0 ... 0 0 0 0 0",
+ ),
+ (
+ None,
+ 2,
+ " 0 1 2 3 4 5 6 7 8 9 10\n"
+ " 0 0 0 0 0 0 0 0 0 0 0\n"
+ " .. .. .. .. .. .. .. .. .. .. ..\n"
+ " 0 0 0 0 0 0 0 0 0 0 0",
+ ),
+ (
+ 10,
+ 2,
+ " 0 1 2 3 4 ... 6 7 8 9 10\n"
+ " 0 0 0 0 0 ... 0 0 0 0 0\n"
+ " .. .. .. .. .. ... .. .. .. .. ..\n"
+ " 0 0 0 0 0 ... 0 0 0 0 0",
+ ),
+ (
+ 9,
+ 2,
+ " 0 1 2 3 ... 7 8 9 10\n"
+ " 0 0 0 0 ... 0 0 0 0\n"
+ " .. .. .. .. ... .. .. .. ..\n"
+ " 0 0 0 0 ... 0 0 0 0",
+ ),
+ (
+ 1,
+ 1,
+ " 0 ...\n 0 ...\n.. ...",
+ ),
+ ],
+ )
+ def test_truncation_no_index(self, max_cols, max_rows, expected):
+ df = DataFrame([[0] * 11] * 4)
+ assert (
+ df.to_string(index=False, max_cols=max_cols, max_rows=max_rows) == expected
+ )
+
+ def test_to_string_no_index(self):
+ # GH#16839, GH#13032
+ df = DataFrame({"x": [11, 22], "y": [33, -44], "z": ["AAA", " "]})
+
+ df_s = df.to_string(index=False)
+ # Leading space is expected for positive numbers.
+ expected = " x y z\n11 33 AAA\n22 -44 "
+ assert df_s == expected
+
+ df_s = df[["y", "x", "z"]].to_string(index=False)
+ expected = " y x z\n 33 11 AAA\n-44 22 "
+ assert df_s == expected
+
+ def test_to_string_unicode_columns(self, float_frame):
+ df = DataFrame({"\u03c3": np.arange(10.0)})
+
+ buf = StringIO()
+ df.to_string(buf=buf)
+ buf.getvalue()
+
+ buf = StringIO()
+ df.info(buf=buf)
+ buf.getvalue()
+
+ result = float_frame.to_string()
+ assert isinstance(result, str)
+
+ @pytest.mark.parametrize("na_rep", ["NaN", "Ted"])
+ def test_to_string_na_rep_and_float_format(self, na_rep):
+ # GH#13828
+ df = DataFrame([["A", 1.2225], ["A", None]], columns=["Group", "Data"])
+ result = df.to_string(na_rep=na_rep, float_format="{:.2f}".format)
+ expected = dedent(
+ f"""\
+ Group Data
+ 0 A 1.22
+ 1 A {na_rep}"""
+ )
+ assert result == expected
+
+ def test_to_string_string_dtype(self):
+ # GH#50099
+ pytest.importorskip("pyarrow")
+ df = DataFrame(
+ {"x": ["foo", "bar", "baz"], "y": ["a", "b", "c"], "z": [1, 2, 3]}
+ )
+ df = df.astype(
+ {"x": "string[pyarrow]", "y": "string[python]", "z": "int64[pyarrow]"}
+ )
+ result = df.dtypes.to_string()
+ expected = dedent(
+ """\
+ x string
+ y string
+ z int64[pyarrow]"""
+ )
+ assert result == expected
+
+ def test_to_string_utf8_columns(self):
+ n = "\u05d0".encode()
+ df = DataFrame([1, 2], columns=[n])
+
+ with option_context("display.max_rows", 1):
+ repr(df)
+
+ def test_to_string_unicode_two(self):
+ dm = DataFrame({"c/\u03c3": []})
+ buf = StringIO()
+ dm.to_string(buf)
+
+ def test_to_string_unicode_three(self):
+ dm = DataFrame(["\xc2"])
+ buf = StringIO()
+ dm.to_string(buf)
+
+ def test_to_string_with_float_index(self):
+ index = Index([1.5, 2, 3, 4, 5])
+ df = DataFrame(np.arange(5), index=index)
+
+ result = df.to_string()
+ expected = " 0\n1.5 0\n2.0 1\n3.0 2\n4.0 3\n5.0 4"
+ assert result == expected
+
+ def test_to_string(self):
+ # big mixed
+ biggie = DataFrame(
+ {
+ "A": np.random.default_rng(2).standard_normal(200),
+ "B": Index([f"{i}?!" for i in range(200)]),
+ },
+ )
+
+ biggie.loc[:20, "A"] = np.nan
+ biggie.loc[:20, "B"] = np.nan
+ s = biggie.to_string()
+
+ buf = StringIO()
+ retval = biggie.to_string(buf=buf)
+ assert retval is None
+ assert buf.getvalue() == s
+
+ assert isinstance(s, str)
+
+ # print in right order
+ result = biggie.to_string(
+ columns=["B", "A"], col_space=17, float_format="%.5f".__mod__
+ )
+ lines = result.split("\n")
+ header = lines[0].strip().split()
+ joined = "\n".join([re.sub(r"\s+", " ", x).strip() for x in lines[1:]])
+ recons = read_csv(StringIO(joined), names=header, header=None, sep=" ")
+ tm.assert_series_equal(recons["B"], biggie["B"])
+ assert recons["A"].count() == biggie["A"].count()
+ assert (np.abs(recons["A"].dropna() - biggie["A"].dropna()) < 0.1).all()
+
+ # FIXME: don't leave commented-out
+ # expected = ['B', 'A']
+ # assert header == expected
+
+ result = biggie.to_string(columns=["A"], col_space=17)
+ header = result.split("\n")[0].strip().split()
+ expected = ["A"]
+ assert header == expected
+
+ biggie.to_string(columns=["B", "A"], formatters={"A": lambda x: f"{x:.1f}"})
+
+ biggie.to_string(columns=["B", "A"], float_format=str)
+ biggie.to_string(columns=["B", "A"], col_space=12, float_format=str)
+
+ frame = DataFrame(index=np.arange(200))
+ frame.to_string()
+
+ # TODO: split or simplify this test?
+ @pytest.mark.xfail(using_string_dtype(), reason="fix when arrow is default")
+ def test_to_string_index_with_nan(self):
+ # GH#2850
+ df = DataFrame(
+ {
+ "id1": {0: "1a3", 1: "9h4"},
+ "id2": {0: np.nan, 1: "d67"},
+ "id3": {0: "78d", 1: "79d"},
+ "value": {0: 123, 1: 64},
+ }
+ )
+
+ # multi-index
+ y = df.set_index(["id1", "id2", "id3"])
+ result = y.to_string()
+ expected = (
+ " value\nid1 id2 id3 \n"
+ "1a3 NaN 78d 123\n9h4 d67 79d 64"
+ )
+ assert result == expected
+
+ # index
+ y = df.set_index("id2")
+ result = y.to_string()
+ expected = (
+ " id1 id3 value\nid2 \n"
+ "NaN 1a3 78d 123\nd67 9h4 79d 64"
+ )
+ assert result == expected
+
+ # with append (this failed in 0.12)
+ y = df.set_index(["id1", "id2"]).set_index("id3", append=True)
+ result = y.to_string()
+ expected = (
+ " value\nid1 id2 id3 \n"
+ "1a3 NaN 78d 123\n9h4 d67 79d 64"
+ )
+ assert result == expected
+
+ # all-nan in mi
+ df2 = df.copy()
+ df2.loc[:, "id2"] = np.nan
+ y = df2.set_index("id2")
+ result = y.to_string()
+ expected = (
+ " id1 id3 value\nid2 \n"
+ "NaN 1a3 78d 123\nNaN 9h4 79d 64"
+ )
+ assert result == expected
+
+ # partial nan in mi
+ df2 = df.copy()
+ df2.loc[:, "id2"] = np.nan
+ y = df2.set_index(["id2", "id3"])
+ result = y.to_string()
+ expected = (
+ " id1 value\nid2 id3 \n"
+ "NaN 78d 1a3 123\n 79d 9h4 64"
+ )
+ assert result == expected
+
+ df = DataFrame(
+ {
+ "id1": {0: np.nan, 1: "9h4"},
+ "id2": {0: np.nan, 1: "d67"},
+ "id3": {0: np.nan, 1: "79d"},
+ "value": {0: 123, 1: 64},
+ }
+ )
+
+ y = df.set_index(["id1", "id2", "id3"])
+ result = y.to_string()
+ expected = (
+ " value\nid1 id2 id3 \n"
+ "NaN NaN NaN 123\n9h4 d67 79d 64"
+ )
+ assert result == expected
+
+ def test_to_string_nonunicode_nonascii_alignment(self):
+ df = DataFrame([["aa\xc3\xa4\xc3\xa4", 1], ["bbbb", 2]])
+ rep_str = df.to_string()
+ lines = rep_str.split("\n")
+ assert len(lines[1]) == len(lines[2])
+
+ def test_unicode_problem_decoding_as_ascii(self):
+ df = DataFrame({"c/\u03c3": Series({"test": np.nan})})
+ str(df.to_string())
+
+ def test_to_string_repr_unicode(self):
+ buf = StringIO()
+
+ unicode_values = ["\u03c3"] * 10
+ unicode_values = np.array(unicode_values, dtype=object)
+ df = DataFrame({"unicode": unicode_values})
+ df.to_string(col_space=10, buf=buf)
+
+ # it works!
+ repr(df)
+ # it works even if sys.stdin in None
+ _stdin = sys.stdin
+ try:
+ sys.stdin = None
+ repr(df)
+ finally:
+ sys.stdin = _stdin
+
+ def test_nested_dataframe(self):
+ df1 = DataFrame({"level1": [["row1"], ["row2"]]})
+ df2 = DataFrame({"level3": [{"level2": df1}]})
+ result = df2.to_string()
+ expected = " level3\n0 {'level2': ['level1']}"
+ assert result == expected
+
+
+class TestSeriesToString:
+ def test_to_string_without_index(self):
+ # GH#11729 Test index=False option
+ ser = Series([1, 2, 3, 4])
+ result = ser.to_string(index=False)
+ expected = "\n".join(["1", "2", "3", "4"])
+ assert result == expected
+
+ def test_to_string_name(self):
+ ser = Series(range(100), dtype="int64")
+ ser.name = "myser"
+ res = ser.to_string(max_rows=2, name=True)
+ exp = "0 0\n ..\n99 99\nName: myser"
+ assert res == exp
+ res = ser.to_string(max_rows=2, name=False)
+ exp = "0 0\n ..\n99 99"
+ assert res == exp
+
+ def test_to_string_dtype(self):
+ ser = Series(range(100), dtype="int64")
+ res = ser.to_string(max_rows=2, dtype=True)
+ exp = "0 0\n ..\n99 99\ndtype: int64"
+ assert res == exp
+ res = ser.to_string(max_rows=2, dtype=False)
+ exp = "0 0\n ..\n99 99"
+ assert res == exp
+
+ def test_to_string_length(self):
+ ser = Series(range(100), dtype="int64")
+ res = ser.to_string(max_rows=2, length=True)
+ exp = "0 0\n ..\n99 99\nLength: 100"
+ assert res == exp
+
+ def test_to_string_na_rep(self):
+ ser = Series(index=range(100), dtype=np.float64)
+ res = ser.to_string(na_rep="foo", max_rows=2)
+ exp = "0 foo\n ..\n99 foo"
+ assert res == exp
+
+ def test_to_string_float_format(self):
+ ser = Series(range(10), dtype="float64")
+ res = ser.to_string(float_format=lambda x: f"{x:2.1f}", max_rows=2)
+ exp = "0 0.0\n ..\n9 9.0"
+ assert res == exp
+
+ def test_to_string_header(self):
+ ser = Series(range(10), dtype="int64")
+ ser.index.name = "foo"
+ res = ser.to_string(header=True, max_rows=2)
+ exp = "foo\n0 0\n ..\n9 9"
+ assert res == exp
+ res = ser.to_string(header=False, max_rows=2)
+ exp = "0 0\n ..\n9 9"
+ assert res == exp
+
+ def test_to_string_empty_col(self):
+ # GH#13653
+ ser = Series(["", "Hello", "World", "", "", "Mooooo", "", ""])
+ res = ser.to_string(index=False)
+ exp = " \n Hello\n World\n \n \nMooooo\n \n "
+ assert re.match(exp, res)
+
+ def test_to_string_timedelta64(self):
+ Series(np.array([1100, 20], dtype="timedelta64[ns]")).to_string()
+
+ ser = Series(date_range("2012-1-1", periods=3, freq="D"))
+
+ # GH#2146
+
+ # adding NaTs
+ y = ser - ser.shift(1)
+ result = y.to_string()
+ assert "1 days" in result
+ assert "00:00:00" not in result
+ assert "NaT" in result
+
+ # with frac seconds
+ o = Series([datetime(2012, 1, 1, microsecond=150)] * 3)
+ y = ser - o
+ result = y.to_string()
+ assert "-1 days +23:59:59.999850" in result
+
+ # rounding?
+ o = Series([datetime(2012, 1, 1, 1)] * 3)
+ y = ser - o
+ result = y.to_string()
+ assert "-1 days +23:00:00" in result
+ assert "1 days 23:00:00" in result
+
+ o = Series([datetime(2012, 1, 1, 1, 1)] * 3)
+ y = ser - o
+ result = y.to_string()
+ assert "-1 days +22:59:00" in result
+ assert "1 days 22:59:00" in result
+
+ o = Series([datetime(2012, 1, 1, 1, 1, microsecond=150)] * 3)
+ y = ser - o
+ result = y.to_string()
+ assert "-1 days +22:58:59.999850" in result
+ assert "0 days 22:58:59.999850" in result
+
+ # neg time
+ td = timedelta(minutes=5, seconds=3)
+ s2 = Series(date_range("2012-1-1", periods=3, freq="D")) + td
+ y = ser - s2
+ result = y.to_string()
+ assert "-1 days +23:54:57" in result
+
+ td = timedelta(microseconds=550)
+ s2 = Series(date_range("2012-1-1", periods=3, freq="D")) + td
+ y = ser - td
+ result = y.to_string()
+ assert "2012-01-01 23:59:59.999450" in result
+
+ # no boxing of the actual elements
+ td = Series(timedelta_range("1 days", periods=3))
+ result = td.to_string()
+ assert result == "0 1 days\n1 2 days\n2 3 days"
+
+ def test_to_string(self):
+ ts = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10, freq="B"),
+ )
+ buf = StringIO()
+
+ s = ts.to_string()
+
+ retval = ts.to_string(buf=buf)
+ assert retval is None
+ assert buf.getvalue().strip() == s
+
+ # pass float_format
+ format = "%.4f".__mod__
+ result = ts.to_string(float_format=format)
+ result = [x.split()[1] for x in result.split("\n")[:-1]]
+ expected = [format(x) for x in ts]
+ assert result == expected
+
+ # empty string
+ result = ts[:0].to_string()
+ assert result == "Series([], Freq: B)"
+
+ result = ts[:0].to_string(length=0)
+ assert result == "Series([], Freq: B)"
+
+ # name and length
+ cp = ts.copy()
+ cp.name = "foo"
+ result = cp.to_string(length=True, name=True, dtype=True)
+ last_line = result.split("\n")[-1].strip()
+ assert last_line == (f"Freq: B, Name: foo, Length: {len(cp)}, dtype: float64")
+
+ @pytest.mark.parametrize(
+ "input_array, expected",
+ [
+ ("a", "a"),
+ (["a", "b"], "a\nb"),
+ ([1, "a"], "1\na"),
+ (1, "1"),
+ ([0, -1], " 0\n-1"),
+ (1.0, "1.0"),
+ ([" a", " b"], " a\n b"),
+ ([".1", "1"], ".1\n 1"),
+ (["10", "-10"], " 10\n-10"),
+ ],
+ )
+ def test_format_remove_leading_space_series(self, input_array, expected):
+ # GH: 24980
+ ser = Series(input_array)
+ result = ser.to_string(index=False)
+ assert result == expected
+
+ def test_to_string_complex_number_trims_zeros(self):
+ ser = Series([1.000000 + 1.000000j, 1.0 + 1.0j, 1.05 + 1.0j])
+ result = ser.to_string()
+ expected = dedent(
+ """\
+ 0 1.00+1.00j
+ 1 1.00+1.00j
+ 2 1.05+1.00j"""
+ )
+ assert result == expected
+
+ def test_nullable_float_to_string(self, float_ea_dtype):
+ # https://github.com/pandas-dev/pandas/issues/36775
+ dtype = float_ea_dtype
+ ser = Series([0.0, 1.0, None], dtype=dtype)
+ result = ser.to_string()
+ expected = dedent(
+ """\
+ 0 0.0
+ 1 1.0
+ 2 """
+ )
+ assert result == expected
+
+ def test_nullable_int_to_string(self, any_int_ea_dtype):
+ # https://github.com/pandas-dev/pandas/issues/36775
+ dtype = any_int_ea_dtype
+ ser = Series([0, 1, None], dtype=dtype)
+ result = ser.to_string()
+ expected = dedent(
+ """\
+ 0 0
+ 1 1
+ 2 """
+ )
+ assert result == expected
+
+ def test_to_string_mixed(self):
+ ser = Series(["foo", np.nan, -1.23, 4.56])
+ result = ser.to_string()
+ expected = "".join(["0 foo\n", "1 NaN\n", "2 -1.23\n", "3 4.56"])
+ assert result == expected
+
+ # but don't count NAs as floats
+ ser = Series(["foo", np.nan, "bar", "baz"])
+ result = ser.to_string()
+ expected = "".join(["0 foo\n", "1 NaN\n", "2 bar\n", "3 baz"])
+ assert result == expected
+
+ ser = Series(["foo", 5, "bar", "baz"])
+ result = ser.to_string()
+ expected = "".join(["0 foo\n", "1 5\n", "2 bar\n", "3 baz"])
+ assert result == expected
+
+ def test_to_string_float_na_spacing(self):
+ ser = Series([0.0, 1.5678, 2.0, -3.0, 4.0])
+ ser[::2] = np.nan
+
+ result = ser.to_string()
+ expected = "0 NaN\n1 1.5678\n2 NaN\n3 -3.0000\n4 NaN"
+ assert result == expected
+
+ def test_to_string_with_datetimeindex(self):
+ index = date_range("20130102", periods=6)
+ ser = Series(1, index=index)
+ result = ser.to_string()
+ assert "2013-01-02" in result
+
+ # nat in index
+ s2 = Series(2, index=[Timestamp("20130111"), NaT])
+ ser = concat([s2, ser])
+ result = ser.to_string()
+ assert "NaT" in result
+
+ # nat in summary
+ result = str(s2.index)
+ assert "NaT" in result
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/generate_legacy_storage_files.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/generate_legacy_storage_files.py
new file mode 100644
index 0000000000000000000000000000000000000000..04f176a550edf0b927727788e958421b422c26d0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/generate_legacy_storage_files.py
@@ -0,0 +1,421 @@
+"""
+self-contained to write legacy storage pickle files
+
+To use this script. Create an environment where you want
+generate pickles, say its for 0.20.3, with your pandas clone
+in ~/pandas
+
+. activate pandas_0.20.3
+cd ~/pandas/pandas
+
+$ python -m tests.io.generate_legacy_storage_files \
+ tests/io/data/legacy_pickle/0.20.3/ pickle
+
+This script generates a storage file for the current arch, system,
+and python version
+ pandas version: 0.20.3
+ output dir : pandas/pandas/tests/io/data/legacy_pickle/0.20.3/
+ storage format: pickle
+created pickle file: 0.20.3_x86_64_darwin_3.5.2.pickle
+
+The idea here is you are using the *current* version of the
+generate_legacy_storage_files with an *older* version of pandas to
+generate a pickle file. We will then check this file into a current
+branch, and test using test_pickle.py. This will load the *older*
+pickles and test versus the current data that is generated
+(with main). These are then compared.
+
+If we have cases where we changed the signature (e.g. we renamed
+offset -> freq in Timestamp). Then we have to conditionally execute
+in the generate_legacy_storage_files.py to make it
+run under the older AND the newer version.
+
+"""
+
+from datetime import timedelta
+import os
+import pickle
+import platform as pl
+import sys
+
+# Remove script directory from path, otherwise Python will try to
+# import the JSON test directory as the json module
+sys.path.pop(0)
+
+import numpy as np
+
+import pandas
+from pandas import (
+ Categorical,
+ DataFrame,
+ Index,
+ MultiIndex,
+ NaT,
+ Period,
+ RangeIndex,
+ Series,
+ Timestamp,
+ bdate_range,
+ date_range,
+ interval_range,
+ period_range,
+ timedelta_range,
+)
+from pandas.arrays import SparseArray
+
+from pandas.tseries.offsets import (
+ FY5253,
+ BusinessDay,
+ BusinessHour,
+ CustomBusinessDay,
+ DateOffset,
+ Day,
+ Easter,
+ Hour,
+ LastWeekOfMonth,
+ Minute,
+ MonthBegin,
+ MonthEnd,
+ QuarterBegin,
+ QuarterEnd,
+ SemiMonthBegin,
+ SemiMonthEnd,
+ Week,
+ WeekOfMonth,
+ YearBegin,
+ YearEnd,
+)
+
+
+def _create_sp_series():
+ nan = np.nan
+
+ # nan-based
+ arr = np.arange(15, dtype=np.float64)
+ arr[7:12] = nan
+ arr[-1:] = nan
+
+ bseries = Series(SparseArray(arr, kind="block"))
+ bseries.name = "bseries"
+ return bseries
+
+
+def _create_sp_tsseries():
+ nan = np.nan
+
+ # nan-based
+ arr = np.arange(15, dtype=np.float64)
+ arr[7:12] = nan
+ arr[-1:] = nan
+
+ date_index = bdate_range("1/1/2011", periods=len(arr))
+ bseries = Series(SparseArray(arr, kind="block"), index=date_index)
+ bseries.name = "btsseries"
+ return bseries
+
+
+def _create_sp_frame():
+ nan = np.nan
+
+ data = {
+ "A": [nan, nan, nan, 0, 1, 2, 3, 4, 5, 6],
+ "B": [0, 1, 2, nan, nan, nan, 3, 4, 5, 6],
+ "C": np.arange(10).astype(np.int64),
+ "D": [0, 1, 2, 3, 4, 5, nan, nan, nan, nan],
+ }
+
+ dates = bdate_range("1/1/2011", periods=10)
+ return DataFrame(data, index=dates).apply(SparseArray)
+
+
+def create_pickle_data(test: bool = True):
+ """create the pickle data"""
+ data = {
+ "A": [0.0, 1.0, 2.0, 3.0, np.nan],
+ "B": [0, 1, 0, 1, 0],
+ "C": ["foo1", "foo2", "foo3", "foo4", "foo5"],
+ "D": date_range("1/1/2009", periods=5),
+ "E": [0.0, 1, Timestamp("20100101"), "foo", 2.0],
+ }
+
+ scalars = {"timestamp": Timestamp("20130101"), "period": Period("2012", "M")}
+
+ index = {
+ "int": Index(np.arange(10)),
+ "date": date_range("20130101", periods=10),
+ "period": period_range("2013-01-01", freq="M", periods=10),
+ "float": Index(np.arange(10, dtype=np.float64)),
+ "uint": Index(np.arange(10, dtype=np.uint64)),
+ "timedelta": timedelta_range("00:00:00", freq="30min", periods=10),
+ "string": Index(["foo", "bar", "baz", "qux", "quux"], dtype="string"),
+ }
+
+ index["range"] = RangeIndex(10)
+
+ index["interval"] = interval_range(0, periods=10)
+
+ mi = {
+ "reg2": MultiIndex.from_tuples(
+ tuple(
+ zip(
+ *[
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ )
+ ),
+ names=["first", "second"],
+ )
+ }
+
+ series = {
+ "float": Series(data["A"]),
+ "int": Series(data["B"]),
+ "mixed": Series(data["E"]),
+ "ts": Series(
+ np.arange(10).astype(np.int64), index=date_range("20130101", periods=10)
+ ),
+ "mi": Series(
+ np.arange(5).astype(np.float64),
+ index=MultiIndex.from_tuples(
+ tuple(zip(*[[1, 1, 2, 2, 2], [3, 4, 3, 4, 5]])), names=["one", "two"]
+ ),
+ ),
+ "dup": Series(np.arange(5).astype(np.float64), index=["A", "B", "C", "D", "A"]),
+ "cat": Series(Categorical(["foo", "bar", "baz"])),
+ "dt": Series(date_range("20130101", periods=5)),
+ "dt_tz": Series(date_range("20130101", periods=5, tz="US/Eastern")),
+ "period": Series([Period("2000Q1")] * 5),
+ "string": Series(["foo", "bar", "baz", "qux", "quux"], dtype="string"),
+ }
+
+ mixed_dup_df = DataFrame(data)
+ mixed_dup_df.columns = list("ABCDA")
+ frame = {
+ "float": DataFrame({"A": series["float"], "B": series["float"] + 1}),
+ "int": DataFrame({"A": series["int"], "B": series["int"] + 1}),
+ "mixed": DataFrame({k: data[k] for k in ["A", "B", "C", "D"]}),
+ "mi": DataFrame(
+ {"A": np.arange(5).astype(np.float64), "B": np.arange(5).astype(np.int64)},
+ index=MultiIndex.from_tuples(
+ tuple(
+ zip(
+ *[
+ ["bar", "bar", "baz", "baz", "baz"],
+ ["one", "two", "one", "two", "three"],
+ ]
+ )
+ ),
+ names=["first", "second"],
+ ),
+ ),
+ "dup": DataFrame(
+ np.arange(15).reshape(5, 3).astype(np.float64), columns=["A", "B", "A"]
+ ),
+ "cat_onecol": DataFrame({"A": Categorical(["foo", "bar"])}),
+ "cat_and_float": DataFrame(
+ {
+ "A": Categorical(["foo", "bar", "baz"]),
+ "B": np.arange(3).astype(np.int64),
+ }
+ ),
+ "mixed_dup": mixed_dup_df,
+ "dt_mixed_tzs": DataFrame(
+ {
+ "A": Timestamp("20130102", tz="US/Eastern"),
+ "B": Timestamp("20130603", tz="CET"),
+ },
+ index=range(5),
+ ),
+ "dt_mixed2_tzs": DataFrame(
+ {
+ "A": Timestamp("20130102", tz="US/Eastern"),
+ "B": Timestamp("20130603", tz="CET"),
+ "C": Timestamp("20130603", tz="UTC"),
+ },
+ index=range(5),
+ ),
+ "string": DataFrame(
+ {
+ "A": Series(["foo", "bar", "baz", "qux", "quux"], dtype="string"),
+ "B": Series(["one", "two", "one", "two", "three"], dtype="string"),
+ }
+ ),
+ }
+
+ cat = {
+ "int8": Categorical(list("abcdefg")),
+ "int16": Categorical(np.arange(1000)),
+ "int32": Categorical(np.arange(10000)),
+ }
+
+ timestamp = {
+ "normal": Timestamp("2011-01-01"),
+ "nat": NaT,
+ "tz": Timestamp("2011-01-01", tz="US/Eastern"),
+ }
+ if test:
+ # kept because those are present in the legacy pickles (<= 1.4)
+ timestamp["freq"] = Timestamp("2011-01-01")
+ timestamp["both"] = Timestamp("2011-01-01", tz="Asia/Tokyo")
+
+ off = {
+ "DateOffset": DateOffset(years=1),
+ "DateOffset_h_ns": DateOffset(hour=6, nanoseconds=5824),
+ "BusinessDay": BusinessDay(offset=timedelta(seconds=9)),
+ "BusinessHour": BusinessHour(normalize=True, n=6, end="15:14"),
+ "CustomBusinessDay": CustomBusinessDay(weekmask="Mon Fri"),
+ "SemiMonthBegin": SemiMonthBegin(day_of_month=9),
+ "SemiMonthEnd": SemiMonthEnd(day_of_month=24),
+ "MonthBegin": MonthBegin(1),
+ "MonthEnd": MonthEnd(1),
+ "QuarterBegin": QuarterBegin(1),
+ "QuarterEnd": QuarterEnd(1),
+ "Day": Day(1),
+ "YearBegin": YearBegin(1),
+ "YearEnd": YearEnd(1),
+ "Week": Week(1),
+ "Week_Tues": Week(2, normalize=False, weekday=1),
+ "WeekOfMonth": WeekOfMonth(week=3, weekday=4),
+ "LastWeekOfMonth": LastWeekOfMonth(n=1, weekday=3),
+ "FY5253": FY5253(n=2, weekday=6, startingMonth=7, variation="last"),
+ "Easter": Easter(),
+ "Hour": Hour(1),
+ "Minute": Minute(1),
+ }
+
+ return {
+ "series": series,
+ "frame": frame,
+ "index": index,
+ "scalars": scalars,
+ "mi": mi,
+ "sp_series": {"float": _create_sp_series(), "ts": _create_sp_tsseries()},
+ "sp_frame": {"float": _create_sp_frame()},
+ "cat": cat,
+ "timestamp": timestamp,
+ "offsets": off,
+ }
+
+
+def create_dataframe_all_types():
+ timestamps = Series(
+ [
+ Timestamp("2013-01-01"),
+ NaT,
+ Timestamp("2013-01-03"),
+ Timestamp("2013-01-04"),
+ Timestamp("2013-01-05"),
+ ]
+ )
+ timedeltas = timestamps - timestamps[0]
+
+ data = {
+ # "string": Series(
+ # ["a", "b", "c", None, "e"], dtype=StringDtype(na_value=np.nan)
+ # ),
+ # "object": Series(["a", "b", "c", None, "e"], dtype=object),
+ # "object_nan": Series(["a", "b", "c", np.nan, "e"], dtype=object),
+ "int": list(range(1, 6)),
+ "uint64": np.arange(3, 8).astype("uint64"),
+ "float": [0.1, 0.2, 0.3, 0.4, np.nan],
+ "float32": Series([0.1, 0.2, 0.3, 0.4, np.nan], dtype="float32"),
+ "bool": [True, False, True, False, True],
+ "datetime_ns": timestamps.dt.as_unit("ns"),
+ "datetime_us": timestamps.dt.as_unit("us"),
+ "datetime_ms": timestamps.dt.as_unit("ms"),
+ "datetime_s": timestamps.dt.as_unit("s"),
+ "datetimetz_ns": timestamps.dt.tz_localize("US/Eastern").dt.as_unit("ns"),
+ "datetimetz_us": timestamps.dt.tz_localize("US/Eastern").dt.as_unit("us"),
+ "timedelta_ns": timedeltas.dt.as_unit("ns"),
+ "timedelta_us": timedeltas.dt.as_unit("us"),
+ "timedelta_ms": timedeltas.dt.as_unit("ms"),
+ "timedelta_s": timedeltas.dt.as_unit("s"),
+ # "categorical": Categorical(
+ # Series(
+ # ["foo", "bar", "baz",np.nan,"foo"],dtype=StringDtype(na_value=np.nan)
+ # )
+ # ),
+ # "categorical_object": Categorical(
+ # Series(["foo", "bar", "baz", np.nan, "foo"], dtype=object)
+ # ),
+ "categorical_int": Categorical([1, 2, 3, np.nan, 1]),
+ }
+ return DataFrame(data)
+
+
+def platform_name():
+ return "_".join(
+ [
+ str(pandas.__version__),
+ str(pl.machine()),
+ str(pl.system().lower()),
+ str(pl.python_version()),
+ ]
+ )
+
+
+def write_legacy_pickles(output_dir):
+ pth = f"{platform_name()}.pickle"
+
+ with open(os.path.join(output_dir, pth), "wb") as fh:
+ pickle.dump(create_pickle_data(test=False), fh, pickle.DEFAULT_PROTOCOL)
+
+ print(f"created pickle file: {pth}")
+
+
+def write_legacy_hdf(output_dir, format):
+ import tables
+
+ pth = f"{platform_name()}_pytables-{tables.__version__}_{format}.h5"
+
+ df = create_dataframe_all_types()
+ if format == "fixed":
+ # df = df.drop(columns=["categorical", "categorical_object", "categorical_int"])
+ df = df.drop(columns=["categorical_int"])
+ complevel = 9 if format == "table" else None
+ df.to_hdf(
+ os.path.join(output_dir, pth),
+ key="df_alltypes",
+ format=format,
+ complevel=complevel,
+ )
+
+ print(f"created hdf file: {pth}")
+
+
+def write_legacy_file():
+ # force our cwd to be the first searched
+ sys.path.insert(0, "")
+
+ if not 3 <= len(sys.argv) <= 4:
+ sys.exit(
+ "Specify output directory and storage type: generate_legacy_"
+ "storage_files.py "
+ )
+
+ output_dir = str(sys.argv[1])
+ storage_type = str(sys.argv[2])
+
+ print(
+ "This script generates a storage file for the current arch, system, "
+ "and python version"
+ )
+ print(f" pandas version: {pandas.__version__}")
+ print(f" output dir : {output_dir}")
+ print(f" storage format: {storage_type}")
+
+ if not os.path.exists(output_dir):
+ os.mkdir(output_dir)
+
+ if storage_type == "pickle":
+ write_legacy_pickles(output_dir=output_dir)
+ elif storage_type == "hdf":
+ write_legacy_hdf(output_dir=output_dir, format="fixed")
+ write_legacy_hdf(output_dir=output_dir, format="table")
+ else:
+ sys.exit("storage_type must be one of {'pickle', 'hdf'}")
+
+
+if __name__ == "__main__":
+ write_legacy_file()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e848cd48b42d70033af233bf71c6904b9d069f9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/conftest.py
@@ -0,0 +1,9 @@
+import pytest
+
+
+@pytest.fixture(params=["split", "records", "index", "columns", "values"])
+def orient(request):
+ """
+ Fixture for orients excluding the table format.
+ """
+ return request.param
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_compression.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_compression.py
new file mode 100644
index 0000000000000000000000000000000000000000..d29f7d3c5ff2e87e482fa87814e75a5b88d56849
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_compression.py
@@ -0,0 +1,130 @@
+from io import (
+ BytesIO,
+ StringIO,
+)
+import uuid
+
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+import pandas._testing as tm
+
+
+def test_compression_roundtrip(compression, temp_file):
+ df = pd.DataFrame(
+ [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ index=["A", "B"],
+ columns=["X", "Y", "Z"],
+ )
+
+ df.to_json(temp_file, compression=compression)
+ tm.assert_frame_equal(df, pd.read_json(temp_file, compression=compression))
+
+ # explicitly ensure file was compressed.
+ with tm.decompress_file(temp_file, compression) as fh:
+ result = fh.read().decode("utf8")
+ data = StringIO(result)
+ tm.assert_frame_equal(df, pd.read_json(data))
+
+
+def test_read_zipped_json(datapath):
+ uncompressed_path = datapath("io", "json", "data", "tsframe_v012.json")
+ uncompressed_df = pd.read_json(uncompressed_path)
+
+ compressed_path = datapath("io", "json", "data", "tsframe_v012.json.zip")
+ compressed_df = pd.read_json(compressed_path, compression="zip")
+
+ tm.assert_frame_equal(uncompressed_df, compressed_df)
+
+
+@td.skip_if_not_us_locale
+@pytest.mark.single_cpu
+@pytest.mark.network
+def test_with_s3_url(compression, s3_bucket_public, s3so, temp_file):
+ # Bucket created in tests/io/conftest.py
+ df = pd.read_json(StringIO('{"a": [1, 2, 3], "b": [4, 5, 6]}'))
+
+ key = f"{uuid.uuid4()}.json"
+ df.to_json(temp_file, compression=compression)
+ with open(temp_file, "rb") as f:
+ s3_bucket_public.put_object(Key=key, Body=f)
+
+ roundtripped_df = pd.read_json(
+ f"s3://{s3_bucket_public.name}/{key}",
+ compression=compression,
+ storage_options=s3so,
+ )
+ tm.assert_frame_equal(df, roundtripped_df)
+
+
+def test_lines_with_compression(compression, temp_file):
+ df = pd.read_json(StringIO('{"a": [1, 2, 3], "b": [4, 5, 6]}'))
+ df.to_json(temp_file, orient="records", lines=True, compression=compression)
+ roundtripped_df = pd.read_json(temp_file, lines=True, compression=compression)
+ tm.assert_frame_equal(df, roundtripped_df)
+
+
+def test_chunksize_with_compression(compression, temp_file):
+ df = pd.read_json(StringIO('{"a": ["foo", "bar", "baz"], "b": [4, 5, 6]}'))
+ df.to_json(temp_file, orient="records", lines=True, compression=compression)
+
+ with pd.read_json(
+ temp_file, lines=True, chunksize=1, compression=compression
+ ) as res:
+ roundtripped_df = pd.concat(res)
+ tm.assert_frame_equal(df, roundtripped_df)
+
+
+def test_write_unsupported_compression_type(temp_file):
+ df = pd.read_json(StringIO('{"a": [1, 2, 3], "b": [4, 5, 6]}'))
+ msg = "Unrecognized compression type: unsupported"
+ with pytest.raises(ValueError, match=msg):
+ df.to_json(temp_file, compression="unsupported")
+
+
+def test_read_unsupported_compression_type(temp_file):
+ msg = "Unrecognized compression type: unsupported"
+ with pytest.raises(ValueError, match=msg):
+ pd.read_json(temp_file, compression="unsupported")
+
+
+@pytest.mark.parametrize(
+ "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))]
+)
+@pytest.mark.parametrize("to_infer", [True, False])
+@pytest.mark.parametrize("read_infer", [True, False])
+def test_to_json_compression(
+ compression_only,
+ read_infer,
+ to_infer,
+ compression_to_extension,
+ infer_string,
+ tmp_path,
+):
+ with pd.option_context("future.infer_string", infer_string):
+ # see gh-15008
+ compression = compression_only
+
+ # We'll complete file extension subsequently.
+ filename = tmp_path / f"test.{compression_to_extension[compression]}"
+
+ df = pd.DataFrame({"A": [1]})
+
+ to_compression = "infer" if to_infer else compression
+ read_compression = "infer" if read_infer else compression
+
+ df.to_json(filename, compression=to_compression)
+ result = pd.read_json(filename, compression=read_compression)
+ tm.assert_frame_equal(result, df)
+
+
+def test_to_json_compression_mode(compression):
+ # GH 39985 (read_json does not support user-provided binary files)
+ expected = pd.DataFrame({"A": [1]})
+
+ with BytesIO() as buffer:
+ expected.to_json(buffer, compression=compression)
+ # df = pd.read_json(buffer, compression=compression)
+ # tm.assert_frame_equal(expected, df)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_deprecated_kwargs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_deprecated_kwargs.py
new file mode 100644
index 0000000000000000000000000000000000000000..9da682c90a285e3b1e1aff9d02709522bfdac98f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_deprecated_kwargs.py
@@ -0,0 +1,22 @@
+"""
+Tests for the deprecated keyword arguments for `read_json`.
+"""
+
+from io import StringIO
+
+import pandas as pd
+import pandas._testing as tm
+
+from pandas.io.json import read_json
+
+
+def test_good_kwargs():
+ df = pd.DataFrame({"A": [2, 4, 6], "B": [3, 6, 9]}, index=[0, 1, 2])
+
+ with tm.assert_produces_warning(None):
+ data1 = StringIO(df.to_json(orient="split"))
+ tm.assert_frame_equal(df, read_json(data1, orient="split"))
+ data2 = StringIO(df.to_json(orient="columns"))
+ tm.assert_frame_equal(df, read_json(data2, orient="columns"))
+ data3 = StringIO(df.to_json(orient="index"))
+ tm.assert_frame_equal(df, read_json(data3, orient="index"))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_json_table_schema.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_json_table_schema.py
new file mode 100644
index 0000000000000000000000000000000000000000..bec53a0450b68898d5dc6c8199b5100d581a2a4e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_json_table_schema.py
@@ -0,0 +1,898 @@
+"""Tests for Table Schema integration."""
+
+from collections import OrderedDict
+from io import StringIO
+import json
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.dtypes import (
+ CategoricalDtype,
+ DatetimeTZDtype,
+ PeriodDtype,
+)
+
+import pandas as pd
+from pandas import DataFrame
+import pandas._testing as tm
+
+from pandas.io.json._table_schema import (
+ as_json_table_type,
+ build_table_schema,
+ convert_json_field_to_pandas_type,
+ convert_pandas_type_to_json_field,
+ set_default_names,
+)
+
+
+@pytest.fixture
+def df_schema():
+ return DataFrame(
+ {
+ "A": [1, 2, 3, 4],
+ "B": ["a", "b", "c", "c"],
+ "C": pd.date_range("2016-01-01", freq="D", periods=4),
+ "D": pd.timedelta_range("1h", periods=4, freq="min"),
+ },
+ index=pd.Index(range(4), name="idx"),
+ )
+
+
+@pytest.fixture
+def df_table():
+ return DataFrame(
+ {
+ "A": [1, 2, 3, 4],
+ "B": ["a", "b", "c", "c"],
+ "C": pd.date_range("2016-01-01", freq="D", periods=4),
+ "D": pd.timedelta_range("1h", periods=4, freq="min"),
+ "E": pd.Series(pd.Categorical(["a", "b", "c", "c"])),
+ "F": pd.Series(pd.Categorical(["a", "b", "c", "c"], ordered=True)),
+ "G": [1.0, 2.0, 3, 4.0],
+ "H": pd.date_range("2016-01-01", freq="D", periods=4, tz="US/Central"),
+ },
+ index=pd.Index(range(4), name="idx"),
+ )
+
+
+class TestBuildSchema:
+ def test_build_table_schema(self, df_schema, using_infer_string):
+ result = build_table_schema(df_schema, version=False)
+ expected = {
+ "fields": [
+ {"name": "idx", "type": "integer"},
+ {"name": "A", "type": "integer"},
+ {"name": "B", "type": "string"},
+ {"name": "C", "type": "datetime"},
+ {"name": "D", "type": "duration"},
+ ],
+ "primaryKey": ["idx"],
+ }
+ if using_infer_string:
+ expected["fields"][2] = {"name": "B", "type": "string", "extDtype": "str"}
+ assert result == expected
+ result = build_table_schema(df_schema)
+ assert "pandas_version" in result
+
+ def test_series(self):
+ s = pd.Series([1, 2, 3], name="foo")
+ result = build_table_schema(s, version=False)
+ expected = {
+ "fields": [
+ {"name": "index", "type": "integer"},
+ {"name": "foo", "type": "integer"},
+ ],
+ "primaryKey": ["index"],
+ }
+ assert result == expected
+ result = build_table_schema(s)
+ assert "pandas_version" in result
+
+ def test_series_unnamed(self):
+ result = build_table_schema(pd.Series([1, 2, 3]), version=False)
+ expected = {
+ "fields": [
+ {"name": "index", "type": "integer"},
+ {"name": "values", "type": "integer"},
+ ],
+ "primaryKey": ["index"],
+ }
+ assert result == expected
+
+ def test_multiindex(self, df_schema, using_infer_string):
+ df = df_schema
+ idx = pd.MultiIndex.from_product([("a", "b"), (1, 2)])
+ df.index = idx
+
+ result = build_table_schema(df, version=False)
+ expected = {
+ "fields": [
+ {"name": "level_0", "type": "string"},
+ {"name": "level_1", "type": "integer"},
+ {"name": "A", "type": "integer"},
+ {"name": "B", "type": "string"},
+ {"name": "C", "type": "datetime"},
+ {"name": "D", "type": "duration"},
+ ],
+ "primaryKey": ["level_0", "level_1"],
+ }
+ if using_infer_string:
+ expected["fields"][0] = {
+ "name": "level_0",
+ "type": "string",
+ "extDtype": "str",
+ }
+ expected["fields"][3] = {"name": "B", "type": "string", "extDtype": "str"}
+ assert result == expected
+
+ df.index.names = ["idx0", None]
+ expected["fields"][0]["name"] = "idx0"
+ expected["primaryKey"] = ["idx0", "level_1"]
+ result = build_table_schema(df, version=False)
+ assert result == expected
+
+
+class TestTableSchemaType:
+ @pytest.mark.parametrize("int_type", [int, np.int16, np.int32, np.int64])
+ def test_as_json_table_type_int_data(self, int_type):
+ int_data = [1, 2, 3]
+ assert as_json_table_type(np.array(int_data, dtype=int_type).dtype) == "integer"
+
+ @pytest.mark.parametrize("float_type", [float, np.float16, np.float32, np.float64])
+ def test_as_json_table_type_float_data(self, float_type):
+ float_data = [1.0, 2.0, 3.0]
+ assert (
+ as_json_table_type(np.array(float_data, dtype=float_type).dtype) == "number"
+ )
+
+ @pytest.mark.parametrize("bool_type", [bool, np.bool_])
+ def test_as_json_table_type_bool_data(self, bool_type):
+ bool_data = [True, False]
+ assert (
+ as_json_table_type(np.array(bool_data, dtype=bool_type).dtype) == "boolean"
+ )
+
+ @pytest.mark.parametrize(
+ "date_data",
+ [
+ pd.to_datetime(["2016"]),
+ pd.to_datetime(["2016"], utc=True),
+ pd.Series(pd.to_datetime(["2016"])),
+ pd.Series(pd.to_datetime(["2016"], utc=True)),
+ pd.period_range("2016", freq="Y", periods=3),
+ ],
+ )
+ def test_as_json_table_type_date_data(self, date_data):
+ assert as_json_table_type(date_data.dtype) == "datetime"
+
+ @pytest.mark.parametrize("klass", [pd.Series, pd.Index])
+ def test_as_json_table_type_string_data(self, klass):
+ str_data = klass(["a", "b"], dtype=object)
+ assert as_json_table_type(str_data.dtype) == "string"
+
+ @pytest.mark.parametrize(
+ "cat_data",
+ [
+ pd.Categorical(["a"]),
+ pd.Categorical([1]),
+ pd.Series(pd.Categorical([1])),
+ pd.CategoricalIndex([1]),
+ ],
+ )
+ def test_as_json_table_type_categorical_data(self, cat_data):
+ assert as_json_table_type(cat_data.dtype) == "any"
+
+ # ------
+ # dtypes
+ # ------
+ @pytest.mark.parametrize("int_dtype", [int, np.int16, np.int32, np.int64])
+ def test_as_json_table_type_int_dtypes(self, int_dtype):
+ assert as_json_table_type(int_dtype) == "integer"
+
+ @pytest.mark.parametrize("float_dtype", [float, np.float16, np.float32, np.float64])
+ def test_as_json_table_type_float_dtypes(self, float_dtype):
+ assert as_json_table_type(float_dtype) == "number"
+
+ @pytest.mark.parametrize("bool_dtype", [bool, np.bool_])
+ def test_as_json_table_type_bool_dtypes(self, bool_dtype):
+ assert as_json_table_type(bool_dtype) == "boolean"
+
+ @pytest.mark.parametrize(
+ "date_dtype",
+ [
+ np.dtype(" None:
+ self.hexed = hexed
+ self.binary = bytes.fromhex(hexed)
+
+ def __str__(self) -> str:
+ return self.hexed
+
+ hexed = "574b4454ba8c5eb4f98a8f45"
+ binthing = BinaryThing(hexed)
+
+ # verify the proper conversion of printable content
+ df_printable = DataFrame({"A": [binthing.hexed]})
+ assert df_printable.to_json() == f'{{"A":{{"0":"{hexed}"}}}}'
+
+ # check if non-printable content throws appropriate Exception
+ df_nonprintable = DataFrame({"A": [binthing]})
+ msg = "Unsupported UTF-8 sequence length when encoding string"
+ with pytest.raises(OverflowError, match=msg):
+ df_nonprintable.to_json()
+
+ # the same with multiple columns threw segfaults
+ df_mixed = DataFrame({"A": [binthing], "B": [1]}, columns=["A", "B"])
+ with pytest.raises(OverflowError, match=msg):
+ df_mixed.to_json()
+
+ # default_handler should resolve exceptions for non-string types
+ result = df_nonprintable.to_json(default_handler=str)
+ expected = f'{{"A":{{"0":"{hexed}"}}}}'
+ assert result == expected
+ assert (
+ df_mixed.to_json(default_handler=str)
+ == f'{{"A":{{"0":"{hexed}"}},"B":{{"0":1}}}}'
+ )
+
+ def test_label_overflow(self):
+ # GH14256: buffer length not checked when writing label
+ result = DataFrame({"bar" * 100000: [1], "foo": [1337]}).to_json()
+ expected = f'{{"{"bar" * 100000}":{{"0":1}},"foo":{{"0":1337}}}}'
+ assert result == expected
+
+ def test_series_non_unique_index(self):
+ s = Series(["a", "b"], index=[1, 1])
+
+ msg = "Series index must be unique for orient='index'"
+ with pytest.raises(ValueError, match=msg):
+ s.to_json(orient="index")
+
+ tm.assert_series_equal(
+ s,
+ read_json(
+ StringIO(s.to_json(orient="split")), orient="split", typ="series"
+ ),
+ )
+ unserialized = read_json(
+ StringIO(s.to_json(orient="records")), orient="records", typ="series"
+ )
+ tm.assert_equal(s.values, unserialized.values)
+
+ def test_series_default_orient(self, string_series):
+ assert string_series.to_json() == string_series.to_json(orient="index")
+
+ def test_series_roundtrip_simple(self, orient, string_series, using_infer_string):
+ data = StringIO(string_series.to_json(orient=orient))
+ result = read_json(data, typ="series", orient=orient)
+
+ expected = string_series
+ if using_infer_string and orient in ("split", "index", "columns"):
+ # These schemas don't contain dtypes, so we infer string
+ expected.index = expected.index.astype("str")
+ if orient in ("values", "records"):
+ expected = expected.reset_index(drop=True)
+ if orient != "split":
+ expected.name = None
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", [False, None])
+ def test_series_roundtrip_object(self, orient, dtype, object_series):
+ data = StringIO(object_series.to_json(orient=orient))
+ result = read_json(data, typ="series", orient=orient, dtype=dtype)
+
+ expected = object_series
+ if orient in ("values", "records"):
+ expected = expected.reset_index(drop=True)
+ if orient != "split":
+ expected.name = None
+
+ if using_string_dtype():
+ expected = expected.astype("str")
+
+ tm.assert_series_equal(result, expected)
+
+ def test_series_roundtrip_empty(self, orient):
+ empty_series = Series([], index=[], dtype=np.float64)
+ data = StringIO(empty_series.to_json(orient=orient))
+ result = read_json(data, typ="series", orient=orient)
+
+ expected = empty_series.reset_index(drop=True)
+ if orient in ("split"):
+ expected.index = expected.index.astype(np.float64)
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype_backend", [None, pytest.param("pyarrow", marks=td.skip_if_no("pyarrow"))]
+ )
+ def test_series_roundtrip_timeseries(self, dtype_backend, orient, datetime_series):
+ expected = datetime_series.copy()
+ if dtype_backend is not None:
+ datetime_series.index = Series(datetime_series.index).convert_dtypes(
+ dtype_backend=dtype_backend
+ )
+
+ data = StringIO(datetime_series.to_json(orient=orient))
+ result = read_json(data, typ="series", orient=orient)
+
+ if orient in ("values", "records"):
+ expected = expected.reset_index(drop=True)
+ else:
+ expected.index = expected.index.as_unit("ms")
+ if orient != "split":
+ expected.name = None
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", [np.float64, int])
+ def test_series_roundtrip_numeric(self, orient, dtype):
+ s = Series(range(6), index=["a", "b", "c", "d", "e", "f"])
+ data = StringIO(s.to_json(orient=orient))
+ result = read_json(data, typ="series", orient=orient)
+
+ expected = s.copy()
+ if orient in ("values", "records"):
+ expected = expected.reset_index(drop=True)
+
+ tm.assert_series_equal(result, expected)
+
+ def test_series_to_json_except(self):
+ s = Series([1, 2, 3])
+ msg = "Invalid value 'garbage' for option 'orient'"
+ with pytest.raises(ValueError, match=msg):
+ s.to_json(orient="garbage")
+
+ def test_series_from_json_precise_float(self):
+ s = Series([4.56, 4.56, 4.56])
+ result = read_json(StringIO(s.to_json()), typ="series", precise_float=True)
+ tm.assert_series_equal(result, s, check_index_type=False)
+
+ def test_series_with_dtype(self):
+ # GH 21986
+ s = Series([4.56, 4.56, 4.56])
+ result = read_json(StringIO(s.to_json()), typ="series", dtype=np.int64)
+ expected = Series([4] * 3)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype_backend", [None, pytest.param("pyarrow", marks=td.skip_if_no("pyarrow"))]
+ )
+ @pytest.mark.parametrize(
+ "dtype,expected",
+ [
+ (True, Series(["2000-01-01"], dtype="datetime64[ms]")),
+ (False, Series([946684800000])),
+ ],
+ )
+ def test_series_with_dtype_datetime(self, dtype, expected, dtype_backend):
+ s = Series(["2000-01-01"], dtype="datetime64[ns]")
+ if dtype_backend is not None:
+ s = s.convert_dtypes(dtype_backend=dtype_backend)
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ data = StringIO(s.to_json())
+ result = read_json(data, typ="series", dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_frame_from_json_precise_float(self):
+ df = DataFrame([[4.56, 4.56, 4.56], [4.56, 4.56, 4.56]])
+ result = read_json(StringIO(df.to_json()), precise_float=True)
+ tm.assert_frame_equal(result, df)
+
+ def test_typ(self):
+ s = Series(range(6), index=["a", "b", "c", "d", "e", "f"], dtype="int64")
+ result = read_json(StringIO(s.to_json()), typ="series")
+ tm.assert_series_equal(result, s)
+
+ def test_reconstruction_index(self):
+ df = DataFrame([[1, 2, 3], [4, 5, 6]])
+ result = read_json(StringIO(df.to_json()))
+ tm.assert_frame_equal(result, df)
+
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}, index=["A", "B", "C"])
+ result = read_json(StringIO(df.to_json()))
+ tm.assert_frame_equal(result, df)
+
+ def test_path(self, float_frame, int_frame, datetime_frame, temp_file):
+ for df in [float_frame, int_frame, datetime_frame]:
+ df.to_json(temp_file)
+ read_json(temp_file)
+
+ def test_axis_dates(self, datetime_series, datetime_frame):
+ # frame
+ json = StringIO(datetime_frame.to_json())
+ result = read_json(json)
+ expected = datetime_frame.copy()
+ expected.index = expected.index.as_unit("ms")
+ tm.assert_frame_equal(result, expected)
+
+ # series
+ json = StringIO(datetime_series.to_json())
+ result = read_json(json, typ="series")
+ expected = datetime_series.copy()
+ expected.index = expected.index.as_unit("ms")
+ tm.assert_series_equal(result, expected, check_names=False)
+ assert result.name is None
+
+ def test_convert_dates(self, datetime_series, datetime_frame):
+ # frame
+ df = datetime_frame
+ df["date"] = Timestamp("20130101").as_unit("ns")
+
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ json = StringIO(df.to_json())
+ result = read_json(json)
+ expected = df.copy()
+ expected["date"] = expected["date"].dt.as_unit("ms")
+ expected.index = expected.index.as_unit("ms")
+ tm.assert_frame_equal(result, expected)
+
+ df["foo"] = 1.0
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ json = StringIO(df.to_json(date_unit="ns"))
+
+ result = read_json(json, convert_dates=False)
+ expected = df.copy()
+ expected["date"] = expected["date"].values.view("i8")
+ expected["foo"] = expected["foo"].astype("int64")
+ tm.assert_frame_equal(result, expected)
+
+ # series
+ ts = Series(Timestamp("20130101").as_unit("ns"), index=datetime_series.index)
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ json = StringIO(ts.to_json())
+ result = read_json(json, typ="series")
+ expected = ts.dt.as_unit("ms")
+ expected.index = expected.index.as_unit("ms")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("date_format", ["epoch", "iso"])
+ @pytest.mark.parametrize("as_object", [True, False])
+ @pytest.mark.parametrize("date_typ", [datetime.date, datetime.datetime, Timestamp])
+ def test_date_index_and_values(self, date_format, as_object, date_typ):
+ data = [date_typ(year=2020, month=1, day=1), pd.NaT]
+ if as_object:
+ data.append("a")
+
+ ser = Series(data, index=data)
+ if not as_object:
+ ser = ser.astype("M8[ns]")
+ if isinstance(ser.index, DatetimeIndex):
+ ser.index = ser.index.as_unit("ns")
+
+ expected_warning = None
+ if date_format == "epoch":
+ expected = '{"1577836800000":1577836800000,"null":null}'
+ expected_warning = Pandas4Warning
+ else:
+ expected = (
+ '{"2020-01-01T00:00:00.000":"2020-01-01T00:00:00.000","null":null}'
+ )
+
+ msg = (
+ "'epoch' date format is deprecated and will be removed in a future "
+ "version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(expected_warning, match=msg):
+ result = ser.to_json(date_format=date_format)
+
+ if as_object:
+ expected = expected.replace("}", ',"a":"a"}')
+
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "infer_word",
+ [
+ "trade_time",
+ "date",
+ "datetime",
+ "sold_at",
+ "modified",
+ "timestamp",
+ "timestamps",
+ ],
+ )
+ def test_convert_dates_infer(self, infer_word):
+ # GH10747
+
+ data = [{"id": 1, infer_word: 1036713600000}, {"id": 2}]
+ expected = DataFrame(
+ [[1, Timestamp("2002-11-08")], [2, pd.NaT]], columns=["id", infer_word]
+ )
+ expected[infer_word] = expected[infer_word].astype("M8[ms]")
+
+ result = read_json(StringIO(ujson_dumps(data)))[["id", infer_word]]
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "date,date_unit",
+ [
+ ("20130101 20:43:42.123", None),
+ ("20130101 20:43:42", "s"),
+ ("20130101 20:43:42.123", "ms"),
+ ("20130101 20:43:42.123456", "us"),
+ ("20130101 20:43:42.123456789", "ns"),
+ ],
+ )
+ def test_date_format_frame(self, date, date_unit, datetime_frame):
+ df = datetime_frame
+
+ df["date"] = Timestamp(date).as_unit("ns")
+ df.iloc[1, df.columns.get_loc("date")] = pd.NaT
+ df.iloc[5, df.columns.get_loc("date")] = pd.NaT
+ if date_unit:
+ json = df.to_json(date_format="iso", date_unit=date_unit)
+ else:
+ json = df.to_json(date_format="iso")
+
+ result = read_json(StringIO(json))
+ expected = df.copy()
+ if date_unit != "ns":
+ expected["date"] = expected["date"].dt.as_unit("us")
+ expected.index = expected.index.as_unit("us")
+ tm.assert_frame_equal(result, expected)
+
+ def test_date_format_frame_raises(self, datetime_frame):
+ df = datetime_frame
+ msg = "Invalid value 'foo' for option 'date_unit'"
+ with pytest.raises(ValueError, match=msg):
+ df.to_json(date_format="iso", date_unit="foo")
+
+ @pytest.mark.parametrize(
+ "date,date_unit",
+ [
+ ("20130101 20:43:42.123", None),
+ ("20130101 20:43:42", "s"),
+ ("20130101 20:43:42.123", "ms"),
+ ("20130101 20:43:42.123456", "us"),
+ ("20130101 20:43:42.123456789", "ns"),
+ ],
+ )
+ def test_date_format_series(self, date, date_unit, datetime_series):
+ ts = Series(Timestamp(date), index=datetime_series.index)
+ ts.iloc[1] = pd.NaT
+ ts.iloc[5] = pd.NaT
+ if date_unit:
+ json = ts.to_json(date_format="iso", date_unit=date_unit)
+ else:
+ json = ts.to_json(date_format="iso")
+
+ result = read_json(StringIO(json), typ="series")
+ expected = ts.copy()
+ if date_unit != "ns":
+ expected.index = expected.index.as_unit("us")
+ tm.assert_series_equal(result, expected)
+
+ def test_date_format_series_raises(self, datetime_series):
+ ts = Series(Timestamp("20130101 20:43:42.123"), index=datetime_series.index)
+ msg = "Invalid value 'foo' for option 'date_unit'"
+ with pytest.raises(ValueError, match=msg):
+ ts.to_json(date_format="iso", date_unit="foo")
+
+ def test_date_unit(self, unit, datetime_frame):
+ df = datetime_frame
+ df["date"] = Timestamp("20130101 20:43:42").as_unit("ns")
+ dl = df.columns.get_loc("date")
+ df.iloc[1, dl] = Timestamp("19710101 20:43:42")
+ df.iloc[2, dl] = Timestamp("21460101 20:43:42")
+ df.iloc[4, dl] = pd.NaT
+
+ msg = (
+ "'epoch' date format is deprecated and will be removed in a future "
+ "version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ json = df.to_json(date_format="epoch", date_unit=unit)
+
+ # force date unit
+ # result = read_json(StringIO(json), date_unit=unit)
+ expected = df.copy()
+ expected["date"] = expected["date"].dt.as_unit(unit)
+ expected.index = expected.index.as_unit(unit)
+ # tm.assert_frame_equal(result, expected)
+
+ # detect date unit
+ result = read_json(StringIO(json), date_unit=None)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "df, warn",
+ [
+ (DataFrame({"A": ["a", "b", "c"], "B": np.arange(3)}), None),
+ (DataFrame({"A": [True, False, False]}), None),
+ (
+ DataFrame(
+ {"A": ["a", "b", "c"], "B": pd.to_timedelta(np.arange(3), unit="D")}
+ ),
+ Pandas4Warning,
+ ),
+ (
+ DataFrame(
+ {"A": pd.to_datetime(["2020-01-01", "2020-02-01", "2020-03-01"])}
+ ),
+ Pandas4Warning,
+ ),
+ ],
+ )
+ def test_default_epoch_date_format_deprecated(self, df, warn):
+ # GH 57063
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(warn, match=msg):
+ df.to_json()
+
+ @pytest.mark.parametrize(
+ "dtype_backend", [None, pytest.param("pyarrow", marks=td.skip_if_no("pyarrow"))]
+ )
+ @pytest.mark.parametrize("unit", ["s", "ms", "us"])
+ def test_iso_non_nano_datetimes(self, dtype_backend, unit):
+ # Test that numpy datetimes
+ # in an Index or a column with non-nano resolution can be serialized
+ # correctly
+ # GH53686
+ index = DatetimeIndex(
+ [np.datetime64("2023-01-01T11:22:33.123456", unit)],
+ dtype=f"datetime64[{unit}]",
+ )
+ df = DataFrame(
+ {
+ "date": Series(
+ [np.datetime64("2022-01-01T11:22:33.123456", unit)],
+ dtype=f"datetime64[{unit}]",
+ index=index,
+ ),
+ "date_obj": Series(
+ [np.datetime64("2023-01-01T11:22:33.123456", unit)],
+ dtype=object,
+ index=index,
+ ),
+ },
+ )
+ expected = df.copy()
+ expected.index = expected.index.as_unit("us")
+ expected["date"] = expected["date"].dt.as_unit("us")
+ expected["date_obj"] = expected["date_obj"].astype("datetime64[us]")
+
+ if dtype_backend is not None:
+ df = df.convert_dtypes(dtype_backend=dtype_backend)
+
+ json = df.to_json(date_format="iso", date_unit=unit)
+ result = read_json(StringIO(json), convert_dates=["date", "date_obj"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_weird_nested_json(self):
+ # this used to core dump the parser
+ s = r"""{
+ "status": "success",
+ "data": {
+ "posts": [
+ {
+ "id": 1,
+ "title": "A blog post",
+ "body": "Some useful content"
+ },
+ {
+ "id": 2,
+ "title": "Another blog post",
+ "body": "More content"
+ }
+ ]
+ }
+ }"""
+ read_json(StringIO(s))
+
+ def test_doc_example(self):
+ dfj2 = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 2)), columns=list("AB")
+ )
+ dfj2["date"] = Timestamp("20130101")
+ dfj2["ints"] = range(5)
+ dfj2["bools"] = True
+ dfj2.index = date_range("20130101", periods=5, unit="ns")
+
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ json = StringIO(dfj2.to_json())
+ result = read_json(json, dtype={"ints": np.int64, "bools": np.bool_})
+ tm.assert_frame_equal(result, result)
+
+ def test_round_trip_exception(self, datapath):
+ # GH 3867
+ path = datapath("io", "json", "data", "teams.csv")
+ df = pd.read_csv(path)
+ s = df.to_json()
+
+ result = read_json(StringIO(s))
+ res = result.reindex(index=df.index, columns=df.columns)
+ res = res.fillna(np.nan)
+ tm.assert_frame_equal(res, df)
+
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_url(self, httpserver):
+ data = '{"created_at": ["2023-06-23T18:21:36Z"], "closed_at": ["2023-06-23T18:21:36"], "updated_at": ["2023-06-23T18:21:36Z"]}\n' # noqa: E501
+ httpserver.serve_content(content=data)
+ result = read_json(httpserver.url, convert_dates=True)
+ for field, dtype in [
+ ["created_at", pd.DatetimeTZDtype("us", tz="UTC")],
+ ["closed_at", "datetime64[us]"],
+ ["updated_at", pd.DatetimeTZDtype("us", tz="UTC")],
+ ]:
+ assert result[field].dtype == dtype
+
+ def test_timedelta(self, unit):
+ converter = lambda x: pd.to_timedelta(x, unit="ms")
+
+ ser = Series([timedelta(23), timedelta(seconds=5)], dtype=f"m8[{unit}]")
+ assert ser.dtype == f"timedelta64[{unit}]"
+
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = read_json(StringIO(ser.to_json()), typ="series").apply(converter)
+ tm.assert_series_equal(result, ser.astype("m8[ms]"))
+
+ ser = Series(
+ [timedelta(23), timedelta(seconds=5)], index=Index([0, 1]), dtype="m8[ns]"
+ )
+ assert ser.dtype == "timedelta64[ns]"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = read_json(StringIO(ser.to_json()), typ="series").apply(converter)
+ tm.assert_series_equal(result, ser.astype("m8[ms]"))
+
+ frame = DataFrame([timedelta(23), timedelta(seconds=5)], dtype="m8[ns]")
+ assert frame[0].dtype == "timedelta64[ns]"
+
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ json = frame.to_json()
+ result = read_json(StringIO(json)).apply(converter)
+ tm.assert_frame_equal(frame.astype("m8[ms]"), result)
+
+ def test_timedelta2(self):
+ frame = DataFrame(
+ {
+ "a": [timedelta(days=23), timedelta(seconds=5)],
+ "b": [1, 2],
+ "c": date_range(start="20130101", periods=2, unit="ns"),
+ }
+ )
+ frame["a"] = frame["a"].astype("m8[ns]")
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ data = StringIO(frame.to_json(date_unit="ns"))
+ result = read_json(data)
+ result["a"] = pd.to_timedelta(result.a, unit="ns")
+ result["c"] = pd.to_datetime(result.c)
+ tm.assert_frame_equal(frame, result)
+
+ def test_mixed_timedelta_datetime(self):
+ td = timedelta(23)
+ ts = Timestamp("20130101")
+ frame = DataFrame({"a": [td, ts]}, dtype=object)
+
+ expected = DataFrame(
+ {"a": [pd.Timedelta(td).as_unit("ns")._value, ts.as_unit("ns")._value]}
+ )
+ data = StringIO(frame.to_json(date_unit="ns"))
+ result = read_json(data, dtype={"a": "int64"})
+ tm.assert_frame_equal(result, expected, check_index_type=False)
+
+ @pytest.mark.parametrize("as_object", [True, False])
+ @pytest.mark.parametrize("date_format", ["iso", "epoch"])
+ @pytest.mark.parametrize("timedelta_typ", [pd.Timedelta, timedelta])
+ def test_timedelta_to_json(self, as_object, date_format, timedelta_typ):
+ # GH28156: to_json not correctly formatting Timedelta
+ data = [timedelta_typ(days=1), timedelta_typ(days=2), pd.NaT]
+ if as_object:
+ data.append("a")
+
+ ser = Series(data, index=data)
+ if not as_object:
+ ser = ser.astype("m8[ns]")
+ ser.index = ser.index.astype("m8[ns]")
+ expected_warning = None
+ if date_format == "iso":
+ expected = (
+ '{"P1DT0H0M0S":"P1DT0H0M0S","P2DT0H0M0S":"P2DT0H0M0S","null":null}'
+ )
+ else:
+ expected_warning = Pandas4Warning
+ expected = '{"86400000":86400000,"172800000":172800000,"null":null}'
+
+ if as_object:
+ expected = expected.replace("}", ',"a":"a"}')
+
+ msg = (
+ "'epoch' date format is deprecated and will be removed in a future "
+ "version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(expected_warning, match=msg):
+ result = ser.to_json(date_format=date_format)
+ assert result == expected
+
+ @pytest.mark.parametrize("as_object", [True, False])
+ @pytest.mark.parametrize("timedelta_typ", [pd.Timedelta, timedelta])
+ def test_timedelta_to_json_fractional_precision(self, as_object, timedelta_typ):
+ data = [timedelta_typ(milliseconds=42)]
+ ser = Series(data, index=data).astype("m8[ns]")
+ ser.index = ser.index.astype("m8[ns]")
+ warn = Pandas4Warning
+ if as_object:
+ ser = ser.astype(object)
+ warn = None
+
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(warn, match=msg):
+ result = ser.to_json()
+ expected = '{"42":42}'
+ assert result == expected
+
+ def test_default_handler(self):
+ value = object()
+ frame = DataFrame({"a": [7, value]})
+ expected = DataFrame({"a": [7, str(value)]})
+ result = read_json(StringIO(frame.to_json(default_handler=str)))
+ tm.assert_frame_equal(expected, result, check_index_type=False)
+
+ def test_default_handler_indirect(self):
+ def default(obj):
+ if isinstance(obj, complex):
+ return [("mathjs", "Complex"), ("re", obj.real), ("im", obj.imag)]
+ return str(obj)
+
+ df_list = [
+ 9,
+ DataFrame(
+ {"a": [1, "STR", complex(4, -5)], "b": [float("nan"), None, "N/A"]},
+ columns=["a", "b"],
+ ),
+ ]
+ expected = (
+ '[9,[[1,null],["STR",null],[[["mathjs","Complex"],'
+ '["re",4.0],["im",-5.0]],"N\\/A"]]]'
+ )
+ assert (
+ ujson_dumps(df_list, default_handler=default, orient="values") == expected
+ )
+
+ def test_default_handler_numpy_unsupported_dtype(self):
+ # GH12554 to_json raises 'Unhandled numpy dtype 15'
+ df = DataFrame(
+ {"a": [1, 2.3, complex(4, -5)], "b": [float("nan"), None, complex(1.2, 0)]},
+ columns=["a", "b"],
+ )
+ expected = (
+ '[["(1+0j)","(nan+0j)"],["(2.3+0j)","(nan+0j)"],["(4-5j)","(1.2+0j)"]]'
+ )
+ assert df.to_json(default_handler=str, orient="values") == expected
+
+ def test_default_handler_raises(self):
+ msg = "raisin"
+
+ def my_handler_raises(obj):
+ raise TypeError(msg)
+
+ with pytest.raises(TypeError, match=msg):
+ DataFrame({"a": [1, 2, object()]}).to_json(
+ default_handler=my_handler_raises
+ )
+ with pytest.raises(TypeError, match=msg):
+ DataFrame({"a": [1, 2, complex(4, -5)]}).to_json(
+ default_handler=my_handler_raises
+ )
+
+ def test_categorical(self):
+ # GH4377 df.to_json segfaults with non-ndarray blocks
+ df = DataFrame({"A": ["a", "b", "c", "a", "b", "b", "a"]})
+ df["B"] = df["A"]
+ expected = df.to_json()
+
+ df["B"] = df["A"].astype("category")
+ assert expected == df.to_json()
+
+ s = df["A"]
+ sc = df["B"]
+ assert s.to_json() == sc.to_json()
+
+ def test_datetime_tz(self):
+ # GH4377 df.to_json segfaults with non-ndarray blocks
+ tz_range = date_range("20130101", periods=3, tz="US/Eastern", unit="ns")
+ tz_naive = tz_range.tz_convert("utc").tz_localize(None)
+
+ df = DataFrame(
+ {"A": tz_range, "B": date_range("20130101", periods=3, unit="ns")}
+ )
+
+ df_naive = df.copy()
+ df_naive["A"] = tz_naive
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = df_naive.to_json()
+ assert expected == df.to_json()
+
+ stz = Series(tz_range)
+ s_naive = Series(tz_naive)
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ assert stz.to_json() == s_naive.to_json()
+
+ def test_sparse(self):
+ # GH4377 df.to_json segfaults with non-ndarray blocks
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ df.loc[:8] = np.nan
+
+ sdf = df.astype("Sparse")
+ expected = df.to_json()
+ assert expected == sdf.to_json()
+
+ s = Series(np.random.default_rng(2).standard_normal(10))
+ s.loc[:8] = np.nan
+ ss = s.astype("Sparse")
+
+ expected = s.to_json()
+ assert expected == ss.to_json()
+
+ @pytest.mark.parametrize(
+ "ts",
+ [
+ Timestamp("2013-01-10 05:00:00Z"),
+ Timestamp("2013-01-10 00:00:00", tz="US/Eastern"),
+ Timestamp("2013-01-10 00:00:00-0500"),
+ ],
+ )
+ def test_tz_is_utc(self, ts):
+ exp = '"2013-01-10T05:00:00.000Z"'
+
+ assert ujson_dumps(ts, iso_dates=True) == exp
+ dt = ts.to_pydatetime()
+ assert ujson_dumps(dt, iso_dates=True) == exp
+
+ def test_tz_is_naive(self):
+ ts = Timestamp("2013-01-10 05:00:00")
+ exp = '"2013-01-10T05:00:00.000"'
+
+ assert ujson_dumps(ts, iso_dates=True) == exp
+ dt = ts.to_pydatetime()
+ assert ujson_dumps(dt, iso_dates=True) == exp
+
+ @pytest.mark.parametrize(
+ "tz_range",
+ [
+ date_range("2013-01-01 05:00:00Z", periods=2, unit="ns"),
+ date_range("2013-01-01 00:00:00", periods=2, tz="US/Eastern", unit="ns"),
+ date_range("2013-01-01 00:00:00-0500", periods=2, unit="ns"),
+ ],
+ )
+ def test_tz_range_is_utc(self, tz_range):
+ exp = '["2013-01-01T05:00:00.000Z","2013-01-02T05:00:00.000Z"]'
+ dfexp = '{"DT":{"0":"2013-01-01T05:00:00.000Z","1":"2013-01-02T05:00:00.000Z"}}'
+
+ assert ujson_dumps(tz_range, iso_dates=True) == exp
+ dti = DatetimeIndex(tz_range)
+ # Ensure datetimes in object array are serialized correctly
+ # in addition to the normal DTI case
+ assert ujson_dumps(dti, iso_dates=True) == exp
+ assert ujson_dumps(dti.astype(object), iso_dates=True) == exp
+ df = DataFrame({"DT": dti})
+ result = ujson_dumps(df, iso_dates=True)
+ assert result == dfexp
+ assert ujson_dumps(df.astype({"DT": object}), iso_dates=True)
+
+ def test_tz_range_is_naive(self):
+ dti = date_range("2013-01-01 05:00:00", periods=2, unit="ns")
+
+ exp = '["2013-01-01T05:00:00.000","2013-01-02T05:00:00.000"]'
+ dfexp = '{"DT":{"0":"2013-01-01T05:00:00.000","1":"2013-01-02T05:00:00.000"}}'
+
+ # Ensure datetimes in object array are serialized correctly
+ # in addition to the normal DTI case
+ assert ujson_dumps(dti, iso_dates=True) == exp
+ assert ujson_dumps(dti.astype(object), iso_dates=True) == exp
+ df = DataFrame({"DT": dti})
+ result = ujson_dumps(df, iso_dates=True)
+ assert result == dfexp
+ assert ujson_dumps(df.astype({"DT": object}), iso_dates=True)
+
+ def test_read_inline_jsonl(self):
+ # GH9180
+
+ result = read_json(StringIO('{"a": 1, "b": 2}\n{"b":2, "a" :1}\n'), lines=True)
+ expected = DataFrame([[1, 2], [1, 2]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.single_cpu
+ @pytest.mark.network
+ @td.skip_if_not_us_locale
+ def test_read_s3_jsonl(self, s3_bucket_public_with_data, s3so):
+ # GH17200
+ result = read_json(
+ f"s3n://{s3_bucket_public_with_data.name}/items.jsonl",
+ lines=True,
+ storage_options=s3so,
+ )
+ expected = DataFrame([[1, 2], [1, 2]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_read_local_jsonl(self, temp_file):
+ # GH17200
+ with open(temp_file, "w", encoding="utf-8") as infile:
+ infile.write('{"a": 1, "b": 2}\n{"b":2, "a" :1}\n')
+ result = read_json(temp_file, lines=True)
+ expected = DataFrame([[1, 2], [1, 2]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_read_jsonl_unicode_chars(self):
+ # GH15132: non-ascii unicode characters
+ # \u201d == RIGHT DOUBLE QUOTATION MARK
+
+ # simulate file handle
+ json = '{"a": "foo”", "b": "bar"}\n{"a": "foo", "b": "bar"}\n'
+ json = StringIO(json)
+ result = read_json(json, lines=True)
+ expected = DataFrame([["foo\u201d", "bar"], ["foo", "bar"]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+ # simulate string
+ json = StringIO('{"a": "foo”", "b": "bar"}\n{"a": "foo", "b": "bar"}\n')
+ result = read_json(json, lines=True)
+ expected = DataFrame([["foo\u201d", "bar"], ["foo", "bar"]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("bigNum", [sys.maxsize + 1, -(sys.maxsize + 2)])
+ def test_to_json_large_numbers(self, bigNum):
+ # GH34473
+ series = Series(bigNum, dtype=object, index=["articleId"])
+ json = series.to_json()
+ expected = '{"articleId":' + str(bigNum) + "}"
+ assert json == expected
+
+ df = DataFrame(bigNum, dtype=object, index=["articleId"], columns=[0])
+ json = df.to_json()
+ expected = '{"0":{"articleId":' + str(bigNum) + "}}"
+ assert json == expected
+
+ @pytest.mark.parametrize("bigNum", [-(2**63) - 1, 2**64])
+ def test_read_json_large_numbers(self, bigNum):
+ # GH20599, 26068
+ json = StringIO('{"articleId":' + str(bigNum) + "}")
+ msg = r"Value is too small|Value is too big"
+ with pytest.raises(ValueError, match=msg):
+ read_json(json)
+
+ json = StringIO('{"0":{"articleId":' + str(bigNum) + "}}")
+ with pytest.raises(ValueError, match=msg):
+ read_json(json)
+
+ def test_read_json_large_numbers2(self):
+ # GH18842
+ json = '{"articleId": "1404366058080022500245"}'
+ json = StringIO(json)
+ result = read_json(json, typ="series")
+ expected = Series(1.404366e21, index=["articleId"])
+ tm.assert_series_equal(result, expected)
+
+ json = '{"0": {"articleId": "1404366058080022500245"}}'
+ json = StringIO(json)
+ result = read_json(json)
+ expected = DataFrame(1.404366e21, index=["articleId"], columns=[0])
+ tm.assert_frame_equal(result, expected)
+
+ def test_to_jsonl(self):
+ # GH9180
+ df = DataFrame([[1, 2], [1, 2]], columns=["a", "b"])
+ result = df.to_json(orient="records", lines=True)
+ expected = '{"a":1,"b":2}\n{"a":1,"b":2}\n'
+ assert result == expected
+
+ df = DataFrame([["foo}", "bar"], ['foo"', "bar"]], columns=["a", "b"])
+ result = df.to_json(orient="records", lines=True)
+ expected = '{"a":"foo}","b":"bar"}\n{"a":"foo\\"","b":"bar"}\n'
+ assert result == expected
+ tm.assert_frame_equal(read_json(StringIO(result), lines=True), df)
+
+ # GH15096: escaped characters in columns and data
+ df = DataFrame([["foo\\", "bar"], ['foo"', "bar"]], columns=["a\\", "b"])
+ result = df.to_json(orient="records", lines=True)
+ expected = '{"a\\\\":"foo\\\\","b":"bar"}\n{"a\\\\":"foo\\"","b":"bar"}\n'
+ assert result == expected
+
+ tm.assert_frame_equal(read_json(StringIO(result), lines=True), df)
+
+ # TODO: there is a near-identical test for pytables; can we share?
+ @pytest.mark.xfail(reason="GH#13774 encoding kwarg not supported", raises=TypeError)
+ @pytest.mark.parametrize(
+ "val",
+ [
+ [b"E\xc9, 17", b"", b"a", b"b", b"c"],
+ [b"E\xc9, 17", b"a", b"b", b"c"],
+ [b"EE, 17", b"", b"a", b"b", b"c"],
+ [b"E\xc9, 17", b"\xf8\xfc", b"a", b"b", b"c"],
+ [b"", b"a", b"b", b"c"],
+ [b"\xf8\xfc", b"a", b"b", b"c"],
+ [b"A\xf8\xfc", b"", b"a", b"b", b"c"],
+ [np.nan, b"", b"b", b"c"],
+ [b"A\xf8\xfc", np.nan, b"", b"b", b"c"],
+ ],
+ )
+ @pytest.mark.parametrize("dtype", ["category", object])
+ def test_latin_encoding(self, dtype, val, temp_file):
+ # GH 13774
+ ser = Series(
+ [x.decode("latin-1") if isinstance(x, bytes) else x for x in val],
+ dtype=dtype,
+ )
+ encoding = "latin-1"
+ ser.to_json(temp_file, encoding=encoding)
+ retr = read_json(StringIO(temp_file), encoding=encoding)
+ tm.assert_series_equal(ser, retr, check_categorical=False)
+
+ def test_data_frame_size_after_to_json(self):
+ # GH15344
+ df = DataFrame({"a": [str(1)]})
+
+ size_before = df.memory_usage(index=True, deep=True).sum()
+ df.to_json()
+ size_after = df.memory_usage(index=True, deep=True).sum()
+
+ assert size_before == size_after
+
+ @pytest.mark.parametrize(
+ "index", [None, [1, 2], [1.0, 2.0], ["a", "b"], ["1", "2"], ["1.", "2."]]
+ )
+ @pytest.mark.parametrize("columns", [["a", "b"], ["1", "2"], ["1.", "2."]])
+ def test_from_json_to_json_table_index_and_columns(self, index, columns):
+ # GH25433 GH25435
+ expected = DataFrame([[1, 2], [3, 4]], index=index, columns=columns)
+ dfjson = expected.to_json(orient="table")
+
+ result = read_json(StringIO(dfjson), orient="table")
+ tm.assert_frame_equal(result, expected)
+
+ def test_from_json_to_json_table_dtypes(self):
+ # GH21345
+ expected = DataFrame({"a": [1, 2], "b": [3.0, 4.0], "c": ["5", "6"]})
+ dfjson = expected.to_json(orient="table")
+ result = read_json(StringIO(dfjson), orient="table")
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("orient", ["split", "records", "index", "columns"])
+ def test_to_json_from_json_columns_dtypes(self, orient, using_infer_string):
+ # GH21892 GH33205
+ expected = DataFrame.from_dict(
+ {
+ "Integer": Series([1, 2, 3], dtype="int64"),
+ "Float": Series([None, 2.0, 3.0], dtype="float64"),
+ "Object": Series([None, "", "c"], dtype="object"),
+ "Bool": Series([True, False, True], dtype="bool"),
+ "Category": Series(["a", "b", None], dtype="category"),
+ "Datetime": Series(
+ ["2020-01-01", None, "2020-01-03"], dtype="datetime64[ms]"
+ ),
+ }
+ )
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ dfjson = expected.to_json(orient=orient)
+
+ if using_infer_string:
+ # When this is read back in it is inferred to "str" dtype which
+ # uses NaN instead of None.
+ expected.loc[0, "Object"] = np.nan
+
+ result = read_json(
+ StringIO(dfjson),
+ orient=orient,
+ dtype={
+ "Integer": "int64",
+ "Float": "float64",
+ "Object": "object",
+ "Bool": "bool",
+ "Category": "category",
+ "Datetime": "datetime64[ns]",
+ },
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_to_json_with_index_as_a_column_name(self):
+ df = DataFrame(data={"index": [1, 2], "a": [2, 3]})
+ with pytest.raises(
+ ValueError, match="Overlapping names between the index and columns"
+ ):
+ df.to_json(orient="table")
+
+ @pytest.mark.parametrize("dtype", [True, {"b": int, "c": int}])
+ def test_read_json_table_dtype_raises(self, dtype):
+ # GH21345
+ df = DataFrame({"a": [1, 2], "b": [3.0, 4.0], "c": ["5", "6"]})
+ dfjson = df.to_json(orient="table")
+ msg = "cannot pass both dtype and orient='table'"
+ with pytest.raises(ValueError, match=msg):
+ read_json(dfjson, orient="table", dtype=dtype)
+
+ @pytest.mark.parametrize("orient", ["index", "columns", "records", "values"])
+ def test_read_json_table_empty_axes_dtype(self, orient):
+ # GH28558
+
+ expected = DataFrame()
+ result = read_json(StringIO("{}"), orient=orient, convert_axes=True)
+ tm.assert_index_equal(result.index, expected.index)
+ tm.assert_index_equal(result.columns, expected.columns)
+
+ def test_read_json_table_convert_axes_raises(self):
+ # GH25433 GH25435
+ df = DataFrame([[1, 2], [3, 4]], index=[1.0, 2.0], columns=["1.", "2."])
+ dfjson = df.to_json(orient="table")
+ msg = "cannot pass both convert_axes and orient='table'"
+ with pytest.raises(ValueError, match=msg):
+ read_json(dfjson, orient="table", convert_axes=True)
+
+ @pytest.mark.parametrize(
+ "data, expected",
+ [
+ (
+ DataFrame([[1, 2], [4, 5]], columns=["a", "b"]),
+ {"columns": ["a", "b"], "data": [[1, 2], [4, 5]]},
+ ),
+ (
+ DataFrame([[1, 2], [4, 5]], columns=["a", "b"]).rename_axis("foo"),
+ {"columns": ["a", "b"], "data": [[1, 2], [4, 5]]},
+ ),
+ (
+ DataFrame(
+ [[1, 2], [4, 5]], columns=["a", "b"], index=[["a", "b"], ["c", "d"]]
+ ),
+ {"columns": ["a", "b"], "data": [[1, 2], [4, 5]]},
+ ),
+ (Series([1, 2, 3], name="A"), {"name": "A", "data": [1, 2, 3]}),
+ (
+ Series([1, 2, 3], name="A").rename_axis("foo"),
+ {"name": "A", "data": [1, 2, 3]},
+ ),
+ (
+ Series([1, 2], name="A", index=[["a", "b"], ["c", "d"]]),
+ {"name": "A", "data": [1, 2]},
+ ),
+ ],
+ )
+ def test_index_false_to_json_split(self, data, expected):
+ # GH 17394
+ # Testing index=False in to_json with orient='split'
+
+ result = data.to_json(orient="split", index=False)
+ result = json.loads(result)
+
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "data",
+ [
+ (DataFrame([[1, 2], [4, 5]], columns=["a", "b"])),
+ (DataFrame([[1, 2], [4, 5]], columns=["a", "b"]).rename_axis("foo")),
+ (
+ DataFrame(
+ [[1, 2], [4, 5]], columns=["a", "b"], index=[["a", "b"], ["c", "d"]]
+ )
+ ),
+ (Series([1, 2, 3], name="A")),
+ (Series([1, 2, 3], name="A").rename_axis("foo")),
+ (Series([1, 2], name="A", index=[["a", "b"], ["c", "d"]])),
+ ],
+ )
+ def test_index_false_to_json_table(self, data):
+ # GH 17394
+ # Testing index=False in to_json with orient='table'
+
+ result = data.to_json(orient="table", index=False)
+ result = json.loads(result)
+
+ expected = {
+ "schema": pd.io.json.build_table_schema(data, index=False),
+ "data": DataFrame(data).to_dict(orient="records"),
+ }
+
+ assert result == expected
+
+ @pytest.mark.parametrize("orient", ["index", "columns"])
+ def test_index_false_error_to_json(self, orient):
+ # GH 17394, 25513
+ # Testing error message from to_json with index=False
+
+ df = DataFrame([[1, 2], [4, 5]], columns=["a", "b"])
+
+ msg = (
+ "'index=False' is only valid when 'orient' is 'split', "
+ "'table', 'records', or 'values'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.to_json(orient=orient, index=False)
+
+ @pytest.mark.parametrize("orient", ["records", "values"])
+ def test_index_true_error_to_json(self, orient):
+ # GH 25513
+ # Testing error message from to_json with index=True
+
+ df = DataFrame([[1, 2], [4, 5]], columns=["a", "b"])
+
+ msg = (
+ "'index=True' is only valid when 'orient' is 'split', "
+ "'table', 'index', or 'columns'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.to_json(orient=orient, index=True)
+
+ @pytest.mark.parametrize("orient", ["split", "table"])
+ @pytest.mark.parametrize("index", [True, False])
+ def test_index_false_from_json_to_json(self, orient, index):
+ # GH25170
+ # Test index=False in from_json to_json
+ expected = DataFrame({"a": [1, 2], "b": [3, 4]})
+ dfjson = expected.to_json(orient=orient, index=index)
+ result = read_json(StringIO(dfjson), orient=orient)
+ tm.assert_frame_equal(result, expected)
+
+ def test_read_timezone_information(self):
+ # GH 25546
+ result = read_json(
+ StringIO('{"2019-01-01T11:00:00.000Z":88}'), typ="series", orient="index"
+ )
+ exp_dti = DatetimeIndex(["2019-01-01 11:00:00"], dtype="M8[us, UTC]")
+ expected = Series([88], index=exp_dti)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "url",
+ [
+ "s3://example-fsspec/",
+ "gcs://another-fsspec/file.json",
+ "filecache::s3://yet-another-fsspec/file.json",
+ "https://example-site.com/data",
+ "some-protocol://data.txt",
+ ],
+ )
+ def test_read_json_with_url_value(self, url):
+ # GH 36271
+ result = read_json(StringIO(f'{{"url":{{"0":"{url}"}}}}'))
+ expected = DataFrame({"url": [url]})
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "compression",
+ ["", ".gz", ".bz2", ".tar"],
+ )
+ def test_read_json_with_very_long_file_path(self, compression):
+ # GH 46718
+ long_json_path = f"{'a' * 1000}.json{compression}"
+ with pytest.raises(
+ FileNotFoundError, match=f"File {long_json_path} does not exist"
+ ):
+ # path too long for Windows is handled in file_exists() but raises in
+ # _get_data_from_filepath()
+ read_json(long_json_path)
+
+ @pytest.mark.parametrize(
+ "date_format,key", [("epoch", 86400000), ("iso", "1970-01-02T00:00:00.000")]
+ )
+ def test_datetime_as_label(self, date_format, key, unit):
+ df = DataFrame(
+ [[1]], columns=[(Timestamp(0) + pd.Timedelta("1D")).as_unit(unit)]
+ )
+ expected = f'{{"{key}":{{"0":1}}}}'
+
+ expected_warning = None
+ if date_format == "epoch":
+ expected_warning = Pandas4Warning
+
+ msg = (
+ "'epoch' date format is deprecated and will be removed in a future "
+ "version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(expected_warning, match=msg):
+ result = df.to_json(date_format=date_format)
+
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "date_format,key", [("epoch", 86400000), ("iso", "P1DT0H0M0S")]
+ )
+ def test_timedelta_as_label(self, date_format, key, unit):
+ df = DataFrame([[1]], columns=[pd.Timedelta("1D").as_unit(unit)])
+ expected = f'{{"{key}":{{"0":1}}}}'
+
+ expected_warning = None
+ if date_format == "epoch":
+ expected_warning = Pandas4Warning
+
+ msg = (
+ "'epoch' date format is deprecated and will be removed in a future "
+ "version, please use 'iso' date format instead."
+ )
+ with tm.assert_produces_warning(expected_warning, match=msg):
+ result = df.to_json(date_format=date_format)
+
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "orient,expected",
+ [
+ ("index", "{\"('a', 'b')\":{\"('c', 'd')\":1}}"),
+ ("columns", "{\"('c', 'd')\":{\"('a', 'b')\":1}}"),
+ # TODO: the below have separate encoding procedures
+ pytest.param(
+ "split",
+ "",
+ marks=pytest.mark.xfail(
+ reason="Produces JSON but not in a consistent manner"
+ ),
+ ),
+ pytest.param(
+ "table",
+ "",
+ marks=pytest.mark.xfail(
+ reason="Produces JSON but not in a consistent manner"
+ ),
+ ),
+ ],
+ )
+ def test_tuple_labels(self, orient, expected):
+ # GH 20500
+ df = DataFrame([[1]], index=[("a", "b")], columns=[("c", "d")])
+ result = df.to_json(orient=orient)
+ assert result == expected
+
+ @pytest.mark.parametrize("indent", [1, 2, 4])
+ def test_to_json_indent(self, indent):
+ # GH 12004
+ df = DataFrame([["foo", "bar"], ["baz", "qux"]], columns=["a", "b"])
+
+ result = df.to_json(indent=indent)
+ spaces = " " * indent
+ expected = f"""{{
+{spaces}"a":{{
+{spaces}{spaces}"0":"foo",
+{spaces}{spaces}"1":"baz"
+{spaces}}},
+{spaces}"b":{{
+{spaces}{spaces}"0":"bar",
+{spaces}{spaces}"1":"qux"
+{spaces}}}
+}}"""
+
+ assert result == expected
+
+ @pytest.mark.skipif(
+ using_string_dtype(),
+ reason="Adjust expected when infer_string is default, no bug here, "
+ "just a complicated parametrization",
+ )
+ @pytest.mark.parametrize(
+ "orient,expected",
+ [
+ (
+ "split",
+ """{
+ "columns":[
+ "a",
+ "b"
+ ],
+ "index":[
+ 0,
+ 1
+ ],
+ "data":[
+ [
+ "foo",
+ "bar"
+ ],
+ [
+ "baz",
+ "qux"
+ ]
+ ]
+}""",
+ ),
+ (
+ "records",
+ """[
+ {
+ "a":"foo",
+ "b":"bar"
+ },
+ {
+ "a":"baz",
+ "b":"qux"
+ }
+]""",
+ ),
+ (
+ "index",
+ """{
+ "0":{
+ "a":"foo",
+ "b":"bar"
+ },
+ "1":{
+ "a":"baz",
+ "b":"qux"
+ }
+}""",
+ ),
+ (
+ "columns",
+ """{
+ "a":{
+ "0":"foo",
+ "1":"baz"
+ },
+ "b":{
+ "0":"bar",
+ "1":"qux"
+ }
+}""",
+ ),
+ (
+ "values",
+ """[
+ [
+ "foo",
+ "bar"
+ ],
+ [
+ "baz",
+ "qux"
+ ]
+]""",
+ ),
+ (
+ "table",
+ """{
+ "schema":{
+ "fields":[
+ {
+ "name":"index",
+ "type":"integer"
+ },
+ {
+ "name":"a",
+ "type":"string"
+ },
+ {
+ "name":"b",
+ "type":"string"
+ }
+ ],
+ "primaryKey":[
+ "index"
+ ],
+ "pandas_version":"1.4.0"
+ },
+ "data":[
+ {
+ "index":0,
+ "a":"foo",
+ "b":"bar"
+ },
+ {
+ "index":1,
+ "a":"baz",
+ "b":"qux"
+ }
+ ]
+}""",
+ ),
+ ],
+ )
+ def test_json_indent_all_orients(self, orient, expected):
+ # GH 12004
+ df = DataFrame([["foo", "bar"], ["baz", "qux"]], columns=["a", "b"])
+ result = df.to_json(orient=orient, indent=4)
+ assert result == expected
+
+ def test_json_negative_indent_raises(self):
+ with pytest.raises(ValueError, match="must be a nonnegative integer"):
+ DataFrame().to_json(indent=-1)
+
+ def test_emca_262_nan_inf_support(self):
+ # GH 12213
+ data = StringIO(
+ '["a", NaN, "NaN", Infinity, "Infinity", -Infinity, "-Infinity"]'
+ )
+ result = read_json(data)
+ expected = DataFrame(
+ ["a", None, "NaN", np.inf, "Infinity", -np.inf, "-Infinity"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_frame_int_overflow(self):
+ # GH 30320
+ encoded_json = json.dumps([{"col": "31900441201190696999"}, {"col": "Text"}])
+ expected = DataFrame({"col": ["31900441201190696999", "Text"]})
+ result = read_json(StringIO(encoded_json))
+ tm.assert_frame_equal(result, expected)
+
+ def test_json_multiindex(self):
+ dataframe = DataFrame({"x": [1, 2, 3], "y": ["a", "b", "c"]})
+ expected = (
+ '{"(0, \'x\')":1,"(0, \'y\')":"a","(1, \'x\')":2,'
+ '"(1, \'y\')":"b","(2, \'x\')":3,"(2, \'y\')":"c"}'
+ )
+ series = dataframe.stack()
+ result = series.to_json(orient="index")
+ assert result == expected
+
+ @pytest.mark.single_cpu
+ @pytest.mark.network
+ def test_to_s3(self, s3_bucket_public, s3so):
+ # GH 28375
+ mock_bucket_name = s3_bucket_public.name
+ target_file = f"{uuid.uuid4()}.json"
+ df = DataFrame({"x": [1, 2, 3], "y": [2, 4, 6]})
+ uri = f"s3://{mock_bucket_name}/{target_file}"
+ df.to_json(uri, storage_options=s3so)
+ result = read_json(uri, storage_options=s3so)
+ tm.assert_frame_equal(result, df)
+
+ def test_json_pandas_nulls(self, nulls_fixture):
+ # GH 31615
+ expected_warning = None
+ msg = (
+ "The default 'epoch' date format is deprecated and will be removed "
+ "in a future version, please use 'iso' date format instead."
+ )
+ if nulls_fixture is pd.NaT:
+ expected_warning = Pandas4Warning
+
+ with tm.assert_produces_warning(expected_warning, match=msg):
+ result = DataFrame([[nulls_fixture]]).to_json()
+ assert result == '{"0":{"0":null}}'
+
+ def test_readjson_bool_series(self):
+ # GH31464
+ result = read_json(StringIO("[true, true, false]"), typ="series")
+ expected = Series([True, True, False])
+ tm.assert_series_equal(result, expected)
+
+ def test_to_json_multiindex_escape(self):
+ # GH 15273
+ df = DataFrame(
+ True,
+ index=date_range("2017-01-20", "2017-01-23", unit="ns"),
+ columns=["foo", "bar"],
+ ).stack()
+ result = df.to_json()
+ expected = (
+ "{\"(Timestamp('2017-01-20 00:00:00'), 'foo')\":true,"
+ "\"(Timestamp('2017-01-20 00:00:00'), 'bar')\":true,"
+ "\"(Timestamp('2017-01-21 00:00:00'), 'foo')\":true,"
+ "\"(Timestamp('2017-01-21 00:00:00'), 'bar')\":true,"
+ "\"(Timestamp('2017-01-22 00:00:00'), 'foo')\":true,"
+ "\"(Timestamp('2017-01-22 00:00:00'), 'bar')\":true,"
+ "\"(Timestamp('2017-01-23 00:00:00'), 'foo')\":true,"
+ "\"(Timestamp('2017-01-23 00:00:00'), 'bar')\":true}"
+ )
+ assert result == expected
+
+ def test_to_json_series_of_objects(self):
+ class _TestObject:
+ def __init__(self, a, b, _c, d) -> None:
+ self.a = a
+ self.b = b
+ self._c = _c
+ self.d = d
+
+ def e(self):
+ return 5
+
+ # JSON keys should be all non-callable non-underscore attributes, see GH-42768
+ series = Series([_TestObject(a=1, b=2, _c=3, d=4)])
+ assert json.loads(series.to_json()) == {"0": {"a": 1, "b": 2, "d": 4}}
+
+ @pytest.mark.parametrize(
+ "data,expected",
+ [
+ (
+ Series({0: -6 + 8j, 1: 0 + 1j, 2: 9 - 5j}),
+ '{"0":{"imag":8.0,"real":-6.0},'
+ '"1":{"imag":1.0,"real":0.0},'
+ '"2":{"imag":-5.0,"real":9.0}}',
+ ),
+ (
+ Series({0: -9.39 + 0.66j, 1: 3.95 + 9.32j, 2: 4.03 - 0.17j}),
+ '{"0":{"imag":0.66,"real":-9.39},'
+ '"1":{"imag":9.32,"real":3.95},'
+ '"2":{"imag":-0.17,"real":4.03}}',
+ ),
+ (
+ DataFrame([[-2 + 3j, -1 - 0j], [4 - 3j, -0 - 10j]]),
+ '{"0":{"0":{"imag":3.0,"real":-2.0},'
+ '"1":{"imag":-3.0,"real":4.0}},'
+ '"1":{"0":{"imag":0.0,"real":-1.0},'
+ '"1":{"imag":-10.0,"real":0.0}}}',
+ ),
+ (
+ DataFrame(
+ [[-0.28 + 0.34j, -1.08 - 0.39j], [0.41 - 0.34j, -0.78 - 1.35j]]
+ ),
+ '{"0":{"0":{"imag":0.34,"real":-0.28},'
+ '"1":{"imag":-0.34,"real":0.41}},'
+ '"1":{"0":{"imag":-0.39,"real":-1.08},'
+ '"1":{"imag":-1.35,"real":-0.78}}}',
+ ),
+ ],
+ )
+ def test_complex_data_tojson(self, data, expected):
+ # GH41174
+ result = data.to_json()
+ assert result == expected
+
+ def test_json_uint64(self):
+ # GH21073
+ expected = (
+ '{"columns":["col1"],"index":[0,1],'
+ '"data":[[13342205958987758245],[12388075603347835679]]}'
+ )
+ df = DataFrame(data={"col1": [13342205958987758245, 12388075603347835679]})
+ result = df.to_json(orient="split")
+ assert result == expected
+
+ def test_read_json_dtype_backend(
+ self, string_storage, dtype_backend, orient, using_infer_string
+ ):
+ # GH#50750
+ df = DataFrame(
+ {
+ "a": Series([1, NA, 3], dtype="Int64"),
+ "b": Series([1, 2, 3], dtype="Int64"),
+ "c": Series([1.5, NA, 2.5], dtype="Float64"),
+ "d": Series([1.5, 2.0, 2.5], dtype="Float64"),
+ "e": [True, False, None],
+ "f": [True, False, True],
+ "g": ["a", "b", "c"],
+ "h": ["a", "b", None],
+ }
+ )
+
+ out = df.to_json(orient=orient)
+ with pd.option_context("mode.string_storage", string_storage):
+ result = read_json(
+ StringIO(out), dtype_backend=dtype_backend, orient=orient
+ )
+
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ string_dtype = pd.ArrowDtype(pa.string())
+ else:
+ string_dtype = pd.StringDtype(string_storage)
+
+ expected = DataFrame(
+ {
+ "a": Series([1, NA, 3], dtype="Int64"),
+ "b": Series([1, 2, 3], dtype="Int64"),
+ "c": Series([1.5, NA, 2.5], dtype="Float64"),
+ "d": Series([1.5, 2.0, 2.5], dtype="Float64"),
+ "e": Series([True, False, NA], dtype="boolean"),
+ "f": Series([True, False, True], dtype="boolean"),
+ "g": Series(["a", "b", "c"], dtype=string_dtype),
+ "h": Series(["a", "b", None], dtype=string_dtype),
+ }
+ )
+
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ from pandas.arrays import ArrowExtensionArray
+
+ expected = DataFrame(
+ {
+ col: ArrowExtensionArray(pa.array(expected[col], from_pandas=True))
+ for col in expected.columns
+ }
+ )
+
+ if orient == "values":
+ expected.columns = list(range(8))
+
+ # the storage of the str columns' Index is also affected by the
+ # string_storage setting -> ignore that for checking the result
+ tm.assert_frame_equal(result, expected, check_column_type=False)
+
+ @td.skip_if_no("pyarrow")
+ @pytest.mark.filterwarnings("ignore:Passing a BlockManager:DeprecationWarning")
+ def test_read_json_pyarrow_with_dtype(self):
+ dtype = {"a": "int32[pyarrow]", "b": "int64[pyarrow]"}
+ json = b'{"a": 1, "b": 2}\n'
+
+ df = read_json(
+ BytesIO(json),
+ dtype=dtype,
+ lines=True,
+ engine="pyarrow",
+ dtype_backend="pyarrow",
+ )
+
+ result = df.dtypes
+ expected = Series(
+ data=[
+ pd.ArrowDtype.construct_from_string("int32[pyarrow]"),
+ pd.ArrowDtype.construct_from_string("int64[pyarrow]"),
+ ],
+ index=["a", "b"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("orient", ["split", "records", "index"])
+ def test_read_json_nullable_series(self, string_storage, dtype_backend, orient):
+ # GH#50750
+ pa = pytest.importorskip("pyarrow")
+ ser = Series([1, NA, 3], dtype="Int64")
+
+ out = ser.to_json(orient=orient)
+ with pd.option_context("mode.string_storage", string_storage):
+ result = read_json(
+ StringIO(out), dtype_backend=dtype_backend, orient=orient, typ="series"
+ )
+
+ expected = Series([1, NA, 3], dtype="Int64")
+
+ if dtype_backend == "pyarrow":
+ from pandas.arrays import ArrowExtensionArray
+
+ expected = Series(ArrowExtensionArray(pa.array(expected, from_pandas=True)))
+
+ tm.assert_series_equal(result, expected)
+
+ def test_invalid_dtype_backend(self):
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_json("test", dtype_backend="numpy")
+
+
+def test_invalid_engine():
+ # GH 48893
+ ser = Series(range(1))
+ out = ser.to_json()
+ with pytest.raises(ValueError, match="The engine type foo"):
+ read_json(out, engine="foo")
+
+
+def test_pyarrow_engine_lines_false():
+ # GH 48893
+ ser = Series(range(1))
+ out = ser.to_json()
+ with pytest.raises(ValueError, match="currently pyarrow engine only supports"):
+ read_json(out, engine="pyarrow", lines=False)
+
+
+def test_json_roundtrip_string_inference(orient):
+ df = DataFrame(
+ [["a", "b"], ["c", "d"]], index=["row 1", "row 2"], columns=["col 1", "col 2"]
+ )
+ out = df.to_json()
+ with pd.option_context("future.infer_string", True):
+ result = read_json(StringIO(out))
+ dtype = pd.StringDtype(na_value=np.nan)
+ expected = DataFrame(
+ [["a", "b"], ["c", "d"]],
+ dtype=dtype,
+ index=Index(["row 1", "row 2"], dtype=dtype),
+ columns=Index(["col 1", "col 2"], dtype=dtype),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_to_json_ea_null():
+ # GH#57224
+ df = DataFrame(
+ {
+ "a": Series([1, NA], dtype="int64[pyarrow]"),
+ "b": Series([2, NA], dtype="Int64"),
+ }
+ )
+ result = df.to_json(orient="records", lines=True)
+ expected = """{"a":1,"b":2}
+{"a":null,"b":null}
+"""
+ assert result == expected
+
+
+def test_read_json_lines_rangeindex():
+ # GH 57429
+ data = """
+{"a": 1, "b": 2}
+{"a": 3, "b": 4}
+"""
+ result = read_json(StringIO(data), lines=True).index
+ expected = RangeIndex(2)
+ tm.assert_index_equal(result, expected, exact=True)
+
+
+def test_large_number():
+ # GH#20608
+ result = read_json(
+ StringIO('["9999999999999999"]'),
+ orient="values",
+ typ="series",
+ convert_dates=False,
+ )
+ expected = Series([9999999999999999])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_readlines.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_readlines.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e65a11792391655d5c1726b4c60f6cc114f67ff
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_readlines.py
@@ -0,0 +1,544 @@
+from collections.abc import Iterator
+from io import (
+ BytesIO,
+ StringIO,
+)
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ read_json,
+)
+import pandas._testing as tm
+
+from pandas.io.json._json import JsonReader
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.fixture
+def lines_json_df():
+ df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
+ return df.to_json(lines=True, orient="records")
+
+
+@pytest.fixture(params=["ujson", "pyarrow"])
+def engine(request):
+ if request.param == "pyarrow":
+ pytest.importorskip("pyarrow.json")
+ return request.param
+
+
+def test_read_jsonl():
+ # GH9180
+ result = read_json(StringIO('{"a": 1, "b": 2}\n{"b":2, "a" :1}\n'), lines=True)
+ expected = DataFrame([[1, 2], [1, 2]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_jsonl_engine_pyarrow(datapath, engine):
+ result = read_json(
+ datapath("io", "json", "data", "line_delimited.json"),
+ lines=True,
+ engine=engine,
+ )
+ expected = DataFrame({"a": [1, 3, 5], "b": [2, 4, 6]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_datetime(request, engine):
+ # GH33787
+ if engine == "pyarrow":
+ # GH 48893
+ reason = "Pyarrow only supports a file path as an input and line delimited json"
+ request.applymarker(pytest.mark.xfail(reason=reason, raises=ValueError))
+
+ df = DataFrame(
+ [([1, 2], ["2020-03-05", "2020-04-08T09:58:49+00:00"], "hector")],
+ columns=["accounts", "date", "name"],
+ )
+ json_line = df.to_json(lines=True, orient="records")
+
+ if engine == "pyarrow":
+ result = read_json(StringIO(json_line), engine=engine)
+ else:
+ result = read_json(StringIO(json_line), engine=engine)
+ expected = DataFrame(
+ [[1, "2020-03-05", "hector"], [2, "2020-04-08T09:58:49+00:00", "hector"]],
+ columns=["accounts", "date", "name"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_jsonl_unicode_chars():
+ # GH15132: non-ascii unicode characters
+ # \u201d == RIGHT DOUBLE QUOTATION MARK
+
+ # simulate file handle
+ json = '{"a": "foo”", "b": "bar"}\n{"a": "foo", "b": "bar"}\n'
+ json = StringIO(json)
+ result = read_json(json, lines=True)
+ expected = DataFrame([["foo\u201d", "bar"], ["foo", "bar"]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+ # simulate string
+ json = '{"a": "foo”", "b": "bar"}\n{"a": "foo", "b": "bar"}\n'
+ result = read_json(StringIO(json), lines=True)
+ expected = DataFrame([["foo\u201d", "bar"], ["foo", "bar"]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_to_jsonl():
+ # GH9180
+ df = DataFrame([[1, 2], [1, 2]], columns=["a", "b"])
+ result = df.to_json(orient="records", lines=True)
+ expected = '{"a":1,"b":2}\n{"a":1,"b":2}\n'
+ assert result == expected
+
+ df = DataFrame([["foo}", "bar"], ['foo"', "bar"]], columns=["a", "b"])
+ result = df.to_json(orient="records", lines=True)
+ expected = '{"a":"foo}","b":"bar"}\n{"a":"foo\\"","b":"bar"}\n'
+ assert result == expected
+ tm.assert_frame_equal(read_json(StringIO(result), lines=True), df)
+
+ # GH15096: escaped characters in columns and data
+ df = DataFrame([["foo\\", "bar"], ['foo"', "bar"]], columns=["a\\", "b"])
+ result = df.to_json(orient="records", lines=True)
+ expected = '{"a\\\\":"foo\\\\","b":"bar"}\n{"a\\\\":"foo\\"","b":"bar"}\n'
+ assert result == expected
+ tm.assert_frame_equal(read_json(StringIO(result), lines=True), df)
+
+
+def test_to_jsonl_count_new_lines():
+ # GH36888
+ df = DataFrame([[1, 2], [1, 2]], columns=["a", "b"])
+ actual_new_lines_count = df.to_json(orient="records", lines=True).count("\n")
+ expected_new_lines_count = 2
+ assert actual_new_lines_count == expected_new_lines_count
+
+
+@pytest.mark.parametrize("chunksize", [1, 1.0])
+@pytest.mark.parametrize("buffer", [BytesIO, StringIO])
+def test_readjson_chunks(request, lines_json_df, chunksize, buffer, engine):
+ # Basic test that read_json(chunks=True) gives the same result as
+ # read_json(chunks=False)
+ # GH17048: memory usage when lines=True
+ # GH#28906: read binary json lines in chunks
+
+ if buffer == BytesIO:
+ lines_json_df = lines_json_df.encode()
+
+ if engine == "pyarrow":
+ # GH 48893
+ reason = (
+ "Pyarrow only supports a file path as an input and line delimited json"
+ "and doesn't support chunksize parameter."
+ )
+ request.applymarker(pytest.mark.xfail(reason=reason, raises=ValueError))
+
+ unchunked = read_json(buffer(lines_json_df), lines=True)
+ with (
+ buffer(lines_json_df) as buf,
+ read_json(buf, lines=True, chunksize=chunksize, engine=engine) as reader,
+ ):
+ chunked = pd.concat(reader)
+
+ tm.assert_frame_equal(chunked, unchunked)
+
+
+def test_readjson_chunksize_requires_lines(lines_json_df, engine):
+ msg = "chunksize can only be passed if lines=True"
+ with pytest.raises(ValueError, match=msg):
+ with read_json(
+ StringIO(lines_json_df), lines=False, chunksize=2, engine=engine
+ ) as _:
+ pass
+
+
+def test_readjson_chunks_series(request, engine):
+ if engine == "pyarrow":
+ # GH 48893
+ reason = (
+ "Pyarrow only supports a file path as an input and line delimited json"
+ "and doesn't support chunksize parameter."
+ )
+ request.applymarker(pytest.mark.xfail(reason=reason))
+
+ # Test reading line-format JSON to Series with chunksize param
+ s = pd.Series({"A": 1, "B": 2})
+
+ strio = StringIO(s.to_json(lines=True, orient="records"))
+ unchunked = read_json(strio, lines=True, typ="series", engine=engine)
+
+ strio = StringIO(s.to_json(lines=True, orient="records"))
+ with read_json(
+ strio, lines=True, typ="series", chunksize=1, engine=engine
+ ) as reader:
+ chunked = pd.concat(reader)
+
+ tm.assert_series_equal(chunked, unchunked)
+
+
+def test_readjson_each_chunk(request, lines_json_df, engine):
+ if engine == "pyarrow":
+ # GH 48893
+ reason = (
+ "Pyarrow only supports a file path as an input and line delimited json"
+ "and doesn't support chunksize parameter."
+ )
+ request.applymarker(pytest.mark.xfail(reason=reason, raises=ValueError))
+
+ # Other tests check that the final result of read_json(chunksize=True)
+ # is correct. This checks the intermediate chunks.
+ with read_json(
+ StringIO(lines_json_df), lines=True, chunksize=2, engine=engine
+ ) as reader:
+ chunks = list(reader)
+ assert chunks[0].shape == (2, 2)
+ assert chunks[1].shape == (1, 2)
+
+
+def test_readjson_chunks_from_file(request, engine, temp_file):
+ if engine == "pyarrow":
+ # GH 48893
+ reason = (
+ "Pyarrow only supports a file path as an input and line delimited json"
+ "and doesn't support chunksize parameter."
+ )
+ request.applymarker(pytest.mark.xfail(reason=reason, raises=ValueError))
+
+ df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
+ df.to_json(temp_file, lines=True, orient="records")
+ with read_json(temp_file, lines=True, chunksize=1, engine=engine) as reader:
+ chunked = pd.concat(reader)
+ unchunked = read_json(temp_file, lines=True, engine=engine)
+ tm.assert_frame_equal(unchunked, chunked)
+
+
+@pytest.mark.parametrize("chunksize", [None, 1])
+def test_readjson_chunks_closes(chunksize, temp_file):
+ df = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
+ df.to_json(temp_file, lines=True, orient="records")
+ reader = JsonReader(
+ temp_file,
+ orient=None,
+ typ="frame",
+ dtype=True,
+ convert_axes=True,
+ convert_dates=True,
+ keep_default_dates=True,
+ precise_float=False,
+ date_unit=None,
+ encoding=None,
+ lines=True,
+ chunksize=chunksize,
+ compression=None,
+ nrows=None,
+ )
+ with reader:
+ reader.read()
+ assert reader.handles.handle.closed, (
+ f"didn't close stream with chunksize = {chunksize}"
+ )
+
+
+@pytest.mark.parametrize("chunksize", [0, -1, 2.2, "foo"])
+def test_readjson_invalid_chunksize(lines_json_df, chunksize, engine):
+ msg = r"'chunksize' must be an integer >=1"
+
+ with pytest.raises(ValueError, match=msg):
+ with read_json(
+ StringIO(lines_json_df), lines=True, chunksize=chunksize, engine=engine
+ ) as _:
+ pass
+
+
+@pytest.mark.parametrize("chunksize", [None, 1, 2])
+def test_readjson_chunks_multiple_empty_lines(chunksize):
+ j = """
+
+ {"A":1,"B":4}
+
+
+
+ {"A":2,"B":5}
+
+
+
+
+
+
+
+ {"A":3,"B":6}
+ """
+ orig = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
+ test = read_json(StringIO(j), lines=True, chunksize=chunksize)
+ if chunksize is not None:
+ with test:
+ test = pd.concat(test)
+ tm.assert_frame_equal(orig, test, obj=f"chunksize: {chunksize}")
+
+
+def test_readjson_unicode(request, monkeypatch, engine, temp_file):
+ if engine == "pyarrow":
+ # GH 48893
+ reason = (
+ "Pyarrow only supports a file path as an input and line delimited json"
+ "and doesn't support chunksize parameter."
+ )
+ request.applymarker(pytest.mark.xfail(reason=reason, raises=ValueError))
+
+ monkeypatch.setattr("locale.getpreferredencoding", lambda do_setlocale: "cp949")
+ with open(temp_file, "w", encoding="utf-8") as f:
+ f.write('{"£©µÀÆÖÞßéöÿ":["АБВГДабвгд가"]}')
+
+ result = read_json(temp_file, engine=engine)
+ expected = DataFrame({"£©µÀÆÖÞßéöÿ": ["АБВГДабвгд가"]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("nrows", [1, 2])
+def test_readjson_nrows(nrows, engine):
+ # GH 33916
+ # Test reading line-format JSON to Series with nrows param
+ jsonl = """{"a": 1, "b": 2}
+ {"a": 3, "b": 4}
+ {"a": 5, "b": 6}
+ {"a": 7, "b": 8}"""
+ result = read_json(StringIO(jsonl), lines=True, nrows=nrows)
+ expected = DataFrame({"a": [1, 3, 5, 7], "b": [2, 4, 6, 8]}).iloc[:nrows]
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("nrows,chunksize", [(2, 2), (4, 2)])
+def test_readjson_nrows_chunks(request, nrows, chunksize, engine):
+ # GH 33916
+ # Test reading line-format JSON to Series with nrows and chunksize param
+ if engine == "pyarrow":
+ # GH 48893
+ reason = (
+ "Pyarrow only supports a file path as an input and line delimited json"
+ "and doesn't support chunksize parameter."
+ )
+ request.applymarker(pytest.mark.xfail(reason=reason, raises=ValueError))
+
+ jsonl = """{"a": 1, "b": 2}
+ {"a": 3, "b": 4}
+ {"a": 5, "b": 6}
+ {"a": 7, "b": 8}"""
+
+ if engine != "pyarrow":
+ with read_json(
+ StringIO(jsonl), lines=True, nrows=nrows, chunksize=chunksize, engine=engine
+ ) as reader:
+ chunked = pd.concat(reader)
+ else:
+ with read_json(
+ jsonl, lines=True, nrows=nrows, chunksize=chunksize, engine=engine
+ ) as reader:
+ chunked = pd.concat(reader)
+ expected = DataFrame({"a": [1, 3, 5, 7], "b": [2, 4, 6, 8]}).iloc[:nrows]
+ tm.assert_frame_equal(chunked, expected)
+
+
+def test_readjson_nrows_requires_lines(engine):
+ # GH 33916
+ # Test ValueError raised if nrows is set without setting lines in read_json
+ jsonl = """{"a": 1, "b": 2}
+ {"a": 3, "b": 4}
+ {"a": 5, "b": 6}
+ {"a": 7, "b": 8}"""
+ msg = "nrows can only be passed if lines=True"
+ with pytest.raises(ValueError, match=msg):
+ read_json(jsonl, lines=False, nrows=2, engine=engine)
+
+
+def test_readjson_lines_chunks_fileurl(request, datapath, engine):
+ # GH 27135
+ # Test reading line-format JSON from file url
+ if engine == "pyarrow":
+ # GH 48893
+ reason = (
+ "Pyarrow only supports a file path as an input and line delimited json"
+ "and doesn't support chunksize parameter."
+ )
+ request.applymarker(pytest.mark.xfail(reason=reason, raises=ValueError))
+
+ df_list_expected = [
+ DataFrame([[1, 2]], columns=["a", "b"], index=[0]),
+ DataFrame([[3, 4]], columns=["a", "b"], index=[1]),
+ DataFrame([[5, 6]], columns=["a", "b"], index=[2]),
+ ]
+ os_path = datapath("io", "json", "data", "line_delimited.json")
+ file_url = Path(os_path).as_uri()
+ with read_json(file_url, lines=True, chunksize=1, engine=engine) as url_reader:
+ for index, chuck in enumerate(url_reader):
+ tm.assert_frame_equal(chuck, df_list_expected[index])
+
+
+def test_chunksize_is_incremental():
+ # See https://github.com/pandas-dev/pandas/issues/34548
+ jsonl = (
+ """{"a": 1, "b": 2}
+ {"a": 3, "b": 4}
+ {"a": 5, "b": 6}
+ {"a": 7, "b": 8}\n"""
+ * 1000
+ )
+
+ class MyReader:
+ def __init__(self, contents) -> None:
+ self.read_count = 0
+ self.stringio = StringIO(contents)
+
+ def read(self, *args):
+ self.read_count += 1
+ return self.stringio.read(*args)
+
+ def __iter__(self) -> Iterator:
+ self.read_count += 1
+ return iter(self.stringio)
+
+ reader = MyReader(jsonl)
+ assert len(list(read_json(reader, lines=True, chunksize=100))) > 1
+ assert reader.read_count > 10
+
+
+@pytest.mark.parametrize("orient_", ["split", "index", "table"])
+def test_to_json_append_orient(orient_):
+ # GH 35849
+ # Test ValueError when orient is not 'records'
+ df = DataFrame({"col1": [1, 2], "col2": ["a", "b"]})
+ msg = (
+ r"mode='a' \(append\) is only supported when "
+ "lines is True and orient is 'records'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.to_json(mode="a", orient=orient_)
+
+
+def test_to_json_append_lines():
+ # GH 35849
+ # Test ValueError when lines is not True
+ df = DataFrame({"col1": [1, 2], "col2": ["a", "b"]})
+ msg = (
+ r"mode='a' \(append\) is only supported when "
+ "lines is True and orient is 'records'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.to_json(mode="a", lines=False, orient="records")
+
+
+@pytest.mark.parametrize("mode_", ["r", "x"])
+def test_to_json_append_mode(mode_):
+ # GH 35849
+ # Test ValueError when mode is not supported option
+ df = DataFrame({"col1": [1, 2], "col2": ["a", "b"]})
+ msg = (
+ f"mode={mode_} is not a valid option.Only 'w' and 'a' are currently supported."
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.to_json(mode=mode_, lines=False, orient="records")
+
+
+def test_to_json_append_output_consistent_columns(temp_file):
+ # GH 35849
+ # Testing that resulting output reads in as expected.
+ # Testing same columns, new rows
+ df1 = DataFrame({"col1": [1, 2], "col2": ["a", "b"]})
+ df2 = DataFrame({"col1": [3, 4], "col2": ["c", "d"]})
+
+ expected = DataFrame({"col1": [1, 2, 3, 4], "col2": ["a", "b", "c", "d"]})
+ # Save dataframes to the same file
+ df1.to_json(temp_file, lines=True, orient="records")
+ df2.to_json(temp_file, mode="a", lines=True, orient="records")
+
+ # Read path file
+ result = read_json(temp_file, lines=True)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_to_json_append_output_inconsistent_columns(temp_file):
+ # GH 35849
+ # Testing that resulting output reads in as expected.
+ # Testing one new column, one old column, new rows
+ df1 = DataFrame({"col1": [1, 2], "col2": ["a", "b"]})
+ df3 = DataFrame({"col2": ["e", "f"], "col3": ["!", "#"]})
+
+ expected = DataFrame(
+ {
+ "col1": [1, 2, None, None],
+ "col2": ["a", "b", "e", "f"],
+ "col3": [np.nan, np.nan, "!", "#"],
+ }
+ )
+ # Save dataframes to the same file
+ df1.to_json(temp_file, mode="a", lines=True, orient="records")
+ df3.to_json(temp_file, mode="a", lines=True, orient="records")
+
+ # Read path file
+ result = read_json(temp_file, lines=True)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_to_json_append_output_different_columns(temp_file):
+ # GH 35849
+ # Testing that resulting output reads in as expected.
+ # Testing same, differing and new columns
+ df1 = DataFrame({"col1": [1, 2], "col2": ["a", "b"]})
+ df2 = DataFrame({"col1": [3, 4], "col2": ["c", "d"]})
+ df3 = DataFrame({"col2": ["e", "f"], "col3": ["!", "#"]})
+ df4 = DataFrame({"col4": [True, False]})
+
+ expected = DataFrame(
+ {
+ "col1": [1, 2, 3, 4, None, None, None, None],
+ "col2": ["a", "b", "c", "d", "e", "f", np.nan, np.nan],
+ "col3": [np.nan, np.nan, np.nan, np.nan, "!", "#", np.nan, np.nan],
+ "col4": [None, None, None, None, None, None, True, False],
+ }
+ ).astype({"col4": "float"})
+ # Save dataframes to the same file
+ df1.to_json(temp_file, mode="a", lines=True, orient="records")
+ df2.to_json(temp_file, mode="a", lines=True, orient="records")
+ df3.to_json(temp_file, mode="a", lines=True, orient="records")
+ df4.to_json(temp_file, mode="a", lines=True, orient="records")
+
+ # Read path file
+ result = read_json(temp_file, lines=True)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_to_json_append_output_different_columns_reordered(temp_file):
+ # GH 35849
+ # Testing that resulting output reads in as expected.
+ # Testing specific result column order.
+ df1 = DataFrame({"col1": [1, 2], "col2": ["a", "b"]})
+ df2 = DataFrame({"col1": [3, 4], "col2": ["c", "d"]})
+ df3 = DataFrame({"col2": ["e", "f"], "col3": ["!", "#"]})
+ df4 = DataFrame({"col4": [True, False]})
+
+ # df4, df3, df2, df1 (in that order)
+ expected = DataFrame(
+ {
+ "col4": [True, False, None, None, None, None, None, None],
+ "col2": [np.nan, np.nan, "e", "f", "c", "d", "a", "b"],
+ "col3": [np.nan, np.nan, "!", "#", np.nan, np.nan, np.nan, np.nan],
+ "col1": [None, None, None, None, 3, 4, 1, 2],
+ }
+ ).astype({"col4": "float"})
+ # Save dataframes to the same file
+ df4.to_json(temp_file, mode="a", lines=True, orient="records")
+ df3.to_json(temp_file, mode="a", lines=True, orient="records")
+ df2.to_json(temp_file, mode="a", lines=True, orient="records")
+ df1.to_json(temp_file, mode="a", lines=True, orient="records")
+
+ # Read path file
+ result = read_json(temp_file, lines=True)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_ujson.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_ujson.py
new file mode 100644
index 0000000000000000000000000000000000000000..9470841c87abb081440bca72b75ea5c261f53299
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/json/test_ujson.py
@@ -0,0 +1,1045 @@
+import calendar
+import datetime
+import decimal
+import json
+import locale
+import math
+import re
+import time
+
+import dateutil
+import numpy as np
+import pytest
+
+import pandas._libs.json as ujson
+from pandas.compat import IS64
+
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ NaT,
+ PeriodIndex,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def _clean_dict(d):
+ """
+ Sanitize dictionary for JSON by converting all keys to strings.
+
+ Parameters
+ ----------
+ d : dict
+ The dictionary to convert.
+
+ Returns
+ -------
+ cleaned_dict : dict
+ """
+ return {str(k): v for k, v in d.items()}
+
+
+@pytest.fixture(
+ params=[None, "split", "records", "values", "index"] # Column indexed by default.
+)
+def orient(request):
+ return request.param
+
+
+class TestUltraJSONTests:
+ @pytest.mark.skipif(not IS64, reason="not compliant on 32-bit, xref #15865")
+ @pytest.mark.parametrize(
+ "value, double_precision",
+ [
+ ("1337.1337", 15),
+ ("0.95", 1),
+ ("0.94", 1),
+ ("1.95", 1),
+ ("-1.95", 1),
+ ("0.995", 2),
+ ("0.9995", 3),
+ ("0.99999999999999944", 15),
+ ],
+ )
+ def test_encode_decimal(self, value, double_precision):
+ sut = decimal.Decimal(value)
+ encoded = ujson.ujson_dumps(sut, double_precision=double_precision)
+ decoded = ujson.ujson_loads(encoded)
+ assert decoded == value
+
+ @pytest.mark.parametrize("ensure_ascii", [True, False])
+ def test_encode_string_conversion(self, ensure_ascii):
+ string_input = "A string \\ / \b \f \n \r \t &"
+ not_html_encoded = '"A string \\\\ \\/ \\b \\f \\n \\r \\t <\\/script> &"'
+ html_encoded = (
+ '"A string \\\\ \\/ \\b \\f \\n \\r \\t \\u003c\\/script\\u003e \\u0026"'
+ )
+
+ def helper(expected_output, **encode_kwargs):
+ output = ujson.ujson_dumps(
+ string_input, ensure_ascii=ensure_ascii, **encode_kwargs
+ )
+
+ assert output == expected_output
+ assert string_input == json.loads(output)
+ assert string_input == ujson.ujson_loads(output)
+
+ # Default behavior assumes encode_html_chars=False.
+ helper(not_html_encoded)
+
+ # Make sure explicit encode_html_chars=False works.
+ helper(not_html_encoded, encode_html_chars=False)
+
+ # Make sure explicit encode_html_chars=True does the encoding.
+ helper(html_encoded, encode_html_chars=True)
+
+ @pytest.mark.parametrize(
+ "long_number", [-4342969734183514, -12345678901234.56789012, -528656961.4399388]
+ )
+ def test_double_long_numbers(self, long_number):
+ sut = {"a": long_number}
+ encoded = ujson.ujson_dumps(sut, double_precision=15)
+
+ decoded = ujson.ujson_loads(encoded)
+ assert sut == decoded
+
+ def test_encode_non_c_locale(self):
+ lc_category = locale.LC_NUMERIC
+
+ # We just need one of these locales to work.
+ for new_locale in ("it_IT.UTF-8", "Italian_Italy"):
+ if tm.can_set_locale(new_locale, lc_category):
+ with tm.set_locale(new_locale, lc_category):
+ assert ujson.ujson_loads(ujson.ujson_dumps(4.78e60)) == 4.78e60
+ assert ujson.ujson_loads("4.78", precise_float=True) == 4.78
+ break
+
+ def test_decimal_decode_test_precise(self):
+ sut = {"a": 4.56}
+ encoded = ujson.ujson_dumps(sut)
+ decoded = ujson.ujson_loads(encoded, precise_float=True)
+ assert sut == decoded
+
+ def test_encode_double_tiny_exponential(self):
+ num = 1e-40
+ assert num == ujson.ujson_loads(ujson.ujson_dumps(num))
+ num = 1e-100
+ assert num == ujson.ujson_loads(ujson.ujson_dumps(num))
+ num = -1e-45
+ assert num == ujson.ujson_loads(ujson.ujson_dumps(num))
+ num = -1e-145
+ assert np.allclose(num, ujson.ujson_loads(ujson.ujson_dumps(num)))
+
+ @pytest.mark.parametrize("unicode_key", ["key1", "بن"])
+ def test_encode_dict_with_unicode_keys(self, unicode_key):
+ unicode_dict = {unicode_key: "value1"}
+ assert unicode_dict == ujson.ujson_loads(ujson.ujson_dumps(unicode_dict))
+
+ @pytest.mark.parametrize("double_input", [math.pi, -math.pi])
+ def test_encode_double_conversion(self, double_input):
+ output = ujson.ujson_dumps(double_input)
+ assert round(double_input, 5) == round(json.loads(output), 5)
+ assert round(double_input, 5) == round(ujson.ujson_loads(output), 5)
+
+ def test_encode_with_decimal(self):
+ decimal_input = 1.0
+ output = ujson.ujson_dumps(decimal_input)
+
+ assert output == "1.0"
+
+ def test_encode_array_of_nested_arrays(self):
+ nested_input = [[[[]]]] * 20
+ output = ujson.ujson_dumps(nested_input)
+
+ assert nested_input == json.loads(output)
+ assert nested_input == ujson.ujson_loads(output)
+
+ def test_encode_array_of_doubles(self):
+ doubles_input = [31337.31337, 31337.31337, 31337.31337, 31337.31337] * 10
+ output = ujson.ujson_dumps(doubles_input)
+
+ assert doubles_input == json.loads(output)
+ assert doubles_input == ujson.ujson_loads(output)
+
+ def test_double_precision(self):
+ double_input = 30.012345678901234
+ output = ujson.ujson_dumps(double_input, double_precision=15)
+
+ assert double_input == json.loads(output)
+ assert double_input == ujson.ujson_loads(output)
+
+ for double_precision in (3, 9):
+ output = ujson.ujson_dumps(double_input, double_precision=double_precision)
+ rounded_input = round(double_input, double_precision)
+
+ assert rounded_input == json.loads(output)
+ assert rounded_input == ujson.ujson_loads(output)
+
+ @pytest.mark.parametrize(
+ "invalid_val",
+ [
+ 20,
+ -1,
+ "9",
+ None,
+ ],
+ )
+ def test_invalid_double_precision(self, invalid_val):
+ double_input = 30.12345678901234567890
+ expected_exception = ValueError if isinstance(invalid_val, int) else TypeError
+ msg = (
+ r"Invalid value '.*' for option 'double_precision', max is '15'|"
+ r"an integer is required \(got type |"
+ r"object cannot be interpreted as an integer"
+ )
+ with pytest.raises(expected_exception, match=msg):
+ ujson.ujson_dumps(double_input, double_precision=invalid_val)
+
+ def test_encode_string_conversion2(self):
+ string_input = "A string \\ / \b \f \n \r \t"
+ output = ujson.ujson_dumps(string_input)
+
+ assert string_input == json.loads(output)
+ assert string_input == ujson.ujson_loads(output)
+ assert output == '"A string \\\\ \\/ \\b \\f \\n \\r \\t"'
+
+ @pytest.mark.parametrize(
+ "unicode_input",
+ ["Räksmörgås اسامة بن محمد بن عوض بن لادن", "\xe6\x97\xa5\xd1\x88"],
+ )
+ def test_encode_unicode_conversion(self, unicode_input):
+ enc = ujson.ujson_dumps(unicode_input)
+ dec = ujson.ujson_loads(enc)
+
+ assert enc == json.dumps(unicode_input)
+ assert dec == json.loads(enc)
+
+ def test_encode_control_escaping(self):
+ escaped_input = "\x19"
+ enc = ujson.ujson_dumps(escaped_input)
+ dec = ujson.ujson_loads(enc)
+
+ assert escaped_input == dec
+ assert enc == json.dumps(escaped_input)
+
+ def test_encode_unicode_surrogate_pair(self):
+ surrogate_input = "\xf0\x90\x8d\x86"
+ enc = ujson.ujson_dumps(surrogate_input)
+ dec = ujson.ujson_loads(enc)
+
+ assert enc == json.dumps(surrogate_input)
+ assert dec == json.loads(enc)
+
+ def test_encode_unicode_4bytes_utf8(self):
+ four_bytes_input = "\xf0\x91\x80\xb0TRAILINGNORMAL"
+ enc = ujson.ujson_dumps(four_bytes_input)
+ dec = ujson.ujson_loads(enc)
+
+ assert enc == json.dumps(four_bytes_input)
+ assert dec == json.loads(enc)
+
+ def test_encode_unicode_4bytes_utf8highest(self):
+ four_bytes_input = "\xf3\xbf\xbf\xbfTRAILINGNORMAL"
+ enc = ujson.ujson_dumps(four_bytes_input)
+
+ dec = ujson.ujson_loads(enc)
+
+ assert enc == json.dumps(four_bytes_input)
+ assert dec == json.loads(enc)
+
+ def test_encode_unicode_error(self):
+ string = "'\udac0'"
+ msg = (
+ r"'utf-8' codec can't encode character '\\udac0' "
+ r"in position 1: surrogates not allowed"
+ )
+ with pytest.raises(UnicodeEncodeError, match=msg):
+ ujson.ujson_dumps([string])
+
+ def test_encode_array_in_array(self):
+ arr_in_arr_input = [[[[]]]]
+ output = ujson.ujson_dumps(arr_in_arr_input)
+
+ assert arr_in_arr_input == json.loads(output)
+ assert output == json.dumps(arr_in_arr_input)
+ assert arr_in_arr_input == ujson.ujson_loads(output)
+
+ @pytest.mark.parametrize(
+ "num_input",
+ [
+ 31337,
+ -31337, # Negative number.
+ -9223372036854775808, # Large negative number.
+ ],
+ )
+ def test_encode_num_conversion(self, num_input):
+ output = ujson.ujson_dumps(num_input)
+ assert num_input == json.loads(output)
+ assert output == json.dumps(num_input)
+ assert num_input == ujson.ujson_loads(output)
+
+ def test_encode_list_conversion(self):
+ list_input = [1, 2, 3, 4]
+ output = ujson.ujson_dumps(list_input)
+
+ assert list_input == json.loads(output)
+ assert list_input == ujson.ujson_loads(output)
+
+ def test_encode_dict_conversion(self):
+ dict_input = {"k1": 1, "k2": 2, "k3": 3, "k4": 4}
+ output = ujson.ujson_dumps(dict_input)
+
+ assert dict_input == json.loads(output)
+ assert dict_input == ujson.ujson_loads(output)
+
+ @pytest.mark.parametrize("builtin_value", [None, True, False])
+ def test_encode_builtin_values_conversion(self, builtin_value):
+ output = ujson.ujson_dumps(builtin_value)
+ assert builtin_value == json.loads(output)
+ assert output == json.dumps(builtin_value)
+ assert builtin_value == ujson.ujson_loads(output)
+
+ def test_encode_datetime_conversion(self):
+ datetime_input = datetime.datetime.fromtimestamp(time.time())
+ output = ujson.ujson_dumps(datetime_input, date_unit="s")
+ expected = calendar.timegm(datetime_input.utctimetuple())
+
+ assert int(expected) == json.loads(output)
+ assert int(expected) == ujson.ujson_loads(output)
+
+ def test_encode_date_conversion(self):
+ date_input = datetime.date.fromtimestamp(time.time())
+ output = ujson.ujson_dumps(date_input, date_unit="s")
+
+ tup = (date_input.year, date_input.month, date_input.day, 0, 0, 0)
+ expected = calendar.timegm(tup)
+
+ assert int(expected) == json.loads(output)
+ assert int(expected) == ujson.ujson_loads(output)
+
+ @pytest.mark.parametrize(
+ "test",
+ [datetime.time(), datetime.time(1, 2, 3), datetime.time(10, 12, 15, 343243)],
+ )
+ def test_encode_time_conversion_basic(self, test):
+ output = ujson.ujson_dumps(test)
+ expected = f'"{test.isoformat()}"'
+ assert expected == output
+
+ def test_encode_time_conversion_pytz(self):
+ # see gh-11473: to_json segfaults with timezone-aware datetimes
+ pytz = pytest.importorskip("pytz")
+ test = datetime.time(10, 12, 15, 343243, pytz.utc)
+ output = ujson.ujson_dumps(test)
+ expected = f'"{test.isoformat()}"'
+ assert expected == output
+
+ def test_encode_time_conversion_dateutil(self):
+ # see gh-11473: to_json segfaults with timezone-aware datetimes
+ test = datetime.time(10, 12, 15, 343243, dateutil.tz.tzutc())
+ output = ujson.ujson_dumps(test)
+ expected = f'"{test.isoformat()}"'
+ assert expected == output
+
+ @pytest.mark.parametrize(
+ "decoded_input", [NaT, np.datetime64("NaT"), np.nan, np.inf, -np.inf]
+ )
+ def test_encode_as_null(self, decoded_input):
+ assert ujson.ujson_dumps(decoded_input) == "null", "Expected null"
+
+ def test_datetime_units(self):
+ val = datetime.datetime(2013, 8, 17, 21, 17, 12, 215504)
+ stamp = Timestamp(val).as_unit("ns")
+
+ roundtrip = ujson.ujson_loads(ujson.ujson_dumps(val, date_unit="s"))
+ assert roundtrip == stamp._value // 10**9
+
+ roundtrip = ujson.ujson_loads(ujson.ujson_dumps(val, date_unit="ms"))
+ assert roundtrip == stamp._value // 10**6
+
+ roundtrip = ujson.ujson_loads(ujson.ujson_dumps(val, date_unit="us"))
+ assert roundtrip == stamp._value // 10**3
+
+ roundtrip = ujson.ujson_loads(ujson.ujson_dumps(val, date_unit="ns"))
+ assert roundtrip == stamp._value
+
+ msg = "Invalid value 'foo' for option 'date_unit'"
+ with pytest.raises(ValueError, match=msg):
+ ujson.ujson_dumps(val, date_unit="foo")
+
+ def test_encode_to_utf8(self):
+ unencoded = "\xe6\x97\xa5\xd1\x88"
+
+ enc = ujson.ujson_dumps(unencoded, ensure_ascii=False)
+ dec = ujson.ujson_loads(enc)
+
+ assert enc == json.dumps(unencoded, ensure_ascii=False)
+ assert dec == json.loads(enc)
+
+ def test_decode_from_unicode(self):
+ unicode_input = '{"obj": 31337}'
+
+ dec1 = ujson.ujson_loads(unicode_input)
+ dec2 = ujson.ujson_loads(str(unicode_input))
+
+ assert dec1 == dec2
+
+ def test_encode_recursion_max(self):
+ # 8 is the max recursion depth
+
+ class O2:
+ member = 0
+
+ class O1:
+ member = 0
+
+ decoded_input = O1()
+ decoded_input.member = O2()
+ decoded_input.member.member = decoded_input
+
+ with pytest.raises(OverflowError, match="Maximum recursion level reached"):
+ ujson.ujson_dumps(decoded_input)
+
+ def test_decode_jibberish(self):
+ jibberish = "fdsa sda v9sa fdsa"
+ msg = "Unexpected character found when decoding 'false'"
+ with pytest.raises(ValueError, match=msg):
+ ujson.ujson_loads(jibberish)
+
+ @pytest.mark.parametrize(
+ "broken_json",
+ [
+ "[", # Broken array start.
+ "{", # Broken object start.
+ "]", # Broken array end.
+ "}", # Broken object end.
+ ],
+ )
+ def test_decode_broken_json(self, broken_json):
+ msg = "Expected object or value"
+ with pytest.raises(ValueError, match=msg):
+ ujson.ujson_loads(broken_json)
+
+ @pytest.mark.parametrize("too_big_char", ["[", "{"])
+ def test_decode_depth_too_big(self, too_big_char):
+ with pytest.raises(ValueError, match="Reached object decoding depth limit"):
+ ujson.ujson_loads(too_big_char * (1024 * 1024))
+
+ @pytest.mark.parametrize(
+ "bad_string",
+ [
+ '"TESTING', # Unterminated.
+ '"TESTING\\"', # Unterminated escape.
+ "tru", # Broken True.
+ "fa", # Broken False.
+ "n", # Broken None.
+ ],
+ )
+ def test_decode_bad_string(self, bad_string):
+ msg = (
+ "Unexpected character found when decoding|"
+ "Unmatched ''\"' when when decoding 'string'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ujson.ujson_loads(bad_string)
+
+ @pytest.mark.parametrize(
+ "broken_json, err_msg",
+ [
+ (
+ '{{1337:""}}',
+ "Key name of object must be 'string' when decoding 'object'",
+ ),
+ ('{{"key":"}', "Unmatched ''\"' when when decoding 'string'"),
+ ("[[[true", "Unexpected character found when decoding array value (2)"),
+ ],
+ )
+ def test_decode_broken_json_leak(self, broken_json, err_msg):
+ for _ in range(1000):
+ with pytest.raises(ValueError, match=re.escape(err_msg)):
+ ujson.ujson_loads(broken_json)
+
+ @pytest.mark.parametrize(
+ "invalid_dict",
+ [
+ "{{{{31337}}}}", # No key.
+ '{{{{"key":}}}}', # No value.
+ '{{{{"key"}}}}', # No colon or value.
+ ],
+ )
+ def test_decode_invalid_dict(self, invalid_dict):
+ msg = (
+ "Key name of object must be 'string' when decoding 'object'|"
+ "No ':' found when decoding object value|"
+ "Expected object or value"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ujson.ujson_loads(invalid_dict)
+
+ @pytest.mark.parametrize("numeric_int_as_str", ["31337", "-31337"])
+ def test_decode_numeric_int(self, numeric_int_as_str):
+ assert int(numeric_int_as_str) == ujson.ujson_loads(numeric_int_as_str)
+
+ def test_encode_null_character(self):
+ wrapped_input = "31337 \x00 1337"
+ output = ujson.ujson_dumps(wrapped_input)
+
+ assert wrapped_input == json.loads(output)
+ assert output == json.dumps(wrapped_input)
+ assert wrapped_input == ujson.ujson_loads(output)
+
+ alone_input = "\x00"
+ output = ujson.ujson_dumps(alone_input)
+
+ assert alone_input == json.loads(output)
+ assert output == json.dumps(alone_input)
+ assert alone_input == ujson.ujson_loads(output)
+ assert '" \\u0000\\r\\n "' == ujson.ujson_dumps(" \u0000\r\n ")
+
+ def test_decode_null_character(self):
+ wrapped_input = '"31337 \\u0000 31337"'
+ assert ujson.ujson_loads(wrapped_input) == json.loads(wrapped_input)
+
+ def test_encode_list_long_conversion(self):
+ long_input = [
+ 9223372036854775807,
+ 9223372036854775807,
+ 9223372036854775807,
+ 9223372036854775807,
+ 9223372036854775807,
+ 9223372036854775807,
+ ]
+ output = ujson.ujson_dumps(long_input)
+
+ assert long_input == json.loads(output)
+ assert long_input == ujson.ujson_loads(output)
+
+ @pytest.mark.parametrize("long_input", [9223372036854775807, 18446744073709551615])
+ def test_encode_long_conversion(self, long_input):
+ output = ujson.ujson_dumps(long_input)
+
+ assert long_input == json.loads(output)
+ assert output == json.dumps(long_input)
+ assert long_input == ujson.ujson_loads(output)
+
+ @pytest.mark.parametrize("bigNum", [2**64, -(2**63) - 1])
+ def test_dumps_ints_larger_than_maxsize(self, bigNum):
+ encoding = ujson.ujson_dumps(bigNum)
+ assert str(bigNum) == encoding
+
+ with pytest.raises(
+ ValueError,
+ match="Value is too big|Value is too small",
+ ):
+ assert ujson.ujson_loads(encoding) == bigNum
+
+ @pytest.mark.parametrize(
+ "int_exp", ["1337E40", "1.337E40", "1337E+9", "1.337e+40", "1.337E-4"]
+ )
+ def test_decode_numeric_int_exp(self, int_exp):
+ assert ujson.ujson_loads(int_exp) == json.loads(int_exp)
+
+ def test_loads_non_str_bytes_raises(self):
+ msg = "a bytes-like object is required, not 'NoneType'"
+ with pytest.raises(TypeError, match=msg):
+ ujson.ujson_loads(None)
+
+ @pytest.mark.parametrize("val", [3590016419, 2**31, 2**32, (2**32) - 1])
+ def test_decode_number_with_32bit_sign_bit(self, val):
+ # Test that numbers that fit within 32 bits but would have the
+ # sign bit set (2**31 <= x < 2**32) are decoded properly.
+ doc = f'{{"id": {val}}}'
+ assert ujson.ujson_loads(doc)["id"] == val
+
+ def test_encode_big_escape(self):
+ # Make sure no Exception is raised.
+ for _ in range(10):
+ base = "\u00e5".encode()
+ escape_input = base * 1024 * 1024 * 2
+ ujson.ujson_dumps(escape_input)
+
+ def test_decode_big_escape(self):
+ # Make sure no Exception is raised.
+ for _ in range(10):
+ base = "\u00e5".encode()
+ quote = b'"'
+
+ escape_input = quote + (base * 1024 * 1024 * 2) + quote
+ ujson.ujson_loads(escape_input)
+
+ def test_to_dict(self):
+ d = {"key": 31337}
+
+ class DictTest:
+ def toDict(self):
+ return d
+
+ o = DictTest()
+ output = ujson.ujson_dumps(o)
+
+ dec = ujson.ujson_loads(output)
+ assert dec == d
+
+ def test_default_handler(self):
+ class _TestObject:
+ def __init__(self, val) -> None:
+ self.val = val
+
+ @property
+ def recursive_attr(self):
+ return _TestObject("recursive_attr")
+
+ def __str__(self) -> str:
+ return str(self.val)
+
+ msg = "Maximum recursion level reached"
+ with pytest.raises(OverflowError, match=msg):
+ ujson.ujson_dumps(_TestObject("foo"))
+ assert '"foo"' == ujson.ujson_dumps(_TestObject("foo"), default_handler=str)
+
+ def my_handler(_):
+ return "foobar"
+
+ assert '"foobar"' == ujson.ujson_dumps(
+ _TestObject("foo"), default_handler=my_handler
+ )
+
+ def my_handler_raises(_):
+ raise TypeError("I raise for anything")
+
+ with pytest.raises(TypeError, match="I raise for anything"):
+ ujson.ujson_dumps(_TestObject("foo"), default_handler=my_handler_raises)
+
+ def my_int_handler(_):
+ return 42
+
+ assert (
+ ujson.ujson_loads(
+ ujson.ujson_dumps(_TestObject("foo"), default_handler=my_int_handler)
+ )
+ == 42
+ )
+
+ def my_obj_handler(_):
+ return datetime.datetime(2013, 2, 3)
+
+ assert ujson.ujson_loads(
+ ujson.ujson_dumps(datetime.datetime(2013, 2, 3))
+ ) == ujson.ujson_loads(
+ ujson.ujson_dumps(_TestObject("foo"), default_handler=my_obj_handler)
+ )
+
+ obj_list = [_TestObject("foo"), _TestObject("bar")]
+ assert json.loads(json.dumps(obj_list, default=str)) == ujson.ujson_loads(
+ ujson.ujson_dumps(obj_list, default_handler=str)
+ )
+
+ def test_encode_object(self):
+ class _TestObject:
+ def __init__(self, a, b, _c, d) -> None:
+ self.a = a
+ self.b = b
+ self._c = _c
+ self.d = d
+
+ def e(self):
+ return 5
+
+ # JSON keys should be all non-callable non-underscore attributes, see GH-42768
+ test_object = _TestObject(a=1, b=2, _c=3, d=4)
+ assert ujson.ujson_loads(ujson.ujson_dumps(test_object)) == {
+ "a": 1,
+ "b": 2,
+ "d": 4,
+ }
+
+ def test_ujson__name__(self):
+ # GH 52898
+ assert ujson.__name__ == "pandas._libs.json"
+
+
+class TestNumpyJSONTests:
+ @pytest.mark.parametrize("bool_input", [True, False])
+ def test_bool(self, bool_input):
+ b = bool(bool_input)
+ assert ujson.ujson_loads(ujson.ujson_dumps(b)) == b
+
+ def test_bool_array(self):
+ bool_array = np.array(
+ [True, False, True, True, False, True, False, False], dtype=bool
+ )
+ output = np.array(ujson.ujson_loads(ujson.ujson_dumps(bool_array)), dtype=bool)
+ tm.assert_numpy_array_equal(bool_array, output)
+
+ def test_int(self, any_int_numpy_dtype):
+ klass = np.dtype(any_int_numpy_dtype).type
+ num = klass(1)
+
+ assert klass(ujson.ujson_loads(ujson.ujson_dumps(num))) == num
+
+ def test_int_array(self, any_int_numpy_dtype):
+ arr = np.arange(100, dtype=int)
+ arr_input = arr.astype(any_int_numpy_dtype)
+
+ arr_output = np.array(
+ ujson.ujson_loads(ujson.ujson_dumps(arr_input)), dtype=any_int_numpy_dtype
+ )
+ tm.assert_numpy_array_equal(arr_input, arr_output)
+
+ def test_int_max(self, any_int_numpy_dtype):
+ if any_int_numpy_dtype in ("int64", "uint64") and not IS64:
+ pytest.skip("Cannot test 64-bit integer on 32-bit platform")
+
+ klass = np.dtype(any_int_numpy_dtype).type
+
+ # uint64 max will always overflow,
+ # as it's encoded to signed.
+ if any_int_numpy_dtype == "uint64":
+ num = np.iinfo("int64").max
+ else:
+ num = np.iinfo(any_int_numpy_dtype).max
+
+ assert klass(ujson.ujson_loads(ujson.ujson_dumps(num))) == num
+
+ def test_float(self, float_numpy_dtype):
+ klass = np.dtype(float_numpy_dtype).type
+ num = klass(256.2013)
+
+ assert klass(ujson.ujson_loads(ujson.ujson_dumps(num))) == num
+
+ def test_float_array(self, float_numpy_dtype):
+ arr = np.arange(12.5, 185.72, 1.7322, dtype=float)
+ float_input = arr.astype(float_numpy_dtype)
+
+ float_output = np.array(
+ ujson.ujson_loads(ujson.ujson_dumps(float_input, double_precision=15)),
+ dtype=float_numpy_dtype,
+ )
+ tm.assert_almost_equal(float_input, float_output)
+
+ def test_float_max(self, float_numpy_dtype):
+ klass = np.dtype(float_numpy_dtype).type
+ num = klass(np.finfo(float_numpy_dtype).max / 10)
+
+ tm.assert_almost_equal(
+ klass(ujson.ujson_loads(ujson.ujson_dumps(num, double_precision=15))), num
+ )
+
+ def test_array_basic(self):
+ arr = np.arange(96)
+ arr = arr.reshape((2, 2, 2, 2, 3, 2))
+
+ tm.assert_numpy_array_equal(
+ np.array(ujson.ujson_loads(ujson.ujson_dumps(arr))), arr
+ )
+
+ @pytest.mark.parametrize("shape", [(10, 10), (5, 5, 4), (100, 1)])
+ def test_array_reshaped(self, shape):
+ arr = np.arange(100)
+ arr = arr.reshape(shape)
+
+ tm.assert_numpy_array_equal(
+ np.array(ujson.ujson_loads(ujson.ujson_dumps(arr))), arr
+ )
+
+ def test_array_list(self):
+ arr_list = [
+ "a",
+ [],
+ {},
+ {},
+ [],
+ 42,
+ 97.8,
+ ["a", "b"],
+ {"key": "val"},
+ ]
+ arr = np.array(arr_list, dtype=object)
+ result = np.array(ujson.ujson_loads(ujson.ujson_dumps(arr)), dtype=object)
+ tm.assert_numpy_array_equal(result, arr)
+
+ def test_array_float(self):
+ dtype = np.float32
+
+ arr = np.arange(100.202, 200.202, 1, dtype=dtype)
+ arr = arr.reshape((5, 5, 4))
+
+ arr_out = np.array(ujson.ujson_loads(ujson.ujson_dumps(arr)), dtype=dtype)
+ tm.assert_almost_equal(arr, arr_out)
+
+ def test_0d_array(self):
+ # gh-18878
+ msg = re.escape(
+ "array(1) (numpy-scalar) is not JSON serializable at the moment"
+ )
+ with pytest.raises(TypeError, match=msg):
+ ujson.ujson_dumps(np.array(1))
+
+ def test_array_long_double(self):
+ msg = re.compile(
+ "1234.5.* \\(numpy-scalar\\) is not JSON serializable at the moment"
+ )
+ with pytest.raises(TypeError, match=msg):
+ ujson.ujson_dumps(np.longdouble(1234.5))
+
+
+class TestPandasJSONTests:
+ def test_dataframe(self, orient):
+ dtype = np.int64
+
+ df = DataFrame(
+ [[1, 2, 3], [4, 5, 6]],
+ index=["a", "b"],
+ columns=["x", "y", "z"],
+ dtype=dtype,
+ )
+ encode_kwargs = {} if orient is None else {"orient": orient}
+ assert (df.dtypes == dtype).all()
+
+ output = ujson.ujson_loads(ujson.ujson_dumps(df, **encode_kwargs))
+ assert (df.dtypes == dtype).all()
+
+ # Ensure proper DataFrame initialization.
+ if orient == "split":
+ dec = _clean_dict(output)
+ output = DataFrame(**dec)
+ else:
+ output = DataFrame(output)
+
+ # Corrections to enable DataFrame comparison.
+ if orient == "values":
+ df.columns = [0, 1, 2]
+ df.index = [0, 1]
+ elif orient == "records":
+ df.index = [0, 1]
+ elif orient == "index":
+ df = df.transpose()
+
+ assert (df.dtypes == dtype).all()
+ tm.assert_frame_equal(output, df)
+
+ def test_dataframe_nested(self, orient):
+ df = DataFrame(
+ [[1, 2, 3], [4, 5, 6]], index=["a", "b"], columns=["x", "y", "z"]
+ )
+
+ nested = {"df1": df, "df2": df.copy()}
+ kwargs = {} if orient is None else {"orient": orient}
+
+ exp = {
+ "df1": ujson.ujson_loads(ujson.ujson_dumps(df, **kwargs)),
+ "df2": ujson.ujson_loads(ujson.ujson_dumps(df, **kwargs)),
+ }
+ assert ujson.ujson_loads(ujson.ujson_dumps(nested, **kwargs)) == exp
+
+ def test_series(self, orient):
+ dtype = np.int64
+ s = Series(
+ [10, 20, 30, 40, 50, 60],
+ name="series",
+ index=[6, 7, 8, 9, 10, 15],
+ dtype=dtype,
+ ).sort_values()
+ assert s.dtype == dtype
+
+ encode_kwargs = {} if orient is None else {"orient": orient}
+
+ output = ujson.ujson_loads(ujson.ujson_dumps(s, **encode_kwargs))
+ assert s.dtype == dtype
+
+ if orient == "split":
+ dec = _clean_dict(output)
+ output = Series(**dec)
+ else:
+ output = Series(output)
+
+ if orient in (None, "index"):
+ s.name = None
+ output = output.sort_values()
+ s.index = ["6", "7", "8", "9", "10", "15"]
+ elif orient in ("records", "values"):
+ s.name = None
+ s.index = [0, 1, 2, 3, 4, 5]
+
+ assert s.dtype == dtype
+ tm.assert_series_equal(output, s)
+
+ def test_series_nested(self, orient):
+ s = Series(
+ [10, 20, 30, 40, 50, 60], name="series", index=[6, 7, 8, 9, 10, 15]
+ ).sort_values()
+ nested = {"s1": s, "s2": s.copy()}
+ kwargs = {} if orient is None else {"orient": orient}
+
+ exp = {
+ "s1": ujson.ujson_loads(ujson.ujson_dumps(s, **kwargs)),
+ "s2": ujson.ujson_loads(ujson.ujson_dumps(s, **kwargs)),
+ }
+ assert ujson.ujson_loads(ujson.ujson_dumps(nested, **kwargs)) == exp
+
+ def test_index(self):
+ i = Index([23, 45, 18, 98, 43, 11], name="index")
+
+ # Column indexed.
+ output = Index(ujson.ujson_loads(ujson.ujson_dumps(i)), name="index")
+ tm.assert_index_equal(i, output)
+
+ dec = _clean_dict(ujson.ujson_loads(ujson.ujson_dumps(i, orient="split")))
+ output = Index(**dec)
+
+ tm.assert_index_equal(i, output)
+ assert i.name == output.name
+
+ tm.assert_index_equal(i, output)
+ assert i.name == output.name
+
+ output = Index(
+ ujson.ujson_loads(ujson.ujson_dumps(i, orient="values")), name="index"
+ )
+ tm.assert_index_equal(i, output)
+
+ output = Index(
+ ujson.ujson_loads(ujson.ujson_dumps(i, orient="records")), name="index"
+ )
+ tm.assert_index_equal(i, output)
+
+ output = Index(
+ ujson.ujson_loads(ujson.ujson_dumps(i, orient="index")), name="index"
+ )
+ tm.assert_index_equal(i, output)
+
+ def test_datetime_index(self):
+ date_unit = "ns"
+
+ # freq doesn't round-trip
+ rng = DatetimeIndex(
+ list(date_range("1/1/2000", periods=20, unit="ns")), freq=None
+ )
+ encoded = ujson.ujson_dumps(rng, date_unit=date_unit)
+
+ decoded = DatetimeIndex(np.array(ujson.ujson_loads(encoded)))
+ tm.assert_index_equal(rng, decoded)
+
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+ decoded = Series(ujson.ujson_loads(ujson.ujson_dumps(ts, date_unit=date_unit)))
+
+ idx_values = decoded.index.values.astype(np.int64)
+ decoded.index = DatetimeIndex(idx_values)
+ tm.assert_series_equal(ts, decoded)
+
+ @pytest.mark.parametrize(
+ "invalid_arr",
+ [
+ "[31337,]", # Trailing comma.
+ "[,31337]", # Leading comma.
+ "[]]", # Unmatched bracket.
+ "[,]", # Only comma.
+ ],
+ )
+ def test_decode_invalid_array(self, invalid_arr):
+ msg = (
+ "Expected object or value|Trailing data|"
+ "Unexpected character found when decoding array value"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ujson.ujson_loads(invalid_arr)
+
+ @pytest.mark.parametrize("arr", [[], [31337]])
+ def test_decode_array(self, arr):
+ assert arr == ujson.ujson_loads(str(arr))
+
+ @pytest.mark.parametrize("extreme_num", [9223372036854775807, -9223372036854775808])
+ def test_decode_extreme_numbers(self, extreme_num):
+ assert extreme_num == ujson.ujson_loads(str(extreme_num))
+
+ @pytest.mark.parametrize("too_extreme_num", [f"{2**64}", f"{-(2**63) - 1}"])
+ def test_decode_too_extreme_numbers(self, too_extreme_num):
+ with pytest.raises(
+ ValueError,
+ match="Value is too big|Value is too small",
+ ):
+ ujson.ujson_loads(too_extreme_num)
+
+ def test_decode_with_trailing_whitespaces(self):
+ assert {} == ujson.ujson_loads("{}\n\t ")
+
+ def test_decode_with_trailing_non_whitespaces(self):
+ with pytest.raises(ValueError, match="Trailing data"):
+ ujson.ujson_loads("{}\n\t a")
+
+ @pytest.mark.parametrize("value", [f"{2**64}", f"{-(2**63) - 1}"])
+ def test_decode_array_with_big_int(self, value):
+ with pytest.raises(
+ ValueError,
+ match="Value is too big|Value is too small",
+ ):
+ ujson.ujson_loads(value)
+
+ @pytest.mark.parametrize(
+ "float_number",
+ [
+ 1.1234567893,
+ 1.234567893,
+ 1.34567893,
+ 1.4567893,
+ 1.567893,
+ 1.67893,
+ 1.7893,
+ 1.893,
+ 1.3,
+ ],
+ )
+ @pytest.mark.parametrize("sign", [-1, 1])
+ def test_decode_floating_point(self, sign, float_number):
+ float_number *= sign
+ tm.assert_almost_equal(
+ float_number, ujson.ujson_loads(str(float_number)), rtol=1e-15
+ )
+
+ def test_encode_big_set(self):
+ s = set(range(100000))
+ # Make sure no Exception is raised.
+ ujson.ujson_dumps(s)
+
+ def test_encode_empty_set(self):
+ assert "[]" == ujson.ujson_dumps(set())
+
+ def test_encode_set(self):
+ s = {1, 2, 3, 4, 5, 6, 7, 8, 9}
+ enc = ujson.ujson_dumps(s)
+ dec = ujson.ujson_loads(enc)
+
+ for v in dec:
+ assert v in s
+
+ @pytest.mark.parametrize(
+ "td",
+ [
+ Timedelta(days=366),
+ Timedelta(days=-1),
+ Timedelta(hours=13, minutes=5, seconds=5),
+ Timedelta(hours=13, minutes=20, seconds=30),
+ Timedelta(days=-1, nanoseconds=5),
+ Timedelta(nanoseconds=1),
+ Timedelta(microseconds=1, nanoseconds=1),
+ Timedelta(milliseconds=1, microseconds=1, nanoseconds=1),
+ Timedelta(milliseconds=999, microseconds=999, nanoseconds=999),
+ ],
+ )
+ def test_encode_timedelta_iso(self, td):
+ # GH 28256
+ result = ujson.ujson_dumps(td, iso_dates=True)
+ expected = f'"{td.isoformat()}"'
+
+ assert result == expected
+
+ def test_encode_periodindex(self):
+ # GH 46683
+ p = PeriodIndex(["2022-04-06", "2022-04-07"], freq="D")
+ df = DataFrame(index=p)
+ assert df.to_json() == "{}"
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_chunksize.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_chunksize.py
new file mode 100644
index 0000000000000000000000000000000000000000..98dbb7d45e9112c29263c6477e0d19b24be38959
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_chunksize.py
@@ -0,0 +1,364 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas._libs import parsers as libparsers
+from pandas.errors import DtypeWarning
+
+from pandas import (
+ DataFrame,
+ concat,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.mark.parametrize("index_col", [0, "index"])
+def test_read_chunksize_with_index(all_parsers, index_col):
+ parser = all_parsers
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+
+ expected = DataFrame(
+ [
+ ["foo", 2, 3, 4, 5],
+ ["bar", 7, 8, 9, 10],
+ ["baz", 12, 13, 14, 15],
+ ["qux", 12, 13, 14, 15],
+ ["foo2", 12, 13, 14, 15],
+ ["bar2", 12, 13, 14, 15],
+ ],
+ columns=["index", "A", "B", "C", "D"],
+ )
+ expected = expected.set_index("index")
+
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ with parser.read_csv(StringIO(data), index_col=0, chunksize=2) as reader:
+ list(reader)
+ return
+
+ with parser.read_csv(StringIO(data), index_col=0, chunksize=2) as reader:
+ chunks = list(reader)
+ tm.assert_frame_equal(chunks[0], expected[:2])
+ tm.assert_frame_equal(chunks[1], expected[2:4])
+ tm.assert_frame_equal(chunks[2], expected[4:])
+
+
+@pytest.mark.parametrize("chunksize", [1.3, "foo", 0])
+def test_read_chunksize_bad(all_parsers, chunksize):
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+ parser = all_parsers
+ msg = r"'chunksize' must be an integer >=1"
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+
+ with pytest.raises(ValueError, match=msg):
+ with parser.read_csv(StringIO(data), chunksize=chunksize) as _:
+ pass
+
+
+@pytest.mark.parametrize("chunksize", [2, 8])
+def test_read_chunksize_and_nrows(all_parsers, chunksize):
+ # see gh-15755
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+ parser = all_parsers
+ kwargs = {"index_col": 0, "nrows": 5}
+
+ if parser.engine == "pyarrow":
+ msg = "The 'nrows' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+ return
+
+ expected = parser.read_csv(StringIO(data), **kwargs)
+ with parser.read_csv(StringIO(data), chunksize=chunksize, **kwargs) as reader:
+ tm.assert_frame_equal(concat(reader), expected)
+
+
+def test_read_chunksize_and_nrows_changing_size(all_parsers):
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+ parser = all_parsers
+ kwargs = {"index_col": 0, "nrows": 5}
+
+ if parser.engine == "pyarrow":
+ msg = "The 'nrows' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+ return
+
+ expected = parser.read_csv(StringIO(data), **kwargs)
+ with parser.read_csv(StringIO(data), chunksize=8, **kwargs) as reader:
+ tm.assert_frame_equal(reader.get_chunk(size=2), expected.iloc[:2])
+ tm.assert_frame_equal(reader.get_chunk(size=4), expected.iloc[2:5])
+
+ with pytest.raises(StopIteration, match="^$"):
+ reader.get_chunk(size=3)
+
+
+def test_get_chunk_passed_chunksize(all_parsers):
+ parser = all_parsers
+ data = """A,B,C
+1,2,3
+4,5,6
+7,8,9
+1,2,3"""
+
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ with parser.read_csv(StringIO(data), chunksize=2) as reader:
+ reader.get_chunk()
+ return
+
+ with parser.read_csv(StringIO(data), chunksize=2) as reader:
+ result = reader.get_chunk()
+
+ expected = DataFrame([[1, 2, 3], [4, 5, 6]], columns=["A", "B", "C"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("kwargs", [{}, {"index_col": 0}])
+def test_read_chunksize_compat(all_parsers, kwargs):
+ # see gh-12185
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), **kwargs)
+
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ with parser.read_csv(StringIO(data), chunksize=2, **kwargs) as reader:
+ concat(reader)
+ return
+
+ with parser.read_csv(StringIO(data), chunksize=2, **kwargs) as reader:
+ via_reader = concat(reader)
+ tm.assert_frame_equal(via_reader, result)
+
+
+def test_read_chunksize_jagged_names(all_parsers):
+ # see gh-23509
+ parser = all_parsers
+ data = "\n".join(["0"] * 7 + [",".join(["0"] * 10)])
+
+ expected = DataFrame([[0] + [np.nan] * 9] * 7 + [[0] * 10])
+
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ with parser.read_csv(
+ StringIO(data), names=range(10), chunksize=4
+ ) as reader:
+ concat(reader)
+ return
+
+ with parser.read_csv(StringIO(data), names=range(10), chunksize=4) as reader:
+ result = concat(reader)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_chunk_begins_with_newline_whitespace(all_parsers):
+ # see gh-10022
+ parser = all_parsers
+ data = "\n hello\nworld\n"
+
+ result = parser.read_csv(StringIO(data), header=None)
+ expected = DataFrame([" hello", "world"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.slow
+def test_chunks_have_consistent_numerical_type(all_parsers, monkeypatch):
+ # mainly an issue with the C parser
+ heuristic = 2**3
+ parser = all_parsers
+ integers = [str(i) for i in range(heuristic - 1)]
+ data = "a\n" + "\n".join([*integers, "1.0", "2.0", *integers])
+
+ # Coercions should work without warnings.
+ with monkeypatch.context() as m:
+ m.setattr(libparsers, "DEFAULT_BUFFER_HEURISTIC", heuristic)
+ result = parser.read_csv(StringIO(data))
+
+ assert type(result.a[0]) is np.float64
+ assert result.a.dtype == float
+
+
+def test_warn_if_chunks_have_mismatched_type(
+ all_parsers, using_infer_string, monkeypatch
+):
+ warning_type = None
+ parser = all_parsers
+ heuristic = 2**3
+ size = 10
+
+ # see gh-3866: if chunks are different types and can't
+ # be coerced using numerical types, then issue warning.
+ if parser.engine == "c" and parser.low_memory:
+ warning_type = DtypeWarning
+ # Use a size to hit warning path dictated by DEFAULT_BUFFER_HEURISTIC
+ # monkeypatched below
+ size = heuristic - 1
+
+ integers = [str(i) for i in range(size)]
+ data = "a\n" + "\n".join([*integers, "a", "b", *integers])
+
+ buf = StringIO(data)
+
+ if parser.engine == "pyarrow":
+ df = parser.read_csv(
+ buf,
+ )
+ else:
+ with monkeypatch.context() as m:
+ m.setattr(libparsers, "DEFAULT_BUFFER_HEURISTIC", heuristic)
+ df = parser.read_csv_check_warnings(
+ warning_type,
+ r"Columns \(0: a\) have mixed types. "
+ "Specify dtype option on import or set low_memory=False.",
+ buf,
+ )
+ if parser.engine == "c" and parser.low_memory:
+ assert df.a.dtype == object
+ elif using_infer_string:
+ assert df.a.dtype == "str"
+ else:
+ assert df.a.dtype == object
+
+
+@pytest.mark.parametrize("iterator", [True, False])
+def test_empty_with_nrows_chunksize(all_parsers, iterator):
+ # see gh-9535
+ parser = all_parsers
+ expected = DataFrame(columns=["foo", "bar"])
+
+ nrows = 10
+ data = StringIO("foo,bar\n")
+
+ if parser.engine == "pyarrow":
+ msg = (
+ "The '(nrows|chunksize)' option is not supported with the 'pyarrow' engine"
+ )
+ with pytest.raises(ValueError, match=msg):
+ if iterator:
+ with parser.read_csv(data, chunksize=nrows) as reader:
+ next(iter(reader))
+ else:
+ parser.read_csv(data, nrows=nrows)
+ return
+
+ if iterator:
+ with parser.read_csv(data, chunksize=nrows) as reader:
+ result = next(iter(reader))
+ else:
+ result = parser.read_csv(data, nrows=nrows)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_chunksize_with_usecols_second_block_shorter(all_parsers):
+ # GH#21211
+ parser = all_parsers
+ data = """1,2,3,4
+5,6,7,8
+9,10,11
+"""
+
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ names=["a", "b"],
+ chunksize=2,
+ usecols=[0, 1],
+ header=None,
+ )
+ return
+
+ result_chunks = parser.read_csv(
+ StringIO(data),
+ names=["a", "b"],
+ chunksize=2,
+ usecols=[0, 1],
+ header=None,
+ )
+
+ expected_frames = [
+ DataFrame({"a": [1, 5], "b": [2, 6]}),
+ DataFrame({"a": [9], "b": [10]}, index=[2]),
+ ]
+
+ for i, result in enumerate(result_chunks):
+ tm.assert_frame_equal(result, expected_frames[i])
+
+
+def test_chunksize_second_block_shorter(all_parsers):
+ # GH#21211
+ parser = all_parsers
+ data = """a,b,c,d
+1,2,3,4
+5,6,7,8
+9,10,11
+"""
+
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), chunksize=2)
+ return
+
+ result_chunks = parser.read_csv(StringIO(data), chunksize=2)
+
+ expected_frames = [
+ DataFrame({"a": [1, 5], "b": [2, 6], "c": [3, 7], "d": [4, 8]}),
+ DataFrame({"a": [9], "b": [10], "c": [11], "d": [np.nan]}, index=[2]),
+ ]
+
+ for i, result in enumerate(result_chunks):
+ tm.assert_frame_equal(result, expected_frames[i])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_common_basic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_common_basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..981c254652f076a02595f4a9438c905e4e6badc9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_common_basic.py
@@ -0,0 +1,852 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+from datetime import datetime
+from inspect import signature
+from io import StringIO
+import os
+import sys
+
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+from pandas.compat import HAS_PYARROW
+from pandas.errors import (
+ EmptyDataError,
+ ParserError,
+ ParserWarning,
+)
+
+from pandas import (
+ DataFrame,
+ Index,
+ compat,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+def test_read_csv_local(all_parsers, csv1):
+ prefix = "file:///" if compat.is_platform_windows() else "file://"
+ parser = all_parsers
+
+ fname = prefix + str(os.path.abspath(csv1))
+ result = parser.read_csv(fname, index_col=0, parse_dates=True)
+
+ expected = DataFrame(
+ [
+ [0.980269, 3.685731, -0.364216805298, -1.159738],
+ [1.047916, -0.041232, -0.16181208307, 0.212549],
+ [0.498581, 0.731168, -0.537677223318, 1.346270],
+ [1.120202, 1.567621, 0.00364077397681, 0.675253],
+ [-0.487094, 0.571455, -1.6116394093, 0.103469],
+ [0.836649, 0.246462, 0.588542635376, 1.062782],
+ [-0.157161, 1.340307, 1.1957779562, -1.097007],
+ ],
+ columns=["A", "B", "C", "D"],
+ index=Index(
+ [
+ datetime(2000, 1, 3),
+ datetime(2000, 1, 4),
+ datetime(2000, 1, 5),
+ datetime(2000, 1, 6),
+ datetime(2000, 1, 7),
+ datetime(2000, 1, 10),
+ datetime(2000, 1, 11),
+ ],
+ dtype="M8[us]",
+ name="index",
+ ),
+ )
+ if parser.engine == "pyarrow":
+ expected.index = expected.index.astype("M8[s]")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "number_csv, expected_number",
+ [
+ ("2,334", 2334),
+ ("-2,334", -2334),
+ ("-2,334,", -2334),
+ # Multiple consecutive thousand separators are allowed in C engine,
+ # but it's not necessarily intended behavior and may change in the future.
+ ("2,,,,,,,,,,,,,,,5", 25),
+ ("2,,3,4,,,,,,,,,,,,5", 2345),
+ ],
+)
+def test_1000_sep(all_parsers, number_csv, expected_number, request):
+ parser = all_parsers
+ data = f"""A|B|C
+1|{number_csv}|5
+10|13|10.
+"""
+ expected = DataFrame({"A": [1, 10], "B": [expected_number, 13], "C": [5, 10.0]})
+
+ if parser.engine == "pyarrow":
+ msg = "The 'thousands' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), sep="|", thousands=",")
+ return
+ elif parser.engine == "python" and ",," in number_csv:
+ mark = pytest.mark.xfail(
+ reason="Python engine doesn't allow consecutive thousands separators"
+ )
+ request.applymarker(mark)
+
+ result = parser.read_csv(StringIO(data), sep="|", thousands=",")
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: Found non-unique column index
+def test_unnamed_columns(all_parsers):
+ data = """A,B,C,,
+1,2,3,4,5
+6,7,8,9,10
+11,12,13,14,15
+"""
+ parser = all_parsers
+ expected = DataFrame(
+ [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]],
+ dtype=np.int64,
+ columns=["A", "B", "C", "Unnamed: 3", "Unnamed: 4"],
+ )
+ result = parser.read_csv(StringIO(data))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_csv_mixed_type(all_parsers):
+ data = """A,B,C
+a,1,2
+b,3,4
+c,4,5
+"""
+ parser = all_parsers
+ expected = DataFrame({"A": ["a", "b", "c"], "B": [1, 3, 4], "C": [2, 4, 5]})
+ result = parser.read_csv(StringIO(data))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_csv_low_memory_no_rows_with_index(all_parsers):
+ # see gh-21141
+ parser = all_parsers
+
+ if not parser.low_memory:
+ pytest.skip("This is a low-memory specific test")
+
+ data = """A,B,C
+1,1,1,2
+2,2,3,4
+3,3,4,5
+"""
+
+ if parser.engine == "pyarrow":
+ msg = "The 'nrows' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), low_memory=True, index_col=0, nrows=0)
+ return
+
+ result = parser.read_csv(StringIO(data), low_memory=True, index_col=0, nrows=0)
+ expected = DataFrame(columns=["A", "B", "C"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_csv_dataframe(all_parsers, csv1):
+ parser = all_parsers
+ result = parser.read_csv(csv1, index_col=0, parse_dates=True)
+ expected = DataFrame(
+ [
+ [0.980269, 3.685731, -0.364216805298, -1.159738],
+ [1.047916, -0.041232, -0.16181208307, 0.212549],
+ [0.498581, 0.731168, -0.537677223318, 1.346270],
+ [1.120202, 1.567621, 0.00364077397681, 0.675253],
+ [-0.487094, 0.571455, -1.6116394093, 0.103469],
+ [0.836649, 0.246462, 0.588542635376, 1.062782],
+ [-0.157161, 1.340307, 1.1957779562, -1.097007],
+ ],
+ columns=["A", "B", "C", "D"],
+ index=Index(
+ [
+ datetime(2000, 1, 3),
+ datetime(2000, 1, 4),
+ datetime(2000, 1, 5),
+ datetime(2000, 1, 6),
+ datetime(2000, 1, 7),
+ datetime(2000, 1, 10),
+ datetime(2000, 1, 11),
+ ],
+ dtype="M8[us]",
+ name="index",
+ ),
+ )
+ if parser.engine == "pyarrow":
+ expected.index = expected.index.astype("M8[s]")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("nrows", [3, 3.0])
+def test_read_nrows(all_parsers, nrows):
+ # see gh-10476
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+ expected = DataFrame(
+ [["foo", 2, 3, 4, 5], ["bar", 7, 8, 9, 10], ["baz", 12, 13, 14, 15]],
+ columns=["index", "A", "B", "C", "D"],
+ )
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ msg = "The 'nrows' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), nrows=nrows)
+ return
+
+ result = parser.read_csv(StringIO(data), nrows=nrows)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("nrows", [1.2, "foo", -1])
+def test_read_nrows_bad(all_parsers, nrows):
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+ msg = r"'nrows' must be an integer >=0"
+ parser = all_parsers
+ if parser.engine == "pyarrow":
+ msg = "The 'nrows' option is not supported with the 'pyarrow' engine"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), nrows=nrows)
+
+
+def test_nrows_skipfooter_errors(all_parsers):
+ msg = "'skipfooter' not supported with 'nrows'"
+ data = "a\n1\n2\n3\n4\n5\n6"
+ parser = all_parsers
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), skipfooter=1, nrows=5)
+
+
+@skip_pyarrow
+def test_missing_trailing_delimiters(all_parsers):
+ parser = all_parsers
+ data = """A,B,C,D
+1,2,3,4
+1,3,3,
+1,4,5"""
+
+ result = parser.read_csv(StringIO(data))
+ expected = DataFrame(
+ [[1, 2, 3, 4], [1, 3, 3, np.nan], [1, 4, 5, np.nan]],
+ columns=["A", "B", "C", "D"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_skip_initial_space(all_parsers):
+ data = (
+ '"09-Apr-2012", "01:10:18.300", 2456026.548822908, 12849, '
+ "1.00361, 1.12551, 330.65659, 0355626618.16711, 73.48821, "
+ "314.11625, 1917.09447, 179.71425, 80.000, 240.000, -350, "
+ "70.06056, 344.98370, 1, 1, -0.689265, -0.692787, "
+ "0.212036, 14.7674, 41.605, -9999.0, -9999.0, "
+ "-9999.0, -9999.0, -9999.0, -9999.0, 000, 012, 128"
+ )
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ msg = "The 'skipinitialspace' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ names=list(range(33)),
+ header=None,
+ na_values=["-9999.0"],
+ skipinitialspace=True,
+ )
+ return
+
+ result = parser.read_csv(
+ StringIO(data),
+ names=list(range(33)),
+ header=None,
+ na_values=["-9999.0"],
+ skipinitialspace=True,
+ )
+ expected = DataFrame(
+ [
+ [
+ "09-Apr-2012",
+ "01:10:18.300",
+ 2456026.548822908,
+ 12849,
+ 1.00361,
+ 1.12551,
+ 330.65659,
+ 355626618.16711,
+ 73.48821,
+ 314.11625,
+ 1917.09447,
+ 179.71425,
+ 80.0,
+ 240.0,
+ -350,
+ 70.06056,
+ 344.9837,
+ 1,
+ 1,
+ -0.689265,
+ -0.692787,
+ 0.212036,
+ 14.7674,
+ 41.605,
+ np.nan,
+ np.nan,
+ np.nan,
+ np.nan,
+ np.nan,
+ np.nan,
+ 0,
+ 12,
+ 128,
+ ]
+ ]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+def test_trailing_delimiters(all_parsers):
+ # see gh-2442
+ data = """A,B,C
+1,2,3,
+4,5,6,
+7,8,9,"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), index_col=False)
+
+ expected = DataFrame({"A": [1, 4, 7], "B": [2, 5, 8], "C": [3, 6, 9]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_escapechar(all_parsers):
+ # https://stackoverflow.com/questions/13824840/feature-request-for-
+ # pandas-read-csv
+ data = '''SEARCH_TERM,ACTUAL_URL
+"bra tv board","http://www.ikea.com/se/sv/catalog/categories/departments/living_room/10475/?se%7cps%7cnonbranded%7cvardagsrum%7cgoogle%7ctv_bord"
+"tv p\xc3\xa5 hjul","http://www.ikea.com/se/sv/catalog/categories/departments/living_room/10475/?se%7cps%7cnonbranded%7cvardagsrum%7cgoogle%7ctv_bord"
+"SLAGBORD, \\"Bergslagen\\", IKEA:s 1700-tals series","http://www.ikea.com/se/sv/catalog/categories/departments/living_room/10475/?se%7cps%7cnonbranded%7cvardagsrum%7cgoogle%7ctv_bord"'''
+
+ parser = all_parsers
+ result = parser.read_csv(
+ StringIO(data), escapechar="\\", quotechar='"', encoding="utf-8"
+ )
+
+ assert result["SEARCH_TERM"][2] == 'SLAGBORD, "Bergslagen", IKEA:s 1700-tals series'
+
+ tm.assert_index_equal(result.columns, Index(["SEARCH_TERM", "ACTUAL_URL"]))
+
+
+def test_ignore_leading_whitespace(all_parsers):
+ # see gh-3374, gh-6607
+ parser = all_parsers
+ data = " a b c\n 1 2 3\n 4 5 6\n 7 8 9"
+
+ if parser.engine == "pyarrow":
+ msg = "the 'pyarrow' engine does not support regex separators"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), sep=r"\s+")
+ return
+ result = parser.read_csv(StringIO(data), sep=r"\s+")
+
+ expected = DataFrame({"a": [1, 4, 7], "b": [2, 5, 8], "c": [3, 6, 9]})
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+@pytest.mark.parametrize("usecols", [None, [0, 1], ["a", "b"]])
+def test_uneven_lines_with_usecols(all_parsers, usecols):
+ # see gh-12203
+ parser = all_parsers
+ data = r"""a,b,c
+0,1,2
+3,4,5,6,7
+8,9,10"""
+
+ if usecols is None:
+ # Make sure that an error is still raised
+ # when the "usecols" parameter is not provided.
+ msg = r"Expected \d+ fields in line \d+, saw \d+"
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data))
+ else:
+ expected = DataFrame({"a": [0, 3, 8], "b": [1, 4, 9]})
+
+ result = parser.read_csv(StringIO(data), usecols=usecols)
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+@pytest.mark.parametrize(
+ "data,kwargs,expected",
+ [
+ # First, check to see that the response of parser when faced with no
+ # provided columns raises the correct error, with or without usecols.
+ ("", {}, None),
+ ("", {"usecols": ["X"]}, None),
+ (
+ ",,",
+ {"names": ["Dummy", "X", "Dummy_2"], "usecols": ["X"]},
+ DataFrame(columns=["X"], index=[0], dtype=np.float64),
+ ),
+ (
+ "",
+ {"names": ["Dummy", "X", "Dummy_2"], "usecols": ["X"]},
+ DataFrame(columns=["X"]),
+ ),
+ ],
+)
+def test_read_empty_with_usecols(all_parsers, data, kwargs, expected):
+ # see gh-12493
+ parser = all_parsers
+
+ if expected is None:
+ msg = "No columns to parse from file"
+ with pytest.raises(EmptyDataError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+ else:
+ result = parser.read_csv(StringIO(data), **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "kwargs,expected_data",
+ [
+ # gh-8661, gh-8679: this should ignore six lines, including
+ # lines with trailing whitespace and blank lines.
+ (
+ {
+ "header": None,
+ "sep": r"\s+",
+ "skiprows": [0, 1, 2, 3, 5, 6],
+ "skip_blank_lines": True,
+ },
+ [[1.0, 2.0, 4.0], [5.1, np.nan, 10.0]],
+ ),
+ # gh-8983: test skipping set of rows after a row with trailing spaces.
+ (
+ {
+ "sep": r"\s+",
+ "skiprows": [1, 2, 3, 5, 6],
+ "skip_blank_lines": True,
+ },
+ {"A": [1.0, 5.1], "B": [2.0, np.nan], "C": [4.0, 10]},
+ ),
+ ],
+)
+def test_trailing_spaces(all_parsers, kwargs, expected_data):
+ data = "A B C \nrandom line with trailing spaces \nskip\n1,2,3\n1,2.,4.\nrandom line with trailing tabs\t\t\t\n \n5.1,NaN,10.0\n" # noqa: E501
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ with pytest.raises(ValueError, match="the 'pyarrow' engine does not support"):
+ parser.read_csv(StringIO(data.replace(",", " ")), **kwargs)
+ return
+ expected = DataFrame(expected_data)
+ result = parser.read_csv(StringIO(data.replace(",", " ")), **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_filepath_or_buffer(all_parsers):
+ # see gh-43366
+ parser = all_parsers
+
+ with pytest.raises(TypeError, match="Expected file path name or file-like"):
+ parser.read_csv(filepath_or_buffer=b"input")
+
+
+def test_single_char_leading_whitespace(all_parsers):
+ # see gh-9710
+ parser = all_parsers
+ data = """\
+MyColumn
+a
+b
+a
+b\n"""
+
+ if parser.engine == "pyarrow":
+ msg = "The 'skipinitialspace' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ skipinitialspace=True,
+ )
+ return
+ expected = DataFrame({"MyColumn": list("abab")})
+ result = parser.read_csv(StringIO(data), skipinitialspace=True, sep=r"\s+")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "sep,skip_blank_lines,exp_data",
+ [
+ (",", True, [[1.0, 2.0, 4.0], [5.0, np.nan, 10.0], [-70.0, 0.4, 1.0]]),
+ (r"\s+", True, [[1.0, 2.0, 4.0], [5.0, np.nan, 10.0], [-70.0, 0.4, 1.0]]),
+ (
+ ",",
+ False,
+ [
+ [1.0, 2.0, 4.0],
+ [np.nan, np.nan, np.nan],
+ [np.nan, np.nan, np.nan],
+ [5.0, np.nan, 10.0],
+ [np.nan, np.nan, np.nan],
+ [-70.0, 0.4, 1.0],
+ ],
+ ),
+ ],
+)
+def test_empty_lines(all_parsers, sep, skip_blank_lines, exp_data, request):
+ parser = all_parsers
+ data = """\
+A,B,C
+1,2.,4.
+
+
+5.,NaN,10.0
+
+-70,.4,1
+"""
+
+ if sep == r"\s+":
+ data = data.replace(",", " ")
+
+ if parser.engine == "pyarrow":
+ msg = "the 'pyarrow' engine does not support regex separators"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data), sep=sep, skip_blank_lines=skip_blank_lines
+ )
+ return
+
+ result = parser.read_csv(StringIO(data), sep=sep, skip_blank_lines=skip_blank_lines)
+ expected = DataFrame(exp_data, columns=["A", "B", "C"])
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+def test_whitespace_lines(all_parsers):
+ parser = all_parsers
+ data = """
+
+\t \t\t
+\t
+A,B,C
+\t 1,2.,4.
+5.,NaN,10.0
+"""
+ expected = DataFrame([[1, 2.0, 4.0], [5.0, np.nan, 10.0]], columns=["A", "B", "C"])
+ result = parser.read_csv(StringIO(data))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data,expected",
+ [
+ (
+ """ A B C D
+a 1 2 3 4
+b 1 2 3 4
+c 1 2 3 4
+""",
+ DataFrame(
+ [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]],
+ columns=["A", "B", "C", "D"],
+ index=["a", "b", "c"],
+ ),
+ ),
+ (
+ " a b c\n1 2 3 \n4 5 6\n 7 8 9",
+ DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=["a", "b", "c"]),
+ ),
+ ],
+)
+def test_whitespace_regex_separator(all_parsers, data, expected):
+ # see gh-6607
+ parser = all_parsers
+ if parser.engine == "pyarrow":
+ msg = "the 'pyarrow' engine does not support regex separators"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), sep=r"\s+")
+ return
+
+ result = parser.read_csv(StringIO(data), sep=r"\s+")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_sub_character(all_parsers, csv_dir_path):
+ # see gh-16893
+ filename = os.path.join(csv_dir_path, "sub_char.csv")
+ expected = DataFrame([[1, 2, 3]], columns=["a", "\x1ab", "c"])
+
+ parser = all_parsers
+ result = parser.read_csv(filename)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("filename", ["sé-es-vé.csv", "ru-sй.csv", "中文文件名.csv"])
+def test_filename_with_special_chars(all_parsers, filename, tmp_path):
+ # see gh-15086.
+ parser = all_parsers
+ df = DataFrame({"a": [1, 2, 3]})
+
+ path = tmp_path / filename
+ df.to_csv(path, index=False)
+
+ result = parser.read_csv(path)
+ tm.assert_frame_equal(result, df)
+
+
+def test_read_table_same_signature_as_read_csv(all_parsers):
+ # GH-34976
+ parser = all_parsers
+
+ table_sign = signature(parser.read_table)
+ csv_sign = signature(parser.read_csv)
+
+ assert table_sign.parameters.keys() == csv_sign.parameters.keys()
+ assert table_sign.return_annotation == csv_sign.return_annotation
+
+ for key, csv_param in csv_sign.parameters.items():
+ table_param = table_sign.parameters[key]
+ if key == "sep":
+ assert csv_param.default == ","
+ assert table_param.default == "\t"
+ assert table_param.annotation == csv_param.annotation
+ assert table_param.kind == csv_param.kind
+ continue
+
+ assert table_param == csv_param
+
+
+def test_read_table_equivalency_to_read_csv(all_parsers):
+ # see gh-21948
+ # As of 0.25.0, read_table is undeprecated
+ parser = all_parsers
+ data = "a\tb\n1\t2\n3\t4"
+ expected = parser.read_csv(StringIO(data), sep="\t")
+ result = parser.read_table(StringIO(data))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("read_func", ["read_csv", "read_table"])
+def test_read_csv_and_table_sys_setprofile(all_parsers, read_func):
+ # GH#41069
+ parser = all_parsers
+ data = "a b\n0 1"
+
+ sys.setprofile(lambda *a, **k: None)
+ result = getattr(parser, read_func)(StringIO(data))
+ sys.setprofile(None)
+
+ expected = DataFrame({"a b": ["0 1"]})
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+def test_first_row_bom(all_parsers):
+ # see gh-26545
+ parser = all_parsers
+ data = '''\ufeff"Head1"\t"Head2"\t"Head3"'''
+
+ result = parser.read_csv(StringIO(data), delimiter="\t")
+ expected = DataFrame(columns=["Head1", "Head2", "Head3"])
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+def test_first_row_bom_unquoted(all_parsers):
+ # see gh-36343
+ parser = all_parsers
+ data = """\ufeffHead1\tHead2\tHead3"""
+
+ result = parser.read_csv(StringIO(data), delimiter="\t")
+ expected = DataFrame(columns=["Head1", "Head2", "Head3"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("nrows", range(1, 6))
+def test_blank_lines_between_header_and_data_rows(all_parsers, nrows):
+ # GH 28071
+ ref = DataFrame(
+ [[np.nan, np.nan], [np.nan, np.nan], [1, 2], [np.nan, np.nan], [3, 4]],
+ columns=list("ab"),
+ )
+ csv = "\nheader\n\na,b\n\n\n1,2\n\n3,4"
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ msg = "The 'nrows' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(csv), header=3, nrows=nrows, skip_blank_lines=False
+ )
+ return
+
+ df = parser.read_csv(StringIO(csv), header=3, nrows=nrows, skip_blank_lines=False)
+ tm.assert_frame_equal(df, ref[:nrows])
+
+
+@skip_pyarrow
+def test_no_header_two_extra_columns(all_parsers):
+ # GH 26218
+ column_names = ["one", "two", "three"]
+ ref = DataFrame([["foo", "bar", "baz"]], columns=column_names)
+ stream = StringIO("foo,bar,baz,bam,blah")
+ parser = all_parsers
+ df = parser.read_csv_check_warnings(
+ ParserWarning,
+ "Length of header or names does not match length of data. "
+ "This leads to a loss of data with index_col=False.",
+ stream,
+ header=None,
+ names=column_names,
+ index_col=False,
+ )
+ tm.assert_frame_equal(df, ref)
+
+
+def test_read_csv_names_not_accepting_sets(all_parsers):
+ # GH 34946
+ data = """\
+ 1,2,3
+ 4,5,6\n"""
+ parser = all_parsers
+ with pytest.raises(ValueError, match="Names should be an ordered collection."):
+ parser.read_csv(StringIO(data), names=set("QAZ"))
+
+
+def test_read_csv_delimiter_and_sep_no_default(all_parsers):
+ # GH#39823
+ f = StringIO("a,b\n1,2")
+ parser = all_parsers
+ msg = "Specified a sep and a delimiter; you can only specify one."
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(f, sep=" ", delimiter=".")
+
+
+@pytest.mark.parametrize("kwargs", [{"delimiter": "\n"}, {"sep": "\n"}])
+def test_read_csv_line_break_as_separator(kwargs, all_parsers):
+ # GH#43528
+ parser = all_parsers
+ data = """a,b,c
+1,2,3
+ """
+ msg = (
+ r"Specified \\n as separator or delimiter. This forces the python engine "
+ r"which does not accept a line terminator. Hence it is not allowed to use "
+ r"the line terminator as separator."
+ )
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+
+
+@skip_pyarrow
+def test_dict_keys_as_names(all_parsers):
+ # GH: 36928
+ data = "1,2"
+
+ keys = {"a": int, "b": int}.keys()
+ parser = all_parsers
+
+ result = parser.read_csv(StringIO(data), names=keys)
+ expected = DataFrame({"a": [1], "b": [2]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.xfail(using_string_dtype() and HAS_PYARROW, reason="TODO(infer_string)")
+@xfail_pyarrow # UnicodeDecodeError: 'utf-8' codec can't decode byte 0xed in position 0
+def test_encoding_surrogatepass(all_parsers, tmp_path):
+ # GH39017
+ parser = all_parsers
+ content = b"\xed\xbd\xbf"
+ decoded = content.decode("utf-8", errors="surrogatepass")
+ expected = DataFrame({decoded: [decoded]}, index=[decoded * 2])
+ expected.index.name = decoded * 2
+
+ path = tmp_path / "test_encoding.csv"
+ path.write_bytes(
+ content * 2 + b"," + content + b"\n" + content * 2 + b"," + content
+ )
+ df = parser.read_csv(path, encoding_errors="surrogatepass", index_col=0)
+ tm.assert_frame_equal(df, expected)
+ with pytest.raises(UnicodeDecodeError, match="'utf-8' codec can't decode byte"):
+ parser.read_csv(path)
+
+
+def test_malformed_second_line(all_parsers):
+ # see GH14782
+ parser = all_parsers
+ data = "\na\nb\n"
+ result = parser.read_csv(StringIO(data), skip_blank_lines=False, header=1)
+ expected = DataFrame({"a": ["b"]})
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+def test_short_single_line(all_parsers):
+ # GH 47566
+ parser = all_parsers
+ columns = ["a", "b", "c"]
+ data = "1,2"
+ result = parser.read_csv(StringIO(data), header=None, names=columns)
+ expected = DataFrame({"a": [1], "b": [2], "c": [np.nan]})
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: Length mismatch: Expected axis has 2 elements
+def test_short_multi_line(all_parsers):
+ # GH 47566
+ parser = all_parsers
+ columns = ["a", "b", "c"]
+ data = "1,2\n1,2"
+ result = parser.read_csv(StringIO(data), header=None, names=columns)
+ expected = DataFrame({"a": [1, 1], "b": [2, 2], "c": [np.nan, np.nan]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_seek(all_parsers, tmp_path):
+ # GH48646
+ parser = all_parsers
+ prefix = "### DATA\n"
+ content = "nkey,value\ntables,rectangular\n"
+ path = tmp_path / "test_seek.csv"
+ path.write_text(prefix + content, encoding="utf-8")
+ with open(path, encoding="utf-8") as file:
+ file.readline()
+ actual = parser.read_csv(file)
+ expected = parser.read_csv(StringIO(content))
+ tm.assert_frame_equal(actual, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_data_list.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_data_list.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf9293ddd841d63093ea112decab707e4a43c7ae
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_data_list.py
@@ -0,0 +1,92 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+import csv
+from io import StringIO
+
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+from pandas.io.parsers import TextParser
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+
+
+@xfail_pyarrow
+def test_read_data_list(all_parsers):
+ parser = all_parsers
+ kwargs = {"index_col": 0}
+ data = "A,B,C\nfoo,1,2,3\nbar,4,5,6"
+
+ data_list = [["A", "B", "C"], ["foo", "1", "2", "3"], ["bar", "4", "5", "6"]]
+ expected = parser.read_csv(StringIO(data), **kwargs)
+
+ with TextParser(data_list, chunksize=2, **kwargs) as parser:
+ result = parser.read()
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_reader_list(all_parsers):
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+ parser = all_parsers
+ kwargs = {"index_col": 0}
+
+ lines = list(csv.reader(StringIO(data)))
+ with TextParser(lines, chunksize=2, **kwargs) as reader:
+ chunks = list(reader)
+
+ expected = parser.read_csv(StringIO(data), **kwargs)
+
+ tm.assert_frame_equal(chunks[0], expected[:2])
+ tm.assert_frame_equal(chunks[1], expected[2:4])
+ tm.assert_frame_equal(chunks[2], expected[4:])
+
+
+def test_reader_list_skiprows(all_parsers):
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+ parser = all_parsers
+ kwargs = {"index_col": 0}
+
+ lines = list(csv.reader(StringIO(data)))
+ with TextParser(lines, chunksize=2, skiprows=[1], **kwargs) as reader:
+ chunks = list(reader)
+
+ expected = parser.read_csv(StringIO(data), **kwargs)
+
+ tm.assert_frame_equal(chunks[0], expected[1:3])
+
+
+def test_read_csv_parse_simple_list(all_parsers):
+ parser = all_parsers
+ data = """foo
+bar baz
+qux foo
+foo
+bar"""
+
+ result = parser.read_csv(StringIO(data), header=None)
+ expected = DataFrame(["foo", "bar baz", "qux foo", "foo", "bar"])
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_decimal.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_decimal.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb6c97097e5fb35bea06e2a3b6491a48ae1ded78
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_decimal.py
@@ -0,0 +1,73 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+from io import StringIO
+
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.mark.parametrize(
+ "data,thousands,decimal",
+ [
+ (
+ """A|B|C
+1|2,334.01|5
+10|13|10.
+""",
+ ",",
+ ".",
+ ),
+ (
+ """A|B|C
+1|2.334,01|5
+10|13|10,
+""",
+ ".",
+ ",",
+ ),
+ ],
+)
+def test_1000_sep_with_decimal(all_parsers, data, thousands, decimal):
+ parser = all_parsers
+ expected = DataFrame({"A": [1, 10], "B": [2334.01, 13], "C": [5, 10.0]})
+
+ if parser.engine == "pyarrow":
+ msg = "The 'thousands' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data), sep="|", thousands=thousands, decimal=decimal
+ )
+ return
+
+ result = parser.read_csv(
+ StringIO(data), sep="|", thousands=thousands, decimal=decimal
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_euro_decimal_format(all_parsers):
+ parser = all_parsers
+ data = """Id;Number1;Number2;Text1;Text2;Number3
+1;1521,1541;187101,9543;ABC;poi;4,738797819
+2;121,12;14897,76;DEF;uyt;0,377320872
+3;878,158;108013,434;GHI;rez;2,735694704"""
+
+ result = parser.read_csv(StringIO(data), sep=";", decimal=",")
+ expected = DataFrame(
+ [
+ [1, 1521.1541, 187101.9543, "ABC", "poi", 4.738797819],
+ [2, 121.12, 14897.76, "DEF", "uyt", 0.377320872],
+ [3, 878.158, 108013.434, "GHI", "rez", 2.735694704],
+ ],
+ columns=["Id", "Number1", "Number2", "Text1", "Text2", "Number3"],
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_file_buffer_url.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_file_buffer_url.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b2f6fc949256c9632816b56e86f67b89710adb7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_file_buffer_url.py
@@ -0,0 +1,470 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+from io import (
+ BytesIO,
+ StringIO,
+)
+import os
+import platform
+from urllib.error import URLError
+import uuid
+
+import numpy as np
+import pytest
+
+from pandas.compat import WASM
+from pandas.errors import (
+ EmptyDataError,
+ ParserError,
+)
+import pandas.util._test_decorators as td
+
+from pandas import (
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+@pytest.mark.network
+@pytest.mark.single_cpu
+def test_url(all_parsers, csv_dir_path, httpserver):
+ parser = all_parsers
+ kwargs = {"sep": "\t"}
+
+ local_path = os.path.join(csv_dir_path, "salaries.csv")
+ with open(local_path, encoding="utf-8") as f:
+ httpserver.serve_content(content=f.read())
+
+ url_result = parser.read_csv(httpserver.url, **kwargs)
+
+ local_result = parser.read_csv(local_path, **kwargs)
+ tm.assert_frame_equal(url_result, local_result)
+
+
+@pytest.mark.slow
+def test_local_file(all_parsers, csv_dir_path):
+ parser = all_parsers
+ kwargs = {"sep": "\t"}
+
+ local_path = os.path.join(csv_dir_path, "salaries.csv")
+ local_result = parser.read_csv(local_path, **kwargs)
+ url = "file://localhost/" + local_path
+
+ try:
+ url_result = parser.read_csv(url, **kwargs)
+ tm.assert_frame_equal(url_result, local_result)
+ except URLError:
+ # Fails on some systems.
+ pytest.skip("Failing on: " + " ".join(platform.uname()))
+
+
+@xfail_pyarrow # AssertionError: DataFrame.index are different
+def test_path_path_lib(all_parsers, temp_file):
+ parser = all_parsers
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ result = tm.round_trip_pathlib(
+ df.to_csv, lambda p: parser.read_csv(p, index_col=0), temp_file
+ )
+ tm.assert_frame_equal(df, result)
+
+
+@pytest.mark.skipif(WASM, reason="limited file system access on WASM")
+def test_nonexistent_path(all_parsers):
+ # gh-2428: pls no segfault
+ # gh-14086: raise more helpful FileNotFoundError
+ # GH#29233 "File foo" instead of "File b'foo'"
+ parser = all_parsers
+ path = f"{uuid.uuid4()}.csv"
+
+ msg = r"\[Errno 2\]"
+ with pytest.raises(FileNotFoundError, match=msg) as e:
+ parser.read_csv(path)
+ assert path == e.value.filename
+
+
+@pytest.mark.skipif(WASM, reason="limited file system access on WASM")
+@td.skip_if_windows # os.chmod does not work in windows
+def test_no_permission(all_parsers, temp_file):
+ # GH 23784
+ parser = all_parsers
+
+ msg = r"\[Errno 13\]"
+ path = temp_file
+ os.chmod(path, 0) # make file unreadable
+
+ # verify that this process cannot open the file (not running as sudo)
+ try:
+ with open(path, encoding="utf-8"):
+ pass
+ pytest.skip("Running as sudo.")
+ except PermissionError:
+ pass
+
+ with pytest.raises(PermissionError, match=msg) as e:
+ parser.read_csv(path)
+ assert str(path.resolve()) == e.value.filename
+
+
+@pytest.mark.parametrize(
+ "data,kwargs,expected,msg",
+ [
+ # gh-10728: WHITESPACE_LINE
+ (
+ "a,b,c\n4,5,6\n ",
+ {},
+ DataFrame([[4, 5, 6]], columns=["a", "b", "c"]),
+ None,
+ ),
+ # gh-10548: EAT_LINE_COMMENT
+ (
+ "a,b,c\n4,5,6\n#comment",
+ {"comment": "#"},
+ DataFrame([[4, 5, 6]], columns=["a", "b", "c"]),
+ None,
+ ),
+ # EAT_CRNL_NOP
+ (
+ "a,b,c\n4,5,6\n\r",
+ {},
+ DataFrame([[4, 5, 6]], columns=["a", "b", "c"]),
+ None,
+ ),
+ # EAT_COMMENT
+ (
+ "a,b,c\n4,5,6#comment",
+ {"comment": "#"},
+ DataFrame([[4, 5, 6]], columns=["a", "b", "c"]),
+ None,
+ ),
+ # SKIP_LINE
+ (
+ "a,b,c\n4,5,6\nskipme",
+ {"skiprows": [2]},
+ DataFrame([[4, 5, 6]], columns=["a", "b", "c"]),
+ None,
+ ),
+ # EAT_LINE_COMMENT
+ (
+ "a,b,c\n4,5,6\n#comment",
+ {"comment": "#", "skip_blank_lines": False},
+ DataFrame([[4, 5, 6]], columns=["a", "b", "c"]),
+ None,
+ ),
+ # IN_FIELD
+ (
+ "a,b,c\n4,5,6\n ",
+ {"skip_blank_lines": False},
+ DataFrame([["4", 5, 6], [" ", None, None]], columns=["a", "b", "c"]),
+ None,
+ ),
+ # EAT_CRNL
+ (
+ "a,b,c\n4,5,6\n\r",
+ {"skip_blank_lines": False},
+ DataFrame([[4, 5, 6], [None, None, None]], columns=["a", "b", "c"]),
+ None,
+ ),
+ # ESCAPED_CHAR
+ (
+ "a,b,c\n4,5,6\n\\",
+ {"escapechar": "\\"},
+ None,
+ "(EOF following escape character)|(unexpected end of data)",
+ ),
+ # ESCAPE_IN_QUOTED_FIELD
+ (
+ 'a,b,c\n4,5,6\n"\\',
+ {"escapechar": "\\"},
+ None,
+ "(EOF inside string starting at row 2)|(unexpected end of data)",
+ ),
+ # IN_QUOTED_FIELD
+ (
+ 'a,b,c\n4,5,6\n"',
+ {"escapechar": "\\"},
+ None,
+ "(EOF inside string starting at row 2)|(unexpected end of data)",
+ ),
+ ],
+ ids=[
+ "whitespace-line",
+ "eat-line-comment",
+ "eat-crnl-nop",
+ "eat-comment",
+ "skip-line",
+ "eat-line-comment",
+ "in-field",
+ "eat-crnl",
+ "escaped-char",
+ "escape-in-quoted-field",
+ "in-quoted-field",
+ ],
+)
+def test_eof_states(all_parsers, data, kwargs, expected, msg, request):
+ # see gh-10728, gh-10548
+ parser = all_parsers
+
+ if parser.engine == "pyarrow" and "comment" in kwargs:
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+ return
+
+ if parser.engine == "pyarrow" and "\r" not in data:
+ # pandas.errors.ParserError: CSV parse error: Expected 3 columns, got 1:
+ # ValueError: skiprows argument must be an integer when using engine='pyarrow'
+ # AssertionError: Regex pattern did not match.
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+
+ if expected is None:
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+ else:
+ result = parser.read_csv(StringIO(data), **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_temporary_file(all_parsers, temp_file):
+ # see gh-13398
+ parser = all_parsers
+ data = "0 0"
+
+ with temp_file.open(mode="w+", encoding="utf-8") as new_file:
+ new_file.write(data)
+ new_file.flush()
+ new_file.seek(0)
+
+ if parser.engine == "pyarrow":
+ msg = "the 'pyarrow' engine does not support regex separators"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(new_file, sep=r"\s+", header=None)
+ return
+
+ result = parser.read_csv(new_file, sep=r"\s+", header=None)
+
+ expected = DataFrame([[0, 0]])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_internal_eof_byte(all_parsers):
+ # see gh-5500
+ parser = all_parsers
+ data = "a,b\n1\x1a,2"
+
+ expected = DataFrame([["1\x1a", 2]], columns=["a", "b"])
+ result = parser.read_csv(StringIO(data))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_internal_eof_byte_to_file(all_parsers, temp_file):
+ # see gh-16559
+ parser = all_parsers
+ data = b'c1,c2\r\n"test \x1a test", test\r\n'
+ expected = DataFrame([["test \x1a test", " test"]], columns=["c1", "c2"])
+ path = f"__{uuid.uuid4()}__.csv"
+
+ path2 = temp_file.parent / path
+ with open(path2, "wb") as f:
+ f.write(data)
+
+ result = parser.read_csv(path2)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_file_handle_string_io(all_parsers):
+ # gh-14418
+ #
+ # Don't close user provided file handles.
+ parser = all_parsers
+ data = "a,b\n1,2"
+
+ fh = StringIO(data)
+ parser.read_csv(fh)
+ assert not fh.closed
+
+
+def test_file_handles_with_open(all_parsers, csv1):
+ # gh-14418
+ #
+ # Don't close user provided file handles.
+ parser = all_parsers
+
+ for mode in ["r", "rb"]:
+ with open(csv1, mode, encoding="utf-8" if mode == "r" else None) as f:
+ parser.read_csv(f)
+ assert not f.closed
+
+
+def test_invalid_file_buffer_class(all_parsers):
+ # see gh-15337
+ class InvalidBuffer:
+ pass
+
+ parser = all_parsers
+ msg = "Invalid file path or buffer object type"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(InvalidBuffer())
+
+
+def test_invalid_file_buffer_mock(all_parsers):
+ # see gh-15337
+ parser = all_parsers
+ msg = "Invalid file path or buffer object type"
+
+ class Foo:
+ pass
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(Foo())
+
+
+def test_valid_file_buffer_seems_invalid(all_parsers):
+ # gh-16135: we want to ensure that "tell" and "seek"
+ # aren't actually being used when we call `read_csv`
+ #
+ # Thus, while the object may look "invalid" (these
+ # methods are attributes of the `StringIO` class),
+ # it is still a valid file-object for our purposes.
+ class NoSeekTellBuffer(StringIO):
+ def tell(self):
+ raise AttributeError("No tell method")
+
+ def seek(self, pos, whence=0):
+ raise AttributeError("No seek method")
+
+ data = "a\n1"
+ parser = all_parsers
+ expected = DataFrame({"a": [1]})
+
+ result = parser.read_csv(NoSeekTellBuffer(data))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("io_class", [StringIO, BytesIO])
+@pytest.mark.parametrize("encoding", [None, "utf-8"])
+def test_read_csv_file_handle(all_parsers, io_class, encoding):
+ """
+ Test whether read_csv does not close user-provided file handles.
+
+ GH 36980
+ """
+ parser = all_parsers
+ expected = DataFrame({"a": [1], "b": [2]})
+
+ content = "a,b\n1,2"
+ handle = io_class(content.encode("utf-8") if io_class == BytesIO else content)
+
+ tm.assert_frame_equal(parser.read_csv(handle, encoding=encoding), expected)
+ assert not handle.closed
+
+
+def test_memory_map_compression(all_parsers, compression, temp_file):
+ """
+ Support memory map for compressed files.
+
+ GH 37621
+ """
+ parser = all_parsers
+ expected = DataFrame({"a": [1], "b": [2]})
+
+ path = temp_file
+ expected.to_csv(path, index=False, compression=compression)
+
+ if parser.engine == "pyarrow":
+ msg = "The 'memory_map' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(path, memory_map=True, compression=compression)
+ return
+
+ result = parser.read_csv(path, memory_map=True, compression=compression)
+
+ tm.assert_frame_equal(
+ result,
+ expected,
+ )
+
+
+def test_context_manager(all_parsers, datapath):
+ # make sure that opened files are closed
+ parser = all_parsers
+
+ path = datapath("io", "data", "csv", "iris.csv")
+
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(path, chunksize=1)
+ return
+
+ reader = parser.read_csv(path, chunksize=1)
+ assert not reader.handles.handle.closed
+ try:
+ with reader:
+ next(reader)
+ raise AssertionError
+ except AssertionError:
+ assert reader.handles.handle.closed
+
+
+def test_context_manageri_user_provided(all_parsers, datapath):
+ # make sure that user-provided handles are not closed
+ parser = all_parsers
+
+ with open(datapath("io", "data", "csv", "iris.csv"), encoding="utf-8") as path:
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(path, chunksize=1)
+ return
+
+ reader = parser.read_csv(path, chunksize=1)
+ assert not reader.handles.handle.closed
+ try:
+ with reader:
+ next(reader)
+ raise AssertionError
+ except AssertionError:
+ assert not reader.handles.handle.closed
+
+
+@skip_pyarrow # ParserError: Empty CSV file
+def test_file_descriptor_leak(all_parsers, temp_file):
+ # GH 31488
+ parser = all_parsers
+ path = temp_file
+ with pytest.raises(EmptyDataError, match="No columns to parse from file"):
+ parser.read_csv(path)
+
+
+def test_memory_map(all_parsers, csv_dir_path):
+ mmap_file = os.path.join(csv_dir_path, "test_mmap.csv")
+ parser = all_parsers
+
+ expected = DataFrame(
+ {"a": [1, 2, 3], "b": ["one", "two", "three"], "c": ["I", "II", "III"]}
+ )
+
+ if parser.engine == "pyarrow":
+ msg = "The 'memory_map' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(mmap_file, memory_map=True)
+ return
+
+ result = parser.read_csv(mmap_file, memory_map=True)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_float.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_float.py
new file mode 100644
index 0000000000000000000000000000000000000000..d711ee6053b41b63fc6514fad6ff005aa4e5f170
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_float.py
@@ -0,0 +1,110 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+@skip_pyarrow # ParserError: CSV parse error: Empty CSV file or block
+def test_float_parser(all_parsers):
+ # see gh-9565
+ parser = all_parsers
+ data = "45e-1,4.5,45.,inf,-inf"
+ result = parser.read_csv(StringIO(data), header=None)
+
+ expected = DataFrame([[float(s) for s in data.split(",")]])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_scientific_no_exponent(all_parsers_all_precisions):
+ # see gh-12215
+ df = DataFrame.from_dict({"w": ["2e"], "x": ["3E"], "y": ["42e"], "z": ["632E"]})
+ data = df.to_csv(index=False)
+ parser, precision = all_parsers_all_precisions
+
+ df_roundtrip = parser.read_csv(StringIO(data), float_precision=precision)
+ tm.assert_frame_equal(df_roundtrip, df)
+
+
+@pytest.mark.parametrize(
+ "value, expected_value",
+ [
+ ("0E-617", 0.0),
+ ("0E99999999", 0.0),
+ ("-0E99999999", 0.0),
+ ("-0E-99999999", 0.0),
+ ("10E-617", 0.0),
+ ("10E-100000", 0.0),
+ ("-10E-100000", 0.0),
+ ("10e-99999999999", 0.0),
+ ("10e-999999999999", 0.0),
+ ("10e-9999999999999", 0.0),
+ ("10E999", np.inf),
+ ("-10e99999999999", -np.inf),
+ ("10e99999999999", np.inf),
+ ("10e999999999999", np.inf),
+ ("10e9999999999999", np.inf),
+ ("50060e8007123400", np.inf),
+ ("-50060e8007123400", -np.inf),
+ ],
+)
+def test_large_exponent(all_parsers_all_precisions, value, expected_value):
+ # GH#38753; GH#38794; GH#62740
+ parser, precision = all_parsers_all_precisions
+
+ data = f"data\n{value}"
+ result = parser.read_csv(StringIO(data), float_precision=precision)
+ expected = DataFrame({"data": [expected_value]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "value, expected_value",
+ [
+ ("32.0", 32.0),
+ ("32e0", 32.0),
+ ("3.2e1", 32.0),
+ ("3.2e80", 3.2e80),
+ ("3.2e-80", 3.2e-80),
+ ("18446744073709551616.0", float(1 << 64)), # loses precision
+ ("18446744073709551616.5", float(1 << 64)), # loses precision
+ ("36893488147419103232.3", float(1 << 65)), # loses precision
+ ],
+)
+def test_small_int_followed_by_float(
+ all_parsers_all_precisions, value, expected_value, request
+):
+ # GH#51295
+ parser, precision = all_parsers_all_precisions
+ data = f"""data
+ 42
+ {value}"""
+ result = parser.read_csv(StringIO(data), float_precision=precision)
+ expected = DataFrame({"data": [42.0, expected_value]})
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "value", ["81e31d04049863b72", "d81e31d04049863b72", "81e3104049863b72"]
+)
+def test_invalid_float_number(all_parsers_all_precisions, value):
+ # GH#62617
+ parser, precision = all_parsers_all_precisions
+ data = f"h1,h2,h3\ndata1,{value},data3"
+
+ result = parser.read_csv(StringIO(data), float_precision=precision)
+ expected = DataFrame({"h1": ["data1"], "h2": [value], "h3": "data3"})
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_index.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..7570a4df1e89fa2a6ced35959e67daab6007e784
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_index.py
@@ -0,0 +1,307 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+from datetime import datetime
+from io import StringIO
+import os
+
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+@pytest.mark.parametrize(
+ "data,kwargs,expected",
+ [
+ (
+ """foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+""",
+ {"index_col": 0, "names": ["index", "A", "B", "C", "D"]},
+ DataFrame(
+ [
+ [2, 3, 4, 5],
+ [7, 8, 9, 10],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ ],
+ index=Index(["foo", "bar", "baz", "qux", "foo2", "bar2"], name="index"),
+ columns=["A", "B", "C", "D"],
+ ),
+ ),
+ (
+ """foo,one,2,3,4,5
+foo,two,7,8,9,10
+foo,three,12,13,14,15
+bar,one,12,13,14,15
+bar,two,12,13,14,15
+""",
+ {"index_col": [0, 1], "names": ["index1", "index2", "A", "B", "C", "D"]},
+ DataFrame(
+ [
+ [2, 3, 4, 5],
+ [7, 8, 9, 10],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ ],
+ index=MultiIndex.from_tuples(
+ [
+ ("foo", "one"),
+ ("foo", "two"),
+ ("foo", "three"),
+ ("bar", "one"),
+ ("bar", "two"),
+ ],
+ names=["index1", "index2"],
+ ),
+ columns=["A", "B", "C", "D"],
+ ),
+ ),
+ ],
+)
+def test_pass_names_with_index(all_parsers, data, kwargs, expected):
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_col", [[0, 1], [1, 0]])
+def test_multi_index_no_level_names(
+ request, all_parsers, index_col, using_infer_string
+):
+ data = """index1,index2,A,B,C,D
+foo,one,2,3,4,5
+foo,two,7,8,9,10
+foo,three,12,13,14,15
+bar,one,12,13,14,15
+bar,two,12,13,14,15
+"""
+ headless_data = "\n".join(data.split("\n")[1:])
+
+ names = ["A", "B", "C", "D"]
+ parser = all_parsers
+
+ result = parser.read_csv(
+ StringIO(headless_data), index_col=index_col, header=None, names=names
+ )
+ expected = parser.read_csv(StringIO(data), index_col=index_col)
+
+ # No index names in headless data.
+ expected.index.names = [None] * 2
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+def test_multi_index_no_level_names_implicit(all_parsers):
+ parser = all_parsers
+ data = """A,B,C,D
+foo,one,2,3,4,5
+foo,two,7,8,9,10
+foo,three,12,13,14,15
+bar,one,12,13,14,15
+bar,two,12,13,14,15
+"""
+
+ result = parser.read_csv(StringIO(data))
+ expected = DataFrame(
+ [
+ [2, 3, 4, 5],
+ [7, 8, 9, 10],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ ],
+ columns=["A", "B", "C", "D"],
+ index=MultiIndex.from_tuples(
+ [
+ ("foo", "one"),
+ ("foo", "two"),
+ ("foo", "three"),
+ ("bar", "one"),
+ ("bar", "two"),
+ ]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+@pytest.mark.parametrize(
+ "data,columns,header",
+ [
+ ("a,b", ["a", "b"], [0]),
+ (
+ "a,b\nc,d",
+ MultiIndex.from_tuples([("a", "c"), ("b", "d")]),
+ [0, 1],
+ ),
+ ],
+)
+@pytest.mark.parametrize("round_trip", [True, False])
+def test_multi_index_blank_df(all_parsers, data, columns, header, round_trip):
+ # see gh-14545
+ parser = all_parsers
+ expected = DataFrame(columns=columns)
+ data = expected.to_csv(index=False) if round_trip else data
+
+ result = parser.read_csv(StringIO(data), header=header)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # AssertionError: DataFrame.columns are different
+def test_no_unnamed_index(all_parsers):
+ parser = all_parsers
+ data = """ id c0 c1 c2
+0 1 0 a b
+1 2 0 c d
+2 2 2 e f
+"""
+ result = parser.read_csv(StringIO(data), sep=" ")
+ expected = DataFrame(
+ [[0, 1, 0, "a", "b"], [1, 2, 0, "c", "d"], [2, 2, 2, "e", "f"]],
+ columns=["Unnamed: 0", "id", "c0", "c1", "c2"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_duplicate_index_explicit(all_parsers):
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo,12,13,14,15
+bar,12,13,14,15
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), index_col=0)
+
+ expected = DataFrame(
+ [
+ [2, 3, 4, 5],
+ [7, 8, 9, 10],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ ],
+ columns=["A", "B", "C", "D"],
+ index=Index(["foo", "bar", "baz", "qux", "foo", "bar"], name="index"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+def test_read_duplicate_index_implicit(all_parsers):
+ data = """A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo,12,13,14,15
+bar,12,13,14,15
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data))
+
+ expected = DataFrame(
+ [
+ [2, 3, 4, 5],
+ [7, 8, 9, 10],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ [12, 13, 14, 15],
+ ],
+ columns=["A", "B", "C", "D"],
+ index=Index(["foo", "bar", "baz", "qux", "foo", "bar"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+def test_read_csv_no_index_name(all_parsers, csv_dir_path):
+ parser = all_parsers
+ csv2 = os.path.join(csv_dir_path, "test2.csv")
+ result = parser.read_csv(csv2, index_col=0, parse_dates=True)
+
+ expected = DataFrame(
+ [
+ [0.980269, 3.685731, -0.364216805298, -1.159738, "foo"],
+ [1.047916, -0.041232, -0.16181208307, 0.212549, "bar"],
+ [0.498581, 0.731168, -0.537677223318, 1.346270, "baz"],
+ [1.120202, 1.567621, 0.00364077397681, 0.675253, "qux"],
+ [-0.487094, 0.571455, -1.6116394093, 0.103469, "foo2"],
+ ],
+ columns=["A", "B", "C", "D", "E"],
+ index=Index(
+ [
+ datetime(2000, 1, 3),
+ datetime(2000, 1, 4),
+ datetime(2000, 1, 5),
+ datetime(2000, 1, 6),
+ datetime(2000, 1, 7),
+ ],
+ dtype="M8[us]",
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+def test_empty_with_index(all_parsers):
+ # see gh-10184
+ data = "x,y"
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), index_col=0)
+
+ expected = DataFrame(columns=["y"], index=Index([], name="x"))
+ tm.assert_frame_equal(result, expected)
+
+
+# CSV parse error: Empty CSV file or block: cannot infer number of columns
+@skip_pyarrow
+def test_empty_with_multi_index(all_parsers):
+ # see gh-10467
+ data = "x,y,z"
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), index_col=["x", "y"])
+
+ expected = DataFrame(
+ columns=["z"], index=MultiIndex.from_arrays([[]] * 2, names=["x", "y"])
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+# CSV parse error: Empty CSV file or block: cannot infer number of columns
+@skip_pyarrow
+def test_empty_with_reversed_multi_index(all_parsers):
+ data = "x,y,z"
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), index_col=[1, 0])
+
+ expected = DataFrame(
+ columns=["z"], index=MultiIndex.from_arrays([[]] * 2, names=["y", "x"])
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_inf.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_inf.py
new file mode 100644
index 0000000000000000000000000000000000000000..657aa3278a4424b08f062f2f36213d206d38099f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_inf.py
@@ -0,0 +1,59 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+from io import StringIO
+
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+
+
+@xfail_pyarrow # AssertionError: DataFrame.index are different
+@pytest.mark.parametrize("na_filter", [True, False])
+def test_inf_parsing(all_parsers, na_filter):
+ parser = all_parsers
+ data = """\
+,A
+a,inf
+b,-inf
+c,+Inf
+d,-Inf
+e,INF
+f,-INF
+g,+INf
+h,-INf
+i,inF
+j,-inF"""
+ expected = DataFrame(
+ {"A": [float("inf"), float("-inf")] * 5},
+ index=["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
+ )
+ result = parser.read_csv(StringIO(data), index_col=0, na_filter=na_filter)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # AssertionError: DataFrame.index are different
+@pytest.mark.parametrize("na_filter", [True, False])
+def test_infinity_parsing(all_parsers, na_filter):
+ parser = all_parsers
+ data = """\
+,A
+a,Infinity
+b,-Infinity
+c,+Infinity
+"""
+ expected = DataFrame(
+ {"A": [float("infinity"), float("-infinity"), float("+infinity")]},
+ index=["a", "b", "c"],
+ )
+ result = parser.read_csv(StringIO(data), index_col=0, na_filter=na_filter)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_ints.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_ints.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a98f99f80adc8d2eca31b895799ef95a90d005d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_ints.py
@@ -0,0 +1,244 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+def test_int_conversion(all_parsers):
+ data = """A,B
+1.0,1
+2.0,2
+3.0,3
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data))
+
+ expected = DataFrame([[1.0, 1], [2.0, 2], [3.0, 3]], columns=["A", "B"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data,kwargs,expected",
+ [
+ (
+ "A,B\nTrue,1\nFalse,2\nTrue,3",
+ {},
+ [[True, 1], [False, 2], [True, 3]],
+ ),
+ (
+ "A,B\nYES,1\nno,2\nyes,3\nNo,3\nYes,3",
+ {"true_values": ["yes", "Yes", "YES"], "false_values": ["no", "NO", "No"]},
+ [[True, 1], [False, 2], [True, 3], [False, 3], [True, 3]],
+ ),
+ (
+ "A,B\nTRUE,1\nFALSE,2\nTRUE,3",
+ {},
+ [[True, 1], [False, 2], [True, 3]],
+ ),
+ (
+ "A,B\nfoo,bar\nbar,foo",
+ {"true_values": ["foo"], "false_values": ["bar"]},
+ [[True, False], [False, True]],
+ ),
+ ],
+)
+def test_parse_bool(all_parsers, data, kwargs, expected):
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), **kwargs)
+ expected = DataFrame(expected, columns=["A", "B"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_parse_integers_above_fp_precision(all_parsers):
+ data = """Numbers
+17007000002000191
+17007000002000191
+17007000002000191
+17007000002000191
+17007000002000192
+17007000002000192
+17007000002000192
+17007000002000192
+17007000002000192
+17007000002000194"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data))
+ expected = DataFrame(
+ {
+ "Numbers": [
+ 17007000002000191,
+ 17007000002000191,
+ 17007000002000191,
+ 17007000002000191,
+ 17007000002000192,
+ 17007000002000192,
+ 17007000002000192,
+ 17007000002000192,
+ 17007000002000192,
+ 17007000002000194,
+ ]
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("sep", [" ", r"\s+"])
+def test_integer_overflow_bug(all_parsers, sep):
+ # see gh-2601
+ data = "65248E10 11\n55555E55 22\n"
+ parser = all_parsers
+ if parser.engine == "pyarrow" and sep != " ":
+ msg = "the 'pyarrow' engine does not support regex separators"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), header=None, sep=sep)
+ return
+
+ result = parser.read_csv(StringIO(data), header=None, sep=sep)
+ expected = DataFrame([[6.5248e14, 11], [5.5555e59, 22]])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_int64_min_issues(all_parsers):
+ # see gh-2599
+ parser = all_parsers
+ data = "A,B\n0,0\n0,"
+ result = parser.read_csv(StringIO(data))
+
+ expected = DataFrame({"A": [0, 0], "B": [0, np.nan]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conv", [None, np.int64, np.uint64])
+def test_int64_overflow(all_parsers, conv, request):
+ data = """ID
+00013007854817840016671868
+00013007854817840016749251
+00013007854817840016754630
+00013007854817840016781876
+00013007854817840017028824
+00013007854817840017963235
+00013007854817840018860166"""
+ parser = all_parsers
+
+ if conv is None:
+ # 13007854817840016671868 > UINT64_MAX, so this
+ # will overflow and return object as the dtype.
+ if parser.engine == "pyarrow":
+ mark = pytest.mark.xfail(reason="parses to float64")
+ request.applymarker(mark)
+
+ result = parser.read_csv(StringIO(data))
+ expected = DataFrame(
+ [
+ 13007854817840016671868,
+ 13007854817840016749251,
+ 13007854817840016754630,
+ 13007854817840016781876,
+ 13007854817840017028824,
+ 13007854817840017963235,
+ 13007854817840018860166,
+ ],
+ columns=["ID"],
+ )
+ tm.assert_frame_equal(result, expected)
+ else:
+ # 13007854817840016671868 > UINT64_MAX, so attempts
+ # to cast to either int64 or uint64 will result in
+ # an OverflowError being raised.
+ msg = "|".join(
+ [
+ "Python int too large to convert to C long",
+ "long too big to convert",
+ "int too big to convert",
+ ]
+ )
+ err = OverflowError
+ if parser.engine == "pyarrow":
+ err = ValueError
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+
+ with pytest.raises(err, match=msg):
+ parser.read_csv(StringIO(data), converters={"ID": conv})
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+@pytest.mark.parametrize(
+ "val", [np.iinfo(np.uint64).max, np.iinfo(np.int64).max, np.iinfo(np.int64).min]
+)
+def test_int64_uint64_range(all_parsers, val):
+ # These numbers fall right inside the int64-uint64
+ # range, so they should be parsed as integer.
+ parser = all_parsers
+ result = parser.read_csv(StringIO(str(val)), header=None)
+
+ expected = DataFrame([val])
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+@pytest.mark.parametrize(
+ "val", [np.iinfo(np.uint64).max + 1, np.iinfo(np.int64).min - 1]
+)
+def test_outside_int64_uint64_range(all_parsers, val, request):
+ # These numbers fall just outside the int64-uint64
+ # range, so they should be parsed as object.
+ parser = all_parsers
+
+ result = parser.read_csv(StringIO(str(val)), header=None)
+
+ expected = DataFrame([val])
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+@pytest.mark.parametrize(
+ "val", [np.iinfo(np.uint64).max + 1, np.iinfo(np.int64).min - 1]
+)
+def test_outside_int64_uint64_range_follow_str(all_parsers, val):
+ parser = all_parsers
+
+ result = parser.read_csv(StringIO(f"{val}\nabc"), header=None)
+
+ expected = DataFrame([str(val), "abc"])
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # gets float64 dtype instead of object
+@pytest.mark.parametrize("exp_data", [[str(-1), str(2**63)], [str(2**63), str(-1)]])
+def test_numeric_range_too_wide(all_parsers, exp_data):
+ # No numerical dtype can hold both negative and uint64
+ # values, so they should be cast as string.
+ parser = all_parsers
+ data = "\n".join(exp_data)
+ expected = DataFrame(exp_data)
+
+ result = parser.read_csv(StringIO(data), header=None)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_integer_precision(all_parsers):
+ # Gh 7072
+ s = """1,1;0;0;0;1;1;3844;3844;3844;1;1;1;1;1;1;0;0;1;1;0;0,,,4321583677327450765
+5,1;0;0;0;1;1;843;843;843;1;1;1;1;1;1;0;0;1;1;0;0,64.0,;,4321113141090630389"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(s), header=None)[4]
+ expected = Series([4321583677327450765, 4321113141090630389], name=4)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_iterator.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_iterator.py
new file mode 100644
index 0000000000000000000000000000000000000000..5841fb7ad9594a2fb6aa7191d56829c462457a4b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_iterator.py
@@ -0,0 +1,159 @@
+"""
+Tests that work on both the Python and C engines but do not have a
+specific classification into the other test modules.
+"""
+
+from io import StringIO
+
+import pytest
+
+from pandas import (
+ DataFrame,
+ concat,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+def test_iterator(all_parsers):
+ # see gh-6607
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+ parser = all_parsers
+ kwargs = {"index_col": 0}
+
+ expected = parser.read_csv(StringIO(data), **kwargs)
+
+ if parser.engine == "pyarrow":
+ msg = "The 'iterator' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), iterator=True, **kwargs)
+ return
+
+ with parser.read_csv(StringIO(data), iterator=True, **kwargs) as reader:
+ first_chunk = reader.read(3)
+ tm.assert_frame_equal(first_chunk, expected[:3])
+
+ last_chunk = reader.read(5)
+ tm.assert_frame_equal(last_chunk, expected[3:])
+
+
+def test_iterator2(all_parsers):
+ parser = all_parsers
+ data = """A,B,C
+foo,1,2,3
+bar,4,5,6
+baz,7,8,9
+"""
+
+ if parser.engine == "pyarrow":
+ msg = "The 'iterator' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), iterator=True)
+ return
+
+ with parser.read_csv(StringIO(data), iterator=True) as reader:
+ result = list(reader)
+
+ expected = DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ index=["foo", "bar", "baz"],
+ columns=["A", "B", "C"],
+ )
+ tm.assert_frame_equal(result[0], expected)
+
+
+def test_iterator_stop_on_chunksize(all_parsers):
+ # gh-3967: stopping iteration when chunksize is specified
+ parser = all_parsers
+ data = """A,B,C
+foo,1,2,3
+bar,4,5,6
+baz,7,8,9
+"""
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), chunksize=1)
+ return
+
+ with parser.read_csv(StringIO(data), chunksize=1) as reader:
+ result = list(reader)
+
+ assert len(result) == 3
+ expected = DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ index=["foo", "bar", "baz"],
+ columns=["A", "B", "C"],
+ )
+ tm.assert_frame_equal(concat(result), expected)
+
+
+def test_nrows_iterator_without_chunksize(all_parsers):
+ # GH 59079
+ parser = all_parsers
+ data = """A,B,C
+foo,1,2,3
+bar,4,5,6
+baz,7,8,9
+"""
+ if parser.engine == "pyarrow":
+ msg = "The 'iterator' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), iterator=True, nrows=2)
+ return
+
+ with parser.read_csv(StringIO(data), iterator=True, nrows=2) as reader:
+ result = reader.get_chunk()
+
+ expected = DataFrame(
+ [[1, 2, 3], [4, 5, 6]],
+ index=["foo", "bar"],
+ columns=["A", "B", "C"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "kwargs", [{"iterator": True, "chunksize": 1}, {"iterator": True}, {"chunksize": 1}]
+)
+def test_iterator_skipfooter_errors(all_parsers, kwargs):
+ msg = "'skipfooter' not supported for iteration"
+ parser = all_parsers
+ data = "a\n1\n2"
+
+ if parser.engine == "pyarrow":
+ msg = (
+ "The '(chunksize|iterator)' option is not supported with the "
+ "'pyarrow' engine"
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ with parser.read_csv(StringIO(data), skipfooter=1, **kwargs) as _:
+ pass
+
+
+def test_iteration_open_handle(temp_file, all_parsers):
+ parser = all_parsers
+ kwargs = {"header": None}
+
+ with open(temp_file, "w", encoding="utf-8") as f:
+ f.write("AAA\nBBB\nCCC\nDDD\nEEE\nFFF\nGGG")
+
+ with open(temp_file, encoding="utf-8") as f:
+ for line in f:
+ if "CCC" in line:
+ break
+
+ result = parser.read_csv(f, **kwargs)
+ expected = DataFrame({0: ["DDD", "EEE", "FFF", "GGG"]})
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_read_errors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_read_errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8cf435ef0443a17f71b48626579a7acd62b333e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/common/test_read_errors.py
@@ -0,0 +1,315 @@
+"""
+Tests that work on the Python, C and PyArrow engines but do not have a
+specific classification into the other test modules.
+"""
+
+import codecs
+import csv
+from io import StringIO
+import os
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+from pandas.errors import (
+ EmptyDataError,
+ ParserError,
+ ParserWarning,
+)
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+def test_empty_decimal_marker(all_parsers):
+ data = """A|B|C
+1|2,334|5
+10|13|10.
+"""
+ # Parsers support only length-1 decimals
+ msg = "Only length-1 decimal markers supported"
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ msg = (
+ "only single character unicode strings can be "
+ "converted to Py_UCS4, got length 0"
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), decimal="")
+
+
+def test_bad_stream_exception(all_parsers, csv_dir_path):
+ # see gh-13652
+ #
+ # This test validates that both the Python engine and C engine will
+ # raise UnicodeDecodeError instead of C engine raising ParserError
+ # and swallowing the exception that caused read to fail.
+ path = os.path.join(csv_dir_path, "sauron.SHIFT_JIS.csv")
+ codec = codecs.lookup("utf-8")
+ utf8 = codecs.lookup("utf-8")
+ parser = all_parsers
+ msg = "'utf-8' codec can't decode byte"
+
+ # Stream must be binary UTF8.
+ with (
+ open(path, "rb") as handle,
+ codecs.StreamRecoder(
+ handle, utf8.encode, utf8.decode, codec.streamreader, codec.streamwriter
+ ) as stream,
+ ):
+ with pytest.raises(UnicodeDecodeError, match=msg):
+ parser.read_csv(stream)
+
+
+def test_malformed(all_parsers):
+ # see gh-6607
+ parser = all_parsers
+ data = """ignore
+A,B,C
+1,2,3 # comment
+1,2,3,4,5
+2,3,4
+"""
+ msg = "Expected 3 fields in line 4, saw 5"
+ err = ParserError
+ if parser.engine == "pyarrow":
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ err = ValueError
+ with pytest.raises(err, match=msg):
+ parser.read_csv(StringIO(data), header=1, comment="#")
+
+
+@pytest.mark.parametrize("nrows", [5, 3, None])
+def test_malformed_chunks(all_parsers, nrows):
+ data = """ignore
+A,B,C
+skip
+1,2,3
+3,5,10 # comment
+1,2,3,4,5
+2,3,4
+"""
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ msg = "The 'iterator' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ header=1,
+ comment="#",
+ iterator=True,
+ chunksize=1,
+ skiprows=[2],
+ )
+ return
+
+ msg = "Expected 3 fields in line 6, saw 5"
+ with parser.read_csv(
+ StringIO(data), header=1, comment="#", iterator=True, chunksize=1, skiprows=[2]
+ ) as reader:
+ with pytest.raises(ParserError, match=msg):
+ reader.read(nrows)
+
+
+@xfail_pyarrow # does not raise
+def test_catch_too_many_names(all_parsers):
+ # see gh-5156
+ data = """\
+1,2,3
+4,,6
+7,8,9
+10,11,12\n"""
+ parser = all_parsers
+ msg = (
+ "Too many columns specified: expected 4 and found 3"
+ if parser.engine == "c"
+ else "Number of passed names did not match number of header fields in the file"
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), header=0, names=["a", "b", "c", "d"])
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+@pytest.mark.parametrize("nrows", [0, 1, 2, 3, 4, 5])
+def test_raise_on_no_columns(all_parsers, nrows):
+ parser = all_parsers
+ data = "\n" * nrows
+
+ msg = "No columns to parse from file"
+ with pytest.raises(EmptyDataError, match=msg):
+ parser.read_csv(StringIO(data))
+
+
+def test_unexpected_keyword_parameter_exception(all_parsers):
+ # GH-34976
+ parser = all_parsers
+
+ msg = "{}\\(\\) got an unexpected keyword argument 'foo'"
+ with pytest.raises(TypeError, match=msg.format("read_csv")):
+ parser.read_csv("foo.csv", foo=1)
+ with pytest.raises(TypeError, match=msg.format("read_table")):
+ parser.read_table("foo.tsv", foo=1)
+
+
+def test_suppress_error_output(all_parsers):
+ # see gh-15925
+ parser = all_parsers
+ data = "a\n1\n1,2,3\n4\n5,6,7"
+ expected = DataFrame({"a": [1, 4]})
+
+ result = parser.read_csv(StringIO(data), on_bad_lines="skip")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_error_bad_lines(all_parsers):
+ # see gh-15925
+ parser = all_parsers
+ data = "a\n1\n1,2,3\n4\n5,6,7"
+
+ msg = "Expected 1 fields in line 3, saw 3"
+
+ if parser.engine == "pyarrow":
+ # "CSV parse error: Expected 1 columns, got 3: 1,2,3"
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data), on_bad_lines="error")
+
+
+def test_warn_bad_lines(all_parsers):
+ # see gh-15925
+ parser = all_parsers
+ data = "a\n1\n1,2,3\n4\n5,6,7"
+ expected = DataFrame({"a": [1, 4]})
+ match_msg = "Skipping line"
+
+ expected_warning = ParserWarning
+ if parser.engine == "pyarrow":
+ match_msg = "Expected 1 columns, but found 3: 1,2,3"
+
+ with tm.assert_produces_warning(
+ expected_warning, match=match_msg, check_stacklevel=False
+ ):
+ result = parser.read_csv(StringIO(data), on_bad_lines="warn")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_csv_wrong_num_columns(all_parsers):
+ # Too few columns.
+ data = """A,B,C,D,E,F
+1,2,3,4,5,6
+6,7,8,9,10,11,12
+11,12,13,14,15,16
+"""
+ parser = all_parsers
+ msg = "Expected 6 fields in line 3, saw 7"
+
+ if parser.engine == "pyarrow":
+ # Expected 6 columns, got 7: 6,7,8,9,10,11,12
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data))
+
+
+def test_null_byte_char(request, all_parsers):
+ # see gh-2741
+ data = "\x00,foo"
+ names = ["a", "b"]
+ parser = all_parsers
+
+ if parser.engine in ["c", "python"]:
+ if parser.engine == "python":
+ request.applymarker(
+ pytest.mark.xfail(reason="This is read as an empty character not null")
+ )
+ expected = DataFrame([[np.nan, "foo"]], columns=names)
+ out = parser.read_csv(StringIO(data), names=names)
+ tm.assert_frame_equal(out, expected)
+ else:
+ if parser.engine == "pyarrow":
+ # CSV parse error: Empty CSV file or block: "
+ # cannot infer number of columns"
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+ else:
+ msg = "NULL byte detected"
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data), names=names)
+
+
+@pytest.mark.filterwarnings("always::ResourceWarning")
+def test_open_file(all_parsers, temp_file):
+ # GH 39024
+ parser = all_parsers
+
+ msg = "Could not determine delimiter"
+ err = csv.Error
+ if parser.engine == "c":
+ msg = "object of type 'NoneType' has no len"
+ err = TypeError
+ elif parser.engine == "pyarrow":
+ msg = "'utf-8' codec can't decode byte 0xe4"
+ err = ValueError
+
+ file = Path(temp_file)
+ file.write_bytes(b"\xe4\na\n1")
+
+ with tm.assert_produces_warning(None):
+ # should not trigger a ResourceWarning
+ with pytest.raises(err, match=msg):
+ parser.read_csv(file, sep=None, encoding_errors="replace")
+
+
+def test_invalid_on_bad_line(all_parsers):
+ parser = all_parsers
+ data = "a\n1\n1,2,3\n4\n5,6,7"
+ with pytest.raises(ValueError, match="Argument abc is invalid for on_bad_lines"):
+ parser.read_csv(StringIO(data), on_bad_lines="abc")
+
+
+def test_bad_header_uniform_error(all_parsers):
+ parser = all_parsers
+ data = "+++123456789...\ncol1,col2,col3,col4\n1,2,3,4\n"
+ msg = "Expected 2 fields in line 2, saw 4"
+ if parser.engine == "c":
+ msg = (
+ "Could not construct index. Requested to use 1 "
+ "number of columns, but 3 left to parse."
+ )
+ elif parser.engine == "pyarrow":
+ # "CSV parse error: Expected 1 columns, got 4: col1,col2,col3,col4"
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data), index_col=0, on_bad_lines="error")
+
+
+def test_on_bad_lines_warn_correct_formatting(all_parsers):
+ # see gh-15925
+ parser = all_parsers
+ data = """1,2
+a,b
+a,b,c
+a,b,d
+a,b
+"""
+ expected = DataFrame({"1": "a", "2": ["b"] * 2})
+ match_msg = "Skipping line"
+
+ expected_warning = ParserWarning
+ if parser.engine == "pyarrow":
+ match_msg = "Expected 2 columns, but found 3: a,b,c"
+
+ with tm.assert_produces_warning(
+ expected_warning, match=match_msg, check_stacklevel=False
+ ):
+ result = parser.read_csv(StringIO(data), on_bad_lines="warn")
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..90f77a7024235f3458f96ba4e2938b7c28f966b4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/conftest.py
@@ -0,0 +1,337 @@
+from __future__ import annotations
+
+import os
+
+import pytest
+
+from pandas.compat import HAS_PYARROW
+from pandas.compat._optional import VERSIONS
+
+from pandas import (
+ read_csv,
+ read_table,
+)
+import pandas._testing as tm
+
+
+class BaseParser:
+ engine: str | None = None
+ low_memory = True
+ float_precision_choices: list[str | None] = []
+
+ def update_kwargs(self, kwargs):
+ kwargs = kwargs.copy()
+ kwargs.update({"engine": self.engine, "low_memory": self.low_memory})
+
+ return kwargs
+
+ def read_csv(self, *args, **kwargs):
+ kwargs = self.update_kwargs(kwargs)
+ return read_csv(*args, **kwargs)
+
+ def read_csv_check_warnings(
+ self,
+ warn_type: type[Warning],
+ warn_msg: str,
+ *args,
+ raise_on_extra_warnings=True,
+ check_stacklevel: bool = True,
+ **kwargs,
+ ):
+ # We need to check the stacklevel here instead of in the tests
+ # since this is where read_csv is called and where the warning
+ # should point to.
+ kwargs = self.update_kwargs(kwargs)
+ with tm.assert_produces_warning(
+ warn_type,
+ match=warn_msg,
+ raise_on_extra_warnings=raise_on_extra_warnings,
+ check_stacklevel=check_stacklevel,
+ ):
+ return read_csv(*args, **kwargs)
+
+ def read_table(self, *args, **kwargs):
+ kwargs = self.update_kwargs(kwargs)
+ return read_table(*args, **kwargs)
+
+ def read_table_check_warnings(
+ self,
+ warn_type: type[Warning],
+ warn_msg: str,
+ *args,
+ raise_on_extra_warnings=True,
+ **kwargs,
+ ):
+ # We need to check the stacklevel here instead of in the tests
+ # since this is where read_table is called and where the warning
+ # should point to.
+ kwargs = self.update_kwargs(kwargs)
+ with tm.assert_produces_warning(
+ warn_type, match=warn_msg, raise_on_extra_warnings=raise_on_extra_warnings
+ ):
+ return read_table(*args, **kwargs)
+
+
+class CParser(BaseParser):
+ engine = "c"
+ float_precision_choices = [None, "high", "round_trip"]
+
+
+class CParserHighMemory(CParser):
+ low_memory = False
+
+
+class CParserLowMemory(CParser):
+ low_memory = True
+
+
+class PythonParser(BaseParser):
+ engine = "python"
+ float_precision_choices = [None]
+
+
+class PyArrowParser(BaseParser):
+ engine = "pyarrow"
+ float_precision_choices = [None]
+
+
+@pytest.fixture
+def csv_dir_path(datapath):
+ """
+ The directory path to the data files needed for parser tests.
+ """
+ return datapath("io", "parser", "data")
+
+
+@pytest.fixture
+def csv1(datapath):
+ """
+ The path to the data file "test1.csv" needed for parser tests.
+ """
+ return os.path.join(datapath("io", "data", "csv"), "test1.csv")
+
+
+_cParserHighMemory = CParserHighMemory
+_cParserLowMemory = CParserLowMemory
+_pythonParser = PythonParser
+_pyarrowParser = PyArrowParser
+
+_py_parsers_only = [_pythonParser]
+_c_parsers_only = [_cParserHighMemory, _cParserLowMemory]
+_pyarrow_parsers_only = [
+ pytest.param(
+ _pyarrowParser,
+ marks=[
+ pytest.mark.single_cpu,
+ pytest.mark.skipif(not HAS_PYARROW, reason="pyarrow is not installed"),
+ ],
+ )
+]
+
+_all_parsers = [*_c_parsers_only, *_py_parsers_only, *_pyarrow_parsers_only]
+
+_py_parser_ids = ["python"]
+_c_parser_ids = ["c_high", "c_low"]
+_pyarrow_parsers_ids = ["pyarrow"]
+
+_all_parser_ids = [*_c_parser_ids, *_py_parser_ids, *_pyarrow_parsers_ids]
+
+
+@pytest.fixture(params=_all_parsers, ids=_all_parser_ids)
+def all_parsers(request):
+ """
+ Fixture all of the CSV parsers.
+ """
+ parser = request.param()
+ if parser.engine == "pyarrow":
+ pytest.importorskip("pyarrow", VERSIONS["pyarrow"])
+ # Try finding a way to disable threads all together
+ # for more stable CI runs
+ import pyarrow
+
+ pyarrow.set_cpu_count(1)
+ return parser
+
+
+@pytest.fixture(params=_c_parsers_only, ids=_c_parser_ids)
+def c_parser_only(request):
+ """
+ Fixture all of the CSV parsers using the C engine.
+ """
+ return request.param()
+
+
+@pytest.fixture(params=_py_parsers_only, ids=_py_parser_ids)
+def python_parser_only(request):
+ """
+ Fixture all of the CSV parsers using the Python engine.
+ """
+ return request.param()
+
+
+@pytest.fixture(params=_pyarrow_parsers_only, ids=_pyarrow_parsers_ids)
+def pyarrow_parser_only(request):
+ """
+ Fixture all of the CSV parsers using the Pyarrow engine.
+ """
+ return request.param()
+
+
+def _get_all_parser_float_precision_combinations():
+ """
+ Return all allowable parser and float precision
+ combinations and corresponding ids.
+ """
+ params = []
+ ids = []
+ for parser, parser_id in zip(_all_parsers, _all_parser_ids):
+ if hasattr(parser, "values"):
+ # Wrapped in pytest.param, get the actual parser back
+ parser = parser.values[0]
+ for precision in parser.float_precision_choices:
+ # Re-wrap in pytest.param for pyarrow
+ mark = (
+ [
+ pytest.mark.single_cpu,
+ pytest.mark.skipif(
+ not HAS_PYARROW, reason="pyarrow is not installed"
+ ),
+ ]
+ if parser.engine == "pyarrow"
+ else ()
+ )
+ param = pytest.param((parser(), precision), marks=mark)
+ params.append(param)
+ ids.append(f"{parser_id}-{precision}")
+
+ return {"params": params, "ids": ids}
+
+
+@pytest.fixture(
+ params=_get_all_parser_float_precision_combinations()["params"],
+ ids=_get_all_parser_float_precision_combinations()["ids"],
+)
+def all_parsers_all_precisions(request):
+ """
+ Fixture for all allowable combinations of parser
+ and float precision
+ """
+ return request.param
+
+
+_utf_values = [8, 16, 32]
+
+_encoding_seps = ["", "-", "_"]
+_encoding_prefixes = ["utf", "UTF"]
+
+_encoding_fmts = [
+ f"{prefix}{sep}{{0}}" for sep in _encoding_seps for prefix in _encoding_prefixes
+]
+
+
+@pytest.fixture(params=_utf_values)
+def utf_value(request):
+ """
+ Fixture for all possible integer values for a UTF encoding.
+ """
+ return request.param
+
+
+@pytest.fixture(params=_encoding_fmts)
+def encoding_fmt(request):
+ """
+ Fixture for all possible string formats of a UTF encoding.
+ """
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ ("-1,0", -1.0),
+ ("-1,2e0", -1.2),
+ ("-1e0", -1.0),
+ ("+1e0", 1.0),
+ ("+1e+0", 1.0),
+ ("+1e-1", 0.1),
+ ("+,1e1", 1.0),
+ ("+1,e0", 1.0),
+ ("-,1e1", -1.0),
+ ("-1,e0", -1.0),
+ ("0,1", 0.1),
+ ("1,", 1.0),
+ (",1", 0.1),
+ ("-,1", -0.1),
+ ("1_,", 1.0),
+ ("1_234,56", 1234.56),
+ ("1_234,56e0", 1234.56),
+ # negative cases; must not parse as float
+ ("_", "_"),
+ ("-_", "-_"),
+ ("-_1", "-_1"),
+ ("-_1e0", "-_1e0"),
+ ("_1", "_1"),
+ ("_1,", "_1,"),
+ ("_1,_", "_1,_"),
+ ("_1e0", "_1e0"),
+ ("1,2e_1", "1,2e_1"),
+ ("1,2e1_0", "1,2e1_0"),
+ ("1,_2", "1,_2"),
+ (",1__2", ",1__2"),
+ (",1e", ",1e"),
+ ("-,1e", "-,1e"),
+ ("1_000,000_000", "1_000,000_000"),
+ ("1,e1_2", "1,e1_2"),
+ ("e11,2", "e11,2"),
+ ("1e11,2", "1e11,2"),
+ ("1,2,2", "1,2,2"),
+ ("1,2_1", "1,2_1"),
+ ("1,2e-10e1", "1,2e-10e1"),
+ ("--1,2", "--1,2"),
+ ("1a_2,1", "1a_2,1"),
+ ("1,2E-1", 0.12),
+ ("1,2E1", 12.0),
+ ]
+)
+def numeric_decimal(request):
+ """
+ Fixture for all numeric formats which should get recognized. The first entry
+ represents the value to read while the second represents the expected result.
+ """
+ return request.param
+
+
+@pytest.fixture
+def pyarrow_xfail(request):
+ """
+ Fixture that xfails a test if the engine is pyarrow.
+
+ Use if failure is do to unsupported keywords or inconsistent results.
+ """
+ if "all_parsers" in request.fixturenames:
+ parser = request.getfixturevalue("all_parsers")
+ elif "all_parsers_all_precisions" in request.fixturenames:
+ # Return value is tuple of (engine, precision)
+ parser = request.getfixturevalue("all_parsers_all_precisions")[0]
+ else:
+ return
+ if parser.engine == "pyarrow":
+ mark = pytest.mark.xfail(reason="pyarrow doesn't support this.")
+ request.applymarker(mark)
+
+
+@pytest.fixture
+def pyarrow_skip(request):
+ """
+ Fixture that skips a test if the engine is pyarrow.
+
+ Use if failure is do a parsing failure from pyarrow.csv.read_csv
+ """
+ if "all_parsers" in request.fixturenames:
+ parser = request.getfixturevalue("all_parsers")
+ elif "all_parsers_all_precisions" in request.fixturenames:
+ # Return value is tuple of (engine, precision)
+ parser = request.getfixturevalue("all_parsers_all_precisions")[0]
+ else:
+ return
+ if parser.engine == "pyarrow":
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/test_categorical.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/test_categorical.py
new file mode 100644
index 0000000000000000000000000000000000000000..c157510e552b4ebe0f59b994b4c5c9dcda34be0b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/test_categorical.py
@@ -0,0 +1,338 @@
+"""
+Tests dtype specification during parsing
+for all of the parsers defined in parsers.py
+"""
+
+from io import StringIO
+import os
+
+import numpy as np
+import pytest
+
+from pandas._libs import parsers as libparsers
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.dtypes import CategoricalDtype
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ Timestamp,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+
+
+@xfail_pyarrow # AssertionError: Attributes of DataFrame.iloc[:, 0] are different
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ "category",
+ CategoricalDtype(),
+ {"a": "category", "b": "category", "c": CategoricalDtype()},
+ ],
+)
+def test_categorical_dtype(all_parsers, dtype):
+ # see gh-10153
+ parser = all_parsers
+ data = """a,b,c
+1,a,3.4
+1,a,3.4
+2,b,4.5"""
+ expected = DataFrame(
+ {
+ "a": Categorical(["1", "1", "2"]),
+ "b": Categorical(["a", "a", "b"]),
+ "c": Categorical(["3.4", "3.4", "4.5"]),
+ }
+ )
+ actual = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(actual, expected)
+
+
+@pytest.mark.parametrize("dtype", [{"b": "category"}, {1: "category"}])
+def test_categorical_dtype_single(all_parsers, dtype, request):
+ # see gh-10153
+ parser = all_parsers
+ data = """a,b,c
+1,a,3.4
+1,a,3.4
+2,b,4.5"""
+ expected = DataFrame(
+ {"a": [1, 1, 2], "b": Categorical(["a", "a", "b"]), "c": [3.4, 3.4, 4.5]}
+ )
+ if parser.engine == "pyarrow":
+ mark = pytest.mark.xfail(
+ strict=False,
+ reason="Flaky test sometimes gives object dtype instead of Categorical",
+ )
+ request.applymarker(mark)
+
+ actual = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(actual, expected)
+
+
+@xfail_pyarrow # AssertionError: Attributes of DataFrame.iloc[:, 0] are different
+def test_categorical_dtype_unsorted(all_parsers):
+ # see gh-10153
+ parser = all_parsers
+ data = """a,b,c
+1,b,3.4
+1,b,3.4
+2,a,4.5"""
+ expected = DataFrame(
+ {
+ "a": Categorical(["1", "1", "2"]),
+ "b": Categorical(["b", "b", "a"]),
+ "c": Categorical(["3.4", "3.4", "4.5"]),
+ }
+ )
+ actual = parser.read_csv(StringIO(data), dtype="category")
+ tm.assert_frame_equal(actual, expected)
+
+
+@xfail_pyarrow # AssertionError: Attributes of DataFrame.iloc[:, 0] are different
+def test_categorical_dtype_missing(all_parsers):
+ # see gh-10153
+ parser = all_parsers
+ data = """a,b,c
+1,b,3.4
+1,nan,3.4
+2,a,4.5"""
+ expected = DataFrame(
+ {
+ "a": Categorical(["1", "1", "2"]),
+ "b": Categorical(["b", np.nan, "a"]),
+ "c": Categorical(["3.4", "3.4", "4.5"]),
+ }
+ )
+ actual = parser.read_csv(StringIO(data), dtype="category")
+ tm.assert_frame_equal(actual, expected)
+
+
+@xfail_pyarrow # AssertionError: Attributes of DataFrame.iloc[:, 0] are different
+@pytest.mark.slow
+def test_categorical_dtype_high_cardinality_numeric(all_parsers, monkeypatch):
+ # see gh-18186
+ # was an issue with C parser, due to DEFAULT_BUFFER_HEURISTIC
+ parser = all_parsers
+ heuristic = 2**5
+ data = np.sort([str(i) for i in range(heuristic + 1)])
+ expected = DataFrame({"a": Categorical(data, ordered=True)})
+ with monkeypatch.context() as m:
+ m.setattr(libparsers, "DEFAULT_BUFFER_HEURISTIC", heuristic)
+ actual = parser.read_csv(StringIO("a\n" + "\n".join(data)), dtype="category")
+ actual["a"] = actual["a"].cat.reorder_categories(
+ np.sort(actual.a.cat.categories), ordered=True
+ )
+ tm.assert_frame_equal(actual, expected)
+
+
+def test_categorical_dtype_utf16(all_parsers, csv_dir_path):
+ # see gh-10153
+ pth = os.path.join(csv_dir_path, "utf16_ex.txt")
+ parser = all_parsers
+ encoding = "utf-16"
+ sep = "\t"
+
+ expected = parser.read_csv(pth, sep=sep, encoding=encoding)
+ expected = expected.apply(Categorical)
+
+ actual = parser.read_csv(pth, sep=sep, encoding=encoding, dtype="category")
+ tm.assert_frame_equal(actual, expected)
+
+
+def test_categorical_dtype_chunksize_infer_categories(all_parsers):
+ # see gh-10153
+ parser = all_parsers
+ data = """a,b
+1,a
+1,b
+1,b
+2,c"""
+ expecteds = [
+ DataFrame({"a": [1, 1], "b": Categorical(["a", "b"])}),
+ DataFrame({"a": [1, 2], "b": Categorical(["b", "c"])}, index=[2, 3]),
+ ]
+
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), dtype={"b": "category"}, chunksize=2)
+ return
+
+ with parser.read_csv(
+ StringIO(data), dtype={"b": "category"}, chunksize=2
+ ) as actuals:
+ for actual, expected in zip(actuals, expecteds):
+ tm.assert_frame_equal(actual, expected)
+
+
+def test_categorical_dtype_chunksize_explicit_categories(all_parsers):
+ # see gh-10153
+ parser = all_parsers
+ data = """a,b
+1,a
+1,b
+1,b
+2,c"""
+ cats = ["a", "b", "c"]
+ expecteds = [
+ DataFrame({"a": [1, 1], "b": Categorical(["a", "b"], categories=cats)}),
+ DataFrame(
+ {"a": [1, 2], "b": Categorical(["b", "c"], categories=cats)},
+ index=[2, 3],
+ ),
+ ]
+ dtype = CategoricalDtype(cats)
+
+ if parser.engine == "pyarrow":
+ msg = "The 'chunksize' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), dtype={"b": dtype}, chunksize=2)
+ return
+
+ with parser.read_csv(StringIO(data), dtype={"b": dtype}, chunksize=2) as actuals:
+ for actual, expected in zip(actuals, expecteds):
+ tm.assert_frame_equal(actual, expected)
+
+
+def test_categorical_dtype_latin1(all_parsers, csv_dir_path):
+ # see gh-10153
+ pth = os.path.join(csv_dir_path, "unicode_series.csv")
+ parser = all_parsers
+ encoding = "latin-1"
+
+ expected = parser.read_csv(pth, header=None, encoding=encoding)
+ expected[1] = Categorical(expected[1])
+
+ actual = parser.read_csv(pth, header=None, encoding=encoding, dtype={1: "category"})
+ tm.assert_frame_equal(actual, expected)
+
+
+@pytest.mark.parametrize("ordered", [False, True])
+@pytest.mark.parametrize(
+ "categories",
+ [["a", "b", "c"], ["a", "c", "b"], ["a", "b", "c", "d"], ["c", "b", "a"]],
+)
+def test_categorical_category_dtype(all_parsers, categories, ordered):
+ parser = all_parsers
+ data = """a,b
+1,a
+1,b
+1,b
+2,c"""
+ expected = DataFrame(
+ {
+ "a": [1, 1, 1, 2],
+ "b": Categorical(
+ ["a", "b", "b", "c"], categories=categories, ordered=ordered
+ ),
+ }
+ )
+
+ dtype = {"b": CategoricalDtype(categories=categories, ordered=ordered)}
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_category_dtype_unsorted(all_parsers):
+ parser = all_parsers
+ data = """a,b
+1,a
+1,b
+1,b
+2,c"""
+ dtype = CategoricalDtype(["c", "b", "a"])
+ expected = DataFrame(
+ {
+ "a": [1, 1, 1, 2],
+ "b": Categorical(["a", "b", "b", "c"], categories=["c", "b", "a"]),
+ }
+ )
+
+ result = parser.read_csv(StringIO(data), dtype={"b": dtype})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_coerces_numeric(all_parsers):
+ parser = all_parsers
+ dtype = {"b": CategoricalDtype([1, 2, 3])}
+
+ data = "b\n1\n1\n2\n3"
+ expected = DataFrame({"b": Categorical([1, 1, 2, 3])})
+
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_coerces_datetime(all_parsers):
+ parser = all_parsers
+ dti = pd.DatetimeIndex(["2017-01-01", "2018-01-01", "2019-01-01"], freq=None)
+ dtype = {"b": CategoricalDtype(dti)}
+
+ data = "b\n2017-01-01\n2018-01-01\n2019-01-01"
+ expected = DataFrame({"b": Categorical(dtype["b"].categories)})
+
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_coerces_timestamp(all_parsers):
+ parser = all_parsers
+ dtype = {"b": CategoricalDtype([Timestamp("2014")])}
+
+ data = "b\n2014-01-01\n2014-01-01"
+ expected = DataFrame({"b": Categorical([Timestamp("2014")] * 2)})
+
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_coerces_timedelta(all_parsers):
+ parser = all_parsers
+ dtype = {"b": CategoricalDtype(pd.to_timedelta(["1h", "2h", "3h"]))}
+
+ data = "b\n1h\n2h\n3h"
+ expected = DataFrame({"b": Categorical(dtype["b"].categories)})
+
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ "b\nTrue\nFalse\nNA\nFalse",
+ "b\ntrue\nfalse\nNA\nfalse",
+ "b\nTRUE\nFALSE\nNA\nFALSE",
+ "b\nTrue\nFalse\nNA\nFALSE",
+ ],
+)
+def test_categorical_dtype_coerces_boolean(all_parsers, data):
+ # see gh-20498
+ parser = all_parsers
+ dtype = {"b": CategoricalDtype([False, True])}
+ expected = DataFrame({"b": Categorical([True, False, None, False])})
+
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_unexpected_categories(all_parsers):
+ parser = all_parsers
+ dtype = {"b": CategoricalDtype(["a", "b", "d", "e"])}
+
+ data = "b\nd\na\nc\nd" # Unexpected c
+ expected = DataFrame({"b": Categorical(["d", "a", None, "d"], dtype=dtype["b"])})
+
+ msg = "Constructing a Categorical with a dtype and values containing"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg, check_stacklevel=False):
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/test_dtypes_basic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/test_dtypes_basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..786fda332ad1fcbcf28941cb34e42e8ec0b1cc6c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/test_dtypes_basic.py
@@ -0,0 +1,639 @@
+"""
+Tests dtype specification during parsing
+for all of the parsers defined in parsers.py
+"""
+
+from collections import defaultdict
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas.errors import ParserWarning
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.core.arrays import IntegerArray
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+
+
+@pytest.mark.parametrize("dtype", [str, object])
+@pytest.mark.parametrize("check_orig", [True, False])
+@pytest.mark.usefixtures("pyarrow_xfail")
+def test_dtype_all_columns(
+ all_parsers, dtype, check_orig, using_infer_string, temp_file
+):
+ # see gh-3795, gh-6607
+ parser = all_parsers
+
+ df = DataFrame(
+ np.random.default_rng(2).random((5, 2)).round(4),
+ columns=list("AB"),
+ index=["1A", "1B", "1C", "1D", "1E"],
+ )
+
+ df.to_csv(temp_file)
+
+ result = parser.read_csv(temp_file, dtype=dtype, index_col=0)
+
+ if check_orig:
+ expected = df.copy()
+ result = result.astype(float)
+ elif using_infer_string and dtype is str:
+ expected = df.astype(str)
+ else:
+ expected = df.astype(str).astype(object)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+def test_dtype_per_column(all_parsers):
+ parser = all_parsers
+ data = """\
+one,two
+1,2.5
+2,3.5
+3,4.5
+4,5.5"""
+ expected = DataFrame(
+ [[1, "2.5"], [2, "3.5"], [3, "4.5"], [4, "5.5"]], columns=["one", "two"]
+ )
+ expected["one"] = expected["one"].astype(np.float64)
+
+ result = parser.read_csv(StringIO(data), dtype={"one": np.float64, 1: str})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_invalid_dtype_per_column(all_parsers):
+ parser = all_parsers
+ data = """\
+one,two
+1,2.5
+2,3.5
+3,4.5
+4,5.5"""
+
+ with pytest.raises(TypeError, match="data type [\"']foo[\"'] not understood"):
+ parser.read_csv(StringIO(data), dtype={"one": "foo", 1: "int"})
+
+
+def test_raise_on_passed_int_dtype_with_nas(all_parsers):
+ # see gh-2631
+ parser = all_parsers
+ data = """YEAR, DOY, a
+2001,106380451,10
+2001,,11
+2001,106380451,67"""
+
+ if parser.engine == "c":
+ msg = "Integer column has NA values"
+ elif parser.engine == "pyarrow":
+ msg = "The 'skipinitialspace' option is not supported with the 'pyarrow' engine"
+ else:
+ msg = "Unable to convert column DOY"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), dtype={"DOY": np.int64}, skipinitialspace=True)
+
+
+def test_dtype_with_converters(all_parsers):
+ parser = all_parsers
+ data = """a,b
+1.1,2.2
+1.2,2.3"""
+
+ if parser.engine == "pyarrow":
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data), dtype={"a": "i8"}, converters={"a": lambda x: str(x)}
+ )
+ return
+
+ # Dtype spec ignored if converted specified.
+ result = parser.read_csv_check_warnings(
+ ParserWarning,
+ "Both a converter and dtype were specified for column a "
+ "- only the converter will be used.",
+ StringIO(data),
+ dtype={"a": "i8"},
+ converters={"a": lambda x: str(x)},
+ )
+ expected = DataFrame({"a": ["1.1", "1.2"], "b": [2.2, 2.3]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_numeric_dtype(all_parsers, any_real_numpy_dtype):
+ data = "0\n1"
+ parser = all_parsers
+ expected = DataFrame([0, 1], dtype=any_real_numpy_dtype)
+
+ result = parser.read_csv(StringIO(data), header=None, dtype=any_real_numpy_dtype)
+ tm.assert_frame_equal(expected, result, check_column_type=False)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+def test_boolean_dtype(all_parsers):
+ parser = all_parsers
+ data = "\n".join(
+ [
+ "a",
+ "True",
+ "TRUE",
+ "true",
+ "1",
+ "1.0",
+ "False",
+ "FALSE",
+ "false",
+ "0",
+ "0.0",
+ "NaN",
+ "nan",
+ "NA",
+ "null",
+ "NULL",
+ ]
+ )
+
+ result = parser.read_csv(StringIO(data), dtype="boolean")
+ expected = DataFrame(
+ {
+ "a": pd.array(
+ [
+ True,
+ True,
+ True,
+ True,
+ True,
+ False,
+ False,
+ False,
+ False,
+ False,
+ None,
+ None,
+ None,
+ None,
+ None,
+ ],
+ dtype="boolean",
+ )
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+def test_delimiter_with_usecols_and_parse_dates(all_parsers):
+ # GH#35873
+ result = all_parsers.read_csv(
+ StringIO('"dump","-9,1","-9,1",20101010'),
+ engine="python",
+ names=["col", "col1", "col2", "col3"],
+ usecols=["col1", "col2", "col3"],
+ parse_dates=["col3"],
+ decimal=",",
+ )
+ expected = DataFrame(
+ {"col1": [-9.1], "col2": [-9.1], "col3": [Timestamp("2010-10-10")]}
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("thousands", ["_", None])
+def test_decimal_and_exponential(
+ request, python_parser_only, numeric_decimal, thousands
+):
+ # GH#31920
+ decimal_number_check(request, python_parser_only, numeric_decimal, thousands, None)
+
+
+@pytest.mark.parametrize("thousands", ["_", None])
+@pytest.mark.parametrize("float_precision", [None, "legacy", "high", "round_trip"])
+def test_1000_sep_decimal_float_precision(
+ request, c_parser_only, numeric_decimal, float_precision, thousands
+):
+ # test decimal and thousand sep handling in across 'float_precision'
+ # parsers
+ decimal_number_check(
+ request, c_parser_only, numeric_decimal, thousands, float_precision
+ )
+ text, value = numeric_decimal
+ text = " " + text + " "
+ if isinstance(value, str): # the negative cases (parse as text)
+ value = " " + value + " "
+ decimal_number_check(
+ request, c_parser_only, (text, value), thousands, float_precision
+ )
+
+
+def decimal_number_check(request, parser, numeric_decimal, thousands, float_precision):
+ # GH#31920
+ value = numeric_decimal[0]
+ if thousands is None and value in ("1_,", "1_234,56", "1_234,56e0"):
+ request.applymarker(
+ pytest.mark.xfail(reason=f"thousands={thousands} and sep is in {value}")
+ )
+ df = parser.read_csv(
+ StringIO(value),
+ float_precision=float_precision,
+ sep="|",
+ thousands=thousands,
+ decimal=",",
+ header=None,
+ )
+ val = df.iloc[0, 0]
+ assert val == numeric_decimal[1]
+
+
+@pytest.mark.parametrize("float_precision", [None, "legacy", "high", "round_trip"])
+def test_skip_whitespace(c_parser_only, float_precision):
+ DATA = """id\tnum\t
+1\t1.2 \t
+1\t 2.1\t
+2\t 1\t
+2\t 1.2 \t
+"""
+ df = c_parser_only.read_csv(
+ StringIO(DATA),
+ float_precision=float_precision,
+ sep="\t",
+ header=0,
+ dtype={1: np.float64},
+ )
+ tm.assert_series_equal(df.iloc[:, 1], pd.Series([1.2, 2.1, 1.0, 1.2], name="num"))
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+def test_true_values_cast_to_bool(all_parsers):
+ # GH#34655
+ text = """a,b
+yes,xxx
+no,yyy
+1,zzz
+0,aaa
+ """
+ parser = all_parsers
+ result = parser.read_csv(
+ StringIO(text),
+ true_values=["yes"],
+ false_values=["no"],
+ dtype={"a": "boolean"},
+ )
+ expected = DataFrame(
+ {"a": [True, False, True, False], "b": ["xxx", "yyy", "zzz", "aaa"]}
+ )
+ expected["a"] = expected["a"].astype("boolean")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+@pytest.mark.parametrize("dtypes, exp_value", [({}, "1"), ({"a.1": "int64"}, 1)])
+def test_dtype_mangle_dup_cols(all_parsers, dtypes, exp_value):
+ # GH#35211
+ parser = all_parsers
+ data = """a,a\n1,1"""
+ dtype_dict = {"a": str, **dtypes}
+ # GH#42462
+ dtype_dict_copy = dtype_dict.copy()
+ result = parser.read_csv(StringIO(data), dtype=dtype_dict)
+ expected = DataFrame({"a": ["1"], "a.1": [exp_value]})
+ assert dtype_dict == dtype_dict_copy, "dtype dict changed"
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+def test_dtype_mangle_dup_cols_single_dtype(all_parsers):
+ # GH#42022
+ parser = all_parsers
+ data = """a,a\n1,1"""
+ result = parser.read_csv(StringIO(data), dtype=str)
+ expected = DataFrame({"a": ["1"], "a.1": ["1"]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+def test_dtype_multi_index(all_parsers):
+ # GH 42446
+ parser = all_parsers
+ data = "A,B,B\nX,Y,Z\n1,2,3"
+
+ result = parser.read_csv(
+ StringIO(data),
+ header=list(range(2)),
+ dtype={
+ ("A", "X"): np.int32,
+ ("B", "Y"): np.int32,
+ ("B", "Z"): np.float32,
+ },
+ )
+
+ expected = DataFrame(
+ {
+ ("A", "X"): np.int32([1]),
+ ("B", "Y"): np.int32([2]),
+ ("B", "Z"): np.float32([3]),
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_nullable_int_dtype(all_parsers, any_int_ea_dtype):
+ # GH 25472
+ parser = all_parsers
+ dtype = any_int_ea_dtype
+
+ data = """a,b,c
+,3,5
+1,,6
+2,4,"""
+ expected = DataFrame(
+ {
+ "a": pd.array([pd.NA, 1, 2], dtype=dtype),
+ "b": pd.array([3, pd.NA, 4], dtype=dtype),
+ "c": pd.array([5, 6, pd.NA], dtype=dtype),
+ }
+ )
+ actual = parser.read_csv(StringIO(data), dtype=dtype)
+ tm.assert_frame_equal(actual, expected)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+@pytest.mark.parametrize("default", ["float", "float64"])
+def test_dtypes_defaultdict(all_parsers, default):
+ # GH#41574
+ data = """a,b
+1,2
+"""
+ dtype = defaultdict(lambda: default, a="int64")
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+ expected = DataFrame({"a": [1], "b": 2.0})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+def test_dtypes_defaultdict_mangle_dup_cols(all_parsers):
+ # GH#41574
+ data = """a,b,a,b,b.1
+1,2,3,4,5
+"""
+ dtype = defaultdict(lambda: "float64", a="int64")
+ dtype["b.1"] = "int64"
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+ expected = DataFrame({"a": [1], "b": [2.0], "a.1": [3], "b.2": [4.0], "b.1": [5]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+def test_dtypes_defaultdict_invalid(all_parsers):
+ # GH#41574
+ data = """a,b
+1,2
+"""
+ dtype = defaultdict(lambda: "invalid_dtype", a="int64")
+ parser = all_parsers
+ with pytest.raises(TypeError, match="not understood"):
+ parser.read_csv(StringIO(data), dtype=dtype)
+
+
+def test_dtype_backend(all_parsers):
+ # GH#36712
+
+ parser = all_parsers
+
+ data = """a,b,c,d,e,f,g,h,i,j
+1,2.5,True,a,,,,,12-31-2019,
+3,4.5,False,b,6,7.5,True,a,12-31-2019,
+"""
+ result = parser.read_csv(
+ StringIO(data), dtype_backend="numpy_nullable", parse_dates=["i"]
+ )
+ expected = DataFrame(
+ {
+ "a": pd.Series([1, 3], dtype="Int64"),
+ "b": pd.Series([2.5, 4.5], dtype="Float64"),
+ "c": pd.Series([True, False], dtype="boolean"),
+ "d": pd.Series(["a", "b"], dtype="string"),
+ "e": pd.Series([pd.NA, 6], dtype="Int64"),
+ "f": pd.Series([pd.NA, 7.5], dtype="Float64"),
+ "g": pd.Series([pd.NA, True], dtype="boolean"),
+ "h": pd.Series([pd.NA, "a"], dtype="string"),
+ "i": pd.Series([Timestamp("2019-12-31")] * 2),
+ "j": pd.Series([pd.NA, pd.NA], dtype="Int64"),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dtype_backend_and_dtype(all_parsers):
+ # GH#36712
+
+ parser = all_parsers
+
+ data = """a,b
+1,2.5
+,
+"""
+ result = parser.read_csv(
+ StringIO(data), dtype_backend="numpy_nullable", dtype="float64"
+ )
+ expected = DataFrame({"a": [1.0, np.nan], "b": [2.5, np.nan]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dtype_backend_string(all_parsers, string_storage):
+ # GH#36712
+ with pd.option_context("mode.string_storage", string_storage):
+ parser = all_parsers
+
+ data = """a,b
+a,x
+b,
+"""
+ result = parser.read_csv(StringIO(data), dtype_backend="numpy_nullable")
+
+ expected = DataFrame(
+ {
+ "a": pd.array(["a", "b"], dtype=pd.StringDtype(string_storage)),
+ "b": pd.array(["x", pd.NA], dtype=pd.StringDtype(string_storage)),
+ },
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dtype_backend_ea_dtype_specified(all_parsers):
+ # GH#491496
+ data = """a,b
+1,2
+"""
+ parser = all_parsers
+ result = parser.read_csv(
+ StringIO(data), dtype="Int64", dtype_backend="numpy_nullable"
+ )
+ expected = DataFrame({"a": [1], "b": 2}, dtype="Int64")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dtype_backend_pyarrow(all_parsers, request):
+ # GH#36712
+ pa = pytest.importorskip("pyarrow")
+ parser = all_parsers
+
+ data = """a,b,c,d,e,f,g,h,i,j
+1,2.5,True,a,,,,,12-31-2019,
+3,4.5,False,b,6,7.5,True,a,12-31-2019,
+"""
+ result = parser.read_csv(StringIO(data), dtype_backend="pyarrow", parse_dates=["i"])
+ expected = DataFrame(
+ {
+ "a": pd.Series([1, 3], dtype="int64[pyarrow]"),
+ "b": pd.Series([2.5, 4.5], dtype="float64[pyarrow]"),
+ "c": pd.Series([True, False], dtype="bool[pyarrow]"),
+ "d": pd.Series(["a", "b"], dtype=pd.ArrowDtype(pa.string())),
+ "e": pd.Series([pd.NA, 6], dtype="int64[pyarrow]"),
+ "f": pd.Series([pd.NA, 7.5], dtype="float64[pyarrow]"),
+ "g": pd.Series([pd.NA, True], dtype="bool[pyarrow]"),
+ "h": pd.Series(
+ [pd.NA, "a"],
+ dtype=pd.ArrowDtype(pa.string()),
+ ),
+ "i": pd.Series([Timestamp("2019-12-31")] * 2),
+ "j": pd.Series([pd.NA, pd.NA], dtype="null[pyarrow]"),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_ea_int_avoid_overflow(all_parsers):
+ # GH#32134
+ parser = all_parsers
+ data = """a,b
+1,1
+,1
+1582218195625938945,1
+"""
+ result = parser.read_csv(StringIO(data), dtype={"a": "Int64"})
+ expected = DataFrame(
+ {
+ "a": IntegerArray(
+ np.array([1, 1, 1582218195625938945]), np.array([False, True, False])
+ ),
+ "b": 1,
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_string_inference(all_parsers):
+ # GH#54430
+ dtype = pd.StringDtype(na_value=np.nan)
+
+ data = """a,b
+x,1
+y,2
+,3"""
+ parser = all_parsers
+ with pd.option_context("future.infer_string", True):
+ result = parser.read_csv(StringIO(data))
+
+ expected = DataFrame(
+ {"a": pd.Series(["x", "y", None], dtype=dtype), "b": [1, 2, 3]},
+ columns=pd.Index(["a", "b"], dtype=dtype),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["O", object, "object", np.object_, str, np.str_])
+def test_string_inference_object_dtype(all_parsers, dtype, using_infer_string):
+ # GH#56047
+ data = """a,b
+x,a
+y,a
+z,a"""
+ parser = all_parsers
+ with pd.option_context("future.infer_string", True):
+ result = parser.read_csv(StringIO(data), dtype=dtype)
+
+ expected_dtype = pd.StringDtype(na_value=np.nan) if dtype is str else object
+ expected = DataFrame(
+ {
+ "a": pd.Series(["x", "y", "z"], dtype=expected_dtype),
+ "b": pd.Series(["a", "a", "a"], dtype=expected_dtype),
+ },
+ columns=pd.Index(["a", "b"], dtype=pd.StringDtype(na_value=np.nan)),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ with pd.option_context("future.infer_string", True):
+ result = parser.read_csv(StringIO(data), dtype={"a": dtype})
+
+ expected = DataFrame(
+ {
+ "a": pd.Series(["x", "y", "z"], dtype=expected_dtype),
+ "b": pd.Series(["a", "a", "a"], dtype=pd.StringDtype(na_value=np.nan)),
+ },
+ columns=pd.Index(["a", "b"], dtype=pd.StringDtype(na_value=np.nan)),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_accurate_parsing_of_large_integers(all_parsers):
+ # GH#52505
+ data = """SYMBOL,MOMENT,ID,ID_DEAL
+AAPL,20230301181139587,1925036343869802844,
+AAPL,20230301181139587,2023552585717889863,2023552585717263358
+NVDA,20230301181139587,2023552585717889863,2023552585717263359
+AMC,20230301181139587,2023552585717889863,2023552585717263360
+AMZN,20230301181139587,2023552585717889759,2023552585717263360
+MSFT,20230301181139587,2023552585717889863,2023552585717263361
+NVDA,20230301181139587,2023552585717889827,2023552585717263361"""
+ orders = all_parsers.read_csv(StringIO(data), dtype={"ID_DEAL": pd.Int64Dtype()})
+ assert len(orders.loc[orders["ID_DEAL"] == 2023552585717263358, "ID_DEAL"]) == 1
+ assert len(orders.loc[orders["ID_DEAL"] == 2023552585717263359, "ID_DEAL"]) == 1
+ assert len(orders.loc[orders["ID_DEAL"] == 2023552585717263360, "ID_DEAL"]) == 2
+ assert len(orders.loc[orders["ID_DEAL"] == 2023552585717263361, "ID_DEAL"]) == 2
+
+
+def test_dtypes_with_usecols(all_parsers):
+ # GH#54868
+
+ parser = all_parsers
+ data = """a,b,c
+1,2,3
+4,5,6"""
+
+ result = parser.read_csv(StringIO(data), usecols=["a", "c"], dtype={"a": object})
+ if parser.engine == "pyarrow":
+ values = [1, 4]
+ else:
+ values = ["1", "4"]
+ expected = DataFrame({"a": pd.Series(values, dtype=object), "c": [3, 6]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_index_col_with_dtype_no_rangeindex(all_parsers):
+ data = StringIO("345.5,519.5,0\n519.5,726.5,1")
+ result = all_parsers.read_csv(
+ data,
+ header=None,
+ names=["start", "stop", "bin_id"],
+ dtype={"start": np.float32, "stop": np.float32, "bin_id": np.uint32},
+ index_col="bin_id",
+ ).index
+ expected = pd.Index([0, 1], dtype=np.uint32, name="bin_id")
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/test_empty.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/test_empty.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebc61e7f0ca2bf3a587d9abee30627de83658310
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/dtypes/test_empty.py
@@ -0,0 +1,181 @@
+"""
+Tests dtype specification during parsing
+for all of the parsers defined in parsers.py
+"""
+
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ concat,
+)
+import pandas._testing as tm
+
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_dtype_all_columns_empty(all_parsers):
+ # see gh-12048
+ parser = all_parsers
+ result = parser.read_csv(StringIO("A,B"), dtype=str)
+
+ expected = DataFrame({"A": [], "B": []}, dtype=str)
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_empty_pass_dtype(all_parsers):
+ parser = all_parsers
+
+ data = "one,two"
+ result = parser.read_csv(StringIO(data), dtype={"one": "u1"})
+
+ expected = DataFrame(
+ {"one": np.empty(0, dtype="u1"), "two": np.empty(0, dtype=object)},
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_empty_with_index_pass_dtype(all_parsers):
+ parser = all_parsers
+
+ data = "one,two"
+ result = parser.read_csv(
+ StringIO(data), index_col=["one"], dtype={"one": "u1", 1: "f"}
+ )
+
+ expected = DataFrame(
+ {"two": np.empty(0, dtype="f")}, index=Index([], dtype="u1", name="one")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_empty_with_multi_index_pass_dtype(all_parsers):
+ parser = all_parsers
+
+ data = "one,two,three"
+ result = parser.read_csv(
+ StringIO(data), index_col=["one", "two"], dtype={"one": "u1", 1: "f8"}
+ )
+
+ exp_idx = MultiIndex.from_arrays(
+ [np.empty(0, dtype="u1"), np.empty(0, dtype=np.float64)],
+ names=["one", "two"],
+ )
+ expected = DataFrame({"three": np.empty(0, dtype=object)}, index=exp_idx)
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_empty_with_mangled_column_pass_dtype_by_names(all_parsers):
+ parser = all_parsers
+
+ data = "one,one"
+ result = parser.read_csv(StringIO(data), dtype={"one": "u1", "one.1": "f"})
+
+ expected = DataFrame(
+ {"one": np.empty(0, dtype="u1"), "one.1": np.empty(0, dtype="f")},
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_empty_with_mangled_column_pass_dtype_by_indexes(all_parsers):
+ parser = all_parsers
+
+ data = "one,one"
+ result = parser.read_csv(StringIO(data), dtype={0: "u1", 1: "f"})
+
+ expected = DataFrame(
+ {"one": np.empty(0, dtype="u1"), "one.1": np.empty(0, dtype="f")},
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_empty_with_dup_column_pass_dtype_by_indexes(all_parsers):
+ # see gh-9424
+ parser = all_parsers
+ expected = concat(
+ [Series([], name="one", dtype="u1"), Series([], name="one.1", dtype="f")],
+ axis=1,
+ )
+
+ data = "one,one"
+ result = parser.read_csv(StringIO(data), dtype={0: "u1", 1: "f"})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_empty_with_dup_column_pass_dtype_by_indexes_raises(all_parsers):
+ # see gh-9424
+ parser = all_parsers
+ expected = concat(
+ [Series([], name="one", dtype="u1"), Series([], name="one.1", dtype="f")],
+ axis=1,
+ )
+ expected.index = expected.index.astype(object)
+
+ with pytest.raises(ValueError, match="Duplicate names"):
+ parser.read_csv(StringIO(""), names=["one", "one"], dtype={0: "u1", 1: "f"})
+
+
+@pytest.mark.parametrize(
+ "dtype,expected",
+ [
+ (np.float64, DataFrame(columns=["a", "b"], dtype=np.float64)),
+ (
+ "category",
+ DataFrame({"a": Categorical([]), "b": Categorical([])}),
+ ),
+ (
+ {"a": "category", "b": "category"},
+ DataFrame({"a": Categorical([]), "b": Categorical([])}),
+ ),
+ ("datetime64[ns]", DataFrame(columns=["a", "b"], dtype="datetime64[ns]")),
+ (
+ "timedelta64[ns]",
+ DataFrame(
+ {
+ "a": Series([], dtype="timedelta64[ns]"),
+ "b": Series([], dtype="timedelta64[ns]"),
+ },
+ ),
+ ),
+ (
+ {"a": np.int64, "b": np.int32},
+ DataFrame(
+ {"a": Series([], dtype=np.int64), "b": Series([], dtype=np.int32)},
+ ),
+ ),
+ (
+ {0: np.int64, 1: np.int32},
+ DataFrame(
+ {"a": Series([], dtype=np.int64), "b": Series([], dtype=np.int32)},
+ ),
+ ),
+ (
+ {"a": np.int64, 1: np.int32},
+ DataFrame(
+ {"a": Series([], dtype=np.int64), "b": Series([], dtype=np.int32)},
+ ),
+ ),
+ ],
+)
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_empty_dtype(all_parsers, dtype, expected):
+ # see gh-14712
+ parser = all_parsers
+ data = "a,b"
+
+ result = parser.read_csv(StringIO(data), header=0, dtype=dtype)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_c_parser_only.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_c_parser_only.py
new file mode 100644
index 0000000000000000000000000000000000000000..08e9eae8a851e5e11f86fd4ca0ba942ec764233a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_c_parser_only.py
@@ -0,0 +1,644 @@
+"""
+Tests that apply specifically to the CParser. Unless specifically stated
+as a CParser-specific issue, the goal is to eventually move as many of
+these tests out of this module as soon as the Python parser can accept
+further arguments when parsing.
+"""
+
+from decimal import Decimal
+from io import (
+ BytesIO,
+ StringIO,
+ TextIOWrapper,
+)
+import mmap
+import os
+import tarfile
+
+import numpy as np
+import pytest
+
+from pandas.compat import WASM
+from pandas.errors import (
+ ParserError,
+ ParserWarning,
+)
+import pandas.util._test_decorators as td
+
+from pandas import (
+ DataFrame,
+ concat,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "malformed",
+ ["1\r1\r1\r 1\r 1\r", "1\r1\r1\r 1\r 1\r11\r", "1\r1\r1\r 1\r 1\r11\r1\r"],
+ ids=["words pointer", "stream pointer", "lines pointer"],
+)
+def test_buffer_overflow(c_parser_only, malformed):
+ # see gh-9205: test certain malformed input files that cause
+ # buffer overflows in tokenizer.c
+ msg = "Buffer overflow caught - possible malformed input file."
+ parser = c_parser_only
+
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(malformed))
+
+
+def test_delim_whitespace_custom_terminator(c_parser_only):
+ # See gh-12912
+ data = "a b c~1 2 3~4 5 6~7 8 9"
+ parser = c_parser_only
+
+ df = parser.read_csv(StringIO(data), lineterminator="~", sep=r"\s+")
+ expected = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=["a", "b", "c"])
+ tm.assert_frame_equal(df, expected)
+
+
+def test_dtype_and_names_error(c_parser_only):
+ # see gh-8833: passing both dtype and names
+ # resulting in an error reporting issue
+ parser = c_parser_only
+ data = """
+1.0 1
+2.0 2
+3.0 3
+"""
+ # base cases
+ result = parser.read_csv(StringIO(data), sep=r"\s+", header=None)
+ expected = DataFrame([[1.0, 1], [2.0, 2], [3.0, 3]])
+ tm.assert_frame_equal(result, expected)
+
+ result = parser.read_csv(StringIO(data), sep=r"\s+", header=None, names=["a", "b"])
+ expected = DataFrame([[1.0, 1], [2.0, 2], [3.0, 3]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+ # fallback casting
+ result = parser.read_csv(
+ StringIO(data), sep=r"\s+", header=None, names=["a", "b"], dtype={"a": np.int32}
+ )
+ expected = DataFrame([[1, 1], [2, 2], [3, 3]], columns=["a", "b"])
+ expected["a"] = expected["a"].astype(np.int32)
+ tm.assert_frame_equal(result, expected)
+
+ data = """
+1.0 1
+nan 2
+3.0 3
+"""
+ # fallback casting, but not castable
+ if not WASM: # no fp exception support in wasm
+ with pytest.raises(ValueError, match="cannot safely convert"):
+ with tm.assert_produces_warning(RuntimeWarning, check_stacklevel=False):
+ parser.read_csv(
+ StringIO(data),
+ sep=r"\s+",
+ header=None,
+ names=["a", "b"],
+ dtype={"a": np.int32},
+ )
+
+
+@pytest.mark.parametrize(
+ "match,kwargs",
+ [
+ # For each of these cases, all of the dtypes are valid, just unsupported.
+ (
+ (
+ "the dtype datetime64 is not supported for parsing, "
+ "pass this column using parse_dates instead"
+ ),
+ {"dtype": {"A": "datetime64", "B": "float64"}},
+ ),
+ (
+ (
+ "the dtype datetime64 is not supported for parsing, "
+ "pass this column using parse_dates instead"
+ ),
+ {"dtype": {"A": "datetime64", "B": "float64"}, "parse_dates": ["B"]},
+ ),
+ (
+ "the dtype timedelta64 is not supported for parsing",
+ {"dtype": {"A": "timedelta64", "B": "float64"}},
+ ),
+ (
+ f"the dtype {tm.ENDIAN}U8 is not supported for parsing",
+ {"dtype": {"A": "U8"}},
+ ),
+ ],
+ ids=["dt64-0", "dt64-1", "td64", f"{tm.ENDIAN}U8"],
+)
+def test_unsupported_dtype(c_parser_only, match, kwargs, temp_file):
+ parser = c_parser_only
+ df = DataFrame(
+ np.random.default_rng(2).random((5, 2)),
+ columns=list("AB"),
+ index=["1A", "1B", "1C", "1D", "1E"],
+ )
+
+ df.to_csv(temp_file)
+
+ with pytest.raises(TypeError, match=match):
+ parser.read_csv(temp_file, index_col=0, **kwargs)
+
+
+@td.skip_if_32bit
+@pytest.mark.slow
+# test numbers between 1 and 2
+@pytest.mark.parametrize("num", np.linspace(1.0, 2.0, num=21))
+def test_precise_conversion(c_parser_only, num):
+ parser = c_parser_only
+
+ normal_errors = []
+ precise_errors = []
+
+ def error(val: float, actual_val: Decimal) -> Decimal:
+ return abs(Decimal(f"{val:.100}") - actual_val)
+
+ # 25 decimal digits of precision
+ text = f"a\n{num:.25}"
+
+ normal_val = float(
+ parser.read_csv(StringIO(text), float_precision="legacy")["a"][0]
+ )
+ precise_val = float(parser.read_csv(StringIO(text), float_precision="high")["a"][0])
+ roundtrip_val = float(
+ parser.read_csv(StringIO(text), float_precision="round_trip")["a"][0]
+ )
+ actual_val = Decimal(text[2:])
+
+ normal_errors.append(error(normal_val, actual_val))
+ precise_errors.append(error(precise_val, actual_val))
+
+ # round-trip should match float()
+ assert roundtrip_val == float(text[2:])
+
+ assert sum(precise_errors) <= sum(normal_errors)
+ assert max(precise_errors) <= max(normal_errors)
+
+
+def test_usecols_dtypes(c_parser_only, using_infer_string):
+ parser = c_parser_only
+ data = """\
+1,2,3
+4,5,6
+7,8,9
+10,11,12"""
+
+ result = parser.read_csv(
+ StringIO(data),
+ usecols=(0, 1, 2),
+ names=("a", "b", "c"),
+ header=None,
+ converters={"a": str},
+ dtype={"b": int, "c": float},
+ )
+ result2 = parser.read_csv(
+ StringIO(data),
+ usecols=(0, 2),
+ names=("a", "b", "c"),
+ header=None,
+ converters={"a": str},
+ dtype={"b": int, "c": float},
+ )
+
+ if using_infer_string:
+ assert (result.dtypes == ["string", int, float]).all()
+ assert (result2.dtypes == ["string", float]).all()
+ else:
+ assert (result.dtypes == [object, int, float]).all()
+ assert (result2.dtypes == [object, float]).all()
+
+
+def test_disable_bool_parsing(c_parser_only):
+ # see gh-2090
+
+ parser = c_parser_only
+ data = """A,B,C
+Yes,No,Yes
+No,Yes,Yes
+Yes,,Yes
+No,No,No"""
+
+ result = parser.read_csv(StringIO(data), dtype=object)
+ assert (result.dtypes == object).all()
+
+ result = parser.read_csv(StringIO(data), dtype=object, na_filter=False)
+ assert result["B"][2] == ""
+
+
+def test_custom_lineterminator(c_parser_only):
+ parser = c_parser_only
+ data = "a,b,c~1,2,3~4,5,6"
+
+ result = parser.read_csv(StringIO(data), lineterminator="~")
+ expected = parser.read_csv(StringIO(data.replace("~", "\n")))
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_parse_ragged_csv(c_parser_only):
+ parser = c_parser_only
+ data = """1,2,3
+1,2,3,4
+1,2,3,4,5
+1,2
+1,2,3,4"""
+
+ nice_data = """1,2,3,,
+1,2,3,4,
+1,2,3,4,5
+1,2,,,
+1,2,3,4,"""
+ result = parser.read_csv(
+ StringIO(data), header=None, names=["a", "b", "c", "d", "e"]
+ )
+
+ expected = parser.read_csv(
+ StringIO(nice_data), header=None, names=["a", "b", "c", "d", "e"]
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ # too many columns, cause segfault if not careful
+ data = "1,2\n3,4,5"
+
+ result = parser.read_csv(StringIO(data), header=None, names=range(50))
+ expected = parser.read_csv(StringIO(data), header=None, names=range(3)).reindex(
+ columns=range(50)
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_tokenize_CR_with_quoting(c_parser_only):
+ # see gh-3453
+ parser = c_parser_only
+ data = ' a,b,c\r"a,b","e,d","f,f"'
+
+ result = parser.read_csv(StringIO(data), header=None)
+ expected = parser.read_csv(StringIO(data.replace("\r", "\n")), header=None)
+ tm.assert_frame_equal(result, expected)
+
+ result = parser.read_csv(StringIO(data))
+ expected = parser.read_csv(StringIO(data.replace("\r", "\n")))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.slow
+@pytest.mark.parametrize("count", [3 * 2**n for n in range(6)])
+def test_grow_boundary_at_cap(c_parser_only, count):
+ # See gh-12494
+ #
+ # Cause of error was that the C parser
+ # was not increasing the buffer size when
+ # the desired space would fill the buffer
+ # to capacity, which would later cause a
+ # buffer overflow error when checking the
+ # EOF terminator of the CSV stream.
+ # 3 * 2^n commas was observed to break the parser
+ parser = c_parser_only
+
+ with StringIO("," * count) as s:
+ expected = DataFrame(columns=[f"Unnamed: {i}" for i in range(count + 1)])
+ df = parser.read_csv(s)
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.slow
+@pytest.mark.parametrize("encoding", [None, "utf-8"])
+def test_parse_trim_buffers(c_parser_only, encoding):
+ # This test is part of a bugfix for gh-13703. It attempts to
+ # to stress the system memory allocator, to cause it to move the
+ # stream buffer and either let the OS reclaim the region, or let
+ # other memory requests of parser otherwise modify the contents
+ # of memory space, where it was formally located.
+ # This test is designed to cause a `segfault` with unpatched
+ # `tokenizer.c`. Sometimes the test fails on `segfault`, other
+ # times it fails due to memory corruption, which causes the
+ # loaded DataFrame to differ from the expected one.
+
+ # Also force 'utf-8' encoding, so that `_string_convert` would take
+ # a different execution branch.
+
+ parser = c_parser_only
+
+ # Generate a large mixed-type CSV file on-the-fly (one record is
+ # approx 1.5KiB).
+ record_ = (
+ """9999-9,99:99,,,,ZZ,ZZ,,,ZZZ-ZZZZ,.Z-ZZZZ,-9.99,,,9.99,Z"""
+ """ZZZZ,,-99,9,ZZZ-ZZZZ,ZZ-ZZZZ,,9.99,ZZZ-ZZZZZ,ZZZ-ZZZZZ,"""
+ """ZZZ-ZZZZ,ZZZ-ZZZZ,ZZZ-ZZZZ,ZZZ-ZZZZ,ZZZ-ZZZZ,ZZZ-ZZZZ,9"""
+ """99,ZZZ-ZZZZ,,ZZ-ZZZZ,,,,,ZZZZ,ZZZ-ZZZZZ,ZZZ-ZZZZ,,,9,9,"""
+ """9,9,99,99,999,999,ZZZZZ,ZZZ-ZZZZZ,ZZZ-ZZZZ,9,ZZ-ZZZZ,9."""
+ """99,ZZ-ZZZZ,ZZ-ZZZZ,,,,ZZZZ,,,ZZ,ZZ,,,,,,,,,,,,,9,,,999."""
+ """99,999.99,,,ZZZZZ,,,Z9,,,,,,,ZZZ,ZZZ,,,,,,,,,,,ZZZZZ,ZZ"""
+ """ZZZ,ZZZ-ZZZZZZ,ZZZ-ZZZZZZ,ZZ-ZZZZ,ZZ-ZZZZ,ZZ-ZZZZ,ZZ-ZZ"""
+ """ZZ,,,999999,999999,ZZZ,ZZZ,,,ZZZ,ZZZ,999.99,999.99,,,,Z"""
+ """ZZ-ZZZ,ZZZ-ZZZ,-9.99,-9.99,9,9,,99,,9.99,9.99,9,9,9.99,"""
+ """9.99,,,,9.99,9.99,,99,,99,9.99,9.99,,,ZZZ,ZZZ,,999.99,,"""
+ """999.99,ZZZ,ZZZ-ZZZZ,ZZZ-ZZZZ,,,ZZZZZ,ZZZZZ,ZZZ,ZZZ,9,9,"""
+ """,,,,,ZZZ-ZZZZ,ZZZ999Z,,,999.99,,999.99,ZZZ-ZZZZ,,,9.999"""
+ """,9.999,9.999,9.999,-9.999,-9.999,-9.999,-9.999,9.999,9."""
+ """999,9.999,9.999,9.999,9.999,9.999,9.999,99999,ZZZ-ZZZZ,"""
+ """,9.99,ZZZ,,,,,,,,ZZZ,,,,,9,,,,9,,,,,,,,,,ZZZ-ZZZZ,ZZZ-Z"""
+ """ZZZ,,ZZZZZ,ZZZZZ,ZZZZZ,ZZZZZ,,,9.99,,ZZ-ZZZZ,ZZ-ZZZZ,ZZ"""
+ """,999,,,,ZZ-ZZZZ,ZZZ,ZZZ,ZZZ-ZZZZ,ZZZ-ZZZZ,,,99.99,99.99"""
+ """,,,9.99,9.99,9.99,9.99,ZZZ-ZZZZ,,,ZZZ-ZZZZZ,,,,,-9.99,-"""
+ """9.99,-9.99,-9.99,,,,,,,,,ZZZ-ZZZZ,,9,9.99,9.99,99ZZ,,-9"""
+ """.99,-9.99,ZZZ-ZZZZ,,,,,,,ZZZ-ZZZZ,9.99,9.99,9999,,,,,,,"""
+ """,,,-9.9,Z/Z-ZZZZ,999.99,9.99,,999.99,ZZ-ZZZZ,ZZ-ZZZZ,9."""
+ """99,9.99,9.99,9.99,9.99,9.99,,ZZZ-ZZZZZ,ZZZ-ZZZZZ,ZZZ-ZZ"""
+ """ZZZ,ZZZ-ZZZZZ,ZZZ-ZZZZZ,ZZZ,ZZZ,ZZZ,ZZZ,9.99,,,-9.99,ZZ"""
+ """-ZZZZ,-999.99,,-9999,,999.99,,,,999.99,99.99,,,ZZ-ZZZZZ"""
+ """ZZZ,ZZ-ZZZZ-ZZZZZZZ,,,,ZZ-ZZ-ZZZZZZZZ,ZZZZZZZZ,ZZZ-ZZZZ"""
+ """,9999,999.99,ZZZ-ZZZZ,-9.99,-9.99,ZZZ-ZZZZ,99:99:99,,99"""
+ """,99,,9.99,,-99.99,,,,,,9.99,ZZZ-ZZZZ,-9.99,-9.99,9.99,9"""
+ """.99,,ZZZ,,,,,,,ZZZ,ZZZ,,,,,"""
+ )
+
+ # Set the number of lines so that a call to `parser_trim_buffers`
+ # is triggered: after a couple of full chunks are consumed a
+ # relatively small 'residual' chunk would cause reallocation
+ # within the parser.
+ chunksize, n_lines = 128, 2 * 128 + 15
+ csv_data = "\n".join([record_] * n_lines) + "\n"
+
+ # We will use StringIO to load the CSV from this text buffer.
+ # pd.read_csv() will iterate over the file in chunks and will
+ # finally read a residual chunk of really small size.
+
+ # Generate the expected output: manually create the dataframe
+ # by splitting by comma and repeating the `n_lines` times.
+ row = tuple(val_ if val_ else np.nan for val_ in record_.split(","))
+ expected = DataFrame(
+ [row for _ in range(n_lines)], dtype=object, columns=None, index=None
+ )
+
+ # Iterate over the CSV file in chunks of `chunksize` lines
+ with parser.read_csv(
+ StringIO(csv_data),
+ header=None,
+ dtype=object,
+ chunksize=chunksize,
+ encoding=encoding,
+ ) as chunks_:
+ result = concat(chunks_, axis=0, ignore_index=True)
+
+ # Check for data corruption if there was no segfault
+ tm.assert_frame_equal(result, expected)
+
+
+def test_internal_null_byte(c_parser_only):
+ # see gh-14012
+ #
+ # The null byte ('\x00') should not be used as a
+ # true line terminator, escape character, or comment
+ # character, only as a placeholder to indicate that
+ # none was specified.
+ #
+ # This test should be moved to test_common.py ONLY when
+ # Python's csv class supports parsing '\x00'.
+ parser = c_parser_only
+
+ names = ["a", "b", "c"]
+ data = "1,2,3\n4,\x00,6\n7,8,9"
+ expected = DataFrame([[1, 2.0, 3], [4, np.nan, 6], [7, 8, 9]], columns=names)
+
+ result = parser.read_csv(StringIO(data), names=names)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_nrows_large(c_parser_only):
+ # gh-7626 - Read only nrows of data in for large inputs (>262144b)
+ parser = c_parser_only
+ header_narrow = "\t".join(["COL_HEADER_" + str(i) for i in range(10)]) + "\n"
+ data_narrow = "\t".join(["somedatasomedatasomedata1" for _ in range(10)]) + "\n"
+ header_wide = "\t".join(["COL_HEADER_" + str(i) for i in range(15)]) + "\n"
+ data_wide = "\t".join(["somedatasomedatasomedata2" for _ in range(15)]) + "\n"
+ test_input = header_narrow + data_narrow * 1050 + header_wide + data_wide * 2
+
+ df = parser.read_csv(StringIO(test_input), sep="\t", nrows=1010)
+
+ assert df.size == 1010 * 10
+
+
+def test_float_precision_round_trip_with_text(c_parser_only):
+ # see gh-15140
+ parser = c_parser_only
+ df = parser.read_csv(StringIO("a"), header=None, float_precision="round_trip")
+ tm.assert_frame_equal(df, DataFrame({0: ["a"]}))
+
+
+def test_large_difference_in_columns(c_parser_only):
+ # see gh-14125
+ parser = c_parser_only
+
+ count = 10000
+ large_row = ("X," * count)[:-1] + "\n"
+ normal_row = "XXXXXX XXXXXX,111111111111111\n"
+ test_input = (large_row + normal_row * 6)[:-1]
+
+ result = parser.read_csv(StringIO(test_input), header=None, usecols=[0])
+ rows = test_input.split("\n")
+
+ expected = DataFrame([row.split(",")[0] for row in rows])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_data_after_quote(c_parser_only):
+ # see gh-15910
+ parser = c_parser_only
+
+ data = 'a\n1\n"b"a'
+ result = parser.read_csv(StringIO(data))
+
+ expected = DataFrame({"a": ["1", "ba"]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_comment_whitespace_delimited(c_parser_only):
+ parser = c_parser_only
+ test_input = """\
+1 2
+2 2 3
+3 2 3 # 3 fields
+4 2 3# 3 fields
+5 2 # 2 fields
+6 2# 2 fields
+7 # 1 field, NaN
+8# 1 field, NaN
+9 2 3 # skipped line
+# comment"""
+ with tm.assert_produces_warning(
+ ParserWarning, match="Skipping line", check_stacklevel=False
+ ):
+ df = parser.read_csv(
+ StringIO(test_input),
+ comment="#",
+ header=None,
+ delimiter="\\s+",
+ skiprows=0,
+ on_bad_lines="warn",
+ )
+ expected = DataFrame([[1, 2], [5, 2], [6, 2], [7, np.nan], [8, np.nan]])
+ tm.assert_frame_equal(df, expected)
+
+
+def test_file_like_no_next(c_parser_only):
+ # gh-16530: the file-like need not have a "next" or "__next__"
+ # attribute despite having an "__iter__" attribute.
+ #
+ # NOTE: This is only true for the C engine, not Python engine.
+ class NoNextBuffer(StringIO):
+ def __next__(self):
+ raise AttributeError("No next method")
+
+ next = __next__
+
+ parser = c_parser_only
+ data = "a\n1"
+
+ expected = DataFrame({"a": [1]})
+ result = parser.read_csv(NoNextBuffer(data))
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_buffer_rd_bytes_bad_unicode(c_parser_only):
+ # see gh-22748
+ t = BytesIO(b"\xb0")
+ t = TextIOWrapper(t, encoding="UTF-8", errors="surrogateescape")
+ msg = "'utf-8' codec can't encode character"
+ with pytest.raises(UnicodeError, match=msg):
+ c_parser_only.read_csv(t, encoding="UTF-8")
+
+
+@pytest.mark.parametrize("tar_suffix", [".tar", ".tar.gz"])
+def test_read_tarfile(c_parser_only, csv_dir_path, tar_suffix):
+ # see gh-16530
+ #
+ # Unfortunately, Python's CSV library can't handle
+ # tarfile objects (expects string, not bytes when
+ # iterating through a file-like).
+ parser = c_parser_only
+ tar_path = os.path.join(csv_dir_path, "tar_csv" + tar_suffix)
+
+ with tarfile.open(tar_path, "r") as tar:
+ data_file = tar.extractfile("tar_data.csv")
+
+ out = parser.read_csv(data_file)
+ expected = DataFrame({"a": [1]})
+ tm.assert_frame_equal(out, expected)
+
+
+def test_chunk_whitespace_on_boundary(c_parser_only):
+ # see gh-9735: this issue is C parser-specific (bug when
+ # parsing whitespace and characters at chunk boundary)
+ #
+ # This test case has a field too large for the Python parser / CSV library.
+ parser = c_parser_only
+
+ chunk1 = "a" * (1024 * 256 - 2) + "\na"
+ chunk2 = "\n a"
+ result = parser.read_csv(StringIO(chunk1 + chunk2), header=None)
+
+ expected = DataFrame(["a" * (1024 * 256 - 2), "a", " a"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.skipif(WASM, reason="limited file system access on WASM")
+def test_file_handles_mmap(c_parser_only, csv1):
+ # gh-14418
+ #
+ # Don't close user provided file handles.
+ parser = c_parser_only
+
+ with open(csv1, encoding="utf-8") as f:
+ with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as m:
+ parser.read_csv(m)
+ assert not m.closed
+
+
+def test_file_binary_mode(c_parser_only, temp_file):
+ # see gh-23779
+ parser = c_parser_only
+ expected = DataFrame([[1, 2, 3], [4, 5, 6]])
+
+ path = temp_file
+ with open(path, "w", encoding="utf-8") as f:
+ f.write("1,2,3\n4,5,6")
+
+ with open(path, "rb") as f:
+ result = parser.read_csv(f, header=None)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_unix_style_breaks(c_parser_only, temp_file):
+ # GH 11020
+ parser = c_parser_only
+ path = temp_file
+ with open(path, "w", newline="\n", encoding="utf-8") as f:
+ f.write("blah\n\ncol_1,col_2,col_3\n\n")
+ result = parser.read_csv(path, skiprows=2, encoding="utf-8", engine="c")
+ expected = DataFrame(columns=["col_1", "col_2", "col_3"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("float_precision", [None, "legacy", "high", "round_trip"])
+@pytest.mark.parametrize(
+ "data,thousands,decimal",
+ [
+ (
+ """A|B|C
+1|2,334.01|5
+10|13|10.
+""",
+ ",",
+ ".",
+ ),
+ (
+ """A|B|C
+1|2.334,01|5
+10|13|10,
+""",
+ ".",
+ ",",
+ ),
+ ],
+)
+def test_1000_sep_with_decimal(
+ c_parser_only, data, thousands, decimal, float_precision
+):
+ parser = c_parser_only
+ expected = DataFrame({"A": [1, 10], "B": [2334.01, 13], "C": [5, 10.0]})
+
+ result = parser.read_csv(
+ StringIO(data),
+ sep="|",
+ thousands=thousands,
+ decimal=decimal,
+ float_precision=float_precision,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_float_precision_options(c_parser_only):
+ # GH 17154, 36228
+ parser = c_parser_only
+ s = "foo\n243.164\n"
+ df = parser.read_csv(StringIO(s))
+ df2 = parser.read_csv(StringIO(s), float_precision="high")
+
+ tm.assert_frame_equal(df, df2)
+
+ df3 = parser.read_csv(StringIO(s), float_precision="legacy")
+
+ assert not df.iloc[0, 0] == df3.iloc[0, 0]
+
+ msg = "Unrecognized float_precision option: junk"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(s), float_precision="junk")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_comment.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_comment.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba27b170aecdcb0e7492764c025aea219b390559
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_comment.py
@@ -0,0 +1,214 @@
+"""
+Tests that comments are properly handled during parsing
+for all of the parsers defined in parsers.py
+"""
+
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("na_values", [None, ["NaN"]])
+def test_comment(all_parsers, na_values):
+ parser = all_parsers
+ data = """A,B,C
+1,2.,4.#hello world
+5.,NaN,10.0
+"""
+ expected = DataFrame(
+ [[1.0, 2.0, 4.0], [5.0, np.nan, 10.0]], columns=["A", "B", "C"]
+ )
+ if parser.engine == "pyarrow":
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), comment="#", na_values=na_values)
+ return
+ result = parser.read_csv(StringIO(data), comment="#", na_values=na_values)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("read_kwargs", [{}, {"lineterminator": "*"}, {"sep": r"\s+"}])
+def test_line_comment(all_parsers, read_kwargs):
+ parser = all_parsers
+ data = """# empty
+A,B,C
+1,2.,4.#hello world
+#ignore this line
+5.,NaN,10.0
+"""
+ if read_kwargs.get("sep"):
+ data = data.replace(",", " ")
+ elif read_kwargs.get("lineterminator"):
+ data = data.replace("\n", read_kwargs.get("lineterminator"))
+
+ read_kwargs["comment"] = "#"
+ if parser.engine == "pyarrow":
+ if "lineterminator" in read_kwargs:
+ msg = (
+ "The 'lineterminator' option is not supported with the 'pyarrow' engine"
+ )
+ else:
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), **read_kwargs)
+ return
+ elif parser.engine == "python" and read_kwargs.get("lineterminator"):
+ msg = r"Custom line terminators not supported in python parser \(yet\)"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), **read_kwargs)
+ return
+
+ result = parser.read_csv(StringIO(data), **read_kwargs)
+ expected = DataFrame(
+ [[1.0, 2.0, 4.0], [5.0, np.nan, 10.0]], columns=["A", "B", "C"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_comment_skiprows(all_parsers):
+ parser = all_parsers
+ data = """# empty
+random line
+# second empty line
+1,2,3
+A,B,C
+1,2.,4.
+5.,NaN,10.0
+"""
+ # This should ignore the first four lines (including comments).
+ expected = DataFrame(
+ [[1.0, 2.0, 4.0], [5.0, np.nan, 10.0]], columns=["A", "B", "C"]
+ )
+ if parser.engine == "pyarrow":
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), comment="#", skiprows=4)
+ return
+
+ result = parser.read_csv(StringIO(data), comment="#", skiprows=4)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_comment_header(all_parsers):
+ parser = all_parsers
+ data = """# empty
+# second empty line
+1,2,3
+A,B,C
+1,2.,4.
+5.,NaN,10.0
+"""
+ # Header should begin at the second non-comment line.
+ expected = DataFrame(
+ [[1.0, 2.0, 4.0], [5.0, np.nan, 10.0]], columns=["A", "B", "C"]
+ )
+ if parser.engine == "pyarrow":
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), comment="#", header=1)
+ return
+ result = parser.read_csv(StringIO(data), comment="#", header=1)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_comment_skiprows_header(all_parsers):
+ parser = all_parsers
+ data = """# empty
+# second empty line
+# third empty line
+X,Y,Z
+1,2,3
+A,B,C
+1,2.,4.
+5.,NaN,10.0
+"""
+ # Skiprows should skip the first 4 lines (including comments),
+ # while header should start from the second non-commented line,
+ # starting with line 5.
+ expected = DataFrame(
+ [[1.0, 2.0, 4.0], [5.0, np.nan, 10.0]], columns=["A", "B", "C"]
+ )
+ if parser.engine == "pyarrow":
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
+ return
+
+ result = parser.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("comment_char", ["#", "~", "&", "^", "*", "@"])
+def test_custom_comment_char(all_parsers, comment_char):
+ parser = all_parsers
+ data = "a,b,c\n1,2,3#ignore this!\n4,5,6#ignorethistoo"
+
+ if parser.engine == "pyarrow":
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data.replace("#", comment_char)), comment=comment_char
+ )
+ return
+ result = parser.read_csv(
+ StringIO(data.replace("#", comment_char)), comment=comment_char
+ )
+
+ expected = DataFrame([[1, 2, 3], [4, 5, 6]], columns=["a", "b", "c"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("header", ["infer", None])
+def test_comment_first_line(all_parsers, header):
+ # see gh-4623
+ parser = all_parsers
+ data = "# notes\na,b,c\n# more notes\n1,2,3"
+
+ if header is None:
+ expected = DataFrame({0: ["a", "1"], 1: ["b", "2"], 2: ["c", "3"]})
+ else:
+ expected = DataFrame([[1, 2, 3]], columns=["a", "b", "c"])
+
+ if parser.engine == "pyarrow":
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), comment="#", header=header)
+ return
+ result = parser.read_csv(StringIO(data), comment="#", header=header)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_comment_char_in_default_value(all_parsers, request):
+ # GH#34002
+ if all_parsers.engine == "c":
+ reason = "see gh-34002: works on the python engine but not the c engine"
+ # NA value containing comment char is interpreted as comment
+ request.applymarker(pytest.mark.xfail(reason=reason, raises=AssertionError))
+ parser = all_parsers
+
+ data = (
+ "# this is a comment\n"
+ "col1,col2,col3,col4\n"
+ "1,2,3,4#inline comment\n"
+ "4,5#,6,10\n"
+ "7,8,#N/A,11\n"
+ )
+ if parser.engine == "pyarrow":
+ msg = "The 'comment' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), comment="#", na_values="#N/A")
+ return
+ result = parser.read_csv(StringIO(data), comment="#", na_values="#N/A")
+ expected = DataFrame(
+ {
+ "col1": [1, 4, 7],
+ "col2": [2, 5, 8],
+ "col3": [3.0, np.nan, np.nan],
+ "col4": [4.0, np.nan, 11.0],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_compression.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_compression.py
new file mode 100644
index 0000000000000000000000000000000000000000..9db820cc1566c78d9069f0d8719e65380a2b9e52
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_compression.py
@@ -0,0 +1,214 @@
+"""
+Tests compressed data parsing functionality for all
+of the parsers defined in parsers.py
+"""
+
+import os
+from pathlib import Path
+import tarfile
+import zipfile
+
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.fixture(params=[True, False])
+def buffer(request):
+ return request.param
+
+
+@pytest.fixture
+def parser_and_data(all_parsers, csv1):
+ parser = all_parsers
+
+ with open(csv1, "rb") as f:
+ data = f.read()
+ expected = parser.read_csv(csv1)
+
+ return parser, data, expected
+
+
+@pytest.mark.parametrize("compression", ["zip", "infer", "zip2"])
+def test_zip(tmp_path, parser_and_data, compression):
+ parser, data, expected = parser_and_data
+
+ path = tmp_path / "test_file.zip"
+ with zipfile.ZipFile(path, mode="w") as tmp:
+ tmp.writestr("test_file", data)
+
+ if compression == "zip2":
+ with open(path, "rb") as f:
+ result = parser.read_csv(f, compression="zip")
+ else:
+ result = parser.read_csv(path, compression=compression)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("compression", ["zip", "infer"])
+def test_zip_error_multiple_files(tmp_path, parser_and_data, compression):
+ parser, data, expected = parser_and_data
+
+ path = tmp_path / "combined_zip.zip"
+ inner_file_names = ["test_file", "second_file"]
+
+ with zipfile.ZipFile(path, mode="w") as tmp:
+ for file_name in inner_file_names:
+ tmp.writestr(file_name, data)
+
+ with pytest.raises(ValueError, match="Multiple files"):
+ parser.read_csv(path, compression=compression)
+
+
+def test_zip_error_no_files(tmp_path, parser_and_data):
+ parser, _, _ = parser_and_data
+
+ path = tmp_path / "test_file.zip"
+ with zipfile.ZipFile(path, mode="w"):
+ pass
+
+ with pytest.raises(ValueError, match="Zero files"):
+ parser.read_csv(path, compression="zip")
+
+
+def test_zip_error_invalid_zip(tmp_path, parser_and_data):
+ parser, _, _ = parser_and_data
+
+ path = tmp_path / "invalid_file.zip"
+ path.touch()
+ with open(path, "rb") as f:
+ with pytest.raises(zipfile.BadZipFile, match="File is not a zip file"):
+ parser.read_csv(f, compression="zip")
+
+
+@pytest.mark.parametrize("filename", [None, "test.{ext}"])
+def test_compression(
+ tmp_path,
+ request,
+ parser_and_data,
+ compression_only,
+ buffer,
+ filename,
+ compression_to_extension,
+):
+ parser, data, expected = parser_and_data
+ compress_type = compression_only
+
+ ext = compression_to_extension[compress_type]
+ filename = filename if filename is None else filename.format(ext=ext)
+
+ if filename and buffer:
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="Cannot deduce compression from buffer of compressed data."
+ )
+ )
+
+ path = tmp_path / filename if filename else tmp_path / "test_file"
+ tm.write_to_compressed(compress_type, path, data)
+ compression = "infer" if filename else compress_type
+
+ if buffer:
+ with open(path, "rb") as f:
+ result = parser.read_csv(f, compression=compression)
+ else:
+ result = parser.read_csv(path, compression=compression)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("ext", [None, "gz", "bz2"])
+def test_infer_compression(all_parsers, csv1, buffer, ext):
+ # see gh-9770
+ parser = all_parsers
+ kwargs = {"index_col": 0, "parse_dates": True}
+
+ expected = parser.read_csv(csv1, **kwargs)
+ kwargs["compression"] = "infer"
+
+ if buffer:
+ with open(csv1, encoding="utf-8") as f:
+ result = parser.read_csv(f, **kwargs)
+ else:
+ ext = "." + ext if ext else ""
+ result = parser.read_csv(csv1 + ext, **kwargs)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_compression_utf_encoding(all_parsers, csv_dir_path, utf_value, encoding_fmt):
+ # see gh-18071, gh-24130
+ parser = all_parsers
+ encoding = encoding_fmt.format(utf_value)
+ path = os.path.join(csv_dir_path, f"utf{utf_value}_ex_small.zip")
+
+ result = parser.read_csv(path, encoding=encoding, compression="zip", sep="\t")
+ expected = DataFrame(
+ {
+ "Country": ["Venezuela", "Venezuela"],
+ "Twitter": ["Hugo Chávez Frías", "Henrique Capriles R."],
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("invalid_compression", ["sfark", "bz3", "zipper"])
+def test_invalid_compression(all_parsers, invalid_compression):
+ parser = all_parsers
+ compress_kwargs = {"compression": invalid_compression}
+
+ msg = f"Unrecognized compression type: {invalid_compression}"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv("test_file.zip", **compress_kwargs)
+
+
+def test_compression_tar_archive(all_parsers, csv_dir_path):
+ parser = all_parsers
+ path = os.path.join(csv_dir_path, "tar_csv.tar.gz")
+ df = parser.read_csv(path)
+ assert list(df.columns) == ["a"]
+
+
+def test_ignore_compression_extension(tmp_path, all_parsers):
+ parser = all_parsers
+ df = DataFrame({"a": [0, 1]})
+
+ path_csv = tmp_path / "test.csv"
+ path_zip = tmp_path / "test.csv.zip"
+ # make sure to create un-compressed file with zip extension
+ df.to_csv(path_csv, index=False)
+ Path(path_zip).write_text(
+ Path(path_csv).read_text(encoding="utf-8"), encoding="utf-8"
+ )
+
+ tm.assert_frame_equal(parser.read_csv(path_zip, compression=None), df)
+
+
+def test_writes_tar_gz(tmp_path, all_parsers):
+ parser = all_parsers
+ data = DataFrame(
+ {
+ "Country": ["Venezuela", "Venezuela"],
+ "Twitter": ["Hugo Chávez Frías", "Henrique Capriles R."],
+ }
+ )
+ tar_path = tmp_path / "test.tar.gz"
+ data.to_csv(tar_path, index=False)
+
+ # test that read_csv infers .tar.gz to gzip:
+ tm.assert_frame_equal(parser.read_csv(tar_path), data)
+
+ # test that file is indeed gzipped:
+ with tarfile.open(tar_path, "r:gz") as tar:
+ result = parser.read_csv(
+ tar.extractfile(tar.getnames()[0]), compression="infer"
+ )
+ tm.assert_frame_equal(result, data)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_concatenate_chunks.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_concatenate_chunks.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e418faeff8e03ff5b524085a78cc94a23518ae7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_concatenate_chunks.py
@@ -0,0 +1,38 @@
+import numpy as np
+import pytest
+
+from pandas.errors import DtypeWarning
+
+import pandas._testing as tm
+from pandas.core.arrays import ArrowExtensionArray
+
+from pandas.io.parsers.c_parser_wrapper import _concatenate_chunks
+
+
+def test_concatenate_chunks_pyarrow():
+ # GH#51876
+ pa = pytest.importorskip("pyarrow")
+ chunks = [
+ {0: ArrowExtensionArray(pa.array([1.5, 2.5]))},
+ {0: ArrowExtensionArray(pa.array([1, 2]))},
+ ]
+ result = _concatenate_chunks(chunks, ["column_0", "column_1"])
+ expected = ArrowExtensionArray(pa.array([1.5, 2.5, 1.0, 2.0]))
+ tm.assert_extension_array_equal(result[0], expected)
+
+
+def test_concatenate_chunks_pyarrow_strings():
+ # GH#51876
+ pa = pytest.importorskip("pyarrow")
+ chunks = [
+ {0: ArrowExtensionArray(pa.array([1.5, 2.5]))},
+ {0: ArrowExtensionArray(pa.array(["a", "b"]))},
+ ]
+ with tm.assert_produces_warning(
+ DtypeWarning, match="Columns \\(0: column_0\\) have mixed types"
+ ):
+ result = _concatenate_chunks(chunks, ["column_0", "column_1"])
+ expected = np.concatenate(
+ [np.array([1.5, 2.5], dtype=object), np.array(["a", "b"])]
+ )
+ tm.assert_numpy_array_equal(result[0], expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_converters.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_converters.py
new file mode 100644
index 0000000000000000000000000000000000000000..87d95e77d30d7f495762ee33a474e811345af374
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_converters.py
@@ -0,0 +1,265 @@
+"""
+Tests column conversion functionality during parsing
+for all of the parsers defined in parsers.py
+"""
+
+from io import StringIO
+
+from dateutil.parser import parse
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+
+
+def test_converters_type_must_be_dict(all_parsers):
+ parser = all_parsers
+ data = """index,A,B,C,D
+foo,2,3,4,5
+"""
+ if parser.engine == "pyarrow":
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), converters=0)
+ return
+ with pytest.raises(TypeError, match="Type converters.+"):
+ parser.read_csv(StringIO(data), converters=0)
+
+
+@pytest.mark.parametrize("column", [3, "D"])
+@pytest.mark.parametrize(
+ "converter",
+ [parse, lambda x: int(x.split("/")[2])], # Produce integer.
+)
+def test_converters(all_parsers, column, converter):
+ parser = all_parsers
+ data = """A,B,C,D
+a,1,2,01/01/2009
+b,3,4,01/02/2009
+c,4,5,01/03/2009
+"""
+ if parser.engine == "pyarrow":
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), converters={column: converter})
+ return
+
+ result = parser.read_csv(StringIO(data), converters={column: converter})
+
+ expected = parser.read_csv(StringIO(data))
+ expected["D"] = expected["D"].map(converter)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_converters_no_implicit_conv(all_parsers):
+ # see gh-2184
+ parser = all_parsers
+ data = """000102,1.2,A\n001245,2,B"""
+
+ converters = {0: lambda x: x.strip()}
+
+ if parser.engine == "pyarrow":
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), header=None, converters=converters)
+ return
+
+ result = parser.read_csv(StringIO(data), header=None, converters=converters)
+
+ # Column 0 should not be casted to numeric and should remain as object.
+ expected = DataFrame([["000102", 1.2, "A"], ["001245", 2, "B"]])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_converters_euro_decimal_format(all_parsers):
+ # see gh-583
+ converters = {}
+ parser = all_parsers
+
+ data = """Id;Number1;Number2;Text1;Text2;Number3
+1;1521,1541;187101,9543;ABC;poi;4,7387
+2;121,12;14897,76;DEF;uyt;0,3773
+3;878,158;108013,434;GHI;rez;2,7356"""
+ converters["Number1"] = converters["Number2"] = converters["Number3"] = lambda x: (
+ float(x.replace(",", "."))
+ )
+
+ if parser.engine == "pyarrow":
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), sep=";", converters=converters)
+ return
+
+ result = parser.read_csv(StringIO(data), sep=";", converters=converters)
+ expected = DataFrame(
+ [
+ [1, 1521.1541, 187101.9543, "ABC", "poi", 4.7387],
+ [2, 121.12, 14897.76, "DEF", "uyt", 0.3773],
+ [3, 878.158, 108013.434, "GHI", "rez", 2.7356],
+ ],
+ columns=["Id", "Number1", "Number2", "Text1", "Text2", "Number3"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_converters_corner_with_nans(all_parsers):
+ parser = all_parsers
+ data = """id,score,days
+1,2,12
+2,2-5,
+3,,14+
+4,6-12,2"""
+
+ # Example converters.
+ def convert_days(x):
+ x = x.strip()
+
+ if not x:
+ return np.nan
+
+ is_plus = x.endswith("+")
+
+ if is_plus:
+ x = int(x[:-1]) + 1
+ else:
+ x = int(x)
+
+ return x
+
+ def convert_days_sentinel(x):
+ x = x.strip()
+
+ if not x:
+ return np.nan
+
+ is_plus = x.endswith("+")
+
+ if is_plus:
+ x = int(x[:-1]) + 1
+ else:
+ x = int(x)
+
+ return x
+
+ def convert_score(x):
+ x = x.strip()
+
+ if not x:
+ return np.nan
+
+ if x.find("-") > 0:
+ val_min, val_max = map(int, x.split("-"))
+ val = 0.5 * (val_min + val_max)
+ else:
+ val = float(x)
+
+ return val
+
+ results = []
+
+ for day_converter in [convert_days, convert_days_sentinel]:
+ if parser.engine == "pyarrow":
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ converters={"score": convert_score, "days": day_converter},
+ na_values=["", None],
+ )
+ continue
+
+ result = parser.read_csv(
+ StringIO(data),
+ converters={"score": convert_score, "days": day_converter},
+ na_values=["", None],
+ )
+ assert pd.isna(result["days"][1])
+ results.append(result)
+
+ if parser.engine != "pyarrow":
+ tm.assert_frame_equal(results[0], results[1])
+
+
+@pytest.mark.parametrize("conv_f", [lambda x: x, str])
+def test_converter_index_col_bug(all_parsers, conv_f):
+ # see gh-1835 , GH#40589
+ parser = all_parsers
+ data = "A;B\n1;2\n3;4"
+
+ if parser.engine == "pyarrow":
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data), sep=";", index_col="A", converters={"A": conv_f}
+ )
+ return
+
+ rs = parser.read_csv(
+ StringIO(data), sep=";", index_col="A", converters={"A": conv_f}
+ )
+
+ xp = DataFrame({"B": [2, 4]}, index=Index(["1", "3"], name="A"))
+ tm.assert_frame_equal(rs, xp)
+
+
+def test_converter_identity_object(all_parsers):
+ # GH#40589
+ parser = all_parsers
+ data = "A,B\n1,2\n3,4"
+
+ if parser.engine == "pyarrow":
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), converters={"A": lambda x: x})
+ return
+
+ rs = parser.read_csv(StringIO(data), converters={"A": lambda x: x})
+
+ xp = DataFrame({"A": ["1", "3"], "B": [2, 4]})
+ tm.assert_frame_equal(rs, xp)
+
+
+def test_converter_multi_index(all_parsers):
+ # GH 42446
+ parser = all_parsers
+ data = "A,B,B\nX,Y,Z\n1,2,3"
+
+ if parser.engine == "pyarrow":
+ msg = "The 'converters' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ header=list(range(2)),
+ converters={
+ ("A", "X"): np.int32,
+ ("B", "Y"): np.int32,
+ ("B", "Z"): np.float32,
+ },
+ )
+ return
+
+ result = parser.read_csv(
+ StringIO(data),
+ header=list(range(2)),
+ converters={
+ ("A", "X"): np.int32,
+ ("B", "Y"): np.int32,
+ ("B", "Z"): np.float32,
+ },
+ )
+
+ expected = DataFrame(
+ {
+ ("A", "X"): np.int32([1]),
+ ("B", "Y"): np.int32([2]),
+ ("B", "Z"): np.float32([3]),
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_dialect.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_dialect.py
new file mode 100644
index 0000000000000000000000000000000000000000..803114723bc7403942d9182f3f94d4edc7fd941a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_dialect.py
@@ -0,0 +1,195 @@
+"""
+Tests that dialects are properly handled during parsing
+for all of the parsers defined in parsers.py
+"""
+
+import csv
+from io import StringIO
+
+import pytest
+
+from pandas.errors import ParserWarning
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.fixture
+def custom_dialect():
+ dialect_name = "weird"
+ dialect_kwargs = {
+ "doublequote": False,
+ "escapechar": "~",
+ "delimiter": ":",
+ "skipinitialspace": False,
+ "quotechar": "`",
+ "quoting": 3,
+ }
+ return dialect_name, dialect_kwargs
+
+
+def test_dialect(all_parsers):
+ parser = all_parsers
+ data = """\
+label1,label2,label3
+index1,"a,c,e
+index2,b,d,f
+"""
+
+ dia = csv.excel()
+ dia.quoting = csv.QUOTE_NONE
+
+ if parser.engine == "pyarrow":
+ msg = "The 'dialect' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), dialect=dia)
+ return
+
+ df = parser.read_csv(StringIO(data), dialect=dia)
+
+ data = """\
+label1,label2,label3
+index1,a,c,e
+index2,b,d,f
+"""
+ exp = parser.read_csv(StringIO(data))
+ exp.replace("a", '"a', inplace=True)
+ tm.assert_frame_equal(df, exp)
+
+
+def test_dialect_str(all_parsers):
+ dialect_name = "mydialect"
+ parser = all_parsers
+ data = """\
+fruit:vegetable
+apple:broccoli
+pear:tomato
+"""
+ exp = DataFrame({"fruit": ["apple", "pear"], "vegetable": ["broccoli", "tomato"]})
+
+ with tm.with_csv_dialect(dialect_name, delimiter=":"):
+ if parser.engine == "pyarrow":
+ msg = "The 'dialect' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), dialect=dialect_name)
+ return
+
+ df = parser.read_csv(StringIO(data), dialect=dialect_name)
+ tm.assert_frame_equal(df, exp)
+
+
+def test_invalid_dialect(all_parsers):
+ class InvalidDialect:
+ pass
+
+ data = "a\n1"
+ parser = all_parsers
+ msg = "Invalid dialect"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), dialect=InvalidDialect)
+
+
+@pytest.mark.parametrize(
+ "arg",
+ [None, "doublequote", "escapechar", "skipinitialspace", "quotechar", "quoting"],
+)
+@pytest.mark.parametrize("value", ["dialect", "default", "other"])
+def test_dialect_conflict_except_delimiter(all_parsers, custom_dialect, arg, value):
+ # see gh-23761.
+ dialect_name, dialect_kwargs = custom_dialect
+ parser = all_parsers
+
+ expected = DataFrame({"a": [1], "b": [2]})
+ data = "a:b\n1:2"
+
+ warning_klass = None
+ kwds = {}
+
+ # arg=None tests when we pass in the dialect without any other arguments.
+ if arg is not None:
+ if value == "dialect": # No conflict --> no warning.
+ kwds[arg] = dialect_kwargs[arg]
+ elif value == "default": # Default --> no warning.
+ from pandas.io.parsers.base_parser import parser_defaults
+
+ kwds[arg] = parser_defaults[arg]
+ else: # Non-default + conflict with dialect --> warning.
+ warning_klass = ParserWarning
+ kwds[arg] = "blah"
+
+ with tm.with_csv_dialect(dialect_name, **dialect_kwargs):
+ if parser.engine == "pyarrow":
+ msg = "The 'dialect' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv_check_warnings(
+ # No warning bc we raise
+ None,
+ "Conflicting values for",
+ StringIO(data),
+ dialect=dialect_name,
+ **kwds,
+ )
+ return
+ result = parser.read_csv_check_warnings(
+ warning_klass,
+ "Conflicting values for",
+ StringIO(data),
+ dialect=dialect_name,
+ **kwds,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "kwargs,warning_klass",
+ [
+ ({"sep": ","}, None), # sep is default --> sep_override=True
+ ({"sep": "."}, ParserWarning), # sep isn't default --> sep_override=False
+ ({"delimiter": ":"}, None), # No conflict
+ ({"delimiter": None}, None), # Default arguments --> sep_override=True
+ ({"delimiter": ","}, ParserWarning), # Conflict
+ ({"delimiter": "."}, ParserWarning), # Conflict
+ ],
+ ids=[
+ "sep-override-true",
+ "sep-override-false",
+ "delimiter-no-conflict",
+ "delimiter-default-arg",
+ "delimiter-conflict",
+ "delimiter-conflict2",
+ ],
+)
+def test_dialect_conflict_delimiter(all_parsers, custom_dialect, kwargs, warning_klass):
+ # see gh-23761.
+ dialect_name, dialect_kwargs = custom_dialect
+ parser = all_parsers
+
+ expected = DataFrame({"a": [1], "b": [2]})
+ data = "a:b\n1:2"
+
+ with tm.with_csv_dialect(dialect_name, **dialect_kwargs):
+ if parser.engine == "pyarrow":
+ msg = "The 'dialect' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv_check_warnings(
+ # no warning bc we raise
+ None,
+ "Conflicting values for 'delimiter'",
+ StringIO(data),
+ dialect=dialect_name,
+ **kwargs,
+ )
+ return
+ result = parser.read_csv_check_warnings(
+ warning_klass,
+ "Conflicting values for 'delimiter'",
+ StringIO(data),
+ dialect=dialect_name,
+ **kwargs,
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_encoding.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_encoding.py
new file mode 100644
index 0000000000000000000000000000000000000000..50d42f15cc0f82c8d6055dd1ae68b4e4c1543f80
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_encoding.py
@@ -0,0 +1,333 @@
+"""
+Tests encoding functionality during parsing
+for all of the parsers defined in parsers.py
+"""
+
+from io import (
+ BytesIO,
+ TextIOWrapper,
+)
+import os
+import tempfile
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ read_csv,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+def test_bytes_io_input(all_parsers):
+ encoding = "cp1255"
+ parser = all_parsers
+
+ data = BytesIO("שלום:1234\n562:123".encode(encoding))
+ result = parser.read_csv(data, sep=":", encoding=encoding)
+
+ expected = DataFrame([[562, 123]], columns=["שלום", "1234"])
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_read_csv_unicode(all_parsers):
+ parser = all_parsers
+ data = BytesIO("\u0141aski, Jan;1".encode())
+
+ result = parser.read_csv(data, sep=";", encoding="utf-8", header=None)
+ expected = DataFrame([["\u0141aski, Jan", 1]])
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow
+@pytest.mark.parametrize("sep", [",", "\t"])
+@pytest.mark.parametrize("encoding", ["utf-16", "utf-16le", "utf-16be"])
+def test_utf16_bom_skiprows(all_parsers, sep, encoding, temp_file):
+ # see gh-2298
+ parser = all_parsers
+ data = """skip this
+skip this too
+A,B,C
+1,2,3
+4,5,6""".replace(",", sep)
+ kwargs = {"sep": sep, "skiprows": 2}
+ utf8 = "utf-8"
+
+ bytes_data = data.encode(encoding)
+
+ with open(temp_file, "wb") as f:
+ f.write(bytes_data)
+
+ with TextIOWrapper(BytesIO(data.encode(utf8)), encoding=utf8) as bytes_buffer:
+ result = parser.read_csv(temp_file, encoding=encoding, **kwargs)
+ expected = parser.read_csv(bytes_buffer, encoding=utf8, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_utf16_example(all_parsers, csv_dir_path):
+ path = os.path.join(csv_dir_path, "utf16_ex.txt")
+ parser = all_parsers
+ result = parser.read_csv(path, encoding="utf-16", sep="\t")
+ assert len(result) == 50
+
+
+def test_unicode_encoding(all_parsers, csv_dir_path):
+ path = os.path.join(csv_dir_path, "unicode_series.csv")
+ parser = all_parsers
+
+ result = parser.read_csv(path, header=None, encoding="latin-1")
+ result = result.set_index(0)
+ got = result[1][1632]
+
+ expected = "\xc1 k\xf6ldum klaka (Cold Fever) (1994)"
+ assert got == expected
+
+
+@pytest.mark.parametrize(
+ "data,kwargs,expected",
+ [
+ # Basic test
+ ("a\n1", {}, [1]),
+ # "Regular" quoting
+ ('"a"\n1', {"quotechar": '"'}, [1]),
+ # Test in a data row instead of header
+ ("b\n1", {"names": ["a"]}, ["b", "1"]),
+ # Test in empty data row with skipping
+ ("\n1", {"names": ["a"], "skip_blank_lines": True}, [1]),
+ # Test in empty data row without skipping
+ (
+ "\n1",
+ {"names": ["a"], "skip_blank_lines": False},
+ [np.nan, 1],
+ ),
+ ],
+)
+def test_utf8_bom(all_parsers, data, kwargs, expected):
+ # see gh-4793
+ parser = all_parsers
+ bom = "\ufeff"
+ utf8 = "utf-8"
+
+ def _encode_data_with_bom(_data):
+ bom_data = (bom + _data).encode(utf8)
+ return BytesIO(bom_data)
+
+ if (
+ parser.engine == "pyarrow"
+ and data == "\n1"
+ and kwargs.get("skip_blank_lines", True)
+ ):
+ # CSV parse error: Empty CSV file or block: cannot infer number of columns
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+
+ result = parser.read_csv(_encode_data_with_bom(data), encoding=utf8, **kwargs)
+ expected = DataFrame({"a": expected})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_csv_utf_aliases(all_parsers, utf_value, encoding_fmt):
+ # see gh-13549
+ expected = DataFrame({"mb_num": [4.8], "multibyte": ["test"]})
+ parser = all_parsers
+
+ encoding = encoding_fmt.format(utf_value)
+ data = "mb_num,multibyte\n4.8,test".encode(encoding)
+
+ result = parser.read_csv(BytesIO(data), encoding=encoding)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "file_path,encoding",
+ [
+ (("io", "data", "csv", "test1.csv"), "utf-8"),
+ (("io", "parser", "data", "unicode_series.csv"), "latin-1"),
+ (("io", "parser", "data", "sauron.SHIFT_JIS.csv"), "shiftjis"),
+ ],
+)
+def test_binary_mode_file_buffers(all_parsers, file_path, encoding, datapath):
+ # gh-23779: Python csv engine shouldn't error on files opened in binary.
+ # gh-31575: Python csv engine shouldn't error on files opened in raw binary.
+ parser = all_parsers
+
+ fpath = datapath(*file_path)
+ expected = parser.read_csv(fpath, encoding=encoding)
+
+ with open(fpath, encoding=encoding) as fa:
+ result = parser.read_csv(fa)
+ assert not fa.closed
+ tm.assert_frame_equal(expected, result)
+
+ with open(fpath, mode="rb") as fb:
+ result = parser.read_csv(fb, encoding=encoding)
+ assert not fb.closed
+ tm.assert_frame_equal(expected, result)
+
+ with open(fpath, mode="rb", buffering=0) as fb:
+ result = parser.read_csv(fb, encoding=encoding)
+ assert not fb.closed
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize("pass_encoding", [True, False])
+def test_encoding_temp_file(
+ all_parsers, utf_value, encoding_fmt, pass_encoding, temp_file
+):
+ # see gh-24130
+ parser = all_parsers
+ encoding = encoding_fmt.format(utf_value)
+
+ if parser.engine == "pyarrow" and pass_encoding is True and utf_value in [16, 32]:
+ # FIXME: this is bad!
+ pytest.skip("These cases freeze")
+
+ expected = DataFrame({"foo": ["bar"]})
+
+ with temp_file.open(mode="w+", encoding=encoding) as f:
+ f.write("foo\nbar")
+ f.seek(0)
+
+ result = parser.read_csv(f, encoding=encoding if pass_encoding else None)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_encoding_named_temp_file(all_parsers):
+ # see gh-31819
+ parser = all_parsers
+ encoding = "shift-jis"
+
+ title = "てすと"
+ data = "こむ"
+
+ expected = DataFrame({title: [data]})
+
+ with tempfile.NamedTemporaryFile() as f:
+ f.write(f"{title}\n{data}".encode(encoding))
+
+ f.seek(0)
+
+ result = parser.read_csv(f, encoding=encoding)
+ tm.assert_frame_equal(result, expected)
+ assert not f.closed
+
+
+@pytest.mark.parametrize(
+ "encoding", ["utf-8", "utf-16", "utf-16-be", "utf-16-le", "utf-32"]
+)
+def test_parse_encoded_special_characters(encoding):
+ # GH16218 Verify parsing of data with encoded special characters
+ # Data contains a Unicode 'FULLWIDTH COLON' (U+FF1A) at position (0,"a")
+ data = "a\tb\n:foo\t0\nbar\t1\nbaz\t2" # noqa: RUF001
+ encoded_data = BytesIO(data.encode(encoding))
+ result = read_csv(encoded_data, delimiter="\t", encoding=encoding)
+
+ expected = DataFrame(
+ data=[[":foo", 0], ["bar", 1], ["baz", 2]], # noqa: RUF001
+ columns=["a", "b"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("encoding", ["utf-8", None, "utf-16", "cp1255", "latin-1"])
+def test_encoding_memory_map(all_parsers, encoding, temp_file):
+ # GH40986
+ parser = all_parsers
+ expected = DataFrame(
+ {
+ "name": ["Raphael", "Donatello", "Miguel Angel", "Leonardo"],
+ "mask": ["red", "purple", "orange", "blue"],
+ "weapon": ["sai", "bo staff", "nunchunk", "katana"],
+ }
+ )
+ expected.to_csv(temp_file, index=False, encoding=encoding)
+
+ if parser.engine == "pyarrow":
+ msg = "The 'memory_map' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(temp_file, encoding=encoding, memory_map=True)
+ return
+
+ df = parser.read_csv(temp_file, encoding=encoding, memory_map=True)
+ tm.assert_frame_equal(df, expected)
+
+
+def test_chunk_splits_multibyte_char(all_parsers, temp_file):
+ """
+ Chunk splits a multibyte character with memory_map=True
+
+ GH 43540
+ """
+ parser = all_parsers
+ # DEFAULT_CHUNKSIZE = 262144, defined in parsers.pyx
+ df = DataFrame(data=["a" * 127] * 2048)
+
+ # Put two-bytes utf-8 encoded character "ą" at the end of chunk
+ # utf-8 encoding of "ą" is b'\xc4\x85'
+ df.iloc[2047] = "a" * 127 + "ą"
+ df.to_csv(temp_file, index=False, header=False, encoding="utf-8")
+
+ if parser.engine == "pyarrow":
+ msg = "The 'memory_map' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(temp_file, header=None, memory_map=True)
+ return
+
+ dfr = parser.read_csv(temp_file, header=None, memory_map=True)
+ tm.assert_frame_equal(dfr, df)
+
+
+def test_readcsv_memmap_utf8(all_parsers, temp_file):
+ """
+ GH 43787
+
+ Test correct handling of UTF-8 chars when memory_map=True and encoding is UTF-8
+ """
+ lines = []
+ line_length = 128
+ start_char = " "
+ end_char = "\U00010080"
+ # This for loop creates a list of 128-char strings
+ # consisting of consecutive Unicode chars
+ for lnum in range(ord(start_char), ord(end_char), line_length):
+ line = "".join([chr(c) for c in range(lnum, lnum + 0x80)]) + "\n"
+ try:
+ line.encode("utf-8")
+ except UnicodeEncodeError:
+ continue
+ lines.append(line)
+ parser = all_parsers
+ df = DataFrame(lines)
+ df.to_csv(temp_file, index=False, header=False, encoding="utf-8")
+
+ if parser.engine == "pyarrow":
+ msg = "The 'memory_map' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(temp_file, header=None, memory_map=True, encoding="utf-8")
+ return
+
+ dfr = parser.read_csv(temp_file, header=None, memory_map=True, encoding="utf-8")
+ tm.assert_frame_equal(df, dfr)
+
+
+@pytest.mark.usefixtures("pyarrow_xfail")
+@pytest.mark.parametrize("mode", ["w+b", "w+t"])
+def test_not_readable(all_parsers, mode):
+ # GH43439
+ parser = all_parsers
+ content = b"abcd"
+ if "t" in mode:
+ content = "abcd"
+ with tempfile.SpooledTemporaryFile(mode=mode, encoding="utf-8") as handle:
+ handle.write(content)
+ handle.seek(0)
+ df = parser.read_csv(handle)
+ expected = DataFrame([], columns=["abcd"])
+ tm.assert_frame_equal(df, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_header.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_header.py
new file mode 100644
index 0000000000000000000000000000000000000000..d333aef723de29411ac15e606d0f13de792b8b01
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_header.py
@@ -0,0 +1,727 @@
+"""
+Tests that the file header is properly handled or inferred
+during parsing for all of the parsers defined in parsers.py
+"""
+
+from collections import namedtuple
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas.errors import ParserError
+
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_read_with_bad_header(all_parsers):
+ parser = all_parsers
+ msg = r"but only \d+ lines in file"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(",,"), header=[10])
+
+
+def test_negative_header(all_parsers):
+ # see gh-27779
+ parser = all_parsers
+ data = """1,2,3,4,5
+6,7,8,9,10
+11,12,13,14,15
+"""
+ with pytest.raises(
+ ValueError,
+ match="Passing negative integer to header is invalid. "
+ "For no header, use header=None instead",
+ ):
+ parser.read_csv(StringIO(data), header=-1)
+
+
+@pytest.mark.parametrize("header", [([-1, 2, 4]), ([-5, 0])])
+def test_negative_multi_index_header(all_parsers, header):
+ # see gh-27779
+ parser = all_parsers
+ data = """1,2,3,4,5
+ 6,7,8,9,10
+ 11,12,13,14,15
+ """
+ with pytest.raises(
+ ValueError, match="cannot specify multi-index header with negative integers"
+ ):
+ parser.read_csv(StringIO(data), header=header)
+
+
+@pytest.mark.parametrize("header", [True, False])
+def test_bool_header_arg(all_parsers, header):
+ # see gh-6114
+ parser = all_parsers
+ data = """\
+MyColumn
+a
+b
+a
+b"""
+ msg = "Passing a bool to header is invalid"
+ with pytest.raises(TypeError, match=msg):
+ parser.read_csv(StringIO(data), header=header)
+
+
+@xfail_pyarrow # AssertionError: DataFrame are different
+def test_header_with_index_col(all_parsers):
+ parser = all_parsers
+ data = """foo,1,2,3
+bar,4,5,6
+baz,7,8,9
+"""
+ names = ["A", "B", "C"]
+ result = parser.read_csv(StringIO(data), names=names)
+
+ expected = DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ index=["foo", "bar", "baz"],
+ columns=["A", "B", "C"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_header_not_first_line(all_parsers):
+ parser = all_parsers
+ data = """got,to,ignore,this,line
+got,to,ignore,this,line
+index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+"""
+ data2 = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+"""
+
+ result = parser.read_csv(StringIO(data), header=2, index_col=0)
+ expected = parser.read_csv(StringIO(data2), header=0, index_col=0)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_header_multi_index(all_parsers):
+ parser = all_parsers
+
+ data = """\
+C0,,C_l0_g0,C_l0_g1,C_l0_g2
+
+C1,,C_l1_g0,C_l1_g1,C_l1_g2
+C2,,C_l2_g0,C_l2_g1,C_l2_g2
+C3,,C_l3_g0,C_l3_g1,C_l3_g2
+R0,R1,,,
+R_l0_g0,R_l1_g0,R0C0,R0C1,R0C2
+R_l0_g1,R_l1_g1,R1C0,R1C1,R1C2
+R_l0_g2,R_l1_g2,R2C0,R2C1,R2C2
+R_l0_g3,R_l1_g3,R3C0,R3C1,R3C2
+R_l0_g4,R_l1_g4,R4C0,R4C1,R4C2
+"""
+ result = parser.read_csv(StringIO(data), header=[0, 1, 2, 3], index_col=[0, 1])
+ data_gen_f = lambda r, c: f"R{r}C{c}"
+
+ data = [[data_gen_f(r, c) for c in range(3)] for r in range(5)]
+ index = MultiIndex.from_arrays(
+ [[f"R_l0_g{i}" for i in range(5)], [f"R_l1_g{i}" for i in range(5)]],
+ names=["R0", "R1"],
+ )
+ columns = MultiIndex.from_arrays(
+ [
+ [f"C_l0_g{i}" for i in range(3)],
+ [f"C_l1_g{i}" for i in range(3)],
+ [f"C_l2_g{i}" for i in range(3)],
+ [f"C_l3_g{i}" for i in range(3)],
+ ],
+ names=["C0", "C1", "C2", "C3"],
+ )
+ expected = DataFrame(data, columns=columns, index=index)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "kwargs,msg",
+ [
+ (
+ {"index_col": ["foo", "bar"]},
+ (
+ "index_col must only contain "
+ "integers of column positions when specifying "
+ "a multi-index header"
+ ),
+ ),
+ (
+ {"index_col": [0, 1], "names": ["foo", "bar"]},
+ ("cannot specify names when specifying a multi-index header"),
+ ),
+ (
+ {"index_col": [0, 1], "usecols": ["foo", "bar"]},
+ ("cannot specify usecols when specifying a multi-index header"),
+ ),
+ ],
+)
+def test_header_multi_index_invalid(all_parsers, kwargs, msg):
+ data = """\
+C0,,C_l0_g0,C_l0_g1,C_l0_g2
+
+C1,,C_l1_g0,C_l1_g1,C_l1_g2
+C2,,C_l2_g0,C_l2_g1,C_l2_g2
+C3,,C_l3_g0,C_l3_g1,C_l3_g2
+R0,R1,,,
+R_l0_g0,R_l1_g0,R0C0,R0C1,R0C2
+R_l0_g1,R_l1_g1,R1C0,R1C1,R1C2
+R_l0_g2,R_l1_g2,R2C0,R2C1,R2C2
+R_l0_g3,R_l1_g3,R3C0,R3C1,R3C2
+R_l0_g4,R_l1_g4,R4C0,R4C1,R4C2
+"""
+ parser = all_parsers
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), header=[0, 1, 2, 3], **kwargs)
+
+
+_TestTuple = namedtuple("_TestTuple", ["first", "second"])
+
+
+@xfail_pyarrow # TypeError: an integer is required
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"header": [0, 1]},
+ {
+ "skiprows": 3,
+ "names": [
+ ("a", "q"),
+ ("a", "r"),
+ ("a", "s"),
+ ("b", "t"),
+ ("c", "u"),
+ ("c", "v"),
+ ],
+ },
+ {
+ "skiprows": 3,
+ "names": [
+ _TestTuple("a", "q"),
+ _TestTuple("a", "r"),
+ _TestTuple("a", "s"),
+ _TestTuple("b", "t"),
+ _TestTuple("c", "u"),
+ _TestTuple("c", "v"),
+ ],
+ },
+ ],
+)
+def test_header_multi_index_common_format1(all_parsers, kwargs):
+ parser = all_parsers
+ expected = DataFrame(
+ [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]],
+ index=["one", "two"],
+ columns=MultiIndex.from_tuples(
+ [("a", "q"), ("a", "r"), ("a", "s"), ("b", "t"), ("c", "u"), ("c", "v")]
+ ),
+ )
+ data = """,a,a,a,b,c,c
+,q,r,s,t,u,v
+,,,,,,
+one,1,2,3,4,5,6
+two,7,8,9,10,11,12"""
+
+ result = parser.read_csv(StringIO(data), index_col=0, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"header": [0, 1]},
+ {
+ "skiprows": 2,
+ "names": [
+ ("a", "q"),
+ ("a", "r"),
+ ("a", "s"),
+ ("b", "t"),
+ ("c", "u"),
+ ("c", "v"),
+ ],
+ },
+ {
+ "skiprows": 2,
+ "names": [
+ _TestTuple("a", "q"),
+ _TestTuple("a", "r"),
+ _TestTuple("a", "s"),
+ _TestTuple("b", "t"),
+ _TestTuple("c", "u"),
+ _TestTuple("c", "v"),
+ ],
+ },
+ ],
+)
+def test_header_multi_index_common_format2(all_parsers, kwargs):
+ parser = all_parsers
+ expected = DataFrame(
+ [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]],
+ index=["one", "two"],
+ columns=MultiIndex.from_tuples(
+ [("a", "q"), ("a", "r"), ("a", "s"), ("b", "t"), ("c", "u"), ("c", "v")]
+ ),
+ )
+ data = """,a,a,a,b,c,c
+,q,r,s,t,u,v
+one,1,2,3,4,5,6
+two,7,8,9,10,11,12"""
+
+ result = parser.read_csv(StringIO(data), index_col=0, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"header": [0, 1]},
+ {
+ "skiprows": 2,
+ "names": [
+ ("a", "q"),
+ ("a", "r"),
+ ("a", "s"),
+ ("b", "t"),
+ ("c", "u"),
+ ("c", "v"),
+ ],
+ },
+ {
+ "skiprows": 2,
+ "names": [
+ _TestTuple("a", "q"),
+ _TestTuple("a", "r"),
+ _TestTuple("a", "s"),
+ _TestTuple("b", "t"),
+ _TestTuple("c", "u"),
+ _TestTuple("c", "v"),
+ ],
+ },
+ ],
+)
+def test_header_multi_index_common_format3(all_parsers, kwargs):
+ parser = all_parsers
+ expected = DataFrame(
+ [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]],
+ index=["one", "two"],
+ columns=MultiIndex.from_tuples(
+ [("a", "q"), ("a", "r"), ("a", "s"), ("b", "t"), ("c", "u"), ("c", "v")]
+ ),
+ )
+ expected = expected.reset_index(drop=True)
+ data = """a,a,a,b,c,c
+q,r,s,t,u,v
+1,2,3,4,5,6
+7,8,9,10,11,12"""
+
+ result = parser.read_csv(StringIO(data), index_col=None, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_header_multi_index_common_format_malformed1(all_parsers):
+ parser = all_parsers
+ expected = DataFrame(
+ np.array([[2, 3, 4, 5, 6], [8, 9, 10, 11, 12]], dtype="int64"),
+ index=Index([1, 7]),
+ columns=MultiIndex(
+ levels=[["a", "b", "c"], ["r", "s", "t", "u", "v"]],
+ codes=[[0, 0, 1, 2, 2], [0, 1, 2, 3, 4]],
+ names=["a", "q"],
+ ),
+ )
+ data = """a,a,a,b,c,c
+q,r,s,t,u,v
+1,2,3,4,5,6
+7,8,9,10,11,12"""
+
+ result = parser.read_csv(StringIO(data), header=[0, 1], index_col=0)
+ tm.assert_frame_equal(expected, result)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_header_multi_index_common_format_malformed2(all_parsers):
+ parser = all_parsers
+ expected = DataFrame(
+ np.array([[2, 3, 4, 5, 6], [8, 9, 10, 11, 12]], dtype="int64"),
+ index=range(1, 13, 6),
+ columns=MultiIndex(
+ levels=[["a", "b", "c"], ["r", "s", "t", "u", "v"]],
+ codes=[[0, 0, 1, 2, 2], [0, 1, 2, 3, 4]],
+ names=[None, "q"],
+ ),
+ )
+
+ data = """,a,a,b,c,c
+q,r,s,t,u,v
+1,2,3,4,5,6
+7,8,9,10,11,12"""
+
+ result = parser.read_csv(StringIO(data), header=[0, 1], index_col=0)
+ tm.assert_frame_equal(expected, result)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_header_multi_index_common_format_malformed3(all_parsers):
+ parser = all_parsers
+ expected = DataFrame(
+ np.array([[3, 4, 5, 6], [9, 10, 11, 12]], dtype="int64"),
+ index=MultiIndex(levels=[[1, 7], [2, 8]], codes=[[0, 1], [0, 1]]),
+ columns=MultiIndex(
+ levels=[["a", "b", "c"], ["s", "t", "u", "v"]],
+ codes=[[0, 1, 2, 2], [0, 1, 2, 3]],
+ names=[None, "q"],
+ ),
+ )
+ data = """,a,a,b,c,c
+q,r,s,t,u,v
+1,2,3,4,5,6
+7,8,9,10,11,12"""
+
+ result = parser.read_csv(StringIO(data), header=[0, 1], index_col=[0, 1])
+ tm.assert_frame_equal(expected, result)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_header_multi_index_blank_line(all_parsers):
+ # GH 40442
+ parser = all_parsers
+ data = [[None, None], [1, 2], [3, 4]]
+ columns = MultiIndex.from_tuples([("a", "A"), ("b", "B")])
+ expected = DataFrame(data, columns=columns)
+ data = "a,b\nA,B\n,\n1,2\n3,4"
+ result = parser.read_csv(StringIO(data), header=[0, 1])
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize(
+ "data,header", [("1,2,3\n4,5,6", None), ("foo,bar,baz\n1,2,3\n4,5,6", 0)]
+)
+def test_header_names_backward_compat(all_parsers, data, header, request):
+ # see gh-2539
+ parser = all_parsers
+
+ if parser.engine == "pyarrow" and header is not None:
+ mark = pytest.mark.xfail(reason="DataFrame.columns are different")
+ request.applymarker(mark)
+
+ expected = parser.read_csv(StringIO("1,2,3\n4,5,6"), names=["a", "b", "c"])
+
+ result = parser.read_csv(StringIO(data), names=["a", "b", "c"], header=header)
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block: cannot infer
+@pytest.mark.parametrize("kwargs", [{}, {"index_col": False}])
+def test_read_only_header_no_rows(all_parsers, kwargs):
+ # See gh-7773
+ parser = all_parsers
+ expected = DataFrame(columns=["a", "b", "c"])
+
+ result = parser.read_csv(StringIO("a,b,c"), **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "kwargs,names",
+ [
+ ({}, [0, 1, 2, 3, 4]),
+ (
+ {"names": ["foo", "bar", "baz", "quux", "panda"]},
+ ["foo", "bar", "baz", "quux", "panda"],
+ ),
+ ],
+)
+def test_no_header(all_parsers, kwargs, names):
+ parser = all_parsers
+ data = """1,2,3,4,5
+6,7,8,9,10
+11,12,13,14,15
+"""
+ expected = DataFrame(
+ [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]], columns=names
+ )
+ result = parser.read_csv(StringIO(data), header=None, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("header", [["a", "b"], "string_header"])
+def test_non_int_header(all_parsers, header):
+ # see gh-16338
+ msg = "header must be integer or list of integers"
+ data = """1,2\n3,4"""
+ parser = all_parsers
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), header=header)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_singleton_header(all_parsers):
+ # see gh-7757
+ data = """a,b,c\n0,1,2\n1,2,3"""
+ parser = all_parsers
+
+ expected = DataFrame({"a": [0, 1], "b": [1, 2], "c": [2, 3]})
+ result = parser.read_csv(StringIO(data), header=[0])
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+@pytest.mark.parametrize(
+ "data,expected",
+ [
+ (
+ "A,A,A,B\none,one,one,two\n0,40,34,0.1",
+ DataFrame(
+ [[0, 40, 34, 0.1]],
+ columns=MultiIndex.from_tuples(
+ [("A", "one"), ("A", "one.1"), ("A", "one.2"), ("B", "two")]
+ ),
+ ),
+ ),
+ (
+ "A,A,A,B\none,one,one.1,two\n0,40,34,0.1",
+ DataFrame(
+ [[0, 40, 34, 0.1]],
+ columns=MultiIndex.from_tuples(
+ [("A", "one"), ("A", "one.1"), ("A", "one.1.1"), ("B", "two")]
+ ),
+ ),
+ ),
+ (
+ "A,A,A,B,B\none,one,one.1,two,two\n0,40,34,0.1,0.1",
+ DataFrame(
+ [[0, 40, 34, 0.1, 0.1]],
+ columns=MultiIndex.from_tuples(
+ [
+ ("A", "one"),
+ ("A", "one.1"),
+ ("A", "one.1.1"),
+ ("B", "two"),
+ ("B", "two.1"),
+ ]
+ ),
+ ),
+ ),
+ ],
+)
+def test_mangles_multi_index(all_parsers, data, expected):
+ # see gh-18062
+ parser = all_parsers
+
+ result = parser.read_csv(StringIO(data), header=[0, 1])
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+@pytest.mark.parametrize("index_col", [None, [0]])
+@pytest.mark.parametrize(
+ "columns", [None, (["", "Unnamed"]), (["Unnamed", ""]), (["Unnamed", "NotUnnamed"])]
+)
+def test_multi_index_unnamed(all_parsers, index_col, columns):
+ # see gh-23687
+ #
+ # When specifying a multi-index header, make sure that
+ # we don't error just because one of the rows in our header
+ # has ALL column names containing the string "Unnamed". The
+ # correct condition to check is whether the row contains
+ # ALL columns that did not have names (and instead were given
+ # placeholder ones).
+ parser = all_parsers
+ header = [0, 1]
+
+ if index_col is None:
+ data = ",".join(columns or ["", ""]) + "\n0,1\n2,3\n4,5\n"
+ else:
+ data = ",".join([""] + (columns or ["", ""])) + "\n,0,1\n0,2,3\n1,4,5\n"
+
+ result = parser.read_csv(StringIO(data), header=header, index_col=index_col)
+ exp_columns = []
+
+ if columns is None:
+ columns = ["", "", ""]
+
+ for i, col in enumerate(columns):
+ if not col: # Unnamed.
+ col = f"Unnamed: {i if index_col is None else i + 1}_level_0"
+
+ exp_columns.append(col)
+
+ columns = MultiIndex.from_tuples(zip(exp_columns, ["0", "1"]))
+ expected = DataFrame([[2, 3], [4, 5]], columns=columns)
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Expected 2 columns, got 3
+def test_names_longer_than_header_but_equal_with_data_rows(all_parsers):
+ # GH#38453
+ parser = all_parsers
+ data = """a, b
+1,2,3
+5,6,4
+"""
+ result = parser.read_csv(StringIO(data), header=0, names=["A", "B", "C"])
+ expected = DataFrame({"A": [1, 5], "B": [2, 6], "C": [3, 4]})
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_read_csv_multiindex_columns(all_parsers):
+ # GH#6051
+ parser = all_parsers
+
+ s1 = "Male, Male, Male, Female, Female\nR, R, L, R, R\n.86, .67, .88, .78, .81"
+ s2 = (
+ "Male, Male, Male, Female, Female\n"
+ "R, R, L, R, R\n"
+ ".86, .67, .88, .78, .81\n"
+ ".86, .67, .88, .78, .82"
+ )
+
+ mi = MultiIndex.from_tuples(
+ [
+ ("Male", "R"),
+ (" Male", " R"),
+ (" Male", " L"),
+ (" Female", " R"),
+ (" Female", " R.1"),
+ ]
+ )
+ expected = DataFrame(
+ [[0.86, 0.67, 0.88, 0.78, 0.81], [0.86, 0.67, 0.88, 0.78, 0.82]], columns=mi
+ )
+
+ df1 = parser.read_csv(StringIO(s1), header=[0, 1])
+ tm.assert_frame_equal(df1, expected.iloc[:1])
+ df2 = parser.read_csv(StringIO(s2), header=[0, 1])
+ tm.assert_frame_equal(df2, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_read_csv_multi_header_length_check(all_parsers):
+ # GH#43102
+ parser = all_parsers
+
+ case = """row11,row12,row13
+row21,row22, row23
+row31,row32
+"""
+
+ with pytest.raises(
+ ParserError, match="Header rows must have an equal number of columns."
+ ):
+ parser.read_csv(StringIO(case), header=[0, 2])
+
+
+@skip_pyarrow # CSV parse error: Expected 3 columns, got 2
+def test_header_none_and_implicit_index(all_parsers):
+ # GH#22144
+ parser = all_parsers
+ data = "x,1,5\ny,2\nz,3\n"
+ result = parser.read_csv(StringIO(data), names=["a", "b"], header=None)
+ expected = DataFrame(
+ {"a": [1, 2, 3], "b": [5, np.nan, np.nan]}, index=["x", "y", "z"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # regex mismatch "CSV parse error: Expected 2 columns, got "
+def test_header_none_and_implicit_index_in_second_row(all_parsers):
+ # GH#22144
+ parser = all_parsers
+ data = "x,1\ny,2,5\nz,3\n"
+ with pytest.raises(ParserError, match="Expected 2 fields in line 2, saw 3"):
+ parser.read_csv(StringIO(data), names=["a", "b"], header=None)
+
+
+def test_header_none_and_on_bad_lines_skip(all_parsers):
+ # GH#22144
+ parser = all_parsers
+ data = "x,1\ny,2,5\nz,3\n"
+ result = parser.read_csv(
+ StringIO(data), names=["a", "b"], header=None, on_bad_lines="skip"
+ )
+ expected = DataFrame({"a": ["x", "z"], "b": [1, 3]})
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_header_missing_rows(all_parsers):
+ # GH#47400
+ parser = all_parsers
+ data = """a,b
+1,2
+"""
+ msg = r"Passed header=\[0,1,2\], len of 3, but only 2 lines in file"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), header=[0, 1, 2])
+
+
+# ValueError: the 'pyarrow' engine does not support regex separators
+@xfail_pyarrow
+def test_header_multiple_whitespaces(all_parsers):
+ # GH#54931
+ parser = all_parsers
+ data = """aa bb(1,1) cc(1,1)
+ 0 2 3.5"""
+
+ result = parser.read_csv(StringIO(data), sep=r"\s+")
+ expected = DataFrame({"aa": [0], "bb(1,1)": 2, "cc(1,1)": 3.5})
+ tm.assert_frame_equal(result, expected)
+
+
+# ValueError: the 'pyarrow' engine does not support regex separators
+@xfail_pyarrow
+def test_header_delim_whitespace(all_parsers):
+ # GH#54918
+ parser = all_parsers
+ data = """a,b
+1,2
+3,4
+ """
+ result = parser.read_csv(StringIO(data), sep=r"\s+")
+ expected = DataFrame({"a,b": ["1,2", "3,4"]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_no_header_pyarrow(pyarrow_parser_only):
+ parser = pyarrow_parser_only
+ data = """
+a,i,x
+b,j,y
+"""
+ result = parser.read_csv(
+ StringIO(data),
+ header=None,
+ usecols=[0, 1],
+ dtype="string[pyarrow]",
+ dtype_backend="pyarrow",
+ engine="pyarrow",
+ )
+ expected = DataFrame([["a", "i"], ["b", "j"]], dtype="string[pyarrow]")
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_index_col.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_index_col.py
new file mode 100644
index 0000000000000000000000000000000000000000..73875468f45fced28b0a7403f4d8966daeedec66
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_index_col.py
@@ -0,0 +1,375 @@
+"""
+Tests that the specified index column (a.k.a "index_col")
+is properly handled or inferred during parsing for all of
+the parsers defined in parsers.py
+"""
+
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+@pytest.mark.parametrize("with_header", [True, False])
+def test_index_col_named(all_parsers, with_header):
+ parser = all_parsers
+ no_header = """\
+KORD1,19990127, 19:00:00, 18:56:00, 0.8100, 2.8100, 7.2000, 0.0000, 280.0000
+KORD2,19990127, 20:00:00, 19:56:00, 0.0100, 2.2100, 7.2000, 0.0000, 260.0000
+KORD3,19990127, 21:00:00, 20:56:00, -0.5900, 2.2100, 5.7000, 0.0000, 280.0000
+KORD4,19990127, 21:00:00, 21:18:00, -0.9900, 2.0100, 3.6000, 0.0000, 270.0000
+KORD5,19990127, 22:00:00, 21:56:00, -0.5900, 1.7100, 5.1000, 0.0000, 290.0000
+KORD6,19990127, 23:00:00, 22:56:00, -0.5900, 1.7100, 4.6000, 0.0000, 280.0000"""
+ header = "ID,date,NominalTime,ActualTime,TDew,TAir,Windspeed,Precip,WindDir\n"
+
+ if with_header:
+ data = header + no_header
+
+ result = parser.read_csv(StringIO(data), index_col="ID")
+ expected = parser.read_csv(StringIO(data), header=0).set_index("ID")
+ tm.assert_frame_equal(result, expected)
+ else:
+ data = no_header
+ msg = "Index ID invalid"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), index_col="ID")
+
+
+def test_index_col_named2(all_parsers):
+ parser = all_parsers
+ data = """\
+1,2,3,4,hello
+5,6,7,8,world
+9,10,11,12,foo
+"""
+
+ expected = DataFrame(
+ {"a": [1, 5, 9], "b": [2, 6, 10], "c": [3, 7, 11], "d": [4, 8, 12]},
+ index=Index(["hello", "world", "foo"], name="message"),
+ )
+ names = ["a", "b", "c", "d", "message"]
+
+ result = parser.read_csv(StringIO(data), names=names, index_col=["message"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_index_col_is_true(all_parsers):
+ # see gh-9798
+ data = "a,b\n1,2"
+ parser = all_parsers
+
+ msg = "The value of index_col couldn't be 'True'"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), index_col=True)
+
+
+@skip_pyarrow # CSV parse error: Expected 3 columns, got 4
+def test_infer_index_col(all_parsers):
+ data = """A,B,C
+foo,1,2,3
+bar,4,5,6
+baz,7,8,9
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data))
+
+ expected = DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ index=["foo", "bar", "baz"],
+ columns=["A", "B", "C"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+@pytest.mark.parametrize(
+ "index_col,kwargs",
+ [
+ (None, {"columns": ["x", "y", "z"]}),
+ (False, {"columns": ["x", "y", "z"]}),
+ (0, {"columns": ["y", "z"], "index": Index([], name="x")}),
+ (1, {"columns": ["x", "z"], "index": Index([], name="y")}),
+ ("x", {"columns": ["y", "z"], "index": Index([], name="x")}),
+ ("y", {"columns": ["x", "z"], "index": Index([], name="y")}),
+ (
+ [0, 1],
+ {
+ "columns": ["z"],
+ "index": MultiIndex.from_arrays([[]] * 2, names=["x", "y"]),
+ },
+ ),
+ (
+ ["x", "y"],
+ {
+ "columns": ["z"],
+ "index": MultiIndex.from_arrays([[]] * 2, names=["x", "y"]),
+ },
+ ),
+ (
+ [1, 0],
+ {
+ "columns": ["z"],
+ "index": MultiIndex.from_arrays([[]] * 2, names=["y", "x"]),
+ },
+ ),
+ (
+ ["y", "x"],
+ {
+ "columns": ["z"],
+ "index": MultiIndex.from_arrays([[]] * 2, names=["y", "x"]),
+ },
+ ),
+ ],
+)
+def test_index_col_empty_data(all_parsers, index_col, kwargs):
+ data = "x,y,z"
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), index_col=index_col)
+
+ expected = DataFrame(**kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_empty_with_index_col_false(all_parsers):
+ # see gh-10413
+ data = "x,y"
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), index_col=False)
+
+ expected = DataFrame(columns=["x", "y"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index_names",
+ [
+ ["", ""],
+ ["foo", ""],
+ ["", "bar"],
+ ["foo", "bar"],
+ ["NotReallyUnnamed", "Unnamed: 0"],
+ ],
+)
+def test_multi_index_naming(all_parsers, index_names, request):
+ parser = all_parsers
+
+ if parser.engine == "pyarrow" and "" in index_names:
+ mark = pytest.mark.xfail(reason="One case raises, others are wrong")
+ request.applymarker(mark)
+
+ # We don't want empty index names being replaced with "Unnamed: 0"
+ data = ",".join([*index_names, "col\na,c,1\na,d,2\nb,c,3\nb,d,4"])
+ result = parser.read_csv(StringIO(data), index_col=[0, 1])
+
+ expected = DataFrame(
+ {"col": [1, 2, 3, 4]}, index=MultiIndex.from_product([["a", "b"], ["c", "d"]])
+ )
+ expected.index.names = [name if name else None for name in index_names]
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: Found non-unique column index
+def test_multi_index_naming_not_all_at_beginning(all_parsers):
+ parser = all_parsers
+ data = ",Unnamed: 2,\na,c,1\na,d,2\nb,c,3\nb,d,4"
+ result = parser.read_csv(StringIO(data), index_col=[0, 2])
+
+ expected = DataFrame(
+ {"Unnamed: 2": ["c", "d", "c", "d"]},
+ index=MultiIndex(
+ levels=[["a", "b"], [1, 2, 3, 4]], codes=[[0, 0, 1, 1], [0, 1, 2, 3]]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: Found non-unique column index
+def test_no_multi_index_level_names_empty(temp_file, all_parsers):
+ # GH 10984
+ parser = all_parsers
+ midx = MultiIndex.from_tuples([("A", 1, 2), ("A", 1, 2), ("B", 1, 2)])
+ expected = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ index=midx,
+ columns=["x", "y", "z"],
+ )
+ expected.to_csv(temp_file)
+ result = parser.read_csv(temp_file, index_col=[0, 1, 2])
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_header_with_index_col(all_parsers):
+ # GH 33476
+ parser = all_parsers
+ data = """
+I11,A,A
+I12,B,B
+I2,1,3
+"""
+ midx = MultiIndex.from_tuples([("A", "B"), ("A", "B.1")], names=["I11", "I12"])
+ idx = Index(["I2"])
+ expected = DataFrame([[1, 3]], index=idx, columns=midx)
+
+ result = parser.read_csv(StringIO(data), index_col=0, header=[0, 1])
+ tm.assert_frame_equal(result, expected)
+
+ col_idx = Index(["A", "A.1"])
+ idx = Index(["I12", "I2"], name="I11")
+ expected = DataFrame([["B", "B"], ["1", "3"]], index=idx, columns=col_idx)
+
+ result = parser.read_csv(StringIO(data), index_col="I11", header=0)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.slow
+def test_index_col_large_csv(temp_file, all_parsers, monkeypatch):
+ # https://github.com/pandas-dev/pandas/issues/37094
+ parser = all_parsers
+
+ ARR_LEN = 100
+ df = DataFrame(
+ {
+ "a": range(ARR_LEN + 1),
+ "b": np.random.default_rng(2).standard_normal(ARR_LEN + 1),
+ }
+ )
+
+ df.to_csv(temp_file, index=False)
+ with monkeypatch.context() as m:
+ m.setattr("pandas.core.algorithms._MINIMUM_COMP_ARR_LEN", ARR_LEN)
+ result = parser.read_csv(temp_file, index_col=[0])
+
+ tm.assert_frame_equal(result, df.set_index("a"))
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_index_col_multiindex_columns_no_data(all_parsers):
+ # GH#38292
+ parser = all_parsers
+ result = parser.read_csv(
+ StringIO("a0,a1,a2\nb0,b1,b2\n"), header=[0, 1], index_col=0
+ )
+ expected = DataFrame(
+ [],
+ index=Index([]),
+ columns=MultiIndex.from_arrays(
+ [["a1", "a2"], ["b1", "b2"]], names=["a0", "b0"]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_index_col_header_no_data(all_parsers):
+ # GH#38292
+ parser = all_parsers
+ result = parser.read_csv(StringIO("a0,a1,a2\n"), header=[0], index_col=0)
+ expected = DataFrame(
+ [],
+ columns=["a1", "a2"],
+ index=Index([], name="a0"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_multiindex_columns_no_data(all_parsers):
+ # GH#38292
+ parser = all_parsers
+ result = parser.read_csv(StringIO("a0,a1,a2\nb0,b1,b2\n"), header=[0, 1])
+ expected = DataFrame(
+ [], columns=MultiIndex.from_arrays([["a0", "a1", "a2"], ["b0", "b1", "b2"]])
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_multiindex_columns_index_col_with_data(all_parsers):
+ # GH#38292
+ parser = all_parsers
+ result = parser.read_csv(
+ StringIO("a0,a1,a2\nb0,b1,b2\ndata,data,data"), header=[0, 1], index_col=0
+ )
+ expected = DataFrame(
+ [["data", "data"]],
+ columns=MultiIndex.from_arrays(
+ [["a1", "a2"], ["b1", "b2"]], names=["a0", "b0"]
+ ),
+ index=Index(["data"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Empty CSV file or block
+def test_infer_types_boolean_sum(all_parsers):
+ # GH#44079
+ parser = all_parsers
+ result = parser.read_csv(
+ StringIO("0,1"),
+ names=["a", "b"],
+ index_col=["a"],
+ dtype={"a": "UInt8"},
+ )
+ expected = DataFrame(
+ data={
+ "a": [
+ 0,
+ ],
+ "b": [1],
+ }
+ ).set_index("a")
+ # Not checking index type now, because the C parser will return a
+ # index column of dtype 'object', and the Python parser will return a
+ # index column of dtype 'int64'.
+ tm.assert_frame_equal(result, expected, check_index_type=False)
+
+
+@pytest.mark.parametrize("dtype, val", [(object, "01"), ("int64", 1)])
+def test_specify_dtype_for_index_col(all_parsers, dtype, val, request):
+ # GH#9435
+ data = "a,b\n01,2"
+ parser = all_parsers
+ if dtype == object and parser.engine == "pyarrow":
+ request.applymarker(
+ pytest.mark.xfail(reason="Cannot disable type-inference for pyarrow engine")
+ )
+ result = parser.read_csv(StringIO(data), index_col="a", dtype={"a": dtype})
+ expected = DataFrame({"b": [2]}, index=Index([val], name="a", dtype=dtype))
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_multiindex_columns_not_leading_index_col(all_parsers):
+ # GH#38549
+ parser = all_parsers
+ data = """a,b,c,d
+e,f,g,h
+x,y,1,2
+"""
+ result = parser.read_csv(
+ StringIO(data),
+ header=[0, 1],
+ index_col=1,
+ )
+ cols = MultiIndex.from_tuples(
+ [("a", "e"), ("c", "g"), ("d", "h")], names=["b", "f"]
+ )
+ expected = DataFrame([["x", 1, 2]], columns=cols, index=["y"])
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_mangle_dupes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_mangle_dupes.py
new file mode 100644
index 0000000000000000000000000000000000000000..55c8bbc4bb9e1f79ff8cea22b85a7e6104c2d9ae
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_mangle_dupes.py
@@ -0,0 +1,183 @@
+"""
+Tests that duplicate columns are handled appropriately when parsed by the
+CSV engine. In general, the expected result is that they are either thoroughly
+de-duplicated (if mangling requested) or ignored otherwise.
+"""
+
+from io import StringIO
+
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@xfail_pyarrow # ValueError: Found non-unique column index
+def test_basic(all_parsers):
+ parser = all_parsers
+
+ data = "a,a,b,b,b\n1,2,3,4,5"
+ result = parser.read_csv(StringIO(data), sep=",")
+
+ expected = DataFrame([[1, 2, 3, 4, 5]], columns=["a", "a.1", "b", "b.1", "b.2"])
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: Found non-unique column index
+def test_basic_names(all_parsers):
+ # See gh-7160
+ parser = all_parsers
+
+ data = "a,b,a\n0,1,2\n3,4,5"
+ expected = DataFrame([[0, 1, 2], [3, 4, 5]], columns=["a", "b", "a.1"])
+
+ result = parser.read_csv(StringIO(data))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_basic_names_raise(all_parsers):
+ # See gh-7160
+ parser = all_parsers
+
+ data = "0,1,2\n3,4,5"
+ with pytest.raises(ValueError, match="Duplicate names"):
+ parser.read_csv(StringIO(data), names=["a", "b", "a"])
+
+
+@xfail_pyarrow # ValueError: Found non-unique column index
+@pytest.mark.parametrize(
+ "data,expected",
+ [
+ ("a,a,a.1\n1,2,3", DataFrame([[1, 2, 3]], columns=["a", "a.2", "a.1"])),
+ (
+ "a,a,a.1,a.1.1,a.1.1.1,a.1.1.1.1\n1,2,3,4,5,6",
+ DataFrame(
+ [[1, 2, 3, 4, 5, 6]],
+ columns=["a", "a.2", "a.1", "a.1.1", "a.1.1.1", "a.1.1.1.1"],
+ ),
+ ),
+ (
+ "a,a,a.3,a.1,a.2,a,a\n1,2,3,4,5,6,7",
+ DataFrame(
+ [[1, 2, 3, 4, 5, 6, 7]],
+ columns=["a", "a.4", "a.3", "a.1", "a.2", "a.5", "a.6"],
+ ),
+ ),
+ ],
+)
+def test_thorough_mangle_columns(all_parsers, data, expected):
+ # see gh-17060
+ parser = all_parsers
+
+ result = parser.read_csv(StringIO(data))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data,names,expected",
+ [
+ (
+ "a,b,b\n1,2,3",
+ ["a.1", "a.1", "a.1.1"],
+ DataFrame(
+ [["a", "b", "b"], ["1", "2", "3"]], columns=["a.1", "a.1.1", "a.1.1.1"]
+ ),
+ ),
+ (
+ "a,b,c,d,e,f\n1,2,3,4,5,6",
+ ["a", "a", "a.1", "a.1.1", "a.1.1.1", "a.1.1.1.1"],
+ DataFrame(
+ [["a", "b", "c", "d", "e", "f"], ["1", "2", "3", "4", "5", "6"]],
+ columns=["a", "a.1", "a.1.1", "a.1.1.1", "a.1.1.1.1", "a.1.1.1.1.1"],
+ ),
+ ),
+ (
+ "a,b,c,d,e,f,g\n1,2,3,4,5,6,7",
+ ["a", "a", "a.3", "a.1", "a.2", "a", "a"],
+ DataFrame(
+ [
+ ["a", "b", "c", "d", "e", "f", "g"],
+ ["1", "2", "3", "4", "5", "6", "7"],
+ ],
+ columns=["a", "a.1", "a.3", "a.1.1", "a.2", "a.2.1", "a.3.1"],
+ ),
+ ),
+ ],
+)
+def test_thorough_mangle_names(all_parsers, data, names, expected):
+ # see gh-17095
+ parser = all_parsers
+
+ with pytest.raises(ValueError, match="Duplicate names"):
+ parser.read_csv(StringIO(data), names=names)
+
+
+@xfail_pyarrow # AssertionError: DataFrame.columns are different
+def test_mangled_unnamed_placeholders(all_parsers):
+ # xref gh-13017
+ orig_key = "0"
+ parser = all_parsers
+
+ orig_value = [1, 2, 3]
+ df = DataFrame({orig_key: orig_value})
+
+ # This test recursively updates `df`.
+ for i in range(3):
+ expected = DataFrame(columns=Index([], dtype="str"))
+
+ for j in range(i + 1):
+ col_name = "Unnamed: 0" + f".{1 * j}" * min(j, 1)
+ expected.insert(loc=0, column=col_name, value=[0, 1, 2])
+
+ expected[orig_key] = orig_value
+ df = parser.read_csv(StringIO(df.to_csv()))
+
+ tm.assert_frame_equal(df, expected)
+
+
+@xfail_pyarrow # ValueError: Found non-unique column index
+def test_mangle_dupe_cols_already_exists(all_parsers):
+ # GH#14704
+ parser = all_parsers
+
+ data = "a,a,a.1,a,a.3,a.1,a.1.1\n1,2,3,4,5,6,7"
+ result = parser.read_csv(StringIO(data))
+ expected = DataFrame(
+ [[1, 2, 3, 4, 5, 6, 7]],
+ columns=["a", "a.2", "a.1", "a.4", "a.3", "a.1.2", "a.1.1"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: Found non-unique column index
+def test_mangle_dupe_cols_already_exists_unnamed_col(all_parsers):
+ # GH#14704
+ parser = all_parsers
+
+ data = ",Unnamed: 0,,Unnamed: 2\n1,2,3,4"
+ result = parser.read_csv(StringIO(data))
+ expected = DataFrame(
+ [[1, 2, 3, 4]],
+ columns=["Unnamed: 0.1", "Unnamed: 0", "Unnamed: 2.1", "Unnamed: 2"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("usecol, engine", [([0, 1, 1], "python"), ([0, 1, 1], "c")])
+def test_mangle_cols_names(all_parsers, usecol, engine):
+ # GH 11823
+ parser = all_parsers
+ data = "1,2,3"
+ names = ["A", "A", "B"]
+ with pytest.raises(ValueError, match="Duplicate names"):
+ parser.read_csv(StringIO(data), names=names, usecols=usecol, engine=engine)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_multi_thread.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_multi_thread.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa9465c9a66af200311def2dca612fd28739c6e1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_multi_thread.py
@@ -0,0 +1,159 @@
+"""
+Tests multithreading behaviour for reading and
+parsing files for each parser defined in parsers.py
+"""
+
+from contextlib import ExitStack
+from io import BytesIO
+from multiprocessing.pool import ThreadPool
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import DataFrame
+import pandas._testing as tm
+from pandas.util.version import Version
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+
+# We'll probably always skip these for pyarrow
+# Maybe we'll add our own tests for pyarrow too
+pytestmark = [
+ pytest.mark.single_cpu,
+ pytest.mark.slow,
+]
+
+
+@pytest.mark.filterwarnings("ignore:Passing a BlockManager:DeprecationWarning")
+def test_multi_thread_string_io_read_csv(all_parsers, request):
+ # see gh-11786
+ parser = all_parsers
+ if parser.engine == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ if Version(pa.__version__) < Version("16.0"):
+ request.applymarker(
+ pytest.mark.xfail(reason="# ValueError: Found non-unique column index")
+ )
+ max_row_range = 100
+ num_files = 10
+
+ bytes_to_df = (
+ "\n".join([f"{i:d},{i:d},{i:d}" for i in range(max_row_range)]).encode()
+ for _ in range(num_files)
+ )
+
+ # Read all files in many threads.
+ with ExitStack() as stack:
+ files = [stack.enter_context(BytesIO(b)) for b in bytes_to_df]
+
+ pool = stack.enter_context(ThreadPool(8))
+
+ results = pool.map(parser.read_csv, files)
+ first_result = results[0]
+
+ for result in results:
+ tm.assert_frame_equal(first_result, result)
+
+
+def _generate_multi_thread_dataframe(parser, path, num_rows, num_tasks):
+ """
+ Generate a DataFrame via multi-thread.
+
+ Parameters
+ ----------
+ parser : BaseParser
+ The parser object to use for reading the data.
+ path : str
+ The location of the CSV file to read.
+ num_rows : int
+ The number of rows to read per task.
+ num_tasks : int
+ The number of tasks to use for reading this DataFrame.
+
+ Returns
+ -------
+ df : DataFrame
+ """
+
+ def reader(arg):
+ """
+ Create a reader for part of the CSV.
+
+ Parameters
+ ----------
+ arg : tuple
+ A tuple of the following:
+
+ * start : int
+ The starting row to start for parsing CSV
+ * nrows : int
+ The number of rows to read.
+
+ Returns
+ -------
+ df : DataFrame
+ """
+ start, nrows = arg
+
+ if not start:
+ return parser.read_csv(
+ path, index_col=0, header=0, nrows=nrows, parse_dates=["date"]
+ )
+
+ return parser.read_csv(
+ path,
+ index_col=0,
+ header=None,
+ skiprows=int(start) + 1,
+ nrows=nrows,
+ parse_dates=[9],
+ )
+
+ tasks = [
+ (num_rows * i // num_tasks, num_rows // num_tasks) for i in range(num_tasks)
+ ]
+
+ with ThreadPool(processes=num_tasks) as pool:
+ results = pool.map(reader, tasks)
+
+ header = results[0].columns
+
+ for r in results[1:]:
+ r.columns = header
+
+ final_dataframe = pd.concat(results)
+ return final_dataframe
+
+
+@xfail_pyarrow # ValueError: The 'nrows' option is not supported
+def test_multi_thread_path_multipart_read_csv(tmp_path, all_parsers):
+ # see gh-11786
+ num_tasks = 4
+ num_rows = 48
+
+ parser = all_parsers
+ file_name = "__thread_pool_reader__.csv"
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).random(num_rows),
+ "b": np.random.default_rng(2).random(num_rows),
+ "c": np.random.default_rng(2).random(num_rows),
+ "d": np.random.default_rng(2).random(num_rows),
+ "e": np.random.default_rng(2).random(num_rows),
+ "foo": ["foo"] * num_rows,
+ "bar": ["bar"] * num_rows,
+ "baz": ["baz"] * num_rows,
+ "date": pd.date_range("20000101 09:00:00", periods=num_rows, freq="s"),
+ "int": np.arange(num_rows, dtype="int64"),
+ }
+ )
+
+ path = tmp_path / file_name
+ df.to_csv(path)
+
+ result = _generate_multi_thread_dataframe(parser, path, num_rows, num_tasks)
+
+ expected = df[:]
+ expected["date"] = expected["date"].astype("M8[us]")
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_na_values.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_na_values.py
new file mode 100644
index 0000000000000000000000000000000000000000..9998ba1b49bddc6778996fa4c106c5630c32c76c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_na_values.py
@@ -0,0 +1,844 @@
+"""
+Tests that NA values are properly handled during
+parsing for all of the parsers defined in parsers.py
+"""
+
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas._libs.parsers import STR_NA_VALUES
+
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+def test_string_nas(all_parsers):
+ parser = all_parsers
+ data = """A,B,C
+a,b,c
+d,,f
+,g,h
+"""
+ result = parser.read_csv(StringIO(data))
+ expected = DataFrame(
+ [["a", "b", "c"], ["d", np.nan, "f"], [np.nan, "g", "h"]],
+ columns=["A", "B", "C"],
+ )
+ if parser.engine == "pyarrow":
+ expected.loc[2, "A"] = None
+ expected.loc[1, "B"] = None
+ tm.assert_frame_equal(result, expected)
+
+
+def test_detect_string_na(all_parsers):
+ parser = all_parsers
+ data = """A,B
+foo,bar
+NA,baz
+NaN,nan
+"""
+ expected = DataFrame(
+ [["foo", "bar"], [np.nan, "baz"], [np.nan, np.nan]], columns=["A", "B"]
+ )
+ if parser.engine == "pyarrow":
+ expected.loc[[1, 2], "A"] = None
+ expected.loc[2, "B"] = None
+ result = parser.read_csv(StringIO(data))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "na_values",
+ [
+ ["-999.0", "-999"],
+ [-999, -999.0],
+ [-999.0, -999],
+ ["-999.0"],
+ ["-999"],
+ [-999.0],
+ [-999],
+ ],
+)
+@pytest.mark.parametrize(
+ "data",
+ [
+ """A,B
+-999,1.2
+2,-999
+3,4.5
+""",
+ """A,B
+-999,1.200
+2,-999.000
+3,4.500
+""",
+ ],
+)
+def test_non_string_na_values(all_parsers, data, na_values, request):
+ # see gh-3611: with an odd float format, we can't match
+ # the string "999.0" exactly but still need float matching
+ parser = all_parsers
+ expected = DataFrame([[np.nan, 1.2], [2.0, np.nan], [3.0, 4.5]], columns=["A", "B"])
+
+ if parser.engine == "pyarrow" and not all(isinstance(x, str) for x in na_values):
+ msg = "The 'pyarrow' engine requires all na_values to be strings"
+ with pytest.raises(TypeError, match=msg):
+ parser.read_csv(StringIO(data), na_values=na_values)
+ return
+ elif parser.engine == "pyarrow" and "-999.000" in data:
+ # bc the pyarrow engine does not include the float-ified version
+ # of "-999" -> -999, it does not match the entry with the trailing
+ # zeros, so "-999.000" is not treated as null.
+ mark = pytest.mark.xfail(
+ reason="pyarrow engined does not recognize equivalent floats"
+ )
+ request.applymarker(mark)
+
+ result = parser.read_csv(StringIO(data), na_values=na_values)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_default_na_values(all_parsers):
+ _NA_VALUES = {
+ "-1.#IND",
+ "1.#QNAN",
+ "1.#IND",
+ "-1.#QNAN",
+ "#N/A",
+ "N/A",
+ "n/a",
+ "NA",
+ "",
+ "#NA",
+ "NULL",
+ "null",
+ "NaN",
+ "nan",
+ "-NaN",
+ "-nan",
+ "#N/A N/A",
+ "",
+ "None",
+ }
+ assert _NA_VALUES == STR_NA_VALUES
+
+ parser = all_parsers
+ nv = len(_NA_VALUES)
+
+ def f(i, v):
+ if i == 0:
+ buf = ""
+ elif i > 0:
+ buf = "".join([","] * i)
+
+ buf = f"{buf}{v}"
+
+ if i < nv - 1:
+ joined = "".join([","] * (nv - i - 1))
+ buf = f"{buf}{joined}"
+
+ return buf
+
+ data = StringIO("\n".join([f(i, v) for i, v in enumerate(_NA_VALUES)]))
+ expected = DataFrame(np.nan, columns=range(nv), index=range(nv))
+
+ result = parser.read_csv(data, header=None)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("na_values", ["baz", ["baz"]])
+def test_custom_na_values(all_parsers, na_values):
+ parser = all_parsers
+ data = """A,B,C
+ignore,this,row
+1,NA,3
+-1.#IND,5,baz
+7,8,NaN
+"""
+ expected = DataFrame(
+ [[1.0, np.nan, 3], [np.nan, 5, np.nan], [7, 8, np.nan]], columns=["A", "B", "C"]
+ )
+ if parser.engine == "pyarrow":
+ msg = "skiprows argument must be an integer when using engine='pyarrow'"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), na_values=na_values, skiprows=[1])
+ return
+
+ result = parser.read_csv(StringIO(data), na_values=na_values, skiprows=[1])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_bool_na_values(all_parsers):
+ data = """A,B,C
+True,False,True
+NA,True,False
+False,NA,True"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data))
+ expected = DataFrame(
+ {
+ "A": np.array([True, np.nan, False], dtype=object),
+ "B": np.array([False, True, np.nan], dtype=object),
+ "C": [True, False, True],
+ }
+ )
+ if parser.engine == "pyarrow":
+ expected.loc[1, "A"] = None
+ expected.loc[2, "B"] = None
+ tm.assert_frame_equal(result, expected)
+
+
+def test_na_value_dict(all_parsers):
+ data = """A,B,C
+foo,bar,NA
+bar,foo,foo
+foo,bar,NA
+bar,foo,foo"""
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ msg = "pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), na_values={"A": ["foo"], "B": ["bar"]})
+ return
+
+ df = parser.read_csv(StringIO(data), na_values={"A": ["foo"], "B": ["bar"]})
+ expected = DataFrame(
+ {
+ "A": [np.nan, "bar", np.nan, "bar"],
+ "B": [np.nan, "foo", np.nan, "foo"],
+ "C": [np.nan, "foo", np.nan, "foo"],
+ }
+ )
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.parametrize(
+ "index_col,expected",
+ [
+ (
+ [0],
+ DataFrame({"b": [np.nan], "c": [1], "d": [5]}, index=Index([0], name="a")),
+ ),
+ (
+ [0, 2],
+ DataFrame(
+ {"b": [np.nan], "d": [5]},
+ index=MultiIndex.from_tuples([(0, 1)], names=["a", "c"]),
+ ),
+ ),
+ (
+ ["a", "c"],
+ DataFrame(
+ {"b": [np.nan], "d": [5]},
+ index=MultiIndex.from_tuples([(0, 1)], names=["a", "c"]),
+ ),
+ ),
+ ],
+)
+def test_na_value_dict_multi_index(all_parsers, index_col, expected):
+ data = """\
+a,b,c,d
+0,NA,1,5
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), na_values=set(), index_col=index_col)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "kwargs,expected",
+ [
+ (
+ {},
+ {
+ "A": ["a", "b", np.nan, "d", "e", np.nan, "g"],
+ "B": [1, 2, 3, 4, 5, 6, 7],
+ "C": ["one", "two", "three", np.nan, "five", np.nan, "seven"],
+ },
+ ),
+ (
+ {"na_values": {"A": [], "C": []}, "keep_default_na": False},
+ {
+ "A": ["a", "b", "", "d", "e", "nan", "g"],
+ "B": [1, 2, 3, 4, 5, 6, 7],
+ "C": ["one", "two", "three", "nan", "five", "", "seven"],
+ },
+ ),
+ (
+ {"na_values": ["a"], "keep_default_na": False},
+ {
+ "A": [np.nan, "b", "", "d", "e", "nan", "g"],
+ "B": [1, 2, 3, 4, 5, 6, 7],
+ "C": ["one", "two", "three", "nan", "five", "", "seven"],
+ },
+ ),
+ (
+ {"na_values": {"A": [], "C": []}},
+ {
+ "A": ["a", "b", np.nan, "d", "e", np.nan, "g"],
+ "B": [1, 2, 3, 4, 5, 6, 7],
+ "C": ["one", "two", "three", np.nan, "five", np.nan, "seven"],
+ },
+ ),
+ ],
+)
+def test_na_values_keep_default(
+ all_parsers, kwargs, expected, request, using_infer_string
+):
+ data = """\
+A,B,C
+a,1,one
+b,2,two
+,3,three
+d,4,nan
+e,5,five
+nan,6,
+g,7,seven
+"""
+ parser = all_parsers
+ if parser.engine == "pyarrow":
+ if "na_values" in kwargs and isinstance(kwargs["na_values"], dict):
+ msg = "The pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+ return
+ if not using_infer_string or "na_values" in kwargs:
+ mark = pytest.mark.xfail()
+ request.applymarker(mark)
+
+ result = parser.read_csv(StringIO(data), **kwargs)
+ expected = DataFrame(expected)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_na_values_no_keep_default(all_parsers):
+ # see gh-4318: passing na_values=None and
+ # keep_default_na=False yields 'None" as a na_value
+ data = """\
+A,B,C
+a,1,None
+b,2,two
+,3,None
+d,4,nan
+e,5,five
+nan,6,
+g,7,seven
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), keep_default_na=False)
+
+ expected = DataFrame(
+ {
+ "A": ["a", "b", "", "d", "e", "nan", "g"],
+ "B": [1, 2, 3, 4, 5, 6, 7],
+ "C": ["None", "two", "None", "nan", "five", "", "seven"],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_keep_default_na_dict_na_values(all_parsers):
+ # see gh-19227
+ data = "a,b\n,2"
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data), na_values={"b": ["2"]}, keep_default_na=False
+ )
+ return
+
+ result = parser.read_csv(
+ StringIO(data), na_values={"b": ["2"]}, keep_default_na=False
+ )
+ expected = DataFrame({"a": [""], "b": [np.nan]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_keep_default_na_dict_na_scalar_values(all_parsers):
+ # see gh-19227
+ #
+ # Scalar values shouldn't cause the parsing to crash or fail.
+ data = "a,b\n1,2"
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), na_values={"b": 2}, keep_default_na=False)
+ return
+
+ df = parser.read_csv(StringIO(data), na_values={"b": 2}, keep_default_na=False)
+ expected = DataFrame({"a": [1], "b": [np.nan]})
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.parametrize("col_zero_na_values", [113125, "113125"])
+def test_no_keep_default_na_dict_na_values_diff_reprs(all_parsers, col_zero_na_values):
+ # see gh-19227
+ data = """\
+113125,"blah","/blaha",kjsdkj,412.166,225.874,214.008
+729639,"qwer","",asdfkj,466.681,,252.373
+"""
+ parser = all_parsers
+ expected = DataFrame(
+ {
+ 0: [np.nan, 729639.0],
+ 1: [np.nan, "qwer"],
+ 2: ["/blaha", np.nan],
+ 3: ["kjsdkj", "asdfkj"],
+ 4: [412.166, 466.681],
+ 5: ["225.874", ""],
+ 6: [np.nan, 252.373],
+ }
+ )
+
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ header=None,
+ keep_default_na=False,
+ na_values={2: "", 6: "214.008", 1: "blah", 0: col_zero_na_values},
+ )
+ return
+
+ result = parser.read_csv(
+ StringIO(data),
+ header=None,
+ keep_default_na=False,
+ na_values={2: "", 6: "214.008", 1: "blah", 0: col_zero_na_values},
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "na_filter,row_data",
+ [
+ (True, [[1, "A"], [np.nan, np.nan], [3, "C"]]),
+ (False, [["1", "A"], ["nan", "B"], ["3", "C"]]),
+ ],
+)
+def test_na_values_na_filter_override(
+ request, all_parsers, na_filter, row_data, using_infer_string
+):
+ parser = all_parsers
+ if parser.engine == "pyarrow":
+ # mismatched dtypes in both cases, FutureWarning in the True case
+ if not (using_infer_string and na_filter):
+ mark = pytest.mark.xfail(reason="pyarrow doesn't support this.")
+ request.applymarker(mark)
+ data = """\
+A,B
+1,A
+nan,B
+3,C
+"""
+ result = parser.read_csv(StringIO(data), na_values=["B"], na_filter=na_filter)
+
+ expected = DataFrame(row_data, columns=["A", "B"])
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Expected 8 columns, got 5:
+def test_na_trailing_columns(all_parsers):
+ parser = all_parsers
+ data = """Date,Currency,Symbol,Type,Units,UnitPrice,Cost,Tax
+2012-03-14,USD,AAPL,BUY,1000
+2012-05-12,USD,SBUX,SELL,500"""
+
+ # Trailing columns should be all NaN.
+ result = parser.read_csv(StringIO(data))
+ expected = DataFrame(
+ [
+ ["2012-03-14", "USD", "AAPL", "BUY", 1000, np.nan, np.nan, np.nan],
+ ["2012-05-12", "USD", "SBUX", "SELL", 500, np.nan, np.nan, np.nan],
+ ],
+ columns=[
+ "Date",
+ "Currency",
+ "Symbol",
+ "Type",
+ "Units",
+ "UnitPrice",
+ "Cost",
+ "Tax",
+ ],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "na_values,row_data",
+ [
+ (1, [[np.nan, 2.0], [2.0, np.nan]]),
+ ({"a": 2, "b": 1}, [[1.0, 2.0], [np.nan, np.nan]]),
+ ],
+)
+def test_na_values_scalar(all_parsers, na_values, row_data):
+ # see gh-12224
+ parser = all_parsers
+ names = ["a", "b"]
+ data = "1,2\n2,1"
+
+ if parser.engine == "pyarrow" and isinstance(na_values, dict):
+ msg = "The pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), names=names, na_values=na_values)
+ return
+ elif parser.engine == "pyarrow":
+ msg = "The 'pyarrow' engine requires all na_values to be strings"
+ with pytest.raises(TypeError, match=msg):
+ parser.read_csv(StringIO(data), names=names, na_values=na_values)
+ return
+
+ result = parser.read_csv(StringIO(data), names=names, na_values=na_values)
+ expected = DataFrame(row_data, columns=names)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_na_values_dict_aliasing(all_parsers):
+ parser = all_parsers
+ na_values = {"a": 2, "b": 1}
+ na_values_copy = na_values.copy()
+
+ names = ["a", "b"]
+ data = "1,2\n2,1"
+
+ expected = DataFrame([[1.0, 2.0], [np.nan, np.nan]], columns=names)
+
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), names=names, na_values=na_values)
+ return
+
+ result = parser.read_csv(StringIO(data), names=names, na_values=na_values)
+
+ tm.assert_frame_equal(result, expected)
+ tm.assert_dict_equal(na_values, na_values_copy)
+
+
+def test_na_values_dict_null_column_name(all_parsers):
+ # see gh-57547
+ parser = all_parsers
+ data = ",x,y\n\nMA,1,2\nNA,2,1\nOA,,3"
+ names = [None, "x", "y"]
+ na_values = dict.fromkeys(names, STR_NA_VALUES)
+ dtype = {None: "object", "x": "float64", "y": "float64"}
+
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ index_col=0,
+ header=0,
+ dtype=dtype,
+ names=names,
+ na_values=na_values,
+ keep_default_na=False,
+ )
+ return
+
+ expected = DataFrame(
+ {"x": [1.0, 2.0, np.nan], "y": [2.0, 1.0, 3.0]},
+ index=Index(["MA", "NA", "OA"], dtype=object),
+ )
+
+ result = parser.read_csv(
+ StringIO(data),
+ index_col=0,
+ header=0,
+ dtype=dtype,
+ names=names,
+ na_values=na_values,
+ keep_default_na=False,
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_na_values_dict_col_index(all_parsers):
+ # see gh-14203
+ data = "a\nfoo\n1"
+ parser = all_parsers
+ na_values = {0: "foo"}
+
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), na_values=na_values)
+ return
+
+ result = parser.read_csv(StringIO(data), na_values=na_values)
+ expected = DataFrame({"a": [np.nan, 1]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data,kwargs,expected",
+ [
+ (
+ str(2**63) + "\n" + str(2**63 + 1),
+ {"na_values": [2**63]},
+ [str(2**63), str(2**63 + 1)],
+ ),
+ (str(2**63) + ",1" + "\n,2", {}, [[str(2**63), 1], ["", 2]]),
+ (str(2**63) + "\n1", {"na_values": [2**63]}, [np.nan, 1]),
+ ],
+)
+def test_na_values_uint64(all_parsers, data, kwargs, expected, request):
+ # see gh-14983
+ parser = all_parsers
+
+ if parser.engine == "pyarrow" and "na_values" in kwargs:
+ msg = "The 'pyarrow' engine requires all na_values to be strings"
+ with pytest.raises(TypeError, match=msg):
+ parser.read_csv(StringIO(data), header=None, **kwargs)
+ return
+ elif parser.engine == "pyarrow":
+ mark = pytest.mark.xfail(reason="Returns float64 instead of object")
+ request.applymarker(mark)
+
+ result = parser.read_csv(StringIO(data), header=None, **kwargs)
+ expected = DataFrame(expected)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_empty_na_values_no_default_with_index(all_parsers):
+ # see gh-15835
+ data = "a,1\nb,2"
+ parser = all_parsers
+ expected = DataFrame({"1": [2]}, index=Index(["b"], name="a"))
+
+ result = parser.read_csv(StringIO(data), index_col=0, keep_default_na=False)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "na_filter,index_data", [(False, ["", "5"]), (True, [np.nan, 5.0])]
+)
+def test_no_na_filter_on_index(all_parsers, na_filter, index_data, request):
+ # see gh-5239
+ #
+ # Don't parse NA-values in index unless na_filter=True
+ parser = all_parsers
+ data = "a,b,c\n1,,3\n4,5,6"
+
+ if parser.engine == "pyarrow" and na_filter is False:
+ mark = pytest.mark.xfail(reason="mismatched index result")
+ request.applymarker(mark)
+
+ expected = DataFrame({"a": [1, 4], "c": [3, 6]}, index=Index(index_data, name="b"))
+ result = parser.read_csv(StringIO(data), index_col=[1], na_filter=na_filter)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_inf_na_values_with_int_index(all_parsers):
+ # see gh-17128
+ parser = all_parsers
+ data = "idx,col1,col2\n1,3,4\n2,inf,-inf"
+
+ # Don't fail with OverflowError with inf's and integer index column.
+ out = parser.read_csv(StringIO(data), index_col=[0], na_values=["inf", "-inf"])
+ expected = DataFrame(
+ {"col1": [3, np.nan], "col2": [4, np.nan]}, index=Index([1, 2], name="idx")
+ )
+ tm.assert_frame_equal(out, expected)
+
+
+@pytest.mark.parametrize("na_filter", [True, False])
+def test_na_values_with_dtype_str_and_na_filter(
+ all_parsers, na_filter, using_infer_string, request
+):
+ # see gh-20377
+ parser = all_parsers
+ if parser.engine == "pyarrow" and (na_filter is False or not using_infer_string):
+ mark = pytest.mark.xfail(reason="mismatched shape")
+ request.applymarker(mark)
+
+ data = "a,b,c\n1,,3\n4,5,6"
+
+ # na_filter=True --> missing value becomes NaN.
+ # na_filter=False --> missing value remains empty string.
+ empty = np.nan if na_filter else ""
+ expected = DataFrame({"a": ["1", "4"], "b": [empty, "5"], "c": ["3", "6"]})
+
+ result = parser.read_csv(StringIO(data), na_filter=na_filter, dtype=str)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # mismatched exception message
+@pytest.mark.parametrize(
+ "data, na_values",
+ [
+ ("false,1\n,1\ntrue", None),
+ ("false,1\nnull,1\ntrue", None),
+ ("false,1\nnan,1\ntrue", None),
+ ("false,1\nfoo,1\ntrue", "foo"),
+ ("false,1\nfoo,1\ntrue", ["foo"]),
+ ("false,1\nfoo,1\ntrue", {"a": "foo"}),
+ ],
+)
+def test_cast_NA_to_bool_raises_error(all_parsers, data, na_values):
+ parser = all_parsers
+ msg = "|".join(
+ [
+ "Bool column has NA values in column [0a]",
+ "cannot safely convert passed user dtype of "
+ "bool for object dtyped data in column 0",
+ ]
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ header=None,
+ names=["a", "b"],
+ dtype={"a": "bool"},
+ na_values=na_values,
+ )
+
+
+# TODO: this test isn't about the na_values keyword, it is about the empty entries
+# being returned with NaN entries, whereas the pyarrow engine returns "nan"
+@xfail_pyarrow # mismatched shapes
+def test_str_nan_dropped(all_parsers):
+ # see gh-21131
+ parser = all_parsers
+
+ data = """File: small.csv,,
+10010010233,0123,654
+foo,,bar
+01001000155,4530,898"""
+
+ result = parser.read_csv(
+ StringIO(data),
+ header=None,
+ names=["col1", "col2", "col3"],
+ dtype={"col1": str, "col2": str, "col3": str},
+ ).dropna()
+
+ expected = DataFrame(
+ {
+ "col1": ["10010010233", "01001000155"],
+ "col2": ["0123", "4530"],
+ "col3": ["654", "898"],
+ },
+ index=[1, 3],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_nan_multi_index(all_parsers):
+ # GH 42446
+ parser = all_parsers
+ data = "A,B,B\nX,Y,Z\n1,2,inf"
+
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine doesn't support passing a dict for na_values"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data), header=list(range(2)), na_values={("B", "Z"): "inf"}
+ )
+ return
+
+ result = parser.read_csv(
+ StringIO(data), header=list(range(2)), na_values={("B", "Z"): "inf"}
+ )
+
+ expected = DataFrame(
+ {
+ ("A", "X"): [1],
+ ("B", "Y"): [2],
+ ("B", "Z"): [np.nan],
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # Failed: DID NOT RAISE ; it casts the NaN to False
+def test_bool_and_nan_to_bool(all_parsers):
+ # GH#42808
+ parser = all_parsers
+ data = """0
+NaN
+True
+False
+"""
+ with pytest.raises(ValueError, match="NA values"):
+ parser.read_csv(StringIO(data), dtype="bool")
+
+
+def test_bool_and_nan_to_int(all_parsers):
+ # GH#42808
+ parser = all_parsers
+ data = """0
+NaN
+True
+False
+"""
+ msg = (
+ "cannot safely convert passed user dtype of int(64|32) for "
+ " dtyped data in column 0 due to NA values"
+ )
+ if parser.engine == "python":
+ msg = "Unable to convert column 0 to type int(64|32)"
+ elif parser.engine == "pyarrow":
+ msg = (
+ r"int\(\) argument must be a string, a bytes-like object or a "
+ "real number, not 'NoneType"
+ )
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), dtype="int")
+
+
+def test_bool_and_nan_to_float(all_parsers):
+ # GH#42808
+ parser = all_parsers
+ data = """0
+NaN
+True
+False
+"""
+ result = parser.read_csv(StringIO(data), dtype="float")
+ expected = DataFrame.from_dict({"0": [np.nan, 1.0, 0.0]})
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow
+@pytest.mark.parametrize(
+ "na_values",
+ [[-99.0, -99], [-99, -99.0]],
+)
+def test_na_values_dict_without_dtype(all_parsers, na_values):
+ parser = all_parsers
+ data = """A
+-99
+-99
+-99.0
+-99.0"""
+
+ result = parser.read_csv(StringIO(data), na_values=na_values)
+ expected = DataFrame({"A": [np.nan, np.nan, np.nan, np.nan]})
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_network.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_network.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6bdff5be9b4e06a28a1df2eabbd38cc45a3a772
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_network.py
@@ -0,0 +1,244 @@
+"""
+Tests parsers ability to read and parse non-local files
+and hence require a network connection to be read.
+"""
+
+from io import BytesIO
+import logging
+import re
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+from pandas.io.feather_format import read_feather
+from pandas.io.parsers import read_csv
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.mark.network
+@pytest.mark.single_cpu
+@pytest.mark.parametrize("mode", ["explicit", "infer"])
+@pytest.mark.parametrize("engine", ["python", "c"])
+def test_compressed_urls(
+ httpserver,
+ datapath,
+ salaries_table,
+ mode,
+ engine,
+ compression_only,
+ compression_to_extension,
+):
+ # test reading compressed urls with various engines and
+ # extension inference
+ if compression_only == "tar":
+ pytest.skip("TODO: Add tar salaries.csv to pandas/io/parsers/data")
+
+ extension = compression_to_extension[compression_only]
+ with open(datapath("io", "parser", "data", "salaries.csv" + extension), "rb") as f:
+ httpserver.serve_content(content=f.read())
+
+ url = httpserver.url + "/salaries.csv" + extension
+
+ if mode != "explicit":
+ compression_only = mode
+
+ url_table = read_csv(url, sep="\t", compression=compression_only, engine=engine)
+ tm.assert_frame_equal(url_table, salaries_table)
+
+
+@pytest.mark.network
+@pytest.mark.single_cpu
+def test_url_encoding_csv(httpserver, datapath):
+ """
+ read_csv should honor the requested encoding for URLs.
+
+ GH 10424
+ """
+ with open(datapath("io", "parser", "data", "unicode_series.csv"), "rb") as f:
+ httpserver.serve_content(content=f.read())
+ df = read_csv(httpserver.url, encoding="latin-1", header=None)
+ assert df.loc[15, 1] == "Á köldum klaka (Cold Fever) (1994)"
+
+
+@pytest.fixture
+def tips_df(datapath):
+ """DataFrame with the tips dataset."""
+ return read_csv(datapath("io", "data", "csv", "tips.csv"))
+
+
+@pytest.mark.single_cpu
+@pytest.mark.network
+@td.skip_if_not_us_locale()
+class TestS3:
+ @pytest.mark.parametrize(
+ "suffix, compression",
+ [
+ ("", None),
+ (".gz", "gzip"),
+ (".bz2", "bz2"),
+ ],
+ )
+ @pytest.mark.parametrize("nrows", [None, 10])
+ @pytest.mark.parametrize("engine", ["c", "python"])
+ def test_parse_public_s3_bucket(
+ self,
+ s3_bucket_public_with_data,
+ s3so,
+ tips_df,
+ suffix,
+ compression,
+ nrows,
+ engine,
+ ):
+ # more of an integration test due to the not-public contents portion
+ # can probably mock this though.
+ pytest.importorskip("s3fs")
+ df = read_csv(
+ f"s3://{s3_bucket_public_with_data.name}/tips.csv{suffix}",
+ nrows=nrows,
+ compression=compression,
+ storage_options=s3so,
+ engine=engine,
+ )
+ tm.assert_frame_equal(df, tips_df.iloc[:nrows])
+
+ def test_parse_private_s3_bucket(self, s3_bucket_private_with_data, s3so, tips_df):
+ # Read public file from bucket with not-public contents
+ pytest.importorskip("s3fs")
+ df = read_csv(
+ f"s3://{s3_bucket_private_with_data.name}/tips.csv", storage_options=s3so
+ )
+ tm.assert_frame_equal(df, tips_df)
+
+ @pytest.mark.parametrize("scheme", ["s3n", "s3a"])
+ def test_parse_public_bucket_s3n_s3a(
+ self, s3_bucket_public_with_data, s3so, tips_df, scheme
+ ):
+ nrows = 10
+ df = read_csv(
+ f"{scheme}://{s3_bucket_public_with_data.name}/tips.csv",
+ nrows=nrows,
+ storage_options=s3so,
+ )
+ tm.assert_frame_equal(df, tips_df.iloc[:nrows])
+
+ @pytest.mark.parametrize(
+ "suffix, compression",
+ [
+ ("", None),
+ (".gz", "gzip"),
+ (".bz2", "bz2"),
+ ],
+ )
+ @pytest.mark.parametrize("engine", ["c", "python"])
+ def test_parse_public_s3_bucket_chunked(
+ self, s3_bucket_public_with_data, s3so, tips_df, suffix, compression, engine
+ ):
+ # Read with a chunksize
+ chunksize = 5
+ with read_csv(
+ f"s3://{s3_bucket_public_with_data.name}/tips.csv{suffix}",
+ chunksize=chunksize,
+ compression=compression,
+ storage_options=s3so,
+ engine=engine,
+ ) as df_reader:
+ assert df_reader.chunksize == chunksize
+ for i_chunk in [0, 1, 2]:
+ # Read a couple of chunks and make sure we see them
+ # properly.
+ df = df_reader.get_chunk()
+ assert isinstance(df, DataFrame)
+ assert not df.empty
+ true_df = tips_df.iloc[chunksize * i_chunk : chunksize * (i_chunk + 1)]
+ tm.assert_frame_equal(true_df, df)
+
+ @pytest.mark.parametrize("suffix", ["", ".gz", ".bz2"])
+ def test_infer_s3_compression(
+ self, s3_bucket_public_with_data, s3so, tips_df, suffix
+ ):
+ df = read_csv(
+ f"s3://{s3_bucket_public_with_data.name}/tips.csv{suffix}",
+ engine="python",
+ compression="infer",
+ storage_options=s3so,
+ )
+ tm.assert_frame_equal(df, tips_df)
+
+ def test_read_s3_fails(self, s3_bucket_public_with_data, s3so):
+ msg = "The specified bucket does not exist"
+ with pytest.raises(OSError, match=msg):
+ read_csv("s3://nyqpug/asdf.csv", storage_options=s3so)
+
+ def test_read_s3_fails_private(self, s3_bucket_private_with_data, s3so):
+ s3_url = f"{s3_bucket_private_with_data.name}/file.csv"
+ msg = rf"{s3_url}"
+ # Receive a permission error when trying to read a private bucket.
+ # It's irrelevant here that this isn't actually a table.
+ with pytest.raises(FileNotFoundError, match=msg):
+ read_csv(
+ f"s3://{s3_url}",
+ storage_options=s3so,
+ )
+
+ @pytest.mark.single_cpu
+ def test_read_csv_handles_boto_s3_object(
+ self, s3_bucket_public_with_data, tips_file
+ ):
+ # see gh-16135
+
+ s3_object = s3_bucket_public_with_data.Object("tips.csv")
+
+ with BytesIO(s3_object.get()["Body"].read()) as buffer:
+ result = read_csv(buffer, encoding="utf8")
+ assert isinstance(result, DataFrame)
+ assert not result.empty
+
+ expected = read_csv(tips_file)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.single_cpu
+ def test_read_csv_chunked_download(self, s3_bucket_public, s3so, caplog):
+ # 8 MB, S3FS uses 5MB chunks
+ df = DataFrame(np.zeros((100000, 4)), columns=list("abcd"))
+ with BytesIO(df.to_csv().encode("utf-8")) as buf:
+ s3_bucket_public.put_object(Key="large-file.csv", Body=buf)
+ uri = f"{s3_bucket_public.name}/large-file.csv"
+ match_re = re.compile(rf"^Fetch: {uri}, 0-(?P\d+)$")
+ with caplog.at_level(logging.DEBUG, logger="s3fs"):
+ read_csv(
+ f"s3://{uri}",
+ nrows=5,
+ storage_options=s3so,
+ )
+ for log in caplog.messages:
+ if match := re.match(match_re, log):
+ # Less than 8 MB
+ assert int(match.group("stop")) < 8000000
+
+ def test_read_s3_with_hash_in_key(self, s3_bucket_public_with_data, s3so, tips_df):
+ # GH 25945
+ result = read_csv(
+ f"s3://{s3_bucket_public_with_data.name}/tips#1.csv", storage_options=s3so
+ )
+ tm.assert_frame_equal(tips_df, result)
+
+ def test_read_feather_s3_file_path(
+ self, s3_bucket_public_with_data, s3so, feather_file
+ ):
+ # GH 29055
+ pytest.importorskip("pyarrow")
+ expected = read_feather(feather_file)
+ res = read_feather(
+ f"s3://{s3_bucket_public_with_data.name}/simple_dataset.feather",
+ storage_options=s3so,
+ )
+ tm.assert_frame_equal(expected, res)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_parse_dates.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_parse_dates.py
new file mode 100644
index 0000000000000000000000000000000000000000..7aed7acb8e50db5535b066a33e22f06a1ad7e87c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_parse_dates.py
@@ -0,0 +1,861 @@
+"""
+Tests date parsing functionality for all of the
+parsers defined in parsers.py
+"""
+
+from datetime import (
+ datetime,
+ timedelta,
+ timezone,
+)
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.core.indexes.datetimes import date_range
+from pandas.core.tools.datetimes import start_caching_at
+
+from pandas.io.parsers import read_csv
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+def test_date_col_as_index_col(all_parsers):
+ data = """\
+KORD,19990127 19:00:00, 18:56:00, 0.8100, 2.8100, 7.2000, 0.0000, 280.0000
+KORD,19990127 20:00:00, 19:56:00, 0.0100, 2.2100, 7.2000, 0.0000, 260.0000
+KORD,19990127 21:00:00, 20:56:00, -0.5900, 2.2100, 5.7000, 0.0000, 280.0000
+KORD,19990127 21:00:00, 21:18:00, -0.9900, 2.0100, 3.6000, 0.0000, 270.0000
+KORD,19990127 22:00:00, 21:56:00, -0.5900, 1.7100, 5.1000, 0.0000, 290.0000
+"""
+ parser = all_parsers
+ kwds = {
+ "header": None,
+ "parse_dates": [1],
+ "index_col": 1,
+ "names": ["X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7"],
+ }
+ result = parser.read_csv(StringIO(data), **kwds)
+
+ index = Index(
+ [
+ datetime(1999, 1, 27, 19, 0),
+ datetime(1999, 1, 27, 20, 0),
+ datetime(1999, 1, 27, 21, 0),
+ datetime(1999, 1, 27, 21, 0),
+ datetime(1999, 1, 27, 22, 0),
+ ],
+ dtype="M8[us]",
+ name="X1",
+ )
+ expected = DataFrame(
+ [
+ ["KORD", " 18:56:00", 0.81, 2.81, 7.2, 0.0, 280.0],
+ ["KORD", " 19:56:00", 0.01, 2.21, 7.2, 0.0, 260.0],
+ ["KORD", " 20:56:00", -0.59, 2.21, 5.7, 0.0, 280.0],
+ ["KORD", " 21:18:00", -0.99, 2.01, 3.6, 0.0, 270.0],
+ ["KORD", " 21:56:00", -0.59, 1.71, 5.1, 0.0, 290.0],
+ ],
+ columns=["X0", "X2", "X3", "X4", "X5", "X6", "X7"],
+ index=index,
+ )
+ if parser.engine == "pyarrow":
+ # https://github.com/pandas-dev/pandas/issues/44231
+ # pyarrow 6.0 starts to infer time type
+ expected["X2"] = pd.to_datetime("1970-01-01" + expected["X2"]).dt.time
+
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow
+def test_nat_parse(all_parsers, temp_file):
+ # see gh-3062
+ parser = all_parsers
+ df = DataFrame(
+ {
+ "A": np.arange(10, dtype="float64"),
+ "B": Timestamp("20010101"),
+ }
+ )
+ df.iloc[3:6, :] = np.nan
+
+ path = temp_file
+ df.to_csv(path)
+
+ result = parser.read_csv(path, index_col=0, parse_dates=["B"])
+ tm.assert_frame_equal(result, df)
+
+
+@skip_pyarrow
+def test_parse_dates_implicit_first_col(all_parsers):
+ data = """A,B,C
+20090101,a,1,2
+20090102,b,3,4
+20090103,c,4,5
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), parse_dates=True)
+
+ expected = parser.read_csv(StringIO(data), index_col=0, parse_dates=True)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow
+def test_parse_dates_string(all_parsers):
+ data = """date,A,B,C
+20090101,a,1,2
+20090102,b,3,4
+20090103,c,4,5
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), index_col="date", parse_dates=["date"])
+ # freq doesn't round-trip
+ index = date_range("1/1/2009", periods=3, name="date", unit="us")._with_freq(None)
+
+ expected = DataFrame(
+ {"A": ["a", "b", "c"], "B": [1, 3, 4], "C": [2, 4, 5]}, index=index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow
+@pytest.mark.parametrize("parse_dates", [[0, 2], ["a", "c"]])
+def test_parse_dates_column_list(all_parsers, parse_dates):
+ data = "a,b,c\n01/01/2010,1,15/02/2010"
+ parser = all_parsers
+
+ expected = DataFrame(
+ {"a": [datetime(2010, 1, 1)], "b": [1], "c": [datetime(2010, 2, 15)]}
+ )
+ expected["a"] = expected["a"].astype("M8[us]")
+ expected["c"] = expected["c"].astype("M8[us]")
+ expected = expected.set_index(["a", "b"])
+
+ result = parser.read_csv(
+ StringIO(data), index_col=[0, 1], parse_dates=parse_dates, dayfirst=True
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow
+@pytest.mark.parametrize("index_col", [[0, 1], [1, 0]])
+def test_multi_index_parse_dates(all_parsers, index_col):
+ data = """index1,index2,A,B,C
+20090101,one,a,1,2
+20090101,two,b,3,4
+20090101,three,c,4,5
+20090102,one,a,1,2
+20090102,two,b,3,4
+20090102,three,c,4,5
+20090103,one,a,1,2
+20090103,two,b,3,4
+20090103,three,c,4,5
+"""
+ parser = all_parsers
+ dti = date_range("2009-01-01", periods=3, freq="D", unit="us")
+ index = MultiIndex.from_product(
+ [
+ dti,
+ ("one", "two", "three"),
+ ],
+ names=["index1", "index2"],
+ )
+
+ # Out of order.
+ if index_col == [1, 0]:
+ index = index.swaplevel(0, 1)
+
+ expected = DataFrame(
+ [
+ ["a", 1, 2],
+ ["b", 3, 4],
+ ["c", 4, 5],
+ ["a", 1, 2],
+ ["b", 3, 4],
+ ["c", 4, 5],
+ ["a", 1, 2],
+ ["b", 3, 4],
+ ["c", 4, 5],
+ ],
+ columns=["A", "B", "C"],
+ index=index,
+ )
+ result = parser.read_csv_check_warnings(
+ UserWarning,
+ "Could not infer format",
+ StringIO(data),
+ index_col=index_col,
+ parse_dates=True,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_parse_tz_aware(all_parsers):
+ # See gh-1693
+ parser = all_parsers
+ data = "Date,x\n2012-06-13T01:39:00Z,0.5"
+
+ result = parser.read_csv(StringIO(data), index_col=0, parse_dates=True)
+ expected = DataFrame(
+ {"x": [0.5]}, index=Index([Timestamp("2012-06-13 01:39:00+00:00")], name="Date")
+ )
+ if parser.engine == "pyarrow":
+ pytz = pytest.importorskip("pytz")
+ expected_tz = pytz.utc
+ expected.index = expected.index.as_unit("s")
+ else:
+ expected_tz = timezone.utc
+ tm.assert_frame_equal(result, expected)
+ assert result.index.tz is expected_tz
+
+
+@pytest.mark.parametrize("kwargs", [{}, {"index_col": "C"}])
+def test_read_with_parse_dates_scalar_non_bool(all_parsers, kwargs):
+ # see gh-5636
+ parser = all_parsers
+ msg = "Only booleans and lists are accepted for the 'parse_dates' parameter"
+ data = """A,B,C
+ 1,2,2003-11-1"""
+
+ with pytest.raises(TypeError, match=msg):
+ parser.read_csv(StringIO(data), parse_dates="C", **kwargs)
+
+
+@pytest.mark.parametrize("parse_dates", [(1,), np.array([4, 5]), {1, 3}])
+def test_read_with_parse_dates_invalid_type(all_parsers, parse_dates):
+ parser = all_parsers
+ msg = "Only booleans and lists are accepted for the 'parse_dates' parameter"
+ data = """A,B,C
+ 1,2,2003-11-1"""
+
+ with pytest.raises(TypeError, match=msg):
+ parser.read_csv(StringIO(data), parse_dates=parse_dates)
+
+
+@pytest.mark.parametrize("value", ["nan", ""])
+def test_bad_date_parse(all_parsers, cache, value):
+ # if we have an invalid date make sure that we handle this with
+ # and w/o the cache properly
+ parser = all_parsers
+ s = StringIO((f"{value},\n") * (start_caching_at + 1))
+
+ parser.read_csv(
+ s,
+ header=None,
+ names=["foo", "bar"],
+ parse_dates=["foo"],
+ cache_dates=cache,
+ )
+
+
+def test_bad_date_parse_with_warning(all_parsers, cache):
+ # if we have an invalid date make sure that we handle this with
+ # and w/o the cache properly.
+ parser = all_parsers
+ s = StringIO(("0,\n") * (start_caching_at + 1))
+
+ if parser.engine == "pyarrow":
+ # pyarrow reads "0" as 0 (of type int64), and so
+ # pandas doesn't try to guess the datetime format
+ # TODO: parse dates directly in pyarrow, see
+ # https://github.com/pandas-dev/pandas/issues/48017
+ warn = None
+ elif cache:
+ # Note: warning is not raised if 'cache_dates', because here there is only a
+ # single unique date and hence no risk of inconsistent parsing.
+ warn = None
+ else:
+ warn = UserWarning
+ parser.read_csv_check_warnings(
+ warn,
+ "Could not infer format",
+ s,
+ header=None,
+ names=["foo", "bar"],
+ parse_dates=["foo"],
+ cache_dates=cache,
+ raise_on_extra_warnings=False,
+ )
+
+
+def test_parse_dates_empty_string(all_parsers):
+ # see gh-2263
+ parser = all_parsers
+ data = "Date,test\n2012-01-01,1\n,2"
+ result = parser.read_csv(StringIO(data), parse_dates=["Date"], na_filter=False)
+
+ expected = DataFrame(
+ [[datetime(2012, 1, 1), 1], [pd.NaT, 2]], columns=["Date", "test"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow
+@pytest.mark.parametrize(
+ "data,kwargs,expected",
+ [
+ (
+ "a\n04.15.2016",
+ {"parse_dates": ["a"]},
+ DataFrame([datetime(2016, 4, 15)], columns=["a"], dtype="M8[us]"),
+ ),
+ (
+ "a\n04.15.2016",
+ {"parse_dates": True, "index_col": 0},
+ DataFrame(
+ index=DatetimeIndex(["2016-04-15"], dtype="M8[us]", name="a"),
+ columns=[],
+ ),
+ ),
+ (
+ "a,b\n04.15.2016,09.16.2013",
+ {"parse_dates": ["a", "b"]},
+ DataFrame(
+ [[datetime(2016, 4, 15), datetime(2013, 9, 16)]],
+ dtype="M8[us]",
+ columns=["a", "b"],
+ ),
+ ),
+ (
+ "a,b\n04.15.2016,09.16.2013",
+ {"parse_dates": True, "index_col": [0, 1]},
+ DataFrame(
+ index=MultiIndex.from_tuples(
+ [
+ (
+ Timestamp(2016, 4, 15),
+ Timestamp(2013, 9, 16),
+ )
+ ],
+ names=["a", "b"],
+ ),
+ columns=[],
+ ),
+ ),
+ ],
+)
+def test_parse_dates_no_convert_thousands(all_parsers, data, kwargs, expected):
+ # see gh-14066
+ parser = all_parsers
+
+ result = parser.read_csv(StringIO(data), thousands=".", **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_parse_date_column_with_empty_string(all_parsers):
+ # see gh-6428
+ parser = all_parsers
+ data = "case,opdate\n7,10/18/2006\n7,10/18/2008\n621, "
+ result = parser.read_csv(StringIO(data), parse_dates=["opdate"])
+
+ expected_data = [[7, "10/18/2006"], [7, "10/18/2008"], [621, " "]]
+ expected = DataFrame(expected_data, columns=["case", "opdate"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data,expected",
+ [
+ (
+ "a\n135217135789158401\n1352171357E+5",
+ [135217135789158401, 135217135700000],
+ ),
+ (
+ "a\n99999999999\n123456789012345\n1234E+0",
+ [99999999999, 123456789012345, 1234],
+ ),
+ ],
+)
+@pytest.mark.parametrize("parse_dates", [True, False])
+def test_parse_date_float(all_parsers, data, expected, parse_dates):
+ # see gh-2697
+ #
+ # Date parsing should fail, so we leave the data untouched
+ # (i.e. float precision should remain unchanged).
+ parser = all_parsers
+
+ result = parser.read_csv(StringIO(data), parse_dates=parse_dates)
+ expected = DataFrame({"a": expected}, dtype="float64")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_parse_timezone(all_parsers):
+ # see gh-22256
+ parser = all_parsers
+ data = """dt,val
+ 2018-01-04 09:01:00+09:00,23350
+ 2018-01-04 09:02:00+09:00,23400
+ 2018-01-04 09:03:00+09:00,23400
+ 2018-01-04 09:04:00+09:00,23400
+ 2018-01-04 09:05:00+09:00,23400"""
+ result = parser.read_csv(StringIO(data), parse_dates=["dt"])
+
+ dti = date_range(
+ start="2018-01-04 09:01:00",
+ end="2018-01-04 09:05:00",
+ freq="1min",
+ tz=timezone(timedelta(minutes=540)),
+ unit="us",
+ )._with_freq(None)
+ expected_data = {"dt": dti, "val": [23350, 23400, 23400, 23400, 23400]}
+
+ expected = DataFrame(expected_data)
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # pandas.errors.ParserError: CSV parse error
+@pytest.mark.parametrize(
+ "date_string",
+ ["32/32/2019", "02/30/2019", "13/13/2019", "13/2019", "a3/11/2018", "10/11/2o17"],
+)
+def test_invalid_parse_delimited_date(all_parsers, date_string):
+ parser = all_parsers
+ expected = DataFrame({0: [date_string]}, dtype="str")
+ result = parser.read_csv(
+ StringIO(date_string),
+ header=None,
+ parse_dates=[0],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "date_string,dayfirst,expected",
+ [
+ # %d/%m/%Y; month > 12 thus replacement
+ ("13/02/2019", True, datetime(2019, 2, 13)),
+ # %m/%d/%Y; day > 12 thus there will be no replacement
+ ("02/13/2019", False, datetime(2019, 2, 13)),
+ # %d/%m/%Y; dayfirst==True thus replacement
+ ("04/02/2019", True, datetime(2019, 2, 4)),
+ ],
+)
+def test_parse_delimited_date_swap_no_warning(
+ all_parsers, date_string, dayfirst, expected, request
+):
+ parser = all_parsers
+ expected = DataFrame({0: [expected]}, dtype="datetime64[us]")
+ if parser.engine == "pyarrow":
+ if not dayfirst:
+ # "CSV parse error: Empty CSV file or block"
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+ msg = "The 'dayfirst' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(date_string), header=None, dayfirst=dayfirst, parse_dates=[0]
+ )
+ return
+
+ result = parser.read_csv(
+ StringIO(date_string), header=None, dayfirst=dayfirst, parse_dates=[0]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+# ArrowInvalid: CSV parse error: Empty CSV file or block: cannot infer number of columns
+@skip_pyarrow
+@pytest.mark.parametrize(
+ "date_string,dayfirst,expected",
+ [
+ # %d/%m/%Y; month > 12
+ ("13/02/2019", False, datetime(2019, 2, 13)),
+ # %m/%d/%Y; day > 12
+ ("02/13/2019", True, datetime(2019, 2, 13)),
+ ],
+)
+def test_parse_delimited_date_swap_with_warning(
+ all_parsers, date_string, dayfirst, expected
+):
+ parser = all_parsers
+ expected = DataFrame({0: [expected]}, dtype="datetime64[us]")
+ warning_msg = (
+ "Parsing dates in .* format when dayfirst=.* was specified. "
+ "Pass `dayfirst=.*` or specify a format to silence this warning."
+ )
+ result = parser.read_csv_check_warnings(
+ UserWarning,
+ warning_msg,
+ StringIO(date_string),
+ header=None,
+ dayfirst=dayfirst,
+ parse_dates=[0],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_parse_multiple_delimited_dates_with_swap_warnings():
+ # GH46210
+ with pytest.raises(
+ ValueError,
+ match=(
+ r'^time data "31/05/2000" doesn\'t match format "%m/%d/%Y". '
+ r"You might want to try:"
+ ),
+ ):
+ pd.to_datetime(["01/01/2000", "31/05/2000", "31/05/2001", "01/02/2000"])
+
+
+# ArrowKeyError: Column 'fdate1' in include_columns does not exist in CSV file
+@skip_pyarrow
+@pytest.mark.parametrize(
+ "names, usecols, parse_dates, missing_cols",
+ [
+ (None, ["val"], ["date", "time"], "date, time"),
+ (None, ["val"], [0, "time"], "time"),
+ (["date1", "time1", "temperature"], None, ["date", "time"], "date, time"),
+ (
+ ["date1", "time1", "temperature"],
+ ["date1", "temperature"],
+ ["date1", "time"],
+ "time",
+ ),
+ ],
+)
+def test_missing_parse_dates_column_raises(
+ all_parsers, names, usecols, parse_dates, missing_cols
+):
+ # gh-31251 column names provided in parse_dates could be missing.
+ parser = all_parsers
+ content = StringIO("date,time,val\n2020-01-31,04:20:32,32\n")
+ msg = f"Missing column provided to 'parse_dates': '{missing_cols}'"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ content, sep=",", names=names, usecols=usecols, parse_dates=parse_dates
+ )
+
+
+@xfail_pyarrow # mismatched shape
+def test_date_parser_and_names(all_parsers):
+ # GH#33699
+ parser = all_parsers
+ data = StringIO("""x,y\n1,2""")
+ warn = UserWarning
+ if parser.engine == "pyarrow":
+ # Pandas4Warning for passing a Manager object
+ warn = (UserWarning, Pandas4Warning)
+ result = parser.read_csv_check_warnings(
+ warn,
+ "Could not infer format",
+ data,
+ parse_dates=["B"],
+ names=["B"],
+ )
+ expected = DataFrame({"B": ["y", "2"]}, index=["x", "1"])
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # TypeError: an integer is required
+def test_date_parser_multiindex_columns(all_parsers):
+ parser = all_parsers
+ data = """a,b
+1,2
+2019-12-31,6"""
+ result = parser.read_csv(StringIO(data), parse_dates=[("a", "1")], header=[0, 1])
+ expected = DataFrame({("a", "1"): Timestamp("2019-12-31"), ("b", "2"): [6]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_date_parser_usecols_thousands(all_parsers):
+ # GH#39365
+ data = """A,B,C
+ 1,3,20-09-01-01
+ 2,4,20-09-01-01
+ """
+
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ # DeprecationWarning for passing a Manager object
+ msg = "The 'thousands' option is not supported with the 'pyarrow' engine"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(
+ StringIO(data),
+ parse_dates=[1],
+ usecols=[1, 2],
+ thousands="-",
+ )
+ return
+
+ result = parser.read_csv_check_warnings(
+ UserWarning,
+ "Could not infer format",
+ StringIO(data),
+ parse_dates=[1],
+ usecols=[1, 2],
+ thousands="-",
+ )
+ expected = DataFrame({"B": [3, 4], "C": [Timestamp("20-09-2001 01:00:00")] * 2})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dayfirst_warnings():
+ # GH 12585
+
+ # CASE 1: valid input
+ input = "date\n31/12/2014\n10/03/2011"
+ expected = DatetimeIndex(
+ ["2014-12-31", "2011-03-10"], dtype="datetime64[us]", freq=None, name="date"
+ )
+ warning_msg = (
+ "Parsing dates in .* format when dayfirst=.* was specified. "
+ "Pass `dayfirst=.*` or specify a format to silence this warning."
+ )
+
+ # A. dayfirst arg correct, no warning
+ res1 = read_csv(
+ StringIO(input), parse_dates=["date"], dayfirst=True, index_col="date"
+ ).index
+ tm.assert_index_equal(expected, res1)
+
+ # B. dayfirst arg incorrect, warning
+ with tm.assert_produces_warning(UserWarning, match=warning_msg):
+ res2 = read_csv(
+ StringIO(input), parse_dates=["date"], dayfirst=False, index_col="date"
+ ).index
+ tm.assert_index_equal(expected, res2)
+
+ # CASE 2: invalid input
+ # cannot consistently process with single format
+ # return to user unaltered
+
+ # first in DD/MM/YYYY, second in MM/DD/YYYY
+ input = "date\n31/12/2014\n03/30/2011"
+ expected = Index(["31/12/2014", "03/30/2011"], dtype="str", name="date")
+
+ # A. use dayfirst=True
+ res5 = read_csv(
+ StringIO(input), parse_dates=["date"], dayfirst=True, index_col="date"
+ ).index
+ tm.assert_index_equal(expected, res5)
+
+ # B. use dayfirst=False
+ with tm.assert_produces_warning(UserWarning, match=warning_msg):
+ res6 = read_csv(
+ StringIO(input), parse_dates=["date"], dayfirst=False, index_col="date"
+ ).index
+ tm.assert_index_equal(expected, res6)
+
+
+@pytest.mark.parametrize(
+ "date_string, dayfirst",
+ [
+ pytest.param(
+ "31/1/2014",
+ False,
+ id="second date is single-digit",
+ ),
+ pytest.param(
+ "1/31/2014",
+ True,
+ id="first date is single-digit",
+ ),
+ ],
+)
+def test_dayfirst_warnings_no_leading_zero(date_string, dayfirst):
+ # GH47880
+ initial_value = f"date\n{date_string}"
+ expected = DatetimeIndex(
+ ["2014-01-31"], dtype="datetime64[us]", freq=None, name="date"
+ )
+ warning_msg = (
+ "Parsing dates in .* format when dayfirst=.* was specified. "
+ "Pass `dayfirst=.*` or specify a format to silence this warning."
+ )
+ with tm.assert_produces_warning(UserWarning, match=warning_msg):
+ res = read_csv(
+ StringIO(initial_value),
+ parse_dates=["date"],
+ index_col="date",
+ dayfirst=dayfirst,
+ ).index
+ tm.assert_index_equal(expected, res)
+
+
+@skip_pyarrow # CSV parse error: Expected 3 columns, got 4
+def test_infer_first_column_as_index(all_parsers):
+ # GH#11019
+ parser = all_parsers
+ data = "a,b,c\n1970-01-01,2,3,4"
+ result = parser.read_csv(
+ StringIO(data),
+ parse_dates=["a"],
+ )
+ expected = DataFrame({"a": "2", "b": 3, "c": 4}, index=["1970-01-01"])
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # pyarrow engine doesn't support passing a dict for na_values
+def test_replace_nans_before_parsing_dates(all_parsers):
+ # GH#26203
+ parser = all_parsers
+ data = """Test
+2012-10-01
+0
+2015-05-15
+#
+2017-09-09
+"""
+ result = parser.read_csv(
+ StringIO(data),
+ na_values={"Test": ["#", "0"]},
+ parse_dates=["Test"],
+ date_format="%Y-%m-%d",
+ )
+ expected = DataFrame(
+ {
+ "Test": [
+ Timestamp("2012-10-01"),
+ pd.NaT,
+ Timestamp("2015-05-15"),
+ pd.NaT,
+ Timestamp("2017-09-09"),
+ ]
+ },
+ dtype="M8[us]",
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # string[python] instead of dt64[ns]
+def test_parse_dates_and_string_dtype(all_parsers):
+ # GH#34066
+ parser = all_parsers
+ data = """a,b
+1,2019-12-31
+"""
+ result = parser.read_csv(StringIO(data), dtype="string", parse_dates=["b"])
+ expected = DataFrame({"a": ["1"], "b": [Timestamp("2019-12-31")]})
+ expected["a"] = expected["a"].astype("string")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_parse_dot_separated_dates(all_parsers):
+ # https://github.com/pandas-dev/pandas/issues/2586
+ parser = all_parsers
+ data = """a,b
+27.03.2003 14:55:00.000,1
+03.08.2003 15:20:00.000,2"""
+ if parser.engine == "pyarrow":
+ expected_index = Index(
+ ["27.03.2003 14:55:00.000", "03.08.2003 15:20:00.000"],
+ dtype="str",
+ name="a",
+ )
+ warn = None
+ else:
+ expected_index = DatetimeIndex(
+ ["2003-03-27 14:55:00", "2003-08-03 15:20:00"],
+ dtype="datetime64[us]",
+ name="a",
+ )
+ warn = UserWarning
+ msg = r"when dayfirst=False \(the default\) was specified"
+ result = parser.read_csv_check_warnings(
+ warn,
+ msg,
+ StringIO(data),
+ parse_dates=True,
+ index_col=0,
+ raise_on_extra_warnings=False,
+ )
+ expected = DataFrame({"b": [1, 2]}, index=expected_index)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_parse_dates_dict_format(all_parsers):
+ # GH#51240
+ parser = all_parsers
+ data = """a,b
+2019-12-31,31-12-2019
+2020-12-31,31-12-2020"""
+
+ result = parser.read_csv(
+ StringIO(data),
+ date_format={"a": "%Y-%m-%d", "b": "%d-%m-%Y"},
+ parse_dates=["a", "b"],
+ )
+ expected = DataFrame(
+ {
+ "a": [Timestamp("2019-12-31"), Timestamp("2020-12-31")],
+ "b": [Timestamp("2019-12-31"), Timestamp("2020-12-31")],
+ },
+ dtype="M8[us]",
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # object dtype index
+def test_parse_dates_dict_format_index(all_parsers):
+ # GH#51240
+ parser = all_parsers
+ data = """a,b
+2019-12-31,31-12-2019
+2020-12-31,31-12-2020"""
+
+ result = parser.read_csv(
+ StringIO(data), date_format={"a": "%Y-%m-%d"}, parse_dates=True, index_col=0
+ )
+ expected = DataFrame(
+ {
+ "b": ["31-12-2019", "31-12-2020"],
+ },
+ index=Index([Timestamp("2019-12-31"), Timestamp("2020-12-31")], name="a"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_parse_dates_arrow_engine(all_parsers):
+ # GH#53295
+ parser = all_parsers
+ data = """a,b
+2000-01-01 00:00:00,1
+2000-01-01 00:00:01,1"""
+
+ result = parser.read_csv(StringIO(data), parse_dates=["a"])
+ expected = DataFrame(
+ {
+ "a": [
+ Timestamp("2000-01-01 00:00:00"),
+ Timestamp("2000-01-01 00:00:01"),
+ ],
+ "b": 1,
+ }
+ )
+ if parser.engine == "pyarrow":
+ expected["a"] = expected["a"].astype("M8[s]")
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # object dtype index
+def test_from_csv_with_mixed_offsets(all_parsers):
+ parser = all_parsers
+ data = "a\n2020-01-01T00:00:00+01:00\n2020-01-01T00:00:00+00:00"
+ result = parser.read_csv(StringIO(data), parse_dates=["a"])["a"]
+ expected = Series(
+ [
+ "2020-01-01T00:00:00+01:00",
+ "2020-01-01T00:00:00+00:00",
+ ],
+ name="a",
+ index=[0, 1],
+ )
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_python_parser_only.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_python_parser_only.py
new file mode 100644
index 0000000000000000000000000000000000000000..17f0187f4377e1210f3f915675e16bdfe9c66774
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_python_parser_only.py
@@ -0,0 +1,643 @@
+"""
+Tests that apply specifically to the Python parser. Unless specifically
+stated as a Python-specific issue, the goal is to eventually move as many of
+these tests out of this module as soon as the C parser can accept further
+arguments when parsing.
+"""
+
+from __future__ import annotations
+
+import csv
+from io import (
+ BytesIO,
+ StringIO,
+ TextIOWrapper,
+)
+from typing import TYPE_CHECKING
+
+import numpy as np
+import pytest
+
+from pandas.errors import (
+ ParserError,
+ ParserWarning,
+)
+
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
+
+def test_default_separator(python_parser_only):
+ # see gh-17333
+ #
+ # csv.Sniffer in Python treats "o" as separator.
+ data = "aob\n1o2\n3o4"
+ parser = python_parser_only
+ expected = DataFrame({"a": [1, 3], "b": [2, 4]})
+
+ result = parser.read_csv(StringIO(data), sep=None)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("skipfooter", ["foo", 1.5, True])
+def test_invalid_skipfooter_non_int(python_parser_only, skipfooter):
+ # see gh-15925 (comment)
+ data = "a\n1\n2"
+ parser = python_parser_only
+ msg = "skipfooter must be an integer"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), skipfooter=skipfooter)
+
+
+def test_invalid_skipfooter_negative(python_parser_only):
+ # see gh-15925 (comment)
+ data = "a\n1\n2"
+ parser = python_parser_only
+ msg = "skipfooter cannot be negative"
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), skipfooter=-1)
+
+
+@pytest.mark.parametrize("kwargs", [{"sep": None}, {"delimiter": "|"}])
+def test_sniff_delimiter(python_parser_only, kwargs):
+ data = """index|A|B|C
+foo|1|2|3
+bar|4|5|6
+baz|7|8|9
+"""
+ parser = python_parser_only
+ result = parser.read_csv(StringIO(data), index_col=0, **kwargs)
+ expected = DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ columns=["A", "B", "C"],
+ index=Index(["foo", "bar", "baz"], name="index"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_sniff_delimiter_comment(python_parser_only):
+ data = """# comment line
+index|A|B|C
+# comment line
+foo|1|2|3 # ignore | this
+bar|4|5|6
+baz|7|8|9
+"""
+ parser = python_parser_only
+ result = parser.read_csv(StringIO(data), index_col=0, sep=None, comment="#")
+ expected = DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ columns=["A", "B", "C"],
+ index=Index(["foo", "bar", "baz"], name="index"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("encoding", [None, "utf-8"])
+def test_sniff_delimiter_encoding(python_parser_only, encoding):
+ parser = python_parser_only
+ data = """ignore this
+ignore this too
+index|A|B|C
+foo|1|2|3
+bar|4|5|6
+baz|7|8|9
+"""
+
+ if encoding is not None:
+ data = data.encode(encoding)
+ data = BytesIO(data)
+ data = TextIOWrapper(data, encoding=encoding)
+ else:
+ data = StringIO(data)
+
+ result = parser.read_csv(data, index_col=0, sep=None, skiprows=2, encoding=encoding)
+ expected = DataFrame(
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ columns=["A", "B", "C"],
+ index=Index(["foo", "bar", "baz"], name="index"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_single_line(python_parser_only):
+ # see gh-6607: sniff separator
+ parser = python_parser_only
+ result = parser.read_csv(StringIO("1,2"), names=["a", "b"], header=None, sep=None)
+
+ expected = DataFrame({"a": [1], "b": [2]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("kwargs", [{"skipfooter": 2}, {"nrows": 3}])
+def test_skipfooter(python_parser_only, kwargs):
+ # see gh-6607
+ data = """A,B,C
+1,2,3
+4,5,6
+7,8,9
+want to skip this
+also also skip this
+"""
+ parser = python_parser_only
+ result = parser.read_csv(StringIO(data), **kwargs)
+
+ expected = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=["A", "B", "C"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "compression,klass", [("gzip", "GzipFile"), ("bz2", "BZ2File")]
+)
+def test_decompression_regex_sep(
+ temp_file, python_parser_only, csv1, compression, klass
+):
+ # see gh-6607
+ parser = python_parser_only
+
+ with open(csv1, "rb") as f:
+ data = f.read()
+
+ data = data.replace(b",", b"::")
+ expected = parser.read_csv(csv1)
+
+ module = pytest.importorskip(compression)
+ klass = getattr(module, klass)
+
+ with klass(temp_file, mode="wb") as tmp:
+ tmp.write(data)
+
+ result = parser.read_csv(temp_file, sep="::", compression=compression)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_csv_buglet_4x_multi_index(python_parser_only):
+ # see gh-6607
+ data = """ A B C D E
+one two three four
+a b 10.0032 5 -0.5109 -2.3358 -0.4645 0.05076 0.3640
+a q 20 4 0.4473 1.4152 0.2834 1.00661 0.1744
+x q 30 3 -0.6662 -0.5243 -0.3580 0.89145 2.5838"""
+ parser = python_parser_only
+
+ expected = DataFrame(
+ [
+ [-0.5109, -2.3358, -0.4645, 0.05076, 0.3640],
+ [0.4473, 1.4152, 0.2834, 1.00661, 0.1744],
+ [-0.6662, -0.5243, -0.3580, 0.89145, 2.5838],
+ ],
+ columns=["A", "B", "C", "D", "E"],
+ index=MultiIndex.from_tuples(
+ [("a", "b", 10.0032, 5), ("a", "q", 20, 4), ("x", "q", 30, 3)],
+ names=["one", "two", "three", "four"],
+ ),
+ )
+ result = parser.read_csv(StringIO(data), sep=r"\s+")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_csv_buglet_4x_multi_index2(python_parser_only):
+ # see gh-6893
+ data = " A B C\na b c\n1 3 7 0 3 6\n3 1 4 1 5 9"
+ parser = python_parser_only
+
+ expected = DataFrame.from_records(
+ [(1, 3, 7, 0, 3, 6), (3, 1, 4, 1, 5, 9)],
+ columns=list("abcABC"),
+ index=list("abc"),
+ )
+ result = parser.read_csv(StringIO(data), sep=r"\s+")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("add_footer", [True, False])
+def test_skipfooter_with_decimal(python_parser_only, add_footer):
+ # see gh-6971
+ data = "1#2\n3#4"
+ parser = python_parser_only
+ expected = DataFrame({"a": [1.2, 3.4]})
+
+ if add_footer:
+ # The stray footer line should not mess with the
+ # casting of the first two lines if we skip it.
+ kwargs = {"skipfooter": 1}
+ data += "\nFooter"
+ else:
+ kwargs = {}
+
+ result = parser.read_csv(StringIO(data), names=["a"], decimal="#", **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "sep", ["::", "#####", "!!!", "123", "#1!c5", "%!c!d", "@@#4:2", "_!pd#_"]
+)
+@pytest.mark.parametrize(
+ "encoding", ["utf-16", "utf-16-be", "utf-16-le", "utf-32", "cp037"]
+)
+def test_encoding_non_utf8_multichar_sep(python_parser_only, sep, encoding):
+ # see gh-3404
+ expected = DataFrame({"a": [1], "b": [2]})
+ parser = python_parser_only
+
+ data = "1" + sep + "2"
+ encoded_data = data.encode(encoding)
+
+ result = parser.read_csv(
+ BytesIO(encoded_data), sep=sep, names=["a", "b"], encoding=encoding
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("quoting", [csv.QUOTE_MINIMAL, csv.QUOTE_NONE])
+def test_multi_char_sep_quotes(python_parser_only, quoting):
+ # see gh-13374
+ kwargs = {"sep": ",,"}
+ parser = python_parser_only
+
+ data = 'a,,b\n1,,a\n2,,"2,,b"'
+
+ if quoting == csv.QUOTE_NONE:
+ msg = "Expected 2 fields in line 3, saw 3"
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data), quoting=quoting, **kwargs)
+ else:
+ msg = "ignored when a multi-char delimiter is used"
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data), quoting=quoting, **kwargs)
+
+
+def test_none_delimiter(python_parser_only):
+ # see gh-13374 and gh-17465
+ parser = python_parser_only
+ data = "a,b,c\n0,1,2\n3,4,5,6\n7,8,9"
+ expected = DataFrame({"a": [0, 7], "b": [1, 8], "c": [2, 9]})
+
+ # We expect the third line in the data to be
+ # skipped because it is malformed, but we do
+ # not expect any errors to occur.
+ with tm.assert_produces_warning(
+ ParserWarning, match="Skipping line 3", check_stacklevel=False
+ ):
+ result = parser.read_csv(
+ StringIO(data), header=0, sep=None, on_bad_lines="warn"
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("data", ['a\n1\n"b"a', 'a,b,c\ncat,foo,bar\ndog,foo,"baz'])
+@pytest.mark.parametrize("skipfooter", [0, 1])
+def test_skipfooter_bad_row(python_parser_only, data, skipfooter):
+ # see gh-13879 and gh-15910
+ parser = python_parser_only
+ if skipfooter:
+ msg = "parsing errors in the skipped footer rows"
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data), skipfooter=skipfooter)
+ else:
+ msg = "unexpected end of data|expected after"
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data), skipfooter=skipfooter)
+
+
+def test_malformed_skipfooter(python_parser_only):
+ parser = python_parser_only
+ data = """ignore
+A,B,C
+1,2,3 # comment
+1,2,3,4,5
+2,3,4
+footer
+"""
+ msg = "Expected 3 fields in line 4, saw 5"
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data), header=1, comment="#", skipfooter=1)
+
+
+def test_python_engine_file_no_next(python_parser_only):
+ parser = python_parser_only
+
+ class NoNextBuffer:
+ def __init__(self, csv_data) -> None:
+ self.data = csv_data
+
+ def __iter__(self) -> Iterator:
+ return self.data.__iter__()
+
+ def read(self):
+ return self.data
+
+ def readline(self):
+ return self.data
+
+ parser.read_csv(NoNextBuffer("a\n1"))
+
+
+@pytest.mark.parametrize("bad_line_func", [lambda x: ["2", "3"], lambda x: x[:2]])
+def test_on_bad_lines_callable(python_parser_only, bad_line_func):
+ # GH 5686
+ parser = python_parser_only
+ data = """a,b
+1,2
+2,3,4,5,6
+3,4
+"""
+ bad_sio = StringIO(data)
+ result = parser.read_csv(bad_sio, on_bad_lines=bad_line_func)
+ expected = DataFrame({"a": [1, 2, 3], "b": [2, 3, 4]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_on_bad_lines_callable_write_to_external_list(python_parser_only):
+ # GH 5686
+ parser = python_parser_only
+ data = """a,b
+1,2
+2,3,4,5,6
+3,4
+"""
+ bad_sio = StringIO(data)
+ lst = []
+
+ def bad_line_func(bad_line: list[str]) -> list[str]:
+ lst.append(bad_line)
+ return ["2", "3"]
+
+ result = parser.read_csv(bad_sio, on_bad_lines=bad_line_func)
+ expected = DataFrame({"a": [1, 2, 3], "b": [2, 3, 4]})
+ tm.assert_frame_equal(result, expected)
+ assert lst == [["2", "3", "4", "5", "6"]]
+
+
+@pytest.mark.parametrize("bad_line_func", [lambda x: ["foo", "bar"], lambda x: x[:2]])
+@pytest.mark.parametrize("sep", [",", "111"])
+def test_on_bad_lines_callable_iterator_true(python_parser_only, bad_line_func, sep):
+ # GH 5686
+ # iterator=True has a separate code path than iterator=False
+ parser = python_parser_only
+ data = f"""
+0{sep}1
+hi{sep}there
+foo{sep}bar{sep}baz
+good{sep}bye
+"""
+ bad_sio = StringIO(data)
+ result_iter = parser.read_csv(
+ bad_sio, on_bad_lines=bad_line_func, chunksize=1, iterator=True, sep=sep
+ )
+ expecteds = [
+ {"0": "hi", "1": "there"},
+ {"0": "foo", "1": "bar"},
+ {"0": "good", "1": "bye"},
+ ]
+ for i, (result, expected) in enumerate(zip(result_iter, expecteds)):
+ expected = DataFrame(expected, index=range(i, i + 1))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_on_bad_lines_callable_dont_swallow_errors(python_parser_only):
+ # GH 5686
+ parser = python_parser_only
+ data = """a,b
+1,2
+2,3,4,5,6
+3,4
+"""
+ bad_sio = StringIO(data)
+ msg = "This function is buggy."
+
+ def bad_line_func(bad_line):
+ raise ValueError(msg)
+
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(bad_sio, on_bad_lines=bad_line_func)
+
+
+def test_on_bad_lines_callable_not_expected_length(python_parser_only):
+ # GH 5686
+ parser = python_parser_only
+ data = """a,b
+1,2
+2,3,4,5,6
+3,4
+"""
+ bad_sio = StringIO(data)
+
+ result = parser.read_csv_check_warnings(
+ ParserWarning, "from bad_lines callable", bad_sio, on_bad_lines=lambda x: x
+ )
+ expected = DataFrame({"a": [1, 2, 3], "b": [2, 3, 4]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_on_bad_lines_callable_returns_none(python_parser_only):
+ # GH 5686
+ parser = python_parser_only
+ data = """a,b
+1,2
+2,3,4,5,6
+3,4
+"""
+ bad_sio = StringIO(data)
+
+ result = parser.read_csv(bad_sio, on_bad_lines=lambda x: None)
+ expected = DataFrame({"a": [1, 3], "b": [2, 4]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_on_bad_lines_index_col_inferred(python_parser_only):
+ # GH 5686
+ parser = python_parser_only
+ data = """a,b
+1,2,3
+4,5,6
+"""
+ bad_sio = StringIO(data)
+
+ result = parser.read_csv(bad_sio, on_bad_lines=lambda x: ["99", "99"])
+ expected = DataFrame({"a": [2, 5], "b": [3, 6]}, index=[1, 4])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_index_col_false_and_header_none(python_parser_only):
+ # GH#46955
+ parser = python_parser_only
+ data = """
+0.5,0.03
+0.1,0.2,0.3,2
+"""
+ result = parser.read_csv_check_warnings(
+ ParserWarning,
+ "Length of header",
+ StringIO(data),
+ sep=",",
+ header=None,
+ index_col=False,
+ )
+ expected = DataFrame({0: [0.5, 0.1], 1: [0.03, 0.2]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_header_int_do_not_infer_multiindex_names_on_different_line(python_parser_only):
+ # GH#46569
+ parser = python_parser_only
+ data = StringIO("a\na,b\nc,d,e\nf,g,h")
+ result = parser.read_csv_check_warnings(
+ ParserWarning, "Length of header", data, engine="python", index_col=False
+ )
+ expected = DataFrame({"a": ["a", "c", "f"]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype", [{"a": object}, {"a": str, "b": np.int64, "c": np.int64}]
+)
+def test_no_thousand_convert_with_dot_for_non_numeric_cols(python_parser_only, dtype):
+ # GH#50270
+ parser = python_parser_only
+ data = """\
+a;b;c
+0000.7995;16.000;0
+3.03.001.00514;0;4.000
+4923.600.041;23.000;131"""
+ result = parser.read_csv(
+ StringIO(data),
+ sep=";",
+ dtype=dtype,
+ thousands=".",
+ )
+ expected = DataFrame(
+ {
+ "a": ["0000.7995", "3.03.001.00514", "4923.600.041"],
+ "b": [16000, 0, 23000],
+ "c": [0, 4000, 131],
+ }
+ )
+ if dtype["a"] == object:
+ expected["a"] = expected["a"].astype(object)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype,expected",
+ [
+ (
+ {"a": str, "b": np.float64, "c": np.int64},
+ {
+ "b": [16000.1, 0, 23000],
+ "c": [0, 4001, 131],
+ },
+ ),
+ (
+ str,
+ {
+ "b": ["16,000.1", "0", "23,000"],
+ "c": ["0", "4,001", "131"],
+ },
+ ),
+ ],
+)
+def test_no_thousand_convert_for_non_numeric_cols(python_parser_only, dtype, expected):
+ # GH#50270
+ parser = python_parser_only
+ data = """a;b;c
+0000,7995;16,000.1;0
+3,03,001,00514;0;4,001
+4923,600,041;23,000;131
+"""
+ result = parser.read_csv(
+ StringIO(data),
+ sep=";",
+ dtype=dtype,
+ thousands=",",
+ )
+ expected = DataFrame(expected)
+ expected.insert(0, "a", ["0000,7995", "3,03,001,00514", "4923,600,041"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_col", [None, 0])
+def test_on_bad_lines_callable_warns_and_truncates_with_index_col(
+ python_parser_only, index_col
+):
+ # GH#61837
+ parser = python_parser_only
+ data = "id,field_1,field_2\n101,A,B\n102,C,D,E\n103,F,G\n"
+
+ def fixer(bad_line):
+ return [*list(bad_line), "EXTRA1", "EXTRA2"]
+
+ result = parser.read_csv_check_warnings(
+ ParserWarning,
+ "from bad_lines callable",
+ StringIO(data),
+ on_bad_lines=fixer,
+ index_col=index_col,
+ )
+
+ if index_col is None:
+ expected = DataFrame(
+ {
+ "id": [101, 102, 103],
+ "field_1": ["A", "C", "F"],
+ "field_2": ["B", "D", "G"],
+ }
+ )
+ else:
+ expected = DataFrame(
+ {"field_1": ["A", "C", "F"], "field_2": ["B", "D", "G"]},
+ index=Index([101, 102, 103], name="id"),
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_csv_leading_quote_skip(python_parser_only):
+ # GH 62739
+ tbl = """\
+ "
+a b
+1 3
+"""
+ parser = python_parser_only
+ result = parser.read_csv(
+ StringIO(tbl),
+ delimiter=" ",
+ skiprows=1,
+ )
+ expected = DataFrame({"a": [1], "b": [3]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_csv_unclosed_double_quote_in_data_still_errors(python_parser_only):
+ # GH 62739
+ tbl = """\
+a b
+"
+1 3
+"""
+ parser = python_parser_only
+ with pytest.raises(ParserError, match="unexpected end of data"):
+ parser.read_csv(StringIO(tbl), delimiter=" ", skiprows=1)
+
+
+def test_read_csv_skiprows_zero(python_parser_only):
+ # GH 62739
+ tbl = """\
+"
+a b
+1 3
+"""
+ parser = python_parser_only
+ # don't skip anything
+ with pytest.raises(ParserError, match="unexpected end of data"):
+ parser.read_csv(StringIO(tbl), delimiter=" ", skiprows=0, engine="python")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_quoting.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_quoting.py
new file mode 100644
index 0000000000000000000000000000000000000000..63c459d0f8bdee3b3292d8e0d9419caaea4c81b2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_quoting.py
@@ -0,0 +1,196 @@
+"""
+Tests that quoting specifications are properly handled
+during parsing for all of the parsers defined in parsers.py
+"""
+
+import csv
+from io import StringIO
+
+import pytest
+
+from pandas.compat import PY314
+from pandas.errors import ParserError
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+
+if PY314:
+ # TODO: write a regex that works with all new possitibilities here
+ MSG1 = ""
+ MSG2 = r"[\s\S]*"
+else:
+ MSG1 = "a(n)? 1-character string"
+ MSG2 = "string( or None)?"
+
+
+@pytest.mark.parametrize(
+ "kwargs,msg",
+ [
+ ({"quotechar": "foo"}, f'"quotechar" must be {MSG1}'),
+ (
+ {"quotechar": None, "quoting": csv.QUOTE_MINIMAL},
+ "quotechar must be set if quoting enabled",
+ ),
+ ({"quotechar": 2}, f'"quotechar" must be {MSG2}, not int'),
+ ],
+)
+@skip_pyarrow # ParserError: CSV parse error: Empty CSV file or block
+def test_bad_quote_char(all_parsers, kwargs, msg):
+ data = "1,2,3"
+ parser = all_parsers
+
+ with pytest.raises(TypeError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+
+
+@pytest.mark.parametrize(
+ "quoting,msg",
+ [
+ ("foo", '"quoting" must be an integer|Argument'),
+ (10, 'bad "quoting" value'), # quoting must be in the range [0, 3]
+ ],
+)
+@xfail_pyarrow # ValueError: The 'quoting' option is not supported
+def test_bad_quoting(all_parsers, quoting, msg):
+ data = "1,2,3"
+ parser = all_parsers
+
+ with pytest.raises(TypeError, match=msg):
+ parser.read_csv(StringIO(data), quoting=quoting)
+
+
+def test_quote_char_basic(all_parsers):
+ parser = all_parsers
+ data = 'a,b,c\n1,2,"cat"'
+ expected = DataFrame([[1, 2, "cat"]], columns=["a", "b", "c"])
+
+ result = parser.read_csv(StringIO(data), quotechar='"')
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("quote_char", ["~", "*", "%", "$", "@", "P"])
+def test_quote_char_various(all_parsers, quote_char):
+ parser = all_parsers
+ expected = DataFrame([[1, 2, "cat"]], columns=["a", "b", "c"])
+
+ data = 'a,b,c\n1,2,"cat"'
+ new_data = data.replace('"', quote_char)
+
+ result = parser.read_csv(StringIO(new_data), quotechar=quote_char)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: The 'quoting' option is not supported
+@pytest.mark.parametrize("quoting", [csv.QUOTE_MINIMAL, csv.QUOTE_NONE])
+@pytest.mark.parametrize("quote_char", ["", None])
+def test_null_quote_char(all_parsers, quoting, quote_char):
+ kwargs = {"quotechar": quote_char, "quoting": quoting}
+ data = "a,b,c\n1,2,3"
+ parser = all_parsers
+
+ if quoting != csv.QUOTE_NONE:
+ # Sanity checking.
+ if not PY314:
+ msg = "1-character string"
+ else:
+ msg = "unicode character or None"
+ msg = (
+ f'"quotechar" must be a {msg}'
+ if all_parsers.engine == "python" and quote_char == ""
+ else "quotechar must be set if quoting enabled"
+ )
+
+ with pytest.raises(TypeError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+ elif all_parsers.engine != "python":
+ # Python doesn't support null/blank quote chars in their csv parsers
+ expected = DataFrame([[1, 2, 3]], columns=["a", "b", "c"])
+ result = parser.read_csv(StringIO(data), **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "kwargs,exp_data",
+ [
+ ({}, [[1, 2, "foo"]]), # Test default.
+ # QUOTE_MINIMAL only applies to CSV writing, so no effect on reading.
+ ({"quotechar": '"', "quoting": csv.QUOTE_MINIMAL}, [[1, 2, "foo"]]),
+ # QUOTE_MINIMAL only applies to CSV writing, so no effect on reading.
+ ({"quotechar": '"', "quoting": csv.QUOTE_ALL}, [[1, 2, "foo"]]),
+ # QUOTE_NONE tells the reader to do no special handling
+ # of quote characters and leave them alone.
+ ({"quotechar": '"', "quoting": csv.QUOTE_NONE}, [[1, 2, '"foo"']]),
+ # QUOTE_NONNUMERIC tells the reader to cast
+ # all non-quoted fields to float
+ ({"quotechar": '"', "quoting": csv.QUOTE_NONNUMERIC}, [[1.0, 2.0, "foo"]]),
+ ],
+)
+@xfail_pyarrow # ValueError: The 'quoting' option is not supported
+def test_quoting_various(all_parsers, kwargs, exp_data):
+ data = '1,2,"foo"'
+ parser = all_parsers
+ columns = ["a", "b", "c"]
+
+ result = parser.read_csv(StringIO(data), names=columns, **kwargs)
+ expected = DataFrame(exp_data, columns=columns)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "doublequote,exp_data", [(True, [[3, '4 " 5']]), (False, [[3, '4 " 5"']])]
+)
+def test_double_quote(all_parsers, doublequote, exp_data, request):
+ parser = all_parsers
+ data = 'a,b\n3,"4 "" 5"'
+
+ if parser.engine == "pyarrow" and not doublequote:
+ mark = pytest.mark.xfail(reason="Mismatched result")
+ request.applymarker(mark)
+
+ result = parser.read_csv(StringIO(data), quotechar='"', doublequote=doublequote)
+ expected = DataFrame(exp_data, columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("quotechar", ['"', "\u0001"])
+def test_quotechar_unicode(all_parsers, quotechar):
+ # see gh-14477
+ data = "a\n1"
+ parser = all_parsers
+ expected = DataFrame({"a": [1]})
+
+ result = parser.read_csv(StringIO(data), quotechar=quotechar)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("balanced", [True, False])
+def test_unbalanced_quoting(all_parsers, balanced, request):
+ # see gh-22789.
+ parser = all_parsers
+ data = 'a,b,c\n1,2,"3'
+
+ if parser.engine == "pyarrow" and not balanced:
+ mark = pytest.mark.xfail(reason="Mismatched result")
+ request.applymarker(mark)
+
+ if balanced:
+ # Re-balance the quoting and read in without errors.
+ expected = DataFrame([[1, 2, 3]], columns=["a", "b", "c"])
+ result = parser.read_csv(StringIO(data + '"'))
+ tm.assert_frame_equal(result, expected)
+ else:
+ msg = (
+ "EOF inside string starting at row 1"
+ if parser.engine == "c"
+ else "unexpected end of data"
+ )
+
+ with pytest.raises(ParserError, match=msg):
+ parser.read_csv(StringIO(data))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_read_fwf.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_read_fwf.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae6eef3ba0d486e287eadaab4f91411ee073ea90
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_read_fwf.py
@@ -0,0 +1,1005 @@
+"""
+Tests the 'read_fwf' function in parsers.py. This
+test suite is independent of the others because the
+engine is set to 'python-fwf' internally.
+"""
+
+from io import (
+ BytesIO,
+ StringIO,
+)
+
+import numpy as np
+import pytest
+
+from pandas.errors import EmptyDataError
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+)
+import pandas._testing as tm
+
+from pandas.io.common import urlopen
+from pandas.io.parsers import (
+ read_csv,
+ read_fwf,
+)
+
+
+def test_basic():
+ data = """\
+A B C D
+201158 360.242940 149.910199 11950.7
+201159 444.953632 166.985655 11788.4
+201160 364.136849 183.628767 11806.2
+201161 413.836124 184.375703 11916.8
+201162 502.953953 173.237159 12468.3
+"""
+ result = read_fwf(StringIO(data))
+ expected = DataFrame(
+ [
+ [201158, 360.242940, 149.910199, 11950.7],
+ [201159, 444.953632, 166.985655, 11788.4],
+ [201160, 364.136849, 183.628767, 11806.2],
+ [201161, 413.836124, 184.375703, 11916.8],
+ [201162, 502.953953, 173.237159, 12468.3],
+ ],
+ columns=["A", "B", "C", "D"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_colspecs():
+ data = """\
+A B C D E
+201158 360.242940 149.910199 11950.7
+201159 444.953632 166.985655 11788.4
+201160 364.136849 183.628767 11806.2
+201161 413.836124 184.375703 11916.8
+201162 502.953953 173.237159 12468.3
+"""
+ colspecs = [(0, 4), (4, 8), (8, 20), (21, 33), (34, 43)]
+ result = read_fwf(StringIO(data), colspecs=colspecs)
+
+ expected = DataFrame(
+ [
+ [2011, 58, 360.242940, 149.910199, 11950.7],
+ [2011, 59, 444.953632, 166.985655, 11788.4],
+ [2011, 60, 364.136849, 183.628767, 11806.2],
+ [2011, 61, 413.836124, 184.375703, 11916.8],
+ [2011, 62, 502.953953, 173.237159, 12468.3],
+ ],
+ columns=["A", "B", "C", "D", "E"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_widths():
+ data = """\
+A B C D E
+2011 58 360.242940 149.910199 11950.7
+2011 59 444.953632 166.985655 11788.4
+2011 60 364.136849 183.628767 11806.2
+2011 61 413.836124 184.375703 11916.8
+2011 62 502.953953 173.237159 12468.3
+"""
+ result = read_fwf(StringIO(data), widths=[5, 5, 13, 13, 7])
+
+ expected = DataFrame(
+ [
+ [2011, 58, 360.242940, 149.910199, 11950.7],
+ [2011, 59, 444.953632, 166.985655, 11788.4],
+ [2011, 60, 364.136849, 183.628767, 11806.2],
+ [2011, 61, 413.836124, 184.375703, 11916.8],
+ [2011, 62, 502.953953, 173.237159, 12468.3],
+ ],
+ columns=["A", "B", "C", "D", "E"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_non_space_filler():
+ # From Thomas Kluyver:
+ #
+ # Apparently, some non-space filler characters can be seen, this is
+ # supported by specifying the 'delimiter' character:
+ #
+ # http://publib.boulder.ibm.com/infocenter/dmndhelp/v6r1mx/index.jsp?topic=/com.ibm.wbit.612.help.config.doc/topics/rfixwidth.html
+ data = """\
+A~~~~B~~~~C~~~~~~~~~~~~D~~~~~~~~~~~~E
+201158~~~~360.242940~~~149.910199~~~11950.7
+201159~~~~444.953632~~~166.985655~~~11788.4
+201160~~~~364.136849~~~183.628767~~~11806.2
+201161~~~~413.836124~~~184.375703~~~11916.8
+201162~~~~502.953953~~~173.237159~~~12468.3
+"""
+ colspecs = [(0, 4), (4, 8), (8, 20), (21, 33), (34, 43)]
+ result = read_fwf(StringIO(data), colspecs=colspecs, delimiter="~")
+
+ expected = DataFrame(
+ [
+ [2011, 58, 360.242940, 149.910199, 11950.7],
+ [2011, 59, 444.953632, 166.985655, 11788.4],
+ [2011, 60, 364.136849, 183.628767, 11806.2],
+ [2011, 61, 413.836124, 184.375703, 11916.8],
+ [2011, 62, 502.953953, 173.237159, 12468.3],
+ ],
+ columns=["A", "B", "C", "D", "E"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_over_specified():
+ data = """\
+A B C D E
+201158 360.242940 149.910199 11950.7
+201159 444.953632 166.985655 11788.4
+201160 364.136849 183.628767 11806.2
+201161 413.836124 184.375703 11916.8
+201162 502.953953 173.237159 12468.3
+"""
+ colspecs = [(0, 4), (4, 8), (8, 20), (21, 33), (34, 43)]
+
+ with pytest.raises(ValueError, match="must specify only one of"):
+ read_fwf(StringIO(data), colspecs=colspecs, widths=[6, 10, 10, 7])
+
+
+def test_under_specified():
+ data = """\
+A B C D E
+201158 360.242940 149.910199 11950.7
+201159 444.953632 166.985655 11788.4
+201160 364.136849 183.628767 11806.2
+201161 413.836124 184.375703 11916.8
+201162 502.953953 173.237159 12468.3
+"""
+ with pytest.raises(ValueError, match="Must specify either"):
+ read_fwf(StringIO(data), colspecs=None, widths=None)
+
+
+def test_read_csv_compat():
+ csv_data = """\
+A,B,C,D,E
+2011,58,360.242940,149.910199,11950.7
+2011,59,444.953632,166.985655,11788.4
+2011,60,364.136849,183.628767,11806.2
+2011,61,413.836124,184.375703,11916.8
+2011,62,502.953953,173.237159,12468.3
+"""
+ expected = read_csv(StringIO(csv_data), engine="python")
+
+ fwf_data = """\
+A B C D E
+201158 360.242940 149.910199 11950.7
+201159 444.953632 166.985655 11788.4
+201160 364.136849 183.628767 11806.2
+201161 413.836124 184.375703 11916.8
+201162 502.953953 173.237159 12468.3
+"""
+ colspecs = [(0, 4), (4, 8), (8, 20), (21, 33), (34, 43)]
+ result = read_fwf(StringIO(fwf_data), colspecs=colspecs)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_bytes_io_input():
+ data = BytesIO("שלום\nשלום".encode()) # noqa: RUF001
+ result = read_fwf(data, widths=[2, 2], encoding="utf8")
+ expected = DataFrame([["של", "ום"]], columns=["של", "ום"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_fwf_colspecs_is_list_or_tuple():
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+
+ msg = "column specifications must be a list or tuple.+"
+
+ with pytest.raises(TypeError, match=msg):
+ read_fwf(StringIO(data), colspecs={"a": 1}, delimiter=",")
+
+
+def test_fwf_colspecs_is_list_or_tuple_of_two_element_tuples():
+ data = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+
+ msg = "Each column specification must be.+"
+
+ with pytest.raises(TypeError, match=msg):
+ read_fwf(StringIO(data), colspecs=[("a", 1)])
+
+
+@pytest.mark.parametrize(
+ "colspecs,exp_data",
+ [
+ ([(0, 3), (3, None)], [[123, 456], [456, 789]]),
+ ([(None, 3), (3, 6)], [[123, 456], [456, 789]]),
+ ([(0, None), (3, None)], [[123456, 456], [456789, 789]]),
+ ([(None, None), (3, 6)], [[123456, 456], [456789, 789]]),
+ ],
+)
+def test_fwf_colspecs_none(colspecs, exp_data):
+ # see gh-7079
+ data = """\
+123456
+456789
+"""
+ expected = DataFrame(exp_data)
+
+ result = read_fwf(StringIO(data), colspecs=colspecs, header=None)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "infer_nrows,exp_data",
+ [
+ # infer_nrows --> colspec == [(2, 3), (5, 6)]
+ (1, [[1, 2], [3, 8]]),
+ # infer_nrows > number of rows
+ (10, [[1, 2], [123, 98]]),
+ ],
+)
+def test_fwf_colspecs_infer_nrows(infer_nrows, exp_data):
+ # see gh-15138
+ data = """\
+ 1 2
+123 98
+"""
+ expected = DataFrame(exp_data)
+
+ result = read_fwf(StringIO(data), infer_nrows=infer_nrows, header=None)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_fwf_regression():
+ # see gh-3594
+ #
+ # Turns out "T060" is parsable as a datetime slice!
+ tz_list = [1, 10, 20, 30, 60, 80, 100]
+ widths = [16] + [8] * len(tz_list)
+ names = ["SST"] + [f"T{z:03d}" for z in tz_list[1:]]
+
+ data = """ 2009164202000 9.5403 9.4105 8.6571 7.8372 6.0612 5.8843 5.5192
+2009164203000 9.5435 9.2010 8.6167 7.8176 6.0804 5.8728 5.4869
+2009164204000 9.5873 9.1326 8.4694 7.5889 6.0422 5.8526 5.4657
+2009164205000 9.5810 9.0896 8.4009 7.4652 6.0322 5.8189 5.4379
+2009164210000 9.6034 9.0897 8.3822 7.4905 6.0908 5.7904 5.4039
+"""
+ expected = DataFrame(
+ [
+ [9.5403, 9.4105, 8.6571, 7.8372, 6.0612, 5.8843, 5.5192],
+ [9.5435, 9.2010, 8.6167, 7.8176, 6.0804, 5.8728, 5.4869],
+ [9.5873, 9.1326, 8.4694, 7.5889, 6.0422, 5.8526, 5.4657],
+ [9.5810, 9.0896, 8.4009, 7.4652, 6.0322, 5.8189, 5.4379],
+ [9.6034, 9.0897, 8.3822, 7.4905, 6.0908, 5.7904, 5.4039],
+ ],
+ index=DatetimeIndex(
+ [
+ "2009-06-13 20:20:00",
+ "2009-06-13 20:30:00",
+ "2009-06-13 20:40:00",
+ "2009-06-13 20:50:00",
+ "2009-06-13 21:00:00",
+ ],
+ dtype="M8[us]",
+ ),
+ columns=["SST", "T010", "T020", "T030", "T060", "T080", "T100"],
+ )
+ result = read_fwf(
+ StringIO(data),
+ index_col=0,
+ header=None,
+ names=names,
+ widths=widths,
+ parse_dates=True,
+ date_format="%Y%j%H%M%S",
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_fwf_for_uint8():
+ data = """1421302965.213420 PRI=3 PGN=0xef00 DST=0x17 SRC=0x28 04 154 00 00 00 00 00 127
+1421302964.226776 PRI=6 PGN=0xf002 SRC=0x47 243 00 00 255 247 00 00 71""" # noqa: E501
+ df = read_fwf(
+ StringIO(data),
+ colspecs=[(0, 17), (25, 26), (33, 37), (49, 51), (58, 62), (63, 1000)],
+ names=["time", "pri", "pgn", "dst", "src", "data"],
+ converters={
+ "pgn": lambda x: int(x, 16),
+ "src": lambda x: int(x, 16),
+ "dst": lambda x: int(x, 16),
+ "data": lambda x: len(x.split(" ")),
+ },
+ )
+
+ expected = DataFrame(
+ [
+ [1421302965.213420, 3, 61184, 23, 40, 8],
+ [1421302964.226776, 6, 61442, None, 71, 8],
+ ],
+ columns=["time", "pri", "pgn", "dst", "src", "data"],
+ )
+ expected["dst"] = expected["dst"].astype(object)
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.parametrize("comment", ["#", "~", "!"])
+def test_fwf_comment(comment):
+ data = """\
+ 1 2. 4 #hello world
+ 5 NaN 10.0
+"""
+ data = data.replace("#", comment)
+
+ colspecs = [(0, 3), (4, 9), (9, 25)]
+ expected = DataFrame([[1, 2.0, 4], [5, np.nan, 10.0]])
+
+ result = read_fwf(StringIO(data), colspecs=colspecs, header=None, comment=comment)
+ tm.assert_almost_equal(result, expected)
+
+
+def test_fwf_skip_blank_lines():
+ data = """
+
+A B C D
+
+201158 360.242940 149.910199 11950.7
+201159 444.953632 166.985655 11788.4
+
+
+201162 502.953953 173.237159 12468.3
+
+"""
+ result = read_fwf(StringIO(data), skip_blank_lines=True)
+ expected = DataFrame(
+ [
+ [201158, 360.242940, 149.910199, 11950.7],
+ [201159, 444.953632, 166.985655, 11788.4],
+ [201162, 502.953953, 173.237159, 12468.3],
+ ],
+ columns=["A", "B", "C", "D"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ data = """\
+A B C D
+201158 360.242940 149.910199 11950.7
+201159 444.953632 166.985655 11788.4
+
+
+201162 502.953953 173.237159 12468.3
+"""
+ result = read_fwf(StringIO(data), skip_blank_lines=False)
+ expected = DataFrame(
+ [
+ [201158, 360.242940, 149.910199, 11950.7],
+ [201159, 444.953632, 166.985655, 11788.4],
+ [np.nan, np.nan, np.nan, np.nan],
+ [np.nan, np.nan, np.nan, np.nan],
+ [201162, 502.953953, 173.237159, 12468.3],
+ ],
+ columns=["A", "B", "C", "D"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("thousands", [",", "#", "~"])
+def test_fwf_thousands(thousands):
+ data = """\
+ 1 2,334.0 5
+10 13 10.
+"""
+ data = data.replace(",", thousands)
+
+ colspecs = [(0, 3), (3, 11), (12, 16)]
+ expected = DataFrame([[1, 2334.0, 5], [10, 13, 10.0]])
+
+ result = read_fwf(
+ StringIO(data), header=None, colspecs=colspecs, thousands=thousands
+ )
+ tm.assert_almost_equal(result, expected)
+
+
+@pytest.mark.parametrize("header", [True, False])
+def test_bool_header_arg(header):
+ # see gh-6114
+ data = """\
+MyColumn
+ a
+ b
+ a
+ b"""
+
+ msg = "Passing a bool to header is invalid"
+ with pytest.raises(TypeError, match=msg):
+ read_fwf(StringIO(data), header=header)
+
+
+def test_full_file():
+ # File with all values.
+ test = """index A B C
+2000-01-03T00:00:00 0.980268513777 3 foo
+2000-01-04T00:00:00 1.04791624281 -4 bar
+2000-01-05T00:00:00 0.498580885705 73 baz
+2000-01-06T00:00:00 1.12020151869 1 foo
+2000-01-07T00:00:00 0.487094399463 0 bar
+2000-01-10T00:00:00 0.836648671666 2 baz
+2000-01-11T00:00:00 0.157160753327 34 foo"""
+ colspecs = ((0, 19), (21, 35), (38, 40), (42, 45))
+ expected = read_fwf(StringIO(test), colspecs=colspecs)
+
+ result = read_fwf(StringIO(test))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_full_file_with_missing():
+ # File with missing values.
+ test = """index A B C
+2000-01-03T00:00:00 0.980268513777 3 foo
+2000-01-04T00:00:00 1.04791624281 -4 bar
+ 0.498580885705 73 baz
+2000-01-06T00:00:00 1.12020151869 1 foo
+2000-01-07T00:00:00 0 bar
+2000-01-10T00:00:00 0.836648671666 2 baz
+ 34"""
+ colspecs = ((0, 19), (21, 35), (38, 40), (42, 45))
+ expected = read_fwf(StringIO(test), colspecs=colspecs)
+
+ result = read_fwf(StringIO(test))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_full_file_with_spaces():
+ # File with spaces in columns.
+ test = """
+Account Name Balance CreditLimit AccountCreated
+101 Keanu Reeves 9315.45 10000.00 1/17/1998
+312 Gerard Butler 90.00 1000.00 8/6/2003
+868 Jennifer Love Hewitt 0 17000.00 5/25/1985
+761 Jada Pinkett-Smith 49654.87 100000.00 12/5/2006
+317 Bill Murray 789.65 5000.00 2/5/2007
+""".strip("\r\n")
+ colspecs = ((0, 7), (8, 28), (30, 38), (42, 53), (56, 70))
+ expected = read_fwf(StringIO(test), colspecs=colspecs)
+
+ result = read_fwf(StringIO(test))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_full_file_with_spaces_and_missing():
+ # File with spaces and missing values in columns.
+ test = """
+Account Name Balance CreditLimit AccountCreated
+101 10000.00 1/17/1998
+312 Gerard Butler 90.00 1000.00 8/6/2003
+868 5/25/1985
+761 Jada Pinkett-Smith 49654.87 100000.00 12/5/2006
+317 Bill Murray 789.65
+""".strip("\r\n")
+ colspecs = ((0, 7), (8, 28), (30, 38), (42, 53), (56, 70))
+ expected = read_fwf(StringIO(test), colspecs=colspecs)
+
+ result = read_fwf(StringIO(test))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_messed_up_data():
+ # Completely messed up file.
+ test = """
+ Account Name Balance Credit Limit Account Created
+ 101 10000.00 1/17/1998
+ 312 Gerard Butler 90.00 1000.00
+
+ 761 Jada Pinkett-Smith 49654.87 100000.00 12/5/2006
+ 317 Bill Murray 789.65
+""".strip("\r\n")
+ colspecs = ((2, 10), (15, 33), (37, 45), (49, 61), (64, 79))
+ expected = read_fwf(StringIO(test), colspecs=colspecs)
+
+ result = read_fwf(StringIO(test))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_multiple_delimiters():
+ test = r"""
+col1~~~~~col2 col3++++++++++++++++++col4
+~~22.....11.0+++foo~~~~~~~~~~Keanu Reeves
+ 33+++122.33\\\bar.........Gerard Butler
+++44~~~~12.01 baz~~Jennifer Love Hewitt
+~~55 11+++foo++++Jada Pinkett-Smith
+..66++++++.03~~~bar Bill Murray
+""".strip("\r\n")
+ delimiter = " +~.\\"
+ colspecs = ((0, 4), (7, 13), (15, 19), (21, 41))
+ expected = read_fwf(StringIO(test), colspecs=colspecs, delimiter=delimiter)
+
+ result = read_fwf(StringIO(test), delimiter=delimiter)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_variable_width_unicode():
+ data = """
+שלום שלום
+ום שלל
+של ום
+""".strip("\r\n")
+ encoding = "utf8"
+ kwargs = {"header": None, "encoding": encoding}
+
+ expected = read_fwf(
+ BytesIO(data.encode(encoding)), colspecs=[(0, 4), (5, 9)], **kwargs
+ )
+ result = read_fwf(BytesIO(data.encode(encoding)), **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", [{}, {"a": "float64", "b": str, "c": "int32"}])
+def test_dtype(dtype):
+ data = """ a b c
+1 2 3.2
+3 4 5.2
+"""
+ colspecs = [(0, 5), (5, 10), (10, None)]
+ result = read_fwf(StringIO(data), colspecs=colspecs, dtype=dtype)
+
+ expected = DataFrame(
+ {"a": [1, 3], "b": [2, 4], "c": [3.2, 5.2]}, columns=["a", "b", "c"]
+ )
+
+ for col, dt in dtype.items():
+ expected[col] = expected[col].astype(dt)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_skiprows_inference():
+ # see gh-11256
+ data = """
+Text contained in the file header
+
+DataCol1 DataCol2
+ 0.0 1.0
+ 101.6 956.1
+""".strip()
+ skiprows = 2
+
+ expected = read_csv(StringIO(data), skiprows=skiprows, sep=r"\s+")
+ result = read_fwf(StringIO(data), skiprows=skiprows)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_skiprows_by_index_inference():
+ data = """
+To be skipped
+Not To Be Skipped
+Once more to be skipped
+123 34 8 123
+456 78 9 456
+""".strip()
+ skiprows = [0, 2]
+
+ expected = read_csv(StringIO(data), skiprows=skiprows, sep=r"\s+")
+ result = read_fwf(StringIO(data), skiprows=skiprows)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_skiprows_inference_empty():
+ data = """
+AA BBB C
+12 345 6
+78 901 2
+""".strip()
+
+ msg = "No rows from which to infer column width"
+ with pytest.raises(EmptyDataError, match=msg):
+ read_fwf(StringIO(data), skiprows=3)
+
+
+def test_whitespace_preservation():
+ # see gh-16772
+ header = None
+ csv_data = """
+ a ,bbb
+ cc,dd """
+
+ fwf_data = """
+ a bbb
+ ccdd """
+ result = read_fwf(
+ StringIO(fwf_data), widths=[3, 3], header=header, skiprows=[0], delimiter="\n\t"
+ )
+ expected = read_csv(StringIO(csv_data), header=header)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_default_delimiter():
+ header = None
+ csv_data = """
+a,bbb
+cc,dd"""
+
+ fwf_data = """
+a \tbbb
+cc\tdd """
+ result = read_fwf(StringIO(fwf_data), widths=[3, 3], header=header, skiprows=[0])
+ expected = read_csv(StringIO(csv_data), header=header)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("infer", [True, False])
+def test_fwf_compression(compression_only, infer, compression_to_extension, temp_file):
+ data = """1111111111
+ 2222222222
+ 3333333333""".strip()
+
+ compression = compression_only
+ extension = compression_to_extension[compression]
+
+ kwargs = {"widths": [5, 5], "names": ["one", "two"]}
+ expected = read_fwf(StringIO(data), **kwargs)
+
+ data = bytes(data, encoding="utf-8")
+
+ path = temp_file.parent / f"tmp.{extension}"
+ tm.write_to_compressed(compression, path, data)
+
+ if infer is not None:
+ kwargs["compression"] = "infer" if infer else compression
+
+ result = read_fwf(path, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_binary_mode(temp_file):
+ """
+ read_fwf supports opening files in binary mode.
+
+ GH 18035.
+ """
+ data = """aaa aaa aaa
+bba bab b a"""
+ df_reference = DataFrame(
+ [["bba", "bab", "b a"]], columns=["aaa", "aaa.1", "aaa.2"], index=[0]
+ )
+ path = temp_file
+ path.write_text(data, encoding="utf-8")
+ with open(path, "rb") as file:
+ df = read_fwf(file)
+ file.seek(0)
+ tm.assert_frame_equal(df, df_reference)
+
+
+@pytest.mark.parametrize("memory_map", [True, False])
+def test_encoding_mmap(memory_map, temp_file):
+ """
+ encoding should be working, even when using a memory-mapped file.
+
+ GH 23254.
+ """
+ encoding = "iso8859_1"
+ path = temp_file
+ path.write_bytes(" 1 A Ä 2\n".encode(encoding))
+ df = read_fwf(
+ path,
+ header=None,
+ widths=[2, 2, 2, 2],
+ encoding=encoding,
+ memory_map=memory_map,
+ )
+ df_reference = DataFrame([[1, "A", "Ä", 2]])
+ tm.assert_frame_equal(df, df_reference)
+
+
+@pytest.mark.parametrize(
+ "colspecs, names, widths, index_col",
+ [
+ (
+ [(0, 6), (6, 12), (12, 18), (18, None)],
+ list("abcde"),
+ None,
+ None,
+ ),
+ (
+ None,
+ list("abcde"),
+ [6] * 4,
+ None,
+ ),
+ (
+ [(0, 6), (6, 12), (12, 18), (18, None)],
+ list("abcde"),
+ None,
+ True,
+ ),
+ (
+ None,
+ list("abcde"),
+ [6] * 4,
+ False,
+ ),
+ (
+ None,
+ list("abcde"),
+ [6] * 4,
+ True,
+ ),
+ (
+ [(0, 6), (6, 12), (12, 18), (18, None)],
+ list("abcde"),
+ None,
+ False,
+ ),
+ ],
+)
+def test_len_colspecs_len_names(colspecs, names, widths, index_col):
+ # GH#40830
+ data = """col1 col2 col3 col4
+ bab ba 2"""
+ msg = "Length of colspecs must match length of names"
+ with pytest.raises(ValueError, match=msg):
+ read_fwf(
+ StringIO(data),
+ colspecs=colspecs,
+ names=names,
+ widths=widths,
+ index_col=index_col,
+ )
+
+
+@pytest.mark.parametrize(
+ "colspecs, names, widths, index_col, expected",
+ [
+ (
+ [(0, 6), (6, 12), (12, 18), (18, None)],
+ list("abc"),
+ None,
+ 0,
+ DataFrame(
+ index=["col1", "ba"],
+ columns=["a", "b", "c"],
+ data=[["col2", "col3", "col4"], ["b ba", "2", np.nan]],
+ ),
+ ),
+ (
+ [(0, 6), (6, 12), (12, 18), (18, None)],
+ list("ab"),
+ None,
+ [0, 1],
+ DataFrame(
+ index=[["col1", "ba"], ["col2", "b ba"]],
+ columns=["a", "b"],
+ data=[["col3", "col4"], ["2", np.nan]],
+ ),
+ ),
+ (
+ [(0, 6), (6, 12), (12, 18), (18, None)],
+ list("a"),
+ None,
+ [0, 1, 2],
+ DataFrame(
+ index=[["col1", "ba"], ["col2", "b ba"], ["col3", "2"]],
+ columns=["a"],
+ data=[["col4"], [np.nan]],
+ ),
+ ),
+ (
+ None,
+ list("abc"),
+ [6] * 4,
+ 0,
+ DataFrame(
+ index=["col1", "ba"],
+ columns=["a", "b", "c"],
+ data=[["col2", "col3", "col4"], ["b ba", "2", np.nan]],
+ ),
+ ),
+ (
+ None,
+ list("ab"),
+ [6] * 4,
+ [0, 1],
+ DataFrame(
+ index=[["col1", "ba"], ["col2", "b ba"]],
+ columns=["a", "b"],
+ data=[["col3", "col4"], ["2", np.nan]],
+ ),
+ ),
+ (
+ None,
+ list("a"),
+ [6] * 4,
+ [0, 1, 2],
+ DataFrame(
+ index=[["col1", "ba"], ["col2", "b ba"], ["col3", "2"]],
+ columns=["a"],
+ data=[["col4"], [np.nan]],
+ ),
+ ),
+ ],
+)
+def test_len_colspecs_len_names_with_index_col(
+ colspecs, names, widths, index_col, expected
+):
+ # GH#40830
+ data = """col1 col2 col3 col4
+ bab ba 2"""
+ result = read_fwf(
+ StringIO(data),
+ colspecs=colspecs,
+ names=names,
+ widths=widths,
+ index_col=index_col,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_colspecs_with_comment():
+ # GH 14135
+ result = read_fwf(
+ StringIO("#\nA1K\n"), colspecs=[(1, 2), (2, 3)], comment="#", header=None
+ )
+ expected = DataFrame([[1, "K"]], columns=[0, 1])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_skip_rows_and_n_rows():
+ # GH#44021
+ data = """a\tb
+1\t a
+2\t b
+3\t c
+4\t d
+5\t e
+6\t f
+ """
+ result = read_fwf(StringIO(data), nrows=4, skiprows=[2, 4])
+ expected = DataFrame({"a": [1, 3, 5, 6], "b": ["a", "c", "e", "f"]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_skiprows_with_iterator():
+ # GH#10261, GH#56323
+ data = """0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+ """
+ df_iter = read_fwf(
+ StringIO(data),
+ colspecs=[(0, 2)],
+ names=["a"],
+ iterator=True,
+ chunksize=2,
+ skiprows=[0, 1, 2, 6, 9],
+ )
+ expected_frames = [
+ DataFrame({"a": [3, 4]}),
+ DataFrame({"a": [5, 7]}, index=[2, 3]),
+ DataFrame({"a": [8]}, index=[4]),
+ ]
+ for i, result in enumerate(df_iter):
+ tm.assert_frame_equal(result, expected_frames[i])
+
+
+def test_names_and_infer_colspecs():
+ # GH#45337
+ data = """X Y Z
+ 959.0 345 22.2
+ """
+ result = read_fwf(StringIO(data), skiprows=1, usecols=[0, 2], names=["a", "b"])
+ expected = DataFrame({"a": [959.0], "b": 22.2})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_widths_and_usecols():
+ # GH#46580
+ data = """0 1 n -0.4100.1
+0 2 p 0.2 90.1
+0 3 n -0.3140.4"""
+ result = read_fwf(
+ StringIO(data),
+ header=None,
+ usecols=(0, 1, 3),
+ widths=(3, 5, 1, 5, 5),
+ index_col=False,
+ names=("c0", "c1", "c3"),
+ )
+ expected = DataFrame(
+ {
+ "c0": 0,
+ "c1": [1, 2, 3],
+ "c3": [-0.4, 0.2, -0.3],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_dtype_backend(string_storage, dtype_backend):
+ # GH#50289
+ data = """a b c d e f g h i
+1 2.5 True a
+3 4.5 False b True 6 7.5 a"""
+ with pd.option_context("mode.string_storage", string_storage):
+ result = read_fwf(StringIO(data), dtype_backend=dtype_backend)
+
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ string_dtype = pd.ArrowDtype(pa.string())
+ else:
+ string_dtype = pd.StringDtype(string_storage)
+
+ expected = DataFrame(
+ {
+ "a": pd.Series([1, 3], dtype="Int64"),
+ "b": pd.Series([2.5, 4.5], dtype="Float64"),
+ "c": pd.Series([True, False], dtype="boolean"),
+ "d": pd.Series(["a", "b"], dtype=string_dtype),
+ "e": pd.Series([pd.NA, True], dtype="boolean"),
+ "f": pd.Series([pd.NA, 6], dtype="Int64"),
+ "g": pd.Series([pd.NA, 7.5], dtype="Float64"),
+ "h": pd.Series([None, "a"], dtype=string_dtype),
+ "i": pd.Series([pd.NA, pd.NA], dtype="Int64"),
+ }
+ )
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ from pandas.arrays import ArrowExtensionArray
+
+ expected = DataFrame(
+ {
+ col: ArrowExtensionArray(pa.array(expected[col], from_pandas=True))
+ for col in expected.columns
+ }
+ )
+ expected["i"] = ArrowExtensionArray(pa.array([None, None]))
+
+ # the storage of the str columns' Index is also affected by the
+ # string_storage setting -> ignore that for checking the result
+ tm.assert_frame_equal(result, expected, check_column_type=False)
+
+
+def test_invalid_dtype_backend():
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_fwf("test", dtype_backend="numpy")
+
+
+@pytest.mark.network
+@pytest.mark.single_cpu
+def test_url_urlopen(httpserver):
+ data = """\
+A B C D
+201158 360.242940 149.910199 11950.7
+201159 444.953632 166.985655 11788.4
+201160 364.136849 183.628767 11806.2
+201161 413.836124 184.375703 11916.8
+201162 502.953953 173.237159 12468.3
+"""
+ httpserver.serve_content(content=data)
+ expected = pd.Index(list("ABCD"))
+ with urlopen(httpserver.url) as f:
+ result = read_fwf(f).columns
+
+ tm.assert_index_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_skiprows.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_skiprows.py
new file mode 100644
index 0000000000000000000000000000000000000000..619b53abec8c711297ddf4a2469a8fce77507ca3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_skiprows.py
@@ -0,0 +1,336 @@
+"""
+Tests that skipped rows are properly handled during
+parsing for all of the parsers defined in parsers.py
+"""
+
+from datetime import datetime
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas.errors import EmptyDataError
+
+from pandas import (
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+@pytest.mark.parametrize("skiprows", [list(range(6)), 6])
+def test_skip_rows_bug(all_parsers, skiprows):
+ # see gh-505
+ parser = all_parsers
+ text = """#foo,a,b,c
+#foo,a,b,c
+#foo,a,b,c
+#foo,a,b,c
+#foo,a,b,c
+#foo,a,b,c
+1/1/2000,1.,2.,3.
+1/2/2000,4,5,6
+1/3/2000,7,8,9
+"""
+ result = parser.read_csv(
+ StringIO(text), skiprows=skiprows, header=None, index_col=0, parse_dates=True
+ )
+ index = Index(
+ [datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)],
+ dtype="M8[us]",
+ name=0,
+ )
+
+ expected = DataFrame(
+ np.arange(1.0, 10.0).reshape((3, 3)), columns=[1, 2, 3], index=index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+def test_deep_skip_rows(all_parsers):
+ # see gh-4382
+ parser = all_parsers
+ data = "a,b,c\n" + "\n".join(
+ [",".join([str(i), str(i + 1), str(i + 2)]) for i in range(10)]
+ )
+ condensed_data = "a,b,c\n" + "\n".join(
+ [",".join([str(i), str(i + 1), str(i + 2)]) for i in [0, 1, 2, 3, 4, 6, 8, 9]]
+ )
+
+ result = parser.read_csv(StringIO(data), skiprows=[6, 8])
+ condensed_result = parser.read_csv(StringIO(condensed_data))
+ tm.assert_frame_equal(result, condensed_result)
+
+
+@xfail_pyarrow # AssertionError: DataFrame are different
+def test_skip_rows_blank(all_parsers):
+ # see gh-9832
+ parser = all_parsers
+ text = """#foo,a,b,c
+#foo,a,b,c
+
+#foo,a,b,c
+#foo,a,b,c
+
+1/1/2000,1.,2.,3.
+1/2/2000,4,5,6
+1/3/2000,7,8,9
+"""
+ data = parser.read_csv(
+ StringIO(text), skiprows=6, header=None, index_col=0, parse_dates=True
+ )
+ index = Index(
+ [datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)],
+ dtype="M8[us]",
+ name=0,
+ )
+
+ expected = DataFrame(
+ np.arange(1.0, 10.0).reshape((3, 3)), columns=[1, 2, 3], index=index
+ )
+ tm.assert_frame_equal(data, expected)
+
+
+@pytest.mark.parametrize(
+ "data,kwargs,expected",
+ [
+ (
+ """id,text,num_lines
+1,"line 11
+line 12",2
+2,"line 21
+line 22",2
+3,"line 31",1""",
+ {"skiprows": [1]},
+ DataFrame(
+ [[2, "line 21\nline 22", 2], [3, "line 31", 1]],
+ columns=["id", "text", "num_lines"],
+ ),
+ ),
+ (
+ "a,b,c\n~a\n b~,~e\n d~,~f\n f~\n1,2,~12\n 13\n 14~",
+ {"quotechar": "~", "skiprows": [2]},
+ DataFrame([["a\n b", "e\n d", "f\n f"]], columns=["a", "b", "c"]),
+ ),
+ (
+ (
+ "Text,url\n~example\n "
+ "sentence\n one~,url1\n~"
+ "example\n sentence\n two~,url2\n~"
+ "example\n sentence\n three~,url3"
+ ),
+ {"quotechar": "~", "skiprows": [1, 3]},
+ DataFrame([["example\n sentence\n two", "url2"]], columns=["Text", "url"]),
+ ),
+ ],
+)
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+def test_skip_row_with_newline(all_parsers, data, kwargs, expected):
+ # see gh-12775 and gh-10911
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+def test_skip_row_with_quote(all_parsers):
+ # see gh-12775 and gh-10911
+ parser = all_parsers
+ data = """id,text,num_lines
+1,"line '11' line 12",2
+2,"line '21' line 22",2
+3,"line '31' line 32",1"""
+
+ exp_data = [[2, "line '21' line 22", 2], [3, "line '31' line 32", 1]]
+ expected = DataFrame(exp_data, columns=["id", "text", "num_lines"])
+
+ result = parser.read_csv(StringIO(data), skiprows=[1])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data,exp_data",
+ [
+ (
+ """id,text,num_lines
+1,"line \n'11' line 12",2
+2,"line \n'21' line 22",2
+3,"line \n'31' line 32",1""",
+ [[2, "line \n'21' line 22", 2], [3, "line \n'31' line 32", 1]],
+ ),
+ (
+ """id,text,num_lines
+1,"line '11\n' line 12",2
+2,"line '21\n' line 22",2
+3,"line '31\n' line 32",1""",
+ [[2, "line '21\n' line 22", 2], [3, "line '31\n' line 32", 1]],
+ ),
+ (
+ """id,text,num_lines
+1,"line '11\n' \r\tline 12",2
+2,"line '21\n' \r\tline 22",2
+3,"line '31\n' \r\tline 32",1""",
+ [[2, "line '21\n' \r\tline 22", 2], [3, "line '31\n' \r\tline 32", 1]],
+ ),
+ ],
+)
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+def test_skip_row_with_newline_and_quote(all_parsers, data, exp_data):
+ # see gh-12775 and gh-10911
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), skiprows=[1])
+
+ expected = DataFrame(exp_data, columns=["id", "text", "num_lines"])
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: the 'pyarrow' engine does not support regex separators
+@pytest.mark.parametrize(
+ "lineterminator",
+ ["\n", "\r\n", "\r"], # "LF" # "CRLF" # "CR"
+)
+def test_skiprows_lineterminator(all_parsers, lineterminator, request):
+ # see gh-9079
+ parser = all_parsers
+ data = "\n".join(
+ [
+ "SMOSMANIA ThetaProbe-ML2X ",
+ "2007/01/01 01:00 0.2140 U M ",
+ "2007/01/01 02:00 0.2141 M O ",
+ "2007/01/01 04:00 0.2142 D M ",
+ ]
+ )
+ expected = DataFrame(
+ [
+ ["2007/01/01", "01:00", 0.2140, "U", "M"],
+ ["2007/01/01", "02:00", 0.2141, "M", "O"],
+ ["2007/01/01", "04:00", 0.2142, "D", "M"],
+ ],
+ columns=["date", "time", "var", "flag", "oflag"],
+ )
+
+ if parser.engine == "python" and lineterminator == "\r":
+ mark = pytest.mark.xfail(reason="'CR' not respect with the Python parser yet")
+ request.applymarker(mark)
+
+ data = data.replace("\n", lineterminator)
+
+ result = parser.read_csv(
+ StringIO(data),
+ skiprows=1,
+ sep=r"\s+",
+ names=["date", "time", "var", "flag", "oflag"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # AssertionError: DataFrame are different
+def test_skiprows_infield_quote(all_parsers):
+ # see gh-14459
+ parser = all_parsers
+ data = 'a"\nb"\na\n1'
+ expected = DataFrame({"a": [1]})
+
+ result = parser.read_csv(StringIO(data), skiprows=2)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+@pytest.mark.parametrize(
+ "kwargs,expected",
+ [
+ ({}, "1"),
+ ({"header": 0, "names": ["foo"]}, "foo"),
+ ],
+)
+def test_skip_rows_callable(all_parsers, kwargs, expected):
+ parser = all_parsers
+ data = "a\n1\n2\n3\n4\n5"
+
+ result = parser.read_csv(StringIO(data), skiprows=lambda x: x % 2 == 0, **kwargs)
+ expected = DataFrame({expected: [3, 5]})
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+def test_skip_rows_callable_not_in(all_parsers):
+ parser = all_parsers
+ data = "0,a\n1,b\n2,c\n3,d\n4,e"
+ expected = DataFrame([[1, "b"], [3, "d"]])
+
+ result = parser.read_csv(
+ StringIO(data), header=None, skiprows=lambda x: x not in [1, 3]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+def test_skip_rows_skip_all(all_parsers):
+ parser = all_parsers
+ data = "a\n1\n2\n3\n4\n5"
+ msg = "No columns to parse from file"
+
+ with pytest.raises(EmptyDataError, match=msg):
+ parser.read_csv(StringIO(data), skiprows=lambda x: True)
+
+
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+def test_skip_rows_bad_callable(all_parsers):
+ msg = "by zero"
+ parser = all_parsers
+ data = "a\n1\n2\n3\n4\n5"
+
+ with pytest.raises(ZeroDivisionError, match=msg):
+ parser.read_csv(StringIO(data), skiprows=lambda x: 1 / 0)
+
+
+@xfail_pyarrow # ValueError: skiprows argument must be an integer
+def test_skip_rows_and_n_rows(all_parsers):
+ # GH#44021
+ data = """a,b
+1,a
+2,b
+3,c
+4,d
+5,e
+6,f
+7,g
+8,h
+"""
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), nrows=5, skiprows=[2, 4, 6])
+ expected = DataFrame({"a": [1, 3, 5, 7, 8], "b": ["a", "c", "e", "g", "h"]})
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow
+def test_skip_rows_with_chunks(all_parsers):
+ # GH 55677
+ data = """col_a
+10
+20
+30
+40
+50
+60
+70
+80
+90
+100
+"""
+ parser = all_parsers
+ reader = parser.read_csv(
+ StringIO(data), engine=parser, skiprows=lambda x: x in [1, 4, 5], chunksize=4
+ )
+ df1 = next(reader)
+ df2 = next(reader)
+
+ tm.assert_frame_equal(df1, DataFrame({"col_a": [20, 30, 60, 70]}))
+ tm.assert_frame_equal(df2, DataFrame({"col_a": [80, 90, 100]}, index=[4, 5, 6]))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_textreader.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_textreader.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea44564e3f3e14e691ab682a2be6975b18c75c10
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_textreader.py
@@ -0,0 +1,386 @@
+"""
+Tests the TextReader class in parsers.pyx, which
+is integral to the C engine in parsers.py
+"""
+
+from io import (
+ BytesIO,
+ StringIO,
+)
+
+import numpy as np
+import pytest
+
+import pandas._libs.parsers as parser
+from pandas._libs.parsers import TextReader
+from pandas.errors import ParserWarning
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+from pandas.io.parsers import (
+ TextFileReader,
+ read_csv,
+)
+from pandas.io.parsers.c_parser_wrapper import ensure_dtype_objs
+
+# The only non-test way that TextReader gets called has na_valuess and na_fvalues
+# either both-sets or both dicts, and the code assumes this is the case.
+# But the default argument in its __init__ is None, so we have to pass these
+# explicitly in tests.
+_na_value_kwargs: dict[str, set] = {"na_values": set(), "na_fvalues": set()}
+
+
+class TestTextReader:
+ @pytest.fixture
+ def csv_path(self, datapath):
+ return datapath("io", "data", "csv", "test1.csv")
+
+ def test_file_handle(self, csv_path):
+ with open(csv_path, "rb") as f:
+ reader = TextReader(f, **_na_value_kwargs)
+ reader.read()
+
+ def test_file_handle_mmap(self, csv_path):
+ # this was never using memory_map=True
+ with open(csv_path, "rb") as f:
+ reader = TextReader(f, header=None, **_na_value_kwargs)
+ reader.read()
+
+ def test_StringIO(self, csv_path):
+ with open(csv_path, "rb") as f:
+ text = f.read()
+ src = BytesIO(text)
+ reader = TextReader(src, header=None, **_na_value_kwargs)
+ reader.read()
+
+ def test_encoding_mismatch_warning(self, csv_path):
+ # GH-57954
+ with open(csv_path, encoding="UTF-8") as f:
+ msg = "latin1 is different from the encoding"
+ with pytest.raises(ValueError, match=msg):
+ read_csv(f, encoding="latin1")
+
+ def test_string_factorize(self):
+ # should this be optional?
+ data = "a\nb\na\nb\na"
+ reader = TextReader(StringIO(data), header=None, **_na_value_kwargs)
+ result = reader.read()
+ assert len(set(map(id, result[0]))) == 2
+
+ def test_skipinitialspace(self):
+ data = "a, b\na, b\na, b\na, b"
+
+ reader = TextReader(
+ StringIO(data), skipinitialspace=True, header=None, **_na_value_kwargs
+ )
+ result = reader.read()
+
+ tm.assert_numpy_array_equal(
+ result[0], np.array(["a", "a", "a", "a"], dtype=np.object_)
+ )
+ tm.assert_numpy_array_equal(
+ result[1], np.array(["b", "b", "b", "b"], dtype=np.object_)
+ )
+
+ def test_parse_booleans(self):
+ data = "True\nFalse\nTrue\nTrue"
+
+ reader = TextReader(StringIO(data), header=None, **_na_value_kwargs)
+ result = reader.read()
+
+ assert result[0].dtype == np.bool_
+
+ def test_delimit_whitespace(self):
+ data = 'a b\na\t\t "b"\n"a"\t \t b'
+
+ reader = TextReader(
+ StringIO(data), delim_whitespace=True, header=None, **_na_value_kwargs
+ )
+ result = reader.read()
+
+ tm.assert_numpy_array_equal(
+ result[0], np.array(["a", "a", "a"], dtype=np.object_)
+ )
+ tm.assert_numpy_array_equal(
+ result[1], np.array(["b", "b", "b"], dtype=np.object_)
+ )
+
+ def test_embedded_newline(self):
+ data = 'a\n"hello\nthere"\nthis'
+
+ reader = TextReader(StringIO(data), header=None, **_na_value_kwargs)
+ result = reader.read()
+
+ expected = np.array(["a", "hello\nthere", "this"], dtype=np.object_)
+ tm.assert_numpy_array_equal(result[0], expected)
+
+ def test_euro_decimal(self):
+ data = "12345,67\n345,678"
+
+ reader = TextReader(
+ StringIO(data), delimiter=":", decimal=",", header=None, **_na_value_kwargs
+ )
+ result = reader.read()
+
+ expected = np.array([12345.67, 345.678])
+ tm.assert_almost_equal(result[0], expected)
+
+ def test_integer_thousands(self):
+ data = "123,456\n12,500"
+
+ reader = TextReader(
+ StringIO(data),
+ delimiter=":",
+ thousands=",",
+ header=None,
+ **_na_value_kwargs,
+ )
+ result = reader.read()
+
+ expected = np.array([123456, 12500], dtype=np.int64)
+ tm.assert_almost_equal(result[0], expected)
+
+ def test_integer_thousands_alt(self):
+ data = "123.456\n12.500"
+
+ reader = TextFileReader(
+ StringIO(data), delimiter=":", thousands=".", header=None
+ )
+ result = reader.read()
+
+ expected = DataFrame([123456, 12500])
+ tm.assert_frame_equal(result, expected)
+
+ def test_skip_bad_lines(self):
+ # too many lines, see #2430 for why
+ data = "a:b:c\nd:e:f\ng:h:i\nj:k:l:m\nl:m:n\no:p:q:r"
+
+ reader = TextReader(
+ StringIO(data), delimiter=":", header=None, **_na_value_kwargs
+ )
+ msg = r"Error tokenizing data\. C error: Expected 3 fields in line 4, saw 4"
+ with pytest.raises(parser.ParserError, match=msg):
+ reader.read()
+
+ reader = TextReader(
+ StringIO(data),
+ delimiter=":",
+ header=None,
+ on_bad_lines=2, # Skip
+ **_na_value_kwargs,
+ )
+ result = reader.read()
+ expected = {
+ 0: np.array(["a", "d", "g", "l"], dtype=object),
+ 1: np.array(["b", "e", "h", "m"], dtype=object),
+ 2: np.array(["c", "f", "i", "n"], dtype=object),
+ }
+ assert_array_dicts_equal(result, expected)
+
+ with tm.assert_produces_warning(ParserWarning, match="Skipping line"):
+ reader = TextReader(
+ StringIO(data),
+ delimiter=":",
+ header=None,
+ on_bad_lines=1, # Warn
+ **_na_value_kwargs,
+ )
+ reader.read()
+
+ def test_header_not_enough_lines(self):
+ data = "skip this\nskip this\na,b,c\n1,2,3\n4,5,6"
+
+ reader = TextReader(StringIO(data), delimiter=",", header=2, **_na_value_kwargs)
+ header = reader.header
+ expected = [["a", "b", "c"]]
+ assert header == expected
+
+ recs = reader.read()
+ expected = {
+ 0: np.array([1, 4], dtype=np.int64),
+ 1: np.array([2, 5], dtype=np.int64),
+ 2: np.array([3, 6], dtype=np.int64),
+ }
+ assert_array_dicts_equal(recs, expected)
+
+ def test_escapechar(self):
+ data = '\\"hello world"\n\\"hello world"\n\\"hello world"'
+
+ reader = TextReader(
+ StringIO(data),
+ delimiter=",",
+ header=None,
+ escapechar="\\",
+ **_na_value_kwargs,
+ )
+ result = reader.read()
+ expected = {0: np.array(['"hello world"'] * 3, dtype=object)}
+ assert_array_dicts_equal(result, expected)
+
+ def test_eof_has_eol(self):
+ # handling of new line at EOF
+ pass
+
+ def test_na_substitution(self):
+ pass
+
+ def test_numpy_string_dtype(self):
+ data = """\
+a,1
+aa,2
+aaa,3
+aaaa,4
+aaaaa,5"""
+
+ def _make_reader(**kwds):
+ if "dtype" in kwds:
+ kwds["dtype"] = ensure_dtype_objs(kwds["dtype"])
+ return TextReader(
+ StringIO(data), delimiter=",", header=None, **kwds, **_na_value_kwargs
+ )
+
+ reader = _make_reader(dtype="S5,i4")
+ result = reader.read()
+
+ assert result[0].dtype == "S5"
+
+ ex_values = np.array(["a", "aa", "aaa", "aaaa", "aaaaa"], dtype="S5")
+ assert (result[0] == ex_values).all()
+ assert result[1].dtype == "i4"
+
+ reader = _make_reader(dtype="S4")
+ result = reader.read()
+ assert result[0].dtype == "S4"
+ ex_values = np.array(["a", "aa", "aaa", "aaaa", "aaaa"], dtype="S4")
+ assert (result[0] == ex_values).all()
+ assert result[1].dtype == "S4"
+
+ def test_pass_dtype(self):
+ data = """\
+one,two
+1,a
+2,b
+3,c
+4,d"""
+
+ def _make_reader(**kwds):
+ if "dtype" in kwds:
+ kwds["dtype"] = ensure_dtype_objs(kwds["dtype"])
+ return TextReader(StringIO(data), delimiter=",", **kwds, **_na_value_kwargs)
+
+ reader = _make_reader(dtype={"one": "u1", 1: "S1"})
+ result = reader.read()
+ assert result[0].dtype == "u1"
+ assert result[1].dtype == "S1"
+
+ reader = _make_reader(dtype={"one": np.uint8, 1: object})
+ result = reader.read()
+ assert result[0].dtype == "u1"
+ assert result[1].dtype == "O"
+
+ reader = _make_reader(dtype={"one": np.dtype("u1"), 1: np.dtype("O")})
+ result = reader.read()
+ assert result[0].dtype == "u1"
+ assert result[1].dtype == "O"
+
+ def test_usecols(self):
+ data = """\
+a,b,c
+1,2,3
+4,5,6
+7,8,9
+10,11,12"""
+
+ def _make_reader(**kwds):
+ return TextReader(StringIO(data), delimiter=",", **kwds, **_na_value_kwargs)
+
+ reader = _make_reader(usecols=(1, 2))
+ result = reader.read()
+
+ exp = _make_reader().read()
+ assert len(result) == 2
+ assert (result[1] == exp[1]).all()
+ assert (result[2] == exp[2]).all()
+
+ @pytest.mark.parametrize(
+ "text, kwargs",
+ [
+ ("a,b,c\r1,2,3\r4,5,6\r7,8,9\r10,11,12", {"delimiter": ","}),
+ (
+ "a b c\r1 2 3\r4 5 6\r7 8 9\r10 11 12",
+ {"delim_whitespace": True},
+ ),
+ ("a,b,c\r1,2,3\r4,5,6\r,88,9\r10,11,12", {"delimiter": ","}),
+ (
+ (
+ "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O\r"
+ "AAAAA,BBBBB,0,0,0,0,0,0,0,0,0,0,0,0,0\r"
+ ",BBBBB,0,0,0,0,0,0,0,0,0,0,0,0,0"
+ ),
+ {"delimiter": ","},
+ ),
+ ("A B C\r 2 3\r4 5 6", {"delim_whitespace": True}),
+ ("A B C\r2 3\r4 5 6", {"delim_whitespace": True}),
+ ],
+ )
+ def test_cr_delimited(self, text, kwargs):
+ nice_text = text.replace("\r", "\r\n")
+ result = TextReader(StringIO(text), **kwargs, **_na_value_kwargs).read()
+ expected = TextReader(StringIO(nice_text), **kwargs, **_na_value_kwargs).read()
+ assert_array_dicts_equal(result, expected)
+
+ def test_empty_field_eof(self):
+ data = "a,b,c\n1,2,3\n4,,"
+
+ result = TextReader(StringIO(data), delimiter=",", **_na_value_kwargs).read()
+
+ expected = {
+ 0: np.array([1, 4], dtype=np.int64),
+ 1: np.array(["2", ""], dtype=object),
+ 2: np.array(["3", ""], dtype=object),
+ }
+ assert_array_dicts_equal(result, expected)
+
+ @pytest.mark.parametrize("repeat", range(10))
+ def test_empty_field_eof_mem_access_bug(self, repeat):
+ # GH5664
+ a = DataFrame([["b"], [np.nan]], columns=["a"], index=["a", "c"])
+ b = DataFrame([[1, 1, 1, 0], [1, 1, 1, 0]], columns=list("abcd"), index=[1, 1])
+ c = DataFrame(
+ [
+ [1, 2, 3, 4],
+ [6, np.nan, np.nan, np.nan],
+ [8, 9, 10, 11],
+ [13, 14, np.nan, np.nan],
+ ],
+ columns=list("abcd"),
+ index=[0, 5, 7, 12],
+ )
+
+ df = read_csv(StringIO("a,b\nc\n"), skiprows=0, names=["a"], engine="c")
+ tm.assert_frame_equal(df, a)
+
+ df = read_csv(
+ StringIO("1,1,1,1,0\n" * 2 + "\n" * 2), names=list("abcd"), engine="c"
+ )
+ tm.assert_frame_equal(df, b)
+
+ df = read_csv(
+ StringIO("0,1,2,3,4\n5,6\n7,8,9,10,11\n12,13,14"),
+ names=list("abcd"),
+ engine="c",
+ )
+ tm.assert_frame_equal(df, c)
+
+ def test_empty_csv_input(self):
+ # GH14867
+ with read_csv(
+ StringIO(), chunksize=20, header=None, names=["a", "b", "c"]
+ ) as df:
+ assert isinstance(df, TextFileReader)
+
+
+def assert_array_dicts_equal(left, right):
+ for k, v in left.items():
+ tm.assert_numpy_array_equal(np.asarray(v), np.asarray(right[k]))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_unsupported.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_unsupported.py
new file mode 100644
index 0000000000000000000000000000000000000000..8812a65ee4e7d1fb2d19399dd5ce6ba409182249
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_unsupported.py
@@ -0,0 +1,207 @@
+"""
+Tests that features that are currently unsupported in
+either the Python or C parser are actually enforced
+and are clearly communicated to the user.
+
+Ultimately, the goal is to remove test cases from this
+test suite as new feature support is added to the parsers.
+"""
+
+from io import StringIO
+import os
+from pathlib import Path
+
+import pytest
+
+from pandas.errors import ParserError
+
+import pandas._testing as tm
+
+from pandas.io.parsers import read_csv
+import pandas.io.parsers.readers as parsers
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.fixture(params=["python", "python-fwf"], ids=lambda val: val)
+def python_engine(request):
+ return request.param
+
+
+class TestUnsupportedFeatures:
+ def test_mangle_dupe_cols_false(self):
+ # see gh-12935
+ data = "a b c\n1 2 3"
+
+ for engine in ("c", "python"):
+ with pytest.raises(TypeError, match="unexpected keyword"):
+ read_csv(StringIO(data), engine=engine, mangle_dupe_cols=True)
+
+ def test_c_engine(self):
+ # see gh-6607
+ data = "a b c\n1 2 3"
+ msg = "does not support"
+
+ # specify C engine with unsupported options (raise)
+ with pytest.raises(ValueError, match=msg):
+ read_csv(StringIO(data), engine="c", sep=r"\s")
+ with pytest.raises(ValueError, match=msg):
+ read_csv(StringIO(data), engine="c", sep="\t", quotechar=chr(128))
+ with pytest.raises(ValueError, match=msg):
+ read_csv(StringIO(data), engine="c", skipfooter=1)
+
+ # specify C-unsupported options without python-unsupported options
+ with tm.assert_produces_warning(parsers.ParserWarning):
+ read_csv(StringIO(data), sep=r"\s")
+ with tm.assert_produces_warning(parsers.ParserWarning):
+ read_csv(StringIO(data), sep="\t", quotechar=chr(128))
+ with tm.assert_produces_warning(parsers.ParserWarning):
+ read_csv(StringIO(data), skipfooter=1)
+
+ text = """ A B C D E
+one two three four
+a b 10.0032 5 -0.5109 -2.3358 -0.4645 0.05076 0.3640
+a q 20 4 0.4473 1.4152 0.2834 1.00661 0.1744
+x q 30 3 -0.6662 -0.5243 -0.3580 0.89145 2.5838"""
+ msg = "Error tokenizing data"
+
+ with pytest.raises(ParserError, match=msg):
+ read_csv(StringIO(text), sep="\\s+")
+ with pytest.raises(ParserError, match=msg):
+ read_csv(StringIO(text), engine="c", sep="\\s+")
+
+ msg = "Only length-1 thousands markers supported"
+ data = """A|B|C
+1|2,334|5
+10|13|10.
+"""
+ with pytest.raises(ValueError, match=msg):
+ read_csv(StringIO(data), thousands=",,")
+ with pytest.raises(ValueError, match=msg):
+ read_csv(StringIO(data), thousands="")
+
+ msg = "Only length-1 line terminators supported"
+ data = "a,b,c~~1,2,3~~4,5,6"
+ with pytest.raises(ValueError, match=msg):
+ read_csv(StringIO(data), lineterminator="~~")
+
+ def test_python_engine(self, python_engine):
+ from pandas.io.parsers.readers import _python_unsupported as py_unsupported
+
+ data = """1,2,3,,
+1,2,3,4,
+1,2,3,4,5
+1,2,,,
+1,2,3,4,"""
+
+ for default in py_unsupported:
+ msg = (
+ f"The {default!r} option is not "
+ f"supported with the {python_engine!r} engine"
+ )
+
+ kwargs = {default: object()}
+ with pytest.raises(ValueError, match=msg):
+ read_csv(StringIO(data), engine=python_engine, **kwargs)
+
+ def test_python_engine_file_no_iter(self, python_engine):
+ # see gh-16530
+ class NoNextBuffer:
+ def __init__(self, csv_data) -> None:
+ self.data = csv_data
+
+ def __next__(self):
+ return self.data.__next__()
+
+ def read(self):
+ return self.data
+
+ def readline(self):
+ return self.data
+
+ data = "a\n1"
+ msg = "'NoNextBuffer' object is not iterable|argument 1 must be an iterator"
+
+ with pytest.raises(TypeError, match=msg):
+ read_csv(NoNextBuffer(data), engine=python_engine)
+
+ def test_pyarrow_engine(self):
+ from pandas.io.parsers.readers import _pyarrow_unsupported as pa_unsupported
+
+ data = """1,2,3,,
+ 1,2,3,4,
+ 1,2,3,4,5
+ 1,2,,,
+ 1,2,3,4,"""
+
+ for default in pa_unsupported:
+ msg = f"The {default!r} option is not supported with the 'pyarrow' engine"
+ kwargs = {default: object()}
+ default_needs_bool = {"warn_bad_lines", "error_bad_lines"}
+ if default == "dialect":
+ kwargs[default] = "excel" # test a random dialect
+ elif default in default_needs_bool:
+ kwargs[default] = True
+ elif default == "on_bad_lines":
+ kwargs[default] = "warn"
+
+ with pytest.raises(ValueError, match=msg):
+ read_csv(StringIO(data), engine="pyarrow", **kwargs)
+
+ def test_on_bad_lines_callable_python_or_pyarrow(self, all_parsers):
+ # GH 5686
+ # GH 54643
+ sio = StringIO("a,b\n1,2")
+ bad_lines_func = lambda x: x
+ parser = all_parsers
+ if all_parsers.engine not in ["python", "pyarrow"]:
+ msg = (
+ "on_bad_line can only be a callable "
+ "function if engine='python' or 'pyarrow'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(sio, on_bad_lines=bad_lines_func)
+ else:
+ parser.read_csv(sio, on_bad_lines=bad_lines_func)
+
+
+def test_close_file_handle_on_invalid_usecols(all_parsers, temp_file):
+ # GH 45384
+ parser = all_parsers
+
+ error = ValueError
+ if parser.engine == "pyarrow":
+ # Raises pyarrow.lib.ArrowKeyError
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+
+ fname = temp_file
+ Path(fname).write_text("col1,col2\na,b\n1,2", encoding="utf-8")
+ with tm.assert_produces_warning(False):
+ with pytest.raises(error, match="col3"):
+ parser.read_csv(fname, usecols=["col1", "col2", "col3"])
+ # unlink fails on windows if file handles still point to it
+ os.unlink(fname)
+
+
+def test_invalid_file_inputs(request, all_parsers):
+ # GH#45957
+ parser = all_parsers
+ if parser.engine == "python":
+ request.applymarker(
+ pytest.mark.xfail(reason=f"{parser.engine} engine supports lists.")
+ )
+
+ with pytest.raises(ValueError, match="Invalid"):
+ parser.read_csv([])
+
+
+def test_invalid_dtype_backend(all_parsers):
+ parser = all_parsers
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv("test", dtype_backend="numpy")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_upcast.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_upcast.py
new file mode 100644
index 0000000000000000000000000000000000000000..c17b7b6871945534f835003f46e53646e86bd8ae
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/test_upcast.py
@@ -0,0 +1,101 @@
+import numpy as np
+import pytest
+
+from pandas._libs.parsers import (
+ _maybe_upcast,
+ na_values,
+)
+
+import pandas as pd
+from pandas import NA
+import pandas._testing as tm
+from pandas.core.arrays import (
+ ArrowStringArray,
+ BooleanArray,
+ FloatingArray,
+ IntegerArray,
+)
+
+
+def test_maybe_upcast(any_real_numpy_dtype):
+ # GH#36712
+
+ dtype = np.dtype(any_real_numpy_dtype)
+ na_value = na_values[dtype]
+ arr = np.array([1, 2, na_value], dtype=dtype)
+ result = _maybe_upcast(arr, use_dtype_backend=True)
+
+ expected_mask = np.array([False, False, True])
+ if issubclass(dtype.type, np.integer):
+ expected = IntegerArray(arr, mask=expected_mask)
+ else:
+ expected = FloatingArray(arr, mask=expected_mask)
+
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_maybe_upcast_no_na(any_real_numpy_dtype):
+ # GH#36712
+ arr = np.array([1, 2, 3], dtype=any_real_numpy_dtype)
+ result = _maybe_upcast(arr, use_dtype_backend=True)
+
+ expected_mask = np.array([False, False, False])
+ if issubclass(np.dtype(any_real_numpy_dtype).type, np.integer):
+ expected = IntegerArray(arr, mask=expected_mask)
+ else:
+ expected = FloatingArray(arr, mask=expected_mask)
+
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_maybe_upcaste_bool():
+ # GH#36712
+ dtype = np.bool_
+ na_value = na_values[dtype]
+ arr = np.array([True, False, na_value], dtype="uint8").view(dtype)
+ result = _maybe_upcast(arr, use_dtype_backend=True)
+
+ expected_mask = np.array([False, False, True])
+ expected = BooleanArray(arr, mask=expected_mask)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_maybe_upcaste_bool_no_nan():
+ # GH#36712
+ dtype = np.bool_
+ arr = np.array([True, False, False], dtype="uint8").view(dtype)
+ result = _maybe_upcast(arr, use_dtype_backend=True)
+
+ expected_mask = np.array([False, False, False])
+ expected = BooleanArray(arr, mask=expected_mask)
+ tm.assert_extension_array_equal(result, expected)
+
+
+def test_maybe_upcaste_all_nan():
+ # GH#36712
+ dtype = np.int64
+ na_value = na_values[dtype]
+ arr = np.array([na_value, na_value], dtype=dtype)
+ result = _maybe_upcast(arr, use_dtype_backend=True)
+
+ expected_mask = np.array([True, True])
+ expected = IntegerArray(arr, mask=expected_mask)
+ tm.assert_extension_array_equal(result, expected)
+
+
+@pytest.mark.parametrize("val", [na_values[np.object_], "c"])
+def test_maybe_upcast_object(val, string_storage):
+ # GH#36712
+ pa = pytest.importorskip("pyarrow")
+
+ with pd.option_context("mode.string_storage", string_storage):
+ arr = np.array(["a", "b", val], dtype=np.object_)
+ result = _maybe_upcast(arr, use_dtype_backend=True)
+
+ if string_storage == "python":
+ exp_val = "c" if val == "c" else NA
+ expected = pd.array(["a", "b", exp_val], dtype=pd.StringDtype())
+ else:
+ exp_val = "c" if val == "c" else None
+ expected = ArrowStringArray(pa.array(["a", "b", exp_val]))
+ tm.assert_extension_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/test_parse_dates.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/test_parse_dates.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc54f2487aa60230c091bbce1fc5f52a34e79a4f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/test_parse_dates.py
@@ -0,0 +1,87 @@
+"""
+Tests the usecols functionality during parsing
+for all of the parsers defined in parsers.py
+"""
+
+from io import StringIO
+
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Timestamp,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+_msg_pyarrow_requires_names = (
+ "The pyarrow engine does not allow 'usecols' to be integer column "
+ "positions. Pass a list of string column names instead."
+)
+
+
+@skip_pyarrow # pyarrow.lib.ArrowKeyError: Column 'fdate' in include_columns
+def test_usecols_with_parse_dates2(all_parsers):
+ # see gh-13604
+ parser = all_parsers
+ data = """2008-02-07 09:40,1032.43
+2008-02-07 09:50,1042.54
+2008-02-07 10:00,1051.65"""
+
+ names = ["date", "values"]
+ usecols = names[:]
+ parse_dates = [0]
+
+ index = Index(
+ [
+ Timestamp("2008-02-07 09:40"),
+ Timestamp("2008-02-07 09:50"),
+ Timestamp("2008-02-07 10:00"),
+ ],
+ name="date",
+ )
+ cols = {"values": [1032.43, 1042.54, 1051.65]}
+ expected = DataFrame(cols, index=index)
+
+ result = parser.read_csv(
+ StringIO(data),
+ parse_dates=parse_dates,
+ index_col=0,
+ usecols=usecols,
+ header=None,
+ names=names,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_with_parse_dates3(all_parsers):
+ # see gh-14792
+ parser = all_parsers
+ data = """a,b,c,d,e,f,g,h,i,j
+2016/09/21,1,1,2,3,4,5,6,7,8"""
+
+ usecols = list("abcdefghij")
+ parse_dates = [0]
+
+ cols = {
+ "a": Timestamp("2016-09-21"),
+ "b": [1],
+ "c": [1],
+ "d": [2],
+ "e": [3],
+ "f": [4],
+ "g": [5],
+ "h": [6],
+ "i": [7],
+ "j": [8],
+ }
+ expected = DataFrame(cols, columns=usecols)
+
+ result = parser.read_csv(StringIO(data), usecols=usecols, parse_dates=parse_dates)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/test_strings.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/test_strings.py
new file mode 100644
index 0000000000000000000000000000000000000000..1538bd4e805f7b2d1e0d495a5286198eae063e60
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/test_strings.py
@@ -0,0 +1,96 @@
+"""
+Tests the usecols functionality during parsing
+for all of the parsers defined in parsers.py
+"""
+
+from io import StringIO
+
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+def test_usecols_with_unicode_strings(all_parsers):
+ # see gh-13219
+ data = """AAA,BBB,CCC,DDD
+0.056674973,8,True,a
+2.613230982,2,False,b
+3.568935038,7,False,a"""
+ parser = all_parsers
+
+ exp_data = {
+ "AAA": {
+ 0: 0.056674972999999997,
+ 1: 2.6132309819999997,
+ 2: 3.5689350380000002,
+ },
+ "BBB": {0: 8, 1: 2, 2: 7},
+ }
+ expected = DataFrame(exp_data)
+
+ result = parser.read_csv(StringIO(data), usecols=["AAA", "BBB"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_with_single_byte_unicode_strings(all_parsers):
+ # see gh-13219
+ data = """A,B,C,D
+0.056674973,8,True,a
+2.613230982,2,False,b
+3.568935038,7,False,a"""
+ parser = all_parsers
+
+ exp_data = {
+ "A": {
+ 0: 0.056674972999999997,
+ 1: 2.6132309819999997,
+ 2: 3.5689350380000002,
+ },
+ "B": {0: 8, 1: 2, 2: 7},
+ }
+ expected = DataFrame(exp_data)
+
+ result = parser.read_csv(StringIO(data), usecols=["A", "B"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("usecols", [["AAA", b"BBB"], [b"AAA", "BBB"]])
+def test_usecols_with_mixed_encoding_strings(all_parsers, usecols):
+ data = """AAA,BBB,CCC,DDD
+0.056674973,8,True,a
+2.613230982,2,False,b
+3.568935038,7,False,a"""
+ parser = all_parsers
+ _msg_validate_usecols_arg = (
+ "'usecols' must either be list-like "
+ "of all strings, all unicode, all "
+ "integers or a callable."
+ )
+ with pytest.raises(ValueError, match=_msg_validate_usecols_arg):
+ parser.read_csv(StringIO(data), usecols=usecols)
+
+
+def test_usecols_with_multi_byte_characters(all_parsers):
+ data = """あああ,いい,ううう,ええええ
+0.056674973,8,True,a
+2.613230982,2,False,b
+3.568935038,7,False,a"""
+ parser = all_parsers
+
+ exp_data = {
+ "あああ": {
+ 0: 0.056674972999999997,
+ 1: 2.6132309819999997,
+ 2: 3.5689350380000002,
+ },
+ "いい": {0: 8, 1: 2, 2: 7},
+ }
+ expected = DataFrame(exp_data)
+
+ result = parser.read_csv(StringIO(data), usecols=["あああ", "いい"])
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/test_usecols_basic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/test_usecols_basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..82b42beb38ae00c6ede967222f0d6729e74d43b9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/parser/usecols/test_usecols_basic.py
@@ -0,0 +1,547 @@
+"""
+Tests the usecols functionality during parsing
+for all of the parsers defined in parsers.py
+"""
+
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas.errors import ParserError
+
+from pandas import (
+ DataFrame,
+ Index,
+ array,
+)
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+_msg_validate_usecols_arg = (
+ "'usecols' must either be list-like "
+ "of all strings, all unicode, all "
+ "integers or a callable."
+)
+_msg_validate_usecols_names = (
+ "Usecols do not match columns, columns expected but not found: {0}"
+)
+_msg_pyarrow_requires_names = (
+ "The pyarrow engine does not allow 'usecols' to be integer column "
+ "positions. Pass a list of string column names instead."
+)
+
+xfail_pyarrow = pytest.mark.usefixtures("pyarrow_xfail")
+skip_pyarrow = pytest.mark.usefixtures("pyarrow_skip")
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame is deprecated:DeprecationWarning"
+)
+
+
+def test_raise_on_mixed_dtype_usecols(all_parsers):
+ # See gh-12678
+ data = """a,b,c
+ 1000,2000,3000
+ 4000,5000,6000
+ """
+ usecols = [0, "b", 2]
+ parser = all_parsers
+
+ with pytest.raises(ValueError, match=_msg_validate_usecols_arg):
+ parser.read_csv(StringIO(data), usecols=usecols)
+
+
+@pytest.mark.parametrize("usecols", [(1, 2), ("b", "c")])
+def test_usecols(all_parsers, usecols, request):
+ data = """\
+a,b,c
+1,2,3
+4,5,6
+7,8,9
+10,11,12"""
+ parser = all_parsers
+ if parser.engine == "pyarrow" and isinstance(usecols[0], int):
+ with pytest.raises(ValueError, match=_msg_pyarrow_requires_names):
+ parser.read_csv(StringIO(data), usecols=usecols)
+ return
+
+ result = parser.read_csv(StringIO(data), usecols=usecols)
+
+ expected = DataFrame([[2, 3], [5, 6], [8, 9], [11, 12]], columns=["b", "c"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_with_names(all_parsers):
+ data = """\
+a,b,c
+1,2,3
+4,5,6
+7,8,9
+10,11,12"""
+ parser = all_parsers
+ names = ["foo", "bar"]
+
+ if parser.engine == "pyarrow":
+ with pytest.raises(ValueError, match=_msg_pyarrow_requires_names):
+ parser.read_csv(StringIO(data), names=names, usecols=[1, 2], header=0)
+ return
+
+ result = parser.read_csv(StringIO(data), names=names, usecols=[1, 2], header=0)
+
+ expected = DataFrame([[2, 3], [5, 6], [8, 9], [11, 12]], columns=names)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "names,usecols", [(["b", "c"], [1, 2]), (["a", "b", "c"], ["b", "c"])]
+)
+def test_usecols_relative_to_names(all_parsers, names, usecols):
+ data = """\
+1,2,3
+4,5,6
+7,8,9
+10,11,12"""
+ parser = all_parsers
+ if parser.engine == "pyarrow" and not isinstance(usecols[0], int):
+ # ArrowKeyError: Column 'fb' in include_columns does not exist
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+
+ result = parser.read_csv(StringIO(data), names=names, header=None, usecols=usecols)
+
+ expected = DataFrame([[2, 3], [5, 6], [8, 9], [11, 12]], columns=["b", "c"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_relative_to_names2(all_parsers):
+ # see gh-5766
+ data = """\
+1,2,3
+4,5,6
+7,8,9
+10,11,12"""
+ parser = all_parsers
+
+ result = parser.read_csv(
+ StringIO(data), names=["a", "b"], header=None, usecols=[0, 1]
+ )
+
+ expected = DataFrame([[1, 2], [4, 5], [7, 8], [10, 11]], columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+
+# regex mismatch: "Length mismatch: Expected axis has 1 elements"
+@xfail_pyarrow
+def test_usecols_name_length_conflict(all_parsers):
+ data = """\
+1,2,3
+4,5,6
+7,8,9
+10,11,12"""
+ parser = all_parsers
+ msg = "Number of passed names did not match number of header fields in the file"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), names=["a", "b"], header=None, usecols=[1])
+
+
+def test_usecols_single_string(all_parsers):
+ # see gh-20558
+ parser = all_parsers
+ data = """foo, bar, baz
+1000, 2000, 3000
+4000, 5000, 6000"""
+
+ with pytest.raises(ValueError, match=_msg_validate_usecols_arg):
+ parser.read_csv(StringIO(data), usecols="foo")
+
+
+# ArrowKeyError: Column 'a' in include_columns does not exist in CSV file
+@skip_pyarrow
+@pytest.mark.parametrize(
+ "data", ["a,b,c,d\n1,2,3,4\n5,6,7,8", "a,b,c,d\n1,2,3,4,\n5,6,7,8,"]
+)
+def test_usecols_index_col_false(all_parsers, data):
+ # see gh-9082
+ parser = all_parsers
+ usecols = ["a", "c", "d"]
+ expected = DataFrame({"a": [1, 5], "c": [3, 7], "d": [4, 8]})
+
+ result = parser.read_csv(StringIO(data), usecols=usecols, index_col=False)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("index_col", ["b", 0])
+@pytest.mark.parametrize("usecols", [["b", "c"], [1, 2]])
+def test_usecols_index_col_conflict(all_parsers, usecols, index_col, request):
+ # see gh-4201: test that index_col as integer reflects usecols
+ parser = all_parsers
+ data = "a,b,c,d\nA,a,1,one\nB,b,2,two"
+
+ if parser.engine == "pyarrow" and isinstance(usecols[0], int):
+ with pytest.raises(ValueError, match=_msg_pyarrow_requires_names):
+ parser.read_csv(StringIO(data), usecols=usecols, index_col=index_col)
+ return
+
+ expected = DataFrame({"c": [1, 2]}, index=Index(["a", "b"], name="b"))
+
+ result = parser.read_csv(StringIO(data), usecols=usecols, index_col=index_col)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_index_col_conflict2(all_parsers):
+ # see gh-4201: test that index_col as integer reflects usecols
+ parser = all_parsers
+ data = "a,b,c,d\nA,a,1,one\nB,b,2,two"
+
+ expected = DataFrame({"b": ["a", "b"], "c": [1, 2], "d": ("one", "two")})
+ expected = expected.set_index(["b", "c"])
+
+ result = parser.read_csv(
+ StringIO(data), usecols=["b", "c", "d"], index_col=["b", "c"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Expected 3 columns, got 4
+def test_usecols_implicit_index_col(all_parsers):
+ # see gh-2654
+ parser = all_parsers
+ data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"
+
+ result = parser.read_csv(StringIO(data), usecols=["a", "b"])
+ expected = DataFrame({"a": ["apple", "orange"], "b": ["bat", "cow"]}, index=[4, 8])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_index_col_middle(all_parsers):
+ # GH#9098
+ parser = all_parsers
+ data = """a,b,c,d
+1,2,3,4
+"""
+ result = parser.read_csv(StringIO(data), usecols=["b", "c", "d"], index_col="c")
+ expected = DataFrame({"b": [2], "d": [4]}, index=Index([3], name="c"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_index_col_end(all_parsers):
+ # GH#9098
+ parser = all_parsers
+ data = """a,b,c,d
+1,2,3,4
+"""
+ result = parser.read_csv(StringIO(data), usecols=["b", "c", "d"], index_col="d")
+ expected = DataFrame({"b": [2], "c": [3]}, index=Index([4], name="d"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_regex_sep(all_parsers):
+ # see gh-2733
+ parser = all_parsers
+ data = "a b c\n4 apple bat 5.7\n8 orange cow 10"
+
+ if parser.engine == "pyarrow":
+ msg = "the 'pyarrow' engine does not support regex separators"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), sep=r"\s+", usecols=("a", "b"))
+ return
+
+ result = parser.read_csv(StringIO(data), sep=r"\s+", usecols=("a", "b"))
+
+ expected = DataFrame({"a": ["apple", "orange"], "b": ["bat", "cow"]}, index=[4, 8])
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # Column 'a' in include_columns does not exist in CSV file
+def test_usecols_with_whitespace(all_parsers):
+ parser = all_parsers
+ data = "a b c\n4 apple bat 5.7\n8 orange cow 10"
+
+ result = parser.read_csv(StringIO(data), sep=r"\s+", usecols=("a", "b"))
+ expected = DataFrame({"a": ["apple", "orange"], "b": ["bat", "cow"]}, index=[4, 8])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "usecols,expected",
+ [
+ # Column selection by index.
+ ([0, 1], DataFrame(data=[[1000, 2000], [4000, 5000]], columns=["2", "0"])),
+ # Column selection by name.
+ (
+ ["0", "1"],
+ DataFrame(data=[[2000, 3000], [5000, 6000]], columns=["0", "1"]),
+ ),
+ ],
+)
+def test_usecols_with_integer_like_header(all_parsers, usecols, expected, request):
+ parser = all_parsers
+ data = """2,0,1
+1000,2000,3000
+4000,5000,6000"""
+
+ if parser.engine == "pyarrow" and isinstance(usecols[0], int):
+ with pytest.raises(ValueError, match=_msg_pyarrow_requires_names):
+ parser.read_csv(StringIO(data), usecols=usecols)
+ return
+
+ result = parser.read_csv(StringIO(data), usecols=usecols)
+ tm.assert_frame_equal(result, expected)
+
+
+@xfail_pyarrow # mismatched shape
+def test_empty_usecols(all_parsers):
+ data = "a,b,c\n1,2,3\n4,5,6"
+ expected = DataFrame(columns=Index([]))
+ parser = all_parsers
+
+ result = parser.read_csv(StringIO(data), usecols=set())
+ tm.assert_frame_equal(result, expected)
+
+
+def test_np_array_usecols(all_parsers):
+ # see gh-12546
+ parser = all_parsers
+ data = "a,b,c\n1,2,3"
+ usecols = np.array(["a", "b"])
+
+ expected = DataFrame([[1, 2]], columns=usecols)
+ result = parser.read_csv(StringIO(data), usecols=usecols)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "usecols,expected",
+ [
+ (
+ lambda x: x.upper() in ["AAA", "BBB", "DDD"],
+ DataFrame(
+ {
+ "AaA": {
+ 0: 0.056674972999999997,
+ 1: 2.6132309819999997,
+ 2: 3.5689350380000002,
+ },
+ "bBb": {0: 8, 1: 2, 2: 7},
+ "ddd": {0: "a", 1: "b", 2: "a"},
+ }
+ ),
+ ),
+ (lambda x: False, DataFrame(columns=Index([]))),
+ ],
+)
+def test_callable_usecols(all_parsers, usecols, expected):
+ # see gh-14154
+ data = """AaA,bBb,CCC,ddd
+0.056674973,8,True,a
+2.613230982,2,False,b
+3.568935038,7,False,a"""
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine does not allow 'usecols' to be a callable"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), usecols=usecols)
+ return
+
+ result = parser.read_csv(StringIO(data), usecols=usecols)
+ tm.assert_frame_equal(result, expected)
+
+
+# ArrowKeyError: Column 'fa' in include_columns does not exist in CSV file
+@skip_pyarrow
+@pytest.mark.parametrize("usecols", [["a", "c"], lambda x: x in ["a", "c"]])
+def test_incomplete_first_row(all_parsers, usecols):
+ # see gh-6710
+ data = "1,2\n1,2,3"
+ parser = all_parsers
+ names = ["a", "b", "c"]
+ expected = DataFrame({"a": [1, 1], "c": [np.nan, 3]})
+
+ result = parser.read_csv(StringIO(data), names=names, usecols=usecols)
+ tm.assert_frame_equal(result, expected)
+
+
+@skip_pyarrow # CSV parse error: Expected 3 columns, got 4
+@pytest.mark.parametrize(
+ "data,usecols,kwargs,expected",
+ [
+ # see gh-8985
+ (
+ "19,29,39\n" * 2 + "10,20,30,40",
+ [0, 1, 2],
+ {"header": None},
+ [[19, 29, 39], [19, 29, 39], [10, 20, 30]],
+ ),
+ # see gh-9549
+ (
+ ("A,B,C\n1,2,3\n3,4,5\n1,2,4,5,1,6\n1,2,3,,,1,\n1,2,3\n5,6,7"),
+ ["A", "B", "C"],
+ {},
+ {
+ "A": [1, 3, 1, 1, 1, 5],
+ "B": [2, 4, 2, 2, 2, 6],
+ "C": [3, 5, 4, 3, 3, 7],
+ },
+ ),
+ ],
+)
+def test_uneven_length_cols(all_parsers, data, usecols, kwargs, expected):
+ # see gh-8985
+ parser = all_parsers
+ result = parser.read_csv(StringIO(data), usecols=usecols, **kwargs)
+ expected = DataFrame(expected)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "usecols,kwargs,expected,msg",
+ [
+ (
+ ["a", "b", "c", "d"],
+ {},
+ DataFrame({"a": [1, 5], "b": [2, 6], "c": [3, 7], "d": [4, 8]}),
+ None,
+ ),
+ (
+ ["a", "b", "c", "f"],
+ {},
+ None,
+ _msg_validate_usecols_names.format(r"\['f'\]"),
+ ),
+ (["a", "b", "f"], {}, None, _msg_validate_usecols_names.format(r"\['f'\]")),
+ (
+ ["a", "b", "f", "g"],
+ {},
+ None,
+ _msg_validate_usecols_names.format(r"\[('f', 'g'|'g', 'f')\]"),
+ ),
+ # see gh-14671
+ (
+ None,
+ {"header": 0, "names": ["A", "B", "C", "D"]},
+ DataFrame({"A": [1, 5], "B": [2, 6], "C": [3, 7], "D": [4, 8]}),
+ None,
+ ),
+ (
+ ["A", "B", "C", "f"],
+ {"header": 0, "names": ["A", "B", "C", "D"]},
+ None,
+ _msg_validate_usecols_names.format(r"\['f'\]"),
+ ),
+ (
+ ["A", "B", "f"],
+ {"names": ["A", "B", "C", "D"]},
+ None,
+ _msg_validate_usecols_names.format(r"\['f'\]"),
+ ),
+ ],
+)
+def test_raises_on_usecols_names_mismatch(
+ all_parsers, usecols, kwargs, expected, msg, request
+):
+ data = "a,b,c,d\n1,2,3,4\n5,6,7,8"
+ kwargs.update(usecols=usecols)
+ parser = all_parsers
+
+ if parser.engine == "pyarrow" and not (
+ usecols is not None and expected is not None
+ ):
+ # everything but the first case
+ # ArrowKeyError: Column 'f' in include_columns does not exist in CSV file
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+
+ if expected is None:
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO(data), **kwargs)
+ else:
+ result = parser.read_csv(StringIO(data), **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("usecols", [["A", "C"], [0, 2]])
+def test_usecols_subset_names_mismatch_orig_columns(all_parsers, usecols, request):
+ data = "a,b,c,d\n1,2,3,4\n5,6,7,8"
+ names = ["A", "B", "C", "D"]
+ parser = all_parsers
+
+ if parser.engine == "pyarrow":
+ if isinstance(usecols[0], int):
+ with pytest.raises(ValueError, match=_msg_pyarrow_requires_names):
+ parser.read_csv(StringIO(data), header=0, names=names, usecols=usecols)
+ return
+ # "pyarrow.lib.ArrowKeyError: Column 'A' in include_columns does not exist"
+ pytest.skip(reason="https://github.com/apache/arrow/issues/38676")
+
+ result = parser.read_csv(StringIO(data), header=0, names=names, usecols=usecols)
+ expected = DataFrame({"A": [1, 5], "C": [3, 7]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("names", [None, ["a", "b"]])
+def test_usecols_indices_out_of_bounds(all_parsers, names):
+ # GH#25623 & GH 41130; enforced in 2.0
+ parser = all_parsers
+ data = """
+a,b
+1,2
+ """
+
+ err = ParserError
+ msg = "Defining usecols with out-of-bounds"
+ if parser.engine == "pyarrow":
+ err = ValueError
+ msg = _msg_pyarrow_requires_names
+
+ with pytest.raises(err, match=msg):
+ parser.read_csv(StringIO(data), usecols=[0, 2], names=names, header=0)
+
+
+def test_usecols_additional_columns(all_parsers):
+ # GH#46997
+ parser = all_parsers
+ usecols = lambda header: header.strip() in ["a", "b", "c"]
+
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine does not allow 'usecols' to be a callable"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO("a,b\nx,y,z"), index_col=False, usecols=usecols)
+ return
+ result = parser.read_csv(StringIO("a,b\nx,y,z"), index_col=False, usecols=usecols)
+ expected = DataFrame({"a": ["x"], "b": "y"})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_additional_columns_integer_columns(all_parsers):
+ # GH#46997
+ parser = all_parsers
+ usecols = lambda header: header.strip() in ["0", "1"]
+ if parser.engine == "pyarrow":
+ msg = "The pyarrow engine does not allow 'usecols' to be a callable"
+ with pytest.raises(ValueError, match=msg):
+ parser.read_csv(StringIO("0,1\nx,y,z"), index_col=False, usecols=usecols)
+ return
+ result = parser.read_csv(StringIO("0,1\nx,y,z"), index_col=False, usecols=usecols)
+ expected = DataFrame({"0": ["x"], "1": "y"})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_usecols_dtype(all_parsers):
+ parser = all_parsers
+ data = """
+col1,col2,col3
+a,1,x
+b,2,y
+"""
+ result = parser.read_csv(
+ StringIO(data),
+ usecols=["col1", "col2"],
+ dtype={"col1": "string", "col2": "uint8", "col3": "string"},
+ )
+ expected = DataFrame(
+ {"col1": array(["a", "b"]), "col2": np.array([1, 2], dtype="uint8")}
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d0a7951ce82222094fbdab78a788d845dada1be
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/conftest.py
@@ -0,0 +1,25 @@
+import uuid
+
+import pytest
+
+from pandas.io.pytables import HDFStore
+
+tables = pytest.importorskip("tables")
+# set these parameters so we don't have file sharing
+tables.parameters.MAX_NUMEXPR_THREADS = 1
+tables.parameters.MAX_BLOSC_THREADS = 1
+tables.parameters.MAX_THREADS = 1
+
+
+@pytest.fixture
+def temp_h5_path(tmp_path):
+ """Fixture for HDF5 path"""
+ file_path = tmp_path / f"{uuid.uuid4()}.h5"
+ file_path.touch()
+ return file_path
+
+
+@pytest.fixture
+def temp_hdfstore(temp_h5_path):
+ with HDFStore(temp_h5_path, mode="a") as store:
+ yield store
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_append.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_append.py
new file mode 100644
index 0000000000000000000000000000000000000000..d19f6dda5ffc51c7a5309a7db4f828afcc693a5d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_append.py
@@ -0,0 +1,989 @@
+import datetime
+from datetime import timedelta
+import re
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import Timestamp
+from pandas.compat import PY312
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ _testing as tm,
+ concat,
+ date_range,
+ read_hdf,
+)
+
+pytestmark = [pytest.mark.single_cpu]
+
+tables = pytest.importorskip("tables")
+
+
+@pytest.mark.filterwarnings("ignore::tables.NaturalNameWarning")
+def test_append(temp_hdfstore):
+ # this is allowed by almost always don't want to do it
+ # tables.NaturalNameWarning):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((20, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=20, freq="B"),
+ )
+ temp_hdfstore.append("df1", df[:10])
+ temp_hdfstore.append("df1", df[10:])
+ tm.assert_frame_equal(temp_hdfstore["df1"], df)
+
+ temp_hdfstore.put("df2", df[:10], format="table")
+ temp_hdfstore.append("df2", df[10:])
+ tm.assert_frame_equal(temp_hdfstore["df2"], df)
+
+ temp_hdfstore.append("/df3", df[:10])
+ temp_hdfstore.append("/df3", df[10:])
+ tm.assert_frame_equal(temp_hdfstore["df3"], df)
+
+ # this is allowed by almost always don't want to do it
+ # tables.NaturalNameWarning
+ temp_hdfstore.append("/df3 foo", df[:10])
+ temp_hdfstore.append("/df3 foo", df[10:])
+ tm.assert_frame_equal(temp_hdfstore["df3 foo"], df)
+
+ # dtype issues - mizxed type in a single object column
+ df = DataFrame(data=[[1, 2], [0, 1], [1, 2], [0, 0]])
+ df["mixed_column"] = "testing"
+ df.loc[2, "mixed_column"] = np.nan
+ temp_hdfstore.append("df", df)
+ tm.assert_frame_equal(temp_hdfstore["df"], df)
+
+ # uints - test storage of uints
+ uint_data = DataFrame(
+ {
+ "u08": Series(
+ np.random.default_rng(2).integers(0, high=255, size=5),
+ dtype=np.uint8,
+ ),
+ "u16": Series(
+ np.random.default_rng(2).integers(0, high=65535, size=5),
+ dtype=np.uint16,
+ ),
+ "u32": Series(
+ np.random.default_rng(2).integers(0, high=2**30, size=5),
+ dtype=np.uint32,
+ ),
+ "u64": Series(
+ [2**58, 2**59, 2**60, 2**61, 2**62],
+ dtype=np.uint64,
+ ),
+ },
+ index=np.arange(5),
+ )
+ temp_hdfstore.append("uints", uint_data)
+ tm.assert_frame_equal(temp_hdfstore["uints"], uint_data, check_index_type=True)
+
+ # uints - test storage of uints in indexable columns
+ temp_hdfstore.remove("uints")
+ # 64-bit indices not yet supported
+ temp_hdfstore.append("uints", uint_data, data_columns=["u08", "u16", "u32"])
+ tm.assert_frame_equal(temp_hdfstore["uints"], uint_data, check_index_type=True)
+
+
+def test_append_series(temp_hdfstore):
+ # basic
+ ss = Series(range(20), dtype=np.float64, index=[f"i_{i}" for i in range(20)])
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ ns = Series(np.arange(100))
+
+ temp_hdfstore.append("ss", ss)
+ result = temp_hdfstore["ss"]
+ tm.assert_series_equal(result, ss)
+ assert result.name is None
+
+ temp_hdfstore.append("ts", ts)
+ result = temp_hdfstore["ts"]
+ tm.assert_series_equal(result, ts)
+ assert result.name is None
+
+ ns.name = "foo"
+ temp_hdfstore.append("ns", ns)
+ result = temp_hdfstore["ns"]
+ tm.assert_series_equal(result, ns)
+ assert result.name == ns.name
+
+ # select on the values
+ expected = ns[ns > 60]
+ result = temp_hdfstore.select("ns", "foo>60")
+ tm.assert_series_equal(result, expected)
+
+ # select on the index and values
+ expected = ns[(ns > 70) & (ns.index < 90)]
+ # Reading/writing RangeIndex info is not supported yet
+ expected.index = Index(expected.index._data)
+ result = temp_hdfstore.select("ns", "foo>70 and index<90")
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ # multi-index
+ mi = DataFrame(np.random.default_rng(2).standard_normal((5, 1)), columns=["A"])
+ mi["B"] = np.arange(len(mi))
+ mi["C"] = "foo"
+ mi.loc[3:5, "C"] = "bar"
+ mi.set_index(["C", "B"], inplace=True)
+ s = mi.stack()
+ s.index = s.index.droplevel(2)
+ temp_hdfstore.append("mi", s)
+ tm.assert_series_equal(temp_hdfstore["mi"], s, check_index_type=True)
+
+
+def test_append_some_nans(temp_hdfstore):
+ df = DataFrame(
+ {
+ "A": Series(np.random.default_rng(2).standard_normal(20)).astype("int32"),
+ "A1": np.random.default_rng(2).standard_normal(20),
+ "A2": np.random.default_rng(2).standard_normal(20),
+ "B": "foo",
+ "C": "bar",
+ "D": Timestamp("2001-01-01").as_unit("ns"),
+ "E": Timestamp("2001-01-02").as_unit("ns"),
+ },
+ index=np.arange(20),
+ )
+ # some nans
+ df.loc[0:15, ["A1", "B", "D", "E"]] = np.nan
+ temp_hdfstore.append("df1", df[:10])
+ temp_hdfstore.append("df1", df[10:])
+ tm.assert_frame_equal(temp_hdfstore["df1"], df, check_index_type=True)
+
+ # first column
+ df1 = df.copy()
+ df1["A1"] = np.nan
+ temp_hdfstore.remove("df1")
+ temp_hdfstore.append("df1", df1[:10])
+ temp_hdfstore.append("df1", df1[10:])
+ tm.assert_frame_equal(temp_hdfstore["df1"], df1, check_index_type=True)
+
+ # 2nd column
+ df2 = df.copy()
+ df2["A2"] = np.nan
+ temp_hdfstore.append("df2", df2[:10])
+ temp_hdfstore.append("df2", df2[10:])
+ tm.assert_frame_equal(temp_hdfstore["df2"], df2, check_index_type=True)
+
+ # datetimes
+ df3 = df.copy()
+ df3["E"] = np.nan
+ temp_hdfstore.append("df3", df3[:10])
+ temp_hdfstore.append("df3", df3[10:])
+ tm.assert_frame_equal(temp_hdfstore["df3"], df3, check_index_type=True)
+
+
+def test_append_all_nans(temp_hdfstore, using_infer_string):
+ df = DataFrame(
+ {
+ "A1": np.random.default_rng(2).standard_normal(20),
+ "A2": np.random.default_rng(2).standard_normal(20),
+ },
+ index=np.arange(20),
+ )
+ df.loc[0:15, :] = np.nan
+
+ # nan some entire rows (dropna=True)
+ temp_hdfstore.append("df", df[:10], dropna=True)
+ temp_hdfstore.append("df", df[10:], dropna=True)
+ tm.assert_frame_equal(temp_hdfstore["df"], df[-4:], check_index_type=True)
+
+ # nan some entire rows (dropna=False)
+ temp_hdfstore.append("df2", df[:10], dropna=False)
+ temp_hdfstore.append("df2", df[10:], dropna=False)
+ tm.assert_frame_equal(temp_hdfstore["df2"], df, check_index_type=True)
+
+ # tests the option io.hdf.dropna_table
+ with pd.option_context("io.hdf.dropna_table", False):
+ temp_hdfstore.append("df3", df[:10])
+ temp_hdfstore.append("df3", df[10:])
+ tm.assert_frame_equal(temp_hdfstore["df3"], df)
+
+ with pd.option_context("io.hdf.dropna_table", True):
+ temp_hdfstore.append("df4", df[:10])
+ temp_hdfstore.append("df4", df[10:])
+ tm.assert_frame_equal(temp_hdfstore["df4"], df[-4:])
+
+ # nan some entire rows (string are still written!)
+ df = DataFrame(
+ {
+ "A1": np.random.default_rng(2).standard_normal(20),
+ "A2": np.random.default_rng(2).standard_normal(20),
+ "B": "foo",
+ "C": "bar",
+ },
+ index=np.arange(20),
+ )
+
+ df.loc[0:15, :] = np.nan
+
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df[:10], dropna=True)
+ temp_hdfstore.append("df", df[10:], dropna=True)
+ result = temp_hdfstore["df"]
+ expected = df
+ if using_infer_string:
+ # TODO: Test is incorrect when not using_infer_string.
+ # Should take the last 4 rows uncondiationally.
+ expected = expected[-4:]
+ tm.assert_frame_equal(result, expected, check_index_type=True)
+
+ temp_hdfstore.remove("df2")
+ temp_hdfstore.append("df2", df[:10], dropna=False)
+ temp_hdfstore.append("df2", df[10:], dropna=False)
+ tm.assert_frame_equal(temp_hdfstore["df2"], df, check_index_type=True)
+
+ # nan some entire rows (but since we have dates they are still
+ # written!)
+ df = DataFrame(
+ {
+ "A1": np.random.default_rng(2).standard_normal(20),
+ "A2": np.random.default_rng(2).standard_normal(20),
+ "B": "foo",
+ "C": "bar",
+ "D": Timestamp("2001-01-01").as_unit("ns"),
+ "E": Timestamp("2001-01-02").as_unit("ns"),
+ },
+ index=np.arange(20),
+ )
+
+ df.loc[0:15, :] = np.nan
+
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df[:10], dropna=True)
+ temp_hdfstore.append("df", df[10:], dropna=True)
+ tm.assert_frame_equal(temp_hdfstore["df"], df, check_index_type=True)
+
+ temp_hdfstore.remove("df2")
+ temp_hdfstore.append("df2", df[:10], dropna=False)
+ temp_hdfstore.append("df2", df[10:], dropna=False)
+ tm.assert_frame_equal(temp_hdfstore["df2"], df, check_index_type=True)
+
+
+def test_append_frame_column_oriented(temp_hdfstore, request):
+ # column oriented
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df.index = df.index._with_freq(None) # freq doesn't round-trip
+
+ temp_hdfstore.append("df1", df.iloc[:, :2], axes=["columns"])
+ temp_hdfstore.append("df1", df.iloc[:, 2:])
+ tm.assert_frame_equal(temp_hdfstore["df1"], df)
+
+ result = temp_hdfstore.select("df1", "columns=A")
+ expected = df.reindex(columns=["A"])
+ tm.assert_frame_equal(expected, result)
+
+ # selection on the non-indexable
+ request.applymarker(
+ pytest.mark.xfail(
+ PY312,
+ reason="AST change in PY312",
+ raises=ValueError,
+ )
+ )
+ result = temp_hdfstore.select("df1", ("columns=A", "index=df.index[0:4]"))
+ expected = df.reindex(columns=["A"], index=df.index[0:4])
+ tm.assert_frame_equal(expected, result)
+
+ # this isn't supported
+ msg = re.escape(
+ "passing a filterable condition to a non-table indexer "
+ "[Filter: Not Initialized]"
+ )
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.select("df1", "columns=A and index>df.index[4]")
+
+
+def test_append_with_different_block_ordering(temp_hdfstore):
+ # GH 4096; using same frames, but different block orderings
+ for i in range(10):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)), columns=list("AB")
+ )
+ df["index"] = range(10)
+ df["index"] += i * 10
+ df["int64"] = Series([1] * len(df), dtype="int64")
+ df["int16"] = Series([1] * len(df), dtype="int16")
+
+ if i % 2 == 0:
+ del df["int64"]
+ df["int64"] = Series([1] * len(df), dtype="int64")
+ if i % 3 == 0:
+ a = df.pop("A")
+ df["A"] = a
+
+ df.set_index("index", inplace=True)
+
+ temp_hdfstore.append("df", df)
+
+ # test a different ordering but with more fields (like invalid
+ # combinations)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ columns=list("AB"),
+ dtype="float64",
+ )
+ df["int64"] = Series([1] * len(df), dtype="int64")
+ df["int16"] = Series([1] * len(df), dtype="int16")
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df)
+
+ # store additional fields in different blocks
+ df["int16_2"] = Series([1] * len(df), dtype="int16")
+ msg = re.escape(
+ "cannot match existing table structure for [int16] on appending data"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df", df)
+
+ # store multiple additional fields in different blocks
+ df["float_3"] = Series([1.0] * len(df), dtype="float64")
+ msg = re.escape("cannot match existing table structure for [A,B] on appending data")
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df", df)
+
+
+def test_append_with_strings(temp_hdfstore):
+ def check_col(key, name, size):
+ assert (
+ getattr(temp_hdfstore.get_storer(key).table.description, name).itemsize
+ == size
+ )
+
+ # avoid truncation on elements
+ df = DataFrame([[123, "asdqwerty"], [345, "dggnhebbsdfbdfb"]])
+ temp_hdfstore.append("df_big", df)
+ tm.assert_frame_equal(temp_hdfstore.select("df_big"), df)
+ check_col("df_big", "values_block_1", 15)
+
+ # appending smaller string ok
+ df2 = DataFrame([[124, "asdqy"], [346, "dggnhefbdfb"]])
+ temp_hdfstore.append("df_big", df2)
+ expected = concat([df, df2])
+ tm.assert_frame_equal(temp_hdfstore.select("df_big"), expected)
+ check_col("df_big", "values_block_1", 15)
+
+ # avoid truncation on elements
+ df = DataFrame([[123, "asdqwerty"], [345, "dggnhebbsdfbdfb"]])
+ temp_hdfstore.append("df_big2", df, min_itemsize={"values": 50})
+ tm.assert_frame_equal(temp_hdfstore.select("df_big2"), df)
+ check_col("df_big2", "values_block_1", 50)
+
+ # bigger string on next append
+ temp_hdfstore.append("df_new", df)
+ df_new = DataFrame([[124, "abcdefqhij"], [346, "abcdefghijklmnopqrtsuvwxyz"]])
+ msg = (
+ r"Trying to store a string with len \[26\] in "
+ r"\[values_block_1\] column but\n"
+ r"this column has a limit of \[15\]!\n"
+ "Consider using min_itemsize to preset the sizes on these "
+ "columns"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df_new", df_new)
+
+ # min_itemsize on Series index (GH 11412)
+ df = DataFrame(
+ {
+ "A": [0.0, 1.0, 2.0, 3.0, 4.0],
+ "B": [0.0, 1.0, 0.0, 1.0, 0.0],
+ "C": Index(["foo1", "foo2", "foo3", "foo4", "foo5"]),
+ "D": date_range("20130101", periods=5),
+ }
+ ).set_index("C")
+ temp_hdfstore.append("ss", df["B"], min_itemsize={"index": 4})
+ tm.assert_series_equal(temp_hdfstore.select("ss"), df["B"])
+
+ # same as above, with data_columns=True
+ temp_hdfstore.append("ss2", df["B"], data_columns=True, min_itemsize={"index": 4})
+ tm.assert_series_equal(temp_hdfstore.select("ss2"), df["B"])
+
+ # min_itemsize in index without appending (GH 10381)
+ temp_hdfstore.put("ss3", df, format="table", min_itemsize={"index": 6})
+ # just make sure there is a longer string:
+ df2 = df.copy().reset_index().assign(C="longer").set_index("C")
+ temp_hdfstore.append("ss3", df2)
+ tm.assert_frame_equal(temp_hdfstore.select("ss3"), concat([df, df2]))
+
+ # same as above, with a Series
+ temp_hdfstore.put("ss4", df["B"], format="table", min_itemsize={"index": 6})
+ temp_hdfstore.append("ss4", df2["B"])
+ tm.assert_series_equal(temp_hdfstore.select("ss4"), concat([df["B"], df2["B"]]))
+
+ # with nans
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df["string"] = "foo"
+ df.loc[df.index[1:4], "string"] = np.nan
+ df["string2"] = "bar"
+ df.loc[df.index[4:8], "string2"] = np.nan
+ df["string3"] = "bah"
+ df.loc[df.index[1:], "string3"] = np.nan
+ temp_hdfstore.append("df", df)
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(result, df)
+
+
+def test_append_with_strings2(temp_hdfstore):
+ def check_col(key, name, size):
+ assert (
+ getattr(temp_hdfstore.get_storer(key).table.description, name).itemsize
+ == size
+ )
+
+ df = DataFrame({"A": "foo", "B": "bar"}, index=range(10))
+
+ # a min_itemsize that creates a data_column
+ temp_hdfstore.append("df", df, min_itemsize={"A": 200})
+ check_col("df", "A", 200)
+ assert temp_hdfstore.get_storer("df").data_columns == ["A"]
+
+ # a min_itemsize that creates a data_column2
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df, data_columns=["B"], min_itemsize={"A": 200})
+ check_col("df", "A", 200)
+ assert temp_hdfstore.get_storer("df").data_columns == ["B", "A"]
+
+ # a min_itemsize that creates a data_column2
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df, data_columns=["B"], min_itemsize={"values": 200})
+ check_col("df", "B", 200)
+ check_col("df", "values_block_0", 200)
+ assert temp_hdfstore.get_storer("df").data_columns == ["B"]
+
+ # infer the .typ on subsequent appends
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df[:5], min_itemsize=200)
+ temp_hdfstore.append("df", df[5:], min_itemsize=200)
+ tm.assert_frame_equal(temp_hdfstore["df"], df)
+
+ # invalid min_itemsize keys
+ df = DataFrame(["foo", "foo", "foo", "barh", "barh", "barh"], columns=["A"])
+ temp_hdfstore.remove("df")
+ msg = re.escape(
+ "min_itemsize has the key [foo] which is not an axis or data_column"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df", df, min_itemsize={"foo": 20, "foobar": 20})
+
+
+def test_append_with_empty_string(temp_hdfstore):
+ # with all empty strings (GH 12242)
+ df = DataFrame({"x": ["a", "b", "c", "d", "e", "f", ""]})
+ temp_hdfstore.append("df", df[:-1], min_itemsize={"x": 1})
+ temp_hdfstore.append("df", df[-1:], min_itemsize={"x": 1})
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+
+
+def test_append_with_data_columns(temp_hdfstore):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ df.iloc[0, df.columns.get_loc("B")] = 1.0
+ temp_hdfstore.append("df", df[:2], data_columns=["B"])
+ temp_hdfstore.append("df", df[2:])
+ tm.assert_frame_equal(temp_hdfstore["df"], df)
+
+ # check that we have indices created
+ assert temp_hdfstore._handle.root.df.table.cols.index.is_indexed is True
+ assert temp_hdfstore._handle.root.df.table.cols.B.is_indexed is True
+
+ # data column searching
+ result = temp_hdfstore.select("df", "B>0")
+ expected = df[df.B > 0]
+ tm.assert_frame_equal(result, expected)
+
+ # data column searching (with an indexable and a data_columns)
+ result = temp_hdfstore.select("df", "B>0 and index>df.index[3]")
+ df_new = df.reindex(index=df.index[4:])
+ expected = df_new[df_new.B > 0]
+ tm.assert_frame_equal(result, expected)
+
+ # data column selection with a string data_column
+ df_new = df.copy()
+ df_new["string"] = "foo"
+ df_new.loc[df_new.index[1:4], "string"] = np.nan
+ df_new.loc[df_new.index[5:6], "string"] = "bar"
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df_new, data_columns=["string"])
+ result = temp_hdfstore.select("df", "string='foo'")
+ expected = df_new[df_new.string == "foo"]
+ tm.assert_frame_equal(result, expected)
+
+ # using min_itemsize and a data column
+ def check_col(key, name, size):
+ assert (
+ getattr(temp_hdfstore.get_storer(key).table.description, name).itemsize
+ == size
+ )
+
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append(
+ "df", df_new, data_columns=["string"], min_itemsize={"string": 30}
+ )
+ check_col("df", "string", 30)
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df_new, data_columns=["string"], min_itemsize=30)
+ check_col("df", "string", 30)
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append(
+ "df", df_new, data_columns=["string"], min_itemsize={"values": 30}
+ )
+ check_col("df", "string", 30)
+
+ df_new["string2"] = "foobarbah"
+ df_new["string_block1"] = "foobarbah1"
+ df_new["string_block2"] = "foobarbah2"
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append(
+ "df",
+ df_new,
+ data_columns=["string", "string2"],
+ min_itemsize={"string": 30, "string2": 40, "values": 50},
+ )
+ check_col("df", "string", 30)
+ check_col("df", "string2", 40)
+ check_col("df", "values_block_1", 50)
+
+ # multiple data columns
+ df_new = df.copy()
+ df_new.iloc[0, df_new.columns.get_loc("A")] = 1.0
+ df_new.iloc[0, df_new.columns.get_loc("B")] = -1.0
+ df_new["string"] = "foo"
+
+ sl = df_new.columns.get_loc("string")
+ df_new.iloc[1:4, sl] = np.nan
+ df_new.iloc[5:6, sl] = "bar"
+
+ df_new["string2"] = "foo"
+ sl = df_new.columns.get_loc("string2")
+ df_new.iloc[2:5, sl] = np.nan
+ df_new.iloc[7:8, sl] = "bar"
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df_new, data_columns=["A", "B", "string", "string2"])
+ result = temp_hdfstore.select(
+ "df", "string='foo' and string2='foo' and A>0 and B<0"
+ )
+ expected = df_new[
+ (df_new.string == "foo")
+ & (df_new.string2 == "foo")
+ & (df_new.A > 0)
+ & (df_new.B < 0)
+ ]
+ tm.assert_frame_equal(result, expected, check_freq=False)
+ # FIXME: 2020-05-07 freq check randomly fails in the CI
+
+ # yield an empty frame
+ result = temp_hdfstore.select("df", "string='foo' and string2='cool'")
+ expected = df_new[(df_new.string == "foo") & (df_new.string2 == "cool")]
+ tm.assert_frame_equal(result, expected)
+
+ # doc example
+ df_dc = df.copy()
+ df_dc["string"] = "foo"
+ df_dc.loc[df_dc.index[4:6], "string"] = np.nan
+ df_dc.loc[df_dc.index[7:9], "string"] = "bar"
+ df_dc["string2"] = "cool"
+ df_dc["datetime"] = Timestamp("20010102").as_unit("ns")
+ df_dc.loc[df_dc.index[3:5], ["A", "B", "datetime"]] = np.nan
+
+ temp_hdfstore.append(
+ "df_dc", df_dc, data_columns=["B", "C", "string", "string2", "datetime"]
+ )
+ result = temp_hdfstore.select("df_dc", "B>0")
+
+ expected = df_dc[df_dc.B > 0]
+ tm.assert_frame_equal(result, expected)
+
+ result = temp_hdfstore.select("df_dc", ["B > 0", "C > 0", "string == foo"])
+ expected = df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
+ tm.assert_frame_equal(result, expected, check_freq=False)
+ # FIXME: 2020-12-07 intermittent build failures here with freq of
+ # None instead of BDay(4)
+
+ # doc example part 2
+
+ index = date_range("1/1/2000", periods=8)
+ df_dc = DataFrame(
+ np.random.default_rng(2).standard_normal((8, 3)),
+ index=index,
+ columns=["A", "B", "C"],
+ )
+ df_dc["string"] = "foo"
+ df_dc.loc[df_dc.index[4:6], "string"] = np.nan
+ df_dc.loc[df_dc.index[7:9], "string"] = "bar"
+ df_dc[["B", "C"]] = df_dc[["B", "C"]].abs()
+ df_dc["string2"] = "cool"
+
+ # on-disk operations
+ temp_hdfstore.remove("df_dc")
+ temp_hdfstore.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])
+
+ result = temp_hdfstore.select("df_dc", "B>0")
+ expected = df_dc[df_dc.B > 0]
+ tm.assert_frame_equal(result, expected)
+
+ result = temp_hdfstore.select("df_dc", ["B > 0", "C > 0", 'string == "foo"'])
+ expected = df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_append_hierarchical(temp_hdfstore, multiindex_dataframe_random_data):
+ df = multiindex_dataframe_random_data
+ df.columns.name = None
+
+ temp_hdfstore.append("mi", df)
+ result = temp_hdfstore.select("mi")
+ tm.assert_frame_equal(result, df)
+
+ # GH 3748
+ result = temp_hdfstore.select("mi", columns=["A", "B"])
+ expected = df.reindex(columns=["A", "B"])
+ tm.assert_frame_equal(result, expected)
+
+ df.to_hdf(temp_hdfstore, key="df", format="table")
+ result = read_hdf(temp_hdfstore, "df", columns=["A", "B"])
+ expected = df.reindex(columns=["A", "B"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_append_misc(temp_hdfstore):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ temp_hdfstore.append("df", df, chunksize=1)
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(result, df)
+
+ temp_hdfstore.append("df1", df, expectedrows=10)
+ result = temp_hdfstore.select("df1")
+ tm.assert_frame_equal(result, df)
+
+
+@pytest.mark.parametrize("chunksize", [10, 200, 1000])
+def test_append_misc_chunksize(temp_hdfstore, chunksize):
+ # more chunksize in append tests
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ df["string"] = "foo"
+ df["float322"] = 1.0
+ df["float322"] = df["float322"].astype("float32")
+ df["bool"] = df["float322"] > 0
+ df["time1"] = Timestamp("20130101").as_unit("ns")
+ df["time2"] = Timestamp("20130102").as_unit("ns")
+ temp_hdfstore.append("obj", df, chunksize=chunksize)
+ result = temp_hdfstore.select("obj")
+ tm.assert_frame_equal(result, df)
+
+
+def test_append_misc_empty_frame(temp_hdfstore):
+ # empty frame, GH4273
+ # 0 len
+ df_empty = DataFrame(columns=list("ABC"))
+ temp_hdfstore.append("df", df_empty)
+ with pytest.raises(KeyError, match="'No object named df in the file'"):
+ temp_hdfstore.select("df")
+
+ # repeated append of 0/non-zero frames
+ df = DataFrame(np.random.default_rng(2).random((10, 3)), columns=list("ABC"))
+ temp_hdfstore.append("df", df)
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+ temp_hdfstore.append("df", df_empty)
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+
+ # store
+ df = DataFrame(columns=list("ABC"))
+ temp_hdfstore.put("df2", df)
+ tm.assert_frame_equal(temp_hdfstore.select("df2"), df)
+
+
+def test_append_raise(temp_hdfstore, using_infer_string):
+ # test append with invalid input to get good error messages
+
+ # list in column
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ df["invalid"] = [["a"]] * len(df)
+ assert df.dtypes["invalid"] == np.object_
+ msg = re.escape(
+ """Cannot serialize the column [invalid]
+because its data contents are not [string] but [mixed] object dtype"""
+ )
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.append("df", df)
+
+ # multiple invalid columns
+ df["invalid2"] = [["a"]] * len(df)
+ df["invalid3"] = [["a"]] * len(df)
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.append("df", df)
+
+ # datetime with embedded nans as object
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ s = Series(datetime.datetime(2001, 1, 2), index=df.index)
+ s = s.astype(object)
+ s[0:5] = np.nan
+ df["invalid"] = s
+ assert df.dtypes["invalid"] == np.object_
+ msg = "too many timezones in this block, create separate data columns"
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.append("df", df)
+
+ # directly ndarray
+ msg = "value must be None, Series, or DataFrame"
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.append("df", np.arange(10))
+
+ # series directly
+ msg = re.escape(
+ "cannot properly create the storer for: "
+ "[group->df,value->]"
+ )
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.append("df", Series(np.arange(10)))
+
+ # appending an incompatible table
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ temp_hdfstore.append("df", df)
+
+ df["foo"] = "foo"
+ msg = re.escape(
+ "invalid combination of [non_index_axes] on appending data "
+ "[(1, ['A', 'B', 'C', 'D', 'foo'])] vs current table "
+ "[(1, ['A', 'B', 'C', 'D'])]"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df", df)
+
+ # incompatible type (GH 41897)
+ df["foo"] = Timestamp("20130101")
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df)
+ df["foo"] = "bar"
+ msg = re.escape(
+ "Cannot serialize the column [foo] "
+ "because its data contents are not [string] "
+ "but [datetime64[us]] object dtype"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df", df)
+
+
+def test_append_with_timedelta(temp_hdfstore, unit):
+ # GH 3577
+ # append timedelta
+
+ ts = Timestamp("20130101").as_unit("ns")
+ df = DataFrame(
+ {
+ "A": ts,
+ "B": [ts + timedelta(days=i, seconds=10) for i in range(10)],
+ }
+ )
+ df["C"] = df["A"] - df["B"]
+ df["C"] = df["C"].astype(f"m8[{unit}]")
+ df.loc[3:5, "C"] = np.nan
+
+ # table
+ temp_hdfstore.append("df", df, data_columns=True)
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(result, df)
+
+ result = temp_hdfstore.select("df", where="C<100000")
+ tm.assert_frame_equal(result, df)
+
+ result = temp_hdfstore.select("df", where="C0", "B>0"], selector="df1"
+ )
+ expected = df[(df.A > 0) & (df.B > 0)]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_append_to_multiple_dropna(temp_hdfstore):
+ df1 = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ ).rename(columns="{}_2".format)
+ df1.iloc[1, df1.columns.get_indexer(["A", "B"])] = np.nan
+ df = concat([df1, df2], axis=1)
+
+ # dropna=True should guarantee rows are synchronized
+ temp_hdfstore.append_to_multiple(
+ {"df1": ["A", "B"], "df2": None}, df, selector="df1", dropna=True
+ )
+ result = temp_hdfstore.select_as_multiple(["df1", "df2"])
+ expected = df.dropna()
+ tm.assert_frame_equal(result, expected, check_index_type=True)
+ tm.assert_index_equal(
+ temp_hdfstore.select("df1").index, temp_hdfstore.select("df2").index
+ )
+
+
+def test_append_to_multiple_dropna_false(temp_hdfstore):
+ df1 = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df2 = df1.copy().rename(columns="{}_2".format)
+ df1.iloc[1, df1.columns.get_indexer(["A", "B"])] = np.nan
+ df = concat([df1, df2], axis=1)
+
+ with pd.option_context("io.hdf.dropna_table", True):
+ # dropna=False shouldn't synchronize row indexes
+ temp_hdfstore.append_to_multiple(
+ {"df1a": ["A", "B"], "df2a": None}, df, selector="df1a", dropna=False
+ )
+
+ msg = "all tables must have exactly the same nrows!"
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.select_as_multiple(["df1a", "df2a"])
+
+ assert not temp_hdfstore.select("df1a").index.equals(
+ temp_hdfstore.select("df2a").index
+ )
+
+
+def test_append_to_multiple_min_itemsize(temp_hdfstore):
+ # GH 11238
+ df = DataFrame(
+ {
+ "IX": np.arange(1, 21),
+ "Num": np.arange(1, 21),
+ "BigNum": np.arange(1, 21) * 88,
+ "Str": ["a" for _ in range(20)],
+ "LongStr": ["abcde" for _ in range(20)],
+ }
+ )
+ expected = df.iloc[[0]]
+ # Reading/writing RangeIndex info is not supported yet
+ expected.index = Index(list(range(len(expected.index))))
+
+ temp_hdfstore.append_to_multiple(
+ {
+ "index": ["IX"],
+ "nums": ["Num", "BigNum"],
+ "strs": ["Str", "LongStr"],
+ },
+ df.iloc[[0]],
+ "index",
+ min_itemsize={"Str": 10, "LongStr": 100, "Num": 2},
+ )
+ result = temp_hdfstore.select_as_multiple(["index", "nums", "strs"])
+ tm.assert_frame_equal(result, expected, check_index_type=True)
+
+
+def test_append_string_nan_rep(temp_hdfstore):
+ # GH 16300
+ df = DataFrame({"A": "a", "B": "foo"}, index=np.arange(10))
+ df_nan = df.copy()
+ df_nan.loc[0:4, :] = np.nan
+ msg = "NaN representation is too large for existing column size"
+
+ # string column too small
+ temp_hdfstore.append("sa", df["A"])
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("sa", df_nan["A"])
+
+ # nan_rep too big
+ temp_hdfstore.append("sb", df["B"], nan_rep="bars")
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("sb", df_nan["B"])
+
+ # smaller modified nan_rep
+ temp_hdfstore.append("sc", df["A"], nan_rep="n")
+ temp_hdfstore.append("sc", df_nan["A"])
+ result = temp_hdfstore["sc"]
+ expected = concat([df["A"], df_nan["A"]])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_categorical.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_categorical.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce39f21fb3f8c74064f1cbe69bd6b887fd43b408
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_categorical.py
@@ -0,0 +1,189 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ DataFrame,
+ Series,
+ _testing as tm,
+ concat,
+ read_hdf,
+)
+
+pytestmark = [pytest.mark.single_cpu]
+
+
+def test_categorical(temp_hdfstore):
+ # Basic
+ s = Series(
+ Categorical(
+ ["a", "b", "b", "a", "a", "c"],
+ categories=["a", "b", "c", "d"],
+ ordered=False,
+ )
+ )
+ temp_hdfstore.append("s", s, format="table")
+ result = temp_hdfstore.select("s")
+ tm.assert_series_equal(s, result)
+
+ s = Series(
+ Categorical(
+ ["a", "b", "b", "a", "a", "c"],
+ categories=["a", "b", "c", "d"],
+ ordered=True,
+ )
+ )
+ temp_hdfstore.append("s_ordered", s, format="table")
+ result = temp_hdfstore.select("s_ordered")
+ tm.assert_series_equal(s, result)
+
+ df = DataFrame({"s": s, "vals": [1, 2, 3, 4, 5, 6]})
+ temp_hdfstore.append("df", df, format="table")
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(result, df)
+
+ # Dtypes
+ s = Series([1, 1, 2, 2, 3, 4, 5]).astype("category")
+ temp_hdfstore.append("si", s)
+ result = temp_hdfstore.select("si")
+ tm.assert_series_equal(result, s)
+
+ s = Series([1, 1, np.nan, 2, 3, 4, 5]).astype("category")
+ temp_hdfstore.append("si2", s)
+ result = temp_hdfstore.select("si2")
+ tm.assert_series_equal(result, s)
+
+ # Multiple
+ df2 = df.copy()
+ df2["s2"] = Series(list("abcdefg")).astype("category")
+ temp_hdfstore.append("df2", df2)
+ result = temp_hdfstore.select("df2")
+ tm.assert_frame_equal(result, df2)
+
+ # Make sure the metadata is OK
+ info = temp_hdfstore.info()
+ assert "/df2 " in info
+ # df2._mgr.blocks[0] and df2._mgr.blocks[2] are Categorical
+ assert "/df2/meta/values_block_0/meta" in info
+ assert "/df2/meta/values_block_2/meta" in info
+
+ # unordered
+ s = Series(
+ Categorical(
+ ["a", "b", "b", "a", "a", "c"],
+ categories=["a", "b", "c", "d"],
+ ordered=False,
+ )
+ )
+ temp_hdfstore.append("s2", s, format="table")
+ result = temp_hdfstore.select("s2")
+ tm.assert_series_equal(result, s)
+
+ # Query
+ temp_hdfstore.append("df3", df, data_columns=["s"])
+ expected = df[df.s.isin(["b", "c"])]
+ result = temp_hdfstore.select("df3", where=['s in ["b","c"]'])
+ tm.assert_frame_equal(result, expected)
+
+ expected = df[df.s.isin(["b", "c"])]
+ result = temp_hdfstore.select("df3", where=['s = ["b","c"]'])
+ tm.assert_frame_equal(result, expected)
+
+ expected = df[df.s.isin(["d"])]
+ result = temp_hdfstore.select("df3", where=['s in ["d"]'])
+ tm.assert_frame_equal(result, expected)
+
+ expected = df[df.s.isin(["f"])]
+ result = temp_hdfstore.select("df3", where=['s in ["f"]'])
+ tm.assert_frame_equal(result, expected)
+
+ # Appending with same categories is ok
+ temp_hdfstore.append("df3", df)
+
+ df = concat([df, df])
+ expected = df[df.s.isin(["b", "c"])]
+ result = temp_hdfstore.select("df3", where=['s in ["b","c"]'])
+ tm.assert_frame_equal(result, expected)
+
+ # Appending must have the same categories
+ df3 = df.copy()
+ df3["s"] = df3["s"].cat.remove_unused_categories()
+
+ msg = "cannot append a categorical with different categories to the existing"
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df3", df3)
+
+ # Remove, and make sure meta data is removed (its a recursive
+ # removal so should be).
+ result = temp_hdfstore.select("df3/meta/s/meta")
+ assert result is not None
+ temp_hdfstore.remove("df3")
+
+ with pytest.raises(KeyError, match="'No object named df3/meta/s/meta in the file'"):
+ temp_hdfstore.select("df3/meta/s/meta")
+
+
+def test_categorical_conversion(temp_h5_path):
+ # GH13322
+ # Check that read_hdf with categorical columns doesn't return rows if
+ # where criteria isn't met.
+ obsids = ["ESP_012345_6789", "ESP_987654_3210"]
+ imgids = ["APF00006np", "APF0001imm"]
+ data = [4.3, 9.8]
+
+ # Test without categories
+ df = DataFrame({"obsids": obsids, "imgids": imgids, "data": data})
+
+ # We are expecting an empty DataFrame matching types of df
+ expected = df.iloc[[], :]
+ df.to_hdf(temp_h5_path, key="df", format="table", data_columns=True)
+ result = read_hdf(temp_h5_path, "df", where="obsids=B")
+ tm.assert_frame_equal(result, expected)
+
+ # Test with categories
+ df.obsids = df.obsids.astype("category")
+ df.imgids = df.imgids.astype("category")
+
+ # We are expecting an empty DataFrame matching types of df
+ expected = df.iloc[[], :]
+ df.to_hdf(temp_h5_path, key="df", format="table", data_columns=True)
+ result = read_hdf(temp_h5_path, "df", where="obsids=B")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_categorical_nan_only_columns(temp_h5_path):
+ # GH18413
+ # Check that read_hdf with categorical columns with NaN-only values can
+ # be read back.
+ df = DataFrame(
+ {
+ "a": ["a", "b", "c", np.nan],
+ "b": [np.nan, np.nan, np.nan, np.nan],
+ "c": [1, 2, 3, 4],
+ "d": Series([None] * 4, dtype=object),
+ }
+ )
+ df["a"] = df.a.astype("category")
+ df["b"] = df.b.astype("category")
+ df["d"] = df.b.astype("category")
+ expected = df
+ df.to_hdf(temp_h5_path, key="df", format="table", data_columns=True)
+ result = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("where, expected", [["q", []], ["a", ["a"]]])
+def test_convert_value(temp_h5_path, where: str, expected):
+ # GH39420
+ # Check that read_hdf with categorical columns can filter by where condition.
+ df = DataFrame({"col": ["a", "b", "s"]})
+ df.col = df.col.astype("category")
+ max_widths = {"col": 1}
+ categorical_values = sorted(df.col.unique())
+ expected = DataFrame({"col": expected})
+ expected.col = expected.col.astype("category")
+ expected.col = expected.col.cat.set_categories(categorical_values)
+
+ df.to_hdf(temp_h5_path, key="df", format="table", min_itemsize=max_widths)
+ result = read_hdf(temp_h5_path, where=f'col=="{where}"')
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..4262b1031c657c7a9a976bed0cd7935694517622
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_compat.py
@@ -0,0 +1,156 @@
+from pathlib import Path
+
+import pytest
+
+from pandas.compat.numpy import np_version_gt2
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.tests.io.generate_legacy_storage_files import create_dataframe_all_types
+from pandas.util.version import Version
+
+tables = pytest.importorskip("tables")
+
+
+@pytest.fixture
+def pytables_hdf5_file(temp_h5_path):
+ """
+ Use PyTables to create a simple HDF5 file.
+ """
+ table_schema = {
+ "c0": tables.Time64Col(pos=0),
+ "c1": tables.StringCol(5, pos=1),
+ "c2": tables.Int64Col(pos=2),
+ }
+
+ t0 = 1_561_105_000.0
+
+ testsamples = [
+ {"c0": t0, "c1": "aaaaa", "c2": 1},
+ {"c0": t0 + 1, "c1": "bbbbb", "c2": 2},
+ {"c0": t0 + 2, "c1": "ccccc", "c2": 10**5},
+ {"c0": t0 + 3, "c1": "ddddd", "c2": 4_294_967_295},
+ ]
+
+ objname = "pandas_test_timeseries"
+
+ with tables.open_file(temp_h5_path, mode="w") as f:
+ t = f.create_table("/", name=objname, description=table_schema)
+ for sample in testsamples:
+ for key, value in sample.items():
+ t.row[key] = value
+ t.row.append()
+
+ return temp_h5_path, objname, pd.DataFrame(testsamples)
+
+
+class TestReadPyTablesHDF5:
+ """
+ A group of tests which covers reading HDF5 files written by plain PyTables
+ (not written by pandas).
+
+ Was introduced for regression-testing issue 11188.
+ """
+
+ def test_read_complete(self, pytables_hdf5_file):
+ path, objname, df = pytables_hdf5_file
+ result = pd.read_hdf(path, key=objname)
+ expected = df
+ tm.assert_frame_equal(result, expected, check_index_type=True)
+
+ def test_read_with_start(self, pytables_hdf5_file):
+ path, objname, df = pytables_hdf5_file
+ # This is a regression test for pandas-dev/pandas/issues/11188
+ result = pd.read_hdf(path, key=objname, start=1)
+ expected = df[1:].reset_index(drop=True)
+ tm.assert_frame_equal(result, expected, check_index_type=True)
+
+ def test_read_with_stop(self, pytables_hdf5_file):
+ path, objname, df = pytables_hdf5_file
+ # This is a regression test for pandas-dev/pandas/issues/11188
+ result = pd.read_hdf(path, key=objname, stop=1)
+ expected = df[:1].reset_index(drop=True)
+ tm.assert_frame_equal(result, expected, check_index_type=True)
+
+ def test_read_with_startstop(self, pytables_hdf5_file):
+ path, objname, df = pytables_hdf5_file
+ # This is a regression test for pandas-dev/pandas/issues/11188
+ result = pd.read_hdf(path, key=objname, start=1, stop=2)
+ expected = df[1:2].reset_index(drop=True)
+ tm.assert_frame_equal(result, expected, check_index_type=True)
+
+
+_legacy_files = list(Path(__file__).parent.parent.glob("data/legacy_hdf/*/*.h5"))
+
+
+@pytest.mark.parametrize("legacy_file", _legacy_files, ids=lambda x: x.name)
+def test_legacy_files(datapath, legacy_file, using_infer_string, request):
+ legacy_version = Version(legacy_file.parent.name)
+ legacy_file = datapath(legacy_file)
+
+ if not np_version_gt2 and legacy_file.endswith("fixed.h5"):
+ # Files created for versions 2.0-3.0 used a numpy version >= 2.0, and
+ # unpickling the object dtype column fails with older numpy
+ pytest.skip("Fixed format pickle objects don't deserialize with numpy < 2.0")
+
+ result = pd.read_hdf(legacy_file)
+
+ expected = create_dataframe_all_types()
+
+ # the fixed format doesn't include categorical columns (not supported)
+ if legacy_file.endswith("fixed.h5"):
+ expected = expected.drop(
+ # columns=["categorical", "categorical_object", "categorical_int"]
+ columns=["categorical_int"]
+ )
+
+ # # object dtype columns with strings get read as `str`
+ # if using_infer_string:
+ # expected["object"] = expected["object"].astype("str")
+ # expected["object_nan"] = expected["object_nan"].astype("str")
+ # if legacy_file.endswith("table.h5"):
+ # expected["categorical_object"] = expected["categorical_object"].astype(
+ # pd.CategoricalDtype(
+ # expected["categorical_object"].cat.categories.astype("str")
+ # )
+ # )
+ # else:
+ # expected["string"] = expected["string"].astype("object")
+ # if legacy_file.endswith("table.h5"):
+ # expected["object"] = expected["object"].fillna(np.nan)
+ # expected["categorical"] = expected["categorical"].astype(
+ # pd.CategoricalDtype(
+ # expected["categorical"].cat.categories.astype(object)
+ # )
+ # )
+ # else:
+ # expected["string"] = expected["string"].fillna("nan")
+
+ if legacy_version < Version("2.2.0") or (
+ legacy_version < Version("3.0.0") and legacy_file.endswith("fixed.h5")
+ ):
+ # timedelta columns gets read as nanoseconds, resulting in buggy values
+ # (this also happened for direct roundtrips with those versions)
+ assert not result["timedelta_us"].equals(expected["timedelta_us"])
+ assert not result["timedelta_ms"].equals(expected["timedelta_ms"])
+ assert not result["timedelta_s"].equals(expected["timedelta_s"])
+ result = result.drop(columns=["timedelta_us", "timedelta_ms", "timedelta_s"])
+ expected = expected.drop(
+ columns=["timedelta_us", "timedelta_ms", "timedelta_s"]
+ )
+
+ if legacy_version < Version("2.2.0"):
+ # datetime columns gets read as nanoseconds, resulting in buggy values
+ # (this also happened for direct roundtrips with those versions)
+ assert not result["datetime_us"].equals(expected["datetime_us"])
+ assert not result["datetime_ms"].equals(expected["datetime_ms"])
+ assert not result["datetime_s"].equals(expected["datetime_s"])
+ assert not result["datetimetz_us"].equals(expected["datetimetz_us"])
+ result = result.drop(
+ columns=["datetime_us", "datetime_ms", "datetime_s", "datetimetz_us"]
+ )
+ expected = expected.drop(
+ columns=["datetime_us", "datetime_ms", "datetime_s", "datetimetz_us"]
+ )
+
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_complex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_complex.py
new file mode 100644
index 0000000000000000000000000000000000000000..046477c6fc8ab45ae7a828d2b2cb7f601ddd8ca8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_complex.py
@@ -0,0 +1,206 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+from pandas.io.pytables import read_hdf
+
+
+def test_complex_fixed(temp_h5_path):
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)).astype(np.complex64),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+
+ df.to_hdf(temp_h5_path, key="df")
+ reread = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df, reread)
+
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)).astype(np.complex128),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+ df.to_hdf(temp_h5_path, key="df")
+ reread = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df, reread)
+
+
+def test_complex_table(temp_h5_path):
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)).astype(np.complex64),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+
+ df.to_hdf(temp_h5_path, key="df", format="table")
+ reread = read_hdf(temp_h5_path, key="df")
+ tm.assert_frame_equal(df, reread)
+
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)).astype(np.complex128),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+
+ df.to_hdf(temp_h5_path, key="df", format="table", mode="w")
+ reread = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df, reread)
+
+
+def test_complex_mixed_fixed(temp_h5_path):
+ complex64 = np.array(
+ [1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j], dtype=np.complex64
+ )
+ complex128 = np.array(
+ [1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j], dtype=np.complex128
+ )
+ df = DataFrame(
+ {
+ "A": [1, 2, 3, 4],
+ "B": ["a", "b", "c", "d"],
+ "C": complex64,
+ "D": complex128,
+ "E": [1.0, 2.0, 3.0, 4.0],
+ },
+ index=list("abcd"),
+ )
+ df.to_hdf(temp_h5_path, key="df")
+ reread = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df, reread)
+
+
+@pytest.mark.filterwarnings(
+ "ignore:`alltrue` is deprecated as of NumPy 1.25.0:DeprecationWarning"
+)
+def test_complex_mixed_table_store_select(temp_hdfstore):
+ complex64 = np.array(
+ [1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j], dtype=np.complex64
+ )
+ complex128 = np.array(
+ [1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j], dtype=np.complex128
+ )
+ df = DataFrame(
+ {
+ "A": [1, 2, 3, 4],
+ "B": ["a", "b", "c", "d"],
+ "C": complex64,
+ "D": complex128,
+ "E": [1.0, 2.0, 3.0, 4.0],
+ },
+ index=list("abcd"),
+ )
+
+ temp_hdfstore.append("df", df, data_columns=["A", "B"])
+ result = temp_hdfstore.select("df", where="A>2")
+ tm.assert_frame_equal(df.loc[df.A > 2], result)
+
+
+@pytest.mark.filterwarnings(
+ "ignore:`alltrue` is deprecated as of NumPy 1.25.0:DeprecationWarning"
+)
+def test_complex_mixed_table_store_to_path(temp_h5_path):
+ complex64 = np.array(
+ [1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j], dtype=np.complex64
+ )
+ complex128 = np.array(
+ [1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j], dtype=np.complex128
+ )
+ df = DataFrame(
+ {
+ "A": [1, 2, 3, 4],
+ "B": ["a", "b", "c", "d"],
+ "C": complex64,
+ "D": complex128,
+ "E": [1.0, 2.0, 3.0, 4.0],
+ },
+ index=list("abcd"),
+ )
+
+ df.to_hdf(temp_h5_path, key="df", format="table")
+ reread = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df, reread)
+
+
+def test_complex_across_dimensions_fixed(temp_h5_path):
+ complex128 = np.array([1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j])
+ s = Series(complex128, index=list("abcd"))
+ df = DataFrame({"A": s, "B": s})
+
+ objs = [s, df]
+ comps = [tm.assert_series_equal, tm.assert_frame_equal]
+ for obj, comp in zip(objs, comps):
+ obj.to_hdf(temp_h5_path, key="obj", format="fixed")
+ reread = read_hdf(temp_h5_path, "obj")
+ comp(obj, reread)
+
+
+def test_complex_across_dimensions(temp_h5_path):
+ complex128 = np.array([1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j])
+ s = Series(complex128, index=list("abcd"))
+ df = DataFrame({"A": s, "B": s})
+
+ df.to_hdf(temp_h5_path, key="obj", format="table")
+ reread = read_hdf(temp_h5_path, "obj")
+ tm.assert_frame_equal(df, reread)
+
+
+def test_complex_indexing_error(temp_hdfstore):
+ complex128 = np.array(
+ [1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j], dtype=np.complex128
+ )
+ df = DataFrame(
+ {"A": [1, 2, 3, 4], "B": ["a", "b", "c", "d"], "C": complex128},
+ index=list("abcd"),
+ )
+
+ msg = (
+ "Columns containing complex values can be stored "
+ "but cannot be indexed when using table format. "
+ "Either use fixed format, set index=False, "
+ "or do not include the columns containing complex "
+ "values to data_columns when initializing the table."
+ )
+
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.append("df", df, data_columns=["C"])
+
+
+def test_complex_series_error(temp_h5_path):
+ complex128 = np.array([1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j, 1.0 + 1.0j])
+ s = Series(complex128, index=list("abcd"))
+
+ msg = (
+ "Columns containing complex values can be stored "
+ "but cannot be indexed when using table format. "
+ "Either use fixed format, set index=False, "
+ "or do not include the columns containing complex "
+ "values to data_columns when initializing the table."
+ )
+
+ with pytest.raises(TypeError, match=msg):
+ s.to_hdf(temp_h5_path, key="obj", format="t")
+
+ s.to_hdf(temp_h5_path, key="obj", format="t", index=False)
+ reread = read_hdf(temp_h5_path, "obj")
+ tm.assert_series_equal(s, reread)
+
+
+def test_complex_append(temp_hdfstore):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(100).astype(np.complex128),
+ "b": np.random.default_rng(2).standard_normal(100),
+ }
+ )
+
+ temp_hdfstore.append("df", df, data_columns=["b"])
+ temp_hdfstore.append("df", df)
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(pd.concat([df, df], axis=0), result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_errors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..7898508cde385822bf071a92c02975c6881e9fa0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_errors.py
@@ -0,0 +1,254 @@
+import datetime
+from io import BytesIO
+import re
+import uuid
+
+import numpy as np
+import pytest
+
+from pandas import (
+ CategoricalIndex,
+ DataFrame,
+ HDFStore,
+ Index,
+ MultiIndex,
+ date_range,
+ read_hdf,
+)
+
+from pandas.io.pytables import (
+ Term,
+ _maybe_adjust_name,
+)
+
+pytestmark = [pytest.mark.single_cpu]
+
+
+def test_pass_spec_to_storer(temp_hdfstore):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ temp_hdfstore.put("df", df)
+ msg = (
+ "cannot pass a column specification when reading a Fixed format "
+ "store. this store must be selected in its entirety"
+ )
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.select("df", columns=["A"])
+ msg = (
+ "cannot pass a where specification when reading from a Fixed "
+ "format store. this store must be selected in its entirety"
+ )
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.select("df", where=["columns=A"])
+
+
+def test_table_index_incompatible_dtypes(temp_hdfstore):
+ df1 = DataFrame({"a": [1, 2, 3]})
+ df2 = DataFrame(
+ {"a": [4, 5, 6]}, index=date_range("1/1/2000", periods=3, unit="ns")
+ )
+
+ temp_hdfstore.put("frame", df1, format="table")
+ msg = re.escape("incompatible kind in col [integer - datetime64[ns]]")
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.put("frame", df2, format="table", append=True)
+
+
+def test_unimplemented_dtypes_table_columns(temp_hdfstore):
+ dtypes = [("date", datetime.date(2001, 1, 2))]
+
+ # currently not supported dtypes ####
+ for n, f in dtypes:
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ df[n] = f
+ msg = re.escape(f"[{n}] is not implemented as a table column")
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.append(f"df1_{n}", df)
+
+
+def test_unimplemented_dtypes_table_columns2(temp_hdfstore):
+ # frame
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ df["obj1"] = "foo"
+ df["obj2"] = "bar"
+ df["datetime1"] = datetime.date(2001, 1, 2)
+ df = df._consolidate()
+
+ # this fails because we have a date in the object block......
+ msg = "|".join(
+ [
+ re.escape(
+ "Cannot serialize the column [datetime1]\nbecause its data "
+ "contents are not [string] but [date] object dtype"
+ ),
+ re.escape("[date] is not implemented as a table column"),
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.append("df_unimplemented", df)
+
+
+def test_invalid_terms(temp_hdfstore):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ df["string"] = "foo"
+ df.loc[df.index[0:4], "string"] = "bar"
+
+ temp_hdfstore.put("df", df, format="table")
+
+ # some invalid terms
+ msg = re.escape("__init__() missing 1 required positional argument: 'where'")
+ with pytest.raises(TypeError, match=msg):
+ Term()
+
+ # more invalid
+ msg = re.escape(
+ "cannot process expression [df.index[3]], "
+ "[2000-01-06 00:00:00] is not a valid condition"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.select("df", "df.index[3]")
+
+ msg = "invalid syntax"
+ with pytest.raises(SyntaxError, match=msg):
+ temp_hdfstore.select("df", "index>")
+
+
+def test_invalid_terms_from_docs(temp_h5_path):
+ # from the docs
+ dfq = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=list("ABCD"),
+ index=date_range("20130101", periods=10, unit="ns"),
+ )
+ dfq.to_hdf(temp_h5_path, key="dfq", format="table", data_columns=True)
+
+ # check ok
+ read_hdf(
+ temp_h5_path, "dfq", where="index>Timestamp('20130104') & columns=['A', 'B']"
+ )
+ read_hdf(temp_h5_path, "dfq", where="A>0 or C>0")
+
+
+def test_invalid_terms_reference(temp_h5_path):
+ # catch the invalid reference
+ dfq = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=list("ABCD"),
+ index=date_range("20130101", periods=10, unit="ns"),
+ )
+ dfq.to_hdf(temp_h5_path, key="dfq", format="table")
+
+ msg = (
+ r"The passed where expression: A>0 or C>0\n\s*"
+ r"contains an invalid variable reference\n\s*"
+ r"all of the variable references must be a reference to\n\s*"
+ r"an axis \(e.g. 'index' or 'columns'\), or a data_column\n\s*"
+ r"The currently defined references are: index,columns\n"
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_hdf(temp_h5_path, "dfq", where="A>0 or C>0")
+
+
+def test_append_with_diff_col_name_types_raises_value_error(temp_hdfstore):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 1)))
+ df2 = DataFrame({"a": np.random.default_rng(2).standard_normal(10)})
+ df3 = DataFrame({(1, 2): np.random.default_rng(2).standard_normal(10)})
+ df4 = DataFrame({("1", 2): np.random.default_rng(2).standard_normal(10)})
+ df5 = DataFrame({("1", 2, object): np.random.default_rng(2).standard_normal(10)})
+
+ name = "df_diff_valerror"
+ temp_hdfstore.append(name, df)
+
+ for d in (df2, df3, df4, df5):
+ msg = re.escape(
+ "cannot match existing table structure for [0] on appending data"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append(name, d)
+
+
+def test_invalid_complib(temp_h5_path):
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+ msg = r"complib only supports \[.*\] compression."
+ with pytest.raises(ValueError, match=msg):
+ df.to_hdf(temp_h5_path, key="df", complib="foolib")
+
+
+@pytest.mark.parametrize(
+ "idx",
+ [
+ date_range("2019", freq="D", periods=3, tz="UTC", unit="ns"),
+ CategoricalIndex(list("abc")),
+ ],
+)
+def test_to_hdf_multiindex_extension_dtype(idx, temp_h5_path):
+ # GH 7775
+ mi = MultiIndex.from_arrays([idx, idx])
+ df = DataFrame(0, index=mi, columns=["a"])
+ with pytest.raises(NotImplementedError, match="Saving a MultiIndex"):
+ df.to_hdf(temp_h5_path, key="df")
+
+
+def test_unsuppored_hdf_file_error(datapath):
+ # GH 9539
+ data_path = datapath("io", "data", "legacy_hdf/incompatible_dataset.h5")
+ message = (
+ r"Dataset\(s\) incompatible with Pandas data types, "
+ "not table, or no datasets found in HDF5 file."
+ )
+
+ with pytest.raises(ValueError, match=message):
+ read_hdf(data_path)
+
+
+def test_read_hdf_errors(temp_h5_path):
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+
+ msg = r"File [\S]* does not exist"
+ with pytest.raises(OSError, match=msg):
+ read_hdf(f"{uuid.uuid4()}.h5", "key")
+
+ df.to_hdf(temp_h5_path, key="df")
+ store = HDFStore(temp_h5_path, mode="r")
+ store.close()
+
+ msg = "The HDFStore must be open for reading."
+ with pytest.raises(OSError, match=msg):
+ read_hdf(store, "df")
+
+
+def test_read_hdf_generic_buffer_errors():
+ msg = "Support for generic buffers has not been implemented."
+ with pytest.raises(NotImplementedError, match=msg):
+ read_hdf(BytesIO(b""), "df")
+
+
+@pytest.mark.parametrize("bad_version", [(1, 2), (1,), [], "12", "123"])
+def test_maybe_adjust_name_bad_version_raises(bad_version):
+ msg = "Version is incorrect, expected sequence of 3 integers"
+ with pytest.raises(ValueError, match=msg):
+ _maybe_adjust_name("values_block_0", version=bad_version)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_file_handling.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_file_handling.py
new file mode 100644
index 0000000000000000000000000000000000000000..d81c545de262286832a27acf547e2d7c3aca811c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_file_handling.py
@@ -0,0 +1,489 @@
+import os
+import uuid
+
+import numpy as np
+import pytest
+
+from pandas.compat import (
+ is_platform_linux,
+ is_platform_little_endian,
+ is_platform_mac,
+)
+from pandas.errors import (
+ ClosedFileError,
+ PossibleDataLossError,
+)
+
+from pandas import (
+ DataFrame,
+ HDFStore,
+ Index,
+ Series,
+ _testing as tm,
+ date_range,
+ read_hdf,
+)
+
+from pandas.io import pytables
+from pandas.io.pytables import Term
+
+tables = pytest.importorskip("tables")
+pytestmark = [pytest.mark.single_cpu]
+
+
+@pytest.mark.parametrize("mode", ["r", "r+", "a", "w"])
+def test_mode(temp_h5_path, mode, using_infer_string):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ msg = r"[\S]* does not exist"
+ doesnt_exist = f"{uuid.uuid4()}.h5"
+
+ # constructor
+ if mode in ["r", "r+"]:
+ with pytest.raises(OSError, match=msg):
+ HDFStore(doesnt_exist, mode=mode)
+
+ else:
+ with HDFStore(temp_h5_path, mode=mode) as store:
+ assert store._handle.mode == mode
+
+ # context
+ if mode in ["r", "r+"]:
+ with pytest.raises(OSError, match=msg):
+ with HDFStore(doesnt_exist, mode=mode) as store:
+ pass
+ else:
+ with HDFStore(temp_h5_path, mode=mode) as store:
+ assert store._handle.mode == mode
+
+ # conv write
+ if mode in ["r", "r+"]:
+ with pytest.raises(OSError, match=msg):
+ df.to_hdf(doesnt_exist, key="df", mode=mode)
+ df.to_hdf(temp_h5_path, key="df", mode="w")
+ else:
+ df.to_hdf(temp_h5_path, key="df", mode=mode)
+
+ # conv read
+ if mode in ["w"]:
+ msg = (
+ "mode w is not allowed while performing a read. "
+ r"Allowed modes are r, r\+ and a."
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_hdf(temp_h5_path, "df", mode=mode)
+ else:
+ result = read_hdf(temp_h5_path, "df", mode=mode)
+ if using_infer_string:
+ df.columns = df.columns.astype("str")
+ tm.assert_frame_equal(result, df)
+
+
+def test_default_mode(temp_h5_path, using_infer_string):
+ # read_hdf uses default mode
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df.to_hdf(temp_h5_path, key="df", mode="w")
+ result = read_hdf(temp_h5_path, "df")
+ expected = df.copy()
+ if using_infer_string:
+ expected.columns = expected.columns.astype("str")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_reopen_handle(temp_h5_path):
+ store = HDFStore(temp_h5_path, mode="a")
+ store["a"] = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+
+ msg = (
+ r"Re-opening the file \[[\S]*\] with mode \[a\] will delete the "
+ "current file!"
+ )
+ # invalid mode change
+ with pytest.raises(PossibleDataLossError, match=msg):
+ store.open("w")
+
+ store.close()
+ assert not store.is_open
+
+ # truncation ok here
+ store.open("w")
+ assert store.is_open
+ assert len(store) == 0
+ store.close()
+ assert not store.is_open
+
+ store = HDFStore(temp_h5_path, mode="a")
+ store["a"] = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+
+ # reopen as read
+ store.open("r")
+ assert store.is_open
+ assert len(store) == 1
+ assert store._mode == "r"
+ store.close()
+ assert not store.is_open
+
+ # reopen as append
+ store.open("a")
+ assert store.is_open
+ assert len(store) == 1
+ assert store._mode == "a"
+ store.close()
+ assert not store.is_open
+
+ # reopen as append (again)
+ store.open("a")
+ assert store.is_open
+ assert len(store) == 1
+ assert store._mode == "a"
+ store.close()
+ assert not store.is_open
+
+
+def test_open_args(using_infer_string):
+ not_written = f"{uuid.uuid4()}.h5"
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ # create an in memory store
+ store = HDFStore(
+ not_written, mode="a", driver="H5FD_CORE", driver_core_backing_store=0
+ )
+ store["df"] = df
+ store.append("df2", df)
+
+ expected = df.copy()
+ if using_infer_string:
+ expected.index = expected.index.astype("str")
+ expected.columns = expected.columns.astype("str")
+
+ tm.assert_frame_equal(store["df"], expected)
+ tm.assert_frame_equal(store["df2"], expected)
+
+ store.close()
+
+ # the file should not have actually been written
+ assert not os.path.exists(not_written)
+
+
+def test_flush(temp_h5_path):
+ with HDFStore(temp_h5_path, mode="w") as store:
+ store["a"] = Series(range(5))
+ store.flush()
+ store.flush(fsync=True)
+
+
+def test_complibs_default_settings(temp_h5_path, using_infer_string):
+ # GH15943
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ # Set complevel and check if complib is automatically set to
+ # default value
+ df.to_hdf(temp_h5_path, key="df", complevel=9)
+ result = read_hdf(temp_h5_path, "df")
+ expected = df.copy()
+ if using_infer_string:
+ expected.index = expected.index.astype("str")
+ expected.columns = expected.columns.astype("str")
+ tm.assert_frame_equal(result, expected)
+
+ with tables.open_file(temp_h5_path, mode="r") as h5file:
+ for node in h5file.walk_nodes(where="/df", classname="Leaf"):
+ assert node.filters.complevel == 9
+ assert node.filters.complib == "zlib"
+
+ # Set complib and check to see if compression is disabled
+ df.to_hdf(temp_h5_path, key="df", complib="zlib")
+ result = read_hdf(temp_h5_path, "df")
+ expected = df.copy()
+ if using_infer_string:
+ expected.index = expected.index.astype("str")
+ expected.columns = expected.columns.astype("str")
+ tm.assert_frame_equal(result, expected)
+
+ with tables.open_file(temp_h5_path, mode="r") as h5file:
+ for node in h5file.walk_nodes(where="/df", classname="Leaf"):
+ assert node.filters.complevel == 0
+ assert node.filters.complib is None
+
+ # Check if not setting complib or complevel results in no compression
+ df.to_hdf(temp_h5_path, key="df")
+ result = read_hdf(temp_h5_path, "df")
+ expected = df.copy()
+ if using_infer_string:
+ expected.index = expected.index.astype("str")
+ expected.columns = expected.columns.astype("str")
+ tm.assert_frame_equal(result, expected)
+
+ with tables.open_file(temp_h5_path, mode="r") as h5file:
+ for node in h5file.walk_nodes(where="/df", classname="Leaf"):
+ assert node.filters.complevel == 0
+ assert node.filters.complib is None
+
+
+def test_complibs_default_settings_override(temp_h5_path):
+ # Check if file-defaults can be overridden on a per table basis
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ store = HDFStore(temp_h5_path)
+ store.append("dfc", df, complevel=9, complib="blosc")
+ store.append("df", df)
+ store.close()
+
+ with tables.open_file(temp_h5_path, mode="r") as h5file:
+ for node in h5file.walk_nodes(where="/df", classname="Leaf"):
+ assert node.filters.complevel == 0
+ assert node.filters.complib is None
+ for node in h5file.walk_nodes(where="/dfc", classname="Leaf"):
+ assert node.filters.complevel == 9
+ assert node.filters.complib == "blosc"
+
+
+@pytest.mark.parametrize("lvl", range(10))
+@pytest.mark.parametrize("lib", tables.filters.all_complibs)
+@pytest.mark.filterwarnings("ignore:object name is not a valid")
+def test_complibs(tmp_path, lvl, lib, request):
+ # GH14478
+ if is_platform_linux() and lib == "blosc2" and lvl != 0:
+ request.applymarker(pytest.mark.xfail(reason=f"Fails for {lib} on Linux"))
+ df = DataFrame(
+ np.ones((30, 4)), columns=list("ABCD"), index=np.arange(30).astype(np.str_)
+ )
+
+ # Remove lzo if its not available on this platform
+ if not tables.which_lib_version("lzo"):
+ pytest.skip("lzo not available")
+ # Remove bzip2 if its not available on this platform
+ if not tables.which_lib_version("bzip2"):
+ pytest.skip("bzip2 not available")
+
+ tmpfile = tmp_path / f"{lvl}_{lib}.h5"
+ gname = f"{lvl}_{lib}"
+
+ # Write and read file to see if data is consistent
+ df.to_hdf(tmpfile, key=gname, complib=lib, complevel=lvl)
+ result = read_hdf(tmpfile, gname)
+ tm.assert_frame_equal(result, df)
+
+ is_mac = is_platform_mac()
+
+ # Open file and check metadata for correct amount of compression
+ with tables.open_file(tmpfile, mode="r") as h5table:
+ for node in h5table.walk_nodes(where="/" + gname, classname="Leaf"):
+ assert node.filters.complevel == lvl
+ if lvl == 0:
+ assert node.filters.complib is None
+ elif is_mac and lib == "blosc2":
+ res = node.filters.complib
+ assert res in [lib, "blosc2:blosclz"], res
+ else:
+ assert node.filters.complib == lib
+
+
+@pytest.mark.skipif(
+ not is_platform_little_endian(), reason="reason platform is not little endian"
+)
+def test_encoding(temp_hdfstore):
+ df = DataFrame({"A": "foo", "B": "bar"}, index=range(5))
+ df.loc[2, "A"] = np.nan
+ df.loc[3, "B"] = np.nan
+ temp_hdfstore.append("df", df, encoding="ascii")
+ tm.assert_frame_equal(temp_hdfstore["df"], df)
+
+ expected = df.reindex(columns=["A"])
+ result = temp_hdfstore.select("df", Term("columns=A", encoding="ascii"))
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "val",
+ [
+ [b"E\xc9, 17", b"", b"a", b"b", b"c"],
+ [b"E\xc9, 17", b"a", b"b", b"c"],
+ [b"EE, 17", b"", b"a", b"b", b"c"],
+ [b"E\xc9, 17", b"\xf8\xfc", b"a", b"b", b"c"],
+ [b"", b"a", b"b", b"c"],
+ [b"\xf8\xfc", b"a", b"b", b"c"],
+ [b"A\xf8\xfc", b"", b"a", b"b", b"c"],
+ [np.nan, b"", b"b", b"c"],
+ [b"A\xf8\xfc", np.nan, b"", b"b", b"c"],
+ ],
+)
+@pytest.mark.parametrize("dtype", ["category", None])
+def test_latin_encoding(temp_h5_path, dtype, val):
+ enc = "latin-1"
+ nan_rep = ""
+ key = "data"
+
+ val = [x.decode(enc) if isinstance(x, bytes) else x for x in val]
+ ser = Series(val, dtype=dtype)
+
+ ser.to_hdf(temp_h5_path, key=key, format="table", encoding=enc, nan_rep=nan_rep)
+ retr = read_hdf(temp_h5_path, key)
+
+ # TODO:(3.0): once Categorical replace deprecation is enforced,
+ # we may be able to re-simplify the construction of s_nan
+ if dtype == "category":
+ if nan_rep in ser.cat.categories:
+ s_nan = ser.cat.remove_categories([nan_rep])
+ else:
+ s_nan = ser
+ else:
+ s_nan = ser.replace(nan_rep, np.nan)
+
+ tm.assert_series_equal(s_nan, retr)
+
+
+def test_multiple_open_close(temp_h5_path):
+ # gh-4409: open & close multiple times
+
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ df.to_hdf(temp_h5_path, key="df", mode="w", format="table")
+
+ # single
+ store = HDFStore(temp_h5_path)
+ assert "CLOSED" not in store.info()
+ assert store.is_open
+
+ store.close()
+ assert "CLOSED" in store.info()
+ assert not store.is_open
+
+ if pytables._table_file_open_policy_is_strict:
+ # multiples
+ store1 = HDFStore(temp_h5_path)
+ msg = (
+ r"The file [\S]* is already opened\. Please close it before "
+ r"reopening in write mode\."
+ )
+ with pytest.raises(ValueError, match=msg):
+ HDFStore(temp_h5_path)
+
+ store1.close()
+ else:
+ # multiples
+ store1 = HDFStore(temp_h5_path)
+ store2 = HDFStore(temp_h5_path)
+
+ assert "CLOSED" not in store1.info()
+ assert "CLOSED" not in store2.info()
+ assert store1.is_open
+ assert store2.is_open
+
+ store1.close()
+ assert "CLOSED" in store1.info()
+ assert not store1.is_open
+ assert "CLOSED" not in store2.info()
+ assert store2.is_open
+
+ store2.close()
+ assert "CLOSED" in store1.info()
+ assert "CLOSED" in store2.info()
+ assert not store1.is_open
+ assert not store2.is_open
+
+ # nested close
+ store = HDFStore(temp_h5_path, mode="w")
+ store.append("df", df)
+
+ store2 = HDFStore(temp_h5_path)
+ store2.append("df2", df)
+ store2.close()
+ assert "CLOSED" in store2.info()
+ assert not store2.is_open
+
+ store.close()
+ assert "CLOSED" in store.info()
+ assert not store.is_open
+
+ # double closing
+ store = HDFStore(temp_h5_path, mode="w")
+ store.append("df", df)
+
+ store2 = HDFStore(temp_h5_path)
+ store.close()
+ assert "CLOSED" in store.info()
+ assert not store.is_open
+
+ store2.close()
+ assert "CLOSED" in store2.info()
+ assert not store2.is_open
+
+ # ops on a closed store
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ df.to_hdf(temp_h5_path, key="df", mode="w", format="table")
+
+ store = HDFStore(temp_h5_path)
+ store.close()
+
+ msg = r"[\S]* file is not open!"
+ with pytest.raises(ClosedFileError, match=msg):
+ store.keys()
+
+ with pytest.raises(ClosedFileError, match=msg):
+ "df" in store
+
+ with pytest.raises(ClosedFileError, match=msg):
+ len(store)
+
+ with pytest.raises(ClosedFileError, match=msg):
+ store["df"]
+
+ with pytest.raises(ClosedFileError, match=msg):
+ store.select("df")
+
+ with pytest.raises(ClosedFileError, match=msg):
+ store.get("df")
+
+ with pytest.raises(ClosedFileError, match=msg):
+ store.append("df2", df)
+
+ with pytest.raises(ClosedFileError, match=msg):
+ store.put("df3", df)
+
+ with pytest.raises(ClosedFileError, match=msg):
+ store.get_storer("df2")
+
+ with pytest.raises(ClosedFileError, match=msg):
+ store.remove("df2")
+
+ with pytest.raises(ClosedFileError, match=msg):
+ store.select("df")
+
+ msg = "'HDFStore' object has no attribute 'df'"
+ with pytest.raises(AttributeError, match=msg):
+ store.df
+
+
+def test_fspath(temp_h5_path):
+ with HDFStore(temp_h5_path) as store:
+ assert os.fspath(store) == str(temp_h5_path)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_keys.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_keys.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0f22c332d0b555eca78ef3337768967c2e38477
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_keys.py
@@ -0,0 +1,80 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ HDFStore,
+ Index,
+ Series,
+ date_range,
+)
+
+tables = pytest.importorskip("tables")
+
+pytestmark = [pytest.mark.single_cpu]
+
+
+def test_keys(temp_hdfstore):
+ temp_hdfstore["a"] = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ temp_hdfstore["b"] = Series(
+ range(10), dtype="float64", index=[f"i_{i}" for i in range(10)]
+ )
+ temp_hdfstore["c"] = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ assert len(temp_hdfstore) == 3
+ expected = {"/a", "/b", "/c"}
+ assert set(temp_hdfstore.keys()) == expected
+ assert set(temp_hdfstore) == expected
+
+
+def test_non_pandas_keys(temp_h5_path):
+ class Table1(tables.IsDescription):
+ value1 = tables.Float32Col()
+
+ class Table2(tables.IsDescription):
+ value2 = tables.Float32Col()
+
+ class Table3(tables.IsDescription):
+ value3 = tables.Float32Col()
+
+ with tables.open_file(temp_h5_path, mode="w") as h5file:
+ group = h5file.create_group("/", "group")
+ h5file.create_table(group, "table1", Table1, "Table 1")
+ h5file.create_table(group, "table2", Table2, "Table 2")
+ h5file.create_table(group, "table3", Table3, "Table 3")
+ with HDFStore(temp_h5_path) as store:
+ assert len(store.keys(include="native")) == 3
+ expected = {"/group/table1", "/group/table2", "/group/table3"}
+ assert set(store.keys(include="native")) == expected
+ assert set(store.keys(include="pandas")) == set()
+ for name in expected:
+ df = store.get(name)
+ assert len(df.columns) == 1
+
+
+def test_keys_illegal_include_keyword_value(temp_hdfstore):
+ with pytest.raises(
+ ValueError,
+ match="`include` should be either 'pandas' or 'native' but is 'illegal'",
+ ):
+ temp_hdfstore.keys(include="illegal")
+
+
+def test_keys_ignore_hdf_softlink(temp_hdfstore):
+ # GH 20523
+ # Puts a softlink into HDF file and rereads
+ df = DataFrame({"A": range(5), "B": range(5)})
+ temp_hdfstore.put("df", df)
+
+ assert temp_hdfstore.keys() == ["/df"]
+
+ temp_hdfstore._handle.create_soft_link(temp_hdfstore._handle.root, "symlink", "df")
+
+ # Should ignore the softlink
+ assert temp_hdfstore.keys() == ["/df"]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_put.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_put.py
new file mode 100644
index 0000000000000000000000000000000000000000..125a81abd33b83a7c2cf1fbf61028079fd2f063e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_put.py
@@ -0,0 +1,405 @@
+import re
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import Timestamp
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ HDFStore,
+ Index,
+ MultiIndex,
+ Series,
+ _testing as tm,
+ concat,
+ date_range,
+)
+from pandas.util import _test_decorators as td
+
+pytestmark = [pytest.mark.single_cpu]
+
+
+def test_format_type(temp_hdfstore):
+ df = DataFrame({"A": [1, 2]})
+ temp_hdfstore.put("a", df, format="fixed")
+ temp_hdfstore.put("b", df, format="table")
+
+ assert temp_hdfstore.get_storer("a").format_type == "fixed"
+ assert temp_hdfstore.get_storer("b").format_type == "table"
+
+
+def test_format_kwarg_in_constructor(temp_h5_path):
+ # GH 13291
+
+ msg = "format is not a defined argument for HDFStore"
+
+ with pytest.raises(ValueError, match=msg):
+ HDFStore(temp_h5_path, format="table")
+
+
+def test_api_default_format(temp_hdfstore):
+ # default_format option
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ with pd.option_context("io.hdf.default_format", "fixed"):
+ temp_hdfstore.put("df", df)
+ assert not temp_hdfstore.get_storer("df").is_table
+
+ msg = "Can only append to Tables"
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df2", df)
+
+ with pd.option_context("io.hdf.default_format", "table"):
+ temp_hdfstore.remove("df")
+ temp_hdfstore.put("df", df)
+ assert temp_hdfstore.get_storer("df").is_table
+
+ temp_hdfstore.append("df2", df)
+ assert temp_hdfstore.get_storer("df").is_table
+
+
+def test_api_default_format_path(temp_h5_path):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ with pd.option_context("io.hdf.default_format", "fixed"):
+ df.to_hdf(temp_h5_path, key="df")
+ with HDFStore(temp_h5_path) as store:
+ assert not store.get_storer("df").is_table
+ msg = "Can only append to Tables"
+ with pytest.raises(ValueError, match=msg):
+ df.to_hdf(temp_h5_path, key="df2", append=True)
+
+ with pd.option_context("io.hdf.default_format", "table"):
+ df.to_hdf(temp_h5_path, key="df3")
+ with HDFStore(temp_h5_path) as store:
+ assert store.get_storer("df3").is_table
+ df.to_hdf(temp_h5_path, key="df4", append=True)
+ with HDFStore(temp_h5_path) as store:
+ assert store.get_storer("df4").is_table
+
+
+def test_put(temp_hdfstore):
+ store = temp_hdfstore
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((20, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=20, freq="B"),
+ )
+ store["a"] = ts
+ store["b"] = df[:10]
+ store["foo/bar/bah"] = df[:10]
+ store["foo"] = df[:10]
+ store["/foo"] = df[:10]
+ store.put("c", df[:10], format="table")
+
+ # not OK, not a table
+ msg = "Can only append to Tables"
+ with pytest.raises(ValueError, match=msg):
+ store.put("b", df[10:], append=True)
+
+ # node does not currently exist, test _is_table_type returns False
+ # in this case
+ with pytest.raises(ValueError, match=msg):
+ store.put("f", df[10:], append=True)
+
+ # can't put to a table (use append instead)
+ with pytest.raises(ValueError, match=msg):
+ store.put("c", df[10:], append=True)
+
+ # overwrite table
+ store.put("c", df[:10], format="table", append=False)
+ tm.assert_frame_equal(df[:10], store["c"])
+
+
+def test_put_string_index(temp_hdfstore):
+ store = temp_hdfstore
+ index = Index([f"I am a very long string index: {i}" for i in range(20)])
+ s = Series(np.arange(20), index=index)
+ df = DataFrame({"A": s, "B": s})
+
+ store["a"] = s
+ tm.assert_series_equal(store["a"], s)
+
+ store["b"] = df
+ tm.assert_frame_equal(store["b"], df)
+
+ # mixed length
+ index = Index(
+ ["abcdefghijklmnopqrstuvwxyz1234567890"]
+ + [f"I am a very long string index: {i}" for i in range(20)]
+ )
+ s = Series(np.arange(21), index=index)
+ df = DataFrame({"A": s, "B": s})
+ store["a"] = s
+ tm.assert_series_equal(store["a"], s)
+
+ store["b"] = df
+ tm.assert_frame_equal(store["b"], df)
+
+
+def test_put_compression(temp_hdfstore):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+
+ temp_hdfstore.put("c", df, format="table", complib="zlib")
+ tm.assert_frame_equal(temp_hdfstore["c"], df)
+
+ # can't compress if format='fixed'
+ msg = "Compression not supported on Fixed format stores"
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.put("b", df, format="fixed", complib="zlib")
+
+
+@td.skip_if_windows
+def test_put_compression_blosc(temp_hdfstore):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ # can't compress if format='fixed'
+ msg = "Compression not supported on Fixed format stores"
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.put("b", df, format="fixed", complib="blosc")
+
+ temp_hdfstore.put("c", df, format="table", complib="blosc")
+ tm.assert_frame_equal(temp_hdfstore["c"], df)
+
+
+def test_put_datetime_ser(temp_hdfstore, performance_warning, using_infer_string):
+ # https://github.com/pandas-dev/pandas/pull/60663
+ ser = Series(3 * [Timestamp("20010102").as_unit("ns")])
+ temp_hdfstore.put("ser", ser)
+ expected = ser.copy()
+ result = temp_hdfstore.get("ser")
+ tm.assert_series_equal(result, expected)
+
+
+def test_put_mixed_type(temp_hdfstore, performance_warning, using_infer_string):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df["obj1"] = "foo"
+ df["obj2"] = "bar"
+ df["bool1"] = df["A"] > 0
+ df["bool2"] = df["B"] > 0
+ df["bool3"] = True
+ df["int1"] = 1
+ df["int2"] = 2
+ df["timestamp1"] = Timestamp("20010102").as_unit("ns")
+ df["timestamp2"] = Timestamp("20010103").as_unit("ns")
+ df["datetime1"] = Timestamp("20010102").as_unit("ns")
+ df["datetime2"] = Timestamp("20010103").as_unit("ns")
+ df.loc[df.index[3:6], ["obj1"]] = np.nan
+ df = df._consolidate()
+
+ warning = None if using_infer_string else performance_warning
+ with tm.assert_produces_warning(warning):
+ temp_hdfstore.put("df", df)
+
+ expected = temp_hdfstore.get("df")
+ tm.assert_frame_equal(expected, df)
+
+
+def test_put_str_frame(temp_hdfstore, performance_warning, string_dtype_arguments):
+ # https://github.com/pandas-dev/pandas/pull/60663
+ dtype = pd.StringDtype(*string_dtype_arguments)
+ df = DataFrame({"a": pd.array(["x", pd.NA, "y"], dtype=dtype)})
+
+ temp_hdfstore.put("df", df)
+ expected_dtype = "str" if dtype.na_value is np.nan else "string"
+ expected = df.astype(expected_dtype)
+ result = temp_hdfstore.get("df")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_put_str_series(temp_hdfstore, performance_warning, string_dtype_arguments):
+ # https://github.com/pandas-dev/pandas/pull/60663
+ dtype = pd.StringDtype(*string_dtype_arguments)
+ ser = Series(["x", pd.NA, "y"], dtype=dtype)
+
+ temp_hdfstore.put("ser", ser)
+ expected_dtype = "str" if dtype.na_value is np.nan else "string"
+ expected = ser.astype(expected_dtype)
+ result = temp_hdfstore.get("ser")
+ tm.assert_series_equal(result, expected)
+
+
+def test_put_str_frame_complevel(temp_hdfstore, string_dtype_arguments):
+ # GH#64180 - writing StringDtype columns to HDFStore with fixed format + complevel
+ dtype = pd.StringDtype(*string_dtype_arguments)
+ df = DataFrame({"a": pd.array(["x", pd.NA, "y"], dtype=dtype), "b": [1, 2, 3]})
+ temp_hdfstore.put("df", df, complevel=1)
+ expected_dtype = "str" if dtype.na_value is np.nan else "string"
+ expected = df.copy()
+ expected["a"] = expected["a"].astype(expected_dtype)
+ result = temp_hdfstore.get("df")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("format", ["table", "fixed"])
+@pytest.mark.parametrize(
+ "index",
+ [
+ Index([str(i) for i in range(10)]),
+ Index(np.arange(10, dtype=float)),
+ Index(np.arange(10)),
+ date_range("2020-01-01", periods=10),
+ pd.period_range("2020-01-01", periods=10),
+ ],
+)
+def test_store_index_types(temp_hdfstore, format, index):
+ # GH5386
+ # test storing various index types
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ columns=list("AB"),
+ index=index,
+ )
+ temp_hdfstore.put("df", df, format=format)
+ tm.assert_frame_equal(df, temp_hdfstore["df"])
+
+
+def test_column_multiindex(temp_hdfstore, using_infer_string):
+ # GH 4710
+ # recreate multi-indexes properly
+
+ index = MultiIndex.from_tuples(
+ [("A", "a"), ("A", "b"), ("B", "a"), ("B", "b")], names=["first", "second"]
+ )
+ df = DataFrame(np.arange(12).reshape(3, 4), columns=index)
+ expected = df.set_axis(df.index.to_numpy())
+
+ temp_hdfstore.put("df", df)
+ tm.assert_frame_equal(
+ temp_hdfstore["df"], expected, check_index_type=True, check_column_type=True
+ )
+
+ temp_hdfstore.put("df1", df, format="table")
+ tm.assert_frame_equal(
+ temp_hdfstore["df1"], expected, check_index_type=True, check_column_type=True
+ )
+
+ msg = re.escape("cannot use a multi-index on axis [1] with data_columns ['A']")
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.put("df2", df, format="table", data_columns=["A"])
+ msg = re.escape("cannot use a multi-index on axis [1] with data_columns True")
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.put("df3", df, format="table", data_columns=True)
+
+
+def test_column_multiindex_existing(temp_hdfstore, using_infer_string):
+ # appending multi-column on existing table (see GH 6167)
+
+ index = MultiIndex.from_tuples(
+ [("A", "a"), ("A", "b"), ("B", "a"), ("B", "b")], names=["first", "second"]
+ )
+ df = DataFrame(np.arange(12).reshape(3, 4), columns=index)
+ temp_hdfstore.append("df2", df)
+ temp_hdfstore.append("df2", df)
+
+ tm.assert_frame_equal(temp_hdfstore["df2"], concat((df, df)))
+
+
+def test_column_multiindex_non_index_axes(temp_hdfstore, using_infer_string):
+ df = DataFrame(np.arange(12).reshape(3, 4), columns=Index(list("ABCD"), name="foo"))
+ expected = df.set_axis(df.index.to_numpy())
+
+ temp_hdfstore.put("df1", df, format="table")
+ tm.assert_frame_equal(
+ temp_hdfstore["df1"], expected, check_index_type=True, check_column_type=True
+ )
+
+
+def test_store_multiindex(temp_hdfstore):
+ # validate multi-index names
+ # GH 5527
+
+ def make_index(names=None):
+ dti = date_range("2013-12-01", "2013-12-02")
+ mi = MultiIndex.from_product([dti, range(2), range(3)], names=names)
+ return mi
+
+ # no names
+ df = DataFrame(np.zeros((12, 2)), columns=["a", "b"], index=make_index())
+ temp_hdfstore.append("df", df)
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+
+ # partial names
+ temp_hdfstore.remove("df")
+ df = DataFrame(
+ np.zeros((12, 2)),
+ columns=["a", "b"],
+ index=make_index(["date", None, None]),
+ )
+ temp_hdfstore.append("df", df)
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+
+ # series
+ ser = Series(np.zeros(12), index=make_index(["date", None, None]))
+ temp_hdfstore.append("ser", ser)
+ xp = Series(np.zeros(12), index=make_index(["date", "level_1", "level_2"]))
+ tm.assert_series_equal(temp_hdfstore.select("ser"), xp)
+
+ # dup with column
+ temp_hdfstore.remove("df")
+ df = DataFrame(
+ np.zeros((12, 2)),
+ columns=["a", "b"],
+ index=make_index(["date", "a", "t"]),
+ )
+ msg = "duplicate names/columns in the multi-index when storing as a table"
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df", df)
+
+ # dup within level
+ temp_hdfstore.remove("df")
+ df = DataFrame(
+ np.zeros((12, 2)),
+ columns=["a", "b"],
+ index=make_index(["date", "date", "date"]),
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df", df)
+
+ # fully names
+ temp_hdfstore.remove("df")
+ df = DataFrame(
+ np.zeros((12, 2)),
+ columns=["a", "b"],
+ index=make_index(["date", "s", "t"]),
+ )
+ temp_hdfstore.append("df", df)
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+
+
+@pytest.mark.parametrize("format", ["fixed", "table"])
+def test_store_periodindex(temp_h5_path, format):
+ # GH 7796
+ # test of PeriodIndex in HDFStore
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 1)),
+ index=pd.period_range("20220101", freq="M", periods=5),
+ )
+
+ df.to_hdf(temp_h5_path, key="df", mode="w", format=format)
+ expected = pd.read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_pytables_missing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_pytables_missing.py
new file mode 100644
index 0000000000000000000000000000000000000000..5594ef5fa626130e354fae9425d9cd988d2fd7fc
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_pytables_missing.py
@@ -0,0 +1,12 @@
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+
+
+@td.skip_if_installed("tables")
+def test_pytables_raises(temp_h5_path):
+ df = pd.DataFrame({"A": [1, 2]})
+ with pytest.raises(ImportError, match="tables"):
+ df.to_hdf(temp_h5_path, key="df")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_read.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_read.py
new file mode 100644
index 0000000000000000000000000000000000000000..e8b01ead9c17b0aec12ffedf03e1f46d1f9c58ff
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_read.py
@@ -0,0 +1,305 @@
+from contextlib import closing
+import re
+
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_windows
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ HDFStore,
+ Index,
+ Series,
+ _testing as tm,
+ date_range,
+ read_hdf,
+)
+
+from pandas.io.pytables import TableIterator
+
+pytestmark = [pytest.mark.single_cpu]
+
+
+def test_read_missing_key_close_store(temp_h5_path):
+ # GH 25766
+ df = DataFrame({"a": range(2), "b": range(2)})
+ df.to_hdf(temp_h5_path, key="k1")
+
+ with pytest.raises(KeyError, match="'No object named k2 in the file'"):
+ read_hdf(temp_h5_path, "k2")
+
+ # smoke test to test that file is properly closed after
+ # read with KeyError before another write
+ df.to_hdf(temp_h5_path, key="k2")
+
+
+def test_read_index_error_close_store(temp_h5_path):
+ # GH 25766
+ df = DataFrame({"A": [], "B": []}, index=[])
+ df.to_hdf(temp_h5_path, key="k1")
+
+ with pytest.raises(IndexError, match=r"list index out of range"):
+ read_hdf(temp_h5_path, "k1", stop=0)
+
+ # smoke test to test that file is properly closed after
+ # read with IndexError before another write
+ df.to_hdf(temp_h5_path, key="k1")
+
+
+def test_read_missing_key_opened_store(temp_h5_path):
+ # GH 28699
+ df = DataFrame({"a": range(2), "b": range(2)})
+ df.to_hdf(temp_h5_path, key="k1")
+
+ with HDFStore(temp_h5_path, "r") as store:
+ with pytest.raises(KeyError, match="'No object named k2 in the file'"):
+ read_hdf(store, "k2")
+
+ # Test that the file is still open after a KeyError and that we can
+ # still read from it.
+ read_hdf(store, "k1")
+
+
+def test_read_column(temp_hdfstore):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+
+ # GH 17912
+ # HDFStore.select_column should raise a KeyError
+ # exception if the key is not a valid store
+ with pytest.raises(KeyError, match="No object named df in the file"):
+ temp_hdfstore.select_column("df", "index")
+
+ temp_hdfstore.append("df", df)
+ # error
+ with pytest.raises(
+ KeyError, match=re.escape("'column [foo] not found in the table'")
+ ):
+ temp_hdfstore.select_column("df", "foo")
+
+ msg = re.escape("select_column() got an unexpected keyword argument 'where'")
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.select_column("df", "index", where=["index>5"])
+
+ # valid
+ result = temp_hdfstore.select_column("df", "index")
+ tm.assert_almost_equal(result.values, Series(df.index).values)
+ assert isinstance(result, Series)
+
+ # not a data indexable column
+ msg = re.escape(
+ "column [values_block_0] can not be extracted individually; "
+ "it is not data indexable"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.select_column("df", "values_block_0")
+
+ # a data column
+ df2 = df.copy()
+ df2["string"] = "foo"
+ temp_hdfstore.append("df2", df2, data_columns=["string"])
+ result = temp_hdfstore.select_column("df2", "string")
+ tm.assert_almost_equal(result.values, df2["string"].values)
+
+ # a data column with NaNs, result excludes the NaNs
+ df3 = df.copy()
+ df3["string"] = "foo"
+ df3.loc[df3.index[4:6], "string"] = np.nan
+ temp_hdfstore.append("df3", df3, data_columns=["string"])
+ result = temp_hdfstore.select_column("df3", "string")
+ tm.assert_almost_equal(result.values, df3["string"].values)
+
+ # start/stop
+ result = temp_hdfstore.select_column("df3", "string", start=2)
+ tm.assert_almost_equal(result.values, df3["string"].values[2:])
+
+ result = temp_hdfstore.select_column("df3", "string", start=-2)
+ tm.assert_almost_equal(result.values, df3["string"].values[-2:])
+
+ result = temp_hdfstore.select_column("df3", "string", stop=2)
+ tm.assert_almost_equal(result.values, df3["string"].values[:2])
+
+ result = temp_hdfstore.select_column("df3", "string", stop=-2)
+ tm.assert_almost_equal(result.values, df3["string"].values[:-2])
+
+ result = temp_hdfstore.select_column("df3", "string", start=2, stop=-2)
+ tm.assert_almost_equal(result.values, df3["string"].values[2:-2])
+
+ result = temp_hdfstore.select_column("df3", "string", start=-2, stop=2)
+ tm.assert_almost_equal(result.values, df3["string"].values[-2:2])
+
+ # GH 10392 - make sure column name is preserved
+ df4 = DataFrame({"A": np.random.default_rng(2).standard_normal(10), "B": "foo"})
+ temp_hdfstore.append("df4", df4, data_columns=True)
+ expected = df4["B"]
+ result = temp_hdfstore.select_column("df4", "B")
+ tm.assert_series_equal(result, expected)
+
+
+def test_pytables_native_read(datapath):
+ with HDFStore(
+ datapath("io", "data", "legacy_hdf/pytables_native.h5"), mode="r"
+ ) as store:
+ d2 = store["detector/readout"]
+ assert isinstance(d2, DataFrame)
+
+
+@pytest.mark.skipif(is_platform_windows(), reason="native2 read fails oddly on windows")
+def test_pytables_native2_read(datapath):
+ with HDFStore(
+ datapath("io", "data", "legacy_hdf", "pytables_native2.h5"), mode="r"
+ ) as store:
+ str(store)
+ d1 = store["detector"]
+ assert isinstance(d1, DataFrame)
+
+
+def test_read_hdf_open_store(temp_h5_path, using_infer_string):
+ # GH10330
+ # No check for non-string path_or-buf, and no test of open store
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+ df.index.name = "letters"
+ df = df.set_index(keys="E", append=True)
+
+ df.to_hdf(temp_h5_path, key="df", mode="w")
+ direct = read_hdf(temp_h5_path, "df")
+ with HDFStore(temp_h5_path, mode="r") as store:
+ indirect = read_hdf(store, "df")
+ tm.assert_frame_equal(direct, indirect)
+ assert store.is_open
+
+
+def test_read_hdf_index_not_view(temp_h5_path):
+ # GH 37441
+ # Ensure that the index of the DataFrame is not a view
+ # into the original recarray that pytables reads in
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)),
+ index=[0, 1, 2, 3],
+ columns=list("ABCDE"),
+ )
+
+ df.to_hdf(temp_h5_path, key="df", mode="w", format="table")
+
+ df2 = read_hdf(temp_h5_path, "df")
+ assert df2.index._data.base is None
+ tm.assert_frame_equal(df, df2)
+
+
+def test_read_hdf_iterator(temp_h5_path):
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+ df.index.name = "letters"
+ df = df.set_index(keys="E", append=True)
+
+ df.to_hdf(temp_h5_path, key="df", mode="w", format="t")
+ direct = read_hdf(temp_h5_path, "df")
+ iterator = read_hdf(temp_h5_path, "df", iterator=True)
+ with closing(iterator.store):
+ assert isinstance(iterator, TableIterator)
+ indirect = next(iterator.__iter__())
+ tm.assert_frame_equal(direct, indirect)
+
+
+def test_read_nokey(temp_h5_path):
+ # GH10443
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+
+ # Categorical dtype not supported for "fixed" format. So no need
+ # to test with that dtype in the dataframe here.
+ df.to_hdf(temp_h5_path, key="df", mode="a")
+ reread = read_hdf(temp_h5_path)
+ tm.assert_frame_equal(df, reread)
+ df.to_hdf(temp_h5_path, key="df2", mode="a")
+
+ msg = "key must be provided when HDF5 file contains multiple datasets."
+ with pytest.raises(ValueError, match=msg):
+ read_hdf(temp_h5_path)
+
+
+def test_read_nokey_table(temp_h5_path):
+ # GH13231
+ df = DataFrame({"i": range(5), "c": Series(list("abacd"), dtype="category")})
+
+ df.to_hdf(temp_h5_path, key="df", mode="a", format="table")
+ reread = read_hdf(temp_h5_path)
+ tm.assert_frame_equal(df, reread)
+ df.to_hdf(temp_h5_path, key="df2", mode="a", format="table")
+
+ msg = "key must be provided when HDF5 file contains multiple datasets."
+ with pytest.raises(ValueError, match=msg):
+ read_hdf(temp_h5_path)
+
+
+def test_read_nokey_empty(temp_h5_path):
+ store = HDFStore(temp_h5_path)
+ store.close()
+ msg = re.escape(
+ "Dataset(s) incompatible with Pandas data types, not table, or no "
+ "datasets found in HDF5 file."
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_hdf(temp_h5_path)
+
+
+def test_read_from_pathlib_path(temp_h5_path):
+ # GH11773
+ expected = DataFrame(
+ np.random.default_rng(2).random((4, 5)),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+
+ expected.to_hdf(temp_h5_path, key="df", mode="a")
+ actual = read_hdf(temp_h5_path, key="df")
+
+ tm.assert_frame_equal(expected, actual)
+
+
+@pytest.mark.parametrize("format", ["fixed", "table"])
+def test_read_hdf_series_mode_r(temp_h5_path, format):
+ # GH 16583
+ # Tests that reading a Series saved to an HDF file
+ # still works if a mode='r' argument is supplied
+ series = Series(range(10), dtype=np.float64)
+ series.to_hdf(temp_h5_path, key="data", format=format)
+ result = read_hdf(temp_h5_path, key="data", mode="r")
+ tm.assert_series_equal(result, series)
+
+
+def test_read_infer_string(temp_h5_path):
+ # GH#54431
+ df = DataFrame({"a": ["a", "b", None]})
+ df.to_hdf(temp_h5_path, key="data", format="table")
+ with pd.option_context("future.infer_string", True):
+ result = read_hdf(temp_h5_path, key="data", mode="r")
+ expected = DataFrame(
+ {"a": ["a", "b", None]},
+ dtype=pd.StringDtype(na_value=np.nan),
+ columns=Index(["a"], dtype=pd.StringDtype(na_value=np.nan)),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_hdfstore_read_datetime64_unit_s(temp_hdfstore):
+ # GH 59004
+ df_s = DataFrame(["2001-01-01", "2002-02-02"], dtype="datetime64[s]")
+ temp_hdfstore.put("df_s", df_s)
+ df_fromstore = temp_hdfstore.get("df_s")
+ tm.assert_frame_equal(df_s, df_fromstore)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_retain_attributes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_retain_attributes.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a88018e2c7ad04ddffb45c88a24b2e188688f96
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_retain_attributes.py
@@ -0,0 +1,95 @@
+import pytest
+
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Series,
+ _testing as tm,
+ date_range,
+ errors,
+ read_hdf,
+)
+
+pytestmark = pytest.mark.single_cpu
+
+
+def test_retain_index_attributes(temp_hdfstore, unit):
+ # GH 3499, losing frequency info on index recreation
+ dti = date_range("2000-1-1", periods=3, freq="h", unit=unit)
+ df = DataFrame({"A": Series(range(3), index=dti)})
+
+ temp_hdfstore.put("data", df, format="table")
+
+ result = temp_hdfstore.get("data")
+ tm.assert_frame_equal(df, result)
+
+ for attr in ["freq", "tz", "name"]:
+ for idx in ["index", "columns"]:
+ assert getattr(getattr(df, idx), attr, None) == getattr(
+ getattr(result, idx), attr, None
+ )
+
+ dti2 = date_range("2002-1-1", periods=3, freq="D", unit=unit)
+ # try to append a table with a different frequency
+ with tm.assert_produces_warning(errors.AttributeConflictWarning):
+ df2 = DataFrame({"A": Series(range(3), index=dti2)})
+ temp_hdfstore.append("data", df2)
+
+ assert temp_hdfstore.get_storer("data").info["index"]["freq"] is None
+
+ # this is ok
+ dti3 = DatetimeIndex(
+ ["2001-01-01", "2001-01-02", "2002-01-01"], dtype=f"M8[{unit}]"
+ )
+ df2 = DataFrame(
+ {
+ "A": Series(
+ range(3),
+ index=dti3,
+ )
+ }
+ )
+ temp_hdfstore.append("df2", df2)
+ dti4 = date_range("2002-1-1", periods=3, freq="D", unit=unit)
+ df3 = DataFrame({"A": Series(range(3), index=dti4)})
+ temp_hdfstore.append("df2", df3)
+
+
+def test_retain_index_attributes2(temp_h5_path):
+ with tm.assert_produces_warning(errors.AttributeConflictWarning):
+ df = DataFrame(
+ {"A": Series(range(3), index=date_range("2000-1-1", periods=3, freq="h"))}
+ )
+ df.to_hdf(temp_h5_path, key="data", mode="w", append=True)
+ df2 = DataFrame(
+ {"A": Series(range(3), index=date_range("2002-1-1", periods=3, freq="D"))}
+ )
+
+ df2.to_hdf(temp_h5_path, key="data", append=True)
+
+ idx = date_range("2000-1-1", periods=3, freq="h")
+ idx.name = "foo"
+ df = DataFrame({"A": Series(range(3), index=idx)})
+ df.to_hdf(temp_h5_path, key="data", mode="w", append=True)
+
+ assert read_hdf(temp_h5_path, key="data").index.name == "foo"
+
+ with tm.assert_produces_warning(errors.AttributeConflictWarning):
+ idx2 = date_range("2001-1-1", periods=3, freq="h")
+ idx2.name = "bar"
+ df2 = DataFrame({"A": Series(range(3), index=idx2)})
+ df2.to_hdf(temp_h5_path, key="data", append=True)
+
+ assert read_hdf(temp_h5_path, "data").index.name is None
+
+
+def test_retain_datetime_attribute(temp_h5_path):
+ ser = Series(
+ ["2024-08-26 15:13:14", "2024-08-26 15:14:14"],
+ dtype="datetime64[us, UTC]",
+ )
+ dataframe = DataFrame(ser)
+ dataframe.to_hdf(temp_h5_path, key="Annotations", mode="w")
+
+ recovered_dataframe = read_hdf(temp_h5_path, key="Annotations")
+ tm.assert_frame_equal(dataframe, recovered_dataframe)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_round_trip.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_round_trip.py
new file mode 100644
index 0000000000000000000000000000000000000000..62814d7c778d382d5f4a9f879736665c1bfea0e0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_round_trip.py
@@ -0,0 +1,572 @@
+import datetime
+import re
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import Timestamp
+from pandas.compat import is_platform_windows
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ HDFStore,
+ Index,
+ Series,
+ _testing as tm,
+ bdate_range,
+ date_range,
+ read_hdf,
+)
+from pandas.util import _test_decorators as td
+
+pytestmark = [pytest.mark.single_cpu]
+
+
+def test_conv_read_write(temp_h5_path):
+ def roundtrip(key, obj, **kwargs):
+ obj.to_hdf(temp_h5_path, key=key, **kwargs)
+ return read_hdf(temp_h5_path, key)
+
+ o = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ tm.assert_series_equal(o, roundtrip("series", o))
+
+ o = Series(range(10), dtype="float64", index=[f"i_{i}" for i in range(10)])
+ tm.assert_series_equal(o, roundtrip("string_series", o))
+
+ o = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ tm.assert_frame_equal(o, roundtrip("frame", o))
+
+ # table
+ df = DataFrame({"A": range(5), "B": range(5)})
+ df.to_hdf(temp_h5_path, key="table", append=True)
+ result = read_hdf(temp_h5_path, "table", where=["index>2"])
+ tm.assert_frame_equal(df[df.index > 2], result)
+
+
+def test_long_strings(temp_hdfstore):
+ # GH6166
+ data = ["a" * 50] * 10
+ df = DataFrame({"a": data}, index=data)
+
+ temp_hdfstore.append("df", df, data_columns=["a"])
+
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(df, result)
+
+
+def test_api(temp_h5_path):
+ # GH4584
+ # API issue when to_hdf doesn't accept append AND format args
+ path = temp_h5_path
+
+ df = DataFrame(range(20))
+ df.iloc[:10].to_hdf(path, key="df", append=True, format="table")
+ df.iloc[10:].to_hdf(path, key="df", append=True, format="table")
+ tm.assert_frame_equal(read_hdf(path, "df"), df)
+
+ # append to False
+ df.iloc[:10].to_hdf(path, key="df", append=False, format="table")
+ df.iloc[10:].to_hdf(path, key="df", append=True, format="table")
+ tm.assert_frame_equal(read_hdf(path, "df"), df)
+
+
+def test_api_append(temp_h5_path):
+ path = temp_h5_path
+
+ df = DataFrame(range(20))
+ df.iloc[:10].to_hdf(path, key="df", append=True)
+ df.iloc[10:].to_hdf(path, key="df", append=True, format="table")
+ tm.assert_frame_equal(read_hdf(path, "df"), df)
+
+ # append to False
+ df.iloc[:10].to_hdf(path, key="df", append=False, format="table")
+ df.iloc[10:].to_hdf(path, key="df", append=True)
+ tm.assert_frame_equal(read_hdf(path, "df"), df)
+
+
+def test_api_2(temp_h5_path):
+ df = DataFrame(range(20))
+ df.to_hdf(temp_h5_path, key="df", append=False, format="fixed")
+ tm.assert_frame_equal(read_hdf(temp_h5_path, "df"), df)
+
+ df.to_hdf(temp_h5_path, key="df", append=False, format="f")
+ tm.assert_frame_equal(read_hdf(temp_h5_path, "df"), df)
+
+ df.to_hdf(temp_h5_path, key="df", append=False)
+ tm.assert_frame_equal(read_hdf(temp_h5_path, "df"), df)
+
+ df.to_hdf(temp_h5_path, key="df")
+ tm.assert_frame_equal(read_hdf(temp_h5_path, "df"), df)
+
+
+def test_api_3(temp_hdfstore):
+ df = DataFrame(range(20))
+
+ temp_hdfstore.append("df", df.iloc[:10], append=True, format="table")
+ temp_hdfstore.append("df", df.iloc[10:], append=True, format="table")
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+
+ # append to False
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df.iloc[:10], append=False, format="table")
+ temp_hdfstore.append("df", df.iloc[10:], append=True, format="table")
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+
+ # formats
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df.iloc[:10], append=False, format="table")
+ temp_hdfstore.append("df", df.iloc[10:], append=True, format="table")
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df.iloc[:10], append=False, format="table")
+ temp_hdfstore.append("df", df.iloc[10:], append=True, format=None)
+ tm.assert_frame_equal(temp_hdfstore.select("df"), df)
+
+
+def test_api_invalid(temp_h5_path):
+ path = temp_h5_path
+ # Invalid.
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ msg = "Can only append to Tables"
+
+ with pytest.raises(ValueError, match=msg):
+ df.to_hdf(path, key="df", append=True, format="f")
+
+ with pytest.raises(ValueError, match=msg):
+ df.to_hdf(path, key="df", append=True, format="fixed")
+
+ msg = r"invalid HDFStore format specified \[foo\]"
+
+ with pytest.raises(TypeError, match=msg):
+ df.to_hdf(path, key="df", append=True, format="foo")
+
+ with pytest.raises(TypeError, match=msg):
+ df.to_hdf(path, key="df", append=False, format="foo")
+
+ # File path doesn't exist
+ path = ""
+ msg = f"File {path} does not exist"
+
+ with pytest.raises(FileNotFoundError, match=msg):
+ read_hdf(path, "df")
+
+
+def test_get(temp_hdfstore):
+ temp_hdfstore["a"] = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ left = temp_hdfstore.get("a")
+ right = temp_hdfstore["a"]
+ tm.assert_series_equal(left, right)
+
+ left = temp_hdfstore.get("/a")
+ right = temp_hdfstore["/a"]
+ tm.assert_series_equal(left, right)
+
+ with pytest.raises(KeyError, match="'No object named b in the file'"):
+ temp_hdfstore.get("b")
+
+
+def test_put_integer(temp_h5_path):
+ # non-date, non-string index
+ df = DataFrame(np.random.default_rng(2).standard_normal((50, 100)))
+ _check_roundtrip(df, tm.assert_frame_equal, temp_h5_path)
+
+
+def test_table_values_dtypes_roundtrip(temp_hdfstore, using_infer_string):
+ df1 = DataFrame({"a": [1, 2, 3]}, dtype="f8")
+ temp_hdfstore.append("df_f8", df1)
+ tm.assert_series_equal(df1.dtypes, temp_hdfstore["df_f8"].dtypes)
+
+ df2 = DataFrame({"a": [1, 2, 3]}, dtype="i8")
+ temp_hdfstore.append("df_i8", df2)
+ tm.assert_series_equal(df2.dtypes, temp_hdfstore["df_i8"].dtypes)
+
+ # incompatible dtype
+ msg = re.escape(
+ "Cannot serialize the column [a] "
+ "because its data contents are not [float] "
+ "but [integer] object dtype"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df_i8", df1)
+
+ # check creation/storage/retrieval of float32 (a bit hacky to
+ # actually create them thought)
+ df1 = DataFrame(np.array([[1], [2], [3]], dtype="f4"), columns=["A"])
+ temp_hdfstore.append("df_f4", df1)
+ tm.assert_series_equal(df1.dtypes, temp_hdfstore["df_f4"].dtypes)
+ assert df1.dtypes.iloc[0] == "float32"
+
+ # check with mixed dtypes
+ df1 = DataFrame(
+ {
+ c: Series(np.random.default_rng(2).integers(5), dtype=c)
+ for c in ["float32", "float64", "int32", "int64", "int16", "int8"]
+ }
+ )
+ df1["string"] = "foo"
+ df1["float322"] = 1.0
+ df1["float322"] = df1["float322"].astype("float32")
+ df1["bool"] = df1["float32"] > 0
+ df1["time_s_1"] = Timestamp("20130101").as_unit("s")
+ df1["time_s_2"] = Timestamp("20130101 00:00:00").as_unit("s")
+ df1["time_ms"] = Timestamp("20130101 00:00:00.000").as_unit("ms")
+ df1["time_ns"] = Timestamp("20130102 00:00:00.000000000")
+
+ temp_hdfstore.append("df_mixed_dtypes1", df1)
+ result = temp_hdfstore.select("df_mixed_dtypes1").dtypes.value_counts()
+ result.index = [str(i) for i in result.index]
+ str_dtype = "str" if using_infer_string else "object"
+ expected = Series(
+ {
+ "float32": 2,
+ "float64": 1,
+ "int32": 1,
+ "bool": 1,
+ "int16": 1,
+ "int8": 1,
+ "int64": 1,
+ str_dtype: 1,
+ "datetime64[s]": 2,
+ "datetime64[ms]": 1,
+ "datetime64[ns]": 1,
+ },
+ name="count",
+ )
+ result = result.sort_index()
+ expected = expected.sort_index()
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.filterwarnings("ignore::pandas.errors.PerformanceWarning")
+def test_series(temp_h5_path):
+ s = Series(range(10), dtype="float64", index=[f"i_{i}" for i in range(10)])
+ _check_roundtrip(s, tm.assert_series_equal, path=temp_h5_path)
+
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ _check_roundtrip(ts, tm.assert_series_equal, path=temp_h5_path)
+
+ ts2 = Series(ts.index, Index(ts.index))
+ _check_roundtrip(ts2, tm.assert_series_equal, path=temp_h5_path)
+
+ ts3 = Series(ts.values, Index(np.asarray(ts.index)))
+ _check_roundtrip(
+ ts3, tm.assert_series_equal, path=temp_h5_path, check_index_type=False
+ )
+
+
+def test_float_index(temp_h5_path):
+ # GH #454
+ index = np.random.default_rng(2).standard_normal(10)
+ s = Series(np.random.default_rng(2).standard_normal(10), index=index)
+ _check_roundtrip(s, tm.assert_series_equal, path=temp_h5_path)
+
+
+def test_tuple_index(temp_h5_path, performance_warning):
+ # GH #492
+ col = np.arange(10)
+ idx = [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0)]
+ data = np.random.default_rng(2).standard_normal(30).reshape((3, 10))
+ DF = DataFrame(data, index=idx, columns=col)
+
+ with tm.assert_produces_warning(performance_warning):
+ _check_roundtrip(DF, tm.assert_frame_equal, path=temp_h5_path)
+
+
+@pytest.mark.filterwarnings("ignore::pandas.errors.PerformanceWarning")
+def test_index_types(temp_h5_path):
+ values = np.random.default_rng(2).standard_normal(2)
+
+ func = lambda lhs, rhs: tm.assert_series_equal(lhs, rhs, check_index_type=True)
+
+ ser = Series(values, [0, "y"])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, [datetime.datetime.today(), 0])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, ["y", 0])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, [datetime.date.today(), "a"])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, [0, "y"])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, [datetime.datetime.today(), 0])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, ["y", 0])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, [datetime.date.today(), "a"])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, [1.23, "b"])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, [1, 1.53])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser = Series(values, [1, 5])
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ dti = DatetimeIndex(["2012-01-01", "2012-01-02"], dtype="M8[ns]")
+ ser = Series(values, index=dti)
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+ ser.index = ser.index.as_unit("s")
+ _check_roundtrip(ser, func, path=temp_h5_path)
+
+
+def test_timeseries_preepoch(temp_h5_path, request):
+ dr = bdate_range("1/1/1940", "1/1/1960")
+ ts = Series(np.random.default_rng(2).standard_normal(len(dr)), index=dr)
+ try:
+ _check_roundtrip(ts, tm.assert_series_equal, path=temp_h5_path)
+ except OverflowError:
+ if is_platform_windows():
+ request.applymarker(
+ pytest.mark.xfail("known failure on some windows platforms")
+ )
+ raise
+
+
+@pytest.mark.parametrize(
+ "compression", [False, pytest.param(True, marks=td.skip_if_windows)]
+)
+def test_frame(compression, temp_h5_path):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ # put in some random NAs
+ df.iloc[0, 0] = np.nan
+ df.iloc[5, 3] = np.nan
+
+ _check_roundtrip_table(
+ df, tm.assert_frame_equal, path=temp_h5_path, compression=compression
+ )
+ _check_roundtrip(
+ df, tm.assert_frame_equal, path=temp_h5_path, compression=compression
+ )
+
+ tdf = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ _check_roundtrip(
+ tdf, tm.assert_frame_equal, path=temp_h5_path, compression=compression
+ )
+
+ with HDFStore(temp_h5_path) as store:
+ # not consolidated
+ df["foo"] = np.random.default_rng(2).standard_normal(len(df))
+ store["df"] = df
+ recons = store["df"]
+ assert recons._mgr.is_consolidated()
+
+ # empty
+ df2 = df[:0]
+ # Prevent df2 from having index with inferred_type as string
+ df2.index = Index([])
+ _check_roundtrip(df2[:0], tm.assert_frame_equal, path=temp_h5_path)
+
+
+def test_empty_series_frame(temp_h5_path):
+ s0 = Series(dtype=object)
+ s1 = Series(name="myseries", dtype=object)
+ df0 = DataFrame()
+ df1 = DataFrame(index=["a", "b", "c"])
+ df2 = DataFrame(columns=["d", "e", "f"])
+
+ _check_roundtrip(s0, tm.assert_series_equal, path=temp_h5_path)
+ _check_roundtrip(s1, tm.assert_series_equal, path=temp_h5_path)
+ _check_roundtrip(df0, tm.assert_frame_equal, path=temp_h5_path)
+ _check_roundtrip(df1, tm.assert_frame_equal, path=temp_h5_path)
+ _check_roundtrip(df2, tm.assert_frame_equal, path=temp_h5_path)
+
+
+@pytest.mark.parametrize("dtype", [np.int64, np.float64, object, "m8[ns]", "M8[ns]"])
+def test_empty_series(dtype, temp_h5_path):
+ s = Series(dtype=dtype)
+ _check_roundtrip(s, tm.assert_series_equal, path=temp_h5_path)
+
+
+def test_can_serialize_dates(temp_h5_path):
+ rng = [x.date() for x in bdate_range("1/1/2000", "1/30/2000")]
+ frame = DataFrame(
+ np.random.default_rng(2).standard_normal((len(rng), 4)), index=rng
+ )
+
+ _check_roundtrip(frame, tm.assert_frame_equal, path=temp_h5_path)
+
+
+def test_store_hierarchical(
+ temp_h5_path, using_infer_string, multiindex_dataframe_random_data
+):
+ frame = multiindex_dataframe_random_data
+
+ _check_roundtrip(frame, tm.assert_frame_equal, path=temp_h5_path)
+ _check_roundtrip(frame.T, tm.assert_frame_equal, path=temp_h5_path)
+ _check_roundtrip(frame["A"], tm.assert_series_equal, path=temp_h5_path)
+
+ # check that the names are stored
+ with HDFStore(temp_h5_path) as store:
+ store["frame"] = frame
+ recons = store["frame"]
+ tm.assert_frame_equal(recons, frame)
+
+
+@pytest.mark.parametrize(
+ "compression", [False, pytest.param(True, marks=td.skip_if_windows)]
+)
+def test_store_mixed(compression, temp_h5_path):
+ def _make_one():
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ df["obj1"] = "foo"
+ df["obj2"] = "bar"
+ df["bool1"] = df["A"] > 0
+ df["bool2"] = df["B"] > 0
+ df["int1"] = 1
+ df["int2"] = 2
+ return df._consolidate()
+
+ df1 = _make_one()
+ df2 = _make_one()
+
+ _check_roundtrip(df1, tm.assert_frame_equal, path=temp_h5_path)
+ _check_roundtrip(df2, tm.assert_frame_equal, path=temp_h5_path)
+
+ with HDFStore(temp_h5_path) as store:
+ store["obj"] = df1
+ tm.assert_frame_equal(store["obj"], df1)
+ store["obj"] = df2
+ tm.assert_frame_equal(store["obj"], df2)
+
+ # check that can store Series of all of these types
+ _check_roundtrip(
+ df1["obj1"],
+ tm.assert_series_equal,
+ path=temp_h5_path,
+ compression=compression,
+ )
+ _check_roundtrip(
+ df1["bool1"],
+ tm.assert_series_equal,
+ path=temp_h5_path,
+ compression=compression,
+ )
+ _check_roundtrip(
+ df1["int1"],
+ tm.assert_series_equal,
+ path=temp_h5_path,
+ compression=compression,
+ )
+
+
+def _check_roundtrip(obj, comparator, path, compression=False, **kwargs):
+ options = {}
+ if compression:
+ options["complib"] = "blosc"
+
+ with HDFStore(path, "w", **options) as store:
+ store["obj"] = obj
+ retrieved = store["obj"]
+ comparator(retrieved, obj, **kwargs)
+
+
+def _check_roundtrip_table(obj, comparator, path, compression=False):
+ options = {}
+ if compression:
+ options["complib"] = "blosc"
+
+ with HDFStore(path, "w", **options) as store:
+ store.put("obj", obj, format="table")
+ retrieved = store["obj"]
+
+ comparator(retrieved, obj)
+
+
+def test_unicode_index(temp_h5_path):
+ unicode_values = ["\u03c3", "\u03c3\u03c3"]
+
+ s = Series(
+ np.random.default_rng(2).standard_normal(len(unicode_values)),
+ unicode_values,
+ )
+ _check_roundtrip(s, tm.assert_series_equal, path=temp_h5_path)
+
+
+def test_unicode_longer_encoded(temp_hdfstore):
+ # GH 11234
+ char = "\u0394"
+ df = DataFrame({"A": [char]})
+ temp_hdfstore.put("df", df, format="table", encoding="utf-8")
+ result = temp_hdfstore.get("df")
+ tm.assert_frame_equal(result, df)
+
+ df = DataFrame({"A": ["a", char], "B": ["b", "b"]})
+ temp_hdfstore.remove("df")
+ temp_hdfstore.put("df", df, format="table", encoding="utf-8")
+ result = temp_hdfstore.get("df")
+ tm.assert_frame_equal(result, df)
+
+
+def test_store_datetime_mixed(temp_h5_path):
+ df = DataFrame({"a": [1, 2, 3], "b": [1.0, 2.0, 3.0], "c": ["a", "b", "c"]})
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ df["d"] = ts.index[:3]
+ _check_roundtrip(df, tm.assert_frame_equal, path=temp_h5_path)
+
+
+def test_round_trip_equals(temp_h5_path):
+ # GH 9330
+ df = DataFrame({"B": [1, 2], "A": ["x", "y"]})
+
+ df.to_hdf(temp_h5_path, key="df", format="table")
+ other = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df, other)
+ assert df.equals(other)
+ assert other.equals(df)
+
+
+def test_infer_string_columns(temp_h5_path):
+ # GH#
+ pytest.importorskip("pyarrow")
+ with pd.option_context("future.infer_string", True):
+ df = DataFrame(1, columns=list("ABCD"), index=list(range(10))).set_index(
+ ["A", "B"]
+ )
+ expected = df.copy()
+ df.to_hdf(temp_h5_path, key="df", format="table")
+
+ result = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_select.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_select.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca98dcb2520851cffad2fefecd16e6c6da96cc5f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_select.py
@@ -0,0 +1,1050 @@
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import Timestamp
+from pandas.compat import PY312
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ HDFStore,
+ Index,
+ MultiIndex,
+ Series,
+ _testing as tm,
+ bdate_range,
+ concat,
+ date_range,
+ isna,
+ read_hdf,
+)
+
+from pandas.io.pytables import Term
+
+pytestmark = [pytest.mark.single_cpu]
+
+
+def test_select_columns_in_where(temp_hdfstore):
+ # GH 6169
+ # recreate multi-indexes when columns is passed
+ # in the `where` argument
+ index = MultiIndex(
+ levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
+ codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
+ names=["foo_name", "bar_name"],
+ )
+
+ # With a DataFrame
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 3)),
+ index=index,
+ columns=["A", "B", "C"],
+ )
+
+ temp_hdfstore.put("df", df, format="table")
+ expected = df[["A"]]
+
+ tm.assert_frame_equal(temp_hdfstore.select("df", columns=["A"]), expected)
+
+ tm.assert_frame_equal(temp_hdfstore.select("df", where="columns=['A']"), expected)
+
+ # With a Series
+ s = Series(np.random.default_rng(2).standard_normal(10), index=index, name="A")
+ temp_hdfstore.put("s", s, format="table")
+ tm.assert_series_equal(temp_hdfstore.select("s", where="columns=['A']"), s)
+
+
+def test_select_with_dups(temp_hdfstore):
+ # single dtypes
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)), columns=["A", "A", "B", "B"]
+ )
+ df.index = date_range("20130101 9:30", periods=10, freq="min", unit="ns")
+
+ temp_hdfstore.append("df", df)
+
+ result = temp_hdfstore.select("df")
+ expected = df
+ tm.assert_frame_equal(result, expected, by_blocks=True)
+
+ result = temp_hdfstore.select("df", columns=df.columns)
+ expected = df
+ tm.assert_frame_equal(result, expected, by_blocks=True)
+
+ result = temp_hdfstore.select("df", columns=["A"])
+ expected = df.loc[:, ["A"]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_select_with_dups_across_dtypes(temp_hdfstore):
+ df = concat(
+ [
+ DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=["A", "A", "B", "B"],
+ ),
+ DataFrame(
+ np.random.default_rng(2).integers(0, 10, size=20).reshape(10, 2),
+ columns=["A", "C"],
+ ),
+ ],
+ axis=1,
+ )
+ df.index = date_range("20130101 9:30", periods=10, freq="min", unit="ns")
+
+ temp_hdfstore.append("df", df)
+
+ result = temp_hdfstore.select("df")
+ expected = df
+ tm.assert_frame_equal(result, expected, by_blocks=True)
+
+ result = temp_hdfstore.select("df", columns=df.columns)
+ expected = df
+ tm.assert_frame_equal(result, expected, by_blocks=True)
+
+ expected = df.loc[:, ["A"]]
+ result = temp_hdfstore.select("df", columns=["A"])
+ tm.assert_frame_equal(result, expected, by_blocks=True)
+
+ expected = df.loc[:, ["B", "A"]]
+ result = temp_hdfstore.select("df", columns=["B", "A"])
+ tm.assert_frame_equal(result, expected, by_blocks=True)
+
+
+def test_select_with_dups_across_index_and_columns(temp_hdfstore):
+ df = concat(
+ [
+ DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=["A", "A", "B", "B"],
+ ),
+ DataFrame(
+ np.random.default_rng(2).integers(0, 10, size=20).reshape(10, 2),
+ columns=["A", "C"],
+ ),
+ ],
+ axis=1,
+ )
+ df.index = date_range("20130101 9:30", periods=10, freq="min", unit="ns")
+ temp_hdfstore.append("df", df)
+ temp_hdfstore.append("df", df)
+
+ expected = df.loc[:, ["B", "A"]]
+ expected = concat([expected, expected])
+ result = temp_hdfstore.select("df", columns=["B", "A"])
+ tm.assert_frame_equal(result, expected, by_blocks=True)
+
+
+def test_select(temp_hdfstore):
+ # select with columns=
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ temp_hdfstore.append("df", df)
+ result = temp_hdfstore.select("df", columns=["A", "B"])
+ expected = df.reindex(columns=["A", "B"])
+ tm.assert_frame_equal(expected, result)
+
+ # equivalently
+ result = temp_hdfstore.select("df", ["columns=['A', 'B']"])
+ expected = df.reindex(columns=["A", "B"])
+ tm.assert_frame_equal(expected, result)
+
+ # with a data column
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df, data_columns=["A"])
+ result = temp_hdfstore.select("df", ["A > 0"], columns=["A", "B"])
+ expected = df[df.A > 0].reindex(columns=["A", "B"])
+ tm.assert_frame_equal(expected, result)
+
+ # all a data columns
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df, data_columns=True)
+ result = temp_hdfstore.select("df", ["A > 0"], columns=["A", "B"])
+ expected = df[df.A > 0].reindex(columns=["A", "B"])
+ tm.assert_frame_equal(expected, result)
+
+ # with a data column, but different columns
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df, data_columns=["A"])
+ result = temp_hdfstore.select("df", ["A > 0"], columns=["C", "D"])
+ expected = df[df.A > 0].reindex(columns=["C", "D"])
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_dtypes_timestamp(temp_hdfstore):
+ # with a Timestamp data column (GH #2637)
+ df = DataFrame(
+ {
+ "ts": bdate_range("2012-01-01", periods=300, unit="ns"),
+ "A": np.random.default_rng(2).standard_normal(300),
+ }
+ )
+ temp_hdfstore.append("df", df, data_columns=["ts", "A"])
+
+ result = temp_hdfstore.select("df", "ts>=Timestamp('2012-02-01')")
+ expected = df[df.ts >= Timestamp("2012-02-01")]
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_dtypes_bools(temp_hdfstore):
+ # bool columns (GH #2849)
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 2)), columns=["A", "B"])
+ df["object"] = "foo"
+ df.loc[4:5, "object"] = "bar"
+ df["boolv"] = df["A"] > 0
+ temp_hdfstore.append("df", df, data_columns=True)
+
+ expected = df[df.boolv == True].reindex(columns=["A", "boolv"]) # noqa: E712
+ for v in [True, "true", 1]:
+ result = temp_hdfstore.select("df", f"boolv == {v}", columns=["A", "boolv"])
+ tm.assert_frame_equal(expected, result)
+
+ expected = df[df.boolv == False].reindex(columns=["A", "boolv"]) # noqa: E712
+ for v in [False, "false", 0]:
+ result = temp_hdfstore.select("df", f"boolv == {v}", columns=["A", "boolv"])
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_dtypes_integer_index(temp_hdfstore):
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).random(20),
+ "B": np.random.default_rng(2).random(20),
+ }
+ )
+ temp_hdfstore.append("df_int", df)
+ result = temp_hdfstore.select("df_int", "index<10 and columns=['A']")
+ expected = df.reindex(index=list(df.index)[0:10], columns=["A"])
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_dtypes_float_index(temp_hdfstore):
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).random(20),
+ "B": np.random.default_rng(2).random(20),
+ "index": np.arange(20, dtype="f8"),
+ }
+ )
+ temp_hdfstore.append("df_float", df)
+ result = temp_hdfstore.select("df_float", "index<10.0 and columns=['A']")
+ expected = df.reindex(index=list(df.index)[0:10], columns=["A"])
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_dtypes_floats_without_nan(temp_hdfstore):
+ df = DataFrame({"cols": range(11), "values": range(11)}, dtype="float64")
+ df["cols"] = (df["cols"] + 10).apply(str)
+
+ temp_hdfstore.append("df1", df, data_columns=True)
+ result = temp_hdfstore.select("df1", where="values>2.0")
+ expected = df[df["values"] > 2.0]
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_dtypes_floats_with_nan(temp_hdfstore):
+ df = DataFrame({"cols": range(11), "values": range(11)}, dtype="float64")
+ df["cols"] = (df["cols"] + 10).apply(str)
+ df.iloc[0] = np.nan
+ expected = df[df["values"] > 2.0]
+
+ temp_hdfstore.append("df2", df, data_columns=True, index=False)
+ result = temp_hdfstore.select("df2", where="values>2.0")
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_dtypes_floats_with_nan_not_first_position(temp_hdfstore):
+ df = DataFrame({"cols": range(11), "values": range(11)}, dtype="float64")
+ df["cols"] = (df["cols"] + 10).apply(str)
+
+ df.iloc[1] = np.nan
+ expected = df[df["values"] > 2.0]
+
+ temp_hdfstore.append("df4", df, data_columns=True)
+ result = temp_hdfstore.select("df4", where="values>2.0")
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_dtypes_comparison_with_numpy_scalar(temp_hdfstore, request):
+ # GH 11283
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ expected = df[df["A"] > 0]
+
+ temp_hdfstore.append("df", df, data_columns=True)
+ request.applymarker(
+ pytest.mark.xfail(
+ PY312,
+ reason="AST change in PY312",
+ raises=ValueError,
+ )
+ )
+ np_zero = np.float64(0) # noqa: F841
+ result = temp_hdfstore.select("df", where=["A>np_zero"])
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_with_many_inputs(temp_hdfstore):
+ df = DataFrame(
+ {
+ "ts": bdate_range("2012-01-01", periods=300, unit="ns"),
+ "A": np.random.default_rng(2).standard_normal(300),
+ "B": range(300),
+ "users": ["a"] * 50
+ + ["b"] * 50
+ + ["c"] * 100
+ + [f"a{i:03d}" for i in range(100)],
+ }
+ )
+ temp_hdfstore.append("df", df, data_columns=["ts", "A", "B", "users"])
+
+ # regular select
+ result = temp_hdfstore.select("df", "ts>=Timestamp('2012-02-01')")
+ expected = df[df.ts >= Timestamp("2012-02-01")]
+ tm.assert_frame_equal(expected, result)
+
+ # small selector
+ result = temp_hdfstore.select(
+ "df", "ts>=Timestamp('2012-02-01') & users=['a','b','c']"
+ )
+ expected = df[(df.ts >= Timestamp("2012-02-01")) & df.users.isin(["a", "b", "c"])]
+ tm.assert_frame_equal(expected, result)
+
+ # big selector along the columns
+ selector = ["a", "b", "c"] + [f"a{i:03d}" for i in range(60)]
+ result = temp_hdfstore.select(
+ "df", "ts>=Timestamp('2012-02-01') and users=selector"
+ )
+ expected = df[(df.ts >= Timestamp("2012-02-01")) & df.users.isin(selector)]
+ tm.assert_frame_equal(expected, result)
+
+ selector = range(100, 200)
+ result = temp_hdfstore.select("df", "B=selector")
+ expected = df[df.B.isin(selector)]
+ tm.assert_frame_equal(expected, result)
+ assert len(result) == 100
+
+ # big selector along the index
+ selector = Index(df.ts[0:100].values)
+ result = temp_hdfstore.select("df", "ts=selector")
+ expected = df[df.ts.isin(selector.values)]
+ tm.assert_frame_equal(expected, result)
+ assert len(result) == 100
+
+
+def test_select_iterator(temp_hdfstore):
+ # single table
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ temp_hdfstore.append("df", df)
+
+ expected = temp_hdfstore.select("df")
+
+ results = list(temp_hdfstore.select("df", iterator=True))
+ result = concat(results)
+ tm.assert_frame_equal(expected, result)
+
+ results = list(temp_hdfstore.select("df", chunksize=2))
+ assert len(results) == 5
+ result = concat(results)
+ tm.assert_frame_equal(expected, result)
+
+ results = list(temp_hdfstore.select("df", chunksize=2))
+ result = concat(results)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_select_iterator2(temp_h5_path):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ df.to_hdf(temp_h5_path, key="df_non_table")
+
+ msg = "can only use an iterator or chunksize on a table"
+ with pytest.raises(TypeError, match=msg):
+ read_hdf(temp_h5_path, "df_non_table", chunksize=2)
+
+ with pytest.raises(TypeError, match=msg):
+ read_hdf(temp_h5_path, "df_non_table", iterator=True)
+
+
+def test_select_iterator3(temp_h5_path):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ df.to_hdf(temp_h5_path, key="df", format="table")
+
+ results = list(read_hdf(temp_h5_path, "df", chunksize=2))
+ result = concat(results)
+
+ assert len(results) == 5
+ tm.assert_frame_equal(result, df)
+ tm.assert_frame_equal(result, read_hdf(temp_h5_path, "df"))
+
+
+def test_select_iterator_multiple(temp_hdfstore):
+ df1 = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ temp_hdfstore.append("df1", df1, data_columns=True)
+ df2 = df1.copy().rename(columns="{}_2".format)
+ df2["foo"] = "bar"
+ temp_hdfstore.append("df2", df2)
+
+ # full selection
+ expected = temp_hdfstore.select_as_multiple(["df1", "df2"], selector="df1")
+ results = list(
+ temp_hdfstore.select_as_multiple(["df1", "df2"], selector="df1", chunksize=2)
+ )
+ result = concat(results)
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_iterator_complete_8014(temp_hdfstore):
+ # GH 8014
+ # using iterator and where clause
+ # no iterator
+ expected = DataFrame(
+ np.random.default_rng(2).standard_normal((100064, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=100064, freq="s", unit="ns"),
+ )
+ temp_hdfstore.append("df", expected)
+
+ beg_dt = expected.index[0]
+ end_dt = expected.index[-1]
+
+ # select w/o iteration and no where clause works
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(expected, result)
+
+ # select w/o iterator and where clause, single term, begin
+ # of range, works
+ where = f"index >= '{beg_dt}'"
+ result = temp_hdfstore.select("df", where=where)
+ tm.assert_frame_equal(expected, result)
+
+ # select w/o iterator and where clause, single term, end
+ # of range, works
+ where = f"index <= '{end_dt}'"
+ result = temp_hdfstore.select("df", where=where)
+ tm.assert_frame_equal(expected, result)
+
+ # select w/o iterator and where clause, inclusive range,
+ # works
+ where = f"index >= '{beg_dt}' & index <= '{end_dt}'"
+ result = temp_hdfstore.select("df", where=where)
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_iterator_complete_8014_full_range(temp_hdfstore):
+ # GH 8014
+ chunksize = 1e4
+ expected = DataFrame(
+ np.random.default_rng(2).standard_normal((100064, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=100064, freq="s", unit="ns"),
+ )
+ temp_hdfstore.append("df", expected)
+
+ beg_dt = expected.index[0]
+ end_dt = expected.index[-1]
+
+ # select w/iterator and no where clause works
+ results = list(temp_hdfstore.select("df", chunksize=chunksize))
+ result = concat(results)
+ tm.assert_frame_equal(expected, result)
+
+ # select w/iterator and where clause, single term, begin of range
+ where = f"index >= '{beg_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+ result = concat(results)
+ tm.assert_frame_equal(expected, result)
+
+ # select w/iterator and where clause, single term, end of range
+ where = f"index <= '{end_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+ result = concat(results)
+ tm.assert_frame_equal(expected, result)
+
+ # select w/iterator and where clause, inclusive range
+ where = f"index >= '{beg_dt}' & index <= '{end_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+ result = concat(results)
+ tm.assert_frame_equal(expected, result)
+
+
+def test_select_iterator_non_complete_8014(temp_hdfstore):
+ # GH 8014
+ # using iterator and where clause
+ chunksize = 1e4
+
+ # with iterator, non complete range
+ expected = DataFrame(
+ np.random.default_rng(2).standard_normal((100064, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=100064, freq="s", unit="ns"),
+ )
+ temp_hdfstore.append("df", expected)
+
+ beg_dt = expected.index[1]
+ end_dt = expected.index[-2]
+
+ # select w/iterator and where clause, single term, begin of range
+ where = f"index >= '{beg_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+ result = concat(results)
+ rexpected = expected[expected.index >= beg_dt]
+ tm.assert_frame_equal(rexpected, result)
+
+ # select w/iterator and where clause, single term, end of range
+ where = f"index <= '{end_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+ result = concat(results)
+ rexpected = expected[expected.index <= end_dt]
+ tm.assert_frame_equal(rexpected, result)
+
+ # select w/iterator and where clause, inclusive range
+ where = f"index >= '{beg_dt}' & index <= '{end_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+ result = concat(results)
+ rexpected = expected[(expected.index >= beg_dt) & (expected.index <= end_dt)]
+ tm.assert_frame_equal(rexpected, result)
+
+
+def test_select_iterator_non_complete_8014_empty_where(temp_hdfstore):
+ chunksize = 1e4
+ expected = DataFrame(
+ np.random.default_rng(2).standard_normal((100064, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=100064, freq="s", unit="ns"),
+ )
+ temp_hdfstore.append("df", expected)
+
+ end_dt = expected.index[-1]
+
+ # select w/iterator and where clause, single term, begin of range
+ where = f"index > '{end_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+ assert 0 == len(results)
+
+
+def test_select_iterator_many_empty_frames(temp_hdfstore):
+ # GH 8014
+ # using iterator and where clause can return many empty
+ # frames.
+ chunksize = 10_000
+
+ # with iterator, range limited to the first chunk
+ expected = DataFrame(
+ np.random.default_rng(2).standard_normal((100064, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=100064, freq="s", unit="ns"),
+ )
+ temp_hdfstore.append("df", expected)
+
+ beg_dt = expected.index[0]
+ end_dt = expected.index[chunksize - 1]
+
+ # select w/iterator and where clause, single term, begin of range
+ where = f"index >= '{beg_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+ result = concat(results)
+ rexpected = expected[expected.index >= beg_dt]
+ tm.assert_frame_equal(rexpected, result)
+
+ # select w/iterator and where clause, single term, end of range
+ where = f"index <= '{end_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+
+ assert len(results) == 1
+ result = concat(results)
+ rexpected = expected[expected.index <= end_dt]
+ tm.assert_frame_equal(rexpected, result)
+
+ # select w/iterator and where clause, inclusive range
+ where = f"index >= '{beg_dt}' & index <= '{end_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+
+ # should be 1, is 10
+ assert len(results) == 1
+ result = concat(results)
+ rexpected = expected[(expected.index >= beg_dt) & (expected.index <= end_dt)]
+ tm.assert_frame_equal(rexpected, result)
+
+ # select w/iterator and where clause which selects
+ # *nothing*.
+ #
+ # To be consistent with Python idiom I suggest this should
+ # return [] e.g. `for e in []: print True` never prints
+ # True.
+
+ where = f"index <= '{beg_dt}' & index >= '{end_dt}'"
+ results = list(temp_hdfstore.select("df", where=where, chunksize=chunksize))
+
+ # should be []
+ assert len(results) == 0
+
+
+def test_frame_select(temp_hdfstore, request):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+
+ temp_hdfstore.put("frame", df, format="table")
+ date = df.index[len(df) // 2]
+
+ crit1 = Term("index>=date")
+ assert crit1.env.scope["date"] == date
+
+ crit2 = "columns=['A', 'D']"
+ crit3 = "columns=A"
+
+ request.applymarker(
+ pytest.mark.xfail(
+ PY312,
+ reason="AST change in PY312",
+ raises=TypeError,
+ )
+ )
+ result = temp_hdfstore.select("frame", [crit1, crit2])
+ expected = df.loc[date:, ["A", "D"]]
+ tm.assert_frame_equal(result, expected)
+
+ result = temp_hdfstore.select("frame", [crit3])
+ expected = df.loc[:, ["A"]]
+ tm.assert_frame_equal(result, expected)
+
+ # invalid terms
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ temp_hdfstore.append("df_time", df)
+ msg = "day is out of range for month: 0"
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.select("df_time", "index>0")
+
+ # can't select if not written as table
+ # temp_hdfstore['frame'] = df
+ # with pytest.raises(ValueError):
+ # temp_hdfstore.select('frame', [crit1, crit2])
+
+
+def test_frame_select_complex(temp_hdfstore):
+ # select via complex criteria
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ df["string"] = "foo"
+ df.loc[df.index[0:4], "string"] = "bar"
+
+ temp_hdfstore.put("df", df, format="table", data_columns=["string"])
+
+ # empty
+ result = temp_hdfstore.select("df", 'index>df.index[3] & string="bar"')
+ expected = df.loc[(df.index > df.index[3]) & (df.string == "bar")]
+ tm.assert_frame_equal(result, expected)
+
+ result = temp_hdfstore.select("df", 'index>df.index[3] & string="foo"')
+ expected = df.loc[(df.index > df.index[3]) & (df.string == "foo")]
+ tm.assert_frame_equal(result, expected)
+
+ # or
+ result = temp_hdfstore.select("df", 'index>df.index[3] | string="bar"')
+ expected = df.loc[(df.index > df.index[3]) | (df.string == "bar")]
+ tm.assert_frame_equal(result, expected)
+
+ result = temp_hdfstore.select(
+ "df", '(index>df.index[3] & index<=df.index[6]) | string="bar"'
+ )
+ expected = df.loc[
+ ((df.index > df.index[3]) & (df.index <= df.index[6])) | (df.string == "bar")
+ ]
+ tm.assert_frame_equal(result, expected)
+
+ # invert
+ result = temp_hdfstore.select("df", 'string!="bar"')
+ expected = df.loc[df.string != "bar"]
+ tm.assert_frame_equal(result, expected)
+
+ # invert not implemented in numexpr :(
+ msg = "cannot use an invert condition when passing to numexpr"
+ with pytest.raises(NotImplementedError, match=msg):
+ temp_hdfstore.select("df", '~(string="bar")')
+
+ # invert ok for filters
+ result = temp_hdfstore.select("df", "~(columns=['A','B'])")
+ expected = df.loc[:, df.columns.difference(["A", "B"])]
+ tm.assert_frame_equal(result, expected)
+
+ # in
+ result = temp_hdfstore.select("df", "index>df.index[3] & columns in ['A','B']")
+ expected = df.loc[df.index > df.index[3]].reindex(columns=["A", "B"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_frame_select_complex2(tmp_path):
+ pp = tmp_path / "params.hdf"
+ hh = tmp_path / "hist.hdf"
+
+ # use non-trivial selection criteria
+ params = DataFrame({"A": [1, 1, 2, 2, 3]})
+ params.to_hdf(pp, key="df", mode="w", format="table", data_columns=["A"])
+
+ selection = read_hdf(pp, "df", where="A=[2,3]")
+ hist = DataFrame(
+ np.random.default_rng(2).standard_normal((25, 1)),
+ columns=["data"],
+ index=MultiIndex.from_tuples(
+ [(i, j) for i in range(5) for j in range(5)], names=["l1", "l2"]
+ ),
+ )
+
+ hist.to_hdf(hh, key="df", mode="w", format="table")
+
+ expected = read_hdf(hh, "df", where="l1=[2, 3, 4]")
+
+ # scope with list like
+ l0 = selection.index.tolist() # noqa: F841
+ with HDFStore(hh) as store:
+ result = store.select("df", where="l1=l0")
+ tm.assert_frame_equal(result, expected)
+
+ result = read_hdf(hh, "df", where="l1=l0")
+ tm.assert_frame_equal(result, expected)
+
+ # index
+ index = selection.index # noqa: F841
+ result = read_hdf(hh, "df", where="l1=index")
+ tm.assert_frame_equal(result, expected)
+
+ result = read_hdf(hh, "df", where="l1=selection.index")
+ tm.assert_frame_equal(result, expected)
+
+ result = read_hdf(hh, "df", where="l1=selection.index.tolist()")
+ tm.assert_frame_equal(result, expected)
+
+ result = read_hdf(hh, "df", where="l1=list(selection.index)")
+ tm.assert_frame_equal(result, expected)
+
+ # scope with index
+ with HDFStore(hh) as store:
+ result = store.select("df", where="l1=index")
+ tm.assert_frame_equal(result, expected)
+
+ result = store.select("df", where="l1=selection.index")
+ tm.assert_frame_equal(result, expected)
+
+ result = store.select("df", where="l1=selection.index.tolist()")
+ tm.assert_frame_equal(result, expected)
+
+ result = store.select("df", where="l1=list(selection.index)")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_invalid_filtering(temp_hdfstore):
+ # can't use more than one filter (atm)
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+
+ temp_hdfstore.put("df", df, format="table")
+
+ msg = "unable to collapse Joint Filters"
+ # not implemented
+ with pytest.raises(NotImplementedError, match=msg):
+ temp_hdfstore.select("df", "columns=['A'] | columns=['B']")
+
+ # in theory we could deal with this
+ with pytest.raises(NotImplementedError, match=msg):
+ temp_hdfstore.select("df", "columns=['A','B'] & columns=['C']")
+
+
+def test_string_select(temp_hdfstore):
+ # GH 2973
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+
+ # test string ==/!=
+ df["x"] = "none"
+ df.loc[df.index[2:7], "x"] = ""
+
+ temp_hdfstore.append("df", df, data_columns=["x"])
+
+ result = temp_hdfstore.select("df", "x=none")
+ expected = df[df.x == "none"]
+ tm.assert_frame_equal(result, expected)
+
+ result = temp_hdfstore.select("df", "x!=none")
+ expected = df[df.x != "none"]
+ tm.assert_frame_equal(result, expected)
+
+ df2 = df.copy()
+ df2.loc[df2.x == "", "x"] = np.nan
+
+ temp_hdfstore.append("df2", df2, data_columns=["x"])
+ result = temp_hdfstore.select("df2", "x!=none")
+ expected = df2[isna(df2.x)]
+ tm.assert_frame_equal(result, expected)
+
+ # int ==/!=
+ df["int"] = 1
+ df.loc[df.index[2:7], "int"] = 2
+
+ temp_hdfstore.append("df3", df, data_columns=["int"])
+
+ result = temp_hdfstore.select("df3", "int=2")
+ expected = df[df.int == 2]
+ tm.assert_frame_equal(result, expected)
+
+ result = temp_hdfstore.select("df3", "int!=2")
+ expected = df[df.int != 2]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_select_as_multiple(temp_hdfstore):
+ df1 = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B", unit="ns"),
+ )
+ df2 = df1.copy().rename(columns="{}_2".format)
+ df2["foo"] = "bar"
+
+ msg = "keys must be a list/tuple"
+ # no tables stored
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.select_as_multiple(None, where=["A>0", "B>0"], selector="df1")
+
+ temp_hdfstore.append("df1", df1, data_columns=["A", "B"])
+ temp_hdfstore.append("df2", df2)
+
+ # exceptions
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.select_as_multiple(None, where=["A>0", "B>0"], selector="df1")
+
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.select_as_multiple([None], where=["A>0", "B>0"], selector="df1")
+
+ msg = "'No object named df3 in the file'"
+ with pytest.raises(KeyError, match=msg):
+ temp_hdfstore.select_as_multiple(
+ ["df1", "df3"], where=["A>0", "B>0"], selector="df1"
+ )
+
+ with pytest.raises(KeyError, match=msg):
+ temp_hdfstore.select_as_multiple(["df3"], where=["A>0", "B>0"], selector="df1")
+
+ with pytest.raises(KeyError, match="'No object named df4 in the file'"):
+ temp_hdfstore.select_as_multiple(
+ ["df1", "df2"], where=["A>0", "B>0"], selector="df4"
+ )
+
+ # default select
+ result = temp_hdfstore.select("df1", ["A>0", "B>0"])
+ expected = temp_hdfstore.select_as_multiple(
+ ["df1"], where=["A>0", "B>0"], selector="df1"
+ )
+ tm.assert_frame_equal(result, expected)
+ expected = temp_hdfstore.select_as_multiple(
+ "df1", where=["A>0", "B>0"], selector="df1"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # multiple
+ result = temp_hdfstore.select_as_multiple(
+ ["df1", "df2"], where=["A>0", "B>0"], selector="df1"
+ )
+ expected = concat([df1, df2], axis=1)
+ expected = expected[(expected.A > 0) & (expected.B > 0)]
+ tm.assert_frame_equal(result, expected, check_freq=False)
+ # FIXME: 2021-01-20 this is failing with freq None vs 4B on some builds
+
+ # multiple (diff selector)
+ result = temp_hdfstore.select_as_multiple(
+ ["df1", "df2"], where="index>df2.index[4]", selector="df2"
+ )
+ expected = concat([df1, df2], axis=1)
+ expected = expected[5:]
+ tm.assert_frame_equal(result, expected)
+
+ # test exception for diff rows
+ df3 = df1.copy().head(2)
+ temp_hdfstore.append("df3", df3)
+ msg = "all tables must have exactly the same nrows!"
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.select_as_multiple(
+ ["df1", "df3"], where=["A>0", "B>0"], selector="df1"
+ )
+
+
+def test_nan_selection_bug_4858(temp_hdfstore):
+ df = DataFrame({"cols": range(6), "values": range(6)}, dtype="float64")
+ df["cols"] = (df["cols"] + 10).apply(str)
+ df.iloc[0] = np.nan
+
+ expected = DataFrame(
+ {"cols": ["13.0", "14.0", "15.0"], "values": [3.0, 4.0, 5.0]},
+ index=[3, 4, 5],
+ )
+
+ # write w/o the index on that particular column
+ temp_hdfstore.append("df", df, data_columns=True, index=["cols"])
+ result = temp_hdfstore.select("df", where="values>2.0")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_query_with_nested_special_character(temp_hdfstore):
+ df = DataFrame(
+ {
+ "a": ["a", "a", "c", "b", "test & test", "c", "b", "e"],
+ "b": [1, 2, 3, 4, 5, 6, 7, 8],
+ }
+ )
+ expected = df[df.a == "test & test"]
+ temp_hdfstore.append("test", df, format="table", data_columns=True)
+ result = temp_hdfstore.select("test", 'a = "test & test"')
+ tm.assert_frame_equal(expected, result)
+
+
+def test_query_long_float_literal(temp_hdfstore):
+ # GH 14241
+ df = DataFrame({"A": [1000000000.0009, 1000000000.0011, 1000000000.0015]})
+
+ temp_hdfstore.append("test", df, format="table", data_columns=True)
+
+ cutoff = 1000000000.0006
+ result = temp_hdfstore.select("test", f"A < {cutoff:.4f}")
+ assert result.empty
+
+ cutoff = 1000000000.0010
+ result = temp_hdfstore.select("test", f"A > {cutoff:.4f}")
+ expected = df.loc[[1, 2], :]
+ tm.assert_frame_equal(expected, result)
+
+ exact = 1000000000.0011
+ result = temp_hdfstore.select("test", f"A == {exact:.4f}")
+ expected = df.loc[[1], :]
+ tm.assert_frame_equal(expected, result)
+
+
+def test_query_compare_column_type(temp_hdfstore):
+ # GH 15492
+ df = DataFrame(
+ {
+ "date": ["2014-01-01", "2014-01-02"],
+ "real_date": date_range("2014-01-01", periods=2, unit="ns"),
+ "float": [1.1, 1.2],
+ "int": [1, 2],
+ },
+ columns=["date", "real_date", "float", "int"],
+ )
+
+ temp_hdfstore.append("test", df, format="table", data_columns=True)
+
+ ts = Timestamp("2014-01-01") # noqa: F841
+ result = temp_hdfstore.select("test", where="real_date > ts")
+ expected = df.loc[[1], :]
+ tm.assert_frame_equal(expected, result)
+
+ for op in ["<", ">", "=="]:
+ # non strings to string column always fail
+ for v in [2.1, True, Timestamp("2014-01-01"), pd.Timedelta(1, "s")]:
+ query = f"date {op} v"
+ msg = f"Cannot compare {v} of type {type(v)} to string column"
+ with pytest.raises(TypeError, match=msg):
+ temp_hdfstore.select("test", where=query)
+
+ # strings to other columns must be convertible to type
+ v = "a"
+ for col in ["int", "float", "real_date"]:
+ query = f"{col} {op} v"
+ if col == "real_date":
+ msg = 'Given date string "a" not likely a datetime'
+ else:
+ msg = "could not convert string to"
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.select("test", where=query)
+
+ for v, col in zip(["1", "1.1", "2014-01-01"], ["int", "float", "real_date"]):
+ query = f"{col} {op} v"
+ result = temp_hdfstore.select("test", where=query)
+
+ if op == "==":
+ expected = df.loc[[0], :]
+ elif op == ">":
+ expected = df.loc[[1], :]
+ else:
+ expected = df.loc[[], :]
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize("where", ["", (), (None,), [], [None]])
+def test_select_empty_where(temp_hdfstore, where):
+ # GH26610
+
+ df = DataFrame([1, 2, 3])
+ temp_hdfstore.put("df", df, "t")
+ result = read_hdf(temp_hdfstore, "df", where=where)
+ tm.assert_frame_equal(result, df)
+
+
+def test_select_large_integer(temp_hdfstore):
+ df = DataFrame(
+ zip(
+ ["a", "b", "c", "d"],
+ [-9223372036854775801, -9223372036854775802, -9223372036854775803, 123],
+ ),
+ columns=["x", "y"],
+ )
+ temp_hdfstore.append("data", df, data_columns=True, index=False)
+ result = (
+ temp_hdfstore.select("data", where="y==-9223372036854775801").get("y").get(0)
+ )
+ expected = df["y"][0]
+
+ assert expected == result
+
+
+@pytest.mark.parametrize("unit", ["us", "ns", "ms", "s"])
+def test_select_where_datetime_index_with_non_ns_resolution(temp_hdfstore, unit):
+ # GH#64310
+ idx = date_range("2020-01-01", periods=10, freq="h", unit=unit)
+ df = DataFrame({"value": range(10)}, index=idx)
+
+ temp_hdfstore.append("df", df)
+
+ result = temp_hdfstore.select(
+ "df", where="index >= '2020-01-02' & index <= '2020-01-03'"
+ )
+ expected = df[(df.index >= "2020-01-02") & (df.index <= "2020-01-03")]
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_store.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_store.py
new file mode 100644
index 0000000000000000000000000000000000000000..7274acc7b4cd84b312bdbd86beab634fc29679e1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_store.py
@@ -0,0 +1,1102 @@
+import contextlib
+import datetime as dt
+import hashlib
+import tempfile
+import time
+
+import numpy as np
+import pytest
+
+from pandas.compat import PY312
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ concat,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.api.types import (
+ CategoricalDtype,
+)
+from pandas.util.version import Version
+
+from pandas.io.pytables import (
+ HDFStore,
+ read_hdf,
+)
+
+pytestmark = [pytest.mark.single_cpu]
+
+tables = pytest.importorskip("tables")
+
+
+def test_context(temp_h5_path):
+ try:
+ with HDFStore(temp_h5_path) as tbl:
+ raise ValueError("blah")
+ except ValueError:
+ pass
+ with HDFStore(temp_h5_path) as tbl:
+ tbl["a"] = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ assert len(tbl) == 1
+ assert type(tbl["a"]) == DataFrame
+
+
+@pytest.mark.xfail(
+ Version(tables.hdf5_version) >= Version("2"),
+ reason="track_times=False produces non-deterministic files with HDF5 >= 2",
+)
+def test_no_track_times(temp_h5_path):
+ # GH 32682
+ # enables to set track_times (see `pytables` `create_table` documentation)
+
+ def checksum(filename, hash_factory=hashlib.md5, chunk_num_blocks=128):
+ h = hash_factory()
+ with open(filename, "rb") as f:
+ for chunk in iter(lambda: f.read(chunk_num_blocks * h.block_size), b""):
+ h.update(chunk)
+ return h.digest()
+
+ def create_h5_and_return_checksum(temp_h5_path, track_times):
+ df = DataFrame({"a": [1]})
+
+ with HDFStore(temp_h5_path, mode="w") as hdf:
+ hdf.put(
+ "table",
+ df,
+ format="table",
+ data_columns=True,
+ index=None,
+ track_times=track_times,
+ )
+
+ return checksum(temp_h5_path)
+
+ checksum_0_tt_false = create_h5_and_return_checksum(temp_h5_path, track_times=False)
+ checksum_0_tt_true = create_h5_and_return_checksum(temp_h5_path, track_times=True)
+
+ # sleep is necessary to create h5 with different creation time
+ time.sleep(1)
+
+ checksum_1_tt_false = create_h5_and_return_checksum(temp_h5_path, track_times=False)
+ checksum_1_tt_true = create_h5_and_return_checksum(temp_h5_path, track_times=True)
+
+ # checksums are the same if track_time = False
+ assert checksum_0_tt_false == checksum_1_tt_false
+
+ # checksums are NOT same if track_time = True
+ assert checksum_0_tt_true != checksum_1_tt_true
+
+
+def test_iter_empty(temp_hdfstore):
+ # GH 12221
+ assert list(temp_hdfstore) == []
+
+
+def test_repr(temp_hdfstore, performance_warning, using_infer_string):
+ store = temp_hdfstore
+ repr(store)
+ store.info()
+ store["a"] = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ store["b"] = Series(range(10), dtype="float64", index=[f"i_{i}" for i in range(10)])
+ store["c"] = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ df["obj1"] = "foo"
+ df["obj2"] = "bar"
+ df["bool1"] = df["A"] > 0
+ df["bool2"] = df["B"] > 0
+ df["bool3"] = True
+ df["int1"] = 1
+ df["int2"] = 2
+ df["timestamp1"] = Timestamp("20010102")
+ df["timestamp2"] = Timestamp("20010103")
+ df["datetime1"] = dt.datetime(2001, 1, 2, 0, 0)
+ df["datetime2"] = dt.datetime(2001, 1, 3, 0, 0)
+ df.loc[df.index[3:6], ["obj1"]] = np.nan
+ df = df._consolidate()
+
+ warning = None if using_infer_string else performance_warning
+ msg = "cannot\nmap directly to c-types .* dtype='object'"
+ with tm.assert_produces_warning(warning, match=msg):
+ store["df"] = df
+
+ # make a random group in hdf space
+ store._handle.create_group(store._handle.root, "bah")
+
+ assert store.filename in repr(store)
+ assert store.filename in str(store)
+ store.info()
+
+
+def test_repr_get_storer(temp_hdfstore):
+ # storers
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ temp_hdfstore.append("df", df)
+
+ s = temp_hdfstore.get_storer("df")
+ repr(s)
+ str(s)
+
+
+def test_contains(temp_hdfstore):
+ store = temp_hdfstore
+ store["a"] = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ store["b"] = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ store["foo/bar"] = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ assert "a" in store
+ assert "b" in store
+ assert "c" not in store
+ assert "foo/bar" in store
+ assert "/foo/bar" in store
+ assert "/foo/b" not in store
+ assert "bar" not in store
+
+ # gh-2694: tables.NaturalNameWarning
+ with tm.assert_produces_warning(tables.NaturalNameWarning, check_stacklevel=False):
+ store["node())"] = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ assert "node())" in store
+
+
+def test_versioning(temp_hdfstore):
+ store = temp_hdfstore
+ store["a"] = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ store["b"] = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((20, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=20, freq="B"),
+ )
+ store.append("df1", df[:10])
+ store.append("df1", df[10:])
+ assert store.root.a._v_attrs.pandas_version == "0.15.2"
+ assert store.root.b._v_attrs.pandas_version == "0.15.2"
+ assert store.root.df1._v_attrs.pandas_version == "0.15.2"
+
+ # write a file and wipe its versioning
+ store.append("df2", df)
+
+ # this is an error because its table_type is appendable, but no
+ # version info
+ store.get_node("df2")._v_attrs.pandas_version = None
+
+ msg = "'NoneType' object has no attribute 'startswith'"
+
+ with pytest.raises(Exception, match=msg):
+ store.select("df2")
+
+
+@pytest.mark.parametrize(
+ "where, expected",
+ [
+ (
+ "/",
+ {
+ "": ({"first_group", "second_group"}, set()),
+ "/first_group": (set(), {"df1", "df2"}),
+ "/second_group": ({"third_group"}, {"df3", "s1"}),
+ "/second_group/third_group": (set(), {"df4"}),
+ },
+ ),
+ (
+ "/second_group",
+ {
+ "/second_group": ({"third_group"}, {"df3", "s1"}),
+ "/second_group/third_group": (set(), {"df4"}),
+ },
+ ),
+ ],
+)
+def test_walk(where, expected, temp_hdfstore):
+ # GH10143
+ objs = {
+ "df1": DataFrame([1, 2, 3]),
+ "df2": DataFrame([4, 5, 6]),
+ "df3": DataFrame([6, 7, 8]),
+ "df4": DataFrame([9, 10, 11]),
+ "s1": Series([10, 9, 8]),
+ # Next 3 items aren't pandas objects and should be ignored
+ "a1": np.array([[1, 2, 3], [4, 5, 6]]),
+ "tb1": np.array([(1, 2, 3), (4, 5, 6)], dtype="i,i,i"),
+ "tb2": np.array([(7, 8, 9), (10, 11, 12)], dtype="i,i,i"),
+ }
+
+ store = temp_hdfstore
+ store.put("/first_group/df1", objs["df1"])
+ store.put("/first_group/df2", objs["df2"])
+ store.put("/second_group/df3", objs["df3"])
+ store.put("/second_group/s1", objs["s1"])
+ store.put("/second_group/third_group/df4", objs["df4"])
+ # Create non-pandas objects
+ store._handle.create_array("/first_group", "a1", objs["a1"])
+ store._handle.create_table("/first_group", "tb1", obj=objs["tb1"])
+ store._handle.create_table("/second_group", "tb2", obj=objs["tb2"])
+
+ assert len(list(store.walk(where=where))) == len(expected)
+ for path, groups, leaves in store.walk(where=where):
+ assert path in expected
+ expected_groups, expected_frames = expected[path]
+ assert expected_groups == set(groups)
+ assert expected_frames == set(leaves)
+ for leaf in leaves:
+ frame_path = "/".join([path, leaf])
+ obj = store.get(frame_path)
+ if "df" in leaf:
+ tm.assert_frame_equal(obj, objs[leaf])
+ else:
+ tm.assert_series_equal(obj, objs[leaf])
+
+
+def test_getattr(temp_hdfstore):
+ store = temp_hdfstore
+ s = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ store["a"] = s
+
+ # test attribute access
+ result = store.a
+ tm.assert_series_equal(result, s)
+ result = store.a
+ tm.assert_series_equal(result, s)
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ store["df"] = df
+ result = store.df
+ tm.assert_frame_equal(result, df)
+
+ # errors
+ for x in ["d", "mode", "path", "handle", "complib"]:
+ msg = f"'HDFStore' object has no attribute '{x}'"
+ with pytest.raises(AttributeError, match=msg):
+ getattr(store, x)
+
+ # not stores
+ for x in ["mode", "path", "handle", "complib"]:
+ getattr(store, f"_{x}")
+
+
+def test_store_dropna(temp_h5_path):
+ df_with_missing = DataFrame(
+ {"col1": [0.0, np.nan, 2.0], "col2": [1.0, np.nan, np.nan]},
+ index=list("abc"),
+ )
+ df_without_missing = DataFrame(
+ {"col1": [0.0, 2.0], "col2": [1.0, np.nan]}, index=list("ac")
+ )
+
+ # # Test to make sure defaults are to not drop.
+ # # Corresponding to Issue 9382
+ df_with_missing.to_hdf(temp_h5_path, key="df", format="table")
+ reloaded = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df_with_missing, reloaded)
+
+ df_with_missing.to_hdf(temp_h5_path, key="df", format="table", dropna=False)
+ reloaded = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df_with_missing, reloaded)
+
+ df_with_missing.to_hdf(temp_h5_path, key="df", format="table", dropna=True)
+ reloaded = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(df_without_missing, reloaded)
+
+
+def test_to_hdf_with_min_itemsize(temp_h5_path):
+ # min_itemsize in index with to_hdf (GH 10381)
+ df = DataFrame(
+ {
+ "A": [0.0, 1.0, 2.0, 3.0, 4.0],
+ "B": [0.0, 1.0, 0.0, 1.0, 0.0],
+ "C": Index(["foo1", "foo2", "foo3", "foo4", "foo5"]),
+ "D": date_range("20130101", periods=5),
+ }
+ ).set_index("C")
+ df.to_hdf(temp_h5_path, key="ss3", format="table", min_itemsize={"index": 6})
+ # just make sure there is a longer string:
+ df2 = df.copy().reset_index().assign(C="longer").set_index("C")
+ df2.to_hdf(temp_h5_path, key="ss3", append=True, format="table")
+ tm.assert_frame_equal(read_hdf(temp_h5_path, "ss3"), concat([df, df2]))
+
+ # same as above, with a Series
+ df["B"].to_hdf(temp_h5_path, key="ss4", format="table", min_itemsize={"index": 6})
+ df2["B"].to_hdf(temp_h5_path, key="ss4", append=True, format="table")
+ tm.assert_series_equal(read_hdf(temp_h5_path, "ss4"), concat([df["B"], df2["B"]]))
+
+
+@pytest.mark.parametrize("format", ["fixed", "table"])
+def test_to_hdf_errors(format, temp_h5_path, using_infer_string):
+ data = ["\ud800foo"]
+ ser = Series(data, index=Index(data, dtype="object"), dtype="object")
+ # GH 20835
+ ser.to_hdf(temp_h5_path, key="table", format=format, errors="surrogatepass")
+
+ result = read_hdf(temp_h5_path, "table", errors="surrogatepass")
+
+ if using_infer_string:
+ # https://github.com/pandas-dev/pandas/pull/60993
+ # Surrogates fallback to python storage.
+ dtype = pd.StringDtype(storage="python", na_value=np.nan)
+ else:
+ dtype = "object"
+ expected = Series(data, index=Index(data, dtype=dtype), dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_create_table_index(temp_hdfstore):
+ store = temp_hdfstore
+
+ def col(t, column):
+ return getattr(store.get_storer(t).table.cols, column)
+
+ # data columns
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df["string"] = "foo"
+ df["string2"] = "bar"
+ store.append("f", df, data_columns=["string", "string2"])
+ assert col("f", "index").is_indexed is True
+ assert col("f", "string").is_indexed is True
+ assert col("f", "string2").is_indexed is True
+
+ # specify index=columns
+ store.append("f2", df, index=["string"], data_columns=["string", "string2"])
+ assert col("f2", "index").is_indexed is False
+ assert col("f2", "string").is_indexed is True
+ assert col("f2", "string2").is_indexed is False
+
+ # try to index a non-table
+ store.put("f2", df)
+ msg = "cannot create table index on a Fixed format store"
+ with pytest.raises(TypeError, match=msg):
+ store.create_table_index("f2")
+
+
+def test_create_table_index_data_columns_argument(temp_hdfstore):
+ # GH 28156
+
+ store = temp_hdfstore
+
+ def col(t, column):
+ return getattr(store.get_storer(t).table.cols, column)
+
+ # data columns
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df["string"] = "foo"
+ df["string2"] = "bar"
+ store.append("f", df, data_columns=["string"])
+ assert col("f", "index").is_indexed is True
+ assert col("f", "string").is_indexed is True
+
+ msg = "'Cols' object has no attribute 'string2'"
+ with pytest.raises(AttributeError, match=msg):
+ col("f", "string2").is_indexed
+
+ # try to index a col which isn't a data_column
+ msg = (
+ "column string2 is not a data_column.\n"
+ "In order to read column string2 you must reload the dataframe \n"
+ "into HDFStore and include string2 with the data_columns argument."
+ )
+ with pytest.raises(AttributeError, match=msg):
+ store.create_table_index("f", columns=["string2"])
+
+
+def test_mi_data_columns(temp_hdfstore):
+ # GH 14435
+ idx = MultiIndex.from_arrays(
+ [date_range("2000-01-01", periods=5), range(5)], names=["date", "id"]
+ )
+ df = DataFrame({"a": [1.1, 1.2, 1.3, 1.4, 1.5]}, index=idx)
+
+ temp_hdfstore.append("df", df, data_columns=True)
+
+ actual = temp_hdfstore.select("df", where="id == 1")
+ expected = df.iloc[[1], :]
+ tm.assert_frame_equal(actual, expected)
+
+
+def test_table_mixed_dtypes(temp_hdfstore):
+ # frame
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ df["obj1"] = "foo"
+ df["obj2"] = "bar"
+ df["bool1"] = df["A"] > 0
+ df["bool2"] = df["B"] > 0
+ df["bool3"] = True
+ df["int1"] = 1
+ df["int2"] = 2
+ df["timestamp1"] = Timestamp("20010102").as_unit("ns")
+ df["timestamp2"] = Timestamp("20010103").as_unit("ns")
+ df["datetime1"] = Timestamp("20010102").as_unit("ns")
+ df["datetime2"] = Timestamp("20010103").as_unit("ns")
+ df.loc[df.index[3:6], ["obj1"]] = np.nan
+ df = df._consolidate()
+
+ temp_hdfstore.append("df1_mixed", df)
+ tm.assert_frame_equal(temp_hdfstore.select("df1_mixed"), df)
+
+
+def test_calendar_roundtrip_issue(temp_hdfstore):
+ # 8591
+ # doc example from tseries holiday section
+ weekmask_egypt = "Sun Mon Tue Wed Thu"
+ holidays = [
+ "2012-05-01",
+ dt.datetime(2013, 5, 1),
+ np.datetime64("2014-05-01"),
+ ]
+ bday_egypt = pd.offsets.CustomBusinessDay(
+ holidays=holidays, weekmask=weekmask_egypt
+ )
+ mydt = dt.datetime(2013, 4, 30)
+ dts = date_range(mydt, periods=5, freq=bday_egypt)
+
+ s = Series(dts.weekday, dts).map(Series("Mon Tue Wed Thu Fri Sat Sun".split()))
+
+ temp_hdfstore.put("fixed", s)
+ result = temp_hdfstore.select("fixed")
+ tm.assert_series_equal(result, s)
+
+ temp_hdfstore.append("table", s)
+ result = temp_hdfstore.select("table")
+ tm.assert_series_equal(result, s)
+
+
+def test_remove(temp_hdfstore):
+ store = temp_hdfstore
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ store["a"] = ts
+ store["b"] = df
+ store.remove("a")
+ assert len(store) == 1
+ tm.assert_frame_equal(df, store["b"])
+
+ store.remove("b")
+ assert len(store) == 0
+
+ # nonexistence
+ with pytest.raises(
+ KeyError, match="'No object named a_nonexistent_store in the file'"
+ ):
+ store.remove("a_nonexistent_store")
+
+ # pathing
+ store["a"] = ts
+ store["b/foo"] = df
+ store.remove("b/foo")
+ assert len(store) == 1
+
+ store["a"] = ts
+ store["b/foo"] = df
+ store.remove("b")
+ assert len(store) == 1
+
+ # __delitem__
+ store["a"] = ts
+ store["b"] = df
+ del store["a"]
+ del store["b"]
+ assert len(store) == 0
+
+
+def test_same_name_scoping(temp_hdfstore):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((20, 2)),
+ index=date_range("20130101", periods=20, unit="ns"),
+ )
+ temp_hdfstore.put("df", df, format="table")
+ expected = df[df.index > Timestamp("20130105")]
+
+ result = temp_hdfstore.select("df", "index>datetime.datetime(2013,1,5)")
+ tm.assert_frame_equal(result, expected)
+
+ # changes what 'datetime' points to in the namespace where
+ # 'select' does the lookup
+
+ # technically an error, but allow it
+ result = temp_hdfstore.select("df", "index>datetime.datetime(2013,1,5)")
+ tm.assert_frame_equal(result, expected)
+
+ result = temp_hdfstore.select("df", "index>datetime(2013,1,5)")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_store_index_name(temp_hdfstore):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ df.index.name = "foo"
+
+ temp_hdfstore["frame"] = df
+ recons = temp_hdfstore["frame"]
+ tm.assert_frame_equal(recons, df)
+
+
+@pytest.mark.parametrize("tz", [None, "US/Pacific"])
+@pytest.mark.parametrize("table_format", ["table", "fixed"])
+def test_store_index_name_numpy_str(table_format, temp_h5_path, unit, tz):
+ # GH #13492
+ idx = (
+ DatetimeIndex(
+ [dt.date(2000, 1, 1), dt.date(2000, 1, 2)],
+ name="cols\u05d2",
+ )
+ .tz_localize(tz)
+ .as_unit(unit)
+ )
+ idx1 = (
+ DatetimeIndex(
+ [dt.date(2010, 1, 1), dt.date(2010, 1, 2)],
+ name="rows\u05d0",
+ )
+ .as_unit(unit)
+ .tz_localize(tz)
+ )
+ df = DataFrame(np.arange(4).reshape(2, 2), columns=idx, index=idx1)
+
+ # This used to fail, returning numpy strings instead of python strings.
+ df.to_hdf(temp_h5_path, key="df", format=table_format)
+ df2 = read_hdf(temp_h5_path, "df")
+
+ tm.assert_frame_equal(df, df2, check_names=True)
+
+ assert isinstance(df2.index.name, str)
+ assert isinstance(df2.columns.name, str)
+
+
+def test_store_series_name(temp_hdfstore):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ series = df["A"]
+
+ temp_hdfstore["series"] = series
+ recons = temp_hdfstore["series"]
+ tm.assert_series_equal(recons, series)
+
+
+def test_overwrite_node(temp_hdfstore):
+ temp_hdfstore["a"] = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ temp_hdfstore["a"] = ts
+
+ tm.assert_series_equal(temp_hdfstore["a"], ts)
+
+
+def test_coordinates(temp_hdfstore):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+
+ store = temp_hdfstore
+ store.append("df", df)
+
+ # all
+ c = store.select_as_coordinates("df")
+ assert (c.values == np.arange(len(df.index))).all()
+
+
+def test_coordinates2(temp_hdfstore):
+ # get coordinates back & test vs frame
+ store = temp_hdfstore
+
+ df = DataFrame({"A": range(5), "B": range(5)})
+ store.append("df", df)
+ c = store.select_as_coordinates("df", ["index<3"])
+ assert (c.values == np.arange(3)).all()
+ result = store.select("df", where=c)
+ expected = df.loc[0:2, :]
+ tm.assert_frame_equal(result, expected)
+
+ c = store.select_as_coordinates("df", ["index>=3", "index<=4"])
+ assert (c.values == np.arange(2) + 3).all()
+ result = store.select("df", where=c)
+ expected = df.loc[3:4, :]
+ tm.assert_frame_equal(result, expected)
+ assert isinstance(c, Index)
+
+
+def test_coordinates_multiple_tables(temp_hdfstore):
+ store = temp_hdfstore
+ df1 = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df2 = df1.copy().rename(columns="{}_2".format)
+ store.append("df1", df1, data_columns=["A", "B"])
+ store.append("df2", df2)
+
+ c = store.select_as_coordinates("df1", ["A>0", "B>0"])
+ df1_result = store.select("df1", c)
+ df2_result = store.select("df2", c)
+ result = concat([df1_result, df2_result], axis=1)
+
+ expected = concat([df1, df2], axis=1)
+ expected = expected[(expected.A > 0) & (expected.B > 0)]
+ tm.assert_frame_equal(result, expected, check_freq=False)
+ # FIXME: 2021-01-18 on some (mostly windows) builds we get freq=None
+ # but expect freq="18B"
+
+
+def test_coordinates_array_mask(temp_hdfstore):
+ store = temp_hdfstore
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((1000, 2)),
+ index=date_range("20000101", periods=1000),
+ )
+ store.append("df", df)
+ c = store.select_column("df", "index")
+ where = c[DatetimeIndex(c).month == 5].index
+ expected = df.iloc[where]
+
+ # locations
+ result = store.select("df", where=where)
+ tm.assert_frame_equal(result, expected)
+
+ # boolean
+ result = store.select("df", where=where)
+ tm.assert_frame_equal(result, expected)
+
+ # invalid
+ msg = "where must be passed as a string, PyTablesExpr, or list-like of PyTablesExpr"
+ with pytest.raises(TypeError, match=msg):
+ store.select("df", where=np.arange(len(df), dtype="float64"))
+
+ with pytest.raises(TypeError, match=msg):
+ store.select("df", where=np.arange(len(df) + 1))
+
+ with pytest.raises(TypeError, match=msg):
+ store.select("df", where=np.arange(len(df)), start=5)
+
+ with pytest.raises(TypeError, match=msg):
+ store.select("df", where=np.arange(len(df)), start=5, stop=10)
+
+ # selection with filter
+ selection = date_range("20000101", periods=500)
+ result = store.select("df", where="index in selection")
+ expected = df[df.index.isin(selection)]
+ tm.assert_frame_equal(result, expected)
+
+ # list
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ store.append("df2", df)
+ result = store.select("df2", where=[0, 3, 5])
+ expected = df.iloc[[0, 3, 5]]
+ tm.assert_frame_equal(result, expected)
+
+ # boolean
+ where = [True] * 10
+ where[-2] = False
+ result = store.select("df2", where=where)
+ expected = df.loc[where]
+ tm.assert_frame_equal(result, expected)
+
+ # start/stop
+ result = store.select("df2", start=5, stop=10)
+ expected = df[5:10]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_start_stop_table(temp_hdfstore):
+ # table
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).random(20),
+ "B": np.random.default_rng(2).random(20),
+ }
+ )
+ temp_hdfstore.append("df", df)
+
+ result = temp_hdfstore.select("df", "columns=['A']", start=0, stop=5)
+ expected = df.loc[0:4, ["A"]]
+ tm.assert_frame_equal(result, expected)
+
+ # out of range
+ result = temp_hdfstore.select("df", "columns=['A']", start=30, stop=40)
+ assert len(result) == 0
+ expected = df.loc[30:40, ["A"]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_start_stop_multiple(temp_hdfstore):
+ # GH 16209
+ df = DataFrame({"foo": [1, 2], "bar": [1, 2]})
+
+ temp_hdfstore.append_to_multiple(
+ {"selector": ["foo"], "data": None}, df, selector="selector"
+ )
+ result = temp_hdfstore.select_as_multiple(
+ ["selector", "data"], selector="selector", start=0, stop=1
+ )
+ expected = df.loc[[0], ["foo", "bar"]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_start_stop_fixed(temp_hdfstore):
+ # fixed, GH 8287
+ store = temp_hdfstore
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).random(20),
+ "B": np.random.default_rng(2).random(20),
+ },
+ index=date_range("20130101", periods=20),
+ )
+ store.put("df", df)
+
+ result = store.select("df", start=0, stop=5)
+ expected = df.iloc[0:5, :]
+ tm.assert_frame_equal(result, expected)
+
+ result = store.select("df", start=5, stop=10)
+ expected = df.iloc[5:10, :]
+ tm.assert_frame_equal(result, expected)
+
+ # out of range
+ result = store.select("df", start=30, stop=40)
+ expected = df.iloc[30:40, :]
+ tm.assert_frame_equal(result, expected)
+
+ # series
+ s = df.A
+ store.put("s", s)
+ result = store.select("s", start=0, stop=5)
+ expected = s.iloc[0:5]
+ tm.assert_series_equal(result, expected)
+
+ result = store.select("s", start=5, stop=10)
+ expected = s.iloc[5:10]
+ tm.assert_series_equal(result, expected)
+
+ # sparse; not implemented
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ df.iloc[3:5, 1:3] = np.nan
+ df.iloc[8:10, -2] = np.nan
+
+
+def test_select_filter_corner(temp_hdfstore, request):
+ df = DataFrame(np.random.default_rng(2).standard_normal((50, 100)))
+ df.index = [f"{c:3d}" for c in df.index]
+ df.columns = [f"{c:3d}" for c in df.columns]
+
+ temp_hdfstore.put("frame", df, format="table")
+
+ request.applymarker(
+ pytest.mark.xfail(
+ PY312,
+ reason="AST change in PY312",
+ raises=ValueError,
+ )
+ )
+ crit = "columns=df.columns[:75]"
+ result = temp_hdfstore.select("frame", [crit])
+ tm.assert_frame_equal(result, df.loc[:, df.columns[:75]])
+
+ crit = "columns=df.columns[:75:2]"
+ result = temp_hdfstore.select("frame", [crit])
+ tm.assert_frame_equal(result, df.loc[:, df.columns[:75:2]])
+
+
+def test_path_pathlib(temp_h5_path):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ result = tm.round_trip_pathlib(
+ lambda p: df.to_hdf(p, key="df"), lambda p: read_hdf(p, "df"), temp_h5_path
+ )
+ tm.assert_frame_equal(df, result)
+
+
+@pytest.mark.parametrize("start, stop", [(0, 2), (1, 2), (None, None)])
+def test_contiguous_mixed_data_table(start, stop, temp_hdfstore):
+ # GH 17021
+ df = DataFrame(
+ {
+ "a": Series([20111010, 20111011, 20111012]),
+ "b": Series(["ab", "cd", "ab"]),
+ }
+ )
+
+ temp_hdfstore.append("test_dataset", df)
+
+ result = temp_hdfstore.select("test_dataset", start=start, stop=stop)
+ tm.assert_frame_equal(df[start:stop], result)
+
+
+def test_path_pathlib_hdfstore(temp_h5_path):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ def writer(path):
+ with HDFStore(path) as store:
+ df.to_hdf(store, key="df")
+
+ def reader(path):
+ with HDFStore(path) as store:
+ return read_hdf(store, "df")
+
+ result = tm.round_trip_pathlib(writer, reader, temp_h5_path)
+ tm.assert_frame_equal(df, result)
+
+
+def test_pickle_path_localpath(temp_h5_path):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+ result = tm.round_trip_pathlib(
+ lambda p: df.to_hdf(p, key="df"), lambda p: read_hdf(p, "df"), temp_h5_path
+ )
+ tm.assert_frame_equal(df, result)
+
+
+@pytest.mark.parametrize("propindexes", [True, False])
+def test_copy(propindexes, temp_file):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ with HDFStore(temp_file) as st:
+ st.append("df", df, data_columns=["A"])
+ with tempfile.NamedTemporaryFile() as new_f:
+ with HDFStore(temp_file) as store:
+ with contextlib.closing(
+ store.copy(new_f.name, keys=None, propindexes=propindexes)
+ ) as tstore:
+ # check keys
+ keys = store.keys()
+ assert set(keys) == set(tstore.keys())
+ # check indices & nrows
+ for k in tstore.keys():
+ if tstore.get_storer(k).is_table:
+ new_t = tstore.get_storer(k)
+ orig_t = store.get_storer(k)
+
+ assert orig_t.nrows == new_t.nrows
+
+ # check propindixes
+ if propindexes:
+ for a in orig_t.axes:
+ if a.is_indexed:
+ assert new_t[a.name].is_indexed
+
+
+def test_duplicate_column_name(temp_h5_path):
+ df = DataFrame(columns=["a", "a"], data=[[0, 0]])
+
+ msg = "Columns index has to be unique for fixed format"
+ with pytest.raises(ValueError, match=msg):
+ df.to_hdf(temp_h5_path, key="df", format="fixed")
+
+ df.to_hdf(temp_h5_path, key="df", format="table")
+ other = read_hdf(temp_h5_path, "df")
+
+ tm.assert_frame_equal(df, other)
+ assert df.equals(other)
+ assert other.equals(df)
+
+
+def test_preserve_timedeltaindex_type(temp_hdfstore, unit):
+ # GH9635
+ df = DataFrame(np.random.default_rng(2).normal(size=(10, 5)))
+ df.index = timedelta_range(
+ start="0s", periods=10, freq="1s", name="example", unit=unit
+ )
+
+ temp_hdfstore["df"] = df
+ tm.assert_frame_equal(temp_hdfstore["df"], df)
+
+
+def test_columns_multiindex_modified(temp_h5_path):
+ # BUG: 7212
+
+ df = DataFrame(
+ np.random.default_rng(2).random((4, 5)),
+ index=list("abcd"),
+ columns=list("ABCDE"),
+ )
+ df.index.name = "letters"
+ df = df.set_index(keys="E", append=True)
+
+ data_columns = df.index.names + df.columns.tolist()
+ df.to_hdf(
+ temp_h5_path,
+ key="df",
+ mode="a",
+ append=True,
+ data_columns=data_columns,
+ index=False,
+ )
+ cols2load = list("BCD")
+ cols2load_original = list(cols2load)
+ # GH#10055 make sure read_hdf call does not alter cols2load inplace
+ read_hdf(temp_h5_path, "df", columns=cols2load)
+ assert cols2load_original == cols2load
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+@pytest.mark.parametrize(
+ "columns",
+ [
+ Index([0, 1], dtype=np.int64),
+ Index([0.0, 1.0], dtype=np.float64),
+ date_range("2020-01-01", periods=2),
+ timedelta_range("1 day", periods=2),
+ period_range("2020-01-01", periods=2, freq="D"),
+ ],
+)
+def test_to_hdf_with_object_column_names_should_fail(temp_h5_path, columns):
+ # GH9057
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)), columns=columns)
+ msg = "cannot have non-object label DataIndexableCol"
+ with pytest.raises(ValueError, match=msg):
+ df.to_hdf(temp_h5_path, key="df", format="table", data_columns=True)
+
+
+@pytest.mark.parametrize("dtype", [None, "category"])
+def test_to_hdf_with_object_column_names_should_run(temp_h5_path, dtype):
+ # GH9057
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ columns=Index(["a", "b"], dtype=dtype),
+ )
+ df.to_hdf(temp_h5_path, key="df", format="table", data_columns=True)
+ result = read_hdf(temp_h5_path, "df", where=f"index = [{df.index[0]}]")
+ assert len(result)
+
+
+def test_hdfstore_strides(temp_hdfstore):
+ # GH22073
+ df = DataFrame({"a": [1, 2, 3, 4], "b": [5, 6, 7, 8]})
+ temp_hdfstore.put("df", df)
+ assert df["a"].values.strides == temp_hdfstore["df"]["a"].values.strides
+
+
+def test_store_bool_index(temp_h5_path):
+ # GH#48667
+ df = DataFrame([[1]], columns=[True], index=Index([False], dtype="bool"))
+ expected = df.copy()
+
+ # # Test to make sure defaults are to not drop.
+ # # Corresponding to Issue 9382
+ df.to_hdf(temp_h5_path, key="a")
+ result = read_hdf(temp_h5_path, "a")
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize("model", ["name", "longname", "verylongname"])
+def test_select_categorical_string_columns(temp_hdfstore, model):
+ # Corresponding to BUG: 57608
+
+ models = CategoricalDtype(categories=["name", "longname", "verylongname"])
+ df = DataFrame(
+ {"modelId": ["name", "longname", "longname"], "value": [1, 2, 3]}
+ ).astype({"modelId": models, "value": int})
+
+ temp_hdfstore.append("df", df, data_columns=["modelId"])
+
+ result = temp_hdfstore.select("df", "modelId == model")
+ expected = df[df["modelId"] == model]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_to_hdf_multiindex_string_dtype_crash(temp_h5_path):
+ # GH#63412
+ index = MultiIndex.from_tuples([("a", "x"), ("b", "y")], names=["level1", "level2"])
+ df = DataFrame({"value": [1, 2]}, index=index)
+ df.to_hdf(temp_h5_path, key="test")
+ result = read_hdf(temp_h5_path, key="test")
+ tm.assert_frame_equal(df, result, check_dtype=False)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_subclass.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_subclass.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa50575ece840ac227ec7cfae5ba8e6c348827f6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_subclass.py
@@ -0,0 +1,48 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+from pandas.io.pytables import (
+ HDFStore,
+ read_hdf,
+)
+
+pytest.importorskip("tables")
+
+
+class TestHDFStoreSubclass:
+ # GH 33748
+ def test_supported_for_subclass_dataframe(self, temp_h5_path):
+ data = {"a": [1, 2], "b": [3, 4]}
+ sdf = tm.SubclassedDataFrame(data, dtype=np.intp)
+
+ expected = DataFrame(data, dtype=np.intp)
+
+ sdf.to_hdf(temp_h5_path, key="df")
+ result = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(result, expected)
+
+ with HDFStore(temp_h5_path) as store:
+ store.put("df", sdf)
+ result = read_hdf(temp_h5_path, "df")
+ tm.assert_frame_equal(result, expected)
+
+ def test_supported_for_subclass_series(self, temp_h5_path):
+ data = [1, 2, 3]
+ sser = tm.SubclassedSeries(data, dtype=np.intp)
+
+ expected = Series(data, dtype=np.intp)
+
+ sser.to_hdf(temp_h5_path, key="ser")
+ result = read_hdf(temp_h5_path, "ser")
+ tm.assert_series_equal(result, expected)
+
+ with HDFStore(temp_h5_path) as store:
+ store.put("ser", sser)
+ result = read_hdf(temp_h5_path, "ser")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_time_series.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_time_series.py
new file mode 100644
index 0000000000000000000000000000000000000000..42d2f4ddb4db917f4b2da6780b660ee82d5af1ef
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_time_series.py
@@ -0,0 +1,66 @@
+import datetime
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Series,
+ _testing as tm,
+ date_range,
+ period_range,
+)
+
+pytestmark = pytest.mark.single_cpu
+
+
+@pytest.mark.parametrize("unit", ["us", "ns"])
+def test_store_datetime_fractional_secs(temp_hdfstore, unit):
+ dt = datetime.datetime(2012, 1, 2, 3, 4, 5, 123456)
+ dti = DatetimeIndex([dt], dtype=f"M8[{unit}]")
+ series = Series([0], index=dti)
+ temp_hdfstore["a"] = series
+ assert temp_hdfstore["a"].index[0] == dt
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+def test_tseries_indices_series(temp_hdfstore):
+ idx = date_range("2020-01-01", periods=10)
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ temp_hdfstore["a"] = ser
+ result = temp_hdfstore["a"]
+
+ tm.assert_series_equal(result, ser)
+ assert result.index.freq == ser.index.freq
+ tm.assert_class_equal(result.index, ser.index, obj="series index")
+
+ idx = period_range("2020-01-01", periods=10, freq="D")
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ temp_hdfstore["a"] = ser
+ result = temp_hdfstore["a"]
+
+ tm.assert_series_equal(result, ser)
+ assert result.index.freq == ser.index.freq
+ tm.assert_class_equal(result.index, ser.index, obj="series index")
+
+
+@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+def test_tseries_indices_frame(temp_hdfstore):
+ idx = date_range("2020-01-01", periods=10)
+ df = DataFrame(np.random.default_rng(2).standard_normal((len(idx), 3)), index=idx)
+ temp_hdfstore["a"] = df
+ result = temp_hdfstore["a"]
+
+ tm.assert_frame_equal(result, df)
+ assert result.index.freq == df.index.freq
+ tm.assert_class_equal(result.index, df.index, obj="dataframe index")
+
+ idx = period_range("2020-01-01", periods=10, freq="D")
+ df = DataFrame(np.random.default_rng(2).standard_normal((len(idx), 3)), idx)
+ temp_hdfstore["a"] = df
+ result = temp_hdfstore["a"]
+
+ tm.assert_frame_equal(result, df)
+ assert result.index.freq == df.index.freq
+ tm.assert_class_equal(result.index, df.index, obj="dataframe index")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_timezones.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_timezones.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f7b635a5ff633bff75d30fbb3484e623c2740b0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/pytables/test_timezones.py
@@ -0,0 +1,334 @@
+from datetime import (
+ date,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs.timezones import maybe_get_tz
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def _compare_with_tz(a, b):
+ tm.assert_frame_equal(a, b)
+
+ # compare the zones on each element
+ for c in a.columns:
+ for i in a.index:
+ a_e = a.loc[i, c]
+ b_e = b.loc[i, c]
+ if not (a_e == b_e and a_e.tz == b_e.tz):
+ raise AssertionError(f"invalid tz comparison [{a_e}] [{b_e}]")
+
+
+# use maybe_get_tz instead of dateutil.tz.gettz to handle the windows
+# filename issues.
+gettz_dateutil = lambda x: maybe_get_tz("dateutil/" + x)
+gettz_pytz = lambda x: x
+
+
+@pytest.mark.filterwarnings(
+ "ignore:`alltrue` is deprecated as of NumPy 1.25.0:DeprecationWarning"
+)
+@pytest.mark.parametrize("gettz", [gettz_dateutil, gettz_pytz])
+def test_append_with_timezones(temp_hdfstore, gettz):
+ # as columns
+
+ # Single-tzinfo, no DST transition
+ df_est = DataFrame(
+ {
+ "A": [
+ Timestamp("20130102 2:00:00", tz=gettz("US/Eastern")).as_unit("ns")
+ + timedelta(hours=1) * i
+ for i in range(5)
+ ]
+ }
+ )
+
+ # frame with all columns having same tzinfo, but different sides
+ # of DST transition
+ df_crosses_dst = DataFrame(
+ {
+ "A": Timestamp("20130102", tz=gettz("US/Eastern")).as_unit("ns"),
+ "B": Timestamp("20130603", tz=gettz("US/Eastern")).as_unit("ns"),
+ },
+ index=range(5),
+ )
+
+ df_mixed_tz = DataFrame(
+ {
+ "A": Timestamp("20130102", tz=gettz("US/Eastern")).as_unit("ns"),
+ "B": Timestamp("20130102", tz=gettz("EET")).as_unit("ns"),
+ },
+ index=range(5),
+ )
+
+ df_different_tz = DataFrame(
+ {
+ "A": Timestamp("20130102", tz=gettz("US/Eastern")).as_unit("ns"),
+ "B": Timestamp("20130102", tz=gettz("CET")).as_unit("ns"),
+ },
+ index=range(5),
+ )
+
+ temp_hdfstore.append("df_tz", df_est, data_columns=["A"])
+ result = temp_hdfstore["df_tz"]
+ _compare_with_tz(result, df_est)
+ tm.assert_frame_equal(result, df_est)
+
+ # select with tz aware
+ expected = df_est[df_est.A >= df_est.A[3]]
+ result = temp_hdfstore.select("df_tz", where="A>=df_est.A[3]")
+ _compare_with_tz(result, expected)
+
+ # ensure we include dates in DST and STD time here.
+ temp_hdfstore.remove("df_tz")
+ temp_hdfstore.append("df_tz", df_crosses_dst)
+ result = temp_hdfstore["df_tz"]
+ _compare_with_tz(result, df_crosses_dst)
+ tm.assert_frame_equal(result, df_crosses_dst)
+
+ msg = (
+ r"invalid info for \[values_block_1\] for \[tz\], "
+ r"existing_value \[(dateutil/.*)?(US/Eastern|America/New_York)\] "
+ r"conflicts with new value \[(dateutil/.*)?EET\]"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df_tz", df_mixed_tz)
+
+ # this is ok
+ temp_hdfstore.remove("df_tz")
+ temp_hdfstore.append("df_tz", df_mixed_tz, data_columns=["A", "B"])
+ result = temp_hdfstore["df_tz"]
+ _compare_with_tz(result, df_mixed_tz)
+ tm.assert_frame_equal(result, df_mixed_tz)
+
+ # can't append with diff timezone
+ msg = (
+ r"invalid info for \[B\] for \[tz\], "
+ r"existing_value \[(dateutil/.*)?EET\] "
+ r"conflicts with new value \[(dateutil/.*)?CET\]"
+ )
+ with pytest.raises(ValueError, match=msg):
+ temp_hdfstore.append("df_tz", df_different_tz)
+
+
+@pytest.mark.parametrize("gettz", [gettz_dateutil, gettz_pytz])
+def test_append_with_timezones_as_index(temp_hdfstore, gettz):
+ # GH#4098 example
+
+ dti = date_range("2000-1-1", periods=3, freq="h", tz=gettz("US/Eastern"))
+ dti = dti._with_freq(None) # freq doesn't round-trip
+
+ df = DataFrame({"A": Series(range(3), index=dti)})
+
+ temp_hdfstore.put("df", df)
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(result, df)
+
+ temp_hdfstore.remove("df")
+ temp_hdfstore.append("df", df)
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(result, df)
+
+
+def test_roundtrip_tz_aware_index(temp_hdfstore, unit):
+ # GH 17618
+ ts = Timestamp("2000-01-01 01:00:00", tz="US/Eastern")
+ dti = DatetimeIndex([ts]).as_unit(unit)
+ df = DataFrame(data=[0], index=dti)
+
+ temp_hdfstore.put("frame", df, format="fixed")
+ recons = temp_hdfstore["frame"]
+ tm.assert_frame_equal(recons, df)
+
+ value = recons.index[0]._value
+ denom = {"ns": 1, "us": 1000, "ms": 10**6, "s": 10**9}[unit]
+ assert value == 946706400000000000 // denom
+
+
+def test_store_index_name_with_tz(temp_hdfstore):
+ # GH 13884
+ df = DataFrame({"A": [1, 2]})
+ df.index = DatetimeIndex([1234567890123456787, 1234567890123456788])
+ df.index = df.index.tz_localize("UTC")
+ df.index.name = "foo"
+
+ temp_hdfstore.put("frame", df, format="table")
+ recons = temp_hdfstore["frame"]
+ tm.assert_frame_equal(recons, df)
+
+
+def test_tseries_select_index_column(temp_hdfstore):
+ # GH7777
+ # selecting a UTC datetimeindex column did
+ # not preserve UTC tzinfo set before storing
+
+ # check that no tz still works
+ rng = date_range("1/1/2000", "1/30/2000")
+ frame = DataFrame(
+ np.random.default_rng(2).standard_normal((len(rng), 4)), index=rng
+ )
+
+ temp_hdfstore.append("frame", frame)
+ result = temp_hdfstore.select_column("frame", "index")
+ assert rng.tz == DatetimeIndex(result.values).tz
+
+ # check utc
+ rng = date_range("1/1/2000", "1/30/2000", tz="UTC")
+ frame = DataFrame(
+ np.random.default_rng(2).standard_normal((len(rng), 4)), index=rng
+ )
+
+ temp_hdfstore.remove("frame")
+ temp_hdfstore.append("frame", frame)
+ result = temp_hdfstore.select_column("frame", "index")
+ assert rng.tz == result.dt.tz
+
+ # double check non-utc
+ rng = date_range("1/1/2000", "1/30/2000", tz="US/Eastern")
+ frame = DataFrame(
+ np.random.default_rng(2).standard_normal((len(rng), 4)), index=rng
+ )
+
+ temp_hdfstore.remove("frame")
+ temp_hdfstore.append("frame", frame)
+ result = temp_hdfstore.select_column("frame", "index")
+ assert rng.tz == result.dt.tz
+
+
+def test_timezones_fixed_format_frame_non_empty(temp_hdfstore):
+ # index
+ rng = date_range("1/1/2000", "1/30/2000", tz="US/Eastern")
+ rng = rng._with_freq(None) # freq doesn't round-trip
+ df = DataFrame(np.random.default_rng(2).standard_normal((len(rng), 4)), index=rng)
+ temp_hdfstore["df"] = df
+ result = temp_hdfstore["df"]
+ tm.assert_frame_equal(result, df)
+
+
+def test_timezones_fixed_format_frame_non_empty_as_data(temp_hdfstore):
+ # GH11411
+ rng = date_range("1/1/2000", "1/30/2000", tz="US/Eastern")
+ rng = rng._with_freq(None) # freq doesn't round-trip
+ df = DataFrame(
+ {
+ "A": rng,
+ "B": rng.tz_convert("UTC").tz_localize(None),
+ "C": rng.tz_convert("CET"),
+ "D": range(len(rng)),
+ },
+ index=rng,
+ )
+ temp_hdfstore["df"] = df
+ result = temp_hdfstore["df"]
+ tm.assert_frame_equal(result, df)
+
+
+def test_timezones_fixed_format_empty(temp_hdfstore, tz_aware_fixture, frame_or_series):
+ # GH 20594
+
+ dtype = pd.DatetimeTZDtype(tz=tz_aware_fixture)
+
+ obj = Series(dtype=dtype, name="A")
+ if frame_or_series is DataFrame:
+ obj = obj.to_frame()
+
+ temp_hdfstore["obj"] = obj
+ result = temp_hdfstore["obj"]
+ tm.assert_equal(result, obj)
+
+
+def test_timezones_fixed_format_series_nonempty(temp_hdfstore, tz_aware_fixture):
+ # GH 20594
+
+ dtype = pd.DatetimeTZDtype(tz=tz_aware_fixture)
+
+ s = Series([0], dtype=dtype)
+ temp_hdfstore["s"] = s
+ result = temp_hdfstore["s"]
+ tm.assert_series_equal(result, s)
+
+
+def test_fixed_offset_tz(temp_hdfstore):
+ rng = date_range("1/1/2000 00:00:00-07:00", "1/30/2000 00:00:00-07:00")
+ frame = DataFrame(
+ np.random.default_rng(2).standard_normal((len(rng), 4)), index=rng
+ )
+
+ temp_hdfstore["frame"] = frame
+ recons = temp_hdfstore["frame"]
+ tm.assert_index_equal(recons.index, rng)
+ assert rng.tz == recons.index.tz
+
+
+@td.skip_if_windows
+def test_store_timezone(temp_hdfstore):
+ # GH2852
+ # issue storing datetime.date with a timezone as it resets when read
+ # back in a new timezone
+
+ # original method
+ today = date(2013, 9, 10)
+ df = DataFrame([1, 2, 3], index=[today, today, today])
+ temp_hdfstore["obj1"] = df
+ result = temp_hdfstore["obj1"]
+ tm.assert_frame_equal(result, df)
+
+ # with tz setting
+ with tm.set_timezone("EST5EDT"):
+ today = date(2013, 9, 10)
+ df = DataFrame([1, 2, 3], index=[today, today, today])
+ temp_hdfstore["obj2"] = df
+
+ with tm.set_timezone("CST6CDT"):
+ result = temp_hdfstore["obj2"]
+
+ tm.assert_frame_equal(result, df)
+
+
+def test_dst_transitions(temp_hdfstore):
+ # make sure we are not failing on transitions
+ times = date_range(
+ "2013-10-26 23:00",
+ "2013-10-27 01:00",
+ tz="Europe/London",
+ freq="h",
+ ambiguous="infer",
+ )
+ times = times._with_freq(None) # freq doesn't round-trip
+
+ for i in [times, times + pd.Timedelta("10min")]:
+ df = DataFrame({"A": range(len(i)), "B": i}, index=i)
+ temp_hdfstore.append("df", df)
+ result = temp_hdfstore.select("df")
+ tm.assert_frame_equal(result, df)
+ temp_hdfstore.remove("df")
+
+
+@pytest.mark.filterwarnings(
+ "ignore:`alltrue` is deprecated as of NumPy 1.25.0:DeprecationWarning"
+)
+def test_read_with_where_tz_aware_index(temp_hdfstore):
+ # GH 11926
+ periods = 10
+ dts = date_range("20151201", periods=periods, freq="D", tz="UTC", unit="ns")
+ mi = pd.MultiIndex.from_arrays([dts, range(periods)], names=["DATE", "NO"])
+ expected = DataFrame({"MYCOL": 0}, index=mi)
+
+ key = "mykey"
+ with pd.HDFStore(temp_hdfstore) as store:
+ store.append(key, expected, format="table", append=True)
+ result = pd.read_hdf(temp_hdfstore, key, where="DATE > 20151130")
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_byteswap.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_byteswap.py
new file mode 100644
index 0000000000000000000000000000000000000000..78f0d2232c4ba08b27ea294cc60136acb0890e56
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_byteswap.py
@@ -0,0 +1,57 @@
+from hypothesis import (
+ assume,
+ example,
+ given,
+ strategies as st,
+)
+import numpy as np
+import pytest
+
+from pandas._libs.byteswap import (
+ read_double_with_byteswap,
+ read_float_with_byteswap,
+ read_uint16_with_byteswap,
+ read_uint32_with_byteswap,
+ read_uint64_with_byteswap,
+)
+
+import pandas._testing as tm
+
+
+@pytest.mark.slow
+@given(read_offset=st.integers(0, 11), number=st.integers(min_value=0))
+@example(number=2**16, read_offset=0)
+@example(number=2**32, read_offset=0)
+@example(number=2**64, read_offset=0)
+@pytest.mark.parametrize("int_type", [np.uint16, np.uint32, np.uint64])
+@pytest.mark.parametrize("should_byteswap", [True, False])
+def test_int_byteswap(read_offset, number, int_type, should_byteswap):
+ assume(number < 2 ** (8 * int_type(0).itemsize))
+ _test(number, int_type, read_offset, should_byteswap)
+
+
+@pytest.mark.slow
+@pytest.mark.filterwarnings("ignore:overflow encountered:RuntimeWarning")
+@given(read_offset=st.integers(0, 11), number=st.floats())
+@pytest.mark.parametrize("float_type", [np.float32, np.float64])
+@pytest.mark.parametrize("should_byteswap", [True, False])
+def test_float_byteswap(read_offset, number, float_type, should_byteswap):
+ _test(number, float_type, read_offset, should_byteswap)
+
+
+def _test(number, number_type, read_offset, should_byteswap):
+ number = number_type(number)
+ data = np.random.default_rng(2).integers(0, 256, size=20, dtype="uint8")
+ data[read_offset : read_offset + number.itemsize] = number[None].view("uint8")
+ swap_func = {
+ np.float32: read_float_with_byteswap,
+ np.float64: read_double_with_byteswap,
+ np.uint16: read_uint16_with_byteswap,
+ np.uint32: read_uint32_with_byteswap,
+ np.uint64: read_uint64_with_byteswap,
+ }[type(number)]
+ output_number = number_type(swap_func(bytes(data), read_offset, should_byteswap))
+ if should_byteswap:
+ tm.assert_equal(output_number, number.byteswap())
+ else:
+ tm.assert_equal(output_number, number)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_sas.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_sas.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea072b6e9c4e01822176e883a2cf6c31bb96ca83
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_sas.py
@@ -0,0 +1,33 @@
+from io import StringIO
+
+import pytest
+
+from pandas import read_sas
+import pandas._testing as tm
+
+
+class TestSas:
+ def test_sas_buffer_format(self):
+ # see gh-14947
+ b = StringIO("")
+
+ msg = (
+ "If this is a buffer object rather than a string "
+ "name, you must specify a format string"
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_sas(b)
+
+ def test_sas_read_no_format_or_extension(self, temp_file):
+ # see gh-24548
+ msg = "unable to infer format of SAS file.+"
+ with pytest.raises(ValueError, match=msg):
+ read_sas(temp_file)
+
+
+def test_sas_archive(datapath):
+ fname_uncompressed = datapath("io", "sas", "data", "airline.sas7bdat")
+ df_uncompressed = read_sas(fname_uncompressed)
+ fname_compressed = datapath("io", "sas", "data", "airline.sas7bdat.gz")
+ df_compressed = read_sas(fname_compressed, format="sas7bdat")
+ tm.assert_frame_equal(df_uncompressed, df_compressed)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_sas7bdat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_sas7bdat.py
new file mode 100644
index 0000000000000000000000000000000000000000..a17cd27f8284e527de8b8017776e4a5134e31f7e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_sas7bdat.py
@@ -0,0 +1,416 @@
+import contextlib
+from datetime import datetime
+import io
+import os
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+from pandas.compat._constants import (
+ IS64,
+ WASM,
+)
+from pandas.errors import EmptyDataError
+
+import pandas as pd
+import pandas._testing as tm
+
+from pandas.io.sas.sas7bdat import SAS7BDATReader
+
+
+@pytest.fixture
+def dirpath(datapath):
+ return datapath("io", "sas", "data")
+
+
+@pytest.fixture(params=[(1, range(1, 16)), (2, [16])])
+def data_test_ix(request, dirpath):
+ i, test_ix = request.param
+ fname = os.path.join(dirpath, f"test_sas7bdat_{i}.csv")
+ df = pd.read_csv(fname)
+ epoch = datetime(1960, 1, 1)
+ t1 = pd.to_timedelta(df["Column4"], unit="D")
+ df["Column4"] = (epoch + t1).astype("M8[s]")
+ t2 = pd.to_timedelta(df["Column12"], unit="D")
+ df["Column12"] = (epoch + t2).astype("M8[s]")
+ for k in range(df.shape[1]):
+ col = df.iloc[:, k]
+ if col.dtype == np.int64:
+ df.isetitem(k, df.iloc[:, k].astype(np.float64))
+ return df, test_ix
+
+
+# https://github.com/cython/cython/issues/1720
+class TestSAS7BDAT:
+ @pytest.mark.slow
+ def test_from_file(self, dirpath, data_test_ix):
+ expected, test_ix = data_test_ix
+ for k in test_ix:
+ fname = os.path.join(dirpath, f"test{k}.sas7bdat")
+ df = pd.read_sas(fname, encoding="utf-8")
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.slow
+ def test_from_buffer(self, dirpath, data_test_ix):
+ expected, test_ix = data_test_ix
+ for k in test_ix:
+ fname = os.path.join(dirpath, f"test{k}.sas7bdat")
+ with open(fname, "rb") as f:
+ byts = f.read()
+ buf = io.BytesIO(byts)
+ with pd.read_sas(
+ buf, format="sas7bdat", iterator=True, encoding="utf-8"
+ ) as rdr:
+ df = rdr.read()
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.slow
+ def test_from_iterator(self, dirpath, data_test_ix):
+ expected, test_ix = data_test_ix
+ for k in test_ix:
+ fname = os.path.join(dirpath, f"test{k}.sas7bdat")
+ with pd.read_sas(fname, iterator=True, encoding="utf-8") as rdr:
+ df = rdr.read(2)
+ tm.assert_frame_equal(df, expected.iloc[0:2, :])
+ df = rdr.read(3)
+ tm.assert_frame_equal(df, expected.iloc[2:5, :])
+
+ @pytest.mark.slow
+ def test_path_pathlib(self, dirpath, data_test_ix):
+ expected, test_ix = data_test_ix
+ for k in test_ix:
+ fname = Path(os.path.join(dirpath, f"test{k}.sas7bdat"))
+ df = pd.read_sas(fname, encoding="utf-8")
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("chunksize", (3, 5, 10, 11))
+ @pytest.mark.parametrize("k", range(1, 17))
+ def test_iterator_loop(self, dirpath, k, chunksize):
+ # github #13654
+ fname = os.path.join(dirpath, f"test{k}.sas7bdat")
+ with pd.read_sas(fname, chunksize=chunksize, encoding="utf-8") as rdr:
+ y = 0
+ for x in rdr:
+ y += x.shape[0]
+ assert y == rdr.row_count
+
+ def test_iterator_read_too_much(self, dirpath):
+ # github #14734
+ fname = os.path.join(dirpath, "test1.sas7bdat")
+ with pd.read_sas(
+ fname, format="sas7bdat", iterator=True, encoding="utf-8"
+ ) as rdr:
+ d1 = rdr.read(rdr.row_count + 20)
+
+ with pd.read_sas(fname, iterator=True, encoding="utf-8") as rdr:
+ d2 = rdr.read(rdr.row_count + 20)
+ tm.assert_frame_equal(d1, d2)
+
+
+def test_encoding_options(datapath):
+ fname = datapath("io", "sas", "data", "test1.sas7bdat")
+ df1 = pd.read_sas(fname)
+ df2 = pd.read_sas(fname, encoding="utf-8")
+ for col in df1.columns:
+ try:
+ df1[col] = df1[col].str.decode("utf-8")
+ except AttributeError:
+ pass
+ tm.assert_frame_equal(df1, df2)
+
+ with contextlib.closing(SAS7BDATReader(fname, convert_header_text=False)) as rdr:
+ df3 = rdr.read()
+ for x, y in zip(df1.columns, df3.columns):
+ assert x == y.decode()
+
+
+def test_encoding_infer(datapath):
+ fname = datapath("io", "sas", "data", "test1.sas7bdat")
+
+ with pd.read_sas(fname, encoding="infer", iterator=True) as df1_reader:
+ # check: is encoding inferred correctly from file
+ assert df1_reader.inferred_encoding == "cp1252"
+ df1 = df1_reader.read()
+
+ with pd.read_sas(fname, encoding="cp1252", iterator=True) as df2_reader:
+ df2 = df2_reader.read()
+
+ # check: reader reads correct information
+ tm.assert_frame_equal(df1, df2)
+
+
+def test_productsales(datapath):
+ fname = datapath("io", "sas", "data", "productsales.sas7bdat")
+ df = pd.read_sas(fname, encoding="utf-8")
+ fname = datapath("io", "sas", "data", "productsales.csv")
+ df0 = pd.read_csv(fname, parse_dates=["MONTH"])
+ vn = ["ACTUAL", "PREDICT", "QUARTER", "YEAR"]
+ df0[vn] = df0[vn].astype(np.float64)
+
+ df0["MONTH"] = df0["MONTH"].astype("M8[s]")
+ tm.assert_frame_equal(df, df0)
+
+
+def test_12659(datapath):
+ fname = datapath("io", "sas", "data", "test_12659.sas7bdat")
+ df = pd.read_sas(fname)
+ fname = datapath("io", "sas", "data", "test_12659.csv")
+ df0 = pd.read_csv(fname)
+ df0 = df0.astype(np.float64)
+ tm.assert_frame_equal(df, df0)
+
+
+def test_airline(datapath):
+ fname = datapath("io", "sas", "data", "airline.sas7bdat")
+ df = pd.read_sas(fname)
+ fname = datapath("io", "sas", "data", "airline.csv")
+ df0 = pd.read_csv(fname)
+ df0 = df0.astype(np.float64)
+ tm.assert_frame_equal(df, df0)
+
+
+@pytest.mark.skipif(WASM, reason="Pyodide/WASM has 32-bitness")
+def test_date_time(datapath):
+ # Support of different SAS date/datetime formats (PR #15871)
+ fname = datapath("io", "sas", "data", "datetime.sas7bdat")
+ df = pd.read_sas(fname)
+ fname = datapath("io", "sas", "data", "datetime.csv")
+ df0 = pd.read_csv(
+ fname, parse_dates=["Date1", "Date2", "DateTime", "DateTimeHi", "Taiw"]
+ )
+ # GH 19732: Timestamps imported from sas will incur floating point errors
+ # See GH#56014 for discussion of the correct "expected" results
+ # We are really just testing that we are "close". This only seems to be
+ # an issue near the implementation bounds.
+
+ df[df.columns[3]] = df.iloc[:, 3].dt.round("us")
+ df0["Date1"] = df0["Date1"].astype("M8[s]")
+ df0["Date2"] = df0["Date2"].astype("M8[s]")
+ df0["DateTime"] = df0["DateTime"].astype("M8[ms]")
+ df0["Taiw"] = df0["Taiw"].astype("M8[s]")
+
+ res = df0["DateTimeHi"].astype("M8[us]").dt.round("ms")
+ df0["DateTimeHi"] = res.astype("M8[ms]")
+
+ if not IS64:
+ # No good reason for this, just what we get on the CI
+ df0.loc[0, "DateTimeHi"] += np.timedelta64(1, "ms")
+ df0.loc[[2, 3], "DateTimeHi"] -= np.timedelta64(1, "ms")
+ tm.assert_frame_equal(df, df0)
+
+
+@pytest.mark.parametrize("column", ["WGT", "CYL"])
+def test_compact_numerical_values(datapath, column):
+ # Regression test for #21616
+ fname = datapath("io", "sas", "data", "cars.sas7bdat")
+ df = pd.read_sas(fname, encoding="latin-1")
+ # The two columns CYL and WGT in cars.sas7bdat have column
+ # width < 8 and only contain integral values.
+ # Test that pandas doesn't corrupt the numbers by adding
+ # decimals.
+ result = df[column]
+ expected = df[column].round()
+ tm.assert_series_equal(result, expected, check_exact=True)
+
+
+def test_many_columns(datapath):
+ # Test for looking for column information in more places (PR #22628)
+ fname = datapath("io", "sas", "data", "many_columns.sas7bdat")
+
+ df = pd.read_sas(fname, encoding="latin-1")
+
+ fname = datapath("io", "sas", "data", "many_columns.csv")
+ df0 = pd.read_csv(fname, encoding="latin-1")
+ tm.assert_frame_equal(df, df0)
+
+
+def test_inconsistent_number_of_rows(datapath):
+ # Regression test for issue #16615. (PR #22628)
+ fname = datapath("io", "sas", "data", "load_log.sas7bdat")
+ df = pd.read_sas(fname, encoding="latin-1")
+ assert len(df) == 2097
+
+
+def test_zero_variables(datapath):
+ # Check if the SAS file has zero variables (PR #18184)
+ fname = datapath("io", "sas", "data", "zero_variables.sas7bdat")
+ with pytest.raises(EmptyDataError, match="No columns to parse from file"):
+ pd.read_sas(fname)
+
+
+@pytest.mark.parametrize("encoding", [None, "utf8"])
+def test_zero_rows(datapath, encoding):
+ # GH 18198
+ fname = datapath("io", "sas", "data", "zero_rows.sas7bdat")
+ result = pd.read_sas(fname, encoding=encoding)
+ str_value = b"a" if encoding is None else "a"
+ expected = pd.DataFrame([{"char_field": str_value, "num_field": 1.0}]).iloc[:0]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_corrupt_read(datapath):
+ # We don't really care about the exact failure, the important thing is
+ # that the resource should be cleaned up afterwards (BUG #35566)
+ fname = datapath("io", "sas", "data", "corrupt.sas7bdat")
+ msg = "'SAS7BDATReader' object has no attribute 'row_count'"
+ with pytest.raises(AttributeError, match=msg):
+ pd.read_sas(fname)
+
+
+@pytest.mark.xfail(WASM, reason="failing with currently set tolerances on WASM")
+def test_max_sas_date(datapath):
+ # GH 20927
+ # NB. max datetime in SAS dataset is 31DEC9999:23:59:59.999
+ # but this is read as 29DEC9999:23:59:59.998993 by a buggy
+ # sas7bdat module
+ # See also GH#56014 for discussion of the correct "expected" results.
+ fname = datapath("io", "sas", "data", "max_sas_date.sas7bdat")
+ df = pd.read_sas(fname, encoding="iso-8859-1")
+
+ expected = pd.DataFrame(
+ {
+ "text": ["max", "normal"],
+ "dt_as_float": [253717747199.999, 1880323199.999],
+ "dt_as_dt": np.array(
+ [
+ datetime(9999, 12, 29, 23, 59, 59, 999000),
+ datetime(2019, 8, 1, 23, 59, 59, 999000),
+ ],
+ dtype="M8[ms]",
+ ),
+ "date_as_float": [2936547.0, 21762.0],
+ "date_as_date": np.array(
+ [
+ datetime(9999, 12, 29),
+ datetime(2019, 8, 1),
+ ],
+ dtype="M8[s]",
+ ),
+ },
+ columns=["text", "dt_as_float", "dt_as_dt", "date_as_float", "date_as_date"],
+ )
+
+ if not IS64:
+ # No good reason for this, just what we get on the CI
+ expected.loc[:, "dt_as_dt"] -= np.timedelta64(1, "ms")
+
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.xfail(WASM, reason="failing with currently set tolerances on WASM")
+def test_max_sas_date_iterator(datapath):
+ # GH 20927
+ # when called as an iterator, only those chunks with a date > pd.Timestamp.max
+ # are returned as datetime.datetime, if this happens that whole chunk is returned
+ # as datetime.datetime
+ col_order = ["text", "dt_as_float", "dt_as_dt", "date_as_float", "date_as_date"]
+ fname = datapath("io", "sas", "data", "max_sas_date.sas7bdat")
+ results = []
+ for df in pd.read_sas(fname, encoding="iso-8859-1", chunksize=1):
+ # GH 19732: Timestamps imported from sas will incur floating point errors
+ df.reset_index(inplace=True, drop=True)
+ results.append(df)
+ expected = [
+ pd.DataFrame(
+ {
+ "text": ["max"],
+ "dt_as_float": [253717747199.999],
+ "dt_as_dt": np.array(
+ [datetime(9999, 12, 29, 23, 59, 59, 999000)], dtype="M8[ms]"
+ ),
+ "date_as_float": [2936547.0],
+ "date_as_date": np.array([datetime(9999, 12, 29)], dtype="M8[s]"),
+ },
+ columns=col_order,
+ ),
+ pd.DataFrame(
+ {
+ "text": ["normal"],
+ "dt_as_float": [1880323199.999],
+ "dt_as_dt": np.array(["2019-08-01 23:59:59.999"], dtype="M8[ms]"),
+ "date_as_float": [21762.0],
+ "date_as_date": np.array(["2019-08-01"], dtype="M8[s]"),
+ },
+ columns=col_order,
+ ),
+ ]
+ if not IS64:
+ # No good reason for this, just what we get on the CI
+ expected[0].loc[0, "dt_as_dt"] -= np.timedelta64(1, "ms")
+ expected[1].loc[0, "dt_as_dt"] -= np.timedelta64(1, "ms")
+
+ tm.assert_frame_equal(results[0], expected[0])
+ tm.assert_frame_equal(results[1], expected[1])
+
+
+@pytest.mark.skipif(WASM, reason="Pyodide/WASM has 32-bitness")
+def test_null_date(datapath):
+ fname = datapath("io", "sas", "data", "dates_null.sas7bdat")
+ df = pd.read_sas(fname, encoding="utf-8")
+
+ expected = pd.DataFrame(
+ {
+ "datecol": np.array(
+ [
+ datetime(9999, 12, 29),
+ np.datetime64("NaT"),
+ ],
+ dtype="M8[s]",
+ ),
+ "datetimecol": np.array(
+ [
+ datetime(9999, 12, 29, 23, 59, 59, 999000),
+ np.datetime64("NaT"),
+ ],
+ dtype="M8[ms]",
+ ),
+ },
+ )
+ if not IS64:
+ # No good reason for this, just what we get on the CI
+ expected.loc[0, "datetimecol"] -= np.timedelta64(1, "ms")
+ tm.assert_frame_equal(df, expected)
+
+
+def test_meta2_page(datapath):
+ # GH 35545
+ fname = datapath("io", "sas", "data", "test_meta2_page.sas7bdat")
+ df = pd.read_sas(fname)
+ assert len(df) == 1000
+
+
+@pytest.mark.parametrize(
+ "test_file, override_offset, override_value, expected_msg",
+ [
+ ("test2.sas7bdat", 0x10000 + 55229, 0x80 | 0x0F, "Out of bounds"),
+ ("test2.sas7bdat", 0x10000 + 55229, 0x10, "unknown control byte"),
+ ("test3.sas7bdat", 118170, 184, "Out of bounds"),
+ ],
+)
+def test_rle_rdc_exceptions(
+ datapath, test_file, override_offset, override_value, expected_msg
+):
+ """Errors in RLE/RDC decompression should propagate."""
+ with open(datapath("io", "sas", "data", test_file), "rb") as fd:
+ data = bytearray(fd.read())
+ data[override_offset] = override_value
+ with pytest.raises(Exception, match=expected_msg):
+ pd.read_sas(io.BytesIO(data), format="sas7bdat")
+
+
+def test_0x40_control_byte(datapath):
+ # GH 31243
+ fname = datapath("io", "sas", "data", "0x40controlbyte.sas7bdat")
+ df = pd.read_sas(fname, encoding="ascii")
+ fname = datapath("io", "sas", "data", "0x40controlbyte.csv")
+ df0 = pd.read_csv(fname, dtype="str")
+ tm.assert_frame_equal(df, df0)
+
+
+def test_0x00_control_byte(datapath):
+ # GH 47099
+ fname = datapath("io", "sas", "data", "0x00controlbyte.sas7bdat.bz2")
+ df = next(pd.read_sas(fname, chunksize=11_000))
+ assert df.shape == (11_000, 20)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_xport.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_xport.py
new file mode 100644
index 0000000000000000000000000000000000000000..30ca70d7e000963207695da02490c003f61f7d28
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/sas/test_xport.py
@@ -0,0 +1,153 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+from pandas.io.sas.sasreader import read_sas
+
+# CSV versions of test xpt files were obtained using the R foreign library
+
+# Numbers in a SAS xport file are always float64, so need to convert
+# before making comparisons.
+
+
+def numeric_as_float(data):
+ for v in data.columns:
+ if data[v].dtype is np.dtype("int64"):
+ data[v] = data[v].astype(np.float64)
+
+
+class TestXport:
+ @pytest.mark.slow
+ def test1_basic(self, datapath):
+ # Tests with DEMO_G.xpt (all numeric file)
+
+ # Compare to this
+ file01 = datapath("io", "sas", "data", "DEMO_G.xpt")
+ data_csv = pd.read_csv(file01.replace(".xpt", ".csv"))
+ numeric_as_float(data_csv)
+
+ # Read full file
+ data = read_sas(file01, format="xport")
+ tm.assert_frame_equal(data, data_csv)
+ num_rows = data.shape[0]
+
+ # Test reading beyond end of file
+ with read_sas(file01, format="xport", iterator=True) as reader:
+ data = reader.read(num_rows + 100)
+ assert data.shape[0] == num_rows
+
+ # Test incremental read with `read` method.
+ with read_sas(file01, format="xport", iterator=True) as reader:
+ data = reader.read(10)
+ tm.assert_frame_equal(data, data_csv.iloc[0:10, :])
+
+ # Test incremental read with `get_chunk` method.
+ with read_sas(file01, format="xport", chunksize=10) as reader:
+ data = reader.get_chunk()
+ tm.assert_frame_equal(data, data_csv.iloc[0:10, :])
+
+ # Test read in loop
+ m = 0
+ with read_sas(file01, format="xport", chunksize=100) as reader:
+ for x in reader:
+ m += x.shape[0]
+ assert m == num_rows
+
+ # Read full file with `read_sas` method
+ data = read_sas(file01)
+ tm.assert_frame_equal(data, data_csv)
+
+ def test1_index(self, datapath):
+ # Tests with DEMO_G.xpt using index (all numeric file)
+
+ # Compare to this
+ file01 = datapath("io", "sas", "data", "DEMO_G.xpt")
+ data_csv = pd.read_csv(file01.replace(".xpt", ".csv"))
+ data_csv = data_csv.set_index("SEQN")
+ numeric_as_float(data_csv)
+
+ # Read full file
+ data = read_sas(file01, index="SEQN", format="xport")
+ tm.assert_frame_equal(data, data_csv, check_index_type=False)
+
+ # Test incremental read with `read` method.
+ with read_sas(file01, index="SEQN", format="xport", iterator=True) as reader:
+ data = reader.read(10)
+ tm.assert_frame_equal(data, data_csv.iloc[0:10, :], check_index_type=False)
+
+ # Test incremental read with `get_chunk` method.
+ with read_sas(file01, index="SEQN", format="xport", chunksize=10) as reader:
+ data = reader.get_chunk()
+ tm.assert_frame_equal(data, data_csv.iloc[0:10, :], check_index_type=False)
+
+ def test1_incremental(self, datapath):
+ # Test with DEMO_G.xpt, reading full file incrementally
+
+ file01 = datapath("io", "sas", "data", "DEMO_G.xpt")
+ data_csv = pd.read_csv(file01.replace(".xpt", ".csv"))
+ data_csv = data_csv.set_index("SEQN")
+ numeric_as_float(data_csv)
+
+ with read_sas(file01, index="SEQN", chunksize=1000) as reader:
+ all_data = list(reader)
+ data = pd.concat(all_data, axis=0)
+
+ tm.assert_frame_equal(data, data_csv, check_index_type=False)
+
+ def test2(self, datapath):
+ # Test with SSHSV1_A.xpt
+
+ file02 = datapath("io", "sas", "data", "SSHSV1_A.xpt")
+ # Compare to this
+ data_csv = pd.read_csv(file02.replace(".xpt", ".csv"))
+ numeric_as_float(data_csv)
+
+ data = read_sas(file02)
+ tm.assert_frame_equal(data, data_csv)
+
+ def test2_binary(self, datapath):
+ # Test with SSHSV1_A.xpt, read as a binary file
+
+ # Compare to this
+ file02 = datapath("io", "sas", "data", "SSHSV1_A.xpt")
+ data_csv = pd.read_csv(file02.replace(".xpt", ".csv"))
+ numeric_as_float(data_csv)
+
+ with open(file02, "rb") as fd:
+ # GH#35693 ensure that if we pass an open file, we
+ # dont incorrectly close it in read_sas
+ data = read_sas(fd, format="xport")
+
+ tm.assert_frame_equal(data, data_csv)
+
+ def test_multiple_types(self, datapath):
+ # Test with DRXFCD_G.xpt (contains text and numeric variables)
+
+ # Compare to this
+ file03 = datapath("io", "sas", "data", "DRXFCD_G.xpt")
+ data_csv = pd.read_csv(file03.replace(".xpt", ".csv"))
+
+ data = read_sas(file03, encoding="utf-8")
+ tm.assert_frame_equal(data, data_csv)
+
+ def test_truncated_float_support(self, datapath):
+ # Test with paxraw_d_short.xpt, a shortened version of:
+ # http://wwwn.cdc.gov/Nchs/Nhanes/2005-2006/PAXRAW_D.ZIP
+ # This file has truncated floats (5 bytes in this case).
+
+ # GH 11713
+ file04 = datapath("io", "sas", "data", "paxraw_d_short.xpt")
+ data_csv = pd.read_csv(file04.replace(".xpt", ".csv"))
+
+ data = read_sas(file04, format="xport")
+ tm.assert_frame_equal(data.astype("int64"), data_csv)
+
+ def test_cport_header_found_raises(self, datapath):
+ # Test with DEMO_PUF.cpt, the beginning of puf2019_1_fall.xpt
+ # from https://www.cms.gov/files/zip/puf2019.zip
+ # (despite the extension, it's a cpt file)
+ msg = "Header record indicates a CPORT file, which is not readable."
+ with pytest.raises(ValueError, match=msg):
+ read_sas(datapath("io", "sas", "data", "DEMO_PUF.cpt"), format="xport")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_clipboard.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_clipboard.py
new file mode 100644
index 0000000000000000000000000000000000000000..25834c47c09c67c3feadd7817a4902e6bdeff378
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_clipboard.py
@@ -0,0 +1,402 @@
+from textwrap import dedent
+
+import numpy as np
+import pytest
+
+from pandas.errors import (
+ PyperclipException,
+ PyperclipWindowsException,
+)
+
+import pandas as pd
+from pandas import (
+ NA,
+ DataFrame,
+ Series,
+ get_option,
+ read_clipboard,
+)
+import pandas._testing as tm
+
+from pandas.io.clipboard import (
+ CheckedCall,
+ _stringifyText,
+ init_qt_clipboard,
+)
+
+
+def build_kwargs(sep, excel):
+ kwargs = {}
+ if excel != "default":
+ kwargs["excel"] = excel
+ if sep != "default":
+ kwargs["sep"] = sep
+ return kwargs
+
+
+@pytest.fixture(
+ params=[
+ "delims",
+ "utf8",
+ "utf16",
+ "string",
+ "long",
+ "nonascii",
+ "colwidth",
+ "mixed",
+ "float",
+ "int",
+ ]
+)
+def df(request):
+ data_type = request.param
+
+ if data_type == "delims":
+ return DataFrame({"a": ['"a,\t"b|c', "d\tef`"], "b": ["hi'j", "k''lm"]})
+ elif data_type == "utf8":
+ return DataFrame({"a": ["µasd", "Ωœ∑`"], "b": ["øπ∆˚¬", "œ∑`®"]})
+ elif data_type == "utf16":
+ return DataFrame(
+ {"a": ["\U0001f44d\U0001f44d", "\U0001f44d\U0001f44d"], "b": ["abc", "def"]}
+ )
+ elif data_type == "string":
+ return DataFrame(
+ np.array([f"i-{i}" for i in range(15)]).reshape(5, 3), columns=list("abc")
+ )
+ elif data_type == "long":
+ max_rows = get_option("display.max_rows")
+ return DataFrame(
+ np.random.default_rng(2).integers(0, 10, size=(max_rows + 1, 3)),
+ columns=list("abc"),
+ )
+ elif data_type == "nonascii":
+ return DataFrame({"en": "in English".split(), "es": "en español".split()})
+ elif data_type == "colwidth":
+ _cw = get_option("display.max_colwidth") + 1
+ return DataFrame(
+ np.array(["x" * _cw for _ in range(15)]).reshape(5, 3), columns=list("abc")
+ )
+ elif data_type == "mixed":
+ return DataFrame(
+ {
+ "a": np.arange(1.0, 6.0) + 0.01,
+ "b": np.arange(1, 6).astype(np.int64),
+ "c": list("abcde"),
+ }
+ )
+ elif data_type == "float":
+ return DataFrame(np.random.default_rng(2).random((5, 3)), columns=list("abc"))
+ elif data_type == "int":
+ return DataFrame(
+ np.random.default_rng(2).integers(0, 10, (5, 3)), columns=list("abc")
+ )
+ else:
+ raise ValueError
+
+
+@pytest.fixture
+def mock_ctypes(monkeypatch):
+ """
+ Mocks WinError to help with testing the clipboard.
+ """
+
+ def _mock_win_error():
+ return "Window Error"
+
+ # Set raising to False because WinError won't exist on non-windows platforms
+ with monkeypatch.context() as m:
+ m.setattr("ctypes.WinError", _mock_win_error, raising=False)
+ yield
+
+
+@pytest.mark.usefixtures("mock_ctypes")
+def test_checked_call_with_bad_call(monkeypatch):
+ """
+ Give CheckCall a function that returns a falsey value and
+ mock get_errno so it returns false so an exception is raised.
+ """
+
+ def _return_false():
+ return False
+
+ monkeypatch.setattr("pandas.io.clipboard.get_errno", lambda: True)
+ msg = f"Error calling {_return_false.__name__} \\(Window Error\\)"
+
+ with pytest.raises(PyperclipWindowsException, match=msg):
+ CheckedCall(_return_false)()
+
+
+@pytest.mark.usefixtures("mock_ctypes")
+def test_checked_call_with_valid_call(monkeypatch):
+ """
+ Give CheckCall a function that returns a truthy value and
+ mock get_errno so it returns true so an exception is not raised.
+ The function should return the results from _return_true.
+ """
+
+ def _return_true():
+ return True
+
+ monkeypatch.setattr("pandas.io.clipboard.get_errno", lambda: False)
+
+ # Give CheckedCall a callable that returns a truthy value s
+ checked_call = CheckedCall(_return_true)
+ assert checked_call() is True
+
+
+@pytest.mark.parametrize(
+ "text",
+ [
+ "String_test",
+ True,
+ 1,
+ 1.0,
+ 1j,
+ ],
+)
+def test_stringify_text(text):
+ valid_types = (str, int, float, bool)
+
+ if isinstance(text, valid_types):
+ result = _stringifyText(text)
+ assert result == str(text)
+ else:
+ msg = (
+ "only str, int, float, and bool values "
+ f"can be copied to the clipboard, not {type(text).__name__}"
+ )
+ with pytest.raises(PyperclipException, match=msg):
+ _stringifyText(text)
+
+
+@pytest.fixture
+def set_pyqt_clipboard(monkeypatch):
+ qt_cut, qt_paste = init_qt_clipboard()
+ with monkeypatch.context() as m:
+ m.setattr(pd.io.clipboard, "clipboard_set", qt_cut)
+ m.setattr(pd.io.clipboard, "clipboard_get", qt_paste)
+ yield
+
+
+@pytest.fixture
+def clipboard(qapp):
+ clip = qapp.clipboard()
+ yield clip
+ clip.clear()
+
+
+@pytest.mark.single_cpu
+@pytest.mark.clipboard
+@pytest.mark.usefixtures("set_pyqt_clipboard")
+@pytest.mark.usefixtures("clipboard")
+class TestClipboard:
+ # Test that default arguments copy as tab delimited
+ # Test that explicit delimiters are respected
+ @pytest.mark.parametrize("sep", [None, "\t", ",", "|"])
+ @pytest.mark.parametrize("encoding", [None, "UTF-8", "utf-8", "utf8"])
+ def test_round_trip_frame_sep(self, df, sep, encoding):
+ df.to_clipboard(excel=None, sep=sep, encoding=encoding)
+ result = read_clipboard(sep=sep or "\t", index_col=0, encoding=encoding)
+ tm.assert_frame_equal(df, result)
+
+ # Test white space separator
+ def test_round_trip_frame_string(self, df):
+ df.to_clipboard(excel=False, sep=None)
+ result = read_clipboard()
+ assert df.to_string() == result.to_string()
+ assert df.shape == result.shape
+
+ # Two character separator is not supported in to_clipboard
+ # Test that multi-character separators are not silently passed
+ def test_excel_sep_warning(self, df):
+ with tm.assert_produces_warning(
+ UserWarning,
+ match="to_clipboard in excel mode requires a single character separator.",
+ check_stacklevel=False,
+ ):
+ df.to_clipboard(excel=True, sep=r"\t")
+
+ # Separator is ignored when excel=False and should produce a warning
+ def test_copy_delim_warning(self, df):
+ with tm.assert_produces_warning(UserWarning, match="ignores the sep argument"):
+ df.to_clipboard(excel=False, sep="\t")
+
+ # Tests that the default behavior of to_clipboard is tab
+ # delimited and excel="True"
+ @pytest.mark.parametrize("sep", ["\t", None, "default"])
+ @pytest.mark.parametrize("excel", [True, None, "default"])
+ def test_clipboard_copy_tabs_default(self, sep, excel, df, clipboard):
+ kwargs = build_kwargs(sep, excel)
+ df.to_clipboard(**kwargs)
+ assert clipboard.text() == df.to_csv(sep="\t")
+
+ # Tests reading of white space separated tables
+ @pytest.mark.parametrize("sep", [None, "default"])
+ def test_clipboard_copy_strings(self, sep, df):
+ kwargs = build_kwargs(sep, False)
+ df.to_clipboard(**kwargs)
+ result = read_clipboard(sep=r"\s+")
+ assert result.to_string() == df.to_string()
+ assert df.shape == result.shape
+
+ def test_read_clipboard_infer_excel(self, clipboard):
+ # gh-19010: avoid warnings
+ clip_kwargs = {"engine": "python"}
+
+ text = dedent(
+ """
+ John James\tCharlie Mingus
+ 1\t2
+ 4\tHarry Carney
+ """.strip()
+ )
+ clipboard.setText(text)
+ df = read_clipboard(**clip_kwargs)
+
+ # excel data is parsed correctly
+ assert df.iloc[1, 1] == "Harry Carney"
+
+ # having diff tab counts doesn't trigger it
+ text = dedent(
+ """
+ a\t b
+ 1 2
+ 3 4
+ """.strip()
+ )
+ clipboard.setText(text)
+ res = read_clipboard(**clip_kwargs)
+
+ text = dedent(
+ """
+ a b
+ 1 2
+ 3 4
+ """.strip()
+ )
+ clipboard.setText(text)
+ exp = read_clipboard(**clip_kwargs)
+
+ tm.assert_frame_equal(res, exp)
+
+ def test_infer_excel_with_nulls(self, clipboard):
+ # GH41108
+ text = "col1\tcol2\n1\tred\n\tblue\n2\tgreen"
+
+ clipboard.setText(text)
+ df = read_clipboard()
+ df_expected = DataFrame(
+ data={"col1": [1, None, 2], "col2": ["red", "blue", "green"]}
+ )
+
+ # excel data is parsed correctly
+ tm.assert_frame_equal(df, df_expected)
+
+ @pytest.mark.parametrize(
+ "multiindex",
+ [
+ ( # Can't use `dedent` here as it will remove the leading `\t`
+ "\n".join(
+ [
+ "\t\t\tcol1\tcol2",
+ "A\t0\tTrue\t1\tred",
+ "A\t1\tTrue\t\tblue",
+ "B\t0\tFalse\t2\tgreen",
+ ]
+ ),
+ [["A", "A", "B"], [0, 1, 0], [True, True, False]],
+ ),
+ (
+ "\n".join(
+ ["\t\tcol1\tcol2", "A\t0\t1\tred", "A\t1\t\tblue", "B\t0\t2\tgreen"]
+ ),
+ [["A", "A", "B"], [0, 1, 0]],
+ ),
+ ],
+ )
+ def test_infer_excel_with_multiindex(self, clipboard, multiindex):
+ # GH41108
+
+ clipboard.setText(multiindex[0])
+ df = read_clipboard()
+ df_expected = DataFrame(
+ data={"col1": [1, None, 2], "col2": ["red", "blue", "green"]},
+ index=multiindex[1],
+ )
+
+ # excel data is parsed correctly
+ tm.assert_frame_equal(df, df_expected)
+
+ def test_invalid_encoding(self, df):
+ msg = "clipboard only supports utf-8 encoding"
+ # test case for testing invalid encoding
+ with pytest.raises(ValueError, match=msg):
+ df.to_clipboard(encoding="ascii")
+ with pytest.raises(NotImplementedError, match=msg):
+ read_clipboard(encoding="ascii")
+
+ @pytest.mark.parametrize("data", ["\U0001f44d...", "Ωœ∑`...", "abcd..."])
+ def test_raw_roundtrip(self, data):
+ # PR #25040 wide unicode wasn't copied correctly on PY3 on windows
+ df = DataFrame({"data": [data]})
+ df.to_clipboard()
+ result = read_clipboard()
+ tm.assert_frame_equal(df, result)
+
+ @pytest.mark.parametrize("engine", ["c", "python"])
+ def test_read_clipboard_dtype_backend(
+ self, clipboard, string_storage, dtype_backend, engine, using_infer_string
+ ):
+ # GH#50502
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ string_dtype = pd.ArrowDtype(pa.string())
+ else:
+ string_dtype = pd.StringDtype(string_storage)
+
+ text = """a,b,c,d,e,f,g,h,i
+x,1,4.0,x,2,4.0,,True,False
+y,2,5.0,,,,,False,"""
+ clipboard.setText(text)
+
+ with pd.option_context("mode.string_storage", string_storage):
+ result = read_clipboard(sep=",", dtype_backend=dtype_backend, engine=engine)
+
+ expected = DataFrame(
+ {
+ "a": Series(["x", "y"], dtype=string_dtype),
+ "b": Series([1, 2], dtype="Int64"),
+ "c": Series([4.0, 5.0], dtype="Float64"),
+ "d": Series(["x", None], dtype=string_dtype),
+ "e": Series([2, NA], dtype="Int64"),
+ "f": Series([4.0, NA], dtype="Float64"),
+ "g": Series([NA, NA], dtype="Int64"),
+ "h": Series([True, False], dtype="boolean"),
+ "i": Series([False, NA], dtype="boolean"),
+ }
+ )
+ if dtype_backend == "pyarrow":
+ from pandas.arrays import ArrowExtensionArray
+
+ expected = DataFrame(
+ {
+ col: ArrowExtensionArray(pa.array(expected[col], from_pandas=True))
+ for col in expected.columns
+ }
+ )
+ expected["g"] = ArrowExtensionArray(pa.array([None, None]))
+
+ if using_infer_string:
+ expected.columns = expected.columns.astype(
+ pd.StringDtype(string_storage, na_value=np.nan)
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_invalid_dtype_backend(self):
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_clipboard(dtype_backend="numpy")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5081109d2299799f9c982d8117f65157049d3ad
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_common.py
@@ -0,0 +1,688 @@
+"""
+Tests for the pandas.io.common functionalities
+"""
+
+import codecs
+import errno
+from functools import partial
+from io import (
+ BytesIO,
+ StringIO,
+ UnsupportedOperation,
+)
+import mmap
+import os
+from pathlib import Path
+import pickle
+import tempfile
+
+import numpy as np
+import pytest
+
+from pandas.compat import (
+ WASM,
+ is_platform_windows,
+)
+from pandas.compat.pyarrow import pa_version_under19p0
+import pandas.util._test_decorators as td
+
+import pandas as pd
+import pandas._testing as tm
+
+import pandas.io.common as icom
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+class CustomFSPath:
+ """For testing fspath on unknown objects"""
+
+ def __init__(self, path) -> None:
+ self.path = path
+
+ def __fspath__(self):
+ return self.path
+
+
+HERE = os.path.abspath(os.path.dirname(__file__))
+
+
+# https://github.com/cython/cython/issues/1720
+class TestCommonIOCapabilities:
+ data1 = """index,A,B,C,D
+foo,2,3,4,5
+bar,7,8,9,10
+baz,12,13,14,15
+qux,12,13,14,15
+foo2,12,13,14,15
+bar2,12,13,14,15
+"""
+
+ def test_expand_user(self):
+ filename = "~/sometest"
+ expanded_name = icom._expand_user(filename)
+
+ assert expanded_name != filename
+ assert os.path.isabs(expanded_name)
+ assert os.path.expanduser(filename) == expanded_name
+
+ def test_expand_user_normal_path(self):
+ filename = "/somefolder/sometest"
+ expanded_name = icom._expand_user(filename)
+
+ assert expanded_name == filename
+ assert os.path.expanduser(filename) == expanded_name
+
+ def test_stringify_path_pathlib(self):
+ rel_path = icom.stringify_path(Path("."))
+ assert rel_path == "."
+ redundant_path = icom.stringify_path(Path("foo//bar"))
+ assert redundant_path == os.path.join("foo", "bar")
+
+ def test_stringify_path_fspath(self):
+ p = CustomFSPath("foo/bar.csv")
+ result = icom.stringify_path(p)
+ assert result == "foo/bar.csv"
+
+ def test_stringify_file_and_path_like(self, temp_file):
+ # GH 38125: do not stringify file objects that are also path-like
+ fsspec = pytest.importorskip("fsspec")
+ with fsspec.open(f"file://{temp_file}", mode="wb") as fsspec_obj:
+ assert fsspec_obj == icom.stringify_path(fsspec_obj)
+
+ @pytest.mark.parametrize("path_type", [str, CustomFSPath, Path])
+ def test_infer_compression_from_path(self, compression_format, path_type):
+ extension, expected = compression_format
+ path = path_type("foo/bar.csv" + extension)
+ compression = icom.infer_compression(path, compression="infer")
+ assert compression == expected
+
+ @pytest.mark.parametrize("path_type", [str, CustomFSPath, Path])
+ def test_get_handle_with_path(self, path_type):
+ with tempfile.TemporaryDirectory(dir=Path.home()) as tmp:
+ filename = path_type("~/" + Path(tmp).name + "/sometest")
+ with icom.get_handle(filename, "w") as handles:
+ assert Path(handles.handle.name).is_absolute()
+ assert os.path.expanduser(filename) == handles.handle.name
+
+ def test_get_handle_with_buffer(self):
+ with StringIO() as input_buffer:
+ with icom.get_handle(input_buffer, "r") as handles:
+ assert handles.handle == input_buffer
+ assert not input_buffer.closed
+ assert input_buffer.closed
+
+ # Test that BytesIOWrapper(get_handle) returns correct amount of bytes every time
+ def test_bytesiowrapper_returns_correct_bytes(self):
+ # Test latin1, ucs-2, and ucs-4 chars
+ data = """a,b,c
+1,2,3
+©,®,®
+Look,a snake,🐍"""
+ with icom.get_handle(StringIO(data), "rb", is_text=False) as handles:
+ result = b""
+ chunksize = 5
+ while True:
+ chunk = handles.handle.read(chunksize)
+ # Make sure each chunk is correct amount of bytes
+ assert len(chunk) <= chunksize
+ if len(chunk) < chunksize:
+ # Can be less amount of bytes, but only at EOF
+ # which happens when read returns empty
+ assert len(handles.handle.read()) == 0
+ result += chunk
+ break
+ result += chunk
+ assert result == data.encode("utf-8")
+
+ # Test that pyarrow can handle a file opened with get_handle
+ def test_get_handle_pyarrow_compat(sel, using_infer_string):
+ pa_csv = pytest.importorskip("pyarrow.csv")
+
+ # Test latin1, ucs-2, and ucs-4 chars
+ data = """a,b,c
+1,2,3
+©,®,®
+Look,a snake,🐍"""
+ expected = pd.DataFrame(
+ {"a": ["1", "©", "Look"], "b": ["2", "®", "a snake"], "c": ["3", "®", "🐍"]}
+ )
+ s = StringIO(data)
+ with icom.get_handle(s, "rb", is_text=False) as handles:
+ df = pa_csv.read_csv(handles.handle).to_pandas()
+ if pa_version_under19p0:
+ expected = expected.astype("object")
+ elif not using_infer_string:
+ expected = expected.astype(pd.StringDtype(na_value=np.nan))
+ tm.assert_frame_equal(df, expected)
+ assert not s.closed
+
+ def test_iterator(self):
+ with pd.read_csv(StringIO(self.data1), chunksize=1) as reader:
+ result = pd.concat(reader, ignore_index=True)
+ expected = pd.read_csv(StringIO(self.data1))
+ tm.assert_frame_equal(result, expected)
+
+ # GH12153
+ with pd.read_csv(StringIO(self.data1), chunksize=1) as it:
+ first = next(it)
+ tm.assert_frame_equal(first, expected.iloc[[0]])
+ tm.assert_frame_equal(pd.concat(it), expected.iloc[1:])
+
+ @pytest.mark.skipif(WASM, reason="limited file system access on WASM")
+ @pytest.mark.parametrize(
+ "reader, module, error_class, fn_ext",
+ [
+ (pd.read_csv, "os", FileNotFoundError, "csv"),
+ (pd.read_fwf, "os", FileNotFoundError, "txt"),
+ (pd.read_excel, "xlrd", FileNotFoundError, "xlsx"),
+ (pd.read_feather, "pyarrow", OSError, "feather"),
+ (pd.read_hdf, "tables", FileNotFoundError, "h5"),
+ (pd.read_stata, "os", FileNotFoundError, "dta"),
+ (pd.read_sas, "os", FileNotFoundError, "sas7bdat"),
+ (pd.read_json, "os", FileNotFoundError, "json"),
+ (pd.read_pickle, "os", FileNotFoundError, "pickle"),
+ ],
+ )
+ def test_read_non_existent(self, reader, module, error_class, fn_ext):
+ pytest.importorskip(module)
+
+ path = os.path.join(HERE, "data", "does_not_exist." + fn_ext)
+ msg1 = rf"File (b')?.+does_not_exist\.{fn_ext}'? does not exist"
+ msg2 = rf"\[Errno 2\] No such file or directory: '.+does_not_exist\.{fn_ext}'"
+ msg3 = "Expected object or value"
+ msg4 = "path_or_buf needs to be a string file path or file-like"
+ msg5 = (
+ rf"\[Errno 2\] File .+does_not_exist\.{fn_ext} does not exist: "
+ rf"'.+does_not_exist\.{fn_ext}'"
+ )
+ msg6 = rf"\[Errno 2\] 没有那个文件或目录: '.+does_not_exist\.{fn_ext}'"
+ msg7 = (
+ rf"\[Errno 2\] File o directory non esistente: '.+does_not_exist\.{fn_ext}'"
+ )
+ msg8 = rf"Failed to open local file.+does_not_exist\.{fn_ext}"
+
+ with pytest.raises(
+ error_class,
+ match=rf"({msg1}|{msg2}|{msg3}|{msg4}|{msg5}|{msg6}|{msg7}|{msg8})",
+ ):
+ reader(path)
+
+ @pytest.mark.parametrize(
+ "method, module, error_class, fn_ext",
+ [
+ (pd.DataFrame.to_csv, "os", OSError, "csv"),
+ (pd.DataFrame.to_html, "os", OSError, "html"),
+ (pd.DataFrame.to_excel, "xlrd", OSError, "xlsx"),
+ (pd.DataFrame.to_feather, "pyarrow", OSError, "feather"),
+ (pd.DataFrame.to_parquet, "pyarrow", OSError, "parquet"),
+ (pd.DataFrame.to_stata, "os", OSError, "dta"),
+ (pd.DataFrame.to_json, "os", OSError, "json"),
+ (pd.DataFrame.to_pickle, "os", OSError, "pickle"),
+ ],
+ )
+ # NOTE: Missing parent directory for pd.DataFrame.to_hdf is handled by PyTables
+ def test_write_missing_parent_directory(self, method, module, error_class, fn_ext):
+ pytest.importorskip(module)
+
+ dummy_frame = pd.DataFrame({"a": [1, 2, 3], "b": [2, 3, 4], "c": [3, 4, 5]})
+
+ path = os.path.join(HERE, "data", "missing_folder", "does_not_exist." + fn_ext)
+
+ with pytest.raises(
+ error_class,
+ match=r"Cannot save file into a non-existent directory: .*missing_folder",
+ ):
+ method(dummy_frame, path)
+
+ @pytest.mark.skipif(WASM, reason="limited file system access on WASM")
+ @pytest.mark.parametrize(
+ "reader, module, error_class, fn_ext",
+ [
+ (pd.read_csv, "os", FileNotFoundError, "csv"),
+ (pd.read_table, "os", FileNotFoundError, "csv"),
+ (pd.read_fwf, "os", FileNotFoundError, "txt"),
+ (pd.read_excel, "xlrd", FileNotFoundError, "xlsx"),
+ (pd.read_feather, "pyarrow", OSError, "feather"),
+ (pd.read_hdf, "tables", FileNotFoundError, "h5"),
+ (pd.read_stata, "os", FileNotFoundError, "dta"),
+ (pd.read_sas, "os", FileNotFoundError, "sas7bdat"),
+ (pd.read_json, "os", FileNotFoundError, "json"),
+ (pd.read_pickle, "os", FileNotFoundError, "pickle"),
+ ],
+ )
+ def test_read_expands_user_home_dir(
+ self, reader, module, error_class, fn_ext, monkeypatch
+ ):
+ pytest.importorskip(module)
+
+ path = os.path.join("~", "does_not_exist." + fn_ext)
+ monkeypatch.setattr(icom, "_expand_user", lambda x: os.path.join("foo", x))
+
+ msg1 = rf"File (b')?.+does_not_exist\.{fn_ext}'? does not exist"
+ msg2 = rf"\[Errno 2\] No such file or directory: '.+does_not_exist\.{fn_ext}'"
+ msg3 = "Unexpected character found when decoding 'false'"
+ msg4 = "path_or_buf needs to be a string file path or file-like"
+ msg5 = (
+ rf"\[Errno 2\] File .+does_not_exist\.{fn_ext} does not exist: "
+ rf"'.+does_not_exist\.{fn_ext}'"
+ )
+ msg6 = rf"\[Errno 2\] 没有那个文件或目录: '.+does_not_exist\.{fn_ext}'"
+ msg7 = (
+ rf"\[Errno 2\] File o directory non esistente: '.+does_not_exist\.{fn_ext}'"
+ )
+ msg8 = rf"Failed to open local file.+does_not_exist\.{fn_ext}"
+
+ with pytest.raises(
+ error_class,
+ match=rf"({msg1}|{msg2}|{msg3}|{msg4}|{msg5}|{msg6}|{msg7}|{msg8})",
+ ):
+ reader(path)
+
+ @pytest.mark.parametrize(
+ "reader, module, path",
+ [
+ (pd.read_csv, "os", ("io", "data", "csv", "iris.csv")),
+ (pd.read_table, "os", ("io", "data", "csv", "iris.csv")),
+ (
+ pd.read_fwf,
+ "os",
+ ("io", "data", "fixed_width", "fixed_width_format.txt"),
+ ),
+ (pd.read_excel, "xlrd", ("io", "data", "excel", "test1.xlsx")),
+ (
+ pd.read_feather,
+ "pyarrow",
+ ("io", "data", "feather", "feather-0_3_1.feather"),
+ ),
+ (
+ pd.read_hdf,
+ "tables",
+ ("io", "data", "legacy_hdf", "pytables_native2.h5"),
+ ),
+ (pd.read_stata, "os", ("io", "data", "stata", "stata10_115.dta")),
+ (pd.read_sas, "os", ("io", "sas", "data", "test1.sas7bdat")),
+ (pd.read_json, "os", ("io", "json", "data", "tsframe_v012.json")),
+ (
+ pd.read_pickle,
+ "os",
+ ("io", "data", "pickle", "categorical.0.25.0.pickle"),
+ ),
+ ],
+ )
+ def test_read_fspath_all(self, reader, module, path, datapath):
+ pytest.importorskip(module)
+ path = datapath(*path)
+
+ mypath = CustomFSPath(path)
+ result = reader(mypath)
+ expected = reader(path)
+
+ if path.endswith(".pickle"):
+ # categorical
+ tm.assert_categorical_equal(result, expected)
+ else:
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "writer_name, writer_kwargs, module",
+ [
+ ("to_csv", {}, "os"),
+ ("to_excel", {"engine": "openpyxl"}, "openpyxl"),
+ ("to_feather", {}, "pyarrow"),
+ ("to_html", {}, "os"),
+ ("to_json", {}, "os"),
+ ("to_latex", {}, "os"),
+ ("to_pickle", {}, "os"),
+ ("to_stata", {"time_stamp": pd.to_datetime("2019-01-01 00:00")}, "os"),
+ ],
+ )
+ def test_write_fspath_all(self, writer_name, writer_kwargs, module, tmp_path):
+ if writer_name in ["to_latex"]: # uses Styler implementation
+ pytest.importorskip("jinja2")
+ string = str(tmp_path / "string")
+ fspath = str(tmp_path / "fspath")
+ df = pd.DataFrame({"A": [1, 2]})
+
+ pytest.importorskip(module)
+ mypath = CustomFSPath(fspath)
+ writer = getattr(df, writer_name)
+
+ writer(string, **writer_kwargs)
+ writer(mypath, **writer_kwargs)
+ with open(string, "rb") as f_str, open(fspath, "rb") as f_path:
+ if writer_name == "to_excel":
+ # binary representation of excel contains time creation
+ # data that causes flaky CI failures
+ result = pd.read_excel(f_str, **writer_kwargs)
+ expected = pd.read_excel(f_path, **writer_kwargs)
+ tm.assert_frame_equal(result, expected)
+ else:
+ result = f_str.read()
+ expected = f_path.read()
+ assert result == expected
+
+ def test_write_fspath_hdf5(self, tmp_path):
+ # Same test as write_fspath_all, except HDF5 files aren't
+ # necessarily byte-for-byte identical for a given dataframe, so we'll
+ # have to read and compare equality
+ pytest.importorskip("tables")
+
+ df = pd.DataFrame({"A": [1, 2]})
+ string = str(tmp_path / "string")
+ fspath = str(tmp_path / "fspath")
+
+ mypath = CustomFSPath(fspath)
+ df.to_hdf(mypath, key="bar")
+ df.to_hdf(string, key="bar")
+
+ result = pd.read_hdf(fspath, key="bar")
+ expected = pd.read_hdf(string, key="bar")
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.fixture
+def mmap_file(datapath):
+ return datapath("io", "data", "csv", "test_mmap.csv")
+
+
+class TestMMapWrapper:
+ @pytest.mark.skipif(WASM, reason="limited file system access on WASM")
+ def test_constructor_bad_file(self, mmap_file):
+ non_file = StringIO("I am not a file")
+ non_file.fileno = lambda: -1
+
+ # the error raised is different on Windows
+ if is_platform_windows():
+ msg = "The parameter is incorrect"
+ err = OSError
+ else:
+ msg = "[Errno 22]"
+ err = mmap.error
+
+ with pytest.raises(err, match=msg):
+ icom._maybe_memory_map(non_file, True)
+
+ with open(mmap_file, encoding="utf-8") as target:
+ pass
+
+ msg = "I/O operation on closed file"
+ with pytest.raises(ValueError, match=msg):
+ icom._maybe_memory_map(target, True)
+
+ @pytest.mark.skipif(WASM, reason="limited file system access on WASM")
+ def test_next(self, mmap_file):
+ with open(mmap_file, encoding="utf-8") as target:
+ lines = target.readlines()
+
+ with icom.get_handle(
+ target, "r", is_text=True, memory_map=True
+ ) as wrappers:
+ wrapper = wrappers.handle
+ assert isinstance(wrapper.buffer.buffer, mmap.mmap)
+
+ for line in lines:
+ next_line = next(wrapper)
+ assert next_line.strip() == line.strip()
+
+ with pytest.raises(StopIteration, match=r"^$"):
+ next(wrapper)
+
+ def test_unknown_engine(self, temp_file):
+ df = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ df.to_csv(temp_file)
+ with pytest.raises(ValueError, match="Unknown engine"):
+ pd.read_csv(temp_file, engine="pyt")
+
+ def test_binary_mode(self, temp_file):
+ """
+ 'encoding' shouldn't be passed to 'open' in binary mode.
+
+ GH 35058
+ """
+ df = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ df.to_csv(temp_file, mode="w+b")
+ tm.assert_frame_equal(df, pd.read_csv(temp_file, index_col=0))
+
+ @pytest.mark.parametrize("encoding", ["utf-16", "utf-32"])
+ @pytest.mark.parametrize("compression_", ["bz2", "xz"])
+ def test_warning_missing_utf_bom(self, encoding, compression_, temp_file):
+ """
+ bz2 and xz do not write the byte order mark (BOM) for utf-16/32.
+
+ https://stackoverflow.com/questions/55171439
+
+ GH 35681
+ """
+ df = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ with tm.assert_produces_warning(UnicodeWarning, match="byte order mark"):
+ df.to_csv(temp_file, compression=compression_, encoding=encoding)
+
+ # reading should fail (otherwise we wouldn't need the warning)
+ msg = (
+ r"UTF-\d+ stream does not start with BOM|"
+ r"'utf-\d+' codec can't decode byte"
+ )
+ with pytest.raises(UnicodeError, match=msg):
+ pd.read_csv(temp_file, compression=compression_, encoding=encoding)
+
+
+def test_is_fsspec_url():
+ assert icom.is_fsspec_url("gcs://pandas/somethingelse.com")
+ assert icom.is_fsspec_url("gs://pandas/somethingelse.com")
+ # the following is the only remote URL that is handled without fsspec
+ assert not icom.is_fsspec_url("http://pandas/somethingelse.com")
+ assert not icom.is_fsspec_url("random:pandas/somethingelse.com")
+ assert not icom.is_fsspec_url("/local/path")
+ assert not icom.is_fsspec_url("relative/local/path")
+ # fsspec URL in string should not be recognized
+ assert not icom.is_fsspec_url("this is not fsspec://url")
+ assert not icom.is_fsspec_url("{'url': 'gs://pandas/somethingelse.com'}")
+ # accept everything that conforms to RFC 3986 schema
+ assert icom.is_fsspec_url("RFC-3986+compliant.spec://something")
+
+
+def test_is_fsspec_url_chained():
+ # GH#48978 Support chained fsspec URLs
+ # See https://filesystem-spec.readthedocs.io/en/latest/features.html#url-chaining.
+ assert icom.is_fsspec_url("filecache::s3://pandas/test.csv")
+ assert icom.is_fsspec_url("zip://test.csv::filecache::gcs://bucket/file.zip")
+ assert icom.is_fsspec_url("filecache::zip://test.csv::gcs://bucket/file.zip")
+ assert icom.is_fsspec_url("filecache::dask::s3://pandas/test.csv")
+ assert not icom.is_fsspec_url("filecache:s3://pandas/test.csv")
+ assert not icom.is_fsspec_url("filecache:::s3://pandas/test.csv")
+ assert not icom.is_fsspec_url("filecache::://pandas/test.csv")
+
+
+@pytest.mark.parametrize("format", ["csv", "json"])
+def test_codecs_encoding(format, temp_file):
+ # GH39247
+ expected = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ with open(temp_file, mode="w", encoding="utf-8") as handle:
+ getattr(expected, f"to_{format}")(handle)
+ with open(temp_file, encoding="utf-8") as handle:
+ if format == "csv":
+ df = pd.read_csv(handle, index_col=0)
+ else:
+ df = pd.read_json(handle)
+ tm.assert_frame_equal(expected, df)
+
+
+def test_codecs_get_writer_reader(temp_file):
+ # GH39247
+ expected = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ with open(temp_file, "wb") as handle:
+ with codecs.getwriter("utf-8")(handle) as encoded:
+ expected.to_csv(encoded)
+ with open(temp_file, "rb") as handle:
+ with codecs.getreader("utf-8")(handle) as encoded:
+ df = pd.read_csv(encoded, index_col=0)
+ tm.assert_frame_equal(expected, df)
+
+
+@pytest.mark.parametrize(
+ "io_class,mode,msg",
+ [
+ (BytesIO, "t", "a bytes-like object is required, not 'str'"),
+ (StringIO, "b", "string argument expected, got 'bytes'"),
+ ],
+)
+def test_explicit_encoding(io_class, mode, msg):
+ # GH39247; this test makes sure that if a user provides mode="*t" or "*b",
+ # it is used. In the case of this test it leads to an error as intentionally the
+ # wrong mode is requested
+ expected = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ with io_class() as buffer:
+ with pytest.raises(TypeError, match=msg):
+ expected.to_csv(buffer, mode=f"w{mode}")
+
+
+@pytest.mark.parametrize("encoding_errors", ["strict", "replace"])
+@pytest.mark.parametrize("format", ["csv", "json"])
+def test_encoding_errors(encoding_errors, format, temp_file):
+ # GH39450
+ msg = "'utf-8' codec can't decode byte"
+ bad_encoding = b"\xe4"
+
+ if format == "csv":
+ content = b"," + bad_encoding + b"\n" + bad_encoding * 2 + b"," + bad_encoding
+ reader = partial(pd.read_csv, index_col=0)
+ else:
+ content = (
+ b'{"'
+ + bad_encoding * 2
+ + b'": {"'
+ + bad_encoding
+ + b'":"'
+ + bad_encoding
+ + b'"}}'
+ )
+ reader = partial(pd.read_json, orient="index")
+ file = temp_file
+ file.write_bytes(content)
+
+ if encoding_errors != "replace":
+ with pytest.raises(UnicodeDecodeError, match=msg):
+ reader(temp_file, encoding_errors=encoding_errors)
+ else:
+ df = reader(temp_file, encoding_errors=encoding_errors)
+ decoded = bad_encoding.decode(errors=encoding_errors)
+ expected = pd.DataFrame({decoded: [decoded]}, index=[decoded * 2])
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.parametrize("encoding_errors", [0, None])
+def test_encoding_errors_badtype(encoding_errors):
+ # GH 59075
+ content = StringIO("A,B\n1,2\n3,4\n")
+ reader = partial(pd.read_csv, encoding_errors=encoding_errors)
+ expected_error = "encoding_errors must be a string, got "
+ expected_error += f"{type(encoding_errors).__name__}"
+ with pytest.raises(ValueError, match=expected_error):
+ reader(content)
+
+
+def test_bad_encdoing_errors(temp_file):
+ # GH 39777
+ with pytest.raises(LookupError, match="unknown error handler name"):
+ icom.get_handle(temp_file, "w", errors="bad")
+
+
+@pytest.mark.skipif(WASM, reason="limited file system access on WASM")
+def test_errno_attribute():
+ # GH 13872
+ with pytest.raises(FileNotFoundError, match="\\[Errno 2\\]") as err:
+ pd.read_csv("doesnt_exist")
+ assert err.errno == errno.ENOENT
+
+
+def test_fail_mmap():
+ with pytest.raises(UnsupportedOperation, match="fileno"):
+ with BytesIO() as buffer:
+ icom.get_handle(buffer, "rb", memory_map=True)
+
+
+def test_close_on_error():
+ # GH 47136
+ class TestError:
+ def close(self):
+ raise OSError("test")
+
+ with pytest.raises(OSError, match="test"):
+ with BytesIO() as buffer:
+ with icom.get_handle(buffer, "rb") as handles:
+ handles.created_handles.append(TestError())
+
+
+@td.skip_if_no("fsspec")
+@pytest.mark.parametrize("compression", [None, "infer"])
+def test_read_csv_chained_url_no_error(datapath, compression):
+ # GH 60100
+ tar_file_path = datapath("io", "data", "tar", "test-csv.tar")
+ chained_file_url = f"tar://test.csv::file://{tar_file_path}"
+
+ result = pd.read_csv(chained_file_url, compression=compression, sep=";")
+ expected = pd.DataFrame({"1": {0: 3}, "2": {0: 4}})
+
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize(
+ "reader",
+ [
+ pd.read_csv,
+ pd.read_fwf,
+ pd.read_excel,
+ pd.read_feather,
+ pd.read_hdf,
+ pd.read_stata,
+ pd.read_sas,
+ pd.read_json,
+ pd.read_pickle,
+ ],
+)
+def test_pickle_reader(reader):
+ # GH 22265
+ with BytesIO() as buffer:
+ pickle.dump(reader, buffer)
+
+
+@td.skip_if_no("pyarrow")
+def test_pyarrow_read_csv_datetime_dtype():
+ # GH 59904
+ data = '"date"\n"20/12/2025"\n""\n"31/12/2020"'
+ result = pd.read_csv(
+ StringIO(data), parse_dates=["date"], dayfirst=True, dtype_backend="pyarrow"
+ )
+
+ expect_data = pd.to_datetime(["20/12/2025", pd.NaT, "31/12/2020"], dayfirst=True)
+ expect = pd.DataFrame({"date": expect_data})
+
+ tm.assert_frame_equal(expect, result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_compression.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_compression.py
new file mode 100644
index 0000000000000000000000000000000000000000..97b64a29a7f2cff7bfd5c1311fab9b40dcdf1a0a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_compression.py
@@ -0,0 +1,382 @@
+import gzip
+import io
+import os
+import subprocess
+import sys
+import tarfile
+import textwrap
+import zipfile
+
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_windows
+
+import pandas as pd
+import pandas._testing as tm
+
+import pandas.io.common as icom
+
+
+@pytest.mark.parametrize(
+ "obj",
+ [
+ pd.DataFrame(
+ 100 * [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ columns=["X", "Y", "Z"],
+ ),
+ pd.Series(100 * [0.123456, 0.234567, 0.567567], name="X"),
+ ],
+)
+@pytest.mark.parametrize("method", ["to_pickle", "to_json", "to_csv"])
+def test_compression_size(obj, method, compression_only, temp_file):
+ if compression_only == "tar":
+ compression_only = {"method": "tar", "mode": "w:gz"}
+
+ path = temp_file
+ getattr(obj, method)(path, compression=compression_only)
+ compressed_size = os.path.getsize(path)
+ getattr(obj, method)(path, compression=None)
+ uncompressed_size = os.path.getsize(path)
+ assert uncompressed_size > compressed_size
+
+
+@pytest.mark.parametrize(
+ "obj",
+ [
+ pd.DataFrame(
+ 100 * [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ columns=["X", "Y", "Z"],
+ ),
+ pd.Series(100 * [0.123456, 0.234567, 0.567567], name="X"),
+ ],
+)
+@pytest.mark.parametrize("method", ["to_csv", "to_json"])
+def test_compression_size_fh(obj, method, compression_only, temp_file):
+ path = temp_file
+ with icom.get_handle(
+ path,
+ "w:gz" if compression_only == "tar" else "w",
+ compression=compression_only,
+ ) as handles:
+ getattr(obj, method)(handles.handle)
+ assert not handles.handle.closed
+ compressed_size = os.path.getsize(path)
+
+ # Create a new temporary file for uncompressed comparison
+ path2 = temp_file.parent / f"{temp_file.stem}_uncompressed{temp_file.suffix}"
+ path2.touch()
+ with icom.get_handle(path2, "w", compression=None) as handles:
+ getattr(obj, method)(handles.handle)
+ assert not handles.handle.closed
+ uncompressed_size = os.path.getsize(path2)
+ assert uncompressed_size > compressed_size
+
+
+@pytest.mark.parametrize(
+ "write_method, write_kwargs, read_method",
+ [
+ ("to_csv", {"index": False}, pd.read_csv),
+ ("to_json", {}, pd.read_json),
+ ("to_pickle", {}, pd.read_pickle),
+ ],
+)
+def test_dataframe_compression_defaults_to_infer(
+ write_method,
+ write_kwargs,
+ read_method,
+ compression_only,
+ compression_to_extension,
+ temp_file,
+):
+ # GH22004
+ input = pd.DataFrame([[1.0, 0, -4], [3.4, 5, 2]], columns=["X", "Y", "Z"])
+ extension = compression_to_extension[compression_only]
+ path = temp_file.parent / f"compressed{extension}"
+ getattr(input, write_method)(path, **write_kwargs)
+ output = read_method(path, compression=compression_only)
+ tm.assert_frame_equal(output, input)
+
+
+@pytest.mark.parametrize(
+ "write_method,write_kwargs,read_method,read_kwargs",
+ [
+ ("to_csv", {"index": False, "header": True}, pd.read_csv, {"squeeze": True}),
+ ("to_json", {}, pd.read_json, {"typ": "series"}),
+ ("to_pickle", {}, pd.read_pickle, {}),
+ ],
+)
+def test_series_compression_defaults_to_infer(
+ write_method,
+ write_kwargs,
+ read_method,
+ read_kwargs,
+ compression_only,
+ compression_to_extension,
+ temp_file,
+):
+ # GH22004
+ input = pd.Series([0, 5, -2, 10], name="X")
+ extension = compression_to_extension[compression_only]
+ path = temp_file.parent / f"compressed{extension}"
+ getattr(input, write_method)(path, **write_kwargs)
+ if "squeeze" in read_kwargs:
+ kwargs = read_kwargs.copy()
+ del kwargs["squeeze"]
+ output = read_method(path, compression=compression_only, **kwargs).squeeze(
+ "columns"
+ )
+ else:
+ output = read_method(path, compression=compression_only, **read_kwargs)
+ tm.assert_series_equal(output, input, check_names=False)
+
+
+def test_compression_warning(compression_only, temp_file):
+ # Assert that passing a file object to to_csv while explicitly specifying a
+ # compression protocol triggers a RuntimeWarning, as per GH21227.
+ df = pd.DataFrame(
+ 100 * [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ columns=["X", "Y", "Z"],
+ )
+ path = temp_file
+ with icom.get_handle(path, "w", compression=compression_only) as handles:
+ with tm.assert_produces_warning(RuntimeWarning, match="has no effect"):
+ df.to_csv(handles.handle, compression=compression_only)
+
+
+def test_compression_binary(compression_only, temp_file):
+ """
+ Binary file handles support compression.
+
+ GH22555
+ """
+ df = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+
+ # with a file
+ path = temp_file
+ with open(path, mode="wb") as file:
+ df.to_csv(file, mode="wb", compression=compression_only)
+ file.seek(0) # file shouldn't be closed
+ tm.assert_frame_equal(
+ df, pd.read_csv(path, index_col=0, compression=compression_only)
+ )
+
+ # with BytesIO
+ file = io.BytesIO()
+ df.to_csv(file, mode="wb", compression=compression_only)
+ file.seek(0) # file shouldn't be closed
+ tm.assert_frame_equal(
+ df, pd.read_csv(file, index_col=0, compression=compression_only)
+ )
+
+
+def test_gzip_reproducibility_file_name(temp_file):
+ """
+ Gzip should create reproducible archives with mtime.
+
+ Note: Archives created with different filenames will still be different!
+
+ GH 28103
+ """
+ df = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ compression_options = {"method": "gzip", "mtime": 1}
+
+ # test for filename
+ path = temp_file
+ df.to_csv(path, compression=compression_options)
+ output = path.read_bytes()
+ df.to_csv(path, compression=compression_options)
+ assert output == path.read_bytes()
+
+
+def test_gzip_reproducibility_file_object():
+ """
+ Gzip should create reproducible archives with mtime.
+
+ GH 28103
+ """
+ df = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ compression_options = {"method": "gzip", "mtime": 1}
+
+ # test for file object
+ buffer = io.BytesIO()
+ df.to_csv(buffer, compression=compression_options, mode="wb")
+ output = buffer.getvalue()
+ buffer = io.BytesIO()
+ df.to_csv(buffer, compression=compression_options, mode="wb")
+ assert output == buffer.getvalue()
+
+
+@pytest.mark.single_cpu
+def test_with_missing_lzma():
+ """Tests if import pandas works when lzma is not present."""
+ # https://github.com/pandas-dev/pandas/issues/27575
+ code = textwrap.dedent(
+ """\
+ import sys
+ sys.modules['lzma'] = None
+ import pandas
+ """
+ )
+ subprocess.check_output([sys.executable, "-c", code], stderr=subprocess.PIPE)
+
+
+@pytest.mark.single_cpu
+def test_with_missing_lzma_runtime():
+ """Tests if ModuleNotFoundError is hit when calling lzma without
+ having the module available.
+ """
+ code = textwrap.dedent(
+ """
+ import sys
+ import pytest
+ sys.modules['lzma'] = None
+ import pandas as pd
+ df = pd.DataFrame()
+ with pytest.raises(ModuleNotFoundError, match='import of lzma'):
+ df.to_csv('foo.csv', compression='xz')
+ """
+ )
+ subprocess.check_output([sys.executable, "-c", code], stderr=subprocess.PIPE)
+
+
+@pytest.mark.parametrize(
+ "obj",
+ [
+ pd.DataFrame(
+ 100 * [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ columns=["X", "Y", "Z"],
+ ),
+ pd.Series(100 * [0.123456, 0.234567, 0.567567], name="X"),
+ ],
+)
+@pytest.mark.parametrize("method", ["to_pickle", "to_json", "to_csv"])
+def test_gzip_compression_level(obj, method, temp_file):
+ # GH33196
+ path = temp_file
+ getattr(obj, method)(path, compression="gzip")
+ compressed_size_default = os.path.getsize(path)
+ getattr(obj, method)(path, compression={"method": "gzip", "compresslevel": 1})
+ compressed_size_fast = os.path.getsize(path)
+ assert compressed_size_default < compressed_size_fast
+
+
+@pytest.mark.parametrize(
+ "obj",
+ [
+ pd.DataFrame(
+ 100 * [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ columns=["X", "Y", "Z"],
+ ),
+ pd.Series(100 * [0.123456, 0.234567, 0.567567], name="X"),
+ ],
+)
+@pytest.mark.parametrize("method", ["to_pickle", "to_json", "to_csv"])
+def test_xz_compression_level_read(obj, method, temp_file):
+ path = temp_file
+ getattr(obj, method)(path, compression="xz")
+ compressed_size_default = os.path.getsize(path)
+ getattr(obj, method)(path, compression={"method": "xz", "preset": 1})
+ compressed_size_fast = os.path.getsize(path)
+ assert compressed_size_default < compressed_size_fast
+ if method == "to_csv":
+ pd.read_csv(path, compression="xz")
+
+
+@pytest.mark.parametrize(
+ "obj",
+ [
+ pd.DataFrame(
+ 100 * [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ columns=["X", "Y", "Z"],
+ ),
+ pd.Series(100 * [0.123456, 0.234567, 0.567567], name="X"),
+ ],
+)
+@pytest.mark.parametrize("method", ["to_pickle", "to_json", "to_csv"])
+def test_bzip_compression_level(obj, method, temp_file):
+ """GH33196 bzip needs file size > 100k to show a size difference between
+ compression levels, so here we just check if the call works when
+ compression is passed as a dict.
+ """
+ path = temp_file
+ getattr(obj, method)(path, compression={"method": "bz2", "compresslevel": 1})
+
+
+@pytest.mark.parametrize(
+ "suffix,archive",
+ [
+ (".zip", zipfile.ZipFile),
+ (".tar", tarfile.TarFile),
+ ],
+)
+def test_empty_archive_zip(suffix, archive, temp_file):
+ path = temp_file.parent / f"archive{suffix}"
+ with archive(path, "w"):
+ pass
+ with pytest.raises(ValueError, match="Zero files found"):
+ pd.read_csv(path)
+
+
+def test_ambiguous_archive_zip(temp_file):
+ path = temp_file.parent / "archive.zip"
+ with zipfile.ZipFile(path, "w") as file:
+ file.writestr("a.csv", "foo,bar")
+ file.writestr("b.csv", "foo,bar")
+ with pytest.raises(ValueError, match="Multiple files found in ZIP file"):
+ pd.read_csv(path)
+
+
+def test_ambiguous_archive_tar(tmp_path):
+ csvAPath = tmp_path / "a.csv"
+ with open(csvAPath, "w", encoding="utf-8") as a:
+ a.write("foo,bar\n")
+ csvBPath = tmp_path / "b.csv"
+ with open(csvBPath, "w", encoding="utf-8") as b:
+ b.write("foo,bar\n")
+
+ tarpath = tmp_path / "archive.tar"
+ with tarfile.TarFile(tarpath, "w") as tar:
+ tar.add(csvAPath, "a.csv")
+ tar.add(csvBPath, "b.csv")
+
+ with pytest.raises(ValueError, match="Multiple files found in TAR archive"):
+ pd.read_csv(tarpath)
+
+
+def test_tar_gz_to_different_filename(temp_file):
+ file = temp_file.parent / "archive.foo"
+ pd.DataFrame(
+ [["1", "2"]],
+ columns=["foo", "bar"],
+ ).to_csv(file, compression={"method": "tar", "mode": "w:gz"}, index=False)
+ with gzip.open(file) as uncompressed:
+ with tarfile.TarFile(fileobj=uncompressed) as archive:
+ members = archive.getmembers()
+ assert len(members) == 1
+ content = archive.extractfile(members[0]).read().decode("utf8")
+
+ if is_platform_windows():
+ expected = "foo,bar\r\n1,2\r\n"
+ else:
+ expected = "foo,bar\n1,2\n"
+
+ assert content == expected
+
+
+def test_tar_no_error_on_close():
+ with io.BytesIO() as buffer:
+ with icom._BytesTarFile(fileobj=buffer, mode="w"):
+ pass
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_feather.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_feather.py
new file mode 100644
index 0000000000000000000000000000000000000000..6351a9760b773e2ffdd1547061b7f1918ce325e4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_feather.py
@@ -0,0 +1,291 @@
+"""test feather-format compat"""
+
+from datetime import datetime
+import zoneinfo
+
+import numpy as np
+import pytest
+
+from pandas.compat.pyarrow import (
+ pa_version_under18p0,
+ pa_version_under19p0,
+)
+
+import pandas as pd
+import pandas._testing as tm
+
+from pandas.io.feather_format import read_feather, to_feather # isort:skip
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+pa = pytest.importorskip("pyarrow")
+
+
+@pytest.mark.single_cpu
+class TestFeather:
+ def check_error_on_write(self, df, exc, err_msg, temp_file):
+ # check that we are raising the exception
+ # on writing
+
+ with pytest.raises(exc, match=err_msg):
+ to_feather(df, temp_file)
+
+ def check_external_error_on_write(self, df, temp_file):
+ # check that we are raising the exception
+ # on writing
+
+ with tm.external_error_raised(Exception):
+ to_feather(df, temp_file)
+
+ def check_round_trip(
+ self, df, temp_file, expected=None, write_kwargs=None, **read_kwargs
+ ):
+ if write_kwargs is None:
+ write_kwargs = {}
+ if expected is None:
+ expected = df.copy()
+
+ to_feather(df, temp_file, **write_kwargs)
+
+ result = read_feather(temp_file, **read_kwargs)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_error(self, temp_file):
+ msg = "feather only support IO with DataFrames"
+ for obj in [
+ pd.Series([1, 2, 3]),
+ 1,
+ "foo",
+ pd.Timestamp("20130101"),
+ np.array([1, 2, 3]),
+ ]:
+ self.check_error_on_write(obj, ValueError, msg, temp_file)
+
+ def test_basic(self, temp_file):
+ tz = zoneinfo.ZoneInfo("US/Eastern")
+ df = pd.DataFrame(
+ {
+ "string": list("abc"),
+ "int": list(range(1, 4)),
+ "uint": np.arange(3, 6).astype("u1"),
+ "float": np.arange(4.0, 7.0, dtype="float64"),
+ "float_with_null": [1.0, np.nan, 3],
+ "bool": [True, False, True],
+ "bool_with_null": [True, np.nan, False],
+ "cat": pd.Categorical(list("abc")),
+ "dt": pd.DatetimeIndex(
+ list(pd.date_range("20130101", periods=3)), freq=None
+ ),
+ "dttz": pd.DatetimeIndex(
+ list(pd.date_range("20130101", periods=3, tz=tz)),
+ freq=None,
+ ),
+ "dt_with_null": [
+ pd.Timestamp("20130101"),
+ pd.NaT,
+ pd.Timestamp("20130103"),
+ ],
+ "dtns": pd.DatetimeIndex(
+ list(pd.date_range("20130101", periods=3, freq="ns")), freq=None
+ ),
+ }
+ )
+ df["periods"] = pd.period_range("2013", freq="M", periods=3)
+ df["timedeltas"] = pd.timedelta_range("1 day", periods=3)
+ df["intervals"] = pd.interval_range(0, 3, 3)
+
+ assert df.dttz.dtype.tz.key == "US/Eastern"
+
+ expected = df.copy()
+ expected.loc[1, "bool_with_null"] = None
+ self.check_round_trip(df, temp_file, expected=expected)
+
+ def test_duplicate_columns(self, temp_file):
+ # https://github.com/wesm/feather/issues/53
+ # not currently able to handle duplicate columns
+ df = pd.DataFrame(np.arange(12).reshape(4, 3), columns=list("aaa")).copy()
+ self.check_external_error_on_write(df, temp_file)
+
+ def test_read_columns(self, temp_file):
+ # GH 24025
+ df = pd.DataFrame(
+ {
+ "col1": list("abc"),
+ "col2": list(range(1, 4)),
+ "col3": list("xyz"),
+ "col4": list(range(4, 7)),
+ }
+ )
+ columns = ["col1", "col3"]
+ self.check_round_trip(df, temp_file, expected=df[columns], columns=columns)
+
+ def test_read_columns_different_order(self, temp_file):
+ # GH 33878
+ df = pd.DataFrame({"A": [1, 2], "B": ["x", "y"], "C": [True, False]})
+ expected = df[["B", "A"]]
+ self.check_round_trip(df, temp_file, expected, columns=["B", "A"])
+
+ def test_unsupported_other(self, temp_file):
+ # mixed python objects
+ df = pd.DataFrame({"a": ["a", 1, 2.0]})
+ self.check_external_error_on_write(df, temp_file)
+
+ def test_rw_use_threads(self, temp_file):
+ df = pd.DataFrame({"A": np.arange(100000)})
+ self.check_round_trip(df, temp_file, use_threads=True)
+ self.check_round_trip(df, temp_file, use_threads=False)
+
+ def test_path_pathlib(self, temp_file):
+ df = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ ).reset_index()
+ result = tm.round_trip_pathlib(df.to_feather, read_feather, temp_file)
+ tm.assert_frame_equal(df, result)
+
+ def test_passthrough_keywords(self, temp_file):
+ df = pd.DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ ).reset_index()
+ self.check_round_trip(df, temp_file, write_kwargs={"version": 1})
+
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_http_path(self, feather_file, httpserver):
+ # GH 29055
+ expected = read_feather(feather_file)
+ with open(feather_file, "rb") as f:
+ httpserver.serve_content(content=f.read())
+ res = read_feather(httpserver.url)
+ tm.assert_frame_equal(expected, res)
+
+ def test_read_feather_dtype_backend(
+ self, string_storage, dtype_backend, using_infer_string, temp_file
+ ):
+ # GH#50765
+ df = pd.DataFrame(
+ {
+ "a": pd.Series([1, pd.NA, 3], dtype="Int64"),
+ "b": pd.Series([1, 2, 3], dtype="Int64"),
+ "c": pd.Series([1.5, pd.NA, 2.5], dtype="Float64"),
+ "d": pd.Series([1.5, 2.0, 2.5], dtype="Float64"),
+ "e": [True, False, None],
+ "f": [True, False, True],
+ "g": ["a", "b", "c"],
+ "h": ["a", "b", None],
+ }
+ )
+
+ to_feather(df, temp_file)
+ with pd.option_context("mode.string_storage", string_storage):
+ result = read_feather(temp_file, dtype_backend=dtype_backend)
+
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ if using_infer_string:
+ string_dtype = pd.ArrowDtype(pa.large_string())
+ else:
+ string_dtype = pd.ArrowDtype(pa.string())
+ else:
+ string_dtype = pd.StringDtype(string_storage)
+
+ expected = pd.DataFrame(
+ {
+ "a": pd.Series([1, pd.NA, 3], dtype="Int64"),
+ "b": pd.Series([1, 2, 3], dtype="Int64"),
+ "c": pd.Series([1.5, pd.NA, 2.5], dtype="Float64"),
+ "d": pd.Series([1.5, 2.0, 2.5], dtype="Float64"),
+ "e": pd.Series([True, False, pd.NA], dtype="boolean"),
+ "f": pd.Series([True, False, True], dtype="boolean"),
+ "g": pd.Series(["a", "b", "c"], dtype=string_dtype),
+ "h": pd.Series(["a", "b", None], dtype=string_dtype),
+ }
+ )
+
+ if dtype_backend == "pyarrow":
+ from pandas.arrays import ArrowExtensionArray
+
+ expected = pd.DataFrame(
+ {
+ col: ArrowExtensionArray(pa.array(expected[col], from_pandas=True))
+ for col in expected.columns
+ }
+ )
+
+ if using_infer_string:
+ expected.columns = expected.columns.astype(
+ pd.StringDtype(string_storage, na_value=np.nan)
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_int_columns_and_index(self, temp_file):
+ df = pd.DataFrame({"a": [1, 2, 3]}, index=pd.Index([3, 4, 5], name="test"))
+ self.check_round_trip(df, temp_file)
+
+ def test_invalid_dtype_backend(self, temp_file):
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ df = pd.DataFrame({"int": list(range(1, 4))})
+ df.to_feather(temp_file)
+ with pytest.raises(ValueError, match=msg):
+ read_feather(temp_file, dtype_backend="numpy")
+
+ def test_string_inference(self, temp_file, using_infer_string):
+ # GH#54431
+ df = pd.DataFrame(data={"a": ["x", "y"]})
+ df.to_feather(temp_file)
+ with pd.option_context("future.infer_string", True):
+ result = read_feather(temp_file)
+ dtype = pd.StringDtype(na_value=np.nan)
+ expected = pd.DataFrame(
+ data={"a": ["x", "y"]}, dtype=pd.StringDtype(na_value=np.nan)
+ )
+ expected = pd.DataFrame(
+ data={"a": ["x", "y"]},
+ dtype=dtype,
+ columns=pd.Index(
+ ["a"],
+ dtype=object
+ if pa_version_under19p0 and not using_infer_string
+ else dtype,
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.skipif(pa_version_under18p0, reason="not supported before 18.0")
+ def test_string_inference_string_view_type(self, temp_file):
+ # GH#54798
+ import pyarrow as pa
+ from pyarrow import feather
+
+ table = pa.table({"a": pa.array([None, "b", "c"], pa.string_view())})
+ feather.write_feather(table, temp_file)
+
+ with pd.option_context("future.infer_string", True):
+ result = read_feather(temp_file)
+
+ expected = pd.DataFrame(
+ data={"a": [None, "b", "c"]}, dtype=pd.StringDtype(na_value=np.nan)
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_out_of_bounds_datetime_to_feather(self, temp_file):
+ # GH#47832
+ df = pd.DataFrame(
+ {
+ "date": [
+ datetime.fromisoformat("1654-01-01"),
+ datetime.fromisoformat("1920-01-01"),
+ ],
+ }
+ )
+ self.check_round_trip(df, temp_file)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_fsspec.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_fsspec.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d76a622d29148caa47deb98c3e7687226ea5b1f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_fsspec.py
@@ -0,0 +1,348 @@
+import io
+
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+from pandas.compat import HAS_PYARROW
+from pandas.compat.pyarrow import pa_version_under14p0
+
+from pandas import (
+ DataFrame,
+ date_range,
+ read_csv,
+ read_excel,
+ read_feather,
+ read_json,
+ read_parquet,
+ read_pickle,
+ read_stata,
+ read_table,
+)
+import pandas._testing as tm
+from pandas.util import _test_decorators as td
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.fixture
+def fsspectest():
+ pytest.importorskip("fsspec")
+ from fsspec import register_implementation
+ from fsspec.implementations.memory import MemoryFileSystem
+ from fsspec.registry import _registry as registry
+
+ class TestMemoryFS(MemoryFileSystem):
+ protocol = "testmem"
+ test = [None]
+
+ def __init__(self, **kwargs) -> None:
+ self.test[0] = kwargs.pop("test", None)
+ super().__init__(**kwargs)
+
+ register_implementation("testmem", TestMemoryFS, clobber=True)
+ yield TestMemoryFS()
+ registry.pop("testmem", None)
+ TestMemoryFS.test[0] = None
+ TestMemoryFS.store.clear()
+
+
+@pytest.fixture
+def df1():
+ return DataFrame(
+ {
+ "int": [1, 3],
+ "float": [2.0, np.nan],
+ "str": ["t", "s"],
+ "dt": date_range("2018-06-18", periods=2),
+ }
+ )
+
+
+@pytest.fixture
+def cleared_fs():
+ fsspec = pytest.importorskip("fsspec")
+
+ memfs = fsspec.filesystem("memory")
+ yield memfs
+ memfs.store.clear()
+
+
+def test_read_csv(cleared_fs, df1):
+ text = str(df1.to_csv(index=False)).encode()
+ with cleared_fs.open("test/test.csv", "wb") as w:
+ w.write(text)
+ df2 = read_csv("memory://test/test.csv", parse_dates=["dt"])
+
+ expected = df1.copy()
+ expected["dt"] = expected["dt"].astype("M8[us]")
+ tm.assert_frame_equal(df2, expected)
+
+
+def test_reasonable_error(monkeypatch, cleared_fs):
+ from fsspec.registry import known_implementations
+
+ with pytest.raises(ValueError, match="nosuchprotocol"):
+ read_csv("nosuchprotocol://test/test.csv")
+ err_msg = "test error message"
+ monkeypatch.setitem(
+ known_implementations,
+ "couldexist",
+ {"class": "unimportable.CouldExist", "err": err_msg},
+ )
+ with pytest.raises(ImportError, match=err_msg):
+ read_csv("couldexist://test/test.csv")
+
+
+def test_to_csv(cleared_fs, df1):
+ df1.to_csv("memory://test/test.csv", index=True)
+
+ df2 = read_csv("memory://test/test.csv", parse_dates=["dt"], index_col=0)
+
+ expected = df1.copy()
+ expected["dt"] = expected["dt"].astype("M8[us]")
+ tm.assert_frame_equal(df2, expected)
+
+
+def test_to_excel(cleared_fs, df1):
+ pytest.importorskip("openpyxl")
+ ext = "xlsx"
+ path = f"memory://test/test.{ext}"
+ df1.to_excel(path, index=True)
+
+ df2 = read_excel(path, parse_dates=["dt"], index_col=0)
+
+ expected = df1.copy()
+ expected["dt"] = expected["dt"].astype("M8[us]")
+ tm.assert_frame_equal(df2, expected)
+
+
+@pytest.mark.parametrize("binary_mode", [False, True])
+def test_to_csv_fsspec_object(cleared_fs, binary_mode, df1):
+ fsspec = pytest.importorskip("fsspec")
+
+ path = "memory://test/test.csv"
+ mode = "wb" if binary_mode else "w"
+ with fsspec.open(path, mode=mode).open() as fsspec_object:
+ df1.to_csv(fsspec_object, index=True)
+ assert not fsspec_object.closed
+
+ mode = mode.replace("w", "r")
+ with fsspec.open(path, mode=mode) as fsspec_object:
+ df2 = read_csv(
+ fsspec_object,
+ parse_dates=["dt"],
+ index_col=0,
+ )
+ assert not fsspec_object.closed
+
+ expected = df1.copy()
+ expected["dt"] = expected["dt"].astype("M8[us]")
+ tm.assert_frame_equal(df2, expected)
+
+
+def test_csv_options(fsspectest):
+ df = DataFrame({"a": [0]})
+ df.to_csv(
+ "testmem://test/test.csv", storage_options={"test": "csv_write"}, index=False
+ )
+ assert fsspectest.test[0] == "csv_write"
+ read_csv("testmem://test/test.csv", storage_options={"test": "csv_read"})
+ assert fsspectest.test[0] == "csv_read"
+
+
+def test_read_table_options(fsspectest):
+ # GH #39167
+ df = DataFrame({"a": [0]})
+ df.to_csv(
+ "testmem://test/test.csv", storage_options={"test": "csv_write"}, index=False
+ )
+ assert fsspectest.test[0] == "csv_write"
+ read_table("testmem://test/test.csv", storage_options={"test": "csv_read"})
+ assert fsspectest.test[0] == "csv_read"
+
+
+def test_excel_options(fsspectest):
+ pytest.importorskip("openpyxl")
+ extension = "xlsx"
+
+ df = DataFrame({"a": [0]})
+
+ path = f"testmem://test/test.{extension}"
+
+ df.to_excel(path, storage_options={"test": "write"}, index=False)
+ assert fsspectest.test[0] == "write"
+ read_excel(path, storage_options={"test": "read"})
+ assert fsspectest.test[0] == "read"
+
+
+@pytest.mark.xfail(
+ using_string_dtype() and HAS_PYARROW and not pa_version_under14p0,
+ reason="TODO(infer_string) fastparquet",
+)
+def test_to_parquet_new_file(cleared_fs, df1):
+ """Regression test for writing to a not-yet-existent GCS Parquet file."""
+ pytest.importorskip("fastparquet")
+
+ df1.to_parquet(
+ "memory://test/test.csv", index=True, engine="fastparquet", compression=None
+ )
+
+
+def test_arrowparquet_options(fsspectest):
+ """Regression test for writing to a not-yet-existent GCS Parquet file."""
+ pytest.importorskip("pyarrow")
+ df = DataFrame({"a": [0]})
+ df.to_parquet(
+ "testmem://test/test.csv",
+ engine="pyarrow",
+ compression=None,
+ storage_options={"test": "parquet_write"},
+ )
+ assert fsspectest.test[0] == "parquet_write"
+ read_parquet(
+ "testmem://test/test.csv",
+ engine="pyarrow",
+ storage_options={"test": "parquet_read"},
+ )
+ assert fsspectest.test[0] == "parquet_read"
+
+
+def test_fastparquet_options(fsspectest):
+ """Regression test for writing to a not-yet-existent GCS Parquet file."""
+ pytest.importorskip("fastparquet")
+
+ df = DataFrame({"a": [0]})
+ df.to_parquet(
+ "testmem://test/test.csv",
+ engine="fastparquet",
+ compression=None,
+ storage_options={"test": "parquet_write"},
+ )
+ assert fsspectest.test[0] == "parquet_write"
+ read_parquet(
+ "testmem://test/test.csv",
+ engine="fastparquet",
+ storage_options={"test": "parquet_read"},
+ )
+ assert fsspectest.test[0] == "parquet_read"
+
+
+@pytest.mark.single_cpu
+@pytest.mark.parametrize("compression_suffix", ["", ".gz", ".bz2"])
+def test_from_s3_csv(s3_bucket_public_with_data, s3so, tips_file, compression_suffix):
+ pytest.importorskip("s3fs")
+ df_from_s3 = read_csv(
+ f"s3://{s3_bucket_public_with_data.name}/tips.csv{compression_suffix}",
+ storage_options=s3so,
+ )
+ df_from_local = read_csv(tips_file)
+ tm.assert_equal(df_from_s3, df_from_local)
+
+
+@pytest.mark.single_cpu
+@pytest.mark.parametrize("protocol", ["s3", "s3a", "s3n"])
+def test_s3_protocols(s3_bucket_public_with_data, s3so, tips_file, protocol):
+ pytest.importorskip("s3fs")
+ df_from_s3 = read_csv(
+ f"{protocol}://{s3_bucket_public_with_data.name}/tips.csv",
+ storage_options=s3so,
+ )
+ df_from_local = read_csv(tips_file)
+ tm.assert_equal(df_from_s3, df_from_local)
+
+
+@pytest.mark.xfail(using_string_dtype(), reason="TODO(infer_string) fastparquet")
+@pytest.mark.single_cpu
+def test_s3_parquet(s3_bucket_public, s3so, df1):
+ pytest.importorskip("fastparquet")
+ pytest.importorskip("s3fs")
+
+ fn = f"s3://{s3_bucket_public.name}/test.parquet"
+ df1.to_parquet(
+ fn, index=False, engine="fastparquet", compression=None, storage_options=s3so
+ )
+ df2 = read_parquet(fn, engine="fastparquet", storage_options=s3so)
+ tm.assert_equal(df1, df2)
+
+
+@td.skip_if_installed("fsspec")
+def test_not_present_exception():
+ msg = "`Import fsspec` failed. Use pip or conda to install the fsspec package."
+ with pytest.raises(ImportError, match=msg):
+ read_csv("memory://test/test.csv")
+
+
+def test_feather_options(fsspectest):
+ pytest.importorskip("pyarrow")
+ df = DataFrame({"a": [0]})
+ df.to_feather("testmem://mockfile", storage_options={"test": "feather_write"})
+ assert fsspectest.test[0] == "feather_write"
+ out = read_feather("testmem://mockfile", storage_options={"test": "feather_read"})
+ assert fsspectest.test[0] == "feather_read"
+ tm.assert_frame_equal(df, out)
+
+
+def test_pickle_options(fsspectest):
+ df = DataFrame({"a": [0]})
+ df.to_pickle("testmem://mockfile", storage_options={"test": "pickle_write"})
+ assert fsspectest.test[0] == "pickle_write"
+ out = read_pickle("testmem://mockfile", storage_options={"test": "pickle_read"})
+ assert fsspectest.test[0] == "pickle_read"
+ tm.assert_frame_equal(df, out)
+
+
+def test_json_options(fsspectest, compression):
+ df = DataFrame({"a": [0]})
+ df.to_json(
+ "testmem://mockfile",
+ compression=compression,
+ storage_options={"test": "json_write"},
+ )
+ assert fsspectest.test[0] == "json_write"
+ out = read_json(
+ "testmem://mockfile",
+ compression=compression,
+ storage_options={"test": "json_read"},
+ )
+ assert fsspectest.test[0] == "json_read"
+ tm.assert_frame_equal(df, out)
+
+
+def test_stata_options(fsspectest):
+ df = DataFrame({"a": [0]})
+ df.to_stata(
+ "testmem://mockfile", storage_options={"test": "stata_write"}, write_index=False
+ )
+ assert fsspectest.test[0] == "stata_write"
+ out = read_stata("testmem://mockfile", storage_options={"test": "stata_read"})
+ assert fsspectest.test[0] == "stata_read"
+ tm.assert_frame_equal(df, out.astype("int64"))
+
+
+def test_markdown_options(fsspectest):
+ pytest.importorskip("tabulate")
+ df = DataFrame({"a": [0]})
+ df.to_markdown("testmem://mockfile", storage_options={"test": "md_write"})
+ assert fsspectest.test[0] == "md_write"
+ assert fsspectest.cat("testmem://mockfile")
+
+
+def test_non_fsspec_options():
+ pytest.importorskip("pyarrow")
+ with pytest.raises(ValueError, match="storage_options"):
+ read_csv("localfile", storage_options={"a": True})
+ with pytest.raises(ValueError, match="storage_options"):
+ # separate test for parquet, which has a different code path
+ read_parquet("localfile", storage_options={"a": True})
+ by = io.BytesIO()
+
+ with pytest.raises(ValueError, match="storage_options"):
+ read_csv(by, storage_options={"a": True})
+
+ df = DataFrame({"a": [0]})
+ with pytest.raises(ValueError, match="storage_options"):
+ df.to_parquet("nonfsspecpath", storage_options={"a": True})
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_gcs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_gcs.py
new file mode 100644
index 0000000000000000000000000000000000000000..022fd89c1f555551a47d6361f0368dcbb3d28ab8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_gcs.py
@@ -0,0 +1,233 @@
+from io import BytesIO
+import os
+import pathlib
+import tarfile
+import zipfile
+
+import numpy as np
+import pytest
+
+from pandas.compat.pyarrow import pa_version_under17p0
+
+from pandas import (
+ DataFrame,
+ Index,
+ date_range,
+ read_csv,
+ read_excel,
+ read_json,
+ read_parquet,
+)
+import pandas._testing as tm
+from pandas.util import _test_decorators as td
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.fixture
+def gcs_buffer():
+ """Emulate GCS using a binary buffer."""
+ pytest.importorskip("gcsfs")
+ fsspec = pytest.importorskip("fsspec")
+
+ gcs_buffer = BytesIO()
+ gcs_buffer.close = lambda: True
+
+ class MockGCSFileSystem(fsspec.AbstractFileSystem):
+ @staticmethod
+ def open(*args, **kwargs):
+ gcs_buffer.seek(0)
+ return gcs_buffer
+
+ def ls(self, path, **kwargs):
+ # needed for pyarrow
+ return [{"name": path, "type": "file"}]
+
+ # Overwrites the default implementation from gcsfs to our mock class
+ fsspec.register_implementation("gs", MockGCSFileSystem, clobber=True)
+
+ return gcs_buffer
+
+
+# Patches pyarrow; other processes should not pick up change
+@pytest.mark.single_cpu
+@pytest.mark.parametrize("format", ["csv", "json", "parquet", "excel", "markdown"])
+def test_to_read_gcs(gcs_buffer, format, monkeypatch, capsys, request):
+ """
+ Test that many to/read functions support GCS.
+
+ GH 33987
+ """
+
+ df1 = DataFrame(
+ {
+ "int": [1, 3],
+ "float": [2.0, np.nan],
+ "str": ["t", "s"],
+ "dt": date_range("2018-06-18", periods=2, unit="ns"),
+ }
+ )
+
+ path = f"gs://test/test.{format}"
+
+ if format == "csv":
+ df1.to_csv(path, index=True)
+ df2 = read_csv(path, parse_dates=["dt"], index_col=0)
+ elif format == "excel":
+ path = "gs://test/test.xlsx"
+ df1.to_excel(path)
+ df2 = read_excel(path, parse_dates=["dt"], index_col=0)
+ elif format == "json":
+ df1.to_json(path, date_format="iso")
+ df2 = read_json(path, convert_dates=["dt"])
+ elif format == "parquet":
+ pytest.importorskip("pyarrow")
+ pa_fs = pytest.importorskip("pyarrow.fs")
+
+ class MockFileSystem(pa_fs.FileSystem):
+ @staticmethod
+ def from_uri(path):
+ print("Using pyarrow filesystem")
+ to_local = pathlib.Path(path.replace("gs://", "")).absolute().as_uri()
+ return pa_fs.LocalFileSystem(to_local)
+
+ request.applymarker(
+ pytest.mark.xfail(
+ not pa_version_under17p0,
+ raises=TypeError,
+ reason="pyarrow 17 broke the mocked filesystem",
+ )
+ )
+ with monkeypatch.context() as m:
+ m.setattr(pa_fs, "FileSystem", MockFileSystem)
+ df1.to_parquet(path)
+ df2 = read_parquet(path)
+ captured = capsys.readouterr()
+ assert captured.out == "Using pyarrow filesystem\nUsing pyarrow filesystem\n"
+ elif format == "markdown":
+ pytest.importorskip("tabulate")
+ df1.to_markdown(path)
+ df2 = df1
+
+ expected = df1[:]
+ if format in ["csv", "excel", "json"]:
+ expected["dt"] = expected["dt"].dt.as_unit("us")
+
+ tm.assert_frame_equal(df2, expected)
+
+
+def assert_equal_zip_safe(result: bytes, expected: bytes, compression: str):
+ """
+ For zip compression, only compare the CRC-32 checksum of the file contents
+ to avoid checking the time-dependent last-modified timestamp which
+ in some CI builds is off-by-one
+
+ See https://en.wikipedia.org/wiki/ZIP_(file_format)#File_headers
+ """
+ if compression == "zip":
+ # Only compare the CRC checksum of the file contents
+ with (
+ zipfile.ZipFile(BytesIO(result)) as exp,
+ zipfile.ZipFile(BytesIO(expected)) as res,
+ ):
+ for res_info, exp_info in zip(res.infolist(), exp.infolist()):
+ assert res_info.CRC == exp_info.CRC
+ elif compression == "tar":
+ with (
+ tarfile.open(fileobj=BytesIO(result)) as tar_exp,
+ tarfile.open(fileobj=BytesIO(expected)) as tar_res,
+ ):
+ for tar_res_info, tar_exp_info in zip(
+ tar_res.getmembers(), tar_exp.getmembers()
+ ):
+ actual_file = tar_res.extractfile(tar_res_info)
+ expected_file = tar_exp.extractfile(tar_exp_info)
+ assert (actual_file is None) == (expected_file is None)
+ if actual_file is not None and expected_file is not None:
+ assert actual_file.read() == expected_file.read()
+ else:
+ assert result == expected
+
+
+@pytest.mark.parametrize("encoding", ["utf-8", "cp1251"])
+def test_to_csv_compression_encoding_gcs(
+ gcs_buffer, compression_only, encoding, compression_to_extension
+):
+ """
+ Compression and encoding should with GCS.
+
+ GH 35677 (to_csv, compression), GH 26124 (to_csv, encoding), and
+ GH 32392 (read_csv, encoding)
+ """
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD")),
+ index=Index([f"i-{i}" for i in range(30)]),
+ )
+
+ # reference of compressed and encoded file
+ compression = {"method": compression_only}
+ if compression_only == "gzip":
+ compression["mtime"] = 1 # be reproducible
+ buffer = BytesIO()
+ df.to_csv(buffer, compression=compression, encoding=encoding, mode="wb")
+
+ # write compressed file with explicit compression
+ path_gcs = "gs://test/test.csv"
+ df.to_csv(path_gcs, compression=compression, encoding=encoding)
+ res = gcs_buffer.getvalue()
+ expected = buffer.getvalue()
+ assert_equal_zip_safe(res, expected, compression_only)
+
+ read_df = read_csv(
+ path_gcs, index_col=0, compression=compression_only, encoding=encoding
+ )
+ tm.assert_frame_equal(df, read_df)
+
+ # write compressed file with implicit compression
+ file_ext = compression_to_extension[compression_only]
+ compression["method"] = "infer"
+ path_gcs += f".{file_ext}"
+ df.to_csv(path_gcs, compression=compression, encoding=encoding)
+
+ res = gcs_buffer.getvalue()
+ expected = buffer.getvalue()
+ assert_equal_zip_safe(res, expected, compression_only)
+
+ read_df = read_csv(path_gcs, index_col=0, compression="infer", encoding=encoding)
+ tm.assert_frame_equal(df, read_df)
+
+
+def test_to_parquet_gcs_new_file(monkeypatch, tmpdir):
+ """Regression test for writing to a not-yet-existent GCS Parquet file."""
+ pytest.importorskip("fastparquet")
+ pytest.importorskip("gcsfs")
+
+ from fsspec import AbstractFileSystem
+
+ df1 = DataFrame(
+ {
+ "int": [1, 3],
+ "float": [2.0, np.nan],
+ "dt": date_range("2018-06-18", periods=2),
+ }
+ )
+
+ class MockGCSFileSystem(AbstractFileSystem):
+ def open(self, path, mode="r", *args):
+ if "w" not in mode:
+ raise FileNotFoundError
+ return open(os.path.join(tmpdir, "test.parquet"), mode, encoding="utf-8")
+
+ monkeypatch.setattr("gcsfs.GCSFileSystem", MockGCSFileSystem)
+ df1.to_parquet(
+ "gs://test/test.csv", index=True, engine="fastparquet", compression=None
+ )
+
+
+@td.skip_if_installed("gcsfs")
+def test_gcs_not_present_exception():
+ with tm.external_error_raised(ImportError):
+ read_csv("gs://test/test.csv")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_html.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_html.py
new file mode 100644
index 0000000000000000000000000000000000000000..abc0cfbb36332daeb3ea606450a1df75d455ed50
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_html.py
@@ -0,0 +1,1669 @@
+from collections.abc import Iterator
+from functools import partial
+from io import (
+ BytesIO,
+ StringIO,
+)
+import os
+from pathlib import Path
+import re
+import threading
+from urllib.error import URLError
+
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_windows
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ NA,
+ DataFrame,
+ MultiIndex,
+ Series,
+ Timestamp,
+ date_range,
+ read_csv,
+ read_html,
+ to_datetime,
+)
+import pandas._testing as tm
+
+from pandas.io.common import file_path_to_url
+
+
+@pytest.fixture(
+ params=[
+ "chinese_utf-16.html",
+ "chinese_utf-32.html",
+ "chinese_utf-8.html",
+ "letz_latin1.html",
+ ]
+)
+def html_encoding_file(request, datapath):
+ """Parametrized fixture for HTML encoding test filenames."""
+ return datapath("io", "data", "html_encoding", request.param)
+
+
+def assert_framelist_equal(list1, list2, *args, **kwargs):
+ assert len(list1) == len(list2), (
+ "lists are not of equal size "
+ f"len(list1) == {len(list1)}, "
+ f"len(list2) == {len(list2)}"
+ )
+ msg = "not all list elements are DataFrames"
+ both_frames = all(
+ map(
+ lambda x, y: isinstance(x, DataFrame) and isinstance(y, DataFrame),
+ list1,
+ list2,
+ )
+ )
+ assert both_frames, msg
+ for frame_i, frame_j in zip(list1, list2):
+ tm.assert_frame_equal(frame_i, frame_j, *args, **kwargs)
+ assert not frame_i.empty, "frames are both empty"
+
+
+def test_bs4_version_fails(monkeypatch, datapath):
+ bs4 = pytest.importorskip("bs4")
+ pytest.importorskip("html5lib")
+
+ monkeypatch.setattr(bs4, "__version__", "4.2")
+ with pytest.raises(ImportError, match="Pandas requires version"):
+ read_html(datapath("io", "data", "html", "spam.html"), flavor="bs4")
+
+
+def test_invalid_flavor():
+ url = "google.com"
+ flavor = "invalid flavor"
+ msg = r"\{" + flavor + r"\} is not a valid set of flavors"
+
+ with pytest.raises(ValueError, match=msg):
+ read_html(StringIO(url), match="google", flavor=flavor)
+
+
+def test_same_ordering(datapath):
+ pytest.importorskip("bs4")
+ pytest.importorskip("lxml")
+ pytest.importorskip("html5lib")
+
+ filename = datapath("io", "data", "html", "valid_markup.html")
+ dfs_lxml = read_html(filename, index_col=0, flavor=["lxml"])
+ dfs_bs4 = read_html(filename, index_col=0, flavor=["bs4"])
+ assert_framelist_equal(dfs_lxml, dfs_bs4)
+
+
+@pytest.fixture(
+ params=[
+ pytest.param("bs4", marks=[td.skip_if_no("bs4"), td.skip_if_no("html5lib")]),
+ pytest.param("lxml", marks=td.skip_if_no("lxml")),
+ ],
+)
+def flavor_read_html(request):
+ return partial(read_html, flavor=request.param)
+
+
+class TestReadHtml:
+ def test_literal_html_deprecation(self, flavor_read_html):
+ # GH 53785
+ msg = r"\[Errno 2\] No such file or director"
+
+ with pytest.raises(FileNotFoundError, match=msg):
+ flavor_read_html(
+ """
+
+
+ A
+ B
+
+
+
+
+ 1
+ 2
+
+
+
+
+ 3
+ 4
+
+
+
"""
+ )
+
+ @pytest.fixture
+ def spam_data(self, datapath):
+ return datapath("io", "data", "html", "spam.html")
+
+ @pytest.fixture
+ def banklist_data(self, datapath):
+ return datapath("io", "data", "html", "banklist.html")
+
+ def test_to_html_compat(self, flavor_read_html):
+ df = (
+ DataFrame(
+ np.random.default_rng(2).random((4, 3)),
+ columns=pd.Index(list("abc")),
+ )
+ .map("{:.3f}".format)
+ .astype(float)
+ )
+ out = df.to_html()
+ res = flavor_read_html(
+ StringIO(out), attrs={"class": "dataframe"}, index_col=0
+ )[0]
+ tm.assert_frame_equal(res, df)
+
+ def test_dtype_backend(self, string_storage, dtype_backend, flavor_read_html):
+ # GH#50286
+ df = DataFrame(
+ {
+ "a": Series([1, NA, 3], dtype="Int64"),
+ "b": Series([1, 2, 3], dtype="Int64"),
+ "c": Series([1.5, NA, 2.5], dtype="Float64"),
+ "d": Series([1.5, 2.0, 2.5], dtype="Float64"),
+ "e": [True, False, None],
+ "f": [True, False, True],
+ "g": ["a", "b", "c"],
+ "h": ["a", "b", None],
+ }
+ )
+
+ out = df.to_html(index=False)
+ with pd.option_context("mode.string_storage", string_storage):
+ result = flavor_read_html(StringIO(out), dtype_backend=dtype_backend)[0]
+
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ string_dtype = pd.ArrowDtype(pa.string())
+ else:
+ string_dtype = pd.StringDtype(string_storage)
+
+ expected = DataFrame(
+ {
+ "a": Series([1, NA, 3], dtype="Int64"),
+ "b": Series([1, 2, 3], dtype="Int64"),
+ "c": Series([1.5, NA, 2.5], dtype="Float64"),
+ "d": Series([1.5, 2.0, 2.5], dtype="Float64"),
+ "e": Series([True, False, NA], dtype="boolean"),
+ "f": Series([True, False, True], dtype="boolean"),
+ "g": Series(["a", "b", "c"], dtype=string_dtype),
+ "h": Series(["a", "b", None], dtype=string_dtype),
+ }
+ )
+
+ if dtype_backend == "pyarrow":
+ import pyarrow as pa
+
+ from pandas.arrays import ArrowExtensionArray
+
+ expected = DataFrame(
+ {
+ col: ArrowExtensionArray(pa.array(expected[col], from_pandas=True))
+ for col in expected.columns
+ }
+ )
+
+ # the storage of the str columns' Index is also affected by the
+ # string_storage setting -> ignore that for checking the result
+ tm.assert_frame_equal(result, expected, check_column_type=False)
+
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_banklist_url(self, httpserver, banklist_data, flavor_read_html):
+ with open(banklist_data, encoding="utf-8") as f:
+ httpserver.serve_content(content=f.read())
+ df1 = flavor_read_html(
+ # lxml cannot find attrs leave out for now
+ httpserver.url,
+ match="First Federal Bank of Florida", # attrs={"class": "dataTable"}
+ )
+ # lxml cannot find attrs leave out for now
+ df2 = flavor_read_html(
+ httpserver.url,
+ match="Metcalf Bank",
+ ) # attrs={"class": "dataTable"})
+
+ assert_framelist_equal(df1, df2)
+
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_spam_url(self, httpserver, spam_data, flavor_read_html):
+ with open(spam_data, encoding="utf-8") as f:
+ httpserver.serve_content(content=f.read())
+ df1 = flavor_read_html(httpserver.url, match=".*Water.*")
+ df2 = flavor_read_html(httpserver.url, match="Unit")
+
+ assert_framelist_equal(df1, df2)
+
+ @pytest.mark.slow
+ def test_banklist(self, banklist_data, flavor_read_html):
+ df1 = flavor_read_html(
+ banklist_data, match=".*Florida.*", attrs={"id": "table"}
+ )
+ df2 = flavor_read_html(
+ banklist_data, match="Metcalf Bank", attrs={"id": "table"}
+ )
+
+ assert_framelist_equal(df1, df2)
+
+ def test_spam(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*")
+ df2 = flavor_read_html(spam_data, match="Unit")
+ assert_framelist_equal(df1, df2)
+
+ assert df1[0].iloc[0, 0] == "Proximates"
+ assert df1[0].columns[0] == "Nutrient"
+
+ def test_spam_no_match(self, spam_data, flavor_read_html):
+ dfs = flavor_read_html(spam_data)
+ for df in dfs:
+ assert isinstance(df, DataFrame)
+
+ def test_banklist_no_match(self, banklist_data, flavor_read_html):
+ dfs = flavor_read_html(banklist_data, attrs={"id": "table"})
+ for df in dfs:
+ assert isinstance(df, DataFrame)
+
+ def test_spam_header(self, spam_data, flavor_read_html):
+ df = flavor_read_html(spam_data, match=".*Water.*", header=2)[0]
+ assert df.columns[0] == "Proximates"
+ assert not df.empty
+
+ def test_skiprows_int(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", skiprows=1)
+ df2 = flavor_read_html(spam_data, match="Unit", skiprows=1)
+
+ assert_framelist_equal(df1, df2)
+
+ def test_skiprows_range(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", skiprows=range(2))
+ df2 = flavor_read_html(spam_data, match="Unit", skiprows=range(2))
+
+ assert_framelist_equal(df1, df2)
+
+ def test_skiprows_list(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", skiprows=[1, 2])
+ df2 = flavor_read_html(spam_data, match="Unit", skiprows=[2, 1])
+
+ assert_framelist_equal(df1, df2)
+
+ def test_skiprows_set(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", skiprows={1, 2})
+ df2 = flavor_read_html(spam_data, match="Unit", skiprows={2, 1})
+
+ assert_framelist_equal(df1, df2)
+
+ def test_skiprows_slice(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", skiprows=1)
+ df2 = flavor_read_html(spam_data, match="Unit", skiprows=1)
+
+ assert_framelist_equal(df1, df2)
+
+ def test_skiprows_slice_short(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", skiprows=slice(2))
+ df2 = flavor_read_html(spam_data, match="Unit", skiprows=slice(2))
+
+ assert_framelist_equal(df1, df2)
+
+ def test_skiprows_slice_long(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", skiprows=slice(2, 5))
+ df2 = flavor_read_html(spam_data, match="Unit", skiprows=slice(4, 1, -1))
+
+ assert_framelist_equal(df1, df2)
+
+ def test_skiprows_ndarray(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", skiprows=np.arange(2))
+ df2 = flavor_read_html(spam_data, match="Unit", skiprows=np.arange(2))
+
+ assert_framelist_equal(df1, df2)
+
+ def test_skiprows_invalid(self, spam_data, flavor_read_html):
+ with pytest.raises(TypeError, match=("is not a valid type for skipping rows")):
+ flavor_read_html(spam_data, match=".*Water.*", skiprows="asdf")
+
+ def test_index(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", index_col=0)
+ df2 = flavor_read_html(spam_data, match="Unit", index_col=0)
+ assert_framelist_equal(df1, df2)
+
+ def test_header_and_index_no_types(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", header=1, index_col=0)
+ df2 = flavor_read_html(spam_data, match="Unit", header=1, index_col=0)
+ assert_framelist_equal(df1, df2)
+
+ def test_header_and_index_with_types(self, spam_data, flavor_read_html):
+ df1 = flavor_read_html(spam_data, match=".*Water.*", header=1, index_col=0)
+ df2 = flavor_read_html(spam_data, match="Unit", header=1, index_col=0)
+ assert_framelist_equal(df1, df2)
+
+ def test_infer_types(self, spam_data, flavor_read_html):
+ # 10892 infer_types removed
+ df1 = flavor_read_html(spam_data, match=".*Water.*", index_col=0)
+ df2 = flavor_read_html(spam_data, match="Unit", index_col=0)
+ assert_framelist_equal(df1, df2)
+
+ def test_string_io(self, spam_data, flavor_read_html):
+ with open(spam_data, encoding="UTF-8") as f:
+ data1 = StringIO(f.read())
+
+ with open(spam_data, encoding="UTF-8") as f:
+ data2 = StringIO(f.read())
+
+ df1 = flavor_read_html(data1, match=".*Water.*")
+ df2 = flavor_read_html(data2, match="Unit")
+ assert_framelist_equal(df1, df2)
+
+ def test_string(self, spam_data, flavor_read_html):
+ with open(spam_data, encoding="UTF-8") as f:
+ data = f.read()
+
+ df1 = flavor_read_html(StringIO(data), match=".*Water.*")
+ df2 = flavor_read_html(StringIO(data), match="Unit")
+
+ assert_framelist_equal(df1, df2)
+
+ def test_file_like(self, spam_data, flavor_read_html):
+ with open(spam_data, encoding="UTF-8") as f:
+ df1 = flavor_read_html(f, match=".*Water.*")
+
+ with open(spam_data, encoding="UTF-8") as f:
+ df2 = flavor_read_html(f, match="Unit")
+
+ assert_framelist_equal(df1, df2)
+
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_bad_url_protocol(self, httpserver, flavor_read_html):
+ httpserver.serve_content("urlopen error unknown url type: git", code=404)
+ with pytest.raises(URLError, match="urlopen error unknown url type: git"):
+ flavor_read_html("git://github.com", match=".*Water.*")
+
+ @pytest.mark.slow
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_invalid_url(self, httpserver, flavor_read_html):
+ httpserver.serve_content("Name or service not known", code=404)
+ try:
+ with pytest.raises(
+ (URLError, ValueError), match="HTTP Error 404: NOT FOUND"
+ ) as err:
+ flavor_read_html(httpserver.url, match=".*Water.*")
+ finally:
+ if isinstance(err.value, URLError):
+ # Has a file-like handle that we can close
+ # https://docs.python.org/3/library/urllib.error.html#urllib.error.HTTPError
+ err.value.close()
+
+ @pytest.mark.slow
+ def test_file_url(self, banklist_data, flavor_read_html):
+ url = banklist_data
+ dfs = flavor_read_html(
+ file_path_to_url(os.path.abspath(url)), match="First", attrs={"id": "table"}
+ )
+ assert isinstance(dfs, list)
+ for df in dfs:
+ assert isinstance(df, DataFrame)
+
+ @pytest.mark.slow
+ def test_invalid_table_attrs(self, banklist_data, flavor_read_html):
+ url = banklist_data
+ with pytest.raises(ValueError, match="No tables found"):
+ flavor_read_html(
+ url, match="First Federal Bank of Florida", attrs={"id": "tasdfable"}
+ )
+
+ @pytest.mark.slow
+ def test_multiindex_header(self, banklist_data, flavor_read_html):
+ df = flavor_read_html(
+ banklist_data, match="Metcalf", attrs={"id": "table"}, header=[0, 1]
+ )[0]
+ assert isinstance(df.columns, MultiIndex)
+
+ @pytest.mark.slow
+ def test_multiindex_index(self, banklist_data, flavor_read_html):
+ df = flavor_read_html(
+ banklist_data, match="Metcalf", attrs={"id": "table"}, index_col=[0, 1]
+ )[0]
+ assert isinstance(df.index, MultiIndex)
+
+ @pytest.mark.slow
+ def test_multiindex_header_index(self, banklist_data, flavor_read_html):
+ df = flavor_read_html(
+ banklist_data,
+ match="Metcalf",
+ attrs={"id": "table"},
+ header=[0, 1],
+ index_col=[0, 1],
+ )[0]
+ assert isinstance(df.columns, MultiIndex)
+ assert isinstance(df.index, MultiIndex)
+
+ @pytest.mark.slow
+ def test_multiindex_header_skiprows_tuples(self, banklist_data, flavor_read_html):
+ df = flavor_read_html(
+ banklist_data,
+ match="Metcalf",
+ attrs={"id": "table"},
+ header=[0, 1],
+ skiprows=1,
+ )[0]
+ assert isinstance(df.columns, MultiIndex)
+
+ @pytest.mark.slow
+ def test_multiindex_header_skiprows(self, banklist_data, flavor_read_html):
+ df = flavor_read_html(
+ banklist_data,
+ match="Metcalf",
+ attrs={"id": "table"},
+ header=[0, 1],
+ skiprows=1,
+ )[0]
+ assert isinstance(df.columns, MultiIndex)
+
+ @pytest.mark.slow
+ def test_multiindex_header_index_skiprows(self, banklist_data, flavor_read_html):
+ df = flavor_read_html(
+ banklist_data,
+ match="Metcalf",
+ attrs={"id": "table"},
+ header=[0, 1],
+ index_col=[0, 1],
+ skiprows=1,
+ )[0]
+ assert isinstance(df.index, MultiIndex)
+ assert isinstance(df.columns, MultiIndex)
+
+ @pytest.mark.slow
+ def test_regex_idempotency(self, banklist_data, flavor_read_html):
+ url = banklist_data
+ dfs = flavor_read_html(
+ file_path_to_url(os.path.abspath(url)),
+ match=re.compile(re.compile("Florida")),
+ attrs={"id": "table"},
+ )
+ assert isinstance(dfs, list)
+ for df in dfs:
+ assert isinstance(df, DataFrame)
+
+ def test_negative_skiprows(self, spam_data, flavor_read_html):
+ msg = r"\(you passed a negative value\)"
+ with pytest.raises(ValueError, match=msg):
+ flavor_read_html(spam_data, match="Water", skiprows=-1)
+
+ @pytest.fixture
+ def python_docs(self):
+ return """
+
+
+ What's new in Python 2.7?
+ or all "What's new" documents since 2.0
+ Tutorial
+ start here
+ Library Reference
+ keep this under your pillow
+ Language Reference
+ describes syntax and language elements
+ Python Setup and Usage
+ how to use Python on different platforms
+ Python HOWTOs
+ in-depth documents on specific topics
+
+ Installing Python Modules
+ installing from the Python Package Index & other sources
+ Distributing Python Modules
+ publishing modules for installation by others
+ Extending and Embedding
+ tutorial for C/C++ programmers
+ Python/C API
+ reference for C/C++ programmers
+ FAQs
+ frequently asked questions (with answers!)
+
+
+
+ Indices and tables:
+
+
+ Python Global Module Index
+ quick access to all modules
+ General Index
+ all functions, classes, terms
+ Glossary
+ the most important terms explained
+
+ Search page
+ search this documentation
+ Complete Table of Contents
+ lists all sections and subsections
+
+
+ """ # noqa: E501
+
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_multiple_matches(self, python_docs, httpserver, flavor_read_html):
+ httpserver.serve_content(content=python_docs)
+ dfs = flavor_read_html(httpserver.url, match="Python")
+ assert len(dfs) > 1
+
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_python_docs_table(self, python_docs, httpserver, flavor_read_html):
+ httpserver.serve_content(content=python_docs)
+ dfs = flavor_read_html(httpserver.url, match="Python")
+ zz = [df.iloc[0, 0][0:4] for df in dfs]
+ assert sorted(zz) == ["Pyth", "What"]
+
+ def test_empty_tables(self, flavor_read_html):
+ """
+ Make sure that read_html ignores empty tables.
+ """
+ html = """
+
+
+
+ A
+ B
+
+
+
+
+ 1
+ 2
+
+
+
+
+
+
+
+ """
+ result = flavor_read_html(StringIO(html))
+ assert len(result) == 1
+
+ def test_multiple_tbody(self, flavor_read_html):
+ # GH-20690
+ # Read all tbody tags within a single table.
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ A
+ B
+
+
+
+
+ 1
+ 2
+
+
+
+
+ 3
+ 4
+
+
+
"""
+ )
+ )[0]
+
+ expected = DataFrame(data=[[1, 2], [3, 4]], columns=["A", "B"])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_header_and_one_column(self, flavor_read_html):
+ """
+ Don't fail with bs4 when there is a header and only one column
+ as described in issue #9178
+ """
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ Header
+
+
+
+
+ first
+
+
+
"""
+ )
+ )[0]
+
+ expected = DataFrame(data={"Header": "first"}, index=[0])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_thead_without_tr(self, flavor_read_html):
+ """
+ Ensure parser adds within on malformed HTML.
+ """
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ Country
+ Municipality
+ Year
+
+
+
+
+ Ukraine
+ Odessa
+ 1944
+
+
+
"""
+ )
+ )[0]
+
+ expected = DataFrame(
+ data=[["Ukraine", "Odessa", 1944]],
+ columns=["Country", "Municipality", "Year"],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_tfoot_read(self, flavor_read_html):
+ """
+ Make sure that read_html reads tfoot, containing td or th.
+ Ignores empty tfoot
+ """
+ data_template = """
+
+
+ A
+ B
+
+
+
+
+ bodyA
+ bodyB
+
+
+
+ {footer}
+
+
"""
+
+ expected1 = DataFrame(data=[["bodyA", "bodyB"]], columns=["A", "B"])
+
+ expected2 = DataFrame(
+ data=[["bodyA", "bodyB"], ["footA", "footB"]], columns=["A", "B"]
+ )
+
+ data1 = data_template.format(footer="")
+ data2 = data_template.format(footer="footA footB ")
+
+ result1 = flavor_read_html(StringIO(data1))[0]
+ result2 = flavor_read_html(StringIO(data2))[0]
+
+ tm.assert_frame_equal(result1, expected1)
+ tm.assert_frame_equal(result2, expected2)
+
+ def test_parse_header_of_non_string_column(self, flavor_read_html):
+ # GH5048: if header is specified explicitly, an int column should be
+ # parsed as int while its header is parsed as str
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ S
+ I
+
+
+ text
+ 1944
+
+
+ """
+ ),
+ header=0,
+ )[0]
+
+ expected = DataFrame([["text", 1944]], columns=("S", "I"))
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.slow
+ def test_banklist_header(self, banklist_data, datapath, flavor_read_html):
+ from pandas.io.html import _remove_whitespace
+
+ def try_remove_ws(x):
+ try:
+ return _remove_whitespace(x)
+ except AttributeError:
+ return x
+
+ df = flavor_read_html(banklist_data, match="Metcalf", attrs={"id": "table"})[0]
+ ground_truth = read_csv(
+ datapath("io", "data", "csv", "banklist.csv"),
+ converters={"Updated Date": Timestamp, "Closing Date": Timestamp},
+ )
+ # html is a truncated version of banklist since bs4 is slow to parse it
+ assert df.shape == (len(df), ground_truth.shape[1])
+ old = [
+ "First Vietnamese American Bank In Vietnamese",
+ "Westernbank Puerto Rico En Espanol",
+ "R-G Premier Bank of Puerto Rico En Espanol",
+ "Eurobank En Espanol",
+ "Sanderson State Bank En Espanol",
+ "Washington Mutual Bank (Including its subsidiary Washington "
+ "Mutual Bank FSB)",
+ "Silver State Bank En Espanol",
+ "AmTrade International Bank En Espanol",
+ "Hamilton Bank, NA En Espanol",
+ "The Citizens Savings Bank Pioneer Community Bank, Inc.",
+ ]
+ new = [
+ "First Vietnamese American Bank",
+ "Westernbank Puerto Rico",
+ "R-G Premier Bank of Puerto Rico",
+ "Eurobank",
+ "Sanderson State Bank",
+ "Washington Mutual Bank",
+ "Silver State Bank",
+ "AmTrade International Bank",
+ "Hamilton Bank, NA",
+ "The Citizens Savings Bank",
+ ]
+ dfnew = df.map(try_remove_ws).replace(old, new)
+ gtnew = ground_truth.map(try_remove_ws)
+ converted = dfnew
+ date_cols = ["Closing Date", "Updated Date"]
+ converted[date_cols] = converted[date_cols].apply(to_datetime)
+ gtnew = gtnew[gtnew["Bank Name"].isin(converted["Bank Name"])].reset_index(
+ drop=True
+ )
+ tm.assert_frame_equal(converted, gtnew)
+
+ @pytest.mark.slow
+ def test_heartland_bank(self, banklist_data, flavor_read_html):
+ gc = "Heartland Bank"
+ with open(banklist_data, encoding="utf-8") as f:
+ raw_text = f.read()
+
+ assert gc in raw_text
+ df = flavor_read_html(banklist_data, match=gc, attrs={"id": "table"})[0]
+ assert gc in df.to_string()
+
+ def test_different_number_of_cols(self, flavor_read_html):
+ expected = flavor_read_html(
+ StringIO(
+ """
+
+
+
+ C_l0_g0
+ C_l0_g1
+ C_l0_g2
+ C_l0_g3
+ C_l0_g4
+
+
+
+
+ R_l0_g0
+ 0.763
+ 0.233
+ nan
+ nan
+ nan
+
+
+ R_l0_g1
+ 0.244
+ 0.285
+ 0.392
+ 0.137
+ 0.222
+
+
+
"""
+ ),
+ index_col=0,
+ )[0]
+
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+
+ C_l0_g0
+ C_l0_g1
+ C_l0_g2
+ C_l0_g3
+ C_l0_g4
+
+
+
+
+ R_l0_g0
+ 0.763
+ 0.233
+
+
+ R_l0_g1
+ 0.244
+ 0.285
+ 0.392
+ 0.137
+ 0.222
+
+
+
"""
+ ),
+ index_col=0,
+ )[0]
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_colspan_rowspan_1(self, flavor_read_html):
+ # GH17054
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ A
+ B
+ C
+
+
+ a
+ b
+ c
+
+
+ """
+ )
+ )[0]
+
+ expected = DataFrame([["a", "b", "c"]], columns=["A", "B", "C"])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_colspan_rowspan_copy_values(self, flavor_read_html):
+ # GH17054
+
+ # In ASCII, with lowercase letters being copies:
+ #
+ # X x Y Z W
+ # A B b z C
+
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ X
+ Y
+ Z
+ W
+
+
+ A
+ B
+ C
+
+
+ """
+ ),
+ header=0,
+ )[0]
+
+ expected = DataFrame(
+ data=[["A", "B", "B", "Z", "C"]], columns=["X", "X.1", "Y", "Z", "W"]
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_colspan_rowspan_both_not_1(self, flavor_read_html):
+ # GH17054
+
+ # In ASCII, with lowercase letters being copies:
+ #
+ # A B b b C
+ # a b b b D
+
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ A
+ B
+ C
+
+
+ D
+
+
+ """
+ ),
+ header=0,
+ )[0]
+
+ expected = DataFrame(
+ data=[["A", "B", "B", "B", "D"]], columns=["A", "B", "B.1", "B.2", "C"]
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_rowspan_at_end_of_row(self, flavor_read_html):
+ # GH17054
+
+ # In ASCII, with lowercase letters being copies:
+ #
+ # A B
+ # C b
+
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ A
+ B
+
+
+ C
+
+
+ """
+ ),
+ header=0,
+ )[0]
+
+ expected = DataFrame(data=[["C", "B"]], columns=["A", "B"])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_rowspan_only_rows(self, flavor_read_html):
+ # GH17054
+
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ A
+ B
+
+
+ """
+ ),
+ header=0,
+ )[0]
+
+ expected = DataFrame(data=[["A", "B"], ["A", "B"]], columns=["A", "B"])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_rowspan_in_header_overflowing_to_body(self, flavor_read_html):
+ # GH60210
+
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ A
+ B
+
+
+ 1
+
+
+ C
+ 2
+
+
+ """
+ )
+ )[0]
+
+ expected = DataFrame(data=[["A", 1], ["C", 2]], columns=["A", "B"])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_header_inferred_from_rows_with_only_th(self, flavor_read_html):
+ # GH17054
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ A
+ B
+
+
+ a
+ b
+
+
+ 1
+ 2
+
+
+ """
+ )
+ )[0]
+
+ columns = MultiIndex(levels=[["A", "B"], ["a", "b"]], codes=[[0, 1], [0, 1]])
+ expected = DataFrame(data=[[1, 2]], columns=columns)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_parse_dates_list(self, flavor_read_html):
+ df = DataFrame({"date": date_range("1/1/2001", periods=10)})
+
+ expected = df[:]
+ expected["date"] = expected["date"].dt.as_unit("us")
+
+ str_df = df.to_html()
+ res = flavor_read_html(StringIO(str_df), parse_dates=[1], index_col=0)
+ tm.assert_frame_equal(expected, res[0])
+ res = flavor_read_html(StringIO(str_df), parse_dates=["date"], index_col=0)
+ tm.assert_frame_equal(expected, res[0])
+
+ def test_wikipedia_states_table(self, datapath, flavor_read_html):
+ data = datapath("io", "data", "html", "wikipedia_states.html")
+ assert os.path.isfile(data), f"{data!r} is not a file"
+ assert os.path.getsize(data), f"{data!r} is an empty file"
+ result = flavor_read_html(data, match="Arizona", header=1)[0]
+ assert result.shape == (60, 12)
+ assert "Unnamed" in result.columns[-1]
+ assert result["sq mi"].dtype == np.dtype("float64")
+ assert np.allclose(result.loc[0, "sq mi"], 665384.04)
+
+ def test_wikipedia_states_multiindex(self, datapath, flavor_read_html):
+ data = datapath("io", "data", "html", "wikipedia_states.html")
+ result = flavor_read_html(data, match="Arizona", index_col=0)[0]
+ assert result.shape == (60, 11)
+ assert "Unnamed" in result.columns[-1][1]
+ assert result.columns.nlevels == 2
+ assert np.allclose(result.loc["Alaska", ("Total area[2]", "sq mi")], 665384.04)
+
+ def test_parser_error_on_empty_header_row(self, flavor_read_html):
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+
+ A B
+
+
+ a b
+
+
+ """
+ ),
+ header=[0, 1],
+ )
+ expected = DataFrame(
+ [["a", "b"]],
+ columns=MultiIndex.from_tuples(
+ [("Unnamed: 0_level_0", "A"), ("Unnamed: 1_level_0", "B")]
+ ),
+ )
+ tm.assert_frame_equal(result[0], expected)
+
+ def test_decimal_rows(self, flavor_read_html):
+ # GH 12907
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+
+
+ Header
+
+
+
+
+ 1100#101
+
+
+
+
+ """
+ ),
+ decimal="#",
+ )[0]
+
+ expected = DataFrame(data={"Header": 1100.101}, index=[0])
+
+ assert result["Header"].dtype == np.dtype("float64")
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("arg", [True, False])
+ def test_bool_header_arg(self, spam_data, arg, flavor_read_html):
+ # GH 6114
+ msg = re.escape(
+ "Passing a bool to header is invalid. Use header=None for no header or "
+ "header=int or list-like of ints to specify the row(s) making up the "
+ "column names"
+ )
+ with pytest.raises(TypeError, match=msg):
+ flavor_read_html(spam_data, header=arg)
+
+ def test_converters(self, flavor_read_html):
+ # GH 13461
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ a
+
+
+
+
+ 0.763
+
+
+ 0.244
+
+
+
"""
+ ),
+ converters={"a": str},
+ )[0]
+
+ expected = DataFrame({"a": ["0.763", "0.244"]})
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_na_values(self, flavor_read_html):
+ # GH 13461
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ a
+
+
+
+
+ 0.763
+
+
+ 0.244
+
+
+
"""
+ ),
+ na_values=[0.244],
+ )[0]
+
+ expected = DataFrame({"a": [0.763, np.nan]})
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_keep_default_na(self, flavor_read_html):
+ html_data = """
+
+
+ a
+
+
+
+
+ N/A
+
+
+ NA
+
+
+
"""
+
+ expected_df = DataFrame({"a": ["N/A", "NA"]})
+ html_df = flavor_read_html(StringIO(html_data), keep_default_na=False)[0]
+ tm.assert_frame_equal(expected_df, html_df)
+
+ expected_df = DataFrame({"a": [np.nan, np.nan]})
+ html_df = flavor_read_html(StringIO(html_data), keep_default_na=True)[0]
+ tm.assert_frame_equal(expected_df, html_df)
+
+ def test_preserve_empty_rows(self, flavor_read_html):
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ A
+ B
+
+
+ a
+ b
+
+
+
+
+
+
+ """
+ )
+ )[0]
+
+ expected = DataFrame(data=[["a", "b"], [np.nan, np.nan]], columns=["A", "B"])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_ignore_empty_rows_when_inferring_header(self, flavor_read_html):
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+
+ A B
+ a b
+
+
+ 1 2
+
+
+ """
+ )
+ )[0]
+
+ columns = MultiIndex(levels=[["A", "B"], ["a", "b"]], codes=[[0, 1], [0, 1]])
+ expected = DataFrame(data=[[1, 2]], columns=columns)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_multiple_header_rows(self, flavor_read_html):
+ # Issue #13434
+ expected_df = DataFrame(
+ data=[("Hillary", 68, "D"), ("Bernie", 74, "D"), ("Donald", 69, "R")]
+ )
+ expected_df.columns = [
+ ["Unnamed: 0_level_0", "Age", "Party"],
+ ["Name", "Unnamed: 1_level_1", "Unnamed: 2_level_1"],
+ ]
+ html = expected_df.to_html(index=False)
+ html_df = flavor_read_html(StringIO(html))[0]
+ tm.assert_frame_equal(expected_df, html_df)
+
+ def test_works_on_valid_markup(self, datapath, flavor_read_html):
+ filename = datapath("io", "data", "html", "valid_markup.html")
+ dfs = flavor_read_html(filename, index_col=0)
+ assert isinstance(dfs, list)
+ assert isinstance(dfs[0], DataFrame)
+
+ @pytest.mark.slow
+ def test_fallback_success(self, datapath, flavor_read_html):
+ banklist_data = datapath("io", "data", "html", "banklist.html")
+
+ flavor_read_html(banklist_data, match=".*Water.*", flavor=["lxml", "html5lib"])
+
+ def test_to_html_timestamp(self):
+ rng = date_range("2000-01-01", periods=10)
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)), index=rng)
+
+ result = df.to_html()
+ assert "2000-01-01" in result
+
+ def test_to_html_borderless(self):
+ df = DataFrame([{"A": 1, "B": 2}])
+ out_border_default = df.to_html()
+ out_border_true = df.to_html(border=True)
+ out_border_explicit_default = df.to_html(border=1)
+ out_border_nondefault = df.to_html(border=2)
+ out_border_zero = df.to_html(border=0)
+
+ out_border_false = df.to_html(border=False)
+
+ assert ' border="1"' in out_border_default
+ assert out_border_true == out_border_default
+ assert out_border_default == out_border_explicit_default
+ assert out_border_default != out_border_nondefault
+ assert ' border="2"' in out_border_nondefault
+ assert ' border="0"' not in out_border_zero
+ assert " border" not in out_border_false
+ assert out_border_zero == out_border_false
+
+ @pytest.mark.parametrize(
+ "displayed_only,exp0,exp1",
+ [
+ (True, ["foo"], None),
+ (False, ["foo bar baz qux"], DataFrame(["foo"])),
+ ],
+ )
+ def test_displayed_only(self, displayed_only, exp0, exp1, flavor_read_html):
+ # GH 20027
+ data = """
+
+
+
+
+ foo
+
+
+
+
+
+
+
+
+ """
+
+ exp0 = DataFrame(exp0)
+ dfs = flavor_read_html(StringIO(data), displayed_only=displayed_only)
+ tm.assert_frame_equal(dfs[0], exp0)
+
+ if exp1 is not None:
+ tm.assert_frame_equal(dfs[1], exp1)
+ else:
+ assert len(dfs) == 1 # Should not parse hidden table
+
+ @pytest.mark.parametrize("displayed_only", [True, False])
+ def test_displayed_only_with_many_elements(self, displayed_only, flavor_read_html):
+ html_table = """
+
+
+ A
+ B
+
+
+ 1
+ 2
+
+
+ 4
+ 5
+
+
+ """
+ result = flavor_read_html(StringIO(html_table), displayed_only=displayed_only)[
+ 0
+ ]
+ expected = DataFrame({"A": [1, 4], "B": [2, 5]})
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.filterwarnings(
+ "ignore:You provided Unicode markup but also provided a value for "
+ "from_encoding.*:UserWarning"
+ )
+ def test_encode(self, html_encoding_file, flavor_read_html):
+ base_path = os.path.basename(html_encoding_file)
+ root = os.path.splitext(base_path)[0]
+ _, encoding = root.split("_")
+
+ try:
+ with open(html_encoding_file, "rb") as fobj:
+ from_string = flavor_read_html(
+ BytesIO(fobj.read()), encoding=encoding, index_col=0
+ ).pop()
+
+ with open(html_encoding_file, "rb") as fobj:
+ from_file_like = flavor_read_html(
+ BytesIO(fobj.read()), encoding=encoding, index_col=0
+ ).pop()
+
+ from_filename = flavor_read_html(
+ html_encoding_file, encoding=encoding, index_col=0
+ ).pop()
+ tm.assert_frame_equal(from_string, from_file_like)
+ tm.assert_frame_equal(from_string, from_filename)
+ except Exception:
+ # seems utf-16/32 fail on windows
+ if is_platform_windows():
+ if "16" in encoding or "32" in encoding:
+ pytest.skip()
+ raise
+
+ def test_parse_failure_unseekable(self, flavor_read_html):
+ # Issue #17975
+
+ if flavor_read_html.keywords.get("flavor") == "lxml":
+ pytest.skip("Not applicable for lxml")
+
+ class UnseekableStringIO(StringIO):
+ def seekable(self):
+ return False
+
+ bad = UnseekableStringIO(
+ """
+ spam eggs
"""
+ )
+
+ assert flavor_read_html(bad)
+
+ with pytest.raises(ValueError, match="passed a non-rewindable file object"):
+ flavor_read_html(bad)
+
+ def test_parse_failure_rewinds(self, flavor_read_html):
+ # Issue #17975
+
+ class MockFile:
+ def __init__(self, data) -> None:
+ self.data = data
+ self.at_end = False
+
+ def read(self, size=None):
+ data = "" if self.at_end else self.data
+ self.at_end = True
+ return data
+
+ def seek(self, offset):
+ self.at_end = False
+
+ def seekable(self):
+ return True
+
+ def __next__(self): ...
+
+ def __iter__(self) -> Iterator:
+ # `is_file_like` depends on the presence of
+ # the __iter__ attribute.
+ return self
+
+ good = MockFile("spam
eggs
")
+ bad = MockFile("spam eggs
")
+
+ assert flavor_read_html(good)
+ assert flavor_read_html(bad)
+
+ @pytest.mark.slow
+ @pytest.mark.single_cpu
+ def test_importcheck_thread_safety(self, datapath, flavor_read_html):
+ # see gh-16928
+
+ class ErrorThread(threading.Thread):
+ def run(self):
+ try:
+ super().run()
+ except Exception as err:
+ self.err = err
+ else:
+ self.err = None
+
+ filename = datapath("io", "data", "html", "valid_markup.html")
+ helper_thread1 = ErrorThread(target=flavor_read_html, args=(filename,))
+ helper_thread2 = ErrorThread(target=flavor_read_html, args=(filename,))
+
+ helper_thread1.start()
+ helper_thread2.start()
+
+ while helper_thread1.is_alive() or helper_thread2.is_alive():
+ pass
+ assert None is helper_thread1.err is helper_thread2.err
+
+ def test_parse_path_object(self, datapath, flavor_read_html):
+ # GH 37705
+ file_path_string = datapath("io", "data", "html", "spam.html")
+ file_path = Path(file_path_string)
+ df1 = flavor_read_html(file_path_string)[0]
+ df2 = flavor_read_html(file_path)[0]
+ tm.assert_frame_equal(df1, df2)
+
+ def test_parse_br_as_space(self, flavor_read_html):
+ # GH 29528: pd.read_html() convert
to space
+ result = flavor_read_html(
+ StringIO(
+ """
+
+
+ A
+
+
+ word1
word2
+
+
+ """
+ )
+ )[0]
+
+ expected = DataFrame(data=[["word1 word2"]], columns=["A"])
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("arg", ["all", "body", "header", "footer"])
+ def test_extract_links(self, arg, flavor_read_html):
+ gh_13141_data = """
+
+
+ HTTP
+ FTP
+ Linkless
+
+
+ Wikipedia
+ SURROUNDING Debian TEXT
+ Linkless
+
+
+
+ Footer
+
+ Multiple links: Only first captured.
+
+
+
+
+ """
+
+ gh_13141_expected = {
+ "head_ignore": ["HTTP", "FTP", "Linkless"],
+ "head_extract": [
+ ("HTTP", None),
+ ("FTP", None),
+ ("Linkless", "https://en.wiktionary.org/wiki/linkless"),
+ ],
+ "body_ignore": ["Wikipedia", "SURROUNDING Debian TEXT", "Linkless"],
+ "body_extract": [
+ ("Wikipedia", "https://en.wikipedia.org/"),
+ ("SURROUNDING Debian TEXT", "ftp://ftp.us.debian.org/"),
+ ("Linkless", None),
+ ],
+ "footer_ignore": [
+ "Footer",
+ "Multiple links: Only first captured.",
+ None,
+ ],
+ "footer_extract": [
+ ("Footer", "https://en.wikipedia.org/wiki/Page_footer"),
+ ("Multiple links: Only first captured.", "1"),
+ None,
+ ],
+ }
+
+ data_exp = gh_13141_expected["body_ignore"]
+ foot_exp = gh_13141_expected["footer_ignore"]
+ head_exp = gh_13141_expected["head_ignore"]
+ if arg == "all":
+ data_exp = gh_13141_expected["body_extract"]
+ foot_exp = gh_13141_expected["footer_extract"]
+ head_exp = gh_13141_expected["head_extract"]
+ elif arg == "body":
+ data_exp = gh_13141_expected["body_extract"]
+ elif arg == "footer":
+ foot_exp = gh_13141_expected["footer_extract"]
+ elif arg == "header":
+ head_exp = gh_13141_expected["head_extract"]
+
+ result = flavor_read_html(StringIO(gh_13141_data), extract_links=arg)[0]
+ expected = DataFrame([data_exp, foot_exp], columns=head_exp)
+ expected = expected.fillna(np.nan)
+ tm.assert_frame_equal(result, expected)
+
+ def test_extract_links_bad(self, spam_data):
+ msg = (
+ "`extract_links` must be one of "
+ '{None, "header", "footer", "body", "all"}, got "incorrect"'
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_html(spam_data, extract_links="incorrect")
+
+ def test_extract_links_all_no_header(self, flavor_read_html):
+ # GH 48316
+ data = """
+
+
+
+ Google.com
+
+
+
+ """
+ result = flavor_read_html(StringIO(data), extract_links="all")[0]
+ expected = DataFrame([[("Google.com", "https://google.com")]])
+ tm.assert_frame_equal(result, expected)
+
+ def test_invalid_dtype_backend(self):
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_html("test", dtype_backend="numpy")
+
+ def test_style_tag(self, flavor_read_html):
+ # GH 48316
+ data = """
+
+
+
+
+ A
+
+ B
+
+
+ A1
+ B1
+
+
+ A2
+ B2
+
+
+ """
+ result = flavor_read_html(StringIO(data))[0]
+ expected = DataFrame(data=[["A1", "B1"], ["A2", "B2"]], columns=["A", "B"])
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_http_headers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_http_headers.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b9c8769ad9dc909f99d346395d5cd2113984992
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_http_headers.py
@@ -0,0 +1,174 @@
+"""
+Tests for the pandas custom headers in http(s) requests
+"""
+
+from functools import partial
+import gzip
+from io import BytesIO
+
+import pytest
+
+from pandas._config import using_string_dtype
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+import pandas._testing as tm
+
+pytestmark = [
+ pytest.mark.single_cpu,
+ pytest.mark.network,
+ pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+ ),
+]
+
+
+def gzip_bytes(response_bytes):
+ with BytesIO() as bio:
+ with gzip.GzipFile(fileobj=bio, mode="w") as zipper:
+ zipper.write(response_bytes)
+ return bio.getvalue()
+
+
+def csv_responder(df):
+ return df.to_csv(index=False).encode("utf-8")
+
+
+def gz_csv_responder(df):
+ return gzip_bytes(csv_responder(df))
+
+
+def json_responder(df):
+ return df.to_json().encode("utf-8")
+
+
+def gz_json_responder(df):
+ return gzip_bytes(json_responder(df))
+
+
+def html_responder(df):
+ return df.to_html(index=False).encode("utf-8")
+
+
+def parquetpyarrow_reponder(df):
+ return df.to_parquet(index=False, engine="pyarrow")
+
+
+def parquetfastparquet_responder(df):
+ # the fastparquet engine doesn't like to write to a buffer
+ # it can do it via the open_with function being set appropriately
+ # however it automatically calls the close method and wipes the buffer
+ # so just overwrite that attribute on this instance to not do that
+
+ # protected by an importorskip in the respective test
+ import fsspec
+
+ df.to_parquet(
+ "memory://fastparquet_user_agent.parquet",
+ index=False,
+ engine="fastparquet",
+ compression=None,
+ )
+ with fsspec.open("memory://fastparquet_user_agent.parquet", "rb") as f:
+ return f.read()
+
+
+def pickle_respnder(df):
+ with BytesIO() as bio:
+ df.to_pickle(bio)
+ return bio.getvalue()
+
+
+def stata_responder(df):
+ with BytesIO() as bio:
+ df.to_stata(bio, write_index=False)
+ return bio.getvalue()
+
+
+@pytest.mark.parametrize(
+ "responder, read_method",
+ [
+ (csv_responder, pd.read_csv),
+ (json_responder, pd.read_json),
+ (
+ html_responder,
+ lambda *args, **kwargs: pd.read_html(*args, **kwargs)[0],
+ ),
+ pytest.param(
+ parquetpyarrow_reponder,
+ partial(pd.read_parquet, engine="pyarrow"),
+ marks=td.skip_if_no("pyarrow"),
+ ),
+ pytest.param(
+ parquetfastparquet_responder,
+ partial(pd.read_parquet, engine="fastparquet"),
+ marks=[
+ td.skip_if_no("fastparquet"),
+ td.skip_if_no("fsspec"),
+ pytest.mark.xfail(using_string_dtype(), reason="TODO(infer_string"),
+ ],
+ ),
+ (pickle_respnder, pd.read_pickle),
+ (stata_responder, pd.read_stata),
+ (gz_csv_responder, pd.read_csv),
+ (gz_json_responder, pd.read_json),
+ ],
+)
+@pytest.mark.parametrize(
+ "storage_options",
+ [
+ None,
+ {"User-Agent": "foo"},
+ {"User-Agent": "foo", "Auth": "bar"},
+ ],
+)
+def test_request_headers(responder, read_method, httpserver, storage_options):
+ expected = pd.DataFrame({"a": ["b"]})
+ default_headers = ["Accept-Encoding", "Host", "Connection", "User-Agent"]
+ if "gz" in responder.__name__:
+ extra = {"Content-Encoding": "gzip"}
+ if storage_options is None:
+ storage_options = extra
+ else:
+ storage_options |= extra
+ else:
+ extra = None
+ expected_headers = set(default_headers).union(
+ storage_options.keys() if storage_options else []
+ )
+ httpserver.serve_content(content=responder(expected), headers=extra)
+ result = read_method(httpserver.url, storage_options=storage_options)
+ tm.assert_frame_equal(result, expected)
+
+ request_headers = dict(httpserver.requests[0].headers)
+ for header in expected_headers:
+ exp = request_headers.pop(header)
+ if storage_options and header in storage_options:
+ assert exp == storage_options[header]
+ # No extra headers added
+ assert not request_headers
+
+
+@pytest.mark.parametrize(
+ "engine",
+ [
+ "pyarrow",
+ "fastparquet",
+ ],
+)
+def test_to_parquet_to_disk_with_storage_options(engine):
+ headers = {
+ "User-Agent": "custom",
+ "Auth": "other_custom",
+ }
+
+ pytest.importorskip(engine)
+
+ true_df = pd.DataFrame({"column_name": ["column_value"]})
+ msg = (
+ "storage_options passed with file object or non-fsspec file path|"
+ "storage_options passed with buffer, or non-supported URL"
+ )
+ with pytest.raises(ValueError, match=msg):
+ true_df.to_parquet("/tmp/junk.parquet", storage_options=headers, engine=engine)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_iceberg.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_iceberg.py
new file mode 100644
index 0000000000000000000000000000000000000000..689eddb1985e6344d72c26fbb30a5237489cdadd
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_iceberg.py
@@ -0,0 +1,222 @@
+"""
+Tests for the Apache Iceberg format.
+
+Tests in this file use a simple Iceberg catalog based on SQLite, with the same
+data used for Parquet tests (``pandas/tests/io/data/parquet/simple.parquet``).
+"""
+
+import collections
+import importlib
+import pathlib
+
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+from pandas.io.iceberg import read_iceberg
+
+pytestmark = pytest.mark.single_cpu
+
+pyiceberg = pytest.importorskip("pyiceberg")
+pyiceberg_catalog = pytest.importorskip("pyiceberg.catalog")
+pq = pytest.importorskip("pyarrow.parquet")
+
+Catalog = collections.namedtuple("Catalog", ["name", "uri", "warehouse"])
+
+
+@pytest.fixture
+def catalog(request, tmp_path):
+ # the catalog stores the full path of data files, so the catalog needs to be
+ # created dynamically, and not saved in pandas/tests/io/data as other formats
+ uri = f"sqlite:///{tmp_path}/catalog.sqlite"
+ warehouse = f"file://{tmp_path}"
+ catalog_name = request.param if hasattr(request, "param") else None
+ catalog = pyiceberg_catalog.load_catalog(
+ catalog_name or "default",
+ type="sql",
+ uri=uri,
+ warehouse=warehouse,
+ )
+ catalog.create_namespace("ns")
+
+ df = pq.read_table(
+ pathlib.Path(__file__).parent / "data" / "parquet" / "simple.parquet"
+ )
+ table = catalog.create_table("ns.my_table", schema=df.schema)
+ table.append(df)
+
+ if catalog_name is not None:
+ config_path = pathlib.Path.home() / ".pyiceberg.yaml"
+ with open(config_path, "w", encoding="utf-8") as f:
+ f.write(f"""\
+catalog:
+ {catalog_name}:
+ type: sql
+ uri: {uri}
+ warehouse: {warehouse}""")
+
+ importlib.reload(pyiceberg_catalog) # needed to reload the config file
+
+ yield Catalog(name=catalog_name or "default", uri=uri, warehouse=warehouse)
+
+ if catalog_name is not None:
+ config_path.unlink()
+
+
+class TestIceberg:
+ def test_read(self, catalog):
+ expected = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": ["foo", "foo", "foo"],
+ }
+ )
+ result = read_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("catalog", ["default", "pandas_tests"], indirect=True)
+ def test_read_by_catalog_name(self, catalog):
+ expected = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": ["foo", "foo", "foo"],
+ }
+ )
+ result = read_iceberg(
+ "ns.my_table",
+ catalog_name=catalog.name,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_read_with_row_filter(self, catalog):
+ expected = pd.DataFrame(
+ {
+ "A": [2, 3],
+ "B": ["foo", "foo"],
+ }
+ )
+ result = read_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ row_filter="A > 1",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_read_with_case_sensitive(self, catalog):
+ expected = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ }
+ )
+ result = read_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ columns=["a"],
+ case_sensitive=False,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ with pytest.raises(ValueError, match="^Could not find column"):
+ read_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ columns=["a"],
+ case_sensitive=True,
+ )
+
+ def test_read_with_limit(self, catalog):
+ expected = pd.DataFrame(
+ {
+ "A": [1, 2],
+ "B": ["foo", "foo"],
+ }
+ )
+ result = read_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ limit=2,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_write(self, catalog):
+ df = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": ["foo", "foo", "foo"],
+ }
+ )
+ df.to_iceberg(
+ "ns.new_table",
+ catalog_properties={"uri": catalog.uri},
+ location=catalog.warehouse,
+ )
+ result = read_iceberg(
+ "ns.new_table",
+ catalog_properties={"uri": catalog.uri},
+ )
+ tm.assert_frame_equal(result, df)
+
+ @pytest.mark.parametrize("catalog", ["default", "pandas_tests"], indirect=True)
+ def test_write_by_catalog_name(self, catalog):
+ df = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": ["foo", "foo", "foo"],
+ }
+ )
+ df.to_iceberg(
+ "ns.new_table",
+ catalog_name=catalog.name,
+ )
+ result = read_iceberg(
+ "ns.new_table",
+ catalog_name=catalog.name,
+ )
+ tm.assert_frame_equal(result, df)
+
+ def test_write_existing_table_with_append_true(self, catalog):
+ original = read_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ )
+ new = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": ["foo", "foo", "foo"],
+ }
+ )
+ expected = pd.concat([original, new], ignore_index=True)
+ new.to_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ location=catalog.warehouse,
+ append=True,
+ )
+ result = read_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_write_existing_table_with_append_false(self, catalog):
+ df = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": ["foo", "foo", "foo"],
+ }
+ )
+ df.to_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ location=catalog.warehouse,
+ append=False,
+ )
+ result = read_iceberg(
+ "ns.my_table",
+ catalog_properties={"uri": catalog.uri},
+ )
+ tm.assert_frame_equal(result, df)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_orc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_orc.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e61494103355c5836f3ad0e9187d712093610dc
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_orc.py
@@ -0,0 +1,432 @@
+"""test orc compat"""
+
+import datetime
+from decimal import Decimal
+from io import BytesIO
+import os
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import read_orc
+import pandas._testing as tm
+
+pytest.importorskip("pyarrow.orc")
+
+import pyarrow as pa
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+)
+
+
+@pytest.fixture
+def dirpath(datapath):
+ return datapath("io", "data", "orc")
+
+
+def test_orc_reader_empty(dirpath, using_infer_string):
+ columns = [
+ "boolean1",
+ "byte1",
+ "short1",
+ "int1",
+ "long1",
+ "float1",
+ "double1",
+ "bytes1",
+ "string1",
+ ]
+ dtypes = [
+ "bool",
+ "int8",
+ "int16",
+ "int32",
+ "int64",
+ "float32",
+ "float64",
+ "object",
+ "str" if using_infer_string else "object",
+ ]
+ expected = pd.DataFrame(index=pd.RangeIndex(0))
+ for colname, dtype in zip(columns, dtypes, strict=True):
+ expected[colname] = pd.Series(dtype=dtype)
+ expected.columns = expected.columns.astype("str")
+
+ inputfile = os.path.join(dirpath, "TestOrcFile.emptyFile.orc")
+ got = read_orc(inputfile, columns=columns)
+
+ tm.assert_equal(expected, got)
+
+
+def test_orc_reader_basic(dirpath):
+ data = {
+ "boolean1": np.array([False, True], dtype="bool"),
+ "byte1": np.array([1, 100], dtype="int8"),
+ "short1": np.array([1024, 2048], dtype="int16"),
+ "int1": np.array([65536, 65536], dtype="int32"),
+ "long1": np.array([9223372036854775807, 9223372036854775807], dtype="int64"),
+ "float1": np.array([1.0, 2.0], dtype="float32"),
+ "double1": np.array([-15.0, -5.0], dtype="float64"),
+ "bytes1": np.array([b"\x00\x01\x02\x03\x04", b""], dtype="object"),
+ "string1": np.array(["hi", "bye"], dtype="object"),
+ }
+ expected = pd.DataFrame.from_dict(data)
+
+ inputfile = os.path.join(dirpath, "TestOrcFile.test1.orc")
+ got = read_orc(inputfile, columns=data.keys())
+
+ tm.assert_equal(expected, got)
+
+
+def test_orc_reader_decimal(dirpath):
+ # Only testing the first 10 rows of data
+ data = {
+ "_col0": np.array(
+ [
+ Decimal("-1000.50000"),
+ Decimal("-999.60000"),
+ Decimal("-998.70000"),
+ Decimal("-997.80000"),
+ Decimal("-996.90000"),
+ Decimal("-995.10000"),
+ Decimal("-994.11000"),
+ Decimal("-993.12000"),
+ Decimal("-992.13000"),
+ Decimal("-991.14000"),
+ ],
+ dtype="object",
+ )
+ }
+ expected = pd.DataFrame.from_dict(data)
+
+ inputfile = os.path.join(dirpath, "TestOrcFile.decimal.orc")
+ got = read_orc(inputfile).iloc[:10]
+
+ tm.assert_equal(expected, got)
+
+
+def test_orc_reader_date_low(dirpath):
+ data = {
+ "time": np.array(
+ [
+ "1900-05-05 12:34:56.100000",
+ "1900-05-05 12:34:56.100100",
+ "1900-05-05 12:34:56.100200",
+ "1900-05-05 12:34:56.100300",
+ "1900-05-05 12:34:56.100400",
+ "1900-05-05 12:34:56.100500",
+ "1900-05-05 12:34:56.100600",
+ "1900-05-05 12:34:56.100700",
+ "1900-05-05 12:34:56.100800",
+ "1900-05-05 12:34:56.100900",
+ ],
+ dtype="datetime64[ns]",
+ ),
+ "date": np.array(
+ [
+ datetime.date(1900, 12, 25),
+ datetime.date(1900, 12, 25),
+ datetime.date(1900, 12, 25),
+ datetime.date(1900, 12, 25),
+ datetime.date(1900, 12, 25),
+ datetime.date(1900, 12, 25),
+ datetime.date(1900, 12, 25),
+ datetime.date(1900, 12, 25),
+ datetime.date(1900, 12, 25),
+ datetime.date(1900, 12, 25),
+ ],
+ dtype="object",
+ ),
+ }
+ expected = pd.DataFrame.from_dict(data)
+
+ inputfile = os.path.join(dirpath, "TestOrcFile.testDate1900.orc")
+ got = read_orc(inputfile).iloc[:10]
+
+ tm.assert_equal(expected, got)
+
+
+def test_orc_reader_date_high(dirpath):
+ data = {
+ "time": np.array(
+ [
+ "2038-05-05 12:34:56.100000",
+ "2038-05-05 12:34:56.100100",
+ "2038-05-05 12:34:56.100200",
+ "2038-05-05 12:34:56.100300",
+ "2038-05-05 12:34:56.100400",
+ "2038-05-05 12:34:56.100500",
+ "2038-05-05 12:34:56.100600",
+ "2038-05-05 12:34:56.100700",
+ "2038-05-05 12:34:56.100800",
+ "2038-05-05 12:34:56.100900",
+ ],
+ dtype="datetime64[ns]",
+ ),
+ "date": np.array(
+ [
+ datetime.date(2038, 12, 25),
+ datetime.date(2038, 12, 25),
+ datetime.date(2038, 12, 25),
+ datetime.date(2038, 12, 25),
+ datetime.date(2038, 12, 25),
+ datetime.date(2038, 12, 25),
+ datetime.date(2038, 12, 25),
+ datetime.date(2038, 12, 25),
+ datetime.date(2038, 12, 25),
+ datetime.date(2038, 12, 25),
+ ],
+ dtype="object",
+ ),
+ }
+ expected = pd.DataFrame.from_dict(data)
+
+ inputfile = os.path.join(dirpath, "TestOrcFile.testDate2038.orc")
+ got = read_orc(inputfile).iloc[:10]
+
+ tm.assert_equal(expected, got)
+
+
+def test_orc_reader_snappy_compressed(dirpath):
+ data = {
+ "int1": np.array(
+ [
+ -1160101563,
+ 1181413113,
+ 2065821249,
+ -267157795,
+ 172111193,
+ 1752363137,
+ 1406072123,
+ 1911809390,
+ -1308542224,
+ -467100286,
+ ],
+ dtype="int32",
+ ),
+ "string1": np.array(
+ [
+ "f50dcb8",
+ "382fdaaa",
+ "90758c6",
+ "9e8caf3f",
+ "ee97332b",
+ "d634da1",
+ "2bea4396",
+ "d67d89e8",
+ "ad71007e",
+ "e8c82066",
+ ],
+ dtype="object",
+ ),
+ }
+ expected = pd.DataFrame.from_dict(data)
+
+ inputfile = os.path.join(dirpath, "TestOrcFile.testSnappy.orc")
+ got = read_orc(inputfile).iloc[:10]
+
+ tm.assert_equal(expected, got)
+
+
+def test_orc_roundtrip_file(dirpath, temp_file):
+ # GH44554
+ # PyArrow gained ORC write support with the current argument order
+ pytest.importorskip("pyarrow")
+
+ data = {
+ "boolean1": np.array([False, True], dtype="bool"),
+ "byte1": np.array([1, 100], dtype="int8"),
+ "short1": np.array([1024, 2048], dtype="int16"),
+ "int1": np.array([65536, 65536], dtype="int32"),
+ "long1": np.array([9223372036854775807, 9223372036854775807], dtype="int64"),
+ "float1": np.array([1.0, 2.0], dtype="float32"),
+ "double1": np.array([-15.0, -5.0], dtype="float64"),
+ "bytes1": np.array([b"\x00\x01\x02\x03\x04", b""], dtype="object"),
+ "string1": np.array(["hi", "bye"], dtype="object"),
+ }
+ expected = pd.DataFrame.from_dict(data)
+
+ expected.to_orc(temp_file)
+ got = read_orc(temp_file)
+
+ tm.assert_equal(expected, got)
+
+
+def test_orc_roundtrip_bytesio():
+ # GH44554
+ # PyArrow gained ORC write support with the current argument order
+ pytest.importorskip("pyarrow")
+
+ data = {
+ "boolean1": np.array([False, True], dtype="bool"),
+ "byte1": np.array([1, 100], dtype="int8"),
+ "short1": np.array([1024, 2048], dtype="int16"),
+ "int1": np.array([65536, 65536], dtype="int32"),
+ "long1": np.array([9223372036854775807, 9223372036854775807], dtype="int64"),
+ "float1": np.array([1.0, 2.0], dtype="float32"),
+ "double1": np.array([-15.0, -5.0], dtype="float64"),
+ "bytes1": np.array([b"\x00\x01\x02\x03\x04", b""], dtype="object"),
+ "string1": np.array(["hi", "bye"], dtype="object"),
+ }
+ expected = pd.DataFrame.from_dict(data)
+
+ bytes = expected.to_orc()
+ got = read_orc(BytesIO(bytes))
+
+ tm.assert_equal(expected, got)
+
+
+@pytest.mark.parametrize(
+ "orc_writer_dtypes_not_supported",
+ [
+ np.array([1, 20], dtype="uint64"),
+ pd.Series(["a", "b", "a"], dtype="category"),
+ [pd.Interval(left=0, right=2), pd.Interval(left=0, right=5)],
+ [pd.Period("2022-01-03", freq="D"), pd.Period("2022-01-04", freq="D")],
+ ],
+)
+def test_orc_writer_dtypes_not_supported(orc_writer_dtypes_not_supported):
+ # GH44554
+ # PyArrow gained ORC write support with the current argument order
+ pytest.importorskip("pyarrow")
+
+ df = pd.DataFrame({"unimpl": orc_writer_dtypes_not_supported})
+ msg = "The dtype of one or more columns is not supported yet."
+ with pytest.raises(NotImplementedError, match=msg):
+ df.to_orc()
+
+
+def test_orc_dtype_backend_pyarrow(using_infer_string):
+ pytest.importorskip("pyarrow")
+ df = pd.DataFrame(
+ {
+ "string": list("abc"),
+ "string_with_nan": ["a", np.nan, "c"],
+ "string_with_none": ["a", None, "c"],
+ "bytes": [b"foo", b"bar", None],
+ "int": list(range(1, 4)),
+ "float": np.arange(4.0, 7.0, dtype="float64"),
+ "float_with_nan": [2.0, np.nan, 3.0],
+ "bool": [True, False, True],
+ "bool_with_na": [True, False, None],
+ "datetime": pd.date_range("20130101", periods=3, unit="ns"),
+ "datetime_with_nat": [
+ pd.Timestamp("20130101"),
+ pd.NaT,
+ pd.Timestamp("20130103"),
+ ],
+ }
+ )
+ # FIXME: without casting to ns we do not round-trip correctly
+ df["datetime_with_nat"] = df["datetime_with_nat"].astype("M8[ns]")
+
+ bytes_data = df.copy().to_orc()
+ result = read_orc(BytesIO(bytes_data), dtype_backend="pyarrow")
+
+ expected = pd.DataFrame(
+ {
+ col: pd.arrays.ArrowExtensionArray(pa.array(df[col], from_pandas=True))
+ for col in df.columns
+ }
+ )
+ if using_infer_string:
+ # ORC does not preserve distinction between string and large string
+ # -> the default large string comes back as string
+ string_dtype = pd.ArrowDtype(pa.string())
+ expected["string"] = expected["string"].astype(string_dtype)
+ expected["string_with_nan"] = expected["string_with_nan"].astype(string_dtype)
+ expected["string_with_none"] = expected["string_with_none"].astype(string_dtype)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_orc_dtype_backend_numpy_nullable():
+ # GH#50503
+ pytest.importorskip("pyarrow")
+ df = pd.DataFrame(
+ {
+ "string": list("abc"),
+ "string_with_nan": ["a", np.nan, "c"],
+ "string_with_none": ["a", None, "c"],
+ "int": list(range(1, 4)),
+ "int_with_nan": pd.Series([1, pd.NA, 3], dtype="Int64"),
+ "na_only": pd.Series([pd.NA, pd.NA, pd.NA], dtype="Int64"),
+ "float": np.arange(4.0, 7.0, dtype="float64"),
+ "float_with_nan": [2.0, np.nan, 3.0],
+ "bool": [True, False, True],
+ "bool_with_na": [True, False, None],
+ }
+ )
+
+ bytes_data = df.copy().to_orc()
+ result = read_orc(BytesIO(bytes_data), dtype_backend="numpy_nullable")
+
+ expected = pd.DataFrame(
+ {
+ "string": pd.array(["a", "b", "c"], dtype=pd.StringDtype()),
+ "string_with_nan": pd.array(["a", pd.NA, "c"], dtype=pd.StringDtype()),
+ "string_with_none": pd.array(["a", pd.NA, "c"], dtype=pd.StringDtype()),
+ "int": pd.Series([1, 2, 3], dtype="Int64"),
+ "int_with_nan": pd.Series([1, pd.NA, 3], dtype="Int64"),
+ "na_only": pd.Series([pd.NA, pd.NA, pd.NA], dtype="Int64"),
+ "float": pd.Series([4.0, 5.0, 6.0], dtype="Float64"),
+ "float_with_nan": pd.Series([2.0, pd.NA, 3.0], dtype="Float64"),
+ "bool": pd.Series([True, False, True], dtype="boolean"),
+ "bool_with_na": pd.Series([True, False, pd.NA], dtype="boolean"),
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_orc_uri_path(temp_file):
+ expected = pd.DataFrame({"int": list(range(1, 4))})
+ expected.to_orc(temp_file)
+ uri = temp_file.as_uri()
+ result = read_orc(uri)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ pd.RangeIndex(start=2, stop=5, step=1),
+ pd.RangeIndex(start=0, stop=3, step=1, name="non-default"),
+ pd.Index([1, 2, 3]),
+ ],
+)
+def test_to_orc_non_default_index(index):
+ df = pd.DataFrame({"a": [1, 2, 3]}, index=index)
+ msg = (
+ "orc does not support serializing a non-default index|"
+ "orc does not serialize index meta-data"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.to_orc()
+
+
+def test_invalid_dtype_backend(temp_file):
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ df = pd.DataFrame({"int": list(range(1, 4))})
+ df.to_orc(temp_file)
+ with pytest.raises(ValueError, match=msg):
+ read_orc(temp_file, dtype_backend="numpy")
+
+
+def test_string_inference(temp_file):
+ # GH#54431
+ df = pd.DataFrame(data={"a": ["x", "y"]})
+ df.to_orc(temp_file)
+ with pd.option_context("future.infer_string", True):
+ result = read_orc(temp_file)
+ expected = pd.DataFrame(
+ data={"a": ["x", "y"]},
+ dtype=pd.StringDtype(na_value=np.nan),
+ columns=pd.Index(["a"], dtype=pd.StringDtype(na_value=np.nan)),
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_parquet.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_parquet.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b7f49e18f5490c0d74ba41e4581a8edc336ddc6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_parquet.py
@@ -0,0 +1,1465 @@
+"""test parquet compat"""
+
+import datetime
+from decimal import Decimal
+from io import BytesIO
+import os
+import pathlib
+
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+from pandas.compat import is_platform_windows
+from pandas.compat.pyarrow import (
+ pa_version_under15p0,
+ pa_version_under17p0,
+ pa_version_under19p0,
+ pa_version_under20p0,
+)
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.util.version import Version
+
+from pandas.io.parquet import (
+ FastParquetImpl,
+ PyArrowImpl,
+ get_engine,
+ read_parquet,
+ to_parquet,
+)
+
+try:
+ import pyarrow
+
+ _HAVE_PYARROW = True
+except ImportError:
+ _HAVE_PYARROW = False
+
+try:
+ import fastparquet
+
+ _HAVE_FASTPARQUET = True
+except ImportError:
+ _HAVE_FASTPARQUET = False
+
+
+pytestmark = [
+ pytest.mark.filterwarnings("ignore:DataFrame._data is deprecated:FutureWarning"),
+ pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+ ),
+]
+
+
+# setup engines & skips
+@pytest.fixture(
+ params=[
+ pytest.param(
+ "fastparquet",
+ marks=[
+ pytest.mark.skipif(
+ not _HAVE_FASTPARQUET,
+ reason="fastparquet is not installed",
+ ),
+ pytest.mark.xfail(
+ using_string_dtype(),
+ reason="TODO(infer_string) fastparquet",
+ strict=False,
+ ),
+ ],
+ ),
+ pytest.param(
+ "pyarrow",
+ marks=pytest.mark.skipif(
+ not _HAVE_PYARROW, reason="pyarrow is not installed"
+ ),
+ ),
+ ]
+)
+def engine(request):
+ return request.param
+
+
+@pytest.fixture
+def pa():
+ if not _HAVE_PYARROW:
+ pytest.skip("pyarrow is not installed")
+ return "pyarrow"
+
+
+@pytest.fixture
+def fp(request):
+ if not _HAVE_FASTPARQUET:
+ pytest.skip("fastparquet is not installed")
+ if using_string_dtype():
+ request.applymarker(
+ pytest.mark.xfail(reason="TODO(infer_string) fastparquet", strict=False)
+ )
+ return "fastparquet"
+
+
+@pytest.fixture
+def df_compat():
+ return pd.DataFrame({"A": [1, 2, 3], "B": "foo"}, columns=pd.Index(["A", "B"]))
+
+
+@pytest.fixture
+def df_cross_compat():
+ df = pd.DataFrame(
+ {
+ "a": list("abc"),
+ "b": list(range(1, 4)),
+ # 'c': np.arange(3, 6).astype('u1'),
+ "d": np.arange(4.0, 7.0, dtype="float64"),
+ "e": [True, False, True],
+ "f": pd.date_range("20130101", periods=3),
+ # 'g': pd.date_range('20130101', periods=3,
+ # tz='US/Eastern'),
+ # 'h': pd.date_range('20130101', periods=3, freq='ns')
+ }
+ )
+ return df
+
+
+@pytest.fixture
+def df_full():
+ return pd.DataFrame(
+ {
+ "string": list("abc"),
+ "string_with_nan": ["a", np.nan, "c"],
+ "string_with_none": ["a", None, "c"],
+ "bytes": [b"foo", b"bar", b"baz"],
+ "unicode": ["foo", "bar", "baz"],
+ "int": list(range(1, 4)),
+ "uint": np.arange(3, 6).astype("u1"),
+ "float": np.arange(4.0, 7.0, dtype="float64"),
+ "float_with_nan": [2.0, np.nan, 3.0],
+ "bool": [True, False, True],
+ "datetime": pd.date_range("20130101", periods=3, unit="ns"),
+ "datetime_with_nat": [
+ pd.Timestamp("20130101"),
+ pd.NaT,
+ pd.Timestamp("20130103"),
+ ],
+ }
+ )
+
+
+@pytest.fixture(
+ params=[
+ datetime.datetime.now(datetime.UTC),
+ datetime.datetime.now(datetime.timezone.min),
+ datetime.datetime.now(datetime.timezone.max),
+ datetime.datetime.strptime("2019-01-04T16:41:24+0200", "%Y-%m-%dT%H:%M:%S%z"),
+ datetime.datetime.strptime("2019-01-04T16:41:24+0215", "%Y-%m-%dT%H:%M:%S%z"),
+ datetime.datetime.strptime("2019-01-04T16:41:24-0200", "%Y-%m-%dT%H:%M:%S%z"),
+ datetime.datetime.strptime("2019-01-04T16:41:24-0215", "%Y-%m-%dT%H:%M:%S%z"),
+ ]
+)
+def timezone_aware_date_list(request):
+ return request.param
+
+
+def check_round_trip(
+ df,
+ temp_file,
+ engine=None,
+ path=None,
+ write_kwargs=None,
+ read_kwargs=None,
+ expected=None,
+ check_names=True,
+ check_like=False,
+ check_dtype=True,
+ repeat=2,
+):
+ """Verify parquet serializer and deserializer produce the same results.
+
+ Performs a pandas to disk and disk to pandas round trip,
+ then compares the 2 resulting DataFrames to verify equality.
+
+ Parameters
+ ----------
+ df: Dataframe
+ engine: str, optional
+ 'pyarrow' or 'fastparquet'
+ path: str, optional
+ write_kwargs: dict of str:str, optional
+ read_kwargs: dict of str:str, optional
+ expected: DataFrame, optional
+ Expected deserialization result, otherwise will be equal to `df`
+ check_names: list of str, optional
+ Closed set of column names to be compared
+ check_like: bool, optional
+ If True, ignore the order of index & columns.
+ repeat: int, optional
+ How many times to repeat the test
+ """
+ if not isinstance(temp_file, pathlib.Path):
+ raise ValueError("temp_file must be a pathlib.Path")
+ write_kwargs = write_kwargs or {"compression": None}
+ read_kwargs = read_kwargs or {}
+
+ if expected is None:
+ expected = df
+
+ if engine:
+ write_kwargs["engine"] = engine
+ read_kwargs["engine"] = engine
+
+ def compare(repeat):
+ for _ in range(repeat):
+ df.to_parquet(path, **write_kwargs)
+ actual = read_parquet(path, **read_kwargs)
+
+ if "string_with_nan" in expected:
+ expected.loc[1, "string_with_nan"] = None
+ tm.assert_frame_equal(
+ expected,
+ actual,
+ check_names=check_names,
+ check_like=check_like,
+ check_dtype=check_dtype,
+ )
+
+ if path is None:
+ path = temp_file
+ compare(repeat)
+ else:
+ compare(repeat)
+
+
+def check_partition_names(path, expected):
+ """Check partitions of a parquet file are as expected.
+
+ Parameters
+ ----------
+ path: str
+ Path of the dataset.
+ expected: iterable of str
+ Expected partition names.
+ """
+ import pyarrow.dataset as ds
+
+ dataset = ds.dataset(path, partitioning="hive")
+ assert dataset.partitioning.schema.names == expected
+
+
+def test_invalid_engine(df_compat, temp_file):
+ msg = "engine must be one of 'pyarrow', 'fastparquet'"
+ with pytest.raises(ValueError, match=msg):
+ check_round_trip(df_compat, temp_file, "foo", "bar")
+
+
+def test_options_py(df_compat, pa, using_infer_string, temp_file):
+ # use the set option
+ if using_infer_string and not pa_version_under19p0:
+ df_compat.columns = df_compat.columns.astype("str")
+
+ with pd.option_context("io.parquet.engine", "pyarrow"):
+ check_round_trip(df_compat, temp_file)
+
+
+def test_options_fp(df_compat, fp, temp_file):
+ # use the set option
+
+ with pd.option_context("io.parquet.engine", "fastparquet"):
+ check_round_trip(df_compat, temp_file)
+
+
+def test_options_auto(df_compat, fp, pa, temp_file):
+ # use the set option
+
+ with pd.option_context("io.parquet.engine", "auto"):
+ check_round_trip(df_compat, temp_file)
+
+
+def test_options_get_engine(fp, pa):
+ assert isinstance(get_engine("pyarrow"), PyArrowImpl)
+ assert isinstance(get_engine("fastparquet"), FastParquetImpl)
+
+ with pd.option_context("io.parquet.engine", "pyarrow"):
+ assert isinstance(get_engine("auto"), PyArrowImpl)
+ assert isinstance(get_engine("pyarrow"), PyArrowImpl)
+ assert isinstance(get_engine("fastparquet"), FastParquetImpl)
+
+ with pd.option_context("io.parquet.engine", "fastparquet"):
+ assert isinstance(get_engine("auto"), FastParquetImpl)
+ assert isinstance(get_engine("pyarrow"), PyArrowImpl)
+ assert isinstance(get_engine("fastparquet"), FastParquetImpl)
+
+ with pd.option_context("io.parquet.engine", "auto"):
+ assert isinstance(get_engine("auto"), PyArrowImpl)
+ assert isinstance(get_engine("pyarrow"), PyArrowImpl)
+ assert isinstance(get_engine("fastparquet"), FastParquetImpl)
+
+
+def test_get_engine_auto_error_message():
+ # Expect different error messages from get_engine(engine="auto")
+ # if engines aren't installed vs. are installed but bad version
+ from pandas.compat._optional import VERSIONS
+
+ # Do we have engines installed, but a bad version of them?
+ pa_min_ver = VERSIONS.get("pyarrow")
+ fp_min_ver = VERSIONS.get("fastparquet")
+ have_pa_bad_version = (
+ False
+ if not _HAVE_PYARROW
+ else Version(pyarrow.__version__) < Version(pa_min_ver)
+ )
+ have_fp_bad_version = (
+ False
+ if not _HAVE_FASTPARQUET
+ else Version(fastparquet.__version__) < Version(fp_min_ver)
+ )
+ # Do we have usable engines installed?
+ have_usable_pa = _HAVE_PYARROW and not have_pa_bad_version
+ have_usable_fp = _HAVE_FASTPARQUET and not have_fp_bad_version
+
+ if not have_usable_pa and not have_usable_fp:
+ # No usable engines found.
+ if have_pa_bad_version:
+ match = f"Pandas requires version .{pa_min_ver}. or newer of .pyarrow."
+ with pytest.raises(ImportError, match=match):
+ get_engine("auto")
+ else:
+ match = "Unable to find a usable engine; tried using: 'pyarrow'"
+ with pytest.raises(ImportError, match=match):
+ get_engine("auto")
+
+ if have_fp_bad_version:
+ match = f"Pandas requires version .{fp_min_ver}. or newer of .fastparquet."
+ with pytest.raises(ImportError, match=match):
+ get_engine("auto")
+ else:
+ match = "Use pip or conda to install the fastparquet package"
+ with pytest.raises(ImportError, match=match):
+ get_engine("auto")
+
+
+def test_cross_engine_pa_fp(df_cross_compat, pa, fp, temp_file):
+ # cross-compat with differing reading/writing engines
+
+ df = df_cross_compat
+ df.to_parquet(temp_file, engine=pa, compression=None)
+
+ result = read_parquet(temp_file, engine=fp)
+ tm.assert_frame_equal(result, df)
+
+ result = read_parquet(temp_file, engine=fp, columns=["a", "d"])
+ tm.assert_frame_equal(result, df[["a", "d"]])
+
+
+def test_cross_engine_fp_pa(df_cross_compat, pa, fp, temp_file):
+ # cross-compat with differing reading/writing engines
+ df = df_cross_compat
+
+ df.to_parquet(temp_file, engine=fp, compression=None)
+
+ result = read_parquet(temp_file, engine=pa)
+ tm.assert_frame_equal(result, df)
+
+ result = read_parquet(temp_file, engine=pa, columns=["a", "d"])
+ tm.assert_frame_equal(result, df[["a", "d"]])
+
+
+class Base:
+ def check_error_on_write(self, df, engine, exc, err_msg, temp_file_path):
+ # check that we are raising the exception on writing
+ with pytest.raises(exc, match=err_msg):
+ to_parquet(df, temp_file_path, engine, compression=None)
+
+ def check_external_error_on_write(self, df, engine, exc, temp_file_path):
+ # check that an external library is raising the exception on writing
+ with tm.external_error_raised(exc):
+ to_parquet(df, temp_file_path, engine, compression=None)
+
+
+class TestBasic(Base):
+ def test_error(self, engine, temp_file):
+ for obj in [
+ pd.Series([1, 2, 3]),
+ 1,
+ "foo",
+ pd.Timestamp("20130101"),
+ np.array([1, 2, 3]),
+ ]:
+ msg = "to_parquet only supports IO with DataFrames"
+ self.check_error_on_write(obj, engine, ValueError, msg, temp_file)
+
+ def test_columns_dtypes(self, engine, temp_file):
+ df = pd.DataFrame({"string": list("abc"), "int": list(range(1, 4))})
+
+ # unicode
+ df.columns = ["foo", "bar"]
+ check_round_trip(df, temp_file, engine)
+
+ @pytest.mark.parametrize("compression", [None, "gzip", "snappy", "brotli"])
+ def test_compression(self, engine, compression, temp_file):
+ df = pd.DataFrame({"A": [1, 2, 3]})
+ check_round_trip(
+ df, temp_file, engine, write_kwargs={"compression": compression}
+ )
+
+ def test_read_columns(self, engine, temp_file):
+ # GH18154
+ df = pd.DataFrame({"string": list("abc"), "int": list(range(1, 4))})
+
+ expected = pd.DataFrame({"string": list("abc")})
+ check_round_trip(
+ df,
+ temp_file,
+ engine,
+ expected=expected,
+ read_kwargs={"columns": ["string"]},
+ )
+
+ def test_read_filters(self, engine, tmp_path):
+ df = pd.DataFrame(
+ {
+ "int": list(range(4)),
+ "part": list("aabb"),
+ }
+ )
+
+ expected = pd.DataFrame({"int": [0, 1]})
+ check_round_trip(
+ df,
+ tmp_path,
+ engine,
+ expected=expected,
+ write_kwargs={"partition_cols": ["part"]},
+ read_kwargs={"filters": [("part", "==", "a")], "columns": ["int"]},
+ repeat=1,
+ )
+
+ def test_write_index(self, temp_file):
+ pytest.importorskip("pyarrow")
+ df = pd.DataFrame({"A": [1, 2, 3]})
+ check_round_trip(df, temp_file, "pyarrow")
+
+ indexes = [
+ [2, 3, 4],
+ pd.date_range("20130101", periods=3, unit="ns"),
+ list("abc"),
+ [1, 3, 4],
+ ]
+ # non-default index
+ for index in indexes:
+ df.index = index
+ if isinstance(index, pd.DatetimeIndex):
+ df.index = df.index._with_freq(None) # freq doesn't round-trip
+ check_round_trip(df, temp_file, "pyarrow")
+
+ # index with meta-data
+ df.index = [0, 1, 2]
+ df.index.name = "foo"
+ check_round_trip(df, temp_file, "pyarrow")
+
+ def test_write_multiindex(self, pa, temp_file):
+ # Not supported in fastparquet as of 0.1.3 or older pyarrow version
+ engine = pa
+
+ df = pd.DataFrame({"A": [1, 2, 3]})
+ index = pd.MultiIndex.from_tuples([("a", 1), ("a", 2), ("b", 1)])
+ df.index = index
+ check_round_trip(df, temp_file, engine)
+
+ def test_multiindex_with_columns(self, pa, temp_file):
+ engine = pa
+ dates = pd.date_range("01-Jan-2018", "01-Dec-2018", freq="MS", unit="ns")
+ df = pd.DataFrame(
+ np.random.default_rng(2).standard_normal((2 * len(dates), 3)),
+ columns=list("ABC"),
+ )
+ index1 = pd.MultiIndex.from_product(
+ [["Level1", "Level2"], dates], names=["level", "date"]
+ )
+ index2 = index1.copy(names=None)
+ for index in [index1, index2]:
+ df.index = index
+
+ check_round_trip(df, temp_file, engine)
+ check_round_trip(
+ df,
+ temp_file,
+ engine,
+ read_kwargs={"columns": ["A", "B"]},
+ expected=df[["A", "B"]],
+ )
+
+ def test_write_ignoring_index(self, engine, temp_file):
+ # ENH 20768
+ # Ensure index=False omits the index from the written Parquet file.
+ df = pd.DataFrame({"a": [1, 2, 3], "b": ["q", "r", "s"]})
+
+ write_kwargs = {"compression": None, "index": False}
+
+ # Because we're dropping the index, we expect the loaded dataframe to
+ # have the default integer index.
+ expected = df.reset_index(drop=True)
+
+ check_round_trip(
+ df, temp_file, engine, write_kwargs=write_kwargs, expected=expected
+ )
+
+ # Ignore custom index
+ df = pd.DataFrame(
+ {"a": [1, 2, 3], "b": ["q", "r", "s"]}, index=["zyx", "wvu", "tsr"]
+ )
+
+ check_round_trip(
+ df, temp_file, engine, write_kwargs=write_kwargs, expected=expected
+ )
+
+ # Ignore multi-indexes as well.
+ arrays = [
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ df = pd.DataFrame(
+ {"one": list(range(8)), "two": [-i for i in range(8)]}, index=arrays
+ )
+
+ expected = df.reset_index(drop=True)
+ check_round_trip(
+ df, temp_file, engine, write_kwargs=write_kwargs, expected=expected
+ )
+
+ def test_write_column_multiindex(self, engine, temp_file):
+ # Not able to write column multi-indexes with non-string column names.
+ mi_columns = pd.MultiIndex.from_tuples([("a", 1), ("a", 2), ("b", 1)])
+ df = pd.DataFrame(
+ np.random.default_rng(2).standard_normal((4, 3)), columns=mi_columns
+ )
+
+ if engine == "fastparquet":
+ self.check_error_on_write(
+ df, engine, TypeError, "Column name must be a string", temp_file
+ )
+ elif engine == "pyarrow":
+ check_round_trip(df, temp_file, engine)
+
+ def test_write_column_multiindex_nonstring(self, engine, temp_file):
+ # GH #34777
+
+ # Not able to write column multi-indexes with non-string column names
+ arrays = [
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ [1, 2, 1, 2, 1, 2, 1, 2],
+ ]
+ df = pd.DataFrame(
+ np.random.default_rng(2).standard_normal((8, 8)), columns=arrays
+ )
+ df.columns.names = ["Level1", "Level2"]
+ if engine == "fastparquet":
+ self.check_error_on_write(df, engine, ValueError, "Column name", temp_file)
+ elif engine == "pyarrow":
+ check_round_trip(df, temp_file, engine)
+
+ def test_write_column_multiindex_string(self, pa, temp_file):
+ # GH #34777
+ # Not supported in fastparquet as of 0.1.3
+ engine = pa
+
+ # Write column multi-indexes with string column names
+ arrays = [
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ df = pd.DataFrame(
+ np.random.default_rng(2).standard_normal((8, 8)), columns=arrays
+ )
+ df.columns.names = ["ColLevel1", "ColLevel2"]
+
+ check_round_trip(df, temp_file, engine)
+
+ def test_write_column_index_string(self, pa, temp_file):
+ # GH #34777
+ # Not supported in fastparquet as of 0.1.3
+ engine = pa
+
+ # Write column indexes with string column names
+ arrays = ["bar", "baz", "foo", "qux"]
+ df = pd.DataFrame(
+ np.random.default_rng(2).standard_normal((8, 4)), columns=arrays
+ )
+ df.columns.name = "StringCol"
+
+ check_round_trip(df, temp_file, engine)
+
+ def test_write_column_index_nonstring(self, engine, temp_file):
+ # GH #34777
+
+ # Write column indexes with string column names
+ arrays = [1, 2, 3, 4]
+ df = pd.DataFrame(
+ np.random.default_rng(2).standard_normal((8, 4)), columns=arrays
+ )
+ df.columns.name = "NonStringCol"
+ if engine == "fastparquet":
+ self.check_error_on_write(
+ df, engine, TypeError, "Column name must be a string", temp_file
+ )
+ else:
+ check_round_trip(df, temp_file, engine)
+
+ def test_dtype_backend(self, engine, request, temp_file):
+ pq = pytest.importorskip("pyarrow.parquet")
+
+ if engine == "fastparquet":
+ # We are manually disabling fastparquet's
+ # nullable dtype support pending discussion
+ mark = pytest.mark.xfail(
+ reason="Fastparquet nullable dtype support is disabled"
+ )
+ request.applymarker(mark)
+
+ table = pyarrow.table(
+ {
+ "a": pyarrow.array([1, 2, 3, None], "int64"),
+ "b": pyarrow.array([1, 2, 3, None], "uint8"),
+ "c": pyarrow.array(["a", "b", "c", None]),
+ "d": pyarrow.array([True, False, True, None]),
+ # Test that nullable dtypes used even in absence of nulls
+ "e": pyarrow.array([1, 2, 3, 4], "int64"),
+ # GH 45694
+ "f": pyarrow.array([1.0, 2.0, 3.0, None], "float32"),
+ "g": pyarrow.array([1.0, 2.0, 3.0, None], "float64"),
+ }
+ )
+ # write manually with pyarrow to write integers
+ pq.write_table(table, temp_file)
+ result1 = read_parquet(temp_file, engine=engine)
+ result2 = read_parquet(temp_file, engine=engine, dtype_backend="numpy_nullable")
+
+ assert result1["a"].dtype == np.dtype("float64")
+ expected = pd.DataFrame(
+ {
+ "a": pd.array([1, 2, 3, None], dtype="Int64"),
+ "b": pd.array([1, 2, 3, None], dtype="UInt8"),
+ "c": pd.array(["a", "b", "c", None], dtype="string"),
+ "d": pd.array([True, False, True, None], dtype="boolean"),
+ "e": pd.array([1, 2, 3, 4], dtype="Int64"),
+ "f": pd.array([1.0, 2.0, 3.0, None], dtype="Float32"),
+ "g": pd.array([1.0, 2.0, 3.0, None], dtype="Float64"),
+ }
+ )
+ if engine == "fastparquet":
+ # Fastparquet doesn't support string columns yet
+ # Only int and boolean
+ result2 = result2.drop("c", axis=1)
+ expected = expected.drop("c", axis=1)
+ tm.assert_frame_equal(result2, expected)
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ "Int64",
+ "UInt8",
+ "boolean",
+ "object",
+ "datetime64[ns, UTC]",
+ "float",
+ "period[D]",
+ "Float64",
+ "string",
+ ],
+ )
+ def test_read_empty_array(self, pa, dtype, temp_file):
+ # GH #41241
+ df = pd.DataFrame(
+ {
+ "value": pd.array([], dtype=dtype),
+ }
+ )
+ pytest.importorskip("pyarrow", "11.0.0")
+ # GH 45694
+ expected = None
+ if dtype == "float":
+ expected = pd.DataFrame(
+ {
+ "value": pd.array([], dtype="Float64"),
+ }
+ )
+ check_round_trip(
+ df,
+ temp_file,
+ pa,
+ read_kwargs={"dtype_backend": "numpy_nullable"},
+ expected=expected,
+ )
+
+ @pytest.mark.network
+ @pytest.mark.single_cpu
+ def test_parquet_read_from_url(self, httpserver, datapath, df_compat, engine):
+ if engine != "auto":
+ pytest.importorskip(engine)
+ with open(datapath("io", "data", "parquet", "simple.parquet"), mode="rb") as f:
+ httpserver.serve_content(content=f.read())
+ df = read_parquet(httpserver.url, engine=engine)
+
+ expected = df_compat
+ if pa_version_under19p0:
+ expected.columns = expected.columns.astype(object)
+ tm.assert_frame_equal(df, expected)
+
+
+class TestParquetPyArrow(Base):
+ def test_basic(self, pa, df_full, temp_file):
+ df = df_full
+ pytest.importorskip("pyarrow", "11.0.0")
+
+ # additional supported types for pyarrow
+ dti = pd.date_range("20130101", periods=3, tz="Europe/Brussels")
+ dti = dti._with_freq(None) # freq doesn't round-trip
+ df["datetime_tz"] = dti
+ df["bool_with_none"] = [True, None, True]
+
+ check_round_trip(df, temp_file, pa)
+
+ def test_basic_subset_columns(self, pa, df_full, temp_file):
+ # GH18628
+
+ df = df_full
+ # additional supported types for pyarrow
+ df["datetime_tz"] = pd.date_range("20130101", periods=3, tz="Europe/Brussels")
+
+ check_round_trip(
+ df,
+ temp_file,
+ pa,
+ expected=df[["string", "int"]],
+ read_kwargs={"columns": ["string", "int"]},
+ )
+
+ def test_to_bytes_without_path_or_buf_provided(self, pa, df_full):
+ # GH 37105
+ buf_bytes = df_full.to_parquet(engine=pa)
+ assert isinstance(buf_bytes, bytes)
+
+ buf_stream = BytesIO(buf_bytes)
+ res = read_parquet(buf_stream)
+
+ expected = df_full.copy()
+ expected.loc[1, "string_with_nan"] = None
+ expected["datetime_with_nat"] = expected["datetime_with_nat"].astype("M8[us]")
+ tm.assert_frame_equal(res, expected)
+
+ def test_duplicate_columns(self, pa, temp_file):
+ # not currently able to handle duplicate columns
+ df = pd.DataFrame(np.arange(12).reshape(4, 3), columns=list("aaa")).copy()
+ self.check_error_on_write(
+ df, pa, ValueError, "Duplicate column names found", temp_file
+ )
+
+ def test_timedelta(self, pa, temp_file):
+ df = pd.DataFrame({"a": pd.timedelta_range("1 day", periods=3)})
+ check_round_trip(df, temp_file, pa)
+
+ def test_unsupported(self, pa, temp_file):
+ # mixed python objects
+ df = pd.DataFrame({"a": ["a", 1, 2.0]})
+ # pyarrow 0.11 raises ArrowTypeError
+ # older pyarrows raise ArrowInvalid
+ self.check_external_error_on_write(df, pa, pyarrow.ArrowException, temp_file)
+
+ def test_unsupported_float16(self, pa, temp_file):
+ # #44847, #44914
+ # Not able to write float 16 column using pyarrow.
+ data = np.arange(2, 10, dtype=np.float16)
+ df = pd.DataFrame(data=data, columns=["fp16"])
+ if pa_version_under15p0:
+ self.check_external_error_on_write(
+ df, pa, pyarrow.ArrowException, temp_file
+ )
+ else:
+ check_round_trip(df, temp_file, pa)
+
+ @pytest.mark.xfail(
+ is_platform_windows(),
+ reason=(
+ "PyArrow does not cleanup of partial files dumps when unsupported "
+ "dtypes are passed to_parquet function in windows"
+ ),
+ )
+ @pytest.mark.skipif(not pa_version_under15p0, reason="float16 works on 15")
+ @pytest.mark.parametrize("path_type", [str, pathlib.Path])
+ def test_unsupported_float16_cleanup(self, pa, path_type, temp_file):
+ # #44847, #44914
+ # Not able to write float 16 column using pyarrow.
+ # Tests cleanup by pyarrow in case of an error
+ data = np.arange(2, 10, dtype=np.float16)
+ df = pd.DataFrame(data=data, columns=["fp16"])
+
+ path = path_type(temp_file)
+ with tm.external_error_raised(pyarrow.ArrowException):
+ df.to_parquet(path=path, engine=pa)
+ assert not os.path.isfile(path)
+
+ def test_categorical(self, pa, temp_file):
+ # supported in >= 0.7.0
+ df = pd.DataFrame(
+ {
+ "a": pd.Categorical(list("abcdef")),
+ # test for null, out-of-order values, and unobserved category
+ "b": pd.Categorical(
+ ["bar", "foo", "foo", "bar", None, "bar"],
+ dtype=pd.CategoricalDtype(["foo", "bar", "baz"]),
+ ),
+ # test for ordered flag
+ "c": pd.Categorical(
+ [None, "b", "c", None, "c", "b"],
+ categories=["b", "c", "d"],
+ ordered=True,
+ ),
+ }
+ )
+
+ check_round_trip(df, temp_file, pa)
+
+ @pytest.mark.single_cpu
+ def test_s3_roundtrip_explicit_fs(
+ self, df_compat, s3_bucket_public, s3so, pa, temp_file
+ ):
+ s3fs = pytest.importorskip("s3fs")
+ s3 = s3fs.S3FileSystem(**s3so)
+ kw = {"filesystem": s3}
+ check_round_trip(
+ df_compat,
+ temp_file,
+ pa,
+ path=f"{s3_bucket_public.name}/pyarrow.parquet",
+ read_kwargs=kw,
+ write_kwargs=kw,
+ )
+
+ @pytest.mark.single_cpu
+ def test_s3_roundtrip(self, df_compat, s3_bucket_public, s3so, pa, temp_file):
+ # GH #19134
+ s3so = {"storage_options": s3so}
+ check_round_trip(
+ df_compat,
+ temp_file,
+ pa,
+ path=f"s3://{s3_bucket_public.name}/pyarrow.parquet",
+ read_kwargs=s3so,
+ write_kwargs=s3so,
+ )
+
+ @pytest.mark.single_cpu
+ @pytest.mark.parametrize("partition_col", [["A"], []])
+ def test_s3_roundtrip_for_dir(
+ self, df_compat, s3_bucket_public, pa, partition_col, s3so, temp_file
+ ):
+ pytest.importorskip("s3fs")
+ # GH #26388
+ expected_df = df_compat.copy()
+
+ # GH #35791
+ if partition_col:
+ expected_df = expected_df.astype(dict.fromkeys(partition_col, np.int32))
+ partition_col_type = "category"
+
+ expected_df[partition_col] = expected_df[partition_col].astype(
+ partition_col_type
+ )
+
+ check_round_trip(
+ df_compat,
+ temp_file,
+ pa,
+ expected=expected_df,
+ path=f"s3://{s3_bucket_public.name}/parquet_dir",
+ read_kwargs={"storage_options": s3so},
+ write_kwargs={
+ "partition_cols": partition_col,
+ "compression": None,
+ "storage_options": s3so,
+ },
+ check_like=True,
+ repeat=1,
+ )
+
+ def test_read_file_like_obj_support(self, df_compat, using_infer_string):
+ pytest.importorskip("pyarrow")
+ buffer = BytesIO()
+ df_compat.to_parquet(buffer)
+ df_from_buf = read_parquet(buffer)
+ if using_infer_string and not pa_version_under19p0:
+ df_compat.columns = df_compat.columns.astype("str")
+ tm.assert_frame_equal(df_compat, df_from_buf)
+
+ def test_expand_user(self, df_compat, monkeypatch):
+ pytest.importorskip("pyarrow")
+ monkeypatch.setenv("HOME", "TestingUser")
+ monkeypatch.setenv("USERPROFILE", "TestingUser")
+ with pytest.raises(OSError, match=r".*TestingUser.*"):
+ read_parquet("~/file.parquet")
+ with pytest.raises(OSError, match=r".*TestingUser.*"):
+ df_compat.to_parquet("~/file.parquet")
+
+ def test_partition_cols_supported(self, tmp_path, pa, df_full):
+ # GH #23283
+ partition_cols = ["bool", "int"]
+ df = df_full
+ df.to_parquet(tmp_path, partition_cols=partition_cols, compression=None)
+ check_partition_names(tmp_path, partition_cols)
+ assert read_parquet(tmp_path).shape == df.shape
+
+ def test_partition_cols_string(self, tmp_path, pa, df_full):
+ # GH #27117
+ partition_cols = "bool"
+ partition_cols_list = [partition_cols]
+ df = df_full
+ df.to_parquet(tmp_path, partition_cols=partition_cols, compression=None)
+ check_partition_names(tmp_path, partition_cols_list)
+ assert read_parquet(tmp_path).shape == df.shape
+
+ @pytest.mark.parametrize(
+ "path_type", [str, lambda x: x], ids=["string", "pathlib.Path"]
+ )
+ def test_partition_cols_pathlib(self, tmp_path, pa, df_compat, path_type):
+ # GH 35902
+
+ partition_cols = "B"
+ partition_cols_list = [partition_cols]
+ df = df_compat
+
+ path = path_type(tmp_path)
+ df.to_parquet(path, partition_cols=partition_cols_list)
+ assert read_parquet(path).shape == df.shape
+
+ def test_empty_dataframe(self, pa, temp_file):
+ # GH #27339
+ df = pd.DataFrame(index=[], columns=[])
+ check_round_trip(df, temp_file, pa)
+
+ def test_write_with_schema(self, pa, temp_file):
+ import pyarrow
+
+ df = pd.DataFrame({"x": [0, 1]})
+ schema = pyarrow.schema([pyarrow.field("x", type=pyarrow.bool_())])
+ out_df = df.astype(bool)
+ check_round_trip(
+ df, temp_file, pa, write_kwargs={"schema": schema}, expected=out_df
+ )
+
+ def test_additional_extension_arrays(self, pa, using_infer_string, temp_file):
+ # test additional ExtensionArrays that are supported through the
+ # __arrow_array__ protocol
+ pytest.importorskip("pyarrow")
+ df = pd.DataFrame(
+ {
+ "a": pd.Series([1, 2, 3], dtype="Int64"),
+ "b": pd.Series([1, 2, 3], dtype="UInt32"),
+ "c": pd.Series(["a", None, "c"], dtype="string"),
+ }
+ )
+ if using_infer_string and pa_version_under19p0:
+ check_round_trip(df, temp_file, pa, expected=df.astype({"c": "str"}))
+ else:
+ check_round_trip(df, temp_file, pa)
+
+ df = pd.DataFrame({"a": pd.Series([1, 2, 3, None], dtype="Int64")})
+ check_round_trip(df, temp_file, pa)
+
+ def test_pyarrow_backed_string_array(
+ self, pa, string_storage, using_infer_string, temp_file
+ ):
+ # test ArrowStringArray supported through the __arrow_array__ protocol
+ pytest.importorskip("pyarrow")
+ df = pd.DataFrame({"a": pd.Series(["a", None, "c"], dtype="string[pyarrow]")})
+ with pd.option_context("string_storage", string_storage):
+ if using_infer_string:
+ if pa_version_under19p0:
+ expected = df.astype("str")
+ else:
+ expected = df.astype(f"string[{string_storage}]")
+ expected.columns = expected.columns.astype("str")
+ else:
+ expected = df.astype(f"string[{string_storage}]")
+ check_round_trip(df, temp_file, pa, expected=expected)
+
+ def test_additional_extension_types(self, pa, temp_file):
+ # test additional ExtensionArrays that are supported through the
+ # __arrow_array__ protocol + by defining a custom ExtensionType
+ pytest.importorskip("pyarrow")
+ df = pd.DataFrame(
+ {
+ "c": pd.IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)]),
+ "d": pd.period_range("2012-01-01", periods=3, freq="D"),
+ # GH-45881 issue with interval with datetime64[ns] subtype
+ "e": pd.IntervalIndex.from_breaks(
+ pd.date_range("2012-01-01", periods=4, freq="D")
+ ),
+ }
+ )
+ check_round_trip(df, temp_file, pa)
+
+ def test_timestamp_nanoseconds(self, pa, temp_file):
+ # with version 2.6, pyarrow defaults to writing the nanoseconds, so
+ # this should work without error, even for pyarrow < 13
+ ver = "2.6"
+ df = pd.DataFrame({"a": pd.date_range("2017-01-01", freq="1ns", periods=10)})
+ check_round_trip(df, temp_file, pa, write_kwargs={"version": ver})
+
+ def test_timezone_aware_index(self, pa, timezone_aware_date_list, temp_file):
+ idx = 5 * [timezone_aware_date_list]
+ df = pd.DataFrame(index=idx, data={"index_as_col": idx})
+
+ # see gh-36004
+ # compare time(zone) values only, skip their class:
+ # pyarrow always creates fixed offset timezones using pytz.FixedOffset()
+ # even if it was datetime.timezone() originally
+ #
+ # technically they are the same:
+ # they both implement datetime.tzinfo
+ # they both wrap datetime.timedelta()
+ # this use-case sets the resolution to 1 minute
+
+ expected = df[:]
+ if timezone_aware_date_list.tzinfo != datetime.UTC:
+ # pyarrow returns pytz.FixedOffset while pandas constructs datetime.timezone
+ # https://github.com/pandas-dev/pandas/issues/37286
+ try:
+ import pytz
+ except ImportError:
+ pass
+ else:
+ offset = df.index.tz.utcoffset(timezone_aware_date_list)
+ tz = pytz.FixedOffset(offset.total_seconds() / 60)
+ expected.index = expected.index.tz_convert(tz)
+ expected["index_as_col"] = expected["index_as_col"].dt.tz_convert(tz)
+ check_round_trip(df, temp_file, pa, check_dtype=False, expected=expected)
+
+ def test_filter_row_groups(self, pa, temp_file):
+ # https://github.com/pandas-dev/pandas/issues/26551
+ pytest.importorskip("pyarrow")
+ df = pd.DataFrame({"a": list(range(3))})
+ df.to_parquet(temp_file, engine=pa)
+ result = read_parquet(temp_file, pa, filters=[("a", "==", 0)])
+ assert len(result) == 1
+
+ @pytest.mark.filterwarnings("ignore:make_block is deprecated:DeprecationWarning")
+ def test_read_dtype_backend_pyarrow_config(self, pa, df_full, temp_file):
+ import pyarrow
+
+ df = df_full
+
+ # additional supported types for pyarrow
+ dti = pd.date_range("20130101", periods=3, tz="Europe/Brussels", unit="ns")
+ dti = dti._with_freq(None) # freq doesn't round-trip
+ df["datetime_tz"] = dti
+ df["bool_with_none"] = [True, None, True]
+
+ pa_table = pyarrow.Table.from_pandas(df)
+ expected = pa_table.to_pandas(types_mapper=pd.ArrowDtype)
+ expected["datetime_with_nat"] = expected["datetime_with_nat"].astype(
+ "timestamp[us][pyarrow]"
+ )
+
+ check_round_trip(
+ df,
+ temp_file,
+ engine=pa,
+ read_kwargs={"dtype_backend": "pyarrow"},
+ expected=expected,
+ )
+
+ def test_read_dtype_backend_pyarrow_config_index(self, pa, temp_file):
+ df = pd.DataFrame(
+ {"a": [1, 2]}, index=pd.Index([3, 4], name="test"), dtype="int64[pyarrow]"
+ )
+ expected = df.copy()
+
+ expected.index = expected.index.astype("int64[pyarrow]")
+ check_round_trip(
+ df,
+ temp_file,
+ engine=pa,
+ read_kwargs={"dtype_backend": "pyarrow"},
+ expected=expected,
+ )
+
+ @pytest.mark.parametrize(
+ "columns",
+ [
+ [0, 1],
+ pytest.param(
+ [b"foo", b"bar"],
+ marks=pytest.mark.xfail(
+ pa_version_under20p0,
+ raises=NotImplementedError,
+ reason="https://github.com/apache/arrow/pull/44171",
+ ),
+ ),
+ pytest.param(
+ [
+ datetime.datetime(2011, 1, 1, 0, 0),
+ datetime.datetime(2011, 1, 1, 1, 1),
+ ],
+ marks=pytest.mark.xfail(
+ pa_version_under17p0,
+ reason="pa.pandas_compat passes 'datetime64' to .astype",
+ ),
+ ),
+ ],
+ )
+ def test_columns_dtypes_not_invalid(self, pa, columns, temp_file):
+ df = pd.DataFrame({"string": list("abc"), "int": list(range(1, 4))})
+
+ df.columns = columns
+ check_round_trip(df, temp_file, pa)
+
+ def test_empty_columns(self, pa, temp_file):
+ # GH 52034
+ df = pd.DataFrame(index=pd.Index(["a", "b", "c"], name="custom name"))
+ check_round_trip(df, temp_file, pa)
+
+ def test_df_attrs_persistence(self, temp_file, pa):
+ df = pd.DataFrame(data={1: [1]})
+ df.attrs = {"test_attribute": 1}
+ df.to_parquet(temp_file, engine=pa)
+ new_df = read_parquet(temp_file, engine=pa)
+ assert new_df.attrs == df.attrs
+
+ def test_string_inference(self, temp_file, pa, using_infer_string):
+ # GH#54431
+ df = pd.DataFrame(data={"a": ["x", "y"]}, index=["a", "b"])
+ df.to_parquet(temp_file, engine=pa)
+ with pd.option_context("future.infer_string", True):
+ result = read_parquet(temp_file, engine=pa)
+ dtype = pd.StringDtype(na_value=np.nan)
+ expected = pd.DataFrame(
+ data={"a": ["x", "y"]},
+ dtype=dtype,
+ index=pd.Index(["a", "b"], dtype=dtype),
+ columns=pd.Index(
+ ["a"],
+ dtype=(
+ object if pa_version_under19p0 and not using_infer_string else dtype
+ ),
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_roundtrip_decimal(self, temp_file, pa):
+ # GH#54768
+ import pyarrow as pa
+
+ df = pd.DataFrame({"a": [Decimal("123.00")]}, dtype="string[pyarrow]")
+ df.to_parquet(temp_file, schema=pa.schema([("a", pa.decimal128(5))]))
+ result = read_parquet(temp_file)
+ if pa_version_under19p0:
+ expected = pd.DataFrame({"a": ["123"]}, dtype="string")
+ else:
+ expected = pd.DataFrame({"a": [Decimal("123.00")]}, dtype="object")
+ tm.assert_frame_equal(result, expected)
+
+ def test_infer_string_large_string_type(self, temp_file, pa):
+ # GH#54798
+ import pyarrow as pa
+ import pyarrow.parquet as pq
+
+ table = pa.table({"a": pa.array([None, "b", "c"], pa.large_string())})
+ pq.write_table(table, temp_file)
+
+ with pd.option_context("future.infer_string", True):
+ result = read_parquet(temp_file)
+ expected = pd.DataFrame(
+ data={"a": [None, "b", "c"]},
+ dtype=pd.StringDtype(na_value=np.nan),
+ columns=pd.Index(["a"], dtype=pd.StringDtype(na_value=np.nan)),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # NOTE: this test is not run by default, because it requires a lot of memory (>5GB)
+ # @pytest.mark.slow
+ # def test_string_column_above_2GB(self, tmp_path, pa):
+ # # https://github.com/pandas-dev/pandas/issues/55606
+ # # above 2GB of string data
+ # v1 = b"x" * 100000000
+ # v2 = b"x" * 147483646
+ # df = pd.DataFrame({"strings": [v1] * 20 + [v2] + ["x"] * 20}, dtype="string")
+ # df.to_parquet(tmp_path / "test.parquet")
+ # result = read_parquet(tmp_path / "test.parquet")
+ # assert result["strings"].dtype == "string"
+ # FIXME: don't leave commented-out
+
+ def test_non_nanosecond_timestamps(self, temp_file):
+ # GH#49236
+ pa = pytest.importorskip("pyarrow", "13.0.0")
+ pq = pytest.importorskip("pyarrow.parquet")
+
+ arr = pa.array([datetime.datetime(1600, 1, 1)], type=pa.timestamp("us"))
+ table = pa.table([arr], names=["timestamp"])
+ pq.write_table(table, temp_file)
+ result = read_parquet(temp_file)
+ expected = pd.DataFrame(
+ data={"timestamp": [datetime.datetime(1600, 1, 1)]},
+ dtype="datetime64[us]",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_maps_as_pydicts(self, pa, temp_file):
+ pyarrow = pytest.importorskip("pyarrow", "13.0.0")
+
+ schema = pyarrow.schema(
+ [("foo", pyarrow.map_(pyarrow.string(), pyarrow.int64()))]
+ )
+ df = pd.DataFrame([{"foo": {"A": 1}}, {"foo": {"B": 2}}])
+ check_round_trip(
+ df,
+ temp_file,
+ pa,
+ write_kwargs={"schema": schema},
+ read_kwargs={"to_pandas_kwargs": {"maps_as_pydicts": "strict"}},
+ )
+
+
+class TestParquetFastParquet(Base):
+ def test_basic(self, fp, df_full, request, temp_file):
+ pytz = pytest.importorskip("pytz")
+
+ tz = pytz.timezone("US/Eastern")
+ df = df_full
+
+ dti = pd.date_range("20130101", periods=3, tz=tz)
+ dti = dti._with_freq(None) # freq doesn't round-trip
+ df["datetime_tz"] = dti
+ df["timedelta"] = pd.timedelta_range("1 day", periods=3)
+ check_round_trip(df, temp_file, fp)
+
+ def test_columns_dtypes_invalid(self, fp, temp_file):
+ df = pd.DataFrame({"string": list("abc"), "int": list(range(1, 4))})
+
+ err = TypeError
+ msg = "Column name must be a string"
+
+ # numeric
+ df.columns = [0, 1]
+ self.check_error_on_write(df, fp, err, msg, temp_file)
+
+ # bytes
+ df.columns = [b"foo", b"bar"]
+ self.check_error_on_write(df, fp, err, msg, temp_file)
+
+ # python object
+ df.columns = [
+ datetime.datetime(2011, 1, 1, 0, 0),
+ datetime.datetime(2011, 1, 1, 1, 1),
+ ]
+ self.check_error_on_write(df, fp, err, msg, temp_file)
+
+ def test_duplicate_columns(self, fp, temp_file):
+ # not currently able to handle duplicate columns
+ df = pd.DataFrame(np.arange(12).reshape(4, 3), columns=list("aaa")).copy()
+ msg = "Cannot create parquet dataset with duplicate column names"
+ self.check_error_on_write(df, fp, ValueError, msg, temp_file)
+
+ def test_bool_with_none(self, fp, request, temp_file):
+ df = pd.DataFrame({"a": [True, None, False]})
+ expected = pd.DataFrame({"a": [1.0, np.nan, 0.0]}, dtype="float16")
+ # Fastparquet bug in 0.7.1 makes it so that this dtype becomes
+ # float64
+ check_round_trip(df, temp_file, fp, expected=expected, check_dtype=False)
+
+ def test_unsupported(self, fp, temp_file):
+ # period
+ df = pd.DataFrame({"a": pd.period_range("2013", freq="M", periods=3)})
+ # error from fastparquet -> don't check exact error message
+ self.check_error_on_write(df, fp, ValueError, None, temp_file)
+
+ # mixed
+ df = pd.DataFrame({"a": ["a", 1, 2.0]})
+ msg = "Can't infer object conversion type"
+ self.check_error_on_write(df, fp, ValueError, msg, temp_file)
+
+ def test_categorical(self, fp, temp_file):
+ df = pd.DataFrame({"a": pd.Categorical(list("abc"))})
+ check_round_trip(df, temp_file, fp)
+
+ def test_filter_row_groups(self, fp, temp_file):
+ d = {"a": list(range(3))}
+ df = pd.DataFrame(d)
+ df.to_parquet(temp_file, engine=fp, compression=None, row_group_offsets=1)
+ result = read_parquet(temp_file, fp, filters=[("a", "==", 0)])
+ assert len(result) == 1
+
+ @pytest.mark.single_cpu
+ def test_s3_roundtrip(self, df_compat, s3_bucket_public, s3so, fp, temp_file):
+ # GH #19134
+ check_round_trip(
+ df_compat,
+ temp_file,
+ fp,
+ path=f"s3://{s3_bucket_public.name}/fastparquet.parquet",
+ read_kwargs={"storage_options": s3so},
+ write_kwargs={"compression": None, "storage_options": s3so},
+ )
+
+ def test_partition_cols_supported(self, tmp_path, fp, df_full):
+ # GH #23283
+ partition_cols = ["bool", "int"]
+ df = df_full
+ df.to_parquet(
+ tmp_path,
+ engine="fastparquet",
+ partition_cols=partition_cols,
+ compression=None,
+ )
+ assert os.path.exists(tmp_path)
+ import fastparquet
+
+ actual_partition_cols = fastparquet.ParquetFile(str(tmp_path), False).cats
+ assert len(actual_partition_cols) == 2
+
+ def test_partition_cols_string(self, tmp_path, fp, df_full):
+ # GH #27117
+ partition_cols = "bool"
+ df = df_full
+ df.to_parquet(
+ tmp_path,
+ engine="fastparquet",
+ partition_cols=partition_cols,
+ compression=None,
+ )
+ assert os.path.exists(tmp_path)
+ import fastparquet
+
+ actual_partition_cols = fastparquet.ParquetFile(str(tmp_path), False).cats
+ assert len(actual_partition_cols) == 1
+
+ def test_partition_on_supported(self, tmp_path, fp, df_full):
+ # GH #23283
+ partition_cols = ["bool", "int"]
+ df = df_full
+ df.to_parquet(
+ tmp_path,
+ engine="fastparquet",
+ compression=None,
+ partition_on=partition_cols,
+ )
+ assert os.path.exists(tmp_path)
+ import fastparquet
+
+ actual_partition_cols = fastparquet.ParquetFile(str(tmp_path), False).cats
+ assert len(actual_partition_cols) == 2
+
+ def test_error_on_using_partition_cols_and_partition_on(
+ self, tmp_path, fp, df_full
+ ):
+ # GH #23283
+ partition_cols = ["bool", "int"]
+ df = df_full
+ msg = (
+ "Cannot use both partition_on and partition_cols. Use partition_cols for "
+ "partitioning data"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.to_parquet(
+ tmp_path,
+ engine="fastparquet",
+ compression=None,
+ partition_on=partition_cols,
+ partition_cols=partition_cols,
+ )
+
+ def test_empty_dataframe(self, fp, temp_file):
+ # GH #27339
+ df = pd.DataFrame()
+ expected = df.copy()
+ check_round_trip(df, temp_file, fp, expected=expected)
+
+ def test_timezone_aware_index(
+ self, fp, timezone_aware_date_list, request, temp_file
+ ):
+ idx = 5 * [timezone_aware_date_list]
+
+ df = pd.DataFrame(index=idx, data={"index_as_col": idx})
+
+ expected = df.copy()
+ expected.index.name = "index"
+ check_round_trip(df, temp_file, fp, expected=expected)
+
+ def test_close_file_handle_on_read_error(self, temp_file):
+ pathlib.Path(temp_file).write_bytes(b"breakit")
+ with tm.external_error_raised(Exception): # Not important which exception
+ read_parquet(temp_file, engine="fastparquet")
+ # The next line raises an error on Windows if the file is still open
+ pathlib.Path(temp_file).unlink(missing_ok=False)
+
+ def test_bytes_file_name(self, engine, temp_file):
+ # GH#48944
+ df = pd.DataFrame(data={"A": [0, 1], "B": [1, 0]})
+ with open(temp_file, "wb") as f:
+ df.to_parquet(f)
+
+ result = read_parquet(temp_file, engine=engine)
+ tm.assert_frame_equal(result, df)
+
+ def test_filesystem_notimplemented(self, temp_file):
+ pytest.importorskip("fastparquet")
+ df = pd.DataFrame(data={"A": [0, 1], "B": [1, 0]})
+ with pytest.raises(NotImplementedError, match="filesystem is not implemented"):
+ df.to_parquet(temp_file, engine="fastparquet", filesystem="foo")
+
+ pathlib.Path(temp_file).write_bytes(b"foo")
+ with pytest.raises(NotImplementedError, match="filesystem is not implemented"):
+ read_parquet(temp_file, engine="fastparquet", filesystem="foo")
+
+ def test_invalid_filesystem(self, temp_file):
+ pytest.importorskip("pyarrow")
+ df = pd.DataFrame(data={"A": [0, 1], "B": [1, 0]})
+
+ with pytest.raises(
+ ValueError, match="filesystem must be a pyarrow or fsspec FileSystem"
+ ):
+ df.to_parquet(temp_file, engine="pyarrow", filesystem="foo")
+
+ pathlib.Path(temp_file).write_bytes(b"foo")
+ with pytest.raises(
+ ValueError, match="filesystem must be a pyarrow or fsspec FileSystem"
+ ):
+ read_parquet(temp_file, engine="pyarrow", filesystem="foo")
+
+ def test_unsupported_pa_filesystem_storage_options(self, temp_file):
+ pa_fs = pytest.importorskip("pyarrow.fs")
+ df = pd.DataFrame(data={"A": [0, 1], "B": [1, 0]})
+
+ with pytest.raises(
+ NotImplementedError,
+ match="storage_options not supported with a pyarrow FileSystem.",
+ ):
+ df.to_parquet(
+ temp_file,
+ engine="pyarrow",
+ filesystem=pa_fs.LocalFileSystem(),
+ storage_options={"foo": "bar"},
+ )
+
+ pathlib.Path(temp_file).write_bytes(b"foo")
+ with pytest.raises(
+ NotImplementedError,
+ match="storage_options not supported with a pyarrow FileSystem.",
+ ):
+ read_parquet(
+ temp_file,
+ engine="pyarrow",
+ filesystem=pa_fs.LocalFileSystem(),
+ storage_options={"foo": "bar"},
+ )
+
+ def test_invalid_dtype_backend(self, engine, temp_file):
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ df = pd.DataFrame({"int": list(range(1, 4))})
+ df.to_parquet(temp_file)
+ with pytest.raises(ValueError, match=msg):
+ read_parquet(temp_file, dtype_backend="numpy")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_pickle.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_pickle.py
new file mode 100644
index 0000000000000000000000000000000000000000..7754c58a88ef137825906a543a3783f2e21c71b0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_pickle.py
@@ -0,0 +1,590 @@
+"""
+manage legacy pickle tests
+
+How to add pickle tests:
+
+1. Install pandas version intended to output the pickle.
+
+2. Execute "generate_legacy_storage_files.py" to create the pickle.
+$ python generate_legacy_storage_files.py pickle
+
+3. Move the created pickle to "data/legacy_pickle/" directory.
+"""
+
+from __future__ import annotations
+
+import bz2
+import datetime
+import functools
+from functools import partial
+import gzip
+import io
+import os
+from pathlib import Path
+import pickle
+import shutil
+import tarfile
+from typing import Any
+import uuid
+import zipfile
+
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_little_endian
+from pandas.compat._optional import import_optional_dependency
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ period_range,
+)
+import pandas._testing as tm
+from pandas.tests.io.generate_legacy_storage_files import create_pickle_data
+from pandas.util.version import Version
+
+import pandas.io.common as icom
+from pandas.tseries.offsets import (
+ Day,
+ MonthEnd,
+)
+
+
+# ---------------------
+# comparison functions
+# ---------------------
+def compare_element(result, expected, typ):
+ if isinstance(expected, Index):
+ tm.assert_index_equal(result, expected)
+ return
+
+ if typ.startswith("sp_"):
+ tm.assert_equal(result, expected)
+ elif typ == "timestamp":
+ if expected is pd.NaT:
+ assert result is pd.NaT
+ else:
+ assert result == expected
+ else:
+ comparator = getattr(tm, f"assert_{typ}_equal", tm.assert_almost_equal)
+ comparator(result, expected)
+
+
+# ---------------------
+# tests
+# ---------------------
+
+
+def test_pickles(datapath):
+ pytest.importorskip("pytz")
+ if not is_platform_little_endian():
+ pytest.skip("known failure on non-little endian")
+
+ current_data = create_pickle_data()
+
+ # For loop for compat with --strict-data-files
+ for legacy_pickle in Path(__file__).parent.glob("data/legacy_pickle/*/*.p*kl*"):
+ legacy_version = Version(legacy_pickle.parent.name)
+ legacy_pickle = datapath(legacy_pickle)
+
+ data = pd.read_pickle(legacy_pickle)
+
+ for typ, dv in data.items():
+ for dt, result in dv.items():
+ expected = current_data[typ][dt]
+
+ if (
+ typ == "timestamp"
+ and dt in ("tz", "both")
+ and legacy_version < Version("1.3.0")
+ ):
+ # convert to wall time
+ # (bug since pandas 2.0 that tz gets dropped for older pickle files)
+ expected = expected.tz_convert(None)
+
+ if legacy_version < Version("3.0.0.dev0"):
+ # before 3.0, we had:
+ # - object dtype instead of string
+ # - ns instead of us as the default unit
+ if typ in ("frame", "sp_frame"):
+ expected.columns = expected.columns.astype("object")
+ if dt in ("mixed", "mixed_dup"):
+ expected["C"] = expected["C"].astype(object)
+ expected["D"] = expected["D"].dt.as_unit("ns")
+ elif dt in ("cat_onecol", "cat_and_float"):
+ expected["A"] = expected["A"].astype(
+ pd.CategoricalDtype(
+ expected["A"].cat.categories.astype(object)
+ )
+ )
+ elif typ == "sp_frame" and dt == "float":
+ expected.index = expected.index.as_unit("ns")
+ elif dt == "mi":
+ expected.index = expected.index.set_levels(
+ [
+ level.astype("object")
+ for level in expected.index.levels
+ ],
+ )
+ elif typ in ("series", "sp_series"):
+ if dt == "ts":
+ expected.index = expected.index.as_unit("ns")
+ elif dt in ("dt", "dt_tz"):
+ expected = expected.dt.as_unit("ns")
+ elif dt == "cat":
+ expected = expected.astype(
+ pd.CategoricalDtype(
+ expected.cat.categories.astype(object)
+ )
+ )
+ elif dt == "dup":
+ expected.index = expected.index.astype(object)
+ elif typ == "index" and dt in ("date", "timedelta"):
+ expected = expected.as_unit("ns")
+ elif typ == "mi":
+ expected = expected.set_levels(
+ [level.astype("object") for level in expected.levels],
+ )
+ if dt == "string":
+ # we switched from python to pyarrow as default storage in 3.0
+ expected = expected.astype(pd.StringDtype("python"))
+
+ if dt in ("dt_mixed_tzs", "dt_mixed2_tzs"):
+ if legacy_version < Version("2.1"):
+ # in pandas < 2.0, Timestamp() unit defaulted to 'ns'
+ expected_unit = "ns"
+ elif Version("2.1") <= legacy_version < Version("3.0.0.dev0"):
+ # in pandas 2.x, Timestamp() unit depended on input
+ expected_unit = "s"
+ else:
+ expected_unit = "us"
+ for col in expected.columns:
+ expected[col] = expected[col].dt.as_unit(expected_unit)
+ if typ == "index" and dt == "int" and "windows" in legacy_pickle:
+ expected = expected.astype(np.int32)
+
+ if typ == "series" and dt == "ts":
+ # GH 7748
+ tm.assert_series_equal(result, expected)
+ assert result.index.freq == expected.index.freq
+ assert not result.index.freq.normalize
+ tm.assert_series_equal(result > 0, expected > 0)
+
+ # GH 9291
+ freq = result.index.freq
+ assert freq + Day(1) == Day(2)
+
+ res = freq + pd.Timedelta(hours=1)
+ assert isinstance(res, pd.Timedelta)
+ assert res == pd.Timedelta(days=1, hours=1)
+
+ res = freq + pd.Timedelta(nanoseconds=1)
+ assert isinstance(res, pd.Timedelta)
+ assert res == pd.Timedelta(days=1, nanoseconds=1)
+ elif typ == "index" and dt == "period":
+ tm.assert_index_equal(result, expected)
+ assert isinstance(result.freq, MonthEnd)
+ assert result.freq == MonthEnd()
+ assert result.freqstr == "M"
+ tm.assert_index_equal(result.shift(2), expected.shift(2))
+ elif typ == "series" and dt in ("dt_tz", "cat"):
+ tm.assert_series_equal(result, expected)
+ elif typ == "frame" and dt in (
+ "dt_mixed_tzs",
+ "cat_onecol",
+ "cat_and_float",
+ ):
+ tm.assert_frame_equal(result, expected)
+ else:
+ compare_element(result, expected, typ)
+
+
+def python_pickler(obj, path):
+ with open(path, "wb") as fh:
+ pickle.dump(obj, fh, protocol=-1)
+
+
+def python_unpickler(path):
+ with open(path, "rb") as fh:
+ fh.seek(0)
+ return pickle.load(fh)
+
+
+def flatten(data: dict) -> list[tuple[str, Any]]:
+ """Flatten create_pickle_data"""
+ return [
+ (typ, example)
+ for typ, examples in data.items()
+ for example in examples.values()
+ ]
+
+
+@pytest.mark.parametrize(
+ "pickle_writer",
+ [
+ pytest.param(python_pickler, id="python"),
+ pytest.param(pd.to_pickle, id="pandas_proto_default"),
+ pytest.param(
+ functools.partial(pd.to_pickle, protocol=pickle.HIGHEST_PROTOCOL),
+ id="pandas_proto_highest",
+ ),
+ pytest.param(functools.partial(pd.to_pickle, protocol=4), id="pandas_proto_4"),
+ pytest.param(
+ functools.partial(pd.to_pickle, protocol=5),
+ id="pandas_proto_5",
+ ),
+ ],
+)
+@pytest.mark.parametrize("writer", [pd.to_pickle, python_pickler])
+@pytest.mark.parametrize("typ, expected", flatten(create_pickle_data()))
+def test_round_trip_current(typ, expected, pickle_writer, writer, temp_file):
+ path = temp_file
+ # test writing with each pickler
+ pickle_writer(expected, path)
+
+ # test reading with each unpickler
+ result = pd.read_pickle(path)
+ compare_element(result, expected, typ)
+
+ result = python_unpickler(path)
+ compare_element(result, expected, typ)
+
+ # and the same for file objects (GH 35679)
+ with open(path, mode="wb") as handle:
+ writer(expected, path)
+ handle.seek(0) # shouldn't close file handle
+ with open(path, mode="rb") as handle:
+ result = pd.read_pickle(handle)
+ handle.seek(0) # shouldn't close file handle
+ compare_element(result, expected, typ)
+
+
+def test_pickle_path_pathlib(temp_file):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ result = tm.round_trip_pathlib(df.to_pickle, pd.read_pickle, temp_file)
+ tm.assert_frame_equal(df, result)
+
+
+# ---------------------
+# test pickle compression
+# ---------------------
+
+
+@pytest.fixture
+def get_random_path():
+ return f"__{uuid.uuid4()}__.pickle"
+
+
+class TestCompression:
+ _extension_to_compression = icom.extension_to_compression
+
+ def compress_file(self, src_path, dest_path, compression):
+ if compression is None:
+ shutil.copyfile(src_path, dest_path)
+ return
+
+ if compression == "gzip":
+ f = gzip.open(dest_path, "w")
+ elif compression == "bz2":
+ f = bz2.BZ2File(dest_path, "w")
+ elif compression == "zip":
+ with zipfile.ZipFile(dest_path, "w", compression=zipfile.ZIP_DEFLATED) as f:
+ f.write(src_path, os.path.basename(src_path))
+ elif compression == "tar":
+ with open(src_path, "rb") as fh:
+ with tarfile.open(dest_path, mode="w") as tar:
+ tarinfo = tar.gettarinfo(src_path, os.path.basename(src_path))
+ tar.addfile(tarinfo, fh)
+ elif compression == "xz":
+ import lzma
+
+ f = lzma.LZMAFile(dest_path, "w")
+ elif compression == "zstd":
+ f = import_optional_dependency("zstandard").open(dest_path, "wb")
+ else:
+ msg = f"Unrecognized compression type: {compression}"
+ raise ValueError(msg)
+
+ if compression not in ["zip", "tar"]:
+ with open(src_path, "rb") as fh:
+ with f:
+ f.write(fh.read())
+
+ def test_write_explicit(self, compression, get_random_path, temp_file):
+ p1 = temp_file.parent / f"{temp_file.stem}.compressed"
+ p2 = temp_file.parent / f"{temp_file.stem}.raw"
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ # write to compressed file
+ df.to_pickle(p1, compression=compression)
+
+ # decompress
+ with tm.decompress_file(p1, compression=compression) as f:
+ with open(p2, "wb") as fh:
+ fh.write(f.read())
+
+ # read decompressed file
+ df2 = pd.read_pickle(p2, compression=None)
+
+ tm.assert_frame_equal(df, df2)
+
+ @pytest.mark.parametrize("compression", ["", "None", "bad", "7z"])
+ def test_write_explicit_bad(self, compression, get_random_path, temp_file):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ path = temp_file
+ with pytest.raises(ValueError, match="Unrecognized compression type"):
+ df.to_pickle(path, compression=compression)
+
+ def test_write_infer(self, compression_ext, get_random_path, temp_file):
+ p1 = temp_file.parent / f"{temp_file.stem}{compression_ext}"
+ p2 = temp_file.parent / f"{temp_file.stem}.raw"
+ compression = self._extension_to_compression.get(compression_ext.lower())
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ # write to compressed file by inferred compression method
+ df.to_pickle(p1)
+
+ # decompress
+ with tm.decompress_file(p1, compression=compression) as f:
+ with open(p2, "wb") as fh:
+ fh.write(f.read())
+
+ # read decompressed file
+ df2 = pd.read_pickle(p2, compression=None)
+
+ tm.assert_frame_equal(df, df2)
+
+ def test_read_explicit(self, compression, get_random_path, temp_file):
+ p1 = temp_file.parent / f"{temp_file.stem}.raw"
+ p2 = temp_file.parent / f"{temp_file.stem}.compressed"
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ # write to uncompressed file
+ df.to_pickle(p1, compression=None)
+
+ # compress
+ self.compress_file(p1, p2, compression=compression)
+
+ # read compressed file
+ df2 = pd.read_pickle(p2, compression=compression)
+ tm.assert_frame_equal(df, df2)
+
+ def test_read_infer(self, compression_ext, get_random_path, temp_file):
+ p1 = temp_file.parent / f"{temp_file.stem}.raw"
+ p2 = temp_file.parent / f"{temp_file.stem}{compression_ext}"
+ compression = self._extension_to_compression.get(compression_ext.lower())
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ # write to uncompressed file
+ df.to_pickle(p1, compression=None)
+
+ # compress
+ self.compress_file(p1, p2, compression=compression)
+
+ # read compressed file by inferred compression method
+ df2 = pd.read_pickle(p2)
+ tm.assert_frame_equal(df, df2)
+
+
+# ---------------------
+# test pickle compression
+# ---------------------
+
+
+class TestProtocol:
+ @pytest.mark.parametrize("protocol", [-1, 0, 1, 2])
+ def test_read(self, protocol, get_random_path, temp_file):
+ path = temp_file
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ df.to_pickle(path, protocol=protocol)
+ df2 = pd.read_pickle(path)
+ tm.assert_frame_equal(df, df2)
+
+
+def test_pickle_buffer_roundtrip(temp_file):
+ path = temp_file
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ with open(path, "wb") as fh:
+ df.to_pickle(fh)
+ with open(path, "rb") as fh:
+ result = pd.read_pickle(fh)
+ tm.assert_frame_equal(df, result)
+
+
+def test_pickle_fsspec_roundtrip(temp_file):
+ pytest.importorskip("fsspec")
+ # Using temp_file for context, but fsspec uses memory URL
+ mockurl = "memory://mockfile"
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+ df.to_pickle(mockurl)
+ result = pd.read_pickle(mockurl)
+ tm.assert_frame_equal(df, result)
+
+
+class MyTz(datetime.tzinfo):
+ def __init__(self) -> None:
+ pass
+
+
+def test_read_pickle_with_subclass(temp_file):
+ # GH 12163
+ expected = Series(dtype=object), MyTz()
+ result = tm.round_trip_pickle(expected, temp_file)
+
+ tm.assert_series_equal(result[0], expected[0])
+ assert isinstance(result[1], MyTz)
+
+
+def test_pickle_binary_object_compression(compression, temp_file):
+ """
+ Read/write from binary file-objects w/wo compression.
+
+ GH 26237, GH 29054, and GH 29570
+ """
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )
+
+ # reference for compression
+ path = temp_file
+ df.to_pickle(path, compression=compression)
+ reference = path.read_bytes()
+
+ # write
+ buffer = io.BytesIO()
+ df.to_pickle(buffer, compression=compression)
+ buffer.seek(0)
+
+ # gzip and zip safe the filename: cannot compare the compressed content
+ assert buffer.getvalue() == reference or compression in ("gzip", "zip", "tar")
+
+ # read
+ read_df = pd.read_pickle(buffer, compression=compression)
+ buffer.seek(0)
+ tm.assert_frame_equal(df, read_df)
+
+
+def test_pickle_dataframe_with_multilevel_index(
+ multiindex_year_month_day_dataframe_random_data,
+ multiindex_dataframe_random_data,
+ temp_file,
+):
+ ymd = multiindex_year_month_day_dataframe_random_data
+ frame = multiindex_dataframe_random_data
+
+ def _test_roundtrip(frame, temp_file):
+ unpickled = tm.round_trip_pickle(frame, temp_file)
+ tm.assert_frame_equal(frame, unpickled)
+
+ _test_roundtrip(frame, temp_file)
+ _test_roundtrip(frame.T, temp_file)
+ _test_roundtrip(ymd, temp_file)
+ _test_roundtrip(ymd.T, temp_file)
+
+
+def test_pickle_timeseries_periodindex(temp_file):
+ # GH#2891
+ prng = period_range("1/1/2011", "1/1/2012", freq="M")
+ ts = Series(np.random.default_rng(2).standard_normal(len(prng)), prng)
+ new_ts = tm.round_trip_pickle(ts, temp_file)
+ assert new_ts.index.freqstr == "M"
+
+
+@pytest.mark.parametrize(
+ "name", [777, 777.0, "name", datetime.datetime(2001, 11, 11), (1, 2)]
+)
+def test_pickle_preserve_name(name, temp_file):
+ unpickled = tm.round_trip_pickle(
+ Series(np.arange(10, dtype=np.float64), name=name), temp_file
+ )
+ assert unpickled.name == name
+
+
+def test_pickle_datetimes(datetime_series, temp_file):
+ unp_ts = tm.round_trip_pickle(datetime_series, temp_file)
+ tm.assert_series_equal(unp_ts, datetime_series)
+
+
+def test_pickle_strings(string_series, temp_file):
+ unp_series = tm.round_trip_pickle(string_series, temp_file)
+ tm.assert_series_equal(unp_series, string_series)
+
+
+def test_pickle_preserves_block_ndim(temp_file):
+ # GH#37631
+ ser = Series(list("abc")).astype("category").iloc[[0]]
+ res = tm.round_trip_pickle(ser, temp_file)
+
+ assert res._mgr.blocks[0].ndim == 1
+ assert res._mgr.blocks[0].shape == (1,)
+
+ # GH#37631 OP issue was about indexing, underlying problem was pickle
+ tm.assert_series_equal(res[[True]], ser)
+
+
+@pytest.mark.parametrize("protocol", [pickle.DEFAULT_PROTOCOL, pickle.HIGHEST_PROTOCOL])
+def test_pickle_big_dataframe_compression(protocol, compression, temp_file):
+ # GH#39002
+ df = DataFrame(range(100000))
+ result = tm.round_trip_pathlib(
+ partial(df.to_pickle, protocol=protocol, compression=compression),
+ partial(pd.read_pickle, compression=compression),
+ temp_file,
+ )
+ tm.assert_frame_equal(df, result)
+
+
+def test_pickle_frame_v124_unpickle_130(datapath):
+ # GH#42345 DataFrame created in 1.2.x, unpickle in 1.3.x
+ path = datapath(
+ Path(__file__).parent,
+ "data",
+ "legacy_pickle",
+ "1.2.4",
+ "empty_frame_v1_2_4-GH#42345.pkl",
+ )
+ with open(path, "rb") as fd:
+ df = pickle.load(fd)
+
+ expected = DataFrame(index=[], columns=[])
+ tm.assert_frame_equal(df, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_s3.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_s3.py
new file mode 100644
index 0000000000000000000000000000000000000000..31d22223b0a33218db58120cea1db393827ff699
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_s3.py
@@ -0,0 +1,33 @@
+from io import BytesIO
+
+import pytest
+
+from pandas import read_csv
+
+
+@pytest.mark.parametrize("data", [b"foo,bar,baz\n1,2,3\n4,5,6\n", b"just,the,header\n"])
+def test_streaming_s3_objects(data):
+ # GH 17135
+ # botocore gained iteration support in 1.10.47, can now be used in read_*
+ pytest.importorskip("botocore", minversion="1.10.47")
+ from botocore.response import StreamingBody
+
+ body = StreamingBody(BytesIO(data), content_length=len(data))
+ read_csv(body)
+
+
+@pytest.mark.single_cpu
+@pytest.mark.parametrize("header", ["infer", None])
+def test_read_with_and_without_creds_from_pub_bucket(
+ s3_bucket_public_with_data, s3so, header
+):
+ # GH 34626
+ pytest.importorskip("s3fs")
+ nrows = 5
+ df = read_csv(
+ f"s3://{s3_bucket_public_with_data.name}/tips.csv",
+ nrows=nrows,
+ header=header,
+ storage_options=s3so,
+ )
+ assert len(df) == nrows
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_spss.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_spss.py
new file mode 100644
index 0000000000000000000000000000000000000000..6210c0289a160e0ec853cb810dd80e404e11c37e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_spss.py
@@ -0,0 +1,169 @@
+import datetime
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+pyreadstat = pytest.importorskip("pyreadstat")
+
+
+# TODO(CoW) - detection of chained assignment in cython
+# https://github.com/pandas-dev/pandas/issues/51315
+@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
+@pytest.mark.parametrize("path_klass", [lambda p: p, Path])
+def test_spss_labelled_num(path_klass, datapath):
+ # test file from the Haven project (https://haven.tidyverse.org/)
+ # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT
+ fname = path_klass(datapath("io", "data", "spss", "labelled-num.sav"))
+
+ df = pd.read_spss(fname, convert_categoricals=True)
+ expected = pd.DataFrame({"VAR00002": "This is one"}, index=[0])
+ expected["VAR00002"] = pd.Categorical(expected["VAR00002"])
+ tm.assert_frame_equal(df, expected)
+
+ df = pd.read_spss(fname, convert_categoricals=False)
+ expected = pd.DataFrame({"VAR00002": 1.0}, index=[0])
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
+def test_spss_labelled_num_na(datapath):
+ # test file from the Haven project (https://haven.tidyverse.org/)
+ # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT
+ fname = datapath("io", "data", "spss", "labelled-num-na.sav")
+
+ df = pd.read_spss(fname, convert_categoricals=True)
+ expected = pd.DataFrame({"VAR00002": ["This is one", None]})
+ expected["VAR00002"] = pd.Categorical(expected["VAR00002"])
+ tm.assert_frame_equal(df, expected)
+
+ df = pd.read_spss(fname, convert_categoricals=False)
+ expected = pd.DataFrame({"VAR00002": [1.0, np.nan]})
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
+def test_spss_labelled_str(datapath):
+ # test file from the Haven project (https://haven.tidyverse.org/)
+ # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT
+ fname = datapath("io", "data", "spss", "labelled-str.sav")
+
+ df = pd.read_spss(fname, convert_categoricals=True)
+ expected = pd.DataFrame({"gender": ["Male", "Female"]})
+ expected["gender"] = pd.Categorical(expected["gender"])
+ tm.assert_frame_equal(df, expected)
+
+ df = pd.read_spss(fname, convert_categoricals=False)
+ expected = pd.DataFrame({"gender": ["M", "F"]})
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
+def test_spss_kwargs(datapath):
+ # test file from the Haven project (https://haven.tidyverse.org/)
+ # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT
+ fname = datapath("io", "data", "spss", "labelled-str.sav")
+
+ df = pd.read_spss(fname, convert_categoricals=True, row_limit=1)
+ expected = pd.DataFrame({"gender": ["Male"]}, dtype="category")
+ tm.assert_frame_equal(df, expected)
+
+ df = pd.read_spss(fname, convert_categoricals=False, row_offset=1)
+ expected = pd.DataFrame({"gender": ["F"]})
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
+def test_spss_umlauts(datapath):
+ # test file from the Haven project (https://haven.tidyverse.org/)
+ # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT
+ fname = datapath("io", "data", "spss", "umlauts.sav")
+
+ df = pd.read_spss(fname, convert_categoricals=True)
+ expected = pd.DataFrame(
+ {"var1": ["the ä umlaut", "the ü umlaut", "the ä umlaut", "the ö umlaut"]}
+ )
+ expected["var1"] = pd.Categorical(expected["var1"])
+ tm.assert_frame_equal(df, expected)
+
+ df = pd.read_spss(fname, convert_categoricals=False)
+ expected = pd.DataFrame({"var1": [1.0, 2.0, 1.0, 3.0]})
+ tm.assert_frame_equal(df, expected)
+
+
+def test_spss_usecols(datapath):
+ # usecols must be list-like
+ fname = datapath("io", "data", "spss", "labelled-num.sav")
+
+ with pytest.raises(TypeError, match="usecols must be list-like."):
+ pd.read_spss(fname, usecols="VAR00002")
+
+
+def test_spss_umlauts_dtype_backend(datapath, dtype_backend):
+ # test file from the Haven project (https://haven.tidyverse.org/)
+ # Licence at LICENSES/HAVEN_LICENSE, LICENSES/HAVEN_MIT
+ fname = datapath("io", "data", "spss", "umlauts.sav")
+
+ df = pd.read_spss(fname, convert_categoricals=False, dtype_backend=dtype_backend)
+ expected = pd.DataFrame({"var1": [1.0, 2.0, 1.0, 3.0]}, dtype="Int64")
+
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+
+ from pandas.arrays import ArrowExtensionArray
+
+ expected = pd.DataFrame(
+ {
+ col: ArrowExtensionArray(pa.array(expected[col], from_pandas=True))
+ for col in expected.columns
+ }
+ )
+
+ tm.assert_frame_equal(df, expected)
+
+
+def test_invalid_dtype_backend():
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ with pytest.raises(ValueError, match=msg):
+ pd.read_spss("test", dtype_backend="numpy")
+
+
+@pytest.mark.filterwarnings("ignore::pandas.errors.ChainedAssignmentError")
+def test_spss_metadata(datapath):
+ # GH 54264
+ fname = datapath("io", "data", "spss", "labelled-num.sav")
+
+ df = pd.read_spss(fname)
+ metadata = {
+ "column_names": ["VAR00002"],
+ "column_labels": [None],
+ "column_names_to_labels": {"VAR00002": None},
+ "file_encoding": "UTF-8",
+ "number_columns": 1,
+ "number_rows": 1,
+ "variable_value_labels": {"VAR00002": {1.0: "This is one"}},
+ "value_labels": {"labels0": {1.0: "This is one"}},
+ "variable_to_label": {"VAR00002": "labels0"},
+ "notes": [],
+ "original_variable_types": {"VAR00002": "F8.0"},
+ "readstat_variable_types": {"VAR00002": "double"},
+ "table_name": None,
+ "missing_ranges": {},
+ "missing_user_values": {},
+ "variable_storage_width": {"VAR00002": 8},
+ "variable_display_width": {"VAR00002": 8},
+ "variable_alignment": {"VAR00002": "unknown"},
+ "variable_measure": {"VAR00002": "unknown"},
+ "file_label": None,
+ "file_format": "sav/zsav",
+ "creation_time": datetime.datetime(2015, 2, 6, 14, 33, 36),
+ "modification_time": datetime.datetime(2015, 2, 6, 14, 33, 36),
+ "mr_sets": {},
+ }
+ tm.assert_dict_equal(df.attrs, metadata)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_sql.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_sql.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0e06b204379986e0af0b5edcde4182a091e5fb9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_sql.py
@@ -0,0 +1,4398 @@
+from __future__ import annotations
+
+import contextlib
+import csv
+from datetime import (
+ date,
+ datetime,
+ time,
+ timedelta,
+)
+from decimal import Decimal
+from io import StringIO
+from pathlib import Path
+import sqlite3
+from typing import TYPE_CHECKING
+import uuid
+
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+from pandas._libs import lib
+from pandas.compat import pa_version_under14p1
+from pandas.compat._optional import import_optional_dependency
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ concat,
+ date_range,
+ isna,
+ to_datetime,
+ to_timedelta,
+)
+import pandas._testing as tm
+from pandas.util.version import Version
+
+from pandas.io import sql
+from pandas.io.sql import (
+ SQLAlchemyEngine,
+ SQLDatabase,
+ SQLiteDatabase,
+ get_engine,
+ pandasSQL_builder,
+ read_sql_query,
+ read_sql_table,
+)
+
+if TYPE_CHECKING:
+ import sqlalchemy
+
+
+pytestmark = [
+ pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager to DataFrame:DeprecationWarning"
+ ),
+ pytest.mark.single_cpu,
+]
+
+
+@pytest.fixture
+def sql_strings():
+ return {
+ "read_parameters": {
+ "sqlite": "SELECT * FROM iris WHERE Name=? AND SepalLength=?",
+ "mysql": "SELECT * FROM iris WHERE `Name`=%s AND `SepalLength`=%s",
+ "postgresql": 'SELECT * FROM iris WHERE "Name"=%s AND "SepalLength"=%s',
+ },
+ "read_named_parameters": {
+ "sqlite": """
+ SELECT * FROM iris WHERE Name=:name AND SepalLength=:length
+ """,
+ "mysql": """
+ SELECT * FROM iris WHERE
+ `Name`=%(name)s AND `SepalLength`=%(length)s
+ """,
+ "postgresql": """
+ SELECT * FROM iris WHERE
+ "Name"=%(name)s AND "SepalLength"=%(length)s
+ """,
+ },
+ "read_no_parameters_with_percent": {
+ "sqlite": "SELECT * FROM iris WHERE Name LIKE '%'",
+ "mysql": "SELECT * FROM iris WHERE `Name` LIKE '%'",
+ "postgresql": "SELECT * FROM iris WHERE \"Name\" LIKE '%'",
+ },
+ }
+
+
+def iris_table_metadata():
+ import sqlalchemy
+ from sqlalchemy import (
+ Column,
+ Double,
+ Float,
+ MetaData,
+ String,
+ Table,
+ )
+
+ dtype = Double if Version(sqlalchemy.__version__) >= Version("2.0.0") else Float
+ metadata = MetaData()
+ iris = Table(
+ "iris",
+ metadata,
+ Column("SepalLength", dtype),
+ Column("SepalWidth", dtype),
+ Column("PetalLength", dtype),
+ Column("PetalWidth", dtype),
+ Column("Name", String(200)),
+ )
+ return iris
+
+
+def create_and_load_iris_sqlite3(conn, iris_file: Path):
+ stmt = """CREATE TABLE iris (
+ "SepalLength" REAL,
+ "SepalWidth" REAL,
+ "PetalLength" REAL,
+ "PetalWidth" REAL,
+ "Name" TEXT
+ )"""
+
+ cur = conn.cursor()
+ cur.execute(stmt)
+ with iris_file.open(newline=None, encoding="utf-8") as csvfile:
+ reader = csv.reader(csvfile)
+ next(reader)
+ stmt = "INSERT INTO iris VALUES(?, ?, ?, ?, ?)"
+ # ADBC requires explicit types - no implicit str -> float conversion
+ records = []
+ records = [
+ (
+ float(row[0]),
+ float(row[1]),
+ float(row[2]),
+ float(row[3]),
+ row[4],
+ )
+ for row in reader
+ ]
+
+ cur.executemany(stmt, records)
+ cur.close()
+
+ conn.commit()
+
+
+def create_and_load_iris_postgresql(conn, iris_file: Path):
+ stmt = """CREATE TABLE iris (
+ "SepalLength" DOUBLE PRECISION,
+ "SepalWidth" DOUBLE PRECISION,
+ "PetalLength" DOUBLE PRECISION,
+ "PetalWidth" DOUBLE PRECISION,
+ "Name" TEXT
+ )"""
+ with conn.cursor() as cur:
+ cur.execute(stmt)
+ with iris_file.open(newline=None, encoding="utf-8") as csvfile:
+ reader = csv.reader(csvfile)
+ next(reader)
+ stmt = "INSERT INTO iris VALUES($1, $2, $3, $4, $5)"
+ # ADBC requires explicit types - no implicit str -> float conversion
+ records = [
+ (
+ float(row[0]),
+ float(row[1]),
+ float(row[2]),
+ float(row[3]),
+ row[4],
+ )
+ for row in reader
+ ]
+
+ cur.executemany(stmt, records)
+
+ conn.commit()
+
+
+def create_and_load_iris(conn, iris_file: Path):
+ from sqlalchemy import insert
+
+ iris = iris_table_metadata()
+
+ with iris_file.open(newline=None, encoding="utf-8") as csvfile:
+ reader = csv.reader(csvfile)
+ header = next(reader)
+ params = [dict(zip(header, row)) for row in reader]
+ stmt = insert(iris).values(params)
+ with conn.begin() as con:
+ iris.drop(con, checkfirst=True)
+ iris.create(bind=con)
+ con.execute(stmt)
+
+
+def create_and_load_iris_view(conn):
+ stmt = "CREATE VIEW iris_view AS SELECT * FROM iris"
+ if isinstance(conn, sqlite3.Connection):
+ cur = conn.cursor()
+ cur.execute(stmt)
+ else:
+ adbc = import_optional_dependency("adbc_driver_manager.dbapi", errors="ignore")
+ if adbc and isinstance(conn, adbc.Connection):
+ with conn.cursor() as cur:
+ cur.execute(stmt)
+ conn.commit()
+ else:
+ from sqlalchemy import text
+
+ stmt = text(stmt)
+ with conn.begin() as con:
+ con.execute(stmt)
+
+
+def types_table_metadata(dialect: str):
+ from sqlalchemy import (
+ TEXT,
+ Boolean,
+ Column,
+ DateTime,
+ Float,
+ Integer,
+ MetaData,
+ Table,
+ )
+
+ date_type = TEXT if dialect == "sqlite" else DateTime
+ bool_type = Integer if dialect == "sqlite" else Boolean
+ metadata = MetaData()
+ types = Table(
+ "types",
+ metadata,
+ Column("TextCol", TEXT),
+ # error: Cannot infer type argument 1 of "Column"
+ Column("DateCol", date_type), # type: ignore[misc]
+ Column("IntDateCol", Integer),
+ Column("IntDateOnlyCol", Integer),
+ Column("FloatCol", Float),
+ Column("IntCol", Integer),
+ # error: Cannot infer type argument 1 of "Column"
+ Column("BoolCol", bool_type), # type: ignore[misc]
+ Column("IntColWithNull", Integer),
+ # error: Cannot infer type argument 1 of "Column"
+ Column("BoolColWithNull", bool_type), # type: ignore[misc]
+ )
+ return types
+
+
+def create_and_load_types_sqlite3(conn, types_data: list[dict]):
+ stmt = """CREATE TABLE types (
+ "TextCol" TEXT,
+ "DateCol" TEXT,
+ "IntDateCol" INTEGER,
+ "IntDateOnlyCol" INTEGER,
+ "FloatCol" REAL,
+ "IntCol" INTEGER,
+ "BoolCol" INTEGER,
+ "IntColWithNull" INTEGER,
+ "BoolColWithNull" INTEGER
+ )"""
+
+ ins_stmt = """
+ INSERT INTO types
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)
+ """
+
+ if isinstance(conn, sqlite3.Connection):
+ cur = conn.cursor()
+ cur.execute(stmt)
+ cur.executemany(ins_stmt, types_data)
+ else:
+ with conn.cursor() as cur:
+ cur.execute(stmt)
+ cur.executemany(ins_stmt, types_data)
+
+ conn.commit()
+
+
+def create_and_load_types_postgresql(conn, types_data: list[dict]):
+ with conn.cursor() as cur:
+ stmt = """CREATE TABLE types (
+ "TextCol" TEXT,
+ "DateCol" TIMESTAMP,
+ "IntDateCol" INTEGER,
+ "IntDateOnlyCol" INTEGER,
+ "FloatCol" DOUBLE PRECISION,
+ "IntCol" INTEGER,
+ "BoolCol" BOOLEAN,
+ "IntColWithNull" INTEGER,
+ "BoolColWithNull" BOOLEAN
+ )"""
+ cur.execute(stmt)
+
+ stmt = """
+ INSERT INTO types
+ VALUES($1, $2::timestamp, $3, $4, $5, $6, $7, $8, $9)
+ """
+
+ cur.executemany(stmt, types_data)
+
+ conn.commit()
+
+
+def create_and_load_types(conn, types_data: list[dict], dialect: str):
+ from sqlalchemy import insert
+ from sqlalchemy.engine import Engine
+
+ types = types_table_metadata(dialect)
+
+ stmt = insert(types).values(types_data)
+ if isinstance(conn, Engine):
+ with conn.connect() as conn:
+ with conn.begin():
+ types.drop(conn, checkfirst=True)
+ types.create(bind=conn)
+ conn.execute(stmt)
+ else:
+ with conn.begin():
+ types.drop(conn, checkfirst=True)
+ types.create(bind=conn)
+ conn.execute(stmt)
+
+
+def create_and_load_postgres_datetz(conn):
+ from sqlalchemy import (
+ Column,
+ DateTime,
+ MetaData,
+ Table,
+ insert,
+ )
+ from sqlalchemy.engine import Engine
+
+ metadata = MetaData()
+ datetz = Table("datetz", metadata, Column("DateColWithTz", DateTime(timezone=True)))
+ datetz_data = [
+ {
+ "DateColWithTz": "2000-01-01 00:00:00-08:00",
+ },
+ {
+ "DateColWithTz": "2000-06-01 00:00:00-07:00",
+ },
+ ]
+ stmt = insert(datetz).values(datetz_data)
+ if isinstance(conn, Engine):
+ with conn.connect() as conn:
+ with conn.begin():
+ datetz.drop(conn, checkfirst=True)
+ datetz.create(bind=conn)
+ conn.execute(stmt)
+ else:
+ with conn.begin():
+ datetz.drop(conn, checkfirst=True)
+ datetz.create(bind=conn)
+ conn.execute(stmt)
+
+ # "2000-01-01 00:00:00-08:00" should convert to
+ # "2000-01-01 08:00:00"
+ # "2000-06-01 00:00:00-07:00" should convert to
+ # "2000-06-01 07:00:00"
+ # GH 6415
+ expected_data = [
+ Timestamp("2000-01-01 08:00:00", tz="UTC"),
+ Timestamp("2000-06-01 07:00:00", tz="UTC"),
+ ]
+ return Series(expected_data, name="DateColWithTz").astype("M8[us, UTC]")
+
+
+def check_iris_frame(frame: DataFrame):
+ pytype = frame.dtypes.iloc[0].type
+ row = frame.iloc[0]
+ assert issubclass(pytype, np.floating)
+ tm.assert_series_equal(
+ row, Series([5.1, 3.5, 1.4, 0.2, "Iris-setosa"], index=frame.columns, name=0)
+ )
+ assert frame.shape in ((150, 5), (8, 5))
+
+
+def count_rows(conn, table_name: str):
+ stmt = f"SELECT count(*) AS count_1 FROM {table_name}"
+ adbc = import_optional_dependency("adbc_driver_manager.dbapi", errors="ignore")
+ if isinstance(conn, sqlite3.Connection):
+ cur = conn.cursor()
+ return cur.execute(stmt).fetchone()[0]
+ elif adbc and isinstance(conn, adbc.Connection):
+ with conn.cursor() as cur:
+ cur.execute(stmt)
+ return cur.fetchone()[0]
+ else:
+ from sqlalchemy import create_engine
+ from sqlalchemy.engine import Engine
+
+ if isinstance(conn, str):
+ try:
+ engine = create_engine(conn)
+ with engine.connect() as conn:
+ return conn.exec_driver_sql(stmt).scalar_one()
+ finally:
+ engine.dispose()
+ elif isinstance(conn, Engine):
+ with conn.connect() as conn:
+ return conn.exec_driver_sql(stmt).scalar_one()
+ else:
+ return conn.exec_driver_sql(stmt).scalar_one()
+
+
+@pytest.fixture
+def iris_path(datapath):
+ iris_path = datapath("io", "data", "csv", "iris.csv")
+ return Path(iris_path)
+
+
+@pytest.fixture
+def types_data():
+ return [
+ {
+ "TextCol": "first",
+ "DateCol": "2000-01-03 00:00:00",
+ "IntDateCol": 535852800,
+ "IntDateOnlyCol": 20101010,
+ "FloatCol": 10.10,
+ "IntCol": 1,
+ "BoolCol": False,
+ "IntColWithNull": 1,
+ "BoolColWithNull": False,
+ },
+ {
+ "TextCol": "first",
+ "DateCol": "2000-01-04 00:00:00",
+ "IntDateCol": 1356998400,
+ "IntDateOnlyCol": 20101212,
+ "FloatCol": 10.10,
+ "IntCol": 1,
+ "BoolCol": False,
+ "IntColWithNull": None,
+ "BoolColWithNull": None,
+ },
+ ]
+
+
+@pytest.fixture
+def types_data_frame(types_data):
+ dtypes = {
+ "TextCol": "str",
+ "DateCol": "str",
+ "IntDateCol": "int64",
+ "IntDateOnlyCol": "int64",
+ "FloatCol": "float",
+ "IntCol": "int64",
+ "BoolCol": "int64",
+ "IntColWithNull": "float",
+ "BoolColWithNull": "float",
+ }
+ df = DataFrame(types_data)
+ return df[dtypes.keys()].astype(dtypes)
+
+
+@pytest.fixture
+def test_frame1():
+ columns = ["index", "A", "B", "C", "D"]
+ data = [
+ (
+ "2000-01-03 00:00:00",
+ 0.980268513777,
+ 3.68573087906,
+ -0.364216805298,
+ -1.15973806169,
+ ),
+ (
+ "2000-01-04 00:00:00",
+ 1.04791624281,
+ -0.0412318367011,
+ -0.16181208307,
+ 0.212549316967,
+ ),
+ (
+ "2000-01-05 00:00:00",
+ 0.498580885705,
+ 0.731167677815,
+ -0.537677223318,
+ 1.34627041952,
+ ),
+ (
+ "2000-01-06 00:00:00",
+ 1.12020151869,
+ 1.56762092543,
+ 0.00364077397681,
+ 0.67525259227,
+ ),
+ ]
+ return DataFrame(data, columns=columns)
+
+
+@pytest.fixture
+def test_frame3():
+ columns = ["index", "A", "B"]
+ data = [
+ ("2000-01-03 00:00:00", 2**31 - 1, -1.987670),
+ ("2000-01-04 00:00:00", -29, -0.0412318367011),
+ ("2000-01-05 00:00:00", 20000, 0.731167677815),
+ ("2000-01-06 00:00:00", -290867, 1.56762092543),
+ ]
+ return DataFrame(data, columns=columns)
+
+
+def get_all_views(conn):
+ if isinstance(conn, sqlite3.Connection):
+ c = conn.execute("SELECT name FROM sqlite_master WHERE type='view'")
+ return [view[0] for view in c.fetchall()]
+ else:
+ adbc = import_optional_dependency("adbc_driver_manager.dbapi", errors="ignore")
+ if adbc and isinstance(conn, adbc.Connection):
+ results = []
+ info = conn.adbc_get_objects().read_all().to_pylist()
+ for catalog in info:
+ catalog["catalog_name"]
+ for schema in catalog["catalog_db_schemas"]:
+ schema["db_schema_name"]
+ for table in schema["db_schema_tables"]:
+ if table["table_type"] == "view":
+ view_name = table["table_name"]
+ results.append(view_name)
+
+ return results
+ else:
+ from sqlalchemy import inspect
+
+ return inspect(conn).get_view_names()
+
+
+def get_all_tables(conn):
+ if isinstance(conn, sqlite3.Connection):
+ c = conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
+ return [table[0] for table in c.fetchall()]
+ else:
+ adbc = import_optional_dependency("adbc_driver_manager.dbapi", errors="ignore")
+
+ if adbc and isinstance(conn, adbc.Connection):
+ results = []
+ info = conn.adbc_get_objects().read_all().to_pylist()
+ for catalog in info:
+ for schema in catalog["catalog_db_schemas"]:
+ for table in schema["db_schema_tables"]:
+ if table["table_type"] == "table":
+ table_name = table["table_name"]
+ results.append(table_name)
+
+ return results
+ else:
+ from sqlalchemy import inspect
+
+ return inspect(conn).get_table_names()
+
+
+def drop_table(
+ table_name: str,
+ conn: sqlite3.Connection | sqlalchemy.engine.Engine | sqlalchemy.engine.Connection,
+):
+ if isinstance(conn, sqlite3.Connection):
+ conn.execute(f"DROP TABLE IF EXISTS {sql._get_valid_sqlite_name(table_name)}")
+ conn.commit()
+
+ else:
+ adbc = import_optional_dependency("adbc_driver_manager.dbapi", errors="ignore")
+ if adbc and isinstance(conn, adbc.Connection):
+ with conn.cursor() as cur:
+ cur.execute(f'DROP TABLE IF EXISTS "{table_name}"')
+ else:
+ with conn.begin() as con:
+ with sql.SQLDatabase(con) as db:
+ db.drop_table(table_name)
+
+
+def drop_view(
+ view_name: str,
+ conn: sqlite3.Connection | sqlalchemy.engine.Engine | sqlalchemy.engine.Connection,
+):
+ import sqlalchemy
+
+ if isinstance(conn, sqlite3.Connection):
+ conn.execute(f"DROP VIEW IF EXISTS {sql._get_valid_sqlite_name(view_name)}")
+ conn.commit()
+ else:
+ adbc = import_optional_dependency("adbc_driver_manager.dbapi", errors="ignore")
+ if adbc and isinstance(conn, adbc.Connection):
+ with conn.cursor() as cur:
+ cur.execute(f'DROP VIEW IF EXISTS "{view_name}"')
+ else:
+ quoted_view = conn.engine.dialect.identifier_preparer.quote_identifier(
+ view_name
+ )
+ stmt = sqlalchemy.text(f"DROP VIEW IF EXISTS {quoted_view}")
+ with conn.begin() as con:
+ con.execute(stmt) # type: ignore[union-attr]
+
+
+@pytest.fixture
+def mysql_pymysql_engine():
+ sqlalchemy = pytest.importorskip("sqlalchemy")
+ pymysql = pytest.importorskip("pymysql")
+ engine = sqlalchemy.create_engine(
+ "mysql+pymysql://root@localhost:3306/pandas",
+ connect_args={"client_flag": pymysql.constants.CLIENT.MULTI_STATEMENTS},
+ poolclass=sqlalchemy.pool.NullPool,
+ )
+ yield engine
+ for view in get_all_views(engine):
+ drop_view(view, engine)
+ for tbl in get_all_tables(engine):
+ drop_table(tbl, engine)
+ engine.dispose()
+
+
+@pytest.fixture
+def mysql_pymysql_engine_iris(mysql_pymysql_engine, iris_path):
+ create_and_load_iris(mysql_pymysql_engine, iris_path)
+ create_and_load_iris_view(mysql_pymysql_engine)
+ return mysql_pymysql_engine
+
+
+@pytest.fixture
+def mysql_pymysql_engine_types(mysql_pymysql_engine, types_data):
+ create_and_load_types(mysql_pymysql_engine, types_data, "mysql")
+ return mysql_pymysql_engine
+
+
+@pytest.fixture
+def mysql_pymysql_conn(mysql_pymysql_engine):
+ with mysql_pymysql_engine.connect() as conn:
+ yield conn
+
+
+@pytest.fixture
+def mysql_pymysql_conn_iris(mysql_pymysql_engine_iris):
+ with mysql_pymysql_engine_iris.connect() as conn:
+ yield conn
+
+
+@pytest.fixture
+def mysql_pymysql_conn_types(mysql_pymysql_engine_types):
+ with mysql_pymysql_engine_types.connect() as conn:
+ yield conn
+
+
+@pytest.fixture
+def postgresql_psycopg2_engine():
+ sqlalchemy = pytest.importorskip("sqlalchemy")
+ pytest.importorskip("psycopg2")
+ engine = sqlalchemy.create_engine(
+ "postgresql+psycopg2://postgres:postgres@localhost:5432/pandas",
+ poolclass=sqlalchemy.pool.NullPool,
+ )
+ yield engine
+ for view in get_all_views(engine):
+ drop_view(view, engine)
+ for tbl in get_all_tables(engine):
+ drop_table(tbl, engine)
+ engine.dispose()
+
+
+@pytest.fixture
+def postgresql_psycopg2_engine_iris(postgresql_psycopg2_engine, iris_path):
+ create_and_load_iris(postgresql_psycopg2_engine, iris_path)
+ create_and_load_iris_view(postgresql_psycopg2_engine)
+ return postgresql_psycopg2_engine
+
+
+@pytest.fixture
+def postgresql_psycopg2_engine_types(postgresql_psycopg2_engine, types_data):
+ create_and_load_types(postgresql_psycopg2_engine, types_data, "postgres")
+ return postgresql_psycopg2_engine
+
+
+@pytest.fixture
+def postgresql_psycopg2_conn(postgresql_psycopg2_engine):
+ with postgresql_psycopg2_engine.connect() as conn:
+ yield conn
+
+
+@pytest.fixture
+def postgresql_adbc_conn():
+ pytest.importorskip("pyarrow")
+ pytest.importorskip("adbc_driver_postgresql")
+ from adbc_driver_postgresql import dbapi
+
+ uri = "postgresql://postgres:postgres@localhost:5432/pandas"
+ with dbapi.connect(uri) as conn:
+ yield conn
+ for view in get_all_views(conn):
+ drop_view(view, conn)
+ for tbl in get_all_tables(conn):
+ drop_table(tbl, conn)
+ conn.commit()
+
+
+@pytest.fixture
+def postgresql_adbc_iris(postgresql_adbc_conn, iris_path):
+ import adbc_driver_manager as mgr
+
+ conn = postgresql_adbc_conn
+
+ try:
+ conn.adbc_get_table_schema("iris")
+ except mgr.ProgrammingError:
+ conn.rollback()
+ create_and_load_iris_postgresql(conn, iris_path)
+ try:
+ conn.adbc_get_table_schema("iris_view")
+ except mgr.ProgrammingError: # note arrow-adbc issue 1022
+ conn.rollback()
+ create_and_load_iris_view(conn)
+ return conn
+
+
+@pytest.fixture
+def postgresql_adbc_types(postgresql_adbc_conn, types_data):
+ import adbc_driver_manager as mgr
+
+ conn = postgresql_adbc_conn
+
+ try:
+ conn.adbc_get_table_schema("types")
+ except mgr.ProgrammingError:
+ conn.rollback()
+ new_data = [tuple(entry.values()) for entry in types_data]
+
+ create_and_load_types_postgresql(conn, new_data)
+
+ return conn
+
+
+@pytest.fixture
+def postgresql_psycopg2_conn_iris(postgresql_psycopg2_engine_iris):
+ with postgresql_psycopg2_engine_iris.connect() as conn:
+ yield conn
+
+
+@pytest.fixture
+def postgresql_psycopg2_conn_types(postgresql_psycopg2_engine_types):
+ with postgresql_psycopg2_engine_types.connect() as conn:
+ yield conn
+
+
+@pytest.fixture
+def sqlite_str(temp_file):
+ pytest.importorskip("sqlalchemy")
+ return f"sqlite:///{temp_file}"
+
+
+@pytest.fixture
+def sqlite_engine(sqlite_str):
+ sqlalchemy = pytest.importorskip("sqlalchemy")
+ engine = sqlalchemy.create_engine(sqlite_str, poolclass=sqlalchemy.pool.NullPool)
+ yield engine
+ for view in get_all_views(engine):
+ drop_view(view, engine)
+ for tbl in get_all_tables(engine):
+ drop_table(tbl, engine)
+ engine.dispose()
+
+
+@pytest.fixture
+def sqlite_conn(sqlite_engine):
+ with sqlite_engine.connect() as conn:
+ yield conn
+
+
+@pytest.fixture
+def sqlite_str_iris(sqlite_str, iris_path):
+ sqlalchemy = pytest.importorskip("sqlalchemy")
+ engine = sqlalchemy.create_engine(sqlite_str)
+ create_and_load_iris(engine, iris_path)
+ create_and_load_iris_view(engine)
+ engine.dispose()
+ return sqlite_str
+
+
+@pytest.fixture
+def sqlite_engine_iris(sqlite_engine, iris_path):
+ create_and_load_iris(sqlite_engine, iris_path)
+ create_and_load_iris_view(sqlite_engine)
+ return sqlite_engine
+
+
+@pytest.fixture
+def sqlite_conn_iris(sqlite_engine_iris):
+ with sqlite_engine_iris.connect() as conn:
+ yield conn
+
+
+@pytest.fixture
+def sqlite_str_types(sqlite_str, types_data):
+ sqlalchemy = pytest.importorskip("sqlalchemy")
+ engine = sqlalchemy.create_engine(sqlite_str)
+ create_and_load_types(engine, types_data, "sqlite")
+ engine.dispose()
+ return sqlite_str
+
+
+@pytest.fixture
+def sqlite_engine_types(sqlite_engine, types_data):
+ create_and_load_types(sqlite_engine, types_data, "sqlite")
+ return sqlite_engine
+
+
+@pytest.fixture
+def sqlite_conn_types(sqlite_engine_types):
+ with sqlite_engine_types.connect() as conn:
+ yield conn
+
+
+@pytest.fixture
+def sqlite_adbc_conn(temp_file):
+ pytest.importorskip("pyarrow")
+ pytest.importorskip("adbc_driver_sqlite")
+ from adbc_driver_sqlite import dbapi
+
+ uri = f"file:{temp_file}"
+ with dbapi.connect(uri) as conn:
+ yield conn
+ for view in get_all_views(conn):
+ drop_view(view, conn)
+ for tbl in get_all_tables(conn):
+ drop_table(tbl, conn)
+ conn.commit()
+
+
+@pytest.fixture
+def sqlite_adbc_iris(sqlite_adbc_conn, iris_path):
+ import adbc_driver_manager as mgr
+
+ conn = sqlite_adbc_conn
+ try:
+ conn.adbc_get_table_schema("iris")
+ except mgr.ProgrammingError:
+ conn.rollback()
+ create_and_load_iris_sqlite3(conn, iris_path)
+ try:
+ conn.adbc_get_table_schema("iris_view")
+ except mgr.ProgrammingError:
+ conn.rollback()
+ create_and_load_iris_view(conn)
+ return conn
+
+
+@pytest.fixture
+def sqlite_adbc_types(sqlite_adbc_conn, types_data):
+ import adbc_driver_manager as mgr
+
+ conn = sqlite_adbc_conn
+ try:
+ conn.adbc_get_table_schema("types")
+ except mgr.ProgrammingError:
+ conn.rollback()
+ new_data = []
+ for entry in types_data:
+ entry["BoolCol"] = int(entry["BoolCol"])
+ if entry["BoolColWithNull"] is not None:
+ entry["BoolColWithNull"] = int(entry["BoolColWithNull"])
+ new_data.append(tuple(entry.values()))
+
+ create_and_load_types_sqlite3(conn, new_data)
+ conn.commit()
+
+ return conn
+
+
+@pytest.fixture
+def sqlite_buildin():
+ with contextlib.closing(sqlite3.connect(":memory:")) as closing_conn:
+ with closing_conn as conn:
+ yield conn
+
+
+@pytest.fixture
+def sqlite_buildin_iris(sqlite_buildin, iris_path):
+ create_and_load_iris_sqlite3(sqlite_buildin, iris_path)
+ create_and_load_iris_view(sqlite_buildin)
+ return sqlite_buildin
+
+
+@pytest.fixture
+def sqlite_buildin_types(sqlite_buildin, types_data):
+ types_data = [tuple(entry.values()) for entry in types_data]
+ create_and_load_types_sqlite3(sqlite_buildin, types_data)
+ return sqlite_buildin
+
+
+mysql_connectable = [
+ pytest.param("mysql_pymysql_engine", marks=pytest.mark.db),
+ pytest.param("mysql_pymysql_conn", marks=pytest.mark.db),
+]
+
+mysql_connectable_iris = [
+ pytest.param("mysql_pymysql_engine_iris", marks=pytest.mark.db),
+ pytest.param("mysql_pymysql_conn_iris", marks=pytest.mark.db),
+]
+
+mysql_connectable_types = [
+ pytest.param("mysql_pymysql_engine_types", marks=pytest.mark.db),
+ pytest.param("mysql_pymysql_conn_types", marks=pytest.mark.db),
+]
+
+postgresql_connectable = [
+ pytest.param("postgresql_psycopg2_engine", marks=pytest.mark.db),
+ pytest.param("postgresql_psycopg2_conn", marks=pytest.mark.db),
+]
+
+postgresql_connectable_iris = [
+ pytest.param("postgresql_psycopg2_engine_iris", marks=pytest.mark.db),
+ pytest.param("postgresql_psycopg2_conn_iris", marks=pytest.mark.db),
+]
+
+postgresql_connectable_types = [
+ pytest.param("postgresql_psycopg2_engine_types", marks=pytest.mark.db),
+ pytest.param("postgresql_psycopg2_conn_types", marks=pytest.mark.db),
+]
+
+sqlite_connectable = [
+ "sqlite_engine",
+ "sqlite_conn",
+ "sqlite_str",
+]
+
+sqlite_connectable_iris = [
+ "sqlite_engine_iris",
+ "sqlite_conn_iris",
+ "sqlite_str_iris",
+]
+
+sqlite_connectable_types = [
+ "sqlite_engine_types",
+ "sqlite_conn_types",
+ "sqlite_str_types",
+]
+
+sqlalchemy_connectable = mysql_connectable + postgresql_connectable + sqlite_connectable
+
+sqlalchemy_connectable_iris = (
+ mysql_connectable_iris + postgresql_connectable_iris + sqlite_connectable_iris
+)
+
+sqlalchemy_connectable_types = (
+ mysql_connectable_types + postgresql_connectable_types + sqlite_connectable_types
+)
+
+adbc_connectable = [
+ "sqlite_adbc_conn",
+ pytest.param("postgresql_adbc_conn", marks=pytest.mark.db),
+]
+
+adbc_connectable_iris = [
+ pytest.param("postgresql_adbc_iris", marks=pytest.mark.db),
+ "sqlite_adbc_iris",
+]
+
+adbc_connectable_types = [
+ pytest.param("postgresql_adbc_types", marks=pytest.mark.db),
+ "sqlite_adbc_types",
+]
+
+
+all_connectable = [*sqlalchemy_connectable, "sqlite_buildin", *adbc_connectable]
+
+all_connectable_iris = [
+ *sqlalchemy_connectable_iris,
+ "sqlite_buildin_iris",
+ *adbc_connectable_iris,
+]
+
+all_connectable_types = [
+ *sqlalchemy_connectable_types,
+ "sqlite_buildin_types",
+ *adbc_connectable_types,
+]
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_dataframe_to_sql(conn, test_frame1, request):
+ # GH 51086 if conn is sqlite_engine
+ conn = request.getfixturevalue(conn)
+ test_frame1.to_sql(name="test", con=conn, if_exists="append", index=False)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_dataframe_to_sql_empty(conn, test_frame1, request):
+ if conn == "postgresql_adbc_conn" and not using_string_dtype():
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="postgres ADBC driver < 1.2 cannot insert index with null type",
+ )
+ )
+
+ # GH 51086 if conn is sqlite_engine
+ conn = request.getfixturevalue(conn)
+ empty_df = test_frame1.iloc[:0]
+ empty_df.to_sql(name="test", con=conn, if_exists="append", index=False)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_dataframe_to_sql_arrow_dtypes(conn, request):
+ # GH 52046
+ pytest.importorskip("pyarrow")
+ df = DataFrame(
+ {
+ "int": pd.array([1], dtype="int8[pyarrow]"),
+ "datetime": pd.array(
+ [datetime(2023, 1, 1)], dtype="timestamp[ns][pyarrow]"
+ ),
+ "date": pd.array([date(2023, 1, 1)], dtype="date32[day][pyarrow]"),
+ "timedelta": pd.array([timedelta(1)], dtype="duration[ns][pyarrow]"),
+ "string": pd.array(["a"], dtype="string[pyarrow]"),
+ }
+ )
+
+ if "adbc" in conn:
+ if conn == "sqlite_adbc_conn":
+ df = df.drop(columns=["timedelta"])
+ if pa_version_under14p1:
+ exp_warning = DeprecationWarning
+ msg = "is_sparse is deprecated"
+ else:
+ exp_warning = None
+ msg = ""
+ else:
+ exp_warning = UserWarning
+ msg = "the 'timedelta'"
+
+ conn = request.getfixturevalue(conn)
+ with tm.assert_produces_warning(exp_warning, match=msg, check_stacklevel=False):
+ df.to_sql(name="test_arrow", con=conn, if_exists="replace", index=False)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_dataframe_to_sql_arrow_dtypes_missing(conn, request, nulls_fixture):
+ # GH 52046
+ pytest.importorskip("pyarrow")
+ if isinstance(nulls_fixture, Decimal):
+ pytest.skip(
+ # GH#61773
+ reason="Decimal('NaN') not supported in constructor for timestamp dtype"
+ )
+
+ df = DataFrame(
+ {
+ "datetime": pd.array(
+ [datetime(2023, 1, 1), nulls_fixture], dtype="timestamp[ns][pyarrow]"
+ ),
+ }
+ )
+ conn = request.getfixturevalue(conn)
+ df.to_sql(name="test_arrow", con=conn, if_exists="replace", index=False)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+@pytest.mark.parametrize("method", [None, "multi"])
+def test_to_sql(conn, method, test_frame1, request):
+ if method == "multi" and "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'method' not implemented for ADBC drivers", strict=True
+ )
+ )
+
+ conn = request.getfixturevalue(conn)
+ with pandasSQL_builder(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.to_sql(test_frame1, "test_frame", method=method)
+ assert pandasSQL.has_table("test_frame")
+ assert count_rows(conn, "test_frame") == len(test_frame1)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+@pytest.mark.parametrize(
+ "mode, num_row_coef", [("replace", 1), ("append", 2), ("delete_rows", 1)]
+)
+def test_to_sql_exist(conn, mode, num_row_coef, test_frame1, request):
+ conn = request.getfixturevalue(conn)
+ with pandasSQL_builder(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.to_sql(test_frame1, "test_frame", if_exists="fail")
+ pandasSQL.to_sql(test_frame1, "test_frame", if_exists=mode)
+ assert pandasSQL.has_table("test_frame")
+ assert count_rows(conn, "test_frame") == num_row_coef * len(test_frame1)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_to_sql_exist_fail(conn, test_frame1, request):
+ conn = request.getfixturevalue(conn)
+ with pandasSQL_builder(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.to_sql(test_frame1, "test_frame", if_exists="fail")
+ assert pandasSQL.has_table("test_frame")
+
+ msg = "Table 'test_frame' already exists"
+ with pytest.raises(ValueError, match=msg):
+ pandasSQL.to_sql(test_frame1, "test_frame", if_exists="fail")
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_read_iris_query(conn, request):
+ conn = request.getfixturevalue(conn)
+ iris_frame = read_sql_query("SELECT * FROM iris", conn)
+ check_iris_frame(iris_frame)
+ iris_frame = pd.read_sql("SELECT * FROM iris", conn)
+ check_iris_frame(iris_frame)
+ iris_frame = pd.read_sql("SELECT * FROM iris where 0=1", conn)
+ assert iris_frame.shape == (0, 5)
+ assert "SepalWidth" in iris_frame.columns
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_read_iris_query_chunksize(conn, request):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'chunksize' not implemented for ADBC drivers",
+ strict=True,
+ )
+ )
+ conn = request.getfixturevalue(conn)
+ iris_frame = concat(read_sql_query("SELECT * FROM iris", conn, chunksize=7))
+ check_iris_frame(iris_frame)
+ iris_frame = concat(pd.read_sql("SELECT * FROM iris", conn, chunksize=7))
+ check_iris_frame(iris_frame)
+ iris_frame = concat(pd.read_sql("SELECT * FROM iris where 0=1", conn, chunksize=7))
+ assert iris_frame.shape == (0, 5)
+ assert "SepalWidth" in iris_frame.columns
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_iris)
+def test_read_iris_query_expression_with_parameter(conn, request):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'chunksize' not implemented for ADBC drivers",
+ strict=True,
+ )
+ )
+ conn = request.getfixturevalue(conn)
+ from sqlalchemy import (
+ MetaData,
+ Table,
+ create_engine,
+ select,
+ )
+
+ metadata = MetaData()
+ autoload_con = create_engine(conn) if isinstance(conn, str) else conn
+ iris = Table("iris", metadata, autoload_with=autoload_con)
+ iris_frame = read_sql_query(
+ select(iris), conn, params={"name": "Iris-setosa", "length": 5.1}
+ )
+ check_iris_frame(iris_frame)
+ if isinstance(conn, str):
+ autoload_con.dispose()
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_read_iris_query_string_with_parameter(conn, request, sql_strings):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'chunksize' not implemented for ADBC drivers",
+ strict=True,
+ )
+ )
+
+ for db, query in sql_strings["read_parameters"].items():
+ if db in conn:
+ break
+ else:
+ raise KeyError(f"No part of {conn} found in sql_strings['read_parameters']")
+ conn = request.getfixturevalue(conn)
+ iris_frame = read_sql_query(query, conn, params=("Iris-setosa", 5.1))
+ check_iris_frame(iris_frame)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_iris)
+def test_read_iris_table(conn, request):
+ # GH 51015 if conn = sqlite_iris_str
+ conn = request.getfixturevalue(conn)
+ iris_frame = read_sql_table("iris", conn)
+ check_iris_frame(iris_frame)
+ iris_frame = pd.read_sql("iris", conn)
+ check_iris_frame(iris_frame)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_iris)
+def test_read_iris_table_chunksize(conn, request):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(reason="chunksize argument NotImplemented with ADBC")
+ )
+ conn = request.getfixturevalue(conn)
+ iris_frame = concat(read_sql_table("iris", conn, chunksize=7))
+ check_iris_frame(iris_frame)
+ iris_frame = concat(pd.read_sql("iris", conn, chunksize=7))
+ check_iris_frame(iris_frame)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_to_sql_callable(conn, test_frame1, request):
+ conn = request.getfixturevalue(conn)
+
+ check = [] # used to double check function below is really being used
+
+ def sample(pd_table, conn, keys, data_iter):
+ check.append(1)
+ data = [dict(zip(keys, row)) for row in data_iter]
+ conn.execute(pd_table.table.insert(), data)
+
+ with pandasSQL_builder(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.to_sql(test_frame1, "test_frame", method=sample)
+ assert pandasSQL.has_table("test_frame")
+ assert check == [1]
+ assert count_rows(conn, "test_frame") == len(test_frame1)
+
+
+@pytest.mark.parametrize("conn", all_connectable_types)
+def test_default_type_conversion(conn, request):
+ conn_name = conn
+ if conn_name == "sqlite_buildin_types":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="sqlite_buildin connection does not implement read_sql_table"
+ )
+ )
+
+ conn = request.getfixturevalue(conn)
+ df = sql.read_sql_table("types", conn)
+
+ assert issubclass(df.FloatCol.dtype.type, np.floating)
+ assert issubclass(df.IntCol.dtype.type, np.integer)
+
+ # MySQL/sqlite has no real BOOL type
+ if "postgresql" in conn_name:
+ assert issubclass(df.BoolCol.dtype.type, np.bool_)
+ else:
+ assert issubclass(df.BoolCol.dtype.type, np.integer)
+
+ # Int column with NA values stays as float
+ assert issubclass(df.IntColWithNull.dtype.type, np.floating)
+
+ # Bool column with NA = int column with NA values => becomes float
+ if "postgresql" in conn_name:
+ assert issubclass(df.BoolColWithNull.dtype.type, object)
+ else:
+ assert issubclass(df.BoolColWithNull.dtype.type, np.floating)
+
+
+@pytest.mark.parametrize("conn", mysql_connectable)
+def test_read_procedure(conn, request):
+ conn = request.getfixturevalue(conn)
+
+ # GH 7324
+ # Although it is more an api test, it is added to the
+ # mysql tests as sqlite does not have stored procedures
+ from sqlalchemy import text
+ from sqlalchemy.engine import Engine
+
+ df = DataFrame({"a": [1, 2, 3], "b": [0.1, 0.2, 0.3]})
+ df.to_sql(name="test_frame", con=conn, index=False)
+
+ proc = """DROP PROCEDURE IF EXISTS get_testdb;
+
+ CREATE PROCEDURE get_testdb ()
+
+ BEGIN
+ SELECT * FROM test_frame;
+ END"""
+ proc = text(proc)
+ if isinstance(conn, Engine):
+ with conn.connect() as engine_conn:
+ with engine_conn.begin():
+ engine_conn.execute(proc)
+ else:
+ with conn.begin():
+ conn.execute(proc)
+
+ res1 = sql.read_sql_query("CALL get_testdb();", conn)
+ tm.assert_frame_equal(df, res1)
+
+ # test delegation to read_sql_query
+ res2 = sql.read_sql("CALL get_testdb();", conn)
+ tm.assert_frame_equal(df, res2)
+
+
+@pytest.mark.parametrize("conn", postgresql_connectable)
+@pytest.mark.parametrize("expected_count", [2, "Success!"])
+def test_copy_from_callable_insertion_method(conn, expected_count, request):
+ # GH 8953
+ # Example in io.rst found under _io.sql.method
+ # not available in sqlite, mysql
+ def psql_insert_copy(table, conn, keys, data_iter):
+ # gets a DBAPI connection that can provide a cursor
+ dbapi_conn = conn.connection
+ with dbapi_conn.cursor() as cur:
+ s_buf = StringIO()
+ writer = csv.writer(s_buf)
+ writer.writerows(data_iter)
+ s_buf.seek(0)
+
+ columns = ", ".join([f'"{k}"' for k in keys])
+ if table.schema:
+ table_name = f"{table.schema}.{table.name}"
+ else:
+ table_name = table.name
+
+ sql_query = f"COPY {table_name} ({columns}) FROM STDIN WITH CSV"
+ cur.copy_expert(sql=sql_query, file=s_buf)
+ return expected_count
+
+ conn = request.getfixturevalue(conn)
+ expected = DataFrame({"col1": [1, 2], "col2": [0.1, 0.2], "col3": ["a", "n"]})
+ result_count = expected.to_sql(
+ name="test_frame", con=conn, index=False, method=psql_insert_copy
+ )
+ # GH 46891
+ if expected_count is None:
+ assert result_count is None
+ else:
+ assert result_count == expected_count
+ result = sql.read_sql_table("test_frame", conn)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", postgresql_connectable)
+def test_insertion_method_on_conflict_do_nothing(conn, request):
+ # GH 15988: Example in to_sql docstring
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy.dialects.postgresql import insert
+ from sqlalchemy.engine import Engine
+ from sqlalchemy.sql import text
+
+ def insert_on_conflict(table, conn, keys, data_iter):
+ data = [dict(zip(keys, row)) for row in data_iter]
+ stmt = (
+ insert(table.table)
+ .values(data)
+ .on_conflict_do_nothing(index_elements=["a"])
+ )
+ result = conn.execute(stmt)
+ return result.rowcount
+
+ create_sql = text(
+ """
+ CREATE TABLE test_insert_conflict (
+ a integer PRIMARY KEY,
+ b numeric,
+ c text
+ );
+ """
+ )
+ if isinstance(conn, Engine):
+ with conn.connect() as con:
+ with con.begin():
+ con.execute(create_sql)
+ else:
+ with conn.begin():
+ conn.execute(create_sql)
+
+ expected = DataFrame([[1, 2.1, "a"]], columns=list("abc"))
+ expected.to_sql(
+ name="test_insert_conflict", con=conn, if_exists="append", index=False
+ )
+
+ df_insert = DataFrame([[1, 3.2, "b"]], columns=list("abc"))
+ inserted = df_insert.to_sql(
+ name="test_insert_conflict",
+ con=conn,
+ index=False,
+ if_exists="append",
+ method=insert_on_conflict,
+ )
+ result = sql.read_sql_table("test_insert_conflict", conn)
+ tm.assert_frame_equal(result, expected)
+ assert inserted == 0
+
+ # Cleanup
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_insert_conflict")
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_to_sql_on_public_schema(conn, request):
+ if "sqlite" in conn or "mysql" in conn:
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="test for public schema only specific to postgresql"
+ )
+ )
+
+ conn = request.getfixturevalue(conn)
+
+ test_data = DataFrame([[1, 2.1, "a"], [2, 3.1, "b"]], columns=list("abc"))
+ test_data.to_sql(
+ name="test_public_schema",
+ con=conn,
+ if_exists="append",
+ index=False,
+ schema="public",
+ )
+
+ df_out = sql.read_sql_table("test_public_schema", conn, schema="public")
+ tm.assert_frame_equal(test_data, df_out)
+
+
+@pytest.mark.parametrize("conn", mysql_connectable)
+def test_insertion_method_on_conflict_update(conn, request):
+ # GH 14553: Example in to_sql docstring
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy.dialects.mysql import insert
+ from sqlalchemy.engine import Engine
+ from sqlalchemy.sql import text
+
+ def insert_on_conflict(table, conn, keys, data_iter):
+ data = [dict(zip(keys, row)) for row in data_iter]
+ stmt = insert(table.table).values(data)
+ stmt = stmt.on_duplicate_key_update(b=stmt.inserted.b, c=stmt.inserted.c)
+ result = conn.execute(stmt)
+ return result.rowcount
+
+ create_sql = text(
+ """
+ CREATE TABLE test_insert_conflict (
+ a INT PRIMARY KEY,
+ b FLOAT,
+ c VARCHAR(10)
+ );
+ """
+ )
+ if isinstance(conn, Engine):
+ with conn.connect() as con:
+ with con.begin():
+ con.execute(create_sql)
+ else:
+ with conn.begin():
+ conn.execute(create_sql)
+
+ df = DataFrame([[1, 2.1, "a"]], columns=list("abc"))
+ df.to_sql(name="test_insert_conflict", con=conn, if_exists="append", index=False)
+
+ expected = DataFrame([[1, 3.2, "b"]], columns=list("abc"))
+ inserted = expected.to_sql(
+ name="test_insert_conflict",
+ con=conn,
+ index=False,
+ if_exists="append",
+ method=insert_on_conflict,
+ )
+ result = sql.read_sql_table("test_insert_conflict", conn)
+ tm.assert_frame_equal(result, expected)
+ assert inserted == 2
+
+ # Cleanup
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_insert_conflict")
+
+
+@pytest.mark.parametrize("conn", postgresql_connectable)
+def test_read_view_postgres(conn, request):
+ # GH 52969
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy.engine import Engine
+ from sqlalchemy.sql import text
+
+ table_name = f"group_{uuid.uuid4().hex}"
+ view_name = f"group_view_{uuid.uuid4().hex}"
+
+ sql_stmt = text(
+ f"""
+ CREATE TABLE {table_name} (
+ group_id INTEGER,
+ name TEXT
+ );
+ INSERT INTO {table_name} VALUES
+ (1, 'name');
+ CREATE VIEW {view_name}
+ AS
+ SELECT * FROM {table_name};
+ """
+ )
+ if isinstance(conn, Engine):
+ with conn.connect() as con:
+ with con.begin():
+ con.execute(sql_stmt)
+ else:
+ with conn.begin():
+ conn.execute(sql_stmt)
+ result = read_sql_table(view_name, conn)
+ expected = DataFrame({"group_id": [1], "name": "name"})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_read_view_sqlite(sqlite_buildin):
+ # GH 52969
+ create_table = """
+CREATE TABLE groups (
+ group_id INTEGER,
+ name TEXT
+);
+"""
+ insert_into = """
+INSERT INTO groups VALUES
+ (1, 'name');
+"""
+ create_view = """
+CREATE VIEW group_view
+AS
+SELECT * FROM groups;
+"""
+ sqlite_buildin.execute(create_table)
+ sqlite_buildin.execute(insert_into)
+ sqlite_buildin.execute(create_view)
+ result = pd.read_sql("SELECT * FROM group_view", sqlite_buildin)
+ expected = DataFrame({"group_id": [1], "name": "name"})
+ tm.assert_frame_equal(result, expected)
+
+
+def flavor(conn_name):
+ if "postgresql" in conn_name:
+ return "postgresql"
+ elif "sqlite" in conn_name:
+ return "sqlite"
+ elif "mysql" in conn_name:
+ return "mysql"
+
+ raise ValueError(f"unsupported connection: {conn_name}")
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_read_sql_iris_parameter(conn, request, sql_strings):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'params' not implemented for ADBC drivers",
+ strict=True,
+ )
+ )
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ query = sql_strings["read_parameters"][flavor(conn_name)]
+ params = ("Iris-setosa", 5.1)
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ iris_frame = pandasSQL.read_query(query, params=params)
+ check_iris_frame(iris_frame)
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_read_sql_iris_named_parameter(conn, request, sql_strings):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'params' not implemented for ADBC drivers",
+ strict=True,
+ )
+ )
+
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ query = sql_strings["read_named_parameters"][flavor(conn_name)]
+ params = {"name": "Iris-setosa", "length": 5.1}
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ iris_frame = pandasSQL.read_query(query, params=params)
+ check_iris_frame(iris_frame)
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_read_sql_iris_no_parameter_with_percent(conn, request, sql_strings):
+ if "mysql" in conn or ("postgresql" in conn and "adbc" not in conn):
+ request.applymarker(pytest.mark.xfail(reason="broken test"))
+
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+
+ query = sql_strings["read_no_parameters_with_percent"][flavor(conn_name)]
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ iris_frame = pandasSQL.read_query(query, params=None)
+ check_iris_frame(iris_frame)
+
+
+# -----------------------------------------------------------------------------
+# -- Testing the public API
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_api_read_sql_view(conn, request):
+ conn = request.getfixturevalue(conn)
+ iris_frame = sql.read_sql_query("SELECT * FROM iris_view", conn)
+ check_iris_frame(iris_frame)
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_api_read_sql_with_chunksize_no_result(conn, request):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(reason="chunksize argument NotImplemented with ADBC")
+ )
+ conn = request.getfixturevalue(conn)
+ query = 'SELECT * FROM iris_view WHERE "SepalLength" < 0.0'
+ with_batch = sql.read_sql_query(query, conn, chunksize=5)
+ without_batch = sql.read_sql_query(query, conn)
+ tm.assert_frame_equal(concat(with_batch), without_batch)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_to_sql(conn, request, test_frame1):
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_frame1", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_frame1")
+
+ sql.to_sql(test_frame1, "test_frame1", conn)
+ assert sql.has_table("test_frame1", conn)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_to_sql_fail(conn, request, test_frame1):
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_frame2", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_frame2")
+
+ sql.to_sql(test_frame1, "test_frame2", conn, if_exists="fail")
+ assert sql.has_table("test_frame2", conn)
+
+ msg = "Table 'test_frame2' already exists"
+ with pytest.raises(ValueError, match=msg):
+ sql.to_sql(test_frame1, "test_frame2", conn, if_exists="fail")
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_to_sql_replace(conn, request, test_frame1):
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_frame3", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_frame3")
+
+ sql.to_sql(test_frame1, "test_frame3", conn, if_exists="fail")
+ # Add to table again
+ sql.to_sql(test_frame1, "test_frame3", conn, if_exists="replace")
+ assert sql.has_table("test_frame3", conn)
+
+ num_entries = len(test_frame1)
+ num_rows = count_rows(conn, "test_frame3")
+
+ assert num_rows == num_entries
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_to_sql_append(conn, request, test_frame1):
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_frame4", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_frame4")
+
+ assert sql.to_sql(test_frame1, "test_frame4", conn, if_exists="fail") == 4
+
+ # Add to table again
+ assert sql.to_sql(test_frame1, "test_frame4", conn, if_exists="append") == 4
+ assert sql.has_table("test_frame4", conn)
+
+ num_entries = 2 * len(test_frame1)
+ num_rows = count_rows(conn, "test_frame4")
+
+ assert num_rows == num_entries
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_to_sql_type_mapping(conn, request, test_frame3):
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_frame5", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_frame5")
+
+ sql.to_sql(test_frame3, "test_frame5", conn, index=False)
+ result = sql.read_sql("SELECT * FROM test_frame5", conn)
+
+ tm.assert_frame_equal(test_frame3, result)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_to_sql_series(conn, request):
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_series", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_series")
+
+ s = Series(np.arange(5, dtype="int64"), name="series")
+ sql.to_sql(s, "test_series", conn, index=False)
+ s2 = sql.read_sql_query("SELECT * FROM test_series", conn)
+ tm.assert_frame_equal(s.to_frame(), s2)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_roundtrip(conn, request, test_frame1):
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_frame_roundtrip", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_frame_roundtrip")
+
+ sql.to_sql(test_frame1, "test_frame_roundtrip", con=conn)
+ result = sql.read_sql_query("SELECT * FROM test_frame_roundtrip", con=conn)
+
+ # HACK!
+ if "adbc" in conn_name:
+ result = result.drop(columns="__index_level_0__")
+ else:
+ result = result.drop(columns="level_0")
+ tm.assert_frame_equal(result, test_frame1)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_roundtrip_chunksize(conn, request, test_frame1):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(reason="chunksize argument NotImplemented with ADBC")
+ )
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_frame_roundtrip", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_frame_roundtrip")
+
+ sql.to_sql(
+ test_frame1,
+ "test_frame_roundtrip",
+ con=conn,
+ index=False,
+ chunksize=2,
+ )
+ result = sql.read_sql_query("SELECT * FROM test_frame_roundtrip", con=conn)
+ tm.assert_frame_equal(result, test_frame1)
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_api_execute_sql(conn, request):
+ # drop_sql = "DROP TABLE IF EXISTS test" # should already be done
+ conn = request.getfixturevalue(conn)
+ with sql.pandasSQL_builder(conn) as pandas_sql:
+ iris_results = pandas_sql.execute("SELECT * FROM iris")
+ row = iris_results.fetchone()
+ iris_results.close()
+ assert list(row) == [5.1, 3.5, 1.4, 0.2, "Iris-setosa"]
+
+
+@pytest.mark.parametrize("conn", all_connectable_types)
+def test_api_date_parsing(conn, request):
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ # Test date parsing in read_sql
+ # No Parsing
+ df = sql.read_sql_query("SELECT * FROM types", conn)
+ if not ("mysql" in conn_name or "postgres" in conn_name):
+ assert not issubclass(df.DateCol.dtype.type, np.datetime64)
+
+ df = sql.read_sql_query("SELECT * FROM types", conn, parse_dates=["DateCol"])
+ assert issubclass(df.DateCol.dtype.type, np.datetime64)
+ assert df.DateCol.tolist() == [
+ Timestamp(2000, 1, 3, 0, 0, 0),
+ Timestamp(2000, 1, 4, 0, 0, 0),
+ ]
+
+ df = sql.read_sql_query(
+ "SELECT * FROM types",
+ conn,
+ parse_dates={"DateCol": "%Y-%m-%d %H:%M:%S"},
+ )
+ assert issubclass(df.DateCol.dtype.type, np.datetime64)
+ assert df.DateCol.tolist() == [
+ Timestamp(2000, 1, 3, 0, 0, 0),
+ Timestamp(2000, 1, 4, 0, 0, 0),
+ ]
+
+ df = sql.read_sql_query("SELECT * FROM types", conn, parse_dates=["IntDateCol"])
+ assert issubclass(df.IntDateCol.dtype.type, np.datetime64)
+ assert df.IntDateCol.tolist() == [
+ Timestamp(1986, 12, 25, 0, 0, 0),
+ Timestamp(2013, 1, 1, 0, 0, 0),
+ ]
+
+ df = sql.read_sql_query(
+ "SELECT * FROM types", conn, parse_dates={"IntDateCol": "s"}
+ )
+ assert issubclass(df.IntDateCol.dtype.type, np.datetime64)
+ assert df.IntDateCol.tolist() == [
+ Timestamp(1986, 12, 25, 0, 0, 0),
+ Timestamp(2013, 1, 1, 0, 0, 0),
+ ]
+
+ df = sql.read_sql_query(
+ "SELECT * FROM types",
+ conn,
+ parse_dates={"IntDateOnlyCol": "%Y%m%d"},
+ )
+ assert issubclass(df.IntDateOnlyCol.dtype.type, np.datetime64)
+ assert df.IntDateOnlyCol.tolist() == [
+ Timestamp("2010-10-10"),
+ Timestamp("2010-12-12"),
+ ]
+
+
+@pytest.mark.parametrize("conn", all_connectable_types)
+@pytest.mark.parametrize("error", ["raise", "coerce"])
+@pytest.mark.parametrize(
+ "read_sql, text, mode",
+ [
+ (sql.read_sql, "SELECT * FROM types", ("sqlalchemy", "fallback")),
+ (sql.read_sql, "types", ("sqlalchemy")),
+ (
+ sql.read_sql_query,
+ "SELECT * FROM types",
+ ("sqlalchemy", "fallback"),
+ ),
+ (sql.read_sql_table, "types", ("sqlalchemy")),
+ ],
+)
+def test_api_custom_dateparsing_error(
+ conn, request, read_sql, text, mode, error, types_data_frame
+):
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ if text == "types" and conn_name == "sqlite_buildin_types":
+ request.applymarker(
+ pytest.mark.xfail(reason="failing combination of arguments")
+ )
+
+ expected = types_data_frame.astype({"DateCol": "datetime64[us]"})
+
+ result = read_sql(
+ text,
+ con=conn,
+ parse_dates={
+ "DateCol": {"errors": error},
+ },
+ )
+ if "postgres" in conn_name:
+ # TODO: clean up types_data_frame fixture
+ result["BoolCol"] = result["BoolCol"].astype(int)
+ result["BoolColWithNull"] = result["BoolColWithNull"].astype(float)
+
+ if conn_name == "postgresql_adbc_types":
+ expected = expected.astype(
+ {
+ "IntDateCol": "int32",
+ "IntDateOnlyCol": "int32",
+ "IntCol": "int32",
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", all_connectable_types)
+def test_api_date_and_index(conn, request):
+ # Test case where same column appears in parse_date and index_col
+ conn = request.getfixturevalue(conn)
+ df = sql.read_sql_query(
+ "SELECT * FROM types",
+ conn,
+ index_col="DateCol",
+ parse_dates=["DateCol", "IntDateCol"],
+ )
+
+ assert issubclass(df.index.dtype.type, np.datetime64)
+ assert issubclass(df.IntDateCol.dtype.type, np.datetime64)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_timedelta(conn, request):
+ # see #6921
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_timedelta", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_timedelta")
+
+ df = to_timedelta(Series(["00:00:01", "00:00:03"], name="foo")).to_frame()
+
+ if conn_name == "sqlite_adbc_conn":
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="sqlite ADBC driver doesn't implement timedelta",
+ )
+ )
+
+ if "adbc" in conn_name:
+ if pa_version_under14p1:
+ exp_warning = DeprecationWarning
+ else:
+ exp_warning = None
+ else:
+ exp_warning = UserWarning
+
+ with tm.assert_produces_warning(exp_warning, check_stacklevel=False):
+ result_count = df.to_sql(name="test_timedelta", con=conn)
+ assert result_count == 2
+ result = sql.read_sql_query("SELECT * FROM test_timedelta", conn)
+
+ if conn_name == "postgresql_adbc_conn":
+ # TODO: Postgres stores an INTERVAL, which ADBC reads as a Month-Day-Nano
+ # Interval; the default pandas type mapper maps this to a DateOffset
+ # but maybe we should try and restore the timedelta here?
+ expected = Series(
+ [
+ pd.DateOffset(months=0, days=0, microseconds=1000000, nanoseconds=0),
+ pd.DateOffset(months=0, days=0, microseconds=3000000, nanoseconds=0),
+ ],
+ name="foo",
+ )
+ else:
+ expected = df["foo"].astype("int64")
+ tm.assert_series_equal(result["foo"], expected)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_complex_raises(conn, request):
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ df = DataFrame({"a": [1 + 1j, 2j]})
+
+ if "adbc" in conn_name:
+ msg = "datatypes not supported"
+ else:
+ msg = "Complex datatypes not supported"
+ with pytest.raises(ValueError, match=msg):
+ assert df.to_sql("test_complex", con=conn) is None
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+@pytest.mark.parametrize(
+ "index_name,index_label,expected",
+ [
+ # no index name, defaults to 'index'
+ (None, None, "index"),
+ # specifying index_label
+ (None, "other_label", "other_label"),
+ # using the index name
+ ("index_name", None, "index_name"),
+ # has index name, but specifying index_label
+ ("index_name", "other_label", "other_label"),
+ # index name is integer
+ (0, None, "0"),
+ # index name is None but index label is integer
+ (None, 0, "0"),
+ ],
+)
+def test_api_to_sql_index_label(conn, request, index_name, index_label, expected):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(reason="index_label argument NotImplemented with ADBC")
+ )
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_index_label", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_index_label")
+
+ temp_frame = DataFrame({"col1": range(4)})
+ temp_frame.index.name = index_name
+ query = "SELECT * FROM test_index_label"
+ sql.to_sql(temp_frame, "test_index_label", conn, index_label=index_label)
+ frame = sql.read_sql_query(query, conn)
+ assert frame.columns[0] == expected
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_to_sql_index_label_multiindex(conn, request):
+ conn_name = conn
+ if "mysql" in conn_name:
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="MySQL can fail using TEXT without length as key", strict=False
+ )
+ )
+ elif "adbc" in conn_name:
+ request.node.add_marker(
+ pytest.mark.xfail(reason="index_label argument NotImplemented with ADBC")
+ )
+
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_index_label", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_index_label")
+
+ expected_row_count = 4
+ temp_frame = DataFrame(
+ {"col1": range(4)},
+ index=MultiIndex.from_product([("A0", "A1"), ("B0", "B1")]),
+ )
+
+ # no index name, defaults to 'level_0' and 'level_1'
+ result = sql.to_sql(temp_frame, "test_index_label", conn)
+ assert result == expected_row_count
+ frame = sql.read_sql_query("SELECT * FROM test_index_label", conn)
+ assert frame.columns[0] == "level_0"
+ assert frame.columns[1] == "level_1"
+
+ # specifying index_label
+ result = sql.to_sql(
+ temp_frame,
+ "test_index_label",
+ conn,
+ if_exists="replace",
+ index_label=["A", "B"],
+ )
+ assert result == expected_row_count
+ frame = sql.read_sql_query("SELECT * FROM test_index_label", conn)
+ assert frame.columns[:2].tolist() == ["A", "B"]
+
+ # using the index name
+ temp_frame.index.names = ["A", "B"]
+ result = sql.to_sql(temp_frame, "test_index_label", conn, if_exists="replace")
+ assert result == expected_row_count
+ frame = sql.read_sql_query("SELECT * FROM test_index_label", conn)
+ assert frame.columns[:2].tolist() == ["A", "B"]
+
+ # has index name, but specifying index_label
+ result = sql.to_sql(
+ temp_frame,
+ "test_index_label",
+ conn,
+ if_exists="replace",
+ index_label=["C", "D"],
+ )
+ assert result == expected_row_count
+ frame = sql.read_sql_query("SELECT * FROM test_index_label", conn)
+ assert frame.columns[:2].tolist() == ["C", "D"]
+
+ msg = "Length of 'index_label' should match number of levels, which is 2"
+ with pytest.raises(ValueError, match=msg):
+ sql.to_sql(
+ temp_frame,
+ "test_index_label",
+ conn,
+ if_exists="replace",
+ index_label="C",
+ )
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_multiindex_roundtrip(conn, request):
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_multiindex_roundtrip", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_multiindex_roundtrip")
+
+ df = DataFrame.from_records(
+ [(1, 2.1, "line1"), (2, 1.5, "line2")],
+ columns=["A", "B", "C"],
+ index=["A", "B"],
+ )
+
+ df.to_sql(name="test_multiindex_roundtrip", con=conn)
+ result = sql.read_sql_query(
+ "SELECT * FROM test_multiindex_roundtrip", conn, index_col=["A", "B"]
+ )
+ tm.assert_frame_equal(df, result, check_index_type=True)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ None,
+ int,
+ float,
+ {"A": int, "B": float},
+ ],
+)
+def test_api_dtype_argument(conn, request, dtype):
+ # GH10285 Add dtype argument to read_sql_query
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_dtype_argument", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_dtype_argument")
+
+ df = DataFrame([[1.2, 3.4], [5.6, 7.8]], columns=["A", "B"])
+ assert df.to_sql(name="test_dtype_argument", con=conn) == 2
+
+ expected = df.astype(dtype)
+
+ if "postgres" in conn_name:
+ query = 'SELECT "A", "B" FROM test_dtype_argument'
+ else:
+ query = "SELECT A, B FROM test_dtype_argument"
+ result = sql.read_sql_query(query, con=conn, dtype=dtype)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_integer_col_names(conn, request):
+ conn = request.getfixturevalue(conn)
+ df = DataFrame([[1, 2], [3, 4]], columns=[0, 1])
+ sql.to_sql(df, "test_frame_integer_col_names", conn, if_exists="replace")
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_get_schema(conn, request, test_frame1):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'get_schema' not implemented for ADBC drivers",
+ strict=True,
+ )
+ )
+ conn = request.getfixturevalue(conn)
+ create_sql = sql.get_schema(test_frame1, "test", con=conn)
+ assert "CREATE" in create_sql
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_get_schema_with_schema(conn, request, test_frame1):
+ # GH28486
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'get_schema' not implemented for ADBC drivers",
+ strict=True,
+ )
+ )
+ conn = request.getfixturevalue(conn)
+ create_sql = sql.get_schema(test_frame1, "test", con=conn, schema="pypi")
+ assert "CREATE TABLE pypi." in create_sql
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_get_schema_dtypes(conn, request):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'get_schema' not implemented for ADBC drivers",
+ strict=True,
+ )
+ )
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ float_frame = DataFrame({"a": [1.1, 1.2], "b": [2.1, 2.2]})
+
+ if conn_name == "sqlite_buildin":
+ dtype = "INTEGER"
+ else:
+ from sqlalchemy import Integer
+
+ dtype = Integer
+ create_sql = sql.get_schema(float_frame, "test", con=conn, dtype={"b": dtype})
+ assert "CREATE" in create_sql
+ assert "INTEGER" in create_sql
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_get_schema_keys(conn, request, test_frame1):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="'get_schema' not implemented for ADBC drivers",
+ strict=True,
+ )
+ )
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ frame = DataFrame({"Col1": [1.1, 1.2], "Col2": [2.1, 2.2]})
+ create_sql = sql.get_schema(frame, "test", con=conn, keys="Col1")
+
+ if "mysql" in conn_name:
+ constraint_sentence = "CONSTRAINT test_pk PRIMARY KEY (`Col1`)"
+ else:
+ constraint_sentence = 'CONSTRAINT test_pk PRIMARY KEY ("Col1")'
+ assert constraint_sentence in create_sql
+
+ # multiple columns as key (GH10385)
+ create_sql = sql.get_schema(test_frame1, "test", con=conn, keys=["A", "B"])
+ if "mysql" in conn_name:
+ constraint_sentence = "CONSTRAINT test_pk PRIMARY KEY (`A`, `B`)"
+ else:
+ constraint_sentence = 'CONSTRAINT test_pk PRIMARY KEY ("A", "B")'
+ assert constraint_sentence in create_sql
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_chunksize_read(conn, request):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(reason="chunksize argument NotImplemented with ADBC")
+ )
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_chunksize", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_chunksize")
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((22, 5)), columns=list("abcde")
+ )
+ df.to_sql(name="test_chunksize", con=conn, index=False)
+
+ # reading the query in one time
+ res1 = sql.read_sql_query("select * from test_chunksize", conn)
+
+ # reading the query in chunks with read_sql_query
+ res2 = DataFrame()
+ i = 0
+ sizes = [5, 5, 5, 5, 2]
+
+ for chunk in sql.read_sql_query("select * from test_chunksize", conn, chunksize=5):
+ res2 = concat([res2, chunk], ignore_index=True)
+ assert len(chunk) == sizes[i]
+ i += 1
+
+ tm.assert_frame_equal(res1, res2)
+
+ # reading the query in chunks with read_sql_query
+ if conn_name == "sqlite_buildin":
+ with pytest.raises(NotImplementedError, match="^$"):
+ sql.read_sql_table("test_chunksize", conn, chunksize=5)
+ else:
+ res3 = DataFrame()
+ i = 0
+ sizes = [5, 5, 5, 5, 2]
+
+ for chunk in sql.read_sql_table("test_chunksize", conn, chunksize=5):
+ res3 = concat([res3, chunk], ignore_index=True)
+ assert len(chunk) == sizes[i]
+ i += 1
+
+ tm.assert_frame_equal(res1, res3)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_categorical(conn, request):
+ if conn == "postgresql_adbc_conn":
+ adbc = import_optional_dependency("adbc_driver_postgresql", errors="ignore")
+ if adbc is not None and Version(adbc.__version__) < Version("0.9.0"):
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="categorical dtype not implemented for ADBC postgres driver",
+ strict=True,
+ )
+ )
+ # GH8624
+ # test that categorical gets written correctly as dense column
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_categorical", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_categorical")
+
+ df = DataFrame(
+ {
+ "person_id": [1, 2, 3],
+ "person_name": ["John P. Doe", "Jane Dove", "John P. Doe"],
+ }
+ )
+ df2 = df.copy()
+ df2["person_name"] = df2["person_name"].astype("category")
+
+ df2.to_sql(name="test_categorical", con=conn, index=False)
+ res = sql.read_sql_query("SELECT * FROM test_categorical", conn)
+
+ tm.assert_frame_equal(res, df)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_unicode_column_name(conn, request):
+ # GH 11431
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_unicode", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_unicode")
+
+ df = DataFrame([[1, 2], [3, 4]], columns=["\xe9", "b"])
+ df.to_sql(name="test_unicode", con=conn, index=False)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_escaped_table_name(conn, request):
+ # GH 13206
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("d1187b08-4943-4c8d-a7f6", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("d1187b08-4943-4c8d-a7f6")
+
+ df = DataFrame({"A": [0, 1, 2], "B": [0.2, np.nan, 5.6]})
+ df.to_sql(name="d1187b08-4943-4c8d-a7f6", con=conn, index=False)
+
+ if "postgres" in conn_name:
+ query = 'SELECT * FROM "d1187b08-4943-4c8d-a7f6"'
+ else:
+ query = "SELECT * FROM `d1187b08-4943-4c8d-a7f6`"
+ res = sql.read_sql_query(query, conn)
+
+ tm.assert_frame_equal(res, df)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_api_read_sql_duplicate_columns(conn, request):
+ # GH#53117
+ if "adbc" in conn:
+ pa = pytest.importorskip("pyarrow")
+ if not (
+ Version(pa.__version__) >= Version("16.0")
+ and conn in ["sqlite_adbc_conn", "postgresql_adbc_conn"]
+ ):
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="pyarrow->pandas throws ValueError", strict=True
+ )
+ )
+ conn = request.getfixturevalue(conn)
+ if sql.has_table("test_table", conn):
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("test_table")
+
+ df = DataFrame({"a": [1, 2, 3], "b": [0.1, 0.2, 0.3], "c": 1})
+ df.to_sql(name="test_table", con=conn, index=False)
+
+ result = pd.read_sql("SELECT a, b, a +1 as a, c FROM test_table", conn)
+ expected = DataFrame(
+ [[1, 0.1, 2, 1], [2, 0.2, 3, 1], [3, 0.3, 4, 1]],
+ columns=["a", "b", "a", "c"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_read_table_columns(conn, request, test_frame1):
+ # test columns argument in read_table
+ conn_name = conn
+ if conn_name == "sqlite_buildin":
+ request.applymarker(pytest.mark.xfail(reason="Not Implemented"))
+
+ conn = request.getfixturevalue(conn)
+ sql.to_sql(test_frame1, "test_frame", conn)
+
+ cols = ["A", "B"]
+
+ result = sql.read_sql_table("test_frame", conn, columns=cols)
+ assert result.columns.tolist() == cols
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_read_table_index_col(conn, request, test_frame1):
+ # test columns argument in read_table
+ conn_name = conn
+ if conn_name == "sqlite_buildin":
+ request.applymarker(pytest.mark.xfail(reason="Not Implemented"))
+
+ conn = request.getfixturevalue(conn)
+ sql.to_sql(test_frame1, "test_frame", conn)
+
+ result = sql.read_sql_table("test_frame", conn, index_col="index")
+ assert result.index.names == ["index"]
+
+ result = sql.read_sql_table("test_frame", conn, index_col=["A", "B"])
+ assert result.index.names == ["A", "B"]
+
+ result = sql.read_sql_table(
+ "test_frame", conn, index_col=["A", "B"], columns=["C", "D"]
+ )
+ assert result.index.names == ["A", "B"]
+ assert result.columns.tolist() == ["C", "D"]
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_read_sql_delegate(conn, request):
+ if conn == "sqlite_buildin_iris":
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="sqlite_buildin connection does not implement read_sql_table"
+ )
+ )
+
+ conn = request.getfixturevalue(conn)
+ iris_frame1 = sql.read_sql_query("SELECT * FROM iris", conn)
+ iris_frame2 = sql.read_sql("SELECT * FROM iris", conn)
+ tm.assert_frame_equal(iris_frame1, iris_frame2)
+
+ iris_frame1 = sql.read_sql_table("iris", conn)
+ iris_frame2 = sql.read_sql("iris", conn)
+ tm.assert_frame_equal(iris_frame1, iris_frame2)
+
+
+def test_not_reflect_all_tables(sqlite_conn):
+ conn = sqlite_conn
+ from sqlalchemy import text
+ from sqlalchemy.engine import Engine
+
+ # create invalid table
+ query_list = [
+ text("CREATE TABLE invalid (x INTEGER, y UNKNOWN);"),
+ text("CREATE TABLE other_table (x INTEGER, y INTEGER);"),
+ ]
+
+ for query in query_list:
+ if isinstance(conn, Engine):
+ with conn.connect() as conn:
+ with conn.begin():
+ conn.execute(query)
+ else:
+ with conn.begin():
+ conn.execute(query)
+
+ with tm.assert_produces_warning(None):
+ sql.read_sql_table("other_table", conn)
+ sql.read_sql_query("SELECT * FROM other_table", conn)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_warning_case_insensitive_table_name(conn, request, test_frame1):
+ conn_name = conn
+ if conn_name == "sqlite_buildin" or "adbc" in conn_name:
+ request.applymarker(pytest.mark.xfail(reason="Does not raise warning"))
+
+ conn = request.getfixturevalue(conn)
+ # see gh-7815
+ with tm.assert_produces_warning(
+ UserWarning,
+ match=(
+ r"The provided table name 'TABLE1' is not found exactly as such in "
+ r"the database after writing the table, possibly due to case "
+ r"sensitivity issues. Consider using lower case table names."
+ ),
+ ):
+ with sql.SQLDatabase(conn) as db:
+ db.check_case_sensitive("TABLE1", "")
+
+ # Test that the warning is certainly NOT triggered in a normal case.
+ with tm.assert_produces_warning(None):
+ test_frame1.to_sql(name="CaseSensitive", con=conn)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_sqlalchemy_type_mapping(conn, request):
+ conn = request.getfixturevalue(conn)
+ from sqlalchemy import TIMESTAMP
+
+ # Test Timestamp objects (no datetime64 because of timezone) (GH9085)
+ df = DataFrame(
+ {"time": to_datetime(["2014-12-12 01:54", "2014-12-11 02:54"], utc=True)}
+ )
+ with sql.SQLDatabase(conn) as db:
+ table = sql.SQLTable("test_type", db, frame=df)
+ # GH 9086: TIMESTAMP is the suggested type for datetimes with timezones
+ assert isinstance(table.table.c["time"].type, TIMESTAMP)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+@pytest.mark.parametrize(
+ "integer, expected",
+ [
+ ("int8", "SMALLINT"),
+ ("Int8", "SMALLINT"),
+ ("uint8", "SMALLINT"),
+ ("UInt8", "SMALLINT"),
+ ("int16", "SMALLINT"),
+ ("Int16", "SMALLINT"),
+ ("uint16", "INTEGER"),
+ ("UInt16", "INTEGER"),
+ ("int32", "INTEGER"),
+ ("Int32", "INTEGER"),
+ ("uint32", "BIGINT"),
+ ("UInt32", "BIGINT"),
+ ("int64", "BIGINT"),
+ ("Int64", "BIGINT"),
+ (int, "BIGINT" if np.dtype(int).name == "int64" else "INTEGER"),
+ ],
+)
+def test_sqlalchemy_integer_mapping(conn, request, integer, expected):
+ # GH35076 Map pandas integer to optimal SQLAlchemy integer type
+ conn = request.getfixturevalue(conn)
+ df = DataFrame([0, 1], columns=["a"], dtype=integer)
+ with sql.SQLDatabase(conn) as db:
+ table = sql.SQLTable("test_type", db, frame=df)
+
+ result = str(table.table.c.a.type)
+ assert result == expected
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+@pytest.mark.parametrize("integer", ["uint64", "UInt64"])
+def test_sqlalchemy_integer_overload_mapping(conn, request, integer):
+ conn = request.getfixturevalue(conn)
+ # GH35076 Map pandas integer to optimal SQLAlchemy integer type
+ df = DataFrame([0, 1], columns=["a"], dtype=integer)
+ with sql.SQLDatabase(conn) as db:
+ with pytest.raises(
+ ValueError, match="Unsigned 64 bit integer datatype is not supported"
+ ):
+ sql.SQLTable("test_type", db, frame=df)
+
+
+def test_database_uri_string(temp_file, request, test_frame1):
+ pytest.importorskip("sqlalchemy")
+ # Test read_sql and .to_sql method with a database URI (GH10654)
+ # db_uri = 'sqlite:///:memory:' # raises
+ # sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near
+ # "iris": syntax error [SQL: 'iris']
+ name = str(temp_file)
+ db_uri = "sqlite:///" + name
+ table = "iris"
+ test_frame1.to_sql(name=table, con=db_uri, if_exists="replace", index=False)
+ test_frame2 = sql.read_sql(table, db_uri)
+ test_frame3 = sql.read_sql_table(table, db_uri)
+ query = "SELECT * FROM iris"
+ test_frame4 = sql.read_sql_query(query, db_uri)
+ tm.assert_frame_equal(test_frame1, test_frame2)
+ tm.assert_frame_equal(test_frame1, test_frame3)
+ tm.assert_frame_equal(test_frame1, test_frame4)
+
+
+@td.skip_if_installed("pg8000")
+def test_pg8000_sqlalchemy_passthrough_error(request):
+ pytest.importorskip("sqlalchemy")
+ # using driver that will not be installed on CI to trigger error
+ # in sqlalchemy.create_engine -> test passing of this error to user
+ db_uri = "postgresql+pg8000://user:pass@host/dbname"
+ with pytest.raises(ImportError, match="pg8000"):
+ sql.read_sql("select * from table", db_uri)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_iris)
+def test_query_by_text_obj(conn, request):
+ # WIP : GH10846
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ from sqlalchemy import text
+
+ if "postgres" in conn_name:
+ name_text = text('select * from iris where "Name"=:name')
+ else:
+ name_text = text("select * from iris where name=:name")
+ iris_df = sql.read_sql(name_text, conn, params={"name": "Iris-versicolor"})
+ all_names = set(iris_df["Name"])
+ assert all_names == {"Iris-versicolor"}
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_iris)
+def test_query_by_select_obj(conn, request):
+ conn = request.getfixturevalue(conn)
+ # WIP : GH10846
+ from sqlalchemy import (
+ bindparam,
+ select,
+ )
+
+ iris = iris_table_metadata()
+ name_select = select(iris).where(iris.c.Name == bindparam("name"))
+ iris_df = sql.read_sql(name_select, conn, params={"name": "Iris-setosa"})
+ all_names = set(iris_df["Name"])
+ assert all_names == {"Iris-setosa"}
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_column_with_percentage(conn, request):
+ # GH 37157
+ conn_name = conn
+ if conn_name == "sqlite_buildin":
+ request.applymarker(pytest.mark.xfail(reason="Not Implemented"))
+
+ conn = request.getfixturevalue(conn)
+ df = DataFrame({"A": [0, 1, 2], "%_variation": [3, 4, 5]})
+ df.to_sql(name="test_column_percentage", con=conn, index=False)
+
+ res = sql.read_sql_table("test_column_percentage", conn)
+
+ tm.assert_frame_equal(res, df)
+
+
+def test_sql_open_close(temp_file, test_frame3):
+ # Test if the IO in the database still work if the connection closed
+ # between the writing and reading (as in many real situations).
+
+ with contextlib.closing(sqlite3.connect(temp_file)) as conn:
+ assert sql.to_sql(test_frame3, "test_frame3_legacy", conn, index=False) == 4
+
+ with contextlib.closing(sqlite3.connect(temp_file)) as conn:
+ result = sql.read_sql_query("SELECT * FROM test_frame3_legacy;", conn)
+
+ tm.assert_frame_equal(test_frame3, result)
+
+
+@td.skip_if_installed("sqlalchemy")
+def test_con_string_import_error():
+ conn = "mysql://root@localhost/pandas"
+ msg = "Using URI string without sqlalchemy installed"
+ with pytest.raises(ImportError, match=msg):
+ sql.read_sql("SELECT * FROM iris", conn)
+
+
+@td.skip_if_installed("sqlalchemy")
+def test_con_unknown_dbapi2_class_does_not_error_without_sql_alchemy_installed():
+ class MockSqliteConnection:
+ def __init__(self, *args, **kwargs) -> None:
+ self.conn = sqlite3.Connection(*args, **kwargs)
+
+ def __getattr__(self, name):
+ return getattr(self.conn, name)
+
+ def close(self):
+ self.conn.close()
+
+ with contextlib.closing(MockSqliteConnection(":memory:")) as conn:
+ with tm.assert_produces_warning(UserWarning, match="only supports SQLAlchemy"):
+ sql.read_sql("SELECT 1", conn)
+
+
+def test_sqlite_read_sql_delegate(sqlite_buildin_iris):
+ conn = sqlite_buildin_iris
+ iris_frame1 = sql.read_sql_query("SELECT * FROM iris", conn)
+ iris_frame2 = sql.read_sql("SELECT * FROM iris", conn)
+ tm.assert_frame_equal(iris_frame1, iris_frame2)
+
+ msg = "Execution failed on sql 'iris': near \"iris\": syntax error"
+ with pytest.raises(sql.DatabaseError, match=msg):
+ sql.read_sql("iris", conn)
+
+
+def test_get_schema2(test_frame1):
+ # without providing a connection object (available for backwards comp)
+ create_sql = sql.get_schema(test_frame1, "test")
+ assert "CREATE" in create_sql
+
+
+def test_sqlite_type_mapping(sqlite_buildin):
+ # Test Timestamp objects (no datetime64 because of timezone) (GH9085)
+ conn = sqlite_buildin
+ df = DataFrame(
+ {"time": to_datetime(["2014-12-12 01:54", "2014-12-11 02:54"], utc=True)}
+ )
+ db = sql.SQLiteDatabase(conn)
+ table = sql.SQLiteTable("test_type", db, frame=df)
+ schema = table.sql_schema()
+ for col in schema.split("\n"):
+ if col.split()[0].strip('"') == "time":
+ assert col.split()[1] == "TIMESTAMP"
+
+
+# -----------------------------------------------------------------------------
+# -- Database flavor specific tests
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_create_table(conn, request):
+ if conn == "sqlite_str":
+ pytest.skip("sqlite_str has no inspection system")
+
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy import inspect
+
+ temp_frame = DataFrame({"one": [1.0, 2.0, 3.0, 4.0], "two": [4.0, 3.0, 2.0, 1.0]})
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ assert pandasSQL.to_sql(temp_frame, "temp_frame") == 4
+
+ insp = inspect(conn)
+ assert insp.has_table("temp_frame")
+
+ # Cleanup
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("temp_frame")
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_drop_table(conn, request):
+ if conn == "sqlite_str":
+ pytest.skip("sqlite_str has no inspection system")
+
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy import inspect
+
+ temp_frame = DataFrame({"one": [1.0, 2.0, 3.0, 4.0], "two": [4.0, 3.0, 2.0, 1.0]})
+ with sql.SQLDatabase(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ assert pandasSQL.to_sql(temp_frame, "temp_frame") == 4
+
+ insp = inspect(conn)
+ assert insp.has_table("temp_frame")
+
+ with pandasSQL.run_transaction():
+ pandasSQL.drop_table("temp_frame")
+ try:
+ insp.clear_cache() # needed with SQLAlchemy 2.0, unavailable prior
+ except AttributeError:
+ pass
+ assert not insp.has_table("temp_frame")
+
+
+@pytest.mark.parametrize("conn_name", all_connectable)
+def test_delete_rows_success(conn_name, test_frame1, request):
+ table_name = "temp_delete_rows_frame"
+ conn = request.getfixturevalue(conn_name)
+
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ assert pandasSQL.to_sql(test_frame1, table_name) == test_frame1.shape[0]
+
+ with pandasSQL.run_transaction():
+ assert pandasSQL.delete_rows(table_name) is None
+
+ assert count_rows(conn, table_name) == 0
+ assert pandasSQL.has_table(table_name)
+
+
+@pytest.mark.parametrize("conn_name", all_connectable)
+def test_delete_rows_is_atomic(conn_name, request):
+ sqlalchemy = pytest.importorskip("sqlalchemy")
+
+ table_name = "temp_delete_rows_atomic_frame"
+ table_stmt = f"CREATE TABLE {table_name} (a INTEGER, b INTEGER UNIQUE NOT NULL)"
+
+ if conn_name != "sqlite_buildin" and "adbc" not in conn_name:
+ table_stmt = sqlalchemy.text(table_stmt)
+
+ # setting dtype is mandatory for adbc related tests
+ original_df = DataFrame({"a": [1, 2], "b": [3, 4]}, dtype="int32")
+ replacing_df = DataFrame({"a": [5, 6, 7], "b": [8, 8, 8]}, dtype="int32")
+
+ conn = request.getfixturevalue(conn_name)
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction() as cur:
+ cur.execute(table_stmt)
+
+ with pandasSQL.run_transaction():
+ pandasSQL.to_sql(original_df, table_name, if_exists="append", index=False)
+
+ # inserting duplicated values in a UNIQUE constraint column
+ with pytest.raises(pd.errors.DatabaseError):
+ with pandasSQL.run_transaction():
+ pandasSQL.to_sql(
+ replacing_df, table_name, if_exists="delete_rows", index=False
+ )
+
+ # failed "delete_rows" is rolled back preserving original data
+ with pandasSQL.run_transaction():
+ result_df = pandasSQL.read_query(
+ f"SELECT * FROM {table_name}", dtype="int32"
+ )
+ tm.assert_frame_equal(result_df, original_df)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_roundtrip(conn, request, test_frame1):
+ if conn == "sqlite_str":
+ pytest.skip("sqlite_str has no inspection system")
+
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ assert pandasSQL.to_sql(test_frame1, "test_frame_roundtrip") == 4
+ result = pandasSQL.read_query("SELECT * FROM test_frame_roundtrip")
+
+ if "adbc" in conn_name:
+ result = result.rename(columns={"__index_level_0__": "level_0"})
+ result.set_index("level_0", inplace=True)
+ # result.index.astype(int)
+
+ result.index.name = None
+
+ tm.assert_frame_equal(result, test_frame1)
+
+
+@pytest.mark.parametrize("conn", all_connectable_iris)
+def test_execute_sql(conn, request):
+ conn = request.getfixturevalue(conn)
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ iris_results = pandasSQL.execute("SELECT * FROM iris")
+ row = iris_results.fetchone()
+ iris_results.close()
+ assert list(row) == [5.1, 3.5, 1.4, 0.2, "Iris-setosa"]
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_iris)
+def test_sqlalchemy_read_table(conn, request):
+ conn = request.getfixturevalue(conn)
+ iris_frame = sql.read_sql_table("iris", con=conn)
+ check_iris_frame(iris_frame)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_iris)
+def test_sqlalchemy_read_table_columns(conn, request):
+ conn = request.getfixturevalue(conn)
+ iris_frame = sql.read_sql_table(
+ "iris", con=conn, columns=["SepalLength", "SepalLength"]
+ )
+ tm.assert_index_equal(iris_frame.columns, Index(["SepalLength", "SepalLength__1"]))
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_iris)
+def test_read_table_absent_raises(conn, request):
+ conn = request.getfixturevalue(conn)
+ msg = "Table this_doesnt_exist not found"
+ with pytest.raises(ValueError, match=msg):
+ sql.read_sql_table("this_doesnt_exist", con=conn)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_types)
+def test_sqlalchemy_default_type_conversion(conn, request):
+ conn_name = conn
+ if conn_name == "sqlite_str":
+ pytest.skip("types tables not created in sqlite_str fixture")
+ elif "mysql" in conn_name or "sqlite" in conn_name:
+ request.applymarker(
+ pytest.mark.xfail(reason="boolean dtype not inferred properly")
+ )
+
+ conn = request.getfixturevalue(conn)
+ df = sql.read_sql_table("types", conn)
+
+ assert issubclass(df.FloatCol.dtype.type, np.floating)
+ assert issubclass(df.IntCol.dtype.type, np.integer)
+ assert issubclass(df.BoolCol.dtype.type, np.bool_)
+
+ # Int column with NA values stays as float
+ assert issubclass(df.IntColWithNull.dtype.type, np.floating)
+ # Bool column with NA values becomes object
+ assert issubclass(df.BoolColWithNull.dtype.type, object)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_bigint(conn, request):
+ # int64 should be converted to BigInteger, GH7433
+ conn = request.getfixturevalue(conn)
+ df = DataFrame(data={"i64": [2**62]})
+ assert df.to_sql(name="test_bigint", con=conn, index=False) == 1
+ result = sql.read_sql_table("test_bigint", conn)
+
+ tm.assert_frame_equal(df, result)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_types)
+def test_default_date_load(conn, request):
+ conn_name = conn
+ if conn_name == "sqlite_str":
+ pytest.skip("types tables not created in sqlite_str fixture")
+ elif "sqlite" in conn_name:
+ request.applymarker(
+ pytest.mark.xfail(reason="sqlite does not read date properly")
+ )
+
+ conn = request.getfixturevalue(conn)
+ df = sql.read_sql_table("types", conn)
+
+ assert issubclass(df.DateCol.dtype.type, np.datetime64)
+
+
+@pytest.mark.parametrize("conn", postgresql_connectable)
+@pytest.mark.parametrize("parse_dates", [None, ["DateColWithTz"]])
+def test_datetime_with_timezone_query(conn, request, parse_dates):
+ # edge case that converts postgresql datetime with time zone types
+ # to datetime64[ns,psycopg2.tz.FixedOffsetTimezone..], which is ok
+ # but should be more natural, so coerce to datetime64[ns] for now
+ conn = request.getfixturevalue(conn)
+ expected = create_and_load_postgres_datetz(conn)
+
+ # GH11216
+ df = read_sql_query("select * from datetz", conn, parse_dates=parse_dates)
+ col = df.DateColWithTz
+ tm.assert_series_equal(col, expected)
+
+
+@pytest.mark.parametrize("conn", postgresql_connectable)
+def test_datetime_with_timezone_query_chunksize(conn, request):
+ conn = request.getfixturevalue(conn)
+ expected = create_and_load_postgres_datetz(conn)
+
+ df = concat(
+ list(read_sql_query("select * from datetz", conn, chunksize=1)),
+ ignore_index=True,
+ )
+ col = df.DateColWithTz
+ tm.assert_series_equal(col, expected)
+
+
+@pytest.mark.parametrize("conn", postgresql_connectable)
+def test_datetime_with_timezone_table(conn, request):
+ conn = request.getfixturevalue(conn)
+ expected = create_and_load_postgres_datetz(conn)
+ result = sql.read_sql_table("datetz", conn)
+
+ exp_frame = expected.to_frame()
+ tm.assert_frame_equal(result, exp_frame)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_datetime_with_timezone_roundtrip(conn, request):
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ # GH 9086
+ # Write datetimetz data to a db and read it back
+ # For dbs that support timestamps with timezones, should get back UTC
+ # otherwise naive data should be returned
+ expected = DataFrame(
+ {"A": date_range("2013-01-01 09:00:00", periods=3, tz="US/Pacific", unit="us")}
+ )
+ assert expected.to_sql(name="test_datetime_tz", con=conn, index=False) == 3
+
+ if "postgresql" in conn_name:
+ # SQLAlchemy "timezones" (i.e. offsets) are coerced to UTC
+ expected["A"] = expected["A"].dt.tz_convert("UTC")
+ else:
+ # Otherwise, timestamps are returned as local, naive
+ expected["A"] = expected["A"].dt.tz_localize(None)
+
+ result = sql.read_sql_table("test_datetime_tz", conn)
+ tm.assert_frame_equal(result, expected)
+
+ result = sql.read_sql_query("SELECT * FROM test_datetime_tz", conn)
+ if "sqlite" in conn_name:
+ # read_sql_query does not return datetime type like read_sql_table
+ assert isinstance(result.loc[0, "A"], str)
+ result["A"] = to_datetime(result["A"]).dt.as_unit("us")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_out_of_bounds_datetime(conn, request):
+ # GH 26761
+ conn = request.getfixturevalue(conn)
+ data = DataFrame({"date": datetime(9999, 1, 1)}, index=[0])
+ assert data.to_sql(name="test_datetime_obb", con=conn, index=False) == 1
+ result = sql.read_sql_table("test_datetime_obb", conn)
+ expected = DataFrame(
+ np.array([datetime(9999, 1, 1)], dtype="M8[us]"), columns=["date"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_naive_datetimeindex_roundtrip(conn, request):
+ # GH 23510
+ # Ensure that a naive DatetimeIndex isn't converted to UTC
+ conn = request.getfixturevalue(conn)
+ dates = date_range("2018-01-01", periods=5, freq="6h", unit="us")._with_freq(None)
+ expected = DataFrame({"nums": range(5)}, index=dates)
+ assert expected.to_sql(name="foo_table", con=conn, index_label="info_date") == 5
+ result = sql.read_sql_table("foo_table", conn, index_col="info_date")
+ # result index with gain a name from a set_index operation; expected
+ tm.assert_frame_equal(result, expected, check_names=False)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable_types)
+def test_date_parsing(conn, request):
+ # No Parsing
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ df = sql.read_sql_table("types", conn)
+ expected_type = object if "sqlite" in conn_name else np.datetime64
+ assert issubclass(df.DateCol.dtype.type, expected_type)
+
+ df = sql.read_sql_table("types", conn, parse_dates=["DateCol"])
+ assert issubclass(df.DateCol.dtype.type, np.datetime64)
+
+ df = sql.read_sql_table("types", conn, parse_dates={"DateCol": "%Y-%m-%d %H:%M:%S"})
+ assert issubclass(df.DateCol.dtype.type, np.datetime64)
+
+ df = sql.read_sql_table(
+ "types",
+ conn,
+ parse_dates={"DateCol": {"format": "%Y-%m-%d %H:%M:%S"}},
+ )
+ assert issubclass(df.DateCol.dtype.type, np.datetime64)
+
+ df = sql.read_sql_table("types", conn, parse_dates=["IntDateCol"])
+ assert issubclass(df.IntDateCol.dtype.type, np.datetime64)
+
+ df = sql.read_sql_table("types", conn, parse_dates={"IntDateCol": "s"})
+ assert issubclass(df.IntDateCol.dtype.type, np.datetime64)
+
+ df = sql.read_sql_table("types", conn, parse_dates={"IntDateCol": {"unit": "s"}})
+ assert issubclass(df.IntDateCol.dtype.type, np.datetime64)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_datetime(conn, request):
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ df = DataFrame(
+ {"A": date_range("2013-01-01 09:00:00", periods=3), "B": np.arange(3.0)}
+ )
+ assert df.to_sql(name="test_datetime", con=conn) == 3
+
+ # with read_table -> type information from schema used
+ result = sql.read_sql_table("test_datetime", conn)
+ result = result.drop("index", axis=1)
+
+ expected = df[:]
+ expected["A"] = expected["A"].astype("M8[us]")
+ tm.assert_frame_equal(result, expected)
+
+ # with read_sql -> no type information -> sqlite has no native
+ result = sql.read_sql_query("SELECT * FROM test_datetime", conn)
+ result = result.drop("index", axis=1)
+ if "sqlite" in conn_name:
+ assert isinstance(result.loc[0, "A"], str)
+ result["A"] = to_datetime(result["A"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_datetime_NaT(conn, request):
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ df = DataFrame(
+ {"A": date_range("2013-01-01 09:00:00", periods=3), "B": np.arange(3.0)}
+ )
+ df.loc[1, "A"] = np.nan
+ assert df.to_sql(name="test_datetime", con=conn, index=False) == 3
+
+ # with read_table -> type information from schema used
+ result = sql.read_sql_table("test_datetime", conn)
+ expected = df[:]
+ expected["A"] = expected["A"].astype("M8[us]")
+ tm.assert_frame_equal(result, expected)
+
+ # with read_sql -> no type information -> sqlite has no native
+ result = sql.read_sql_query("SELECT * FROM test_datetime", conn)
+ if "sqlite" in conn_name:
+ assert isinstance(result.loc[0, "A"], str)
+ result["A"] = to_datetime(result["A"], errors="coerce")
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_datetime_date(conn, request):
+ # test support for datetime.date
+ conn = request.getfixturevalue(conn)
+ df = DataFrame([date(2014, 1, 1), date(2014, 1, 2)], columns=["a"])
+ assert df.to_sql(name="test_date", con=conn, index=False) == 2
+ res = read_sql_table("test_date", conn)
+ result = res["a"]
+ expected = to_datetime(df["a"])
+ # comes back as datetime64
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_datetime_time(conn, request, sqlite_buildin):
+ # test support for datetime.time
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ df = DataFrame([time(9, 0, 0), time(9, 1, 30)], columns=["a"])
+ assert df.to_sql(name="test_time", con=conn, index=False) == 2
+ res = read_sql_table("test_time", conn)
+ tm.assert_frame_equal(res, df)
+
+ # GH8341
+ # first, use the fallback to have the sqlite adapter put in place
+ sqlite_conn = sqlite_buildin
+ assert sql.to_sql(df, "test_time2", sqlite_conn, index=False) == 2
+ res = sql.read_sql_query("SELECT * FROM test_time2", sqlite_conn)
+ ref = df.map(lambda _: _.strftime("%H:%M:%S.%f"))
+ tm.assert_frame_equal(ref, res) # check if adapter is in place
+ # then test if sqlalchemy is unaffected by the sqlite adapter
+ assert sql.to_sql(df, "test_time3", conn, index=False) == 2
+ if "sqlite" in conn_name:
+ res = sql.read_sql_query("SELECT * FROM test_time3", conn)
+ ref = df.map(lambda _: _.strftime("%H:%M:%S.%f"))
+ tm.assert_frame_equal(ref, res)
+ res = sql.read_sql_table("test_time3", conn)
+ tm.assert_frame_equal(df, res)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_mixed_dtype_insert(conn, request):
+ # see GH6509
+ conn = request.getfixturevalue(conn)
+ s1 = Series(2**25 + 1, dtype=np.int32)
+ s2 = Series(0.0, dtype=np.float32)
+ df = DataFrame({"s1": s1, "s2": s2})
+
+ # write and read again
+ assert df.to_sql(name="test_read_write", con=conn, index=False) == 1
+ df2 = sql.read_sql_table("test_read_write", conn)
+
+ tm.assert_frame_equal(df, df2, check_dtype=False, check_exact=True)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_nan_numeric(conn, request):
+ # NaNs in numeric float column
+ conn = request.getfixturevalue(conn)
+ df = DataFrame({"A": [0, 1, 2], "B": [0.2, np.nan, 5.6]})
+ assert df.to_sql(name="test_nan", con=conn, index=False) == 3
+
+ # with read_table
+ result = sql.read_sql_table("test_nan", conn)
+ tm.assert_frame_equal(result, df)
+
+ # with read_sql
+ result = sql.read_sql_query("SELECT * FROM test_nan", conn)
+ tm.assert_frame_equal(result, df)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_nan_fullcolumn(conn, request):
+ # full NaN column (numeric float column)
+ conn = request.getfixturevalue(conn)
+ df = DataFrame({"A": [0, 1, 2], "B": [np.nan, np.nan, np.nan]})
+ assert df.to_sql(name="test_nan", con=conn, index=False) == 3
+
+ # with read_table
+ result = sql.read_sql_table("test_nan", conn)
+ tm.assert_frame_equal(result, df)
+
+ # with read_sql -> not type info from table -> stays None
+ df["B"] = df["B"].astype("object")
+ df["B"] = None
+ result = sql.read_sql_query("SELECT * FROM test_nan", conn)
+ tm.assert_frame_equal(result, df)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_nan_string(conn, request):
+ # NaNs in string column
+ conn = request.getfixturevalue(conn)
+ df = DataFrame({"A": [0, 1, 2], "B": ["a", "b", np.nan]})
+ assert df.to_sql(name="test_nan", con=conn, index=False) == 3
+
+ # NaNs are coming back as None
+ df.loc[2, "B"] = None
+
+ # with read_table
+ result = sql.read_sql_table("test_nan", conn)
+ tm.assert_frame_equal(result, df)
+
+ # with read_sql
+ result = sql.read_sql_query("SELECT * FROM test_nan", conn)
+ tm.assert_frame_equal(result, df)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_to_sql_save_index(conn, request):
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(
+ reason="ADBC implementation does not create index", strict=True
+ )
+ )
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ df = DataFrame.from_records(
+ [(1, 2.1, "line1"), (2, 1.5, "line2")], columns=["A", "B", "C"], index=["A"]
+ )
+
+ tbl_name = "test_to_sql_saves_index"
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ assert pandasSQL.to_sql(df, tbl_name) == 2
+
+ if conn_name in {"sqlite_buildin", "sqlite_str"}:
+ ixs = sql.read_sql_query(
+ "SELECT * FROM sqlite_master WHERE type = 'index' "
+ f"AND tbl_name = '{tbl_name}'",
+ conn,
+ )
+ ix_cols = []
+ for ix_name in ixs.name:
+ ix_info = sql.read_sql_query(f"PRAGMA index_info({ix_name})", conn)
+ ix_cols.append(ix_info.name.tolist())
+ else:
+ from sqlalchemy import inspect
+
+ insp = inspect(conn)
+
+ ixs = insp.get_indexes(tbl_name)
+ ix_cols = [i["column_names"] for i in ixs]
+
+ assert ix_cols == [["A"]]
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_transactions(conn, request):
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+
+ stmt = "CREATE TABLE test_trans (A INT, B TEXT)"
+ if conn_name != "sqlite_buildin" and "adbc" not in conn_name:
+ from sqlalchemy import text
+
+ stmt = text(stmt)
+
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction() as trans:
+ trans.execute(stmt)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_transaction_rollback(conn, request):
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction() as trans:
+ stmt = "CREATE TABLE test_trans (A INT, B TEXT)"
+ if "adbc" in conn_name or isinstance(pandasSQL, SQLiteDatabase):
+ trans.execute(stmt)
+ else:
+ from sqlalchemy import text
+
+ stmt = text(stmt)
+ trans.execute(stmt)
+
+ class DummyException(Exception):
+ pass
+
+ # Make sure when transaction is rolled back, no rows get inserted
+ ins_sql = "INSERT INTO test_trans (A,B) VALUES (1, 'blah')"
+ if isinstance(pandasSQL, SQLDatabase):
+ from sqlalchemy import text
+
+ ins_sql = text(ins_sql)
+ try:
+ with pandasSQL.run_transaction() as trans:
+ trans.execute(ins_sql)
+ raise DummyException("error")
+ except DummyException:
+ # ignore raised exception
+ pass
+ with pandasSQL.run_transaction():
+ res = pandasSQL.read_query("SELECT * FROM test_trans")
+ assert len(res) == 0
+
+ # Make sure when transaction is committed, rows do get inserted
+ with pandasSQL.run_transaction() as trans:
+ trans.execute(ins_sql)
+ res2 = pandasSQL.read_query("SELECT * FROM test_trans")
+ assert len(res2) == 1
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_get_schema_create_table(conn, request, test_frame3):
+ # Use a dataframe without a bool column, since MySQL converts bool to
+ # TINYINT (which read_sql_table returns as an int and causes a dtype
+ # mismatch)
+ if conn == "sqlite_str":
+ request.applymarker(
+ pytest.mark.xfail(reason="test does not support sqlite_str fixture")
+ )
+
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy import text
+ from sqlalchemy.engine import Engine
+
+ tbl = "test_get_schema_create_table"
+ create_sql = sql.get_schema(test_frame3, tbl, con=conn)
+ blank_test_df = test_frame3.iloc[:0]
+
+ create_sql = text(create_sql)
+ if isinstance(conn, Engine):
+ with conn.connect() as newcon:
+ with newcon.begin():
+ newcon.execute(create_sql)
+ else:
+ conn.execute(create_sql)
+ returned_df = sql.read_sql_table(tbl, conn)
+ tm.assert_frame_equal(returned_df, blank_test_df, check_index_type=False)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_dtype(conn, request):
+ if conn == "sqlite_str":
+ pytest.skip("sqlite_str has no inspection system")
+
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy import (
+ TEXT,
+ String,
+ )
+ from sqlalchemy.schema import MetaData
+
+ cols = ["A", "B"]
+ data = [(0.8, True), (0.9, None)]
+ df = DataFrame(data, columns=cols)
+ assert df.to_sql(name="dtype_test", con=conn) == 2
+ assert df.to_sql(name="dtype_test2", con=conn, dtype={"B": TEXT}) == 2
+ meta = MetaData()
+ meta.reflect(bind=conn)
+ sqltype = meta.tables["dtype_test2"].columns["B"].type
+ assert isinstance(sqltype, TEXT)
+ msg = "The type of B is not a SQLAlchemy type"
+ with pytest.raises(ValueError, match=msg):
+ df.to_sql(name="error", con=conn, dtype={"B": str})
+
+ # GH9083
+ assert df.to_sql(name="dtype_test3", con=conn, dtype={"B": String(10)}) == 2
+ meta.reflect(bind=conn)
+ sqltype = meta.tables["dtype_test3"].columns["B"].type
+ assert isinstance(sqltype, String)
+ assert sqltype.length == 10
+
+ # single dtype
+ assert df.to_sql(name="single_dtype_test", con=conn, dtype=TEXT) == 2
+ meta.reflect(bind=conn)
+ sqltypea = meta.tables["single_dtype_test"].columns["A"].type
+ sqltypeb = meta.tables["single_dtype_test"].columns["B"].type
+ assert isinstance(sqltypea, TEXT)
+ assert isinstance(sqltypeb, TEXT)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_notna_dtype(conn, request):
+ if conn == "sqlite_str":
+ pytest.skip("sqlite_str has no inspection system")
+
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy import (
+ Boolean,
+ DateTime,
+ Float,
+ Integer,
+ )
+ from sqlalchemy.schema import MetaData
+
+ cols = {
+ "Bool": Series([True, None]),
+ "Date": Series([datetime(2012, 5, 1), None]),
+ "Int": Series([1, None], dtype="object"),
+ "Float": Series([1.1, None]),
+ }
+ df = DataFrame(cols)
+
+ tbl = "notna_dtype_test"
+ assert df.to_sql(name=tbl, con=conn) == 2
+ _ = sql.read_sql_table(tbl, conn)
+ meta = MetaData()
+ meta.reflect(bind=conn)
+ my_type = Integer if "mysql" in conn_name else Boolean
+ col_dict = meta.tables[tbl].columns
+ assert isinstance(col_dict["Bool"].type, my_type)
+ assert isinstance(col_dict["Date"].type, DateTime)
+ assert isinstance(col_dict["Int"].type, Integer)
+ assert isinstance(col_dict["Float"].type, Float)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_double_precision(conn, request):
+ if conn == "sqlite_str":
+ pytest.skip("sqlite_str has no inspection system")
+
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy import (
+ BigInteger,
+ Float,
+ Integer,
+ )
+ from sqlalchemy.schema import MetaData
+
+ V = 1.23456789101112131415
+
+ df = DataFrame(
+ {
+ "f32": Series([V], dtype="float32"),
+ "f64": Series([V], dtype="float64"),
+ "f64_as_f32": Series([V], dtype="float64"),
+ "i32": Series([5], dtype="int32"),
+ "i64": Series([5], dtype="int64"),
+ }
+ )
+
+ assert (
+ df.to_sql(
+ name="test_dtypes",
+ con=conn,
+ index=False,
+ if_exists="replace",
+ dtype={"f64_as_f32": Float(precision=23)},
+ )
+ == 1
+ )
+ res = sql.read_sql_table("test_dtypes", conn)
+
+ # check precision of float64
+ assert np.round(df["f64"].iloc[0], 14) == np.round(res["f64"].iloc[0], 14)
+
+ # check sql types
+ meta = MetaData()
+ meta.reflect(bind=conn)
+ col_dict = meta.tables["test_dtypes"].columns
+ assert str(col_dict["f32"].type) == str(col_dict["f64_as_f32"].type)
+ assert isinstance(col_dict["f32"].type, Float)
+ assert isinstance(col_dict["f64"].type, Float)
+ assert isinstance(col_dict["i32"].type, Integer)
+ assert isinstance(col_dict["i64"].type, BigInteger)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_connectable_issue_example(conn, request):
+ conn = request.getfixturevalue(conn)
+
+ # This tests the example raised in issue
+ # https://github.com/pandas-dev/pandas/issues/10104
+ from sqlalchemy.engine import Engine
+
+ def test_select(connection):
+ query = "SELECT test_foo_data FROM test_foo_data"
+ return sql.read_sql_query(query, con=connection)
+
+ def test_append(connection, data):
+ data.to_sql(name="test_foo_data", con=connection, if_exists="append")
+
+ def test_connectable(conn):
+ # https://github.com/sqlalchemy/sqlalchemy/commit/
+ # 00b5c10846e800304caa86549ab9da373b42fa5d#r48323973
+ foo_data = test_select(conn)
+ test_append(conn, foo_data)
+
+ def main(connectable):
+ if isinstance(connectable, Engine):
+ with connectable.connect() as conn:
+ with conn.begin():
+ test_connectable(conn)
+ else:
+ test_connectable(connectable)
+
+ assert (
+ DataFrame({"test_foo_data": [0, 1, 2]}).to_sql(name="test_foo_data", con=conn)
+ == 3
+ )
+ main(conn)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+@pytest.mark.parametrize(
+ "input",
+ [{"foo": [np.inf]}, {"foo": [-np.inf]}, {"foo": [-np.inf], "infe0": ["bar"]}],
+)
+def test_to_sql_with_negative_npinf(conn, request, input):
+ # GH 34431
+
+ df = DataFrame(input)
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+
+ if "mysql" in conn_name:
+ # GH 36465
+ # The input {"foo": [-np.inf], "infe0": ["bar"]} does not raise any error
+ # for pymysql version >= 0.10
+ msg = "Execution failed on sql"
+ with pytest.raises(pd.errors.DatabaseError, match=msg):
+ df.to_sql(name="foobar", con=conn, index=False)
+ else:
+ assert df.to_sql(name="foobar", con=conn, index=False) == 1
+ res = sql.read_sql_table("foobar", conn)
+ tm.assert_equal(df, res)
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_temporary_table(conn, request):
+ if conn == "sqlite_str":
+ pytest.skip("test does not work with str connection")
+
+ conn = request.getfixturevalue(conn)
+
+ from sqlalchemy import (
+ Column,
+ Integer,
+ Unicode,
+ select,
+ )
+ from sqlalchemy.orm import (
+ Session,
+ declarative_base,
+ )
+
+ test_data = "Hello, World!"
+ expected = DataFrame({"spam": [test_data]})
+ Base = declarative_base()
+
+ class Temporary(Base):
+ __tablename__ = "temp_test"
+ __table_args__ = {"prefixes": ["TEMPORARY"]}
+ id = Column(Integer, primary_key=True)
+ spam = Column(Unicode(30), nullable=False)
+
+ with Session(conn) as session:
+ with session.begin():
+ conn = session.connection()
+ Temporary.__table__.create(conn)
+ session.add(Temporary(spam=test_data))
+ session.flush()
+ df = sql.read_sql_query(sql=select(Temporary.spam), con=conn)
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_invalid_engine(conn, request, test_frame1):
+ if conn == "sqlite_buildin" or "adbc" in conn:
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="SQLiteDatabase/ADBCDatabase does not raise for bad engine"
+ )
+ )
+
+ conn = request.getfixturevalue(conn)
+ msg = "engine must be one of 'auto', 'sqlalchemy'"
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pytest.raises(ValueError, match=msg):
+ pandasSQL.to_sql(test_frame1, "test_frame1", engine="bad_engine")
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_to_sql_with_sql_engine(conn, request, test_frame1):
+ """`to_sql` with the `engine` param"""
+ # mostly copied from this class's `_to_sql()` method
+ conn = request.getfixturevalue(conn)
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ assert pandasSQL.to_sql(test_frame1, "test_frame1", engine="auto") == 4
+ assert pandasSQL.has_table("test_frame1")
+
+ num_entries = len(test_frame1)
+ num_rows = count_rows(conn, "test_frame1")
+ assert num_rows == num_entries
+
+
+@pytest.mark.parametrize("conn", sqlalchemy_connectable)
+def test_options_sqlalchemy(conn, request, test_frame1):
+ # use the set option
+ conn = request.getfixturevalue(conn)
+ with pd.option_context("io.sql.engine", "sqlalchemy"):
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ assert pandasSQL.to_sql(test_frame1, "test_frame1") == 4
+ assert pandasSQL.has_table("test_frame1")
+
+ num_entries = len(test_frame1)
+ num_rows = count_rows(conn, "test_frame1")
+ assert num_rows == num_entries
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_options_auto(conn, request, test_frame1):
+ # use the set option
+ conn = request.getfixturevalue(conn)
+ with pd.option_context("io.sql.engine", "auto"):
+ with pandasSQL_builder(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ assert pandasSQL.to_sql(test_frame1, "test_frame1") == 4
+ assert pandasSQL.has_table("test_frame1")
+
+ num_entries = len(test_frame1)
+ num_rows = count_rows(conn, "test_frame1")
+ assert num_rows == num_entries
+
+
+def test_options_get_engine():
+ pytest.importorskip("sqlalchemy")
+ assert isinstance(get_engine("sqlalchemy"), SQLAlchemyEngine)
+
+ with pd.option_context("io.sql.engine", "sqlalchemy"):
+ assert isinstance(get_engine("auto"), SQLAlchemyEngine)
+ assert isinstance(get_engine("sqlalchemy"), SQLAlchemyEngine)
+
+ with pd.option_context("io.sql.engine", "auto"):
+ assert isinstance(get_engine("auto"), SQLAlchemyEngine)
+ assert isinstance(get_engine("sqlalchemy"), SQLAlchemyEngine)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+@pytest.mark.parametrize("func", ["read_sql", "read_sql_query"])
+def test_read_sql_dtype_backend(
+ conn,
+ request,
+ string_storage,
+ func,
+ dtype_backend,
+ dtype_backend_data,
+ dtype_backend_expected,
+):
+ # GH#50048
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ table = "test"
+ df = dtype_backend_data
+ df.to_sql(name=table, con=conn, index=False, if_exists="replace")
+
+ with pd.option_context("mode.string_storage", string_storage):
+ result = getattr(pd, func)(
+ f"Select * from {table}", conn, dtype_backend=dtype_backend
+ )
+ expected = dtype_backend_expected(string_storage, dtype_backend, conn_name)
+
+ tm.assert_frame_equal(result, expected)
+
+ if "adbc" in conn_name:
+ # adbc does not support chunksize argument
+ request.applymarker(
+ pytest.mark.xfail(reason="adbc does not support chunksize argument")
+ )
+
+ with pd.option_context("mode.string_storage", string_storage):
+ iterator = getattr(pd, func)(
+ f"Select * from {table}",
+ con=conn,
+ dtype_backend=dtype_backend,
+ chunksize=3,
+ )
+ expected = dtype_backend_expected(string_storage, dtype_backend, conn_name)
+ for result in iterator:
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+@pytest.mark.parametrize("func", ["read_sql", "read_sql_table"])
+def test_read_sql_dtype_backend_table(
+ conn,
+ request,
+ string_storage,
+ func,
+ dtype_backend,
+ dtype_backend_data,
+ dtype_backend_expected,
+):
+ if "sqlite" in conn and "adbc" not in conn:
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=(
+ "SQLite actually returns proper boolean values via "
+ "read_sql_table, but before pytest refactor was skipped"
+ )
+ )
+ )
+ # GH#50048
+ conn_name = conn
+ conn = request.getfixturevalue(conn)
+ table = "test"
+ df = dtype_backend_data
+ df.to_sql(name=table, con=conn, index=False, if_exists="replace")
+
+ with pd.option_context("mode.string_storage", string_storage):
+ result = getattr(pd, func)(table, conn, dtype_backend=dtype_backend)
+ expected = dtype_backend_expected(string_storage, dtype_backend, conn_name)
+ tm.assert_frame_equal(result, expected)
+
+ if "adbc" in conn_name:
+ # adbc does not support chunksize argument
+ return
+
+ with pd.option_context("mode.string_storage", string_storage):
+ iterator = getattr(pd, func)(
+ table,
+ conn,
+ dtype_backend=dtype_backend,
+ chunksize=3,
+ )
+ expected = dtype_backend_expected(string_storage, dtype_backend, conn_name)
+ for result in iterator:
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+@pytest.mark.parametrize("func", ["read_sql", "read_sql_table", "read_sql_query"])
+def test_read_sql_invalid_dtype_backend_table(conn, request, func, dtype_backend_data):
+ conn = request.getfixturevalue(conn)
+ table = "test"
+ df = dtype_backend_data
+ df.to_sql(name=table, con=conn, index=False, if_exists="replace")
+
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ with pytest.raises(ValueError, match=msg):
+ getattr(pd, func)(table, conn, dtype_backend="numpy")
+
+
+@pytest.fixture
+def dtype_backend_data() -> DataFrame:
+ return DataFrame(
+ {
+ "a": Series([1, pd.NA, 3], dtype="Int64"),
+ "b": Series([1, 2, 3], dtype="Int64"),
+ "c": Series([1.5, pd.NA, 2.5], dtype="Float64"),
+ "d": Series([1.5, 2.0, 2.5], dtype="Float64"),
+ "e": [True, False, None],
+ "f": [True, False, True],
+ "g": ["a", "b", "c"],
+ "h": ["a", "b", None],
+ }
+ )
+
+
+@pytest.fixture
+def dtype_backend_expected():
+ def func(string_storage, dtype_backend, conn_name) -> DataFrame:
+ string_dtype: pd.StringDtype | pd.ArrowDtype
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ string_dtype = pd.ArrowDtype(pa.string())
+ else:
+ string_dtype = pd.StringDtype(string_storage)
+
+ df = DataFrame(
+ {
+ "a": Series([1, pd.NA, 3], dtype="Int64"),
+ "b": Series([1, 2, 3], dtype="Int64"),
+ "c": Series([1.5, pd.NA, 2.5], dtype="Float64"),
+ "d": Series([1.5, 2.0, 2.5], dtype="Float64"),
+ "e": Series([True, False, pd.NA], dtype="boolean"),
+ "f": Series([True, False, True], dtype="boolean"),
+ "g": Series(["a", "b", "c"], dtype=string_dtype),
+ "h": Series(["a", "b", None], dtype=string_dtype),
+ }
+ )
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+
+ from pandas.arrays import ArrowExtensionArray
+
+ df = DataFrame(
+ {
+ col: ArrowExtensionArray(pa.array(df[col], from_pandas=True))
+ for col in df.columns
+ }
+ )
+
+ if "mysql" in conn_name or "sqlite" in conn_name:
+ if dtype_backend == "numpy_nullable":
+ df = df.astype({"e": "Int64", "f": "Int64"})
+ else:
+ df = df.astype({"e": "int64[pyarrow]", "f": "int64[pyarrow]"})
+
+ return df
+
+ return func
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+def test_chunksize_empty_dtypes(conn, request):
+ # GH#50245
+ if "adbc" in conn:
+ request.node.add_marker(
+ pytest.mark.xfail(reason="chunksize argument NotImplemented with ADBC")
+ )
+ conn = request.getfixturevalue(conn)
+ dtypes = {"a": "int64", "b": "object"}
+ df = DataFrame(columns=["a", "b"]).astype(dtypes)
+ expected = df.copy()
+ df.to_sql(name="test", con=conn, index=False, if_exists="replace")
+
+ for result in read_sql_query(
+ "SELECT * FROM test",
+ conn,
+ dtype=dtypes,
+ chunksize=1,
+ ):
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("conn", all_connectable)
+@pytest.mark.parametrize("dtype_backend", [lib.no_default, "numpy_nullable"])
+@pytest.mark.parametrize("func", ["read_sql", "read_sql_query"])
+def test_read_sql_dtype(conn, request, func, dtype_backend):
+ # GH#50797
+ conn = request.getfixturevalue(conn)
+ table = "test"
+ df = DataFrame({"a": [1, 2, 3], "b": 5})
+ df.to_sql(name=table, con=conn, index=False, if_exists="replace")
+
+ result = getattr(pd, func)(
+ f"Select * from {table}",
+ conn,
+ dtype={"a": np.float64},
+ dtype_backend=dtype_backend,
+ )
+ expected = DataFrame(
+ {
+ "a": Series([1, 2, 3], dtype=np.float64),
+ "b": Series(
+ [5, 5, 5],
+ dtype="int64" if not dtype_backend == "numpy_nullable" else "Int64",
+ ),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_bigint_warning(sqlite_engine):
+ conn = sqlite_engine
+ # test no warning for BIGINT (to support int64) is raised (GH7433)
+ df = DataFrame({"a": [1, 2]}, dtype="int64")
+ assert df.to_sql(name="test_bigintwarning", con=conn, index=False) == 2
+
+ with tm.assert_produces_warning(None):
+ sql.read_sql_table("test_bigintwarning", conn)
+
+
+def test_valueerror_exception(sqlite_engine):
+ conn = sqlite_engine
+ df = DataFrame({"col1": [1, 2], "col2": [3, 4]})
+ with pytest.raises(ValueError, match="Empty table name specified"):
+ df.to_sql(name="", con=conn, if_exists="replace", index=False)
+
+
+def test_row_object_is_named_tuple(sqlite_engine):
+ conn = sqlite_engine
+ # GH 40682
+ # Test for the is_named_tuple() function
+ # Placed here due to its usage of sqlalchemy
+
+ from sqlalchemy import (
+ Column,
+ Integer,
+ String,
+ )
+ from sqlalchemy.orm import (
+ declarative_base,
+ sessionmaker,
+ )
+
+ BaseModel = declarative_base()
+
+ class Test(BaseModel):
+ __tablename__ = "test_frame"
+ id = Column(Integer, primary_key=True)
+ string_column = Column(String(50))
+
+ with conn.begin():
+ BaseModel.metadata.create_all(conn)
+ Session = sessionmaker(bind=conn)
+ with Session() as session:
+ df = DataFrame({"id": [0, 1], "string_column": ["hello", "world"]})
+ assert (
+ df.to_sql(name="test_frame", con=conn, index=False, if_exists="replace")
+ == 2
+ )
+ session.commit()
+ test_query = session.query(Test.id, Test.string_column)
+ df = DataFrame(test_query)
+
+ assert list(df.columns) == ["id", "string_column"]
+
+
+def test_read_sql_string_inference(sqlite_engine):
+ conn = sqlite_engine
+ # GH#54430
+ table = "test"
+ df = DataFrame({"a": ["x", "y"]})
+ df.to_sql(table, con=conn, index=False, if_exists="replace")
+
+ with pd.option_context("future.infer_string", True):
+ result = read_sql_table(table, conn)
+
+ dtype = pd.StringDtype(na_value=np.nan)
+ expected = DataFrame(
+ {"a": ["x", "y"]}, dtype=dtype, columns=Index(["a"], dtype=dtype)
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_roundtripping_datetimes(sqlite_engine):
+ conn = sqlite_engine
+ # GH#54877
+ df = DataFrame({"t": [datetime(2020, 12, 31, 12)]}, dtype="datetime64[ns]")
+ df.to_sql("test", conn, if_exists="replace", index=False)
+ result = pd.read_sql("select * from test", conn).iloc[0, 0]
+ assert result == "2020-12-31 12:00:00.000000"
+
+
+@pytest.fixture
+def sqlite_builtin_detect_types():
+ with contextlib.closing(
+ sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)
+ ) as closing_conn:
+ with closing_conn as conn:
+ yield conn
+
+
+def test_roundtripping_datetimes_detect_types(sqlite_builtin_detect_types):
+ # https://github.com/pandas-dev/pandas/issues/55554
+ conn = sqlite_builtin_detect_types
+ df = DataFrame({"t": [datetime(2020, 12, 31, 12)]}, dtype="datetime64[ns]")
+ df.to_sql("test", conn, if_exists="replace", index=False)
+ result = pd.read_sql("select * from test", conn).iloc[0, 0]
+ assert result == Timestamp("2020-12-31 12:00:00.000000")
+
+
+@pytest.mark.db
+def test_psycopg2_schema_support(postgresql_psycopg2_engine):
+ conn = postgresql_psycopg2_engine
+
+ # only test this for postgresql (schema's not supported in
+ # mysql/sqlite)
+ df = DataFrame({"col1": [1, 2], "col2": [0.1, 0.2], "col3": ["a", "n"]})
+
+ # create a schema
+ with conn.connect() as con:
+ with con.begin():
+ con.exec_driver_sql("DROP SCHEMA IF EXISTS other CASCADE;")
+ con.exec_driver_sql("CREATE SCHEMA other;")
+
+ # write dataframe to different schema's
+ assert df.to_sql(name="test_schema_public", con=conn, index=False) == 2
+ assert (
+ df.to_sql(
+ name="test_schema_public_explicit",
+ con=conn,
+ index=False,
+ schema="public",
+ )
+ == 2
+ )
+ assert (
+ df.to_sql(name="test_schema_other", con=conn, index=False, schema="other") == 2
+ )
+
+ # read dataframes back in
+ res1 = sql.read_sql_table("test_schema_public", conn)
+ tm.assert_frame_equal(df, res1)
+ res2 = sql.read_sql_table("test_schema_public_explicit", conn)
+ tm.assert_frame_equal(df, res2)
+ res3 = sql.read_sql_table("test_schema_public_explicit", conn, schema="public")
+ tm.assert_frame_equal(df, res3)
+ res4 = sql.read_sql_table("test_schema_other", conn, schema="other")
+ tm.assert_frame_equal(df, res4)
+ msg = "Table test_schema_other not found"
+ with pytest.raises(ValueError, match=msg):
+ sql.read_sql_table("test_schema_other", conn, schema="public")
+
+ # different if_exists options
+
+ # create a schema
+ with conn.connect() as con:
+ with con.begin():
+ con.exec_driver_sql("DROP SCHEMA IF EXISTS other CASCADE;")
+ con.exec_driver_sql("CREATE SCHEMA other;")
+
+ # write dataframe with different if_exists options
+ assert (
+ df.to_sql(name="test_schema_other", con=conn, schema="other", index=False) == 2
+ )
+ df.to_sql(
+ name="test_schema_other",
+ con=conn,
+ schema="other",
+ index=False,
+ if_exists="replace",
+ )
+ assert (
+ df.to_sql(
+ name="test_schema_other",
+ con=conn,
+ schema="other",
+ index=False,
+ if_exists="append",
+ )
+ == 2
+ )
+ res = sql.read_sql_table("test_schema_other", conn, schema="other")
+ tm.assert_frame_equal(concat([df, df], ignore_index=True), res)
+
+
+@pytest.mark.db
+def test_self_join_date_columns(postgresql_psycopg2_engine):
+ # GH 44421
+ conn = postgresql_psycopg2_engine
+ from sqlalchemy.sql import text
+
+ create_table = text(
+ """
+ CREATE TABLE person
+ (
+ id serial constraint person_pkey primary key,
+ created_dt timestamp with time zone
+ );
+
+ INSERT INTO person
+ VALUES (1, '2021-01-01T00:00:00Z');
+ """
+ )
+ with conn.connect() as con:
+ with con.begin():
+ con.execute(create_table)
+
+ sql_query = (
+ 'SELECT * FROM "person" AS p1 INNER JOIN "person" AS p2 ON p1.id = p2.id;'
+ )
+ result = pd.read_sql(sql_query, conn)
+ expected = DataFrame(
+ [[1, Timestamp("2021", tz="UTC")] * 2], columns=["id", "created_dt"] * 2
+ )
+ expected["created_dt"] = expected["created_dt"].astype("M8[us, UTC]")
+ tm.assert_frame_equal(result, expected)
+
+ # Cleanup
+ with sql.SQLDatabase(conn, need_transaction=True) as pandasSQL:
+ pandasSQL.drop_table("person")
+
+
+def test_create_and_drop_table(sqlite_engine):
+ conn = sqlite_engine
+ temp_frame = DataFrame({"one": [1.0, 2.0, 3.0, 4.0], "two": [4.0, 3.0, 2.0, 1.0]})
+ with sql.SQLDatabase(conn) as pandasSQL:
+ with pandasSQL.run_transaction():
+ assert pandasSQL.to_sql(temp_frame, "drop_test_frame") == 4
+
+ assert pandasSQL.has_table("drop_test_frame")
+
+ with pandasSQL.run_transaction():
+ pandasSQL.drop_table("drop_test_frame")
+
+ assert not pandasSQL.has_table("drop_test_frame")
+
+
+def test_sqlite_datetime_date(sqlite_buildin):
+ conn = sqlite_buildin
+ df = DataFrame([date(2014, 1, 1), date(2014, 1, 2)], columns=["a"])
+ assert df.to_sql(name="test_date", con=conn, index=False) == 2
+ res = read_sql_query("SELECT * FROM test_date", conn)
+ # comes back as strings
+ tm.assert_frame_equal(res, df.astype(str))
+
+
+@pytest.mark.parametrize("tz_aware", [False, True])
+def test_sqlite_datetime_time(tz_aware, sqlite_buildin):
+ conn = sqlite_buildin
+ # test support for datetime.time, GH #8341
+ if not tz_aware:
+ tz_times = [time(9, 0, 0), time(9, 1, 30)]
+ else:
+ tz_dt = date_range("2013-01-01 09:00:00", periods=2, tz="US/Pacific")
+ tz_times = Series(tz_dt.to_pydatetime()).map(lambda dt: dt.timetz())
+
+ df = DataFrame(tz_times, columns=["a"])
+
+ assert df.to_sql(name="test_time", con=conn, index=False) == 2
+ res = read_sql_query("SELECT * FROM test_time", conn)
+ # comes back as strings
+ expected = df.map(lambda _: _.strftime("%H:%M:%S.%f"))
+ tm.assert_frame_equal(res, expected)
+
+
+def get_sqlite_column_type(conn, table, column):
+ recs = conn.execute(f"PRAGMA table_info({table})")
+ for cid, name, ctype, not_null, default, pk in recs:
+ if name == column:
+ return ctype
+ raise ValueError(f"Table {table}, column {column} not found")
+
+
+def test_sqlite_test_dtype(sqlite_buildin):
+ conn = sqlite_buildin
+ cols = ["A", "B"]
+ data = [(0.8, True), (0.9, None)]
+ df = DataFrame(data, columns=cols)
+ assert df.to_sql(name="dtype_test", con=conn) == 2
+ assert df.to_sql(name="dtype_test2", con=conn, dtype={"B": "STRING"}) == 2
+
+ # sqlite stores Boolean values as INTEGER
+ assert get_sqlite_column_type(conn, "dtype_test", "B") == "INTEGER"
+
+ assert get_sqlite_column_type(conn, "dtype_test2", "B") == "STRING"
+ msg = r"B \(\) not a string"
+ with pytest.raises(ValueError, match=msg):
+ df.to_sql(name="error", con=conn, dtype={"B": bool})
+
+ # single dtype
+ assert df.to_sql(name="single_dtype_test", con=conn, dtype="STRING") == 2
+ assert get_sqlite_column_type(conn, "single_dtype_test", "A") == "STRING"
+ assert get_sqlite_column_type(conn, "single_dtype_test", "B") == "STRING"
+
+
+def test_sqlite_notna_dtype(sqlite_buildin):
+ conn = sqlite_buildin
+ cols = {
+ "Bool": Series([True, None]),
+ "Date": Series([datetime(2012, 5, 1), None]),
+ "Int": Series([1, None], dtype="object"),
+ "Float": Series([1.1, None]),
+ }
+ df = DataFrame(cols)
+
+ tbl = "notna_dtype_test"
+ assert df.to_sql(name=tbl, con=conn) == 2
+
+ assert get_sqlite_column_type(conn, tbl, "Bool") == "INTEGER"
+ assert get_sqlite_column_type(conn, tbl, "Date") == "TIMESTAMP"
+ assert get_sqlite_column_type(conn, tbl, "Int") == "INTEGER"
+ assert get_sqlite_column_type(conn, tbl, "Float") == "REAL"
+
+
+def test_sqlite_illegal_names(sqlite_buildin):
+ # For sqlite, these should work fine
+ conn = sqlite_buildin
+ df = DataFrame([[1, 2], [3, 4]], columns=["a", "b"])
+
+ msg = "Empty table or column name specified"
+ with pytest.raises(ValueError, match=msg):
+ df.to_sql(name="", con=conn)
+
+ for ndx, weird_name in enumerate(
+ [
+ "test_weird_name]",
+ "test_weird_name[",
+ "test_weird_name`",
+ 'test_weird_name"',
+ "test_weird_name'",
+ "_b.test_weird_name_01-30",
+ '"_b.test_weird_name_01-30"',
+ "99beginswithnumber",
+ "12345",
+ "\xe9",
+ ]
+ ):
+ assert df.to_sql(name=weird_name, con=conn) == 2
+ sql.table_exists(weird_name, conn)
+
+ df2 = DataFrame([[1, 2], [3, 4]], columns=["a", weird_name])
+ c_tbl = f"test_weird_col_name{ndx:d}"
+ assert df2.to_sql(name=c_tbl, con=conn) == 2
+ sql.table_exists(c_tbl, conn)
+
+
+def format_query(sql, *args):
+ _formatters = {
+ datetime: "'{}'".format,
+ str: "'{}'".format,
+ np.str_: "'{}'".format,
+ bytes: "'{}'".format,
+ float: "{:.8f}".format,
+ int: "{:d}".format,
+ type(None): lambda x: "NULL",
+ np.float64: "{:.10f}".format,
+ bool: "'{!s}'".format,
+ }
+ processed_args = []
+ for arg in args:
+ if isinstance(arg, float) and isna(arg):
+ arg = None
+
+ formatter = _formatters[type(arg)]
+ processed_args.append(formatter(arg))
+
+ return sql % tuple(processed_args)
+
+
+def tquery(query, con=None):
+ """Replace removed sql.tquery function"""
+ with sql.pandasSQL_builder(con) as pandas_sql:
+ res = pandas_sql.execute(query).fetchall()
+ return None if res is None else list(res)
+
+
+def test_xsqlite_basic(sqlite_buildin):
+ frame = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ assert sql.to_sql(frame, name="test_table", con=sqlite_buildin, index=False) == 10
+ result = sql.read_sql("select * from test_table", sqlite_buildin)
+
+ # HACK! Change this once indexes are handled properly.
+ result.index = frame.index
+
+ expected = frame
+ tm.assert_frame_equal(result, frame)
+
+ frame["txt"] = ["a"] * len(frame)
+ frame2 = frame.copy()
+ new_idx = Index(np.arange(len(frame2)), dtype=np.int64) + 10
+ frame2["Idx"] = new_idx.copy()
+ assert sql.to_sql(frame2, name="test_table2", con=sqlite_buildin, index=False) == 10
+ result = sql.read_sql("select * from test_table2", sqlite_buildin, index_col="Idx")
+ expected = frame.copy()
+ expected.index = new_idx
+ expected.index.name = "Idx"
+ tm.assert_frame_equal(expected, result)
+
+
+def test_xsqlite_write_row_by_row(sqlite_buildin):
+ frame = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ frame.iloc[0, 0] = np.nan
+ create_sql = sql.get_schema(frame, "test")
+ cur = sqlite_buildin.cursor()
+ cur.execute(create_sql)
+
+ ins = "INSERT INTO test VALUES (%s, %s, %s, %s)"
+ for _, row in frame.iterrows():
+ fmt_sql = format_query(ins, *row)
+ tquery(fmt_sql, con=sqlite_buildin)
+
+ sqlite_buildin.commit()
+
+ result = sql.read_sql("select * from test", con=sqlite_buildin)
+ result.index = frame.index
+ tm.assert_frame_equal(result, frame, rtol=1e-3)
+
+
+def test_xsqlite_execute(sqlite_buildin):
+ frame = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ create_sql = sql.get_schema(frame, "test")
+ cur = sqlite_buildin.cursor()
+ cur.execute(create_sql)
+ ins = "INSERT INTO test VALUES (?, ?, ?, ?)"
+
+ row = frame.iloc[0]
+ with sql.pandasSQL_builder(sqlite_buildin) as pandas_sql:
+ pandas_sql.execute(ins, tuple(row))
+ sqlite_buildin.commit()
+
+ result = sql.read_sql("select * from test", sqlite_buildin)
+ result.index = frame.index[:1]
+ tm.assert_frame_equal(result, frame[:1])
+
+
+def test_xsqlite_schema(sqlite_buildin):
+ frame = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ create_sql = sql.get_schema(frame, "test")
+ lines = create_sql.splitlines()
+ for line in lines:
+ tokens = line.split(" ")
+ if len(tokens) == 2 and tokens[0] == "A":
+ assert tokens[1] == "DATETIME"
+
+ create_sql = sql.get_schema(frame, "test", keys=["A", "B"])
+ lines = create_sql.splitlines()
+ assert 'PRIMARY KEY ("A", "B")' in create_sql
+ cur = sqlite_buildin.cursor()
+ cur.execute(create_sql)
+
+
+def test_xsqlite_execute_fail(sqlite_buildin):
+ create_sql = """
+ CREATE TABLE test
+ (
+ a TEXT,
+ b TEXT,
+ c REAL,
+ PRIMARY KEY (a, b)
+ );
+ """
+ cur = sqlite_buildin.cursor()
+ cur.execute(create_sql)
+
+ with sql.pandasSQL_builder(sqlite_buildin) as pandas_sql:
+ pandas_sql.execute("INSERT INTO test VALUES('foo', 'bar', 1.234)")
+ pandas_sql.execute("INSERT INTO test VALUES('foo', 'baz', 2.567)")
+
+ with pytest.raises(sql.DatabaseError, match="Execution failed on sql"):
+ pandas_sql.execute("INSERT INTO test VALUES('foo', 'bar', 7)")
+
+
+def test_xsqlite_execute_closed_connection():
+ create_sql = """
+ CREATE TABLE test
+ (
+ a TEXT,
+ b TEXT,
+ c REAL,
+ PRIMARY KEY (a, b)
+ );
+ """
+ with contextlib.closing(sqlite3.connect(":memory:")) as conn:
+ cur = conn.cursor()
+ cur.execute(create_sql)
+
+ with sql.pandasSQL_builder(conn) as pandas_sql:
+ pandas_sql.execute("INSERT INTO test VALUES('foo', 'bar', 1.234)")
+
+ msg = "Cannot operate on a closed database."
+ with pytest.raises(sqlite3.ProgrammingError, match=msg):
+ tquery("select * from test", con=conn)
+
+
+def test_xsqlite_keyword_as_column_names(sqlite_buildin):
+ df = DataFrame({"From": np.ones(5)})
+ assert sql.to_sql(df, con=sqlite_buildin, name="testkeywords", index=False) == 5
+
+
+def test_xsqlite_onecolumn_of_integer(sqlite_buildin):
+ # GH 3628
+ # a column_of_integers dataframe should transfer well to sql
+
+ mono_df = DataFrame([1, 2], columns=["c0"])
+ assert sql.to_sql(mono_df, con=sqlite_buildin, name="mono_df", index=False) == 2
+ # computing the sum via sql
+ con_x = sqlite_buildin
+ the_sum = sum(my_c0[0] for my_c0 in con_x.execute("select * from mono_df"))
+ # it should not fail, and gives 3 ( Issue #3628 )
+ assert the_sum == 3
+
+ result = sql.read_sql("select * from mono_df", con_x)
+ tm.assert_frame_equal(result, mono_df)
+
+
+def test_xsqlite_if_exists(sqlite_buildin):
+ df_if_exists_1 = DataFrame({"col1": [1, 2], "col2": ["A", "B"]})
+ df_if_exists_2 = DataFrame({"col1": [3, 4, 5], "col2": ["C", "D", "E"]})
+ table_name = "table_if_exists"
+ sql_select = f"SELECT * FROM {table_name}"
+
+ msg = "'notvalidvalue' is not valid for if_exists"
+ with pytest.raises(ValueError, match=msg):
+ sql.to_sql(
+ frame=df_if_exists_1,
+ con=sqlite_buildin,
+ name=table_name,
+ if_exists="notvalidvalue",
+ )
+ drop_table(table_name, sqlite_buildin)
+
+ # test if_exists='fail'
+ sql.to_sql(
+ frame=df_if_exists_1, con=sqlite_buildin, name=table_name, if_exists="fail"
+ )
+ msg = "Table 'table_if_exists' already exists"
+ with pytest.raises(ValueError, match=msg):
+ sql.to_sql(
+ frame=df_if_exists_1,
+ con=sqlite_buildin,
+ name=table_name,
+ if_exists="fail",
+ )
+ # test if_exists='replace'
+ sql.to_sql(
+ frame=df_if_exists_1,
+ con=sqlite_buildin,
+ name=table_name,
+ if_exists="replace",
+ index=False,
+ )
+ assert tquery(sql_select, con=sqlite_buildin) == [(1, "A"), (2, "B")]
+ assert (
+ sql.to_sql(
+ frame=df_if_exists_2,
+ con=sqlite_buildin,
+ name=table_name,
+ if_exists="replace",
+ index=False,
+ )
+ == 3
+ )
+ assert tquery(sql_select, con=sqlite_buildin) == [(3, "C"), (4, "D"), (5, "E")]
+ drop_table(table_name, sqlite_buildin)
+
+ # test if_exists='append'
+ assert (
+ sql.to_sql(
+ frame=df_if_exists_1,
+ con=sqlite_buildin,
+ name=table_name,
+ if_exists="fail",
+ index=False,
+ )
+ == 2
+ )
+ assert tquery(sql_select, con=sqlite_buildin) == [(1, "A"), (2, "B")]
+ assert (
+ sql.to_sql(
+ frame=df_if_exists_2,
+ con=sqlite_buildin,
+ name=table_name,
+ if_exists="append",
+ index=False,
+ )
+ == 3
+ )
+ assert tquery(sql_select, con=sqlite_buildin) == [
+ (1, "A"),
+ (2, "B"),
+ (3, "C"),
+ (4, "D"),
+ (5, "E"),
+ ]
+ drop_table(table_name, sqlite_buildin)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_stata.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_stata.py
new file mode 100644
index 0000000000000000000000000000000000000000..f69ec1f6105605f80eadccd0072c7c0683158b49
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/test_stata.py
@@ -0,0 +1,2624 @@
+import bz2
+import datetime as dt
+from datetime import datetime
+import gzip
+import io
+import itertools
+import os
+import string
+import struct
+import tarfile
+import zipfile
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import CategoricalDtype
+import pandas._testing as tm
+from pandas.core.frame import (
+ DataFrame,
+ Series,
+)
+
+from pandas.io.parsers import read_csv
+from pandas.io.stata import (
+ CategoricalConversionWarning,
+ InvalidColumnName,
+ PossiblePrecisionLoss,
+ StataMissingValue,
+ StataReader,
+ StataWriter,
+ StataWriterUTF8,
+ ValueLabelTypeMismatch,
+ read_stata,
+)
+
+
+@pytest.fixture
+def mixed_frame():
+ return DataFrame(
+ {
+ "a": [1, 2, 3, 4],
+ "b": [1.0, 3.0, 27.0, 81.0],
+ "c": ["Atlanta", "Birmingham", "Cincinnati", "Detroit"],
+ }
+ )
+
+
+@pytest.fixture
+def parsed_114(datapath):
+ dta14_114 = datapath("io", "data", "stata", "stata5_114.dta")
+ parsed_114 = read_stata(dta14_114, convert_dates=True)
+ parsed_114.index.name = "index"
+ return parsed_114
+
+
+class TestStata:
+ def read_dta(self, file):
+ # Legacy default reader configuration
+ return read_stata(file, convert_dates=True)
+
+ def read_csv(self, file):
+ return read_csv(file, parse_dates=True)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_read_empty_dta(self, version, temp_file):
+ empty_ds = DataFrame(columns=["unit"])
+ # GH 7369, make sure can read a 0-obs dta file
+ path = temp_file
+ empty_ds.to_stata(path, write_index=False, version=version)
+ empty_ds2 = read_stata(path)
+ tm.assert_frame_equal(empty_ds, empty_ds2)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_read_empty_dta_with_dtypes(self, version, temp_file):
+ # GH 46240
+ # Fixing above bug revealed that types are not correctly preserved when
+ # writing empty DataFrames
+ empty_df_typed = DataFrame(
+ {
+ "i8": np.array([0], dtype=np.int8),
+ "i16": np.array([0], dtype=np.int16),
+ "i32": np.array([0], dtype=np.int32),
+ "i64": np.array([0], dtype=np.int64),
+ "u8": np.array([0], dtype=np.uint8),
+ "u16": np.array([0], dtype=np.uint16),
+ "u32": np.array([0], dtype=np.uint32),
+ "u64": np.array([0], dtype=np.uint64),
+ "f32": np.array([0], dtype=np.float32),
+ "f64": np.array([0], dtype=np.float64),
+ }
+ )
+ # GH 7369, make sure can read a 0-obs dta file
+ path = temp_file
+ empty_df_typed.to_stata(path, write_index=False, version=version)
+ empty_reread = read_stata(path)
+
+ expected = empty_df_typed
+ # No uint# support. Downcast since values in range for int#
+ expected["u8"] = expected["u8"].astype(np.int8)
+ expected["u16"] = expected["u16"].astype(np.int16)
+ expected["u32"] = expected["u32"].astype(np.int32)
+ # No int64 supported at all. Downcast since values in range for int32
+ expected["u64"] = expected["u64"].astype(np.int32)
+ expected["i64"] = expected["i64"].astype(np.int32)
+
+ tm.assert_frame_equal(expected, empty_reread)
+ tm.assert_series_equal(expected.dtypes, empty_reread.dtypes)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_read_index_col_none(self, version, temp_file):
+ df = DataFrame({"a": range(5), "b": ["b1", "b2", "b3", "b4", "b5"]})
+ # GH 7369, make sure can read a 0-obs dta file
+ path = temp_file
+ df.to_stata(path, write_index=False, version=version)
+ read_df = read_stata(path)
+
+ assert isinstance(read_df.index, pd.RangeIndex)
+ expected = df
+ expected["a"] = expected["a"].astype(np.int32)
+ tm.assert_frame_equal(read_df, expected, check_index_type=True)
+
+ @pytest.mark.parametrize(
+ "version", [102, 103, 104, 105, 108, 110, 111, 113, 114, 115, 117, 118, 119]
+ )
+ def test_read_dta1(self, version, datapath):
+ file = datapath("io", "data", "stata", f"stata1_{version}.dta")
+ parsed = self.read_dta(file)
+
+ # Pandas uses np.nan as missing value.
+ # Thus, all columns will be of type float, regardless of their name.
+ expected = DataFrame(
+ [(np.nan, np.nan, np.nan, np.nan, np.nan)],
+ columns=["float_miss", "double_miss", "byte_miss", "int_miss", "long_miss"],
+ )
+
+ # this is an oddity as really the nan should be float64, but
+ # the casting doesn't fail so need to match stata here
+ expected["float_miss"] = expected["float_miss"].astype(np.float32)
+
+ # Column names too long for older Stata formats
+ if version <= 108:
+ expected = expected.rename(
+ columns={
+ "float_miss": "f_miss",
+ "double_miss": "d_miss",
+ "byte_miss": "b_miss",
+ "int_miss": "i_miss",
+ "long_miss": "l_miss",
+ }
+ )
+
+ tm.assert_frame_equal(parsed, expected)
+
+ def test_read_dta2(self, datapath):
+ expected = DataFrame.from_records(
+ [
+ (
+ datetime(2006, 11, 19, 23, 13, 20),
+ 1479596223000,
+ datetime(2010, 1, 20),
+ datetime(2010, 1, 8),
+ datetime(2010, 1, 1),
+ datetime(1974, 7, 1),
+ datetime(2010, 1, 1),
+ datetime(2010, 1, 1),
+ ),
+ (
+ datetime(1959, 12, 31, 20, 3, 20),
+ -1479590,
+ datetime(1953, 10, 2),
+ datetime(1948, 6, 10),
+ datetime(1955, 1, 1),
+ datetime(1955, 7, 1),
+ datetime(1955, 1, 1),
+ datetime(2, 1, 1),
+ ),
+ (pd.NaT, pd.NaT, pd.NaT, pd.NaT, pd.NaT, pd.NaT, pd.NaT, pd.NaT),
+ ],
+ columns=[
+ "datetime_c",
+ "datetime_big_c",
+ "date",
+ "weekly_date",
+ "monthly_date",
+ "quarterly_date",
+ "half_yearly_date",
+ "yearly_date",
+ ],
+ )
+ # TODO(GH#55564): just pass M8[s] to the constructor
+ expected["datetime_c"] = expected["datetime_c"].astype("M8[ms]")
+ expected["date"] = expected["date"].astype("M8[s]")
+ expected["weekly_date"] = expected["weekly_date"].astype("M8[s]")
+ expected["monthly_date"] = expected["monthly_date"].astype("M8[s]")
+ expected["quarterly_date"] = expected["quarterly_date"].astype("M8[s]")
+ expected["half_yearly_date"] = expected["half_yearly_date"].astype("M8[s]")
+ expected["yearly_date"] = expected["yearly_date"].astype("M8[s]")
+
+ path1 = datapath("io", "data", "stata", "stata2_114.dta")
+ path2 = datapath("io", "data", "stata", "stata2_115.dta")
+ path3 = datapath("io", "data", "stata", "stata2_117.dta")
+
+ msg = "Leaving in Stata Internal Format"
+ with tm.assert_produces_warning(UserWarning, match=msg):
+ parsed_114 = self.read_dta(path1)
+ with tm.assert_produces_warning(UserWarning, match=msg):
+ parsed_115 = self.read_dta(path2)
+ with tm.assert_produces_warning(UserWarning, match=msg):
+ parsed_117 = self.read_dta(path3)
+ # FIXME: don't leave commented-out
+ # 113 is buggy due to limits of date format support in Stata
+ # parsed_113 = self.read_dta(
+ # datapath("io", "data", "stata", "stata2_113.dta")
+ # )
+
+ # FIXME: don't leave commented-out
+ # buggy test because of the NaT comparison on certain platforms
+ # Format 113 test fails since it does not support tc and tC formats
+ # tm.assert_frame_equal(parsed_113, expected)
+ tm.assert_frame_equal(parsed_114, expected)
+ tm.assert_frame_equal(parsed_115, expected)
+ tm.assert_frame_equal(parsed_117, expected)
+
+ @pytest.mark.parametrize(
+ "file", ["stata3_113", "stata3_114", "stata3_115", "stata3_117"]
+ )
+ def test_read_dta3(self, file, datapath):
+ file = datapath("io", "data", "stata", f"{file}.dta")
+ parsed = self.read_dta(file)
+
+ # match stata here
+ expected = self.read_csv(datapath("io", "data", "stata", "stata3.csv"))
+ expected = expected.astype(np.float32)
+ expected["year"] = expected["year"].astype(np.int16)
+ expected["quarter"] = expected["quarter"].astype(np.int8)
+
+ tm.assert_frame_equal(parsed, expected)
+
+ @pytest.mark.parametrize("version", [110, 111, 113, 114, 115, 117])
+ def test_read_dta4(self, version, datapath):
+ file = datapath("io", "data", "stata", f"stata4_{version}.dta")
+ parsed = self.read_dta(file)
+
+ expected = DataFrame.from_records(
+ [
+ ["one", "ten", "one", "one", "one"],
+ ["two", "nine", "two", "two", "two"],
+ ["three", "eight", "three", "three", "three"],
+ ["four", "seven", 4, "four", "four"],
+ ["five", "six", 5, np.nan, "five"],
+ ["six", "five", 6, np.nan, "six"],
+ ["seven", "four", 7, np.nan, "seven"],
+ ["eight", "three", 8, np.nan, "eight"],
+ ["nine", "two", 9, np.nan, "nine"],
+ ["ten", "one", "ten", np.nan, "ten"],
+ ],
+ columns=[
+ "fully_labeled",
+ "fully_labeled2",
+ "incompletely_labeled",
+ "labeled_with_missings",
+ "float_labelled",
+ ],
+ )
+
+ # these are all categoricals
+ for col in expected:
+ orig = expected[col].copy()
+
+ categories = np.asarray(expected["fully_labeled"][orig.notna()])
+ if col == "incompletely_labeled":
+ categories = orig
+
+ cat = orig.astype("category")._values
+ cat = cat.set_categories(categories, ordered=True)
+ cat.categories.rename(None, inplace=True)
+
+ expected[col] = cat
+
+ # stata doesn't save .category metadata
+ tm.assert_frame_equal(parsed, expected)
+
+ @pytest.mark.parametrize("version", [102, 103, 104, 105, 108])
+ def test_readold_dta4(self, version, datapath):
+ # This test is the same as test_read_dta4 above except that the columns
+ # had to be renamed to match the restrictions in older file format
+ file = datapath("io", "data", "stata", f"stata4_{version}.dta")
+ parsed = self.read_dta(file)
+
+ expected = DataFrame.from_records(
+ [
+ ["one", "ten", "one", "one", "one"],
+ ["two", "nine", "two", "two", "two"],
+ ["three", "eight", "three", "three", "three"],
+ ["four", "seven", 4, "four", "four"],
+ ["five", "six", 5, np.nan, "five"],
+ ["six", "five", 6, np.nan, "six"],
+ ["seven", "four", 7, np.nan, "seven"],
+ ["eight", "three", 8, np.nan, "eight"],
+ ["nine", "two", 9, np.nan, "nine"],
+ ["ten", "one", "ten", np.nan, "ten"],
+ ],
+ columns=[
+ "fulllab",
+ "fulllab2",
+ "incmplab",
+ "misslab",
+ "floatlab",
+ ],
+ )
+
+ # these are all categoricals
+ for col in expected:
+ orig = expected[col].copy()
+
+ categories = np.asarray(expected["fulllab"][orig.notna()])
+ if col == "incmplab":
+ categories = orig
+
+ cat = orig.astype("category")._values
+ cat = cat.set_categories(categories, ordered=True)
+ cat.categories.rename(None, inplace=True)
+
+ expected[col] = cat
+
+ # stata doesn't save .category metadata
+ tm.assert_frame_equal(parsed, expected)
+
+ # File containing strls
+ @pytest.mark.parametrize(
+ "file",
+ [
+ "stata12_117",
+ "stata12_be_117",
+ "stata12_118",
+ "stata12_be_118",
+ "stata12_119",
+ "stata12_be_119",
+ ],
+ )
+ def test_read_dta_strl(self, file, datapath):
+ parsed = self.read_dta(datapath("io", "data", "stata", f"{file}.dta"))
+ expected = DataFrame.from_records(
+ [
+ [1, "abc", "abcdefghi"],
+ [3, "cba", "qwertywertyqwerty"],
+ [93, "", "strl"],
+ ],
+ columns=["x", "y", "z"],
+ )
+
+ tm.assert_frame_equal(parsed, expected, check_dtype=False)
+
+ # 117 is not included in this list as it uses ASCII strings
+ @pytest.mark.parametrize(
+ "file",
+ [
+ "stata14_118",
+ "stata14_be_118",
+ "stata14_119",
+ "stata14_be_119",
+ ],
+ )
+ def test_read_dta118_119(self, file, datapath):
+ parsed_118 = self.read_dta(datapath("io", "data", "stata", f"{file}.dta"))
+ parsed_118["Bytes"] = parsed_118["Bytes"].astype("O")
+ expected = DataFrame.from_records(
+ [
+ ["Cat", "Bogota", "Bogotá", 1, 1.0, "option b Ünicode", 1.0],
+ ["Dog", "Boston", "Uzunköprü", np.nan, np.nan, np.nan, np.nan],
+ ["Plane", "Rome", "Tromsø", 0, 0.0, "option a", 0.0],
+ ["Potato", "Tokyo", "Elâzığ", -4, 4.0, 4, 4], # noqa: RUF001
+ ["", "", "", 0, 0.3332999, "option a", 1 / 3.0],
+ ],
+ columns=[
+ "Things",
+ "Cities",
+ "Unicode_Cities_Strl",
+ "Ints",
+ "Floats",
+ "Bytes",
+ "Longs",
+ ],
+ )
+ expected["Floats"] = expected["Floats"].astype(np.float32)
+ for col in parsed_118.columns:
+ tm.assert_almost_equal(parsed_118[col], expected[col])
+
+ with StataReader(datapath("io", "data", "stata", f"{file}.dta")) as rdr:
+ vl = rdr.variable_labels()
+ vl_expected = {
+ "Unicode_Cities_Strl": "Here are some strls with Ünicode chars",
+ "Longs": "long data",
+ "Things": "Here are some things",
+ "Bytes": "byte data",
+ "Ints": "int data",
+ "Cities": "Here are some cities",
+ "Floats": "float data",
+ }
+ tm.assert_dict_equal(vl, vl_expected)
+
+ assert rdr.data_label == "This is a Ünicode data label"
+
+ def test_read_write_dta5(self, temp_file):
+ original = DataFrame(
+ [(np.nan, np.nan, np.nan, np.nan, np.nan)],
+ columns=["float_miss", "double_miss", "byte_miss", "int_miss", "long_miss"],
+ )
+ original.index.name = "index"
+
+ path = temp_file
+ original.to_stata(path, convert_dates=None)
+ written_and_read_again = self.read_dta(path)
+
+ expected = original
+ expected.index = expected.index.astype(np.int32)
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ def test_write_dta6(self, datapath, temp_file):
+ original = self.read_csv(datapath("io", "data", "stata", "stata3.csv"))
+ original.index.name = "index"
+ original.index = original.index.astype(np.int32)
+ original["year"] = original["year"].astype(np.int32)
+ original["quarter"] = original["quarter"].astype(np.int32)
+
+ path = temp_file
+ original.to_stata(path, convert_dates=None)
+ written_and_read_again = self.read_dta(path)
+ tm.assert_frame_equal(
+ written_and_read_again.set_index("index"),
+ original,
+ check_index_type=False,
+ )
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_read_write_dta10(self, version, temp_file, using_infer_string):
+ original = DataFrame(
+ data=[["string", "object", 1, 1.1, np.datetime64("2003-12-25")]],
+ columns=["string", "object", "integer", "floating", "datetime"],
+ )
+ original["object"] = Series(original["object"], dtype=object)
+ original.index.name = "index"
+ original.index = original.index.astype(np.int32)
+ original["integer"] = original["integer"].astype(np.int32)
+
+ path = temp_file
+ original.to_stata(path, convert_dates={"datetime": "tc"}, version=version)
+ written_and_read_again = self.read_dta(path)
+
+ expected = original.copy()
+ # "tc" convert_dates means we store in ms
+ expected["datetime"] = expected["datetime"].astype("M8[ms]")
+ if using_infer_string:
+ expected["object"] = expected["object"].astype("str")
+
+ tm.assert_frame_equal(
+ written_and_read_again.set_index("index"),
+ expected,
+ )
+
+ def test_stata_doc_examples(self, temp_file):
+ path = temp_file
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)), columns=list("AB")
+ )
+ df.to_stata(path)
+
+ def test_write_preserves_original(self, temp_file):
+ # 9795
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)), columns=list("abcd")
+ )
+ df.loc[2, "a":"c"] = np.nan
+ df_copy = df.copy()
+ path = temp_file
+ df.to_stata(path, write_index=False)
+ tm.assert_frame_equal(df, df_copy)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_encoding(self, version, datapath, temp_file):
+ # GH 4626, proper encoding handling
+ raw = read_stata(datapath("io", "data", "stata", "stata1_encoding.dta"))
+ encoded = read_stata(datapath("io", "data", "stata", "stata1_encoding.dta"))
+ result = encoded.kreis1849[0]
+
+ expected = raw.kreis1849[0]
+ assert result == expected
+ assert isinstance(result, str)
+
+ path = temp_file
+ encoded.to_stata(path, write_index=False, version=version)
+ reread_encoded = read_stata(path)
+ tm.assert_frame_equal(encoded, reread_encoded)
+
+ def test_read_write_dta11(self, temp_file):
+ original = DataFrame(
+ [(1, 2, 3, 4)],
+ columns=[
+ "good",
+ "b\u00e4d",
+ "8number",
+ "astringwithmorethan32characters______",
+ ],
+ )
+ formatted = DataFrame(
+ [(1, 2, 3, 4)],
+ columns=["good", "b_d", "_8number", "astringwithmorethan32characters_"],
+ )
+ formatted.index.name = "index"
+ formatted = formatted.astype(np.int32)
+
+ path = temp_file
+ msg = "Not all pandas column names were valid Stata variable names"
+ with tm.assert_produces_warning(InvalidColumnName, match=msg):
+ original.to_stata(path, convert_dates=None)
+
+ written_and_read_again = self.read_dta(path)
+
+ expected = formatted
+ expected.index = expected.index.astype(np.int32)
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_read_write_dta12(self, version, temp_file):
+ original = DataFrame(
+ [(1, 2, 3, 4, 5, 6)],
+ columns=[
+ "astringwithmorethan32characters_1",
+ "astringwithmorethan32characters_2",
+ "+",
+ "-",
+ "short",
+ "delete",
+ ],
+ )
+ formatted = DataFrame(
+ [(1, 2, 3, 4, 5, 6)],
+ columns=[
+ "astringwithmorethan32characters_",
+ "_0astringwithmorethan32character",
+ "_",
+ "_1_",
+ "_short",
+ "_delete",
+ ],
+ )
+ formatted.index.name = "index"
+ formatted = formatted.astype(np.int32)
+
+ path = temp_file
+ msg = "Not all pandas column names were valid Stata variable names"
+ with tm.assert_produces_warning(InvalidColumnName, match=msg):
+ original.to_stata(path, convert_dates=None, version=version)
+ # should get a warning for that format.
+
+ written_and_read_again = self.read_dta(path)
+
+ expected = formatted
+ expected.index = expected.index.astype(np.int32)
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ def test_read_write_dta13(self, temp_file):
+ s1 = Series(2**9, dtype=np.int16)
+ s2 = Series(2**17, dtype=np.int32)
+ s3 = Series(2**33, dtype=np.int64)
+ original = DataFrame({"int16": s1, "int32": s2, "int64": s3})
+ original.index.name = "index"
+
+ formatted = original
+ formatted["int64"] = formatted["int64"].astype(np.float64)
+
+ path = temp_file
+ original.to_stata(path)
+ written_and_read_again = self.read_dta(path)
+
+ expected = formatted
+ expected.index = expected.index.astype(np.int32)
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ @pytest.mark.parametrize(
+ "file", ["stata5_113", "stata5_114", "stata5_115", "stata5_117"]
+ )
+ def test_read_write_reread_dta14(
+ self, file, parsed_114, version, datapath, temp_file
+ ):
+ file = datapath("io", "data", "stata", f"{file}.dta")
+ parsed = self.read_dta(file)
+ parsed.index.name = "index"
+
+ tm.assert_frame_equal(parsed_114, parsed)
+
+ path = temp_file
+ parsed_114.to_stata(path, convert_dates={"date_td": "td"}, version=version)
+ written_and_read_again = self.read_dta(path)
+
+ expected = parsed_114.copy()
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ @pytest.mark.parametrize(
+ "file", ["stata6_113", "stata6_114", "stata6_115", "stata6_117"]
+ )
+ def test_read_write_reread_dta15(self, file, datapath):
+ expected = self.read_csv(datapath("io", "data", "stata", "stata6.csv"))
+ expected["byte_"] = expected["byte_"].astype(np.int8)
+ expected["int_"] = expected["int_"].astype(np.int16)
+ expected["long_"] = expected["long_"].astype(np.int32)
+ expected["float_"] = expected["float_"].astype(np.float32)
+ expected["double_"] = expected["double_"].astype(np.float64)
+
+ # TODO(GH#55564): directly cast to M8[s]
+ arr = expected["date_td"].astype("Period[D]")._values.asfreq("s", how="S")
+ expected["date_td"] = arr.view("M8[s]")
+
+ file = datapath("io", "data", "stata", f"{file}.dta")
+ parsed = self.read_dta(file)
+
+ tm.assert_frame_equal(expected, parsed)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_timestamp_and_label(self, version, temp_file):
+ original = DataFrame([(1,)], columns=["variable"])
+ time_stamp = datetime(2000, 2, 29, 14, 21)
+ data_label = "This is a data file."
+ path = temp_file
+ original.to_stata(
+ path, time_stamp=time_stamp, data_label=data_label, version=version
+ )
+
+ with StataReader(path) as reader:
+ assert reader.time_stamp == "29 Feb 2000 14:21"
+ assert reader.data_label == data_label
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_invalid_timestamp(self, version, temp_file):
+ original = DataFrame([(1,)], columns=["variable"])
+ time_stamp = "01 Jan 2000, 00:00:00"
+ path = temp_file
+ msg = "time_stamp should be datetime type"
+ with pytest.raises(ValueError, match=msg):
+ original.to_stata(path, time_stamp=time_stamp, version=version)
+ assert not os.path.isfile(path)
+
+ def test_numeric_column_names(self, temp_file):
+ original = DataFrame(np.reshape(np.arange(25.0), (5, 5)))
+ original.index.name = "index"
+ path = temp_file
+ # should get a warning for that format.
+ msg = "Not all pandas column names were valid Stata variable names"
+ with tm.assert_produces_warning(InvalidColumnName, match=msg):
+ original.to_stata(path)
+
+ written_and_read_again = self.read_dta(path)
+
+ written_and_read_again = written_and_read_again.set_index("index")
+ columns = list(written_and_read_again.columns)
+ convert_col_name = lambda x: int(x[1])
+ written_and_read_again.columns = map(convert_col_name, columns)
+
+ expected = original
+ tm.assert_frame_equal(expected, written_and_read_again)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_nan_to_missing_value(self, version, temp_file):
+ s1 = Series(np.arange(4.0), dtype=np.float32)
+ s2 = Series(np.arange(4.0), dtype=np.float64)
+ s1[::2] = np.nan
+ s2[1::2] = np.nan
+ original = DataFrame({"s1": s1, "s2": s2})
+ original.index.name = "index"
+
+ path = temp_file
+ original.to_stata(path, version=version)
+ written_and_read_again = self.read_dta(path)
+
+ written_and_read_again = written_and_read_again.set_index("index")
+ expected = original
+ tm.assert_frame_equal(written_and_read_again, expected)
+
+ def test_no_index(self, temp_file):
+ columns = ["x", "y"]
+ original = DataFrame(np.reshape(np.arange(10.0), (5, 2)), columns=columns)
+ original.index.name = "index_not_written"
+ path = temp_file
+ original.to_stata(path, write_index=False)
+ written_and_read_again = self.read_dta(path)
+ with pytest.raises(KeyError, match=original.index.name):
+ written_and_read_again["index_not_written"]
+
+ def test_string_no_dates(self, temp_file):
+ s1 = Series(["a", "A longer string"])
+ s2 = Series([1.0, 2.0], dtype=np.float64)
+ original = DataFrame({"s1": s1, "s2": s2})
+ original.index.name = "index"
+ path = temp_file
+ original.to_stata(path)
+ written_and_read_again = self.read_dta(path)
+
+ expected = original
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ def test_large_value_conversion(self, temp_file):
+ s0 = Series([1, 99], dtype=np.int8)
+ s1 = Series([1, 127], dtype=np.int8)
+ s2 = Series([1, 2**15 - 1], dtype=np.int16)
+ s3 = Series([1, 2**63 - 1], dtype=np.int64)
+ original = DataFrame({"s0": s0, "s1": s1, "s2": s2, "s3": s3})
+ original.index.name = "index"
+ path = temp_file
+ with tm.assert_produces_warning(PossiblePrecisionLoss, match="from int64 to"):
+ original.to_stata(path)
+
+ written_and_read_again = self.read_dta(path)
+
+ modified = original
+ modified["s1"] = Series(modified["s1"], dtype=np.int16)
+ modified["s2"] = Series(modified["s2"], dtype=np.int32)
+ modified["s3"] = Series(modified["s3"], dtype=np.float64)
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), modified)
+
+ def test_dates_invalid_column(self, temp_file):
+ original = DataFrame([datetime(2006, 11, 19, 23, 13, 20)])
+ original.index.name = "index"
+ path = temp_file
+ msg = "Not all pandas column names were valid Stata variable names"
+ with tm.assert_produces_warning(InvalidColumnName, match=msg):
+ original.to_stata(path, convert_dates={0: "tc"})
+
+ written_and_read_again = self.read_dta(path)
+
+ expected = original.copy()
+ expected.columns = ["_0"]
+ expected.index = original.index.astype(np.int32)
+ expected["_0"] = expected["_0"].astype("M8[ms]")
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ def test_105(self, datapath):
+ # Data obtained from:
+ # http://go.worldbank.org/ZXY29PVJ21
+ dpath = datapath("io", "data", "stata", "S4_EDUC1.dta")
+ df = read_stata(dpath)
+ df0 = [[1, 1, 3, -2], [2, 1, 2, -2], [4, 1, 1, -2]]
+ df0 = DataFrame(df0)
+ df0.columns = ["clustnum", "pri_schl", "psch_num", "psch_dis"]
+ df0["clustnum"] = df0["clustnum"].astype(np.int16)
+ df0["pri_schl"] = df0["pri_schl"].astype(np.int8)
+ df0["psch_num"] = df0["psch_num"].astype(np.int8)
+ df0["psch_dis"] = df0["psch_dis"].astype(np.float32)
+ tm.assert_frame_equal(df.head(3), df0)
+
+ def test_value_labels_old_format(self, datapath):
+ # GH 19417
+ #
+ # Test that value_labels() returns an empty dict if the file format
+ # predates supporting value labels.
+ dpath = datapath("io", "data", "stata", "S4_EDUC1.dta")
+ with StataReader(dpath) as reader:
+ assert reader.value_labels() == {}
+
+ def test_date_export_formats(self, temp_file):
+ columns = ["tc", "td", "tw", "tm", "tq", "th", "ty"]
+ conversions = {c: c for c in columns}
+ data = [datetime(2006, 11, 20, 23, 13, 20)] * len(columns)
+ original = DataFrame([data], columns=columns)
+ original.index.name = "index"
+ expected_values = [
+ datetime(2006, 11, 20, 23, 13, 20), # Time
+ datetime(2006, 11, 20), # Day
+ datetime(2006, 11, 19), # Week
+ datetime(2006, 11, 1), # Month
+ datetime(2006, 10, 1), # Quarter year
+ datetime(2006, 7, 1), # Half year
+ datetime(2006, 1, 1),
+ ] # Year
+
+ expected = DataFrame(
+ [expected_values],
+ index=pd.Index([0], dtype=np.int32, name="index"),
+ columns=columns,
+ dtype="M8[s]",
+ )
+ expected["tc"] = expected["tc"].astype("M8[ms]")
+
+ path = temp_file
+ original.to_stata(path, convert_dates=conversions)
+ written_and_read_again = self.read_dta(path)
+
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ def test_write_missing_strings(self, temp_file):
+ original = DataFrame([["1"], [None]], columns=["foo"])
+
+ expected = DataFrame(
+ [["1"], [""]],
+ index=pd.RangeIndex(2, name="index"),
+ columns=["foo"],
+ )
+
+ path = temp_file
+ original.to_stata(path)
+ written_and_read_again = self.read_dta(path)
+
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ @pytest.mark.parametrize("byteorder", [">", "<"])
+ def test_bool_uint(self, byteorder, version, temp_file):
+ s0 = Series([0, 1, True], dtype=np.bool_)
+ s1 = Series([0, 1, 100], dtype=np.uint8)
+ s2 = Series([0, 1, 255], dtype=np.uint8)
+ s3 = Series([0, 1, 2**15 - 100], dtype=np.uint16)
+ s4 = Series([0, 1, 2**16 - 1], dtype=np.uint16)
+ s5 = Series([0, 1, 2**31 - 100], dtype=np.uint32)
+ s6 = Series([0, 1, 2**32 - 1], dtype=np.uint32)
+
+ original = DataFrame(
+ {"s0": s0, "s1": s1, "s2": s2, "s3": s3, "s4": s4, "s5": s5, "s6": s6}
+ )
+ original.index.name = "index"
+
+ path = temp_file
+ original.to_stata(path, byteorder=byteorder, version=version)
+ written_and_read_again = self.read_dta(path)
+
+ written_and_read_again = written_and_read_again.set_index("index")
+
+ expected = original
+ expected_types = (
+ np.int8,
+ np.int8,
+ np.int16,
+ np.int16,
+ np.int32,
+ np.int32,
+ np.float64,
+ )
+ for c, t in zip(expected.columns, expected_types):
+ expected[c] = expected[c].astype(t)
+
+ tm.assert_frame_equal(written_and_read_again, expected)
+
+ def test_variable_labels(self, datapath):
+ with StataReader(datapath("io", "data", "stata", "stata7_115.dta")) as rdr:
+ sr_115 = rdr.variable_labels()
+ with StataReader(datapath("io", "data", "stata", "stata7_117.dta")) as rdr:
+ sr_117 = rdr.variable_labels()
+ keys = ("var1", "var2", "var3")
+ labels = ("label1", "label2", "label3")
+ for k, v in sr_115.items():
+ assert k in sr_117
+ assert v == sr_117[k]
+ assert k in keys
+ assert v in labels
+
+ def test_minimal_size_col(self, temp_file):
+ str_lens = (1, 100, 244)
+ s = {}
+ for str_len in str_lens:
+ s["s" + str(str_len)] = Series(
+ ["a" * str_len, "b" * str_len, "c" * str_len]
+ )
+ original = DataFrame(s)
+ path = temp_file
+ original.to_stata(path, write_index=False)
+
+ with StataReader(path) as sr:
+ sr._ensure_open() # The `_*list` variables are initialized here
+ for variable, fmt, typ in zip(sr._varlist, sr._fmtlist, sr._typlist):
+ assert int(variable[1:]) == int(fmt[1:-1])
+ assert int(variable[1:]) == typ
+
+ def test_excessively_long_string(self, temp_file):
+ str_lens = (1, 244, 500)
+ s = {}
+ for str_len in str_lens:
+ s["s" + str(str_len)] = Series(
+ ["a" * str_len, "b" * str_len, "c" * str_len]
+ )
+ original = DataFrame(s)
+ msg = (
+ r"Fixed width strings in Stata \.dta files are limited to 244 "
+ r"\(or fewer\)\ncharacters\. Column 's500' does not satisfy "
+ r"this restriction\. Use the\n'version=117' parameter to write "
+ r"the newer \(Stata 13 and later\) format\."
+ )
+ with pytest.raises(ValueError, match=msg):
+ path = temp_file
+ original.to_stata(path)
+
+ def test_missing_value_generator(self, temp_file):
+ types = ("b", "h", "l")
+ df = DataFrame([[0.0]], columns=["float_"])
+ path = temp_file
+ df.to_stata(path)
+ with StataReader(path) as rdr:
+ valid_range = rdr.VALID_RANGE
+ expected_values = ["." + chr(97 + i) for i in range(26)]
+ expected_values.insert(0, ".")
+ for t in types:
+ offset = valid_range[t][1]
+ for i in range(27):
+ val = StataMissingValue(offset + 1 + i)
+ assert val.string == expected_values[i]
+
+ # Test extremes for floats
+ val = StataMissingValue(struct.unpack(" DataFrame:
+ """
+ Emulate the categorical casting behavior we expect from roundtripping.
+ """
+ for col in from_frame:
+ ser = from_frame[col]
+ if isinstance(ser.dtype, CategoricalDtype):
+ cat = ser._values.remove_unused_categories()
+ if cat.categories.dtype == object:
+ categories = pd.Index._with_infer(
+ cat.categories._values, copy=False
+ )
+ cat = cat.set_categories(categories)
+ elif cat.categories.dtype == "string" and len(cat.categories) == 0:
+ # if the read categories are empty, it comes back as object dtype
+ categories = cat.categories.astype(object)
+ cat = cat.set_categories(categories)
+ from_frame[col] = cat
+ return from_frame
+
+ def test_iterator(self, datapath):
+ fname = datapath("io", "data", "stata", "stata12_117.dta")
+
+ parsed = read_stata(fname)
+ expected = parsed.iloc[0:5, :]
+
+ with read_stata(fname, iterator=True) as itr:
+ chunk = itr.read(5)
+ tm.assert_frame_equal(expected, chunk)
+
+ with read_stata(fname, chunksize=5) as itr:
+ chunk = next(itr)
+ tm.assert_frame_equal(expected, chunk)
+
+ with read_stata(fname, iterator=True) as itr:
+ chunk = itr.get_chunk(5)
+ tm.assert_frame_equal(expected, chunk)
+
+ with read_stata(fname, chunksize=5) as itr:
+ chunk = itr.get_chunk()
+ tm.assert_frame_equal(expected, chunk)
+
+ # GH12153
+ with read_stata(fname, chunksize=4) as itr:
+ from_chunks = pd.concat(itr)
+ tm.assert_frame_equal(parsed, from_chunks)
+
+ @pytest.mark.filterwarnings("ignore::UserWarning")
+ @pytest.mark.parametrize(
+ "file",
+ [
+ "stata2_115",
+ "stata3_115",
+ "stata4_115",
+ "stata5_115",
+ "stata6_115",
+ "stata7_115",
+ "stata8_115",
+ "stata9_115",
+ "stata10_115",
+ "stata11_115",
+ ],
+ )
+ @pytest.mark.parametrize("chunksize", [1, 2])
+ @pytest.mark.parametrize("convert_categoricals", [False, True])
+ @pytest.mark.parametrize("convert_dates", [False, True])
+ def test_read_chunks_115(
+ self, file, chunksize, convert_categoricals, convert_dates, datapath
+ ):
+ fname = datapath("io", "data", "stata", f"{file}.dta")
+
+ # Read the whole file
+ parsed = read_stata(
+ fname,
+ convert_categoricals=convert_categoricals,
+ convert_dates=convert_dates,
+ )
+
+ # Compare to what we get when reading by chunk
+ with read_stata(
+ fname,
+ iterator=True,
+ convert_dates=convert_dates,
+ convert_categoricals=convert_categoricals,
+ ) as itr:
+ pos = 0
+ for j in range(5):
+ try:
+ chunk = itr.read(chunksize)
+ except StopIteration:
+ break
+ from_frame = parsed.iloc[pos : pos + chunksize, :].copy()
+ from_frame = self._convert_categorical(from_frame)
+ tm.assert_frame_equal(
+ from_frame,
+ chunk,
+ check_dtype=False,
+ )
+ pos += chunksize
+
+ def test_read_chunks_columns(self, datapath):
+ fname = datapath("io", "data", "stata", "stata3_117.dta")
+ columns = ["quarter", "cpi", "m1"]
+ chunksize = 2
+
+ parsed = read_stata(fname, columns=columns)
+ with read_stata(fname, iterator=True) as itr:
+ pos = 0
+ for j in range(5):
+ chunk = itr.read(chunksize, columns=columns)
+ if chunk is None:
+ break
+ from_frame = parsed.iloc[pos : pos + chunksize, :]
+ tm.assert_frame_equal(from_frame, chunk, check_dtype=False)
+ pos += chunksize
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_write_variable_labels(self, version, mixed_frame, temp_file):
+ # GH 13631, add support for writing variable labels
+ mixed_frame.index.name = "index"
+ variable_labels = {"a": "City Rank", "b": "City Exponent", "c": "City"}
+ path = temp_file
+ mixed_frame.to_stata(path, variable_labels=variable_labels, version=version)
+ with StataReader(path) as sr:
+ read_labels = sr.variable_labels()
+ expected_labels = {
+ "index": "",
+ "a": "City Rank",
+ "b": "City Exponent",
+ "c": "City",
+ }
+ assert read_labels == expected_labels
+
+ variable_labels["index"] = "The Index"
+ path = temp_file
+ mixed_frame.to_stata(path, variable_labels=variable_labels, version=version)
+ with StataReader(path) as sr:
+ read_labels = sr.variable_labels()
+ assert read_labels == variable_labels
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_invalid_variable_labels(self, version, mixed_frame, temp_file):
+ mixed_frame.index.name = "index"
+ variable_labels = {"a": "very long" * 10, "b": "City Exponent", "c": "City"}
+ path = temp_file
+ msg = "Variable labels must be 80 characters or fewer"
+ with pytest.raises(ValueError, match=msg):
+ mixed_frame.to_stata(path, variable_labels=variable_labels, version=version)
+
+ @pytest.mark.parametrize("version", [114, 117])
+ def test_invalid_variable_label_encoding(self, version, mixed_frame, temp_file):
+ mixed_frame.index.name = "index"
+ variable_labels = {"a": "very long" * 10, "b": "City Exponent", "c": "City"}
+ variable_labels["a"] = "invalid character Œ"
+ path = temp_file
+ with pytest.raises(
+ ValueError, match="Variable labels must contain only characters"
+ ):
+ mixed_frame.to_stata(path, variable_labels=variable_labels, version=version)
+
+ def test_write_variable_label_errors(self, mixed_frame, temp_file):
+ values = ["\u03a1", "\u0391", "\u039d", "\u0394", "\u0391", "\u03a3"]
+
+ variable_labels_utf8 = {
+ "a": "City Rank",
+ "b": "City Exponent",
+ "c": "".join(values),
+ }
+
+ msg = (
+ "Variable labels must contain only characters that can be "
+ "encoded in Latin-1"
+ )
+ with pytest.raises(ValueError, match=msg):
+ path = temp_file
+ mixed_frame.to_stata(path, variable_labels=variable_labels_utf8)
+
+ variable_labels_long = {
+ "a": "City Rank",
+ "b": "City Exponent",
+ "c": "A very, very, very long variable label "
+ "that is too long for Stata which means "
+ "that it has more than 80 characters",
+ }
+
+ msg = "Variable labels must be 80 characters or fewer"
+ with pytest.raises(ValueError, match=msg):
+ path = temp_file
+ mixed_frame.to_stata(path, variable_labels=variable_labels_long)
+
+ def test_default_date_conversion(self, temp_file):
+ # GH 12259
+ dates = [
+ dt.datetime(1999, 12, 31, 12, 12, 12, 12000),
+ dt.datetime(2012, 12, 21, 12, 21, 12, 21000),
+ dt.datetime(1776, 7, 4, 7, 4, 7, 4000),
+ ]
+ original = DataFrame(
+ {
+ "nums": [1.0, 2.0, 3.0],
+ "strs": ["apple", "banana", "cherry"],
+ "dates": dates,
+ }
+ )
+
+ expected = original[:]
+ # "tc" for convert_dates below stores with "ms" resolution
+ expected["dates"] = expected["dates"].astype("M8[ms]")
+
+ path = temp_file
+ original.to_stata(path, write_index=False)
+ reread = read_stata(path, convert_dates=True)
+ tm.assert_frame_equal(expected, reread)
+
+ original.to_stata(path, write_index=False, convert_dates={"dates": "tc"})
+ direct = read_stata(path, convert_dates=True)
+ tm.assert_frame_equal(reread, direct)
+
+ dates_idx = original.columns.tolist().index("dates")
+ original.to_stata(path, write_index=False, convert_dates={dates_idx: "tc"})
+ direct = read_stata(path, convert_dates=True)
+ tm.assert_frame_equal(reread, direct)
+
+ def test_unsupported_type(self, temp_file):
+ original = DataFrame({"a": [1 + 2j, 2 + 4j]})
+
+ msg = "Data type complex128 not supported"
+ with pytest.raises(NotImplementedError, match=msg):
+ path = temp_file
+ original.to_stata(path)
+
+ def test_unsupported_datetype(self, temp_file):
+ dates = [
+ dt.datetime(1999, 12, 31, 12, 12, 12, 12000),
+ dt.datetime(2012, 12, 21, 12, 21, 12, 21000),
+ dt.datetime(1776, 7, 4, 7, 4, 7, 4000),
+ ]
+ original = DataFrame(
+ {
+ "nums": [1.0, 2.0, 3.0],
+ "strs": ["apple", "banana", "cherry"],
+ "dates": dates,
+ }
+ )
+
+ msg = "Format %tC not implemented"
+ with pytest.raises(NotImplementedError, match=msg):
+ path = temp_file
+ original.to_stata(path, convert_dates={"dates": "tC"})
+
+ dates = pd.date_range("1-1-1990", periods=3, tz="Asia/Hong_Kong")
+ original = DataFrame(
+ {
+ "nums": [1.0, 2.0, 3.0],
+ "strs": ["apple", "banana", "cherry"],
+ "dates": dates,
+ }
+ )
+ with pytest.raises(NotImplementedError, match="Data type datetime64"):
+ path = temp_file
+ original.to_stata(path)
+
+ def test_repeated_column_labels(self, datapath):
+ # GH 13923, 25772
+ msg = """
+Value labels for column ethnicsn are not unique. These cannot be converted to
+pandas categoricals.
+
+Either read the file with `convert_categoricals` set to False or use the
+low level interface in `StataReader` to separately read the values and the
+value_labels.
+
+The repeated labels are:\n-+\nwolof
+"""
+ with pytest.raises(ValueError, match=msg):
+ read_stata(
+ datapath("io", "data", "stata", "stata15.dta"),
+ convert_categoricals=True,
+ )
+
+ def test_stata_111(self, datapath):
+ # 111 is an old version but still used by current versions of
+ # SAS when exporting to Stata format. We do not know of any
+ # on-line documentation for this version.
+ df = read_stata(datapath("io", "data", "stata", "stata7_111.dta"))
+ original = DataFrame(
+ {
+ "y": [1, 1, 1, 1, 1, 0, 0, np.nan, 0, 0],
+ "x": [1, 2, 1, 3, np.nan, 4, 3, 5, 1, 6],
+ "w": [2, np.nan, 5, 2, 4, 4, 3, 1, 2, 3],
+ "z": ["a", "b", "c", "d", "e", "", "g", "h", "i", "j"],
+ }
+ )
+ original = original[["y", "x", "w", "z"]]
+ tm.assert_frame_equal(original, df)
+
+ def test_out_of_range_double(self, temp_file):
+ # GH 14618
+ df = DataFrame(
+ {
+ "ColumnOk": [0.0, np.finfo(np.double).eps, 4.49423283715579e307],
+ "ColumnTooBig": [0.0, np.finfo(np.double).eps, np.finfo(np.double).max],
+ }
+ )
+ msg = (
+ r"Column ColumnTooBig has a maximum value \(.+\) outside the range "
+ r"supported by Stata \(.+\)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ path = temp_file
+ df.to_stata(path)
+
+ def test_out_of_range_float(self, temp_file):
+ original = DataFrame(
+ {
+ "ColumnOk": [
+ 0.0,
+ np.finfo(np.float32).eps,
+ np.finfo(np.float32).max / 10.0,
+ ],
+ "ColumnTooBig": [
+ 0.0,
+ np.finfo(np.float32).eps,
+ np.finfo(np.float32).max,
+ ],
+ }
+ )
+ original.index.name = "index"
+ for col in original:
+ original[col] = original[col].astype(np.float32)
+
+ path = temp_file
+ original.to_stata(path)
+ reread = read_stata(path)
+
+ original["ColumnTooBig"] = original["ColumnTooBig"].astype(np.float64)
+ expected = original
+ tm.assert_frame_equal(reread.set_index("index"), expected)
+
+ @pytest.mark.parametrize("infval", [np.inf, -np.inf])
+ def test_inf(self, infval, temp_file):
+ # GH 45350
+ df = DataFrame({"WithoutInf": [0.0, 1.0], "WithInf": [2.0, infval]})
+ msg = (
+ "Column WithInf contains infinity or -infinity"
+ "which is outside the range supported by Stata."
+ )
+ with pytest.raises(ValueError, match=msg):
+ path = temp_file
+ df.to_stata(path)
+
+ def test_path_pathlib(self, temp_file):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ df.index.name = "index"
+ reader = lambda x: read_stata(x).set_index("index")
+ result = tm.round_trip_pathlib(df.to_stata, reader, temp_file)
+ tm.assert_frame_equal(df, result)
+
+ @pytest.mark.parametrize("write_index", [True, False])
+ def test_value_labels_iterator(self, write_index, temp_file):
+ # GH 16923
+ d = {"A": ["B", "E", "C", "A", "E"]}
+ df = DataFrame(data=d)
+ df["A"] = df["A"].astype("category")
+ path = temp_file
+ df.to_stata(path, write_index=write_index)
+
+ with read_stata(path, iterator=True) as dta_iter:
+ value_labels = dta_iter.value_labels()
+ assert value_labels == {"A": {0: "A", 1: "B", 2: "C", 3: "E"}}
+
+ def test_set_index(self, temp_file):
+ # GH 17328
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ df.index.name = "index"
+ path = temp_file
+ df.to_stata(path)
+ reread = read_stata(path, index_col="index")
+ tm.assert_frame_equal(df, reread)
+
+ @pytest.mark.parametrize(
+ "column", ["ms", "day", "week", "month", "qtr", "half", "yr"]
+ )
+ def test_date_parsing_ignores_format_details(self, column, datapath):
+ # GH 17797
+ #
+ # Test that display formats are ignored when determining if a numeric
+ # column is a date value.
+ #
+ # All date types are stored as numbers and format associated with the
+ # column denotes both the type of the date and the display format.
+ #
+ # STATA supports 9 date types which each have distinct units. We test 7
+ # of the 9 types, ignoring %tC and %tb. %tC is a variant of %tc that
+ # accounts for leap seconds and %tb relies on STATAs business calendar.
+ df = read_stata(datapath("io", "data", "stata", "stata13_dates.dta"))
+ unformatted = df.loc[0, column]
+ formatted = df.loc[0, column + "_fmt"]
+ assert unformatted == formatted
+
+ @pytest.mark.parametrize("byteorder", ["little", "big"])
+ def test_writer_117(self, byteorder, temp_file, using_infer_string):
+ original = DataFrame(
+ data=[
+ [
+ "string",
+ "object",
+ 1,
+ 1,
+ 1,
+ 1.1,
+ 1.1,
+ np.datetime64("2003-12-25"),
+ "a",
+ "a" * 2045,
+ "a" * 5000,
+ "a",
+ ],
+ [
+ "string-1",
+ "object-1",
+ 1,
+ 1,
+ 1,
+ 1.1,
+ 1.1,
+ np.datetime64("2003-12-26"),
+ "b",
+ "b" * 2045,
+ "",
+ "",
+ ],
+ ],
+ columns=[
+ "string",
+ "object",
+ "int8",
+ "int16",
+ "int32",
+ "float32",
+ "float64",
+ "datetime",
+ "s1",
+ "s2045",
+ "srtl",
+ "forced_strl",
+ ],
+ )
+ original["object"] = Series(original["object"], dtype=object)
+ original["int8"] = Series(original["int8"], dtype=np.int8)
+ original["int16"] = Series(original["int16"], dtype=np.int16)
+ original["int32"] = original["int32"].astype(np.int32)
+ original["float32"] = Series(original["float32"], dtype=np.float32)
+ original.index.name = "index"
+ copy = original.copy()
+ path = temp_file
+ original.to_stata(
+ path,
+ convert_dates={"datetime": "tc"},
+ byteorder=byteorder,
+ convert_strl=["forced_strl"],
+ version=117,
+ )
+ written_and_read_again = self.read_dta(path)
+
+ expected = original[:]
+ # "tc" for convert_dates means we store with "ms" resolution
+ expected["datetime"] = expected["datetime"].astype("M8[ms]")
+ if using_infer_string:
+ # object dtype (with only strings/None) comes back as string dtype
+ expected["object"] = expected["object"].astype("str")
+
+ tm.assert_frame_equal(
+ written_and_read_again.set_index("index"),
+ expected,
+ )
+ tm.assert_frame_equal(original, copy)
+
+ def test_convert_strl_name_swap(self, temp_file):
+ original = DataFrame(
+ [["a" * 3000, "A", "apple"], ["b" * 1000, "B", "banana"]],
+ columns=["long1" * 10, "long", 1],
+ )
+ original.index.name = "index"
+
+ msg = "Not all pandas column names were valid Stata variable names"
+ with tm.assert_produces_warning(InvalidColumnName, match=msg):
+ path = temp_file
+ original.to_stata(path, convert_strl=["long", 1], version=117)
+ reread = self.read_dta(path)
+ reread = reread.set_index("index")
+ reread.columns = original.columns
+ tm.assert_frame_equal(reread, original, check_index_type=False)
+
+ def test_invalid_date_conversion(self, temp_file):
+ # GH 12259
+ dates = [
+ dt.datetime(1999, 12, 31, 12, 12, 12, 12000),
+ dt.datetime(2012, 12, 21, 12, 21, 12, 21000),
+ dt.datetime(1776, 7, 4, 7, 4, 7, 4000),
+ ]
+ original = DataFrame(
+ {
+ "nums": [1.0, 2.0, 3.0],
+ "strs": ["apple", "banana", "cherry"],
+ "dates": dates,
+ }
+ )
+
+ path = temp_file
+ msg = "convert_dates key must be a column or an integer"
+ with pytest.raises(ValueError, match=msg):
+ original.to_stata(path, convert_dates={"wrong_name": "tc"})
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_nonfile_writing(self, version, temp_file):
+ # GH 21041
+ bio = io.BytesIO()
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ df.index.name = "index"
+ path = temp_file
+ df.to_stata(bio, version=version)
+ bio.seek(0)
+ with open(path, "wb") as dta:
+ dta.write(bio.read())
+ reread = read_stata(path, index_col="index")
+ tm.assert_frame_equal(df, reread)
+
+ def test_gzip_writing(self, temp_file):
+ # writing version 117 requires seek and cannot be used with gzip
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=pd.Index(list("ABCD")),
+ index=pd.Index([f"i-{i}" for i in range(30)]),
+ )
+ df.index.name = "index"
+ path = temp_file
+ with gzip.GzipFile(path, "wb") as gz:
+ df.to_stata(gz, version=114)
+ with gzip.GzipFile(path, "rb") as gz:
+ reread = read_stata(gz, index_col="index")
+ tm.assert_frame_equal(df, reread)
+
+ # 117 is not included in this list as it uses ASCII strings
+ @pytest.mark.parametrize(
+ "file",
+ [
+ "stata16_118",
+ "stata16_be_118",
+ "stata16_119",
+ "stata16_be_119",
+ ],
+ )
+ def test_unicode_dta_118_119(self, file, datapath):
+ unicode_df = self.read_dta(datapath("io", "data", "stata", f"{file}.dta"))
+
+ columns = ["utf8", "latin1", "ascii", "utf8_strl", "ascii_strl"]
+ values = [
+ ["ραηδας", "PÄNDÄS", "p", "ραηδας", "p"],
+ ["ƤĀńĐąŜ", "Ö", "a", "ƤĀńĐąŜ", "a"],
+ ["ᴘᴀᴎᴅᴀS", "Ü", "n", "ᴘᴀᴎᴅᴀS", "n"],
+ [" ", " ", "d", " ", "d"],
+ [" ", "", "a", " ", "a"],
+ ["", "", "s", "", "s"],
+ ["", "", " ", "", " "],
+ ]
+ expected = DataFrame(values, columns=columns)
+
+ tm.assert_frame_equal(unicode_df, expected)
+
+ def test_mixed_string_strl(self, temp_file, using_infer_string):
+ # GH 23633
+ output = [{"mixed": "string" * 500, "number": 0}, {"mixed": None, "number": 1}]
+ output = DataFrame(output)
+ output.number = output.number.astype("int32")
+
+ path = temp_file
+ output.to_stata(path, write_index=False, version=117)
+ reread = read_stata(path)
+ expected = output.fillna("")
+ tm.assert_frame_equal(reread, expected)
+
+ # Check strl supports all None (null)
+ output["mixed"] = None
+ output.to_stata(path, write_index=False, convert_strl=["mixed"], version=117)
+ reread = read_stata(path)
+ expected = output.fillna("")
+ if using_infer_string:
+ expected["mixed"] = expected["mixed"].astype("str")
+ tm.assert_frame_equal(reread, expected)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_all_none_exception(self, version, temp_file):
+ output = [{"none": "none", "number": 0}, {"none": None, "number": 1}]
+ output = DataFrame(output)
+ output["none"] = None
+ with pytest.raises(ValueError, match="Column `none` cannot be exported"):
+ output.to_stata(temp_file, version=version)
+
+ @pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+ def test_invalid_file_not_written(self, version, temp_file):
+ content = "Here is one __�__ Another one __·__ Another one __½__"
+ df = DataFrame([content], columns=["invalid"])
+ msg1 = (
+ r"'latin-1' codec can't encode character '\\ufffd' "
+ r"in position 14: ordinal not in range\(256\)"
+ )
+ msg2 = (
+ "'ascii' codec can't decode byte 0xef in position 14: "
+ r"ordinal not in range\(128\)"
+ )
+ with pytest.raises(UnicodeEncodeError, match=f"{msg1}|{msg2}"):
+ df.to_stata(temp_file)
+
+ def test_strl_latin1(self, temp_file):
+ # GH 23573, correct GSO data to reflect correct size
+ output = DataFrame(
+ [["pandas"] * 2, ["þâÑÐŧ"] * 2], columns=["var_str", "var_strl"]
+ )
+
+ output.to_stata(temp_file, version=117, convert_strl=["var_strl"])
+ with open(temp_file, "rb") as reread:
+ content = reread.read()
+ expected = "þâÑÐŧ"
+ assert expected.encode("latin-1") in content
+ assert expected.encode("utf-8") in content
+ gsos = content.split(b"strls")[1][1:-2]
+ for gso in gsos.split(b"GSO")[1:]:
+ val = gso.split(b"\x00")[-2]
+ size = gso[gso.find(b"\x82") + 1]
+ assert len(val) == size - 1
+
+ def test_encoding_latin1_118(self, datapath):
+ # GH 25960
+ msg = """
+One or more strings in the dta file could not be decoded using utf-8, and
+so the fallback encoding of latin-1 is being used. This can happen when a file
+has been incorrectly encoded by Stata or some other software. You should verify
+the string values returned are correct."""
+ # Move path outside of read_stata, or else assert_produces_warning
+ # will block pytests skip mechanism from triggering (failing the test)
+ # if the path is not present
+ path = datapath("io", "data", "stata", "stata1_encoding_118.dta")
+ with tm.assert_produces_warning(UnicodeWarning, filter_level="once") as w:
+ encoded = read_stata(path)
+ # with filter_level="always", produces 151 warnings which can be slow
+ assert len(w) == 1
+ assert w[0].message.args[0] == msg
+
+ expected = DataFrame([["Düsseldorf"]] * 151, columns=["kreis1849"])
+ tm.assert_frame_equal(encoded, expected)
+
+ @pytest.mark.slow
+ def test_stata_119(self, datapath):
+ # Gzipped since contains 32,999 variables and uncompressed is 20MiB
+ # Just validate that the reader reports correct number of variables
+ # to avoid high peak memory
+ with gzip.open(
+ datapath("io", "data", "stata", "stata1_119.dta.gz"), "rb"
+ ) as gz:
+ with StataReader(gz) as reader:
+ reader._ensure_open()
+ assert reader._nvar == 32999
+
+ @pytest.mark.parametrize("version", [118, 119, None])
+ @pytest.mark.parametrize("byteorder", ["little", "big"])
+ def test_utf8_writer(self, version, byteorder, temp_file):
+ cat = pd.Categorical(["a", "β", "ĉ"], ordered=True)
+ data = DataFrame(
+ [
+ [1.0, 1, "ᴬ", "ᴀ relatively long ŝtring"],
+ [2.0, 2, "ᴮ", ""],
+ [3.0, 3, "ᴰ", None],
+ ],
+ columns=["Å", "β", "ĉ", "strls"],
+ )
+ data["ᴐᴬᵀ"] = cat
+ variable_labels = {
+ "Å": "apple",
+ "β": "ᵈᵉᵊ",
+ "ĉ": "ᴎტჄႲႳႴႶႺ",
+ "strls": "Long Strings",
+ "ᴐᴬᵀ": "",
+ }
+ data_label = "ᴅaᵀa-label"
+ value_labels = {"β": {1: "label", 2: "æøå", 3: "ŋot valid latin-1"}}
+ data["β"] = data["β"].astype(np.int32)
+ writer = StataWriterUTF8(
+ temp_file,
+ data,
+ data_label=data_label,
+ convert_strl=["strls"],
+ variable_labels=variable_labels,
+ write_index=False,
+ byteorder=byteorder,
+ version=version,
+ value_labels=value_labels,
+ )
+ writer.write_file()
+ reread_encoded = read_stata(temp_file)
+ # Missing is intentionally converted to empty strl
+ data["strls"] = data["strls"].fillna("")
+ # Variable with value labels is reread as categorical
+ data["β"] = (
+ data["β"].replace(value_labels["β"]).astype("category").cat.as_ordered()
+ )
+ tm.assert_frame_equal(data, reread_encoded)
+ with StataReader(temp_file) as reader:
+ assert reader.data_label == data_label
+ assert reader.variable_labels() == variable_labels
+
+ data.to_stata(temp_file, version=version, write_index=False)
+ reread_to_stata = read_stata(temp_file)
+ tm.assert_frame_equal(data, reread_to_stata)
+
+ def test_writer_118_exceptions(self, temp_file):
+ df = DataFrame(np.zeros((1, 33000), dtype=np.int8))
+ with pytest.raises(ValueError, match="version must be either 118 or 119."):
+ StataWriterUTF8(temp_file, df, version=117)
+ with pytest.raises(ValueError, match="You must use version 119"):
+ StataWriterUTF8(temp_file, df, version=118)
+
+ @pytest.mark.parametrize(
+ "dtype_backend",
+ ["numpy_nullable", pytest.param("pyarrow", marks=td.skip_if_no("pyarrow"))],
+ )
+ def test_read_write_ea_dtypes(self, dtype_backend, temp_file, tmp_path):
+ dtype = "Int64" if dtype_backend == "numpy_nullable" else "int64[pyarrow]"
+ df = DataFrame(
+ {
+ "a": pd.array([1, 2, None], dtype=dtype),
+ "b": ["a", "b", "c"],
+ "c": [True, False, None],
+ "d": [1.5, 2.5, 3.5],
+ "e": pd.date_range("2020-12-31", periods=3, freq="D"),
+ },
+ index=pd.Index([0, 1, 2], name="index"),
+ )
+ df = df.convert_dtypes(dtype_backend=dtype_backend)
+ stata_path = tmp_path / "test_stata.dta"
+ df.to_stata(stata_path, version=118)
+
+ df.to_stata(temp_file)
+ written_and_read_again = self.read_dta(temp_file)
+
+ expected = DataFrame(
+ {
+ "a": [1, 2, np.nan],
+ "b": ["a", "b", "c"],
+ "c": [1.0, 0, np.nan],
+ "d": [1.5, 2.5, 3.5],
+ # stata stores with ms unit, so unit does not round-trip exactly
+ "e": pd.date_range("2020-12-31", periods=3, freq="D", unit="ms"),
+ },
+ index=pd.RangeIndex(range(3), name="index"),
+ )
+
+ tm.assert_frame_equal(written_and_read_again.set_index("index"), expected)
+
+ @pytest.mark.parametrize("version", [113, 114, 115, 117, 118, 119])
+ def test_read_data_int_validranges(self, version, datapath):
+ expected = DataFrame(
+ {
+ "byte": np.array([-127, 100], dtype=np.int8),
+ "int": np.array([-32767, 32740], dtype=np.int16),
+ "long": np.array([-2147483647, 2147483620], dtype=np.int32),
+ }
+ )
+
+ parsed = read_stata(
+ datapath("io", "data", "stata", f"stata_int_validranges_{version}.dta")
+ )
+ tm.assert_frame_equal(parsed, expected)
+
+ @pytest.mark.parametrize("version", [104, 105, 108, 110, 111])
+ def test_read_data_int_validranges_compat(self, version, datapath):
+ expected = DataFrame(
+ {
+ "byte": np.array([-128, 126], dtype=np.int8),
+ "int": np.array([-32768, 32766], dtype=np.int16),
+ "long": np.array([-2147483648, 2147483646], dtype=np.int32),
+ }
+ )
+
+ parsed = read_stata(
+ datapath("io", "data", "stata", f"stata_int_validranges_{version}.dta")
+ )
+ tm.assert_frame_equal(parsed, expected)
+
+ # The byte type was not supported prior to the 104 format
+ @pytest.mark.parametrize("version", [102, 103])
+ def test_read_data_int_validranges_compat_nobyte(self, version, datapath):
+ expected = DataFrame(
+ {
+ "byte": np.array([-128, 126], dtype=np.int16),
+ "int": np.array([-32768, 32766], dtype=np.int16),
+ "long": np.array([-2147483648, 2147483646], dtype=np.int32),
+ }
+ )
+
+ parsed = read_stata(
+ datapath("io", "data", "stata", f"stata_int_validranges_{version}.dta")
+ )
+ tm.assert_frame_equal(parsed, expected)
+
+
+@pytest.mark.parametrize("version", [105, 108, 110, 111, 113, 114])
+def test_backward_compat(version, datapath):
+ data_base = datapath("io", "data", "stata")
+ ref = os.path.join(data_base, "stata-compat-118.dta")
+ old = os.path.join(data_base, f"stata-compat-{version}.dta")
+ expected = read_stata(ref)
+ old_dta = read_stata(old)
+ tm.assert_frame_equal(old_dta, expected, check_dtype=False)
+
+
+@pytest.mark.parametrize("version", [103, 104])
+def test_backward_compat_nodateconversion(version, datapath):
+ # The Stata data format prior to 105 did not support a date format
+ # so read the raw values for comparison
+ data_base = datapath("io", "data", "stata")
+ ref = os.path.join(data_base, "stata-compat-118.dta")
+ old = os.path.join(data_base, f"stata-compat-{version}.dta")
+ expected = read_stata(ref, convert_dates=False)
+ old_dta = read_stata(old, convert_dates=False)
+ tm.assert_frame_equal(old_dta, expected, check_dtype=False)
+
+
+@pytest.mark.parametrize("version", [102])
+def test_backward_compat_nostring(version, datapath):
+ # The Stata data format prior to 105 did not support a date format
+ # so read the raw values for comparison
+ ref = datapath("io", "data", "stata", "stata-compat-118.dta")
+ old = datapath("io", "data", "stata", f"stata-compat-{version}.dta")
+ expected = read_stata(ref, convert_dates=False)
+ # The Stata data format prior to 103 did not support string data
+ expected = expected.drop(columns=["s10"])
+ old_dta = read_stata(old, convert_dates=False)
+ tm.assert_frame_equal(old_dta, expected, check_dtype=False)
+
+
+@pytest.mark.parametrize("version", [105, 108, 110, 111, 113, 114, 118])
+def test_bigendian(version, datapath):
+ ref = datapath("io", "data", "stata", f"stata-compat-{version}.dta")
+ big = datapath("io", "data", "stata", f"stata-compat-be-{version}.dta")
+ expected = read_stata(ref)
+ big_dta = read_stata(big)
+ tm.assert_frame_equal(big_dta, expected)
+
+
+# Note: 102 format does not support big-endian byte order
+@pytest.mark.parametrize("version", [103, 104])
+def test_bigendian_nodateconversion(version, datapath):
+ # The Stata data format prior to 105 did not support a date format
+ # so read the raw values for comparison
+ ref = datapath("io", "data", "stata", f"stata-compat-{version}.dta")
+ big = datapath("io", "data", "stata", f"stata-compat-be-{version}.dta")
+ expected = read_stata(ref, convert_dates=False)
+ big_dta = read_stata(big, convert_dates=False)
+ tm.assert_frame_equal(big_dta, expected)
+
+
+def test_direct_read(datapath, monkeypatch):
+ file_path = datapath("io", "data", "stata", "stata-compat-118.dta")
+
+ # Test that opening a file path doesn't buffer the file.
+ with StataReader(file_path) as reader:
+ # Must not have been buffered to memory
+ assert not reader.read().empty
+ assert not isinstance(reader._path_or_buf, io.BytesIO)
+
+ # Test that we use a given fp exactly, if possible.
+ with open(file_path, "rb") as fp:
+ with StataReader(fp) as reader:
+ assert not reader.read().empty
+ assert reader._path_or_buf is fp
+
+ # Test that we use a given BytesIO exactly, if possible.
+ with open(file_path, "rb") as fp:
+ with io.BytesIO(fp.read()) as bio:
+ with StataReader(bio) as reader:
+ assert not reader.read().empty
+ assert reader._path_or_buf is bio
+
+
+@pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+@pytest.mark.parametrize("use_dict", [True, False])
+@pytest.mark.parametrize("infer", [True, False])
+def test_compression(
+ compression, version, use_dict, infer, compression_to_extension, tmp_path
+):
+ file_name = "dta_inferred_compression.dta"
+ if compression:
+ if use_dict:
+ file_ext = compression
+ else:
+ file_ext = compression_to_extension[compression]
+ file_name += f".{file_ext}"
+ compression_arg = compression
+ if infer:
+ compression_arg = "infer"
+ if use_dict:
+ compression_arg = {"method": compression}
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)), columns=list("AB")
+ )
+ df.index.name = "index"
+ path = tmp_path / file_name
+ path.touch()
+ df.to_stata(path, version=version, compression=compression_arg)
+ if compression == "gzip":
+ with gzip.open(path, "rb") as comp:
+ fp = io.BytesIO(comp.read())
+ elif compression == "zip":
+ with zipfile.ZipFile(path, "r") as comp:
+ fp = io.BytesIO(comp.read(comp.filelist[0]))
+ elif compression == "tar":
+ with tarfile.open(path) as tar:
+ fp = io.BytesIO(tar.extractfile(tar.getnames()[0]).read())
+ elif compression == "bz2":
+ with bz2.open(path, "rb") as comp:
+ fp = io.BytesIO(comp.read())
+ elif compression == "zstd":
+ zstd = pytest.importorskip("zstandard")
+ with zstd.open(path, "rb") as comp:
+ fp = io.BytesIO(comp.read())
+ elif compression == "xz":
+ lzma = pytest.importorskip("lzma")
+ with lzma.open(path, "rb") as comp:
+ fp = io.BytesIO(comp.read())
+ elif compression is None:
+ fp = path
+ reread = read_stata(fp, index_col="index")
+
+ expected = df
+ tm.assert_frame_equal(reread, expected)
+
+
+@pytest.mark.parametrize("method", ["zip", "infer"])
+@pytest.mark.parametrize("file_ext", [None, "dta", "zip"])
+def test_compression_dict(method, file_ext, tmp_path):
+ file_name = f"test.{file_ext}"
+ archive_name = "test.dta"
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)), columns=list("AB")
+ )
+ df.index.name = "index"
+ compression = {"method": method, "archive_name": archive_name}
+ path = tmp_path / file_name
+ path.touch()
+ df.to_stata(path, compression=compression)
+ if method == "zip" or file_ext == "zip":
+ with zipfile.ZipFile(path, "r") as zp:
+ assert len(zp.filelist) == 1
+ assert zp.filelist[0].filename == archive_name
+ fp = io.BytesIO(zp.read(zp.filelist[0]))
+ else:
+ fp = path
+ reread = read_stata(fp, index_col="index")
+
+ expected = df
+ tm.assert_frame_equal(reread, expected)
+
+
+@pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+def test_chunked_categorical(version, temp_file):
+ df = DataFrame({"cats": Series(["a", "b", "a", "b", "c"], dtype="category")})
+ df.index.name = "index"
+
+ expected = df.copy()
+
+ df.to_stata(temp_file, version=version)
+ with StataReader(temp_file, chunksize=2, order_categoricals=False) as reader:
+ for i, block in enumerate(reader):
+ block = block.set_index("index")
+ assert "cats" in block
+ tm.assert_series_equal(
+ block.cats,
+ expected.cats.iloc[2 * i : 2 * (i + 1)],
+ check_index_type=len(block) > 1,
+ )
+
+
+def test_chunked_categorical_partial(datapath):
+ dta_file = datapath("io", "data", "stata", "stata-dta-partially-labeled.dta")
+ values = ["a", "b", "a", "b", 3.0]
+ msg = "series with value labels are not fully labeled"
+ with StataReader(dta_file, chunksize=2) as reader:
+ with tm.assert_produces_warning(CategoricalConversionWarning, match=msg):
+ for i, block in enumerate(reader):
+ assert list(block.cats) == values[2 * i : 2 * (i + 1)]
+ if i < 2:
+ idx = pd.Index(["a", "b"])
+ else:
+ idx = pd.Index([3.0], dtype="float64")
+ tm.assert_index_equal(block.cats.cat.categories, idx)
+ with tm.assert_produces_warning(CategoricalConversionWarning, match=msg):
+ with StataReader(dta_file, chunksize=5) as reader:
+ large_chunk = reader.__next__()
+ direct = read_stata(dta_file)
+ tm.assert_frame_equal(direct, large_chunk)
+
+
+@pytest.mark.parametrize("chunksize", (-1, 0, "apple"))
+def test_iterator_errors(datapath, chunksize):
+ dta_file = datapath("io", "data", "stata", "stata-dta-partially-labeled.dta")
+ with pytest.raises(ValueError, match="chunksize must be a positive"):
+ with StataReader(dta_file, chunksize=chunksize):
+ pass
+
+
+def test_iterator_value_labels(temp_file):
+ # GH 31544
+ values = ["c_label", "b_label"] + ["a_label"] * 500
+ df = DataFrame({f"col{k}": pd.Categorical(values, ordered=True) for k in range(2)})
+ df.to_stata(temp_file, write_index=False)
+ expected = pd.Index(["a_label", "b_label", "c_label"])
+ with read_stata(temp_file, chunksize=100) as reader:
+ for j, chunk in enumerate(reader):
+ for i in range(2):
+ tm.assert_index_equal(chunk.dtypes.iloc[i].categories, expected)
+ tm.assert_frame_equal(chunk, df.iloc[j * 100 : (j + 1) * 100])
+
+
+def test_precision_loss(temp_file):
+ df = DataFrame(
+ [[sum(2**i for i in range(60)), sum(2**i for i in range(52))]],
+ columns=["big", "little"],
+ )
+ with tm.assert_produces_warning(
+ PossiblePrecisionLoss, match="Column converted from int64 to float64"
+ ):
+ df.to_stata(temp_file, write_index=False)
+ reread = read_stata(temp_file)
+ expected_dt = Series([np.float64, np.float64], index=["big", "little"])
+ tm.assert_series_equal(reread.dtypes, expected_dt)
+ assert reread.loc[0, "little"] == df.loc[0, "little"]
+ assert reread.loc[0, "big"] == float(df.loc[0, "big"])
+
+
+def test_compression_roundtrip(compression, temp_file):
+ df = DataFrame(
+ [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ index=["A", "B"],
+ columns=["X", "Y", "Z"],
+ )
+ df.index.name = "index"
+
+ df.to_stata(temp_file, compression=compression)
+ reread = read_stata(temp_file, compression=compression, index_col="index")
+ tm.assert_frame_equal(df, reread)
+
+ # explicitly ensure file was compressed.
+ with tm.decompress_file(temp_file, compression) as fh:
+ contents = io.BytesIO(fh.read())
+ reread = read_stata(contents, index_col="index")
+ tm.assert_frame_equal(df, reread)
+
+
+@pytest.mark.parametrize("to_infer", [True, False])
+@pytest.mark.parametrize("read_infer", [True, False])
+def test_stata_compression(
+ compression_only, read_infer, to_infer, compression_to_extension, tmp_path
+):
+ compression = compression_only
+
+ ext = compression_to_extension[compression]
+ filename = f"test.{ext}"
+
+ df = DataFrame(
+ [[0.123456, 0.234567, 0.567567], [12.32112, 123123.2, 321321.2]],
+ index=["A", "B"],
+ columns=["X", "Y", "Z"],
+ )
+ df.index.name = "index"
+
+ to_compression = "infer" if to_infer else compression
+ read_compression = "infer" if read_infer else compression
+
+ path = tmp_path / filename
+ path.touch()
+ df.to_stata(path, compression=to_compression)
+ result = read_stata(path, compression=read_compression, index_col="index")
+ tm.assert_frame_equal(result, df)
+
+
+def test_non_categorical_value_labels(temp_file):
+ data = DataFrame(
+ {
+ "fully_labelled": [1, 2, 3, 3, 1],
+ "partially_labelled": [1.0, 2.0, np.nan, 9.0, np.nan],
+ "Y": [7, 7, 9, 8, 10],
+ "Z": pd.Categorical(["j", "k", "l", "k", "j"]),
+ }
+ )
+
+ path = temp_file
+ value_labels = {
+ "fully_labelled": {1: "one", 2: "two", 3: "three"},
+ "partially_labelled": {1.0: "one", 2.0: "two"},
+ }
+ expected = {**value_labels, "Z": {0: "j", 1: "k", 2: "l"}}
+
+ writer = StataWriter(path, data, value_labels=value_labels)
+ writer.write_file()
+
+ with StataReader(path) as reader:
+ reader_value_labels = reader.value_labels()
+ assert reader_value_labels == expected
+
+ msg = "Can't create value labels for notY, it wasn't found in the dataset."
+ value_labels = {"notY": {7: "label1", 8: "label2"}}
+ with pytest.raises(KeyError, match=msg):
+ StataWriter(path, data, value_labels=value_labels)
+
+ msg = (
+ "Can't create value labels for Z, value labels "
+ "can only be applied to numeric columns."
+ )
+ value_labels = {"Z": {1: "a", 2: "k", 3: "j", 4: "i"}}
+ with pytest.raises(ValueError, match=msg):
+ StataWriter(path, data, value_labels=value_labels)
+
+
+def test_non_categorical_value_label_name_conversion(temp_file):
+ # Check conversion of invalid variable names
+ data = DataFrame(
+ {
+ "invalid~!": [1, 1, 2, 3, 5, 8], # Only alphanumeric and _
+ "6_invalid": [1, 1, 2, 3, 5, 8], # Must start with letter or _
+ "invalid_name_longer_than_32_characters": [8, 8, 9, 9, 8, 8], # Too long
+ "aggregate": [2, 5, 5, 6, 6, 9], # Reserved words
+ (1, 2): [1, 2, 3, 4, 5, 6], # Hashable non-string
+ }
+ )
+
+ value_labels = {
+ "invalid~!": {1: "label1", 2: "label2"},
+ "6_invalid": {1: "label1", 2: "label2"},
+ "invalid_name_longer_than_32_characters": {8: "eight", 9: "nine"},
+ "aggregate": {5: "five"},
+ (1, 2): {3: "three"},
+ }
+
+ expected = {
+ "invalid__": {1: "label1", 2: "label2"},
+ "_6_invalid": {1: "label1", 2: "label2"},
+ "invalid_name_longer_than_32_char": {8: "eight", 9: "nine"},
+ "_aggregate": {5: "five"},
+ "_1__2_": {3: "three"},
+ }
+
+ msg = "Not all pandas column names were valid Stata variable names"
+ with tm.assert_produces_warning(InvalidColumnName, match=msg):
+ data.to_stata(temp_file, value_labels=value_labels)
+
+ with StataReader(temp_file) as reader:
+ reader_value_labels = reader.value_labels()
+ assert reader_value_labels == expected
+
+
+def test_non_categorical_value_label_convert_categoricals_error(temp_file):
+ # Mapping more than one value to the same label is valid for Stata
+ # labels, but can't be read with convert_categoricals=True
+ value_labels = {
+ "repeated_labels": {10: "Ten", 20: "More than ten", 40: "More than ten"}
+ }
+
+ data = DataFrame(
+ {
+ "repeated_labels": [10, 10, 20, 20, 40, 40],
+ }
+ )
+
+ data.to_stata(temp_file, value_labels=value_labels)
+
+ with StataReader(temp_file, convert_categoricals=False) as reader:
+ reader_value_labels = reader.value_labels()
+ assert reader_value_labels == value_labels
+
+ col = "repeated_labels"
+ repeats = "-" * 80 + "\n" + "\n".join(["More than ten"])
+
+ msg = f"""
+Value labels for column {col} are not unique. These cannot be converted to
+pandas categoricals.
+
+Either read the file with `convert_categoricals` set to False or use the
+low level interface in `StataReader` to separately read the values and the
+value_labels.
+
+The repeated labels are:
+{repeats}
+"""
+ with pytest.raises(ValueError, match=msg):
+ read_stata(temp_file, convert_categoricals=True)
+
+
+@pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ pd.BooleanDtype,
+ pd.Int8Dtype,
+ pd.Int16Dtype,
+ pd.Int32Dtype,
+ pd.Int64Dtype,
+ pd.UInt8Dtype,
+ pd.UInt16Dtype,
+ pd.UInt32Dtype,
+ pd.UInt64Dtype,
+ ],
+)
+def test_nullable_support(dtype, version, temp_file):
+ df = DataFrame(
+ {
+ "a": Series([1.0, 2.0, 3.0]),
+ "b": Series([1, pd.NA, pd.NA], dtype=dtype.name),
+ "c": Series(["a", "b", None]),
+ }
+ )
+ dtype_name = df.b.dtype.numpy_dtype.name
+ # Only use supported names: no uint, bool or int64
+ dtype_name = dtype_name.replace("u", "")
+ if dtype_name == "int64":
+ dtype_name = "int32"
+ elif dtype_name == "bool":
+ dtype_name = "int8"
+ value = StataMissingValue.BASE_MISSING_VALUES[dtype_name]
+ smv = StataMissingValue(value)
+ expected_b = Series([1, smv, smv], dtype=object, name="b")
+ expected_c = Series(["a", "b", ""], name="c")
+ df.to_stata(temp_file, write_index=False, version=version)
+ reread = read_stata(temp_file, convert_missing=True)
+ tm.assert_series_equal(df.a, reread.a)
+ tm.assert_series_equal(reread.b, expected_b)
+ tm.assert_series_equal(reread.c, expected_c)
+
+
+def test_empty_frame(temp_file):
+ # GH 46240
+ # create an empty DataFrame with int64 and float64 dtypes
+ df = DataFrame(data={"a": range(3), "b": [1.0, 2.0, 3.0]}).head(0)
+ path = temp_file
+ df.to_stata(path, write_index=False, version=117)
+ # Read entire dataframe
+ df2 = read_stata(path)
+ assert "b" in df2
+ # Dtypes don't match since no support for int32
+ dtypes = Series({"a": np.dtype("int32"), "b": np.dtype("float64")})
+ tm.assert_series_equal(df2.dtypes, dtypes)
+ # read one column of empty .dta file
+ df3 = read_stata(path, columns=["a"])
+ assert "b" not in df3
+ tm.assert_series_equal(df3.dtypes, dtypes.loc[["a"]])
+
+
+@pytest.mark.parametrize("version", [114, 117, 118, 119, None])
+def test_many_strl(temp_file, version):
+ n = 65534
+ df = DataFrame(np.arange(n), columns=["col"])
+ lbls = ["".join(v) for v in itertools.product(*([string.ascii_letters] * 3))]
+ value_labels = {"col": {i: lbls[i] for i in range(n)}}
+ df.to_stata(temp_file, value_labels=value_labels, version=version)
+
+
+@pytest.mark.parametrize("version", [117, 118, 119, None])
+def test_strl_missings(temp_file, version):
+ # GH 23633
+ # Check that strl supports None and pd.NA
+ df = DataFrame(
+ [
+ {"str1": "string" * 500, "number": 0},
+ {"str1": None, "number": 1},
+ {"str1": pd.NA, "number": 1},
+ ]
+ )
+ df.to_stata(temp_file, version=version)
+
+
+@pytest.mark.parametrize("version", [117, 118, 119, None])
+def test_ascii_error(temp_file, version):
+ # GH #61583
+ # Check that 2 byte long unicode characters doesn't cause export error
+ df = DataFrame({"doubleByteCol": ["§" * 1500]})
+ df.to_stata(temp_file, write_index=0, version=version)
+ df_input = read_stata(temp_file)
+ tm.assert_frame_equal(df, df_input)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..8900b0dc46754a2b7b5191c3619b75afe57eb232
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/conftest.py
@@ -0,0 +1,105 @@
+from pathlib import Path
+
+import pytest
+
+
+@pytest.fixture
+def xml_data_path():
+ """
+ Returns a Path object to the XML example directory.
+
+ Examples
+ --------
+ >>> def test_read_xml(xml_data_path):
+ ... pd.read_xml(xml_data_path / "file.xsl")
+ """
+ return Path(__file__).parent.parent / "data" / "xml"
+
+
+@pytest.fixture
+def xml_books(xml_data_path, datapath):
+ """
+ Returns the path (as `str`) to the `books.xml` example file.
+
+ Examples
+ --------
+ >>> def test_read_xml(xml_books):
+ ... pd.read_xml(xml_books)
+ """
+ return datapath(xml_data_path / "books.xml")
+
+
+@pytest.fixture
+def xml_doc_ch_utf(xml_data_path, datapath):
+ """
+ Returns the path (as `str`) to the `doc_ch_utf.xml` example file.
+
+ Examples
+ --------
+ >>> def test_read_xml(xml_doc_ch_utf):
+ ... pd.read_xml(xml_doc_ch_utf)
+ """
+ return datapath(xml_data_path / "doc_ch_utf.xml")
+
+
+@pytest.fixture
+def xml_baby_names(xml_data_path, datapath):
+ """
+ Returns the path (as `str`) to the `baby_names.xml` example file.
+
+ Examples
+ --------
+ >>> def test_read_xml(xml_baby_names):
+ ... pd.read_xml(xml_baby_names)
+ """
+ return datapath(xml_data_path / "baby_names.xml")
+
+
+@pytest.fixture
+def kml_cta_rail_lines(xml_data_path, datapath):
+ """
+ Returns the path (as `str`) to the `cta_rail_lines.kml` example file.
+
+ Examples
+ --------
+ >>> def test_read_xml(kml_cta_rail_lines):
+ ... pd.read_xml(
+ ... kml_cta_rail_lines,
+ ... xpath=".//k:Placemark",
+ ... namespaces={"k": "http://www.opengis.net/kml/2.2"},
+ ... stylesheet=xsl_flatten_doc,
+ ... )
+ """
+ return datapath(xml_data_path / "cta_rail_lines.kml")
+
+
+@pytest.fixture
+def xsl_flatten_doc(xml_data_path, datapath):
+ """
+ Returns the path (as `str`) to the `flatten_doc.xsl` example file.
+
+ Examples
+ --------
+ >>> def test_read_xsl(xsl_flatten_doc, mode):
+ ... with open(
+ ... xsl_flatten_doc, mode, encoding="utf-8" if mode == "r" else None
+ ... ) as f:
+ ... xsl_obj = f.read()
+ """
+ return datapath(xml_data_path / "flatten_doc.xsl")
+
+
+@pytest.fixture
+def xsl_row_field_output(xml_data_path, datapath):
+ """
+ Returns the path (as `str`) to the `row_field_output.xsl` example file.
+
+ Examples
+ --------
+ >>> def test_read_xsl(xsl_row_field_output, mode):
+ ... with open(
+ ... xsl_row_field_output, mode, encoding="utf-8" if mode == "r" else None
+ ... ) as f:
+ ... xsl_obj = f.read()
+ """
+ return datapath(xml_data_path / "row_field_output.xsl")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/test_to_xml.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/test_to_xml.py
new file mode 100644
index 0000000000000000000000000000000000000000..2178d904d8461ad5e691f8158217afca17851998
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/test_to_xml.py
@@ -0,0 +1,1357 @@
+from __future__ import annotations
+
+from io import (
+ BytesIO,
+ StringIO,
+)
+import os
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas import (
+ NA,
+ DataFrame,
+ Index,
+)
+import pandas._testing as tm
+
+from pandas.io.common import get_handle
+from pandas.io.xml import read_xml
+
+# CHECKLIST
+
+# [x] - ValueError: "Values for parser can only be lxml or etree."
+
+# etree
+# [x] - ImportError: "lxml not found, please install or use the etree parser."
+# [X] - TypeError: "...is not a valid type for attr_cols"
+# [X] - TypeError: "...is not a valid type for elem_cols"
+# [X] - LookupError: "unknown encoding"
+# [X] - KeyError: "...is not included in namespaces"
+# [X] - KeyError: "no valid column"
+# [X] - ValueError: "To use stylesheet, you need lxml installed..."
+# [] - OSError: (NEED PERMISSION ISSUE, DISK FULL, ETC.)
+# [X] - FileNotFoundError: "No such file or directory"
+# [X] - PermissionError: "Forbidden"
+
+# lxml
+# [X] - TypeError: "...is not a valid type for attr_cols"
+# [X] - TypeError: "...is not a valid type for elem_cols"
+# [X] - LookupError: "unknown encoding"
+# [] - OSError: (NEED PERMISSION ISSUE, DISK FULL, ETC.)
+# [X] - FileNotFoundError: "No such file or directory"
+# [X] - KeyError: "...is not included in namespaces"
+# [X] - KeyError: "no valid column"
+# [X] - ValueError: "stylesheet is not a url, file, or xml string."
+# [] - LookupError: (NEED WRONG ENCODING FOR FILE OUTPUT)
+# [] - URLError: (USUALLY DUE TO NETWORKING)
+# [] - HTTPError: (NEED AN ONLINE STYLESHEET)
+# [X] - OSError: "failed to load external entity"
+# [X] - XMLSyntaxError: "Opening and ending tag mismatch"
+# [X] - XSLTApplyError: "Cannot resolve URI"
+# [X] - XSLTParseError: "failed to compile"
+# [X] - PermissionError: "Forbidden"
+
+
+@pytest.fixture
+def geom_df():
+ return DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": [4, np.nan, 3],
+ }
+ )
+
+
+@pytest.fixture
+def planet_df():
+ return DataFrame(
+ {
+ "planet": [
+ "Mercury",
+ "Venus",
+ "Earth",
+ "Mars",
+ "Jupiter",
+ "Saturn",
+ "Uranus",
+ "Neptune",
+ ],
+ "type": [
+ "terrestrial",
+ "terrestrial",
+ "terrestrial",
+ "terrestrial",
+ "gas giant",
+ "gas giant",
+ "ice giant",
+ "ice giant",
+ ],
+ "location": [
+ "inner",
+ "inner",
+ "inner",
+ "inner",
+ "outer",
+ "outer",
+ "outer",
+ "outer",
+ ],
+ "mass": [
+ 0.330114,
+ 4.86747,
+ 5.97237,
+ 0.641712,
+ 1898.187,
+ 568.3174,
+ 86.8127,
+ 102.4126,
+ ],
+ }
+ )
+
+
+@pytest.fixture
+def from_file_expected():
+ return """\
+
+
+
+ 0
+ cooking
+ Everyday Italian
+ Giada De Laurentiis
+ 2005
+ 30.0
+
+
+ 1
+ children
+ Harry Potter
+ J K. Rowling
+ 2005
+ 29.99
+
+
+ 2
+ web
+ Learning XML
+ Erik T. Ray
+ 2003
+ 39.95
+
+"""
+
+
+def equalize_decl(doc):
+ # etree and lxml differ on quotes and case in xml declaration
+ if doc is not None:
+ doc = doc.replace(
+ '
+
+
+ cooking
+ Everyday Italian
+ Giada De Laurentiis
+ 2005
+ 30.0
+
+
+ children
+ Harry Potter
+ J K. Rowling
+ 2005
+ 29.99
+
+
+ web
+ Learning XML
+ Erik T. Ray
+ 2003
+ 39.95
+
+"""
+
+ df_file = read_xml(xml_books, parser=parser)
+
+ df_file.to_xml(temp_file, index=False, parser=parser)
+ output = temp_file.read_text(encoding="utf-8").strip()
+
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+def test_index_false_rename_row_root(xml_books, parser, temp_file):
+ expected = """\
+
+
+
+ cooking
+ Everyday Italian
+ Giada De Laurentiis
+ 2005
+ 30.0
+
+
+ children
+ Harry Potter
+ J K. Rowling
+ 2005
+ 29.99
+
+
+ web
+ Learning XML
+ Erik T. Ray
+ 2003
+ 39.95
+
+ """
+
+ df_file = read_xml(xml_books, parser=parser)
+
+ df_file.to_xml(
+ temp_file, index=False, root_name="books", row_name="book", parser=parser
+ )
+ output = temp_file.read_text(encoding="utf-8").strip()
+
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+@pytest.mark.parametrize("typ", [int, str])
+def test_index_false_with_offset_input_index(parser, typ, geom_df):
+ """
+ Tests that the output does not contain the `` field when the index of the
+ input Dataframe has an offset.
+
+ This is a regression test for issue #42458.
+ """
+
+ expected = """\
+
+
+
+ square
+ 360
+ 4.0
+
+
+ circle
+ 360
+
+
+
+ triangle
+ 180
+ 3.0
+
+"""
+ offset_index = [typ(i) for i in range(10, 13)]
+ offset_geom_df = geom_df.copy()
+ offset_geom_df.index = Index(offset_index)
+ output = offset_geom_df.to_xml(index=False, parser=parser)
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+# NA_REP
+
+na_expected = """\
+
+
+
+ 0
+ square
+ 360
+ 4.0
+
+
+ 1
+ circle
+ 360
+
+
+
+ 2
+ triangle
+ 180
+ 3.0
+
+"""
+
+
+def test_na_elem_output(parser, geom_df):
+ output = geom_df.to_xml(parser=parser)
+ output = equalize_decl(output)
+
+ assert output == na_expected
+
+
+def test_na_empty_str_elem_option(parser, geom_df):
+ output = geom_df.to_xml(na_rep="", parser=parser)
+ output = equalize_decl(output)
+
+ assert output == na_expected
+
+
+def test_na_empty_elem_option(parser, geom_df):
+ expected = """\
+
+
+
+ 0
+ square
+ 360
+ 4.0
+
+
+ 1
+ circle
+ 360
+ 0.0
+
+
+ 2
+ triangle
+ 180
+ 3.0
+
+"""
+
+ output = geom_df.to_xml(na_rep="0.0", parser=parser)
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+# ATTR_COLS
+
+
+def test_attrs_cols_nan_output(parser, geom_df):
+ expected = """\
+
+
+
+
+
+"""
+
+ output = geom_df.to_xml(attr_cols=["shape", "degrees", "sides"], parser=parser)
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+def test_attrs_cols_prefix(parser, geom_df):
+ expected = """\
+
+
+
+
+
+ """
+
+ output = geom_df.to_xml(
+ attr_cols=["index", "shape", "degrees", "sides"],
+ namespaces={"doc": "http://example.xom"},
+ prefix="doc",
+ parser=parser,
+ )
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+def test_attrs_unknown_column(parser, geom_df):
+ with pytest.raises(KeyError, match=("no valid column")):
+ geom_df.to_xml(attr_cols=["shape", "degree", "sides"], parser=parser)
+
+
+def test_attrs_wrong_type(parser, geom_df):
+ with pytest.raises(TypeError, match=("is not a valid type for attr_cols")):
+ geom_df.to_xml(attr_cols='"shape", "degree", "sides"', parser=parser)
+
+
+# ELEM_COLS
+
+
+def test_elems_cols_nan_output(parser, geom_df):
+ elems_cols_expected = """\
+
+
+
+ 360
+ 4.0
+ square
+
+
+ 360
+
+ circle
+
+
+ 180
+ 3.0
+ triangle
+
+"""
+
+ output = geom_df.to_xml(
+ index=False, elem_cols=["degrees", "sides", "shape"], parser=parser
+ )
+ output = equalize_decl(output)
+
+ assert output == elems_cols_expected
+
+
+def test_elems_unknown_column(parser, geom_df):
+ with pytest.raises(KeyError, match=("no valid column")):
+ geom_df.to_xml(elem_cols=["shape", "degree", "sides"], parser=parser)
+
+
+def test_elems_wrong_type(parser, geom_df):
+ with pytest.raises(TypeError, match=("is not a valid type for elem_cols")):
+ geom_df.to_xml(elem_cols='"shape", "degree", "sides"', parser=parser)
+
+
+def test_elems_and_attrs_cols(parser, geom_df):
+ elems_cols_expected = """\
+
+
+
+ 360
+ 4.0
+
+
+ 360
+
+
+
+ 180
+ 3.0
+
+"""
+
+ output = geom_df.to_xml(
+ index=False,
+ elem_cols=["degrees", "sides"],
+ attr_cols=["shape"],
+ parser=parser,
+ )
+ output = equalize_decl(output)
+
+ assert output == elems_cols_expected
+
+
+# HIERARCHICAL COLUMNS
+
+
+def test_hierarchical_columns(parser, planet_df):
+ expected = """\
+
+
+
+ inner
+ terrestrial
+ 4
+ 11.81
+ 2.95
+
+
+ outer
+ gas giant
+ 2
+ 2466.5
+ 1233.25
+
+
+ outer
+ ice giant
+ 2
+ 189.23
+ 94.61
+
+
+ All
+
+ 8
+ 2667.54
+ 333.44
+
+"""
+
+ pvt = planet_df.pivot_table(
+ index=["location", "type"],
+ values="mass",
+ aggfunc=["count", "sum", "mean"],
+ margins=True,
+ ).round(2)
+
+ output = pvt.to_xml(parser=parser)
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+def test_hierarchical_attrs_columns(parser, planet_df):
+ expected = """\
+
+
+
+
+
+
+"""
+
+ pvt = planet_df.pivot_table(
+ index=["location", "type"],
+ values="mass",
+ aggfunc=["count", "sum", "mean"],
+ margins=True,
+ ).round(2)
+
+ output = pvt.to_xml(attr_cols=list(pvt.reset_index().columns.values), parser=parser)
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+# MULTIINDEX
+
+
+def test_multi_index(parser, planet_df):
+ expected = """\
+
+
+
+ inner
+ terrestrial
+ 4
+ 11.81
+ 2.95
+
+
+ outer
+ gas giant
+ 2
+ 2466.5
+ 1233.25
+
+
+ outer
+ ice giant
+ 2
+ 189.23
+ 94.61
+
+"""
+
+ agg = (
+ planet_df.groupby(["location", "type"])["mass"]
+ .agg(["count", "sum", "mean"])
+ .round(2)
+ )
+
+ output = agg.to_xml(parser=parser)
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+def test_multi_index_attrs_cols(parser, planet_df):
+ expected = """\
+
+
+
+
+
+"""
+
+ agg = (
+ planet_df.groupby(["location", "type"])["mass"]
+ .agg(["count", "sum", "mean"])
+ .round(2)
+ )
+ output = agg.to_xml(attr_cols=list(agg.reset_index().columns.values), parser=parser)
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+# NAMESPACE
+
+
+def test_default_namespace(parser, geom_df):
+ expected = """\
+
+
+
+ 0
+ square
+ 360
+ 4.0
+
+
+ 1
+ circle
+ 360
+
+
+
+ 2
+ triangle
+ 180
+ 3.0
+
+"""
+
+ output = geom_df.to_xml(namespaces={"": "http://example.com"}, parser=parser)
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+def test_unused_namespaces(parser, geom_df):
+ expected = """\
+
+
+
+ 0
+ square
+ 360
+ 4.0
+
+
+ 1
+ circle
+ 360
+
+
+
+ 2
+ triangle
+ 180
+ 3.0
+
+"""
+
+ output = geom_df.to_xml(
+ namespaces={"oth": "http://other.org", "ex": "http://example.com"},
+ parser=parser,
+ )
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+# PREFIX
+
+
+def test_namespace_prefix(parser, geom_df):
+ expected = """\
+
+
+
+ 0
+ square
+ 360
+ 4.0
+
+
+ 1
+ circle
+ 360
+
+
+
+ 2
+ triangle
+ 180
+ 3.0
+
+ """
+
+ output = geom_df.to_xml(
+ namespaces={"doc": "http://example.com"}, prefix="doc", parser=parser
+ )
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+def test_missing_prefix_in_nmsp(parser, geom_df):
+ with pytest.raises(KeyError, match=("doc is not included in namespaces")):
+ geom_df.to_xml(
+ namespaces={"": "http://example.com"}, prefix="doc", parser=parser
+ )
+
+
+def test_namespace_prefix_and_default(parser, geom_df):
+ expected = """\
+
+
+
+ 0
+ square
+ 360
+ 4.0
+
+
+ 1
+ circle
+ 360
+
+
+
+ 2
+ triangle
+ 180
+ 3.0
+
+ """
+
+ output = geom_df.to_xml(
+ namespaces={"": "http://example.com", "doc": "http://other.org"},
+ prefix="doc",
+ parser=parser,
+ )
+ output = equalize_decl(output)
+
+ assert output == expected
+
+
+# ENCODING
+
+encoding_expected = """\
+
+
+
+ 0
+ 1
+ José
+ Sofía
+
+
+ 1
+ 2
+ Luis
+ Valentina
+
+
+ 2
+ 3
+ Carlos
+ Isabella
+
+
+ 3
+ 4
+ Juan
+ Camila
+
+
+ 4
+ 5
+ Jorge
+ Valeria
+
+"""
+
+
+def test_encoding_option_str(xml_baby_names, parser):
+ df_file = read_xml(xml_baby_names, parser=parser, encoding="ISO-8859-1").head(5)
+
+ output = df_file.to_xml(encoding="ISO-8859-1", parser=parser)
+
+ if output is not None:
+ # etree and lxml differ on quotes and case in xml declaration
+ output = output.replace(
+ '
+
+ 0
+ square
+ 360
+ 4.0
+
+
+ 1
+ circle
+ 360
+
+
+
+ 2
+ triangle
+ 180
+ 3.0
+
+"""
+
+ output = geom_df.to_xml(xml_declaration=False)
+
+ assert output == expected
+
+
+def test_no_pretty_print_with_decl(parser, geom_df):
+ expected = (
+ "\n"
+ "0 square "
+ "360 4.0 |
"
+ "1 circle 360"
+ "
2 "
+ "triangle 180 3.0 "
+ "
"
+ )
+
+ output = geom_df.to_xml(pretty_print=False, parser=parser)
+ output = equalize_decl(output)
+
+ # etree adds space for closed tags
+ if output is not None:
+ output = output.replace(" />", "/>")
+
+ assert output == expected
+
+
+def test_no_pretty_print_no_decl(parser, geom_df):
+ expected = (
+ "0 square "
+ "360 4.0 |
"
+ "1 circle 360"
+ "
2 "
+ "triangle 180 3.0 "
+ "
"
+ )
+
+ output = geom_df.to_xml(xml_declaration=False, pretty_print=False, parser=parser)
+
+ # etree adds space for closed tags
+ if output is not None:
+ output = output.replace(" />", "/>")
+
+ assert output == expected
+
+
+# PARSER
+
+
+@td.skip_if_installed("lxml")
+def test_default_parser_no_lxml(geom_df):
+ with pytest.raises(
+ ImportError, match=("lxml not found, please install or use the etree parser.")
+ ):
+ geom_df.to_xml()
+
+
+def test_unknown_parser(geom_df):
+ with pytest.raises(
+ ValueError, match=("Values for parser can only be lxml or etree.")
+ ):
+ geom_df.to_xml(parser="bs4")
+
+
+# STYLESHEET
+
+xsl_expected = """\
+
+
+
+ 0
+ square
+ 360
+ 4.0
+
+
+ 1
+ circle
+ 360
+
+
+
+ 2
+ triangle
+ 180
+ 3.0
+
+"""
+
+
+def test_stylesheet_file_like(xsl_row_field_output, mode, geom_df):
+ pytest.importorskip("lxml")
+ with open(
+ xsl_row_field_output, mode, encoding="utf-8" if mode == "r" else None
+ ) as f:
+ assert geom_df.to_xml(stylesheet=f) == xsl_expected
+
+
+def test_stylesheet_io(xsl_row_field_output, mode, geom_df):
+ # note: By default the bodies of untyped functions are not checked,
+ # consider using --check-untyped-defs
+ pytest.importorskip("lxml")
+ xsl_obj: BytesIO | StringIO # type: ignore[annotation-unchecked]
+
+ with open(
+ xsl_row_field_output, mode, encoding="utf-8" if mode == "r" else None
+ ) as f:
+ if mode == "rb":
+ xsl_obj = BytesIO(f.read())
+ else:
+ xsl_obj = StringIO(f.read())
+
+ output = geom_df.to_xml(stylesheet=xsl_obj)
+
+ assert output == xsl_expected
+
+
+def test_stylesheet_buffered_reader(xsl_row_field_output, mode, geom_df):
+ pytest.importorskip("lxml")
+ with open(
+ xsl_row_field_output, mode, encoding="utf-8" if mode == "r" else None
+ ) as f:
+ output = geom_df.to_xml(stylesheet=f)
+
+ assert output == xsl_expected
+
+
+def test_stylesheet_wrong_path(geom_df):
+ pytest.importorskip("lxml.etree")
+
+ xsl = os.path.join("does", "not", "exist", "row_field_output.xslt")
+
+ with pytest.raises(
+ FileNotFoundError, match=r"\[Errno 2\] No such file or director"
+ ):
+ geom_df.to_xml(stylesheet=xsl)
+
+
+@pytest.mark.parametrize("val", [StringIO(""), BytesIO(b"")])
+def test_empty_string_stylesheet(val, geom_df):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ msg = "|".join(
+ [
+ "Document is empty",
+ "Start tag expected, '<' not found",
+ # Seen on Mac with lxml 4.9.1
+ r"None \(line 0\)",
+ ]
+ )
+
+ with pytest.raises(lxml_etree.XMLSyntaxError, match=msg):
+ geom_df.to_xml(stylesheet=val)
+
+
+def test_incorrect_xsl_syntax(geom_df):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ xsl = """\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+ with pytest.raises(
+ lxml_etree.XMLSyntaxError, match="Opening and ending tag mismatch"
+ ):
+ geom_df.to_xml(stylesheet=StringIO(xsl))
+
+
+def test_incorrect_xsl_eval(geom_df):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ xsl = """\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+ with pytest.raises(lxml_etree.XSLTParseError, match="failed to compile"):
+ geom_df.to_xml(stylesheet=StringIO(xsl))
+
+
+def test_incorrect_xsl_apply(geom_df, temp_file):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ xsl = """\
+
+
+
+
+
+
+
+
+
+ """
+
+ with pytest.raises(lxml_etree.XSLTApplyError, match="Cannot resolve URI"):
+ geom_df.to_xml(temp_file, stylesheet=StringIO(xsl))
+
+
+def test_stylesheet_with_etree(geom_df):
+ xsl = """\
+
+
+
+
+
+
+
+
+ """
+
+ with pytest.raises(ValueError, match="To use stylesheet, you need lxml installed"):
+ geom_df.to_xml(parser="etree", stylesheet=StringIO(xsl))
+
+
+def test_style_to_csv(geom_df):
+ pytest.importorskip("lxml")
+ xsl = """\
+
+
+
+
+ ,
+
+ ,shape,degrees,sides
+
+
+
+
+
+
+
+ """
+
+ out_csv = geom_df.to_csv(lineterminator="\n")
+
+ if out_csv is not None:
+ out_csv = out_csv.strip()
+ out_xml = geom_df.to_xml(stylesheet=StringIO(xsl))
+
+ assert out_csv == out_xml
+
+
+def test_style_to_string(geom_df):
+ pytest.importorskip("lxml")
+ xsl = """\
+
+
+
+
+
+
+ shape degrees sides
+
+
+
+
+
+
+
+ """
+
+ out_str = geom_df.to_string()
+ out_xml = geom_df.to_xml(na_rep="NaN", stylesheet=StringIO(xsl))
+
+ assert out_xml == out_str
+
+
+def test_style_to_json(geom_df):
+ pytest.importorskip("lxml")
+ xsl = """\
+
+
+
+
+ "
+
+
+ {"shape":{
+
+ },"degrees":{
+
+ },"sides":{
+
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+
+
+ """
+
+ out_json = geom_df.to_json()
+ out_xml = geom_df.to_xml(stylesheet=StringIO(xsl))
+
+ assert out_json == out_xml
+
+
+# COMPRESSION
+
+
+geom_xml = """\
+
+
+
+ 0
+ square
+ 360
+ 4.0
+
+
+ 1
+ circle
+ 360
+
+
+
+ 2
+ triangle
+ 180
+ 3.0
+
+"""
+
+
+def test_compression_output(parser, compression_only, geom_df, temp_file):
+ path = temp_file
+ geom_df.to_xml(path, parser=parser, compression=compression_only)
+
+ with get_handle(
+ path,
+ "r",
+ compression=compression_only,
+ ) as handle_obj:
+ output = handle_obj.handle.read()
+
+ output = equalize_decl(output)
+
+ assert geom_xml == output.strip()
+
+
+def test_filename_and_suffix_comp(
+ parser, compression_only, geom_df, compression_to_extension, tmp_path
+):
+ compfile = "xml." + compression_to_extension[compression_only]
+ path = tmp_path / compfile
+ geom_df.to_xml(path, parser=parser, compression=compression_only)
+
+ with get_handle(
+ path,
+ "r",
+ compression=compression_only,
+ ) as handle_obj:
+ output = handle_obj.handle.read()
+
+ output = equalize_decl(output)
+
+ assert geom_xml == output.strip()
+
+
+def test_ea_dtypes(any_numeric_ea_dtype, parser):
+ # GH#43903
+ expected = """
+
+
+ 0
+
+
+"""
+ df = DataFrame({"a": [NA]}).astype(any_numeric_ea_dtype)
+ result = df.to_xml(parser=parser)
+ assert equalize_decl(result).strip() == expected
+
+
+def test_unsupported_compression(parser, geom_df, temp_file):
+ with pytest.raises(ValueError, match="Unrecognized compression type"):
+ path = temp_file
+ geom_df.to_xml(path, parser=parser, compression="7z")
+
+
+# STORAGE OPTIONS
+
+
+@pytest.mark.single_cpu
+def test_s3_permission_output(parser, s3_bucket_public, geom_df):
+ s3fs = pytest.importorskip("s3fs")
+ pytest.importorskip("lxml")
+
+ with tm.external_error_raised((PermissionError, FileNotFoundError)):
+ fs = s3fs.S3FileSystem(anon=True)
+ fs.ls(s3_bucket_public.name)
+
+ geom_df.to_xml(
+ f"s3://{s3_bucket_public.name}/geom.xml", compression="zip", parser=parser
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/test_xml.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/test_xml.py
new file mode 100644
index 0000000000000000000000000000000000000000..8317a3337e0e19bcf1df35ec45f310eae907b48a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/test_xml.py
@@ -0,0 +1,2067 @@
+from __future__ import annotations
+
+from io import (
+ BytesIO,
+ StringIO,
+)
+from lzma import LZMAError
+import os
+from tarfile import ReadError
+from urllib.error import HTTPError
+from xml.etree.ElementTree import ParseError
+from zipfile import BadZipFile
+
+import numpy as np
+import pytest
+
+from pandas.compat import WASM
+from pandas.compat._optional import import_optional_dependency
+from pandas.errors import (
+ EmptyDataError,
+ ParserError,
+)
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ NA,
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+from pandas.io.common import get_handle
+from pandas.io.xml import read_xml
+
+# CHECK LIST
+
+# [x] - ValueError: "Values for parser can only be lxml or etree."
+
+# etree
+# [X] - ImportError: "lxml not found, please install or use the etree parser."
+# [X] - TypeError: "expected str, bytes or os.PathLike object, not NoneType"
+# [X] - ValueError: "Either element or attributes can be parsed not both."
+# [X] - ValueError: "xpath does not return any nodes..."
+# [X] - SyntaxError: "You have used an incorrect or unsupported XPath"
+# [X] - ValueError: "names does not match length of child elements in xpath."
+# [X] - TypeError: "...is not a valid type for names"
+# [X] - ValueError: "To use stylesheet, you need lxml installed..."
+# [] - URLError: (GENERAL ERROR WITH HTTPError AS SUBCLASS)
+# [X] - HTTPError: "HTTP Error 404: Not Found"
+# [] - OSError: (GENERAL ERROR WITH FileNotFoundError AS SUBCLASS)
+# [X] - FileNotFoundError: "No such file or directory"
+# [] - ParseError (FAILSAFE CATCH ALL FOR VERY COMPLEX XML)
+# [X] - UnicodeDecodeError: "'utf-8' codec can't decode byte 0xe9..."
+# [X] - UnicodeError: "UTF-16 stream does not start with BOM"
+# [X] - BadZipFile: "File is not a zip file"
+# [X] - OSError: "Invalid data stream"
+# [X] - LZMAError: "Input format not supported by decoder"
+# [X] - ValueError: "Unrecognized compression type"
+# [X] - PermissionError: "Forbidden"
+
+# lxml
+# [X] - ValueError: "Either element or attributes can be parsed not both."
+# [X] - AttributeError: "__enter__"
+# [X] - XSLTApplyError: "Cannot resolve URI"
+# [X] - XSLTParseError: "document is not a stylesheet"
+# [X] - ValueError: "xpath does not return any nodes."
+# [X] - XPathEvalError: "Invalid expression"
+# [] - XPathSyntaxError: (OLD VERSION IN lxml FOR XPATH ERRORS)
+# [X] - TypeError: "empty namespace prefix is not supported in XPath"
+# [X] - ValueError: "names does not match length of child elements in xpath."
+# [X] - TypeError: "...is not a valid type for names"
+# [X] - LookupError: "unknown encoding"
+# [] - URLError: (USUALLY DUE TO NETWORKING)
+# [X - HTTPError: "HTTP Error 404: Not Found"
+# [X] - OSError: "failed to load external entity"
+# [X] - XMLSyntaxError: "Start tag expected, '<' not found"
+# [] - ParserError: (FAILSAFE CATCH ALL FOR VERY COMPLEX XML
+# [X] - ValueError: "Values for parser can only be lxml or etree."
+# [X] - UnicodeDecodeError: "'utf-8' codec can't decode byte 0xe9..."
+# [X] - UnicodeError: "UTF-16 stream does not start with BOM"
+# [X] - BadZipFile: "File is not a zip file"
+# [X] - OSError: "Invalid data stream"
+# [X] - LZMAError: "Input format not supported by decoder"
+# [X] - ValueError: "Unrecognized compression type"
+# [X] - PermissionError: "Forbidden"
+
+geom_df = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": [4, np.nan, 3],
+ }
+)
+
+xml_default_nmsp = """\
+
+
+
+ square
+ 360
+ 4
+
+
+ circle
+ 360
+
+
+
+ triangle
+ 180
+ 3
+
+"""
+
+xml_prefix_nmsp = """\
+
+
+
+ square
+ 360
+ 4.0
+
+
+ circle
+ 360
+
+
+
+ triangle
+ 180
+ 3.0
+
+ """
+
+
+df_kml = DataFrame(
+ {
+ "id": {
+ 0: "ID_00001",
+ 1: "ID_00002",
+ 2: "ID_00003",
+ 3: "ID_00004",
+ 4: "ID_00005",
+ },
+ "name": {
+ 0: "Blue Line (Forest Park)",
+ 1: "Red, Purple Line",
+ 2: "Red, Purple Line",
+ 3: "Red, Purple Line",
+ 4: "Red, Purple Line",
+ },
+ "styleUrl": {
+ 0: "#LineStyle01",
+ 1: "#LineStyle01",
+ 2: "#LineStyle01",
+ 3: "#LineStyle01",
+ 4: "#LineStyle01",
+ },
+ "extrude": {0: 0, 1: 0, 2: 0, 3: 0, 4: 0},
+ "altitudeMode": {
+ 0: "clampedToGround",
+ 1: "clampedToGround",
+ 2: "clampedToGround",
+ 3: "clampedToGround",
+ 4: "clampedToGround",
+ },
+ "coordinates": {
+ 0: (
+ "-87.77678526964958,41.8708863930319,0 "
+ "-87.77826234150609,41.87097820122218,0 "
+ "-87.78251583439344,41.87130129991005,0 "
+ "-87.78418294588424,41.87145055520308,0 "
+ "-87.7872369165933,41.8717239119163,0 "
+ "-87.79160214925886,41.87210797280065,0"
+ ),
+ 1: (
+ "-87.65758750947528,41.96427269188822,0 "
+ "-87.65802133507393,41.96581929055245,0 "
+ "-87.65819033925305,41.96621846093642,0 "
+ "-87.6583189819129,41.96650362897086,0 "
+ "-87.65835858701473,41.96669002089185,0 "
+ "-87.65838428411853,41.96688150295095,0 "
+ "-87.65842208882658,41.96745896091846,0 "
+ "-87.65846556843937,41.9683761425439,0 "
+ "-87.65849296214573,41.96913893870342,0"
+ ),
+ 2: (
+ "-87.65492939166126,41.95377494531437,0 "
+ "-87.65557043199591,41.95376544118533,0 "
+ "-87.65606302030132,41.95376391658746,0 "
+ "-87.65623502146268,41.95377379126367,0 "
+ "-87.65634748981634,41.95380103566435,0 "
+ "-87.65646537904269,41.95387703994676,0 "
+ "-87.65656532461145,41.95396622645799,0 "
+ "-87.65664760856414,41.95404201996044,0 "
+ "-87.65671750555913,41.95416647054043,0 "
+ "-87.65673983607117,41.95429949810849,0 "
+ "-87.65673866475777,41.95441024240925,0 "
+ "-87.6567690255541,41.95490657227902,0 "
+ "-87.65683672482363,41.95692259283837,0 "
+ "-87.6568900886376,41.95861070983142,0 "
+ "-87.65699865558875,41.96181418669004,0 "
+ "-87.65756347177603,41.96397045777844,0 "
+ "-87.65758750947528,41.96427269188822,0"
+ ),
+ 3: (
+ "-87.65362593118043,41.94742799535678,0 "
+ "-87.65363554415794,41.94819886386848,0 "
+ "-87.6536456393239,41.95059994675451,0 "
+ "-87.65365831235026,41.95108288489359,0 "
+ "-87.6536604873874,41.9519954657554,0 "
+ "-87.65362592053201,41.95245597302328,0 "
+ "-87.65367158496069,41.95311153649393,0 "
+ "-87.65368468595476,41.9533202828916,0 "
+ "-87.65369271253692,41.95343095587119,0 "
+ "-87.65373335834569,41.95351536301472,0 "
+ "-87.65378605844126,41.95358212680591,0 "
+ "-87.65385067928185,41.95364452823767,0 "
+ "-87.6539390793817,41.95370263886964,0 "
+ "-87.6540786298351,41.95373403675265,0 "
+ "-87.65430648647626,41.9537535411832,0 "
+ "-87.65492939166126,41.95377494531437,0"
+ ),
+ 4: (
+ "-87.65345391792157,41.94217681262115,0 "
+ "-87.65342448305786,41.94237224420864,0 "
+ "-87.65339745703922,41.94268217746244,0 "
+ "-87.65337753982941,41.94288140770284,0 "
+ "-87.65336256753105,41.94317369618263,0 "
+ "-87.65338799707138,41.94357253961736,0 "
+ "-87.65340240886648,41.94389158188269,0 "
+ "-87.65341837392448,41.94406444407721,0 "
+ "-87.65342275247338,41.94421065714904,0 "
+ "-87.65347469646018,41.94434829382345,0 "
+ "-87.65351486483024,41.94447699917548,0 "
+ "-87.65353483605053,41.9453896864472,0 "
+ "-87.65361975532807,41.94689193720703,0 "
+ "-87.65362593118043,41.94742799535678,0"
+ ),
+ },
+ },
+ index=range(5),
+)
+
+
+def test_literal_xml_raises():
+ # GH 53809
+ pytest.importorskip("lxml")
+ msg = "|".join([r".*No such file or directory", r".*Invalid argument"])
+
+ with pytest.raises((FileNotFoundError, OSError), match=msg):
+ read_xml(xml_default_nmsp)
+
+
+@pytest.fixture(params=["rb", "r"])
+def mode(request):
+ return request.param
+
+
+@pytest.fixture(params=[pytest.param("lxml", marks=td.skip_if_no("lxml")), "etree"])
+def parser(request):
+ return request.param
+
+
+def read_xml_iterparse(data, temp_path, **kwargs):
+ temp_path.write_text(data, encoding="utf-8")
+ return read_xml(temp_path, **kwargs)
+
+
+def read_xml_iterparse_comp(comp_path, compression_only, temp_path, **kwargs):
+ with get_handle(comp_path, "r", compression=compression_only) as handles:
+ temp_path.write_text(handles.handle.read(), encoding="utf-8")
+ return read_xml(temp_path, **kwargs)
+
+
+# FILE / URL
+
+
+def test_parser_consistency_file(xml_books):
+ pytest.importorskip("lxml")
+ df_file_lxml = read_xml(xml_books, parser="lxml")
+ df_file_etree = read_xml(xml_books, parser="etree")
+
+ df_iter_lxml = read_xml(
+ xml_books,
+ parser="lxml",
+ iterparse={"book": ["category", "title", "year", "author", "price"]},
+ )
+ df_iter_etree = read_xml(
+ xml_books,
+ parser="etree",
+ iterparse={"book": ["category", "title", "year", "author", "price"]},
+ )
+
+ tm.assert_frame_equal(df_file_lxml, df_file_etree)
+ tm.assert_frame_equal(df_file_lxml, df_iter_lxml)
+ tm.assert_frame_equal(df_iter_lxml, df_iter_etree)
+
+
+@pytest.mark.network
+@pytest.mark.single_cpu
+def test_parser_consistency_url(parser, httpserver):
+ httpserver.serve_content(content=xml_default_nmsp)
+
+ df_xpath = read_xml(StringIO(xml_default_nmsp), parser=parser)
+ df_iter = read_xml(
+ BytesIO(xml_default_nmsp.encode()),
+ parser=parser,
+ iterparse={"row": ["shape", "degrees", "sides"]},
+ )
+
+ tm.assert_frame_equal(df_xpath, df_iter)
+
+
+def test_file_like(xml_books, parser, mode):
+ with open(xml_books, mode, encoding="utf-8" if mode == "r" else None) as f:
+ df_file = read_xml(f, parser=parser)
+
+ df_expected = DataFrame(
+ {
+ "category": ["cooking", "children", "web"],
+ "title": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "author": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "year": [2005, 2005, 2003],
+ "price": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_file, df_expected)
+
+
+def test_file_io(xml_books, parser, mode):
+ with open(xml_books, mode, encoding="utf-8" if mode == "r" else None) as f:
+ xml_obj = f.read()
+
+ df_io = read_xml(
+ (BytesIO(xml_obj) if isinstance(xml_obj, bytes) else StringIO(xml_obj)),
+ parser=parser,
+ )
+
+ df_expected = DataFrame(
+ {
+ "category": ["cooking", "children", "web"],
+ "title": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "author": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "year": [2005, 2005, 2003],
+ "price": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_io, df_expected)
+
+
+def test_file_buffered_reader_string(xml_books, parser, mode):
+ with open(xml_books, mode, encoding="utf-8" if mode == "r" else None) as f:
+ xml_obj = f.read()
+
+ if mode == "rb":
+ xml_obj = StringIO(xml_obj.decode())
+ elif mode == "r":
+ xml_obj = StringIO(xml_obj)
+
+ df_str = read_xml(xml_obj, parser=parser)
+
+ df_expected = DataFrame(
+ {
+ "category": ["cooking", "children", "web"],
+ "title": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "author": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "year": [2005, 2005, 2003],
+ "price": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_str, df_expected)
+
+
+def test_file_buffered_reader_no_xml_declaration(xml_books, parser, mode):
+ with open(xml_books, mode, encoding="utf-8" if mode == "r" else None) as f:
+ next(f)
+ xml_obj = f.read()
+
+ if mode == "rb":
+ xml_obj = StringIO(xml_obj.decode())
+ elif mode == "r":
+ xml_obj = StringIO(xml_obj)
+
+ df_str = read_xml(xml_obj, parser=parser)
+
+ df_expected = DataFrame(
+ {
+ "category": ["cooking", "children", "web"],
+ "title": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "author": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "year": [2005, 2005, 2003],
+ "price": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_str, df_expected)
+
+
+def test_string_charset(parser):
+ txt = "<中文標籤>1 2
中文標籤>"
+
+ df_str = read_xml(StringIO(txt), parser=parser)
+
+ df_expected = DataFrame({"c1": 1, "c2": 2}, index=range(1))
+
+ tm.assert_frame_equal(df_str, df_expected)
+
+
+def test_file_charset(xml_doc_ch_utf, parser):
+ df_file = read_xml(xml_doc_ch_utf, parser=parser)
+
+ df_expected = DataFrame(
+ {
+ "問": [
+ "問 若箇是邪而言破邪 何者是正而道(Sorry, this is Big5 only)申正",
+ "問 既破有得申無得 亦應但破性執申假名以不",
+ "問 既破性申假 亦應但破有申無 若有無兩洗 亦應性假雙破耶",
+ ],
+ "答": [
+ "".join(
+ [
+ "答 邪既無量 正亦多途 大略為言不出二種 謂",
+ "有得與無得 有得是邪須破 無得是正須申\n\t\t故",
+ ]
+ ),
+ None,
+ "答 不例 有無皆是性 所以須雙破 既分性假異 故有破不破",
+ ],
+ "a": [
+ None,
+ "答 性執是有得 假名是無得 今破有得申無得 即是破性執申假名也",
+ None,
+ ],
+ }
+ )
+
+ tm.assert_frame_equal(df_file, df_expected)
+
+
+def test_file_handle_close(xml_books, parser):
+ with open(xml_books, "rb") as f:
+ read_xml(BytesIO(f.read()), parser=parser)
+
+ assert not f.closed
+
+
+@pytest.mark.parametrize("val", ["", b""])
+def test_empty_string_lxml(val):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ msg = "|".join(
+ [
+ "Document is empty",
+ # Seen on Mac with lxml 4.91
+ r"None \(line 0\)",
+ ]
+ )
+ if isinstance(val, str):
+ data = StringIO(val)
+ else:
+ data = BytesIO(val)
+ with pytest.raises(lxml_etree.XMLSyntaxError, match=msg):
+ read_xml(data, parser="lxml")
+
+
+@pytest.mark.parametrize("val", ["", b""])
+def test_empty_string_etree(val):
+ if isinstance(val, str):
+ data = StringIO(val)
+ else:
+ data = BytesIO(val)
+ with pytest.raises(ParseError, match="no element found"):
+ read_xml(data, parser="etree")
+
+
+@pytest.mark.skipif(WASM, reason="limited file system access on WASM")
+def test_wrong_file_path(parser):
+ filename = os.path.join("does", "not", "exist", "books.xml")
+
+ with pytest.raises(
+ FileNotFoundError, match=r"\[Errno 2\] No such file or directory"
+ ):
+ read_xml(filename, parser=parser)
+
+
+@pytest.mark.network
+@pytest.mark.single_cpu
+def test_url(httpserver, xml_file):
+ pytest.importorskip("lxml")
+ with open(xml_file, encoding="utf-8") as f:
+ httpserver.serve_content(content=f.read())
+ df_url = read_xml(httpserver.url, xpath=".//book[count(*)=4]")
+
+ df_expected = DataFrame(
+ {
+ "category": ["cooking", "children", "web"],
+ "title": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "author": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "year": [2005, 2005, 2003],
+ "price": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_url, df_expected)
+
+
+@pytest.mark.network
+@pytest.mark.single_cpu
+def test_wrong_url(parser, httpserver):
+ httpserver.serve_content("NOT FOUND", code=404)
+ try:
+ with pytest.raises(HTTPError, match=("HTTP Error 404: NOT FOUND")) as err:
+ read_xml(httpserver.url, xpath=".//book[count(*)=4]", parser=parser)
+ finally:
+ if isinstance(err.value, HTTPError):
+ # Has a file-like handle that we can close
+ # https://docs.python.org/3/library/urllib.error.html#urllib.error.HTTPError
+ err.value.close()
+
+
+# CONTENT
+
+
+def test_whitespace(parser, temp_file):
+ xml = """
+
+
+
+ square
+
+ 360
+
+
+
+ circle
+
+ 360
+
+
+
+ triangle
+
+ 180
+
+ """
+
+ df_xpath = read_xml(StringIO(xml), parser=parser, dtype="string")
+
+ df_iter = read_xml_iterparse(
+ xml,
+ temp_file,
+ parser=parser,
+ iterparse={"row": ["sides", "shape", "degrees"]},
+ dtype="string",
+ )
+
+ df_expected = DataFrame(
+ {
+ "sides": [" 4 ", " 0 ", " 3 "],
+ "shape": [
+ "\n square\n ",
+ "\n circle\n ",
+ "\n triangle\n ",
+ ],
+ "degrees": ["\t360\t", "\t360\t", "\t180\t"],
+ },
+ dtype="string",
+ )
+
+ tm.assert_frame_equal(df_xpath, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+# XPATH
+
+
+def test_empty_xpath_lxml(xml_books):
+ pytest.importorskip("lxml")
+ with pytest.raises(ValueError, match=("xpath does not return any nodes")):
+ read_xml(xml_books, xpath=".//python", parser="lxml")
+
+
+def test_bad_xpath_etree(xml_books):
+ with pytest.raises(
+ SyntaxError, match=("You have used an incorrect or unsupported XPath")
+ ):
+ read_xml(xml_books, xpath=".//[book]", parser="etree")
+
+
+def test_bad_xpath_lxml(xml_books):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ with pytest.raises(lxml_etree.XPathEvalError, match=("Invalid expression")):
+ read_xml(xml_books, xpath=".//[book]", parser="lxml")
+
+
+# NAMESPACE
+
+
+def test_default_namespace(parser, temp_file):
+ df_nmsp = read_xml(
+ StringIO(xml_default_nmsp),
+ xpath=".//ns:row",
+ namespaces={"ns": "http://example.com"},
+ parser=parser,
+ )
+
+ df_iter = read_xml_iterparse(
+ xml_default_nmsp,
+ temp_file,
+ parser=parser,
+ iterparse={"row": ["shape", "degrees", "sides"]},
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": [4.0, float("nan"), 3.0],
+ }
+ )
+
+ tm.assert_frame_equal(df_nmsp, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_prefix_namespace(parser, temp_file):
+ df_nmsp = read_xml(
+ StringIO(xml_prefix_nmsp),
+ xpath=".//doc:row",
+ namespaces={"doc": "http://example.com"},
+ parser=parser,
+ )
+ df_iter = read_xml_iterparse(
+ xml_prefix_nmsp,
+ temp_file,
+ parser=parser,
+ iterparse={"row": ["shape", "degrees", "sides"]},
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": [4.0, float("nan"), 3.0],
+ }
+ )
+
+ tm.assert_frame_equal(df_nmsp, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_consistency_default_namespace():
+ pytest.importorskip("lxml")
+ df_lxml = read_xml(
+ StringIO(xml_default_nmsp),
+ xpath=".//ns:row",
+ namespaces={"ns": "http://example.com"},
+ parser="lxml",
+ )
+
+ df_etree = read_xml(
+ StringIO(xml_default_nmsp),
+ xpath=".//doc:row",
+ namespaces={"doc": "http://example.com"},
+ parser="etree",
+ )
+
+ tm.assert_frame_equal(df_lxml, df_etree)
+
+
+def test_consistency_prefix_namespace():
+ pytest.importorskip("lxml")
+ df_lxml = read_xml(
+ StringIO(xml_prefix_nmsp),
+ xpath=".//doc:row",
+ namespaces={"doc": "http://example.com"},
+ parser="lxml",
+ )
+
+ df_etree = read_xml(
+ StringIO(xml_prefix_nmsp),
+ xpath=".//doc:row",
+ namespaces={"doc": "http://example.com"},
+ parser="etree",
+ )
+
+ tm.assert_frame_equal(df_lxml, df_etree)
+
+
+# PREFIX
+
+
+def test_missing_prefix_with_default_namespace(xml_books, parser):
+ with pytest.raises(ValueError, match=("xpath does not return any nodes")):
+ read_xml(xml_books, xpath=".//Placemark", parser=parser)
+
+
+def test_missing_prefix_definition_etree(kml_cta_rail_lines):
+ with pytest.raises(SyntaxError, match=("you used an undeclared namespace prefix")):
+ read_xml(kml_cta_rail_lines, xpath=".//kml:Placemark", parser="etree")
+
+
+def test_missing_prefix_definition_lxml(kml_cta_rail_lines):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ with pytest.raises(lxml_etree.XPathEvalError, match=("Undefined namespace prefix")):
+ read_xml(kml_cta_rail_lines, xpath=".//kml:Placemark", parser="lxml")
+
+
+@pytest.mark.parametrize("key", ["", None])
+def test_none_namespace_prefix(key):
+ pytest.importorskip("lxml")
+ with pytest.raises(
+ TypeError, match=("empty namespace prefix is not supported in XPath")
+ ):
+ read_xml(
+ StringIO(xml_default_nmsp),
+ xpath=".//kml:Placemark",
+ namespaces={key: "http://www.opengis.net/kml/2.2"},
+ parser="lxml",
+ )
+
+
+# ELEMS AND ATTRS
+
+
+def test_file_elems_and_attrs(xml_books, parser):
+ df_file = read_xml(xml_books, parser=parser)
+ df_iter = read_xml(
+ xml_books,
+ parser=parser,
+ iterparse={"book": ["category", "title", "author", "year", "price"]},
+ )
+ df_expected = DataFrame(
+ {
+ "category": ["cooking", "children", "web"],
+ "title": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "author": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "year": [2005, 2005, 2003],
+ "price": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_file, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_file_only_attrs(xml_books, parser):
+ df_file = read_xml(xml_books, attrs_only=True, parser=parser)
+ df_iter = read_xml(xml_books, parser=parser, iterparse={"book": ["category"]})
+ df_expected = DataFrame({"category": ["cooking", "children", "web"]})
+
+ tm.assert_frame_equal(df_file, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_file_only_elems(xml_books, parser):
+ df_file = read_xml(xml_books, elems_only=True, parser=parser)
+ df_iter = read_xml(
+ xml_books,
+ parser=parser,
+ iterparse={"book": ["title", "author", "year", "price"]},
+ )
+ df_expected = DataFrame(
+ {
+ "title": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "author": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "year": [2005, 2005, 2003],
+ "price": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_file, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_elem_and_attrs_only(kml_cta_rail_lines, parser):
+ with pytest.raises(
+ ValueError,
+ match=("Either element or attributes can be parsed not both"),
+ ):
+ read_xml(kml_cta_rail_lines, elems_only=True, attrs_only=True, parser=parser)
+
+
+def test_empty_attrs_only(parser):
+ xml = """
+
+
+ square
+ 360
+
+
+ circle
+ 360
+
+
+ triangle
+ 180
+
+ """
+
+ with pytest.raises(
+ ValueError,
+ match=("xpath does not return any nodes or attributes"),
+ ):
+ read_xml(StringIO(xml), xpath="./row", attrs_only=True, parser=parser)
+
+
+def test_empty_elems_only(parser):
+ xml = """
+
+
+
+
+ """
+
+ with pytest.raises(
+ ValueError,
+ match=("xpath does not return any nodes or attributes"),
+ ):
+ read_xml(StringIO(xml), xpath="./row", elems_only=True, parser=parser)
+
+
+def test_attribute_centric_xml(temp_file):
+ pytest.importorskip("lxml")
+ xml = """\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+ df_lxml = read_xml(StringIO(xml), xpath=".//station")
+ df_etree = read_xml(StringIO(xml), xpath=".//station", parser="etree")
+
+ df_iter_lx = read_xml_iterparse(
+ xml, temp_file, iterparse={"station": ["Name", "coords"]}
+ )
+ df_iter_et = read_xml_iterparse(
+ xml, temp_file, parser="etree", iterparse={"station": ["Name", "coords"]}
+ )
+
+ tm.assert_frame_equal(df_lxml, df_etree)
+ tm.assert_frame_equal(df_iter_lx, df_iter_et)
+
+
+# NAMES
+
+
+def test_names_option_output(xml_books, parser):
+ df_file = read_xml(
+ xml_books, names=["Col1", "Col2", "Col3", "Col4", "Col5"], parser=parser
+ )
+ df_iter = read_xml(
+ xml_books,
+ parser=parser,
+ names=["Col1", "Col2", "Col3", "Col4", "Col5"],
+ iterparse={"book": ["category", "title", "author", "year", "price"]},
+ )
+
+ df_expected = DataFrame(
+ {
+ "Col1": ["cooking", "children", "web"],
+ "Col2": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "Col3": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "Col4": [2005, 2005, 2003],
+ "Col5": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_file, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_repeat_names(parser, temp_file):
+ xml = """\
+
+
+ circle
+ curved
+
+
+ sphere
+ curved
+
+ """
+ df_xpath = read_xml(
+ StringIO(xml),
+ xpath=".//shape",
+ parser=parser,
+ names=["type_dim", "shape", "type_edge"],
+ )
+
+ df_iter = read_xml_iterparse(
+ xml,
+ temp_file,
+ parser=parser,
+ iterparse={"shape": ["type", "name", "type"]},
+ names=["type_dim", "shape", "type_edge"],
+ )
+
+ df_expected = DataFrame(
+ {
+ "type_dim": ["2D", "3D"],
+ "shape": ["circle", "sphere"],
+ "type_edge": ["curved", "curved"],
+ }
+ )
+
+ tm.assert_frame_equal(df_xpath, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_repeat_values_new_names(parser, temp_file):
+ xml = """\
+
+
+ rectangle
+ rectangle
+
+
+ square
+ rectangle
+
+
+ ellipse
+ ellipse
+
+
+ circle
+ ellipse
+
+ """
+ df_xpath = read_xml(
+ StringIO(xml), xpath=".//shape", parser=parser, names=["name", "group"]
+ )
+
+ df_iter = read_xml_iterparse(
+ xml,
+ temp_file,
+ parser=parser,
+ iterparse={"shape": ["name", "family"]},
+ names=["name", "group"],
+ )
+
+ df_expected = DataFrame(
+ {
+ "name": ["rectangle", "square", "ellipse", "circle"],
+ "group": ["rectangle", "rectangle", "ellipse", "ellipse"],
+ }
+ )
+
+ tm.assert_frame_equal(df_xpath, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_repeat_elements(parser, temp_file):
+ xml = """\
+
+
+ circle
+ ellipse
+ 360
+ 0
+
+
+ triangle
+ polygon
+ 180
+ 3
+
+
+ square
+ polygon
+ 360
+ 4
+
+ """
+ df_xpath = read_xml(
+ StringIO(xml),
+ xpath=".//shape",
+ parser=parser,
+ names=["name", "family", "degrees", "sides"],
+ )
+
+ df_iter = read_xml_iterparse(
+ xml,
+ temp_file,
+ parser=parser,
+ iterparse={"shape": ["value", "value", "value", "value"]},
+ names=["name", "family", "degrees", "sides"],
+ )
+
+ df_expected = DataFrame(
+ {
+ "name": ["circle", "triangle", "square"],
+ "family": ["ellipse", "polygon", "polygon"],
+ "degrees": [360, 180, 360],
+ "sides": [0, 3, 4],
+ }
+ )
+
+ tm.assert_frame_equal(df_xpath, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_names_option_wrong_length(xml_books, parser):
+ with pytest.raises(ValueError, match=("names does not match length")):
+ read_xml(xml_books, names=["Col1", "Col2", "Col3"], parser=parser)
+
+
+def test_names_option_wrong_type(xml_books, parser):
+ with pytest.raises(TypeError, match=("is not a valid type for names")):
+ read_xml(xml_books, names="Col1, Col2, Col3", parser=parser)
+
+
+# ENCODING
+
+
+def test_wrong_encoding(xml_baby_names, parser):
+ with pytest.raises(UnicodeDecodeError, match=("'utf-8' codec can't decode")):
+ read_xml(xml_baby_names, parser=parser)
+
+
+def test_utf16_encoding(xml_baby_names, parser):
+ with pytest.raises(
+ UnicodeError,
+ match=(
+ "UTF-16 stream does not start with BOM|"
+ "'utf-16(-le)?' codec can't decode byte"
+ ),
+ ):
+ read_xml(xml_baby_names, encoding="UTF-16", parser=parser)
+
+
+def test_unknown_encoding(xml_baby_names, parser):
+ with pytest.raises(LookupError, match=("unknown encoding: UFT-8")):
+ read_xml(xml_baby_names, encoding="UFT-8", parser=parser)
+
+
+def test_ascii_encoding(xml_baby_names, parser):
+ with pytest.raises(UnicodeDecodeError, match=("'ascii' codec can't decode byte")):
+ read_xml(xml_baby_names, encoding="ascii", parser=parser)
+
+
+def test_parser_consistency_with_encoding(xml_baby_names):
+ pytest.importorskip("lxml")
+ df_xpath_lxml = read_xml(xml_baby_names, parser="lxml", encoding="ISO-8859-1")
+ df_xpath_etree = read_xml(xml_baby_names, parser="etree", encoding="iso-8859-1")
+
+ df_iter_lxml = read_xml(
+ xml_baby_names,
+ parser="lxml",
+ encoding="ISO-8859-1",
+ iterparse={"row": ["rank", "malename", "femalename"]},
+ )
+ df_iter_etree = read_xml(
+ xml_baby_names,
+ parser="etree",
+ encoding="ISO-8859-1",
+ iterparse={"row": ["rank", "malename", "femalename"]},
+ )
+
+ tm.assert_frame_equal(df_xpath_lxml, df_xpath_etree)
+ tm.assert_frame_equal(df_xpath_etree, df_iter_etree)
+ tm.assert_frame_equal(df_iter_lxml, df_iter_etree)
+
+
+def test_wrong_encoding_for_lxml():
+ pytest.importorskip("lxml")
+ # GH#45133
+ data = """
+
+ c
+
+
+"""
+ with pytest.raises(TypeError, match="encoding None"):
+ read_xml(StringIO(data), parser="lxml", encoding=None)
+
+
+def test_none_encoding_etree():
+ # GH#45133
+ data = """
+
+ c
+
+
+"""
+ result = read_xml(StringIO(data), parser="etree", encoding=None)
+ expected = DataFrame({"a": ["c"]})
+ tm.assert_frame_equal(result, expected)
+
+
+# PARSER
+
+
+@td.skip_if_installed("lxml")
+def test_default_parser_no_lxml(xml_books):
+ with pytest.raises(
+ ImportError, match=("lxml not found, please install or use the etree parser.")
+ ):
+ read_xml(xml_books)
+
+
+def test_wrong_parser(xml_books):
+ with pytest.raises(
+ ValueError, match=("Values for parser can only be lxml or etree.")
+ ):
+ read_xml(xml_books, parser="bs4")
+
+
+# STYLESHEET
+
+
+def test_stylesheet_file(kml_cta_rail_lines, xsl_flatten_doc):
+ pytest.importorskip("lxml")
+ df_style = read_xml(
+ kml_cta_rail_lines,
+ xpath=".//k:Placemark",
+ namespaces={"k": "http://www.opengis.net/kml/2.2"},
+ stylesheet=xsl_flatten_doc,
+ )
+
+ df_iter = read_xml(
+ kml_cta_rail_lines,
+ iterparse={
+ "Placemark": [
+ "id",
+ "name",
+ "styleUrl",
+ "extrude",
+ "altitudeMode",
+ "coordinates",
+ ]
+ },
+ )
+
+ tm.assert_frame_equal(df_kml, df_style)
+ tm.assert_frame_equal(df_kml, df_iter)
+
+
+def test_stylesheet_file_like(kml_cta_rail_lines, xsl_flatten_doc, mode):
+ pytest.importorskip("lxml")
+ with open(xsl_flatten_doc, mode, encoding="utf-8" if mode == "r" else None) as f:
+ df_style = read_xml(
+ kml_cta_rail_lines,
+ xpath=".//k:Placemark",
+ namespaces={"k": "http://www.opengis.net/kml/2.2"},
+ stylesheet=f,
+ )
+
+ tm.assert_frame_equal(df_kml, df_style)
+
+
+def test_stylesheet_io(kml_cta_rail_lines, xsl_flatten_doc, mode):
+ # note: By default the bodies of untyped functions are not checked,
+ # consider using --check-untyped-defs
+ pytest.importorskip("lxml")
+ xsl_obj: BytesIO | StringIO # type: ignore[annotation-unchecked]
+
+ with open(xsl_flatten_doc, mode, encoding="utf-8" if mode == "r" else None) as f:
+ if mode == "rb":
+ xsl_obj = BytesIO(f.read())
+ else:
+ xsl_obj = StringIO(f.read())
+
+ df_style = read_xml(
+ kml_cta_rail_lines,
+ xpath=".//k:Placemark",
+ namespaces={"k": "http://www.opengis.net/kml/2.2"},
+ stylesheet=xsl_obj,
+ )
+
+ tm.assert_frame_equal(df_kml, df_style)
+
+
+def test_stylesheet_buffered_reader(kml_cta_rail_lines, xsl_flatten_doc, mode):
+ pytest.importorskip("lxml")
+ with open(xsl_flatten_doc, mode, encoding="utf-8" if mode == "r" else None) as f:
+ df_style = read_xml(
+ kml_cta_rail_lines,
+ xpath=".//k:Placemark",
+ namespaces={"k": "http://www.opengis.net/kml/2.2"},
+ stylesheet=f,
+ )
+
+ tm.assert_frame_equal(df_kml, df_style)
+
+
+def test_style_charset():
+ pytest.importorskip("lxml")
+ xml = "<中文標籤>1 2
中文標籤>"
+
+ xsl = """\
+
+
+
+
+
+
+
+
+
+
+
+ <根>
+
+ 根>
+
+
+ """
+
+ df_orig = read_xml(StringIO(xml))
+ df_style = read_xml(StringIO(xml), stylesheet=StringIO(xsl))
+
+ tm.assert_frame_equal(df_orig, df_style)
+
+
+def test_not_stylesheet(kml_cta_rail_lines, xml_books):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ with pytest.raises(
+ lxml_etree.XSLTParseError, match=("document is not a stylesheet")
+ ):
+ read_xml(kml_cta_rail_lines, stylesheet=xml_books)
+
+
+def test_incorrect_xsl_syntax(kml_cta_rail_lines):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ xsl = """\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+ with pytest.raises(
+ lxml_etree.XMLSyntaxError, match="Extra content at the end of the document"
+ ):
+ read_xml(kml_cta_rail_lines, stylesheet=StringIO(xsl))
+
+
+def test_incorrect_xsl_eval(kml_cta_rail_lines):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ xsl = """\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+ with pytest.raises(lxml_etree.XSLTParseError, match="failed to compile"):
+ read_xml(kml_cta_rail_lines, stylesheet=StringIO(xsl))
+
+
+def test_incorrect_xsl_apply(kml_cta_rail_lines):
+ lxml_etree = pytest.importorskip("lxml.etree")
+
+ xsl = """\
+
+
+
+
+
+
+
+
+
+ """
+
+ with pytest.raises(lxml_etree.XSLTApplyError, match="Cannot resolve URI"):
+ read_xml(kml_cta_rail_lines, stylesheet=StringIO(xsl))
+
+
+def test_wrong_stylesheet(kml_cta_rail_lines, xml_data_path):
+ pytest.importorskip("lxml.etree")
+
+ xsl = xml_data_path / "flatten_doesnt_exist.xsl"
+
+ with pytest.raises(
+ FileNotFoundError, match=r"\[Errno 2\] No such file or directory"
+ ):
+ read_xml(kml_cta_rail_lines, stylesheet=xsl)
+
+
+def test_stylesheet_file_close(kml_cta_rail_lines, xsl_flatten_doc, mode):
+ # note: By default the bodies of untyped functions are not checked,
+ # consider using --check-untyped-defs
+ pytest.importorskip("lxml")
+ xsl_obj: BytesIO | StringIO # type: ignore[annotation-unchecked]
+
+ with open(xsl_flatten_doc, mode, encoding="utf-8" if mode == "r" else None) as f:
+ if mode == "rb":
+ xsl_obj = BytesIO(f.read())
+ else:
+ xsl_obj = StringIO(f.read())
+
+ read_xml(kml_cta_rail_lines, stylesheet=xsl_obj)
+
+ assert not f.closed
+
+
+def test_stylesheet_with_etree(kml_cta_rail_lines, xsl_flatten_doc):
+ pytest.importorskip("lxml")
+ with pytest.raises(
+ ValueError, match=("To use stylesheet, you need lxml installed")
+ ):
+ read_xml(kml_cta_rail_lines, parser="etree", stylesheet=xsl_flatten_doc)
+
+
+@pytest.mark.parametrize("val", [StringIO(""), BytesIO(b"")])
+def test_empty_stylesheet(val, kml_cta_rail_lines):
+ lxml_etree = pytest.importorskip("lxml.etree")
+ with pytest.raises(lxml_etree.XMLSyntaxError):
+ read_xml(kml_cta_rail_lines, stylesheet=val)
+
+
+# ITERPARSE
+def test_file_like_iterparse(xml_books, parser, mode):
+ with open(xml_books, mode, encoding="utf-8" if mode == "r" else None) as f:
+ if mode == "r" and parser == "lxml":
+ with pytest.raises(
+ TypeError, match=("reading file objects must return bytes objects")
+ ):
+ read_xml(
+ f,
+ parser=parser,
+ iterparse={
+ "book": ["category", "title", "year", "author", "price"]
+ },
+ )
+ return None
+ else:
+ df_filelike = read_xml(
+ f,
+ parser=parser,
+ iterparse={"book": ["category", "title", "year", "author", "price"]},
+ )
+
+ df_expected = DataFrame(
+ {
+ "category": ["cooking", "children", "web"],
+ "title": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "author": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "year": [2005, 2005, 2003],
+ "price": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_filelike, df_expected)
+
+
+def test_file_io_iterparse(xml_books, parser, mode):
+ funcIO = StringIO if mode == "r" else BytesIO
+ with open(
+ xml_books,
+ mode,
+ encoding="utf-8" if mode == "r" else None,
+ ) as f:
+ with funcIO(f.read()) as b:
+ if mode == "r" and parser == "lxml":
+ with pytest.raises(
+ TypeError, match=("reading file objects must return bytes objects")
+ ):
+ read_xml(
+ b,
+ parser=parser,
+ iterparse={
+ "book": ["category", "title", "year", "author", "price"]
+ },
+ )
+ return None
+ else:
+ df_fileio = read_xml(
+ b,
+ parser=parser,
+ iterparse={
+ "book": ["category", "title", "year", "author", "price"]
+ },
+ )
+
+ df_expected = DataFrame(
+ {
+ "category": ["cooking", "children", "web"],
+ "title": ["Everyday Italian", "Harry Potter", "Learning XML"],
+ "author": ["Giada De Laurentiis", "J K. Rowling", "Erik T. Ray"],
+ "year": [2005, 2005, 2003],
+ "price": [30.00, 29.99, 39.95],
+ }
+ )
+
+ tm.assert_frame_equal(df_fileio, df_expected)
+
+
+@pytest.mark.network
+@pytest.mark.single_cpu
+def test_url_path_error(parser, httpserver, xml_file):
+ with open(xml_file, encoding="utf-8") as f:
+ httpserver.serve_content(content=f.read())
+ with pytest.raises(
+ ParserError, match=("iterparse is designed for large XML files")
+ ):
+ read_xml(
+ httpserver.url,
+ parser=parser,
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ )
+
+
+def test_compression_error(parser, compression_only, tmp_path):
+ path = tmp_path / "geom_xml.zip"
+ geom_df.to_xml(path, parser=parser, compression=compression_only)
+
+ with pytest.raises(
+ ParserError, match=("iterparse is designed for large XML files")
+ ):
+ read_xml(
+ path,
+ parser=parser,
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ compression=compression_only,
+ )
+
+
+def test_wrong_dict_type(xml_books, parser):
+ with pytest.raises(TypeError, match="list is not a valid type for iterparse"):
+ read_xml(
+ xml_books,
+ parser=parser,
+ iterparse=["category", "title", "year", "author", "price"],
+ )
+
+
+def test_wrong_dict_value(xml_books, parser):
+ with pytest.raises(
+ TypeError, match=" is not a valid type for value in iterparse"
+ ):
+ read_xml(xml_books, parser=parser, iterparse={"book": "category"})
+
+
+def test_bad_xml(parser, temp_file):
+ bad_xml = """\
+
+
+ square
+ 00360
+ 4.0
+ 2020-01-01
+
+
+ circle
+ 00360
+
+ 2021-01-01
+
+
+ triangle
+ 00180
+ 3.0
+ 2022-01-01
+
+"""
+ temp_file.write_text(bad_xml, encoding="utf-8")
+
+ with pytest.raises(
+ SyntaxError,
+ match="Extra content at the end of the document|junk after document element",
+ ):
+ read_xml(
+ temp_file,
+ parser=parser,
+ parse_dates=["date"],
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ )
+
+
+def test_comment(parser, temp_file):
+ xml = """\
+
+
+
+
+ circle
+ 2D
+
+
+ sphere
+ 3D
+
+
+
+
+"""
+
+ df_xpath = read_xml(StringIO(xml), xpath=".//shape", parser=parser)
+
+ df_iter = read_xml_iterparse(
+ xml, temp_file, parser=parser, iterparse={"shape": ["name", "type"]}
+ )
+
+ df_expected = DataFrame(
+ {
+ "name": ["circle", "sphere"],
+ "type": ["2D", "3D"],
+ }
+ )
+
+ tm.assert_frame_equal(df_xpath, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_dtd(parser, temp_file):
+ xml = """\
+
+
+
+
+]>
+
+
+ circle
+ 2D
+
+
+ sphere
+ 3D
+
+ """
+
+ df_xpath = read_xml(StringIO(xml), xpath=".//shape", parser=parser)
+
+ df_iter = read_xml_iterparse(
+ xml, temp_file, parser=parser, iterparse={"shape": ["name", "type"]}
+ )
+
+ df_expected = DataFrame(
+ {
+ "name": ["circle", "sphere"],
+ "type": ["2D", "3D"],
+ }
+ )
+
+ tm.assert_frame_equal(df_xpath, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_processing_instruction(parser, temp_file):
+ xml = """\
+
+
+
+
+
+, , ?>
+
+
+ circle
+ 2D
+
+
+ sphere
+ 3D
+
+ """
+
+ df_xpath = read_xml(StringIO(xml), xpath=".//shape", parser=parser)
+
+ df_iter = read_xml_iterparse(
+ xml, temp_file, parser=parser, iterparse={"shape": ["name", "type"]}
+ )
+
+ df_expected = DataFrame(
+ {
+ "name": ["circle", "sphere"],
+ "type": ["2D", "3D"],
+ }
+ )
+
+ tm.assert_frame_equal(df_xpath, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_no_result(xml_books, parser):
+ with pytest.raises(
+ ParserError, match="No result from selected items in iterparse."
+ ):
+ read_xml(
+ xml_books,
+ parser=parser,
+ iterparse={"node": ["attr1", "elem1", "elem2", "elem3"]},
+ )
+
+
+def test_empty_data(xml_books, parser):
+ with pytest.raises(EmptyDataError, match="No columns to parse from file"):
+ read_xml(
+ xml_books,
+ parser=parser,
+ iterparse={"book": ["attr1", "elem1", "elem2", "elem3"]},
+ )
+
+
+def test_online_stylesheet():
+ pytest.importorskip("lxml")
+ xml = """\
+
+
+
+ Empire Burlesque
+ Bob Dylan
+ USA
+ Columbia
+ 10.90
+ 1985
+
+
+ Hide your heart
+ Bonnie Tyler
+ UK
+ CBS Records
+ 9.90
+ 1988
+
+
+ Greatest Hits
+ Dolly Parton
+ USA
+ RCA
+ 9.90
+ 1982
+
+
+ Still got the blues
+ Gary Moore
+ UK
+ Virgin records
+ 10.20
+ 1990
+
+
+ Eros
+ Eros Ramazzotti
+ EU
+ BMG
+ 9.90
+ 1997
+
+
+ One night only
+ Bee Gees
+ UK
+ Polydor
+ 10.90
+ 1998
+
+
+ Sylvias Mother
+ Dr.Hook
+ UK
+ CBS
+ 8.10
+ 1973
+
+
+ Maggie May
+ Rod Stewart
+ UK
+ Pickwick
+ 8.50
+ 1990
+
+
+ Romanza
+ Andrea Bocelli
+ EU
+ Polydor
+ 10.80
+ 1996
+
+
+ When a man loves a woman
+ Percy Sledge
+ USA
+ Atlantic
+ 8.70
+ 1987
+
+
+ Black angel
+ Savage Rose
+ EU
+ Mega
+ 10.90
+ 1995
+
+
+ 1999 Grammy Nominees
+ Many
+ USA
+ Grammy
+ 10.20
+ 1999
+
+
+ For the good times
+ Kenny Rogers
+ UK
+ Mucik Master
+ 8.70
+ 1995
+
+
+ Big Willie style
+ Will Smith
+ USA
+ Columbia
+ 9.90
+ 1997
+
+
+ Tupelo Honey
+ Van Morrison
+ UK
+ Polydor
+ 8.20
+ 1971
+
+
+ Soulsville
+ Jorn Hoel
+ Norway
+ WEA
+ 7.90
+ 1996
+
+
+ The very best of
+ Cat Stevens
+ UK
+ Island
+ 8.90
+ 1990
+
+
+ Stop
+ Sam Brown
+ UK
+ A and M
+ 8.90
+ 1988
+
+
+ Bridge of Spies
+ T`Pau
+ UK
+ Siren
+ 7.90
+ 1987
+
+
+ Private Dancer
+ Tina Turner
+ UK
+ Capitol
+ 8.90
+ 1983
+
+
+ Midt om natten
+ Kim Larsen
+ EU
+ Medley
+ 7.80
+ 1983
+
+
+ Pavarotti Gala Concert
+ Luciano Pavarotti
+ UK
+ DECCA
+ 9.90
+ 1991
+
+
+ The dock of the bay
+ Otis Redding
+ USA
+ Stax Records
+ 7.90
+ 1968
+
+
+ Picture book
+ Simply Red
+ EU
+ Elektra
+ 7.20
+ 1985
+
+
+ Red
+ The Communards
+ UK
+ London
+ 7.80
+ 1987
+
+
+ Unchain my heart
+ Joe Cocker
+ USA
+ EMI
+ 8.20
+ 1987
+
+
+"""
+ xsl = """\
+
+
+
+
+
+ My CD Collection
+
+
+ Title
+ Artist
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+ df_xsl = read_xml(
+ StringIO(xml),
+ xpath=".//tr[td and position() <= 6]",
+ names=["title", "artist"],
+ stylesheet=StringIO(xsl),
+ )
+
+ df_expected = DataFrame(
+ {
+ "title": {
+ 0: "Empire Burlesque",
+ 1: "Hide your heart",
+ 2: "Greatest Hits",
+ 3: "Still got the blues",
+ 4: "Eros",
+ },
+ "artist": {
+ 0: "Bob Dylan",
+ 1: "Bonnie Tyler",
+ 2: "Dolly Parton",
+ 3: "Gary Moore",
+ 4: "Eros Ramazzotti",
+ },
+ }
+ )
+
+ tm.assert_frame_equal(df_expected, df_xsl)
+
+
+# COMPRESSION
+
+
+def test_compression_read(parser, compression_only, tmp_path, temp_file):
+ comp_path = tmp_path / "test.xml"
+ geom_df.to_xml(comp_path, index=False, parser=parser, compression=compression_only)
+
+ df_xpath = read_xml(comp_path, parser=parser, compression=compression_only)
+
+ df_iter = read_xml_iterparse_comp(
+ comp_path,
+ compression_only,
+ temp_file,
+ parser=parser,
+ iterparse={"row": ["shape", "degrees", "sides"]},
+ compression=compression_only,
+ )
+
+ tm.assert_frame_equal(df_xpath, geom_df)
+ tm.assert_frame_equal(df_iter, geom_df)
+
+
+def test_wrong_compression(parser, compression, compression_only, temp_file):
+ actual_compression = compression
+ attempted_compression = compression_only
+
+ if actual_compression == attempted_compression:
+ pytest.skip(f"{actual_compression} == {attempted_compression}")
+
+ errors = {
+ "bz2": (OSError, "Invalid data stream"),
+ "gzip": (OSError, "Not a gzipped file"),
+ "zip": (BadZipFile, "File is not a zip file"),
+ "tar": (ReadError, "file could not be opened successfully"),
+ }
+ zstd = import_optional_dependency("zstandard", errors="ignore")
+ if zstd is not None:
+ errors["zstd"] = (zstd.ZstdError, "Unknown frame descriptor")
+ lzma = import_optional_dependency("lzma", errors="ignore")
+ if lzma is not None:
+ errors["xz"] = (LZMAError, "Input format not supported by decoder")
+ error_cls, error_str = errors[attempted_compression]
+
+ geom_df.to_xml(temp_file, parser=parser, compression=actual_compression)
+
+ with pytest.raises(error_cls, match=error_str):
+ read_xml(temp_file, parser=parser, compression=attempted_compression)
+
+
+def test_unsupported_compression(parser, temp_file):
+ with pytest.raises(ValueError, match="Unrecognized compression type"):
+ read_xml(temp_file, parser=parser, compression="7z")
+
+
+# STORAGE OPTIONS
+
+
+@pytest.mark.network
+@pytest.mark.single_cpu
+def test_s3_parser_consistency(s3_bucket_public_with_data, s3so):
+ pytest.importorskip("s3fs")
+ pytest.importorskip("lxml")
+ s3 = f"s3://{s3_bucket_public_with_data.name}/books.xml"
+ df_lxml = read_xml(s3, parser="lxml", storage_options=s3so)
+
+ df_etree = read_xml(s3, parser="etree", storage_options=s3so)
+
+ tm.assert_frame_equal(df_lxml, df_etree)
+
+
+def test_read_xml_nullable_dtypes(
+ parser, string_storage, dtype_backend, using_infer_string
+):
+ # GH#50500
+ data = """
+
+
+ x
+ 1
+ 4.0
+ x
+ 2
+ 4.0
+
+ True
+ False
+
+
+ y
+ 2
+ 5.0
+
+
+
+
+ False
+
+
+"""
+
+ with pd.option_context("mode.string_storage", string_storage):
+ result = read_xml(StringIO(data), parser=parser, dtype_backend=dtype_backend)
+
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ string_dtype = pd.ArrowDtype(pa.string())
+ else:
+ string_dtype = pd.StringDtype(string_storage)
+
+ expected = DataFrame(
+ {
+ "a": Series(["x", "y"], dtype=string_dtype),
+ "b": Series([1, 2], dtype="Int64"),
+ "c": Series([4.0, 5.0], dtype="Float64"),
+ "d": Series(["x", None], dtype=string_dtype),
+ "e": Series([2, NA], dtype="Int64"),
+ "f": Series([4.0, NA], dtype="Float64"),
+ "g": Series([NA, NA], dtype="Int64"),
+ "h": Series([True, False], dtype="boolean"),
+ "i": Series([False, NA], dtype="boolean"),
+ }
+ )
+
+ if dtype_backend == "pyarrow":
+ pa = pytest.importorskip("pyarrow")
+ from pandas.arrays import ArrowExtensionArray
+
+ expected = DataFrame(
+ {
+ col: ArrowExtensionArray(pa.array(expected[col], from_pandas=True))
+ for col in expected.columns
+ }
+ )
+ expected["g"] = ArrowExtensionArray(pa.array([None, None]))
+
+ # the storage of the str columns' Index is also affected by the
+ # string_storage setting -> ignore that for checking the result
+ tm.assert_frame_equal(result, expected, check_column_type=False)
+
+
+def test_invalid_dtype_backend():
+ msg = (
+ "dtype_backend numpy is invalid, only 'numpy_nullable' and "
+ "'pyarrow' are allowed."
+ )
+ with pytest.raises(ValueError, match=msg):
+ read_xml("test", dtype_backend="numpy")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/test_xml_dtypes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/test_xml_dtypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..825bc3a7f9f96ee2d9c112f9609e04911000759f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/io/xml/test_xml_dtypes.py
@@ -0,0 +1,443 @@
+from __future__ import annotations
+
+from io import StringIO
+
+import pytest
+
+from pandas.errors import ParserWarning
+import pandas.util._test_decorators as td
+
+from pandas import (
+ NA,
+ DataFrame,
+ DatetimeIndex,
+ Series,
+ to_datetime,
+)
+import pandas._testing as tm
+
+from pandas.io.xml import read_xml
+
+
+@pytest.fixture(params=[pytest.param("lxml", marks=td.skip_if_no("lxml")), "etree"])
+def parser(request):
+ return request.param
+
+
+@pytest.fixture(
+ params=[None, {"book": ["category", "title", "author", "year", "price"]}]
+)
+def iterparse(request):
+ return request.param
+
+
+def read_xml_iterparse(data, temp_file, **kwargs):
+ with open(temp_file, "w", encoding="utf-8") as f:
+ f.write(data)
+ return read_xml(temp_file, **kwargs)
+
+
+xml_types = """\
+
+
+
+ square
+ 00360
+ 4.0
+
+
+ circle
+ 00360
+
+
+
+ triangle
+ 00180
+ 3.0
+
+"""
+
+xml_dates = """
+
+
+ square
+ 00360
+ 4.0
+ 2020-01-01
+
+
+ circle
+ 00360
+
+ 2021-01-01
+
+
+ triangle
+ 00180
+ 3.0
+ 2022-01-01
+
+"""
+
+
+# DTYPE
+
+
+def test_dtype_single_str(parser, temp_file):
+ df_result = read_xml(StringIO(xml_types), dtype={"degrees": "str"}, parser=parser)
+ df_iter = read_xml_iterparse(
+ xml_types,
+ parser=parser,
+ dtype={"degrees": "str"},
+ iterparse={"row": ["shape", "degrees", "sides"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": ["00360", "00360", "00180"],
+ "sides": [4.0, float("nan"), 3.0],
+ }
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_dtypes_all_str(parser, temp_file):
+ df_result = read_xml(StringIO(xml_dates), dtype="string", parser=parser)
+ df_iter = read_xml_iterparse(
+ xml_dates,
+ parser=parser,
+ dtype="string",
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": ["00360", "00360", "00180"],
+ "sides": ["4.0", None, "3.0"],
+ "date": ["2020-01-01", "2021-01-01", "2022-01-01"],
+ },
+ dtype="string",
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_dtypes_with_names(parser, temp_file):
+ df_result = read_xml(
+ StringIO(xml_dates),
+ names=["Col1", "Col2", "Col3", "Col4"],
+ dtype={"Col2": "string", "Col3": "Int64", "Col4": "datetime64[ns]"},
+ parser=parser,
+ )
+ df_iter = read_xml_iterparse(
+ xml_dates,
+ parser=parser,
+ names=["Col1", "Col2", "Col3", "Col4"],
+ dtype={"Col2": "string", "Col3": "Int64", "Col4": "datetime64[ns]"},
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "Col1": ["square", "circle", "triangle"],
+ "Col2": Series(["00360", "00360", "00180"], dtype="string"),
+ "Col3": Series([4.0, NA, 3.0], dtype="Int64"),
+ "Col4": DatetimeIndex(
+ ["2020-01-01", "2021-01-01", "2022-01-01"], dtype="M8[ns]"
+ ),
+ }
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_dtype_nullable_int(parser, temp_file):
+ df_result = read_xml(StringIO(xml_types), dtype={"sides": "Int64"}, parser=parser)
+ df_iter = read_xml_iterparse(
+ xml_types,
+ parser=parser,
+ dtype={"sides": "Int64"},
+ iterparse={"row": ["shape", "degrees", "sides"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": Series([4.0, NA, 3.0], dtype="Int64"),
+ }
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_dtype_float(parser, temp_file):
+ df_result = read_xml(StringIO(xml_types), dtype={"degrees": "float"}, parser=parser)
+ df_iter = read_xml_iterparse(
+ xml_types,
+ parser=parser,
+ dtype={"degrees": "float"},
+ iterparse={"row": ["shape", "degrees", "sides"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": Series([360, 360, 180]).astype("float"),
+ "sides": [4.0, float("nan"), 3.0],
+ }
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_wrong_dtype(xml_books, parser, iterparse):
+ with pytest.raises(
+ ValueError, match=('Unable to parse string "Everyday Italian" at position 0')
+ ):
+ read_xml(
+ xml_books, dtype={"title": "Int64"}, parser=parser, iterparse=iterparse
+ )
+
+
+def test_both_dtype_converters(parser, temp_file):
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": ["00360", "00360", "00180"],
+ "sides": [4.0, float("nan"), 3.0],
+ }
+ )
+
+ with tm.assert_produces_warning(ParserWarning, match="Both a converter and dtype"):
+ df_result = read_xml(
+ StringIO(xml_types),
+ dtype={"degrees": "str"},
+ converters={"degrees": str},
+ parser=parser,
+ )
+ df_iter = read_xml_iterparse(
+ xml_types,
+ dtype={"degrees": "str"},
+ converters={"degrees": str},
+ parser=parser,
+ iterparse={"row": ["shape", "degrees", "sides"]},
+ temp_file=temp_file,
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+# CONVERTERS
+
+
+def test_converters_str(parser, temp_file):
+ df_result = read_xml(
+ StringIO(xml_types), converters={"degrees": str}, parser=parser
+ )
+ df_iter = read_xml_iterparse(
+ xml_types,
+ parser=parser,
+ converters={"degrees": str},
+ iterparse={"row": ["shape", "degrees", "sides"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": ["00360", "00360", "00180"],
+ "sides": [4.0, float("nan"), 3.0],
+ }
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_converters_date(parser, temp_file):
+ convert_to_datetime = lambda x: to_datetime(x)
+ df_result = read_xml(
+ StringIO(xml_dates), converters={"date": convert_to_datetime}, parser=parser
+ )
+ df_iter = read_xml_iterparse(
+ xml_dates,
+ parser=parser,
+ converters={"date": convert_to_datetime},
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": [4.0, float("nan"), 3.0],
+ "date": to_datetime(["2020-01-01", "2021-01-01", "2022-01-01"]),
+ }
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_wrong_converters_type(xml_books, parser, iterparse):
+ with pytest.raises(TypeError, match=("Type converters must be a dict or subclass")):
+ read_xml(
+ xml_books, converters={"year", str}, parser=parser, iterparse=iterparse
+ )
+
+
+def test_callable_func_converters(xml_books, parser, iterparse):
+ with pytest.raises(TypeError, match=("'float' object is not callable")):
+ read_xml(
+ xml_books, converters={"year": float()}, parser=parser, iterparse=iterparse
+ )
+
+
+def test_callable_str_converters(xml_books, parser, iterparse):
+ with pytest.raises(TypeError, match=("'str' object is not callable")):
+ read_xml(
+ xml_books, converters={"year": "float"}, parser=parser, iterparse=iterparse
+ )
+
+
+# PARSE DATES
+
+
+def test_parse_dates_column_name(parser, temp_file):
+ df_result = read_xml(StringIO(xml_dates), parse_dates=["date"], parser=parser)
+ df_iter = read_xml_iterparse(
+ xml_dates,
+ parser=parser,
+ parse_dates=["date"],
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": [4.0, float("nan"), 3.0],
+ "date": to_datetime(["2020-01-01", "2021-01-01", "2022-01-01"]),
+ }
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_parse_dates_column_index(parser, temp_file):
+ df_result = read_xml(StringIO(xml_dates), parse_dates=[3], parser=parser)
+ df_iter = read_xml_iterparse(
+ xml_dates,
+ parser=parser,
+ parse_dates=[3],
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": [4.0, float("nan"), 3.0],
+ "date": to_datetime(["2020-01-01", "2021-01-01", "2022-01-01"]),
+ }
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_parse_dates_true(parser, temp_file):
+ df_result = read_xml(StringIO(xml_dates), parse_dates=True, parser=parser)
+
+ df_iter = read_xml_iterparse(
+ xml_dates,
+ parser=parser,
+ parse_dates=True,
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ temp_file=temp_file,
+ )
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": [4.0, float("nan"), 3.0],
+ "date": ["2020-01-01", "2021-01-01", "2022-01-01"],
+ }
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_day_first_parse_dates(parser, temp_file):
+ xml = """\
+
+
+
+ square
+ 00360
+ 4.0
+ 31/12/2020
+
+
+ circle
+ 00360
+
+ 31/12/2021
+
+
+ triangle
+ 00180
+ 3.0
+ 31/12/2022
+
+"""
+
+ df_expected = DataFrame(
+ {
+ "shape": ["square", "circle", "triangle"],
+ "degrees": [360, 360, 180],
+ "sides": [4.0, float("nan"), 3.0],
+ "date": to_datetime(["2020-12-31", "2021-12-31", "2022-12-31"]),
+ }
+ )
+
+ with tm.assert_produces_warning(
+ UserWarning, match="Parsing dates in %d/%m/%Y format"
+ ):
+ df_result = read_xml(StringIO(xml), parse_dates=["date"], parser=parser)
+ df_iter = read_xml_iterparse(
+ xml,
+ parse_dates=["date"],
+ parser=parser,
+ iterparse={"row": ["shape", "degrees", "sides", "date"]},
+ temp_file=temp_file,
+ )
+
+ tm.assert_frame_equal(df_result, df_expected)
+ tm.assert_frame_equal(df_iter, df_expected)
+
+
+def test_wrong_parse_dates_type(xml_books, parser, iterparse):
+ with pytest.raises(TypeError, match="Only booleans and lists are accepted"):
+ read_xml(xml_books, parse_dates={"date"}, parser=parser, iterparse=iterparse)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_hashtable.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_hashtable.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f24f87348595b7416e6717f929b92f0c9df22c4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_hashtable.py
@@ -0,0 +1,782 @@
+from collections import namedtuple
+from collections.abc import Generator
+from contextlib import contextmanager
+import re
+import struct
+import tracemalloc
+
+import numpy as np
+import pytest
+
+from pandas._libs import hashtable as ht
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.algorithms import isin
+
+
+@contextmanager
+def activated_tracemalloc() -> Generator[None, None, None]:
+ tracemalloc.start()
+ try:
+ yield
+ finally:
+ tracemalloc.stop()
+
+
+def get_allocated_khash_memory():
+ snapshot = tracemalloc.take_snapshot()
+ snapshot = snapshot.filter_traces(
+ (tracemalloc.DomainFilter(True, ht.get_hashtable_trace_domain()),)
+ )
+ return sum(x.size for x in snapshot.traces)
+
+
+@pytest.mark.parametrize(
+ "table_type, dtype",
+ [
+ (ht.PyObjectHashTable, np.object_),
+ (ht.Complex128HashTable, np.complex128),
+ (ht.Int64HashTable, np.int64),
+ (ht.UInt64HashTable, np.uint64),
+ (ht.Float64HashTable, np.float64),
+ (ht.Complex64HashTable, np.complex64),
+ (ht.Int32HashTable, np.int32),
+ (ht.UInt32HashTable, np.uint32),
+ (ht.Float32HashTable, np.float32),
+ (ht.Int16HashTable, np.int16),
+ (ht.UInt16HashTable, np.uint16),
+ (ht.Int8HashTable, np.int8),
+ (ht.UInt8HashTable, np.uint8),
+ (ht.IntpHashTable, np.intp),
+ ],
+)
+class TestHashTable:
+ def test_get_set_contains_len(self, table_type, dtype):
+ index = 5
+ table = table_type(55)
+ assert len(table) == 0
+ assert index not in table
+
+ table.set_item(index, 42)
+ assert len(table) == 1
+ assert index in table
+ assert table.get_item(index) == 42
+
+ table.set_item(index + 1, 41)
+ assert index in table
+ assert index + 1 in table
+ assert len(table) == 2
+ assert table.get_item(index) == 42
+ assert table.get_item(index + 1) == 41
+
+ table.set_item(index, 21)
+ assert index in table
+ assert index + 1 in table
+ assert len(table) == 2
+ assert table.get_item(index) == 21
+ assert table.get_item(index + 1) == 41
+ assert index + 2 not in table
+
+ table.set_item(index + 1, 21)
+ assert index in table
+ assert index + 1 in table
+ assert len(table) == 2
+ assert table.get_item(index) == 21
+ assert table.get_item(index + 1) == 21
+
+ with pytest.raises(KeyError, match=str(index + 2)):
+ table.get_item(index + 2)
+
+ def test_get_set_contains_len_mask(self, table_type, dtype):
+ if table_type == ht.PyObjectHashTable:
+ pytest.skip("Mask not supported for object")
+ index = 5
+ table = table_type(55, uses_mask=True)
+ assert len(table) == 0
+ assert index not in table
+
+ table.set_item(index, 42)
+ assert len(table) == 1
+ assert index in table
+ assert table.get_item(index) == 42
+ with pytest.raises(KeyError, match="NA"):
+ table.get_na()
+
+ table.set_item(index + 1, 41)
+ table.set_na(41)
+ assert pd.NA in table
+ assert index in table
+ assert index + 1 in table
+ assert len(table) == 3
+ assert table.get_item(index) == 42
+ assert table.get_item(index + 1) == 41
+ assert table.get_na() == 41
+
+ table.set_na(21)
+ assert index in table
+ assert index + 1 in table
+ assert len(table) == 3
+ assert table.get_item(index + 1) == 41
+ assert table.get_na() == 21
+ assert index + 2 not in table
+
+ with pytest.raises(KeyError, match=str(index + 2)):
+ table.get_item(index + 2)
+
+ def test_map_keys_to_values(self, table_type, dtype, writable):
+ # only Int64HashTable has this method
+ if table_type == ht.Int64HashTable:
+ N = 77
+ table = table_type()
+ keys = np.arange(N).astype(dtype)
+ vals = np.arange(N).astype(np.int64) + N
+ keys.flags.writeable = writable
+ vals.flags.writeable = writable
+ table.map_keys_to_values(keys, vals)
+ for i in range(N):
+ assert table.get_item(keys[i]) == i + N
+
+ def test_map_locations(self, table_type, dtype, writable):
+ N = 8
+ table = table_type()
+ keys = (np.arange(N) + N).astype(dtype)
+ keys.flags.writeable = writable
+ table.map_locations(keys)
+ for i in range(N):
+ assert table.get_item(keys[i]) == i
+
+ def test_map_locations_mask(self, table_type, dtype, writable):
+ if table_type == ht.PyObjectHashTable:
+ pytest.skip("Mask not supported for object")
+ N = 129 # must be > 128 to test GH#58924
+ table = table_type(uses_mask=True)
+ keys = (np.arange(N) + N).astype(dtype)
+ keys.flags.writeable = writable
+ mask = np.concatenate([np.repeat(False, N - 1), [True]], axis=0)
+ table.map_locations(keys, mask)
+ for i in range(N - 1):
+ assert table.get_item(keys[i]) == i
+
+ with pytest.raises(KeyError, match=re.escape(str(keys[N - 1]))):
+ table.get_item(keys[N - 1])
+
+ assert table.get_na() == N - 1
+
+ def test_lookup(self, table_type, dtype, writable):
+ N = 3
+ table = table_type()
+ keys = (np.arange(N) + N).astype(dtype)
+ keys.flags.writeable = writable
+ table.map_locations(keys)
+ result = table.lookup(keys)
+ expected = np.arange(N)
+ tm.assert_numpy_array_equal(result.astype(np.int64), expected.astype(np.int64))
+
+ def test_lookup_wrong(self, table_type, dtype):
+ if dtype in (np.int8, np.uint8):
+ N = 100
+ else:
+ N = 512
+ table = table_type()
+ keys = (np.arange(N) + N).astype(dtype)
+ table.map_locations(keys)
+ wrong_keys = np.arange(N).astype(dtype)
+ result = table.lookup(wrong_keys)
+ assert np.all(result == -1)
+
+ def test_lookup_mask(self, table_type, dtype, writable):
+ if table_type == ht.PyObjectHashTable:
+ pytest.skip("Mask not supported for object")
+ N = 3
+ table = table_type(uses_mask=True)
+ keys = (np.arange(N) + N).astype(dtype)
+ mask = np.array([False, True, False])
+ keys.flags.writeable = writable
+ table.map_locations(keys, mask)
+ result = table.lookup(keys, mask)
+ expected = np.arange(N)
+ tm.assert_numpy_array_equal(result.astype(np.int64), expected.astype(np.int64))
+
+ result = table.lookup(np.array([1 + N]).astype(dtype), np.array([False]))
+ tm.assert_numpy_array_equal(
+ result.astype(np.int64), np.array([-1], dtype=np.int64)
+ )
+
+ def test_unique(self, table_type, dtype, writable):
+ if dtype in (np.int8, np.uint8):
+ N = 88
+ else:
+ N = 1000
+ table = table_type()
+ expected = (np.arange(N) + N).astype(dtype)
+ keys = np.repeat(expected, 5)
+ keys.flags.writeable = writable
+ unique = table.unique(keys)
+ tm.assert_numpy_array_equal(unique, expected)
+
+ def test_tracemalloc_works(self, table_type, dtype):
+ if dtype in (np.int8, np.uint8):
+ N = 256
+ else:
+ N = 30000
+ keys = np.arange(N).astype(dtype)
+ with activated_tracemalloc():
+ table = table_type()
+ table.map_locations(keys)
+ used = get_allocated_khash_memory()
+ my_size = table.sizeof()
+ assert used == my_size
+ del table
+ assert get_allocated_khash_memory() == 0
+
+ def test_tracemalloc_for_empty(self, table_type, dtype):
+ with activated_tracemalloc():
+ table = table_type()
+ used = get_allocated_khash_memory()
+ my_size = table.sizeof()
+ assert used == my_size
+ del table
+ assert get_allocated_khash_memory() == 0
+
+ def test_get_state(self, table_type, dtype):
+ table = table_type(1000)
+ state = table.get_state()
+ assert state["size"] == 0
+ assert state["n_occupied"] == 0
+ assert "n_buckets" in state
+ assert "upper_bound" in state
+
+ @pytest.mark.parametrize("N", range(1, 110, 4))
+ def test_no_reallocation(self, table_type, dtype, N):
+ keys = np.arange(N).astype(dtype)
+ preallocated_table = table_type(N)
+ n_buckets_start = preallocated_table.get_state()["n_buckets"]
+ preallocated_table.map_locations(keys)
+ n_buckets_end = preallocated_table.get_state()["n_buckets"]
+ # original number of buckets was enough:
+ assert n_buckets_start == n_buckets_end
+ # check with clean table (not too much preallocated)
+ clean_table = table_type()
+ clean_table.map_locations(keys)
+ assert n_buckets_start == clean_table.get_state()["n_buckets"]
+
+
+class TestHashTableUnsorted:
+ # TODO: moved from test_algos; may be redundancies with other tests
+ def test_string_hashtable_set_item_signature(self):
+ # GH#30419 fix typing in StringHashTable.set_item to prevent segfault
+ tbl = ht.StringHashTable()
+
+ tbl.set_item("key", 1)
+ assert tbl.get_item("key") == 1
+
+ with pytest.raises(TypeError, match="'key' has incorrect type"):
+ # key arg typed as string, not object
+ tbl.set_item(4, 6)
+ with pytest.raises(TypeError, match="'val' has incorrect type"):
+ tbl.get_item(4)
+
+ def test_lookup_nan(self, writable):
+ # GH#21688 ensure we can deal with readonly memory views
+ xs = np.array([2.718, 3.14, np.nan, -7, 5, 2, 3])
+ xs.setflags(write=writable)
+ m = ht.Float64HashTable()
+ m.map_locations(xs)
+ tm.assert_numpy_array_equal(m.lookup(xs), np.arange(len(xs), dtype=np.intp))
+
+ def test_add_signed_zeros(self):
+ # GH#21866 inconsistent hash-function for float64
+ # default hash-function would lead to different hash-buckets
+ # for 0.0 and -0.0 if there are more than 2^30 hash-buckets
+ # but this would mean 16GB
+ N = 4 # 12 * 10**8 would trigger the error, if you have enough memory
+ m = ht.Float64HashTable(N)
+ m.set_item(0.0, 0)
+ m.set_item(-0.0, 0)
+ assert len(m) == 1 # 0.0 and -0.0 are equivalent
+
+ def test_add_different_nans(self):
+ # GH#21866 inconsistent hash-function for float64
+ # create different nans from bit-patterns:
+ NAN1 = struct.unpack("d", struct.pack("=Q", 0x7FF8000000000000))[0]
+ NAN2 = struct.unpack("d", struct.pack("=Q", 0x7FF8000000000001))[0]
+ assert NAN1 != NAN1
+ assert NAN2 != NAN2
+ # default hash function would lead to different hash-buckets
+ # for NAN1 and NAN2 even if there are only 4 buckets:
+ m = ht.Float64HashTable()
+ m.set_item(NAN1, 0)
+ m.set_item(NAN2, 0)
+ assert len(m) == 1 # NAN1 and NAN2 are equivalent
+
+ def test_lookup_overflow(self, writable):
+ xs = np.array([1, 2, 2**63], dtype=np.uint64)
+ # GH 21688 ensure we can deal with readonly memory views
+ xs.setflags(write=writable)
+ m = ht.UInt64HashTable()
+ m.map_locations(xs)
+ tm.assert_numpy_array_equal(m.lookup(xs), np.arange(len(xs), dtype=np.intp))
+
+ @pytest.mark.parametrize("nvals", [0, 10]) # resizing to 0 is special case
+ @pytest.mark.parametrize(
+ "htable, uniques, dtype, safely_resizes",
+ [
+ (ht.PyObjectHashTable, ht.ObjectVector, "object", False),
+ (ht.StringHashTable, ht.ObjectVector, "object", True),
+ (ht.Float64HashTable, ht.Float64Vector, "float64", False),
+ (ht.Int64HashTable, ht.Int64Vector, "int64", False),
+ (ht.Int32HashTable, ht.Int32Vector, "int32", False),
+ (ht.UInt64HashTable, ht.UInt64Vector, "uint64", False),
+ ],
+ )
+ def test_vector_resize(
+ self, writable, htable, uniques, dtype, safely_resizes, nvals
+ ):
+ # Test for memory errors after internal vector
+ # reallocations (GH 7157)
+ # Changed from using np.random.default_rng(2).rand to range
+ # which could cause flaky CI failures when safely_resizes=False
+ vals = np.array(range(1000), dtype=dtype)
+
+ # GH 21688 ensures we can deal with read-only memory views
+ vals.setflags(write=writable)
+
+ # initialise instances; cannot initialise in parametrization,
+ # as otherwise external views would be held on the array (which is
+ # one of the things this test is checking)
+ htable = htable()
+ uniques = uniques()
+
+ # get_labels may append to uniques
+ htable.get_labels(vals[:nvals], uniques, 0, -1)
+ # to_array() sets an external_view_exists flag on uniques.
+ tmp = uniques.to_array()
+ oldshape = tmp.shape
+
+ # subsequent get_labels() calls can no longer append to it
+ # (except for StringHashTables + ObjectVector)
+ if safely_resizes:
+ htable.get_labels(vals, uniques, 0, -1)
+ else:
+ with pytest.raises(ValueError, match="external reference.*"):
+ htable.get_labels(vals, uniques, 0, -1)
+
+ uniques.to_array() # should not raise here
+ assert tmp.shape == oldshape
+
+ @pytest.mark.parametrize(
+ "hashtable",
+ [
+ ht.PyObjectHashTable,
+ ht.StringHashTable,
+ ht.Float64HashTable,
+ ht.Int64HashTable,
+ ht.Int32HashTable,
+ ht.UInt64HashTable,
+ ],
+ )
+ def test_hashtable_large_sizehint(self, hashtable):
+ # GH#22729 smoketest for not raising when passing a large size_hint
+ size_hint = np.iinfo(np.uint32).max + 1
+ hashtable(size_hint=size_hint)
+
+
+class TestPyObjectHashTableWithNans:
+ def test_nan_float(self):
+ nan1 = float("nan")
+ nan2 = float("nan")
+ assert nan1 is not nan2
+ table = ht.PyObjectHashTable()
+ table.set_item(nan1, 42)
+ assert table.get_item(nan2) == 42
+
+ def test_nan_complex_both(self):
+ nan1 = complex(float("nan"), float("nan"))
+ nan2 = complex(float("nan"), float("nan"))
+ assert nan1 is not nan2
+ table = ht.PyObjectHashTable()
+ table.set_item(nan1, 42)
+ assert table.get_item(nan2) == 42
+
+ def test_nan_complex_real(self):
+ nan1 = complex(float("nan"), 1)
+ nan2 = complex(float("nan"), 1)
+ other = complex(float("nan"), 2)
+ assert nan1 is not nan2
+ table = ht.PyObjectHashTable()
+ table.set_item(nan1, 42)
+ assert table.get_item(nan2) == 42
+ with pytest.raises(KeyError, match=re.escape(repr(other))):
+ table.get_item(other)
+
+ def test_nan_complex_imag(self):
+ nan1 = complex(1, float("nan"))
+ nan2 = complex(1, float("nan"))
+ other = complex(2, float("nan"))
+ assert nan1 is not nan2
+ table = ht.PyObjectHashTable()
+ table.set_item(nan1, 42)
+ assert table.get_item(nan2) == 42
+ with pytest.raises(KeyError, match=re.escape(repr(other))):
+ table.get_item(other)
+
+ def test_nan_in_tuple(self):
+ nan1 = (float("nan"),)
+ nan2 = (float("nan"),)
+ assert nan1[0] is not nan2[0]
+ table = ht.PyObjectHashTable()
+ table.set_item(nan1, 42)
+ assert table.get_item(nan2) == 42
+
+ def test_nan_in_nested_tuple(self):
+ nan1 = (1, (2, (float("nan"),)))
+ nan2 = (1, (2, (float("nan"),)))
+ other = (1, 2)
+ table = ht.PyObjectHashTable()
+ table.set_item(nan1, 42)
+ assert table.get_item(nan2) == 42
+ with pytest.raises(KeyError, match=re.escape(repr(other))):
+ table.get_item(other)
+
+ def test_nan_in_namedtuple(self):
+ T = namedtuple("T", ["x"])
+ nan1 = T(float("nan"))
+ nan2 = T(float("nan"))
+ assert nan1.x is not nan2.x
+ table = ht.PyObjectHashTable()
+ table.set_item(nan1, 42)
+ assert table.get_item(nan2) == 42
+
+ def test_nan_in_nested_namedtuple(self):
+ T = namedtuple("T", ["x", "y"])
+ nan1 = T(1, (2, (float("nan"),)))
+ nan2 = T(1, (2, (float("nan"),)))
+ other = T(1, 2)
+ table = ht.PyObjectHashTable()
+ table.set_item(nan1, 42)
+ assert table.get_item(nan2) == 42
+ with pytest.raises(KeyError, match=re.escape(repr(other))):
+ table.get_item(other)
+
+
+def test_hash_equal_tuple_with_nans():
+ a = (float("nan"), (float("nan"), float("nan")))
+ b = (float("nan"), (float("nan"), float("nan")))
+ assert ht.object_hash(a) == ht.object_hash(b)
+ assert ht.objects_are_equal(a, b)
+
+
+def test_hash_equal_namedtuple_with_nans():
+ T = namedtuple("T", ["x", "y"])
+ a = T(float("nan"), (float("nan"), float("nan")))
+ b = T(float("nan"), (float("nan"), float("nan")))
+ assert ht.object_hash(a) == ht.object_hash(b)
+ assert ht.objects_are_equal(a, b)
+
+
+def test_hash_equal_namedtuple_and_tuple():
+ T = namedtuple("T", ["x", "y"])
+ a = T(1, (2, 3))
+ b = (1, (2, 3))
+ assert ht.object_hash(a) == ht.object_hash(b)
+ assert ht.objects_are_equal(a, b)
+
+
+def test_get_labels_groupby_for_Int64(writable):
+ table = ht.Int64HashTable()
+ vals = np.array([1, 2, -1, 2, 1, -1], dtype=np.int64)
+ vals.flags.writeable = writable
+ arr, unique = table.get_labels_groupby(vals)
+ expected_arr = np.array([0, 1, -1, 1, 0, -1], dtype=np.intp)
+ expected_unique = np.array([1, 2], dtype=np.int64)
+ tm.assert_numpy_array_equal(arr, expected_arr)
+ tm.assert_numpy_array_equal(unique, expected_unique)
+
+
+def test_tracemalloc_works_for_StringHashTable():
+ N = 1000
+ keys = np.arange(N).astype(np.str_).astype(np.object_)
+ with activated_tracemalloc():
+ table = ht.StringHashTable()
+ table.map_locations(keys)
+ used = get_allocated_khash_memory()
+ my_size = table.sizeof()
+ assert used == my_size
+ del table
+ assert get_allocated_khash_memory() == 0
+
+
+def test_tracemalloc_for_empty_StringHashTable():
+ with activated_tracemalloc():
+ table = ht.StringHashTable()
+ used = get_allocated_khash_memory()
+ my_size = table.sizeof()
+ assert used == my_size
+ del table
+ assert get_allocated_khash_memory() == 0
+
+
+@pytest.mark.parametrize("N", range(1, 110, 4))
+def test_no_reallocation_StringHashTable(N):
+ keys = np.arange(N).astype(np.str_).astype(np.object_)
+ preallocated_table = ht.StringHashTable(N)
+ n_buckets_start = preallocated_table.get_state()["n_buckets"]
+ preallocated_table.map_locations(keys)
+ n_buckets_end = preallocated_table.get_state()["n_buckets"]
+ # original number of buckets was enough:
+ assert n_buckets_start == n_buckets_end
+ # check with clean table (not too much preallocated)
+ clean_table = ht.StringHashTable()
+ clean_table.map_locations(keys)
+ assert n_buckets_start == clean_table.get_state()["n_buckets"]
+
+
+@pytest.mark.parametrize(
+ "table_type, dtype",
+ [
+ (ht.Float64HashTable, np.float64),
+ (ht.Float32HashTable, np.float32),
+ (ht.Complex128HashTable, np.complex128),
+ (ht.Complex64HashTable, np.complex64),
+ ],
+)
+class TestHashTableWithNans:
+ def test_get_set_contains_len(self, table_type, dtype):
+ index = float("nan")
+ table = table_type()
+ assert index not in table
+
+ table.set_item(index, 42)
+ assert len(table) == 1
+ assert index in table
+ assert table.get_item(index) == 42
+
+ table.set_item(index, 41)
+ assert len(table) == 1
+ assert index in table
+ assert table.get_item(index) == 41
+
+ def test_map_locations(self, table_type, dtype):
+ N = 10
+ table = table_type()
+ keys = np.full(N, np.nan, dtype=dtype)
+ table.map_locations(keys)
+ assert len(table) == 1
+ assert table.get_item(np.nan) == N - 1
+
+ def test_unique(self, table_type, dtype):
+ N = 1020
+ table = table_type()
+ keys = np.full(N, np.nan, dtype=dtype)
+ unique = table.unique(keys)
+ assert np.all(np.isnan(unique)) and len(unique) == 1
+
+
+def test_unique_for_nan_objects_floats():
+ table = ht.PyObjectHashTable()
+ keys = np.array([float("nan") for i in range(50)], dtype=np.object_)
+ unique = table.unique(keys)
+ assert len(unique) == 1
+
+
+def test_unique_for_nan_objects_complex():
+ table = ht.PyObjectHashTable()
+ keys = np.array([complex(float("nan"), 1.0) for i in range(50)], dtype=np.object_)
+ unique = table.unique(keys)
+ assert len(unique) == 1
+
+
+def test_unique_for_nan_objects_tuple():
+ table = ht.PyObjectHashTable()
+ keys = np.array(
+ [1] + [(1.0, (float("nan"), 1.0)) for i in range(50)], dtype=np.object_
+ )
+ unique = table.unique(keys)
+ assert len(unique) == 2
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ np.object_,
+ np.complex128,
+ np.int64,
+ np.uint64,
+ np.float64,
+ np.complex64,
+ np.int32,
+ np.uint32,
+ np.float32,
+ np.int16,
+ np.uint16,
+ np.int8,
+ np.uint8,
+ np.intp,
+ ],
+)
+class TestHelpFunctions:
+ def test_value_count(self, dtype, writable):
+ N = 43
+ expected = (np.arange(N) + N).astype(dtype)
+ values = np.repeat(expected, 5)
+ values.flags.writeable = writable
+ keys, counts, _ = ht.value_count(values, False)
+ tm.assert_numpy_array_equal(np.sort(keys), expected)
+ assert np.all(counts == 5)
+
+ def test_value_count_mask(self, dtype):
+ if dtype == np.object_:
+ pytest.skip("mask not implemented for object dtype")
+ values = np.array([1] * 5, dtype=dtype)
+ mask = np.zeros((5,), dtype=np.bool_)
+ mask[1] = True
+ mask[4] = True
+ keys, counts, na_counter = ht.value_count(values, False, mask=mask)
+ assert len(keys) == 2
+ assert na_counter == 2
+
+ def test_value_count_stable(self, dtype, writable):
+ # GH12679
+ values = np.array([2, 1, 5, 22, 3, -1, 8]).astype(dtype)
+ values.flags.writeable = writable
+ keys, counts, _ = ht.value_count(values, False)
+ tm.assert_numpy_array_equal(keys, values)
+ assert np.all(counts == 1)
+
+ def test_duplicated_first(self, dtype, writable):
+ N = 100
+ values = np.repeat(np.arange(N).astype(dtype), 5)
+ values.flags.writeable = writable
+ result = ht.duplicated(values)
+ expected = np.ones_like(values, dtype=np.bool_)
+ expected[::5] = False
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_ismember_yes(self, dtype, writable):
+ N = 127
+ arr = np.arange(N).astype(dtype)
+ values = np.arange(N).astype(dtype)
+ arr.flags.writeable = writable
+ values.flags.writeable = writable
+ result = ht.ismember(arr, values)
+ expected = np.ones_like(values, dtype=np.bool_)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_ismember_no(self, dtype):
+ N = 17
+ arr = np.arange(N).astype(dtype)
+ values = (np.arange(N) + N).astype(dtype)
+ result = ht.ismember(arr, values)
+ expected = np.zeros_like(values, dtype=np.bool_)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_mode(self, dtype, writable):
+ if dtype in (np.int8, np.uint8):
+ N = 53
+ else:
+ N = 11111
+ values = np.repeat(np.arange(N).astype(dtype), 5)
+ values[0] = 42
+ values.flags.writeable = writable
+ result = ht.mode(values, False)[0]
+ assert result == 42
+
+ def test_mode_stable(self, dtype, writable):
+ values = np.array([2, 1, 5, 22, 3, -1, 8]).astype(dtype)
+ values.flags.writeable = writable
+ keys = ht.mode(values, False)[0]
+ tm.assert_numpy_array_equal(keys, values)
+
+
+def test_modes_with_nans():
+ # GH42688, nans aren't mangled
+ nulls = [pd.NA, np.nan, pd.NaT, None]
+ values = np.array([True] + nulls * 2, dtype=np.object_)
+ modes = ht.mode(values, False)[0]
+ assert modes.size == len(nulls)
+
+
+def test_unique_label_indices_intp(writable):
+ keys = np.array([1, 2, 2, 2, 1, 3], dtype=np.intp)
+ keys.flags.writeable = writable
+ result = ht.unique_label_indices(keys)
+ expected = np.array([0, 1, 5], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_unique_label_indices():
+ a = np.random.default_rng(2).integers(1, 1 << 10, 1 << 15).astype(np.intp)
+
+ left = ht.unique_label_indices(a)
+ right = np.unique(a, return_index=True)[1]
+
+ tm.assert_numpy_array_equal(left, right, check_dtype=False)
+
+ a[np.random.default_rng(2).choice(len(a), 10)] = -1
+ left = ht.unique_label_indices(a)
+ right = np.unique(a, return_index=True)[1][1:]
+ tm.assert_numpy_array_equal(left, right, check_dtype=False)
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ np.float64,
+ np.float32,
+ np.complex128,
+ np.complex64,
+ ],
+)
+class TestHelpFunctionsWithNans:
+ def test_value_count(self, dtype):
+ values = np.array([np.nan, np.nan, np.nan], dtype=dtype)
+ keys, counts, _ = ht.value_count(values, True)
+ assert len(keys) == 0
+ keys, counts, _ = ht.value_count(values, False)
+ assert len(keys) == 1 and np.all(np.isnan(keys))
+ assert counts[0] == 3
+
+ def test_duplicated_first(self, dtype):
+ values = np.array([np.nan, np.nan, np.nan], dtype=dtype)
+ result = ht.duplicated(values)
+ expected = np.array([False, True, True])
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_ismember_yes(self, dtype):
+ arr = np.array([np.nan, np.nan, np.nan], dtype=dtype)
+ values = np.array([np.nan, np.nan], dtype=dtype)
+ result = ht.ismember(arr, values)
+ expected = np.array([True, True, True], dtype=np.bool_)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_ismember_no(self, dtype):
+ arr = np.array([np.nan, np.nan, np.nan], dtype=dtype)
+ values = np.array([1], dtype=dtype)
+ result = ht.ismember(arr, values)
+ expected = np.array([False, False, False], dtype=np.bool_)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_mode(self, dtype):
+ values = np.array([42, np.nan, np.nan, np.nan], dtype=dtype)
+ assert ht.mode(values, True)[0] == 42
+ assert np.isnan(ht.mode(values, False)[0])
+
+
+def test_ismember_tuple_with_nans():
+ # GH-41836
+ values = np.empty(2, dtype=object)
+ values[:] = [("a", float("nan")), ("b", 1)]
+ comps = [("a", float("nan"))]
+
+ result = isin(values, comps)
+ expected = np.array([True, False], dtype=np.bool_)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_float_complex_int_are_equal_as_objects():
+ values = ["a", 5, 5.0, 5.0 + 0j]
+ comps = list(range(129))
+ result = isin(np.array(values, dtype=object), np.asarray(comps))
+ expected = np.array([False, True, True, True], dtype=np.bool_)
+ tm.assert_numpy_array_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf8b4fabc54cbab338e9b4a3a061eb09b19714da
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_join.py
@@ -0,0 +1,388 @@
+import numpy as np
+import pytest
+
+from pandas._libs import join as libjoin
+from pandas._libs.join import (
+ inner_join,
+ left_outer_join,
+)
+
+import pandas._testing as tm
+
+
+class TestIndexer:
+ @pytest.mark.parametrize(
+ "dtype", ["int32", "int64", "float32", "float64", "object"]
+ )
+ def test_outer_join_indexer(self, dtype):
+ indexer = libjoin.outer_join_indexer
+
+ left = np.arange(3, dtype=dtype)
+ right = np.arange(2, 5, dtype=dtype)
+ empty = np.array([], dtype=dtype)
+
+ result, lindexer, rindexer = indexer(left, right)
+ assert isinstance(result, np.ndarray)
+ assert isinstance(lindexer, np.ndarray)
+ assert isinstance(rindexer, np.ndarray)
+ tm.assert_numpy_array_equal(result, np.arange(5, dtype=dtype))
+ exp = np.array([0, 1, 2, -1, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(lindexer, exp)
+ exp = np.array([-1, -1, 0, 1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(rindexer, exp)
+
+ result, lindexer, rindexer = indexer(empty, right)
+ tm.assert_numpy_array_equal(result, right)
+ exp = np.array([-1, -1, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(lindexer, exp)
+ exp = np.array([0, 1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(rindexer, exp)
+
+ result, lindexer, rindexer = indexer(left, empty)
+ tm.assert_numpy_array_equal(result, left)
+ exp = np.array([0, 1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(lindexer, exp)
+ exp = np.array([-1, -1, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(rindexer, exp)
+
+ def test_cython_left_outer_join(self):
+ left = np.array([0, 1, 2, 1, 2, 0, 0, 1, 2, 3, 3], dtype=np.intp)
+ right = np.array([1, 1, 0, 4, 2, 2, 1], dtype=np.intp)
+ max_group = 5
+
+ ls, rs = left_outer_join(left, right, max_group)
+
+ exp_ls = left.argsort(kind="mergesort")
+ exp_rs = right.argsort(kind="mergesort")
+
+ exp_li = np.array([0, 1, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10])
+ exp_ri = np.array(
+ [0, 0, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 4, 5, 4, 5, 4, 5, -1, -1]
+ )
+
+ exp_ls = exp_ls.take(exp_li)
+ exp_ls[exp_li == -1] = -1
+
+ exp_rs = exp_rs.take(exp_ri)
+ exp_rs[exp_ri == -1] = -1
+
+ tm.assert_numpy_array_equal(ls, exp_ls, check_dtype=False)
+ tm.assert_numpy_array_equal(rs, exp_rs, check_dtype=False)
+
+ def test_cython_right_outer_join(self):
+ left = np.array([0, 1, 2, 1, 2, 0, 0, 1, 2, 3, 3], dtype=np.intp)
+ right = np.array([1, 1, 0, 4, 2, 2, 1], dtype=np.intp)
+ max_group = 5
+
+ rs, ls = left_outer_join(right, left, max_group)
+
+ exp_ls = left.argsort(kind="mergesort")
+ exp_rs = right.argsort(kind="mergesort")
+
+ # 0 1 1 1
+ exp_li = np.array(
+ [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 3,
+ 4,
+ 5,
+ 3,
+ 4,
+ 5,
+ # 2 2 4
+ 6,
+ 7,
+ 8,
+ 6,
+ 7,
+ 8,
+ -1,
+ ]
+ )
+ exp_ri = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6])
+
+ exp_ls = exp_ls.take(exp_li)
+ exp_ls[exp_li == -1] = -1
+
+ exp_rs = exp_rs.take(exp_ri)
+ exp_rs[exp_ri == -1] = -1
+
+ tm.assert_numpy_array_equal(ls, exp_ls)
+ tm.assert_numpy_array_equal(rs, exp_rs)
+
+ def test_cython_inner_join(self):
+ left = np.array([0, 1, 2, 1, 2, 0, 0, 1, 2, 3, 3], dtype=np.intp)
+ right = np.array([1, 1, 0, 4, 2, 2, 1, 4], dtype=np.intp)
+ max_group = 5
+
+ ls, rs = inner_join(left, right, max_group)
+
+ exp_ls = left.argsort(kind="mergesort")
+ exp_rs = right.argsort(kind="mergesort")
+
+ exp_li = np.array([0, 1, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8])
+ exp_ri = np.array([0, 0, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 4, 5, 4, 5, 4, 5])
+
+ exp_ls = exp_ls.take(exp_li)
+ exp_ls[exp_li == -1] = -1
+
+ exp_rs = exp_rs.take(exp_ri)
+ exp_rs[exp_ri == -1] = -1
+
+ tm.assert_numpy_array_equal(ls, exp_ls)
+ tm.assert_numpy_array_equal(rs, exp_rs)
+
+
+def test_left_join_indexer_unique(writable):
+ a = np.array([1, 2, 3, 4, 5], dtype=np.int64)
+ b = np.array([2, 2, 3, 4, 4], dtype=np.int64)
+ # GH#37312, GH#37264
+ a.setflags(write=writable)
+ b.setflags(write=writable)
+
+ result = libjoin.left_join_indexer_unique(b, a)
+ expected = np.array([1, 1, 2, 3, 3], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_left_outer_join_bug():
+ left = np.array(
+ [
+ 0,
+ 1,
+ 0,
+ 1,
+ 1,
+ 2,
+ 3,
+ 1,
+ 0,
+ 2,
+ 1,
+ 2,
+ 0,
+ 1,
+ 1,
+ 2,
+ 3,
+ 2,
+ 3,
+ 2,
+ 1,
+ 1,
+ 3,
+ 0,
+ 3,
+ 2,
+ 3,
+ 0,
+ 0,
+ 2,
+ 3,
+ 2,
+ 0,
+ 3,
+ 1,
+ 3,
+ 0,
+ 1,
+ 3,
+ 0,
+ 0,
+ 1,
+ 0,
+ 3,
+ 1,
+ 0,
+ 1,
+ 0,
+ 1,
+ 1,
+ 0,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 0,
+ 3,
+ 1,
+ 2,
+ 0,
+ 0,
+ 3,
+ 1,
+ 3,
+ 2,
+ 2,
+ 0,
+ 1,
+ 3,
+ 0,
+ 2,
+ 3,
+ 2,
+ 3,
+ 3,
+ 2,
+ 3,
+ 3,
+ 1,
+ 3,
+ 2,
+ 0,
+ 0,
+ 3,
+ 1,
+ 1,
+ 1,
+ 0,
+ 2,
+ 3,
+ 3,
+ 1,
+ 2,
+ 0,
+ 3,
+ 1,
+ 2,
+ 0,
+ 2,
+ ],
+ dtype=np.intp,
+ )
+
+ right = np.array([3, 1], dtype=np.intp)
+ max_groups = 4
+
+ lidx, ridx = libjoin.left_outer_join(left, right, max_groups, sort=False)
+
+ exp_lidx = np.arange(len(left), dtype=np.intp)
+ exp_ridx = -np.ones(len(left), dtype=np.intp)
+
+ exp_ridx[left == 1] = 1
+ exp_ridx[left == 3] = 0
+
+ tm.assert_numpy_array_equal(lidx, exp_lidx)
+ tm.assert_numpy_array_equal(ridx, exp_ridx)
+
+
+def test_inner_join_indexer():
+ a = np.array([1, 2, 3, 4, 5], dtype=np.int64)
+ b = np.array([0, 3, 5, 7, 9], dtype=np.int64)
+
+ index, ares, bres = libjoin.inner_join_indexer(a, b)
+
+ index_exp = np.array([3, 5], dtype=np.int64)
+ tm.assert_almost_equal(index, index_exp)
+
+ aexp = np.array([2, 4], dtype=np.intp)
+ bexp = np.array([1, 2], dtype=np.intp)
+ tm.assert_almost_equal(ares, aexp)
+ tm.assert_almost_equal(bres, bexp)
+
+ a = np.array([5], dtype=np.int64)
+ b = np.array([5], dtype=np.int64)
+
+ index, ares, bres = libjoin.inner_join_indexer(a, b)
+ tm.assert_numpy_array_equal(index, np.array([5], dtype=np.int64))
+ tm.assert_numpy_array_equal(ares, np.array([0], dtype=np.intp))
+ tm.assert_numpy_array_equal(bres, np.array([0], dtype=np.intp))
+
+
+def test_outer_join_indexer():
+ a = np.array([1, 2, 3, 4, 5], dtype=np.int64)
+ b = np.array([0, 3, 5, 7, 9], dtype=np.int64)
+
+ index, ares, bres = libjoin.outer_join_indexer(a, b)
+
+ index_exp = np.array([0, 1, 2, 3, 4, 5, 7, 9], dtype=np.int64)
+ tm.assert_almost_equal(index, index_exp)
+
+ aexp = np.array([-1, 0, 1, 2, 3, 4, -1, -1], dtype=np.intp)
+ bexp = np.array([0, -1, -1, 1, -1, 2, 3, 4], dtype=np.intp)
+ tm.assert_almost_equal(ares, aexp)
+ tm.assert_almost_equal(bres, bexp)
+
+ a = np.array([5], dtype=np.int64)
+ b = np.array([5], dtype=np.int64)
+
+ index, ares, bres = libjoin.outer_join_indexer(a, b)
+ tm.assert_numpy_array_equal(index, np.array([5], dtype=np.int64))
+ tm.assert_numpy_array_equal(ares, np.array([0], dtype=np.intp))
+ tm.assert_numpy_array_equal(bres, np.array([0], dtype=np.intp))
+
+
+def test_left_join_indexer():
+ a = np.array([1, 2, 3, 4, 5], dtype=np.int64)
+ b = np.array([0, 3, 5, 7, 9], dtype=np.int64)
+
+ index, ares, bres = libjoin.left_join_indexer(a, b)
+
+ tm.assert_almost_equal(index, a)
+
+ aexp = np.array([0, 1, 2, 3, 4], dtype=np.intp)
+ bexp = np.array([-1, -1, 1, -1, 2], dtype=np.intp)
+ tm.assert_almost_equal(ares, aexp)
+ tm.assert_almost_equal(bres, bexp)
+
+ a = np.array([5], dtype=np.int64)
+ b = np.array([5], dtype=np.int64)
+
+ index, ares, bres = libjoin.left_join_indexer(a, b)
+ tm.assert_numpy_array_equal(index, np.array([5], dtype=np.int64))
+ tm.assert_numpy_array_equal(ares, np.array([0], dtype=np.intp))
+ tm.assert_numpy_array_equal(bres, np.array([0], dtype=np.intp))
+
+
+def test_left_join_indexer2():
+ idx = np.array([1, 1, 2, 5], dtype=np.int64)
+ idx2 = np.array([1, 2, 5, 7, 9], dtype=np.int64)
+
+ res, lidx, ridx = libjoin.left_join_indexer(idx2, idx)
+
+ exp_res = np.array([1, 1, 2, 5, 7, 9], dtype=np.int64)
+ tm.assert_almost_equal(res, exp_res)
+
+ exp_lidx = np.array([0, 0, 1, 2, 3, 4], dtype=np.intp)
+ tm.assert_almost_equal(lidx, exp_lidx)
+
+ exp_ridx = np.array([0, 1, 2, 3, -1, -1], dtype=np.intp)
+ tm.assert_almost_equal(ridx, exp_ridx)
+
+
+def test_outer_join_indexer2():
+ idx = np.array([1, 1, 2, 5], dtype=np.int64)
+ idx2 = np.array([1, 2, 5, 7, 9], dtype=np.int64)
+
+ res, lidx, ridx = libjoin.outer_join_indexer(idx2, idx)
+
+ exp_res = np.array([1, 1, 2, 5, 7, 9], dtype=np.int64)
+ tm.assert_almost_equal(res, exp_res)
+
+ exp_lidx = np.array([0, 0, 1, 2, 3, 4], dtype=np.intp)
+ tm.assert_almost_equal(lidx, exp_lidx)
+
+ exp_ridx = np.array([0, 1, 2, 3, -1, -1], dtype=np.intp)
+ tm.assert_almost_equal(ridx, exp_ridx)
+
+
+def test_inner_join_indexer2():
+ idx = np.array([1, 1, 2, 5], dtype=np.int64)
+ idx2 = np.array([1, 2, 5, 7, 9], dtype=np.int64)
+
+ res, lidx, ridx = libjoin.inner_join_indexer(idx2, idx)
+
+ exp_res = np.array([1, 1, 2, 5], dtype=np.int64)
+ tm.assert_almost_equal(res, exp_res)
+
+ exp_lidx = np.array([0, 0, 1, 2], dtype=np.intp)
+ tm.assert_almost_equal(lidx, exp_lidx)
+
+ exp_ridx = np.array([0, 1, 2, 3], dtype=np.intp)
+ tm.assert_almost_equal(ridx, exp_ridx)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_lib.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_lib.py
new file mode 100644
index 0000000000000000000000000000000000000000..792b2ef121cf2f0bd8266050217031b1c04f06c2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_lib.py
@@ -0,0 +1,358 @@
+import pickle
+
+import numpy as np
+import pytest
+
+from pandas._libs import (
+ Timedelta,
+ lib,
+ writers as libwriters,
+)
+from pandas.compat import IS64
+
+from pandas import Index
+import pandas._testing as tm
+
+
+class TestMisc:
+ def test_max_len_string_array(self):
+ arr = a = np.array(["foo", "b", np.nan], dtype="object")
+ assert libwriters.max_len_string_array(arr) == 3
+
+ # unicode
+ arr = a.astype("U").astype(object)
+ assert libwriters.max_len_string_array(arr) == 3
+
+ # bytes for python3
+ arr = a.astype("S").astype(object)
+ assert libwriters.max_len_string_array(arr) == 3
+
+ # raises
+ msg = "No matching signature found"
+ with pytest.raises(TypeError, match=msg):
+ libwriters.max_len_string_array(arr.astype("U"))
+
+ def test_fast_unique_multiple_list_gen_sort(self):
+ keys = [["p", "a"], ["n", "d"], ["a", "s"]]
+
+ gen = (key for key in keys)
+ expected = np.array(["a", "d", "n", "p", "s"])
+ out = lib.fast_unique_multiple_list_gen(gen, sort=True)
+ tm.assert_numpy_array_equal(np.array(out), expected)
+
+ gen = (key for key in keys)
+ expected = np.array(["p", "a", "n", "d", "s"])
+ out = lib.fast_unique_multiple_list_gen(gen, sort=False)
+ tm.assert_numpy_array_equal(np.array(out), expected)
+
+ def test_fast_multiget_timedelta_resos(self):
+ # This will become relevant for test_constructor_dict_timedelta64_index
+ # once Timedelta constructor preserves reso when passed a
+ # np.timedelta64 object
+ td = Timedelta(days=1)
+
+ mapping1 = {td: 1}
+ mapping2 = {td.as_unit("s"): 1}
+
+ oindex = Index([td * n for n in range(3)])._values.astype(object)
+
+ expected = lib.fast_multiget(mapping1, oindex)
+ result = lib.fast_multiget(mapping2, oindex)
+ tm.assert_numpy_array_equal(result, expected)
+
+ # case that can't be cast to td64ns
+ td = Timedelta(np.timedelta64(146000, "D"))
+ assert hash(td) == hash(td.as_unit("ms"))
+ assert hash(td) == hash(td.as_unit("us"))
+ mapping1 = {td: 1}
+ mapping2 = {td.as_unit("ms"): 1}
+
+ oindex = Index([td * n for n in range(3)])._values.astype(object)
+
+ expected = lib.fast_multiget(mapping1, oindex)
+ result = lib.fast_multiget(mapping2, oindex)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestIndexing:
+ def test_maybe_indices_to_slice_left_edge(self):
+ target = np.arange(100)
+
+ # slice
+ indices = np.array([], dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ @pytest.mark.parametrize("end", [1, 2, 5, 20, 99])
+ @pytest.mark.parametrize("step", [1, 2, 4])
+ def test_maybe_indices_to_slice_left_edge_not_slice_end_steps(self, end, step):
+ target = np.arange(100)
+ indices = np.arange(0, end, step, dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ # reverse
+ indices = indices[::-1]
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ @pytest.mark.parametrize(
+ "case", [[2, 1, 2, 0], [2, 2, 1, 0], [0, 1, 2, 1], [-2, 0, 2], [2, 0, -2]]
+ )
+ def test_maybe_indices_to_slice_left_edge_not_slice(self, case):
+ # not slice
+ target = np.arange(100)
+ indices = np.array(case, dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert not isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(maybe_slice, indices)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ @pytest.mark.parametrize("start", [0, 2, 5, 20, 97, 98])
+ @pytest.mark.parametrize("step", [1, 2, 4])
+ def test_maybe_indices_to_slice_right_edge(self, start, step):
+ target = np.arange(100)
+
+ # slice
+ indices = np.arange(start, 99, step, dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ # reverse
+ indices = indices[::-1]
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ def test_maybe_indices_to_slice_right_edge_not_slice(self):
+ # not slice
+ target = np.arange(100)
+ indices = np.array([97, 98, 99, 100], dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert not isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(maybe_slice, indices)
+
+ msg = "index 100 is out of bounds for axis (0|1) with size 100"
+
+ with pytest.raises(IndexError, match=msg):
+ target[indices]
+ with pytest.raises(IndexError, match=msg):
+ target[maybe_slice]
+
+ indices = np.array([100, 99, 98, 97], dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert not isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(maybe_slice, indices)
+
+ with pytest.raises(IndexError, match=msg):
+ target[indices]
+ with pytest.raises(IndexError, match=msg):
+ target[maybe_slice]
+
+ @pytest.mark.parametrize(
+ "case", [[99, 97, 99, 96], [99, 99, 98, 97], [98, 98, 97, 96]]
+ )
+ def test_maybe_indices_to_slice_right_edge_cases(self, case):
+ target = np.arange(100)
+ indices = np.array(case, dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert not isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(maybe_slice, indices)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ @pytest.mark.parametrize("step", [1, 2, 4, 5, 8, 9])
+ def test_maybe_indices_to_slice_both_edges(self, step):
+ target = np.arange(10)
+
+ # slice
+ indices = np.arange(0, 9, step, dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+ assert isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ # reverse
+ indices = indices[::-1]
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+ assert isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ @pytest.mark.parametrize("case", [[4, 2, 0, -2], [2, 2, 1, 0], [0, 1, 2, 1]])
+ def test_maybe_indices_to_slice_both_edges_not_slice(self, case):
+ # not slice
+ target = np.arange(10)
+ indices = np.array(case, dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+ assert not isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(maybe_slice, indices)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ @pytest.mark.parametrize("start, end", [(2, 10), (5, 25), (65, 97)])
+ @pytest.mark.parametrize("step", [1, 2, 4, 20])
+ def test_maybe_indices_to_slice_middle(self, start, end, step):
+ target = np.arange(100)
+
+ # slice
+ indices = np.arange(start, end, step, dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ # reverse
+ indices = indices[::-1]
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ @pytest.mark.parametrize(
+ "case", [[14, 12, 10, 12], [12, 12, 11, 10], [10, 11, 12, 11]]
+ )
+ def test_maybe_indices_to_slice_middle_not_slice(self, case):
+ # not slice
+ target = np.arange(100)
+ indices = np.array(case, dtype=np.intp)
+ maybe_slice = lib.maybe_indices_to_slice(indices, len(target))
+
+ assert not isinstance(maybe_slice, slice)
+ tm.assert_numpy_array_equal(maybe_slice, indices)
+ tm.assert_numpy_array_equal(target[indices], target[maybe_slice])
+
+ def test_maybe_booleans_to_slice(self):
+ arr = np.array([0, 0, 1, 1, 1, 0, 1], dtype=np.uint8)
+ result = lib.maybe_booleans_to_slice(arr)
+ assert result.dtype == np.bool_
+
+ result = lib.maybe_booleans_to_slice(arr[:0])
+ assert result == slice(0, 0)
+
+ def test_get_reverse_indexer(self):
+ indexer = np.array([-1, -1, 1, 2, 0, -1, 3, 4], dtype=np.intp)
+ result = lib.get_reverse_indexer(indexer, 5)
+ expected = np.array([4, 2, 3, 6, 7], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["int64", "int32"])
+ def test_is_range_indexer(self, dtype):
+ # GH#50592
+ left = np.arange(0, 100, dtype=dtype)
+ assert lib.is_range_indexer(left, 100)
+
+ @pytest.mark.skipif(
+ not IS64,
+ reason="2**31 is too big for Py_ssize_t on 32-bit. "
+ "It doesn't matter though since you cannot create an array that long on 32-bit",
+ )
+ @pytest.mark.parametrize("dtype", ["int64", "int32"])
+ def test_is_range_indexer_big_n(self, dtype):
+ # GH53616
+ left = np.arange(0, 100, dtype=dtype)
+
+ assert not lib.is_range_indexer(left, 2**31)
+
+ @pytest.mark.parametrize("dtype", ["int64", "int32"])
+ def test_is_range_indexer_not_equal(self, dtype):
+ # GH#50592
+ left = np.array([1, 2], dtype=dtype)
+ assert not lib.is_range_indexer(left, 2)
+
+ @pytest.mark.parametrize("dtype", ["int64", "int32"])
+ def test_is_range_indexer_not_equal_shape(self, dtype):
+ # GH#50592
+ left = np.array([0, 1, 2], dtype=dtype)
+ assert not lib.is_range_indexer(left, 2)
+
+
+def test_cache_readonly_preserve_docstrings():
+ # GH18197
+ assert Index.hasnans.__doc__ is not None
+
+
+def test_no_default_pickle(temp_file):
+ # GH#40397
+ obj = tm.round_trip_pickle(lib.no_default, temp_file)
+ assert obj is lib.no_default
+
+
+def test_ensure_string_array_copy():
+ # ensure the original array is not modified in case of copy=False with
+ # pickle-roundtripped object dtype array
+ # https://github.com/pandas-dev/pandas/issues/54654
+ arr = np.array(["a", None], dtype=object)
+ arr = pickle.loads(pickle.dumps(arr))
+ result = lib.ensure_string_array(arr, copy=False)
+ assert not np.shares_memory(arr, result)
+ assert arr[1] is None
+ assert result[1] is np.nan
+
+
+def test_ensure_string_array_list_of_lists():
+ # GH#61155: ensure list of lists doesn't get converted to string
+ arr = [list("test"), list("word")]
+ result = lib.ensure_string_array(arr)
+
+ # Each item in result should still be a list, not a stringified version
+ expected = np.array(["['t', 'e', 's', 't']", "['w', 'o', 'r', 'd']"], dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_item_from_zerodim_for_subclasses():
+ # GH#62981 Ensure item_from_zerodim preserves subclasses of ndarray
+ # Define a custom ndarray subclass
+ class TestArray(np.ndarray):
+ def __new__(cls, input_array):
+ return np.asarray(input_array).view(cls)
+
+ def __array_finalize__(self, obj) -> None:
+ self._is_test_array = True
+
+ # Define test data
+ val_0_dim = 1
+ val_1_dim = [1, 2, 3]
+
+ # 0-dim and 1-dim numpy arrays
+ arr_0_dim = np.array(val_0_dim)
+ arr_1_dim = np.array(val_1_dim)
+
+ # 0-dim and 1-dim TestArray arrays
+ test_arr_0_dim = TestArray(val_0_dim)
+ test_arr_1_dim = TestArray(val_1_dim)
+
+ # Check that behavior did not change for regular numpy arrays
+ # Test with regular numpy 0-dim array
+ result = lib.item_from_zerodim(arr_0_dim)
+ expected = val_0_dim
+ assert result == expected
+ assert np.isscalar(result)
+
+ # Test with regular numpy 1-dim array
+ result = lib.item_from_zerodim(arr_1_dim)
+ expected = arr_1_dim
+ tm.assert_numpy_array_equal(result, expected)
+ assert isinstance(result, np.ndarray)
+
+ # Check that behaviour for subclasses now is as expected
+ # Test with TestArray 0-dim array
+ result = lib.item_from_zerodim(test_arr_0_dim)
+ expected = test_arr_0_dim
+ assert result == expected
+ assert isinstance(result, TestArray)
+
+ # Test with TestArray 1-dim array
+ result = lib.item_from_zerodim(test_arr_1_dim)
+ expected = test_arr_1_dim
+ assert np.all(result == expected)
+ assert isinstance(result, TestArray)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_libalgos.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_libalgos.py
new file mode 100644
index 0000000000000000000000000000000000000000..42d09c72aab2baa9636093d172d864cbe0e41b12
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/libs/test_libalgos.py
@@ -0,0 +1,162 @@
+from datetime import datetime
+from itertools import permutations
+
+import numpy as np
+
+from pandas._libs import algos as libalgos
+
+import pandas._testing as tm
+
+
+def test_ensure_platform_int():
+ arr = np.arange(100, dtype=np.intp)
+
+ result = libalgos.ensure_platform_int(arr)
+ assert result is arr
+
+
+def test_is_lexsorted():
+ failure = [
+ np.array(
+ ([3] * 32) + ([2] * 32) + ([1] * 32) + ([0] * 32),
+ dtype="int64",
+ ),
+ np.array(
+ list(range(31))[::-1] * 4,
+ dtype="int64",
+ ),
+ ]
+
+ assert not libalgos.is_lexsorted(failure)
+
+
+def test_groupsort_indexer():
+ a = np.random.default_rng(2).integers(0, 1000, 100).astype(np.intp)
+ b = np.random.default_rng(2).integers(0, 1000, 100).astype(np.intp)
+
+ result = libalgos.groupsort_indexer(a, 1000)[0]
+
+ # need to use a stable sort
+ # np.argsort returns int, groupsort_indexer
+ # always returns intp
+ expected = np.argsort(a, kind="mergesort")
+ expected = expected.astype(np.intp)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+ # compare with lexsort
+ # np.lexsort returns int, groupsort_indexer
+ # always returns intp
+ key = a * 1000 + b
+ result = libalgos.groupsort_indexer(key, 1000000)[0]
+ expected = np.lexsort((b, a))
+ expected = expected.astype(np.intp)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestPadBackfill:
+ def test_backfill(self):
+ old = np.array([1, 5, 10], dtype=np.int64)
+ new = np.array(list(range(12)), dtype=np.int64)
+
+ filler = libalgos.backfill["int64_t"](old, new)
+
+ expect_filler = np.array([0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(filler, expect_filler)
+
+ # corner case
+ old = np.array([1, 4], dtype=np.int64)
+ new = np.array(list(range(5, 10)), dtype=np.int64)
+ filler = libalgos.backfill["int64_t"](old, new)
+
+ expect_filler = np.array([-1, -1, -1, -1, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(filler, expect_filler)
+
+ def test_pad(self):
+ old = np.array([1, 5, 10], dtype=np.int64)
+ new = np.array(list(range(12)), dtype=np.int64)
+
+ filler = libalgos.pad["int64_t"](old, new)
+
+ expect_filler = np.array([-1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(filler, expect_filler)
+
+ # corner case
+ old = np.array([5, 10], dtype=np.int64)
+ new = np.arange(5, dtype=np.int64)
+ filler = libalgos.pad["int64_t"](old, new)
+ expect_filler = np.array([-1, -1, -1, -1, -1], dtype=np.intp)
+ tm.assert_numpy_array_equal(filler, expect_filler)
+
+ def test_pad_backfill_object_segfault(self):
+ old = np.array([], dtype="O")
+ new = np.array([datetime(2010, 12, 31)], dtype="O")
+
+ result = libalgos.pad["object"](old, new)
+ expected = np.array([-1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = libalgos.pad["object"](new, old)
+ expected = np.array([], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = libalgos.backfill["object"](old, new)
+ expected = np.array([-1], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+ result = libalgos.backfill["object"](new, old)
+ expected = np.array([], dtype=np.intp)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestInfinity:
+ def test_infinity_sort(self):
+ # GH#13445
+ # numpy's argsort can be unhappy if something is less than
+ # itself. Instead, let's give our infinities a self-consistent
+ # ordering, but outside the float extended real line.
+
+ Inf = libalgos.Infinity()
+ NegInf = libalgos.NegInfinity()
+
+ ref_nums = [NegInf, float("-inf"), -1e100, 0, 1e100, float("inf"), Inf]
+
+ assert all(Inf >= x for x in ref_nums)
+ assert all(Inf > x or x is Inf for x in ref_nums)
+ assert Inf >= Inf and Inf == Inf
+ assert not Inf < Inf and not Inf > Inf
+ assert libalgos.Infinity() == libalgos.Infinity()
+ assert not libalgos.Infinity() != libalgos.Infinity()
+
+ assert all(NegInf <= x for x in ref_nums)
+ assert all(NegInf < x or x is NegInf for x in ref_nums)
+ assert NegInf <= NegInf and NegInf == NegInf
+ assert not NegInf < NegInf and not NegInf > NegInf
+ assert libalgos.NegInfinity() == libalgos.NegInfinity()
+ assert not libalgos.NegInfinity() != libalgos.NegInfinity()
+
+ for perm in permutations(ref_nums):
+ assert sorted(perm) == ref_nums
+
+ # smoke tests
+ np.array([libalgos.Infinity()] * 32).argsort()
+ np.array([libalgos.NegInfinity()] * 32).argsort()
+
+ def test_infinity_against_nan(self):
+ Inf = libalgos.Infinity()
+ NegInf = libalgos.NegInfinity()
+
+ assert not Inf > np.nan
+ assert not Inf >= np.nan
+ assert not Inf < np.nan
+ assert not Inf <= np.nan
+ assert not Inf == np.nan
+ assert Inf != np.nan
+
+ assert not NegInf > np.nan
+ assert not NegInf >= np.nan
+ assert not NegInf < np.nan
+ assert not NegInf <= np.nan
+ assert not NegInf == np.nan
+ assert NegInf != np.nan
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..588bbf88e856243ce539a5378b22468228bcf280
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/common.py
@@ -0,0 +1,579 @@
+"""
+Module consolidating common testing functions for checking plotting.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import numpy as np
+
+from pandas.core.dtypes.api import is_list_like
+
+import pandas as pd
+from pandas import Series
+import pandas._testing as tm
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+
+ from matplotlib.axes import Axes
+
+
+def _check_legend_labels(axes, labels=None, visible=True):
+ """
+ Check each axes has expected legend labels
+
+ Parameters
+ ----------
+ axes : matplotlib Axes object, or its list-like
+ labels : list-like
+ expected legend labels
+ visible : bool
+ expected legend visibility. labels are checked only when visible is
+ True
+ """
+ if visible and (labels is None):
+ raise ValueError("labels must be specified when visible is True")
+ axes = _flatten_visible(axes)
+ for ax in axes:
+ if visible:
+ assert ax.get_legend() is not None
+ _check_text_labels(ax.get_legend().get_texts(), labels)
+ else:
+ assert ax.get_legend() is None
+
+
+def _check_legend_marker(ax, expected_markers=None, visible=True):
+ """
+ Check ax has expected legend markers
+
+ Parameters
+ ----------
+ ax : matplotlib Axes object
+ expected_markers : list-like
+ expected legend markers
+ visible : bool
+ expected legend visibility. labels are checked only when visible is
+ True
+ """
+ if visible and (expected_markers is None):
+ raise ValueError("Markers must be specified when visible is True")
+ if visible:
+ handles, _ = ax.get_legend_handles_labels()
+ markers = [handle.get_marker() for handle in handles]
+ assert markers == expected_markers
+ else:
+ assert ax.get_legend() is None
+
+
+def _check_data(xp, rs):
+ """
+ Check each axes has identical lines
+
+ Parameters
+ ----------
+ xp : matplotlib Axes object
+ rs : matplotlib Axes object
+ """
+ xp_lines = xp.get_lines()
+ rs_lines = rs.get_lines()
+
+ assert len(xp_lines) == len(rs_lines)
+ for xpl, rsl in zip(xp_lines, rs_lines, strict=True):
+ xpdata = xpl.get_xydata()
+ rsdata = rsl.get_xydata()
+ tm.assert_almost_equal(xpdata, rsdata)
+
+
+def _check_visible(collections, visible=True):
+ """
+ Check each artist is visible or not
+
+ Parameters
+ ----------
+ collections : matplotlib Artist or its list-like
+ target Artist or its list or collection
+ visible : bool
+ expected visibility
+ """
+ from matplotlib.collections import Collection
+
+ if not isinstance(collections, Collection) and not is_list_like(collections):
+ collections = [collections]
+
+ for patch in collections:
+ assert patch.get_visible() == visible
+
+
+def _check_patches_all_filled(axes: Axes | Sequence[Axes], filled: bool = True) -> None:
+ """
+ Check for each artist whether it is filled or not
+
+ Parameters
+ ----------
+ axes : matplotlib Axes object, or its list-like
+ filled : bool
+ expected filling
+ """
+
+ axes = _flatten_visible(axes)
+ for ax in axes:
+ for patch in ax.patches:
+ assert patch.fill == filled
+
+
+def _get_colors_mapped(series, colors):
+ unique = series.unique()
+ # unique and colors length can be differed
+ # depending on slice value
+ mapped = dict(zip(unique, colors))
+ return [mapped[v] for v in series.values]
+
+
+def _check_colors(collections, linecolors=None, facecolors=None, mapping=None):
+ """
+ Check each artist has expected line colors and face colors
+
+ Parameters
+ ----------
+ collections : list-like
+ list or collection of target artist
+ linecolors : list-like which has the same length as collections
+ list of expected line colors
+ facecolors : list-like which has the same length as collections
+ list of expected face colors
+ mapping : Series
+ Series used for color grouping key
+ used for andrew_curves, parallel_coordinates, radviz test
+ """
+ from matplotlib import colors
+ from matplotlib.collections import (
+ Collection,
+ LineCollection,
+ PolyCollection,
+ )
+ from matplotlib.lines import Line2D
+
+ conv = colors.ColorConverter
+ if linecolors is not None:
+ if mapping is not None:
+ linecolors = _get_colors_mapped(mapping, linecolors)
+ linecolors = linecolors[: len(collections)]
+
+ assert len(collections) == len(linecolors)
+ for patch, color in zip(collections, linecolors, strict=True):
+ if isinstance(patch, Line2D):
+ result = patch.get_color()
+ # Line2D may contains string color expression
+ result = conv.to_rgba(result)
+ elif isinstance(patch, (PolyCollection, LineCollection)):
+ result = tuple(patch.get_edgecolor()[0])
+ else:
+ result = patch.get_edgecolor()
+
+ expected = conv.to_rgba(color)
+ assert result == expected
+
+ if facecolors is not None:
+ if mapping is not None:
+ facecolors = _get_colors_mapped(mapping, facecolors)
+ facecolors = facecolors[: len(collections)]
+
+ assert len(collections) == len(facecolors)
+ for patch, color in zip(collections, facecolors, strict=True):
+ if isinstance(patch, Collection):
+ # returned as list of np.array
+ result = patch.get_facecolor()[0]
+ else:
+ result = patch.get_facecolor()
+
+ if isinstance(result, np.ndarray):
+ result = tuple(result)
+
+ expected = conv.to_rgba(color)
+ assert result == expected
+
+
+def _check_text_labels(texts, expected):
+ """
+ Check each text has expected labels
+
+ Parameters
+ ----------
+ texts : matplotlib Text object, or its list-like
+ target text, or its list
+ expected : str or list-like which has the same length as texts
+ expected text label, or its list
+ """
+ if not is_list_like(texts):
+ assert texts.get_text() == expected
+ else:
+ labels = [t.get_text() for t in texts]
+ assert len(labels) == len(expected)
+ for label, e in zip(labels, expected, strict=True):
+ assert label == e
+
+
+def _check_ticks_props(axes, xlabelsize=None, xrot=None, ylabelsize=None, yrot=None):
+ """
+ Check each axes has expected tick properties
+
+ Parameters
+ ----------
+ axes : matplotlib Axes object, or its list-like
+ xlabelsize : number
+ expected xticks font size
+ xrot : number
+ expected xticks rotation
+ ylabelsize : number
+ expected yticks font size
+ yrot : number
+ expected yticks rotation
+ """
+ from matplotlib.ticker import NullFormatter
+
+ axes = _flatten_visible(axes)
+ for ax in axes:
+ if xlabelsize is not None or xrot is not None:
+ if isinstance(ax.xaxis.get_minor_formatter(), NullFormatter):
+ # If minor ticks has NullFormatter, rot / fontsize are not
+ # retained
+ labels = ax.get_xticklabels()
+ else:
+ labels = ax.get_xticklabels() + ax.get_xticklabels(minor=True)
+
+ for label in labels:
+ if xlabelsize is not None:
+ tm.assert_almost_equal(label.get_fontsize(), xlabelsize)
+ if xrot is not None:
+ tm.assert_almost_equal(label.get_rotation(), xrot)
+
+ if ylabelsize is not None or yrot is not None:
+ if isinstance(ax.yaxis.get_minor_formatter(), NullFormatter):
+ labels = ax.get_yticklabels()
+ else:
+ labels = ax.get_yticklabels() + ax.get_yticklabels(minor=True)
+
+ for label in labels:
+ if ylabelsize is not None:
+ tm.assert_almost_equal(label.get_fontsize(), ylabelsize)
+ if yrot is not None:
+ tm.assert_almost_equal(label.get_rotation(), yrot)
+
+
+def _check_ax_scales(axes, xaxis="linear", yaxis="linear"):
+ """
+ Check each axes has expected scales
+
+ Parameters
+ ----------
+ axes : matplotlib Axes object, or its list-like
+ xaxis : {'linear', 'log'}
+ expected xaxis scale
+ yaxis : {'linear', 'log'}
+ expected yaxis scale
+ """
+ axes = _flatten_visible(axes)
+ for ax in axes:
+ assert ax.xaxis.get_scale() == xaxis
+ assert ax.yaxis.get_scale() == yaxis
+
+
+def _check_axes_shape(axes, axes_num=None, layout=None, figsize=None):
+ """
+ Check expected number of axes is drawn in expected layout
+
+ Parameters
+ ----------
+ axes : matplotlib Axes object, or its list-like
+ axes_num : number
+ expected number of axes. Unnecessary axes should be set to
+ invisible.
+ layout : tuple
+ expected layout, (expected number of rows , columns)
+ figsize : tuple
+ expected figsize. default is matplotlib default
+ """
+ from pandas.plotting._matplotlib.tools import flatten_axes
+
+ if figsize is None:
+ figsize = (6.4, 4.8)
+ visible_axes = _flatten_visible(axes)
+
+ if axes_num is not None:
+ assert len(visible_axes) == axes_num
+ for ax in visible_axes:
+ # check something drawn on visible axes
+ assert len(ax.get_children()) > 0
+
+ if layout is not None:
+ x_set = set()
+ y_set = set()
+ for ax in flatten_axes(axes):
+ # check axes coordinates to estimate layout
+ points = ax.get_position().get_points()
+ x_set.add(points[0][0])
+ y_set.add(points[0][1])
+ result = (len(y_set), len(x_set))
+ assert result == layout
+
+ tm.assert_numpy_array_equal(
+ visible_axes[0].figure.get_size_inches(),
+ np.array(figsize, dtype=np.float64),
+ )
+
+
+def _flatten_visible(axes: Axes | Sequence[Axes]) -> Sequence[Axes]:
+ """
+ Flatten axes, and filter only visible
+
+ Parameters
+ ----------
+ axes : matplotlib Axes object, or its list-like
+
+ """
+ from pandas.plotting._matplotlib.tools import flatten_axes
+
+ axes_ndarray = flatten_axes(axes)
+ axes = [ax for ax in axes_ndarray if ax.get_visible()]
+ return axes
+
+
+def _check_has_errorbars(axes, xerr=0, yerr=0):
+ """
+ Check axes has expected number of errorbars
+
+ Parameters
+ ----------
+ axes : matplotlib Axes object, or its list-like
+ xerr : number
+ expected number of x errorbar
+ yerr : number
+ expected number of y errorbar
+ """
+ axes = _flatten_visible(axes)
+ for ax in axes:
+ containers = ax.containers
+ xerr_count = 0
+ yerr_count = 0
+ for c in containers:
+ has_xerr = getattr(c, "has_xerr", False)
+ has_yerr = getattr(c, "has_yerr", False)
+ if has_xerr:
+ xerr_count += 1
+ if has_yerr:
+ yerr_count += 1
+ assert xerr == xerr_count
+ assert yerr == yerr_count
+
+
+def _check_box_return_type(
+ returned, return_type, expected_keys=None, check_ax_title=True
+):
+ """
+ Check box returned type is correct
+
+ Parameters
+ ----------
+ returned : object to be tested, returned from boxplot
+ return_type : str
+ return_type passed to boxplot
+ expected_keys : list-like, optional
+ group labels in subplot case. If not passed,
+ the function checks assuming boxplot uses single ax
+ check_ax_title : bool
+ Whether to check the ax.title is the same as expected_key
+ Intended to be checked by calling from ``boxplot``.
+ Normal ``plot`` doesn't attach ``ax.title``, it must be disabled.
+ """
+ from matplotlib.axes import Axes
+
+ types = {"dict": dict, "axes": Axes, "both": tuple}
+ if expected_keys is None:
+ # should be fixed when the returning default is changed
+ if return_type is None:
+ return_type = "dict"
+
+ assert isinstance(returned, types[return_type])
+ if return_type == "both":
+ assert isinstance(returned.ax, Axes)
+ assert isinstance(returned.lines, dict)
+ else:
+ # should be fixed when the returning default is changed
+ if return_type is None:
+ for r in _flatten_visible(returned):
+ assert isinstance(r, Axes)
+ return
+
+ assert isinstance(returned, Series)
+
+ assert sorted(returned.keys()) == sorted(expected_keys)
+ for key, value in returned.items():
+ assert isinstance(value, types[return_type])
+ # check returned dict has correct mapping
+ if return_type == "axes":
+ if check_ax_title:
+ assert value.get_title() == key
+ elif return_type == "both":
+ if check_ax_title:
+ assert value.ax.get_title() == key
+ assert isinstance(value.ax, Axes)
+ assert isinstance(value.lines, dict)
+ elif return_type == "dict":
+ line = value["medians"][0]
+ axes = line.axes
+ if check_ax_title:
+ assert axes.get_title() == key
+ else:
+ raise AssertionError
+
+
+def _check_grid_settings(obj, kinds, kws=None):
+ # Make sure plot defaults to rcParams['axes.grid'] setting, GH 9792
+
+ import matplotlib as mpl
+
+ def is_grid_on():
+ xticks = mpl.pyplot.gca().xaxis.get_major_ticks()
+ yticks = mpl.pyplot.gca().yaxis.get_major_ticks()
+ xoff = all(not g.gridline.get_visible() for g in xticks)
+ yoff = all(not g.gridline.get_visible() for g in yticks)
+
+ return not (xoff and yoff)
+
+ if kws is None:
+ kws = {}
+ spndx = 1
+ for kind in kinds:
+ mpl.pyplot.subplot(1, 4 * len(kinds), spndx)
+ spndx += 1
+ mpl.rc("axes", grid=False)
+ obj.plot(kind=kind, **kws)
+ assert not is_grid_on()
+ mpl.pyplot.clf()
+
+ mpl.pyplot.subplot(1, 4 * len(kinds), spndx)
+ spndx += 1
+ mpl.rc("axes", grid=True)
+ obj.plot(kind=kind, grid=False, **kws)
+ assert not is_grid_on()
+ mpl.pyplot.clf()
+
+ if kind not in ["pie", "hexbin", "scatter"]:
+ mpl.pyplot.subplot(1, 4 * len(kinds), spndx)
+ spndx += 1
+ mpl.rc("axes", grid=True)
+ obj.plot(kind=kind, **kws)
+ assert is_grid_on()
+ mpl.pyplot.clf()
+
+ mpl.pyplot.subplot(1, 4 * len(kinds), spndx)
+ spndx += 1
+ mpl.rc("axes", grid=False)
+ obj.plot(kind=kind, grid=True, **kws)
+ assert is_grid_on()
+ mpl.pyplot.clf()
+
+
+def _unpack_cycler(rcParams, field="color"):
+ """
+ Auxiliary function for correctly unpacking cycler after MPL >= 1.5
+ """
+ return [v[field] for v in rcParams["axes.prop_cycle"]]
+
+
+def get_x_axis(ax):
+ return ax._shared_axes["x"]
+
+
+def get_y_axis(ax):
+ return ax._shared_axes["y"]
+
+
+def assert_is_valid_plot_return_object(objs) -> None:
+ from matplotlib.artist import Artist
+ from matplotlib.axes import Axes
+
+ if isinstance(objs, (Series, np.ndarray)):
+ if isinstance(objs, Series):
+ objs = objs._values
+ for el in objs.reshape(-1):
+ msg = (
+ "one of 'objs' is not a matplotlib Axes instance, "
+ f"type encountered {type(el).__name__!r}"
+ )
+ assert isinstance(el, (Axes, dict)), msg
+ else:
+ msg = (
+ "objs is neither an ndarray of Artist instances nor a single "
+ "ArtistArtist instance, tuple, or dict, 'objs' is a "
+ f"{type(objs).__name__!r}"
+ )
+ assert isinstance(objs, (Artist, tuple, dict)), msg
+
+
+def _check_plot_works(f, default_axes=False, **kwargs):
+ """
+ Create plot and ensure that plot return object is valid.
+
+ Parameters
+ ----------
+ f : func
+ Plotting function.
+ default_axes : bool, optional
+ If False (default):
+ - If `ax` not in `kwargs`, then create subplot(211) and plot there
+ - Create new subplot(212) and plot there as well
+ - Mind special corner case for bootstrap_plot (see `_gen_two_subplots`)
+ If True:
+ - Simply run plotting function with kwargs provided
+ - All required axes instances will be created automatically
+ - It is recommended to use it when the plotting function
+ creates multiple axes itself. It helps avoid warnings like
+ 'UserWarning: To output multiple subplots,
+ the figure containing the passed axes is being cleared'
+ **kwargs
+ Keyword arguments passed to the plotting function.
+
+ Returns
+ -------
+ Plot object returned by the last plotting.
+ """
+ import matplotlib.pyplot as plt
+
+ if default_axes:
+ gen_plots = _gen_default_plot
+ else:
+ gen_plots = _gen_two_subplots
+
+ ret = None
+ fig = kwargs.get("figure", plt.gcf())
+ fig.clf()
+
+ for ret in gen_plots(f, fig, **kwargs):
+ assert_is_valid_plot_return_object(ret)
+
+ return ret
+
+
+def _gen_default_plot(f, fig, **kwargs):
+ """
+ Create plot in a default way.
+ """
+ yield f(**kwargs)
+
+
+def _gen_two_subplots(f, fig, **kwargs):
+ """
+ Create plot on two subplots forcefully created.
+ """
+ if "ax" not in kwargs:
+ fig.add_subplot(211)
+ yield f(**kwargs)
+
+ if f is pd.plotting.bootstrap_plot:
+ assert "ax" not in kwargs
+ else:
+ kwargs["ax"] = fig.add_subplot(212)
+ yield f(**kwargs)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb5a1f1f6382e3fe48fd0fa4050a673160807a9a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/conftest.py
@@ -0,0 +1,39 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ to_datetime,
+)
+
+
+@pytest.fixture(autouse=True)
+def autouse_mpl_cleanup(mpl_cleanup):
+ pass
+
+
+@pytest.fixture
+def hist_df():
+ n = 50
+ rng = np.random.default_rng(10)
+ gender = rng.choice(["Male", "Female"], size=n)
+ classroom = rng.choice(["A", "B", "C"], size=n)
+
+ hist_df = DataFrame(
+ {
+ "gender": gender,
+ "classroom": classroom,
+ "height": rng.normal(66, 4, size=n),
+ "weight": rng.normal(161, 32, size=n),
+ "category": rng.integers(4, size=n),
+ "datetime": to_datetime(
+ rng.integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=n,
+ dtype=np.int64,
+ )
+ ),
+ }
+ )
+ return hist_df
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame.py
new file mode 100644
index 0000000000000000000000000000000000000000..2696005b0091d6f192bdcedd1de486df76e168aa
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame.py
@@ -0,0 +1,2661 @@
+"""Test cases for DataFrame.plot"""
+
+from datetime import (
+ date,
+ datetime,
+)
+import itertools
+import re
+import string
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.api import is_list_like
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ PeriodIndex,
+ Series,
+ bdate_range,
+ date_range,
+ option_context,
+ plotting,
+)
+import pandas._testing as tm
+from pandas.tests.plotting.common import (
+ _check_ax_scales,
+ _check_axes_shape,
+ _check_box_return_type,
+ _check_colors,
+ _check_data,
+ _check_grid_settings,
+ _check_has_errorbars,
+ _check_legend_labels,
+ _check_plot_works,
+ _check_text_labels,
+ _check_ticks_props,
+ _check_visible,
+ get_y_axis,
+)
+from pandas.util.version import Version
+
+from pandas.io.formats.printing import pprint_thing
+
+mpl = pytest.importorskip("matplotlib")
+plt = pytest.importorskip("matplotlib.pyplot")
+
+pytestmark = [
+ pytest.mark.filterwarnings(
+ "ignore:divide by zero encountered in scalar divide:RuntimeWarning"
+ ),
+ pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in scalar multiply:RuntimeWarning"
+ ),
+]
+
+
+class TestDataFramePlots:
+ @pytest.mark.slow
+ def test_plot(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ _check_plot_works(df.plot, grid=False)
+
+ @pytest.mark.slow
+ def test_plot_subplots(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ # _check_plot_works adds an ax so use default_axes=True to avoid warning
+ axes = _check_plot_works(df.plot, default_axes=True, subplots=True)
+ _check_axes_shape(axes, axes_num=4, layout=(4, 1))
+
+ @pytest.mark.slow
+ def test_plot_subplots_negative_layout(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ axes = _check_plot_works(
+ df.plot,
+ default_axes=True,
+ subplots=True,
+ layout=(-1, 2),
+ )
+ _check_axes_shape(axes, axes_num=4, layout=(2, 2))
+
+ @pytest.mark.slow
+ def test_plot_subplots_use_index(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ axes = _check_plot_works(
+ df.plot,
+ default_axes=True,
+ subplots=True,
+ use_index=False,
+ )
+ _check_ticks_props(axes, xrot=0)
+ _check_axes_shape(axes, axes_num=4, layout=(4, 1))
+
+ @pytest.mark.xfail(reason="Api changed in 3.6.0")
+ @pytest.mark.slow
+ def test_plot_invalid_arg(self):
+ df = DataFrame({"x": [1, 2], "y": [3, 4]})
+ msg = "'Line2D' object has no property 'blarg'"
+ with pytest.raises(AttributeError, match=msg):
+ df.plot.line(blarg=True)
+
+ @pytest.mark.slow
+ def test_plot_tick_props(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+
+ ax = _check_plot_works(df.plot, use_index=True)
+ _check_ticks_props(ax, xrot=0)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"yticks": [1, 5, 10]},
+ {"xticks": [1, 5, 10]},
+ {"ylim": (-100, 100), "xlim": (-100, 100)},
+ {"default_axes": True, "subplots": True, "title": "blah"},
+ ],
+ )
+ def test_plot_other_args(self, kwargs):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+ _check_plot_works(df.plot, **kwargs)
+
+ @pytest.mark.slow
+ def test_plot_visible_ax(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+ # We have to redo it here because _check_plot_works does two plots,
+ # once without an ax kwarg and once with an ax kwarg and the new sharex
+ # behaviour does not remove the visibility of the latter axis (as ax is
+ # present). see: https://github.com/pandas-dev/pandas/issues/9737
+
+ axes = df.plot(subplots=True, title="blah")
+ _check_axes_shape(axes, axes_num=3, layout=(3, 1))
+ for ax in axes[:2]:
+ _check_visible(ax.xaxis) # xaxis must be visible for grid
+ _check_visible(ax.get_xticklabels(), visible=False)
+ _check_visible(ax.get_xticklabels(minor=True), visible=False)
+ _check_visible([ax.xaxis.get_label()], visible=False)
+ for ax in [axes[2]]:
+ _check_visible(ax.xaxis)
+ _check_visible(ax.get_xticklabels())
+ _check_visible([ax.xaxis.get_label()])
+ _check_ticks_props(ax, xrot=0)
+
+ @pytest.mark.slow
+ def test_plot_title(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+ _check_plot_works(df.plot, title="blah")
+
+ @pytest.mark.slow
+ def test_plot_multiindex(self):
+ tuples = zip(string.ascii_letters[:10], range(10), strict=True)
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=MultiIndex.from_tuples(tuples),
+ )
+ ax = _check_plot_works(df.plot, use_index=True)
+ _check_ticks_props(ax, xrot=0)
+
+ @pytest.mark.slow
+ def test_plot_multiindex_unicode(self):
+ # unicode
+ index = MultiIndex.from_tuples(
+ [
+ ("\u03b1", 0),
+ ("\u03b1", 1),
+ ("\u03b2", 2),
+ ("\u03b2", 3),
+ ("\u03b3", 4),
+ ("\u03b3", 5),
+ ("\u03b4", 6),
+ ("\u03b4", 7),
+ ],
+ names=["i0", "i1"],
+ )
+ columns = MultiIndex.from_tuples(
+ [("bar", "\u0394"), ("bar", "\u0395")], names=["c0", "c1"]
+ )
+ df = DataFrame(
+ np.random.default_rng(2).integers(0, 10, (8, 2)),
+ columns=columns,
+ index=index,
+ )
+ _check_plot_works(df.plot, title="\u03a3")
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("layout", [None, (-1, 1)])
+ def test_plot_single_column_bar(self, layout):
+ # GH 6951
+ # Test with single column
+ df = DataFrame({"x": np.random.default_rng(2).random(10)})
+ axes = _check_plot_works(df.plot.bar, subplots=True, layout=layout)
+ _check_axes_shape(axes, axes_num=1, layout=(1, 1))
+
+ @pytest.mark.slow
+ def test_plot_passed_ax(self):
+ # When ax is supplied and required number of axes is 1,
+ # passed ax should be used:
+ df = DataFrame({"x": np.random.default_rng(2).random(10)})
+ _, ax = mpl.pyplot.subplots()
+ axes = df.plot.bar(subplots=True, ax=ax)
+ assert len(axes) == 1
+ result = ax.axes
+ assert result is axes[0]
+
+ @pytest.mark.parametrize(
+ "cols, x, y",
+ [
+ [list("ABCDE"), "A", "B"],
+ [["A", "B"], "A", "B"],
+ [["C", "A"], "C", "A"],
+ [["A", "C"], "A", "C"],
+ [["B", "C"], "B", "C"],
+ [["A", "D"], "A", "D"],
+ [["A", "E"], "A", "E"],
+ ],
+ )
+ def test_nullable_int_plot(self, cols, x, y):
+ # GH 32073
+ dates = ["2008", "2009", None, "2011", "2012"]
+ df = DataFrame(
+ {
+ "A": [1, 2, 3, 4, 5],
+ "B": [1, 2, 3, 4, 5],
+ "C": np.array([7, 5, np.nan, 3, 2], dtype=object),
+ "D": pd.to_datetime(dates, format="%Y").view("i8"),
+ "E": pd.to_datetime(dates, format="%Y", utc=True).view("i8"),
+ }
+ )
+
+ _check_plot_works(df[cols].plot, x=x, y=y)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("plot", ["line", "bar", "hist", "pie"])
+ def test_integer_array_plot_series(self, plot):
+ # GH 25587
+ arr = pd.array([1, 2, 3, 4], dtype="UInt32")
+
+ s = Series(arr)
+ _check_plot_works(getattr(s.plot, plot))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "plot, kwargs",
+ [
+ ["line", {}],
+ ["bar", {}],
+ ["hist", {}],
+ ["pie", {"y": "y"}],
+ ["scatter", {"x": "x", "y": "y"}],
+ ["hexbin", {"x": "x", "y": "y"}],
+ ],
+ )
+ def test_integer_array_plot_df(self, plot, kwargs):
+ # GH 25587
+ arr = pd.array([1, 2, 3, 4], dtype="UInt32")
+ df = DataFrame({"x": arr, "y": arr})
+ _check_plot_works(getattr(df.plot, plot), **kwargs)
+
+ def test_nonnumeric_exclude(self):
+ df = DataFrame({"A": ["x", "y", "z"], "B": [1, 2, 3]})
+ ax = df.plot()
+ assert len(ax.get_lines()) == 1 # B was plotted
+
+ def test_implicit_label(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 3)), columns=["a", "b", "c"]
+ )
+ ax = df.plot(x="a", y="b")
+ _check_text_labels(ax.xaxis.get_label(), "a")
+
+ def test_donot_overwrite_index_name(self):
+ # GH 8494
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((2, 2)), columns=["a", "b"]
+ )
+ df.index.name = "NAME"
+ df.plot(y="b", label="LABEL")
+ assert df.index.name == "NAME"
+
+ def test_plot_xy(self):
+ # columns.inferred_type == 'string'
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=5, freq="B"),
+ )
+ _check_data(df.plot(x=0, y=1), df.set_index("A")["B"].plot())
+ _check_data(df.plot(x=0), df.set_index("A").plot())
+ _check_data(df.plot(y=0), df.B.plot())
+ _check_data(df.plot(x="A", y="B"), df.set_index("A").B.plot())
+ _check_data(df.plot(x="A"), df.set_index("A").plot())
+ _check_data(df.plot(y="B"), df.B.plot())
+
+ def test_plot_xy_int_cols(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=5, freq="B"),
+ )
+ # columns.inferred_type == 'integer'
+ df.columns = np.arange(1, len(df.columns) + 1)
+ _check_data(df.plot(x=1, y=2), df.set_index(1)[2].plot())
+ _check_data(df.plot(x=1), df.set_index(1).plot())
+ _check_data(df.plot(y=1), df[1].plot())
+
+ def test_plot_xy_figsize_and_title(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=5, freq="B"),
+ )
+ # figsize and title
+ ax = df.plot(x=1, y=2, title="Test", figsize=(16, 8))
+ _check_text_labels(ax.title, "Test")
+ _check_axes_shape(ax, axes_num=1, layout=(1, 1), figsize=(16.0, 8.0))
+
+ # columns.inferred_type == 'mixed'
+ # TODO add MultiIndex test
+
+ @pytest.mark.parametrize(
+ "input_log, expected_log", [(True, "log"), ("sym", "symlog")]
+ )
+ def test_logscales(self, input_log, expected_log):
+ df = DataFrame({"a": np.arange(100)}, index=np.arange(100))
+
+ ax = df.plot(logy=input_log)
+ _check_ax_scales(ax, yaxis=expected_log)
+ assert ax.get_yscale() == expected_log
+
+ ax = df.plot(logx=input_log)
+ _check_ax_scales(ax, xaxis=expected_log)
+ assert ax.get_xscale() == expected_log
+
+ ax = df.plot(loglog=input_log)
+ _check_ax_scales(ax, xaxis=expected_log, yaxis=expected_log)
+ assert ax.get_xscale() == expected_log
+ assert ax.get_yscale() == expected_log
+
+ @pytest.mark.parametrize("input_param", ["logx", "logy", "loglog"])
+ def test_invalid_logscale(self, input_param):
+ # GH: 24867
+ df = DataFrame({"a": np.arange(100)}, index=np.arange(100))
+
+ msg = f"keyword '{input_param}' should be bool, None, or 'sym', not 'sm'"
+ with pytest.raises(ValueError, match=msg):
+ df.plot(**{input_param: "sm"})
+
+ msg = f"PiePlot ignores the '{input_param}' keyword"
+ with tm.assert_produces_warning(UserWarning, match=msg):
+ df.plot.pie(subplots=True, **{input_param: True})
+
+ def test_xcompat(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ ax = df.plot(x_compat=True)
+ lines = ax.get_lines()
+ assert not isinstance(lines[0].get_xdata(), PeriodIndex)
+ _check_ticks_props(ax, xrot=30)
+
+ def test_xcompat_plot_params(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ plotting.plot_params["xaxis.compat"] = True
+ ax = df.plot()
+ lines = ax.get_lines()
+ assert not isinstance(lines[0].get_xdata(), PeriodIndex)
+ _check_ticks_props(ax, xrot=30)
+
+ def test_xcompat_plot_params_x_compat(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ plotting.plot_params["x_compat"] = False
+
+ ax = df.plot()
+ lines = ax.get_lines()
+ assert not isinstance(lines[0].get_xdata(), PeriodIndex)
+ msg = r"PeriodDtype\[B\] is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ assert isinstance(PeriodIndex(lines[0].get_xdata()), PeriodIndex)
+
+ def test_xcompat_plot_params_context_manager(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ # useful if you're plotting a bunch together
+ with plotting.plot_params.use("x_compat", True):
+ ax = df.plot()
+ lines = ax.get_lines()
+ assert not isinstance(lines[0].get_xdata(), PeriodIndex)
+ _check_ticks_props(ax, xrot=30)
+
+ def test_xcompat_plot_period(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ ax = df.plot()
+ lines = ax.get_lines()
+ assert not isinstance(lines[0].get_xdata(), PeriodIndex)
+ msg = r"PeriodDtype\[B\] is deprecated "
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ assert isinstance(PeriodIndex(lines[0].get_xdata()), PeriodIndex)
+ _check_ticks_props(ax, xrot=0)
+
+ def test_period_compat(self):
+ # GH 9012
+ # period-array conversions
+ df = DataFrame(
+ np.random.default_rng(2).random((21, 2)),
+ index=bdate_range(datetime(2000, 1, 1), datetime(2000, 1, 31)),
+ columns=["a", "b"],
+ )
+
+ df.plot()
+ mpl.pyplot.axhline(y=0)
+
+ @pytest.mark.parametrize("index_dtype", [np.int64, np.float64])
+ def test_unsorted_index(self, index_dtype):
+ df = DataFrame(
+ {"y": np.arange(100)},
+ index=Index(np.arange(99, -1, -1), dtype=index_dtype),
+ dtype=np.int64,
+ )
+ ax = df.plot()
+ lines = ax.get_lines()[0]
+ rs = lines.get_xydata()
+ rs = Series(rs[:, 1], rs[:, 0], dtype=np.int64, name="y")
+ tm.assert_series_equal(rs, df.y, check_index_type=False)
+
+ @pytest.mark.parametrize(
+ "df",
+ [
+ DataFrame({"y": [0.0, 1.0, 2.0, 3.0]}, index=[1.0, 0.0, 3.0, 2.0]),
+ DataFrame(
+ {"y": [0.0, 1.0, np.nan, 3.0, 4.0, 5.0, 6.0]},
+ index=[1.0, 0.0, 3.0, 2.0, np.nan, 3.0, 2.0],
+ ),
+ ],
+ )
+ def test_unsorted_index_lims(self, df):
+ ax = df.plot()
+ xmin, xmax = ax.get_xlim()
+ lines = ax.get_lines()
+ assert xmin <= np.nanmin(lines[0].get_data()[0])
+ assert xmax >= np.nanmax(lines[0].get_data()[0])
+
+ def test_unsorted_index_lims_x_y(self):
+ df = DataFrame({"y": [0.0, 1.0, 2.0, 3.0], "z": [91.0, 90.0, 93.0, 92.0]})
+ ax = df.plot(x="z", y="y")
+ xmin, xmax = ax.get_xlim()
+ lines = ax.get_lines()
+ assert xmin <= np.nanmin(lines[0].get_data()[0])
+ assert xmax >= np.nanmax(lines[0].get_data()[0])
+
+ def test_negative_log(self):
+ df = -DataFrame(
+ np.random.default_rng(2).random((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["x", "y", "z", "four"],
+ )
+ msg = "Log-y scales are not supported in area plot"
+ with pytest.raises(ValueError, match=msg):
+ df.plot.area(logy=True)
+ with pytest.raises(ValueError, match=msg):
+ df.plot.area(loglog=True)
+
+ def _compare_stacked_y_cood(self, normal_lines, stacked_lines):
+ base = np.zeros(len(normal_lines[0].get_data()[1]))
+ for nl, sl in zip(normal_lines, stacked_lines, strict=True):
+ base += nl.get_data()[1] # get y coordinates
+ sy = sl.get_data()[1]
+ tm.assert_numpy_array_equal(base, sy)
+
+ @pytest.mark.parametrize("kind", ["line", "area"])
+ @pytest.mark.parametrize("mult", [1, -1])
+ def test_line_area_stacked(self, kind, mult):
+ df = mult * DataFrame(
+ np.random.default_rng(2).random((6, 4)), columns=["w", "x", "y", "z"]
+ )
+
+ ax1 = _check_plot_works(df.plot, kind=kind, stacked=False)
+ ax2 = _check_plot_works(df.plot, kind=kind, stacked=True)
+ self._compare_stacked_y_cood(ax1.lines, ax2.lines)
+
+ @pytest.mark.parametrize("kind", ["line", "area"])
+ def test_line_area_stacked_sep_df(self, kind):
+ # each column has either positive or negative value
+ sep_df = DataFrame(
+ {
+ "w": np.random.default_rng(2).random(6),
+ "x": np.random.default_rng(2).random(6),
+ "y": -np.random.default_rng(2).random(6),
+ "z": -np.random.default_rng(2).random(6),
+ }
+ )
+ ax1 = _check_plot_works(sep_df.plot, kind=kind, stacked=False)
+ ax2 = _check_plot_works(sep_df.plot, kind=kind, stacked=True)
+ self._compare_stacked_y_cood(ax1.lines[:2], ax2.lines[:2])
+ self._compare_stacked_y_cood(ax1.lines[2:], ax2.lines[2:])
+
+ def test_line_area_stacked_mixed(self):
+ mixed_df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["w", "x", "y", "z"],
+ )
+ _check_plot_works(mixed_df.plot, stacked=False)
+
+ msg = (
+ "When stacked is True, each column must be either all positive or "
+ "all negative. Column 'w' contains both positive and negative "
+ "values"
+ )
+ with pytest.raises(ValueError, match=msg):
+ mixed_df.plot(stacked=True)
+
+ @pytest.mark.parametrize("kind", ["line", "area"])
+ def test_line_area_stacked_positive_idx(self, kind):
+ df = DataFrame(
+ np.random.default_rng(2).random((6, 4)), columns=["w", "x", "y", "z"]
+ )
+ # Use an index with strictly positive values, preventing
+ # matplotlib from warning about ignoring xlim
+ df2 = df.set_index(df.index + 1)
+ _check_plot_works(df2.plot, kind=kind, logx=True, stacked=True)
+
+ @pytest.mark.parametrize(
+ "idx", [range(4), date_range("2023-01-1", freq="D", periods=4)]
+ )
+ def test_line_area_nan_df(self, idx):
+ values1 = [1, 2, np.nan, 3]
+ values2 = [3, np.nan, 2, 1]
+ df = DataFrame({"a": values1, "b": values2}, index=idx)
+
+ ax = _check_plot_works(df.plot)
+ masked1 = ax.lines[0].get_ydata()
+ masked2 = ax.lines[1].get_ydata()
+ # remove nan for comparison purpose
+
+ exp = np.array([1, 2, 3], dtype=np.float64)
+ tm.assert_numpy_array_equal(np.delete(masked1.data, 2), exp)
+
+ exp = np.array([3, 2, 1], dtype=np.float64)
+ tm.assert_numpy_array_equal(np.delete(masked2.data, 1), exp)
+ tm.assert_numpy_array_equal(masked1.mask, np.array([False, False, True, False]))
+ tm.assert_numpy_array_equal(masked2.mask, np.array([False, True, False, False]))
+
+ @pytest.mark.parametrize(
+ "idx", [range(4), date_range("2023-01-1", freq="D", periods=4)]
+ )
+ def test_line_area_nan_df_stacked(self, idx):
+ values1 = [1, 2, np.nan, 3]
+ values2 = [3, np.nan, 2, 1]
+ df = DataFrame({"a": values1, "b": values2}, index=idx)
+
+ expected1 = np.array([1, 2, 0, 3], dtype=np.float64)
+ expected2 = np.array([3, 0, 2, 1], dtype=np.float64)
+
+ ax = _check_plot_works(df.plot, stacked=True)
+ tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected1)
+ tm.assert_numpy_array_equal(ax.lines[1].get_ydata(), expected1 + expected2)
+
+ @pytest.mark.parametrize(
+ "idx", [range(4), date_range("2023-01-1", freq="D", periods=4)]
+ )
+ @pytest.mark.parametrize("kwargs", [{}, {"stacked": False}])
+ def test_line_area_nan_df_stacked_area(self, idx, kwargs):
+ values1 = [1, 2, np.nan, 3]
+ values2 = [3, np.nan, 2, 1]
+ df = DataFrame({"a": values1, "b": values2}, index=idx)
+
+ expected1 = np.array([1, 2, 0, 3], dtype=np.float64)
+ expected2 = np.array([3, 0, 2, 1], dtype=np.float64)
+
+ ax = _check_plot_works(df.plot.area, **kwargs)
+ tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected1)
+ if kwargs:
+ tm.assert_numpy_array_equal(ax.lines[1].get_ydata(), expected2)
+ else:
+ tm.assert_numpy_array_equal(ax.lines[1].get_ydata(), expected1 + expected2)
+
+ ax = _check_plot_works(df.plot.area, stacked=False)
+ tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected1)
+ tm.assert_numpy_array_equal(ax.lines[1].get_ydata(), expected2)
+
+ @pytest.mark.parametrize("kwargs", [{}, {"secondary_y": True}])
+ def test_line_lim(self, kwargs):
+ df = DataFrame(np.random.default_rng(2).random((6, 3)), columns=["x", "y", "z"])
+ ax = df.plot(**kwargs)
+ xmin, xmax = ax.get_xlim()
+ lines = ax.get_lines()
+ assert xmin <= lines[0].get_data()[0][0]
+ assert xmax >= lines[0].get_data()[0][-1]
+
+ def test_line_lim_subplots(self):
+ df = DataFrame(np.random.default_rng(2).random((6, 3)), columns=["x", "y", "z"])
+ axes = df.plot(secondary_y=True, subplots=True)
+ _check_axes_shape(axes, axes_num=3, layout=(3, 1))
+ for ax in axes:
+ assert hasattr(ax, "left_ax")
+ assert not hasattr(ax, "right_ax")
+ xmin, xmax = ax.get_xlim()
+ lines = ax.get_lines()
+ assert xmin <= lines[0].get_data()[0][0]
+ assert xmax >= lines[0].get_data()[0][-1]
+
+ @pytest.mark.xfail(
+ strict=False,
+ reason="2020-12-01 this has been failing periodically on the "
+ "ymin==0 assertion for a week or so.",
+ )
+ @pytest.mark.parametrize("stacked", [True, False])
+ def test_area_lim(self, stacked):
+ df = DataFrame(
+ np.random.default_rng(2).random((6, 4)), columns=["x", "y", "z", "four"]
+ )
+
+ neg_df = -df
+
+ ax = _check_plot_works(df.plot.area, stacked=stacked)
+ xmin, xmax = ax.get_xlim()
+ ymin, ymax = ax.get_ylim()
+ lines = ax.get_lines()
+ assert xmin <= lines[0].get_data()[0][0]
+ assert xmax >= lines[0].get_data()[0][-1]
+ assert ymin == 0
+
+ ax = _check_plot_works(neg_df.plot.area, stacked=stacked)
+ ymin, ymax = ax.get_ylim()
+ assert ymax == 0
+
+ def test_area_sharey_dont_overwrite(self):
+ # GH37942
+ df = DataFrame(np.random.default_rng(2).random((4, 2)), columns=["x", "y"])
+ fig, (ax1, ax2) = mpl.pyplot.subplots(1, 2, sharey=True)
+
+ df.plot(ax=ax1, kind="area")
+ df.plot(ax=ax2, kind="area")
+
+ assert get_y_axis(ax1).joined(ax1, ax2)
+ assert get_y_axis(ax2).joined(ax1, ax2)
+
+ @pytest.mark.parametrize("stacked", [True, False])
+ def test_bar_linewidth(self, stacked):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+
+ ax = df.plot.bar(stacked=stacked, linewidth=2)
+ for r in ax.patches:
+ assert r.get_linewidth() == 2
+
+ def test_bar_linewidth_subplots(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # subplots
+ axes = df.plot.bar(linewidth=2, subplots=True)
+ _check_axes_shape(axes, axes_num=5, layout=(5, 1))
+ for ax in axes:
+ for r in ax.patches:
+ assert r.get_linewidth() == 2
+
+ @pytest.mark.parametrize(
+ "meth, dim", [("bar", "get_width"), ("barh", "get_height")]
+ )
+ @pytest.mark.parametrize("stacked", [True, False])
+ def test_bar_barwidth(self, meth, dim, stacked):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+
+ width = 0.9
+
+ ax = getattr(df.plot, meth)(stacked=stacked, width=width)
+ for r in ax.patches:
+ if not stacked:
+ assert getattr(r, dim)() == width / len(df.columns)
+ else:
+ assert getattr(r, dim)() == width
+
+ @pytest.mark.parametrize(
+ "meth, dim", [("bar", "get_width"), ("barh", "get_height")]
+ )
+ def test_barh_barwidth_subplots(self, meth, dim):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+
+ width = 0.9
+
+ axes = getattr(df.plot, meth)(width=width, subplots=True)
+ for ax in axes:
+ for r in ax.patches:
+ assert getattr(r, dim)() == width
+
+ def test_bar_bottom_left_bottom(self):
+ df = DataFrame(np.random.default_rng(2).random((5, 5)))
+ ax = df.plot.bar(stacked=False, bottom=1)
+ result = [p.get_y() for p in ax.patches]
+ assert result == [1] * 25
+
+ ax = df.plot.bar(stacked=True, bottom=[-1, -2, -3, -4, -5])
+ result = [p.get_y() for p in ax.patches[:5]]
+ assert result == [-1, -2, -3, -4, -5]
+
+ def test_bar_bottom_left_left(self):
+ df = DataFrame(np.random.default_rng(2).random((5, 5)))
+ ax = df.plot.barh(stacked=False, left=np.array([1, 1, 1, 1, 1]))
+ result = [p.get_x() for p in ax.patches]
+ assert result == [1] * 25
+
+ ax = df.plot.barh(stacked=True, left=[1, 2, 3, 4, 5])
+ result = [p.get_x() for p in ax.patches[:5]]
+ assert result == [1, 2, 3, 4, 5]
+
+ def test_bar_bottom_left_subplots(self):
+ df = DataFrame(np.random.default_rng(2).random((5, 5)))
+ axes = df.plot.bar(subplots=True, bottom=-1)
+ for ax in axes:
+ result = [p.get_y() for p in ax.patches]
+ assert result == [-1] * 5
+
+ axes = df.plot.barh(subplots=True, left=np.array([1, 1, 1, 1, 1]))
+ for ax in axes:
+ result = [p.get_x() for p in ax.patches]
+ assert result == [1] * 5
+
+ def test_bar_nan(self):
+ df = DataFrame({"A": [10, np.nan, 20], "B": [5, 10, 20], "C": [1, 2, 3]})
+ ax = df.plot.bar()
+ expected = [10, 0, 20, 5, 10, 20, 1, 2, 3]
+ result = [p.get_height() for p in ax.patches]
+ assert result == expected
+
+ def test_bar_nan_stacked(self):
+ df = DataFrame({"A": [10, np.nan, 20], "B": [5, 10, 20], "C": [1, 2, 3]})
+ ax = df.plot.bar(stacked=True)
+ expected = [10, 0, 20, 5, 10, 20, 1, 2, 3]
+ result = [p.get_height() for p in ax.patches]
+ assert result == expected
+
+ result = [p.get_y() for p in ax.patches]
+ expected = [0.0, 0.0, 0.0, 10.0, 0.0, 20.0, 15.0, 10.0, 40.0]
+ assert result == expected
+
+ def test_bar_stacked_label_position_with_zero_height(self):
+ # GH 59429
+ df = DataFrame({"A": [3, 0, 1], "B": [0, 2, 4], "C": [5, 0, 2]})
+ ax = df.plot.bar(stacked=True)
+ ax.bar_label(ax.containers[-1])
+ expected = [8.0, 2.0, 7.0]
+ result = [text.xy[1] for text in ax.texts]
+ tm.assert_almost_equal(result, expected)
+ plt.close("all")
+
+ @pytest.mark.parametrize("idx", [Index, pd.CategoricalIndex])
+ def test_bar_categorical(self, idx):
+ # GH 13019
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 5)),
+ index=idx(list("ABCDEF")),
+ columns=idx(list("abcde")),
+ )
+
+ ax = df.plot.bar()
+ ticks = ax.xaxis.get_ticklocs()
+ tm.assert_numpy_array_equal(ticks, np.array([0, 1, 2, 3, 4, 5]))
+ assert ax.get_xlim() == (-0.5, 5.5)
+ # check left-edge of bars
+ assert ax.patches[0].get_x() == -0.25
+ assert ax.patches[-1].get_x() == 5.15
+
+ ax = df.plot.bar(stacked=True)
+ tm.assert_numpy_array_equal(ticks, np.array([0, 1, 2, 3, 4, 5]))
+ assert ax.get_xlim() == (-0.5, 5.5)
+ assert ax.patches[0].get_x() == -0.25
+ assert ax.patches[-1].get_x() == 4.75
+
+ @pytest.mark.parametrize("x, y", [("x", "y"), (1, 2)])
+ def test_plot_scatter(self, x, y):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["x", "y", "z", "four"],
+ )
+
+ _check_plot_works(df.plot.scatter, x=x, y=y)
+
+ def test_plot_scatter_error(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["x", "y", "z", "four"],
+ )
+ msg = re.escape("scatter() missing 1 required positional argument: 'y'")
+ with pytest.raises(TypeError, match=msg):
+ df.plot.scatter(x="x")
+ msg = re.escape("scatter() missing 1 required positional argument: 'x'")
+ with pytest.raises(TypeError, match=msg):
+ df.plot.scatter(y="y")
+
+ def test_plot_scatter_shape(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["x", "y", "z", "four"],
+ )
+ # GH 6951
+ axes = df.plot(x="x", y="y", kind="scatter", subplots=True)
+ _check_axes_shape(axes, axes_num=1, layout=(1, 1))
+
+ def test_scatter_on_datetime_time_data(self):
+ # datetime.time type is now supported in scatter, since a converter
+ # is implemented in ScatterPlot
+ df = DataFrame(np.random.default_rng(2).standard_normal(10), columns=["a"])
+ df["dtime"] = date_range(start="2014-01-01", freq="h", periods=10).time
+ df.plot(kind="scatter", x="dtime", y="a")
+
+ def test_scatter_line_xticks(self):
+ # GH#61005
+ df = DataFrame(
+ [(datetime(year=2025, month=1, day=1, hour=n), n) for n in range(3)],
+ columns=["datetime", "y"],
+ )
+ fig, ax = plt.subplots(2, sharex=True)
+ df.plot.scatter(x="datetime", y="y", ax=ax[0])
+ scatter_xticks = ax[0].get_xticks()
+ df.plot(x="datetime", y="y", ax=ax[1])
+ line_xticks = ax[1].get_xticks()
+ assert scatter_xticks[0] == line_xticks[0]
+ assert scatter_xticks[-1] == line_xticks[-1]
+
+ @pytest.mark.parametrize("x, y", [("dates", "vals"), (0, 1)])
+ def test_scatterplot_datetime_data(self, x, y):
+ # GH 30391
+ dates = date_range(start=date(2019, 1, 1), periods=12, freq="W")
+ vals = np.random.default_rng(2).normal(0, 1, len(dates))
+ df = DataFrame({"dates": dates, "vals": vals})
+
+ _check_plot_works(df.plot.scatter, x=x, y=y)
+
+ @pytest.mark.parametrize(
+ "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))]
+ )
+ @pytest.mark.parametrize("x, y", [("a", "b"), (0, 1)])
+ @pytest.mark.parametrize("b_col", [[2, 3, 4], ["a", "b", "c"]])
+ def test_scatterplot_object_data(self, b_col, x, y, infer_string):
+ # GH 18755
+ with option_context("future.infer_string", infer_string):
+ df = DataFrame({"a": ["A", "B", "C"], "b": b_col})
+
+ _check_plot_works(df.plot.scatter, x=x, y=y)
+
+ @pytest.mark.parametrize("ordered", [True, False])
+ @pytest.mark.parametrize(
+ "categories",
+ (["setosa", "versicolor", "virginica"], ["versicolor", "virginica", "setosa"]),
+ )
+ def test_scatterplot_color_by_categorical(self, ordered, categories):
+ df = DataFrame(
+ [[5.1, 3.5], [4.9, 3.0], [7.0, 3.2], [6.4, 3.2], [5.9, 3.0]],
+ columns=["length", "width"],
+ )
+ df["species"] = pd.Categorical(
+ ["setosa", "setosa", "virginica", "virginica", "versicolor"],
+ ordered=ordered,
+ categories=categories,
+ )
+ ax = df.plot.scatter(x=0, y=1, c="species")
+ (colorbar_collection,) = ax.collections
+ colorbar = colorbar_collection.colorbar
+
+ expected_ticks = np.array([0.5, 1.5, 2.5])
+ result_ticks = colorbar.get_ticks()
+ tm.assert_numpy_array_equal(result_ticks, expected_ticks)
+
+ expected_boundaries = np.array([0.0, 1.0, 2.0, 3.0])
+ result_boundaries = colorbar._boundaries
+ tm.assert_numpy_array_equal(result_boundaries, expected_boundaries)
+
+ expected_yticklabels = categories
+ result_yticklabels = [i.get_text() for i in colorbar.ax.get_ymajorticklabels()]
+ assert all(
+ i == j
+ for i, j in zip(result_yticklabels, expected_yticklabels, strict=True)
+ )
+
+ @pytest.mark.parametrize("x, y", [("x", "y"), ("y", "x"), ("y", "y")])
+ def test_plot_scatter_with_categorical_data(self, x, y):
+ # after fixing GH 18755, should be able to plot categorical data
+ df = DataFrame({"x": [1, 2, 3, 4], "y": pd.Categorical(["a", "b", "a", "c"])})
+
+ _check_plot_works(df.plot.scatter, x=x, y=y)
+
+ @pytest.mark.parametrize("x, y, c", [("x", "y", "z"), (0, 1, 2)])
+ def test_plot_scatter_with_c(self, x, y, c):
+ df = DataFrame(
+ np.random.default_rng(2).integers(low=0, high=100, size=(6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["x", "y", "z", "four"],
+ )
+
+ ax = df.plot.scatter(x=x, y=y, c=c)
+ # default to Greys
+ assert ax.collections[0].cmap.name == "Greys"
+
+ assert ax.collections[0].colorbar.ax.get_ylabel() == "z"
+
+ def test_plot_scatter_with_c_props(self):
+ df = DataFrame(
+ np.random.default_rng(2).integers(low=0, high=100, size=(6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["x", "y", "z", "four"],
+ )
+ cm = "cubehelix"
+ ax = df.plot.scatter(x="x", y="y", c="z", colormap=cm)
+ assert ax.collections[0].cmap.name == cm
+
+ # verify turning off colorbar works
+ ax = df.plot.scatter(x="x", y="y", c="z", colorbar=False)
+ assert ax.collections[0].colorbar is None
+
+ # verify that we can still plot a solid color
+ ax = df.plot.scatter(x=0, y=1, c="red")
+ assert ax.collections[0].colorbar is None
+ _check_colors(ax.collections, facecolors=["r"])
+
+ def test_plot_scatter_with_c_array(self):
+ # Ensure that we can pass an np.array straight through to matplotlib,
+ # this functionality was accidentally removed previously.
+ # See https://github.com/pandas-dev/pandas/issues/8852 for bug report
+ #
+ # Exercise colormap path and non-colormap path as they are independent
+ #
+ df = DataFrame({"A": [1, 2], "B": [3, 4]})
+ red_rgba = [1.0, 0.0, 0.0, 1.0]
+ green_rgba = [0.0, 1.0, 0.0, 1.0]
+ rgba_array = np.array([red_rgba, green_rgba])
+ ax = df.plot.scatter(x="A", y="B", c=rgba_array)
+ # expect the face colors of the points in the non-colormap path to be
+ # identical to the values we supplied, normally we'd be on shaky ground
+ # comparing floats for equality but here we expect them to be
+ # identical.
+ tm.assert_numpy_array_equal(ax.collections[0].get_facecolor(), rgba_array)
+ # we don't test the colors of the faces in this next plot because they
+ # are dependent on the spring colormap, which may change its colors
+ # later.
+ float_array = np.array([0.0, 1.0])
+ df.plot.scatter(x="A", y="B", c=float_array, cmap="spring")
+
+ def test_plot_scatter_with_s(self):
+ # this refers to GH 32904
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)) * 100, columns=["a", "b", "c"]
+ )
+
+ ax = df.plot.scatter(x="a", y="b", s="c")
+ tm.assert_numpy_array_equal(df["c"].values, right=ax.collections[0].get_sizes())
+
+ def test_plot_scatter_with_norm(self):
+ # added while fixing GH 45809
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)) * 100, columns=["a", "b", "c"]
+ )
+ norm = mpl.colors.LogNorm()
+ ax = df.plot.scatter(x="a", y="b", c="c", norm=norm)
+ assert ax.collections[0].norm is norm
+
+ def test_plot_scatter_without_norm(self):
+ # added while fixing GH 45809
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)) * 100, columns=["a", "b", "c"]
+ )
+ ax = df.plot.scatter(x="a", y="b", c="c")
+ plot_norm = ax.collections[0].norm
+ color_min_max = (df.c.min(), df.c.max())
+ default_norm = mpl.colors.Normalize(*color_min_max)
+ for value in df.c:
+ assert plot_norm(value) == default_norm(value)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "kwargs",
+ [
+ {},
+ {"legend": False},
+ {"default_axes": True, "subplots": True},
+ {"stacked": True},
+ ],
+ )
+ def test_plot_bar(self, kwargs):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["one", "two", "three", "four"],
+ )
+
+ _check_plot_works(df.plot.bar, **kwargs)
+
+ @pytest.mark.slow
+ def test_plot_bar_int_col(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 15)),
+ index=list(string.ascii_letters[:10]),
+ columns=range(15),
+ )
+ _check_plot_works(df.plot.bar)
+
+ @pytest.mark.slow
+ def test_plot_bar_ticks(self):
+ df = DataFrame({"a": [0, 1], "b": [1, 0]})
+ ax = _check_plot_works(df.plot.bar)
+ _check_ticks_props(ax, xrot=90)
+
+ ax = df.plot.bar(rot=35, fontsize=10)
+ _check_ticks_props(ax, xrot=35, xlabelsize=10, ylabelsize=10)
+
+ @pytest.mark.slow
+ def test_plot_barh_ticks(self):
+ df = DataFrame({"a": [0, 1], "b": [1, 0]})
+ ax = _check_plot_works(df.plot.barh)
+ _check_ticks_props(ax, yrot=0)
+
+ ax = df.plot.barh(rot=55, fontsize=11)
+ _check_ticks_props(ax, yrot=55, ylabelsize=11, xlabelsize=11)
+
+ def test_boxplot(self, hist_df):
+ df = hist_df
+ numeric_cols = df._get_numeric_data().columns
+ labels = [pprint_thing(c) for c in numeric_cols]
+
+ ax = _check_plot_works(df.plot.box)
+ _check_text_labels(ax.get_xticklabels(), labels)
+ tm.assert_numpy_array_equal(
+ ax.xaxis.get_ticklocs(), np.arange(1, len(numeric_cols) + 1)
+ )
+ assert len(ax.lines) == 7 * len(numeric_cols)
+
+ def test_boxplot_series(self, hist_df):
+ df = hist_df
+ series = df["height"]
+ axes = series.plot.box(rot=40)
+ _check_ticks_props(axes, xrot=40, yrot=0)
+
+ _check_plot_works(series.plot.box)
+
+ def test_boxplot_series_positions(self, hist_df):
+ df = hist_df
+ positions = np.array([1, 6, 7])
+ ax = df.plot.box(positions=positions)
+ numeric_cols = df._get_numeric_data().columns
+ labels = [pprint_thing(c) for c in numeric_cols]
+ _check_text_labels(ax.get_xticklabels(), labels)
+ tm.assert_numpy_array_equal(ax.xaxis.get_ticklocs(), positions)
+ assert len(ax.lines) == 7 * len(numeric_cols)
+
+ @pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
+ @pytest.mark.xfail(
+ Version(mpl.__version__) >= Version("3.10"),
+ reason="Fails starting with matplotlib 3.10",
+ )
+ def test_boxplot_vertical(self, hist_df):
+ df = hist_df
+ numeric_cols = df._get_numeric_data().columns
+ labels = [pprint_thing(c) for c in numeric_cols]
+
+ # if horizontal, yticklabels are rotated
+ kwargs = (
+ {"vert": False}
+ if Version(mpl.__version__) < Version("3.10")
+ else {"orientation": "horizontal"}
+ )
+ ax = df.plot.box(rot=50, fontsize=8, **kwargs)
+ _check_ticks_props(ax, xrot=0, yrot=50, ylabelsize=8)
+ _check_text_labels(ax.get_yticklabels(), labels)
+ assert len(ax.lines) == 7 * len(numeric_cols)
+
+ @pytest.mark.filterwarnings("ignore::UserWarning")
+ @pytest.mark.xfail(
+ Version(mpl.__version__) >= Version("3.10"),
+ reason="Fails starting with matplotlib version 3.10",
+ )
+ def test_boxplot_vertical_subplots(self, hist_df):
+ df = hist_df
+ numeric_cols = df._get_numeric_data().columns
+ labels = [pprint_thing(c) for c in numeric_cols]
+ kwargs = (
+ {"vert": False}
+ if Version(mpl.__version__) < Version("3.10")
+ else {"orientation": "horizontal"}
+ )
+ axes = _check_plot_works(
+ df.plot.box, default_axes=True, subplots=True, logx=True, **kwargs
+ )
+ _check_axes_shape(axes, axes_num=3, layout=(1, 3))
+ _check_ax_scales(axes, xaxis="log")
+ for ax, label in zip(axes, labels, strict=True):
+ _check_text_labels(ax.get_yticklabels(), [label])
+ assert len(ax.lines) == 7
+
+ @pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
+ @pytest.mark.xfail(
+ Version(mpl.__version__) >= Version("3.10"),
+ reason="Fails starting with matplotlib 3.10",
+ )
+ def test_boxplot_vertical_positions(self, hist_df):
+ df = hist_df
+ numeric_cols = df._get_numeric_data().columns
+ labels = [pprint_thing(c) for c in numeric_cols]
+ positions = np.array([3, 2, 8])
+ kwargs = (
+ {"vert": False}
+ if Version(mpl.__version__) < Version("3.10")
+ else {"orientation": "horizontal"}
+ )
+ ax = df.plot.box(positions=positions, **kwargs)
+ _check_text_labels(ax.get_yticklabels(), labels)
+ tm.assert_numpy_array_equal(ax.yaxis.get_ticklocs(), positions)
+ assert len(ax.lines) == 7 * len(numeric_cols)
+
+ def test_boxplot_return_type_invalid(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["one", "two", "three", "four"],
+ )
+ msg = "return_type must be {None, 'axes', 'dict', 'both'}"
+ with pytest.raises(ValueError, match=msg):
+ df.plot.box(return_type="not_a_type")
+
+ @pytest.mark.parametrize("return_type", ["dict", "axes", "both"])
+ def test_boxplot_return_type_invalid_type(self, return_type):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["one", "two", "three", "four"],
+ )
+ result = df.plot.box(return_type=return_type)
+ _check_box_return_type(result, return_type)
+
+ def test_kde_df(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ ax = _check_plot_works(df.plot, kind="kde")
+ expected = [pprint_thing(c) for c in df.columns]
+ _check_legend_labels(ax, labels=expected)
+ _check_ticks_props(ax, xrot=0)
+
+ def test_kde_df_rot(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ ax = df.plot(kind="kde", rot=20, fontsize=5)
+ _check_ticks_props(ax, xrot=20, xlabelsize=5, ylabelsize=5)
+
+ def test_kde_df_subplots(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ axes = _check_plot_works(
+ df.plot,
+ default_axes=True,
+ kind="kde",
+ subplots=True,
+ )
+ _check_axes_shape(axes, axes_num=4, layout=(4, 1))
+
+ def test_kde_df_logy(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ axes = df.plot(kind="kde", logy=True, subplots=True)
+ _check_ax_scales(axes, yaxis="log")
+
+ def test_kde_missing_vals(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).uniform(size=(100, 4)))
+ df.loc[0, 0] = np.nan
+ _check_plot_works(df.plot, kind="kde")
+
+ def test_hist_df(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((100, 4)))
+
+ ax = _check_plot_works(df.plot.hist)
+ expected = [pprint_thing(c) for c in df.columns]
+ _check_legend_labels(ax, labels=expected)
+
+ axes = _check_plot_works(
+ df.plot.hist,
+ default_axes=True,
+ subplots=True,
+ logy=True,
+ )
+ _check_axes_shape(axes, axes_num=4, layout=(4, 1))
+ _check_ax_scales(axes, yaxis="log")
+
+ def test_hist_df_series(self):
+ series = Series(np.random.default_rng(2).random(10))
+ axes = series.plot.hist(rot=40)
+ _check_ticks_props(axes, xrot=40, yrot=0)
+
+ def test_hist_df_series_cumulative_density(self):
+ series = Series(np.random.default_rng(2).random(10))
+ ax = series.plot.hist(cumulative=True, bins=4, density=True)
+ # height of last bin (index 5) must be 1.0
+ rects = [x for x in ax.get_children() if isinstance(x, mpl.patches.Rectangle)]
+ tm.assert_almost_equal(rects[-1].get_height(), 1.0)
+
+ def test_hist_df_series_cumulative(self):
+ series = Series(np.random.default_rng(2).random(10))
+ ax = series.plot.hist(cumulative=True, bins=4)
+ rects = [x for x in ax.get_children() if isinstance(x, mpl.patches.Rectangle)]
+
+ tm.assert_almost_equal(rects[-2].get_height(), 10.0)
+
+ def test_hist_df_orientation(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ # if horizontal, yticklabels are rotated
+ axes = df.plot.hist(rot=50, fontsize=8, orientation="horizontal")
+ _check_ticks_props(axes, xrot=0, yrot=50, ylabelsize=8)
+
+ @pytest.mark.parametrize("weight_shape", [(100,), (100, 2)])
+ def test_hist_weights(self, weight_shape):
+ # GH 33173
+ weights = 0.1 * np.ones(shape=weight_shape)
+ df = DataFrame(
+ dict(
+ zip(
+ ["A", "B"],
+ np.random.default_rng(2).standard_normal((2, 100)),
+ strict=True,
+ )
+ )
+ )
+
+ ax1 = _check_plot_works(df.plot, kind="hist", weights=weights)
+ ax2 = _check_plot_works(df.plot, kind="hist")
+
+ patch_height_with_weights = [patch.get_height() for patch in ax1.patches]
+
+ # original heights with no weights, and we manually multiply with example
+ # weights, so after multiplication, they should be almost same
+ expected_patch_height = [0.1 * patch.get_height() for patch in ax2.patches]
+
+ tm.assert_almost_equal(patch_height_with_weights, expected_patch_height)
+
+ def _check_box_coord(
+ self,
+ patches,
+ expected_y=None,
+ expected_h=None,
+ expected_x=None,
+ expected_w=None,
+ ):
+ result_y = np.array([p.get_y() for p in patches])
+ result_height = np.array([p.get_height() for p in patches])
+ result_x = np.array([p.get_x() for p in patches])
+ result_width = np.array([p.get_width() for p in patches])
+ # dtype is depending on above values, no need to check
+
+ if expected_y is not None:
+ tm.assert_numpy_array_equal(result_y, expected_y, check_dtype=False)
+ if expected_h is not None:
+ tm.assert_numpy_array_equal(result_height, expected_h, check_dtype=False)
+ if expected_x is not None:
+ tm.assert_numpy_array_equal(result_x, expected_x, check_dtype=False)
+ if expected_w is not None:
+ tm.assert_numpy_array_equal(result_width, expected_w, check_dtype=False)
+
+ @pytest.mark.parametrize(
+ "data",
+ [
+ {
+ "A": np.repeat(np.array([1, 2, 3, 4, 5]), np.array([10, 9, 8, 7, 6])),
+ "B": np.repeat(np.array([1, 2, 3, 4, 5]), np.array([8, 8, 8, 8, 8])),
+ "C": np.repeat(np.array([1, 2, 3, 4, 5]), np.array([6, 7, 8, 9, 10])),
+ },
+ {
+ "A": np.repeat(
+ np.array([np.nan, 1, 2, 3, 4, 5]), np.array([3, 10, 9, 8, 7, 6])
+ ),
+ "B": np.repeat(
+ np.array([1, np.nan, 2, 3, 4, 5]), np.array([8, 3, 8, 8, 8, 8])
+ ),
+ "C": np.repeat(
+ np.array([1, 2, 3, np.nan, 4, 5]), np.array([6, 7, 8, 3, 9, 10])
+ ),
+ },
+ ],
+ )
+ def test_hist_df_coord(self, data):
+ df = DataFrame(data)
+
+ ax = df.plot.hist(bins=5)
+ self._check_box_coord(
+ ax.patches[:5],
+ expected_y=np.array([0, 0, 0, 0, 0]),
+ expected_h=np.array([10, 9, 8, 7, 6]),
+ )
+ self._check_box_coord(
+ ax.patches[5:10],
+ expected_y=np.array([0, 0, 0, 0, 0]),
+ expected_h=np.array([8, 8, 8, 8, 8]),
+ )
+ self._check_box_coord(
+ ax.patches[10:],
+ expected_y=np.array([0, 0, 0, 0, 0]),
+ expected_h=np.array([6, 7, 8, 9, 10]),
+ )
+
+ ax = df.plot.hist(bins=5, stacked=True)
+ self._check_box_coord(
+ ax.patches[:5],
+ expected_y=np.array([0, 0, 0, 0, 0]),
+ expected_h=np.array([10, 9, 8, 7, 6]),
+ )
+ self._check_box_coord(
+ ax.patches[5:10],
+ expected_y=np.array([10, 9, 8, 7, 6]),
+ expected_h=np.array([8, 8, 8, 8, 8]),
+ )
+ self._check_box_coord(
+ ax.patches[10:],
+ expected_y=np.array([18, 17, 16, 15, 14]),
+ expected_h=np.array([6, 7, 8, 9, 10]),
+ )
+
+ axes = df.plot.hist(bins=5, stacked=True, subplots=True)
+ self._check_box_coord(
+ axes[0].patches,
+ expected_y=np.array([0, 0, 0, 0, 0]),
+ expected_h=np.array([10, 9, 8, 7, 6]),
+ )
+ self._check_box_coord(
+ axes[1].patches,
+ expected_y=np.array([0, 0, 0, 0, 0]),
+ expected_h=np.array([8, 8, 8, 8, 8]),
+ )
+ self._check_box_coord(
+ axes[2].patches,
+ expected_y=np.array([0, 0, 0, 0, 0]),
+ expected_h=np.array([6, 7, 8, 9, 10]),
+ )
+
+ # horizontal
+ ax = df.plot.hist(bins=5, orientation="horizontal")
+ self._check_box_coord(
+ ax.patches[:5],
+ expected_x=np.array([0, 0, 0, 0, 0]),
+ expected_w=np.array([10, 9, 8, 7, 6]),
+ )
+ self._check_box_coord(
+ ax.patches[5:10],
+ expected_x=np.array([0, 0, 0, 0, 0]),
+ expected_w=np.array([8, 8, 8, 8, 8]),
+ )
+ self._check_box_coord(
+ ax.patches[10:],
+ expected_x=np.array([0, 0, 0, 0, 0]),
+ expected_w=np.array([6, 7, 8, 9, 10]),
+ )
+
+ ax = df.plot.hist(bins=5, stacked=True, orientation="horizontal")
+ self._check_box_coord(
+ ax.patches[:5],
+ expected_x=np.array([0, 0, 0, 0, 0]),
+ expected_w=np.array([10, 9, 8, 7, 6]),
+ )
+ self._check_box_coord(
+ ax.patches[5:10],
+ expected_x=np.array([10, 9, 8, 7, 6]),
+ expected_w=np.array([8, 8, 8, 8, 8]),
+ )
+ self._check_box_coord(
+ ax.patches[10:],
+ expected_x=np.array([18, 17, 16, 15, 14]),
+ expected_w=np.array([6, 7, 8, 9, 10]),
+ )
+
+ axes = df.plot.hist(
+ bins=5, stacked=True, subplots=True, orientation="horizontal"
+ )
+ self._check_box_coord(
+ axes[0].patches,
+ expected_x=np.array([0, 0, 0, 0, 0]),
+ expected_w=np.array([10, 9, 8, 7, 6]),
+ )
+ self._check_box_coord(
+ axes[1].patches,
+ expected_x=np.array([0, 0, 0, 0, 0]),
+ expected_w=np.array([8, 8, 8, 8, 8]),
+ )
+ self._check_box_coord(
+ axes[2].patches,
+ expected_x=np.array([0, 0, 0, 0, 0]),
+ expected_w=np.array([6, 7, 8, 9, 10]),
+ )
+
+ def test_plot_int_columns(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((100, 4))).cumsum()
+ _check_plot_works(df.plot, legend=True)
+
+ @pytest.mark.parametrize(
+ "markers",
+ [
+ {0: "^", 1: "+", 2: "o"},
+ {0: "^", 1: "+"},
+ ["^", "+", "o"],
+ ["^", "+"],
+ ],
+ )
+ def test_style_by_column(self, markers):
+ fig = plt.gcf()
+ fig.clf()
+ fig.add_subplot(111)
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 3)))
+ ax = df.plot(style=markers)
+ for idx, line in enumerate(ax.get_lines()[: len(markers)]):
+ assert line.get_marker() == markers[idx]
+
+ def test_line_label_none(self):
+ s = Series([1, 2])
+ ax = s.plot()
+ assert ax.get_legend() is None
+
+ ax = s.plot(legend=True)
+ assert ax.get_legend().get_texts()[0].get_text() == ""
+
+ @pytest.mark.parametrize(
+ "props, expected",
+ [
+ ("boxprops", "boxes"),
+ ("whiskerprops", "whiskers"),
+ ("capprops", "caps"),
+ ("medianprops", "medians"),
+ ],
+ )
+ def test_specified_props_kwd_plot_box(self, props, expected):
+ # GH 30346
+ df = DataFrame({k: np.random.default_rng(2).random(100) for k in "ABC"})
+ kwd = {props: {"color": "C1"}}
+ result = df.plot.box(return_type="dict", **kwd)
+
+ assert result[expected][0].get_color() == "C1"
+
+ def test_unordered_ts(self):
+ # GH#2609, GH#55906
+ index = [date(2012, 10, 1), date(2012, 9, 1), date(2012, 8, 1)]
+ values = [3.0, 2.0, 1.0]
+ df = DataFrame(
+ np.array(values),
+ index=index,
+ columns=["test"],
+ )
+ ax = df.plot()
+ xticks = ax.lines[0].get_xdata()
+ tm.assert_numpy_array_equal(xticks, np.array(index, dtype=object))
+ ydata = ax.lines[0].get_ydata()
+ tm.assert_numpy_array_equal(ydata, np.array(values))
+
+ # even though we don't sort the data before passing it to matplotlib,
+ # the ticks are sorted
+ xticks = ax.xaxis.get_ticklabels()
+ xlocs = [x.get_position()[0] for x in xticks]
+ assert Index(xlocs).is_monotonic_increasing
+ xlabels = [x.get_text() for x in xticks]
+ assert pd.to_datetime(xlabels, format="%Y-%m-%d").is_monotonic_increasing
+
+ @pytest.mark.parametrize("kind", plotting.PlotAccessor._common_kinds)
+ def test_kind_both_ways(self, kind):
+ pytest.importorskip("scipy")
+ df = DataFrame({"x": [1, 2, 3]})
+ df.plot(kind=kind)
+ getattr(df.plot, kind)()
+
+ @pytest.mark.parametrize("kind", ["scatter", "hexbin"])
+ def test_kind_both_ways_x_y(self, kind):
+ pytest.importorskip("scipy")
+ df = DataFrame({"x": [1, 2, 3]})
+ df.plot("x", "x", kind=kind)
+ getattr(df.plot, kind)("x", "x")
+
+ @pytest.mark.parametrize("kind", plotting.PlotAccessor._common_kinds)
+ def test_all_invalid_plot_data(self, kind):
+ df = DataFrame(list("abcd"))
+ msg = "no numeric data to plot"
+ with pytest.raises(TypeError, match=msg):
+ df.plot(kind=kind)
+
+ @pytest.mark.parametrize(
+ "kind", [*list(plotting.PlotAccessor._common_kinds), "area"]
+ )
+ def test_partially_invalid_plot_data_numeric(self, kind):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ dtype=object,
+ )
+ df[np.random.default_rng(2).random(df.shape[0]) > 0.5] = "a"
+ msg = "no numeric data to plot"
+ with pytest.raises(TypeError, match=msg):
+ df.plot(kind=kind)
+
+ def test_invalid_kind(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ msg = "invalid_plot_kind is not a valid plot kind"
+ with pytest.raises(ValueError, match=msg):
+ df.plot(kind="invalid_plot_kind")
+
+ @pytest.mark.parametrize(
+ "x,y,lbl",
+ [
+ (["B", "C"], "A", "a"),
+ (["A"], ["B", "C"], ["b", "c"]),
+ ],
+ )
+ def test_invalid_xy_args(self, x, y, lbl):
+ # GH 18671, 19699 allows y to be list-like but not x
+ df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]})
+ with pytest.raises(ValueError, match="x must be a label or position"):
+ df.plot(x=x, y=y, label=lbl)
+
+ def test_bad_label(self):
+ df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]})
+ msg = "label should be list-like and same length as y"
+ with pytest.raises(ValueError, match=msg):
+ df.plot(x="A", y=["B", "C"], label="bad_label")
+
+ @pytest.mark.parametrize("x,y", [("A", "B"), (["A"], "B")])
+ def test_invalid_xy_args_dup_cols(self, x, y):
+ # GH 18671, 19699 allows y to be list-like but not x
+ df = DataFrame([[1, 3, 5], [2, 4, 6]], columns=list("AAB"))
+ with pytest.raises(ValueError, match="x must be a label or position"):
+ df.plot(x=x, y=y)
+
+ @pytest.mark.parametrize(
+ "x,y,lbl,colors",
+ [
+ ("A", ["B"], ["b"], ["red"]),
+ ("A", ["B", "C"], ["b", "c"], ["red", "blue"]),
+ (0, [1, 2], ["bokeh", "cython"], ["green", "yellow"]),
+ ],
+ )
+ def test_y_listlike(self, x, y, lbl, colors):
+ # GH 19699: tests list-like y and verifies lbls & colors
+ df = DataFrame({"A": [1, 2], "B": [3, 4], "C": [5, 6]})
+ _check_plot_works(df.plot, x="A", y=y, label=lbl)
+
+ ax = df.plot(x=x, y=y, label=lbl, color=colors)
+ assert len(ax.lines) == len(y)
+ _check_colors(ax.get_lines(), linecolors=colors)
+
+ @pytest.mark.parametrize("x,y,colnames", [(0, 1, ["A", "B"]), (1, 0, [0, 1])])
+ def test_xy_args_integer(self, x, y, colnames):
+ # GH 20056: tests integer args for xy and checks col names
+ df = DataFrame({"A": [1, 2], "B": [3, 4]})
+ df.columns = colnames
+ _check_plot_works(df.plot, x=x, y=y)
+
+ def test_hexbin_basic(self):
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).uniform(size=20),
+ "B": np.random.default_rng(2).uniform(size=20),
+ "C": np.arange(20) + np.random.default_rng(2).uniform(size=20),
+ }
+ )
+
+ ax = df.plot.hexbin(x="A", y="B", gridsize=10)
+ # TODO: need better way to test. This just does existence.
+ assert len(ax.collections) == 1
+
+ def test_hexbin_basic_subplots(self):
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).uniform(size=20),
+ "B": np.random.default_rng(2).uniform(size=20),
+ "C": np.arange(20) + np.random.default_rng(2).uniform(size=20),
+ }
+ )
+ # GH 6951
+ axes = df.plot.hexbin(x="A", y="B", subplots=True)
+ # hexbin should have 2 axes in the figure, 1 for plotting and another
+ # is colorbar
+ assert len(axes[0].figure.axes) == 2
+ # return value is single axes
+ _check_axes_shape(axes, axes_num=1, layout=(1, 1))
+
+ @pytest.mark.parametrize("reduce_C", [None, np.std])
+ def test_hexbin_with_c(self, reduce_C):
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).uniform(size=20),
+ "B": np.random.default_rng(2).uniform(size=20),
+ "C": np.arange(20) + np.random.default_rng(2).uniform(size=20),
+ }
+ )
+
+ ax = df.plot.hexbin(x="A", y="B", C="C", reduce_C_function=reduce_C)
+ assert len(ax.collections) == 1
+
+ @pytest.mark.parametrize(
+ "kwargs, expected",
+ [
+ ({}, "BuGn"), # default cmap
+ ({"colormap": "cubehelix"}, "cubehelix"),
+ ({"cmap": "YlGn"}, "YlGn"),
+ ],
+ )
+ def test_hexbin_cmap(self, kwargs, expected):
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).uniform(size=20),
+ "B": np.random.default_rng(2).uniform(size=20),
+ "C": np.arange(20) + np.random.default_rng(2).uniform(size=20),
+ }
+ )
+ ax = df.plot.hexbin(x="A", y="B", **kwargs)
+ assert ax.collections[0].cmap.name == expected
+
+ def test_pie_df_err(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((5, 3)),
+ columns=["X", "Y", "Z"],
+ index=["a", "b", "c", "d", "e"],
+ )
+ msg = "pie requires either y column or 'subplots=True'"
+ with pytest.raises(ValueError, match=msg):
+ df.plot.pie()
+
+ @pytest.mark.parametrize("y", ["Y", 2])
+ def test_pie_df(self, y):
+ df = DataFrame(
+ np.random.default_rng(2).random((5, 3)),
+ columns=["X", "Y", "Z"],
+ index=["a", "b", "c", "d", "e"],
+ )
+ ax = _check_plot_works(df.plot.pie, y=y)
+ _check_text_labels(ax.texts, df.index)
+
+ def test_pie_df_subplots(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((5, 3)),
+ columns=["X", "Y", "Z"],
+ index=["a", "b", "c", "d", "e"],
+ )
+ axes = _check_plot_works(
+ df.plot.pie,
+ default_axes=True,
+ subplots=True,
+ )
+ assert len(axes) == len(df.columns)
+ for ax in axes:
+ _check_text_labels(ax.texts, df.index)
+ for ax, ylabel in zip(axes, df.columns, strict=True):
+ assert ax.get_ylabel() == ""
+
+ def test_pie_df_labels_colors(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((5, 3)),
+ columns=["X", "Y", "Z"],
+ index=["a", "b", "c", "d", "e"],
+ )
+ labels = ["A", "B", "C", "D", "E"]
+ color_args = ["r", "g", "b", "c", "m"]
+ axes = _check_plot_works(
+ df.plot.pie,
+ default_axes=True,
+ subplots=True,
+ labels=labels,
+ colors=color_args,
+ )
+ assert len(axes) == len(df.columns)
+
+ for ax in axes:
+ _check_text_labels(ax.texts, labels)
+ _check_colors(ax.patches, facecolors=color_args)
+
+ def test_pie_df_nan(self):
+ df = DataFrame(np.random.default_rng(2).random((4, 4)))
+ for i in range(4):
+ df.iloc[i, i] = np.nan
+ _, axes = mpl.pyplot.subplots(ncols=4)
+
+ # GH 37668
+ kwargs = {"normalize": True}
+
+ with tm.assert_produces_warning(None):
+ df.plot.pie(subplots=True, ax=axes, legend=True, **kwargs)
+
+ base_expected = ["0", "1", "2", "3"]
+ for i, ax in enumerate(axes):
+ expected = list(base_expected) # force copy
+ expected[i] = ""
+ result = [x.get_text() for x in ax.texts]
+ assert result == expected
+
+ # legend labels
+ # NaN's not included in legend with subplots
+ # see https://github.com/pandas-dev/pandas/issues/8390
+ result_labels = [x.get_text() for x in ax.get_legend().get_texts()]
+ expected_labels = base_expected[:i] + base_expected[i + 1 :]
+ assert result_labels == expected_labels
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"logy": True},
+ {"logx": True, "logy": True},
+ {"loglog": True},
+ ],
+ )
+ def test_errorbar_plot(self, kwargs):
+ d = {"x": np.arange(12), "y": np.arange(12, 0, -1)}
+ df = DataFrame(d)
+ d_err = {"x": np.ones(12) * 0.2, "y": np.ones(12) * 0.4}
+ df_err = DataFrame(d_err)
+
+ # check line plots
+ ax = _check_plot_works(df.plot, yerr=df_err, **kwargs)
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ @pytest.mark.slow
+ def test_errorbar_plot_bar(self):
+ d = {"x": np.arange(12), "y": np.arange(12, 0, -1)}
+ df = DataFrame(d)
+ d_err = {"x": np.ones(12) * 0.2, "y": np.ones(12) * 0.4}
+ df_err = DataFrame(d_err)
+ ax = _check_plot_works(
+ (df + 1).plot, yerr=df_err, xerr=df_err, kind="bar", log=True
+ )
+ _check_has_errorbars(ax, xerr=2, yerr=2)
+
+ @pytest.mark.slow
+ def test_errorbar_plot_yerr_array(self):
+ d = {"x": np.arange(12), "y": np.arange(12, 0, -1)}
+ df = DataFrame(d)
+ # yerr is raw error values
+ ax = _check_plot_works(df["y"].plot, yerr=np.ones(12) * 0.4)
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+
+ ax = _check_plot_works(df.plot, yerr=np.ones((2, 12)) * 0.4)
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("yerr", ["yerr", "誤差"])
+ def test_errorbar_plot_column_name(self, yerr):
+ d = {"x": np.arange(12), "y": np.arange(12, 0, -1)}
+ df = DataFrame(d)
+ df[yerr] = np.ones(12) * 0.2
+
+ ax = _check_plot_works(df.plot, yerr=yerr)
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ ax = _check_plot_works(df.plot, y="y", x="x", yerr=yerr)
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+
+ @pytest.mark.slow
+ def test_errorbar_plot_external_valueerror(self):
+ d = {"x": np.arange(12), "y": np.arange(12, 0, -1)}
+ df = DataFrame(d)
+ with tm.external_error_raised(ValueError):
+ df.plot(yerr=np.random.default_rng(2).standard_normal(11))
+
+ @pytest.mark.slow
+ def test_errorbar_plot_external_typeerror(self):
+ d = {"x": np.arange(12), "y": np.arange(12, 0, -1)}
+ df = DataFrame(d)
+ df_err = DataFrame({"x": ["zzz"] * 12, "y": ["zzz"] * 12})
+ with tm.external_error_raised(TypeError):
+ df.plot(yerr=df_err)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kind", ["line", "bar", "barh"])
+ @pytest.mark.parametrize(
+ "y_err",
+ [
+ Series(np.ones(12) * 0.2, name="x"),
+ DataFrame({"x": np.ones(12) * 0.2, "y": np.ones(12) * 0.4}),
+ ],
+ )
+ def test_errorbar_plot_different_yerr(self, kind, y_err):
+ df = DataFrame({"x": np.arange(12), "y": np.arange(12, 0, -1)})
+
+ ax = _check_plot_works(df.plot, yerr=y_err, kind=kind)
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kind", ["line", "bar", "barh"])
+ @pytest.mark.parametrize(
+ "y_err, x_err",
+ [
+ (
+ DataFrame({"x": np.ones(12) * 0.2, "y": np.ones(12) * 0.4}),
+ DataFrame({"x": np.ones(12) * 0.2, "y": np.ones(12) * 0.4}),
+ ),
+ (Series(np.ones(12) * 0.2, name="x"), Series(np.ones(12) * 0.2, name="x")),
+ (0.2, 0.2),
+ ],
+ )
+ def test_errorbar_plot_different_yerr_xerr(self, kind, y_err, x_err):
+ df = DataFrame({"x": np.arange(12), "y": np.arange(12, 0, -1)})
+ ax = _check_plot_works(df.plot, yerr=y_err, xerr=x_err, kind=kind)
+ _check_has_errorbars(ax, xerr=2, yerr=2)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kind", ["line", "bar", "barh"])
+ def test_errorbar_plot_different_yerr_xerr_subplots(self, kind):
+ df = DataFrame({"x": np.arange(12), "y": np.arange(12, 0, -1)})
+ df_err = DataFrame({"x": np.ones(12) * 0.2, "y": np.ones(12) * 0.4})
+ axes = _check_plot_works(
+ df.plot,
+ default_axes=True,
+ yerr=df_err,
+ xerr=df_err,
+ subplots=True,
+ kind=kind,
+ )
+ _check_has_errorbars(axes, xerr=1, yerr=1)
+
+ @pytest.mark.xfail(reason="Iterator is consumed", raises=ValueError)
+ def test_errorbar_plot_iterator(self):
+ d = {"x": np.arange(12), "y": np.arange(12, 0, -1)}
+ df = DataFrame(d)
+
+ # yerr is iterator
+ ax = _check_plot_works(df.plot, yerr=itertools.repeat(0.1, len(df)))
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ def test_errorbar_with_integer_column_names(self):
+ # test with integer column names
+ df = DataFrame(np.abs(np.random.default_rng(2).standard_normal((10, 2))))
+ df_err = DataFrame(np.abs(np.random.default_rng(2).standard_normal((10, 2))))
+ ax = _check_plot_works(df.plot, yerr=df_err)
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+ ax = _check_plot_works(df.plot, y=0, yerr=1)
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kind", ["line", "bar"])
+ def test_errorbar_with_partial_columns_kind(self, kind):
+ df = DataFrame(np.abs(np.random.default_rng(2).standard_normal((10, 3))))
+ df_err = DataFrame(
+ np.abs(np.random.default_rng(2).standard_normal((10, 2))), columns=[0, 2]
+ )
+ ax = _check_plot_works(df.plot, yerr=df_err, kind=kind)
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ @pytest.mark.slow
+ def test_errorbar_with_partial_columns_dti(self):
+ df = DataFrame(np.abs(np.random.default_rng(2).standard_normal((10, 3))))
+ df_err = DataFrame(
+ np.abs(np.random.default_rng(2).standard_normal((10, 2))), columns=[0, 2]
+ )
+ ix = date_range("1/1/2000", periods=10, freq="ME")
+ df.set_index(ix, inplace=True)
+ df_err.set_index(ix, inplace=True)
+ ax = _check_plot_works(df.plot, yerr=df_err, kind="line")
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("err_box", [lambda x: x, DataFrame])
+ def test_errorbar_with_partial_columns_box(self, err_box):
+ d = {"x": np.arange(12), "y": np.arange(12, 0, -1)}
+ df = DataFrame(d)
+ err = err_box({"x": np.ones(12) * 0.2, "z": np.ones(12) * 0.4})
+ ax = _check_plot_works(df.plot, yerr=err)
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+
+ @pytest.mark.parametrize("kind", ["line", "bar", "barh"])
+ def test_errorbar_timeseries(self, kind):
+ d = {"x": np.arange(12), "y": np.arange(12, 0, -1)}
+ d_err = {"x": np.ones(12) * 0.2, "y": np.ones(12) * 0.4}
+
+ # check time-series plots
+ ix = date_range("1/1/2000", "1/1/2001", freq="ME")
+ tdf = DataFrame(d, index=ix)
+ tdf_err = DataFrame(d_err, index=ix)
+
+ ax = _check_plot_works(tdf.plot, yerr=tdf_err, kind=kind)
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ ax = _check_plot_works(tdf.plot, yerr=d_err, kind=kind)
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ ax = _check_plot_works(tdf.plot, y="y", yerr=tdf_err["x"], kind=kind)
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+
+ ax = _check_plot_works(tdf.plot, y="y", yerr="x", kind=kind)
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+
+ ax = _check_plot_works(tdf.plot, yerr=tdf_err, kind=kind)
+ _check_has_errorbars(ax, xerr=0, yerr=2)
+
+ axes = _check_plot_works(
+ tdf.plot,
+ default_axes=True,
+ kind=kind,
+ yerr=tdf_err,
+ subplots=True,
+ )
+ _check_has_errorbars(axes, xerr=0, yerr=1)
+
+ def test_errorbar_asymmetrical(self):
+ err = np.random.default_rng(2).random((3, 2, 5))
+
+ # each column is [0, 1, 2, 3, 4], [3, 4, 5, 6, 7]...
+ df = DataFrame(np.arange(15).reshape(3, 5)).T
+
+ ax = df.plot(yerr=err, xerr=err / 2)
+
+ yerr_0_0 = ax.collections[1].get_paths()[0].vertices[:, 1]
+ expected_0_0 = err[0, :, 0] * np.array([-1, 1])
+ tm.assert_almost_equal(yerr_0_0, expected_0_0)
+
+ msg = re.escape(
+ "Asymmetrical error bars should be provided with the shape (3, 2, 5)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.plot(yerr=err.T)
+
+ def test_table(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+ _check_plot_works(df.plot, table=True)
+ _check_plot_works(df.plot, table=df)
+
+ # GH 35945 UserWarning
+ with tm.assert_produces_warning(None):
+ ax = df.plot()
+ assert len(ax.tables) == 0
+ plotting.table(ax, df.T)
+ assert len(ax.tables) == 1
+
+ def test_errorbar_scatter(self):
+ df = DataFrame(
+ np.abs(np.random.default_rng(2).standard_normal((5, 2))),
+ index=range(5),
+ columns=["x", "y"],
+ )
+ df_err = DataFrame(
+ np.abs(np.random.default_rng(2).standard_normal((5, 2))) / 5,
+ index=range(5),
+ columns=["x", "y"],
+ )
+
+ ax = _check_plot_works(df.plot.scatter, x="x", y="y")
+ _check_has_errorbars(ax, xerr=0, yerr=0)
+ ax = _check_plot_works(df.plot.scatter, x="x", y="y", xerr=df_err)
+ _check_has_errorbars(ax, xerr=1, yerr=0)
+
+ ax = _check_plot_works(df.plot.scatter, x="x", y="y", yerr=df_err)
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+ ax = _check_plot_works(df.plot.scatter, x="x", y="y", xerr=df_err, yerr=df_err)
+ _check_has_errorbars(ax, xerr=1, yerr=1)
+
+ def test_errorbar_scatter_color(self):
+ def _check_errorbar_color(containers, expected, has_err="has_xerr"):
+ lines = []
+ errs = next(c.lines for c in ax.containers if getattr(c, has_err, False))
+ for el in errs:
+ if is_list_like(el):
+ lines.extend(el)
+ else:
+ lines.append(el)
+ err_lines = [x for x in lines if x in ax.collections]
+ _check_colors(err_lines, linecolors=np.array([expected] * len(err_lines)))
+
+ # GH 8081
+ df = DataFrame(
+ np.abs(np.random.default_rng(2).standard_normal((10, 5))),
+ columns=["a", "b", "c", "d", "e"],
+ )
+ ax = df.plot.scatter(x="a", y="b", xerr="d", yerr="e", c="red")
+ _check_has_errorbars(ax, xerr=1, yerr=1)
+ _check_errorbar_color(ax.containers, "red", has_err="has_xerr")
+ _check_errorbar_color(ax.containers, "red", has_err="has_yerr")
+
+ ax = df.plot.scatter(x="a", y="b", yerr="e", color="green")
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+ _check_errorbar_color(ax.containers, "green", has_err="has_yerr")
+
+ def test_scatter_unknown_colormap(self):
+ # GH#48726
+ df = DataFrame({"a": [1, 2, 3], "b": 4})
+ with pytest.raises((ValueError, KeyError), match="'unknown' is not a"):
+ df.plot(x="a", y="b", colormap="unknown", kind="scatter")
+
+ def test_sharex_and_ax(self):
+ # https://github.com/pandas-dev/pandas/issues/9737 using gridspec,
+ # the axis in fig.get_axis() are sorted differently than pandas
+ # expected them, so make sure that only the right ones are removed
+ gs, axes = _generate_4_axes_via_gridspec()
+
+ df = DataFrame(
+ {
+ "a": [1, 2, 3, 4, 5, 6],
+ "b": [1, 2, 3, 4, 5, 6],
+ "c": [1, 2, 3, 4, 5, 6],
+ "d": [1, 2, 3, 4, 5, 6],
+ }
+ )
+
+ def _check(axes):
+ for ax in axes:
+ assert len(ax.lines) == 1
+ _check_visible(ax.get_yticklabels(), visible=True)
+ for ax in [axes[0], axes[2]]:
+ _check_visible(ax.get_xticklabels(), visible=False)
+ _check_visible(ax.get_xticklabels(minor=True), visible=False)
+ for ax in [axes[1], axes[3]]:
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+
+ for ax in axes:
+ df.plot(x="a", y="b", title="title", ax=ax, sharex=True)
+ gs.tight_layout(plt.gcf())
+ _check(axes)
+ plt.close("all")
+
+ gs, axes = _generate_4_axes_via_gridspec()
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ axes = df.plot(subplots=True, ax=axes, sharex=True)
+ _check(axes)
+
+ def test_sharex_false_and_ax(self):
+ # https://github.com/pandas-dev/pandas/issues/9737 using gridspec,
+ # the axis in fig.get_axis() are sorted differently than pandas
+ # expected them, so make sure that only the right ones are removed
+ df = DataFrame(
+ {
+ "a": [1, 2, 3, 4, 5, 6],
+ "b": [1, 2, 3, 4, 5, 6],
+ "c": [1, 2, 3, 4, 5, 6],
+ "d": [1, 2, 3, 4, 5, 6],
+ }
+ )
+ gs, axes = _generate_4_axes_via_gridspec()
+ # without sharex, no labels should be touched!
+ for ax in axes:
+ df.plot(x="a", y="b", title="title", ax=ax)
+
+ gs.tight_layout(plt.gcf())
+ for ax in axes:
+ assert len(ax.lines) == 1
+ _check_visible(ax.get_yticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+
+ def test_sharey_and_ax(self):
+ # https://github.com/pandas-dev/pandas/issues/9737 using gridspec,
+ # the axis in fig.get_axis() are sorted differently than pandas
+ # expected them, so make sure that only the right ones are removed
+ gs, axes = _generate_4_axes_via_gridspec()
+
+ df = DataFrame(
+ {
+ "a": [1, 2, 3, 4, 5, 6],
+ "b": [1, 2, 3, 4, 5, 6],
+ "c": [1, 2, 3, 4, 5, 6],
+ "d": [1, 2, 3, 4, 5, 6],
+ }
+ )
+
+ def _check(axes):
+ for ax in axes:
+ assert len(ax.lines) == 1
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+ for ax in [axes[0], axes[1]]:
+ _check_visible(ax.get_yticklabels(), visible=True)
+ for ax in [axes[2], axes[3]]:
+ _check_visible(ax.get_yticklabels(), visible=False)
+
+ for ax in axes:
+ df.plot(x="a", y="b", title="title", ax=ax, sharey=True)
+ gs.tight_layout(plt.gcf())
+ _check(axes)
+ plt.close("all")
+
+ gs, axes = _generate_4_axes_via_gridspec()
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ axes = df.plot(subplots=True, ax=axes, sharey=True)
+
+ gs.tight_layout(plt.gcf())
+ _check(axes)
+
+ def test_sharey_and_ax_tight(self):
+ # https://github.com/pandas-dev/pandas/issues/9737 using gridspec,
+ df = DataFrame(
+ {
+ "a": [1, 2, 3, 4, 5, 6],
+ "b": [1, 2, 3, 4, 5, 6],
+ "c": [1, 2, 3, 4, 5, 6],
+ "d": [1, 2, 3, 4, 5, 6],
+ }
+ )
+ gs, axes = _generate_4_axes_via_gridspec()
+ # without sharex, no labels should be touched!
+ for ax in axes:
+ df.plot(x="a", y="b", title="title", ax=ax)
+
+ gs.tight_layout(plt.gcf())
+ for ax in axes:
+ assert len(ax.lines) == 1
+ _check_visible(ax.get_yticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+
+ @pytest.mark.parametrize("kind", plotting.PlotAccessor._all_kinds)
+ def test_memory_leak(self, kind):
+ """Check that every plot type gets properly collected."""
+ pytest.importorskip("scipy")
+ args = {}
+ if kind in ["hexbin", "scatter", "pie"]:
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).uniform(size=20),
+ "B": np.random.default_rng(2).uniform(size=20),
+ "C": np.arange(20) + np.random.default_rng(2).uniform(size=20),
+ }
+ )
+ args = {"x": "A", "y": "B"}
+ elif kind == "area":
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ ).abs()
+ else:
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+
+ ax = df.plot(kind=kind, **args)
+ # https://github.com/pandas-dev/pandas/issues/9003#issuecomment-70544889
+ if kind in ["line", "area"]:
+ for i, (cached_data, _, _) in enumerate(ax._plot_data):
+ ser = df.iloc[:, i]
+ assert not tm.shares_memory(ser, cached_data)
+ tm.assert_numpy_array_equal(ser._values, cached_data._values)
+ else:
+ assert not hasattr(ax, "_plot_data")
+
+ def test_df_gridspec_patterns_vert_horiz(self):
+ # GH 10819
+ ts = Series(
+ np.random.default_rng(2).standard_normal(10),
+ index=date_range("1/1/2000", periods=10),
+ )
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ index=ts.index,
+ columns=list("AB"),
+ )
+
+ def _get_vertical_grid():
+ gs = mpl.gridspec.GridSpec(3, 1)
+ fig = plt.figure()
+ ax1 = fig.add_subplot(gs[:2, :])
+ ax2 = fig.add_subplot(gs[2, :])
+ return ax1, ax2
+
+ def _get_horizontal_grid():
+ gs = mpl.gridspec.GridSpec(1, 3)
+ fig = plt.figure()
+ ax1 = fig.add_subplot(gs[:, :2])
+ ax2 = fig.add_subplot(gs[:, 2])
+ return ax1, ax2
+
+ for ax1, ax2 in [_get_vertical_grid(), _get_horizontal_grid()]:
+ ax1 = ts.plot(ax=ax1)
+ assert len(ax1.lines) == 1
+ ax2 = df.plot(ax=ax2)
+ assert len(ax2.lines) == 2
+ for ax in [ax1, ax2]:
+ _check_visible(ax.get_yticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+ plt.close("all")
+
+ # subplots=True
+ for ax1, ax2 in [_get_vertical_grid(), _get_horizontal_grid()]:
+ axes = df.plot(subplots=True, ax=[ax1, ax2])
+ assert len(ax1.lines) == 1
+ assert len(ax2.lines) == 1
+ for ax in axes:
+ _check_visible(ax.get_yticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+ plt.close("all")
+
+ # vertical / subplots / sharex=True / sharey=True
+ ax1, ax2 = _get_vertical_grid()
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ axes = df.plot(subplots=True, ax=[ax1, ax2], sharex=True, sharey=True)
+ assert len(axes[0].lines) == 1
+ assert len(axes[1].lines) == 1
+ for ax in [ax1, ax2]:
+ # yaxis are visible because there is only one column
+ _check_visible(ax.get_yticklabels(), visible=True)
+ # xaxis of axes0 (top) are hidden
+ _check_visible(axes[0].get_xticklabels(), visible=False)
+ _check_visible(axes[0].get_xticklabels(minor=True), visible=False)
+ _check_visible(axes[1].get_xticklabels(), visible=True)
+ _check_visible(axes[1].get_xticklabels(minor=True), visible=True)
+ plt.close("all")
+
+ # horizontal / subplots / sharex=True / sharey=True
+ ax1, ax2 = _get_horizontal_grid()
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ axes = df.plot(subplots=True, ax=[ax1, ax2], sharex=True, sharey=True)
+ assert len(axes[0].lines) == 1
+ assert len(axes[1].lines) == 1
+ _check_visible(axes[0].get_yticklabels(), visible=True)
+ # yaxis of axes1 (right) are hidden
+ _check_visible(axes[1].get_yticklabels(), visible=False)
+ for ax in [ax1, ax2]:
+ # xaxis are visible because there is only one column
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+ plt.close("all")
+
+ def test_df_gridspec_patterns_boxed(self):
+ # GH 10819
+ ts = Series(
+ np.random.default_rng(2).standard_normal(10),
+ index=date_range("1/1/2000", periods=10),
+ )
+
+ # boxed
+ def _get_boxed_grid():
+ gs = mpl.gridspec.GridSpec(3, 3)
+ fig = plt.figure()
+ ax1 = fig.add_subplot(gs[:2, :2])
+ ax2 = fig.add_subplot(gs[:2, 2])
+ ax3 = fig.add_subplot(gs[2, :2])
+ ax4 = fig.add_subplot(gs[2, 2])
+ return ax1, ax2, ax3, ax4
+
+ axes = _get_boxed_grid()
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ index=ts.index,
+ columns=list("ABCD"),
+ )
+ axes = df.plot(subplots=True, ax=axes)
+ for ax in axes:
+ assert len(ax.lines) == 1
+ # axis are visible because these are not shared
+ _check_visible(ax.get_yticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+ plt.close("all")
+
+ # subplots / sharex=True / sharey=True
+ axes = _get_boxed_grid()
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ axes = df.plot(subplots=True, ax=axes, sharex=True, sharey=True)
+ for ax in axes:
+ assert len(ax.lines) == 1
+ for ax in [axes[0], axes[2]]: # left column
+ _check_visible(ax.get_yticklabels(), visible=True)
+ for ax in [axes[1], axes[3]]: # right column
+ _check_visible(ax.get_yticklabels(), visible=False)
+ for ax in [axes[0], axes[1]]: # top row
+ _check_visible(ax.get_xticklabels(), visible=False)
+ _check_visible(ax.get_xticklabels(minor=True), visible=False)
+ for ax in [axes[2], axes[3]]: # bottom row
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+ plt.close("all")
+
+ def test_df_grid_settings(self):
+ # Make sure plot defaults to rcParams['axes.grid'] setting, GH 9792
+ _check_grid_settings(
+ DataFrame({"a": [1, 2, 3], "b": [2, 3, 4]}),
+ plotting.PlotAccessor._dataframe_kinds,
+ kws={"x": "a", "y": "b"},
+ )
+
+ def test_plain_axes(self):
+ # supplied ax itself is a SubplotAxes, but figure contains also
+ # a plain Axes object (GH11556)
+ fig, ax = mpl.pyplot.subplots()
+ fig.add_axes([0.2, 0.2, 0.2, 0.2])
+ Series(np.random.default_rng(2).random(10)).plot(ax=ax)
+
+ def test_plain_axes_df(self):
+ # supplied ax itself is a plain Axes, but because the cmap keyword
+ # a new ax is created for the colorbar -> also multiples axes (GH11520)
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(8),
+ "b": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+ fig = mpl.pyplot.figure()
+ ax = fig.add_axes((0, 0, 1, 1))
+ df.plot(kind="scatter", ax=ax, x="a", y="b", c="a", cmap="hsv")
+
+ def test_plain_axes_make_axes_locatable(self):
+ # other examples
+ fig, ax = mpl.pyplot.subplots()
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
+
+ divider = make_axes_locatable(ax)
+ cax = divider.append_axes("right", size="5%", pad=0.05)
+ Series(np.random.default_rng(2).random(10)).plot(ax=ax)
+ Series(np.random.default_rng(2).random(10)).plot(ax=cax)
+
+ def test_plain_axes_make_inset_axes(self):
+ fig, ax = mpl.pyplot.subplots()
+ from mpl_toolkits.axes_grid1.inset_locator import inset_axes
+
+ iax = inset_axes(ax, width="30%", height=1.0, loc=3)
+ Series(np.random.default_rng(2).random(10)).plot(ax=ax)
+ Series(np.random.default_rng(2).random(10)).plot(ax=iax)
+
+ @pytest.mark.parametrize("method", ["line", "barh", "bar"])
+ def test_secondary_axis_font_size(self, method):
+ # GH: 12565
+ df = (
+ DataFrame(
+ np.random.default_rng(2).standard_normal((15, 2)), columns=list("AB")
+ )
+ .assign(C=lambda df: df.B.cumsum())
+ .assign(D=lambda df: df.C * 1.1)
+ )
+
+ fontsize = 20
+ sy = ["C", "D"]
+
+ kwargs = {"secondary_y": sy, "fontsize": fontsize, "mark_right": True}
+ ax = getattr(df.plot, method)(**kwargs)
+ _check_ticks_props(axes=ax.right_ax, ylabelsize=fontsize)
+
+ def test_x_string_values_ticks(self):
+ # Test if string plot index have a fixed xtick position
+ # GH: 7612, GH: 22334
+ df = DataFrame(
+ {
+ "sales": [3, 2, 3],
+ "visits": [20, 42, 28],
+ "day": ["Monday", "Tuesday", "Wednesday"],
+ }
+ )
+ ax = df.plot.area(x="day")
+ ax.set_xlim(-1, 3)
+ xticklabels = [t.get_text() for t in ax.get_xticklabels()]
+ labels_position = dict(zip(xticklabels, ax.get_xticks(), strict=False))
+ # Testing if the label stayed at the right position
+ assert labels_position["Monday"] == 0.0
+ assert labels_position["Tuesday"] == 1.0
+ assert labels_position["Wednesday"] == 2.0
+
+ def test_x_multiindex_values_ticks(self):
+ # Test if multiindex plot index have a fixed xtick position
+ # GH: 15912
+ index = MultiIndex.from_product([[2012, 2013], [1, 2]])
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 2)),
+ columns=["A", "B"],
+ index=index,
+ )
+ ax = df.plot()
+ ax.set_xlim(-1, 4)
+ xticklabels = [t.get_text() for t in ax.get_xticklabels()]
+ labels_position = dict(zip(xticklabels, ax.get_xticks(), strict=False))
+ # Testing if the label stayed at the right position
+ assert labels_position["(2012, 1)"] == 0.0
+ assert labels_position["(2012, 2)"] == 1.0
+ assert labels_position["(2013, 1)"] == 2.0
+ assert labels_position["(2013, 2)"] == 3.0
+
+ @pytest.mark.parametrize("kind", ["line", "area"])
+ def test_xlim_plot_line(self, kind):
+ # test if xlim is set correctly in plot.line and plot.area
+ # GH 27686
+ df = DataFrame([2, 4], index=[1, 2])
+ ax = df.plot(kind=kind)
+ xlims = ax.get_xlim()
+ assert xlims[0] < 1
+ assert xlims[1] > 2
+
+ def test_xlim_plot_line_correctly_in_mixed_plot_type(self):
+ # test if xlim is set correctly when ax contains multiple different kinds
+ # of plots, GH 27686
+ fig, ax = mpl.pyplot.subplots()
+
+ indexes = ["k1", "k2", "k3", "k4"]
+ df = DataFrame(
+ {
+ "s1": [1000, 2000, 1500, 2000],
+ "s2": [900, 1400, 2000, 3000],
+ "s3": [1500, 1500, 1600, 1200],
+ "secondary_y": [1, 3, 4, 3],
+ },
+ index=indexes,
+ )
+ df[["s1", "s2", "s3"]].plot.bar(ax=ax, stacked=False)
+ df[["secondary_y"]].plot(ax=ax, secondary_y=True)
+
+ xlims = ax.get_xlim()
+ assert xlims[0] < 0
+ assert xlims[1] > 3
+
+ # make sure axis labels are plotted correctly as well
+ xticklabels = [t.get_text() for t in ax.get_xticklabels()]
+ assert xticklabels == indexes
+
+ def test_plot_no_rows(self):
+ # GH 27758
+ df = DataFrame(columns=["foo"], dtype=int)
+ assert df.empty
+ ax = df.plot()
+ assert len(ax.get_lines()) == 1
+ line = ax.get_lines()[0]
+ assert len(line.get_xdata()) == 0
+ assert len(line.get_ydata()) == 0
+
+ def test_plot_no_numeric_data(self):
+ df = DataFrame(["a", "b", "c"])
+ with pytest.raises(TypeError, match="no numeric data to plot"):
+ df.plot()
+
+ @pytest.mark.parametrize(
+ "kind", ("line", "bar", "barh", "hist", "kde", "density", "area", "pie")
+ )
+ def test_group_subplot(self, kind):
+ pytest.importorskip("scipy")
+ d = {
+ "a": np.arange(10),
+ "b": np.arange(10) + 1,
+ "c": np.arange(10) + 1,
+ "d": np.arange(10),
+ "e": np.arange(10),
+ }
+ df = DataFrame(d)
+
+ axes = df.plot(subplots=[("b", "e"), ("c", "d")], kind=kind)
+ assert len(axes) == 3 # 2 groups + single column a
+
+ expected_labels = (["b", "e"], ["c", "d"], ["a"])
+ for ax, labels in zip(axes, expected_labels, strict=True):
+ if kind != "pie":
+ _check_legend_labels(ax, labels=labels)
+ if kind == "line":
+ assert len(ax.lines) == len(labels)
+
+ def test_group_subplot_series_notimplemented(self):
+ ser = Series(range(1))
+ msg = "An iterable subplots for a Series"
+ with pytest.raises(NotImplementedError, match=msg):
+ ser.plot(subplots=[("a",)])
+
+ def test_group_subplot_multiindex_notimplemented(self):
+ df = DataFrame(np.eye(2), columns=MultiIndex.from_tuples([(0, 1), (1, 2)]))
+ msg = "An iterable subplots for a DataFrame with a MultiIndex"
+ with pytest.raises(NotImplementedError, match=msg):
+ df.plot(subplots=[(0, 1)])
+
+ def test_group_subplot_nonunique_cols_notimplemented(self):
+ df = DataFrame(np.eye(2), columns=["a", "a"])
+ msg = "An iterable subplots for a DataFrame with non-unique"
+ with pytest.raises(NotImplementedError, match=msg):
+ df.plot(subplots=[("a",)])
+
+ @pytest.mark.parametrize(
+ "subplots, expected_msg",
+ [
+ (123, "subplots should be a bool or an iterable"),
+ ("a", "each entry should be a list/tuple"), # iterable of non-iterable
+ ((1,), "each entry should be a list/tuple"), # iterable of non-iterable
+ (("a",), "each entry should be a list/tuple"), # iterable of strings
+ ],
+ )
+ def test_group_subplot_bad_input(self, subplots, expected_msg):
+ # Make sure error is raised when subplots is not a properly
+ # formatted iterable. Only iterables of iterables are permitted, and
+ # entries should not be strings.
+ d = {"a": np.arange(10), "b": np.arange(10)}
+ df = DataFrame(d)
+
+ with pytest.raises(ValueError, match=expected_msg):
+ df.plot(subplots=subplots)
+
+ def test_group_subplot_invalid_column_name(self):
+ d = {"a": np.arange(10), "b": np.arange(10)}
+ df = DataFrame(d)
+
+ if Version(np.__version__) < Version("2.0.0"):
+ with pytest.raises(ValueError, match=r"Column label\(s\) \['bad_name'\]"):
+ df.plot(subplots=[("a", "bad_name")])
+ else:
+ with pytest.raises(
+ ValueError, match=r"Column label\(s\) \[np\.str\_\('bad_name'\)\]"
+ ):
+ df.plot(subplots=[("a", "bad_name")])
+
+ def test_group_subplot_duplicated_column(self):
+ d = {"a": np.arange(10), "b": np.arange(10), "c": np.arange(10)}
+ df = DataFrame(d)
+
+ with pytest.raises(ValueError, match="should be in only one subplot"):
+ df.plot(subplots=[("a", "b"), ("a", "c")])
+
+ @pytest.mark.parametrize("kind", ("box", "scatter", "hexbin"))
+ def test_group_subplot_invalid_kind(self, kind):
+ d = {"a": np.arange(10), "b": np.arange(10)}
+ df = DataFrame(d)
+ with pytest.raises(
+ ValueError, match="When subplots is an iterable, kind must be one of"
+ ):
+ df.plot(subplots=[("a", "b")], kind=kind)
+
+ @pytest.mark.parametrize(
+ "index_name, old_label, new_label",
+ [
+ (None, "", "new"),
+ ("old", "old", "new"),
+ (None, "", ""),
+ (None, "", 1),
+ (None, "", [1, 2]),
+ ],
+ )
+ @pytest.mark.parametrize("kind", ["line", "area", "bar"])
+ def test_xlabel_ylabel_dataframe_single_plot(
+ self, kind, index_name, old_label, new_label
+ ):
+ # GH 9093
+ df = DataFrame([[1, 2], [2, 5]], columns=["Type A", "Type B"])
+ df.index.name = index_name
+
+ # default is the ylabel is not shown and xlabel is index name
+ ax = df.plot(kind=kind)
+ assert ax.get_xlabel() == old_label
+ assert ax.get_ylabel() == ""
+
+ # old xlabel will be overridden and assigned ylabel will be used as ylabel
+ ax = df.plot(kind=kind, ylabel=new_label, xlabel=new_label)
+ assert ax.get_ylabel() == str(new_label)
+ assert ax.get_xlabel() == str(new_label)
+
+ @pytest.mark.parametrize(
+ "xlabel, ylabel",
+ [
+ (None, None),
+ ("X Label", None),
+ (None, "Y Label"),
+ ("X Label", "Y Label"),
+ ],
+ )
+ @pytest.mark.parametrize("kind", ["scatter", "hexbin"])
+ def test_xlabel_ylabel_dataframe_plane_plot(self, kind, xlabel, ylabel):
+ # GH 37001
+ xcol = "Type A"
+ ycol = "Type B"
+ df = DataFrame([[1, 2], [2, 5]], columns=[xcol, ycol])
+
+ # default is the labels are column names
+ ax = df.plot(kind=kind, x=xcol, y=ycol, xlabel=xlabel, ylabel=ylabel)
+ assert ax.get_xlabel() == (xcol if xlabel is None else xlabel)
+ assert ax.get_ylabel() == (ycol if ylabel is None else ylabel)
+
+ @pytest.mark.parametrize("secondary_y", (False, True))
+ def test_secondary_y(self, secondary_y):
+ ax_df = DataFrame([0]).plot(
+ secondary_y=secondary_y, ylabel="Y", ylim=(0, 100), yticks=[99]
+ )
+ for ax in ax_df.figure.axes:
+ if ax.yaxis.get_visible():
+ assert ax.get_ylabel() == "Y"
+ assert ax.get_ylim() == (0, 100)
+ assert ax.get_yticks()[0] == 99
+
+ @pytest.mark.slow
+ def test_plot_no_warning(self):
+ # GH 55138
+ # TODO(3.0): this can be removed once Period[B] deprecation is enforced
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ with tm.assert_produces_warning(False):
+ _ = df.plot()
+ _ = df.T.plot()
+
+ @pytest.mark.parametrize("freq", ["h", "7h", "60min", "120min", "3M"])
+ def test_plot_period_index_makes_no_right_shift(self, freq):
+ # GH#57587
+ idx = pd.period_range("01/01/2000", freq=freq, periods=4)
+ df = DataFrame(
+ np.array([0, 1, 0, 1]),
+ index=idx,
+ columns=["A"],
+ )
+ expected = idx.values
+
+ ax = df.plot()
+ result = ax.get_lines()[0].get_xdata()
+ assert all(str(result[i]) == str(expected[i]) for i in range(4))
+
+ def test_plot_display_xlabel_and_xticks(self):
+ # GH#44050
+ df = DataFrame(np.random.default_rng(2).random((10, 2)), columns=["a", "b"])
+ ax = df.plot.hexbin(x="a", y="b")
+
+ _check_visible([ax.xaxis.get_label()], visible=True)
+ _check_visible(ax.get_xticklabels(), visible=True)
+
+
+def _generate_4_axes_via_gridspec():
+ gs = mpl.gridspec.GridSpec(2, 2)
+ ax_tl = plt.subplot(gs[0, 0])
+ ax_ll = plt.subplot(gs[1, 0])
+ ax_tr = plt.subplot(gs[0, 1])
+ ax_lr = plt.subplot(gs[1, 1])
+
+ return gs, [ax_tl, ax_ll, ax_tr, ax_lr]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_color.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_color.py
new file mode 100644
index 0000000000000000000000000000000000000000..96adf8e5bb8c91ce83f85088f648bdaf737b8e83
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_color.py
@@ -0,0 +1,734 @@
+"""Test cases for DataFrame.plot"""
+
+import re
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import DataFrame
+import pandas._testing as tm
+from pandas.tests.plotting.common import (
+ _check_colors,
+ _check_plot_works,
+ _unpack_cycler,
+)
+
+mpl = pytest.importorskip("matplotlib")
+plt = pytest.importorskip("matplotlib.pyplot")
+cm = pytest.importorskip("matplotlib.cm")
+
+
+def _check_colors_box(bp, box_c, whiskers_c, medians_c, caps_c="k", fliers_c=None):
+ if fliers_c is None:
+ fliers_c = "k"
+ _check_colors(bp["boxes"], linecolors=[box_c] * len(bp["boxes"]))
+ _check_colors(bp["whiskers"], linecolors=[whiskers_c] * len(bp["whiskers"]))
+ _check_colors(bp["medians"], linecolors=[medians_c] * len(bp["medians"]))
+ _check_colors(bp["fliers"], linecolors=[fliers_c] * len(bp["fliers"]))
+ _check_colors(bp["caps"], linecolors=[caps_c] * len(bp["caps"]))
+
+
+class TestDataFrameColor:
+ @pytest.mark.parametrize("color", list(range(10)))
+ def test_mpl2_color_cycle_str(self, color):
+ # GH 15516
+ color = f"C{color}"
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 3)), columns=["a", "b", "c"]
+ )
+ _check_plot_works(df.plot, color=color)
+
+ def test_color_single_series_list(self):
+ # GH 3486
+ df = DataFrame({"A": [1, 2, 3]})
+ _check_plot_works(df.plot, color=["red"])
+
+ @pytest.mark.parametrize("color", [(1, 0, 0), (1, 0, 0, 0.5)])
+ def test_rgb_tuple_color(self, color):
+ # GH 16695
+ df = DataFrame({"x": [1, 2], "y": [3, 4]})
+ _check_plot_works(df.plot, x="x", y="y", color=color)
+
+ def test_color_empty_string(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ with pytest.raises(ValueError, match="Invalid color argument:"):
+ df.plot(color="")
+
+ def test_color_and_style_arguments(self):
+ df = DataFrame({"x": [1, 2], "y": [3, 4]})
+ # passing both 'color' and 'style' arguments should be allowed
+ # if there is no color symbol in the style strings:
+ ax = df.plot(color=["red", "black"], style=["-", "--"])
+ # check that the linestyles are correctly set:
+ linestyle = [line.get_linestyle() for line in ax.lines]
+ assert linestyle == ["-", "--"]
+ # check that the colors are correctly set:
+ color = [line.get_color() for line in ax.lines]
+ assert color == ["red", "black"]
+ # passing both 'color' and 'style' arguments should not be allowed
+ # if there is a color symbol in the style strings:
+ msg = (
+ "Cannot pass 'style' string with a color symbol and 'color' keyword "
+ "argument. Please use one or the other or pass 'style' without a color "
+ "symbol"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.plot(color=["red", "black"], style=["k-", "r--"])
+
+ @pytest.mark.parametrize(
+ "color, expected",
+ [
+ ("green", ["green"] * 4),
+ (["yellow", "red", "green", "blue"], ["yellow", "red", "green", "blue"]),
+ ],
+ )
+ def test_color_and_marker(self, color, expected):
+ # GH 21003
+ df = DataFrame(np.random.default_rng(2).random((7, 4)))
+ ax = df.plot(color=color, style="d--")
+ # check colors
+ result = [i.get_color() for i in ax.lines]
+ assert result == expected
+ # check markers and linestyles
+ assert all(i.get_linestyle() == "--" for i in ax.lines)
+ assert all(i.get_marker() == "d" for i in ax.lines)
+
+ def test_color_and_style(self):
+ color = {"g": "black", "h": "brown"}
+ style = {"g": "-", "h": "--"}
+ expected_color = ["black", "brown"]
+ expected_style = ["-", "--"]
+ df = DataFrame({"g": [1, 2], "h": [2, 3]}, index=[1, 2])
+ ax = df.plot.line(color=color, style=style)
+ color = [i.get_color() for i in ax.lines]
+ style = [i.get_linestyle() for i in ax.lines]
+ assert color == expected_color
+ assert style == expected_style
+
+ def test_bar_colors(self):
+ default_colors = _unpack_cycler(plt.rcParams)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot.bar()
+ _check_colors(ax.patches[::5], facecolors=default_colors[:5])
+
+ def test_bar_colors_custom(self):
+ custom_colors = "rgcby"
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot.bar(color=custom_colors)
+ _check_colors(ax.patches[::5], facecolors=custom_colors)
+
+ @pytest.mark.parametrize("colormap", ["jet", cm.jet])
+ def test_bar_colors_cmap(self, colormap):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+
+ ax = df.plot.bar(colormap=colormap)
+ rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, 5)]
+ _check_colors(ax.patches[::5], facecolors=rgba_colors)
+
+ def test_bar_colors_single_col(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.loc[:, [0]].plot.bar(color="DodgerBlue")
+ _check_colors([ax.patches[0]], facecolors=["DodgerBlue"])
+
+ def test_bar_colors_green(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot(kind="bar", color="green")
+ _check_colors(ax.patches[::5], facecolors=["green"] * 5)
+
+ def test_bar_user_colors(self):
+ df = DataFrame(
+ {"A": range(4), "B": range(1, 5), "color": ["red", "blue", "blue", "red"]}
+ )
+ # This should *only* work when `y` is specified, else
+ # we use one color per column
+ ax = df.plot.bar(y="A", color=df["color"])
+ result = [p.get_facecolor() for p in ax.patches]
+ expected = [
+ (1.0, 0.0, 0.0, 1.0),
+ (0.0, 0.0, 1.0, 1.0),
+ (0.0, 0.0, 1.0, 1.0),
+ (1.0, 0.0, 0.0, 1.0),
+ ]
+ assert result == expected
+
+ def test_if_scatterplot_colorbar_affects_xaxis_visibility(self):
+ # addressing issue #10611, to ensure colobar does not
+ # interfere with x-axis label and ticklabels with
+ # ipython inline backend.
+ random_array = np.random.default_rng(2).random((10, 3))
+ df = DataFrame(random_array, columns=["A label", "B label", "C label"])
+
+ ax1 = df.plot.scatter(x="A label", y="B label")
+ ax2 = df.plot.scatter(x="A label", y="B label", c="C label")
+
+ vis1 = [vis.get_visible() for vis in ax1.xaxis.get_minorticklabels()]
+ vis2 = [vis.get_visible() for vis in ax2.xaxis.get_minorticklabels()]
+ assert vis1 == vis2
+
+ vis1 = [vis.get_visible() for vis in ax1.xaxis.get_majorticklabels()]
+ vis2 = [vis.get_visible() for vis in ax2.xaxis.get_majorticklabels()]
+ assert vis1 == vis2
+
+ assert (
+ ax1.xaxis.get_label().get_visible() == ax2.xaxis.get_label().get_visible()
+ )
+
+ def test_if_hexbin_xaxis_label_is_visible(self):
+ # addressing issue #10678, to ensure colobar does not
+ # interfere with x-axis label and ticklabels with
+ # ipython inline backend.
+ random_array = np.random.default_rng(2).random((10, 3))
+ df = DataFrame(random_array, columns=["A label", "B label", "C label"])
+
+ ax = df.plot.hexbin("A label", "B label", gridsize=12)
+ assert all(vis.get_visible() for vis in ax.xaxis.get_minorticklabels())
+ assert all(vis.get_visible() for vis in ax.xaxis.get_majorticklabels())
+ assert ax.xaxis.get_label().get_visible()
+
+ def test_if_scatterplot_colorbars_are_next_to_parent_axes(self):
+ random_array = np.random.default_rng(2).random((10, 3))
+ df = DataFrame(random_array, columns=["A label", "B label", "C label"])
+
+ fig, axes = plt.subplots(1, 2)
+ df.plot.scatter("A label", "B label", c="C label", ax=axes[0])
+ df.plot.scatter("A label", "B label", c="C label", ax=axes[1])
+ plt.tight_layout()
+
+ points = np.array([ax.get_position().get_points() for ax in fig.axes])
+ axes_x_coords = points[:, :, 0]
+ parent_distance = axes_x_coords[1, :] - axes_x_coords[0, :]
+ colorbar_distance = axes_x_coords[3, :] - axes_x_coords[2, :]
+ assert np.isclose(parent_distance, colorbar_distance, atol=1e-7).all()
+
+ @pytest.mark.parametrize("cmap", [None, "Greys"])
+ def test_scatter_with_c_column_name_with_colors(self, cmap):
+ # https://github.com/pandas-dev/pandas/issues/34316
+
+ df = DataFrame(
+ [[5.1, 3.5], [4.9, 3.0], [7.0, 3.2], [6.4, 3.2], [5.9, 3.0]],
+ columns=["length", "width"],
+ )
+ df["species"] = ["r", "r", "g", "g", "b"]
+ if cmap is not None:
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ ax = df.plot.scatter(x=0, y=1, cmap=cmap, c="species")
+ else:
+ ax = df.plot.scatter(x=0, y=1, c="species", cmap=cmap)
+
+ assert len(np.unique(ax.collections[0].get_facecolor(), axis=0)) == 3 # r/g/b
+ assert (
+ np.unique(ax.collections[0].get_facecolor(), axis=0)
+ == np.array(
+ [[0.0, 0.0, 1.0, 1.0], [0.0, 0.5, 0.0, 1.0], [1.0, 0.0, 0.0, 1.0]]
+ ) # r/g/b
+ ).all()
+ assert ax.collections[0].colorbar is None
+
+ def test_scatter_with_c_column_name_without_colors(self):
+ # Given
+ colors = ["NY", "MD", "MA", "CA"]
+ color_count = 4 # 4 unique colors
+
+ # When
+ df = DataFrame(
+ {
+ "dataX": range(100),
+ "dataY": range(100),
+ "color": (colors[i % len(colors)] for i in range(100)),
+ }
+ )
+
+ # Then
+ ax = df.plot.scatter("dataX", "dataY", c="color")
+ assert len(np.unique(ax.collections[0].get_facecolor(), axis=0)) == color_count
+
+ # Given
+ colors = ["r", "g", "not-a-color"]
+ color_count = 3
+ # Also, since not all are mpl-colors, points matching 'r' or 'g'
+ # are not necessarily red or green
+
+ # When
+ df = DataFrame(
+ {
+ "dataX": range(100),
+ "dataY": range(100),
+ "color": (colors[i % len(colors)] for i in range(100)),
+ }
+ )
+
+ # Then
+ ax = df.plot.scatter("dataX", "dataY", c="color")
+ assert len(np.unique(ax.collections[0].get_facecolor(), axis=0)) == color_count
+
+ def test_scatter_colors(self):
+ df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]})
+ with pytest.raises(TypeError, match="Specify exactly one of `c` and `color`"):
+ df.plot.scatter(x="a", y="b", c="c", color="green")
+
+ def test_scatter_colors_not_raising_warnings(self):
+ # GH-53908. Do not raise UserWarning: No data for colormapping
+ # provided via 'c'. Parameters 'cmap' will be ignored
+ df = DataFrame({"x": [1, 2, 3], "y": [1, 2, 3]})
+ with tm.assert_produces_warning(None):
+ ax = df.plot.scatter(x="x", y="y", c="b")
+ assert (
+ len(np.unique(ax.collections[0].get_facecolor(), axis=0)) == 1
+ ) # blue
+ assert (
+ np.unique(ax.collections[0].get_facecolor(), axis=0)
+ == np.array([[0.0, 0.0, 1.0, 1.0]])
+ ).all() # blue
+
+ def test_scatter_colors_default(self):
+ df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]})
+ default_colors = _unpack_cycler(mpl.pyplot.rcParams)
+
+ ax = df.plot.scatter(x="a", y="b", c="c")
+ tm.assert_numpy_array_equal(
+ ax.collections[0].get_facecolor()[0],
+ np.array(mpl.colors.ColorConverter.to_rgba(default_colors[0])),
+ )
+
+ def test_scatter_colors_white(self):
+ df = DataFrame({"a": [1, 2, 3], "b": [1, 2, 3], "c": [1, 2, 3]})
+ ax = df.plot.scatter(x="a", y="b", color="white")
+ tm.assert_numpy_array_equal(
+ ax.collections[0].get_facecolor()[0],
+ np.array([1, 1, 1, 1], dtype=np.float64),
+ )
+
+ def test_scatter_colorbar_different_cmap(self):
+ # GH 33389
+ df = DataFrame({"x": [1, 2, 3], "y": [1, 3, 2], "c": [1, 2, 3]})
+ df["x2"] = df["x"] + 1
+
+ _, ax = plt.subplots()
+ df.plot("x", "y", c="c", kind="scatter", cmap="cividis", ax=ax)
+ df.plot("x2", "y", c="c", kind="scatter", cmap="magma", ax=ax)
+
+ assert ax.collections[0].cmap.name == "cividis"
+ assert ax.collections[1].cmap.name == "magma"
+
+ def test_line_colors(self):
+ custom_colors = "rgcby"
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+
+ ax = df.plot(color=custom_colors)
+ _check_colors(ax.get_lines(), linecolors=custom_colors)
+
+ plt.close("all")
+
+ ax2 = df.plot(color=custom_colors)
+ lines2 = ax2.get_lines()
+
+ for l1, l2 in zip(ax.get_lines(), lines2, strict=True):
+ assert l1.get_color() == l2.get_color()
+
+ @pytest.mark.parametrize("colormap", ["jet", cm.jet])
+ def test_line_colors_cmap(self, colormap):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot(colormap=colormap)
+ rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
+ _check_colors(ax.get_lines(), linecolors=rgba_colors)
+
+ def test_line_colors_single_col(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # make color a list if plotting one column frame
+ # handles cases like df.plot(color='DodgerBlue')
+ ax = df.loc[:, [0]].plot(color="DodgerBlue")
+ _check_colors(ax.lines, linecolors=["DodgerBlue"])
+
+ def test_line_colors_single_color(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot(color="red")
+ _check_colors(ax.get_lines(), linecolors=["red"] * 5)
+
+ def test_line_colors_hex(self):
+ # GH 10299
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ custom_colors = ["#FF0000", "#0000FF", "#FFFF00", "#000000", "#FFFFFF"]
+ ax = df.plot(color=custom_colors)
+ _check_colors(ax.get_lines(), linecolors=custom_colors)
+
+ def test_dont_modify_colors(self):
+ colors = ["r", "g", "b"]
+ DataFrame(np.random.default_rng(2).random((10, 2))).plot(color=colors)
+ assert len(colors) == 3
+
+ def test_line_colors_and_styles_subplots(self):
+ # GH 9894
+ default_colors = _unpack_cycler(mpl.pyplot.rcParams)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+
+ axes = df.plot(subplots=True)
+ for ax, c in zip(axes, list(default_colors)):
+ _check_colors(ax.get_lines(), linecolors=[c])
+
+ @pytest.mark.parametrize("color", ["k", "green"])
+ def test_line_colors_and_styles_subplots_single_color_str(self, color):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ axes = df.plot(subplots=True, color=color)
+ for ax in axes:
+ _check_colors(ax.get_lines(), linecolors=[color])
+
+ @pytest.mark.parametrize("color", ["rgcby", list("rgcby")])
+ def test_line_colors_and_styles_subplots_custom_colors(self, color):
+ # GH 9894
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ axes = df.plot(color=color, subplots=True)
+ for ax, c in zip(axes, list(color), strict=True):
+ _check_colors(ax.get_lines(), linecolors=[c])
+
+ def test_line_colors_and_styles_subplots_colormap_hex(self):
+ # GH 9894
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # GH 10299
+ custom_colors = ["#FF0000", "#0000FF", "#FFFF00", "#000000", "#FFFFFF"]
+ axes = df.plot(color=custom_colors, subplots=True)
+ for ax, c in zip(axes, list(custom_colors), strict=True):
+ _check_colors(ax.get_lines(), linecolors=[c])
+
+ @pytest.mark.parametrize("cmap", ["jet", cm.jet])
+ def test_line_colors_and_styles_subplots_colormap_subplot(self, cmap):
+ # GH 9894
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
+ axes = df.plot(colormap=cmap, subplots=True)
+ for ax, c in zip(axes, rgba_colors, strict=True):
+ _check_colors(ax.get_lines(), linecolors=[c])
+
+ def test_line_colors_and_styles_subplots_single_col(self):
+ # GH 9894
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # make color a list if plotting one column frame
+ # handles cases like df.plot(color='DodgerBlue')
+ axes = df.loc[:, [0]].plot(color="DodgerBlue", subplots=True)
+ _check_colors(axes[0].lines, linecolors=["DodgerBlue"])
+
+ def test_line_colors_and_styles_subplots_single_char(self):
+ # GH 9894
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # single character style
+ axes = df.plot(style="r", subplots=True)
+ for ax in axes:
+ _check_colors(ax.get_lines(), linecolors=["r"])
+
+ def test_line_colors_and_styles_subplots_list_styles(self):
+ # GH 9894
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # list of styles
+ styles = list("rgcby")
+ axes = df.plot(style=styles, subplots=True)
+ for ax, c in zip(axes, styles, strict=True):
+ _check_colors(ax.get_lines(), linecolors=[c])
+
+ def test_area_colors(self):
+ custom_colors = "rgcby"
+ df = DataFrame(np.random.default_rng(2).random((5, 5)))
+
+ ax = df.plot.area(color=custom_colors)
+ _check_colors(ax.get_lines(), linecolors=custom_colors)
+ poly = [
+ o
+ for o in ax.get_children()
+ if isinstance(o, mpl.collections.PolyCollection)
+ ]
+ _check_colors(poly, facecolors=custom_colors)
+
+ handles, _ = ax.get_legend_handles_labels()
+ _check_colors(handles, facecolors=custom_colors)
+
+ for h in handles:
+ assert h.get_alpha() is None
+
+ def test_area_colors_poly(self):
+ df = DataFrame(np.random.default_rng(2).random((5, 5)))
+ ax = df.plot.area(colormap="jet")
+ jet_colors = [mpl.cm.jet(n) for n in np.linspace(0, 1, len(df))]
+ _check_colors(ax.get_lines(), linecolors=jet_colors)
+ poly = [
+ o
+ for o in ax.get_children()
+ if isinstance(o, mpl.collections.PolyCollection)
+ ]
+ _check_colors(poly, facecolors=jet_colors)
+
+ handles, _ = ax.get_legend_handles_labels()
+ _check_colors(handles, facecolors=jet_colors)
+ for h in handles:
+ assert h.get_alpha() is None
+
+ def test_area_colors_stacked_false(self):
+ df = DataFrame(np.random.default_rng(2).random((5, 5)))
+ jet_colors = [mpl.cm.jet(n) for n in np.linspace(0, 1, len(df))]
+ # When stacked=False, alpha is set to 0.5
+ ax = df.plot.area(colormap=mpl.cm.jet, stacked=False)
+ _check_colors(ax.get_lines(), linecolors=jet_colors)
+ poly = [
+ o
+ for o in ax.get_children()
+ if isinstance(o, mpl.collections.PolyCollection)
+ ]
+ jet_with_alpha = [(c[0], c[1], c[2], 0.5) for c in jet_colors]
+ _check_colors(poly, facecolors=jet_with_alpha)
+
+ handles, _ = ax.get_legend_handles_labels()
+ linecolors = jet_with_alpha
+ _check_colors(handles[: len(jet_colors)], linecolors=linecolors)
+ for h in handles:
+ assert h.get_alpha() == 0.5
+
+ def test_hist_colors(self):
+ default_colors = _unpack_cycler(mpl.pyplot.rcParams)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot.hist()
+ _check_colors(ax.patches[::10], facecolors=default_colors[:5])
+
+ def test_hist_colors_single_custom(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ custom_colors = "rgcby"
+ ax = df.plot.hist(color=custom_colors)
+ _check_colors(ax.patches[::10], facecolors=custom_colors)
+
+ @pytest.mark.parametrize("colormap", ["jet", cm.jet])
+ def test_hist_colors_cmap(self, colormap):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot.hist(colormap=colormap)
+ rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, 5)]
+ _check_colors(ax.patches[::10], facecolors=rgba_colors)
+
+ def test_hist_colors_single_col(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.loc[:, [0]].plot.hist(color="DodgerBlue")
+ _check_colors([ax.patches[0]], facecolors=["DodgerBlue"])
+
+ def test_hist_colors_single_color(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot(kind="hist", color="green")
+ _check_colors(ax.patches[::10], facecolors=["green"] * 5)
+
+ def test_kde_colors(self):
+ pytest.importorskip("scipy")
+ custom_colors = "rgcby"
+ df = DataFrame(np.random.default_rng(2).random((5, 5)))
+
+ ax = df.plot.kde(color=custom_colors)
+ _check_colors(ax.get_lines(), linecolors=custom_colors)
+
+ @pytest.mark.parametrize("colormap", ["jet", cm.jet])
+ def test_kde_colors_cmap(self, colormap):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot.kde(colormap=colormap)
+ rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
+ _check_colors(ax.get_lines(), linecolors=rgba_colors)
+
+ def test_kde_colors_and_styles_subplots(self):
+ pytest.importorskip("scipy")
+ default_colors = _unpack_cycler(mpl.pyplot.rcParams)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+
+ axes = df.plot(kind="kde", subplots=True)
+ for ax, c in zip(axes, list(default_colors)):
+ _check_colors(ax.get_lines(), linecolors=[c])
+
+ @pytest.mark.parametrize("colormap", ["k", "red"])
+ def test_kde_colors_and_styles_subplots_single_col_str(self, colormap):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ axes = df.plot(kind="kde", color=colormap, subplots=True)
+ for ax in axes:
+ _check_colors(ax.get_lines(), linecolors=[colormap])
+
+ def test_kde_colors_and_styles_subplots_custom_color(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ custom_colors = "rgcby"
+ axes = df.plot(kind="kde", color=custom_colors, subplots=True)
+ for ax, c in zip(axes, list(custom_colors), strict=True):
+ _check_colors(ax.get_lines(), linecolors=[c])
+
+ @pytest.mark.parametrize("colormap", ["jet", cm.jet])
+ def test_kde_colors_and_styles_subplots_cmap(self, colormap):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ rgba_colors = [cm.jet(n) for n in np.linspace(0, 1, len(df))]
+ axes = df.plot(kind="kde", colormap=colormap, subplots=True)
+ for ax, c in zip(axes, rgba_colors, strict=True):
+ _check_colors(ax.get_lines(), linecolors=[c])
+
+ def test_kde_colors_and_styles_subplots_single_col(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # make color a list if plotting one column frame
+ # handles cases like df.plot(color='DodgerBlue')
+ axes = df.loc[:, [0]].plot(kind="kde", color="DodgerBlue", subplots=True)
+ _check_colors(axes[0].lines, linecolors=["DodgerBlue"])
+
+ def test_kde_colors_and_styles_subplots_single_char(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # list of styles
+ # single character style
+ axes = df.plot(kind="kde", style="r", subplots=True)
+ for ax in axes:
+ _check_colors(ax.get_lines(), linecolors=["r"])
+
+ def test_kde_colors_and_styles_subplots_list(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # list of styles
+ styles = list("rgcby")
+ axes = df.plot(kind="kde", style=styles, subplots=True)
+ for ax, c in zip(axes, styles, strict=True):
+ _check_colors(ax.get_lines(), linecolors=[c])
+
+ def test_boxplot_colors(self):
+ default_colors = _unpack_cycler(mpl.pyplot.rcParams)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ bp = df.plot.box(return_type="dict")
+ _check_colors_box(
+ bp,
+ default_colors[0],
+ default_colors[0],
+ default_colors[2],
+ default_colors[0],
+ )
+
+ def test_boxplot_colors_dict_colors(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ dict_colors = {
+ "boxes": "#572923",
+ "whiskers": "#982042",
+ "medians": "#804823",
+ "caps": "#123456",
+ }
+ bp = df.plot.box(color=dict_colors, sym="r+", return_type="dict")
+ _check_colors_box(
+ bp,
+ dict_colors["boxes"],
+ dict_colors["whiskers"],
+ dict_colors["medians"],
+ dict_colors["caps"],
+ "r",
+ )
+
+ def test_boxplot_colors_default_color(self):
+ default_colors = _unpack_cycler(mpl.pyplot.rcParams)
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # partial colors
+ dict_colors = {"whiskers": "c", "medians": "m"}
+ bp = df.plot.box(color=dict_colors, return_type="dict")
+ _check_colors_box(bp, default_colors[0], "c", "m", default_colors[0])
+
+ @pytest.mark.parametrize("colormap", ["jet", cm.jet])
+ def test_boxplot_colors_cmap(self, colormap):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ bp = df.plot.box(colormap=colormap, return_type="dict")
+ jet_colors = [cm.jet(n) for n in np.linspace(0, 1, 3)]
+ _check_colors_box(
+ bp, jet_colors[0], jet_colors[0], jet_colors[2], jet_colors[0]
+ )
+
+ def test_boxplot_colors_single(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # string color is applied to all artists except fliers
+ bp = df.plot.box(color="DodgerBlue", return_type="dict")
+ _check_colors_box(bp, "DodgerBlue", "DodgerBlue", "DodgerBlue", "DodgerBlue")
+
+ def test_boxplot_colors_tuple(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # tuple is also applied to all artists except fliers
+ bp = df.plot.box(color=(0, 1, 0), sym="#123456", return_type="dict")
+ _check_colors_box(bp, (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), "#123456")
+
+ def test_boxplot_colors_invalid(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ msg = re.escape(
+ "color dict contains invalid key 'xxxx'. The key must be either "
+ "['boxes', 'whiskers', 'medians', 'caps']"
+ )
+ with pytest.raises(ValueError, match=msg):
+ # Color contains invalid key results in ValueError
+ df.plot.box(color={"boxes": "red", "xxxx": "blue"})
+
+ def test_default_color_cycle(self):
+ import cycler
+
+ colors = list("rgbk")
+ plt.rcParams["axes.prop_cycle"] = cycler.cycler("color", colors)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)))
+ ax = df.plot()
+
+ expected = _unpack_cycler(plt.rcParams)[:3]
+ _check_colors(ax.get_lines(), linecolors=expected)
+
+ def test_no_color_bar(self):
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).uniform(size=20),
+ "B": np.random.default_rng(2).uniform(size=20),
+ "C": np.arange(20) + np.random.default_rng(2).uniform(size=20),
+ }
+ )
+ ax = df.plot.hexbin(x="A", y="B", colorbar=None)
+ assert ax.collections[0].colorbar is None
+
+ def test_mixing_cmap_and_colormap_raises(self):
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).uniform(size=20),
+ "B": np.random.default_rng(2).uniform(size=20),
+ "C": np.arange(20) + np.random.default_rng(2).uniform(size=20),
+ }
+ )
+ msg = "Only specify one of `cmap` and `colormap`"
+ with pytest.raises(TypeError, match=msg):
+ df.plot.hexbin(x="A", y="B", cmap="YlGn", colormap="BuGn")
+
+ def test_passed_bar_colors(self):
+ color_tuples = [(0.9, 0, 0, 1), (0, 0.9, 0, 1), (0, 0, 0.9, 1)]
+ colormap = mpl.colors.ListedColormap(color_tuples)
+ barplot = DataFrame([[1, 2, 3]]).plot(kind="bar", cmap=colormap)
+ assert color_tuples == [c.get_facecolor() for c in barplot.patches]
+
+ def test_rcParams_bar_colors(self):
+ color_tuples = [(0.9, 0, 0, 1), (0, 0.9, 0, 1), (0, 0, 0.9, 1)]
+ with mpl.rc_context(rc={"axes.prop_cycle": mpl.cycler("color", color_tuples)}):
+ barplot = DataFrame([[1, 2, 3]]).plot(kind="bar")
+ assert color_tuples == [c.get_facecolor() for c in barplot.patches]
+
+ def test_colors_of_columns_with_same_name(self):
+ # ISSUE 11136 -> https://github.com/pandas-dev/pandas/issues/11136
+ # Creating a DataFrame with duplicate column labels and testing colors of them.
+ df = DataFrame({"b": [0, 1, 0], "a": [1, 2, 3]})
+ df1 = DataFrame({"a": [2, 4, 6]})
+ df_concat = pd.concat([df, df1], axis=1)
+ result = df_concat.plot()
+ legend = result.get_legend()
+ handles = legend.legend_handles
+ for legend, line in zip(handles, result.lines, strict=True):
+ assert legend.get_color() == line.get_color()
+
+ def test_invalid_colormap(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 2)), columns=["A", "B"]
+ )
+ msg = "(is not a valid value)|(is not a known colormap)"
+ with pytest.raises((ValueError, KeyError), match=msg):
+ df.plot(colormap="invalid_colormap")
+
+ def test_dataframe_none_color(self):
+ # GH51953
+ df = DataFrame([[1, 2, 3]])
+ ax = df.plot(color=None)
+ expected = _unpack_cycler(mpl.pyplot.rcParams)[:3]
+ _check_colors(ax.get_lines(), linecolors=expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_groupby.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_groupby.py
new file mode 100644
index 0000000000000000000000000000000000000000..89589f5adbbc08f0bdd75d1752d5b732f935fb05
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_groupby.py
@@ -0,0 +1,72 @@
+"""Test cases for DataFrame.plot"""
+
+import pytest
+
+from pandas import DataFrame
+from pandas.tests.plotting.common import _check_visible
+
+pytest.importorskip("matplotlib")
+
+
+class TestDataFramePlotsGroupby:
+ def _assert_ytickslabels_visibility(self, axes, expected):
+ for ax, exp in zip(axes, expected, strict=True):
+ _check_visible(ax.get_yticklabels(), visible=exp)
+
+ def _assert_xtickslabels_visibility(self, axes, expected):
+ for ax, exp in zip(axes, expected, strict=True):
+ _check_visible(ax.get_xticklabels(), visible=exp)
+
+ @pytest.mark.parametrize(
+ "kwargs, expected",
+ [
+ # behavior without keyword
+ ({}, [True, False, True, False]),
+ # set sharey=True should be identical
+ ({"sharey": True}, [True, False, True, False]),
+ # sharey=False, all yticklabels should be visible
+ ({"sharey": False}, [True, True, True, True]),
+ ],
+ )
+ def test_groupby_boxplot_sharey(self, kwargs, expected):
+ # https://github.com/pandas-dev/pandas/issues/20968
+ # sharey can now be switched check whether the right
+ # pair of axes is turned on or off
+ df = DataFrame(
+ {
+ "a": [-1.43, -0.15, -3.70, -1.43, -0.14],
+ "b": [0.56, 0.84, 0.29, 0.56, 0.85],
+ "c": [0, 1, 2, 3, 1],
+ },
+ index=[0, 1, 2, 3, 4],
+ )
+ axes = df.groupby("c").boxplot(**kwargs)
+ self._assert_ytickslabels_visibility(axes, expected)
+
+ @pytest.mark.parametrize(
+ "kwargs, expected",
+ [
+ # behavior without keyword
+ ({}, [True, True, True, True]),
+ # set sharex=False should be identical
+ ({"sharex": False}, [True, True, True, True]),
+ # sharex=True, xticklabels should be visible
+ # only for bottom plots
+ ({"sharex": True}, [False, False, True, True]),
+ ],
+ )
+ def test_groupby_boxplot_sharex(self, kwargs, expected):
+ # https://github.com/pandas-dev/pandas/issues/20968
+ # sharex can now be switched check whether the right
+ # pair of axes is turned on or off
+
+ df = DataFrame(
+ {
+ "a": [-1.43, -0.15, -3.70, -1.43, -0.14],
+ "b": [0.56, 0.84, 0.29, 0.56, 0.85],
+ "c": [0, 1, 2, 3, 1],
+ },
+ index=[0, 1, 2, 3, 4],
+ )
+ axes = df.groupby("c").boxplot(**kwargs)
+ self._assert_xtickslabels_visibility(axes, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_legend.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_legend.py
new file mode 100644
index 0000000000000000000000000000000000000000..755293e0bf6d753a1061c0a86ca18495e054e04e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_legend.py
@@ -0,0 +1,262 @@
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas import (
+ DataFrame,
+ date_range,
+)
+from pandas.tests.plotting.common import (
+ _check_legend_labels,
+ _check_legend_marker,
+ _check_text_labels,
+)
+
+mpl = pytest.importorskip("matplotlib")
+
+
+class TestFrameLegend:
+ @pytest.mark.xfail(
+ reason=(
+ "Open bug in matplotlib "
+ "https://github.com/matplotlib/matplotlib/issues/11357"
+ )
+ )
+ def test_mixed_yerr(self):
+ # https://github.com/pandas-dev/pandas/issues/39522
+ df = DataFrame([{"x": 1, "a": 1, "b": 1}, {"x": 2, "a": 2, "b": 3}])
+
+ ax = df.plot("x", "a", c="orange", yerr=0.1, label="orange")
+ df.plot("x", "b", c="blue", yerr=None, ax=ax, label="blue")
+
+ legend = ax.get_legend()
+ result_handles = legend.legend_handles
+
+ assert isinstance(result_handles[0], mpl.collections.LineCollection)
+ assert isinstance(result_handles[1], mpl.lines.Line2D)
+
+ def test_legend_false(self):
+ # https://github.com/pandas-dev/pandas/issues/40044
+ df = DataFrame({"a": [1, 1], "b": [2, 3]})
+ df2 = DataFrame({"d": [2.5, 2.5]})
+
+ ax = df.plot(legend=True, color={"a": "blue", "b": "green"}, secondary_y="b")
+ df2.plot(legend=True, color={"d": "red"}, ax=ax)
+ legend = ax.get_legend()
+ handles = legend.legend_handles
+ result = [handle.get_color() for handle in handles]
+ expected = ["blue", "green", "red"]
+ assert result == expected
+
+ @pytest.mark.parametrize("kind", ["line", "bar", "barh", "kde", "area", "hist"])
+ def test_df_legend_labels(self, kind):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).random((3, 3)), columns=["a", "b", "c"])
+ df2 = DataFrame(
+ np.random.default_rng(2).random((3, 3)), columns=["d", "e", "f"]
+ )
+ df3 = DataFrame(
+ np.random.default_rng(2).random((3, 3)), columns=["g", "h", "i"]
+ )
+ df4 = DataFrame(
+ np.random.default_rng(2).random((3, 3)), columns=["j", "k", "l"]
+ )
+
+ ax = df.plot(kind=kind, legend=True)
+ _check_legend_labels(ax, labels=df.columns)
+
+ ax = df2.plot(kind=kind, legend=False, ax=ax)
+ _check_legend_labels(ax, labels=df.columns)
+
+ ax = df3.plot(kind=kind, legend=True, ax=ax)
+ _check_legend_labels(ax, labels=df.columns.union(df3.columns))
+
+ ax = df4.plot(kind=kind, legend="reverse", ax=ax)
+ expected = list(df.columns.union(df3.columns)) + list(reversed(df4.columns))
+ _check_legend_labels(ax, labels=expected)
+
+ def test_df_legend_labels_secondary_y(self):
+ pytest.importorskip("scipy")
+ df = DataFrame(np.random.default_rng(2).random((3, 3)), columns=["a", "b", "c"])
+ df2 = DataFrame(
+ np.random.default_rng(2).random((3, 3)), columns=["d", "e", "f"]
+ )
+ df3 = DataFrame(
+ np.random.default_rng(2).random((3, 3)), columns=["g", "h", "i"]
+ )
+ # Secondary Y
+ ax = df.plot(legend=True, secondary_y="b")
+ _check_legend_labels(ax, labels=["a", "b (right)", "c"])
+ ax = df2.plot(legend=False, ax=ax)
+ _check_legend_labels(ax, labels=["a", "b (right)", "c"])
+ ax = df3.plot(kind="bar", legend=True, secondary_y="h", ax=ax)
+ _check_legend_labels(ax, labels=["a", "b (right)", "c", "g", "h (right)", "i"])
+
+ def test_df_legend_labels_time_series(self):
+ # Time Series
+ pytest.importorskip("scipy")
+ ind = date_range("1/1/2014", periods=3)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=["a", "b", "c"],
+ index=ind,
+ )
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=["d", "e", "f"],
+ index=ind,
+ )
+ df3 = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=["g", "h", "i"],
+ index=ind,
+ )
+ ax = df.plot(legend=True, secondary_y="b")
+ _check_legend_labels(ax, labels=["a", "b (right)", "c"])
+ ax = df2.plot(legend=False, ax=ax)
+ _check_legend_labels(ax, labels=["a", "b (right)", "c"])
+ ax = df3.plot(legend=True, ax=ax)
+ _check_legend_labels(ax, labels=["a", "b (right)", "c", "g", "h", "i"])
+
+ def test_df_legend_labels_time_series_scatter(self):
+ # Time Series
+ pytest.importorskip("scipy")
+ ind = date_range("1/1/2014", periods=3)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=["a", "b", "c"],
+ index=ind,
+ )
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=["d", "e", "f"],
+ index=ind,
+ )
+ df3 = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=["g", "h", "i"],
+ index=ind,
+ )
+ # scatter
+ ax = df.plot.scatter(x="a", y="b", label="data1")
+ _check_legend_labels(ax, labels=["data1"])
+ ax = df2.plot.scatter(x="d", y="e", legend=False, label="data2", ax=ax)
+ _check_legend_labels(ax, labels=["data1"])
+ ax = df3.plot.scatter(x="g", y="h", label="data3", ax=ax)
+ _check_legend_labels(ax, labels=["data1", "data3"])
+
+ def test_df_legend_labels_time_series_no_mutate(self):
+ pytest.importorskip("scipy")
+ ind = date_range("1/1/2014", periods=3)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 3)),
+ columns=["a", "b", "c"],
+ index=ind,
+ )
+ # ensure label args pass through and
+ # index name does not mutate
+ # column names don't mutate
+ df5 = df.set_index("a")
+ ax = df5.plot(y="b")
+ _check_legend_labels(ax, labels=["b"])
+ ax = df5.plot(y="b", label="LABEL_b")
+ _check_legend_labels(ax, labels=["LABEL_b"])
+ _check_text_labels(ax.xaxis.get_label(), "a")
+ ax = df5.plot(y="c", label="LABEL_c", ax=ax)
+ _check_legend_labels(ax, labels=["LABEL_b", "LABEL_c"])
+ assert df5.columns.tolist() == ["b", "c"]
+
+ def test_missing_marker_multi_plots_on_same_ax(self):
+ # GH 18222
+ df = DataFrame(data=[[1, 1, 1, 1], [2, 2, 4, 8]], columns=["x", "r", "g", "b"])
+ _, ax = mpl.pyplot.subplots(nrows=1, ncols=3)
+ # Left plot
+ df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[0])
+ df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[0])
+ df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[0])
+ _check_legend_labels(ax[0], labels=["r", "g", "b"])
+ _check_legend_marker(ax[0], expected_markers=["o", "x", "o"])
+ # Center plot
+ df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[1])
+ df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[1])
+ df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[1])
+ _check_legend_labels(ax[1], labels=["b", "r", "g"])
+ _check_legend_marker(ax[1], expected_markers=["o", "o", "x"])
+ # Right plot
+ df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[2])
+ df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[2])
+ df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[2])
+ _check_legend_labels(ax[2], labels=["g", "b", "r"])
+ _check_legend_marker(ax[2], expected_markers=["x", "o", "o"])
+
+ def test_legend_name(self):
+ multi = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 4)),
+ columns=[np.array(["a", "a", "b", "b"]), np.array(["x", "y", "x", "y"])],
+ )
+ multi.columns.names = ["group", "individual"]
+
+ ax = multi.plot()
+ leg_title = ax.legend_.get_title()
+ _check_text_labels(leg_title, "group,individual")
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot(legend=True, ax=ax)
+ leg_title = ax.legend_.get_title()
+ _check_text_labels(leg_title, "group,individual")
+
+ df.columns.name = "new"
+ ax = df.plot(legend=False, ax=ax)
+ leg_title = ax.legend_.get_title()
+ _check_text_labels(leg_title, "group,individual")
+
+ ax = df.plot(legend=True, ax=ax)
+ leg_title = ax.legend_.get_title()
+ _check_text_labels(leg_title, "new")
+
+ @pytest.mark.parametrize(
+ "kind",
+ [
+ "line",
+ "bar",
+ "barh",
+ pytest.param("kde", marks=td.skip_if_no("scipy")),
+ "area",
+ "hist",
+ ],
+ )
+ def test_no_legend(self, kind):
+ df = DataFrame(np.random.default_rng(2).random((3, 3)), columns=["a", "b", "c"])
+ ax = df.plot(kind=kind, legend=False)
+ _check_legend_labels(ax, visible=False)
+
+ def test_missing_markers_legend(self):
+ # 14958
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((8, 3)), columns=["A", "B", "C"]
+ )
+ ax = df.plot(y=["A"], marker="x", linestyle="solid")
+ df.plot(y=["B"], marker="o", linestyle="dotted", ax=ax)
+ df.plot(y=["C"], marker="<", linestyle="dotted", ax=ax)
+
+ _check_legend_labels(ax, labels=["A", "B", "C"])
+ _check_legend_marker(ax, expected_markers=["x", "o", "<"])
+
+ def test_missing_markers_legend_using_style(self):
+ # 14563
+ df = DataFrame(
+ {
+ "A": [1, 2, 3, 4, 5, 6],
+ "B": [2, 4, 1, 3, 2, 4],
+ "C": [3, 3, 2, 6, 4, 2],
+ "X": [1, 2, 3, 4, 5, 6],
+ }
+ )
+
+ _, ax = mpl.pyplot.subplots()
+ for kind in "ABC":
+ df.plot("X", kind, label=kind, ax=ax, style=".")
+
+ _check_legend_labels(ax, labels=["A", "B", "C"])
+ _check_legend_marker(ax, expected_markers=[".", ".", "."])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_subplots.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_subplots.py
new file mode 100644
index 0000000000000000000000000000000000000000..e8cdc02591bfe3451fd5cc6114e0674277117dd2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_frame_subplots.py
@@ -0,0 +1,751 @@
+"""Test cases for DataFrame.plot"""
+
+import string
+
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_linux
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.tests.plotting.common import (
+ _check_axes_shape,
+ _check_box_return_type,
+ _check_legend_labels,
+ _check_ticks_props,
+ _check_visible,
+ _flatten_visible,
+)
+
+from pandas.io.formats.printing import pprint_thing
+
+mpl = pytest.importorskip("matplotlib")
+plt = pytest.importorskip("matplotlib.pyplot")
+
+
+class TestDataFramePlotsSubplots:
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kind", ["bar", "barh", "line", "area"])
+ def test_subplots(self, kind):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+
+ axes = df.plot(kind=kind, subplots=True, sharex=True, legend=True)
+ _check_axes_shape(axes, axes_num=3, layout=(3, 1))
+ assert axes.shape == (3,)
+
+ for ax, column in zip(axes, df.columns, strict=True):
+ _check_legend_labels(ax, labels=[pprint_thing(column)])
+
+ for ax in axes[:-2]:
+ _check_visible(ax.xaxis) # xaxis must be visible for grid
+ _check_visible(ax.get_xticklabels(), visible=False)
+ if kind != "bar":
+ # change https://github.com/pandas-dev/pandas/issues/26714
+ _check_visible(ax.get_xticklabels(minor=True), visible=False)
+ _check_visible(ax.xaxis.get_label(), visible=False)
+ _check_visible(ax.get_yticklabels())
+
+ _check_visible(axes[-1].xaxis)
+ _check_visible(axes[-1].get_xticklabels())
+ _check_visible(axes[-1].get_xticklabels(minor=True))
+ _check_visible(axes[-1].xaxis.get_label())
+ _check_visible(axes[-1].get_yticklabels())
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kind", ["bar", "barh", "line", "area"])
+ def test_subplots_no_share_x(self, kind):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+ axes = df.plot(kind=kind, subplots=True, sharex=False)
+ for ax in axes:
+ _check_visible(ax.xaxis)
+ _check_visible(ax.get_xticklabels())
+ _check_visible(ax.get_xticklabels(minor=True))
+ _check_visible(ax.xaxis.get_label())
+ _check_visible(ax.get_yticklabels())
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kind", ["bar", "barh", "line", "area"])
+ def test_subplots_no_legend(self, kind):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+ axes = df.plot(kind=kind, subplots=True, legend=False)
+ for ax in axes:
+ assert ax.get_legend() is None
+
+ @pytest.mark.parametrize("kind", ["line", "area"])
+ def test_subplots_timeseries(self, kind):
+ idx = date_range(start="2014-07-01", freq="ME", periods=10)
+ df = DataFrame(np.random.default_rng(2).random((10, 3)), index=idx)
+
+ axes = df.plot(kind=kind, subplots=True, sharex=True)
+ _check_axes_shape(axes, axes_num=3, layout=(3, 1))
+
+ for ax in axes[:-2]:
+ # GH 7801
+ _check_visible(ax.xaxis) # xaxis must be visible for grid
+ _check_visible(ax.get_xticklabels(), visible=False)
+ _check_visible(ax.get_xticklabels(minor=True), visible=False)
+ _check_visible(ax.xaxis.get_label(), visible=False)
+ _check_visible(ax.get_yticklabels())
+
+ _check_visible(axes[-1].xaxis)
+ _check_visible(axes[-1].get_xticklabels())
+ _check_visible(axes[-1].get_xticklabels(minor=True))
+ _check_visible(axes[-1].xaxis.get_label())
+ _check_visible(axes[-1].get_yticklabels())
+ _check_ticks_props(axes, xrot=0)
+
+ @pytest.mark.parametrize("kind", ["line", "area"])
+ def test_subplots_timeseries_rot(self, kind):
+ idx = date_range(start="2014-07-01", freq="ME", periods=10)
+ df = DataFrame(np.random.default_rng(2).random((10, 3)), index=idx)
+ axes = df.plot(kind=kind, subplots=True, sharex=False, rot=45, fontsize=7)
+ for ax in axes:
+ _check_visible(ax.xaxis)
+ _check_visible(ax.get_xticklabels())
+ _check_visible(ax.get_xticklabels(minor=True))
+ _check_visible(ax.xaxis.get_label())
+ _check_visible(ax.get_yticklabels())
+ _check_ticks_props(ax, xlabelsize=7, xrot=45, ylabelsize=7)
+
+ @pytest.mark.parametrize(
+ "col", ["numeric", "timedelta", "datetime_no_tz", "datetime_all_tz"]
+ )
+ def test_subplots_timeseries_y_axis(self, col):
+ # GH16953
+ data = {
+ "numeric": np.array([1, 2, 5]),
+ "timedelta": [
+ pd.Timedelta(-10, unit="s"),
+ pd.Timedelta(10, unit="m"),
+ pd.Timedelta(10, unit="h"),
+ ],
+ "datetime_no_tz": [
+ pd.to_datetime("2017-08-01 00:00:00"),
+ pd.to_datetime("2017-08-01 02:00:00"),
+ pd.to_datetime("2017-08-02 00:00:00"),
+ ],
+ "datetime_all_tz": [
+ pd.to_datetime("2017-08-01 00:00:00", utc=True),
+ pd.to_datetime("2017-08-01 02:00:00", utc=True),
+ pd.to_datetime("2017-08-02 00:00:00", utc=True),
+ ],
+ "text": ["This", "should", "fail"],
+ }
+ testdata = DataFrame(data)
+
+ ax = testdata.plot(y=col)
+ result = ax.get_lines()[0].get_data()[1]
+ expected = testdata[col].values
+ assert (result == expected).all()
+
+ def test_subplots_timeseries_y_text_error(self):
+ # GH16953
+ data = {
+ "numeric": np.array([1, 2, 5]),
+ "text": ["This", "should", "fail"],
+ }
+ testdata = DataFrame(data)
+ msg = "no numeric data to plot"
+ with pytest.raises(TypeError, match=msg):
+ testdata.plot(y="text")
+
+ @pytest.mark.xfail(reason="not support for period, categorical, datetime_mixed_tz")
+ def test_subplots_timeseries_y_axis_not_supported(self):
+ """
+ This test will fail for:
+ period:
+ since period isn't yet implemented in ``select_dtypes``
+ and because it will need a custom value converter +
+ tick formatter (as was done for x-axis plots)
+
+ categorical:
+ because it will need a custom value converter +
+ tick formatter (also doesn't work for x-axis, as of now)
+
+ datetime_mixed_tz:
+ because of the way how pandas handles ``Series`` of
+ ``datetime`` objects with different timezone,
+ generally converting ``datetime`` objects in a tz-aware
+ form could help with this problem
+ """
+ data = {
+ "numeric": np.array([1, 2, 5]),
+ "period": [
+ pd.Period("2017-08-01 00:00:00", freq="h"),
+ pd.Period("2017-08-01 02:00", freq="h"),
+ pd.Period("2017-08-02 00:00:00", freq="h"),
+ ],
+ "categorical": pd.Categorical(
+ ["c", "b", "a"], categories=["a", "b", "c"], ordered=False
+ ),
+ "datetime_mixed_tz": [
+ pd.to_datetime("2017-08-01 00:00:00", utc=True),
+ pd.to_datetime("2017-08-01 02:00:00"),
+ pd.to_datetime("2017-08-02 00:00:00"),
+ ],
+ }
+ testdata = DataFrame(data)
+ ax_period = testdata.plot(x="numeric", y="period")
+ assert (
+ ax_period.get_lines()[0].get_data()[1] == testdata["period"].values
+ ).all()
+ ax_categorical = testdata.plot(x="numeric", y="categorical")
+ assert (
+ ax_categorical.get_lines()[0].get_data()[1]
+ == testdata["categorical"].values
+ ).all()
+ ax_datetime_mixed_tz = testdata.plot(x="numeric", y="datetime_mixed_tz")
+ assert (
+ ax_datetime_mixed_tz.get_lines()[0].get_data()[1]
+ == testdata["datetime_mixed_tz"].values
+ ).all()
+
+ @pytest.mark.parametrize(
+ "layout, exp_layout",
+ [
+ [(2, 2), (2, 2)],
+ [(-1, 2), (2, 2)],
+ [(2, -1), (2, 2)],
+ [(1, 4), (1, 4)],
+ [(-1, 4), (1, 4)],
+ [(4, -1), (4, 1)],
+ ],
+ )
+ def test_subplots_layout_multi_column(self, layout, exp_layout):
+ # GH 6667
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+
+ axes = df.plot(subplots=True, layout=layout)
+ _check_axes_shape(axes, axes_num=3, layout=exp_layout)
+ assert axes.shape == exp_layout
+
+ def test_subplots_layout_multi_column_error(self):
+ # GH 6667
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+ msg = "Layout of 1x1 must be larger than required size 3"
+
+ with pytest.raises(ValueError, match=msg):
+ df.plot(subplots=True, layout=(1, 1))
+
+ msg = "At least one dimension of layout must be positive"
+ with pytest.raises(ValueError, match=msg):
+ df.plot(subplots=True, layout=(-1, -1))
+
+ @pytest.mark.parametrize(
+ "kwargs, expected_axes_num, expected_layout, expected_shape",
+ [
+ ({}, 1, (1, 1), (1,)),
+ ({"layout": (3, 3)}, 1, (3, 3), (3, 3)),
+ ],
+ )
+ def test_subplots_layout_single_column(
+ self, kwargs, expected_axes_num, expected_layout, expected_shape
+ ):
+ # GH 6667
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 1)),
+ index=list(string.ascii_letters[:10]),
+ )
+ axes = df.plot(subplots=True, **kwargs)
+ _check_axes_shape(
+ axes,
+ axes_num=expected_axes_num,
+ layout=expected_layout,
+ )
+ assert axes.shape == expected_shape
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("idx", [range(5), date_range("1/1/2000", periods=5)])
+ def test_subplots_warnings(self, idx):
+ # GH 9464
+ with tm.assert_produces_warning(None):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 4)), index=idx)
+ df.plot(subplots=True, layout=(3, 2))
+
+ def test_subplots_multiple_axes(self):
+ # GH 5353, 6970, GH 7069
+ fig, axes = mpl.pyplot.subplots(2, 3)
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+
+ returned = df.plot(subplots=True, ax=axes[0], sharex=False, sharey=False)
+ _check_axes_shape(returned, axes_num=3, layout=(1, 3))
+ assert returned.shape == (3,)
+ assert returned[0].figure is fig
+ # draw on second row
+ returned = df.plot(subplots=True, ax=axes[1], sharex=False, sharey=False)
+ _check_axes_shape(returned, axes_num=3, layout=(1, 3))
+ assert returned.shape == (3,)
+ assert returned[0].figure is fig
+ _check_axes_shape(axes, axes_num=6, layout=(2, 3))
+
+ def test_subplots_multiple_axes_error(self):
+ # GH 5353, 6970, GH 7069
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=list(string.ascii_letters[:10]),
+ )
+ msg = "The number of passed axes must be 3, the same as the output plot"
+ _, axes = mpl.pyplot.subplots(2, 3)
+
+ with pytest.raises(ValueError, match=msg):
+ # pass different number of axes from required
+ df.plot(subplots=True, ax=axes)
+
+ @pytest.mark.parametrize(
+ "layout, exp_layout",
+ [
+ [(2, 1), (2, 2)],
+ [(2, -1), (2, 2)],
+ [(-1, 2), (2, 2)],
+ ],
+ )
+ def test_subplots_multiple_axes_2_dim(self, layout, exp_layout):
+ # GH 5353, 6970, GH 7069
+ # pass 2-dim axes and invalid layout
+ # invalid layout should not affect to input and return value
+ # (show warning is tested in
+ # TestDataFrameGroupByPlots.test_grouped_box_multiple_axes
+ _, axes = mpl.pyplot.subplots(2, 2)
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 4)),
+ index=list(string.ascii_letters[:10]),
+ )
+ with tm.assert_produces_warning(UserWarning, match="layout keyword is ignored"):
+ returned = df.plot(
+ subplots=True, ax=axes, layout=layout, sharex=False, sharey=False
+ )
+ _check_axes_shape(returned, axes_num=4, layout=exp_layout)
+ assert returned.shape == (4,)
+
+ def test_subplots_multiple_axes_single_col(self):
+ # GH 5353, 6970, GH 7069
+ # single column
+ _, axes = mpl.pyplot.subplots(1, 1)
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 1)),
+ index=list(string.ascii_letters[:10]),
+ )
+
+ axes = df.plot(subplots=True, ax=[axes], sharex=False, sharey=False)
+ _check_axes_shape(axes, axes_num=1, layout=(1, 1))
+ assert axes.shape == (1,)
+
+ def test_subplots_ts_share_axes(self):
+ # GH 3964
+ _, axes = mpl.pyplot.subplots(3, 3, sharex=True, sharey=True)
+ mpl.pyplot.subplots_adjust(left=0.05, right=0.95, hspace=0.3, wspace=0.3)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 9)),
+ index=date_range(start="2014-07-01", freq="ME", periods=10),
+ )
+ for i, ax in enumerate(axes.ravel()):
+ df[i].plot(ax=ax, fontsize=5)
+
+ # Rows other than bottom should not be visible
+ for ax in axes[0:-1].ravel():
+ _check_visible(ax.get_xticklabels(), visible=False)
+
+ # Bottom row should be visible
+ for ax in axes[-1].ravel():
+ _check_visible(ax.get_xticklabels(), visible=True)
+
+ # First column should be visible
+ for ax in axes[[0, 1, 2], [0]].ravel():
+ _check_visible(ax.get_yticklabels(), visible=True)
+
+ # Other columns should not be visible
+ for ax in axes[[0, 1, 2], [1]].ravel():
+ _check_visible(ax.get_yticklabels(), visible=False)
+ for ax in axes[[0, 1, 2], [2]].ravel():
+ _check_visible(ax.get_yticklabels(), visible=False)
+
+ def test_subplots_sharex_axes_existing_axes(self):
+ # GH 9158
+ d = {"A": [1.0, 2.0, 3.0, 4.0], "B": [4.0, 3.0, 2.0, 1.0], "C": [5, 1, 3, 4]}
+ df = DataFrame(d, index=date_range("2014 10 11", "2014 10 14"))
+
+ axes = df[["A", "B"]].plot(subplots=True)
+ df["C"].plot(ax=axes[0], secondary_y=True)
+
+ _check_visible(axes[0].get_xticklabels(), visible=False)
+ _check_visible(axes[1].get_xticklabels(), visible=True)
+ for ax in axes.ravel():
+ _check_visible(ax.get_yticklabels(), visible=True)
+
+ def test_subplots_dup_columns(self):
+ # GH 10962
+ df = DataFrame(np.random.default_rng(2).random((5, 5)), columns=list("aaaaa"))
+ axes = df.plot(subplots=True)
+ for ax in axes:
+ _check_legend_labels(ax, labels=["a"])
+ assert len(ax.lines) == 1
+
+ def test_subplots_dup_columns_secondary_y(self):
+ # GH 10962
+ df = DataFrame(np.random.default_rng(2).random((5, 5)), columns=list("aaaaa"))
+ axes = df.plot(subplots=True, secondary_y="a")
+ for ax in axes:
+ # (right) is only attached when subplots=False
+ _check_legend_labels(ax, labels=["a"])
+ assert len(ax.lines) == 1
+
+ def test_subplots_dup_columns_secondary_y_no_subplot(self):
+ # GH 10962
+ df = DataFrame(np.random.default_rng(2).random((5, 5)), columns=list("aaaaa"))
+ ax = df.plot(secondary_y="a")
+ _check_legend_labels(ax, labels=["a (right)"] * 5)
+ assert len(ax.lines) == 0
+ assert len(ax.right_ax.lines) == 5
+
+ @pytest.mark.xfail(
+ is_platform_linux(),
+ reason="Weird rounding problems",
+ strict=False,
+ )
+ def test_bar_log_no_subplots(self):
+ # GH3254, GH3298 matplotlib/matplotlib#1882, #1892
+ # regressions in 1.2.1
+ expected = np.array([0.1, 1.0, 10.0, 100])
+
+ # no subplots
+ df = DataFrame({"A": [3] * 5, "B": list(range(1, 6))}, index=range(5))
+ ax = df.plot.bar(grid=True, log=True)
+ tm.assert_numpy_array_equal(ax.yaxis.get_ticklocs(), expected)
+
+ @pytest.mark.xfail(
+ is_platform_linux(),
+ reason="Weird rounding problems",
+ strict=False,
+ )
+ def test_bar_log_subplots(self):
+ expected = np.array([0.1, 1.0, 10.0, 100.0, 1000.0, 1e4])
+
+ ax = DataFrame([Series([200, 300]), Series([300, 500])]).plot.bar(
+ log=True, subplots=True
+ )
+
+ tm.assert_numpy_array_equal(ax[0].yaxis.get_ticklocs(), expected)
+ tm.assert_numpy_array_equal(ax[1].yaxis.get_ticklocs(), expected)
+
+ def test_boxplot_subplots_return_type_default(self, hist_df):
+ df = hist_df
+
+ # normal style: return_type=None
+ result = df.plot.box(subplots=True)
+ assert isinstance(result, Series)
+ _check_box_return_type(
+ result, None, expected_keys=["height", "weight", "category"]
+ )
+
+ @pytest.mark.parametrize("rt", ["dict", "axes", "both"])
+ def test_boxplot_subplots_return_type(self, hist_df, rt):
+ df = hist_df
+ returned = df.plot.box(return_type=rt, subplots=True)
+ _check_box_return_type(
+ returned,
+ rt,
+ expected_keys=["height", "weight", "category"],
+ check_ax_title=False,
+ )
+
+ def test_df_subplots_patterns_minorticks(self):
+ # GH 10657
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ index=date_range("1/1/2000", periods=10),
+ columns=list("AB"),
+ )
+
+ # shared subplots
+ _, axes = plt.subplots(2, 1, sharex=True)
+ axes = df.plot(subplots=True, ax=axes)
+ for ax in axes:
+ assert len(ax.lines) == 1
+ _check_visible(ax.get_yticklabels(), visible=True)
+ # xaxis of 1st ax must be hidden
+ _check_visible(axes[0].get_xticklabels(), visible=False)
+ _check_visible(axes[0].get_xticklabels(minor=True), visible=False)
+ _check_visible(axes[1].get_xticklabels(), visible=True)
+ _check_visible(axes[1].get_xticklabels(minor=True), visible=True)
+
+ def test_df_subplots_patterns_minorticks_1st_ax_hidden(self):
+ # GH 10657
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ index=date_range("1/1/2000", periods=10),
+ columns=list("AB"),
+ )
+ _, axes = plt.subplots(2, 1)
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ axes = df.plot(subplots=True, ax=axes, sharex=True)
+ for ax in axes:
+ assert len(ax.lines) == 1
+ _check_visible(ax.get_yticklabels(), visible=True)
+ # xaxis of 1st ax must be hidden
+ _check_visible(axes[0].get_xticklabels(), visible=False)
+ _check_visible(axes[0].get_xticklabels(minor=True), visible=False)
+ _check_visible(axes[1].get_xticklabels(), visible=True)
+ _check_visible(axes[1].get_xticklabels(minor=True), visible=True)
+
+ def test_df_subplots_patterns_minorticks_not_shared(self):
+ # GH 10657
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ index=date_range("1/1/2000", periods=10),
+ columns=list("AB"),
+ )
+ # not shared
+ _, axes = plt.subplots(2, 1)
+ axes = df.plot(subplots=True, ax=axes)
+ for ax in axes:
+ assert len(ax.lines) == 1
+ _check_visible(ax.get_yticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(), visible=True)
+ _check_visible(ax.get_xticklabels(minor=True), visible=True)
+
+ def test_subplots_sharex_false(self):
+ # test when sharex is set to False, two plots should have different
+ # labels, GH 25160
+ df = DataFrame(np.random.default_rng(2).random((10, 2)))
+ df.iloc[5:, 1] = np.nan
+ df.iloc[:5, 0] = np.nan
+
+ _, axs = mpl.pyplot.subplots(2, 1)
+ df.plot.line(ax=axs, subplots=True, sharex=False)
+
+ expected_ax1 = np.arange(4.5, 10, 0.5)
+ expected_ax2 = np.arange(-0.5, 5, 0.5)
+
+ tm.assert_numpy_array_equal(axs[0].get_xticks(), expected_ax1)
+ tm.assert_numpy_array_equal(axs[1].get_xticks(), expected_ax2)
+
+ def test_subplots_constrained_layout(self, temp_file):
+ # GH 25261
+ idx = date_range(start="now", periods=10)
+ df = DataFrame(np.random.default_rng(2).random((10, 3)), index=idx)
+ kwargs = {}
+ if hasattr(mpl.pyplot.Figure, "get_constrained_layout"):
+ kwargs["constrained_layout"] = True
+ _, axes = mpl.pyplot.subplots(2, **kwargs)
+ with tm.assert_produces_warning(None):
+ df.plot(ax=axes[0])
+ with temp_file.open(mode="wb") as path:
+ mpl.pyplot.savefig(path)
+
+ @pytest.mark.parametrize(
+ "index_name, old_label, new_label",
+ [
+ (None, "", "new"),
+ ("old", "old", "new"),
+ (None, "", ""),
+ (None, "", 1),
+ (None, "", [1, 2]),
+ ],
+ )
+ @pytest.mark.parametrize("kind", ["line", "area", "bar"])
+ def test_xlabel_ylabel_dataframe_subplots(
+ self, kind, index_name, old_label, new_label
+ ):
+ # GH 9093
+ df = DataFrame([[1, 2], [2, 5]], columns=["Type A", "Type B"])
+ df.index.name = index_name
+
+ # default is the ylabel is not shown and xlabel is index name
+ axes = df.plot(kind=kind, subplots=True)
+ assert all(ax.get_ylabel() == "" for ax in axes)
+ assert all(ax.get_xlabel() == old_label for ax in axes)
+
+ # old xlabel will be overridden and assigned ylabel will be used as ylabel
+ axes = df.plot(kind=kind, ylabel=new_label, xlabel=new_label, subplots=True)
+ assert all(ax.get_ylabel() == str(new_label) for ax in axes)
+ assert all(ax.get_xlabel() == str(new_label) for ax in axes)
+
+ @pytest.mark.parametrize(
+ "kwargs",
+ [
+ # stacked center
+ {"kind": "bar", "stacked": True},
+ {"kind": "bar", "stacked": True, "width": 0.9},
+ {"kind": "barh", "stacked": True},
+ {"kind": "barh", "stacked": True, "width": 0.9},
+ # center
+ {"kind": "bar", "stacked": False},
+ {"kind": "bar", "stacked": False, "width": 0.9},
+ {"kind": "barh", "stacked": False},
+ {"kind": "barh", "stacked": False, "width": 0.9},
+ # subplots center
+ {"kind": "bar", "subplots": True},
+ {"kind": "bar", "subplots": True, "width": 0.9},
+ {"kind": "barh", "subplots": True},
+ {"kind": "barh", "subplots": True, "width": 0.9},
+ # align edge
+ {"kind": "bar", "stacked": True, "align": "edge"},
+ {"kind": "bar", "stacked": True, "width": 0.9, "align": "edge"},
+ {"kind": "barh", "stacked": True, "align": "edge"},
+ {"kind": "barh", "stacked": True, "width": 0.9, "align": "edge"},
+ {"kind": "bar", "stacked": False, "align": "edge"},
+ {"kind": "bar", "stacked": False, "width": 0.9, "align": "edge"},
+ {"kind": "barh", "stacked": False, "align": "edge"},
+ {"kind": "barh", "stacked": False, "width": 0.9, "align": "edge"},
+ {"kind": "bar", "subplots": True, "align": "edge"},
+ {"kind": "bar", "subplots": True, "width": 0.9, "align": "edge"},
+ {"kind": "barh", "subplots": True, "align": "edge"},
+ {"kind": "barh", "subplots": True, "width": 0.9, "align": "edge"},
+ ],
+ )
+ def test_bar_align_multiple_columns(self, kwargs):
+ # GH2157
+ df = DataFrame({"A": [3] * 5, "B": list(range(5))}, index=range(5))
+ self._check_bar_alignment(df, **kwargs)
+
+ @pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"kind": "bar", "stacked": False},
+ {"kind": "bar", "stacked": True},
+ {"kind": "barh", "stacked": False},
+ {"kind": "barh", "stacked": True},
+ {"kind": "bar", "subplots": True},
+ {"kind": "barh", "subplots": True},
+ ],
+ )
+ def test_bar_align_single_column(self, kwargs):
+ df = DataFrame(np.random.default_rng(2).standard_normal(5))
+ self._check_bar_alignment(df, **kwargs)
+
+ @pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"kind": "bar", "stacked": False},
+ {"kind": "bar", "stacked": True},
+ {"kind": "barh", "stacked": False},
+ {"kind": "barh", "stacked": True},
+ {"kind": "bar", "subplots": True},
+ {"kind": "barh", "subplots": True},
+ ],
+ )
+ def test_bar_barwidth_position(self, kwargs):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ self._check_bar_alignment(df, width=0.9, position=0.2, **kwargs)
+
+ @pytest.mark.parametrize("w", [1, 1.0])
+ def test_bar_barwidth_position_int(self, w):
+ # GH 12979
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ ax = df.plot.bar(stacked=True, width=w)
+ ticks = ax.xaxis.get_ticklocs()
+ tm.assert_numpy_array_equal(ticks, np.array([0, 1, 2, 3, 4]))
+ assert ax.get_xlim() == (-0.75, 4.75)
+ # check left-edge of bars
+ assert ax.patches[0].get_x() == -0.5
+ assert ax.patches[-1].get_x() == 3.5
+
+ @pytest.mark.parametrize(
+ "kind, kwargs",
+ [
+ ["bar", {"stacked": True}],
+ ["barh", {"stacked": False}],
+ ["barh", {"stacked": True}],
+ ["bar", {"subplots": True}],
+ ["barh", {"subplots": True}],
+ ],
+ )
+ def test_bar_barwidth_position_int_width_1(self, kind, kwargs):
+ # GH 12979
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ self._check_bar_alignment(df, kind=kind, width=1, **kwargs)
+
+ def _check_bar_alignment(
+ self,
+ df,
+ kind="bar",
+ stacked=False,
+ subplots=False,
+ align="center",
+ width=0.5,
+ position=0.5,
+ ):
+ axes = df.plot(
+ kind=kind,
+ stacked=stacked,
+ subplots=subplots,
+ align=align,
+ width=width,
+ position=position,
+ grid=True,
+ )
+
+ axes = _flatten_visible(axes)
+
+ for ax in axes:
+ if kind == "bar":
+ axis = ax.xaxis
+ ax_min, ax_max = ax.get_xlim()
+ min_edge = min(p.get_x() for p in ax.patches)
+ max_edge = max(p.get_x() + p.get_width() for p in ax.patches)
+ elif kind == "barh":
+ axis = ax.yaxis
+ ax_min, ax_max = ax.get_ylim()
+ min_edge = min(p.get_y() for p in ax.patches)
+ max_edge = max(p.get_y() + p.get_height() for p in ax.patches)
+ else:
+ raise ValueError
+
+ # GH 7498
+ # compare margins between lim and bar edges
+ tm.assert_almost_equal(ax_min, min_edge - 0.25)
+ tm.assert_almost_equal(ax_max, max_edge + 0.25)
+
+ p = ax.patches[0]
+ if kind == "bar" and (stacked is True or subplots is True):
+ edge = p.get_x()
+ center = edge + p.get_width() * position
+ elif kind == "bar" and stacked is False:
+ center = p.get_x() + p.get_width() * len(df.columns) * position
+ edge = p.get_x()
+ elif kind == "barh" and (stacked is True or subplots is True):
+ center = p.get_y() + p.get_height() * position
+ edge = p.get_y()
+ elif kind == "barh" and stacked is False:
+ center = p.get_y() + p.get_height() * len(df.columns) * position
+ edge = p.get_y()
+ else:
+ raise ValueError
+
+ # Check the ticks locates on integer
+ assert (axis.get_ticklocs() == np.arange(len(df))).all()
+
+ if align == "center":
+ # Check whether the bar locates on center
+ tm.assert_almost_equal(axis.get_ticklocs()[0], center)
+ elif align == "edge":
+ # Check whether the bar's edge starts from the tick
+ tm.assert_almost_equal(axis.get_ticklocs()[0], edge)
+ else:
+ raise ValueError
+
+ return axes
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_hist_box_by.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_hist_box_by.py
new file mode 100644
index 0000000000000000000000000000000000000000..a9250fa8347cc04fa34c28b016e1fb27d837284f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/frame/test_hist_box_by.py
@@ -0,0 +1,342 @@
+import re
+
+import numpy as np
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+from pandas.tests.plotting.common import (
+ _check_axes_shape,
+ _check_plot_works,
+ get_x_axis,
+ get_y_axis,
+)
+
+pytest.importorskip("matplotlib")
+
+
+@pytest.fixture
+def hist_df():
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((30, 2)), columns=["A", "B"]
+ )
+ df["C"] = np.random.default_rng(2).choice(["a", "b", "c"], 30)
+ df["D"] = np.random.default_rng(2).choice(["a", "b", "c"], 30)
+ return df
+
+
+class TestHistWithBy:
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "by, column, titles, legends",
+ [
+ ("C", "A", ["a", "b", "c"], [["A"]] * 3),
+ ("C", ["A", "B"], ["a", "b", "c"], [["A", "B"]] * 3),
+ ("C", None, ["a", "b", "c"], [["A", "B"]] * 3),
+ (
+ ["C", "D"],
+ "A",
+ [
+ "(a, a)",
+ "(b, b)",
+ "(c, c)",
+ ],
+ [["A"]] * 3,
+ ),
+ (
+ ["C", "D"],
+ ["A", "B"],
+ [
+ "(a, a)",
+ "(b, b)",
+ "(c, c)",
+ ],
+ [["A", "B"]] * 3,
+ ),
+ (
+ ["C", "D"],
+ None,
+ [
+ "(a, a)",
+ "(b, b)",
+ "(c, c)",
+ ],
+ [["A", "B"]] * 3,
+ ),
+ ],
+ )
+ def test_hist_plot_by_argument(self, by, column, titles, legends, hist_df):
+ # GH 15079
+ axes = _check_plot_works(
+ hist_df.plot.hist, column=column, by=by, default_axes=True
+ )
+ result_titles = [ax.get_title() for ax in axes]
+ result_legends = [
+ [legend.get_text() for legend in ax.get_legend().texts] for ax in axes
+ ]
+
+ assert result_legends == legends
+ assert result_titles == titles
+
+ @pytest.mark.parametrize(
+ "by, column, titles, legends",
+ [
+ (0, "A", ["a", "b", "c"], [["A"]] * 3),
+ (0, None, ["a", "b", "c"], [["A", "B"]] * 3),
+ (
+ [0, "D"],
+ "A",
+ [
+ "(a, a)",
+ "(b, b)",
+ "(c, c)",
+ ],
+ [["A"]] * 3,
+ ),
+ ],
+ )
+ def test_hist_plot_by_0(self, by, column, titles, legends, hist_df):
+ # GH 15079
+ df = hist_df.copy()
+ df = df.rename(columns={"C": 0})
+
+ axes = _check_plot_works(df.plot.hist, default_axes=True, column=column, by=by)
+ result_titles = [ax.get_title() for ax in axes]
+ result_legends = [
+ [legend.get_text() for legend in ax.get_legend().texts] for ax in axes
+ ]
+
+ assert result_legends == legends
+ assert result_titles == titles
+
+ @pytest.mark.parametrize(
+ "by, column",
+ [
+ ([], ["A"]),
+ ([], ["A", "B"]),
+ ((), None),
+ ((), ["A", "B"]),
+ ],
+ )
+ def test_hist_plot_empty_list_string_tuple_by(self, by, column, hist_df):
+ # GH 15079
+ msg = "No group keys passed"
+ with pytest.raises(ValueError, match=msg):
+ _check_plot_works(
+ hist_df.plot.hist, default_axes=True, column=column, by=by
+ )
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "by, column, layout, axes_num",
+ [
+ (["C"], "A", (2, 2), 3),
+ ("C", "A", (2, 2), 3),
+ (["C"], ["A"], (1, 3), 3),
+ ("C", None, (3, 1), 3),
+ ("C", ["A", "B"], (3, 1), 3),
+ (["C", "D"], "A", (9, 1), 3),
+ (["C", "D"], "A", (3, 3), 3),
+ (["C", "D"], ["A"], (5, 2), 3),
+ (["C", "D"], ["A", "B"], (9, 1), 3),
+ (["C", "D"], None, (9, 1), 3),
+ (["C", "D"], ["A", "B"], (5, 2), 3),
+ ],
+ )
+ def test_hist_plot_layout_with_by(self, by, column, layout, axes_num, hist_df):
+ # GH 15079
+ # _check_plot_works adds an ax so catch warning. see GH #13188
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(
+ hist_df.plot.hist, column=column, by=by, layout=layout
+ )
+ _check_axes_shape(axes, axes_num=axes_num, layout=layout)
+
+ @pytest.mark.parametrize(
+ "msg, by, layout",
+ [
+ ("larger than required size", ["C", "D"], (1, 1)),
+ (re.escape("Layout must be a tuple of (rows, columns)"), "C", (1,)),
+ ("At least one dimension of layout must be positive", "C", (-1, -1)),
+ ],
+ )
+ def test_hist_plot_invalid_layout_with_by_raises(self, msg, by, layout, hist_df):
+ # GH 15079, test if error is raised when invalid layout is given
+
+ with pytest.raises(ValueError, match=msg):
+ hist_df.plot.hist(column=["A", "B"], by=by, layout=layout)
+
+ @pytest.mark.slow
+ def test_axis_share_x_with_by(self, hist_df):
+ # GH 15079
+ ax1, ax2, ax3 = hist_df.plot.hist(column="A", by="C", sharex=True)
+
+ # share x
+ assert get_x_axis(ax1).joined(ax1, ax2)
+ assert get_x_axis(ax2).joined(ax1, ax2)
+ assert get_x_axis(ax3).joined(ax1, ax3)
+ assert get_x_axis(ax3).joined(ax2, ax3)
+
+ # don't share y
+ assert not get_y_axis(ax1).joined(ax1, ax2)
+ assert not get_y_axis(ax2).joined(ax1, ax2)
+ assert not get_y_axis(ax3).joined(ax1, ax3)
+ assert not get_y_axis(ax3).joined(ax2, ax3)
+
+ @pytest.mark.slow
+ def test_axis_share_y_with_by(self, hist_df):
+ # GH 15079
+ ax1, ax2, ax3 = hist_df.plot.hist(column="A", by="C", sharey=True)
+
+ # share y
+ assert get_y_axis(ax1).joined(ax1, ax2)
+ assert get_y_axis(ax2).joined(ax1, ax2)
+ assert get_y_axis(ax3).joined(ax1, ax3)
+ assert get_y_axis(ax3).joined(ax2, ax3)
+
+ # don't share x
+ assert not get_x_axis(ax1).joined(ax1, ax2)
+ assert not get_x_axis(ax2).joined(ax1, ax2)
+ assert not get_x_axis(ax3).joined(ax1, ax3)
+ assert not get_x_axis(ax3).joined(ax2, ax3)
+
+ @pytest.mark.parametrize("figsize", [(12, 8), (20, 10)])
+ def test_figure_shape_hist_with_by(self, figsize, hist_df):
+ # GH 15079
+ axes = hist_df.plot.hist(column="A", by="C", figsize=figsize)
+ _check_axes_shape(axes, axes_num=3, figsize=figsize)
+
+
+class TestBoxWithBy:
+ @pytest.mark.parametrize(
+ "by, column, titles, xticklabels",
+ [
+ ("C", "A", ["A"], [["a", "b", "c"]]),
+ (
+ ["C", "D"],
+ "A",
+ ["A"],
+ [
+ [
+ "(a, a)",
+ "(b, b)",
+ "(c, c)",
+ ]
+ ],
+ ),
+ ("C", ["A", "B"], ["A", "B"], [["a", "b", "c"]] * 2),
+ (
+ ["C", "D"],
+ ["A", "B"],
+ ["A", "B"],
+ [
+ [
+ "(a, a)",
+ "(b, b)",
+ "(c, c)",
+ ]
+ ]
+ * 2,
+ ),
+ (["C"], None, ["A", "B"], [["a", "b", "c"]] * 2),
+ ],
+ )
+ def test_box_plot_by_argument(self, by, column, titles, xticklabels, hist_df):
+ # GH 15079
+ axes = _check_plot_works(
+ hist_df.plot.box, default_axes=True, column=column, by=by
+ )
+ result_titles = [ax.get_title() for ax in axes]
+ result_xticklabels = [
+ [label.get_text() for label in ax.get_xticklabels()] for ax in axes
+ ]
+
+ assert result_xticklabels == xticklabels
+ assert result_titles == titles
+
+ @pytest.mark.parametrize(
+ "by, column, titles, xticklabels",
+ [
+ (0, "A", ["A"], [["a", "b", "c"]]),
+ (
+ [0, "D"],
+ "A",
+ ["A"],
+ [
+ [
+ "(a, a)",
+ "(b, b)",
+ "(c, c)",
+ ]
+ ],
+ ),
+ (0, None, ["A", "B"], [["a", "b", "c"]] * 2),
+ ],
+ )
+ def test_box_plot_by_0(self, by, column, titles, xticklabels, hist_df):
+ # GH 15079
+ df = hist_df.copy()
+ df = df.rename(columns={"C": 0})
+
+ axes = _check_plot_works(df.plot.box, default_axes=True, column=column, by=by)
+ result_titles = [ax.get_title() for ax in axes]
+ result_xticklabels = [
+ [label.get_text() for label in ax.get_xticklabels()] for ax in axes
+ ]
+
+ assert result_xticklabels == xticklabels
+ assert result_titles == titles
+
+ @pytest.mark.parametrize(
+ "by, column",
+ [
+ ([], ["A"]),
+ ((), "A"),
+ ([], None),
+ ((), ["A", "B"]),
+ ],
+ )
+ def test_box_plot_with_none_empty_list_by(self, by, column, hist_df):
+ # GH 15079
+ msg = "No group keys passed"
+ with pytest.raises(ValueError, match=msg):
+ _check_plot_works(hist_df.plot.box, default_axes=True, column=column, by=by)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "by, column, layout, axes_num",
+ [
+ (["C"], "A", (1, 1), 1),
+ ("C", "A", (1, 1), 1),
+ ("C", None, (2, 1), 2),
+ ("C", ["A", "B"], (1, 2), 2),
+ (["C", "D"], "A", (1, 1), 1),
+ (["C", "D"], None, (1, 2), 2),
+ ],
+ )
+ def test_box_plot_layout_with_by(self, by, column, layout, axes_num, hist_df):
+ # GH 15079
+ axes = _check_plot_works(
+ hist_df.plot.box, default_axes=True, column=column, by=by, layout=layout
+ )
+ _check_axes_shape(axes, axes_num=axes_num, layout=layout)
+
+ @pytest.mark.parametrize(
+ "msg, by, layout",
+ [
+ ("larger than required size", ["C", "D"], (1, 1)),
+ (re.escape("Layout must be a tuple of (rows, columns)"), "C", (1,)),
+ ("At least one dimension of layout must be positive", "C", (-1, -1)),
+ ],
+ )
+ def test_box_plot_invalid_layout_with_by_raises(self, msg, by, layout, hist_df):
+ # GH 15079, test if error is raised when invalid layout is given
+
+ with pytest.raises(ValueError, match=msg):
+ hist_df.plot.box(column=["A", "B"], by=by, layout=layout)
+
+ @pytest.mark.parametrize("figsize", [(12, 8), (20, 10)])
+ def test_figure_shape_hist_with_by(self, figsize, hist_df):
+ # GH 15079
+ axes = hist_df.plot.box(column="A", by="C", figsize=figsize)
+ _check_axes_shape(axes, axes_num=1, figsize=figsize)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_backend.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_backend.py
new file mode 100644
index 0000000000000000000000000000000000000000..683bfcfe54f3ed45567c77b636119973ac910f38
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_backend.py
@@ -0,0 +1,100 @@
+import sys
+import types
+
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas
+
+pytestmark = pytest.mark.single_cpu
+
+
+@pytest.fixture
+def dummy_backend():
+ db = types.ModuleType("pandas_dummy_backend")
+ setattr(db, "plot", lambda *args, **kwargs: "used_dummy")
+ return db
+
+
+@pytest.fixture
+def restore_backend():
+ """Restore the plotting backend to matplotlib"""
+ with pandas.option_context("plotting.backend", "matplotlib"):
+ yield
+
+
+def test_backend_is_not_module():
+ msg = "Could not find plotting backend 'not_an_existing_module'."
+ with pytest.raises(ValueError, match=msg):
+ pandas.set_option("plotting.backend", "not_an_existing_module")
+
+ assert pandas.options.plotting.backend == "matplotlib"
+
+
+def test_backend_is_correct(monkeypatch, restore_backend, dummy_backend):
+ monkeypatch.setitem(sys.modules, "pandas_dummy_backend", dummy_backend)
+
+ pandas.set_option("plotting.backend", "pandas_dummy_backend")
+ assert pandas.get_option("plotting.backend") == "pandas_dummy_backend"
+ assert (
+ pandas.plotting._core._get_plot_backend("pandas_dummy_backend") is dummy_backend
+ )
+
+
+def test_backend_can_be_set_in_plot_call(monkeypatch, restore_backend, dummy_backend):
+ monkeypatch.setitem(sys.modules, "pandas_dummy_backend", dummy_backend)
+ df = pandas.DataFrame([1, 2, 3])
+
+ assert pandas.get_option("plotting.backend") == "matplotlib"
+ assert df.plot(backend="pandas_dummy_backend") == "used_dummy"
+
+
+def test_register_entrypoint(restore_backend, tmp_path, monkeypatch, dummy_backend):
+ monkeypatch.syspath_prepend(tmp_path)
+ monkeypatch.setitem(sys.modules, "pandas_dummy_backend", dummy_backend)
+
+ dist_info = tmp_path / "my_backend-0.0.0.dist-info"
+ dist_info.mkdir()
+ # entry_point name should not match module name - otherwise pandas will
+ # fall back to backend lookup by module name
+ (dist_info / "entry_points.txt").write_bytes(
+ b"[pandas_plotting_backends]\nmy_ep_backend = pandas_dummy_backend\n"
+ )
+
+ assert pandas.plotting._core._get_plot_backend("my_ep_backend") is dummy_backend
+
+ with pandas.option_context("plotting.backend", "my_ep_backend"):
+ assert pandas.plotting._core._get_plot_backend() is dummy_backend
+
+
+def test_setting_backend_without_plot_raises(monkeypatch):
+ # GH-28163
+ module = types.ModuleType("pandas_plot_backend")
+ monkeypatch.setitem(sys.modules, "pandas_plot_backend", module)
+
+ assert pandas.options.plotting.backend == "matplotlib"
+ with pytest.raises(
+ ValueError, match="Could not find plotting backend 'pandas_plot_backend'."
+ ):
+ pandas.set_option("plotting.backend", "pandas_plot_backend")
+
+ assert pandas.options.plotting.backend == "matplotlib"
+
+
+@td.skip_if_installed("matplotlib")
+def test_no_matplotlib_ok():
+ msg = (
+ 'matplotlib is required for plotting when the default backend "matplotlib" is '
+ "selected."
+ )
+ with pytest.raises(ImportError, match=msg):
+ pandas.plotting._core._get_plot_backend("matplotlib")
+
+
+def test_extra_kinds_ok(monkeypatch, restore_backend, dummy_backend):
+ # https://github.com/pandas-dev/pandas/pull/28647
+ monkeypatch.setitem(sys.modules, "pandas_dummy_backend", dummy_backend)
+ pandas.set_option("plotting.backend", "pandas_dummy_backend")
+ df = pandas.DataFrame({"A": [1, 2, 3]})
+ df.plot(kind="not a real kind")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_boxplot_method.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_boxplot_method.py
new file mode 100644
index 0000000000000000000000000000000000000000..3554f1549e4889660340eb0ea2c41149e55df9a0
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_boxplot_method.py
@@ -0,0 +1,774 @@
+"""Test cases for .boxplot method"""
+
+from __future__ import annotations
+
+import itertools
+import string
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ MultiIndex,
+ Series,
+ date_range,
+ plotting,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.tests.plotting.common import (
+ _check_axes_shape,
+ _check_box_return_type,
+ _check_plot_works,
+ _check_ticks_props,
+ _check_visible,
+)
+from pandas.util.version import Version
+
+from pandas.io.formats.printing import pprint_thing
+
+mpl = pytest.importorskip("matplotlib")
+plt = pytest.importorskip("matplotlib.pyplot")
+
+
+def _check_ax_limits(col, ax):
+ y_min, y_max = ax.get_ylim()
+ assert y_min <= col.min()
+ assert y_max >= col.max()
+
+
+if Version(mpl.__version__) < Version("3.10"):
+ verts: list[dict[str, bool | str]] = [{"vert": False}, {"vert": True}]
+else:
+ verts = [{"orientation": "horizontal"}, {"orientation": "vertical"}]
+
+
+@pytest.fixture(params=verts)
+def vert(request):
+ return request.param
+
+
+class TestDataFramePlots:
+ def test_stacked_boxplot_set_axis(self):
+ # GH2980
+ n = 30
+ df = DataFrame(
+ {
+ "Clinical": np.random.default_rng(2).choice([0, 1, 2, 3], n),
+ "Confirmed": np.random.default_rng(2).choice([0, 1, 2, 3], n),
+ "Discarded": np.random.default_rng(2).choice([0, 1, 2, 3], n),
+ },
+ index=np.arange(0, n),
+ )
+ ax = df.plot(kind="bar", stacked=True)
+ assert [int(x.get_text()) for x in ax.get_xticklabels()] == df.index.to_list()
+ ax.set_xticks(np.arange(0, n, 10))
+ plt.draw() # Update changes
+ assert [int(x.get_text()) for x in ax.get_xticklabels()] == list(
+ np.arange(0, n, 10)
+ )
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "kwargs, warn",
+ [
+ [{"return_type": "dict"}, None],
+ [{"column": ["one", "two"]}, None],
+ [{"column": ["one", "two"], "by": "indic"}, UserWarning],
+ [{"column": ["one"], "by": ["indic", "indic2"]}, None],
+ [{"by": "indic"}, UserWarning],
+ [{"by": ["indic", "indic2"]}, UserWarning],
+ [{"notch": 1}, None],
+ [{"by": "indic", "notch": 1}, UserWarning],
+ ],
+ )
+ def test_boxplot_legacy1(self, kwargs, warn):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["one", "two", "three", "four"],
+ )
+ df["indic"] = ["foo", "bar"] * 3
+ df["indic2"] = ["foo", "bar", "foo"] * 2
+
+ # _check_plot_works can add an ax so catch warning. see GH #13188
+ with tm.assert_produces_warning(warn, check_stacklevel=False):
+ _check_plot_works(df.boxplot, **kwargs)
+
+ def test_boxplot_legacy1_series(self):
+ ser = Series(np.random.default_rng(2).standard_normal(6))
+ _check_plot_works(plotting._core.boxplot, data=ser, return_type="dict")
+
+ def test_boxplot_legacy2(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
+ )
+ df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
+ df["Y"] = Series(["A"] * 10)
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ _check_plot_works(df.boxplot, by="X")
+
+ def test_boxplot_legacy2_with_ax(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
+ )
+ df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
+ df["Y"] = Series(["A"] * 10)
+ # When ax is supplied and required number of axes is 1,
+ # passed ax should be used:
+ _, ax = mpl.pyplot.subplots()
+ axes = df.boxplot("Col1", by="X", ax=ax)
+ ax_axes = ax.axes
+ assert ax_axes is axes
+
+ def test_boxplot_legacy2_with_ax_return_type(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
+ )
+ df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
+ df["Y"] = Series(["A"] * 10)
+ fig, ax = mpl.pyplot.subplots()
+ axes = df.groupby("Y").boxplot(ax=ax, return_type="axes")
+ ax_axes = ax.axes
+ assert ax_axes is axes["A"]
+
+ def test_boxplot_legacy2_with_multi_col(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
+ )
+ df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
+ df["Y"] = Series(["A"] * 10)
+ # Multiple columns with an ax argument should use same figure
+ fig, ax = mpl.pyplot.subplots()
+ msg = "the figure containing the passed axes is being cleared"
+ with tm.assert_produces_warning(UserWarning, match=msg):
+ axes = df.boxplot(
+ column=["Col1", "Col2"], by="X", ax=ax, return_type="axes"
+ )
+ assert axes["Col1"].get_figure() is fig
+
+ def test_boxplot_legacy2_by_none(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 2)), columns=["Col1", "Col2"]
+ )
+ df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"])
+ df["Y"] = Series(["A"] * 10)
+ # When by is None, check that all relevant lines are present in the
+ # dict
+ _, ax = mpl.pyplot.subplots()
+ d = df.boxplot(ax=ax, return_type="dict")
+ lines = list(itertools.chain.from_iterable(d.values()))
+ assert len(ax.get_lines()) == len(lines)
+
+ def test_boxplot_return_type_none(self, hist_df):
+ # GH 12216; return_type=None & by=None -> axes
+ result = hist_df.boxplot()
+ assert isinstance(result, mpl.pyplot.Axes)
+
+ def test_boxplot_return_type_legacy(self):
+ # API change in https://github.com/pandas-dev/pandas/pull/7096
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["one", "two", "three", "four"],
+ )
+ msg = "return_type must be {'axes', 'dict', 'both'}"
+ with pytest.raises(ValueError, match=msg):
+ df.boxplot(return_type="NOT_A_TYPE")
+
+ result = df.boxplot()
+ _check_box_return_type(result, "axes")
+
+ @pytest.mark.parametrize("return_type", ["dict", "axes", "both"])
+ def test_boxplot_return_type_legacy_return_type(self, return_type):
+ # API change in https://github.com/pandas-dev/pandas/pull/7096
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 4)),
+ index=list(string.ascii_letters[:6]),
+ columns=["one", "two", "three", "four"],
+ )
+ with tm.assert_produces_warning(False):
+ result = df.boxplot(return_type=return_type)
+ _check_box_return_type(result, return_type)
+
+ def test_boxplot_axis_limits(self, hist_df):
+ df = hist_df.copy()
+ df["age"] = np.random.default_rng(2).integers(1, 20, df.shape[0])
+ # One full row
+ height_ax, weight_ax = df.boxplot(["height", "weight"], by="category")
+ _check_ax_limits(df["height"], height_ax)
+ _check_ax_limits(df["weight"], weight_ax)
+ assert weight_ax._sharey == height_ax
+
+ def test_boxplot_axis_limits_two_rows(self, hist_df):
+ df = hist_df.copy()
+ df["age"] = np.random.default_rng(2).integers(1, 20, df.shape[0])
+ # Two rows, one partial
+ p = df.boxplot(["height", "weight", "age"], by="category")
+ height_ax, weight_ax, age_ax = p[0, 0], p[0, 1], p[1, 0]
+ dummy_ax = p[1, 1]
+
+ _check_ax_limits(df["height"], height_ax)
+ _check_ax_limits(df["weight"], weight_ax)
+ _check_ax_limits(df["age"], age_ax)
+ assert weight_ax._sharey == height_ax
+ assert age_ax._sharey == height_ax
+ assert dummy_ax._sharey is None
+
+ def test_boxplot_empty_column(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((20, 4)))
+ df.loc[:, 0] = np.nan
+ _check_plot_works(df.boxplot, return_type="axes")
+
+ def test_figsize(self):
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 5)), columns=["A", "B", "C", "D", "E"]
+ )
+ result = df.boxplot(return_type="axes", figsize=(12, 8))
+ assert result.figure.bbox_inches.width == 12
+ assert result.figure.bbox_inches.height == 8
+
+ def test_fontsize(self):
+ df = DataFrame({"a": [1, 2, 3, 4, 5, 6]})
+ _check_ticks_props(df.boxplot("a", fontsize=16), xlabelsize=16, ylabelsize=16)
+
+ def test_boxplot_numeric_data(self):
+ # GH 22799
+ df = DataFrame(
+ {
+ "a": date_range("2012-01-01", periods=10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ "c": np.random.default_rng(2).standard_normal(10) + 2,
+ "d": date_range("2012-01-01", periods=10).astype(str),
+ "e": date_range("2012-01-01", periods=10, tz="UTC"),
+ "f": timedelta_range("1 days", periods=10),
+ }
+ )
+ ax = df.plot(kind="box")
+ assert [x.get_text() for x in ax.get_xticklabels()] == ["b", "c"]
+
+ @pytest.mark.parametrize(
+ "colors_kwd, expected",
+ [
+ (
+ {"boxes": "r", "whiskers": "b", "medians": "g", "caps": "c"},
+ {"boxes": "r", "whiskers": "b", "medians": "g", "caps": "c"},
+ ),
+ ({"boxes": "r"}, {"boxes": "r"}),
+ ("r", {"boxes": "r", "whiskers": "r", "medians": "r", "caps": "r"}),
+ ],
+ )
+ def test_color_kwd(self, colors_kwd, expected):
+ # GH: 26214
+ df = DataFrame(np.random.default_rng(2).random((10, 2)))
+ result = df.boxplot(color=colors_kwd, return_type="dict")
+ for k, v in expected.items():
+ assert result[k][0].get_color() == v
+
+ @pytest.mark.parametrize(
+ "scheme,expected",
+ [
+ (
+ "dark_background",
+ {
+ "boxes": "#8dd3c7",
+ "whiskers": "#8dd3c7",
+ "medians": "#bfbbd9",
+ "caps": "#8dd3c7",
+ },
+ ),
+ (
+ "default",
+ {
+ "boxes": "#1f77b4",
+ "whiskers": "#1f77b4",
+ "medians": "#2ca02c",
+ "caps": "#1f77b4",
+ },
+ ),
+ ],
+ )
+ def test_colors_in_theme(self, scheme, expected):
+ # GH: 40769
+ df = DataFrame(np.random.default_rng(2).random((10, 2)))
+ plt.style.use(scheme)
+ result = df.plot.box(return_type="dict")
+ for k, v in expected.items():
+ assert result[k][0].get_color() == v
+
+ @pytest.mark.parametrize(
+ "dict_colors, msg",
+ [({"boxes": "r", "invalid_key": "r"}, "invalid key 'invalid_key'")],
+ )
+ def test_color_kwd_errors(self, dict_colors, msg):
+ # GH: 26214
+ df = DataFrame(np.random.default_rng(2).random((10, 2)))
+ with pytest.raises(ValueError, match=msg):
+ df.boxplot(color=dict_colors, return_type="dict")
+
+ @pytest.mark.parametrize(
+ "props, expected",
+ [
+ ("boxprops", "boxes"),
+ ("whiskerprops", "whiskers"),
+ ("capprops", "caps"),
+ ("medianprops", "medians"),
+ ],
+ )
+ def test_specified_props_kwd(self, props, expected):
+ # GH 30346
+ df = DataFrame({k: np.random.default_rng(2).random(10) for k in "ABC"})
+ kwd = {props: {"color": "C1"}}
+ result = df.boxplot(return_type="dict", **kwd)
+
+ assert result[expected][0].get_color() == "C1"
+
+ @pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
+ def test_plot_xlabel_ylabel(self, vert):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ "group": np.random.default_rng(2).choice(["group1", "group2"], 10),
+ }
+ )
+ xlabel, ylabel = "x", "y"
+ ax = df.plot(kind="box", xlabel=xlabel, ylabel=ylabel, **vert)
+ assert ax.get_xlabel() == xlabel
+ assert ax.get_ylabel() == ylabel
+
+ @pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
+ def test_plot_box(self, vert):
+ # GH 54941
+ rng = np.random.default_rng(2)
+ df1 = DataFrame(rng.integers(0, 100, size=(10, 4)), columns=list("ABCD"))
+ df2 = DataFrame(rng.integers(0, 100, size=(10, 4)), columns=list("ABCD"))
+
+ xlabel, ylabel = "x", "y"
+ _, axs = plt.subplots(ncols=2, figsize=(10, 7), sharey=True)
+ df1.plot.box(ax=axs[0], xlabel=xlabel, ylabel=ylabel, **vert)
+ df2.plot.box(ax=axs[1], xlabel=xlabel, ylabel=ylabel, **vert)
+ for ax in axs:
+ assert ax.get_xlabel() == xlabel
+ assert ax.get_ylabel() == ylabel
+
+ @pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
+ def test_boxplot_xlabel_ylabel(self, vert):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ "group": np.random.default_rng(2).choice(["group1", "group2"], 10),
+ }
+ )
+ xlabel, ylabel = "x", "y"
+ ax = df.boxplot(xlabel=xlabel, ylabel=ylabel, **vert)
+ assert ax.get_xlabel() == xlabel
+ assert ax.get_ylabel() == ylabel
+
+ @pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
+ def test_boxplot_group_xlabel_ylabel(self, vert):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ "group": np.random.default_rng(2).choice(["group1", "group2"], 10),
+ }
+ )
+ xlabel, ylabel = "x", "y"
+ ax = df.boxplot(by="group", xlabel=xlabel, ylabel=ylabel, **vert)
+ for subplot in ax:
+ assert subplot.get_xlabel() == xlabel
+ assert subplot.get_ylabel() == ylabel
+
+ @pytest.mark.filterwarnings("ignore:set_ticklabels:UserWarning")
+ def test_boxplot_group_no_xlabel_ylabel(self, vert, request):
+ if Version(mpl.__version__) >= Version("3.10") and vert == {
+ "orientation": "horizontal"
+ }:
+ request.applymarker(
+ pytest.mark.xfail(reason=f"{vert} fails starting with matplotlib 3.10")
+ )
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ "group": np.random.default_rng(2).choice(["group1", "group2"], 10),
+ }
+ )
+ ax = df.boxplot(by="group", **vert)
+ for subplot in ax:
+ target_label = (
+ subplot.get_xlabel()
+ if vert in ({"vert": True}, {"orientation": "vertical"})
+ else subplot.get_ylabel()
+ )
+ assert target_label == pprint_thing(["group"])
+
+
+class TestDataFrameGroupByPlots:
+ def test_boxplot_legacy1(self, hist_df):
+ grouped = hist_df.groupby(by="gender")
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(grouped.boxplot, return_type="axes")
+ _check_axes_shape(list(axes.values), axes_num=2, layout=(1, 2))
+
+ def test_boxplot_legacy1_return_type(self, hist_df):
+ grouped = hist_df.groupby(by="gender")
+ axes = _check_plot_works(grouped.boxplot, subplots=False, return_type="axes")
+ _check_axes_shape(axes, axes_num=1, layout=(1, 1))
+
+ @pytest.mark.slow
+ def test_boxplot_legacy2(self):
+ tuples = zip(string.ascii_letters[:10], range(10), strict=True)
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=MultiIndex.from_tuples(tuples),
+ )
+ grouped = df.groupby(level=1)
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(grouped.boxplot, return_type="axes")
+ _check_axes_shape(list(axes.values), axes_num=10, layout=(4, 3))
+
+ @pytest.mark.slow
+ def test_boxplot_legacy2_return_type(self):
+ tuples = zip(string.ascii_letters[:10], range(10), strict=True)
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 3)),
+ index=MultiIndex.from_tuples(tuples),
+ )
+ grouped = df.groupby(level=1)
+ axes = _check_plot_works(grouped.boxplot, subplots=False, return_type="axes")
+ _check_axes_shape(axes, axes_num=1, layout=(1, 1))
+
+ def test_grouped_plot_fignums(self):
+ n = 10
+ weight = Series(np.random.default_rng(2).normal(166, 20, size=n))
+ height = Series(np.random.default_rng(2).normal(60, 10, size=n))
+ gender = np.random.default_rng(2).choice(["male", "female"], size=n)
+ df = DataFrame({"height": height, "weight": weight, "gender": gender})
+ gb = df.groupby("gender")
+
+ res = gb.plot()
+ assert len(mpl.pyplot.get_fignums()) == 2
+ assert len(res) == 2
+ plt.close("all")
+
+ res = gb.boxplot(return_type="axes")
+ assert len(mpl.pyplot.get_fignums()) == 1
+ assert len(res) == 2
+
+ def test_grouped_plot_fignums_excluded_col(self):
+ n = 10
+ weight = Series(np.random.default_rng(2).normal(166, 20, size=n))
+ height = Series(np.random.default_rng(2).normal(60, 10, size=n))
+ gender = np.random.default_rng(2).choice(["male", "female"], size=n)
+ df = DataFrame({"height": height, "weight": weight, "gender": gender})
+ # now works with GH 5610 as gender is excluded
+ df.groupby("gender").hist()
+
+ @pytest.mark.slow
+ def test_grouped_box_return_type(self, hist_df):
+ df = hist_df
+
+ # old style: return_type=None
+ result = df.boxplot(by="gender")
+ assert isinstance(result, np.ndarray)
+ _check_box_return_type(
+ result, None, expected_keys=["height", "weight", "category"]
+ )
+
+ @pytest.mark.slow
+ def test_grouped_box_return_type_groupby(self, hist_df):
+ df = hist_df
+ # now for groupby
+ result = df.groupby("gender").boxplot(return_type="dict")
+ _check_box_return_type(result, "dict", expected_keys=["Male", "Female"])
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("return_type", ["dict", "axes", "both"])
+ def test_grouped_box_return_type_arg(self, hist_df, return_type):
+ df = hist_df
+
+ returned = df.groupby("classroom").boxplot(return_type=return_type)
+ _check_box_return_type(returned, return_type, expected_keys=["A", "B", "C"])
+
+ returned = df.boxplot(by="classroom", return_type=return_type)
+ _check_box_return_type(
+ returned, return_type, expected_keys=["height", "weight", "category"]
+ )
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("return_type", ["dict", "axes", "both"])
+ def test_grouped_box_return_type_arg_duplcate_cats(self, return_type):
+ columns2 = "X B C D A".split()
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((6, 5)), columns=columns2
+ )
+ categories2 = "A B".split()
+ df2["category"] = categories2 * 3
+
+ returned = df2.groupby("category").boxplot(return_type=return_type)
+ _check_box_return_type(returned, return_type, expected_keys=categories2)
+
+ returned = df2.boxplot(by="category", return_type=return_type)
+ _check_box_return_type(returned, return_type, expected_keys=columns2)
+
+ @pytest.mark.slow
+ def test_grouped_box_layout_too_small(self, hist_df):
+ df = hist_df
+
+ msg = "Layout of 1x1 must be larger than required size 2"
+ with pytest.raises(ValueError, match=msg):
+ df.boxplot(column=["weight", "height"], by=df.gender, layout=(1, 1))
+
+ @pytest.mark.slow
+ def test_grouped_box_layout_needs_by(self, hist_df):
+ df = hist_df
+ msg = "The 'layout' keyword is not supported when 'by' is None"
+ with pytest.raises(ValueError, match=msg):
+ df.boxplot(
+ column=["height", "weight", "category"],
+ layout=(2, 1),
+ return_type="dict",
+ )
+
+ @pytest.mark.slow
+ def test_grouped_box_layout_positive_layout(self, hist_df):
+ df = hist_df
+ msg = "At least one dimension of layout must be positive"
+ with pytest.raises(ValueError, match=msg):
+ df.boxplot(column=["weight", "height"], by=df.gender, layout=(-1, -1))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "gb_key, axes_num, rows",
+ [["gender", 2, 1], ["category", 4, 2], ["classroom", 3, 2]],
+ )
+ def test_grouped_box_layout_positive_layout_axes(
+ self, hist_df, gb_key, axes_num, rows
+ ):
+ df = hist_df
+ # _check_plot_works adds an ax so catch warning. see GH #13188 GH 6769
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ _check_plot_works(
+ df.groupby(gb_key).boxplot, column="height", return_type="dict"
+ )
+ _check_axes_shape(mpl.pyplot.gcf().axes, axes_num=axes_num, layout=(rows, 2))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "col, visible", [["height", False], ["weight", True], ["category", True]]
+ )
+ def test_grouped_box_layout_visible(self, hist_df, col, visible):
+ df = hist_df
+ # GH 5897
+ axes = df.boxplot(
+ column=["height", "weight", "category"], by="gender", return_type="axes"
+ )
+ _check_axes_shape(mpl.pyplot.gcf().axes, axes_num=3, layout=(2, 2))
+ ax = axes[col]
+ _check_visible(ax.get_xticklabels(), visible=visible)
+ _check_visible([ax.xaxis.get_label()], visible=visible)
+
+ @pytest.mark.slow
+ def test_grouped_box_layout_shape(self, hist_df):
+ df = hist_df
+ df.groupby("classroom").boxplot(
+ column=["height", "weight", "category"], return_type="dict"
+ )
+ _check_axes_shape(mpl.pyplot.gcf().axes, axes_num=3, layout=(2, 2))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("cols", [2, -1])
+ def test_grouped_box_layout_works(self, hist_df, cols):
+ df = hist_df
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ _check_plot_works(
+ df.groupby("category").boxplot,
+ column="height",
+ layout=(3, cols),
+ return_type="dict",
+ )
+ _check_axes_shape(mpl.pyplot.gcf().axes, axes_num=4, layout=(3, 2))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("rows, res", [[4, 4], [-1, 3]])
+ def test_grouped_box_layout_axes_shape_rows(self, hist_df, rows, res):
+ df = hist_df
+ df.boxplot(
+ column=["height", "weight", "category"], by="gender", layout=(rows, 1)
+ )
+ _check_axes_shape(mpl.pyplot.gcf().axes, axes_num=3, layout=(res, 1))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("cols, res", [[4, 4], [-1, 3]])
+ def test_grouped_box_layout_axes_shape_cols_groupby(self, hist_df, cols, res):
+ df = hist_df
+ df.groupby("classroom").boxplot(
+ column=["height", "weight", "category"],
+ layout=(1, cols),
+ return_type="dict",
+ )
+ _check_axes_shape(mpl.pyplot.gcf().axes, axes_num=3, layout=(1, res))
+
+ @pytest.mark.slow
+ def test_grouped_box_multiple_axes(self, hist_df):
+ # GH 6970, GH 7069
+ df = hist_df
+
+ # check warning to ignore sharex / sharey
+ # this check should be done in the first function which
+ # passes multiple axes to plot, hist or boxplot
+ # location should be changed if other test is added
+ # which has earlier alphabetical order
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ _, axes = mpl.pyplot.subplots(2, 2)
+ df.groupby("category").boxplot(column="height", return_type="axes", ax=axes)
+ _check_axes_shape(mpl.pyplot.gcf().axes, axes_num=4, layout=(2, 2))
+
+ @pytest.mark.slow
+ def test_grouped_box_multiple_axes_on_fig(self, hist_df):
+ # GH 6970, GH 7069
+ df = hist_df
+ fig, axes = mpl.pyplot.subplots(2, 3)
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ returned = df.boxplot(
+ column=["height", "weight", "category"],
+ by="gender",
+ return_type="axes",
+ ax=axes[0],
+ )
+ returned = np.array(list(returned.values))
+ _check_axes_shape(returned, axes_num=3, layout=(1, 3))
+ tm.assert_numpy_array_equal(returned, axes[0])
+ assert returned[0].figure is fig
+
+ # draw on second row
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ returned = df.groupby("classroom").boxplot(
+ column=["height", "weight", "category"], return_type="axes", ax=axes[1]
+ )
+ returned = np.array(list(returned.values))
+ _check_axes_shape(returned, axes_num=3, layout=(1, 3))
+ tm.assert_numpy_array_equal(returned, axes[1])
+ assert returned[0].figure is fig
+
+ @pytest.mark.slow
+ def test_grouped_box_multiple_axes_ax_error(self, hist_df):
+ # GH 6970, GH 7069
+ df = hist_df
+ msg = "The number of passed axes must be 3, the same as the output plot"
+ _, axes = mpl.pyplot.subplots(2, 3)
+ with pytest.raises(ValueError, match=msg):
+ # pass different number of axes from required
+ with tm.assert_produces_warning(UserWarning, match="sharex and sharey"):
+ axes = df.groupby("classroom").boxplot(ax=axes)
+
+ def test_fontsize(self):
+ df = DataFrame({"a": [1, 2, 3, 4, 5, 6], "b": [0, 0, 0, 1, 1, 1]})
+ _check_ticks_props(
+ df.boxplot("a", by="b", fontsize=16), xlabelsize=16, ylabelsize=16
+ )
+
+ @pytest.mark.parametrize(
+ "col, expected_xticklabel",
+ [
+ ("v", ["(a, v)", "(b, v)", "(c, v)", "(d, v)", "(e, v)"]),
+ (["v"], ["(a, v)", "(b, v)", "(c, v)", "(d, v)", "(e, v)"]),
+ ("v1", ["(a, v1)", "(b, v1)", "(c, v1)", "(d, v1)", "(e, v1)"]),
+ (
+ ["v", "v1"],
+ [
+ "(a, v)",
+ "(a, v1)",
+ "(b, v)",
+ "(b, v1)",
+ "(c, v)",
+ "(c, v1)",
+ "(d, v)",
+ "(d, v1)",
+ "(e, v)",
+ "(e, v1)",
+ ],
+ ),
+ (
+ None,
+ [
+ "(a, v)",
+ "(a, v1)",
+ "(b, v)",
+ "(b, v1)",
+ "(c, v)",
+ "(c, v1)",
+ "(d, v)",
+ "(d, v1)",
+ "(e, v)",
+ "(e, v1)",
+ ],
+ ),
+ ],
+ )
+ def test_groupby_boxplot_subplots_false(self, col, expected_xticklabel):
+ # GH 16748
+ df = DataFrame(
+ {
+ "cat": np.random.default_rng(2).choice(list("abcde"), 100),
+ "v": np.random.default_rng(2).random(100),
+ "v1": np.random.default_rng(2).random(100),
+ }
+ )
+ grouped = df.groupby("cat")
+
+ axes = _check_plot_works(
+ grouped.boxplot, subplots=False, column=col, return_type="axes"
+ )
+
+ result_xticklabel = [x.get_text() for x in axes.get_xticklabels()]
+ assert expected_xticklabel == result_xticklabel
+
+ def test_groupby_boxplot_object(self, hist_df):
+ # GH 43480
+ df = hist_df.astype("object")
+ grouped = df.groupby("gender")
+ msg = "boxplot method requires numerical columns, nothing to plot"
+ with pytest.raises(ValueError, match=msg):
+ _check_plot_works(grouped.boxplot, subplots=False)
+
+ def test_boxplot_multiindex_column(self):
+ # GH 16748
+ arrays = [
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ tuples = list(zip(*arrays, strict=True))
+ index = MultiIndex.from_tuples(tuples, names=["first", "second"])
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 8)),
+ index=["A", "B", "C"],
+ columns=index,
+ )
+
+ col = [("bar", "one"), ("bar", "two")]
+ axes = _check_plot_works(df.boxplot, column=col, return_type="axes")
+
+ expected_xticklabel = ["(bar, one)", "(bar, two)"]
+ result_xticklabel = [x.get_text() for x in axes.get_xticklabels()]
+ assert expected_xticklabel == result_xticklabel
+
+ @pytest.mark.parametrize("group", ["X", ["X", "Y"]])
+ def test_boxplot_multi_groupby_groups(self, group):
+ # GH 14701
+ rows = 20
+ df = DataFrame(
+ np.random.default_rng(12).normal(size=(rows, 2)), columns=["Col1", "Col2"]
+ )
+ df["X"] = Series(np.repeat(["A", "B"], int(rows / 2)))
+ df["Y"] = Series(np.tile(["C", "D"], int(rows / 2)))
+ grouped = df.groupby(group)
+ _check_plot_works(df.boxplot, by=group, default_axes=True)
+ _check_plot_works(df.plot.box, by=group, default_axes=True)
+ _check_plot_works(grouped.boxplot, default_axes=True)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..20daf5935624843af3224f991497f84fa6639a0d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_common.py
@@ -0,0 +1,60 @@
+import pytest
+
+from pandas import DataFrame
+from pandas.tests.plotting.common import (
+ _check_plot_works,
+ _check_ticks_props,
+ _gen_two_subplots,
+)
+
+plt = pytest.importorskip("matplotlib.pyplot")
+
+
+class TestCommon:
+ def test__check_ticks_props(self):
+ # GH 34768
+ df = DataFrame({"b": [0, 1, 0], "a": [1, 2, 3]})
+ ax = _check_plot_works(df.plot, rot=30)
+ ax.yaxis.set_tick_params(rotation=30)
+ msg = "expected 0.00000 but got "
+ with pytest.raises(AssertionError, match=msg):
+ _check_ticks_props(ax, xrot=0)
+ with pytest.raises(AssertionError, match=msg):
+ _check_ticks_props(ax, xlabelsize=0)
+ with pytest.raises(AssertionError, match=msg):
+ _check_ticks_props(ax, yrot=0)
+ with pytest.raises(AssertionError, match=msg):
+ _check_ticks_props(ax, ylabelsize=0)
+
+ def test__gen_two_subplots_with_ax(self):
+ fig = plt.gcf()
+ gen = _gen_two_subplots(f=lambda **kwargs: None, fig=fig, ax="test")
+ # On the first yield, no subplot should be added since ax was passed
+ next(gen)
+ assert fig.get_axes() == []
+ # On the second, the one axis should match fig.subplot(2, 1, 2)
+ next(gen)
+ axes = fig.get_axes()
+ assert len(axes) == 1
+ subplot_geometry = list(axes[0].get_subplotspec().get_geometry()[:-1])
+ subplot_geometry[-1] += 1
+ assert subplot_geometry == [2, 1, 2]
+
+ def test_colorbar_layout(self):
+ fig = plt.figure()
+
+ axes = fig.subplot_mosaic(
+ """
+ AB
+ CC
+ """
+ )
+
+ x = [1, 2, 3]
+ y = [1, 2, 3]
+
+ cs0 = axes["A"].scatter(x, y)
+ axes["B"].scatter(x, y)
+
+ fig.colorbar(cs0, ax=[axes["A"], axes["B"]], location="right")
+ DataFrame(x).plot(ax=axes["C"])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_converter.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_converter.py
new file mode 100644
index 0000000000000000000000000000000000000000..e33e91ccf6c6e2c508d4b528dbf1c194cd7918fa
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_converter.py
@@ -0,0 +1,391 @@
+from datetime import (
+ date,
+ datetime,
+)
+import subprocess
+import sys
+
+import numpy as np
+import pytest
+
+import pandas._config.config as cf
+
+from pandas._libs.tslibs import to_offset
+
+from pandas import (
+ Index,
+ Period,
+ PeriodIndex,
+ Series,
+ Timestamp,
+ arrays,
+ date_range,
+)
+import pandas._testing as tm
+
+from pandas.plotting import (
+ deregister_matplotlib_converters,
+ register_matplotlib_converters,
+)
+from pandas.tseries.offsets import (
+ Day,
+ Micro,
+ Milli,
+ Second,
+)
+
+plt = pytest.importorskip("matplotlib.pyplot")
+dates = pytest.importorskip("matplotlib.dates")
+units = pytest.importorskip("matplotlib.units")
+
+from pandas.plotting._matplotlib import converter
+
+
+@pytest.mark.single_cpu
+def test_registry_mpl_resets():
+ # Check that Matplotlib converters are properly reset (see issue #27481)
+ code = (
+ "import matplotlib.units as units; "
+ "import matplotlib.dates as mdates; "
+ "n_conv = len(units.registry); "
+ "import pandas as pd; "
+ "pd.plotting.register_matplotlib_converters(); "
+ "pd.plotting.deregister_matplotlib_converters(); "
+ "assert len(units.registry) == n_conv"
+ )
+ call = [sys.executable, "-c", code]
+ subprocess.check_output(call)
+
+
+def test_timtetonum_accepts_unicode():
+ assert converter.time2num("00:01") == converter.time2num("00:01")
+
+
+class TestRegistration:
+ @pytest.mark.single_cpu
+ def test_dont_register_by_default(self):
+ # Run in subprocess to ensure a clean state
+ code = (
+ "import matplotlib.units; "
+ "import pandas as pd; "
+ "units = dict(matplotlib.units.registry); "
+ "assert pd.Timestamp not in units"
+ )
+ call = [sys.executable, "-c", code]
+ assert subprocess.check_call(call) == 0
+
+ def test_registering_no_warning(self):
+ s = Series(range(12), index=date_range("2017", periods=12))
+ _, ax = plt.subplots()
+
+ # Set to the "warn" state, in case this isn't the first test run
+ register_matplotlib_converters()
+ ax.plot(s.index, s.values)
+
+ def test_pandas_plots_register(self):
+ s = Series(range(12), index=date_range("2017", periods=12))
+ # Set to the "warn" state, in case this isn't the first test run
+ with tm.assert_produces_warning(None) as w:
+ s.plot()
+
+ assert len(w) == 0
+
+ def test_matplotlib_formatters(self):
+ # Can't make any assertion about the start state.
+ # We we check that toggling converters off removes it, and toggling it
+ # on restores it.
+
+ with cf.option_context("plotting.matplotlib.register_converters", True):
+ with cf.option_context("plotting.matplotlib.register_converters", False):
+ assert Timestamp not in units.registry
+ assert Timestamp in units.registry
+
+ def test_option_no_warning(self):
+ s = Series(range(12), index=date_range("2017", periods=12))
+ _, ax = plt.subplots()
+
+ # Test without registering first, no warning
+ with cf.option_context("plotting.matplotlib.register_converters", False):
+ ax.plot(s.index, s.values)
+
+ # Now test with registering
+ register_matplotlib_converters()
+ with cf.option_context("plotting.matplotlib.register_converters", False):
+ ax.plot(s.index, s.values)
+
+ def test_registry_resets(self):
+ # make a copy, to reset to
+ original = dict(units.registry)
+
+ try:
+ # get to a known state
+ units.registry.clear()
+ date_converter = dates.DateConverter()
+ units.registry[datetime] = date_converter
+ units.registry[date] = date_converter
+
+ register_matplotlib_converters()
+ assert units.registry[date] is not date_converter
+ deregister_matplotlib_converters()
+ assert units.registry[date] is date_converter
+
+ finally:
+ # restore original stater
+ units.registry.clear()
+ for k, v in original.items():
+ units.registry[k] = v
+
+
+class TestDateTimeConverter:
+ @pytest.fixture
+ def dtc(self):
+ return converter.DatetimeConverter()
+
+ def test_convert_accepts_unicode(self, dtc):
+ r1 = dtc.convert("2000-01-01 12:22", None, None)
+ r2 = dtc.convert("2000-01-01 12:22", None, None)
+ assert r1 == r2, "DatetimeConverter.convert should accept unicode"
+
+ def test_conversion(self, dtc):
+ rs = dtc.convert(["2012-1-1"], None, None)[0]
+ xp = dates.date2num(datetime(2012, 1, 1))
+ assert rs == xp
+
+ rs = dtc.convert("2012-1-1", None, None)
+ assert rs == xp
+
+ rs = dtc.convert(date(2012, 1, 1), None, None)
+ assert rs == xp
+
+ rs = dtc.convert("2012-1-1", None, None)
+ assert rs == xp
+
+ rs = dtc.convert(Timestamp("2012-1-1"), None, None)
+ assert rs == xp
+
+ # also testing datetime64 dtype (GH8614)
+ rs = dtc.convert("2012-01-01", None, None)
+ assert rs == xp
+
+ rs = dtc.convert("2012-01-01 00:00:00+0000", None, None)
+ assert rs == xp
+
+ rs = dtc.convert(
+ np.array(["2012-01-01 00:00:00+0000", "2012-01-02 00:00:00+0000"]),
+ None,
+ None,
+ )
+ assert rs[0] == xp
+
+ # we have a tz-aware date (constructed to that when we turn to utc it
+ # is the same as our sample)
+ ts = Timestamp("2012-01-01").tz_localize("UTC").tz_convert("US/Eastern")
+ rs = dtc.convert(ts, None, None)
+ assert rs == xp
+
+ rs = dtc.convert(ts.to_pydatetime(), None, None)
+ assert rs == xp
+
+ rs = dtc.convert(Index([ts - Day(1), ts]), None, None)
+ assert rs[1] == xp
+
+ rs = dtc.convert(Index([ts - Day(1), ts]).to_pydatetime(), None, None)
+ assert rs[1] == xp
+
+ def test_conversion_float(self, dtc):
+ rtol = 0.5 * 10**-9
+
+ rs = dtc.convert(Timestamp("2012-1-1 01:02:03", tz="UTC"), None, None)
+ xp = dates.date2num(Timestamp("2012-1-1 01:02:03", tz="UTC"))
+ tm.assert_almost_equal(rs, xp, rtol=rtol)
+
+ rs = dtc.convert(
+ Timestamp("2012-1-1 09:02:03", tz="Asia/Hong_Kong"), None, None
+ )
+ tm.assert_almost_equal(rs, xp, rtol=rtol)
+
+ rs = dtc.convert(datetime(2012, 1, 1, 1, 2, 3), None, None)
+ tm.assert_almost_equal(rs, xp, rtol=rtol)
+
+ @pytest.mark.parametrize(
+ "values",
+ [
+ [date(1677, 1, 1), date(1677, 1, 2)],
+ [datetime(1677, 1, 1, 12), datetime(1677, 1, 2, 12)],
+ ],
+ )
+ def test_conversion_outofbounds_datetime(self, dtc, values):
+ # 2579
+ rs = dtc.convert(values, None, None)
+ xp = dates.date2num(values)
+ tm.assert_numpy_array_equal(rs, xp)
+ rs = dtc.convert(values[0], None, None)
+ xp = dates.date2num(values[0])
+ assert rs == xp
+
+ @pytest.mark.parametrize(
+ "time,format_expected",
+ [
+ (0, "00:00"), # time2num(datetime.time.min)
+ (86399.999999, "23:59:59.999999"), # time2num(datetime.time.max)
+ (90000, "01:00"),
+ (3723, "01:02:03"),
+ (39723.2, "11:02:03.200"),
+ ],
+ )
+ def test_time_formatter(self, time, format_expected):
+ # issue 18478
+ result = converter.TimeFormatter(None)(time)
+ assert result == format_expected
+
+ @pytest.mark.parametrize("freq", ("B", "ms", "s"))
+ def test_dateindex_conversion(self, freq, dtc):
+ rtol = 10**-9
+ dateindex = date_range("2020-01-01", periods=10, freq=freq)
+ rs = dtc.convert(dateindex, None, None)
+ xp = dates.date2num(dateindex._mpl_repr())
+ tm.assert_almost_equal(rs, xp, rtol=rtol)
+
+ @pytest.mark.parametrize("offset", [Second(), Milli(), Micro(50)])
+ def test_resolution(self, offset, dtc):
+ # Matplotlib's time representation using floats cannot distinguish
+ # intervals smaller than ~10 microsecond in the common range of years.
+ ts1 = Timestamp("2012-1-1")
+ ts2 = ts1 + offset
+ val1 = dtc.convert(ts1, None, None)
+ val2 = dtc.convert(ts2, None, None)
+ if not val1 < val2:
+ raise AssertionError(f"{val1} is not less than {val2}.")
+
+ def test_convert_nested(self, dtc):
+ inner = [Timestamp("2017-01-01"), Timestamp("2017-01-02")]
+ data = [inner, inner]
+ result = dtc.convert(data, None, None)
+ expected = [dtc.convert(x, None, None) for x in data]
+ assert (np.array(result) == expected).all()
+
+
+class TestPeriodConverter:
+ @pytest.fixture
+ def pc(self):
+ return converter.PeriodConverter()
+
+ @pytest.fixture
+ def axis(self):
+ class Axis:
+ pass
+
+ axis = Axis()
+ axis.freq = "D"
+ return axis
+
+ def test_convert_accepts_unicode(self, pc, axis):
+ r1 = pc.convert("2012-1-1", None, axis)
+ r2 = pc.convert("2012-1-1", None, axis)
+ assert r1 == r2
+
+ def test_conversion(self, pc, axis):
+ rs = pc.convert(["2012-1-1"], None, axis)[0]
+ xp = Period("2012-1-1").ordinal
+ assert rs == xp
+
+ rs = pc.convert("2012-1-1", None, axis)
+ assert rs == xp
+
+ rs = pc.convert([date(2012, 1, 1)], None, axis)[0]
+ assert rs == xp
+
+ rs = pc.convert(date(2012, 1, 1), None, axis)
+ assert rs == xp
+
+ rs = pc.convert([Timestamp("2012-1-1")], None, axis)[0]
+ assert rs == xp
+
+ rs = pc.convert(Timestamp("2012-1-1"), None, axis)
+ assert rs == xp
+
+ rs = pc.convert("2012-01-01", None, axis)
+ assert rs == xp
+
+ rs = pc.convert("2012-01-01 00:00:00+0000", None, axis)
+ assert rs == xp
+
+ rs = pc.convert(
+ np.array(
+ ["2012-01-01 00:00:00", "2012-01-02 00:00:00"],
+ dtype="datetime64[ns]",
+ ),
+ None,
+ axis,
+ )
+ assert rs[0] == xp
+
+ def test_integer_passthrough(self, pc, axis):
+ # GH9012
+ rs = pc.convert([0, 1], None, axis)
+ xp = [0, 1]
+ assert rs == xp
+
+ def test_convert_nested(self, pc, axis):
+ data = ["2012-1-1", "2012-1-2"]
+ r1 = pc.convert([data, data], None, axis)
+ r2 = [pc.convert(data, None, axis) for _ in range(2)]
+ assert r1 == r2
+
+
+class TestTimeDeltaConverter:
+ """Test timedelta converter"""
+
+ @pytest.mark.parametrize(
+ "x, decimal, format_expected",
+ [
+ (0.0, 0, "00:00:00"),
+ (3972320000000, 1, "01:06:12.3"),
+ (713233432000000, 2, "8 days 06:07:13.43"),
+ (32423432000000, 4, "09:00:23.4320"),
+ ],
+ )
+ def test_format_timedelta_ticks(self, x, decimal, format_expected):
+ tdc = converter.TimeSeries_TimedeltaFormatter
+ result = tdc.format_timedelta_ticks(x, pos=None, n_decimals=decimal, exp=9)
+ assert result == format_expected
+
+ @pytest.mark.parametrize("view_interval", [(1, 2), (2, 1)])
+ def test_call_w_different_view_intervals(self, view_interval, monkeypatch):
+ # previously broke on reversed xlmits; see GH37454
+ class mock_axis:
+ def get_view_interval(self):
+ return view_interval
+
+ tdc = converter.TimeSeries_TimedeltaFormatter()
+ monkeypatch.setattr(tdc, "axis", mock_axis())
+ tdc(0.0, 0)
+
+
+@pytest.mark.parametrize("year_span", [11.25, 30, 80, 150, 400, 800, 1500, 2500, 3500])
+# The range is limited to 11.25 at the bottom by if statements in
+# the _quarterly_finder() function
+def test_quarterly_finder(year_span):
+ vmin = -1000
+ vmax = vmin + year_span * 4
+ span = vmax - vmin + 1
+ if span < 45:
+ pytest.skip("the quarterly finder is only invoked if the span is >= 45")
+ nyears = span / 4
+ (min_anndef, maj_anndef) = converter._get_default_annual_spacing(nyears)
+ result = converter._quarterly_finder(vmin, vmax, to_offset("QE"))
+ quarters = PeriodIndex(
+ arrays.PeriodArray(np.array([x[0] for x in result]), dtype="period[Q]")
+ )
+ majors = np.array([x[1] for x in result])
+ minors = np.array([x[2] for x in result])
+ major_quarters = quarters[majors]
+ minor_quarters = quarters[minors]
+ check_major_years = major_quarters.year % maj_anndef == 0
+ check_minor_years = minor_quarters.year % min_anndef == 0
+ check_major_quarters = major_quarters.quarter == 1
+ check_minor_quarters = minor_quarters.quarter == 1
+ assert np.all(check_major_years)
+ assert np.all(check_minor_years)
+ assert np.all(check_major_quarters)
+ assert np.all(check_minor_quarters)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_datetimelike.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_datetimelike.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb845c6e6d71d0895c4424b2b54eb333c7b320ce
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_datetimelike.py
@@ -0,0 +1,1721 @@
+"""Test cases for time series specific (freq conversion, etc)"""
+
+from datetime import (
+ date,
+ datetime,
+ time,
+ timedelta,
+)
+import pickle
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import (
+ BaseOffset,
+ to_offset,
+)
+
+from pandas.core.dtypes.dtypes import PeriodDtype
+
+from pandas import (
+ DataFrame,
+ Index,
+ NaT,
+ Series,
+ concat,
+ isna,
+ to_datetime,
+)
+import pandas._testing as tm
+from pandas.core.indexes.datetimes import (
+ DatetimeIndex,
+ bdate_range,
+ date_range,
+)
+from pandas.core.indexes.period import (
+ Period,
+ PeriodIndex,
+ period_range,
+)
+from pandas.core.indexes.timedeltas import timedelta_range
+from pandas.tests.plotting.common import _check_ticks_props
+
+from pandas.tseries.offsets import WeekOfMonth
+
+mpl = pytest.importorskip("matplotlib")
+plt = pytest.importorskip("matplotlib.pyplot")
+
+import pandas.plotting._matplotlib.converter as conv
+
+
+class TestTSPlot:
+ @pytest.mark.filterwarnings("ignore::UserWarning")
+ def test_ts_plot_with_tz(self, tz_aware_fixture):
+ # GH2877, GH17173, GH31205, GH31580
+ tz = tz_aware_fixture
+ index = date_range("1/1/2011", periods=2, freq="h", tz=tz)
+ ts = Series([188.5, 328.25], index=index)
+ _check_plot_works(ts.plot)
+ ax = ts.plot()
+ xdata = next(iter(ax.get_lines())).get_xdata()
+ # Check first and last points' labels are correct
+ assert (xdata[0].hour, xdata[0].minute) == (0, 0)
+ assert (xdata[-1].hour, xdata[-1].minute) == (1, 0)
+
+ def test_fontsize_set_correctly(self):
+ # For issue #8765
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 9)), index=range(10)
+ )
+ _, ax = mpl.pyplot.subplots()
+ df.plot(fontsize=2, ax=ax)
+ for label in ax.get_xticklabels() + ax.get_yticklabels():
+ assert label.get_fontsize() == 2
+
+ def test_frame_inferred(self):
+ # inferred freq
+ idx = date_range("1/1/1987", freq="MS", periods=10)
+ idx = DatetimeIndex(idx.values, freq=None)
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), 3)), index=idx
+ )
+ _check_plot_works(df.plot)
+
+ # axes freq
+ idx = idx[0:4].union(idx[6:])
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), 3)), index=idx
+ )
+ _check_plot_works(df2.plot)
+
+ def test_frame_inferred_n_gt_1(self):
+ # N > 1
+ idx = date_range("2008-1-1 00:15:00", freq="15min", periods=10)
+ idx = DatetimeIndex(idx.values, freq=None)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), 3)), index=idx
+ )
+ _check_plot_works(df.plot)
+
+ def test_is_error_nozeroindex(self):
+ # GH11858
+ i = np.array([1, 2, 3])
+ a = DataFrame(i, index=i)
+ _check_plot_works(a.plot, xerr=a)
+ _check_plot_works(a.plot, yerr=a)
+
+ def test_nonnumeric_exclude(self):
+ idx = date_range("1/1/1987", freq="YE", periods=3)
+ df = DataFrame({"A": ["x", "y", "z"], "B": [1, 2, 3]}, idx)
+
+ fig, ax = mpl.pyplot.subplots()
+ df.plot(ax=ax) # it works
+ assert len(ax.get_lines()) == 1 # B was plotted
+
+ def test_nonnumeric_exclude_error(self):
+ idx = date_range("1/1/1987", freq="YE", periods=3)
+ df = DataFrame({"A": ["x", "y", "z"], "B": [1, 2, 3]}, idx)
+ msg = "no numeric data to plot"
+ with pytest.raises(TypeError, match=msg):
+ df["A"].plot()
+
+ @pytest.mark.parametrize("freq", ["s", "min", "h", "D", "W", "M", "Q", "Y"])
+ def test_tsplot_period(self, freq):
+ idx = period_range("12/31/1999", freq=freq, periods=10)
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ _, ax = mpl.pyplot.subplots()
+ _check_plot_works(ser.plot, ax=ax)
+
+ @pytest.mark.parametrize(
+ "freq", ["s", "min", "h", "D", "W", "ME", "QE-DEC", "YE", "1B30Min"]
+ )
+ def test_tsplot_datetime(self, freq):
+ idx = date_range("12/31/1999", freq=freq, periods=10)
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ _, ax = mpl.pyplot.subplots()
+ _check_plot_works(ser.plot, ax=ax)
+
+ def test_tsplot(self):
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ _, ax = mpl.pyplot.subplots()
+ ts.plot(style="k", ax=ax)
+ color = (0.0, 0.0, 0.0, 1)
+ assert color == ax.get_lines()[0].get_color()
+
+ @pytest.mark.parametrize("index", [None, date_range("2020-01-01", periods=10)])
+ def test_both_style_and_color(self, index):
+ ts = Series(np.arange(10, dtype=np.float64), index=index)
+ msg = (
+ "Cannot pass 'style' string with a color symbol and 'color' "
+ "keyword argument. Please use one or the other or pass 'style' "
+ "without a color symbol"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ts.plot(style="b-", color="#000099")
+
+ @pytest.mark.parametrize("freq", ["ms", "us"])
+ def test_high_freq(self, freq):
+ _, ax = mpl.pyplot.subplots()
+ rng = date_range("1/1/2012", periods=10, freq=freq)
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _check_plot_works(ser.plot, ax=ax)
+
+ def test_get_datevalue(self):
+ assert conv._get_datevalue(None, "D") is None
+ assert conv._get_datevalue(1987, "Y") == 1987
+ assert (
+ conv._get_datevalue(Period(1987, "Y"), "M")
+ == Period("1987-12", "M").ordinal
+ )
+ assert conv._get_datevalue("1/1/1987", "D") == Period("1987-1-1", "D").ordinal
+
+ @pytest.mark.parametrize(
+ "freq, expected_string",
+ [["YE-DEC", "t = 2014 y = 1.000000"], ["D", "t = 2014-01-01 y = 1.000000"]],
+ )
+ def test_ts_plot_format_coord(self, freq, expected_string):
+ ser = Series(1, index=date_range("2014-01-01", periods=3, freq=freq))
+ _, ax = mpl.pyplot.subplots()
+ ser.plot(ax=ax)
+ first_line = ax.get_lines()[0]
+ first_x = first_line.get_xdata()[0].ordinal
+ first_y = first_line.get_ydata()[0]
+ assert expected_string == ax.format_coord(first_x, first_y)
+
+ @pytest.mark.parametrize("freq", ["s", "min", "h", "D", "W", "M", "Q", "Y"])
+ def test_line_plot_period_series(self, freq):
+ idx = period_range("12/31/1999", freq=freq, periods=10)
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ _check_plot_works(ser.plot, ser.index.freq)
+
+ @pytest.mark.parametrize(
+ "frqncy", ["1s", "3s", "5min", "7h", "4D", "8W", "11M", "3Y"]
+ )
+ def test_line_plot_period_mlt_series(self, frqncy):
+ # test period index line plot for series with multiples (`mlt`) of the
+ # frequency (`frqncy`) rule code. tests resolution of issue #14763
+ idx = period_range("12/31/1999", freq=frqncy, periods=10)
+ s = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ _check_plot_works(s.plot, s.index.freq.rule_code)
+
+ @pytest.mark.parametrize(
+ "freq", ["s", "min", "h", "D", "W", "ME", "QE-DEC", "YE", "1B30Min"]
+ )
+ def test_line_plot_datetime_series(self, freq):
+ idx = date_range("12/31/1999", freq=freq, periods=10)
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ _check_plot_works(ser.plot, ser.index.freq.rule_code)
+
+ @pytest.mark.parametrize("freq", ["s", "min", "h", "D", "W", "ME", "QE", "YE"])
+ def test_line_plot_period_frame(self, freq):
+ idx = date_range("12/31/1999", freq=freq, periods=10)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), 3)),
+ index=idx,
+ columns=["A", "B", "C"],
+ )
+ _check_plot_works(df.plot, df.index.freq)
+
+ @pytest.mark.parametrize(
+ "frqncy", ["1s", "3s", "5min", "7h", "4D", "8W", "11M", "3Y"]
+ )
+ def test_line_plot_period_mlt_frame(self, frqncy):
+ # test period index line plot for DataFrames with multiples (`mlt`)
+ # of the frequency (`frqncy`) rule code. tests resolution of issue
+ # #14763
+ idx = period_range("12/31/1999", freq=frqncy, periods=10)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), 3)),
+ index=idx,
+ columns=["A", "B", "C"],
+ )
+ freq = df.index.freq.rule_code
+ _check_plot_works(df.plot, freq)
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ @pytest.mark.parametrize(
+ "freq", ["s", "min", "h", "D", "W", "ME", "QE-DEC", "YE", "1B30Min"]
+ )
+ def test_line_plot_datetime_frame(self, freq):
+ idx = date_range("12/31/1999", freq=freq, periods=10)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), 3)),
+ index=idx,
+ columns=["A", "B", "C"],
+ )
+ freq = PeriodDtype(df.index.freq)._freqstr
+ freq = df.index.to_period(freq).freq
+ _check_plot_works(df.plot, freq)
+
+ @pytest.mark.parametrize(
+ "freq", ["s", "min", "h", "D", "W", "ME", "QE-DEC", "YE", "1B30Min"]
+ )
+ def test_line_plot_inferred_freq(self, freq):
+ idx = date_range("12/31/1999", freq=freq, periods=10)
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ ser = Series(ser.values, Index(np.asarray(ser.index)))
+ _check_plot_works(ser.plot, ser.index.inferred_freq)
+
+ ser = ser.iloc[[0, 3, 5, 6]]
+ _check_plot_works(ser.plot)
+
+ def test_fake_inferred_business(self):
+ _, ax = mpl.pyplot.subplots()
+ rng = date_range("2001-1-1", "2001-1-10")
+ ts = Series(range(len(rng)), index=rng)
+ ts = concat([ts[:3], ts[5:]])
+ ts.plot(ax=ax)
+ assert not hasattr(ax, "freq")
+
+ def test_plot_offset_freq(self):
+ ser = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ _check_plot_works(ser.plot)
+
+ def test_plot_offset_freq_business(self):
+ dr = date_range("2023-01-01", freq="BQS", periods=10)
+ ser = Series(np.random.default_rng(2).standard_normal(len(dr)), index=dr)
+ _check_plot_works(ser.plot)
+
+ def test_plot_multiple_inferred_freq(self):
+ dr = Index([datetime(2000, 1, 1), datetime(2000, 1, 6), datetime(2000, 1, 11)])
+ ser = Series(np.random.default_rng(2).standard_normal(len(dr)), index=dr)
+ _check_plot_works(ser.plot)
+
+ def test_irreg_hf(self):
+ idx = date_range("2012-6-22 21:59:51", freq="s", periods=10)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), 2)), index=idx
+ )
+
+ irreg = df.iloc[[0, 1, 3, 4]]
+ _, ax = mpl.pyplot.subplots()
+ irreg.plot(ax=ax)
+ diffs = Series(ax.get_lines()[0].get_xydata()[:, 0]).diff()
+
+ sec = 1.0 / 24 / 60 / 60
+ assert (np.fabs(diffs[1:] - [sec, sec * 2, sec]) < 1e-8).all()
+
+ def test_irreg_hf_object(self):
+ idx = date_range("2012-6-22 21:59:51", freq="s", periods=10)
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), 2)), index=idx
+ )
+ _, ax = mpl.pyplot.subplots()
+ df2.index = df2.index.astype(object)
+ df2.plot(ax=ax)
+ diffs = Series(ax.get_lines()[0].get_xydata()[:, 0]).diff()
+ sec = 1.0 / 24 / 60 / 60
+ assert (np.fabs(diffs[1:] - sec) < 1e-8).all()
+
+ def test_irregular_datetime64_repr_bug(self):
+ ser = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ ser = ser.iloc[[0, 1, 2, 7]]
+
+ _, ax = mpl.pyplot.subplots()
+
+ ret = ser.plot(ax=ax)
+ assert ret is not None
+
+ for rs, xp in zip(ax.get_lines()[0].get_xdata(), ser.index, strict=True):
+ assert rs == xp
+
+ def test_business_freq(self):
+ bts = Series(range(5), period_range("2020-01-01", periods=5))
+ msg = r"PeriodDtype\[B\] is deprecated"
+ dt = bts.index[0].to_timestamp()
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ bts.index = period_range(start=dt, periods=len(bts), freq="B")
+ _, ax = mpl.pyplot.subplots()
+ bts.plot(ax=ax)
+ assert ax.get_lines()[0].get_xydata()[0, 0] == bts.index[0].ordinal
+ idx = ax.get_lines()[0].get_xdata()
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ assert PeriodIndex(data=idx).freqstr == "B"
+
+ def test_business_freq_convert(self):
+ bts = Series(
+ np.arange(50, dtype=np.float64),
+ index=date_range("2020-01-01", periods=50, freq="B"),
+ ).asfreq("BME")
+ ts = bts.to_period("M")
+ _, ax = mpl.pyplot.subplots()
+ bts.plot(ax=ax)
+ assert ax.get_lines()[0].get_xydata()[0, 0] == ts.index[0].ordinal
+ idx = ax.get_lines()[0].get_xdata()
+ assert PeriodIndex(data=idx).freqstr == "M"
+
+ def test_freq_with_no_period_alias(self):
+ # GH34487
+ freq = WeekOfMonth()
+ bts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ ).asfreq(freq)
+ _, ax = mpl.pyplot.subplots()
+ bts.plot(ax=ax)
+
+ idx = ax.get_lines()[0].get_xdata()
+ msg = "freq not specified and cannot be inferred"
+ with pytest.raises(ValueError, match=msg):
+ PeriodIndex(data=idx)
+
+ def test_nonzero_base(self):
+ # GH2571
+ idx = date_range("2012-12-20", periods=24, freq="h") + timedelta(minutes=30)
+ df = DataFrame(np.arange(24), index=idx)
+ _, ax = mpl.pyplot.subplots()
+ df.plot(ax=ax)
+ rs = ax.get_lines()[0].get_xdata()
+ assert not Index(rs).is_normalized
+
+ def test_dataframe(self):
+ bts = DataFrame(
+ {
+ "a": Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ )
+ }
+ )
+ _, ax = mpl.pyplot.subplots()
+ bts.plot(ax=ax)
+ idx = ax.get_lines()[0].get_xdata()
+ tm.assert_index_equal(bts.index.to_period(), PeriodIndex(idx))
+
+ @pytest.mark.filterwarnings(
+ "ignore:Period with BDay freq is deprecated:FutureWarning"
+ )
+ @pytest.mark.parametrize(
+ "obj",
+ [
+ Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ ),
+ DataFrame(
+ {
+ "a": Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ ),
+ "b": Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ )
+ + 1,
+ }
+ ),
+ ],
+ )
+ def test_axis_limits(self, obj):
+ _, ax = mpl.pyplot.subplots()
+ obj.plot(ax=ax)
+ xlim = ax.get_xlim()
+ ax.set_xlim(xlim[0] - 5, xlim[1] + 10)
+ result = ax.get_xlim()
+ assert result[0] == xlim[0] - 5
+ assert result[1] == xlim[1] + 10
+
+ # string
+ expected = (Period("1/1/2000", ax.freq), Period("4/1/2000", ax.freq))
+ ax.set_xlim("1/1/2000", "4/1/2000")
+ result = ax.get_xlim()
+ assert int(result[0]) == expected[0].ordinal
+ assert int(result[1]) == expected[1].ordinal
+
+ # datetime
+ expected = (Period("1/1/2000", ax.freq), Period("4/1/2000", ax.freq))
+ ax.set_xlim(datetime(2000, 1, 1), datetime(2000, 4, 1))
+ result = ax.get_xlim()
+ assert int(result[0]) == expected[0].ordinal
+ assert int(result[1]) == expected[1].ordinal
+
+ def test_get_finder(self):
+ assert conv.get_finder(to_offset("B")) == conv._daily_finder
+ assert conv.get_finder(to_offset("D")) == conv._daily_finder
+ assert conv.get_finder(to_offset("ME")) == conv._monthly_finder
+ assert conv.get_finder(to_offset("QE")) == conv._quarterly_finder
+ assert conv.get_finder(to_offset("YE")) == conv._annual_finder
+ assert conv.get_finder(to_offset("W")) == conv._daily_finder
+
+ def test_finder_daily(self):
+ day_lst = [10, 40, 252, 400, 950, 2750, 10000]
+
+ msg = "Period with BDay freq is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ xpl1 = xpl2 = [Period("1999-1-1", freq="B").ordinal] * len(day_lst)
+ rs1 = []
+ rs2 = []
+ for n in day_lst:
+ rng = bdate_range("1999-1-1", periods=n)
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ser.plot(ax=ax)
+ xaxis = ax.get_xaxis()
+ rs1.append(xaxis.get_majorticklocs()[0])
+
+ vmin, vmax = ax.get_xlim()
+ ax.set_xlim(vmin + 0.9, vmax)
+ rs2.append(xaxis.get_majorticklocs()[0])
+ mpl.pyplot.close(ax.get_figure())
+
+ assert rs1 == xpl1
+ assert rs2 == xpl2
+
+ def test_finder_quarterly(self):
+ yrs = [3.5, 11]
+
+ xpl1 = xpl2 = [Period("1988Q1").ordinal] * len(yrs)
+ rs1 = []
+ rs2 = []
+ for n in yrs:
+ rng = period_range("1987Q2", periods=int(n * 4), freq="Q")
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ser.plot(ax=ax)
+ xaxis = ax.get_xaxis()
+ rs1.append(xaxis.get_majorticklocs()[0])
+
+ (vmin, vmax) = ax.get_xlim()
+ ax.set_xlim(vmin + 0.9, vmax)
+ rs2.append(xaxis.get_majorticklocs()[0])
+ mpl.pyplot.close(ax.get_figure())
+
+ assert rs1 == xpl1
+ assert rs2 == xpl2
+
+ def test_finder_monthly(self):
+ yrs = [1.15, 2.5, 4, 11]
+
+ xpl1 = xpl2 = [Period("Jan 1988").ordinal] * len(yrs)
+ rs1 = []
+ rs2 = []
+ for n in yrs:
+ rng = period_range("1987Q2", periods=int(n * 12), freq="M")
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ser.plot(ax=ax)
+ xaxis = ax.get_xaxis()
+ rs1.append(xaxis.get_majorticklocs()[0])
+
+ vmin, vmax = ax.get_xlim()
+ ax.set_xlim(vmin + 0.9, vmax)
+ rs2.append(xaxis.get_majorticklocs()[0])
+ mpl.pyplot.close(ax.get_figure())
+
+ assert rs1 == xpl1
+ assert rs2 == xpl2
+
+ def test_finder_monthly_long(self):
+ rng = period_range("1988Q1", periods=24 * 12, freq="M")
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ser.plot(ax=ax)
+ xaxis = ax.get_xaxis()
+ rs = xaxis.get_majorticklocs()[0]
+ xp = Period("1989Q1", "M").ordinal
+ assert rs == xp
+
+ def test_finder_annual(self):
+ xp = [1987, 1988, 1990, 1990, 1995, 2020, 2070, 2170]
+ xp = [Period(x, freq="Y").ordinal for x in xp]
+ rs = []
+ for nyears in [5, 10, 19, 49, 99, 199, 599, 1001]:
+ rng = period_range("1987", periods=nyears, freq="Y")
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ser.plot(ax=ax)
+ xaxis = ax.get_xaxis()
+ rs.append(xaxis.get_majorticklocs()[0])
+ mpl.pyplot.close(ax.get_figure())
+
+ assert rs == xp
+
+ @pytest.mark.slow
+ def test_finder_minutely(self):
+ nminutes = 1 * 24 * 60
+ rng = date_range("1/1/1999", freq="Min", periods=nminutes)
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ser.plot(ax=ax)
+ xaxis = ax.get_xaxis()
+ rs = xaxis.get_majorticklocs()[0]
+ xp = Period("1/1/1999", freq="Min").ordinal
+
+ assert rs == xp
+
+ def test_finder_hourly(self):
+ nhours = 23
+ rng = date_range("1/1/1999", freq="h", periods=nhours)
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ser.plot(ax=ax)
+ xaxis = ax.get_xaxis()
+ rs = xaxis.get_majorticklocs()[0]
+ xp = Period("1/1/1999", freq="h").ordinal
+
+ assert rs == xp
+
+ def test_gaps(self):
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ ts.iloc[5:7] = np.nan
+ _, ax = mpl.pyplot.subplots()
+ ts.plot(ax=ax)
+ lines = ax.get_lines()
+ assert len(lines) == 1
+ line = lines[0]
+ data = line.get_xydata()
+
+ data = np.ma.MaskedArray(data, mask=isna(data), fill_value=np.nan)
+
+ assert isinstance(data, np.ma.core.MaskedArray)
+ mask = data.mask
+ assert mask[5:7, 1].all()
+
+ def test_gaps_irregular(self):
+ # irregular
+ ts = Series(
+ np.arange(30, dtype=np.float64), index=date_range("2020-01-01", periods=30)
+ )
+ ts = ts.iloc[[0, 1, 2, 5, 7, 9, 12, 15, 20]]
+ ts.iloc[2:5] = np.nan
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot(ax=ax)
+ lines = ax.get_lines()
+ assert len(lines) == 1
+ line = lines[0]
+ data = line.get_xydata()
+
+ data = np.ma.MaskedArray(data, mask=isna(data), fill_value=np.nan)
+
+ assert isinstance(data, np.ma.core.MaskedArray)
+ mask = data.mask
+ assert mask[2:5, 1].all()
+
+ def test_gaps_non_ts(self):
+ # non-ts
+ idx = [0, 1, 2, 5, 7, 9, 12, 15, 20]
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx)), idx)
+ ser.iloc[2:5] = np.nan
+ _, ax = mpl.pyplot.subplots()
+ ser.plot(ax=ax)
+ lines = ax.get_lines()
+ assert len(lines) == 1
+ line = lines[0]
+ data = line.get_xydata()
+ data = np.ma.MaskedArray(data, mask=isna(data), fill_value=np.nan)
+
+ assert isinstance(data, np.ma.core.MaskedArray)
+ mask = data.mask
+ assert mask[2:5, 1].all()
+
+ def test_gap_upsample(self):
+ low = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ low.iloc[5:7] = np.nan
+ _, ax = mpl.pyplot.subplots()
+ low.plot(ax=ax)
+
+ idxh = date_range(low.index[0], low.index[-1], freq="12h")
+ s = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ s.plot(secondary_y=True)
+ lines = ax.get_lines()
+ assert len(lines) == 1
+ assert len(ax.right_ax.get_lines()) == 1
+
+ line = lines[0]
+ data = line.get_xydata()
+ data = np.ma.MaskedArray(data, mask=isna(data), fill_value=np.nan)
+
+ assert isinstance(data, np.ma.core.MaskedArray)
+ mask = data.mask
+ assert mask[5:7, 1].all()
+
+ def test_secondary_y(self):
+ ser = Series(np.random.default_rng(2).standard_normal(10))
+ fig, _ = mpl.pyplot.subplots()
+ ax = ser.plot(secondary_y=True)
+ assert hasattr(ax, "left_ax")
+ assert not hasattr(ax, "right_ax")
+ axes = fig.get_axes()
+ line = ax.get_lines()[0]
+ xp = Series(line.get_ydata(), line.get_xdata())
+ tm.assert_series_equal(ser, xp)
+ assert ax.get_yaxis().get_ticks_position() == "right"
+ assert not axes[0].get_yaxis().get_visible()
+
+ def test_secondary_y_yaxis(self):
+ Series(np.random.default_rng(2).standard_normal(10))
+ ser2 = Series(np.random.default_rng(2).standard_normal(10))
+ _, ax2 = mpl.pyplot.subplots()
+ ser2.plot(ax=ax2)
+ assert ax2.get_yaxis().get_ticks_position() == "left"
+
+ def test_secondary_both(self):
+ ser = Series(np.random.default_rng(2).standard_normal(10))
+ ser2 = Series(np.random.default_rng(2).standard_normal(10))
+ ax = ser2.plot()
+ ax2 = ser.plot(secondary_y=True)
+ assert ax.get_yaxis().get_visible()
+ assert not hasattr(ax, "left_ax")
+ assert hasattr(ax, "right_ax")
+ assert hasattr(ax2, "left_ax")
+ assert not hasattr(ax2, "right_ax")
+
+ def test_secondary_y_ts(self):
+ idx = date_range("1/1/2000", periods=10, unit="ns")
+ ser = Series(np.random.default_rng(2).standard_normal(10), idx)
+ fig, _ = mpl.pyplot.subplots()
+ ax = ser.plot(secondary_y=True)
+ assert hasattr(ax, "left_ax")
+ assert not hasattr(ax, "right_ax")
+ axes = fig.get_axes()
+ line = ax.get_lines()[0]
+ xp = Series(line.get_ydata(), line.get_xdata()).to_timestamp()
+ xp.index = xp.index.as_unit("ns")
+ tm.assert_series_equal(ser, xp)
+ assert ax.get_yaxis().get_ticks_position() == "right"
+ assert not axes[0].get_yaxis().get_visible()
+
+ def test_secondary_y_ts_yaxis(self):
+ idx = date_range("1/1/2000", periods=10)
+ ser2 = Series(np.random.default_rng(2).standard_normal(10), idx)
+ _, ax2 = mpl.pyplot.subplots()
+ ser2.plot(ax=ax2)
+ assert ax2.get_yaxis().get_ticks_position() == "left"
+
+ def test_secondary_y_ts_visible(self):
+ idx = date_range("1/1/2000", periods=10)
+ ser2 = Series(np.random.default_rng(2).standard_normal(10), idx)
+ ax = ser2.plot()
+ assert ax.get_yaxis().get_visible()
+
+ def test_secondary_kde(self):
+ pytest.importorskip("scipy")
+ ser = Series(np.random.default_rng(2).standard_normal(10))
+ fig, ax = mpl.pyplot.subplots()
+ ax = ser.plot(secondary_y=True, kind="density", ax=ax)
+ assert hasattr(ax, "left_ax")
+ assert not hasattr(ax, "right_ax")
+ axes = fig.get_axes()
+ assert axes[1].get_yaxis().get_ticks_position() == "right"
+
+ def test_secondary_bar(self):
+ ser = Series(np.random.default_rng(2).standard_normal(10))
+ fig, ax = mpl.pyplot.subplots()
+ ser.plot(secondary_y=True, kind="bar", ax=ax)
+ axes = fig.get_axes()
+ assert axes[1].get_yaxis().get_ticks_position() == "right"
+
+ def test_secondary_frame(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 3)), columns=["a", "b", "c"]
+ )
+ axes = df.plot(secondary_y=["a", "c"], subplots=True)
+ assert axes[0].get_yaxis().get_ticks_position() == "right"
+ assert axes[1].get_yaxis().get_ticks_position() == "left"
+ assert axes[2].get_yaxis().get_ticks_position() == "right"
+
+ def test_secondary_bar_frame(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 3)), columns=["a", "b", "c"]
+ )
+ axes = df.plot(kind="bar", secondary_y=["a", "c"], subplots=True)
+ assert axes[0].get_yaxis().get_ticks_position() == "right"
+ assert axes[1].get_yaxis().get_ticks_position() == "left"
+ assert axes[2].get_yaxis().get_ticks_position() == "right"
+
+ def test_mixed_freq_regular_first(self):
+ # TODO
+ s1 = Series(
+ np.arange(20, dtype=np.float64),
+ index=date_range("2020-01-01", periods=20, freq="B"),
+ )
+ s2 = s1.iloc[[0, 5, 10, 11, 12, 13, 14, 15]]
+
+ # it works!
+ _, ax = mpl.pyplot.subplots()
+ s1.plot(ax=ax)
+
+ ax2 = s2.plot(style="g", ax=ax)
+ lines = ax2.get_lines()
+ msg = r"PeriodDtype\[B\] is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ idx1 = PeriodIndex(lines[0].get_xdata())
+ idx2 = PeriodIndex(lines[1].get_xdata())
+
+ tm.assert_index_equal(idx1, s1.index.to_period("B"))
+ tm.assert_index_equal(idx2, s2.index.to_period("B"))
+
+ left, right = ax2.get_xlim()
+ pidx = s1.index.to_period()
+ assert left <= pidx[0].ordinal
+ assert right >= pidx[-1].ordinal
+
+ def test_mixed_freq_irregular_first(self):
+ s1 = Series(
+ np.arange(20, dtype=np.float64), index=date_range("2020-01-01", periods=20)
+ )
+ s2 = s1.iloc[[0, 5, 10, 11, 12, 13, 14, 15]]
+ _, ax = mpl.pyplot.subplots()
+ s2.plot(style="g", ax=ax)
+ s1.plot(ax=ax)
+ assert not hasattr(ax, "freq")
+ lines = ax.get_lines()
+ x1 = lines[0].get_xdata()
+ tm.assert_numpy_array_equal(x1, s2.index.astype(object).values)
+ x2 = lines[1].get_xdata()
+ tm.assert_numpy_array_equal(x2, s1.index.astype(object).values)
+
+ def test_mixed_freq_regular_first_df(self):
+ # GH 9852
+ s1 = Series(
+ np.arange(20, dtype=np.float64),
+ index=date_range("2020-01-01", periods=20, freq="B"),
+ ).to_frame()
+ s2 = s1.iloc[[0, 5, 10, 11, 12, 13, 14, 15], :]
+ _, ax = mpl.pyplot.subplots()
+ s1.plot(ax=ax)
+ ax2 = s2.plot(style="g", ax=ax)
+ lines = ax2.get_lines()
+ msg = r"PeriodDtype\[B\] is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ idx1 = PeriodIndex(lines[0].get_xdata())
+ idx2 = PeriodIndex(lines[1].get_xdata())
+ assert idx1.equals(s1.index.to_period("B"))
+ assert idx2.equals(s2.index.to_period("B"))
+ left, right = ax2.get_xlim()
+ pidx = s1.index.to_period()
+ assert left <= pidx[0].ordinal
+ assert right >= pidx[-1].ordinal
+
+ def test_mixed_freq_irregular_first_df(self):
+ # GH 9852
+ s1 = Series(
+ np.arange(20, dtype=np.float64), index=date_range("2020-01-01", periods=20)
+ ).to_frame()
+ s2 = s1.iloc[[0, 5, 10, 11, 12, 13, 14, 15], :]
+ _, ax = mpl.pyplot.subplots()
+ s2.plot(style="g", ax=ax)
+ s1.plot(ax=ax)
+ assert not hasattr(ax, "freq")
+ lines = ax.get_lines()
+ x1 = lines[0].get_xdata()
+ tm.assert_numpy_array_equal(x1, s2.index.astype(object).values)
+ x2 = lines[1].get_xdata()
+ tm.assert_numpy_array_equal(x2, s1.index.astype(object).values)
+
+ def test_mixed_freq_hf_first(self):
+ idxh = date_range("1/1/1999", periods=365, freq="D")
+ idxl = date_range("1/1/1999", periods=12, freq="ME")
+ high = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ low = Series(np.random.default_rng(2).standard_normal(len(idxl)), idxl)
+ _, ax = mpl.pyplot.subplots()
+ high.plot(ax=ax)
+ low.plot(ax=ax)
+ for line in ax.get_lines():
+ assert PeriodIndex(data=line.get_xdata()).freq == "D"
+
+ def test_mixed_freq_alignment(self):
+ ts_ind = date_range("2012-01-01 13:00", "2012-01-02", freq="h")
+ ts_data = np.random.default_rng(2).standard_normal(12)
+
+ ts = Series(ts_data, index=ts_ind)
+ ts2 = ts.asfreq("min").interpolate()
+
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot(ax=ax)
+ ts2.plot(style="r", ax=ax)
+
+ assert ax.lines[0].get_xdata()[0] == ax.lines[1].get_xdata()[0]
+
+ def test_mixed_freq_lf_first(self):
+ idxh = date_range("1/1/1999", periods=365, freq="D")
+ idxl = date_range("1/1/1999", periods=12, freq="ME")
+ high = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ low = Series(np.random.default_rng(2).standard_normal(len(idxl)), idxl)
+ _, ax = mpl.pyplot.subplots()
+ low.plot(legend=True, ax=ax)
+ high.plot(legend=True, ax=ax)
+ for line in ax.get_lines():
+ assert PeriodIndex(data=line.get_xdata()).freq == "D"
+ leg = ax.get_legend()
+ assert len(leg.texts) == 2
+ mpl.pyplot.close(ax.get_figure())
+
+ def test_mixed_freq_lf_first_hourly(self):
+ idxh = date_range("1/1/1999", periods=240, freq="min")
+ idxl = date_range("1/1/1999", periods=4, freq="h")
+ high = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ low = Series(np.random.default_rng(2).standard_normal(len(idxl)), idxl)
+ _, ax = mpl.pyplot.subplots()
+ low.plot(ax=ax)
+ high.plot(ax=ax)
+ for line in ax.get_lines():
+ assert PeriodIndex(data=line.get_xdata()).freq == "min"
+
+ @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
+ def test_mixed_freq_irreg_period(self):
+ ts = Series(
+ np.arange(30, dtype=np.float64), index=date_range("2020-01-01", periods=30)
+ )
+ irreg = ts.iloc[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 29]]
+ msg = r"PeriodDtype\[B\] is deprecated"
+ with tm.assert_produces_warning(FutureWarning, match=msg):
+ rng = period_range("1/3/2000", periods=30, freq="B")
+ ps = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _, ax = mpl.pyplot.subplots()
+ irreg.plot(ax=ax)
+ ps.plot(ax=ax)
+
+ def test_mixed_freq_shared_ax(self):
+ # GH13341, using sharex=True
+ idx1 = date_range("2015-01-01", periods=3, freq="ME")
+ idx2 = idx1[:1].union(idx1[2:])
+ s1 = Series(range(len(idx1)), idx1)
+ s2 = Series(range(len(idx2)), idx2)
+
+ _, (ax1, ax2) = mpl.pyplot.subplots(nrows=2, sharex=True)
+ s1.plot(ax=ax1)
+ s2.plot(ax=ax2)
+
+ assert ax1.freq == "M"
+ assert ax2.freq == "M"
+ assert ax1.lines[0].get_xydata()[0, 0] == ax2.lines[0].get_xydata()[0, 0]
+
+ def test_mixed_freq_shared_ax_twin_x(self):
+ # GH13341, using sharex=True
+ idx1 = date_range("2015-01-01", periods=3, freq="ME")
+ idx2 = idx1[:1].union(idx1[2:])
+ s1 = Series(range(len(idx1)), idx1)
+ s2 = Series(range(len(idx2)), idx2)
+ # using twinx
+ _, ax1 = mpl.pyplot.subplots()
+ ax2 = ax1.twinx()
+ s1.plot(ax=ax1)
+ s2.plot(ax=ax2)
+
+ assert ax1.lines[0].get_xydata()[0, 0] == ax2.lines[0].get_xydata()[0, 0]
+
+ @pytest.mark.xfail(reason="TODO (GH14330, GH14322)")
+ def test_mixed_freq_shared_ax_twin_x_irregular_first(self):
+ # GH13341, using sharex=True
+ idx1 = date_range("2015-01-01", periods=3, freq="ME")
+ idx2 = idx1[:1].union(idx1[2:])
+ s1 = Series(range(len(idx1)), idx1)
+ s2 = Series(range(len(idx2)), idx2)
+ _, ax1 = mpl.pyplot.subplots()
+ ax2 = ax1.twinx()
+ s2.plot(ax=ax1)
+ s1.plot(ax=ax2)
+ assert ax1.lines[0].get_xydata()[0, 0] == ax2.lines[0].get_xydata()[0, 0]
+
+ def test_nat_handling(self):
+ _, ax = mpl.pyplot.subplots()
+
+ dti = DatetimeIndex(["2015-01-01", NaT, "2015-01-03"])
+ s = Series(range(len(dti)), dti)
+ s.plot(ax=ax)
+ xdata = ax.get_lines()[0].get_xdata()
+ # plot x data is bounded by index values
+ assert s.index.min() <= Series(xdata).min()
+ assert Series(xdata).max() <= s.index.max()
+
+ def test_to_weekly_resampling_disallow_how_kwd(self):
+ idxh = date_range("1/1/1999", periods=52, freq="W")
+ idxl = date_range("1/1/1999", periods=12, freq="ME")
+ high = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ low = Series(np.random.default_rng(2).standard_normal(len(idxl)), idxl)
+ _, ax = mpl.pyplot.subplots()
+ high.plot(ax=ax)
+
+ msg = (
+ "'how' is not a valid keyword for plotting functions. If plotting "
+ "multiple objects on shared axes, resample manually first."
+ )
+ with pytest.raises(ValueError, match=msg):
+ low.plot(ax=ax, how="foo")
+
+ def test_to_weekly_resampling(self):
+ idxh = date_range("1/1/1999", periods=52, freq="W")
+ idxl = date_range("1/1/1999", periods=12, freq="ME")
+ high = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ low = Series(np.random.default_rng(2).standard_normal(len(idxl)), idxl)
+ _, ax = mpl.pyplot.subplots()
+ high.plot(ax=ax)
+ low.plot(ax=ax)
+ for line in ax.get_lines():
+ assert PeriodIndex(data=line.get_xdata()).freq == idxh.freq
+
+ def test_from_weekly_resampling(self):
+ idxh = date_range("1/1/1999", periods=52, freq="W")
+ idxl = date_range("1/1/1999", periods=12, freq="ME")
+ high = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ low = Series(np.random.default_rng(2).standard_normal(len(idxl)), idxl)
+ _, ax = mpl.pyplot.subplots()
+ low.plot(ax=ax)
+ high.plot(ax=ax)
+
+ expected_h = idxh.to_period().asi8.astype(np.float64)
+ expected_l = np.array(
+ [1514, 1519, 1523, 1527, 1531, 1536, 1540, 1544, 1549, 1553, 1558, 1562],
+ dtype=np.float64,
+ )
+ for line in ax.get_lines():
+ assert PeriodIndex(data=line.get_xdata()).freq == idxh.freq
+ xdata = line.get_xdata(orig=False)
+ if len(xdata) == 12: # idxl lines
+ tm.assert_numpy_array_equal(xdata, expected_l)
+ else:
+ tm.assert_numpy_array_equal(xdata, expected_h)
+
+ @pytest.mark.parametrize("kind1, kind2", [("line", "area"), ("area", "line")])
+ def test_from_resampling_area_line_mixed(self, kind1, kind2):
+ idxh = date_range("1/1/1999", periods=52, freq="W")
+ idxl = date_range("1/1/1999", periods=12, freq="ME")
+ high = DataFrame(
+ np.random.default_rng(2).random((len(idxh), 3)),
+ index=idxh,
+ columns=[0, 1, 2],
+ )
+ low = DataFrame(
+ np.random.default_rng(2).random((len(idxl), 3)),
+ index=idxl,
+ columns=[0, 1, 2],
+ )
+
+ _, ax = mpl.pyplot.subplots()
+ low.plot(kind=kind1, stacked=True, ax=ax)
+ high.plot(kind=kind2, stacked=True, ax=ax)
+
+ # check low dataframe result
+ expected_x = np.array(
+ [
+ 1514,
+ 1519,
+ 1523,
+ 1527,
+ 1531,
+ 1536,
+ 1540,
+ 1544,
+ 1549,
+ 1553,
+ 1558,
+ 1562,
+ ],
+ dtype=np.float64,
+ )
+ expected_y = np.zeros(len(expected_x), dtype=np.float64)
+ for i in range(3):
+ line = ax.lines[i]
+ assert PeriodIndex(line.get_xdata()).freq == idxh.freq
+ tm.assert_numpy_array_equal(line.get_xdata(orig=False), expected_x)
+ # check stacked values are correct
+ expected_y += low[i].values
+ tm.assert_numpy_array_equal(line.get_ydata(orig=False), expected_y)
+
+ # check high dataframe result
+ expected_x = idxh.to_period().asi8.astype(np.float64)
+ expected_y = np.zeros(len(expected_x), dtype=np.float64)
+ for i in range(3):
+ line = ax.lines[3 + i]
+ assert PeriodIndex(data=line.get_xdata()).freq == idxh.freq
+ tm.assert_numpy_array_equal(line.get_xdata(orig=False), expected_x)
+ expected_y += high[i].values
+ tm.assert_numpy_array_equal(line.get_ydata(orig=False), expected_y)
+
+ @pytest.mark.parametrize("kind1, kind2", [("line", "area"), ("area", "line")])
+ def test_from_resampling_area_line_mixed_high_to_low(self, kind1, kind2):
+ idxh = date_range("1/1/1999", periods=52, freq="W")
+ idxl = date_range("1/1/1999", periods=12, freq="ME")
+ high = DataFrame(
+ np.random.default_rng(2).random((len(idxh), 3)),
+ index=idxh,
+ columns=[0, 1, 2],
+ )
+ low = DataFrame(
+ np.random.default_rng(2).random((len(idxl), 3)),
+ index=idxl,
+ columns=[0, 1, 2],
+ )
+ _, ax = mpl.pyplot.subplots()
+ high.plot(kind=kind1, stacked=True, ax=ax)
+ low.plot(kind=kind2, stacked=True, ax=ax)
+
+ # check high dataframe result
+ expected_x = idxh.to_period().asi8.astype(np.float64)
+ expected_y = np.zeros(len(expected_x), dtype=np.float64)
+ for i in range(3):
+ line = ax.lines[i]
+ assert PeriodIndex(data=line.get_xdata()).freq == idxh.freq
+ tm.assert_numpy_array_equal(line.get_xdata(orig=False), expected_x)
+ expected_y += high[i].values
+ tm.assert_numpy_array_equal(line.get_ydata(orig=False), expected_y)
+
+ # check low dataframe result
+ expected_x = np.array(
+ [
+ 1514,
+ 1519,
+ 1523,
+ 1527,
+ 1531,
+ 1536,
+ 1540,
+ 1544,
+ 1549,
+ 1553,
+ 1558,
+ 1562,
+ ],
+ dtype=np.float64,
+ )
+ expected_y = np.zeros(len(expected_x), dtype=np.float64)
+ for i in range(3):
+ lines = ax.lines[3 + i]
+ assert PeriodIndex(data=lines.get_xdata()).freq == idxh.freq
+ tm.assert_numpy_array_equal(lines.get_xdata(orig=False), expected_x)
+ expected_y += low[i].values
+ tm.assert_numpy_array_equal(lines.get_ydata(orig=False), expected_y)
+
+ def test_mixed_freq_second_millisecond(self):
+ # GH 7772, GH 7760
+ idxh = date_range("2014-07-01 09:00", freq="s", periods=5)
+ idxl = date_range("2014-07-01 09:00", freq="100ms", periods=50)
+ high = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ low = Series(np.random.default_rng(2).standard_normal(len(idxl)), idxl)
+ # high to low
+ _, ax = mpl.pyplot.subplots()
+ high.plot(ax=ax)
+ low.plot(ax=ax)
+ assert len(ax.get_lines()) == 2
+ for line in ax.get_lines():
+ assert PeriodIndex(data=line.get_xdata()).freq == "ms"
+
+ def test_mixed_freq_second_millisecond_low_to_high(self):
+ # GH 7772, GH 7760
+ idxh = date_range("2014-07-01 09:00", freq="s", periods=5)
+ idxl = date_range("2014-07-01 09:00", freq="100ms", periods=50)
+ high = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ low = Series(np.random.default_rng(2).standard_normal(len(idxl)), idxl)
+ # low to high
+ _, ax = mpl.pyplot.subplots()
+ low.plot(ax=ax)
+ high.plot(ax=ax)
+ assert len(ax.get_lines()) == 2
+ for line in ax.get_lines():
+ assert PeriodIndex(data=line.get_xdata()).freq == "ms"
+
+ def test_irreg_dtypes(self):
+ # date
+ idx = [date(2000, 1, 1), date(2000, 1, 5), date(2000, 1, 20)]
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(idx), 3)),
+ Index(idx, dtype=object),
+ )
+ _check_plot_works(df.plot)
+
+ def test_irreg_dtypes_dt64(self):
+ # np.datetime64
+ idx = date_range("1/1/2000", periods=10)
+ idx = idx[[0, 2, 5, 9]].astype(object)
+ df = DataFrame(np.random.default_rng(2).standard_normal((len(idx), 3)), idx)
+ _, ax = mpl.pyplot.subplots()
+ _check_plot_works(df.plot, ax=ax)
+
+ def test_time(self):
+ t = datetime(1, 1, 1, 3, 30, 0)
+ deltas = np.random.default_rng(2).integers(1, 20, 3).cumsum()
+ ts = np.array([(t + timedelta(minutes=int(x))).time() for x in deltas])
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(len(ts)),
+ "b": np.random.default_rng(2).standard_normal(len(ts)),
+ },
+ index=ts,
+ )
+ _, ax = mpl.pyplot.subplots()
+ df.plot(ax=ax)
+
+ # verify tick labels
+ ticks = ax.get_xticks()
+ labels = ax.get_xticklabels()
+ for _tick, _label in zip(ticks, labels, strict=True):
+ m, s = divmod(int(_tick), 60)
+ h, m = divmod(m, 60)
+ rs = _label.get_text()
+ if len(rs) > 0:
+ if s != 0:
+ xp = time(h, m, s).strftime("%H:%M:%S")
+ else:
+ xp = time(h, m, s).strftime("%H:%M")
+ assert xp == rs
+
+ def test_time_change_xlim(self):
+ t = datetime(1, 1, 1, 3, 30, 0)
+ deltas = np.random.default_rng(2).integers(1, 20, 3).cumsum()
+ ts = np.array([(t + timedelta(minutes=int(x))).time() for x in deltas])
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(len(ts)),
+ "b": np.random.default_rng(2).standard_normal(len(ts)),
+ },
+ index=ts,
+ )
+ _, ax = mpl.pyplot.subplots()
+ df.plot(ax=ax)
+
+ # verify tick labels
+ ticks = ax.get_xticks()
+ labels = ax.get_xticklabels()
+ for _tick, _label in zip(ticks, labels, strict=True):
+ m, s = divmod(int(_tick), 60)
+ h, m = divmod(m, 60)
+ rs = _label.get_text()
+ if len(rs) > 0:
+ if s != 0:
+ xp = time(h, m, s).strftime("%H:%M:%S")
+ else:
+ xp = time(h, m, s).strftime("%H:%M")
+ assert xp == rs
+
+ # change xlim
+ ax.set_xlim("1:30", "5:00")
+
+ # check tick labels again
+ ticks = ax.get_xticks()
+ labels = ax.get_xticklabels()
+ for _tick, _label in zip(ticks, labels, strict=True):
+ m, s = divmod(int(_tick), 60)
+ h, m = divmod(m, 60)
+ rs = _label.get_text()
+ if len(rs) > 0:
+ if s != 0:
+ xp = time(h, m, s).strftime("%H:%M:%S")
+ else:
+ xp = time(h, m, s).strftime("%H:%M")
+ assert xp == rs
+
+ def test_time_musec(self):
+ t = datetime(1, 1, 1, 3, 30, 0)
+ deltas = np.random.default_rng(2).integers(1, 20, 3).cumsum()
+ ts = np.array([(t + timedelta(microseconds=int(x))).time() for x in deltas])
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(len(ts)),
+ "b": np.random.default_rng(2).standard_normal(len(ts)),
+ },
+ index=ts,
+ )
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot(ax=ax)
+
+ # verify tick labels
+ ticks = ax.get_xticks()
+ labels = ax.get_xticklabels()
+ for _tick, _label in zip(ticks, labels, strict=True):
+ m, s = divmod(int(_tick), 60)
+
+ us = round((_tick - int(_tick)) * 1e6)
+
+ h, m = divmod(m, 60)
+ rs = _label.get_text()
+ if len(rs) > 0:
+ if (us % 1000) != 0:
+ xp = time(h, m, s, us).strftime("%H:%M:%S.%f")
+ elif (us // 1000) != 0:
+ xp = time(h, m, s, us).strftime("%H:%M:%S.%f")[:-3]
+ elif s != 0:
+ xp = time(h, m, s, us).strftime("%H:%M:%S")
+ else:
+ xp = time(h, m, s, us).strftime("%H:%M")
+ assert xp == rs
+
+ def test_secondary_upsample(self):
+ idxh = date_range("1/1/1999", periods=365, freq="D")
+ idxl = date_range("1/1/1999", periods=12, freq="ME")
+ high = Series(np.random.default_rng(2).standard_normal(len(idxh)), idxh)
+ low = Series(np.random.default_rng(2).standard_normal(len(idxl)), idxl)
+ _, ax = mpl.pyplot.subplots()
+ low.plot(ax=ax)
+ ax = high.plot(secondary_y=True, ax=ax)
+ for line in ax.get_lines():
+ assert PeriodIndex(line.get_xdata()).freq == "D"
+ assert hasattr(ax, "left_ax")
+ assert not hasattr(ax, "right_ax")
+ for line in ax.left_ax.get_lines():
+ assert PeriodIndex(line.get_xdata()).freq == "D"
+
+ def test_secondary_legend(self):
+ fig = mpl.pyplot.figure()
+ ax = fig.add_subplot(211)
+
+ # ts
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ df.plot(secondary_y=["A", "B"], ax=ax)
+ leg = ax.get_legend()
+ assert len(leg.get_lines()) == 4
+ assert leg.get_texts()[0].get_text() == "A (right)"
+ assert leg.get_texts()[1].get_text() == "B (right)"
+ assert leg.get_texts()[2].get_text() == "C"
+ assert leg.get_texts()[3].get_text() == "D"
+ assert ax.right_ax.get_legend() is None
+ colors = set()
+ for line in leg.get_lines():
+ colors.add(line.get_color())
+
+ # TODO: color cycle problems
+ assert len(colors) == 4
+
+ def test_secondary_legend_right(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ fig = mpl.pyplot.figure()
+ ax = fig.add_subplot(211)
+ df.plot(secondary_y=["A", "C"], mark_right=False, ax=ax)
+ leg = ax.get_legend()
+ assert len(leg.get_lines()) == 4
+ assert leg.get_texts()[0].get_text() == "A"
+ assert leg.get_texts()[1].get_text() == "B"
+ assert leg.get_texts()[2].get_text() == "C"
+ assert leg.get_texts()[3].get_text() == "D"
+
+ def test_secondary_legend_bar(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ fig, ax = mpl.pyplot.subplots()
+ df.plot(kind="bar", secondary_y=["A"], ax=ax)
+ leg = ax.get_legend()
+ assert leg.get_texts()[0].get_text() == "A (right)"
+ assert leg.get_texts()[1].get_text() == "B"
+
+ def test_secondary_legend_bar_right(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ fig, ax = mpl.pyplot.subplots()
+ df.plot(kind="bar", secondary_y=["A"], mark_right=False, ax=ax)
+ leg = ax.get_legend()
+ assert leg.get_texts()[0].get_text() == "A"
+ assert leg.get_texts()[1].get_text() == "B"
+
+ def test_secondary_legend_multi_col(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ fig = mpl.pyplot.figure()
+ ax = fig.add_subplot(211)
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ ax = df.plot(secondary_y=["C", "D"], ax=ax)
+ leg = ax.get_legend()
+ assert len(leg.get_lines()) == 4
+ assert ax.right_ax.get_legend() is None
+ colors = set()
+ for line in leg.get_lines():
+ colors.add(line.get_color())
+
+ # TODO: color cycle problems
+ assert len(colors) == 4
+
+ def test_secondary_legend_nonts(self):
+ # non-ts
+ df = DataFrame(
+ 1.1 * np.arange(40).reshape((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(10)], dtype=object),
+ )
+ fig = mpl.pyplot.figure()
+ ax = fig.add_subplot(211)
+ ax = df.plot(secondary_y=["A", "B"], ax=ax)
+ leg = ax.get_legend()
+ assert len(leg.get_lines()) == 4
+ assert ax.right_ax.get_legend() is None
+ colors = set()
+ for line in leg.get_lines():
+ colors.add(line.get_color())
+
+ # TODO: color cycle problems
+ assert len(colors) == 4
+
+ def test_secondary_legend_nonts_multi_col(self):
+ # non-ts
+ df = DataFrame(
+ 1.1 * np.arange(40).reshape((10, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(10)], dtype=object),
+ )
+ fig = mpl.pyplot.figure()
+ ax = fig.add_subplot(211)
+ ax = df.plot(secondary_y=["C", "D"], ax=ax)
+ leg = ax.get_legend()
+ assert len(leg.get_lines()) == 4
+ assert ax.right_ax.get_legend() is None
+ colors = set()
+ for line in leg.get_lines():
+ colors.add(line.get_color())
+
+ # TODO: color cycle problems
+ assert len(colors) == 4
+
+ @pytest.mark.xfail(reason="Api changed in 3.6.0")
+ def test_format_date_axis(self):
+ rng = date_range("1/1/2012", periods=12, freq="ME")
+ df = DataFrame(np.random.default_rng(2).standard_normal((len(rng), 3)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot(ax=ax)
+ xaxis = ax.get_xaxis()
+ for line in xaxis.get_ticklabels():
+ if len(line.get_text()) > 0:
+ assert line.get_rotation() == 30
+
+ def test_ax_plot(self):
+ x = date_range(start="2012-01-02", periods=10, freq="D")
+ y = list(range(len(x)))
+ _, ax = mpl.pyplot.subplots()
+ lines = ax.plot(x, y, label="Y")
+ tm.assert_index_equal(DatetimeIndex(lines[0].get_xdata()), x)
+
+ def test_mpl_nopandas(self):
+ dates = [date(2008, 12, 31), date(2009, 1, 31)]
+ values1 = np.arange(10.0, 11.0, 0.5)
+ values2 = np.arange(11.0, 12.0, 0.5)
+
+ _, ax = mpl.pyplot.subplots()
+ (
+ line1,
+ line2,
+ ) = ax.plot(
+ [x.toordinal() for x in dates],
+ values1,
+ "-",
+ [x.toordinal() for x in dates],
+ values2,
+ "-",
+ linewidth=4,
+ )
+
+ exp = np.array([x.toordinal() for x in dates], dtype=np.float64)
+ tm.assert_numpy_array_equal(line1.get_xydata()[:, 0], exp)
+ tm.assert_numpy_array_equal(line2.get_xydata()[:, 0], exp)
+
+ def test_irregular_ts_shared_ax_xlim(self):
+ # GH 2960
+ ts = Series(
+ np.arange(20, dtype=np.float64), index=date_range("2020-01-01", periods=20)
+ )
+ ts_irregular = ts.iloc[[1, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15, 17, 18]]
+
+ # plot the left section of the irregular series, then the right section
+ _, ax = mpl.pyplot.subplots()
+ ts_irregular[:5].plot(ax=ax)
+ ts_irregular[5:].plot(ax=ax)
+
+ # check that axis limits are correct
+ left, right = ax.get_xlim()
+ assert left <= conv.DatetimeConverter.convert(ts_irregular.index.min(), "", ax)
+ assert right >= conv.DatetimeConverter.convert(ts_irregular.index.max(), "", ax)
+
+ def test_secondary_y_non_ts_xlim(self):
+ # GH 3490 - non-timeseries with secondary y
+ index_1 = [1, 2, 3, 4]
+ index_2 = [5, 6, 7, 8]
+ s1 = Series(1, index=index_1)
+ s2 = Series(2, index=index_2)
+
+ _, ax = mpl.pyplot.subplots()
+ s1.plot(ax=ax)
+ left_before, right_before = ax.get_xlim()
+ s2.plot(secondary_y=True, ax=ax)
+ left_after, right_after = ax.get_xlim()
+
+ assert left_before >= left_after
+ assert right_before < right_after
+
+ def test_secondary_y_regular_ts_xlim(self):
+ # GH 3490 - regular-timeseries with secondary y
+ index_1 = date_range(start="2000-01-01", periods=4, freq="D")
+ index_2 = date_range(start="2000-01-05", periods=4, freq="D")
+ s1 = Series(1, index=index_1)
+ s2 = Series(2, index=index_2)
+
+ _, ax = mpl.pyplot.subplots()
+ s1.plot(ax=ax)
+ left_before, right_before = ax.get_xlim()
+ s2.plot(secondary_y=True, ax=ax)
+ left_after, right_after = ax.get_xlim()
+
+ assert left_before >= left_after
+ assert right_before < right_after
+
+ def test_secondary_y_mixed_freq_ts_xlim(self):
+ # GH 3490 - mixed frequency timeseries with secondary y
+ rng = date_range("2000-01-01", periods=10, freq="min")
+ ts = Series(1, index=rng)
+
+ _, ax = mpl.pyplot.subplots()
+ ts.plot(ax=ax)
+ left_before, right_before = ax.get_xlim()
+ ts.resample("D").mean().plot(secondary_y=True, ax=ax)
+ left_after, right_after = ax.get_xlim()
+
+ # a downsample should not have changed either limit
+ assert left_before == left_after
+ assert right_before == right_after
+
+ def test_secondary_y_irregular_ts_xlim(self):
+ # GH 3490 - irregular-timeseries with secondary y
+ ts = Series(
+ np.arange(20, dtype=np.float64), index=date_range("2020-01-01", periods=20)
+ )
+ ts_irregular = ts.iloc[[1, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15, 17, 18]]
+
+ _, ax = mpl.pyplot.subplots()
+ ts_irregular[:5].plot(ax=ax)
+ # plot higher-x values on secondary axis
+ ts_irregular[5:].plot(secondary_y=True, ax=ax)
+ # ensure secondary limits aren't overwritten by plot on primary
+ ts_irregular[:5].plot(ax=ax)
+
+ left, right = ax.get_xlim()
+ assert left <= conv.DatetimeConverter.convert(ts_irregular.index.min(), "", ax)
+ assert right >= conv.DatetimeConverter.convert(ts_irregular.index.max(), "", ax)
+
+ def test_plot_outofbounds_datetime(self):
+ # 2579 - checking this does not raise
+ values = [date(1677, 1, 1), date(1677, 1, 2)]
+ _, ax = mpl.pyplot.subplots()
+ ax.plot(values)
+
+ values = [datetime(1677, 1, 1, 12), datetime(1677, 1, 2, 12)]
+ ax.plot(values)
+
+ def test_format_timedelta_ticks_narrow(self):
+ expected_labels = [f"00:00:00.0000000{i:0>2d}" for i in np.arange(10)]
+
+ rng = timedelta_range("0", periods=10, freq="ns")
+ df = DataFrame(np.random.default_rng(2).standard_normal((len(rng), 3)), rng)
+ _, ax = mpl.pyplot.subplots()
+ df.plot(fontsize=2, ax=ax)
+ mpl.pyplot.draw()
+ labels = ax.get_xticklabels()
+
+ result_labels = [x.get_text() for x in labels]
+ assert len(result_labels) == len(expected_labels)
+ assert result_labels == expected_labels
+
+ def test_format_timedelta_ticks_wide(self, unit):
+ expected_labels = [
+ "00:00:00",
+ "1 days 03:46:40",
+ "2 days 07:33:20",
+ "3 days 11:20:00",
+ "4 days 15:06:40",
+ "5 days 18:53:20",
+ "6 days 22:40:00",
+ "8 days 02:26:40",
+ "9 days 06:13:20",
+ ]
+
+ rng = timedelta_range("0", periods=10, freq="1 D", unit=unit)
+ df = DataFrame(np.random.default_rng(2).standard_normal((len(rng), 3)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot(fontsize=2, ax=ax)
+ mpl.pyplot.draw()
+ labels = ax.get_xticklabels()
+
+ result_labels = [x.get_text() for x in labels]
+ assert len(result_labels) == len(expected_labels)
+ assert result_labels == expected_labels
+
+ def test_timedelta_plot(self):
+ # test issue #8711
+ s = Series(range(5), timedelta_range("1day", periods=5))
+ _, ax = mpl.pyplot.subplots()
+ _check_plot_works(s.plot, ax=ax)
+
+ def test_timedelta_long_period(self):
+ # test long period
+ index = timedelta_range("1 day 2 hr 30 min 10 s", periods=10, freq="1 D")
+ s = Series(np.random.default_rng(2).standard_normal(len(index)), index)
+ _, ax = mpl.pyplot.subplots()
+ _check_plot_works(s.plot, ax=ax)
+
+ def test_timedelta_short_period(self):
+ # test short period
+ index = timedelta_range("1 day 2 hr 30 min 10 s", periods=10, freq="1 ns")
+ s = Series(np.random.default_rng(2).standard_normal(len(index)), index)
+ _, ax = mpl.pyplot.subplots()
+ _check_plot_works(s.plot, ax=ax)
+
+ def test_hist(self):
+ # https://github.com/matplotlib/matplotlib/issues/8459
+ rng = date_range("1/1/2011", periods=10, freq="h")
+ x = rng
+ w1 = np.arange(0, 1, 0.1)
+ w2 = np.arange(0, 1, 0.1)[::-1]
+ _, ax = mpl.pyplot.subplots()
+ ax.hist([x, x], weights=[w1, w2])
+
+ def test_overlapping_datetime(self):
+ # GB 6608
+ s1 = Series(
+ [1, 2, 3],
+ index=[
+ datetime(1995, 12, 31),
+ datetime(2000, 12, 31),
+ datetime(2005, 12, 31),
+ ],
+ )
+ s2 = Series(
+ [1, 2, 3],
+ index=[
+ datetime(1997, 12, 31),
+ datetime(2003, 12, 31),
+ datetime(2008, 12, 31),
+ ],
+ )
+
+ # plot first series, then add the second series to those axes,
+ # then try adding the first series again
+ _, ax = mpl.pyplot.subplots()
+ s1.plot(ax=ax)
+ s2.plot(ax=ax)
+ s1.plot(ax=ax)
+
+ @pytest.mark.xfail(reason="GH9053 matplotlib does not use ax.xaxis.converter")
+ def test_add_matplotlib_datetime64(self):
+ # GH9053 - ensure that a plot with PeriodConverter still understands
+ # datetime64 data. This still fails because matplotlib overrides the
+ # ax.xaxis.converter with a DatetimeConverter
+ s = Series(
+ np.random.default_rng(2).standard_normal(10),
+ index=date_range("1970-01-02", periods=10),
+ )
+ ax = s.plot()
+ with tm.assert_produces_warning(DeprecationWarning):
+ # multi-dimensional indexing
+ ax.plot(s.index, s.values, color="g")
+ l1, l2 = ax.lines
+ tm.assert_numpy_array_equal(l1.get_xydata(), l2.get_xydata())
+
+ def test_matplotlib_scatter_datetime64(self):
+ # https://github.com/matplotlib/matplotlib/issues/11391
+ df = DataFrame(np.random.default_rng(2).random((10, 2)), columns=["x", "y"])
+ df["time"] = date_range("2018-01-01", periods=10, freq="D")
+ _, ax = mpl.pyplot.subplots()
+ ax.scatter(x="time", y="y", data=df)
+ mpl.pyplot.draw()
+ label = ax.get_xticklabels()[0]
+ expected = "2018-01-01"
+ assert label.get_text() == expected
+
+ def test_check_xticks_rot(self):
+ # https://github.com/pandas-dev/pandas/issues/29460
+ # regular time series
+ x = to_datetime(["2020-05-01", "2020-05-02", "2020-05-03"])
+ df = DataFrame({"x": x, "y": [1, 2, 3]})
+ axes = df.plot(x="x", y="y")
+ _check_ticks_props(axes, xrot=0)
+
+ def test_check_xticks_rot_irregular(self):
+ # irregular time series
+ x = to_datetime(["2020-05-01", "2020-05-02", "2020-05-04"])
+ df = DataFrame({"x": x, "y": [1, 2, 3]})
+ axes = df.plot(x="x", y="y")
+ _check_ticks_props(axes, xrot=30)
+
+ def test_check_xticks_rot_use_idx(self):
+ # irregular time series
+ x = to_datetime(["2020-05-01", "2020-05-02", "2020-05-04"])
+ df = DataFrame({"x": x, "y": [1, 2, 3]})
+ # use timeseries index or not
+ axes = df.set_index("x").plot(y="y", use_index=True)
+ _check_ticks_props(axes, xrot=30)
+ axes = df.set_index("x").plot(y="y", use_index=False)
+ _check_ticks_props(axes, xrot=0)
+
+ def test_check_xticks_rot_sharex(self):
+ # irregular time series
+ x = to_datetime(["2020-05-01", "2020-05-02", "2020-05-04"])
+ df = DataFrame({"x": x, "y": [1, 2, 3]})
+ # separate subplots
+ axes = df.plot(x="x", y="y", subplots=True, sharex=True)
+ _check_ticks_props(axes, xrot=30)
+ axes = df.plot(x="x", y="y", subplots=True, sharex=False)
+ _check_ticks_props(axes, xrot=0)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [
+ date_range("2020-01-01", periods=5),
+ date_range("2020-01-01", periods=5, tz="UTC"),
+ timedelta_range("1 day", periods=5, freq="D"),
+ period_range("2020-01-01", periods=5, freq="D"),
+ Index([date(2000, 1, i) for i in [1, 3, 6, 20, 22]], dtype=object),
+ range(5),
+ ],
+ )
+ def test_pickle_fig(self, temp_file, frame_or_series, idx):
+ # GH18439, GH#24088, statsmodels#4772
+ df = frame_or_series(range(5), index=idx)
+ fig, ax = plt.subplots(1, 1)
+ df.plot(ax=ax)
+ with temp_file.open(mode="wb") as path:
+ pickle.dump(fig, path)
+
+
+def _check_plot_works(f, freq=None, series=None, *args, **kwargs):
+ fig = plt.gcf()
+
+ fig.clf()
+ ax = fig.add_subplot(211)
+ orig_ax = kwargs.pop("ax", plt.gca())
+ orig_axfreq = getattr(orig_ax, "freq", None)
+
+ ret = f(*args, **kwargs)
+ assert ret is not None # do something more intelligent
+
+ ax = kwargs.pop("ax", plt.gca())
+ if series is not None:
+ dfreq = series.index.freq
+ if isinstance(dfreq, BaseOffset):
+ dfreq = dfreq.rule_code
+ if orig_axfreq is None:
+ assert ax.freq == dfreq
+
+ if freq is not None and orig_axfreq is None:
+ assert to_offset(ax.freq, is_period=True) == freq
+
+ ax = fig.add_subplot(212)
+ kwargs["ax"] = ax
+ ret = f(*args, **kwargs)
+ assert ret is not None # TODO: do something more intelligent
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_groupby.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_groupby.py
new file mode 100644
index 0000000000000000000000000000000000000000..e86c4e9838d2459f2ad87fcf97a4c46bad2e820d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_groupby.py
@@ -0,0 +1,154 @@
+"""Test cases for GroupBy.plot"""
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+)
+from pandas.tests.plotting.common import (
+ _check_axes_shape,
+ _check_legend_labels,
+)
+
+pytest.importorskip("matplotlib")
+
+
+class TestDataFrameGroupByPlots:
+ def test_series_groupby_plotting_nominally_works(self):
+ n = 10
+ weight = Series(np.random.default_rng(2).normal(166, 20, size=n))
+ gender = np.random.default_rng(2).choice(["male", "female"], size=n)
+
+ weight.groupby(gender).plot()
+
+ def test_series_groupby_plotting_nominally_works_hist(self):
+ n = 10
+ height = Series(np.random.default_rng(2).normal(60, 10, size=n))
+ gender = np.random.default_rng(2).choice(["male", "female"], size=n)
+ height.groupby(gender).hist()
+
+ def test_series_groupby_plotting_nominally_works_alpha(self):
+ n = 10
+ height = Series(np.random.default_rng(2).normal(60, 10, size=n))
+ gender = np.random.default_rng(2).choice(["male", "female"], size=n)
+ # Regression test for GH8733
+ height.groupby(gender).plot(alpha=0.5)
+
+ def test_plotting_with_float_index_works(self):
+ # GH 7025
+ df = DataFrame(
+ {
+ "def": [1, 1, 1, 2, 2, 2, 3, 3, 3],
+ "val": np.random.default_rng(2).standard_normal(9),
+ },
+ index=[1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0],
+ )
+
+ df.groupby("def")["val"].plot()
+
+ def test_plotting_with_float_index_works_apply(self):
+ # GH 7025
+ df = DataFrame(
+ {
+ "def": [1, 1, 1, 2, 2, 2, 3, 3, 3],
+ "val": np.random.default_rng(2).standard_normal(9),
+ },
+ index=[1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0],
+ )
+ df.groupby("def")["val"].apply(lambda x: x.plot())
+
+ def test_hist_single_row(self):
+ # GH10214
+ bins = np.arange(80, 100 + 2, 1)
+ df = DataFrame({"Name": ["AAA", "BBB"], "ByCol": [1, 2], "Mark": [85, 89]})
+ df["Mark"].hist(by=df["ByCol"], bins=bins)
+
+ def test_hist_single_row_single_bycol(self):
+ # GH10214
+ bins = np.arange(80, 100 + 2, 1)
+ df = DataFrame({"Name": ["AAA"], "ByCol": [1], "Mark": [85]})
+ df["Mark"].hist(by=df["ByCol"], bins=bins)
+
+ def test_plot_submethod_works(self):
+ df = DataFrame({"x": [1, 2, 3, 4, 5], "y": [1, 2, 3, 2, 1], "z": list("ababa")})
+ df.groupby("z").plot.scatter("x", "y")
+
+ def test_plot_submethod_works_line(self):
+ df = DataFrame({"x": [1, 2, 3, 4, 5], "y": [1, 2, 3, 2, 1], "z": list("ababa")})
+ df.groupby("z")["x"].plot.line()
+
+ def test_plot_kwargs(self):
+ df = DataFrame({"x": [1, 2, 3, 4, 5], "y": [1, 2, 3, 2, 1], "z": list("ababa")})
+
+ res = df.groupby("z").plot(kind="scatter", x="x", y="y")
+ # check that a scatter plot is effectively plotted: the axes should
+ # contain a PathCollection from the scatter plot (GH11805)
+ assert len(res["a"].collections) == 1
+
+ def test_plot_kwargs_scatter(self):
+ df = DataFrame({"x": [1, 2, 3, 4, 5], "y": [1, 2, 3, 2, 1], "z": list("ababa")})
+ res = df.groupby("z").plot.scatter(x="x", y="y")
+ assert len(res["a"].collections) == 1
+
+ @pytest.mark.parametrize("column, expected_axes_num", [(None, 2), ("b", 1)])
+ def test_groupby_hist_frame_with_legend(self, column, expected_axes_num):
+ # GH 6279 - DataFrameGroupBy histogram can have a legend
+ expected_layout = (1, expected_axes_num)
+ expected_labels = column or [["a"], ["b"]]
+
+ index = Index(15 * ["1"] + 15 * ["2"], name="c")
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((30, 2)),
+ index=index,
+ columns=["a", "b"],
+ )
+ g = df.groupby("c")
+
+ for axes in g.hist(legend=True, column=column):
+ _check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout)
+ for ax, expected_label in zip(axes[0], expected_labels, strict=True):
+ _check_legend_labels(ax, expected_label)
+
+ @pytest.mark.parametrize("column", [None, "b"])
+ def test_groupby_hist_frame_with_legend_raises(self, column):
+ # GH 6279 - DataFrameGroupBy histogram with legend and label raises
+ index = Index(15 * ["1"] + 15 * ["2"], name="c")
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((30, 2)),
+ index=index,
+ columns=["a", "b"],
+ )
+ g = df.groupby("c")
+
+ with pytest.raises(ValueError, match="Cannot use both legend and label"):
+ g.hist(legend=True, column=column, label="d")
+
+ def test_groupby_hist_series_with_legend(self):
+ # GH 6279 - SeriesGroupBy histogram can have a legend
+ index = Index(15 * ["1"] + 15 * ["2"], name="c")
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((30, 2)),
+ index=index,
+ columns=["a", "b"],
+ )
+ g = df.groupby("c")
+
+ for ax in g["a"].hist(legend=True):
+ _check_axes_shape(ax, axes_num=1, layout=(1, 1))
+ _check_legend_labels(ax, ["1", "2"])
+
+ def test_groupby_hist_series_with_legend_raises(self):
+ # GH 6279 - SeriesGroupBy histogram with legend and label raises
+ index = Index(15 * ["1"] + 15 * ["2"], name="c")
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((30, 2)),
+ index=index,
+ columns=["a", "b"],
+ )
+ g = df.groupby("c")
+
+ with pytest.raises(ValueError, match="Cannot use both legend and label"):
+ g.hist(legend=True, label="d")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_hist_method.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_hist_method.py
new file mode 100644
index 0000000000000000000000000000000000000000..e71d4ce5475a89dd686f831afcd1e4c4d726851d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_hist_method.py
@@ -0,0 +1,957 @@
+"""Test cases for .hist method"""
+
+import re
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ date_range,
+ to_datetime,
+)
+import pandas._testing as tm
+from pandas.tests.plotting.common import (
+ _check_ax_scales,
+ _check_axes_shape,
+ _check_colors,
+ _check_legend_labels,
+ _check_patches_all_filled,
+ _check_plot_works,
+ _check_text_labels,
+ _check_ticks_props,
+ get_x_axis,
+ get_y_axis,
+)
+
+mpl = pytest.importorskip("matplotlib")
+plt = pytest.importorskip("matplotlib.pyplot")
+
+from pandas.plotting._matplotlib.hist import _grouped_hist
+
+
+@pytest.fixture
+def ts():
+ return Series(
+ np.arange(30, dtype=np.float64),
+ index=date_range("2020-01-01", periods=30, freq="B"),
+ name="ts",
+ )
+
+
+class TestSeriesPlots:
+ @pytest.mark.parametrize("kwargs", [{}, {"grid": False}, {"figsize": (8, 10)}])
+ def test_hist_legacy_kwargs(self, ts, kwargs):
+ _check_plot_works(ts.hist, **kwargs)
+
+ @pytest.mark.parametrize("kwargs", [{}, {"bins": 5}])
+ def test_hist_legacy_kwargs_warning(self, ts, kwargs):
+ # _check_plot_works adds an ax so catch warning. see GH #13188
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ _check_plot_works(ts.hist, by=ts.index.month, **kwargs)
+
+ def test_hist_legacy_ax(self, ts):
+ fig, ax = mpl.pyplot.subplots(1, 1)
+ _check_plot_works(ts.hist, ax=ax, default_axes=True)
+
+ def test_hist_legacy_ax_and_fig(self, ts):
+ fig, ax = mpl.pyplot.subplots(1, 1)
+ _check_plot_works(ts.hist, ax=ax, figure=fig, default_axes=True)
+
+ def test_hist_legacy_fig(self, ts):
+ fig, _ = mpl.pyplot.subplots(1, 1)
+ _check_plot_works(ts.hist, figure=fig, default_axes=True)
+
+ def test_hist_legacy_multi_ax(self, ts):
+ fig, (ax1, ax2) = mpl.pyplot.subplots(1, 2)
+ _check_plot_works(ts.hist, figure=fig, ax=ax1, default_axes=True)
+ _check_plot_works(ts.hist, figure=fig, ax=ax2, default_axes=True)
+
+ def test_hist_legacy_by_fig_error(self, ts):
+ fig, _ = mpl.pyplot.subplots(1, 1)
+ msg = (
+ "Cannot pass 'figure' when using the 'by' argument, since a new 'Figure' "
+ "instance will be created"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ts.hist(by=ts.index, figure=fig)
+
+ def test_hist_bins_legacy(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ ax = df.hist(bins=2)[0][0]
+ assert len(ax.patches) == 2
+
+ def test_hist_layout(self, hist_df):
+ df = hist_df
+ msg = "The 'layout' keyword is not supported when 'by' is None"
+ with pytest.raises(ValueError, match=msg):
+ df.height.hist(layout=(1, 1))
+
+ with pytest.raises(ValueError, match=msg):
+ df.height.hist(layout=[1, 1])
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "by, layout, axes_num, res_layout",
+ [
+ ["gender", (2, 1), 2, (2, 1)],
+ ["gender", (3, -1), 2, (3, 1)],
+ ["category", (4, 1), 4, (4, 1)],
+ ["category", (2, -1), 4, (2, 2)],
+ ["category", (3, -1), 4, (3, 2)],
+ ["category", (-1, 4), 4, (1, 4)],
+ ["classroom", (2, 2), 3, (2, 2)],
+ ],
+ )
+ def test_hist_layout_with_by(self, hist_df, by, layout, axes_num, res_layout):
+ df = hist_df
+
+ # _check_plot_works adds an `ax` kwarg to the method call
+ # so we get a warning about an axis being cleared, even
+ # though we don't explicitly pass one, see GH #13188
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(df.height.hist, by=getattr(df, by), layout=layout)
+ _check_axes_shape(axes, axes_num=axes_num, layout=res_layout)
+
+ def test_hist_layout_with_by_shape(self, hist_df):
+ df = hist_df
+
+ axes = df.height.hist(by=df.category, layout=(4, 2), figsize=(12, 7))
+ _check_axes_shape(axes, axes_num=4, layout=(4, 2), figsize=(12, 7))
+
+ def test_hist_no_overlap(self):
+ x = Series(np.random.default_rng(2).standard_normal(2))
+ y = Series(np.random.default_rng(2).standard_normal(2))
+ plt.subplot(121)
+ x.hist()
+ plt.subplot(122)
+ y.hist()
+ fig = plt.gcf()
+ axes = fig.axes
+ assert len(axes) == 2
+
+ def test_hist_by_no_extra_plots(self, hist_df):
+ df = hist_df
+ df.height.hist(by=df.gender)
+ assert len(mpl.pyplot.get_fignums()) == 1
+
+ def test_plot_fails_when_ax_differs_from_figure(self, ts):
+ fig1 = plt.figure(1)
+ fig2 = plt.figure(2)
+ ax1 = fig1.add_subplot(111)
+ msg = "passed axis not bound to passed figure"
+ with pytest.raises(AssertionError, match=msg):
+ ts.hist(ax=ax1, figure=fig2)
+
+ @pytest.mark.parametrize(
+ "histtype, expected",
+ [
+ ("bar", True),
+ ("barstacked", True),
+ ("step", False),
+ ("stepfilled", True),
+ ],
+ )
+ def test_histtype_argument(self, histtype, expected):
+ # GH23992 Verify functioning of histtype argument
+ ser = Series(np.random.default_rng(2).integers(1, 10))
+ ax = ser.hist(histtype=histtype)
+ _check_patches_all_filled(ax, filled=expected)
+
+ @pytest.mark.parametrize(
+ "by, expected_axes_num, expected_layout", [(None, 1, (1, 1)), ("b", 2, (1, 2))]
+ )
+ def test_hist_with_legend(self, by, expected_axes_num, expected_layout):
+ # GH 6279 - Series histogram can have a legend
+ index = 5 * ["1"] + 5 * ["2"]
+ s = Series(np.random.default_rng(2).standard_normal(10), index=index, name="a")
+ s.index.name = "b"
+
+ # Use default_axes=True when plotting method generate subplots itself
+ axes = _check_plot_works(s.hist, default_axes=True, legend=True, by=by)
+ _check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout)
+ _check_legend_labels(axes, "a")
+
+ @pytest.mark.parametrize("by", [None, "b"])
+ def test_hist_with_legend_raises(self, by):
+ # GH 6279 - Series histogram with legend and label raises
+ index = 5 * ["1"] + 5 * ["2"]
+ s = Series(np.random.default_rng(2).standard_normal(10), index=index, name="a")
+ s.index.name = "b"
+
+ with pytest.raises(ValueError, match="Cannot use both legend and label"):
+ s.hist(legend=True, by=by, label="c")
+
+ def test_hist_kwargs(self, ts):
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.hist(bins=5, ax=ax)
+ assert len(ax.patches) == 5
+ _check_text_labels(ax.yaxis.get_label(), "Frequency")
+
+ def test_hist_kwargs_horizontal(self, ts):
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.hist(bins=5, ax=ax)
+ ax = ts.plot.hist(orientation="horizontal", ax=ax)
+ _check_text_labels(ax.xaxis.get_label(), "Frequency")
+
+ def test_hist_kwargs_align(self, ts):
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.hist(bins=5, ax=ax)
+ ax = ts.plot.hist(align="left", stacked=True, ax=ax)
+
+ @pytest.mark.xfail(reason="Api changed in 3.6.0")
+ def test_hist_kde(self, ts):
+ pytest.importorskip("scipy")
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.hist(logy=True, ax=ax)
+ _check_ax_scales(ax, yaxis="log")
+ xlabels = ax.get_xticklabels()
+ # ticks are values, thus ticklabels are blank
+ _check_text_labels(xlabels, [""] * len(xlabels))
+ ylabels = ax.get_yticklabels()
+ _check_text_labels(ylabels, [""] * len(ylabels))
+
+ def test_hist_kde_plot_works(self, ts):
+ pytest.importorskip("scipy")
+ _check_plot_works(ts.plot.kde)
+
+ def test_hist_kde_density_works(self, ts):
+ pytest.importorskip("scipy")
+ _check_plot_works(ts.plot.density)
+
+ @pytest.mark.xfail(reason="Api changed in 3.6.0")
+ def test_hist_kde_logy(self, ts):
+ pytest.importorskip("scipy")
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.kde(logy=True, ax=ax)
+ _check_ax_scales(ax, yaxis="log")
+ xlabels = ax.get_xticklabels()
+ _check_text_labels(xlabels, [""] * len(xlabels))
+ ylabels = ax.get_yticklabels()
+ _check_text_labels(ylabels, [""] * len(ylabels))
+
+ def test_hist_kde_color_bins(self, ts):
+ pytest.importorskip("scipy")
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.hist(logy=True, bins=10, color="b", ax=ax)
+ _check_ax_scales(ax, yaxis="log")
+ assert len(ax.patches) == 10
+ _check_colors(ax.patches, facecolors=["b"] * 10)
+
+ def test_hist_kde_color(self, ts):
+ pytest.importorskip("scipy")
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.kde(logy=True, color="r", ax=ax)
+ _check_ax_scales(ax, yaxis="log")
+ lines = ax.get_lines()
+ assert len(lines) == 1
+ _check_colors(lines, ["r"])
+
+
+class TestDataFramePlots:
+ @pytest.mark.slow
+ def test_hist_df_legacy(self, hist_df):
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ _check_plot_works(hist_df.hist)
+
+ @pytest.mark.slow
+ def test_hist_df_legacy_layout(self):
+ # make sure layout is handled
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ df[2] = to_datetime(
+ np.random.default_rng(2).integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(df.hist, grid=False)
+ _check_axes_shape(axes, axes_num=3, layout=(2, 2))
+ assert not axes[1, 1].get_visible()
+
+ _check_plot_works(df[[2]].hist)
+
+ @pytest.mark.slow
+ def test_hist_df_legacy_layout2(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 1)))
+ _check_plot_works(df.hist)
+
+ @pytest.mark.slow
+ def test_hist_df_legacy_layout3(self):
+ # make sure layout is handled
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 5)))
+ df[5] = to_datetime(
+ np.random.default_rng(2).integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(df.hist, layout=(4, 2))
+ _check_axes_shape(axes, axes_num=6, layout=(4, 2))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "kwargs", [{"sharex": True, "sharey": True}, {"figsize": (8, 10)}, {"bins": 5}]
+ )
+ def test_hist_df_legacy_layout_kwargs(self, kwargs):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 5)))
+ df[5] = to_datetime(
+ np.random.default_rng(2).integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ # make sure sharex, sharey is handled
+ # handle figsize arg
+ # check bins argument
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ _check_plot_works(df.hist, **kwargs)
+
+ @pytest.mark.slow
+ def test_hist_df_legacy_layout_labelsize_rot(self, frame_or_series):
+ # make sure xlabelsize and xrot are handled
+ obj = frame_or_series(range(10))
+ xf, yf = 20, 18
+ xrot, yrot = 30, 40
+ axes = obj.hist(xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot)
+ _check_ticks_props(axes, xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot)
+
+ @pytest.mark.slow
+ def test_hist_df_legacy_rectangles(self):
+ ser = Series(range(10))
+ ax = ser.hist(cumulative=True, bins=4, density=True)
+ # height of last bin (index 5) must be 1.0
+ rects = [x for x in ax.get_children() if isinstance(x, mpl.patches.Rectangle)]
+ tm.assert_almost_equal(rects[-1].get_height(), 1.0)
+
+ @pytest.mark.slow
+ def test_hist_df_legacy_scale(self):
+ ser = Series(range(10))
+ ax = ser.hist(log=True)
+ # scale of y must be 'log'
+ _check_ax_scales(ax, yaxis="log")
+
+ @pytest.mark.slow
+ def test_hist_df_legacy_external_error(self):
+ ser = Series(range(10))
+ # propagate attr exception from matplotlib.Axes.hist
+ with tm.external_error_raised(AttributeError):
+ ser.hist(foo="bar")
+
+ def test_hist_non_numerical_or_datetime_raises(self):
+ # gh-10444, GH32590
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).random(10),
+ "b": np.random.default_rng(2).integers(0, 10, 10),
+ "c": to_datetime(
+ np.random.default_rng(2).integers(
+ 1582800000000000000, 1583500000000000000, 10, dtype=np.int64
+ )
+ ),
+ "d": to_datetime(
+ np.random.default_rng(2).integers(
+ 1582800000000000000, 1583500000000000000, 10, dtype=np.int64
+ ),
+ utc=True,
+ ),
+ }
+ )
+ df_o = df.astype(object)
+
+ msg = "hist method requires numerical or datetime columns, nothing to plot."
+ with pytest.raises(ValueError, match=msg):
+ df_o.hist()
+
+ @pytest.mark.parametrize(
+ "layout_test",
+ (
+ {"layout": None, "expected_size": (2, 2)}, # default is 2x2
+ {"layout": (2, 2), "expected_size": (2, 2)},
+ {"layout": (4, 1), "expected_size": (4, 1)},
+ {"layout": (1, 4), "expected_size": (1, 4)},
+ {"layout": (3, 3), "expected_size": (3, 3)},
+ {"layout": (-1, 4), "expected_size": (1, 4)},
+ {"layout": (4, -1), "expected_size": (4, 1)},
+ {"layout": (-1, 2), "expected_size": (2, 2)},
+ {"layout": (2, -1), "expected_size": (2, 2)},
+ ),
+ )
+ def test_hist_layout(self, layout_test):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ df[2] = to_datetime(
+ np.random.default_rng(2).integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ axes = df.hist(layout=layout_test["layout"])
+ expected = layout_test["expected_size"]
+ _check_axes_shape(axes, axes_num=3, layout=expected)
+
+ def test_hist_layout_error(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ df[2] = to_datetime(
+ np.random.default_rng(2).integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ # layout too small for all 4 plots
+ msg = "Layout of 1x1 must be larger than required size 3"
+ with pytest.raises(ValueError, match=msg):
+ df.hist(layout=(1, 1))
+
+ # invalid format for layout
+ msg = re.escape("Layout must be a tuple of (rows, columns)")
+ with pytest.raises(ValueError, match=msg):
+ df.hist(layout=(1,))
+ msg = "At least one dimension of layout must be positive"
+ with pytest.raises(ValueError, match=msg):
+ df.hist(layout=(-1, -1))
+
+ # GH 9351
+ def test_tight_layout(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ df[2] = to_datetime(
+ np.random.default_rng(2).integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ # Use default_axes=True when plotting method generate subplots itself
+ _check_plot_works(df.hist, default_axes=True)
+ mpl.pyplot.tight_layout()
+
+ def test_hist_subplot_xrot(self):
+ # GH 30288
+ df = DataFrame(
+ {
+ "length": [1.5, 0.5, 1.2, 0.9, 3],
+ "animal": ["pig", "rabbit", "pig", "pig", "rabbit"],
+ }
+ )
+ # Use default_axes=True when plotting method generate subplots itself
+ axes = _check_plot_works(
+ df.hist,
+ default_axes=True,
+ column="length",
+ by="animal",
+ bins=5,
+ xrot=0,
+ )
+ _check_ticks_props(axes, xrot=0)
+
+ @pytest.mark.parametrize(
+ "column, expected",
+ [
+ (None, ["width", "length", "height"]),
+ (["length", "width", "height"], ["length", "width", "height"]),
+ ],
+ )
+ def test_hist_column_order_unchanged(self, column, expected):
+ # GH29235
+
+ df = DataFrame(
+ {
+ "width": [0.7, 0.2, 0.15, 0.2, 1.1],
+ "length": [1.5, 0.5, 1.2, 0.9, 3],
+ "height": [3, 0.5, 3.4, 2, 1],
+ },
+ index=["pig", "rabbit", "duck", "chicken", "horse"],
+ )
+
+ # Use default_axes=True when plotting method generate subplots itself
+ axes = _check_plot_works(
+ df.hist,
+ default_axes=True,
+ column=column,
+ layout=(1, 3),
+ )
+ result = [axes[0, i].get_title() for i in range(3)]
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "histtype, expected",
+ [
+ ("bar", True),
+ ("barstacked", True),
+ ("step", False),
+ ("stepfilled", True),
+ ],
+ )
+ def test_histtype_argument(self, histtype, expected):
+ # GH23992 Verify functioning of histtype argument
+ df = DataFrame(
+ np.random.default_rng(2).integers(1, 10, size=(10, 2)), columns=["a", "b"]
+ )
+ ax = df.hist(histtype=histtype)
+ _check_patches_all_filled(ax, filled=expected)
+
+ @pytest.mark.parametrize("by", [None, "c"])
+ @pytest.mark.parametrize("column", [None, "b"])
+ def test_hist_with_legend(self, by, column):
+ # GH 6279 - DataFrame histogram can have a legend
+ expected_axes_num = 1 if by is None and column is not None else 2
+ expected_layout = (1, expected_axes_num)
+ expected_labels = column or ["a", "b"]
+ if by is not None:
+ expected_labels = [expected_labels] * 2
+
+ index = Index(5 * ["1"] + 5 * ["2"], name="c")
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ index=index,
+ columns=["a", "b"],
+ )
+
+ # Use default_axes=True when plotting method generate subplots itself
+ axes = _check_plot_works(
+ df.hist,
+ default_axes=True,
+ legend=True,
+ by=by,
+ column=column,
+ )
+
+ _check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout)
+ if by is None and column is None:
+ axes = axes[0]
+ for expected_label, ax in zip(expected_labels, axes, strict=True):
+ _check_legend_labels(ax, expected_label)
+
+ @pytest.mark.parametrize("by", [None, "c"])
+ @pytest.mark.parametrize("column", [None, "b"])
+ def test_hist_with_legend_raises(self, by, column):
+ # GH 6279 - DataFrame histogram with legend and label raises
+ index = Index(5 * ["1"] + 5 * ["2"], name="c")
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ index=index,
+ columns=["a", "b"],
+ )
+
+ with pytest.raises(ValueError, match="Cannot use both legend and label"):
+ df.hist(legend=True, by=by, column=column, label="d")
+
+ def test_hist_df_kwargs(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 2)))
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot.hist(bins=5, ax=ax)
+ assert len(ax.patches) == 10
+
+ def test_hist_df_with_nonnumerics(self):
+ # GH 9853
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=["A", "B", "C", "D"],
+ )
+ df["E"] = ["x", "y"] * 5
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot.hist(bins=5, ax=ax)
+ assert len(ax.patches) == 20
+
+ def test_hist_df_with_nonnumerics_no_bins(self):
+ # GH 9853
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=["A", "B", "C", "D"],
+ )
+ df["E"] = ["x", "y"] * 5
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot.hist(ax=ax) # bins=10
+ assert len(ax.patches) == 40
+
+ def test_hist_secondary_legend(self):
+ # GH 9610
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)), columns=list("abcd")
+ )
+
+ # primary -> secondary
+ _, ax = mpl.pyplot.subplots()
+ ax = df["a"].plot.hist(legend=True, ax=ax)
+ df["b"].plot.hist(ax=ax, legend=True, secondary_y=True)
+ # both legends are drawn on left ax
+ # left and right axis must be visible
+ _check_legend_labels(ax, labels=["a", "b (right)"])
+ assert ax.get_yaxis().get_visible()
+ assert ax.right_ax.get_yaxis().get_visible()
+
+ def test_hist_secondary_secondary(self):
+ # GH 9610
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)), columns=list("abcd")
+ )
+ # secondary -> secondary
+ _, ax = mpl.pyplot.subplots()
+ ax = df["a"].plot.hist(legend=True, secondary_y=True, ax=ax)
+ df["b"].plot.hist(ax=ax, legend=True, secondary_y=True)
+ # both legends are draw on left ax
+ # left axis must be invisible, right axis must be visible
+ _check_legend_labels(ax.left_ax, labels=["a (right)", "b (right)"])
+ assert not ax.left_ax.get_yaxis().get_visible()
+ assert ax.get_yaxis().get_visible()
+
+ def test_hist_secondary_primary(self):
+ # GH 9610
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)), columns=list("abcd")
+ )
+ # secondary -> primary
+ _, ax = mpl.pyplot.subplots()
+ ax = df["a"].plot.hist(legend=True, secondary_y=True, ax=ax)
+ # right axes is returned
+ df["b"].plot.hist(ax=ax, legend=True)
+ # both legends are draw on left ax
+ # left and right axis must be visible
+ _check_legend_labels(ax.left_ax, labels=["a (right)", "b"])
+ assert ax.left_ax.get_yaxis().get_visible()
+ assert ax.get_yaxis().get_visible()
+
+ def test_hist_with_nans_and_weights(self):
+ # GH 48884
+ df = DataFrame(
+ [[np.nan, 0.2, 0.3], [0.4, np.nan, np.nan], [0.7, 0.8, 0.9]],
+ columns=list("abc"),
+ )
+ weights = np.array([0.25, 0.3, 0.45])
+ no_nan_df = DataFrame([[0.4, 0.2, 0.3], [0.7, 0.8, 0.9]], columns=list("abc"))
+ no_nan_weights = np.array([[0.3, 0.25, 0.25], [0.45, 0.45, 0.45]])
+
+ _, ax0 = mpl.pyplot.subplots()
+ df.plot.hist(ax=ax0, weights=weights)
+ rects = [x for x in ax0.get_children() if isinstance(x, mpl.patches.Rectangle)]
+ heights = [rect.get_height() for rect in rects]
+ _, ax1 = mpl.pyplot.subplots()
+ no_nan_df.plot.hist(ax=ax1, weights=no_nan_weights)
+ no_nan_rects = [
+ x for x in ax1.get_children() if isinstance(x, mpl.patches.Rectangle)
+ ]
+ no_nan_heights = [rect.get_height() for rect in no_nan_rects]
+ assert all(h0 == h1 for h0, h1 in zip(heights, no_nan_heights, strict=True))
+
+ idxerror_weights = np.array([[0.3, 0.25], [0.45, 0.45]])
+
+ msg = "weights must have the same shape as data, or be a single column"
+ _, ax2 = mpl.pyplot.subplots()
+ with pytest.raises(ValueError, match=msg):
+ no_nan_df.plot.hist(ax=ax2, weights=idxerror_weights)
+
+
+class TestDataFrameGroupByPlots:
+ def test_grouped_hist_legacy(self):
+ rs = np.random.default_rng(10)
+ df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
+ df["B"] = to_datetime(
+ rs.integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ df["C"] = rs.integers(0, 4, 10)
+ df["D"] = ["X"] * 10
+
+ axes = _grouped_hist(df.A, by=df.C)
+ _check_axes_shape(axes, axes_num=4, layout=(2, 2))
+
+ def test_grouped_hist_legacy_axes_shape_no_col(self):
+ rs = np.random.default_rng(10)
+ df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
+ df["B"] = to_datetime(
+ rs.integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ df["C"] = rs.integers(0, 4, 10)
+ df["D"] = ["X"] * 10
+ axes = df.hist(by=df.C)
+ _check_axes_shape(axes, axes_num=4, layout=(2, 2))
+
+ def test_grouped_hist_legacy_single_key(self):
+ rs = np.random.default_rng(2)
+ df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
+ df["B"] = to_datetime(
+ rs.integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ df["C"] = rs.integers(0, 4, 10)
+ df["D"] = ["X"] * 10
+ # group by a key with single value
+ axes = df.hist(by="D", rot=30)
+ _check_axes_shape(axes, axes_num=1, layout=(1, 1))
+ _check_ticks_props(axes, xrot=30)
+
+ def test_grouped_hist_legacy_grouped_hist_kwargs(self):
+ rs = np.random.default_rng(2)
+ df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
+ df["B"] = to_datetime(
+ rs.integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ df["C"] = rs.integers(0, 4, 10)
+ # make sure kwargs to hist are handled
+ xf, yf = 20, 18
+ xrot, yrot = 30, 40
+
+ axes = _grouped_hist(
+ df.A,
+ by=df.C,
+ cumulative=True,
+ bins=4,
+ xlabelsize=xf,
+ xrot=xrot,
+ ylabelsize=yf,
+ yrot=yrot,
+ density=True,
+ )
+ # height of last bin (index 5) must be 1.0
+ for ax in axes.ravel():
+ rects = [
+ x for x in ax.get_children() if isinstance(x, mpl.patches.Rectangle)
+ ]
+ height = rects[-1].get_height()
+ tm.assert_almost_equal(height, 1.0)
+ _check_ticks_props(axes, xlabelsize=xf, xrot=xrot, ylabelsize=yf, yrot=yrot)
+
+ def test_grouped_hist_legacy_grouped_hist(self):
+ rs = np.random.default_rng(2)
+ df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
+ df["B"] = to_datetime(
+ rs.integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ df["C"] = rs.integers(0, 4, 10)
+ df["D"] = ["X"] * 10
+ axes = _grouped_hist(df.A, by=df.C, log=True)
+ # scale of y must be 'log'
+ _check_ax_scales(axes, yaxis="log")
+
+ def test_grouped_hist_legacy_external_err(self):
+ rs = np.random.default_rng(2)
+ df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
+ df["B"] = to_datetime(
+ rs.integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ df["C"] = rs.integers(0, 4, 10)
+ df["D"] = ["X"] * 10
+ # propagate attr exception from matplotlib.Axes.hist
+ with tm.external_error_raised(AttributeError):
+ _grouped_hist(df.A, by=df.C, foo="bar")
+
+ def test_grouped_hist_legacy_figsize_err(self):
+ rs = np.random.default_rng(2)
+ df = DataFrame(rs.standard_normal((10, 1)), columns=["A"])
+ df["B"] = to_datetime(
+ rs.integers(
+ 812419200000000000,
+ 819331200000000000,
+ size=10,
+ dtype=np.int64,
+ )
+ )
+ df["C"] = rs.integers(0, 4, 10)
+ df["D"] = ["X"] * 10
+ msg = "Specify figure size by tuple instead"
+ with pytest.raises(ValueError, match=msg):
+ df.hist(by="C", figsize="default")
+
+ def test_grouped_hist_legacy2(self):
+ n = 10
+ weight = Series(np.random.default_rng(2).normal(166, 20, size=n))
+ height = Series(np.random.default_rng(2).normal(60, 10, size=n))
+ gender_int = np.random.default_rng(2).choice([0, 1], size=n)
+ df_int = DataFrame({"height": height, "weight": weight, "gender": gender_int})
+ gb = df_int.groupby("gender")
+ axes = gb.hist()
+ assert len(axes) == 2
+ assert len(mpl.pyplot.get_fignums()) == 2
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "msg, plot_col, by_col, layout",
+ [
+ [
+ "Layout of 1x1 must be larger than required size 2",
+ "weight",
+ "gender",
+ (1, 1),
+ ],
+ [
+ "Layout of 1x3 must be larger than required size 4",
+ "height",
+ "category",
+ (1, 3),
+ ],
+ [
+ "At least one dimension of layout must be positive",
+ "height",
+ "category",
+ (-1, -1),
+ ],
+ ],
+ )
+ def test_grouped_hist_layout_error(self, hist_df, msg, plot_col, by_col, layout):
+ df = hist_df
+ with pytest.raises(ValueError, match=msg):
+ df.hist(column=plot_col, by=getattr(df, by_col), layout=layout)
+
+ @pytest.mark.slow
+ def test_grouped_hist_layout_warning(self, hist_df):
+ df = hist_df
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(
+ df.hist, column="height", by=df.gender, layout=(2, 1)
+ )
+ _check_axes_shape(axes, axes_num=2, layout=(2, 1))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "layout, check_layout, figsize",
+ [[(4, 1), (4, 1), None], [(-1, 1), (4, 1), None], [(4, 2), (4, 2), (12, 8)]],
+ )
+ def test_grouped_hist_layout_figsize(self, hist_df, layout, check_layout, figsize):
+ df = hist_df
+ axes = df.hist(column="height", by=df.category, layout=layout, figsize=figsize)
+ _check_axes_shape(axes, axes_num=4, layout=check_layout, figsize=figsize)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kwargs", [{}, {"column": "height", "layout": (2, 2)}])
+ def test_grouped_hist_layout_by_warning(self, hist_df, kwargs):
+ df = hist_df
+ # GH 6769
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(df.hist, by="classroom", **kwargs)
+ _check_axes_shape(axes, axes_num=3, layout=(2, 2))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "kwargs, axes_num, layout",
+ [
+ [{"by": "gender", "layout": (3, 5)}, 2, (3, 5)],
+ [{"column": ["height", "weight", "category"]}, 3, (2, 2)],
+ ],
+ )
+ def test_grouped_hist_layout_axes(self, hist_df, kwargs, axes_num, layout):
+ df = hist_df
+ axes = df.hist(**kwargs)
+ _check_axes_shape(axes, axes_num=axes_num, layout=layout)
+
+ def test_grouped_hist_multiple_axes(self, hist_df):
+ # GH 6970, GH 7069
+ df = hist_df
+
+ fig, axes = mpl.pyplot.subplots(2, 3)
+ returned = df.hist(column=["height", "weight", "category"], ax=axes[0])
+ _check_axes_shape(returned, axes_num=3, layout=(1, 3))
+ tm.assert_numpy_array_equal(returned, axes[0])
+ assert returned[0].figure is fig
+
+ def test_grouped_hist_multiple_axes_no_cols(self, hist_df):
+ # GH 6970, GH 7069
+ df = hist_df
+
+ fig, axes = mpl.pyplot.subplots(2, 3)
+ returned = df.hist(by="classroom", ax=axes[1])
+ _check_axes_shape(returned, axes_num=3, layout=(1, 3))
+ tm.assert_numpy_array_equal(returned, axes[1])
+ assert returned[0].figure is fig
+
+ def test_grouped_hist_multiple_axes_error(self, hist_df):
+ # GH 6970, GH 7069
+ df = hist_df
+ fig, axes = mpl.pyplot.subplots(2, 3)
+ # pass different number of axes from required
+ msg = "The number of passed axes must be 1, the same as the output plot"
+ with pytest.raises(ValueError, match=msg):
+ axes = df.hist(column="height", ax=axes)
+
+ def test_axis_share_x(self, hist_df):
+ df = hist_df
+ # GH4089
+ ax1, ax2 = df.hist(column="height", by=df.gender, sharex=True)
+
+ # share x
+ assert get_x_axis(ax1).joined(ax1, ax2)
+ assert get_x_axis(ax2).joined(ax1, ax2)
+
+ # don't share y
+ assert not get_y_axis(ax1).joined(ax1, ax2)
+ assert not get_y_axis(ax2).joined(ax1, ax2)
+
+ def test_axis_share_y(self, hist_df):
+ df = hist_df
+ ax1, ax2 = df.hist(column="height", by=df.gender, sharey=True)
+
+ # share y
+ assert get_y_axis(ax1).joined(ax1, ax2)
+ assert get_y_axis(ax2).joined(ax1, ax2)
+
+ # don't share x
+ assert not get_x_axis(ax1).joined(ax1, ax2)
+ assert not get_x_axis(ax2).joined(ax1, ax2)
+
+ def test_axis_share_xy(self, hist_df):
+ df = hist_df
+ ax1, ax2 = df.hist(column="height", by=df.gender, sharex=True, sharey=True)
+
+ # share both x and y
+ assert get_x_axis(ax1).joined(ax1, ax2)
+ assert get_x_axis(ax2).joined(ax1, ax2)
+
+ assert get_y_axis(ax1).joined(ax1, ax2)
+ assert get_y_axis(ax2).joined(ax1, ax2)
+
+ @pytest.mark.parametrize(
+ "histtype, expected",
+ [
+ ("bar", True),
+ ("barstacked", True),
+ ("step", False),
+ ("stepfilled", True),
+ ],
+ )
+ def test_histtype_argument(self, histtype, expected):
+ # GH23992 Verify functioning of histtype argument
+ df = DataFrame(
+ np.random.default_rng(2).integers(1, 10, size=(10, 2)), columns=["a", "b"]
+ )
+ ax = df.hist(by="a", histtype=histtype)
+ _check_patches_all_filled(ax, filled=expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_misc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_misc.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b6d2499787c02347b0f152fe0d4e0231568e544
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_misc.py
@@ -0,0 +1,866 @@
+"""Test cases for misc plot functions"""
+
+import os
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ Timestamp,
+ date_range,
+ interval_range,
+ period_range,
+ plotting,
+ read_csv,
+)
+import pandas._testing as tm
+from pandas.tests.plotting.common import (
+ _check_colors,
+ _check_legend_labels,
+ _check_plot_works,
+ _check_text_labels,
+ _check_ticks_props,
+)
+
+mpl = pytest.importorskip("matplotlib")
+plt = pytest.importorskip("matplotlib.pyplot")
+cm = pytest.importorskip("matplotlib.cm")
+
+import re
+
+from pandas.plotting._matplotlib.style import get_standard_colors
+
+
+@pytest.fixture
+def iris(datapath) -> DataFrame:
+ """
+ The iris dataset as a DataFrame.
+ """
+ return read_csv(datapath("io", "data", "csv", "iris.csv"))
+
+
+@td.skip_if_installed("matplotlib")
+def test_import_error_message():
+ # GH-19810
+ df = DataFrame({"A": [1, 2]})
+
+ with pytest.raises(ImportError, match="matplotlib is required for plotting"):
+ df.plot()
+
+
+def test_get_accessor_args():
+ func = plotting._core.PlotAccessor._get_call_args
+
+ msg = "Called plot accessor for type list, expected Series or DataFrame"
+ with pytest.raises(TypeError, match=msg):
+ func(backend_name="", data=[], args=[], kwargs={})
+
+ msg = "should not be called with positional arguments"
+ with pytest.raises(TypeError, match=msg):
+ func(backend_name="", data=Series(dtype=object), args=["line", None], kwargs={})
+
+ x, y, kind, kwargs = func(
+ backend_name="",
+ data=DataFrame(),
+ args=["x"],
+ kwargs={"y": "y", "kind": "bar", "grid": False},
+ )
+ assert x == "x"
+ assert y == "y"
+ assert kind == "bar"
+ assert kwargs == {"grid": False}
+
+ x, y, kind, kwargs = func(
+ backend_name="pandas.plotting._matplotlib",
+ data=Series(dtype=object),
+ args=[],
+ kwargs={},
+ )
+ assert x is None
+ assert y is None
+ assert kind == "line"
+ assert len(kwargs) == 24
+
+
+@pytest.mark.parametrize("kind", plotting.PlotAccessor._all_kinds)
+@pytest.mark.parametrize(
+ "data", [DataFrame(np.arange(15).reshape(5, 3)), Series(range(5))]
+)
+@pytest.mark.parametrize(
+ "index",
+ [
+ Index(range(5)),
+ date_range("2020-01-01", periods=5),
+ period_range("2020-01-01", periods=5),
+ ],
+)
+def test_savefig(kind, data, index):
+ fig, ax = plt.subplots()
+ data.index = index
+ kwargs = {}
+ if kind in ["hexbin", "scatter", "pie"]:
+ if isinstance(data, Series):
+ pytest.skip(f"{kind} not supported with Series")
+ kwargs = {"x": 0, "y": 1}
+ data.plot(kind=kind, ax=ax, **kwargs)
+ fig.savefig(os.devnull)
+
+
+class TestSeriesPlots:
+ def test_autocorrelation_plot(self):
+ ser = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ # Ensure no UserWarning when making plot
+ with tm.assert_produces_warning(None):
+ _check_plot_works(plotting.autocorrelation_plot, series=ser)
+ _check_plot_works(plotting.autocorrelation_plot, series=ser.values)
+
+ ax = plotting.autocorrelation_plot(ser, label="Test")
+ _check_legend_labels(ax, labels=["Test"])
+
+ @pytest.mark.parametrize("kwargs", [{}, {"lag": 5}])
+ def test_lag_plot(self, kwargs):
+ ser = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ _check_plot_works(plotting.lag_plot, series=ser, **kwargs)
+
+ def test_bootstrap_plot(self):
+ ser = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ _check_plot_works(plotting.bootstrap_plot, series=ser, size=10)
+
+
+class TestDataFramePlots:
+ @pytest.mark.parametrize("pass_axis", [False, True])
+ def test_scatter_matrix_axis(self, pass_axis):
+ pytest.importorskip("scipy")
+ scatter_matrix = plotting.scatter_matrix
+
+ ax = None
+ if pass_axis:
+ _, ax = mpl.pyplot.subplots(3, 3)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 3)))
+
+ # we are plotting multiples on a sub-plot
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(
+ scatter_matrix,
+ frame=df,
+ range_padding=0.1,
+ ax=ax,
+ )
+ axes0_labels = axes[0][0].yaxis.get_majorticklabels()
+ # GH 5662
+ expected = ["-2", "-1", "0"]
+ _check_text_labels(axes0_labels, expected)
+ _check_ticks_props(axes, xlabelsize=8, xrot=90, ylabelsize=8, yrot=0)
+
+ @pytest.mark.parametrize("pass_axis", [False, True])
+ def test_scatter_matrix_axis_smaller(self, pass_axis):
+ pytest.importorskip("scipy")
+ scatter_matrix = plotting.scatter_matrix
+
+ ax = None
+ if pass_axis:
+ _, ax = mpl.pyplot.subplots(3, 3)
+
+ df = DataFrame(np.random.default_rng(11).standard_normal((10, 3)))
+ df[0] = (df[0] - 2) / 3
+
+ # we are plotting multiples on a sub-plot
+ with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
+ axes = _check_plot_works(
+ scatter_matrix,
+ frame=df,
+ range_padding=0.1,
+ ax=ax,
+ )
+ axes0_labels = axes[0][0].yaxis.get_majorticklabels()
+ expected = ["-1.25", "-1.0", "-0.75", "-0.5"]
+ _check_text_labels(axes0_labels, expected)
+ _check_ticks_props(axes, xlabelsize=8, xrot=90, ylabelsize=8, yrot=0)
+
+ @pytest.mark.slow
+ def test_andrews_curves_no_warning(self, iris):
+ # Ensure no UserWarning when making plot
+ with tm.assert_produces_warning(None):
+ _check_plot_works(plotting.andrews_curves, frame=iris, class_column="Name")
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "linecolors",
+ [
+ ("#556270", "#4ECDC4", "#C7F464"),
+ ["dodgerblue", "aquamarine", "seagreen"],
+ ],
+ )
+ @pytest.mark.parametrize(
+ "df",
+ [
+ "iris",
+ DataFrame(
+ {
+ "A": np.random.default_rng(2).standard_normal(10),
+ "B": np.random.default_rng(2).standard_normal(10),
+ "C": np.random.default_rng(2).standard_normal(10),
+ "Name": ["A"] * 10,
+ }
+ ),
+ ],
+ )
+ def test_andrews_curves_linecolors(self, request, df, linecolors):
+ if isinstance(df, str):
+ df = request.getfixturevalue(df)
+ ax = _check_plot_works(
+ plotting.andrews_curves, frame=df, class_column="Name", color=linecolors
+ )
+ _check_colors(
+ ax.get_lines()[:10], linecolors=linecolors, mapping=df["Name"][:10]
+ )
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "df",
+ [
+ "iris",
+ DataFrame(
+ {
+ "A": np.random.default_rng(2).standard_normal(10),
+ "B": np.random.default_rng(2).standard_normal(10),
+ "C": np.random.default_rng(2).standard_normal(10),
+ "Name": ["A"] * 10,
+ }
+ ),
+ ],
+ )
+ def test_andrews_curves_cmap(self, request, df):
+ if isinstance(df, str):
+ df = request.getfixturevalue(df)
+ cmaps = [cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
+ ax = _check_plot_works(
+ plotting.andrews_curves, frame=df, class_column="Name", color=cmaps
+ )
+ _check_colors(ax.get_lines()[:10], linecolors=cmaps, mapping=df["Name"][:10])
+
+ @pytest.mark.slow
+ def test_andrews_curves_handle(self):
+ colors = ["b", "g", "r"]
+ df = DataFrame({"A": [1, 2, 3], "B": [1, 2, 3], "C": [1, 2, 3], "Name": colors})
+ ax = plotting.andrews_curves(df, "Name", color=colors)
+ handles, _ = ax.get_legend_handles_labels()
+ _check_colors(handles, linecolors=colors)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "color",
+ [("#556270", "#4ECDC4", "#C7F464"), ["dodgerblue", "aquamarine", "seagreen"]],
+ )
+ def test_parallel_coordinates_colors(self, iris, color):
+ df = iris
+
+ ax = _check_plot_works(
+ plotting.parallel_coordinates, frame=df, class_column="Name", color=color
+ )
+ _check_colors(ax.get_lines()[:10], linecolors=color, mapping=df["Name"][:10])
+
+ @pytest.mark.slow
+ def test_parallel_coordinates_cmap(self, iris):
+ df = iris
+
+ ax = _check_plot_works(
+ plotting.parallel_coordinates,
+ frame=df,
+ class_column="Name",
+ colormap=cm.jet,
+ )
+ cmaps = [mpl.cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
+ _check_colors(ax.get_lines()[:10], linecolors=cmaps, mapping=df["Name"][:10])
+
+ @pytest.mark.slow
+ def test_parallel_coordinates_line_diff(self, iris):
+ df = iris
+
+ ax = _check_plot_works(
+ plotting.parallel_coordinates, frame=df, class_column="Name"
+ )
+ nlines = len(ax.get_lines())
+ nxticks = len(ax.xaxis.get_ticklabels())
+
+ ax = _check_plot_works(
+ plotting.parallel_coordinates, frame=df, class_column="Name", axvlines=False
+ )
+ assert len(ax.get_lines()) == (nlines - nxticks)
+
+ @pytest.mark.slow
+ def test_parallel_coordinates_handles(self, iris):
+ df = iris
+ colors = ["b", "g", "r"]
+ df = DataFrame({"A": [1, 2, 3], "B": [1, 2, 3], "C": [1, 2, 3], "Name": colors})
+ ax = plotting.parallel_coordinates(df, "Name", color=colors)
+ handles, _ = ax.get_legend_handles_labels()
+ _check_colors(handles, linecolors=colors)
+
+ # not sure if this is indicative of a problem
+ @pytest.mark.filterwarnings("ignore:Attempting to set:UserWarning")
+ def test_parallel_coordinates_with_sorted_labels(self):
+ # GH 15908
+ df = DataFrame(
+ {
+ "feat": list(range(30)),
+ "class": [2 for _ in range(10)]
+ + [3 for _ in range(10)]
+ + [1 for _ in range(10)],
+ }
+ )
+ ax = plotting.parallel_coordinates(df, "class", sort_labels=True)
+ polylines, labels = ax.get_legend_handles_labels()
+ color_label_tuples = zip(
+ [polyline.get_color() for polyline in polylines], labels, strict=True
+ )
+ ordered_color_label_tuples = sorted(color_label_tuples, key=lambda x: x[1])
+ prev_next_tupels = zip(
+ list(ordered_color_label_tuples[0:-1]),
+ list(ordered_color_label_tuples[1:]),
+ strict=True,
+ )
+ for prev, nxt in prev_next_tupels:
+ # labels and colors are ordered strictly increasing
+ assert prev[1] < nxt[1] and prev[0] < nxt[0]
+
+ def test_radviz_no_warning(self, iris):
+ # Ensure no UserWarning when making plot
+ with tm.assert_produces_warning(None):
+ _check_plot_works(plotting.radviz, frame=iris, class_column="Name")
+
+ @pytest.mark.parametrize(
+ "color",
+ [("#556270", "#4ECDC4", "#C7F464"), ["dodgerblue", "aquamarine", "seagreen"]],
+ )
+ def test_radviz_color(self, iris, color):
+ df = iris
+ ax = _check_plot_works(
+ plotting.radviz, frame=df, class_column="Name", color=color
+ )
+ # skip Circle drawn as ticks
+ patches = [p for p in ax.patches[:20] if p.get_label() != ""]
+ _check_colors(patches[:10], facecolors=color, mapping=df["Name"][:10])
+
+ def test_radviz_color_cmap(self, iris):
+ df = iris
+ ax = _check_plot_works(
+ plotting.radviz, frame=df, class_column="Name", colormap=cm.jet
+ )
+ cmaps = [mpl.cm.jet(n) for n in np.linspace(0, 1, df["Name"].nunique())]
+ patches = [p for p in ax.patches[:20] if p.get_label() != ""]
+ _check_colors(patches, facecolors=cmaps, mapping=df["Name"][:10])
+
+ def test_radviz_colors_handles(self):
+ colors = [[0.0, 0.0, 1.0, 1.0], [0.0, 0.5, 1.0, 1.0], [1.0, 0.0, 0.0, 1.0]]
+ df = DataFrame(
+ {"A": [1, 2, 3], "B": [2, 1, 3], "C": [3, 2, 1], "Name": ["b", "g", "r"]}
+ )
+ ax = plotting.radviz(df, "Name", color=colors)
+ handles, _ = ax.get_legend_handles_labels()
+ _check_colors(handles, facecolors=colors)
+
+ def test_subplot_titles(self, iris):
+ df = iris.drop("Name", axis=1).head()
+ # Use the column names as the subplot titles
+ title = list(df.columns)
+
+ # Case len(title) == len(df)
+ plot = df.plot(subplots=True, title=title)
+ assert [p.get_title() for p in plot] == title
+
+ def test_subplot_titles_too_much(self, iris):
+ df = iris.drop("Name", axis=1).head()
+ # Use the column names as the subplot titles
+ title = list(df.columns)
+ # Case len(title) > len(df)
+ msg = (
+ "The length of `title` must equal the number of columns if "
+ "using `title` of type `list` and `subplots=True`"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.plot(subplots=True, title=[*title, "kittens > puppies"])
+
+ def test_subplot_titles_too_little(self, iris):
+ df = iris.drop("Name", axis=1).head()
+ # Use the column names as the subplot titles
+ title = list(df.columns)
+ msg = (
+ "The length of `title` must equal the number of columns if "
+ "using `title` of type `list` and `subplots=True`"
+ )
+ # Case len(title) < len(df)
+ with pytest.raises(ValueError, match=msg):
+ df.plot(subplots=True, title=title[:2])
+
+ def test_subplot_titles_subplots_false(self, iris):
+ df = iris.drop("Name", axis=1).head()
+ # Use the column names as the subplot titles
+ title = list(df.columns)
+ # Case subplots=False and title is of type list
+ msg = (
+ "Using `title` of type `list` is not supported unless "
+ "`subplots=True` is passed"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.plot(subplots=False, title=title)
+
+ def test_subplot_titles_numeric_square_layout(self, iris):
+ df = iris.drop("Name", axis=1).head()
+ # Use the column names as the subplot titles
+ title = list(df.columns)
+ # Case df with 3 numeric columns but layout of (2,2)
+ plot = df.drop("SepalWidth", axis=1).plot(
+ subplots=True, layout=(2, 2), title=title[:-1]
+ )
+ title_list = [ax.get_title() for sublist in plot for ax in sublist]
+ assert title_list == [*title[:3], ""]
+
+ def test_get_standard_colors_random_seed(self):
+ # GH17525
+ df = DataFrame(np.zeros((10, 10)))
+
+ # Make sure that the random seed isn't reset by get_standard_colors
+ plotting.parallel_coordinates(df, 0)
+ rand1 = np.random.default_rng(None).random()
+ plotting.parallel_coordinates(df, 0)
+ rand2 = np.random.default_rng(None).random()
+ assert rand1 != rand2
+
+ def test_get_standard_colors_consistency(self):
+ # GH17525
+ # Make sure it produces the same colors every time it's called
+ color1 = get_standard_colors(1, color_type="random")
+ color2 = get_standard_colors(1, color_type="random")
+ assert color1 == color2
+
+ def test_get_standard_colors_default_num_colors(self):
+ # Make sure the default color_types returns the specified amount
+ color1 = get_standard_colors(1, color_type="default")
+ color2 = get_standard_colors(9, color_type="default")
+ color3 = get_standard_colors(20, color_type="default")
+ assert len(color1) == 1
+ assert len(color2) == 9
+ assert len(color3) == 20
+
+ def test_plot_single_color(self):
+ # Example from #20585. All 3 bars should have the same color
+ df = DataFrame(
+ {
+ "account-start": ["2017-02-03", "2017-03-03", "2017-01-01"],
+ "client": ["Alice Anders", "Bob Baker", "Charlie Chaplin"],
+ "balance": [-1432.32, 10.43, 30000.00],
+ "db-id": [1234, 2424, 251],
+ "proxy-id": [525, 1525, 2542],
+ "rank": [52, 525, 32],
+ }
+ )
+ ax = df.client.value_counts().plot.bar()
+ colors = [rect.get_facecolor() for rect in ax.get_children()[0:3]]
+ assert all(color == colors[0] for color in colors)
+
+ def test_get_standard_colors_no_appending(self):
+ # GH20726
+
+ # Make sure not to add more colors so that matplotlib can cycle
+ # correctly.
+ color_before = mpl.cm.gnuplot(range(5))
+ color_after = get_standard_colors(1, color=color_before)
+ assert len(color_after) == len(color_before)
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((48, 4)), columns=list("ABCD")
+ )
+
+ color_list = mpl.cm.gnuplot(np.linspace(0, 1, 16))
+ p = df.A.plot.bar(figsize=(16, 7), color=color_list)
+ assert p.patches[1].get_facecolor() == p.patches[17].get_facecolor()
+
+ @pytest.mark.parametrize("kind", ["bar", "line"])
+ def test_dictionary_color(self, kind):
+ # issue-8193
+ # Test plot color dictionary format
+ data_files = ["a", "b"]
+
+ expected = [(0.5, 0.24, 0.6), (0.3, 0.7, 0.7)]
+
+ df1 = DataFrame(np.random.default_rng(2).random((2, 2)), columns=data_files)
+ dic_color = {"b": (0.3, 0.7, 0.7), "a": (0.5, 0.24, 0.6)}
+
+ ax = df1.plot(kind=kind, color=dic_color)
+ if kind == "bar":
+ colors = [rect.get_facecolor()[0:-1] for rect in ax.get_children()[0:3:2]]
+ else:
+ colors = [rect.get_color() for rect in ax.get_lines()[0:2]]
+ assert all(color == expected[index] for index, color in enumerate(colors))
+
+ def test_bar_plot(self):
+ # GH38947
+ # Test bar plot with string and int index
+ expected = [mpl.text.Text(0, 0, "0"), mpl.text.Text(1, 0, "Total")]
+
+ df = DataFrame(
+ {
+ "a": [1, 2],
+ },
+ index=Index([0, "Total"]),
+ )
+ plot_bar = df.plot.bar()
+ assert all(
+ (a.get_text() == b.get_text())
+ for a, b in zip(plot_bar.get_xticklabels(), expected, strict=True)
+ )
+
+ def test_barh_plot_labels_mixed_integer_string(self):
+ # GH39126
+ # Test barh plot with string and integer at the same column
+ df = DataFrame([{"word": 1, "value": 0}, {"word": "knowledge", "value": 2}])
+ plot_barh = df.plot.barh(x="word", legend=None)
+ expected_yticklabels = [
+ mpl.text.Text(0, 0, "1"),
+ mpl.text.Text(0, 1, "knowledge"),
+ ]
+ assert all(
+ actual.get_text() == expected.get_text()
+ for actual, expected in zip(
+ plot_barh.get_yticklabels(), expected_yticklabels, strict=True
+ )
+ )
+
+ def test_has_externally_shared_axis_x_axis(self):
+ # GH33819
+ # Test _has_externally_shared_axis() works for x-axis
+ func = plotting._matplotlib.tools._has_externally_shared_axis
+
+ fig = mpl.pyplot.figure()
+ plots = fig.subplots(2, 4)
+
+ # Create *externally* shared axes for first and third columns
+ plots[0][0] = fig.add_subplot(231, sharex=plots[1][0])
+ plots[0][2] = fig.add_subplot(233, sharex=plots[1][2])
+
+ # Create *internally* shared axes for second and third columns
+ plots[0][1].twinx()
+ plots[0][2].twinx()
+
+ # First column is only externally shared
+ # Second column is only internally shared
+ # Third column is both
+ # Fourth column is neither
+ assert func(plots[0][0], "x")
+ assert not func(plots[0][1], "x")
+ assert func(plots[0][2], "x")
+ assert not func(plots[0][3], "x")
+
+ def test_has_externally_shared_axis_y_axis(self):
+ # GH33819
+ # Test _has_externally_shared_axis() works for y-axis
+ func = plotting._matplotlib.tools._has_externally_shared_axis
+
+ fig = mpl.pyplot.figure()
+ plots = fig.subplots(4, 2)
+
+ # Create *externally* shared axes for first and third rows
+ plots[0][0] = fig.add_subplot(321, sharey=plots[0][1])
+ plots[2][0] = fig.add_subplot(325, sharey=plots[2][1])
+
+ # Create *internally* shared axes for second and third rows
+ plots[1][0].twiny()
+ plots[2][0].twiny()
+
+ # First row is only externally shared
+ # Second row is only internally shared
+ # Third row is both
+ # Fourth row is neither
+ assert func(plots[0][0], "y")
+ assert not func(plots[1][0], "y")
+ assert func(plots[2][0], "y")
+ assert not func(plots[3][0], "y")
+
+ def test_has_externally_shared_axis_invalid_compare_axis(self):
+ # GH33819
+ # Test _has_externally_shared_axis() raises an exception when
+ # passed an invalid value as compare_axis parameter
+ func = plotting._matplotlib.tools._has_externally_shared_axis
+
+ fig = mpl.pyplot.figure()
+ plots = fig.subplots(4, 2)
+
+ # Create arbitrary axes
+ plots[0][0] = fig.add_subplot(321, sharey=plots[0][1])
+
+ # Check that an invalid compare_axis value triggers the expected exception
+ msg = "needs 'x' or 'y' as a second parameter"
+ with pytest.raises(ValueError, match=msg):
+ func(plots[0][0], "z")
+
+ def test_externally_shared_axes(self):
+ # Example from GH33819
+ # Create data
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).standard_normal(10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ }
+ )
+
+ # Create figure
+ fig = mpl.pyplot.figure()
+ plots = fig.subplots(2, 3)
+
+ # Create *externally* shared axes
+ plots[0][0] = fig.add_subplot(231, sharex=plots[1][0])
+ # note: no plots[0][1] that's the twin only case
+ plots[0][2] = fig.add_subplot(233, sharex=plots[1][2])
+
+ # Create *internally* shared axes
+ # note: no plots[0][0] that's the external only case
+ twin_ax1 = plots[0][1].twinx()
+ twin_ax2 = plots[0][2].twinx()
+
+ # Plot data to primary axes
+ df["a"].plot(ax=plots[0][0], title="External share only").set_xlabel(
+ "this label should never be visible"
+ )
+ df["a"].plot(ax=plots[1][0])
+
+ df["a"].plot(ax=plots[0][1], title="Internal share (twin) only").set_xlabel(
+ "this label should always be visible"
+ )
+ df["a"].plot(ax=plots[1][1])
+
+ df["a"].plot(ax=plots[0][2], title="Both").set_xlabel(
+ "this label should never be visible"
+ )
+ df["a"].plot(ax=plots[1][2])
+
+ # Plot data to twinned axes
+ df["b"].plot(ax=twin_ax1, color="green")
+ df["b"].plot(ax=twin_ax2, color="yellow")
+
+ assert not plots[0][0].xaxis.get_label().get_visible()
+ assert plots[0][1].xaxis.get_label().get_visible()
+ assert not plots[0][2].xaxis.get_label().get_visible()
+
+ def test_plot_bar_axis_units_timestamp_conversion(self):
+ # GH 38736
+ # Ensure string x-axis from the second plot will not be converted to datetime
+ # due to axis data from first plot
+ df = DataFrame(
+ [1.0],
+ index=[Timestamp("2022-02-22 22:22:22")],
+ )
+ _check_plot_works(df.plot)
+ s = Series({"A": 1.0})
+ _check_plot_works(s.plot.bar)
+
+ def test_bar_plt_xaxis_intervalrange(self):
+ # GH 38969
+ # Ensure IntervalIndex x-axis produces a bar plot as expected
+ expected = [mpl.text.Text(0, 0, "([0, 1],)"), mpl.text.Text(1, 0, "([1, 2],)")]
+ s = Series(
+ [1, 2],
+ index=[interval_range(0, 2, closed="both")],
+ )
+ _check_plot_works(s.plot.bar)
+ assert all(
+ (a.get_text() == b.get_text())
+ for a, b in zip(s.plot.bar().get_xticklabels(), expected, strict=True)
+ )
+
+
+@pytest.fixture
+def df_bar_data():
+ return np.random.default_rng(3).integers(0, 100, 5)
+
+
+@pytest.fixture
+def df_bar_df(df_bar_data) -> DataFrame:
+ df_bar_df = DataFrame(
+ {
+ "A": df_bar_data,
+ "B": df_bar_data[::-1],
+ "C": df_bar_data[0],
+ "D": df_bar_data[-1],
+ }
+ )
+ return df_bar_df
+
+
+def _df_bar_xyheight_from_ax_helper(df_bar_data, ax, subplot_division):
+ subplot_data_df_list = []
+
+ # get xy and height of squares representing data, separated by subplots
+ for i in range(len(subplot_division)):
+ subplot_data = np.array(
+ [
+ (x.get_x(), x.get_y(), x.get_height())
+ for x in ax[i].findobj(plt.Rectangle)
+ if x.get_height() in df_bar_data
+ ]
+ )
+ subplot_data_df_list.append(
+ DataFrame(data=subplot_data, columns=["x_coord", "y_coord", "height"])
+ )
+
+ return subplot_data_df_list
+
+
+def _df_bar_subplot_checker(df_bar_data, df_bar_df, subplot_data_df, subplot_columns):
+ subplot_sliced_by_source = [
+ subplot_data_df.iloc[
+ len(df_bar_data) * i : len(df_bar_data) * (i + 1)
+ ].reset_index()
+ for i in range(len(subplot_columns))
+ ]
+
+ if len(subplot_columns) == 1:
+ expected_total_height = df_bar_df.loc[:, subplot_columns[0]]
+ else:
+ expected_total_height = df_bar_df.loc[:, subplot_columns].sum(axis=1)
+
+ for i in range(len(subplot_columns)):
+ sliced_df = subplot_sliced_by_source[i]
+ if i == 0:
+ # Checks that the bar chart starts y=0
+ assert (sliced_df["y_coord"] == 0).all()
+ height_iter = sliced_df["y_coord"].add(sliced_df["height"])
+ else:
+ height_iter = height_iter + sliced_df["height"]
+
+ if i + 1 == len(subplot_columns):
+ # Checks final height matches what is expected
+ tm.assert_series_equal(
+ height_iter, expected_total_height, check_names=False, check_dtype=False
+ )
+ else:
+ # Checks each preceding bar ends where the next one starts
+ next_start_coord = subplot_sliced_by_source[i + 1]["y_coord"]
+ tm.assert_series_equal(
+ height_iter, next_start_coord, check_names=False, check_dtype=False
+ )
+
+
+# GH Issue 61018
+@pytest.mark.parametrize("columns_used", [["A", "B"], ["C", "D"], ["D", "A"]])
+def test_bar_1_subplot_1_double_stacked(df_bar_data, df_bar_df, columns_used):
+ df_bar_df_trimmed = df_bar_df[columns_used]
+ subplot_division = [columns_used]
+ ax = df_bar_df_trimmed.plot(subplots=subplot_division, kind="bar", stacked=True)
+ subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
+ df_bar_data, ax, subplot_division
+ )
+ for i in range(len(subplot_data_df_list)):
+ _df_bar_subplot_checker(
+ df_bar_data, df_bar_df_trimmed, subplot_data_df_list[i], subplot_division[i]
+ )
+
+
+@pytest.mark.parametrize(
+ "columns_used", [["A", "B", "C"], ["A", "C", "B"], ["D", "A", "C"]]
+)
+def test_bar_2_subplot_1_double_stacked(df_bar_data, df_bar_df, columns_used):
+ df_bar_df_trimmed = df_bar_df[columns_used]
+ subplot_division = [(columns_used[0], columns_used[1]), (columns_used[2],)]
+ ax = df_bar_df_trimmed.plot(subplots=subplot_division, kind="bar", stacked=True)
+ subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
+ df_bar_data, ax, subplot_division
+ )
+ for i in range(len(subplot_data_df_list)):
+ _df_bar_subplot_checker(
+ df_bar_data, df_bar_df_trimmed, subplot_data_df_list[i], subplot_division[i]
+ )
+
+
+@pytest.mark.parametrize(
+ "subplot_division",
+ [
+ [("A", "B"), ("C", "D")],
+ [("A", "D"), ("C", "B")],
+ [("B", "C"), ("D", "A")],
+ [("B", "D"), ("C", "A")],
+ ],
+)
+def test_bar_2_subplot_2_double_stacked(df_bar_data, df_bar_df, subplot_division):
+ ax = df_bar_df.plot(subplots=subplot_division, kind="bar", stacked=True)
+ subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
+ df_bar_data, ax, subplot_division
+ )
+ for i in range(len(subplot_data_df_list)):
+ _df_bar_subplot_checker(
+ df_bar_data, df_bar_df, subplot_data_df_list[i], subplot_division[i]
+ )
+
+
+@pytest.mark.parametrize(
+ "subplot_division",
+ [[("A", "B", "C")], [("A", "D", "B")], [("C", "A", "D")], [("D", "C", "A")]],
+)
+def test_bar_2_subplots_1_triple_stacked(df_bar_data, df_bar_df, subplot_division):
+ ax = df_bar_df.plot(subplots=subplot_division, kind="bar", stacked=True)
+ subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
+ df_bar_data, ax, subplot_division
+ )
+ for i in range(len(subplot_data_df_list)):
+ _df_bar_subplot_checker(
+ df_bar_data, df_bar_df, subplot_data_df_list[i], subplot_division[i]
+ )
+
+
+def test_bar_subplots_stacking_bool(df_bar_data, df_bar_df):
+ subplot_division = [("A"), ("B"), ("C"), ("D")]
+ ax = df_bar_df.plot(subplots=True, kind="bar", stacked=True)
+ subplot_data_df_list = _df_bar_xyheight_from_ax_helper(
+ df_bar_data, ax, subplot_division
+ )
+ for i in range(len(subplot_data_df_list)):
+ _df_bar_subplot_checker(
+ df_bar_data, df_bar_df, subplot_data_df_list[i], subplot_division[i]
+ )
+
+
+def test_plot_bar_label_count_default():
+ df = DataFrame(
+ [(30, 10, 10, 10), (20, 20, 20, 20), (10, 30, 30, 10)], columns=list("ABCD")
+ )
+ df.plot(subplots=True, kind="bar", title=["A", "B", "C", "D"])
+
+
+def test_plot_bar_label_count_expected_fail():
+ df = DataFrame(
+ [(30, 10, 10, 10), (20, 20, 20, 20), (10, 30, 30, 10)], columns=list("ABCD")
+ )
+ error_regex = re.escape(
+ "The number of titles (4) must equal the number of subplots (3)."
+ )
+ with pytest.raises(ValueError, match=error_regex):
+ df.plot(
+ subplots=[("A", "B")],
+ kind="bar",
+ title=["A&B", "C", "D", "Extra Title"],
+ )
+
+
+def test_plot_bar_label_count_expected_success():
+ df = DataFrame(
+ [(30, 10, 10, 10), (20, 20, 20, 20), (10, 30, 30, 10)], columns=list("ABCD")
+ )
+ df.plot(subplots=[("A", "B", "D")], kind="bar", title=["A&B&D", "C"])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_series.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_series.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ef1e660236c899ef19e9419e9c64402d81beace
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_series.py
@@ -0,0 +1,1005 @@
+"""Test cases for Series.plot"""
+
+from datetime import datetime
+from itertools import chain
+
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_linux
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+ date_range,
+ period_range,
+ plotting,
+)
+import pandas._testing as tm
+from pandas.tests.plotting.common import (
+ _check_ax_scales,
+ _check_axes_shape,
+ _check_colors,
+ _check_grid_settings,
+ _check_has_errorbars,
+ _check_legend_labels,
+ _check_plot_works,
+ _check_text_labels,
+ _check_ticks_props,
+ _unpack_cycler,
+ get_y_axis,
+)
+
+from pandas.tseries.offsets import CustomBusinessDay
+
+mpl = pytest.importorskip("matplotlib")
+plt = pytest.importorskip("matplotlib.pyplot")
+
+from pandas.plotting._matplotlib.converter import DatetimeConverter
+from pandas.plotting._matplotlib.style import get_standard_colors
+
+pytestmark = [
+ pytest.mark.filterwarnings(
+ "ignore:divide by zero encountered in scalar divide:RuntimeWarning"
+ ),
+ pytest.mark.filterwarnings(
+ "ignore:invalid value encountered in scalar multiply:RuntimeWarning"
+ ),
+]
+
+
+@pytest.fixture
+def ts():
+ return Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+
+
+@pytest.fixture
+def series():
+ return Series(
+ range(10), dtype=np.float64, name="series", index=[f"i_{i}" for i in range(10)]
+ )
+
+
+class TestSeriesPlots:
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kwargs", [{"label": "foo"}, {"use_index": False}])
+ def test_plot(self, ts, kwargs):
+ _check_plot_works(ts.plot, **kwargs)
+
+ @pytest.mark.slow
+ def test_plot_tick_props(self, ts):
+ axes = _check_plot_works(ts.plot, rot=0)
+ _check_ticks_props(axes, xrot=0)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "scale, exp_scale",
+ [
+ [{"logy": True}, {"yaxis": "log"}],
+ [{"logx": True}, {"xaxis": "log"}],
+ [{"loglog": True}, {"xaxis": "log", "yaxis": "log"}],
+ ],
+ )
+ def test_plot_scales(self, ts, scale, exp_scale):
+ ax = _check_plot_works(ts.plot, style=".", **scale)
+ _check_ax_scales(ax, **exp_scale)
+
+ @pytest.mark.slow
+ def test_plot_ts_bar(self, ts):
+ _check_plot_works(ts[:10].plot.bar)
+
+ @pytest.mark.slow
+ def test_plot_ts_area_stacked(self, ts):
+ _check_plot_works(ts.plot.area, stacked=False)
+
+ def test_plot_iseries(self):
+ ser = Series(range(5), period_range("2020-01-01", periods=5))
+ _check_plot_works(ser.plot)
+
+ @pytest.mark.parametrize(
+ "kind",
+ [
+ "line",
+ "bar",
+ "barh",
+ pytest.param("kde", marks=td.skip_if_no("scipy")),
+ "hist",
+ "box",
+ ],
+ )
+ def test_plot_series_kinds(self, series, kind):
+ _check_plot_works(series[:5].plot, kind=kind)
+
+ def test_plot_series_barh(self, series):
+ _check_plot_works(series[:10].plot.barh)
+
+ def test_plot_series_bar_ax(self):
+ ax = _check_plot_works(
+ Series(np.random.default_rng(2).standard_normal(10)).plot.bar, color="black"
+ )
+ _check_colors([ax.patches[0]], facecolors=["black"])
+
+ @pytest.mark.parametrize("kwargs", [{}, {"layout": (-1, 1)}, {"layout": (1, -1)}])
+ def test_plot_6951(self, ts, kwargs):
+ # GH 6951
+ ax = _check_plot_works(ts.plot, subplots=True, **kwargs)
+ _check_axes_shape(ax, axes_num=1, layout=(1, 1))
+
+ def test_plot_figsize_and_title(self, series):
+ # figsize and title
+ _, ax = mpl.pyplot.subplots()
+ ax = series.plot(title="Test", figsize=(16, 8), ax=ax)
+ _check_text_labels(ax.title, "Test")
+ _check_axes_shape(ax, axes_num=1, layout=(1, 1), figsize=(16, 8))
+
+ def test_dont_modify_rcParams(self):
+ # GH 8242
+ key = "axes.prop_cycle"
+ colors = mpl.pyplot.rcParams[key]
+ _, ax = mpl.pyplot.subplots()
+ Series([1, 2, 3]).plot(ax=ax)
+ assert colors == mpl.pyplot.rcParams[key]
+
+ @pytest.mark.parametrize("kwargs", [{}, {"secondary_y": True}])
+ def test_ts_line_lim(self, ts, kwargs):
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot(ax=ax, **kwargs)
+ xmin, xmax = ax.get_xlim()
+ lines = ax.get_lines()
+ assert xmin <= lines[0].get_data(orig=False)[0][0]
+ assert xmax >= lines[0].get_data(orig=False)[0][-1]
+
+ def test_ts_area_lim(self, ts):
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.area(stacked=False, ax=ax)
+ xmin, xmax = ax.get_xlim()
+ line = ax.get_lines()[0].get_data(orig=False)[0]
+ assert xmin <= line[0]
+ assert xmax >= line[-1]
+ _check_ticks_props(ax, xrot=0)
+
+ def test_ts_area_lim_xcompat(self, ts):
+ # GH 7471
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.area(stacked=False, x_compat=True, ax=ax)
+ xmin, xmax = ax.get_xlim()
+ line = ax.get_lines()[0].get_data(orig=False)[0]
+ assert xmin <= line[0]
+ assert xmax >= line[-1]
+ _check_ticks_props(ax, xrot=30)
+
+ def test_ts_tz_area_lim_xcompat(self, ts):
+ tz_ts = ts.copy()
+ tz_ts.index = tz_ts.tz_localize("GMT").tz_convert("CET")
+ _, ax = mpl.pyplot.subplots()
+ ax = tz_ts.plot.area(stacked=False, x_compat=True, ax=ax)
+ xmin, xmax = ax.get_xlim()
+ line = ax.get_lines()[0].get_data(orig=False)[0]
+ assert xmin <= line[0]
+ assert xmax >= line[-1]
+ _check_ticks_props(ax, xrot=0)
+
+ def test_ts_tz_area_lim_xcompat_secondary_y(self, ts):
+ tz_ts = ts.copy()
+ tz_ts.index = tz_ts.tz_localize("GMT").tz_convert("CET")
+ _, ax = mpl.pyplot.subplots()
+ ax = tz_ts.plot.area(stacked=False, secondary_y=True, ax=ax)
+ xmin, xmax = ax.get_xlim()
+ line = ax.get_lines()[0].get_data(orig=False)[0]
+ assert xmin <= line[0]
+ assert xmax >= line[-1]
+ _check_ticks_props(ax, xrot=0)
+
+ def test_area_sharey_dont_overwrite(self, ts):
+ # GH37942
+ fig, (ax1, ax2) = mpl.pyplot.subplots(1, 2, sharey=True)
+
+ abs(ts).plot(ax=ax1, kind="area")
+ abs(ts).plot(ax=ax2, kind="area")
+
+ assert get_y_axis(ax1).joined(ax1, ax2)
+ assert get_y_axis(ax2).joined(ax1, ax2)
+
+ def test_label(self):
+ s = Series([1, 2])
+ _, ax = mpl.pyplot.subplots()
+ ax = s.plot(label="LABEL", legend=True, ax=ax)
+ _check_legend_labels(ax, labels=["LABEL"])
+
+ def test_label_none(self):
+ s = Series([1, 2])
+ _, ax = mpl.pyplot.subplots()
+ ax = s.plot(legend=True, ax=ax)
+ _check_legend_labels(ax, labels=[""])
+
+ def test_label_ser_name(self):
+ s = Series([1, 2], name="NAME")
+ _, ax = mpl.pyplot.subplots()
+ ax = s.plot(legend=True, ax=ax)
+ _check_legend_labels(ax, labels=["NAME"])
+
+ def test_label_ser_name_override(self):
+ s = Series([1, 2], name="NAME")
+ # override the default
+ _, ax = mpl.pyplot.subplots()
+ ax = s.plot(legend=True, label="LABEL", ax=ax)
+ _check_legend_labels(ax, labels=["LABEL"])
+
+ def test_label_ser_name_override_dont_draw(self):
+ s = Series([1, 2], name="NAME")
+ # Add lebel info, but don't draw
+ _, ax = mpl.pyplot.subplots()
+ ax = s.plot(legend=False, label="LABEL", ax=ax)
+ assert ax.get_legend() is None # Hasn't been drawn
+ ax.legend() # draw it
+ _check_legend_labels(ax, labels=["LABEL"])
+
+ def test_boolean(self):
+ # GH 23719
+ s = Series([False, False, True])
+ _check_plot_works(s.plot, include_bool=True)
+
+ msg = "no numeric data to plot"
+ with pytest.raises(TypeError, match=msg):
+ _check_plot_works(s.plot)
+
+ @pytest.mark.parametrize("index", [None, date_range("2020-01-01", periods=4)])
+ def test_line_area_nan_series(self, index):
+ values = [1, 2, np.nan, 3]
+ d = Series(values, index=index)
+ ax = _check_plot_works(d.plot)
+ masked = ax.lines[0].get_ydata()
+ # remove nan for comparison purpose
+ exp = np.array([1, 2, 3], dtype=np.float64)
+ tm.assert_numpy_array_equal(np.delete(masked.data, 2), exp)
+ tm.assert_numpy_array_equal(masked.mask, np.array([False, False, True, False]))
+
+ expected = np.array([1, 2, 0, 3], dtype=np.float64)
+ ax = _check_plot_works(d.plot, stacked=True)
+ tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
+ ax = _check_plot_works(d.plot.area)
+ tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
+ ax = _check_plot_works(d.plot.area, stacked=False)
+ tm.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected)
+
+ def test_line_use_index_false(self):
+ s = Series([1, 2, 3], index=["a", "b", "c"])
+ s.index.name = "The Index"
+ _, ax = mpl.pyplot.subplots()
+ ax = s.plot(use_index=False, ax=ax)
+ label = ax.get_xlabel()
+ assert label == ""
+
+ def test_line_use_index_false_diff_var(self):
+ s = Series([1, 2, 3], index=["a", "b", "c"])
+ s.index.name = "The Index"
+ _, ax = mpl.pyplot.subplots()
+ ax2 = s.plot.bar(use_index=False, ax=ax)
+ label2 = ax2.get_xlabel()
+ assert label2 == ""
+
+ @pytest.mark.xfail(
+ is_platform_linux(),
+ reason="Weird rounding problems",
+ strict=False,
+ )
+ @pytest.mark.parametrize("axis, meth", [("yaxis", "bar"), ("xaxis", "barh")])
+ def test_bar_log(self, axis, meth):
+ expected = np.array([1e-1, 1e0, 1e1, 1e2, 1e3, 1e4])
+
+ _, ax = mpl.pyplot.subplots()
+ ax = getattr(Series([200, 500]).plot, meth)(log=True, ax=ax)
+ tm.assert_numpy_array_equal(getattr(ax, axis).get_ticklocs(), expected)
+
+ @pytest.mark.xfail(
+ is_platform_linux(),
+ reason="Weird rounding problems",
+ strict=False,
+ )
+ @pytest.mark.parametrize(
+ "axis, kind, res_meth",
+ [["yaxis", "bar", "get_ylim"], ["xaxis", "barh", "get_xlim"]],
+ )
+ def test_bar_log_kind_bar(self, axis, kind, res_meth):
+ # GH 9905
+ expected = np.array([1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e0, 1e1])
+
+ _, ax = mpl.pyplot.subplots()
+ ax = Series([0.1, 0.01, 0.001]).plot(log=True, kind=kind, ax=ax)
+ ymin = 0.0007943282347242822
+ ymax = 0.12589254117941673
+ res = getattr(ax, res_meth)()
+ tm.assert_almost_equal(res[0], ymin)
+ tm.assert_almost_equal(res[1], ymax)
+ tm.assert_numpy_array_equal(getattr(ax, axis).get_ticklocs(), expected)
+
+ def test_bar_ignore_index(self):
+ df = Series([1, 2, 3, 4], index=["a", "b", "c", "d"])
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot.bar(use_index=False, ax=ax)
+ _check_text_labels(ax.get_xticklabels(), ["0", "1", "2", "3"])
+
+ def test_bar_user_colors(self):
+ s = Series([1, 2, 3, 4])
+ ax = s.plot.bar(color=["red", "blue", "blue", "red"])
+ result = [p.get_facecolor() for p in ax.patches]
+ expected = [
+ (1.0, 0.0, 0.0, 1.0),
+ (0.0, 0.0, 1.0, 1.0),
+ (0.0, 0.0, 1.0, 1.0),
+ (1.0, 0.0, 0.0, 1.0),
+ ]
+ assert result == expected
+
+ def test_rotation_default(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ # Default rot 0
+ _, ax = mpl.pyplot.subplots()
+ axes = df.plot(ax=ax)
+ _check_ticks_props(axes, xrot=0)
+
+ def test_rotation_30(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 5)))
+ _, ax = mpl.pyplot.subplots()
+ axes = df.plot(rot=30, ax=ax)
+ _check_ticks_props(axes, xrot=30)
+
+ def test_irregular_datetime(self):
+ rng = date_range("1/1/2000", "1/15/2000")
+ rng = rng[[0, 1, 2, 3, 5, 9, 10, 11, 12]]
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+ _, ax = mpl.pyplot.subplots()
+ ax = ser.plot(ax=ax)
+ xp = DatetimeConverter.convert(datetime(1999, 1, 1), "", ax)
+ ax.set_xlim("1/1/1999", "1/1/2001")
+ assert xp == ax.get_xlim()[0]
+ _check_ticks_props(ax, xrot=30)
+
+ def test_unsorted_index_xlim(self):
+ ser = Series(
+ [0.0, 1.0, np.nan, 3.0, 4.0, 5.0, 6.0],
+ index=[1.0, 0.0, 3.0, 2.0, np.nan, 3.0, 2.0],
+ )
+ _, ax = mpl.pyplot.subplots()
+ ax = ser.plot(ax=ax)
+ xmin, xmax = ax.get_xlim()
+ lines = ax.get_lines()
+ assert xmin <= np.nanmin(lines[0].get_data(orig=False)[0])
+ assert xmax >= np.nanmax(lines[0].get_data(orig=False)[0])
+
+ def test_pie_series(self):
+ # if sum of values is less than 1.0, pie handle them as rate and draw
+ # semicircle.
+ series = Series(
+ np.random.default_rng(2).integers(1, 5),
+ index=["a", "b", "c", "d", "e"],
+ name="YLABEL",
+ )
+ ax = _check_plot_works(series.plot.pie)
+ _check_text_labels(ax.texts, series.index)
+ assert ax.get_ylabel() == ""
+
+ def test_pie_arrow_type(self):
+ # GH 59192
+ pytest.importorskip("pyarrow")
+ ser = Series([1, 2, 3, 4], dtype="int32[pyarrow]")
+ _check_plot_works(ser.plot.pie)
+
+ def test_pie_series_no_label(self):
+ series = Series(
+ np.random.default_rng(2).integers(1, 5),
+ index=["a", "b", "c", "d", "e"],
+ name="YLABEL",
+ )
+ ax = _check_plot_works(series.plot.pie, labels=None)
+ _check_text_labels(ax.texts, [""] * 5)
+
+ def test_pie_series_less_colors_than_elements(self):
+ series = Series(
+ np.random.default_rng(2).integers(1, 5),
+ index=["a", "b", "c", "d", "e"],
+ name="YLABEL",
+ )
+ color_args = ["r", "g", "b"]
+ ax = _check_plot_works(series.plot.pie, colors=color_args)
+
+ color_expected = ["r", "g", "b", "r", "g"]
+ _check_colors(ax.patches, facecolors=color_expected)
+
+ def test_pie_series_labels_and_colors(self):
+ series = Series(
+ np.random.default_rng(2).integers(1, 5),
+ index=["a", "b", "c", "d", "e"],
+ name="YLABEL",
+ )
+ # with labels and colors
+ labels = ["A", "B", "C", "D", "E"]
+ color_args = ["r", "g", "b", "c", "m"]
+ ax = _check_plot_works(series.plot.pie, labels=labels, colors=color_args)
+ _check_text_labels(ax.texts, labels)
+ _check_colors(ax.patches, facecolors=color_args)
+
+ def test_pie_series_autopct_and_fontsize(self):
+ series = Series(
+ np.random.default_rng(2).integers(1, 5),
+ index=["a", "b", "c", "d", "e"],
+ name="YLABEL",
+ )
+ color_args = ["r", "g", "b", "c", "m"]
+ ax = _check_plot_works(
+ series.plot.pie, colors=color_args, autopct="%.2f", fontsize=7
+ )
+ pcts = [f"{s * 100:.2f}" for s in series.values / series.sum()]
+ expected_texts = list(chain.from_iterable(zip(series.index, pcts, strict=True)))
+ _check_text_labels(ax.texts, expected_texts)
+ for t in ax.texts:
+ assert t.get_fontsize() == 7
+
+ def test_pie_series_negative_raises(self):
+ # includes negative value
+ series = Series([1, 2, 0, 4, -1], index=["a", "b", "c", "d", "e"])
+ with pytest.raises(ValueError, match="pie plot doesn't allow negative values"):
+ series.plot.pie()
+
+ def test_pie_series_nan(self):
+ # includes nan
+ series = Series([1, 2, np.nan, 4], index=["a", "b", "c", "d"], name="YLABEL")
+ ax = _check_plot_works(series.plot.pie)
+ _check_text_labels(ax.texts, ["a", "b", "", "d"])
+
+ def test_pie_nan(self):
+ s = Series([1, np.nan, 1, 1])
+ _, ax = mpl.pyplot.subplots()
+ ax = s.plot.pie(legend=True, ax=ax)
+ expected = ["0", "", "2", "3"]
+ result = [x.get_text() for x in ax.texts]
+ assert result == expected
+
+ def test_df_series_secondary_legend(self):
+ # GH 9779
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 3)), columns=list("abc")
+ )
+ s = Series(np.random.default_rng(2).standard_normal(10), name="x")
+
+ # primary -> secondary (without passing ax)
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot(ax=ax)
+ s.plot(legend=True, secondary_y=True, ax=ax)
+ # both legends are drawn on left ax
+ # left and right axis must be visible
+ _check_legend_labels(ax, labels=["a", "b", "c", "x (right)"])
+ assert ax.get_yaxis().get_visible()
+ assert ax.right_ax.get_yaxis().get_visible()
+
+ def test_df_series_secondary_legend_both(self):
+ # GH 9779
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 3)), columns=list("abc")
+ )
+ s = Series(np.random.default_rng(2).standard_normal(10), name="x")
+ # secondary -> secondary (without passing ax)
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot(secondary_y=True, ax=ax)
+ s.plot(legend=True, secondary_y=True, ax=ax)
+ # both legends are drawn on left ax
+ # left axis must be invisible and right axis must be visible
+ expected = ["a (right)", "b (right)", "c (right)", "x (right)"]
+ _check_legend_labels(ax.left_ax, labels=expected)
+ assert not ax.left_ax.get_yaxis().get_visible()
+ assert ax.get_yaxis().get_visible()
+
+ def test_df_series_secondary_legend_both_with_axis_2(self):
+ # GH 9779
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 3)), columns=list("abc")
+ )
+ s = Series(np.random.default_rng(2).standard_normal(10), name="x")
+ # secondary -> secondary (with passing ax)
+ _, ax = mpl.pyplot.subplots()
+ ax = df.plot(secondary_y=True, mark_right=False, ax=ax)
+ s.plot(ax=ax, legend=True, secondary_y=True)
+ # both legends are drawn on left ax
+ # left axis must be invisible and right axis must be visible
+ expected = ["a", "b", "c", "x (right)"]
+ _check_legend_labels(ax.left_ax, expected)
+ assert not ax.left_ax.get_yaxis().get_visible()
+ assert ax.get_yaxis().get_visible()
+
+ @pytest.mark.parametrize(
+ "input_logy, expected_scale", [(True, "log"), ("sym", "symlog")]
+ )
+ @pytest.mark.parametrize("secondary_kwarg", [{}, {"secondary_y": True}])
+ def test_secondary_logy(self, input_logy, expected_scale, secondary_kwarg):
+ # GH 25545, GH 24980
+ s1 = Series(np.random.default_rng(2).standard_normal(10))
+ ax1 = s1.plot(logy=input_logy, **secondary_kwarg)
+ assert ax1.get_yscale() == expected_scale
+
+ def test_plot_fails_with_dupe_color_and_style(self):
+ x = Series(np.random.default_rng(2).standard_normal(2))
+ _, ax = mpl.pyplot.subplots()
+ msg = (
+ "Cannot pass 'style' string with a color symbol and 'color' keyword "
+ "argument. Please use one or the other or pass 'style' without a color "
+ "symbol"
+ )
+ with pytest.raises(ValueError, match=msg):
+ x.plot(style="k--", color="k", ax=ax)
+
+ @pytest.mark.parametrize(
+ "bw_method, ind",
+ [
+ ["scott", 20],
+ [None, 20],
+ [None, np.int_(20)],
+ [0.5, np.linspace(-100, 100, 20)],
+ ],
+ )
+ def test_kde_kwargs(self, ts, bw_method, ind):
+ pytest.importorskip("scipy")
+ _check_plot_works(ts.plot.kde, bw_method=bw_method, ind=ind)
+
+ @pytest.mark.parametrize(
+ "bw_method, ind, weights",
+ [
+ ["scott", 20, None],
+ [None, 20, None],
+ [None, np.int_(20), None],
+ [0.5, np.linspace(-100, 100, 20), None],
+ ["scott", 40, np.linspace(0.0, 2.0, 50)],
+ ],
+ )
+ def test_kde_kwargs_weights(self, bw_method, ind, weights):
+ # GH59337
+ pytest.importorskip("scipy")
+ s = Series(np.random.default_rng(2).uniform(size=50))
+ _check_plot_works(s.plot.kde, bw_method=bw_method, ind=ind, weights=weights)
+
+ def test_density_kwargs(self, ts):
+ pytest.importorskip("scipy")
+ sample_points = np.linspace(-100, 100, 20)
+ _check_plot_works(ts.plot.density, bw_method=0.5, ind=sample_points)
+
+ def test_kde_kwargs_check_axes(self, ts):
+ pytest.importorskip("scipy")
+ _, ax = mpl.pyplot.subplots()
+ sample_points = np.linspace(-100, 100, 20)
+ ax = ts.plot.kde(logy=True, bw_method=0.5, ind=sample_points, ax=ax)
+ _check_ax_scales(ax, yaxis="log")
+ _check_text_labels(ax.yaxis.get_label(), "Density")
+
+ def test_kde_missing_vals(self):
+ pytest.importorskip("scipy")
+ s = Series(np.random.default_rng(2).uniform(size=50))
+ s[0] = np.nan
+ axes = _check_plot_works(s.plot.kde)
+
+ # gh-14821: check if the values have any missing values
+ assert any(~np.isnan(axes.lines[0].get_xdata()))
+
+ @pytest.mark.xfail(reason="Api changed in 3.6.0")
+ def test_boxplot_series(self, ts):
+ _, ax = mpl.pyplot.subplots()
+ ax = ts.plot.box(logy=True, ax=ax)
+ _check_ax_scales(ax, yaxis="log")
+ xlabels = ax.get_xticklabels()
+ _check_text_labels(xlabels, [ts.name])
+ ylabels = ax.get_yticklabels()
+ _check_text_labels(ylabels, [""] * len(ylabels))
+
+ @pytest.mark.parametrize(
+ "kind",
+ plotting.PlotAccessor._common_kinds + plotting.PlotAccessor._series_kinds,
+ )
+ def test_kind_kwarg(self, kind):
+ pytest.importorskip("scipy")
+ s = Series(range(3))
+ _, ax = mpl.pyplot.subplots()
+ s.plot(kind=kind, ax=ax)
+ mpl.pyplot.close()
+
+ @pytest.mark.parametrize(
+ "kind",
+ plotting.PlotAccessor._common_kinds + plotting.PlotAccessor._series_kinds,
+ )
+ def test_kind_attr(self, kind):
+ pytest.importorskip("scipy")
+ s = Series(range(3))
+ _, ax = mpl.pyplot.subplots()
+ getattr(s.plot, kind)()
+ mpl.pyplot.close()
+
+ @pytest.mark.parametrize("kind", plotting.PlotAccessor._common_kinds)
+ def test_invalid_plot_data(self, kind):
+ s = Series(list("abcd"))
+ _, ax = mpl.pyplot.subplots()
+ msg = "no numeric data to plot"
+ with pytest.raises(TypeError, match=msg):
+ s.plot(kind=kind, ax=ax)
+
+ @pytest.mark.parametrize("kind", plotting.PlotAccessor._common_kinds)
+ def test_valid_object_plot(self, kind):
+ pytest.importorskip("scipy")
+ s = Series(range(10), dtype=object)
+ _check_plot_works(s.plot, kind=kind)
+
+ @pytest.mark.parametrize("kind", plotting.PlotAccessor._common_kinds)
+ def test_partially_invalid_plot_data(self, kind):
+ s = Series(["a", "b", 1.0, 2])
+ _, ax = mpl.pyplot.subplots()
+ msg = "no numeric data to plot"
+ with pytest.raises(TypeError, match=msg):
+ s.plot(kind=kind, ax=ax)
+
+ def test_invalid_kind(self):
+ s = Series([1, 2])
+ with pytest.raises(ValueError, match="invalid_kind is not a valid plot kind"):
+ s.plot(kind="invalid_kind")
+
+ def test_dup_datetime_index_plot(self):
+ dr1 = date_range("1/1/2009", periods=4)
+ dr2 = date_range("1/2/2009", periods=4)
+ index = dr1.append(dr2)
+ values = np.random.default_rng(2).standard_normal(index.size)
+ s = Series(values, index=index)
+ _check_plot_works(s.plot)
+
+ def test_errorbar_asymmetrical(self):
+ # GH9536
+ s = Series(np.arange(10), name="x")
+ err = np.random.default_rng(2).random((2, 10))
+
+ ax = s.plot(yerr=err, xerr=err)
+
+ result = np.vstack([i.vertices[:, 1] for i in ax.collections[1].get_paths()])
+ expected = (err.T * np.array([-1, 1])) + s.to_numpy().reshape(-1, 1)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_errorbar_asymmetrical_error(self):
+ # GH9536
+ s = Series(np.arange(10), name="x")
+ msg = (
+ "Asymmetrical error bars should be provided "
+ f"with the shape \\(2, {len(s)}\\)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ s.plot(yerr=np.random.default_rng(2).random((2, 11)))
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize("kind", ["line", "bar"])
+ @pytest.mark.parametrize(
+ "yerr",
+ [
+ Series(np.abs(np.random.default_rng(2).standard_normal(10))),
+ np.abs(np.random.default_rng(2).standard_normal(10)),
+ list(np.abs(np.random.default_rng(2).standard_normal(10))),
+ DataFrame(
+ np.abs(np.random.default_rng(2).standard_normal((10, 2))),
+ columns=["x", "y"],
+ ),
+ ],
+ )
+ def test_errorbar_plot(self, kind, yerr):
+ s = Series(np.arange(10), name="x")
+ ax = _check_plot_works(s.plot, yerr=yerr, kind=kind)
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+
+ @pytest.mark.slow
+ def test_errorbar_plot_yerr_0(self):
+ s = Series(np.arange(10), name="x")
+ s_err = np.abs(np.random.default_rng(2).standard_normal(10))
+ ax = _check_plot_works(s.plot, xerr=s_err)
+ _check_has_errorbars(ax, xerr=1, yerr=0)
+
+ @pytest.mark.slow
+ @pytest.mark.parametrize(
+ "yerr",
+ [
+ Series(np.abs(np.random.default_rng(2).standard_normal(12))),
+ DataFrame(
+ np.abs(np.random.default_rng(2).standard_normal((12, 2))),
+ columns=["x", "y"],
+ ),
+ ],
+ )
+ def test_errorbar_plot_ts(self, yerr):
+ # test time series plotting
+ ix = date_range("1/1/2000", "1/1/2001", freq="ME")
+ ts = Series(np.arange(12), index=ix, name="x")
+ yerr.index = ix
+
+ ax = _check_plot_works(ts.plot, yerr=yerr)
+ _check_has_errorbars(ax, xerr=0, yerr=1)
+
+ @pytest.mark.slow
+ def test_errorbar_plot_invalid_yerr_shape(self):
+ s = Series(np.arange(10), name="x")
+ # check incorrect lengths and types
+ with tm.external_error_raised(ValueError):
+ s.plot(yerr=np.arange(11))
+
+ @pytest.mark.slow
+ def test_errorbar_plot_invalid_yerr(self):
+ s = Series(np.arange(10), name="x")
+ s_err = ["zzz"] * 10
+ with tm.external_error_raised(TypeError):
+ s.plot(yerr=s_err)
+
+ @pytest.mark.slow
+ def test_table_true(self, series):
+ _check_plot_works(series.plot, table=True)
+
+ @pytest.mark.slow
+ def test_table_self(self, series):
+ _check_plot_works(series.plot, table=series)
+
+ @pytest.mark.slow
+ def test_series_grid_settings(self):
+ # Make sure plot defaults to rcParams['axes.grid'] setting, GH 9792
+ pytest.importorskip("scipy")
+ _check_grid_settings(
+ Series([1, 2, 3]),
+ plotting.PlotAccessor._series_kinds + plotting.PlotAccessor._common_kinds,
+ )
+
+ @pytest.mark.parametrize("c", ["r", "red", "green", "#FF0000"])
+ def test_standard_colors(self, c):
+ result = get_standard_colors(1, color=c)
+ assert result == [c]
+
+ result = get_standard_colors(1, color=[c])
+ assert result == [c]
+
+ result = get_standard_colors(3, color=c)
+ assert result == [c] * 3
+
+ result = get_standard_colors(3, color=[c])
+ assert result == [c] * 3
+
+ def test_standard_colors_all(self):
+ # multiple colors like mediumaquamarine
+ for c in mpl.colors.cnames:
+ result = get_standard_colors(num_colors=1, color=c)
+ assert result == [c]
+
+ result = get_standard_colors(num_colors=1, color=[c])
+ assert result == [c]
+
+ result = get_standard_colors(num_colors=3, color=c)
+ assert result == [c] * 3
+
+ result = get_standard_colors(num_colors=3, color=[c])
+ assert result == [c] * 3
+
+ # single letter colors like k
+ for c in mpl.colors.ColorConverter.colors:
+ result = get_standard_colors(num_colors=1, color=c)
+ assert result == [c]
+
+ result = get_standard_colors(num_colors=1, color=[c])
+ assert result == [c]
+
+ result = get_standard_colors(num_colors=3, color=c)
+ assert result == [c] * 3
+
+ result = get_standard_colors(num_colors=3, color=[c])
+ assert result == [c] * 3
+
+ def test_series_plot_color_kwargs(self):
+ # GH1890
+ _, ax = mpl.pyplot.subplots()
+ ax = Series(np.arange(12) + 1).plot(color="green", ax=ax)
+ _check_colors(ax.get_lines(), linecolors=["green"])
+
+ def test_time_series_plot_color_kwargs(self):
+ # #1890
+ _, ax = mpl.pyplot.subplots()
+ ax = Series(np.arange(12) + 1, index=date_range("1/1/2000", periods=12)).plot(
+ color="green", ax=ax
+ )
+ _check_colors(ax.get_lines(), linecolors=["green"])
+
+ def test_time_series_plot_color_with_empty_kwargs(self):
+ def_colors = _unpack_cycler(mpl.rcParams)
+ index = date_range("1/1/2000", periods=12)
+ s = Series(np.arange(1, 13), index=index)
+
+ ncolors = 3
+
+ _, ax = mpl.pyplot.subplots()
+ for i in range(ncolors):
+ ax = s.plot(ax=ax)
+ _check_colors(ax.get_lines(), linecolors=def_colors[:ncolors])
+
+ def test_xticklabels(self):
+ # GH11529
+ s = Series(np.arange(10), index=[f"P{i:02d}" for i in range(10)])
+ _, ax = mpl.pyplot.subplots()
+ ax = s.plot(xticks=[0, 3, 5, 9], ax=ax)
+ exp = [f"P{i:02d}" for i in [0, 3, 5, 9]]
+ _check_text_labels(ax.get_xticklabels(), exp)
+
+ def test_xtick_barPlot(self):
+ # GH28172
+ s = Series(range(10), index=[f"P{i:02d}" for i in range(10)])
+ ax = s.plot.bar(xticks=range(0, 11, 2))
+ exp = np.array(list(range(0, 11, 2)))
+ tm.assert_numpy_array_equal(exp, ax.get_xticks())
+
+ def test_custom_business_day_freq(self):
+ # GH7222
+ s = Series(
+ range(100, 121),
+ index=pd.bdate_range(
+ start="2014-05-01",
+ end="2014-06-01",
+ freq=CustomBusinessDay(holidays=["2014-05-26"]),
+ ),
+ )
+
+ _check_plot_works(s.plot)
+
+ @pytest.mark.xfail(
+ reason="GH#24426, see also "
+ "github.com/pandas-dev/pandas/commit/"
+ "ef1bd69fa42bbed5d09dd17f08c44fc8bfc2b685#r61470674"
+ )
+ def test_plot_accessor_updates_on_inplace(self):
+ ser = Series([1, 2, 3, 4])
+ _, ax = mpl.pyplot.subplots()
+ ax = ser.plot(ax=ax)
+ before = ax.xaxis.get_ticklocs()
+
+ ser.drop([0, 1], inplace=True)
+ _, ax = mpl.pyplot.subplots()
+ after = ax.xaxis.get_ticklocs()
+ tm.assert_numpy_array_equal(before, after)
+
+ @pytest.mark.parametrize("kind", ["line", "area"])
+ def test_plot_xlim_for_series(self, kind):
+ # test if xlim is also correctly plotted in Series for line and area
+ # GH 27686
+ s = Series([2, 3])
+ _, ax = mpl.pyplot.subplots()
+ s.plot(kind=kind, ax=ax)
+ xlims = ax.get_xlim()
+
+ assert xlims[0] < 0
+ assert xlims[1] > 1
+
+ def test_plot_no_rows(self):
+ # GH 27758
+ df = Series(dtype=int)
+ assert df.empty
+ ax = df.plot()
+ assert len(ax.get_lines()) == 1
+ line = ax.get_lines()[0]
+ assert len(line.get_xdata()) == 0
+ assert len(line.get_ydata()) == 0
+
+ def test_plot_no_numeric_data(self):
+ df = Series(["a", "b", "c"])
+ with pytest.raises(TypeError, match="no numeric data to plot"):
+ df.plot()
+
+ @pytest.mark.parametrize(
+ "data, index",
+ [
+ ([1, 2, 3, 4], [3, 2, 1, 0]),
+ ([10, 50, 20, 30], [1910, 1920, 1980, 1950]),
+ ],
+ )
+ def test_plot_order(self, data, index):
+ # GH38865 Verify plot order of a Series
+ ser = Series(data=data, index=index)
+ ax = ser.plot(kind="bar")
+
+ expected = ser.tolist()
+ result = [
+ patch.get_bbox().ymax
+ for patch in sorted(ax.patches, key=lambda patch: patch.get_bbox().xmax)
+ ]
+ assert expected == result
+
+ def test_style_single_ok(self):
+ s = Series([1, 2])
+ ax = s.plot(style="s", color="C3")
+ assert ax.lines[0].get_color() == "C3"
+
+ @pytest.mark.parametrize(
+ "index_name, old_label, new_label",
+ [(None, "", "new"), ("old", "old", "new"), (None, "", "")],
+ )
+ @pytest.mark.parametrize("kind", ["line", "area", "bar", "barh", "hist"])
+ def test_xlabel_ylabel_series(self, kind, index_name, old_label, new_label):
+ # GH 9093
+ ser = Series([1, 2, 3, 4])
+ ser.index.name = index_name
+
+ # default is the ylabel is not shown and xlabel is index name (reverse for barh)
+ ax = ser.plot(kind=kind)
+ if kind == "barh":
+ assert ax.get_xlabel() == ""
+ assert ax.get_ylabel() == old_label
+ elif kind == "hist":
+ assert ax.get_xlabel() == ""
+ assert ax.get_ylabel() == "Frequency"
+ else:
+ assert ax.get_ylabel() == ""
+ assert ax.get_xlabel() == old_label
+
+ # old xlabel will be overridden and assigned ylabel will be used as ylabel
+ ax = ser.plot(kind=kind, ylabel=new_label, xlabel=new_label)
+ assert ax.get_ylabel() == new_label
+ assert ax.get_xlabel() == new_label
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ pd.timedelta_range(start=0, periods=2, freq="D"),
+ [pd.Timedelta(days=1), pd.Timedelta(days=2)],
+ ],
+ )
+ def test_timedelta_index(self, index):
+ # GH37454
+ xlims = (3, 1)
+ ax = Series([1, 2], index=index).plot(xlim=(xlims))
+ assert ax.get_xlim() == (3, 1)
+
+ def test_series_none_color(self):
+ # GH51953
+ series = Series([1, 2, 3])
+ ax = series.plot(color=None)
+ expected = _unpack_cycler(mpl.pyplot.rcParams)[:1]
+ _check_colors(ax.get_lines(), linecolors=expected)
+
+ @pytest.mark.slow
+ def test_plot_no_warning(self, ts):
+ # GH 55138
+ # TODO(3.0): this can be removed once Period[B] deprecation is enforced
+ with tm.assert_produces_warning(False):
+ _ = ts.plot()
+
+ def test_secondary_y_subplot_axis_labels(self):
+ # GH#14102
+ s1 = Series([5, 7, 6, 8, 7], index=[1, 2, 3, 4, 5])
+ s2 = Series([6, 4, 5, 3, 4], index=[1, 2, 3, 4, 5])
+
+ ax = plt.subplot(2, 1, 1)
+ s1.plot(ax=ax)
+ s2.plot(ax=ax, secondary_y=True)
+ ax2 = plt.subplot(2, 1, 2)
+ s1.plot(ax=ax2)
+ assert len(ax.xaxis.get_minor_ticks()) == 0
+ assert len(ax.get_xticklabels()) > 0
+
+ def test_bar_line_plot(self):
+ """
+ Test that bar and line plots with the same x values are superposed
+ and that the x limits are set such that the plots are visible.
+ """
+ # GH61161
+ index = period_range("2023", periods=3, freq="Y")
+ years = set(index.year.astype(str))
+ s = Series([1, 2, 3], index=index)
+ ax = plt.subplot()
+ s.plot(kind="bar", ax=ax)
+ bar_xticks = [
+ label for label in ax.get_xticklabels() if label.get_text() in years
+ ]
+ s.plot(kind="line", ax=ax, color="r")
+ line_xticks = [
+ label for label in ax.get_xticklabels() if label.get_text() in years
+ ]
+ assert len(bar_xticks) == len(index)
+ assert bar_xticks == line_xticks
+ x_limits = ax.get_xlim()
+ assert x_limits[0] <= bar_xticks[0].get_position()[0]
+ assert x_limits[1] >= bar_xticks[-1].get_position()[0]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_style.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_style.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9c89e0a7893f501b2f8f7f0916ca1a008201b7d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/plotting/test_style.py
@@ -0,0 +1,149 @@
+import pytest
+
+from pandas import Series
+
+mpl = pytest.importorskip("matplotlib")
+plt = pytest.importorskip("matplotlib.pyplot")
+from pandas.plotting._matplotlib.style import get_standard_colors
+
+
+class TestGetStandardColors:
+ @pytest.mark.parametrize(
+ "num_colors, expected",
+ [
+ (3, ["red", "green", "blue"]),
+ (5, ["red", "green", "blue", "red", "green"]),
+ (7, ["red", "green", "blue", "red", "green", "blue", "red"]),
+ (2, ["red", "green"]),
+ (1, ["red"]),
+ ],
+ )
+ def test_default_colors_named_from_prop_cycle(self, num_colors, expected):
+ mpl_params = {
+ "axes.prop_cycle": plt.cycler(color=["red", "green", "blue"]),
+ }
+ with mpl.rc_context(rc=mpl_params):
+ result = get_standard_colors(num_colors=num_colors)
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "num_colors, expected",
+ [
+ (1, ["b"]),
+ (3, ["b", "g", "r"]),
+ (4, ["b", "g", "r", "y"]),
+ (5, ["b", "g", "r", "y", "b"]),
+ (7, ["b", "g", "r", "y", "b", "g", "r"]),
+ ],
+ )
+ def test_default_colors_named_from_prop_cycle_string(self, num_colors, expected):
+ mpl_params = {
+ "axes.prop_cycle": plt.cycler(color="bgry"),
+ }
+ with mpl.rc_context(rc=mpl_params):
+ result = get_standard_colors(num_colors=num_colors)
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "num_colors, expected_name",
+ [
+ (1, ["C0"]),
+ (3, ["C0", "C1", "C2"]),
+ (
+ 12,
+ [
+ "C0",
+ "C1",
+ "C2",
+ "C3",
+ "C4",
+ "C5",
+ "C6",
+ "C7",
+ "C8",
+ "C9",
+ "C0",
+ "C1",
+ ],
+ ),
+ ],
+ )
+ def test_default_colors_named_undefined_prop_cycle(self, num_colors, expected_name):
+ with mpl.rc_context(rc={}):
+ expected = [mpl.colors.to_hex(x) for x in expected_name]
+ result = get_standard_colors(num_colors=num_colors)
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "num_colors, expected",
+ [
+ (1, ["red", "green", (0.1, 0.2, 0.3)]),
+ (2, ["red", "green", (0.1, 0.2, 0.3)]),
+ (3, ["red", "green", (0.1, 0.2, 0.3)]),
+ (4, ["red", "green", (0.1, 0.2, 0.3), "red"]),
+ ],
+ )
+ def test_user_input_color_sequence(self, num_colors, expected):
+ color = ["red", "green", (0.1, 0.2, 0.3)]
+ result = get_standard_colors(color=color, num_colors=num_colors)
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "num_colors, expected",
+ [
+ (1, ["r", "g", "b", "k"]),
+ (2, ["r", "g", "b", "k"]),
+ (3, ["r", "g", "b", "k"]),
+ (4, ["r", "g", "b", "k"]),
+ (5, ["r", "g", "b", "k", "r"]),
+ (6, ["r", "g", "b", "k", "r", "g"]),
+ ],
+ )
+ def test_user_input_color_string(self, num_colors, expected):
+ color = "rgbk"
+ result = get_standard_colors(color=color, num_colors=num_colors)
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "num_colors, expected",
+ [
+ (1, [(0.1, 0.2, 0.3)]),
+ (2, [(0.1, 0.2, 0.3), (0.1, 0.2, 0.3)]),
+ (3, [(0.1, 0.2, 0.3), (0.1, 0.2, 0.3), (0.1, 0.2, 0.3)]),
+ ],
+ )
+ def test_user_input_color_floats(self, num_colors, expected):
+ color = (0.1, 0.2, 0.3)
+ result = get_standard_colors(color=color, num_colors=num_colors)
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "color, num_colors, expected",
+ [
+ ("Crimson", 1, ["Crimson"]),
+ ("DodgerBlue", 2, ["DodgerBlue", "DodgerBlue"]),
+ ("firebrick", 3, ["firebrick", "firebrick", "firebrick"]),
+ ],
+ )
+ def test_user_input_named_color_string(self, color, num_colors, expected):
+ result = get_standard_colors(color=color, num_colors=num_colors)
+ assert result == expected
+
+ @pytest.mark.parametrize("color", ["", [], (), Series([], dtype="object")])
+ def test_empty_color_raises(self, color):
+ with pytest.raises(ValueError, match="Invalid color argument"):
+ get_standard_colors(color=color, num_colors=1)
+
+ @pytest.mark.parametrize(
+ "color",
+ [
+ "bad_color",
+ ("red", "green", "bad_color"),
+ (0.1,),
+ (0.1, 0.2),
+ (0.1, 0.2, 0.3, 0.4, 0.5), # must be either 3 or 4 floats
+ ],
+ )
+ def test_bad_color_raises(self, color):
+ with pytest.raises(ValueError, match="Invalid color"):
+ get_standard_colors(color=color, num_colors=5)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c45ece5d8fb963d32945877b4af6521ea7b9450
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/conftest.py
@@ -0,0 +1,33 @@
+import pytest
+
+# The various methods we support
+downsample_methods = [
+ "min",
+ "max",
+ "first",
+ "last",
+ "sum",
+ "mean",
+ "sem",
+ "median",
+ "prod",
+ "var",
+ "std",
+ "ohlc",
+ "quantile",
+]
+upsample_methods = ["count", "size"]
+series_methods = ["nunique"]
+resample_methods = downsample_methods + upsample_methods + series_methods
+
+
+@pytest.fixture(params=downsample_methods)
+def downsample_method(request):
+ """Fixture for parametrization of Grouper downsample methods."""
+ return request.param
+
+
+@pytest.fixture(params=resample_methods)
+def resample_method(request):
+ """Fixture for parametrization of Grouper resample methods."""
+ return request.param
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_base.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..359ad72bd67f32d472d502550dfb28285b33a4af
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_base.py
@@ -0,0 +1,554 @@
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.common import is_extension_array_dtype
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ MultiIndex,
+ NaT,
+ PeriodIndex,
+ Series,
+ TimedeltaIndex,
+)
+import pandas._testing as tm
+from pandas.core.groupby.groupby import DataError
+from pandas.core.groupby.grouper import Grouper
+from pandas.core.indexes.datetimes import date_range
+from pandas.core.indexes.period import period_range
+from pandas.core.indexes.timedeltas import timedelta_range
+from pandas.core.resample import _asfreq_compat
+
+
+@pytest.fixture(
+ params=[
+ "linear",
+ "time",
+ "index",
+ "values",
+ "nearest",
+ "zero",
+ "slinear",
+ "quadratic",
+ "cubic",
+ "barycentric",
+ "krogh",
+ "from_derivatives",
+ "piecewise_polynomial",
+ "pchip",
+ "akima",
+ ],
+)
+def all_1d_no_arg_interpolation_methods(request):
+ return request.param
+
+
+@pytest.mark.parametrize("freq", ["2D", "1h"])
+@pytest.mark.parametrize(
+ "index",
+ [
+ timedelta_range("1 day", "10 day", freq="D"),
+ date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D"),
+ ],
+)
+def test_asfreq(frame_or_series, index, freq):
+ obj = frame_or_series(range(len(index)), index=index)
+ idx_range = date_range if isinstance(index, DatetimeIndex) else timedelta_range
+
+ result = obj.resample(freq).asfreq()
+ new_index = idx_range(obj.index[0], obj.index[-1], freq=freq)
+ expected = obj.reindex(new_index)
+ tm.assert_almost_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ timedelta_range("1 day", "10 day", freq="D"),
+ date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D"),
+ ],
+)
+def test_asfreq_fill_value(index):
+ # test for fill value during resampling, issue 3715
+
+ ser = Series(range(len(index)), index=index, name="a")
+ idx_range = date_range if isinstance(index, DatetimeIndex) else timedelta_range
+
+ result = ser.resample("1h").asfreq()
+ new_index = idx_range(ser.index[0], ser.index[-1], freq="1h")
+ expected = ser.reindex(new_index)
+ tm.assert_series_equal(result, expected)
+
+ # Explicit cast to float to avoid implicit cast when setting None
+ frame = ser.astype("float").to_frame("value")
+ frame.iloc[1] = None
+ result = frame.resample("1h").asfreq(fill_value=4.0)
+ new_index = idx_range(frame.index[0], frame.index[-1], freq="1h")
+ expected = frame.reindex(new_index, fill_value=4.0)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ timedelta_range("1 day", "3 day", freq="D"),
+ date_range(datetime(2005, 1, 1), datetime(2005, 1, 3), freq="D"),
+ period_range(datetime(2005, 1, 1), datetime(2005, 1, 3), freq="D"),
+ ],
+)
+def test_resample_interpolate(index):
+ # GH#12925
+ df = DataFrame(range(len(index)), index=index)
+ result = df.resample("1min").asfreq().interpolate()
+ expected = df.resample("1min").interpolate()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_interpolate_inplace_deprecated():
+ # GH#58690
+ dti = date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D")
+
+ df = DataFrame(range(len(dti)), index=dti)
+ rs = df.resample("1min")
+ msg = "The 'inplace' keyword in DatetimeIndexResampler.interpolate"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ rs.interpolate(inplace=False)
+
+ msg2 = "Cannot interpolate inplace on a resampled object"
+ with pytest.raises(ValueError, match=msg2):
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ rs.interpolate(inplace=True)
+
+
+def test_resample_interpolate_regular_sampling_off_grid(
+ all_1d_no_arg_interpolation_methods,
+):
+ pytest.importorskip("scipy")
+ # GH#21351
+ index = date_range("2000-01-01 00:01:00", periods=5, freq="2h")
+ ser = Series(np.arange(5.0), index)
+
+ method = all_1d_no_arg_interpolation_methods
+ result = ser.resample("1h").interpolate(method)
+
+ if method == "linear":
+ values = np.repeat(np.arange(0.0, 4.0), 2) + np.tile([1 / 3, 2 / 3], 4)
+ elif method == "nearest":
+ values = np.repeat(np.arange(0.0, 5.0), 2)[1:-1]
+ elif method == "zero":
+ values = np.repeat(np.arange(0.0, 4.0), 2)
+ else:
+ values = 0.491667 + np.arange(0.0, 4.0, 0.5)
+ values = np.insert(values, 0, np.nan)
+ index = date_range("2000-01-01 00:00:00", periods=9, freq="1h")
+ expected = Series(values, index=index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_interpolate_irregular_sampling(all_1d_no_arg_interpolation_methods):
+ pytest.importorskip("scipy")
+ # GH#21351
+ ser = Series(
+ np.linspace(0.0, 1.0, 5),
+ index=DatetimeIndex(
+ [
+ "2000-01-01 00:00:03",
+ "2000-01-01 00:00:22",
+ "2000-01-01 00:00:24",
+ "2000-01-01 00:00:31",
+ "2000-01-01 00:00:39",
+ ]
+ ),
+ )
+
+ # Resample to 5 second sampling and interpolate with the given method
+ ser_resampled = ser.resample("5s").interpolate(all_1d_no_arg_interpolation_methods)
+
+ # Check that none of the resampled values are NaN, except the first one
+ # which lies 3 seconds before the first actual data point
+ assert np.isnan(ser_resampled.iloc[0])
+ assert not ser_resampled.iloc[1:].isna().any()
+
+
+def test_raises_on_non_datetimelike_index():
+ # this is a non datetimelike index
+ xp = DataFrame()
+ msg = (
+ "Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, "
+ "but got an instance of 'RangeIndex'"
+ )
+ with pytest.raises(TypeError, match=msg):
+ xp.resample("YE")
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ PeriodIndex([], freq="D", name="a"),
+ DatetimeIndex([], name="a"),
+ TimedeltaIndex([], name="a"),
+ ],
+)
+@pytest.mark.parametrize("freq", ["ME", "D", "h"])
+def test_resample_empty_series(freq, index, resample_method):
+ # GH12771 & GH12868
+
+ ser = Series(index=index, dtype=float)
+ if freq == "ME" and isinstance(ser.index, TimedeltaIndex):
+ msg = (
+ "Resampling on a TimedeltaIndex requires fixed-duration `freq`, "
+ "e.g. '24h' or '3D', not "
+ )
+ with pytest.raises(ValueError, match=msg):
+ ser.resample(freq)
+ return
+ elif freq == "ME" and isinstance(ser.index, PeriodIndex):
+ # index is PeriodIndex, so convert to corresponding Period freq
+ freq = "M"
+ rs = ser.resample(freq)
+ result = getattr(rs, resample_method)()
+
+ if resample_method == "ohlc":
+ expected = DataFrame(
+ [], index=ser.index[:0], columns=["open", "high", "low", "close"]
+ )
+ expected.index = _asfreq_compat(ser.index, freq)
+ tm.assert_frame_equal(result, expected, check_dtype=False)
+ else:
+ expected = ser.copy()
+ expected.index = _asfreq_compat(ser.index, freq)
+ tm.assert_series_equal(result, expected, check_dtype=False)
+
+ tm.assert_index_equal(result.index, expected.index)
+ assert result.index.freq == expected.index.freq
+
+
+@pytest.mark.parametrize("min_count", [0, 1])
+def test_resample_empty_sum_string(string_dtype_no_object, min_count):
+ # https://github.com/pandas-dev/pandas/issues/60229
+ dtype = string_dtype_no_object
+ ser = Series(
+ pd.NA,
+ index=DatetimeIndex(
+ [
+ "2000-01-01 00:00:00",
+ "2000-01-01 00:00:10",
+ "2000-01-01 00:00:20",
+ "2000-01-01 00:00:30",
+ ]
+ ),
+ dtype=dtype,
+ )
+ rs = ser.resample("20s")
+ result = rs.sum(min_count=min_count)
+
+ value = "" if min_count == 0 else pd.NA
+ index = date_range(start="2000-01-01", freq="20s", periods=2, unit="us")
+ expected = Series(value, index=index, dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "freq",
+ [
+ pytest.param("ME", marks=pytest.mark.xfail(reason="Don't know why this fails")),
+ "D",
+ "h",
+ ],
+)
+def test_resample_nat_index_series(freq, resample_method):
+ # GH39227
+
+ ser = Series(range(5), index=PeriodIndex([NaT] * 5, freq=freq))
+
+ rs = ser.resample(freq)
+ result = getattr(rs, resample_method)()
+
+ if resample_method == "ohlc":
+ expected = DataFrame(
+ [], index=ser.index[:0], columns=["open", "high", "low", "close"]
+ )
+ tm.assert_frame_equal(result, expected, check_dtype=False)
+ else:
+ expected = ser[:0].copy()
+ tm.assert_series_equal(result, expected, check_dtype=False)
+ tm.assert_index_equal(result.index, expected.index)
+ assert result.index.freq == expected.index.freq
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ PeriodIndex([], freq="D", name="a"),
+ DatetimeIndex([], name="a"),
+ TimedeltaIndex([], name="a"),
+ ],
+)
+@pytest.mark.parametrize("freq", ["ME", "D", "h"])
+@pytest.mark.parametrize("resample_method", ["count", "size"])
+def test_resample_count_empty_series(freq, index, resample_method):
+ # GH28427
+ ser = Series(index=index)
+ if freq == "ME" and isinstance(ser.index, TimedeltaIndex):
+ msg = (
+ "Resampling on a TimedeltaIndex requires fixed-duration `freq`, "
+ "e.g. '24h' or '3D', not "
+ )
+ with pytest.raises(ValueError, match=msg):
+ ser.resample(freq)
+ return
+ elif freq == "ME" and isinstance(ser.index, PeriodIndex):
+ # index is PeriodIndex, so convert to corresponding Period freq
+ freq = "M"
+ rs = ser.resample(freq)
+
+ result = getattr(rs, resample_method)()
+
+ index = _asfreq_compat(ser.index, freq)
+
+ expected = Series([], dtype="int64", index=index, name=ser.name)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index", [DatetimeIndex([]), TimedeltaIndex([]), PeriodIndex([], freq="D")]
+)
+@pytest.mark.parametrize("freq", ["ME", "D", "h"])
+def test_resample_empty_dataframe(index, freq, resample_method):
+ # GH13212
+ df = DataFrame(index=index)
+ # count retains dimensions too
+ if freq == "ME" and isinstance(df.index, TimedeltaIndex):
+ msg = (
+ "Resampling on a TimedeltaIndex requires fixed-duration `freq`, "
+ "e.g. '24h' or '3D', not "
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.resample(freq, group_keys=False)
+ return
+ elif freq == "ME" and isinstance(df.index, PeriodIndex):
+ # index is PeriodIndex, so convert to corresponding Period freq
+ freq = "M"
+ rs = df.resample(freq, group_keys=False)
+ result = getattr(rs, resample_method)()
+ if resample_method == "ohlc":
+ # TODO: no tests with len(df.columns) > 0
+ mi = MultiIndex.from_product([df.columns, ["open", "high", "low", "close"]])
+ expected = DataFrame([], index=df.index[:0], columns=mi, dtype=np.float64)
+ expected.index = _asfreq_compat(df.index, freq)
+
+ elif resample_method != "size":
+ expected = df.copy()
+ else:
+ # GH14962
+ expected = Series([], dtype=np.int64)
+
+ expected.index = _asfreq_compat(df.index, freq)
+
+ tm.assert_index_equal(result.index, expected.index)
+ assert result.index.freq == expected.index.freq
+ tm.assert_almost_equal(result, expected)
+
+ # test size for GH13212 (currently stays as df)
+
+
+@pytest.mark.parametrize(
+ "index", [DatetimeIndex([]), TimedeltaIndex([]), PeriodIndex([], freq="D")]
+)
+@pytest.mark.parametrize("freq", ["ME", "D", "h"])
+def test_resample_count_empty_dataframe(freq, index):
+ # GH28427
+ empty_frame_dti = DataFrame(index=index, columns=Index(["a"], dtype=object))
+
+ if freq == "ME" and isinstance(empty_frame_dti.index, TimedeltaIndex):
+ msg = (
+ "Resampling on a TimedeltaIndex requires fixed-duration `freq`, "
+ "e.g. '24h' or '3D', not "
+ )
+ with pytest.raises(ValueError, match=msg):
+ empty_frame_dti.resample(freq)
+ return
+ elif freq == "ME" and isinstance(empty_frame_dti.index, PeriodIndex):
+ # index is PeriodIndex, so convert to corresponding Period freq
+ freq = "M"
+ result = empty_frame_dti.resample(freq).count()
+
+ index = _asfreq_compat(empty_frame_dti.index, freq)
+
+ expected = DataFrame(dtype="int64", index=index, columns=Index(["a"], dtype=object))
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index", [DatetimeIndex([]), TimedeltaIndex([]), PeriodIndex([], freq="D")]
+)
+@pytest.mark.parametrize("freq", ["ME", "D", "h"])
+def test_resample_size_empty_dataframe(freq, index):
+ # GH28427
+
+ empty_frame_dti = DataFrame(index=index, columns=Index(["a"], dtype=object))
+
+ if freq == "ME" and isinstance(empty_frame_dti.index, TimedeltaIndex):
+ msg = (
+ "Resampling on a TimedeltaIndex requires fixed-duration `freq`, "
+ "e.g. '24h' or '3D', not "
+ )
+ with pytest.raises(ValueError, match=msg):
+ empty_frame_dti.resample(freq)
+ return
+ elif freq == "ME" and isinstance(empty_frame_dti.index, PeriodIndex):
+ # index is PeriodIndex, so convert to corresponding Period freq
+ freq = "M"
+ result = empty_frame_dti.resample(freq).size()
+
+ index = _asfreq_compat(empty_frame_dti.index, freq)
+
+ expected = Series([], dtype="int64", index=index)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("index", [DatetimeIndex([]), TimedeltaIndex([])])
+@pytest.mark.parametrize("freq", ["D", "h"])
+@pytest.mark.parametrize(
+ "method", ["ffill", "bfill", "nearest", "asfreq", "interpolate", "mean"]
+)
+def test_resample_apply_empty_dataframe(index, freq, method):
+ # GH#55572
+ empty_frame_dti = DataFrame(index=index)
+
+ rs = empty_frame_dti.resample(freq)
+ result = rs.apply(getattr(rs, method))
+
+ expected_index = _asfreq_compat(empty_frame_dti.index, freq)
+ expected = DataFrame([], index=expected_index)
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ PeriodIndex([], freq="M", name="a"),
+ DatetimeIndex([], name="a"),
+ TimedeltaIndex([], name="a"),
+ ],
+)
+@pytest.mark.parametrize("dtype", [float, int, object, "datetime64[ns]"])
+def test_resample_empty_dtypes(index, dtype, resample_method):
+ # Empty series were sometimes causing a segfault (for the functions
+ # with Cython bounds-checking disabled) or an IndexError. We just run
+ # them to ensure they no longer do. (GH #10228)
+ empty_series_dti = Series([], index, dtype)
+ rs = empty_series_dti.resample("D", group_keys=False)
+ try:
+ getattr(rs, resample_method)()
+ except DataError:
+ # Ignore these since some combinations are invalid
+ # (ex: doing mean with dtype of np.object_)
+ pass
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ PeriodIndex([], freq="D", name="a"),
+ DatetimeIndex([], name="a"),
+ TimedeltaIndex([], name="a"),
+ ],
+)
+@pytest.mark.parametrize("freq", ["ME", "D", "h"])
+def test_apply_to_empty_series(index, freq):
+ # GH 14313
+ ser = Series(index=index)
+
+ if freq == "ME" and isinstance(ser.index, TimedeltaIndex):
+ msg = (
+ "Resampling on a TimedeltaIndex requires fixed-duration `freq`, "
+ "e.g. '24h' or '3D', not "
+ )
+ with pytest.raises(ValueError, match=msg):
+ ser.resample(freq)
+ return
+ elif freq == "ME" and isinstance(ser.index, PeriodIndex):
+ # index is PeriodIndex, so convert to corresponding Period freq
+ freq = "M"
+ result = ser.resample(freq, group_keys=False).apply(lambda x: 1)
+ expected = ser.resample(freq).apply("sum")
+
+ tm.assert_series_equal(result, expected, check_dtype=False)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ timedelta_range("1 day", "10 day", freq="D"),
+ date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D"),
+ period_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D"),
+ ],
+)
+def test_resampler_is_iterable(index):
+ # GH 15314
+ series = Series(range(len(index)), index=index)
+ freq = "h"
+ tg = Grouper(freq=freq, convention="start")
+ grouped = series.groupby(tg)
+ resampled = series.resample(freq)
+ for (rk, rv), (gk, gv) in zip(resampled, grouped):
+ assert rk == gk
+ tm.assert_series_equal(rv, gv)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ timedelta_range("1 day", "10 day", freq="D"),
+ date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D"),
+ period_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D"),
+ ],
+)
+def test_resample_quantile(index):
+ # GH 15023
+ ser = Series(range(len(index)), index=index)
+ q = 0.75
+ freq = "h"
+
+ result = ser.resample(freq).quantile(q)
+ expected = ser.resample(freq).agg(lambda x: x.quantile(q)).rename(ser.name)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("how", ["first", "last"])
+def test_first_last_skipna(any_real_nullable_dtype, skipna, how):
+ # GH#57019
+ if is_extension_array_dtype(any_real_nullable_dtype):
+ na_value = Series(dtype=any_real_nullable_dtype).dtype.na_value
+ else:
+ na_value = np.nan
+ df = DataFrame(
+ {
+ "a": [2, 1, 1, 2],
+ "b": [na_value, 3.0, na_value, 4.0],
+ "c": [na_value, 3.0, na_value, 4.0],
+ },
+ index=date_range("2020-01-01", periods=4, freq="D", unit="ns"),
+ dtype=any_real_nullable_dtype,
+ )
+ rs = df.resample("ME")
+ method = getattr(rs, how)
+ result = method(skipna=skipna)
+
+ ts = pd.to_datetime("2020-01-31").as_unit("ns")
+ gb = df.groupby(df.shape[0] * [ts])
+ expected = getattr(gb, how)(skipna=skipna)
+ expected.index.freq = "ME"
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_datetime_index.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_datetime_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..6867b6cf9927142888a066717a15278fbfee9013
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_datetime_index.py
@@ -0,0 +1,2190 @@
+from datetime import datetime
+from functools import partial
+import zoneinfo
+
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+from pandas._libs.tslibs import Day
+from pandas._typing import DatetimeNaTType
+from pandas.compat import is_platform_windows
+from pandas.compat.pyarrow import pa_version_under22p0
+from pandas.errors import Pandas4Warning
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ Timedelta,
+ Timestamp,
+ isna,
+ notna,
+)
+import pandas._testing as tm
+from pandas.core.groupby.grouper import Grouper
+from pandas.core.indexes.datetimes import date_range
+from pandas.core.indexes.period import (
+ Period,
+ period_range,
+)
+from pandas.core.resample import (
+ DatetimeIndex,
+ _get_timestamp_range_edges,
+)
+
+from pandas.tseries import offsets
+from pandas.tseries.frequencies import to_offset
+from pandas.tseries.offsets import Minute
+
+
+@pytest.fixture
+def simple_date_range_series():
+ """
+ Series with date range index and random data for test purposes.
+ """
+
+ def _simple_date_range_series(start, end, freq="D"):
+ rng = date_range(start, end, freq=freq)
+ return Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ return _simple_date_range_series
+
+
+def test_custom_grouper(unit):
+ index = date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="Min")
+ dti = index.as_unit(unit)
+ s = Series(np.array([1] * len(dti)), index=dti, dtype="int64")
+
+ b = Grouper(freq=Minute(5))
+ g = s.groupby(b)
+
+ # check all cython functions work
+ g.ohlc() # doesn't use _cython_agg_general
+ funcs = ["sum", "mean", "prod", "min", "max", "var"]
+ for f in funcs:
+ g._cython_agg_general(f, alt=None, numeric_only=True)
+
+ b = Grouper(freq=Minute(5), closed="right", label="right")
+ g = s.groupby(b)
+ # check all cython functions work
+ g.ohlc() # doesn't use _cython_agg_general
+ funcs = ["sum", "mean", "prod", "min", "max", "var"]
+ for f in funcs:
+ g._cython_agg_general(f, alt=None, numeric_only=True)
+
+ assert g.ngroups == 2593
+ assert notna(g.mean()).all()
+
+ # construct expected val
+ arr = [1] + [5] * 2592
+ idx = dti[0:-1:5]
+ idx = idx.append(dti[-1:])
+ idx = DatetimeIndex(idx, freq="5min").as_unit(unit)
+ expect = Series(arr, index=idx)
+
+ # GH2763 - return input dtype if we can
+ result = g.agg("sum")
+ tm.assert_series_equal(result, expect)
+
+
+def test_custom_grouper_df(unit):
+ index = date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D")
+ b = Grouper(freq=Minute(5), closed="right", label="right")
+ dti = index.as_unit(unit)
+ df = DataFrame(
+ np.random.default_rng(2).random((len(dti), 10)), index=dti, dtype="float64"
+ )
+ r = df.groupby(b).agg("sum")
+
+ assert len(r.columns) == 10
+ assert len(r.index) == 2593
+
+
+@pytest.mark.parametrize(
+ "closed, expected",
+ [
+ (
+ "right",
+ lambda s: Series(
+ [s.iloc[0], s[1:6].mean(), s[6:11].mean(), s[11:].mean()],
+ index=date_range("1/1/2000", periods=4, freq="5min", name="index"),
+ ),
+ ),
+ (
+ "left",
+ lambda s: Series(
+ [s[:5].mean(), s[5:10].mean(), s[10:].mean()],
+ index=date_range(
+ "1/1/2000 00:05", periods=3, freq="5min", name="index"
+ ),
+ ),
+ ),
+ ],
+)
+def test_resample_basic(closed, expected, unit):
+ index = date_range("1/1/2000 00:00:00", "1/1/2000 00:13:00", freq="Min")
+ s = Series(range(len(index)), index=index)
+ s.index.name = "index"
+ s.index = s.index.as_unit(unit)
+ expected = expected(s)
+ expected.index = expected.index.as_unit(unit)
+ result = s.resample("5min", closed=closed, label="right").mean()
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_integerarray(unit):
+ # GH 25580, resample on IntegerArray
+ ts = Series(
+ range(9),
+ index=date_range("1/1/2000", periods=9, freq="min").as_unit(unit),
+ dtype="Int64",
+ )
+ result = ts.resample("3min").sum()
+ expected = Series(
+ [3, 12, 21],
+ index=date_range("1/1/2000", periods=3, freq="3min").as_unit(unit),
+ dtype="Int64",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = ts.resample("3min").mean()
+ expected = Series(
+ [1, 4, 7],
+ index=date_range("1/1/2000", periods=3, freq="3min").as_unit(unit),
+ dtype="Float64",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_basic_grouper(unit):
+ index = date_range("1/1/2000 00:00:00", "1/1/2000 00:13:00", freq="Min")
+ s = Series(range(len(index)), index=index)
+ s.index.name = "index"
+ s.index = s.index.as_unit(unit)
+ result = s.resample("5Min").last()
+ grouper = Grouper(freq=Minute(5), closed="left", label="left")
+ expected = s.groupby(grouper).agg(lambda x: x.iloc[-1])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "keyword,value",
+ [("label", "righttt"), ("closed", "righttt"), ("convention", "starttt")],
+)
+def test_resample_string_kwargs(keyword, value, unit):
+ # see gh-19303
+ # Check that wrong keyword argument strings raise an error
+ index = date_range("1/1/2000 00:00:00", "1/1/2000 00:13:00", freq="Min")
+ series = Series(range(len(index)), index=index)
+ series.index.name = "index"
+ series.index = series.index.as_unit(unit)
+ msg = f"Unsupported value {value} for `{keyword}`"
+ with pytest.raises(ValueError, match=msg):
+ series.resample("5min", **({keyword: value}))
+
+
+def test_resample_how(downsample_method, unit):
+ if downsample_method == "ohlc":
+ pytest.skip("covered by test_resample_how_ohlc")
+ index = date_range("1/1/2000 00:00:00", "1/1/2000 00:13:00", freq="Min")
+ s = Series(range(len(index)), index=index)
+ s.index.name = "index"
+ s.index = s.index.as_unit(unit)
+ grouplist = np.ones_like(s)
+ grouplist[0] = 0
+ grouplist[1:6] = 1
+ grouplist[6:11] = 2
+ grouplist[11:] = 3
+ expected = s.groupby(grouplist).agg(downsample_method)
+ expected.index = date_range(
+ "1/1/2000", periods=4, freq="5min", name="index"
+ ).as_unit(unit)
+
+ result = getattr(
+ s.resample("5min", closed="right", label="right"), downsample_method
+ )()
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_how_ohlc(unit):
+ index = date_range("1/1/2000 00:00:00", "1/1/2000 00:13:00", freq="Min")
+ s = Series(range(len(index)), index=index)
+ s.index.name = "index"
+ s.index = s.index.as_unit(unit)
+ grouplist = np.ones_like(s)
+ grouplist[0] = 0
+ grouplist[1:6] = 1
+ grouplist[6:11] = 2
+ grouplist[11:] = 3
+
+ def _ohlc(group):
+ if isna(group).all():
+ return np.repeat(np.nan, 4)
+ return [group.iloc[0], group.max(), group.min(), group.iloc[-1]]
+
+ expected = DataFrame(
+ s.groupby(grouplist).agg(_ohlc).values.tolist(),
+ index=date_range("1/1/2000", periods=4, freq="5min", name="index").as_unit(
+ unit
+ ),
+ columns=["open", "high", "low", "close"],
+ )
+
+ result = s.resample("5min", closed="right", label="right").ohlc()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_how_callables(unit):
+ # GH#7929
+ data = np.arange(5, dtype=np.int64)
+ msg = "'d' is deprecated and will be removed in a future version."
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ ind = date_range(start="2014-01-01", periods=len(data), freq="d").as_unit(unit)
+ df = DataFrame({"A": data, "B": data}, index=ind)
+
+ def fn(x, a=1):
+ return str(type(x))
+
+ class FnClass:
+ def __call__(self, x):
+ return str(type(x))
+
+ df_standard = df.resample("ME").apply(fn)
+ df_lambda = df.resample("ME").apply(lambda x: str(type(x)))
+ df_partial = df.resample("ME").apply(partial(fn))
+ df_partial2 = df.resample("ME").apply(partial(fn, a=2))
+ df_class = df.resample("ME").apply(FnClass())
+
+ tm.assert_frame_equal(df_standard, df_lambda)
+ tm.assert_frame_equal(df_standard, df_partial)
+ tm.assert_frame_equal(df_standard, df_partial2)
+ tm.assert_frame_equal(df_standard, df_class)
+
+
+def test_resample_rounding(unit):
+ # GH 8371
+ # odd results when rounding is needed
+
+ ts = [
+ "2014-11-08 00:00:01",
+ "2014-11-08 00:00:02",
+ "2014-11-08 00:00:02",
+ "2014-11-08 00:00:03",
+ "2014-11-08 00:00:07",
+ "2014-11-08 00:00:07",
+ "2014-11-08 00:00:08",
+ "2014-11-08 00:00:08",
+ "2014-11-08 00:00:08",
+ "2014-11-08 00:00:09",
+ "2014-11-08 00:00:10",
+ "2014-11-08 00:00:11",
+ "2014-11-08 00:00:11",
+ "2014-11-08 00:00:13",
+ "2014-11-08 00:00:14",
+ "2014-11-08 00:00:15",
+ "2014-11-08 00:00:17",
+ "2014-11-08 00:00:20",
+ "2014-11-08 00:00:21",
+ ]
+ df = DataFrame({"value": [1] * 19}, index=pd.to_datetime(ts))
+ df.index = df.index.as_unit(unit)
+
+ result = df.resample("6s").sum()
+ expected = DataFrame(
+ {"value": [4, 9, 4, 2]},
+ index=date_range("2014-11-08", freq="6s", periods=4).as_unit(unit),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.resample("7s").sum()
+ expected = DataFrame(
+ {"value": [4, 10, 4, 1]},
+ index=date_range("2014-11-08", freq="7s", periods=4).as_unit(unit),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.resample("11s").sum()
+ expected = DataFrame(
+ {"value": [11, 8]},
+ index=date_range("2014-11-08", freq="11s", periods=2).as_unit(unit),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.resample("13s").sum()
+ expected = DataFrame(
+ {"value": [13, 6]},
+ index=date_range("2014-11-08", freq="13s", periods=2).as_unit(unit),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.resample("17s").sum()
+ expected = DataFrame(
+ {"value": [16, 3]},
+ index=date_range("2014-11-08", freq="17s", periods=2).as_unit(unit),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_basic_from_daily(unit):
+ # from daily
+ dti = date_range(
+ start=datetime(2005, 1, 1), end=datetime(2005, 1, 10), freq="D", name="index"
+ ).as_unit(unit)
+
+ s = Series(np.random.default_rng(2).random(len(dti)), dti)
+
+ # to weekly
+ msg = "'w-sun' is deprecated and will be removed in a future version."
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = s.resample("w-sun").last()
+
+ assert len(result) == 3
+ assert (result.index.dayofweek == [6, 6, 6]).all()
+ assert result.iloc[0] == s["1/2/2005"]
+ assert result.iloc[1] == s["1/9/2005"]
+ assert result.iloc[2] == s.iloc[-1]
+
+ result = s.resample("W-MON").last()
+ assert len(result) == 2
+ assert (result.index.dayofweek == [0, 0]).all()
+ assert result.iloc[0] == s["1/3/2005"]
+ assert result.iloc[1] == s["1/10/2005"]
+
+ result = s.resample("W-TUE").last()
+ assert len(result) == 2
+ assert (result.index.dayofweek == [1, 1]).all()
+ assert result.iloc[0] == s["1/4/2005"]
+ assert result.iloc[1] == s["1/10/2005"]
+
+ result = s.resample("W-WED").last()
+ assert len(result) == 2
+ assert (result.index.dayofweek == [2, 2]).all()
+ assert result.iloc[0] == s["1/5/2005"]
+ assert result.iloc[1] == s["1/10/2005"]
+
+ result = s.resample("W-THU").last()
+ assert len(result) == 2
+ assert (result.index.dayofweek == [3, 3]).all()
+ assert result.iloc[0] == s["1/6/2005"]
+ assert result.iloc[1] == s["1/10/2005"]
+
+ result = s.resample("W-FRI").last()
+ assert len(result) == 2
+ assert (result.index.dayofweek == [4, 4]).all()
+ assert result.iloc[0] == s["1/7/2005"]
+ assert result.iloc[1] == s["1/10/2005"]
+
+ # to biz day
+ result = s.resample("B").last()
+ assert len(result) == 7
+ assert (result.index.dayofweek == [4, 0, 1, 2, 3, 4, 0]).all()
+
+ assert result.iloc[0] == s["1/2/2005"]
+ assert result.iloc[1] == s["1/3/2005"]
+ assert result.iloc[5] == s["1/9/2005"]
+ assert result.index.name == "index"
+
+
+def test_resample_upsampling_picked_but_not_correct(unit):
+ # Test for issue #3020
+ dates = date_range("01-Jan-2014", "05-Jan-2014", freq="D").as_unit(unit)
+ series = Series(1, index=dates)
+
+ result = series.resample("D").mean()
+ assert result.index[0] == dates[0]
+
+ # GH 5955
+ # incorrect deciding to upsample when the axis frequency matches the
+ # resample frequency
+
+ s = Series(
+ np.arange(1.0, 6), index=[datetime(1975, 1, i, 12, 0) for i in range(1, 6)]
+ )
+ s.index = s.index.as_unit(unit)
+ expected = Series(
+ np.arange(1.0, 6),
+ index=date_range("19750101", periods=5, freq="D").as_unit(unit),
+ )
+
+ result = s.resample("D").count()
+ tm.assert_series_equal(result, Series(1, index=expected.index))
+
+ result1 = s.resample("D").sum()
+ result2 = s.resample("D").mean()
+ tm.assert_series_equal(result1, expected)
+ tm.assert_series_equal(result2, expected)
+
+
+@pytest.mark.parametrize("f", ["sum", "mean", "prod", "min", "max", "var"])
+def test_resample_frame_basic_cy_funcs(f, unit):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((50, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=50, freq="B"),
+ )
+ df.index = df.index.as_unit(unit)
+
+ b = Grouper(freq="ME")
+ g = df.groupby(b)
+
+ # check all cython functions work
+ g._cython_agg_general(f, alt=None, numeric_only=True)
+
+
+@pytest.mark.parametrize("freq", ["YE", "ME"])
+def test_resample_frame_basic_M_A(freq, unit):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((50, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=50, freq="B"),
+ )
+ df.index = df.index.as_unit(unit)
+ result = df.resample(freq).mean()
+ tm.assert_series_equal(result["A"], df["A"].resample(freq).mean())
+
+
+def test_resample_upsample(unit):
+ # from daily
+ dti = date_range(
+ start=datetime(2005, 1, 1), end=datetime(2005, 1, 10), freq="D", name="index"
+ ).as_unit(unit)
+
+ s = Series(np.random.default_rng(2).random(len(dti)), dti)
+
+ # to minutely, by padding
+ result = s.resample("Min").ffill()
+ assert len(result) == 12961
+ assert result.iloc[0] == s.iloc[0]
+ assert result.iloc[-1] == s.iloc[-1]
+
+ assert result.index.name == "index"
+
+
+def test_resample_how_method(unit):
+ # GH9915
+ s = Series(
+ [11, 22],
+ index=[
+ Timestamp("2015-03-31 21:48:52.672000"),
+ Timestamp("2015-03-31 21:49:52.739000"),
+ ],
+ )
+ s.index = s.index.as_unit(unit)
+ expected = Series(
+ [11, np.nan, np.nan, np.nan, np.nan, np.nan, 22],
+ index=DatetimeIndex(
+ [
+ Timestamp("2015-03-31 21:48:50"),
+ Timestamp("2015-03-31 21:49:00"),
+ Timestamp("2015-03-31 21:49:10"),
+ Timestamp("2015-03-31 21:49:20"),
+ Timestamp("2015-03-31 21:49:30"),
+ Timestamp("2015-03-31 21:49:40"),
+ Timestamp("2015-03-31 21:49:50"),
+ ],
+ freq="10s",
+ ),
+ )
+ expected.index = expected.index.as_unit(unit)
+ tm.assert_series_equal(s.resample("10s").mean(), expected)
+
+
+def test_resample_extra_index_point(unit):
+ # GH#9756
+ index = date_range(start="20150101", end="20150331", freq="BME").as_unit(unit)
+ expected = DataFrame({"A": Series([21, 41, 63], index=index)})
+
+ index = date_range(start="20150101", end="20150331", freq="B").as_unit(unit)
+ df = DataFrame({"A": Series(range(len(index)), index=index)}, dtype="int64")
+ result = df.resample("BME").last()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_upsample_with_limit(unit):
+ rng = date_range("1/1/2000", periods=3, freq="5min").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+
+ result = ts.resample("min").ffill(limit=2)
+ expected = ts.reindex(result.index, method="ffill", limit=2)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("freq", ["1D", "10h", "5Min", "10s"])
+@pytest.mark.parametrize("rule", ["YE", "3ME", "15D", "30h", "15Min", "30s"])
+def test_nearest_upsample_with_limit(tz_aware_fixture, freq, rule, unit):
+ # GH 33939
+ rng = date_range("1/1/2000", periods=3, freq=freq, tz=tz_aware_fixture).as_unit(
+ unit
+ )
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+
+ result = ts.resample(rule).nearest(limit=2)
+ expected = ts.reindex(result.index, method="nearest", limit=2)
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_ohlc(unit):
+ index = date_range(datetime(2005, 1, 1), datetime(2005, 1, 2), freq="Min")
+ s = Series(range(len(index)), index=index)
+ s.index.name = "index"
+ s.index = s.index.as_unit(unit)
+
+ grouper = Grouper(freq=Minute(5))
+ expect = s.groupby(grouper).agg(lambda x: x.iloc[-1])
+ result = s.resample("5Min").ohlc()
+
+ assert len(result) == len(expect)
+ assert len(result.columns) == 4
+
+ xs = result.iloc[-2]
+ assert xs["open"] == s.iloc[-6]
+ assert xs["high"] == s[-6:-1].max()
+ assert xs["low"] == s[-6:-1].min()
+ assert xs["close"] == s.iloc[-2]
+
+ xs = result.iloc[0]
+ assert xs["open"] == s.iloc[0]
+ assert xs["high"] == s[:5].max()
+ assert xs["low"] == s[:5].min()
+ assert xs["close"] == s.iloc[4]
+
+
+def test_resample_ohlc_result(unit):
+ # GH 12332
+ index = date_range("1-1-2000", "2-15-2000", freq="h").as_unit(unit)
+ index = index.union(date_range("4-15-2000", "5-15-2000", freq="h").as_unit(unit))
+ s = Series(range(len(index)), index=index)
+
+ a = s.loc[:"4-15-2000"].resample("30min").ohlc()
+ assert isinstance(a, DataFrame)
+
+ b = s.loc[:"4-14-2000"].resample("30min").ohlc()
+ assert isinstance(b, DataFrame)
+
+
+def test_resample_ohlc_result_odd_period(unit):
+ # GH12348
+ # raising on odd period
+ rng = date_range("2013-12-30", "2014-01-07").as_unit(unit)
+ index = rng.drop(
+ [
+ Timestamp("2014-01-01"),
+ Timestamp("2013-12-31"),
+ Timestamp("2014-01-04"),
+ Timestamp("2014-01-05"),
+ ]
+ )
+ df = DataFrame(data=np.arange(len(index)), index=index)
+ result = df.resample("B").mean()
+ expected = df.reindex(index=date_range(rng[0], rng[-1], freq="B").as_unit(unit))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_ohlc_dataframe(unit):
+ df = (
+ DataFrame(
+ {
+ "PRICE": {
+ Timestamp("2011-01-06 10:59:05", tz=None): 24990,
+ Timestamp("2011-01-06 12:43:33", tz=None): 25499,
+ Timestamp("2011-01-06 12:54:09", tz=None): 25499,
+ },
+ "VOLUME": {
+ Timestamp("2011-01-06 10:59:05", tz=None): 1500000000,
+ Timestamp("2011-01-06 12:43:33", tz=None): 5000000000,
+ Timestamp("2011-01-06 12:54:09", tz=None): 100000000,
+ },
+ }
+ )
+ ).reindex(["VOLUME", "PRICE"], axis=1)
+ df.index = df.index.as_unit(unit)
+ df.columns.name = "Cols"
+ res = df.resample("h").ohlc()
+ exp = pd.concat(
+ [df["VOLUME"].resample("h").ohlc(), df["PRICE"].resample("h").ohlc()],
+ axis=1,
+ keys=df.columns,
+ )
+ assert exp.columns.names[0] == "Cols"
+ tm.assert_frame_equal(exp, res)
+
+ df.columns = [["a", "b"], ["c", "d"]]
+ res = df.resample("h").ohlc()
+ exp.columns = pd.MultiIndex.from_tuples(
+ [
+ ("a", "c", "open"),
+ ("a", "c", "high"),
+ ("a", "c", "low"),
+ ("a", "c", "close"),
+ ("b", "d", "open"),
+ ("b", "d", "high"),
+ ("b", "d", "low"),
+ ("b", "d", "close"),
+ ]
+ )
+ tm.assert_frame_equal(exp, res)
+
+ # dupe columns fail atm
+ # df.columns = ['PRICE', 'PRICE']
+
+
+def test_resample_reresample(unit):
+ dti = date_range(
+ start=datetime(2005, 1, 1), end=datetime(2005, 1, 10), freq="D"
+ ).as_unit(unit)
+ s = Series(np.random.default_rng(2).random(len(dti)), dti)
+ bs = s.resample("B", closed="right", label="right").mean()
+ result = bs.resample("8h").mean()
+ assert len(result) == 25
+ assert isinstance(result.index.freq, offsets.DateOffset)
+ assert result.index.freq == offsets.Hour(8)
+
+
+@pytest.mark.parametrize(
+ "freq, expected_kwargs",
+ [
+ ["YE-DEC", {"start": "1990", "end": "2000", "freq": "Y-DEC"}],
+ ["YE-JUN", {"start": "1990", "end": "2000", "freq": "Y-JUN"}],
+ ["ME", {"start": "1990-01", "end": "2000-01", "freq": "M"}],
+ ],
+)
+def test_resample_timestamp_to_period(
+ simple_date_range_series, freq, expected_kwargs, unit
+):
+ ts = simple_date_range_series("1/1/1990", "1/1/2000")
+ ts.index = ts.index.as_unit(unit)
+
+ result = ts.resample(freq).mean().to_period()
+ expected = ts.resample(freq).mean()
+ expected.index = period_range(**expected_kwargs)
+ tm.assert_series_equal(result, expected)
+
+
+def test_ohlc_5min(unit):
+ def _ohlc(group):
+ if isna(group).all():
+ return np.repeat(np.nan, 4)
+ return [group.iloc[0], group.max(), group.min(), group.iloc[-1]]
+
+ rng = date_range("1/1/2000 00:00:00", "1/1/2000 5:59:50", freq="10s").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ resampled = ts.resample("5min", closed="right", label="right").ohlc()
+
+ assert (resampled.loc["1/1/2000 00:00"] == ts.iloc[0]).all()
+
+ exp = _ohlc(ts[1:31])
+ assert (resampled.loc["1/1/2000 00:05"] == exp).all()
+
+ exp = _ohlc(ts["1/1/2000 5:55:01":])
+ assert (resampled.loc["1/1/2000 6:00:00"] == exp).all()
+
+
+def test_downsample_non_unique(unit):
+ rng = date_range("1/1/2000", "2/29/2000").as_unit(unit)
+ rng2 = rng.repeat(5).values
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng2)), index=rng2)
+
+ result = ts.resample("ME").mean()
+
+ expected = ts.groupby(lambda x: x.month).mean()
+ assert len(result) == 2
+ tm.assert_almost_equal(result.iloc[0], expected[1])
+ tm.assert_almost_equal(result.iloc[1], expected[2])
+
+
+def test_asfreq_non_unique(unit):
+ # GH #1077
+ rng = date_range("1/1/2000", "2/29/2000").as_unit(unit)
+ rng2 = rng.repeat(2).values
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng2)), index=rng2)
+
+ msg = "cannot reindex on an axis with duplicate labels"
+ with pytest.raises(ValueError, match=msg):
+ ts.asfreq("B")
+
+
+@pytest.mark.parametrize("freq", ["min", "5min", "15min", "30min", "4h", "12h"])
+def test_resample_anchored_ticks(freq, unit):
+ # If a fixed delta (5 minute, 4 hour) evenly divides a day, we should
+ # "anchor" the origin at midnight so we get regular intervals rather
+ # than starting from the first timestamp which might start in the
+ # middle of a desired interval
+
+ rng = date_range("1/1/2000 04:00:00", periods=86400, freq="s").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+ ts[:2] = np.nan # so results are the same
+ result = ts[2:].resample(freq, closed="left", label="left").mean()
+ expected = ts.resample(freq, closed="left", label="left").mean()
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("end", [1, 2])
+def test_resample_single_group(end, unit):
+ mysum = lambda x: x.sum()
+
+ rng = date_range("2000-1-1", f"2000-{end}-10", freq="D").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+ tm.assert_series_equal(ts.resample("ME").sum(), ts.resample("ME").apply(mysum))
+
+
+def test_resample_single_group_std(unit):
+ # GH 3849
+ s = Series(
+ [30.1, 31.6],
+ index=[Timestamp("20070915 15:30:00"), Timestamp("20070915 15:40:00")],
+ )
+ s.index = s.index.as_unit(unit)
+ expected = Series(
+ [0.75], index=DatetimeIndex([Timestamp("20070915")], freq="D").as_unit(unit)
+ )
+ result = s.resample("D").apply(lambda x: np.std(x))
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_offset(unit):
+ # GH 31809
+
+ rng = date_range("1/1/2000 00:00:00", "1/1/2000 02:00", freq="s").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ resampled = ts.resample("5min", offset="2min").mean()
+ exp_rng = date_range("12/31/1999 23:57:00", "1/1/2000 01:57", freq="5min").as_unit(
+ unit
+ )
+ tm.assert_index_equal(resampled.index, exp_rng)
+
+
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"origin": "1999-12-31 23:57:00"},
+ {"origin": Timestamp("1970-01-01 00:02:00")},
+ {"origin": "epoch", "offset": "2m"},
+ # origin of '1999-31-12 12:02:00' should be equivalent for this case
+ {"origin": "1999-12-31 12:02:00"},
+ {"offset": "-3m"},
+ ],
+)
+def test_resample_origin(kwargs, unit):
+ # GH 31809
+ rng = date_range("2000-01-01 00:00:00", "2000-01-01 02:00", freq="s").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ exp_rng = date_range(
+ "1999-12-31 23:57:00", "2000-01-01 01:57", freq="5min"
+ ).as_unit(unit)
+
+ resampled = ts.resample("5min", **kwargs).mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+
+
+@pytest.mark.parametrize(
+ "origin", ["invalid_value", "epch", "startday", "startt", "2000-30-30", object()]
+)
+def test_resample_bad_origin(origin, unit):
+ rng = date_range("2000-01-01 00:00:00", "2000-01-01 02:00", freq="s").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+ msg = (
+ "'origin' should be equal to 'epoch', 'start', 'start_day', "
+ "'end', 'end_day' or should be a Timestamp convertible type. Got "
+ f"'{origin}' instead."
+ )
+ with pytest.raises(ValueError, match=msg):
+ ts.resample("5min", origin=origin)
+
+
+@pytest.mark.parametrize("offset", ["invalid_value", "12dayys", "2000-30-30", object()])
+def test_resample_bad_offset(offset, unit):
+ rng = date_range("2000-01-01 00:00:00", "2000-01-01 02:00", freq="s").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+ msg = f"'offset' should be a Timedelta convertible type. Got '{offset}' instead."
+ with pytest.raises(ValueError, match=msg):
+ ts.resample("5min", offset=offset)
+
+
+def test_resample_origin_prime_freq(unit):
+ # GH 31809
+ start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"
+ rng = date_range(start, end, freq="7min").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ exp_rng = date_range(
+ "2000-10-01 23:14:00", "2000-10-02 00:22:00", freq="17min"
+ ).as_unit(unit)
+ resampled = ts.resample("17min").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+ resampled = ts.resample("17min", origin="start_day").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+
+ exp_rng = date_range(
+ "2000-10-01 23:30:00", "2000-10-02 00:21:00", freq="17min"
+ ).as_unit(unit)
+ resampled = ts.resample("17min", origin="start").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+ resampled = ts.resample("17min", offset="23h30min").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+ resampled = ts.resample("17min", origin="start_day", offset="23h30min").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+
+ exp_rng = date_range(
+ "2000-10-01 23:18:00", "2000-10-02 00:26:00", freq="17min"
+ ).as_unit(unit)
+ resampled = ts.resample("17min", origin="epoch").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+
+ exp_rng = date_range(
+ "2000-10-01 23:24:00", "2000-10-02 00:15:00", freq="17min"
+ ).as_unit(unit)
+ resampled = ts.resample("17min", origin="2000-01-01").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+
+
+def test_resample_origin_with_tz(unit):
+ # GH 31809
+ msg = "The origin must have the same timezone as the index."
+
+ tz = "Europe/Paris"
+ rng = date_range(
+ "2000-01-01 00:00:00", "2000-01-01 02:00", freq="s", tz=tz
+ ).as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ exp_rng = date_range(
+ "1999-12-31 23:57:00", "2000-01-01 01:57", freq="5min", tz=tz
+ ).as_unit(unit)
+ resampled = ts.resample("5min", origin="1999-12-31 23:57:00+00:00").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+
+ # origin of '1999-31-12 12:02:00+03:00' should be equivalent for this case
+ resampled = ts.resample("5min", origin="1999-12-31 12:02:00+03:00").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+
+ resampled = ts.resample("5min", origin="epoch", offset="2m").mean()
+ tm.assert_index_equal(resampled.index, exp_rng)
+
+ with pytest.raises(ValueError, match=msg):
+ ts.resample("5min", origin="12/31/1999 23:57:00").mean()
+
+ # if the series is not tz aware, origin should not be tz aware
+ rng = date_range("2000-01-01 00:00:00", "2000-01-01 02:00", freq="s").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+ with pytest.raises(ValueError, match=msg):
+ ts.resample("5min", origin="12/31/1999 23:57:00+03:00").mean()
+
+
+def test_resample_origin_epoch_with_tz_day_vs_24h(unit):
+ # GH 34474
+ start, end = "2000-10-01 23:30:00+0500", "2000-12-02 00:30:00+0500"
+ rng = date_range(start, end, freq="7min").as_unit(unit)
+ random_values = np.random.default_rng(2).standard_normal(len(rng))
+ ts_1 = Series(random_values, index=rng)
+
+ result_1 = ts_1.resample("D").mean()
+ result_2 = ts_1.resample("24h", origin="epoch").mean()
+ tm.assert_series_equal(result_1, result_2, check_freq=False)
+ # GH#41943 check_freq=False bc Day and Hour(24) no longer compare as equal
+
+ # check that we have the same behavior with epoch even if we are not timezone aware
+ ts_no_tz = ts_1.tz_localize(None)
+ result_3 = ts_no_tz.resample("D").mean()
+ result_4 = ts_no_tz.resample("24h", origin="epoch").mean()
+ tm.assert_series_equal(result_1, result_3.tz_localize(rng.tz), check_freq=False)
+ tm.assert_series_equal(result_1, result_4.tz_localize(rng.tz), check_freq=False)
+
+ # check that we have the similar results with two different timezones (+2H and +5H)
+ start, end = "2000-10-01 23:30:00+0200", "2000-12-02 00:30:00+0200"
+ rng = date_range(start, end, freq="7min").as_unit(unit)
+ ts_2 = Series(random_values, index=rng)
+ result_5 = ts_2.resample("D").mean()
+ result_6 = ts_2.resample("24h", origin="epoch").mean()
+ tm.assert_series_equal(result_1.tz_localize(None), result_5.tz_localize(None))
+ tm.assert_series_equal(result_1.tz_localize(None), result_6.tz_localize(None))
+
+
+def test_resample_origin_with_day_freq_on_dst(unit):
+ # GH 31809
+ tz = "America/Chicago"
+ msg = "The '(origin|offset)' keyword does not take effect"
+
+ def _create_series(values, timestamps, freq="D"):
+ return Series(
+ values,
+ index=DatetimeIndex(
+ [Timestamp(t, tz=tz) for t in timestamps], freq=freq, ambiguous=True
+ ).as_unit(unit),
+ )
+
+ # test classical behavior of origin in a DST context
+ start = Timestamp("2013-11-02", tz=tz)
+ end = Timestamp("2013-11-03 23:59", tz=tz)
+ rng = date_range(start, end, freq="1h").as_unit(unit)
+ ts = Series(np.ones(len(rng)), index=rng)
+
+ expected = _create_series([24.0, 25.0], ["2013-11-02", "2013-11-03"])
+ for origin in ["epoch", "start", "start_day", start, None]:
+ warn = RuntimeWarning if origin != "start_day" else None
+ with tm.assert_produces_warning(warn, match=msg):
+ result = ts.resample("D", origin=origin).sum()
+ tm.assert_series_equal(result, expected)
+
+ # test complex behavior of origin/offset in a DST context
+ start = Timestamp("2013-11-03", tz=tz)
+ end = Timestamp("2013-11-03 23:59", tz=tz)
+ rng = date_range(start, end, freq="1h").as_unit(unit)
+ ts = Series(np.ones(len(rng)), index=rng)
+
+ # GH#61985 changed this to behave like "B" rather than "24h"
+ expected_ts = ["2013-11-03 00:00-05:00"]
+ expected = _create_series([25.0], expected_ts)
+ with tm.assert_produces_warning(RuntimeWarning, match=msg):
+ result = ts.resample("D", origin="start", offset="-2h").sum()
+ tm.assert_series_equal(result, expected)
+
+ expected_ts = ["2013-11-02 22:00-05:00", "2013-11-03 21:00-06:00"]
+ expected = _create_series([22.0, 3.0], expected_ts, freq="24h")
+ result = ts.resample("24h", origin="start", offset="-2h").sum()
+ tm.assert_series_equal(result, expected)
+
+ # GH#61985 changed this to behave like "B" rather than "24h"
+ expected_ts = ["2013-11-03 00:00-05:00"]
+ expected = _create_series([25.0], expected_ts)
+ with tm.assert_produces_warning(RuntimeWarning, match=msg):
+ result = ts.resample("D", origin="start", offset="2h").sum()
+ tm.assert_series_equal(result, expected)
+
+ expected_ts = ["2013-11-03 00:00-05:00"]
+ expected = _create_series([25.0], expected_ts)
+ with tm.assert_produces_warning(RuntimeWarning, match=msg):
+ result = ts.resample("D", origin="start", offset="-1h").sum()
+ tm.assert_series_equal(result, expected)
+
+ expected_ts = ["2013-11-03 00:00-05:00"]
+ expected = _create_series([25.0], expected_ts)
+ with tm.assert_produces_warning(RuntimeWarning, match=msg):
+ result = ts.resample("D", origin="start", offset="1h").sum()
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_dst_midnight_last_nonexistent():
+ # GH 58380
+ ts = Series(
+ 1,
+ date_range("2024-04-19", "2024-04-20", tz="Africa/Cairo", freq="15min"),
+ )
+
+ expected = Series([len(ts)], index=DatetimeIndex([ts.index[0]], freq="7D"))
+
+ result = ts.resample("7D").sum()
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_daily_anchored(unit):
+ rng = date_range("1/1/2000 0:00:00", periods=10000, freq="min").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+ ts[:2] = np.nan # so results are the same
+
+ result = ts[2:].resample("D", closed="left", label="left").mean()
+ expected = ts.resample("D", closed="left", label="left").mean()
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_to_period_monthly_buglet(unit):
+ # GH #1259
+
+ rng = date_range("1/1/2000", "12/31/2000").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ result = ts.resample("ME").mean().to_period()
+ exp_index = period_range("Jan-2000", "Dec-2000", freq="M")
+ tm.assert_index_equal(result.index, exp_index)
+
+
+def test_period_with_agg():
+ # aggregate a period resampler with a lambda
+ s2 = Series(
+ np.random.default_rng(2).integers(0, 5, 50),
+ index=period_range("2012-01-01", freq="h", periods=50),
+ dtype="float64",
+ )
+
+ expected = s2.to_timestamp().resample("D").mean().to_period()
+ result = s2.resample("D").agg(lambda x: x.mean())
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_segfault(unit):
+ # GH 8573
+ # segfaulting in older versions
+ all_wins_and_wagers = [
+ (1, datetime(2013, 10, 1, 16, 20), 1, 0),
+ (2, datetime(2013, 10, 1, 16, 10), 1, 0),
+ (2, datetime(2013, 10, 1, 18, 15), 1, 0),
+ (2, datetime(2013, 10, 1, 16, 10, 31), 1, 0),
+ ]
+
+ df = DataFrame.from_records(
+ all_wins_and_wagers, columns=("ID", "timestamp", "A", "B")
+ ).set_index("timestamp")
+ df.index = df.index.as_unit(unit)
+ result = df.groupby("ID").resample("5min").sum()
+ expected = df.groupby("ID").apply(lambda x: x.resample("5min").sum())
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_dtype_preservation(unit):
+ # GH 12202
+ # validation tests for dtype preservation
+
+ df = DataFrame(
+ {
+ "date": date_range(start="2016-01-01", periods=4, freq="W").as_unit(unit),
+ "group": [1, 1, 2, 2],
+ "val": Series([5, 6, 7, 8], dtype="int32"),
+ }
+ ).set_index("date")
+
+ result = df.resample("1D").ffill()
+ assert result.val.dtype == np.int32
+
+ result = df.groupby("group").resample("1D").ffill()
+ assert result.val.dtype == np.int32
+
+
+def test_resample_dtype_coercion(unit):
+ pytest.importorskip("scipy.interpolate")
+
+ # GH 16361
+ df = {"a": [1, 3, 1, 4]}
+ df = DataFrame(df, index=date_range("2017-01-01", "2017-01-04").as_unit(unit))
+
+ expected = df.astype("float64").resample("h").mean()["a"].interpolate("cubic")
+
+ result = df.resample("h")["a"].mean().interpolate("cubic")
+ tm.assert_series_equal(result, expected)
+
+ result = df.resample("h").mean()["a"].interpolate("cubic")
+ tm.assert_series_equal(result, expected)
+
+
+def test_weekly_resample_buglet(unit):
+ # #1327
+ rng = date_range("1/1/2000", freq="B", periods=20).as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ resampled = ts.resample("W").mean()
+ expected = ts.resample("W-SUN").mean()
+ tm.assert_series_equal(resampled, expected)
+
+
+def test_monthly_resample_error(unit):
+ # #1451
+ dates = date_range("4/16/2012 20:00", periods=5000, freq="h").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(dates)), index=dates)
+ # it works!
+ ts.resample("ME")
+
+
+def test_nanosecond_resample_error():
+ # GH 12307 - Values falls after last bin when
+ # Resampling using pd.tseries.offsets.Nano as period
+ start = 1443707890427
+ exp_start = 1443707890400
+ indx = date_range(start=pd.to_datetime(start), periods=10, freq="100ns")
+ ts = Series(range(len(indx)), index=indx)
+ r = ts.resample(pd.tseries.offsets.Nano(100))
+ result = r.agg("mean")
+
+ exp_indx = date_range(start=pd.to_datetime(exp_start), periods=10, freq="100ns")
+ exp = Series(range(len(exp_indx)), index=exp_indx, dtype=float)
+
+ tm.assert_series_equal(result, exp)
+
+
+def test_resample_anchored_intraday(unit):
+ # #1471, #1458
+
+ rng = date_range("1/1/2012", "4/1/2012", freq="100min").as_unit(unit)
+ df = DataFrame(rng.month, index=rng)
+
+ result = df.resample("ME").mean()
+ expected = df.resample("ME").mean().to_period()
+ expected = expected.to_timestamp(how="end")
+ expected.index += Timedelta(1, unit="us") - Timedelta(1, unit="D")
+ expected.index = expected.index.as_unit(unit)._with_freq("infer")
+ assert expected.index.freq == "ME"
+ tm.assert_frame_equal(result, expected)
+
+ result = df.resample("ME", closed="left").mean()
+ exp = df.shift(1, freq="D").resample("ME").mean().to_period()
+ exp = exp.to_timestamp(how="end")
+
+ exp.index = exp.index + Timedelta(1, unit="us") - Timedelta(1, unit="D")
+ exp.index = exp.index.as_unit(unit)._with_freq("infer")
+ assert exp.index.freq == "ME"
+ tm.assert_frame_equal(result, exp)
+
+
+def test_resample_anchored_intraday2(unit):
+ rng = date_range("1/1/2012", "4/1/2012", freq="100min").as_unit(unit)
+ df = DataFrame(rng.month, index=rng)
+
+ result = df.resample("QE").mean()
+ expected = df.resample("QE").mean().to_period()
+ expected = expected.to_timestamp(how="end")
+ expected.index += Timedelta(1, unit="us") - Timedelta(1, unit="D")
+ expected.index._data.freq = "QE"
+ expected.index._freq = lib.no_default
+ expected.index = expected.index.as_unit(unit)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.resample("QE", closed="left").mean()
+ expected = df.shift(1, freq="D").resample("QE").mean()
+ expected = expected.to_period()
+ expected = expected.to_timestamp(how="end")
+ expected.index += Timedelta(1, unit="us") - Timedelta(1, unit="D")
+ expected.index._data.freq = "QE"
+ expected.index._freq = lib.no_default
+ expected.index = expected.index.as_unit(unit)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_anchored_intraday3(simple_date_range_series, unit):
+ ts = simple_date_range_series("2012-04-29 23:00", "2012-04-30 5:00", freq="h")
+ ts.index = ts.index.as_unit(unit)
+ resampled = ts.resample("ME").mean()
+ assert len(resampled) == 1
+
+
+@pytest.mark.parametrize("freq", ["MS", "BMS", "QS-MAR", "YS-DEC", "YS-JUN"])
+def test_resample_anchored_monthstart(simple_date_range_series, freq, unit):
+ ts = simple_date_range_series("1/1/2000", "12/31/2002")
+ ts.index = ts.index.as_unit(unit)
+ ts.resample(freq).mean()
+
+
+@pytest.mark.parametrize("label, sec", [[None, 2.0], ["right", "4.2"]])
+def test_resample_anchored_multiday(label, sec):
+ # When resampling a range spanning multiple days, ensure that the
+ # start date gets used to determine the offset. Fixes issue where
+ # a one day period is not a multiple of the frequency.
+ #
+ # See: https://github.com/pandas-dev/pandas/issues/8683
+
+ index1 = date_range("2014-10-14 23:06:23.206", periods=3, freq="400ms")
+ index2 = date_range("2014-10-15 23:00:00", periods=2, freq="2200ms")
+ index = index1.union(index2)
+
+ s = Series(np.random.default_rng(2).standard_normal(5), index=index)
+
+ # Ensure left closing works
+ result = s.resample("2200ms", label=label).mean()
+ assert result.index[-1] == Timestamp(f"2014-10-15 23:00:{sec}00")
+
+
+def test_corner_cases(unit):
+ # miscellaneous test coverage
+
+ rng = date_range("1/1/2000", periods=12, freq="min").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ result = ts.resample("5min", closed="right", label="left").mean()
+ ex_index = date_range("1999-12-31 23:55", periods=4, freq="5min").as_unit(unit)
+ tm.assert_index_equal(result.index, ex_index)
+
+
+def test_corner_cases_date(simple_date_range_series, unit):
+ # resample to periods
+ ts = simple_date_range_series("2000-04-28", "2000-04-30 11:00", freq="h")
+ ts.index = ts.index.as_unit(unit)
+ result = ts.resample("ME").mean().to_period()
+ assert len(result) == 1
+ assert result.index[0] == Period("2000-04", freq="M")
+
+
+def test_anchored_lowercase_buglet(unit):
+ dates = date_range("4/16/2012 20:00", periods=50000, freq="s").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(dates)), index=dates)
+ # it works!
+ msg = "'d' is deprecated and will be removed in a future version."
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ ts.resample("d").mean()
+
+
+def test_upsample_apply_functions(unit):
+ # #1596
+ rng = date_range("2012-06-12", periods=4, freq="h").as_unit(unit)
+
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ result = ts.resample("20min").aggregate(["mean", "sum"])
+ assert isinstance(result, DataFrame)
+
+
+def test_resample_not_monotonic(unit):
+ rng = date_range("2012-06-12", periods=200, freq="h").as_unit(unit)
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ ts = ts.take(np.random.default_rng(2).permutation(len(ts)))
+
+ result = ts.resample("D").sum()
+ exp = ts.sort_index().resample("D").sum()
+ tm.assert_series_equal(result, exp)
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ "int64",
+ "int32",
+ "float64",
+ "float32",
+ ],
+)
+def test_resample_median_bug_1688(dtype, unit):
+ # GH#55958
+ dti = DatetimeIndex(
+ [datetime(2012, 1, 1, 0, 0, 0), datetime(2012, 1, 1, 0, 5, 0)]
+ ).as_unit(unit)
+ df = DataFrame(
+ [1, 2],
+ index=dti,
+ dtype=dtype,
+ )
+
+ result = df.resample("min").apply(lambda x: x.mean())
+ exp = df.asfreq("min")
+ tm.assert_frame_equal(result, exp)
+
+ result = df.resample("min").median()
+ exp = df.asfreq("min")
+ tm.assert_frame_equal(result, exp)
+
+
+def test_how_lambda_functions(simple_date_range_series, unit):
+ ts = simple_date_range_series("1/1/2000", "4/1/2000")
+ ts.index = ts.index.as_unit(unit)
+
+ result = ts.resample("ME").apply(lambda x: x.mean())
+ exp = ts.resample("ME").mean()
+ tm.assert_series_equal(result, exp)
+
+ foo_exp = ts.resample("ME").mean()
+ foo_exp.name = "foo"
+ bar_exp = ts.resample("ME").std()
+ bar_exp.name = "bar"
+
+ result = ts.resample("ME").apply([lambda x: x.mean(), lambda x: x.std(ddof=1)])
+ result.columns = ["foo", "bar"]
+ tm.assert_series_equal(result["foo"], foo_exp)
+ tm.assert_series_equal(result["bar"], bar_exp)
+
+ # this is a MI Series, so comparing the names of the results
+ # doesn't make sense
+ result = ts.resample("ME").aggregate(
+ {"foo": lambda x: x.mean(), "bar": lambda x: x.std(ddof=1)}
+ )
+ tm.assert_series_equal(result["foo"], foo_exp, check_names=False)
+ tm.assert_series_equal(result["bar"], bar_exp, check_names=False)
+
+
+def test_resample_unequal_times(unit):
+ # #1772
+ start = datetime(1999, 3, 1, 5)
+ # end hour is less than start
+ end = datetime(2012, 7, 31, 4)
+ bad_ind = date_range(start, end, freq="30min").as_unit(unit)
+ df = DataFrame({"close": 1}, index=bad_ind)
+
+ # it works!
+ df.resample("YS").sum()
+
+
+def test_resample_consistency(unit):
+ # GH 6418
+ # resample with bfill / limit / reindex consistency
+
+ i30 = date_range("2002-02-02", periods=4, freq="30min").as_unit(unit)
+ s = Series(np.arange(4.0), index=i30)
+ s.iloc[2] = np.nan
+
+ # Upsample by factor 3 with reindex() and resample() methods:
+ i10 = date_range(i30[0], i30[-1], freq="10min").as_unit(unit)
+
+ s10 = s.reindex(index=i10, method="bfill")
+ s10_2 = s.reindex(index=i10, method="bfill", limit=2)
+ with tm.assert_produces_warning(Pandas4Warning):
+ rl = s.reindex_like(s10, method="bfill", limit=2)
+ r10_2 = s.resample("10Min").bfill(limit=2)
+ r10 = s.resample("10Min").bfill()
+
+ # s10_2, r10, r10_2, rl should all be equal
+ tm.assert_series_equal(s10_2, r10)
+ tm.assert_series_equal(s10_2, r10_2)
+ tm.assert_series_equal(s10_2, rl)
+
+
+dates1: list[DatetimeNaTType] = [
+ datetime(2014, 10, 1),
+ datetime(2014, 9, 3),
+ datetime(2014, 11, 5),
+ datetime(2014, 9, 5),
+ datetime(2014, 10, 8),
+ datetime(2014, 7, 15),
+]
+
+dates2: list[DatetimeNaTType] = [*dates1[:2], pd.NaT, *dates1[2:4], pd.NaT, *dates1[4:]]
+dates3 = [pd.NaT, *dates1, pd.NaT]
+
+
+@pytest.mark.parametrize("dates", [dates1, dates2, dates3])
+def test_resample_timegrouper(dates, unit):
+ # GH 7227
+ dates = DatetimeIndex(dates).as_unit(unit)
+ df = DataFrame({"A": dates, "B": np.arange(len(dates))})
+ result = df.set_index("A").resample("ME").count()
+ exp_idx = DatetimeIndex(
+ ["2014-07-31", "2014-08-31", "2014-09-30", "2014-10-31", "2014-11-30"],
+ freq="ME",
+ name="A",
+ ).as_unit(unit)
+ expected = DataFrame({"B": [1, 0, 2, 2, 1]}, index=exp_idx)
+ if df["A"].isna().any():
+ expected.index = expected.index._with_freq(None)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(Grouper(freq="ME", key="A")).count()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dates", [dates1, dates2, dates3])
+def test_resample_timegrouper2(dates, unit):
+ dates = DatetimeIndex(dates).as_unit(unit)
+
+ df = DataFrame({"A": dates, "B": np.arange(len(dates)), "C": np.arange(len(dates))})
+ result = df.set_index("A").resample("ME").count()
+
+ exp_idx = DatetimeIndex(
+ ["2014-07-31", "2014-08-31", "2014-09-30", "2014-10-31", "2014-11-30"],
+ freq="ME",
+ name="A",
+ ).as_unit(unit)
+ expected = DataFrame(
+ {"B": [1, 0, 2, 2, 1], "C": [1, 0, 2, 2, 1]},
+ index=exp_idx,
+ columns=["B", "C"],
+ )
+ if df["A"].isna().any():
+ expected.index = expected.index._with_freq(None)
+ tm.assert_frame_equal(result, expected)
+
+ result = df.groupby(Grouper(freq="ME", key="A")).count()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_nunique(unit):
+ # GH 12352
+ df = DataFrame(
+ {
+ "ID": {
+ Timestamp("2015-06-05 00:00:00"): "0010100903",
+ Timestamp("2015-06-08 00:00:00"): "0010150847",
+ },
+ "DATE": {
+ Timestamp("2015-06-05 00:00:00"): "2015-06-05",
+ Timestamp("2015-06-08 00:00:00"): "2015-06-08",
+ },
+ }
+ )
+ df.index = df.index.as_unit(unit)
+ r = df.resample("D")
+ g = df.groupby(Grouper(freq="D"))
+ expected = df.groupby(Grouper(freq="D")).ID.apply(lambda x: x.nunique())
+ assert expected.name == "ID"
+
+ for t in [r, g]:
+ result = t.ID.nunique()
+ tm.assert_series_equal(result, expected)
+
+ result = df.ID.resample("D").nunique()
+ tm.assert_series_equal(result, expected)
+
+ result = df.ID.groupby(Grouper(freq="D")).nunique()
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_nunique_preserves_column_level_names(unit):
+ # see gh-23222
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=date_range("2000-01-01", periods=5, freq="D"),
+ ).abs()
+ df.index = df.index.as_unit(unit)
+ df.columns = pd.MultiIndex.from_arrays(
+ [df.columns.tolist()] * 2, names=["lev0", "lev1"]
+ )
+ result = df.resample("1h").nunique()
+ tm.assert_index_equal(df.columns, result.columns)
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda x: x.nunique(),
+ lambda x: x.agg(Series.nunique),
+ lambda x: x.agg("nunique"),
+ ],
+ ids=["nunique", "series_nunique", "nunique_str"],
+)
+def test_resample_nunique_with_date_gap(func, unit):
+ # GH 13453
+ # Since all elements are unique, these should all be the same
+ index = date_range("1-1-2000", "2-15-2000", freq="h").as_unit(unit)
+ index2 = date_range("4-15-2000", "5-15-2000", freq="h").as_unit(unit)
+ index3 = index.append(index2)
+ s = Series(range(len(index3)), index=index3, dtype="int64")
+ r = s.resample("ME")
+ result = r.count()
+ expected = func(r)
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_group_info(unit):
+ # GH10914
+
+ # use a fixed seed to always have the same uniques
+ n = 100
+ k = 10
+ prng = np.random.default_rng(2)
+
+ dr = date_range(start="2015-08-27", periods=n // 10, freq="min").as_unit(unit)
+ ts = Series(prng.integers(0, n // k, n).astype("int64"), index=prng.choice(dr, n))
+
+ left = ts.resample("30min").nunique()
+ ix = date_range(start=ts.index.min(), end=ts.index.max(), freq="30min").as_unit(
+ unit
+ )
+
+ vals = ts.values
+ bins = np.searchsorted(ix.values, ts.index, side="right")
+
+ sorter = np.lexsort((vals, bins))
+ vals, bins = vals[sorter], bins[sorter]
+
+ mask = np.r_[True, vals[1:] != vals[:-1]]
+ mask |= np.r_[True, bins[1:] != bins[:-1]]
+
+ arr = np.bincount(bins[mask] - 1, minlength=len(ix)).astype("int64", copy=False)
+ right = Series(arr, index=ix)
+
+ tm.assert_series_equal(left, right)
+
+
+def test_resample_size(unit):
+ n = 10000
+ dr = date_range("2015-09-19", periods=n, freq="min").as_unit(unit)
+ ts = Series(
+ np.random.default_rng(2).standard_normal(n),
+ index=np.random.default_rng(2).choice(dr, n),
+ )
+
+ left = ts.resample("7min").size()
+ ix = date_range(start=left.index.min(), end=ts.index.max(), freq="7min").as_unit(
+ unit
+ )
+
+ bins = np.searchsorted(ix.values, ts.index.values, side="right")
+ val = np.bincount(bins, minlength=len(ix) + 1)[1:].astype("int64", copy=False)
+
+ right = Series(val, index=ix)
+ tm.assert_series_equal(left, right)
+
+
+def test_resample_across_dst():
+ # The test resamples a DatetimeIndex with values before and after a
+ # DST change
+ # Issue: 14682
+
+ # The DatetimeIndex we will start with
+ # (note that DST happens at 03:00+02:00 -> 02:00+01:00)
+ # 2016-10-30 02:23:00+02:00, 2016-10-30 02:23:00+01:00
+ df1 = DataFrame([1477786980, 1477790580], columns=["ts"])
+ dti1 = DatetimeIndex(
+ pd.to_datetime(df1.ts, unit="s")
+ .dt.tz_localize("UTC")
+ .dt.tz_convert("Europe/Madrid")
+ )
+
+ # The expected DatetimeIndex after resampling.
+ # 2016-10-30 02:00:00+02:00, 2016-10-30 02:00:00+01:00
+ df2 = DataFrame([1477785600, 1477789200], columns=["ts"])
+ dti2 = DatetimeIndex(
+ pd.to_datetime(df2.ts, unit="s")
+ .dt.tz_localize("UTC")
+ .dt.tz_convert("Europe/Madrid"),
+ freq="h",
+ )
+ df = DataFrame([5, 5], index=dti1)
+
+ result = df.resample(rule="h").sum()
+ expected = DataFrame([5, 5], index=dti2)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_with_dst_time_change(unit):
+ # GH 24972
+ index = (
+ DatetimeIndex([1478064900001000000, 1480037118776792000], tz="UTC")
+ .tz_convert("America/Chicago")
+ .as_unit(unit)
+ )
+
+ df = DataFrame([1, 2], index=index)
+ result = df.groupby(Grouper(freq="1D")).last()
+ expected_index_values = date_range(
+ "2016-11-02", "2016-11-24", freq="D", tz="America/Chicago"
+ ).as_unit(unit)
+
+ index = DatetimeIndex(expected_index_values)
+ expected = DataFrame([1.0] + ([np.nan] * 21) + [2.0], index=index)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_dst_anchor(unit):
+ # 5172
+ dti = DatetimeIndex([datetime(2012, 11, 4, 23)], tz="US/Eastern").as_unit(unit)
+ df = DataFrame([5], index=dti)
+
+ dti = DatetimeIndex(df.index.normalize(), freq="D").as_unit(unit)
+ expected = DataFrame([5], index=dti)
+ tm.assert_frame_equal(df.resample(rule="D").sum(), expected)
+ df.resample(rule="MS").sum()
+ tm.assert_frame_equal(
+ df.resample(rule="MS").sum(),
+ DataFrame(
+ [5],
+ index=DatetimeIndex(
+ [datetime(2012, 11, 1)], tz="US/Eastern", freq="MS"
+ ).as_unit(unit),
+ ),
+ )
+
+
+def test_resample_dst_anchor2(unit):
+ dti = date_range(
+ "2013-09-30", "2013-11-02", freq="30Min", tz="Europe/Paris"
+ ).as_unit(unit)
+ values = range(dti.size)
+ df = DataFrame({"a": values, "b": values, "c": values}, index=dti, dtype="int64")
+ how = {"a": "min", "b": "max", "c": "count"}
+
+ rs = df.resample("W-MON")
+ result = rs.agg(how)[["a", "b", "c"]]
+ expected = DataFrame(
+ {
+ "a": [0, 48, 384, 720, 1056, 1394],
+ "b": [47, 383, 719, 1055, 1393, 1586],
+ "c": [48, 336, 336, 336, 338, 193],
+ },
+ index=date_range(
+ "9/30/2013", "11/4/2013", freq="W-MON", tz="Europe/Paris"
+ ).as_unit(unit),
+ )
+ tm.assert_frame_equal(
+ result,
+ expected,
+ "W-MON Frequency",
+ )
+
+ rs2 = df.resample("2W-MON")
+ result2 = rs2.agg(how)[["a", "b", "c"]]
+ expected2 = DataFrame(
+ {
+ "a": [0, 48, 720, 1394],
+ "b": [47, 719, 1393, 1586],
+ "c": [48, 672, 674, 193],
+ },
+ index=date_range(
+ "9/30/2013", "11/11/2013", freq="2W-MON", tz="Europe/Paris"
+ ).as_unit(unit),
+ )
+ tm.assert_frame_equal(
+ result2,
+ expected2,
+ "2W-MON Frequency",
+ )
+
+ rs3 = df.resample("MS")
+ result3 = rs3.agg(how)[["a", "b", "c"]]
+ expected3 = DataFrame(
+ {"a": [0, 48, 1538], "b": [47, 1537, 1586], "c": [48, 1490, 49]},
+ index=date_range("9/1/2013", "11/1/2013", freq="MS", tz="Europe/Paris").as_unit(
+ unit
+ ),
+ )
+ tm.assert_frame_equal(
+ result3,
+ expected3,
+ "MS Frequency",
+ )
+
+ rs4 = df.resample("2MS")
+ result4 = rs4.agg(how)[["a", "b", "c"]]
+ expected4 = DataFrame(
+ {"a": [0, 1538], "b": [1537, 1586], "c": [1538, 49]},
+ index=date_range(
+ "9/1/2013", "11/1/2013", freq="2MS", tz="Europe/Paris"
+ ).as_unit(unit),
+ )
+ tm.assert_frame_equal(
+ result4,
+ expected4,
+ "2MS Frequency",
+ )
+
+ df_daily = df["10/26/2013":"10/29/2013"]
+ rs_d = df_daily.resample("D")
+ result_d = rs_d.agg({"a": "min", "b": "max", "c": "count"})[["a", "b", "c"]]
+ expected_d = DataFrame(
+ {
+ "a": [1248, 1296, 1346, 1394],
+ "b": [1295, 1345, 1393, 1441],
+ "c": [48, 50, 48, 48],
+ },
+ index=date_range(
+ "10/26/2013", "10/29/2013", freq="D", tz="Europe/Paris"
+ ).as_unit(unit),
+ )
+ tm.assert_frame_equal(
+ result_d,
+ expected_d,
+ "D Frequency",
+ )
+
+
+def test_downsample_across_dst(unit):
+ # GH 8531
+ tz = zoneinfo.ZoneInfo("Europe/Berlin")
+ dt = datetime(2014, 10, 26)
+ dates = date_range(dt.astimezone(tz), periods=4, freq="2h").as_unit(unit)
+ result = Series(5, index=dates).resample("h").mean()
+ expected = Series(
+ [5.0, np.nan] * 3 + [5.0],
+ index=date_range(dt.astimezone(tz), periods=7, freq="h").as_unit(unit),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_downsample_across_dst_weekly(unit):
+ # GH 9119, GH 21459
+ df = DataFrame(
+ index=DatetimeIndex(
+ ["2017-03-25", "2017-03-26", "2017-03-27", "2017-03-28", "2017-03-29"],
+ tz="Europe/Amsterdam",
+ ).as_unit(unit),
+ data=[11, 12, 13, 14, 15],
+ )
+ result = df.resample("1W").sum()
+ expected = DataFrame(
+ [23, 42],
+ index=DatetimeIndex(
+ ["2017-03-26", "2017-04-02"], tz="Europe/Amsterdam", freq="W"
+ ).as_unit(unit),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_downsample_across_dst_weekly_2(unit):
+ # GH 9119, GH 21459
+ idx = date_range("2013-04-01", "2013-05-01", tz="Europe/London", freq="h").as_unit(
+ unit
+ )
+ s = Series(index=idx, dtype=np.float64)
+ result = s.resample("W").mean()
+ expected = Series(
+ index=date_range("2013-04-07", freq="W", periods=5, tz="Europe/London").as_unit(
+ unit
+ ),
+ dtype=np.float64,
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_downsample_dst_at_midnight(unit):
+ # GH 25758
+ start = datetime(2018, 11, 3, 12)
+ end = datetime(2018, 11, 5, 12)
+ index = date_range(start, end, freq="1h").as_unit(unit)
+ index = index.tz_localize("UTC").tz_convert("America/Havana")
+ data = list(range(len(index)))
+ dataframe = DataFrame(data, index=index)
+ result = dataframe.groupby(Grouper(freq="1D")).mean()
+
+ dti = date_range("2018-11-03", periods=3).tz_localize(
+ "America/Havana", ambiguous=True
+ )
+ dti = DatetimeIndex(dti, freq="D").as_unit(unit)
+ expected = DataFrame([7.5, 28.0, 44.5], index=dti)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_with_nat(unit):
+ # GH 13020
+ index = DatetimeIndex(
+ [
+ pd.NaT,
+ "1970-01-01 00:00:00",
+ pd.NaT,
+ "1970-01-01 00:00:01",
+ "1970-01-01 00:00:02",
+ ]
+ ).as_unit(unit)
+ frame = DataFrame([2, 3, 5, 7, 11], index=index)
+
+ index_1s = DatetimeIndex(
+ ["1970-01-01 00:00:00", "1970-01-01 00:00:01", "1970-01-01 00:00:02"]
+ ).as_unit(unit)
+ frame_1s = DataFrame([3.0, 7.0, 11.0], index=index_1s)
+ tm.assert_frame_equal(frame.resample("1s").mean(), frame_1s)
+
+ index_2s = DatetimeIndex(["1970-01-01 00:00:00", "1970-01-01 00:00:02"]).as_unit(
+ unit
+ )
+ frame_2s = DataFrame([5.0, 11.0], index=index_2s)
+ tm.assert_frame_equal(frame.resample("2s").mean(), frame_2s)
+
+ index_3s = DatetimeIndex(["1970-01-01 00:00:00"]).as_unit(unit)
+ frame_3s = DataFrame([7.0], index=index_3s)
+ tm.assert_frame_equal(frame.resample("3s").mean(), frame_3s)
+
+ tm.assert_frame_equal(frame.resample("60s").mean(), frame_3s)
+
+
+def test_resample_datetime_values(unit):
+ # GH 13119
+ # check that datetime dtype is preserved when NaT values are
+ # introduced by the resampling
+
+ dates = [datetime(2016, 1, 15), datetime(2016, 1, 19)]
+ df = DataFrame({"timestamp": dates}, index=dates)
+ df.index = df.index.as_unit(unit)
+
+ exp = Series(
+ [datetime(2016, 1, 15), pd.NaT, datetime(2016, 1, 19)],
+ index=date_range("2016-01-15", periods=3, freq="2D").as_unit(unit),
+ name="timestamp",
+ )
+
+ res = df.resample("2D").first()["timestamp"]
+ tm.assert_series_equal(res, exp)
+ res = df["timestamp"].resample("2D").first()
+ tm.assert_series_equal(res, exp)
+
+
+def test_resample_apply_with_additional_args(unit):
+ # GH 14615
+ index = date_range("1/1/2000 00:00:00", "1/1/2000 00:13:00", freq="Min")
+ series = Series(range(len(index)), index=index)
+ series.index.name = "index"
+
+ def f(data, add_arg):
+ return np.mean(data) * add_arg
+
+ series.index = series.index.as_unit(unit)
+
+ multiplier = 10
+ result = series.resample("D").apply(f, multiplier)
+ expected = series.resample("D").mean().multiply(multiplier)
+ tm.assert_series_equal(result, expected)
+
+ # Testing as kwarg
+ result = series.resample("D").apply(f, add_arg=multiplier)
+ expected = series.resample("D").mean().multiply(multiplier)
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_apply_with_additional_args2():
+ # Testing dataframe
+ def f(data, add_arg):
+ return np.mean(data) * add_arg
+
+ multiplier = 10
+
+ df = DataFrame({"A": 1, "B": 2}, index=date_range("2017", periods=10))
+ result = df.groupby("A").resample("D").agg(f, multiplier).astype(float)
+ expected = df.groupby("A").resample("D").mean().multiply(multiplier)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("k", [1, 2, 3])
+@pytest.mark.parametrize(
+ "n1, freq1, n2, freq2",
+ [
+ (30, "s", 0.5, "Min"),
+ (60, "s", 1, "Min"),
+ (3600, "s", 1, "h"),
+ (60, "Min", 1, "h"),
+ (21600, "s", 0.25, "D"),
+ (86400, "s", 1, "D"),
+ (43200, "s", 0.5, "D"),
+ (1440, "Min", 1, "D"),
+ (12, "h", 0.5, "D"),
+ (24, "h", 1, "D"),
+ ],
+)
+def test_resample_equivalent_offsets(n1, freq1, n2, freq2, k, unit):
+ # GH 24127
+ n1_ = n1 * k
+ n2_ = n2 * k
+ dti = date_range("1991-09-05", "1991-09-06", freq=freq1).as_unit(unit)
+ ser = Series(range(len(dti)), index=dti)
+
+ result1 = ser.resample(str(n1_) + freq1).mean()
+ result2 = ser.resample(str(n2_) + freq2).mean()
+ if freq2 == "D" and isinstance(result2.index.freq, Day):
+ # GH#55502 Day is no longer a Tick so no longer compares as equivalent,
+ # but the actual values we expect should still match
+ result2.index.freq = to_offset(Timedelta(days=result2.index.freq.n))
+ tm.assert_series_equal(result1, result2)
+
+
+@pytest.mark.parametrize(
+ "first,last,freq,exp_first,exp_last",
+ [
+ ("19910905", "19920406", "D", "19910905", "19920407"),
+ ("19910905 00:00", "19920406 06:00", "D", "19910905", "19920407"),
+ ("19910905 06:00", "19920406 06:00", "h", "19910905 06:00", "19920406 07:00"),
+ ("19910906", "19920406", "ME", "19910831", "19920430"),
+ ("19910831", "19920430", "ME", "19910831", "19920531"),
+ ("1991-08", "1992-04", "ME", "19910831", "19920531"),
+ ],
+)
+def test_get_timestamp_range_edges(first, last, freq, exp_first, exp_last, unit):
+ first = Period(first)
+ first = first.to_timestamp(first.freq).as_unit(unit)
+ last = Period(last)
+ last = last.to_timestamp(last.freq).as_unit(unit)
+
+ exp_first = Timestamp(exp_first)
+ exp_last = Timestamp(exp_last)
+
+ freq = pd.tseries.frequencies.to_offset(freq)
+ result = _get_timestamp_range_edges(first, last, freq, unit="ns")
+ expected = (exp_first, exp_last)
+ assert result == expected
+
+
+@pytest.mark.parametrize("duplicates", [True, False])
+def test_resample_apply_product(duplicates, unit):
+ # GH 5586
+ index = date_range(start="2012-01-31", freq="ME", periods=12).as_unit(unit)
+
+ ts = Series(range(12), index=index)
+ df = DataFrame({"A": ts, "B": ts + 2})
+ if duplicates:
+ df.columns = ["A", "A"]
+
+ result = df.resample("QE").apply(np.prod)
+ expected = DataFrame(
+ np.array([[0, 24], [60, 210], [336, 720], [990, 1716]], dtype=np.int64),
+ index=DatetimeIndex(
+ ["2012-03-31", "2012-06-30", "2012-09-30", "2012-12-31"], freq="QE-DEC"
+ ).as_unit(unit),
+ columns=df.columns,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "first,last,freq_in,freq_out,exp_last",
+ [
+ (
+ "2020-03-28",
+ "2020-03-31",
+ "D",
+ "24h",
+ "2020-03-30 01:00",
+ ), # includes transition into DST
+ (
+ "2020-03-28",
+ "2020-10-27",
+ "D",
+ "24h",
+ "2020-10-27 00:00",
+ ), # includes transition into and out of DST
+ (
+ "2020-10-25",
+ "2020-10-27",
+ "D",
+ "24h",
+ "2020-10-26 23:00",
+ ), # includes transition out of DST
+ (
+ "2020-03-28",
+ "2020-03-31",
+ "24h",
+ "D",
+ "2020-03-30 00:00",
+ ), # same as above, but from 24H to D
+ ("2020-03-28", "2020-10-27", "24h", "D", "2020-10-27 00:00"),
+ ("2020-10-25", "2020-10-27", "24h", "D", "2020-10-26 00:00"),
+ ],
+)
+def test_resample_calendar_day_with_dst(
+ first: str, last: str, freq_in: str, freq_out: str, exp_last: str, unit
+):
+ # GH 35219
+ ts = Series(
+ 1.0, date_range(first, last, freq=freq_in, tz="Europe/Amsterdam").as_unit(unit)
+ )
+ result = ts.resample(freq_out).ffill()
+ expected = Series(
+ 1.0,
+ date_range(first, exp_last, freq=freq_out, tz="Europe/Amsterdam").as_unit(unit),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("func", ["min", "max", "first", "last"])
+def test_resample_aggregate_functions_min_count(func, unit):
+ # GH#37768
+ index = date_range(start="2020", freq="ME", periods=3).as_unit(unit)
+ ser = Series([1, np.nan, np.nan], index)
+ result = getattr(ser.resample("QE"), func)(min_count=2)
+ expected = Series(
+ [np.nan],
+ index=DatetimeIndex(["2020-03-31"], freq="QE-DEC").as_unit(unit),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_unsigned_int(any_unsigned_int_numpy_dtype, unit):
+ # gh-43329
+ df = DataFrame(
+ index=date_range(start="2000-01-01", end="2000-01-03 23", freq="12h").as_unit(
+ unit
+ ),
+ columns=["x"],
+ data=[0, 1, 0] * 2,
+ dtype=any_unsigned_int_numpy_dtype,
+ )
+ df = df.loc[(df.index < "2000-01-02") | (df.index > "2000-01-03"), :]
+
+ result = df.resample("D").max()
+
+ expected = DataFrame(
+ [1, np.nan, 0],
+ columns=["x"],
+ index=date_range(start="2000-01-01", end="2000-01-03 23", freq="D").as_unit(
+ unit
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_long_rule_non_nano():
+ # https://github.com/pandas-dev/pandas/issues/51024
+ idx = date_range("0300-01-01", "2000-01-01", unit="s", freq="100YE")
+ ser = Series([1, 4, 2, 8, 5, 7, 1, 4, 2, 8, 5, 7, 1, 4, 2, 8, 5], index=idx)
+ result = ser.resample("200YE").mean()
+ expected_idx = DatetimeIndex(
+ np.array(
+ [
+ "0300-12-31",
+ "0500-12-31",
+ "0700-12-31",
+ "0900-12-31",
+ "1100-12-31",
+ "1300-12-31",
+ "1500-12-31",
+ "1700-12-31",
+ "1900-12-31",
+ ]
+ ).astype("datetime64[s]"),
+ freq="200YE-DEC",
+ )
+ expected = Series([1.0, 3.0, 6.5, 4.0, 3.0, 6.5, 4.0, 3.0, 6.5], index=expected_idx)
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_empty_series_with_tz():
+ # GH#53664
+ df = DataFrame({"ts": [], "values": []}).astype(
+ {"ts": "datetime64[ns, Atlantic/Faroe]"}
+ )
+ rs = df.resample("2MS", on="ts", closed="left", label="left")
+ result = rs["values"].sum()
+
+ expected_idx = DatetimeIndex(
+ [], freq="2MS", name="ts", dtype="datetime64[ns, Atlantic/Faroe]"
+ )
+ expected = Series([], index=expected_idx, name="values", dtype="float64")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("freq", ["2M", "2m", "2Q", "2Q-SEP", "2q-sep", "1Y", "2Y-MAR"])
+def test_resample_M_Q_Y_raises(freq):
+ msg = f"Invalid frequency: {freq}"
+
+ s = Series(range(10), index=date_range("20130101", freq="D", periods=10))
+ with pytest.raises(ValueError, match=msg):
+ s.resample(freq).mean()
+
+
+@pytest.mark.parametrize("freq", ["2BM", "1bm", "1BQ", "2BQ-MAR", "2bq=-mar"])
+def test_resample_BM_BQ_raises(freq):
+ msg = f"Invalid frequency: {freq}"
+
+ s = Series(range(10), index=date_range("20130101", freq="D", periods=10))
+ with pytest.raises(ValueError, match=msg):
+ s.resample(freq).mean()
+
+
+@pytest.mark.parametrize(
+ "freq,freq_depr,data",
+ [
+ ("1W-SUN", "1w-sun", ["2013-01-06"]),
+ ("1D", "1d", ["2013-01-01"]),
+ ("1B", "1b", ["2013-01-01"]),
+ ("1C", "1c", ["2013-01-01"]),
+ ],
+)
+def test_resample_depr_lowercase_frequency(freq, freq_depr, data):
+ msg = f"'{freq_depr[1:]}' is deprecated and will be removed in a future version."
+
+ s = Series(range(5), index=date_range("20130101", freq="h", periods=5, unit="ns"))
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = s.resample(freq_depr).mean()
+
+ exp_dti = DatetimeIndex(data=data, dtype="datetime64[ns]", freq=freq)
+ expected = Series(2.0, index=exp_dti)
+ tm.assert_series_equal(result, expected, check_freq=False)
+ # GH#41943 check_freq=False bc 24H and D no longer compare as equal
+
+
+def test_resample_ms_closed_right(unit):
+ # https://github.com/pandas-dev/pandas/issues/55271
+ dti = date_range(start="2020-01-31", freq="1min", periods=6000, unit=unit)
+ df = DataFrame({"ts": dti}, index=dti)
+ grouped = df.resample("MS", closed="right")
+ result = grouped.last()
+ exp_dti = DatetimeIndex(
+ [datetime(2020, 1, 1), datetime(2020, 2, 1)], freq="MS"
+ ).as_unit(unit)
+ expected = DataFrame(
+ {"ts": [datetime(2020, 2, 1), datetime(2020, 2, 4, 3, 59)]},
+ index=exp_dti,
+ ).astype(f"M8[{unit}]")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("freq", ["B", "C"])
+def test_resample_c_b_closed_right(freq: str, unit):
+ # https://github.com/pandas-dev/pandas/issues/55281
+ dti = date_range(start="2020-01-31", freq="1min", periods=6000, unit=unit)
+ df = DataFrame({"ts": dti}, index=dti)
+ grouped = df.resample(freq, closed="right")
+ result = grouped.last()
+
+ exp_dti = DatetimeIndex(
+ [
+ datetime(2020, 1, 30),
+ datetime(2020, 1, 31),
+ datetime(2020, 2, 3),
+ datetime(2020, 2, 4),
+ ],
+ freq=freq,
+ ).as_unit(unit)
+ expected = DataFrame(
+ {
+ "ts": [
+ datetime(2020, 1, 31),
+ datetime(2020, 2, 3),
+ datetime(2020, 2, 4),
+ datetime(2020, 2, 4, 3, 59),
+ ]
+ },
+ index=exp_dti,
+ ).astype(f"M8[{unit}]")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_b_55282(unit):
+ # https://github.com/pandas-dev/pandas/issues/55282
+ dti = date_range("2023-09-26", periods=6, freq="12h", unit=unit)
+ ser = Series([1, 2, 3, 4, 5, 6], index=dti)
+ result = ser.resample("B", closed="right", label="right").mean()
+
+ exp_dti = DatetimeIndex(
+ [
+ datetime(2023, 9, 26),
+ datetime(2023, 9, 27),
+ datetime(2023, 9, 28),
+ datetime(2023, 9, 29),
+ ],
+ freq="B",
+ ).as_unit(unit)
+ expected = Series(
+ [1.0, 2.5, 4.5, 6.0],
+ index=exp_dti,
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+@pytest.mark.parametrize(
+ "tz",
+ [
+ None,
+ pytest.param(
+ "UTC",
+ marks=pytest.mark.xfail(
+ condition=is_platform_windows() and pa_version_under22p0,
+ reason="TODO: Set ARROW_TIMEZONE_DATABASE env var in CI",
+ ),
+ ),
+ ],
+)
+def test_arrow_timestamp_resample(tz):
+ # GH 56371
+ idx = Series(date_range("2020-01-01", periods=5), dtype="timestamp[ns][pyarrow]")
+ if tz is not None:
+ idx = idx.dt.tz_localize(tz)
+ expected = Series(np.arange(5, dtype=np.float64), index=idx)
+ result = expected.resample("1D").mean()
+ tm.assert_series_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_arrow_timestamp_resample_keep_index_name():
+ # https://github.com/pandas-dev/pandas/issues/61222
+ idx = Series(date_range("2020-01-01", periods=5), dtype="timestamp[ns][pyarrow]")
+ expected = Series(np.arange(5, dtype=np.float64), index=idx)
+ expected.index.name = "index_name"
+ result = expected.resample("1D").mean()
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_unit_second_large_years():
+ # GH#57427
+ index = DatetimeIndex(
+ date_range(start=Timestamp("1950-01-01"), periods=10, freq="1000YS", unit="s")
+ )
+ ser = Series(1, index=index)
+ result = ser.resample("2000YS").sum()
+ expected = Series(2, index=index[::2])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("freq", ["1A", "2A-MAR"])
+def test_resample_A_raises(freq):
+ msg = f"Invalid frequency: {freq[1:]}"
+
+ s = Series(range(10), index=date_range("20130101", freq="D", periods=10))
+ with pytest.raises(ValueError, match=msg):
+ s.resample(freq).mean()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_period_index.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_period_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7e2613b823f75f83bd1fdb840d2e25df26c8123
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_period_index.py
@@ -0,0 +1,1032 @@
+from datetime import (
+ datetime,
+ timezone,
+)
+import re
+import warnings
+import zoneinfo
+
+import dateutil
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs.ccalendar import (
+ DAYS,
+ MONTHS,
+)
+from pandas._libs.tslibs.period import IncompatibleFrequency
+from pandas.errors import InvalidIndexError
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.core.indexes.datetimes import date_range
+from pandas.core.indexes.period import (
+ Period,
+ PeriodIndex,
+ period_range,
+)
+from pandas.core.resample import _get_period_range_edges
+
+from pandas.tseries import offsets
+
+
+@pytest.fixture
+def simple_period_range_series():
+ """
+ Series with period range index and random data for test purposes.
+ """
+
+ def _simple_period_range_series(start, end, freq="D"):
+ with warnings.catch_warnings():
+ # suppress Period[B] deprecation warning
+ msg = "|".join(["Period with BDay freq", r"PeriodDtype\[B\] is deprecated"])
+ warnings.filterwarnings(
+ "ignore",
+ msg,
+ category=FutureWarning,
+ )
+ rng = period_range(start, end, freq=freq)
+ return Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ return _simple_period_range_series
+
+
+class TestPeriodIndex:
+ @pytest.mark.parametrize("freq", ["2D", "1h", "2h"])
+ def test_asfreq(self, frame_or_series, freq):
+ # GH 12884, 15944
+
+ obj = frame_or_series(range(5), index=period_range("2020-01-01", periods=5))
+
+ expected = obj.to_timestamp().resample(freq).asfreq()
+ result = obj.to_timestamp().resample(freq).asfreq()
+ tm.assert_almost_equal(result, expected)
+
+ start = obj.index[0].to_timestamp(how="start")
+ end = (obj.index[-1] + obj.index.freq).to_timestamp(how="start")
+ new_index = date_range(start=start, end=end, freq=freq, inclusive="left")
+ expected = obj.to_timestamp().reindex(new_index).to_period(freq)
+
+ result = obj.resample(freq).asfreq()
+ tm.assert_almost_equal(result, expected)
+
+ result = obj.resample(freq).asfreq().to_timestamp().to_period()
+ tm.assert_almost_equal(result, expected)
+
+ def test_asfreq_fill_value(self):
+ # test for fill value during resampling, issue 3715
+
+ index = period_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D")
+ s = Series(range(len(index)), index=index)
+ new_index = date_range(
+ s.index[0].to_timestamp(how="start"),
+ (s.index[-1]).to_timestamp(how="start"),
+ freq="1h",
+ )
+ expected = s.to_timestamp().reindex(new_index, fill_value=4.0)
+ result = s.to_timestamp().resample("1h").asfreq(fill_value=4.0)
+ tm.assert_series_equal(result, expected)
+
+ frame = s.to_frame("value")
+ new_index = date_range(
+ frame.index[0].to_timestamp(how="start"),
+ (frame.index[-1]).to_timestamp(how="start"),
+ freq="1h",
+ )
+ expected = frame.to_timestamp().reindex(new_index, fill_value=3.0)
+ result = frame.to_timestamp().resample("1h").asfreq(fill_value=3.0)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("freq", ["h", "12h", "2D", "W"])
+ @pytest.mark.parametrize("kwargs", [{"on": "date"}, {"level": "d"}])
+ def test_selection(self, freq, kwargs):
+ # This is a bug, these should be implemented
+ # GH 14008
+ index = period_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D")
+ rng = np.arange(len(index), dtype=np.int64)
+ df = DataFrame(
+ {"date": index, "a": rng},
+ index=pd.MultiIndex.from_arrays([rng, index], names=["v", "d"]),
+ )
+ msg = (
+ "Resampling from level= or on= selection with a PeriodIndex is "
+ r"not currently supported, use \.set_index\(\.\.\.\) to "
+ "explicitly set index"
+ )
+ with pytest.raises(NotImplementedError, match=msg):
+ df.resample(freq, **kwargs)
+
+ @pytest.mark.parametrize("month", MONTHS)
+ @pytest.mark.parametrize("meth", ["ffill", "bfill"])
+ @pytest.mark.parametrize("conv", ["start", "end"])
+ @pytest.mark.parametrize(
+ ("offset", "period"), [("D", "D"), ("B", "B"), ("ME", "M"), ("QE", "Q")]
+ )
+ def test_annual_upsample_cases(
+ self, offset, period, conv, meth, month, simple_period_range_series
+ ):
+ ts = simple_period_range_series("1/1/1990", "12/31/1990", freq=f"Y-{month}")
+ warn = FutureWarning if period == "B" else None
+ msg = r"PeriodDtype\[B\] is deprecated"
+ with tm.assert_produces_warning(warn, match=msg):
+ result = getattr(ts.resample(period, convention=conv), meth)()
+ expected = result.to_timestamp(period, how=conv)
+ expected = expected.asfreq(offset, meth).to_period()
+ tm.assert_series_equal(result, expected)
+
+ def test_basic_downsample(self, simple_period_range_series):
+ ts = simple_period_range_series("1/1/1990", "6/30/1995", freq="M")
+ result = ts.resample("Y-DEC").mean()
+
+ expected = ts.groupby(ts.index.year).mean()
+ expected.index = period_range("1/1/1990", "6/30/1995", freq="Y-DEC")
+ tm.assert_series_equal(result, expected)
+
+ # this is ok
+ tm.assert_series_equal(ts.resample("Y-DEC").mean(), result)
+ tm.assert_series_equal(ts.resample("Y").mean(), result)
+
+ @pytest.mark.parametrize(
+ "rule,expected_error_msg",
+ [
+ ("Y-DEC", ""),
+ ("Q-MAR", ""),
+ ("M", ""),
+ ("W-THU", ""),
+ ],
+ )
+ def test_not_subperiod(self, simple_period_range_series, rule, expected_error_msg):
+ # These are incompatible period rules for resampling
+ ts = simple_period_range_series("1/1/1990", "6/30/1995", freq="W-WED")
+ msg = (
+ "Frequency cannot be resampled to "
+ f"{expected_error_msg}, as they are not sub or super periods"
+ )
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ ts.resample(rule).mean()
+
+ @pytest.mark.parametrize("freq", ["D", "2D"])
+ def test_basic_upsample(self, freq, simple_period_range_series):
+ ts = simple_period_range_series("1/1/1990", "6/30/1995", freq="M")
+ result = ts.resample("Y-DEC").mean()
+
+ resampled = result.resample(freq, convention="end").ffill()
+ expected = result.to_timestamp(freq, how="end")
+ expected = expected.asfreq(freq, "ffill").to_period(freq)
+ tm.assert_series_equal(resampled, expected)
+
+ def test_upsample_with_limit(self):
+ rng = period_range("1/1/2000", periods=5, freq="Y")
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), rng)
+
+ result = ts.resample("M", convention="end").ffill(limit=2)
+ expected = ts.asfreq("M").reindex(result.index, method="ffill", limit=2)
+ tm.assert_series_equal(result, expected)
+
+ def test_annual_upsample(self, simple_period_range_series):
+ ts = simple_period_range_series("1/1/1990", "12/31/1995", freq="Y-DEC")
+ df = DataFrame({"a": ts})
+ rdf = df.resample("D").ffill()
+ exp = df["a"].resample("D").ffill()
+ tm.assert_series_equal(rdf["a"], exp)
+
+ def test_annual_upsample2(self):
+ rng = period_range("2000", "2003", freq="Y-DEC")
+ ts = Series([1, 2, 3, 4], index=rng)
+
+ result = ts.resample("M").ffill()
+ ex_index = period_range("2000-01", "2003-12", freq="M")
+
+ expected = ts.asfreq("M", how="start").reindex(ex_index, method="ffill")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("month", MONTHS)
+ @pytest.mark.parametrize("convention", ["start", "end"])
+ @pytest.mark.parametrize(
+ ("offset", "period"), [("D", "D"), ("B", "B"), ("ME", "M")]
+ )
+ def test_quarterly_upsample(
+ self, month, offset, period, convention, simple_period_range_series
+ ):
+ freq = f"Q-{month}"
+ ts = simple_period_range_series("1/1/1990", "12/31/1991", freq=freq)
+ warn = FutureWarning if period == "B" else None
+ msg = r"PeriodDtype\[B\] is deprecated"
+ with tm.assert_produces_warning(warn, match=msg):
+ result = ts.resample(period, convention=convention).ffill()
+ expected = result.to_timestamp(period, how=convention)
+ expected = expected.asfreq(offset, "ffill").to_period()
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("target", ["D", "B"])
+ @pytest.mark.parametrize("convention", ["start", "end"])
+ def test_monthly_upsample(self, target, convention, simple_period_range_series):
+ ts = simple_period_range_series("1/1/1990", "12/31/1995", freq="M")
+
+ warn = None if target == "D" else FutureWarning
+ msg = r"PeriodDtype\[B\] is deprecated"
+ with tm.assert_produces_warning(warn, match=msg):
+ result = ts.resample(target, convention=convention).ffill()
+ expected = result.to_timestamp(target, how=convention)
+ expected = expected.asfreq(target, "ffill").to_period()
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_basic(self):
+ # GH3609
+ s = Series(
+ range(100),
+ index=date_range("20130101", freq="s", periods=100, name="idx"),
+ dtype="float",
+ )
+ s[10:30] = np.nan
+ index = PeriodIndex(
+ [Period("2013-01-01 00:00", "min"), Period("2013-01-01 00:01", "min")],
+ name="idx",
+ )
+ expected = Series([34.5, 79.5], index=index)
+ result = s.to_period().resample("min").mean()
+ tm.assert_series_equal(result, expected)
+ result2 = s.resample("min").mean().to_period()
+ tm.assert_series_equal(result2, expected)
+
+ @pytest.mark.parametrize(
+ "freq,expected_vals", [("M", [31, 29, 31, 9]), ("2M", [31 + 29, 31 + 9])]
+ )
+ def test_resample_count(self, freq, expected_vals):
+ # GH12774
+ series = Series(1, index=period_range(start="2000", periods=100))
+ result = series.resample(freq).count()
+ expected_index = period_range(
+ start="2000", freq=freq, periods=len(expected_vals)
+ )
+ expected = Series(expected_vals, index=expected_index)
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_same_freq(self, resample_method):
+ # GH12770
+ series = Series(range(3), index=period_range(start="2000", periods=3, freq="M"))
+ expected = series
+
+ result = getattr(series.resample("M"), resample_method)()
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_incompat_freq(self):
+ msg = (
+ "Frequency cannot be resampled to , "
+ "as they are not sub or super periods"
+ )
+ pi = period_range(start="2000", periods=3, freq="M")
+ ser = Series(range(3), index=pi)
+ rs = ser.resample("W")
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ # TODO: should this raise at the resample call instead of at the mean call?
+ rs.mean()
+
+ @pytest.mark.parametrize(
+ "tz",
+ [
+ zoneinfo.ZoneInfo("America/Los_Angeles"),
+ dateutil.tz.gettz("America/Los_Angeles"),
+ ],
+ )
+ def test_with_local_timezone(self, tz):
+ # see gh-5430
+ local_timezone = tz
+
+ start = datetime(
+ year=2013, month=11, day=1, hour=0, minute=0, tzinfo=timezone.utc
+ )
+ # 1 day later
+ end = datetime(
+ year=2013, month=11, day=2, hour=0, minute=0, tzinfo=timezone.utc
+ )
+
+ index = date_range(start, end, freq="h", name="idx")
+
+ series = Series(1, index=index)
+ series = series.tz_convert(local_timezone)
+ msg = "Converting to PeriodArray/Index representation will drop timezone"
+ with tm.assert_produces_warning(UserWarning, match=msg):
+ result = series.resample("D").mean().to_period()
+
+ # Create the expected series
+ # Index is moved back a day with the timezone conversion from UTC to
+ # Pacific
+ expected_index = (
+ period_range(start=start, end=end, freq="D", name="idx") - offsets.Day()
+ )
+ expected = Series(1.0, index=expected_index)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "tz",
+ [
+ zoneinfo.ZoneInfo("America/Los_Angeles"),
+ dateutil.tz.gettz("America/Los_Angeles"),
+ ],
+ )
+ def test_resample_with_tz(self, tz, unit):
+ # GH 13238
+ dti = date_range("2017-01-01", periods=48, freq="h", tz=tz, unit=unit)
+ ser = Series(2, index=dti)
+ result = ser.resample("D").mean()
+ exp_dti = pd.DatetimeIndex(
+ ["2017-01-01", "2017-01-02"], tz=tz, freq="D"
+ ).as_unit(unit)
+ expected = Series(
+ 2.0,
+ index=exp_dti,
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_nonexistent_time_bin_edge(self):
+ # GH 19375
+ index = date_range("2017-03-12", "2017-03-12 1:45:00", freq="15min")
+ s = Series(np.zeros(len(index)), index=index)
+ expected = s.tz_localize("US/Pacific")
+ expected.index = pd.DatetimeIndex(expected.index, freq="900s")
+ result = expected.resample("900s").mean()
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_nonexistent_time_bin_edge2(self):
+ # GH 23742
+ index = date_range(start="2017-10-10", end="2017-10-20", freq="1h")
+ index = index.tz_localize("UTC").tz_convert("America/Sao_Paulo")
+ df = DataFrame(data=list(range(len(index))), index=index)
+ result = df.groupby(pd.Grouper(freq="1D")).count()
+ expected = date_range(
+ start="2017-10-09",
+ end="2017-10-20",
+ freq="D",
+ tz="America/Sao_Paulo",
+ nonexistent="shift_forward",
+ inclusive="left",
+ )
+ tm.assert_index_equal(result.index, expected)
+
+ def test_resample_ambiguous_time_bin_edge(self):
+ # GH 10117
+ idx = date_range(
+ "2014-10-25 22:00:00",
+ "2014-10-26 00:30:00",
+ freq="30min",
+ tz="Europe/London",
+ )
+ expected = Series(np.zeros(len(idx)), index=idx)
+ result = expected.resample("30min").mean()
+ tm.assert_series_equal(result, expected)
+
+ def test_fill_method_and_how_upsample(self):
+ # GH2073
+ s = Series(
+ np.arange(9, dtype="int64"),
+ index=date_range("2010-01-01", periods=9, freq="QE"),
+ )
+ last = s.resample("ME").ffill()
+ both = s.resample("ME").ffill().resample("ME").last().astype("int64")
+ tm.assert_series_equal(last, both)
+
+ @pytest.mark.parametrize("day", DAYS)
+ @pytest.mark.parametrize("target", ["D", "B"])
+ @pytest.mark.parametrize("convention", ["start", "end"])
+ def test_weekly_upsample(self, day, target, convention, simple_period_range_series):
+ freq = f"W-{day}"
+ ts = simple_period_range_series("1/1/1990", "07/31/1990", freq=freq)
+ warn = None if target == "D" else FutureWarning
+ msg = r"PeriodDtype\[B\] is deprecated"
+ with tm.assert_produces_warning(warn, match=msg):
+ result = ts.resample(target, convention=convention).ffill()
+ expected = result.to_timestamp(target, how=convention)
+ expected = expected.asfreq(target, "ffill").to_period()
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_to_timestamps(self, simple_period_range_series):
+ ts = simple_period_range_series("1/1/1990", "12/31/1995", freq="M")
+
+ result = ts.resample("Y-DEC").mean().to_timestamp()
+ expected = ts.resample("Y-DEC").mean().to_timestamp(how="start")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("month", MONTHS)
+ def test_resample_to_quarterly(self, simple_period_range_series, month):
+ ts = simple_period_range_series("1990", "1992", freq=f"Y-{month}")
+ quar_ts = ts.resample(f"Q-{month}").ffill()
+
+ stamps = ts.to_timestamp("D", how="start")
+ qdates = period_range(
+ ts.index[0].asfreq("D", "start"),
+ ts.index[-1].asfreq("D", "end"),
+ freq=f"Q-{month}",
+ )
+
+ expected = stamps.reindex(qdates.to_timestamp("D", "s"), method="ffill")
+ expected.index = qdates
+
+ tm.assert_series_equal(quar_ts, expected)
+
+ @pytest.mark.parametrize("how", ["start", "end"])
+ def test_resample_to_quarterly_start_end(self, simple_period_range_series, how):
+ # conforms, but different month
+ ts = simple_period_range_series("1990", "1992", freq="Y-JUN")
+ result = ts.resample("Q-MAR", convention=how).ffill()
+ expected = ts.asfreq("Q-MAR", how=how)
+ expected = expected.reindex(result.index, method="ffill")
+
+ # FIXME: don't leave commented-out
+ # .to_timestamp('D')
+ # expected = expected.resample('Q-MAR').ffill()
+
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_fill_missing(self):
+ rng = PeriodIndex([2000, 2005, 2007, 2009], freq="Y")
+
+ s = Series(np.random.default_rng(2).standard_normal(4), index=rng)
+
+ stamps = s.to_timestamp()
+ filled = s.resample("Y").ffill()
+ expected = stamps.resample("YE").ffill().to_period("Y")
+ tm.assert_series_equal(filled, expected)
+
+ def test_cant_fill_missing_dups(self):
+ rng = PeriodIndex([2000, 2005, 2005, 2007, 2007], freq="Y")
+ s = Series(np.random.default_rng(2).standard_normal(5), index=rng)
+ msg = "Reindexing only valid with uniquely valued Index objects"
+ with pytest.raises(InvalidIndexError, match=msg):
+ s.resample("Y").ffill()
+
+ def test_resample_5minute(self):
+ rng = period_range("1/1/2000", "1/5/2000", freq="min")
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+ expected = ts.to_timestamp().resample("5min").mean()
+ result = ts.resample("5min").mean().to_timestamp()
+ tm.assert_series_equal(result, expected)
+
+ expected = expected.to_period("5min")
+ result = ts.resample("5min").mean()
+ tm.assert_series_equal(result, expected)
+ result = ts.resample("5min").mean().to_timestamp().to_period()
+ tm.assert_series_equal(result, expected)
+
+ def test_upsample_daily_business_daily(self, simple_period_range_series):
+ ts = simple_period_range_series("1/1/2000", "2/1/2000", freq="B")
+
+ result = ts.resample("D").asfreq()
+ expected = ts.asfreq("D").reindex(period_range("1/3/2000", "2/1/2000"))
+ tm.assert_series_equal(result, expected)
+
+ ts = simple_period_range_series("1/1/2000", "2/1/2000")
+ result = ts.resample("h", convention="s").asfreq()
+ exp_rng = period_range("1/1/2000", "2/1/2000 23:00", freq="h")
+ expected = ts.asfreq("h", how="s").reindex(exp_rng)
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_irregular_sparse(self):
+ dr = date_range(start="1/1/2012", freq="5min", periods=1000)
+ s = Series(np.array(100), index=dr)
+ # subset the data.
+ subset = s[:"2012-01-04 06:55"]
+
+ result = subset.resample("10min").apply(len)
+ expected = s.resample("10min").apply(len).loc[result.index]
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_weekly_all_na(self):
+ rng = date_range("1/1/2000", periods=10, freq="W-WED")
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ result = ts.resample("W-THU").asfreq()
+
+ assert result.isna().all()
+
+ result = ts.resample("W-THU").asfreq().ffill()[:-1]
+ expected = ts.asfreq("W-THU").ffill()
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_tz_localized(self, unit):
+ dr = date_range(start="2012-4-13", end="2012-5-1", unit=unit)
+ ts = Series(range(len(dr)), index=dr)
+
+ ts_utc = ts.tz_localize("UTC")
+ ts_local = ts_utc.tz_convert("America/Los_Angeles")
+
+ result = ts_local.resample("W").mean()
+
+ ts_local_naive = ts_local.copy()
+ ts_local_naive.index = ts_local_naive.index.tz_localize(None)
+
+ exp = ts_local_naive.resample("W").mean().tz_localize("America/Los_Angeles")
+ exp.index = pd.DatetimeIndex(exp.index, freq="W")
+
+ tm.assert_series_equal(result, exp)
+
+ # it works
+ result = ts_local.resample("D").mean()
+
+ def test_resample_tz_localized2(self):
+ # #2245
+ idx = date_range(
+ "2001-09-20 15:59", "2001-09-20 16:00", freq="min", tz="Australia/Sydney"
+ )
+ s = Series([1, 2], index=idx)
+
+ # GH#61985 changed this to behave like "B" rather than "24h"
+ result = s.resample("D", closed="right", label="right").mean()
+ ex_index = date_range("2001-09-20", periods=2, freq="D", tz="Australia/Sydney")
+ expected = Series([np.nan, 1.5], index=ex_index)
+
+ tm.assert_series_equal(result, expected)
+
+ # for good measure
+ msg = "Converting to PeriodArray/Index representation will drop timezone "
+ with tm.assert_produces_warning(UserWarning, match=msg):
+ result = s.resample("D").mean().to_period()
+ ex_index = period_range("2001-09-20", periods=1, freq="D")
+ expected = Series([1.5], index=ex_index)
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_tz_localized3(self):
+ # GH 6397
+ # comparing an offset that doesn't propagate tz's
+ rng = date_range("1/1/2011", periods=20000, freq="h")
+ rng = rng.tz_localize("EST")
+ ts = DataFrame(index=rng)
+ ts["first"] = np.random.default_rng(2).standard_normal(len(rng))
+ ts["second"] = np.cumsum(np.random.default_rng(2).standard_normal(len(rng)))
+ expected = DataFrame(
+ {
+ "first": ts.resample("YE").sum()["first"],
+ "second": ts.resample("YE").mean()["second"],
+ },
+ columns=["first", "second"],
+ )
+ result = (
+ ts.resample("YE")
+ .agg({"first": "sum", "second": "mean"})
+ .reindex(columns=["first", "second"])
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_closed_left_corner(self):
+ # #1465
+ s = Series(
+ np.random.default_rng(2).standard_normal(21),
+ index=date_range(start="1/1/2012 9:30", freq="1min", periods=21),
+ )
+ s.iloc[0] = np.nan
+
+ result = s.resample("10min", closed="left", label="right").mean()
+ exp = s[1:].resample("10min", closed="left", label="right").mean()
+ tm.assert_series_equal(result, exp)
+
+ result = s.resample("10min", closed="left", label="left").mean()
+ exp = s[1:].resample("10min", closed="left", label="left").mean()
+
+ ex_index = date_range(start="1/1/2012 9:30", freq="10min", periods=3)
+
+ tm.assert_index_equal(result.index, ex_index)
+ tm.assert_series_equal(result, exp)
+
+ def test_quarterly_resampling(self):
+ rng = period_range("2000Q1", periods=10, freq="Q-DEC")
+ ts = Series(np.arange(10), index=rng)
+
+ result = ts.resample("Y").mean()
+ exp = ts.to_timestamp().resample("YE").mean().to_period()
+ tm.assert_series_equal(result, exp)
+
+ def test_resample_weekly_bug_1726(self):
+ # 8/6/12 is a Monday
+ ind = date_range(start="8/6/2012", end="8/26/2012", freq="D")
+ n = len(ind)
+ data = [[x] * 5 for x in range(n)]
+ df = DataFrame(data, columns=["open", "high", "low", "close", "vol"], index=ind)
+
+ # it works!
+ df.resample("W-MON", closed="left", label="left").first()
+
+ def test_resample_with_dst_time_change(self):
+ # GH 15549
+ index = (
+ pd.DatetimeIndex([1457537600000000000, 1458059600000000000])
+ .tz_localize("UTC")
+ .tz_convert("America/Chicago")
+ )
+ df = DataFrame([1, 2], index=index)
+ result = df.resample("12h", closed="right", label="right").last().ffill()
+
+ expected_index_values = [
+ "2016-03-09 12:00:00-06:00",
+ "2016-03-10 00:00:00-06:00",
+ "2016-03-10 12:00:00-06:00",
+ "2016-03-11 00:00:00-06:00",
+ "2016-03-11 12:00:00-06:00",
+ "2016-03-12 00:00:00-06:00",
+ "2016-03-12 12:00:00-06:00",
+ "2016-03-13 00:00:00-06:00",
+ "2016-03-13 13:00:00-05:00",
+ "2016-03-14 01:00:00-05:00",
+ "2016-03-14 13:00:00-05:00",
+ "2016-03-15 01:00:00-05:00",
+ "2016-03-15 13:00:00-05:00",
+ ]
+ index = (
+ pd.to_datetime(expected_index_values, utc=True)
+ .tz_convert("America/Chicago")
+ .as_unit(index.unit)
+ )
+ index = pd.DatetimeIndex(index, freq="12h")
+ expected = DataFrame(
+ [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0],
+ index=index,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_resample_bms_2752(self):
+ # GH2753
+ timeseries = Series(
+ index=pd.bdate_range("20000101", "20000201"), dtype=np.float64
+ )
+ res1 = timeseries.resample("BMS").mean()
+ res2 = timeseries.resample("BMS").mean().resample("B").mean()
+ assert res1.index[0] == Timestamp("20000103")
+ assert res1.index[0] == res2.index[0]
+
+ @pytest.mark.xfail(reason="Commented out for more than 3 years. Should this work?")
+ def test_monthly_convention_span(self):
+ rng = period_range("2000-01", periods=3, freq="ME")
+ ts = Series(np.arange(3), index=rng)
+
+ # hacky way to get same thing
+ exp_index = period_range("2000-01-01", "2000-03-31", freq="D")
+ expected = ts.asfreq("D", how="end").reindex(exp_index)
+ expected = expected.fillna(method="bfill")
+
+ result = ts.resample("D").mean()
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "from_freq, to_freq", [("D", "ME"), ("QE", "YE"), ("ME", "QE"), ("D", "W")]
+ )
+ def test_default_right_closed_label(self, from_freq, to_freq):
+ idx = date_range(start="8/15/2012", periods=100, freq=from_freq)
+ df = DataFrame(np.random.default_rng(2).standard_normal((len(idx), 2)), idx)
+
+ resampled = df.resample(to_freq).mean()
+ tm.assert_frame_equal(
+ resampled, df.resample(to_freq, closed="right", label="right").mean()
+ )
+
+ @pytest.mark.parametrize(
+ "from_freq, to_freq",
+ [("D", "MS"), ("QE", "YS"), ("ME", "QS"), ("h", "D"), ("min", "h")],
+ )
+ def test_default_left_closed_label(self, from_freq, to_freq):
+ idx = date_range(start="8/15/2012", periods=100, freq=from_freq)
+ df = DataFrame(np.random.default_rng(2).standard_normal((len(idx), 2)), idx)
+
+ resampled = df.resample(to_freq).mean()
+ tm.assert_frame_equal(
+ resampled, df.resample(to_freq, closed="left", label="left").mean()
+ )
+
+ def test_all_values_single_bin(self):
+ # GH#2070
+ index = period_range(start="2012-01-01", end="2012-12-31", freq="M")
+ ser = Series(np.random.default_rng(2).standard_normal(len(index)), index=index)
+
+ result = ser.resample("Y").mean()
+ tm.assert_almost_equal(result.iloc[0], ser.mean())
+
+ def test_evenly_divisible_with_no_extra_bins(self):
+ # GH#4076
+ # when the frequency is evenly divisible, sometimes extra bins
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((9, 3)),
+ index=date_range("2000-1-1", periods=9, unit="ns"),
+ )
+ result = df.resample("5D").mean()
+ expected = pd.concat([df.iloc[0:5].mean(), df.iloc[5:].mean()], axis=1).T
+ expected.index = pd.DatetimeIndex(
+ [Timestamp("2000-1-1"), Timestamp("2000-1-6")], dtype="M8[ns]", freq="5D"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_evenly_divisible_with_no_extra_bins2(self):
+ index = date_range(start="2001-5-4", periods=28)
+ df = DataFrame(
+ [
+ {
+ "REST_KEY": 1,
+ "DLY_TRN_QT": 80,
+ "DLY_SLS_AMT": 90,
+ "COOP_DLY_TRN_QT": 30,
+ "COOP_DLY_SLS_AMT": 20,
+ }
+ ]
+ * 28
+ + [
+ {
+ "REST_KEY": 2,
+ "DLY_TRN_QT": 70,
+ "DLY_SLS_AMT": 10,
+ "COOP_DLY_TRN_QT": 50,
+ "COOP_DLY_SLS_AMT": 20,
+ }
+ ]
+ * 28,
+ index=index.append(index),
+ ).sort_index()
+
+ index = date_range("2001-5-4", periods=4, freq="7D")
+ expected = DataFrame(
+ [
+ {
+ "REST_KEY": 14,
+ "DLY_TRN_QT": 14,
+ "DLY_SLS_AMT": 14,
+ "COOP_DLY_TRN_QT": 14,
+ "COOP_DLY_SLS_AMT": 14,
+ }
+ ]
+ * 4,
+ index=index,
+ )
+ result = df.resample("7D").count()
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(
+ [
+ {
+ "REST_KEY": 21,
+ "DLY_TRN_QT": 1050,
+ "DLY_SLS_AMT": 700,
+ "COOP_DLY_TRN_QT": 560,
+ "COOP_DLY_SLS_AMT": 280,
+ }
+ ]
+ * 4,
+ index=index,
+ )
+ result = df.resample("7D").sum()
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("freq, period_mult", [("h", 24), ("12h", 2)])
+ def test_upsampling_ohlc(self, freq, period_mult):
+ # GH 13083
+ pi = period_range(start="2000", freq="D", periods=10)
+ s = Series(range(len(pi)), index=pi)
+ expected = s.to_timestamp().resample(freq).ohlc().to_period(freq)
+
+ # timestamp-based resampling doesn't include all sub-periods
+ # of the last original period, so extend accordingly:
+ new_index = period_range(start="2000", freq=freq, periods=period_mult * len(pi))
+ expected = expected.reindex(new_index)
+ result = s.resample(freq).ohlc()
+ tm.assert_frame_equal(result, expected)
+
+ result = s.resample(freq).ohlc().to_timestamp().to_period()
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "periods, values",
+ [
+ (
+ [
+ pd.NaT,
+ "1970-01-01 00:00:00",
+ pd.NaT,
+ "1970-01-01 00:00:02",
+ "1970-01-01 00:00:03",
+ ],
+ [2, 3, 5, 7, 11],
+ ),
+ (
+ [
+ pd.NaT,
+ pd.NaT,
+ "1970-01-01 00:00:00",
+ pd.NaT,
+ pd.NaT,
+ pd.NaT,
+ "1970-01-01 00:00:02",
+ "1970-01-01 00:00:03",
+ pd.NaT,
+ pd.NaT,
+ ],
+ [1, 2, 3, 5, 6, 8, 7, 11, 12, 13],
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "freq, expected_values",
+ [
+ ("1s", [3, np.nan, 7, 11]),
+ ("2s", [3, (7 + 11) / 2]),
+ ("3s", [(3 + 7) / 2, 11]),
+ ],
+ )
+ def test_resample_with_nat(self, periods, values, freq, expected_values):
+ # GH 13224
+ index = PeriodIndex(periods, freq="s")
+ frame = DataFrame(values, index=index)
+
+ expected_index = period_range(
+ "1970-01-01 00:00:00", periods=len(expected_values), freq=freq
+ )
+ expected = DataFrame(expected_values, index=expected_index)
+ result = frame.resample(freq).mean()
+ tm.assert_frame_equal(result, expected)
+
+ def test_resample_with_only_nat(self):
+ # GH 13224
+ pi = PeriodIndex([pd.NaT] * 3, freq="s")
+ frame = DataFrame([2, 3, 5], index=pi, columns=["a"])
+ expected_index = PeriodIndex(data=[], freq=pi.freq)
+ expected = DataFrame(index=expected_index, columns=["a"], dtype="float64")
+ result = frame.resample("1s").mean()
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "start,end,start_freq,end_freq,offset",
+ [
+ ("19910905", "19910909 03:00", "h", "24h", "10h"),
+ ("19910905", "19910909 12:00", "h", "24h", "10h"),
+ ("19910905", "19910909 23:00", "h", "24h", "10h"),
+ ("19910905 10:00", "19910909", "h", "24h", "10h"),
+ ("19910905 10:00", "19910909 10:00", "h", "24h", "10h"),
+ ("19910905", "19910909 10:00", "h", "24h", "10h"),
+ ("19910905 12:00", "19910909", "h", "24h", "10h"),
+ ("19910905 12:00", "19910909 03:00", "h", "24h", "10h"),
+ ("19910905 12:00", "19910909 12:00", "h", "24h", "10h"),
+ ("19910905 12:00", "19910909 12:00", "h", "24h", "34h"),
+ ("19910905 12:00", "19910909 12:00", "h", "17h", "10h"),
+ ("19910905 12:00", "19910909 12:00", "h", "17h", "3h"),
+ ("19910905", "19910913 06:00", "2h", "24h", "10h"),
+ ("19910905", "19910905 01:39", "Min", "5Min", "3Min"),
+ ("19910905", "19910905 03:18", "2Min", "5Min", "3Min"),
+ ],
+ )
+ def test_resample_with_offset(self, start, end, start_freq, end_freq, offset):
+ # GH 23882 & 31809
+ pi = period_range(start, end, freq=start_freq)
+ ser = Series(np.arange(len(pi)), index=pi)
+ result = ser.resample(end_freq, offset=offset).mean()
+ result = result.to_timestamp(end_freq)
+
+ expected = ser.to_timestamp().resample(end_freq, offset=offset).mean()
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_with_offset_month(self):
+ # GH 23882 & 31809
+ pi = period_range("19910905 12:00", "19910909 1:00", freq="h")
+ ser = Series(np.arange(len(pi)), index=pi)
+ result = ser.resample("M").mean()
+ result = result.to_timestamp("M")
+ expected = ser.to_timestamp().resample("ME").mean()
+ # TODO: is non-tick the relevant characteristic? (GH 33815)
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "first,last,freq,freq_to_offset,exp_first,exp_last",
+ [
+ ("19910905", "19920406", "D", "D", "19910905", "19920406"),
+ ("19910905 00:00", "19920406 06:00", "D", "D", "19910905", "19920406"),
+ (
+ "19910905 06:00",
+ "19920406 06:00",
+ "h",
+ "h",
+ "19910905 06:00",
+ "19920406 06:00",
+ ),
+ ("19910906", "19920406", "M", "ME", "1991-09", "1992-04"),
+ ("19910831", "19920430", "M", "ME", "1991-08", "1992-04"),
+ ("1991-08", "1992-04", "M", "ME", "1991-08", "1992-04"),
+ ],
+ )
+ def test_get_period_range_edges(
+ self, first, last, freq, freq_to_offset, exp_first, exp_last
+ ):
+ first = Period(first)
+ last = Period(last)
+
+ exp_first = Period(exp_first, freq=freq)
+ exp_last = Period(exp_last, freq=freq)
+
+ freq = pd.tseries.frequencies.to_offset(freq_to_offset)
+ result = _get_period_range_edges(first, last, freq)
+ expected = (exp_first, exp_last)
+ assert result == expected
+
+ def test_sum_min_count(self):
+ # GH 19974
+ index = date_range(start="2018", freq="ME", periods=6)
+ data = np.ones(6)
+ data[3:6] = np.nan
+ s = Series(data, index).to_period()
+ result = s.resample("Q").sum(min_count=1)
+ expected = Series(
+ [3.0, np.nan], index=PeriodIndex(["2018Q1", "2018Q2"], freq="Q-DEC")
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_resample_t_l_deprecated(self):
+ # GH#52536
+ msg_t = "Invalid frequency: T"
+ msg_l = "Invalid frequency: L"
+
+ with pytest.raises(ValueError, match=msg_l):
+ period_range(
+ "2020-01-01 00:00:00 00:00", "2020-01-01 00:00:00 00:01", freq="L"
+ )
+ rng_l = period_range(
+ "2020-01-01 00:00:00 00:00", "2020-01-01 00:00:00 00:01", freq="ms"
+ )
+ ser = Series(np.arange(len(rng_l)), index=rng_l)
+
+ with pytest.raises(ValueError, match=msg_t):
+ ser.resample("T").mean()
+
+ @pytest.mark.parametrize(
+ "freq, freq_depr, freq_depr_res",
+ [
+ ("2Q", "2q", "2y"),
+ ("2M", "2m", "2q"),
+ ],
+ )
+ def test_resample_lowercase_frequency_raises(self, freq, freq_depr, freq_depr_res):
+ msg = f"Invalid frequency: {freq_depr}"
+ with pytest.raises(ValueError, match=msg):
+ period_range("2020-01-01", "2020-08-01", freq=freq_depr)
+
+ msg = f"Invalid frequency: {freq_depr_res}"
+ rng = period_range("2020-01-01", "2020-08-01", freq=freq)
+ ser = Series(np.arange(len(rng)), index=rng)
+ with pytest.raises(ValueError, match=msg):
+ ser.resample(freq_depr_res).mean()
+
+ @pytest.mark.parametrize(
+ "offset",
+ [
+ offsets.MonthBegin(),
+ offsets.BYearBegin(2),
+ offsets.BusinessHour(2),
+ ],
+ )
+ def test_asfreq_invalid_period_offset(self, offset, frame_or_series):
+ # GH#55785
+ msg = re.escape(f"{offset} is not supported as period frequency")
+
+ obj = frame_or_series(range(5), index=period_range("2020-01-01", periods=5))
+ with pytest.raises(ValueError, match=msg):
+ obj.asfreq(freq=offset)
+
+
+@pytest.mark.parametrize(
+ "freq",
+ [
+ ("2ME"),
+ ("2QE"),
+ ("2QE-FEB"),
+ ("2YE"),
+ ("2YE-MAR"),
+ ("2me"),
+ ("2qe"),
+ ("2ye-mar"),
+ ],
+)
+def test_resample_frequency_ME_QE_YE_raises(frame_or_series, freq):
+ # GH#9586
+ msg = f"{freq[1:]} is not supported as period frequency"
+
+ obj = frame_or_series(range(5), index=period_range("2020-01-01", periods=5))
+ msg = f"Invalid frequency: {freq}"
+ with pytest.raises(ValueError, match=msg):
+ obj.resample(freq)
+
+
+def test_corner_cases_period(simple_period_range_series):
+ # miscellaneous test coverage
+ len0pts = simple_period_range_series("2007-01", "2010-05", freq="M")[:0]
+ # it works
+ result = len0pts.resample("Y-DEC").mean()
+ assert len(result) == 0
+
+
+@pytest.mark.parametrize("freq", ["2BME", "2CBME", "2SME", "2BQE-FEB", "2BYE-MAR"])
+def test_resample_frequency_invalid_freq(frame_or_series, freq):
+ # GH#9586
+ msg = f"Invalid frequency: {freq}"
+
+ obj = frame_or_series(range(5), index=period_range("2020-01-01", periods=5))
+ with pytest.raises(ValueError, match=msg):
+ obj.resample(freq)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_resample_api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_resample_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..36ef01178b3bc905bc4099fe6cc3e271f3872b5f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_resample_api.py
@@ -0,0 +1,1018 @@
+from datetime import datetime
+import re
+
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+from pandas._libs.tslibs import Day
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ NamedAgg,
+ Series,
+)
+import pandas._testing as tm
+from pandas.core.indexes.datetimes import date_range
+
+
+@pytest.fixture
+def dti():
+ return date_range(start=datetime(2005, 1, 1), end=datetime(2005, 1, 10), freq="Min")
+
+
+@pytest.fixture
+def _test_series(dti):
+ return Series(np.random.default_rng(2).random(len(dti)), dti)
+
+
+@pytest.fixture
+def test_frame(dti, _test_series):
+ return DataFrame({"A": _test_series, "B": _test_series, "C": np.arange(len(dti))})
+
+
+def test_str(_test_series):
+ r = _test_series.resample("h")
+ assert (
+ "DatetimeIndexResampler [freq=, closed=left, "
+ "label=left, convention=start, origin=start_day]" in str(r)
+ )
+
+ r = _test_series.resample("h", origin="2000-01-01")
+ assert (
+ "DatetimeIndexResampler [freq=, closed=left, "
+ "label=left, convention=start, origin=2000-01-01 00:00:00]" in str(r)
+ )
+
+
+def test_api(_test_series):
+ r = _test_series.resample("h")
+ result = r.mean()
+ assert isinstance(result, Series)
+ assert len(result) == 217
+
+ r = _test_series.to_frame().resample("h")
+ result = r.mean()
+ assert isinstance(result, DataFrame)
+ assert len(result) == 217
+
+
+def test_groupby_resample_api():
+ # GH 12448
+ # .groupby(...).resample(...) hitting warnings
+ # when appropriate
+ df = DataFrame(
+ {
+ "date": date_range(start="2016-01-01", periods=4, freq="W"),
+ "group": [1, 1, 2, 2],
+ "val": [5, 6, 7, 8],
+ }
+ ).set_index("date")
+
+ # replication step
+ i = (
+ date_range("2016-01-03", periods=8).tolist()
+ + date_range("2016-01-17", periods=8).tolist()
+ )
+ index = pd.MultiIndex.from_arrays([[1] * 8 + [2] * 8, i], names=["group", "date"])
+ expected = DataFrame({"val": [5] * 7 + [6] + [7] * 7 + [8]}, index=index)
+ result = df.groupby("group").apply(lambda x: x.resample("1D").ffill())[["val"]]
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_resample_on_api():
+ # GH 15021
+ # .groupby(...).resample(on=...) results in an unexpected
+ # keyword warning.
+ df = DataFrame(
+ {
+ "key": ["A", "B"] * 5,
+ "dates": date_range("2016-01-01", periods=10),
+ "values": np.random.default_rng(2).standard_normal(10),
+ }
+ )
+
+ expected = df.set_index("dates").groupby("key").resample("D").mean()
+ result = df.groupby("key").resample("D", on="dates").mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_group_keys():
+ df = DataFrame({"A": 1, "B": 2}, index=date_range("2000", periods=10, unit="ns"))
+ expected = df.copy()
+
+ # group_keys=False
+ g = df.resample("5D", group_keys=False)
+ result = g.apply(lambda x: x)
+ tm.assert_frame_equal(result, expected)
+
+ # group_keys defaults to False
+ g = df.resample("5D")
+ result = g.apply(lambda x: x)
+ tm.assert_frame_equal(result, expected)
+
+ # group_keys=True
+ expected.index = pd.MultiIndex.from_arrays(
+ [
+ pd.to_datetime(["2000-01-01", "2000-01-06"]).as_unit("ns").repeat(5),
+ expected.index,
+ ]
+ )
+ g = df.resample("5D", group_keys=True)
+ result = g.apply(lambda x: x)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_pipe(test_frame, _test_series):
+ # GH17905
+
+ # series
+ r = _test_series.resample("h")
+ expected = r.max() - r.mean()
+ result = r.pipe(lambda x: x.max() - x.mean())
+ tm.assert_series_equal(result, expected)
+
+ # dataframe
+ r = test_frame.resample("h")
+ expected = r.max() - r.mean()
+ result = r.pipe(lambda x: x.max() - x.mean())
+ tm.assert_frame_equal(result, expected)
+
+
+def test_getitem(test_frame):
+ r = test_frame.resample("h")
+ tm.assert_index_equal(r._selected_obj.columns, test_frame.columns)
+
+ r = test_frame.resample("h")["B"]
+ assert r._selected_obj.name == test_frame.columns[1]
+
+ # technically this is allowed
+ r = test_frame.resample("h")["A", "B"]
+ tm.assert_index_equal(r._selected_obj.columns, test_frame.columns[[0, 1]])
+
+ r = test_frame.resample("h")["A", "B"]
+ tm.assert_index_equal(r._selected_obj.columns, test_frame.columns[[0, 1]])
+
+
+@pytest.mark.parametrize("key", [["D"], ["A", "D"]])
+def test_select_bad_cols(key, test_frame):
+ g = test_frame.resample("h")
+ # 'A' should not be referenced as a bad column...
+ # will have to rethink regex if you change message!
+ msg = r"^\"Columns not found: 'D'\"$"
+ with pytest.raises(KeyError, match=msg):
+ g[key]
+
+
+def test_attribute_access(test_frame):
+ r = test_frame.resample("h")
+ tm.assert_series_equal(r.A.sum(), r["A"].sum())
+
+
+@pytest.mark.parametrize("attr", ["groups", "ngroups", "indices"])
+def test_api_compat_before_use(attr):
+ # make sure that we are setting the binner
+ # on these attributes
+ rng = date_range("1/1/2012", periods=100, freq="s")
+ ts = Series(np.arange(len(rng)), index=rng)
+ rs = ts.resample("30s")
+
+ # before use
+ getattr(rs, attr)
+
+ # after grouper is initialized is ok
+ rs.mean()
+ getattr(rs, attr)
+
+
+def tests_raises_on_nuisance(test_frame, using_infer_string):
+ df = test_frame
+ df["D"] = "foo"
+ r = df.resample("h")
+ result = r[["A", "B"]].mean()
+ expected = pd.concat([r.A.mean(), r.B.mean()], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ expected = r[["A", "B", "C"]].mean()
+ msg = re.escape("agg function failed [how->mean,dtype->")
+ if using_infer_string:
+ msg = "dtype 'str' does not support operation 'mean'"
+ with pytest.raises(TypeError, match=msg):
+ r.mean()
+ result = r.mean(numeric_only=True)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_downsample_but_actually_upsampling():
+ # this is reindex / asfreq
+ rng = date_range("1/1/2012", periods=100, freq="s")
+ ts = Series(np.arange(len(rng), dtype="int64"), index=rng)
+ result = ts.resample("20s").asfreq()
+ expected = Series(
+ [0, 20, 40, 60, 80],
+ index=date_range("2012-01-01 00:00:00", freq="20s", periods=5),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_combined_up_downsampling_of_irregular():
+ # since we are really doing an operation like this
+ # ts2.resample('2s').mean().ffill()
+ # preserve these semantics
+
+ rng = date_range("1/1/2012", periods=100, freq="s", unit="ns")
+ ts = Series(np.arange(len(rng)), index=rng)
+ ts2 = ts.iloc[[0, 1, 2, 3, 5, 7, 11, 15, 16, 25, 30]]
+
+ result = ts2.resample("2s").mean().ffill()
+ expected = Series(
+ [
+ 0.5,
+ 2.5,
+ 5.0,
+ 7.0,
+ 7.0,
+ 11.0,
+ 11.0,
+ 15.0,
+ 16.0,
+ 16.0,
+ 16.0,
+ 16.0,
+ 25.0,
+ 25.0,
+ 25.0,
+ 30.0,
+ ],
+ index=pd.DatetimeIndex(
+ [
+ "2012-01-01 00:00:00",
+ "2012-01-01 00:00:02",
+ "2012-01-01 00:00:04",
+ "2012-01-01 00:00:06",
+ "2012-01-01 00:00:08",
+ "2012-01-01 00:00:10",
+ "2012-01-01 00:00:12",
+ "2012-01-01 00:00:14",
+ "2012-01-01 00:00:16",
+ "2012-01-01 00:00:18",
+ "2012-01-01 00:00:20",
+ "2012-01-01 00:00:22",
+ "2012-01-01 00:00:24",
+ "2012-01-01 00:00:26",
+ "2012-01-01 00:00:28",
+ "2012-01-01 00:00:30",
+ ],
+ dtype="datetime64[ns]",
+ freq="2s",
+ ),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_transform_series(_test_series):
+ r = _test_series.resample("20min")
+ expected = _test_series.groupby(pd.Grouper(freq="20min")).transform("mean")
+ result = r.transform("mean")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("on", [None, "date"])
+def test_transform_frame(on):
+ # GH#47079
+ index = date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D")
+ index.name = "date"
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 2)), columns=list("AB"), index=index
+ )
+ expected = df.groupby(pd.Grouper(freq="20min")).transform("mean")
+ if on == "date":
+ # Move date to being a column; result will then have a RangeIndex
+ expected = expected.reset_index(drop=True)
+ df = df.reset_index()
+
+ r = df.resample("20min", on=on)
+ result = r.transform("mean")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ lambda x: x.resample("20min", group_keys=False),
+ lambda x: x.groupby(pd.Grouper(freq="20min"), group_keys=False),
+ ],
+ ids=["resample", "groupby"],
+)
+def test_apply_without_aggregation(func, _test_series):
+ # both resample and groupby should work w/o aggregation
+ t = func(_test_series)
+ result = t.apply(lambda x: x)
+ tm.assert_series_equal(result, _test_series)
+
+
+def test_apply_without_aggregation2(_test_series):
+ grouped = _test_series.to_frame(name="foo").resample("20min", group_keys=False)
+ result = grouped["foo"].apply(lambda x: x)
+ tm.assert_series_equal(result, _test_series.rename("foo"))
+
+
+def test_agg_consistency():
+ # make sure that we are consistent across
+ # similar aggregations with and w/o selection list
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((1000, 3)),
+ index=date_range("1/1/2012", freq="s", periods=1000),
+ columns=["A", "B", "C"],
+ )
+
+ r = df.resample("3min")
+
+ msg = r"Label\(s\) \['r1', 'r2'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ r.agg({"r1": "mean", "r2": "sum"})
+
+
+def test_agg_consistency_int_str_column_mix():
+ # GH#39025
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((1000, 2)),
+ index=date_range("1/1/2012", freq="s", periods=1000),
+ columns=[1, "a"],
+ )
+
+ r = df.resample("3min")
+
+ msg = r"Label\(s\) \[2, 'b'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ r.agg({2: "mean", "b": "sum"})
+
+
+# TODO(GH#14008): once GH 14008 is fixed, move these tests into
+# `Base` test class
+
+
+@pytest.fixture
+def index():
+ index = date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D", unit="ns")
+ index.name = "date"
+ return index
+
+
+@pytest.fixture
+def df(index):
+ frame = DataFrame(
+ np.random.default_rng(2).random((10, 2)), columns=list("AB"), index=index
+ )
+ return frame
+
+
+@pytest.fixture
+def df_col(df):
+ return df.reset_index()
+
+
+@pytest.fixture
+def df_mult(df_col, index):
+ df_mult = df_col.copy()
+ df_mult.index = pd.MultiIndex.from_arrays(
+ [range(10), index], names=["index", "date"]
+ )
+ return df_mult
+
+
+@pytest.fixture
+def a_mean(df):
+ return df.resample("2D")["A"].mean()
+
+
+@pytest.fixture
+def a_std(df):
+ return df.resample("2D")["A"].std()
+
+
+@pytest.fixture
+def a_sum(df):
+ return df.resample("2D")["A"].sum()
+
+
+@pytest.fixture
+def b_mean(df):
+ return df.resample("2D")["B"].mean()
+
+
+@pytest.fixture
+def b_std(df):
+ return df.resample("2D")["B"].std()
+
+
+@pytest.fixture
+def b_sum(df):
+ return df.resample("2D")["B"].sum()
+
+
+@pytest.fixture
+def df_resample(df):
+ return df.resample("2D")
+
+
+@pytest.fixture
+def df_col_resample(df_col):
+ return df_col.resample("2D", on="date")
+
+
+@pytest.fixture
+def df_mult_resample(df_mult):
+ return df_mult.resample("2D", level="date")
+
+
+@pytest.fixture
+def df_grouper_resample(df):
+ return df.groupby(pd.Grouper(freq="2D"))
+
+
+@pytest.fixture(
+ params=["df_resample", "df_col_resample", "df_mult_resample", "df_grouper_resample"]
+)
+def cases(request):
+ return request.getfixturevalue(request.param)
+
+
+def test_agg_mixed_column_aggregation(cases, a_mean, a_std, b_mean, b_std, request):
+ expected = pd.concat([a_mean, a_std, b_mean, b_std], axis=1)
+ expected.columns = pd.MultiIndex.from_product([["A", "B"], ["mean", ""]])
+ # "date" is an index and a column, so get included in the agg
+ if "df_mult" in request.node.callspec.id:
+ date_mean = cases["date"].mean()
+ date_std = cases["date"].std()
+ expected = pd.concat([date_mean, date_std, expected], axis=1)
+ expected.columns = pd.MultiIndex.from_product(
+ [["date", "A", "B"], ["mean", ""]]
+ )
+ result = cases.aggregate([np.mean, lambda x: np.std(x, ddof=1)])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "agg",
+ [
+ {"func": {"A": np.mean, "B": lambda x: np.std(x, ddof=1)}},
+ {"A": ("A", np.mean), "B": ("B", lambda x: np.std(x, ddof=1))},
+ {"A": NamedAgg("A", np.mean), "B": NamedAgg("B", lambda x: np.std(x, ddof=1))},
+ ],
+)
+def test_agg_both_mean_std_named_result(cases, a_mean, b_std, agg):
+ expected = pd.concat([a_mean, b_std], axis=1)
+ result = cases.aggregate(**agg)
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+
+def test_agg_both_mean_std_dict_of_list(cases, a_mean, a_std):
+ expected = pd.concat([a_mean, a_std], axis=1)
+ expected.columns = pd.MultiIndex.from_tuples([("A", "mean"), ("A", "std")])
+ result = cases.aggregate({"A": ["mean", "std"]})
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "agg", [{"func": ["mean", "sum"]}, {"mean": "mean", "sum": "sum"}]
+)
+def test_agg_both_mean_sum(cases, a_mean, a_sum, agg):
+ expected = pd.concat([a_mean, a_sum], axis=1)
+ expected.columns = ["mean", "sum"]
+ result = cases["A"].aggregate(**agg)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "agg",
+ [
+ {"A": {"mean": "mean", "sum": "sum"}},
+ {
+ "A": {"mean": "mean", "sum": "sum"},
+ "B": {"mean2": "mean", "sum2": "sum"},
+ },
+ ],
+)
+def test_agg_dict_of_dict_specificationerror(cases, agg):
+ msg = "nested renamer is not supported"
+ with pytest.raises(pd.errors.SpecificationError, match=msg):
+ cases.aggregate(agg)
+
+
+def test_agg_dict_of_lists(cases, a_mean, a_std, b_mean, b_std):
+ expected = pd.concat([a_mean, a_std, b_mean, b_std], axis=1)
+ expected.columns = pd.MultiIndex.from_tuples(
+ [("A", "mean"), ("A", "std"), ("B", "mean"), ("B", "std")]
+ )
+ result = cases.aggregate({"A": ["mean", "std"], "B": ["mean", "std"]})
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+
+@pytest.mark.parametrize(
+ "agg",
+ [
+ {"func": {"A": np.sum, "B": lambda x: np.std(x, ddof=1)}},
+ {"A": ("A", np.sum), "B": ("B", lambda x: np.std(x, ddof=1))},
+ {"A": NamedAgg("A", np.sum), "B": NamedAgg("B", lambda x: np.std(x, ddof=1))},
+ ],
+)
+def test_agg_with_lambda(cases, agg):
+ # passed lambda
+ rcustom = cases["B"].apply(lambda x: np.std(x, ddof=1))
+ expected = pd.concat([cases["A"].sum(), rcustom], axis=1)
+ result = cases.agg(**agg)
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+
+@pytest.mark.parametrize(
+ "agg",
+ [
+ {"func": {"result1": np.sum, "result2": np.mean}},
+ {"A": ("result1", np.sum), "B": ("result2", np.mean)},
+ {"A": NamedAgg("result1", np.sum), "B": NamedAgg("result2", np.mean)},
+ ],
+)
+def test_agg_no_column(cases, agg):
+ msg = r"Label\(s\) \['result1', 'result2'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ cases[["A", "B"]].agg(**agg)
+
+
+@pytest.mark.parametrize(
+ "cols, agg",
+ [
+ [None, {"A": ["sum", "std"], "B": ["mean", "std"]}],
+ [
+ [
+ "A",
+ "B",
+ ],
+ {"A": ["sum", "std"], "B": ["mean", "std"]},
+ ],
+ ],
+)
+def test_agg_specificationerror_nested(cases, cols, agg, a_sum, a_std, b_mean, b_std):
+ # agg with different hows
+ # equivalent of using a selection list / or not
+ expected = pd.concat([a_sum, a_std, b_mean, b_std], axis=1)
+ expected.columns = pd.MultiIndex.from_tuples(
+ [("A", "sum"), ("A", "std"), ("B", "mean"), ("B", "std")]
+ )
+ if cols is not None:
+ obj = cases[cols]
+ else:
+ obj = cases
+
+ result = obj.agg(agg)
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+
+@pytest.mark.parametrize(
+ "agg", [{"A": ["sum", "std"]}, {"A": ["sum", "std"], "B": ["mean", "std"]}]
+)
+def test_agg_specificationerror_series(cases, agg):
+ msg = "nested renamer is not supported"
+
+ # series like aggs
+ with pytest.raises(pd.errors.SpecificationError, match=msg):
+ cases["A"].agg(agg)
+
+
+def test_agg_specificationerror_invalid_names(cases):
+ # errors
+ # invalid names in the agg specification
+ msg = r"Label\(s\) \['B'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ cases[["A"]].agg({"A": ["sum", "std"], "B": ["mean", "std"]})
+
+
+def test_agg_nested_dicts():
+ index = date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D")
+ index.name = "date"
+ df = DataFrame(
+ np.random.default_rng(2).random((10, 2)), columns=list("AB"), index=index
+ )
+ df_col = df.reset_index()
+ df_mult = df_col.copy()
+ df_mult.index = pd.MultiIndex.from_arrays(
+ [range(10), df.index], names=["index", "date"]
+ )
+ r = df.resample("2D")
+ cases = [
+ r,
+ df_col.resample("2D", on="date"),
+ df_mult.resample("2D", level="date"),
+ df.groupby(pd.Grouper(freq="2D")),
+ ]
+
+ msg = "nested renamer is not supported"
+ for t in cases:
+ with pytest.raises(pd.errors.SpecificationError, match=msg):
+ t.aggregate({"r1": {"A": ["mean", "sum"]}, "r2": {"B": ["mean", "sum"]}})
+
+ for t in cases:
+ with pytest.raises(pd.errors.SpecificationError, match=msg):
+ t[["A", "B"]].agg(
+ {"A": {"ra": ["mean", "std"]}, "B": {"rb": ["mean", "std"]}}
+ )
+
+ with pytest.raises(pd.errors.SpecificationError, match=msg):
+ t.agg({"A": {"ra": ["mean", "std"]}, "B": {"rb": ["mean", "std"]}})
+
+
+def test_try_aggregate_non_existing_column():
+ # GH 16766
+ data = [
+ {"dt": datetime(2017, 6, 1, 0), "x": 1.0, "y": 2.0},
+ {"dt": datetime(2017, 6, 1, 1), "x": 2.0, "y": 2.0},
+ {"dt": datetime(2017, 6, 1, 2), "x": 3.0, "y": 1.5},
+ ]
+ df = DataFrame(data).set_index("dt")
+
+ # Error as we don't have 'z' column
+ msg = r"Label\(s\) \['z'\] do not exist"
+ with pytest.raises(KeyError, match=msg):
+ df.resample("30min").agg({"x": ["mean"], "y": ["median"], "z": ["sum"]})
+
+
+def test_agg_list_like_func_with_args():
+ # 50624
+ df = DataFrame(
+ {"x": [1, 2, 3]}, index=date_range("2020-01-01", periods=3, freq="D")
+ )
+
+ def foo1(x, a=1, c=0):
+ return x + a + c
+
+ def foo2(x, b=2, c=0):
+ return x + b + c
+
+ msg = r"foo1\(\) got an unexpected keyword argument 'b'"
+ with pytest.raises(TypeError, match=msg):
+ df.resample("D").agg([foo1, foo2], 3, b=3, c=4)
+
+ result = df.resample("D").agg([foo1, foo2], 3, c=4)
+ expected = DataFrame(
+ [[8, 8], [9, 9], [10, 10]],
+ index=date_range("2020-01-01", periods=3, freq="D"),
+ columns=pd.MultiIndex.from_tuples([("x", "foo1"), ("x", "foo2")]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_selection_api_validation():
+ # GH 13500
+ index = date_range(datetime(2005, 1, 1), datetime(2005, 1, 10), freq="D")
+
+ rng = np.arange(len(index), dtype=np.int64)
+ df = DataFrame(
+ {"date": index, "a": rng},
+ index=pd.MultiIndex.from_arrays([rng, index], names=["v", "d"]),
+ )
+ df_exp = DataFrame({"a": rng}, index=index)
+
+ # non DatetimeIndex
+ msg = (
+ "Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, "
+ "but got an instance of 'Index'"
+ )
+ with pytest.raises(TypeError, match=msg):
+ df.resample("2D", level="v")
+
+ msg = "The Grouper cannot specify both a key and a level!"
+ with pytest.raises(ValueError, match=msg):
+ df.resample("2D", on="date", level="d")
+
+ msg = "unhashable type: 'list'"
+ with pytest.raises(TypeError, match=msg):
+ df.resample("2D", on=["a", "date"])
+
+ msg = r"\"Level \['a', 'date'\] not found\""
+ with pytest.raises(KeyError, match=msg):
+ df.resample("2D", level=["a", "date"])
+
+ # upsampling not allowed
+ msg = (
+ "Upsampling from level= or on= selection is not supported, use "
+ r"\.set_index\(\.\.\.\) to explicitly set index to datetime-like"
+ )
+ with pytest.raises(ValueError, match=msg):
+ df.resample("2D", level="d").asfreq()
+ with pytest.raises(ValueError, match=msg):
+ df.resample("2D", on="date").asfreq()
+
+ exp = df_exp.resample("2D").sum()
+ exp.index.name = "date"
+ result = df.resample("2D", on="date").sum()
+ tm.assert_frame_equal(exp, result)
+
+ exp.index.name = "d"
+ with pytest.raises(
+ TypeError, match="datetime64 type does not support operation 'sum'"
+ ):
+ df.resample("2D", level="d").sum()
+ result = df.resample("2D", level="d").sum(numeric_only=True)
+ tm.assert_frame_equal(exp, result)
+
+
+@pytest.mark.parametrize(
+ "col_name", ["t2", "t2x", "t2q", "T_2M", "t2p", "t2m", "t2m1", "T2M"]
+)
+def test_agg_with_datetime_index_list_agg_func(col_name):
+ # GH 22660
+ # The parametrized column names would get converted to dates by our
+ # date parser. Some would result in OutOfBoundsError (ValueError) while
+ # others would result in OverflowError when passed into Timestamp.
+ # We catch these errors and move on to the correct branch.
+ df = DataFrame(
+ list(range(200)),
+ index=date_range(
+ start="2017-01-01", freq="15min", periods=200, tz="Europe/Berlin"
+ ),
+ columns=[col_name],
+ )
+ result = df.resample("1D").aggregate(["mean"])
+ expected = DataFrame(
+ [47.5, 143.5, 195.5],
+ index=date_range(start="2017-01-01", freq="D", periods=3, tz="Europe/Berlin"),
+ columns=pd.MultiIndex(levels=[[col_name], ["mean"]], codes=[[0], [0]]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_agg_readonly():
+ # GH#31710 cython needs to allow readonly data
+ index = date_range("2020-01-01", "2020-01-02", freq="1h", unit="ns")
+ arr = np.zeros_like(index)
+ arr.setflags(write=False)
+
+ ser = Series(arr, index=index)
+ rs = ser.resample("1D")
+
+ expected = Series([pd.Timestamp(0), pd.Timestamp(0)], index=index[::24])
+ expected.index.freq = Day(1) # GH#41943 no longer equivalent to 24h
+
+ result = rs.agg("last")
+ tm.assert_series_equal(result, expected)
+
+ result = rs.agg("first")
+ tm.assert_series_equal(result, expected)
+
+ result = rs.agg("max")
+ tm.assert_series_equal(result, expected)
+
+ result = rs.agg("min")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "start,end,freq,data,resample_freq,origin,closed,exp_data,exp_end,exp_periods",
+ [
+ (
+ "2000-10-01 23:30:00",
+ "2000-10-02 00:26:00",
+ "7min",
+ [0, 3, 6, 9, 12, 15, 18, 21, 24],
+ "17min",
+ "end",
+ None,
+ [0, 18, 27, 63],
+ "20001002 00:26:00",
+ 4,
+ ),
+ (
+ "20200101 8:26:35",
+ "20200101 9:31:58",
+ "77s",
+ [1] * 51,
+ "7min",
+ "end",
+ "right",
+ [1, 6, 5, 6, 5, 6, 5, 6, 5, 6],
+ "2020-01-01 09:30:45",
+ 10,
+ ),
+ (
+ "2000-10-01 23:30:00",
+ "2000-10-02 00:26:00",
+ "7min",
+ [0, 3, 6, 9, 12, 15, 18, 21, 24],
+ "17min",
+ "end",
+ "left",
+ [0, 18, 27, 39, 24],
+ "20001002 00:43:00",
+ 5,
+ ),
+ (
+ "2000-10-01 23:30:00",
+ "2000-10-02 00:26:00",
+ "7min",
+ [0, 3, 6, 9, 12, 15, 18, 21, 24],
+ "17min",
+ "end_day",
+ None,
+ [3, 15, 45, 45],
+ "2000-10-02 00:29:00",
+ 4,
+ ),
+ ],
+)
+def test_end_and_end_day_origin(
+ start,
+ end,
+ freq,
+ data,
+ resample_freq,
+ origin,
+ closed,
+ exp_data,
+ exp_end,
+ exp_periods,
+):
+ rng = date_range(start, end, freq=freq)
+ ts = Series(data, index=rng)
+
+ res = ts.resample(resample_freq, origin=origin, closed=closed).sum()
+ expected = Series(
+ exp_data,
+ index=date_range(end=exp_end, freq=resample_freq, periods=exp_periods),
+ )
+
+ tm.assert_series_equal(res, expected)
+
+
+@pytest.mark.parametrize(
+ # expected_data is a string when op raises a ValueError
+ "method, numeric_only, expected_data",
+ [
+ ("sum", True, {"num": [25]}),
+ ("sum", False, {"cat": ["cat_1cat_2"], "num": [25]}),
+ ("sum", lib.no_default, {"cat": ["cat_1cat_2"], "num": [25]}),
+ ("prod", True, {"num": [100]}),
+ ("prod", False, "can't multiply sequence"),
+ ("prod", lib.no_default, "can't multiply sequence"),
+ ("min", True, {"num": [5]}),
+ ("min", False, {"cat": ["cat_1"], "num": [5]}),
+ ("min", lib.no_default, {"cat": ["cat_1"], "num": [5]}),
+ ("max", True, {"num": [20]}),
+ ("max", False, {"cat": ["cat_2"], "num": [20]}),
+ ("max", lib.no_default, {"cat": ["cat_2"], "num": [20]}),
+ ("first", True, {"num": [5]}),
+ ("first", False, {"cat": ["cat_1"], "num": [5]}),
+ ("first", lib.no_default, {"cat": ["cat_1"], "num": [5]}),
+ ("last", True, {"num": [20]}),
+ ("last", False, {"cat": ["cat_2"], "num": [20]}),
+ ("last", lib.no_default, {"cat": ["cat_2"], "num": [20]}),
+ ("mean", True, {"num": [12.5]}),
+ ("mean", False, "Could not convert"),
+ ("mean", lib.no_default, "Could not convert"),
+ ("median", True, {"num": [12.5]}),
+ ("median", False, r"Cannot convert \['cat_1' 'cat_2'\] to numeric"),
+ ("median", lib.no_default, r"Cannot convert \['cat_1' 'cat_2'\] to numeric"),
+ ("std", True, {"num": [10.606601717798213]}),
+ ("std", False, "could not convert string to float"),
+ ("std", lib.no_default, "could not convert string to float"),
+ ("var", True, {"num": [112.5]}),
+ ("var", False, "could not convert string to float"),
+ ("var", lib.no_default, "could not convert string to float"),
+ ("sem", True, {"num": [7.5]}),
+ ("sem", False, "could not convert string to float"),
+ ("sem", lib.no_default, "could not convert string to float"),
+ ],
+)
+def test_frame_downsample_method(
+ method, numeric_only, expected_data, using_infer_string
+):
+ # GH#46442 test if `numeric_only` behave as expected for DataFrameGroupBy
+
+ index = date_range("2018-01-01", periods=2, freq="D")
+ expected_index = date_range("2018-12-31", periods=1, freq="YE")
+ df = DataFrame({"cat": ["cat_1", "cat_2"], "num": [5, 20]}, index=index)
+ resampled = df.resample("YE")
+ if numeric_only is lib.no_default:
+ kwargs = {}
+ else:
+ kwargs = {"numeric_only": numeric_only}
+
+ func = getattr(resampled, method)
+ if isinstance(expected_data, str):
+ if method in ("var", "mean", "median", "prod"):
+ klass = TypeError
+ msg = re.escape(f"agg function failed [how->{method},dtype->")
+ if using_infer_string:
+ msg = f"dtype 'str' does not support operation '{method}'"
+ elif method in ["sum", "std", "sem"] and using_infer_string:
+ klass = TypeError
+ msg = f"dtype 'str' does not support operation '{method}'"
+ else:
+ klass = ValueError
+ msg = expected_data
+ with pytest.raises(klass, match=msg):
+ _ = func(**kwargs)
+ else:
+ result = func(**kwargs)
+ expected = DataFrame(expected_data, index=expected_index)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "method, numeric_only, expected_data",
+ [
+ ("sum", True, ()),
+ ("sum", False, ["cat_1cat_2"]),
+ ("sum", lib.no_default, ["cat_1cat_2"]),
+ ("prod", True, ()),
+ ("prod", False, ()),
+ ("prod", lib.no_default, ()),
+ ("min", True, ()),
+ ("min", False, ["cat_1"]),
+ ("min", lib.no_default, ["cat_1"]),
+ ("max", True, ()),
+ ("max", False, ["cat_2"]),
+ ("max", lib.no_default, ["cat_2"]),
+ ("first", True, ()),
+ ("first", False, ["cat_1"]),
+ ("first", lib.no_default, ["cat_1"]),
+ ("last", True, ()),
+ ("last", False, ["cat_2"]),
+ ("last", lib.no_default, ["cat_2"]),
+ ],
+)
+def test_series_downsample_method(
+ method, numeric_only, expected_data, using_infer_string
+):
+ # GH#46442 test if `numeric_only` behave as expected for SeriesGroupBy
+
+ index = date_range("2018-01-01", periods=2, freq="D")
+ expected_index = date_range("2018-12-31", periods=1, freq="YE")
+ df = Series(["cat_1", "cat_2"], index=index)
+ resampled = df.resample("YE")
+ kwargs = {} if numeric_only is lib.no_default else {"numeric_only": numeric_only}
+
+ func = getattr(resampled, method)
+ if numeric_only and numeric_only is not lib.no_default:
+ msg = rf"Cannot use numeric_only=True with SeriesGroupBy\.{method}"
+ with pytest.raises(TypeError, match=msg):
+ func(**kwargs)
+ elif method == "prod":
+ msg = re.escape("agg function failed [how->prod,dtype->")
+ if using_infer_string:
+ msg = "dtype 'str' does not support operation 'prod'"
+ with pytest.raises(TypeError, match=msg):
+ func(**kwargs)
+
+ else:
+ result = func(**kwargs)
+ expected = Series(expected_data, index=expected_index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_empty():
+ # GH#52484
+ df = DataFrame(
+ index=pd.to_datetime(
+ ["2018-01-01 00:00:00", "2018-01-01 12:00:00", "2018-01-02 00:00:00"]
+ )
+ )
+ expected = DataFrame(
+ index=pd.to_datetime(
+ [
+ "2018-01-01 00:00:00",
+ "2018-01-01 08:00:00",
+ "2018-01-01 16:00:00",
+ "2018-01-02 00:00:00",
+ ]
+ )
+ )
+ result = df.resample("8h").mean()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_asfreq_respects_origin_with_fixed_freq_all_seconds_equal():
+ # GH#62725: Ensure Resampler.asfreq respects origin="start_day"
+ # when all datetimes share identical seconds values.
+ idx = [
+ datetime(2025, 10, 17, 17, 15, 10),
+ datetime(2025, 10, 17, 17, 16, 10),
+ datetime(2025, 10, 17, 17, 17, 10),
+ ]
+ df = DataFrame({"value": [0, 1, 2]}, index=idx)
+
+ result = df.resample("1min", origin="start_day").asfreq()
+
+ # Expected index: list of Timestamps, matching dtype
+ exp_idx = pd.DatetimeIndex(
+ [
+ pd.Timestamp("2025-10-17 17:15:00"),
+ pd.Timestamp("2025-10-17 17:16:00"),
+ pd.Timestamp("2025-10-17 17:17:00"),
+ ],
+ dtype=result.index.dtype,
+ freq="min",
+ )
+
+ exp = DataFrame({"value": [np.nan, np.nan, np.nan]}, index=exp_idx)
+ tm.assert_frame_equal(result, exp)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_resampler_grouper.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_resampler_grouper.py
new file mode 100644
index 0000000000000000000000000000000000000000..862578decb782af4e056f0f076b1f4cba894c200
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_resampler_grouper.py
@@ -0,0 +1,671 @@
+from textwrap import dedent
+
+import numpy as np
+import pytest
+
+from pandas.compat import is_platform_windows
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ TimedeltaIndex,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.core.indexes.datetimes import date_range
+
+
+@pytest.fixture
+def test_frame():
+ return DataFrame(
+ {"A": [1] * 20 + [2] * 12 + [3] * 8, "B": np.arange(40)},
+ index=date_range("1/1/2000", freq="s", periods=40, unit="ns"),
+ )
+
+
+def test_tab_complete_ipython6_warning(ip):
+ from IPython.core.completer import provisionalcompleter
+
+ code = dedent(
+ """\
+ import numpy as np
+ from pandas import Series, date_range
+ data = np.arange(10, dtype=np.float64)
+ index = date_range("2020-01-01", periods=len(data))
+ s = Series(data, index=index)
+ rs = s.resample("D")
+ """
+ )
+ ip.run_cell(code)
+
+ # GH 31324 newer jedi version raises Deprecation warning;
+ # appears resolved 2021-02-02
+ with tm.assert_produces_warning(None, raise_on_extra_warnings=False):
+ with provisionalcompleter("ignore"):
+ list(ip.Completer.completions("rs.", 1))
+
+
+def test_deferred_with_groupby():
+ # GH 12486
+ # support deferred resample ops with groupby
+ data = [
+ ["2010-01-01", "A", 2],
+ ["2010-01-02", "A", 3],
+ ["2010-01-05", "A", 8],
+ ["2010-01-10", "A", 7],
+ ["2010-01-13", "A", 3],
+ ["2010-01-01", "B", 5],
+ ["2010-01-03", "B", 2],
+ ["2010-01-04", "B", 1],
+ ["2010-01-11", "B", 7],
+ ["2010-01-14", "B", 3],
+ ]
+
+ df = DataFrame(data, columns=["date", "id", "score"])
+ df.date = pd.to_datetime(df.date)
+
+ def f_0(x):
+ return x.set_index("date").resample("D").asfreq()
+
+ expected = df.groupby("id").apply(f_0)
+ result = df.set_index("date").groupby("id").resample("D").asfreq()
+ tm.assert_frame_equal(result, expected)
+
+ df = DataFrame(
+ {
+ "date": date_range(start="2016-01-01", periods=4, freq="W"),
+ "group": [1, 1, 2, 2],
+ "val": [5, 6, 7, 8],
+ }
+ ).set_index("date")
+
+ def f_1(x):
+ return x.resample("1D").ffill()
+
+ expected = df.groupby("group").apply(f_1)
+ result = df.groupby("group").resample("1D").ffill()
+ tm.assert_frame_equal(result, expected)
+
+
+def test_getitem(test_frame):
+ g = test_frame.groupby("A")
+
+ expected = g.B.apply(lambda x: x.resample("2s").mean())
+
+ result = g.resample("2s").B.mean()
+ tm.assert_series_equal(result, expected)
+
+ result = g.B.resample("2s").mean()
+ tm.assert_series_equal(result, expected)
+
+ result = g.resample("2s").mean().B
+ tm.assert_series_equal(result, expected)
+
+
+def test_getitem_multiple():
+ # GH 13174
+ # multiple calls after selection causing an issue with aliasing
+ data = [{"id": 1, "buyer": "A"}, {"id": 2, "buyer": "B"}]
+ df = DataFrame(data, index=date_range("2016-01-01", periods=2))
+ r = df.groupby("id").resample("1D")
+ result = r["buyer"].count()
+
+ exp_mi = pd.MultiIndex.from_arrays([[1, 2], df.index], names=("id", None))
+ expected = Series(
+ [1, 1],
+ index=exp_mi,
+ name="buyer",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = r["buyer"].count()
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_resample_on_api_with_getitem():
+ # GH 17813
+ df = DataFrame(
+ {"id": list("aabbb"), "date": date_range("1-1-2016", periods=5), "data": 1}
+ )
+ exp = df.set_index("date").groupby("id").resample("2D")["data"].sum()
+ result = df.groupby("id").resample("2D", on="date")["data"].sum()
+ tm.assert_series_equal(result, exp)
+
+
+def test_groupby_with_origin():
+ # GH 31809
+
+ freq = "1399min" # prime number that is smaller than 24h
+ start, end = "1/1/2000 00:00:00", "1/31/2000 00:00"
+ middle = "1/15/2000 00:00:00"
+
+ rng = date_range(start, end, freq="1231min") # prime number
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+ ts2 = ts[middle:end]
+
+ # proves that grouper without a fixed origin does not work
+ # when dealing with unusual frequencies
+ simple_grouper = pd.Grouper(freq=freq)
+ count_ts = ts.groupby(simple_grouper).agg("count")
+ count_ts = count_ts[middle:end]
+ count_ts2 = ts2.groupby(simple_grouper).agg("count")
+ with pytest.raises(AssertionError, match="Index are different"):
+ tm.assert_index_equal(count_ts.index, count_ts2.index)
+
+ # test origin on 1970-01-01 00:00:00
+ origin = Timestamp(0)
+ adjusted_grouper = pd.Grouper(freq=freq, origin=origin)
+ adjusted_count_ts = ts.groupby(adjusted_grouper).agg("count")
+ adjusted_count_ts = adjusted_count_ts[middle:end]
+ adjusted_count_ts2 = ts2.groupby(adjusted_grouper).agg("count")
+ tm.assert_series_equal(adjusted_count_ts, adjusted_count_ts2)
+
+ # test origin on 2049-10-18 20:00:00
+ origin_future = Timestamp(0) + pd.Timedelta("1399min") * 30_000
+ adjusted_grouper2 = pd.Grouper(freq=freq, origin=origin_future)
+ adjusted2_count_ts = ts.groupby(adjusted_grouper2).agg("count")
+ adjusted2_count_ts = adjusted2_count_ts[middle:end]
+ adjusted2_count_ts2 = ts2.groupby(adjusted_grouper2).agg("count")
+ tm.assert_series_equal(adjusted2_count_ts, adjusted2_count_ts2)
+
+ # both grouper use an adjusted timestamp that is a multiple of 1399 min
+ # they should be equals even if the adjusted_timestamp is in the future
+ tm.assert_series_equal(adjusted_count_ts, adjusted2_count_ts2)
+
+
+def test_nearest():
+ # GH 17496
+ # Resample nearest
+ index = date_range("1/1/2000", periods=3, freq="min", unit="ns")
+ result = Series(range(3), index=index).resample("20s").nearest()
+
+ expected = Series(
+ [0, 0, 1, 1, 1, 2, 2],
+ index=pd.DatetimeIndex(
+ [
+ "2000-01-01 00:00:00",
+ "2000-01-01 00:00:20",
+ "2000-01-01 00:00:40",
+ "2000-01-01 00:01:00",
+ "2000-01-01 00:01:20",
+ "2000-01-01 00:01:40",
+ "2000-01-01 00:02:00",
+ ],
+ dtype="datetime64[ns]",
+ freq="20s",
+ ),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "f",
+ [
+ "first",
+ "last",
+ "median",
+ "sem",
+ "sum",
+ "mean",
+ "min",
+ "max",
+ "size",
+ "count",
+ "nearest",
+ "bfill",
+ "ffill",
+ "asfreq",
+ "ohlc",
+ ],
+)
+def test_methods(f, test_frame):
+ g = test_frame.groupby("A")
+ r = g.resample("2s")
+
+ result = getattr(r, f)()
+ expected = g.apply(lambda x: getattr(x.resample("2s"), f)())
+ tm.assert_equal(result, expected)
+
+
+def test_methods_nunique(test_frame):
+ # series only
+ g = test_frame.groupby("A")
+ r = g.resample("2s")
+ result = r.B.nunique()
+ expected = g.B.apply(lambda x: x.resample("2s").nunique())
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("f", ["std", "var"])
+def test_methods_std_var(f, test_frame):
+ g = test_frame.groupby("A")
+ r = g.resample("2s")
+ result = getattr(r, f)(ddof=1)
+ expected = g.apply(lambda x: getattr(x.resample("2s"), f)(ddof=1))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply(test_frame):
+ g = test_frame.groupby("A")
+ r = g.resample("2s")
+
+ # reduction
+ expected = g.resample("2s").sum()
+
+ def f_0(x):
+ return x.resample("2s").sum()
+
+ result = r.apply(f_0)
+ tm.assert_frame_equal(result, expected)
+
+ def f_1(x):
+ return x.resample("2s").apply(lambda y: y.sum())
+
+ result = g.apply(f_1)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_with_mutated_index():
+ # GH 15169
+ index = date_range("1-1-2015", "12-31-15", freq="D")
+ df = DataFrame(
+ data={"col1": np.random.default_rng(2).random(len(index))}, index=index
+ )
+
+ def f(x):
+ s = Series([1, 2], index=["a", "b"])
+ return s
+
+ expected = df.groupby(pd.Grouper(freq="ME")).apply(f)
+
+ result = df.resample("ME").apply(f)
+ tm.assert_frame_equal(result, expected)
+
+ # A case for series
+ expected = df["col1"].groupby(pd.Grouper(freq="ME"), group_keys=False).apply(f)
+ result = df["col1"].resample("ME").apply(f)
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_columns_multilevel():
+ # GH 16231
+ cols = pd.MultiIndex.from_tuples([("A", "a", "", "one"), ("B", "b", "i", "two")])
+ ind = date_range(start="2017-01-01", freq="15Min", periods=8)
+ df = DataFrame(
+ np.array([0] * 16, dtype=np.int64).reshape(8, 2), index=ind, columns=cols
+ )
+ agg_dict = {col: (np.sum if col[3] == "one" else np.mean) for col in df.columns}
+ result = df.resample("h").apply(lambda x: agg_dict[x.name](x))
+ expected = DataFrame(
+ 2 * [[0, 0.0]],
+ index=date_range(start="2017-01-01", freq="1h", periods=2),
+ columns=pd.MultiIndex.from_tuples(
+ [("A", "a", "", "one"), ("B", "b", "i", "two")]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_non_naive_index():
+ def weighted_quantile(series, weights, q):
+ series = series.sort_values()
+ cumsum = weights.reindex(series.index).fillna(0).cumsum()
+ cutoff = cumsum.iloc[-1] * q
+ return series[cumsum >= cutoff].iloc[0]
+
+ times = date_range("2017-6-23 18:00", periods=8, freq="15min", tz="UTC")
+ data = Series([1.0, 1, 1, 1, 1, 2, 2, 0], index=times)
+ weights = Series([160.0, 91, 65, 43, 24, 10, 1, 0], index=times)
+
+ result = data.resample("D").apply(weighted_quantile, weights=weights, q=0.5)
+ ind = date_range(
+ "2017-06-23 00:00:00+00:00", "2017-06-23 00:00:00+00:00", freq="D", tz="UTC"
+ )
+ expected = Series([1.0], index=ind)
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_groupby_with_label(unit):
+ # GH 13235
+ index = date_range("2000-01-01", freq="2D", periods=5, unit=unit)
+ df = DataFrame(index=index, data={"col0": [0, 0, 1, 1, 2], "col1": [1, 1, 1, 1, 1]})
+ result = df.groupby("col0").resample("1W", label="left").sum()
+
+ mi = [
+ np.array([0, 0, 1, 2], dtype=np.int64),
+ np.array(
+ ["1999-12-26", "2000-01-02", "2000-01-02", "2000-01-02"],
+ dtype=f"M8[{unit}]",
+ ),
+ ]
+ mindex = pd.MultiIndex.from_arrays(mi, names=["col0", None])
+ expected = DataFrame(data={"col1": [1, 1, 2, 1]}, index=mindex)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_consistency_with_window(test_frame):
+ # consistent return values with window
+ df = test_frame
+ expected = Index([1, 2, 3], name="A")
+ result = df.groupby("A").resample("2s").mean()
+ assert result.index.nlevels == 2
+ tm.assert_index_equal(result.index.levels[0], expected)
+
+ result = df.groupby("A").rolling(20).mean()
+ assert result.index.nlevels == 2
+ tm.assert_index_equal(result.index.levels[0], expected)
+
+
+def test_median_duplicate_columns():
+ # GH 14233
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((20, 3)),
+ columns=list("aaa"),
+ index=date_range("2012-01-01", periods=20, freq="s"),
+ )
+ result = df.resample("5s").median()
+ df.columns = ["a", "b", "c"]
+ expected = df.resample("5s").median()
+ expected.columns = result.columns
+ tm.assert_frame_equal(result, expected)
+
+
+def test_apply_to_one_column_of_df():
+ # GH: 36951
+ df = DataFrame(
+ {"col": range(10), "col1": range(10, 20)},
+ index=date_range("2012-01-01", periods=10, freq="20min"),
+ )
+
+ # access "col" via getattr -> make sure we handle AttributeError
+ result = df.resample("h").apply(lambda group: group.col.sum())
+ expected = Series(
+ [3, 12, 21, 9], index=date_range("2012-01-01", periods=4, freq="h")
+ )
+ tm.assert_series_equal(result, expected)
+
+ # access "col" via _getitem__ -> make sure we handle KeyErrpr
+ result = df.resample("h").apply(lambda group: group["col"].sum())
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_groupby_agg():
+ # GH: 33548
+ df = DataFrame(
+ {
+ "cat": [
+ "cat_1",
+ "cat_1",
+ "cat_2",
+ "cat_1",
+ "cat_2",
+ "cat_1",
+ "cat_2",
+ "cat_1",
+ ],
+ "num": [5, 20, 22, 3, 4, 30, 10, 50],
+ "date": [
+ "2019-2-1",
+ "2018-02-03",
+ "2020-3-11",
+ "2019-2-2",
+ "2019-2-2",
+ "2018-12-4",
+ "2020-3-11",
+ "2020-12-12",
+ ],
+ }
+ )
+ df["date"] = pd.to_datetime(df["date"])
+
+ resampled = df.groupby("cat").resample("YE", on="date")
+ expected = resampled[["num"]].sum()
+ result = resampled.agg({"num": "sum"})
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_groupby_agg_listlike():
+ # GH 42905
+ ts = Timestamp("2021-02-28 00:00:00")
+ df = DataFrame({"class": ["beta"], "value": [69]}, index=Index([ts], name="date"))
+ resampled = df.groupby("class").resample("ME")["value"]
+ result = resampled.agg(["sum", "size"])
+ expected = DataFrame(
+ [[69, 1]],
+ index=pd.MultiIndex.from_tuples([("beta", ts)], names=["class", "date"]),
+ columns=["sum", "size"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("keys", [["a"], ["a", "b"]])
+def test_empty(keys):
+ # GH 26411
+ df = DataFrame([], columns=["a", "b"], index=TimedeltaIndex([]))
+ result = df.groupby(keys).resample(rule=pd.to_timedelta("00:00:01")).mean()
+ expected_columns = ["b"] if keys == ["a"] else []
+ expected = (
+ DataFrame(columns=["a", "b"])
+ .set_index(keys, drop=False)
+ .set_index(TimedeltaIndex([]), append=True)[expected_columns]
+ )
+ if len(keys) == 1:
+ expected.index.name = keys[0]
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("consolidate", [True, False])
+def test_resample_groupby_agg_object_dtype_all_nan(consolidate):
+ # https://github.com/pandas-dev/pandas/issues/39329
+
+ dates = date_range("2020-01-01", periods=15, freq="D", unit="ns")
+ df1 = DataFrame({"key": "A", "date": dates, "col1": range(15), "col_object": "val"})
+ df2 = DataFrame({"key": "B", "date": dates, "col1": range(15)})
+ df = pd.concat([df1, df2], ignore_index=True)
+ if consolidate:
+ df = df._consolidate()
+
+ result = df.groupby(["key"]).resample("W", on="date").min()
+ idx = pd.MultiIndex.from_arrays(
+ [
+ ["A"] * 3 + ["B"] * 3,
+ pd.to_datetime(["2020-01-05", "2020-01-12", "2020-01-19"] * 2).as_unit(
+ "ns"
+ ),
+ ],
+ names=["key", "date"],
+ )
+ expected = DataFrame(
+ {
+ "col1": [0, 5, 12] * 2,
+ "col_object": ["val"] * 3 + [np.nan] * 3,
+ },
+ index=idx,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("min_count", [0, 1])
+def test_groupby_resample_empty_sum_string(
+ string_dtype_no_object, test_frame, min_count
+):
+ # https://github.com/pandas-dev/pandas/issues/60229
+ dtype = string_dtype_no_object
+ test_frame = test_frame.assign(B=pd.array([pd.NA] * len(test_frame), dtype=dtype))
+ gbrs = test_frame.groupby("A").resample("40s")
+ result = gbrs.sum(min_count=min_count)
+
+ index = pd.MultiIndex(
+ levels=[[1, 2, 3], [pd.to_datetime("2000-01-01", unit="ns").as_unit("ns")]],
+ codes=[[0, 1, 2], [0, 0, 0]],
+ names=["A", None],
+ )
+ value = "" if min_count == 0 else pd.NA
+ expected = DataFrame({"B": value}, index=index, dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_resample_with_list_of_keys():
+ # GH 47362
+ df = DataFrame(
+ data={
+ "date": date_range(start="2016-01-01", periods=8),
+ "group": [0, 0, 0, 0, 1, 1, 1, 1],
+ "val": [1, 7, 5, 2, 3, 10, 5, 1],
+ }
+ )
+ result = df.groupby("group").resample("2D", on="date")[["val"]].mean()
+
+ mi_exp = pd.MultiIndex.from_arrays(
+ [[0, 0, 1, 1], df["date"]._values[::2]], names=["group", "date"]
+ )
+ expected = DataFrame(
+ data={
+ "val": [4.0, 3.5, 6.5, 3.0],
+ },
+ index=mi_exp,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("keys", [["a"], ["a", "b"]])
+def test_resample_no_index(keys):
+ # GH 47705
+ df = DataFrame([], columns=["a", "b", "date"])
+ df["date"] = pd.to_datetime(df["date"])
+ df = df.set_index("date")
+ result = df.groupby(keys).resample(rule=pd.to_timedelta("00:00:01")).mean()
+ expected_columns = ["b"] if keys == ["a"] else []
+ expected = DataFrame(columns=["a", "b", "date"]).set_index(keys, drop=False)
+ expected["date"] = pd.to_datetime(expected["date"])
+ expected = expected.set_index("date", append=True, drop=True)[expected_columns]
+ if len(keys) == 1:
+ expected.index.name = keys[0]
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_no_columns():
+ # GH#52484
+ df = DataFrame(
+ index=Index(
+ pd.to_datetime(
+ ["2018-01-01 00:00:00", "2018-01-01 12:00:00", "2018-01-02 00:00:00"]
+ ),
+ name="date",
+ )
+ )
+ result = df.groupby([0, 0, 1]).resample(rule=pd.to_timedelta("06:00:00")).mean()
+ index = pd.to_datetime(
+ [
+ "2018-01-01 00:00:00",
+ "2018-01-01 06:00:00",
+ "2018-01-01 12:00:00",
+ "2018-01-02 00:00:00",
+ ]
+ )
+ expected = DataFrame(
+ index=pd.MultiIndex(
+ levels=[np.array([0, 1], dtype=np.intp), index],
+ codes=[[0, 0, 0, 1], [0, 1, 2, 3]],
+ names=[None, "date"],
+ )
+ )
+
+ # GH#52710 - Index comes out as 32-bit on 64-bit Windows
+ tm.assert_frame_equal(result, expected, check_index_type=not is_platform_windows())
+
+
+def test_groupby_resample_size_all_index_same():
+ # GH 46826
+ df = DataFrame(
+ {"A": [1] * 3 + [2] * 3 + [1] * 3 + [2] * 3, "B": np.arange(12)},
+ index=date_range("31/12/2000 18:00", freq="h", periods=12, unit="ns"),
+ )
+ result = df.groupby("A").resample("D").size()
+
+ mi_exp = pd.MultiIndex.from_arrays(
+ [
+ [1, 1, 2, 2],
+ pd.DatetimeIndex(["2000-12-31", "2001-01-01"] * 2, dtype="M8[ns]"),
+ ],
+ names=["A", None],
+ )
+ expected = Series(
+ 3,
+ index=mi_exp,
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_groupby_resample_on_index_with_list_of_keys():
+ # GH 50840
+ df = DataFrame(
+ data={
+ "group": [0, 0, 0, 0, 1, 1, 1, 1],
+ "val": [3, 1, 4, 1, 5, 9, 2, 6],
+ },
+ index=date_range(start="2016-01-01", periods=8, name="date"),
+ )
+ result = df.groupby("group").resample("2D")[["val"]].mean()
+
+ mi_exp = pd.MultiIndex.from_arrays(
+ [[0, 0, 1, 1], df.index[::2]], names=["group", "date"]
+ )
+ expected = DataFrame(
+ data={
+ "val": [2.0, 2.5, 7.0, 4.0],
+ },
+ index=mi_exp,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_resample_on_index_with_list_of_keys_multi_columns():
+ # GH 50876
+ df = DataFrame(
+ data={
+ "group": [0, 0, 0, 0, 1, 1, 1, 1],
+ "first_val": [3, 1, 4, 1, 5, 9, 2, 6],
+ "second_val": [2, 7, 1, 8, 2, 8, 1, 8],
+ "third_val": [1, 4, 1, 4, 2, 1, 3, 5],
+ },
+ index=date_range(start="2016-01-01", periods=8, name="date"),
+ )
+ result = df.groupby("group").resample("2D")[["first_val", "second_val"]].mean()
+
+ mi_exp = pd.MultiIndex.from_arrays(
+ [[0, 0, 1, 1], df.index[::2]], names=["group", "date"]
+ )
+ expected = DataFrame(
+ data={
+ "first_val": [2.0, 2.5, 7.0, 4.0],
+ "second_val": [4.5, 4.5, 5.0, 4.5],
+ },
+ index=mi_exp,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_resample_on_index_with_list_of_keys_missing_column():
+ # GH 50876
+ df = DataFrame(
+ data={
+ "group": [0, 0, 0, 0, 1, 1, 1, 1],
+ "val": [3, 1, 4, 1, 5, 9, 2, 6],
+ },
+ index=Series(
+ date_range(start="2016-01-01", periods=8),
+ name="date",
+ ),
+ )
+ gb = df.groupby("group")
+ rs = gb.resample("2D")
+ with pytest.raises(KeyError, match="Columns not found"):
+ rs[["val_not_in_dataframe"]]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_time_grouper.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_time_grouper.py
new file mode 100644
index 0000000000000000000000000000000000000000..e214a9f17824dbfc4dc00896b651d9113fdac472
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_time_grouper.py
@@ -0,0 +1,439 @@
+from datetime import datetime
+from operator import methodcaller
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+from pandas.core.groupby.grouper import Grouper
+from pandas.core.indexes.datetimes import date_range
+
+
+@pytest.fixture
+def test_series():
+ return Series(
+ np.random.default_rng(2).standard_normal(1000),
+ index=date_range("1/1/2000", periods=1000),
+ )
+
+
+def test_apply(test_series):
+ grouper = Grouper(freq="YE", label="right", closed="right")
+
+ grouped = test_series.groupby(grouper)
+
+ def f(x):
+ return x.sort_values()[-3:]
+
+ applied = grouped.apply(f)
+ expected = test_series.groupby(lambda x: x.year).apply(f)
+
+ applied.index = applied.index.droplevel(0)
+ expected.index = expected.index.droplevel(0)
+ tm.assert_series_equal(applied, expected)
+
+
+def test_count(test_series):
+ test_series[::3] = np.nan
+
+ expected = test_series.groupby(lambda x: x.year).count()
+
+ grouper = Grouper(freq="YE", label="right", closed="right")
+ result = test_series.groupby(grouper).count()
+ expected.index = result.index
+ tm.assert_series_equal(result, expected)
+
+ result = test_series.resample("YE").count()
+ expected.index = result.index
+ tm.assert_series_equal(result, expected)
+
+
+def test_numpy_reduction(test_series):
+ result = test_series.resample("YE", closed="right").prod()
+ expected = test_series.groupby(lambda x: x.year).agg(np.prod)
+ expected.index = result.index
+ tm.assert_series_equal(result, expected)
+
+
+def test_apply_iteration():
+ # #2300
+ N = 1000
+ ind = date_range(start="2000-01-01", freq="D", periods=N)
+ df = DataFrame({"open": 1, "close": 2}, index=ind)
+ tg = Grouper(freq="ME")
+
+ grouper, _ = tg._get_grouper(df)
+
+ # Errors
+ grouped = df.groupby(grouper, group_keys=False)
+
+ def f(df):
+ return df["close"] / df["open"]
+
+ # it works!
+ result = grouped.apply(f)
+ tm.assert_index_equal(result.index, df.index)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ Index([1, 2]),
+ Index(["a", "b"]),
+ Index([1.1, 2.2]),
+ pd.MultiIndex.from_arrays([[1, 2], ["a", "b"]]),
+ ],
+)
+def test_fails_on_no_datetime_index(index):
+ name = type(index).__name__
+ df = DataFrame({"a": range(len(index))}, index=index)
+
+ msg = (
+ "Only valid with DatetimeIndex, TimedeltaIndex "
+ f"or PeriodIndex, but got an instance of '{name}'"
+ )
+ with pytest.raises(TypeError, match=msg):
+ df.groupby(Grouper(freq="D"))
+
+
+def test_aaa_group_order():
+ # GH 12840
+ # check TimeGrouper perform stable sorts
+ n = 20
+ data = np.random.default_rng(2).standard_normal((n, 4))
+ df = DataFrame(data, columns=["A", "B", "C", "D"])
+ df["key"] = [
+ datetime(2013, 1, 1),
+ datetime(2013, 1, 2),
+ datetime(2013, 1, 3),
+ datetime(2013, 1, 4),
+ datetime(2013, 1, 5),
+ ] * 4
+ grouped = df.groupby(Grouper(key="key", freq="D"))
+
+ tm.assert_frame_equal(grouped.get_group(datetime(2013, 1, 1)), df[::5])
+ tm.assert_frame_equal(grouped.get_group(datetime(2013, 1, 2)), df[1::5])
+ tm.assert_frame_equal(grouped.get_group(datetime(2013, 1, 3)), df[2::5])
+ tm.assert_frame_equal(grouped.get_group(datetime(2013, 1, 4)), df[3::5])
+ tm.assert_frame_equal(grouped.get_group(datetime(2013, 1, 5)), df[4::5])
+
+
+def test_aggregate_normal(resample_method):
+ """Check TimeGrouper's aggregation is identical as normal groupby."""
+
+ data = np.random.default_rng(2).standard_normal((20, 4))
+ normal_df = DataFrame(data, columns=["A", "B", "C", "D"])
+ normal_df["key"] = [1, 2, 3, 4, 5] * 4
+
+ dt_df = DataFrame(data, columns=["A", "B", "C", "D"])
+ dt_df["key"] = Index(
+ [
+ datetime(2013, 1, 1),
+ datetime(2013, 1, 2),
+ datetime(2013, 1, 3),
+ datetime(2013, 1, 4),
+ datetime(2013, 1, 5),
+ ]
+ * 4,
+ dtype="M8[ns]",
+ )
+
+ normal_grouped = normal_df.groupby("key")
+ dt_grouped = dt_df.groupby(Grouper(key="key", freq="D"))
+
+ expected = getattr(normal_grouped, resample_method)()
+ dt_result = getattr(dt_grouped, resample_method)()
+ expected.index = date_range(
+ start="2013-01-01", freq="D", periods=5, unit="ns", name="key"
+ )
+ tm.assert_equal(expected, dt_result)
+
+
+@pytest.mark.xfail(reason="if TimeGrouper is used included, 'nth' doesn't work yet")
+def test_aggregate_nth():
+ """Check TimeGrouper's aggregation is identical as normal groupby."""
+
+ data = np.random.default_rng(2).standard_normal((20, 4))
+ normal_df = DataFrame(data, columns=["A", "B", "C", "D"])
+ normal_df["key"] = [1, 2, 3, 4, 5] * 4
+
+ dt_df = DataFrame(data, columns=["A", "B", "C", "D"])
+ dt_df["key"] = [
+ datetime(2013, 1, 1),
+ datetime(2013, 1, 2),
+ datetime(2013, 1, 3),
+ datetime(2013, 1, 4),
+ datetime(2013, 1, 5),
+ ] * 4
+
+ normal_grouped = normal_df.groupby("key")
+ dt_grouped = dt_df.groupby(Grouper(key="key", freq="D"))
+
+ expected = normal_grouped.nth(3)
+ expected.index = date_range(start="2013-01-01", freq="D", periods=5, name="key")
+ dt_result = dt_grouped.nth(3)
+ tm.assert_frame_equal(expected, dt_result)
+
+
+@pytest.mark.parametrize(
+ "method, method_args, unit",
+ [
+ ("sum", {}, 0),
+ ("sum", {"min_count": 0}, 0),
+ ("sum", {"min_count": 1}, np.nan),
+ ("prod", {}, 1),
+ ("prod", {"min_count": 0}, 1),
+ ("prod", {"min_count": 1}, np.nan),
+ ],
+)
+def test_resample_entirely_nat_window(method, method_args, unit):
+ ser = Series([0] * 2 + [np.nan] * 2, index=date_range("2017", periods=4, unit="ns"))
+ result = methodcaller(method, **method_args)(ser.resample("2D"))
+
+ exp_dti = pd.DatetimeIndex(["2017-01-01", "2017-01-03"], dtype="M8[ns]", freq="2D")
+ expected = Series([0.0, unit], index=exp_dti)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "func, fill_value",
+ [("min", np.nan), ("max", np.nan), ("sum", 0), ("prod", 1), ("count", 0)],
+)
+def test_aggregate_with_nat(func, fill_value):
+ # check TimeGrouper's aggregation is identical as normal groupby
+ # if NaT is included, 'var', 'std', 'mean', 'first','last'
+ # and 'nth' doesn't work yet
+
+ n = 20
+ data = np.random.default_rng(2).standard_normal((n, 4)).astype("int64")
+ normal_df = DataFrame(data, columns=["A", "B", "C", "D"])
+ normal_df["key"] = [1, 2, np.nan, 4, 5] * 4
+
+ dt_df = DataFrame(data, columns=["A", "B", "C", "D"])
+ dt_df["key"] = Index(
+ [
+ datetime(2013, 1, 1),
+ datetime(2013, 1, 2),
+ pd.NaT,
+ datetime(2013, 1, 4),
+ datetime(2013, 1, 5),
+ ]
+ * 4,
+ dtype="M8[ns]",
+ )
+
+ normal_grouped = normal_df.groupby("key")
+ dt_grouped = dt_df.groupby(Grouper(key="key", freq="D"))
+
+ normal_result = getattr(normal_grouped, func)()
+ dt_result = getattr(dt_grouped, func)()
+
+ pad = DataFrame([[fill_value] * 4], index=[3], columns=["A", "B", "C", "D"])
+ expected = pd.concat([normal_result, pad])
+ expected = expected.sort_index()
+ dti = date_range(
+ start="2013-01-01",
+ freq="D",
+ periods=5,
+ name="key",
+ unit=dt_df["key"]._values.unit,
+ )
+ expected.index = dti._with_freq(None) # TODO: is this desired?
+ tm.assert_frame_equal(expected, dt_result)
+ assert dt_result.index.name == "key"
+
+
+def test_aggregate_with_nat_size():
+ # GH 9925
+ n = 20
+ data = np.random.default_rng(2).standard_normal((n, 4)).astype("int64")
+ normal_df = DataFrame(data, columns=["A", "B", "C", "D"])
+ normal_df["key"] = [1, 2, np.nan, 4, 5] * 4
+
+ dt_df = DataFrame(data, columns=["A", "B", "C", "D"])
+ dt_df["key"] = Index(
+ [
+ datetime(2013, 1, 1),
+ datetime(2013, 1, 2),
+ pd.NaT,
+ datetime(2013, 1, 4),
+ datetime(2013, 1, 5),
+ ]
+ * 4,
+ dtype="M8[ns]",
+ )
+
+ normal_grouped = normal_df.groupby("key")
+ dt_grouped = dt_df.groupby(Grouper(key="key", freq="D"))
+
+ normal_result = normal_grouped.size()
+ dt_result = dt_grouped.size()
+
+ pad = Series([0], index=[3])
+ expected = pd.concat([normal_result, pad])
+ expected = expected.sort_index()
+ expected.index = date_range(
+ start="2013-01-01",
+ freq="D",
+ periods=5,
+ name="key",
+ unit=dt_df["key"]._values.unit,
+ )._with_freq(None)
+ tm.assert_series_equal(expected, dt_result)
+ assert dt_result.index.name == "key"
+
+
+def test_repr():
+ # GH18203
+ result = repr(Grouper(key="A", freq="h"))
+ expected = (
+ "TimeGrouper(key='A', freq=, sort=True, dropna=True, "
+ "closed='left', label='left', how='mean', "
+ "convention='e', origin='start_day')"
+ )
+ assert result == expected
+
+ result = repr(Grouper(key="A", freq="h", origin="2000-01-01"))
+ expected = (
+ "TimeGrouper(key='A', freq=, sort=True, dropna=True, "
+ "closed='left', label='left', how='mean', "
+ "convention='e', origin=Timestamp('2000-01-01 00:00:00'))"
+ )
+ assert result == expected
+
+
+@pytest.mark.parametrize(
+ "method, method_args, expected_values",
+ [
+ ("sum", {}, [1, 0, 1]),
+ ("sum", {"min_count": 0}, [1, 0, 1]),
+ ("sum", {"min_count": 1}, [1, np.nan, 1]),
+ ("sum", {"min_count": 2}, [np.nan, np.nan, np.nan]),
+ ("prod", {}, [1, 1, 1]),
+ ("prod", {"min_count": 0}, [1, 1, 1]),
+ ("prod", {"min_count": 1}, [1, np.nan, 1]),
+ ("prod", {"min_count": 2}, [np.nan, np.nan, np.nan]),
+ ],
+)
+def test_upsample_sum(method, method_args, expected_values):
+ ser = Series(1, index=date_range("2017", periods=2, freq="h", unit="ns"))
+ resampled = ser.resample("30min")
+ index = pd.DatetimeIndex(
+ ["2017-01-01T00:00:00", "2017-01-01T00:30:00", "2017-01-01T01:00:00"],
+ dtype="M8[ns]",
+ freq="30min",
+ )
+ result = methodcaller(method, **method_args)(resampled)
+ expected = Series(expected_values, index=index)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.fixture
+def groupy_test_df():
+ return DataFrame(
+ {"price": [10, 11, 9], "volume": [50, 60, 50]},
+ index=date_range("01/01/2018", periods=3, freq="W", unit="ns"),
+ )
+
+
+def test_groupby_resample_interpolate_raises(groupy_test_df):
+ # GH 35325
+
+ # Make a copy of the test data frame that has index.name=None
+ groupy_test_df_without_index_name = groupy_test_df.copy()
+ groupy_test_df_without_index_name.index.name = None
+
+ dfs = [groupy_test_df, groupy_test_df_without_index_name]
+
+ for df in dfs:
+ with pytest.raises(
+ NotImplementedError,
+ match="Direct interpolation of MultiIndex data frames is not supported",
+ ):
+ df.groupby("volume").resample("1D").interpolate(method="linear")
+
+
+def test_groupby_resample_interpolate_with_apply_syntax(groupy_test_df):
+ # GH 35325
+
+ # Make a copy of the test data frame that has index.name=None
+ groupy_test_df_without_index_name = groupy_test_df.copy()
+ groupy_test_df_without_index_name.index.name = None
+
+ dfs = [groupy_test_df, groupy_test_df_without_index_name]
+
+ for df in dfs:
+ result = df.groupby("volume").apply(
+ lambda x: x.resample("1D").interpolate(method="linear"),
+ )
+
+ volume = [50] * 15 + [60]
+ week_starting = [
+ *list(date_range("2018-01-07", "2018-01-21", unit="ns")),
+ Timestamp("2018-01-14"),
+ ]
+ expected_ind = pd.MultiIndex.from_arrays(
+ [volume, week_starting],
+ names=["volume", df.index.name],
+ )
+
+ expected = DataFrame(
+ data={
+ "price": [
+ 10.0,
+ 9.928571428571429,
+ 9.857142857142858,
+ 9.785714285714286,
+ 9.714285714285714,
+ 9.642857142857142,
+ 9.571428571428571,
+ 9.5,
+ 9.428571428571429,
+ 9.357142857142858,
+ 9.285714285714286,
+ 9.214285714285714,
+ 9.142857142857142,
+ 9.071428571428571,
+ 9.0,
+ 11.0,
+ ]
+ },
+ index=expected_ind,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_resample_interpolate_with_apply_syntax_off_grid(groupy_test_df):
+ """Similar test as test_groupby_resample_interpolate_with_apply_syntax but
+ with resampling that results in missing anchor points when interpolating.
+ See GH#21351."""
+ # GH#21351
+ result = groupy_test_df.groupby("volume").apply(
+ lambda x: x.resample("265h").interpolate(method="linear")
+ )
+
+ volume = [50, 50, 60]
+ week_starting = pd.DatetimeIndex(
+ [
+ Timestamp("2018-01-07"),
+ Timestamp("2018-01-18 01:00:00"),
+ Timestamp("2018-01-14"),
+ ]
+ ).as_unit("ns")
+ expected_ind = pd.MultiIndex.from_arrays(
+ [volume, week_starting],
+ names=["volume", "week_starting"],
+ )
+
+ expected = DataFrame(
+ data={"price": [10.0, 9.5, 11.0]},
+ index=expected_ind,
+ )
+ tm.assert_frame_equal(result, expected, check_names=False)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_timedelta.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_timedelta.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fdaea50c7430465bf50c80c14568b1e4deffbad
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/resample/test_timedelta.py
@@ -0,0 +1,218 @@
+from datetime import timedelta
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+from pandas.core.indexes.timedeltas import timedelta_range
+
+
+def test_asfreq_bug():
+ df = DataFrame(data=[1, 3], index=[timedelta(), timedelta(minutes=3)])
+ result = df.resample("1min").asfreq()
+ expected = DataFrame(
+ data=[1, np.nan, np.nan, 3],
+ index=timedelta_range("0 day", periods=4, freq="1min", unit="us"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_with_nat():
+ # GH 13223
+ index = pd.to_timedelta(["0s", pd.NaT, "2s"])
+ result = DataFrame({"value": [2, 3, 5]}, index).resample("1s").mean()
+ expected = DataFrame(
+ {"value": [2.5, np.nan, 5.0]},
+ index=timedelta_range("0 day", periods=3, freq="1s"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_as_freq_with_subperiod():
+ # GH 13022
+ index = timedelta_range("00:00:00", "00:10:00", freq="5min")
+ df = DataFrame(data={"value": [1, 5, 10]}, index=index)
+ result = df.resample("2min").asfreq()
+ expected_data = {"value": [1, np.nan, np.nan, np.nan, np.nan, 10]}
+ expected = DataFrame(
+ data=expected_data, index=timedelta_range("00:00:00", "00:10:00", freq="2min")
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_with_timedeltas():
+ expected = DataFrame({"A": np.arange(1480)})
+ expected = expected.groupby(expected.index // 30).sum()
+ expected.index = timedelta_range("0 days", freq="30min", periods=50)
+
+ df = DataFrame(
+ {"A": np.arange(1480)},
+ index=pd.to_timedelta(np.arange(1480), unit="min").as_unit("us"),
+ )
+ result = df.resample("30min").sum()
+
+ tm.assert_frame_equal(result, expected)
+
+ s = df["A"]
+ result = s.resample("30min").sum()
+ tm.assert_series_equal(result, expected["A"])
+
+
+def test_resample_single_period_timedelta():
+ s = Series(list(range(5)), index=timedelta_range("1 day", freq="s", periods=5))
+ result = s.resample("2s").sum()
+ expected = Series([1, 5, 4], index=timedelta_range("1 day", freq="2s", periods=3))
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_timedelta_idempotency():
+ # GH 12072
+ index = timedelta_range("0", periods=9, freq="10ms")
+ series = Series(range(9), index=index)
+ result = series.resample("10ms").mean()
+ expected = series.astype(float)
+ tm.assert_series_equal(result, expected)
+
+
+def test_resample_offset_with_timedeltaindex():
+ # GH 10530 & 31809
+ rng = timedelta_range(start="0s", periods=25, freq="s")
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ with_base = ts.resample("2s", offset="5s").mean()
+ without_base = ts.resample("2s").mean()
+
+ exp_without_base = timedelta_range(start="0s", end="25s", freq="2s")
+ exp_with_base = timedelta_range(start="5s", end="29s", freq="2s")
+
+ tm.assert_index_equal(without_base.index, exp_without_base)
+ tm.assert_index_equal(with_base.index, exp_with_base)
+
+
+def test_resample_categorical_data_with_timedeltaindex():
+ # GH #12169
+ df = DataFrame({"Group_obj": "A"}, index=pd.to_timedelta(list(range(20)), unit="s"))
+ df["Group"] = df["Group_obj"].astype("category")
+ result = df.resample("10s").agg(lambda x: x.value_counts().index[0])
+ exp_tdi = pd.TimedeltaIndex(np.array([0, 10], dtype="m8[s]"), freq="10s")
+ expected = DataFrame(
+ {"Group_obj": ["A", "A"], "Group": ["A", "A"]},
+ index=exp_tdi,
+ )
+ expected = expected.reindex(["Group_obj", "Group"], axis=1)
+ expected["Group"] = expected["Group_obj"].astype("category")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_timedelta_values():
+ # GH 13119
+ # check that timedelta dtype is preserved when NaT values are
+ # introduced by the resampling
+
+ times = timedelta_range("1 day", "6 day", freq="4D")
+ df = DataFrame({"time": times}, index=times)
+
+ times2 = timedelta_range("1 day", "6 day", freq="2D")
+ exp = Series(times2, index=times2, name="time")
+ exp.iloc[1] = pd.NaT
+
+ res = df.resample("2D").first()["time"]
+ tm.assert_series_equal(res, exp)
+ res = df["time"].resample("2D").first()
+ tm.assert_series_equal(res, exp)
+
+
+@pytest.mark.parametrize(
+ "start, end, freq, resample_freq",
+ [
+ ("8h", "21h59min50s", "10s", "3h"), # GH 30353 example
+ ("3h", "22h", "1h", "5h"),
+ ("527D", "5006D", "3D", "10D"),
+ ("1D", "10D", "1D", "2D"), # GH 13022 example
+ # tests that worked before GH 33498:
+ ("8h", "21h59min50s", "10s", "2h"),
+ ("0h", "21h59min50s", "10s", "3h"),
+ ("10D", "85D", "D", "2D"),
+ ],
+)
+def test_resample_timedelta_edge_case(start, end, freq, resample_freq):
+ # GH 33498
+ # check that the timedelta bins does not contains an extra bin
+ idx = timedelta_range(start=start, end=end, freq=freq)
+ s = Series(np.arange(len(idx)), index=idx)
+ result = s.resample(resample_freq).min()
+ expected_index = timedelta_range(freq=resample_freq, start=start, end=end)
+ tm.assert_index_equal(result.index, expected_index)
+ assert result.index.freq == expected_index.freq
+ assert not np.isnan(result.iloc[-1])
+
+
+@pytest.mark.parametrize("duplicates", [True, False])
+def test_resample_with_timedelta_yields_no_empty_groups(duplicates):
+ # GH 10603
+ df = DataFrame(
+ np.random.default_rng(2).normal(size=(10000, 4)),
+ index=timedelta_range(start="0s", periods=10000, freq="3906250ns"),
+ )
+ if duplicates:
+ # case with non-unique columns
+ df.columns = ["A", "B", "A", "C"]
+
+ result = df.loc["1s":, :].resample("3s").apply(lambda x: len(x))
+
+ expected = DataFrame(
+ [[768] * 4] * 12 + [[528] * 4],
+ index=timedelta_range(start="1s", periods=13, freq="3s", unit="ns"),
+ )
+ expected.columns = df.columns
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_quantile_timedelta(unit):
+ # GH: 29485
+ dtype = np.dtype(f"m8[{unit}]")
+ df = DataFrame(
+ {"value": pd.to_timedelta(np.arange(4), unit="s").astype(dtype)},
+ index=pd.date_range("20200101", periods=4, tz="UTC"),
+ )
+ result = df.resample("2D").quantile(0.99)
+ expected = DataFrame(
+ {
+ "value": [
+ pd.Timedelta("0 days 00:00:00.990000"),
+ pd.Timedelta("0 days 00:00:02.990000"),
+ ]
+ },
+ index=pd.date_range("20200101", periods=2, tz="UTC", freq="2D"),
+ ).astype(dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_resample_closed_right():
+ # GH#45414
+ idx = pd.Index([pd.Timedelta(seconds=120 + i * 30) for i in range(10)])
+ ser = Series(range(10), index=idx)
+ result = ser.resample("min", closed="right", label="right").sum()
+ expected = Series(
+ [0, 3, 7, 11, 15, 9],
+ index=pd.TimedeltaIndex(
+ [pd.Timedelta(seconds=120 + i * 60) for i in range(6)], freq="min"
+ ),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_arrow_duration_resample():
+ # GH 56371
+ idx = pd.Index(timedelta_range("1 day", periods=5), dtype="duration[ns][pyarrow]")
+ expected = Series(np.arange(5, dtype=np.float64), index=idx)
+ result = expected.resample("1D").mean()
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_append.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_append.py
new file mode 100644
index 0000000000000000000000000000000000000000..258d0e89fda7aabf84520b039d8a4a043ed290c3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_append.py
@@ -0,0 +1,351 @@
+import datetime as dt
+from itertools import combinations
+
+import dateutil
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ Timestamp,
+ concat,
+ isna,
+)
+import pandas._testing as tm
+
+
+class TestAppend:
+ def test_append(self, sort, float_frame):
+ mixed_frame = float_frame.copy()
+ mixed_frame["foo"] = "bar"
+
+ begin_index = float_frame.index[:5]
+ end_index = float_frame.index[5:]
+
+ begin_frame = float_frame.reindex(begin_index)
+ end_frame = float_frame.reindex(end_index)
+
+ appended = concat([begin_frame, end_frame])
+ tm.assert_almost_equal(appended["A"], float_frame["A"])
+
+ del end_frame["A"]
+ partial_appended = concat([begin_frame, end_frame], sort=sort)
+ assert "A" in partial_appended
+
+ partial_appended = concat([end_frame, begin_frame], sort=sort)
+ assert "A" in partial_appended
+
+ # mixed type handling
+ appended = concat([mixed_frame[:5], mixed_frame[5:]])
+ tm.assert_frame_equal(appended, mixed_frame)
+
+ # what to test here
+ mixed_appended = concat([mixed_frame[:5], float_frame[5:]], sort=sort)
+ mixed_appended2 = concat([float_frame[:5], mixed_frame[5:]], sort=sort)
+
+ # all equal except 'foo' column
+ tm.assert_frame_equal(
+ mixed_appended.reindex(columns=["A", "B", "C", "D"]),
+ mixed_appended2.reindex(columns=["A", "B", "C", "D"]),
+ )
+
+ def test_append_empty(self, float_frame):
+ empty = DataFrame()
+
+ appended = concat([float_frame, empty])
+ tm.assert_frame_equal(float_frame, appended)
+ assert appended is not float_frame
+
+ appended = concat([empty, float_frame])
+ tm.assert_frame_equal(float_frame, appended)
+ assert appended is not float_frame
+
+ def test_append_overlap_raises(self, float_frame):
+ msg = "Indexes have overlapping values"
+ with pytest.raises(ValueError, match=msg):
+ concat([float_frame, float_frame], verify_integrity=True)
+
+ def test_append_new_columns(self):
+ # see gh-6129: new columns
+ df = DataFrame({"a": {"x": 1, "y": 2}, "b": {"x": 3, "y": 4}})
+ row = Series([5, 6, 7], index=["a", "b", "c"], name="z")
+ expected = DataFrame(
+ {
+ "a": {"x": 1, "y": 2, "z": 5},
+ "b": {"x": 3, "y": 4, "z": 6},
+ "c": {"z": 7},
+ }
+ )
+ result = df._append_internal(row)
+ tm.assert_frame_equal(result, expected)
+
+ def test_append_length0_frame(self, sort):
+ df = DataFrame(columns=["A", "B", "C"])
+ df3 = DataFrame(index=[0, 1], columns=["A", "B"])
+ df5 = concat([df, df3], sort=sort)
+
+ expected = DataFrame(index=[0, 1], columns=["A", "B", "C"])
+ tm.assert_frame_equal(df5, expected)
+
+ def test_append_records(self):
+ arr1 = np.zeros((2,), dtype=("i4,f4,S10"))
+ arr1[:] = [(1, 2.0, "Hello"), (2, 3.0, "World")]
+
+ arr2 = np.zeros((3,), dtype=("i4,f4,S10"))
+ arr2[:] = [(3, 4.0, "foo"), (5, 6.0, "bar"), (7.0, 8.0, "baz")]
+
+ df1 = DataFrame(arr1)
+ df2 = DataFrame(arr2)
+
+ result = concat([df1, df2], ignore_index=True)
+ expected = DataFrame(np.concatenate((arr1, arr2)))
+ tm.assert_frame_equal(result, expected)
+
+ # rewrite sort fixture, since we also want to test default of None
+ def test_append_sorts(self, sort):
+ df1 = DataFrame({"a": [1, 2], "b": [1, 2]}, columns=["b", "a"])
+ df2 = DataFrame({"a": [1, 2], "c": [3, 4]}, index=[2, 3])
+
+ result = concat([df1, df2], sort=sort)
+
+ # for None / True
+ expected = DataFrame(
+ {"b": [1, 2, None, None], "a": [1, 2, 1, 2], "c": [None, None, 3, 4]},
+ columns=["a", "b", "c"],
+ )
+ if sort is False:
+ expected = expected[["b", "a", "c"]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_append_different_columns(self, sort):
+ df = DataFrame(
+ {
+ "bools": np.random.default_rng(2).standard_normal(10) > 0,
+ "ints": np.random.default_rng(2).integers(0, 10, 10),
+ "floats": np.random.default_rng(2).standard_normal(10),
+ "strings": ["foo", "bar"] * 5,
+ }
+ )
+
+ a = df[:5].loc[:, ["bools", "ints", "floats"]]
+ b = df[5:].loc[:, ["strings", "ints", "floats"]]
+
+ appended = concat([a, b], sort=sort)
+ assert isna(appended["strings"][0:4]).all()
+ assert isna(appended["bools"][5:]).all()
+
+ def test_append_preserve_index_name(self):
+ # #980
+ df1 = DataFrame(columns=["A", "B", "C"])
+ df1 = df1.set_index(["A"])
+ df2 = DataFrame(data=[[1, 4, 7], [2, 5, 8], [3, 6, 9]], columns=["A", "B", "C"])
+ df2 = df2.set_index(["A"])
+
+ result = concat([df1, df2])
+ assert result.index.name == "A"
+
+ indexes_can_append = [
+ pd.RangeIndex(3),
+ Index([4, 5, 6]),
+ Index([4.5, 5.5, 6.5]),
+ Index(list("abc")),
+ pd.CategoricalIndex("A B C".split()),
+ pd.CategoricalIndex("D E F".split(), ordered=True),
+ pd.IntervalIndex.from_breaks([7, 8, 9, 10]),
+ pd.DatetimeIndex(
+ [
+ dt.datetime(2013, 1, 3, 0, 0),
+ dt.datetime(2013, 1, 3, 6, 10),
+ dt.datetime(2013, 1, 3, 7, 12),
+ ]
+ ),
+ pd.MultiIndex.from_arrays(["A B C".split(), "D E F".split()]),
+ ]
+
+ @pytest.mark.parametrize(
+ "index", indexes_can_append, ids=lambda x: type(x).__name__
+ )
+ def test_append_same_columns_type(self, index):
+ # GH18359
+
+ # df wider than ser
+ df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=index)
+ ser_index = index[:2]
+ ser = Series([7, 8], index=ser_index, name=2)
+ result = df._append_internal(ser)
+ expected = DataFrame(
+ [[1, 2, 3.0], [4, 5, 6], [7, 8, np.nan]], index=[0, 1, 2], columns=index
+ )
+ # integer dtype is preserved for columns present in ser.index
+ assert expected.dtypes.iloc[0].kind == "i"
+ assert expected.dtypes.iloc[1].kind == "i"
+
+ tm.assert_frame_equal(result, expected)
+
+ # ser wider than df
+ ser_index = index
+ index = index[:2]
+ df = DataFrame([[1, 2], [4, 5]], columns=index)
+ ser = Series([7, 8, 9], index=ser_index, name=2)
+ result = df._append_internal(ser)
+ expected = DataFrame(
+ [[1, 2, np.nan], [4, 5, np.nan], [7, 8, 9]],
+ index=[0, 1, 2],
+ columns=ser_index,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "df_columns, series_index",
+ combinations(indexes_can_append, r=2),
+ ids=lambda x: type(x).__name__,
+ )
+ def test_append_different_columns_types(self, df_columns, series_index):
+ # GH18359
+ # See also test 'test_append_different_columns_types_raises' below
+ # for errors raised when appending
+
+ df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=df_columns)
+ ser = Series([7, 8, 9], index=series_index, name=2)
+
+ result = df._append_internal(ser)
+ idx_diff = ser.index.difference(df_columns)
+ combined_columns = Index(df_columns.tolist()).append(idx_diff)
+ expected = DataFrame(
+ [
+ [1.0, 2.0, 3.0, np.nan, np.nan, np.nan],
+ [4, 5, 6, np.nan, np.nan, np.nan],
+ [np.nan, np.nan, np.nan, 7, 8, 9],
+ ],
+ index=[0, 1, 2],
+ columns=combined_columns,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_append_dtype_coerce(self, sort):
+ # GH 4993
+ # appending with datetime will incorrectly convert datetime64
+
+ df1 = DataFrame(
+ index=[1, 2],
+ data=[dt.datetime(2013, 1, 1, 0, 0), dt.datetime(2013, 1, 2, 0, 0)],
+ columns=["start_time"],
+ )
+ df2 = DataFrame(
+ index=[4, 5],
+ data=[
+ [dt.datetime(2013, 1, 3, 0, 0), dt.datetime(2013, 1, 3, 6, 10)],
+ [dt.datetime(2013, 1, 4, 0, 0), dt.datetime(2013, 1, 4, 7, 10)],
+ ],
+ columns=["start_time", "end_time"],
+ )
+
+ expected = concat(
+ [
+ Series(
+ [
+ pd.NaT,
+ pd.NaT,
+ dt.datetime(2013, 1, 3, 6, 10),
+ dt.datetime(2013, 1, 4, 7, 10),
+ ],
+ name="end_time",
+ ),
+ Series(
+ [
+ dt.datetime(2013, 1, 1, 0, 0),
+ dt.datetime(2013, 1, 2, 0, 0),
+ dt.datetime(2013, 1, 3, 0, 0),
+ dt.datetime(2013, 1, 4, 0, 0),
+ ],
+ name="start_time",
+ ),
+ ],
+ axis=1,
+ sort=sort,
+ )
+ result = concat([df1, df2], ignore_index=True, sort=sort)
+ if sort:
+ expected = expected[["end_time", "start_time"]]
+ else:
+ expected = expected[["start_time", "end_time"]]
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_append_missing_column_proper_upcast(self, sort):
+ df1 = DataFrame({"A": np.array([1, 2, 3, 4], dtype="i8")})
+ df2 = DataFrame({"B": np.array([True, False, True, False], dtype=bool)})
+
+ appended = concat([df1, df2], sort=sort)
+ assert appended["A"].dtype == "f8"
+ assert appended["B"].dtype == "O"
+
+ def test_append_empty_frame_to_series_with_dateutil_tz(self):
+ # GH 23682
+ date = Timestamp("2018-10-24 07:30:00", tz=dateutil.tz.tzutc())
+ ser = Series({"a": 1.0, "b": 2.0, "date": date})
+ df = DataFrame(columns=["c", "d"])
+ result_a = df._append_internal(ser, ignore_index=True)
+ expected = DataFrame(
+ [[np.nan, np.nan, 1.0, 2.0, date]], columns=["c", "d", "a", "b", "date"]
+ )
+ # These columns get cast to object after append
+ expected["c"] = expected["c"].astype(object)
+ expected["d"] = expected["d"].astype(object)
+ tm.assert_frame_equal(result_a, expected)
+
+ expected = DataFrame(
+ [[np.nan, np.nan, 1.0, 2.0, date]] * 2, columns=["c", "d", "a", "b", "date"]
+ )
+ expected["c"] = expected["c"].astype(object)
+ expected["d"] = expected["d"].astype(object)
+ result_b = result_a._append_internal(ser, ignore_index=True)
+ tm.assert_frame_equal(result_b, expected)
+
+ def test_append_empty_tz_frame_with_datetime64ns(self):
+ # https://github.com/pandas-dev/pandas/issues/35460
+ df = DataFrame(columns=["a"]).astype("datetime64[ns, UTC]")
+
+ # also test with typed value to append
+ df = DataFrame(columns=["a"]).astype("datetime64[ns, UTC]")
+ other = Series({"a": pd.NaT}, dtype="datetime64[ns]").to_frame().T
+ result = concat([df, other], ignore_index=True)
+ expected = DataFrame({"a": [pd.NaT]}, dtype=object)
+ tm.assert_frame_equal(result, expected)
+
+ # mismatched tz
+ other = Series({"a": pd.NaT}, dtype="datetime64[ns, US/Pacific]").to_frame().T
+ result = concat([df, other], ignore_index=True)
+ expected = DataFrame({"a": [pd.NaT]}).astype(object)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype_str", ["datetime64[ns, UTC]", "datetime64[ns]", "Int64", "int64"]
+ )
+ @pytest.mark.parametrize("val", [1, "NaT"])
+ def test_append_empty_frame_with_timedelta64ns_nat(self, dtype_str, val):
+ # https://github.com/pandas-dev/pandas/issues/35460
+ df = DataFrame(columns=["a"]).astype(dtype_str)
+
+ other = DataFrame({"a": [np.timedelta64(val, "ns")]})
+ result = concat([df, other])
+
+ expected = other.astype(object)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype_str", ["datetime64[ns, UTC]", "datetime64[ns]", "Int64", "int64"]
+ )
+ @pytest.mark.parametrize("val", [1, "NaT"])
+ def test_append_frame_with_timedelta64ns_nat(self, dtype_str, val):
+ # https://github.com/pandas-dev/pandas/issues/35460
+ df = DataFrame({"a": pd.array([1], dtype=dtype_str)})
+
+ other = DataFrame({"a": [np.timedelta64(val, "ns")]})
+ result = concat([df, other], ignore_index=True)
+
+ expected = DataFrame({"a": [df.iloc[0, 0], other.iloc[0, 0]]}, dtype=object)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_append_common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_append_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..d47fd3c743f956e5d29d01d8dbc6a9fdd768dd32
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_append_common.py
@@ -0,0 +1,715 @@
+import zoneinfo
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture(
+ params=list(
+ {
+ "bool": [True, False, True],
+ "int64": [1, 2, 3],
+ "float64": [1.1, np.nan, 3.3],
+ "category": Categorical(["X", "Y", "Z"]),
+ "object": ["a", "b", "c"],
+ "datetime64[s]": [
+ pd.Timestamp("2011-01-01").as_unit("s"),
+ pd.Timestamp("2011-01-02").as_unit("s"),
+ pd.Timestamp("2011-01-03").as_unit("s"),
+ ],
+ "datetime64[s, US/Eastern]": [
+ pd.Timestamp("2011-01-01", tz="US/Eastern").as_unit("s"),
+ pd.Timestamp("2011-01-02", tz="US/Eastern").as_unit("s"),
+ pd.Timestamp("2011-01-03", tz="US/Eastern").as_unit("s"),
+ ],
+ "timedelta64[us]": [
+ pd.Timedelta("1 days"),
+ pd.Timedelta("2 days"),
+ pd.Timedelta("3 days"),
+ ],
+ "period[M]": [
+ pd.Period("2011-01", freq="M"),
+ pd.Period("2011-02", freq="M"),
+ pd.Period("2011-03", freq="M"),
+ ],
+ }.items()
+ )
+)
+def item(request):
+ key, data = request.param
+ return key, data
+
+
+@pytest.fixture
+def item2(item):
+ return item
+
+
+class TestConcatAppendCommon:
+ """
+ Test common dtype coercion rules between concat and append.
+ """
+
+ def test_dtypes(self, item, index_or_series, using_infer_string):
+ # to confirm test case covers intended dtypes
+ typ, vals = item
+ obj = index_or_series(vals)
+ if typ == "object" and using_infer_string:
+ typ = "string"
+ if isinstance(obj, Index):
+ assert obj.dtype == typ
+ elif isinstance(obj, Series):
+ if typ.startswith("period"):
+ assert obj.dtype == "Period[M]"
+ else:
+ assert obj.dtype == typ
+
+ def test_concatlike_same_dtypes(self, item):
+ # GH 13660
+ typ1, vals1 = item
+
+ vals2 = vals1
+ vals3 = vals1
+
+ if typ1 == "category":
+ exp_data = Categorical(list(vals1) + list(vals2))
+ exp_data3 = Categorical(list(vals1) + list(vals2) + list(vals3))
+ else:
+ exp_data = vals1 + vals2
+ exp_data3 = vals1 + vals2 + vals3
+
+ # ----- Index ----- #
+
+ # index.append
+ res = Index(vals1).append(Index(vals2))
+ exp = Index(exp_data)
+ tm.assert_index_equal(res, exp)
+
+ # 3 elements
+ res = Index(vals1).append([Index(vals2), Index(vals3)])
+ exp = Index(exp_data3)
+ tm.assert_index_equal(res, exp)
+
+ # index.append name mismatch
+ i1 = Index(vals1, name="x")
+ i2 = Index(vals2, name="y")
+ res = i1.append(i2)
+ exp = Index(exp_data)
+ tm.assert_index_equal(res, exp)
+
+ # index.append name match
+ i1 = Index(vals1, name="x")
+ i2 = Index(vals2, name="x")
+ res = i1.append(i2)
+ exp = Index(exp_data, name="x")
+ tm.assert_index_equal(res, exp)
+
+ # cannot append non-index
+ with pytest.raises(TypeError, match="all inputs must be Index"):
+ Index(vals1).append(vals2)
+
+ with pytest.raises(TypeError, match="all inputs must be Index"):
+ Index(vals1).append([Index(vals2), vals3])
+
+ # ----- Series ----- #
+
+ # series.append
+ res = Series(vals1)._append_internal(Series(vals2), ignore_index=True)
+ exp = Series(exp_data)
+ tm.assert_series_equal(res, exp, check_index_type=True)
+
+ # concat
+ res = pd.concat([Series(vals1), Series(vals2)], ignore_index=True)
+ tm.assert_series_equal(res, exp, check_index_type=True)
+
+ # 3 elements
+ exp = Series(exp_data3)
+ res = pd.concat(
+ [Series(vals1), Series(vals2), Series(vals3)],
+ ignore_index=True,
+ )
+ tm.assert_series_equal(res, exp)
+
+ # name mismatch
+ s1 = Series(vals1, name="x")
+ s2 = Series(vals2, name="y")
+ res = s1._append_internal(s2, ignore_index=True)
+ exp = Series(exp_data)
+ tm.assert_series_equal(res, exp, check_index_type=True)
+
+ res = pd.concat([s1, s2], ignore_index=True)
+ tm.assert_series_equal(res, exp, check_index_type=True)
+
+ # name match
+ s1 = Series(vals1, name="x")
+ s2 = Series(vals2, name="x")
+ res = s1._append_internal(s2, ignore_index=True)
+ exp = Series(exp_data, name="x")
+ tm.assert_series_equal(res, exp, check_index_type=True)
+
+ res = pd.concat([s1, s2], ignore_index=True)
+ tm.assert_series_equal(res, exp, check_index_type=True)
+
+ # cannot append non-index
+ msg = (
+ r"cannot concatenate object of type '.+'; "
+ "only Series and DataFrame objs are valid"
+ )
+ with pytest.raises(TypeError, match=msg):
+ pd.concat([Series(vals1), vals2])
+
+ with pytest.raises(TypeError, match=msg):
+ pd.concat([Series(vals1), Series(vals2), vals3])
+
+ def test_concatlike_dtypes_coercion(self, item, item2, request):
+ # GH 13660
+ typ1, vals1 = item
+ typ2, vals2 = item2
+
+ vals3 = vals2
+
+ # basically infer
+ exp_index_dtype = None
+ exp_series_dtype = None
+
+ if typ1 == typ2:
+ pytest.skip("same dtype is tested in test_concatlike_same_dtypes")
+ elif typ1 == "category" or typ2 == "category":
+ pytest.skip("categorical type tested elsewhere")
+
+ # specify expected dtype
+ if typ1 == "bool" and typ2 in ("int64", "float64"):
+ # series coerces to numeric based on numpy rule
+ # index doesn't because bool is object dtype
+ exp_series_dtype = typ2
+ mark = pytest.mark.xfail(reason="GH#39187 casting to object")
+ request.applymarker(mark)
+ elif typ2 == "bool" and typ1 in ("int64", "float64"):
+ exp_series_dtype = typ1
+ mark = pytest.mark.xfail(reason="GH#39187 casting to object")
+ request.applymarker(mark)
+ elif typ1 in {"datetime64[ns, US/Eastern]", "timedelta64[ns]"} or typ2 in {
+ "datetime64[ns, US/Eastern]",
+ "timedelta64[ns]",
+ }:
+ exp_index_dtype = object
+ exp_series_dtype = object
+
+ exp_data = vals1 + vals2
+ exp_data3 = vals1 + vals2 + vals3
+
+ # ----- Index ----- #
+
+ # index.append
+ # GH#39817
+ res = Index(vals1).append(Index(vals2))
+ exp = Index(exp_data, dtype=exp_index_dtype)
+ tm.assert_index_equal(res, exp)
+
+ # 3 elements
+ res = Index(vals1).append([Index(vals2), Index(vals3)])
+ exp = Index(exp_data3, dtype=exp_index_dtype)
+ tm.assert_index_equal(res, exp)
+
+ # ----- Series ----- #
+
+ # series._append
+ # GH#39817
+ res = Series(vals1)._append_internal(Series(vals2), ignore_index=True)
+ exp = Series(exp_data, dtype=exp_series_dtype)
+ tm.assert_series_equal(res, exp, check_index_type=True)
+
+ # concat
+ # GH#39817
+ res = pd.concat([Series(vals1), Series(vals2)], ignore_index=True)
+ tm.assert_series_equal(res, exp, check_index_type=True)
+
+ # 3 elements
+ # GH#39817
+ exp = Series(exp_data3, dtype=exp_series_dtype)
+ res = pd.concat(
+ [Series(vals1), Series(vals2), Series(vals3)],
+ ignore_index=True,
+ )
+ tm.assert_series_equal(res, exp)
+
+ def test_concatlike_common_coerce_to_pandas_object(self):
+ # GH 13626
+ # result must be Timestamp/Timedelta, not datetime.datetime/timedelta
+ dti = pd.DatetimeIndex(["2011-01-01", "2011-01-02"])
+ tdi = pd.TimedeltaIndex(["1 days", "2 days"])
+
+ exp = Index(
+ [
+ pd.Timestamp("2011-01-01"),
+ pd.Timestamp("2011-01-02"),
+ pd.Timedelta("1 days"),
+ pd.Timedelta("2 days"),
+ ]
+ )
+
+ res = dti.append(tdi)
+ tm.assert_index_equal(res, exp)
+ assert isinstance(res[0], pd.Timestamp)
+ assert isinstance(res[-1], pd.Timedelta)
+
+ dts = Series(dti)
+ tds = Series(tdi)
+ res = dts._append_internal(tds)
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+ assert isinstance(res.iloc[0], pd.Timestamp)
+ assert isinstance(res.iloc[-1], pd.Timedelta)
+
+ res = pd.concat([dts, tds])
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+ assert isinstance(res.iloc[0], pd.Timestamp)
+ assert isinstance(res.iloc[-1], pd.Timedelta)
+
+ def test_concatlike_datetimetz(self, tz_aware_fixture):
+ tz = tz_aware_fixture
+ # GH 7795
+ dti1 = pd.DatetimeIndex(["2011-01-01", "2011-01-02"], tz=tz)
+ dti2 = pd.DatetimeIndex(["2012-01-01", "2012-01-02"], tz=tz)
+
+ exp = pd.DatetimeIndex(
+ ["2011-01-01", "2011-01-02", "2012-01-01", "2012-01-02"], tz=tz
+ )
+
+ res = dti1.append(dti2)
+ tm.assert_index_equal(res, exp)
+
+ dts1 = Series(dti1)
+ dts2 = Series(dti2)
+ res = dts1._append_internal(dts2)
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ res = pd.concat([dts1, dts2])
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ @pytest.mark.parametrize("tz", ["UTC", "US/Eastern", "Asia/Tokyo", "EST5EDT"])
+ def test_concatlike_datetimetz_short(self, tz):
+ # GH#7795
+ ix1 = pd.date_range(
+ start="2014-07-15", end="2014-07-17", freq="D", tz=tz, unit="ns"
+ )
+ ix2 = pd.DatetimeIndex(["2014-07-11", "2014-07-21"], tz=tz)
+ df1 = DataFrame(0, index=ix1, columns=["A", "B"])
+ df2 = DataFrame(0, index=ix2, columns=["A", "B"])
+
+ exp_idx = pd.DatetimeIndex(
+ ["2014-07-15", "2014-07-16", "2014-07-17", "2014-07-11", "2014-07-21"],
+ tz=tz,
+ ).as_unit("ns")
+ exp = DataFrame(0, index=exp_idx, columns=["A", "B"])
+
+ tm.assert_frame_equal(pd.concat([df1, df2]), exp)
+
+ def test_concatlike_datetimetz_to_object(self, tz_aware_fixture):
+ tz = tz_aware_fixture
+ # GH 13660
+
+ # different tz coerces to object
+ dti1 = pd.DatetimeIndex(["2011-01-01", "2011-01-02"], tz=tz)
+ dti2 = pd.DatetimeIndex(["2012-01-01", "2012-01-02"])
+
+ exp = Index(
+ [
+ pd.Timestamp("2011-01-01", tz=tz),
+ pd.Timestamp("2011-01-02", tz=tz),
+ pd.Timestamp("2012-01-01"),
+ pd.Timestamp("2012-01-02"),
+ ],
+ dtype=object,
+ )
+
+ res = dti1.append(dti2)
+ tm.assert_index_equal(res, exp)
+
+ dts1 = Series(dti1)
+ dts2 = Series(dti2)
+ res = dts1._append_internal(dts2)
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ res = pd.concat([dts1, dts2])
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ # different tz
+ tz_diff = zoneinfo.ZoneInfo("US/Hawaii")
+ dti3 = pd.DatetimeIndex(["2012-01-01", "2012-01-02"], tz=tz_diff)
+
+ exp = Index(
+ [
+ pd.Timestamp("2011-01-01", tz=tz),
+ pd.Timestamp("2011-01-02", tz=tz),
+ pd.Timestamp("2012-01-01", tz=tz_diff),
+ pd.Timestamp("2012-01-02", tz=tz_diff),
+ ],
+ dtype=object,
+ )
+
+ res = dti1.append(dti3)
+ tm.assert_index_equal(res, exp)
+
+ dts1 = Series(dti1)
+ dts3 = Series(dti3)
+ res = dts1._append_internal(dts3)
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ res = pd.concat([dts1, dts3])
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ def test_concatlike_common_period(self):
+ # GH 13660
+ pi1 = pd.PeriodIndex(["2011-01", "2011-02"], freq="M")
+ pi2 = pd.PeriodIndex(["2012-01", "2012-02"], freq="M")
+
+ exp = pd.PeriodIndex(["2011-01", "2011-02", "2012-01", "2012-02"], freq="M")
+
+ res = pi1.append(pi2)
+ tm.assert_index_equal(res, exp)
+
+ ps1 = Series(pi1)
+ ps2 = Series(pi2)
+ res = ps1._append_internal(ps2)
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ res = pd.concat([ps1, ps2])
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ def test_concatlike_common_period_diff_freq_to_object(self):
+ # GH 13221
+ pi1 = pd.PeriodIndex(["2011-01", "2011-02"], freq="M")
+ pi2 = pd.PeriodIndex(["2012-01-01", "2012-02-01"], freq="D")
+
+ exp = Index(
+ [
+ pd.Period("2011-01", freq="M"),
+ pd.Period("2011-02", freq="M"),
+ pd.Period("2012-01-01", freq="D"),
+ pd.Period("2012-02-01", freq="D"),
+ ],
+ dtype=object,
+ )
+
+ res = pi1.append(pi2)
+ tm.assert_index_equal(res, exp)
+
+ ps1 = Series(pi1)
+ ps2 = Series(pi2)
+ res = ps1._append_internal(ps2)
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ res = pd.concat([ps1, ps2])
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ def test_concatlike_common_period_mixed_dt_to_object(self):
+ # GH 13221
+ # different datetimelike
+ pi1 = pd.PeriodIndex(["2011-01", "2011-02"], freq="M")
+ tdi = pd.TimedeltaIndex(["1 days", "2 days"])
+ exp = Index(
+ [
+ pd.Period("2011-01", freq="M"),
+ pd.Period("2011-02", freq="M"),
+ pd.Timedelta("1 days"),
+ pd.Timedelta("2 days"),
+ ],
+ dtype=object,
+ )
+
+ res = pi1.append(tdi)
+ tm.assert_index_equal(res, exp)
+
+ ps1 = Series(pi1)
+ tds = Series(tdi)
+ res = ps1._append_internal(tds)
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ res = pd.concat([ps1, tds])
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ # inverse
+ exp = Index(
+ [
+ pd.Timedelta("1 days"),
+ pd.Timedelta("2 days"),
+ pd.Period("2011-01", freq="M"),
+ pd.Period("2011-02", freq="M"),
+ ],
+ dtype=object,
+ )
+
+ res = tdi.append(pi1)
+ tm.assert_index_equal(res, exp)
+
+ ps1 = Series(pi1)
+ tds = Series(tdi)
+ res = tds._append_internal(ps1)
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ res = pd.concat([tds, ps1])
+ tm.assert_series_equal(res, Series(exp, index=[0, 1, 0, 1]))
+
+ def test_concat_categorical(self):
+ # GH 13524
+
+ # same categories -> category
+ s1 = Series([1, 2, np.nan], dtype="category")
+ s2 = Series([2, 1, 2], dtype="category")
+
+ exp = Series([1, 2, np.nan, 2, 1, 2], dtype="category")
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), exp)
+
+ # partially different categories => not-category
+ s1 = Series([3, 2], dtype="category")
+ s2 = Series([2, 1], dtype="category")
+
+ exp = Series([3, 2, 2, 1])
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), exp)
+
+ # completely different categories (same dtype) => not-category
+ s1 = Series([10, 11, np.nan], dtype="category")
+ s2 = Series([np.nan, 1, 3, 2], dtype="category")
+
+ exp = Series([10, 11, np.nan, np.nan, 1, 3, 2], dtype=np.float64)
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), exp)
+
+ def test_union_categorical_same_categories_different_order(self):
+ # https://github.com/pandas-dev/pandas/issues/19096
+ a = Series(Categorical(["a", "b", "c"], categories=["a", "b", "c"]))
+ b = Series(Categorical(["a", "b", "c"], categories=["b", "a", "c"]))
+ result = pd.concat([a, b], ignore_index=True)
+ expected = Series(
+ Categorical(["a", "b", "c", "a", "b", "c"], categories=["a", "b", "c"])
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_categorical_coercion(self):
+ # GH 13524
+
+ # category + not-category => not-category
+ s1 = Series([1, 2, np.nan], dtype="category")
+ s2 = Series([2, 1, 2])
+
+ exp = Series([1, 2, np.nan, 2, 1, 2], dtype=np.float64)
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), exp)
+
+ # result shouldn't be affected by 1st elem dtype
+ exp = Series([2, 1, 2, 1, 2, np.nan], dtype=np.float64)
+ tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
+ tm.assert_series_equal(s2._append_internal(s1, ignore_index=True), exp)
+
+ # all values are not in category => not-category
+ s1 = Series([3, 2], dtype="category")
+ s2 = Series([2, 1])
+
+ exp = Series([3, 2, 2, 1])
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), exp)
+
+ exp = Series([2, 1, 3, 2])
+ tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
+ tm.assert_series_equal(s2._append_internal(s1, ignore_index=True), exp)
+
+ # completely different categories => not-category
+ s1 = Series([10, 11, np.nan], dtype="category")
+ s2 = Series([1, 3, 2])
+
+ exp = Series([10, 11, np.nan, 1, 3, 2], dtype=np.float64)
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), exp)
+
+ exp = Series([1, 3, 2, 10, 11, np.nan], dtype=np.float64)
+ tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
+ tm.assert_series_equal(s2._append_internal(s1, ignore_index=True), exp)
+
+ # different dtype => not-category
+ s1 = Series([10, 11, np.nan], dtype="category")
+ s2 = Series(["a", "b", "c"])
+
+ exp = Series([10, 11, np.nan, "a", "b", "c"])
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), exp)
+
+ exp = Series(["a", "b", "c", 10, 11, np.nan])
+ tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
+ tm.assert_series_equal(s2._append_internal(s1, ignore_index=True), exp)
+
+ # if normal series only contains NaN-likes => not-category
+ s1 = Series([10, 11], dtype="category")
+ s2 = Series([np.nan, np.nan, np.nan])
+
+ exp = Series([10, 11, np.nan, np.nan, np.nan])
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), exp)
+
+ exp = Series([np.nan, np.nan, np.nan, 10, 11])
+ tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
+ tm.assert_series_equal(s2._append_internal(s1, ignore_index=True), exp)
+
+ def test_concat_categorical_3elem_coercion(self):
+ # GH 13524
+
+ # mixed dtypes => not-category
+ s1 = Series([1, 2, np.nan], dtype="category")
+ s2 = Series([2, 1, 2], dtype="category")
+ s3 = Series([1, 2, 1, 2, np.nan])
+
+ exp = Series([1, 2, np.nan, 2, 1, 2, 1, 2, 1, 2, np.nan], dtype="float")
+ tm.assert_series_equal(pd.concat([s1, s2, s3], ignore_index=True), exp)
+
+ exp = Series([1, 2, 1, 2, np.nan, 1, 2, np.nan, 2, 1, 2], dtype="float")
+ tm.assert_series_equal(pd.concat([s3, s1, s2], ignore_index=True), exp)
+
+ # values are all in either category => not-category
+ s1 = Series([4, 5, 6], dtype="category")
+ s2 = Series([1, 2, 3], dtype="category")
+ s3 = Series([1, 3, 4])
+
+ exp = Series([4, 5, 6, 1, 2, 3, 1, 3, 4])
+ tm.assert_series_equal(pd.concat([s1, s2, s3], ignore_index=True), exp)
+
+ exp = Series([1, 3, 4, 4, 5, 6, 1, 2, 3])
+ tm.assert_series_equal(pd.concat([s3, s1, s2], ignore_index=True), exp)
+
+ # values are all in either category => not-category
+ s1 = Series([4, 5, 6], dtype="category")
+ s2 = Series([1, 2, 3], dtype="category")
+ s3 = Series([10, 11, 12])
+
+ exp = Series([4, 5, 6, 1, 2, 3, 10, 11, 12])
+ tm.assert_series_equal(pd.concat([s1, s2, s3], ignore_index=True), exp)
+
+ exp = Series([10, 11, 12, 4, 5, 6, 1, 2, 3])
+ tm.assert_series_equal(pd.concat([s3, s1, s2], ignore_index=True), exp)
+
+ def test_concat_categorical_multi_coercion(self):
+ # GH 13524
+
+ s1 = Series([1, 3], dtype="category")
+ s2 = Series([3, 4], dtype="category")
+ s3 = Series([2, 3])
+ s4 = Series([2, 2], dtype="category")
+ s5 = Series([1, np.nan])
+ s6 = Series([1, 3, 2], dtype="category")
+
+ # mixed dtype, values are all in categories => not-category
+ exp = Series([1, 3, 3, 4, 2, 3, 2, 2, 1, np.nan, 1, 3, 2])
+ res = pd.concat([s1, s2, s3, s4, s5, s6], ignore_index=True)
+ tm.assert_series_equal(res, exp)
+
+ exp = Series([1, 3, 2, 1, np.nan, 2, 2, 2, 3, 3, 4, 1, 3])
+ res = pd.concat([s6, s5, s4, s3, s2, s1], ignore_index=True)
+ tm.assert_series_equal(res, exp)
+
+ def test_concat_categorical_ordered(self):
+ # GH 13524
+
+ s1 = Series(Categorical([1, 2, np.nan], ordered=True))
+ s2 = Series(Categorical([2, 1, 2], ordered=True))
+
+ exp = Series(Categorical([1, 2, np.nan, 2, 1, 2], ordered=True))
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+
+ exp = Series(Categorical([1, 2, np.nan, 2, 1, 2, 1, 2, np.nan], ordered=True))
+ tm.assert_series_equal(pd.concat([s1, s2, s1], ignore_index=True), exp)
+
+ def test_concat_categorical_coercion_nan(self):
+ # GH 13524
+
+ # some edge cases
+ # category + not-category => not category
+ s1 = Series(np.array([np.nan, np.nan], dtype=np.float64), dtype="category")
+ s2 = Series([np.nan, 1])
+
+ exp = Series([np.nan, np.nan, np.nan, 1])
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+
+ s1 = Series([1, np.nan], dtype="category")
+ s2 = Series([np.nan, np.nan])
+
+ exp = Series([1, np.nan, np.nan, np.nan], dtype="float")
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+
+ # mixed dtype, all nan-likes => not-category
+ s1 = Series([np.nan, np.nan], dtype="category")
+ s2 = Series([np.nan, np.nan])
+
+ exp = Series([np.nan, np.nan, np.nan, np.nan])
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
+
+ # all category nan-likes => category
+ s1 = Series([np.nan, np.nan], dtype="category")
+ s2 = Series([np.nan, np.nan], dtype="category")
+
+ exp = Series([np.nan, np.nan, np.nan, np.nan], dtype="category")
+
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+
+ def test_concat_categorical_empty(self):
+ # GH 13524
+
+ s1 = Series([], dtype="category")
+ s2 = Series([1, 2], dtype="category")
+ exp = s2.astype(object)
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), exp)
+
+ tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
+ tm.assert_series_equal(s2._append_internal(s1, ignore_index=True), exp)
+
+ s1 = Series([], dtype="category")
+ s2 = Series([], dtype="category")
+
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), s2)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), s2)
+
+ s1 = Series([], dtype="category")
+ s2 = Series([], dtype="object")
+
+ # different dtype => not-category
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), s2)
+ tm.assert_series_equal(s1._append_internal(s2, ignore_index=True), s2)
+ tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), s2)
+ tm.assert_series_equal(s2._append_internal(s1, ignore_index=True), s2)
+
+ s1 = Series([], dtype="category")
+ s2 = Series([np.nan, np.nan])
+
+ exp = Series([np.nan, np.nan], dtype=object)
+ tm.assert_series_equal(pd.concat([s1, s2], ignore_index=True), exp)
+
+ tm.assert_series_equal(pd.concat([s2, s1], ignore_index=True), exp)
+
+ def test_categorical_concat_append(self):
+ cat = Categorical(["a", "b"], categories=["a", "b"])
+ vals = [1, 2]
+ df = DataFrame({"cats": cat, "vals": vals})
+ cat2 = Categorical(["a", "b", "a", "b"], categories=["a", "b"])
+ vals2 = [1, 2, 1, 2]
+ exp = DataFrame({"cats": cat2, "vals": vals2}, index=Index([0, 1, 0, 1]))
+
+ tm.assert_frame_equal(pd.concat([df, df]), exp)
+
+ # GH 13524 can concat different categories
+ cat3 = Categorical(["a", "b"], categories=["a", "b", "c"])
+ vals3 = [1, 2]
+ df_different_categories = DataFrame({"cats": cat3, "vals": vals3})
+
+ res = pd.concat([df, df_different_categories], ignore_index=True)
+ exp = DataFrame({"cats": list("abab"), "vals": [1, 2, 1, 2]})
+ tm.assert_frame_equal(res, exp)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_categorical.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_categorical.py
new file mode 100644
index 0000000000000000000000000000000000000000..8cd02b49892027e4fef22cc98f57aef169657047
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_categorical.py
@@ -0,0 +1,272 @@
+from datetime import datetime
+
+import numpy as np
+
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.dtypes import CategoricalDtype
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestCategoricalConcat:
+ def test_categorical_concat(self, sort):
+ # See GH 10177
+ df1 = DataFrame(
+ np.arange(18, dtype="int64").reshape(6, 3), columns=["a", "b", "c"]
+ )
+
+ df2 = DataFrame(np.arange(14, dtype="int64").reshape(7, 2), columns=["a", "c"])
+
+ cat_values = ["one", "one", "two", "one", "two", "two", "one"]
+ df2["h"] = Series(Categorical(cat_values))
+
+ res = pd.concat((df1, df2), axis=0, ignore_index=True, sort=sort)
+ exp = DataFrame(
+ {
+ "a": [0, 3, 6, 9, 12, 15, 0, 2, 4, 6, 8, 10, 12],
+ "b": [
+ 1,
+ 4,
+ 7,
+ 10,
+ 13,
+ 16,
+ np.nan,
+ np.nan,
+ np.nan,
+ np.nan,
+ np.nan,
+ np.nan,
+ np.nan,
+ ],
+ "c": [2, 5, 8, 11, 14, 17, 1, 3, 5, 7, 9, 11, 13],
+ "h": [None] * 6 + cat_values,
+ }
+ )
+ exp["h"] = exp["h"].astype(df2["h"].dtype)
+ tm.assert_frame_equal(res, exp)
+
+ def test_categorical_concat_dtypes(self, using_infer_string):
+ # GH8143
+ index = ["cat", "obj", "num"]
+ cat = Categorical(["a", "b", "c"])
+ obj = Series(["a", "b", "c"])
+ num = Series([1, 2, 3])
+ df = pd.concat([Series(cat), obj, num], axis=1, keys=index)
+
+ result = df.dtypes == (object if not using_infer_string else "str")
+ expected = Series([False, True, False], index=index)
+ tm.assert_series_equal(result, expected)
+
+ result = df.dtypes == "int64"
+ expected = Series([False, False, True], index=index)
+ tm.assert_series_equal(result, expected)
+
+ result = df.dtypes == "category"
+ expected = Series([True, False, False], index=index)
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_categoricalindex(self):
+ # GH 16111, categories that aren't lexsorted
+ categories = [9, 0, 1, 2, 3]
+
+ a = Series(1, index=pd.CategoricalIndex([9, 0], categories=categories))
+ b = Series(2, index=pd.CategoricalIndex([0, 1], categories=categories))
+ c = Series(3, index=pd.CategoricalIndex([1, 2], categories=categories))
+
+ result = pd.concat([a, b, c], axis=1)
+
+ exp_idx = pd.CategoricalIndex([9, 0, 1, 2], categories=categories)
+ exp = DataFrame(
+ {
+ 0: [1, 1, np.nan, np.nan],
+ 1: [np.nan, 2, 2, np.nan],
+ 2: [np.nan, np.nan, 3, 3],
+ },
+ columns=[0, 1, 2],
+ index=exp_idx,
+ )
+ tm.assert_frame_equal(result, exp)
+
+ def test_categorical_concat_preserve(self):
+ # GH 8641 series concat not preserving category dtype
+ # GH 13524 can concat different categories
+ s = Series(list("abc"), dtype="category")
+ s2 = Series(list("abd"), dtype="category")
+
+ exp = Series(list("abcabd"))
+ res = pd.concat([s, s2], ignore_index=True)
+ tm.assert_series_equal(res, exp)
+
+ exp = Series(list("abcabc"), dtype="category")
+ res = pd.concat([s, s], ignore_index=True)
+ tm.assert_series_equal(res, exp)
+
+ exp = Series(list("abcabc"), index=[0, 1, 2, 0, 1, 2], dtype="category")
+ res = pd.concat([s, s])
+ tm.assert_series_equal(res, exp)
+
+ a = Series(np.arange(6, dtype="int64"))
+ b = Series(list("aabbca"))
+
+ df2 = DataFrame({"A": a, "B": b.astype(CategoricalDtype(list("cab")))})
+ res = pd.concat([df2, df2])
+ exp = DataFrame(
+ {
+ "A": pd.concat([a, a]),
+ "B": pd.concat([b, b]).astype(CategoricalDtype(list("cab"))),
+ }
+ )
+ tm.assert_frame_equal(res, exp)
+
+ def test_categorical_index_preserver(self):
+ a = Series(np.arange(6, dtype="int64"))
+ b = Series(list("aabbca"))
+
+ df2 = DataFrame(
+ {"A": a, "B": b.astype(CategoricalDtype(list("cab")))}
+ ).set_index("B")
+ result = pd.concat([df2, df2])
+ expected = DataFrame(
+ {
+ "A": pd.concat([a, a]),
+ "B": pd.concat([b, b]).astype(CategoricalDtype(list("cab"))),
+ }
+ ).set_index("B")
+ tm.assert_frame_equal(result, expected)
+
+ # wrong categories -> uses concat_compat, which casts to object
+ msg = "Constructing a Categorical with a dtype and values containing"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ df3 = DataFrame(
+ {"A": a, "B": Categorical(b, categories=list("abe"))}
+ ).set_index("B")
+ result = pd.concat([df2, df3])
+ expected = pd.concat(
+ [
+ df2.set_axis(df2.index.astype(object), axis=0),
+ df3.set_axis(df3.index.astype(object), axis=0),
+ ]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_categorical_tz(self):
+ # GH-23816
+ a = Series(pd.date_range("2017-01-01", periods=2, tz="US/Pacific"))
+ b = Series(["a", "b"], dtype="category")
+ result = pd.concat([a, b], ignore_index=True)
+ expected = Series(
+ [
+ pd.Timestamp("2017-01-01", tz="US/Pacific"),
+ pd.Timestamp("2017-01-02", tz="US/Pacific"),
+ "a",
+ "b",
+ ]
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_categorical_datetime(self):
+ # GH-39443
+ df1 = DataFrame(
+ {"x": Series(datetime(2021, 1, 1), index=[0], dtype="category")}
+ )
+ df2 = DataFrame(
+ {"x": Series(datetime(2021, 1, 2), index=[1], dtype="category")}
+ )
+
+ result = pd.concat([df1, df2])
+ expected = DataFrame(
+ {"x": Series([datetime(2021, 1, 1), datetime(2021, 1, 2)])}
+ )
+
+ tm.assert_equal(result, expected)
+
+ def test_concat_categorical_unchanged(self):
+ # GH-12007
+ # test fix for when concat on categorical and float
+ # coerces dtype categorical -> float
+ df = DataFrame(Series(["a", "b", "c"], dtype="category", name="A"))
+ ser = Series([0, 1, 2], index=[0, 1, 3], name="B")
+ result = pd.concat([df, ser], axis=1)
+ expected = DataFrame(
+ {
+ "A": Series(["a", "b", "c", np.nan], dtype="category"),
+ "B": Series([0, 1, np.nan, 2], dtype="float"),
+ }
+ )
+ tm.assert_equal(result, expected)
+
+ def test_categorical_concat_gh7864(self):
+ # GH 7864
+ # make sure ordering is preserved
+ df = DataFrame({"id": [1, 2, 3, 4, 5, 6], "raw_grade": list("abbaae")})
+ df["grade"] = Categorical(df["raw_grade"])
+ df["grade"].cat.set_categories(["e", "a", "b"])
+
+ df1 = df[0:3]
+ df2 = df[3:]
+
+ tm.assert_index_equal(df["grade"].cat.categories, df1["grade"].cat.categories)
+ tm.assert_index_equal(df["grade"].cat.categories, df2["grade"].cat.categories)
+
+ dfx = pd.concat([df1, df2])
+ tm.assert_index_equal(df["grade"].cat.categories, dfx["grade"].cat.categories)
+
+ def test_categorical_index_upcast(self):
+ # GH 17629
+ # test upcasting to object when concatenating on categorical indexes
+ # with non-identical categories
+
+ a = DataFrame({"foo": [1, 2]}, index=Categorical(["foo", "bar"]))
+ b = DataFrame({"foo": [4, 3]}, index=Categorical(["baz", "bar"]))
+
+ res = pd.concat([a, b])
+ exp = DataFrame({"foo": [1, 2, 4, 3]}, index=["foo", "bar", "baz", "bar"])
+
+ tm.assert_equal(res, exp)
+
+ a = Series([1, 2], index=Categorical(["foo", "bar"]))
+ b = Series([4, 3], index=Categorical(["baz", "bar"]))
+
+ res = pd.concat([a, b])
+ exp = Series([1, 2, 4, 3], index=["foo", "bar", "baz", "bar"])
+
+ tm.assert_equal(res, exp)
+
+ def test_categorical_missing_from_one_frame(self):
+ # GH 25412
+ df1 = DataFrame({"f1": [1, 2, 3]})
+ df2 = DataFrame({"f1": [2, 3, 1], "f2": Series([4, 4, 4]).astype("category")})
+ result = pd.concat([df1, df2], sort=True)
+ dtype = CategoricalDtype([4])
+ expected = DataFrame(
+ {
+ "f1": [1, 2, 3, 2, 3, 1],
+ "f2": Categorical.from_codes([-1, -1, -1, 0, 0, 0], dtype=dtype),
+ },
+ index=[0, 1, 2, 0, 1, 2],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_categorical_same_categories_different_order(self):
+ # https://github.com/pandas-dev/pandas/issues/24845
+
+ c1 = pd.CategoricalIndex(["a", "a"], categories=["a", "b"], ordered=False)
+ c2 = pd.CategoricalIndex(["b", "b"], categories=["b", "a"], ordered=False)
+ c3 = pd.CategoricalIndex(
+ ["a", "a", "b", "b"], categories=["a", "b"], ordered=False
+ )
+
+ df1 = DataFrame({"A": [1, 2]}, index=c1)
+ df2 = DataFrame({"A": [3, 4]}, index=c2)
+
+ result = pd.concat((df1, df2))
+ expected = DataFrame({"A": [1, 2, 3, 4]}, index=c3)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_concat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_concat.py
new file mode 100644
index 0000000000000000000000000000000000000000..50a290a883e303183055139b0d59d028133d1c7f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_concat.py
@@ -0,0 +1,1008 @@
+from collections import (
+ abc,
+ deque,
+)
+from collections.abc import Iterator
+from datetime import datetime
+from decimal import Decimal
+import itertools
+
+import numpy as np
+import pytest
+
+from pandas.errors import (
+ InvalidIndexError,
+ Pandas4Warning,
+)
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ PeriodIndex,
+ RangeIndex,
+ Series,
+ concat,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import SparseArray
+from pandas.tests.extension.decimal import to_decimal
+
+
+class TestConcatenate:
+ def test_append_concat(self):
+ # GH#1815
+ d1 = date_range("12/31/1990", "12/31/1999", freq="YE-DEC")
+ d2 = date_range("12/31/2000", "12/31/2009", freq="YE-DEC")
+
+ s1 = Series(np.random.default_rng(2).standard_normal(10), d1)
+ s2 = Series(np.random.default_rng(2).standard_normal(10), d2)
+
+ s1 = s1.to_period()
+ s2 = s2.to_period()
+
+ # drops index
+ result = concat([s1, s2])
+ assert isinstance(result.index, PeriodIndex)
+ assert result.index[0] == s1.index[0]
+
+ def test_concat_copy(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((4, 3)))
+ df2 = DataFrame(np.random.default_rng(2).integers(0, 10, size=4).reshape(4, 1))
+ df3 = DataFrame({5: "foo"}, index=range(4))
+
+ result = concat([df, df2, df3], axis=1)
+
+ for block in result._mgr.blocks:
+ arr = block.values
+ if arr.dtype.kind == "f":
+ assert arr.base is df._mgr.blocks[0].values.base
+ elif arr.dtype.kind in ["i", "u"]:
+ assert arr.base is df2._mgr.blocks[0].values.base
+ elif arr.dtype == object:
+ assert arr.base is not None
+ elif arr.dtype == "string":
+ tm.shares_memory(arr, df3._mgr.blocks[0].values)
+
+ # Float block was consolidated.
+ df4 = DataFrame(np.random.default_rng(2).standard_normal((4, 1)))
+ result = concat([df, df2, df3, df4], axis=1)
+ for blocks in result._mgr.blocks:
+ arr = blocks.values
+ if arr.dtype.kind == "f":
+ # this is a view on some array in either df or df4
+ assert any(
+ np.shares_memory(arr, block.values)
+ for block in itertools.chain(df._mgr.blocks, df4._mgr.blocks)
+ )
+ elif arr.dtype.kind in ["i", "u"]:
+ assert arr.base is df2._mgr.blocks[0].values.base
+ elif arr.dtype == object:
+ # this is a view on df3
+ assert any(
+ np.shares_memory(arr, block.values) for block in df3._mgr.blocks
+ )
+
+ def test_concat_with_group_keys(self):
+ # axis=0
+ df = DataFrame(np.random.default_rng(2).standard_normal((3, 4)))
+ df2 = DataFrame(np.random.default_rng(2).standard_normal((4, 4)))
+
+ result = concat([df, df2], keys=[0, 1])
+ exp_index = MultiIndex.from_arrays(
+ [[0, 0, 0, 1, 1, 1, 1], [0, 1, 2, 0, 1, 2, 3]]
+ )
+ expected = DataFrame(np.r_[df.values, df2.values], index=exp_index)
+ tm.assert_frame_equal(result, expected)
+
+ result = concat([df, df], keys=[0, 1])
+ exp_index2 = MultiIndex.from_arrays([[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]])
+ expected = DataFrame(np.r_[df.values, df.values], index=exp_index2)
+ tm.assert_frame_equal(result, expected)
+
+ # axis=1
+ df = DataFrame(np.random.default_rng(2).standard_normal((4, 3)))
+ df2 = DataFrame(np.random.default_rng(2).standard_normal((4, 4)))
+
+ result = concat([df, df2], keys=[0, 1], axis=1)
+ expected = DataFrame(np.c_[df.values, df2.values], columns=exp_index)
+ tm.assert_frame_equal(result, expected)
+
+ result = concat([df, df], keys=[0, 1], axis=1)
+ expected = DataFrame(np.c_[df.values, df.values], columns=exp_index2)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_keys_specific_levels(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ pieces = [df.iloc[:, [0, 1]], df.iloc[:, [2]], df.iloc[:, [3]]]
+ level = ["three", "two", "one", "zero"]
+ result = concat(
+ pieces,
+ axis=1,
+ keys=["one", "two", "three"],
+ levels=[level],
+ names=["group_key"],
+ )
+
+ tm.assert_index_equal(result.columns.levels[0], Index(level, name="group_key"))
+ tm.assert_index_equal(result.columns.levels[1], Index([0, 1, 2, 3]))
+
+ assert result.columns.names == ["group_key", None]
+
+ @pytest.mark.parametrize("mapping", ["mapping", "dict"])
+ def test_concat_mapping(self, mapping, non_dict_mapping_subclass):
+ constructor = dict if mapping == "dict" else non_dict_mapping_subclass
+ frames = constructor(
+ {
+ "foo": DataFrame(np.random.default_rng(2).standard_normal((4, 3))),
+ "bar": DataFrame(np.random.default_rng(2).standard_normal((4, 3))),
+ "baz": DataFrame(np.random.default_rng(2).standard_normal((4, 3))),
+ "qux": DataFrame(np.random.default_rng(2).standard_normal((4, 3))),
+ }
+ )
+
+ sorted_keys = list(frames.keys())
+
+ result = concat(frames)
+ expected = concat([frames[k] for k in sorted_keys], keys=sorted_keys)
+ tm.assert_frame_equal(result, expected)
+
+ result = concat(frames, axis=1)
+ expected = concat([frames[k] for k in sorted_keys], keys=sorted_keys, axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ keys = ["baz", "foo", "bar"]
+ result = concat(frames, keys=keys)
+ expected = concat([frames[k] for k in keys], keys=keys)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_keys_and_levels(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((1, 3)))
+ df2 = DataFrame(np.random.default_rng(2).standard_normal((1, 4)))
+
+ levels = [["foo", "baz"], ["one", "two"]]
+ names = ["first", "second"]
+ result = concat(
+ [df, df2, df, df2],
+ keys=[("foo", "one"), ("foo", "two"), ("baz", "one"), ("baz", "two")],
+ levels=levels,
+ names=names,
+ )
+ expected = concat([df, df2, df, df2])
+ exp_index = MultiIndex(
+ levels=[*levels, [0]],
+ codes=[[0, 0, 1, 1], [0, 1, 0, 1], [0, 0, 0, 0]],
+ names=[*names, None],
+ )
+ expected.index = exp_index
+
+ tm.assert_frame_equal(result, expected)
+
+ # no names
+ result = concat(
+ [df, df2, df, df2],
+ keys=[("foo", "one"), ("foo", "two"), ("baz", "one"), ("baz", "two")],
+ levels=levels,
+ )
+ assert result.index.names == (None,) * 3
+
+ # no levels
+ result = concat(
+ [df, df2, df, df2],
+ keys=[("foo", "one"), ("foo", "two"), ("baz", "one"), ("baz", "two")],
+ names=["first", "second"],
+ )
+ assert result.index.names == ("first", "second", None)
+ tm.assert_index_equal(
+ result.index.levels[0], Index(["baz", "foo"], name="first")
+ )
+
+ def test_concat_keys_levels_no_overlap(self):
+ # GH #1406
+ df = DataFrame(np.random.default_rng(2).standard_normal((1, 3)), index=["a"])
+ df2 = DataFrame(np.random.default_rng(2).standard_normal((1, 4)), index=["b"])
+
+ msg = "Values not found in passed level"
+ with pytest.raises(ValueError, match=msg):
+ concat([df, df], keys=["one", "two"], levels=[["foo", "bar", "baz"]])
+
+ msg = "Key one not in level"
+ with pytest.raises(ValueError, match=msg):
+ concat([df, df2], keys=["one", "two"], levels=[["foo", "bar", "baz"]])
+
+ def test_crossed_dtypes_weird_corner(self):
+ columns = ["A", "B", "C", "D"]
+ df1 = DataFrame(
+ {
+ "A": np.array([1, 2, 3, 4], dtype="f8"),
+ "B": np.array([1, 2, 3, 4], dtype="i8"),
+ "C": np.array([1, 2, 3, 4], dtype="f8"),
+ "D": np.array([1, 2, 3, 4], dtype="i8"),
+ },
+ columns=columns,
+ )
+
+ df2 = DataFrame(
+ {
+ "A": np.array([1, 2, 3, 4], dtype="i8"),
+ "B": np.array([1, 2, 3, 4], dtype="f8"),
+ "C": np.array([1, 2, 3, 4], dtype="i8"),
+ "D": np.array([1, 2, 3, 4], dtype="f8"),
+ },
+ columns=columns,
+ )
+
+ appended = concat([df1, df2], ignore_index=True)
+ expected = DataFrame(
+ np.concatenate([df1.values, df2.values], axis=0), columns=columns
+ )
+ tm.assert_frame_equal(appended, expected)
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((1, 3)), index=["a"])
+ df2 = DataFrame(np.random.default_rng(2).standard_normal((1, 4)), index=["b"])
+ result = concat([df, df2], keys=["one", "two"], names=["first", "second"])
+ assert result.index.names == ("first", "second")
+
+ def test_with_mixed_tuples(self, sort):
+ # 10697
+ # columns have mixed tuples, so handle properly
+ df1 = DataFrame({"A": "foo", ("B", 1): "bar"}, index=range(2))
+ df2 = DataFrame({"B": "foo", ("B", 1): "bar"}, index=range(2))
+
+ # it works
+ concat([df1, df2], sort=sort)
+
+ def test_concat_mixed_objs_columns(self):
+ # Test column-wise concat for mixed series/frames (axis=1)
+ # G2385
+
+ index = date_range("01-Jan-2013", periods=10, freq="h")
+ arr = np.arange(10, dtype="int64")
+ s1 = Series(arr, index=index)
+ s2 = Series(arr, index=index)
+ df = DataFrame(arr.reshape(-1, 1), index=index)
+
+ expected = DataFrame(
+ np.repeat(arr, 2).reshape(-1, 2), index=index, columns=[0, 0]
+ )
+ result = concat([df, df], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(
+ np.repeat(arr, 2).reshape(-1, 2), index=index, columns=[0, 1]
+ )
+ result = concat([s1, s2], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(
+ np.repeat(arr, 3).reshape(-1, 3), index=index, columns=[0, 1, 2]
+ )
+ result = concat([s1, s2, s1], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(
+ np.repeat(arr, 5).reshape(-1, 5), index=index, columns=[0, 0, 1, 2, 3]
+ )
+ result = concat([s1, df, s2, s2, s1], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ # with names
+ s1.name = "foo"
+ expected = DataFrame(
+ np.repeat(arr, 3).reshape(-1, 3), index=index, columns=["foo", 0, 0]
+ )
+ result = concat([s1, df, s2], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ s2.name = "bar"
+ expected = DataFrame(
+ np.repeat(arr, 3).reshape(-1, 3), index=index, columns=["foo", 0, "bar"]
+ )
+ result = concat([s1, df, s2], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ # ignore index
+ expected = DataFrame(
+ np.repeat(arr, 3).reshape(-1, 3), index=index, columns=[0, 1, 2]
+ )
+ result = concat([s1, df, s2], axis=1, ignore_index=True)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_mixed_objs_index(self):
+ # Test row-wise concat for mixed series/frames with a common name
+ # GH2385, GH15047
+
+ index = date_range("01-Jan-2013", periods=10, freq="h")
+ arr = np.arange(10, dtype="int64")
+ s1 = Series(arr, index=index)
+ s2 = Series(arr, index=index)
+ df = DataFrame(arr.reshape(-1, 1), index=index)
+
+ expected = DataFrame(
+ np.tile(arr, 3).reshape(-1, 1), index=index.tolist() * 3, columns=[0]
+ )
+ result = concat([s1, df, s2])
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_mixed_objs_index_names(self):
+ # Test row-wise concat for mixed series/frames with distinct names
+ # GH2385, GH15047
+ # GH #60723 & GH #56257 (Updated the test case,
+ # as the above GH PR ones were incorrect)
+
+ index = date_range("01-Jan-2013", periods=10, freq="h")
+ arr = np.arange(10, dtype="int64")
+ s1 = Series(arr, index=index, name="foo")
+ s2 = Series(arr, index=index, name="bar")
+ df = DataFrame(arr.reshape(-1, 1), index=index)
+
+ expected = DataFrame(
+ np.kron(np.where(np.identity(3) == 1, 1, np.nan), arr).T,
+ index=index.tolist() * 3,
+ columns=["foo", 0, "bar"],
+ )
+ result = concat([s1, df, s2])
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(
+ np.kron(np.where(np.identity(3) == 1, 1, np.nan), arr).T,
+ index=np.arange(30, dtype=np.int64),
+ columns=["foo", 0, "bar"],
+ )
+ result = concat([s1, df, s2], ignore_index=True)
+ tm.assert_frame_equal(result, expected)
+
+ def test_dtype_coercion(self):
+ # 12411
+ df = DataFrame({"date": [pd.Timestamp("20130101").tz_localize("UTC"), pd.NaT]})
+
+ result = concat([df.iloc[[0]], df.iloc[[1]]])
+ tm.assert_series_equal(result.dtypes, df.dtypes)
+
+ # 12045
+ df = DataFrame({"date": [datetime(2012, 1, 1), datetime(1012, 1, 2)]})
+ result = concat([df.iloc[[0]], df.iloc[[1]]])
+ tm.assert_series_equal(result.dtypes, df.dtypes)
+
+ # 11594
+ df = DataFrame({"text": ["some words"] + [None] * 9})
+ result = concat([df.iloc[[0]], df.iloc[[1]]])
+ tm.assert_series_equal(result.dtypes, df.dtypes)
+
+ def test_concat_single_with_key(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+
+ result = concat([df], keys=["foo"])
+ expected = concat([df, df], keys=["foo", "bar"])
+ tm.assert_frame_equal(result, expected[:10])
+
+ def test_concat_no_items_raises(self):
+ with pytest.raises(ValueError, match="No objects to concatenate"):
+ concat([])
+
+ def test_concat_exclude_none(self):
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+
+ pieces = [df[:5], None, None, df[5:]]
+ result = concat(pieces)
+ tm.assert_frame_equal(result, df)
+ with pytest.raises(ValueError, match="All objects passed were None"):
+ concat([None, None])
+
+ def test_concat_keys_with_none(self):
+ # #1649
+ df0 = DataFrame([[10, 20, 30], [10, 20, 30], [10, 20, 30]])
+
+ result = concat({"a": None, "b": df0, "c": df0[:2], "d": df0[:1], "e": df0})
+ expected = concat({"b": df0, "c": df0[:2], "d": df0[:1], "e": df0})
+ tm.assert_frame_equal(result, expected)
+
+ result = concat(
+ [None, df0, df0[:2], df0[:1], df0], keys=["a", "b", "c", "d", "e"]
+ )
+ expected = concat([df0, df0[:2], df0[:1], df0], keys=["b", "c", "d", "e"])
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("klass", [range, RangeIndex])
+ @pytest.mark.parametrize("include_none", [True, False])
+ def test_concat_preserves_rangeindex(self, klass, include_none):
+ df = DataFrame([1, 2])
+ df2 = DataFrame([3, 4])
+ data = [df, None, df2, None] if include_none else [df, df2]
+ keys_length = 4 if include_none else 2
+ result = concat(data, keys=klass(keys_length))
+ expected = DataFrame(
+ [1, 2, 3, 4],
+ index=MultiIndex(
+ levels=(
+ RangeIndex(start=0, stop=keys_length, step=keys_length / 2),
+ RangeIndex(start=0, stop=2, step=1),
+ ),
+ codes=(
+ np.array([0, 0, 1, 1], dtype=np.int8),
+ np.array([0, 1, 0, 1], dtype=np.int8),
+ ),
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_bug_1719(self):
+ ts1 = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+ ts2 = ts1[::2]
+
+ # to join with union
+ # these two are of different length!
+ left = concat([ts1, ts2], join="outer", axis=1)
+ msg = "Sorting by default when concatenating all DatetimeIndex is deprecated"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ right = concat([ts2, ts1], join="outer", axis=1)
+
+ assert len(left) == len(right)
+
+ def test_concat_bug_2972(self):
+ ts0 = Series(np.zeros(5))
+ ts1 = Series(np.ones(5))
+ ts0.name = ts1.name = "same name"
+ result = concat([ts0, ts1], axis=1)
+
+ expected = DataFrame({0: ts0, 1: ts1})
+ expected.columns = ["same name", "same name"]
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_bug_3602(self):
+ # GH 3602, duplicate columns
+ df1 = DataFrame(
+ {
+ "firmNo": [0, 0, 0, 0],
+ "prc": [6, 6, 6, 6],
+ "stringvar": ["rrr", "rrr", "rrr", "rrr"],
+ }
+ )
+ df2 = DataFrame(
+ {"C": [9, 10, 11, 12], "misc": [1, 2, 3, 4], "prc": [6, 6, 6, 6]}
+ )
+ expected = DataFrame(
+ [
+ [0, 6, "rrr", 9, 1, 6],
+ [0, 6, "rrr", 10, 2, 6],
+ [0, 6, "rrr", 11, 3, 6],
+ [0, 6, "rrr", 12, 4, 6],
+ ]
+ )
+ expected.columns = ["firmNo", "prc", "stringvar", "C", "misc", "prc"]
+
+ result = concat([df1, df2], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_iterables(self):
+ # GH8645 check concat works with tuples, list, generators, and weird
+ # stuff like deque and custom iterables
+ df1 = DataFrame([1, 2, 3])
+ df2 = DataFrame([4, 5, 6])
+ expected = DataFrame([1, 2, 3, 4, 5, 6])
+ tm.assert_frame_equal(concat((df1, df2), ignore_index=True), expected)
+ tm.assert_frame_equal(concat([df1, df2], ignore_index=True), expected)
+ tm.assert_frame_equal(
+ concat((df for df in (df1, df2)), ignore_index=True), expected
+ )
+ tm.assert_frame_equal(concat(deque((df1, df2)), ignore_index=True), expected)
+
+ class CustomIterator1:
+ def __len__(self) -> int:
+ return 2
+
+ def __getitem__(self, index):
+ try:
+ return {0: df1, 1: df2}[index]
+ except KeyError as err:
+ raise IndexError from err
+
+ tm.assert_frame_equal(concat(CustomIterator1(), ignore_index=True), expected)
+
+ class CustomIterator2(abc.Iterable):
+ def __iter__(self) -> Iterator:
+ yield df1
+ yield df2
+
+ tm.assert_frame_equal(concat(CustomIterator2(), ignore_index=True), expected)
+
+ def test_concat_order(self):
+ # GH 17344, GH#47331
+ dfs = [DataFrame(index=range(3), columns=["a", 1, None])]
+ dfs += [DataFrame(index=range(3), columns=[None, 1, "a"]) for _ in range(100)]
+
+ result = concat(dfs, sort=True).columns
+ expected = Index([1, "a", None])
+ tm.assert_index_equal(result, expected)
+
+ def test_concat_different_extension_dtypes_upcasts(self):
+ a = Series(pd.array([1, 2], dtype="Int64"))
+ b = Series(to_decimal([1, 2]))
+
+ result = concat([a, b], ignore_index=True)
+ expected = Series([1, 2, Decimal(1), Decimal(2)], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_ordered_dict(self):
+ # GH 21510
+ expected = concat(
+ [Series(range(3)), Series(range(4))], keys=["First", "Another"]
+ )
+ result = concat({"First": Series(range(3)), "Another": Series(range(4))})
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_duplicate_indices_raise(self):
+ # GH 45888: test raise for concat DataFrames with duplicate indices
+ # https://github.com/pandas-dev/pandas/issues/36263
+ df1 = DataFrame(
+ np.random.default_rng(2).standard_normal(5),
+ index=[0, 1, 2, 3, 3],
+ columns=["a"],
+ )
+ df2 = DataFrame(
+ np.random.default_rng(2).standard_normal(5),
+ index=[0, 1, 2, 2, 4],
+ columns=["b"],
+ )
+ msg = "Reindexing only valid with uniquely valued Index objects"
+ with pytest.raises(InvalidIndexError, match=msg):
+ concat([df1, df2], axis=1)
+
+
+def test_concat_no_unnecessary_upcast(float_numpy_dtype, frame_or_series):
+ # GH 13247
+ dims = frame_or_series(dtype=object).ndim
+ dt = float_numpy_dtype
+
+ dfs = [
+ frame_or_series(np.array([1], dtype=dt, ndmin=dims)),
+ frame_or_series(np.array([np.nan], dtype=dt, ndmin=dims)),
+ frame_or_series(np.array([5], dtype=dt, ndmin=dims)),
+ ]
+ x = concat(dfs)
+ assert x.values.dtype == dt
+
+
+def test_concat_will_upcast(frame_or_series, any_signed_int_numpy_dtype):
+ dt = any_signed_int_numpy_dtype
+ dims = frame_or_series().ndim
+ dfs = [
+ frame_or_series(np.array([1], dtype=dt, ndmin=dims)),
+ frame_or_series(np.array([np.nan], ndmin=dims)),
+ frame_or_series(np.array([5], dtype=dt, ndmin=dims)),
+ ]
+ x = concat(dfs)
+ assert x.values.dtype == "float64"
+
+
+def test_concat_empty_and_non_empty_frame_regression():
+ # GH 18178 regression test
+ df1 = DataFrame({"foo": [1]})
+ df2 = DataFrame({"foo": []})
+ expected = DataFrame({"foo": [1.0]})
+ result = concat([df1, df2])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_sparse():
+ # GH 23557
+ a = Series(SparseArray([0, 1, 2]))
+ expected = DataFrame(data=[[0, 0], [1, 1], [2, 2]]).astype(
+ pd.SparseDtype(np.int64, 0)
+ )
+ result = concat([a, a], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_dense_sparse():
+ # GH 30668
+ dtype = pd.SparseDtype(np.float64, None)
+ a = Series(pd.arrays.SparseArray([1, None]), dtype=dtype)
+ b = Series([1], dtype=float)
+ expected = Series(data=[1, None, 1], index=[0, 1, 0]).astype(dtype)
+ result = concat([a, b], axis=0)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("keys", [["e", "f", "f"], ["f", "e", "f"]])
+def test_duplicate_keys(keys):
+ # GH 33654
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ s1 = Series([7, 8, 9], name="c")
+ s2 = Series([10, 11, 12], name="d")
+ result = concat([df, s1, s2], axis=1, keys=keys)
+ expected_values = [[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]]
+ expected_columns = MultiIndex.from_tuples(
+ [(keys[0], "a"), (keys[0], "b"), (keys[1], "c"), (keys[2], "d")]
+ )
+ expected = DataFrame(expected_values, columns=expected_columns)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_duplicate_keys_same_frame():
+ # GH 43595
+ keys = ["e", "e"]
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ result = concat([df, df], axis=1, keys=keys)
+ expected_values = [[1, 4, 1, 4], [2, 5, 2, 5], [3, 6, 3, 6]]
+ expected_columns = MultiIndex.from_tuples(
+ [(keys[0], "a"), (keys[0], "b"), (keys[1], "a"), (keys[1], "b")]
+ )
+ expected = DataFrame(expected_values, columns=expected_columns)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
+)
+@pytest.mark.parametrize(
+ "obj",
+ [
+ tm.SubclassedDataFrame({"A": np.arange(0, 10)}),
+ tm.SubclassedSeries(np.arange(0, 10), name="A"),
+ ],
+)
+def test_concat_preserves_subclass(obj):
+ # GH28330 -- preserve subclass
+
+ result = concat([obj, obj])
+ assert isinstance(result, type(obj))
+
+
+def test_concat_frame_axis0_extension_dtypes():
+ # preserve extension dtype (through common_dtype mechanism)
+ df1 = DataFrame({"a": pd.array([1, 2, 3], dtype="Int64")})
+ df2 = DataFrame({"a": np.array([4, 5, 6])})
+
+ result = concat([df1, df2], ignore_index=True)
+ expected = DataFrame({"a": [1, 2, 3, 4, 5, 6]}, dtype="Int64")
+ tm.assert_frame_equal(result, expected)
+
+ result = concat([df2, df1], ignore_index=True)
+ expected = DataFrame({"a": [4, 5, 6, 1, 2, 3]}, dtype="Int64")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_preserves_extension_int64_dtype():
+ # GH 24768
+ df_a = DataFrame({"a": [-1]}, dtype="Int64")
+ df_b = DataFrame({"b": [1]}, dtype="Int64")
+ result = concat([df_a, df_b], ignore_index=True)
+ expected = DataFrame({"a": [-1, None], "b": [None, 1]}, dtype="Int64")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype1,dtype2,expected_dtype",
+ [
+ ("bool", "bool", "bool"),
+ ("boolean", "bool", "boolean"),
+ ("bool", "boolean", "boolean"),
+ ("boolean", "boolean", "boolean"),
+ ],
+)
+def test_concat_bool_types(dtype1, dtype2, expected_dtype):
+ # GH 42800
+ ser1 = Series([True, False], dtype=dtype1)
+ ser2 = Series([False, True], dtype=dtype2)
+ result = concat([ser1, ser2], ignore_index=True)
+ expected = Series([True, False, False, True], dtype=expected_dtype)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ ("keys", "integrity"),
+ [
+ (["red"] * 3, True),
+ (["red"] * 3, False),
+ (["red", "blue", "red"], False),
+ (["red", "blue", "red"], True),
+ ],
+)
+def test_concat_repeated_keys(keys, integrity):
+ # GH: 20816
+ series_list = [Series({"a": 1}), Series({"b": 2}), Series({"c": 3})]
+ result = concat(series_list, keys=keys, verify_integrity=integrity)
+ tuples = list(zip(keys, ["a", "b", "c"]))
+ expected = Series([1, 2, 3], index=MultiIndex.from_tuples(tuples))
+ tm.assert_series_equal(result, expected)
+
+
+def test_concat_null_object_with_dti():
+ # GH#40841
+ dti = pd.DatetimeIndex(
+ ["2021-04-08 21:21:14+00:00"], dtype="datetime64[ns, UTC]", name="Time (UTC)"
+ )
+ right = DataFrame(data={"C": [0.5274]}, index=dti)
+
+ idx = Index([None], dtype="object", name="Maybe Time (UTC)")
+ left = DataFrame(data={"A": [None], "B": [np.nan]}, index=idx)
+
+ result = concat([left, right], axis="columns")
+
+ exp_index = Index([None, dti[0]], dtype=object)
+ expected = DataFrame(
+ {
+ "A": np.array([None, np.nan], dtype=object),
+ "B": [np.nan, np.nan],
+ "C": [np.nan, 0.5274],
+ },
+ index=exp_index,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_multiindex_with_empty_rangeindex():
+ # GH#41234
+ mi = MultiIndex.from_tuples([("B", 1), ("C", 1)])
+ df1 = DataFrame([[1, 2]], columns=mi)
+ df2 = DataFrame(index=[1], columns=RangeIndex(0))
+
+ result = concat([df1, df2])
+ expected = DataFrame([[1, 2], [np.nan, np.nan]], columns=mi)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ Series(data=[1, 2]),
+ DataFrame(
+ data={
+ "col1": [1, 2],
+ }
+ ),
+ DataFrame(dtype=float),
+ Series(dtype=float),
+ ],
+)
+def test_concat_drop_attrs(data):
+ # GH#41828
+ df1 = data.copy()
+ df1.attrs = {1: 1}
+ df2 = data.copy()
+ df2.attrs = {1: 2}
+ df = concat([df1, df2])
+ assert len(df.attrs) == 0
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ Series(data=[1, 2]),
+ DataFrame(
+ data={
+ "col1": [1, 2],
+ }
+ ),
+ DataFrame(dtype=float),
+ Series(dtype=float),
+ ],
+)
+def test_concat_retain_attrs(data):
+ # GH#41828
+ df1 = data.copy()
+ df1.attrs = {1: 1}
+ df2 = data.copy()
+ df2.attrs = {1: 1}
+ df = concat([df1, df2])
+ assert df.attrs[1] == 1
+
+
+@pytest.mark.parametrize("df_dtype", ["float64", "int64", "datetime64[ns]"])
+@pytest.mark.parametrize("empty_dtype", [None, "float64", "object"])
+def test_concat_ignore_empty_object_float(empty_dtype, df_dtype):
+ # https://github.com/pandas-dev/pandas/issues/45637
+ df = DataFrame({"foo": [1, 2], "bar": [1, 2]}, dtype=df_dtype)
+ empty = DataFrame(columns=["foo", "bar"], dtype=empty_dtype)
+
+ needs_update = False
+ if df_dtype == "datetime64[ns]" or (
+ df_dtype == "float64" and empty_dtype != "float64"
+ ):
+ needs_update = True
+
+ result = concat([empty, df])
+ expected = df
+ if df_dtype == "int64":
+ # TODO what exact behaviour do we want for integer eventually?
+ if empty_dtype == "float64":
+ expected = df.astype("float64")
+ else:
+ expected = df.astype("object")
+
+ if needs_update:
+ # GH#40893 changed the expected here to retain dependence on empty
+ expected = expected.astype(object)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("df_dtype", ["float64", "int64", "datetime64[ns]"])
+@pytest.mark.parametrize("empty_dtype", [None, "float64", "object"])
+def test_concat_ignore_all_na_object_float(empty_dtype, df_dtype):
+ df = DataFrame({"foo": [1, 2], "bar": [1, 2]}, dtype=df_dtype)
+ empty = DataFrame({"foo": [np.nan], "bar": [np.nan]}, dtype=empty_dtype)
+
+ if df_dtype == "int64":
+ # TODO what exact behaviour do we want for integer eventually?
+ if empty_dtype == "object":
+ df_dtype = "object"
+ else:
+ df_dtype = "float64"
+
+ needs_update = False
+ if empty_dtype != df_dtype and empty_dtype is not None:
+ needs_update = True
+ elif df_dtype == "datetime64[ns]":
+ needs_update = True
+
+ result = concat([empty, df], ignore_index=True)
+
+ expected = DataFrame({"foo": [np.nan, 1, 2], "bar": [np.nan, 1, 2]}, dtype=df_dtype)
+ if needs_update:
+ # GH#40893 changed the expected here to retain dependence on empty
+ expected = expected.astype(object)
+ expected.iloc[0] = np.nan
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_ignore_empty_from_reindex():
+ # https://github.com/pandas-dev/pandas/pull/43507#issuecomment-920375856
+ df1 = DataFrame({"a": [1], "b": [pd.Timestamp("2012-01-01")]})
+ df2 = DataFrame({"a": [2]})
+
+ aligned = df2.reindex(columns=df1.columns)
+
+ result = concat([df1, aligned], ignore_index=True)
+
+ expected = DataFrame(
+ {
+ "a": [1, 2],
+ "b": pd.array([pd.Timestamp("2012-01-01"), np.nan], dtype=object),
+ },
+ dtype=object,
+ )
+ expected["a"] = expected["a"].astype("int64")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_mismatched_keys_length():
+ # GH#43485
+ ser = Series(range(5))
+ sers = [ser + n for n in range(4)]
+ keys = ["A", "B", "C"]
+
+ msg = r"The length of the keys"
+ with pytest.raises(ValueError, match=msg):
+ concat(sers, keys=keys, axis=1)
+ with pytest.raises(ValueError, match=msg):
+ concat(sers, keys=keys, axis=0)
+ with pytest.raises(ValueError, match=msg):
+ concat((x for x in sers), keys=(y for y in keys), axis=1)
+ with pytest.raises(ValueError, match=msg):
+ concat((x for x in sers), keys=(y for y in keys), axis=0)
+
+
+def test_concat_multiindex_with_category():
+ df1 = DataFrame(
+ {
+ "c1": Series(list("abc"), dtype="category"),
+ "c2": Series(list("eee"), dtype="category"),
+ "i2": Series([1, 2, 3]),
+ }
+ )
+ df1 = df1.set_index(["c1", "c2"])
+ df2 = DataFrame(
+ {
+ "c1": Series(list("abc"), dtype="category"),
+ "c2": Series(list("eee"), dtype="category"),
+ "i2": Series([4, 5, 6]),
+ }
+ )
+ df2 = df2.set_index(["c1", "c2"])
+ result = concat([df1, df2])
+ expected = DataFrame(
+ {
+ "c1": Series(list("abcabc"), dtype="category"),
+ "c2": Series(list("eeeeee"), dtype="category"),
+ "i2": Series([1, 2, 3, 4, 5, 6]),
+ }
+ )
+ expected = expected.set_index(["c1", "c2"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_ea_upcast():
+ # GH#54848
+ df1 = DataFrame(["a"], dtype="string")
+ df2 = DataFrame([1], dtype="Int64")
+ result = concat([df1, df2])
+ expected = DataFrame(["a", 1], index=[0, 0])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_none_with_timezone_timestamp():
+ # GH#52093
+ df1 = DataFrame([{"A": None}])
+ df2 = DataFrame([{"A": pd.Timestamp("1990-12-20 00:00:00+00:00")}])
+ result = concat([df1, df2], ignore_index=True)
+ expected = DataFrame(
+ {"A": [None, pd.Timestamp("1990-12-20 00:00:00+00:00")]}, dtype=object
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_with_series_and_frame_returns_rangeindex_columns():
+ ser = Series([0])
+ df = DataFrame([1, 2])
+ result = concat([ser, df])
+ expected = DataFrame([0, 1, 2], index=[0, 0, 1])
+ tm.assert_frame_equal(result, expected, check_column_type=True)
+
+
+def test_concat_with_moot_ignore_index_and_keys():
+ df1 = DataFrame([[0]])
+ df2 = DataFrame([[42]])
+
+ ignore_index = True
+ keys = ["df1", "df2"]
+ msg = f"Cannot set {ignore_index=} and specify keys. Either should be used."
+ with pytest.raises(ValueError, match=msg):
+ concat([df1, df2], keys=keys, ignore_index=ignore_index)
+
+
+@pytest.mark.parametrize(
+ "inputs, ignore_index, axis, expected",
+ [
+ # Concatenating DataFrame and named Series without ignore_index
+ (
+ [DataFrame({"a": [0, 1], "b": [2, 3]}), Series([4, 5], name="c")],
+ False,
+ 0,
+ DataFrame(
+ {
+ "a": [0, 1, None, None],
+ "b": [2, 3, None, None],
+ "c": [None, None, 4, 5],
+ },
+ index=[0, 1, 0, 1],
+ ),
+ ),
+ # Concatenating DataFrame and named Series with ignore_index
+ (
+ [DataFrame({"a": [0, 1], "b": [2, 3]}), Series([4, 5], name="c")],
+ True,
+ 0,
+ DataFrame(
+ {
+ "a": [0, 1, None, None],
+ "b": [2, 3, None, None],
+ "c": [None, None, 4, 5],
+ },
+ index=[0, 1, 2, 3],
+ ),
+ ),
+ # Concatenating DataFrame and unnamed Series along columns
+ (
+ [DataFrame({"a": [0, 1], "b": [2, 3]}), Series([4, 5]), Series([4, 5])],
+ False,
+ 1,
+ DataFrame({"a": [0, 1], "b": [2, 3], 0: [4, 5], 1: [4, 5]}, index=[0, 1]),
+ ),
+ # Concatenating DataFrame and unnamed Series along columns with ignore_index
+ (
+ [DataFrame({"a": [0, 1], "b": [2, 3]}), Series([4, 5]), Series([4, 5])],
+ True,
+ 1,
+ DataFrame({0: [0, 1], 1: [2, 3], 2: [4, 5], 3: [4, 5]}, index=[0, 1]),
+ ),
+ ],
+)
+def test_concat_of_series_and_frame(inputs, ignore_index, axis, expected):
+ # GH #60723 and #56257
+ result = concat(inputs, ignore_index=ignore_index, axis=axis)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_dataframe.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_dataframe.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6da5ed76816b527fb23c9f5c1f68de3de8f8440
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_dataframe.py
@@ -0,0 +1,238 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ concat,
+)
+import pandas._testing as tm
+
+
+class TestDataFrameConcat:
+ @pytest.mark.xfail(reason="GH#62888 the `mi[2][1] is 1` check fails")
+ def test_concat_multiindex_level_bool_and_numeric(self):
+ # GH#21108, GH#45101
+ left = DataFrame([123, 456], columns=["data"], index=[True, False])
+ right = DataFrame(
+ [55, 983, 69, 112, 0], columns=["data"], index=[1, 2, 3, 4, 99]
+ )
+ result = concat({"One": left, "Two": right})
+
+ # in particular, the first two entries should not be cast to ints, the
+ # other 1 should not cast to True
+ mi = pd.MultiIndex.from_arrays(
+ [
+ ["One"] * 2 + ["Two"] * 5,
+ np.array([True, False, 1, 2, 3, 4, 99], dtype=object),
+ ],
+ )
+ assert mi[0][1] is True
+ assert type(mi[2][1]) is int
+ expected = DataFrame({"data": [123, 456, 55, 983, 69, 112, 0]}, index=mi)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_multiple_frames_dtypes(self):
+ # GH#2759
+ df1 = DataFrame(data=np.ones((10, 2)), columns=["foo", "bar"], dtype=np.float64)
+ df2 = DataFrame(data=np.ones((10, 2)), dtype=np.float32)
+ results = concat((df1, df2), axis=1).dtypes
+ expected = Series(
+ [np.dtype("float64")] * 2 + [np.dtype("float32")] * 2,
+ index=["foo", "bar", 0, 1],
+ )
+ tm.assert_series_equal(results, expected)
+
+ def test_concat_tuple_keys(self):
+ # GH#14438
+ df1 = DataFrame(np.ones((2, 2)), columns=list("AB"))
+ df2 = DataFrame(np.ones((3, 2)) * 2, columns=list("AB"))
+ results = concat((df1, df2), keys=[("bee", "bah"), ("bee", "boo")])
+ expected = DataFrame(
+ {
+ "A": {
+ ("bee", "bah", 0): 1.0,
+ ("bee", "bah", 1): 1.0,
+ ("bee", "boo", 0): 2.0,
+ ("bee", "boo", 1): 2.0,
+ ("bee", "boo", 2): 2.0,
+ },
+ "B": {
+ ("bee", "bah", 0): 1.0,
+ ("bee", "bah", 1): 1.0,
+ ("bee", "boo", 0): 2.0,
+ ("bee", "boo", 1): 2.0,
+ ("bee", "boo", 2): 2.0,
+ },
+ }
+ )
+ tm.assert_frame_equal(results, expected)
+
+ def test_concat_named_keys(self):
+ # GH#14252
+ df = DataFrame({"foo": [1, 2], "bar": [0.1, 0.2]})
+ index = Index(["a", "b"], name="baz")
+ concatted_named_from_keys = concat([df, df], keys=index)
+ expected_named = DataFrame(
+ {"foo": [1, 2, 1, 2], "bar": [0.1, 0.2, 0.1, 0.2]},
+ index=pd.MultiIndex.from_product((["a", "b"], [0, 1]), names=["baz", None]),
+ )
+ tm.assert_frame_equal(concatted_named_from_keys, expected_named)
+
+ index_no_name = Index(["a", "b"], name=None)
+ concatted_named_from_names = concat([df, df], keys=index_no_name, names=["baz"])
+ tm.assert_frame_equal(concatted_named_from_names, expected_named)
+
+ concatted_unnamed = concat([df, df], keys=index_no_name)
+ expected_unnamed = DataFrame(
+ {"foo": [1, 2, 1, 2], "bar": [0.1, 0.2, 0.1, 0.2]},
+ index=pd.MultiIndex.from_product((["a", "b"], [0, 1]), names=[None, None]),
+ )
+ tm.assert_frame_equal(concatted_unnamed, expected_unnamed)
+
+ def test_concat_axis_parameter(self):
+ # GH#14369
+ df1 = DataFrame({"A": [0.1, 0.2]}, index=range(2))
+ df2 = DataFrame({"A": [0.3, 0.4]}, index=range(2))
+
+ # Index/row/0 DataFrame
+ expected_index = DataFrame({"A": [0.1, 0.2, 0.3, 0.4]}, index=[0, 1, 0, 1])
+
+ concatted_index = concat([df1, df2], axis="index")
+ tm.assert_frame_equal(concatted_index, expected_index)
+
+ concatted_row = concat([df1, df2], axis="rows")
+ tm.assert_frame_equal(concatted_row, expected_index)
+
+ concatted_0 = concat([df1, df2], axis=0)
+ tm.assert_frame_equal(concatted_0, expected_index)
+
+ # Columns/1 DataFrame
+ expected_columns = DataFrame(
+ [[0.1, 0.3], [0.2, 0.4]], index=[0, 1], columns=["A", "A"]
+ )
+
+ concatted_columns = concat([df1, df2], axis="columns")
+ tm.assert_frame_equal(concatted_columns, expected_columns)
+
+ concatted_1 = concat([df1, df2], axis=1)
+ tm.assert_frame_equal(concatted_1, expected_columns)
+
+ series1 = Series([0.1, 0.2])
+ series2 = Series([0.3, 0.4])
+
+ # Index/row/0 Series
+ expected_index_series = Series([0.1, 0.2, 0.3, 0.4], index=[0, 1, 0, 1])
+
+ concatted_index_series = concat([series1, series2], axis="index")
+ tm.assert_series_equal(concatted_index_series, expected_index_series)
+
+ concatted_row_series = concat([series1, series2], axis="rows")
+ tm.assert_series_equal(concatted_row_series, expected_index_series)
+
+ concatted_0_series = concat([series1, series2], axis=0)
+ tm.assert_series_equal(concatted_0_series, expected_index_series)
+
+ # Columns/1 Series
+ expected_columns_series = DataFrame(
+ [[0.1, 0.3], [0.2, 0.4]], index=[0, 1], columns=[0, 1]
+ )
+
+ concatted_columns_series = concat([series1, series2], axis="columns")
+ tm.assert_frame_equal(concatted_columns_series, expected_columns_series)
+
+ concatted_1_series = concat([series1, series2], axis=1)
+ tm.assert_frame_equal(concatted_1_series, expected_columns_series)
+
+ # Testing ValueError
+ with pytest.raises(ValueError, match="No axis named"):
+ concat([series1, series2], axis="something")
+
+ def test_concat_numerical_names(self):
+ # GH#15262, GH#12223
+ df = DataFrame(
+ {"col": range(9)},
+ dtype="int32",
+ index=(
+ pd.MultiIndex.from_product(
+ [["A0", "A1", "A2"], ["B0", "B1", "B2"]], names=[1, 2]
+ )
+ ),
+ )
+ result = concat((df.iloc[:2, :], df.iloc[-2:, :]))
+ expected = DataFrame(
+ {"col": [0, 1, 7, 8]},
+ dtype="int32",
+ index=pd.MultiIndex.from_tuples(
+ [("A0", "B0"), ("A0", "B1"), ("A2", "B1"), ("A2", "B2")], names=[1, 2]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_astype_dup_col(self):
+ # GH#23049
+ df = DataFrame([{"a": "b"}])
+ df = concat([df, df], axis=1)
+
+ result = df.astype("category")
+ expected = DataFrame(
+ np.array(["b", "b"]).reshape(1, 2), columns=["a", "a"]
+ ).astype("category")
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_dataframe_keys_bug(self, sort):
+ t1 = DataFrame(
+ {"value": Series([1, 2, 3], index=Index(["a", "b", "c"], name="id"))}
+ )
+ t2 = DataFrame({"value": Series([7, 8], index=Index(["a", "b"], name="id"))})
+
+ # it works
+ result = concat([t1, t2], axis=1, keys=["t1", "t2"], sort=sort)
+ assert list(result.columns) == [("t1", "value"), ("t2", "value")]
+
+ def test_concat_bool_with_int(self):
+ # GH#42092 we may want to change this to return object, but that
+ # would need a deprecation
+ df1 = DataFrame(Series([True, False, True, True], dtype="bool"))
+ df2 = DataFrame(Series([1, 0, 1], dtype="int64"))
+
+ result = concat([df1, df2])
+ expected = concat([df1.astype("int64"), df2])
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_duplicates_in_index_with_keys(self):
+ # GH#42651
+ index = [1, 1, 3]
+ data = [1, 2, 3]
+
+ df = DataFrame(data=data, index=index)
+ result = concat([df], keys=["A"], names=["ID", "date"])
+ mi = pd.MultiIndex.from_product([["A"], index], names=["ID", "date"])
+ expected = DataFrame(data=data, index=mi)
+ tm.assert_frame_equal(result, expected)
+ tm.assert_index_equal(result.index.levels[1], Index([1, 3], name="date"))
+
+ def test_outer_sort_columns(self):
+ # GH#47127
+ df1 = DataFrame({"A": [0], "B": [1], 0: 1})
+ df2 = DataFrame({"A": [100]})
+ result = concat([df1, df2], ignore_index=True, join="outer", sort=True)
+ expected = DataFrame({0: [1.0, np.nan], "A": [0, 100], "B": [1.0, np.nan]})
+ tm.assert_frame_equal(result, expected)
+
+ def test_inner_sort_columns(self):
+ # GH#47127
+ df1 = DataFrame({"A": [0], "B": [1], 0: 1})
+ df2 = DataFrame({"A": [100], 0: 2})
+ result = concat([df1, df2], ignore_index=True, join="inner", sort=True)
+ expected = DataFrame({0: [1, 2], "A": [0, 100]})
+ tm.assert_frame_equal(result, expected)
+
+ def test_sort_columns_one_df(self):
+ # GH#47127
+ df1 = DataFrame({"A": [100], 0: 2})
+ result = concat([df1], ignore_index=True, join="inner", sort=True)
+ expected = DataFrame({0: [2], "A": [100]})
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_datetimes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_datetimes.py
new file mode 100644
index 0000000000000000000000000000000000000000..8bd737e0d025758953ab841ae64f8eb3e8571438
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_datetimes.py
@@ -0,0 +1,611 @@
+import datetime as dt
+from datetime import datetime
+
+import dateutil
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ concat,
+ date_range,
+ to_timedelta,
+)
+import pandas._testing as tm
+
+
+class TestDatetimeConcat:
+ def test_concat_datetime64_block(self):
+ rng = date_range("1/1/2000", periods=10)
+
+ df = DataFrame({"time": rng})
+
+ result = concat([df, df])
+ assert (result.iloc[:10]["time"] == rng).all()
+ assert (result.iloc[10:]["time"] == rng).all()
+
+ def test_concat_datetime_datetime64_frame(self):
+ # GH#2624
+ rows = []
+ rows.append([datetime(2010, 1, 1), 1])
+ rows.append([datetime(2010, 1, 2), "hi"])
+
+ df2_obj = DataFrame.from_records(rows, columns=["date", "test"])
+
+ ind = date_range(start="2000/1/1", freq="D", periods=10)
+ df1 = DataFrame({"date": ind, "test": range(10)})
+
+ # it works!
+ concat([df1, df2_obj])
+
+ def test_concat_datetime_timezone(self):
+ # GH 18523
+ idx1 = date_range(
+ "2011-01-01", periods=3, freq="h", tz="Europe/Paris", unit="ns"
+ )
+ idx2 = date_range(start=idx1[0], end=idx1[-1], freq="h", unit="ns")
+ df1 = DataFrame({"a": [1, 2, 3]}, index=idx1)
+ df2 = DataFrame({"b": [1, 2, 3]}, index=idx2)
+ result = concat([df1, df2], axis=1)
+
+ exp_idx = DatetimeIndex(
+ [
+ "2011-01-01 00:00:00+01:00",
+ "2011-01-01 01:00:00+01:00",
+ "2011-01-01 02:00:00+01:00",
+ ],
+ dtype="M8[ns, Europe/Paris]",
+ freq="h",
+ )
+ expected = DataFrame(
+ [[1, 1], [2, 2], [3, 3]], index=exp_idx, columns=["a", "b"]
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ idx3 = date_range("2011-01-01", periods=3, freq="h", tz="Asia/Tokyo", unit="ns")
+ df3 = DataFrame({"b": [1, 2, 3]}, index=idx3)
+ msg = "Sorting by default when concatenating all DatetimeIndex"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = concat([df1, df3], axis=1)
+
+ exp_idx = DatetimeIndex(
+ [
+ "2010-12-31 15:00:00+00:00",
+ "2010-12-31 16:00:00+00:00",
+ "2010-12-31 17:00:00+00:00",
+ "2010-12-31 23:00:00+00:00",
+ "2011-01-01 00:00:00+00:00",
+ "2011-01-01 01:00:00+00:00",
+ ]
+ ).as_unit("ns")
+
+ expected = DataFrame(
+ [
+ [np.nan, 1],
+ [np.nan, 2],
+ [np.nan, 3],
+ [1, np.nan],
+ [2, np.nan],
+ [3, np.nan],
+ ],
+ index=exp_idx,
+ columns=["a", "b"],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ # GH 13783: Concat after resample
+ result = concat([df1.resample("h").mean(), df2.resample("h").mean()], sort=True)
+ expected = DataFrame(
+ {"a": [1, 2, 3] + [np.nan] * 3, "b": [np.nan] * 3 + [1, 2, 3]},
+ index=idx1.append(idx1),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_datetimeindex_freq(self):
+ # GH 3232
+ # Monotonic index result
+ dr = date_range("01-Jan-2013", periods=100, freq="50ms", tz="UTC")
+ data = list(range(100))
+ expected = DataFrame(data, index=dr)
+ result = concat([expected[:50], expected[50:]])
+ tm.assert_frame_equal(result, expected)
+
+ # Non-monotonic index result
+ result = concat([expected[50:], expected[:50]])
+ expected = DataFrame(data[50:] + data[:50], index=dr[50:].append(dr[:50]))
+ expected.index._data.freq = None
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_multiindex_datetime_object_index(self):
+ # https://github.com/pandas-dev/pandas/issues/11058
+ idx = Index(
+ [dt.date(2013, 1, 1), dt.date(2014, 1, 1), dt.date(2015, 1, 1)],
+ dtype="object",
+ )
+
+ s = Series(
+ ["a", "b"],
+ index=MultiIndex.from_arrays(
+ [
+ [1, 2],
+ idx[:-1],
+ ],
+ names=["first", "second"],
+ ),
+ )
+ s2 = Series(
+ ["a", "b"],
+ index=MultiIndex.from_arrays(
+ [[1, 2], idx[::2]],
+ names=["first", "second"],
+ ),
+ )
+ mi = MultiIndex.from_arrays(
+ [[1, 2, 2], idx],
+ names=["first", "second"],
+ )
+ assert mi.levels[1].dtype == object
+
+ expected = DataFrame(
+ [["a", "a"], ["b", np.nan], [np.nan, "b"]],
+ index=mi,
+ )
+ result = concat([s, s2], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_NaT_series(self):
+ # GH 11693
+ # test for merging NaT series with datetime series.
+ x = Series(
+ date_range(
+ "20151124 08:00",
+ "20151124 09:00",
+ freq="1h",
+ tz="US/Eastern",
+ unit="ns",
+ )
+ )
+ y = Series(pd.NaT, index=[0, 1], dtype="datetime64[ns, US/Eastern]")
+ expected = Series([x[0], x[1], pd.NaT, pd.NaT])
+
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+
+ # all NaT with tz
+ expected = Series(pd.NaT, index=range(4), dtype="datetime64[ns, US/Eastern]")
+ result = concat([y, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_NaT_series2(self):
+ # without tz
+ x = Series(date_range("20151124 08:00", "20151124 09:00", freq="1h", unit="ns"))
+ y = Series(date_range("20151124 10:00", "20151124 11:00", freq="1h", unit="ns"))
+ y[:] = pd.NaT
+ expected = Series([x[0], x[1], pd.NaT, pd.NaT])
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+
+ # all NaT without tz
+ x[:] = pd.NaT
+ expected = Series(pd.NaT, index=range(4), dtype="datetime64[ns]")
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("tz", [None, "UTC"])
+ def test_concat_NaT_dataframes(self, tz):
+ # GH 12396
+
+ dti = DatetimeIndex([pd.NaT, pd.NaT], tz=tz)
+ first = DataFrame({0: dti})
+ second = DataFrame(
+ [[Timestamp("2015/01/01", tz=tz)], [Timestamp("2016/01/01", tz=tz)]],
+ index=[2, 3],
+ )
+ expected = DataFrame(
+ [
+ pd.NaT,
+ pd.NaT,
+ Timestamp("2015/01/01", tz=tz),
+ Timestamp("2016/01/01", tz=tz),
+ ]
+ )
+
+ result = concat([first, second], axis=0)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("tz1", [None, "UTC"])
+ @pytest.mark.parametrize("tz2", [None, "UTC"])
+ @pytest.mark.parametrize("item", [pd.NaT, Timestamp("20150101").as_unit("ns")])
+ def test_concat_NaT_dataframes_all_NaT_axis_0(self, tz1, tz2, item):
+ # GH 12396
+
+ # tz-naive
+ first = DataFrame([[pd.NaT], [pd.NaT]]).apply(lambda x: x.dt.tz_localize(tz1))
+ second = DataFrame([item]).apply(lambda x: x.dt.tz_localize(tz2))
+
+ result = concat([first, second], axis=0)
+ expected = DataFrame(Series([pd.NaT, pd.NaT, item], index=[0, 1, 0]))
+ expected = expected.apply(lambda x: x.dt.tz_localize(tz2))
+ if tz1 != tz2:
+ expected = expected.astype(object)
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("tz1", [None, "UTC"])
+ @pytest.mark.parametrize("tz2", [None, "UTC"])
+ def test_concat_NaT_dataframes_all_NaT_axis_1(self, tz1, tz2):
+ # GH 12396
+
+ first = DataFrame(Series([pd.NaT, pd.NaT]).dt.tz_localize(tz1))
+ second = DataFrame(Series([pd.NaT]).dt.tz_localize(tz2), columns=[1])
+ expected = DataFrame(
+ {
+ 0: Series([pd.NaT, pd.NaT]).dt.tz_localize(tz1),
+ 1: Series([pd.NaT, pd.NaT]).dt.tz_localize(tz2),
+ }
+ )
+ result = concat([first, second], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("tz1", [None, "UTC"])
+ @pytest.mark.parametrize("tz2", [None, "UTC"])
+ def test_concat_NaT_series_dataframe_all_NaT(self, tz1, tz2):
+ # GH 12396
+
+ # tz-naive
+ first = Series([pd.NaT, pd.NaT]).dt.tz_localize(tz1)
+ second = DataFrame(
+ [
+ [Timestamp("2015/01/01", tz=tz2)],
+ [Timestamp("2016/01/01", tz=tz2)],
+ ],
+ index=[2, 3],
+ )
+
+ expected = DataFrame(
+ [
+ pd.NaT,
+ pd.NaT,
+ Timestamp("2015/01/01", tz=tz2),
+ Timestamp("2016/01/01", tz=tz2),
+ ]
+ )
+ if tz1 != tz2:
+ expected = expected.astype(object)
+
+ result = concat([first, second])
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_compat_on_non_ns_datetime_EA(self):
+ # GH#33331
+ first = Series(np.array([datetime(2010, 1, 1)], dtype="datetime64[D]"))
+ second = Series(pd.array(["a", "b"], dtype="category"))
+
+ expected = Series([Timestamp("2010-01-01 00:00:00"), "a", "b"])
+
+ result = concat([first, second], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+
+
+class TestTimezoneConcat:
+ def test_concat_tz_series(self):
+ # gh-11755: tz and no tz
+ x = Series(date_range("20151124 08:00", "20151124 09:00", freq="1h", tz="UTC"))
+ y = Series(date_range("2012-01-01", "2012-01-02"))
+ expected = Series([x[0], x[1], y[0], y[1]], dtype="object")
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_tz_series2(self):
+ # gh-11887: concat tz and object
+ x = Series(date_range("20151124 08:00", "20151124 09:00", freq="1h", tz="UTC"))
+ y = Series(["a", "b"])
+ expected = Series([x[0], x[1], y[0], y[1]], dtype="object")
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_tz_series3(self, unit, unit2):
+ # see gh-12217 and gh-12306
+ # Concatenating two UTC times
+ first = DataFrame([[datetime(2016, 1, 1)]], dtype=f"M8[{unit}]")
+ first[0] = first[0].dt.tz_localize("UTC")
+
+ second = DataFrame([[datetime(2016, 1, 2)]], dtype=f"M8[{unit2}]")
+ second[0] = second[0].dt.tz_localize("UTC")
+
+ result = concat([first, second])
+ exp_unit = tm.get_finest_unit(unit, unit2)
+ assert result[0].dtype == f"datetime64[{exp_unit}, UTC]"
+
+ def test_concat_tz_series4(self, unit, unit2):
+ # Concatenating two London times
+ first = DataFrame([[datetime(2016, 1, 1)]], dtype=f"M8[{unit}]")
+ first[0] = first[0].dt.tz_localize("Europe/London")
+
+ second = DataFrame([[datetime(2016, 1, 2)]], dtype=f"M8[{unit2}]")
+ second[0] = second[0].dt.tz_localize("Europe/London")
+
+ result = concat([first, second])
+ exp_unit = tm.get_finest_unit(unit, unit2)
+ assert result[0].dtype == f"datetime64[{exp_unit}, Europe/London]"
+
+ def test_concat_tz_series5(self, unit, unit2):
+ # Concatenating 2+1 London times
+ first = DataFrame(
+ [[datetime(2016, 1, 1)], [datetime(2016, 1, 2)]], dtype=f"M8[{unit}]"
+ )
+ first[0] = first[0].dt.tz_localize("Europe/London")
+
+ second = DataFrame([[datetime(2016, 1, 3)]], dtype=f"M8[{unit2}]")
+ second[0] = second[0].dt.tz_localize("Europe/London")
+
+ result = concat([first, second])
+ exp_unit = tm.get_finest_unit(unit, unit2)
+ assert result[0].dtype == f"datetime64[{exp_unit}, Europe/London]"
+
+ def test_concat_tz_series6(self, unit, unit2):
+ # Concatenating 1+2 London times
+ first = DataFrame([[datetime(2016, 1, 1)]], dtype=f"M8[{unit}]")
+ first[0] = first[0].dt.tz_localize("Europe/London")
+
+ second = DataFrame(
+ [[datetime(2016, 1, 2)], [datetime(2016, 1, 3)]], dtype=f"M8[{unit2}]"
+ )
+ second[0] = second[0].dt.tz_localize("Europe/London")
+
+ result = concat([first, second])
+ exp_unit = tm.get_finest_unit(unit, unit2)
+ assert result[0].dtype == f"datetime64[{exp_unit}, Europe/London]"
+
+ def test_concat_tz_series_tzlocal(self):
+ # see gh-13583
+ x = [
+ Timestamp("2011-01-01", tz=dateutil.tz.tzlocal()),
+ Timestamp("2011-02-01", tz=dateutil.tz.tzlocal()),
+ ]
+ y = [
+ Timestamp("2012-01-01", tz=dateutil.tz.tzlocal()),
+ Timestamp("2012-02-01", tz=dateutil.tz.tzlocal()),
+ ]
+
+ result = concat([Series(x), Series(y)], ignore_index=True)
+ tm.assert_series_equal(result, Series(x + y))
+ assert result.dtype == "datetime64[us, tzlocal()]"
+
+ def test_concat_tz_series_with_datetimelike(self):
+ # see gh-12620: tz and timedelta
+ x = [
+ Timestamp("2011-01-01", tz="US/Eastern"),
+ Timestamp("2011-02-01", tz="US/Eastern"),
+ ]
+ y = [pd.Timedelta("1 day"), pd.Timedelta("2 day")]
+ result = concat([Series(x), Series(y)], ignore_index=True)
+ tm.assert_series_equal(result, Series(x + y, dtype="object"))
+
+ # tz and period
+ y = [pd.Period("2011-03", freq="M"), pd.Period("2011-04", freq="M")]
+ result = concat([Series(x), Series(y)], ignore_index=True)
+ tm.assert_series_equal(result, Series(x + y, dtype="object"))
+
+ def test_concat_tz_frame(self):
+ df2 = DataFrame(
+ {
+ "A": Timestamp("20130102", tz="US/Eastern"),
+ "B": Timestamp("20130603", tz="CET"),
+ },
+ index=range(5),
+ )
+
+ # concat
+ df3 = concat([df2.A.to_frame(), df2.B.to_frame()], axis=1)
+ tm.assert_frame_equal(df2, df3)
+
+ def test_concat_multiple_tzs(self):
+ # GH#12467
+ # combining datetime tz-aware and naive DataFrames
+ ts1 = Timestamp("2015-01-01", tz=None)
+ ts2 = Timestamp("2015-01-01", tz="UTC")
+ ts3 = Timestamp("2015-01-01", tz="EST")
+
+ df1 = DataFrame({"time": [ts1]})
+ df2 = DataFrame({"time": [ts2]})
+ df3 = DataFrame({"time": [ts3]})
+
+ results = concat([df1, df2]).reset_index(drop=True)
+ expected = DataFrame({"time": [ts1, ts2]}, dtype=object)
+ tm.assert_frame_equal(results, expected)
+
+ results = concat([df1, df3]).reset_index(drop=True)
+ expected = DataFrame({"time": [ts1, ts3]}, dtype=object)
+ tm.assert_frame_equal(results, expected)
+
+ results = concat([df2, df3]).reset_index(drop=True)
+ expected = DataFrame({"time": [ts2, ts3]})
+ tm.assert_frame_equal(results, expected)
+
+ def test_concat_multiindex_with_tz(self):
+ # GH 6606
+ df = DataFrame(
+ {
+ "dt": DatetimeIndex(
+ [
+ datetime(2014, 1, 1),
+ datetime(2014, 1, 2),
+ datetime(2014, 1, 3),
+ ],
+ dtype="M8[ns, US/Pacific]",
+ ),
+ "b": ["A", "B", "C"],
+ "c": [1, 2, 3],
+ "d": [4, 5, 6],
+ }
+ )
+ df = df.set_index(["dt", "b"])
+
+ exp_idx1 = DatetimeIndex(
+ ["2014-01-01", "2014-01-02", "2014-01-03"] * 2,
+ dtype="M8[ns, US/Pacific]",
+ name="dt",
+ )
+ exp_idx2 = Index(["A", "B", "C"] * 2, name="b")
+ exp_idx = MultiIndex.from_arrays([exp_idx1, exp_idx2])
+ expected = DataFrame(
+ {"c": [1, 2, 3] * 2, "d": [4, 5, 6] * 2}, index=exp_idx, columns=["c", "d"]
+ )
+
+ result = concat([df, df])
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_tz_not_aligned(self):
+ # GH#22796
+ ts = pd.to_datetime([1, 2]).tz_localize("UTC")
+ a = DataFrame({"A": ts})
+ b = DataFrame({"A": ts, "B": ts})
+ result = concat([a, b], sort=True, ignore_index=True)
+ expected = DataFrame(
+ {"A": list(ts) + list(ts), "B": [pd.NaT, pd.NaT, *list(ts)]}
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "t1",
+ [
+ "2015-01-01",
+ pytest.param(
+ pd.NaT,
+ marks=pytest.mark.xfail(
+ reason="GH23037 incorrect dtype when concatenating"
+ ),
+ ),
+ ],
+ )
+ def test_concat_tz_NaT(self, t1):
+ # GH#22796
+ # Concatenating tz-aware multicolumn DataFrames
+ ts1 = Timestamp(t1, tz="UTC")
+ ts2 = Timestamp("2015-01-01", tz="UTC")
+ ts3 = Timestamp("2015-01-01", tz="UTC")
+
+ df1 = DataFrame([[ts1, ts2]])
+ df2 = DataFrame([[ts3]])
+
+ result = concat([df1, df2])
+ expected = DataFrame([[ts1, ts2], [ts3, pd.NaT]], index=[0, 0])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_tz_with_empty(self):
+ # GH 9188
+ result = concat(
+ [DataFrame(date_range("2000", periods=1, tz="UTC")), DataFrame()]
+ )
+ expected = DataFrame(date_range("2000", periods=1, tz="UTC"))
+ tm.assert_frame_equal(result, expected)
+
+
+class TestPeriodConcat:
+ def test_concat_period_series(self):
+ x = Series(pd.PeriodIndex(["2015-11-01", "2015-12-01"], freq="D"))
+ y = Series(pd.PeriodIndex(["2015-10-01", "2016-01-01"], freq="D"))
+ expected = Series([x[0], x[1], y[0], y[1]], dtype="Period[D]")
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_period_multiple_freq_series(self):
+ x = Series(pd.PeriodIndex(["2015-11-01", "2015-12-01"], freq="D"))
+ y = Series(pd.PeriodIndex(["2015-10-01", "2016-01-01"], freq="M"))
+ expected = Series([x[0], x[1], y[0], y[1]], dtype="object")
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+ assert result.dtype == "object"
+
+ def test_concat_period_other_series(self):
+ x = Series(pd.PeriodIndex(["2015-11-01", "2015-12-01"], freq="D"))
+ y = Series(pd.PeriodIndex(["2015-11-01", "2015-12-01"], freq="M"))
+ expected = Series([x[0], x[1], y[0], y[1]], dtype="object")
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+ assert result.dtype == "object"
+
+ def test_concat_period_other_series2(self):
+ # non-period
+ x = Series(pd.PeriodIndex(["2015-11-01", "2015-12-01"], freq="D"))
+ y = Series(DatetimeIndex(["2015-11-01", "2015-12-01"]))
+ expected = Series([x[0], x[1], y[0], y[1]], dtype="object")
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+ assert result.dtype == "object"
+
+ def test_concat_period_other_series3(self):
+ x = Series(pd.PeriodIndex(["2015-11-01", "2015-12-01"], freq="D"))
+ y = Series(["A", "B"])
+ expected = Series([x[0], x[1], y[0], y[1]], dtype="object")
+ result = concat([x, y], ignore_index=True)
+ tm.assert_series_equal(result, expected)
+ assert result.dtype == "object"
+
+
+def test_concat_timedelta64_block():
+ rng = to_timedelta(np.arange(10), unit="s")
+
+ df = DataFrame({"time": rng})
+
+ result = concat([df, df])
+ tm.assert_frame_equal(result.iloc[:10], df, check_index_type=False)
+ tm.assert_frame_equal(result.iloc[10:], df, check_index_type=False)
+
+
+def test_concat_multiindex_datetime_nat():
+ # GH#44900
+ left = DataFrame({"a": 1}, index=MultiIndex.from_tuples([(1, pd.NaT)]))
+ right = DataFrame(
+ {"b": 2}, index=MultiIndex.from_tuples([(1, pd.NaT), (2, pd.NaT)])
+ )
+ result = concat([left, right], axis="columns")
+ expected = DataFrame(
+ {"a": [1.0, np.nan], "b": 2}, MultiIndex.from_tuples([(1, pd.NaT), (2, pd.NaT)])
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_concat_float_datetime64():
+ # GH#32934
+ df_time = DataFrame({"A": pd.array(["2000"], dtype="datetime64[ns]")})
+ df_float = DataFrame({"A": pd.array([1.0], dtype="float64")})
+
+ expected = DataFrame(
+ {
+ "A": [
+ pd.array(["2000"], dtype="datetime64[ns]")[0],
+ pd.array([1.0], dtype="float64")[0],
+ ]
+ },
+ index=[0, 0],
+ )
+ result = concat([df_time, df_float])
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame({"A": pd.array([], dtype="object")})
+ result = concat([df_time.iloc[:0], df_float.iloc[:0]])
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame({"A": pd.array([1.0], dtype="object")})
+ result = concat([df_time.iloc[:0], df_float])
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame({"A": pd.array(["2000"], dtype="datetime64[ns]")}).astype(
+ object
+ )
+
+ result = concat([df_time, df_float.iloc[:0]])
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_empty.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_empty.py
new file mode 100644
index 0000000000000000000000000000000000000000..4869cfbf3a5568c7d082ff39a58489a0479b158c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_empty.py
@@ -0,0 +1,293 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ RangeIndex,
+ Series,
+ concat,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestEmptyConcat:
+ def test_handle_empty_objects(self, sort, using_infer_string):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)), columns=list("abcd")
+ )
+
+ dfcopy = df[:5].copy()
+ dfcopy["foo"] = "bar"
+ empty = df[5:5]
+
+ frames = [dfcopy, empty, empty, df[5:]]
+ concatted = concat(frames, axis=0, sort=sort)
+
+ expected = df.reindex(columns=["a", "b", "c", "d", "foo"])
+ expected["foo"] = expected["foo"].astype(
+ object if not using_infer_string else "str"
+ )
+ expected.loc[0:4, "foo"] = "bar"
+
+ tm.assert_frame_equal(concatted, expected)
+
+ # empty as first element with time series
+ # GH3259
+ df = DataFrame(
+ {"A": range(10000)}, index=date_range("20130101", periods=10000, freq="s")
+ )
+ empty = DataFrame()
+ result = concat([df, empty], axis=1)
+ tm.assert_frame_equal(result, df)
+ result = concat([empty, df], axis=1)
+ tm.assert_frame_equal(result, df)
+
+ result = concat([df, empty])
+ tm.assert_frame_equal(result, df)
+ result = concat([empty, df])
+ tm.assert_frame_equal(result, df)
+
+ def test_concat_empty_series(self):
+ # GH 11082
+ s1 = Series([1, 2, 3], name="x")
+ s2 = Series(name="y", dtype="float64")
+ res = concat([s1, s2], axis=1)
+ exp = DataFrame(
+ {"x": [1, 2, 3], "y": [np.nan, np.nan, np.nan]},
+ index=RangeIndex(3),
+ )
+ tm.assert_frame_equal(res, exp)
+
+ s1 = Series([1, 2, 3], name="x")
+ s2 = Series(name="y", dtype="float64")
+ res = concat([s1, s2], axis=0)
+ # name will be reset
+ exp = Series([1, 2, 3], dtype="float64")
+ tm.assert_series_equal(res, exp)
+
+ # empty Series with no name
+ s1 = Series([1, 2, 3], name="x")
+ s2 = Series(name=None, dtype="float64")
+ res = concat([s1, s2], axis=1)
+ exp = DataFrame(
+ {"x": [1, 2, 3], 0: [np.nan, np.nan, np.nan]},
+ columns=["x", 0],
+ index=RangeIndex(3),
+ )
+ tm.assert_frame_equal(res, exp)
+
+ @pytest.mark.parametrize("tz", [None, "UTC"])
+ @pytest.mark.parametrize("values", [[], [1, 2, 3]])
+ def test_concat_empty_series_timelike(self, tz, values):
+ # GH 18447
+
+ first = Series([], dtype="M8[ns]").dt.tz_localize(tz)
+ dtype = None if values else np.float64
+ second = Series(values, dtype=dtype)
+
+ expected = DataFrame(
+ {
+ 0: Series([pd.NaT] * len(values), dtype="M8[ns]").dt.tz_localize(tz),
+ 1: values,
+ }
+ )
+ result = concat([first, second], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "left,right,expected",
+ [
+ # booleans
+ (np.bool_, np.int32, np.object_), # changed from int32 in 2.0 GH#39817
+ (np.bool_, np.float32, np.object_),
+ # datetime-like
+ ("m8[ns]", np.bool_, np.object_),
+ ("m8[ns]", np.int64, np.object_),
+ ("M8[ns]", np.bool_, np.object_),
+ ("M8[ns]", np.int64, np.object_),
+ # categorical
+ ("category", "category", "category"),
+ ("category", "object", "object"),
+ ],
+ )
+ def test_concat_empty_series_dtypes(self, left, right, expected):
+ # GH#39817, GH#45101
+ result = concat([Series(dtype=left), Series(dtype=right)])
+ assert result.dtype == expected
+
+ @pytest.mark.parametrize(
+ "dtype", ["float64", "int8", "uint8", "bool", "m8[ns]", "M8[ns]"]
+ )
+ def test_concat_empty_series_dtypes_match_roundtrips(self, dtype):
+ dtype = np.dtype(dtype)
+
+ result = concat([Series(dtype=dtype)])
+ assert result.dtype == dtype
+
+ result = concat([Series(dtype=dtype), Series(dtype=dtype)])
+ assert result.dtype == dtype
+
+ @pytest.mark.parametrize("dtype", ["float64", "int8", "uint8", "m8[ns]", "M8[ns]"])
+ @pytest.mark.parametrize(
+ "dtype2",
+ ["float64", "int8", "uint8", "m8[ns]", "M8[ns]"],
+ )
+ def test_concat_empty_series_dtypes_roundtrips(self, dtype, dtype2):
+ # round-tripping with self & like self
+ if dtype == dtype2:
+ pytest.skip("same dtype is not applicable for test")
+
+ def int_result_type(dtype, dtype2):
+ typs = {dtype.kind, dtype2.kind}
+ if not len(typs - {"i", "u", "b"}) and (
+ dtype.kind == "i" or dtype2.kind == "i"
+ ):
+ return "i"
+ elif not len(typs - {"u", "b"}) and (
+ dtype.kind == "u" or dtype2.kind == "u"
+ ):
+ return "u"
+ return None
+
+ def float_result_type(dtype, dtype2):
+ typs = {dtype.kind, dtype2.kind}
+ if not len(typs - {"f", "i", "u"}) and (
+ dtype.kind == "f" or dtype2.kind == "f"
+ ):
+ return "f"
+ return None
+
+ def get_result_type(dtype, dtype2):
+ result = float_result_type(dtype, dtype2)
+ if result is not None:
+ return result
+ result = int_result_type(dtype, dtype2)
+ if result is not None:
+ return result
+ return "O"
+
+ dtype = np.dtype(dtype)
+ dtype2 = np.dtype(dtype2)
+ expected = get_result_type(dtype, dtype2)
+ result = concat([Series(dtype=dtype), Series(dtype=dtype2)]).dtype
+ assert result.kind == expected
+
+ def test_concat_empty_series_dtypes_triple(self):
+ assert (
+ concat(
+ [Series(dtype="M8[ns]"), Series(dtype=np.bool_), Series(dtype=np.int64)]
+ ).dtype
+ == np.object_
+ )
+
+ def test_concat_empty_series_dtype_category_with_array(self):
+ # GH#18515
+ assert (
+ concat(
+ [Series(np.array([]), dtype="category"), Series(dtype="float64")]
+ ).dtype
+ == "float64"
+ )
+
+ def test_concat_empty_series_dtypes_sparse(self):
+ result = concat(
+ [
+ Series(dtype="float64").astype("Sparse"),
+ Series(dtype="float64").astype("Sparse"),
+ ]
+ )
+ assert result.dtype == "Sparse[float64]"
+
+ result = concat(
+ [Series(dtype="float64").astype("Sparse"), Series(dtype="float64")]
+ )
+ expected = pd.SparseDtype(np.float64)
+ assert result.dtype == expected
+
+ result = concat(
+ [Series(dtype="float64").astype("Sparse"), Series(dtype="object")]
+ )
+ expected = pd.SparseDtype("object")
+ assert result.dtype == expected
+
+ def test_concat_empty_df_object_dtype(self):
+ # GH 9149
+ df_1 = DataFrame({"Row": [0, 1, 1], "EmptyCol": np.nan, "NumberCol": [1, 2, 3]})
+ df_2 = DataFrame(columns=df_1.columns)
+ result = concat([df_1, df_2], axis=0)
+ expected = df_1.astype(object)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_empty_dataframe_dtypes(self):
+ df = DataFrame(columns=list("abc"))
+ df["a"] = df["a"].astype(np.bool_)
+ df["b"] = df["b"].astype(np.int32)
+ df["c"] = df["c"].astype(np.float64)
+
+ result = concat([df, df])
+ assert result["a"].dtype == np.bool_
+ assert result["b"].dtype == np.int32
+ assert result["c"].dtype == np.float64
+
+ result = concat([df, df.astype(np.float64)])
+ assert result["a"].dtype == np.object_
+ assert result["b"].dtype == np.float64
+ assert result["c"].dtype == np.float64
+
+ def test_concat_inner_join_empty(self):
+ # GH 15328
+ df_empty = DataFrame()
+ df_a = DataFrame({"a": [1, 2]}, index=[0, 1], dtype="int64")
+ df_expected = DataFrame({"a": []}, index=RangeIndex(0), dtype="int64")
+
+ result = concat([df_a, df_empty], axis=1, join="inner")
+ tm.assert_frame_equal(result, df_expected)
+
+ result = concat([df_a, df_empty], axis=1, join="outer")
+ tm.assert_frame_equal(result, df_a)
+
+ def test_empty_dtype_coerce(self):
+ # xref to #12411
+ # xref to #12045
+ # xref to #11594
+ # see below
+
+ # 10571
+ df1 = DataFrame(data=[[1, None], [2, None]], columns=["a", "b"])
+ df2 = DataFrame(data=[[3, None], [4, None]], columns=["a", "b"])
+ result = concat([df1, df2])
+ expected = df1.dtypes
+ tm.assert_series_equal(result.dtypes, expected)
+
+ def test_concat_empty_dataframe(self):
+ # 39037
+ df1 = DataFrame(columns=["a", "b"])
+ df2 = DataFrame(columns=["b", "c"])
+ result = concat([df1, df2, df1])
+ expected = DataFrame(columns=["a", "b", "c"])
+ tm.assert_frame_equal(result, expected)
+
+ df3 = DataFrame(columns=["a", "b"])
+ df4 = DataFrame(columns=["b"])
+ result = concat([df3, df4])
+ expected = DataFrame(columns=["a", "b"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_empty_dataframe_different_dtypes(self, using_infer_string):
+ # 39037
+ df1 = DataFrame({"a": [1, 2, 3], "b": ["a", "b", "c"]})
+ df2 = DataFrame({"a": [1, 2, 3]})
+
+ result = concat([df1[:0], df2[:0]])
+ assert result["a"].dtype == np.int64
+ assert result["b"].dtype == np.object_ if not using_infer_string else "str"
+
+ def test_concat_to_empty_ea(self):
+ """48510 `concat` to an empty EA should maintain type EA dtype."""
+ df_empty = DataFrame({"a": pd.array([], dtype=pd.Int64Dtype())})
+ df_new = DataFrame({"a": pd.array([1, 2, 3], dtype=pd.Int64Dtype())})
+ expected = df_new.copy()
+ result = concat([df_empty, df_new])
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_index.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..35649d738ea0ff32dd80979c68f32a1313e349a8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_index.py
@@ -0,0 +1,456 @@
+from copy import deepcopy
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ concat,
+)
+import pandas._testing as tm
+
+
+class TestIndexConcat:
+ def test_concat_ignore_index(self, sort):
+ frame1 = DataFrame(
+ {"test1": ["a", "b", "c"], "test2": [1, 2, 3], "test3": [4.5, 3.2, 1.2]}
+ )
+ frame2 = DataFrame({"test3": [5.2, 2.2, 4.3]})
+ frame1.index = Index(["x", "y", "z"])
+ frame2.index = Index(["x", "y", "q"])
+
+ v1 = concat([frame1, frame2], axis=1, ignore_index=True, sort=sort)
+
+ nan = np.nan
+ expected = DataFrame(
+ [
+ [nan, nan, nan, 4.3],
+ ["a", 1, 4.5, 5.2],
+ ["b", 2, 3.2, 2.2],
+ ["c", 3, 1.2, nan],
+ ],
+ index=Index(["q", "x", "y", "z"]),
+ )
+ if not sort:
+ expected = expected.loc[["x", "y", "z", "q"]]
+
+ tm.assert_frame_equal(v1, expected)
+
+ @pytest.mark.parametrize(
+ "name_in1,name_in2,name_in3,name_out",
+ [
+ ("idx", "idx", "idx", "idx"),
+ ("idx", "idx", None, None),
+ ("idx", None, None, None),
+ ("idx1", "idx2", None, None),
+ ("idx1", "idx1", "idx2", None),
+ ("idx1", "idx2", "idx3", None),
+ (None, None, None, None),
+ ],
+ )
+ def test_concat_same_index_names(self, name_in1, name_in2, name_in3, name_out):
+ # GH13475
+ indices = [
+ Index(["a", "b", "c"], name=name_in1),
+ Index(["b", "c", "d"], name=name_in2),
+ Index(["c", "d", "e"], name=name_in3),
+ ]
+ frames = [
+ DataFrame({c: [0, 1, 2]}, index=i) for i, c in zip(indices, ["x", "y", "z"])
+ ]
+ result = concat(frames, axis=1)
+
+ exp_ind = Index(["a", "b", "c", "d", "e"], name=name_out)
+ expected = DataFrame(
+ {
+ "x": [0, 1, 2, np.nan, np.nan],
+ "y": [np.nan, 0, 1, 2, np.nan],
+ "z": [np.nan, np.nan, 0, 1, 2],
+ },
+ index=exp_ind,
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_rename_index(self):
+ a = DataFrame(
+ np.random.default_rng(2).random((3, 3)),
+ columns=list("ABC"),
+ index=Index(list("abc"), name="index_a"),
+ )
+ b = DataFrame(
+ np.random.default_rng(2).random((3, 3)),
+ columns=list("ABC"),
+ index=Index(list("abc"), name="index_b"),
+ )
+
+ result = concat([a, b], keys=["key0", "key1"], names=["lvl0", "lvl1"])
+
+ exp = concat([a, b], keys=["key0", "key1"], names=["lvl0"])
+ names = list(exp.index.names)
+ names[1] = "lvl1"
+ exp.index.set_names(names, inplace=True)
+
+ tm.assert_frame_equal(result, exp)
+ assert result.index.names == exp.index.names
+
+ def test_concat_copy_index_series(self, axis):
+ # GH 29879
+ ser = Series([1, 2])
+ comb = concat([ser, ser], axis=axis)
+ if axis in [0, "index"]:
+ assert comb.index is not ser.index
+ else:
+ assert comb.index is ser.index
+
+ def test_concat_copy_index_frame(self, axis):
+ # GH 29879
+ df = DataFrame([[1, 2], [3, 4]], columns=["a", "b"])
+ comb = concat([df, df], axis=axis)
+ if axis in [0, "index"]:
+ assert not comb.index.is_(df.index)
+ assert comb.columns.is_(df.columns)
+ elif axis in [1, "columns"]:
+ assert comb.index.is_(df.index)
+ assert not comb.columns.is_(df.columns)
+
+ def test_default_index(self):
+ # is_series and ignore_index
+ s1 = Series([1, 2, 3], name="x")
+ s2 = Series([4, 5, 6], name="y")
+ res = concat([s1, s2], axis=1, ignore_index=True)
+ assert isinstance(res.columns, pd.RangeIndex)
+ exp = DataFrame([[1, 4], [2, 5], [3, 6]])
+ # use check_index_type=True to check the result have
+ # RangeIndex (default index)
+ tm.assert_frame_equal(res, exp, check_index_type=True, check_column_type=True)
+
+ # is_series and all inputs have no names
+ s1 = Series([1, 2, 3])
+ s2 = Series([4, 5, 6])
+ res = concat([s1, s2], axis=1, ignore_index=False)
+ assert isinstance(res.columns, pd.RangeIndex)
+ exp = DataFrame([[1, 4], [2, 5], [3, 6]])
+ exp.columns = pd.RangeIndex(2)
+ tm.assert_frame_equal(res, exp, check_index_type=True, check_column_type=True)
+
+ # is_dataframe and ignore_index
+ df1 = DataFrame({"A": [1, 2], "B": [5, 6]})
+ df2 = DataFrame({"A": [3, 4], "B": [7, 8]})
+
+ res = concat([df1, df2], axis=0, ignore_index=True)
+ exp = DataFrame([[1, 5], [2, 6], [3, 7], [4, 8]], columns=["A", "B"])
+ tm.assert_frame_equal(res, exp, check_index_type=True, check_column_type=True)
+
+ res = concat([df1, df2], axis=1, ignore_index=True)
+ exp = DataFrame([[1, 5, 3, 7], [2, 6, 4, 8]])
+ tm.assert_frame_equal(res, exp, check_index_type=True, check_column_type=True)
+
+ def test_dups_index(self):
+ # GH 4771
+
+ # single dtypes
+ df = DataFrame(
+ np.random.default_rng(2).integers(0, 10, size=40).reshape(10, 4),
+ columns=["A", "A", "C", "C"],
+ )
+
+ result = concat([df, df], axis=1)
+ tm.assert_frame_equal(result.iloc[:, :4], df)
+ tm.assert_frame_equal(result.iloc[:, 4:], df)
+
+ result = concat([df, df], axis=0)
+ tm.assert_frame_equal(result.iloc[:10], df)
+ tm.assert_frame_equal(result.iloc[10:], df)
+
+ # multi dtypes
+ df = concat(
+ [
+ DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=["A", "A", "B", "B"],
+ ),
+ DataFrame(
+ np.random.default_rng(2).integers(0, 10, size=20).reshape(10, 2),
+ columns=["A", "C"],
+ ),
+ ],
+ axis=1,
+ )
+
+ result = concat([df, df], axis=1)
+ tm.assert_frame_equal(result.iloc[:, :6], df)
+ tm.assert_frame_equal(result.iloc[:, 6:], df)
+
+ result = concat([df, df], axis=0)
+ tm.assert_frame_equal(result.iloc[:10], df)
+ tm.assert_frame_equal(result.iloc[10:], df)
+
+
+class TestMultiIndexConcat:
+ def test_concat_multiindex_with_keys(self, multiindex_dataframe_random_data):
+ frame = multiindex_dataframe_random_data
+ index = frame.index
+ result = concat([frame, frame], keys=[0, 1], names=["iteration"])
+
+ assert result.index.names == ("iteration", *index.names)
+ tm.assert_frame_equal(result.loc[0], frame)
+ tm.assert_frame_equal(result.loc[1], frame)
+ assert result.index.nlevels == 3
+
+ def test_concat_multiindex_with_none_in_index_names(self):
+ # GH 15787
+ index = MultiIndex.from_product([[1], range(5)], names=["level1", None])
+ df = DataFrame({"col": range(5)}, index=index, dtype=np.int32)
+
+ result = concat([df, df], keys=[1, 2], names=["level2"])
+ index = MultiIndex.from_product(
+ [[1, 2], [1], range(5)], names=["level2", "level1", None]
+ )
+ expected = DataFrame({"col": list(range(5)) * 2}, index=index, dtype=np.int32)
+ tm.assert_frame_equal(result, expected)
+
+ result = concat([df, df[:2]], keys=[1, 2], names=["level2"])
+ level2 = [1] * 5 + [2] * 2
+ level1 = [1] * 7
+ no_name = list(range(5)) + list(range(2))
+ tuples = list(zip(level2, level1, no_name))
+ index = MultiIndex.from_tuples(tuples, names=["level2", "level1", None])
+ expected = DataFrame({"col": no_name}, index=index, dtype=np.int32)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_multiindex_rangeindex(self):
+ # GH13542
+ # when multi-index levels are RangeIndex objects
+ # there is a bug in concat with objects of len 1
+
+ df = DataFrame(np.random.default_rng(2).standard_normal((9, 2)))
+ df.index = MultiIndex(
+ levels=[pd.RangeIndex(3), pd.RangeIndex(3)],
+ codes=[np.repeat(np.arange(3), 3), np.tile(np.arange(3), 3)],
+ )
+
+ res = concat([df.iloc[[2, 3, 4], :], df.iloc[[5], :]])
+ exp = df.iloc[[2, 3, 4, 5], :]
+ tm.assert_frame_equal(res, exp)
+
+ def test_concat_multiindex_dfs_with_deepcopy(self):
+ # GH 9967
+ example_multiindex1 = MultiIndex.from_product([["a"], ["b"]])
+ example_dataframe1 = DataFrame([0], index=example_multiindex1)
+
+ example_multiindex2 = MultiIndex.from_product([["a"], ["c"]])
+ example_dataframe2 = DataFrame([1], index=example_multiindex2)
+
+ example_dict = {"s1": example_dataframe1, "s2": example_dataframe2}
+ expected_index = MultiIndex(
+ levels=[["s1", "s2"], ["a"], ["b", "c"]],
+ codes=[[0, 1], [0, 0], [0, 1]],
+ names=["testname", None, None],
+ )
+ expected = DataFrame([[0], [1]], index=expected_index)
+ result_copy = concat(deepcopy(example_dict), names=["testname"])
+ tm.assert_frame_equal(result_copy, expected)
+ result_no_copy = concat(example_dict, names=["testname"])
+ tm.assert_frame_equal(result_no_copy, expected)
+
+ @pytest.mark.parametrize(
+ "mi1_list",
+ [
+ [["a"], range(2)],
+ [["b"], np.arange(2.0, 4.0)],
+ [["c"], ["A", "B"]],
+ [["d"], pd.date_range(start="2017", end="2018", periods=2)],
+ ],
+ )
+ @pytest.mark.parametrize(
+ "mi2_list",
+ [
+ [["a"], range(2)],
+ [["b"], np.arange(2.0, 4.0)],
+ [["c"], ["A", "B"]],
+ [["d"], pd.date_range(start="2017", end="2018", periods=2)],
+ ],
+ )
+ def test_concat_with_various_multiindex_dtypes(
+ self, mi1_list: list, mi2_list: list
+ ):
+ # GitHub #23478
+ mi1 = MultiIndex.from_product(mi1_list)
+ mi2 = MultiIndex.from_product(mi2_list)
+
+ df1 = DataFrame(np.zeros((1, len(mi1))), columns=mi1)
+ df2 = DataFrame(np.zeros((1, len(mi2))), columns=mi2)
+
+ if mi1_list[0] == mi2_list[0]:
+ expected_mi = MultiIndex(
+ levels=[mi1_list[0], list(mi1_list[1])],
+ codes=[[0, 0, 0, 0], [0, 1, 0, 1]],
+ )
+ else:
+ expected_mi = MultiIndex(
+ levels=[
+ mi1_list[0] + mi2_list[0],
+ list(mi1_list[1]) + list(mi2_list[1]),
+ ],
+ codes=[[0, 0, 1, 1], [0, 1, 2, 3]],
+ )
+
+ expected_df = DataFrame(np.zeros((1, len(expected_mi))), columns=expected_mi)
+
+ with tm.assert_produces_warning(None):
+ result_df = concat((df1, df2), axis=1)
+
+ tm.assert_frame_equal(expected_df, result_df)
+
+ def test_concat_multiindex_(self):
+ # GitHub #44786
+ df = DataFrame({"col": ["a", "b", "c"]}, index=["1", "2", "2"])
+ df = concat([df], keys=["X"])
+
+ iterables = [["X"], ["1", "2", "2"]]
+ result_index = df.index
+ expected_index = MultiIndex.from_product(iterables)
+
+ tm.assert_index_equal(result_index, expected_index)
+
+ result_df = df
+ expected_df = DataFrame(
+ {"col": ["a", "b", "c"]}, index=MultiIndex.from_product(iterables)
+ )
+ tm.assert_frame_equal(result_df, expected_df)
+
+ def test_concat_with_key_not_unique(self, performance_warning):
+ # GitHub #46519
+ df1 = DataFrame({"name": [1]})
+ df2 = DataFrame({"name": [2]})
+ df3 = DataFrame({"name": [3]})
+ df_a = concat([df1, df2, df3], keys=["x", "y", "x"])
+ # the warning is caused by indexing unsorted multi-index
+ with tm.assert_produces_warning(
+ performance_warning, match="indexing past lexsort depth"
+ ):
+ out_a = df_a.loc[("x", 0), :]
+ df_b = DataFrame(
+ {"name": [1, 2, 3]},
+ index=MultiIndex(
+ levels=[["x", "y"], range(1)], codes=[[0, 1, 0], [0, 0, 0]]
+ ),
+ )
+ with tm.assert_produces_warning(
+ performance_warning, match="indexing past lexsort depth"
+ ):
+ out_b = df_b.loc[("x", 0)]
+
+ tm.assert_frame_equal(out_a, out_b)
+
+ df1 = DataFrame({"name": ["a", "a", "b"]})
+ df2 = DataFrame({"name": ["a", "b"]})
+ df3 = DataFrame({"name": ["c", "d"]})
+ df_a = concat([df1, df2, df3], keys=["x", "y", "x"])
+ with tm.assert_produces_warning(
+ performance_warning, match="indexing past lexsort depth"
+ ):
+ out_a = df_a.loc[("x", 0), :]
+
+ df_b = DataFrame(
+ {
+ "a": ["x", "x", "x", "y", "y", "x", "x"],
+ "b": [0, 1, 2, 0, 1, 0, 1],
+ "name": list("aababcd"),
+ }
+ ).set_index(["a", "b"])
+ df_b.index.names = [None, None]
+ with tm.assert_produces_warning(
+ performance_warning, match="indexing past lexsort depth"
+ ):
+ out_b = df_b.loc[("x", 0), :]
+
+ tm.assert_frame_equal(out_a, out_b)
+
+ def test_concat_with_duplicated_levels(self):
+ # keyword levels should be unique
+ df1 = DataFrame({"A": [1]}, index=["x"])
+ df2 = DataFrame({"A": [1]}, index=["y"])
+ msg = r"Level values not unique: \['x', 'y', 'y'\]"
+ with pytest.raises(ValueError, match=msg):
+ concat([df1, df2], keys=["x", "y"], levels=[["x", "y", "y"]])
+
+ @pytest.mark.parametrize("levels", [[["x", "y"]], [["x", "y", "y"]]])
+ def test_concat_with_levels_with_none_keys(self, levels):
+ df1 = DataFrame({"A": [1]}, index=["x"])
+ df2 = DataFrame({"A": [1]}, index=["y"])
+ msg = "levels supported only when keys is not None"
+ with pytest.raises(ValueError, match=msg):
+ concat([df1, df2], levels=levels)
+
+ def test_concat_range_index_result(self):
+ # GH#47501
+ df1 = DataFrame({"a": [1, 2]})
+ df2 = DataFrame({"b": [1, 2]})
+
+ result = concat([df1, df2], sort=True, axis=1)
+ expected = DataFrame({"a": [1, 2], "b": [1, 2]})
+ tm.assert_frame_equal(result, expected)
+ expected_index = pd.RangeIndex(0, 2)
+ tm.assert_index_equal(result.index, expected_index, exact=True)
+
+ def test_concat_index_keep_dtype(self):
+ # GH#47329
+ df1 = DataFrame([[0, 1, 1]], columns=Index([1, 2, 3], dtype="object"))
+ df2 = DataFrame([[0, 1]], columns=Index([1, 2], dtype="object"))
+ result = concat([df1, df2], ignore_index=True, join="outer", sort=True)
+ expected = DataFrame(
+ [[0, 1, 1.0], [0, 1, np.nan]], columns=Index([1, 2, 3], dtype="object")
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_index_keep_dtype_ea_numeric(self, any_numeric_ea_dtype):
+ # GH#47329
+ df1 = DataFrame(
+ [[0, 1, 1]], columns=Index([1, 2, 3], dtype=any_numeric_ea_dtype)
+ )
+ df2 = DataFrame([[0, 1]], columns=Index([1, 2], dtype=any_numeric_ea_dtype))
+ result = concat([df1, df2], ignore_index=True, join="outer", sort=True)
+ expected = DataFrame(
+ [[0, 1, 1.0], [0, 1, np.nan]],
+ columns=Index([1, 2, 3], dtype=any_numeric_ea_dtype),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["Int8", "Int16", "Int32"])
+ def test_concat_index_find_common(self, dtype):
+ # GH#47329
+ df1 = DataFrame([[0, 1, 1]], columns=Index([1, 2, 3], dtype=dtype))
+ df2 = DataFrame([[0, 1]], columns=Index([1, 2], dtype="Int32"))
+ result = concat([df1, df2], ignore_index=True, join="outer", sort=True)
+ expected = DataFrame(
+ [[0, 1, 1.0], [0, 1, np.nan]], columns=Index([1, 2, 3], dtype="Int32")
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_axis_1_sort_false_rangeindex(self, using_infer_string):
+ # GH 46675
+ s1 = Series(["a", "b", "c"])
+ s2 = Series(["a", "b"])
+ s3 = Series(["a", "b", "c", "d"])
+ s4 = Series([], dtype=object if not using_infer_string else "str")
+ result = concat(
+ [s1, s2, s3, s4], sort=False, join="outer", ignore_index=False, axis=1
+ )
+ expected = DataFrame(
+ [
+ ["a"] * 3 + [np.nan],
+ ["b"] * 3 + [np.nan],
+ ["c", np.nan] * 2,
+ [np.nan] * 2 + ["d"] + [np.nan],
+ ],
+ dtype=object if not using_infer_string else "str",
+ )
+ tm.assert_frame_equal(
+ result, expected, check_index_type=True, check_column_type=True
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_invalid.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_invalid.py
new file mode 100644
index 0000000000000000000000000000000000000000..3910dfa38a4bce615b5ec47903ceec4a9c9c6714
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_invalid.py
@@ -0,0 +1,54 @@
+from io import StringIO
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ concat,
+ read_csv,
+)
+import pandas._testing as tm
+
+
+class TestInvalidConcat:
+ @pytest.mark.parametrize("obj", [1, {}, [1, 2], (1, 2)])
+ def test_concat_invalid(self, obj):
+ # trying to concat an ndframe with a non-ndframe
+ df1 = DataFrame(range(2))
+ msg = (
+ f"cannot concatenate object of type '{type(obj)}'; "
+ "only Series and DataFrame objs are valid"
+ )
+ with pytest.raises(TypeError, match=msg):
+ concat([df1, obj])
+
+ def test_concat_invalid_first_argument(self):
+ df1 = DataFrame(range(2))
+ msg = (
+ "first argument must be an iterable of pandas "
+ 'objects, you passed an object of type "DataFrame"'
+ )
+ with pytest.raises(TypeError, match=msg):
+ concat(df1)
+
+ def test_concat_generator_obj(self):
+ # generator ok though
+ concat(DataFrame(np.random.default_rng(2).random((5, 5))) for _ in range(3))
+
+ def test_concat_textreader_obj(self):
+ # text reader ok
+ # GH6583
+ data = """index,A,B,C,D
+ foo,2,3,4,5
+ bar,7,8,9,10
+ baz,12,13,14,15
+ qux,12,13,14,15
+ foo2,12,13,14,15
+ bar2,12,13,14,15
+ """
+
+ with read_csv(StringIO(data), chunksize=1) as reader:
+ result = concat(reader, ignore_index=True)
+ expected = read_csv(StringIO(data))
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_series.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_series.py
new file mode 100644
index 0000000000000000000000000000000000000000..d49c62c9d7d8099e9bc56774055bef724edb98e3
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_series.py
@@ -0,0 +1,184 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ MultiIndex,
+ Series,
+ concat,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestSeriesConcat:
+ @pytest.mark.parametrize("bool_dtype", [bool, "boolean"])
+ @pytest.mark.parametrize("dtype", [np.int64, np.float64, "Int64", "Float64"])
+ def test_concat_bool_and_numeric(self, bool_dtype, dtype):
+ # GH#21108, GH#45101
+ left = Series([True, False], dtype=bool_dtype)
+ right = Series([1, 2], dtype=dtype)
+ result = concat([left, right], ignore_index=True)
+ expected = Series([True, False, 1, 2], dtype=object)
+ assert result.iloc[0] is True
+ assert type(result.iloc[2]) in [int, float] # i.e. not bool
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_series(self):
+ ts = Series(
+ np.arange(20, dtype=np.float64),
+ index=date_range("2020-01-01", periods=20, unit="ns"),
+ name="foo",
+ )
+ ts.name = "foo"
+
+ pieces = [ts[:5], ts[5:15], ts[15:]]
+
+ result = concat(pieces)
+ tm.assert_series_equal(result, ts)
+ assert result.name == ts.name
+
+ result = concat(pieces, keys=[0, 1, 2])
+ expected = ts.copy()
+ exp_codes = [np.repeat([0, 1, 2], [len(x) for x in pieces]), np.arange(len(ts))]
+ exp_index = MultiIndex(
+ levels=[[0, 1, 2], DatetimeIndex(ts.index.to_numpy(dtype="M8[ns]"))],
+ codes=exp_codes,
+ )
+ expected.index = exp_index
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_empty_and_non_empty_series_regression(self):
+ # GH 18187 regression test
+ s1 = Series([1])
+ s2 = Series([], dtype=object)
+
+ expected = s1.astype(object)
+ result = concat([s1, s2])
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_series_axis1(self):
+ ts = Series(
+ np.arange(10, dtype=np.float64), index=date_range("2020-01-01", periods=10)
+ )
+
+ pieces = [ts[:-2], ts[2:], ts[2:-2]]
+
+ result = concat(pieces, axis=1)
+ expected = DataFrame(pieces).T
+ tm.assert_frame_equal(result, expected)
+
+ result = concat(pieces, keys=["A", "B", "C"], axis=1)
+ expected = DataFrame(pieces, index=["A", "B", "C"]).T
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_series_axis1_preserves_series_names(self):
+ # preserve series names, #2489
+ s = Series(np.random.default_rng(2).standard_normal(5), name="A")
+ s2 = Series(np.random.default_rng(2).standard_normal(5), name="B")
+
+ result = concat([s, s2], axis=1)
+ expected = DataFrame({"A": s, "B": s2})
+ tm.assert_frame_equal(result, expected)
+
+ s2.name = None
+ result = concat([s, s2], axis=1)
+ tm.assert_index_equal(result.columns, Index(["A", 0], dtype="object"))
+
+ def test_concat_series_axis1_with_reindex(self, sort):
+ # must reindex, #2603
+ s = Series(
+ np.random.default_rng(2).standard_normal(3), index=["c", "a", "b"], name="A"
+ )
+ s2 = Series(
+ np.random.default_rng(2).standard_normal(4),
+ index=["d", "a", "b", "c"],
+ name="B",
+ )
+ result = concat([s, s2], axis=1, sort=sort)
+ expected = DataFrame({"A": s, "B": s2}, index=["c", "a", "b", "d"])
+ if sort:
+ expected = expected.sort_index()
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_series_axis1_names_applied(self):
+ # ensure names argument is not ignored on axis=1, #23490
+ s = Series([1, 2, 3])
+ s2 = Series([4, 5, 6])
+ result = concat([s, s2], axis=1, keys=["a", "b"], names=["A"])
+ expected = DataFrame(
+ [[1, 4], [2, 5], [3, 6]], columns=Index(["a", "b"], name="A")
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = concat([s, s2], axis=1, keys=[("a", 1), ("b", 2)], names=["A", "B"])
+ expected = DataFrame(
+ [[1, 4], [2, 5], [3, 6]],
+ columns=MultiIndex.from_tuples([("a", 1), ("b", 2)], names=["A", "B"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_series_axis1_same_names_ignore_index(self):
+ dates = date_range("01-Jan-2013", "01-Jan-2014", freq="MS")[0:-1]
+ s1 = Series(
+ np.random.default_rng(2).standard_normal(len(dates)),
+ index=dates,
+ name="value",
+ )
+ s2 = Series(
+ np.random.default_rng(2).standard_normal(len(dates)),
+ index=dates,
+ name="value",
+ )
+
+ result = concat([s1, s2], axis=1, ignore_index=True)
+ expected = Index(range(2))
+
+ tm.assert_index_equal(result.columns, expected, exact=True)
+
+ @pytest.mark.parametrize("s1name", [np.int64(190), 190])
+ def test_concat_series_name_npscalar_tuple(self, s1name):
+ # GH21015
+ s2name = (43, 0)
+ s1 = Series({"a": 1, "b": 2}, name=s1name)
+ s2 = Series({"c": 5, "d": 6}, name=s2name)
+ result = concat([s1, s2])
+ expected = Series({"a": 1, "b": 2, "c": 5, "d": 6})
+ tm.assert_series_equal(result, expected)
+
+ def test_concat_series_partial_columns_names(self):
+ # GH10698
+ named_series = Series([1, 2], name="foo")
+ unnamed_series1 = Series([1, 2])
+ unnamed_series2 = Series([4, 5])
+
+ result = concat([named_series, unnamed_series1, unnamed_series2], axis=1)
+ expected = DataFrame(
+ {"foo": [1, 2], 0: [1, 2], 1: [4, 5]}, columns=["foo", 0, 1]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = concat(
+ [named_series, unnamed_series1, unnamed_series2],
+ axis=1,
+ keys=["red", "blue", "yellow"],
+ )
+ expected = DataFrame(
+ {"red": [1, 2], "blue": [1, 2], "yellow": [4, 5]},
+ columns=["red", "blue", "yellow"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = concat(
+ [named_series, unnamed_series1, unnamed_series2], axis=1, ignore_index=True
+ )
+ expected = DataFrame({0: [1, 2], 1: [1, 2], 2: [4, 5]})
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_series_length_one_reversed(self, frame_or_series):
+ # GH39401
+ obj = frame_or_series([100])
+ result = concat([obj.iloc[::-1]])
+ tm.assert_equal(result, obj)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_sort.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_sort.py
new file mode 100644
index 0000000000000000000000000000000000000000..2724f819588933cc307dd396fdcd024f04c38eaa
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/concat/test_sort.py
@@ -0,0 +1,118 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import DataFrame
+import pandas._testing as tm
+
+
+class TestConcatSort:
+ def test_concat_sorts_columns(self, sort):
+ # GH-4588
+ df1 = DataFrame({"a": [1, 2], "b": [1, 2]}, columns=["b", "a"])
+ df2 = DataFrame({"a": [3, 4], "c": [5, 6]})
+
+ # for sort=True/None
+ expected = DataFrame(
+ {"a": [1, 2, 3, 4], "b": [1, 2, None, None], "c": [None, None, 5, 6]},
+ columns=["a", "b", "c"],
+ )
+
+ if sort is False:
+ expected = expected[["b", "a", "c"]]
+
+ # default
+ with tm.assert_produces_warning(None):
+ result = pd.concat([df1, df2], ignore_index=True, sort=sort)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_sorts_index(self, sort):
+ df1 = DataFrame({"a": [1, 2, 3]}, index=["c", "a", "b"])
+ df2 = DataFrame({"b": [1, 2]}, index=["a", "b"])
+
+ # For True/None
+ expected = DataFrame(
+ {"a": [2, 3, 1], "b": [1, 2, None]},
+ index=["a", "b", "c"],
+ columns=["a", "b"],
+ )
+ if sort is False:
+ expected = expected.loc[["c", "a", "b"]]
+
+ # Warn and sort by default
+ with tm.assert_produces_warning(None):
+ result = pd.concat([df1, df2], axis=1, sort=sort)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_inner_sort(self, sort):
+ # https://github.com/pandas-dev/pandas/pull/20613
+ df1 = DataFrame(
+ {"a": [1, 2], "b": [1, 2], "c": [1, 2]}, columns=["b", "a", "c"]
+ )
+ df2 = DataFrame({"a": [1, 2], "b": [3, 4]}, index=[3, 4])
+
+ with tm.assert_produces_warning(None):
+ # unset sort should *not* warn for inner join
+ # since that never sorted
+ result = pd.concat([df1, df2], sort=sort, join="inner", ignore_index=True)
+
+ expected = DataFrame({"b": [1, 2, 3, 4], "a": [1, 2, 1, 2]}, columns=["b", "a"])
+ if sort is True:
+ expected = expected[["a", "b"]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_aligned_sort(self):
+ # GH-4588
+ df = DataFrame({"c": [1, 2], "b": [3, 4], "a": [5, 6]}, columns=["c", "b", "a"])
+ result = pd.concat([df, df], sort=True, ignore_index=True)
+ expected = DataFrame(
+ {"a": [5, 6, 5, 6], "b": [3, 4, 3, 4], "c": [1, 2, 1, 2]},
+ columns=["a", "b", "c"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = pd.concat(
+ [df, df[["c", "b"]]], join="inner", sort=True, ignore_index=True
+ )
+ expected = expected[["b", "c"]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_aligned_sort_does_not_raise(self):
+ # GH-4588
+ # We catch TypeErrors from sorting internally and do not re-raise.
+ df = DataFrame({1: [1, 2], "a": [3, 4]}, columns=[1, "a"])
+ expected = DataFrame({1: [1, 2, 1, 2], "a": [3, 4, 3, 4]}, columns=[1, "a"])
+ result = pd.concat([df, df], ignore_index=True, sort=True)
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_frame_with_sort_false(self):
+ # GH 43375
+ result = pd.concat(
+ [DataFrame({i: i}, index=[i]) for i in range(2, 0, -1)], sort=False
+ )
+ expected = DataFrame([[2, np.nan], [np.nan, 1]], index=[2, 1], columns=[2, 1])
+
+ tm.assert_frame_equal(result, expected)
+
+ # GH 37937
+ df1 = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}, index=[1, 2, 3])
+ df2 = DataFrame({"c": [7, 8, 9], "d": [10, 11, 12]}, index=[3, 1, 6])
+ result = pd.concat([df2, df1], axis=1, sort=False)
+ expected = DataFrame(
+ [
+ [7.0, 10.0, 3.0, 6.0],
+ [8.0, 11.0, 1.0, 4.0],
+ [9.0, 12.0, np.nan, np.nan],
+ [np.nan, np.nan, 2.0, 5.0],
+ ],
+ index=[3, 1, 6, 2],
+ columns=["c", "d", "a", "b"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_concat_sort_none_raises(self):
+ # GH#41518
+ df = DataFrame({1: [1, 2], "a": [3, 4]})
+ msg = "The 'sort' keyword only accepts boolean values; None was passed."
+ with pytest.raises(ValueError, match=msg):
+ pd.concat([df, df], sort=None)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_join.py
new file mode 100644
index 0000000000000000000000000000000000000000..c866b81a5349eea1944209bab572021513f62175
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_join.py
@@ -0,0 +1,1186 @@
+from datetime import datetime
+import re
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ Timestamp,
+ bdate_range,
+ concat,
+ merge,
+ option_context,
+)
+import pandas._testing as tm
+
+
+def get_test_data(ngroups=8, n=50):
+ unique_groups = list(range(ngroups))
+ arr = np.asarray(np.tile(unique_groups, n // ngroups))
+
+ if len(arr) < n:
+ arr = np.asarray(list(arr) + unique_groups[: n - len(arr)])
+
+ np.random.default_rng(2).shuffle(arr)
+ return arr
+
+
+class TestJoin:
+ # aggregate multiple columns
+ @pytest.fixture
+ def df(self):
+ df = DataFrame(
+ {
+ "key1": get_test_data(),
+ "key2": get_test_data(),
+ "data1": np.random.default_rng(2).standard_normal(50),
+ "data2": np.random.default_rng(2).standard_normal(50),
+ }
+ )
+
+ # exclude a couple keys for fun
+ df = df[df["key2"] > 1]
+ return df
+
+ @pytest.fixture
+ def df2(self):
+ return DataFrame(
+ {
+ "key1": get_test_data(n=10),
+ "key2": get_test_data(ngroups=4, n=10),
+ "value": np.random.default_rng(2).standard_normal(10),
+ }
+ )
+
+ @pytest.fixture
+ def target_source(self):
+ data = {
+ "A": [0.0, 1.0, 2.0, 3.0, 4.0],
+ "B": [0.0, 1.0, 0.0, 1.0, 0.0],
+ "C": ["foo1", "foo2", "foo3", "foo4", "foo5"],
+ "D": bdate_range("1/1/2009", periods=5),
+ }
+ target = DataFrame(data, index=Index(["a", "b", "c", "d", "e"], dtype=object))
+
+ # Join on string value
+
+ source = DataFrame(
+ {"MergedA": data["A"], "MergedD": data["D"]}, index=data["C"]
+ )
+ return target, source
+
+ def test_left_outer_join(self, df, df2):
+ joined_key2 = merge(df, df2, on="key2")
+ _check_join(df, df2, joined_key2, ["key2"], how="left")
+
+ joined_both = merge(df, df2)
+ _check_join(df, df2, joined_both, ["key1", "key2"], how="left")
+
+ def test_right_outer_join(self, df, df2):
+ joined_key2 = merge(df, df2, on="key2", how="right")
+ _check_join(df, df2, joined_key2, ["key2"], how="right")
+
+ joined_both = merge(df, df2, how="right")
+ _check_join(df, df2, joined_both, ["key1", "key2"], how="right")
+
+ def test_full_outer_join(self, df, df2):
+ joined_key2 = merge(df, df2, on="key2", how="outer")
+ _check_join(df, df2, joined_key2, ["key2"], how="outer")
+
+ joined_both = merge(df, df2, how="outer")
+ _check_join(df, df2, joined_both, ["key1", "key2"], how="outer")
+
+ def test_inner_join(self, df, df2):
+ joined_key2 = merge(df, df2, on="key2", how="inner")
+ _check_join(df, df2, joined_key2, ["key2"], how="inner")
+
+ joined_both = merge(df, df2, how="inner")
+ _check_join(df, df2, joined_both, ["key1", "key2"], how="inner")
+
+ def test_handle_overlap(self, df, df2):
+ joined = merge(df, df2, on="key2", suffixes=(".foo", ".bar"))
+
+ assert "key1.foo" in joined
+ assert "key1.bar" in joined
+
+ def test_handle_overlap_arbitrary_key(self, df, df2):
+ joined = merge(
+ df,
+ df2,
+ left_on="key2",
+ right_on="key1",
+ suffixes=(".foo", ".bar"),
+ )
+ assert "key1.foo" in joined
+ assert "key2.bar" in joined
+
+ @pytest.mark.parametrize(
+ "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))]
+ )
+ def test_join_on(self, target_source, infer_string):
+ target, source = target_source
+
+ merged = target.join(source, on="C")
+ tm.assert_series_equal(merged["MergedA"], target["A"], check_names=False)
+ tm.assert_series_equal(merged["MergedD"], target["D"], check_names=False)
+
+ # join with duplicates (fix regression from DataFrame/Matrix merge)
+ df = DataFrame({"key": ["a", "a", "b", "b", "c"]})
+ df2 = DataFrame({"value": [0, 1, 2]}, index=["a", "b", "c"])
+ joined = df.join(df2, on="key")
+ expected = DataFrame(
+ {"key": ["a", "a", "b", "b", "c"], "value": [0, 0, 1, 1, 2]}
+ )
+ tm.assert_frame_equal(joined, expected)
+
+ # Test when some are missing
+ df_a = DataFrame([[1], [2], [3]], index=["a", "b", "c"], columns=["one"])
+ df_b = DataFrame([["foo"], ["bar"]], index=[1, 2], columns=["two"])
+ df_c = DataFrame([[1], [2]], index=[1, 2], columns=["three"])
+ joined = df_a.join(df_b, on="one")
+ joined = joined.join(df_c, on="one")
+ assert np.isnan(joined["two"]["c"])
+ assert np.isnan(joined["three"]["c"])
+
+ # merge column not p resent
+ with pytest.raises(KeyError, match="^'E'$"):
+ target.join(source, on="E")
+
+ # overlap
+ msg = (
+ "You are trying to merge on float64 and object|str columns for key "
+ "'A'. If you wish to proceed you should use pd.concat"
+ )
+ with pytest.raises(ValueError, match=msg):
+ target.join(source, on="A")
+
+ def test_join_on_fails_with_different_right_index(self):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).choice(["m", "f"], size=3),
+ "b": np.random.default_rng(2).standard_normal(3),
+ }
+ )
+ df2 = DataFrame(
+ {
+ "a": np.random.default_rng(2).choice(["m", "f"], size=10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ },
+ index=MultiIndex.from_product([range(5), ["A", "B"]]),
+ )
+ msg = r'len\(left_on\) must equal the number of levels in the index of "right"'
+ with pytest.raises(ValueError, match=msg):
+ merge(df, df2, left_on="a", right_index=True)
+
+ def test_join_on_fails_with_different_left_index(self):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).choice(["m", "f"], size=3),
+ "b": np.random.default_rng(2).standard_normal(3),
+ },
+ index=MultiIndex.from_arrays([range(3), list("abc")]),
+ )
+ df2 = DataFrame(
+ {
+ "a": np.random.default_rng(2).choice(["m", "f"], size=10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ }
+ )
+ msg = r'len\(right_on\) must equal the number of levels in the index of "left"'
+ with pytest.raises(ValueError, match=msg):
+ merge(df, df2, right_on="b", left_index=True)
+
+ def test_join_on_fails_with_different_column_counts(self):
+ df = DataFrame(
+ {
+ "a": np.random.default_rng(2).choice(["m", "f"], size=3),
+ "b": np.random.default_rng(2).standard_normal(3),
+ }
+ )
+ df2 = DataFrame(
+ {
+ "a": np.random.default_rng(2).choice(["m", "f"], size=10),
+ "b": np.random.default_rng(2).standard_normal(10),
+ },
+ index=MultiIndex.from_product([range(5), ["A", "B"]]),
+ )
+ msg = r"len\(right_on\) must equal len\(left_on\)"
+ with pytest.raises(ValueError, match=msg):
+ merge(df, df2, right_on="a", left_on=["a", "b"])
+
+ @pytest.mark.parametrize("wrong_type", [2, "str", None, np.array([0, 1])])
+ def test_join_on_fails_with_wrong_object_type(self, wrong_type):
+ # GH12081 - original issue
+
+ # GH21220 - merging of Series and DataFrame is now allowed
+ # Edited test to remove the Series object from test parameters
+
+ df = DataFrame({"a": [1, 1]})
+ msg = (
+ "Can only merge Series or DataFrame objects, "
+ f"a {type(wrong_type)} was passed"
+ )
+ with pytest.raises(TypeError, match=msg):
+ merge(wrong_type, df, left_on="a", right_on="a")
+ with pytest.raises(TypeError, match=msg):
+ merge(df, wrong_type, left_on="a", right_on="a")
+
+ def test_join_on_pass_vector(self, target_source):
+ target, source = target_source
+ expected = target.join(source, on="C")
+ expected = expected.rename(columns={"C": "key_0"})
+ expected = expected[["key_0", "A", "B", "D", "MergedA", "MergedD"]]
+
+ join_col = target.pop("C")
+ result = target.join(source, on=join_col)
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_with_len0(self, target_source):
+ # nothing to merge
+ target, source = target_source
+ merged = target.join(source.reindex([]), on="C")
+ for col in source:
+ assert col in merged
+ assert merged[col].isna().all()
+
+ merged2 = target.join(source.reindex([]), on="C", how="inner")
+ tm.assert_index_equal(merged2.columns, merged.columns)
+ assert len(merged2) == 0
+
+ def test_join_on_inner(self):
+ df = DataFrame({"key": ["a", "a", "d", "b", "b", "c"]})
+ df2 = DataFrame({"value": [0, 1]}, index=["a", "b"])
+
+ joined = df.join(df2, on="key", how="inner")
+
+ expected = df.join(df2, on="key")
+ expected = expected[expected["value"].notna()]
+ tm.assert_series_equal(joined["key"], expected["key"])
+ tm.assert_series_equal(joined["value"], expected["value"], check_dtype=False)
+ tm.assert_index_equal(joined.index, expected.index)
+
+ def test_join_on_singlekey_list(self):
+ df = DataFrame({"key": ["a", "a", "b", "b", "c"]})
+ df2 = DataFrame({"value": [0, 1, 2]}, index=["a", "b", "c"])
+
+ # corner cases
+ joined = df.join(df2, on=["key"])
+ expected = df.join(df2, on="key")
+
+ tm.assert_frame_equal(joined, expected)
+
+ def test_join_on_series(self, target_source):
+ target, source = target_source
+ result = target.join(source["MergedA"], on="C")
+ expected = target.join(source[["MergedA"]], on="C")
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_on_series_buglet(self):
+ # GH #638
+ df = DataFrame({"a": [1, 1]})
+ ds = Series([2], index=[1], name="b")
+ result = df.join(ds, on="a")
+ expected = DataFrame({"a": [1, 1], "b": [2, 2]}, index=df.index)
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_index_mixed(self, join_type):
+ # no overlapping blocks
+ df1 = DataFrame(index=np.arange(10))
+ df1["bool"] = True
+ df1["string"] = "foo"
+
+ df2 = DataFrame(index=np.arange(5, 15))
+ df2["int"] = 1
+ df2["float"] = 1.0
+
+ joined = df1.join(df2, how=join_type)
+ expected = _join_by_hand(df1, df2, how=join_type)
+ tm.assert_frame_equal(joined, expected)
+
+ joined = df2.join(df1, how=join_type)
+ expected = _join_by_hand(df2, df1, how=join_type)
+ tm.assert_frame_equal(joined, expected)
+
+ def test_join_index_mixed_overlap(self):
+ df1 = DataFrame(
+ {"A": 1.0, "B": 2, "C": "foo", "D": True},
+ index=np.arange(10),
+ columns=["A", "B", "C", "D"],
+ )
+ assert df1["B"].dtype == np.int64
+ assert df1["D"].dtype == np.bool_
+
+ df2 = DataFrame(
+ {"A": 1.0, "B": 2, "C": "foo", "D": True},
+ index=np.arange(0, 10, 2),
+ columns=["A", "B", "C", "D"],
+ )
+
+ # overlap
+ joined = df1.join(df2, lsuffix="_one", rsuffix="_two")
+ expected_columns = [
+ "A_one",
+ "B_one",
+ "C_one",
+ "D_one",
+ "A_two",
+ "B_two",
+ "C_two",
+ "D_two",
+ ]
+ df1.columns = expected_columns[:4]
+ df2.columns = expected_columns[4:]
+ expected = _join_by_hand(df1, df2)
+ tm.assert_frame_equal(joined, expected)
+
+ def test_join_empty_bug(self):
+ # generated an exception in 0.4.3
+ x = DataFrame()
+ x.join(DataFrame([3], index=[0], columns=["A"]), how="outer")
+
+ def test_join_unconsolidated(self):
+ # GH #331
+ a = DataFrame(
+ np.random.default_rng(2).standard_normal((30, 2)), columns=["a", "b"]
+ )
+ c = Series(np.random.default_rng(2).standard_normal(30))
+ a["c"] = c
+ d = DataFrame(np.random.default_rng(2).standard_normal((30, 1)), columns=["q"])
+
+ # it works!
+ a.join(d)
+ d.join(a)
+
+ def test_join_multiindex(self):
+ index1 = MultiIndex.from_arrays(
+ [["a", "a", "a", "b", "b", "b"], [1, 2, 3, 1, 2, 3]],
+ names=["first", "second"],
+ )
+
+ index2 = MultiIndex.from_arrays(
+ [["b", "b", "b", "c", "c", "c"], [1, 2, 3, 1, 2, 3]],
+ names=["first", "second"],
+ )
+
+ df1 = DataFrame(
+ data=np.random.default_rng(2).standard_normal(6),
+ index=index1,
+ columns=["var X"],
+ )
+ df2 = DataFrame(
+ data=np.random.default_rng(2).standard_normal(6),
+ index=index2,
+ columns=["var Y"],
+ )
+
+ df1 = df1.sort_index(level=0)
+ df2 = df2.sort_index(level=0)
+
+ joined = df1.join(df2, how="outer")
+ ex_index = Index(index1.values).union(Index(index2.values))
+ expected = df1.reindex(ex_index).join(df2.reindex(ex_index))
+ expected.index.names = index1.names
+ tm.assert_frame_equal(joined, expected)
+ assert joined.index.names == index1.names
+
+ df1 = df1.sort_index(level=1)
+ df2 = df2.sort_index(level=1)
+
+ joined = df1.join(df2, how="outer").sort_index(level=0)
+ ex_index = Index(index1.values).union(Index(index2.values))
+ expected = df1.reindex(ex_index).join(df2.reindex(ex_index))
+ expected.index.names = index1.names
+
+ tm.assert_frame_equal(joined, expected)
+ assert joined.index.names == index1.names
+
+ def test_join_inner_multiindex(self, lexsorted_two_level_string_multiindex):
+ key1 = ["bar", "bar", "bar", "foo", "foo", "baz", "baz", "qux", "qux", "snap"]
+ key2 = [
+ "two",
+ "one",
+ "three",
+ "one",
+ "two",
+ "one",
+ "two",
+ "two",
+ "three",
+ "one",
+ ]
+
+ data = np.random.default_rng(2).standard_normal(len(key1))
+ data = DataFrame({"key1": key1, "key2": key2, "data": data})
+
+ index = lexsorted_two_level_string_multiindex
+ to_join = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 3)),
+ index=index,
+ columns=["j_one", "j_two", "j_three"],
+ )
+
+ joined = data.join(to_join, on=["key1", "key2"], how="inner")
+ expected = merge(
+ data,
+ to_join.reset_index(),
+ left_on=["key1", "key2"],
+ right_on=["first", "second"],
+ how="inner",
+ sort=False,
+ )
+
+ expected2 = merge(
+ to_join,
+ data,
+ right_on=["key1", "key2"],
+ left_index=True,
+ how="inner",
+ sort=False,
+ )
+ tm.assert_frame_equal(joined, expected2.reindex_like(joined))
+
+ expected2 = merge(
+ to_join,
+ data,
+ right_on=["key1", "key2"],
+ left_index=True,
+ how="inner",
+ sort=False,
+ )
+
+ expected = expected.drop(["first", "second"], axis=1)
+ expected.index = joined.index
+
+ assert joined.index.is_monotonic_increasing
+ tm.assert_frame_equal(joined, expected)
+
+ # _assert_same_contents(expected, expected2.loc[:, expected.columns])
+
+ def test_join_hierarchical_mixed_raises(self):
+ # GH 2024
+ # GH 40993: For raising, enforced in 2.0
+ df = DataFrame([(1, 2, 3), (4, 5, 6)], columns=["a", "b", "c"])
+ new_df = df.groupby(["a"]).agg({"b": ["mean", "sum"]})
+ other_df = DataFrame([(1, 2, 3), (7, 10, 6)], columns=["a", "b", "d"])
+ other_df.set_index("a", inplace=True)
+ # GH 9455, 12219
+ with pytest.raises(
+ pd.errors.MergeError, match="Not allowed to merge between different levels"
+ ):
+ merge(new_df, other_df, left_index=True, right_index=True)
+
+ def test_join_float64_float32(self):
+ a = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 2)),
+ columns=["a", "b"],
+ dtype=np.float64,
+ )
+ b = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 1)),
+ columns=["c"],
+ dtype=np.float32,
+ )
+ joined = a.join(b)
+ assert joined.dtypes["a"] == "float64"
+ assert joined.dtypes["b"] == "float64"
+ assert joined.dtypes["c"] == "float32"
+
+ a = np.random.default_rng(2).integers(0, 5, 100).astype("int64")
+ b = np.random.default_rng(2).random(100).astype("float64")
+ c = np.random.default_rng(2).random(100).astype("float32")
+ df = DataFrame({"a": a, "b": b, "c": c})
+ xpdf = DataFrame({"a": a, "b": b, "c": c})
+ s = DataFrame(
+ np.random.default_rng(2).random(5).astype("float32"), columns=["md"]
+ )
+ rs = df.merge(s, left_on="a", right_index=True)
+ assert rs.dtypes["a"] == "int64"
+ assert rs.dtypes["b"] == "float64"
+ assert rs.dtypes["c"] == "float32"
+ assert rs.dtypes["md"] == "float32"
+
+ xp = xpdf.merge(s, left_on="a", right_index=True)
+ tm.assert_frame_equal(rs, xp)
+
+ def test_join_many_non_unique_index(self):
+ df1 = DataFrame({"a": [1, 1], "b": [1, 1], "c": [10, 20]})
+ df2 = DataFrame({"a": [1, 1], "b": [1, 2], "d": [100, 200]})
+ df3 = DataFrame({"a": [1, 1], "b": [1, 2], "e": [1000, 2000]})
+ idf1 = df1.set_index(["a", "b"])
+ idf2 = df2.set_index(["a", "b"])
+ idf3 = df3.set_index(["a", "b"])
+
+ result = idf1.join([idf2, idf3], how="outer")
+
+ df_partially_merged = merge(df1, df2, on=["a", "b"], how="outer")
+ expected = merge(df_partially_merged, df3, on=["a", "b"], how="outer")
+
+ result = result.reset_index()
+ expected = expected[result.columns]
+ expected["a"] = expected.a.astype("int64")
+ expected["b"] = expected.b.astype("int64")
+ tm.assert_frame_equal(result, expected)
+
+ df1 = DataFrame({"a": [1, 1, 1], "b": [1, 1, 1], "c": [10, 20, 30]})
+ df2 = DataFrame({"a": [1, 1, 1], "b": [1, 1, 2], "d": [100, 200, 300]})
+ df3 = DataFrame({"a": [1, 1, 1], "b": [1, 1, 2], "e": [1000, 2000, 3000]})
+ idf1 = df1.set_index(["a", "b"])
+ idf2 = df2.set_index(["a", "b"])
+ idf3 = df3.set_index(["a", "b"])
+ result = idf1.join([idf2, idf3], how="inner")
+
+ df_partially_merged = merge(df1, df2, on=["a", "b"], how="inner")
+ expected = merge(df_partially_merged, df3, on=["a", "b"], how="inner")
+
+ result = result.reset_index()
+
+ tm.assert_frame_equal(result, expected.loc[:, result.columns])
+
+ # GH 11519
+ df = DataFrame(
+ {
+ "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
+ "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
+ "C": np.random.default_rng(2).standard_normal(8),
+ "D": np.random.default_rng(2).standard_normal(8),
+ }
+ )
+ s = Series(
+ np.repeat(np.arange(8), 2), index=np.repeat(np.arange(8), 2), name="TEST"
+ )
+ inner = df.join(s, how="inner")
+ outer = df.join(s, how="outer")
+ left = df.join(s, how="left")
+ right = df.join(s, how="right")
+ tm.assert_frame_equal(inner, outer)
+ tm.assert_frame_equal(inner, left)
+ tm.assert_frame_equal(inner, right)
+
+ @pytest.mark.parametrize(
+ "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))]
+ )
+ def test_join_sort(self, infer_string):
+ with option_context("future.infer_string", infer_string):
+ left = DataFrame(
+ {"key": ["foo", "bar", "baz", "foo"], "value": [1, 2, 3, 4]}
+ )
+ right = DataFrame({"value2": ["a", "b", "c"]}, index=["bar", "baz", "foo"])
+
+ joined = left.join(right, on="key", sort=True)
+ expected = DataFrame(
+ {
+ "key": ["bar", "baz", "foo", "foo"],
+ "value": [2, 3, 1, 4],
+ "value2": ["a", "b", "c", "c"],
+ },
+ index=[1, 2, 0, 3],
+ )
+ tm.assert_frame_equal(joined, expected)
+
+ # smoke test
+ joined = left.join(right, on="key", sort=False)
+ tm.assert_index_equal(joined.index, Index(range(4)), exact=True)
+
+ def test_join_mixed_non_unique_index(self):
+ # GH 12814, unorderable types in py3 with a non-unique index
+ df1 = DataFrame({"a": [1, 2, 3, 4]}, index=[1, 2, 3, "a"])
+ df2 = DataFrame({"b": [5, 6, 7, 8]}, index=[1, 3, 3, 4])
+ result = df1.join(df2)
+ expected = DataFrame(
+ {"a": [1, 2, 3, 3, 4], "b": [5, np.nan, 6, 7, np.nan]},
+ index=[1, 2, 3, 3, "a"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ df3 = DataFrame({"a": [1, 2, 3, 4]}, index=[1, 2, 2, "a"])
+ df4 = DataFrame({"b": [5, 6, 7, 8]}, index=[1, 2, 3, 4])
+ result = df3.join(df4)
+ expected = DataFrame(
+ {"a": [1, 2, 3, 4], "b": [5, 6, 6, np.nan]}, index=[1, 2, 2, "a"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_non_unique_period_index(self):
+ # GH #16871
+ index = pd.period_range("2016-01-01", periods=16, freq="M")
+ df = DataFrame(list(range(len(index))), index=index, columns=["pnum"])
+ df2 = concat([df, df])
+ result = df.join(df2, how="inner", rsuffix="_df2")
+ expected = DataFrame(
+ np.tile(np.arange(16, dtype=np.int64).repeat(2).reshape(-1, 1), 2),
+ columns=["pnum", "pnum_df2"],
+ index=df2.sort_index().index,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_mixed_type_join_with_suffix(self, using_infer_string):
+ # GH #916
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((20, 6)),
+ columns=["a", "b", "c", "d", "e", "f"],
+ )
+ df.insert(0, "id", 0)
+ df.insert(5, "dt", "foo")
+
+ grouped = df.groupby("id")
+ msg = re.escape("agg function failed [how->mean,dtype->")
+ if using_infer_string:
+ msg = "dtype 'str' does not support operation 'mean'"
+ with pytest.raises(TypeError, match=msg):
+ grouped.mean()
+ mn = grouped.mean(numeric_only=True)
+ cn = grouped.count()
+
+ # it works!
+ mn.join(cn, rsuffix="_right")
+
+ def test_join_many(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 6)), columns=list("abcdef")
+ )
+ df_list = [df[["a", "b"]], df[["c", "d"]], df[["e", "f"]]]
+
+ joined = df_list[0].join(df_list[1:])
+ tm.assert_frame_equal(joined, df)
+
+ df_list = [df[["a", "b"]][:-2], df[["c", "d"]][2:], df[["e", "f"]][1:9]]
+
+ def _check_diff_index(df_list, result, exp_index):
+ reindexed = [x.reindex(exp_index) for x in df_list]
+ expected = reindexed[0].join(reindexed[1:])
+ tm.assert_frame_equal(result, expected)
+
+ # different join types
+ joined = df_list[0].join(df_list[1:], how="outer")
+ _check_diff_index(df_list, joined, df.index)
+
+ joined = df_list[0].join(df_list[1:])
+ _check_diff_index(df_list, joined, df_list[0].index)
+
+ joined = df_list[0].join(df_list[1:], how="inner")
+ _check_diff_index(df_list, joined, df.index[2:8])
+
+ msg = "Joining multiple DataFrames only supported for joining on index"
+ with pytest.raises(ValueError, match=msg):
+ df_list[0].join(df_list[1:], on="a")
+
+ @pytest.mark.parametrize("how", ["left", "right", "inner", "outer"])
+ def test_join_many_sort_unique(self, how, sort):
+ # https://github.com/pandas-dev/pandas/pull/62954
+ df = DataFrame({"a": [1, 2, 3]}, index=[1, 0, 2])
+ df2 = DataFrame({"b": [4, 5, 6]}, index=[2, 0, 1])
+ if how == "right":
+ expected = DataFrame({"a": [3, 2, 1], "b": [4, 5, 6]}, index=[2, 0, 1])
+ else:
+ expected = DataFrame({"a": [1, 2, 3], "b": [6, 5, 4]}, index=[1, 0, 2])
+ if how == "outer" or sort:
+ # outer always sorts.
+ expected = expected.sort_index()
+ result = df.join([df2], how=how, sort=sort)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("how", ["left", "right", "inner", "outer"])
+ def test_join_many_sort_nonunique(self, how, sort):
+ # https://github.com/pandas-dev/pandas/pull/62954
+ df = DataFrame({"a": [1, 2, 3]}, index=[3, 0, 0])
+ df2 = DataFrame({"b": [4, 5, 6]}, index=[2, 0, 1])
+ if how == "inner":
+ expected = DataFrame({"a": [2, 3], "b": [5, 5]}, index=[0, 0])
+ elif how == "left":
+ expected = DataFrame(
+ {"a": [1, 2, 3], "b": [np.nan, 5.0, 5.0]}, index=[3, 0, 0]
+ )
+ elif how == "right":
+ expected = DataFrame(
+ {"a": [np.nan, 2.0, 3.0, np.nan], "b": [4, 5, 5, 6]}, index=[2, 0, 0, 1]
+ )
+ else:
+ expected = DataFrame(
+ {
+ "a": [2.0, 3.0, np.nan, np.nan, 1.0],
+ "b": [5.0, 5.0, 6.0, 4.0, np.nan],
+ },
+ index=[0, 0, 1, 2, 3],
+ )
+ if sort:
+ expected = expected.sort_index()
+ result = df.join([df2], how=how, sort=sort)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("how", ["inner", "outer", "left", "right"])
+ def test_join_many_datetime_unsorted(self, how):
+ # https://github.com/pandas-dev/pandas/pull/62843
+ index = Index([datetime(2024, 1, 2), datetime(2024, 1, 1)])
+ df = DataFrame({"a": [1, 2]}, index=index)
+ df2 = DataFrame({"b": [1, 2]}, index=index)
+ result = df.join([df2], how=how)
+ if how == "outer":
+ # Outer always sorts the index.
+ expected = DataFrame({"a": [2, 1], "b": [2, 1]}, index=[index[1], index[0]])
+ else:
+ expected = DataFrame({"a": [1, 2], "b": [1, 2]}, index=index)
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_many_mixed(self):
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((8, 4)),
+ columns=["A", "B", "C", "D"],
+ )
+ df["key"] = ["foo", "bar"] * 4
+ df1 = df.loc[:, ["A", "B"]]
+ df2 = df.loc[:, ["C", "D"]]
+ df3 = df.loc[:, ["key"]]
+
+ result = df1.join([df2, df3])
+ tm.assert_frame_equal(result, df)
+
+ def test_join_dups(self):
+ # joining dups
+ df = concat(
+ [
+ DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=["A", "A", "B", "B"],
+ ),
+ DataFrame(
+ np.random.default_rng(2).integers(0, 10, size=20).reshape(10, 2),
+ columns=["A", "C"],
+ ),
+ ],
+ axis=1,
+ )
+
+ expected = concat([df, df], axis=1)
+ result = df.join(df, rsuffix="_2")
+ result.columns = expected.columns
+ tm.assert_frame_equal(result, expected)
+
+ # GH 4975, invalid join on dups
+ w = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 2)), columns=["x", "y"]
+ )
+ x = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 2)), columns=["x", "y"]
+ )
+ y = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 2)), columns=["x", "y"]
+ )
+ z = DataFrame(
+ np.random.default_rng(2).standard_normal((4, 2)), columns=["x", "y"]
+ )
+
+ dta = x.merge(y, left_index=True, right_index=True).merge(
+ z, left_index=True, right_index=True, how="outer"
+ )
+ # GH 40991: As of 2.0 causes duplicate columns
+ with pytest.raises(
+ pd.errors.MergeError,
+ match="Passing 'suffixes' which cause duplicate columns",
+ ):
+ dta.merge(w, left_index=True, right_index=True)
+
+ def test_join_multi_to_multi(self, join_type):
+ # GH 20475
+ leftindex = MultiIndex.from_product(
+ [list("abc"), list("xy"), [1, 2]], names=["abc", "xy", "num"]
+ )
+ left = DataFrame({"v1": range(12)}, index=leftindex)
+
+ rightindex = MultiIndex.from_product(
+ [list("abc"), list("xy")], names=["abc", "xy"]
+ )
+ right = DataFrame({"v2": [100 * i for i in range(1, 7)]}, index=rightindex)
+
+ result = left.join(right, on=["abc", "xy"], how=join_type)
+ expected = (
+ left.reset_index()
+ .merge(right.reset_index(), on=["abc", "xy"], how=join_type)
+ .set_index(["abc", "xy", "num"])
+ )
+ tm.assert_frame_equal(expected, result)
+
+ msg = r'len\(left_on\) must equal the number of levels in the index of "right"'
+ with pytest.raises(ValueError, match=msg):
+ left.join(right, on="xy", how=join_type)
+
+ with pytest.raises(ValueError, match=msg):
+ right.join(left, on=["abc", "xy"], how=join_type)
+
+ def test_join_on_tz_aware_datetimeindex(self):
+ # GH 23931, 26335
+ df1 = DataFrame(
+ {
+ "date": pd.date_range(
+ start="2018-01-01", periods=5, tz="America/Chicago"
+ ),
+ "vals": list("abcde"),
+ }
+ )
+
+ df2 = DataFrame(
+ {
+ "date": pd.date_range(
+ start="2018-01-03", periods=5, tz="America/Chicago"
+ ),
+ "vals_2": list("tuvwx"),
+ }
+ )
+ result = df1.join(df2.set_index("date"), on="date")
+ expected = df1.copy()
+ expected["vals_2"] = Series([np.nan] * 2 + list("tuv"))
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_datetime_string(self):
+ # GH 5647
+ dfa = DataFrame(
+ [
+ ["2012-08-02", "L", 10],
+ ["2012-08-02", "J", 15],
+ ["2013-04-06", "L", 20],
+ ["2013-04-06", "J", 25],
+ ],
+ columns=["x", "y", "a"],
+ )
+ dfa["x"] = pd.to_datetime(dfa["x"]).astype("M8[ns]")
+ dfb = DataFrame(
+ [["2012-08-02", "J", 1], ["2013-04-06", "L", 2]],
+ columns=["x", "y", "z"],
+ index=[2, 4],
+ )
+ dfb["x"] = pd.to_datetime(dfb["x"]).astype("M8[ns]")
+ result = dfb.join(dfa.set_index(["x", "y"]), on=["x", "y"])
+ expected = DataFrame(
+ [
+ [Timestamp("2012-08-02 00:00:00"), "J", 1, 15],
+ [Timestamp("2013-04-06 00:00:00"), "L", 2, 20],
+ ],
+ index=[2, 4],
+ columns=["x", "y", "z", "a"],
+ )
+ expected["x"] = expected["x"].astype("M8[ns]")
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_with_categorical_index(self):
+ # GH47812
+ ix = ["a", "b"]
+ id1 = pd.CategoricalIndex(ix, categories=ix)
+ id2 = pd.CategoricalIndex(reversed(ix), categories=reversed(ix))
+
+ df1 = DataFrame({"c1": ix}, index=id1)
+ df2 = DataFrame({"c2": reversed(ix)}, index=id2)
+ result = df1.join(df2)
+ expected = DataFrame(
+ {"c1": ["a", "b"], "c2": ["a", "b"]},
+ index=pd.CategoricalIndex(["a", "b"], categories=["a", "b"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def _check_join(left, right, result, join_col, how="left", lsuffix="_x", rsuffix="_y"):
+ # some smoke tests
+ for c in join_col:
+ assert result[c].notna().all()
+
+ left_grouped = left.groupby(join_col)
+ right_grouped = right.groupby(join_col)
+
+ for group_key, group in result.groupby(join_col):
+ l_joined = _restrict_to_columns(group, left.columns, lsuffix)
+ r_joined = _restrict_to_columns(group, right.columns, rsuffix)
+
+ try:
+ lgroup = left_grouped.get_group(group_key)
+ except KeyError as err:
+ if how in ("left", "inner"):
+ raise AssertionError(
+ f"key {group_key} should not have been in the join"
+ ) from err
+
+ _assert_all_na(l_joined, left.columns, join_col)
+ else:
+ _assert_same_contents(l_joined, lgroup)
+
+ try:
+ rgroup = right_grouped.get_group(group_key)
+ except KeyError as err:
+ if how in ("right", "inner"):
+ raise AssertionError(
+ f"key {group_key} should not have been in the join"
+ ) from err
+
+ _assert_all_na(r_joined, right.columns, join_col)
+ else:
+ _assert_same_contents(r_joined, rgroup)
+
+
+def _restrict_to_columns(group, columns, suffix):
+ found = [
+ c for c in group.columns if c in columns or c.replace(suffix, "") in columns
+ ]
+
+ # filter
+ group = group.loc[:, found]
+
+ # get rid of suffixes, if any
+ group = group.rename(columns=lambda x: x.replace(suffix, ""))
+
+ # put in the right order...
+ group = group.loc[:, columns]
+
+ return group
+
+
+def _assert_same_contents(join_chunk, source):
+ NA_SENTINEL = -1234567 # drop_duplicates not so NA-friendly...
+
+ jvalues = join_chunk.fillna(NA_SENTINEL).drop_duplicates().values
+ svalues = source.fillna(NA_SENTINEL).drop_duplicates().values
+
+ rows = {tuple(row) for row in jvalues}
+ assert len(rows) == len(source)
+ assert all(tuple(row) in rows for row in svalues)
+
+
+def _assert_all_na(join_chunk, source_columns, join_col):
+ for c in source_columns:
+ if c in join_col:
+ continue
+ assert join_chunk[c].isna().all()
+
+
+def _join_by_hand(a, b, how="left"):
+ join_index = a.index.join(b.index, how=how)
+
+ a_re = a.reindex(join_index)
+ b_re = b.reindex(join_index)
+
+ result_columns = a.columns.append(b.columns)
+
+ for col, s in b_re.items():
+ a_re[col] = s
+ return a_re.reindex(columns=result_columns)
+
+
+def test_join_inner_multiindex_deterministic_order():
+ # GH: 36910
+ left = DataFrame(
+ data={"e": 5},
+ index=MultiIndex.from_tuples([(1, 2, 4)], names=("a", "b", "d")),
+ )
+ right = DataFrame(
+ data={"f": 6}, index=MultiIndex.from_tuples([(2, 3)], names=("b", "c"))
+ )
+ result = left.join(right, how="inner")
+ expected = DataFrame(
+ {"e": [5], "f": [6]},
+ index=MultiIndex.from_tuples([(1, 2, 4, 3)], names=("a", "b", "d", "c")),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ ("input_col", "output_cols"), [("b", ["a", "b"]), ("a", ["a_x", "a_y"])]
+)
+def test_join_cross(input_col, output_cols):
+ # GH#5401
+ left = DataFrame({"a": [1, 3]})
+ right = DataFrame({input_col: [3, 4]})
+ result = left.join(right, how="cross", lsuffix="_x", rsuffix="_y")
+ expected = DataFrame({output_cols[0]: [1, 1, 3, 3], output_cols[1]: [3, 4, 3, 4]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_join_multiindex_one_level(join_type):
+ # GH#36909
+ left = DataFrame(
+ data={"c": 3}, index=MultiIndex.from_tuples([(1, 2)], names=("a", "b"))
+ )
+ right = DataFrame(data={"d": 4}, index=MultiIndex.from_tuples([(2,)], names=("b",)))
+ result = left.join(right, how=join_type)
+ if join_type == "right":
+ expected = DataFrame(
+ {"c": [3], "d": [4]},
+ index=MultiIndex.from_tuples([(2, 1)], names=["b", "a"]),
+ )
+ else:
+ expected = DataFrame(
+ {"c": [3], "d": [4]},
+ index=MultiIndex.from_tuples([(1, 2)], names=["a", "b"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "categories, values",
+ [
+ (["Y", "X"], ["Y", "X", "X"]),
+ ([2, 1], [2, 1, 1]),
+ ([2.5, 1.5], [2.5, 1.5, 1.5]),
+ (
+ [Timestamp("2020-12-31"), Timestamp("2019-12-31")],
+ [Timestamp("2020-12-31"), Timestamp("2019-12-31"), Timestamp("2019-12-31")],
+ ),
+ ],
+)
+def test_join_multiindex_not_alphabetical_categorical(categories, values):
+ # GH#38502
+ left = DataFrame(
+ {
+ "first": ["A", "A"],
+ "second": Categorical(categories, categories=categories),
+ "value": [1, 2],
+ }
+ ).set_index(["first", "second"])
+ right = DataFrame(
+ {
+ "first": ["A", "A", "B"],
+ "second": Categorical(values, categories=categories),
+ "value": [3, 4, 5],
+ }
+ ).set_index(["first", "second"])
+ result = left.join(right, lsuffix="_left", rsuffix="_right")
+
+ expected = DataFrame(
+ {
+ "first": ["A", "A"],
+ "second": Categorical(categories, categories=categories),
+ "value_left": [1, 2],
+ "value_right": [3, 4],
+ }
+ ).set_index(["first", "second"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "left_empty, how, exp",
+ [
+ (False, "left", "left"),
+ (False, "right", "empty"),
+ (False, "inner", "empty"),
+ (False, "outer", "left"),
+ (False, "cross", "empty"),
+ (True, "left", "empty"),
+ (True, "right", "right"),
+ (True, "inner", "empty"),
+ (True, "outer", "right"),
+ (True, "cross", "empty"),
+ ],
+)
+def test_join_empty(left_empty, how, exp):
+ left = DataFrame({"A": [2, 1], "B": [3, 4]}, dtype="int64").set_index("A")
+ right = DataFrame({"A": [1], "C": [5]}, dtype="int64").set_index("A")
+
+ if left_empty:
+ left = left.head(0)
+ else:
+ right = right.head(0)
+
+ result = left.join(right, how=how)
+
+ if exp == "left":
+ expected = DataFrame({"A": [2, 1], "B": [3, 4], "C": [np.nan, np.nan]})
+ expected = expected.set_index("A")
+ elif exp == "right":
+ expected = DataFrame({"B": [np.nan], "A": [1], "C": [5]})
+ expected = expected.set_index("A")
+ elif exp == "empty":
+ expected = DataFrame(columns=["B", "C"], dtype="int64")
+ if how != "cross":
+ expected = expected.rename_axis("A")
+ if how == "outer":
+ expected = expected.sort_index()
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_join_empty_uncomparable_columns():
+ # GH 57048
+ df1 = DataFrame()
+ df2 = DataFrame(columns=["test"])
+ df3 = DataFrame(columns=["foo", ("bar", "baz")])
+
+ result = df1 + df2
+ expected = DataFrame(columns=["test"])
+ tm.assert_frame_equal(result, expected)
+
+ result = df2 + df3
+ expected = DataFrame(columns=[("bar", "baz"), "foo", "test"])
+ tm.assert_frame_equal(result, expected)
+
+ result = df1 + df3
+ expected = DataFrame(columns=[("bar", "baz"), "foo"])
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "how, values",
+ [
+ ("inner", [0, 1, 2]),
+ ("outer", [0, 1, 2]),
+ ("left", [0, 1, 2]),
+ ("right", [0, 2, 1]),
+ ],
+)
+def test_join_multiindex_categorical_output_index_dtype(how, values):
+ # GH#50906
+ df1 = DataFrame(
+ {
+ "a": Categorical([0, 1, 2]),
+ "b": Categorical([0, 1, 2]),
+ "c": [0, 1, 2],
+ }
+ ).set_index(["a", "b"])
+
+ df2 = DataFrame(
+ {
+ "a": Categorical([0, 2, 1]),
+ "b": Categorical([0, 2, 1]),
+ "d": [0, 2, 1],
+ }
+ ).set_index(["a", "b"])
+
+ expected = DataFrame(
+ {
+ "a": Categorical(values),
+ "b": Categorical(values),
+ "c": values,
+ "d": values,
+ }
+ ).set_index(["a", "b"])
+
+ result = df1.join(df2, how=how)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_join_multiindex_with_none_as_label():
+ # GH 58721
+ df1 = DataFrame(
+ {"A": [1]},
+ index=MultiIndex.from_tuples([(3, 3)], names=["X", None]),
+ )
+ df2 = DataFrame(
+ {"B": [2]},
+ index=MultiIndex.from_tuples([(3, 3)], names=[None, "X"]),
+ )
+
+ result12 = df1.join(df2)
+ expected12 = DataFrame(
+ {"A": [1], "B": [2]},
+ index=MultiIndex.from_tuples([(3, 3)], names=["X", None]),
+ )
+ tm.assert_frame_equal(result12, expected12)
+
+ result21 = df2.join(df1)
+ expected21 = DataFrame(
+ {"B": [2], "A": [1]},
+ index=MultiIndex.from_tuples([(3, 3)], names=[None, "X"]),
+ )
+ tm.assert_frame_equal(result21, expected21)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge.py
new file mode 100644
index 0000000000000000000000000000000000000000..71a29d40eb5e7c82e96e12c9b9d0effdad8b7779
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge.py
@@ -0,0 +1,3229 @@
+from datetime import (
+ date,
+ datetime,
+ timedelta,
+)
+import re
+
+import numpy as np
+import pytest
+
+from pandas.compat import PY314
+
+from pandas.core.dtypes.common import (
+ is_object_dtype,
+ is_string_dtype,
+)
+from pandas.core.dtypes.dtypes import CategoricalDtype
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ CategoricalIndex,
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ IntervalIndex,
+ MultiIndex,
+ PeriodIndex,
+ RangeIndex,
+ Series,
+ TimedeltaIndex,
+)
+import pandas._testing as tm
+from pandas.core.reshape.concat import concat
+from pandas.core.reshape.merge import (
+ MergeError,
+ merge,
+)
+
+
+def get_test_data(ngroups=8, n=50):
+ unique_groups = list(range(ngroups))
+ arr = np.asarray(np.tile(unique_groups, n // ngroups))
+
+ if len(arr) < n:
+ arr = np.asarray(list(arr) + unique_groups[: n - len(arr)])
+
+ np.random.default_rng(2).shuffle(arr)
+ return arr
+
+
+@pytest.fixture
+def dfs_for_indicator():
+ df1 = DataFrame({"col1": [0, 1], "col_conflict": [1, 2], "col_left": ["a", "b"]})
+ df2 = DataFrame(
+ {
+ "col1": [1, 2, 3, 4, 5],
+ "col_conflict": [1, 2, 3, 4, 5],
+ "col_right": [2, 2, 2, 2, 2],
+ }
+ )
+ return df1, df2
+
+
+class TestMerge:
+ @pytest.fixture
+ def df(self):
+ df = DataFrame(
+ {
+ "key1": get_test_data(),
+ "key2": get_test_data(),
+ "data1": np.random.default_rng(2).standard_normal(50),
+ "data2": np.random.default_rng(2).standard_normal(50),
+ }
+ )
+
+ # exclude a couple keys for fun
+ df = df[df["key2"] > 1]
+ return df
+
+ @pytest.fixture
+ def df2(self):
+ return DataFrame(
+ {
+ "key1": get_test_data(n=10),
+ "key2": get_test_data(ngroups=4, n=10),
+ "value": np.random.default_rng(2).standard_normal(10),
+ }
+ )
+
+ @pytest.fixture
+ def left(self):
+ return DataFrame(
+ {
+ "key": ["a", "b", "c", "d", "e", "e", "a"],
+ "v1": np.random.default_rng(2).standard_normal(7),
+ }
+ )
+
+ def test_merge_inner_join_empty(self):
+ # GH 15328
+ df_empty = DataFrame()
+ df_a = DataFrame({"a": [1, 2]}, index=[0, 1], dtype="int64")
+ result = merge(df_empty, df_a, left_index=True, right_index=True)
+ expected = DataFrame({"a": []}, dtype="int64")
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_common(self, df, df2):
+ joined = merge(df, df2)
+ exp = merge(df, df2, on=["key1", "key2"])
+ tm.assert_frame_equal(joined, exp)
+
+ def test_merge_non_string_columns(self):
+ # https://github.com/pandas-dev/pandas/issues/17962
+ # Checks that method runs for non string column names
+ left = DataFrame(
+ {0: [1, 0, 1, 0], 1: [0, 1, 0, 0], 2: [0, 0, 2, 0], 3: [1, 0, 0, 3]}
+ )
+
+ right = left.astype(float)
+ expected = left
+ result = merge(left, right)
+ tm.assert_frame_equal(expected, result)
+
+ def test_merge_index_as_on_arg(self, df, df2):
+ # GH14355
+
+ left = df.set_index("key1")
+ right = df2.set_index("key1")
+ result = merge(left, right, on="key1")
+ expected = merge(df, df2, on="key1").set_index("key1")
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_index_singlekey_right_vs_left(self):
+ left = DataFrame(
+ {
+ "key": ["a", "b", "c", "d", "e", "e", "a"],
+ "v1": np.random.default_rng(2).standard_normal(7),
+ }
+ )
+ right = DataFrame(
+ {"v2": np.random.default_rng(2).standard_normal(4)},
+ index=["d", "b", "c", "a"],
+ )
+
+ merged1 = merge(
+ left, right, left_on="key", right_index=True, how="left", sort=False
+ )
+ merged2 = merge(
+ right, left, right_on="key", left_index=True, how="right", sort=False
+ )
+ tm.assert_frame_equal(merged1, merged2.loc[:, merged1.columns])
+
+ merged1 = merge(
+ left, right, left_on="key", right_index=True, how="left", sort=True
+ )
+ merged2 = merge(
+ right, left, right_on="key", left_index=True, how="right", sort=True
+ )
+ tm.assert_frame_equal(merged1, merged2.loc[:, merged1.columns])
+
+ def test_merge_index_singlekey_inner(self):
+ left = DataFrame(
+ {
+ "key": ["a", "b", "c", "d", "e", "e", "a"],
+ "v1": np.random.default_rng(2).standard_normal(7),
+ }
+ )
+ right = DataFrame(
+ {"v2": np.random.default_rng(2).standard_normal(4)},
+ index=["d", "b", "c", "a"],
+ )
+
+ # inner join
+ result = merge(left, right, left_on="key", right_index=True, how="inner")
+ expected = left.join(right, on="key").loc[result.index]
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(right, left, right_on="key", left_index=True, how="inner")
+ expected = left.join(right, on="key").loc[result.index]
+ tm.assert_frame_equal(result, expected.loc[:, result.columns])
+
+ def test_merge_misspecified(self, df, df2, left):
+ right = DataFrame(
+ {"v2": np.random.default_rng(2).standard_normal(4)},
+ index=["d", "b", "c", "a"],
+ )
+ msg = "Must pass right_on or right_index=True"
+ with pytest.raises(pd.errors.MergeError, match=msg):
+ merge(left, right, left_index=True)
+ msg = "Must pass left_on or left_index=True"
+ with pytest.raises(pd.errors.MergeError, match=msg):
+ merge(left, right, right_index=True)
+
+ msg = (
+ 'Can only pass argument "on" OR "left_on" and "right_on", not '
+ "a combination of both"
+ )
+ with pytest.raises(pd.errors.MergeError, match=msg):
+ merge(left, left, left_on="key", on="key")
+
+ msg = r"len\(right_on\) must equal len\(left_on\)"
+ with pytest.raises(ValueError, match=msg):
+ merge(df, df2, left_on=["key1"], right_on=["key1", "key2"])
+
+ def test_index_and_on_parameters_confusion(self, df, df2):
+ msg = "right_index parameter must be of type bool, not "
+ with pytest.raises(ValueError, match=msg):
+ merge(
+ df,
+ df2,
+ how="left",
+ left_index=False,
+ right_index=["key1", "key2"],
+ )
+ msg = "left_index parameter must be of type bool, not "
+ with pytest.raises(ValueError, match=msg):
+ merge(
+ df,
+ df2,
+ how="left",
+ left_index=["key1", "key2"],
+ right_index=False,
+ )
+ with pytest.raises(ValueError, match=msg):
+ merge(
+ df,
+ df2,
+ how="left",
+ left_index=["key1", "key2"],
+ right_index=["key1", "key2"],
+ )
+
+ def test_merge_overlap(self, left):
+ merged = merge(left, left, on="key")
+ exp_len = (left["key"].value_counts() ** 2).sum()
+ assert len(merged) == exp_len
+ assert "v1_x" in merged
+ assert "v1_y" in merged
+
+ def test_merge_different_column_key_names(self):
+ left = DataFrame({"lkey": ["foo", "bar", "baz", "foo"], "value": [1, 2, 3, 4]})
+ right = DataFrame({"rkey": ["foo", "bar", "qux", "foo"], "value": [5, 6, 7, 8]})
+
+ merged = left.merge(
+ right, left_on="lkey", right_on="rkey", how="outer", sort=True
+ )
+
+ exp = Series(["bar", "baz", "foo", "foo", "foo", "foo", np.nan], name="lkey")
+ tm.assert_series_equal(merged["lkey"], exp)
+
+ exp = Series(["bar", np.nan, "foo", "foo", "foo", "foo", "qux"], name="rkey")
+ tm.assert_series_equal(merged["rkey"], exp)
+
+ exp = Series([2, 3, 1, 1, 4, 4, np.nan], name="value_x")
+ tm.assert_series_equal(merged["value_x"], exp)
+
+ exp = Series([6, np.nan, 5, 8, 5, 8, 7], name="value_y")
+ tm.assert_series_equal(merged["value_y"], exp)
+
+ def test_merge_copy(self):
+ left = DataFrame({"a": 0, "b": 1}, index=range(10))
+ right = DataFrame({"c": "foo", "d": "bar"}, index=range(10))
+
+ merged = merge(left, right, left_index=True, right_index=True)
+
+ merged["a"] = 6
+ assert (left["a"] == 0).all()
+
+ merged["d"] = "peekaboo"
+ assert (right["d"] == "bar").all()
+
+ def test_merge_nocopy(self, using_infer_string):
+ left = DataFrame({"a": 0, "b": 1}, index=range(10))
+ right = DataFrame({"c": "foo", "d": "bar"}, index=range(10))
+
+ merged = merge(left, right, left_index=True, right_index=True)
+
+ assert np.shares_memory(merged["a"]._values, left["a"]._values)
+ if not using_infer_string:
+ assert np.shares_memory(merged["d"]._values, right["d"]._values)
+
+ def test_intelligently_handle_join_key(self):
+ # #733, be a bit more 1337 about not returning unconsolidated DataFrame
+
+ left = DataFrame(
+ {"key": [1, 1, 2, 2, 3], "value": list(range(5))}, columns=["value", "key"]
+ )
+ right = DataFrame({"key": [1, 1, 2, 3, 4, 5], "rvalue": list(range(6))})
+
+ joined = merge(left, right, on="key", how="outer")
+ expected = DataFrame(
+ {
+ "key": [1, 1, 1, 1, 2, 2, 3, 4, 5],
+ "value": np.array([0, 0, 1, 1, 2, 3, 4, np.nan, np.nan]),
+ "rvalue": [0, 1, 0, 1, 2, 2, 3, 4, 5],
+ },
+ columns=["value", "key", "rvalue"],
+ )
+ tm.assert_frame_equal(joined, expected)
+
+ def test_merge_join_key_dtype_cast(self):
+ # #8596
+
+ df1 = DataFrame({"key": [1], "v1": [10]})
+ df2 = DataFrame({"key": [2], "v1": [20]})
+ df = merge(df1, df2, how="outer")
+ assert df["key"].dtype == "int64"
+
+ df1 = DataFrame({"key": [True], "v1": [1]})
+ df2 = DataFrame({"key": [False], "v1": [0]})
+ df = merge(df1, df2, how="outer")
+
+ # GH13169
+ # GH#40073
+ assert df["key"].dtype == "bool"
+
+ df1 = DataFrame({"val": [1]})
+ df2 = DataFrame({"val": [2]})
+ lkey = np.array([1])
+ rkey = np.array([2])
+ df = merge(df1, df2, left_on=lkey, right_on=rkey, how="outer")
+ assert df["key_0"].dtype == np.dtype(int)
+
+ def test_handle_join_key_pass_array(self):
+ left = DataFrame(
+ {"key": [1, 1, 2, 2, 3], "value": np.arange(5)},
+ columns=["value", "key"],
+ dtype="int64",
+ )
+ right = DataFrame({"rvalue": np.arange(6)}, dtype="int64")
+ key = np.array([1, 1, 2, 3, 4, 5], dtype="int64")
+
+ merged = merge(left, right, left_on="key", right_on=key, how="outer")
+ merged2 = merge(right, left, left_on=key, right_on="key", how="outer")
+
+ tm.assert_series_equal(merged["key"], merged2["key"])
+ assert merged["key"].notna().all()
+ assert merged2["key"].notna().all()
+
+ left = DataFrame({"value": np.arange(5)}, columns=["value"])
+ right = DataFrame({"rvalue": np.arange(6)})
+ lkey = np.array([1, 1, 2, 2, 3])
+ rkey = np.array([1, 1, 2, 3, 4, 5])
+
+ merged = merge(left, right, left_on=lkey, right_on=rkey, how="outer")
+ expected = Series([1, 1, 1, 1, 2, 2, 3, 4, 5], dtype=int, name="key_0")
+ tm.assert_series_equal(merged["key_0"], expected)
+
+ left = DataFrame({"value": np.arange(3)})
+ right = DataFrame({"rvalue": np.arange(6)})
+
+ key = np.array([0, 1, 1, 2, 2, 3], dtype=np.int64)
+ merged = merge(left, right, left_index=True, right_on=key, how="outer")
+ tm.assert_series_equal(merged["key_0"], Series(key, name="key_0"))
+
+ def test_no_overlap_more_informative_error(self):
+ dt = datetime.now()
+ df1 = DataFrame({"x": ["a"]}, index=[dt])
+
+ df2 = DataFrame({"y": ["b", "c"]}, index=[dt, dt])
+
+ msg = (
+ "No common columns to perform merge on. "
+ f"Merge options: left_on={None}, right_on={None}, "
+ f"left_index={False}, right_index={False}"
+ )
+
+ with pytest.raises(MergeError, match=msg):
+ merge(df1, df2)
+
+ def test_merge_non_unique_indexes(self):
+ dt = datetime(2012, 5, 1)
+ dt2 = datetime(2012, 5, 2)
+ dt3 = datetime(2012, 5, 3)
+ dt4 = datetime(2012, 5, 4)
+
+ df1 = DataFrame({"x": ["a"]}, index=[dt])
+ df2 = DataFrame({"y": ["b", "c"]}, index=[dt, dt])
+ _check_merge(df1, df2)
+
+ # Not monotonic
+ df1 = DataFrame({"x": ["a", "b", "q"]}, index=[dt2, dt, dt4])
+ df2 = DataFrame(
+ {"y": ["c", "d", "e", "f", "g", "h"]}, index=[dt3, dt3, dt2, dt2, dt, dt]
+ )
+ _check_merge(df1, df2)
+
+ df1 = DataFrame({"x": ["a", "b"]}, index=[dt, dt])
+ df2 = DataFrame({"y": ["c", "d"]}, index=[dt, dt])
+ _check_merge(df1, df2)
+
+ def test_merge_non_unique_index_many_to_many(self):
+ dt = datetime(2012, 5, 1)
+ dt2 = datetime(2012, 5, 2)
+ dt3 = datetime(2012, 5, 3)
+ df1 = DataFrame({"x": ["a", "b", "c", "d"]}, index=[dt2, dt2, dt, dt])
+ df2 = DataFrame(
+ {"y": ["e", "f", "g", " h", "i"]}, index=[dt2, dt2, dt3, dt, dt]
+ )
+ _check_merge(df1, df2)
+
+ def test_left_merge_empty_dataframe(self):
+ left = DataFrame({"key": [1], "value": [2]})
+ right = DataFrame({"key": []})
+
+ result = merge(left, right, on="key", how="left")
+ tm.assert_frame_equal(result, left)
+
+ result = merge(right, left, on="key", how="right")
+ tm.assert_frame_equal(result, left)
+
+ def test_merge_empty_dataframe(self, index, join_type):
+ # GH52777
+ left = DataFrame([], index=index[:0])
+ right = left.copy()
+
+ result = left.join(right, how=join_type)
+ tm.assert_frame_equal(result, left)
+
+ @pytest.mark.parametrize(
+ "kwarg",
+ [
+ {"left_index": True, "right_index": True},
+ {"left_index": True, "right_on": "x"},
+ {"left_on": "a", "right_index": True},
+ {"left_on": "a", "right_on": "x"},
+ ],
+ )
+ def test_merge_left_empty_right_empty(self, join_type, kwarg):
+ # GH 10824
+ left = DataFrame(columns=["a", "b", "c"])
+ right = DataFrame(columns=["x", "y", "z"])
+
+ exp_in = DataFrame(columns=["a", "b", "c", "x", "y", "z"], dtype=object)
+
+ result = merge(left, right, how=join_type, **kwarg)
+ tm.assert_frame_equal(result, exp_in)
+
+ def test_merge_left_empty_right_notempty(self):
+ # GH 10824
+ left = DataFrame(columns=["a", "b", "c"])
+ right = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=["x", "y", "z"])
+
+ exp_out = DataFrame(
+ {
+ "a": np.array([np.nan] * 3, dtype=object),
+ "b": np.array([np.nan] * 3, dtype=object),
+ "c": np.array([np.nan] * 3, dtype=object),
+ "x": [1, 4, 7],
+ "y": [2, 5, 8],
+ "z": [3, 6, 9],
+ },
+ columns=["a", "b", "c", "x", "y", "z"],
+ )
+ exp_in = exp_out[0:0] # make empty DataFrame keeping dtype
+
+ def check1(exp, kwarg):
+ result = merge(left, right, how="inner", **kwarg)
+ tm.assert_frame_equal(result, exp)
+ result = merge(left, right, how="left", **kwarg)
+ tm.assert_frame_equal(result, exp)
+
+ def check2(exp, kwarg):
+ result = merge(left, right, how="right", **kwarg)
+ tm.assert_frame_equal(result, exp)
+ result = merge(left, right, how="outer", **kwarg)
+ tm.assert_frame_equal(result, exp)
+
+ for kwarg in [
+ {"left_index": True, "right_index": True},
+ {"left_index": True, "right_on": "x"},
+ ]:
+ check1(exp_in, kwarg)
+ check2(exp_out, kwarg)
+
+ kwarg = {"left_on": "a", "right_index": True}
+ check1(exp_in, kwarg)
+ exp_out["a"] = [0, 1, 2]
+ check2(exp_out, kwarg)
+
+ kwarg = {"left_on": "a", "right_on": "x"}
+ check1(exp_in, kwarg)
+ exp_out["a"] = np.array([np.nan] * 3, dtype=object)
+ check2(exp_out, kwarg)
+
+ def test_merge_left_notempty_right_empty(self):
+ # GH 10824
+ left = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=["a", "b", "c"])
+ right = DataFrame(columns=["x", "y", "z"])
+
+ exp_out = DataFrame(
+ {
+ "a": [1, 4, 7],
+ "b": [2, 5, 8],
+ "c": [3, 6, 9],
+ "x": np.array([np.nan] * 3, dtype=object),
+ "y": np.array([np.nan] * 3, dtype=object),
+ "z": np.array([np.nan] * 3, dtype=object),
+ },
+ columns=["a", "b", "c", "x", "y", "z"],
+ )
+ exp_in = exp_out[0:0] # make empty DataFrame keeping dtype
+ # result will have object dtype
+ exp_in.index = exp_in.index.astype(object)
+
+ def check1(exp, kwarg):
+ result = merge(left, right, how="inner", **kwarg)
+ tm.assert_frame_equal(result, exp)
+ result = merge(left, right, how="right", **kwarg)
+ tm.assert_frame_equal(result, exp)
+
+ def check2(exp, kwarg):
+ result = merge(left, right, how="left", **kwarg)
+ tm.assert_frame_equal(result, exp)
+ result = merge(left, right, how="outer", **kwarg)
+ tm.assert_frame_equal(result, exp)
+
+ # TODO: should the next loop be un-indented? doing so breaks this test
+ for kwarg in [
+ {"left_index": True, "right_index": True},
+ {"left_index": True, "right_on": "x"},
+ {"left_on": "a", "right_index": True},
+ {"left_on": "a", "right_on": "x"},
+ ]:
+ check1(exp_in, kwarg)
+ check2(exp_out, kwarg)
+
+ @pytest.mark.parametrize(
+ "series_of_dtype",
+ [
+ Series([1], dtype="int64"),
+ Series([1], dtype="Int64"),
+ Series([1.23]),
+ Series(["foo"]),
+ Series([True]),
+ Series([pd.Timestamp("2018-01-01")]),
+ Series([pd.Timestamp("2018-01-01", tz="US/Eastern")]),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "series_of_dtype2",
+ [
+ Series([1], dtype="int64"),
+ Series([1], dtype="Int64"),
+ Series([1.23]),
+ Series(["foo"]),
+ Series([True]),
+ Series([pd.Timestamp("2018-01-01")]),
+ Series([pd.Timestamp("2018-01-01", tz="US/Eastern")]),
+ ],
+ )
+ def test_merge_empty_frame(self, series_of_dtype, series_of_dtype2):
+ # GH 25183
+ df = DataFrame(
+ {"key": series_of_dtype, "value": series_of_dtype2},
+ columns=["key", "value"],
+ )
+ df_empty = df[:0]
+ expected = DataFrame(
+ {
+ "key": Series(dtype=df.dtypes["key"]),
+ "value_x": Series(dtype=df.dtypes["value"]),
+ "value_y": Series(dtype=df.dtypes["value"]),
+ },
+ columns=["key", "value_x", "value_y"],
+ )
+ actual = df_empty.merge(df, on="key")
+ tm.assert_frame_equal(actual, expected)
+
+ @pytest.mark.parametrize(
+ "series_of_dtype",
+ [
+ Series([1], dtype="int64"),
+ Series([1], dtype="Int64"),
+ Series([1.23]),
+ Series(["foo"]),
+ Series([True]),
+ Series([pd.Timestamp("2018-01-01")]),
+ Series([pd.Timestamp("2018-01-01", tz="US/Eastern")]),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "series_of_dtype_all_na",
+ [
+ Series([pd.NA], dtype="Int64"),
+ Series([np.nan], dtype="float"),
+ Series([np.nan], dtype="object"),
+ Series([pd.NaT]),
+ ],
+ )
+ def test_merge_all_na_column(self, series_of_dtype, series_of_dtype_all_na):
+ # GH 25183
+ df_left = DataFrame(
+ {"key": series_of_dtype, "value": series_of_dtype_all_na},
+ columns=["key", "value"],
+ )
+ df_right = DataFrame(
+ {"key": series_of_dtype, "value": series_of_dtype_all_na},
+ columns=["key", "value"],
+ )
+ expected = DataFrame(
+ {
+ "key": series_of_dtype,
+ "value_x": series_of_dtype_all_na,
+ "value_y": series_of_dtype_all_na,
+ },
+ columns=["key", "value_x", "value_y"],
+ )
+ actual = df_left.merge(df_right, on="key")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_merge_nosort(self):
+ # GH#2098
+
+ d = {
+ "var1": np.random.default_rng(2).integers(0, 10, size=10),
+ "var2": np.random.default_rng(2).integers(0, 10, size=10),
+ "var3": [
+ datetime(2012, 1, 12),
+ datetime(2011, 2, 4),
+ datetime(2010, 2, 3),
+ datetime(2012, 1, 12),
+ datetime(2011, 2, 4),
+ datetime(2012, 4, 3),
+ datetime(2012, 3, 4),
+ datetime(2008, 5, 1),
+ datetime(2010, 2, 3),
+ datetime(2012, 2, 3),
+ ],
+ }
+ df = DataFrame.from_dict(d)
+ var3 = df.var3.unique()
+ var3 = np.sort(var3)
+ new = DataFrame.from_dict(
+ {"var3": var3, "var8": np.random.default_rng(2).random(7)}
+ )
+
+ result = df.merge(new, on="var3", sort=False)
+ exp = merge(df, new, on="var3", sort=False)
+ tm.assert_frame_equal(result, exp)
+
+ assert (df.var3.unique() == result.var3.unique()).all()
+
+ @pytest.mark.parametrize(
+ ("sort", "values"), [(False, [1, 1, 0, 1, 1]), (True, [0, 1, 1, 1, 1])]
+ )
+ @pytest.mark.parametrize("how", ["left", "right"])
+ def test_merge_same_order_left_right(self, sort, values, how):
+ # GH#35382
+ df = DataFrame({"a": [1, 0, 1]})
+
+ result = df.merge(df, on="a", how=how, sort=sort)
+ expected = DataFrame(values, columns=["a"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_nan_right(self):
+ df1 = DataFrame({"i1": [0, 1], "i2": [0, 1]})
+ df2 = DataFrame({"i1": [0], "i3": [0]})
+ result = df1.join(df2, on="i1", rsuffix="_")
+ expected = (
+ DataFrame(
+ {
+ "i1": {0: 0.0, 1: 1},
+ "i2": {0: 0, 1: 1},
+ "i1_": {0: 0, 1: np.nan},
+ "i3": {0: 0.0, 1: np.nan},
+ None: {0: 0, 1: 0},
+ },
+ columns=Index(["i1", "i2", "i1_", "i3", None], dtype=object),
+ )
+ .set_index(None)
+ .reset_index()[["i1", "i2", "i1_", "i3"]]
+ )
+ result.columns = result.columns.astype("object")
+ tm.assert_frame_equal(result, expected, check_dtype=False)
+
+ def test_merge_nan_right2(self):
+ df1 = DataFrame({"i1": [0, 1], "i2": [0.5, 1.5]})
+ df2 = DataFrame({"i1": [0], "i3": [0.7]})
+ result = df1.join(df2, rsuffix="_", on="i1")
+ expected = DataFrame(
+ {
+ "i1": {0: 0, 1: 1},
+ "i1_": {0: 0.0, 1: np.nan},
+ "i2": {0: 0.5, 1: 1.5},
+ "i3": {0: 0.69999999999999996, 1: np.nan},
+ }
+ )[["i1", "i2", "i1_", "i3"]]
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
+ )
+ def test_merge_type(self, df, df2):
+ class NotADataFrame(DataFrame):
+ @property
+ def _constructor(self):
+ return NotADataFrame
+
+ nad = NotADataFrame(df)
+ result = nad.merge(df2, on="key1")
+
+ assert isinstance(result, NotADataFrame)
+
+ def test_join_append_timedeltas(self):
+ # timedelta64 issues with join/merge
+ # GH 5695
+
+ d = DataFrame.from_dict(
+ {"d": [datetime(2013, 11, 5, 5, 56)], "t": [timedelta(0, 22500)]}
+ )
+ df = DataFrame(columns=list("dt"))
+ df = concat([df, d], ignore_index=True)
+ result = concat([df, d], ignore_index=True)
+ expected = DataFrame(
+ {
+ "d": [datetime(2013, 11, 5, 5, 56), datetime(2013, 11, 5, 5, 56)],
+ "t": [timedelta(0, 22500), timedelta(0, 22500)],
+ },
+ dtype=object,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_append_timedeltas2(self):
+ # timedelta64 issues with join/merge
+ # GH 5695
+ td = np.timedelta64(300000000)
+ lhs = DataFrame(Series([td, td], index=["A", "B"]))
+ rhs = DataFrame(Series([td], index=["A"]))
+
+ result = lhs.join(rhs, rsuffix="r", how="left")
+ expected = DataFrame(
+ {
+ "0": Series([td, td], index=list("AB")),
+ "0r": Series([td, pd.NaT], index=list("AB")),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("unit", ["D", "h", "m", "s", "ms", "us", "ns"])
+ def test_other_datetime_unit(self, unit):
+ # GH 13389
+ df1 = DataFrame({"entity_id": [101, 102]})
+ ser = Series([None, None], index=[101, 102], name="days")
+
+ dtype = f"datetime64[{unit}]"
+
+ if unit in ["D", "h", "m"]:
+ # not supported so we cast to the nearest supported unit, seconds
+ exp_dtype = "datetime64[s]"
+ else:
+ exp_dtype = dtype
+ df2 = ser.astype(exp_dtype).to_frame("days")
+ assert df2["days"].dtype == exp_dtype
+
+ result = df1.merge(df2, left_on="entity_id", right_index=True)
+
+ days = np.array(["nat", "nat"], dtype=exp_dtype)
+ days = pd.core.arrays.DatetimeArray._simple_new(days, dtype=days.dtype)
+ exp = DataFrame(
+ {
+ "entity_id": [101, 102],
+ "days": days,
+ },
+ columns=["entity_id", "days"],
+ )
+ assert exp["days"].dtype == exp_dtype
+ tm.assert_frame_equal(result, exp)
+
+ @pytest.mark.parametrize("unit", ["D", "h", "m", "s", "ms", "us", "ns"])
+ def test_other_timedelta_unit(self, unit):
+ # GH 13389
+ df1 = DataFrame({"entity_id": [101, 102]})
+ ser = Series([None, None], index=[101, 102], name="days")
+
+ dtype = f"m8[{unit}]"
+ if unit in ["D", "h", "m"]:
+ # We cannot astype, instead do nearest supported unit, i.e. "s"
+ msg = "Supported resolutions are 's', 'ms', 'us', 'ns'"
+ with pytest.raises(ValueError, match=msg):
+ ser.astype(dtype)
+
+ df2 = ser.astype("m8[s]").to_frame("days")
+ else:
+ df2 = ser.astype(dtype).to_frame("days")
+ assert df2["days"].dtype == dtype
+
+ result = df1.merge(df2, left_on="entity_id", right_index=True)
+
+ exp = DataFrame(
+ {"entity_id": [101, 102], "days": np.array(["nat", "nat"], dtype=dtype)},
+ columns=["entity_id", "days"],
+ )
+ tm.assert_frame_equal(result, exp)
+
+ def test_overlapping_columns_error_message(self):
+ df = DataFrame({"key": [1, 2, 3], "v1": [4, 5, 6], "v2": [7, 8, 9]})
+ df2 = DataFrame({"key": [1, 2, 3], "v1": [4, 5, 6], "v2": [7, 8, 9]})
+
+ df.columns = ["key", "foo", "foo"]
+ df2.columns = ["key", "bar", "bar"]
+ expected = DataFrame(
+ {
+ "key": [1, 2, 3],
+ "v1": [4, 5, 6],
+ "v2": [7, 8, 9],
+ "v3": [4, 5, 6],
+ "v4": [7, 8, 9],
+ }
+ )
+ expected.columns = ["key", "foo", "foo", "bar", "bar"]
+ tm.assert_frame_equal(merge(df, df2), expected)
+
+ # #2649, #10639
+ df2.columns = ["key1", "foo", "foo"]
+ msg = r"Data columns not unique: Index\(\['foo'\], dtype='object|str'\)"
+ with pytest.raises(MergeError, match=msg):
+ merge(df, df2)
+
+ def test_merge_on_datetime64tz(self):
+ # GH11405
+ left = DataFrame(
+ {
+ "key": pd.date_range("20151010", periods=2, tz="US/Eastern"),
+ "value": [1, 2],
+ }
+ )
+ right = DataFrame(
+ {
+ "key": pd.date_range("20151011", periods=3, tz="US/Eastern"),
+ "value": [1, 2, 3],
+ }
+ )
+
+ expected = DataFrame(
+ {
+ "key": pd.date_range("20151010", periods=4, tz="US/Eastern"),
+ "value_x": [1, 2, np.nan, np.nan],
+ "value_y": [np.nan, 1, 2, 3],
+ }
+ )
+ result = merge(left, right, on="key", how="outer")
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_datetime64tz_values(self):
+ left = DataFrame(
+ {
+ "key": [1, 2],
+ "value": pd.date_range(
+ "20151010", periods=2, tz="US/Eastern", unit="ns"
+ ),
+ }
+ )
+ right = DataFrame(
+ {
+ "key": [2, 3],
+ "value": pd.date_range(
+ "20151011", periods=2, tz="US/Eastern", unit="ns"
+ ),
+ }
+ )
+ expected = DataFrame(
+ {
+ "key": [1, 2, 3],
+ "value_x": [
+ *list(
+ pd.date_range("20151010", periods=2, tz="US/Eastern", unit="ns")
+ ),
+ pd.NaT,
+ ],
+ "value_y": [
+ pd.NaT,
+ *list(
+ pd.date_range("20151011", periods=2, tz="US/Eastern", unit="ns")
+ ),
+ ],
+ }
+ )
+ result = merge(left, right, on="key", how="outer")
+ tm.assert_frame_equal(result, expected)
+ assert result["value_x"].dtype == "datetime64[ns, US/Eastern]"
+ assert result["value_y"].dtype == "datetime64[ns, US/Eastern]"
+
+ def test_merge_on_datetime64tz_empty(self):
+ # https://github.com/pandas-dev/pandas/issues/25014
+ dtz = pd.DatetimeTZDtype(tz="UTC")
+ right = DataFrame(
+ {
+ "date": DatetimeIndex(["2018"], dtype=dtz),
+ "value": [4.0],
+ "date2": DatetimeIndex(["2019"], dtype=dtz),
+ },
+ columns=["date", "value", "date2"],
+ )
+ left = right[:0]
+ result = left.merge(right, on="date")
+ expected = DataFrame(
+ {
+ "date": Series(dtype=dtz),
+ "value_x": Series(dtype=float),
+ "date2_x": Series(dtype=dtz),
+ "value_y": Series(dtype=float),
+ "date2_y": Series(dtype=dtz),
+ },
+ columns=["date", "value_x", "date2_x", "value_y", "date2_y"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_datetime64tz_with_dst_transition(self):
+ # GH 18885
+ df1 = DataFrame(
+ pd.date_range("2017-10-29 01:00", periods=4, freq="h", tz="Europe/Madrid"),
+ columns=["date"],
+ )
+ df1["value"] = 1
+ df2 = DataFrame(
+ {
+ "date": pd.to_datetime(
+ [
+ "2017-10-29 03:00:00",
+ "2017-10-29 04:00:00",
+ "2017-10-29 05:00:00",
+ ]
+ ),
+ "value": 2,
+ }
+ )
+ df2["date"] = df2["date"].dt.tz_localize("UTC").dt.tz_convert("Europe/Madrid")
+ result = merge(df1, df2, how="outer", on="date")
+ expected = DataFrame(
+ {
+ "date": pd.date_range(
+ "2017-10-29 01:00", periods=7, freq="h", tz="Europe/Madrid"
+ ),
+ "value_x": [1] * 4 + [np.nan] * 3,
+ "value_y": [np.nan] * 4 + [2] * 3,
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_non_unique_period_index(self):
+ # GH #16871
+ index = pd.period_range("2016-01-01", periods=16, freq="M")
+ df = DataFrame(list(range(len(index))), index=index, columns=["pnum"])
+ df2 = concat([df, df])
+ result = df.merge(df2, left_index=True, right_index=True, how="inner")
+ expected = DataFrame(
+ np.tile(np.arange(16, dtype=np.int64).repeat(2).reshape(-1, 1), 2),
+ columns=["pnum_x", "pnum_y"],
+ index=df2.sort_index().index,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_on_periods(self):
+ left = DataFrame(
+ {"key": pd.period_range("20151010", periods=2, freq="D"), "value": [1, 2]}
+ )
+ right = DataFrame(
+ {
+ "key": pd.period_range("20151011", periods=3, freq="D"),
+ "value": [1, 2, 3],
+ }
+ )
+
+ expected = DataFrame(
+ {
+ "key": pd.period_range("20151010", periods=4, freq="D"),
+ "value_x": [1, 2, np.nan, np.nan],
+ "value_y": [np.nan, 1, 2, 3],
+ }
+ )
+ result = merge(left, right, on="key", how="outer")
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_period_values(self):
+ left = DataFrame(
+ {"key": [1, 2], "value": pd.period_range("20151010", periods=2, freq="D")}
+ )
+ right = DataFrame(
+ {"key": [2, 3], "value": pd.period_range("20151011", periods=2, freq="D")}
+ )
+
+ exp_x = pd.period_range("20151010", periods=2, freq="D")
+ exp_y = pd.period_range("20151011", periods=2, freq="D")
+ expected = DataFrame(
+ {
+ "key": [1, 2, 3],
+ "value_x": [*list(exp_x), pd.NaT],
+ "value_y": [pd.NaT, *list(exp_y)],
+ }
+ )
+ result = merge(left, right, on="key", how="outer")
+ tm.assert_frame_equal(result, expected)
+ assert result["value_x"].dtype == "Period[D]"
+ assert result["value_y"].dtype == "Period[D]"
+
+ def test_indicator(self, dfs_for_indicator):
+ # PR #10054. xref #7412 and closes #8790.
+ df1, df2 = dfs_for_indicator
+ df1_copy = df1.copy()
+
+ df2_copy = df2.copy()
+
+ df_result = DataFrame(
+ {
+ "col1": [0, 1, 2, 3, 4, 5],
+ "col_conflict_x": [1, 2, np.nan, np.nan, np.nan, np.nan],
+ "col_left": ["a", "b", np.nan, np.nan, np.nan, np.nan],
+ "col_conflict_y": [np.nan, 1, 2, 3, 4, 5],
+ "col_right": [np.nan, 2, 2, 2, 2, 2],
+ }
+ )
+ df_result["_merge"] = Categorical(
+ [
+ "left_only",
+ "both",
+ "right_only",
+ "right_only",
+ "right_only",
+ "right_only",
+ ],
+ categories=["left_only", "right_only", "both"],
+ )
+
+ df_result = df_result[
+ [
+ "col1",
+ "col_conflict_x",
+ "col_left",
+ "col_conflict_y",
+ "col_right",
+ "_merge",
+ ]
+ ]
+
+ test = merge(df1, df2, on="col1", how="outer", indicator=True)
+ tm.assert_frame_equal(test, df_result)
+ test = df1.merge(df2, on="col1", how="outer", indicator=True)
+ tm.assert_frame_equal(test, df_result)
+
+ # No side effects
+ tm.assert_frame_equal(df1, df1_copy)
+ tm.assert_frame_equal(df2, df2_copy)
+
+ # Check with custom name
+ df_result_custom_name = df_result
+ df_result_custom_name = df_result_custom_name.rename(
+ columns={"_merge": "custom_name"}
+ )
+
+ test_custom_name = merge(
+ df1, df2, on="col1", how="outer", indicator="custom_name"
+ )
+ tm.assert_frame_equal(test_custom_name, df_result_custom_name)
+ test_custom_name = df1.merge(
+ df2, on="col1", how="outer", indicator="custom_name"
+ )
+ tm.assert_frame_equal(test_custom_name, df_result_custom_name)
+
+ def test_merge_indicator_arg_validation(self, dfs_for_indicator):
+ # Check only accepts strings and booleans
+ df1, df2 = dfs_for_indicator
+
+ msg = "indicator option can only accept boolean or string arguments"
+ with pytest.raises(ValueError, match=msg):
+ merge(df1, df2, on="col1", how="outer", indicator=5)
+ with pytest.raises(ValueError, match=msg):
+ df1.merge(df2, on="col1", how="outer", indicator=5)
+
+ def test_merge_indicator_result_integrity(self, dfs_for_indicator):
+ # Check result integrity
+ df1, df2 = dfs_for_indicator
+
+ test2 = merge(df1, df2, on="col1", how="left", indicator=True)
+ assert (test2._merge != "right_only").all()
+ test2 = df1.merge(df2, on="col1", how="left", indicator=True)
+ assert (test2._merge != "right_only").all()
+
+ test3 = merge(df1, df2, on="col1", how="right", indicator=True)
+ assert (test3._merge != "left_only").all()
+ test3 = df1.merge(df2, on="col1", how="right", indicator=True)
+ assert (test3._merge != "left_only").all()
+
+ test4 = merge(df1, df2, on="col1", how="inner", indicator=True)
+ assert (test4._merge == "both").all()
+ test4 = df1.merge(df2, on="col1", how="inner", indicator=True)
+ assert (test4._merge == "both").all()
+
+ def test_merge_indicator_invalid(self, dfs_for_indicator):
+ # Check if working name in df
+ df1, _ = dfs_for_indicator
+
+ for i in ["_right_indicator", "_left_indicator", "_merge"]:
+ df_badcolumn = DataFrame({"col1": [1, 2], i: [2, 2]})
+
+ msg = (
+ "Cannot use `indicator=True` option when data contains a "
+ f"column named {i}|"
+ "Cannot use name of an existing column for indicator column"
+ )
+ with pytest.raises(ValueError, match=msg):
+ merge(df1, df_badcolumn, on="col1", how="outer", indicator=True)
+ with pytest.raises(ValueError, match=msg):
+ df1.merge(df_badcolumn, on="col1", how="outer", indicator=True)
+
+ # Check for name conflict with custom name
+ df_badcolumn = DataFrame({"col1": [1, 2], "custom_column_name": [2, 2]})
+
+ msg = "Cannot use name of an existing column for indicator column"
+ with pytest.raises(ValueError, match=msg):
+ merge(
+ df1,
+ df_badcolumn,
+ on="col1",
+ how="outer",
+ indicator="custom_column_name",
+ )
+ with pytest.raises(ValueError, match=msg):
+ df1.merge(
+ df_badcolumn, on="col1", how="outer", indicator="custom_column_name"
+ )
+
+ def test_merge_indicator_multiple_columns(self):
+ # Merge on multiple columns
+ df3 = DataFrame({"col1": [0, 1], "col2": ["a", "b"]})
+
+ df4 = DataFrame({"col1": [1, 1, 3], "col2": ["b", "x", "y"]})
+
+ hand_coded_result = DataFrame(
+ {"col1": [0, 1, 1, 3], "col2": ["a", "b", "x", "y"]}
+ )
+ hand_coded_result["_merge"] = Categorical(
+ ["left_only", "both", "right_only", "right_only"],
+ categories=["left_only", "right_only", "both"],
+ )
+
+ test5 = merge(df3, df4, on=["col1", "col2"], how="outer", indicator=True)
+ tm.assert_frame_equal(test5, hand_coded_result)
+ test5 = df3.merge(df4, on=["col1", "col2"], how="outer", indicator=True)
+ tm.assert_frame_equal(test5, hand_coded_result)
+
+ def test_validation(self):
+ left = DataFrame(
+ {"a": ["a", "b", "c", "d"], "b": ["cat", "dog", "weasel", "horse"]},
+ index=range(4),
+ )
+
+ right = DataFrame(
+ {
+ "a": ["a", "b", "c", "d", "e"],
+ "c": ["meow", "bark", "um... weasel noise?", "nay", "chirp"],
+ },
+ index=range(5),
+ )
+
+ # Make sure no side effects.
+ left_copy = left.copy()
+ right_copy = right.copy()
+
+ result = merge(left, right, left_index=True, right_index=True, validate="1:1")
+ tm.assert_frame_equal(left, left_copy)
+ tm.assert_frame_equal(right, right_copy)
+
+ # make sure merge still correct
+ expected = DataFrame(
+ {
+ "a_x": ["a", "b", "c", "d"],
+ "b": ["cat", "dog", "weasel", "horse"],
+ "a_y": ["a", "b", "c", "d"],
+ "c": ["meow", "bark", "um... weasel noise?", "nay"],
+ },
+ index=range(4),
+ columns=["a_x", "b", "a_y", "c"],
+ )
+
+ result = merge(
+ left, right, left_index=True, right_index=True, validate="one_to_one"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ expected_2 = DataFrame(
+ {
+ "a": ["a", "b", "c", "d"],
+ "b": ["cat", "dog", "weasel", "horse"],
+ "c": ["meow", "bark", "um... weasel noise?", "nay"],
+ },
+ index=range(4),
+ )
+
+ result = merge(left, right, on="a", validate="1:1")
+ tm.assert_frame_equal(left, left_copy)
+ tm.assert_frame_equal(right, right_copy)
+ tm.assert_frame_equal(result, expected_2)
+
+ result = merge(left, right, on="a", validate="one_to_one")
+ tm.assert_frame_equal(result, expected_2)
+
+ # One index, one column
+ expected_3 = DataFrame(
+ {
+ "b": ["cat", "dog", "weasel", "horse"],
+ "a": ["a", "b", "c", "d"],
+ "c": ["meow", "bark", "um... weasel noise?", "nay"],
+ },
+ columns=["b", "a", "c"],
+ index=range(4),
+ )
+
+ left_index_reset = left.set_index("a")
+ result = merge(
+ left_index_reset,
+ right,
+ left_index=True,
+ right_on="a",
+ validate="one_to_one",
+ )
+ tm.assert_frame_equal(result, expected_3)
+
+ # Dups on right
+ right_w_dups = concat([right, DataFrame({"a": ["e"], "c": ["moo"]}, index=[4])])
+ merge(
+ left,
+ right_w_dups,
+ left_index=True,
+ right_index=True,
+ validate="one_to_many",
+ )
+
+ msg = "Merge keys are not unique in right dataset; not a one-to-one merge"
+ with pytest.raises(MergeError, match=msg):
+ merge(
+ left,
+ right_w_dups,
+ left_index=True,
+ right_index=True,
+ validate="one_to_one",
+ )
+
+ with pytest.raises(MergeError, match=msg):
+ merge(left, right_w_dups, on="a", validate="one_to_one")
+
+ # Dups on left
+ left_w_dups = concat(
+ [left, DataFrame({"a": ["a"], "c": ["cow"]}, index=[3])], sort=True
+ )
+ merge(
+ left_w_dups,
+ right,
+ left_index=True,
+ right_index=True,
+ validate="many_to_one",
+ )
+
+ msg = "Merge keys are not unique in left dataset; not a one-to-one merge"
+ with pytest.raises(MergeError, match=msg):
+ merge(
+ left_w_dups,
+ right,
+ left_index=True,
+ right_index=True,
+ validate="one_to_one",
+ )
+
+ with pytest.raises(MergeError, match=msg):
+ merge(left_w_dups, right, on="a", validate="one_to_one")
+
+ # Dups on both
+ merge(left_w_dups, right_w_dups, on="a", validate="many_to_many")
+
+ msg = "Merge keys are not unique in right dataset; not a many-to-one merge"
+ with pytest.raises(MergeError, match=msg):
+ merge(
+ left_w_dups,
+ right_w_dups,
+ left_index=True,
+ right_index=True,
+ validate="many_to_one",
+ )
+
+ msg = "Merge keys are not unique in left dataset; not a one-to-many merge"
+ with pytest.raises(MergeError, match=msg):
+ merge(left_w_dups, right_w_dups, on="a", validate="one_to_many")
+
+ # Check invalid arguments
+ msg = (
+ '"jibberish" is not a valid argument. '
+ "Valid arguments are:\n"
+ '- "1:1"\n'
+ '- "1:m"\n'
+ '- "m:1"\n'
+ '- "m:m"\n'
+ '- "one_to_one"\n'
+ '- "one_to_many"\n'
+ '- "many_to_one"\n'
+ '- "many_to_many"'
+ )
+ with pytest.raises(ValueError, match=msg):
+ merge(left, right, on="a", validate="jibberish")
+
+ # Two column merge, dups in both, but jointly no dups.
+ left = DataFrame(
+ {
+ "a": ["a", "a", "b", "b"],
+ "b": [0, 1, 0, 1],
+ "c": ["cat", "dog", "weasel", "horse"],
+ },
+ index=range(4),
+ )
+
+ right = DataFrame(
+ {
+ "a": ["a", "a", "b"],
+ "b": [0, 1, 0],
+ "d": ["meow", "bark", "um... weasel noise?"],
+ },
+ index=range(3),
+ )
+
+ expected_multi = DataFrame(
+ {
+ "a": ["a", "a", "b"],
+ "b": [0, 1, 0],
+ "c": ["cat", "dog", "weasel"],
+ "d": ["meow", "bark", "um... weasel noise?"],
+ },
+ index=range(3),
+ )
+
+ msg = (
+ "Merge keys are not unique in either left or right dataset; "
+ "not a one-to-one merge"
+ )
+ with pytest.raises(MergeError, match=msg):
+ merge(left, right, on="a", validate="1:1")
+
+ result = merge(left, right, on=["a", "b"], validate="1:1")
+ tm.assert_frame_equal(result, expected_multi)
+
+ def test_merge_validate_error_message(self):
+ # GH#62742
+ left = DataFrame({"key": [1, 1, 2]})
+ right = DataFrame({"key": [1, 2, 2]})
+
+ with pytest.raises(MergeError, match="Duplicates in left:\n key\n 1 ...\n"):
+ merge(left, right, validate="1:1")
+ with pytest.raises(MergeError, match="Duplicates in left:\n key\n 1 ..."):
+ merge(left, right, validate="1:m")
+ with pytest.raises(MergeError, match="Duplicates in right:\n key\n 2 ..."):
+ merge(left, right, validate="1:1")
+ with pytest.raises(MergeError, match="Duplicates in right:\n key\n 2 ..."):
+ merge(left, right, validate="m:1")
+
+ right = DataFrame({"key": [1, 2, 3]})
+ with pytest.raises(MergeError, match="Duplicates in left:\n key\n 1 ..."):
+ merge(left, right, validate="1:1")
+ with pytest.raises(MergeError, match="Duplicates in right:\n key\n 1 ..."):
+ merge(right, left, validate="1:1")
+
+ def test_merge_two_empty_df_no_division_error(self):
+ # GH17776, PR #17846
+ a = DataFrame({"a": [], "b": [], "c": []})
+ with np.errstate(divide="raise"):
+ merge(a, a, on=("a", "b"))
+
+ @pytest.mark.parametrize("how", ["right", "outer"])
+ @pytest.mark.parametrize(
+ "index,expected_index",
+ [
+ (
+ CategoricalIndex([1, 2, 4]),
+ CategoricalIndex([1, 2, 4, None, None, None]),
+ ),
+ (
+ DatetimeIndex(
+ ["2001-01-01", "2002-02-02", "2003-03-03"], dtype="M8[ns]"
+ ),
+ DatetimeIndex(
+ ["2001-01-01", "2002-02-02", "2003-03-03", pd.NaT, pd.NaT, pd.NaT],
+ dtype="M8[ns]",
+ ),
+ ),
+ *[
+ (
+ Index([1, 2, 3], dtype=dtyp),
+ Index([1, 2, 3, None, None, None], dtype=np.float64),
+ )
+ for dtyp in tm.ALL_REAL_NUMPY_DTYPES
+ ],
+ (
+ IntervalIndex.from_tuples([(1, 2), (2, 3), (3, 4)]),
+ IntervalIndex.from_tuples(
+ [(1, 2), (2, 3), (3, 4), np.nan, np.nan, np.nan]
+ ),
+ ),
+ (
+ PeriodIndex(["2001-01-01", "2001-01-02", "2001-01-03"], freq="D"),
+ PeriodIndex(
+ ["2001-01-01", "2001-01-02", "2001-01-03", pd.NaT, pd.NaT, pd.NaT],
+ freq="D",
+ ),
+ ),
+ (
+ TimedeltaIndex(["1D", "2D", "3D"]),
+ TimedeltaIndex(["1D", "2D", "3D", pd.NaT, pd.NaT, pd.NaT]),
+ ),
+ ],
+ )
+ def test_merge_on_index_with_more_values(self, how, index, expected_index):
+ # GH 24212
+ # pd.merge gets [0, 1, 2, -1, -1, -1] as left_indexer, ensure that
+ # -1 is interpreted as a missing value instead of the last element
+ if index.dtype == "float32" and expected_index.dtype == "float64":
+ # GH#41626
+ expected_index = expected_index.astype("float32")
+ df1 = DataFrame({"a": [0, 1, 2], "key": [0, 1, 2]}, index=index)
+ df2 = DataFrame({"b": [0, 1, 2, 3, 4, 5]})
+ result = df1.merge(df2, left_on="key", right_index=True, how=how)
+ expected = DataFrame(
+ [
+ [0, 0, 0],
+ [1, 1, 1],
+ [2, 2, 2],
+ [np.nan, 3, 3],
+ [np.nan, 4, 4],
+ [np.nan, 5, 5],
+ ],
+ columns=["a", "key", "b"],
+ )
+ expected.set_index(expected_index, inplace=True)
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_right_index_right(self):
+ # Note: the expected output here is probably incorrect.
+ # See https://github.com/pandas-dev/pandas/issues/17257 for more.
+ # We include this as a regression test for GH-24897.
+ left = DataFrame({"a": [1, 2, 3], "key": [0, 1, 1]})
+ right = DataFrame({"b": [1, 2, 3]})
+
+ expected = DataFrame(
+ {"a": [1, 2, 3, None], "key": [0, 1, 1, 2], "b": [1, 2, 2, 3]},
+ columns=["a", "key", "b"],
+ index=[0, 1, 2, np.nan],
+ )
+ result = left.merge(right, left_on="key", right_index=True, how="right")
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("how", ["left", "right"])
+ def test_merge_preserves_row_order(self, how):
+ # GH 27453
+ left_df = DataFrame({"animal": ["dog", "pig"], "max_speed": [40, 11]})
+ right_df = DataFrame({"animal": ["quetzal", "pig"], "max_speed": [80, 11]})
+ result = left_df.merge(right_df, on=["animal", "max_speed"], how=how)
+ if how == "right":
+ expected = DataFrame({"animal": ["quetzal", "pig"], "max_speed": [80, 11]})
+ else:
+ expected = DataFrame({"animal": ["dog", "pig"], "max_speed": [40, 11]})
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_take_missing_values_from_index_of_other_dtype(self):
+ # GH 24212
+ left = DataFrame(
+ {
+ "a": [1, 2, 3],
+ "key": Categorical(["a", "a", "b"], categories=list("abc")),
+ }
+ )
+ right = DataFrame({"b": [1, 2, 3]}, index=CategoricalIndex(["a", "b", "c"]))
+ result = left.merge(right, left_on="key", right_index=True, how="right")
+ expected = DataFrame(
+ {
+ "a": [1, 2, 3, None],
+ "key": Categorical(["a", "a", "b", "c"]),
+ "b": [1, 1, 2, 3],
+ },
+ index=[0, 1, 2, np.nan],
+ )
+ expected = expected.reindex(columns=["a", "key", "b"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_readonly(self):
+ # https://github.com/pandas-dev/pandas/issues/27943
+ data1 = DataFrame(
+ np.arange(20).reshape((4, 5)) + 1, columns=["a", "b", "c", "d", "e"]
+ )
+ data2 = DataFrame(
+ np.arange(20).reshape((5, 4)) + 1, columns=["a", "b", "x", "y"]
+ )
+
+ # make each underlying block array / column array read-only
+ for block in data1._mgr.blocks:
+ block.values.flags.writeable = False
+
+ data1.merge(data2) # no error
+
+ def test_merge_how_validation(self):
+ # https://github.com/pandas-dev/pandas/issues/59422
+ data1 = DataFrame(
+ np.arange(20).reshape((4, 5)) + 1, columns=["a", "b", "c", "d", "e"]
+ )
+ data2 = DataFrame(
+ np.arange(20).reshape((5, 4)) + 1, columns=["a", "b", "x", "y"]
+ )
+ msg = (
+ "'full' is not a valid Merge type: left, right, inner, outer, "
+ "left_anti, right_anti, cross, asof"
+ )
+ with pytest.raises(ValueError, match=re.escape(msg)):
+ data1.merge(data2, how="full")
+
+
+def _check_merge(x, y):
+ for how in ["inner", "left", "outer"]:
+ for sort in [True, False]:
+ result = x.join(y, how=how, sort=sort)
+
+ expected = merge(x.reset_index(), y.reset_index(), how=how, sort=sort)
+ expected = expected.set_index("index")
+
+ # TODO check_names on merge?
+ tm.assert_frame_equal(result, expected, check_names=False)
+
+
+class TestMergeDtypes:
+ @pytest.mark.parametrize("dtype", [object, "category"])
+ def test_different(self, dtype):
+ left = DataFrame(
+ {
+ "A": ["foo", "bar"],
+ "B": Series(["foo", "bar"]).astype("category"),
+ "C": [1, 2],
+ "D": [1.0, 2.0],
+ "E": Series([1, 2], dtype="uint64"),
+ "F": Series([1, 2], dtype="int32"),
+ }
+ )
+ right_vals = Series(["foo", "bar"], dtype=dtype)
+ right = DataFrame({"A": right_vals})
+
+ # GH 9780
+ # We allow merging on object and categorical cols and cast
+ # categorical cols to object
+ result = merge(left, right, on="A")
+ assert is_object_dtype(result.A.dtype) or is_string_dtype(result.A.dtype)
+
+ @pytest.mark.parametrize("d2", [np.int64, np.float64, np.float32, np.float16])
+ def test_join_multi_dtypes(self, any_int_numpy_dtype, d2):
+ dtype1 = np.dtype(any_int_numpy_dtype)
+ dtype2 = np.dtype(d2)
+
+ left = DataFrame(
+ {
+ "k1": np.array([0, 1, 2] * 8, dtype=dtype1),
+ "k2": ["foo", "bar"] * 12,
+ "v": np.array(np.arange(24), dtype=np.int64),
+ }
+ )
+
+ index = MultiIndex.from_tuples([(2, "bar"), (1, "foo")])
+ right = DataFrame({"v2": np.array([5, 7], dtype=dtype2)}, index=index)
+
+ result = left.join(right, on=["k1", "k2"])
+
+ expected = left.copy()
+
+ if dtype2.kind == "i":
+ dtype2 = np.dtype("float64")
+ expected["v2"] = np.array(np.nan, dtype=dtype2)
+ expected.loc[(expected.k1 == 2) & (expected.k2 == "bar"), "v2"] = 5
+ expected.loc[(expected.k1 == 1) & (expected.k2 == "foo"), "v2"] = 7
+
+ tm.assert_frame_equal(result, expected)
+
+ result = left.join(right, on=["k1", "k2"], sort=True)
+ expected.sort_values(["k1", "k2"], kind="mergesort", inplace=True)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "int_vals, float_vals, exp_vals",
+ [
+ ([1, 2, 3], [1.0, 2.0, 3.0], {"X": [1, 2, 3], "Y": [1.0, 2.0, 3.0]}),
+ ([1, 2, 3], [1.0, 3.0], {"X": [1, 3], "Y": [1.0, 3.0]}),
+ ([1, 2], [1.0, 2.0, 3.0], {"X": [1, 2], "Y": [1.0, 2.0]}),
+ ],
+ )
+ def test_merge_on_ints_floats(self, int_vals, float_vals, exp_vals):
+ # GH 16572
+ # Check that float column is not cast to object if
+ # merging on float and int columns
+ A = DataFrame({"X": int_vals})
+ B = DataFrame({"Y": float_vals})
+ expected = DataFrame(exp_vals)
+
+ result = A.merge(B, left_on="X", right_on="Y")
+ tm.assert_frame_equal(result, expected)
+
+ result = B.merge(A, left_on="Y", right_on="X")
+ tm.assert_frame_equal(result, expected[["Y", "X"]])
+
+ def test_merge_key_dtype_cast(self):
+ # GH 17044
+ df1 = DataFrame({"key": [1.0, 2.0], "v1": [10, 20]}, columns=["key", "v1"])
+ df2 = DataFrame({"key": [2], "v2": [200]}, columns=["key", "v2"])
+ result = df1.merge(df2, on="key", how="left")
+ expected = DataFrame(
+ {"key": [1.0, 2.0], "v1": [10, 20], "v2": [np.nan, 200.0]},
+ columns=["key", "v1", "v2"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_on_ints_floats_warning(self):
+ # GH 16572
+ # merge will produce a warning when merging on int and
+ # float columns where the float values are not exactly
+ # equal to their int representation
+ A = DataFrame({"X": [1, 2, 3]})
+ B = DataFrame({"Y": [1.1, 2.5, 3.0]})
+ expected = DataFrame({"X": [3], "Y": [3.0]})
+
+ msg = "the float values are not equal to their int representation"
+ with tm.assert_produces_warning(UserWarning, match=msg):
+ result = A.merge(B, left_on="X", right_on="Y")
+ tm.assert_frame_equal(result, expected)
+
+ with tm.assert_produces_warning(UserWarning, match=msg):
+ result = B.merge(A, left_on="Y", right_on="X")
+ tm.assert_frame_equal(result, expected[["Y", "X"]])
+
+ # test no warning if float has NaNs
+ B = DataFrame({"Y": [np.nan, np.nan, 3.0]})
+
+ with tm.assert_produces_warning(None):
+ result = B.merge(A, left_on="Y", right_on="X")
+ tm.assert_frame_equal(result, expected[["Y", "X"]])
+
+ def test_merge_incompat_infer_boolean_object(self):
+ # GH21119: bool + object bool merge OK
+ df1 = DataFrame({"key": Series([True, False], dtype=object)})
+ df2 = DataFrame({"key": [True, False]})
+
+ expected = DataFrame({"key": [True, False]}, dtype=object)
+ result = merge(df1, df2, on="key")
+ tm.assert_frame_equal(result, expected)
+ result = merge(df2, df1, on="key")
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_incompat_infer_boolean_object_with_missing(self):
+ # GH21119: bool + object bool merge OK
+ # with missing value
+ df1 = DataFrame({"key": Series([True, False, np.nan], dtype=object)})
+ df2 = DataFrame({"key": [True, False]})
+
+ expected = DataFrame({"key": [True, False]}, dtype=object)
+ result = merge(df1, df2, on="key")
+ tm.assert_frame_equal(result, expected)
+ result = merge(df2, df1, on="key")
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "df1_vals, df2_vals",
+ [
+ # merge on category coerces to object
+ ([0, 1, 2], Series(["a", "b", "a"]).astype("category")),
+ ([0.0, 1.0, 2.0], Series(["a", "b", "a"]).astype("category")),
+ # no not infer
+ ([0, 1], Series([False, True], dtype=object)),
+ ([0, 1], Series([False, True], dtype=bool)),
+ ],
+ )
+ def test_merge_incompat_dtypes_are_ok(self, df1_vals, df2_vals):
+ # these are explicitly allowed incompat merges, that pass thru
+ # the result type is dependent on if the values on the rhs are
+ # inferred, otherwise these will be coerced to object
+
+ df1 = DataFrame({"A": df1_vals})
+ df2 = DataFrame({"A": df2_vals})
+
+ result = merge(df1, df2, on=["A"])
+ assert is_object_dtype(result.A.dtype)
+ result = merge(df2, df1, on=["A"])
+ assert is_object_dtype(result.A.dtype) or is_string_dtype(result.A.dtype)
+
+ @pytest.mark.parametrize(
+ "df1_vals, df2_vals",
+ [
+ # do not infer to numeric
+ (Series([1, 2], dtype="uint64"), ["a", "b", "c"]),
+ (Series([1, 2], dtype="int32"), ["a", "b", "c"]),
+ ([0, 1, 2], ["0", "1", "2"]),
+ ([0.0, 1.0, 2.0], ["0", "1", "2"]),
+ (
+ pd.date_range("1/1/2011", periods=2, freq="D"),
+ ["2011-01-01", "2011-01-02"],
+ ),
+ (pd.date_range("1/1/2011", periods=2, freq="D"), [0, 1]),
+ (pd.date_range("1/1/2011", periods=2, freq="D"), [0.0, 1.0]),
+ (
+ pd.date_range("20130101", periods=3),
+ pd.date_range("20130101", periods=3, tz="US/Eastern"),
+ ),
+ ],
+ )
+ def test_merge_incompat_dtypes_error(self, df1_vals, df2_vals):
+ # GH 9780, GH 15800
+ # Raise a ValueError when a user tries to merge on
+ # dtypes that are incompatible (e.g., obj and int/float)
+
+ df1 = DataFrame({"A": df1_vals})
+ df2 = DataFrame({"A": df2_vals})
+
+ msg = (
+ f"You are trying to merge on {df1['A'].dtype} and {df2['A'].dtype} "
+ "columns for key 'A'. If you wish to proceed you should use pd.concat"
+ )
+ msg = re.escape(msg)
+ with pytest.raises(ValueError, match=msg):
+ merge(df1, df2, on=["A"])
+
+ # Check that error still raised when swapping order of dataframes
+ msg = (
+ f"You are trying to merge on {df2['A'].dtype} and {df1['A'].dtype} "
+ "columns for key 'A'. If you wish to proceed you should use pd.concat"
+ )
+ msg = re.escape(msg)
+ with pytest.raises(ValueError, match=msg):
+ merge(df2, df1, on=["A"])
+
+ # Check that error still raised when merging on multiple columns
+ # The error message should mention the first incompatible column
+ if len(df1_vals) == len(df2_vals):
+ # Column A in df1 and df2 is of compatible (the same) dtype
+ # Columns B and C in df1 and df2 are of incompatible dtypes
+ df3 = DataFrame({"A": df2_vals, "B": df1_vals, "C": df1_vals})
+ df4 = DataFrame({"A": df2_vals, "B": df2_vals, "C": df2_vals})
+
+ # Check that error raised correctly when merging all columns A, B, and C
+ # The error message should mention key 'B'
+ msg = (
+ f"You are trying to merge on {df3['B'].dtype} and {df4['B'].dtype} "
+ "columns for key 'B'. If you wish to proceed you should use pd.concat"
+ )
+ msg = re.escape(msg)
+ with pytest.raises(ValueError, match=msg):
+ merge(df3, df4)
+
+ # Check that error raised correctly when merging columns A and C
+ # The error message should mention key 'C'
+ msg = (
+ f"You are trying to merge on {df3['C'].dtype} and {df4['C'].dtype} "
+ "columns for key 'C'. If you wish to proceed you should use pd.concat"
+ )
+ msg = re.escape(msg)
+ with pytest.raises(ValueError, match=msg):
+ merge(df3, df4, on=["A", "C"])
+
+ @pytest.mark.parametrize(
+ "expected_data, how",
+ [
+ ([1, 2], "outer"),
+ ([], "inner"),
+ ([2], "right"),
+ ([1], "left"),
+ ],
+ )
+ def test_merge_EA_dtype(self, any_numeric_ea_dtype, how, expected_data):
+ # GH#40073
+ d1 = DataFrame([(1,)], columns=["id"], dtype=any_numeric_ea_dtype)
+ d2 = DataFrame([(2,)], columns=["id"], dtype=any_numeric_ea_dtype)
+ result = merge(d1, d2, how=how)
+ exp_index = RangeIndex(len(expected_data))
+ expected = DataFrame(
+ expected_data, index=exp_index, columns=["id"], dtype=any_numeric_ea_dtype
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "expected_data, how",
+ [
+ (["a", "b"], "outer"),
+ ([], "inner"),
+ (["b"], "right"),
+ (["a"], "left"),
+ ],
+ )
+ def test_merge_string_dtype(self, how, expected_data, any_string_dtype):
+ # GH#40073
+ d1 = DataFrame([("a",)], columns=["id"], dtype=any_string_dtype)
+ d2 = DataFrame([("b",)], columns=["id"], dtype=any_string_dtype)
+ result = merge(d1, d2, how=how)
+ exp_idx = RangeIndex(len(expected_data))
+ expected = DataFrame(
+ expected_data, index=exp_idx, columns=["id"], dtype=any_string_dtype
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "how, expected_data",
+ [
+ ("inner", [[True, 1, 4], [False, 5, 3]]),
+ ("outer", [[False, 5, 3], [True, 1, 4]]),
+ ("left", [[True, 1, 4], [False, 5, 3]]),
+ ("right", [[False, 5, 3], [True, 1, 4]]),
+ ],
+ )
+ def test_merge_bool_dtype(self, how, expected_data):
+ # GH#40073
+ df1 = DataFrame({"A": [True, False], "B": [1, 5]})
+ df2 = DataFrame({"A": [False, True], "C": [3, 4]})
+ result = merge(df1, df2, how=how)
+ expected = DataFrame(expected_data, columns=["A", "B", "C"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_ea_with_string(self, join_type, string_dtype):
+ # GH 43734 Avoid the use of `assign` with multi-index
+ df1 = DataFrame(
+ data={
+ ("lvl0", "lvl1-a"): ["1", "2", "3", "4", None],
+ ("lvl0", "lvl1-b"): ["4", "5", "6", "7", "8"],
+ },
+ dtype=pd.StringDtype(),
+ )
+ df1_copy = df1.copy()
+ df2 = DataFrame(
+ data={
+ ("lvl0", "lvl1-a"): ["1", "2", "3", pd.NA, "5"],
+ ("lvl0", "lvl1-c"): ["7", "8", "9", pd.NA, "11"],
+ },
+ dtype=string_dtype,
+ )
+ df2_copy = df2.copy()
+ merged = merge(left=df1, right=df2, on=[("lvl0", "lvl1-a")], how=join_type)
+
+ # No change in df1 and df2
+ tm.assert_frame_equal(df1, df1_copy)
+ tm.assert_frame_equal(df2, df2_copy)
+
+ # Check the expected types for the merged data frame
+ expected = Series(
+ [np.dtype("O"), pd.StringDtype(), np.dtype("O")],
+ index=MultiIndex.from_tuples(
+ [("lvl0", "lvl1-a"), ("lvl0", "lvl1-b"), ("lvl0", "lvl1-c")]
+ ),
+ )
+ tm.assert_series_equal(merged.dtypes, expected)
+
+ @pytest.mark.parametrize(
+ "left_empty, how, exp",
+ [
+ (False, "left", "left"),
+ (False, "right", "empty"),
+ (False, "inner", "empty"),
+ (False, "outer", "left"),
+ (False, "cross", "empty_cross"),
+ (True, "left", "empty"),
+ (True, "right", "right"),
+ (True, "inner", "empty"),
+ (True, "outer", "right"),
+ (True, "cross", "empty_cross"),
+ ],
+ )
+ def test_merge_empty(self, left_empty, how, exp):
+ left = DataFrame({"A": [2, 1], "B": [3, 4]})
+ right = DataFrame({"A": [1], "C": [5]}, dtype="int64")
+
+ if left_empty:
+ left = left.head(0)
+ else:
+ right = right.head(0)
+
+ result = left.merge(right, how=how)
+
+ if exp == "left":
+ expected = DataFrame({"A": [2, 1], "B": [3, 4], "C": [np.nan, np.nan]})
+ elif exp == "right":
+ expected = DataFrame({"A": [1], "B": [np.nan], "C": [5]})
+ elif exp == "empty":
+ expected = DataFrame(columns=["A", "B", "C"], dtype="int64")
+ elif exp == "empty_cross":
+ expected = DataFrame(columns=["A_x", "B", "A_y", "C"], dtype="int64")
+
+ if how == "outer":
+ expected = expected.sort_values("A", ignore_index=True)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_with_uintc_columns(self):
+ df1 = DataFrame({"a": ["foo", "bar"], "b": np.array([1, 2], dtype=np.uintc)})
+ df2 = DataFrame({"a": ["foo", "baz"], "b": np.array([3, 4], dtype=np.uintc)})
+ result = df1.merge(df2, how="outer")
+ expected = DataFrame(
+ {
+ "a": ["bar", "baz", "foo", "foo"],
+ "b": np.array([2, 4, 1, 3], dtype=np.uintc),
+ }
+ )
+ tm.assert_frame_equal(result.reset_index(drop=True), expected)
+
+ def test_merge_with_intc_columns(self):
+ df1 = DataFrame({"a": ["foo", "bar"], "b": np.array([1, 2], dtype=np.intc)})
+ df2 = DataFrame({"a": ["foo", "baz"], "b": np.array([3, 4], dtype=np.intc)})
+ result = df1.merge(df2, how="outer")
+ expected = DataFrame(
+ {
+ "a": ["bar", "baz", "foo", "foo"],
+ "b": np.array([2, 4, 1, 3], dtype=np.intc),
+ }
+ )
+ tm.assert_frame_equal(result.reset_index(drop=True), expected)
+
+ def test_merge_intc_non_monotonic(self):
+ df = DataFrame({"join_key": Series([0, 2, 1], dtype=np.intc)})
+ df_details = DataFrame(
+ {"join_key": Series([0, 1, 2], dtype=np.intc), "value": ["a", "b", "c"]}
+ )
+ merged = df.merge(df_details, on="join_key", how="left")
+ expected = DataFrame(
+ {"join_key": np.array([0, 2, 1], dtype=np.intc), "value": ["a", "c", "b"]}
+ )
+ tm.assert_frame_equal(merged.reset_index(drop=True), expected)
+
+
+@pytest.fixture
+def left():
+ return DataFrame(
+ {
+ "X": Series(
+ np.random.default_rng(2).choice(["foo", "bar"], size=(10,))
+ ).astype(CategoricalDtype(["foo", "bar"])),
+ "Y": np.random.default_rng(2).choice(["one", "two", "three"], size=(10,)),
+ }
+ )
+
+
+@pytest.fixture
+def right():
+ return DataFrame(
+ {
+ "X": Series(["foo", "bar"]).astype(CategoricalDtype(["foo", "bar"])),
+ "Z": [1, 2],
+ }
+ )
+
+
+class TestMergeCategorical:
+ def test_identical(self, left, using_infer_string):
+ # merging on the same, should preserve dtypes
+ merged = merge(left, left, on="X")
+ result = merged.dtypes.sort_index()
+ dtype = np.dtype("O") if not using_infer_string else "str"
+ expected = Series(
+ [CategoricalDtype(categories=["foo", "bar"]), dtype, dtype],
+ index=["X", "Y_x", "Y_y"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_basic(self, left, right, using_infer_string):
+ # we have matching Categorical dtypes in X
+ # so should preserve the merged column
+ merged = merge(left, right, on="X")
+ result = merged.dtypes.sort_index()
+ dtype = np.dtype("O") if not using_infer_string else "str"
+ expected = Series(
+ [
+ CategoricalDtype(categories=["foo", "bar"]),
+ dtype,
+ np.dtype("int64"),
+ ],
+ index=["X", "Y", "Z"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_merge_categorical(self):
+ # GH 9426
+
+ right = DataFrame(
+ {
+ "c": {0: "a", 1: "b", 2: "c", 3: "d", 4: "e"},
+ "d": {0: "null", 1: "null", 2: "null", 3: "null", 4: "null"},
+ }
+ )
+ left = DataFrame(
+ {
+ "a": {0: "f", 1: "f", 2: "f", 3: "f", 4: "f"},
+ "b": {0: "g", 1: "g", 2: "g", 3: "g", 4: "g"},
+ }
+ )
+ df = merge(left, right, how="left", left_on="b", right_on="c")
+
+ # object-object
+ expected = df.copy()
+
+ # object-cat
+ # note that we propagate the category
+ # because we don't have any matching rows
+ cright = right.copy()
+ cright["d"] = cright["d"].astype("category")
+ result = merge(left, cright, how="left", left_on="b", right_on="c")
+ expected["d"] = expected["d"].astype(CategoricalDtype(["null"]))
+ tm.assert_frame_equal(result, expected)
+
+ # cat-object
+ cleft = left.copy()
+ cleft["b"] = cleft["b"].astype("category")
+ result = merge(cleft, cright, how="left", left_on="b", right_on="c")
+ tm.assert_frame_equal(result, expected)
+
+ # cat-cat
+ cright = right.copy()
+ cright["d"] = cright["d"].astype("category")
+ cleft = left.copy()
+ cleft["b"] = cleft["b"].astype("category")
+ result = merge(cleft, cright, how="left", left_on="b", right_on="c")
+ tm.assert_frame_equal(result, expected)
+
+ def tests_merge_categorical_unordered_equal(self):
+ # GH-19551
+ df1 = DataFrame(
+ {
+ "Foo": Categorical(["A", "B", "C"], categories=["A", "B", "C"]),
+ "Left": ["A0", "B0", "C0"],
+ }
+ )
+
+ df2 = DataFrame(
+ {
+ "Foo": Categorical(["C", "B", "A"], categories=["C", "B", "A"]),
+ "Right": ["C1", "B1", "A1"],
+ }
+ )
+ result = merge(df1, df2, on=["Foo"])
+ expected = DataFrame(
+ {
+ "Foo": Categorical(["A", "B", "C"]),
+ "Left": ["A0", "B0", "C0"],
+ "Right": ["A1", "B1", "C1"],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("ordered", [True, False])
+ def test_multiindex_merge_with_unordered_categoricalindex(self, ordered):
+ # GH 36973
+ pcat = CategoricalDtype(categories=["P2", "P1"], ordered=ordered)
+ df1 = DataFrame(
+ {
+ "id": ["C", "C", "D"],
+ "p": Categorical(["P2", "P1", "P2"], dtype=pcat),
+ "a": [0, 1, 2],
+ }
+ ).set_index(["id", "p"])
+ df2 = DataFrame(
+ {
+ "id": ["A", "C", "C"],
+ "p": Categorical(["P2", "P2", "P1"], dtype=pcat),
+ "d1": [10, 11, 12],
+ }
+ ).set_index(["id", "p"])
+ result = merge(df1, df2, how="left", left_index=True, right_index=True)
+ expected = DataFrame(
+ {
+ "id": ["C", "C", "D"],
+ "p": Categorical(["P2", "P1", "P2"], dtype=pcat),
+ "a": [0, 1, 2],
+ "d1": [11.0, 12.0, np.nan],
+ }
+ ).set_index(["id", "p"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_other_columns(self, left, right, using_infer_string):
+ # non-merge columns should preserve if possible
+ right = right.assign(Z=right.Z.astype("category"))
+
+ merged = merge(left, right, on="X")
+ result = merged.dtypes.sort_index()
+ dtype = np.dtype("O") if not using_infer_string else "str"
+ expected = Series(
+ [
+ CategoricalDtype(categories=["foo", "bar"]),
+ dtype,
+ CategoricalDtype(categories=[1, 2]),
+ ],
+ index=["X", "Y", "Z"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ # categories are preserved
+ assert left.X.values._categories_match_up_to_permutation(merged.X.values)
+ assert right.Z.values._categories_match_up_to_permutation(merged.Z.values)
+
+ @pytest.mark.parametrize(
+ "change",
+ [
+ lambda x: x,
+ lambda x: x.astype(CategoricalDtype(["foo", "bar", "bah"])),
+ lambda x: x.astype(CategoricalDtype(ordered=True)),
+ ],
+ )
+ def test_dtype_on_merged_different(
+ self, change, join_type, left, right, using_infer_string
+ ):
+ # our merging columns, X now has 2 different dtypes
+ # so we must be object as a result
+
+ X = change(right.X.astype("object"))
+ right = right.assign(X=X)
+ assert isinstance(left.X.values.dtype, CategoricalDtype)
+ # assert not left.X.values._categories_match_up_to_permutation(right.X.values)
+
+ merged = merge(left, right, on="X", how=join_type)
+
+ result = merged.dtypes.sort_index()
+ dtype = np.dtype("O") if not using_infer_string else "str"
+ expected = Series([dtype, dtype, np.dtype("int64")], index=["X", "Y", "Z"])
+ tm.assert_series_equal(result, expected)
+
+ def test_self_join_multiple_categories(self):
+ # GH 16767
+ # non-duplicates should work with multiple categories
+ m = 5
+ df = DataFrame(
+ {
+ "a": ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] * m,
+ "b": ["t", "w", "x", "y", "z"] * 2 * m,
+ "c": [
+ letter
+ for each in ["m", "n", "u", "p", "o"]
+ for letter in [each] * 2 * m
+ ],
+ "d": [
+ letter
+ for each in [
+ "aa",
+ "bb",
+ "cc",
+ "dd",
+ "ee",
+ "ff",
+ "gg",
+ "hh",
+ "ii",
+ "jj",
+ ]
+ for letter in [each] * m
+ ],
+ }
+ )
+
+ # change them all to categorical variables
+ df = df.apply(lambda x: x.astype("category"))
+
+ # self-join should equal ourselves
+ result = merge(df, df, on=list(df.columns))
+
+ tm.assert_frame_equal(result, df)
+
+ def test_dtype_on_categorical_dates(self):
+ # GH 16900
+ # dates should not be coerced to ints
+
+ df = DataFrame(
+ [[date(2001, 1, 1), 1.1], [date(2001, 1, 2), 1.3]], columns=["date", "num2"]
+ )
+ df["date"] = df["date"].astype("category")
+
+ df2 = DataFrame(
+ [[date(2001, 1, 1), 1.3], [date(2001, 1, 3), 1.4]], columns=["date", "num4"]
+ )
+ df2["date"] = df2["date"].astype("category")
+
+ expected_outer = DataFrame(
+ [
+ [pd.Timestamp("2001-01-01").date(), 1.1, 1.3],
+ [pd.Timestamp("2001-01-02").date(), 1.3, np.nan],
+ [pd.Timestamp("2001-01-03").date(), np.nan, 1.4],
+ ],
+ columns=["date", "num2", "num4"],
+ )
+ result_outer = merge(df, df2, how="outer", on=["date"])
+ tm.assert_frame_equal(result_outer, expected_outer)
+
+ expected_inner = DataFrame(
+ [[pd.Timestamp("2001-01-01").date(), 1.1, 1.3]],
+ columns=["date", "num2", "num4"],
+ )
+ result_inner = merge(df, df2, how="inner", on=["date"])
+ tm.assert_frame_equal(result_inner, expected_inner)
+
+ @pytest.mark.parametrize("ordered", [True, False])
+ @pytest.mark.parametrize(
+ "category_column,categories,expected_categories",
+ [
+ ([False, True, True, False], [True, False], [True, False]),
+ ([2, 1, 1, 2], [1, 2], [1, 2]),
+ (["False", "True", "True", "False"], ["True", "False"], ["True", "False"]),
+ ],
+ )
+ def test_merging_with_bool_or_int_cateorical_column(
+ self, category_column, categories, expected_categories, ordered
+ ):
+ # GH 17187
+ # merging with a boolean/int categorical column
+ df1 = DataFrame({"id": [1, 2, 3, 4], "cat": category_column})
+ df1["cat"] = df1["cat"].astype(CategoricalDtype(categories, ordered=ordered))
+ df2 = DataFrame({"id": [2, 4], "num": [1, 9]})
+ result = df1.merge(df2)
+ expected = DataFrame({"id": [2, 4], "cat": expected_categories, "num": [1, 9]})
+ expected["cat"] = expected["cat"].astype(
+ CategoricalDtype(categories, ordered=ordered)
+ )
+ tm.assert_frame_equal(expected, result)
+
+ def test_merge_on_int_array(self):
+ # GH 23020
+ df = DataFrame({"A": Series([1, 2, pd.NA], dtype="Int64"), "B": 1})
+ result = merge(df, df, on="A")
+ expected = DataFrame(
+ {"A": Series([1, 2, pd.NA], dtype="Int64"), "B_x": 1, "B_y": 1}
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_category_index_levels_stay_category(self):
+ # GH#37480
+ df1 = DataFrame(
+ {
+ "x": list(range(1, 10)),
+ "y": list(range(1, 10)),
+ "z": list(range(1, 10)),
+ "d": list(range(1, 10)),
+ }
+ )
+
+ df2 = df1.astype({"x": "category", "y": "category", "z": "category"})
+
+ df3 = df2.iloc[:4, :].groupby(["z", "x"], observed=True).agg({"d": "sum"})
+ df4 = df2.iloc[7:, :].groupby(["z", "x", "y"], observed=True).agg({"d": "sum"})
+ result = merge(df3, df4, left_index=True, right_index=True, how="outer")
+
+ cats = list(range(1, 10))
+ z = Categorical(list(range(1, 5)) + list(range(8, 10)), categories=cats)
+ x = Categorical(list(range(1, 5)) + list(range(8, 10)), categories=cats)
+ y = Categorical([np.nan] * 4 + list(range(8, 10)), categories=cats)
+ idx = MultiIndex.from_arrays([z, x, y], names=["z", "x", "y"])
+ expected = DataFrame(
+ {
+ "d_x": list(map(float, range(1, 5))) + [np.nan] * 2,
+ "d_y": [np.nan] * 4 + list(map(float, range(8, 10))),
+ },
+ index=idx,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+class TestMergeOnIndexes:
+ @pytest.mark.parametrize(
+ "how, sort, expected",
+ [
+ ("inner", False, DataFrame({"a": [20, 10], "b": [200, 100]}, index=[2, 1])),
+ ("inner", True, DataFrame({"a": [10, 20], "b": [100, 200]}, index=[1, 2])),
+ (
+ "left",
+ False,
+ DataFrame({"a": [20, 10, 0], "b": [200, 100, np.nan]}, index=[2, 1, 0]),
+ ),
+ (
+ "left",
+ True,
+ DataFrame({"a": [0, 10, 20], "b": [np.nan, 100, 200]}, index=[0, 1, 2]),
+ ),
+ (
+ "right",
+ False,
+ DataFrame(
+ {"a": [np.nan, 10, 20], "b": [300, 100, 200]}, index=[3, 1, 2]
+ ),
+ ),
+ (
+ "right",
+ True,
+ DataFrame(
+ {"a": [10, 20, np.nan], "b": [100, 200, 300]}, index=[1, 2, 3]
+ ),
+ ),
+ (
+ "outer",
+ False,
+ DataFrame(
+ {"a": [0, 10, 20, np.nan], "b": [np.nan, 100, 200, 300]},
+ index=[0, 1, 2, 3],
+ ),
+ ),
+ (
+ "outer",
+ True,
+ DataFrame(
+ {"a": [0, 10, 20, np.nan], "b": [np.nan, 100, 200, 300]},
+ index=[0, 1, 2, 3],
+ ),
+ ),
+ ],
+ )
+ def test_merge_on_indexes(self, how, sort, expected):
+ left_df = DataFrame({"a": [20, 10, 0]}, index=[2, 1, 0])
+ right_df = DataFrame({"b": [300, 100, 200]}, index=[3, 1, 2])
+ result = merge(
+ left_df, right_df, left_index=True, right_index=True, how=how, sort=sort
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ Index([1, 2, 4], dtype=dtyp, name="index_col")
+ for dtyp in tm.ALL_REAL_NUMPY_DTYPES
+ ]
+ + [
+ CategoricalIndex(["A", "B", "C"], categories=["A", "B", "C"], name="index_col"),
+ RangeIndex(start=0, stop=3, name="index_col"),
+ DatetimeIndex(["2018-01-01", "2018-01-02", "2018-01-03"], name="index_col"),
+ ],
+ ids=lambda x: f"{type(x).__name__}[{x.dtype}]",
+)
+def test_merge_index_types(index):
+ # gh-20777
+ # assert key access is consistent across index types
+ left = DataFrame({"left_data": [1, 2, 3]}, index=index)
+ right = DataFrame({"right_data": [1.0, 2.0, 3.0]}, index=index)
+
+ result = left.merge(right, on=["index_col"])
+
+ expected = DataFrame(
+ {"left_data": [1, 2, 3], "right_data": [1.0, 2.0, 3.0]}, index=index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "on,left_on,right_on,left_index,right_index,nm",
+ [
+ (["outer", "inner"], None, None, False, False, "B"),
+ (None, None, None, True, True, "B"),
+ (None, ["outer", "inner"], None, False, True, "B"),
+ (None, None, ["outer", "inner"], True, False, "B"),
+ (["outer", "inner"], None, None, False, False, None),
+ (None, None, None, True, True, None),
+ (None, ["outer", "inner"], None, False, True, None),
+ (None, None, ["outer", "inner"], True, False, None),
+ ],
+)
+def test_merge_series(on, left_on, right_on, left_index, right_index, nm):
+ # GH 21220
+ a = DataFrame(
+ {"A": [1, 2, 3, 4]},
+ index=MultiIndex.from_product([["a", "b"], [0, 1]], names=["outer", "inner"]),
+ )
+ b = Series(
+ [1, 2, 3, 4],
+ index=MultiIndex.from_product([["a", "b"], [1, 2]], names=["outer", "inner"]),
+ name=nm,
+ )
+ expected = DataFrame(
+ {"A": [2, 4], "B": [1, 3]},
+ index=MultiIndex.from_product([["a", "b"], [1]], names=["outer", "inner"]),
+ )
+ if nm is not None:
+ result = merge(
+ a,
+ b,
+ on=on,
+ left_on=left_on,
+ right_on=right_on,
+ left_index=left_index,
+ right_index=right_index,
+ )
+ tm.assert_frame_equal(result, expected)
+ else:
+ msg = "Cannot merge a Series without a name"
+ with pytest.raises(ValueError, match=msg):
+ result = merge(
+ a,
+ b,
+ on=on,
+ left_on=left_on,
+ right_on=right_on,
+ left_index=left_index,
+ right_index=right_index,
+ )
+
+
+def test_merge_series_multilevel():
+ # GH#47946
+ # GH 40993: For raising, enforced in 2.0
+ a = DataFrame(
+ {"A": [1, 2, 3, 4]},
+ index=MultiIndex.from_product([["a", "b"], [0, 1]], names=["outer", "inner"]),
+ )
+ b = Series(
+ [1, 2, 3, 4],
+ index=MultiIndex.from_product([["a", "b"], [1, 2]], names=["outer", "inner"]),
+ name=("B", "C"),
+ )
+ with pytest.raises(
+ MergeError, match="Not allowed to merge between different levels"
+ ):
+ merge(a, b, on=["outer", "inner"])
+
+
+@pytest.mark.parametrize(
+ "col1, col2, kwargs, expected_cols",
+ [
+ (0, 0, {"suffixes": ("", "_dup")}, ["0", "0_dup"]),
+ (0, 0, {"suffixes": (None, "_dup")}, [0, "0_dup"]),
+ (0, 0, {"suffixes": ("_x", "_y")}, ["0_x", "0_y"]),
+ (0, 0, {"suffixes": ["_x", "_y"]}, ["0_x", "0_y"]),
+ ("a", 0, {"suffixes": (None, "_y")}, ["a", 0]),
+ (0.0, 0.0, {"suffixes": ("_x", None)}, ["0.0_x", 0.0]),
+ ("b", "b", {"suffixes": (None, "_y")}, ["b", "b_y"]),
+ ("a", "a", {"suffixes": ("_x", None)}, ["a_x", "a"]),
+ ("a", "b", {"suffixes": ("_x", None)}, ["a", "b"]),
+ ("a", "a", {"suffixes": (None, "_x")}, ["a", "a_x"]),
+ (0, 0, {"suffixes": ("_a", None)}, ["0_a", 0]),
+ ("a", "a", {}, ["a_x", "a_y"]),
+ (0, 0, {}, ["0_x", "0_y"]),
+ ],
+)
+def test_merge_suffix(col1, col2, kwargs, expected_cols):
+ # issue: 24782
+ a = DataFrame({col1: [1, 2, 3]})
+ b = DataFrame({col2: [4, 5, 6]})
+
+ expected = DataFrame([[1, 4], [2, 5], [3, 6]], columns=expected_cols)
+
+ result = a.merge(b, left_index=True, right_index=True, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(a, b, left_index=True, right_index=True, **kwargs)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "how,expected",
+ [
+ (
+ "right",
+ {"A": [100, 200, 300], "B1": [60, 70, np.nan], "B2": [600, 700, 800]},
+ ),
+ (
+ "outer",
+ {
+ "A": [1, 100, 200, 300],
+ "B1": [80, 60, 70, np.nan],
+ "B2": [np.nan, 600, 700, 800],
+ },
+ ),
+ ],
+)
+def test_merge_duplicate_suffix(how, expected):
+ left_df = DataFrame({"A": [100, 200, 1], "B": [60, 70, 80]})
+ right_df = DataFrame({"A": [100, 200, 300], "B": [600, 700, 800]})
+ result = merge(left_df, right_df, on="A", how=how, suffixes=("_x", "_x"))
+ expected = DataFrame(expected)
+ expected.columns = ["A", "B_x", "B_x"]
+
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "col1, col2, suffixes",
+ [("a", "a", (None, None)), ("a", "a", ("", None)), (0, 0, (None, ""))],
+)
+def test_merge_suffix_error(col1, col2, suffixes):
+ # issue: 24782
+ a = DataFrame({col1: [1, 2, 3]})
+ b = DataFrame({col2: [3, 4, 5]})
+
+ # TODO: might reconsider current raise behaviour, see issue 24782
+ msg = "columns overlap but no suffix specified"
+ with pytest.raises(ValueError, match=msg):
+ merge(a, b, left_index=True, right_index=True, suffixes=suffixes)
+
+
+@pytest.mark.parametrize("suffixes", [{"left", "right"}, {"left": 0, "right": 0}])
+def test_merge_suffix_raises(suffixes):
+ a = DataFrame({"a": [1, 2, 3]})
+ b = DataFrame({"b": [3, 4, 5]})
+
+ with pytest.raises(TypeError, match="Passing 'suffixes' as a"):
+ merge(a, b, left_index=True, right_index=True, suffixes=suffixes)
+
+
+TWO_GOT_THREE = "2, got 3" if PY314 else "2"
+
+
+@pytest.mark.parametrize(
+ "col1, col2, suffixes, msg",
+ [
+ (
+ "a",
+ "a",
+ ("a", "b", "c"),
+ (rf"too many values to unpack \(expected {TWO_GOT_THREE}\)"),
+ ),
+ ("a", "a", tuple("a"), r"not enough values to unpack \(expected 2, got 1\)"),
+ ],
+)
+def test_merge_suffix_length_error(col1, col2, suffixes, msg):
+ a = DataFrame({col1: [1, 2, 3]})
+ b = DataFrame({col2: [3, 4, 5]})
+
+ with pytest.raises(ValueError, match=msg):
+ merge(a, b, left_index=True, right_index=True, suffixes=suffixes)
+
+
+@pytest.mark.parametrize("cat_dtype", ["one", "two"])
+@pytest.mark.parametrize("reverse", [True, False])
+def test_merge_equal_cat_dtypes(cat_dtype, reverse):
+ # see gh-22501
+ cat_dtypes = {
+ "one": CategoricalDtype(categories=["a", "b", "c"], ordered=False),
+ "two": CategoricalDtype(categories=["a", "b", "c"], ordered=False),
+ }
+
+ df1 = DataFrame(
+ {"foo": Series(["a", "b", "c"]).astype(cat_dtypes["one"]), "left": [1, 2, 3]}
+ ).set_index("foo")
+
+ data_foo = ["a", "b", "c"]
+ data_right = [1, 2, 3]
+
+ if reverse:
+ data_foo.reverse()
+ data_right.reverse()
+
+ df2 = DataFrame(
+ {"foo": Series(data_foo).astype(cat_dtypes[cat_dtype]), "right": data_right}
+ ).set_index("foo")
+
+ result = df1.merge(df2, left_index=True, right_index=True)
+
+ expected = DataFrame(
+ {
+ "left": [1, 2, 3],
+ "right": [1, 2, 3],
+ "foo": Series(["a", "b", "c"]).astype(cat_dtypes["one"]),
+ }
+ ).set_index("foo")
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_equal_cat_dtypes2():
+ # see gh-22501
+ cat_dtype = CategoricalDtype(categories=["a", "b", "c"], ordered=False)
+
+ # Test Data
+ df1 = DataFrame(
+ {"foo": Series(["a", "b"]).astype(cat_dtype), "left": [1, 2]}
+ ).set_index("foo")
+
+ df2 = DataFrame(
+ {"foo": Series(["a", "b", "c"]).astype(cat_dtype), "right": [3, 2, 1]}
+ ).set_index("foo")
+
+ result = df1.merge(df2, left_index=True, right_index=True)
+
+ expected = DataFrame(
+ {"left": [1, 2], "right": [3, 2], "foo": Series(["a", "b"]).astype(cat_dtype)}
+ ).set_index("foo")
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_on_cat_and_ext_array():
+ # GH 28668
+ right = DataFrame(
+ {"a": Series([pd.Interval(0, 1), pd.Interval(1, 2)], dtype="interval")}
+ )
+ left = right.copy()
+ left["a"] = left["a"].astype("category")
+
+ result = merge(left, right, how="inner", on="a")
+ expected = right.copy()
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_multiindex_columns():
+ # Issue #28518
+ # Verify that merging two dataframes give the expected labels
+ # The original cause of this issue come from a bug lexsort_depth and is tested in
+ # test_lexsort_depth
+
+ letters = ["a", "b", "c", "d"]
+ numbers = ["1", "2", "3"]
+ index = MultiIndex.from_product((letters, numbers), names=["outer", "inner"])
+
+ frame_x = DataFrame(columns=index)
+ frame_x["id"] = ""
+ frame_y = DataFrame(columns=index)
+ frame_y["id"] = ""
+
+ l_suf = "_x"
+ r_suf = "_y"
+ result = frame_x.merge(frame_y, on="id", suffixes=((l_suf, r_suf)))
+
+ # Constructing the expected results
+ tuples = [(letter + l_suf, num) for letter in letters for num in numbers]
+ tuples += [("id", "")]
+ tuples += [(letter + r_suf, num) for letter in letters for num in numbers]
+
+ expected_index = MultiIndex.from_tuples(tuples, names=["outer", "inner"])
+ expected = DataFrame(columns=expected_index)
+
+ tm.assert_frame_equal(result, expected, check_dtype=False)
+
+
+def test_merge_datetime_upcast_dtype():
+ # https://github.com/pandas-dev/pandas/issues/31208
+ df1 = DataFrame({"x": ["a", "b", "c"], "y": ["1", "2", "4"]})
+ df2 = DataFrame(
+ {"y": ["1", "2", "3"], "z": pd.to_datetime(["2000", "2001", "2002"])}
+ )
+ result = merge(df1, df2, how="left", on="y")
+ expected = DataFrame(
+ {
+ "x": ["a", "b", "c"],
+ "y": ["1", "2", "4"],
+ "z": pd.to_datetime(["2000", "2001", "NaT"]),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("n_categories", [5, 128])
+def test_categorical_non_unique_monotonic(n_categories):
+ # GH 28189
+ # With n_categories as 5, we test the int8 case is hit in libjoin,
+ # with n_categories as 128 we test the int16 case.
+ left_index = CategoricalIndex([0, *list(range(n_categories))])
+ df1 = DataFrame(range(n_categories + 1), columns=["value"], index=left_index)
+ df2 = DataFrame(
+ [[6]],
+ columns=["value"],
+ index=CategoricalIndex([0], categories=list(range(n_categories))),
+ )
+
+ result = merge(df1, df2, how="left", left_index=True, right_index=True)
+ expected = DataFrame(
+ [[i, 6.0] if i < 2 else [i, np.nan] for i in range(n_categories + 1)],
+ columns=["value_x", "value_y"],
+ index=left_index,
+ )
+ tm.assert_frame_equal(expected, result)
+
+
+def test_merge_join_categorical_multiindex():
+ # From issue 16627
+ a = {
+ "Cat1": Categorical(["a", "b", "a", "c", "a", "b"], ["a", "b", "c"]),
+ "Int1": [0, 1, 0, 1, 0, 0],
+ }
+ a = DataFrame(a)
+
+ b = {
+ "Cat": Categorical(["a", "b", "c", "a", "b", "c"], ["a", "b", "c"]),
+ "Int": [0, 0, 0, 1, 1, 1],
+ "Factor": [1.1, 1.2, 1.3, 1.4, 1.5, 1.6],
+ }
+ b = DataFrame(b).set_index(["Cat", "Int"])["Factor"]
+
+ expected = merge(
+ a,
+ b.reset_index(),
+ left_on=["Cat1", "Int1"],
+ right_on=["Cat", "Int"],
+ how="left",
+ )
+ expected = expected.drop(["Cat", "Int"], axis=1)
+ result = a.join(b, on=["Cat1", "Int1"])
+ tm.assert_frame_equal(expected, result)
+
+ # Same test, but with ordered categorical
+ a = {
+ "Cat1": Categorical(
+ ["a", "b", "a", "c", "a", "b"], ["b", "a", "c"], ordered=True
+ ),
+ "Int1": [0, 1, 0, 1, 0, 0],
+ }
+ a = DataFrame(a)
+
+ b = {
+ "Cat": Categorical(
+ ["a", "b", "c", "a", "b", "c"], ["b", "a", "c"], ordered=True
+ ),
+ "Int": [0, 0, 0, 1, 1, 1],
+ "Factor": [1.1, 1.2, 1.3, 1.4, 1.5, 1.6],
+ }
+ b = DataFrame(b).set_index(["Cat", "Int"])["Factor"]
+
+ expected = merge(
+ a,
+ b.reset_index(),
+ left_on=["Cat1", "Int1"],
+ right_on=["Cat", "Int"],
+ how="left",
+ )
+ expected = expected.drop(["Cat", "Int"], axis=1)
+ result = a.join(b, on=["Cat1", "Int1"])
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize("func", ["merge", "merge_asof"])
+@pytest.mark.parametrize(
+ ("kwargs", "err_msg"),
+ [
+ ({"left_on": "a", "left_index": True}, ["left_on", "left_index"]),
+ ({"right_on": "a", "right_index": True}, ["right_on", "right_index"]),
+ ],
+)
+def test_merge_join_cols_error_reporting_duplicates(func, kwargs, err_msg):
+ # GH: 16228
+ left = DataFrame({"a": [1, 2], "b": [3, 4]})
+ right = DataFrame({"a": [1, 1], "c": [5, 6]})
+ msg = rf'Can only pass argument "{err_msg[0]}" OR "{err_msg[1]}" not both\.'
+ with pytest.raises(MergeError, match=msg):
+ getattr(pd, func)(left, right, **kwargs)
+
+
+@pytest.mark.parametrize("func", ["merge", "merge_asof"])
+@pytest.mark.parametrize(
+ ("kwargs", "err_msg"),
+ [
+ ({"left_on": "a"}, ["right_on", "right_index"]),
+ ({"right_on": "a"}, ["left_on", "left_index"]),
+ ],
+)
+def test_merge_join_cols_error_reporting_missing(func, kwargs, err_msg):
+ # GH: 16228
+ left = DataFrame({"a": [1, 2], "b": [3, 4]})
+ right = DataFrame({"a": [1, 1], "c": [5, 6]})
+ msg = rf'Must pass "{err_msg[0]}" OR "{err_msg[1]}"\.'
+ with pytest.raises(MergeError, match=msg):
+ getattr(pd, func)(left, right, **kwargs)
+
+
+@pytest.mark.parametrize("func", ["merge", "merge_asof"])
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"right_index": True},
+ {"left_index": True},
+ ],
+)
+def test_merge_join_cols_error_reporting_on_and_index(func, kwargs):
+ # GH: 16228
+ left = DataFrame({"a": [1, 2], "b": [3, 4]})
+ right = DataFrame({"a": [1, 1], "c": [5, 6]})
+ msg = (
+ r'Can only pass argument "on" OR "left_index" '
+ r'and "right_index", not a combination of both\.'
+ )
+ with pytest.raises(MergeError, match=msg):
+ getattr(pd, func)(left, right, on="a", **kwargs)
+
+
+def test_merge_right_left_index():
+ # GH#38616
+ left = DataFrame({"x": [1, 1], "z": ["foo", "foo"]})
+ right = DataFrame({"x": [1, 1], "z": ["foo", "foo"]})
+ result = merge(left, right, how="right", left_index=True, right_on="x")
+ expected = DataFrame(
+ {
+ "x": [1, 1],
+ "x_x": [1, 1],
+ "z_x": ["foo", "foo"],
+ "x_y": [1, 1],
+ "z_y": ["foo", "foo"],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_result_empty_index_and_on():
+ # GH#33814
+ df1 = DataFrame({"a": [1], "b": [2]}).set_index(["a", "b"])
+ df2 = DataFrame({"b": [1]}).set_index(["b"])
+ expected = DataFrame({"a": [], "b": []}, dtype=np.int64).set_index(["a", "b"])
+ result = merge(df1, df2, left_on=["b"], right_index=True)
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(df2, df1, left_index=True, right_on=["b"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_suffixes_produce_dup_columns_raises():
+ # GH#22818; Enforced in 2.0
+ left = DataFrame({"a": [1, 2, 3], "b": 1, "b_x": 2})
+ right = DataFrame({"a": [1, 2, 3], "b": 2})
+
+ with pytest.raises(MergeError, match="Passing 'suffixes' which cause duplicate"):
+ merge(left, right, on="a")
+
+ with pytest.raises(MergeError, match="Passing 'suffixes' which cause duplicate"):
+ merge(right, left, on="a", suffixes=("_y", "_x"))
+
+
+def test_merge_duplicate_columns_with_suffix_no_warning():
+ # GH#22818
+ # Do not raise warning when duplicates are caused by duplicates in origin
+ left = DataFrame([[1, 1, 1], [2, 2, 2]], columns=["a", "b", "b"])
+ right = DataFrame({"a": [1, 3], "b": 2})
+ result = merge(left, right, on="a")
+ expected = DataFrame([[1, 1, 1, 2]], columns=["a", "b_x", "b_x", "b_y"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_duplicate_columns_with_suffix_causing_another_duplicate_raises():
+ # GH#22818, Enforced in 2.0
+ # This should raise warning because suffixes cause another collision
+ left = DataFrame([[1, 1, 1, 1], [2, 2, 2, 2]], columns=["a", "b", "b", "b_x"])
+ right = DataFrame({"a": [1, 3], "b": 2})
+ with pytest.raises(MergeError, match="Passing 'suffixes' which cause duplicate"):
+ merge(left, right, on="a")
+
+
+def test_merge_string_float_column_result():
+ # GH 13353
+ df1 = DataFrame([[1, 2], [3, 4]], columns=Index(["a", 114.0]))
+ df2 = DataFrame([[9, 10], [11, 12]], columns=["x", "y"])
+ result = merge(df2, df1, how="inner", left_index=True, right_index=True)
+ expected = DataFrame(
+ [[9, 10, 1, 2], [11, 12, 3, 4]], columns=Index(["x", "y", "a", 114.0])
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_mergeerror_on_left_index_mismatched_dtypes():
+ # GH 22449
+ df_1 = DataFrame(data=["X"], columns=["C"], index=[22])
+ df_2 = DataFrame(data=["X"], columns=["C"], index=[999])
+ with pytest.raises(MergeError, match="Can only pass argument"):
+ merge(df_1, df_2, on=["C"], left_index=True)
+
+
+def test_merge_on_left_categoricalindex():
+ # GH#48464 don't raise when left_on is a CategoricalIndex
+ ci = CategoricalIndex(range(3))
+
+ right = DataFrame({"A": ci, "B": range(3)})
+ left = DataFrame({"C": range(3, 6)})
+
+ res = merge(left, right, left_on=ci, right_on="A")
+ expected = merge(left, right, left_on=ci._data, right_on="A")
+ tm.assert_frame_equal(res, expected)
+
+
+@pytest.mark.parametrize("dtype", [None, "Int64"])
+def test_merge_outer_with_NaN(dtype):
+ # GH#43550
+ item = np.nan if dtype is None else pd.NA
+ left = DataFrame({"key": [1, 2], "col1": [1, 2]}, dtype=dtype)
+ right = DataFrame({"key": [item, item], "col2": [3, 4]}, dtype=dtype)
+ result = merge(left, right, on="key", how="outer")
+ expected = DataFrame(
+ {
+ "key": [1, 2, item, item],
+ "col1": [1, 2, item, item],
+ "col2": [item, item, 3, 4],
+ },
+ dtype=dtype,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # switch left and right
+ result = merge(right, left, on="key", how="outer")
+ expected = DataFrame(
+ {
+ "key": [1, 2, item, item],
+ "col2": [item, item, 3, 4],
+ "col1": [1, 2, item, item],
+ },
+ dtype=dtype,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_different_index_names():
+ # GH#45094
+ left = DataFrame({"a": [1]}, index=Index([1], name="c"))
+ right = DataFrame({"a": [1]}, index=Index([1], name="d"))
+ result = merge(left, right, left_on="c", right_on="d")
+ expected = DataFrame({"a_x": [1], "a_y": 1})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_ea(any_numeric_ea_dtype, join_type):
+ # GH#44240
+ left = DataFrame({"a": [1, 2, 3], "b": 1}, dtype=any_numeric_ea_dtype)
+ right = DataFrame({"a": [1, 2, 3], "c": 2}, dtype=any_numeric_ea_dtype)
+ result = left.merge(right, how=join_type)
+ expected = DataFrame({"a": [1, 2, 3], "b": 1, "c": 2}, dtype=any_numeric_ea_dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_ea_and_non_ea(any_numeric_ea_dtype, join_type):
+ # GH#44240
+ left = DataFrame({"a": [1, 2, 3], "b": 1}, dtype=any_numeric_ea_dtype)
+ right = DataFrame({"a": [1, 2, 3], "c": 2}, dtype=any_numeric_ea_dtype.lower())
+ result = left.merge(right, how=join_type)
+ expected = DataFrame(
+ {
+ "a": Series([1, 2, 3], dtype=any_numeric_ea_dtype),
+ "b": Series([1, 1, 1], dtype=any_numeric_ea_dtype),
+ "c": Series([2, 2, 2], dtype=any_numeric_ea_dtype.lower()),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["int64", "int64[pyarrow]"])
+def test_merge_arrow_and_numpy_dtypes(dtype):
+ # GH#52406
+ pytest.importorskip("pyarrow")
+ df = DataFrame({"a": [1, 2]}, dtype=dtype)
+ df2 = DataFrame({"a": [1, 2]}, dtype="int64[pyarrow]")
+ result = df.merge(df2)
+ expected = df.copy()
+ tm.assert_frame_equal(result, expected)
+
+ result = df2.merge(df)
+ expected = df2.copy()
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("tz", [None, "America/Chicago"])
+def test_merge_datetime_different_resolution(tz, join_type):
+ # https://github.com/pandas-dev/pandas/issues/53200
+ vals = [
+ pd.Timestamp(2023, 5, 12, tz=tz),
+ pd.Timestamp(2023, 5, 13, tz=tz),
+ pd.Timestamp(2023, 5, 14, tz=tz),
+ ]
+ df1 = DataFrame({"t": vals[:2], "a": [1.0, 2.0]})
+ df1["t"] = df1["t"].dt.as_unit("ns")
+ df2 = DataFrame({"t": vals[1:], "b": [1.0, 2.0]})
+ df2["t"] = df2["t"].dt.as_unit("s")
+
+ expected = DataFrame({"t": vals, "a": [1.0, 2.0, np.nan], "b": [np.nan, 1.0, 2.0]})
+ expected["t"] = expected["t"].dt.as_unit("ns")
+ if join_type == "inner":
+ expected = expected.iloc[[1]].reset_index(drop=True)
+ elif join_type == "left":
+ expected = expected.iloc[[0, 1]]
+ elif join_type == "right":
+ expected = expected.iloc[[1, 2]].reset_index(drop=True)
+
+ result = df1.merge(df2, on="t", how=join_type)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_multiindex_single_level():
+ # GH52331
+ df = DataFrame({"col": ["A", "B"]})
+ df2 = DataFrame(
+ data={"b": [100]},
+ index=MultiIndex.from_tuples([("A",), ("C",)], names=["col"]),
+ )
+ expected = DataFrame({"col": ["A", "B"], "b": [100, np.nan]})
+
+ result = df.merge(df2, left_on=["col"], right_index=True, how="left")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_multiindex_reset_index_mixed():
+ # GH#62150
+ df = DataFrame(
+ {("column_1", ""): [1, 1], ("column_2", ""): [2, 2]},
+ index=MultiIndex.from_arrays(
+ [[1, 1], ["metadata_1", "metadata_2"]], names=["index", "metadata"]
+ ),
+ )
+
+ df2 = DataFrame(
+ data=[1, 1],
+ index=Index([1, 1], name="index"),
+ columns=MultiIndex.from_product([["new_data"], [""]]),
+ )
+
+ with tm.assert_produces_warning(pd.errors.PerformanceWarning):
+ result = df.reset_index().merge(df2.reset_index(), on="index")
+
+ expected = DataFrame(
+ {
+ ("index", ""): [1, 1, 1, 1],
+ ("metadata", ""): ["metadata_1", "metadata_1", "metadata_2", "metadata_2"],
+ ("column_1", ""): [1, 1, 1, 1],
+ ("column_2", ""): [2, 2, 2, 2],
+ ("new_data", ""): [1, 1, 1, 1],
+ }
+ )
+ expected.columns = MultiIndex.from_tuples(expected.columns)
+
+ tm.assert_frame_equal(result, expected)
+
+ result2 = df.reset_index().merge(df2.reset_index(), on=[("index", "")])
+ tm.assert_frame_equal(result2, expected)
+
+
+@pytest.mark.parametrize("on_index", [True, False])
+@pytest.mark.parametrize("left_unique", [True, False])
+@pytest.mark.parametrize("left_monotonic", [True, False])
+@pytest.mark.parametrize("right_unique", [True, False])
+@pytest.mark.parametrize("right_monotonic", [True, False])
+def test_merge_combinations(
+ join_type,
+ sort,
+ on_index,
+ left_unique,
+ left_monotonic,
+ right_unique,
+ right_monotonic,
+):
+ how = join_type
+ # GH 54611
+ left = [2, 3]
+ if left_unique:
+ left.append(4 if left_monotonic else 1)
+ else:
+ left.append(3 if left_monotonic else 2)
+
+ right = [2, 3]
+ if right_unique:
+ right.append(4 if right_monotonic else 1)
+ else:
+ right.append(3 if right_monotonic else 2)
+
+ left = DataFrame({"key": left})
+ right = DataFrame({"key": right})
+
+ if on_index:
+ left = left.set_index("key")
+ right = right.set_index("key")
+ on_kwargs = {"left_index": True, "right_index": True}
+ else:
+ on_kwargs = {"on": "key"}
+
+ result = merge(left, right, how=how, sort=sort, **on_kwargs)
+
+ if on_index:
+ left = left.reset_index()
+ right = right.reset_index()
+
+ if how in ["left", "right", "inner"]:
+ if how in ["left", "inner"]:
+ expected, other, other_unique = left, right, right_unique
+ else:
+ expected, other, other_unique = right, left, left_unique
+ if how == "inner":
+ keep_values = set(left["key"].values).intersection(right["key"].values)
+ keep_mask = expected["key"].isin(keep_values)
+ expected = expected[keep_mask]
+ if sort:
+ expected = expected.sort_values("key")
+ if not other_unique:
+ other_value_counts = other["key"].value_counts()
+ repeats = other_value_counts.reindex(expected["key"].values, fill_value=1)
+ repeats = repeats.astype(np.intp)
+ expected = expected["key"].repeat(repeats.values)
+ expected = expected.to_frame()
+ elif how == "outer":
+ left_counts = left["key"].value_counts()
+ right_counts = right["key"].value_counts()
+ expected_counts = left_counts.mul(right_counts, fill_value=1)
+ expected_counts = expected_counts.astype(np.intp)
+ expected = expected_counts.index.values.repeat(expected_counts.values)
+ expected = DataFrame({"key": expected})
+ expected = expected.sort_values("key")
+
+ if on_index:
+ expected = expected.set_index("key")
+ else:
+ expected = expected.reset_index(drop=True)
+
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_ea_int_and_float_numpy():
+ # GH#46178
+ df1 = DataFrame([1.0, pd.NA], dtype=pd.Int64Dtype())
+ df2 = DataFrame([1.5])
+ expected = DataFrame(columns=[0], dtype="Int64")
+
+ with tm.assert_produces_warning(UserWarning, match="You are merging"):
+ result = df1.merge(df2)
+ tm.assert_frame_equal(result, expected)
+
+ with tm.assert_produces_warning(UserWarning, match="You are merging"):
+ result = df2.merge(df1)
+ tm.assert_frame_equal(result, expected.astype("float64"))
+
+ df2 = DataFrame([1.0])
+ expected = DataFrame([1], columns=[0], dtype="Int64")
+ result = df1.merge(df2)
+ tm.assert_frame_equal(result, expected)
+
+ result = df2.merge(df1)
+ tm.assert_frame_equal(result, expected.astype("float64"))
+
+
+def test_merge_arrow_string_index(any_string_dtype):
+ # GH#54894
+ pytest.importorskip("pyarrow")
+ left = DataFrame({"a": ["a", "b"]}, dtype=any_string_dtype)
+ right = DataFrame({"b": 1}, index=Index(["a", "c"], dtype=any_string_dtype))
+ result = left.merge(right, left_on="a", right_index=True, how="left")
+ expected = DataFrame(
+ {"a": Series(["a", "b"], dtype=any_string_dtype), "b": [1, np.nan]}
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("left_empty", [True, False])
+@pytest.mark.parametrize("right_empty", [True, False])
+def test_merge_empty_frames_column_order(left_empty, right_empty):
+ # GH 51929
+ df1 = DataFrame(1, index=[0], columns=["A", "B"])
+ df2 = DataFrame(1, index=[0], columns=["A", "C", "D"])
+
+ if left_empty:
+ df1 = df1.iloc[:0]
+ if right_empty:
+ df2 = df2.iloc[:0]
+
+ result = merge(df1, df2, on=["A"], how="outer")
+ expected = DataFrame(1, index=range(1), columns=["A", "B", "C", "D"])
+ if left_empty and right_empty:
+ expected = expected.iloc[:0]
+ elif left_empty:
+ expected["B"] = np.nan
+ elif right_empty:
+ expected[["C", "D"]] = np.nan
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("how", ["left", "right", "inner", "outer"])
+def test_merge_datetime_and_timedelta(how):
+ left = DataFrame({"key": Series([1, None], dtype="datetime64[ns]")})
+ right = DataFrame({"key": Series([1], dtype="timedelta64[ns]")})
+
+ msg = (
+ f"You are trying to merge on {left['key'].dtype} and {right['key'].dtype} "
+ "columns for key 'key'. If you wish to proceed you should use pd.concat"
+ )
+ with pytest.raises(ValueError, match=re.escape(msg)):
+ left.merge(right, on="key", how=how)
+
+ msg = (
+ f"You are trying to merge on {right['key'].dtype} and {left['key'].dtype} "
+ "columns for key 'key'. If you wish to proceed you should use pd.concat"
+ )
+ with pytest.raises(ValueError, match=re.escape(msg)):
+ right.merge(left, on="key", how=how)
+
+
+def test_merge_on_all_nan_column():
+ # GH#59421
+ left = DataFrame({"x": [1, 2, 3], "y": [np.nan, np.nan, np.nan], "z": [4, 5, 6]})
+ right = DataFrame({"x": [1, 2, 3], "y": [np.nan, np.nan, np.nan], "zz": [4, 5, 6]})
+ result = left.merge(right, on=["x", "y"], how="outer")
+ # Should not trigger array bounds eerror with bounds checking or asan enabled.
+ expected = DataFrame(
+ {"x": [1, 2, 3], "y": [np.nan, np.nan, np.nan], "z": [4, 5, 6], "zz": [4, 5, 6]}
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("suffixes", [("_dup", ""), ("", "_dup")])
+def test_merge_for_suffix_collisions(suffixes):
+ # GH#61402
+ df1 = DataFrame({"col1": [1], "col2": [2]})
+ df2 = DataFrame({"col1": [1], "col2": [2], "col2_dup": [3]})
+ with pytest.raises(MergeError, match="duplicate columns"):
+ merge(df1, df2, on="col1", suffixes=suffixes)
+
+
+def test_merge_categorical_key_recursion():
+ # GH#56376
+ lt = CategoricalDtype(categories=np.asarray([1, 2, 3], dtype="int64"))
+ rt = CategoricalDtype(categories=np.asarray([1, 2, 3], dtype="float64"))
+ left = DataFrame({"key": Series([1, 2], dtype=lt)})
+ right = DataFrame({"key": Series([1, 3], dtype=rt)})
+
+ result = left.merge(right, on="key", how="outer")
+ expected = left.astype("int64").merge(
+ right.astype("float64"), on="key", how="outer"
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_pyarrow_datetime_duplicates():
+ # GH#61926
+ pytest.importorskip("pyarrow")
+
+ t = pd.date_range("2025-07-06", periods=3, freq="h")
+ df1 = DataFrame({"time": t, "val1": [1, 2, 3]})
+ df1 = df1.convert_dtypes(dtype_backend="pyarrow")
+
+ df2 = DataFrame({"time": t.repeat(2), "val2": [10, 20, 30, 40, 50, 60]})
+ df2 = df2.convert_dtypes(dtype_backend="pyarrow")
+
+ result = merge(df1, df2, on="time", how="left")
+
+ expected = DataFrame(
+ {
+ "time": t.repeat(2),
+ "val1": [1, 1, 2, 2, 3, 3],
+ "val2": [10, 20, 30, 40, 50, 60],
+ }
+ )
+ expected = expected.convert_dtypes(dtype_backend="pyarrow")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_right_on_and_right_index():
+ df1 = DataFrame({"col": [1, 2, 3]})
+ df2 = DataFrame({"col": [2, 3, 4]})
+
+ with pytest.raises(pd.errors.MergeError):
+ df1.merge(df2, left_on="col", right_on="col", right_index=True)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_antijoin.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_antijoin.py
new file mode 100644
index 0000000000000000000000000000000000000000..006622c6e5e94cdddc9dc50f96114bde28a73525
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_antijoin.py
@@ -0,0 +1,280 @@
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ MultiIndex,
+)
+import pandas._testing as tm
+from pandas.core.reshape.merge import merge
+
+
+def test_merge_antijoin():
+ # GH#42916
+ left = DataFrame({"A": [1, 2, 3]}, index=["a", "b", "c"])
+ right = DataFrame({"B": [1, 2, 4]}, index=["a", "b", "d"])
+
+ result = merge(left, right, how="left_anti", left_index=True, right_index=True)
+ expected = DataFrame({"A": [3], "B": [np.nan]}, index=["c"])
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(left, right, how="right_anti", left_index=True, right_index=True)
+ expected = DataFrame({"A": [np.nan], "B": [4]}, index=["d"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_antijoin_on_different_columns():
+ left = DataFrame({"A": [1.0, 2.0, 3.0], "B": ["a", "b", "c"]}).astype({"B": object})
+ right = DataFrame({"C": [1.0, 2.0, 4.0], "D": ["a", "d", "b"]}).astype(
+ {"D": object}
+ )
+
+ result = merge(left, right, how="left_anti", left_on="B", right_on="D")
+ expected = DataFrame(
+ {
+ "A": [3.0],
+ "B": ["c"],
+ "C": [np.nan],
+ "D": [np.nan],
+ },
+ index=[2],
+ ).astype({"B": object, "D": object})
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(left, right, how="right_anti", left_on="B", right_on="D")
+ expected = DataFrame(
+ {
+ "A": [np.nan],
+ "B": [np.nan],
+ "C": [2.0],
+ "D": ["d"],
+ },
+ index=[1],
+ ).astype({"B": object, "D": object})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_antijoin_nonunique_keys():
+ left = DataFrame({"A": [1.0, 2.0, 3.0], "B": ["a", "b", "b"]}).astype({"B": object})
+ right = DataFrame({"C": [1.0, 2.0, 4.0], "D": ["b", "d", "d"]}).astype(
+ {"D": object}
+ )
+
+ result = merge(left, right, how="left_anti", left_on="B", right_on="D")
+ expected = DataFrame(
+ {
+ "A": [1.0],
+ "B": ["a"],
+ "C": [np.nan],
+ "D": [np.nan],
+ },
+ index=[0],
+ ).astype({"B": object, "D": object})
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(left, right, how="right_anti", left_on="B", right_on="D")
+ expected = DataFrame(
+ {
+ "A": [np.nan, np.nan],
+ "B": [np.nan, np.nan],
+ "C": [2.0, 4.0],
+ "D": ["d", "d"],
+ },
+ index=[2, 3],
+ ).astype({"B": object, "D": object})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_antijoin_same_df():
+ left = DataFrame({"A": [1, 2, 3]}, index=["a", "b", "c"], dtype=np.int64)
+ result = merge(left, left, how="left_anti", left_index=True, right_index=True)
+ expected = DataFrame([], columns=["A_x", "A_y"], dtype=np.int64)
+ tm.assert_frame_equal(result, expected, check_index_type=False)
+
+
+def test_merge_antijoin_nans():
+ left = DataFrame({"A": [1.0, 2.0, np.nan], "C": ["a", "b", "c"]}).astype(
+ {"C": object}
+ )
+ right = DataFrame({"A": [3.0, 2.0, np.nan], "D": ["d", "e", "f"]}).astype(
+ {"D": object}
+ )
+ result = merge(left, right, how="left_anti", on="A")
+ expected = DataFrame({"A": [1.0], "C": ["a"], "D": [np.nan]}).astype(
+ {"C": object, "D": object}
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_antijoin_on_datetime64tz():
+ # GH11405
+ left = DataFrame(
+ {
+ "key": pd.date_range("20151010", periods=2, tz="US/Eastern"),
+ "value": [1.0, 2.0],
+ }
+ )
+ right = DataFrame(
+ {
+ "key": pd.date_range("20151011", periods=3, tz="US/Eastern"),
+ "value": [1.0, 2.0, 3.0],
+ }
+ )
+
+ expected = DataFrame(
+ {
+ "key": pd.date_range("20151010", periods=1, tz="US/Eastern"),
+ "value_x": [1.0],
+ "value_y": [np.nan],
+ },
+ index=[0],
+ )
+ result = merge(left, right, on="key", how="left_anti")
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(
+ {
+ "key": pd.date_range("20151012", periods=2, tz="US/Eastern"),
+ "value_x": [np.nan, np.nan],
+ "value_y": [2.0, 3.0],
+ },
+ index=[1, 2],
+ )
+ result = merge(left, right, on="key", how="right_anti")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_antijoin_multiindex():
+ left = DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [4, 5, 6],
+ },
+ index=MultiIndex.from_tuples(
+ [("a", "x"), ("b", "y"), ("c", "z")], names=["first", "second"]
+ ),
+ )
+ right = DataFrame(
+ {
+ "C": [7, 8, 9],
+ "D": [10, 11, 12],
+ },
+ index=MultiIndex.from_tuples(
+ [("a", "x"), ("b", "y"), ("c", "w")], names=["first", "second"]
+ ),
+ )
+
+ result = merge(left, right, how="left_anti", left_index=True, right_index=True)
+ expected = DataFrame(
+ {
+ "A": [3],
+ "B": [6],
+ "C": [np.nan],
+ "D": [np.nan],
+ },
+ index=MultiIndex.from_tuples([("c", "z")], names=["first", "second"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(left, right, how="right_anti", left_index=True, right_index=True)
+ expected = DataFrame(
+ {
+ "A": [np.nan],
+ "B": [np.nan],
+ "C": [9],
+ "D": [12],
+ },
+ index=MultiIndex.from_tuples([("c", "w")], names=["first", "second"]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ "Int64",
+ pytest.param("int64[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ pytest.param("timestamp[s][pyarrow]", marks=td.skip_if_no("pyarrow")),
+ pytest.param("string[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ ],
+)
+def test_merge_antijoin_extension_dtype(dtype):
+ left = DataFrame(
+ {
+ "join_col": [1, 3, 5],
+ "left_val": [1, 2, 3],
+ }
+ )
+ right = DataFrame(
+ {
+ "join_col": [2, 3, 4],
+ "right_val": [1, 2, 3],
+ }
+ )
+ left = left.astype({"join_col": dtype})
+ right = right.astype({"join_col": dtype})
+ result = merge(left, right, how="left_anti", on="join_col")
+ expected = DataFrame(
+ {
+ "join_col": [1, 5],
+ "left_val": [1, 3],
+ "right_val": [np.nan, np.nan],
+ },
+ index=[0, 2],
+ )
+ expected = expected.astype({"join_col": dtype})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_antijoin_empty_dataframe():
+ left = DataFrame({"A": [], "B": []})
+ right = DataFrame({"C": [], "D": []})
+
+ result = merge(left, right, how="left_anti", left_on="A", right_on="C")
+ expected = DataFrame({"A": [], "B": [], "C": [], "D": []})
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(left, right, how="right_anti", left_on="A", right_on="C")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_antijoin_no_common_elements():
+ left = DataFrame({"A": [1, 2, 3]})
+ right = DataFrame({"B": [4, 5, 6]})
+
+ result = merge(left, right, how="left_anti", left_on="A", right_on="B")
+ expected = DataFrame({"A": [1, 2, 3], "B": [np.nan, np.nan, np.nan]})
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(left, right, how="right_anti", left_on="A", right_on="B")
+ expected = DataFrame({"A": [np.nan, np.nan, np.nan], "B": [4, 5, 6]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_antijoin_with_null_values():
+ left = DataFrame({"A": [1.0, 2.0, None, 4.0]})
+ right = DataFrame({"B": [2.0, None, 5.0]})
+
+ result = merge(left, right, how="left_anti", left_on="A", right_on="B")
+ expected = DataFrame({"A": [1.0, 4.0], "B": [np.nan, np.nan]}, index=[0, 3])
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(left, right, how="right_anti", left_on="A", right_on="B")
+ expected = DataFrame({"A": [np.nan], "B": [5.0]}, index=[2])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_antijoin_with_mixed_dtypes():
+ left = DataFrame({"A": [1, "2", 3.0]})
+ right = DataFrame({"B": ["2", 3.0, 4]})
+
+ result = merge(left, right, how="left_anti", left_on="A", right_on="B")
+ expected = DataFrame({"A": [1], "B": [np.nan]}, dtype=object)
+ tm.assert_frame_equal(result, expected)
+
+ result = merge(left, right, how="right_anti", left_on="A", right_on="B")
+ expected = DataFrame({"A": [np.nan], "B": [4]}, dtype=object, index=[2])
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_asof.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_asof.py
new file mode 100644
index 0000000000000000000000000000000000000000..4aff226c77593bd5fc3a0af376f2cd4c80584aab
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_asof.py
@@ -0,0 +1,3634 @@
+import datetime
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ Index,
+ Timedelta,
+ merge_asof,
+ option_context,
+ to_datetime,
+)
+import pandas._testing as tm
+from pandas.core.reshape.merge import MergeError
+
+
+class TestAsOfMerge:
+ def prep_data(self, df, dedupe=False):
+ if dedupe:
+ df = df.drop_duplicates(["time", "ticker"], keep="last").reset_index(
+ drop=True
+ )
+ df.time = to_datetime(df.time)
+ return df
+
+ @pytest.fixture
+ def trades(self):
+ df = pd.DataFrame(
+ [
+ ["20160525 13:30:00.023", "MSFT", "51.9500", "75", "NASDAQ"],
+ ["20160525 13:30:00.038", "MSFT", "51.9500", "155", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.7700", "100", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9200", "100", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9300", "200", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9300", "300", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9300", "600", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9300", "44", "NASDAQ"],
+ ["20160525 13:30:00.074", "AAPL", "98.6700", "478343", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6700", "478343", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6600", "6", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "30", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "75", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "20", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "35", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "10", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.5500", "6", "ARCA"],
+ ["20160525 13:30:00.075", "AAPL", "98.5500", "6", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "1000", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "200", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "300", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "400", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "600", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "200", "ARCA"],
+ ["20160525 13:30:00.078", "MSFT", "51.9500", "783", "NASDAQ"],
+ ["20160525 13:30:00.078", "MSFT", "51.9500", "100", "NASDAQ"],
+ ["20160525 13:30:00.078", "MSFT", "51.9500", "100", "NASDAQ"],
+ ],
+ columns="time,ticker,price,quantity,marketCenter".split(","),
+ )
+ df["price"] = df["price"].astype("float64")
+ df["quantity"] = df["quantity"].astype("int64")
+ return self.prep_data(df)
+
+ @pytest.fixture
+ def quotes(self):
+ df = pd.DataFrame(
+ [
+ ["20160525 13:30:00.023", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.023", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.041", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.048", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.048", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.048", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.048", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.072", "GOOG", "720.50", "720.88"],
+ ["20160525 13:30:00.075", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.076", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.076", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.076", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.078", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.078", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.078", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.078", "MSFT", "51.92", "51.95"],
+ ],
+ columns="time,ticker,bid,ask".split(","),
+ )
+ df["bid"] = df["bid"].astype("float64")
+ df["ask"] = df["ask"].astype("float64")
+ return self.prep_data(df, dedupe=True)
+
+ @pytest.fixture
+ def asof(self):
+ df = pd.DataFrame(
+ [
+ [
+ "20160525 13:30:00.023",
+ "MSFT",
+ "51.95",
+ "75",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.038",
+ "MSFT",
+ "51.95",
+ "155",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.77",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.92",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "200",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "300",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "600",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "44",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.074",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.66",
+ "6",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "30",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "75",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "20",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "35",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "10",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.55",
+ "6",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.55",
+ "6",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "1000",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "300",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "400",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "600",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "783",
+ "NASDAQ",
+ "51.92",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.92",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.92",
+ "51.95",
+ ],
+ ],
+ columns="time,ticker,price,quantity,marketCenter,bid,ask".split(","),
+ )
+ df["price"] = df["price"].astype("float64")
+ df["quantity"] = df["quantity"].astype("int64")
+ df["bid"] = df["bid"].astype("float64")
+ df["ask"] = df["ask"].astype("float64")
+ return self.prep_data(df)
+
+ @pytest.fixture
+ def tolerance(self):
+ df = pd.DataFrame(
+ [
+ [
+ "20160525 13:30:00.023",
+ "MSFT",
+ "51.95",
+ "75",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.038",
+ "MSFT",
+ "51.95",
+ "155",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.77",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.92",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "200",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "300",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "600",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "44",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.074",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.66",
+ "6",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "30",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "75",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "20",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "35",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "10",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.55",
+ "6",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.55",
+ "6",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "1000",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "300",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "400",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "600",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "783",
+ "NASDAQ",
+ "51.92",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.92",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.92",
+ "51.95",
+ ],
+ ],
+ columns="time,ticker,price,quantity,marketCenter,bid,ask".split(","),
+ )
+ df["price"] = df["price"].astype("float64")
+ df["quantity"] = df["quantity"].astype("int64")
+ df["bid"] = df["bid"].astype("float64")
+ df["ask"] = df["ask"].astype("float64")
+ return self.prep_data(df)
+
+ def test_examples1(self):
+ """doc-string examples"""
+ left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": [1, 2, 3, 6, 7], "right_val": [1, 2, 3, 6, 7]})
+
+ expected = pd.DataFrame(
+ {"a": [1, 5, 10], "left_val": ["a", "b", "c"], "right_val": [1, 3, 7]}
+ )
+
+ result = merge_asof(left, right, on="a")
+ tm.assert_frame_equal(result, expected)
+
+ def test_examples2(self, unit):
+ """doc-string examples"""
+ if unit == "s":
+ pytest.skip(
+ "This test is invalid for unit='s' because that would "
+ "round the trades['time']]"
+ )
+ trades = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.038",
+ "20160525 13:30:00.048",
+ "20160525 13:30:00.048",
+ "20160525 13:30:00.048",
+ ]
+ ).astype(f"M8[{unit}]"),
+ "ticker": ["MSFT", "MSFT", "GOOG", "GOOG", "AAPL"],
+ "price": [51.95, 51.95, 720.77, 720.92, 98.00],
+ "quantity": [75, 155, 100, 100, 100],
+ },
+ columns=["time", "ticker", "price", "quantity"],
+ )
+
+ quotes = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.030",
+ "20160525 13:30:00.041",
+ "20160525 13:30:00.048",
+ "20160525 13:30:00.049",
+ "20160525 13:30:00.072",
+ "20160525 13:30:00.075",
+ ]
+ ).astype(f"M8[{unit}]"),
+ "ticker": [
+ "GOOG",
+ "MSFT",
+ "MSFT",
+ "MSFT",
+ "GOOG",
+ "AAPL",
+ "GOOG",
+ "MSFT",
+ ],
+ "bid": [720.50, 51.95, 51.97, 51.99, 720.50, 97.99, 720.50, 52.01],
+ "ask": [720.93, 51.96, 51.98, 52.00, 720.93, 98.01, 720.88, 52.03],
+ },
+ columns=["time", "ticker", "bid", "ask"],
+ )
+
+ merge_asof(trades, quotes, on="time", by="ticker")
+
+ merge_asof(trades, quotes, on="time", by="ticker", tolerance=Timedelta("2ms"))
+
+ expected = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.038",
+ "20160525 13:30:00.048",
+ "20160525 13:30:00.048",
+ "20160525 13:30:00.048",
+ ]
+ ).astype(f"M8[{unit}]"),
+ "ticker": ["MSFT", "MSFT", "GOOG", "GOOG", "AAPL"],
+ "price": [51.95, 51.95, 720.77, 720.92, 98.00],
+ "quantity": [75, 155, 100, 100, 100],
+ "bid": [np.nan, 51.97, np.nan, np.nan, np.nan],
+ "ask": [np.nan, 51.98, np.nan, np.nan, np.nan],
+ },
+ columns=["time", "ticker", "price", "quantity", "bid", "ask"],
+ )
+
+ result = merge_asof(
+ trades,
+ quotes,
+ on="time",
+ by="ticker",
+ tolerance=Timedelta("10ms"),
+ allow_exact_matches=False,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_examples3(self):
+ """doc-string examples"""
+ # GH14887
+
+ left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": [1, 2, 3, 6, 7], "right_val": [1, 2, 3, 6, 7]})
+
+ expected = pd.DataFrame(
+ {"a": [1, 5, 10], "left_val": ["a", "b", "c"], "right_val": [1, 6, np.nan]}
+ )
+
+ result = merge_asof(left, right, on="a", direction="forward")
+ tm.assert_frame_equal(result, expected)
+
+ def test_examples4(self):
+ """doc-string examples"""
+ # GH14887
+
+ left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": [1, 2, 3, 6, 7], "right_val": [1, 2, 3, 6, 7]})
+
+ expected = pd.DataFrame(
+ {"a": [1, 5, 10], "left_val": ["a", "b", "c"], "right_val": [1, 6, 7]}
+ )
+
+ result = merge_asof(left, right, on="a", direction="nearest")
+ tm.assert_frame_equal(result, expected)
+
+ def test_basic(self, trades, asof, quotes):
+ expected = asof
+
+ result = merge_asof(trades, quotes, on="time", by="ticker")
+ tm.assert_frame_equal(result, expected)
+
+ def test_basic_categorical(self, trades, asof, quotes):
+ expected = asof
+ trades.ticker = trades.ticker.astype("category")
+ quotes.ticker = quotes.ticker.astype("category")
+ expected.ticker = expected.ticker.astype("category")
+
+ result = merge_asof(trades, quotes, on="time", by="ticker")
+ tm.assert_frame_equal(result, expected)
+
+ def test_basic_left_index(self, trades, asof, quotes):
+ # GH14253
+ expected = asof
+ trades = trades.set_index("time")
+
+ result = merge_asof(
+ trades, quotes, left_index=True, right_on="time", by="ticker"
+ )
+ # left-only index uses right"s index, oddly
+ expected.index = result.index
+ # time column appears after left"s columns
+ expected = expected[result.columns]
+ tm.assert_frame_equal(result, expected)
+
+ def test_basic_right_index(self, trades, asof, quotes):
+ expected = asof
+ quotes = quotes.set_index("time")
+
+ result = merge_asof(
+ trades, quotes, left_on="time", right_index=True, by="ticker"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_basic_left_index_right_index(self, trades, asof, quotes):
+ expected = asof.set_index("time")
+ trades = trades.set_index("time")
+ quotes = quotes.set_index("time")
+
+ result = merge_asof(
+ trades, quotes, left_index=True, right_index=True, by="ticker"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_multi_index_left(self, trades, quotes):
+ # MultiIndex is prohibited
+ trades = trades.set_index(["time", "price"])
+ quotes = quotes.set_index("time")
+ with pytest.raises(MergeError, match="left can only have one index"):
+ merge_asof(trades, quotes, left_index=True, right_index=True)
+
+ def test_multi_index_right(self, trades, quotes):
+ # MultiIndex is prohibited
+ trades = trades.set_index("time")
+ quotes = quotes.set_index(["time", "bid"])
+ with pytest.raises(MergeError, match="right can only have one index"):
+ merge_asof(trades, quotes, left_index=True, right_index=True)
+
+ def test_on_and_index_left_on(self, trades, quotes):
+ # "on" parameter and index together is prohibited
+ trades = trades.set_index("time")
+ quotes = quotes.set_index("time")
+ msg = 'Can only pass argument "left_on" OR "left_index" not both.'
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(
+ trades, quotes, left_on="price", left_index=True, right_index=True
+ )
+
+ def test_on_and_index_right_on(self, trades, quotes):
+ trades = trades.set_index("time")
+ quotes = quotes.set_index("time")
+ msg = 'Can only pass argument "right_on" OR "right_index" not both.'
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(
+ trades, quotes, right_on="bid", left_index=True, right_index=True
+ )
+
+ def test_basic_left_by_right_by(self, trades, asof, quotes):
+ # GH14253
+ expected = asof
+
+ result = merge_asof(
+ trades, quotes, on="time", left_by="ticker", right_by="ticker"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_missing_right_by(self, trades, asof, quotes):
+ expected = asof
+
+ q = quotes[quotes.ticker != "MSFT"]
+ result = merge_asof(trades, q, on="time", by="ticker")
+ expected.loc[expected.ticker == "MSFT", ["bid", "ask"]] = np.nan
+ tm.assert_frame_equal(result, expected)
+
+ def test_multiby(self):
+ # GH13936
+ trades = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.046",
+ "20160525 13:30:00.048",
+ "20160525 13:30:00.050",
+ ]
+ ),
+ "ticker": ["MSFT", "MSFT", "GOOG", "GOOG", "AAPL"],
+ "exch": ["ARCA", "NSDQ", "NSDQ", "BATS", "NSDQ"],
+ "price": [51.95, 51.95, 720.77, 720.92, 98.00],
+ "quantity": [75, 155, 100, 100, 100],
+ },
+ columns=["time", "ticker", "exch", "price", "quantity"],
+ )
+
+ quotes = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.030",
+ "20160525 13:30:00.041",
+ "20160525 13:30:00.045",
+ "20160525 13:30:00.049",
+ ]
+ ),
+ "ticker": ["GOOG", "MSFT", "MSFT", "MSFT", "GOOG", "AAPL"],
+ "exch": ["BATS", "NSDQ", "ARCA", "ARCA", "NSDQ", "ARCA"],
+ "bid": [720.51, 51.95, 51.97, 51.99, 720.50, 97.99],
+ "ask": [720.92, 51.96, 51.98, 52.00, 720.93, 98.01],
+ },
+ columns=["time", "ticker", "exch", "bid", "ask"],
+ )
+
+ expected = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.046",
+ "20160525 13:30:00.048",
+ "20160525 13:30:00.050",
+ ]
+ ),
+ "ticker": ["MSFT", "MSFT", "GOOG", "GOOG", "AAPL"],
+ "exch": ["ARCA", "NSDQ", "NSDQ", "BATS", "NSDQ"],
+ "price": [51.95, 51.95, 720.77, 720.92, 98.00],
+ "quantity": [75, 155, 100, 100, 100],
+ "bid": [np.nan, 51.95, 720.50, 720.51, np.nan],
+ "ask": [np.nan, 51.96, 720.93, 720.92, np.nan],
+ },
+ columns=["time", "ticker", "exch", "price", "quantity", "bid", "ask"],
+ )
+
+ result = merge_asof(trades, quotes, on="time", by=["ticker", "exch"])
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["object", "string"])
+ def test_multiby_heterogeneous_types(self, dtype):
+ # GH13936
+ trades = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.046",
+ "20160525 13:30:00.048",
+ "20160525 13:30:00.050",
+ ]
+ ),
+ "ticker": [0, 0, 1, 1, 2],
+ "exch": ["ARCA", "NSDQ", "NSDQ", "BATS", "NSDQ"],
+ "price": [51.95, 51.95, 720.77, 720.92, 98.00],
+ "quantity": [75, 155, 100, 100, 100],
+ },
+ columns=["time", "ticker", "exch", "price", "quantity"],
+ )
+ trades = trades.astype({"ticker": dtype, "exch": dtype})
+
+ quotes = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.030",
+ "20160525 13:30:00.041",
+ "20160525 13:30:00.045",
+ "20160525 13:30:00.049",
+ ]
+ ),
+ "ticker": [1, 0, 0, 0, 1, 2],
+ "exch": ["BATS", "NSDQ", "ARCA", "ARCA", "NSDQ", "ARCA"],
+ "bid": [720.51, 51.95, 51.97, 51.99, 720.50, 97.99],
+ "ask": [720.92, 51.96, 51.98, 52.00, 720.93, 98.01],
+ },
+ columns=["time", "ticker", "exch", "bid", "ask"],
+ )
+ quotes = quotes.astype({"ticker": dtype, "exch": dtype})
+
+ expected = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.023",
+ "20160525 13:30:00.046",
+ "20160525 13:30:00.048",
+ "20160525 13:30:00.050",
+ ]
+ ),
+ "ticker": [0, 0, 1, 1, 2],
+ "exch": ["ARCA", "NSDQ", "NSDQ", "BATS", "NSDQ"],
+ "price": [51.95, 51.95, 720.77, 720.92, 98.00],
+ "quantity": [75, 155, 100, 100, 100],
+ "bid": [np.nan, 51.95, 720.50, 720.51, np.nan],
+ "ask": [np.nan, 51.96, 720.93, 720.92, np.nan],
+ },
+ columns=["time", "ticker", "exch", "price", "quantity", "bid", "ask"],
+ )
+ expected = expected.astype({"ticker": dtype, "exch": dtype})
+
+ result = merge_asof(trades, quotes, on="time", by=["ticker", "exch"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_mismatched_index_dtype(self):
+ # similar to test_multiby_indexed, but we change the dtype on left.index
+ left = pd.DataFrame(
+ [
+ [to_datetime("20160602"), 1, "a"],
+ [to_datetime("20160602"), 2, "a"],
+ [to_datetime("20160603"), 1, "b"],
+ [to_datetime("20160603"), 2, "b"],
+ ],
+ columns=["time", "k1", "k2"],
+ ).set_index("time")
+ # different dtype for the index
+ left.index = left.index - pd.Timestamp(0)
+
+ right = pd.DataFrame(
+ [
+ [to_datetime("20160502"), 1, "a", 1.0],
+ [to_datetime("20160502"), 2, "a", 2.0],
+ [to_datetime("20160503"), 1, "b", 3.0],
+ [to_datetime("20160503"), 2, "b", 4.0],
+ ],
+ columns=["time", "k1", "k2", "value"],
+ ).set_index("time")
+
+ msg = "incompatible merge keys"
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(left, right, left_index=True, right_index=True, by=["k1", "k2"])
+
+ def test_multiby_indexed(self):
+ # GH15676
+ left = pd.DataFrame(
+ [
+ [to_datetime("20160602"), 1, "a"],
+ [to_datetime("20160602"), 2, "a"],
+ [to_datetime("20160603"), 1, "b"],
+ [to_datetime("20160603"), 2, "b"],
+ ],
+ columns=["time", "k1", "k2"],
+ ).set_index("time")
+
+ right = pd.DataFrame(
+ [
+ [to_datetime("20160502"), 1, "a", 1.0],
+ [to_datetime("20160502"), 2, "a", 2.0],
+ [to_datetime("20160503"), 1, "b", 3.0],
+ [to_datetime("20160503"), 2, "b", 4.0],
+ ],
+ columns=["time", "k1", "k2", "value"],
+ ).set_index("time")
+
+ expected = pd.DataFrame(
+ [
+ [to_datetime("20160602"), 1, "a", 1.0],
+ [to_datetime("20160602"), 2, "a", 2.0],
+ [to_datetime("20160603"), 1, "b", 3.0],
+ [to_datetime("20160603"), 2, "b", 4.0],
+ ],
+ columns=["time", "k1", "k2", "value"],
+ ).set_index("time")
+
+ result = merge_asof(
+ left, right, left_index=True, right_index=True, by=["k1", "k2"]
+ )
+
+ tm.assert_frame_equal(expected, result)
+
+ with pytest.raises(
+ MergeError, match="left_by and right_by must be the same length"
+ ):
+ merge_asof(
+ left,
+ right,
+ left_index=True,
+ right_index=True,
+ left_by=["k1", "k2"],
+ right_by=["k1"],
+ )
+
+ def test_basic2(self, datapath):
+ expected = pd.DataFrame(
+ [
+ [
+ "20160525 13:30:00.023",
+ "MSFT",
+ "51.95",
+ "75",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.038",
+ "MSFT",
+ "51.95",
+ "155",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.77",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.92",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "200",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "300",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "600",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "44",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.074",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.66",
+ "6",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "30",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "75",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "20",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "35",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "10",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.55",
+ "6",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.55",
+ "6",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "1000",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "300",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "400",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "600",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "783",
+ "NASDAQ",
+ "51.92",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.92",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.92",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.084",
+ "AAPL",
+ "98.64",
+ "40",
+ "NASDAQ",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.084",
+ "AAPL",
+ "98.55",
+ "149",
+ "EDGX",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.086",
+ "AAPL",
+ "98.56",
+ "500",
+ "ARCA",
+ "98.55",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "647",
+ "EDGX",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "300",
+ "EDGX",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "50",
+ "NASDAQ",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "50",
+ "NASDAQ",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "70",
+ "NASDAQ",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "70",
+ "NASDAQ",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "1",
+ "NASDAQ",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "62",
+ "NASDAQ",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "10",
+ "NASDAQ",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.104",
+ "AAPL",
+ "98.63",
+ "100",
+ "ARCA",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.105",
+ "AAPL",
+ "98.63",
+ "100",
+ "ARCA",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.105",
+ "AAPL",
+ "98.63",
+ "700",
+ "ARCA",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.106",
+ "AAPL",
+ "98.63",
+ "61",
+ "EDGX",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.107",
+ "AAPL",
+ "98.63",
+ "100",
+ "ARCA",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.107",
+ "AAPL",
+ "98.63",
+ "53",
+ "ARCA",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.108",
+ "AAPL",
+ "98.63",
+ "100",
+ "ARCA",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.108",
+ "AAPL",
+ "98.63",
+ "839",
+ "ARCA",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.115",
+ "AAPL",
+ "98.63",
+ "5",
+ "EDGX",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.118",
+ "AAPL",
+ "98.63",
+ "295",
+ "EDGX",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.118",
+ "AAPL",
+ "98.63",
+ "5",
+ "EDGX",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.128",
+ "AAPL",
+ "98.63",
+ "100",
+ "NASDAQ",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.128",
+ "AAPL",
+ "98.63",
+ "100",
+ "NASDAQ",
+ "98.62",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.128",
+ "MSFT",
+ "51.92",
+ "100",
+ "ARCA",
+ "51.92",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.129",
+ "AAPL",
+ "98.62",
+ "100",
+ "NASDAQ",
+ "98.61",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.129",
+ "AAPL",
+ "98.62",
+ "10",
+ "NASDAQ",
+ "98.61",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.129",
+ "AAPL",
+ "98.62",
+ "59",
+ "NASDAQ",
+ "98.61",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.129",
+ "AAPL",
+ "98.62",
+ "31",
+ "NASDAQ",
+ "98.61",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.129",
+ "AAPL",
+ "98.62",
+ "69",
+ "NASDAQ",
+ "98.61",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.129",
+ "AAPL",
+ "98.62",
+ "12",
+ "NASDAQ",
+ "98.61",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.129",
+ "AAPL",
+ "98.62",
+ "12",
+ "EDGX",
+ "98.61",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.129",
+ "AAPL",
+ "98.62",
+ "100",
+ "ARCA",
+ "98.61",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.129",
+ "AAPL",
+ "98.62",
+ "100",
+ "ARCA",
+ "98.61",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.130",
+ "MSFT",
+ "51.95",
+ "317",
+ "ARCA",
+ "51.93",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.130",
+ "MSFT",
+ "51.95",
+ "283",
+ "ARCA",
+ "51.93",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.135",
+ "MSFT",
+ "51.93",
+ "100",
+ "EDGX",
+ "51.92",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.135",
+ "AAPL",
+ "98.62",
+ "100",
+ "ARCA",
+ "98.61",
+ "98.62",
+ ],
+ [
+ "20160525 13:30:00.144",
+ "AAPL",
+ "98.62",
+ "12",
+ "NASDAQ",
+ "98.61",
+ "98.62",
+ ],
+ [
+ "20160525 13:30:00.144",
+ "AAPL",
+ "98.62",
+ "88",
+ "NASDAQ",
+ "98.61",
+ "98.62",
+ ],
+ [
+ "20160525 13:30:00.144",
+ "AAPL",
+ "98.62",
+ "162",
+ "NASDAQ",
+ "98.61",
+ "98.62",
+ ],
+ [
+ "20160525 13:30:00.144",
+ "AAPL",
+ "98.61",
+ "100",
+ "BATS",
+ "98.61",
+ "98.62",
+ ],
+ [
+ "20160525 13:30:00.144",
+ "AAPL",
+ "98.62",
+ "61",
+ "ARCA",
+ "98.61",
+ "98.62",
+ ],
+ [
+ "20160525 13:30:00.144",
+ "AAPL",
+ "98.62",
+ "25",
+ "ARCA",
+ "98.61",
+ "98.62",
+ ],
+ [
+ "20160525 13:30:00.144",
+ "AAPL",
+ "98.62",
+ "14",
+ "ARCA",
+ "98.61",
+ "98.62",
+ ],
+ [
+ "20160525 13:30:00.145",
+ "AAPL",
+ "98.62",
+ "12",
+ "ARCA",
+ "98.6",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.145",
+ "AAPL",
+ "98.62",
+ "100",
+ "ARCA",
+ "98.6",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.145",
+ "AAPL",
+ "98.63",
+ "100",
+ "NASDAQ",
+ "98.6",
+ "98.63",
+ ],
+ [
+ "20160525 13:30:00.145",
+ "AAPL",
+ "98.63",
+ "100",
+ "NASDAQ",
+ "98.6",
+ "98.63",
+ ],
+ ],
+ columns="time,ticker,price,quantity,marketCenter,bid,ask".split(","),
+ )
+ expected["price"] = expected["price"].astype("float64")
+ expected["quantity"] = expected["quantity"].astype("int64")
+ expected["bid"] = expected["bid"].astype("float64")
+ expected["ask"] = expected["ask"].astype("float64")
+ expected = self.prep_data(expected)
+
+ trades = pd.DataFrame(
+ [
+ ["20160525 13:30:00.023", "MSFT", "51.9500", "75", "NASDAQ"],
+ ["20160525 13:30:00.038", "MSFT", "51.9500", "155", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.7700", "100", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9200", "100", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9300", "200", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9300", "300", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9300", "600", "NASDAQ"],
+ ["20160525 13:30:00.048", "GOOG", "720.9300", "44", "NASDAQ"],
+ ["20160525 13:30:00.074", "AAPL", "98.6700", "478343", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6700", "478343", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6600", "6", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "30", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "75", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "20", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "35", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.6500", "10", "NASDAQ"],
+ ["20160525 13:30:00.075", "AAPL", "98.5500", "6", "ARCA"],
+ ["20160525 13:30:00.075", "AAPL", "98.5500", "6", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "1000", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "200", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "300", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "400", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "600", "ARCA"],
+ ["20160525 13:30:00.076", "AAPL", "98.5600", "200", "ARCA"],
+ ["20160525 13:30:00.078", "MSFT", "51.9500", "783", "NASDAQ"],
+ ["20160525 13:30:00.078", "MSFT", "51.9500", "100", "NASDAQ"],
+ ["20160525 13:30:00.078", "MSFT", "51.9500", "100", "NASDAQ"],
+ ["20160525 13:30:00.084", "AAPL", "98.6400", "40", "NASDAQ"],
+ ["20160525 13:30:00.084", "AAPL", "98.5500", "149", "EDGX"],
+ ["20160525 13:30:00.086", "AAPL", "98.5600", "500", "ARCA"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "647", "EDGX"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "300", "EDGX"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "50", "NASDAQ"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "50", "NASDAQ"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "70", "NASDAQ"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "70", "NASDAQ"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "1", "NASDAQ"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "62", "NASDAQ"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "10", "NASDAQ"],
+ ["20160525 13:30:00.104", "AAPL", "98.6300", "100", "ARCA"],
+ ["20160525 13:30:00.105", "AAPL", "98.6300", "100", "ARCA"],
+ ["20160525 13:30:00.105", "AAPL", "98.6300", "700", "ARCA"],
+ ["20160525 13:30:00.106", "AAPL", "98.6300", "61", "EDGX"],
+ ["20160525 13:30:00.107", "AAPL", "98.6300", "100", "ARCA"],
+ ["20160525 13:30:00.107", "AAPL", "98.6300", "53", "ARCA"],
+ ["20160525 13:30:00.108", "AAPL", "98.6300", "100", "ARCA"],
+ ["20160525 13:30:00.108", "AAPL", "98.6300", "839", "ARCA"],
+ ["20160525 13:30:00.115", "AAPL", "98.6300", "5", "EDGX"],
+ ["20160525 13:30:00.118", "AAPL", "98.6300", "295", "EDGX"],
+ ["20160525 13:30:00.118", "AAPL", "98.6300", "5", "EDGX"],
+ ["20160525 13:30:00.128", "AAPL", "98.6300", "100", "NASDAQ"],
+ ["20160525 13:30:00.128", "AAPL", "98.6300", "100", "NASDAQ"],
+ ["20160525 13:30:00.128", "MSFT", "51.9200", "100", "ARCA"],
+ ["20160525 13:30:00.129", "AAPL", "98.6200", "100", "NASDAQ"],
+ ["20160525 13:30:00.129", "AAPL", "98.6200", "10", "NASDAQ"],
+ ["20160525 13:30:00.129", "AAPL", "98.6200", "59", "NASDAQ"],
+ ["20160525 13:30:00.129", "AAPL", "98.6200", "31", "NASDAQ"],
+ ["20160525 13:30:00.129", "AAPL", "98.6200", "69", "NASDAQ"],
+ ["20160525 13:30:00.129", "AAPL", "98.6200", "12", "NASDAQ"],
+ ["20160525 13:30:00.129", "AAPL", "98.6200", "12", "EDGX"],
+ ["20160525 13:30:00.129", "AAPL", "98.6200", "100", "ARCA"],
+ ["20160525 13:30:00.129", "AAPL", "98.6200", "100", "ARCA"],
+ ["20160525 13:30:00.130", "MSFT", "51.9500", "317", "ARCA"],
+ ["20160525 13:30:00.130", "MSFT", "51.9500", "283", "ARCA"],
+ ["20160525 13:30:00.135", "MSFT", "51.9300", "100", "EDGX"],
+ ["20160525 13:30:00.135", "AAPL", "98.6200", "100", "ARCA"],
+ ["20160525 13:30:00.144", "AAPL", "98.6200", "12", "NASDAQ"],
+ ["20160525 13:30:00.144", "AAPL", "98.6200", "88", "NASDAQ"],
+ ["20160525 13:30:00.144", "AAPL", "98.6200", "162", "NASDAQ"],
+ ["20160525 13:30:00.144", "AAPL", "98.6100", "100", "BATS"],
+ ["20160525 13:30:00.144", "AAPL", "98.6200", "61", "ARCA"],
+ ["20160525 13:30:00.144", "AAPL", "98.6200", "25", "ARCA"],
+ ["20160525 13:30:00.144", "AAPL", "98.6200", "14", "ARCA"],
+ ["20160525 13:30:00.145", "AAPL", "98.6200", "12", "ARCA"],
+ ["20160525 13:30:00.145", "AAPL", "98.6200", "100", "ARCA"],
+ ["20160525 13:30:00.145", "AAPL", "98.6300", "100", "NASDAQ"],
+ ["20160525 13:30:00.145", "AAPL", "98.6300", "100", "NASDAQ"],
+ ],
+ columns="time,ticker,price,quantity,marketCenter".split(","),
+ )
+ trades["price"] = trades["price"].astype("float64")
+ trades["quantity"] = trades["quantity"].astype("int64")
+ trades = self.prep_data(trades)
+
+ quotes = pd.DataFrame(
+ [
+ ["20160525 13:30:00.023", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.023", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.041", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.048", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.048", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.048", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.048", "GOOG", "720.50", "720.93"],
+ ["20160525 13:30:00.072", "GOOG", "720.50", "720.88"],
+ ["20160525 13:30:00.075", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.076", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.076", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.076", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.078", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.078", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.078", "MSFT", "51.95", "51.95"],
+ ["20160525 13:30:00.078", "MSFT", "51.92", "51.95"],
+ ["20160525 13:30:00.079", "MSFT", "51.92", "51.95"],
+ ["20160525 13:30:00.080", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.084", "AAPL", "98.55", "98.56"],
+ ["20160525 13:30:00.086", "AAPL", "98.55", "98.63"],
+ ["20160525 13:30:00.088", "AAPL", "98.65", "98.63"],
+ ["20160525 13:30:00.089", "AAPL", "98.63", "98.63"],
+ ["20160525 13:30:00.104", "AAPL", "98.63", "98.63"],
+ ["20160525 13:30:00.104", "AAPL", "98.63", "98.63"],
+ ["20160525 13:30:00.104", "AAPL", "98.63", "98.63"],
+ ["20160525 13:30:00.104", "AAPL", "98.63", "98.63"],
+ ["20160525 13:30:00.104", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.105", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.107", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.115", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.115", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.118", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.128", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.128", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.129", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.129", "AAPL", "98.61", "98.63"],
+ ["20160525 13:30:00.129", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.129", "AAPL", "98.62", "98.63"],
+ ["20160525 13:30:00.129", "AAPL", "98.61", "98.63"],
+ ["20160525 13:30:00.130", "MSFT", "51.93", "51.95"],
+ ["20160525 13:30:00.130", "MSFT", "51.93", "51.95"],
+ ["20160525 13:30:00.130", "AAPL", "98.61", "98.63"],
+ ["20160525 13:30:00.131", "AAPL", "98.61", "98.62"],
+ ["20160525 13:30:00.131", "AAPL", "98.61", "98.62"],
+ ["20160525 13:30:00.135", "MSFT", "51.92", "51.95"],
+ ["20160525 13:30:00.135", "AAPL", "98.61", "98.62"],
+ ["20160525 13:30:00.136", "AAPL", "98.61", "98.62"],
+ ["20160525 13:30:00.136", "AAPL", "98.61", "98.62"],
+ ["20160525 13:30:00.144", "AAPL", "98.61", "98.62"],
+ ["20160525 13:30:00.144", "AAPL", "98.61", "98.62"],
+ ["20160525 13:30:00.145", "AAPL", "98.61", "98.62"],
+ ["20160525 13:30:00.145", "AAPL", "98.61", "98.63"],
+ ["20160525 13:30:00.145", "AAPL", "98.61", "98.63"],
+ ["20160525 13:30:00.145", "AAPL", "98.60", "98.63"],
+ ["20160525 13:30:00.145", "AAPL", "98.61", "98.63"],
+ ["20160525 13:30:00.145", "AAPL", "98.60", "98.63"],
+ ],
+ columns="time,ticker,bid,ask".split(","),
+ )
+ quotes["bid"] = quotes["bid"].astype("float64")
+ quotes["ask"] = quotes["ask"].astype("float64")
+ quotes = self.prep_data(quotes, dedupe=True)
+
+ result = merge_asof(trades, quotes, on="time", by="ticker")
+ tm.assert_frame_equal(result, expected)
+
+ def test_basic_no_by(self, trades, asof, quotes):
+ f = lambda x: (
+ x[x.ticker == "MSFT"].drop("ticker", axis=1).reset_index(drop=True)
+ )
+
+ # just use a single ticker
+ expected = f(asof)
+ trades = f(trades)
+ quotes = f(quotes)
+
+ result = merge_asof(trades, quotes, on="time")
+ tm.assert_frame_equal(result, expected)
+
+ def test_valid_join_keys(self, trades, quotes):
+ msg = r"incompatible merge keys \[1\] .* must be the same type"
+
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(trades, quotes, left_on="time", right_on="bid", by="ticker")
+
+ with pytest.raises(MergeError, match="can only asof on a key for left"):
+ merge_asof(trades, quotes, on=["time", "ticker"], by="ticker")
+
+ with pytest.raises(MergeError, match="can only asof on a key for left"):
+ merge_asof(trades, quotes, by="ticker")
+
+ def test_with_duplicates(self, datapath, trades, quotes, asof):
+ q = (
+ pd.concat([quotes, quotes])
+ .sort_values(["time", "ticker"])
+ .reset_index(drop=True)
+ )
+ result = merge_asof(trades, q, on="time", by="ticker")
+ expected = self.prep_data(asof)
+ tm.assert_frame_equal(result, expected)
+
+ def test_with_duplicates_no_on(self):
+ df1 = pd.DataFrame({"key": [1, 1, 3], "left_val": [1, 2, 3]})
+ df2 = pd.DataFrame({"key": [1, 2, 2], "right_val": [1, 2, 3]})
+ result = merge_asof(df1, df2, on="key")
+ expected = pd.DataFrame(
+ {"key": [1, 1, 3], "left_val": [1, 2, 3], "right_val": [1, 1, 3]}
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_valid_allow_exact_matches(self, trades, quotes):
+ msg = "allow_exact_matches must be boolean, passed foo"
+
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(
+ trades, quotes, on="time", by="ticker", allow_exact_matches="foo"
+ )
+
+ def test_valid_tolerance(self, trades, quotes):
+ # dti
+ merge_asof(trades, quotes, on="time", by="ticker", tolerance=Timedelta("1s"))
+
+ # integer
+ merge_asof(
+ trades.reset_index(),
+ quotes.reset_index(),
+ on="index",
+ by="ticker",
+ tolerance=1,
+ )
+
+ msg = r"incompatible tolerance .*, must be compat with type .*"
+
+ # incompat
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(trades, quotes, on="time", by="ticker", tolerance=1)
+
+ # invalid
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(
+ trades.reset_index(),
+ quotes.reset_index(),
+ on="index",
+ by="ticker",
+ tolerance=1.0,
+ )
+
+ msg = "tolerance must be positive"
+
+ # invalid negative
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(
+ trades, quotes, on="time", by="ticker", tolerance=-Timedelta("1s")
+ )
+
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(
+ trades.reset_index(),
+ quotes.reset_index(),
+ on="index",
+ by="ticker",
+ tolerance=-1,
+ )
+
+ def test_non_sorted(self, trades, quotes):
+ trades = trades.sort_values("time", ascending=False)
+ quotes = quotes.sort_values("time", ascending=False)
+
+ # we require that we are already sorted on time & quotes
+ assert not trades.time.is_monotonic_increasing
+ assert not quotes.time.is_monotonic_increasing
+ with pytest.raises(ValueError, match="left keys must be sorted"):
+ merge_asof(trades, quotes, on="time", by="ticker")
+
+ trades = trades.sort_values("time")
+ assert trades.time.is_monotonic_increasing
+ assert not quotes.time.is_monotonic_increasing
+ with pytest.raises(ValueError, match="right keys must be sorted"):
+ merge_asof(trades, quotes, on="time", by="ticker")
+
+ quotes = quotes.sort_values("time")
+ assert trades.time.is_monotonic_increasing
+ assert quotes.time.is_monotonic_increasing
+
+ # ok, though has dupes
+ merge_asof(trades, quotes, on="time", by="ticker")
+
+ @pytest.mark.parametrize(
+ "tolerance_ts",
+ [Timedelta("1day"), datetime.timedelta(days=1)],
+ ids=["Timedelta", "datetime.timedelta"],
+ )
+ def test_tolerance(self, tolerance_ts, trades, quotes, tolerance):
+ result = merge_asof(
+ trades, quotes, on="time", by="ticker", tolerance=tolerance_ts
+ )
+ expected = tolerance
+ tm.assert_frame_equal(result, expected)
+
+ def test_tolerance_forward(self):
+ # GH14887
+
+ left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": [1, 2, 3, 7, 11], "right_val": [1, 2, 3, 7, 11]})
+
+ expected = pd.DataFrame(
+ {"a": [1, 5, 10], "left_val": ["a", "b", "c"], "right_val": [1, np.nan, 11]}
+ )
+
+ result = merge_asof(left, right, on="a", direction="forward", tolerance=1)
+ tm.assert_frame_equal(result, expected)
+
+ def test_tolerance_nearest(self):
+ # GH14887
+
+ left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": [1, 2, 3, 7, 11], "right_val": [1, 2, 3, 7, 11]})
+
+ expected = pd.DataFrame(
+ {"a": [1, 5, 10], "left_val": ["a", "b", "c"], "right_val": [1, np.nan, 11]}
+ )
+
+ result = merge_asof(left, right, on="a", direction="nearest", tolerance=1)
+ tm.assert_frame_equal(result, expected)
+
+ def test_tolerance_tz(self, unit):
+ # GH 14844
+ left = pd.DataFrame(
+ {
+ "date": pd.date_range(
+ start=to_datetime("2016-01-02"),
+ freq="D",
+ periods=5,
+ tz=datetime.UTC,
+ unit=unit,
+ ),
+ "value1": np.arange(5),
+ }
+ )
+ right = pd.DataFrame(
+ {
+ "date": pd.date_range(
+ start=to_datetime("2016-01-01"),
+ freq="D",
+ periods=5,
+ tz=datetime.UTC,
+ unit=unit,
+ ),
+ "value2": list("ABCDE"),
+ }
+ )
+ result = merge_asof(left, right, on="date", tolerance=Timedelta("1 day"))
+
+ expected = pd.DataFrame(
+ {
+ "date": pd.date_range(
+ start=to_datetime("2016-01-02"),
+ freq="D",
+ periods=5,
+ tz=datetime.UTC,
+ unit=unit,
+ ),
+ "value1": np.arange(5),
+ "value2": list("BCDEE"),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_tolerance_float(self):
+ # GH22981
+ left = pd.DataFrame({"a": [1.1, 3.5, 10.9], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame(
+ {"a": [1.0, 2.5, 3.3, 7.5, 11.5], "right_val": [1.0, 2.5, 3.3, 7.5, 11.5]}
+ )
+
+ expected = pd.DataFrame(
+ {
+ "a": [1.1, 3.5, 10.9],
+ "left_val": ["a", "b", "c"],
+ "right_val": [1, 3.3, np.nan],
+ }
+ )
+
+ result = merge_asof(left, right, on="a", direction="nearest", tolerance=0.5)
+ tm.assert_frame_equal(result, expected)
+
+ def test_index_tolerance(self, trades, quotes, tolerance):
+ # GH 15135
+ expected = tolerance.set_index("time")
+ trades = trades.set_index("time")
+ quotes = quotes.set_index("time")
+
+ result = merge_asof(
+ trades,
+ quotes,
+ left_index=True,
+ right_index=True,
+ by="ticker",
+ tolerance=Timedelta("1day"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_allow_exact_matches(self, trades, quotes):
+ result = merge_asof(
+ trades, quotes, on="time", by="ticker", allow_exact_matches=False
+ )
+ df = pd.DataFrame(
+ [
+ [
+ "20160525 13:30:00.023",
+ "MSFT",
+ "51.95",
+ "75",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.038",
+ "MSFT",
+ "51.95",
+ "155",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.77",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.92",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "200",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "300",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "600",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "44",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.074",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.66",
+ "6",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "30",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "75",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "20",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "35",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "10",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ ["20160525 13:30:00.075", "AAPL", "98.55", "6", "ARCA", np.nan, np.nan],
+ ["20160525 13:30:00.075", "AAPL", "98.55", "6", "ARCA", np.nan, np.nan],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "1000",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "300",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "400",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "600",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "783",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ ],
+ columns="time,ticker,price,quantity,marketCenter,bid,ask".split(","),
+ )
+ df["price"] = df["price"].astype("float64")
+ df["quantity"] = df["quantity"].astype("int64")
+ df["bid"] = df["bid"].astype("float64")
+ df["ask"] = df["ask"].astype("float64")
+ expected = self.prep_data(df)
+ tm.assert_frame_equal(result, expected)
+
+ def test_allow_exact_matches_forward(self):
+ # GH14887
+
+ left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": [1, 2, 3, 7, 11], "right_val": [1, 2, 3, 7, 11]})
+
+ expected = pd.DataFrame(
+ {"a": [1, 5, 10], "left_val": ["a", "b", "c"], "right_val": [2, 7, 11]}
+ )
+
+ result = merge_asof(
+ left, right, on="a", direction="forward", allow_exact_matches=False
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_allow_exact_matches_nearest(self):
+ # GH14887
+
+ left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": [1, 2, 3, 7, 11], "right_val": [1, 2, 3, 7, 11]})
+
+ expected = pd.DataFrame(
+ {"a": [1, 5, 10], "left_val": ["a", "b", "c"], "right_val": [2, 3, 11]}
+ )
+
+ result = merge_asof(
+ left, right, on="a", direction="nearest", allow_exact_matches=False
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_allow_exact_matches_and_tolerance(self, trades, quotes):
+ result = merge_asof(
+ trades,
+ quotes,
+ on="time",
+ by="ticker",
+ tolerance=Timedelta("100ms"),
+ allow_exact_matches=False,
+ )
+ df = pd.DataFrame(
+ [
+ [
+ "20160525 13:30:00.023",
+ "MSFT",
+ "51.95",
+ "75",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.038",
+ "MSFT",
+ "51.95",
+ "155",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.77",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.92",
+ "100",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "200",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "300",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "600",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.048",
+ "GOOG",
+ "720.93",
+ "44",
+ "NASDAQ",
+ "720.5",
+ "720.93",
+ ],
+ [
+ "20160525 13:30:00.074",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.67",
+ "478343",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.66",
+ "6",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "30",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "75",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "20",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "35",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ [
+ "20160525 13:30:00.075",
+ "AAPL",
+ "98.65",
+ "10",
+ "NASDAQ",
+ np.nan,
+ np.nan,
+ ],
+ ["20160525 13:30:00.075", "AAPL", "98.55", "6", "ARCA", np.nan, np.nan],
+ ["20160525 13:30:00.075", "AAPL", "98.55", "6", "ARCA", np.nan, np.nan],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "1000",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "300",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "400",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "600",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.076",
+ "AAPL",
+ "98.56",
+ "200",
+ "ARCA",
+ "98.55",
+ "98.56",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "783",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ [
+ "20160525 13:30:00.078",
+ "MSFT",
+ "51.95",
+ "100",
+ "NASDAQ",
+ "51.95",
+ "51.95",
+ ],
+ ],
+ columns="time,ticker,price,quantity,marketCenter,bid,ask".split(","),
+ )
+ df["price"] = df["price"].astype("float64")
+ df["quantity"] = df["quantity"].astype("int64")
+ df["bid"] = df["bid"].astype("float64")
+ df["ask"] = df["ask"].astype("float64")
+ expected = self.prep_data(df)
+ tm.assert_frame_equal(result, expected)
+
+ def test_allow_exact_matches_and_tolerance2(self):
+ # GH 13695
+ df1 = pd.DataFrame(
+ {"time": to_datetime(["2016-07-15 13:30:00.030"]), "username": ["bob"]}
+ )
+ df2 = pd.DataFrame(
+ {
+ "time": to_datetime(
+ ["2016-07-15 13:30:00.000", "2016-07-15 13:30:00.030"]
+ ),
+ "version": [1, 2],
+ }
+ )
+
+ result = merge_asof(df1, df2, on="time")
+ expected = pd.DataFrame(
+ {
+ "time": to_datetime(["2016-07-15 13:30:00.030"]),
+ "username": ["bob"],
+ "version": [2],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = merge_asof(df1, df2, on="time", allow_exact_matches=False)
+ expected = pd.DataFrame(
+ {
+ "time": to_datetime(["2016-07-15 13:30:00.030"]),
+ "username": ["bob"],
+ "version": [1],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = merge_asof(
+ df1,
+ df2,
+ on="time",
+ allow_exact_matches=False,
+ tolerance=Timedelta("10ms"),
+ )
+ expected = pd.DataFrame(
+ {
+ "time": to_datetime(["2016-07-15 13:30:00.030"]),
+ "username": ["bob"],
+ "version": [np.nan],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_allow_exact_matches_and_tolerance3(self):
+ # GH 13709
+ df1 = pd.DataFrame(
+ {
+ "time": to_datetime(
+ ["2016-07-15 13:30:00.030", "2016-07-15 13:30:00.030"]
+ ),
+ "username": ["bob", "charlie"],
+ }
+ )
+ df2 = pd.DataFrame(
+ {
+ "time": to_datetime(
+ ["2016-07-15 13:30:00.000", "2016-07-15 13:30:00.030"]
+ ),
+ "version": [1, 2],
+ }
+ )
+
+ result = merge_asof(
+ df1,
+ df2,
+ on="time",
+ allow_exact_matches=False,
+ tolerance=Timedelta("10ms"),
+ )
+ expected = pd.DataFrame(
+ {
+ "time": to_datetime(
+ ["2016-07-15 13:30:00.030", "2016-07-15 13:30:00.030"]
+ ),
+ "username": ["bob", "charlie"],
+ "version": [np.nan, np.nan],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_allow_exact_matches_and_tolerance_forward(self):
+ # GH14887
+
+ left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": [1, 3, 4, 6, 11], "right_val": [1, 3, 4, 6, 11]})
+
+ expected = pd.DataFrame(
+ {"a": [1, 5, 10], "left_val": ["a", "b", "c"], "right_val": [np.nan, 6, 11]}
+ )
+
+ result = merge_asof(
+ left,
+ right,
+ on="a",
+ direction="forward",
+ allow_exact_matches=False,
+ tolerance=1,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_allow_exact_matches_and_tolerance_nearest(self):
+ # GH14887
+
+ left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": [1, 3, 4, 6, 11], "right_val": [1, 3, 4, 7, 11]})
+
+ expected = pd.DataFrame(
+ {"a": [1, 5, 10], "left_val": ["a", "b", "c"], "right_val": [np.nan, 4, 11]}
+ )
+
+ result = merge_asof(
+ left,
+ right,
+ on="a",
+ direction="nearest",
+ allow_exact_matches=False,
+ tolerance=1,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_forward_by(self):
+ # GH14887
+
+ left = pd.DataFrame(
+ {
+ "a": [1, 5, 10, 12, 15],
+ "b": ["X", "X", "Y", "Z", "Y"],
+ "left_val": ["a", "b", "c", "d", "e"],
+ }
+ )
+ right = pd.DataFrame(
+ {
+ "a": [1, 6, 11, 15, 16],
+ "b": ["X", "Z", "Y", "Z", "Y"],
+ "right_val": [1, 6, 11, 15, 16],
+ }
+ )
+
+ expected = pd.DataFrame(
+ {
+ "a": [1, 5, 10, 12, 15],
+ "b": ["X", "X", "Y", "Z", "Y"],
+ "left_val": ["a", "b", "c", "d", "e"],
+ "right_val": [1, np.nan, 11, 15, 16],
+ }
+ )
+
+ result = merge_asof(left, right, on="a", by="b", direction="forward")
+ tm.assert_frame_equal(result, expected)
+
+ def test_nearest_by(self):
+ # GH14887
+
+ left = pd.DataFrame(
+ {
+ "a": [1, 5, 10, 12, 15],
+ "b": ["X", "X", "Z", "Z", "Y"],
+ "left_val": ["a", "b", "c", "d", "e"],
+ }
+ )
+ right = pd.DataFrame(
+ {
+ "a": [1, 6, 11, 15, 16],
+ "b": ["X", "Z", "Z", "Z", "Y"],
+ "right_val": [1, 6, 11, 15, 16],
+ }
+ )
+
+ expected = pd.DataFrame(
+ {
+ "a": [1, 5, 10, 12, 15],
+ "b": ["X", "X", "Z", "Z", "Y"],
+ "left_val": ["a", "b", "c", "d", "e"],
+ "right_val": [1, 1, 11, 11, 16],
+ }
+ )
+
+ result = merge_asof(left, right, on="a", by="b", direction="nearest")
+ tm.assert_frame_equal(result, expected)
+
+ def test_by_int(self):
+ # we specialize by type, so test that this is correct
+ df1 = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.020",
+ "20160525 13:30:00.030",
+ "20160525 13:30:00.040",
+ "20160525 13:30:00.050",
+ "20160525 13:30:00.060",
+ ]
+ ),
+ "key": [1, 2, 1, 3, 2],
+ "value1": [1.1, 1.2, 1.3, 1.4, 1.5],
+ },
+ columns=["time", "key", "value1"],
+ )
+
+ df2 = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.015",
+ "20160525 13:30:00.020",
+ "20160525 13:30:00.025",
+ "20160525 13:30:00.035",
+ "20160525 13:30:00.040",
+ "20160525 13:30:00.055",
+ "20160525 13:30:00.060",
+ "20160525 13:30:00.065",
+ ]
+ ),
+ "key": [2, 1, 1, 3, 2, 1, 2, 3],
+ "value2": [2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8],
+ },
+ columns=["time", "key", "value2"],
+ )
+
+ result = merge_asof(df1, df2, on="time", by="key")
+
+ expected = pd.DataFrame(
+ {
+ "time": to_datetime(
+ [
+ "20160525 13:30:00.020",
+ "20160525 13:30:00.030",
+ "20160525 13:30:00.040",
+ "20160525 13:30:00.050",
+ "20160525 13:30:00.060",
+ ]
+ ),
+ "key": [1, 2, 1, 3, 2],
+ "value1": [1.1, 1.2, 1.3, 1.4, 1.5],
+ "value2": [2.2, 2.1, 2.3, 2.4, 2.7],
+ },
+ columns=["time", "key", "value1", "value2"],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_on_float(self):
+ # mimics how to determine the minimum-price variation
+ df1 = pd.DataFrame(
+ {
+ "price": [5.01, 0.0023, 25.13, 340.05, 30.78, 1040.90, 0.0078],
+ "symbol": list("ABCDEFG"),
+ },
+ columns=["symbol", "price"],
+ )
+
+ df2 = pd.DataFrame(
+ {"price": [0.0, 1.0, 100.0], "mpv": [0.0001, 0.01, 0.05]},
+ columns=["price", "mpv"],
+ )
+
+ df1 = df1.sort_values("price").reset_index(drop=True)
+
+ result = merge_asof(df1, df2, on="price")
+
+ expected = pd.DataFrame(
+ {
+ "symbol": list("BGACEDF"),
+ "price": [0.0023, 0.0078, 5.01, 25.13, 30.78, 340.05, 1040.90],
+ "mpv": [0.0001, 0.0001, 0.01, 0.01, 0.01, 0.05, 0.05],
+ },
+ columns=["symbol", "price", "mpv"],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_on_specialized_type(self, any_real_numpy_dtype):
+ # see gh-13936
+ dtype = np.dtype(any_real_numpy_dtype).type
+
+ df1 = pd.DataFrame(
+ {"value": [5, 2, 25, 100, 78, 120, 79], "symbol": list("ABCDEFG")},
+ columns=["symbol", "value"],
+ )
+ df1.value = dtype(df1.value)
+
+ df2 = pd.DataFrame(
+ {"value": [0, 80, 120, 125], "result": list("xyzw")},
+ columns=["value", "result"],
+ )
+ df2.value = dtype(df2.value)
+
+ df1 = df1.sort_values("value").reset_index(drop=True)
+ result = merge_asof(df1, df2, on="value")
+
+ expected = pd.DataFrame(
+ {
+ "symbol": list("BACEGDF"),
+ "value": [2, 5, 25, 78, 79, 100, 120],
+ "result": list("xxxxxyz"),
+ },
+ columns=["symbol", "value", "result"],
+ )
+ expected.value = dtype(expected.value)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_on_specialized_type_by_int(self, any_real_numpy_dtype):
+ # see gh-13936
+ dtype = np.dtype(any_real_numpy_dtype).type
+
+ df1 = pd.DataFrame(
+ {
+ "value": [5, 2, 25, 100, 78, 120, 79],
+ "key": [1, 2, 3, 2, 3, 1, 2],
+ "symbol": list("ABCDEFG"),
+ },
+ columns=["symbol", "key", "value"],
+ )
+ df1.value = dtype(df1.value)
+
+ df2 = pd.DataFrame(
+ {"value": [0, 80, 120, 125], "key": [1, 2, 2, 3], "result": list("xyzw")},
+ columns=["value", "key", "result"],
+ )
+ df2.value = dtype(df2.value)
+
+ df1 = df1.sort_values("value").reset_index(drop=True)
+ result = merge_asof(df1, df2, on="value", by="key")
+
+ expected = pd.DataFrame(
+ {
+ "symbol": list("BACEGDF"),
+ "key": [2, 1, 3, 3, 2, 2, 1],
+ "value": [2, 5, 25, 78, 79, 100, 120],
+ "result": [np.nan, "x", np.nan, np.nan, np.nan, "y", "x"],
+ },
+ columns=["symbol", "key", "value", "result"],
+ )
+ expected.value = dtype(expected.value)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_on_float_by_int(self):
+ # type specialize both "by" and "on" parameters
+ df1 = pd.DataFrame(
+ {
+ "symbol": list("AAABBBCCC"),
+ "exch": [1, 2, 3, 1, 2, 3, 1, 2, 3],
+ "price": [
+ 3.26,
+ 3.2599,
+ 3.2598,
+ 12.58,
+ 12.59,
+ 12.5,
+ 378.15,
+ 378.2,
+ 378.25,
+ ],
+ },
+ columns=["symbol", "exch", "price"],
+ )
+
+ df2 = pd.DataFrame(
+ {
+ "exch": [1, 1, 1, 2, 2, 2, 3, 3, 3],
+ "price": [0.0, 1.0, 100.0, 0.0, 5.0, 100.0, 0.0, 5.0, 1000.0],
+ "mpv": [0.0001, 0.01, 0.05, 0.0001, 0.01, 0.1, 0.0001, 0.25, 1.0],
+ },
+ columns=["exch", "price", "mpv"],
+ )
+
+ df1 = df1.sort_values("price").reset_index(drop=True)
+ df2 = df2.sort_values("price").reset_index(drop=True)
+
+ result = merge_asof(df1, df2, on="price", by="exch")
+
+ expected = pd.DataFrame(
+ {
+ "symbol": list("AAABBBCCC"),
+ "exch": [3, 2, 1, 3, 1, 2, 1, 2, 3],
+ "price": [
+ 3.2598,
+ 3.2599,
+ 3.26,
+ 12.5,
+ 12.58,
+ 12.59,
+ 378.15,
+ 378.2,
+ 378.25,
+ ],
+ "mpv": [0.0001, 0.0001, 0.01, 0.25, 0.01, 0.01, 0.05, 0.1, 0.25],
+ },
+ columns=["symbol", "exch", "price", "mpv"],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_datatype_error_raises(self):
+ msg = r"Incompatible merge dtype, .*, both sides must have numeric dtype"
+
+ left = pd.DataFrame({"left_val": [1, 5, 10], "a": ["a", "b", "c"]})
+ right = pd.DataFrame({"right_val": [1, 2, 3, 6, 7], "a": [1, 2, 3, 6, 7]})
+
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(left, right, on="a")
+
+ def test_merge_datatype_categorical_error_raises(self):
+ msg = (
+ r"incompatible merge keys \[0\] .* both sides category, "
+ "but not equal ones"
+ )
+
+ left = pd.DataFrame(
+ {"left_val": [1, 5, 10], "a": pd.Categorical(["a", "b", "c"])}
+ )
+ right = pd.DataFrame(
+ {
+ "right_val": [1, 2, 3, 6, 7],
+ "a": pd.Categorical(["a", "X", "c", "X", "b"]),
+ }
+ )
+
+ with pytest.raises(MergeError, match=msg):
+ merge_asof(left, right, on="a")
+
+ def test_merge_groupby_multiple_column_with_categorical_column(self):
+ # GH 16454
+ df = pd.DataFrame({"x": [0], "y": [0], "z": pd.Categorical([0])})
+ result = merge_asof(df, df, on="x", by=["y", "z"])
+ expected = pd.DataFrame({"x": [0], "y": [0], "z": pd.Categorical([0])})
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "func", [lambda x: x, to_datetime], ids=["numeric", "datetime"]
+ )
+ @pytest.mark.parametrize("side", ["left", "right"])
+ def test_merge_on_nans(self, func, side):
+ # GH 23189
+ msg = f"Merge keys contain null values on {side} side"
+ nulls = func([1.0, 5.0, np.nan])
+ non_nulls = func([1.0, 5.0, 10.0])
+ df_null = pd.DataFrame({"a": nulls, "left_val": ["a", "b", "c"]})
+ df = pd.DataFrame({"a": non_nulls, "right_val": [1, 6, 11]})
+
+ with pytest.raises(ValueError, match=msg):
+ if side == "left":
+ merge_asof(df_null, df, on="a")
+ else:
+ merge_asof(df, df_null, on="a")
+
+ def test_by_nullable(self, any_numeric_ea_dtype, using_infer_string):
+ # Note: this test passes if instead of using pd.array we use
+ # np.array([np.nan, 1]). Other than that, I (@jbrockmendel)
+ # have NO IDEA what the expected behavior is.
+ # TODO(GH#32306): may be relevant to the expected behavior here.
+
+ arr = pd.array([pd.NA, 0, 1], dtype=any_numeric_ea_dtype)
+ if arr.dtype.kind in ["i", "u"]:
+ max_val = np.iinfo(arr.dtype.numpy_dtype).max
+ else:
+ max_val = np.finfo(arr.dtype.numpy_dtype).max
+ # set value s.t. (at least for integer dtypes) arr._values_for_argsort
+ # is not an injection
+ arr[2] = max_val
+
+ left = pd.DataFrame(
+ {
+ "by_col1": arr,
+ "by_col2": ["HELLO", "To", "You"],
+ "on_col": [2, 4, 6],
+ "value": ["a", "c", "e"],
+ }
+ )
+ right = pd.DataFrame(
+ {
+ "by_col1": arr,
+ "by_col2": ["WORLD", "Wide", "Web"],
+ "on_col": [1, 2, 6],
+ "value": ["b", "d", "f"],
+ }
+ )
+
+ result = merge_asof(left, right, by=["by_col1", "by_col2"], on="on_col")
+ expected = pd.DataFrame(
+ {
+ "by_col1": arr,
+ "by_col2": ["HELLO", "To", "You"],
+ "on_col": [2, 4, 6],
+ "value_x": ["a", "c", "e"],
+ }
+ )
+ expected["value_y"] = np.array([np.nan, np.nan, np.nan], dtype=object)
+ if using_infer_string:
+ expected["value_y"] = expected["value_y"].astype("str")
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_by_col_tz_aware(self):
+ # GH 21184
+ left = pd.DataFrame(
+ {
+ "by_col": pd.DatetimeIndex(["2018-01-01"]).tz_localize("UTC"),
+ "on_col": [2],
+ "values": ["a"],
+ }
+ )
+ right = pd.DataFrame(
+ {
+ "by_col": pd.DatetimeIndex(["2018-01-01"]).tz_localize("UTC"),
+ "on_col": [1],
+ "values": ["b"],
+ }
+ )
+ result = merge_asof(left, right, by="by_col", on="on_col")
+ expected = pd.DataFrame(
+ [[pd.Timestamp("2018-01-01", tz="UTC"), 2, "a", "b"]],
+ columns=["by_col", "on_col", "values_x", "values_y"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_by_mixed_tz_aware(self, using_infer_string):
+ # GH 26649
+ left = pd.DataFrame(
+ {
+ "by_col1": pd.DatetimeIndex(["2018-01-01"]).tz_localize("UTC"),
+ "by_col2": ["HELLO"],
+ "on_col": [2],
+ "value": ["a"],
+ }
+ )
+ right = pd.DataFrame(
+ {
+ "by_col1": pd.DatetimeIndex(["2018-01-01"]).tz_localize("UTC"),
+ "by_col2": ["WORLD"],
+ "on_col": [1],
+ "value": ["b"],
+ }
+ )
+ result = merge_asof(left, right, by=["by_col1", "by_col2"], on="on_col")
+ expected = pd.DataFrame(
+ [[pd.Timestamp("2018-01-01", tz="UTC"), "HELLO", 2, "a"]],
+ columns=["by_col1", "by_col2", "on_col", "value_x"],
+ )
+ expected["value_y"] = np.array([np.nan], dtype=object)
+ if using_infer_string:
+ expected["value_y"] = expected["value_y"].astype("str")
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["float64", "int16", "m8[ns]", "M8[us]"])
+ def test_by_dtype(self, dtype):
+ # GH 55453, GH 22794
+ left = pd.DataFrame(
+ {
+ "by_col": np.array([1], dtype=dtype),
+ "on_col": [2],
+ "value": ["a"],
+ }
+ )
+ right = pd.DataFrame(
+ {
+ "by_col": np.array([1], dtype=dtype),
+ "on_col": [1],
+ "value": ["b"],
+ }
+ )
+ result = merge_asof(left, right, by="by_col", on="on_col")
+ expected = pd.DataFrame(
+ {
+ "by_col": np.array([1], dtype=dtype),
+ "on_col": [2],
+ "value_x": ["a"],
+ "value_y": ["b"],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_timedelta_tolerance_nearest(self, unit):
+ # GH 27642
+ if unit == "s":
+ pytest.skip(
+ "This test is invalid with unit='s' because that would "
+ "round left['time']"
+ )
+
+ left = pd.DataFrame(
+ list(zip([0, 5, 10, 15, 20, 25], [0, 1, 2, 3, 4, 5], strict=True)),
+ columns=["time", "left"],
+ )
+
+ left["time"] = pd.to_timedelta(left["time"], "ms").astype(f"m8[{unit}]")
+
+ right = pd.DataFrame(
+ list(zip([0, 3, 9, 12, 15, 18], [0, 1, 2, 3, 4, 5], strict=True)),
+ columns=["time", "right"],
+ )
+
+ right["time"] = pd.to_timedelta(right["time"], "ms").astype(f"m8[{unit}]")
+
+ expected = pd.DataFrame(
+ list(
+ zip(
+ [0, 5, 10, 15, 20, 25],
+ [0, 1, 2, 3, 4, 5],
+ [0, np.nan, 2, 4, np.nan, np.nan],
+ strict=True,
+ )
+ ),
+ columns=["time", "left", "right"],
+ )
+
+ expected["time"] = pd.to_timedelta(expected["time"], "ms").astype(f"m8[{unit}]")
+
+ result = merge_asof(
+ left, right, on="time", tolerance=Timedelta("1ms"), direction="nearest"
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_int_type_tolerance(self, any_int_dtype):
+ # GH #28870
+
+ left = pd.DataFrame({"a": [0, 10, 20], "left_val": [1, 2, 3]})
+ right = pd.DataFrame({"a": [5, 15, 25], "right_val": [1, 2, 3]})
+ left["a"] = left["a"].astype(any_int_dtype)
+ right["a"] = right["a"].astype(any_int_dtype)
+
+ expected = pd.DataFrame(
+ {"a": [0, 10, 20], "left_val": [1, 2, 3], "right_val": [np.nan, 1.0, 2.0]}
+ )
+ expected["a"] = expected["a"].astype(any_int_dtype)
+
+ result = merge_asof(left, right, on="a", tolerance=10)
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_index_column_tz(self):
+ # GH 29864
+ index = pd.date_range("2019-10-01", freq="30min", periods=5, tz="UTC")
+ left = pd.DataFrame([0.9, 0.8, 0.7, 0.6], columns=["xyz"], index=index[1:])
+ right = pd.DataFrame({"from_date": index, "abc": [2.46] * 4 + [2.19]})
+ result = merge_asof(
+ left=left, right=right, left_index=True, right_on=["from_date"]
+ )
+ expected = pd.DataFrame(
+ {
+ "xyz": [0.9, 0.8, 0.7, 0.6],
+ "from_date": index[1:],
+ "abc": [2.46] * 3 + [2.19],
+ },
+ index=pd.date_range(
+ "2019-10-01 00:30:00", freq="30min", periods=4, tz="UTC"
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = merge_asof(
+ left=right, right=left, right_index=True, left_on=["from_date"]
+ )
+ expected = pd.DataFrame(
+ {
+ "from_date": index,
+ "abc": [2.46] * 4 + [2.19],
+ "xyz": [np.nan, 0.9, 0.8, 0.7, 0.6],
+ },
+ index=Index([0, 1, 2, 3, 4]),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_left_index_right_index_tolerance(self, unit):
+ # https://github.com/pandas-dev/pandas/issues/35558
+ if unit == "s":
+ pytest.skip(
+ "This test is invalid with unit='s' because that would round dr1"
+ )
+
+ dr1 = pd.date_range(
+ start="1/1/2020", end="1/20/2020", freq="2D", unit=unit
+ ) + Timedelta(seconds=0.4).as_unit(unit)
+ dr2 = pd.date_range(start="1/1/2020", end="2/1/2020", unit=unit)
+
+ df1 = pd.DataFrame({"val1": "foo"}, index=pd.DatetimeIndex(dr1))
+ df2 = pd.DataFrame({"val2": "bar"}, index=pd.DatetimeIndex(dr2))
+
+ expected = pd.DataFrame(
+ {"val1": "foo", "val2": "bar"}, index=pd.DatetimeIndex(dr1)
+ )
+ result = merge_asof(
+ df1,
+ df2,
+ left_index=True,
+ right_index=True,
+ tolerance=Timedelta(seconds=0.5),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))]
+)
+@pytest.mark.parametrize(
+ "kwargs", [{"on": "x"}, {"left_index": True, "right_index": True}]
+)
+@pytest.mark.parametrize(
+ "data",
+ [["2019-06-01 00:09:12", "2019-06-01 00:10:29"], [1.0, "2019-06-01 00:10:29"]],
+)
+def test_merge_asof_non_numerical_dtype(kwargs, data, infer_string):
+ # GH#29130
+ with option_context("future.infer_string", infer_string):
+ left = pd.DataFrame({"x": data}, index=data)
+ right = pd.DataFrame({"x": data}, index=data)
+ with pytest.raises(
+ MergeError,
+ match=r"Incompatible merge dtype, .*, both sides must have numeric dtype",
+ ):
+ merge_asof(left, right, **kwargs)
+
+
+def test_merge_asof_non_numerical_dtype_object():
+ # GH#29130
+ left = pd.DataFrame({"a": ["12", "13", "15"], "left_val1": ["a", "b", "c"]})
+ right = pd.DataFrame({"a": ["a", "b", "c"], "left_val": ["d", "e", "f"]})
+ with pytest.raises(
+ MergeError,
+ match=r"Incompatible merge dtype, .*, both sides must have numeric dtype",
+ ):
+ merge_asof(
+ left,
+ right,
+ left_on="left_val1",
+ right_on="a",
+ left_by="a",
+ right_by="left_val",
+ )
+
+
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"right_index": True, "left_index": True},
+ {"left_on": "left_time", "right_index": True},
+ {"left_index": True, "right_on": "right"},
+ ],
+)
+def test_merge_asof_index_behavior(kwargs):
+ # GH 33463
+ index = Index([1, 5, 10], name="test")
+ left = pd.DataFrame({"left": ["a", "b", "c"], "left_time": [1, 4, 10]}, index=index)
+ right = pd.DataFrame({"right": [1, 2, 3, 6, 7]}, index=[1, 2, 3, 6, 7])
+ result = merge_asof(left, right, **kwargs)
+
+ expected = pd.DataFrame(
+ {"left": ["a", "b", "c"], "left_time": [1, 4, 10], "right": [1, 3, 7]},
+ index=index,
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_asof_numeric_column_in_index():
+ # GH#34488
+ left = pd.DataFrame({"b": [10, 11, 12]}, index=Index([1, 2, 3], name="a"))
+ right = pd.DataFrame({"c": [20, 21, 22]}, index=Index([0, 2, 3], name="a"))
+
+ result = merge_asof(left, right, left_on="a", right_on="a")
+ expected = pd.DataFrame({"a": [1, 2, 3], "b": [10, 11, 12], "c": [20, 21, 22]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_asof_numeric_column_in_multiindex():
+ # GH#34488
+ left = pd.DataFrame(
+ {"b": [10, 11, 12]},
+ index=pd.MultiIndex.from_arrays([[1, 2, 3], ["a", "b", "c"]], names=["a", "z"]),
+ )
+ right = pd.DataFrame(
+ {"c": [20, 21, 22]},
+ index=pd.MultiIndex.from_arrays([[1, 2, 3], ["x", "y", "z"]], names=["a", "y"]),
+ )
+
+ result = merge_asof(left, right, left_on="a", right_on="a")
+ expected = pd.DataFrame({"a": [1, 2, 3], "b": [10, 11, 12], "c": [20, 21, 22]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_asof_numeri_column_in_index_object_dtype():
+ # GH#34488
+ left = pd.DataFrame({"b": [10, 11, 12]}, index=Index(["1", "2", "3"], name="a"))
+ right = pd.DataFrame({"c": [20, 21, 22]}, index=Index(["m", "n", "o"], name="a"))
+
+ with pytest.raises(
+ MergeError,
+ match=r"Incompatible merge dtype, .*, both sides must have numeric dtype",
+ ):
+ merge_asof(left, right, left_on="a", right_on="a")
+
+ left = left.reset_index().set_index(["a", "b"])
+ right = right.reset_index().set_index(["a", "c"])
+
+ with pytest.raises(
+ MergeError,
+ match=r"Incompatible merge dtype, .*, both sides must have numeric dtype",
+ ):
+ merge_asof(left, right, left_on="a", right_on="a")
+
+
+def test_merge_asof_array_as_on(unit):
+ # GH#42844
+ dti = pd.DatetimeIndex(
+ ["2021/01/01 00:37", "2021/01/01 01:40"], dtype=f"M8[{unit}]"
+ )
+ right = pd.DataFrame(
+ {
+ "a": [2, 6],
+ "ts": dti,
+ }
+ )
+ ts_merge = pd.date_range(
+ start=pd.Timestamp("2021/01/01 00:00"), periods=3, freq="1h", unit=unit
+ )
+ left = pd.DataFrame({"b": [4, 8, 7]})
+ result = merge_asof(
+ left,
+ right,
+ left_on=ts_merge,
+ right_on="ts",
+ allow_exact_matches=False,
+ direction="backward",
+ )
+ expected = pd.DataFrame({"b": [4, 8, 7], "a": [np.nan, 2, 6], "ts": ts_merge})
+ tm.assert_frame_equal(result, expected)
+
+ result = merge_asof(
+ right,
+ left,
+ left_on="ts",
+ right_on=ts_merge,
+ allow_exact_matches=False,
+ direction="backward",
+ )
+ expected = pd.DataFrame(
+ {
+ "a": [2, 6],
+ "ts": dti,
+ "b": [4, 8],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_asof_raise_for_duplicate_columns():
+ # GH#50102
+ left = pd.DataFrame([[1, 2, "a"]], columns=["a", "a", "left_val"])
+ right = pd.DataFrame([[1, 1, 1]], columns=["a", "a", "right_val"])
+
+ with pytest.raises(ValueError, match="column label 'a'"):
+ merge_asof(left, right, on="a")
+
+ with pytest.raises(ValueError, match="column label 'a'"):
+ merge_asof(left, right, left_on="a", right_on="right_val")
+
+ with pytest.raises(ValueError, match="column label 'a'"):
+ merge_asof(left, right, left_on="left_val", right_on="a")
+
+
+@pytest.mark.parametrize(
+ "dtype",
+ [
+ "Int64",
+ pytest.param("int64[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ pytest.param("timestamp[s][pyarrow]", marks=td.skip_if_no("pyarrow")),
+ ],
+)
+def test_merge_asof_extension_dtype(dtype):
+ # GH 52904
+ left = pd.DataFrame(
+ {
+ "join_col": [1, 3, 5],
+ "left_val": [1, 2, 3],
+ }
+ )
+ right = pd.DataFrame(
+ {
+ "join_col": [2, 3, 4],
+ "right_val": [1, 2, 3],
+ }
+ )
+ left = left.astype({"join_col": dtype})
+ right = right.astype({"join_col": dtype})
+ result = merge_asof(left, right, on="join_col")
+ expected = pd.DataFrame(
+ {
+ "join_col": [1, 3, 5],
+ "left_val": [1, 2, 3],
+ "right_val": [np.nan, 2.0, 3.0],
+ }
+ )
+ expected = expected.astype({"join_col": dtype})
+ tm.assert_frame_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_merge_asof_pyarrow_td_tolerance():
+ # GH 56486
+ ser = pd.Series(
+ [datetime.datetime(2023, 1, 1)], dtype="timestamp[us, UTC][pyarrow]"
+ )
+ df = pd.DataFrame(
+ {
+ "timestamp": ser,
+ "value": [1],
+ }
+ )
+ result = merge_asof(df, df, on="timestamp", tolerance=Timedelta("1s"))
+ expected = pd.DataFrame(
+ {
+ "timestamp": ser,
+ "value_x": [1],
+ "value_y": [1],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_asof_read_only_ndarray():
+ # GH 53513
+ left = pd.Series([2], index=[2], name="left")
+ right = pd.Series([1], index=[1], name="right")
+ # set to read-only
+ left.index.values.flags.writeable = False
+ right.index.values.flags.writeable = False
+ result = merge_asof(left, right, left_index=True, right_index=True)
+ expected = pd.DataFrame({"left": [2], "right": [1]}, index=[2])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_asof_multiby_with_categorical():
+ # GH 43541
+ left = pd.DataFrame(
+ {
+ "c1": pd.Categorical(["a", "a", "b", "b"], categories=["a", "b"]),
+ "c2": ["x"] * 4,
+ "t": [1] * 4,
+ "v": range(4),
+ }
+ )
+ right = pd.DataFrame(
+ {
+ "c1": pd.Categorical(["b", "b"], categories=["b", "a"]),
+ "c2": ["x"] * 2,
+ "t": [1, 2],
+ "v": range(2),
+ }
+ )
+ result = merge_asof(
+ left,
+ right,
+ by=["c1", "c2"],
+ on="t",
+ direction="forward",
+ suffixes=["_left", "_right"],
+ )
+ expected = pd.DataFrame(
+ {
+ "c1": pd.Categorical(["a", "a", "b", "b"], categories=["a", "b"]),
+ "c2": ["x"] * 4,
+ "t": [1] * 4,
+ "v_left": range(4),
+ "v_right": [np.nan, np.nan, 0.0, 0.0],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_cross.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_cross.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ab80cf0e0823e2f953c6bcdf0aaa56ed5db028e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_cross.py
@@ -0,0 +1,109 @@
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+from pandas.core.reshape.merge import (
+ MergeError,
+ merge,
+)
+
+
+@pytest.mark.parametrize(
+ ("input_col", "output_cols"), [("b", ["a", "b"]), ("a", ["a_x", "a_y"])]
+)
+def test_merge_cross(input_col, output_cols):
+ # GH#5401
+ left = DataFrame({"a": [1, 3]})
+ right = DataFrame({input_col: [3, 4]})
+ left_copy = left.copy()
+ right_copy = right.copy()
+ result = merge(left, right, how="cross")
+ expected = DataFrame({output_cols[0]: [1, 1, 3, 3], output_cols[1]: [3, 4, 3, 4]})
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(left, left_copy)
+ tm.assert_frame_equal(right, right_copy)
+
+
+@pytest.mark.parametrize(
+ "kwargs",
+ [
+ {"left_index": True},
+ {"right_index": True},
+ {"on": "a"},
+ {"left_on": "a"},
+ {"right_on": "b"},
+ ],
+)
+def test_merge_cross_error_reporting(kwargs):
+ # GH#5401
+ left = DataFrame({"a": [1, 3]})
+ right = DataFrame({"b": [3, 4]})
+ msg = (
+ "Can not pass on, right_on, left_on or set right_index=True or left_index=True"
+ )
+ with pytest.raises(MergeError, match=msg):
+ merge(left, right, how="cross", **kwargs)
+
+
+def test_merge_cross_mixed_dtypes():
+ # GH#5401
+ left = DataFrame(["a", "b", "c"], columns=["A"])
+ right = DataFrame(range(2), columns=["B"])
+ result = merge(left, right, how="cross")
+ expected = DataFrame({"A": ["a", "a", "b", "b", "c", "c"], "B": [0, 1, 0, 1, 0, 1]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_cross_more_than_one_column():
+ # GH#5401
+ left = DataFrame({"A": list("ab"), "B": [2, 1]})
+ right = DataFrame({"C": range(2), "D": range(4, 6)})
+ result = merge(left, right, how="cross")
+ expected = DataFrame(
+ {
+ "A": ["a", "a", "b", "b"],
+ "B": [2, 2, 1, 1],
+ "C": [0, 1, 0, 1],
+ "D": [4, 5, 4, 5],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_merge_cross_null_values(nulls_fixture):
+ # GH#5401
+ left = DataFrame({"a": [1, nulls_fixture]})
+ right = DataFrame({"b": ["a", "b"], "c": [1.0, 2.0]})
+ result = merge(left, right, how="cross")
+ expected = DataFrame(
+ {
+ "a": [1, 1, nulls_fixture, nulls_fixture],
+ "b": ["a", "b", "a", "b"],
+ "c": [1.0, 2.0, 1.0, 2.0],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_join_cross_error_reporting():
+ # GH#5401
+ left = DataFrame({"a": [1, 3]})
+ right = DataFrame({"a": [3, 4]})
+ msg = (
+ "Can not pass on, right_on, left_on or set right_index=True or left_index=True"
+ )
+ with pytest.raises(MergeError, match=msg):
+ left.join(right, how="cross", on="a")
+
+
+def test_merge_cross_series():
+ # GH#54055
+ ls = Series([1, 2, 3, 4], index=[1, 2, 3, 4], name="left")
+ rs = Series([3, 4, 5, 6], index=[3, 4, 5, 6], name="right")
+ res = merge(ls, rs, how="cross")
+
+ expected = merge(ls.to_frame(), rs.to_frame(), how="cross")
+ tm.assert_frame_equal(res, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_index_as_string.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_index_as_string.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4271d4face4fb4e90477196cec7919845f37813
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_index_as_string.py
@@ -0,0 +1,186 @@
+import numpy as np
+import pytest
+
+from pandas import DataFrame
+import pandas._testing as tm
+
+
+@pytest.fixture
+def df1():
+ return DataFrame(
+ {
+ "outer": [1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4],
+ "inner": [1, 2, 3, 1, 2, 3, 4, 1, 2, 1, 2],
+ "v1": np.linspace(0, 1, 11),
+ }
+ )
+
+
+@pytest.fixture
+def df2():
+ return DataFrame(
+ {
+ "outer": [1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3],
+ "inner": [1, 2, 2, 3, 3, 4, 2, 3, 1, 1, 2, 3],
+ "v2": np.linspace(10, 11, 12),
+ }
+ )
+
+
+@pytest.fixture(params=[[], ["outer"], ["outer", "inner"]])
+def left_df(request, df1):
+ """Construct left test DataFrame with specified levels
+ (any of 'outer', 'inner', and 'v1')
+ """
+ levels = request.param
+ if levels:
+ df1 = df1.set_index(levels)
+
+ return df1
+
+
+@pytest.fixture(params=[[], ["outer"], ["outer", "inner"]])
+def right_df(request, df2):
+ """Construct right test DataFrame with specified levels
+ (any of 'outer', 'inner', and 'v2')
+ """
+ levels = request.param
+
+ if levels:
+ df2 = df2.set_index(levels)
+
+ return df2
+
+
+def compute_expected(df_left, df_right, on=None, left_on=None, right_on=None, how=None):
+ """
+ Compute the expected merge result for the test case.
+
+ This method computes the expected result of merging two DataFrames on
+ a combination of their columns and index levels. It does so by
+ explicitly dropping/resetting their named index levels, performing a
+ merge on their columns, and then finally restoring the appropriate
+ index in the result.
+
+ Parameters
+ ----------
+ df_left : DataFrame
+ The left DataFrame (may have zero or more named index levels)
+ df_right : DataFrame
+ The right DataFrame (may have zero or more named index levels)
+ on : list of str
+ The on parameter to the merge operation
+ left_on : list of str
+ The left_on parameter to the merge operation
+ right_on : list of str
+ The right_on parameter to the merge operation
+ how : str
+ The how parameter to the merge operation
+
+ Returns
+ -------
+ DataFrame
+ The expected merge result
+ """
+ # Handle on param if specified
+ if on is not None:
+ left_on, right_on = on, on
+
+ # Compute input named index levels
+ left_levels = [n for n in df_left.index.names if n is not None]
+ right_levels = [n for n in df_right.index.names if n is not None]
+
+ # Compute output named index levels
+ output_levels = [i for i in left_on if i in right_levels and i in left_levels]
+
+ # Drop index levels that aren't involved in the merge
+ drop_left = [n for n in left_levels if n not in left_on]
+ if drop_left:
+ df_left = df_left.reset_index(drop_left, drop=True)
+
+ drop_right = [n for n in right_levels if n not in right_on]
+ if drop_right:
+ df_right = df_right.reset_index(drop_right, drop=True)
+
+ # Convert remaining index levels to columns
+ reset_left = [n for n in left_levels if n in left_on]
+ if reset_left:
+ df_left = df_left.reset_index(level=reset_left)
+
+ reset_right = [n for n in right_levels if n in right_on]
+ if reset_right:
+ df_right = df_right.reset_index(level=reset_right)
+
+ # Perform merge
+ expected = df_left.merge(df_right, left_on=left_on, right_on=right_on, how=how)
+
+ # Restore index levels
+ if output_levels:
+ expected = expected.set_index(output_levels)
+
+ return expected
+
+
+@pytest.mark.parametrize(
+ "on,how",
+ [
+ (["outer"], "inner"),
+ (["inner"], "left"),
+ (["outer", "inner"], "right"),
+ (["inner", "outer"], "outer"),
+ ],
+)
+def test_merge_indexes_and_columns_on(left_df, right_df, on, how):
+ # Construct expected result
+ expected = compute_expected(left_df, right_df, on=on, how=how)
+
+ # Perform merge
+ result = left_df.merge(right_df, on=on, how=how)
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+
+@pytest.mark.parametrize(
+ "left_on,right_on,how",
+ [
+ (["outer"], ["outer"], "inner"),
+ (["inner"], ["inner"], "right"),
+ (["outer", "inner"], ["outer", "inner"], "left"),
+ (["inner", "outer"], ["inner", "outer"], "outer"),
+ ],
+)
+def test_merge_indexes_and_columns_lefton_righton(
+ left_df, right_df, left_on, right_on, how
+):
+ # Construct expected result
+ expected = compute_expected(
+ left_df, right_df, left_on=left_on, right_on=right_on, how=how
+ )
+
+ # Perform merge
+ result = left_df.merge(right_df, left_on=left_on, right_on=right_on, how=how)
+ tm.assert_frame_equal(result, expected, check_like=True)
+
+
+@pytest.mark.parametrize("left_index", ["inner", ["inner", "outer"]])
+def test_join_indexes_and_columns_on(df1, df2, left_index, join_type):
+ # Construct left_df
+ left_df = df1.set_index(left_index)
+
+ # Construct right_df
+ right_df = df2.set_index(["outer", "inner"])
+
+ # Result
+ expected = (
+ left_df.reset_index()
+ .join(
+ right_df, on=["outer", "inner"], how=join_type, lsuffix="_x", rsuffix="_y"
+ )
+ .set_index(left_index)
+ )
+
+ # Perform join
+ result = left_df.join(
+ right_df, on=["outer", "inner"], how=join_type, lsuffix="_x", rsuffix="_y"
+ )
+
+ tm.assert_frame_equal(result, expected, check_like=True)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_ordered.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_ordered.py
new file mode 100644
index 0000000000000000000000000000000000000000..7deecf6bbf4a41a36d7fd3db11cb32ab509926b5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_merge_ordered.py
@@ -0,0 +1,241 @@
+import re
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ merge_ordered,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def left():
+ return DataFrame({"key": ["a", "c", "e"], "lvalue": [1, 2.0, 3]})
+
+
+@pytest.fixture
+def right():
+ return DataFrame({"key": ["b", "c", "d", "f"], "rvalue": [1, 2, 3.0, 4]})
+
+
+class TestMergeOrdered:
+ def test_basic(self, left, right):
+ result = merge_ordered(left, right, on="key")
+ expected = DataFrame(
+ {
+ "key": ["a", "b", "c", "d", "e", "f"],
+ "lvalue": [1, np.nan, 2, np.nan, 3, np.nan],
+ "rvalue": [np.nan, 1, 2, 3, np.nan, 4],
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_ffill(self, left, right):
+ result = merge_ordered(left, right, on="key", fill_method="ffill")
+ expected = DataFrame(
+ {
+ "key": ["a", "b", "c", "d", "e", "f"],
+ "lvalue": [1.0, 1, 2, 2, 3, 3.0],
+ "rvalue": [np.nan, 1, 2, 3, 3, 4],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_multigroup(self, left, right):
+ left = pd.concat([left, left], ignore_index=True)
+
+ left["group"] = ["a"] * 3 + ["b"] * 3
+
+ result = merge_ordered(
+ left, right, on="key", left_by="group", fill_method="ffill"
+ )
+ expected = DataFrame(
+ {
+ "key": ["a", "b", "c", "d", "e", "f"] * 2,
+ "lvalue": [1.0, 1, 2, 2, 3, 3.0] * 2,
+ "rvalue": [np.nan, 1, 2, 3, 3, 4] * 2,
+ }
+ )
+ expected["group"] = ["a"] * 6 + ["b"] * 6
+
+ tm.assert_frame_equal(result, expected.loc[:, result.columns])
+
+ result2 = merge_ordered(
+ right, left, on="key", right_by="group", fill_method="ffill"
+ )
+ tm.assert_frame_equal(result, result2.loc[:, result.columns])
+
+ result = merge_ordered(left, right, on="key", left_by="group")
+ assert result["group"].notna().all()
+
+ @pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
+ )
+ def test_merge_type(self, left, right):
+ class NotADataFrame(DataFrame):
+ @property
+ def _constructor(self):
+ return NotADataFrame
+
+ nad = NotADataFrame(left)
+ result = nad.merge(right, on="key")
+
+ assert isinstance(result, NotADataFrame)
+
+ @pytest.mark.parametrize(
+ "df_seq, pattern",
+ [
+ ((), "[Nn]o objects"),
+ ([], "[Nn]o objects"),
+ ({}, "[Nn]o objects"),
+ ([None], "objects.*None"),
+ ([None, None], "objects.*None"),
+ ],
+ )
+ def test_empty_sequence_concat(self, df_seq, pattern):
+ # GH 9157
+ with pytest.raises(ValueError, match=pattern):
+ pd.concat(df_seq)
+
+ @pytest.mark.parametrize(
+ "arg", [[DataFrame()], [None, DataFrame()], [DataFrame(), None]]
+ )
+ def test_empty_sequence_concat_ok(self, arg):
+ pd.concat(arg)
+
+ def test_doc_example(self):
+ left = DataFrame(
+ {
+ "group": list("aaabbb"),
+ "key": ["a", "c", "e", "a", "c", "e"],
+ "lvalue": [1, 2, 3] * 2,
+ }
+ )
+
+ right = DataFrame({"key": ["b", "c", "d"], "rvalue": [1, 2, 3]})
+
+ result = merge_ordered(left, right, fill_method="ffill", left_by="group")
+
+ expected = DataFrame(
+ {
+ "group": list("aaaaabbbbb"),
+ "key": ["a", "b", "c", "d", "e"] * 2,
+ "lvalue": [1, 1, 2, 2, 3] * 2,
+ "rvalue": [np.nan, 1, 2, 3, 3] * 2,
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "left, right, on, left_by, right_by, expected",
+ [
+ (
+ {"G": ["g", "g"], "H": ["h", "h"], "T": [1, 3]},
+ {"T": [2], "E": [1]},
+ ["T"],
+ ["G", "H"],
+ None,
+ {
+ "G": ["g"] * 3,
+ "H": ["h"] * 3,
+ "T": [1, 2, 3],
+ "E": [np.nan, 1.0, np.nan],
+ },
+ ),
+ (
+ {"G": ["g", "g"], "H": ["h", "h"], "T": [1, 3]},
+ {"T": [2], "E": [1]},
+ "T",
+ ["G", "H"],
+ None,
+ {
+ "G": ["g"] * 3,
+ "H": ["h"] * 3,
+ "T": [1, 2, 3],
+ "E": [np.nan, 1.0, np.nan],
+ },
+ ),
+ (
+ {"T": [2], "E": [1]},
+ {"G": ["g", "g"], "H": ["h", "h"], "T": [1, 3]},
+ ["T"],
+ None,
+ ["G", "H"],
+ {
+ "T": [1, 2, 3],
+ "E": [np.nan, 1.0, np.nan],
+ "G": ["g"] * 3,
+ "H": ["h"] * 3,
+ },
+ ),
+ ],
+ )
+ def test_list_type_by(self, left, right, on, left_by, right_by, expected):
+ # GH 35269
+ left = DataFrame(left)
+ right = DataFrame(right)
+ result = merge_ordered(
+ left=left,
+ right=right,
+ on=on,
+ left_by=left_by,
+ right_by=right_by,
+ )
+ expected = DataFrame(expected)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_left_by_length_equals_to_right_shape0(self):
+ # GH 38166
+ left = DataFrame([["g", "h", 1], ["g", "h", 3]], columns=list("GHE"))
+ right = DataFrame([[2, 1]], columns=list("ET"))
+ result = merge_ordered(left, right, on="E", left_by=["G", "H"])
+ expected = DataFrame(
+ {"G": ["g"] * 3, "H": ["h"] * 3, "E": [1, 2, 3], "T": [np.nan, 1.0, np.nan]}
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_elements_not_in_by_but_in_df(self):
+ # GH 38167
+ left = DataFrame([["g", "h", 1], ["g", "h", 3]], columns=list("GHE"))
+ right = DataFrame([[2, 1]], columns=list("ET"))
+ msg = r"\{'h'\} not found in left columns"
+ with pytest.raises(KeyError, match=msg):
+ merge_ordered(left, right, on="E", left_by=["G", "h"])
+
+ @pytest.mark.parametrize("invalid_method", ["linear", "carrot"])
+ def test_ffill_validate_fill_method(self, left, right, invalid_method):
+ # GH 55884
+ with pytest.raises(
+ ValueError, match=re.escape("fill_method must be 'ffill' or None")
+ ):
+ merge_ordered(left, right, on="key", fill_method=invalid_method)
+
+ def test_ffill_left_merge(self):
+ # GH 57010
+ df1 = DataFrame(
+ {
+ "key": ["a", "c", "e", "a", "c", "e"],
+ "lvalue": [1, 2, 3, 1, 2, 3],
+ "group": ["a", "a", "a", "b", "b", "b"],
+ }
+ )
+ df2 = DataFrame({"key": ["b", "c", "d"], "rvalue": [1, 2, 3]})
+ result = merge_ordered(
+ df1, df2, fill_method="ffill", left_by="group", how="left"
+ )
+ expected = DataFrame(
+ {
+ "key": ["a", "c", "e", "a", "c", "e"],
+ "lvalue": [1, 2, 3, 1, 2, 3],
+ "group": ["a", "a", "a", "b", "b", "b"],
+ "rvalue": [np.nan, 2.0, 2.0, np.nan, 2.0, 2.0],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_multi.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_multi.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ae2fffa042050836473eba8c621817c799a9bb5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/merge/test_multi.py
@@ -0,0 +1,930 @@
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ RangeIndex,
+ Series,
+ Timestamp,
+ option_context,
+)
+import pandas._testing as tm
+from pandas.core.reshape.concat import concat
+from pandas.core.reshape.merge import merge
+
+
+@pytest.fixture
+def left():
+ """left dataframe (not multi-indexed) for multi-index join tests"""
+ # a little relevant example with NAs
+ key1 = ["bar", "bar", "bar", "foo", "foo", "baz", "baz", "qux", "qux", "snap"]
+ key2 = ["two", "one", "three", "one", "two", "one", "two", "two", "three", "one"]
+
+ data = np.random.default_rng(2).standard_normal(len(key1))
+ return DataFrame({"key1": key1, "key2": key2, "data": data})
+
+
+@pytest.fixture
+def right(multiindex_dataframe_random_data):
+ """right dataframe (multi-indexed) for multi-index join tests"""
+ df = multiindex_dataframe_random_data
+ df.index.names = ["key1", "key2"]
+
+ df.columns = ["j_one", "j_two", "j_three"]
+ return df
+
+
+@pytest.fixture
+def left_multi():
+ return DataFrame(
+ {
+ "Origin": ["A", "A", "B", "B", "C"],
+ "Destination": ["A", "B", "A", "C", "A"],
+ "Period": ["AM", "AM", "IP", "AM", "OP"],
+ "TripPurp": ["hbw", "nhb", "hbo", "nhb", "hbw"],
+ "Trips": [1987, 3647, 2470, 4296, 4444],
+ },
+ columns=["Origin", "Destination", "Period", "TripPurp", "Trips"],
+ ).set_index(["Origin", "Destination", "Period", "TripPurp"])
+
+
+@pytest.fixture
+def right_multi():
+ return DataFrame(
+ {
+ "Origin": ["A", "A", "B", "B", "C", "C", "E"],
+ "Destination": ["A", "B", "A", "B", "A", "B", "F"],
+ "Period": ["AM", "AM", "IP", "AM", "OP", "IP", "AM"],
+ "LinkType": ["a", "b", "c", "b", "a", "b", "a"],
+ "Distance": [100, 80, 90, 80, 75, 35, 55],
+ },
+ columns=["Origin", "Destination", "Period", "LinkType", "Distance"],
+ ).set_index(["Origin", "Destination", "Period", "LinkType"])
+
+
+@pytest.fixture
+def on_cols_multi():
+ return ["Origin", "Destination", "Period"]
+
+
+class TestMergeMulti:
+ def test_merge_on_multikey(self, left, right, join_type):
+ on_cols = ["key1", "key2"]
+ result = left.join(right, on=on_cols, how=join_type).reset_index(drop=True)
+
+ expected = merge(left, right.reset_index(), on=on_cols, how=join_type)
+
+ tm.assert_frame_equal(result, expected)
+
+ result = left.join(right, on=on_cols, how=join_type, sort=True).reset_index(
+ drop=True
+ )
+
+ expected = merge(
+ left, right.reset_index(), on=on_cols, how=join_type, sort=True
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))]
+ )
+ def test_left_join_multi_index(self, sort, infer_string):
+ with option_context("future.infer_string", infer_string):
+ icols = ["1st", "2nd", "3rd"]
+
+ def bind_cols(df):
+ iord = lambda a: 0 if a != a else ord(a)
+ f = lambda ts: ts.map(iord) - ord("a")
+ return f(df["1st"]) + f(df["3rd"]) * 1e2 + df["2nd"].fillna(0) * 10
+
+ def run_asserts(left, right, sort):
+ res = left.join(right, on=icols, how="left", sort=sort)
+
+ assert len(left) < len(res) + 1
+ assert not res["4th"].isna().any()
+ assert not res["5th"].isna().any()
+
+ tm.assert_series_equal(res["4th"], -res["5th"], check_names=False)
+ result = bind_cols(res.iloc[:, :-2])
+ tm.assert_series_equal(res["4th"], result, check_names=False)
+ assert result.name is None
+
+ if sort:
+ tm.assert_frame_equal(res, res.sort_values(icols, kind="mergesort"))
+
+ out = merge(left, right.reset_index(), on=icols, sort=sort, how="left")
+
+ res.index = RangeIndex(len(res))
+ tm.assert_frame_equal(out, res)
+
+ lc = list(map(chr, np.arange(ord("a"), ord("z") + 1)))
+ left = DataFrame(
+ np.random.default_rng(2).choice(lc, (50, 2)), columns=["1st", "3rd"]
+ )
+ # Explicit cast to float to avoid implicit cast when setting nan
+ left.insert(
+ 1,
+ "2nd",
+ np.random.default_rng(2).integers(0, 10, len(left)).astype("float"),
+ )
+ right = left.sample(frac=1, random_state=np.random.default_rng(2))
+
+ left["4th"] = bind_cols(left)
+ right["5th"] = -bind_cols(right)
+ right.set_index(icols, inplace=True)
+
+ run_asserts(left, right, sort)
+
+ # inject some nulls
+ left.loc[1::4, "1st"] = np.nan
+ left.loc[2::5, "2nd"] = np.nan
+ left.loc[3::6, "3rd"] = np.nan
+ left["4th"] = bind_cols(left)
+
+ i = np.random.default_rng(2).permutation(len(left))
+ right = left.iloc[i, :-1]
+ right["5th"] = -bind_cols(right)
+ right.set_index(icols, inplace=True)
+
+ run_asserts(left, right, sort)
+
+ def test_merge_right_vs_left(self, left, right, sort):
+ # compare left vs right merge with multikey
+ on_cols = ["key1", "key2"]
+ merged_left_right = left.merge(
+ right, left_on=on_cols, right_index=True, how="left", sort=sort
+ )
+
+ merge_right_left = right.merge(
+ left, right_on=on_cols, left_index=True, how="right", sort=sort
+ )
+
+ # Reorder columns
+ merge_right_left = merge_right_left[merged_left_right.columns]
+
+ tm.assert_frame_equal(merged_left_right, merge_right_left)
+
+ def test_merge_multiple_cols_with_mixed_cols_index(self):
+ # GH29522
+ s = Series(
+ range(6),
+ MultiIndex.from_product([["A", "B"], [1, 2, 3]], names=["lev1", "lev2"]),
+ name="Amount",
+ )
+ df = DataFrame({"lev1": list("AAABBB"), "lev2": [1, 2, 3, 1, 2, 3], "col": 0})
+ result = merge(df, s.reset_index(), on=["lev1", "lev2"])
+ expected = DataFrame(
+ {
+ "lev1": list("AAABBB"),
+ "lev2": [1, 2, 3, 1, 2, 3],
+ "col": [0] * 6,
+ "Amount": range(6),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_compress_group_combinations(self):
+ # ~ 40000000 possible unique groups
+ key1 = [str(i) for i in range(10000)]
+ key1 = np.tile(key1, 2)
+ key2 = key1[::-1]
+
+ df = DataFrame(
+ {
+ "key1": key1,
+ "key2": key2,
+ "value1": np.random.default_rng(2).standard_normal(20000),
+ }
+ )
+
+ df2 = DataFrame(
+ {
+ "key1": key1[::2],
+ "key2": key2[::2],
+ "value2": np.random.default_rng(2).standard_normal(10000),
+ }
+ )
+
+ # just to hit the label compression code path
+ merge(df, df2, how="outer")
+
+ def test_left_join_index_preserve_order(self):
+ on_cols = ["k1", "k2"]
+ left = DataFrame(
+ {
+ "k1": [0, 1, 2] * 8,
+ "k2": ["foo", "bar"] * 12,
+ "v": np.array(np.arange(24), dtype=np.int64),
+ }
+ )
+
+ index = MultiIndex.from_tuples([(2, "bar"), (1, "foo")])
+ right = DataFrame({"v2": [5, 7]}, index=index)
+
+ result = left.join(right, on=on_cols)
+
+ expected = left.copy()
+ expected["v2"] = np.nan
+ expected.loc[(expected.k1 == 2) & (expected.k2 == "bar"), "v2"] = 5
+ expected.loc[(expected.k1 == 1) & (expected.k2 == "foo"), "v2"] = 7
+
+ tm.assert_frame_equal(result, expected)
+
+ result.sort_values(on_cols, kind="mergesort", inplace=True)
+ expected = left.join(right, on=on_cols, sort=True)
+
+ tm.assert_frame_equal(result, expected)
+
+ # test join with multi dtypes blocks
+ left = DataFrame(
+ {
+ "k1": [0, 1, 2] * 8,
+ "k2": ["foo", "bar"] * 12,
+ "k3": np.array([0, 1, 2] * 8, dtype=np.float32),
+ "v": np.array(np.arange(24), dtype=np.int32),
+ }
+ )
+
+ index = MultiIndex.from_tuples([(2, "bar"), (1, "foo")])
+ right = DataFrame({"v2": [5, 7]}, index=index)
+
+ result = left.join(right, on=on_cols)
+
+ expected = left.copy()
+ expected["v2"] = np.nan
+ expected.loc[(expected.k1 == 2) & (expected.k2 == "bar"), "v2"] = 5
+ expected.loc[(expected.k1 == 1) & (expected.k2 == "foo"), "v2"] = 7
+
+ tm.assert_frame_equal(result, expected)
+
+ result = result.sort_values(on_cols, kind="mergesort")
+ expected = left.join(right, on=on_cols, sort=True)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_left_join_index_multi_match_multiindex(self):
+ left = DataFrame(
+ [
+ ["X", "Y", "C", "a"],
+ ["W", "Y", "C", "e"],
+ ["V", "Q", "A", "h"],
+ ["V", "R", "D", "i"],
+ ["X", "Y", "D", "b"],
+ ["X", "Y", "A", "c"],
+ ["W", "Q", "B", "f"],
+ ["W", "R", "C", "g"],
+ ["V", "Y", "C", "j"],
+ ["X", "Y", "B", "d"],
+ ],
+ columns=["cola", "colb", "colc", "tag"],
+ index=[3, 2, 0, 1, 7, 6, 4, 5, 9, 8],
+ )
+
+ right = DataFrame(
+ [
+ ["W", "R", "C", 0],
+ ["W", "Q", "B", 3],
+ ["W", "Q", "B", 8],
+ ["X", "Y", "A", 1],
+ ["X", "Y", "A", 4],
+ ["X", "Y", "B", 5],
+ ["X", "Y", "C", 6],
+ ["X", "Y", "C", 9],
+ ["X", "Q", "C", -6],
+ ["X", "R", "C", -9],
+ ["V", "Y", "C", 7],
+ ["V", "R", "D", 2],
+ ["V", "R", "D", -1],
+ ["V", "Q", "A", -3],
+ ],
+ columns=["col1", "col2", "col3", "val"],
+ ).set_index(["col1", "col2", "col3"])
+
+ result = left.join(right, on=["cola", "colb", "colc"], how="left")
+
+ expected = DataFrame(
+ [
+ ["X", "Y", "C", "a", 6],
+ ["X", "Y", "C", "a", 9],
+ ["W", "Y", "C", "e", np.nan],
+ ["V", "Q", "A", "h", -3],
+ ["V", "R", "D", "i", 2],
+ ["V", "R", "D", "i", -1],
+ ["X", "Y", "D", "b", np.nan],
+ ["X", "Y", "A", "c", 1],
+ ["X", "Y", "A", "c", 4],
+ ["W", "Q", "B", "f", 3],
+ ["W", "Q", "B", "f", 8],
+ ["W", "R", "C", "g", 0],
+ ["V", "Y", "C", "j", 7],
+ ["X", "Y", "B", "d", 5],
+ ],
+ columns=["cola", "colb", "colc", "tag", "val"],
+ index=[3, 3, 2, 0, 1, 1, 7, 6, 6, 4, 4, 5, 9, 8],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ result = left.join(right, on=["cola", "colb", "colc"], how="left", sort=True)
+
+ expected = expected.sort_values(["cola", "colb", "colc"], kind="mergesort")
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_left_join_index_multi_match(self):
+ left = DataFrame(
+ [["c", 0], ["b", 1], ["a", 2], ["b", 3]],
+ columns=["tag", "val"],
+ index=[2, 0, 1, 3],
+ )
+
+ right = DataFrame(
+ [
+ ["a", "v"],
+ ["c", "w"],
+ ["c", "x"],
+ ["d", "y"],
+ ["a", "z"],
+ ["c", "r"],
+ ["e", "q"],
+ ["c", "s"],
+ ],
+ columns=["tag", "char"],
+ ).set_index("tag")
+
+ result = left.join(right, on="tag", how="left")
+
+ expected = DataFrame(
+ [
+ ["c", 0, "w"],
+ ["c", 0, "x"],
+ ["c", 0, "r"],
+ ["c", 0, "s"],
+ ["b", 1, np.nan],
+ ["a", 2, "v"],
+ ["a", 2, "z"],
+ ["b", 3, np.nan],
+ ],
+ columns=["tag", "val", "char"],
+ index=[2, 2, 2, 2, 0, 1, 1, 3],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ result = left.join(right, on="tag", how="left", sort=True)
+ expected2 = expected.sort_values("tag", kind="mergesort")
+
+ tm.assert_frame_equal(result, expected2)
+
+ # GH7331 - maintain left frame order in left merge
+ result = merge(left, right.reset_index(), how="left", on="tag")
+ expected.index = RangeIndex(len(expected))
+ tm.assert_frame_equal(result, expected)
+
+ def test_left_merge_na_buglet(self):
+ left = DataFrame(
+ {
+ "id": list("abcde"),
+ "v1": np.random.default_rng(2).standard_normal(5),
+ "v2": np.random.default_rng(2).standard_normal(5),
+ "dummy": list("abcde"),
+ "v3": np.random.default_rng(2).standard_normal(5),
+ },
+ columns=["id", "v1", "v2", "dummy", "v3"],
+ )
+ right = DataFrame(
+ {
+ "id": ["a", "b", np.nan, np.nan, np.nan],
+ "sv3": [1.234, 5.678, np.nan, np.nan, np.nan],
+ }
+ )
+
+ result = merge(left, right, on="id", how="left")
+
+ rdf = right.drop(["id"], axis=1)
+ expected = left.join(rdf)
+ tm.assert_frame_equal(result, expected)
+
+ def test_merge_na_keys(self):
+ data = [
+ [1950, "A", 1.5],
+ [1950, "B", 1.5],
+ [1955, "B", 1.5],
+ [1960, "B", np.nan],
+ [1970, "B", 4.0],
+ [1950, "C", 4.0],
+ [1960, "C", np.nan],
+ [1965, "C", 3.0],
+ [1970, "C", 4.0],
+ ]
+
+ frame = DataFrame(data, columns=["year", "panel", "data"])
+
+ other_data = [
+ [1960, "A", np.nan],
+ [1970, "A", np.nan],
+ [1955, "A", np.nan],
+ [1965, "A", np.nan],
+ [1965, "B", np.nan],
+ [1955, "C", np.nan],
+ ]
+ other = DataFrame(other_data, columns=["year", "panel", "data"])
+
+ result = frame.merge(other, how="outer")
+
+ expected = frame.fillna(-999).merge(other.fillna(-999), how="outer")
+ expected = expected.replace(-999, np.nan)
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("klass", [None, np.asarray, Series, Index])
+ def test_merge_datetime_index(self, klass):
+ # see gh-19038
+ df = DataFrame(
+ [1, 2, 3], ["2016-01-01", "2017-01-01", "2018-01-01"], columns=["a"]
+ )
+ df.index = pd.to_datetime(df.index)
+ on_vector = df.index.year
+
+ if klass is not None:
+ on_vector = klass(on_vector)
+
+ exp_years = np.array([2016, 2017, 2018], dtype=np.int32)
+ expected = DataFrame({"a": [1, 2, 3], "key_1": exp_years})
+
+ result = df.merge(df, on=["a", on_vector], how="inner")
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame({"key_0": exp_years, "a_x": [1, 2, 3], "a_y": [1, 2, 3]})
+
+ result = df.merge(df, on=[df.index.year], how="inner")
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("merge_type", ["left", "right"])
+ def test_merge_datetime_multi_index_empty_df(self, merge_type):
+ # see gh-36895
+
+ left = DataFrame(
+ data={
+ "data": [1.5, 1.5],
+ },
+ index=MultiIndex.from_tuples(
+ [[Timestamp("1950-01-01"), "A"], [Timestamp("1950-01-02"), "B"]],
+ names=["date", "panel"],
+ ),
+ )
+
+ right = DataFrame(
+ index=MultiIndex.from_tuples([], names=["date", "panel"]), columns=["state"]
+ )
+
+ expected_index = MultiIndex.from_tuples(
+ [[Timestamp("1950-01-01"), "A"], [Timestamp("1950-01-02"), "B"]],
+ names=["date", "panel"],
+ )
+
+ if merge_type == "left":
+ expected = DataFrame(
+ data={
+ "data": [1.5, 1.5],
+ "state": np.array([np.nan, np.nan], dtype=object),
+ },
+ index=expected_index,
+ )
+ results_merge = left.merge(right, how="left", on=["date", "panel"])
+ results_join = left.join(right, how="left")
+ else:
+ expected = DataFrame(
+ data={
+ "state": np.array([np.nan, np.nan], dtype=object),
+ "data": [1.5, 1.5],
+ },
+ index=expected_index,
+ )
+ results_merge = right.merge(left, how="right", on=["date", "panel"])
+ results_join = right.join(left, how="right")
+
+ tm.assert_frame_equal(results_merge, expected)
+ tm.assert_frame_equal(results_join, expected)
+
+ @pytest.fixture
+ def household(self):
+ household = DataFrame(
+ {
+ "household_id": [1, 2, 3],
+ "male": [0, 1, 0],
+ "wealth": [196087.3, 316478.7, 294750],
+ },
+ columns=["household_id", "male", "wealth"],
+ ).set_index("household_id")
+ return household
+
+ @pytest.fixture
+ def portfolio(self):
+ portfolio = DataFrame(
+ {
+ "household_id": [1, 2, 2, 3, 3, 3, 4],
+ "asset_id": [
+ "nl0000301109",
+ "nl0000289783",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "lu0197800237",
+ "nl0000289965",
+ np.nan,
+ ],
+ "name": [
+ "ABN Amro",
+ "Robeco",
+ "Royal Dutch Shell",
+ "Royal Dutch Shell",
+ "AAB Eastern Europe Equity Fund",
+ "Postbank BioTech Fonds",
+ np.nan,
+ ],
+ "share": [1.0, 0.4, 0.6, 0.15, 0.6, 0.25, 1.0],
+ },
+ columns=["household_id", "asset_id", "name", "share"],
+ ).set_index(["household_id", "asset_id"])
+ return portfolio
+
+ @pytest.fixture
+ def expected(self):
+ expected = (
+ DataFrame(
+ {
+ "male": [0, 1, 1, 0, 0, 0],
+ "wealth": [
+ 196087.3,
+ 316478.7,
+ 316478.7,
+ 294750.0,
+ 294750.0,
+ 294750.0,
+ ],
+ "name": [
+ "ABN Amro",
+ "Robeco",
+ "Royal Dutch Shell",
+ "Royal Dutch Shell",
+ "AAB Eastern Europe Equity Fund",
+ "Postbank BioTech Fonds",
+ ],
+ "share": [1.00, 0.40, 0.60, 0.15, 0.60, 0.25],
+ "household_id": [1, 2, 2, 3, 3, 3],
+ "asset_id": [
+ "nl0000301109",
+ "nl0000289783",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "lu0197800237",
+ "nl0000289965",
+ ],
+ }
+ )
+ .set_index(["household_id", "asset_id"])
+ .reindex(columns=["male", "wealth", "name", "share"])
+ )
+ return expected
+
+ def test_join_multi_levels(self, portfolio, household, expected):
+ portfolio = portfolio.copy()
+ household = household.copy()
+
+ # GH 3662
+ # merge multi-levels
+ result = household.join(portfolio, how="inner")
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_multi_levels_merge_equivalence(self, portfolio, household, expected):
+ portfolio = portfolio.copy()
+ household = household.copy()
+
+ # equivalency
+ result = merge(
+ household.reset_index(),
+ portfolio.reset_index(),
+ on=["household_id"],
+ how="inner",
+ ).set_index(["household_id", "asset_id"])
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_multi_levels_outer(self, portfolio, household, expected):
+ portfolio = portfolio.copy()
+ household = household.copy()
+
+ result = household.join(portfolio, how="outer")
+ expected = concat(
+ [
+ expected,
+ (
+ DataFrame(
+ {"share": [1.00]},
+ index=MultiIndex.from_tuples(
+ [(4, np.nan)], names=["household_id", "asset_id"]
+ ),
+ )
+ ),
+ ],
+ axis=0,
+ sort=True,
+ ).reindex(columns=expected.columns)
+ tm.assert_frame_equal(result, expected, check_index_type=False)
+
+ def test_join_multi_levels_invalid(self, portfolio, household):
+ portfolio = portfolio.copy()
+ household = household.copy()
+
+ # invalid cases
+ household.index.name = "foo"
+
+ with pytest.raises(
+ ValueError, match="cannot join with no overlapping index names"
+ ):
+ household.join(portfolio, how="inner")
+
+ portfolio2 = portfolio.copy()
+ portfolio2.index.set_names(["household_id", "foo"])
+
+ with pytest.raises(ValueError, match="columns overlap but no suffix specified"):
+ portfolio2.join(portfolio, how="inner")
+
+ def test_join_multi_levels2(self):
+ # some more advanced merges
+ # GH6360
+ household = DataFrame(
+ {
+ "household_id": [1, 2, 2, 3, 3, 3, 4],
+ "asset_id": [
+ "nl0000301109",
+ "nl0000301109",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "lu0197800237",
+ "nl0000289965",
+ np.nan,
+ ],
+ "share": [1.0, 0.4, 0.6, 0.15, 0.6, 0.25, 1.0],
+ },
+ columns=["household_id", "asset_id", "share"],
+ ).set_index(["household_id", "asset_id"])
+
+ log_return = DataFrame(
+ {
+ "asset_id": [
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "lu0197800237",
+ "lu0197800237",
+ ],
+ "t": [233, 234, 235, 180, 181],
+ "log_return": [
+ 0.09604978,
+ -0.06524096,
+ 0.03532373,
+ 0.03025441,
+ 0.036997,
+ ],
+ }
+ ).set_index(["asset_id", "t"])
+
+ expected = (
+ DataFrame(
+ {
+ "household_id": [2, 2, 2, 3, 3, 3, 3, 3],
+ "asset_id": [
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "lu0197800237",
+ "lu0197800237",
+ ],
+ "t": [233, 234, 235, 233, 234, 235, 180, 181],
+ "share": [0.6, 0.6, 0.6, 0.15, 0.15, 0.15, 0.6, 0.6],
+ "log_return": [
+ 0.09604978,
+ -0.06524096,
+ 0.03532373,
+ 0.09604978,
+ -0.06524096,
+ 0.03532373,
+ 0.03025441,
+ 0.036997,
+ ],
+ }
+ )
+ .set_index(["household_id", "asset_id", "t"])
+ .reindex(columns=["share", "log_return"])
+ )
+
+ # this is the equivalency
+ result = merge(
+ household.reset_index(),
+ log_return.reset_index(),
+ on=["asset_id"],
+ how="inner",
+ ).set_index(["household_id", "asset_id", "t"])
+ tm.assert_frame_equal(result, expected)
+
+ expected = (
+ DataFrame(
+ {
+ "household_id": [2, 2, 2, 3, 3, 3, 3, 3, 3, 1, 2, 4],
+ "asset_id": [
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "gb00b03mlx29",
+ "lu0197800237",
+ "lu0197800237",
+ "nl0000289965",
+ "nl0000301109",
+ "nl0000301109",
+ None,
+ ],
+ "t": [
+ 233,
+ 234,
+ 235,
+ 233,
+ 234,
+ 235,
+ 180,
+ 181,
+ None,
+ None,
+ None,
+ None,
+ ],
+ "share": [
+ 0.6,
+ 0.6,
+ 0.6,
+ 0.15,
+ 0.15,
+ 0.15,
+ 0.6,
+ 0.6,
+ 0.25,
+ 1.0,
+ 0.4,
+ 1.0,
+ ],
+ "log_return": [
+ 0.09604978,
+ -0.06524096,
+ 0.03532373,
+ 0.09604978,
+ -0.06524096,
+ 0.03532373,
+ 0.03025441,
+ 0.036997,
+ None,
+ None,
+ None,
+ None,
+ ],
+ }
+ )
+ .set_index(["household_id", "asset_id", "t"])
+ .reindex(columns=["share", "log_return"])
+ )
+
+ result = merge(
+ household.reset_index(),
+ log_return.reset_index(),
+ on=["asset_id"],
+ how="outer",
+ ).set_index(["household_id", "asset_id", "t"])
+
+ tm.assert_frame_equal(result, expected)
+
+
+class TestJoinMultiMulti:
+ def test_join_multi_multi(self, left_multi, right_multi, join_type, on_cols_multi):
+ left_names = left_multi.index.names
+ right_names = right_multi.index.names
+ if join_type == "right":
+ level_order = right_names + left_names.difference(right_names)
+ else:
+ level_order = left_names + right_names.difference(left_names)
+ # Multi-index join tests
+ expected = (
+ merge(
+ left_multi.reset_index(),
+ right_multi.reset_index(),
+ how=join_type,
+ on=on_cols_multi,
+ )
+ .set_index(level_order)
+ .sort_index()
+ )
+
+ result = left_multi.join(right_multi, how=join_type).sort_index()
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_multi_empty_frames(
+ self, left_multi, right_multi, join_type, on_cols_multi
+ ):
+ left_multi = left_multi.drop(columns=left_multi.columns)
+ right_multi = right_multi.drop(columns=right_multi.columns)
+
+ left_names = left_multi.index.names
+ right_names = right_multi.index.names
+ if join_type == "right":
+ level_order = right_names + left_names.difference(right_names)
+ else:
+ level_order = left_names + right_names.difference(left_names)
+
+ expected = (
+ merge(
+ left_multi.reset_index(),
+ right_multi.reset_index(),
+ how=join_type,
+ on=on_cols_multi,
+ )
+ .set_index(level_order)
+ .sort_index()
+ )
+
+ result = left_multi.join(right_multi, how=join_type).sort_index()
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("box", [None, np.asarray, Series, Index])
+ def test_merge_datetime_index(self, box):
+ # see gh-19038
+ df = DataFrame(
+ [1, 2, 3], ["2016-01-01", "2017-01-01", "2018-01-01"], columns=["a"]
+ )
+ df.index = pd.to_datetime(df.index)
+ on_vector = df.index.year
+
+ if box is not None:
+ on_vector = box(on_vector)
+
+ exp_years = np.array([2016, 2017, 2018], dtype=np.int32)
+ expected = DataFrame({"a": [1, 2, 3], "key_1": exp_years})
+
+ result = df.merge(df, on=["a", on_vector], how="inner")
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame({"key_0": exp_years, "a_x": [1, 2, 3], "a_y": [1, 2, 3]})
+
+ result = df.merge(df, on=[df.index.year], how="inner")
+ tm.assert_frame_equal(result, expected)
+
+ def test_single_common_level(self):
+ index_left = MultiIndex.from_tuples(
+ [("K0", "X0"), ("K0", "X1"), ("K1", "X2")], names=["key", "X"]
+ )
+
+ left = DataFrame(
+ {"A": ["A0", "A1", "A2"], "B": ["B0", "B1", "B2"]}, index=index_left
+ )
+
+ index_right = MultiIndex.from_tuples(
+ [("K0", "Y0"), ("K1", "Y1"), ("K2", "Y2"), ("K2", "Y3")], names=["key", "Y"]
+ )
+
+ right = DataFrame(
+ {"C": ["C0", "C1", "C2", "C3"], "D": ["D0", "D1", "D2", "D3"]},
+ index=index_right,
+ )
+
+ result = left.join(right)
+ expected = merge(
+ left.reset_index(), right.reset_index(), on=["key"], how="inner"
+ ).set_index(["key", "X", "Y"])
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_join_multi_wrong_order(self):
+ # GH 25760
+ # GH 28956
+
+ midx1 = MultiIndex.from_product([[1, 2], [3, 4]], names=["a", "b"])
+ midx3 = MultiIndex.from_tuples([(4, 1), (3, 2), (3, 1)], names=["b", "a"])
+
+ left = DataFrame(index=midx1, data={"x": [10, 20, 30, 40]})
+ right = DataFrame(index=midx3, data={"y": ["foo", "bar", "fing"]})
+
+ result = left.join(right)
+
+ expected = DataFrame(
+ index=midx1,
+ data={"x": [10, 20, 30, 40], "y": ["fing", "foo", "bar", np.nan]},
+ )
+
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_crosstab.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_crosstab.py
new file mode 100644
index 0000000000000000000000000000000000000000..1482da8a074eb41b64d276683ffc7258b4e9d0bb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_crosstab.py
@@ -0,0 +1,879 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ CategoricalDtype,
+ CategoricalIndex,
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ crosstab,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def df():
+ df = DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "C": [
+ "dull",
+ "dull",
+ "shiny",
+ "dull",
+ "dull",
+ "shiny",
+ "shiny",
+ "dull",
+ "shiny",
+ "shiny",
+ "shiny",
+ ],
+ "D": np.random.default_rng(2).standard_normal(11),
+ "E": np.random.default_rng(2).standard_normal(11),
+ "F": np.random.default_rng(2).standard_normal(11),
+ }
+ )
+
+ return pd.concat([df, df], ignore_index=True)
+
+
+class TestCrosstab:
+ def test_crosstab_single(self, df):
+ result = crosstab(df["A"], df["C"])
+ expected = df.groupby(["A", "C"]).size().unstack()
+ tm.assert_frame_equal(result, expected.fillna(0).astype(np.int64))
+
+ def test_crosstab_multiple(self, df):
+ result = crosstab(df["A"], [df["B"], df["C"]])
+ expected = df.groupby(["A", "B", "C"]).size()
+ expected = expected.unstack("B").unstack("C").fillna(0).astype(np.int64)
+ tm.assert_frame_equal(result, expected)
+
+ result = crosstab([df["B"], df["C"]], df["A"])
+ expected = df.groupby(["B", "C", "A"]).size()
+ expected = expected.unstack("A").fillna(0).astype(np.int64)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("box", [np.array, list, tuple])
+ def test_crosstab_ndarray(self, box):
+ # GH 44076
+ a = box(np.random.default_rng(2).integers(0, 5, size=100))
+ b = box(np.random.default_rng(2).integers(0, 3, size=100))
+ c = box(np.random.default_rng(2).integers(0, 10, size=100))
+
+ df = DataFrame({"a": a, "b": b, "c": c})
+
+ result = crosstab(a, [b, c], rownames=["a"], colnames=("b", "c"))
+ expected = crosstab(df["a"], [df["b"], df["c"]])
+ tm.assert_frame_equal(result, expected)
+
+ result = crosstab([b, c], a, colnames=["a"], rownames=("b", "c"))
+ expected = crosstab([df["b"], df["c"]], df["a"])
+ tm.assert_frame_equal(result, expected)
+
+ # assign arbitrary names
+ result = crosstab(a, c)
+ expected = crosstab(df["a"], df["c"])
+ expected.index.names = ["row_0"]
+ expected.columns.names = ["col_0"]
+ tm.assert_frame_equal(result, expected)
+
+ def test_crosstab_non_aligned(self):
+ # GH 17005
+ a = Series([0, 1, 1], index=["a", "b", "c"])
+ b = Series([3, 4, 3, 4, 3], index=["a", "b", "c", "d", "f"])
+ c = np.array([3, 4, 3], dtype=np.int64)
+
+ expected = DataFrame(
+ [[1, 0], [1, 1]],
+ index=Index([0, 1], name="row_0"),
+ columns=Index([3, 4], name="col_0"),
+ )
+
+ result = crosstab(a, b)
+ tm.assert_frame_equal(result, expected)
+
+ result = crosstab(a, c)
+ tm.assert_frame_equal(result, expected)
+
+ def test_crosstab_margins(self):
+ a = np.random.default_rng(2).integers(0, 7, size=100)
+ b = np.random.default_rng(2).integers(0, 3, size=100)
+ c = np.random.default_rng(2).integers(0, 5, size=100)
+
+ df = DataFrame({"a": a, "b": b, "c": c})
+
+ result = crosstab(a, [b, c], rownames=["a"], colnames=("b", "c"), margins=True)
+
+ assert result.index.names == ("a",)
+ assert result.columns.names == ["b", "c"]
+
+ all_cols = result["All", ""]
+ exp_cols = df.groupby(["a"]).size().astype("i8")
+ # to keep index.name
+ exp_margin = Series([len(df)], index=Index(["All"], name="a"))
+ exp_cols = pd.concat([exp_cols, exp_margin])
+ exp_cols.name = ("All", "")
+
+ tm.assert_series_equal(all_cols, exp_cols)
+
+ all_rows = result.loc["All"]
+ exp_rows = df.groupby(["b", "c"]).size().astype("i8")
+ exp_rows = pd.concat([exp_rows, Series([len(df)], index=[("All", "")])])
+ exp_rows.name = "All"
+
+ exp_rows = exp_rows.reindex(all_rows.index)
+ exp_rows = exp_rows.fillna(0).astype(np.int64)
+ tm.assert_series_equal(all_rows, exp_rows)
+
+ def test_crosstab_margins_set_margin_name(self):
+ # GH 15972
+ a = np.random.default_rng(2).integers(0, 7, size=100)
+ b = np.random.default_rng(2).integers(0, 3, size=100)
+ c = np.random.default_rng(2).integers(0, 5, size=100)
+
+ df = DataFrame({"a": a, "b": b, "c": c})
+
+ result = crosstab(
+ a,
+ [b, c],
+ rownames=["a"],
+ colnames=("b", "c"),
+ margins=True,
+ margins_name="TOTAL",
+ )
+
+ assert result.index.names == ("a",)
+ assert result.columns.names == ["b", "c"]
+
+ all_cols = result["TOTAL", ""]
+ exp_cols = df.groupby(["a"]).size().astype("i8")
+ # to keep index.name
+ exp_margin = Series([len(df)], index=Index(["TOTAL"], name="a"))
+ exp_cols = pd.concat([exp_cols, exp_margin])
+ exp_cols.name = ("TOTAL", "")
+
+ tm.assert_series_equal(all_cols, exp_cols)
+
+ all_rows = result.loc["TOTAL"]
+ exp_rows = df.groupby(["b", "c"]).size().astype("i8")
+ exp_rows = pd.concat([exp_rows, Series([len(df)], index=[("TOTAL", "")])])
+ exp_rows.name = "TOTAL"
+
+ exp_rows = exp_rows.reindex(all_rows.index)
+ exp_rows = exp_rows.fillna(0).astype(np.int64)
+ tm.assert_series_equal(all_rows, exp_rows)
+
+ msg = "margins_name argument must be a string"
+ for margins_name in [666, None, ["a", "b"]]:
+ with pytest.raises(ValueError, match=msg):
+ crosstab(
+ a,
+ [b, c],
+ rownames=["a"],
+ colnames=("b", "c"),
+ margins=True,
+ margins_name=margins_name,
+ )
+
+ def test_crosstab_pass_values(self):
+ a = np.random.default_rng(2).integers(0, 7, size=100)
+ b = np.random.default_rng(2).integers(0, 3, size=100)
+ c = np.random.default_rng(2).integers(0, 5, size=100)
+ values = np.random.default_rng(2).standard_normal(100)
+
+ table = crosstab(
+ [a, b], c, values, aggfunc="sum", rownames=["foo", "bar"], colnames=["baz"]
+ )
+
+ df = DataFrame({"foo": a, "bar": b, "baz": c, "values": values})
+
+ expected = df.pivot_table(
+ "values", index=["foo", "bar"], columns="baz", aggfunc="sum"
+ )
+ tm.assert_frame_equal(table, expected)
+
+ def test_crosstab_dropna(self):
+ # GH 3820
+ a = np.array(["foo", "foo", "foo", "bar", "bar", "foo", "foo"], dtype=object)
+ b = np.array(["one", "one", "two", "one", "two", "two", "two"], dtype=object)
+ c = np.array(
+ ["dull", "dull", "dull", "dull", "dull", "shiny", "shiny"], dtype=object
+ )
+ res = crosstab(a, [b, c], rownames=["a"], colnames=["b", "c"], dropna=False)
+ m = MultiIndex.from_tuples(
+ [("one", "dull"), ("one", "shiny"), ("two", "dull"), ("two", "shiny")],
+ names=["b", "c"],
+ )
+ tm.assert_index_equal(res.columns, m)
+
+ def test_crosstab_no_overlap(self):
+ # GS 10291
+
+ s1 = Series([1, 2, 3], index=[1, 2, 3])
+ s2 = Series([4, 5, 6], index=[4, 5, 6])
+
+ actual = crosstab(s1, s2)
+ expected = DataFrame(
+ index=Index([], dtype="int64", name="row_0"),
+ columns=Index([], dtype="int64", name="col_0"),
+ )
+
+ tm.assert_frame_equal(actual, expected)
+
+ def test_margin_dropna(self):
+ # GH 12577
+ # pivot_table counts null into margin ('All')
+ # when margins=true and dropna=true
+
+ df = DataFrame({"a": [1, 2, 2, 2, 2, np.nan], "b": [3, 3, 4, 4, 4, 4]})
+ actual = crosstab(df.a, df.b, margins=True, dropna=True)
+ expected = DataFrame([[1, 0, 1], [1, 3, 4], [2, 3, 5]])
+ expected.index = Index([1.0, 2.0, "All"], name="a")
+ expected.columns = Index([3, 4, "All"], name="b")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_margin_dropna2(self):
+ df = DataFrame(
+ {"a": [1, np.nan, np.nan, np.nan, 2, np.nan], "b": [3, np.nan, 4, 4, 4, 4]}
+ )
+ actual = crosstab(df.a, df.b, margins=True, dropna=True)
+ expected = DataFrame([[1, 0, 1], [0, 1, 1], [1, 1, 2]])
+ expected.index = Index([1.0, 2.0, "All"], name="a")
+ expected.columns = Index([3.0, 4.0, "All"], name="b")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_margin_dropna3(self):
+ df = DataFrame(
+ {"a": [1, np.nan, np.nan, np.nan, np.nan, 2], "b": [3, 3, 4, 4, 4, 4]}
+ )
+ actual = crosstab(df.a, df.b, margins=True, dropna=True)
+ expected = DataFrame([[1, 0, 1], [0, 1, 1], [1, 1, 2]])
+ expected.index = Index([1.0, 2.0, "All"], name="a")
+ expected.columns = Index([3, 4, "All"], name="b")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_margin_dropna4(self):
+ # GH 12642
+ # _add_margins raises KeyError: Level None not found
+ # when margins=True and dropna=False
+ # GH: 10772: Keep np.nan in result with dropna=False
+ df = DataFrame({"a": [1, 2, 2, 2, 2, np.nan], "b": [3, 3, 4, 4, 4, 4]})
+ actual = crosstab(df.a, df.b, margins=True, dropna=False)
+ expected = DataFrame([[1, 0, 1], [1, 3, 4], [0, 1, 1], [2, 4, 6]])
+ expected.index = Index([1.0, 2.0, np.nan, "All"], name="a")
+ expected.columns = Index([3, 4, "All"], name="b")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_margin_dropna5(self):
+ # GH: 10772: Keep np.nan in result with dropna=False
+ df = DataFrame(
+ {"a": [1, np.nan, np.nan, np.nan, 2, np.nan], "b": [3, np.nan, 4, 4, 4, 4]}
+ )
+ actual = crosstab(df.a, df.b, margins=True, dropna=False)
+ expected = DataFrame(
+ [[1, 0, 0, 1.0], [0, 1, 0, 1.0], [0, 3, 1, 4.0], [1, 4, 1, 6.0]]
+ )
+ expected.index = Index([1.0, 2.0, np.nan, "All"], name="a")
+ expected.columns = Index([3.0, 4.0, np.nan, "All"], name="b")
+ tm.assert_frame_equal(actual, expected, check_dtype=False)
+
+ def test_margin_dropna6(self):
+ # GH: 10772: Keep np.nan in result with dropna=False
+ a = np.array(["foo", "foo", "foo", "bar", "bar", "foo", "foo"], dtype=object)
+ b = np.array(["one", "one", "two", "one", "two", np.nan, "two"], dtype=object)
+ c = np.array(
+ ["dull", "dull", "dull", "dull", "dull", "shiny", "shiny"], dtype=object
+ )
+
+ actual = crosstab(
+ a, [b, c], rownames=["a"], colnames=["b", "c"], margins=True, dropna=False
+ )
+ m = MultiIndex.from_arrays(
+ [
+ ["one", "one", "two", "two", np.nan, np.nan, "All"],
+ ["dull", "shiny", "dull", "shiny", "dull", "shiny", ""],
+ ],
+ names=["b", "c"],
+ )
+ expected = DataFrame(
+ [[1, 0, 1, 0, 0, 0, 2], [2, 0, 1, 1, 0, 1, 5], [3, 0, 2, 1, 0, 1, 7]],
+ columns=m,
+ )
+ expected.index = Index(["bar", "foo", "All"], name="a")
+ tm.assert_frame_equal(actual, expected)
+
+ actual = crosstab(
+ [a, b], c, rownames=["a", "b"], colnames=["c"], margins=True, dropna=False
+ )
+ m = MultiIndex.from_arrays(
+ [
+ ["bar", "bar", "bar", "foo", "foo", "foo", "All"],
+ ["one", "two", np.nan, "one", "two", np.nan, ""],
+ ],
+ names=["a", "b"],
+ )
+ expected = DataFrame(
+ [
+ [1, 0, 1.0],
+ [1, 0, 1.0],
+ [0, 0, np.nan],
+ [2, 0, 2.0],
+ [1, 1, 2.0],
+ [0, 1, 1.0],
+ [5, 2, 7.0],
+ ],
+ index=m,
+ )
+ expected.columns = Index(["dull", "shiny", "All"], name="c")
+ tm.assert_frame_equal(actual, expected)
+
+ actual = crosstab(
+ [a, b], c, rownames=["a", "b"], colnames=["c"], margins=True, dropna=True
+ )
+ m = MultiIndex.from_arrays(
+ [["bar", "bar", "foo", "foo", "All"], ["one", "two", "one", "two", ""]],
+ names=["a", "b"],
+ )
+ expected = DataFrame(
+ [[1, 0, 1], [1, 0, 1], [2, 0, 2], [1, 1, 2], [5, 1, 6]], index=m
+ )
+ expected.columns = Index(["dull", "shiny", "All"], name="c")
+ tm.assert_frame_equal(actual, expected)
+
+ def test_crosstab_normalize(self):
+ # Issue 12578
+ df = DataFrame(
+ {"a": [1, 2, 2, 2, 2], "b": [3, 3, 4, 4, 4], "c": [1, 1, np.nan, 1, 1]}
+ )
+
+ rindex = Index([1, 2], name="a")
+ cindex = Index([3, 4], name="b")
+ full_normal = DataFrame([[0.2, 0], [0.2, 0.6]], index=rindex, columns=cindex)
+ row_normal = DataFrame([[1.0, 0], [0.25, 0.75]], index=rindex, columns=cindex)
+ col_normal = DataFrame([[0.5, 0], [0.5, 1.0]], index=rindex, columns=cindex)
+
+ # Check all normalize args
+ tm.assert_frame_equal(crosstab(df.a, df.b, normalize="all"), full_normal)
+ tm.assert_frame_equal(crosstab(df.a, df.b, normalize=True), full_normal)
+ tm.assert_frame_equal(crosstab(df.a, df.b, normalize="index"), row_normal)
+ tm.assert_frame_equal(crosstab(df.a, df.b, normalize="columns"), col_normal)
+ tm.assert_frame_equal(
+ crosstab(df.a, df.b, normalize=1),
+ crosstab(df.a, df.b, normalize="columns"),
+ )
+ tm.assert_frame_equal(
+ crosstab(df.a, df.b, normalize=0), crosstab(df.a, df.b, normalize="index")
+ )
+
+ row_normal_margins = DataFrame(
+ [[1.0, 0], [0.25, 0.75], [0.4, 0.6]],
+ index=Index([1, 2, "All"], name="a", dtype="object"),
+ columns=Index([3, 4], name="b", dtype="object"),
+ )
+ col_normal_margins = DataFrame(
+ [[0.5, 0, 0.2], [0.5, 1.0, 0.8]],
+ index=Index([1, 2], name="a", dtype="object"),
+ columns=Index([3, 4, "All"], name="b", dtype="object"),
+ )
+
+ all_normal_margins = DataFrame(
+ [[0.2, 0, 0.2], [0.2, 0.6, 0.8], [0.4, 0.6, 1]],
+ index=Index([1, 2, "All"], name="a", dtype="object"),
+ columns=Index([3, 4, "All"], name="b", dtype="object"),
+ )
+ tm.assert_frame_equal(
+ crosstab(df.a, df.b, normalize="index", margins=True), row_normal_margins
+ )
+ tm.assert_frame_equal(
+ crosstab(df.a, df.b, normalize="columns", margins=True), col_normal_margins
+ )
+ tm.assert_frame_equal(
+ crosstab(df.a, df.b, normalize=True, margins=True), all_normal_margins
+ )
+
+ def test_crosstab_normalize_arrays(self):
+ # GH#12578
+ df = DataFrame(
+ {"a": [1, 2, 2, 2, 2], "b": [3, 3, 4, 4, 4], "c": [1, 1, np.nan, 1, 1]}
+ )
+
+ # Test arrays
+ crosstab(
+ [np.array([1, 1, 2, 2]), np.array([1, 2, 1, 2])], np.array([1, 2, 1, 2])
+ )
+
+ # Test with aggfunc
+ norm_counts = DataFrame(
+ [[0.25, 0, 0.25], [0.25, 0.5, 0.75], [0.5, 0.5, 1]],
+ index=Index([1, 2, "All"], name="a", dtype="object"),
+ columns=Index([3, 4, "All"], name="b"),
+ )
+ test_case = crosstab(
+ df.a, df.b, df.c, aggfunc="count", normalize="all", margins=True
+ )
+ tm.assert_frame_equal(test_case, norm_counts)
+
+ df = DataFrame(
+ {"a": [1, 2, 2, 2, 2], "b": [3, 3, 4, 4, 4], "c": [0, 4, np.nan, 3, 3]}
+ )
+
+ norm_sum = DataFrame(
+ [[0, 0, 0.0], [0.4, 0.6, 1], [0.4, 0.6, 1]],
+ index=Index([1, 2, "All"], name="a", dtype="object"),
+ columns=Index([3, 4, "All"], name="b", dtype="object"),
+ )
+ test_case = crosstab(
+ df.a, df.b, df.c, aggfunc=np.sum, normalize="all", margins=True
+ )
+ tm.assert_frame_equal(test_case, norm_sum)
+
+ def test_crosstab_with_empties(self):
+ # Check handling of empties
+ df = DataFrame(
+ {
+ "a": [1, 2, 2, 2, 2],
+ "b": [3, 3, 4, 4, 4],
+ "c": [np.nan, np.nan, np.nan, np.nan, np.nan],
+ }
+ )
+
+ empty = DataFrame(
+ [[0.0, 0.0], [0.0, 0.0]],
+ index=Index([1, 2], name="a", dtype="int64"),
+ columns=Index([3, 4], name="b"),
+ )
+
+ for i in [True, "index", "columns"]:
+ calculated = crosstab(df.a, df.b, values=df.c, aggfunc="count", normalize=i)
+ tm.assert_frame_equal(empty, calculated)
+
+ nans = DataFrame(
+ [[0.0, np.nan], [0.0, 0.0]],
+ index=Index([1, 2], name="a", dtype="int64"),
+ columns=Index([3, 4], name="b"),
+ )
+
+ calculated = crosstab(df.a, df.b, values=df.c, aggfunc="count", normalize=False)
+ tm.assert_frame_equal(nans, calculated)
+
+ def test_crosstab_errors(self):
+ # Issue 12578
+
+ df = DataFrame(
+ {"a": [1, 2, 2, 2, 2], "b": [3, 3, 4, 4, 4], "c": [1, 1, np.nan, 1, 1]}
+ )
+
+ error = "values cannot be used without an aggfunc."
+ with pytest.raises(ValueError, match=error):
+ crosstab(df.a, df.b, values=df.c)
+
+ error = "aggfunc cannot be used without values"
+ with pytest.raises(ValueError, match=error):
+ crosstab(df.a, df.b, aggfunc=np.mean)
+
+ error = "Not a valid normalize argument"
+ with pytest.raises(ValueError, match=error):
+ crosstab(df.a, df.b, normalize="42")
+
+ with pytest.raises(ValueError, match=error):
+ crosstab(df.a, df.b, normalize=42)
+
+ error = "Not a valid margins argument"
+ with pytest.raises(ValueError, match=error):
+ crosstab(df.a, df.b, normalize="all", margins=42)
+
+ def test_crosstab_with_categorial_columns(self):
+ # GH 8860
+ df = DataFrame(
+ {
+ "MAKE": ["Honda", "Acura", "Tesla", "Honda", "Honda", "Acura"],
+ "MODEL": ["Sedan", "Sedan", "Electric", "Pickup", "Sedan", "Sedan"],
+ }
+ )
+ categories = ["Sedan", "Electric", "Pickup"]
+ df["MODEL"] = df["MODEL"].astype("category").cat.set_categories(categories)
+ result = crosstab(df["MAKE"], df["MODEL"])
+
+ expected_index = Index(["Acura", "Honda", "Tesla"], name="MAKE")
+ expected_columns = CategoricalIndex(
+ categories, categories=categories, ordered=False, name="MODEL"
+ )
+ expected_data = [[2, 0, 0], [2, 0, 1], [0, 1, 0]]
+ expected = DataFrame(
+ expected_data, index=expected_index, columns=expected_columns
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_crosstab_with_numpy_size(self):
+ # GH 4003
+ df = DataFrame(
+ {
+ "A": ["one", "one", "two", "three"] * 6,
+ "B": ["A", "B", "C"] * 8,
+ "C": ["foo", "foo", "foo", "bar", "bar", "bar"] * 4,
+ "D": np.random.default_rng(2).standard_normal(24),
+ "E": np.random.default_rng(2).standard_normal(24),
+ }
+ )
+ result = crosstab(
+ index=[df["A"], df["B"]],
+ columns=[df["C"]],
+ margins=True,
+ aggfunc=np.size,
+ values=df["D"],
+ )
+ expected_index = MultiIndex(
+ levels=[["All", "one", "three", "two"], ["", "A", "B", "C"]],
+ codes=[[1, 1, 1, 2, 2, 2, 3, 3, 3, 0], [1, 2, 3, 1, 2, 3, 1, 2, 3, 0]],
+ names=["A", "B"],
+ )
+ expected_column = Index(["bar", "foo", "All"], name="C")
+ expected_data = np.array(
+ [
+ [2.0, 2.0, 4.0],
+ [2.0, 2.0, 4.0],
+ [2.0, 2.0, 4.0],
+ [2.0, np.nan, 2.0],
+ [np.nan, 2.0, 2.0],
+ [2.0, np.nan, 2.0],
+ [np.nan, 2.0, 2.0],
+ [2.0, np.nan, 2.0],
+ [np.nan, 2.0, 2.0],
+ [12.0, 12.0, 24.0],
+ ]
+ )
+ expected = DataFrame(
+ expected_data, index=expected_index, columns=expected_column
+ )
+ # aggfunc is np.size, resulting in integers
+ expected["All"] = expected["All"].astype("int64")
+ tm.assert_frame_equal(result, expected)
+
+ def test_crosstab_duplicate_names(self):
+ # GH 13279 / 22529
+
+ s1 = Series(range(3), name="foo")
+ s2_foo = Series(range(1, 4), name="foo")
+ s2_bar = Series(range(1, 4), name="bar")
+ s3 = Series(range(3), name="waldo")
+
+ # check result computed with duplicate labels against
+ # result computed with unique labels, then relabelled
+ mapper = {"bar": "foo"}
+
+ # duplicate row, column labels
+ result = crosstab(s1, s2_foo)
+ expected = crosstab(s1, s2_bar).rename_axis(columns=mapper, axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ # duplicate row, unique column labels
+ result = crosstab([s1, s2_foo], s3)
+ expected = crosstab([s1, s2_bar], s3).rename_axis(index=mapper, axis=0)
+ tm.assert_frame_equal(result, expected)
+
+ # unique row, duplicate column labels
+ result = crosstab(s3, [s1, s2_foo])
+ expected = crosstab(s3, [s1, s2_bar]).rename_axis(columns=mapper, axis=1)
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("names", [["a", ("b", "c")], [("a", "b"), "c"]])
+ def test_crosstab_tuple_name(self, names):
+ s1 = Series(range(3), name=names[0])
+ s2 = Series(range(1, 4), name=names[1])
+
+ mi = MultiIndex.from_arrays([range(3), range(1, 4)], names=names)
+ expected = Series(1, index=mi).unstack(1, fill_value=0)
+
+ result = crosstab(s1, s2)
+ tm.assert_frame_equal(result, expected)
+
+ def test_crosstab_both_tuple_names(self):
+ # GH 18321
+ s1 = Series(range(3), name=("a", "b"))
+ s2 = Series(range(3), name=("c", "d"))
+
+ expected = DataFrame(
+ np.eye(3, dtype="int64"),
+ index=Index(range(3), name=("a", "b")),
+ columns=Index(range(3), name=("c", "d")),
+ )
+ result = crosstab(s1, s2)
+ tm.assert_frame_equal(result, expected)
+
+ def test_crosstab_unsorted_order(self):
+ df = DataFrame({"b": [3, 1, 2], "a": [5, 4, 6]}, index=["C", "A", "B"])
+ result = crosstab(df.index, [df.b, df.a])
+ e_idx = Index(["A", "B", "C"], name="row_0")
+ e_columns = MultiIndex.from_tuples([(1, 4), (2, 6), (3, 5)], names=["b", "a"])
+ expected = DataFrame(
+ [[1, 0, 0], [0, 1, 0], [0, 0, 1]], index=e_idx, columns=e_columns
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_crosstab_normalize_multiple_columns(self):
+ # GH 15150
+ df = DataFrame(
+ {
+ "A": ["one", "one", "two", "three"] * 6,
+ "B": ["A", "B", "C"] * 8,
+ "C": ["foo", "foo", "foo", "bar", "bar", "bar"] * 4,
+ "D": [0] * 24,
+ "E": [0] * 24,
+ }
+ )
+
+ result = crosstab(
+ [df.A, df.B],
+ df.C,
+ values=df.D,
+ aggfunc=np.sum,
+ normalize=True,
+ margins=True,
+ )
+ expected = DataFrame(
+ np.array([0] * 29 + [1], dtype=float).reshape(10, 3),
+ columns=Index(["bar", "foo", "All"], name="C"),
+ index=MultiIndex.from_tuples(
+ [
+ ("one", "A"),
+ ("one", "B"),
+ ("one", "C"),
+ ("three", "A"),
+ ("three", "B"),
+ ("three", "C"),
+ ("two", "A"),
+ ("two", "B"),
+ ("two", "C"),
+ ("All", ""),
+ ],
+ names=["A", "B"],
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_margin_normalize(self):
+ # GH 27500
+ df = DataFrame(
+ {
+ "A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
+ "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"],
+ "C": [
+ "small",
+ "large",
+ "large",
+ "small",
+ "small",
+ "large",
+ "small",
+ "small",
+ "large",
+ ],
+ "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
+ "E": [2, 4, 5, 5, 6, 6, 8, 9, 9],
+ }
+ )
+ # normalize on index
+ result = crosstab(
+ [df.A, df.B], df.C, margins=True, margins_name="Sub-Total", normalize=0
+ )
+ expected = DataFrame(
+ [[0.5, 0.5], [0.5, 0.5], [0.666667, 0.333333], [0, 1], [0.444444, 0.555556]]
+ )
+ expected.index = MultiIndex(
+ levels=[["Sub-Total", "bar", "foo"], ["", "one", "two"]],
+ codes=[[1, 1, 2, 2, 0], [1, 2, 1, 2, 0]],
+ names=["A", "B"],
+ )
+ expected.columns = Index(["large", "small"], name="C")
+ tm.assert_frame_equal(result, expected)
+
+ # normalize on columns
+ result = crosstab(
+ [df.A, df.B], df.C, margins=True, margins_name="Sub-Total", normalize=1
+ )
+ expected = DataFrame(
+ [
+ [0.25, 0.2, 0.222222],
+ [0.25, 0.2, 0.222222],
+ [0.5, 0.2, 0.333333],
+ [0, 0.4, 0.222222],
+ ]
+ )
+ expected.columns = Index(["large", "small", "Sub-Total"], name="C")
+ expected.index = MultiIndex(
+ levels=[["bar", "foo"], ["one", "two"]],
+ codes=[[0, 0, 1, 1], [0, 1, 0, 1]],
+ names=["A", "B"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ # normalize on both index and column
+ result = crosstab(
+ [df.A, df.B], df.C, margins=True, margins_name="Sub-Total", normalize=True
+ )
+ expected = DataFrame(
+ [
+ [0.111111, 0.111111, 0.222222],
+ [0.111111, 0.111111, 0.222222],
+ [0.222222, 0.111111, 0.333333],
+ [0.000000, 0.222222, 0.222222],
+ [0.444444, 0.555555, 1],
+ ]
+ )
+ expected.columns = Index(["large", "small", "Sub-Total"], name="C")
+ expected.index = MultiIndex(
+ levels=[["Sub-Total", "bar", "foo"], ["", "one", "two"]],
+ codes=[[1, 1, 2, 2, 0], [1, 2, 1, 2, 0]],
+ names=["A", "B"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_margin_normalize_multiple_columns(self):
+ # GH 35144
+ # use multiple columns with margins and normalization
+ df = DataFrame(
+ {
+ "A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
+ "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"],
+ "C": [
+ "small",
+ "large",
+ "large",
+ "small",
+ "small",
+ "large",
+ "small",
+ "small",
+ "large",
+ ],
+ "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
+ "E": [2, 4, 5, 5, 6, 6, 8, 9, 9],
+ }
+ )
+ result = crosstab(
+ index=df.C,
+ columns=[df.A, df.B],
+ margins=True,
+ margins_name="margin",
+ normalize=True,
+ )
+ expected = DataFrame(
+ [
+ [0.111111, 0.111111, 0.222222, 0.000000, 0.444444],
+ [0.111111, 0.111111, 0.111111, 0.222222, 0.555556],
+ [0.222222, 0.222222, 0.333333, 0.222222, 1.0],
+ ],
+ index=["large", "small", "margin"],
+ )
+ expected.columns = MultiIndex(
+ levels=[["bar", "foo", "margin"], ["", "one", "two"]],
+ codes=[[0, 0, 1, 1, 2], [1, 2, 1, 2, 0]],
+ names=["A", "B"],
+ )
+ expected.index.name = "C"
+ tm.assert_frame_equal(result, expected)
+
+ def test_margin_support_Float(self):
+ # GH 50313
+ # use Float64 formats and function aggfunc with margins
+ df = DataFrame(
+ {"A": [1, 2, 2, 1], "B": [3, 3, 4, 5], "C": [-1.0, 10.0, 1.0, 10.0]},
+ dtype="Float64",
+ )
+ result = crosstab(
+ df["A"],
+ df["B"],
+ values=df["C"],
+ aggfunc="sum",
+ margins=True,
+ )
+ expected = DataFrame(
+ [
+ [-1.0, pd.NA, 10.0, 9.0],
+ [10.0, 1.0, pd.NA, 11.0],
+ [9.0, 1.0, 10.0, 20.0],
+ ],
+ index=Index([1.0, 2.0, "All"], dtype="object", name="A"),
+ columns=Index([3.0, 4.0, 5.0, "All"], dtype="object", name="B"),
+ dtype="Float64",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_margin_with_ordered_categorical_column(self):
+ # GH 25278
+ df = DataFrame(
+ {
+ "First": ["B", "B", "C", "A", "B", "C"],
+ "Second": ["C", "B", "B", "B", "C", "A"],
+ }
+ )
+ df["First"] = df["First"].astype(CategoricalDtype(ordered=True))
+ customized_categories_order = ["C", "A", "B"]
+ df["First"] = df["First"].cat.reorder_categories(customized_categories_order)
+ result = crosstab(df["First"], df["Second"], margins=True)
+
+ expected_index = Index(["C", "A", "B", "All"], name="First")
+ expected_columns = Index(["A", "B", "C", "All"], name="Second")
+ expected_data = [[1, 1, 0, 2], [0, 1, 0, 1], [0, 1, 2, 3], [1, 3, 2, 6]]
+ expected = DataFrame(
+ expected_data, index=expected_index, columns=expected_columns
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("a_dtype", ["category", "int64"])
+@pytest.mark.parametrize("b_dtype", ["category", "int64"])
+def test_categoricals(a_dtype, b_dtype):
+ # https://github.com/pandas-dev/pandas/issues/37465
+ g = np.random.default_rng(2)
+ a = Series(g.integers(0, 3, size=100)).astype(a_dtype)
+ b = Series(g.integers(0, 2, size=100)).astype(b_dtype)
+ result = crosstab(a, b, margins=True, dropna=False)
+ columns = Index([0, 1, "All"], dtype="object", name="col_0")
+ index = Index([0, 1, 2, "All"], dtype="object", name="row_0")
+ values = [[10, 18, 28], [23, 16, 39], [17, 16, 33], [50, 50, 100]]
+ expected = DataFrame(values, index, columns)
+ tm.assert_frame_equal(result, expected)
+
+ # Verify when categorical does not have all values present
+ a.loc[a == 1] = 2
+ a_is_cat = isinstance(a.dtype, CategoricalDtype)
+ assert not a_is_cat or a.value_counts().loc[1] == 0
+ result = crosstab(a, b, margins=True, dropna=False)
+ values = [[10, 18, 28], [0, 0, 0], [40, 32, 72], [50, 50, 100]]
+ expected = DataFrame(values, index, columns)
+ if not a_is_cat:
+ expected = expected.loc[[0, 2, "All"]]
+ expected["All"] = expected["All"].astype("int64")
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_cut.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_cut.py
new file mode 100644
index 0000000000000000000000000000000000000000..909c10d3f73b20bf55ceba1663e89fd0c692cb30
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_cut.py
@@ -0,0 +1,828 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ Interval,
+ IntervalIndex,
+ Series,
+ TimedeltaIndex,
+ Timestamp,
+ cut,
+ date_range,
+ interval_range,
+ isna,
+ qcut,
+ timedelta_range,
+ to_datetime,
+)
+import pandas._testing as tm
+from pandas.api.types import CategoricalDtype
+import pandas.core.reshape.tile as tmod
+
+
+def test_simple():
+ data = np.ones(5, dtype="int64")
+ result = cut(data, 4, labels=False)
+
+ expected = np.array([1, 1, 1, 1, 1])
+ tm.assert_numpy_array_equal(result, expected, check_dtype=False)
+
+
+@pytest.mark.parametrize("func", [list, np.array])
+def test_bins(func):
+ data = func([0.2, 1.4, 2.5, 6.2, 9.7, 2.1])
+ result, bins = cut(data, 3, retbins=True)
+
+ intervals = IntervalIndex.from_breaks(bins.round(3))
+ intervals = intervals.take([0, 0, 0, 1, 2, 0])
+ expected = Categorical(intervals, ordered=True)
+
+ tm.assert_categorical_equal(result, expected)
+ tm.assert_almost_equal(bins, np.array([0.1905, 3.36666667, 6.53333333, 9.7]))
+
+
+def test_right():
+ data = np.array([0.2, 1.4, 2.5, 6.2, 9.7, 2.1, 2.575])
+ result, bins = cut(data, 4, right=True, retbins=True)
+
+ intervals = IntervalIndex.from_breaks(bins.round(3))
+ expected = Categorical(intervals, ordered=True)
+ expected = expected.take([0, 0, 0, 2, 3, 0, 0])
+
+ tm.assert_categorical_equal(result, expected)
+ tm.assert_almost_equal(bins, np.array([0.1905, 2.575, 4.95, 7.325, 9.7]))
+
+
+def test_no_right():
+ data = np.array([0.2, 1.4, 2.5, 6.2, 9.7, 2.1, 2.575])
+ result, bins = cut(data, 4, right=False, retbins=True)
+
+ intervals = IntervalIndex.from_breaks(bins.round(3), closed="left")
+ intervals = intervals.take([0, 0, 0, 2, 3, 0, 1])
+ expected = Categorical(intervals, ordered=True)
+
+ tm.assert_categorical_equal(result, expected)
+ tm.assert_almost_equal(bins, np.array([0.2, 2.575, 4.95, 7.325, 9.7095]))
+
+
+def test_bins_from_interval_index():
+ c = cut(range(5), 3)
+ expected = c
+ result = cut(range(5), bins=expected.categories)
+ tm.assert_categorical_equal(result, expected)
+
+ expected = Categorical.from_codes(
+ np.append(c.codes, -1), categories=c.categories, ordered=True
+ )
+ result = cut(range(6), bins=expected.categories)
+ tm.assert_categorical_equal(result, expected)
+
+
+def test_bins_from_interval_index_doc_example():
+ # Make sure we preserve the bins.
+ ages = np.array([10, 15, 13, 12, 23, 25, 28, 59, 60])
+ c = cut(ages, bins=[0, 18, 35, 70])
+ expected = IntervalIndex.from_tuples([(0, 18), (18, 35), (35, 70)])
+ tm.assert_index_equal(c.categories, expected)
+
+ result = cut([25, 20, 50], bins=c.categories)
+ tm.assert_index_equal(result.categories, expected)
+ tm.assert_numpy_array_equal(result.codes, np.array([1, 1, 2], dtype="int8"))
+
+
+def test_bins_not_overlapping_from_interval_index():
+ # see gh-23980
+ msg = "Overlapping IntervalIndex is not accepted"
+ ii = IntervalIndex.from_tuples([(0, 10), (2, 12), (4, 14)])
+
+ with pytest.raises(ValueError, match=msg):
+ cut([5, 6], bins=ii)
+
+
+def test_bins_not_monotonic():
+ msg = "bins must increase monotonically"
+ data = [0.2, 1.4, 2.5, 6.2, 9.7, 2.1]
+
+ with pytest.raises(ValueError, match=msg):
+ cut(data, [0.1, 1.5, 1, 10])
+
+
+@pytest.mark.parametrize(
+ "x, bins, expected",
+ [
+ (
+ date_range("2017-12-31", periods=3),
+ [Timestamp.min, Timestamp("2018-01-01"), Timestamp.max],
+ IntervalIndex.from_tuples(
+ [
+ (Timestamp.min, Timestamp("2018-01-01")),
+ (Timestamp("2018-01-01"), Timestamp.max),
+ ]
+ ),
+ ),
+ (
+ [-1, 0, 1],
+ np.array(
+ [np.iinfo(np.int64).min, 0, np.iinfo(np.int64).max], dtype="int64"
+ ),
+ IntervalIndex.from_tuples(
+ [(np.iinfo(np.int64).min, 0), (0, np.iinfo(np.int64).max)]
+ ),
+ ),
+ (
+ [
+ np.timedelta64(-1, "ns"),
+ np.timedelta64(0, "ns"),
+ np.timedelta64(1, "ns"),
+ ],
+ np.array(
+ [
+ np.timedelta64(-np.iinfo(np.int64).max, "ns"),
+ np.timedelta64(0, "ns"),
+ np.timedelta64(np.iinfo(np.int64).max, "ns"),
+ ]
+ ),
+ IntervalIndex.from_tuples(
+ [
+ (
+ np.timedelta64(-np.iinfo(np.int64).max, "ns"),
+ np.timedelta64(0, "ns"),
+ ),
+ (
+ np.timedelta64(0, "ns"),
+ np.timedelta64(np.iinfo(np.int64).max, "ns"),
+ ),
+ ]
+ ),
+ ),
+ ],
+)
+def test_bins_monotonic_not_overflowing(x, bins, expected):
+ # GH 26045
+ result = cut(x, bins)
+ tm.assert_index_equal(result.categories, expected)
+
+
+def test_wrong_num_labels():
+ msg = "Bin labels must be one fewer than the number of bin edges"
+ data = [0.2, 1.4, 2.5, 6.2, 9.7, 2.1]
+
+ with pytest.raises(ValueError, match=msg):
+ cut(data, [0, 1, 10], labels=["foo", "bar", "baz"])
+
+
+@pytest.mark.parametrize(
+ "x,bins,msg",
+ [
+ ([], 2, "Cannot cut empty array"),
+ ([1, 2, 3], 0.5, "`bins` should be a positive integer"),
+ ],
+)
+def test_cut_corner(x, bins, msg):
+ with pytest.raises(ValueError, match=msg):
+ cut(x, bins)
+
+
+@pytest.mark.parametrize("arg", [2, np.eye(2), DataFrame(np.eye(2))])
+@pytest.mark.parametrize("cut_func", [cut, qcut])
+def test_cut_not_1d_arg(arg, cut_func):
+ msg = "Input array must be 1 dimensional"
+ with pytest.raises(ValueError, match=msg):
+ cut_func(arg, 2)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ [0, 1, 2, 3, 4, np.inf],
+ [-np.inf, 0, 1, 2, 3, 4],
+ [-np.inf, 0, 1, 2, 3, 4, np.inf],
+ ],
+)
+def test_int_bins_with_inf(data):
+ # GH 24314
+ msg = "cannot specify integer `bins` when input data contains infinity"
+ with pytest.raises(ValueError, match=msg):
+ cut(data, bins=3)
+
+
+def test_cut_out_of_range_more():
+ # see gh-1511
+ name = "x"
+
+ ser = Series([0, -1, 0, 1, -3], name=name)
+ ind = cut(ser, [0, 1], labels=False)
+
+ exp = Series([np.nan, np.nan, np.nan, 0, np.nan], name=name)
+ tm.assert_series_equal(ind, exp)
+
+
+@pytest.mark.parametrize(
+ "right,breaks,closed",
+ [
+ (True, [-1e-3, 0.25, 0.5, 0.75, 1], "right"),
+ (False, [0, 0.25, 0.5, 0.75, 1 + 1e-3], "left"),
+ ],
+)
+def test_labels(right, breaks, closed):
+ arr = np.tile(np.arange(0, 1.01, 0.1), 4)
+
+ result, bins = cut(arr, 4, retbins=True, right=right)
+ ex_levels = IntervalIndex.from_breaks(breaks, closed=closed)
+ tm.assert_index_equal(result.categories, ex_levels)
+
+
+def test_cut_pass_series_name_to_factor():
+ name = "foo"
+ ser = Series(np.random.default_rng(2).standard_normal(100), name=name)
+
+ factor = cut(ser, 4)
+ assert factor.name == name
+
+
+def test_label_precision():
+ arr = np.arange(0, 0.73, 0.01)
+ result = cut(arr, 4, precision=2)
+
+ ex_levels = IntervalIndex.from_breaks([-0.00072, 0.18, 0.36, 0.54, 0.72])
+ tm.assert_index_equal(result.categories, ex_levels)
+
+
+@pytest.mark.parametrize("labels", [None, False])
+def test_na_handling(labels):
+ arr = np.arange(0, 0.75, 0.01)
+ arr[::3] = np.nan
+
+ result = cut(arr, 4, labels=labels)
+ result = np.asarray(result)
+
+ expected = np.where(isna(arr), np.nan, result)
+ tm.assert_almost_equal(result, expected)
+
+
+def test_inf_handling():
+ data = np.arange(6)
+ data_ser = Series(data, dtype="int64")
+
+ bins = [-np.inf, 2, 4, np.inf]
+ result = cut(data, bins)
+ result_ser = cut(data_ser, bins)
+
+ ex_uniques = IntervalIndex.from_breaks(bins)
+ tm.assert_index_equal(result.categories, ex_uniques)
+
+ assert result[5] == Interval(4, np.inf)
+ assert result[0] == Interval(-np.inf, 2)
+ assert result_ser[5] == Interval(4, np.inf)
+ assert result_ser[0] == Interval(-np.inf, 2)
+
+
+def test_cut_out_of_bounds():
+ arr = np.random.default_rng(2).standard_normal(100)
+ result = cut(arr, [-1, 0, 1])
+
+ mask = isna(result)
+ ex_mask = (arr < -1) | (arr > 1)
+ tm.assert_numpy_array_equal(mask, ex_mask)
+
+
+@pytest.mark.parametrize(
+ "get_labels,get_expected",
+ [
+ (
+ lambda labels: labels,
+ lambda labels: Categorical(
+ ["Medium"] + 4 * ["Small"] + ["Medium", "Large"],
+ categories=labels,
+ ordered=True,
+ ),
+ ),
+ (
+ lambda labels: Categorical.from_codes([0, 1, 2], labels),
+ lambda labels: Categorical.from_codes([1] + 4 * [0] + [1, 2], labels),
+ ),
+ ],
+)
+def test_cut_pass_labels(get_labels, get_expected):
+ bins = [0, 25, 50, 100]
+ arr = [50, 5, 10, 15, 20, 30, 70]
+ labels = ["Small", "Medium", "Large"]
+
+ result = cut(arr, bins, labels=get_labels(labels))
+ tm.assert_categorical_equal(result, get_expected(labels))
+
+
+def test_cut_pass_labels_compat():
+ # see gh-16459
+ arr = [50, 5, 10, 15, 20, 30, 70]
+ labels = ["Good", "Medium", "Bad"]
+
+ result = cut(arr, 3, labels=labels)
+ exp = cut(arr, 3, labels=Categorical(labels, categories=labels, ordered=True))
+ tm.assert_categorical_equal(result, exp)
+
+
+@pytest.mark.parametrize("x", [np.arange(11.0), np.arange(11.0) / 1e10])
+def test_round_frac_just_works(x):
+ # It works.
+ cut(x, 2)
+
+
+@pytest.mark.parametrize(
+ "val,precision,expected",
+ [
+ (-117.9998, 3, -118),
+ (117.9998, 3, 118),
+ (117.9998, 2, 118),
+ (0.000123456, 2, 0.00012),
+ ],
+)
+def test_round_frac(val, precision, expected):
+ # see gh-1979
+ result = tmod._round_frac(val, precision=precision)
+ assert result == expected
+
+
+def test_cut_return_intervals():
+ ser = Series([0, 1, 2, 3, 4, 5, 6, 7, 8])
+ result = cut(ser, 3)
+
+ exp_bins = np.linspace(0, 8, num=4).round(3)
+ exp_bins[0] -= 0.008
+
+ expected = Series(
+ IntervalIndex.from_breaks(exp_bins, closed="right").take(
+ [0, 0, 0, 1, 1, 1, 2, 2, 2]
+ )
+ ).astype(CategoricalDtype(ordered=True))
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_ret_bins():
+ # see gh-8589
+ ser = Series(np.arange(4))
+ result, bins = cut(ser, 2, retbins=True)
+
+ expected = Series(
+ IntervalIndex.from_breaks([-0.003, 1.5, 3], closed="right").repeat(2)
+ ).astype(CategoricalDtype(ordered=True))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "kwargs,msg",
+ [
+ ({"duplicates": "drop"}, None),
+ ({}, "Bin edges must be unique"),
+ ({"duplicates": "raise"}, "Bin edges must be unique"),
+ ({"duplicates": "foo"}, "invalid value for 'duplicates' parameter"),
+ ],
+)
+def test_cut_duplicates_bin(kwargs, msg):
+ # see gh-20947
+ bins = [0, 2, 4, 6, 10, 10]
+ values = Series(np.array([1, 3, 5, 7, 9]), index=["a", "b", "c", "d", "e"])
+
+ if msg is not None:
+ with pytest.raises(ValueError, match=msg):
+ cut(values, bins, **kwargs)
+ else:
+ result = cut(values, bins, **kwargs)
+ expected = cut(values, pd.unique(np.asarray(bins)))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("data", [9.0, -9.0, 0.0])
+@pytest.mark.parametrize("length", [1, 2])
+def test_single_bin(data, length):
+ # see gh-14652, gh-15428
+ ser = Series([data] * length)
+ result = cut(ser, 1, labels=False)
+
+ expected = Series([0] * length, dtype=np.intp)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "values,threshold",
+ [
+ ([0.1, 0.1, 0.1], 0.001), # small positive values
+ ([-0.1, -0.1, -0.1], 0.001), # negative values
+ ([0.01, 0.01, 0.01], 0.0001), # very small values
+ ],
+)
+def test_single_bin_edge_adjustment(values, threshold):
+ # gh-58517 - edge adjustment mutation when all values are same
+ result, bins = cut(values, 3, retbins=True)
+
+ bin_range = bins[-1] - bins[0]
+ assert bin_range < threshold
+
+
+@pytest.mark.parametrize(
+ "array_1_writeable,array_2_writeable", [(True, True), (True, False), (False, False)]
+)
+def test_cut_read_only(array_1_writeable, array_2_writeable):
+ # issue 18773
+ array_1 = np.arange(0, 100, 10)
+ array_1.flags.writeable = array_1_writeable
+
+ array_2 = np.arange(0, 100, 10)
+ array_2.flags.writeable = array_2_writeable
+
+ hundred_elements = np.arange(100)
+ tm.assert_categorical_equal(
+ cut(hundred_elements, array_1), cut(hundred_elements, array_2)
+ )
+
+
+@pytest.mark.parametrize(
+ "conv",
+ [
+ lambda v: Timestamp(v),
+ lambda v: to_datetime(v),
+ lambda v: np.datetime64(v),
+ lambda v: Timestamp(v).to_pydatetime(),
+ ],
+)
+def test_datetime_bin(conv):
+ data = [np.datetime64("2012-12-13"), np.datetime64("2012-12-15")]
+ bin_data = ["2012-12-12", "2012-12-14", "2012-12-16"]
+
+ expected = Series(
+ IntervalIndex(
+ [
+ Interval(Timestamp(bin_data[0]), Timestamp(bin_data[1])),
+ Interval(Timestamp(bin_data[1]), Timestamp(bin_data[2])),
+ ]
+ )
+ )
+
+ bins = [conv(v) for v in bin_data]
+ result = Series(cut(data, bins=bins))
+
+ if type(bins[0]) is np.datetime64:
+ # The bins have microsecond dtype -> so does result
+ expected = expected.astype("interval[datetime64[s]]")
+
+ expected = expected.astype(CategoricalDtype(ordered=True))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("box", [Series, Index, np.array, list])
+def test_datetime_cut(unit, box):
+ # see gh-14714
+ #
+ # Testing time data when it comes in various collection types.
+ data = to_datetime(["2013-01-01", "2013-01-02", "2013-01-03"]).astype(f"M8[{unit}]")
+ data = box(data)
+ result, _ = cut(data, 3, retbins=True)
+
+ if unit == "s":
+ # See https://github.com/pandas-dev/pandas/pull/56101#discussion_r1405325425
+ # for why we round to 8 seconds instead of 7
+ left = DatetimeIndex(
+ ["2012-12-31 23:57:08", "2013-01-01 16:00:00", "2013-01-02 08:00:00"],
+ dtype=f"M8[{unit}]",
+ )
+ else:
+ left = DatetimeIndex(
+ [
+ "2012-12-31 23:57:07.200000",
+ "2013-01-01 16:00:00",
+ "2013-01-02 08:00:00",
+ ],
+ dtype=f"M8[{unit}]",
+ )
+ right = DatetimeIndex(
+ ["2013-01-01 16:00:00", "2013-01-02 08:00:00", "2013-01-03 00:00:00"],
+ dtype=f"M8[{unit}]",
+ )
+
+ exp_intervals = IntervalIndex.from_arrays(left, right)
+ expected = Series(exp_intervals).astype(CategoricalDtype(ordered=True))
+ tm.assert_series_equal(Series(result), expected)
+
+
+@pytest.mark.parametrize("box", [list, np.array, Index, Series])
+def test_datetime_tz_cut_mismatched_tzawareness(box):
+ # GH#54964
+ bins = box(
+ [
+ Timestamp("2013-01-01 04:57:07.200000"),
+ Timestamp("2013-01-01 21:00:00"),
+ Timestamp("2013-01-02 13:00:00"),
+ Timestamp("2013-01-03 05:00:00"),
+ ]
+ )
+ ser = Series(date_range("20130101", periods=3, tz="US/Eastern"))
+
+ msg = "Cannot use timezone-naive bins with timezone-aware values"
+ with pytest.raises(ValueError, match=msg):
+ cut(ser, bins)
+
+
+@pytest.mark.parametrize(
+ "bins",
+ [
+ 3,
+ [
+ Timestamp("2013-01-01 04:57:07.200000", tz="UTC").tz_convert("US/Eastern"),
+ Timestamp("2013-01-01 21:00:00", tz="UTC").tz_convert("US/Eastern"),
+ Timestamp("2013-01-02 13:00:00", tz="UTC").tz_convert("US/Eastern"),
+ Timestamp("2013-01-03 05:00:00", tz="UTC").tz_convert("US/Eastern"),
+ ],
+ ],
+)
+@pytest.mark.parametrize("box", [list, np.array, Index, Series])
+def test_datetime_tz_cut(bins, box):
+ # see gh-19872
+ tz = "US/Eastern"
+ ser = Series(date_range("20130101", periods=3, tz=tz, unit="ns"))
+
+ if not isinstance(bins, int):
+ bins = box(bins)
+
+ result = cut(ser, bins)
+ ii = IntervalIndex(
+ [
+ Interval(
+ Timestamp("2012-12-31 23:57:07.200000", tz=tz),
+ Timestamp("2013-01-01 16:00:00", tz=tz),
+ ),
+ Interval(
+ Timestamp("2013-01-01 16:00:00", tz=tz),
+ Timestamp("2013-01-02 08:00:00", tz=tz),
+ ),
+ Interval(
+ Timestamp("2013-01-02 08:00:00", tz=tz),
+ Timestamp("2013-01-03 00:00:00", tz=tz),
+ ),
+ ]
+ )
+ if isinstance(bins, int):
+ # the dtype is inferred from ser, which has nanosecond unit
+ ii = ii.astype("interval[datetime64[ns, US/Eastern]]")
+ expected = Series(ii).astype(CategoricalDtype(ordered=True))
+ tm.assert_series_equal(result, expected)
+
+
+def test_datetime_nan_error():
+ msg = "bins must be of datetime64 dtype"
+
+ with pytest.raises(ValueError, match=msg):
+ cut(date_range("20130101", periods=3), bins=[0, 2, 4])
+
+
+def test_datetime_nan_mask():
+ result = cut(
+ date_range("20130102", periods=5), bins=date_range("20130101", periods=2)
+ )
+
+ mask = result.categories.isna()
+ tm.assert_numpy_array_equal(mask, np.array([False]))
+
+ mask = result.isna()
+ tm.assert_numpy_array_equal(mask, np.array([False, True, True, True, True]))
+
+
+@pytest.mark.parametrize("tz", [None, "UTC", "US/Pacific"])
+def test_datetime_cut_roundtrip(tz, unit):
+ # see gh-19891
+ ser = Series(date_range("20180101", periods=3, tz=tz, unit=unit))
+ result, result_bins = cut(ser, 2, retbins=True)
+
+ expected = cut(ser, result_bins)
+ tm.assert_series_equal(result, expected)
+
+ if unit == "s":
+ # TODO: constructing DatetimeIndex with dtype="M8[s]" without truncating
+ # the first entry here raises in array_to_datetime. Should truncate
+ # instead of raising?
+ # See https://github.com/pandas-dev/pandas/pull/56101#discussion_r1405325425
+ # for why we round to 8 seconds instead of 7
+ expected_bins = DatetimeIndex(
+ ["2017-12-31 23:57:08", "2018-01-02 00:00:00", "2018-01-03 00:00:00"],
+ dtype=f"M8[{unit}]",
+ )
+ else:
+ expected_bins = DatetimeIndex(
+ [
+ "2017-12-31 23:57:07.200000",
+ "2018-01-02 00:00:00",
+ "2018-01-03 00:00:00",
+ ],
+ dtype=f"M8[{unit}]",
+ )
+ expected_bins = expected_bins.tz_localize(tz)
+ tm.assert_index_equal(result_bins, expected_bins)
+
+
+def test_timedelta_cut_roundtrip():
+ # see gh-19891
+ ser = Series(timedelta_range("1day", periods=3))
+ result, result_bins = cut(ser, 2, retbins=True)
+
+ expected = cut(ser, result_bins)
+ tm.assert_series_equal(result, expected)
+
+ expected_bins = TimedeltaIndex(
+ ["0 days 23:57:07.200000", "2 days 00:00:00", "3 days 00:00:00"]
+ )
+ tm.assert_index_equal(result_bins, expected_bins)
+
+
+@pytest.mark.parametrize("bins", [6, 7])
+@pytest.mark.parametrize(
+ "box, compare",
+ [
+ (Series, tm.assert_series_equal),
+ (np.array, tm.assert_categorical_equal),
+ (list, tm.assert_equal),
+ ],
+)
+def test_cut_bool_coercion_to_int(bins, box, compare):
+ # issue 20303
+ data_expected = box([0, 1, 1, 0, 1] * 10)
+ data_result = box([False, True, True, False, True] * 10)
+ expected = cut(data_expected, bins, duplicates="drop")
+ result = cut(data_result, bins, duplicates="drop")
+ compare(result, expected)
+
+
+@pytest.mark.parametrize("labels", ["foo", 1, True])
+def test_cut_incorrect_labels(labels):
+ # GH 13318
+ values = range(5)
+ msg = "Bin labels must either be False, None or passed in as a list-like argument"
+ with pytest.raises(ValueError, match=msg):
+ cut(values, 4, labels=labels)
+
+
+@pytest.mark.parametrize("bins", [3, [0, 5, 15]])
+@pytest.mark.parametrize("right", [True, False])
+@pytest.mark.parametrize("include_lowest", [True, False])
+def test_cut_nullable_integer(bins, right, include_lowest):
+ a = np.random.default_rng(2).integers(0, 10, size=50).astype(float)
+ a[::2] = np.nan
+ b = a.astype(object)
+ b[::2] = pd.NA
+ result = cut(
+ pd.array(b, dtype="Int64"), bins, right=right, include_lowest=include_lowest
+ )
+ expected = cut(a, bins, right=right, include_lowest=include_lowest)
+ tm.assert_categorical_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data, bins, labels, expected_codes, expected_labels",
+ [
+ ([15, 17, 19], [14, 16, 18, 20], ["A", "B", "A"], [0, 1, 0], ["A", "B"]),
+ ([1, 3, 5], [0, 2, 4, 6, 8], [2, 0, 1, 2], [2, 0, 1], [0, 1, 2]),
+ ],
+)
+def test_cut_non_unique_labels(data, bins, labels, expected_codes, expected_labels):
+ # GH 33141
+ result = cut(data, bins=bins, labels=labels, ordered=False)
+ expected = Categorical.from_codes(
+ expected_codes, categories=expected_labels, ordered=False
+ )
+ tm.assert_categorical_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data, bins, labels, expected_codes, expected_labels",
+ [
+ ([15, 17, 19], [14, 16, 18, 20], ["C", "B", "A"], [0, 1, 2], ["C", "B", "A"]),
+ ([1, 3, 5], [0, 2, 4, 6, 8], [3, 0, 1, 2], [0, 1, 2], [3, 0, 1, 2]),
+ ],
+)
+def test_cut_unordered_labels(data, bins, labels, expected_codes, expected_labels):
+ # GH 33141
+ result = cut(data, bins=bins, labels=labels, ordered=False)
+ expected = Categorical.from_codes(
+ expected_codes, categories=expected_labels, ordered=False
+ )
+ tm.assert_categorical_equal(result, expected)
+
+
+def test_cut_unordered_with_missing_labels_raises_error():
+ # GH 33141
+ msg = "'labels' must be provided if 'ordered = False'"
+ with pytest.raises(ValueError, match=msg):
+ cut([0.5, 3], bins=[0, 1, 2], ordered=False)
+
+
+def test_cut_unordered_with_series_labels():
+ # https://github.com/pandas-dev/pandas/issues/36603
+ ser = Series([1, 2, 3, 4, 5])
+ bins = Series([0, 2, 4, 6])
+ labels = Series(["a", "b", "c"])
+ result = cut(ser, bins=bins, labels=labels, ordered=False)
+ expected = Series(["a", "a", "b", "b", "c"], dtype="category")
+ tm.assert_series_equal(result, expected)
+
+
+def test_cut_no_warnings():
+ df = DataFrame({"value": np.random.default_rng(2).integers(0, 100, 20)})
+ labels = [f"{i} - {i + 9}" for i in range(0, 100, 10)]
+ with tm.assert_produces_warning(False):
+ df["group"] = cut(df.value, range(0, 105, 10), right=False, labels=labels)
+
+
+def test_cut_with_duplicated_index_lowest_included():
+ # GH 42185
+ expected = Series(
+ [Interval(-0.001, 2, closed="right")] * 3
+ + [Interval(2, 4, closed="right"), Interval(-0.001, 2, closed="right")],
+ index=[0, 1, 2, 3, 0],
+ dtype="category",
+ ).cat.as_ordered()
+
+ ser = Series([0, 1, 2, 3, 0], index=[0, 1, 2, 3, 0])
+ result = cut(ser, bins=[0, 2, 4], include_lowest=True)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.filterwarnings("ignore:invalid value encountered in cast:RuntimeWarning")
+def test_cut_with_nonexact_categorical_indices():
+ # GH 42424
+
+ ser = Series(range(100))
+ ser1 = cut(ser, 10).value_counts().head(5)
+ ser2 = cut(ser, 10).value_counts().tail(5)
+ result = DataFrame({"1": ser1, "2": ser2})
+
+ index = pd.CategoricalIndex(
+ [
+ Interval(-0.099, 9.9, closed="right"),
+ Interval(9.9, 19.8, closed="right"),
+ Interval(19.8, 29.7, closed="right"),
+ Interval(29.7, 39.6, closed="right"),
+ Interval(39.6, 49.5, closed="right"),
+ Interval(49.5, 59.4, closed="right"),
+ Interval(59.4, 69.3, closed="right"),
+ Interval(69.3, 79.2, closed="right"),
+ Interval(79.2, 89.1, closed="right"),
+ Interval(89.1, 99, closed="right"),
+ ],
+ ordered=True,
+ )
+
+ expected = DataFrame(
+ {"1": [10] * 5 + [np.nan] * 5, "2": [np.nan] * 5 + [10] * 5}, index=index
+ )
+
+ tm.assert_frame_equal(expected, result)
+
+
+def test_cut_with_timestamp_tuple_labels():
+ # GH 40661
+ labels = [(Timestamp(10),), (Timestamp(20),), (Timestamp(30),)]
+ result = cut([2, 4, 6], bins=[1, 3, 5, 7], labels=labels)
+
+ expected = Categorical.from_codes([0, 1, 2], labels, ordered=True)
+ tm.assert_categorical_equal(result, expected)
+
+
+def test_cut_bins_datetime_intervalindex():
+ # https://github.com/pandas-dev/pandas/issues/46218
+ bins = interval_range(Timestamp("2022-02-25"), Timestamp("2022-02-27"), freq="1D")
+ # passing Series instead of list is important to trigger bug
+ result = cut(Series([Timestamp("2022-02-26")]), bins=bins)
+ expected = Categorical.from_codes([0], bins, ordered=True)
+ tm.assert_categorical_equal(result.array, expected)
+
+
+def test_cut_with_nullable_int64():
+ # GH 30787
+ series = Series([0, 1, 2, 3, 4, pd.NA, 6, 7], dtype="Int64")
+ bins = [0, 2, 4, 6, 8]
+ intervals = IntervalIndex.from_breaks(bins)
+
+ expected = Series(
+ Categorical.from_codes([-1, 0, 0, 1, 1, -1, 2, 3], intervals, ordered=True)
+ )
+
+ result = cut(series, bins=bins)
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_cut_datetime_array_no_attributeerror():
+ # GH 55431
+ ser = Series(to_datetime(["2023-10-06 12:00:00+0000", "2023-10-07 12:00:00+0000"]))
+
+ result = cut(ser.array, bins=2)
+
+ categories = result.categories
+ expected = Categorical.from_codes([0, 1], categories=categories, ordered=True)
+
+ tm.assert_categorical_equal(
+ result, expected, check_dtype=True, check_category_order=True
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_from_dummies.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_from_dummies.py
new file mode 100644
index 0000000000000000000000000000000000000000..0997baf7c3f74bf6b73e62e02283cea9eb1bd696
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_from_dummies.py
@@ -0,0 +1,477 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+ from_dummies,
+ get_dummies,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def dummies_basic():
+ return DataFrame(
+ {
+ "col1_a": [1, 0, 1],
+ "col1_b": [0, 1, 0],
+ "col2_a": [0, 1, 0],
+ "col2_b": [1, 0, 0],
+ "col2_c": [0, 0, 1],
+ },
+ )
+
+
+@pytest.fixture
+def dummies_with_unassigned():
+ return DataFrame(
+ {
+ "col1_a": [1, 0, 0],
+ "col1_b": [0, 1, 0],
+ "col2_a": [0, 1, 0],
+ "col2_b": [0, 0, 0],
+ "col2_c": [0, 0, 1],
+ },
+ )
+
+
+def test_error_wrong_data_type():
+ dummies = [0, 1, 0]
+ with pytest.raises(
+ TypeError,
+ match=r"Expected 'data' to be a 'DataFrame'; Received 'data' of type: list",
+ ):
+ from_dummies(dummies)
+
+
+def test_error_no_prefix_contains_unassigned():
+ dummies = DataFrame({"a": [1, 0, 0], "b": [0, 1, 0]})
+ with pytest.raises(
+ ValueError,
+ match=(
+ r"Dummy DataFrame contains unassigned value\(s\); "
+ r"First instance in row: 2"
+ ),
+ ):
+ from_dummies(dummies)
+
+
+def test_error_no_prefix_wrong_default_category_type():
+ dummies = DataFrame({"a": [1, 0, 1], "b": [0, 1, 1]})
+ with pytest.raises(
+ TypeError,
+ match=(
+ r"Expected 'default_category' to be of type 'None', 'Hashable', or 'dict'; "
+ r"Received 'default_category' of type: list"
+ ),
+ ):
+ from_dummies(dummies, default_category=["c", "d"])
+
+
+def test_error_no_prefix_multi_assignment():
+ dummies = DataFrame({"a": [1, 0, 1], "b": [0, 1, 1]})
+ with pytest.raises(
+ ValueError,
+ match=(
+ r"Dummy DataFrame contains multi-assignment\(s\); "
+ r"First instance in row: 2"
+ ),
+ ):
+ from_dummies(dummies)
+
+
+def test_error_no_prefix_contains_nan():
+ dummies = DataFrame({"a": [1, 0, 0], "b": [0, 1, np.nan]})
+ with pytest.raises(
+ ValueError, match=r"Dummy DataFrame contains NA value in column: 'b'"
+ ):
+ from_dummies(dummies)
+
+
+def test_error_contains_non_dummies():
+ dummies = DataFrame(
+ {"a": [1, 6, 3, 1], "b": [0, 1, 0, 2], "c": ["c1", "c2", "c3", "c4"]}
+ )
+ with pytest.raises(
+ TypeError,
+ match=r"Passed DataFrame contains non-dummy data",
+ ):
+ from_dummies(dummies)
+
+
+def test_error_with_prefix_multiple_separators():
+ dummies = DataFrame(
+ {
+ "col1_a": [1, 0, 1],
+ "col1_b": [0, 1, 0],
+ "col2-a": [0, 1, 0],
+ "col2-b": [1, 0, 1],
+ },
+ )
+ with pytest.raises(
+ ValueError,
+ match=(r"Separator not specified for column: col2-a"),
+ ):
+ from_dummies(dummies, sep="_")
+
+
+def test_error_with_prefix_sep_wrong_type(dummies_basic):
+ with pytest.raises(
+ TypeError,
+ match=(
+ r"Expected 'sep' to be of type 'str' or 'None'; "
+ r"Received 'sep' of type: list"
+ ),
+ ):
+ from_dummies(dummies_basic, sep=["_"])
+
+
+def test_error_with_prefix_contains_unassigned(dummies_with_unassigned):
+ with pytest.raises(
+ ValueError,
+ match=(
+ r"Dummy DataFrame contains unassigned value\(s\); "
+ r"First instance in row: 2"
+ ),
+ ):
+ from_dummies(dummies_with_unassigned, sep="_")
+
+
+def test_error_with_prefix_default_category_wrong_type(dummies_with_unassigned):
+ with pytest.raises(
+ TypeError,
+ match=(
+ r"Expected 'default_category' to be of type 'None', 'Hashable', or 'dict'; "
+ r"Received 'default_category' of type: list"
+ ),
+ ):
+ from_dummies(dummies_with_unassigned, sep="_", default_category=["x", "y"])
+
+
+def test_error_with_prefix_default_category_dict_not_complete(
+ dummies_with_unassigned,
+):
+ with pytest.raises(
+ ValueError,
+ match=(
+ r"Length of 'default_category' \(1\) did not match "
+ r"the length of the columns being encoded \(2\)"
+ ),
+ ):
+ from_dummies(dummies_with_unassigned, sep="_", default_category={"col1": "x"})
+
+
+def test_error_with_prefix_contains_nan(dummies_basic):
+ # Set float64 dtype to avoid upcast when setting np.nan
+ dummies_basic["col2_c"] = dummies_basic["col2_c"].astype("float64")
+ dummies_basic.loc[2, "col2_c"] = np.nan
+ with pytest.raises(
+ ValueError, match=r"Dummy DataFrame contains NA value in column: 'col2_c'"
+ ):
+ from_dummies(dummies_basic, sep="_")
+
+
+def test_error_with_prefix_contains_non_dummies(dummies_basic):
+ # Set object dtype to avoid upcast when setting "str"
+ dummies_basic["col2_c"] = dummies_basic["col2_c"].astype(object)
+ dummies_basic.loc[2, "col2_c"] = "str"
+ with pytest.raises(TypeError, match=r"Passed DataFrame contains non-dummy data"):
+ from_dummies(dummies_basic, sep="_")
+
+
+def test_error_with_prefix_double_assignment():
+ dummies = DataFrame(
+ {
+ "col1_a": [1, 0, 1],
+ "col1_b": [1, 1, 0],
+ "col2_a": [0, 1, 0],
+ "col2_b": [1, 0, 0],
+ "col2_c": [0, 0, 1],
+ },
+ )
+ with pytest.raises(
+ ValueError,
+ match=(
+ r"Dummy DataFrame contains multi-assignment\(s\); "
+ r"First instance in row: 0"
+ ),
+ ):
+ from_dummies(dummies, sep="_")
+
+
+def test_roundtrip_series_to_dataframe():
+ categories = Series(["a", "b", "c", "a"])
+ dummies = get_dummies(categories)
+ result = from_dummies(dummies)
+ expected = DataFrame({"": ["a", "b", "c", "a"]})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_roundtrip_single_column_dataframe():
+ categories = DataFrame({"": ["a", "b", "c", "a"]})
+ dummies = get_dummies(categories)
+ result = from_dummies(dummies, sep="_")
+ expected = categories
+ tm.assert_frame_equal(result, expected)
+
+
+def test_roundtrip_with_prefixes():
+ categories = DataFrame({"col1": ["a", "b", "a"], "col2": ["b", "a", "c"]})
+ dummies = get_dummies(categories)
+ result = from_dummies(dummies, sep="_")
+ expected = categories
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_prefix_string_cats_basic():
+ dummies = DataFrame({"a": [1, 0, 0, 1], "b": [0, 1, 0, 0], "c": [0, 0, 1, 0]})
+ expected = DataFrame({"": ["a", "b", "c", "a"]})
+ result = from_dummies(dummies)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_prefix_string_cats_basic_bool_values():
+ dummies = DataFrame(
+ {
+ "a": [True, False, False, True],
+ "b": [False, True, False, False],
+ "c": [False, False, True, False],
+ }
+ )
+ expected = DataFrame({"": ["a", "b", "c", "a"]})
+ result = from_dummies(dummies)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_prefix_string_cats_basic_mixed_bool_values():
+ dummies = DataFrame(
+ {"a": [1, 0, 0, 1], "b": [False, True, False, False], "c": [0, 0, 1, 0]}
+ )
+ expected = DataFrame({"": ["a", "b", "c", "a"]})
+ result = from_dummies(dummies)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_prefix_int_cats_basic():
+ dummies = DataFrame(
+ {1: [1, 0, 0, 0], 25: [0, 1, 0, 0], 2: [0, 0, 1, 0], 5: [0, 0, 0, 1]}
+ )
+ expected = DataFrame({"": [1, 25, 2, 5]})
+ result = from_dummies(dummies)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_prefix_float_cats_basic():
+ dummies = DataFrame(
+ {1.0: [1, 0, 0, 0], 25.0: [0, 1, 0, 0], 2.5: [0, 0, 1, 0], 5.84: [0, 0, 0, 1]}
+ )
+ expected = DataFrame({"": [1.0, 25.0, 2.5, 5.84]})
+ result = from_dummies(dummies)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_prefix_mixed_cats_basic():
+ dummies = DataFrame(
+ {
+ 1.23: [1, 0, 0, 0, 0],
+ "c": [0, 1, 0, 0, 0],
+ 2: [0, 0, 1, 0, 0],
+ False: [0, 0, 0, 1, 0],
+ None: [0, 0, 0, 0, 1],
+ }
+ )
+ expected = DataFrame({"": [1.23, "c", 2, False, None]}, dtype="object")
+ result = from_dummies(dummies)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_no_prefix_string_cats_contains_get_dummies_NaN_column():
+ dummies = DataFrame({"a": [1, 0, 0], "b": [0, 1, 0], "NaN": [0, 0, 1]})
+ expected = DataFrame({"": ["a", "b", "NaN"]})
+ result = from_dummies(dummies)
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "default_category, expected",
+ [
+ pytest.param(
+ "c",
+ {"": ["a", "b", "c"]},
+ id="default_category is a str",
+ ),
+ pytest.param(
+ 1,
+ {"": ["a", "b", 1]},
+ id="default_category is an int",
+ ),
+ pytest.param(
+ 1.25,
+ {"": ["a", "b", 1.25]},
+ id="default_category is a float",
+ ),
+ pytest.param(
+ 0,
+ {"": ["a", "b", 0]},
+ id="default_category is a 0",
+ ),
+ pytest.param(
+ False,
+ {"": ["a", "b", False]},
+ id="default_category is a bool",
+ ),
+ pytest.param(
+ (1, 2),
+ {"": ["a", "b", (1, 2)]},
+ id="default_category is a tuple",
+ ),
+ ],
+)
+def test_no_prefix_string_cats_default_category(
+ default_category, expected, using_infer_string
+):
+ dummies = DataFrame({"a": [1, 0, 0], "b": [0, 1, 0]})
+ result = from_dummies(dummies, default_category=default_category)
+ expected = DataFrame(expected, dtype=dummies.columns.dtype)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_with_prefix_basic(dummies_basic):
+ expected = DataFrame({"col1": ["a", "b", "a"], "col2": ["b", "a", "c"]})
+ result = from_dummies(dummies_basic, sep="_")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_with_prefix_contains_get_dummies_NaN_column():
+ dummies = DataFrame(
+ {
+ "col1_a": [1, 0, 0],
+ "col1_b": [0, 1, 0],
+ "col1_NaN": [0, 0, 1],
+ "col2_a": [0, 1, 0],
+ "col2_b": [0, 0, 0],
+ "col2_c": [0, 0, 1],
+ "col2_NaN": [1, 0, 0],
+ },
+ )
+ expected = DataFrame({"col1": ["a", "b", "NaN"], "col2": ["NaN", "a", "c"]})
+ result = from_dummies(dummies, sep="_")
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "default_category, expected",
+ [
+ pytest.param(
+ "x",
+ {"col1": ["a", "b", "x"], "col2": ["x", "a", "c"]},
+ id="default_category is a str",
+ ),
+ pytest.param(
+ 0,
+ {"col1": ["a", "b", 0], "col2": [0, "a", "c"]},
+ id="default_category is a 0",
+ ),
+ pytest.param(
+ False,
+ {"col1": ["a", "b", False], "col2": [False, "a", "c"]},
+ id="default_category is a False",
+ ),
+ pytest.param(
+ {"col2": 1, "col1": 2.5},
+ {"col1": ["a", "b", 2.5], "col2": [1, "a", "c"]},
+ id="default_category is a dict with int and float values",
+ ),
+ pytest.param(
+ {"col2": None, "col1": False},
+ {"col1": ["a", "b", False], "col2": [None, "a", "c"]},
+ id="default_category is a dict with bool and None values",
+ ),
+ pytest.param(
+ {"col2": (1, 2), "col1": [1.25, False]},
+ {"col1": ["a", "b", [1.25, False]], "col2": [(1, 2), "a", "c"]},
+ id="default_category is a dict with list and tuple values",
+ ),
+ ],
+)
+def test_with_prefix_default_category(
+ dummies_with_unassigned, default_category, expected, using_infer_string
+):
+ result = from_dummies(
+ dummies_with_unassigned, sep="_", default_category=default_category
+ )
+ expected = DataFrame(expected)
+ if using_infer_string:
+ expected = expected.astype("str")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_ea_categories():
+ # GH 54300
+ df = DataFrame({"a": [1, 0, 0, 1], "b": [0, 1, 0, 0], "c": [0, 0, 1, 0]})
+ df.columns = df.columns.astype("string[python]")
+ result = from_dummies(df)
+ expected = DataFrame({"": Series(list("abca"), dtype="string[python]")})
+ tm.assert_frame_equal(result, expected)
+
+
+def test_ea_categories_with_sep():
+ # GH 54300
+ df = DataFrame(
+ {
+ "col1_a": [1, 0, 1],
+ "col1_b": [0, 1, 0],
+ "col2_a": [0, 1, 0],
+ "col2_b": [1, 0, 0],
+ "col2_c": [0, 0, 1],
+ }
+ )
+ df.columns = df.columns.astype("string[python]")
+ result = from_dummies(df, sep="_")
+ expected = DataFrame(
+ {
+ "col1": Series(list("aba"), dtype="string[python]"),
+ "col2": Series(list("bac"), dtype="string[python]"),
+ }
+ )
+ expected.columns = expected.columns.astype("string[python]")
+ tm.assert_frame_equal(result, expected)
+
+
+def test_maintain_original_index():
+ # GH 54300
+ df = DataFrame(
+ {"a": [1, 0, 0, 1], "b": [0, 1, 0, 0], "c": [0, 0, 1, 0]}, index=list("abcd")
+ )
+ result = from_dummies(df)
+ expected = DataFrame({"": list("abca")}, index=list("abcd"))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_int_columns_with_float_default():
+ # https://github.com/pandas-dev/pandas/pull/60694
+ df = DataFrame(
+ {
+ 3: [1, 0, 0],
+ 4: [0, 1, 0],
+ },
+ )
+ with pytest.raises(ValueError, match="Trying to coerce float values to integers"):
+ from_dummies(df, default_category=0.5)
+
+
+def test_object_dtype_preserved():
+ # https://github.com/pandas-dev/pandas/pull/60694
+ # When the input has object dtype, the result should as
+ # well even when infer_string is True.
+ df = DataFrame(
+ {
+ "x": [1, 0, 0],
+ "y": [0, 1, 0],
+ },
+ )
+ df.columns = df.columns.astype("object")
+ result = from_dummies(df, default_category="z")
+ expected = DataFrame({"": ["x", "y", "z"]}, dtype="object")
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_get_dummies.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_get_dummies.py
new file mode 100644
index 0000000000000000000000000000000000000000..c776e7b2e3d9a4f6ab99042cf95a6996d1ba5b60
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_get_dummies.py
@@ -0,0 +1,741 @@
+import re
+import unicodedata
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas.core.dtypes.common import is_integer_dtype
+
+import pandas as pd
+from pandas import (
+ ArrowDtype,
+ Categorical,
+ CategoricalDtype,
+ CategoricalIndex,
+ DataFrame,
+ Index,
+ RangeIndex,
+ Series,
+ SparseDtype,
+ get_dummies,
+)
+import pandas._testing as tm
+from pandas.core.arrays.sparse import SparseArray
+
+try:
+ import pyarrow as pa
+except ImportError:
+ pa = None
+
+
+class TestGetDummies:
+ @pytest.fixture
+ def df(self):
+ return DataFrame({"A": ["a", "b", "a"], "B": ["b", "b", "c"], "C": [1, 2, 3]})
+
+ @pytest.fixture(params=["uint8", "i8", np.float64, bool, None])
+ def dtype(self, request):
+ return np.dtype(request.param)
+
+ @pytest.fixture(params=["dense", "sparse"])
+ def sparse(self, request):
+ # params are strings to simplify reading test results,
+ # e.g. TestGetDummies::test_basic[uint8-sparse] instead of [uint8-True]
+ return request.param == "sparse"
+
+ def effective_dtype(self, dtype):
+ if dtype is None:
+ return np.uint8
+ return dtype
+
+ def test_get_dummies_raises_on_dtype_object(self, df):
+ msg = "dtype=object is not a valid dtype for get_dummies"
+ with pytest.raises(ValueError, match=msg):
+ get_dummies(df, dtype="object")
+
+ def test_get_dummies_basic(self, sparse, dtype):
+ s_list = list("abc")
+ s_series = Series(s_list)
+ s_series_index = Series(s_list, list("ABC"))
+
+ expected = DataFrame(
+ {"a": [1, 0, 0], "b": [0, 1, 0], "c": [0, 0, 1]},
+ dtype=self.effective_dtype(dtype),
+ )
+ if sparse:
+ if dtype.kind == "b":
+ expected = expected.apply(SparseArray, fill_value=False)
+ else:
+ expected = expected.apply(SparseArray, fill_value=0.0)
+ result = get_dummies(s_list, sparse=sparse, dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+ result = get_dummies(s_series, sparse=sparse, dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+ expected.index = list("ABC")
+ result = get_dummies(s_series_index, sparse=sparse, dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+ def test_get_dummies_basic_types(self, sparse, dtype, using_infer_string):
+ # GH 10531
+ s_list = list("abc")
+ s_series = Series(s_list)
+ s_df = DataFrame(
+ {"a": [0, 1, 0, 1, 2], "b": ["A", "A", "B", "C", "C"], "c": [2, 3, 3, 3, 2]}
+ )
+
+ expected = DataFrame(
+ {"a": [1, 0, 0], "b": [0, 1, 0], "c": [0, 0, 1]},
+ dtype=self.effective_dtype(dtype),
+ columns=list("abc"),
+ )
+ if sparse:
+ if is_integer_dtype(dtype):
+ fill_value = 0
+ elif dtype == bool:
+ fill_value = False
+ else:
+ fill_value = 0.0
+
+ expected = expected.apply(SparseArray, fill_value=fill_value)
+ result = get_dummies(s_list, sparse=sparse, dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+ result = get_dummies(s_series, sparse=sparse, dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+ result = get_dummies(s_df, columns=s_df.columns, sparse=sparse, dtype=dtype)
+ if sparse:
+ dtype_name = f"Sparse[{self.effective_dtype(dtype).name}, {fill_value}]"
+ else:
+ dtype_name = self.effective_dtype(dtype).name
+
+ expected = Series({dtype_name: 8}, name="count")
+ result = result.dtypes.value_counts()
+ result.index = [str(i) for i in result.index]
+ tm.assert_series_equal(result, expected)
+
+ result = get_dummies(s_df, columns=["a"], sparse=sparse, dtype=dtype)
+
+ key = "str" if using_infer_string else "object"
+ expected_counts = {"int64": 1, key: 1}
+ expected_counts[dtype_name] = 3 + expected_counts.get(dtype_name, 0)
+
+ expected = Series(expected_counts, name="count").sort_index()
+ result = result.dtypes.value_counts()
+ result.index = [str(i) for i in result.index]
+ result = result.sort_index()
+ tm.assert_series_equal(result, expected)
+
+ def test_get_dummies_just_na(self, sparse):
+ just_na_list = [np.nan]
+ just_na_series = Series(just_na_list)
+ just_na_series_index = Series(just_na_list, index=["A"])
+
+ res_list = get_dummies(just_na_list, sparse=sparse)
+ res_series = get_dummies(just_na_series, sparse=sparse)
+ res_series_index = get_dummies(just_na_series_index, sparse=sparse)
+
+ assert res_list.empty
+ assert res_series.empty
+ assert res_series_index.empty
+
+ assert res_list.index.tolist() == [0]
+ assert res_series.index.tolist() == [0]
+ assert res_series_index.index.tolist() == ["A"]
+
+ def test_get_dummies_include_na(self, sparse, dtype):
+ s = ["a", "b", np.nan]
+ res = get_dummies(s, sparse=sparse, dtype=dtype)
+ exp = DataFrame(
+ {"a": [1, 0, 0], "b": [0, 1, 0]}, dtype=self.effective_dtype(dtype)
+ )
+ if sparse:
+ if dtype.kind == "b":
+ exp = exp.apply(SparseArray, fill_value=False)
+ else:
+ exp = exp.apply(SparseArray, fill_value=0.0)
+ tm.assert_frame_equal(res, exp)
+
+ # Sparse dataframes do not allow nan labelled columns, see #GH8822
+ res_na = get_dummies(s, dummy_na=True, sparse=sparse, dtype=dtype)
+ exp_na = DataFrame(
+ {np.nan: [0, 0, 1], "a": [1, 0, 0], "b": [0, 1, 0]},
+ dtype=self.effective_dtype(dtype),
+ )
+ exp_na = exp_na.reindex(["a", "b", np.nan], axis=1)
+ # hack (NaN handling in assert_index_equal)
+ exp_na.columns = res_na.columns
+ if sparse:
+ if dtype.kind == "b":
+ exp_na = exp_na.apply(SparseArray, fill_value=False)
+ else:
+ exp_na = exp_na.apply(SparseArray, fill_value=0.0)
+ tm.assert_frame_equal(res_na, exp_na)
+
+ res_just_na = get_dummies([np.nan], dummy_na=True, sparse=sparse, dtype=dtype)
+ exp_just_na = DataFrame(
+ Series(1, index=[0]), columns=[np.nan], dtype=self.effective_dtype(dtype)
+ )
+ tm.assert_numpy_array_equal(res_just_na.values, exp_just_na.values)
+
+ def test_get_dummies_unicode(self, sparse):
+ # See GH 6885 - get_dummies chokes on unicode values
+ e = "e"
+ eacute = unicodedata.lookup("LATIN SMALL LETTER E WITH ACUTE")
+ s = [e, eacute, eacute]
+ res = get_dummies(s, prefix="letter", sparse=sparse)
+ exp = DataFrame(
+ {"letter_e": [True, False, False], f"letter_{eacute}": [False, True, True]}
+ )
+ if sparse:
+ exp = exp.apply(SparseArray, fill_value=False)
+ tm.assert_frame_equal(res, exp)
+
+ def test_dataframe_dummies_all_obj(self, df, sparse):
+ df = df[["A", "B"]]
+ result = get_dummies(df, sparse=sparse)
+ expected = DataFrame(
+ {"A_a": [1, 0, 1], "A_b": [0, 1, 0], "B_b": [1, 1, 0], "B_c": [0, 0, 1]},
+ dtype=bool,
+ )
+ if sparse:
+ expected = DataFrame(
+ {
+ "A_a": SparseArray([1, 0, 1], dtype="bool"),
+ "A_b": SparseArray([0, 1, 0], dtype="bool"),
+ "B_b": SparseArray([1, 1, 0], dtype="bool"),
+ "B_c": SparseArray([0, 0, 1], dtype="bool"),
+ }
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_string_dtype(self, df, any_string_dtype):
+ # GH44965
+ df = df[["A", "B"]]
+ df = df.astype({"A": "str", "B": any_string_dtype})
+ result = get_dummies(df)
+ expected = DataFrame(
+ {
+ "A_a": [1, 0, 1],
+ "A_b": [0, 1, 0],
+ "B_b": [1, 1, 0],
+ "B_c": [0, 0, 1],
+ },
+ dtype=bool,
+ )
+ if any_string_dtype == "string" and any_string_dtype.na_value is pd.NA:
+ expected[["B_b", "B_c"]] = expected[["B_b", "B_c"]].astype("boolean")
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_mix_default(self, df, sparse, dtype):
+ result = get_dummies(df, sparse=sparse, dtype=dtype)
+ if sparse:
+ arr = SparseArray
+ if dtype.kind == "b":
+ typ = SparseDtype(dtype, False)
+ else:
+ typ = SparseDtype(dtype, 0)
+ else:
+ arr = np.array
+ typ = dtype
+ expected = DataFrame(
+ {
+ "C": [1, 2, 3],
+ "A_a": arr([1, 0, 1], dtype=typ),
+ "A_b": arr([0, 1, 0], dtype=typ),
+ "B_b": arr([1, 1, 0], dtype=typ),
+ "B_c": arr([0, 0, 1], dtype=typ),
+ }
+ )
+ expected = expected[["C", "A_a", "A_b", "B_b", "B_c"]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_prefix_list(self, df, sparse):
+ prefixes = ["from_A", "from_B"]
+ result = get_dummies(df, prefix=prefixes, sparse=sparse)
+ expected = DataFrame(
+ {
+ "C": [1, 2, 3],
+ "from_A_a": [True, False, True],
+ "from_A_b": [False, True, False],
+ "from_B_b": [True, True, False],
+ "from_B_c": [False, False, True],
+ },
+ )
+ expected[["C"]] = df[["C"]]
+ cols = ["from_A_a", "from_A_b", "from_B_b", "from_B_c"]
+ expected = expected[["C", *cols]]
+
+ typ = SparseArray if sparse else Series
+ expected[cols] = expected[cols].apply(lambda x: typ(x))
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_prefix_str(self, df, sparse):
+ # not that you should do this...
+ result = get_dummies(df, prefix="bad", sparse=sparse)
+ bad_columns = ["bad_a", "bad_b", "bad_b", "bad_c"]
+ expected = DataFrame(
+ [
+ [1, True, False, True, False],
+ [2, False, True, True, False],
+ [3, True, False, False, True],
+ ],
+ columns=["C", *bad_columns],
+ )
+ expected = expected.astype({"C": np.int64})
+ if sparse:
+ # work around astyping & assigning with duplicate columns
+ # https://github.com/pandas-dev/pandas/issues/14427
+ expected = pd.concat(
+ [
+ Series([1, 2, 3], name="C"),
+ Series([True, False, True], name="bad_a", dtype="Sparse[bool]"),
+ Series([False, True, False], name="bad_b", dtype="Sparse[bool]"),
+ Series([True, True, False], name="bad_b", dtype="Sparse[bool]"),
+ Series([False, False, True], name="bad_c", dtype="Sparse[bool]"),
+ ],
+ axis=1,
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_subset(self, df, sparse):
+ result = get_dummies(df, prefix=["from_A"], columns=["A"], sparse=sparse)
+ expected = DataFrame(
+ {
+ "B": ["b", "b", "c"],
+ "C": [1, 2, 3],
+ "from_A_a": [1, 0, 1],
+ "from_A_b": [0, 1, 0],
+ },
+ )
+ cols = expected.columns
+ expected[cols[1:]] = expected[cols[1:]].astype(bool)
+ expected[["C"]] = df[["C"]]
+ if sparse:
+ cols = ["from_A_a", "from_A_b"]
+ expected[cols] = expected[cols].astype(SparseDtype("bool", False))
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_prefix_sep(self, df, sparse):
+ result = get_dummies(df, prefix_sep="..", sparse=sparse)
+ expected = DataFrame(
+ {
+ "C": [1, 2, 3],
+ "A..a": [True, False, True],
+ "A..b": [False, True, False],
+ "B..b": [True, True, False],
+ "B..c": [False, False, True],
+ },
+ )
+ expected[["C"]] = df[["C"]]
+ expected = expected[["C", "A..a", "A..b", "B..b", "B..c"]]
+ if sparse:
+ cols = ["A..a", "A..b", "B..b", "B..c"]
+ expected[cols] = expected[cols].astype(SparseDtype("bool", False))
+
+ tm.assert_frame_equal(result, expected)
+
+ result = get_dummies(df, prefix_sep=["..", "__"], sparse=sparse)
+ expected = expected.rename(columns={"B..b": "B__b", "B..c": "B__c"})
+ tm.assert_frame_equal(result, expected)
+
+ result = get_dummies(df, prefix_sep={"A": "..", "B": "__"}, sparse=sparse)
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_prefix_bad_length(self, df, sparse):
+ msg = re.escape(
+ "Length of 'prefix' (1) did not match the length of the columns being "
+ "encoded (2)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ get_dummies(df, prefix=["too few"], sparse=sparse)
+
+ def test_dataframe_dummies_prefix_sep_bad_length(self, df, sparse):
+ msg = re.escape(
+ "Length of 'prefix_sep' (1) did not match the length of the columns being "
+ "encoded (2)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ get_dummies(df, prefix_sep=["bad"], sparse=sparse)
+
+ def test_dataframe_dummies_prefix_dict(self, sparse):
+ prefixes = {"A": "from_A", "B": "from_B"}
+ df = DataFrame({"C": [1, 2, 3], "A": ["a", "b", "a"], "B": ["b", "b", "c"]})
+ result = get_dummies(df, prefix=prefixes, sparse=sparse)
+
+ expected = DataFrame(
+ {
+ "C": [1, 2, 3],
+ "from_A_a": [1, 0, 1],
+ "from_A_b": [0, 1, 0],
+ "from_B_b": [1, 1, 0],
+ "from_B_c": [0, 0, 1],
+ }
+ )
+
+ columns = ["from_A_a", "from_A_b", "from_B_b", "from_B_c"]
+ expected[columns] = expected[columns].astype(bool)
+ if sparse:
+ expected[columns] = expected[columns].astype(SparseDtype("bool", False))
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_with_na(self, df, sparse, dtype):
+ df.loc[3, :] = [np.nan, np.nan, np.nan]
+ result = get_dummies(df, dummy_na=True, sparse=sparse, dtype=dtype).sort_index(
+ axis=1
+ )
+
+ if sparse:
+ arr = SparseArray
+ if dtype.kind == "b":
+ typ = SparseDtype(dtype, False)
+ else:
+ typ = SparseDtype(dtype, 0)
+ else:
+ arr = np.array
+ typ = dtype
+
+ expected = DataFrame(
+ {
+ "C": [1, 2, 3, np.nan],
+ "A_a": arr([1, 0, 1, 0], dtype=typ),
+ "A_b": arr([0, 1, 0, 0], dtype=typ),
+ "A_nan": arr([0, 0, 0, 1], dtype=typ),
+ "B_b": arr([1, 1, 0, 0], dtype=typ),
+ "B_c": arr([0, 0, 1, 0], dtype=typ),
+ "B_nan": arr([0, 0, 0, 1], dtype=typ),
+ }
+ ).sort_index(axis=1)
+
+ tm.assert_frame_equal(result, expected)
+
+ result = get_dummies(df, dummy_na=False, sparse=sparse, dtype=dtype)
+ expected = expected[["C", "A_a", "A_b", "B_b", "B_c"]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_with_categorical(self, df, sparse, dtype):
+ df["cat"] = Categorical(["x", "y", "y"])
+ result = get_dummies(df, sparse=sparse, dtype=dtype).sort_index(axis=1)
+ if sparse:
+ arr = SparseArray
+ if dtype.kind == "b":
+ typ = SparseDtype(dtype, False)
+ else:
+ typ = SparseDtype(dtype, 0)
+ else:
+ arr = np.array
+ typ = dtype
+
+ expected = DataFrame(
+ {
+ "C": [1, 2, 3],
+ "A_a": arr([1, 0, 1], dtype=typ),
+ "A_b": arr([0, 1, 0], dtype=typ),
+ "B_b": arr([1, 1, 0], dtype=typ),
+ "B_c": arr([0, 0, 1], dtype=typ),
+ "cat_x": arr([1, 0, 0], dtype=typ),
+ "cat_y": arr([0, 1, 1], dtype=typ),
+ }
+ ).sort_index(axis=1)
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "get_dummies_kwargs,expected",
+ [
+ (
+ {"data": DataFrame({"ä": ["a"]})},
+ "ä_a",
+ ),
+ (
+ {"data": DataFrame({"x": ["ä"]})},
+ "x_ä",
+ ),
+ (
+ {"data": DataFrame({"x": ["a"]}), "prefix": "ä"},
+ "ä_a",
+ ),
+ (
+ {"data": DataFrame({"x": ["a"]}), "prefix_sep": "ä"},
+ "xäa",
+ ),
+ ],
+ )
+ def test_dataframe_dummies_unicode(self, get_dummies_kwargs, expected):
+ # GH22084 get_dummies incorrectly encodes unicode characters
+ # in dataframe column names
+ result = get_dummies(**get_dummies_kwargs)
+ expected = DataFrame({expected: [True]})
+ tm.assert_frame_equal(result, expected)
+
+ def test_get_dummies_basic_drop_first(self, sparse):
+ # GH12402 Add a new parameter `drop_first` to avoid collinearity
+ # Basic case
+ s_list = list("abc")
+ s_series = Series(s_list)
+ s_series_index = Series(s_list, list("ABC"))
+
+ expected = DataFrame({"b": [0, 1, 0], "c": [0, 0, 1]}, dtype=bool)
+
+ result = get_dummies(s_list, drop_first=True, sparse=sparse)
+ if sparse:
+ expected = expected.apply(SparseArray, fill_value=False)
+ tm.assert_frame_equal(result, expected)
+
+ result = get_dummies(s_series, drop_first=True, sparse=sparse)
+ tm.assert_frame_equal(result, expected)
+
+ expected.index = list("ABC")
+ result = get_dummies(s_series_index, drop_first=True, sparse=sparse)
+ tm.assert_frame_equal(result, expected)
+
+ def test_get_dummies_basic_drop_first_one_level(self, sparse):
+ # Test the case that categorical variable only has one level.
+ s_list = list("aaa")
+ s_series = Series(s_list)
+ s_series_index = Series(s_list, list("ABC"))
+
+ expected = DataFrame(index=RangeIndex(3))
+
+ result = get_dummies(s_list, drop_first=True, sparse=sparse)
+ tm.assert_frame_equal(result, expected)
+
+ result = get_dummies(s_series, drop_first=True, sparse=sparse)
+ tm.assert_frame_equal(result, expected)
+
+ expected = DataFrame(index=list("ABC"))
+ result = get_dummies(s_series_index, drop_first=True, sparse=sparse)
+ tm.assert_frame_equal(result, expected)
+
+ def test_get_dummies_basic_drop_first_NA(self, sparse):
+ # Test NA handling together with drop_first
+ s_NA = ["a", "b", np.nan]
+ res = get_dummies(s_NA, drop_first=True, sparse=sparse)
+ exp = DataFrame({"b": [0, 1, 0]}, dtype=bool)
+ if sparse:
+ exp = exp.apply(SparseArray, fill_value=False)
+
+ tm.assert_frame_equal(res, exp)
+
+ res_na = get_dummies(s_NA, dummy_na=True, drop_first=True, sparse=sparse)
+ exp_na = DataFrame({"b": [0, 1, 0], np.nan: [0, 0, 1]}, dtype=bool).reindex(
+ ["b", np.nan], axis=1
+ )
+ if sparse:
+ exp_na = exp_na.apply(SparseArray, fill_value=False)
+ tm.assert_frame_equal(res_na, exp_na)
+
+ res_just_na = get_dummies(
+ [np.nan], dummy_na=True, drop_first=True, sparse=sparse
+ )
+ exp_just_na = DataFrame(index=RangeIndex(1))
+ tm.assert_frame_equal(res_just_na, exp_just_na)
+
+ def test_dataframe_dummies_drop_first(self, df, sparse):
+ df = df[["A", "B"]]
+ result = get_dummies(df, drop_first=True, sparse=sparse)
+ expected = DataFrame({"A_b": [0, 1, 0], "B_c": [0, 0, 1]}, dtype=bool)
+ if sparse:
+ expected = expected.apply(SparseArray, fill_value=False)
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_drop_first_with_categorical(self, df, sparse, dtype):
+ df["cat"] = Categorical(["x", "y", "y"])
+ result = get_dummies(df, drop_first=True, sparse=sparse)
+ expected = DataFrame(
+ {"C": [1, 2, 3], "A_b": [0, 1, 0], "B_c": [0, 0, 1], "cat_y": [0, 1, 1]}
+ )
+ cols = ["A_b", "B_c", "cat_y"]
+ expected[cols] = expected[cols].astype(bool)
+ expected = expected[["C", "A_b", "B_c", "cat_y"]]
+ if sparse:
+ for col in cols:
+ expected[col] = SparseArray(expected[col])
+ tm.assert_frame_equal(result, expected)
+
+ def test_dataframe_dummies_drop_first_with_na(self, df, sparse):
+ df.loc[3, :] = [np.nan, np.nan, np.nan]
+ result = get_dummies(
+ df, dummy_na=True, drop_first=True, sparse=sparse
+ ).sort_index(axis=1)
+ expected = DataFrame(
+ {
+ "C": [1, 2, 3, np.nan],
+ "A_b": [0, 1, 0, 0],
+ "A_nan": [0, 0, 0, 1],
+ "B_c": [0, 0, 1, 0],
+ "B_nan": [0, 0, 0, 1],
+ }
+ )
+ cols = ["A_b", "A_nan", "B_c", "B_nan"]
+ expected[cols] = expected[cols].astype(bool)
+ expected = expected.sort_index(axis=1)
+ if sparse:
+ for col in cols:
+ expected[col] = SparseArray(expected[col])
+
+ tm.assert_frame_equal(result, expected)
+
+ result = get_dummies(df, dummy_na=False, drop_first=True, sparse=sparse)
+ expected = expected[["C", "A_b", "B_c"]]
+ tm.assert_frame_equal(result, expected)
+
+ def test_get_dummies_int_int(self):
+ data = Series([1, 2, 1])
+ result = get_dummies(data)
+ expected = DataFrame([[1, 0], [0, 1], [1, 0]], columns=[1, 2], dtype=bool)
+ tm.assert_frame_equal(result, expected)
+
+ data = Series(Categorical(["a", "b", "a"]))
+ result = get_dummies(data)
+ expected = DataFrame(
+ [[1, 0], [0, 1], [1, 0]], columns=Categorical(["a", "b"]), dtype=bool
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_get_dummies_int_df(self, dtype):
+ data = DataFrame(
+ {
+ "A": [1, 2, 1],
+ "B": Categorical(["a", "b", "a"]),
+ "C": [1, 2, 1],
+ "D": [1.0, 2.0, 1.0],
+ }
+ )
+ columns = ["C", "D", "A_1", "A_2", "B_a", "B_b"]
+ expected = DataFrame(
+ [[1, 1.0, 1, 0, 1, 0], [2, 2.0, 0, 1, 0, 1], [1, 1.0, 1, 0, 1, 0]],
+ columns=columns,
+ )
+ expected[columns[2:]] = expected[columns[2:]].astype(dtype)
+ result = get_dummies(data, columns=["A", "B"], dtype=dtype)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("ordered", [True, False])
+ def test_dataframe_dummies_preserve_categorical_dtype(self, dtype, ordered):
+ # GH13854
+ cat = Categorical(list("xy"), categories=list("xyz"), ordered=ordered)
+ result = get_dummies(cat, dtype=dtype)
+
+ data = np.array([[1, 0, 0], [0, 1, 0]], dtype=self.effective_dtype(dtype))
+ cols = CategoricalIndex(
+ cat.categories, categories=cat.categories, ordered=ordered
+ )
+ expected = DataFrame(data, columns=cols, dtype=self.effective_dtype(dtype))
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("sparse", [True, False])
+ def test_get_dummies_dont_sparsify_all_columns(self, sparse):
+ # GH18914
+ df = DataFrame.from_dict({"GDP": [1, 2], "Nation": ["AB", "CD"]})
+ df = get_dummies(df, columns=["Nation"], sparse=sparse)
+ df2 = df.reindex(columns=["GDP"])
+
+ tm.assert_frame_equal(df[["GDP"]], df2)
+
+ def test_get_dummies_duplicate_columns(self, df):
+ # GH20839
+ df.columns = ["A", "A", "A"]
+ result = get_dummies(df).sort_index(axis=1)
+
+ expected = DataFrame(
+ [
+ [1, True, False, True, False],
+ [2, False, True, True, False],
+ [3, True, False, False, True],
+ ],
+ columns=["A", "A_a", "A_b", "A_b", "A_c"],
+ ).sort_index(axis=1)
+
+ expected = expected.astype({"A": np.int64})
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_get_dummies_all_sparse(self):
+ df = DataFrame({"A": [1, 2]})
+ result = get_dummies(df, columns=["A"], sparse=True)
+ dtype = SparseDtype("bool", False)
+ expected = DataFrame(
+ {
+ "A_1": SparseArray([1, 0], dtype=dtype),
+ "A_2": SparseArray([0, 1], dtype=dtype),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("values", ["baz"])
+ def test_get_dummies_with_string_values(self, values):
+ # issue #28383
+ df = DataFrame(
+ {
+ "bar": [1, 2, 3, 4, 5, 6],
+ "foo": ["one", "one", "one", "two", "two", "two"],
+ "baz": ["A", "B", "C", "A", "B", "C"],
+ "zoo": ["x", "y", "z", "q", "w", "t"],
+ }
+ )
+
+ msg = "Input must be a list-like for parameter `columns`"
+
+ with pytest.raises(TypeError, match=msg):
+ get_dummies(df, columns=values)
+
+ def test_get_dummies_ea_dtype_series(self, any_numeric_ea_and_arrow_dtype):
+ # GH#32430
+ ser = Series(list("abca"))
+ result = get_dummies(ser, dtype=any_numeric_ea_and_arrow_dtype)
+ expected = DataFrame(
+ {"a": [1, 0, 0, 1], "b": [0, 1, 0, 0], "c": [0, 0, 1, 0]},
+ dtype=any_numeric_ea_and_arrow_dtype,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_get_dummies_ea_dtype_dataframe(self, any_numeric_ea_and_arrow_dtype):
+ # GH#32430
+ df = DataFrame({"x": list("abca")})
+ result = get_dummies(df, dtype=any_numeric_ea_and_arrow_dtype)
+ expected = DataFrame(
+ {"x_a": [1, 0, 0, 1], "x_b": [0, 1, 0, 0], "x_c": [0, 0, 1, 0]},
+ dtype=any_numeric_ea_and_arrow_dtype,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype_type", ["string", "category"])
+ def test_get_dummies_ea_dtype(self, dtype_type, string_dtype_no_object):
+ # GH#56273
+ dtype = string_dtype_no_object
+ exp_dtype = "boolean" if dtype.na_value is pd.NA else "bool"
+ if dtype_type == "category":
+ dtype = CategoricalDtype(Index(["a"], dtype))
+ df = DataFrame({"name": Series(["a"], dtype=dtype), "x": 1})
+ result = get_dummies(df)
+ expected = DataFrame({"x": 1, "name_a": Series([True], dtype=exp_dtype)})
+ tm.assert_frame_equal(result, expected)
+
+ @td.skip_if_no("pyarrow")
+ def test_get_dummies_arrow_dtype(self):
+ # GH#56273
+ df = DataFrame({"name": Series(["a"], dtype=ArrowDtype(pa.string())), "x": 1})
+ result = get_dummies(df)
+ expected = DataFrame({"x": 1, "name_a": Series([True], dtype="bool[pyarrow]")})
+ tm.assert_frame_equal(result, expected)
+
+ df = DataFrame(
+ {
+ "name": Series(
+ ["a"],
+ dtype=CategoricalDtype(Index(["a"], dtype=ArrowDtype(pa.string()))),
+ ),
+ "x": 1,
+ }
+ )
+ result = get_dummies(df)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_melt.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_melt.py
new file mode 100644
index 0000000000000000000000000000000000000000..fba9c28282e9491ce5f237e4d92068a4320953ca
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_melt.py
@@ -0,0 +1,1280 @@
+import re
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ date_range,
+ lreshape,
+ melt,
+ wide_to_long,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def df():
+ res = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ res["id1"] = (res["A"] > 0).astype(np.int64)
+ res["id2"] = (res["B"] > 0).astype(np.int64)
+ return res
+
+
+@pytest.fixture
+def df1():
+ res = DataFrame(
+ [
+ [1.067683, -1.110463, 0.20867],
+ [-1.321405, 0.368915, -1.055342],
+ [-0.807333, 0.08298, -0.873361],
+ ]
+ )
+ res.columns = [list("ABC"), list("abc")]
+ res.columns.names = ["CAP", "low"]
+ return res
+
+
+@pytest.fixture
+def var_name():
+ return "var"
+
+
+@pytest.fixture
+def value_name():
+ return "val"
+
+
+class TestMelt:
+ def test_top_level_method(self, df):
+ result = melt(df)
+ assert result.columns.tolist() == ["variable", "value"]
+
+ def test_method_signatures(self, df, df1, var_name, value_name):
+ tm.assert_frame_equal(df.melt(), melt(df))
+
+ tm.assert_frame_equal(
+ df.melt(id_vars=["id1", "id2"], value_vars=["A", "B"]),
+ melt(df, id_vars=["id1", "id2"], value_vars=["A", "B"]),
+ )
+
+ tm.assert_frame_equal(
+ df.melt(var_name=var_name, value_name=value_name),
+ melt(df, var_name=var_name, value_name=value_name),
+ )
+
+ tm.assert_frame_equal(df1.melt(col_level=0), melt(df1, col_level=0))
+
+ def test_default_col_names(self, df):
+ result = df.melt()
+ assert result.columns.tolist() == ["variable", "value"]
+
+ result1 = df.melt(id_vars=["id1"])
+ assert result1.columns.tolist() == ["id1", "variable", "value"]
+
+ result2 = df.melt(id_vars=["id1", "id2"])
+ assert result2.columns.tolist() == ["id1", "id2", "variable", "value"]
+
+ def test_value_vars(self, df):
+ result3 = df.melt(id_vars=["id1", "id2"], value_vars="A")
+ assert len(result3) == 10
+
+ result4 = df.melt(id_vars=["id1", "id2"], value_vars=["A", "B"])
+ expected4 = DataFrame(
+ {
+ "id1": df["id1"].tolist() * 2,
+ "id2": df["id2"].tolist() * 2,
+ "variable": ["A"] * 10 + ["B"] * 10,
+ "value": (df["A"].tolist() + df["B"].tolist()),
+ },
+ columns=["id1", "id2", "variable", "value"],
+ )
+ tm.assert_frame_equal(result4, expected4)
+
+ @pytest.mark.parametrize("type_", (tuple, list, np.array))
+ def test_value_vars_types(self, type_, df):
+ # GH 15348
+ expected = DataFrame(
+ {
+ "id1": df["id1"].tolist() * 2,
+ "id2": df["id2"].tolist() * 2,
+ "variable": ["A"] * 10 + ["B"] * 10,
+ "value": (df["A"].tolist() + df["B"].tolist()),
+ },
+ columns=["id1", "id2", "variable", "value"],
+ )
+ result = df.melt(id_vars=["id1", "id2"], value_vars=type_(("A", "B")))
+ tm.assert_frame_equal(result, expected)
+
+ def test_vars_work_with_multiindex(self, df1):
+ expected = DataFrame(
+ {
+ ("A", "a"): df1[("A", "a")],
+ "CAP": ["B"] * len(df1),
+ "low": ["b"] * len(df1),
+ "value": df1[("B", "b")],
+ },
+ columns=[("A", "a"), "CAP", "low", "value"],
+ )
+
+ result = df1.melt(id_vars=[("A", "a")], value_vars=[("B", "b")])
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "id_vars, value_vars, col_level, expected",
+ [
+ (
+ ["A"],
+ ["B"],
+ 0,
+ {
+ "A": {0: 1.067683, 1: -1.321405, 2: -0.807333},
+ "CAP": {0: "B", 1: "B", 2: "B"},
+ "value": {0: -1.110463, 1: 0.368915, 2: 0.08298},
+ },
+ ),
+ (
+ ["a"],
+ ["b"],
+ 1,
+ {
+ "a": {0: 1.067683, 1: -1.321405, 2: -0.807333},
+ "low": {0: "b", 1: "b", 2: "b"},
+ "value": {0: -1.110463, 1: 0.368915, 2: 0.08298},
+ },
+ ),
+ ],
+ )
+ def test_single_vars_work_with_multiindex(
+ self, id_vars, value_vars, col_level, expected, df1
+ ):
+ result = df1.melt(id_vars, value_vars, col_level=col_level)
+ expected = DataFrame(expected)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "id_vars, value_vars",
+ [
+ [("A", "a"), [("B", "b")]],
+ [[("A", "a")], ("B", "b")],
+ [("A", "a"), ("B", "b")],
+ ],
+ )
+ def test_tuple_vars_fail_with_multiindex(self, id_vars, value_vars, df1):
+ # melt should fail with an informative error message if
+ # the columns have a MultiIndex and a tuple is passed
+ # for id_vars or value_vars.
+ msg = r"(id|value)_vars must be a list of tuples when columns are a MultiIndex"
+ with pytest.raises(ValueError, match=msg):
+ df1.melt(id_vars=id_vars, value_vars=value_vars)
+
+ def test_custom_var_name(self, df, var_name):
+ result5 = df.melt(var_name=var_name)
+ assert result5.columns.tolist() == ["var", "value"]
+
+ result6 = df.melt(id_vars=["id1"], var_name=var_name)
+ assert result6.columns.tolist() == ["id1", "var", "value"]
+
+ result7 = df.melt(id_vars=["id1", "id2"], var_name=var_name)
+ assert result7.columns.tolist() == ["id1", "id2", "var", "value"]
+
+ result8 = df.melt(id_vars=["id1", "id2"], value_vars="A", var_name=var_name)
+ assert result8.columns.tolist() == ["id1", "id2", "var", "value"]
+
+ result9 = df.melt(
+ id_vars=["id1", "id2"], value_vars=["A", "B"], var_name=var_name
+ )
+ expected9 = DataFrame(
+ {
+ "id1": df["id1"].tolist() * 2,
+ "id2": df["id2"].tolist() * 2,
+ var_name: ["A"] * 10 + ["B"] * 10,
+ "value": (df["A"].tolist() + df["B"].tolist()),
+ },
+ columns=["id1", "id2", var_name, "value"],
+ )
+ tm.assert_frame_equal(result9, expected9)
+
+ def test_custom_value_name(self, df, value_name):
+ result10 = df.melt(value_name=value_name)
+ assert result10.columns.tolist() == ["variable", "val"]
+
+ result11 = df.melt(id_vars=["id1"], value_name=value_name)
+ assert result11.columns.tolist() == ["id1", "variable", "val"]
+
+ result12 = df.melt(id_vars=["id1", "id2"], value_name=value_name)
+ assert result12.columns.tolist() == ["id1", "id2", "variable", "val"]
+
+ result13 = df.melt(
+ id_vars=["id1", "id2"], value_vars="A", value_name=value_name
+ )
+ assert result13.columns.tolist() == ["id1", "id2", "variable", "val"]
+
+ result14 = df.melt(
+ id_vars=["id1", "id2"], value_vars=["A", "B"], value_name=value_name
+ )
+ expected14 = DataFrame(
+ {
+ "id1": df["id1"].tolist() * 2,
+ "id2": df["id2"].tolist() * 2,
+ "variable": ["A"] * 10 + ["B"] * 10,
+ value_name: (df["A"].tolist() + df["B"].tolist()),
+ },
+ columns=["id1", "id2", "variable", value_name],
+ )
+ tm.assert_frame_equal(result14, expected14)
+
+ def test_custom_var_and_value_name(self, df, value_name, var_name):
+ result15 = df.melt(var_name=var_name, value_name=value_name)
+ assert result15.columns.tolist() == ["var", "val"]
+
+ result16 = df.melt(id_vars=["id1"], var_name=var_name, value_name=value_name)
+ assert result16.columns.tolist() == ["id1", "var", "val"]
+
+ result17 = df.melt(
+ id_vars=["id1", "id2"], var_name=var_name, value_name=value_name
+ )
+ assert result17.columns.tolist() == ["id1", "id2", "var", "val"]
+
+ result18 = df.melt(
+ id_vars=["id1", "id2"],
+ value_vars="A",
+ var_name=var_name,
+ value_name=value_name,
+ )
+ assert result18.columns.tolist() == ["id1", "id2", "var", "val"]
+
+ result19 = df.melt(
+ id_vars=["id1", "id2"],
+ value_vars=["A", "B"],
+ var_name=var_name,
+ value_name=value_name,
+ )
+ expected19 = DataFrame(
+ {
+ "id1": df["id1"].tolist() * 2,
+ "id2": df["id2"].tolist() * 2,
+ var_name: ["A"] * 10 + ["B"] * 10,
+ value_name: (df["A"].tolist() + df["B"].tolist()),
+ },
+ columns=["id1", "id2", var_name, value_name],
+ )
+ tm.assert_frame_equal(result19, expected19)
+
+ df20 = df.copy()
+ df20.columns.name = "foo"
+ result20 = df20.melt()
+ assert result20.columns.tolist() == ["foo", "value"]
+
+ @pytest.mark.parametrize("col_level", [0, "CAP"])
+ def test_col_level(self, col_level, df1):
+ res = df1.melt(col_level=col_level)
+ assert res.columns.tolist() == ["CAP", "value"]
+
+ def test_multiindex(self, df1):
+ res = df1.melt()
+ assert res.columns.tolist() == ["CAP", "low", "value"]
+
+ @pytest.mark.parametrize(
+ "col",
+ [
+ date_range("2010", periods=5, tz="US/Pacific"),
+ pd.Categorical(["a", "b", "c", "a", "d"]),
+ [0, 1, 0, 0, 0],
+ ],
+ )
+ def test_pandas_dtypes(self, col):
+ # GH 15785
+ col = pd.Series(col)
+ df = DataFrame(
+ {"klass": range(5), "col": col, "attr1": [1, 0, 0, 0, 0], "attr2": col}
+ )
+ expected_value = pd.concat([pd.Series([1, 0, 0, 0, 0]), col], ignore_index=True)
+ result = melt(
+ df, id_vars=["klass", "col"], var_name="attribute", value_name="value"
+ )
+ expected = DataFrame(
+ {
+ 0: list(range(5)) * 2,
+ 1: pd.concat([col] * 2, ignore_index=True),
+ 2: ["attr1"] * 5 + ["attr2"] * 5,
+ 3: expected_value,
+ }
+ )
+ expected.columns = ["klass", "col", "attribute", "value"]
+ tm.assert_frame_equal(result, expected)
+
+ def test_preserve_category(self):
+ # GH 15853
+ data = DataFrame({"A": [1, 2], "B": pd.Categorical(["X", "Y"])})
+ result = melt(data, ["B"], ["A"])
+ expected = DataFrame(
+ {"B": pd.Categorical(["X", "Y"]), "variable": ["A", "A"], "value": [1, 2]}
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_melt_missing_columns_raises(self):
+ # GH-23575
+ # This test is to ensure that pandas raises an error if melting is
+ # attempted with column names absent from the dataframe
+
+ # Generate data
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((5, 4)), columns=list("abcd")
+ )
+
+ # Try to melt with missing `value_vars` column name
+ msg = "The following id_vars or value_vars are not present in the DataFrame:"
+ with pytest.raises(KeyError, match=msg):
+ df.melt(["a", "b"], ["C", "d"])
+
+ # Try to melt with missing `id_vars` column name
+ with pytest.raises(KeyError, match=msg):
+ df.melt(["A", "b"], ["c", "d"])
+
+ # Multiple missing
+ with pytest.raises(
+ KeyError,
+ match=msg,
+ ):
+ df.melt(["a", "b", "not_here", "or_there"], ["c", "d"])
+
+ # Multiindex melt fails if column is missing from multilevel melt
+ df.columns = [list("ABCD"), list("abcd")]
+ with pytest.raises(KeyError, match=msg):
+ df.melt([("E", "a")], [("B", "b")])
+ # Multiindex fails if column is missing from single level melt
+ with pytest.raises(KeyError, match=msg):
+ df.melt(["A"], ["F"], col_level=0)
+
+ def test_melt_mixed_int_str_id_vars(self):
+ # GH 29718
+ df = DataFrame({0: ["foo"], "a": ["bar"], "b": [1], "d": [2]})
+ result = melt(df, id_vars=[0, "a"], value_vars=["b", "d"])
+ expected = DataFrame(
+ {0: ["foo"] * 2, "a": ["bar"] * 2, "variable": list("bd"), "value": [1, 2]}
+ )
+ # the df's columns are mixed type and thus object -> preserves object dtype
+ expected["variable"] = expected["variable"].astype(object)
+ tm.assert_frame_equal(result, expected)
+
+ def test_melt_mixed_int_str_value_vars(self):
+ # GH 29718
+ df = DataFrame({0: ["foo"], "a": ["bar"]})
+ result = melt(df, value_vars=[0, "a"])
+ expected = DataFrame({"variable": [0, "a"], "value": ["foo", "bar"]})
+ tm.assert_frame_equal(result, expected)
+
+ def test_ignore_index(self):
+ # GH 17440
+ df = DataFrame({"foo": [0], "bar": [1]}, index=["first"])
+ result = melt(df, ignore_index=False)
+ expected = DataFrame(
+ {"variable": ["foo", "bar"], "value": [0, 1]}, index=["first", "first"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_ignore_multiindex(self):
+ # GH 17440
+ index = pd.MultiIndex.from_tuples(
+ [("first", "second"), ("first", "third")], names=["baz", "foobar"]
+ )
+ df = DataFrame({"foo": [0, 1], "bar": [2, 3]}, index=index)
+ result = melt(df, ignore_index=False)
+
+ expected_index = pd.MultiIndex.from_tuples(
+ [("first", "second"), ("first", "third")] * 2, names=["baz", "foobar"]
+ )
+ expected = DataFrame(
+ {"variable": ["foo"] * 2 + ["bar"] * 2, "value": [0, 1, 2, 3]},
+ index=expected_index,
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_ignore_index_name_and_type(self):
+ # GH 17440
+ index = Index(["foo", "bar"], dtype="category", name="baz")
+ df = DataFrame({"x": [0, 1], "y": [2, 3]}, index=index)
+ result = melt(df, ignore_index=False)
+
+ expected_index = Index(["foo", "bar"] * 2, dtype="category", name="baz")
+ expected = DataFrame(
+ {"variable": ["x", "x", "y", "y"], "value": [0, 1, 2, 3]},
+ index=expected_index,
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_melt_with_duplicate_columns(self):
+ # GH#41951
+ df = DataFrame([["id", 2, 3]], columns=["a", "b", "b"])
+ result = df.melt(id_vars=["a"], value_vars=["b"])
+ expected = DataFrame(
+ [["id", "b", 2], ["id", "b", 3]], columns=["a", "variable", "value"]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["Int8", "Int64"])
+ def test_melt_ea_dtype(self, dtype):
+ # GH#41570
+ df = DataFrame(
+ {
+ "a": pd.Series([1, 2], dtype="Int8"),
+ "b": pd.Series([3, 4], dtype=dtype),
+ }
+ )
+ result = df.melt()
+ expected = DataFrame(
+ {
+ "variable": ["a", "a", "b", "b"],
+ "value": pd.Series([1, 2, 3, 4], dtype=dtype),
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_melt_ea_columns(self):
+ # GH 54297
+ df = DataFrame(
+ {
+ "A": {0: "a", 1: "b", 2: "c"},
+ "B": {0: 1, 1: 3, 2: 5},
+ "C": {0: 2, 1: 4, 2: 6},
+ }
+ )
+ df.columns = df.columns.astype("string[python]")
+ result = df.melt(id_vars=["A"], value_vars=["B"])
+ expected = DataFrame(
+ {
+ "A": list("abc"),
+ "variable": pd.Series(["B"] * 3, dtype="string[python]"),
+ "value": [1, 3, 5],
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_melt_preserves_datetime(self):
+ df = DataFrame(
+ data=[
+ {
+ "type": "A0",
+ "start_date": pd.Timestamp("2023/03/01", tz="Asia/Tokyo"),
+ "end_date": pd.Timestamp("2023/03/10", tz="Asia/Tokyo"),
+ },
+ {
+ "type": "A1",
+ "start_date": pd.Timestamp("2023/03/01", tz="Asia/Tokyo"),
+ "end_date": pd.Timestamp("2023/03/11", tz="Asia/Tokyo"),
+ },
+ ],
+ index=["aaaa", "bbbb"],
+ )
+ result = df.melt(
+ id_vars=["type"],
+ value_vars=["start_date", "end_date"],
+ var_name="start/end",
+ value_name="date",
+ )
+ expected = DataFrame(
+ {
+ "type": {0: "A0", 1: "A1", 2: "A0", 3: "A1"},
+ "start/end": {
+ 0: "start_date",
+ 1: "start_date",
+ 2: "end_date",
+ 3: "end_date",
+ },
+ "date": {
+ 0: pd.Timestamp("2023-03-01 00:00:00+0900", tz="Asia/Tokyo"),
+ 1: pd.Timestamp("2023-03-01 00:00:00+0900", tz="Asia/Tokyo"),
+ 2: pd.Timestamp("2023-03-10 00:00:00+0900", tz="Asia/Tokyo"),
+ 3: pd.Timestamp("2023-03-11 00:00:00+0900", tz="Asia/Tokyo"),
+ },
+ }
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_melt_allows_non_scalar_id_vars(self):
+ df = DataFrame(
+ data={"a": [1, 2, 3], "b": [4, 5, 6]},
+ index=["11", "22", "33"],
+ )
+ result = df.melt(
+ id_vars="a",
+ var_name=0,
+ value_name=1,
+ )
+ expected = DataFrame({"a": [1, 2, 3], 0: ["b"] * 3, 1: [4, 5, 6]})
+ tm.assert_frame_equal(result, expected)
+
+ def test_melt_allows_non_string_var_name(self):
+ df = DataFrame(
+ data={"a": [1, 2, 3], "b": [4, 5, 6]},
+ index=["11", "22", "33"],
+ )
+ result = df.melt(
+ id_vars=["a"],
+ var_name=0,
+ value_name=1,
+ )
+ expected = DataFrame({"a": [1, 2, 3], 0: ["b"] * 3, 1: [4, 5, 6]})
+ tm.assert_frame_equal(result, expected)
+
+ def test_melt_non_scalar_var_name_raises(self):
+ df = DataFrame(
+ data={"a": [1, 2, 3], "b": [4, 5, 6]},
+ index=["11", "22", "33"],
+ )
+ with pytest.raises(ValueError, match=r".* must be a scalar."):
+ df.melt(id_vars=["a"], var_name=[1, 2])
+
+ def test_melt_multiindex_columns_var_name(self):
+ # GH 58033
+ df = DataFrame({("A", "a"): [1], ("A", "b"): [2]})
+
+ expected = DataFrame(
+ [("A", "a", 1), ("A", "b", 2)], columns=["first", "second", "value"]
+ )
+
+ tm.assert_frame_equal(df.melt(var_name=["first", "second"]), expected)
+ tm.assert_frame_equal(df.melt(var_name=["first"]), expected[["first", "value"]])
+
+ def test_melt_multiindex_columns_var_name_too_many(self):
+ # GH 58033
+ df = DataFrame({("A", "a"): [1], ("A", "b"): [2]})
+
+ with pytest.raises(
+ ValueError, match="but the dataframe columns only have 2 levels"
+ ):
+ df.melt(var_name=["first", "second", "third"])
+
+ def test_melt_duplicate_column_header_raises(self):
+ # GH61475
+ df = DataFrame([[1, 2, 3], [3, 4, 5]], columns=["A", "A", "B"])
+ msg = "id_vars cannot contain duplicate columns."
+
+ with pytest.raises(ValueError, match=msg):
+ df.melt(id_vars=["A"], value_vars=["B"])
+
+
+class TestLreshape:
+ def test_pairs(self):
+ data = {
+ "birthdt": [
+ "08jan2009",
+ "20dec2008",
+ "30dec2008",
+ "21dec2008",
+ "11jan2009",
+ ],
+ "birthwt": [1766, 3301, 1454, 3139, 4133],
+ "id": [101, 102, 103, 104, 105],
+ "sex": ["Male", "Female", "Female", "Female", "Female"],
+ "visitdt1": [
+ "11jan2009",
+ "22dec2008",
+ "04jan2009",
+ "29dec2008",
+ "20jan2009",
+ ],
+ "visitdt2": ["21jan2009", np.nan, "22jan2009", "31dec2008", "03feb2009"],
+ "visitdt3": ["05feb2009", np.nan, np.nan, "02jan2009", "15feb2009"],
+ "wt1": [1823, 3338, 1549, 3298, 4306],
+ "wt2": [2011.0, np.nan, 1892.0, 3338.0, 4575.0],
+ "wt3": [2293.0, np.nan, np.nan, 3377.0, 4805.0],
+ }
+
+ df = DataFrame(data)
+
+ spec = {
+ "visitdt": [f"visitdt{i:d}" for i in range(1, 4)],
+ "wt": [f"wt{i:d}" for i in range(1, 4)],
+ }
+ result = lreshape(df, spec)
+
+ exp_data = {
+ "birthdt": [
+ "08jan2009",
+ "20dec2008",
+ "30dec2008",
+ "21dec2008",
+ "11jan2009",
+ "08jan2009",
+ "30dec2008",
+ "21dec2008",
+ "11jan2009",
+ "08jan2009",
+ "21dec2008",
+ "11jan2009",
+ ],
+ "birthwt": [
+ 1766,
+ 3301,
+ 1454,
+ 3139,
+ 4133,
+ 1766,
+ 1454,
+ 3139,
+ 4133,
+ 1766,
+ 3139,
+ 4133,
+ ],
+ "id": [101, 102, 103, 104, 105, 101, 103, 104, 105, 101, 104, 105],
+ "sex": [
+ "Male",
+ "Female",
+ "Female",
+ "Female",
+ "Female",
+ "Male",
+ "Female",
+ "Female",
+ "Female",
+ "Male",
+ "Female",
+ "Female",
+ ],
+ "visitdt": [
+ "11jan2009",
+ "22dec2008",
+ "04jan2009",
+ "29dec2008",
+ "20jan2009",
+ "21jan2009",
+ "22jan2009",
+ "31dec2008",
+ "03feb2009",
+ "05feb2009",
+ "02jan2009",
+ "15feb2009",
+ ],
+ "wt": [
+ 1823.0,
+ 3338.0,
+ 1549.0,
+ 3298.0,
+ 4306.0,
+ 2011.0,
+ 1892.0,
+ 3338.0,
+ 4575.0,
+ 2293.0,
+ 3377.0,
+ 4805.0,
+ ],
+ }
+ exp = DataFrame(exp_data, columns=result.columns)
+ tm.assert_frame_equal(result, exp)
+
+ result = lreshape(df, spec, dropna=False)
+ exp_data = {
+ "birthdt": [
+ "08jan2009",
+ "20dec2008",
+ "30dec2008",
+ "21dec2008",
+ "11jan2009",
+ "08jan2009",
+ "20dec2008",
+ "30dec2008",
+ "21dec2008",
+ "11jan2009",
+ "08jan2009",
+ "20dec2008",
+ "30dec2008",
+ "21dec2008",
+ "11jan2009",
+ ],
+ "birthwt": [
+ 1766,
+ 3301,
+ 1454,
+ 3139,
+ 4133,
+ 1766,
+ 3301,
+ 1454,
+ 3139,
+ 4133,
+ 1766,
+ 3301,
+ 1454,
+ 3139,
+ 4133,
+ ],
+ "id": [
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ ],
+ "sex": [
+ "Male",
+ "Female",
+ "Female",
+ "Female",
+ "Female",
+ "Male",
+ "Female",
+ "Female",
+ "Female",
+ "Female",
+ "Male",
+ "Female",
+ "Female",
+ "Female",
+ "Female",
+ ],
+ "visitdt": [
+ "11jan2009",
+ "22dec2008",
+ "04jan2009",
+ "29dec2008",
+ "20jan2009",
+ "21jan2009",
+ np.nan,
+ "22jan2009",
+ "31dec2008",
+ "03feb2009",
+ "05feb2009",
+ np.nan,
+ np.nan,
+ "02jan2009",
+ "15feb2009",
+ ],
+ "wt": [
+ 1823.0,
+ 3338.0,
+ 1549.0,
+ 3298.0,
+ 4306.0,
+ 2011.0,
+ np.nan,
+ 1892.0,
+ 3338.0,
+ 4575.0,
+ 2293.0,
+ np.nan,
+ np.nan,
+ 3377.0,
+ 4805.0,
+ ],
+ }
+ exp = DataFrame(exp_data, columns=result.columns)
+ tm.assert_frame_equal(result, exp)
+
+ spec = {
+ "visitdt": [f"visitdt{i:d}" for i in range(1, 3)],
+ "wt": [f"wt{i:d}" for i in range(1, 4)],
+ }
+ msg = "All column lists must be same length"
+ with pytest.raises(ValueError, match=msg):
+ lreshape(df, spec)
+
+
+class TestWideToLong:
+ def test_simple(self):
+ x = np.random.default_rng(2).standard_normal(3)
+ df = DataFrame(
+ {
+ "A1970": {0: "a", 1: "b", 2: "c"},
+ "A1980": {0: "d", 1: "e", 2: "f"},
+ "B1970": {0: 2.5, 1: 1.2, 2: 0.7},
+ "B1980": {0: 3.2, 1: 1.3, 2: 0.1},
+ "X": dict(zip(range(3), x)),
+ }
+ )
+ df["id"] = df.index
+ exp_data = {
+ "X": x.tolist() + x.tolist(),
+ "A": ["a", "b", "c", "d", "e", "f"],
+ "B": [2.5, 1.2, 0.7, 3.2, 1.3, 0.1],
+ "year": [1970, 1970, 1970, 1980, 1980, 1980],
+ "id": [0, 1, 2, 0, 1, 2],
+ }
+ expected = DataFrame(exp_data)
+ expected = expected.set_index(["id", "year"])[["X", "A", "B"]]
+ result = wide_to_long(df, ["A", "B"], i="id", j="year")
+ tm.assert_frame_equal(result, expected)
+
+ def test_stubs(self):
+ # GH9204 wide_to_long call should not modify 'stubs' list
+ df = DataFrame([[0, 1, 2, 3, 8], [4, 5, 6, 7, 9]])
+ df.columns = ["id", "inc1", "inc2", "edu1", "edu2"]
+ stubs = ["inc", "edu"]
+
+ wide_to_long(df, stubs, i="id", j="age")
+
+ assert stubs == ["inc", "edu"]
+
+ def test_separating_character(self):
+ # GH14779
+
+ x = np.random.default_rng(2).standard_normal(3)
+ df = DataFrame(
+ {
+ "A.1970": {0: "a", 1: "b", 2: "c"},
+ "A.1980": {0: "d", 1: "e", 2: "f"},
+ "B.1970": {0: 2.5, 1: 1.2, 2: 0.7},
+ "B.1980": {0: 3.2, 1: 1.3, 2: 0.1},
+ "X": dict(zip(range(3), x)),
+ }
+ )
+ df["id"] = df.index
+ exp_data = {
+ "X": x.tolist() + x.tolist(),
+ "A": ["a", "b", "c", "d", "e", "f"],
+ "B": [2.5, 1.2, 0.7, 3.2, 1.3, 0.1],
+ "year": [1970, 1970, 1970, 1980, 1980, 1980],
+ "id": [0, 1, 2, 0, 1, 2],
+ }
+ expected = DataFrame(exp_data)
+ expected = expected.set_index(["id", "year"])[["X", "A", "B"]]
+ result = wide_to_long(df, ["A", "B"], i="id", j="year", sep=".")
+ tm.assert_frame_equal(result, expected)
+
+ def test_escapable_characters(self):
+ x = np.random.default_rng(2).standard_normal(3)
+ df = DataFrame(
+ {
+ "A(quarterly)1970": {0: "a", 1: "b", 2: "c"},
+ "A(quarterly)1980": {0: "d", 1: "e", 2: "f"},
+ "B(quarterly)1970": {0: 2.5, 1: 1.2, 2: 0.7},
+ "B(quarterly)1980": {0: 3.2, 1: 1.3, 2: 0.1},
+ "X": dict(zip(range(3), x)),
+ }
+ )
+ df["id"] = df.index
+ exp_data = {
+ "X": x.tolist() + x.tolist(),
+ "A(quarterly)": ["a", "b", "c", "d", "e", "f"],
+ "B(quarterly)": [2.5, 1.2, 0.7, 3.2, 1.3, 0.1],
+ "year": [1970, 1970, 1970, 1980, 1980, 1980],
+ "id": [0, 1, 2, 0, 1, 2],
+ }
+ expected = DataFrame(exp_data)
+ expected = expected.set_index(["id", "year"])[
+ ["X", "A(quarterly)", "B(quarterly)"]
+ ]
+ result = wide_to_long(df, ["A(quarterly)", "B(quarterly)"], i="id", j="year")
+ tm.assert_frame_equal(result, expected)
+
+ def test_unbalanced(self):
+ # test that we can have a varying amount of time variables
+ df = DataFrame(
+ {
+ "A2010": [1.0, 2.0],
+ "A2011": [3.0, 4.0],
+ "B2010": [5.0, 6.0],
+ "X": ["X1", "X2"],
+ }
+ )
+ df["id"] = df.index
+ exp_data = {
+ "X": ["X1", "X2", "X1", "X2"],
+ "A": [1.0, 2.0, 3.0, 4.0],
+ "B": [5.0, 6.0, np.nan, np.nan],
+ "id": [0, 1, 0, 1],
+ "year": [2010, 2010, 2011, 2011],
+ }
+ expected = DataFrame(exp_data)
+ expected = expected.set_index(["id", "year"])[["X", "A", "B"]]
+ result = wide_to_long(df, ["A", "B"], i="id", j="year")
+ tm.assert_frame_equal(result, expected)
+
+ def test_character_overlap(self):
+ # Test we handle overlapping characters in both id_vars and value_vars
+ df = DataFrame(
+ {
+ "A11": ["a11", "a22", "a33"],
+ "A12": ["a21", "a22", "a23"],
+ "B11": ["b11", "b12", "b13"],
+ "B12": ["b21", "b22", "b23"],
+ "BB11": [1, 2, 3],
+ "BB12": [4, 5, 6],
+ "BBBX": [91, 92, 93],
+ "BBBZ": [91, 92, 93],
+ }
+ )
+ df["id"] = df.index
+ expected = DataFrame(
+ {
+ "BBBX": [91, 92, 93, 91, 92, 93],
+ "BBBZ": [91, 92, 93, 91, 92, 93],
+ "A": ["a11", "a22", "a33", "a21", "a22", "a23"],
+ "B": ["b11", "b12", "b13", "b21", "b22", "b23"],
+ "BB": [1, 2, 3, 4, 5, 6],
+ "id": [0, 1, 2, 0, 1, 2],
+ "year": [11, 11, 11, 12, 12, 12],
+ }
+ )
+ expected = expected.set_index(["id", "year"])[["BBBX", "BBBZ", "A", "B", "BB"]]
+ result = wide_to_long(df, ["A", "B", "BB"], i="id", j="year")
+ tm.assert_frame_equal(result.sort_index(axis=1), expected.sort_index(axis=1))
+
+ def test_invalid_separator(self):
+ # if an invalid separator is supplied an empty data frame is returned
+ sep = "nope!"
+ df = DataFrame(
+ {
+ "A2010": [1.0, 2.0],
+ "A2011": [3.0, 4.0],
+ "B2010": [5.0, 6.0],
+ "X": ["X1", "X2"],
+ }
+ )
+ df["id"] = df.index
+ exp_data = {
+ "X": "",
+ "A2010": [],
+ "A2011": [],
+ "B2010": [],
+ "id": [],
+ "year": [],
+ "A": [],
+ "B": [],
+ }
+ expected = DataFrame(exp_data).astype({"year": np.int64})
+ expected = expected.set_index(["id", "year"])[
+ ["X", "A2010", "A2011", "B2010", "A", "B"]
+ ]
+ expected.index = expected.index.set_levels([0, 1], level=0)
+ result = wide_to_long(df, ["A", "B"], i="id", j="year", sep=sep)
+ tm.assert_frame_equal(result.sort_index(axis=1), expected.sort_index(axis=1))
+
+ def test_num_string_disambiguation(self):
+ # Test that we can disambiguate number value_vars from
+ # string value_vars
+ df = DataFrame(
+ {
+ "A11": ["a11", "a22", "a33"],
+ "A12": ["a21", "a22", "a23"],
+ "B11": ["b11", "b12", "b13"],
+ "B12": ["b21", "b22", "b23"],
+ "BB11": [1, 2, 3],
+ "BB12": [4, 5, 6],
+ "Arating": [91, 92, 93],
+ "Arating_old": [91, 92, 93],
+ }
+ )
+ df["id"] = df.index
+ expected = DataFrame(
+ {
+ "Arating": [91, 92, 93, 91, 92, 93],
+ "Arating_old": [91, 92, 93, 91, 92, 93],
+ "A": ["a11", "a22", "a33", "a21", "a22", "a23"],
+ "B": ["b11", "b12", "b13", "b21", "b22", "b23"],
+ "BB": [1, 2, 3, 4, 5, 6],
+ "id": [0, 1, 2, 0, 1, 2],
+ "year": [11, 11, 11, 12, 12, 12],
+ }
+ )
+ expected = expected.set_index(["id", "year"])[
+ ["Arating", "Arating_old", "A", "B", "BB"]
+ ]
+ result = wide_to_long(df, ["A", "B", "BB"], i="id", j="year")
+ tm.assert_frame_equal(result.sort_index(axis=1), expected.sort_index(axis=1))
+
+ def test_invalid_suffixtype(self):
+ # If all stubs names end with a string, but a numeric suffix is
+ # assumed, an empty data frame is returned
+ df = DataFrame(
+ {
+ "Aone": [1.0, 2.0],
+ "Atwo": [3.0, 4.0],
+ "Bone": [5.0, 6.0],
+ "X": ["X1", "X2"],
+ }
+ )
+ df["id"] = df.index
+ exp_data = {
+ "X": "",
+ "Aone": [],
+ "Atwo": [],
+ "Bone": [],
+ "id": [],
+ "year": [],
+ "A": [],
+ "B": [],
+ }
+ expected = DataFrame(exp_data).astype({"year": np.int64})
+
+ expected = expected.set_index(["id", "year"])
+ expected.index = expected.index.set_levels([0, 1], level=0)
+ result = wide_to_long(df, ["A", "B"], i="id", j="year")
+ tm.assert_frame_equal(result.sort_index(axis=1), expected.sort_index(axis=1))
+
+ def test_multiple_id_columns(self):
+ # Taken from http://www.ats.ucla.edu/stat/stata/modules/reshapel.htm
+ df = DataFrame(
+ {
+ "famid": [1, 1, 1, 2, 2, 2, 3, 3, 3],
+ "birth": [1, 2, 3, 1, 2, 3, 1, 2, 3],
+ "ht1": [2.8, 2.9, 2.2, 2, 1.8, 1.9, 2.2, 2.3, 2.1],
+ "ht2": [3.4, 3.8, 2.9, 3.2, 2.8, 2.4, 3.3, 3.4, 2.9],
+ }
+ )
+ expected = DataFrame(
+ {
+ "ht": [
+ 2.8,
+ 3.4,
+ 2.9,
+ 3.8,
+ 2.2,
+ 2.9,
+ 2.0,
+ 3.2,
+ 1.8,
+ 2.8,
+ 1.9,
+ 2.4,
+ 2.2,
+ 3.3,
+ 2.3,
+ 3.4,
+ 2.1,
+ 2.9,
+ ],
+ "famid": [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3],
+ "birth": [1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3, 3],
+ "age": [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2],
+ }
+ )
+ expected = expected.set_index(["famid", "birth", "age"])[["ht"]]
+ result = wide_to_long(df, "ht", i=["famid", "birth"], j="age")
+ tm.assert_frame_equal(result, expected)
+
+ def test_non_unique_idvars(self):
+ # GH16382
+ # Raise an error message if non unique id vars (i) are passed
+ df = DataFrame(
+ {"A_A1": [1, 2, 3, 4, 5], "B_B1": [1, 2, 3, 4, 5], "x": [1, 1, 1, 1, 1]}
+ )
+ msg = "the id variables need to uniquely identify each row"
+ with pytest.raises(ValueError, match=msg):
+ wide_to_long(df, ["A_A", "B_B"], i="x", j="colname")
+
+ def test_cast_j_int(self):
+ df = DataFrame(
+ {
+ "actor_1": ["CCH Pounder", "Johnny Depp", "Christoph Waltz"],
+ "actor_2": ["Joel David Moore", "Orlando Bloom", "Rory Kinnear"],
+ "actor_fb_likes_1": [1000.0, 40000.0, 11000.0],
+ "actor_fb_likes_2": [936.0, 5000.0, 393.0],
+ "title": ["Avatar", "Pirates of the Caribbean", "Spectre"],
+ }
+ )
+
+ expected = DataFrame(
+ {
+ "actor": [
+ "CCH Pounder",
+ "Johnny Depp",
+ "Christoph Waltz",
+ "Joel David Moore",
+ "Orlando Bloom",
+ "Rory Kinnear",
+ ],
+ "actor_fb_likes": [1000.0, 40000.0, 11000.0, 936.0, 5000.0, 393.0],
+ "num": [1, 1, 1, 2, 2, 2],
+ "title": [
+ "Avatar",
+ "Pirates of the Caribbean",
+ "Spectre",
+ "Avatar",
+ "Pirates of the Caribbean",
+ "Spectre",
+ ],
+ }
+ ).set_index(["title", "num"])
+ result = wide_to_long(
+ df, ["actor", "actor_fb_likes"], i="title", j="num", sep="_"
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_identical_stubnames(self):
+ df = DataFrame(
+ {
+ "A2010": [1.0, 2.0],
+ "A2011": [3.0, 4.0],
+ "B2010": [5.0, 6.0],
+ "A": ["X1", "X2"],
+ }
+ )
+ msg = "stubname can't be identical to a column name"
+ with pytest.raises(ValueError, match=msg):
+ wide_to_long(df, ["A", "B"], i="A", j="colname")
+
+ def test_nonnumeric_suffix(self):
+ df = DataFrame(
+ {
+ "treatment_placebo": [1.0, 2.0],
+ "treatment_test": [3.0, 4.0],
+ "result_placebo": [5.0, 6.0],
+ "A": ["X1", "X2"],
+ }
+ )
+ expected = DataFrame(
+ {
+ "A": ["X1", "X2", "X1", "X2"],
+ "colname": ["placebo", "placebo", "test", "test"],
+ "result": [5.0, 6.0, np.nan, np.nan],
+ "treatment": [1.0, 2.0, 3.0, 4.0],
+ }
+ )
+ expected = expected.set_index(["A", "colname"])
+ result = wide_to_long(
+ df, ["result", "treatment"], i="A", j="colname", suffix="[a-z]+", sep="_"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_mixed_type_suffix(self):
+ df = DataFrame(
+ {
+ "A": ["X1", "X2"],
+ "result_1": [0, 9],
+ "result_foo": [5.0, 6.0],
+ "treatment_1": [1.0, 2.0],
+ "treatment_foo": [3.0, 4.0],
+ }
+ )
+ expected = DataFrame(
+ {
+ "A": ["X1", "X2", "X1", "X2"],
+ "colname": ["1", "1", "foo", "foo"],
+ "result": [0.0, 9.0, 5.0, 6.0],
+ "treatment": [1.0, 2.0, 3.0, 4.0],
+ }
+ ).set_index(["A", "colname"])
+ result = wide_to_long(
+ df, ["result", "treatment"], i="A", j="colname", suffix=".+", sep="_"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_float_suffix(self):
+ df = DataFrame(
+ {
+ "treatment_1.1": [1.0, 2.0],
+ "treatment_2.1": [3.0, 4.0],
+ "result_1.2": [5.0, 6.0],
+ "result_1": [0, 9],
+ "A": ["X1", "X2"],
+ }
+ )
+ expected = DataFrame(
+ {
+ "A": ["X1", "X2", "X1", "X2", "X1", "X2", "X1", "X2"],
+ "colname": [1.2, 1.2, 1.0, 1.0, 1.1, 1.1, 2.1, 2.1],
+ "result": [5.0, 6.0, 0.0, 9.0, np.nan, np.nan, np.nan, np.nan],
+ "treatment": [np.nan, np.nan, np.nan, np.nan, 1.0, 2.0, 3.0, 4.0],
+ }
+ )
+ expected = expected.set_index(["A", "colname"])
+ result = wide_to_long(
+ df, ["result", "treatment"], i="A", j="colname", suffix="[0-9.]+", sep="_"
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_col_substring_of_stubname(self):
+ # GH22468
+ # Don't raise ValueError when a column name is a substring
+ # of a stubname that's been passed as a string
+ wide_data = {
+ "node_id": {0: 0, 1: 1, 2: 2, 3: 3, 4: 4},
+ "A": {0: 0.80, 1: 0.0, 2: 0.25, 3: 1.0, 4: 0.81},
+ "PA0": {0: 0.74, 1: 0.56, 2: 0.56, 3: 0.98, 4: 0.6},
+ "PA1": {0: 0.77, 1: 0.64, 2: 0.52, 3: 0.98, 4: 0.67},
+ "PA3": {0: 0.34, 1: 0.70, 2: 0.52, 3: 0.98, 4: 0.67},
+ }
+ wide_df = DataFrame.from_dict(wide_data)
+ expected = wide_to_long(wide_df, stubnames=["PA"], i=["node_id", "A"], j="time")
+ result = wide_to_long(wide_df, stubnames="PA", i=["node_id", "A"], j="time")
+ tm.assert_frame_equal(result, expected)
+
+ def test_raise_of_column_name_value(self):
+ # GH34731, enforced in 2.0
+ # raise a ValueError if the resultant value column name matches
+ # a name in the dataframe already (default name is "value")
+ df = DataFrame({"col": list("ABC"), "value": range(10, 16, 2)})
+
+ with pytest.raises(
+ ValueError, match=re.escape("value_name (value) cannot match")
+ ):
+ df.melt(id_vars="value", value_name="value")
+
+ def test_missing_stubname(self, any_string_dtype):
+ # GH46044
+ df = DataFrame({"id": ["1", "2"], "a-1": [100, 200], "a-2": [300, 400]})
+ df = df.astype({"id": any_string_dtype})
+ result = wide_to_long(
+ df,
+ stubnames=["a", "b"],
+ i="id",
+ j="num",
+ sep="-",
+ )
+ index = Index(
+ [("1", 1), ("2", 1), ("1", 2), ("2", 2)],
+ name=("id", "num"),
+ )
+ expected = DataFrame(
+ {"a": [100, 200, 300, 400], "b": [np.nan] * 4},
+ index=index,
+ )
+ new_level = expected.index.levels[0].astype(any_string_dtype)
+ if any_string_dtype == "object":
+ new_level = expected.index.levels[0].astype("str")
+ expected.index = expected.index.set_levels(new_level, level=0)
+ tm.assert_frame_equal(result, expected)
+
+
+def test_wide_to_long_string_columns(string_storage):
+ # GH 57066
+ string_dtype = pd.StringDtype(string_storage, na_value=np.nan)
+ df = DataFrame(
+ {
+ "ID": {0: 1},
+ "R_test1": {0: 1},
+ "R_test2": {0: 1},
+ "R_test3": {0: 2},
+ "D": {0: 1},
+ }
+ )
+ df.columns = df.columns.astype(string_dtype)
+ result = wide_to_long(
+ df, stubnames="R", i="ID", j="UNPIVOTED", sep="_", suffix=".*"
+ )
+ expected = DataFrame(
+ [[1, 1], [1, 1], [1, 2]],
+ columns=Index(["D", "R"]),
+ index=pd.MultiIndex.from_arrays(
+ [
+ [1, 1, 1],
+ Index(["test1", "test2", "test3"], dtype=string_dtype),
+ ],
+ names=["ID", "UNPIVOTED"],
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_pivot.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_pivot.py
new file mode 100644
index 0000000000000000000000000000000000000000..6745ba0bac765a17b583724cc57dc207bb2e81a6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_pivot.py
@@ -0,0 +1,2961 @@
+from datetime import (
+ date,
+ datetime,
+ timedelta,
+)
+from itertools import product
+import re
+
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+import pandas as pd
+from pandas import (
+ ArrowDtype,
+ Categorical,
+ DataFrame,
+ Grouper,
+ Index,
+ MultiIndex,
+ Series,
+ concat,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.api.types import CategoricalDtype
+from pandas.core.reshape import reshape as reshape_lib
+from pandas.core.reshape.pivot import pivot_table
+
+
+class TestPivotTable:
+ @pytest.fixture
+ def data(self):
+ return DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "C": [
+ "dull",
+ "dull",
+ "shiny",
+ "dull",
+ "dull",
+ "shiny",
+ "shiny",
+ "dull",
+ "shiny",
+ "shiny",
+ "shiny",
+ ],
+ "D": np.random.default_rng(2).standard_normal(11),
+ "E": np.random.default_rng(2).standard_normal(11),
+ "F": np.random.default_rng(2).standard_normal(11),
+ }
+ )
+
+ def test_pivot_table(self, observed, data):
+ index = ["A", "B"]
+ columns = "C"
+ table = pivot_table(
+ data, values="D", index=index, columns=columns, observed=observed
+ )
+
+ table2 = data.pivot_table(
+ values="D", index=index, columns=columns, observed=observed
+ )
+ tm.assert_frame_equal(table, table2)
+
+ # this works
+ pivot_table(data, values="D", index=index, observed=observed)
+
+ if len(index) > 1:
+ assert table.index.names == tuple(index)
+ else:
+ assert table.index.name == index[0]
+
+ if len(columns) > 1:
+ assert table.columns.names == columns
+ else:
+ assert table.columns.name == columns[0]
+
+ expected = data.groupby([*index, columns])["D"].agg("mean").unstack()
+ tm.assert_frame_equal(table, expected)
+
+ def test_pivot_table_categorical_observed_equal(self, observed):
+ # issue #24923
+ df = DataFrame(
+ {"col1": list("abcde"), "col2": list("fghij"), "col3": [1, 2, 3, 4, 5]}
+ )
+
+ expected = df.pivot_table(
+ index="col1", values="col3", columns="col2", aggfunc="sum", fill_value=0
+ )
+
+ expected.index = expected.index.astype("category")
+ expected.columns = expected.columns.astype("category")
+
+ df.col1 = df.col1.astype("category")
+ df.col2 = df.col2.astype("category")
+
+ result = df.pivot_table(
+ index="col1",
+ values="col3",
+ columns="col2",
+ aggfunc="sum",
+ fill_value=0,
+ observed=observed,
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_nocols(self):
+ df = DataFrame(
+ {"rows": ["a", "b", "c"], "cols": ["x", "y", "z"], "values": [1, 2, 3]}
+ )
+ rs = df.pivot_table(columns="cols", aggfunc="sum")
+ xp = df.pivot_table(index="cols", aggfunc="sum").T
+ tm.assert_frame_equal(rs, xp)
+
+ rs = df.pivot_table(columns="cols", aggfunc={"values": "mean"})
+ xp = df.pivot_table(index="cols", aggfunc={"values": "mean"}).T
+ tm.assert_frame_equal(rs, xp)
+
+ def test_pivot_table_dropna(self):
+ df = DataFrame(
+ {
+ "amount": {0: 60000, 1: 100000, 2: 50000, 3: 30000},
+ "customer": {0: "A", 1: "A", 2: "B", 3: "C"},
+ "month": {0: 201307, 1: 201309, 2: 201308, 3: 201310},
+ "product": {0: "a", 1: "b", 2: "c", 3: "d"},
+ "quantity": {0: 2000000, 1: 500000, 2: 1000000, 3: 1000000},
+ }
+ )
+ pv_col = df.pivot_table(
+ "quantity", "month", ["customer", "product"], dropna=False
+ )
+ pv_ind = df.pivot_table(
+ "quantity", ["customer", "product"], "month", dropna=False
+ )
+
+ m = MultiIndex.from_tuples(
+ [
+ ("A", "a"),
+ ("A", "b"),
+ ("A", "c"),
+ ("A", "d"),
+ ("B", "a"),
+ ("B", "b"),
+ ("B", "c"),
+ ("B", "d"),
+ ("C", "a"),
+ ("C", "b"),
+ ("C", "c"),
+ ("C", "d"),
+ ],
+ names=["customer", "product"],
+ )
+ tm.assert_index_equal(pv_col.columns, m)
+ tm.assert_index_equal(pv_ind.index, m)
+
+ def test_pivot_table_categorical(self):
+ cat1 = Categorical(
+ ["a", "a", "b", "b"], categories=["a", "b", "z"], ordered=True
+ )
+ cat2 = Categorical(
+ ["c", "d", "c", "d"], categories=["c", "d", "y"], ordered=True
+ )
+ df = DataFrame({"A": cat1, "B": cat2, "values": [1, 2, 3, 4]})
+ result = pivot_table(
+ df, values="values", index=["A", "B"], dropna=True, observed=False
+ )
+
+ exp_index = MultiIndex.from_arrays([cat1, cat2], names=["A", "B"])
+ expected = DataFrame({"values": [1.0, 2.0, 3.0, 4.0]}, index=exp_index)
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_dropna_categoricals(self, dropna):
+ # GH 15193
+ categories = ["a", "b", "c", "d"]
+
+ df = DataFrame(
+ {
+ "A": ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
+ "B": [1, 2, 3, 1, 2, 3, 1, 2, 3],
+ "C": range(9),
+ }
+ )
+
+ df["A"] = df["A"].astype(CategoricalDtype(categories, ordered=False))
+ result = df.pivot_table(
+ index="B", columns="A", values="C", dropna=dropna, observed=False
+ )
+ expected_columns = Series(["a", "b", "c"], name="A")
+ expected_columns = expected_columns.astype(
+ CategoricalDtype(categories, ordered=False)
+ )
+ expected_index = Series([1, 2, 3], name="B")
+ expected = DataFrame(
+ [[0.0, 3.0, 6.0], [1.0, 4.0, 7.0], [2.0, 5.0, 8.0]],
+ index=expected_index,
+ columns=expected_columns,
+ )
+ if not dropna:
+ # add back the non observed to compare
+ expected = expected.reindex(columns=Categorical(categories)).astype("float")
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_with_non_observable_dropna(self, dropna):
+ # gh-21133
+ df = DataFrame(
+ {
+ "A": Categorical(
+ [np.nan, "low", "high", "low", "high"],
+ categories=["low", "high"],
+ ordered=True,
+ ),
+ "B": [0.0, 1.0, 2.0, 3.0, 4.0],
+ }
+ )
+
+ result = df.pivot_table(index="A", values="B", dropna=dropna, observed=False)
+ if dropna:
+ values = [2.0, 3.0]
+ codes = [0, 1]
+ else:
+ # GH: 10772
+ values = [2.0, 3.0, 0.0]
+ codes = [0, 1, -1]
+ expected = DataFrame(
+ {"B": values},
+ index=Index(
+ Categorical.from_codes(codes, categories=["low", "high"], ordered=True),
+ name="A",
+ ),
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_with_non_observable_dropna_multi_cat(self, dropna):
+ # gh-21378
+ df = DataFrame(
+ {
+ "A": Categorical(
+ ["left", "low", "high", "low", "high"],
+ categories=["low", "high", "left"],
+ ordered=True,
+ ),
+ "B": range(5),
+ }
+ )
+
+ result = df.pivot_table(index="A", values="B", dropna=dropna, observed=False)
+ expected = DataFrame(
+ {"B": [2.0, 3.0, 0.0]},
+ index=Index(
+ Categorical.from_codes(
+ [0, 1, 2], categories=["low", "high", "left"], ordered=True
+ ),
+ name="A",
+ ),
+ )
+ if not dropna:
+ expected["B"] = expected["B"].astype(float)
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "left_right", [([0] * 4, [1] * 4), (range(3), range(1, 4))]
+ )
+ def test_pivot_with_interval_index(self, left_right, dropna, closed):
+ # GH 25814
+ left, right = left_right
+ interval_values = Categorical(pd.IntervalIndex.from_arrays(left, right, closed))
+ df = DataFrame({"A": interval_values, "B": 1})
+
+ result = df.pivot_table(index="A", values="B", dropna=dropna, observed=False)
+ expected = DataFrame(
+ {"B": 1.0}, index=Index(interval_values.unique(), name="A")
+ )
+ if not dropna:
+ expected = expected.astype(float)
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_with_interval_index_margins(self):
+ # GH 25815
+ ordered_cat = pd.IntervalIndex.from_arrays([0, 0, 1, 1], [1, 1, 2, 2])
+ df = DataFrame(
+ {
+ "A": np.arange(4, 0, -1, dtype=np.intp),
+ "B": ["a", "b", "a", "b"],
+ "C": Categorical(ordered_cat, ordered=True).sort_values(
+ ascending=False
+ ),
+ }
+ )
+
+ pivot_tab = pivot_table(
+ df,
+ index="C",
+ columns="B",
+ values="A",
+ aggfunc="sum",
+ margins=True,
+ observed=False,
+ )
+
+ result = pivot_tab["All"]
+ expected = Series(
+ [3, 7, 10],
+ index=Index([pd.Interval(0, 1), pd.Interval(1, 2), "All"], name="C"),
+ name="All",
+ dtype=np.intp,
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_pass_array(self, data):
+ result = data.pivot_table("D", index=data.A, columns=data.C)
+ expected = data.pivot_table("D", index="A", columns="C")
+ tm.assert_frame_equal(result, expected)
+
+ def test_pass_function(self, data):
+ result = data.pivot_table("D", index=lambda x: x // 5, columns=data.C)
+ expected = data.pivot_table("D", index=data.index // 5, columns="C")
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_multiple(self, data):
+ index = ["A", "B"]
+ columns = "C"
+ table = pivot_table(data, index=index, columns=columns)
+ expected = data.groupby([*index, columns]).agg("mean").unstack()
+ tm.assert_frame_equal(table, expected)
+
+ def test_pivot_dtypes(self):
+ # can convert dtypes
+ f = DataFrame(
+ {
+ "a": ["cat", "bat", "cat", "bat"],
+ "v": [1, 2, 3, 4],
+ "i": ["a", "b", "a", "b"],
+ }
+ )
+ assert f.dtypes["v"] == "int64"
+
+ z = pivot_table(
+ f, values="v", index=["a"], columns=["i"], fill_value=0, aggfunc="sum"
+ )
+ result = z.dtypes
+ expected = Series([np.dtype("int64")] * 2, index=Index(list("ab"), name="i"))
+ tm.assert_series_equal(result, expected)
+
+ # cannot convert dtypes
+ f = DataFrame(
+ {
+ "a": ["cat", "bat", "cat", "bat"],
+ "v": [1.5, 2.5, 3.5, 4.5],
+ "i": ["a", "b", "a", "b"],
+ }
+ )
+ assert f.dtypes["v"] == "float64"
+
+ z = pivot_table(
+ f, values="v", index=["a"], columns=["i"], fill_value=0, aggfunc="mean"
+ )
+ result = z.dtypes
+ expected = Series([np.dtype("float64")] * 2, index=Index(list("ab"), name="i"))
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "columns,values",
+ [
+ ("bool1", ["float1", "float2"]),
+ ("bool1", ["float1", "float2", "bool1"]),
+ ("bool2", ["float1", "float2", "bool1"]),
+ ],
+ )
+ def test_pivot_preserve_dtypes(self, columns, values):
+ # GH 7142 regression test
+ v = np.arange(5, dtype=np.float64)
+ df = DataFrame(
+ {"float1": v, "float2": v + 2.0, "bool1": v <= 2, "bool2": v <= 3}
+ )
+
+ df_res = df.reset_index().pivot_table(
+ index="index", columns=columns, values=values
+ )
+
+ result = dict(df_res.dtypes)
+ expected = {col: np.dtype("float64") for col in df_res}
+ assert result == expected
+
+ def test_pivot_no_values(self):
+ # GH 14380
+ idx = pd.DatetimeIndex(
+ ["2011-01-01", "2011-02-01", "2011-01-02", "2011-01-01", "2011-01-02"]
+ )
+ df = DataFrame({"A": [1, 2, 3, 4, 5]}, index=idx)
+ res = df.pivot_table(index=df.index.month, columns=df.index.day)
+
+ exp_columns = MultiIndex.from_tuples([("A", 1), ("A", 2)])
+ exp_columns = exp_columns.set_levels(
+ exp_columns.levels[1].astype(np.int32), level=1
+ )
+ exp = DataFrame(
+ [[2.5, 4.0], [2.0, np.nan]],
+ index=Index([1, 2], dtype=np.int32),
+ columns=exp_columns,
+ )
+ tm.assert_frame_equal(res, exp)
+
+ df = DataFrame(
+ {
+ "A": [1, 2, 3, 4, 5],
+ "dt": date_range("2011-01-01", freq="D", periods=5, unit="ns"),
+ },
+ index=idx,
+ )
+ res = df.pivot_table(index=df.index.month, columns=Grouper(key="dt", freq="ME"))
+ exp_columns = MultiIndex.from_arrays(
+ [["A"], pd.DatetimeIndex(["2011-01-31"], dtype="M8[ns]")],
+ names=[None, "dt"],
+ )
+ exp = DataFrame(
+ [3.25, 2.0], index=Index([1, 2], dtype=np.int32), columns=exp_columns
+ )
+ tm.assert_frame_equal(res, exp)
+
+ res = df.pivot_table(
+ index=Grouper(freq="YE"), columns=Grouper(key="dt", freq="ME")
+ )
+ exp = DataFrame(
+ [3.0],
+ index=pd.DatetimeIndex(["2011-12-31"], freq="YE"),
+ columns=exp_columns,
+ )
+ tm.assert_frame_equal(res, exp)
+
+ def test_pivot_multi_values(self, data):
+ result = pivot_table(
+ data, values=["D", "E"], index="A", columns=["B", "C"], fill_value=0
+ )
+ expected = pivot_table(
+ data.drop(["F"], axis=1), index="A", columns=["B", "C"], fill_value=0
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_multi_functions(self, data):
+ f = lambda func: pivot_table(
+ data, values=["D", "E"], index=["A", "B"], columns="C", aggfunc=func
+ )
+ result = f(["mean", "std"])
+ means = f("mean")
+ stds = f("std")
+ expected = concat([means, stds], keys=["mean", "std"], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ # margins not supported??
+ f = lambda func: pivot_table(
+ data,
+ values=["D", "E"],
+ index=["A", "B"],
+ columns="C",
+ aggfunc=func,
+ margins=True,
+ )
+ result = f(["mean", "std"])
+ means = f("mean")
+ stds = f("std")
+ expected = concat([means, stds], keys=["mean", "std"], axis=1)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("method", [True, False])
+ def test_pivot_index_with_nan(self, method):
+ # GH 3588
+ nan = np.nan
+ df = DataFrame(
+ {
+ "a": ["R1", "R2", nan, "R4"],
+ "b": ["C1", "C2", "C3", "C4"],
+ "c": [10, 15, 17, 20],
+ }
+ )
+ if method:
+ result = df.pivot(index="a", columns="b", values="c")
+ else:
+ result = pd.pivot(df, index="a", columns="b", values="c")
+ expected = DataFrame(
+ [
+ [nan, nan, 17, nan],
+ [10, nan, nan, nan],
+ [nan, 15, nan, nan],
+ [nan, nan, nan, 20],
+ ],
+ index=Index([nan, "R1", "R2", "R4"], name="a"),
+ columns=Index(["C1", "C2", "C3", "C4"], name="b"),
+ )
+ tm.assert_frame_equal(result, expected)
+ tm.assert_frame_equal(df.pivot(index="b", columns="a", values="c"), expected.T)
+
+ @pytest.mark.parametrize("method", [True, False])
+ def test_pivot_index_with_nan_dates(self, method):
+ # GH9491
+ df = DataFrame(
+ {
+ "a": date_range("2014-02-01", periods=6, freq="D"),
+ "c": 100 + np.arange(6),
+ }
+ )
+ df["b"] = df["a"] - pd.Timestamp("2014-02-02")
+ df.loc[1, "a"] = df.loc[3, "a"] = np.nan
+ df.loc[1, "b"] = df.loc[4, "b"] = np.nan
+
+ if method:
+ pv = df.pivot(index="a", columns="b", values="c")
+ else:
+ pv = pd.pivot(df, index="a", columns="b", values="c")
+ assert pv.notna().values.sum() == len(df)
+
+ for _, row in df.iterrows():
+ assert pv.loc[row["a"], row["b"]] == row["c"]
+
+ if method:
+ result = df.pivot(index="b", columns="a", values="c")
+ else:
+ result = pd.pivot(df, index="b", columns="a", values="c")
+ tm.assert_frame_equal(result, pv.T)
+
+ @pytest.mark.parametrize("method", [True, False])
+ def test_pivot_with_tz(self, method, unit):
+ # GH 5878
+ df = DataFrame(
+ {
+ "dt1": pd.DatetimeIndex(
+ [
+ datetime(2013, 1, 1, 9, 0),
+ datetime(2013, 1, 2, 9, 0),
+ datetime(2013, 1, 1, 9, 0),
+ datetime(2013, 1, 2, 9, 0),
+ ],
+ dtype=f"M8[{unit}, US/Pacific]",
+ ),
+ "dt2": pd.DatetimeIndex(
+ [
+ datetime(2014, 1, 1, 9, 0),
+ datetime(2014, 1, 1, 9, 0),
+ datetime(2014, 1, 2, 9, 0),
+ datetime(2014, 1, 2, 9, 0),
+ ],
+ dtype=f"M8[{unit}, Asia/Tokyo]",
+ ),
+ "data1": np.arange(4, dtype="int64"),
+ "data2": np.arange(4, dtype="int64"),
+ }
+ )
+
+ exp_col1 = Index(["data1", "data1", "data2", "data2"])
+ exp_col2 = pd.DatetimeIndex(
+ ["2014/01/01 09:00", "2014/01/02 09:00"] * 2,
+ name="dt2",
+ dtype=f"M8[{unit}, Asia/Tokyo]",
+ )
+ exp_col = MultiIndex.from_arrays([exp_col1, exp_col2])
+ exp_idx = pd.DatetimeIndex(
+ ["2013/01/01 09:00", "2013/01/02 09:00"],
+ name="dt1",
+ dtype=f"M8[{unit}, US/Pacific]",
+ )
+ expected = DataFrame(
+ [[0, 2, 0, 2], [1, 3, 1, 3]],
+ index=exp_idx,
+ columns=exp_col,
+ )
+
+ if method:
+ pv = df.pivot(index="dt1", columns="dt2")
+ else:
+ pv = pd.pivot(df, index="dt1", columns="dt2")
+ tm.assert_frame_equal(pv, expected)
+
+ expected = DataFrame(
+ [[0, 2], [1, 3]],
+ index=exp_idx,
+ columns=exp_col2[:2],
+ )
+
+ if method:
+ pv = df.pivot(index="dt1", columns="dt2", values="data1")
+ else:
+ pv = pd.pivot(df, index="dt1", columns="dt2", values="data1")
+ tm.assert_frame_equal(pv, expected)
+
+ def test_pivot_tz_in_values(self):
+ # GH 14948
+ df = DataFrame(
+ [
+ {
+ "uid": "aa",
+ "ts": pd.Timestamp("2016-08-12 13:00:00-0700", tz="US/Pacific"),
+ },
+ {
+ "uid": "aa",
+ "ts": pd.Timestamp("2016-08-12 08:00:00-0700", tz="US/Pacific"),
+ },
+ {
+ "uid": "aa",
+ "ts": pd.Timestamp("2016-08-12 14:00:00-0700", tz="US/Pacific"),
+ },
+ {
+ "uid": "aa",
+ "ts": pd.Timestamp("2016-08-25 11:00:00-0700", tz="US/Pacific"),
+ },
+ {
+ "uid": "aa",
+ "ts": pd.Timestamp("2016-08-25 13:00:00-0700", tz="US/Pacific"),
+ },
+ ]
+ )
+
+ df = df.set_index("ts").reset_index()
+ mins = df.ts.map(lambda x: x.replace(hour=0, minute=0, second=0))
+
+ result = pivot_table(
+ df.set_index("ts").reset_index(),
+ values="ts",
+ index=["uid"],
+ columns=[mins],
+ aggfunc="min",
+ )
+ expected = DataFrame(
+ [
+ [
+ pd.Timestamp("2016-08-12 08:00:00-0700", tz="US/Pacific"),
+ pd.Timestamp("2016-08-25 11:00:00-0700", tz="US/Pacific"),
+ ]
+ ],
+ index=Index(["aa"], name="uid"),
+ columns=pd.DatetimeIndex(
+ [
+ pd.Timestamp("2016-08-12 00:00:00", tz="US/Pacific"),
+ pd.Timestamp("2016-08-25 00:00:00", tz="US/Pacific"),
+ ],
+ name="ts",
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("method", [True, False])
+ def test_pivot_periods(self, method):
+ df = DataFrame(
+ {
+ "p1": [
+ pd.Period("2013-01-01", "D"),
+ pd.Period("2013-01-02", "D"),
+ pd.Period("2013-01-01", "D"),
+ pd.Period("2013-01-02", "D"),
+ ],
+ "p2": [
+ pd.Period("2013-01", "M"),
+ pd.Period("2013-01", "M"),
+ pd.Period("2013-02", "M"),
+ pd.Period("2013-02", "M"),
+ ],
+ "data1": np.arange(4, dtype="int64"),
+ "data2": np.arange(4, dtype="int64"),
+ }
+ )
+
+ exp_col1 = Index(["data1", "data1", "data2", "data2"])
+ exp_col2 = pd.PeriodIndex(["2013-01", "2013-02"] * 2, name="p2", freq="M")
+ exp_col = MultiIndex.from_arrays([exp_col1, exp_col2])
+ expected = DataFrame(
+ [[0, 2, 0, 2], [1, 3, 1, 3]],
+ index=pd.PeriodIndex(["2013-01-01", "2013-01-02"], name="p1", freq="D"),
+ columns=exp_col,
+ )
+ if method:
+ pv = df.pivot(index="p1", columns="p2")
+ else:
+ pv = pd.pivot(df, index="p1", columns="p2")
+ tm.assert_frame_equal(pv, expected)
+
+ expected = DataFrame(
+ [[0, 2], [1, 3]],
+ index=pd.PeriodIndex(["2013-01-01", "2013-01-02"], name="p1", freq="D"),
+ columns=pd.PeriodIndex(["2013-01", "2013-02"], name="p2", freq="M"),
+ )
+ if method:
+ pv = df.pivot(index="p1", columns="p2", values="data1")
+ else:
+ pv = pd.pivot(df, index="p1", columns="p2", values="data1")
+ tm.assert_frame_equal(pv, expected)
+
+ def test_pivot_periods_with_margins(self):
+ # GH 28323
+ df = DataFrame(
+ {
+ "a": [1, 1, 2, 2],
+ "b": [
+ pd.Period("2019Q1"),
+ pd.Period("2019Q2"),
+ pd.Period("2019Q1"),
+ pd.Period("2019Q2"),
+ ],
+ "x": 1.0,
+ }
+ )
+
+ expected = DataFrame(
+ data=1.0,
+ index=Index([1, 2, "All"], name="a"),
+ columns=Index([pd.Period("2019Q1"), pd.Period("2019Q2"), "All"], name="b"),
+ )
+
+ result = df.pivot_table(index="a", columns="b", values="x", margins=True)
+ tm.assert_frame_equal(expected, result)
+
+ @pytest.mark.parametrize("box", [list, np.array, Series, Index])
+ @pytest.mark.parametrize("method", [True, False])
+ def test_pivot_with_list_like_values(self, box, method):
+ # issue #17160
+ values = box(["baz", "zoo"])
+ df = DataFrame(
+ {
+ "foo": ["one", "one", "one", "two", "two", "two"],
+ "bar": ["A", "B", "C", "A", "B", "C"],
+ "baz": [1, 2, 3, 4, 5, 6],
+ "zoo": ["x", "y", "z", "q", "w", "t"],
+ }
+ )
+
+ if method:
+ result = df.pivot(index="foo", columns="bar", values=values)
+ else:
+ result = pd.pivot(df, index="foo", columns="bar", values=values)
+
+ data = [[1, 2, 3, "x", "y", "z"], [4, 5, 6, "q", "w", "t"]]
+ index = Index(data=["one", "two"], name="foo")
+ columns = MultiIndex(
+ levels=[["baz", "zoo"], ["A", "B", "C"]],
+ codes=[[0, 0, 0, 1, 1, 1], [0, 1, 2, 0, 1, 2]],
+ names=[None, "bar"],
+ )
+ expected = DataFrame(data=data, index=index, columns=columns)
+ expected["baz"] = expected["baz"].astype(object)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "values",
+ [
+ ["bar", "baz"],
+ np.array(["bar", "baz"]),
+ Series(["bar", "baz"]),
+ Index(["bar", "baz"]),
+ ],
+ )
+ @pytest.mark.parametrize("method", [True, False])
+ def test_pivot_with_list_like_values_nans(self, values, method):
+ # issue #17160
+ df = DataFrame(
+ {
+ "foo": ["one", "one", "one", "two", "two", "two"],
+ "bar": ["A", "B", "C", "A", "B", "C"],
+ "baz": [1, 2, 3, 4, 5, 6],
+ "zoo": ["x", "y", "z", "q", "w", "t"],
+ }
+ )
+
+ if method:
+ result = df.pivot(index="zoo", columns="foo", values=values)
+ else:
+ result = pd.pivot(df, index="zoo", columns="foo", values=values)
+
+ data = [
+ [np.nan, "A", np.nan, 4],
+ [np.nan, "C", np.nan, 6],
+ [np.nan, "B", np.nan, 5],
+ ["A", np.nan, 1, np.nan],
+ ["B", np.nan, 2, np.nan],
+ ["C", np.nan, 3, np.nan],
+ ]
+ index = Index(data=["q", "t", "w", "x", "y", "z"], name="zoo")
+ columns = MultiIndex(
+ levels=[["bar", "baz"], ["one", "two"]],
+ codes=[[0, 0, 1, 1], [0, 1, 0, 1]],
+ names=[None, "foo"],
+ )
+ expected = DataFrame(data=data, index=index, columns=columns)
+ expected["baz"] = expected["baz"].astype(object)
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_columns_none_raise_error(self):
+ # GH 30924
+ df = DataFrame({"col1": ["a", "b", "c"], "col2": [1, 2, 3], "col3": [1, 2, 3]})
+ msg = r"pivot\(\) missing 1 required keyword-only argument: 'columns'"
+ with pytest.raises(TypeError, match=msg):
+ df.pivot(index="col1", values="col3")
+
+ @pytest.mark.xfail(
+ reason="MultiIndexed unstack with tuple names fails with KeyError GH#19966"
+ )
+ @pytest.mark.parametrize("method", [True, False])
+ def test_pivot_with_multiindex(self, method):
+ # issue #17160
+ index = Index(data=[0, 1, 2, 3, 4, 5])
+ data = [
+ ["one", "A", 1, "x"],
+ ["one", "B", 2, "y"],
+ ["one", "C", 3, "z"],
+ ["two", "A", 4, "q"],
+ ["two", "B", 5, "w"],
+ ["two", "C", 6, "t"],
+ ]
+ columns = MultiIndex(
+ levels=[["bar", "baz"], ["first", "second"]],
+ codes=[[0, 0, 1, 1], [0, 1, 0, 1]],
+ )
+ df = DataFrame(data=data, index=index, columns=columns, dtype="object")
+ if method:
+ result = df.pivot(
+ index=("bar", "first"),
+ columns=("bar", "second"),
+ values=("baz", "first"),
+ )
+ else:
+ result = pd.pivot(
+ df,
+ index=("bar", "first"),
+ columns=("bar", "second"),
+ values=("baz", "first"),
+ )
+
+ data = {
+ "A": Series([1, 4], index=["one", "two"]),
+ "B": Series([2, 5], index=["one", "two"]),
+ "C": Series([3, 6], index=["one", "two"]),
+ }
+ expected = DataFrame(data)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("method", [True, False])
+ def test_pivot_with_tuple_of_values(self, method):
+ # issue #17160
+ df = DataFrame(
+ {
+ "foo": ["one", "one", "one", "two", "two", "two"],
+ "bar": ["A", "B", "C", "A", "B", "C"],
+ "baz": [1, 2, 3, 4, 5, 6],
+ "zoo": ["x", "y", "z", "q", "w", "t"],
+ }
+ )
+ with pytest.raises(KeyError, match=r"^\('bar', 'baz'\)$"):
+ # tuple is seen as a single column name
+ if method:
+ df.pivot(index="zoo", columns="foo", values=("bar", "baz"))
+ else:
+ pd.pivot(df, index="zoo", columns="foo", values=("bar", "baz"))
+
+ def _check_output(
+ self,
+ result,
+ values_col,
+ data,
+ index=None,
+ columns=None,
+ margins_col="All",
+ ):
+ if index is None:
+ index = ["A", "B"]
+ if columns is None:
+ columns = ["C"]
+ col_margins = result.loc[result.index[:-1], margins_col]
+ expected_col_margins = data.groupby(index)[values_col].mean()
+ tm.assert_series_equal(col_margins, expected_col_margins, check_names=False)
+ assert col_margins.name == margins_col
+
+ result = result.sort_index()
+ index_margins = result.loc[(margins_col, "")].iloc[:-1]
+
+ expected_ix_margins = data.groupby(columns)[values_col].mean()
+ tm.assert_series_equal(index_margins, expected_ix_margins, check_names=False)
+ assert index_margins.name == (margins_col, "")
+
+ grand_total_margins = result.loc[(margins_col, ""), margins_col]
+ expected_total_margins = data[values_col].mean()
+ assert grand_total_margins == expected_total_margins
+
+ def test_margins(self, data):
+ # column specified
+ result = data.pivot_table(
+ values="D", index=["A", "B"], columns="C", margins=True, aggfunc="mean"
+ )
+ self._check_output(result, "D", data)
+
+ # Set a different margins_name (not 'All')
+ result = data.pivot_table(
+ values="D",
+ index=["A", "B"],
+ columns="C",
+ margins=True,
+ aggfunc="mean",
+ margins_name="Totals",
+ )
+ self._check_output(result, "D", data, margins_col="Totals")
+
+ # no column specified
+ table = data.pivot_table(
+ index=["A", "B"], columns="C", margins=True, aggfunc="mean"
+ )
+ for value_col in table.columns.levels[0]:
+ self._check_output(table[value_col], value_col, data)
+
+ def test_no_col(self, data, using_infer_string):
+ # no col
+
+ # to help with a buglet
+ data.columns = [k * 2 for k in data.columns]
+ msg = re.escape("agg function failed [how->mean,dtype->")
+ if using_infer_string:
+ msg = "dtype 'str' does not support operation 'mean'"
+ with pytest.raises(TypeError, match=msg):
+ data.pivot_table(index=["AA", "BB"], margins=True, aggfunc="mean")
+ table = data.drop(columns="CC").pivot_table(
+ index=["AA", "BB"], margins=True, aggfunc="mean"
+ )
+ for value_col in table.columns:
+ totals = table.loc[("All", ""), value_col]
+ assert totals == data[value_col].mean()
+
+ with pytest.raises(TypeError, match=msg):
+ data.pivot_table(index=["AA", "BB"], margins=True, aggfunc="mean")
+ table = data.drop(columns="CC").pivot_table(
+ index=["AA", "BB"], margins=True, aggfunc="mean"
+ )
+ for item in ["DD", "EE", "FF"]:
+ totals = table.loc[("All", ""), item]
+ assert totals == data[item].mean()
+
+ @pytest.mark.parametrize(
+ "columns, aggfunc, values, expected_columns",
+ [
+ (
+ "A",
+ "mean",
+ [[5.5, 5.5, 2.2, 2.2], [8.0, 8.0, 4.4, 4.4]],
+ Index(["bar", "All", "foo", "All"], name="A"),
+ ),
+ (
+ ["A", "B"],
+ "sum",
+ [
+ [9, 13, 22, 5, 6, 11],
+ [14, 18, 32, 11, 11, 22],
+ ],
+ MultiIndex.from_tuples(
+ [
+ ("bar", "one"),
+ ("bar", "two"),
+ ("bar", "All"),
+ ("foo", "one"),
+ ("foo", "two"),
+ ("foo", "All"),
+ ],
+ names=["A", "B"],
+ ),
+ ),
+ ],
+ )
+ def test_margin_with_only_columns_defined(
+ self, columns, aggfunc, values, expected_columns, using_infer_string
+ ):
+ # GH 31016
+ df = DataFrame(
+ {
+ "A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
+ "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"],
+ "C": [
+ "small",
+ "large",
+ "large",
+ "small",
+ "small",
+ "large",
+ "small",
+ "small",
+ "large",
+ ],
+ "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
+ "E": [2, 4, 5, 5, 6, 6, 8, 9, 9],
+ }
+ )
+ if aggfunc != "sum":
+ msg = re.escape("agg function failed [how->mean,dtype->")
+ if using_infer_string:
+ msg = "dtype 'str' does not support operation 'mean'"
+ with pytest.raises(TypeError, match=msg):
+ df.pivot_table(columns=columns, margins=True, aggfunc=aggfunc)
+ if "B" not in columns:
+ df = df.drop(columns="B")
+ result = df.drop(columns="C").pivot_table(
+ columns=columns, margins=True, aggfunc=aggfunc
+ )
+ expected = DataFrame(values, index=Index(["D", "E"]), columns=expected_columns)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_margins_dtype(self, data):
+ # GH 17013
+
+ df = data.copy()
+ df[["D", "E", "F"]] = np.arange(len(df) * 3).reshape(len(df), 3).astype("i8")
+
+ mi_val = [*list(product(["bar", "foo"], ["one", "two"])), ("All", "")]
+ mi = MultiIndex.from_tuples(mi_val, names=("A", "B"))
+ expected = DataFrame(
+ {"dull": [12, 21, 3, 9, 45], "shiny": [33, 0, 36, 51, 120]}, index=mi
+ ).rename_axis("C", axis=1)
+ expected["All"] = expected["dull"] + expected["shiny"]
+
+ result = df.pivot_table(
+ values="D",
+ index=["A", "B"],
+ columns="C",
+ margins=True,
+ aggfunc="sum",
+ fill_value=0,
+ )
+
+ tm.assert_frame_equal(expected, result)
+
+ def test_margins_dtype_len(self, data):
+ mi_val = [*list(product(["bar", "foo"], ["one", "two"])), ("All", "")]
+ mi = MultiIndex.from_tuples(mi_val, names=("A", "B"))
+ expected = DataFrame(
+ {"dull": [1, 1, 2, 1, 5], "shiny": [2, 0, 2, 2, 6]}, index=mi
+ ).rename_axis("C", axis=1)
+ expected["All"] = expected["dull"] + expected["shiny"]
+
+ result = data.pivot_table(
+ values="D",
+ index=["A", "B"],
+ columns="C",
+ margins=True,
+ aggfunc=len,
+ fill_value=0,
+ )
+
+ tm.assert_frame_equal(expected, result)
+
+ @pytest.mark.parametrize("cols", [(1, 2), ("a", "b"), (1, "b"), ("a", 1)])
+ def test_pivot_table_multiindex_only(self, cols):
+ # GH 17038
+ df2 = DataFrame({cols[0]: [1, 2, 3], cols[1]: [1, 2, 3], "v": [4, 5, 6]})
+
+ result = df2.pivot_table(values="v", columns=cols)
+ expected = DataFrame(
+ [[4.0, 5.0, 6.0]],
+ columns=MultiIndex.from_tuples([(1, 1), (2, 2), (3, 3)], names=cols),
+ index=Index(["v"], dtype="str" if cols == ("a", "b") else "object"),
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_retains_tz(self):
+ dti = date_range("2016-01-01", periods=3, tz="Europe/Amsterdam")
+ df = DataFrame(
+ {
+ "A": np.random.default_rng(2).standard_normal(3),
+ "B": np.random.default_rng(2).standard_normal(3),
+ "C": dti,
+ }
+ )
+ result = df.pivot_table(index=["B", "C"], dropna=False)
+
+ # check tz retention
+ assert result.index.levels[1].equals(dti)
+
+ def test_pivot_integer_columns(self):
+ # caused by upstream bug in unstack
+
+ d = date.min
+ data = list(
+ product(
+ ["foo", "bar"],
+ ["A", "B", "C"],
+ ["x1", "x2"],
+ [d + timedelta(i) for i in range(20)],
+ [1.0],
+ )
+ )
+ df = DataFrame(data)
+ table = df.pivot_table(values=4, index=[0, 1, 3], columns=[2])
+
+ df2 = df.rename(columns=str)
+ table2 = df2.pivot_table(values="4", index=["0", "1", "3"], columns=["2"])
+
+ tm.assert_frame_equal(table, table2, check_names=False)
+
+ def test_pivot_no_level_overlap(self):
+ # GH #1181
+
+ data = DataFrame(
+ {
+ "a": ["a", "a", "a", "a", "b", "b", "b", "b"] * 2,
+ "b": [0, 0, 0, 0, 1, 1, 1, 1] * 2,
+ "c": (["foo"] * 4 + ["bar"] * 4) * 2,
+ "value": np.random.default_rng(2).standard_normal(16),
+ }
+ )
+
+ table = data.pivot_table("value", index="a", columns=["b", "c"])
+
+ grouped = data.groupby(["a", "b", "c"])["value"].mean()
+ expected = grouped.unstack("b").unstack("c").dropna(axis=1, how="all")
+ tm.assert_frame_equal(table, expected)
+
+ def test_pivot_columns_lexsorted(self):
+ n = 10000
+
+ dtype = np.dtype(
+ [
+ ("Index", object),
+ ("Symbol", object),
+ ("Year", int),
+ ("Month", int),
+ ("Day", int),
+ ("Quantity", int),
+ ("Price", float),
+ ]
+ )
+
+ products = np.array(
+ [
+ ("SP500", "ADBE"),
+ ("SP500", "NVDA"),
+ ("SP500", "ORCL"),
+ ("NDQ100", "AAPL"),
+ ("NDQ100", "MSFT"),
+ ("NDQ100", "GOOG"),
+ ("FTSE", "DGE.L"),
+ ("FTSE", "TSCO.L"),
+ ("FTSE", "GSK.L"),
+ ],
+ dtype=[("Index", object), ("Symbol", object)],
+ )
+ items = np.empty(n, dtype=dtype)
+ iproduct = np.random.default_rng(2).integers(0, len(products), n)
+ items["Index"] = products["Index"][iproduct]
+ items["Symbol"] = products["Symbol"][iproduct]
+ dr = date_range(date(2000, 1, 1), date(2010, 12, 31))
+ dates = dr[np.random.default_rng(2).integers(0, len(dr), n)]
+ items["Year"] = dates.year
+ items["Month"] = dates.month
+ items["Day"] = dates.day
+ items["Price"] = np.random.default_rng(2).lognormal(4.0, 2.0, n)
+
+ df = DataFrame(items)
+
+ pivoted = df.pivot_table(
+ "Price",
+ index=["Month", "Day"],
+ columns=["Index", "Symbol", "Year"],
+ aggfunc="mean",
+ )
+
+ assert pivoted.columns.is_monotonic_increasing
+
+ def test_pivot_complex_aggfunc(self, data):
+ f = {"D": ["std"], "E": ["sum"]}
+ expected = data.groupby(["A", "B"]).agg(f).unstack("B")
+ result = data.pivot_table(index="A", columns="B", aggfunc=f)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_margins_no_values_no_cols(self, data):
+ # Regression test on pivot table: no values or cols passed.
+ result = data[["A", "B"]].pivot_table(
+ index=["A", "B"], aggfunc=len, margins=True
+ )
+ result_list = result.tolist()
+ assert sum(result_list[:-1]) == result_list[-1]
+
+ def test_margins_no_values_two_rows(self, data):
+ # Regression test on pivot table: no values passed but rows are a
+ # multi-index
+ result = data[["A", "B", "C"]].pivot_table(
+ index=["A", "B"], columns="C", aggfunc=len, margins=True
+ )
+ assert result.All.tolist() == [3.0, 1.0, 4.0, 3.0, 11.0]
+
+ def test_margins_no_values_one_row_one_col(self, data):
+ # Regression test on pivot table: no values passed but row and col
+ # defined
+ result = data[["A", "B"]].pivot_table(
+ index="A", columns="B", aggfunc=len, margins=True
+ )
+ assert result.All.tolist() == [4.0, 7.0, 11.0]
+
+ def test_margins_no_values_two_row_two_cols(self, data):
+ # Regression test on pivot table: no values passed but rows and cols
+ # are multi-indexed
+ data["D"] = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]
+ result = data[["A", "B", "C", "D"]].pivot_table(
+ index=["A", "B"], columns=["C", "D"], aggfunc=len, margins=True
+ )
+ assert result.All.tolist() == [3.0, 1.0, 4.0, 3.0, 11.0]
+
+ @pytest.mark.parametrize("margin_name", ["foo", "one", 666, None, ["a", "b"]])
+ def test_pivot_table_with_margins_set_margin_name(self, margin_name, data):
+ # see gh-3335
+ msg = (
+ f'Conflicting name "{margin_name}" in margins|'
+ "margins_name argument must be a string"
+ )
+ with pytest.raises(ValueError, match=msg):
+ # multi-index index
+ pivot_table(
+ data,
+ values="D",
+ index=["A", "B"],
+ columns=["C"],
+ margins=True,
+ margins_name=margin_name,
+ )
+ with pytest.raises(ValueError, match=msg):
+ # multi-index column
+ pivot_table(
+ data,
+ values="D",
+ index=["C"],
+ columns=["A", "B"],
+ margins=True,
+ margins_name=margin_name,
+ )
+ with pytest.raises(ValueError, match=msg):
+ # non-multi-index index/column
+ pivot_table(
+ data,
+ values="D",
+ index=["A"],
+ columns=["B"],
+ margins=True,
+ margins_name=margin_name,
+ )
+
+ def test_pivot_timegrouper(self):
+ df = DataFrame(
+ {
+ "Branch": "A A A A A A A B".split(),
+ "Buyer": "Carl Mark Carl Carl Joe Joe Joe Carl".split(),
+ "Quantity": [1, 3, 5, 1, 8, 1, 9, 3],
+ "Date": [
+ datetime(2013, 1, 1),
+ datetime(2013, 1, 1),
+ datetime(2013, 10, 1),
+ datetime(2013, 10, 2),
+ datetime(2013, 10, 1),
+ datetime(2013, 10, 2),
+ datetime(2013, 12, 2),
+ datetime(2013, 12, 2),
+ ],
+ }
+ ).set_index("Date")
+
+ expected = DataFrame(
+ np.array([10, 18, 3], dtype="int64").reshape(1, 3),
+ index=pd.DatetimeIndex([datetime(2013, 12, 31)], freq="YE"),
+ columns="Carl Joe Mark".split(),
+ )
+ expected.index.name = "Date"
+ expected.columns.name = "Buyer"
+
+ result = pivot_table(
+ df,
+ index=Grouper(freq="YE"),
+ columns="Buyer",
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(
+ df,
+ index="Buyer",
+ columns=Grouper(freq="YE"),
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected.T)
+
+ expected = DataFrame(
+ np.array([1, np.nan, 3, 9, 18, np.nan]).reshape(2, 3),
+ index=pd.DatetimeIndex(
+ [datetime(2013, 1, 1), datetime(2013, 7, 1)], freq="6MS"
+ ),
+ columns="Carl Joe Mark".split(),
+ )
+ expected.index.name = "Date"
+ expected.columns.name = "Buyer"
+
+ result = pivot_table(
+ df,
+ index=Grouper(freq="6MS"),
+ columns="Buyer",
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(
+ df,
+ index="Buyer",
+ columns=Grouper(freq="6MS"),
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected.T)
+
+ # passing the name
+ df = df.reset_index()
+ result = pivot_table(
+ df,
+ index=Grouper(freq="6MS", key="Date"),
+ columns="Buyer",
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(
+ df,
+ index="Buyer",
+ columns=Grouper(freq="6MS", key="Date"),
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected.T)
+
+ msg = "'The grouper name foo is not found'"
+ with pytest.raises(KeyError, match=msg):
+ pivot_table(
+ df,
+ index=Grouper(freq="6MS", key="foo"),
+ columns="Buyer",
+ values="Quantity",
+ aggfunc="sum",
+ )
+ with pytest.raises(KeyError, match=msg):
+ pivot_table(
+ df,
+ index="Buyer",
+ columns=Grouper(freq="6MS", key="foo"),
+ values="Quantity",
+ aggfunc="sum",
+ )
+
+ # passing the level
+ df = df.set_index("Date")
+ result = pivot_table(
+ df,
+ index=Grouper(freq="6MS", level="Date"),
+ columns="Buyer",
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(
+ df,
+ index="Buyer",
+ columns=Grouper(freq="6MS", level="Date"),
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected.T)
+
+ msg = "The level foo is not valid"
+ with pytest.raises(ValueError, match=msg):
+ pivot_table(
+ df,
+ index=Grouper(freq="6MS", level="foo"),
+ columns="Buyer",
+ values="Quantity",
+ aggfunc="sum",
+ )
+ with pytest.raises(ValueError, match=msg):
+ pivot_table(
+ df,
+ index="Buyer",
+ columns=Grouper(freq="6MS", level="foo"),
+ values="Quantity",
+ aggfunc="sum",
+ )
+
+ def test_pivot_timegrouper_double(self):
+ # double grouper
+ df = DataFrame(
+ {
+ "Branch": "A A A A A A A B".split(),
+ "Buyer": "Carl Mark Carl Carl Joe Joe Joe Carl".split(),
+ "Quantity": [1, 3, 5, 1, 8, 1, 9, 3],
+ "Date": [
+ datetime(2013, 11, 1, 13, 0),
+ datetime(2013, 9, 1, 13, 5),
+ datetime(2013, 10, 1, 20, 0),
+ datetime(2013, 10, 2, 10, 0),
+ datetime(2013, 11, 1, 20, 0),
+ datetime(2013, 10, 2, 10, 0),
+ datetime(2013, 10, 2, 12, 0),
+ datetime(2013, 12, 5, 14, 0),
+ ],
+ "PayDay": [
+ datetime(2013, 10, 4, 0, 0),
+ datetime(2013, 10, 15, 13, 5),
+ datetime(2013, 9, 5, 20, 0),
+ datetime(2013, 11, 2, 10, 0),
+ datetime(2013, 10, 7, 20, 0),
+ datetime(2013, 9, 5, 10, 0),
+ datetime(2013, 12, 30, 12, 0),
+ datetime(2013, 11, 20, 14, 0),
+ ],
+ }
+ )
+
+ result = pivot_table(
+ df,
+ index=Grouper(freq="ME", key="Date"),
+ columns=Grouper(freq="ME", key="PayDay"),
+ values="Quantity",
+ aggfunc="sum",
+ )
+ expected = DataFrame(
+ np.array(
+ [
+ np.nan,
+ 3,
+ np.nan,
+ np.nan,
+ 6,
+ np.nan,
+ 1,
+ 9,
+ np.nan,
+ 9,
+ np.nan,
+ np.nan,
+ np.nan,
+ np.nan,
+ 3,
+ np.nan,
+ ]
+ ).reshape(4, 4),
+ index=pd.DatetimeIndex(
+ [
+ datetime(2013, 9, 30),
+ datetime(2013, 10, 31),
+ datetime(2013, 11, 30),
+ datetime(2013, 12, 31),
+ ],
+ freq="ME",
+ ),
+ columns=pd.DatetimeIndex(
+ [
+ datetime(2013, 9, 30),
+ datetime(2013, 10, 31),
+ datetime(2013, 11, 30),
+ datetime(2013, 12, 31),
+ ],
+ freq="ME",
+ ),
+ )
+ expected.index.name = "Date"
+ expected.columns.name = "PayDay"
+
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(
+ df,
+ index=Grouper(freq="ME", key="PayDay"),
+ columns=Grouper(freq="ME", key="Date"),
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected.T)
+
+ tuples = [
+ (datetime(2013, 9, 30), datetime(2013, 10, 31)),
+ (datetime(2013, 10, 31), datetime(2013, 9, 30)),
+ (datetime(2013, 10, 31), datetime(2013, 11, 30)),
+ (datetime(2013, 10, 31), datetime(2013, 12, 31)),
+ (datetime(2013, 11, 30), datetime(2013, 10, 31)),
+ (datetime(2013, 12, 31), datetime(2013, 11, 30)),
+ ]
+ idx = MultiIndex.from_tuples(tuples, names=["Date", "PayDay"])
+ expected = DataFrame(
+ np.array(
+ [3, np.nan, 6, np.nan, 1, np.nan, 9, np.nan, 9, np.nan, np.nan, 3]
+ ).reshape(6, 2),
+ index=idx,
+ columns=["A", "B"],
+ )
+ expected.columns.name = "Branch"
+
+ result = pivot_table(
+ df,
+ index=[Grouper(freq="ME", key="Date"), Grouper(freq="ME", key="PayDay")],
+ columns=["Branch"],
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(
+ df,
+ index=["Branch"],
+ columns=[Grouper(freq="ME", key="Date"), Grouper(freq="ME", key="PayDay")],
+ values="Quantity",
+ aggfunc="sum",
+ )
+ tm.assert_frame_equal(result, expected.T)
+
+ def test_pivot_datetime_tz(self):
+ dates1 = pd.DatetimeIndex(
+ [
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ ],
+ dtype="M8[ns, US/Pacific]",
+ name="dt1",
+ )
+ dates2 = pd.DatetimeIndex(
+ [
+ "2013-01-01 15:00:00",
+ "2013-01-01 15:00:00",
+ "2013-01-01 15:00:00",
+ "2013-02-01 15:00:00",
+ "2013-02-01 15:00:00",
+ "2013-02-01 15:00:00",
+ ],
+ dtype="M8[ns, Asia/Tokyo]",
+ )
+ df = DataFrame(
+ {
+ "label": ["a", "a", "a", "b", "b", "b"],
+ "dt1": dates1,
+ "dt2": dates2,
+ "value1": np.arange(6, dtype="int64"),
+ "value2": [1, 2] * 3,
+ }
+ )
+
+ exp_idx = dates1[:3]
+ exp_col1 = Index(["value1", "value1"])
+ exp_col2 = Index(["a", "b"], name="label")
+ exp_col = MultiIndex.from_arrays([exp_col1, exp_col2])
+ expected = DataFrame(
+ [[0.0, 3.0], [1.0, 4.0], [2.0, 5.0]], index=exp_idx, columns=exp_col
+ )
+ result = pivot_table(df, index=["dt1"], columns=["label"], values=["value1"])
+ tm.assert_frame_equal(result, expected)
+
+ exp_col1 = Index(["sum", "sum", "sum", "sum", "mean", "mean", "mean", "mean"])
+ exp_col2 = Index(["value1", "value1", "value2", "value2"] * 2)
+ exp_col3 = pd.DatetimeIndex(
+ ["2013-01-01 15:00:00", "2013-02-01 15:00:00"] * 4,
+ dtype="M8[ns, Asia/Tokyo]",
+ name="dt2",
+ )
+ exp_col = MultiIndex.from_arrays([exp_col1, exp_col2, exp_col3])
+ expected1 = DataFrame(
+ np.array(
+ [
+ [
+ 0,
+ 3,
+ 1,
+ 2,
+ ],
+ [1, 4, 2, 1],
+ [2, 5, 1, 2],
+ ],
+ dtype="int64",
+ ),
+ index=exp_idx,
+ columns=exp_col[:4],
+ )
+ expected2 = DataFrame(
+ np.array(
+ [
+ [0.0, 3.0, 1.0, 2.0],
+ [1.0, 4.0, 2.0, 1.0],
+ [2.0, 5.0, 1.0, 2.0],
+ ],
+ ),
+ index=exp_idx,
+ columns=exp_col[4:],
+ )
+ expected = concat([expected1, expected2], axis=1)
+
+ result = pivot_table(
+ df,
+ index=["dt1"],
+ columns=["dt2"],
+ values=["value1", "value2"],
+ aggfunc=["sum", "mean"],
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_dtaccessor(self):
+ # GH 8103
+ dates1 = pd.DatetimeIndex(
+ [
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ "2011-07-19 07:00:00",
+ "2011-07-19 08:00:00",
+ "2011-07-19 09:00:00",
+ ]
+ )
+ dates2 = pd.DatetimeIndex(
+ [
+ "2013-01-01 15:00:00",
+ "2013-01-01 15:00:00",
+ "2013-01-01 15:00:00",
+ "2013-02-01 15:00:00",
+ "2013-02-01 15:00:00",
+ "2013-02-01 15:00:00",
+ ]
+ )
+ df = DataFrame(
+ {
+ "label": ["a", "a", "a", "b", "b", "b"],
+ "dt1": dates1,
+ "dt2": dates2,
+ "value1": np.arange(6, dtype="int64"),
+ "value2": [1, 2] * 3,
+ }
+ )
+
+ result = pivot_table(
+ df, index="label", columns=df["dt1"].dt.hour, values="value1"
+ )
+
+ exp_idx = Index(["a", "b"], name="label")
+ expected = DataFrame(
+ {7: [0.0, 3.0], 8: [1.0, 4.0], 9: [2.0, 5.0]},
+ index=exp_idx,
+ columns=Index([7, 8, 9], dtype=np.int32, name="dt1"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(
+ df, index=df["dt2"].dt.month, columns=df["dt1"].dt.hour, values="value1"
+ )
+
+ expected = DataFrame(
+ {7: [0.0, 3.0], 8: [1.0, 4.0], 9: [2.0, 5.0]},
+ index=Index([1, 2], dtype=np.int32, name="dt2"),
+ columns=Index([7, 8, 9], dtype=np.int32, name="dt1"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(
+ df,
+ index=df["dt2"].dt.year.values,
+ columns=[df["dt1"].dt.hour, df["dt2"].dt.month],
+ values="value1",
+ )
+
+ exp_col = MultiIndex.from_arrays(
+ [
+ np.array([7, 7, 8, 8, 9, 9], dtype=np.int32),
+ np.array([1, 2] * 3, dtype=np.int32),
+ ],
+ names=["dt1", "dt2"],
+ )
+ expected = DataFrame(
+ np.array([[0.0, 3.0, 1.0, 4.0, 2.0, 5.0]]),
+ index=Index([2013], dtype=np.int32),
+ columns=exp_col,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(
+ df,
+ index=np.array(["X", "X", "X", "X", "Y", "Y"]),
+ columns=[df["dt1"].dt.hour, df["dt2"].dt.month],
+ values="value1",
+ )
+ expected = DataFrame(
+ np.array(
+ [[0, 3, 1, np.nan, 2, np.nan], [np.nan, np.nan, np.nan, 4, np.nan, 5]]
+ ),
+ index=["X", "Y"],
+ columns=exp_col,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_daily(self):
+ rng = date_range("1/1/2000", "12/31/2004", freq="D")
+ ts = Series(np.arange(len(rng)), index=rng)
+
+ result = pivot_table(
+ DataFrame(ts), index=ts.index.year, columns=ts.index.dayofyear
+ )
+ result.columns = result.columns.droplevel(0)
+
+ doy = np.asarray(ts.index.dayofyear)
+
+ expected = {}
+ for y in ts.index.year.unique().values:
+ mask = ts.index.year == y
+ expected[y] = Series(ts.values[mask], index=doy[mask])
+ expected = DataFrame(expected, dtype=float).T
+ expected.index = expected.index.astype(np.int32)
+ tm.assert_frame_equal(result, expected)
+
+ def test_monthly(self):
+ rng = date_range("1/1/2000", "12/31/2004", freq="ME")
+ ts = Series(np.arange(len(rng)), index=rng)
+
+ result = pivot_table(DataFrame(ts), index=ts.index.year, columns=ts.index.month)
+ result.columns = result.columns.droplevel(0)
+
+ month = np.asarray(ts.index.month)
+ expected = {}
+ for y in ts.index.year.unique().values:
+ mask = ts.index.year == y
+ expected[y] = Series(ts.values[mask], index=month[mask])
+ expected = DataFrame(expected, dtype=float).T
+ expected.index = expected.index.astype(np.int32)
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_with_iterator_values(self, data):
+ # GH 12017
+ aggs = {"D": "sum", "E": "mean"}
+
+ pivot_values_list = pivot_table(
+ data, index=["A"], values=list(aggs.keys()), aggfunc=aggs
+ )
+
+ pivot_values_keys = pivot_table(
+ data, index=["A"], values=aggs.keys(), aggfunc=aggs
+ )
+ tm.assert_frame_equal(pivot_values_keys, pivot_values_list)
+
+ agg_values_gen = (value for value in aggs)
+ pivot_values_gen = pivot_table(
+ data, index=["A"], values=agg_values_gen, aggfunc=aggs
+ )
+ tm.assert_frame_equal(pivot_values_gen, pivot_values_list)
+
+ def test_pivot_table_margins_name_with_aggfunc_list(self):
+ # GH 13354
+ margins_name = "Weekly"
+ costs = DataFrame(
+ {
+ "item": ["bacon", "cheese", "bacon", "cheese"],
+ "cost": [2.5, 4.5, 3.2, 3.3],
+ "day": ["ME", "ME", "T", "T"],
+ }
+ )
+ table = costs.pivot_table(
+ index="item",
+ columns="day",
+ margins=True,
+ margins_name=margins_name,
+ aggfunc=["mean", "max"],
+ )
+ ix = Index(["bacon", "cheese", margins_name], name="item")
+ tups = [
+ ("mean", "cost", "ME"),
+ ("mean", "cost", "T"),
+ ("mean", "cost", margins_name),
+ ("max", "cost", "ME"),
+ ("max", "cost", "T"),
+ ("max", "cost", margins_name),
+ ]
+ cols = MultiIndex.from_tuples(tups, names=[None, None, "day"])
+ expected = DataFrame(table.values, index=ix, columns=cols)
+ tm.assert_frame_equal(table, expected)
+
+ def test_categorical_margins(self, observed):
+ # GH 10989
+ df = DataFrame(
+ {"x": np.arange(8), "y": np.arange(8) // 4, "z": np.arange(8) % 2}
+ )
+
+ expected = DataFrame([[1.0, 2.0, 1.5], [5, 6, 5.5], [3, 4, 3.5]])
+ expected.index = Index([0, 1, "All"], name="y")
+ expected.columns = Index([0, 1, "All"], name="z")
+
+ table = df.pivot_table("x", "y", "z", dropna=observed, margins=True)
+ tm.assert_frame_equal(table, expected)
+
+ def test_categorical_margins_category(self, observed):
+ df = DataFrame(
+ {"x": np.arange(8), "y": np.arange(8) // 4, "z": np.arange(8) % 2}
+ )
+
+ expected = DataFrame([[1.0, 2.0, 1.5], [5, 6, 5.5], [3, 4, 3.5]])
+ expected.index = Index([0, 1, "All"], name="y")
+ expected.columns = Index([0, 1, "All"], name="z")
+
+ df.y = df.y.astype("category")
+ df.z = df.z.astype("category")
+ table = df.pivot_table(
+ "x", "y", "z", dropna=observed, margins=True, observed=False
+ )
+ tm.assert_frame_equal(table, expected)
+
+ def test_margins_casted_to_float(self):
+ # GH 24893
+ df = DataFrame(
+ {
+ "A": [2, 4, 6, 8],
+ "B": [1, 4, 5, 8],
+ "C": [1, 3, 4, 6],
+ "D": ["X", "X", "Y", "Y"],
+ }
+ )
+
+ result = pivot_table(df, index="D", margins=True)
+ expected = DataFrame(
+ {"A": [3.0, 7.0, 5], "B": [2.5, 6.5, 4.5], "C": [2.0, 5.0, 3.5]},
+ index=Index(["X", "Y", "All"], name="D"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_with_categorical(self, observed, ordered):
+ # gh-21370
+ idx = [np.nan, "low", "high", "low", np.nan]
+ col = [np.nan, "A", "B", np.nan, "A"]
+ df = DataFrame(
+ {
+ "In": Categorical(idx, categories=["low", "high"], ordered=ordered),
+ "Col": Categorical(col, categories=["A", "B"], ordered=ordered),
+ "Val": range(1, 6),
+ }
+ )
+ # case with index/columns/value
+ result = df.pivot_table(
+ index="In", columns="Col", values="Val", observed=observed
+ )
+
+ expected_cols = pd.CategoricalIndex(["A", "B"], ordered=ordered, name="Col")
+
+ expected = DataFrame(data=[[2.0, np.nan], [np.nan, 3.0]], columns=expected_cols)
+ expected.index = Index(
+ Categorical(["low", "high"], categories=["low", "high"], ordered=ordered),
+ name="In",
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ # case with columns/value
+ result = df.pivot_table(columns="Col", values="Val", observed=observed)
+
+ expected = DataFrame(
+ data=[[3.5, 3.0]], columns=expected_cols, index=Index(["Val"])
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_categorical_aggfunc(self, observed):
+ # GH 9534
+ df = DataFrame(
+ {"C1": ["A", "B", "C", "C"], "C2": ["a", "a", "b", "b"], "V": [1, 2, 3, 4]}
+ )
+ df["C1"] = df["C1"].astype("category")
+ result = df.pivot_table(
+ "V",
+ index="C1",
+ columns="C2",
+ dropna=observed,
+ aggfunc="count",
+ observed=False,
+ )
+
+ expected_index = pd.CategoricalIndex(
+ ["A", "B", "C"], categories=["A", "B", "C"], ordered=False, name="C1"
+ )
+ expected_columns = Index(["a", "b"], name="C2")
+ expected_data = np.array([[1, 0], [1, 0], [0, 2]], dtype=np.int64)
+ expected = DataFrame(
+ expected_data, index=expected_index, columns=expected_columns
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_categorical_pivot_index_ordering(self, observed):
+ # GH 8731
+ df = DataFrame(
+ {
+ "Sales": [100, 120, 220],
+ "Month": ["January", "January", "January"],
+ "Year": [2013, 2014, 2013],
+ }
+ )
+ months = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ ]
+ df["Month"] = df["Month"].astype("category").cat.set_categories(months)
+ result = df.pivot_table(
+ values="Sales",
+ index="Month",
+ columns="Year",
+ observed=observed,
+ aggfunc="sum",
+ )
+ expected_columns = Index([2013, 2014], name="Year", dtype="int64")
+ expected_index = pd.CategoricalIndex(
+ months, categories=months, ordered=False, name="Month"
+ )
+ expected_data = [[320, 120]] + [[0, 0]] * 11
+ expected = DataFrame(
+ expected_data, index=expected_index, columns=expected_columns
+ )
+ if observed:
+ expected = expected.loc[["January"]]
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_not_series(self):
+ # GH 4386
+ # pivot_table always returns a DataFrame
+ # when values is not list like and columns is None
+ # and aggfunc is not instance of list
+ df = DataFrame({"col1": [3, 4, 5], "col2": ["C", "D", "E"], "col3": [1, 3, 9]})
+
+ result = df.pivot_table("col1", index=["col3", "col2"], aggfunc="sum")
+ m = MultiIndex.from_arrays([[1, 3, 9], ["C", "D", "E"]], names=["col3", "col2"])
+ expected = DataFrame([3, 4, 5], index=m, columns=["col1"])
+
+ tm.assert_frame_equal(result, expected)
+
+ result = df.pivot_table("col1", index="col3", columns="col2", aggfunc="sum")
+ expected = DataFrame(
+ [[3, np.nan, np.nan], [np.nan, 4, np.nan], [np.nan, np.nan, 5]],
+ index=Index([1, 3, 9], name="col3"),
+ columns=Index(["C", "D", "E"], name="col2"),
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ result = df.pivot_table("col1", index="col3", aggfunc=["sum"])
+ m = MultiIndex.from_arrays([["sum"], ["col1"]])
+ expected = DataFrame([3, 4, 5], index=Index([1, 3, 9], name="col3"), columns=m)
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_margins_name_unicode(self):
+ # issue #13292
+ greek = "\u0394\u03bf\u03ba\u03b9\u03bc\u03ae"
+ frame = DataFrame({"foo": [1, 2, 3]}, columns=Index(["foo"], dtype=object))
+ table = pivot_table(
+ frame, index=["foo"], aggfunc=len, margins=True, margins_name=greek
+ )
+ index = Index([1, 2, 3, greek], dtype="object", name="foo")
+ expected = DataFrame(index=index, columns=[])
+ tm.assert_frame_equal(table, expected)
+
+ def test_pivot_string_as_func(self):
+ # GH #18713
+ # for correctness purposes
+ data = DataFrame(
+ {
+ "A": [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ "foo",
+ "foo",
+ "foo",
+ ],
+ "B": [
+ "one",
+ "one",
+ "one",
+ "two",
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "two",
+ "one",
+ ],
+ "C": range(11),
+ }
+ )
+
+ result = pivot_table(data, index="A", columns="B", aggfunc="sum")
+ mi = MultiIndex(
+ levels=[["C"], ["one", "two"]], codes=[[0, 0], [0, 1]], names=[None, "B"]
+ )
+ expected = DataFrame(
+ {("C", "one"): {"bar": 15, "foo": 13}, ("C", "two"): {"bar": 7, "foo": 20}},
+ columns=mi,
+ ).rename_axis("A")
+ tm.assert_frame_equal(result, expected)
+
+ result = pivot_table(data, index="A", columns="B", aggfunc=["sum", "mean"])
+ mi = MultiIndex(
+ levels=[["sum", "mean"], ["C"], ["one", "two"]],
+ codes=[[0, 0, 1, 1], [0, 0, 0, 0], [0, 1, 0, 1]],
+ names=[None, None, "B"],
+ )
+ expected = DataFrame(
+ {
+ ("mean", "C", "one"): {"bar": 5.0, "foo": 3.25},
+ ("mean", "C", "two"): {"bar": 7.0, "foo": 6.666666666666667},
+ ("sum", "C", "one"): {"bar": 15, "foo": 13},
+ ("sum", "C", "two"): {"bar": 7, "foo": 20},
+ },
+ columns=mi,
+ ).rename_axis("A")
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("kwargs", [{"a": 2}, {"a": 2, "b": 3}, {"b": 3, "a": 2}])
+ def test_pivot_table_kwargs(self, kwargs):
+ # GH#57884
+ def f(x, a, b=3):
+ return x.sum() * a + b
+
+ def g(x):
+ return f(x, **kwargs)
+
+ df = DataFrame(
+ {
+ "A": ["good", "bad", "good", "bad", "good"],
+ "B": ["one", "two", "one", "three", "two"],
+ "X": [2, 5, 4, 20, 10],
+ }
+ )
+ result = pivot_table(
+ df, index="A", columns="B", values="X", aggfunc=f, **kwargs
+ )
+ expected = pivot_table(df, index="A", columns="B", values="X", aggfunc=g)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "kwargs", [{}, {"b": 10}, {"a": 3}, {"a": 3, "b": 10}, {"b": 10, "a": 3}]
+ )
+ def test_pivot_table_kwargs_margin(self, data, kwargs):
+ # GH#57884
+ def f(x, a=5, b=7):
+ return (x.sum() + b) * a
+
+ def g(x):
+ return f(x, **kwargs)
+
+ result = data.pivot_table(
+ values="D",
+ index=["A", "B"],
+ columns="C",
+ aggfunc=f,
+ margins=True,
+ fill_value=0,
+ **kwargs,
+ )
+
+ expected = data.pivot_table(
+ values="D",
+ index=["A", "B"],
+ columns="C",
+ aggfunc=g,
+ margins=True,
+ fill_value=0,
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "f, f_numpy",
+ [
+ ("sum", np.sum),
+ ("mean", np.mean),
+ ("min", np.min),
+ (["sum", "mean"], [np.sum, np.mean]),
+ (["sum", "min"], [np.sum, np.min]),
+ (["max", "mean"], [np.max, np.mean]),
+ ],
+ )
+ def test_pivot_string_func_vs_func(self, f, f_numpy, data):
+ # GH #18713
+ # for consistency purposes
+ data = data.drop(columns="C")
+ result = pivot_table(data, index="A", columns="B", aggfunc=f)
+ expected = pivot_table(data, index="A", columns="B", aggfunc=f_numpy)
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.slow
+ def test_pivot_number_of_levels_larger_than_int32_warns(
+ self, performance_warning, monkeypatch
+ ):
+ # GH 20601
+ # GH 26314: Change ValueError to PerformanceWarning
+ class MockUnstacker(reshape_lib._Unstacker):
+ def __init__(self, *args, **kwargs) -> None:
+ # __init__ will raise the warning
+ super().__init__(*args, **kwargs)
+ raise Exception("Don't compute final result.")
+
+ def _make_selectors(self) -> None:
+ pass
+
+ with monkeypatch.context() as m:
+ m.setattr(reshape_lib, "_Unstacker", MockUnstacker)
+ df = DataFrame(
+ {"ind1": np.arange(2**16), "ind2": np.arange(2**16), "count": 0}
+ )
+
+ msg = "The following operation may generate"
+ with tm.assert_produces_warning(performance_warning, match=msg):
+ with pytest.raises(Exception, match="Don't compute final result."):
+ df.pivot_table(
+ index="ind1", columns="ind2", values="count", aggfunc="count"
+ )
+
+ def test_pivot_table_aggfunc_dropna(self, dropna):
+ # GH 22159
+ df = DataFrame(
+ {
+ "fruit": ["apple", "peach", "apple"],
+ "size": [1, 1, 2],
+ "taste": [7, 6, 6],
+ }
+ )
+
+ def ret_one(x):
+ return 1
+
+ def ret_sum(x):
+ return sum(x)
+
+ def ret_none(x):
+ return np.nan
+
+ result = pivot_table(
+ df, columns="fruit", aggfunc=[ret_sum, ret_none, ret_one], dropna=dropna
+ )
+
+ data = [[3, 1, np.nan, np.nan, 1, 1], [13, 6, np.nan, np.nan, 1, 1]]
+ col = MultiIndex.from_product(
+ [["ret_sum", "ret_none", "ret_one"], ["apple", "peach"]],
+ names=[None, "fruit"],
+ )
+ expected = DataFrame(data, index=["size", "taste"], columns=col)
+
+ if dropna:
+ expected = expected.dropna(axis="columns")
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_aggfunc_scalar_dropna(self, dropna):
+ # GH 22159
+ df = DataFrame(
+ {"A": ["one", "two", "one"], "x": [3, np.nan, 2], "y": [1, np.nan, np.nan]}
+ )
+
+ result = pivot_table(df, columns="A", aggfunc="mean", dropna=dropna)
+
+ data = [[2.5, np.nan], [1, np.nan]]
+ col = Index(["one", "two"], name="A")
+ expected = DataFrame(data, index=["x", "y"], columns=col)
+
+ if dropna:
+ expected = expected.dropna(axis="columns")
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("margins", [True, False])
+ def test_pivot_table_empty_aggfunc(self, margins):
+ # GH 9186 & GH 13483 & GH 49240
+ df = DataFrame(
+ {
+ "A": [2, 2, 3, 3, 2],
+ "id": [5, 6, 7, 8, 9],
+ "C": ["p", "q", "q", "p", "q"],
+ "D": [None, None, None, None, None],
+ }
+ )
+ result = df.pivot_table(
+ index="A", columns="D", values="id", aggfunc=np.size, margins=margins
+ )
+ exp_cols = Index([], name="D")
+ expected = DataFrame(index=Index([], dtype="int64", name="A"), columns=exp_cols)
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_no_column_raises(self):
+ # GH 10326
+ def agg(arr):
+ return np.mean(arr)
+
+ df = DataFrame({"X": [0, 0, 1, 1], "Y": [0, 1, 0, 1], "Z": [10, 20, 30, 40]})
+ with pytest.raises(KeyError, match="notpresent"):
+ df.pivot_table("notpresent", "X", "Y", aggfunc=agg)
+
+ def test_pivot_table_multiindex_columns_doctest_case(self):
+ # The relevant characteristic is that the call
+ # to maybe_downcast_to_dtype(agged[v], data[v].dtype) in
+ # __internal_pivot_table has `agged[v]` a DataFrame instead of Series,
+ # In this case this is because agged.columns is a MultiIndex and 'v'
+ # is only indexing on its first level.
+ df = DataFrame(
+ {
+ "A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
+ "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"],
+ "C": [
+ "small",
+ "large",
+ "large",
+ "small",
+ "small",
+ "large",
+ "small",
+ "small",
+ "large",
+ ],
+ "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
+ "E": [2, 4, 5, 5, 6, 6, 8, 9, 9],
+ }
+ )
+
+ table = pivot_table(
+ df,
+ values=["D", "E"],
+ index=["A", "C"],
+ aggfunc={"D": "mean", "E": ["min", "max", "mean"]},
+ )
+ cols = MultiIndex.from_tuples(
+ [("D", "mean"), ("E", "max"), ("E", "mean"), ("E", "min")]
+ )
+ index = MultiIndex.from_tuples(
+ [("bar", "large"), ("bar", "small"), ("foo", "large"), ("foo", "small")],
+ names=["A", "C"],
+ )
+ vals = np.array(
+ [
+ [5.5, 9.0, 7.5, 6.0],
+ [5.5, 9.0, 8.5, 8.0],
+ [2.0, 5.0, 4.5, 4.0],
+ [2.33333333, 6.0, 4.33333333, 2.0],
+ ]
+ )
+ expected = DataFrame(vals, columns=cols, index=index)
+ expected[("E", "min")] = expected[("E", "min")].astype(np.int64)
+ expected[("E", "max")] = expected[("E", "max")].astype(np.int64)
+ tm.assert_frame_equal(table, expected)
+
+ def test_pivot_table_sort_false(self):
+ # GH#39143
+ df = DataFrame(
+ {
+ "a": ["d1", "d4", "d3"],
+ "col": ["a", "b", "c"],
+ "num": [23, 21, 34],
+ "year": ["2018", "2018", "2019"],
+ }
+ )
+ result = df.pivot_table(
+ index=["a", "col"], columns="year", values="num", aggfunc="sum", sort=False
+ )
+ expected = DataFrame(
+ [[23, np.nan], [21, np.nan], [np.nan, 34]],
+ columns=Index(["2018", "2019"], name="year"),
+ index=MultiIndex.from_arrays(
+ [["d1", "d4", "d3"], ["a", "b", "c"]], names=["a", "col"]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_nullable_margins(self):
+ # GH#48681
+ df = DataFrame(
+ {"a": "A", "b": [1, 2], "sales": Series([10, 11], dtype="Int64")}
+ )
+
+ result = df.pivot_table(index="b", columns="a", margins=True, aggfunc="sum")
+ expected = DataFrame(
+ [[10, 10], [11, 11], [21, 21]],
+ index=Index([1, 2, "All"], name="b"),
+ columns=MultiIndex.from_tuples(
+ [("sales", "A"), ("sales", "All")], names=[None, "a"]
+ ),
+ dtype="Int64",
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_sort_false_with_multiple_values(self):
+ df = DataFrame(
+ {
+ "firstname": ["John", "Michael"],
+ "lastname": ["Foo", "Bar"],
+ "height": [173, 182],
+ "age": [47, 33],
+ }
+ )
+ result = df.pivot_table(
+ index=["lastname", "firstname"], values=["height", "age"], sort=False
+ )
+ expected = DataFrame(
+ [[173.0, 47.0], [182.0, 33.0]],
+ columns=["height", "age"],
+ index=MultiIndex.from_tuples(
+ [("Foo", "John"), ("Bar", "Michael")],
+ names=["lastname", "firstname"],
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_with_margins_and_numeric_columns(self):
+ # GH 26568
+ df = DataFrame([["a", "x", 1], ["a", "y", 2], ["b", "y", 3], ["b", "z", 4]])
+ df.columns = [10, 20, 30]
+
+ result = df.pivot_table(
+ index=10, columns=20, values=30, aggfunc="sum", fill_value=0, margins=True
+ )
+
+ expected = DataFrame([[1, 2, 0, 3], [0, 3, 4, 7], [1, 5, 4, 10]])
+ expected.columns = ["x", "y", "z", "All"]
+ expected.index = ["a", "b", "All"]
+ expected.columns.name = 20
+ expected.index.name = 10
+
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype,expected_dtype", [("Int64", "Float64"), ("int64", "float64")]
+ )
+ def test_pivot_ea_dtype_dropna(self, dropna, dtype, expected_dtype):
+ # GH#47477
+ # GH#47971
+ df = DataFrame({"x": "a", "y": "b", "age": Series([20, 40], dtype=dtype)})
+ result = df.pivot_table(
+ index="x", columns="y", values="age", aggfunc="mean", dropna=dropna
+ )
+ expected = DataFrame(
+ [[30]],
+ index=Index(["a"], name="x"),
+ columns=Index(["b"], name="y"),
+ dtype=expected_dtype,
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_datetime_warning(self):
+ # GH#48683
+ df = DataFrame(
+ {
+ "a": "A",
+ "b": [1, 2],
+ "date": pd.Timestamp("2019-12-31"),
+ "sales": [10.0, 11],
+ }
+ )
+ with tm.assert_produces_warning(None):
+ result = df.pivot_table(
+ index=["b", "date"], columns="a", margins=True, aggfunc="sum"
+ )
+ expected = DataFrame(
+ [[10.0, 10.0], [11.0, 11.0], [21.0, 21.0]],
+ index=MultiIndex.from_arrays(
+ [
+ Index([1, 2, "All"], name="b"),
+ Index(
+ [pd.Timestamp("2019-12-31"), pd.Timestamp("2019-12-31"), ""],
+ dtype=object,
+ name="date",
+ ),
+ ]
+ ),
+ columns=MultiIndex.from_tuples(
+ [("sales", "A"), ("sales", "All")], names=[None, "a"]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_with_mixed_nested_tuples(self):
+ # GH 50342
+ df = DataFrame(
+ {
+ "A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
+ "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"],
+ "C": [
+ "small",
+ "large",
+ "large",
+ "small",
+ "small",
+ "large",
+ "small",
+ "small",
+ "large",
+ ],
+ "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
+ "E": [2, 4, 5, 5, 6, 6, 8, 9, 9],
+ ("col5",): [
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "foo",
+ "bar",
+ "bar",
+ "bar",
+ "bar",
+ ],
+ ("col6", 6): [
+ "one",
+ "one",
+ "one",
+ "two",
+ "two",
+ "one",
+ "one",
+ "two",
+ "two",
+ ],
+ (7, "seven"): [
+ "small",
+ "large",
+ "large",
+ "small",
+ "small",
+ "large",
+ "small",
+ "small",
+ "large",
+ ],
+ }
+ )
+ result = pivot_table(
+ df, values="D", index=["A", "B"], columns=[(7, "seven")], aggfunc="sum"
+ )
+ expected = DataFrame(
+ [[4.0, 5.0], [7.0, 6.0], [4.0, 1.0], [np.nan, 6.0]],
+ columns=Index(["large", "small"], name=(7, "seven")),
+ index=MultiIndex.from_arrays(
+ [["bar", "bar", "foo", "foo"], ["one", "two"] * 2], names=["A", "B"]
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_aggfunc_nunique_with_different_values(self):
+ test = DataFrame(
+ {
+ "a": range(10),
+ "b": range(10),
+ "c": range(10),
+ "d": range(10),
+ }
+ )
+
+ columnval = MultiIndex.from_arrays(
+ [
+ ["nunique" for i in range(10)],
+ ["c" for i in range(10)],
+ range(10),
+ ],
+ names=(None, None, "b"),
+ )
+ nparr = np.full((10, 10), np.nan)
+ np.fill_diagonal(nparr, 1.0)
+
+ expected = DataFrame(nparr, index=Index(range(10), name="a"), columns=columnval)
+ result = test.pivot_table(
+ index=[
+ "a",
+ ],
+ columns=[
+ "b",
+ ],
+ values=[
+ "c",
+ ],
+ aggfunc=["nunique"],
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_index_and_column_keys_with_nan(self, dropna):
+ # GH#61113
+ data = {"row": [None, *range(4)], "col": [*range(4), None], "val": range(5)}
+ df = DataFrame(data)
+ result = df.pivot_table(values="val", index="row", columns="col", dropna=dropna)
+ e_axis = [*range(4), None]
+ nan = np.nan
+ e_data = [
+ [nan, 1.0, nan, nan, nan],
+ [nan, nan, 2.0, nan, nan],
+ [nan, nan, nan, 3.0, nan],
+ [nan, nan, nan, nan, 4.0],
+ [0.0, nan, nan, nan, nan],
+ ]
+ expected = DataFrame(
+ data=e_data,
+ index=Index(data=e_axis, name="row"),
+ columns=Index(data=e_axis, name="col"),
+ )
+ if dropna:
+ expected = expected.loc[[0, 1, 2], [1, 2, 3]]
+
+ tm.assert_frame_equal(left=result, right=expected)
+
+ @pytest.mark.parametrize(
+ "index, columns, e_data, e_index, e_cols",
+ [
+ (
+ "Category",
+ "Value",
+ [
+ [1.0, np.nan, 1.0, np.nan],
+ [np.nan, 1.0, np.nan, 1.0],
+ ],
+ Index(data=["A", "B"], name="Category"),
+ Index(data=[10, 20, 40, 50], name="Value"),
+ ),
+ (
+ "Value",
+ "Category",
+ [
+ [1.0, np.nan],
+ [np.nan, 1.0],
+ [1.0, np.nan],
+ [np.nan, 1.0],
+ ],
+ Index(data=[10, 20, 40, 50], name="Value"),
+ Index(data=["A", "B"], name="Category"),
+ ),
+ ],
+ ids=["values-and-columns", "values-and-index"],
+ )
+ def test_pivot_table_values_as_two_params(
+ self, index, columns, e_data, e_index, e_cols
+ ):
+ # GH#57876
+ data = {"Category": ["A", "B", "A", "B"], "Value": [10, 20, 40, 50]}
+ df = DataFrame(data)
+ result = df.pivot_table(
+ index=index, columns=columns, values="Value", aggfunc="count"
+ )
+ expected = DataFrame(data=e_data, index=e_index, columns=e_cols)
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_table_margins_include_nan_groups(self):
+ # GH#61509
+ df = DataFrame(
+ {
+ "i": [1, 2, 3],
+ "g1": ["a", "b", "b"],
+ "g2": ["x", None, None],
+ }
+ )
+
+ result = df.pivot_table(
+ index="g1",
+ columns="g2",
+ values="i",
+ aggfunc="count",
+ dropna=False,
+ margins=True,
+ )
+
+ expected = DataFrame(
+ {
+ "x": {"a": 1.0, "b": np.nan, "All": 1.0},
+ np.nan: {"a": np.nan, "b": 2.0, "All": 2.0},
+ "All": {"a": 1.0, "b": 2.0, "All": 3.0},
+ }
+ )
+ expected.index.name = "g1"
+ expected.columns.name = "g2"
+ tm.assert_frame_equal(result, expected, check_dtype=False)
+
+
+class TestPivot:
+ def test_pivot(self):
+ data = {
+ "index": ["A", "B", "C", "C", "B", "A"],
+ "columns": ["One", "One", "One", "Two", "Two", "Two"],
+ "values": [1.0, 2.0, 3.0, 3.0, 2.0, 1.0],
+ }
+
+ frame = DataFrame(data)
+ pivoted = frame.pivot(index="index", columns="columns", values="values")
+
+ expected = DataFrame(
+ {
+ "One": {"A": 1.0, "B": 2.0, "C": 3.0},
+ "Two": {"A": 1.0, "B": 2.0, "C": 3.0},
+ }
+ )
+
+ expected.index.name, expected.columns.name = "index", "columns"
+ tm.assert_frame_equal(pivoted, expected)
+
+ # name tracking
+ assert pivoted.index.name == "index"
+ assert pivoted.columns.name == "columns"
+
+ # don't specify values
+ pivoted = frame.pivot(index="index", columns="columns")
+ assert pivoted.index.name == "index"
+ assert pivoted.columns.names == (None, "columns")
+
+ def test_pivot_duplicates(self):
+ data = DataFrame(
+ {
+ "a": ["bar", "bar", "foo", "foo", "foo"],
+ "b": ["one", "two", "one", "one", "two"],
+ "c": [1.0, 2.0, 3.0, 3.0, 4.0],
+ }
+ )
+ with pytest.raises(ValueError, match="duplicate entries"):
+ data.pivot(index="a", columns="b", values="c")
+
+ def test_pivot_empty(self):
+ df = DataFrame(columns=["a", "b", "c"])
+ result = df.pivot(index="a", columns="b", values="c")
+ expected = DataFrame(index=[], columns=[])
+ tm.assert_frame_equal(result, expected, check_names=False)
+
+ def test_pivot_integer_bug(self, any_string_dtype):
+ df = DataFrame(
+ data=[("A", "1", "A1"), ("B", "2", "B2")], dtype=any_string_dtype
+ )
+
+ result = df.pivot(index=1, columns=0, values=2)
+ expected_columns = Index(["A", "B"], name=0, dtype=any_string_dtype)
+ tm.assert_index_equal(result.columns, expected_columns)
+
+ def test_pivot_index_none(self):
+ # GH#3962
+ data = {
+ "index": ["A", "B", "C", "C", "B", "A"],
+ "columns": ["One", "One", "One", "Two", "Two", "Two"],
+ "values": [1.0, 2.0, 3.0, 3.0, 2.0, 1.0],
+ }
+
+ frame = DataFrame(data).set_index("index")
+ result = frame.pivot(columns="columns", values="values")
+ expected = DataFrame(
+ {
+ "One": {"A": 1.0, "B": 2.0, "C": 3.0},
+ "Two": {"A": 1.0, "B": 2.0, "C": 3.0},
+ }
+ )
+
+ expected.index.name, expected.columns.name = "index", "columns"
+ tm.assert_frame_equal(result, expected)
+
+ # omit values
+ result = frame.pivot(columns="columns")
+
+ expected.columns = MultiIndex.from_tuples(
+ [("values", "One"), ("values", "Two")], names=[None, "columns"]
+ )
+ expected.index.name = "index"
+ tm.assert_frame_equal(result, expected, check_names=False)
+ assert result.index.name == "index"
+ assert result.columns.names == (None, "columns")
+ expected.columns = expected.columns.droplevel(0)
+ result = frame.pivot(columns="columns", values="values")
+
+ expected.columns.name = "columns"
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_index_list_values_none_immutable_args(self):
+ # GH37635
+ df = DataFrame(
+ {
+ "lev1": [1, 1, 1, 2, 2, 2],
+ "lev2": [1, 1, 2, 1, 1, 2],
+ "lev3": [1, 2, 1, 2, 1, 2],
+ "lev4": [1, 2, 3, 4, 5, 6],
+ "values": [0, 1, 2, 3, 4, 5],
+ }
+ )
+ index = ["lev1", "lev2"]
+ columns = ["lev3"]
+ result = df.pivot(index=index, columns=columns)
+
+ expected = DataFrame(
+ np.array(
+ [
+ [1.0, 2.0, 0.0, 1.0],
+ [3.0, np.nan, 2.0, np.nan],
+ [5.0, 4.0, 4.0, 3.0],
+ [np.nan, 6.0, np.nan, 5.0],
+ ]
+ ),
+ index=MultiIndex.from_arrays(
+ [(1, 1, 2, 2), (1, 2, 1, 2)], names=["lev1", "lev2"]
+ ),
+ columns=MultiIndex.from_arrays(
+ [("lev4", "lev4", "values", "values"), (1, 2, 1, 2)],
+ names=[None, "lev3"],
+ ),
+ )
+
+ tm.assert_frame_equal(result, expected)
+
+ assert index == ["lev1", "lev2"]
+ assert columns == ["lev3"]
+
+ def test_pivot_columns_not_given(self):
+ # GH#48293
+ df = DataFrame({"a": [1], "b": 1})
+ with pytest.raises(TypeError, match="missing 1 required keyword-only argument"):
+ df.pivot()
+
+ # this still fails because columns=None gets passed down to unstack as level=None
+ # while at that point None was converted to NaN
+ @pytest.mark.xfail(
+ using_string_dtype(), reason="TODO(infer_string) None is cast to NaN"
+ )
+ def test_pivot_columns_is_none(self):
+ # GH#48293
+ df = DataFrame({None: [1], "b": 2, "c": 3})
+ result = df.pivot(columns=None)
+ expected = DataFrame({("b", 1): [2], ("c", 1): 3})
+ tm.assert_frame_equal(result, expected)
+
+ result = df.pivot(columns=None, index="b")
+ expected = DataFrame({("c", 1): 3}, index=Index([2], name="b"))
+ tm.assert_frame_equal(result, expected)
+
+ result = df.pivot(columns=None, index="b", values="c")
+ expected = DataFrame({1: 3}, index=Index([2], name="b"))
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_index_is_none(self, using_infer_string):
+ # GH#48293
+ df = DataFrame({None: [1], "b": 2, "c": 3})
+
+ result = df.pivot(columns="b", index=None)
+ expected = DataFrame({("c", 2): 3}, index=[1])
+ expected.columns.names = [None, "b"]
+ tm.assert_frame_equal(result, expected)
+
+ result = df.pivot(columns="b", index=None, values="c")
+ expected = DataFrame(3, index=[1], columns=Index([2], name="b"))
+ if using_infer_string:
+ expected.index.name = np.nan
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_values_is_none(self):
+ # GH#48293
+ df = DataFrame({None: [1], "b": 2, "c": 3})
+
+ result = df.pivot(columns="b", index="c", values=None)
+ expected = DataFrame(
+ 1, index=Index([3], name="c"), columns=Index([2], name="b")
+ )
+ tm.assert_frame_equal(result, expected)
+
+ result = df.pivot(columns="b", values=None)
+ expected = DataFrame(1, index=[0], columns=Index([2], name="b"))
+ tm.assert_frame_equal(result, expected)
+
+ def test_pivot_not_changing_index_name(self):
+ # GH#52692
+ df = DataFrame({"one": ["a"], "two": 0, "three": 1})
+ expected = df.copy(deep=True)
+ df.pivot(index="one", columns="two", values="three")
+ tm.assert_frame_equal(df, expected)
+
+ def test_pivot_table_empty_dataframe_correct_index(self):
+ # GH 21932
+ df = DataFrame([], columns=["a", "b", "value"])
+ pivot = df.pivot_table(index="a", columns="b", values="value", aggfunc="count")
+
+ expected = Index([], dtype="object", name="b")
+ tm.assert_index_equal(pivot.columns, expected)
+
+ def test_pivot_table_handles_explicit_datetime_types(self):
+ # GH#43574
+ df = DataFrame(
+ [
+ {"a": "x", "date_str": "2023-01-01", "amount": 1},
+ {"a": "y", "date_str": "2023-01-02", "amount": 2},
+ {"a": "z", "date_str": "2023-01-03", "amount": 3},
+ ]
+ )
+ df["date"] = pd.to_datetime(df["date_str"])
+
+ with tm.assert_produces_warning(False):
+ pivot = df.pivot_table(
+ index=["a", "date"], values=["amount"], aggfunc="sum", margins=True
+ )
+
+ expected = MultiIndex.from_tuples(
+ [
+ ("x", datetime.strptime("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")),
+ ("y", datetime.strptime("2023-01-02 00:00:00", "%Y-%m-%d %H:%M:%S")),
+ ("z", datetime.strptime("2023-01-03 00:00:00", "%Y-%m-%d %H:%M:%S")),
+ ("All", ""),
+ ],
+ names=["a", "date"],
+ )
+ tm.assert_index_equal(pivot.index, expected)
+
+ def test_pivot_table_with_margins_and_numeric_column_names(self):
+ # GH#26568
+ df = DataFrame([["a", "x", 1], ["a", "y", 2], ["b", "y", 3], ["b", "z", 4]])
+
+ result = df.pivot_table(
+ index=0, columns=1, values=2, aggfunc="sum", fill_value=0, margins=True
+ )
+
+ expected = DataFrame(
+ [[1, 2, 0, 3], [0, 3, 4, 7], [1, 5, 4, 10]],
+ columns=Index(["x", "y", "z", "All"], name=1),
+ index=Index(["a", "b", "All"], name=0),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("m", [1, 10])
+ def test_unstack_copy(self, m):
+ # GH#56633
+ levels = np.arange(m)
+ index = MultiIndex.from_product([levels] * 2)
+ values = np.arange(m * m * 100).reshape(m * m, 100)
+ df = DataFrame(values, index, np.arange(100))
+ df_orig = df.copy()
+ result = df.unstack(sort=False)
+ result.iloc[0, 0] = -1
+ tm.assert_frame_equal(df, df_orig)
+
+ def test_pivot_empty_with_datetime(self):
+ # GH#59126
+ df = DataFrame(
+ {
+ "timestamp": Series([], dtype=pd.DatetimeTZDtype(tz="UTC")),
+ "category": Series([], dtype=str),
+ "value": Series([], dtype=str),
+ }
+ )
+ df_pivoted = df.pivot_table(
+ index="category", columns="value", values="timestamp"
+ )
+ assert df_pivoted.empty
+
+ def test_pivot_margins_with_none_index(self):
+ # GH#58722
+ df = DataFrame(
+ {
+ "x": [1, 1, 2],
+ "y": [3, 3, 4],
+ "z": [5, 5, 6],
+ "w": [7, 8, 9],
+ }
+ )
+ result = df.pivot_table(
+ index=None,
+ columns=["y", "z"],
+ values="w",
+ margins=True,
+ aggfunc="count",
+ )
+ expected = DataFrame(
+ [[2, 2, 1, 1]],
+ index=["w"],
+ columns=MultiIndex(
+ levels=[[3, 4], [5, 6, "All"]],
+ codes=[[0, 0, 1, 1], [0, 2, 1, 2]],
+ names=["y", "z"],
+ ),
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.filterwarnings("ignore:Passing a BlockManager:DeprecationWarning")
+ def test_pivot_with_pyarrow_categorical(self):
+ # GH#53051
+ pa = pytest.importorskip("pyarrow")
+
+ df = DataFrame(
+ {"string_column": ["A", "B", "C"], "number_column": [1, 2, 3]}
+ ).astype(
+ {
+ "string_column": ArrowDtype(pa.dictionary(pa.int32(), pa.string())),
+ "number_column": "float[pyarrow]",
+ }
+ )
+
+ df = df.pivot(columns=["string_column"], values=["number_column"])
+
+ multi_index = MultiIndex.from_arrays(
+ [["number_column", "number_column", "number_column"], ["A", "B", "C"]],
+ names=(None, "string_column"),
+ )
+ df_expected = DataFrame(
+ [[1.0, np.nan, np.nan], [np.nan, 2.0, np.nan], [np.nan, np.nan, 3.0]],
+ columns=multi_index,
+ )
+ tm.assert_frame_equal(
+ df, df_expected, check_dtype=False, check_column_type=False
+ )
+
+ @pytest.mark.parametrize("freq", ["D", "M", "Q", "Y"])
+ def test_pivot_empty_dataframe_period_dtype(self, freq):
+ # GH#62705
+
+ dtype = pd.PeriodDtype(freq=freq)
+ df = DataFrame({"index": [], "columns": [], "values": []})
+ df = df.astype({"values": dtype})
+ result = df.pivot(index="index", columns="columns", values="values")
+
+ expected_index = Index([], name="index", dtype="float64")
+ expected_columns = Index([], name="columns", dtype="float64")
+ expected = DataFrame(
+ index=expected_index, columns=expected_columns, dtype=dtype
+ )
+
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_pivot_multilevel.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_pivot_multilevel.py
new file mode 100644
index 0000000000000000000000000000000000000000..af70210b37f3c0396b1dd972f64822001c04dc50
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_pivot_multilevel.py
@@ -0,0 +1,301 @@
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "input_index, input_columns, input_values, "
+ "expected_values, expected_columns, expected_index",
+ [
+ (
+ ["lev4"],
+ "lev3",
+ "values",
+ [
+ [0.0, np.nan],
+ [np.nan, 1.0],
+ [2.0, np.nan],
+ [np.nan, 3.0],
+ [4.0, np.nan],
+ [np.nan, 5.0],
+ [6.0, np.nan],
+ [np.nan, 7.0],
+ ],
+ Index([1, 2], name="lev3"),
+ Index([1, 2, 3, 4, 5, 6, 7, 8], name="lev4"),
+ ),
+ (
+ ["lev4"],
+ "lev3",
+ lib.no_default,
+ [
+ [1.0, np.nan, 1.0, np.nan, 0.0, np.nan],
+ [np.nan, 1.0, np.nan, 1.0, np.nan, 1.0],
+ [1.0, np.nan, 2.0, np.nan, 2.0, np.nan],
+ [np.nan, 1.0, np.nan, 2.0, np.nan, 3.0],
+ [2.0, np.nan, 1.0, np.nan, 4.0, np.nan],
+ [np.nan, 2.0, np.nan, 1.0, np.nan, 5.0],
+ [2.0, np.nan, 2.0, np.nan, 6.0, np.nan],
+ [np.nan, 2.0, np.nan, 2.0, np.nan, 7.0],
+ ],
+ MultiIndex.from_tuples(
+ [
+ ("lev1", 1),
+ ("lev1", 2),
+ ("lev2", 1),
+ ("lev2", 2),
+ ("values", 1),
+ ("values", 2),
+ ],
+ names=[None, "lev3"],
+ ),
+ Index([1, 2, 3, 4, 5, 6, 7, 8], name="lev4"),
+ ),
+ (
+ ["lev1", "lev2"],
+ "lev3",
+ "values",
+ [[0, 1], [2, 3], [4, 5], [6, 7]],
+ Index([1, 2], name="lev3"),
+ MultiIndex.from_tuples(
+ [(1, 1), (1, 2), (2, 1), (2, 2)], names=["lev1", "lev2"]
+ ),
+ ),
+ (
+ ["lev1", "lev2"],
+ "lev3",
+ lib.no_default,
+ [[1, 2, 0, 1], [3, 4, 2, 3], [5, 6, 4, 5], [7, 8, 6, 7]],
+ MultiIndex.from_tuples(
+ [("lev4", 1), ("lev4", 2), ("values", 1), ("values", 2)],
+ names=[None, "lev3"],
+ ),
+ MultiIndex.from_tuples(
+ [(1, 1), (1, 2), (2, 1), (2, 2)], names=["lev1", "lev2"]
+ ),
+ ),
+ ],
+)
+def test_pivot_list_like_index(
+ input_index,
+ input_columns,
+ input_values,
+ expected_values,
+ expected_columns,
+ expected_index,
+):
+ # GH 21425, test when index is given a list
+ df = pd.DataFrame(
+ {
+ "lev1": [1, 1, 1, 1, 2, 2, 2, 2],
+ "lev2": [1, 1, 2, 2, 1, 1, 2, 2],
+ "lev3": [1, 2, 1, 2, 1, 2, 1, 2],
+ "lev4": [1, 2, 3, 4, 5, 6, 7, 8],
+ "values": [0, 1, 2, 3, 4, 5, 6, 7],
+ }
+ )
+
+ result = df.pivot(index=input_index, columns=input_columns, values=input_values)
+ expected = pd.DataFrame(
+ expected_values, columns=expected_columns, index=expected_index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "input_index, input_columns, input_values, "
+ "expected_values, expected_columns, expected_index",
+ [
+ (
+ "lev4",
+ ["lev3"],
+ "values",
+ [
+ [0.0, np.nan],
+ [np.nan, 1.0],
+ [2.0, np.nan],
+ [np.nan, 3.0],
+ [4.0, np.nan],
+ [np.nan, 5.0],
+ [6.0, np.nan],
+ [np.nan, 7.0],
+ ],
+ Index([1, 2], name="lev3"),
+ Index([1, 2, 3, 4, 5, 6, 7, 8], name="lev4"),
+ ),
+ (
+ ["lev1", "lev2"],
+ ["lev3"],
+ "values",
+ [[0, 1], [2, 3], [4, 5], [6, 7]],
+ Index([1, 2], name="lev3"),
+ MultiIndex.from_tuples(
+ [(1, 1), (1, 2), (2, 1), (2, 2)], names=["lev1", "lev2"]
+ ),
+ ),
+ (
+ ["lev1"],
+ ["lev2", "lev3"],
+ "values",
+ [[0, 1, 2, 3], [4, 5, 6, 7]],
+ MultiIndex.from_tuples(
+ [(1, 1), (1, 2), (2, 1), (2, 2)], names=["lev2", "lev3"]
+ ),
+ Index([1, 2], name="lev1"),
+ ),
+ (
+ ["lev1", "lev2"],
+ ["lev3", "lev4"],
+ "values",
+ [
+ [0.0, 1.0, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
+ [np.nan, np.nan, 2.0, 3.0, np.nan, np.nan, np.nan, np.nan],
+ [np.nan, np.nan, np.nan, np.nan, 4.0, 5.0, np.nan, np.nan],
+ [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 6.0, 7.0],
+ ],
+ MultiIndex.from_tuples(
+ [(1, 1), (2, 2), (1, 3), (2, 4), (1, 5), (2, 6), (1, 7), (2, 8)],
+ names=["lev3", "lev4"],
+ ),
+ MultiIndex.from_tuples(
+ [(1, 1), (1, 2), (2, 1), (2, 2)], names=["lev1", "lev2"]
+ ),
+ ),
+ ],
+)
+def test_pivot_list_like_columns(
+ input_index,
+ input_columns,
+ input_values,
+ expected_values,
+ expected_columns,
+ expected_index,
+):
+ # GH 21425, test when columns is given a list
+ df = pd.DataFrame(
+ {
+ "lev1": [1, 1, 1, 1, 2, 2, 2, 2],
+ "lev2": [1, 1, 2, 2, 1, 1, 2, 2],
+ "lev3": [1, 2, 1, 2, 1, 2, 1, 2],
+ "lev4": [1, 2, 3, 4, 5, 6, 7, 8],
+ "values": [0, 1, 2, 3, 4, 5, 6, 7],
+ }
+ )
+
+ result = df.pivot(index=input_index, columns=input_columns, values=input_values)
+ expected = pd.DataFrame(
+ expected_values, columns=expected_columns, index=expected_index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_pivot_multiindexed_rows_and_cols():
+ # GH 36360
+
+ df = pd.DataFrame(
+ data=np.arange(12).reshape(4, 3),
+ columns=MultiIndex.from_tuples(
+ [(0, 0), (0, 1), (0, 2)], names=["col_L0", "col_L1"]
+ ),
+ index=MultiIndex.from_tuples(
+ [(0, 0, 0), (0, 0, 1), (1, 1, 1), (1, 0, 0)],
+ names=["idx_L0", "idx_L1", "idx_L2"],
+ ),
+ )
+
+ res = df.pivot_table(
+ index=["idx_L0"],
+ columns=["idx_L1"],
+ values=[(0, 1)],
+ aggfunc=lambda col: col.values.sum(),
+ )
+
+ expected = pd.DataFrame(
+ data=[[5, np.nan], [10, 7.0]],
+ columns=MultiIndex.from_tuples(
+ [(0, 1, 0), (0, 1, 1)], names=["col_L0", "col_L1", "idx_L1"]
+ ),
+ index=Index([0, 1], dtype="int64", name="idx_L0"),
+ )
+ expected = expected.astype("float64")
+
+ tm.assert_frame_equal(res, expected)
+
+
+def test_pivot_df_multiindex_index_none():
+ # GH 23955
+ df = pd.DataFrame(
+ [
+ ["A", "A1", "label1", 1],
+ ["A", "A2", "label2", 2],
+ ["B", "A1", "label1", 3],
+ ["B", "A2", "label2", 4],
+ ],
+ columns=["index_1", "index_2", "label", "value"],
+ )
+ df = df.set_index(["index_1", "index_2"])
+
+ result = df.pivot(columns="label", values="value")
+ expected = pd.DataFrame(
+ [[1.0, np.nan], [np.nan, 2.0], [3.0, np.nan], [np.nan, 4.0]],
+ index=df.index,
+ columns=Index(["label1", "label2"], name="label"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index, columns, e_data, e_index, e_cols",
+ [
+ (
+ "index",
+ ["col", "value"],
+ [
+ [50.0, np.nan, 100.0, np.nan],
+ [np.nan, 100.0, np.nan, 200.0],
+ ],
+ Index(data=["A", "B"], name="index"),
+ MultiIndex.from_arrays(
+ arrays=[[1, 1, 2, 2], [50, 100, 100, 200]], names=["col", "value"]
+ ),
+ ),
+ (
+ ["index", "value"],
+ "col",
+ [
+ [50.0, np.nan],
+ [np.nan, 100.0],
+ [100.0, np.nan],
+ [np.nan, 200.0],
+ ],
+ MultiIndex.from_arrays(
+ arrays=[["A", "A", "B", "B"], [50, 100, 100, 200]],
+ names=["index", "value"],
+ ),
+ Index(data=[1, 2], name="col"),
+ ),
+ ],
+ ids=["values-and-columns", "values-and-index"],
+)
+def test_pivot_table_multiindex_values_as_two_params(
+ index, columns, e_data, e_index, e_cols
+):
+ # GH#61292
+ data = [
+ ["A", 1, 50, -1],
+ ["B", 1, 100, -2],
+ ["A", 2, 100, -2],
+ ["B", 2, 200, -4],
+ ]
+ df = pd.DataFrame(data=data, columns=["index", "col", "value", "extra"])
+ result = df.pivot_table(values="value", index=index, columns=columns)
+ expected = pd.DataFrame(data=e_data, index=e_index, columns=e_cols)
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_qcut.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_qcut.py
new file mode 100644
index 0000000000000000000000000000000000000000..51617bc3536807fca79f06c8476dba0694f5d445
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_qcut.py
@@ -0,0 +1,308 @@
+import os
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DatetimeIndex,
+ Interval,
+ IntervalIndex,
+ NaT,
+ Series,
+ Timedelta,
+ TimedeltaIndex,
+ Timestamp,
+ cut,
+ date_range,
+ isna,
+ qcut,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.api.types import CategoricalDtype
+
+from pandas.tseries.offsets import Day
+
+
+def test_qcut():
+ arr = np.random.default_rng(2).standard_normal(1000)
+
+ # We store the bins as Index that have been
+ # rounded to comparisons are a bit tricky.
+ labels, _ = qcut(arr, 4, retbins=True)
+ ex_bins = np.quantile(arr, [0, 0.25, 0.5, 0.75, 1.0])
+
+ result = labels.categories.left.values
+ assert np.allclose(result, ex_bins[:-1], atol=1e-2)
+
+ result = labels.categories.right.values
+ assert np.allclose(result, ex_bins[1:], atol=1e-2)
+
+ ex_levels = cut(arr, ex_bins, include_lowest=True)
+ tm.assert_categorical_equal(labels, ex_levels)
+
+
+def test_qcut_bounds():
+ arr = np.random.default_rng(2).standard_normal(1000)
+
+ factor = qcut(arr, 10, labels=False)
+ assert len(np.unique(factor)) == 10
+
+
+def test_qcut_specify_quantiles():
+ arr = np.random.default_rng(2).standard_normal(100)
+ factor = qcut(arr, [0, 0.25, 0.5, 0.75, 1.0])
+
+ expected = qcut(arr, 4)
+ tm.assert_categorical_equal(factor, expected)
+
+
+def test_qcut_all_bins_same():
+ with pytest.raises(ValueError, match="edges.*unique"):
+ qcut([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 3)
+
+
+def test_qcut_include_lowest():
+ values = np.arange(10)
+ ii = qcut(values, 4)
+
+ ex_levels = IntervalIndex(
+ [
+ Interval(-0.001, 2.25),
+ Interval(2.25, 4.5),
+ Interval(4.5, 6.75),
+ Interval(6.75, 9),
+ ]
+ )
+ tm.assert_index_equal(ii.categories, ex_levels)
+
+
+def test_qcut_nas():
+ arr = np.random.default_rng(2).standard_normal(100)
+ arr[:20] = np.nan
+
+ result = qcut(arr, 4)
+ assert isna(result[:20]).all()
+
+
+def test_qcut_index():
+ result = qcut([0, 2], 2)
+ intervals = [Interval(-0.001, 1), Interval(1, 2)]
+
+ expected = Categorical(intervals, ordered=True)
+ tm.assert_categorical_equal(result, expected)
+
+
+def test_qcut_binning_issues(datapath):
+ # see gh-1978, gh-1979
+ cut_file = datapath(os.path.join("reshape", "data", "cut_data.csv"))
+ arr = np.loadtxt(cut_file)
+ result = qcut(arr, 20)
+
+ starts = result.categories.left
+ ends = result.categories.right
+ assert (starts < ends).all()
+ assert (starts[1:] <= ends[:-1]).all()
+
+
+def test_qcut_return_intervals():
+ ser = Series([0, 1, 2, 3, 4, 5, 6, 7, 8])
+ res = qcut(ser, [0, 0.333, 0.666, 1])
+
+ exp_levels = np.array(
+ [Interval(-0.001, 2.664), Interval(2.664, 5.328), Interval(5.328, 8)]
+ )
+ exp = Series(exp_levels.take([0, 0, 0, 1, 1, 1, 2, 2, 2])).astype(
+ CategoricalDtype(ordered=True)
+ )
+ tm.assert_series_equal(res, exp)
+
+
+@pytest.mark.parametrize("labels", ["foo", 1, True])
+def test_qcut_incorrect_labels(labels):
+ # GH 13318
+ values = range(5)
+ msg = "Bin labels must either be False, None or passed in as a list-like argument"
+ with pytest.raises(ValueError, match=msg):
+ qcut(values, 4, labels=labels)
+
+
+@pytest.mark.parametrize("labels", [["a", "b", "c"], list(range(3))])
+def test_qcut_wrong_length_labels(labels):
+ # GH 13318
+ values = range(10)
+ msg = "Bin labels must be one fewer than the number of bin edges"
+ with pytest.raises(ValueError, match=msg):
+ qcut(values, 4, labels=labels)
+
+
+@pytest.mark.parametrize(
+ "labels, expected",
+ [
+ (["a", "b", "c"], ["a", "b", "c"]),
+ (list(range(3)), [0, 1, 2]),
+ ],
+)
+def test_qcut_list_like_labels(labels, expected):
+ # GH 13318
+ values = range(3)
+ result = qcut(values, 3, labels=labels)
+ expected = Categorical(expected, ordered=True)
+ tm.assert_categorical_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "kwargs,msg",
+ [
+ ({"duplicates": "drop"}, None),
+ ({}, "Bin edges must be unique"),
+ ({"duplicates": "raise"}, "Bin edges must be unique"),
+ ({"duplicates": "foo"}, "invalid value for 'duplicates' parameter"),
+ ],
+)
+def test_qcut_duplicates_bin(kwargs, msg):
+ # see gh-7751
+ values = [0, 0, 0, 0, 1, 2, 3]
+
+ if msg is not None:
+ with pytest.raises(ValueError, match=msg):
+ qcut(values, 3, **kwargs)
+ else:
+ result = qcut(values, 3, **kwargs)
+ expected = IntervalIndex([Interval(-0.001, 1), Interval(1, 3)])
+ tm.assert_index_equal(result.categories, expected)
+
+
+@pytest.mark.parametrize(
+ "data,start,end", [(9.0, 8.999, 9.0), (0.0, -0.001, 0.0), (-9.0, -9.001, -9.0)]
+)
+@pytest.mark.parametrize("length", [1, 2])
+@pytest.mark.parametrize("labels", [None, False])
+def test_single_quantile(data, start, end, length, labels):
+ # see gh-15431
+ ser = Series([data] * length)
+ result = qcut(ser, 1, labels=labels)
+
+ if labels is None:
+ intervals = IntervalIndex([Interval(start, end)] * length, closed="right")
+ expected = Series(intervals).astype(CategoricalDtype(ordered=True))
+ else:
+ expected = Series([0] * length, dtype=np.intp)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ser",
+ [
+ DatetimeIndex(["20180101", NaT, "20180103"]),
+ TimedeltaIndex(["0 days", NaT, "2 days"]),
+ ],
+ ids=lambda x: str(x.dtype),
+)
+def test_qcut_nat(ser, unit):
+ # see gh-19768
+ ser = Series(ser)
+ ser = ser.dt.as_unit(unit)
+ td = Timedelta(1, unit=unit).as_unit(unit)
+
+ left = Series([ser[0] - td, np.nan, ser[2] - Day()], dtype=ser.dtype)
+ right = Series([ser[2] - Day(), np.nan, ser[2]], dtype=ser.dtype)
+ intervals = IntervalIndex.from_arrays(left, right)
+ expected = Series(Categorical(intervals, ordered=True))
+
+ result = qcut(ser, 2)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("bins", [3, np.linspace(0, 1, 4)])
+def test_datetime_tz_qcut(bins):
+ # see gh-19872
+ tz = "US/Eastern"
+ ser = Series(date_range("20130101", periods=3, tz=tz, unit="ns"))
+
+ result = qcut(ser, bins)
+ expected = Series(
+ IntervalIndex(
+ [
+ Interval(
+ Timestamp("2012-12-31 23:59:59.999999999", tz=tz),
+ Timestamp("2013-01-01 16:00:00", tz=tz),
+ ),
+ Interval(
+ Timestamp("2013-01-01 16:00:00", tz=tz),
+ Timestamp("2013-01-02 08:00:00", tz=tz),
+ ),
+ Interval(
+ Timestamp("2013-01-02 08:00:00", tz=tz),
+ Timestamp("2013-01-03 00:00:00", tz=tz),
+ ),
+ ]
+ )
+ ).astype(CategoricalDtype(ordered=True))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "arg,expected_bins",
+ [
+ [
+ timedelta_range("1day", periods=3),
+ TimedeltaIndex(["1 days", "2 days", "3 days"]),
+ ],
+ [
+ date_range("20180101", periods=3),
+ DatetimeIndex(["2018-01-01", "2018-01-02", "2018-01-03"]),
+ ],
+ ],
+)
+def test_date_like_qcut_bins(arg, expected_bins, unit):
+ # see gh-19891
+ arg = arg.as_unit(unit)
+ expected_bins = expected_bins.as_unit(unit)
+ ser = Series(arg)
+ result, result_bins = qcut(ser, 2, retbins=True)
+ tm.assert_index_equal(result_bins, expected_bins)
+
+
+@pytest.mark.parametrize("bins", [6, 7])
+@pytest.mark.parametrize(
+ "box, compare",
+ [
+ (Series, tm.assert_series_equal),
+ (np.array, tm.assert_categorical_equal),
+ (list, tm.assert_equal),
+ ],
+)
+def test_qcut_bool_coercion_to_int(bins, box, compare):
+ # issue 20303
+ data_expected = box([0, 1, 1, 0, 1] * 10)
+ data_result = box([False, True, True, False, True] * 10)
+ expected = qcut(data_expected, bins, duplicates="drop")
+ result = qcut(data_result, bins, duplicates="drop")
+ compare(result, expected)
+
+
+@pytest.mark.parametrize("q", [2, 5, 10])
+def test_qcut_nullable_integer(q, any_numeric_ea_dtype):
+ arr = pd.array(np.arange(100), dtype=any_numeric_ea_dtype)
+ arr[::2] = pd.NA
+
+ result = qcut(arr, q)
+ expected = qcut(arr.astype(float), q)
+
+ tm.assert_categorical_equal(result, expected)
+
+
+@pytest.mark.parametrize("scale", [1.0, 1 / 3, 17.0])
+@pytest.mark.parametrize("q", [3, 7, 9])
+@pytest.mark.parametrize("precision", [1, 3, 16])
+def test_qcut_contains(scale, q, precision):
+ # GH-59355
+ arr = (scale * np.arange(q + 1)).round(precision)
+ result = qcut(arr, q, precision=precision)
+
+ for value, bucket in zip(arr, result):
+ assert value in bucket
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_union_categoricals.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_union_categoricals.py
new file mode 100644
index 0000000000000000000000000000000000000000..081feae6fc43fef590d546ad040d4dc0f412028d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/reshape/test_union_categoricals.py
@@ -0,0 +1,369 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.concat import union_categoricals
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ CategoricalIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestUnionCategoricals:
+ @pytest.mark.parametrize(
+ "a, b, combined",
+ [
+ (list("abc"), list("abd"), list("abcabd")),
+ ([0, 1, 2], [2, 3, 4], [0, 1, 2, 2, 3, 4]),
+ ([0, 1.2, 2], [2, 3.4, 4], [0, 1.2, 2, 2, 3.4, 4]),
+ (
+ ["b", "b", np.nan, "a"],
+ ["a", np.nan, "c"],
+ ["b", "b", np.nan, "a", "a", np.nan, "c"],
+ ),
+ (
+ pd.date_range("2014-01-01", "2014-01-05"),
+ pd.date_range("2014-01-06", "2014-01-07"),
+ pd.date_range("2014-01-01", "2014-01-07"),
+ ),
+ (
+ pd.date_range("2014-01-01", "2014-01-05", tz="US/Central"),
+ pd.date_range("2014-01-06", "2014-01-07", tz="US/Central"),
+ pd.date_range("2014-01-01", "2014-01-07", tz="US/Central"),
+ ),
+ (
+ pd.period_range("2014-01-01", "2014-01-05"),
+ pd.period_range("2014-01-06", "2014-01-07"),
+ pd.period_range("2014-01-01", "2014-01-07"),
+ ),
+ ],
+ )
+ @pytest.mark.parametrize("box", [Categorical, CategoricalIndex, Series])
+ def test_union_categorical(self, a, b, combined, box):
+ # GH 13361
+ result = union_categoricals([box(Categorical(a)), box(Categorical(b))])
+ expected = Categorical(combined)
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categorical_ordered_appearance(self):
+ # new categories ordered by appearance
+ s = Categorical(["x", "y", "z"])
+ s2 = Categorical(["a", "b", "c"])
+ result = union_categoricals([s, s2])
+ expected = Categorical(
+ ["x", "y", "z", "a", "b", "c"], categories=["x", "y", "z", "a", "b", "c"]
+ )
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categorical_ordered_true(self):
+ s = Categorical([0, 1.2, 2], ordered=True)
+ s2 = Categorical([0, 1.2, 2], ordered=True)
+ result = union_categoricals([s, s2])
+ expected = Categorical([0, 1.2, 2, 0, 1.2, 2], ordered=True)
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categorical_match_types(self):
+ # must exactly match types
+ s = Categorical([0, 1.2, 2])
+ s2 = Categorical([2, 3, 4])
+ msg = "dtype of categories must be the same"
+ with pytest.raises(TypeError, match=msg):
+ union_categoricals([s, s2])
+
+ def test_union_categorical_empty(self):
+ msg = "No Categoricals to union"
+ with pytest.raises(ValueError, match=msg):
+ union_categoricals([])
+
+ def test_union_categoricals_nan(self):
+ # GH 13759
+ res = union_categoricals(
+ [Categorical([1, 2, np.nan]), Categorical([3, 2, np.nan])]
+ )
+ exp = Categorical([1, 2, np.nan, 3, 2, np.nan])
+ tm.assert_categorical_equal(res, exp)
+
+ res = union_categoricals(
+ [Categorical(["A", "B"]), Categorical(["B", "B", np.nan])]
+ )
+ exp = Categorical(["A", "B", "B", "B", np.nan])
+ tm.assert_categorical_equal(res, exp)
+
+ val1 = [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-03-01"), pd.NaT]
+ val2 = [pd.NaT, pd.Timestamp("2011-01-01"), pd.Timestamp("2011-02-01")]
+
+ res = union_categoricals([Categorical(val1), Categorical(val2)])
+ exp = Categorical(
+ val1 + val2,
+ categories=[
+ pd.Timestamp("2011-01-01"),
+ pd.Timestamp("2011-03-01"),
+ pd.Timestamp("2011-02-01"),
+ ],
+ )
+ tm.assert_categorical_equal(res, exp)
+
+ # all NaN
+ res = union_categoricals(
+ [
+ Categorical(np.array([np.nan, np.nan], dtype=object)),
+ Categorical(["X"], categories=pd.Index(["X"], dtype=object)),
+ ]
+ )
+ exp = Categorical([np.nan, np.nan, "X"])
+ tm.assert_categorical_equal(res, exp)
+
+ res = union_categoricals(
+ [Categorical([np.nan, np.nan]), Categorical([np.nan, np.nan])]
+ )
+ exp = Categorical([np.nan, np.nan, np.nan, np.nan])
+ tm.assert_categorical_equal(res, exp)
+
+ @pytest.mark.parametrize("val", [[], ["1"]])
+ def test_union_categoricals_empty(self, val, request, using_infer_string):
+ # GH 13759
+ if using_infer_string and val == ["1"]:
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="TDOD(infer_string) object and strings dont match"
+ )
+ )
+ res = union_categoricals([Categorical([]), Categorical(val)])
+ exp = Categorical(val)
+ tm.assert_categorical_equal(res, exp)
+
+ def test_union_categorical_same_category(self):
+ # check fastpath
+ c1 = Categorical([1, 2, 3, 4], categories=[1, 2, 3, 4])
+ c2 = Categorical([3, 2, 1, np.nan], categories=[1, 2, 3, 4])
+ res = union_categoricals([c1, c2])
+ exp = Categorical([1, 2, 3, 4, 3, 2, 1, np.nan], categories=[1, 2, 3, 4])
+ tm.assert_categorical_equal(res, exp)
+
+ def test_union_categorical_same_category_str(self):
+ c1 = Categorical(["z", "z", "z"], categories=["x", "y", "z"])
+ c2 = Categorical(["x", "x", "x"], categories=["x", "y", "z"])
+ res = union_categoricals([c1, c2])
+ exp = Categorical(["z", "z", "z", "x", "x", "x"], categories=["x", "y", "z"])
+ tm.assert_categorical_equal(res, exp)
+
+ def test_union_categorical_same_categories_different_order(self):
+ # https://github.com/pandas-dev/pandas/issues/19096
+ c1 = Categorical(["a", "b", "c"], categories=["a", "b", "c"])
+ c2 = Categorical(["a", "b", "c"], categories=["b", "a", "c"])
+ result = union_categoricals([c1, c2])
+ expected = Categorical(
+ ["a", "b", "c", "a", "b", "c"], categories=["a", "b", "c"]
+ )
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categoricals_ordered(self):
+ c1 = Categorical([1, 2, 3], ordered=True)
+ c2 = Categorical([1, 2, 3], ordered=False)
+
+ msg = "Categorical.ordered must be the same"
+ with pytest.raises(TypeError, match=msg):
+ union_categoricals([c1, c2])
+
+ res = union_categoricals([c1, c1])
+ exp = Categorical([1, 2, 3, 1, 2, 3], ordered=True)
+ tm.assert_categorical_equal(res, exp)
+
+ c1 = Categorical([1, 2, 3, np.nan], ordered=True)
+ c2 = Categorical([3, 2], categories=[1, 2, 3], ordered=True)
+
+ res = union_categoricals([c1, c2])
+ exp = Categorical([1, 2, 3, np.nan, 3, 2], ordered=True)
+ tm.assert_categorical_equal(res, exp)
+
+ c1 = Categorical([1, 2, 3], ordered=True)
+ c2 = Categorical([1, 2, 3], categories=[3, 2, 1], ordered=True)
+
+ msg = "to union ordered Categoricals, all categories must be the same"
+ with pytest.raises(TypeError, match=msg):
+ union_categoricals([c1, c2])
+
+ def test_union_categoricals_ignore_order(self):
+ # GH 15219
+ c1 = Categorical([1, 2, 3], ordered=True)
+ c2 = Categorical([1, 2, 3], ordered=False)
+
+ res = union_categoricals([c1, c2], ignore_order=True)
+ exp = Categorical([1, 2, 3, 1, 2, 3])
+ tm.assert_categorical_equal(res, exp)
+
+ msg = "Categorical.ordered must be the same"
+ with pytest.raises(TypeError, match=msg):
+ union_categoricals([c1, c2], ignore_order=False)
+
+ res = union_categoricals([c1, c1], ignore_order=True)
+ exp = Categorical([1, 2, 3, 1, 2, 3])
+ tm.assert_categorical_equal(res, exp)
+
+ res = union_categoricals([c1, c1], ignore_order=False)
+ exp = Categorical([1, 2, 3, 1, 2, 3], categories=[1, 2, 3], ordered=True)
+ tm.assert_categorical_equal(res, exp)
+
+ c1 = Categorical([1, 2, 3, np.nan], ordered=True)
+ c2 = Categorical([3, 2], categories=[1, 2, 3], ordered=True)
+
+ res = union_categoricals([c1, c2], ignore_order=True)
+ exp = Categorical([1, 2, 3, np.nan, 3, 2])
+ tm.assert_categorical_equal(res, exp)
+
+ c1 = Categorical([1, 2, 3], ordered=True)
+ c2 = Categorical([1, 2, 3], categories=[3, 2, 1], ordered=True)
+
+ res = union_categoricals([c1, c2], ignore_order=True)
+ exp = Categorical([1, 2, 3, 1, 2, 3])
+ tm.assert_categorical_equal(res, exp)
+
+ res = union_categoricals([c2, c1], ignore_order=True, sort_categories=True)
+ exp = Categorical([1, 2, 3, 1, 2, 3], categories=[1, 2, 3])
+ tm.assert_categorical_equal(res, exp)
+
+ c1 = Categorical([1, 2, 3], ordered=True)
+ c2 = Categorical([4, 5, 6], ordered=True)
+ result = union_categoricals([c1, c2], ignore_order=True)
+ expected = Categorical([1, 2, 3, 4, 5, 6])
+ tm.assert_categorical_equal(result, expected)
+
+ msg = "to union ordered Categoricals, all categories must be the same"
+ with pytest.raises(TypeError, match=msg):
+ union_categoricals([c1, c2], ignore_order=False)
+
+ with pytest.raises(TypeError, match=msg):
+ union_categoricals([c1, c2])
+
+ def test_union_categoricals_sort(self):
+ # GH 13846
+ c1 = Categorical(["x", "y", "z"])
+ c2 = Categorical(["a", "b", "c"])
+ result = union_categoricals([c1, c2], sort_categories=True)
+ expected = Categorical(
+ ["x", "y", "z", "a", "b", "c"], categories=["a", "b", "c", "x", "y", "z"]
+ )
+ tm.assert_categorical_equal(result, expected)
+
+ # fastpath
+ c1 = Categorical(["a", "b"], categories=["b", "a", "c"])
+ c2 = Categorical(["b", "c"], categories=["b", "a", "c"])
+ result = union_categoricals([c1, c2], sort_categories=True)
+ expected = Categorical(["a", "b", "b", "c"], categories=["a", "b", "c"])
+ tm.assert_categorical_equal(result, expected)
+
+ c1 = Categorical(["a", "b"], categories=["c", "a", "b"])
+ c2 = Categorical(["b", "c"], categories=["c", "a", "b"])
+ result = union_categoricals([c1, c2], sort_categories=True)
+ expected = Categorical(["a", "b", "b", "c"], categories=["a", "b", "c"])
+ tm.assert_categorical_equal(result, expected)
+
+ # fastpath - skip resort
+ c1 = Categorical(["a", "b"], categories=["a", "b", "c"])
+ c2 = Categorical(["b", "c"], categories=["a", "b", "c"])
+ result = union_categoricals([c1, c2], sort_categories=True)
+ expected = Categorical(["a", "b", "b", "c"], categories=["a", "b", "c"])
+ tm.assert_categorical_equal(result, expected)
+
+ c1 = Categorical(["x", np.nan])
+ c2 = Categorical([np.nan, "b"])
+ result = union_categoricals([c1, c2], sort_categories=True)
+ expected = Categorical(["x", np.nan, np.nan, "b"], categories=["b", "x"])
+ tm.assert_categorical_equal(result, expected)
+
+ c1 = Categorical([np.nan])
+ c2 = Categorical([np.nan])
+ result = union_categoricals([c1, c2], sort_categories=True)
+ expected = Categorical([np.nan, np.nan])
+ tm.assert_categorical_equal(result, expected)
+
+ c1 = Categorical([])
+ c2 = Categorical([])
+ result = union_categoricals([c1, c2], sort_categories=True)
+ expected = Categorical([])
+ tm.assert_categorical_equal(result, expected)
+
+ c1 = Categorical(["b", "a"], categories=["b", "a", "c"], ordered=True)
+ c2 = Categorical(["a", "c"], categories=["b", "a", "c"], ordered=True)
+ msg = "Cannot use sort_categories=True with ordered Categoricals"
+ with pytest.raises(TypeError, match=msg):
+ union_categoricals([c1, c2], sort_categories=True)
+
+ def test_union_categoricals_sort_false(self):
+ # GH 13846
+ c1 = Categorical(["x", "y", "z"])
+ c2 = Categorical(["a", "b", "c"])
+ result = union_categoricals([c1, c2], sort_categories=False)
+ expected = Categorical(
+ ["x", "y", "z", "a", "b", "c"], categories=["x", "y", "z", "a", "b", "c"]
+ )
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categoricals_sort_false_fastpath(self):
+ # fastpath
+ c1 = Categorical(["a", "b"], categories=["b", "a", "c"])
+ c2 = Categorical(["b", "c"], categories=["b", "a", "c"])
+ result = union_categoricals([c1, c2], sort_categories=False)
+ expected = Categorical(["a", "b", "b", "c"], categories=["b", "a", "c"])
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categoricals_sort_false_skipresort(self):
+ # fastpath - skip resort
+ c1 = Categorical(["a", "b"], categories=["a", "b", "c"])
+ c2 = Categorical(["b", "c"], categories=["a", "b", "c"])
+ result = union_categoricals([c1, c2], sort_categories=False)
+ expected = Categorical(["a", "b", "b", "c"], categories=["a", "b", "c"])
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categoricals_sort_false_one_nan(self):
+ c1 = Categorical(["x", np.nan])
+ c2 = Categorical([np.nan, "b"])
+ result = union_categoricals([c1, c2], sort_categories=False)
+ expected = Categorical(["x", np.nan, np.nan, "b"], categories=["x", "b"])
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categoricals_sort_false_only_nan(self):
+ c1 = Categorical([np.nan])
+ c2 = Categorical([np.nan])
+ result = union_categoricals([c1, c2], sort_categories=False)
+ expected = Categorical([np.nan, np.nan])
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categoricals_sort_false_empty(self):
+ c1 = Categorical([])
+ c2 = Categorical([])
+ result = union_categoricals([c1, c2], sort_categories=False)
+ expected = Categorical([])
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categoricals_sort_false_ordered_true(self):
+ c1 = Categorical(["b", "a"], categories=["b", "a", "c"], ordered=True)
+ c2 = Categorical(["a", "c"], categories=["b", "a", "c"], ordered=True)
+ result = union_categoricals([c1, c2], sort_categories=False)
+ expected = Categorical(
+ ["b", "a", "a", "c"], categories=["b", "a", "c"], ordered=True
+ )
+ tm.assert_categorical_equal(result, expected)
+
+ def test_union_categorical_unwrap(self):
+ # GH 14173
+ c1 = Categorical(["a", "b"])
+ c2 = Series(["b", "c"], dtype="category")
+ result = union_categoricals([c1, c2])
+ expected = Categorical(["a", "b", "b", "c"])
+ tm.assert_categorical_equal(result, expected)
+
+ c2 = CategoricalIndex(c2)
+ result = union_categoricals([c1, c2])
+ tm.assert_categorical_equal(result, expected)
+
+ c1 = Series(c1)
+ result = union_categoricals([c1, c2])
+ tm.assert_categorical_equal(result, expected)
+
+ msg = "all components to combine must be Categorical"
+ with pytest.raises(TypeError, match=msg):
+ union_categoricals([c1, ["a", "b", "c"]])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_cat_accessor.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_cat_accessor.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8bbf770132c4456052a1c345577af537dbe5065
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_cat_accessor.py
@@ -0,0 +1,260 @@
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+
+from pandas import (
+ Categorical,
+ DataFrame,
+ Index,
+ Series,
+ Timestamp,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays.categorical import CategoricalAccessor
+from pandas.core.indexes.accessors import Properties
+
+
+class TestCatAccessor:
+ @pytest.mark.parametrize(
+ "method",
+ [
+ lambda x: x.cat.set_categories([1, 2, 3]),
+ lambda x: x.cat.reorder_categories([2, 3, 1], ordered=True),
+ lambda x: x.cat.rename_categories([1, 2, 3]),
+ lambda x: x.cat.remove_unused_categories(),
+ lambda x: x.cat.remove_categories([2]),
+ lambda x: x.cat.add_categories([4]),
+ lambda x: x.cat.as_ordered(),
+ lambda x: x.cat.as_unordered(),
+ ],
+ )
+ def test_getname_categorical_accessor(self, method):
+ # GH#17509
+ ser = Series([1, 2, 3], name="A").astype("category")
+ expected = "A"
+ result = method(ser).name
+ assert result == expected
+
+ def test_cat_accessor(self):
+ ser = Series(Categorical(["a", "b", np.nan, "a"]))
+ tm.assert_index_equal(ser.cat.categories, Index(["a", "b"]))
+ assert not ser.cat.ordered
+
+ exp = Categorical(["a", "b", np.nan, "a"], categories=["b", "a"])
+
+ res = ser.cat.set_categories(["b", "a"])
+ tm.assert_categorical_equal(res.values, exp)
+
+ ser[:] = "a"
+ ser = ser.cat.remove_unused_categories()
+ tm.assert_index_equal(ser.cat.categories, Index(["a"]))
+
+ def test_cat_accessor_api(self):
+ # GH#9322
+
+ assert Series.cat is CategoricalAccessor
+ ser = Series(list("aabbcde")).astype("category")
+ assert isinstance(ser.cat, CategoricalAccessor)
+
+ invalid = Series([1])
+ with pytest.raises(AttributeError, match="only use .cat accessor"):
+ invalid.cat
+ assert not hasattr(invalid, "cat")
+
+ def test_cat_accessor_no_new_attributes(self):
+ # https://github.com/pandas-dev/pandas/issues/10673
+ cat = Series(list("aabbcde")).astype("category")
+ with pytest.raises(AttributeError, match="You cannot add any new attribute"):
+ cat.cat.xlabel = "a"
+
+ def test_categorical_delegations(self):
+ # invalid accessor
+ msg = r"Can only use \.cat accessor with a 'category' dtype"
+ with pytest.raises(AttributeError, match=msg):
+ Series([1, 2, 3]).cat
+ with pytest.raises(AttributeError, match=msg):
+ Series([1, 2, 3]).cat()
+ with pytest.raises(AttributeError, match=msg):
+ Series(["a", "b", "c"]).cat
+ with pytest.raises(AttributeError, match=msg):
+ Series(np.arange(5.0)).cat
+ with pytest.raises(AttributeError, match=msg):
+ Series([Timestamp("20130101")]).cat
+
+ # Series should delegate calls to '.categories', '.codes', '.ordered'
+ # and the methods '.set_categories()' 'drop_unused_categories()' to the
+ # categorical
+ ser = Series(Categorical(["a", "b", "c", "a"], ordered=True))
+ exp_categories = Index(["a", "b", "c"])
+ tm.assert_index_equal(ser.cat.categories, exp_categories)
+ ser = ser.cat.rename_categories([1, 2, 3])
+ exp_categories = Index([1, 2, 3])
+ tm.assert_index_equal(ser.cat.categories, exp_categories)
+
+ exp_codes = Series([0, 1, 2, 0], dtype="int8")
+ tm.assert_series_equal(ser.cat.codes, exp_codes)
+
+ assert ser.cat.ordered
+ ser = ser.cat.as_unordered()
+ assert not ser.cat.ordered
+
+ ser = ser.cat.as_ordered()
+ assert ser.cat.ordered
+
+ # reorder
+ ser = Series(Categorical(["a", "b", "c", "a"], ordered=True))
+ exp_categories = Index(["c", "b", "a"])
+ exp_values = np.array(["a", "b", "c", "a"], dtype=np.object_)
+ ser = ser.cat.set_categories(["c", "b", "a"])
+ tm.assert_index_equal(ser.cat.categories, exp_categories)
+ tm.assert_numpy_array_equal(ser.values.__array__(), exp_values)
+ tm.assert_numpy_array_equal(ser.__array__(), exp_values)
+
+ # remove unused categories
+ ser = Series(Categorical(["a", "b", "b", "a"], categories=["a", "b", "c"]))
+ exp_categories = Index(["a", "b"])
+ exp_values = np.array(["a", "b", "b", "a"], dtype=np.object_)
+ ser = ser.cat.remove_unused_categories()
+ tm.assert_index_equal(ser.cat.categories, exp_categories)
+ tm.assert_numpy_array_equal(ser.values.__array__(), exp_values)
+ tm.assert_numpy_array_equal(ser.__array__(), exp_values)
+
+ # This method is likely to be confused, so test that it raises an error
+ # on wrong inputs:
+ msg = "'Series' object has no attribute 'set_categories'"
+ with pytest.raises(AttributeError, match=msg):
+ ser.set_categories([4, 3, 2, 1])
+
+ # right: ser.cat.set_categories([4,3,2,1])
+
+ # GH#18862 (let Series.cat.rename_categories take callables)
+ ser = Series(Categorical(["a", "b", "c", "a"], ordered=True))
+ result = ser.cat.rename_categories(lambda x: x.upper())
+ expected = Series(
+ Categorical(["A", "B", "C", "A"], categories=["A", "B", "C"], ordered=True)
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "idx",
+ [
+ date_range("1/1/2015", periods=5, unit="ns"),
+ date_range("1/1/2015", periods=5, tz="MET", unit="ns"),
+ period_range("1/1/2015", freq="D", periods=5),
+ timedelta_range("1 days", "10 days"),
+ ],
+ )
+ def test_dt_accessor_api_for_categorical(self, idx):
+ # https://github.com/pandas-dev/pandas/issues/10661
+
+ ser = Series(idx)
+ cat = ser.astype("category")
+
+ # only testing field (like .day)
+ # and bool (is_month_start)
+ attr_names = type(ser._values)._datetimelike_ops
+
+ assert isinstance(cat.dt, Properties)
+
+ special_func_defs = [
+ ("strftime", ("%Y-%m-%d",), {}),
+ ("round", ("D",), {}),
+ ("floor", ("D",), {}),
+ ("ceil", ("D",), {}),
+ ("asfreq", ("D",), {}),
+ ("as_unit", ("s"), {}),
+ ]
+ if idx.dtype == "M8[ns]":
+ # exclude dt64tz since that is already localized and would raise
+ tup = ("tz_localize", ("UTC",), {})
+ special_func_defs.append(tup)
+ elif idx.dtype.kind == "M":
+ # exclude dt64 since that is not localized so would raise
+ tup = ("tz_convert", ("EST",), {})
+ special_func_defs.append(tup)
+
+ _special_func_names = [f[0] for f in special_func_defs]
+
+ _ignore_names = ["components", "tz_localize", "tz_convert"]
+
+ func_names = [
+ fname
+ for fname in dir(ser.dt)
+ if not (
+ fname.startswith("_")
+ or fname in attr_names
+ or fname in _special_func_names
+ or fname in _ignore_names
+ )
+ ]
+
+ func_defs = [(fname, (), {}) for fname in func_names]
+ func_defs.extend(
+ f_def for f_def in special_func_defs if f_def[0] in dir(ser.dt)
+ )
+
+ for func, args, kwargs in func_defs:
+ warn_cls = []
+ if func == "to_period" and getattr(idx, "tz", None) is not None:
+ # dropping TZ
+ warn_cls.append(UserWarning)
+ elif func == "to_pytimedelta":
+ # GH 57463
+ warn_cls.append(Pandas4Warning)
+ if warn_cls:
+ warn_cls = tuple(warn_cls)
+ else:
+ warn_cls = None
+ with tm.assert_produces_warning(warn_cls):
+ res = getattr(cat.dt, func)(*args, **kwargs)
+ exp = getattr(ser.dt, func)(*args, **kwargs)
+
+ tm.assert_equal(res, exp)
+
+ for attr in attr_names:
+ res = getattr(cat.dt, attr)
+ exp = getattr(ser.dt, attr)
+
+ tm.assert_equal(res, exp)
+
+ def test_dt_accessor_api_for_categorical_invalid(self):
+ invalid = Series([1, 2, 3]).astype("category")
+ msg = "Can only use .dt accessor with datetimelike"
+
+ with pytest.raises(AttributeError, match=msg):
+ invalid.dt
+ assert not hasattr(invalid, "str")
+
+ def test_set_categories_setitem(self):
+ # GH#43334
+
+ df = DataFrame({"Survived": [1, 0, 1], "Sex": [0, 1, 1]}, dtype="category")
+
+ df["Survived"] = df["Survived"].cat.rename_categories(["No", "Yes"])
+ df["Sex"] = df["Sex"].cat.rename_categories(["female", "male"])
+
+ # values should not be coerced to NaN
+ assert list(df["Sex"]) == ["female", "male", "male"]
+ assert list(df["Survived"]) == ["Yes", "No", "Yes"]
+
+ df["Sex"] = Categorical(df["Sex"], categories=["female", "male"], ordered=False)
+ df["Survived"] = Categorical(
+ df["Survived"], categories=["No", "Yes"], ordered=False
+ )
+
+ # values should not be coerced to NaN
+ assert list(df["Sex"]) == ["female", "male", "male"]
+ assert list(df["Survived"]) == ["Yes", "No", "Yes"]
+
+ def test_categorical_of_booleans_is_boolean(self):
+ # https://github.com/pandas-dev/pandas/issues/46313
+ df = DataFrame(
+ {"int_cat": [1, 2, 3], "bool_cat": [True, False, False]}, dtype="category"
+ )
+ value = df["bool_cat"].cat.categories.dtype
+ expected = np.dtype(np.bool_)
+ assert value is expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_dt_accessor.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_dt_accessor.py
new file mode 100644
index 0000000000000000000000000000000000000000..04e8d6a5dba938fdabcc0f47436e8dfdea537b01
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_dt_accessor.py
@@ -0,0 +1,832 @@
+import calendar
+from datetime import (
+ date,
+ datetime,
+ time,
+)
+import locale
+import unicodedata
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs.timezones import maybe_get_tz
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.common import (
+ is_integer_dtype,
+ is_list_like,
+)
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ Period,
+ PeriodIndex,
+ Series,
+ StringDtype,
+ TimedeltaIndex,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import (
+ DatetimeArray,
+ PeriodArray,
+ TimedeltaArray,
+)
+
+ok_for_period = PeriodArray._datetimelike_ops
+ok_for_period_methods = ["strftime", "to_timestamp", "asfreq"]
+ok_for_dt = DatetimeArray._datetimelike_ops
+ok_for_dt_methods = [
+ "to_period",
+ "to_pydatetime",
+ "tz_localize",
+ "tz_convert",
+ "normalize",
+ "strftime",
+ "round",
+ "floor",
+ "ceil",
+ "day_name",
+ "month_name",
+ "isocalendar",
+ "as_unit",
+]
+ok_for_td = TimedeltaArray._datetimelike_ops
+ok_for_td_methods = [
+ "components",
+ "to_pytimedelta",
+ "total_seconds",
+ "round",
+ "floor",
+ "ceil",
+ "as_unit",
+]
+
+
+def get_dir(ser):
+ # check limited display api
+ results = [r for r in ser.dt.__dir__() if not r.startswith("_")]
+ return sorted(set(results))
+
+
+class TestSeriesDatetimeValues:
+ def _compare(self, ser, name):
+ # GH 7207, 11128
+ # test .dt namespace accessor
+
+ def get_expected(ser, prop):
+ result = getattr(Index(ser._values), prop)
+ if isinstance(result, np.ndarray):
+ if is_integer_dtype(result):
+ result = result.astype("int64")
+ elif not is_list_like(result) or isinstance(result, DataFrame):
+ return result
+ return Series(result, index=ser.index, name=ser.name)
+
+ left = getattr(ser.dt, name)
+ right = get_expected(ser, name)
+ if not (is_list_like(left) and is_list_like(right)):
+ assert left == right
+ elif isinstance(left, DataFrame):
+ tm.assert_frame_equal(left, right)
+ else:
+ tm.assert_series_equal(left, right)
+
+ @pytest.mark.parametrize("freq", ["D", "s", "ms"])
+ def test_dt_namespace_accessor_datetime64(self, freq):
+ # GH#7207, GH#11128
+ # test .dt namespace accessor
+
+ # datetimeindex
+ dti = date_range("20130101", periods=5, freq=freq)
+ ser = Series(dti, name="xxx")
+
+ for prop in ok_for_dt:
+ # we test freq below
+ if prop != "freq":
+ self._compare(ser, prop)
+
+ for prop in ok_for_dt_methods:
+ getattr(ser.dt, prop)
+
+ result = ser.dt.to_pydatetime()
+ assert isinstance(result, Series)
+ assert result.dtype == object
+
+ result = ser.dt.tz_localize("US/Eastern")
+ exp_values = DatetimeIndex(ser.values).tz_localize("US/Eastern")
+ expected = Series(exp_values, index=ser.index, name="xxx")
+ tm.assert_series_equal(result, expected)
+
+ tz_result = result.dt.tz
+ assert str(tz_result) == "US/Eastern"
+ freq_result = ser.dt.freq
+ assert freq_result == DatetimeIndex(ser.values, freq="infer").freq
+
+ # let's localize, then convert
+ result = ser.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
+ exp_values = (
+ DatetimeIndex(ser.values).tz_localize("UTC").tz_convert("US/Eastern")
+ )
+ expected = Series(exp_values, index=ser.index, name="xxx")
+ tm.assert_series_equal(result, expected)
+
+ def test_dt_namespace_accessor_datetime64tz(self):
+ # GH#7207, GH#11128
+ # test .dt namespace accessor
+
+ # datetimeindex with tz
+ dti = date_range("20130101", periods=5, tz="US/Eastern")
+ ser = Series(dti, name="xxx")
+ for prop in ok_for_dt:
+ # we test freq below
+ if prop != "freq":
+ self._compare(ser, prop)
+
+ for prop in ok_for_dt_methods:
+ getattr(ser.dt, prop)
+
+ result = ser.dt.to_pydatetime()
+ assert isinstance(result, Series)
+ assert result.dtype == object
+
+ result = ser.dt.tz_convert("CET")
+ expected = Series(ser._values.tz_convert("CET"), index=ser.index, name="xxx")
+ tm.assert_series_equal(result, expected)
+
+ tz_result = result.dt.tz
+ assert str(tz_result) == "CET"
+ freq_result = ser.dt.freq
+ assert freq_result == DatetimeIndex(ser.values, freq="infer").freq
+
+ def test_dt_namespace_accessor_timedelta(self):
+ # GH#7207, GH#11128
+ # test .dt namespace accessor
+
+ # timedelta index
+ cases = [
+ Series(
+ timedelta_range("1 day", periods=5), index=list("abcde"), name="xxx"
+ ),
+ Series(timedelta_range("1 day 01:23:45", periods=5, freq="s"), name="xxx"),
+ Series(
+ timedelta_range("2 days 01:23:45.012345", periods=5, freq="ms"),
+ name="xxx",
+ ),
+ ]
+ for ser in cases:
+ for prop in ok_for_td:
+ # we test freq below
+ if prop != "freq":
+ self._compare(ser, prop)
+
+ for prop in ok_for_td_methods:
+ getattr(ser.dt, prop)
+
+ result = ser.dt.components
+ assert isinstance(result, DataFrame)
+ tm.assert_index_equal(result.index, ser.index)
+
+ msg = "The behavior of TimedeltaProperties.to_pytimedelta is deprecated"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = ser.dt.to_pytimedelta()
+ assert isinstance(result, np.ndarray)
+ assert result.dtype == object
+
+ result = ser.dt.total_seconds()
+ assert isinstance(result, Series)
+ assert result.dtype == "float64"
+
+ freq_result = ser.dt.freq
+ assert freq_result == TimedeltaIndex(ser.values, freq="infer").freq
+
+ def test_dt_namespace_accessor_period(self):
+ # GH#7207, GH#11128
+ # test .dt namespace accessor
+
+ # periodindex
+ pi = period_range("20130101", periods=5, freq="D")
+ ser = Series(pi, name="xxx")
+
+ for prop in ok_for_period:
+ # we test freq below
+ if prop != "freq":
+ self._compare(ser, prop)
+
+ for prop in ok_for_period_methods:
+ getattr(ser.dt, prop)
+
+ freq_result = ser.dt.freq
+ assert freq_result == PeriodIndex(ser.values).freq
+
+ def test_dt_namespace_accessor_index_and_values(self):
+ # both
+ index = date_range("20130101", periods=3, freq="D")
+ dti = date_range("20140204", periods=3, freq="s")
+ ser = Series(dti, index=index, name="xxx")
+ exp = Series(
+ np.array([2014, 2014, 2014], dtype="int32"), index=index, name="xxx"
+ )
+ tm.assert_series_equal(ser.dt.year, exp)
+
+ exp = Series(np.array([2, 2, 2], dtype="int32"), index=index, name="xxx")
+ tm.assert_series_equal(ser.dt.month, exp)
+
+ exp = Series(np.array([0, 1, 2], dtype="int32"), index=index, name="xxx")
+ tm.assert_series_equal(ser.dt.second, exp)
+
+ exp = Series([ser.iloc[0]] * 3, index=index, name="xxx")
+ tm.assert_series_equal(ser.dt.normalize(), exp)
+
+ def test_dt_accessor_limited_display_api(self):
+ # tznaive
+ ser = Series(date_range("20130101", periods=5, freq="D"), name="xxx")
+ results = get_dir(ser)
+ tm.assert_almost_equal(results, sorted(set(ok_for_dt + ok_for_dt_methods)))
+
+ # tzaware
+ ser = Series(date_range("2015-01-01", "2016-01-01", freq="min"), name="xxx")
+ ser = ser.dt.tz_localize("UTC").dt.tz_convert("America/Chicago")
+ results = get_dir(ser)
+ tm.assert_almost_equal(results, sorted(set(ok_for_dt + ok_for_dt_methods)))
+
+ # Period
+ idx = period_range("20130101", periods=5, freq="D", name="xxx")
+ ser = Series(idx)
+ results = get_dir(ser)
+ tm.assert_almost_equal(
+ results, sorted(set(ok_for_period + ok_for_period_methods))
+ )
+
+ def test_dt_accessor_ambiguous_freq_conversions(self):
+ # GH#11295
+ # ambiguous time error on the conversions
+ ser = Series(date_range("2015-01-01", "2016-01-01", freq="min"), name="xxx")
+ ser = ser.dt.tz_localize("UTC").dt.tz_convert("America/Chicago")
+
+ exp_values = date_range(
+ "2015-01-01", "2016-01-01", freq="min", tz="UTC"
+ ).tz_convert("America/Chicago")
+ # freq not preserved by tz_localize above
+ exp_values = exp_values._with_freq(None)
+ expected = Series(exp_values, name="xxx")
+ tm.assert_series_equal(ser, expected)
+
+ def test_dt_accessor_not_writeable(self):
+ # no setting allowed
+ ser = Series(date_range("20130101", periods=5, freq="D"), name="xxx")
+ with pytest.raises(ValueError, match="modifications"):
+ ser.dt.hour = 5
+
+ # trying to set a copy
+ with tm.raises_chained_assignment_error():
+ ser.dt.hour[0] = 5
+
+ @pytest.mark.parametrize(
+ "method, dates",
+ [
+ ["round", ["2012-01-02", "2012-01-02", "2012-01-01"]],
+ ["floor", ["2012-01-01", "2012-01-01", "2012-01-01"]],
+ ["ceil", ["2012-01-02", "2012-01-02", "2012-01-02"]],
+ ],
+ )
+ def test_dt_round(self, method, dates):
+ # round
+ ser = Series(
+ pd.to_datetime(
+ ["2012-01-01 13:00:00", "2012-01-01 12:01:00", "2012-01-01 08:00:00"]
+ ),
+ name="xxx",
+ )
+ result = getattr(ser.dt, method)("D")
+ expected = Series(pd.to_datetime(dates), name="xxx")
+ tm.assert_series_equal(result, expected)
+
+ def test_dt_round_tz(self):
+ ser = Series(
+ pd.to_datetime(
+ ["2012-01-01 13:00:00", "2012-01-01 12:01:00", "2012-01-01 08:00:00"]
+ ),
+ name="xxx",
+ )
+ result = ser.dt.tz_localize("UTC").dt.tz_convert("US/Eastern").dt.round("D")
+
+ exp_values = pd.to_datetime(
+ ["2012-01-01", "2012-01-01", "2012-01-01"]
+ ).tz_localize("US/Eastern")
+ expected = Series(exp_values, name="xxx")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("method", ["ceil", "round", "floor"])
+ def test_dt_round_tz_ambiguous(self, method):
+ # GH 18946 round near "fall back" DST
+ df1 = DataFrame(
+ [
+ pd.to_datetime("2017-10-29 02:00:00+02:00", utc=True),
+ pd.to_datetime("2017-10-29 02:00:00+01:00", utc=True),
+ pd.to_datetime("2017-10-29 03:00:00+01:00", utc=True),
+ ],
+ columns=["date"],
+ )
+ df1["date"] = df1["date"].dt.tz_convert("Europe/Madrid")
+ # infer
+ result = getattr(df1.date.dt, method)("h", ambiguous="infer")
+ expected = df1["date"]
+ tm.assert_series_equal(result, expected)
+
+ # bool-array
+ result = getattr(df1.date.dt, method)("h", ambiguous=[True, False, False])
+ tm.assert_series_equal(result, expected)
+
+ # NaT
+ result = getattr(df1.date.dt, method)("h", ambiguous="NaT")
+ expected = df1["date"].copy()
+ expected.iloc[0:2] = pd.NaT
+ tm.assert_series_equal(result, expected)
+
+ # raise
+ with tm.external_error_raised(ValueError):
+ getattr(df1.date.dt, method)("h", ambiguous="raise")
+
+ @pytest.mark.parametrize(
+ "method, ts_str, freq",
+ [
+ ["ceil", "2018-03-11 01:59:00-0600", "5min"],
+ ["round", "2018-03-11 01:59:00-0600", "5min"],
+ ["floor", "2018-03-11 03:01:00-0500", "2h"],
+ ],
+ )
+ def test_dt_round_tz_nonexistent(self, method, ts_str, freq):
+ # GH 23324 round near "spring forward" DST
+ ser = Series([pd.Timestamp(ts_str, tz="America/Chicago").as_unit("s")])
+ result = getattr(ser.dt, method)(freq, nonexistent="shift_forward")
+ expected = Series(
+ [pd.Timestamp("2018-03-11 03:00:00", tz="America/Chicago").as_unit("s")]
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = getattr(ser.dt, method)(freq, nonexistent="NaT")
+ expected = Series([pd.NaT]).dt.tz_localize(result.dt.tz)
+ tm.assert_series_equal(result, expected)
+
+ with pytest.raises(ValueError, match="2018-03-11 02:00:00"):
+ getattr(ser.dt, method)(freq, nonexistent="raise")
+
+ @pytest.mark.parametrize("freq", ["ns", "us", "1000us"])
+ def test_dt_round_nonnano_higher_resolution_no_op(self, freq):
+ # GH 52761
+ ser = Series(
+ ["2020-05-31 08:00:00", "2000-12-31 04:00:05", "1800-03-14 07:30:20"],
+ dtype="datetime64[ms]",
+ )
+ expected = ser.copy()
+ result = ser.dt.round(freq)
+ tm.assert_series_equal(result, expected)
+
+ assert not np.shares_memory(ser.array._ndarray, result.array._ndarray)
+
+ def test_dt_namespace_accessor_categorical(self):
+ # GH 19468
+ dti = DatetimeIndex(["20171111", "20181212"]).repeat(2)
+ ser = Series(pd.Categorical(dti), name="foo")
+ result = ser.dt.year
+ expected = Series([2017, 2017, 2018, 2018], dtype="int32", name="foo")
+ tm.assert_series_equal(result, expected)
+
+ def test_dt_tz_localize_categorical(self, tz_aware_fixture):
+ # GH 27952
+ tz = tz_aware_fixture
+ datetimes = Series(
+ ["2019-01-01", "2019-01-01", "2019-01-02"], dtype="datetime64[ns]"
+ )
+ categorical = datetimes.astype("category")
+ result = categorical.dt.tz_localize(tz)
+ expected = datetimes.dt.tz_localize(tz)
+ tm.assert_series_equal(result, expected)
+
+ def test_dt_tz_convert_categorical(self, tz_aware_fixture):
+ # GH 27952
+ tz = tz_aware_fixture
+ datetimes = Series(
+ ["2019-01-01", "2019-01-01", "2019-01-02"], dtype="datetime64[ns, MET]"
+ )
+ categorical = datetimes.astype("category")
+ result = categorical.dt.tz_convert(tz)
+ expected = datetimes.dt.tz_convert(tz)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("accessor", ["year", "month", "day"])
+ def test_dt_other_accessors_categorical(self, accessor):
+ # GH 27952
+ datetimes = Series(
+ ["2018-01-01", "2018-01-01", "2019-01-02"], dtype="datetime64[ns]"
+ )
+ categorical = datetimes.astype("category")
+ result = getattr(categorical.dt, accessor)
+ expected = getattr(datetimes.dt, accessor)
+ tm.assert_series_equal(result, expected)
+
+ def test_dt_accessor_no_new_attributes(self):
+ # https://github.com/pandas-dev/pandas/issues/10673
+ ser = Series(date_range("20130101", periods=5, freq="D"))
+ with pytest.raises(AttributeError, match="You cannot add any new attribute"):
+ ser.dt.xlabel = "a"
+
+ @pytest.mark.parametrize(
+ "time_locale",
+ [None, *tm.get_locales()],
+ )
+ def test_dt_accessor_datetime_name_accessors(self, time_locale):
+ # Test Monday -> Sunday and January -> December, in that sequence
+ if time_locale is None:
+ # If the time_locale is None, day-name and month_name should
+ # return the english attributes
+ expected_days = [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]
+ expected_months = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ ]
+ else:
+ with tm.set_locale(time_locale, locale.LC_TIME):
+ expected_days = calendar.day_name[:]
+ expected_months = calendar.month_name[1:]
+
+ ser = Series(date_range(freq="D", start=datetime(1998, 1, 1), periods=365))
+ english_days = [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]
+ for day, name, eng_name in zip(range(4, 11), expected_days, english_days):
+ name = name.capitalize()
+ assert ser.dt.day_name(locale=time_locale)[day] == name
+ assert ser.dt.day_name(locale=None)[day] == eng_name
+ ser = pd.concat([ser, Series([pd.NaT])])
+ assert np.isnan(ser.dt.day_name(locale=time_locale).iloc[-1])
+
+ ser = Series(date_range(freq="ME", start="2012", end="2013"))
+ result = ser.dt.month_name(locale=time_locale)
+ expected = Series([month.capitalize() for month in expected_months])
+
+ # work around https://github.com/pandas-dev/pandas/issues/22342
+ result = result.str.normalize("NFD")
+ expected = expected.str.normalize("NFD")
+
+ tm.assert_series_equal(result, expected)
+
+ for s_date, expected in zip(ser, expected_months):
+ result = s_date.month_name(locale=time_locale)
+ expected = expected.capitalize()
+
+ result = unicodedata.normalize("NFD", result)
+ expected = unicodedata.normalize("NFD", expected)
+
+ assert result == expected
+
+ ser = pd.concat([ser, Series([pd.NaT])])
+ assert np.isnan(ser.dt.month_name(locale=time_locale).iloc[-1])
+
+ def test_strftime(self):
+ # GH 10086
+ ser = Series(date_range("20130101", periods=5))
+ result = ser.dt.strftime("%Y/%m/%d")
+ expected = Series(
+ ["2013/01/01", "2013/01/02", "2013/01/03", "2013/01/04", "2013/01/05"]
+ )
+ tm.assert_series_equal(result, expected)
+
+ ser = Series(date_range("2015-02-03 11:22:33.4567", periods=5))
+ result = ser.dt.strftime("%Y/%m/%d %H-%M-%S")
+ expected = Series(
+ [
+ "2015/02/03 11-22-33",
+ "2015/02/04 11-22-33",
+ "2015/02/05 11-22-33",
+ "2015/02/06 11-22-33",
+ "2015/02/07 11-22-33",
+ ]
+ )
+ tm.assert_series_equal(result, expected)
+
+ ser = Series(period_range("20130101", periods=5))
+ result = ser.dt.strftime("%Y/%m/%d")
+ expected = Series(
+ ["2013/01/01", "2013/01/02", "2013/01/03", "2013/01/04", "2013/01/05"]
+ )
+ tm.assert_series_equal(result, expected)
+
+ ser = Series(period_range("2015-02-03 11:22:33.4567", periods=5, freq="s"))
+ result = ser.dt.strftime("%Y/%m/%d %H-%M-%S")
+ expected = Series(
+ [
+ "2015/02/03 11-22-33",
+ "2015/02/03 11-22-34",
+ "2015/02/03 11-22-35",
+ "2015/02/03 11-22-36",
+ "2015/02/03 11-22-37",
+ ]
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_strftime_dt64_days(self):
+ ser = Series(date_range("20130101", periods=5))
+ ser.iloc[0] = pd.NaT
+ result = ser.dt.strftime("%Y/%m/%d")
+ expected = Series(
+ [np.nan, "2013/01/02", "2013/01/03", "2013/01/04", "2013/01/05"]
+ )
+ tm.assert_series_equal(result, expected)
+
+ datetime_index = date_range("20150301", periods=5)
+ result = datetime_index.strftime("%Y/%m/%d")
+
+ expected = Index(
+ ["2015/03/01", "2015/03/02", "2015/03/03", "2015/03/04", "2015/03/05"],
+ )
+ # dtype may be S10 or U10 depending on python version
+ tm.assert_index_equal(result, expected)
+
+ def test_strftime_period_days(self, using_infer_string):
+ period_index = period_range("20150301", periods=5)
+ result = period_index.strftime("%Y/%m/%d")
+ expected = Index(
+ ["2015/03/01", "2015/03/02", "2015/03/03", "2015/03/04", "2015/03/05"],
+ dtype="=U10",
+ )
+ if using_infer_string:
+ expected = expected.astype(StringDtype(na_value=np.nan))
+ tm.assert_index_equal(result, expected)
+
+ def test_strftime_dt64_microsecond_resolution(self):
+ ser = Series([datetime(2013, 1, 1, 2, 32, 59), datetime(2013, 1, 2, 14, 32, 1)])
+ result = ser.dt.strftime("%Y-%m-%d %H:%M:%S")
+ expected = Series(["2013-01-01 02:32:59", "2013-01-02 14:32:01"])
+ tm.assert_series_equal(result, expected)
+
+ def test_strftime_period_hours(self):
+ ser = Series(period_range("20130101", periods=4, freq="h"))
+ result = ser.dt.strftime("%Y/%m/%d %H:%M:%S")
+ expected = Series(
+ [
+ "2013/01/01 00:00:00",
+ "2013/01/01 01:00:00",
+ "2013/01/01 02:00:00",
+ "2013/01/01 03:00:00",
+ ]
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_strftime_period_minutes(self):
+ ser = Series(period_range("20130101", periods=4, freq="ms"))
+ result = ser.dt.strftime("%Y/%m/%d %H:%M:%S.%l")
+ expected = Series(
+ [
+ "2013/01/01 00:00:00.000",
+ "2013/01/01 00:00:00.001",
+ "2013/01/01 00:00:00.002",
+ "2013/01/01 00:00:00.003",
+ ]
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "data",
+ [
+ DatetimeIndex(["2019-01-01", pd.NaT]),
+ PeriodIndex(["2019-01-01", pd.NaT], dtype="period[D]"),
+ ],
+ )
+ def test_strftime_nat(self, data):
+ # GH 29578
+ ser = Series(data)
+ result = ser.dt.strftime("%Y-%m-%d")
+ expected = Series(["2019-01-01", np.nan])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "data", [DatetimeIndex([pd.NaT]), PeriodIndex([pd.NaT], dtype="period[D]")]
+ )
+ def test_strftime_all_nat(self, data):
+ # https://github.com/pandas-dev/pandas/issues/45858
+ ser = Series(data)
+ with tm.assert_produces_warning(None):
+ result = ser.dt.strftime("%Y-%m-%d")
+ expected = Series([np.nan], dtype="str")
+ tm.assert_series_equal(result, expected)
+
+ def test_valid_dt_with_missing_values(self):
+ # GH 8689
+ ser = Series(date_range("20130101", periods=5, freq="D"))
+ ser.iloc[2] = pd.NaT
+
+ for attr in ["microsecond", "nanosecond", "second", "minute", "hour", "day"]:
+ expected = getattr(ser.dt, attr).copy()
+ expected.iloc[2] = np.nan
+ result = getattr(ser.dt, attr)
+ tm.assert_series_equal(result, expected)
+
+ result = ser.dt.date
+ expected = Series(
+ [
+ date(2013, 1, 1),
+ date(2013, 1, 2),
+ pd.NaT,
+ date(2013, 1, 4),
+ date(2013, 1, 5),
+ ],
+ dtype="object",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = ser.dt.time
+ expected = Series([time(0), time(0), pd.NaT, time(0), time(0)], dtype="object")
+ tm.assert_series_equal(result, expected)
+
+ def test_dt_accessor_api(self):
+ # GH 9322
+ from pandas.core.indexes.accessors import (
+ CombinedDatetimelikeProperties,
+ DatetimeProperties,
+ )
+
+ assert Series.dt is CombinedDatetimelikeProperties
+
+ ser = Series(date_range("2000-01-01", periods=3))
+ assert isinstance(ser.dt, DatetimeProperties)
+
+ @pytest.mark.parametrize(
+ "data",
+ [
+ np.arange(5),
+ list("abcde"),
+ np.random.default_rng(2).standard_normal(5),
+ ],
+ )
+ def test_dt_accessor_invalid(self, data):
+ # GH#9322 check that series with incorrect dtypes don't have attr
+ ser = Series(data)
+ with pytest.raises(AttributeError, match="only use .dt accessor"):
+ ser.dt
+ assert not hasattr(ser, "dt")
+
+ def test_dt_accessor_updates_on_inplace(self):
+ ser = Series(date_range("2018-01-01", periods=10))
+ ser[2] = None
+ result = ser.fillna(pd.Timestamp("2018-01-01"), inplace=True)
+ assert result is ser
+ result = ser.dt.date
+ assert result[0] == result[2]
+
+ def test_date_tz(self):
+ # GH11757
+ rng = DatetimeIndex(
+ ["2014-04-04 23:56", "2014-07-18 21:24", "2015-11-22 22:14"],
+ tz="US/Eastern",
+ )
+ ser = Series(rng)
+ expected = Series([date(2014, 4, 4), date(2014, 7, 18), date(2015, 11, 22)])
+ tm.assert_series_equal(ser.dt.date, expected)
+ tm.assert_series_equal(ser.apply(lambda x: x.date()), expected)
+
+ def test_dt_timetz_accessor(self, tz_naive_fixture):
+ # GH21358
+ tz = maybe_get_tz(tz_naive_fixture)
+
+ dtindex = DatetimeIndex(
+ ["2014-04-04 23:56", "2014-07-18 21:24", "2015-11-22 22:14"], tz=tz
+ )
+ ser = Series(dtindex)
+ expected = Series(
+ [time(23, 56, tzinfo=tz), time(21, 24, tzinfo=tz), time(22, 14, tzinfo=tz)]
+ )
+ result = ser.dt.timetz
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "input_series, expected_output",
+ [
+ [["2020-01-01"], [[2020, 1, 3]]],
+ [[pd.NaT], [[None, None, None]]],
+ [["2019-12-31", "2019-12-29"], [[2020, 1, 2], [2019, 52, 7]]],
+ [["2010-01-01", pd.NaT], [[2009, 53, 5], [None, None, None]]],
+ # see GH#36032
+ [["2016-01-08", "2016-01-04"], [[2016, 1, 5], [2016, 1, 1]]],
+ [["2016-01-07", "2016-01-01"], [[2016, 1, 4], [2015, 53, 5]]],
+ ],
+ )
+ def test_isocalendar(self, input_series, expected_output):
+ result = pd.to_datetime(Series(input_series)).dt.isocalendar()
+ expected_frame = DataFrame(
+ expected_output, columns=["year", "week", "day"], dtype="UInt32"
+ )
+ tm.assert_frame_equal(result, expected_frame)
+
+ def test_hour_index(self):
+ dt_series = Series(
+ date_range(start="2021-01-01", periods=5, freq="h"),
+ index=[2, 6, 7, 8, 11],
+ dtype="category",
+ )
+ result = dt_series.dt.hour
+ expected = Series(
+ [0, 1, 2, 3, 4],
+ dtype="int32",
+ index=[2, 6, 7, 8, 11],
+ )
+ tm.assert_series_equal(result, expected)
+
+
+class TestSeriesPeriodValuesDtAccessor:
+ @pytest.mark.parametrize(
+ "input_vals",
+ [
+ [Period("2016-01", freq="M"), Period("2016-02", freq="M")],
+ [Period("2016-01-01", freq="D"), Period("2016-01-02", freq="D")],
+ [
+ Period("2016-01-01 00:00:00", freq="h"),
+ Period("2016-01-01 01:00:00", freq="h"),
+ ],
+ [
+ Period("2016-01-01 00:00:00", freq="M"),
+ Period("2016-01-01 00:01:00", freq="M"),
+ ],
+ [
+ Period("2016-01-01 00:00:00", freq="s"),
+ Period("2016-01-01 00:00:01", freq="s"),
+ ],
+ ],
+ )
+ def test_end_time_timevalues(self, input_vals):
+ # GH#17157
+ # Check that the time part of the Period is adjusted by end_time
+ # when using the dt accessor on a Series
+ dtype = pd.PeriodDtype(input_vals[0].freq)
+ input_vals = PeriodArray._from_sequence(np.asarray(input_vals), dtype=dtype)
+
+ ser = Series(input_vals)
+ result = ser.dt.end_time
+ expected = ser.apply(lambda x: x.end_time)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("input_vals", [("2001"), ("NaT")])
+ def test_to_period(self, input_vals):
+ # GH#21205
+ expected = Series([input_vals], dtype="Period[D]")
+ result = Series([input_vals], dtype="datetime64[ns]").dt.to_period("D")
+ tm.assert_series_equal(result, expected)
+
+
+def test_normalize_pre_epoch_dates():
+ # GH: 36294
+ ser = pd.to_datetime(Series(["1969-01-01 09:00:00", "2016-01-01 09:00:00"]))
+ result = ser.dt.normalize()
+ expected = pd.to_datetime(Series(["1969-01-01", "2016-01-01"]))
+ tm.assert_series_equal(result, expected)
+
+
+def test_day_attribute_non_nano_beyond_int32():
+ # GH 52386
+ data = np.array(
+ [
+ 136457654736252,
+ 134736784364431,
+ 245345345545332,
+ 223432411,
+ 2343241,
+ 3634548734,
+ 23234,
+ ],
+ dtype="timedelta64[s]",
+ )
+ ser = Series(data)
+ result = ser.dt.days
+ expected = Series([1579371003, 1559453522, 2839645203, 2586, 27, 42066, 0])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_list_accessor.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_list_accessor.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ff01ebf2fc98ecf5d6c875b40fd96b80b25f5c2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_list_accessor.py
@@ -0,0 +1,146 @@
+import re
+
+import pytest
+
+from pandas import (
+ ArrowDtype,
+ Series,
+)
+import pandas._testing as tm
+
+pa = pytest.importorskip("pyarrow")
+
+
+@pytest.mark.parametrize(
+ "list_dtype",
+ (
+ pa.list_(pa.int64()),
+ pa.list_(pa.int64(), list_size=3),
+ pa.large_list(pa.int64()),
+ ),
+)
+def test_list_getitem(list_dtype):
+ ser = Series(
+ [[1, 2, 3], [4, None, 5], None],
+ dtype=ArrowDtype(list_dtype),
+ name="a",
+ )
+ actual = ser.list[1]
+ expected = Series([2, None, None], dtype="int64[pyarrow]", name="a")
+ tm.assert_series_equal(actual, expected)
+
+
+def test_list_getitem_index():
+ # GH 58425
+ ser = Series(
+ [[1, 2, 3], [4, None, 5], None],
+ dtype=ArrowDtype(pa.list_(pa.int64())),
+ index=[1, 3, 7],
+ name="a",
+ )
+ actual = ser.list[1]
+ expected = Series(
+ [2, None, None],
+ dtype="int64[pyarrow]",
+ index=[1, 3, 7],
+ name="a",
+ )
+ tm.assert_series_equal(actual, expected)
+
+
+def test_list_getitem_slice():
+ ser = Series(
+ [[1, 2, 3], [4, None, 5], None],
+ dtype=ArrowDtype(pa.list_(pa.int64())),
+ index=[1, 3, 7],
+ name="a",
+ )
+ actual = ser.list[1:None:None]
+ expected = Series(
+ [[2, 3], [None, 5], None],
+ dtype=ArrowDtype(pa.list_(pa.int64())),
+ index=[1, 3, 7],
+ name="a",
+ )
+ tm.assert_series_equal(actual, expected)
+
+
+def test_list_len():
+ ser = Series(
+ [[1, 2, 3], [4, None], None],
+ dtype=ArrowDtype(pa.list_(pa.int64())),
+ name="a",
+ )
+ actual = ser.list.len()
+ expected = Series([3, 2, None], dtype=ArrowDtype(pa.int32()), name="a")
+ tm.assert_series_equal(actual, expected)
+
+
+def test_list_flatten():
+ ser = Series(
+ [[1, 2, 3], None, [4, None], [], [7, 8]],
+ dtype=ArrowDtype(pa.list_(pa.int64())),
+ name="a",
+ )
+ actual = ser.list.flatten()
+ expected = Series(
+ [1, 2, 3, 4, None, 7, 8],
+ dtype=ArrowDtype(pa.int64()),
+ index=[0, 0, 0, 2, 2, 4, 4],
+ name="a",
+ )
+ tm.assert_series_equal(actual, expected)
+
+
+def test_list_getitem_slice_invalid():
+ ser = Series(
+ [[1, 2, 3], [4, None, 5], None],
+ dtype=ArrowDtype(pa.list_(pa.int64())),
+ )
+ with tm.external_error_raised(pa.ArrowInvalid):
+ ser.list[1:None:0]
+
+
+def test_list_accessor_non_list_dtype():
+ ser = Series(
+ [1, 2, 4],
+ dtype=ArrowDtype(pa.int64()),
+ )
+ with pytest.raises(
+ AttributeError,
+ match=re.escape(
+ "Can only use the '.list' accessor with 'list[pyarrow]' dtype, "
+ "not int64[pyarrow]."
+ ),
+ ):
+ ser.list[1:None:0]
+
+
+@pytest.mark.parametrize(
+ "list_dtype",
+ (
+ pa.list_(pa.int64()),
+ pa.list_(pa.int64(), list_size=3),
+ pa.large_list(pa.int64()),
+ ),
+)
+def test_list_getitem_invalid_index(list_dtype):
+ ser = Series(
+ [[1, 2, 3], [4, None, 5], None],
+ dtype=ArrowDtype(list_dtype),
+ )
+ with tm.external_error_raised(pa.ArrowInvalid):
+ ser.list[-1]
+ with tm.external_error_raised(pa.ArrowInvalid):
+ ser.list[5]
+ with pytest.raises(ValueError, match="key must be an int or slice, got str"):
+ ser.list["abc"]
+
+
+def test_list_accessor_not_iterable():
+ ser = Series(
+ [[1, 2, 3], [4, None], None],
+ dtype=ArrowDtype(pa.list_(pa.int64())),
+ )
+ with pytest.raises(TypeError, match="'ListAccessor' object is not iterable"):
+ iter(ser.list)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_sparse_accessor.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_sparse_accessor.py
new file mode 100644
index 0000000000000000000000000000000000000000..118095b5dcdbc4f63ba511feb954673485f34427
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_sparse_accessor.py
@@ -0,0 +1,9 @@
+from pandas import Series
+
+
+class TestSparseAccessor:
+ def test_sparse_accessor_updates_on_inplace(self):
+ ser = Series([1, 1, 2, 3], dtype="Sparse[int]")
+ return_value = ser.drop([0, 1], inplace=True)
+ assert return_value is None
+ assert ser.sparse.density == 1.0
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_str_accessor.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_str_accessor.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff530459b78fbc74da55c556fa58ba051573c713
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_str_accessor.py
@@ -0,0 +1,26 @@
+import pytest
+
+from pandas import Series
+import pandas._testing as tm
+
+
+class TestStrAccessor:
+ def test_str_attribute(self):
+ # GH#9068
+ methods = ["strip", "rstrip", "lstrip"]
+ ser = Series([" jack", "jill ", " jesse ", "frank"])
+ for method in methods:
+ expected = Series([getattr(str, method)(x) for x in ser.values])
+ tm.assert_series_equal(getattr(Series.str, method)(ser.str), expected)
+
+ # str accessor only valid with string values
+ ser = Series(range(5))
+ msg = "Can only use .str accessor with string values, not integer"
+ with pytest.raises(AttributeError, match=msg):
+ ser.str.repeat(2)
+
+ def test_str_accessor_updates_on_inplace(self):
+ ser = Series(list("abc"))
+ return_value = ser.drop([0], inplace=True)
+ assert return_value is None
+ assert len(ser.str.lower()) == 2
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_struct_accessor.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_struct_accessor.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0bf81621610a3290d68b62745828139614bf268
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/accessors/test_struct_accessor.py
@@ -0,0 +1,188 @@
+import re
+
+import pytest
+
+from pandas import (
+ ArrowDtype,
+ DataFrame,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+pa = pytest.importorskip("pyarrow")
+pc = pytest.importorskip("pyarrow.compute")
+
+
+def test_struct_accessor_dtypes():
+ ser = Series(
+ [],
+ dtype=ArrowDtype(
+ pa.struct(
+ [
+ ("int_col", pa.int64()),
+ ("string_col", pa.string()),
+ (
+ "struct_col",
+ pa.struct(
+ [
+ ("int_col", pa.int64()),
+ ("float_col", pa.float64()),
+ ]
+ ),
+ ),
+ ]
+ )
+ ),
+ )
+ actual = ser.struct.dtypes
+ expected = Series(
+ [
+ ArrowDtype(pa.int64()),
+ ArrowDtype(pa.string()),
+ ArrowDtype(
+ pa.struct(
+ [
+ ("int_col", pa.int64()),
+ ("float_col", pa.float64()),
+ ]
+ )
+ ),
+ ],
+ index=Index(["int_col", "string_col", "struct_col"]),
+ )
+ tm.assert_series_equal(actual, expected)
+
+
+def test_struct_accessor_field():
+ index = Index([-100, 42, 123])
+ ser = Series(
+ [
+ {"rice": 1.0, "maize": -1, "wheat": "a"},
+ {"rice": 2.0, "maize": 0, "wheat": "b"},
+ {"rice": 3.0, "maize": 1, "wheat": "c"},
+ ],
+ dtype=ArrowDtype(
+ pa.struct(
+ [
+ ("rice", pa.float64()),
+ ("maize", pa.int64()),
+ ("wheat", pa.string()),
+ ]
+ )
+ ),
+ index=index,
+ )
+ by_name = ser.struct.field("maize")
+ by_name_expected = Series(
+ [-1, 0, 1],
+ dtype=ArrowDtype(pa.int64()),
+ index=index,
+ name="maize",
+ )
+ tm.assert_series_equal(by_name, by_name_expected)
+
+ by_index = ser.struct.field(2)
+ by_index_expected = Series(
+ ["a", "b", "c"],
+ dtype=ArrowDtype(pa.string()),
+ index=index,
+ name="wheat",
+ )
+ tm.assert_series_equal(by_index, by_index_expected)
+
+
+def test_struct_accessor_field_with_invalid_name_or_index():
+ ser = Series([], dtype=ArrowDtype(pa.struct([("field", pa.int64())])))
+
+ with pytest.raises(ValueError, match="name_or_index must be an int, str,"):
+ ser.struct.field(1.1)
+
+
+def test_struct_accessor_explode():
+ index = Index([-100, 42, 123])
+ ser = Series(
+ [
+ {"painted": 1, "snapping": {"sea": "green"}},
+ {"painted": 2, "snapping": {"sea": "leatherback"}},
+ {"painted": 3, "snapping": {"sea": "hawksbill"}},
+ ],
+ dtype=ArrowDtype(
+ pa.struct(
+ [
+ ("painted", pa.int64()),
+ ("snapping", pa.struct([("sea", pa.string())])),
+ ]
+ )
+ ),
+ index=index,
+ )
+ actual = ser.struct.explode()
+ expected = DataFrame(
+ {
+ "painted": Series([1, 2, 3], index=index, dtype=ArrowDtype(pa.int64())),
+ "snapping": Series(
+ [{"sea": "green"}, {"sea": "leatherback"}, {"sea": "hawksbill"}],
+ index=index,
+ dtype=ArrowDtype(pa.struct([("sea", pa.string())])),
+ ),
+ },
+ )
+ tm.assert_frame_equal(actual, expected)
+
+
+@pytest.mark.parametrize(
+ "invalid",
+ [
+ pytest.param(Series([1, 2, 3], dtype="int64"), id="int64"),
+ pytest.param(
+ Series(["a", "b", "c"], dtype="string[pyarrow]"), id="string-pyarrow"
+ ),
+ ],
+)
+def test_struct_accessor_api_for_invalid(invalid):
+ with pytest.raises(
+ AttributeError,
+ match=re.escape(
+ "Can only use the '.struct' accessor with 'struct[pyarrow]' dtype, "
+ f"not {invalid.dtype}."
+ ),
+ ):
+ invalid.struct
+
+
+@pytest.mark.parametrize(
+ ["indices", "name"],
+ [
+ (0, "int_col"),
+ ([1, 2], "str_col"),
+ (pc.field("int_col"), "int_col"),
+ ("int_col", "int_col"),
+ (b"string_col", b"string_col"),
+ ([b"string_col"], "string_col"),
+ ],
+)
+def test_struct_accessor_field_expanded(indices, name):
+ arrow_type = pa.struct(
+ [
+ ("int_col", pa.int64()),
+ (
+ "struct_col",
+ pa.struct(
+ [
+ ("int_col", pa.int64()),
+ ("float_col", pa.float64()),
+ ("str_col", pa.string()),
+ ]
+ ),
+ ),
+ (b"string_col", pa.string()),
+ ]
+ )
+
+ data = pa.array([], type=arrow_type)
+ ser = Series(data, dtype=ArrowDtype(arrow_type))
+ expected = pc.struct_field(data, indices)
+ result = ser.struct.field(indices)
+ tm.assert_equal(result.array._pa_array.combine_chunks(), expected)
+ assert result.name == name
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_datetime.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d0ae973d6c662fc53aada8e8dd383af393eb7a8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_datetime.py
@@ -0,0 +1,493 @@
+"""
+Also test support for datetime64[ns] in Series / DataFrame
+"""
+
+from datetime import (
+ datetime,
+ timedelta,
+)
+import re
+
+from dateutil.tz import (
+ gettz,
+ tzutc,
+)
+import numpy as np
+import pytest
+
+from pandas._libs import index as libindex
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Series,
+ Timestamp,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+def test_fancy_getitem():
+ dti = date_range(
+ freq="WOM-1FRI", start=datetime(2005, 1, 1), end=datetime(2010, 1, 1)
+ )
+
+ s = Series(np.arange(len(dti)), index=dti)
+
+ assert s["1/2/2009"] == 48
+ assert s["2009-1-2"] == 48
+ assert s[datetime(2009, 1, 2)] == 48
+ assert s[Timestamp(datetime(2009, 1, 2))] == 48
+ with pytest.raises(KeyError, match=r"^'2009-1-3'$"):
+ s["2009-1-3"]
+ tm.assert_series_equal(
+ s["3/6/2009":"2009-06-05"], s[datetime(2009, 3, 6) : datetime(2009, 6, 5)]
+ )
+
+
+def test_fancy_setitem():
+ dti = date_range(
+ freq="WOM-1FRI", start=datetime(2005, 1, 1), end=datetime(2010, 1, 1)
+ )
+
+ s = Series(np.arange(len(dti)), index=dti)
+
+ s["1/2/2009"] = -2
+ assert s.iloc[48] == -2
+ s["1/2/2009":"2009-06-05"] = -3
+ assert (s[48:54] == -3).all()
+
+
+@pytest.mark.parametrize("tz_source", ["pytz", "dateutil"])
+def test_getitem_setitem_datetime_tz(tz_source):
+ if tz_source == "pytz":
+ pytz = pytest.importorskip(tz_source)
+ tzget = pytz.timezone
+ else:
+ # handle special case for utc in dateutil
+ tzget = lambda x: tzutc() if x == "UTC" else gettz(x)
+
+ N = 50
+ # testing with timezone, GH #2785
+ rng = date_range("1/1/1990", periods=N, freq="h", tz=tzget("US/Eastern"))
+ ts = Series(np.random.default_rng(2).standard_normal(N), index=rng)
+
+ # also test Timestamp tz handling, GH #2789
+ result = ts.copy()
+ result["1990-01-01 09:00:00+00:00"] = 0
+ result["1990-01-01 09:00:00+00:00"] = ts.iloc[4]
+ tm.assert_series_equal(result, ts)
+
+ result = ts.copy()
+ result["1990-01-01 03:00:00-06:00"] = 0
+ result["1990-01-01 03:00:00-06:00"] = ts.iloc[4]
+ tm.assert_series_equal(result, ts)
+
+ # repeat with datetimes
+ result = ts.copy()
+ result[datetime(1990, 1, 1, 9, tzinfo=tzget("UTC"))] = 0
+ result[datetime(1990, 1, 1, 9, tzinfo=tzget("UTC"))] = ts.iloc[4]
+ tm.assert_series_equal(result, ts)
+
+ result = ts.copy()
+ dt = Timestamp(1990, 1, 1, 3).tz_localize(tzget("US/Central"))
+ dt = dt.to_pydatetime()
+ result[dt] = 0
+ result[dt] = ts.iloc[4]
+ tm.assert_series_equal(result, ts)
+
+
+def test_getitem_setitem_datetimeindex():
+ N = 50
+ # testing with timezone, GH #2785
+ rng = date_range("1/1/1990", periods=N, freq="h", tz="US/Eastern", unit="ns")
+ ts = Series(np.random.default_rng(2).standard_normal(N), index=rng)
+
+ result = ts["1990-01-01 04:00:00"]
+ expected = ts.iloc[4]
+ assert result == expected
+
+ result = ts.copy()
+ result["1990-01-01 04:00:00"] = 0
+ result["1990-01-01 04:00:00"] = ts.iloc[4]
+ tm.assert_series_equal(result, ts)
+
+ result = ts["1990-01-01 04:00:00":"1990-01-01 07:00:00"]
+ expected = ts[4:8]
+ tm.assert_series_equal(result, expected)
+
+ result = ts.copy()
+ result["1990-01-01 04:00:00":"1990-01-01 07:00:00"] = 0
+ result["1990-01-01 04:00:00":"1990-01-01 07:00:00"] = ts[4:8]
+ tm.assert_series_equal(result, ts)
+
+ lb = "1990-01-01 04:00:00"
+ rb = "1990-01-01 07:00:00"
+ # GH#18435 strings get a pass from tzawareness compat
+ result = ts[(ts.index >= lb) & (ts.index <= rb)]
+ expected = ts[4:8]
+ tm.assert_series_equal(result, expected)
+
+ lb = "1990-01-01 04:00:00-0500"
+ rb = "1990-01-01 07:00:00-0500"
+ result = ts[(ts.index >= lb) & (ts.index <= rb)]
+ expected = ts[4:8]
+ tm.assert_series_equal(result, expected)
+
+ # But we do not give datetimes a pass on tzawareness compat
+ msg = "Cannot compare tz-naive and tz-aware datetime-like objects"
+ naive = datetime(1990, 1, 1, 4)
+ for key in [naive, Timestamp(naive), np.datetime64(naive, "ns")]:
+ with pytest.raises(KeyError, match=re.escape(repr(key))):
+ # GH#36148 as of 2.0 we require tzawareness-compat
+ ts[key]
+
+ result = ts.copy()
+ # GH#36148 as of 2.0 we do not ignore tzawareness mismatch in indexing,
+ # so setting it as a new key casts to object rather than matching
+ # rng[4]
+ result[naive] = ts.iloc[4]
+ assert result.index.dtype == object
+ tm.assert_index_equal(result.index[:-1], rng.astype(object))
+ assert result.index[-1] == naive
+
+ msg = "Cannot compare tz-naive and tz-aware datetime-like objects"
+ with pytest.raises(TypeError, match=msg):
+ # GH#36148 require tzawareness compat as of 2.0
+ ts[naive : datetime(1990, 1, 1, 7)]
+
+ result = ts.copy()
+ with pytest.raises(TypeError, match=msg):
+ # GH#36148 require tzawareness compat as of 2.0
+ result[naive : datetime(1990, 1, 1, 7)] = 0
+ with pytest.raises(TypeError, match=msg):
+ # GH#36148 require tzawareness compat as of 2.0
+ result[naive : datetime(1990, 1, 1, 7)] = 99
+ # the __setitems__ here failed, so result should still match ts
+ tm.assert_series_equal(result, ts)
+
+ lb = naive
+ rb = datetime(1990, 1, 1, 7)
+ msg = r"Invalid comparison between dtype=datetime64\[ns, US/Eastern\] and datetime"
+ with pytest.raises(TypeError, match=msg):
+ # tznaive vs tzaware comparison is invalid
+ # see GH#18376, GH#18162
+ ts[(ts.index >= lb) & (ts.index <= rb)]
+
+ lb = Timestamp(naive).tz_localize(rng.tzinfo)
+ rb = Timestamp(datetime(1990, 1, 1, 7)).tz_localize(rng.tzinfo)
+ result = ts[(ts.index >= lb) & (ts.index <= rb)]
+ expected = ts[4:8]
+ tm.assert_series_equal(result, expected)
+
+ result = ts[ts.index[4]]
+ expected = ts.iloc[4]
+ assert result == expected
+
+ result = ts[ts.index[4:8]]
+ expected = ts[4:8]
+ tm.assert_series_equal(result, expected)
+
+ result = ts.copy()
+ result[ts.index[4:8]] = 0
+ result.iloc[4:8] = ts.iloc[4:8]
+ tm.assert_series_equal(result, ts)
+
+ # also test partial date slicing
+ result = ts["1990-01-02"]
+ expected = ts[24:48]
+ tm.assert_series_equal(result, expected)
+
+ result = ts.copy()
+ result["1990-01-02"] = 0
+ result["1990-01-02"] = ts[24:48]
+ tm.assert_series_equal(result, ts)
+
+
+def test_getitem_setitem_periodindex():
+ N = 50
+ rng = period_range("1/1/1990", periods=N, freq="h")
+ ts = Series(np.random.default_rng(2).standard_normal(N), index=rng)
+
+ result = ts["1990-01-01 04"]
+ expected = ts.iloc[4]
+ assert result == expected
+
+ result = ts.copy()
+ result["1990-01-01 04"] = 0
+ result["1990-01-01 04"] = ts.iloc[4]
+ tm.assert_series_equal(result, ts)
+
+ result = ts["1990-01-01 04":"1990-01-01 07"]
+ expected = ts[4:8]
+ tm.assert_series_equal(result, expected)
+
+ result = ts.copy()
+ result["1990-01-01 04":"1990-01-01 07"] = 0
+ result["1990-01-01 04":"1990-01-01 07"] = ts[4:8]
+ tm.assert_series_equal(result, ts)
+
+ lb = "1990-01-01 04"
+ rb = "1990-01-01 07"
+ result = ts[(ts.index >= lb) & (ts.index <= rb)]
+ expected = ts[4:8]
+ tm.assert_series_equal(result, expected)
+
+ # GH 2782
+ result = ts[ts.index[4]]
+ expected = ts.iloc[4]
+ assert result == expected
+
+ result = ts[ts.index[4:8]]
+ expected = ts[4:8]
+ tm.assert_series_equal(result, expected)
+
+ result = ts.copy()
+ result[ts.index[4:8]] = 0
+ result.iloc[4:8] = ts.iloc[4:8]
+ tm.assert_series_equal(result, ts)
+
+
+def test_datetime_indexing():
+ index = date_range("1/1/2000", "1/7/2000")
+ index = index.repeat(3)
+
+ s = Series(len(index), index=index)
+ stamp = Timestamp("1/8/2000")
+
+ with pytest.raises(KeyError, match=re.escape(repr(stamp))):
+ s[stamp]
+ s[stamp] = 0
+ assert s[stamp] == 0
+
+ # not monotonic
+ s = Series(len(index), index=index)
+ s = s[::-1]
+
+ with pytest.raises(KeyError, match=re.escape(repr(stamp))):
+ s[stamp]
+ s[stamp] = 0
+ assert s[stamp] == 0
+
+
+# test duplicates in time series
+
+
+def test_indexing_with_duplicate_datetimeindex(
+ rand_series_with_duplicate_datetimeindex,
+):
+ ts = rand_series_with_duplicate_datetimeindex
+
+ uniques = ts.index.unique()
+ for date in uniques:
+ result = ts[date]
+
+ mask = ts.index == date
+ total = (ts.index == date).sum()
+ expected = ts[mask]
+ if total > 1:
+ tm.assert_series_equal(result, expected)
+ else:
+ tm.assert_almost_equal(result, expected.iloc[0])
+
+ cp = ts.copy()
+ cp[date] = 0
+ expected = Series(np.where(mask, 0, ts), index=ts.index)
+ tm.assert_series_equal(cp, expected)
+
+ key = datetime(2000, 1, 6)
+ with pytest.raises(KeyError, match=re.escape(repr(key))):
+ ts[key]
+
+ # new index
+ ts[datetime(2000, 1, 6)] = 0
+ assert ts[datetime(2000, 1, 6)] == 0
+
+
+def test_loc_getitem_over_size_cutoff(monkeypatch):
+ # #1821
+
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", 1000)
+
+ # create large list of non periodic datetime
+ dates = []
+ sec = timedelta(seconds=1)
+ half_sec = timedelta(microseconds=500000)
+ d = datetime(2011, 12, 5, 20, 30)
+ n = 1100
+ for i in range(n):
+ dates.append(d)
+ dates.append(d + sec)
+ dates.append(d + sec + half_sec)
+ dates.append(d + sec + sec + half_sec)
+ d += 3 * sec
+
+ # duplicate some values in the list
+ duplicate_positions = np.random.default_rng(2).integers(0, len(dates) - 1, 20)
+ for p in duplicate_positions:
+ dates[p + 1] = dates[p]
+
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((len(dates), 4)),
+ index=dates,
+ columns=list("ABCD"),
+ )
+
+ pos = n * 3
+ timestamp = df.index[pos]
+ assert timestamp in df.index
+
+ # it works!
+ df.loc[timestamp]
+ assert len(df.loc[[timestamp]]) > 0
+
+
+def test_indexing_over_size_cutoff_period_index(monkeypatch):
+ # GH 27136
+
+ monkeypatch.setattr(libindex, "_SIZE_CUTOFF", 1000)
+
+ n = 1100
+ idx = period_range("1/1/2000", freq="min", periods=n)
+ assert idx._engine.over_size_threshold
+
+ s = Series(np.random.default_rng(2).standard_normal(len(idx)), index=idx)
+
+ pos = n - 1
+ timestamp = idx[pos]
+ assert timestamp in s.index
+
+ # it works!
+ s[timestamp]
+ assert len(s.loc[[timestamp]]) > 0
+
+
+def test_indexing_unordered():
+ # GH 2437
+ rng = date_range(start="2011-01-01", end="2011-01-15")
+ ts = Series(np.random.default_rng(2).random(len(rng)), index=rng)
+ ts2 = pd.concat([ts[0:4], ts[-4:], ts[4:-4]])
+
+ for t in ts.index:
+ expected = ts[t]
+ result = ts2[t]
+ assert expected == result
+
+ # GH 3448 (ranges)
+ def compare(slobj):
+ result = ts2[slobj].copy()
+ result = result.sort_index()
+ expected = ts[slobj]
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+
+ for key in [
+ slice("2011-01-01", "2011-01-15"),
+ slice("2010-12-30", "2011-01-15"),
+ slice("2011-01-01", "2011-01-16"),
+ # partial ranges
+ slice("2011-01-01", "2011-01-6"),
+ slice("2011-01-06", "2011-01-8"),
+ slice("2011-01-06", "2011-01-12"),
+ ]:
+ with pytest.raises(
+ KeyError, match="Value based partial slicing on non-monotonic"
+ ):
+ compare(key)
+
+ # single values
+ result = ts2["2011"].sort_index()
+ expected = ts["2011"]
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+
+
+def test_indexing_unordered2():
+ # diff freq
+ rng = date_range(datetime(2005, 1, 1), periods=20, freq="ME")
+ ts = Series(np.arange(len(rng)), index=rng)
+ ts = ts.take(np.random.default_rng(2).permutation(20))
+
+ result = ts["2005"]
+ for t in result.index:
+ assert t.year == 2005
+
+
+def test_indexing():
+ idx = date_range("2001-1-1", periods=20, freq="ME")
+ ts = Series(np.random.default_rng(2).random(len(idx)), index=idx)
+
+ # getting
+
+ # GH 3070, make sure semantics work on Series/Frame
+ result = ts["2001"]
+ tm.assert_series_equal(result, ts.iloc[:12])
+
+ df = DataFrame({"A": ts})
+
+ # GH#36179 pre-2.0 df["2001"] operated as slicing on rows. in 2.0 it behaves
+ # like any other key, so raises
+ with pytest.raises(KeyError, match="2001"):
+ df["2001"]
+
+ # setting
+ ts = Series(np.random.default_rng(2).random(len(idx)), index=idx)
+ expected = ts.copy()
+ expected.iloc[:12] = 1
+ ts["2001"] = 1
+ tm.assert_series_equal(ts, expected)
+
+ expected = df.copy()
+ expected.iloc[:12, 0] = 1
+ df.loc["2001", "A"] = 1
+ tm.assert_frame_equal(df, expected)
+
+
+def test_getitem_str_month_with_datetimeindex():
+ # GH3546 (not including times on the last day)
+ idx = date_range(start="2013-05-31 00:00", end="2013-05-31 23:00", freq="h")
+ ts = Series(range(len(idx)), index=idx)
+ expected = ts["2013-05"]
+ tm.assert_series_equal(expected, ts)
+
+ idx = date_range(start="2013-05-31 00:00", end="2013-05-31 23:59", freq="s")
+ ts = Series(range(len(idx)), index=idx)
+ expected = ts["2013-05"]
+ tm.assert_series_equal(expected, ts)
+
+
+def test_getitem_str_year_with_datetimeindex():
+ idx = [
+ Timestamp("2013-05-31 00:00"),
+ Timestamp(datetime(2013, 5, 31, 23, 59, 59, 999999)),
+ ]
+ ts = Series(range(len(idx)), index=idx)
+ expected = ts["2013"]
+ tm.assert_series_equal(expected, ts)
+
+
+def test_getitem_str_second_with_datetimeindex():
+ # GH14826, indexing with a seconds resolution string / datetime object
+ df = DataFrame(
+ np.random.default_rng(2).random((5, 5)),
+ columns=["open", "high", "low", "close", "volume"],
+ index=date_range("2012-01-02 18:01:00", periods=5, tz="US/Central", freq="s"),
+ )
+
+ # this is a single date, so will raise
+ with pytest.raises(KeyError, match=r"^'2012-01-02 18:01:02'$"):
+ df["2012-01-02 18:01:02"]
+
+ msg = r"Timestamp\('2012-01-02 18:01:02-0600', tz='US/Central'\)"
+ with pytest.raises(KeyError, match=msg):
+ df[df.index[2]]
+
+
+def test_compare_datetime_with_all_none():
+ # GH#54870
+ ser = Series(["2020-01-01", "2020-01-02"], dtype="datetime64[ns]")
+ ser2 = Series([None, None])
+ result = ser > ser2
+ expected = Series([False, False])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_delitem.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_delitem.py
new file mode 100644
index 0000000000000000000000000000000000000000..7440ef2692c47fbce129f06276a8e8761a23cf2b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_delitem.py
@@ -0,0 +1,69 @@
+import pytest
+
+from pandas import (
+ Index,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestSeriesDelItem:
+ def test_delitem(self):
+ # GH#5542
+ # should delete the item inplace
+ s = Series(range(5))
+ del s[0]
+
+ expected = Series(range(1, 5), index=range(1, 5))
+ tm.assert_series_equal(s, expected)
+
+ del s[1]
+ expected = Series(range(2, 5), index=range(2, 5))
+ tm.assert_series_equal(s, expected)
+
+ # only 1 left, del, add, del
+ s = Series(1)
+ del s[0]
+ tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="int64")))
+ s[0] = 1
+ tm.assert_series_equal(s, Series(1))
+ del s[0]
+ tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="int64")))
+
+ def test_delitem_object_index(self):
+ # Index(dtype=object)
+ s = Series(1, index=Index(["a"], dtype="str"))
+ del s["a"]
+ tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="str")))
+ s["a"] = 1
+ tm.assert_series_equal(s, Series(1, index=Index(["a"], dtype="str")))
+ del s["a"]
+ tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="str")))
+
+ def test_delitem_missing_key(self):
+ # empty
+ s = Series(dtype=object)
+
+ with pytest.raises(KeyError, match=r"^0$"):
+ del s[0]
+
+ def test_delitem_extension_dtype(self):
+ # GH#40386
+ # DatetimeTZDtype
+ dti = date_range("2016-01-01", periods=3, tz="US/Pacific")
+ ser = Series(dti)
+
+ expected = ser[[0, 2]]
+ del ser[1]
+ assert ser.dtype == dti.dtype
+ tm.assert_series_equal(ser, expected)
+
+ # PeriodDtype
+ pi = dti.tz_localize(None).to_period("D")
+ ser = Series(pi)
+
+ expected = ser[:2]
+ del ser[2]
+ assert ser.dtype == pi.dtype
+ tm.assert_series_equal(ser, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_get.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_get.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ff92ca89efba514e1b0a795f1faa66934248132
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_get.py
@@ -0,0 +1,228 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DatetimeIndex,
+ Index,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def test_get():
+ # GH 6383
+ s = Series(
+ np.array(
+ [
+ 43,
+ 48,
+ 60,
+ 48,
+ 50,
+ 51,
+ 50,
+ 45,
+ 57,
+ 48,
+ 56,
+ 45,
+ 51,
+ 39,
+ 55,
+ 43,
+ 54,
+ 52,
+ 51,
+ 54,
+ ]
+ )
+ )
+
+ result = s.get(25, 0)
+ expected = 0
+ assert result == expected
+
+ s = Series(
+ np.array(
+ [
+ 43,
+ 48,
+ 60,
+ 48,
+ 50,
+ 51,
+ 50,
+ 45,
+ 57,
+ 48,
+ 56,
+ 45,
+ 51,
+ 39,
+ 55,
+ 43,
+ 54,
+ 52,
+ 51,
+ 54,
+ ]
+ ),
+ index=Index(
+ [
+ 25.0,
+ 36.0,
+ 49.0,
+ 64.0,
+ 81.0,
+ 100.0,
+ 121.0,
+ 144.0,
+ 169.0,
+ 196.0,
+ 1225.0,
+ 1296.0,
+ 1369.0,
+ 1444.0,
+ 1521.0,
+ 1600.0,
+ 1681.0,
+ 1764.0,
+ 1849.0,
+ 1936.0,
+ ],
+ dtype=np.float64,
+ ),
+ )
+
+ result = s.get(25, 0)
+ expected = 43
+ assert result == expected
+
+ # GH 7407
+ # with a boolean accessor
+ df = pd.DataFrame({"i": [0] * 3, "b": [False] * 3})
+ vc = df.i.value_counts()
+ result = vc.get(99, default="Missing")
+ assert result == "Missing"
+
+ vc = df.b.value_counts()
+ result = vc.get(False, default="Missing")
+ assert result == 3
+
+ result = vc.get(True, default="Missing")
+ assert result == "Missing"
+
+
+def test_get_nan(float_numpy_dtype):
+ # GH 8569
+ s = Index(range(10), dtype=float_numpy_dtype).to_series()
+ assert s.get(np.nan) is None
+ assert s.get(np.nan, default="Missing") == "Missing"
+
+
+def test_get_nan_multiple(float_numpy_dtype):
+ # GH 8569
+ # ensure that fixing "test_get_nan" above hasn't broken get
+ # with multiple elements
+ s = Index(range(10), dtype=float_numpy_dtype).to_series()
+
+ idx = [2, 30]
+ assert s.get(idx) is None
+
+ idx = [2, np.nan]
+ assert s.get(idx) is None
+
+ # GH 17295 - all missing keys
+ idx = [20, 30]
+ assert s.get(idx) is None
+
+ idx = [np.nan, np.nan]
+ assert s.get(idx) is None
+
+
+def test_get_with_default():
+ # GH#7725
+ d0 = ["a", "b", "c", "d"]
+ d1 = np.arange(4, dtype="int64")
+
+ for data, index in ((d0, d1), (d1, d0)):
+ s = Series(data, index=index)
+ for i, d in zip(index, data):
+ assert s.get(i) == d
+ assert s.get(i, d) == d
+ assert s.get(i, "z") == d
+
+ assert s.get("e", "z") == "z"
+ assert s.get("e", "e") == "e"
+
+ assert s.get(10, "z") == "z"
+ assert s.get(10, 10) == 10
+
+
+@pytest.mark.parametrize(
+ "arr",
+ [
+ np.random.default_rng(2).standard_normal(10),
+ DatetimeIndex(date_range("2020-01-01", periods=10), name="a").tz_localize(
+ tz="US/Eastern"
+ ),
+ ],
+)
+def test_get_with_ea(arr):
+ # GH#21260
+ ser = Series(arr, index=[2 * i for i in range(len(arr))])
+ assert ser.get(4) == ser.iloc[2]
+
+ result = ser.get([4, 6])
+ expected = ser.iloc[[2, 3]]
+ tm.assert_series_equal(result, expected)
+
+ result = ser.get(slice(2))
+ expected = ser.iloc[[0, 1]]
+ tm.assert_series_equal(result, expected)
+
+ assert ser.get(-1) is None
+ assert ser.get(ser.index.max() + 1) is None
+
+ ser = Series(arr[:6], index=list("abcdef"))
+ assert ser.get("c") == ser.iloc[2]
+
+ result = ser.get(slice("b", "d"))
+ expected = ser.iloc[[1, 2, 3]]
+ tm.assert_series_equal(result, expected)
+
+ result = ser.get("Z")
+ assert result is None
+
+ # As of 3.0, ints are treated as labels
+ assert ser.get(4) is None
+ assert ser.get(-1) is None
+ assert ser.get(len(ser)) is None
+
+ # GH#21257
+ ser = Series(arr)
+ ser2 = ser[::2]
+ assert ser2.get(1) is None
+
+
+def test_getitem_get(string_series, object_series):
+ for obj in [string_series, object_series]:
+ idx = obj.index[5]
+
+ assert obj[idx] == obj.get(idx)
+ assert obj[idx] == obj.iloc[5]
+
+ # As of 3.0, ints are treated as labels
+ assert string_series.get(-1) is None
+ assert string_series.iloc[5] == string_series.get(string_series.index[5])
+
+
+def test_get_none():
+ # GH#5652
+ s1 = Series(dtype=object)
+ s2 = Series(dtype=object, index=list("abc"))
+ for s in [s1, s2]:
+ result = s.get(None)
+ assert result is None
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_getitem.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_getitem.py
new file mode 100644
index 0000000000000000000000000000000000000000..37d6c9b42e0037c98f4df2c7931b6f65957660fc
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_getitem.py
@@ -0,0 +1,721 @@
+"""
+Series.__getitem__ test classes are organized by the type of key passed.
+"""
+
+from datetime import (
+ date,
+ datetime,
+ time,
+)
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import (
+ conversion,
+ timezones,
+)
+from pandas.compat.numpy import np_version_gt2
+from pandas.errors import Pandas4Warning
+
+from pandas.core.dtypes.common import is_scalar
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ Series,
+ Timestamp,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.indexing import IndexingError
+
+from pandas.tseries.offsets import BDay
+
+
+class TestSeriesGetitemScalars:
+ def test_getitem_object_index_float_string(self):
+ # GH#17286
+ ser = Series([1] * 4, index=Index(["a", "b", "c", 1.0]))
+ assert ser["a"] == 1
+ assert ser[1.0] == 1
+
+ def test_getitem_float_keys_tuple_values(self):
+ # see GH#13509
+
+ # unique Index
+ ser = Series([(1, 1), (2, 2), (3, 3)], index=[0.0, 0.1, 0.2], name="foo")
+ result = ser[0.0]
+ assert result == (1, 1)
+
+ # non-unique Index
+ expected = Series([(1, 1), (2, 2)], index=[0.0, 0.0], name="foo")
+ ser = Series([(1, 1), (2, 2), (3, 3)], index=[0.0, 0.0, 0.2], name="foo")
+
+ result = ser[0.0]
+ tm.assert_series_equal(result, expected)
+
+ def test_getitem_unrecognized_scalar(self):
+ # GH#32684 a scalar key that is not recognized by lib.is_scalar
+
+ # a series that might be produced via `frame.dtypes`
+ ser = Series([1, 2], index=[np.dtype("O"), np.dtype("i8")])
+
+ key = ser.index[1]
+
+ result = ser[key]
+ assert result == 2
+
+ def test_getitem_negative_out_of_bounds(self):
+ ser = Series(["a"] * 10, index=["a"] * 10)
+
+ with pytest.raises(KeyError, match="^-11$"):
+ ser[-11]
+
+ def test_getitem_out_of_bounds_indexerror(self, datetime_series):
+ # don't segfault, GH#495
+ N = len(datetime_series)
+ with pytest.raises(KeyError, match=str(N)):
+ datetime_series[N]
+
+ def test_getitem_out_of_bounds_empty_rangeindex_keyerror(self):
+ # GH#917
+ # With a RangeIndex, an int key gives a KeyError
+ ser = Series([], dtype=object)
+ with pytest.raises(KeyError, match="-1"):
+ ser[-1]
+
+ def test_getitem_keyerror_with_integer_index(self, any_int_numpy_dtype):
+ dtype = any_int_numpy_dtype
+ ser = Series(
+ np.random.default_rng(2).standard_normal(6),
+ index=Index([0, 0, 1, 1, 2, 2], dtype=dtype),
+ )
+
+ with pytest.raises(KeyError, match=r"^5$"):
+ ser[5]
+
+ with pytest.raises(KeyError, match=r"^'c'$"):
+ ser["c"]
+
+ # not monotonic
+ ser = Series(
+ np.random.default_rng(2).standard_normal(6), index=[2, 2, 0, 0, 1, 1]
+ )
+
+ with pytest.raises(KeyError, match=r"^5$"):
+ ser[5]
+
+ with pytest.raises(KeyError, match=r"^'c'$"):
+ ser["c"]
+
+ def test_getitem_int64(self, datetime_series):
+ if np_version_gt2:
+ msg = r"^np.int64\(5\)$"
+ else:
+ msg = "^5$"
+ idx = np.int64(5)
+ with pytest.raises(KeyError, match=msg):
+ datetime_series[idx]
+
+ def test_getitem_full_range(self):
+ # github.com/pandas-dev/pandas/commit/4f433773141d2eb384325714a2776bcc5b2e20f7
+ ser = Series(range(5), index=list(range(5)))
+ result = ser[list(range(5))]
+ tm.assert_series_equal(result, ser)
+
+ # ------------------------------------------------------------------
+ # Series with DatetimeIndex
+
+ @pytest.mark.parametrize("tzstr", ["Europe/Berlin", "dateutil/Europe/Berlin"])
+ def test_getitem_pydatetime_tz(self, tzstr):
+ tz = timezones.maybe_get_tz(tzstr)
+
+ index = date_range(
+ start="2012-12-24 16:00", end="2012-12-24 18:00", freq="h", tz=tzstr
+ )
+ ts = Series(index=index, data=index.hour)
+ time_pandas = Timestamp("2012-12-24 17:00", tz=tzstr)
+
+ dt = datetime(2012, 12, 24, 17, 0)
+ time_datetime = conversion.localize_pydatetime(dt, tz)
+ assert ts[time_pandas] == ts[time_datetime]
+
+ @pytest.mark.parametrize("tz", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_string_index_alias_tz_aware(self, tz):
+ rng = date_range("1/1/2000", periods=10, tz=tz)
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ result = ser["1/3/2000"]
+ tm.assert_almost_equal(result, ser.iloc[2])
+
+ def test_getitem_time_object(self):
+ rng = date_range("1/1/2000", "1/5/2000", freq="5min")
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ mask = (rng.hour == 9) & (rng.minute == 30)
+ result = ts[time(9, 30)]
+ expected = ts[mask]
+ result.index = result.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+
+ # ------------------------------------------------------------------
+ # Series with CategoricalIndex
+
+ def test_getitem_scalar_categorical_index(self):
+ cats = Categorical([Timestamp("12-31-1999"), Timestamp("12-31-2000")])
+
+ ser = Series([1, 2], index=cats)
+
+ expected = ser.iloc[0]
+ result = ser[cats[0]]
+ assert result == expected
+
+ def test_getitem_numeric_categorical_listlike_matches_scalar(self):
+ # GH#15470
+ ser = Series(["a", "b", "c"], index=pd.CategoricalIndex([2, 1, 0]))
+
+ # 0 is treated as a label
+ assert ser[0] == "c"
+
+ # the listlike analogue should also be treated as labels
+ res = ser[[0]]
+ expected = ser.iloc[-1:]
+ tm.assert_series_equal(res, expected)
+
+ res2 = ser[[0, 1, 2]]
+ tm.assert_series_equal(res2, ser.iloc[::-1])
+
+ def test_getitem_integer_categorical_not_positional(self):
+ # GH#14865
+ ser = Series(["a", "b", "c"], index=Index([1, 2, 3], dtype="category"))
+ assert ser.get(3) == "c"
+ assert ser[3] == "c"
+
+ def test_getitem_str_with_timedeltaindex(self):
+ rng = timedelta_range("1 day 10:11:12", freq="h", periods=500)
+ ser = Series(np.arange(len(rng)), index=rng)
+
+ key = "6 days, 23:11:12"
+ indexer = rng.get_loc(key)
+ assert indexer == 133
+
+ result = ser[key]
+ assert result == ser.iloc[133]
+
+ msg = r"^Timedelta\('50 days 00:00:00'\)$"
+ with pytest.raises(KeyError, match=msg):
+ rng.get_loc("50 days")
+ with pytest.raises(KeyError, match=msg):
+ ser["50 days"]
+
+ def test_getitem_bool_index_positional(self):
+ # GH#48653
+ ser = Series({True: 1, False: 0})
+ with pytest.raises(KeyError, match="^0$"):
+ ser[0]
+
+
+class TestSeriesGetitemSlices:
+ def test_getitem_partial_str_slice_with_datetimeindex(self):
+ # GH#34860
+ arr = date_range("1/1/2008", "1/1/2009")
+ ser = arr.to_series()
+ result = ser["2008"]
+
+ rng = date_range(start="2008-01-01", end="2008-12-31")
+ expected = Series(rng, index=rng)
+
+ tm.assert_series_equal(result, expected)
+
+ def test_getitem_slice_strings_with_datetimeindex(self):
+ idx = DatetimeIndex(
+ ["1/1/2000", "1/2/2000", "1/2/2000", "1/3/2000", "1/4/2000"]
+ )
+
+ ts = Series(np.random.default_rng(2).standard_normal(len(idx)), index=idx)
+
+ result = ts["1/2/2000":]
+ expected = ts[1:]
+ tm.assert_series_equal(result, expected)
+
+ result = ts["1/2/2000":"1/3/2000"]
+ expected = ts[1:4]
+ tm.assert_series_equal(result, expected)
+
+ def test_getitem_partial_str_slice_with_timedeltaindex(self):
+ rng = timedelta_range("1 day 10:11:12", freq="h", periods=500)
+ ser = Series(np.arange(len(rng)), index=rng)
+
+ result = ser["5 day":"6 day"]
+ expected = ser.iloc[86:134]
+ tm.assert_series_equal(result, expected)
+
+ result = ser["5 day":]
+ expected = ser.iloc[86:]
+ tm.assert_series_equal(result, expected)
+
+ result = ser[:"6 day"]
+ expected = ser.iloc[:134]
+ tm.assert_series_equal(result, expected)
+
+ def test_getitem_partial_str_slice_high_reso_with_timedeltaindex(self):
+ # higher reso
+ rng = timedelta_range("1 day 10:11:12", freq="us", periods=2000)
+ ser = Series(np.arange(len(rng)), index=rng)
+
+ result = ser["1 day 10:11:12":]
+ expected = ser.iloc[0:]
+ tm.assert_series_equal(result, expected)
+
+ result = ser["1 day 10:11:12.001":]
+ expected = ser.iloc[1000:]
+ tm.assert_series_equal(result, expected)
+
+ result = ser["1 days, 10:11:12.001001"]
+ assert result == ser.iloc[1001]
+
+ def test_getitem_slice_2d(self, datetime_series):
+ # GH#30588 multi-dimensional indexing deprecated
+ with pytest.raises(ValueError, match="Multi-dimensional indexing"):
+ datetime_series[:, np.newaxis]
+
+ def test_getitem_median_slice_bug(self):
+ index = date_range("20090415", "20090519", freq="2B")
+ ser = Series(np.random.default_rng(2).standard_normal(13), index=index)
+
+ indexer = [slice(6, 7, None)]
+ msg = "Indexing with a single-item list"
+ with pytest.raises(ValueError, match=msg):
+ # GH#31299
+ ser[indexer]
+ # but we're OK with a single-element tuple
+ result = ser[(indexer[0],)]
+ expected = ser[indexer[0]]
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "slc, positions",
+ [
+ [slice(date(2018, 1, 1), None), [0, 1, 2]],
+ [slice(date(2019, 1, 2), None), [2]],
+ [slice(date(2020, 1, 1), None), []],
+ [slice(None, date(2020, 1, 1)), [0, 1, 2]],
+ [slice(None, date(2019, 1, 1)), [0]],
+ ],
+ )
+ def test_getitem_slice_date(self, slc, positions):
+ # https://github.com/pandas-dev/pandas/issues/31501
+ ser = Series(
+ [0, 1, 2],
+ DatetimeIndex(["2019-01-01", "2019-01-01T06:00:00", "2019-01-02"]),
+ )
+ msg = "Slicing with a datetime.date object is deprecated"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = ser[slc]
+ expected = ser.take(positions)
+ tm.assert_series_equal(result, expected)
+
+ def test_getitem_slice_float_raises(self, datetime_series):
+ msg = (
+ "cannot do slice indexing on DatetimeIndex with these indexers "
+ r"\[{key}\] of type float"
+ )
+ with pytest.raises(TypeError, match=msg.format(key=r"4\.0")):
+ datetime_series[4.0:10.0]
+
+ with pytest.raises(TypeError, match=msg.format(key=r"4\.5")):
+ datetime_series[4.5:10.0]
+
+ def test_getitem_slice_bug(self):
+ ser = Series(range(10), index=list(range(10)))
+ result = ser[-12:]
+ tm.assert_series_equal(result, ser)
+
+ result = ser[-7:]
+ tm.assert_series_equal(result, ser[3:])
+
+ result = ser[:-12]
+ tm.assert_series_equal(result, ser[:0])
+
+ def test_getitem_slice_integers(self):
+ ser = Series(
+ np.random.default_rng(2).standard_normal(8),
+ index=[2, 4, 6, 8, 10, 12, 14, 16],
+ )
+
+ result = ser[:4]
+ expected = Series(ser.values[:4], index=[2, 4, 6, 8])
+ tm.assert_series_equal(result, expected)
+
+
+class TestSeriesGetitemListLike:
+ @pytest.mark.parametrize("box", [list, np.array, Index, Series])
+ def test_getitem_no_matches(self, box):
+ # GH#33462 we expect the same behavior for list/ndarray/Index/Series
+ ser = Series(["A", "B"])
+
+ key = Series(["C"], dtype=object)
+ key = box(key)
+
+ msg = r"None of \[Index\(\['C'\], dtype='object|str'\)\] are in the \[index\]"
+ with pytest.raises(KeyError, match=msg):
+ ser[key]
+
+ def test_getitem_intlist_intindex_periodvalues(self):
+ ser = Series(period_range("2000-01-01", periods=10, freq="D"))
+
+ result = ser[[2, 4]]
+ exp = Series(
+ [pd.Period("2000-01-03", freq="D"), pd.Period("2000-01-05", freq="D")],
+ index=[2, 4],
+ dtype="Period[D]",
+ )
+ tm.assert_series_equal(result, exp)
+ assert result.dtype == "Period[D]"
+
+ @pytest.mark.parametrize("box", [list, np.array, Index])
+ def test_getitem_intlist_intervalindex_non_int(self, box):
+ # GH#33404 fall back to positional since ints are unambiguous;
+ # changed in 3.0 to never fallback
+ dti = date_range("2000-01-03", periods=3)._with_freq(None)
+ ii = pd.IntervalIndex.from_breaks(dti)
+ ser = Series(range(len(ii)), index=ii)
+
+ key = box([0])
+ msg = r"None of \[Index\(\[0\], dtype='int(32|64)'\)\] are in the \[index\]"
+ with pytest.raises(KeyError, match=msg):
+ ser[key]
+
+ @pytest.mark.parametrize("box", [list, np.array, Index])
+ @pytest.mark.parametrize("dtype", [np.int64, np.float64, np.uint64])
+ def test_getitem_intlist_multiindex_numeric_level(self, dtype, box):
+ # GH#33404 do _not_ fall back to positional since ints are ambiguous
+ idx = Index(range(4)).astype(dtype)
+ dti = date_range("2000-01-03", periods=3)
+ mi = pd.MultiIndex.from_product([idx, dti])
+ ser = Series(range(len(mi))[::-1], index=mi)
+
+ key = box([5])
+ with pytest.raises(KeyError, match="5"):
+ ser[key]
+
+ def test_getitem_uint_array_key(self, any_unsigned_int_numpy_dtype):
+ # GH #37218
+ ser = Series([1, 2, 3])
+ key = np.array([4], dtype=any_unsigned_int_numpy_dtype)
+
+ with pytest.raises(KeyError, match="4"):
+ ser[key]
+ with pytest.raises(KeyError, match="4"):
+ ser.loc[key]
+
+
+class TestGetitemBooleanMask:
+ def test_getitem_boolean(self, string_series):
+ ser = string_series
+ mask = ser > ser.median()
+
+ # passing list is OK
+ result = ser[list(mask)]
+ expected = ser[mask]
+ tm.assert_series_equal(result, expected)
+ tm.assert_index_equal(result.index, ser.index[mask])
+
+ def test_getitem_boolean_empty(self):
+ ser = Series([], dtype=np.int64)
+ ser.index.name = "index_name"
+ ser = ser[ser.isna()]
+ assert ser.index.name == "index_name"
+ assert ser.dtype == np.int64
+
+ # GH#5877
+ # indexing with empty series
+ ser = Series(["A", "B"], dtype=object)
+ expected = Series(dtype=object, index=Index([], dtype="int64"))
+ result = ser[Series([], dtype=object)]
+ tm.assert_series_equal(result, expected)
+
+ # invalid because of the boolean indexer
+ # that's empty or not-aligned
+ msg = (
+ r"Unalignable boolean Series provided as indexer \(index of "
+ r"the boolean Series and of the indexed object do not match"
+ )
+ with pytest.raises(IndexingError, match=msg):
+ ser[Series([], dtype=bool)]
+
+ with pytest.raises(IndexingError, match=msg):
+ ser[Series([True], dtype=bool)]
+
+ def test_getitem_boolean_object(self, string_series):
+ # using column from DataFrame
+
+ ser = string_series
+ mask = ser > ser.median()
+ omask = mask.astype(object)
+
+ # getitem
+ result = ser[omask]
+ expected = ser[mask]
+ tm.assert_series_equal(result, expected)
+
+ # setitem
+ s2 = ser.copy()
+ cop = ser.copy()
+ cop[omask] = 5
+ s2[mask] = 5
+ tm.assert_series_equal(cop, s2)
+
+ # nans raise exception
+ omask[5:10] = np.nan
+ msg = "Cannot mask with non-boolean array containing NA / NaN values"
+ with pytest.raises(ValueError, match=msg):
+ ser[omask]
+ with pytest.raises(ValueError, match=msg):
+ ser[omask] = 5
+
+ def test_getitem_boolean_dt64_copies(self):
+ # GH#36210
+ dti = date_range("2016-01-01", periods=4, tz="US/Pacific")
+ key = np.array([True, True, False, False])
+
+ ser = Series(dti._data)
+
+ res = ser[key]
+ assert res._values._ndarray.base is None
+
+ # compare with numeric case for reference
+ ser2 = Series(range(4))
+ res2 = ser2[key]
+ assert res2._values.base is None
+
+ def test_getitem_boolean_corner(self, datetime_series):
+ ts = datetime_series
+ mask_shifted = ts.shift(1, freq=BDay()) > ts.median()
+
+ msg = (
+ r"Unalignable boolean Series provided as indexer \(index of "
+ r"the boolean Series and of the indexed object do not match"
+ )
+ with pytest.raises(IndexingError, match=msg):
+ ts[mask_shifted]
+
+ with pytest.raises(IndexingError, match=msg):
+ ts.loc[mask_shifted]
+
+ def test_getitem_boolean_different_order(self, string_series):
+ ordered = string_series.sort_values()
+
+ sel = string_series[ordered > 0]
+ exp = string_series[string_series > 0]
+ tm.assert_series_equal(sel, exp)
+
+ def test_getitem_boolean_contiguous_preserve_freq(self):
+ rng = date_range("1/1/2000", "3/1/2000", freq="B")
+
+ mask = np.zeros(len(rng), dtype=bool)
+ mask[10:20] = True
+
+ masked = rng[mask]
+ expected = rng[10:20]
+ assert expected.freq == rng.freq
+ tm.assert_index_equal(masked, expected)
+
+ mask[22] = True
+ masked = rng[mask]
+ assert masked.freq is None
+
+
+class TestGetitemCallable:
+ def test_getitem_callable(self):
+ # GH#12533
+ ser = Series(4, index=list("ABCD"))
+ result = ser[lambda x: "A"]
+ assert result == ser.loc["A"]
+
+ result = ser[lambda x: ["A", "B"]]
+ expected = ser.loc[["A", "B"]]
+ tm.assert_series_equal(result, expected)
+
+ result = ser[lambda x: [True, False, True, True]]
+ expected = ser.iloc[[0, 2, 3]]
+ tm.assert_series_equal(result, expected)
+
+
+def test_getitem_generator(string_series):
+ gen = (x > 0 for x in string_series)
+ result = string_series[gen]
+ result2 = string_series[iter(string_series > 0)]
+ expected = string_series[string_series > 0]
+ tm.assert_series_equal(result, expected)
+ tm.assert_series_equal(result2, expected)
+
+
+@pytest.mark.parametrize(
+ "data",
+ [
+ [0, 1],
+ date_range("2012-01-01", periods=2),
+ date_range("2012-01-01", periods=2, tz="CET"),
+ ],
+)
+def test_getitem_ndim_deprecated(data):
+ series = Series(data)
+ with pytest.raises(ValueError, match="Multi-dimensional indexing"):
+ series[:, None]
+
+
+def test_getitem_multilevel_scalar_slice_not_implemented(
+ multiindex_year_month_day_dataframe_random_data,
+):
+ # not implementing this for now
+ df = multiindex_year_month_day_dataframe_random_data
+ ser = df["A"]
+
+ msg = r"\(2000, slice\(3, 4, None\)\)"
+ with pytest.raises(TypeError, match=msg):
+ ser[2000, 3:4]
+
+
+def test_getitem_dataframe_raises():
+ rng = list(range(10))
+ ser = Series(10, index=rng)
+ df = DataFrame(rng, index=rng)
+ msg = (
+ "Indexing a Series with DataFrame is not supported, "
+ "use the appropriate DataFrame column"
+ )
+ with pytest.raises(TypeError, match=msg):
+ ser[df > 5]
+
+
+def test_getitem_assignment_series_alignment():
+ # https://github.com/pandas-dev/pandas/issues/37427
+ # with getitem, when assigning with a Series, it is not first aligned
+ ser = Series(range(10))
+ idx = np.array([2, 4, 9])
+ ser[idx] = Series([10, 11, 12])
+ expected = Series([0, 1, 10, 3, 11, 5, 6, 7, 8, 12])
+ tm.assert_series_equal(ser, expected)
+
+
+def test_getitem_duplicate_index_mistyped_key_raises_keyerror():
+ # GH#29189 float_index.get_loc(None) should raise KeyError, not TypeError
+ ser = Series([2, 5, 6, 8], index=[2.0, 4.0, 4.0, 5.0])
+ with pytest.raises(KeyError, match="None"):
+ ser[None]
+
+ with pytest.raises(KeyError, match="None"):
+ ser.index.get_loc(None)
+
+ with pytest.raises(KeyError, match="None"):
+ ser.index._engine.get_loc(None)
+
+
+def test_getitem_1tuple_slice_without_multiindex():
+ ser = Series(range(5))
+ key = (slice(3),)
+
+ result = ser[key]
+ expected = ser[key[0]]
+ tm.assert_series_equal(result, expected)
+
+
+def test_getitem_preserve_name(datetime_series):
+ result = datetime_series[datetime_series > 0]
+ assert result.name == datetime_series.name
+
+ result = datetime_series[5:10]
+ assert result.name == datetime_series.name
+
+
+def test_getitem_with_integer_labels():
+ # integer indexes, be careful
+ ser = Series(
+ np.random.default_rng(2).standard_normal(10), index=list(range(0, 20, 2))
+ )
+ inds = [0, 2, 5, 7, 8]
+ arr_inds = np.array([0, 2, 5, 7, 8])
+ with pytest.raises(KeyError, match="not in index"):
+ ser[inds]
+
+ with pytest.raises(KeyError, match="not in index"):
+ ser[arr_inds]
+
+
+def test_getitem_missing(datetime_series):
+ # missing
+ d = datetime_series.index[0] - BDay()
+ msg = r"Timestamp\('1999-12-31 00:00:00'\)"
+ with pytest.raises(KeyError, match=msg):
+ datetime_series[d]
+
+
+def test_getitem_fancy(string_series, object_series):
+ msg = r"None of \[Index\(\[1, 2, 3\], dtype='int(32|64)'\)\] are in the \[index\]"
+ with pytest.raises(KeyError, match=msg):
+ string_series[[1, 2, 3]]
+ with pytest.raises(KeyError, match=msg):
+ object_series[[1, 2, 3]]
+
+
+def test_getitem_box_float64(datetime_series):
+ with pytest.raises(KeyError, match="^5$"):
+ datetime_series[5]
+
+
+def test_getitem_unordered_dup():
+ obj = Series(range(5), index=["c", "a", "a", "b", "b"])
+ assert is_scalar(obj["c"])
+ assert obj["c"] == 0
+
+
+def test_getitem_dups():
+ ser = Series(range(5), index=["A", "A", "B", "C", "C"], dtype=np.int64)
+ expected = Series([3, 4], index=["C", "C"], dtype=np.int64)
+ result = ser["C"]
+ tm.assert_series_equal(result, expected)
+
+
+def test_getitem_categorical_str():
+ # GH#31765
+ ser = Series(range(5), index=Categorical(["a", "b", "c", "a", "b"]))
+ result = ser["a"]
+ expected = ser.iloc[[0, 3]]
+ tm.assert_series_equal(result, expected)
+
+
+def test_slice_can_reorder_not_uniquely_indexed():
+ ser = Series(1, index=["a", "a", "b", "b", "c"])
+ ser[::-1] # it works!
+
+
+@pytest.mark.parametrize("index_vals", ["aabcd", "aadcb"])
+def test_duplicated_index_getitem_positional_indexer(index_vals):
+ # GH 11747; changed in 3.0 integers are treated as always-labels
+ s = Series(range(5), index=list(index_vals))
+
+ with pytest.raises(KeyError, match="^3$"):
+ s[3]
+
+
+class TestGetitemDeprecatedIndexers:
+ @pytest.mark.parametrize("key", [{1}, {1: 1}])
+ def test_getitem_dict_and_set_deprecated(self, key):
+ # GH#42825 enforced in 2.0
+ ser = Series([1, 2, 3])
+ with pytest.raises(TypeError, match="as an indexer is not supported"):
+ ser[key]
+
+ @pytest.mark.parametrize("key", [{1}, {1: 1}])
+ def test_setitem_dict_and_set_disallowed(self, key):
+ # GH#42825 enforced in 2.0
+ ser = Series([1, 2, 3])
+ with pytest.raises(TypeError, match="as an indexer is not supported"):
+ ser[key] = 1
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_indexing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_indexing.py
new file mode 100644
index 0000000000000000000000000000000000000000..0da224e262de036ca406c532d2bea629a7658817
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_indexing.py
@@ -0,0 +1,510 @@
+"""test get/set & misc"""
+
+from datetime import timedelta
+import re
+
+import numpy as np
+import pytest
+
+from pandas.errors import IndexingError
+
+from pandas import (
+ NA,
+ DataFrame,
+ Index,
+ IndexSlice,
+ MultiIndex,
+ NaT,
+ Series,
+ Timedelta,
+ Timestamp,
+ concat,
+ date_range,
+ isna,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+def test_basic_indexing():
+ s = Series(
+ np.random.default_rng(2).standard_normal(5), index=["a", "b", "a", "a", "b"]
+ )
+
+ with pytest.raises(KeyError, match="^5$"):
+ s[5]
+
+ with pytest.raises(KeyError, match=r"^'c'$"):
+ s["c"]
+
+ s = s.sort_index()
+
+ with pytest.raises(KeyError, match="^5$"):
+ s[5]
+
+
+def test_getitem_numeric_should_not_fallback_to_positional(any_numeric_dtype):
+ # GH51053
+ dtype = any_numeric_dtype
+ idx = Index([1, 0, 1], dtype=dtype)
+ ser = Series(range(3), index=idx)
+ result = ser[1]
+ expected = Series([0, 2], index=Index([1, 1], dtype=dtype))
+ tm.assert_series_equal(result, expected, check_exact=True)
+
+
+def test_setitem_numeric_should_not_fallback_to_positional(any_numeric_dtype):
+ # GH51053
+ dtype = any_numeric_dtype
+ idx = Index([1, 0, 1], dtype=dtype)
+ ser = Series(range(3), index=idx)
+ ser[1] = 10
+ expected = Series([10, 1, 10], index=idx)
+ tm.assert_series_equal(ser, expected, check_exact=True)
+
+
+def test_basic_getitem_with_labels(datetime_series):
+ indices = datetime_series.index[[5, 10, 15]]
+
+ result = datetime_series[indices]
+ expected = datetime_series.reindex(indices)
+ tm.assert_series_equal(result, expected)
+
+ result = datetime_series[indices[0] : indices[2]]
+ expected = datetime_series.loc[indices[0] : indices[2]]
+ tm.assert_series_equal(result, expected)
+
+
+def test_basic_getitem_dt64tz_values():
+ # GH12089
+ # with tz for values
+ ser = Series(
+ date_range("2011-01-01", periods=3, tz="US/Eastern"), index=["a", "b", "c"]
+ )
+ expected = Timestamp("2011-01-01", tz="US/Eastern")
+ result = ser.loc["a"]
+ assert result == expected
+ result = ser.iloc[0]
+ assert result == expected
+ result = ser["a"]
+ assert result == expected
+
+
+def test_getitem_setitem_ellipsis():
+ s = Series(np.random.default_rng(2).standard_normal(10))
+
+ result = s[...]
+ tm.assert_series_equal(result, s)
+
+
+@pytest.mark.parametrize(
+ "result_1, duplicate_item, expected_1",
+ [
+ [
+ {1: 12, 2: [1, 2, 2, 3]},
+ {1: 313},
+ Series({1: 12}, dtype=object),
+ ],
+ [
+ {1: [1, 2, 3], 2: [1, 2, 2, 3]},
+ {1: [1, 2, 3]},
+ Series({1: [1, 2, 3]}),
+ ],
+ ],
+)
+def test_getitem_with_duplicates_indices(result_1, duplicate_item, expected_1):
+ # GH 17610
+ result_1 = Series(result_1)
+ duplicate_item = Series(duplicate_item)
+ result = result_1._append_internal(duplicate_item)
+ expected = expected_1._append_internal(duplicate_item)
+ tm.assert_series_equal(result[1], expected)
+ assert result[2] == result_1[2]
+
+
+def test_getitem_setitem_integers():
+ # caused bug without test
+ s = Series([1, 2, 3], ["a", "b", "c"])
+
+ assert s.iloc[0] == s["a"]
+ s.iloc[0] = 5
+ tm.assert_almost_equal(s["a"], 5)
+
+
+def test_series_box_timestamp():
+ rng = date_range("20090415", "20090519", freq="B")
+ ser = Series(rng)
+ assert isinstance(ser[0], Timestamp)
+ assert isinstance(ser.at[1], Timestamp)
+ assert isinstance(ser.iat[2], Timestamp)
+ assert isinstance(ser.loc[3], Timestamp)
+ assert isinstance(ser.iloc[4], Timestamp)
+
+ ser = Series(rng, index=rng)
+ assert isinstance(ser[rng[0]], Timestamp)
+ assert isinstance(ser.at[rng[1]], Timestamp)
+ assert isinstance(ser.iat[2], Timestamp)
+ assert isinstance(ser.loc[rng[3]], Timestamp)
+ assert isinstance(ser.iloc[4], Timestamp)
+
+
+def test_series_box_timedelta():
+ rng = timedelta_range("1 day 1 s", periods=5, freq="h")
+ ser = Series(rng)
+ assert isinstance(ser[0], Timedelta)
+ assert isinstance(ser.at[1], Timedelta)
+ assert isinstance(ser.iat[2], Timedelta)
+ assert isinstance(ser.loc[3], Timedelta)
+ assert isinstance(ser.iloc[4], Timedelta)
+
+
+def test_getitem_ambiguous_keyerror(indexer_sl):
+ ser = Series(range(10), index=list(range(0, 20, 2)))
+ with pytest.raises(KeyError, match=r"^1$"):
+ indexer_sl(ser)[1]
+
+
+def test_getitem_dups_with_missing(indexer_sl):
+ # breaks reindex, so need to use .loc internally
+ # GH 4246
+ ser = Series([1, 2, 3, 4], ["foo", "bar", "foo", "bah"])
+ with pytest.raises(KeyError, match=re.escape("['bam'] not in index")):
+ indexer_sl(ser)[["foo", "bar", "bah", "bam"]]
+
+
+def test_setitem_ambiguous_keyerror(indexer_sl):
+ s = Series(range(10), index=list(range(0, 20, 2)))
+
+ # equivalent of an append
+ s2 = s.copy()
+ indexer_sl(s2)[1] = 5
+ expected = concat([s, Series([5], index=[1])])
+ tm.assert_series_equal(s2, expected)
+
+
+def test_setitem(datetime_series):
+ datetime_series[datetime_series.index[5]] = np.nan
+ datetime_series.iloc[[1, 2, 17]] = np.nan
+ datetime_series.iloc[6] = np.nan
+ assert np.isnan(datetime_series.iloc[6])
+ assert np.isnan(datetime_series.iloc[2])
+ datetime_series[np.isnan(datetime_series)] = 5
+ assert not np.isnan(datetime_series.iloc[2])
+
+
+def test_setslice(datetime_series):
+ sl = datetime_series[5:20]
+ assert len(sl) == len(sl.index)
+ assert sl.index.is_unique is True
+
+
+def test_basic_getitem_setitem_corner(datetime_series):
+ # invalid tuples, e.g. td.ts[:, None] vs. td.ts[:, 2]
+ msg = "key of type tuple not found and not a MultiIndex"
+ with pytest.raises(KeyError, match=msg):
+ datetime_series[:, 2]
+ with pytest.raises(KeyError, match=msg):
+ datetime_series[:, 2] = 2
+
+ # weird lists. [slice(0, 5)] raises but not two slices
+ msg = "Indexing with a single-item list"
+ with pytest.raises(ValueError, match=msg):
+ # GH#31299
+ datetime_series[[slice(None, 5)]]
+
+ # but we're OK with a single-element tuple
+ result = datetime_series[(slice(None, 5),)]
+ expected = datetime_series[:5]
+ tm.assert_series_equal(result, expected)
+
+ # OK
+ msg = r"unhashable type(: 'slice')?"
+ with pytest.raises(TypeError, match=msg):
+ datetime_series[[5, [None, None]]]
+ with pytest.raises(TypeError, match=msg):
+ datetime_series[[5, [None, None]]] = 2
+
+
+def test_slice(string_series, object_series):
+ original = string_series.copy()
+ numSlice = string_series[10:20]
+ numSliceEnd = string_series[-10:]
+ objSlice = object_series[10:20]
+
+ assert string_series.index[9] not in numSlice.index
+ assert object_series.index[9] not in objSlice.index
+
+ assert len(numSlice) == len(numSlice.index)
+ assert string_series[numSlice.index[0]] == numSlice[numSlice.index[0]]
+
+ assert numSlice.index[1] == string_series.index[11]
+ tm.assert_numpy_array_equal(np.array(numSliceEnd), np.array(string_series)[-10:])
+
+ # Test return view.
+ sl = string_series[10:20]
+ sl[:] = 0
+
+ # Doesn't modify parent (CoW)
+ tm.assert_series_equal(string_series, original)
+
+
+def test_timedelta_assignment():
+ # GH 8209
+ s = Series([], dtype=object)
+ s.loc["B"] = timedelta(1)
+ expected = Series(Timedelta("1 days"), dtype="timedelta64[us]", index=["B"])
+ tm.assert_series_equal(s, expected)
+
+ s = s.reindex(s.index.insert(0, "A"))
+ expected = Series(
+ [np.nan, Timedelta("1 days")], dtype="timedelta64[us]", index=["A", "B"]
+ )
+ tm.assert_series_equal(s, expected)
+
+ s.loc["A"] = timedelta(1)
+ expected = Series(Timedelta("1 days"), dtype="timedelta64[us]", index=["A", "B"])
+ tm.assert_series_equal(s, expected)
+
+
+def test_underlying_data_conversion():
+ # GH 4080
+ df = DataFrame({c: [1, 2, 3] for c in ["a", "b", "c"]})
+ return_value = df.set_index(["a", "b", "c"], inplace=True)
+ assert return_value is None
+ s = Series([1], index=[(2, 2, 2)])
+ df["val"] = 0
+ df_original = df.copy()
+ df
+
+ with tm.raises_chained_assignment_error():
+ df["val"].update(s)
+ expected = df_original
+ tm.assert_frame_equal(df, expected)
+
+
+def test_preserve_refs(datetime_series):
+ seq = datetime_series.iloc[[5, 10, 15]]
+ seq.iloc[1] = np.nan
+ assert not np.isnan(datetime_series.iloc[10])
+
+
+def test_multilevel_preserve_name(lexsorted_two_level_string_multiindex, indexer_sl):
+ index = lexsorted_two_level_string_multiindex
+ ser = Series(
+ np.random.default_rng(2).standard_normal(len(index)), index=index, name="sth"
+ )
+
+ result = indexer_sl(ser)["foo"]
+ assert result.name == ser.name
+
+
+# miscellaneous methods
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ date_range("2014-01-01", periods=20, freq="MS"),
+ period_range("2014-01", periods=20, freq="M"),
+ timedelta_range("0", periods=20, freq="h"),
+ ],
+)
+def test_slice_with_negative_step(index):
+ keystr1 = str(index[9])
+ keystr2 = str(index[13])
+
+ ser = Series(np.arange(20), index)
+ SLC = IndexSlice
+
+ for key in [keystr1, index[9]]:
+ tm.assert_indexing_slices_equivalent(ser, SLC[key::-1], SLC[9::-1])
+ tm.assert_indexing_slices_equivalent(ser, SLC[:key:-1], SLC[:8:-1])
+
+ for key2 in [keystr2, index[13]]:
+ tm.assert_indexing_slices_equivalent(ser, SLC[key2:key:-1], SLC[13:8:-1])
+ tm.assert_indexing_slices_equivalent(ser, SLC[key:key2:-1], SLC[0:0:-1])
+
+
+def test_tuple_index():
+ # GH 35534 - Selecting values when a Series has an Index of tuples
+ s = Series([1, 2], index=[("a",), ("b",)])
+ assert s[("a",)] == 1
+ assert s[("b",)] == 2
+ s[("b",)] = 3
+ assert s[("b",)] == 3
+
+
+def test_frozenset_index():
+ # GH35747 - Selecting values when a Series has an Index of frozenset
+ idx0, idx1 = frozenset("a"), frozenset("b")
+ s = Series([1, 2], index=[idx0, idx1])
+ assert s[idx0] == 1
+ assert s[idx1] == 2
+ s[idx1] = 3
+ assert s[idx1] == 3
+
+
+def test_loc_setitem_all_false_indexer():
+ # GH#45778
+ ser = Series([1, 2], index=["a", "b"])
+ expected = ser.copy()
+ rhs = Series([6, 7], index=["a", "b"])
+ ser.loc[ser > 100] = rhs
+ tm.assert_series_equal(ser, expected)
+
+
+def test_loc_boolean_indexer_non_matching_index():
+ # GH#46551
+ ser = Series([1])
+ result = ser.loc[Series([NA, False], dtype="boolean")]
+ expected = Series([], dtype="int64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_loc_boolean_indexer_miss_matching_index():
+ # GH#46551
+ ser = Series([1])
+ indexer = Series([NA, False], dtype="boolean", index=[1, 2])
+ with pytest.raises(IndexingError, match="Unalignable"):
+ ser.loc[indexer]
+
+
+def test_loc_setitem_nested_data_enlargement():
+ # GH#48614
+ df = DataFrame({"a": [1]})
+ ser = Series({"label": df})
+ ser.loc["new_label"] = df
+ expected = Series({"label": df, "new_label": df})
+ tm.assert_series_equal(ser, expected)
+
+
+def test_loc_ea_numeric_index_oob_slice_end():
+ # GH#50161
+ ser = Series(1, index=Index([0, 1, 2], dtype="Int64"))
+ result = ser.loc[2:3]
+ expected = Series(1, index=Index([2], dtype="Int64"))
+ tm.assert_series_equal(result, expected)
+
+
+def test_getitem_bool_int_key():
+ # GH#48653
+ ser = Series({True: 1, False: 0})
+ with pytest.raises(KeyError, match="0"):
+ ser.loc[0]
+
+
+@pytest.mark.parametrize("val", [{}, {"b": "x"}])
+@pytest.mark.parametrize("indexer", [[], [False, False], slice(0, -1), np.array([])])
+def test_setitem_empty_indexer(indexer, val):
+ # GH#45981
+ df = DataFrame({"a": [1, 2], **val})
+ expected = df.copy()
+ df.loc[indexer] = 1.5
+ tm.assert_frame_equal(df, expected)
+
+
+def test_loc_non_monotonic_index_with_a_missing_label():
+ msg = "Cannot get left slice bound for non-monotonic index with a missing label 4"
+ ser = Series([3, 6, 7, 6], index=[3, 8, 7, 6])
+ with pytest.raises(KeyError, match=msg):
+ ser.loc[4:7]
+
+
+class TestDeprecatedIndexers:
+ @pytest.mark.parametrize("key", [{1}, {1: 1}])
+ def test_getitem_dict_and_set_deprecated(self, key):
+ # GH#42825 enforced in 2.0
+ ser = Series([1, 2])
+ with pytest.raises(TypeError, match="as an indexer is not supported"):
+ ser.loc[key]
+
+ @pytest.mark.parametrize("key", [{1}, {1: 1}, ({1}, 2), ({1: 1}, 2)])
+ def test_getitem_dict_and_set_deprecated_multiindex(self, key):
+ # GH#42825 enforced in 2.0
+ ser = Series([1, 2], index=MultiIndex.from_tuples([(1, 2), (3, 4)]))
+ with pytest.raises(TypeError, match="as an indexer is not supported"):
+ ser.loc[key]
+
+ @pytest.mark.parametrize("key", [{1}, {1: 1}])
+ def test_setitem_dict_and_set_disallowed(self, key):
+ # GH#42825 enforced in 2.0
+ ser = Series([1, 2])
+ with pytest.raises(TypeError, match="as an indexer is not supported"):
+ ser.loc[key] = 1
+
+ @pytest.mark.parametrize("key", [{1}, {1: 1}, ({1}, 2), ({1: 1}, 2)])
+ def test_setitem_dict_and_set_disallowed_multiindex(self, key):
+ # GH#42825 enforced in 2.0
+ ser = Series([1, 2], index=MultiIndex.from_tuples([(1, 2), (3, 4)]))
+ with pytest.raises(TypeError, match="as an indexer is not supported"):
+ ser.loc[key] = 1
+
+
+class TestSetitemValidation:
+ # This is adapted from pandas/tests/arrays/masked/test_indexing.py
+ def _check_setitem_invalid(self, ser, invalid, indexer):
+ orig_ser = ser.copy()
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[indexer] = invalid
+ ser = orig_ser.copy()
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.iloc[indexer] = invalid
+ ser = orig_ser.copy()
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.loc[indexer] = invalid
+ ser = orig_ser.copy()
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[:] = invalid
+
+ def _check_setitem_valid(self, ser, value, indexer):
+ orig_ser = ser.copy()
+
+ ser[indexer] = value
+ ser = orig_ser.copy()
+
+ ser.iloc[indexer] = value
+ ser = orig_ser.copy()
+
+ ser.loc[indexer] = value
+ ser = orig_ser.copy()
+
+ ser[:] = value
+
+ _invalid_scalars = [
+ 1 + 2j,
+ "True",
+ "1",
+ "1.0",
+ NaT,
+ np.datetime64("NaT"),
+ np.timedelta64("NaT"),
+ ]
+ _indexers = [0, [0], slice(0, 1), [True, False, False], slice(None, None, None)]
+
+ @pytest.mark.parametrize(
+ "invalid", [*_invalid_scalars, 1, 1.0, np.int64(1), np.float64(1)]
+ )
+ @pytest.mark.parametrize("indexer", _indexers)
+ def test_setitem_validation_scalar_bool(self, invalid, indexer):
+ ser = Series([True, False, False], dtype="bool")
+ self._check_setitem_invalid(ser, invalid, indexer)
+
+ @pytest.mark.parametrize("invalid", [*_invalid_scalars, True, 1.5, np.float64(1.5)])
+ @pytest.mark.parametrize("indexer", _indexers)
+ def test_setitem_validation_scalar_int(self, invalid, any_int_numpy_dtype, indexer):
+ ser = Series([1, 2, 3], dtype=any_int_numpy_dtype)
+ if isna(invalid) and invalid is not NaT and not np.isnat(invalid):
+ self._check_setitem_valid(ser, invalid, indexer)
+ else:
+ self._check_setitem_invalid(ser, invalid, indexer)
+
+ @pytest.mark.parametrize("invalid", [*_invalid_scalars, True])
+ @pytest.mark.parametrize("indexer", _indexers)
+ def test_setitem_validation_scalar_float(self, invalid, float_numpy_dtype, indexer):
+ ser = Series([1, 2, None], dtype=float_numpy_dtype)
+ self._check_setitem_invalid(ser, invalid, indexer)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_mask.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_mask.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c21cd0d5ca648dcf0f1ac412dd232221c031c6f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_mask.py
@@ -0,0 +1,69 @@
+import numpy as np
+import pytest
+
+from pandas import Series
+import pandas._testing as tm
+
+
+def test_mask():
+ # compare with tested results in test_where
+ s = Series(np.random.default_rng(2).standard_normal(5))
+ cond = s > 0
+
+ rs = s.where(~cond, np.nan)
+ tm.assert_series_equal(rs, s.mask(cond))
+
+ rs = s.where(~cond)
+ rs2 = s.mask(cond)
+ tm.assert_series_equal(rs, rs2)
+
+ rs = s.where(~cond, -s)
+ rs2 = s.mask(cond, -s)
+ tm.assert_series_equal(rs, rs2)
+
+ cond = Series([True, False, False, True, False], index=s.index)
+ s2 = -(s.abs())
+ rs = s2.where(~cond[:3])
+ rs2 = s2.mask(cond[:3])
+ tm.assert_series_equal(rs, rs2)
+
+ rs = s2.where(~cond[:3], -s2)
+ rs2 = s2.mask(cond[:3], -s2)
+ tm.assert_series_equal(rs, rs2)
+
+ msg = "Array conditional must be same shape as self"
+ with pytest.raises(ValueError, match=msg):
+ s.mask(1)
+ with pytest.raises(ValueError, match=msg):
+ s.mask(cond[:3].values, -s)
+
+
+def test_mask_casts():
+ # dtype changes
+ ser = Series([1, 2, 3, 4])
+ result = ser.mask(ser > 2, np.nan)
+ expected = Series([1, 2, np.nan, np.nan])
+ tm.assert_series_equal(result, expected)
+
+
+def test_mask_casts2():
+ # see gh-21891
+ ser = Series([1, 2])
+ res = ser.mask([True, False])
+
+ exp = Series([np.nan, 2])
+ tm.assert_series_equal(res, exp)
+
+
+def test_mask_inplace():
+ s = Series(np.random.default_rng(2).standard_normal(5))
+ cond = s > 0
+
+ rs = s.copy()
+ rs.mask(cond, inplace=True)
+ tm.assert_series_equal(rs.dropna(), s[~cond])
+ tm.assert_series_equal(rs, s.mask(cond))
+
+ rs = s.copy()
+ rs.mask(cond, -s, inplace=True)
+ tm.assert_series_equal(rs, s.mask(cond, -s))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_set_value.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_set_value.py
new file mode 100644
index 0000000000000000000000000000000000000000..3bf89da53d9230b0d5990ad2e4e72c37e225b5b9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_set_value.py
@@ -0,0 +1,49 @@
+from datetime import datetime
+
+import numpy as np
+
+from pandas import (
+ DatetimeIndex,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+
+def test_series_set_value(using_infer_string):
+ # GH#1561, GH#51363 as of 3.0 we do not do inference in Index.insert
+
+ dates = [datetime(2001, 1, 1), datetime(2001, 1, 2)]
+ if using_infer_string:
+ index = DatetimeIndex(dates)
+ else:
+ index = Index(dates, dtype=object)
+
+ s = Series(dtype=object)
+ s._set_value(dates[0], 1.0)
+ s._set_value(dates[1], np.nan)
+
+ expected = Series([1.0, np.nan], index=index)
+
+ tm.assert_series_equal(s, expected)
+
+
+def test_set_value_dt64(datetime_series):
+ idx = datetime_series.index[10]
+ res = datetime_series._set_value(idx, 0)
+ assert res is None
+ assert datetime_series[idx] == 0
+
+
+def test_set_value_str_index(string_series):
+ # equiv
+ ser = string_series.copy()
+ res = ser._set_value("foobar", 0)
+ assert res is None
+ assert ser.index[-1] == "foobar"
+ assert ser["foobar"] == 0
+
+ ser2 = string_series.copy()
+ ser2.loc["foobar"] = 0
+ assert ser2.index[-1] == "foobar"
+ assert ser2["foobar"] == 0
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_setitem.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_setitem.py
new file mode 100644
index 0000000000000000000000000000000000000000..322ea76b0d920f0d90a134dfa4632717e4e5a4cb
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_setitem.py
@@ -0,0 +1,1843 @@
+import contextlib
+from datetime import (
+ date,
+ datetime,
+)
+from decimal import Decimal
+
+import numpy as np
+import pytest
+
+from pandas.compat.numpy import np_version_gt2
+from pandas.errors import IndexingError
+
+from pandas.core.dtypes.common import is_list_like
+
+from pandas import (
+ NA,
+ Categorical,
+ DataFrame,
+ DatetimeIndex,
+ Index,
+ Interval,
+ IntervalIndex,
+ MultiIndex,
+ NaT,
+ Period,
+ Series,
+ StringDtype,
+ Timedelta,
+ Timestamp,
+ array,
+ concat,
+ date_range,
+ interval_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+from pandas.tseries.offsets import BDay
+
+
+class TestSetitemDT64Values:
+ def test_setitem_none_nan(self):
+ series = Series(date_range("1/1/2000", periods=10, unit="ns"))
+ series[3] = None
+ assert series[3] is NaT
+
+ series[3:5] = None
+ assert series[4] is NaT
+
+ series[5] = np.nan
+ assert series[5] is NaT
+
+ series[5:7] = np.nan
+ assert series[6] is NaT
+
+ def test_setitem_multiindex_empty_slice(self):
+ # https://github.com/pandas-dev/pandas/issues/35878
+ idx = MultiIndex.from_tuples([("a", 1), ("b", 2)])
+ result = Series([1, 2], index=idx)
+ expected = result.copy()
+ result.loc[[]] = 0
+ tm.assert_series_equal(result, expected)
+
+ def test_setitem_with_string_index(self):
+ # GH#23451
+ # Set object dtype to avoid upcast when setting date.today()
+ ser = Series([1, 2, 3], index=["Date", "b", "other"], dtype=object)
+ ser["Date"] = date.today()
+ assert ser.Date == date.today()
+ assert ser["Date"] == date.today()
+
+ def test_setitem_tuple_with_datetimetz_values(self):
+ # GH#20441
+ arr = date_range("2017", periods=4, tz="US/Eastern", unit="ns")
+ index = [(0, 1), (0, 2), (0, 3), (0, 4)]
+ result = Series(arr, index=index)
+ expected = result.copy()
+ result[(0, 1)] = np.nan
+ expected.iloc[0] = np.nan
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("tz", ["US/Eastern", "UTC", "Asia/Tokyo"])
+ def test_setitem_with_tz(self, tz, indexer_sli):
+ orig = Series(date_range("2016-01-01", freq="h", periods=3, tz=tz, unit="ns"))
+ assert orig.dtype == f"datetime64[ns, {tz}]"
+
+ exp = Series(
+ [
+ Timestamp("2016-01-01 00:00", tz=tz),
+ Timestamp("2011-01-01 00:00", tz=tz),
+ Timestamp("2016-01-01 02:00", tz=tz),
+ ],
+ dtype=orig.dtype,
+ )
+
+ # scalar
+ ser = orig.copy()
+ indexer_sli(ser)[1] = Timestamp("2011-01-01", tz=tz)
+ tm.assert_series_equal(ser, exp)
+
+ # vector
+ vals = Series(
+ [Timestamp("2011-01-01", tz=tz), Timestamp("2012-01-01", tz=tz)],
+ index=[1, 2],
+ dtype=orig.dtype,
+ )
+ assert vals.dtype == f"datetime64[ns, {tz}]"
+
+ exp = Series(
+ [
+ Timestamp("2016-01-01 00:00", tz=tz),
+ Timestamp("2011-01-01 00:00", tz=tz),
+ Timestamp("2012-01-01 00:00", tz=tz),
+ ],
+ dtype=orig.dtype,
+ )
+
+ ser = orig.copy()
+ indexer_sli(ser)[[1, 2]] = vals
+ tm.assert_series_equal(ser, exp)
+
+ def test_setitem_with_tz_dst(self, indexer_sli):
+ # GH#14146 trouble setting values near DST boundary
+ tz = "US/Eastern"
+ orig = Series(date_range("2016-11-06", freq="h", periods=3, tz=tz, unit="ns"))
+ assert orig.dtype == f"datetime64[ns, {tz}]"
+
+ exp = Series(
+ [
+ Timestamp("2016-11-06 00:00-04:00", tz=tz),
+ Timestamp("2011-01-01 00:00-05:00", tz=tz),
+ Timestamp("2016-11-06 01:00-05:00", tz=tz),
+ ],
+ dtype=orig.dtype,
+ )
+
+ # scalar
+ ser = orig.copy()
+ indexer_sli(ser)[1] = Timestamp("2011-01-01", tz=tz)
+ tm.assert_series_equal(ser, exp)
+
+ # vector
+ vals = Series(
+ [Timestamp("2011-01-01", tz=tz), Timestamp("2012-01-01", tz=tz)],
+ index=[1, 2],
+ dtype=orig.dtype,
+ )
+ assert vals.dtype == f"datetime64[ns, {tz}]"
+
+ exp = Series(
+ [
+ Timestamp("2016-11-06 00:00", tz=tz),
+ Timestamp("2011-01-01 00:00", tz=tz),
+ Timestamp("2012-01-01 00:00", tz=tz),
+ ],
+ dtype=orig.dtype,
+ )
+
+ ser = orig.copy()
+ indexer_sli(ser)[[1, 2]] = vals
+ tm.assert_series_equal(ser, exp)
+
+ def test_object_series_setitem_dt64array_exact_match(self):
+ # make sure the dt64 isn't cast by numpy to integers
+ # https://github.com/numpy/numpy/issues/12550
+
+ ser = Series({"X": np.nan}, dtype=object)
+
+ indexer = [True]
+
+ # "exact_match" -> size of array being set matches size of ser
+ value = np.array([4], dtype="M8[ns]")
+
+ ser.iloc[indexer] = value
+
+ expected = Series([value[0]], index=["X"], dtype=object)
+ assert all(isinstance(x, np.datetime64) for x in expected.values)
+
+ tm.assert_series_equal(ser, expected)
+
+
+class TestSetitemScalarIndexer:
+ def test_setitem_negative_out_of_bounds(self):
+ # As of 3.0, int keys are treated as labels, so this becomes
+ # setitem-with-expansion
+ ser = Series(["a"] * 10, index=["a"] * 10)
+ ser[-11] = "foo"
+ exp = Series(["a"] * 10 + ["foo"], index=["a"] * 10 + [-11])
+ tm.assert_series_equal(ser, exp)
+
+ @pytest.mark.parametrize("indexer", [tm.loc, tm.at])
+ @pytest.mark.parametrize("ser_index", [0, 1])
+ def test_setitem_series_object_dtype(self, indexer, ser_index):
+ # GH#38303
+ ser = Series([0, 0], dtype="object")
+ idxr = indexer(ser)
+ idxr[0] = Series([42], index=[ser_index])
+ expected = Series([Series([42], index=[ser_index]), 0], dtype="object")
+ tm.assert_series_equal(ser, expected)
+
+ @pytest.mark.parametrize("index, exp_value", [(0, 42), (1, np.nan)])
+ def test_setitem_series(self, index, exp_value):
+ # GH#38303
+ ser = Series([0, 0])
+ ser.loc[0] = Series([42], index=[index])
+ expected = Series([exp_value, 0])
+ tm.assert_series_equal(ser, expected)
+
+
+class TestSetitemSlices:
+ def test_setitem_slice_float_raises(self, datetime_series):
+ msg = (
+ "cannot do slice indexing on DatetimeIndex with these indexers "
+ r"\[{key}\] of type float"
+ )
+ with pytest.raises(TypeError, match=msg.format(key=r"4\.0")):
+ datetime_series[4.0:10.0] = 0
+
+ with pytest.raises(TypeError, match=msg.format(key=r"4\.5")):
+ datetime_series[4.5:10.0] = 0
+
+ def test_setitem_slice(self):
+ ser = Series(range(10), index=list(range(10)))
+ ser[-12:] = 0
+ assert (ser == 0).all()
+
+ ser[:-12] = 5
+ assert (ser == 0).all()
+
+ def test_setitem_slice_integers(self):
+ ser = Series(
+ np.random.default_rng(2).standard_normal(8),
+ index=[2, 4, 6, 8, 10, 12, 14, 16],
+ )
+
+ ser[:4] = 0
+ assert (ser[:4] == 0).all()
+ assert not (ser[4:] == 0).any()
+
+ def test_setitem_slicestep(self):
+ # caught this bug when writing tests
+ series = Series(
+ np.arange(20, dtype=np.float64), index=np.arange(20, dtype=np.int64)
+ )
+
+ series[::2] = 0
+ assert (series[::2] == 0).all()
+
+ def test_setitem_multiindex_slice(self, indexer_sli):
+ # GH 8856
+ mi = MultiIndex.from_product(([0, 1], list("abcde")))
+ result = Series(np.arange(10, dtype=np.int64), mi)
+ indexer_sli(result)[::4] = 100
+ expected = Series([100, 1, 2, 3, 100, 5, 6, 7, 100, 9], mi)
+ tm.assert_series_equal(result, expected)
+
+
+class TestSetitemBooleanMask:
+ def test_setitem_mask_cast(self):
+ # GH#2746
+ # need to upcast
+ ser = Series([1, 2], index=[1, 2], dtype="int64")
+ ser[[True, False]] = Series([0], index=[1], dtype="int64")
+ expected = Series([0, 2], index=[1, 2], dtype="int64")
+
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_mask_align_and_promote(self):
+ # GH#8387: test that changing types does not break alignment
+ ts = Series(
+ np.random.default_rng(2).standard_normal(100), index=np.arange(100, 0, -1)
+ ).round(5)
+ mask = ts > 0
+ left = ts.copy()
+ right = ts[mask].copy().map(str)
+ with pytest.raises(TypeError, match="Invalid value"):
+ left[mask] = right
+
+ def test_setitem_mask_promote_strs(self):
+ ser = Series([0, 1, 2, 0])
+ mask = ser > 0
+ ser2 = ser[mask].map(str)
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[mask] = ser2
+
+ def test_setitem_mask_promote(self):
+ ser = Series([0, "foo", "bar", 0])
+ mask = Series([False, True, True, False])
+ ser2 = ser[mask]
+ ser[mask] = ser2
+
+ expected = Series([0, "foo", "bar", 0])
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_boolean(self, string_series):
+ mask = string_series > string_series.median()
+
+ # similar indexed series
+ result = string_series.copy()
+ result[mask] = string_series * 2
+ expected = string_series * 2
+ tm.assert_series_equal(result[mask], expected[mask])
+
+ # needs alignment
+ result = string_series.copy()
+ result[mask] = (string_series * 2)[0:5]
+ expected = (string_series * 2)[0:5].reindex_like(string_series)
+ expected[-mask] = string_series[mask]
+ tm.assert_series_equal(result[mask], expected[mask])
+
+ def test_setitem_boolean_corner(self, datetime_series):
+ ts = datetime_series
+ mask_shifted = ts.shift(1, freq=BDay()) > ts.median()
+
+ msg = (
+ r"Unalignable boolean Series provided as indexer \(index of "
+ r"the boolean Series and of the indexed object do not match"
+ )
+ with pytest.raises(IndexingError, match=msg):
+ ts[mask_shifted] = 1
+
+ with pytest.raises(IndexingError, match=msg):
+ ts.loc[mask_shifted] = 1
+
+ def test_setitem_boolean_different_order(self, string_series):
+ ordered = string_series.sort_values()
+
+ copy = string_series.copy()
+ copy[ordered > 0] = 0
+
+ expected = string_series.copy()
+ expected[expected > 0] = 0
+
+ tm.assert_series_equal(copy, expected)
+
+ @pytest.mark.parametrize("func", [list, np.array, Series])
+ def test_setitem_boolean_python_list(self, func):
+ # GH19406
+ ser = Series([None, "b", None])
+ mask = func([True, False, True])
+ ser[mask] = ["a", "c"]
+ expected = Series(["a", "b", "c"])
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_boolean_nullable_int_types(self, any_numeric_ea_dtype):
+ # GH: 26468
+ ser = Series([5, 6, 7, 8], dtype=any_numeric_ea_dtype)
+ ser[ser > 6] = Series(range(4), dtype=any_numeric_ea_dtype)
+ expected = Series([5, 6, 2, 3], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(ser, expected)
+
+ ser = Series([5, 6, 7, 8], dtype=any_numeric_ea_dtype)
+ ser.loc[ser > 6] = Series(range(4), dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(ser, expected)
+
+ ser = Series([5, 6, 7, 8], dtype=any_numeric_ea_dtype)
+ loc_ser = Series(range(4), dtype=any_numeric_ea_dtype)
+ ser.loc[ser > 6] = loc_ser.loc[loc_ser > 1]
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_with_bool_mask_and_values_matching_n_trues_in_length(self):
+ # GH#30567
+ ser = Series([None] * 10)
+ mask = [False] * 3 + [True] * 5 + [False] * 2
+ ser[mask] = range(5)
+ result = ser
+ expected = Series([None] * 3 + list(range(5)) + [None] * 2, dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ def test_setitem_nan_with_bool(self):
+ # GH 13034
+ result = Series([True, False, True])
+ with pytest.raises(TypeError, match="Invalid value"):
+ result[0] = np.nan
+
+ def test_setitem_mask_smallint_upcast(self):
+ orig = Series([1, 2, 3], dtype="int8")
+ alt = np.array([999, 1000, 1001], dtype=np.int64)
+
+ mask = np.array([True, False, True])
+
+ ser = orig.copy()
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[mask] = Series(alt)
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.mask(mask, alt, inplace=True)
+
+ res = ser.where(~mask, Series(alt))
+ expected = Series([999, 2, 1001])
+ tm.assert_series_equal(res, expected)
+
+ def test_setitem_mask_smallint_no_upcast(self):
+ # like test_setitem_mask_smallint_upcast, but while we can't hold 'alt',
+ # we *can* hold alt[mask] without casting
+ orig = Series([1, 2, 3], dtype="uint8")
+ alt = Series([245, 1000, 246], dtype=np.int64)
+
+ mask = np.array([True, False, True])
+
+ ser = orig.copy()
+ ser[mask] = alt
+ expected = Series([245, 2, 246], dtype="uint8")
+ tm.assert_series_equal(ser, expected)
+
+ ser2 = orig.copy()
+ ser2.mask(mask, alt, inplace=True)
+ tm.assert_series_equal(ser2, expected)
+
+ # TODO: ser.where(~mask, alt) unnecessarily upcasts to int64
+ ser3 = orig.copy()
+ res = ser3.where(~mask, alt)
+ tm.assert_series_equal(res, expected, check_dtype=False)
+
+
+class TestSetitemViewCopySemantics:
+ def test_setitem_invalidates_datetime_index_freq(self):
+ # GH#24096 altering a datetime64tz Series inplace invalidates the
+ # `freq` attribute on the underlying DatetimeIndex
+
+ dti = date_range("20130101", periods=3, tz="US/Eastern")
+ ts = dti[1]
+ ser = Series(dti)
+ assert ser._values is not dti
+ assert ser._values._ndarray.base is dti._data._ndarray.base
+ assert dti.freq == "D"
+ ser.iloc[1] = NaT
+ assert ser._values.freq is None
+
+ # check that the DatetimeIndex was not altered in place
+ assert ser._values is not dti
+ assert ser._values._ndarray.base is not dti._data._ndarray.base
+ assert dti[1] == ts
+ assert dti.freq == "D"
+
+ def test_dt64tz_setitem_does_not_mutate_dti(self):
+ # GH#21907, GH#24096
+ dti = date_range("2016-01-01", periods=10, tz="US/Pacific")
+ ts = dti[0]
+ ser = Series(dti)
+ assert ser._values is not dti
+ assert ser._values._ndarray.base is dti._data._ndarray.base
+ assert ser._mgr.blocks[0].values._ndarray.base is dti._data._ndarray.base
+
+ assert ser._mgr.blocks[0].values is not dti
+
+ ser[::3] = NaT
+ assert ser[0] is NaT
+ assert dti[0] == ts
+
+
+class TestSetitemCallable:
+ def test_setitem_callable_key(self):
+ # GH#12533
+ ser = Series([1, 2, 3, 4], index=list("ABCD"))
+ ser[lambda x: "A"] = -1
+
+ expected = Series([-1, 2, 3, 4], index=list("ABCD"))
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_callable_other(self):
+ # GH#13299
+ inc = lambda x: x + 1
+
+ # set object dtype to avoid upcast when setting inc
+ ser = Series([1, 2, -1, 4], dtype=object)
+ ser[ser < 0] = inc
+
+ expected = Series([1, 2, inc, 4])
+ tm.assert_series_equal(ser, expected)
+
+
+class TestSetitemWithExpansion:
+ def test_setitem_empty_series(self, using_infer_string):
+ # GH#10193
+ key = Timestamp("2012-01-01")
+ series = Series(dtype=object)
+ series[key] = 47
+ expected = Series(
+ 47, index=[key] if using_infer_string else Index([key], dtype=object)
+ )
+ tm.assert_series_equal(series, expected)
+
+ def test_setitem_empty_series_datetimeindex_preserves_freq(self):
+ # GH#33573 our index should retain its freq
+ dti = DatetimeIndex([], freq="D", dtype="M8[ns]")
+ series = Series([], index=dti, dtype=object)
+ key = Timestamp("2012-01-01")
+ series[key] = 47
+ expected = Series(47, DatetimeIndex([key], freq="D").as_unit("ns"))
+ tm.assert_series_equal(series, expected)
+ assert series.index.freq == expected.index.freq
+
+ def test_setitem_empty_series_timestamp_preserves_dtype(self):
+ # GH 21881
+ timestamp = Timestamp(1412526600000000000)
+ series = Series([timestamp], index=["timestamp"], dtype=object)
+ expected = series["timestamp"]
+
+ series = Series([], dtype=object)
+ series["anything"] = 300.0
+ series["timestamp"] = timestamp
+ result = series["timestamp"]
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "td",
+ [
+ Timedelta("9 days"),
+ Timedelta("9 days").to_timedelta64(),
+ Timedelta("9 days").to_pytimedelta(),
+ ],
+ )
+ def test_append_timedelta_does_not_cast(self, td, using_infer_string, request):
+ # GH#22717 inserting a Timedelta should _not_ cast to int64
+ if using_infer_string and not isinstance(td, Timedelta):
+ # TODO: GH#56010
+ request.applymarker(pytest.mark.xfail(reason="inferred as string"))
+
+ expected = Series(["x", td], index=[0, "td"], dtype=object)
+
+ ser = Series(["x"])
+ ser["td"] = td
+ tm.assert_series_equal(ser, expected)
+ assert isinstance(ser["td"], Timedelta)
+
+ ser = Series(["x"])
+ ser.loc["td"] = Timedelta("9 days")
+ tm.assert_series_equal(ser, expected)
+ assert isinstance(ser["td"], Timedelta)
+
+ def test_setitem_with_expansion_type_promotion(self):
+ # GH#12599
+ ser = Series(dtype=object)
+ ser["a"] = Timestamp("2016-01-01")
+ ser["b"] = 3.0
+ ser["c"] = "foo"
+ expected = Series([Timestamp("2016-01-01"), 3.0, "foo"], index=["a", "b", "c"])
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_not_contained(self, string_series):
+ # set item that's not contained
+ ser = string_series.copy()
+ assert "foobar" not in ser.index
+ ser["foobar"] = 1
+
+ app = Series([1], index=["foobar"], name="series")
+ expected = concat([string_series, app])
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_keep_precision(self, any_numeric_ea_dtype):
+ # GH#32346
+ ser = Series([1, 2], dtype=any_numeric_ea_dtype)
+ ser[2] = 10
+ expected = Series([1, 2, 10], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(ser, expected)
+
+ @pytest.mark.parametrize(
+ "na, target_na, dtype, target_dtype, indexer, raises",
+ [
+ (NA, NA, "Int64", "Int64", 1, False),
+ (NA, NA, "Int64", "Int64", 2, False),
+ (NA, np.nan, "int64", "float64", 1, False),
+ (NA, np.nan, "int64", "float64", 2, False),
+ (NaT, NaT, "int64", "object", 1, True),
+ (NaT, NaT, "int64", "object", 2, False),
+ (np.nan, NA, "Int64", "Int64", 1, False),
+ (np.nan, NA, "Int64", "Int64", 2, False),
+ (np.nan, NA, "Float64", "Float64", 1, False),
+ (np.nan, NA, "Float64", "Float64", 2, False),
+ (np.nan, np.nan, "int64", "float64", 1, False),
+ (np.nan, np.nan, "int64", "float64", 2, False),
+ ],
+ )
+ def test_setitem_enlarge_with_na(
+ self, na, target_na, dtype, target_dtype, indexer, raises
+ ):
+ # GH#32346
+ ser = Series([1, 2], dtype=dtype)
+ if raises:
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[indexer] = na
+ else:
+ ser[indexer] = na
+ expected_values = [1, target_na] if indexer == 1 else [1, 2, target_na]
+ expected = Series(expected_values, dtype=target_dtype)
+ tm.assert_series_equal(ser, expected)
+
+ def test_setitem_enlargement_object_none(self, nulls_fixture, using_infer_string):
+ # GH#48665
+ ser = Series(["a", "b"])
+ ser[3] = nulls_fixture
+ dtype = (
+ "str"
+ if using_infer_string and not isinstance(nulls_fixture, Decimal)
+ else object
+ )
+ expected = Series(["a", "b", nulls_fixture], index=[0, 1, 3], dtype=dtype)
+ tm.assert_series_equal(ser, expected)
+ if using_infer_string:
+ ser[3] is np.nan
+ else:
+ assert ser[3] is nulls_fixture
+
+
+def test_setitem_scalar_into_readonly_backing_data():
+ # GH#14359: test that you cannot mutate a read only buffer
+
+ array = np.zeros(5)
+ array.flags.writeable = False # make the array immutable
+ series = Series(array, copy=False)
+
+ for n in series.index:
+ msg = "assignment destination is read-only"
+ with pytest.raises(ValueError, match=msg):
+ series[n] = 1
+
+ assert array[n] == 0
+
+
+def test_setitem_slice_into_readonly_backing_data():
+ # GH#14359: test that you cannot mutate a read only buffer
+
+ array = np.zeros(5)
+ array.flags.writeable = False # make the array immutable
+ series = Series(array, copy=False)
+
+ msg = "assignment destination is read-only"
+ with pytest.raises(ValueError, match=msg):
+ series[1:3] = 1
+
+ assert not array.any()
+
+
+def test_setitem_categorical_assigning_ops():
+ orig = Series(Categorical(["b", "b"], categories=["a", "b"]))
+ ser = orig.copy()
+ ser[:] = "a"
+ exp = Series(Categorical(["a", "a"], categories=["a", "b"]))
+ tm.assert_series_equal(ser, exp)
+
+ ser = orig.copy()
+ ser[1] = "a"
+ exp = Series(Categorical(["b", "a"], categories=["a", "b"]))
+ tm.assert_series_equal(ser, exp)
+
+ ser = orig.copy()
+ ser[ser.index > 0] = "a"
+ exp = Series(Categorical(["b", "a"], categories=["a", "b"]))
+ tm.assert_series_equal(ser, exp)
+
+ ser = orig.copy()
+ ser[[False, True]] = "a"
+ exp = Series(Categorical(["b", "a"], categories=["a", "b"]))
+ tm.assert_series_equal(ser, exp)
+
+ ser = orig.copy()
+ ser.index = ["x", "y"]
+ ser["y"] = "a"
+ exp = Series(Categorical(["b", "a"], categories=["a", "b"]), index=["x", "y"])
+ tm.assert_series_equal(ser, exp)
+
+
+def test_setitem_nan_into_categorical():
+ # ensure that one can set something to np.nan
+ ser = Series(Categorical([1, 2, 3]))
+ exp = Series(Categorical([1, np.nan, 3], categories=[1, 2, 3]))
+ ser[1] = np.nan
+ tm.assert_series_equal(ser, exp)
+
+
+class TestSetitemCasting:
+ @pytest.mark.parametrize("unique", [True, False])
+ @pytest.mark.parametrize("val", [3, 3.0, "3"], ids=type)
+ def test_setitem_non_bool_into_bool(self, val, indexer_sli, unique):
+ # dont cast these 3-like values to bool
+ ser = Series([True, False])
+ if not unique:
+ ser.index = [1, 1]
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ indexer_sli(ser)[1] = val
+
+ def test_setitem_boolean_array_into_npbool(self):
+ # GH#45462
+ ser = Series([True, False, True])
+ values = ser._values
+ arr = array([True, False, None])
+
+ ser[:2] = arr[:2] # no NAs -> can set inplace
+ assert ser._values is values
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[1:] = arr[1:] # has an NA -> cast to boolean dtype
+
+
+class SetitemCastingEquivalents:
+ """
+ Check each of several methods that _should_ be equivalent to `obj[key] = val`
+
+ We assume that
+ - obj.index is the default Index(range(len(obj)))
+ - the setitem does not expand the obj
+ """
+
+ @pytest.fixture
+ def is_inplace(self, obj, expected):
+ """
+ Whether we expect the setting to be in-place or not.
+ """
+ return expected.dtype == obj.dtype
+
+ def check_indexer(self, obj, key, expected, val, indexer, is_inplace):
+ orig = obj
+ obj = obj.copy()
+ arr = obj._values
+
+ indexer(obj)[key] = val
+ tm.assert_series_equal(obj, expected)
+
+ self._check_inplace(is_inplace, orig, arr, obj)
+
+ def _check_inplace(self, is_inplace, orig, arr, obj):
+ if is_inplace is None:
+ # We are not (yet) checking whether setting is inplace or not
+ pass
+ elif is_inplace:
+ if arr.dtype.kind in ["m", "M"]:
+ # We may not have the same DTA/TDA, but will have the same
+ # underlying data
+ assert arr._ndarray is obj._values._ndarray
+ else:
+ assert obj._values is arr
+ else:
+ # otherwise original array should be unchanged
+ tm.assert_equal(arr, orig._values)
+
+ def test_int_key(self, obj, key, expected, raises, val, indexer_sli, is_inplace):
+ if not isinstance(key, int):
+ pytest.skip("Not relevant for int key")
+ if raises:
+ ctx = pytest.raises(TypeError, match="Invalid value")
+ else:
+ ctx = contextlib.nullcontext()
+
+ with ctx:
+ self.check_indexer(obj, key, expected, val, indexer_sli, is_inplace)
+
+ if indexer_sli is tm.loc:
+ with ctx:
+ self.check_indexer(obj, key, expected, val, tm.at, is_inplace)
+ elif indexer_sli is tm.iloc:
+ with ctx:
+ self.check_indexer(obj, key, expected, val, tm.iat, is_inplace)
+
+ rng = range(key, key + 1)
+ with ctx:
+ self.check_indexer(obj, rng, expected, val, indexer_sli, is_inplace)
+
+ if indexer_sli is not tm.loc:
+ # Note: no .loc because that handles slice edges differently
+ slc = slice(key, key + 1)
+ with ctx:
+ self.check_indexer(obj, slc, expected, val, indexer_sli, is_inplace)
+
+ ilkey = [key]
+ with ctx:
+ self.check_indexer(obj, ilkey, expected, val, indexer_sli, is_inplace)
+
+ indkey = np.array(ilkey)
+ with ctx:
+ self.check_indexer(obj, indkey, expected, val, indexer_sli, is_inplace)
+
+ genkey = (x for x in [key])
+ with ctx:
+ self.check_indexer(obj, genkey, expected, val, indexer_sli, is_inplace)
+
+ def test_slice_key(self, obj, key, expected, raises, val, indexer_sli, is_inplace):
+ if not isinstance(key, slice):
+ pytest.skip("Not relevant for slice key")
+ if raises:
+ ctx = pytest.raises(TypeError, match="Invalid value")
+ else:
+ ctx = contextlib.nullcontext()
+
+ if indexer_sli is not tm.loc:
+ # Note: no .loc because that handles slice edges differently
+ with ctx:
+ self.check_indexer(obj, key, expected, val, indexer_sli, is_inplace)
+
+ ilkey = list(range(len(obj)))[key]
+ with ctx:
+ self.check_indexer(obj, ilkey, expected, val, indexer_sli, is_inplace)
+
+ indkey = np.array(ilkey)
+ with ctx:
+ self.check_indexer(obj, indkey, expected, val, indexer_sli, is_inplace)
+
+ genkey = (x for x in indkey)
+ with ctx:
+ self.check_indexer(obj, genkey, expected, val, indexer_sli, is_inplace)
+
+ def test_mask_key(self, obj, key, expected, raises, val, indexer_sli):
+ # setitem with boolean mask
+ mask = np.zeros(obj.shape, dtype=bool)
+ mask[key] = True
+
+ obj = obj.copy()
+
+ if is_list_like(val) and len(val) < mask.sum():
+ msg = "boolean index did not match indexed array along dimension"
+ with pytest.raises(IndexError, match=msg):
+ indexer_sli(obj)[mask] = val
+ return
+
+ if raises:
+ with pytest.raises(TypeError, match="Invalid value"):
+ indexer_sli(obj)[mask] = val
+ else:
+ indexer_sli(obj)[mask] = val
+
+ def test_series_where(self, obj, key, expected, raises, val, is_inplace):
+ mask = np.zeros(obj.shape, dtype=bool)
+ mask[key] = True
+
+ if is_list_like(val) and len(val) < len(obj):
+ # Series.where is not valid here
+ msg = "operands could not be broadcast together with shapes"
+ with pytest.raises(ValueError, match=msg):
+ obj.where(~mask, val)
+ return
+
+ orig = obj
+ obj = obj.copy()
+ arr = obj._values
+
+ res = obj.where(~mask, val)
+
+ if val is NA and res.dtype == object:
+ expected = expected.fillna(NA)
+ elif val is None and res.dtype == object:
+ assert expected.dtype == object
+ expected = expected.copy()
+ expected[expected.isna()] = None
+ tm.assert_series_equal(res, expected)
+
+ self._check_inplace(is_inplace, orig, arr, obj)
+
+ def test_index_where(self, obj, key, expected, raises, val):
+ mask = np.zeros(obj.shape, dtype=bool)
+ mask[key] = True
+
+ res = Index(obj).where(~mask, val)
+ expected_idx = Index(expected, dtype=expected.dtype)
+ tm.assert_index_equal(res, expected_idx)
+
+ def test_index_putmask(self, obj, key, expected, raises, val):
+ mask = np.zeros(obj.shape, dtype=bool)
+ mask[key] = True
+
+ res = Index(obj).putmask(mask, val)
+ tm.assert_index_equal(res, Index(expected, dtype=expected.dtype))
+
+
+@pytest.mark.parametrize(
+ "obj,expected,key,raises",
+ [
+ pytest.param(
+ # GH#45568 setting a valid NA value into IntervalDtype[int] should
+ # cast to IntervalDtype[float]
+ Series(interval_range(1, 5)),
+ Series(
+ [Interval(1, 2), np.nan, Interval(3, 4), Interval(4, 5)],
+ dtype="interval[float64]",
+ ),
+ 1,
+ True,
+ id="interval_int_na_value",
+ ),
+ pytest.param(
+ # these induce dtype changes
+ Series([2, 3, 4, 5, 6, 7, 8, 9, 10]),
+ Series([np.nan, 3, np.nan, 5, np.nan, 7, np.nan, 9, np.nan]),
+ slice(None, None, 2),
+ False,
+ id="int_series_slice_key_step",
+ ),
+ pytest.param(
+ Series([True, True, False, False]),
+ Series([np.nan, True, np.nan, False], dtype=object),
+ slice(None, None, 2),
+ True,
+ id="bool_series_slice_key_step",
+ ),
+ pytest.param(
+ # these induce dtype changes
+ Series(np.arange(10)),
+ Series([np.nan, np.nan, np.nan, np.nan, np.nan, 5, 6, 7, 8, 9]),
+ slice(None, 5),
+ False,
+ id="int_series_slice_key",
+ ),
+ pytest.param(
+ # changes dtype GH#4463
+ Series([1, 2, 3]),
+ Series([np.nan, 2, 3]),
+ 0,
+ False,
+ id="int_series_int_key",
+ ),
+ pytest.param(
+ # changes dtype GH#4463
+ Series([False]),
+ Series([np.nan], dtype=object),
+ # TODO: maybe go to float64 since we are changing the _whole_ Series?
+ 0,
+ True,
+ id="bool_series_int_key_change_all",
+ ),
+ pytest.param(
+ # changes dtype GH#4463
+ Series([False, True]),
+ Series([np.nan, True], dtype=object),
+ 0,
+ True,
+ id="bool_series_int_key",
+ ),
+ ],
+)
+class TestSetitemCastingEquivalents(SetitemCastingEquivalents):
+ @pytest.fixture(params=[np.nan, np.float64("NaN"), None, NA])
+ def val(self, request):
+ """
+ NA values that should generally be valid_na for *all* dtypes.
+
+ Include both python float NaN and np.float64; only np.float64 has a
+ `dtype` attribute.
+ """
+ return request.param
+
+
+class TestSetitemTimedelta64IntoNumeric(SetitemCastingEquivalents):
+ # timedelta64 should not be treated as integers when setting into
+ # numeric Series
+
+ @pytest.fixture
+ def val(self):
+ td = np.timedelta64(4, "ns")
+ return td
+ # TODO: could also try np.full((1,), td)
+
+ @pytest.fixture(params=[complex, int, float])
+ def dtype(self, request):
+ return request.param
+
+ @pytest.fixture
+ def obj(self, dtype):
+ arr = np.arange(5).astype(dtype)
+ ser = Series(arr)
+ return ser
+
+ @pytest.fixture
+ def expected(self, dtype):
+ arr = np.arange(5).astype(dtype)
+ ser = Series(arr)
+ ser = ser.astype(object)
+ ser.iloc[0] = np.timedelta64(4, "ns")
+ return ser
+
+ @pytest.fixture
+ def key(self):
+ return 0
+
+ @pytest.fixture
+ def raises(self):
+ return True
+
+
+class TestSetitemDT64IntoInt(SetitemCastingEquivalents):
+ # GH#39619 dont cast dt64 to int when doing this setitem
+
+ @pytest.fixture(params=["M8[ns]", "m8[ns]"])
+ def dtype(self, request):
+ return request.param
+
+ @pytest.fixture
+ def scalar(self, dtype):
+ val = np.datetime64("2021-01-18 13:25:00", "ns")
+ if dtype == "m8[ns]":
+ val = val - val
+ return val
+
+ @pytest.fixture
+ def expected(self, scalar):
+ expected = Series([scalar, scalar, 3], dtype=object)
+ assert isinstance(expected[0], type(scalar))
+ return expected
+
+ @pytest.fixture
+ def obj(self):
+ return Series([1, 2, 3])
+
+ @pytest.fixture
+ def key(self):
+ return slice(None, -1)
+
+ @pytest.fixture(params=[None, list, np.array])
+ def val(self, scalar, request):
+ box = request.param
+ if box is None:
+ return scalar
+ return box([scalar, scalar])
+
+ @pytest.fixture
+ def raises(self):
+ return True
+
+
+class TestSetitemNAPeriodDtype(SetitemCastingEquivalents):
+ # Setting compatible NA values into Series with PeriodDtype
+
+ @pytest.fixture
+ def expected(self, key):
+ exp = Series(period_range("2000-01-01", periods=10, freq="D"))
+ exp._values.view("i8")[key] = NaT._value
+ assert exp[key] is NaT or all(x is NaT for x in exp[key])
+ return exp
+
+ @pytest.fixture
+ def obj(self):
+ return Series(period_range("2000-01-01", periods=10, freq="D"))
+
+ @pytest.fixture(params=[3, slice(3, 5)])
+ def key(self, request):
+ return request.param
+
+ @pytest.fixture(params=[None, np.nan])
+ def val(self, request):
+ return request.param
+
+ @pytest.fixture
+ def raises(self):
+ return False
+
+
+class TestSetitemNADatetimeLikeDtype(SetitemCastingEquivalents):
+ # some nat-like values should be cast to datetime64/timedelta64 when
+ # inserting into a datetime64/timedelta64 series. Others should coerce
+ # to object and retain their dtypes.
+ # GH#18586 for td64 and boolean mask case
+
+ @pytest.fixture(
+ params=["m8[ns]", "M8[ns]", "datetime64[ns, UTC]", "datetime64[ns, US/Central]"]
+ )
+ def dtype(self, request):
+ return request.param
+
+ @pytest.fixture
+ def obj(self, dtype):
+ i8vals = date_range("2016-01-01", periods=3).asi8
+ idx = Index(i8vals, dtype=dtype)
+ assert idx.dtype == dtype
+ return Series(idx)
+
+ @pytest.fixture(
+ params=[
+ None,
+ np.nan,
+ NaT,
+ np.timedelta64("NaT", "ns"),
+ np.datetime64("NaT", "ns"),
+ ]
+ )
+ def val(self, request):
+ return request.param
+
+ @pytest.fixture
+ def is_inplace(self, val, obj):
+ # td64 -> cast to object iff val is datetime64("NaT")
+ # dt64 -> cast to object iff val is timedelta64("NaT")
+ # dt64tz -> cast to object with anything _but_ NaT
+ return val is NaT or val is None or val is np.nan or obj.dtype == val.dtype
+
+ @pytest.fixture
+ def expected(self, obj, val, is_inplace):
+ dtype = obj.dtype if is_inplace else object
+ expected = Series([val, *list(obj[1:])], dtype=dtype)
+ return expected
+
+ @pytest.fixture
+ def key(self):
+ return 0
+
+ @pytest.fixture
+ def raises(self, is_inplace):
+ return False if is_inplace else True
+
+
+class TestSetitemMismatchedTZCastsToObject(SetitemCastingEquivalents):
+ # GH#24024
+ @pytest.fixture
+ def obj(self):
+ return Series(date_range("2000", periods=2, tz="US/Central"))
+
+ @pytest.fixture
+ def val(self):
+ return Timestamp("2000", tz="US/Eastern")
+
+ @pytest.fixture
+ def key(self):
+ return 0
+
+ @pytest.fixture
+ def expected(self, obj, val):
+ # pre-2.0 this would cast to object, in 2.0 we cast the val to
+ # the target tz
+ expected = Series(
+ [
+ val.tz_convert("US/Central"),
+ Timestamp("2000-01-02 00:00:00-06:00", tz="US/Central"),
+ ],
+ dtype=obj.dtype,
+ )
+ return expected
+
+ @pytest.fixture
+ def raises(self):
+ return False
+
+
+@pytest.mark.parametrize(
+ "obj,expected",
+ [
+ # For numeric series, we should coerce to NaN.
+ (Series([1, 2, 3]), Series([np.nan, 2, 3])),
+ (Series([1.0, 2.0, 3.0]), Series([np.nan, 2.0, 3.0])),
+ # For datetime series, we should coerce to NaT.
+ (
+ Series([datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)]),
+ Series([NaT, datetime(2000, 1, 2), datetime(2000, 1, 3)]),
+ ),
+ # For objects, we should preserve the None value.
+ (Series(["foo", "bar", "baz"]), Series([None, "bar", "baz"])),
+ ],
+)
+class TestSeriesNoneCoercion(SetitemCastingEquivalents):
+ @pytest.fixture
+ def key(self):
+ return 0
+
+ @pytest.fixture
+ def val(self):
+ return None
+
+ @pytest.fixture
+ def raises(self):
+ return False
+
+
+class TestSetitemFloatIntervalWithIntIntervalValues(SetitemCastingEquivalents):
+ # GH#44201 Cast to shared IntervalDtype rather than object
+
+ def test_setitem_example(self):
+ # Just a case here to make obvious what this test class is aimed at
+ idx = IntervalIndex.from_breaks(range(4))
+ obj = Series(idx)
+ val = Interval(0.5, 1.5)
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ obj[0] = val
+
+ @pytest.fixture
+ def obj(self):
+ """
+ Fixture to create a Series [(0, 1], (1, 2], (2, 3]]
+ """
+ idx = IntervalIndex.from_breaks(range(4))
+ return Series(idx)
+
+ @pytest.fixture
+ def val(self):
+ """
+ Fixture to get an interval (0.5, 1.5]
+ """
+ return Interval(0.5, 1.5)
+
+ @pytest.fixture
+ def key(self):
+ """
+ Fixture to get a key 0
+ """
+ return 0
+
+ @pytest.fixture
+ def expected(self, obj, val):
+ """
+ Fixture to get a Series [(0.5, 1.5], (1.0, 2.0], (2.0, 3.0]]
+ """
+ data = [val, *list(obj[1:])]
+ idx = IntervalIndex(data, dtype="Interval[float64]")
+ return Series(idx)
+
+ @pytest.fixture
+ def raises(self):
+ """
+ Fixture to enable raising pytest exceptions
+ """
+ return True
+
+
+class TestSetitemRangeIntoIntegerSeries(SetitemCastingEquivalents):
+ # GH#44261 Setting a range with sufficiently-small integers into
+ # small-itemsize integer dtypes should not need to upcast
+
+ @pytest.fixture
+ def obj(self, any_int_numpy_dtype):
+ dtype = np.dtype(any_int_numpy_dtype)
+ ser = Series(range(5), dtype=dtype)
+ return ser
+
+ @pytest.fixture
+ def val(self):
+ return range(2, 4)
+
+ @pytest.fixture
+ def key(self):
+ return slice(0, 2)
+
+ @pytest.fixture
+ def expected(self, any_int_numpy_dtype):
+ dtype = np.dtype(any_int_numpy_dtype)
+ exp = Series([2, 3, 2, 3, 4], dtype=dtype)
+ return exp
+
+ @pytest.fixture
+ def raises(self):
+ return False
+
+
+@pytest.mark.parametrize(
+ "val, raises",
+ [
+ (np.array([2.0, 3.0]), False),
+ (np.array([2.5, 3.5]), True),
+ (
+ np.array([2**65, 2**65 + 1], dtype=np.float64),
+ True,
+ ), # all ints, but can't cast
+ ],
+)
+class TestSetitemFloatNDarrayIntoIntegerSeries(SetitemCastingEquivalents):
+ @pytest.fixture
+ def obj(self):
+ return Series(range(5), dtype=np.int64)
+
+ @pytest.fixture
+ def key(self):
+ return slice(0, 2)
+
+ @pytest.fixture
+ def expected(self, val):
+ if val[0] == 2:
+ # NB: this condition is based on currently-hardcoded "val" cases
+ dtype = np.int64
+ else:
+ dtype = np.float64
+ res_values = np.array(range(5), dtype=dtype)
+ res_values[:2] = val
+ return Series(res_values)
+
+
+@pytest.mark.parametrize("val", [512, np.int16(512)])
+class TestSetitemIntoIntegerSeriesNeedsUpcast(SetitemCastingEquivalents):
+ @pytest.fixture
+ def obj(self):
+ return Series([1, 2, 3], dtype=np.int8)
+
+ @pytest.fixture
+ def key(self):
+ return 1
+
+ @pytest.fixture
+ def expected(self):
+ return Series([1, 512, 3], dtype=np.int16)
+
+ @pytest.fixture
+ def raises(self):
+ return True
+
+
+@pytest.mark.parametrize("val", [2**33 + 1.0, 2**33 + 1.1, 2**62])
+class TestSmallIntegerSetitemUpcast(SetitemCastingEquivalents):
+ # https://github.com/pandas-dev/pandas/issues/39584#issuecomment-941212124
+ @pytest.fixture
+ def obj(self):
+ return Series([1, 2, 3], dtype="i4")
+
+ @pytest.fixture
+ def key(self):
+ return 0
+
+ @pytest.fixture
+ def expected(self, val):
+ if val % 1 != 0:
+ dtype = "f8"
+ else:
+ dtype = "i8"
+ return Series([val, 2, 3], dtype=dtype)
+
+ @pytest.fixture
+ def raises(self):
+ return True
+
+
+class CoercionTest(SetitemCastingEquivalents):
+ # Tests ported from tests.indexing.test_coercion
+
+ @pytest.fixture
+ def key(self):
+ return 1
+
+ @pytest.fixture
+ def expected(self, obj, key, val, exp_dtype):
+ vals = list(obj)
+ vals[key] = val
+ return Series(vals, dtype=exp_dtype)
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [(np.int32(1), np.int8, None), (np.int16(2**9), np.int16, True)],
+)
+class TestCoercionInt8(CoercionTest):
+ # previously test_setitem_series_int8 in tests.indexing.test_coercion
+ @pytest.fixture
+ def obj(self):
+ return Series([1, 2, 3, 4], dtype=np.int8)
+
+
+@pytest.mark.parametrize("val", [1, 1.1, 1 + 1j, True])
+@pytest.mark.parametrize("exp_dtype", [object])
+class TestCoercionObject(CoercionTest):
+ # previously test_setitem_series_object in tests.indexing.test_coercion
+ @pytest.fixture
+ def obj(self):
+ return Series(["a", "b", "c", "d"], dtype=object)
+
+ @pytest.fixture
+ def raises(self):
+ return False
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [
+ (1, object, True),
+ ("e", StringDtype(na_value=np.nan), False),
+ ],
+)
+class TestCoercionString(CoercionTest):
+ @pytest.fixture
+ def obj(self):
+ return Series(["a", "b", "c", "d"], dtype=StringDtype(na_value=np.nan))
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [
+ (1, np.complex128, False),
+ (1.1, np.complex128, False),
+ (1 + 1j, np.complex128, False),
+ (True, object, True),
+ ],
+)
+class TestCoercionComplex(CoercionTest):
+ # previously test_setitem_series_complex128 in tests.indexing.test_coercion
+ @pytest.fixture
+ def obj(self):
+ return Series([1 + 1j, 2 + 2j, 3 + 3j, 4 + 4j])
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [
+ (1, object, True),
+ ("3", object, True),
+ (3, object, True),
+ (1.1, object, True),
+ (1 + 1j, object, True),
+ (True, bool, False),
+ ],
+)
+class TestCoercionBool(CoercionTest):
+ # previously test_setitem_series_bool in tests.indexing.test_coercion
+ @pytest.fixture
+ def obj(self):
+ return Series([True, False, True, False], dtype=bool)
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [
+ (1, np.int64, False),
+ (1.1, np.float64, True),
+ (1 + 1j, np.complex128, True),
+ (True, object, True),
+ ],
+)
+class TestCoercionInt64(CoercionTest):
+ # previously test_setitem_series_int64 in tests.indexing.test_coercion
+ @pytest.fixture
+ def obj(self):
+ return Series([1, 2, 3, 4])
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [
+ (1, np.float64, False),
+ (1.1, np.float64, False),
+ (1 + 1j, np.complex128, True),
+ (True, object, True),
+ ],
+)
+class TestCoercionFloat64(CoercionTest):
+ # previously test_setitem_series_float64 in tests.indexing.test_coercion
+ @pytest.fixture
+ def obj(self):
+ return Series([1.1, 2.2, 3.3, 4.4])
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [
+ (1, np.float32, False),
+ pytest.param(
+ 1.1,
+ np.float32,
+ False,
+ marks=pytest.mark.xfail(
+ not np_version_gt2,
+ reason="np.float32(1.1) ends up as 1.100000023841858, so "
+ "np_can_hold_element raises and we cast to float64",
+ ),
+ ),
+ (1 + 1j, np.complex128, True),
+ (True, object, True),
+ (np.uint8(2), np.float32, False),
+ (np.uint32(2), np.float32, False),
+ # float32 cannot hold np.iinfo(np.uint32).max exactly
+ # (closest it can hold is 4294967300.0 which off by 5.0), so
+ # we cast to float64
+ (np.uint32(np.iinfo(np.uint32).max), np.float64, True),
+ (np.uint64(2), np.float32, False),
+ (np.int64(2), np.float32, False),
+ ],
+)
+class TestCoercionFloat32(CoercionTest):
+ @pytest.fixture
+ def obj(self):
+ return Series([1.1, 2.2, 3.3, 4.4], dtype=np.float32)
+
+ def test_slice_key(self, obj, key, expected, raises, val, indexer_sli, is_inplace):
+ super().test_slice_key(obj, key, expected, raises, val, indexer_sli, is_inplace)
+
+ if isinstance(val, float):
+ # the xfail would xpass bc test_slice_key short-circuits
+ raise AssertionError("xfail not relevant for this test.")
+
+
+@pytest.mark.parametrize(
+ "exp_dtype",
+ [
+ "M8[ms]",
+ "M8[ms, UTC]",
+ "m8[ms]",
+ ],
+)
+class TestCoercionDatetime64HigherReso(CoercionTest):
+ @pytest.fixture
+ def obj(self, exp_dtype):
+ idx = date_range("2011-01-01", freq="D", periods=4, unit="s")
+ if exp_dtype == "m8[ms]":
+ idx = idx - Timestamp("1970-01-01").as_unit("s")
+ assert idx.dtype == "m8[s]"
+ elif exp_dtype == "M8[ms, UTC]":
+ idx = idx.tz_localize("UTC")
+ return Series(idx)
+
+ @pytest.fixture
+ def val(self, exp_dtype):
+ ts = Timestamp("2011-01-02 03:04:05.678").as_unit("ms")
+ if exp_dtype == "m8[ms]":
+ return ts - Timestamp("1970-01-01").as_unit("s")
+ elif exp_dtype == "M8[ms, UTC]":
+ return ts.tz_localize("UTC")
+ return ts
+
+ @pytest.fixture
+ def raises(self):
+ return True
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [
+ (Timestamp("2012-01-01"), "datetime64[ns]", False),
+ (1, object, True),
+ ("x", object, True),
+ ],
+)
+class TestCoercionDatetime64(CoercionTest):
+ # previously test_setitem_series_datetime64 in tests.indexing.test_coercion
+
+ @pytest.fixture
+ def obj(self):
+ return Series(date_range("2011-01-01", freq="D", periods=4, unit="ns"))
+
+ @pytest.fixture
+ def raises(self):
+ return False
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [
+ (Timestamp("2012-01-01", tz="US/Eastern"), "datetime64[ns, US/Eastern]", False),
+ # pre-2.0, a mis-matched tz would end up casting to object
+ (Timestamp("2012-01-01", tz="US/Pacific"), "datetime64[ns, US/Eastern]", False),
+ (Timestamp("2012-01-01"), object, True),
+ (1, object, True),
+ ],
+)
+class TestCoercionDatetime64TZ(CoercionTest):
+ # previously test_setitem_series_datetime64tz in tests.indexing.test_coercion
+ @pytest.fixture
+ def obj(self):
+ tz = "US/Eastern"
+ return Series(date_range("2011-01-01", freq="D", periods=4, tz=tz, unit="ns"))
+
+ @pytest.fixture
+ def raises(self):
+ return False
+
+
+@pytest.mark.parametrize(
+ "val,exp_dtype,raises",
+ [
+ (Timedelta("12 day"), "timedelta64[us]", False),
+ (1, object, True),
+ ("x", object, True),
+ ],
+)
+class TestCoercionTimedelta64(CoercionTest):
+ # previously test_setitem_series_timedelta64 in tests.indexing.test_coercion
+ @pytest.fixture
+ def obj(self):
+ return Series(timedelta_range("1 day", periods=4))
+
+ @pytest.fixture
+ def raises(self):
+ return False
+
+
+@pytest.mark.parametrize(
+ "val", ["foo", Period("2016", freq="Y"), Interval(1, 2, closed="both")]
+)
+@pytest.mark.parametrize("exp_dtype", [object])
+class TestPeriodIntervalCoercion(CoercionTest):
+ # GH#45768
+ @pytest.fixture(
+ params=[
+ period_range("2016-01-01", periods=3, freq="D"),
+ interval_range(1, 5),
+ ]
+ )
+ def obj(self, request):
+ return Series(request.param)
+
+ @pytest.fixture
+ def raises(self):
+ return True
+
+
+def test_20643():
+ # closed by GH#45121
+ orig = Series([0, 1, 2], index=["a", "b", "c"])
+
+ ser = orig.copy()
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.at["b"] = 2.7
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.loc["b"] = 2.7
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser["b"] = 2.7
+
+ ser = orig.copy()
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.iat[1] = 2.7
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.iloc[1] = 2.7
+
+ orig_df = orig.to_frame("A")
+
+ df = orig_df.copy()
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.at["b", "A"] = 2.7
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.loc["b", "A"] = 2.7
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.iloc[1, 0] = 2.7
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ df.iat[1, 0] = 2.7
+
+
+def test_20643_comment():
+ # https://github.com/pandas-dev/pandas/issues/20643#issuecomment-431244590
+ # fixed sometime prior to GH#45121
+ orig = Series([0, 1, 2], index=["a", "b", "c"])
+ expected = Series([np.nan, 1, 2], index=["a", "b", "c"])
+
+ ser = orig.copy()
+ ser.iat[0] = None
+ tm.assert_series_equal(ser, expected)
+
+ ser = orig.copy()
+ ser.iloc[0] = None
+ tm.assert_series_equal(ser, expected)
+
+
+def test_15413():
+ # fixed by GH#45121
+ ser = Series([1, 2, 3])
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[ser == 2] += 0.5
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[1] += 0.5
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.loc[1] += 0.5
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.iloc[1] += 0.5
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.iat[1] += 0.5
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.at[1] += 0.5
+
+
+def test_32878_int_itemsize():
+ # Fixed by GH#45121
+ arr = np.arange(5).astype("i4")
+ ser = Series(arr)
+ val = np.int64(np.iinfo(np.int64).max)
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[0] = val
+
+
+def test_32878_complex_itemsize():
+ arr = np.arange(5).astype("c8")
+ ser = Series(arr)
+ val = np.finfo(np.float64).max
+ val = val.astype("c16")
+
+ # GH#32878 used to coerce val to inf+0.000000e+00j
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[0] = val
+
+
+def test_37692(indexer_al):
+ # GH#37692
+ ser = Series([1, 2, 3], index=["a", "b", "c"])
+ with pytest.raises(TypeError, match="Invalid value"):
+ indexer_al(ser)["b"] = "test"
+
+
+def test_setitem_bool_int_float_consistency(indexer_sli):
+ # GH#21513
+ # bool-with-int and bool-with-float both upcast to object
+ # int-with-float and float-with-int are both non-casting so long
+ # as the setitem can be done losslessly
+ for dtype in [np.float64, np.int64]:
+ ser = Series(0, index=range(3), dtype=dtype)
+ with pytest.raises(TypeError, match="Invalid value"):
+ indexer_sli(ser)[0] = True
+
+ ser = Series(0, index=range(3), dtype=bool)
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser[0] = dtype(1)
+
+ # 1.0 can be held losslessly, so no casting
+ ser = Series(0, index=range(3), dtype=np.int64)
+ indexer_sli(ser)[0] = np.float64(1.0)
+ assert ser.dtype == np.int64
+
+ # 1 can be held losslessly, so no casting
+ ser = Series(0, index=range(3), dtype=np.float64)
+ indexer_sli(ser)[0] = np.int64(1)
+
+
+def test_setitem_positional_with_casting():
+ # GH#45070 case where in __setitem__ we get a KeyError, then when
+ # we fallback we *also* get a ValueError if we try to set inplace.
+ # As of 3.0 we always treat int keys as labels, so this becomes
+ # setitem-with-expansion
+ ser = Series([1, 2, 3], index=["a", "b", "c"])
+
+ ser[0] = "X"
+ expected = Series([1, 2, 3, "X"], index=["a", "b", "c", 0], dtype=object)
+ tm.assert_series_equal(ser, expected)
+
+
+def test_setitem_positional_float_into_int_coerces():
+ # Case where we hit a KeyError and then trying to set in-place incorrectly
+ # casts a float to an int;
+ # As of 3.0 we always treat int keys as labels, so this becomes
+ # setitem-with-expansion
+ ser = Series([1, 2, 3], index=["a", "b", "c"])
+
+ ser[0] = 1.5
+ expected = Series([1, 2, 3, 1.5], index=["a", "b", "c", 0])
+ tm.assert_series_equal(ser, expected)
+
+
+def test_setitem_int_not_positional():
+ # GH#42215 deprecated falling back to positional on __setitem__ with an
+ # int not contained in the index; enforced in 2.0
+ ser = Series([1, 2, 3, 4], index=[1.1, 2.1, 3.0, 4.1])
+ assert not ser.index._should_fallback_to_positional
+ # assert not ser.index.astype(object)._should_fallback_to_positional
+
+ # 3.0 is in our index, so post-enforcement behavior is unchanged
+ ser[3] = 10
+ expected = Series([1, 2, 10, 4], index=ser.index)
+ tm.assert_series_equal(ser, expected)
+
+ # pre-enforcement `ser[5] = 5` raised IndexError
+ ser[5] = 5
+ expected = Series([1, 2, 10, 4, 5], index=[1.1, 2.1, 3.0, 4.1, 5.0])
+ tm.assert_series_equal(ser, expected)
+
+ ii = IntervalIndex.from_breaks(range(10))[::2]
+ ser2 = Series(range(len(ii)), index=ii)
+ exp_index = ii.astype(object).append(Index([4]))
+ expected2 = Series([0, 1, 2, 3, 4, 9], index=exp_index)
+ # pre-enforcement `ser2[4] = 9` interpreted 4 as positional
+ ser2[4] = 9
+ tm.assert_series_equal(ser2, expected2)
+
+ mi = MultiIndex.from_product([ser.index, ["A", "B"]])
+ ser3 = Series(range(len(mi)), index=mi)
+ expected3 = ser3.copy()
+ expected3.loc[4] = 99
+ # pre-enforcement `ser3[4] = 99` interpreted 4 as positional
+ ser3[4] = 99
+ tm.assert_series_equal(ser3, expected3)
+
+
+def test_setitem_with_bool_indexer():
+ # GH#42530
+
+ df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+ result = df.pop("b").copy()
+ result[[True, False, False]] = 9
+ expected = Series(data=[9, 5, 6], name="b")
+ tm.assert_series_equal(result, expected)
+
+ df.loc[[True, False, False], "a"] = 10
+ expected = DataFrame({"a": [10, 2, 3]})
+ tm.assert_frame_equal(df, expected)
+
+
+@pytest.mark.parametrize("size", range(2, 6))
+@pytest.mark.parametrize(
+ "mask", [[True, False, False, False, False], [True, False], [False]]
+)
+@pytest.mark.parametrize(
+ "item", [2.0, np.nan, np.finfo(float).max, np.finfo(float).min]
+)
+@pytest.mark.parametrize("box", [np.array, list, tuple])
+def test_setitem_bool_indexer_dont_broadcast_length1_values(size, mask, item, box):
+ # GH#44265
+ # see also tests.series.indexing.test_where.test_broadcast
+
+ selection = np.resize(mask, size)
+
+ data = np.arange(size, dtype=float)
+
+ ser = Series(data)
+
+ if selection.sum() != 1:
+ msg = (
+ "cannot set using a list-like indexer with a different "
+ "length than the value"
+ )
+ with pytest.raises(ValueError, match=msg):
+ # GH#44265
+ ser[selection] = box([item])
+ else:
+ # In this corner case setting is equivalent to setting with the unboxed
+ # item
+ ser[selection] = box([item])
+
+ expected = Series(np.arange(size, dtype=float))
+ expected[selection] = item
+ tm.assert_series_equal(ser, expected)
+
+
+def test_setitem_empty_mask_dont_upcast_dt64():
+ dti = date_range("2016-01-01", periods=3)
+ ser = Series(dti)
+ orig = ser.copy()
+ mask = np.zeros(3, dtype=bool)
+
+ ser[mask] = "foo"
+ assert ser.dtype == dti.dtype # no-op -> dont upcast
+ tm.assert_series_equal(ser, orig)
+
+ ser.mask(mask, "foo", inplace=True)
+ assert ser.dtype == dti.dtype # no-op -> dont upcast
+ tm.assert_series_equal(ser, orig)
+
+
+def test_setitem_bool_dtype_with_boolean_indexer():
+ # GH 57338
+ s1 = Series([True, True, True], dtype=bool)
+ s2 = Series([False, False, False], dtype=bool)
+ condition = [False, True, False]
+ s1[condition] = s2[condition]
+ expected = Series([True, False, True], dtype=bool)
+ tm.assert_series_equal(s1, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_take.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_take.py
new file mode 100644
index 0000000000000000000000000000000000000000..43fbae89089665fb2bf589d35db1b3f3041dd018
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_take.py
@@ -0,0 +1,50 @@
+import pytest
+
+import pandas as pd
+from pandas import Series
+import pandas._testing as tm
+
+
+def test_take_validate_axis():
+ # GH#51022
+ ser = Series([-1, 5, 6, 2, 4])
+
+ msg = "No axis named foo for object type Series"
+ with pytest.raises(ValueError, match=msg):
+ ser.take([1, 2], axis="foo")
+
+
+def test_take():
+ ser = Series([-1, 5, 6, 2, 4])
+
+ actual = ser.take([1, 3, 4])
+ expected = Series([5, 2, 4], index=[1, 3, 4])
+ tm.assert_series_equal(actual, expected)
+
+ actual = ser.take([-1, 3, 4])
+ expected = Series([4, 2, 4], index=[4, 3, 4])
+ tm.assert_series_equal(actual, expected)
+
+ msg = "indices are out-of-bounds"
+ with pytest.raises(IndexError, match=msg):
+ ser.take([1, 10])
+ with pytest.raises(IndexError, match=msg):
+ ser.take([2, 5])
+
+
+def test_take_categorical():
+ # https://github.com/pandas-dev/pandas/issues/20664
+ ser = Series(pd.Categorical(["a", "b", "c"]))
+ result = ser.take([-2, -2, 0])
+ expected = Series(
+ pd.Categorical(["b", "b", "a"], categories=["a", "b", "c"]), index=[1, 1, 0]
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_take_slice_raises():
+ ser = Series([-1, 5, 6, 2, 4])
+
+ msg = "Series.take requires a sequence of integers, not slice"
+ with pytest.raises(TypeError, match=msg):
+ ser.take(slice(0, 3, 1))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_where.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_where.py
new file mode 100644
index 0000000000000000000000000000000000000000..307da36629fd3a5eada337e690f9b570d050d350
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_where.py
@@ -0,0 +1,446 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import is_integer
+
+import pandas as pd
+from pandas import (
+ Series,
+ Timestamp,
+ date_range,
+ isna,
+)
+import pandas._testing as tm
+
+
+def test_where_unsafe_int(any_signed_int_numpy_dtype):
+ s = Series(np.arange(10), dtype=any_signed_int_numpy_dtype)
+ mask = s < 5
+
+ s[mask] = range(2, 7)
+ expected = Series(
+ list(range(2, 7)) + list(range(5, 10)),
+ dtype=any_signed_int_numpy_dtype,
+ )
+
+ tm.assert_series_equal(s, expected)
+
+
+def test_where_unsafe_float(float_numpy_dtype):
+ s = Series(np.arange(10), dtype=float_numpy_dtype)
+ mask = s < 5
+
+ s[mask] = range(2, 7)
+ data = list(range(2, 7)) + list(range(5, 10))
+ expected = Series(data, dtype=float_numpy_dtype)
+
+ tm.assert_series_equal(s, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype,expected_dtype",
+ [
+ (np.int8, np.float64),
+ (np.int16, np.float64),
+ (np.int32, np.float64),
+ (np.int64, np.float64),
+ (np.float32, np.float32),
+ (np.float64, np.float64),
+ ],
+)
+def test_where_unsafe_upcast(dtype, expected_dtype):
+ # see gh-9743
+ s = Series(np.arange(10), dtype=dtype)
+ values = [2.5, 3.5, 4.5, 5.5, 6.5]
+ mask = s < 5
+ if np.dtype(dtype).kind == np.dtype(expected_dtype).kind == "f":
+ s[mask] = values
+ expected = Series(values + list(range(5, 10)), dtype=expected_dtype)
+ tm.assert_series_equal(s, expected)
+ else:
+ with pytest.raises(TypeError, match="Invalid value"):
+ s[mask] = values
+
+
+def test_where_unsafe():
+ # see gh-9731
+ s = Series(np.arange(10), dtype="int64")
+ values = [2.5, 3.5, 4.5, 5.5]
+
+ mask = s > 5
+ expected = Series(list(range(6)) + values, dtype="float64")
+
+ with pytest.raises(TypeError, match="Invalid value"):
+ s[mask] = values
+ s = s.astype("float64")
+ s[mask] = values
+
+ # see gh-3235
+ s = Series(np.arange(10), dtype="int64")
+ mask = s < 5
+ s[mask] = range(2, 7)
+ expected = Series(list(range(2, 7)) + list(range(5, 10)), dtype="int64")
+ tm.assert_series_equal(s, expected)
+ assert s.dtype == expected.dtype
+
+ s = Series(np.arange(10), dtype="int64")
+ mask = s > 5
+ s[mask] = [0] * 4
+ expected = Series([0, 1, 2, 3, 4, 5] + [0] * 4, dtype="int64")
+ tm.assert_series_equal(s, expected)
+
+ s = Series(np.arange(10))
+ mask = s > 5
+
+ msg = "cannot set using a list-like indexer with a different length than the value"
+ with pytest.raises(ValueError, match=msg):
+ s[mask] = [5, 4, 3, 2, 1]
+
+ with pytest.raises(ValueError, match=msg):
+ s[mask] = [0] * 5
+
+ # dtype changes
+ s = Series([1, 2, 3, 4])
+ result = s.where(s > 2, np.nan)
+ expected = Series([np.nan, np.nan, 3, 4])
+ tm.assert_series_equal(result, expected)
+
+ # GH 4667
+ # setting with None changes dtype
+ s = Series(range(10)).astype(float)
+ s[8] = None
+ result = s[8]
+ assert isna(result)
+
+ s = Series(range(10)).astype(float)
+ s[s > 8] = None
+ result = s[isna(s)]
+ expected = Series(np.nan, index=[9])
+ tm.assert_series_equal(result, expected)
+
+
+def test_where():
+ s = Series(np.random.default_rng(2).standard_normal(5))
+ cond = s > 0
+
+ rs = s.where(cond).dropna()
+ rs2 = s[cond]
+ tm.assert_series_equal(rs, rs2)
+
+ rs = s.where(cond, -s)
+ tm.assert_series_equal(rs, s.abs())
+
+ rs = s.where(cond)
+ assert s.shape == rs.shape
+ assert rs is not s
+
+ # test alignment
+ cond = Series([True, False, False, True, False], index=s.index)
+ s2 = -(s.abs())
+
+ expected = s2[cond].reindex(s2.index[:3]).reindex(s2.index)
+ rs = s2.where(cond[:3])
+ tm.assert_series_equal(rs, expected)
+
+ expected = s2.abs()
+ expected.iloc[0] = s2[0]
+ rs = s2.where(cond[:3], -s2)
+ tm.assert_series_equal(rs, expected)
+
+
+def test_where_error():
+ s = Series(np.random.default_rng(2).standard_normal(5))
+ cond = s > 0
+
+ msg = "Array conditional must be same shape as self"
+ with pytest.raises(ValueError, match=msg):
+ s.where(1)
+ with pytest.raises(ValueError, match=msg):
+ s.where(cond[:3].values, -s)
+
+ # GH 2745
+ s = Series([1, 2])
+ s[[True, False]] = [0, 1]
+ expected = Series([0, 2])
+ tm.assert_series_equal(s, expected)
+
+ # failures
+ msg = "cannot set using a list-like indexer with a different length than the value"
+ with pytest.raises(ValueError, match=msg):
+ s[[True, False]] = [0, 2, 3]
+
+ with pytest.raises(ValueError, match=msg):
+ s[[True, False]] = []
+
+
+@pytest.mark.parametrize("klass", [list, tuple, np.array, Series])
+def test_where_array_like(klass):
+ # see gh-15414
+ s = Series([1, 2, 3])
+ cond = [False, True, True]
+ expected = Series([np.nan, 2, 3])
+
+ result = s.where(klass(cond))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "cond",
+ [
+ [1, 0, 1],
+ Series([2, 5, 7]),
+ ["True", "False", "True"],
+ [Timestamp("2017-01-01"), pd.NaT, Timestamp("2017-01-02")],
+ ],
+)
+def test_where_invalid_input(cond):
+ # see gh-15414: only boolean arrays accepted
+ s = Series([1, 2, 3])
+ msg = "Boolean array expected for the condition"
+
+ with pytest.raises(TypeError, match=msg):
+ s.where(cond)
+
+ msg = "Array conditional must be same shape as self"
+ with pytest.raises(ValueError, match=msg):
+ s.where([True])
+
+
+def test_where_ndframe_align():
+ msg = "Array conditional must be same shape as self"
+ s = Series([1, 2, 3])
+
+ cond = [True]
+ with pytest.raises(ValueError, match=msg):
+ s.where(cond)
+
+ expected = Series([1, np.nan, np.nan])
+
+ out = s.where(Series(cond))
+ tm.assert_series_equal(out, expected)
+
+ cond = np.array([False, True, False, True])
+ with pytest.raises(ValueError, match=msg):
+ s.where(cond)
+
+ expected = Series([np.nan, 2, np.nan])
+
+ out = s.where(Series(cond))
+ tm.assert_series_equal(out, expected)
+
+
+def test_where_setitem_invalid():
+ # GH 2702
+ # make sure correct exceptions are raised on invalid list assignment
+
+ msg = lambda x: (
+ f"cannot set using a {x} indexer with a different length than the value"
+ )
+ # slice
+ s = Series(list("abc"), dtype=object)
+
+ with pytest.raises(ValueError, match=msg("slice")):
+ s[0:3] = list(range(27))
+
+ s[0:3] = list(range(3))
+ expected = Series([0, 1, 2])
+ tm.assert_series_equal(s.astype(np.int64), expected)
+
+ # slice with step
+ s = Series(list("abcdef"), dtype=object)
+
+ with pytest.raises(ValueError, match=msg("slice")):
+ s[0:4:2] = list(range(27))
+
+ s = Series(list("abcdef"), dtype=object)
+ s[0:4:2] = list(range(2))
+ expected = Series([0, "b", 1, "d", "e", "f"])
+ tm.assert_series_equal(s, expected)
+
+ # neg slices
+ s = Series(list("abcdef"), dtype=object)
+
+ with pytest.raises(ValueError, match=msg("slice")):
+ s[:-1] = list(range(27))
+
+ s[-3:-1] = list(range(2))
+ expected = Series(["a", "b", "c", 0, 1, "f"])
+ tm.assert_series_equal(s, expected)
+
+ # list
+ s = Series(list("abc"), dtype=object)
+
+ with pytest.raises(ValueError, match=msg("list-like")):
+ s[[0, 1, 2]] = list(range(27))
+
+ s = Series(list("abc"), dtype=object)
+
+ with pytest.raises(ValueError, match=msg("list-like")):
+ s[[0, 1, 2]] = list(range(2))
+
+ # scalar
+ s = Series(list("abc"), dtype=object)
+ s[0] = list(range(10))
+ expected = Series([list(range(10)), "b", "c"])
+ tm.assert_series_equal(s, expected)
+
+
+@pytest.mark.parametrize("size", range(2, 6))
+@pytest.mark.parametrize(
+ "mask", [[True, False, False, False, False], [True, False], [False]]
+)
+@pytest.mark.parametrize(
+ "item", [2.0, np.nan, np.finfo(float).max, np.finfo(float).min]
+)
+@pytest.mark.parametrize("box", [np.array, list, tuple])
+def test_broadcast(size, mask, item, box):
+ # GH#8801, GH#4195
+ selection = np.resize(mask, size)
+
+ data = np.arange(size, dtype=float)
+
+ # Construct the expected series by taking the source
+ # data or item based on the selection
+ expected = Series(
+ [item if use_item else data[i] for i, use_item in enumerate(selection)]
+ )
+
+ s = Series(data)
+
+ s[selection] = item
+ tm.assert_series_equal(s, expected)
+
+ s = Series(data)
+ result = s.where(~selection, box([item]))
+ tm.assert_series_equal(result, expected)
+
+ s = Series(data)
+ result = s.mask(selection, box([item]))
+ tm.assert_series_equal(result, expected)
+
+
+def test_where_inplace():
+ s = Series(np.random.default_rng(2).standard_normal(5))
+ cond = s > 0
+
+ rs = s.copy()
+
+ result = rs.where(cond, inplace=True)
+ assert result is rs
+ tm.assert_series_equal(rs.dropna(), s[cond])
+ tm.assert_series_equal(rs, s.where(cond))
+
+ rs = s.copy()
+ result = rs.where(cond, -s, inplace=True)
+ assert result is rs
+ tm.assert_series_equal(rs, s.where(cond, -s))
+
+
+def test_where_dups():
+ # GH 4550
+ # where crashes with dups in index
+ s1 = Series(list(range(3)))
+ s2 = Series(list(range(3)))
+ comb = pd.concat([s1, s2])
+ result = comb.where(comb < 2)
+ expected = Series([0, 1, np.nan, 0, 1, np.nan], index=[0, 1, 2, 0, 1, 2])
+ tm.assert_series_equal(result, expected)
+
+ # GH 4548
+ # inplace updating not working with dups
+ comb[comb < 1] = 5
+ expected = Series([5, 1, 2, 5, 1, 2], index=[0, 1, 2, 0, 1, 2])
+ tm.assert_series_equal(comb, expected)
+
+ comb[comb < 2] += 10
+ expected = Series([5, 11, 2, 5, 11, 2], index=[0, 1, 2, 0, 1, 2])
+ tm.assert_series_equal(comb, expected)
+
+
+def test_where_numeric_with_string():
+ # GH 9280
+ s = Series([1, 2, 3])
+ w = s.where(s > 1, "X")
+
+ assert not is_integer(w[0])
+ assert is_integer(w[1])
+ assert is_integer(w[2])
+ assert isinstance(w[0], str)
+ assert w.dtype == "object"
+
+ w = s.where(s > 1, ["X", "Y", "Z"])
+ assert not is_integer(w[0])
+ assert is_integer(w[1])
+ assert is_integer(w[2])
+ assert isinstance(w[0], str)
+ assert w.dtype == "object"
+
+ w = s.where(s > 1, np.array(["X", "Y", "Z"]))
+ assert not is_integer(w[0])
+ assert is_integer(w[1])
+ assert is_integer(w[2])
+ assert isinstance(w[0], str)
+ assert w.dtype == "object"
+
+
+def test_where_datetimetz():
+ # GH 15701
+ timestamps = ["2016-12-31 12:00:04+00:00", "2016-12-31 12:00:04.010000+00:00"]
+ ser = Series([Timestamp(t) for t in timestamps], dtype="datetime64[ns, UTC]")
+ rs = ser.where(Series([False, True]))
+ expected = Series([pd.NaT, ser[1]], dtype="datetime64[ns, UTC]")
+ tm.assert_series_equal(rs, expected)
+
+
+def test_where_sparse():
+ # GH#17198 make sure we dont get an AttributeError for sp_index
+ ser = Series(pd.arrays.SparseArray([1, 2]))
+ result = ser.where(ser >= 2, 0)
+ expected = Series(pd.arrays.SparseArray([0, 2]))
+ tm.assert_series_equal(result, expected)
+
+
+def test_where_empty_series_and_empty_cond_having_non_bool_dtypes():
+ # https://github.com/pandas-dev/pandas/issues/34592
+ ser = Series([], dtype=float)
+ result = ser.where([])
+ tm.assert_series_equal(result, ser)
+
+
+def test_where_categorical(frame_or_series):
+ # https://github.com/pandas-dev/pandas/issues/18888
+ exp = frame_or_series(
+ pd.Categorical(["A", "A", "B", "B", np.nan], categories=["A", "B", "C"]),
+ dtype="category",
+ )
+ df = frame_or_series(["A", "A", "B", "B", "C"], dtype="category")
+ res = df.where(df != "C")
+ tm.assert_equal(exp, res)
+
+
+def test_where_datetimelike_categorical(tz_naive_fixture):
+ # GH#37682
+ tz = tz_naive_fixture
+
+ dr = date_range("2001-01-01", periods=3, tz=tz)._with_freq(None)
+ lvals = pd.DatetimeIndex([dr[0], dr[1], pd.NaT])
+ rvals = pd.Categorical([dr[0], pd.NaT, dr[2]])
+
+ mask = np.array([True, True, False])
+
+ # DatetimeIndex.where
+ res = lvals.where(mask, rvals)
+ tm.assert_index_equal(res, dr)
+
+ # DatetimeArray.where
+ res = lvals._data._where(mask, rvals)
+ tm.assert_datetime_array_equal(res, dr._data)
+
+ # Series.where
+ res = Series(lvals).where(mask, rvals)
+ tm.assert_series_equal(res, Series(dr))
+
+ # DataFrame.where
+ res = pd.DataFrame(lvals).where(mask[:, None], pd.DataFrame(rvals))
+
+ tm.assert_frame_equal(res, pd.DataFrame(dr))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_xs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_xs.py
new file mode 100644
index 0000000000000000000000000000000000000000..a67f3ec708f24a97dab544bb5cf859dc54659215
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/indexing/test_xs.py
@@ -0,0 +1,82 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ MultiIndex,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def test_xs_datetimelike_wrapping():
+ # GH#31630 a case where we shouldn't wrap datetime64 in Timestamp
+ arr = date_range("2016-01-01", periods=3)._data._ndarray
+
+ ser = Series(arr, dtype=object)
+ for i in range(len(ser)):
+ ser.iloc[i] = arr[i]
+ assert ser.dtype == object
+ assert isinstance(ser[0], np.datetime64)
+
+ result = ser.xs(0)
+ assert isinstance(result, np.datetime64)
+
+
+class TestXSWithMultiIndex:
+ def test_xs_level_series(self, multiindex_dataframe_random_data):
+ df = multiindex_dataframe_random_data
+ ser = df["A"]
+ expected = ser[:, "two"]
+ result = df.xs("two", level=1)["A"]
+ tm.assert_series_equal(result, expected)
+
+ def test_series_getitem_multiindex_xs_by_label(self):
+ # GH#5684
+ idx = MultiIndex.from_tuples(
+ [("a", "one"), ("a", "two"), ("b", "one"), ("b", "two")]
+ )
+ ser = Series([1, 2, 3, 4], index=idx)
+ return_value = ser.index.set_names(["L1", "L2"], inplace=True)
+ assert return_value is None
+ expected = Series([1, 3], index=["a", "b"])
+ return_value = expected.index.set_names(["L1"], inplace=True)
+ assert return_value is None
+
+ result = ser.xs("one", level="L2")
+ tm.assert_series_equal(result, expected)
+
+ def test_series_getitem_multiindex_xs(self):
+ # GH#6258
+ dt = list(date_range("20130903", periods=3))
+ idx = MultiIndex.from_product([list("AB"), dt])
+ ser = Series([1, 3, 4, 1, 3, 4], index=idx)
+ expected = Series([1, 1], index=list("AB"))
+
+ result = ser.xs("20130903", level=1)
+ tm.assert_series_equal(result, expected)
+
+ def test_series_xs_droplevel_false(self):
+ # GH: 19056
+ mi = MultiIndex.from_tuples(
+ [("a", "x"), ("a", "y"), ("b", "x")], names=["level1", "level2"]
+ )
+ ser = Series([1, 1, 1], index=mi)
+ result = ser.xs("a", axis=0, drop_level=False)
+ expected = Series(
+ [1, 1],
+ index=MultiIndex.from_tuples(
+ [("a", "x"), ("a", "y")], names=["level1", "level2"]
+ ),
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_xs_key_as_list(self):
+ # GH#41760
+ mi = MultiIndex.from_tuples([("a", "x")], names=["level1", "level2"])
+ ser = Series([1], index=mi)
+ with pytest.raises(TypeError, match="list keys are not supported"):
+ ser.xs(["a", "x"], axis=0, drop_level=False)
+
+ with pytest.raises(TypeError, match="list keys are not supported"):
+ ser.xs(["a"], axis=0, drop_level=False)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bcb0d30f405e2079a1ff21d838f52cd5f659fd93
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/__init__.py
@@ -0,0 +1,7 @@
+"""
+Test files dedicated to individual (stand-alone) Series methods
+
+Ideally these files/tests should correspond 1-to-1 with tests.frame.methods
+
+These may also present opportunities for sharing/de-duplicating test code.
+"""
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_add_prefix_suffix.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_add_prefix_suffix.py
new file mode 100644
index 0000000000000000000000000000000000000000..289a56b98b7e123fb1c1edf5cc9ff41369d51122
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_add_prefix_suffix.py
@@ -0,0 +1,41 @@
+import pytest
+
+from pandas import Index
+import pandas._testing as tm
+
+
+def test_add_prefix_suffix(string_series):
+ with_prefix = string_series.add_prefix("foo#")
+ expected = Index([f"foo#{c}" for c in string_series.index])
+ tm.assert_index_equal(with_prefix.index, expected)
+
+ with_suffix = string_series.add_suffix("#foo")
+ expected = Index([f"{c}#foo" for c in string_series.index])
+ tm.assert_index_equal(with_suffix.index, expected)
+
+ with_pct_prefix = string_series.add_prefix("%")
+ expected = Index([f"%{c}" for c in string_series.index])
+ tm.assert_index_equal(with_pct_prefix.index, expected)
+
+ with_pct_suffix = string_series.add_suffix("%")
+ expected = Index([f"{c}%" for c in string_series.index])
+ tm.assert_index_equal(with_pct_suffix.index, expected)
+
+
+def test_add_prefix_suffix_axis(string_series):
+ # GH 47819
+ with_prefix = string_series.add_prefix("foo#", axis=0)
+ expected = Index([f"foo#{c}" for c in string_series.index])
+ tm.assert_index_equal(with_prefix.index, expected)
+
+ with_pct_suffix = string_series.add_suffix("#foo", axis=0)
+ expected = Index([f"{c}#foo" for c in string_series.index])
+ tm.assert_index_equal(with_pct_suffix.index, expected)
+
+
+def test_add_prefix_suffix_invalid_axis(string_series):
+ with pytest.raises(ValueError, match="No axis named 1 for object type Series"):
+ string_series.add_prefix("foo#", axis=1)
+
+ with pytest.raises(ValueError, match="No axis named 1 for object type Series"):
+ string_series.add_suffix("foo#", axis=1)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_align.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_align.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe2d8f92fc5501364b71fa8652e13dc4bbf723f8
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_align.py
@@ -0,0 +1,195 @@
+from datetime import timezone
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Series,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "first_slice,second_slice",
+ [
+ [[2, None], [None, -5]],
+ [[None, 0], [None, -5]],
+ [[None, -5], [None, 0]],
+ [[None, 0], [None, 0]],
+ ],
+)
+@pytest.mark.parametrize("fill", [None, -1])
+def test_align(datetime_series, first_slice, second_slice, join_type, fill):
+ a = datetime_series[slice(*first_slice)]
+ b = datetime_series[slice(*second_slice)]
+
+ aa, ab = a.align(b, join=join_type, fill_value=fill)
+
+ join_index = a.index.join(b.index, how=join_type)
+ if fill is not None:
+ diff_a = aa.index.difference(join_index)
+ diff_b = ab.index.difference(join_index)
+ if len(diff_a) > 0:
+ assert (aa.reindex(diff_a) == fill).all()
+ if len(diff_b) > 0:
+ assert (ab.reindex(diff_b) == fill).all()
+
+ ea = a.reindex(join_index)
+ eb = b.reindex(join_index)
+
+ if fill is not None:
+ ea = ea.fillna(fill)
+ eb = eb.fillna(fill)
+
+ tm.assert_series_equal(aa, ea)
+ tm.assert_series_equal(ab, eb)
+ assert aa.name == "ts"
+ assert ea.name == "ts"
+ assert ab.name == "ts"
+ assert eb.name == "ts"
+
+
+def test_align_nocopy(datetime_series):
+ b = datetime_series[:5].copy()
+
+ # do copy
+ a = datetime_series.copy()
+ ra, _ = a.align(b, join="left")
+ ra[:5] = 5
+ assert not (a[:5] == 5).any()
+
+ # do not copy
+ a = datetime_series.copy()
+ ra, _ = a.align(b, join="left")
+ ra[:5] = 5
+ assert not (a[:5] == 5).any()
+
+ # do copy
+ a = datetime_series.copy()
+ b = datetime_series[:5].copy()
+ _, rb = a.align(b, join="right")
+ rb[:3] = 5
+ assert not (b[:3] == 5).any()
+
+ # do not copy
+ a = datetime_series.copy()
+ b = datetime_series[:5].copy()
+ _, rb = a.align(b, join="right")
+ rb[:2] = 5
+ assert not (b[:2] == 5).any()
+
+
+def test_align_same_index(datetime_series):
+ a, b = datetime_series.align(datetime_series)
+ assert a.index is not datetime_series.index
+ assert b.index is not datetime_series.index
+ assert a.index.is_(datetime_series.index)
+ assert b.index.is_(datetime_series.index)
+
+
+def test_align_multiindex():
+ # GH 10665
+
+ midx = pd.MultiIndex.from_product(
+ [range(2), range(3), range(2)], names=("a", "b", "c")
+ )
+ idx = pd.Index(range(2), name="b")
+ s1 = Series(np.arange(12, dtype="int64"), index=midx)
+ s2 = Series(np.arange(2, dtype="int64"), index=idx)
+
+ # these must be the same results (but flipped)
+ res1l, res1r = s1.align(s2, join="left")
+ res2l, res2r = s2.align(s1, join="right")
+
+ expl = s1
+ tm.assert_series_equal(expl, res1l)
+ tm.assert_series_equal(expl, res2r)
+ expr = Series([0, 0, 1, 1, np.nan, np.nan] * 2, index=midx)
+ tm.assert_series_equal(expr, res1r)
+ tm.assert_series_equal(expr, res2l)
+
+ res1l, res1r = s1.align(s2, join="right")
+ res2l, res2r = s2.align(s1, join="left")
+
+ exp_idx = pd.MultiIndex.from_product(
+ [range(2), range(2), range(2)], names=("a", "b", "c")
+ )
+ expl = Series([0, 1, 2, 3, 6, 7, 8, 9], index=exp_idx)
+ tm.assert_series_equal(expl, res1l)
+ tm.assert_series_equal(expl, res2r)
+ expr = Series([0, 0, 1, 1] * 2, index=exp_idx)
+ tm.assert_series_equal(expr, res1r)
+ tm.assert_series_equal(expr, res2l)
+
+
+def test_align_dt64tzindex_mismatched_tzs():
+ idx1 = date_range("2001", periods=5, freq="h", tz="US/Eastern")
+ ser = Series(np.random.default_rng(2).standard_normal(len(idx1)), index=idx1)
+ ser_central = ser.tz_convert("US/Central")
+ # different timezones convert to UTC
+
+ new1, new2 = ser.align(ser_central)
+ assert new1.index.tz is timezone.utc
+ assert new2.index.tz is timezone.utc
+
+
+def test_align_periodindex(join_type):
+ rng = period_range("1/1/2000", "1/1/2010", freq="Y")
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ # TODO: assert something?
+ ts.align(ts[::2], join=join_type)
+
+
+def test_align_stringindex(any_string_dtype):
+ left = Series(range(3), index=pd.Index(["a", "b", "d"], dtype=any_string_dtype))
+ right = Series(range(3), index=pd.Index(["a", "b", "c"], dtype=any_string_dtype))
+ result_left, result_right = left.align(right)
+
+ expected_idx = pd.Index(["a", "b", "c", "d"], dtype=any_string_dtype)
+ expected_left = Series([0, 1, np.nan, 2], index=expected_idx)
+ expected_right = Series([0, 1, 2, np.nan], index=expected_idx)
+
+ tm.assert_series_equal(result_left, expected_left)
+ tm.assert_series_equal(result_right, expected_right)
+
+
+def test_align_left_fewer_levels():
+ # GH#45224
+ left = Series([2], index=pd.MultiIndex.from_tuples([(1, 3)], names=["a", "c"]))
+ right = Series(
+ [1], index=pd.MultiIndex.from_tuples([(1, 2, 3)], names=["a", "b", "c"])
+ )
+ result_left, result_right = left.align(right)
+
+ expected_right = Series(
+ [1], index=pd.MultiIndex.from_tuples([(1, 3, 2)], names=["a", "c", "b"])
+ )
+ expected_left = Series(
+ [2], index=pd.MultiIndex.from_tuples([(1, 3, 2)], names=["a", "c", "b"])
+ )
+ tm.assert_series_equal(result_left, expected_left)
+ tm.assert_series_equal(result_right, expected_right)
+
+
+def test_align_left_different_named_levels():
+ # GH#45224
+ left = Series(
+ [2], index=pd.MultiIndex.from_tuples([(1, 4, 3)], names=["a", "d", "c"])
+ )
+ right = Series(
+ [1], index=pd.MultiIndex.from_tuples([(1, 2, 3)], names=["a", "b", "c"])
+ )
+ result_left, result_right = left.align(right)
+
+ expected_left = Series(
+ [2], index=pd.MultiIndex.from_tuples([(1, 4, 3, 2)], names=["a", "d", "c", "b"])
+ )
+ expected_right = Series(
+ [1], index=pd.MultiIndex.from_tuples([(1, 4, 3, 2)], names=["a", "d", "c", "b"])
+ )
+ tm.assert_series_equal(result_left, expected_left)
+ tm.assert_series_equal(result_right, expected_right)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_argsort.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_argsort.py
new file mode 100644
index 0000000000000000000000000000000000000000..09bc6a8300d7f995067138ec7972febc7984fcb9
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_argsort.py
@@ -0,0 +1,76 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Series,
+ Timestamp,
+ isna,
+)
+import pandas._testing as tm
+
+
+class TestSeriesArgsort:
+ def test_argsort_axis(self):
+ # GH#54257
+ ser = Series(range(3))
+
+ msg = "No axis named 2 for object type Series"
+ with pytest.raises(ValueError, match=msg):
+ ser.argsort(axis=2)
+
+ def test_argsort_numpy(self, datetime_series):
+ ser = datetime_series
+ res = np.argsort(ser).values
+ expected = np.argsort(np.array(ser))
+ tm.assert_numpy_array_equal(res, expected)
+
+ def test_argsort_numpy_missing(self):
+ data = [0.1, np.nan, 0.2, np.nan, 0.3]
+ ser = Series(data)
+ result = np.argsort(ser)
+ expected = np.argsort(np.array(data))
+
+ tm.assert_numpy_array_equal(result.values, expected)
+
+ def test_argsort(self, datetime_series):
+ argsorted = datetime_series.argsort()
+ assert issubclass(argsorted.dtype.type, np.integer)
+
+ def test_argsort_dt64(self, unit):
+ # GH#2967 (introduced bug in 0.11-dev I think)
+ ser = Series(
+ [Timestamp(f"201301{i:02d}") for i in range(1, 6)], dtype=f"M8[{unit}]"
+ )
+ assert ser.dtype == f"datetime64[{unit}]"
+ shifted = ser.shift(-1)
+ assert shifted.dtype == f"datetime64[{unit}]"
+ assert isna(shifted[4])
+
+ result = ser.argsort()
+ expected = Series(range(5), dtype=np.intp)
+ tm.assert_series_equal(result, expected)
+
+ result = shifted.argsort()
+ expected = Series([*list(range(4)), 4], dtype=np.intp)
+ tm.assert_series_equal(result, expected)
+
+ def test_argsort_stable(self):
+ ser = Series(np.random.default_rng(2).integers(0, 100, size=10000))
+ mindexer = ser.argsort(kind="mergesort")
+ qindexer = ser.argsort()
+
+ mexpected = np.argsort(ser.values, kind="mergesort")
+ qexpected = np.argsort(ser.values, kind="quicksort")
+
+ tm.assert_series_equal(mindexer.astype(np.intp), Series(mexpected))
+ tm.assert_series_equal(qindexer.astype(np.intp), Series(qexpected))
+ msg = (
+ r"ndarray Expected type , "
+ r"found instead"
+ )
+ with pytest.raises(AssertionError, match=msg):
+ tm.assert_numpy_array_equal(qindexer, mindexer)
+
+ def test_argsort_preserve_name(self, datetime_series):
+ result = datetime_series.argsort()
+ assert result.name == datetime_series.name
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_asof.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_asof.py
new file mode 100644
index 0000000000000000000000000000000000000000..2acc2921e5efc089b1dd4ed7aa9a6cebc1a3d0fe
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_asof.py
@@ -0,0 +1,205 @@
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import IncompatibleFrequency
+
+from pandas import (
+ DatetimeIndex,
+ PeriodIndex,
+ Series,
+ Timestamp,
+ date_range,
+ isna,
+ notna,
+ offsets,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestSeriesAsof:
+ def test_asof_nanosecond_index_access(self):
+ ts = Timestamp("20130101").as_unit("ns")._value
+ dti = DatetimeIndex([ts + 50 + i for i in range(100)])
+ ser = Series(np.random.default_rng(2).standard_normal(100), index=dti)
+
+ first_value = ser.asof(ser.index[0])
+
+ # GH#46903 previously incorrectly was "day"
+ assert dti.resolution == "nanosecond"
+
+ # this used to not work bc parsing was done by dateutil that didn't
+ # handle nanoseconds
+ assert first_value == ser["2013-01-01 00:00:00.000000050"]
+
+ expected_ts = np.datetime64("2013-01-01 00:00:00.000000050", "ns")
+ assert first_value == ser[Timestamp(expected_ts)]
+
+ def test_basic(self):
+ # array or list or dates
+ N = 50
+ rng = date_range("1/1/1990", periods=N, freq="53s")
+ ts = Series(np.random.default_rng(2).standard_normal(N), index=rng)
+ ts.iloc[15:30] = np.nan
+ dates = date_range("1/1/1990", periods=N * 3, freq="25s")
+
+ result = ts.asof(dates)
+ assert notna(result).all()
+ lb = ts.index[14]
+ ub = ts.index[30]
+
+ result = ts.asof(list(dates))
+ assert notna(result).all()
+ lb = ts.index[14]
+ ub = ts.index[30]
+
+ mask = (result.index >= lb) & (result.index < ub)
+ rs = result[mask]
+ assert (rs == ts[lb]).all()
+
+ val = result[result.index[result.index >= ub][0]]
+ assert ts[ub] == val
+
+ def test_scalar(self):
+ N = 30
+ rng = date_range("1/1/1990", periods=N, freq="53s")
+ # Explicit cast to float avoid implicit cast when setting nan
+ ts = Series(np.arange(N), index=rng, dtype="float")
+ ts.iloc[5:10] = np.nan
+ ts.iloc[15:20] = np.nan
+
+ val1 = ts.asof(ts.index[7])
+ val2 = ts.asof(ts.index[19])
+
+ assert val1 == ts.iloc[4]
+ assert val2 == ts.iloc[14]
+
+ # accepts strings
+ val1 = ts.asof(str(ts.index[7]))
+ assert val1 == ts.iloc[4]
+
+ # in there
+ result = ts.asof(ts.index[3])
+ assert result == ts.iloc[3]
+
+ # no as of value
+ d = ts.index[0] - offsets.BDay()
+ assert np.isnan(ts.asof(d))
+
+ def test_with_nan(self):
+ # basic asof test
+ rng = date_range("1/1/2000", "1/2/2000", freq="4h")
+ s = Series(np.arange(len(rng)), index=rng)
+ r = s.resample("2h").mean()
+
+ result = r.asof(r.index)
+ expected = Series(
+ [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6.0],
+ index=date_range("1/1/2000", "1/2/2000", freq="2h"),
+ )
+ tm.assert_series_equal(result, expected)
+
+ r.iloc[3:5] = np.nan
+ result = r.asof(r.index)
+ expected = Series(
+ [0, 0, 1, 1, 1, 1, 3, 3, 4, 4, 5, 5, 6.0],
+ index=date_range("1/1/2000", "1/2/2000", freq="2h"),
+ )
+ tm.assert_series_equal(result, expected)
+
+ r.iloc[-3:] = np.nan
+ result = r.asof(r.index)
+ expected = Series(
+ [0, 0, 1, 1, 1, 1, 3, 3, 4, 4, 4, 4, 4.0],
+ index=date_range("1/1/2000", "1/2/2000", freq="2h"),
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_periodindex(self):
+ # array or list or dates
+ N = 50
+ rng = period_range("1/1/1990", periods=N, freq="h")
+ ts = Series(np.random.default_rng(2).standard_normal(N), index=rng)
+ ts.iloc[15:30] = np.nan
+ dates = date_range("1/1/1990", periods=N * 3, freq="37min")
+
+ result = ts.asof(dates)
+ assert notna(result).all()
+ lb = ts.index[14]
+ ub = ts.index[30]
+
+ result = ts.asof(list(dates))
+ assert notna(result).all()
+ lb = ts.index[14]
+ ub = ts.index[30]
+
+ pix = PeriodIndex(result.index.values, freq="h")
+ mask = (pix >= lb) & (pix < ub)
+ rs = result[mask]
+ assert (rs == ts[lb]).all()
+
+ ts.iloc[5:10] = np.nan
+ ts.iloc[15:20] = np.nan
+
+ val1 = ts.asof(ts.index[7])
+ val2 = ts.asof(ts.index[19])
+
+ assert val1 == ts.iloc[4]
+ assert val2 == ts.iloc[14]
+
+ # accepts strings
+ val1 = ts.asof(str(ts.index[7]))
+ assert val1 == ts.iloc[4]
+
+ # in there
+ assert ts.asof(ts.index[3]) == ts.iloc[3]
+
+ # no as of value
+ d = ts.index[0].to_timestamp() - offsets.BDay()
+ assert isna(ts.asof(d))
+
+ # Mismatched freq
+ msg = "Input has different freq"
+ with pytest.raises(IncompatibleFrequency, match=msg):
+ ts.asof(rng.asfreq("D"))
+
+ def test_errors(self):
+ s = Series(
+ [1, 2, 3],
+ index=[Timestamp("20130101"), Timestamp("20130103"), Timestamp("20130102")],
+ )
+
+ # non-monotonic
+ assert not s.index.is_monotonic_increasing
+ with pytest.raises(ValueError, match="requires a sorted index"):
+ s.asof(s.index[0])
+
+ # subset with Series
+ N = 10
+ rng = date_range("1/1/1990", periods=N, freq="53s")
+ s = Series(np.random.default_rng(2).standard_normal(N), index=rng)
+ with pytest.raises(ValueError, match="not valid for Series"):
+ s.asof(s.index[0], subset="foo")
+
+ def test_all_nans(self):
+ # GH 15713
+ # series is all nans
+
+ # testing non-default indexes
+ N = 50
+ rng = date_range("1/1/1990", periods=N, freq="53s")
+
+ dates = date_range("1/1/1990", periods=N * 3, freq="25s")
+ result = Series(np.nan, index=rng).asof(dates)
+ expected = Series(np.nan, index=dates)
+ tm.assert_series_equal(result, expected)
+
+ # testing scalar input
+ date = date_range("1/1/1990", periods=N * 3, freq="25s")[0]
+ result = Series(np.nan, index=rng).asof(date)
+ assert isna(result)
+
+ # test name is propagated
+ result = Series(np.nan, index=[1, 2, 3, 4], name="test").asof([4, 5])
+ expected = Series(np.nan, index=[4, 5], name="test")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_astype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_astype.py
new file mode 100644
index 0000000000000000000000000000000000000000..07869d40d85a519022d7c12a37a0afd279d01df5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_astype.py
@@ -0,0 +1,686 @@
+from datetime import (
+ datetime,
+ timedelta,
+)
+from importlib import reload
+import string
+import sys
+
+import numpy as np
+import pytest
+
+from pandas._libs.tslibs import iNaT
+from pandas.errors import Pandas4Warning
+import pandas.util._test_decorators as td
+
+from pandas import (
+ NA,
+ Categorical,
+ CategoricalDtype,
+ DatetimeTZDtype,
+ Index,
+ Interval,
+ NaT,
+ Series,
+ Timedelta,
+ Timestamp,
+ cut,
+ date_range,
+ to_datetime,
+)
+import pandas._testing as tm
+
+
+def rand_str(nchars: int) -> str:
+ """
+ Generate one random byte string.
+ """
+ RANDS_CHARS = np.array(
+ list(string.ascii_letters + string.digits), dtype=(np.str_, 1)
+ )
+ return "".join(np.random.default_rng(2).choice(RANDS_CHARS, nchars))
+
+
+class TestAstypeAPI:
+ def test_astype_unitless_dt64_raises(self):
+ # GH#47844
+ ser = Series(["1970-01-01", "1970-01-01", "1970-01-01"], dtype="datetime64[ns]")
+ df = ser.to_frame()
+
+ msg = "Casting to unit-less dtype 'datetime64' is not supported"
+ with pytest.raises(TypeError, match=msg):
+ ser.astype(np.datetime64)
+ with pytest.raises(TypeError, match=msg):
+ df.astype(np.datetime64)
+ with pytest.raises(TypeError, match=msg):
+ ser.astype("datetime64")
+ with pytest.raises(TypeError, match=msg):
+ df.astype("datetime64")
+
+ def test_arg_for_errors_in_astype(self):
+ # see GH#14878
+ ser = Series([1, 2, 3])
+
+ msg = (
+ r"Expected value of kwarg 'errors' to be one of \['raise', "
+ r"'ignore'\]\. Supplied value is 'False'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ser.astype(np.float64, errors=False)
+
+ ser.astype(np.int8, errors="raise")
+
+ @pytest.mark.parametrize("dtype_class", [dict, Series])
+ def test_astype_dict_like(self, dtype_class):
+ # see GH#7271
+ ser = Series(range(0, 10, 2), name="abc")
+
+ dt1 = dtype_class({"abc": str})
+ result = ser.astype(dt1)
+ expected = Series(["0", "2", "4", "6", "8"], name="abc", dtype="str")
+ tm.assert_series_equal(result, expected)
+
+ dt2 = dtype_class({"abc": "float64"})
+ result = ser.astype(dt2)
+ expected = Series([0.0, 2.0, 4.0, 6.0, 8.0], dtype="float64", name="abc")
+ tm.assert_series_equal(result, expected)
+
+ dt3 = dtype_class({"abc": str, "def": str})
+ msg = (
+ "Only the Series name can be used for the key in Series dtype "
+ r"mappings\."
+ )
+ with pytest.raises(KeyError, match=msg):
+ ser.astype(dt3)
+
+ dt4 = dtype_class({0: str})
+ with pytest.raises(KeyError, match=msg):
+ ser.astype(dt4)
+
+ # GH#16717
+ # if dtypes provided is empty, it should error
+ if dtype_class is Series:
+ dt5 = dtype_class({}, dtype=object)
+ else:
+ dt5 = dtype_class({})
+
+ with pytest.raises(KeyError, match=msg):
+ ser.astype(dt5)
+
+
+class TestAstype:
+ @pytest.mark.parametrize("tz", [None, "UTC", "US/Pacific"])
+ def test_astype_object_to_dt64_non_nano(self, tz):
+ # GH#55756, GH#54620
+ ts = Timestamp("2999-01-01")
+ dtype = "M8[us]"
+ if tz is not None:
+ dtype = f"M8[us, {tz}]"
+ vals = [ts, "2999-01-02 03:04:05.678910", 2500]
+ ser = Series(vals, dtype=object)
+ result = ser.astype(dtype)
+
+ # The 2500 is interpreted as microseconds, consistent with what
+ # we would get if we created DatetimeIndexes from vals[:2] and vals[2:]
+ # and concated the results.
+ pointwise = [
+ vals[0].tz_localize(tz),
+ Timestamp(vals[1], tz=tz),
+ to_datetime(vals[2], unit="us", utc=True).tz_convert(tz),
+ ]
+ exp_vals = [x.as_unit("us").asm8 for x in pointwise]
+ exp_arr = np.array(exp_vals, dtype="M8[us]")
+ expected = Series(exp_arr, dtype="M8[us]")
+ if tz is not None:
+ expected = expected.dt.tz_localize("UTC").dt.tz_convert(tz)
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_mixed_object_to_dt64tz(self):
+ # pre-2.0 this raised ValueError bc of tz mismatch
+ # xref GH#32581
+ ts = Timestamp("2016-01-04 05:06:07", tz="US/Pacific")
+ ts2 = ts.tz_convert("Asia/Tokyo")
+
+ ser = Series([ts, ts2], dtype=object)
+ res = ser.astype("datetime64[ns, Europe/Brussels]")
+ expected = Series(
+ [ts.tz_convert("Europe/Brussels"), ts2.tz_convert("Europe/Brussels")],
+ dtype="datetime64[ns, Europe/Brussels]",
+ )
+ tm.assert_series_equal(res, expected)
+
+ @pytest.mark.parametrize("dtype", np.typecodes["All"])
+ def test_astype_empty_constructor_equality(self, dtype):
+ # see GH#15524
+
+ if dtype not in (
+ "S",
+ "V", # poor support (if any) currently
+ "M",
+ "m", # Generic timestamps raise a ValueError. Already tested.
+ ):
+ init_empty = Series([], dtype=dtype)
+ as_type_empty = Series([]).astype(dtype)
+ tm.assert_series_equal(init_empty, as_type_empty)
+
+ @pytest.mark.parametrize("dtype", [str, np.str_])
+ @pytest.mark.parametrize(
+ "data",
+ [
+ [string.digits * 10, rand_str(63), rand_str(64), rand_str(1000)],
+ [string.digits * 10, rand_str(63), rand_str(64), np.nan, 1.0],
+ ],
+ )
+ def test_astype_str_map(self, dtype, data, using_infer_string):
+ # see GH#4405
+ series = Series(data)
+ using_string_dtype = using_infer_string and dtype is str
+ result = series.astype(dtype)
+ if using_string_dtype:
+ expected = series.map(lambda val: str(val) if val is not np.nan else np.nan)
+ else:
+ expected = series.map(str)
+ if using_infer_string:
+ expected = expected.astype(object)
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_float_to_period(self):
+ result = Series([np.nan]).astype("period[D]")
+ expected = Series([NaT], dtype="period[D]")
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_no_pandas_dtype(self):
+ # https://github.com/pandas-dev/pandas/pull/24866
+ ser = Series([1, 2], dtype="int64")
+ # Don't have NumpyEADtype in the public API, so we use `.array.dtype`,
+ # which is a NumpyEADtype.
+ result = ser.astype(ser.array.dtype)
+ tm.assert_series_equal(result, ser)
+
+ @pytest.mark.parametrize("dtype", [np.datetime64, np.timedelta64])
+ def test_astype_generic_timestamp_no_frequency(self, dtype, request):
+ # see GH#15524, GH#15987
+ data = [1]
+ ser = Series(data)
+
+ if np.dtype(dtype).name not in ["timedelta64", "datetime64"]:
+ mark = pytest.mark.xfail(reason="GH#33890 Is assigned ns unit")
+ request.applymarker(mark)
+
+ msg = (
+ rf"The '{dtype.__name__}' dtype has no unit\. "
+ rf"Please pass in '{dtype.__name__}\[ns\]' instead."
+ )
+ with pytest.raises(ValueError, match=msg):
+ ser.astype(dtype)
+
+ def test_astype_dt64_to_str(self):
+ # GH#10442 : testing astype(str) is correct for Series/DatetimeIndex
+ dti = date_range("2012-01-01", periods=3)
+ result = Series(dti).astype(str)
+ expected = Series(["2012-01-01", "2012-01-02", "2012-01-03"], dtype="str")
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_dt64tz_to_str(self):
+ # GH#10442 : testing astype(str) is correct for Series/DatetimeIndex
+ dti_tz = date_range("2012-01-01", periods=3, tz="US/Eastern")
+ result = Series(dti_tz).astype(str)
+ expected = Series(
+ [
+ "2012-01-01 00:00:00-05:00",
+ "2012-01-02 00:00:00-05:00",
+ "2012-01-03 00:00:00-05:00",
+ ],
+ dtype="str",
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_datetime(self, unit):
+ ser = Series(iNaT, dtype=f"M8[{unit}]", index=range(5))
+
+ ser = ser.astype("O")
+ assert ser.dtype == np.object_
+
+ ser = Series([datetime(2001, 1, 2, 0, 0)])
+
+ ser = ser.astype("O")
+ assert ser.dtype == np.object_
+
+ ser = Series(
+ [datetime(2001, 1, 2, 0, 0) for i in range(3)], dtype=f"M8[{unit}]"
+ )
+
+ ser[1] = np.nan
+ assert ser.dtype == f"M8[{unit}]"
+
+ ser = ser.astype("O")
+ assert ser.dtype == np.object_
+
+ def test_astype_datetime64tz(self):
+ ser = Series(date_range("20130101", periods=3, tz="US/Eastern", unit="ns"))
+
+ # astype
+ result = ser.astype(object)
+ expected = Series(ser.astype(object), dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ result = Series(ser.values).dt.tz_localize("UTC").dt.tz_convert(ser.dt.tz)
+ tm.assert_series_equal(result, ser)
+
+ # astype - object, preserves on construction
+ result = Series(ser.astype(object))
+ expected = ser.astype(object)
+ tm.assert_series_equal(result, expected)
+
+ # astype - datetime64[ns, tz]
+ msg = "Cannot use .astype to convert from timezone-naive"
+ with pytest.raises(TypeError, match=msg):
+ # dt64->dt64tz astype deprecated
+ Series(ser.values).astype("datetime64[ns, US/Eastern]")
+
+ with pytest.raises(TypeError, match=msg):
+ # dt64->dt64tz astype deprecated
+ Series(ser.values).astype(ser.dtype)
+
+ result = ser.astype("datetime64[ns, CET]")
+ expected = Series(
+ date_range("20130101 06:00:00", periods=3, tz="CET", unit="ns")
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_str_cast_dt64(self):
+ # see GH#9757
+ ts = Series([Timestamp("2010-01-04 00:00:00")])
+ res = ts.astype(str)
+
+ expected = Series(["2010-01-04"], dtype="str")
+ tm.assert_series_equal(res, expected)
+
+ ts = Series([Timestamp("2010-01-04 00:00:00", tz="US/Eastern")])
+ res = ts.astype(str)
+
+ expected = Series(["2010-01-04 00:00:00-05:00"], dtype="str")
+ tm.assert_series_equal(res, expected)
+
+ def test_astype_str_cast_td64(self):
+ # see GH#9757
+
+ td = Series([Timedelta(1, unit="D")])
+ ser = td.astype(str)
+
+ expected = Series(["1 days"], dtype="str")
+ tm.assert_series_equal(ser, expected)
+
+ def test_dt64_series_astype_object(self):
+ dt64ser = Series(date_range("20130101", periods=3))
+ result = dt64ser.astype(object)
+ assert isinstance(result.iloc[0], datetime)
+ assert result.dtype == np.object_
+
+ def test_td64_series_astype_object(self):
+ tdser = Series(["59 Days", "59 Days", "NaT"], dtype="timedelta64[ns]")
+ result = tdser.astype(object)
+ assert isinstance(result.iloc[0], timedelta)
+ assert result.dtype == np.object_
+
+ @pytest.mark.parametrize(
+ "data, dtype",
+ [
+ (["x", "y", "z"], "string[python]"),
+ pytest.param(
+ ["x", "y", "z"],
+ "string[pyarrow]",
+ marks=td.skip_if_no("pyarrow"),
+ ),
+ (["x", "y", "z"], "category"),
+ (3 * [Timestamp("2020-01-01", tz="UTC")], None),
+ (3 * [Interval(0, 1)], None),
+ ],
+ )
+ @pytest.mark.parametrize("errors", ["raise", "ignore"])
+ def test_astype_ignores_errors_for_extension_dtypes(self, data, dtype, errors):
+ # https://github.com/pandas-dev/pandas/issues/35471
+ ser = Series(data, dtype=dtype)
+ if errors == "ignore":
+ expected = ser
+ result = ser.astype(float, errors="ignore")
+ tm.assert_series_equal(result, expected)
+ else:
+ msg = "(Cannot cast)|(could not convert)"
+ with pytest.raises((ValueError, TypeError), match=msg):
+ ser.astype(float, errors=errors)
+
+ def test_astype_from_float_to_str(self, any_float_dtype):
+ # https://github.com/pandas-dev/pandas/issues/36451
+ ser = Series([0.1], dtype=any_float_dtype)
+ result = ser.astype(str)
+ expected = Series(["0.1"], dtype="str")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "value, string_value",
+ [
+ (None, "None"),
+ (np.nan, "nan"),
+ (NA, ""),
+ ],
+ )
+ def test_astype_to_str_preserves_na(self, value, string_value, using_infer_string):
+ # https://github.com/pandas-dev/pandas/issues/36904
+ ser = Series(["a", "b", value], dtype=object)
+ result = ser.astype(str)
+ expected = Series(
+ ["a", "b", None if using_infer_string else string_value], dtype="str"
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["float32", "float64", "int64", "int32"])
+ def test_astype(self, dtype):
+ ser = Series(np.random.default_rng(2).standard_normal(5), name="foo")
+ as_typed = ser.astype(dtype)
+
+ assert as_typed.dtype == dtype
+ assert as_typed.name == ser.name
+
+ @pytest.mark.parametrize("value", [np.nan, np.inf])
+ def test_astype_cast_nan_inf_int(self, any_int_numpy_dtype, value):
+ # gh-14265: check NaN and inf raise error when converting to int
+ msg = "Cannot convert non-finite values \\(NA or inf\\) to integer"
+ ser = Series([value])
+
+ with pytest.raises(ValueError, match=msg):
+ ser.astype(any_int_numpy_dtype)
+
+ def test_astype_cast_object_int_fail(self, any_int_numpy_dtype):
+ arr = Series(["car", "house", "tree", "1"])
+ msg = r"invalid literal for int\(\) with base 10: 'car'"
+ with pytest.raises(ValueError, match=msg):
+ arr.astype(any_int_numpy_dtype)
+
+ def test_astype_float_to_uint_negatives_raise(
+ self, float_numpy_dtype, any_unsigned_int_numpy_dtype
+ ):
+ # GH#45151 We don't cast negative numbers to nonsense values
+ # TODO: same for EA float/uint dtypes, signed integers?
+ arr = np.arange(5).astype(float_numpy_dtype) - 3 # includes negatives
+ ser = Series(arr)
+
+ msg = "Cannot losslessly cast from .* to .*"
+ with pytest.raises(ValueError, match=msg):
+ ser.astype(any_unsigned_int_numpy_dtype)
+
+ with pytest.raises(ValueError, match=msg):
+ ser.to_frame().astype(any_unsigned_int_numpy_dtype)
+
+ with pytest.raises(ValueError, match=msg):
+ # We currently catch and re-raise in Index.astype
+ Index(ser).astype(any_unsigned_int_numpy_dtype)
+
+ with pytest.raises(ValueError, match=msg):
+ ser.array.astype(any_unsigned_int_numpy_dtype)
+
+ def test_astype_cast_object_int(self):
+ arr = Series(["1", "2", "3", "4"], dtype=object)
+ result = arr.astype(int)
+
+ tm.assert_series_equal(result, Series(np.arange(1, 5)))
+
+ def test_astype_unicode(self, using_infer_string):
+ # see GH#7758: A bit of magic is required to set
+ # default encoding to utf-8
+ digits = string.digits
+ test_series = [
+ Series([digits * 10, rand_str(63), rand_str(64), rand_str(1000)]),
+ Series(["データーサイエンス、お前はもう死んでいる"]),
+ ]
+
+ former_encoding = None
+
+ if sys.getdefaultencoding() == "utf-8":
+ # GH#45326 as of 2.0 Series.astype matches Index.astype by handling
+ # bytes with obj.decode() instead of str(obj)
+ item = "野菜食べないとやばい"
+ ser = Series([item.encode()])
+ result = ser.astype(np.str_)
+ expected = Series([item], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ for ser in test_series:
+ res = ser.astype(np.str_)
+ expec = ser.map(str)
+ if using_infer_string:
+ expec = expec.astype(object)
+ tm.assert_series_equal(res, expec)
+
+ # Restore the former encoding
+ if former_encoding is not None and former_encoding != "utf-8":
+ reload(sys)
+ sys.setdefaultencoding(former_encoding)
+
+ def test_astype_bytes(self):
+ # GH#39474
+ result = Series(["foo", "bar", "baz"]).astype(bytes)
+ assert result.dtypes == np.dtype("S3")
+
+ def test_astype_nan_to_bool(self):
+ # GH#43018
+ ser = Series(np.nan, dtype="object")
+ result = ser.astype("bool")
+ expected = Series(True, dtype="bool")
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_ea_to_datetimetzdtype(self, any_numeric_ea_dtype):
+ # GH37553
+ ser = Series([4, 0, 9], dtype=any_numeric_ea_dtype)
+ result = ser.astype(DatetimeTZDtype(tz="US/Pacific"))
+
+ expected = Series(
+ {
+ 0: Timestamp("1969-12-31 16:00:00.000000004-08:00", tz="US/Pacific"),
+ 1: Timestamp("1969-12-31 16:00:00.000000000-08:00", tz="US/Pacific"),
+ 2: Timestamp("1969-12-31 16:00:00.000000009-08:00", tz="US/Pacific"),
+ }
+ )
+
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_retain_attrs(self, any_numpy_dtype):
+ # GH#44414
+ ser = Series([0, 1, 2, 3])
+ ser.attrs["Location"] = "Michigan"
+
+ result = ser.astype(any_numpy_dtype).attrs
+ expected = ser.attrs
+
+ tm.assert_dict_equal(expected, result)
+
+
+class TestAstypeString:
+ @pytest.mark.parametrize(
+ "data, dtype",
+ [
+ ([True, NA], "boolean"),
+ (["A", NA], "category"),
+ (["2020-10-10", "2020-10-10"], "datetime64[ns]"),
+ (["2020-10-10", "2020-10-10", NaT], "datetime64[ns]"),
+ (
+ ["2012-01-01 00:00:00-05:00", NaT],
+ "datetime64[ns, US/Eastern]",
+ ),
+ ([1, None], "UInt16"),
+ (["1/1/2021", "2/1/2021"], "period[M]"),
+ (["1/1/2021", "2/1/2021", NaT], "period[M]"),
+ (["1 Day", "59 Days", NaT], "timedelta64[us]"),
+ # currently no way to parse IntervalArray from a list of strings
+ ],
+ )
+ def test_astype_string_to_extension_dtype_roundtrip(
+ self, data, dtype, request, nullable_string_dtype
+ ):
+ if dtype == "boolean":
+ mark = pytest.mark.xfail(
+ reason="TODO StringArray.astype() with missing values #GH40566"
+ )
+ request.applymarker(mark)
+ # GH-40351
+ ser = Series(data, dtype=dtype)
+
+ # Note: just passing .astype(dtype) fails for dtype="category"
+ # with bc ser.dtype.categories will be object dtype whereas
+ # result.dtype.categories will have string dtype
+ result = ser.astype(nullable_string_dtype).astype(ser.dtype)
+ tm.assert_series_equal(result, ser)
+
+
+class TestAstypeCategorical:
+ def test_astype_categorical_to_other(self):
+ cat = Categorical([f"{i} - {i + 499}" for i in range(0, 10000, 500)])
+ ser = Series(np.random.default_rng(2).integers(0, 10000, 100)).sort_values()
+ ser = cut(ser, range(0, 10500, 500), right=False, labels=cat)
+
+ expected = ser
+ tm.assert_series_equal(ser.astype("category"), expected)
+ tm.assert_series_equal(ser.astype(CategoricalDtype()), expected)
+ msg = r"Cannot cast object|str dtype to float64"
+ with pytest.raises(ValueError, match=msg):
+ ser.astype("float64")
+
+ cat = Series(Categorical(["a", "b", "b", "a", "a", "c", "c", "c"]))
+ exp = Series(["a", "b", "b", "a", "a", "c", "c", "c"], dtype="str")
+ tm.assert_series_equal(cat.astype("str"), exp)
+ s2 = Series(Categorical(["1", "2", "3", "4"]))
+ exp2 = Series([1, 2, 3, 4]).astype("int")
+ tm.assert_series_equal(s2.astype("int"), exp2)
+
+ # object don't sort correctly, so just compare that we have the same
+ # values
+ def cmp(a, b):
+ tm.assert_almost_equal(np.sort(np.unique(a)), np.sort(np.unique(b)))
+
+ expected = Series(np.array(ser.values), name="value_group")
+ cmp(ser.astype("object"), expected)
+ cmp(ser.astype(np.object_), expected)
+
+ # array conversion
+ tm.assert_almost_equal(np.array(ser), np.array(ser.values))
+
+ tm.assert_series_equal(ser.astype("category"), ser)
+ tm.assert_series_equal(ser.astype(CategoricalDtype()), ser)
+
+ roundtrip_expected = ser.cat.set_categories(
+ ser.cat.categories.sort_values()
+ ).cat.remove_unused_categories()
+ result = ser.astype("object").astype("category")
+ tm.assert_series_equal(result, roundtrip_expected)
+ result = ser.astype("object").astype(CategoricalDtype())
+ tm.assert_series_equal(result, roundtrip_expected)
+
+ def test_astype_categorical_invalid_conversions(self):
+ # invalid conversion (these are NOT a dtype)
+ cat = Categorical([f"{i} - {i + 499}" for i in range(0, 10000, 500)])
+ ser = Series(np.random.default_rng(2).integers(0, 10000, 100)).sort_values()
+ ser = cut(ser, range(0, 10500, 500), right=False, labels=cat)
+
+ msg = "dtype '' not understood"
+ with pytest.raises(TypeError, match=msg):
+ ser.astype(Categorical)
+ with pytest.raises(TypeError, match=msg):
+ ser.astype("object").astype(Categorical)
+
+ def test_astype_categoricaldtype(self):
+ ser = Series(["a", "b", "a"])
+ result = ser.astype(CategoricalDtype(["a", "b"], ordered=True))
+ expected = Series(Categorical(["a", "b", "a"], ordered=True))
+ tm.assert_series_equal(result, expected)
+
+ result = ser.astype(CategoricalDtype(["a", "b"], ordered=False))
+ expected = Series(Categorical(["a", "b", "a"], ordered=False))
+ tm.assert_series_equal(result, expected)
+
+ result = ser.astype(CategoricalDtype(["a", "b", "c"], ordered=False))
+ expected = Series(
+ Categorical(["a", "b", "a"], categories=["a", "b", "c"], ordered=False)
+ )
+ tm.assert_series_equal(result, expected)
+ tm.assert_index_equal(result.cat.categories, Index(["a", "b", "c"]))
+
+ @pytest.mark.parametrize("name", [None, "foo"])
+ @pytest.mark.parametrize("dtype_ordered", [True, False])
+ @pytest.mark.parametrize("series_ordered", [True, False])
+ def test_astype_categorical_to_categorical(
+ self, name, dtype_ordered, series_ordered
+ ):
+ # GH#10696, GH#18593
+ s_data = list("abcaacbab")
+ s_dtype = CategoricalDtype(list("bac"), ordered=series_ordered)
+ ser = Series(s_data, dtype=s_dtype, name=name)
+
+ # unspecified categories
+ dtype = CategoricalDtype(ordered=dtype_ordered)
+ result = ser.astype(dtype)
+ exp_dtype = CategoricalDtype(s_dtype.categories, dtype_ordered)
+ expected = Series(s_data, name=name, dtype=exp_dtype)
+ tm.assert_series_equal(result, expected)
+
+ # different categories
+ dtype = CategoricalDtype(list("adc"), dtype_ordered)
+ msg = "Constructing a Categorical with a dtype and values containing"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ result = ser.astype(dtype)
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ expected = Series(s_data, name=name, dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ if dtype_ordered is False:
+ # not specifying ordered, so only test once
+ expected = ser
+ result = ser.astype("category")
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_bool_missing_to_categorical(self):
+ # GH-19182
+ ser = Series([True, False, np.nan])
+ assert ser.dtypes == np.object_
+
+ result = ser.astype(CategoricalDtype(categories=[True, False]))
+ expected = Series(Categorical([True, False, np.nan], categories=[True, False]))
+ tm.assert_series_equal(result, expected)
+
+ def test_astype_categories_raises(self):
+ # deprecated GH#17636, removed in GH#27141
+ ser = Series(["a", "b", "a"])
+ with pytest.raises(TypeError, match="got an unexpected"):
+ ser.astype("category", categories=["a", "b"], ordered=True)
+
+ @pytest.mark.parametrize("items", [["a", "b", "c", "a"], [1, 2, 3, 1]])
+ def test_astype_from_categorical(self, items):
+ ser = Series(items)
+ exp = Series(Categorical(items))
+ res = ser.astype("category")
+ tm.assert_series_equal(res, exp)
+
+ def test_astype_from_categorical_with_keywords(self):
+ # with keywords
+ lst = ["a", "b", "c", "a"]
+ ser = Series(lst)
+ exp = Series(Categorical(lst, ordered=True))
+ res = ser.astype(CategoricalDtype(None, ordered=True))
+ tm.assert_series_equal(res, exp)
+
+ exp = Series(Categorical(lst, categories=list("abcdef"), ordered=True))
+ res = ser.astype(CategoricalDtype(list("abcdef"), ordered=True))
+ tm.assert_series_equal(res, exp)
+
+ def test_astype_timedelta64_with_np_nan(self):
+ # GH45798
+ result = Series([Timedelta(1), np.nan], dtype="timedelta64[ns]")
+ expected = Series([Timedelta(1), NaT], dtype="timedelta64[ns]")
+ tm.assert_series_equal(result, expected)
+
+ @td.skip_if_no("pyarrow")
+ def test_astype_int_na_string(self):
+ # GH#57418
+ ser = Series([12, NA], dtype="Int64[pyarrow]")
+ result = ser.astype("string[pyarrow]")
+ expected = Series(["12", NA], dtype="string[pyarrow]")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_autocorr.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_autocorr.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1d768cf02f37baceb01b9ef39b91e419cef1e0f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_autocorr.py
@@ -0,0 +1,30 @@
+import numpy as np
+
+
+class TestAutoCorr:
+ def test_autocorr(self, datetime_series):
+ # Just run the function
+ corr1 = datetime_series.autocorr()
+
+ # Now run it with the lag parameter
+ corr2 = datetime_series.autocorr(lag=1)
+
+ # corr() with lag needs Series of at least length 2
+ if len(datetime_series) <= 2:
+ assert np.isnan(corr1)
+ assert np.isnan(corr2)
+ else:
+ assert corr1 == corr2
+
+ # Choose a random lag between 1 and length of Series - 2
+ # and compare the result with the Series corr() function
+ n = 1 + np.random.default_rng(2).integers(max(1, len(datetime_series) - 2))
+ corr1 = datetime_series.corr(datetime_series.shift(n))
+ corr2 = datetime_series.autocorr(lag=n)
+
+ # corr() with lag needs Series of at least length 2
+ if len(datetime_series) <= 2:
+ assert np.isnan(corr1)
+ assert np.isnan(corr2)
+ else:
+ assert corr1 == corr2
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_between.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_between.py
new file mode 100644
index 0000000000000000000000000000000000000000..f035767e2ce0e99fd3e03df7d6eb953cb0f7a22e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_between.py
@@ -0,0 +1,74 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Series,
+ bdate_range,
+ date_range,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestBetween:
+ def test_between(self):
+ series = Series(date_range("1/1/2000", periods=10))
+ left, right = series[[2, 7]]
+
+ result = series.between(left, right)
+ expected = (series >= left) & (series <= right)
+ tm.assert_series_equal(result, expected)
+
+ def test_between_datetime_object_dtype(self):
+ ser = Series(bdate_range("1/1/2000", periods=20), dtype=object)
+ ser[::2] = np.nan
+
+ result = ser[ser.between(ser[3], ser[17])]
+ expected = ser[3:18].dropna()
+ tm.assert_series_equal(result, expected)
+
+ result = ser[ser.between(ser[3], ser[17], inclusive="neither")]
+ expected = ser[5:16].dropna()
+ tm.assert_series_equal(result, expected)
+
+ def test_between_period_values(self):
+ ser = Series(period_range("2000-01-01", periods=10, freq="D"))
+ left, right = ser[[2, 7]]
+ result = ser.between(left, right)
+ expected = (ser >= left) & (ser <= right)
+ tm.assert_series_equal(result, expected)
+
+ def test_between_inclusive_string(self):
+ # GH 40628
+ series = Series(date_range("1/1/2000", periods=10))
+ left, right = series[[2, 7]]
+
+ result = series.between(left, right, inclusive="both")
+ expected = (series >= left) & (series <= right)
+ tm.assert_series_equal(result, expected)
+
+ result = series.between(left, right, inclusive="left")
+ expected = (series >= left) & (series < right)
+ tm.assert_series_equal(result, expected)
+
+ result = series.between(left, right, inclusive="right")
+ expected = (series > left) & (series <= right)
+ tm.assert_series_equal(result, expected)
+
+ result = series.between(left, right, inclusive="neither")
+ expected = (series > left) & (series < right)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("inclusive", ["yes", True, False])
+ def test_between_error_args(self, inclusive):
+ # GH 40628
+ series = Series(date_range("1/1/2000", periods=10))
+ left, right = series[[2, 7]]
+
+ value_error_msg = (
+ "Inclusive has to be either string of 'both','left', 'right', or 'neither'."
+ )
+
+ series = Series(date_range("1/1/2000", periods=10))
+ with pytest.raises(ValueError, match=value_error_msg):
+ series.between(left, right, inclusive=inclusive)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_case_when.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_case_when.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff52cff4e1255725b5c81e08d521a74c44055375
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_case_when.py
@@ -0,0 +1,210 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+ array as pd_array,
+ col,
+ date_range,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture
+def df():
+ """
+ base dataframe for testing
+ """
+ return DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+
+
+def test_case_when_caselist_is_not_a_list(df):
+ """
+ Raise ValueError if caselist is not a list.
+ """
+ msg = "The caselist argument should be a list; "
+ msg += "instead got.+"
+ with pytest.raises(TypeError, match=msg): # GH39154
+ df["a"].case_when(caselist=())
+
+
+def test_case_when_no_caselist(df):
+ """
+ Raise ValueError if no caselist is provided.
+ """
+ msg = "provide at least one boolean condition, "
+ msg += "with a corresponding replacement."
+ with pytest.raises(ValueError, match=msg): # GH39154
+ df["a"].case_when([])
+
+
+def test_case_when_odd_caselist(df):
+ """
+ Raise ValueError if no of caselist is odd.
+ """
+ msg = "Argument 0 must have length 2; "
+ msg += "a condition and replacement; instead got length 3."
+
+ with pytest.raises(ValueError, match=msg):
+ df["a"].case_when([(df["a"].eq(1), 1, df.a.gt(1))])
+
+
+def test_case_when_raise_error_from_mask(df):
+ """
+ Raise Error from within Series.mask
+ """
+ msg = "Failed to apply condition0 and replacement0."
+ with pytest.raises(ValueError, match=msg):
+ df["a"].case_when([(df["a"].eq(1), [1, 2])])
+
+
+def test_case_when_single_condition(df):
+ """
+ Test output on a single condition.
+ """
+ result = Series([np.nan, np.nan, np.nan]).case_when([(df.a.eq(1), 1)])
+ expected = Series([1, np.nan, np.nan])
+ tm.assert_series_equal(result, expected)
+
+
+def test_case_when_multiple_conditions(df):
+ """
+ Test output when booleans are derived from a computation
+ """
+ result = Series([np.nan, np.nan, np.nan]).case_when(
+ [(df.a.eq(1), 1), (Series([False, True, False]), 2)]
+ )
+ expected = Series([1, 2, np.nan])
+ tm.assert_series_equal(result, expected)
+
+
+def test_case_when_multiple_conditions_replacement_list(df):
+ """
+ Test output when replacement is a list
+ """
+ result = Series([np.nan, np.nan, np.nan]).case_when(
+ [([True, False, False], 1), (df["a"].gt(1) & df["b"].eq(5), [1, 2, 3])]
+ )
+ expected = Series([1, 2, np.nan])
+ tm.assert_series_equal(result, expected)
+
+
+def test_case_when_multiple_conditions_replacement_extension_dtype(df):
+ """
+ Test output when replacement has an extension dtype
+ """
+ result = Series([np.nan, np.nan, np.nan]).case_when(
+ [
+ ([True, False, False], 1),
+ (df["a"].gt(1) & df["b"].eq(5), pd_array([1, 2, 3], dtype="Int64")),
+ ],
+ )
+ expected = Series([1, 2, np.nan], dtype="Float64")
+ tm.assert_series_equal(result, expected)
+
+
+def test_case_when_multiple_conditions_replacement_series(df):
+ """
+ Test output when replacement is a Series
+ """
+ result = Series([np.nan, np.nan, np.nan]).case_when(
+ [
+ (np.array([True, False, False]), 1),
+ (df["a"].gt(1) & df["b"].eq(5), Series([1, 2, 3])),
+ ],
+ )
+ expected = Series([1, 2, np.nan])
+ tm.assert_series_equal(result, expected)
+
+
+def test_case_when_non_range_index():
+ """
+ Test output if index is not RangeIndex
+ """
+ rng = np.random.default_rng(seed=123)
+ dates = date_range("1/1/2000", periods=8)
+ df = DataFrame(
+ rng.standard_normal(size=(8, 4)), index=dates, columns=["A", "B", "C", "D"]
+ )
+ result = Series(5, index=df.index, name="A").case_when([(df.A.gt(0), df.B)])
+ expected = df.A.mask(df.A.gt(0), df.B).where(df.A.gt(0), 5)
+ tm.assert_series_equal(result, expected)
+
+
+def test_case_when_callable():
+ """
+ Test output on a callable
+ """
+ # https://numpy.org/doc/stable/reference/generated/numpy.piecewise.html
+ x = np.linspace(-2.5, 2.5, 6)
+ ser = Series(x)
+ result = ser.case_when(
+ caselist=[
+ (lambda df: df < 0, lambda df: -df),
+ (lambda df: df >= 0, lambda df: df),
+ ]
+ )
+ expected = np.piecewise(x, [x < 0, x >= 0], [lambda x: -x, lambda x: x])
+ tm.assert_series_equal(result, Series(expected))
+
+
+def test_case_when_expression_condition(df):
+ # GH 64070
+ result = df.assign(out=col("a").case_when([(col("a") > 1, 10), (col("a") <= 1, 5)]))
+ expected = df.assign(out=Series([5, 10, 10], index=df.index))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_case_when_expression_replacement(df):
+ # GH 64070
+ result = df.assign(
+ out=col("a").case_when(
+ [(df["a"] > 1, col("a") + 100), (df["a"] <= 1, col("a") + 1)]
+ )
+ )
+ expected = df.assign(out=Series([2, 102, 103], index=df.index))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_case_when_expression_in_assign():
+ # GH 64070
+ df = DataFrame({"age": [65, 30], "name": ["Jason", "Anna"]})
+ result = df.assign(elderly=col("age").case_when([(col("name").eq("Jason"), 1)]))
+ expected = df.assign(elderly=Series([1, 30], index=df.index))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_case_when_expression_mixed_args_in_assign():
+ # GH 64070
+ df = DataFrame({"name": ["Jason", "Amy", "Bob"], "age": [42, 10, 5]})
+ caselist = [
+ (lambda s: s < 10, col("age") + 100),
+ (col("name").eq("Jason"), 1),
+ ]
+ result = df.assign(elderly=col("age").case_when(caselist=caselist))
+ expected = df.assign(elderly=Series([1, 10, 105], index=df.index))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_case_when_expression_math_condition_in_assign():
+ # GH 64070
+ df = DataFrame({"a": [-1, 1, 2], "b": [0, -2, 1]})
+ caselist = [(col("a") + col("b") > 0, 99)]
+ result = df.assign(flag=col("a").case_when(caselist=caselist))
+ expected = df.assign(flag=Series([-1, 1, 99], index=df.index))
+ tm.assert_frame_equal(result, expected)
+
+
+def test_case_when_expression_missing_values_in_assign():
+ # GH 64070
+ df = DataFrame({"age": [np.nan, 5.0, 20.0]})
+ caselist = [
+ (col("age").isna(), "missing"),
+ (col("age") > 10, "adult"),
+ ]
+ result = df.assign(group=col("age").case_when(caselist=caselist))
+ expected = df.assign(
+ group=Series(["missing", 5.0, "adult"], index=df.index, dtype=object)
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_clip.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_clip.py
new file mode 100644
index 0000000000000000000000000000000000000000..293f034ea322c91499e00fb87c3cc81ce451863c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_clip.py
@@ -0,0 +1,157 @@
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+from pandas.errors import OutOfBoundsDatetime
+
+import pandas as pd
+from pandas import (
+ Series,
+ Timestamp,
+ isna,
+ notna,
+)
+import pandas._testing as tm
+
+
+class TestSeriesClip:
+ def test_clip(self, datetime_series):
+ val = datetime_series.median()
+
+ assert datetime_series.clip(lower=val).min() == val
+ assert datetime_series.clip(upper=val).max() == val
+
+ result = datetime_series.clip(-0.5, 0.5)
+ expected = np.clip(datetime_series, -0.5, 0.5)
+ tm.assert_series_equal(result, expected)
+ assert isinstance(expected, Series)
+
+ def test_clip_types_and_nulls(self):
+ sers = [
+ Series([np.nan, 1.0, 2.0, 3.0]),
+ Series([None, "a", "b", "c"]),
+ Series(pd.to_datetime([np.nan, 1, 2, 3], unit="D")),
+ ]
+
+ for s in sers:
+ thresh = s[2]
+ lower = s.clip(lower=thresh)
+ upper = s.clip(upper=thresh)
+ assert lower[notna(lower)].min() == thresh
+ assert upper[notna(upper)].max() == thresh
+ assert list(isna(s)) == list(isna(lower))
+ assert list(isna(s)) == list(isna(upper))
+
+ def test_series_clipping_with_na_values(self, any_numeric_ea_dtype):
+ # Ensure that clipping method can handle NA values with out failing
+ # GH#40581
+
+ ser = Series([pd.NA, 1.0, 3.0], dtype=any_numeric_ea_dtype)
+ s_clipped_upper = ser.clip(upper=2.0)
+ s_clipped_lower = ser.clip(lower=2.0)
+
+ expected_upper = Series([pd.NA, 1.0, 2.0], dtype=any_numeric_ea_dtype)
+ expected_lower = Series([pd.NA, 2.0, 3.0], dtype=any_numeric_ea_dtype)
+
+ tm.assert_series_equal(s_clipped_upper, expected_upper)
+ tm.assert_series_equal(s_clipped_lower, expected_lower)
+
+ def test_clip_with_na_args(self):
+ """Should process np.nan argument as None"""
+ # GH#17276
+ s = Series([1, 2, 3])
+
+ tm.assert_series_equal(s.clip(np.nan), Series([1, 2, 3]))
+ tm.assert_series_equal(s.clip(upper=np.nan, lower=np.nan), Series([1, 2, 3]))
+
+ # GH#19992
+
+ res = s.clip(lower=[0, 4, np.nan])
+ tm.assert_series_equal(res, Series([1, 4, 3.0]))
+ res = s.clip(upper=[1, np.nan, 1])
+ tm.assert_series_equal(res, Series([1, 2, 1.0]))
+
+ # GH#40420
+ s = Series([1, 2, 3])
+ result = s.clip(0, [np.nan, np.nan, np.nan])
+ tm.assert_series_equal(s, result)
+
+ def test_clip_against_series(self):
+ # GH#6966
+
+ s = Series([1.0, 1.0, 4.0])
+
+ lower = Series([1.0, 2.0, 3.0])
+ upper = Series([1.5, 2.5, 3.5])
+
+ tm.assert_series_equal(s.clip(lower, upper), Series([1.0, 2.0, 3.5]))
+ tm.assert_series_equal(s.clip(1.5, upper), Series([1.5, 1.5, 3.5]))
+
+ @pytest.mark.parametrize("inplace", [True, False])
+ @pytest.mark.parametrize("upper", [[1, 2, 3], np.asarray([1, 2, 3])])
+ def test_clip_against_list_like(self, inplace, upper):
+ # GH#15390
+ original = Series([5, 6, 7])
+ result = original.clip(upper=upper, inplace=inplace)
+ expected = Series([1, 2, 3])
+
+ if inplace:
+ assert result is original
+ tm.assert_series_equal(result, expected, check_exact=True)
+
+ def test_clip_with_datetimes(self):
+ # GH#11838
+ # naive and tz-aware datetimes
+
+ t = Timestamp("2015-12-01 09:30:30")
+ s = Series([Timestamp("2015-12-01 09:30:00"), Timestamp("2015-12-01 09:31:00")])
+ result = s.clip(upper=t)
+ expected = Series(
+ [Timestamp("2015-12-01 09:30:00"), Timestamp("2015-12-01 09:30:30")]
+ )
+ tm.assert_series_equal(result, expected)
+
+ t = Timestamp("2015-12-01 09:30:30", tz="US/Eastern")
+ s = Series(
+ [
+ Timestamp("2015-12-01 09:30:00", tz="US/Eastern"),
+ Timestamp("2015-12-01 09:31:00", tz="US/Eastern"),
+ ]
+ )
+ result = s.clip(upper=t)
+ expected = Series(
+ [
+ Timestamp("2015-12-01 09:30:00", tz="US/Eastern"),
+ Timestamp("2015-12-01 09:30:30", tz="US/Eastern"),
+ ]
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_clip_with_timestamps_and_oob_datetimes_object(self):
+ # GH-42794
+ ser = Series([datetime(1, 1, 1), datetime(9999, 9, 9)], dtype=object)
+
+ result = ser.clip(lower=Timestamp.min, upper=Timestamp.max)
+ expected = Series([Timestamp.min, Timestamp.max], dtype=object)
+
+ tm.assert_series_equal(result, expected)
+
+ def test_clip_with_timestamps_and_oob_datetimes_non_nano(self):
+ # GH#56410
+ dtype = "M8[us]"
+ ser = Series([datetime(1, 1, 1), datetime(9999, 9, 9)], dtype=dtype)
+
+ msg = (
+ r"Incompatible \(high-resolution\) value for dtype='datetime64\[us\]'. "
+ "Explicitly cast before operating"
+ )
+ with pytest.raises(OutOfBoundsDatetime, match=msg):
+ ser.clip(lower=Timestamp.min, upper=Timestamp.max)
+
+ lower = Timestamp.min.as_unit("us")
+ upper = Timestamp.max.as_unit("us")
+ result = ser.clip(lower=lower, upper=upper)
+ expected = Series([lower, upper], dtype=dtype)
+
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_combine.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_combine.py
new file mode 100644
index 0000000000000000000000000000000000000000..06b830d3db3e1929e82266f4d2c3082ad2ba1f12
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_combine.py
@@ -0,0 +1,26 @@
+from pandas import (
+ NA,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestCombine:
+ def test_combine_scalar(self):
+ # GH#21248
+ ser = Series([i * 10 for i in range(5)])
+ result = ser.combine(3, lambda x, y: x + y)
+ expected = Series([i * 10 + 3 for i in range(5)])
+ tm.assert_series_equal(result, expected)
+
+ result = ser.combine(22, lambda x, y: min(x, y))
+ expected = Series([min(i * 10, 22) for i in range(5)])
+ tm.assert_series_equal(result, expected)
+
+ def test_combine_series(self):
+ # GH#31899
+ s1 = Series([91, NA, 94], dtype="Int8")
+ s2 = Series([91, NA, 11], dtype="Int8")
+ result = s1.combine(s2, lambda x, y: x + y)
+ expected = Series([-74, NA, 105], dtype="Int8") # dtype should be preserved
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_combine_first.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_combine_first.py
new file mode 100644
index 0000000000000000000000000000000000000000..7de879681d74ff5108b7dcf8071cef60ddc50d68
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_combine_first.py
@@ -0,0 +1,164 @@
+from datetime import datetime
+
+import numpy as np
+
+from pandas.errors import Pandas4Warning
+
+import pandas as pd
+from pandas import (
+ Period,
+ Series,
+ date_range,
+ period_range,
+ to_datetime,
+)
+import pandas._testing as tm
+
+
+class TestCombineFirst:
+ def test_combine_first_period_datetime(self):
+ # GH#3367
+ didx = date_range(start="1950-01-31", end="1950-07-31", freq="ME")
+ pidx = period_range(start=Period("1950-1"), end=Period("1950-7"), freq="M")
+ # check to be consistent with DatetimeIndex
+ for idx in [didx, pidx]:
+ a = Series([1, np.nan, np.nan, 4, 5, np.nan, 7], index=idx)
+ b = Series([9, 9, 9, 9, 9, 9, 9], index=idx)
+
+ result = a.combine_first(b)
+ expected = Series([1, 9, 9, 4, 5, 9, 7], index=idx, dtype=np.float64)
+ tm.assert_series_equal(result, expected)
+
+ def test_combine_first_name(self, datetime_series):
+ result = datetime_series.combine_first(datetime_series[:5])
+ assert result.name == datetime_series.name
+
+ def test_combine_first(self, using_infer_string):
+ values = np.arange(20, dtype=np.float64)
+ series = Series(values, index=np.arange(20, dtype=np.int64))
+
+ series_copy = series * 2
+ series_copy[::2] = np.nan
+
+ # nothing used from the input
+ combined = series.combine_first(series_copy)
+
+ tm.assert_series_equal(combined, series)
+
+ # Holes filled from input
+ combined = series_copy.combine_first(series)
+ assert np.isfinite(combined).all()
+
+ tm.assert_series_equal(combined[::2], series[::2])
+ tm.assert_series_equal(combined[1::2], series_copy[1::2])
+
+ # mixed types
+ index = pd.Index([str(i) for i in range(20)])
+ floats = Series(np.random.default_rng(2).standard_normal(20), index=index)
+ strings = Series([str(i) for i in range(10)], index=index[::2], dtype=object)
+
+ combined = strings.combine_first(floats)
+
+ tm.assert_series_equal(strings, combined.loc[index[::2]])
+ tm.assert_series_equal(floats[1::2].astype(object), combined.loc[index[1::2]])
+
+ # corner case
+ ser = Series([1.0, 2, 3], index=[0, 1, 2])
+ empty = Series([], index=[], dtype=object)
+ result = ser.combine_first(empty)
+ if not using_infer_string:
+ ser.index = ser.index.astype("O")
+ tm.assert_series_equal(result, ser.astype(object))
+
+ def test_combine_first_dt64(self, unit):
+ s0 = to_datetime(Series(["2010", np.nan])).dt.as_unit(unit)
+ s1 = to_datetime(Series([np.nan, "2011"])).dt.as_unit(unit)
+ rs = s0.combine_first(s1)
+ xp = to_datetime(Series(["2010", "2011"])).dt.as_unit(unit)
+ tm.assert_series_equal(rs, xp)
+
+ def test_combine_first_dt64_casting_deprecation(self, unit):
+ # GH#62931
+ s0 = to_datetime(Series(["2010", np.nan])).dt.as_unit(unit)
+ s1 = Series([np.nan, "2011"])
+
+ msg = "Silently casting non-datetime 'other' to datetime"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ rs = s0.combine_first(s1)
+
+ xp = Series([datetime(2010, 1, 1), "2011"], dtype=f"datetime64[{unit}]")
+ if unit in ["s", "ms"]:
+ # TODO: should _cast_pointwise_result attempt to preserve unit?
+ xp = xp.dt.as_unit("us")
+ tm.assert_series_equal(rs, xp)
+
+ def test_combine_first_dt_tz_values(self, tz_naive_fixture):
+ ser1 = Series(
+ pd.DatetimeIndex(["20150101", "20150102", "20150103"], tz=tz_naive_fixture),
+ name="ser1",
+ )
+ ser2 = Series(
+ pd.DatetimeIndex(["20160514", "20160515", "20160516"], tz=tz_naive_fixture),
+ index=[2, 3, 4],
+ name="ser2",
+ )
+ result = ser1.combine_first(ser2)
+ exp_vals = pd.DatetimeIndex(
+ ["20150101", "20150102", "20150103", "20160515", "20160516"],
+ tz=tz_naive_fixture,
+ )
+ exp = Series(exp_vals, name="ser1")
+ tm.assert_series_equal(exp, result)
+
+ def test_combine_first_timezone_series_with_empty_series(self):
+ # GH 41800
+ time_index = date_range(
+ datetime(2021, 1, 1, 1),
+ datetime(2021, 1, 1, 10),
+ freq="h",
+ tz="Europe/Rome",
+ )
+ s1 = Series(range(10), index=time_index)
+ s2 = Series(index=time_index)
+ result = s1.combine_first(s2)
+ tm.assert_series_equal(result, s1.astype(np.float64))
+
+ def test_combine_first_preserves_dtype(self):
+ # GH51764
+ s1 = Series([1666880195890293744, 1666880195890293837])
+ s2 = Series([1, 2, 3])
+ result = s1.combine_first(s2)
+ expected = Series([1666880195890293744, 1666880195890293837, 3])
+ tm.assert_series_equal(result, expected)
+
+ def test_combine_mixed_timezone(self):
+ # GH 26283
+ uniform_tz = Series({pd.Timestamp("2019-05-01", tz="UTC"): 1.0})
+ multi_tz = Series(
+ {
+ pd.Timestamp("2019-05-01 01:00:00+0100", tz="Europe/London"): 2.0,
+ pd.Timestamp("2019-05-02", tz="UTC"): 3.0,
+ }
+ )
+
+ result = uniform_tz.combine_first(multi_tz)
+ expected = Series(
+ [1.0, 3.0],
+ index=pd.Index(
+ [
+ pd.Timestamp("2019-05-01 00:00:00+00:00", tz="UTC"),
+ pd.Timestamp("2019-05-02 00:00:00+00:00", tz="UTC"),
+ ],
+ dtype="object",
+ ),
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_combine_first_none_not_nan(self):
+ # GH#58977
+ s1 = Series([None, None, None], index=["a", "b", "c"])
+ s2 = Series([None, None, None], index=["b", "c", "d"])
+
+ result = s1.combine_first(s2)
+ expected = Series([None] * 4, index=["a", "b", "c", "d"])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_compare.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_compare.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a57d5139b62cd9a9ec374638fee5cd596d00b4e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_compare.py
@@ -0,0 +1,140 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+def test_compare_axis(axis):
+ # GH#30429
+ s1 = pd.Series(["a", "b", "c"])
+ s2 = pd.Series(["x", "b", "z"])
+
+ result = s1.compare(s2, align_axis=axis)
+
+ if axis in (1, "columns"):
+ indices = pd.Index([0, 2])
+ columns = pd.Index(["self", "other"])
+ expected = pd.DataFrame(
+ [["a", "x"], ["c", "z"]], index=indices, columns=columns
+ )
+ tm.assert_frame_equal(result, expected)
+ else:
+ indices = pd.MultiIndex.from_product([[0, 2], ["self", "other"]])
+ expected = pd.Series(["a", "x", "c", "z"], index=indices)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "keep_shape, keep_equal",
+ [
+ (True, False),
+ (False, True),
+ (True, True),
+ # False, False case is already covered in test_compare_axis
+ ],
+)
+def test_compare_various_formats(keep_shape, keep_equal):
+ s1 = pd.Series(["a", "b", "c"])
+ s2 = pd.Series(["x", "b", "z"])
+
+ result = s1.compare(s2, keep_shape=keep_shape, keep_equal=keep_equal)
+
+ if keep_shape:
+ indices = pd.Index([0, 1, 2])
+ columns = pd.Index(["self", "other"])
+ if keep_equal:
+ expected = pd.DataFrame(
+ [["a", "x"], ["b", "b"], ["c", "z"]], index=indices, columns=columns
+ )
+ else:
+ expected = pd.DataFrame(
+ [["a", "x"], [np.nan, np.nan], ["c", "z"]],
+ index=indices,
+ columns=columns,
+ )
+ else:
+ indices = pd.Index([0, 2])
+ columns = pd.Index(["self", "other"])
+ expected = pd.DataFrame(
+ [["a", "x"], ["c", "z"]], index=indices, columns=columns
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_compare_with_equal_nulls():
+ # We want to make sure two NaNs are considered the same
+ # and dropped where applicable
+ s1 = pd.Series(["a", "b", np.nan])
+ s2 = pd.Series(["x", "b", np.nan])
+
+ result = s1.compare(s2)
+ expected = pd.DataFrame([["a", "x"]], columns=["self", "other"])
+ tm.assert_frame_equal(result, expected)
+
+
+def test_compare_with_non_equal_nulls():
+ # We want to make sure the relevant NaNs do not get dropped
+ s1 = pd.Series(["a", "b", "c"])
+ s2 = pd.Series(["x", "b", np.nan])
+
+ result = s1.compare(s2, align_axis=0)
+
+ indices = pd.MultiIndex.from_product([[0, 2], ["self", "other"]])
+ expected = pd.Series(["a", "x", "c", np.nan], index=indices)
+ tm.assert_series_equal(result, expected)
+
+
+def test_compare_multi_index():
+ index = pd.MultiIndex.from_arrays([[0, 0, 1], [0, 1, 2]])
+ s1 = pd.Series(["a", "b", "c"], index=index)
+ s2 = pd.Series(["x", "b", "z"], index=index)
+
+ result = s1.compare(s2, align_axis=0)
+
+ indices = pd.MultiIndex.from_arrays(
+ [[0, 0, 1, 1], [0, 0, 2, 2], ["self", "other", "self", "other"]]
+ )
+ expected = pd.Series(["a", "x", "c", "z"], index=indices)
+ tm.assert_series_equal(result, expected)
+
+
+def test_compare_different_indices():
+ msg = "Can only compare identically-labeled Series objects"
+ ser1 = pd.Series([1, 2, 3], index=["a", "b", "c"])
+ ser2 = pd.Series([1, 2, 3], index=["a", "b", "d"])
+ with pytest.raises(ValueError, match=msg):
+ ser1.compare(ser2)
+
+
+def test_compare_different_lengths():
+ msg = "Can only compare identically-labeled Series objects"
+ ser1 = pd.Series([1, 2, 3])
+ ser2 = pd.Series([1, 2, 3, 4])
+ with pytest.raises(ValueError, match=msg):
+ ser1.compare(ser2)
+
+
+def test_compare_datetime64_and_string():
+ # Issue https://github.com/pandas-dev/pandas/issues/45506
+ # Catch OverflowError when comparing datetime64 and string
+ data = [
+ {"a": "2015-07-01", "b": "08335394550"},
+ {"a": "2015-07-02", "b": "+49 (0) 0345 300033"},
+ {"a": "2015-07-03", "b": "+49(0)2598 04457"},
+ {"a": "2015-07-04", "b": "0741470003"},
+ {"a": "2015-07-05", "b": "04181 83668"},
+ ]
+ dtypes = {"a": "datetime64[ns]", "b": "string"}
+ df = pd.DataFrame(data=data).astype(dtypes)
+
+ result_eq1 = df["a"].eq(df["b"])
+ result_eq2 = df["a"] == df["b"]
+ result_neq = df["a"] != df["b"]
+
+ expected_eq = pd.Series([False] * 5) # For .eq and ==
+ expected_neq = pd.Series([True] * 5) # For !=
+
+ tm.assert_series_equal(result_eq1, expected_eq)
+ tm.assert_series_equal(result_eq2, expected_eq)
+ tm.assert_series_equal(result_neq, expected_neq)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_convert_dtypes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_convert_dtypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..57757fcf9ac29fc92186418d53fec372e321ebfa
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_convert_dtypes.py
@@ -0,0 +1,340 @@
+from itertools import product
+
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+import pandas.util._test_decorators as td
+
+import pandas as pd
+import pandas._testing as tm
+
+
+class TestSeriesConvertDtypes:
+ @pytest.mark.parametrize(
+ "data, maindtype, expected_default, expected_other",
+ [
+ (
+ # data
+ [1, 2, 3],
+ # original dtype
+ np.dtype("int32"),
+ # default expected dtype
+ "Int32",
+ # exceptions on expected dtype
+ {("convert_integer", False): np.dtype("int32")},
+ ),
+ (
+ [1, 2, 3],
+ np.dtype("int64"),
+ "Int64",
+ {("convert_integer", False): np.dtype("int64")},
+ ),
+ (
+ ["x", "y", "z"],
+ np.dtype("O"),
+ pd.StringDtype(),
+ {("convert_string", False): np.dtype("O")},
+ ),
+ (
+ [True, False, np.nan],
+ np.dtype("O"),
+ pd.BooleanDtype(),
+ {("convert_boolean", False): np.dtype("O")},
+ ),
+ (
+ ["h", "i", np.nan],
+ np.dtype("O"),
+ pd.StringDtype(),
+ {("convert_string", False): np.dtype("O")},
+ ),
+ ( # GH32117
+ ["h", "i", 1],
+ np.dtype("O"),
+ np.dtype("O"),
+ {},
+ ),
+ (
+ [10, np.nan, 20],
+ np.dtype("float"),
+ "Int64",
+ {
+ ("convert_integer", False, "convert_floating", True): "Float64",
+ ("convert_integer", False, "convert_floating", False): np.dtype(
+ "float"
+ ),
+ },
+ ),
+ (
+ [np.nan, 100.5, 200],
+ np.dtype("float"),
+ "Float64",
+ {("convert_floating", False): np.dtype("float")},
+ ),
+ (
+ [3, 4, 5],
+ "Int8",
+ "Int8",
+ {},
+ ),
+ (
+ [[1, 2], [3, 4], [5]],
+ None,
+ np.dtype("O"),
+ {},
+ ),
+ (
+ [4, 5, 6],
+ np.dtype("uint32"),
+ "UInt32",
+ {("convert_integer", False): np.dtype("uint32")},
+ ),
+ (
+ [-10, 12, 13],
+ np.dtype("i1"),
+ "Int8",
+ {("convert_integer", False): np.dtype("i1")},
+ ),
+ (
+ [1.2, 1.3],
+ np.dtype("float32"),
+ "Float32",
+ {("convert_floating", False): np.dtype("float32")},
+ ),
+ (
+ [1, 2.0],
+ object,
+ "Int64",
+ {
+ ("convert_integer", False): "Float64",
+ ("convert_integer", False, "convert_floating", False): np.dtype(
+ "float"
+ ),
+ ("infer_objects", False): np.dtype("object"),
+ },
+ ),
+ (
+ [1, 2.5],
+ object,
+ "Float64",
+ {
+ ("convert_floating", False): np.dtype("float"),
+ ("infer_objects", False): np.dtype("object"),
+ },
+ ),
+ (["a", "b"], pd.CategoricalDtype(), pd.CategoricalDtype(), {}),
+ (
+ pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("s"),
+ pd.DatetimeTZDtype(tz="UTC"),
+ pd.DatetimeTZDtype(tz="UTC"),
+ {},
+ ),
+ (
+ pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("ms"),
+ pd.DatetimeTZDtype(tz="UTC"),
+ pd.DatetimeTZDtype(tz="UTC"),
+ {},
+ ),
+ (
+ pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("us"),
+ pd.DatetimeTZDtype(tz="UTC"),
+ pd.DatetimeTZDtype(tz="UTC"),
+ {},
+ ),
+ (
+ pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("ns"),
+ pd.DatetimeTZDtype(tz="UTC"),
+ pd.DatetimeTZDtype(tz="UTC"),
+ {},
+ ),
+ (
+ pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("ns"),
+ "datetime64[ns]",
+ np.dtype("datetime64[ns]"),
+ {},
+ ),
+ (
+ pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("ns"),
+ object,
+ np.dtype("datetime64[ns]"),
+ {("infer_objects", False): np.dtype("object")},
+ ),
+ (
+ pd.period_range("1/1/2011", freq="M", periods=3),
+ None,
+ pd.PeriodDtype("M"),
+ {},
+ ),
+ (
+ pd.arrays.IntervalArray([pd.Interval(0, 1), pd.Interval(1, 5)]),
+ None,
+ pd.IntervalDtype("int64", "right"),
+ {},
+ ),
+ ],
+ )
+ @pytest.mark.parametrize("params", product(*[(True, False)] * 5))
+ def test_convert_dtypes(
+ self,
+ data,
+ maindtype,
+ expected_default,
+ expected_other,
+ params,
+ using_infer_string,
+ using_nan_is_na,
+ ):
+ if (
+ hasattr(data, "dtype")
+ and lib.is_np_dtype(data.dtype, "M")
+ and isinstance(maindtype, pd.DatetimeTZDtype)
+ ):
+ # this astype is deprecated in favor of tz_localize
+ msg = "Cannot use .astype to convert from timezone-naive dtype"
+ with pytest.raises(TypeError, match=msg):
+ pd.Series(data, dtype=maindtype)
+ return
+
+ if maindtype is not None:
+ series = pd.Series(data, dtype=maindtype)
+ else:
+ series = pd.Series(data)
+
+ result = series.convert_dtypes(*params)
+
+ param_names = [
+ "infer_objects",
+ "convert_string",
+ "convert_integer",
+ "convert_boolean",
+ "convert_floating",
+ ]
+ params_dict = dict(zip(param_names, params, strict=True))
+
+ expected_dtype = expected_default
+ for spec, dtype in expected_other.items():
+ if all(
+ params_dict[key] is val
+ for key, val in zip(spec[::2], spec[1::2], strict=False)
+ ):
+ expected_dtype = dtype
+ if (
+ using_infer_string
+ and expected_default == "string"
+ and expected_dtype == object
+ and params[0]
+ and not params[1]
+ ):
+ # If convert_string=False and infer_objects=True, we end up with the
+ # default string dtype instead of preserving object for string data
+ expected_dtype = pd.StringDtype(na_value=np.nan)
+ if (
+ not using_nan_is_na
+ and expected_dtype == "Int64"
+ and isinstance(data[1], float)
+ and np.isnan(data[1])
+ ):
+ if params_dict["convert_floating"]:
+ expected_dtype = "Float64"
+ else:
+ expected_dtype = "float64"
+
+ expected = pd.Series(data, dtype=expected_dtype)
+ tm.assert_series_equal(result, expected)
+
+ # Test that it is a copy
+ copy = series.copy(deep=True)
+
+ if result.notna().sum() > 0 and result.dtype in ["interval[int64, right]"]:
+ with pytest.raises(TypeError, match="Invalid value"):
+ result[result.notna()] = np.nan
+ else:
+ result[result.notna()] = pd.NA
+
+ # Make sure original not changed
+ tm.assert_series_equal(series, copy)
+
+ def test_convert_string_dtype(self, nullable_string_dtype):
+ # https://github.com/pandas-dev/pandas/issues/31731 -> converting columns
+ # that are already string dtype
+ df = pd.DataFrame(
+ {"A": ["a", "b", pd.NA], "B": ["ä", "ö", "ü"]}, dtype=nullable_string_dtype
+ )
+ result = df.convert_dtypes()
+ tm.assert_frame_equal(df, result)
+
+ def test_convert_bool_dtype(self):
+ # GH32287
+ df = pd.DataFrame({"A": pd.array([True])})
+ tm.assert_frame_equal(df, df.convert_dtypes())
+
+ def test_convert_byte_string_dtype(self):
+ # GH-43183
+ byte_str = b"binary-string"
+
+ df = pd.DataFrame(data={"A": byte_str}, index=[0])
+ result = df.convert_dtypes()
+ expected = df
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "infer_objects, dtype", [(True, "Int64"), (False, "object")]
+ )
+ def test_convert_dtype_object_with_na(self, infer_objects, dtype):
+ # GH#48791
+ ser = pd.Series([1, pd.NA])
+ result = ser.convert_dtypes(infer_objects=infer_objects)
+ expected = pd.Series([1, pd.NA], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "infer_objects, dtype", [(True, "Float64"), (False, "object")]
+ )
+ def test_convert_dtype_object_with_na_float(self, infer_objects, dtype):
+ # GH#48791
+ ser = pd.Series([1.5, pd.NA])
+ result = ser.convert_dtypes(infer_objects=infer_objects)
+ expected = pd.Series([1.5, pd.NA], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_convert_dtypes_pyarrow_to_np_nullable(self):
+ # GH 53648
+ pytest.importorskip("pyarrow")
+ ser = pd.Series(range(2), dtype="int32[pyarrow]")
+ result = ser.convert_dtypes(dtype_backend="numpy_nullable")
+ expected = pd.Series(range(2), dtype="Int32")
+ tm.assert_series_equal(result, expected)
+
+ def test_convert_dtypes_pyarrow_null(self):
+ # GH#55346
+ pa = pytest.importorskip("pyarrow")
+ ser = pd.Series([None, None])
+ result = ser.convert_dtypes(dtype_backend="pyarrow")
+ expected = pd.Series([None, None], dtype=pd.ArrowDtype(pa.null()))
+ tm.assert_series_equal(result, expected)
+
+ @td.skip_if_no("pyarrow")
+ @pytest.mark.parametrize("categories", [None, ["S1", "S2"]])
+ def test_convert_empty_categorical_to_pyarrow(self, categories):
+ # GH#59934
+ ser = pd.Series(pd.Categorical([None] * 5, categories=categories))
+ converted = ser.convert_dtypes(dtype_backend="pyarrow")
+ expected = ser
+ tm.assert_series_equal(converted, expected)
+
+ def test_convert_dtype_pyarrow_timezone_preserve(self):
+ # GH 60237
+ pytest.importorskip("pyarrow")
+ ser = pd.Series(
+ pd.to_datetime(range(5), utc=True, unit="h"),
+ dtype="timestamp[ns, tz=UTC][pyarrow]",
+ )
+ result = ser.convert_dtypes(dtype_backend="pyarrow")
+ expected = ser.copy()
+ tm.assert_series_equal(result, expected)
+
+ def test_convert_dtypes_complex(self):
+ # GH 60129
+ ser = pd.Series([1.5 + 3.0j, 1.5 - 3.0j])
+ result = ser.convert_dtypes()
+ tm.assert_series_equal(result, ser)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_copy.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_copy.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad5417d330d510fcfba81a8bfaf26e138e462074
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_copy.py
@@ -0,0 +1,77 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+
+
+class TestCopy:
+ @pytest.mark.parametrize("deep", ["default", None, False, True])
+ def test_copy(self, deep):
+ ser = Series(np.arange(10), dtype="float64")
+
+ # default deep is True
+ if deep == "default":
+ ser2 = ser.copy()
+ else:
+ ser2 = ser.copy(deep=deep)
+
+ # INFO(CoW) a shallow copy doesn't yet copy the data
+ # but parent will not be modified (CoW)
+ if deep is None or deep is False:
+ assert np.may_share_memory(ser.values, ser2.values)
+ else:
+ assert not np.may_share_memory(ser.values, ser2.values)
+
+ ser2[::2] = np.nan
+
+ # Did not modify original Series
+ assert np.isnan(ser2[0])
+ assert not np.isnan(ser[0])
+
+ @pytest.mark.filterwarnings("ignore:Setting a value on a view:FutureWarning")
+ @pytest.mark.parametrize("deep", ["default", None, False, True])
+ def test_copy_tzaware(self, deep):
+ # GH#11794
+ # copy of tz-aware
+ expected = Series([Timestamp("2012/01/01", tz="UTC")])
+ expected2 = Series([Timestamp("1999/01/01", tz="UTC")])
+
+ ser = Series([Timestamp("2012/01/01", tz="UTC")])
+
+ if deep == "default":
+ ser2 = ser.copy()
+ else:
+ ser2 = ser.copy(deep=deep)
+
+ # INFO(CoW) a shallow copy doesn't yet copy the data
+ # but parent will not be modified (CoW)
+ if deep is None or deep is False:
+ assert np.may_share_memory(ser.values, ser2.values)
+ else:
+ assert not np.may_share_memory(ser.values, ser2.values)
+
+ ser2[0] = Timestamp("1999/01/01", tz="UTC")
+
+ # Did not modify original Series
+ tm.assert_series_equal(ser2, expected2)
+ tm.assert_series_equal(ser, expected)
+
+ def test_copy_name(self, datetime_series):
+ result = datetime_series.copy()
+ assert result.name == datetime_series.name
+
+ def test_copy_index_name_checking(self, datetime_series):
+ # don't want to be able to modify the index stored elsewhere after
+ # making a copy
+
+ datetime_series.index.name = None
+ assert datetime_series.index.name is None
+ assert datetime_series is datetime_series
+
+ cp = datetime_series.copy()
+ cp.index.name = "foo"
+ assert datetime_series.index.name is None
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_count.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_count.py
new file mode 100644
index 0000000000000000000000000000000000000000..2172a3205ef55487dfe20ab95848330f49717412
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_count.py
@@ -0,0 +1,24 @@
+import numpy as np
+
+from pandas import (
+ Categorical,
+ Series,
+)
+
+
+class TestSeriesCount:
+ def test_count(self, datetime_series):
+ assert datetime_series.count() == len(datetime_series)
+
+ datetime_series[::2] = np.nan
+
+ assert datetime_series.count() == np.isfinite(datetime_series).sum()
+
+ def test_count_categorical(self):
+ ser = Series(
+ Categorical(
+ [np.nan, 1, 2, np.nan], categories=[5, 4, 3, 2, 1], ordered=True
+ )
+ )
+ result = ser.count()
+ assert result == 2
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_cov_corr.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_cov_corr.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a4d48fb76940605b8ce80be6cdca21d9fb5419e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_cov_corr.py
@@ -0,0 +1,186 @@
+import math
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Series,
+ date_range,
+ isna,
+)
+import pandas._testing as tm
+
+
+class TestSeriesCov:
+ def test_cov(self, datetime_series):
+ # full overlap
+ tm.assert_almost_equal(
+ datetime_series.cov(datetime_series), datetime_series.std() ** 2
+ )
+
+ # partial overlap
+ tm.assert_almost_equal(
+ datetime_series[:15].cov(datetime_series[5:]),
+ datetime_series[5:15].std() ** 2,
+ )
+
+ # No overlap
+ assert np.isnan(datetime_series[::2].cov(datetime_series[1::2]))
+
+ # all NA
+ cp = datetime_series[:10].copy()
+ cp[:] = np.nan
+ assert isna(cp.cov(cp))
+
+ # min_periods
+ assert isna(datetime_series[:15].cov(datetime_series[5:], min_periods=12))
+
+ ts1 = datetime_series[:15].reindex(datetime_series.index)
+ ts2 = datetime_series[5:].reindex(datetime_series.index)
+ assert isna(ts1.cov(ts2, min_periods=12))
+
+ @pytest.mark.parametrize("test_ddof", [None, 0, 1, 2, 3])
+ @pytest.mark.parametrize("dtype", ["float64", "Float64"])
+ def test_cov_ddof(self, test_ddof, dtype):
+ # GH#34611
+ np_array1 = np.random.default_rng(2).random(10)
+ np_array2 = np.random.default_rng(2).random(10)
+
+ s1 = Series(np_array1, dtype=dtype)
+ s2 = Series(np_array2, dtype=dtype)
+
+ result = s1.cov(s2, ddof=test_ddof)
+ expected = np.cov(np_array1, np_array2, ddof=test_ddof)[0][1]
+ assert math.isclose(expected, result)
+
+
+class TestSeriesCorr:
+ def test_corr(self, datetime_series, any_float_dtype):
+ stats = pytest.importorskip("scipy.stats")
+
+ datetime_series = datetime_series.astype(any_float_dtype)
+
+ # full overlap
+ tm.assert_almost_equal(datetime_series.corr(datetime_series), 1)
+
+ # partial overlap
+ tm.assert_almost_equal(datetime_series[:15].corr(datetime_series[5:]), 1)
+
+ assert isna(datetime_series[:15].corr(datetime_series[5:], min_periods=12))
+
+ ts1 = datetime_series[:15].reindex(datetime_series.index)
+ ts2 = datetime_series[5:].reindex(datetime_series.index)
+ assert isna(ts1.corr(ts2, min_periods=12))
+
+ # No overlap
+ assert np.isnan(datetime_series[::2].corr(datetime_series[1::2]))
+
+ # all NA
+ cp = datetime_series[:10].copy()
+ cp[:] = np.nan
+ assert isna(cp.corr(cp))
+
+ A = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ result = A.corr(A)
+ expected, _ = stats.pearsonr(A, A)
+ tm.assert_almost_equal(result, expected)
+
+ def test_corr_rank(self):
+ stats = pytest.importorskip("scipy.stats")
+
+ # kendall and spearman
+ B = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ A = Series(
+ np.concatenate([np.arange(5, dtype=np.float64)] * 2),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ result = A.corr(B, method="kendall")
+ expected = stats.kendalltau(A, B)[0]
+ tm.assert_almost_equal(result, expected)
+
+ result = A.corr(B, method="spearman")
+ expected = stats.spearmanr(A, B)[0]
+ tm.assert_almost_equal(result, expected)
+
+ # results from R
+ A = Series(
+ [
+ -0.89926396,
+ 0.94209606,
+ -1.03289164,
+ -0.95445587,
+ 0.76910310,
+ -0.06430576,
+ -2.09704447,
+ 0.40660407,
+ -0.89926396,
+ 0.94209606,
+ ]
+ )
+ B = Series(
+ [
+ -1.01270225,
+ -0.62210117,
+ -1.56895827,
+ 0.59592943,
+ -0.01680292,
+ 1.17258718,
+ -1.06009347,
+ -0.10222060,
+ -0.89076239,
+ 0.89372375,
+ ]
+ )
+ kexp = 0.4319297
+ sexp = 0.5853767
+ tm.assert_almost_equal(A.corr(B, method="kendall"), kexp)
+ tm.assert_almost_equal(A.corr(B, method="spearman"), sexp)
+
+ def test_corr_invalid_method(self):
+ # GH PR #22298
+ s1 = Series(np.random.default_rng(2).standard_normal(10))
+ s2 = Series(np.random.default_rng(2).standard_normal(10))
+ msg = "method must be either 'pearson', 'spearman', 'kendall', or a callable, "
+ with pytest.raises(ValueError, match=msg):
+ s1.corr(s2, method="____")
+
+ def test_corr_callable_method(self, datetime_series):
+ # simple correlation example
+ # returns 1 if exact equality, 0 otherwise
+ my_corr = lambda a, b: 1.0 if (a == b).all() else 0.0
+
+ # simple example
+ s1 = Series([1, 2, 3, 4, 5])
+ s2 = Series([5, 4, 3, 2, 1])
+ expected = 0
+ tm.assert_almost_equal(s1.corr(s2, method=my_corr), expected)
+
+ # full overlap
+ tm.assert_almost_equal(
+ datetime_series.corr(datetime_series, method=my_corr), 1.0
+ )
+
+ # partial overlap
+ tm.assert_almost_equal(
+ datetime_series[:15].corr(datetime_series[5:], method=my_corr), 1.0
+ )
+
+ # No overlap
+ assert np.isnan(
+ datetime_series[::2].corr(datetime_series[1::2], method=my_corr)
+ )
+
+ # dataframe example
+ df = pd.DataFrame([s1, s2])
+ expected = pd.DataFrame([{0: 1.0, 1: 0}, {0: 0, 1: 1.0}])
+ tm.assert_almost_equal(df.transpose().corr(method=my_corr), expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_describe.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_describe.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3246fd35227c98e1a476483351bb62878a61dfe
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_describe.py
@@ -0,0 +1,201 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import (
+ is_complex_dtype,
+ is_extension_array_dtype,
+)
+
+from pandas import (
+ NA,
+ Period,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestSeriesDescribe:
+ def test_describe_ints(self):
+ ser = Series([0, 1, 2, 3, 4], name="int_data")
+ result = ser.describe()
+ expected = Series(
+ [5, 2, ser.std(), 0, 1, 2, 3, 4],
+ name="int_data",
+ index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_describe_bools(self):
+ ser = Series([True, True, False, False, False], name="bool_data")
+ result = ser.describe()
+ expected = Series(
+ [5, 2, False, 3], name="bool_data", index=["count", "unique", "top", "freq"]
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_describe_strs(self):
+ ser = Series(["a", "a", "b", "c", "d"], name="str_data")
+ result = ser.describe()
+ expected = Series(
+ [5, 4, "a", 2], name="str_data", index=["count", "unique", "top", "freq"]
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_describe_timedelta64(self):
+ ser = Series(
+ [
+ Timedelta("1 days"),
+ Timedelta("2 days"),
+ Timedelta("3 days"),
+ Timedelta("4 days"),
+ Timedelta("5 days"),
+ ],
+ name="timedelta_data",
+ )
+ result = ser.describe()
+ expected = Series(
+ [5, ser[2], ser.std(), ser[0], ser[1], ser[2], ser[3], ser[4]],
+ name="timedelta_data",
+ index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_describe_period(self):
+ ser = Series(
+ [Period("2020-01", "M"), Period("2020-01", "M"), Period("2019-12", "M")],
+ name="period_data",
+ )
+ result = ser.describe()
+ expected = Series(
+ [3, 2, ser[0], 2],
+ name="period_data",
+ index=["count", "unique", "top", "freq"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_describe_empty_object(self):
+ # https://github.com/pandas-dev/pandas/issues/27183
+ s = Series([None, None], dtype=object)
+ result = s.describe()
+ expected = Series(
+ [0, 0, np.nan, np.nan],
+ dtype=object,
+ index=["count", "unique", "top", "freq"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = s[:0].describe()
+ tm.assert_series_equal(result, expected)
+ # ensure NaN, not None
+ assert np.isnan(result.iloc[2])
+ assert np.isnan(result.iloc[3])
+
+ def test_describe_with_tz(self, tz_naive_fixture):
+ # GH 21332
+ tz = tz_naive_fixture
+ name = str(tz_naive_fixture)
+ start = Timestamp(2018, 1, 1)
+ end = Timestamp(2018, 1, 5)
+ s = Series(date_range(start, end, tz=tz), name=name)
+ result = s.describe()
+ expected = Series(
+ [
+ 5,
+ Timestamp(2018, 1, 3).tz_localize(tz),
+ start.tz_localize(tz),
+ s[1],
+ s[2],
+ s[3],
+ end.tz_localize(tz),
+ ],
+ name=name,
+ index=["count", "mean", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_describe_with_tz_numeric(self):
+ name = tz = "CET"
+ start = Timestamp(2018, 1, 1)
+ end = Timestamp(2018, 1, 5)
+ s = Series(date_range(start, end, tz=tz), name=name)
+
+ result = s.describe()
+
+ expected = Series(
+ [
+ 5,
+ Timestamp("2018-01-03 00:00:00", tz=tz),
+ Timestamp("2018-01-01 00:00:00", tz=tz),
+ Timestamp("2018-01-02 00:00:00", tz=tz),
+ Timestamp("2018-01-03 00:00:00", tz=tz),
+ Timestamp("2018-01-04 00:00:00", tz=tz),
+ Timestamp("2018-01-05 00:00:00", tz=tz),
+ ],
+ name=name,
+ index=["count", "mean", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_datetime_is_numeric_includes_datetime(self):
+ s = Series(date_range("2012", periods=3))
+ result = s.describe()
+ expected = Series(
+ [
+ 3,
+ Timestamp("2012-01-02"),
+ Timestamp("2012-01-01"),
+ Timestamp("2012-01-01T12:00:00"),
+ Timestamp("2012-01-02"),
+ Timestamp("2012-01-02T12:00:00"),
+ Timestamp("2012-01-03"),
+ ],
+ index=["count", "mean", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.filterwarnings("ignore:Casting complex values to real discards")
+ def test_numeric_result_dtype(self, any_numeric_dtype):
+ # GH#48340 - describe should always return float on non-complex numeric input
+ if is_extension_array_dtype(any_numeric_dtype):
+ dtype = "Float64"
+ else:
+ dtype = "complex128" if is_complex_dtype(any_numeric_dtype) else None
+
+ ser = Series([0, 1], dtype=any_numeric_dtype)
+ if dtype == "complex128":
+ with pytest.raises(
+ TypeError, match=r"^a must be an array of real numbers$"
+ ):
+ ser.describe()
+ return
+ result = ser.describe()
+ expected = Series(
+ [
+ 2.0,
+ 0.5,
+ ser.std(),
+ 0,
+ 0.25,
+ 0.5,
+ 0.75,
+ 1.0,
+ ],
+ index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"],
+ dtype=dtype,
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_describe_one_element_ea(self):
+ # GH#52515
+ ser = Series([0.0], dtype="Float64")
+ with tm.assert_produces_warning(None):
+ result = ser.describe()
+ expected = Series(
+ [1, 0, NA, 0, 0, 0, 0, 0],
+ dtype="Float64",
+ index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"],
+ )
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_diff.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_diff.py
new file mode 100644
index 0000000000000000000000000000000000000000..dde51e5ac56ded5d785cc8e910e040f61dff534b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_diff.py
@@ -0,0 +1,92 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Series,
+ TimedeltaIndex,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestSeriesDiff:
+ def test_diff_series_requires_integer(self):
+ series = Series(np.random.default_rng(2).standard_normal(2))
+ with pytest.raises(ValueError, match="periods must be an integer"):
+ series.diff(1.5)
+
+ def test_diff_np(self):
+ # TODO(__array_function__): could make np.diff return a Series
+ # matching ser.diff()
+
+ ser = Series(np.arange(5))
+
+ res = np.diff(ser)
+ expected = np.array([1, 1, 1, 1])
+ tm.assert_numpy_array_equal(res, expected)
+
+ def test_diff_int(self):
+ # int dtype
+ a = 10000000000000000
+ b = a + 1
+ ser = Series([a, b])
+
+ result = ser.diff()
+ assert result[1] == 1
+
+ def test_diff_tz(self):
+ # Combined datetime diff, normal diff and boolean diff test
+ ts = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10),
+ name="ts",
+ )
+ ts.diff()
+
+ # neg n
+ result = ts.diff(-1)
+ expected = ts - ts.shift(-1)
+ tm.assert_series_equal(result, expected)
+
+ # 0
+ result = ts.diff(0)
+ expected = ts - ts
+ tm.assert_series_equal(result, expected)
+
+ def test_diff_dt64(self):
+ # datetime diff (GH#3100)
+ ser = Series(date_range("20130102", periods=5))
+ result = ser.diff()
+ expected = ser - ser.shift(1)
+ tm.assert_series_equal(result, expected)
+
+ # timedelta diff
+ result = result - result.shift(1) # previous result
+ expected = expected.diff() # previously expected
+ tm.assert_series_equal(result, expected)
+
+ def test_diff_dt64tz(self):
+ # with tz
+ ser = Series(
+ date_range("2000-01-01 09:00:00", periods=5, tz="US/Eastern"),
+ name="foo",
+ )
+ result = ser.diff()
+ expected = Series(TimedeltaIndex(["NaT"] + ["1 days"] * 4), name="foo")
+ tm.assert_series_equal(result, expected)
+
+ def test_diff_bool(self):
+ # boolean series (test for fixing #17294)
+ data = [False, True, True, False, False]
+ output = [np.nan, True, False, True, False]
+ ser = Series(data)
+ result = ser.diff()
+ expected = Series(output)
+ tm.assert_series_equal(result, expected)
+
+ def test_diff_object_dtype(self):
+ # object series
+ ser = Series([False, True, 5.0, np.nan, True, False])
+ result = ser.diff()
+ expected = ser - ser.shift(1)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_drop.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_drop.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2a5a3324e886f894ae70dc9c77ac2bdcfd2c3db
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_drop.py
@@ -0,0 +1,99 @@
+import pytest
+
+from pandas import (
+ Index,
+ Series,
+)
+import pandas._testing as tm
+from pandas.api.types import is_bool_dtype
+
+
+@pytest.mark.parametrize(
+ "data, index, drop_labels, axis, expected_data, expected_index",
+ [
+ # Unique Index
+ ([1, 2], ["one", "two"], ["two"], 0, [1], ["one"]),
+ ([1, 2], ["one", "two"], ["two"], "rows", [1], ["one"]),
+ ([1, 1, 2], ["one", "two", "one"], ["two"], 0, [1, 2], ["one", "one"]),
+ # GH 5248 Non-Unique Index
+ ([1, 1, 2], ["one", "two", "one"], "two", 0, [1, 2], ["one", "one"]),
+ ([1, 1, 2], ["one", "two", "one"], ["one"], 0, [1], ["two"]),
+ ([1, 1, 2], ["one", "two", "one"], "one", 0, [1], ["two"]),
+ ],
+)
+def test_drop_unique_and_non_unique_index(
+ data, index, axis, drop_labels, expected_data, expected_index
+):
+ ser = Series(data=data, index=index)
+ result = ser.drop(drop_labels, axis=axis)
+ expected = Series(data=expected_data, index=expected_index)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "drop_labels, axis, error_type, error_desc",
+ [
+ # single string/tuple-like
+ ("bc", 0, KeyError, "not found in axis"),
+ # bad axis
+ (("a",), 0, KeyError, "not found in axis"),
+ ("one", "columns", ValueError, "No axis named columns"),
+ ],
+)
+def test_drop_exception_raised(drop_labels, axis, error_type, error_desc):
+ ser = Series(range(3), index=list("abc"))
+ with pytest.raises(error_type, match=error_desc):
+ ser.drop(drop_labels, axis=axis)
+
+
+def test_drop_with_ignore_errors():
+ # errors='ignore'
+ ser = Series(range(3), index=list("abc"))
+ result = ser.drop("bc", errors="ignore")
+ tm.assert_series_equal(result, ser)
+ result = ser.drop(["a", "d"], errors="ignore")
+ expected = ser.iloc[1:]
+ tm.assert_series_equal(result, expected)
+
+ # GH 8522
+ ser = Series([2, 3], index=[True, False])
+ assert is_bool_dtype(ser.index)
+ assert ser.index.dtype == bool
+ result = ser.drop(True)
+ expected = Series([3], index=[False])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("index", [[1, 2, 3], [1, 1, 3]])
+@pytest.mark.parametrize("drop_labels", [[], [1], [3]])
+def test_drop_empty_list(index, drop_labels):
+ # GH 21494
+ expected_index = [i for i in index if i not in drop_labels]
+ series = Series(index=index, dtype=object).drop(drop_labels)
+ expected = Series(index=expected_index, dtype=object)
+ tm.assert_series_equal(series, expected)
+
+
+@pytest.mark.parametrize(
+ "data, index, drop_labels",
+ [
+ (None, [1, 2, 3], [1, 4]),
+ (None, [1, 2, 2], [1, 4]),
+ ([2, 3], [0, 1], [False, True]),
+ ],
+)
+def test_drop_non_empty_list(data, index, drop_labels):
+ # GH 21494 and GH 16877
+ dtype = object if data is None else None
+ ser = Series(data=data, index=index, dtype=dtype)
+ with pytest.raises(KeyError, match="not found in axis"):
+ ser.drop(drop_labels)
+
+
+def test_drop_index_ea_dtype(any_numeric_ea_dtype):
+ # GH#45860
+ df = Series(100, index=Index([1, 2, 2], dtype=any_numeric_ea_dtype))
+ idx = Index([df.index[1]])
+ result = df.drop(idx)
+ expected = Series(100, index=Index([1], dtype=any_numeric_ea_dtype))
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_drop_duplicates.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_drop_duplicates.py
new file mode 100644
index 0000000000000000000000000000000000000000..2dbd61530dc41575446c4ffb009449445db24c33
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_drop_duplicates.py
@@ -0,0 +1,275 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "keep, expected",
+ [
+ ("first", Series([False, False, False, False, True, True, False])),
+ ("last", Series([False, True, True, False, False, False, False])),
+ (False, Series([False, True, True, False, True, True, False])),
+ ],
+)
+def test_drop_duplicates(any_numpy_dtype, keep, expected):
+ tc = Series([1, 0, 3, 5, 3, 0, 4], dtype=np.dtype(any_numpy_dtype))
+
+ if tc.dtype == "bool":
+ pytest.skip("tested separately in test_drop_duplicates_bool")
+
+ tm.assert_series_equal(tc.duplicated(keep=keep), expected)
+ tm.assert_series_equal(tc.drop_duplicates(keep=keep), tc[~expected])
+ sc = tc.copy()
+ return_value = sc.drop_duplicates(keep=keep, inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc[~expected])
+
+
+@pytest.mark.parametrize(
+ "keep, expected",
+ [
+ ("first", [False, False, True, True]),
+ ("last", [True, True, False, False]),
+ (False, [True, True, True, True]),
+ ],
+)
+def test_drop_duplicates_bool(keep, expected):
+ tc = Series([True, False, True, False])
+ expected = Series(expected)
+ tm.assert_series_equal(tc.duplicated(keep=keep), expected)
+ tm.assert_series_equal(tc.drop_duplicates(keep=keep), tc[~expected])
+ sc = tc.copy()
+ return_value = sc.drop_duplicates(keep=keep, inplace=True)
+ tm.assert_series_equal(sc, tc[~expected])
+ assert return_value is None
+
+
+@pytest.mark.parametrize("values", [[], list(range(5))])
+def test_drop_duplicates_no_duplicates(any_numpy_dtype, keep, values):
+ tc = Series(values, dtype=np.dtype(any_numpy_dtype))
+ expected = Series([False] * len(tc), dtype="bool")
+
+ if tc.dtype == "bool":
+ # 0 -> False and 1-> True
+ # any other value would be duplicated
+ tc = tc[:2]
+ expected = expected[:2]
+
+ tm.assert_series_equal(tc.duplicated(keep=keep), expected)
+
+ result_dropped = tc.drop_duplicates(keep=keep)
+ tm.assert_series_equal(result_dropped, tc)
+
+ # validate shallow copy
+ assert result_dropped is not tc
+
+
+class TestSeriesDropDuplicates:
+ @pytest.fixture(
+ params=["int_", "uint", "float64", "str_", "timedelta64[h]", "datetime64[D]"]
+ )
+ def dtype(self, request):
+ """
+ Fixture that provides different data types for testing.
+ """
+ return request.param
+
+ @pytest.fixture
+ def cat_series_unused_category(self, dtype, ordered):
+ """
+ Fixture that creates a Categorical Series with some unused categories.
+ """
+ # Test case 1
+ cat_array = np.array([1, 2, 3, 4, 5], dtype=np.dtype(dtype))
+
+ input1 = np.array([1, 2, 3, 3], dtype=np.dtype(dtype))
+ cat = Categorical(input1, categories=cat_array, ordered=ordered)
+ tc1 = Series(cat)
+ return tc1
+
+ def test_drop_duplicates_categorical_non_bool(self, cat_series_unused_category):
+ tc1 = cat_series_unused_category
+
+ expected = Series([False, False, False, True])
+
+ result = tc1.duplicated()
+ tm.assert_series_equal(result, expected)
+
+ result = tc1.drop_duplicates()
+ tm.assert_series_equal(result, tc1[~expected])
+
+ sc = tc1.copy()
+ return_value = sc.drop_duplicates(inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc1[~expected])
+
+ def test_drop_duplicates_categorical_non_bool_keeplast(
+ self, cat_series_unused_category
+ ):
+ tc1 = cat_series_unused_category
+
+ expected = Series([False, False, True, False])
+
+ result = tc1.duplicated(keep="last")
+ tm.assert_series_equal(result, expected)
+
+ result = tc1.drop_duplicates(keep="last")
+ tm.assert_series_equal(result, tc1[~expected])
+
+ sc = tc1.copy()
+ return_value = sc.drop_duplicates(keep="last", inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc1[~expected])
+
+ def test_drop_duplicates_categorical_non_bool_keepfalse(
+ self, cat_series_unused_category
+ ):
+ tc1 = cat_series_unused_category
+
+ expected = Series([False, False, True, True])
+
+ result = tc1.duplicated(keep=False)
+ tm.assert_series_equal(result, expected)
+
+ result = tc1.drop_duplicates(keep=False)
+ tm.assert_series_equal(result, tc1[~expected])
+
+ sc = tc1.copy()
+ return_value = sc.drop_duplicates(keep=False, inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc1[~expected])
+
+ @pytest.fixture
+ def cat_series(self, dtype, ordered):
+ """
+ Fixture that creates a Categorical Series with no unused categories.
+ """
+ cat_array = np.array([1, 2, 3, 4, 5], dtype=np.dtype(dtype))
+
+ input2 = np.array([1, 2, 3, 5, 3, 2, 4], dtype=np.dtype(dtype))
+ cat = Categorical(input2, categories=cat_array, ordered=ordered)
+ tc2 = Series(cat)
+ return tc2
+
+ def test_drop_duplicates_categorical_non_bool2(self, cat_series):
+ tc2 = cat_series
+
+ expected = Series([False, False, False, False, True, True, False])
+
+ result = tc2.duplicated()
+ tm.assert_series_equal(result, expected)
+
+ result = tc2.drop_duplicates()
+ tm.assert_series_equal(result, tc2[~expected])
+
+ sc = tc2.copy()
+ return_value = sc.drop_duplicates(inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc2[~expected])
+
+ def test_drop_duplicates_categorical_non_bool2_keeplast(self, cat_series):
+ tc2 = cat_series
+
+ expected = Series([False, True, True, False, False, False, False])
+
+ result = tc2.duplicated(keep="last")
+ tm.assert_series_equal(result, expected)
+
+ result = tc2.drop_duplicates(keep="last")
+ tm.assert_series_equal(result, tc2[~expected])
+
+ sc = tc2.copy()
+ return_value = sc.drop_duplicates(keep="last", inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc2[~expected])
+
+ def test_drop_duplicates_categorical_non_bool2_keepfalse(self, cat_series):
+ tc2 = cat_series
+
+ expected = Series([False, True, True, False, True, True, False])
+
+ result = tc2.duplicated(keep=False)
+ tm.assert_series_equal(result, expected)
+
+ result = tc2.drop_duplicates(keep=False)
+ tm.assert_series_equal(result, tc2[~expected])
+
+ sc = tc2.copy()
+ return_value = sc.drop_duplicates(keep=False, inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc2[~expected])
+
+ def test_drop_duplicates_categorical_bool(self, ordered):
+ tc = Series(
+ Categorical(
+ [True, False, True, False], categories=[True, False], ordered=ordered
+ )
+ )
+
+ expected = Series([False, False, True, True])
+ tm.assert_series_equal(tc.duplicated(), expected)
+ tm.assert_series_equal(tc.drop_duplicates(), tc[~expected])
+ sc = tc.copy()
+ return_value = sc.drop_duplicates(inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc[~expected])
+
+ expected = Series([True, True, False, False])
+ tm.assert_series_equal(tc.duplicated(keep="last"), expected)
+ tm.assert_series_equal(tc.drop_duplicates(keep="last"), tc[~expected])
+ sc = tc.copy()
+ return_value = sc.drop_duplicates(keep="last", inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc[~expected])
+
+ expected = Series([True, True, True, True])
+ tm.assert_series_equal(tc.duplicated(keep=False), expected)
+ tm.assert_series_equal(tc.drop_duplicates(keep=False), tc[~expected])
+ sc = tc.copy()
+ return_value = sc.drop_duplicates(keep=False, inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(sc, tc[~expected])
+
+ def test_drop_duplicates_categorical_bool_na(self, nulls_fixture):
+ # GH#44351
+ ser = Series(
+ Categorical(
+ [True, False, True, False, nulls_fixture],
+ categories=[True, False],
+ ordered=True,
+ )
+ )
+ result = ser.drop_duplicates()
+ expected = Series(
+ Categorical([True, False, np.nan], categories=[True, False], ordered=True),
+ index=[0, 1, 4],
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_drop_duplicates_ignore_index(self):
+ # GH#48304
+ ser = Series([1, 2, 2, 3])
+ result = ser.drop_duplicates(ignore_index=True)
+ expected = Series([1, 2, 3])
+ tm.assert_series_equal(result, expected)
+
+ def test_duplicated_arrow_dtype(self):
+ pytest.importorskip("pyarrow")
+ ser = Series([True, False, None, False], dtype="bool[pyarrow]")
+ result = ser.drop_duplicates()
+ expected = Series([True, False, None], dtype="bool[pyarrow]")
+ tm.assert_series_equal(result, expected)
+
+ def test_drop_duplicates_arrow_strings(self):
+ # GH#54904
+ pa = pytest.importorskip("pyarrow")
+ ser = Series(["a", "a"], dtype=pd.ArrowDtype(pa.string()))
+ result = ser.drop_duplicates()
+ expecetd = Series(["a"], dtype=pd.ArrowDtype(pa.string()))
+ tm.assert_series_equal(result, expecetd)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_dropna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_dropna.py
new file mode 100644
index 0000000000000000000000000000000000000000..d03fcac24003e99b724a2e7ac43b66d3d9b51bcf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_dropna.py
@@ -0,0 +1,117 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ IntervalIndex,
+ NaT,
+ Period,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+
+
+class TestDropna:
+ def test_dropna_empty(self):
+ ser = Series([], dtype=object)
+
+ assert len(ser.dropna()) == 0
+ return_value = ser.dropna(inplace=True)
+ assert return_value is None
+ assert len(ser) == 0
+
+ # invalid axis
+ msg = "No axis named 1 for object type Series"
+ with pytest.raises(ValueError, match=msg):
+ ser.dropna(axis=1)
+
+ def test_dropna_preserve_name(self, datetime_series):
+ datetime_series[:5] = np.nan
+ result = datetime_series.dropna()
+ assert result.name == datetime_series.name
+ name = datetime_series.name
+ ts = datetime_series.copy()
+ return_value = ts.dropna(inplace=True)
+ assert return_value is None
+ assert ts.name == name
+
+ def test_dropna_no_nan(self):
+ for ser in [
+ Series([1, 2, 3], name="x"),
+ Series([False, True, False], name="x"),
+ ]:
+ result = ser.dropna()
+ tm.assert_series_equal(result, ser)
+ assert result is not ser
+
+ s2 = ser.copy()
+ return_value = s2.dropna(inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(s2, ser)
+
+ def test_dropna_intervals(self):
+ ser = Series(
+ [np.nan, 1, 2, 3],
+ IntervalIndex.from_arrays([np.nan, 0, 1, 2], [np.nan, 1, 2, 3]),
+ )
+
+ result = ser.dropna()
+ expected = ser.iloc[1:]
+ tm.assert_series_equal(result, expected)
+
+ def test_dropna_period_dtype(self):
+ # GH#13737
+ ser = Series([Period("2011-01", freq="M"), Period("NaT", freq="M")])
+ result = ser.dropna()
+ expected = Series([Period("2011-01", freq="M")])
+
+ tm.assert_series_equal(result, expected)
+
+ def test_datetime64_tz_dropna(self, unit):
+ # DatetimeLikeBlock
+ ser = Series(
+ [
+ Timestamp("2011-01-01 10:00"),
+ NaT,
+ Timestamp("2011-01-03 10:00"),
+ NaT,
+ ],
+ dtype=f"M8[{unit}]",
+ )
+ result = ser.dropna()
+ expected = Series(
+ [Timestamp("2011-01-01 10:00"), Timestamp("2011-01-03 10:00")],
+ index=[0, 2],
+ dtype=f"M8[{unit}]",
+ )
+ tm.assert_series_equal(result, expected)
+
+ # DatetimeTZBlock
+ idx = DatetimeIndex(
+ ["2011-01-01 10:00", NaT, "2011-01-03 10:00", NaT], tz="Asia/Tokyo"
+ ).as_unit(unit)
+ ser = Series(idx)
+ assert ser.dtype == f"datetime64[{unit}, Asia/Tokyo]"
+ result = ser.dropna()
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"),
+ Timestamp("2011-01-03 10:00", tz="Asia/Tokyo"),
+ ],
+ index=[0, 2],
+ dtype=f"datetime64[{unit}, Asia/Tokyo]",
+ )
+ assert result.dtype == f"datetime64[{unit}, Asia/Tokyo]"
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("val", [1, 1.5])
+ def test_dropna_ignore_index(self, val):
+ # GH#31725
+ ser = Series([1, 2, val], index=[3, 2, 1])
+ result = ser.dropna(ignore_index=True)
+ expected = Series([1, 2, val])
+ tm.assert_series_equal(result, expected)
+
+ ser.dropna(ignore_index=True, inplace=True)
+ tm.assert_series_equal(ser, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_dtypes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_dtypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..82260bc2a65b9f7e387532b380b9ce013d29cbb7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_dtypes.py
@@ -0,0 +1,7 @@
+import numpy as np
+
+
+class TestSeriesDtypes:
+ def test_dtype(self, datetime_series):
+ assert datetime_series.dtype == np.dtype("float64")
+ assert datetime_series.dtypes == np.dtype("float64")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_duplicated.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_duplicated.py
new file mode 100644
index 0000000000000000000000000000000000000000..45aa9b1c3f63e93f02a07fcad1fcc7f31b6bb8ee
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_duplicated.py
@@ -0,0 +1,79 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ NA,
+ Categorical,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize(
+ "keep, expected",
+ [
+ ("first", [False, False, True, False, True]),
+ ("last", [True, True, False, False, False]),
+ (False, [True, True, True, False, True]),
+ ],
+)
+def test_duplicated_keep(keep, expected):
+ ser = Series(["a", "b", "b", "c", "a"], name="name")
+
+ result = ser.duplicated(keep=keep)
+ expected = Series(expected, name="name")
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "keep, expected",
+ [
+ ("first", [False, False, True, False, True]),
+ ("last", [True, True, False, False, False]),
+ (False, [True, True, True, False, True]),
+ ],
+)
+def test_duplicated_nan_none(keep, expected):
+ ser = Series([np.nan, 3, 3, None, np.nan], dtype=object)
+
+ result = ser.duplicated(keep=keep)
+ expected = Series(expected)
+ tm.assert_series_equal(result, expected)
+
+
+def test_duplicated_categorical_bool_na(nulls_fixture):
+ # GH#44351
+ ser = Series(
+ Categorical(
+ [True, False, True, False, nulls_fixture],
+ categories=[True, False],
+ ordered=True,
+ )
+ )
+ result = ser.duplicated()
+ expected = Series([False, False, True, True, False])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "keep, vals",
+ [
+ ("last", [True, True, False]),
+ ("first", [False, True, True]),
+ (False, [True, True, True]),
+ ],
+)
+def test_duplicated_mask(keep, vals):
+ # GH#48150
+ ser = Series([1, 2, NA, NA, NA], dtype="Int64")
+ result = ser.duplicated(keep=keep)
+ expected = Series([False, False, *vals])
+ tm.assert_series_equal(result, expected)
+
+
+def test_duplicated_mask_no_duplicated_na(keep):
+ # GH#48150
+ ser = Series([1, 2, NA], dtype="Int64")
+ result = ser.duplicated(keep=keep)
+ expected = Series([False, False, False])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_equals.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_equals.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c52eacd7e51651190531322605b7700004724f2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_equals.py
@@ -0,0 +1,133 @@
+import copy
+
+import numpy as np
+import pytest
+
+from pandas._libs.missing import is_matching_na
+
+from pandas.core.dtypes.common import is_float
+
+from pandas import (
+ Index,
+ MultiIndex,
+ Series,
+)
+
+
+@pytest.mark.parametrize(
+ "arr, idx",
+ [
+ ([1, 2, 3, 4], [0, 2, 1, 3]),
+ ([1, np.nan, 3, np.nan], [0, 2, 1, 3]),
+ (
+ [1, np.nan, 3, np.nan],
+ MultiIndex.from_tuples([(0, "a"), (1, "b"), (2, "c"), (3, "c")]),
+ ),
+ ],
+)
+def test_equals(arr, idx):
+ s1 = Series(arr, index=idx)
+ s2 = s1.copy()
+ assert s1.equals(s2)
+
+ s1[1] = 9
+ assert not s1.equals(s2)
+
+
+@pytest.mark.parametrize(
+ "val", [1, 1.1, 1 + 1j, True, "abc", [1, 2], (1, 2), {1, 2}, {"a": 1}, None]
+)
+def test_equals_list_array(val):
+ # GH20676 Verify equals operator for list of Numpy arrays
+ arr = np.array([1, 2])
+ s1 = Series([arr, arr])
+ s2 = s1.copy()
+ assert s1.equals(s2)
+
+ s1[1] = val
+ assert not s1.equals(s2)
+
+
+def test_equals_false_negative():
+ # GH8437 Verify false negative behavior of equals function for dtype object
+ arr = [False, np.nan]
+ s1 = Series(arr)
+ s2 = s1.copy()
+ s3 = Series(index=range(2), dtype=object)
+ s4 = s3.copy()
+ s5 = s3.copy()
+ s6 = s3.copy()
+
+ s3[:-1] = s4[:-1] = s5[0] = s6[0] = False
+ assert s1.equals(s1)
+ assert s1.equals(s2)
+ assert s1.equals(s3)
+ assert s1.equals(s4)
+ assert s1.equals(s5)
+ assert s5.equals(s6)
+
+
+def test_equals_matching_nas():
+ # matching but not identical NAs
+ left = Series([np.datetime64("NaT")], dtype=object)
+ right = Series([np.datetime64("NaT")], dtype=object)
+ assert left.equals(right)
+ assert Index(left).equals(Index(right))
+ assert left.array.equals(right.array)
+
+ left = Series([np.timedelta64("NaT")], dtype=object)
+ right = Series([np.timedelta64("NaT")], dtype=object)
+ assert left.equals(right)
+ assert Index(left).equals(Index(right))
+ assert left.array.equals(right.array)
+
+ left = Series([np.float64("NaN")], dtype=object)
+ right = Series([np.float64("NaN")], dtype=object)
+ assert left.equals(right)
+ assert Index(left, dtype=left.dtype).equals(Index(right, dtype=right.dtype))
+ assert left.array.equals(right.array)
+
+
+def test_equals_mismatched_nas(nulls_fixture, nulls_fixture2):
+ # GH#39650
+ left = nulls_fixture
+ right = nulls_fixture2
+ if hasattr(right, "copy"):
+ right = right.copy()
+ else:
+ right = copy.copy(right)
+
+ ser = Series([left], dtype=object)
+ ser2 = Series([right], dtype=object)
+
+ if is_matching_na(left, right):
+ assert ser.equals(ser2)
+ elif (left is None and is_float(right)) or (right is None and is_float(left)):
+ assert ser.equals(ser2)
+ else:
+ assert not ser.equals(ser2)
+
+
+def test_equals_none_vs_nan():
+ # GH#39650
+ ser = Series([1, None], dtype=object)
+ ser2 = Series([1, np.nan], dtype=object)
+
+ assert ser.equals(ser2)
+ assert Index(ser, dtype=ser.dtype).equals(Index(ser2, dtype=ser2.dtype))
+ assert ser.array.equals(ser2.array)
+
+
+def test_equals_None_vs_float():
+ # GH#44190
+ left = Series([-np.inf, np.nan, -1.0, 0.0, 1.0, 10 / 3, np.inf], dtype=object)
+ right = Series([None] * len(left))
+
+ # these series were found to be equal due to a bug, check that they are correctly
+ # found to not equal
+ assert not left.equals(right)
+ assert not right.equals(left)
+ assert not left.to_frame().equals(right.to_frame())
+ assert not right.to_frame().equals(left.to_frame())
+ assert not Index(left, dtype="object").equals(Index(right, dtype="object"))
+ assert not Index(right, dtype="object").equals(Index(left, dtype="object"))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_explode.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_explode.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c08f47c0d6783a9e606335f3289f43f96976844
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_explode.py
@@ -0,0 +1,185 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+
+def test_basic():
+ s = pd.Series([[0, 1, 2], np.nan, [], (3, 4)], index=list("abcd"), name="foo")
+ result = s.explode()
+ expected = pd.Series(
+ [0, 1, 2, np.nan, np.nan, 3, 4], index=list("aaabcdd"), dtype=object, name="foo"
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_mixed_type():
+ s = pd.Series(
+ [[0, 1, 2], np.nan, None, np.array([]), pd.Series(["a", "b"])], name="foo"
+ )
+ result = s.explode()
+ expected = pd.Series(
+ [0, 1, 2, np.nan, None, np.nan, "a", "b"],
+ index=[0, 0, 0, 1, 2, 3, 4, 4],
+ dtype=object,
+ name="foo",
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_empty():
+ s = pd.Series(dtype=object)
+ result = s.explode()
+ expected = s.copy()
+ tm.assert_series_equal(result, expected)
+
+
+def test_nested_lists():
+ s = pd.Series([[[1, 2, 3]], [1, 2], 1])
+ result = s.explode()
+ expected = pd.Series([[1, 2, 3], 1, 2, 1], index=[0, 1, 1, 2])
+ tm.assert_series_equal(result, expected)
+
+
+def test_multi_index():
+ s = pd.Series(
+ [[0, 1, 2], np.nan, [], (3, 4)],
+ name="foo",
+ index=pd.MultiIndex.from_product([list("ab"), range(2)], names=["foo", "bar"]),
+ )
+ result = s.explode()
+ index = pd.MultiIndex.from_tuples(
+ [("a", 0), ("a", 0), ("a", 0), ("a", 1), ("b", 0), ("b", 1), ("b", 1)],
+ names=["foo", "bar"],
+ )
+ expected = pd.Series(
+ [0, 1, 2, np.nan, np.nan, 3, 4], index=index, dtype=object, name="foo"
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_large():
+ s = pd.Series([range(256)]).explode()
+ result = s.explode()
+ tm.assert_series_equal(result, s)
+
+
+def test_invert_array():
+ df = pd.DataFrame({"a": pd.date_range("20190101", periods=3, tz="UTC")})
+
+ listify = df.apply(lambda x: x.array, axis=1)
+ result = listify.explode()
+ tm.assert_series_equal(result, df["a"].rename())
+
+
+@pytest.mark.parametrize(
+ "data", [[1, 2, 3], pd.date_range("2019", periods=3, tz="UTC")]
+)
+def test_non_object_dtype(data):
+ ser = pd.Series(data)
+ result = ser.explode()
+ tm.assert_series_equal(result, ser)
+
+
+def test_typical_usecase():
+ df = pd.DataFrame(
+ [{"var1": "a,b,c", "var2": 1}, {"var1": "d,e,f", "var2": 2}],
+ columns=["var1", "var2"],
+ )
+ exploded = df.var1.str.split(",").explode()
+ result = df[["var2"]].join(exploded)
+ expected = pd.DataFrame(
+ {"var2": [1, 1, 1, 2, 2, 2], "var1": list("abcdef")},
+ columns=["var2", "var1"],
+ index=[0, 0, 0, 1, 1, 1],
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_nested_EA():
+ # a nested EA array
+ s = pd.Series(
+ [
+ pd.date_range("20170101", periods=3, tz="UTC"),
+ pd.date_range("20170104", periods=3, tz="UTC"),
+ ]
+ )
+ result = s.explode()
+ expected = pd.Series(
+ pd.date_range("20170101", periods=6, tz="UTC"), index=[0, 0, 0, 1, 1, 1]
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_duplicate_index():
+ # GH 28005
+ s = pd.Series([[1, 2], [3, 4]], index=[0, 0])
+ result = s.explode()
+ expected = pd.Series([1, 2, 3, 4], index=[0, 0, 0, 0], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+
+def test_ignore_index():
+ # GH 34932
+ s = pd.Series([[1, 2], [3, 4]])
+ result = s.explode(ignore_index=True)
+ expected = pd.Series([1, 2, 3, 4], index=[0, 1, 2, 3], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+
+def test_explode_sets():
+ # https://github.com/pandas-dev/pandas/issues/35614
+ s = pd.Series([{"a", "b", "c"}], index=[1])
+ result = s.explode().sort_values()
+ expected = pd.Series(["a", "b", "c"], index=[1, 1, 1])
+ tm.assert_series_equal(result, expected)
+
+
+def test_explode_scalars_can_ignore_index():
+ # https://github.com/pandas-dev/pandas/issues/40487
+ s = pd.Series([1, 2, 3], index=["a", "b", "c"])
+ result = s.explode(ignore_index=True)
+ expected = pd.Series([1, 2, 3])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("ignore_index", [True, False])
+@pytest.mark.parametrize("list_type", ["list_", "large_list"])
+def test_explode_pyarrow_list_type(ignore_index, list_type):
+ # GH 53602, 61091
+ pa = pytest.importorskip("pyarrow")
+
+ data = [
+ [None, None],
+ [1],
+ [],
+ [2, 3],
+ None,
+ ]
+ ser = pd.Series(data, dtype=pd.ArrowDtype(getattr(pa, list_type)(pa.int64())))
+ result = ser.explode(ignore_index=ignore_index)
+ expected = pd.Series(
+ data=[None, None, 1, None, 2, 3, None],
+ index=None if ignore_index else [0, 0, 1, 2, 3, 3, 4],
+ dtype=pd.ArrowDtype(pa.int64()),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("ignore_index", [True, False])
+def test_explode_pyarrow_non_list_type(ignore_index):
+ pa = pytest.importorskip("pyarrow")
+ data = [1, 2, 3]
+ ser = pd.Series(data, dtype=pd.ArrowDtype(pa.int64()))
+ result = ser.explode(ignore_index=ignore_index)
+ expected = pd.Series([1, 2, 3], dtype="int64[pyarrow]", index=[0, 1, 2])
+ tm.assert_series_equal(result, expected)
+
+
+def test_str_dtype():
+ # https://github.com/pandas-dev/pandas/pull/61623
+ ser = pd.Series(["x", "y"], dtype="str")
+ result = ser.explode()
+ assert result is not ser
+ tm.assert_series_equal(result, ser)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_fillna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_fillna.py
new file mode 100644
index 0000000000000000000000000000000000000000..568bdb260d4d4f071730052b20b3404d0839d58f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_fillna.py
@@ -0,0 +1,1037 @@
+from datetime import (
+ datetime,
+ timedelta,
+ timezone,
+)
+
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ DataFrame,
+ DatetimeIndex,
+ NaT,
+ Period,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+ isna,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import period_array
+
+
+class TestSeriesFillNA:
+ def test_fillna_nat(self):
+ series = Series([0, 1, 2, NaT._value], dtype="M8[ns]")
+
+ filled = series.ffill()
+ filled2 = series.fillna(value=series.values[2])
+
+ expected = series.copy()
+ expected.iloc[3] = expected.iloc[2]
+
+ tm.assert_series_equal(filled, expected)
+ tm.assert_series_equal(filled2, expected)
+
+ df = DataFrame({"A": series})
+ filled = df.ffill()
+ filled2 = df.fillna(value=series.values[2])
+ expected = DataFrame({"A": expected})
+ tm.assert_frame_equal(filled, expected)
+ tm.assert_frame_equal(filled2, expected)
+
+ series = Series([NaT._value, 0, 1, 2], dtype="M8[ns]")
+
+ filled = series.bfill()
+ filled2 = series.fillna(value=series[1])
+
+ expected = series.copy()
+ expected[0] = expected[1]
+
+ tm.assert_series_equal(filled, expected)
+ tm.assert_series_equal(filled2, expected)
+
+ df = DataFrame({"A": series})
+ filled = df.bfill()
+ filled2 = df.fillna(value=series[1])
+ expected = DataFrame({"A": expected})
+ tm.assert_frame_equal(filled, expected)
+ tm.assert_frame_equal(filled2, expected)
+
+ def test_fillna(self):
+ ts = Series(
+ [0.0, 1.0, 2.0, 3.0, 4.0], index=date_range("2020-01-01", periods=5)
+ )
+
+ tm.assert_series_equal(ts, ts.ffill())
+
+ ts.iloc[2] = np.nan
+
+ exp = Series([0.0, 1.0, 1.0, 3.0, 4.0], index=ts.index)
+ tm.assert_series_equal(ts.ffill(), exp)
+
+ exp = Series([0.0, 1.0, 3.0, 3.0, 4.0], index=ts.index)
+ tm.assert_series_equal(ts.bfill(), exp)
+
+ exp = Series([0.0, 1.0, 5.0, 3.0, 4.0], index=ts.index)
+ tm.assert_series_equal(ts.fillna(value=5), exp)
+
+ def test_fillna_nonscalar(self):
+ # GH#5703
+ s1 = Series([np.nan])
+ s2 = Series([1])
+ result = s1.fillna(s2)
+ expected = Series([1.0])
+ tm.assert_series_equal(result, expected)
+ result = s1.fillna({})
+ tm.assert_series_equal(result, s1)
+ result = s1.fillna(Series((), dtype=object))
+ tm.assert_series_equal(result, s1)
+ result = s2.fillna(s1)
+ tm.assert_series_equal(result, s2)
+ result = s1.fillna({0: 1})
+ tm.assert_series_equal(result, expected)
+ result = s1.fillna({1: 1})
+ tm.assert_series_equal(result, Series([np.nan]))
+ result = s1.fillna({0: 1, 1: 1})
+ tm.assert_series_equal(result, expected)
+ result = s1.fillna(Series({0: 1, 1: 1}))
+ tm.assert_series_equal(result, expected)
+ result = s1.fillna(Series({0: 1, 1: 1}, index=[4, 5]))
+ tm.assert_series_equal(result, s1)
+
+ def test_fillna_aligns(self):
+ s1 = Series([0, 1, 2], list("abc"))
+ s2 = Series([0, np.nan, 2], list("bac"))
+ result = s2.fillna(s1)
+ expected = Series([0, 0, 2.0], list("bac"))
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_limit(self):
+ ser = Series(np.nan, index=[0, 1, 2])
+ result = ser.fillna(999, limit=1)
+ expected = Series([999, np.nan, np.nan], index=[0, 1, 2])
+ tm.assert_series_equal(result, expected)
+
+ result = ser.fillna(999, limit=2)
+ expected = Series([999, 999, np.nan], index=[0, 1, 2])
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_dont_cast_strings(self):
+ # GH#9043
+ # make sure a string representation of int/float values can be filled
+ # correctly without raising errors or being converted
+ vals = ["0", "1.5", "-0.3"]
+ for val in vals:
+ ser = Series([0, 1, np.nan, np.nan, 4], dtype="float64")
+ result = ser.fillna(val)
+ expected = Series([0, 1, val, val, 4], dtype="object")
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_consistency(self):
+ # GH#16402
+ # fillna with a tz aware to a tz-naive, should result in object
+
+ ser = Series([Timestamp("20130101"), NaT])
+
+ result = ser.fillna(Timestamp("20130101", tz="US/Eastern"))
+ expected = Series(
+ [Timestamp("20130101"), Timestamp("2013-01-01", tz="US/Eastern")],
+ dtype="object",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = ser.where([True, False], Timestamp("20130101", tz="US/Eastern"))
+ tm.assert_series_equal(result, expected)
+
+ result = ser.where([True, False], Timestamp("20130101", tz="US/Eastern"))
+ tm.assert_series_equal(result, expected)
+
+ # with a non-datetime
+ result = ser.fillna("foo")
+ expected = Series([Timestamp("20130101"), "foo"])
+ tm.assert_series_equal(result, expected)
+
+ # assignment
+ ser2 = ser.copy()
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser2[1] = "foo"
+
+ def test_timedelta_fillna(self, frame_or_series, unit):
+ # GH#3371
+ ser = Series(
+ [
+ Timestamp("20130101"),
+ Timestamp("20130101"),
+ Timestamp("20130102"),
+ Timestamp("20130103 9:01:01"),
+ ],
+ dtype=f"M8[{unit}]",
+ )
+ td = ser.diff()
+ obj = frame_or_series(td).copy()
+
+ # reg fillna
+ result = obj.fillna(Timedelta(seconds=0))
+ expected = Series(
+ [
+ timedelta(0),
+ timedelta(0),
+ timedelta(1),
+ timedelta(days=1, seconds=9 * 3600 + 60 + 1),
+ ],
+ dtype=f"m8[{unit}]",
+ )
+ expected = frame_or_series(expected)
+ tm.assert_equal(result, expected)
+
+ # GH#45746 pre-1.? ints were interpreted as seconds. then that was
+ # deprecated and changed to raise. In 2.0 it casts to common dtype,
+ # consistent with every other dtype's behavior
+ res = obj.fillna(1)
+ expected = obj.astype(object).fillna(1)
+ tm.assert_equal(res, expected)
+
+ result = obj.fillna(Timedelta(seconds=1))
+ expected = Series(
+ [
+ timedelta(seconds=1),
+ timedelta(0),
+ timedelta(1),
+ timedelta(days=1, seconds=9 * 3600 + 60 + 1),
+ ],
+ dtype=f"m8[{unit}]",
+ )
+ expected = frame_or_series(expected)
+ tm.assert_equal(result, expected)
+
+ result = obj.fillna(timedelta(days=1, seconds=1))
+ expected = Series(
+ [
+ timedelta(days=1, seconds=1),
+ timedelta(0),
+ timedelta(1),
+ timedelta(days=1, seconds=9 * 3600 + 60 + 1),
+ ],
+ dtype=f"m8[{unit}]",
+ )
+ expected = frame_or_series(expected)
+ tm.assert_equal(result, expected)
+
+ result = obj.fillna(np.timedelta64(10**9))
+ expected = Series(
+ [
+ timedelta(seconds=1),
+ timedelta(0),
+ timedelta(1),
+ timedelta(days=1, seconds=9 * 3600 + 60 + 1),
+ ],
+ dtype=f"m8[{unit}]",
+ )
+ expected = frame_or_series(expected)
+ tm.assert_equal(result, expected)
+
+ result = obj.fillna(NaT)
+ expected = Series(
+ [
+ NaT,
+ timedelta(0),
+ timedelta(1),
+ timedelta(days=1, seconds=9 * 3600 + 60 + 1),
+ ],
+ dtype=f"m8[{unit}]",
+ )
+ expected = frame_or_series(expected)
+ tm.assert_equal(result, expected)
+
+ # ffill
+ td[2] = np.nan
+ obj = frame_or_series(td).copy()
+ result = obj.ffill()
+ expected = td.fillna(Timedelta(seconds=0))
+ expected[0] = np.nan
+ expected = frame_or_series(expected)
+
+ tm.assert_equal(result, expected)
+
+ # bfill
+ td[2] = np.nan
+ obj = frame_or_series(td)
+ result = obj.bfill()
+ expected = td.fillna(Timedelta(seconds=0))
+ expected[2] = timedelta(days=1, seconds=9 * 3600 + 60 + 1)
+ expected = frame_or_series(expected)
+ tm.assert_equal(result, expected)
+
+ def test_datetime64_fillna(self):
+ ser = Series(
+ [
+ Timestamp("20130101"),
+ Timestamp("20130101"),
+ Timestamp("20130102"),
+ Timestamp("20130103 9:01:01"),
+ ]
+ )
+ ser[2] = np.nan
+
+ # ffill
+ result = ser.ffill()
+ expected = Series(
+ [
+ Timestamp("20130101"),
+ Timestamp("20130101"),
+ Timestamp("20130101"),
+ Timestamp("20130103 9:01:01"),
+ ]
+ )
+ tm.assert_series_equal(result, expected)
+
+ # bfill
+ result = ser.bfill()
+ expected = Series(
+ [
+ Timestamp("20130101"),
+ Timestamp("20130101"),
+ Timestamp("20130103 9:01:01"),
+ Timestamp("20130103 9:01:01"),
+ ]
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "scalar",
+ [
+ False,
+ True,
+ ],
+ )
+ @pytest.mark.parametrize("tz", [None, "UTC"])
+ def test_datetime64_fillna_mismatched_reso_no_rounding(self, tz, scalar):
+ # GH#56410
+ dti = date_range("2016-01-01", periods=3, unit="s", tz=tz)
+ item = Timestamp("2016-02-03 04:05:06.789", tz=tz).as_unit("ms")
+ vec = date_range(item, periods=3, unit="ms")
+
+ exp_dtype = "M8[ms]" if tz is None else "M8[ms, UTC]"
+ expected = Series([item, dti[1], dti[2]], dtype=exp_dtype)
+
+ ser = Series(dti)
+ ser[0] = NaT
+ ser2 = ser.copy()
+
+ res = ser.fillna(item)
+ res2 = ser2.fillna(Series(vec))
+
+ if scalar:
+ tm.assert_series_equal(res, expected)
+ else:
+ tm.assert_series_equal(res2, expected)
+
+ @pytest.mark.parametrize(
+ "scalar",
+ [
+ False,
+ True,
+ ],
+ )
+ def test_timedelta64_fillna_mismatched_reso_no_rounding(self, scalar):
+ # GH#56410
+ ts = Timestamp("1970-01-01").as_unit("s")
+ tdi = date_range("2016-01-01", periods=3, unit="s") - ts
+ item = Timestamp("2016-02-03 04:05:06.789").as_unit("ms") - ts
+ vec = timedelta_range(item, periods=3, unit="ms")
+
+ expected = Series([item, tdi[1], tdi[2]], dtype="m8[ms]")
+
+ ser = Series(tdi)
+ ser[0] = NaT
+ ser2 = ser.copy()
+
+ res = ser.fillna(item)
+ res2 = ser2.fillna(Series(vec))
+
+ if scalar:
+ tm.assert_series_equal(res, expected)
+ else:
+ tm.assert_series_equal(res2, expected)
+
+ def test_datetime64_fillna_backfill(self):
+ # GH#6587
+ # make sure that we are treating as integer when filling
+ ser = Series([NaT, NaT, "2013-08-05 15:30:00.000001"], dtype="M8[ns]")
+
+ expected = Series(
+ [
+ "2013-08-05 15:30:00.000001",
+ "2013-08-05 15:30:00.000001",
+ "2013-08-05 15:30:00.000001",
+ ],
+ dtype="M8[ns]",
+ )
+ result = ser.bfill()
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("tz", ["US/Eastern", "Asia/Tokyo"])
+ def test_datetime64_tz_fillna(self, tz, unit):
+ # DatetimeLikeBlock
+ ser = Series(
+ [
+ Timestamp("2011-01-01 10:00"),
+ NaT,
+ Timestamp("2011-01-03 10:00"),
+ NaT,
+ ],
+ dtype=f"M8[{unit}]",
+ )
+ null_loc = Series([False, True, False, True])
+
+ result = ser.fillna(Timestamp("2011-01-02 10:00").as_unit("s"))
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00"),
+ Timestamp("2011-01-02 10:00"),
+ Timestamp("2011-01-03 10:00"),
+ Timestamp("2011-01-02 10:00"),
+ ],
+ dtype=f"M8[{unit}]",
+ )
+ tm.assert_series_equal(expected, result)
+ # check s is not changed
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna(Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"))
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00").as_unit("s"),
+ Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"),
+ Timestamp("2011-01-03 10:00").as_unit("s"),
+ Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"),
+ ],
+ )
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna("AAA")
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00").as_unit("s"),
+ "AAA",
+ Timestamp("2011-01-03 10:00").as_unit("s"),
+ "AAA",
+ ],
+ dtype=object,
+ )
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna(
+ {
+ 1: Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"),
+ 3: Timestamp("2011-01-04 10:00").as_unit("s"),
+ }
+ )
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00").as_unit("s"),
+ Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"),
+ Timestamp("2011-01-03 10:00").as_unit("s"),
+ Timestamp("2011-01-04 10:00").as_unit("s"),
+ ]
+ )
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna(
+ {
+ 1: Timestamp("2011-01-02 10:00").as_unit("s"),
+ 3: Timestamp("2011-01-04 10:00").as_unit("s"),
+ }
+ )
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00"),
+ Timestamp("2011-01-02 10:00"),
+ Timestamp("2011-01-03 10:00"),
+ Timestamp("2011-01-04 10:00"),
+ ],
+ dtype=f"M8[{unit}]",
+ )
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ # DatetimeTZBlock
+ idx = DatetimeIndex(
+ ["2011-01-01 10:00", NaT, "2011-01-03 10:00", NaT], tz=tz
+ ).as_unit(unit)
+ ser = Series(idx)
+ assert ser.dtype == f"datetime64[{unit}, {tz}]"
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna(Timestamp("2011-01-02 10:00").as_unit("s"))
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"),
+ Timestamp("2011-01-02 10:00").as_unit("s"),
+ Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"),
+ Timestamp("2011-01-02 10:00").as_unit("s"),
+ ]
+ )
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna(Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"))
+ idx = DatetimeIndex(
+ [
+ "2011-01-01 10:00",
+ "2011-01-02 10:00",
+ "2011-01-03 10:00",
+ "2011-01-02 10:00",
+ ],
+ tz=tz,
+ ).as_unit(unit)
+ expected = Series(idx)
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna(Timestamp("2011-01-02 10:00", tz=tz).to_pydatetime())
+ idx = DatetimeIndex(
+ [
+ "2011-01-01 10:00",
+ "2011-01-02 10:00",
+ "2011-01-03 10:00",
+ "2011-01-02 10:00",
+ ],
+ tz=tz,
+ ).as_unit(unit)
+ expected = Series(idx)
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna("AAA")
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"),
+ "AAA",
+ Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"),
+ "AAA",
+ ],
+ dtype=object,
+ )
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna(
+ {
+ 1: Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"),
+ 3: Timestamp("2011-01-04 10:00").as_unit("s"),
+ }
+ )
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"),
+ Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"),
+ Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"),
+ Timestamp("2011-01-04 10:00").as_unit("s"),
+ ]
+ )
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ result = ser.fillna(
+ {
+ 1: Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"),
+ 3: Timestamp("2011-01-04 10:00", tz=tz).as_unit("s"),
+ }
+ )
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00", tz=tz),
+ Timestamp("2011-01-02 10:00", tz=tz),
+ Timestamp("2011-01-03 10:00", tz=tz),
+ Timestamp("2011-01-04 10:00", tz=tz),
+ ]
+ ).dt.as_unit(unit)
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ # filling with a naive/other zone, coerce to object
+ result = ser.fillna(Timestamp("20130101").as_unit("s"))
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"),
+ Timestamp("2013-01-01").as_unit("s"),
+ Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"),
+ Timestamp("2013-01-01").as_unit("s"),
+ ]
+ )
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ # pre-2.0 fillna with mixed tzs would cast to object, in 2.0
+ # it retains dtype.
+ result = ser.fillna(Timestamp("20130101", tz="US/Pacific").as_unit("s"))
+ expected = Series(
+ [
+ Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"),
+ Timestamp("2013-01-01", tz="US/Pacific").as_unit("s").tz_convert(tz),
+ Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"),
+ Timestamp("2013-01-01", tz="US/Pacific").as_unit("s").tz_convert(tz),
+ ]
+ ).dt.as_unit(unit)
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(isna(ser), null_loc)
+
+ def test_fillna_dt64tz_with_method(self):
+ # with timezone
+ # GH#15855
+ ser = Series([Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"), NaT])
+ exp = Series(
+ [
+ Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"),
+ Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"),
+ ]
+ )
+ tm.assert_series_equal(ser.ffill(), exp)
+
+ ser = Series([NaT, Timestamp("2012-11-11 00:00:00+01:00").as_unit("s")])
+ exp = Series(
+ [
+ Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"),
+ Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"),
+ ]
+ )
+ tm.assert_series_equal(ser.bfill(), exp)
+
+ def test_fillna_pytimedelta(self):
+ # GH#8209
+ ser = Series([np.nan, Timedelta("1 days")], index=["A", "B"])
+
+ result = ser.fillna(timedelta(1))
+ expected = Series(Timedelta("1 days"), index=["A", "B"], dtype="m8[us]")
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_period(self):
+ # GH#13737
+ ser = Series([Period("2011-01", freq="M"), Period("NaT", freq="M")])
+
+ res = ser.fillna(Period("2012-01", freq="M"))
+ exp = Series([Period("2011-01", freq="M"), Period("2012-01", freq="M")])
+ tm.assert_series_equal(res, exp)
+ assert res.dtype == "Period[M]"
+
+ def test_fillna_dt64_timestamp(self, frame_or_series):
+ ser = Series(
+ [
+ Timestamp("20130101"),
+ Timestamp("20130101"),
+ Timestamp("20130102"),
+ Timestamp("20130103 9:01:01"),
+ ]
+ )
+ ser[2] = np.nan
+ obj = frame_or_series(ser)
+
+ # reg fillna
+ result = obj.fillna(Timestamp("20130104"))
+ expected = Series(
+ [
+ Timestamp("20130101"),
+ Timestamp("20130101"),
+ Timestamp("20130104"),
+ Timestamp("20130103 9:01:01"),
+ ]
+ )
+ expected = frame_or_series(expected)
+ tm.assert_equal(result, expected)
+
+ result = obj.fillna(NaT)
+ expected = obj
+ tm.assert_equal(result, expected)
+
+ def test_fillna_dt64_non_nao(self):
+ # GH#27419
+ ser = Series([Timestamp("2010-01-01"), NaT, Timestamp("2000-01-01")])
+ val = np.datetime64("1975-04-05", "ms")
+
+ result = ser.fillna(val)
+ expected = Series(
+ [Timestamp("2010-01-01"), Timestamp("1975-04-05"), Timestamp("2000-01-01")]
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_numeric_inplace(self):
+ x = Series([np.nan, 1.0, np.nan, 3.0, np.nan], ["z", "a", "b", "c", "d"])
+ y = x.copy()
+
+ result = y.fillna(value=0, inplace=True)
+ assert result is y
+
+ expected = x.fillna(value=0)
+ tm.assert_series_equal(y, expected)
+
+ # ---------------------------------------------------------------
+ # CategoricalDtype
+
+ @pytest.mark.parametrize(
+ "fill_value, expected_output",
+ [
+ ("a", ["a", "a", "b", "a", "a"]),
+ ({1: "a", 3: "b", 4: "b"}, ["a", "a", "b", "b", "b"]),
+ ({1: "a"}, ["a", "a", "b", np.nan, np.nan]),
+ ({1: "a", 3: "b"}, ["a", "a", "b", "b", np.nan]),
+ (Series("a"), ["a", np.nan, "b", np.nan, np.nan]),
+ (Series("a", index=[1]), ["a", "a", "b", np.nan, np.nan]),
+ (Series({1: "a", 3: "b"}), ["a", "a", "b", "b", np.nan]),
+ (Series(["a", "b"], index=[3, 4]), ["a", np.nan, "b", "a", "b"]),
+ ],
+ )
+ def test_fillna_categorical(self, fill_value, expected_output):
+ # GH#17033
+ # Test fillna for a Categorical series
+ data = ["a", np.nan, "b", np.nan, np.nan]
+ ser = Series(Categorical(data, categories=["a", "b"]))
+ exp = Series(Categorical(expected_output, categories=["a", "b"]))
+ result = ser.fillna(fill_value)
+ tm.assert_series_equal(result, exp)
+
+ @pytest.mark.parametrize(
+ "fill_value, expected_output",
+ [
+ (["a", "b", "c", "d", "e"], ["a", "b", "b", "d", "e"]),
+ (["b", "d", "a", "d", "a"], ["a", "d", "b", "d", "a"]),
+ (
+ Categorical(
+ ["b", "d", "a", "d", "a"], categories=["b", "c", "d", "e", "a"]
+ ),
+ ["a", "d", "b", "d", "a"],
+ ),
+ ],
+ )
+ def test_fillna_categorical_with_new_categories(self, fill_value, expected_output):
+ # GH#26215
+ data = ["a", np.nan, "b", np.nan, np.nan]
+ ser = Series(Categorical(data, categories=["a", "b", "c", "d", "e"]))
+ exp = Series(Categorical(expected_output, categories=["a", "b", "c", "d", "e"]))
+ fill_value = Series(fill_value)
+ result = ser.fillna(fill_value)
+ tm.assert_series_equal(result, exp)
+
+ def test_fillna_categorical_raises(self):
+ data = ["a", np.nan, "b", np.nan, np.nan]
+ ser = Series(Categorical(data, categories=["a", "b"]))
+ cat = ser._values
+
+ msg = "Cannot setitem on a Categorical with a new category"
+ with pytest.raises(TypeError, match=msg):
+ ser.fillna("d")
+
+ msg2 = "Length of 'value' does not match."
+ with pytest.raises(ValueError, match=msg2):
+ cat.fillna(Series("d"))
+
+ with pytest.raises(TypeError, match=msg):
+ ser.fillna({1: "d", 3: "a"})
+
+ msg = '"value" parameter must be a scalar or dict, but you passed a "list"'
+ with pytest.raises(TypeError, match=msg):
+ ser.fillna(["a", "b"])
+
+ msg = '"value" parameter must be a scalar or dict, but you passed a "tuple"'
+ with pytest.raises(TypeError, match=msg):
+ ser.fillna(("a", "b"))
+
+ msg = (
+ '"value" parameter must be a scalar, dict '
+ 'or Series, but you passed a "DataFrame"'
+ )
+ with pytest.raises(TypeError, match=msg):
+ ser.fillna(DataFrame({1: ["a"], 3: ["b"]}))
+
+ @pytest.mark.parametrize("dtype", [float, "float32", "float64"])
+ @pytest.mark.parametrize("scalar", [True, False])
+ def test_fillna_float_casting(self, dtype, any_real_numpy_dtype, scalar):
+ # GH-43424
+ ser = Series([np.nan, 1.2], dtype=dtype)
+ fill_values = Series([2, 2], dtype=any_real_numpy_dtype)
+ if scalar:
+ fill_values = fill_values.dtype.type(2)
+
+ result = ser.fillna(fill_values)
+ expected = Series([2.0, 1.2], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ ser = Series([np.nan, 1.2], dtype=dtype)
+ mask = ser.isna().to_numpy()
+ ser[mask] = fill_values
+ tm.assert_series_equal(ser, expected)
+
+ ser = Series([np.nan, 1.2], dtype=dtype)
+ ser.mask(mask, fill_values, inplace=True)
+ tm.assert_series_equal(ser, expected)
+
+ ser = Series([np.nan, 1.2], dtype=dtype)
+ res = ser.where(~mask, fill_values)
+ tm.assert_series_equal(res, expected)
+
+ def test_fillna_f32_upcast_with_dict(self):
+ # GH-43424
+ ser = Series([np.nan, 1.2], dtype=np.float32)
+ result = ser.fillna({0: 1})
+ expected = Series([1.0, 1.2], dtype=np.float32)
+ tm.assert_series_equal(result, expected)
+
+ # ---------------------------------------------------------------
+ # Invalid Usages
+
+ def test_fillna_listlike_invalid(self):
+ ser = Series(np.random.default_rng(2).integers(-100, 100, 50))
+ msg = '"value" parameter must be a scalar or dict, but you passed a "list"'
+ with pytest.raises(TypeError, match=msg):
+ ser.fillna([1, 2])
+
+ msg = '"value" parameter must be a scalar or dict, but you passed a "tuple"'
+ with pytest.raises(TypeError, match=msg):
+ ser.fillna((1, 2))
+
+ def test_fillna_method_and_limit_invalid(self):
+ # related GH#9217, make sure limit is an int and greater than 0
+ ser = Series([1, 2, 3, None])
+ msg = "|".join(
+ [
+ r"Cannot specify both 'value' and 'method'\.",
+ "Limit must be greater than 0",
+ "Limit must be an integer",
+ ]
+ )
+ for limit in [-1, 0, 1.0, 2.0]:
+ with pytest.raises(ValueError, match=msg):
+ ser.fillna(1, limit=limit)
+
+ def test_fillna_datetime64_with_timezone_tzinfo(self):
+ # https://github.com/pandas-dev/pandas/issues/38851
+ # different tzinfos representing UTC treated as equal
+ ser = Series(date_range("2020", periods=3, tz="UTC"))
+ expected = ser.copy()
+ ser[1] = NaT
+ result = ser.fillna(datetime(2020, 1, 2, tzinfo=timezone.utc))
+ tm.assert_series_equal(result, expected)
+
+ # pre-2.0 we cast to object with mixed tzs, in 2.0 we retain dtype
+ ts = Timestamp("2000-01-01", tz="US/Pacific")
+ ser2 = Series(ser._values.tz_convert("dateutil/US/Pacific"))
+ assert ser2.dtype.kind == "M"
+ result = ser2.fillna(ts)
+ expected = Series(
+ [ser2[0], ts.tz_convert(ser2.dtype.tz), ser2[2]],
+ dtype=ser2.dtype,
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "input, input_fillna, expected_data, expected_categories",
+ [
+ (["A", "B", None, "A"], "B", ["A", "B", "B", "A"], ["A", "B"]),
+ (["A", "B", np.nan, "A"], "B", ["A", "B", "B", "A"], ["A", "B"]),
+ ],
+ )
+ def test_fillna_categorical_accept_same_type(
+ self, input, input_fillna, expected_data, expected_categories
+ ):
+ # GH32414
+ cat = Categorical(input)
+ ser = Series(cat).fillna(input_fillna)
+ filled = cat.fillna(ser)
+ result = cat.fillna(filled)
+ expected = Categorical(expected_data, categories=expected_categories)
+ tm.assert_categorical_equal(result, expected)
+
+
+class TestFillnaPad:
+ def test_fillna_bug(self):
+ ser = Series([np.nan, 1.0, np.nan, 3.0, np.nan], ["z", "a", "b", "c", "d"])
+ filled = ser.ffill()
+ expected = Series([np.nan, 1.0, 1.0, 3.0, 3.0], ser.index)
+ tm.assert_series_equal(filled, expected)
+
+ filled = ser.bfill()
+ expected = Series([1.0, 1.0, 3.0, 3.0, np.nan], ser.index)
+ tm.assert_series_equal(filled, expected)
+
+ def test_ffill_mixed_dtypes_without_missing_data(self):
+ # GH#14956
+ series = Series([datetime(2015, 1, 1, tzinfo=timezone.utc), 1])
+ result = series.ffill()
+ tm.assert_series_equal(series, result)
+
+ def test_pad_nan(self):
+ x = Series(
+ [np.nan, 1.0, np.nan, 3.0, np.nan], ["z", "a", "b", "c", "d"], dtype=float
+ )
+
+ result = x.ffill(inplace=True)
+ assert result is x
+
+ expected = Series(
+ [np.nan, 1.0, 1.0, 3.0, 3.0], ["z", "a", "b", "c", "d"], dtype=float
+ )
+ tm.assert_series_equal(x[1:], expected[1:])
+ assert np.isnan(x.iloc[0]), np.isnan(expected.iloc[0])
+
+ def test_series_fillna_limit(self):
+ index = np.arange(10)
+ s = Series(np.random.default_rng(2).standard_normal(10), index=index)
+
+ result = s[:2].reindex(index)
+ result = result.ffill(limit=5)
+
+ expected = s[:2].reindex(index).ffill()
+ expected[-3:] = np.nan
+ tm.assert_series_equal(result, expected)
+
+ result = s[-2:].reindex(index)
+ result = result.bfill(limit=5)
+
+ expected = s[-2:].reindex(index).bfill()
+ expected[:3] = np.nan
+ tm.assert_series_equal(result, expected)
+
+ def test_series_pad_backfill_limit(self):
+ index = np.arange(10)
+ s = Series(np.random.default_rng(2).standard_normal(10), index=index)
+
+ result = s[:2].reindex(index, method="pad", limit=5)
+
+ expected = s[:2].reindex(index).ffill()
+ expected[-3:] = np.nan
+ tm.assert_series_equal(result, expected)
+
+ result = s[-2:].reindex(index, method="backfill", limit=5)
+
+ expected = s[-2:].reindex(index).bfill()
+ expected[:3] = np.nan
+ tm.assert_series_equal(result, expected)
+
+ def test_fillna_int(self):
+ ser = Series(np.random.default_rng(2).integers(-100, 100, 50))
+ result = ser.ffill(inplace=True)
+ assert result is ser
+ tm.assert_series_equal(ser.ffill(inplace=False), ser)
+
+ def test_datetime64tz_fillna_round_issue(self):
+ # GH#14872
+
+ data = Series(
+ [NaT, NaT, datetime(2016, 12, 12, 22, 24, 6, 100001, tzinfo=timezone.utc)]
+ )
+
+ filled = data.bfill()
+
+ expected = Series(
+ [
+ datetime(2016, 12, 12, 22, 24, 6, 100001, tzinfo=timezone.utc),
+ datetime(2016, 12, 12, 22, 24, 6, 100001, tzinfo=timezone.utc),
+ datetime(2016, 12, 12, 22, 24, 6, 100001, tzinfo=timezone.utc),
+ ]
+ )
+
+ tm.assert_series_equal(filled, expected)
+
+ def test_fillna_parr(self):
+ # GH-24537
+ dti = date_range(
+ Timestamp.max - Timedelta(nanoseconds=10), periods=5, freq="ns"
+ )
+ ser = Series(dti.to_period("ns"))
+ ser[2] = NaT
+ arr = period_array(
+ [
+ Timestamp("2262-04-11 23:47:16.854775797"),
+ Timestamp("2262-04-11 23:47:16.854775798"),
+ Timestamp("2262-04-11 23:47:16.854775798"),
+ Timestamp("2262-04-11 23:47:16.854775800"),
+ Timestamp("2262-04-11 23:47:16.854775801"),
+ ],
+ freq="ns",
+ )
+ expected = Series(arr)
+
+ filled = ser.ffill()
+
+ tm.assert_series_equal(filled, expected)
+
+
+@pytest.mark.parametrize(
+ "data, expected_data, method, kwargs",
+ (
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ [np.nan, np.nan, 3.0, 3.0, 3.0, 3.0, 7.0, np.nan, np.nan],
+ "ffill",
+ {"limit_area": "inside"},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ [np.nan, np.nan, 3.0, 3.0, np.nan, np.nan, 7.0, np.nan, np.nan],
+ "ffill",
+ {"limit_area": "inside", "limit": 1},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, 7.0],
+ "ffill",
+ {"limit_area": "outside"},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, np.nan],
+ "ffill",
+ {"limit_area": "outside", "limit": 1},
+ ),
+ (
+ [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
+ [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
+ "ffill",
+ {"limit_area": "outside", "limit": 1},
+ ),
+ (
+ range(5),
+ range(5),
+ "ffill",
+ {"limit_area": "outside", "limit": 1},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ [np.nan, np.nan, 3.0, 7.0, 7.0, 7.0, 7.0, np.nan, np.nan],
+ "bfill",
+ {"limit_area": "inside"},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ [np.nan, np.nan, 3.0, np.nan, np.nan, 7.0, 7.0, np.nan, np.nan],
+ "bfill",
+ {"limit_area": "inside", "limit": 1},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ [3.0, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan],
+ "bfill",
+ {"limit_area": "outside"},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ [np.nan, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan],
+ "bfill",
+ {"limit_area": "outside", "limit": 1},
+ ),
+ ),
+)
+def test_ffill_bfill_limit_area(data, expected_data, method, kwargs):
+ # GH#56492
+ s = Series(data)
+ expected = Series(expected_data)
+ result = getattr(s, method)(**kwargs)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_get_numeric_data.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_get_numeric_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..31225d595343dfe744824e79e7494b9054a2fd27
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_get_numeric_data.py
@@ -0,0 +1,31 @@
+from pandas import (
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestGetNumericData:
+ def test_get_numeric_data_preserve_dtype(self):
+ # get the numeric data
+ obj = Series([1, 2, 3])
+ result = obj._get_numeric_data()
+ tm.assert_series_equal(result, obj)
+
+ # returned object is a shallow copy
+ result.iloc[0] = 0
+ assert obj.iloc[0] == 1
+
+ obj = Series([1, "2", 3.0])
+ result = obj._get_numeric_data()
+ expected = Series([], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ obj = Series([True, False, True])
+ result = obj._get_numeric_data()
+ tm.assert_series_equal(result, obj)
+
+ obj = Series(date_range("20130101", periods=3, unit="ns"))
+ result = obj._get_numeric_data()
+ expected = Series([], dtype="M8[ns]")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_head_tail.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_head_tail.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9f8d85eda350e0627790626d52b7703d23ead02
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_head_tail.py
@@ -0,0 +1,8 @@
+import pandas._testing as tm
+
+
+def test_head_tail(string_series):
+ tm.assert_series_equal(string_series.head(), string_series[:5])
+ tm.assert_series_equal(string_series.head(0), string_series[0:0])
+ tm.assert_series_equal(string_series.tail(), string_series[-5:])
+ tm.assert_series_equal(string_series.tail(0), string_series[0:0])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_infer_objects.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_infer_objects.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d411e08a010706b59799ffcb14d66a664ced31b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_infer_objects.py
@@ -0,0 +1,56 @@
+import numpy as np
+
+from pandas import (
+ Series,
+ interval_range,
+)
+import pandas._testing as tm
+
+
+class TestInferObjects:
+ def test_copy(self, index_or_series):
+ # GH#50096
+ # case where we don't need to do inference because it is already non-object
+ obj = index_or_series(np.array([1, 2, 3], dtype="int64"))
+
+ result = obj.infer_objects()
+ assert tm.shares_memory(result, obj)
+
+ # case where we try to do inference but can't do better than object
+ obj2 = index_or_series(np.array(["foo", 2], dtype=object))
+ result2 = obj2.infer_objects()
+ assert tm.shares_memory(result2, obj2)
+
+ def test_infer_objects_series(self, index_or_series):
+ # GH#11221
+ actual = index_or_series(np.array([1, 2, 3], dtype="O")).infer_objects()
+ expected = index_or_series([1, 2, 3])
+ tm.assert_equal(actual, expected)
+
+ actual = index_or_series(np.array([1, 2, 3, None], dtype="O")).infer_objects()
+ expected = index_or_series([1.0, 2.0, 3.0, np.nan])
+ tm.assert_equal(actual, expected)
+
+ # only soft conversions, unconvertible pass thru unchanged
+
+ obj = index_or_series(np.array([1, 2, 3, None, "a"], dtype="O"))
+ actual = obj.infer_objects()
+ expected = index_or_series([1, 2, 3, None, "a"], dtype=object)
+
+ assert actual.dtype == "object"
+ tm.assert_equal(actual, expected)
+
+ def test_infer_objects_interval(self, index_or_series):
+ # GH#50090
+ ii = interval_range(1, 10)
+ obj = index_or_series(ii)
+
+ result = obj.astype(object).infer_objects()
+ tm.assert_equal(result, obj)
+
+ def test_infer_objects_bytes(self):
+ # GH#49650
+ ser = Series([b"a"], dtype="bytes")
+ expected = ser.copy()
+ result = ser.infer_objects()
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_info.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_info.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1fee2b2ee648026ae1050097e06c17471c3bc5b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_info.py
@@ -0,0 +1,212 @@
+from io import StringIO
+from string import ascii_uppercase
+import textwrap
+
+import numpy as np
+import pytest
+
+from pandas._config import using_string_dtype
+
+from pandas.compat import (
+ HAS_PYARROW,
+ PYPY,
+)
+
+from pandas import (
+ CategoricalIndex,
+ Index,
+ MultiIndex,
+ Series,
+ date_range,
+)
+
+
+def test_info_categorical_column_just_works():
+ n = 2500
+ data = np.array(list("abcdefghij")).take(
+ np.random.default_rng(2).integers(0, 10, size=n, dtype=int)
+ )
+ s = Series(data).astype("category")
+ s.isna()
+ buf = StringIO()
+ s.info(buf=buf)
+
+ s2 = s[s == "d"]
+ buf = StringIO()
+ s2.info(buf=buf)
+
+
+def test_info_categorical():
+ # GH14298
+ idx = CategoricalIndex(["a", "b"])
+ s = Series(np.zeros(2), index=idx)
+ buf = StringIO()
+ s.info(buf=buf)
+
+
+@pytest.mark.parametrize("verbose", [True, False])
+def test_info_series(
+ lexsorted_two_level_string_multiindex, verbose, using_infer_string
+):
+ index = lexsorted_two_level_string_multiindex
+ ser = Series(range(len(index)), index=index, name="sth")
+ buf = StringIO()
+ ser.info(verbose=verbose, buf=buf)
+ result = buf.getvalue()
+
+ expected = textwrap.dedent(
+ """\
+
+ MultiIndex: 10 entries, ('foo', 'one') to ('qux', 'three')
+ """
+ )
+ if verbose:
+ expected += textwrap.dedent(
+ """\
+ Series name: sth
+ Non-Null Count Dtype
+ -------------- -----
+ 10 non-null int64
+ """
+ )
+ qualifier = "" if using_infer_string and HAS_PYARROW else "+"
+ expected += textwrap.dedent(
+ f"""\
+ dtypes: int64(1)
+ memory usage: {ser.memory_usage()}.0{qualifier} bytes
+ """
+ )
+ assert result == expected
+
+
+def test_info_memory():
+ s = Series([1, 2], dtype="i8")
+ buf = StringIO()
+ s.info(buf=buf)
+ result = buf.getvalue()
+ memory_bytes = float(s.memory_usage())
+ expected = textwrap.dedent(
+ f"""\
+
+ RangeIndex: 2 entries, 0 to 1
+ Series name: None
+ Non-Null Count Dtype
+ -------------- -----
+ 2 non-null int64
+ dtypes: int64(1)
+ memory usage: {memory_bytes} bytes
+ """
+ )
+ assert result == expected
+
+
+def test_info_wide():
+ s = Series(np.random.default_rng(2).standard_normal(101))
+ msg = "Argument `max_cols` can only be passed in DataFrame.info, not Series.info"
+ with pytest.raises(ValueError, match=msg):
+ s.info(max_cols=1)
+
+
+def test_info_shows_dtypes():
+ dtypes = [
+ "int64",
+ "float64",
+ "datetime64[ns]",
+ "timedelta64[ns]",
+ "complex128",
+ "object",
+ "bool",
+ ]
+ n = 10
+ for dtype in dtypes:
+ s = Series(np.random.default_rng(2).integers(2, size=n).astype(dtype))
+ buf = StringIO()
+ s.info(buf=buf)
+ res = buf.getvalue()
+ name = f"{n:d} non-null {dtype}"
+ assert name in res
+
+
+@pytest.mark.xfail(PYPY, reason="on PyPy deep=True doesn't change result")
+def test_info_memory_usage_deep_not_pypy():
+ s_with_object_index = Series({"a": [1]}, index=["foo"])
+ assert s_with_object_index.memory_usage(
+ index=True, deep=True
+ ) > s_with_object_index.memory_usage(index=True)
+
+ s_object = Series({"a": ["a"]})
+ assert s_object.memory_usage(deep=True) > s_object.memory_usage()
+
+
+@pytest.mark.xfail(not PYPY, reason="on PyPy deep=True does not change result")
+def test_info_memory_usage_deep_pypy():
+ s_with_object_index = Series({"a": [1]}, index=["foo"])
+ assert s_with_object_index.memory_usage(
+ index=True, deep=True
+ ) == s_with_object_index.memory_usage(index=True)
+
+ s_object = Series({"a": ["a"]})
+ assert s_object.memory_usage(deep=True) == s_object.memory_usage()
+
+
+@pytest.mark.parametrize(
+ "index, plus",
+ [
+ ([1, 2, 3], False),
+ (Index(list("ABC"), dtype="str"), not (using_string_dtype() and HAS_PYARROW)),
+ (Index(list("ABC"), dtype=object), True),
+ (MultiIndex.from_product([range(3), range(3)]), False),
+ (
+ MultiIndex.from_product([range(3), ["foo", "bar"]]),
+ not (using_string_dtype() and HAS_PYARROW),
+ ),
+ ],
+)
+def test_info_memory_usage_qualified(index, plus):
+ buf = StringIO()
+ series = Series(1, index=index)
+ series.info(buf=buf)
+ if plus:
+ assert "+" in buf.getvalue()
+ else:
+ assert "+" not in buf.getvalue()
+
+
+def test_info_memory_usage_bug_on_multiindex():
+ # GH 14308
+ # memory usage introspection should not materialize .values
+ N = 100
+ M = len(ascii_uppercase)
+ index = MultiIndex.from_product(
+ [list(ascii_uppercase), date_range("20160101", periods=N)],
+ names=["id", "date"],
+ )
+ s = Series(np.random.default_rng(2).standard_normal(N * M), index=index)
+
+ unstacked = s.unstack("id")
+ assert s.values.nbytes == unstacked.values.nbytes
+ assert s.memory_usage(deep=True) > unstacked.memory_usage(deep=True).sum()
+
+ # high upper bound
+ diff = unstacked.memory_usage(deep=True).sum() - s.memory_usage(deep=True)
+ assert diff < 2000
+
+
+def test_info_show_counts_false():
+ s = Series([1])
+ buf = StringIO()
+ s.info(buf=buf, show_counts=False)
+ result = buf.getvalue()
+ memory_bytes = float(s.memory_usage())
+ expected = textwrap.dedent(
+ f"""\
+
+ RangeIndex: 1 entries, 0 to 0
+ Series name: None
+ Dtype
+ -----
+ int64
+ dtypes: int64(1)
+ memory usage: {memory_bytes} bytes"""
+ )
+ assert result.strip() == expected.strip()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_interpolate.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_interpolate.py
new file mode 100644
index 0000000000000000000000000000000000000000..341a5405420e7c6b97b8853da64bfefb50d55064
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_interpolate.py
@@ -0,0 +1,824 @@
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ Index,
+ MultiIndex,
+ Series,
+ date_range,
+ isna,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture(
+ params=[
+ "linear",
+ "index",
+ "values",
+ "nearest",
+ "slinear",
+ "zero",
+ "quadratic",
+ "cubic",
+ "barycentric",
+ "krogh",
+ "polynomial",
+ "spline",
+ "piecewise_polynomial",
+ "from_derivatives",
+ "pchip",
+ "akima",
+ "cubicspline",
+ ]
+)
+def nontemporal_method(request):
+ """Fixture that returns an (method name, required kwargs) pair.
+
+ This fixture does not include method 'time' as a parameterization; that
+ method requires a Series with a DatetimeIndex, and is generally tested
+ separately from these non-temporal methods.
+ """
+ method = request.param
+ kwargs = {"order": 1} if method in ("spline", "polynomial") else {}
+ return method, kwargs
+
+
+@pytest.fixture(
+ params=[
+ "linear",
+ "slinear",
+ "zero",
+ "quadratic",
+ "cubic",
+ "barycentric",
+ "krogh",
+ "polynomial",
+ "spline",
+ "piecewise_polynomial",
+ "from_derivatives",
+ "pchip",
+ "akima",
+ "cubicspline",
+ ]
+)
+def interp_methods_ind(request):
+ """Fixture that returns a (method name, required kwargs) pair to
+ be tested for various Index types.
+
+ This fixture does not include methods - 'time', 'index', 'nearest',
+ 'values' as a parameterization
+ """
+ method = request.param
+ kwargs = {"order": 1} if method in ("spline", "polynomial") else {}
+ return method, kwargs
+
+
+class TestSeriesInterpolateData:
+ @pytest.mark.xfail(reason="EA.fillna does not handle 'linear' method")
+ def test_interpolate_period_values(self):
+ orig = Series(date_range("2012-01-01", periods=5))
+ ser = orig.copy()
+ ser[2] = pd.NaT
+
+ # period cast
+ ser_per = ser.dt.to_period("D")
+ res_per = ser_per.interpolate()
+ expected_per = orig.dt.to_period("D")
+ tm.assert_series_equal(res_per, expected_per)
+
+ def test_interpolate(self, datetime_series):
+ ts = Series(np.arange(len(datetime_series), dtype=float), datetime_series.index)
+
+ ts_copy = ts.copy()
+
+ # Set data between Tuesday and Thursday to NaN for 2 consecutive weeks.
+ # Linear interpolation should fill in the missing values correctly,
+ # as the index is equally-spaced within each week.
+ ts_copy[1:4] = np.nan
+ ts_copy[6:9] = np.nan
+
+ linear_interp = ts_copy.interpolate(method="linear")
+ tm.assert_series_equal(linear_interp, ts)
+
+ ord_ts = Series(
+ [d.toordinal() for d in datetime_series.index], index=datetime_series.index
+ ).astype(float)
+
+ ord_ts_copy = ord_ts.copy()
+ ord_ts_copy[5:10] = np.nan
+
+ time_interp = ord_ts_copy.interpolate(method="time")
+ tm.assert_series_equal(time_interp, ord_ts)
+
+ def test_interpolate_time_raises_for_non_timeseries(self):
+ # When method='time' is used on a non-TimeSeries that contains a null
+ # value, a ValueError should be raised.
+ non_ts = Series([0, 1, 2, np.nan])
+ msg = "time-weighted interpolation only works on Series.* with a DatetimeIndex"
+ with pytest.raises(ValueError, match=msg):
+ non_ts.interpolate(method="time")
+
+ def test_interpolate_cubicspline(self):
+ pytest.importorskip("scipy")
+ ser = Series([10, 11, 12, 13])
+
+ expected = Series(
+ [11.00, 11.25, 11.50, 11.75, 12.00, 12.25, 12.50, 12.75, 13.00],
+ index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]),
+ )
+ # interpolate at new_index
+ new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype(
+ float
+ )
+ result = ser.reindex(new_index).interpolate(method="cubicspline").loc[1:3]
+ tm.assert_series_equal(result, expected)
+
+ def test_interpolate_pchip(self):
+ pytest.importorskip("scipy")
+ ser = Series(np.sort(np.random.default_rng(2).uniform(size=100)))
+
+ # interpolate at new_index
+ new_index = ser.index.union(
+ Index([49.25, 49.5, 49.75, 50.25, 50.5, 50.75])
+ ).astype(float)
+ interp_s = ser.reindex(new_index).interpolate(method="pchip")
+ # does not blow up, GH5977
+ interp_s.loc[49:51]
+
+ def test_interpolate_akima(self):
+ pytest.importorskip("scipy")
+ ser = Series([10, 11, 12, 13])
+
+ # interpolate at new_index where `der` is zero
+ expected = Series(
+ [11.00, 11.25, 11.50, 11.75, 12.00, 12.25, 12.50, 12.75, 13.00],
+ index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]),
+ )
+ new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype(
+ float
+ )
+ interp_s = ser.reindex(new_index).interpolate(method="akima")
+ tm.assert_series_equal(interp_s.loc[1:3], expected)
+
+ # interpolate at new_index where `der` is a non-zero int
+ expected = Series(
+ [11.0, 1.0, 1.0, 1.0, 12.0, 1.0, 1.0, 1.0, 13.0],
+ index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]),
+ )
+ new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype(
+ float
+ )
+ interp_s = ser.reindex(new_index).interpolate(method="akima", der=1)
+ tm.assert_series_equal(interp_s.loc[1:3], expected)
+
+ def test_interpolate_piecewise_polynomial(self):
+ pytest.importorskip("scipy")
+ ser = Series([10, 11, 12, 13])
+
+ expected = Series(
+ [11.00, 11.25, 11.50, 11.75, 12.00, 12.25, 12.50, 12.75, 13.00],
+ index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]),
+ )
+ # interpolate at new_index
+ new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype(
+ float
+ )
+ interp_s = ser.reindex(new_index).interpolate(method="piecewise_polynomial")
+ tm.assert_series_equal(interp_s.loc[1:3], expected)
+
+ def test_interpolate_from_derivatives(self):
+ pytest.importorskip("scipy")
+ ser = Series([10, 11, 12, 13])
+
+ expected = Series(
+ [11.00, 11.25, 11.50, 11.75, 12.00, 12.25, 12.50, 12.75, 13.00],
+ index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]),
+ )
+ # interpolate at new_index
+ new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype(
+ float
+ )
+ interp_s = ser.reindex(new_index).interpolate(method="from_derivatives")
+ tm.assert_series_equal(interp_s.loc[1:3], expected)
+
+ @pytest.mark.parametrize(
+ "kwargs",
+ [
+ {},
+ pytest.param(
+ {"method": "polynomial", "order": 1}, marks=td.skip_if_no("scipy")
+ ),
+ ],
+ )
+ def test_interpolate_corners(self, kwargs):
+ s = Series([np.nan, np.nan])
+ tm.assert_series_equal(s.interpolate(**kwargs), s)
+
+ s = Series([], dtype=object).interpolate()
+ tm.assert_series_equal(s.interpolate(**kwargs), s)
+
+ def test_interpolate_index_values(self):
+ s = Series(np.nan, index=np.sort(np.random.default_rng(2).random(30)))
+ s.loc[::3] = np.random.default_rng(2).standard_normal(10)
+
+ vals = s.index.values.astype(float)
+
+ result = s.interpolate(method="index")
+
+ bad = isna(s)
+ good = ~bad
+ expected = Series(
+ np.interp(vals[bad], vals[good], s.values[good]), index=s.index[bad]
+ )
+
+ tm.assert_series_equal(result[bad], expected)
+
+ # 'values' is synonymous with 'index' for the method kwarg
+ other_result = s.interpolate(method="values")
+
+ tm.assert_series_equal(other_result, result)
+ tm.assert_series_equal(other_result[bad], expected)
+
+ def test_interpolate_non_ts(self):
+ s = Series([1, 3, np.nan, np.nan, np.nan, 11])
+ msg = (
+ "time-weighted interpolation only works on Series or DataFrames "
+ "with a DatetimeIndex"
+ )
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method="time")
+
+ @pytest.mark.parametrize(
+ "kwargs",
+ [
+ {},
+ pytest.param(
+ {"method": "polynomial", "order": 1}, marks=td.skip_if_no("scipy")
+ ),
+ ],
+ )
+ def test_nan_interpolate(self, kwargs):
+ s = Series([0, 1, np.nan, 3])
+ result = s.interpolate(**kwargs)
+ expected = Series([0.0, 1.0, 2.0, 3.0])
+ tm.assert_series_equal(result, expected)
+
+ def test_nan_irregular_index(self):
+ s = Series([1, 2, np.nan, 4], index=[1, 3, 5, 9])
+ result = s.interpolate()
+ expected = Series([1.0, 2.0, 3.0, 4.0], index=[1, 3, 5, 9])
+ tm.assert_series_equal(result, expected)
+
+ def test_nan_str_index(self):
+ s = Series([0, 1, 2, np.nan], index=list("abcd"))
+ result = s.interpolate()
+ expected = Series([0.0, 1.0, 2.0, 2.0], index=list("abcd"))
+ tm.assert_series_equal(result, expected)
+
+ def test_interp_quad(self):
+ pytest.importorskip("scipy")
+ sq = Series([1, 4, np.nan, 16], index=[1, 2, 3, 4])
+ result = sq.interpolate(method="quadratic")
+ expected = Series([1.0, 4.0, 9.0, 16.0], index=[1, 2, 3, 4])
+ tm.assert_series_equal(result, expected)
+
+ def test_interp_scipy_basic(self):
+ pytest.importorskip("scipy")
+ s = Series([1, 3, np.nan, 12, np.nan, 25])
+ # slinear
+ expected = Series([1.0, 3.0, 7.5, 12.0, 18.5, 25.0])
+ result = s.interpolate(method="slinear")
+ tm.assert_series_equal(result, expected)
+ result = s.interpolate(method="slinear")
+ tm.assert_series_equal(result, expected)
+ # nearest
+ expected = Series([1, 3, 3, 12, 12, 25.0])
+ result = s.interpolate(method="nearest")
+ tm.assert_series_equal(result, expected.astype("float"))
+
+ result = s.interpolate(method="nearest")
+ tm.assert_series_equal(result, expected)
+ # zero
+ expected = Series([1, 3, 3, 12, 12, 25.0])
+ result = s.interpolate(method="zero")
+ tm.assert_series_equal(result, expected.astype("float"))
+
+ result = s.interpolate(method="zero")
+ tm.assert_series_equal(result, expected)
+ # quadratic
+ # GH #15662.
+ expected = Series([1, 3.0, 6.823529, 12.0, 18.058824, 25.0])
+ result = s.interpolate(method="quadratic")
+ tm.assert_series_equal(result, expected)
+
+ result = s.interpolate(method="quadratic")
+ tm.assert_series_equal(result, expected)
+ # cubic
+ expected = Series([1.0, 3.0, 6.8, 12.0, 18.2, 25.0])
+ result = s.interpolate(method="cubic")
+ tm.assert_series_equal(result, expected)
+
+ def test_interp_limit(self):
+ s = Series([1, 3, np.nan, np.nan, np.nan, 11])
+
+ expected = Series([1.0, 3.0, 5.0, 7.0, np.nan, 11.0])
+ result = s.interpolate(method="linear", limit=2)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("limit", [-1, 0])
+ def test_interpolate_invalid_nonpositive_limit(self, nontemporal_method, limit):
+ # GH 9217: make sure limit is greater than zero.
+ s = Series([1, 2, np.nan, 4])
+ method, kwargs = nontemporal_method
+ with pytest.raises(ValueError, match="Limit must be greater than 0"):
+ s.interpolate(limit=limit, method=method, **kwargs)
+
+ def test_interpolate_invalid_float_limit(self, nontemporal_method):
+ # GH 9217: make sure limit is an integer.
+ s = Series([1, 2, np.nan, 4])
+ method, kwargs = nontemporal_method
+ limit = 2.0
+ with pytest.raises(ValueError, match="Limit must be an integer"):
+ s.interpolate(limit=limit, method=method, **kwargs)
+
+ @pytest.mark.parametrize("invalid_method", [None, "nonexistent_method"])
+ def test_interp_invalid_method(self, invalid_method):
+ s = Series([1, 3, np.nan, 12, np.nan, 25])
+
+ msg = "Can not interpolate with method=nonexistent_method"
+ if invalid_method is None:
+ msg = "'method' should be a string, not None"
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method=invalid_method)
+
+ # When an invalid method and invalid limit (such as -1) are
+ # provided, the error message reflects the invalid method.
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method=invalid_method, limit=-1)
+
+ def test_interp_limit_forward(self):
+ s = Series([1, 3, np.nan, np.nan, np.nan, 11])
+
+ # Provide 'forward' (the default) explicitly here.
+ expected = Series([1.0, 3.0, 5.0, 7.0, np.nan, 11.0])
+
+ result = s.interpolate(method="linear", limit=2, limit_direction="forward")
+ tm.assert_series_equal(result, expected)
+
+ result = s.interpolate(method="linear", limit=2, limit_direction="FORWARD")
+ tm.assert_series_equal(result, expected)
+
+ def test_interp_unlimited(self):
+ # these test are for issue #16282 default Limit=None is unlimited
+ s = Series([np.nan, 1.0, 3.0, np.nan, np.nan, np.nan, 11.0, np.nan])
+ expected = Series([1.0, 1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 11.0])
+ result = s.interpolate(method="linear", limit_direction="both")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([np.nan, 1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 11.0])
+ result = s.interpolate(method="linear", limit_direction="forward")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([1.0, 1.0, 3.0, 5.0, 7.0, 9.0, 11.0, np.nan])
+ result = s.interpolate(method="linear", limit_direction="backward")
+ tm.assert_series_equal(result, expected)
+
+ def test_interp_limit_bad_direction(self):
+ s = Series([1, 3, np.nan, np.nan, np.nan, 11])
+
+ msg = (
+ r"Invalid limit_direction: expecting one of \['forward', "
+ r"'backward', 'both'\], got 'abc'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method="linear", limit=2, limit_direction="abc")
+
+ # raises an error even if no limit is specified.
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method="linear", limit_direction="abc")
+
+ # limit_area introduced GH #16284
+ def test_interp_limit_area(self):
+ # These tests are for issue #9218 -- fill NaNs in both directions.
+ s = Series([np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan])
+
+ expected = Series([np.nan, np.nan, 3.0, 4.0, 5.0, 6.0, 7.0, np.nan, np.nan])
+ result = s.interpolate(method="linear", limit_area="inside")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series(
+ [np.nan, np.nan, 3.0, 4.0, np.nan, np.nan, 7.0, np.nan, np.nan]
+ )
+ result = s.interpolate(method="linear", limit_area="inside", limit=1)
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([np.nan, np.nan, 3.0, 4.0, np.nan, 6.0, 7.0, np.nan, np.nan])
+ result = s.interpolate(
+ method="linear", limit_area="inside", limit_direction="both", limit=1
+ )
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, 7.0])
+ result = s.interpolate(method="linear", limit_area="outside")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series(
+ [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, np.nan]
+ )
+ result = s.interpolate(method="linear", limit_area="outside", limit=1)
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([np.nan, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, np.nan])
+ result = s.interpolate(
+ method="linear", limit_area="outside", limit_direction="both", limit=1
+ )
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([3.0, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan])
+ result = s.interpolate(
+ method="linear", limit_area="outside", limit_direction="backward"
+ )
+ tm.assert_series_equal(result, expected)
+
+ # raises an error even if limit type is wrong.
+ msg = r"Invalid limit_area: expecting one of \['inside', 'outside'\], got abc"
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method="linear", limit_area="abc")
+
+ @pytest.mark.parametrize(
+ "data, kwargs",
+ (
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ {"method": "pad", "limit_area": "inside"},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ {"method": "pad", "limit_area": "inside", "limit": 1},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ {"method": "pad", "limit_area": "outside"},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ {"method": "pad", "limit_area": "outside", "limit": 1},
+ ),
+ (
+ [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
+ {"method": "pad", "limit_area": "outside", "limit": 1},
+ ),
+ (
+ range(5),
+ {"method": "pad", "limit_area": "outside", "limit": 1},
+ ),
+ ),
+ )
+ def test_interp_limit_area_with_pad(self, data, kwargs):
+ # GH26796
+
+ s = Series(data)
+ msg = "Can not interpolate with method=pad"
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(**kwargs)
+
+ @pytest.mark.parametrize(
+ "data, kwargs",
+ (
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ {"method": "bfill", "limit_area": "inside"},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ {"method": "bfill", "limit_area": "inside", "limit": 1},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ {"method": "bfill", "limit_area": "outside"},
+ ),
+ (
+ [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+ {"method": "bfill", "limit_area": "outside", "limit": 1},
+ ),
+ ),
+ )
+ def test_interp_limit_area_with_backfill(self, data, kwargs):
+ # GH26796
+ s = Series(data)
+
+ msg = "Can not interpolate with method=bfill"
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(**kwargs)
+
+ def test_interp_limit_direction(self):
+ # These tests are for issue #9218 -- fill NaNs in both directions.
+ s = Series([1, 3, np.nan, np.nan, np.nan, 11])
+
+ expected = Series([1.0, 3.0, np.nan, 7.0, 9.0, 11.0])
+ result = s.interpolate(method="linear", limit=2, limit_direction="backward")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([1.0, 3.0, 5.0, np.nan, 9.0, 11.0])
+ result = s.interpolate(method="linear", limit=1, limit_direction="both")
+ tm.assert_series_equal(result, expected)
+
+ # Check that this works on a longer series of nans.
+ s = Series([1, 3, np.nan, np.nan, np.nan, 7, 9, np.nan, np.nan, 12, np.nan])
+
+ expected = Series([1.0, 3.0, 4.0, 5.0, 6.0, 7.0, 9.0, 10.0, 11.0, 12.0, 12.0])
+ result = s.interpolate(method="linear", limit=2, limit_direction="both")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series(
+ [1.0, 3.0, 4.0, np.nan, 6.0, 7.0, 9.0, 10.0, 11.0, 12.0, 12.0]
+ )
+ result = s.interpolate(method="linear", limit=1, limit_direction="both")
+ tm.assert_series_equal(result, expected)
+
+ def test_interp_limit_direction_limit_exceeds_length(self):
+ # GH#64322
+ s = Series([np.nan, 1, 1, 1, 1, np.nan])
+ result = s.interpolate(limit_direction="both", limit=10)
+ expected = Series([1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
+ tm.assert_series_equal(result, expected)
+
+ s2 = Series([1, np.nan, np.nan, 4])
+ result_fwd = s2.interpolate(limit_direction="forward", limit=100)
+ expected_fwd = Series([1.0, 2.0, 3.0, 4.0])
+ tm.assert_series_equal(result_fwd, expected_fwd)
+
+ result_bwd = s2.interpolate(limit_direction="backward", limit=100)
+ expected_bwd = Series([1.0, 2.0, 3.0, 4.0])
+ tm.assert_series_equal(result_bwd, expected_bwd)
+
+ def test_interp_limit_to_ends(self):
+ # These test are for issue #10420 -- flow back to beginning.
+ s = Series([np.nan, np.nan, 5, 7, 9, np.nan])
+
+ expected = Series([5.0, 5.0, 5.0, 7.0, 9.0, np.nan])
+ result = s.interpolate(method="linear", limit=2, limit_direction="backward")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([5.0, 5.0, 5.0, 7.0, 9.0, 9.0])
+ result = s.interpolate(method="linear", limit=2, limit_direction="both")
+ tm.assert_series_equal(result, expected)
+
+ def test_interp_limit_before_ends(self):
+ # These test are for issue #11115 -- limit ends properly.
+ s = Series([np.nan, np.nan, 5, 7, np.nan, np.nan])
+
+ expected = Series([np.nan, np.nan, 5.0, 7.0, 7.0, np.nan])
+ result = s.interpolate(method="linear", limit=1, limit_direction="forward")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([np.nan, 5.0, 5.0, 7.0, np.nan, np.nan])
+ result = s.interpolate(method="linear", limit=1, limit_direction="backward")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([np.nan, 5.0, 5.0, 7.0, 7.0, np.nan])
+ result = s.interpolate(method="linear", limit=1, limit_direction="both")
+ tm.assert_series_equal(result, expected)
+
+ def test_interp_all_good(self):
+ pytest.importorskip("scipy")
+ s = Series([1, 2, 3])
+ result = s.interpolate(method="polynomial", order=1)
+ tm.assert_series_equal(result, s)
+
+ # non-scipy
+ result = s.interpolate()
+ tm.assert_series_equal(result, s)
+
+ @pytest.mark.parametrize(
+ "check_scipy", [False, pytest.param(True, marks=td.skip_if_no("scipy"))]
+ )
+ def test_interp_multiIndex(self, check_scipy):
+ idx = MultiIndex.from_tuples([(0, "a"), (1, "b"), (2, "c")])
+ s = Series([1, 2, np.nan], index=idx)
+
+ expected = s.copy()
+ expected.loc[2] = 2
+ result = s.interpolate()
+ tm.assert_series_equal(result, expected)
+
+ msg = "Only `method=linear` interpolation is supported on MultiIndexes"
+ if check_scipy:
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method="polynomial", order=1)
+
+ def test_interp_nonmono_raise(self):
+ pytest.importorskip("scipy")
+ s = Series([1, np.nan, 3], index=[0, 2, 1])
+ msg = "krogh interpolation requires that the index be monotonic"
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method="krogh")
+
+ @pytest.mark.parametrize("method", ["nearest", "pad"])
+ def test_interp_datetime64(self, method, tz_naive_fixture):
+ pytest.importorskip("scipy")
+ df = Series(
+ [1, np.nan, 3], index=date_range("1/1/2000", periods=3, tz=tz_naive_fixture)
+ )
+
+ if method == "nearest":
+ result = df.interpolate(method=method)
+ expected = Series(
+ [1.0, 1.0, 3.0],
+ index=date_range("1/1/2000", periods=3, tz=tz_naive_fixture),
+ )
+ tm.assert_series_equal(result, expected)
+ else:
+ msg = "Can not interpolate with method=pad"
+ with pytest.raises(ValueError, match=msg):
+ df.interpolate(method=method)
+
+ def test_interp_pad_datetime64tz_values(self):
+ # GH#27628 missing.interpolate_2d should handle datetimetz values
+ dti = date_range("2015-04-05", periods=3, tz="US/Central")
+ ser = Series(dti)
+ ser[1] = pd.NaT
+
+ msg = "Can not interpolate with method=pad"
+ with pytest.raises(ValueError, match=msg):
+ ser.interpolate(method="pad")
+
+ def test_interp_limit_no_nans(self):
+ # GH 7173
+ s = Series([1.0, 2.0, 3.0])
+ result = s.interpolate(limit=1)
+ expected = s
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("method", ["polynomial", "spline"])
+ def test_no_order(self, method):
+ # see GH-10633, GH-24014
+ pytest.importorskip("scipy")
+ s = Series([0, 1, np.nan, 3])
+ msg = "You must specify the order of the spline or polynomial"
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method=method)
+
+ @pytest.mark.parametrize("order", [-1, -1.0, 0, 0.0, np.nan])
+ def test_interpolate_spline_invalid_order(self, order):
+ pytest.importorskip("scipy")
+ s = Series([0, 1, np.nan, 3])
+ msg = "order needs to be specified and greater than 0"
+ with pytest.raises(ValueError, match=msg):
+ s.interpolate(method="spline", order=order)
+
+ def test_spline(self):
+ pytest.importorskip("scipy")
+ s = Series([1, 2, np.nan, 4, 5, np.nan, 7])
+ result = s.interpolate(method="spline", order=1)
+ expected = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0])
+ tm.assert_series_equal(result, expected)
+
+ def test_spline_extrapolate(self):
+ pytest.importorskip("scipy")
+ s = Series([1, 2, 3, 4, np.nan, 6, np.nan])
+ result3 = s.interpolate(method="spline", order=1, ext=3)
+ expected3 = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 6.0])
+ tm.assert_series_equal(result3, expected3)
+
+ result1 = s.interpolate(method="spline", order=1, ext=0)
+ expected1 = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0])
+ tm.assert_series_equal(result1, expected1)
+
+ def test_spline_smooth(self):
+ pytest.importorskip("scipy")
+ s = Series([1, 2, np.nan, 4, 5.1, np.nan, 7])
+ assert (
+ s.interpolate(method="spline", order=3, s=0)[5]
+ != s.interpolate(method="spline", order=3)[5]
+ )
+
+ def test_spline_interpolation(self):
+ # Explicit cast to float to avoid implicit cast when setting np.nan
+ pytest.importorskip("scipy")
+ s = Series(np.arange(10) ** 2, dtype="float")
+ s[np.random.default_rng(2).integers(0, 9, 3)] = np.nan
+ result1 = s.interpolate(method="spline", order=1)
+ expected1 = s.interpolate(method="spline", order=1)
+ tm.assert_series_equal(result1, expected1)
+
+ def test_interp_timedelta64(self):
+ # GH 6424
+ df = Series([1, np.nan, 3], index=pd.to_timedelta([1, 2, 3]))
+ result = df.interpolate(method="time")
+ expected = Series([1.0, 2.0, 3.0], index=pd.to_timedelta([1, 2, 3]))
+ tm.assert_series_equal(result, expected)
+
+ # test for non uniform spacing
+ df = Series([1, np.nan, 3], index=pd.to_timedelta([1, 2, 4]))
+ result = df.interpolate(method="time")
+ expected = Series([1.0, 1.666667, 3.0], index=pd.to_timedelta([1, 2, 4]))
+ tm.assert_series_equal(result, expected)
+
+ def test_series_interpolate_method_values(self):
+ # GH#1646
+ rng = date_range("1/1/2000", "1/20/2000", freq="D")
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ ts[::2] = np.nan
+
+ result = ts.interpolate(method="values")
+ exp = ts.interpolate()
+ tm.assert_series_equal(result, exp)
+
+ def test_series_interpolate_intraday(self):
+ # #1698
+ index = date_range("1/1/2012", periods=4, freq="12D")
+ ts = Series([0, 12, 24, 36], index)
+ new_index = index.append(index + pd.DateOffset(days=1)).sort_values()
+
+ exp = ts.reindex(new_index).interpolate(method="time")
+
+ index = date_range("1/1/2012", periods=4, freq="12h")
+ ts = Series([0, 12, 24, 36], index)
+ new_index = index.append(index + pd.DateOffset(hours=1)).sort_values()
+ result = ts.reindex(new_index).interpolate(method="time")
+
+ tm.assert_numpy_array_equal(result.values, exp.values)
+
+ @pytest.mark.parametrize(
+ "ind",
+ [
+ ["a", "b", "c", "d"],
+ pd.period_range(start="2019-01-01", periods=4),
+ pd.interval_range(start=0, end=4),
+ ],
+ )
+ def test_interp_non_timedelta_index(self, interp_methods_ind, ind):
+ # gh 21662
+ df = pd.DataFrame([0, 1, np.nan, 3], index=ind)
+
+ method, kwargs = interp_methods_ind
+ if method == "pchip":
+ pytest.importorskip("scipy")
+
+ if method == "linear":
+ result = df[0].interpolate(**kwargs)
+ expected = Series([0.0, 1.0, 2.0, 3.0], name=0, index=ind)
+ tm.assert_series_equal(result, expected)
+ else:
+ expected_error = (
+ "Index column must be numeric or datetime type when "
+ f"using {method} method other than linear. "
+ "Try setting a numeric or datetime index column before "
+ "interpolating."
+ )
+ with pytest.raises(ValueError, match=expected_error):
+ df[0].interpolate(method=method, **kwargs)
+
+ def test_interpolate_timedelta_index(self, request, interp_methods_ind):
+ """
+ Tests for non numerical index types - object, period, timedelta
+ Note that all methods except time, index, nearest and values
+ are tested here.
+ """
+ # gh 21662
+ pytest.importorskip("scipy")
+ ind = pd.timedelta_range(start=1, periods=4)
+ df = pd.DataFrame([0, 1, np.nan, 3], index=ind)
+
+ method, kwargs = interp_methods_ind
+
+ if method in {"cubic", "zero"}:
+ request.applymarker(
+ pytest.mark.xfail(
+ reason=f"{method} interpolation is not supported for TimedeltaIndex"
+ )
+ )
+ result = df[0].interpolate(method=method, **kwargs)
+ expected = Series([0.0, 1.0, 2.0, 3.0], name=0, index=ind)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "ascending, expected_values",
+ [(True, [1, 2, 3, 9, 10]), (False, [10, 9, 3, 2, 1])],
+ )
+ def test_interpolate_unsorted_index(self, ascending, expected_values):
+ # GH 21037
+ ts = Series(data=[10, 9, np.nan, 2, 1], index=[10, 9, 3, 2, 1])
+ result = ts.sort_index(ascending=ascending).interpolate(method="index")
+ expected = Series(data=expected_values, index=expected_values, dtype=float)
+ tm.assert_series_equal(result, expected)
+
+ def test_interpolate_asfreq_raises(self):
+ ser = Series(["a", None, "b"], dtype=object)
+ msg = "Can not interpolate with method=asfreq"
+ with pytest.raises(ValueError, match=msg):
+ ser.interpolate(method="asfreq")
+
+ def test_interpolate_fill_value(self):
+ # GH#54920
+ pytest.importorskip("scipy")
+ ser = Series([np.nan, 0, 1, np.nan, 3, np.nan])
+ result = ser.interpolate(method="nearest", fill_value=0)
+ expected = Series([np.nan, 0, 1, 1, 3, 0])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_is_monotonic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_is_monotonic.py
new file mode 100644
index 0000000000000000000000000000000000000000..073ec4172aff6b041d29011bc3151f84f3cbeb19
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_is_monotonic.py
@@ -0,0 +1,26 @@
+import numpy as np
+
+from pandas import (
+ Series,
+ date_range,
+)
+
+
+class TestIsMonotonic:
+ def test_is_monotonic_numeric(self):
+ ser = Series(np.random.default_rng(2).integers(0, 10, size=1000))
+ assert not ser.is_monotonic_increasing
+ ser = Series(np.arange(1000))
+ assert ser.is_monotonic_increasing is True
+ assert ser.is_monotonic_increasing is True
+ ser = Series(np.arange(1000, 0, -1))
+ assert ser.is_monotonic_decreasing is True
+
+ def test_is_monotonic_dt64(self):
+ ser = Series(date_range("20130101", periods=10))
+ assert ser.is_monotonic_increasing is True
+ assert ser.is_monotonic_increasing is True
+
+ ser = Series(list(reversed(ser)))
+ assert ser.is_monotonic_increasing is False
+ assert ser.is_monotonic_decreasing is True
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_is_unique.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_is_unique.py
new file mode 100644
index 0000000000000000000000000000000000000000..edf3839c2cebb6f51d0bb21b06ea8a1c47dec0fe
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_is_unique.py
@@ -0,0 +1,40 @@
+import numpy as np
+import pytest
+
+from pandas import Series
+
+
+@pytest.mark.parametrize(
+ "data, expected",
+ [
+ (np.random.default_rng(2).integers(0, 10, size=1000), False),
+ (np.arange(1000), True),
+ ([], True),
+ ([np.nan], True),
+ (["foo", "bar", np.nan], True),
+ (["foo", "foo", np.nan], False),
+ (["foo", "bar", np.nan, np.nan], False),
+ ],
+)
+def test_is_unique(data, expected):
+ # GH#11946 / GH#25180
+ ser = Series(data)
+ assert ser.is_unique is expected
+
+
+def test_is_unique_class_ne(capsys):
+ # GH#20661
+ class Foo:
+ def __init__(self, val) -> None:
+ self._value = val
+
+ def __ne__(self, other):
+ raise Exception("NEQ not supported")
+
+ with capsys.disabled():
+ li = [Foo(i) for i in range(5)]
+ ser = Series(li, index=list(range(5)))
+
+ ser.is_unique
+ captured = capsys.readouterr()
+ assert len(captured.err) == 0
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_isin.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_isin.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f8484252ba8ff3ebea28e5d4990a6eba0a15716
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_isin.py
@@ -0,0 +1,269 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.core import algorithms
+from pandas.core.arrays import PeriodArray
+
+
+class TestSeriesIsIn:
+ def test_isin(self):
+ s = Series(["A", "B", "C", "a", "B", "B", "A", "C"])
+
+ result = s.isin(["A", "C"])
+ expected = Series([True, False, True, False, False, False, True, True])
+ tm.assert_series_equal(result, expected)
+
+ # GH#16012
+ # This specific issue has to have a series over 1e6 in len, but the
+ # comparison array (in_list) must be large enough so that numpy doesn't
+ # do a manual masking trick that will avoid this issue altogether
+ s = Series(list("abcdefghijk" * 10**5))
+ # If numpy doesn't do the manual comparison/mask, these
+ # unorderable mixed types are what cause the exception in numpy
+ in_list = [-1, "a", "b", "G", "Y", "Z", "E", "K", "E", "S", "I", "R", "R"] * 6
+
+ assert s.isin(in_list).sum() == 200000
+
+ def test_isin_with_string_scalar(self):
+ # GH#4763
+ s = Series(["A", "B", "C", "a", "B", "B", "A", "C"])
+ msg = (
+ r"only list-like objects are allowed to be passed to isin\(\), "
+ r"you passed a `str`"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s.isin("a")
+
+ s = Series(["aaa", "b", "c"])
+ with pytest.raises(TypeError, match=msg):
+ s.isin("aaa")
+
+ def test_isin_datetimelike_mismatched_reso(self):
+ expected = Series([True, True, False, False, False])
+
+ ser = Series(date_range("jan-01-2013", "jan-05-2013"))
+
+ # fails on dtype conversion in the first place
+ day_values = np.asarray(ser[0:2].values).astype("datetime64[D]")
+ result = ser.isin(day_values)
+ tm.assert_series_equal(result, expected)
+
+ dta = ser[:2]._values.astype("M8[s]")
+ result = ser.isin(dta)
+ tm.assert_series_equal(result, expected)
+
+ def test_isin_datetimelike_mismatched_reso_list(self):
+ expected = Series([True, True, False, False, False])
+
+ ser = Series(date_range("jan-01-2013", "jan-05-2013"))
+
+ dta = ser[:2]._values.astype("M8[s]")
+ result = ser.isin(list(dta))
+ tm.assert_series_equal(result, expected)
+
+ def test_isin_with_i8(self):
+ # GH#5021
+
+ expected = Series([True, True, False, False, False])
+ expected2 = Series([False, True, False, False, False])
+
+ # datetime64[ns]
+ s = Series(date_range("jan-01-2013", "jan-05-2013"))
+
+ result = s.isin(s[0:2])
+ tm.assert_series_equal(result, expected)
+
+ result = s.isin(s[0:2].values)
+ tm.assert_series_equal(result, expected)
+
+ result = s.isin([s[1]])
+ tm.assert_series_equal(result, expected2)
+
+ result = s.isin([np.datetime64(s[1])])
+ tm.assert_series_equal(result, expected2)
+
+ result = s.isin(set(s[0:2]))
+ tm.assert_series_equal(result, expected)
+
+ # timedelta64[ns]
+ s = Series(pd.to_timedelta(range(5), unit="D"))
+ result = s.isin(s[0:2])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("empty", [[], Series(dtype=object), np.array([])])
+ def test_isin_empty(self, empty):
+ # see GH#16991
+ s = Series(["a", "b"])
+ expected = Series([False, False])
+
+ result = s.isin(empty)
+ tm.assert_series_equal(expected, result)
+
+ def test_isin_read_only(self):
+ # https://github.com/pandas-dev/pandas/issues/37174
+ arr = np.array([1, 2, 3])
+ arr.setflags(write=False)
+ s = Series([1, 2, 3])
+ result = s.isin(arr)
+ expected = Series([True, True, True])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", [object, None])
+ def test_isin_dt64_values_vs_ints(self, dtype):
+ # GH#36621 dont cast integers to datetimes for isin
+ dti = date_range("2013-01-01", "2013-01-05")
+ ser = Series(dti)
+
+ comps = np.asarray([1356998400000000000], dtype=dtype)
+
+ res = dti.isin(comps)
+ expected = np.array([False] * len(dti), dtype=bool)
+ tm.assert_numpy_array_equal(res, expected)
+
+ res = ser.isin(comps)
+ tm.assert_series_equal(res, Series(expected))
+
+ res = pd.core.algorithms.isin(ser, comps)
+ tm.assert_numpy_array_equal(res, expected)
+
+ def test_isin_tzawareness_mismatch(self):
+ dti = date_range("2013-01-01", "2013-01-05")
+ ser = Series(dti)
+
+ other = dti.tz_localize("UTC")
+
+ res = dti.isin(other)
+ expected = np.array([False] * len(dti), dtype=bool)
+ tm.assert_numpy_array_equal(res, expected)
+
+ res = ser.isin(other)
+ tm.assert_series_equal(res, Series(expected))
+
+ res = pd.core.algorithms.isin(ser, other)
+ tm.assert_numpy_array_equal(res, expected)
+
+ def test_isin_period_freq_mismatch(self):
+ dti = date_range("2013-01-01", "2013-01-05")
+ pi = dti.to_period("M")
+ ser = Series(pi)
+
+ # We construct another PeriodIndex with the same i8 values
+ # but different dtype
+ dtype = dti.to_period("Y").dtype
+ other = PeriodArray._simple_new(pi.asi8, dtype=dtype)
+
+ res = pi.isin(other)
+ expected = np.array([False] * len(pi), dtype=bool)
+ tm.assert_numpy_array_equal(res, expected)
+
+ res = ser.isin(other)
+ tm.assert_series_equal(res, Series(expected))
+
+ res = pd.core.algorithms.isin(ser, other)
+ tm.assert_numpy_array_equal(res, expected)
+
+ @pytest.mark.parametrize("values", [[-9.0, 0.0], [-9, 0]])
+ def test_isin_float_in_int_series(self, values):
+ # GH#19356 GH#21804
+ ser = Series(values)
+ result = ser.isin([-9, -0.5])
+ expected = Series([True, False])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["boolean", "Int64", "Float64"])
+ @pytest.mark.parametrize(
+ "data,values,expected",
+ [
+ ([0, 1, 0], [1], [False, True, False]),
+ ([0, 1, 0], [1, pd.NA], [False, True, False]),
+ ([0, pd.NA, 0], [1, 0], [True, False, True]),
+ ([0, 1, pd.NA], [1, pd.NA], [False, True, True]),
+ ([0, 1, pd.NA], [1, np.nan], [False, True, False]),
+ ([0, pd.NA, pd.NA], [np.nan, pd.NaT, None], [False, False, False]),
+ ],
+ )
+ def test_isin_masked_types(self, dtype, data, values, expected):
+ # GH#42405
+ ser = Series(data, dtype=dtype)
+
+ result = ser.isin(values)
+ expected = Series(expected, dtype="boolean")
+
+ tm.assert_series_equal(result, expected)
+
+
+def test_isin_large_series_mixed_dtypes_and_nan(monkeypatch):
+ # https://github.com/pandas-dev/pandas/issues/37094
+ # combination of object dtype for the values
+ # and > _MINIMUM_COMP_ARR_LEN elements
+ min_isin_comp = 5
+ ser = Series([1, 2, np.nan] * min_isin_comp)
+ with monkeypatch.context() as m:
+ m.setattr(algorithms, "_MINIMUM_COMP_ARR_LEN", min_isin_comp)
+ result = ser.isin({"foo", "bar"})
+ expected = Series([False] * 3 * min_isin_comp)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "dtype, data, values, expected",
+ [
+ ("boolean", [pd.NA, False, True], [False, pd.NA], [True, True, False]),
+ ("Int64", [pd.NA, 2, 1], [1, pd.NA], [True, False, True]),
+ ("boolean", [pd.NA, False, True], [pd.NA, True, "a", 20], [True, False, True]),
+ ("boolean", [pd.NA, False, True], [], [False, False, False]),
+ ("Float64", [20.0, 30.0, pd.NA], [pd.NA], [False, False, True]),
+ ],
+)
+def test_isin_large_series_and_pdNA(dtype, data, values, expected, monkeypatch):
+ # https://github.com/pandas-dev/pandas/issues/60678
+ # combination of large series (> _MINIMUM_COMP_ARR_LEN elements) and
+ # values contains pdNA
+ min_isin_comp = 2
+ ser = Series(data, dtype=dtype)
+ expected = Series(expected, dtype="boolean")
+
+ with monkeypatch.context() as m:
+ m.setattr(algorithms, "_MINIMUM_COMP_ARR_LEN", min_isin_comp)
+ result = ser.isin(values)
+ tm.assert_series_equal(result, expected)
+
+
+def test_isin_complex_numbers():
+ # GH 17927
+ array = [0, 1j, 1j, 1, 1 + 1j, 1 + 2j, 1 + 1j]
+ result = Series(array).isin([1j, 1 + 1j, 1 + 2j])
+ expected = Series([False, True, True, False, True, True, True], dtype=bool)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data,is_in",
+ [([1, [2]], [1]), (["simple str", [{"values": 3}]], ["simple str"])],
+)
+def test_isin_filtering_with_mixed_object_types(data, is_in):
+ # GH 20883
+
+ ser = Series(data)
+ result = ser.isin(is_in)
+ expected = Series([True, False])
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("data", [[1, 2, 3], [1.0, 2.0, 3.0]])
+@pytest.mark.parametrize("isin", [[1, 2], [1.0, 2.0]])
+def test_isin_filtering_on_iterable(data, isin):
+ # GH 50234
+
+ ser = Series(data)
+ result = ser.isin(i for i in isin)
+ expected_result = Series([True, True, False])
+
+ tm.assert_series_equal(result, expected_result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_isna.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_isna.py
new file mode 100644
index 0000000000000000000000000000000000000000..92bf2945cc0d1f5967683a8b7edb35a896388e60
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_isna.py
@@ -0,0 +1,36 @@
+"""
+We also test Series.notna in this file.
+"""
+
+import numpy as np
+
+from pandas import (
+ Period,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestIsna:
+ def test_isna_period_dtype(self):
+ # GH#13737
+ ser = Series([Period("2011-01", freq="M"), Period("NaT", freq="M")])
+
+ expected = Series([False, True])
+
+ result = ser.isna()
+ tm.assert_series_equal(result, expected)
+
+ result = ser.notna()
+ tm.assert_series_equal(result, ~expected)
+
+ def test_isna(self):
+ ser = Series([0, 5.4, 3, np.nan, -0.001])
+ expected = Series([False, False, False, True, False])
+ tm.assert_series_equal(ser.isna(), expected)
+ tm.assert_series_equal(ser.notna(), ~expected)
+
+ ser = Series(["hi", "", np.nan])
+ expected = Series([False, False, True])
+ tm.assert_series_equal(ser.isna(), expected)
+ tm.assert_series_equal(ser.notna(), ~expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_item.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_item.py
new file mode 100644
index 0000000000000000000000000000000000000000..761ab84485b7987fd2f39ccaa25898b84b6ba96b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_item.py
@@ -0,0 +1,60 @@
+"""
+Series.item method, mainly testing that we get python scalars as opposed to
+numpy scalars.
+"""
+
+import pytest
+
+from pandas import (
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+)
+
+
+class TestItem:
+ def test_item(self):
+ # We are testing that we get python scalars as opposed to numpy scalars
+ ser = Series([1])
+ result = ser.item()
+ assert result == 1
+ assert result == ser.iloc[0]
+ assert isinstance(result, int) # i.e. not np.int64
+
+ ser = Series([0.5], index=[3])
+ result = ser.item()
+ assert type(result) == float
+ assert result == 0.5
+
+ ser = Series([1, 2])
+ msg = "can only convert an array of size 1"
+ with pytest.raises(ValueError, match=msg):
+ ser.item()
+
+ dti = date_range("2016-01-01", periods=2)
+ with pytest.raises(ValueError, match=msg):
+ dti.item()
+ with pytest.raises(ValueError, match=msg):
+ Series(dti).item()
+
+ val = dti[:1].item()
+ assert isinstance(val, Timestamp)
+ val = Series(dti)[:1].item()
+ assert isinstance(val, Timestamp)
+
+ tdi = dti - dti
+ with pytest.raises(ValueError, match=msg):
+ tdi.item()
+ with pytest.raises(ValueError, match=msg):
+ Series(tdi).item()
+
+ val = tdi[:1].item()
+ assert isinstance(val, Timedelta)
+ val = Series(tdi)[:1].item()
+ assert isinstance(val, Timedelta)
+
+ # Case where ser[0] would not work
+ ser = Series(dti, index=[5, 6])
+ val = ser.iloc[:1].item()
+ assert val == dti[0]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_map.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_map.py
new file mode 100644
index 0000000000000000000000000000000000000000..bfda028b58011c922f52f904f6d98d05e955b48f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_map.py
@@ -0,0 +1,679 @@
+from collections import (
+ Counter,
+ defaultdict,
+)
+from decimal import Decimal
+import math
+
+import numpy as np
+import pytest
+
+from pandas.errors import Pandas4Warning
+import pandas.util._test_decorators as td
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ bdate_range,
+ date_range,
+ isna,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+# The fixture it's mostly used in pandas/tests/apply, so it's defined in that
+# conftest, which is out of scope here. So we need to manually import
+from pandas.tests.apply.conftest import engine # noqa: F401
+
+
+def test_series_map_box_timedelta():
+ # GH#11349
+ ser = Series(timedelta_range("1 day 1 s", periods=5, freq="h"))
+
+ def f(x):
+ return x.total_seconds()
+
+ ser.map(f)
+
+
+def test_map_callable(datetime_series, engine): # noqa: F811
+ with np.errstate(all="ignore"):
+ tm.assert_series_equal(
+ datetime_series.map(np.sqrt, engine=engine), np.sqrt(datetime_series)
+ )
+
+ # map function element-wise
+ tm.assert_series_equal(
+ datetime_series.map(math.exp, engine=engine), np.exp(datetime_series)
+ )
+
+ # empty series
+ s = Series(dtype=object, name="foo", index=Index([], name="bar"))
+ rs = s.map(lambda x: x, engine=engine)
+ tm.assert_series_equal(s, rs)
+
+ # check all metadata (GH 9322)
+ assert s is not rs
+ assert s.index is rs.index
+ assert s.dtype == rs.dtype
+ assert s.name == rs.name
+
+ # index but no data
+ s = Series(index=[1, 2, 3], dtype=np.float64)
+ rs = s.map(lambda x: x, engine=engine)
+ tm.assert_series_equal(s, rs)
+
+
+def test_map_same_length_inference_bug():
+ s = Series([1, 2])
+
+ def f(x):
+ return (x, x + 1)
+
+ s = Series([1, 2, 3])
+ result = s.map(f)
+ expected = Series([(1, 2), (2, 3), (3, 4)])
+ tm.assert_series_equal(result, expected)
+
+ s = Series(["foo,bar"])
+ result = s.map(lambda x: x.split(","))
+ expected = Series([("foo", "bar")])
+ tm.assert_series_equal(result, expected)
+
+
+def test_series_map_box_timestamps():
+ # GH#2689, GH#2627
+ ser = Series(date_range("1/1/2000", periods=3))
+
+ def func(x):
+ return (x.hour, x.day, x.month)
+
+ result = ser.map(func)
+ expected = Series([(0, 1, 1), (0, 2, 1), (0, 3, 1)])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_series_stringdtype(any_string_dtype, using_infer_string):
+ # map test on StringDType, GH#40823
+ ser1 = Series(
+ data=["cat", "dog", "rabbit"],
+ index=["id1", "id2", "id3"],
+ dtype=any_string_dtype,
+ )
+ ser2 = Series(["id3", "id2", "id1", "id7000"], dtype=any_string_dtype)
+ result = ser2.map(ser1)
+
+ item = pd.NA
+ if ser2.dtype == object:
+ item = np.nan
+
+ expected = Series(data=["rabbit", "dog", "cat", item], dtype=any_string_dtype)
+ if using_infer_string and any_string_dtype == "object":
+ expected = expected.astype("str")
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "data, expected_dtype",
+ [(["1-1", "1-1", np.nan], "category"), (["1-1", "1-2", np.nan], "str")],
+)
+def test_map_categorical_with_nan_values(data, expected_dtype):
+ # GH 20714 bug fixed in: GH 24275
+ def func(val):
+ return val.split("-")[0]
+
+ s = Series(data, dtype="category")
+
+ result = s.map(func, na_action="ignore")
+ expected = Series(["1", "1", np.nan], dtype=expected_dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_empty_integer_series():
+ # GH52384
+ s = Series([], dtype=int)
+ result = s.map(lambda x: x)
+ tm.assert_series_equal(result, s)
+
+
+def test_map_empty_integer_series_with_datetime_index():
+ # GH 21245
+ s = Series([], index=date_range(start="2018-01-01", periods=0), dtype=int)
+ result = s.map(lambda x: x)
+ tm.assert_series_equal(result, s)
+
+
+@pytest.mark.parametrize("func", [str, lambda x: str(x)])
+def test_map_simple_str_callables_same_as_astype(
+ string_series, func, using_infer_string
+):
+ # test that we are evaluating row-by-row first
+ # before vectorized evaluation
+ result = string_series.map(func)
+ expected = string_series.astype(str if not using_infer_string else "str")
+ tm.assert_series_equal(result, expected)
+
+
+def test_list_raises(string_series):
+ with pytest.raises(TypeError, match="'list' object is not callable"):
+ string_series.map([lambda x: x])
+
+
+def test_map():
+ data = {
+ "A": [0.0, 1.0, 2.0, 3.0, 4.0],
+ "B": [0.0, 1.0, 0.0, 1.0, 0.0],
+ "C": ["foo1", "foo2", "foo3", "foo4", "foo5"],
+ "D": bdate_range("1/1/2009", periods=5),
+ }
+
+ source = Series(data["B"], index=data["C"])
+ target = Series(data["C"][:4], index=data["D"][:4])
+
+ merged = target.map(source)
+
+ for k, v in merged.items():
+ assert v == source[target[k]]
+
+ # input could be a dict
+ merged = target.map(source.to_dict())
+
+ for k, v in merged.items():
+ assert v == source[target[k]]
+
+
+def test_map_datetime(datetime_series):
+ # function
+ result = datetime_series.map(lambda x: x * 2)
+ tm.assert_series_equal(result, datetime_series * 2)
+
+
+def test_map_category():
+ # GH 10324
+ a = Series([1, 2, 3, 4])
+ b = Series(["even", "odd", "even", "odd"], dtype="category")
+ c = Series(["even", "odd", "even", "odd"])
+
+ exp = Series(["odd", "even", "odd", np.nan], dtype="category")
+ tm.assert_series_equal(a.map(b), exp)
+ exp = Series(["odd", "even", "odd", np.nan])
+ tm.assert_series_equal(a.map(c), exp)
+
+
+def test_map_category_numeric():
+ a = Series(["a", "b", "c", "d"])
+ b = Series([1, 2, 3, 4], index=pd.CategoricalIndex(["b", "c", "d", "e"]))
+ c = Series([1, 2, 3, 4], index=Index(["b", "c", "d", "e"]))
+
+ exp = Series([np.nan, 1, 2, 3])
+ tm.assert_series_equal(a.map(b), exp)
+ exp = Series([np.nan, 1, 2, 3])
+ tm.assert_series_equal(a.map(c), exp)
+
+
+def test_map_category_string():
+ a = Series(["a", "b", "c", "d"])
+ b = Series(
+ ["B", "C", "D", "E"],
+ dtype="category",
+ index=pd.CategoricalIndex(["b", "c", "d", "e"]),
+ )
+ c = Series(["B", "C", "D", "E"], index=Index(["b", "c", "d", "e"]))
+
+ exp = Series(
+ pd.Categorical([np.nan, "B", "C", "D"], categories=["B", "C", "D", "E"])
+ )
+ tm.assert_series_equal(a.map(b), exp)
+ exp = Series([np.nan, "B", "C", "D"])
+ tm.assert_series_equal(a.map(c), exp)
+
+
+def test_map_empty(request, index):
+ if isinstance(index, MultiIndex):
+ request.applymarker(
+ pytest.mark.xfail(
+ reason="Initializing a Series from a MultiIndex is not supported"
+ )
+ )
+
+ s = Series(index)
+ result = s.map({})
+
+ expected = Series(np.nan, index=s.index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_compat():
+ # related GH 8024
+ s = Series([True, True, False], index=[1, 2, 3])
+ result = s.map({True: "foo", False: "bar"})
+ expected = Series(["foo", "foo", "bar"], index=[1, 2, 3])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_int():
+ left = Series({"a": 1.0, "b": 2.0, "c": 3.0, "d": 4})
+ right = Series({1: 11, 2: 22, 3: 33})
+
+ assert left.dtype == np.float64
+ assert issubclass(right.dtype.type, np.integer)
+
+ merged = left.map(right)
+ assert merged.dtype == np.float64
+ assert isna(merged["d"])
+ assert not isna(merged["c"])
+
+
+def test_map_type_inference():
+ s = Series(range(3))
+ s2 = s.map(lambda x: np.where(x == 0, 0, 1))
+ assert issubclass(s2.dtype.type, np.integer)
+
+
+def test_map_decimal(string_series):
+ result = string_series.map(lambda x: Decimal(str(x)))
+ assert result.dtype == np.object_
+ assert isinstance(result.iloc[0], Decimal)
+
+
+def test_map_na_exclusion(engine): # noqa: F811
+ s = Series([1.5, np.nan, 3, np.nan, 5])
+
+ result = s.map(lambda x: x * 2, na_action="ignore", engine=engine)
+ exp = s * 2
+ tm.assert_series_equal(result, exp)
+
+
+def test_map_dict_with_tuple_keys():
+ """
+ Due to new MultiIndex-ing behaviour in v0.14.0,
+ dicts with tuple keys passed to map were being
+ converted to a multi-index, preventing tuple values
+ from being mapped properly.
+ """
+ # GH 18496
+ df = DataFrame({"a": [(1,), (2,), (3, 4), (5, 6)]})
+ label_mappings = {(1,): "A", (2,): "B", (3, 4): "A", (5, 6): "B"}
+
+ df["labels"] = df["a"].map(label_mappings)
+ df["expected_labels"] = Series(["A", "B", "A", "B"], index=df.index)
+ # All labels should be filled now
+ tm.assert_series_equal(df["labels"], df["expected_labels"], check_names=False)
+
+
+def test_map_counter():
+ s = Series(["a", "b", "c"], index=[1, 2, 3])
+ counter = Counter()
+ counter["b"] = 5
+ counter["c"] += 1
+ result = s.map(counter)
+ expected = Series([0, 5, 1], index=[1, 2, 3])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_defaultdict():
+ s = Series([1, 2, 3], index=["a", "b", "c"])
+ default_dict = defaultdict(lambda: "blank")
+ default_dict[1] = "stuff"
+ result = s.map(default_dict)
+ expected = Series(["stuff", "blank", "blank"], index=["a", "b", "c"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_dict_na_key():
+ # https://github.com/pandas-dev/pandas/issues/17648
+ # Checks that np.nan key is appropriately mapped
+ s = Series([1, 2, np.nan])
+ expected = Series(["a", "b", "c"])
+ result = s.map({1: "a", 2: "b", np.nan: "c"})
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_defaultdict_na_key(na_action):
+ # GH 48813
+ s = Series([1, 2, np.nan])
+ default_map = defaultdict(lambda: "missing", {1: "a", 2: "b", np.nan: "c"})
+ result = s.map(default_map, na_action=na_action)
+ expected = Series({0: "a", 1: "b", 2: "c" if na_action is None else np.nan})
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_defaultdict_missing_key(na_action):
+ # GH 48813
+ s = Series([1, 2, np.nan])
+ default_map = defaultdict(lambda: "missing", {1: "a", 2: "b", 3: "c"})
+ result = s.map(default_map, na_action=na_action)
+ expected = Series({0: "a", 1: "b", 2: "missing" if na_action is None else np.nan})
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_defaultdict_unmutated(na_action):
+ # GH 48813
+ s = Series([1, 2, np.nan])
+ default_map = defaultdict(lambda: "missing", {1: "a", 2: "b", np.nan: "c"})
+ expected_default_map = default_map.copy()
+ s.map(default_map, na_action=na_action)
+ assert default_map == expected_default_map
+
+
+@pytest.mark.parametrize("arg_func", [dict, Series])
+def test_map_dict_ignore_na(arg_func):
+ # GH#47527
+ mapping = arg_func({1: 10, np.nan: 42})
+ ser = Series([1, np.nan, 2])
+ result = ser.map(mapping, na_action="ignore")
+ expected = Series([10, np.nan, np.nan])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_defaultdict_ignore_na():
+ # GH#47527
+ mapping = defaultdict(int, {1: 10, np.nan: 42})
+ ser = Series([1, np.nan, 2])
+ result = ser.map(mapping)
+ expected = Series([10, 42, 0])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "na_action, expected",
+ [(None, Series([10.0, 42.0, np.nan])), ("ignore", Series([10, np.nan, np.nan]))],
+)
+def test_map_categorical_na_ignore(na_action, expected):
+ # GH#47527
+ values = pd.Categorical([1, np.nan, 2], categories=[10, 1, 2])
+ ser = Series(values)
+ result = ser.map({1: 10, np.nan: 42}, na_action=na_action)
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_dict_subclass_with_missing():
+ """
+ Test Series.map with a dictionary subclass that defines __missing__,
+ i.e. sets a default value (GH #15999).
+ """
+
+ class DictWithMissing(dict):
+ def __missing__(self, key):
+ return "missing"
+
+ s = Series([1, 2, 3])
+ dictionary = DictWithMissing({3: "three"})
+ result = s.map(dictionary)
+ expected = Series(["missing", "missing", "three"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_dict_subclass_without_missing():
+ class DictWithoutMissing(dict):
+ pass
+
+ s = Series([1, 2, 3])
+ dictionary = DictWithoutMissing({3: "three"})
+ result = s.map(dictionary)
+ expected = Series([np.nan, np.nan, "three"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_abc_mapping(non_dict_mapping_subclass):
+ # https://github.com/pandas-dev/pandas/issues/29733
+ # Check collections.abc.Mapping support as mapper for Series.map
+ s = Series([1, 2, 3])
+ not_a_dictionary = non_dict_mapping_subclass({3: "three"})
+ result = s.map(not_a_dictionary)
+ expected = Series([np.nan, np.nan, "three"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_abc_mapping_with_missing(non_dict_mapping_subclass):
+ # https://github.com/pandas-dev/pandas/issues/29733
+ # Check collections.abc.Mapping support as mapper for Series.map
+ class NonDictMappingWithMissing(non_dict_mapping_subclass):
+ def __missing__(self, key):
+ return "missing"
+
+ s = Series([1, 2, 3])
+ not_a_dictionary = NonDictMappingWithMissing({3: "three"})
+ result = s.map(not_a_dictionary)
+ # __missing__ is a dict concept, not a Mapping concept,
+ # so it should not change the result!
+ expected = Series([np.nan, np.nan, "three"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_box_dt64(unit):
+ vals = [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")]
+ ser = Series(vals).dt.as_unit(unit)
+ assert ser.dtype == f"datetime64[{unit}]"
+ # boxed value must be Timestamp instance
+ res = ser.map(lambda x: f"{type(x).__name__}_{x.day}_{x.tz}")
+ exp = Series(["Timestamp_1_None", "Timestamp_2_None"])
+ tm.assert_series_equal(res, exp)
+
+
+def test_map_box_dt64tz(unit):
+ vals = [
+ pd.Timestamp("2011-01-01", tz="US/Eastern"),
+ pd.Timestamp("2011-01-02", tz="US/Eastern"),
+ ]
+ ser = Series(vals).dt.as_unit(unit)
+ assert ser.dtype == f"datetime64[{unit}, US/Eastern]"
+ res = ser.map(lambda x: f"{type(x).__name__}_{x.day}_{x.tz}")
+ exp = Series(["Timestamp_1_US/Eastern", "Timestamp_2_US/Eastern"])
+ tm.assert_series_equal(res, exp)
+
+
+def test_map_box_td64(unit):
+ # timedelta
+ vals = [pd.Timedelta("1 days"), pd.Timedelta("2 days")]
+ ser = Series(vals).dt.as_unit(unit)
+ assert ser.dtype == f"timedelta64[{unit}]"
+ res = ser.map(lambda x: f"{type(x).__name__}_{x.days}")
+ exp = Series(["Timedelta_1", "Timedelta_2"])
+ tm.assert_series_equal(res, exp)
+
+
+def test_map_box_period():
+ # period
+ vals = [pd.Period("2011-01-01", freq="M"), pd.Period("2011-01-02", freq="M")]
+ ser = Series(vals)
+ assert ser.dtype == "Period[M]"
+ res = ser.map(lambda x: f"{type(x).__name__}_{x.freqstr}")
+ exp = Series(["Period_M", "Period_M"])
+ tm.assert_series_equal(res, exp)
+
+
+def test_map_categorical(na_action, using_infer_string):
+ values = pd.Categorical(list("ABBABCD"), categories=list("DCBA"), ordered=True)
+ s = Series(values, name="XX", index=list("abcdefg"))
+
+ result = s.map(lambda x: x.lower(), na_action=na_action)
+ exp_values = pd.Categorical(list("abbabcd"), categories=list("dcba"), ordered=True)
+ exp = Series(exp_values, name="XX", index=list("abcdefg"))
+ tm.assert_series_equal(result, exp)
+ tm.assert_categorical_equal(result.values, exp_values)
+
+ result = s.map(lambda x: "A", na_action=na_action)
+ exp = Series(["A"] * 7, name="XX", index=list("abcdefg"))
+ tm.assert_series_equal(result, exp)
+ assert result.dtype == object if not using_infer_string else "str"
+
+
+@pytest.mark.parametrize(
+ "na_action, expected",
+ (
+ [None, Series(["A", "B", "nan"], name="XX")],
+ [
+ "ignore",
+ Series(
+ ["A", "B", np.nan],
+ name="XX",
+ dtype=pd.CategoricalDtype(list("DCBA"), True),
+ ),
+ ],
+ ),
+)
+def test_map_categorical_na_action(na_action, expected):
+ dtype = pd.CategoricalDtype(list("DCBA"), ordered=True)
+ values = pd.Categorical([*list("AB"), np.nan], dtype=dtype)
+ s = Series(values, name="XX")
+ result = s.map(str, na_action=na_action)
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_datetimetz():
+ values = date_range("2011-01-01", "2011-01-02", freq="h").tz_localize("Asia/Tokyo")
+ s = Series(values, name="XX")
+
+ # keep tz
+ result = s.map(lambda x: x + pd.offsets.Day())
+ exp_values = date_range("2011-01-02", "2011-01-03", freq="h").tz_localize(
+ "Asia/Tokyo"
+ )
+ exp = Series(exp_values, name="XX")
+ tm.assert_series_equal(result, exp)
+
+ result = s.map(lambda x: x.hour)
+ exp = Series([*list(range(24)), 0], name="XX", dtype=np.int64)
+ tm.assert_series_equal(result, exp)
+
+ # not vectorized
+ def f(x):
+ if not isinstance(x, pd.Timestamp):
+ raise ValueError
+ return str(x.tz)
+
+ result = s.map(f)
+ exp = Series(["Asia/Tokyo"] * 25, name="XX")
+ tm.assert_series_equal(result, exp)
+
+
+@pytest.mark.parametrize(
+ "vals,mapping,exp",
+ [
+ (list("abc"), {np.nan: "not NaN"}, [np.nan] * 3 + ["not NaN"]),
+ (list("abc"), {"a": "a letter"}, ["a letter"] + [np.nan] * 3),
+ (list(range(3)), {0: 42}, [42] + [np.nan] * 3),
+ ],
+)
+def test_map_missing_mixed(vals, mapping, exp):
+ # GH20495
+ s = Series([*vals, np.nan])
+ result = s.map(mapping)
+ exp = Series(exp)
+ tm.assert_series_equal(result, exp)
+
+
+def test_map_scalar_on_date_time_index_aware_series():
+ # GH 25959
+ # Calling map on a localized time series should not cause an error
+ series = Series(
+ np.arange(10, dtype=np.float64),
+ index=date_range("2020-01-01", periods=10, tz="UTC"),
+ name="ts",
+ )
+ result = Series(series.index).map(lambda x: 1)
+ tm.assert_series_equal(result, Series(np.ones(len(series)), dtype="int64"))
+
+
+def test_map_float_to_string_precision():
+ # GH 13228
+ ser = Series(1 / 3)
+ result = ser.map(lambda val: str(val)).to_dict()
+ expected = {0: "0.3333333333333333"}
+ assert result == expected
+
+
+def test_map_to_timedelta():
+ list_of_valid_strings = ["00:00:01", "00:00:02"]
+ a = pd.to_timedelta(list_of_valid_strings)
+ b = Series(list_of_valid_strings).map(pd.to_timedelta)
+ tm.assert_series_equal(Series(a), b)
+
+ list_of_strings = ["00:00:01", np.nan, pd.NaT, pd.NaT]
+
+ a = pd.to_timedelta(list_of_strings)
+ ser = Series(list_of_strings)
+ b = ser.map(pd.to_timedelta)
+ tm.assert_series_equal(Series(a), b)
+
+
+def test_map_type():
+ # GH 46719
+ s = Series([3, "string", float], index=["a", "b", "c"])
+ result = s.map(type)
+ expected = Series([int, str, type], index=["a", "b", "c"])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_kwargs():
+ # GH 59814
+ result = Series([2, 4, 5]).map(lambda x, y: x + y, y=2)
+ expected = Series([4, 6, 7])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_arg_as_kwarg():
+ with tm.assert_produces_warning(
+ Pandas4Warning, match="`arg` has been renamed to `func`"
+ ):
+ Series([1, 2]).map(arg={})
+
+
+def test_map_func_and_arg():
+ # `arg`is considered a normal kwarg that should be passed to the function
+ result = Series([1, 2]).map(lambda _, arg: arg, arg=3)
+ expected = Series([3, 3])
+ tm.assert_series_equal(result, expected)
+
+
+def test_map_no_func_or_arg():
+ with pytest.raises(ValueError, match="The `func` parameter is required"):
+ Series([1, 2]).map()
+
+
+def test_map_func_is_none():
+ with pytest.raises(ValueError, match="The `func` parameter is required"):
+ Series([1, 2]).map(func=None)
+
+
+@pytest.mark.parametrize("func", [{}, {1: 2}, Series([3, 4])])
+def test_map_engine_no_function(func):
+ s = Series([1, 2])
+
+ with pytest.raises(ValueError, match="engine argument can only be specified"):
+ s.map(func, engine="something")
+
+
+def test_map_engine_not_executor():
+ s = Series([1, 2])
+
+ with pytest.raises(ValueError, match="Not a valid engine: 'something'"):
+ s.map(lambda x: x, engine="something")
+
+
+@td.skip_if_no("pyarrow")
+@pytest.mark.parametrize("as_td", [True, False])
+def test_map_pyarrow_timestamp(as_td):
+ # GH#61231
+ dti = date_range("2018-01-01 00:00:00", "2018-01-07 00:00:00")
+ ser = Series(dti, dtype="timestamp[ns][pyarrow]", name="a")
+ if as_td:
+ # duration dtype
+ ser = ser - ser[0]
+
+ mapper = {date: i for i, date in enumerate(ser)}
+
+ res_series = ser.map(mapper)
+ expected = Series(range(len(ser)), name="a", dtype="int64")
+ tm.assert_series_equal(res_series, expected)
+
+ res_index = Index(ser).map(mapper)
+ # For now (as of 2025-09-06) at least, we do inference on Index.map that
+ # we don't for Series.map
+ expected_index = Index(expected).astype("int64[pyarrow]")
+ tm.assert_index_equal(res_index, expected_index)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_matmul.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_matmul.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ca3ad3f7031e20b8d6cec36caadbcefef8c4196
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_matmul.py
@@ -0,0 +1,82 @@
+import operator
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestMatmul:
+ def test_matmul(self):
+ # matmul test is for GH#10259
+ a = Series(
+ np.random.default_rng(2).standard_normal(4), index=["p", "q", "r", "s"]
+ )
+ b = DataFrame(
+ np.random.default_rng(2).standard_normal((3, 4)),
+ index=["1", "2", "3"],
+ columns=["p", "q", "r", "s"],
+ ).T
+
+ # Series @ DataFrame -> Series
+ result = operator.matmul(a, b)
+ expected = Series(np.dot(a.values, b.values), index=["1", "2", "3"])
+ tm.assert_series_equal(result, expected)
+
+ # DataFrame @ Series -> Series
+ result = operator.matmul(b.T, a)
+ expected = Series(np.dot(b.T.values, a.T.values), index=["1", "2", "3"])
+ tm.assert_series_equal(result, expected)
+
+ # Series @ Series -> scalar
+ result = operator.matmul(a, a)
+ expected = np.dot(a.values, a.values)
+ tm.assert_almost_equal(result, expected)
+
+ # GH#21530
+ # vector (1D np.array) @ Series (__rmatmul__)
+ result = operator.matmul(a.values, a)
+ expected = np.dot(a.values, a.values)
+ tm.assert_almost_equal(result, expected)
+
+ # GH#21530
+ # vector (1D list) @ Series (__rmatmul__)
+ result = operator.matmul(a.values.tolist(), a)
+ expected = np.dot(a.values, a.values)
+ tm.assert_almost_equal(result, expected)
+
+ # GH#21530
+ # matrix (2D np.array) @ Series (__rmatmul__)
+ result = operator.matmul(b.T.values, a)
+ expected = np.dot(b.T.values, a.values)
+ tm.assert_almost_equal(result, expected)
+
+ # GH#21530
+ # matrix (2D nested lists) @ Series (__rmatmul__)
+ result = operator.matmul(b.T.values.tolist(), a)
+ expected = np.dot(b.T.values, a.values)
+ tm.assert_almost_equal(result, expected)
+
+ # mixed dtype DataFrame @ Series
+ a["p"] = int(a.p)
+ result = operator.matmul(b.T, a)
+ expected = Series(np.dot(b.T.values, a.T.values), index=["1", "2", "3"])
+ tm.assert_series_equal(result, expected)
+
+ # different dtypes DataFrame @ Series
+ a = a.astype(int)
+ result = operator.matmul(b.T, a)
+ expected = Series(np.dot(b.T.values, a.T.values), index=["1", "2", "3"])
+ tm.assert_series_equal(result, expected)
+
+ msg = r"Dot product shape mismatch, \(4,\) vs \(3,\)"
+ # exception raised is of type Exception
+ with pytest.raises(Exception, match=msg):
+ a.dot(a.values[:3])
+ msg = "matrices are not aligned"
+ with pytest.raises(ValueError, match=msg):
+ a.dot(b.T)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_nlargest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_nlargest.py
new file mode 100644
index 0000000000000000000000000000000000000000..67ba1d7ca51b70325610aadfb48f60c6a3d68741
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_nlargest.py
@@ -0,0 +1,202 @@
+"""
+Note: for naming purposes, most tests are title with as e.g. "test_nlargest_foo"
+but are implicitly also testing nsmallest_foo.
+"""
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import Series
+import pandas._testing as tm
+
+
+def assert_check_nselect_boundary(vals, dtype, method):
+ # helper function for 'test_boundary_{dtype}' tests
+ ser = Series(vals, dtype=dtype)
+ result = getattr(ser, method)(3)
+ expected_idxr = range(3) if method == "nsmallest" else range(3, 0, -1)
+ expected = ser.loc[expected_idxr]
+ tm.assert_series_equal(result, expected)
+
+
+class TestSeriesNLargestNSmallest:
+ @pytest.mark.parametrize(
+ "r",
+ [
+ Series([3.0, 2, 1, 2, "5"], dtype="object"),
+ Series([3.0, 2, 1, 2, 5], dtype="object"),
+ # not supported on some archs
+ # Series([3., 2, 1, 2, 5], dtype='complex256'),
+ Series([3.0, 2, 1, 2, 5], dtype="complex128"),
+ Series(list("abcde")),
+ Series(list("abcde"), dtype="category"),
+ ],
+ )
+ @pytest.mark.parametrize("method", ["nlargest", "nsmallest"])
+ @pytest.mark.parametrize("arg", [2, 5, 0, -1])
+ def test_nlargest_error(self, r, method, arg):
+ dt = r.dtype
+ msg = f"Cannot use method 'n(largest|smallest)' with dtype {dt}"
+ with pytest.raises(TypeError, match=msg):
+ getattr(r, method)(arg)
+
+ @pytest.mark.parametrize(
+ "data",
+ [
+ pd.to_datetime(["2003", "2002", "2001", "2002", "2005"]),
+ pd.to_datetime(["2003", "2002", "2001", "2002", "2005"], utc=True),
+ pd.to_timedelta(["3D", "2D", "1D", "2D", "5D"]),
+ np.array([3, 2, 1, 2, 5], dtype="int8"),
+ np.array([3, 2, 1, 2, 5], dtype="int16"),
+ np.array([3, 2, 1, 2, 5], dtype="int32"),
+ np.array([3, 2, 1, 2, 5], dtype="int64"),
+ np.array([3, 2, 1, 2, 5], dtype="uint8"),
+ np.array([3, 2, 1, 2, 5], dtype="uint16"),
+ np.array([3, 2, 1, 2, 5], dtype="uint32"),
+ np.array([3, 2, 1, 2, 5], dtype="uint64"),
+ np.array([3, 2, 1, 2, 5], dtype="float32"),
+ np.array([3, 2, 1, 2, 5], dtype="float64"),
+ ],
+ )
+ def test_nsmallest_nlargest(self, data):
+ # float, int, datetime64 (use i8), timedelts64 (same),
+ # object that are numbers, object that are strings
+ ser = Series(data)
+
+ tm.assert_series_equal(ser.nsmallest(2), ser.iloc[[2, 1]])
+ tm.assert_series_equal(ser.nsmallest(2, keep="last"), ser.iloc[[2, 3]])
+
+ empty = ser.iloc[0:0]
+ tm.assert_series_equal(ser.nsmallest(0), empty)
+ tm.assert_series_equal(ser.nsmallest(-1), empty)
+ tm.assert_series_equal(ser.nlargest(0), empty)
+ tm.assert_series_equal(ser.nlargest(-1), empty)
+
+ tm.assert_series_equal(ser.nsmallest(len(ser)), ser.sort_values())
+ tm.assert_series_equal(ser.nsmallest(len(ser) + 1), ser.sort_values())
+ tm.assert_series_equal(ser.nlargest(len(ser)), ser.iloc[[4, 0, 1, 3, 2]])
+ tm.assert_series_equal(ser.nlargest(len(ser) + 1), ser.iloc[[4, 0, 1, 3, 2]])
+
+ def test_nlargest_misc(self):
+ ser = Series([3.0, np.nan, 1, 2, 5])
+ result = ser.nlargest()
+ expected = ser.iloc[[4, 0, 3, 2, 1]]
+ tm.assert_series_equal(result, expected)
+ result = ser.nsmallest()
+ expected = ser.iloc[[2, 3, 0, 4, 1]]
+ tm.assert_series_equal(result, expected)
+
+ msg = 'keep must be either "first", "last"'
+ with pytest.raises(ValueError, match=msg):
+ ser.nsmallest(keep="invalid")
+ with pytest.raises(ValueError, match=msg):
+ ser.nlargest(keep="invalid")
+
+ # GH#15297
+ ser = Series([1] * 5, index=[1, 2, 3, 4, 5])
+ expected_first = Series([1] * 3, index=[1, 2, 3])
+ expected_last = Series([1] * 3, index=[5, 4, 3])
+
+ result = ser.nsmallest(3)
+ tm.assert_series_equal(result, expected_first)
+
+ result = ser.nsmallest(3, keep="last")
+ tm.assert_series_equal(result, expected_last)
+
+ result = ser.nlargest(3)
+ tm.assert_series_equal(result, expected_first)
+
+ result = ser.nlargest(3, keep="last")
+ tm.assert_series_equal(result, expected_last)
+
+ @pytest.mark.parametrize("n", range(1, 5))
+ def test_nlargest_n(self, n):
+ # GH 13412
+ ser = Series([1, 4, 3, 2], index=[0, 0, 1, 1])
+ result = ser.nlargest(n)
+ expected = ser.sort_values(ascending=False).head(n)
+ tm.assert_series_equal(result, expected)
+
+ result = ser.nsmallest(n)
+ expected = ser.sort_values().head(n)
+ tm.assert_series_equal(result, expected)
+
+ def test_nlargest_boundary_integer(self, nselect_method, any_int_numpy_dtype):
+ # GH#21426
+ dtype_info = np.iinfo(any_int_numpy_dtype)
+ min_val, max_val = dtype_info.min, dtype_info.max
+ vals = [min_val, min_val + 1, max_val - 1, max_val]
+ assert_check_nselect_boundary(vals, any_int_numpy_dtype, nselect_method)
+
+ def test_nlargest_boundary_float(self, nselect_method, float_numpy_dtype):
+ # GH#21426
+ dtype_info = np.finfo(float_numpy_dtype)
+ min_val, max_val = dtype_info.min, dtype_info.max
+ min_2nd, max_2nd = np.nextafter([min_val, max_val], 0, dtype=float_numpy_dtype)
+ vals = [min_val, min_2nd, max_2nd, max_val]
+ assert_check_nselect_boundary(vals, float_numpy_dtype, nselect_method)
+
+ @pytest.mark.parametrize("dtype", ["datetime64[ns]", "timedelta64[ns]"])
+ def test_nlargest_boundary_datetimelike(self, nselect_method, dtype):
+ # GH#21426
+ # use int64 bounds and +1 to min_val since true minimum is NaT
+ # (include min_val/NaT at end to maintain same expected_idxr)
+ dtype_info = np.iinfo("int64")
+ min_val, max_val = dtype_info.min, dtype_info.max
+ vals = [min_val + 1, min_val + 2, max_val - 1, max_val, min_val]
+ assert_check_nselect_boundary(vals, dtype, nselect_method)
+
+ def test_nlargest_duplicate_keep_all_ties(self):
+ # see GH#16818
+ ser = Series([10, 9, 8, 7, 7, 7, 7, 6])
+ result = ser.nlargest(4, keep="all")
+ expected = Series([10, 9, 8, 7, 7, 7, 7])
+ tm.assert_series_equal(result, expected)
+
+ result = ser.nsmallest(2, keep="all")
+ expected = Series([6, 7, 7, 7, 7], index=[7, 3, 4, 5, 6])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "data,expected", [([True, False], [True]), ([True, False, True, True], [True])]
+ )
+ def test_nlargest_boolean(self, data, expected):
+ # GH#26154 : ensure True > False
+ ser = Series(data)
+ result = ser.nlargest(1)
+ expected = Series(expected)
+ tm.assert_series_equal(result, expected)
+
+ def test_nlargest_nullable(self, any_numeric_ea_dtype):
+ # GH#42816
+ dtype = any_numeric_ea_dtype
+ if dtype.startswith("UInt"):
+ # Can't cast from negative float to uint on some platforms
+ arr = np.random.default_rng(2).integers(1, 10, 10)
+ else:
+ arr = np.random.default_rng(2).standard_normal(10)
+ arr = arr.astype(dtype.lower(), copy=False)
+
+ ser = Series(arr, dtype=dtype, copy=True)
+ ser[1] = pd.NA
+ result = ser.nlargest(5)
+
+ expected = (
+ Series(np.delete(arr, 1), index=ser.index.delete(1))
+ .nlargest(5)
+ .astype(dtype)
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_nsmallest_nan_when_keep_is_all(self):
+ # GH#46589
+ s = Series([1, 2, 3, 3, 3, None])
+ result = s.nsmallest(3, keep="all")
+ expected = Series([1.0, 2.0, 3.0, 3.0, 3.0])
+ tm.assert_series_equal(result, expected)
+
+ s = Series([1, 2, None, None, None])
+ result = s.nsmallest(3, keep="all")
+ expected = Series([1, 2, None, None, None])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_nunique.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_nunique.py
new file mode 100644
index 0000000000000000000000000000000000000000..826132eb28162603d03635add59c3ea3da569256
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_nunique.py
@@ -0,0 +1,24 @@
+import numpy as np
+
+from pandas import (
+ Categorical,
+ Series,
+)
+
+
+def test_nunique():
+ # basics.rst doc example
+ series = Series(np.random.default_rng(2).standard_normal(500))
+ series[20:500] = np.nan
+ series[10:20] = 5000
+ result = series.nunique()
+ assert result == 11
+
+
+def test_nunique_categorical():
+ # GH#18051
+ ser = Series(Categorical([]))
+ assert ser.nunique() == 0
+
+ ser = Series(Categorical([np.nan]))
+ assert ser.nunique() == 0
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_pct_change.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_pct_change.py
new file mode 100644
index 0000000000000000000000000000000000000000..6279cf64818b865e7d5a640354497ad45e0b8f1b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_pct_change.py
@@ -0,0 +1,76 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestSeriesPctChange:
+ def test_pct_change(self, datetime_series):
+ rs = datetime_series.pct_change()
+ tm.assert_series_equal(rs, datetime_series / datetime_series.shift(1) - 1)
+
+ rs = datetime_series.pct_change(2)
+ filled = datetime_series.ffill()
+ tm.assert_series_equal(rs, filled / filled.shift(2) - 1)
+
+ rs = datetime_series.pct_change(freq="5D")
+ filled = datetime_series.ffill()
+ tm.assert_series_equal(
+ rs, (filled / filled.shift(freq="5D") - 1).reindex_like(filled)
+ )
+
+ def test_pct_change_with_duplicate_axis(self):
+ # GH#28664
+ common_idx = date_range("2019-11-14", periods=5, freq="D")
+ result = Series(range(5), common_idx).pct_change(freq="B")
+
+ # the reason that the expected should be like this is documented at PR 28681
+ expected = Series([np.nan, np.inf, np.nan, np.nan, 3.0], common_idx)
+
+ tm.assert_series_equal(result, expected)
+
+ def test_pct_change_shift_over_nas(self):
+ s = Series([1.0, 1.5, np.nan, 2.5, 3.0])
+ chg = s.pct_change()
+ expected = Series([np.nan, 0.5, np.nan, np.nan, 0.2])
+ tm.assert_series_equal(chg, expected)
+
+ @pytest.mark.parametrize("freq, periods", [("5B", 5), ("3B", 3), ("14B", 14)])
+ def test_pct_change_periods_freq(self, freq, periods, datetime_series):
+ # GH#7292
+ rs_freq = datetime_series.pct_change(freq=freq)
+ rs_periods = datetime_series.pct_change(periods)
+ tm.assert_series_equal(rs_freq, rs_periods)
+
+ empty_ts = Series(index=datetime_series.index, dtype=object)
+ rs_freq = empty_ts.pct_change(freq=freq)
+ rs_periods = empty_ts.pct_change(periods)
+ tm.assert_series_equal(rs_freq, rs_periods)
+
+
+def test_pct_change_with_duplicated_indices():
+ # GH30463
+ s = Series([np.nan, 1, 2, 3, 9, 18], index=["a", "b"] * 3)
+ result = s.pct_change()
+ expected = Series([np.nan, np.nan, 1.0, 0.5, 2.0, 1.0], index=["a", "b"] * 3)
+ tm.assert_series_equal(result, expected)
+
+
+def test_pct_change_no_warning_na_beginning():
+ # GH#54981
+ ser = Series([None, None, 1, 2, 3])
+ result = ser.pct_change()
+ expected = Series([np.nan, np.nan, np.nan, 1, 0.5])
+ tm.assert_series_equal(result, expected)
+
+
+def test_pct_change_empty():
+ # GH 57056
+ ser = Series([], dtype="float64")
+ expected = ser.copy()
+ result = ser.pct_change(periods=0)
+ tm.assert_series_equal(expected, result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_pop.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_pop.py
new file mode 100644
index 0000000000000000000000000000000000000000..7453f98ab3735e924dd7601622d23b4bafdd2176
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_pop.py
@@ -0,0 +1,13 @@
+from pandas import Series
+import pandas._testing as tm
+
+
+def test_pop():
+ # GH#6600
+ ser = Series([0, 4, 0], index=["A", "B", "C"], name=4)
+
+ result = ser.pop("B")
+ assert result == 4
+
+ expected = Series([0, 0], index=["A", "C"], name=4)
+ tm.assert_series_equal(ser, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_quantile.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_quantile.py
new file mode 100644
index 0000000000000000000000000000000000000000..294437e557fdc3685aeaa240cdf91bc89ee6187c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_quantile.py
@@ -0,0 +1,254 @@
+import numpy as np
+import pytest
+
+from pandas.core.dtypes.common import is_integer
+
+import pandas as pd
+from pandas import (
+ Index,
+ Series,
+)
+import pandas._testing as tm
+from pandas.core.indexes.datetimes import Timestamp
+
+
+class TestSeriesQuantile:
+ def test_quantile(self, datetime_series):
+ q = datetime_series.quantile(0.1)
+ assert q == np.percentile(datetime_series.dropna(), 10)
+
+ q = datetime_series.quantile(0.9)
+ assert q == np.percentile(datetime_series.dropna(), 90)
+
+ # object dtype
+ q = Series(datetime_series, dtype=object).quantile(0.9)
+ assert q == np.percentile(datetime_series.dropna(), 90)
+
+ # datetime64[ns] dtype
+ dts = datetime_series.index.to_series()
+ q = dts.quantile(0.2)
+ assert q == Timestamp("2000-01-10 19:12:00")
+
+ # timedelta64[ns] dtype
+ tds = dts.diff()
+ q = tds.quantile(0.25)
+ assert q == pd.to_timedelta("24:00:00")
+
+ # GH7661
+ result = Series([np.timedelta64("NaT")]).sum()
+ assert result == pd.Timedelta(0)
+
+ msg = "percentiles should all be in the interval \\[0, 1\\]"
+ for invalid in [-1, 2, [0.5, -1], [0.5, 2]]:
+ with pytest.raises(ValueError, match=msg):
+ datetime_series.quantile(invalid)
+
+ s = Series(np.random.default_rng(2).standard_normal(100))
+ percentile_array = [-0.5, 0.25, 1.5]
+ with pytest.raises(ValueError, match=msg):
+ s.quantile(percentile_array)
+
+ def test_quantile_multi(self, datetime_series, unit):
+ datetime_series.index = datetime_series.index.as_unit(unit)
+ qs = [0.1, 0.9]
+ result = datetime_series.quantile(qs)
+ expected = Series(
+ [
+ np.percentile(datetime_series.dropna(), 10),
+ np.percentile(datetime_series.dropna(), 90),
+ ],
+ index=qs,
+ name=datetime_series.name,
+ )
+ tm.assert_series_equal(result, expected)
+
+ dts = datetime_series.index.to_series()
+ dts.name = "xxx"
+ result = dts.quantile((0.2, 0.2))
+ expected = Series(
+ [Timestamp("2000-01-10 19:12:00"), Timestamp("2000-01-10 19:12:00")],
+ index=[0.2, 0.2],
+ name="xxx",
+ dtype=f"M8[{unit}]",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = datetime_series.quantile([])
+ expected = Series(
+ [], name=datetime_series.name, index=Index([], dtype=float), dtype="float64"
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_quantile_interpolation(self, datetime_series):
+ # see gh-10174
+
+ # interpolation = linear (default case)
+ q = datetime_series.quantile(0.1, interpolation="linear")
+ assert q == np.percentile(datetime_series.dropna(), 10)
+ q1 = datetime_series.quantile(0.1)
+ assert q1 == np.percentile(datetime_series.dropna(), 10)
+
+ # test with and without interpolation keyword
+ assert q == q1
+
+ def test_quantile_interpolation_dtype(self):
+ # GH #10174
+
+ # interpolation = linear (default case)
+ q = Series([1, 3, 4]).quantile(0.5, interpolation="lower")
+ assert q == np.percentile(np.array([1, 3, 4]), 50)
+ assert is_integer(q)
+
+ q = Series([1, 3, 4]).quantile(0.5, interpolation="higher")
+ assert q == np.percentile(np.array([1, 3, 4]), 50)
+ assert is_integer(q)
+
+ def test_quantile_nan(self):
+ # GH 13098
+ ser = Series([1, 2, 3, 4, np.nan])
+ result = ser.quantile(0.5)
+ expected = 2.5
+ assert result == expected
+
+ # all nan/empty
+ s1 = Series([], dtype=object)
+ cases = [s1, Series([np.nan, np.nan])]
+
+ for ser in cases:
+ res = ser.quantile(0.5)
+ assert np.isnan(res)
+
+ res = ser.quantile([0.5])
+ tm.assert_series_equal(res, Series([np.nan], index=[0.5]))
+
+ res = ser.quantile([0.2, 0.3])
+ tm.assert_series_equal(res, Series([np.nan, np.nan], index=[0.2, 0.3]))
+
+ @pytest.mark.parametrize(
+ "case",
+ [
+ [
+ Timestamp("2011-01-01"),
+ Timestamp("2011-01-02"),
+ Timestamp("2011-01-03"),
+ ],
+ [
+ Timestamp("2011-01-01", tz="US/Eastern"),
+ Timestamp("2011-01-02", tz="US/Eastern"),
+ Timestamp("2011-01-03", tz="US/Eastern"),
+ ],
+ [pd.Timedelta("1 days"), pd.Timedelta("2 days"), pd.Timedelta("3 days")],
+ # NaT
+ [
+ Timestamp("2011-01-01"),
+ Timestamp("2011-01-02"),
+ Timestamp("2011-01-03"),
+ pd.NaT,
+ ],
+ [
+ Timestamp("2011-01-01", tz="US/Eastern"),
+ Timestamp("2011-01-02", tz="US/Eastern"),
+ Timestamp("2011-01-03", tz="US/Eastern"),
+ pd.NaT,
+ ],
+ [
+ pd.Timedelta("1 days"),
+ pd.Timedelta("2 days"),
+ pd.Timedelta("3 days"),
+ pd.NaT,
+ ],
+ ],
+ )
+ def test_quantile_box(self, case):
+ ser = Series(case, name="XXX")
+ res = ser.quantile(0.5)
+ assert res == case[1]
+
+ res = ser.quantile([0.5])
+ exp = Series([case[1]], index=[0.5], name="XXX")
+ tm.assert_series_equal(res, exp)
+
+ def test_datetime_timedelta_quantiles(self):
+ # covers #9694
+ assert pd.isna(Series([], dtype="M8[ns]").quantile(0.5))
+ assert pd.isna(Series([], dtype="m8[ns]").quantile(0.5))
+
+ def test_quantile_nat(self):
+ res = Series([pd.NaT, pd.NaT]).quantile(0.5)
+ assert res is pd.NaT
+
+ res = Series([pd.NaT, pd.NaT]).quantile([0.5])
+ tm.assert_series_equal(res, Series([pd.NaT], index=[0.5]))
+
+ @pytest.mark.parametrize(
+ "values, dtype",
+ [([0, 0, 0, 1, 2, 3], "Sparse[int]"), ([0.0, None, 1.0, 2.0], "Sparse[float]")],
+ )
+ def test_quantile_sparse(self, values, dtype):
+ ser = Series(values, dtype=dtype)
+ result = ser.quantile([0.5])
+ expected = Series(np.asarray(ser)).quantile([0.5]).astype("Sparse[float]")
+ tm.assert_series_equal(result, expected)
+
+ def test_quantile_empty_float64(self):
+ # floats
+ ser = Series([], dtype="float64")
+
+ res = ser.quantile(0.5)
+ assert np.isnan(res)
+
+ res = ser.quantile([0.5])
+ exp = Series([np.nan], index=[0.5])
+ tm.assert_series_equal(res, exp)
+
+ def test_quantile_empty_int64(self):
+ # int
+ ser = Series([], dtype="int64")
+
+ res = ser.quantile(0.5)
+ assert np.isnan(res)
+
+ res = ser.quantile([0.5])
+ exp = Series([np.nan], index=[0.5])
+ tm.assert_series_equal(res, exp)
+
+ def test_quantile_empty_dt64(self):
+ # datetime
+ ser = Series([], dtype="datetime64[ns]")
+
+ res = ser.quantile(0.5)
+ assert res is pd.NaT
+
+ res = ser.quantile([0.5])
+ exp = Series([pd.NaT], index=[0.5], dtype=ser.dtype)
+ tm.assert_series_equal(res, exp)
+
+ @pytest.mark.parametrize("dtype", [int, float, "Int64"])
+ def test_quantile_dtypes(self, dtype):
+ result = Series([1, 2, 3], dtype=dtype).quantile(np.arange(0, 1, 0.25))
+ expected = Series(np.arange(1, 3, 0.5), index=np.arange(0, 1, 0.25))
+ if dtype == "Int64":
+ expected = expected.astype("Float64")
+ tm.assert_series_equal(result, expected)
+
+ def test_quantile_all_na(self, any_int_ea_dtype):
+ # GH#50681
+ ser = Series([pd.NA, pd.NA], dtype=any_int_ea_dtype)
+ with tm.assert_produces_warning(None):
+ result = ser.quantile([0.1, 0.5])
+ expected = Series([pd.NA, pd.NA], dtype=any_int_ea_dtype, index=[0.1, 0.5])
+ tm.assert_series_equal(result, expected)
+
+ def test_quantile_dtype_size(self, any_int_ea_dtype):
+ # GH#50681
+ ser = Series([pd.NA, pd.NA, 1], dtype=any_int_ea_dtype)
+ result = ser.quantile([0.1, 0.5])
+ expected = Series([1, 1], dtype=any_int_ea_dtype, index=[0.1, 0.5])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("typ", ["datetime64", "timedelta64"])
+def test_quantile_empty_datetimelike(typ, unit):
+ ser = Series([], dtype=f"{typ}[{unit}]")
+ result = ser.quantile()
+ assert result is pd.NaT
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_rank.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_rank.py
new file mode 100644
index 0000000000000000000000000000000000000000..0102bc811de9b53e637bb40cd1589244e3fac9ca
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_rank.py
@@ -0,0 +1,625 @@
+from itertools import chain
+import operator
+
+import numpy as np
+import pytest
+
+from pandas._libs.algos import (
+ Infinity,
+ NegInfinity,
+)
+import pandas.util._test_decorators as td
+
+from pandas import (
+ NA,
+ NaT,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.api.types import CategoricalDtype
+
+
+@pytest.fixture
+def ser():
+ return Series([1, 3, 4, 2, np.nan, 2, 1, 5, np.nan, 3])
+
+
+@pytest.fixture(
+ params=[
+ ["average", np.array([1.5, 5.5, 7.0, 3.5, np.nan, 3.5, 1.5, 8.0, np.nan, 5.5])],
+ ["min", np.array([1, 5, 7, 3, np.nan, 3, 1, 8, np.nan, 5])],
+ ["max", np.array([2, 6, 7, 4, np.nan, 4, 2, 8, np.nan, 6])],
+ ["first", np.array([1, 5, 7, 3, np.nan, 4, 2, 8, np.nan, 6])],
+ ["dense", np.array([1, 3, 4, 2, np.nan, 2, 1, 5, np.nan, 3])],
+ ],
+ ids=lambda x: x[0],
+)
+def results(request):
+ return request.param
+
+
+@pytest.fixture(
+ params=[
+ "object",
+ "float64",
+ "int64",
+ "Float64",
+ "Int64",
+ pytest.param("float64[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ pytest.param("int64[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ pytest.param("string[pyarrow]", marks=td.skip_if_no("pyarrow")),
+ "string[python]",
+ "str",
+ ]
+)
+def dtype(request):
+ return request.param
+
+
+def expected_dtype(dtype, method, pct=False):
+ exp_dtype = "float64"
+ # elif dtype in ["Int64", "Float64", "string[pyarrow]", "string[python]"]:
+ if dtype in ["string[pyarrow]"]:
+ exp_dtype = "Float64"
+ elif dtype in ["float64[pyarrow]", "int64[pyarrow]"]:
+ if method == "average" or pct:
+ exp_dtype = "double[pyarrow]"
+ else:
+ exp_dtype = "uint64[pyarrow]"
+ elif dtype in ["Float64", "Int64"]:
+ if method == "average" or pct:
+ exp_dtype = "Float64"
+ else:
+ exp_dtype = "UInt64"
+
+ return exp_dtype
+
+
+class TestSeriesRank:
+ def test_rank(self, datetime_series):
+ sp_stats = pytest.importorskip("scipy.stats")
+
+ datetime_series[::2] = np.nan
+ datetime_series[:10:3] = 4.0
+
+ ranks = datetime_series.rank()
+ oranks = datetime_series.astype("O").rank()
+
+ tm.assert_series_equal(ranks, oranks)
+
+ mask = np.isnan(datetime_series)
+ filled = datetime_series.fillna(np.inf)
+
+ # rankdata returns an ndarray
+ exp = Series(sp_stats.rankdata(filled), index=filled.index, name="ts")
+ exp[mask] = np.nan
+
+ tm.assert_series_equal(ranks, exp)
+
+ iseries = Series(np.arange(5).repeat(2))
+
+ iranks = iseries.rank()
+ exp = iseries.astype(float).rank()
+ tm.assert_series_equal(iranks, exp)
+ iseries = Series(np.arange(5)) + 1.0
+ exp = iseries / 5.0
+ iranks = iseries.rank(pct=True)
+
+ tm.assert_series_equal(iranks, exp)
+
+ iseries = Series(np.repeat(1, 100))
+ exp = Series(np.repeat(0.505, 100))
+ iranks = iseries.rank(pct=True)
+ tm.assert_series_equal(iranks, exp)
+
+ # Explicit cast to float to avoid implicit cast when setting nan
+ iseries = iseries.astype("float")
+ iseries[1] = np.nan
+ exp = Series(np.repeat(50.0 / 99.0, 100))
+ exp[1] = np.nan
+ iranks = iseries.rank(pct=True)
+ tm.assert_series_equal(iranks, exp)
+
+ iseries = Series(np.arange(5)) + 1.0
+ iseries[4] = np.nan
+ exp = iseries / 4.0
+ iranks = iseries.rank(pct=True)
+ tm.assert_series_equal(iranks, exp)
+
+ iseries = Series(np.repeat(np.nan, 100))
+ exp = iseries
+ iranks = iseries.rank(pct=True)
+ tm.assert_series_equal(iranks, exp)
+
+ # Explicit cast to float to avoid implicit cast when setting nan
+ iseries = Series(np.arange(5), dtype="float") + 1
+ iseries[4] = np.nan
+ exp = iseries / 4.0
+ iranks = iseries.rank(pct=True)
+ tm.assert_series_equal(iranks, exp)
+
+ rng = date_range("1/1/1990", periods=5)
+ # Explicit cast to float to avoid implicit cast when setting nan
+ iseries = Series(np.arange(5), rng, dtype="float") + 1
+ iseries.iloc[4] = np.nan
+ exp = iseries / 4.0
+ iranks = iseries.rank(pct=True)
+ tm.assert_series_equal(iranks, exp)
+
+ iseries = Series([1e-50, 1e-100, 1e-20, 1e-2, 1e-20 + 1e-30, 1e-1])
+ exp = Series([2, 1, 3, 5, 4, 6.0])
+ iranks = iseries.rank()
+ tm.assert_series_equal(iranks, exp)
+
+ # GH 5968
+ iseries = Series(["3 day", "1 day 10m", "-2 day", NaT], dtype="m8[ns]")
+ exp = Series([3, 2, 1, np.nan])
+ iranks = iseries.rank()
+ tm.assert_series_equal(iranks, exp)
+
+ values = np.array(
+ [-50, -1, -1e-20, -1e-25, -1e-50, 0, 1e-40, 1e-20, 1e-10, 2, 40],
+ dtype="float64",
+ )
+ random_order = np.random.default_rng(2).permutation(len(values))
+ iseries = Series(values[random_order])
+ exp = Series(random_order + 1.0, dtype="float64")
+ iranks = iseries.rank()
+ tm.assert_series_equal(iranks, exp)
+
+ def test_rank_categorical(self):
+ # GH issue #15420 rank incorrectly orders ordered categories
+
+ # Test ascending/descending ranking for ordered categoricals
+ exp = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
+ exp_desc = Series([6.0, 5.0, 4.0, 3.0, 2.0, 1.0])
+ ordered = Series(
+ ["first", "second", "third", "fourth", "fifth", "sixth"]
+ ).astype(
+ CategoricalDtype(
+ categories=["first", "second", "third", "fourth", "fifth", "sixth"],
+ ordered=True,
+ )
+ )
+ tm.assert_series_equal(ordered.rank(), exp)
+ tm.assert_series_equal(ordered.rank(ascending=False), exp_desc)
+
+ # Unordered categoricals should be ranked as objects
+ unordered = Series(
+ ["first", "second", "third", "fourth", "fifth", "sixth"]
+ ).astype(
+ CategoricalDtype(
+ categories=["first", "second", "third", "fourth", "fifth", "sixth"],
+ ordered=False,
+ )
+ )
+ exp_unordered = Series([2.0, 4.0, 6.0, 3.0, 1.0, 5.0])
+ res = unordered.rank()
+ tm.assert_series_equal(res, exp_unordered)
+
+ unordered1 = Series([1, 2, 3, 4, 5, 6]).astype(
+ CategoricalDtype([1, 2, 3, 4, 5, 6], False)
+ )
+ exp_unordered1 = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
+ res1 = unordered1.rank()
+ tm.assert_series_equal(res1, exp_unordered1)
+
+ # Test na_option for rank data
+ na_ser = Series(
+ ["first", "second", "third", "fourth", "fifth", "sixth", np.nan]
+ ).astype(
+ CategoricalDtype(
+ ["first", "second", "third", "fourth", "fifth", "sixth", "seventh"],
+ True,
+ )
+ )
+
+ exp_top = Series([2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 1.0])
+ exp_bot = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0])
+ exp_keep = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, np.nan])
+
+ tm.assert_series_equal(na_ser.rank(na_option="top"), exp_top)
+ tm.assert_series_equal(na_ser.rank(na_option="bottom"), exp_bot)
+ tm.assert_series_equal(na_ser.rank(na_option="keep"), exp_keep)
+
+ # Test na_option for rank data with ascending False
+ exp_top = Series([7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0])
+ exp_bot = Series([6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 7.0])
+ exp_keep = Series([6.0, 5.0, 4.0, 3.0, 2.0, 1.0, np.nan])
+
+ tm.assert_series_equal(na_ser.rank(na_option="top", ascending=False), exp_top)
+ tm.assert_series_equal(
+ na_ser.rank(na_option="bottom", ascending=False), exp_bot
+ )
+ tm.assert_series_equal(na_ser.rank(na_option="keep", ascending=False), exp_keep)
+
+ # Test invalid values for na_option
+ msg = "na_option must be one of 'keep', 'top', or 'bottom'"
+
+ with pytest.raises(ValueError, match=msg):
+ na_ser.rank(na_option="bad", ascending=False)
+
+ # invalid type
+ with pytest.raises(ValueError, match=msg):
+ na_ser.rank(na_option=True, ascending=False)
+
+ # Test with pct=True
+ na_ser = Series(["first", "second", "third", "fourth", np.nan]).astype(
+ CategoricalDtype(["first", "second", "third", "fourth"], True)
+ )
+ exp_top = Series([0.4, 0.6, 0.8, 1.0, 0.2])
+ exp_bot = Series([0.2, 0.4, 0.6, 0.8, 1.0])
+ exp_keep = Series([0.25, 0.5, 0.75, 1.0, np.nan])
+
+ tm.assert_series_equal(na_ser.rank(na_option="top", pct=True), exp_top)
+ tm.assert_series_equal(na_ser.rank(na_option="bottom", pct=True), exp_bot)
+ tm.assert_series_equal(na_ser.rank(na_option="keep", pct=True), exp_keep)
+
+ def test_rank_nullable_integer(self):
+ # GH 56976
+ exp = Series([None, 2, None, 3, 3, 2, 3, 1], dtype="Int64")
+ result = exp.rank(na_option="keep")
+
+ expected = Series([None, 2.5, None, 5.0, 5.0, 2.5, 5.0, 1.0], dtype="Float64")
+
+ tm.assert_series_equal(result, expected)
+
+ def test_rank_signature(self):
+ s = Series([0, 1])
+ s.rank(method="average")
+ msg = "No axis named average for object type Series"
+ with pytest.raises(ValueError, match=msg):
+ s.rank("average")
+
+ def test_rank_tie_methods(
+ self, ser, results, dtype, using_infer_string, using_nan_is_na
+ ):
+ method, exp = results
+ if (
+ dtype == "int64"
+ or (
+ # TODO: these can work but need to update ser construction.
+ dtype in ["int64[pyarrow]", "uint64[pyarrow]", "Int64"]
+ and not using_nan_is_na
+ )
+ or (not using_infer_string and dtype == "str")
+ ):
+ pytest.skip("int64/str does not support NaN")
+
+ ser = ser if dtype is None else ser.astype(dtype)
+ if dtype in ["float64[pyarrow]", "Float64"] and not using_nan_is_na:
+ # TODO: use ser.replace(np.nan, NA) once that works
+ ser[np.isnan(ser.to_numpy(dtype=np.float64, na_value=np.nan))] = NA
+ mask = np.isnan(exp)
+ exp = exp.astype(object)
+ exp[mask] = NA
+
+ result = ser.rank(method=method)
+
+ if dtype == "string[pyarrow]" and not using_nan_is_na:
+ mask = np.isnan(exp)
+ exp = exp.astype(object)
+ exp[mask] = NA
+
+ expected = Series(exp, dtype=expected_dtype(dtype, method))
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("na_option", ["top", "bottom", "keep"])
+ @pytest.mark.parametrize(
+ "dtype, na_value, pos_inf, neg_inf",
+ [
+ ("object", None, Infinity(), NegInfinity()),
+ ("float64", np.nan, np.inf, -np.inf),
+ ("Float64", NA, np.inf, -np.inf),
+ pytest.param(
+ "float64[pyarrow]",
+ NA,
+ np.inf,
+ -np.inf,
+ marks=td.skip_if_no("pyarrow"),
+ ),
+ ],
+ )
+ def test_rank_tie_methods_on_infs_nans(
+ self,
+ rank_method,
+ na_option,
+ ascending,
+ dtype,
+ na_value,
+ pos_inf,
+ neg_inf,
+ using_nan_is_na,
+ ):
+ pytest.importorskip("scipy")
+ if dtype == "float64[pyarrow]":
+ if rank_method == "average":
+ exp_dtype = "float64[pyarrow]"
+ else:
+ exp_dtype = "uint64[pyarrow]"
+ elif dtype == "Float64":
+ # GH#62043
+ if rank_method == "average":
+ exp_dtype = "Float64"
+ else:
+ exp_dtype = "UInt64"
+ else:
+ exp_dtype = "float64"
+
+ chunk = 3
+ in_arr = [neg_inf] * chunk + [na_value] * chunk + [pos_inf] * chunk
+ iseries = Series(in_arr, dtype=dtype)
+ exp_ranks = {
+ "average": ([2, 2, 2], [5, 5, 5], [8, 8, 8]),
+ "min": ([1, 1, 1], [4, 4, 4], [7, 7, 7]),
+ "max": ([3, 3, 3], [6, 6, 6], [9, 9, 9]),
+ "first": ([1, 2, 3], [4, 5, 6], [7, 8, 9]),
+ "dense": ([1, 1, 1], [2, 2, 2], [3, 3, 3]),
+ }
+ ranks = exp_ranks[rank_method]
+ if na_option == "top":
+ order = [ranks[1], ranks[0], ranks[2]]
+ elif na_option == "bottom":
+ order = [ranks[0], ranks[2], ranks[1]]
+ elif dtype in ("float64[pyarrow]", "Float64") and not using_nan_is_na:
+ order = [ranks[0], [NA] * chunk, ranks[1]]
+ else:
+ order = [ranks[0], [np.nan] * chunk, ranks[1]]
+ expected = order if ascending else order[::-1]
+ expected = list(chain.from_iterable(expected))
+ result = iseries.rank(
+ method=rank_method, na_option=na_option, ascending=ascending
+ )
+ exp_ser = Series(expected, dtype=exp_dtype)
+ tm.assert_series_equal(result, exp_ser)
+
+ def test_rank_desc_mix_nans_infs(self):
+ # GH 19538
+ # check descending ranking when mix nans and infs
+ iseries = Series([1, np.nan, np.inf, -np.inf, 25])
+ result = iseries.rank(ascending=False)
+ exp = Series([3, np.nan, 1, 4, 2], dtype="float64")
+ tm.assert_series_equal(result, exp)
+
+ @pytest.mark.parametrize(
+ "op, value",
+ [
+ [operator.add, 0],
+ [operator.add, 1e6],
+ [operator.mul, 1e-6],
+ ],
+ )
+ def test_rank_methods_series(self, rank_method, op, value):
+ sp_stats = pytest.importorskip("scipy.stats")
+
+ xs = np.random.default_rng(2).standard_normal(9)
+ xs = np.concatenate([xs[i:] for i in range(0, 9, 2)]) # add duplicates
+ np.random.default_rng(2).shuffle(xs)
+
+ index = [chr(ord("a") + i) for i in range(len(xs))]
+ vals = op(xs, value)
+ ts = Series(vals, index=index)
+ result = ts.rank(method=rank_method)
+ sprank = sp_stats.rankdata(
+ vals, rank_method if rank_method != "first" else "ordinal"
+ )
+ expected = Series(sprank, index=index).astype("float64")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "ser, exp",
+ [
+ ([1], [1]),
+ ([2], [1]),
+ ([0], [1]),
+ ([2, 2], [1, 1]),
+ ([1, 2, 3], [1, 2, 3]),
+ ([4, 2, 1], [3, 2, 1]),
+ ([1, 1, 5, 5, 3], [1, 1, 3, 3, 2]),
+ ([-5, -4, -3, -2, -1], [1, 2, 3, 4, 5]),
+ ],
+ )
+ def test_rank_dense_method(self, dtype, ser, exp):
+ if ser[0] < 0 and dtype.startswith("str"):
+ exp = exp[::-1]
+ s = Series(ser).astype(dtype)
+ result = s.rank(method="dense")
+ expected = Series(exp).astype(expected_dtype(dtype, "dense"))
+ tm.assert_series_equal(result, expected)
+
+ def test_rank_descending(
+ self, ser, results, dtype, using_infer_string, using_nan_is_na
+ ):
+ method, _ = results
+ if (
+ dtype == "int64"
+ or (dtype in ["int64[pyarrow]", "Int64", "Float64"] and not using_nan_is_na)
+ or (not using_infer_string and dtype == "str")
+ ):
+ s = ser.dropna().astype(dtype)
+ else:
+ s = ser.astype(dtype)
+
+ res = s.rank(ascending=False)
+ if dtype.startswith("str"):
+ expected = (s.astype("float64").max() - s.astype("float64")).rank()
+ else:
+ expected = (s.max() - s).rank()
+ if dtype == "string[pyarrow]" and not using_nan_is_na:
+ expected = expected.replace(np.nan, NA)
+ tm.assert_series_equal(res, expected.astype(expected_dtype(dtype, "average")))
+
+ if dtype.startswith("str"):
+ expected = (s.astype("float64").max() - s.astype("float64")).rank(
+ method=method
+ )
+ else:
+ expected = (s.max() - s).rank(method=method)
+ res2 = s.rank(method=method, ascending=False)
+ if dtype == "string[pyarrow]" and not using_nan_is_na:
+ expected = expected.replace(np.nan, NA)
+ tm.assert_series_equal(res2, expected.astype(expected_dtype(dtype, method)))
+
+ def test_rank_int(self, ser, results):
+ method, exp = results
+ s = ser.dropna().astype("i8")
+
+ result = s.rank(method=method)
+ expected = Series(exp).dropna()
+ expected.index = result.index
+ tm.assert_series_equal(result, expected)
+
+ def test_rank_object_bug(self):
+ # GH 13445
+
+ # smoke tests
+ Series([np.nan] * 32).astype(object).rank(ascending=True)
+ Series([np.nan] * 32).astype(object).rank(ascending=False)
+
+ def test_rank_modify_inplace(self):
+ # GH 18521
+ # Check rank does not mutate series
+ s = Series([Timestamp("2017-01-05 10:20:27.569000"), NaT])
+ expected = s.copy()
+
+ s.rank()
+ result = s
+ tm.assert_series_equal(result, expected)
+
+ def test_rank_ea_small_values(self):
+ # GH#52471
+ ser = Series(
+ [5.4954145e29, -9.791984e-21, 9.3715776e-26, NA, 1.8790257e-28],
+ dtype="Float64",
+ )
+ result = ser.rank(method="min")
+ expected = Series([4, 1, 3, NA, 2], dtype="UInt64")
+ tm.assert_series_equal(result, expected)
+
+
+# GH15630, pct should be on 100% basis when method='dense'
+
+
+@pytest.mark.parametrize(
+ "ser, exp",
+ [
+ ([1], [1.0]),
+ ([1, 2], [1.0 / 2, 2.0 / 2]),
+ ([2, 2], [1.0, 1.0]),
+ ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]),
+ ([1, 2, 2], [1.0 / 2, 2.0 / 2, 2.0 / 2]),
+ ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]),
+ ([1, 1, 5, 5, 3], [1.0 / 3, 1.0 / 3, 3.0 / 3, 3.0 / 3, 2.0 / 3]),
+ ([1, 1, 3, 3, 5, 5], [1.0 / 3, 1.0 / 3, 2.0 / 3, 2.0 / 3, 3.0 / 3, 3.0 / 3]),
+ ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]),
+ ],
+)
+def test_rank_dense_pct(dtype, ser, exp):
+ if ser[0] < 0 and dtype.startswith("str"):
+ exp = exp[::-1]
+ s = Series(ser).astype(dtype)
+ result = s.rank(method="dense", pct=True)
+ expected = Series(exp).astype(expected_dtype(dtype, "dense", pct=True))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ser, exp",
+ [
+ ([1], [1.0]),
+ ([1, 2], [1.0 / 2, 2.0 / 2]),
+ ([2, 2], [1.0 / 2, 1.0 / 2]),
+ ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]),
+ ([1, 2, 2], [1.0 / 3, 2.0 / 3, 2.0 / 3]),
+ ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]),
+ ([1, 1, 5, 5, 3], [1.0 / 5, 1.0 / 5, 4.0 / 5, 4.0 / 5, 3.0 / 5]),
+ ([1, 1, 3, 3, 5, 5], [1.0 / 6, 1.0 / 6, 3.0 / 6, 3.0 / 6, 5.0 / 6, 5.0 / 6]),
+ ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]),
+ ],
+)
+def test_rank_min_pct(dtype, ser, exp):
+ if ser[0] < 0 and dtype.startswith("str"):
+ exp = exp[::-1]
+ s = Series(ser).astype(dtype)
+ result = s.rank(method="min", pct=True)
+ expected = Series(exp).astype(expected_dtype(dtype, "min", pct=True))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ser, exp",
+ [
+ ([1], [1.0]),
+ ([1, 2], [1.0 / 2, 2.0 / 2]),
+ ([2, 2], [1.0, 1.0]),
+ ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]),
+ ([1, 2, 2], [1.0 / 3, 3.0 / 3, 3.0 / 3]),
+ ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]),
+ ([1, 1, 5, 5, 3], [2.0 / 5, 2.0 / 5, 5.0 / 5, 5.0 / 5, 3.0 / 5]),
+ ([1, 1, 3, 3, 5, 5], [2.0 / 6, 2.0 / 6, 4.0 / 6, 4.0 / 6, 6.0 / 6, 6.0 / 6]),
+ ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]),
+ ],
+)
+def test_rank_max_pct(dtype, ser, exp):
+ if ser[0] < 0 and dtype.startswith("str"):
+ exp = exp[::-1]
+ s = Series(ser).astype(dtype)
+ result = s.rank(method="max", pct=True)
+ expected = Series(exp).astype(expected_dtype(dtype, "max", pct=True))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ser, exp",
+ [
+ ([1], [1.0]),
+ ([1, 2], [1.0 / 2, 2.0 / 2]),
+ ([2, 2], [1.5 / 2, 1.5 / 2]),
+ ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]),
+ ([1, 2, 2], [1.0 / 3, 2.5 / 3, 2.5 / 3]),
+ ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]),
+ ([1, 1, 5, 5, 3], [1.5 / 5, 1.5 / 5, 4.5 / 5, 4.5 / 5, 3.0 / 5]),
+ ([1, 1, 3, 3, 5, 5], [1.5 / 6, 1.5 / 6, 3.5 / 6, 3.5 / 6, 5.5 / 6, 5.5 / 6]),
+ ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]),
+ ],
+)
+def test_rank_average_pct(dtype, ser, exp):
+ if ser[0] < 0 and dtype.startswith("str"):
+ exp = exp[::-1]
+ s = Series(ser).astype(dtype)
+ result = s.rank(method="average", pct=True)
+ expected = Series(exp).astype(expected_dtype(dtype, "average", pct=True))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "ser, exp",
+ [
+ ([1], [1.0]),
+ ([1, 2], [1.0 / 2, 2.0 / 2]),
+ ([2, 2], [1.0 / 2, 2.0 / 2.0]),
+ ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]),
+ ([1, 2, 2], [1.0 / 3, 2.0 / 3, 3.0 / 3]),
+ ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]),
+ ([1, 1, 5, 5, 3], [1.0 / 5, 2.0 / 5, 4.0 / 5, 5.0 / 5, 3.0 / 5]),
+ ([1, 1, 3, 3, 5, 5], [1.0 / 6, 2.0 / 6, 3.0 / 6, 4.0 / 6, 5.0 / 6, 6.0 / 6]),
+ ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]),
+ ],
+)
+def test_rank_first_pct(dtype, ser, exp):
+ if ser[0] < 0 and dtype.startswith("str"):
+ exp = exp[::-1]
+ s = Series(ser).astype(dtype)
+ result = s.rank(method="first", pct=True)
+ expected = Series(exp).astype(expected_dtype(dtype, "first", pct=True))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.single_cpu
+def test_pct_max_many_rows():
+ # GH 18271
+ s = Series(np.arange(2**24 + 1))
+ result = s.rank(pct=True).max()
+ assert result == 1
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_reindex.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_reindex.py
new file mode 100644
index 0000000000000000000000000000000000000000..a99ed68ec9edb2151e6d0eb6df690f87873202a2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_reindex.py
@@ -0,0 +1,474 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ NA,
+ Categorical,
+ Float64Dtype,
+ Index,
+ MultiIndex,
+ NaT,
+ Period,
+ PeriodIndex,
+ RangeIndex,
+ Series,
+ Timedelta,
+ Timestamp,
+ date_range,
+ isna,
+)
+import pandas._testing as tm
+
+
+def test_reindex(datetime_series, string_series):
+ identity = string_series.reindex(string_series.index)
+
+ assert tm.shares_memory(string_series.index, identity.index)
+
+ assert identity.index.is_(string_series.index)
+ assert identity.index.identical(string_series.index)
+
+ subIndex = string_series.index[10:20]
+ subSeries = string_series.reindex(subIndex)
+
+ for idx, val in subSeries.items():
+ assert val == string_series[idx]
+
+ subIndex2 = datetime_series.index[10:20]
+ subTS = datetime_series.reindex(subIndex2)
+
+ for idx, val in subTS.items():
+ assert val == datetime_series[idx]
+ stuffSeries = datetime_series.reindex(subIndex)
+
+ assert np.isnan(stuffSeries).all()
+
+ # This is extremely important for the Cython code to not screw up
+ nonContigIndex = datetime_series.index[::2]
+ subNonContig = datetime_series.reindex(nonContigIndex)
+ for idx, val in subNonContig.items():
+ assert val == datetime_series[idx]
+
+ # return a copy the same index here
+ result = datetime_series.reindex()
+ assert result is not datetime_series
+
+
+def test_reindex_nan():
+ ts = Series([2, 3, 5, 7], index=[1, 4, np.nan, 8])
+
+ i, j = [np.nan, 1, np.nan, 8, 4, np.nan], [2, 0, 2, 3, 1, 2]
+ tm.assert_series_equal(ts.reindex(i), ts.iloc[j])
+
+ ts.index = ts.index.astype("object")
+
+ # reindex coerces index.dtype to float, loc/iloc doesn't
+ tm.assert_series_equal(ts.reindex(i), ts.iloc[j], check_index_type=False)
+
+
+def test_reindex_series_add_nat():
+ rng = date_range("1/1/2000 00:00:00", periods=10, freq="10s")
+ series = Series(rng)
+
+ result = series.reindex(range(15))
+ assert np.issubdtype(result.dtype, np.dtype("M8[ns]"))
+
+ mask = result.isna()
+ assert mask[-5:].all()
+ assert not mask[:-5].any()
+
+
+def test_reindex_with_datetimes():
+ rng = date_range("1/1/2000", periods=20)
+ ts = Series(np.random.default_rng(2).standard_normal(20), index=rng)
+
+ result = ts.reindex(list(ts.index[5:10]))
+ expected = ts[5:10]
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+
+ result = ts[list(ts.index[5:10])]
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_corner(datetime_series):
+ # (don't forget to fix this) I think it's fixed
+ empty = Series(index=[])
+ empty.reindex(datetime_series.index, method="pad") # it works
+
+ # corner case: pad empty series
+ reindexed = empty.reindex(datetime_series.index, method="pad")
+
+ # pass non-Index
+ reindexed = datetime_series.reindex(list(datetime_series.index))
+ datetime_series.index = datetime_series.index._with_freq(None)
+ tm.assert_series_equal(datetime_series, reindexed)
+
+ # bad fill method
+ ts = datetime_series[::2]
+ msg = (
+ r"Invalid fill method\. Expecting pad \(ffill\), backfill "
+ r"\(bfill\) or nearest\. Got foo"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ts.reindex(datetime_series.index, method="foo")
+
+
+def test_reindex_pad():
+ s = Series(np.arange(10), dtype="int64")
+ s2 = s[::2]
+
+ reindexed = s2.reindex(s.index, method="pad")
+ reindexed2 = s2.reindex(s.index, method="ffill")
+ tm.assert_series_equal(reindexed, reindexed2)
+
+ expected = Series([0, 0, 2, 2, 4, 4, 6, 6, 8, 8])
+ tm.assert_series_equal(reindexed, expected)
+
+
+def test_reindex_pad2():
+ # GH4604
+ s = Series([1, 2, 3, 4, 5], index=["a", "b", "c", "d", "e"])
+ new_index = ["a", "g", "c", "f"]
+ expected = Series([1, 1, 3, 3.0], index=new_index)
+
+ # this changes dtype because the ffill happens after
+ result = s.reindex(new_index).ffill()
+ tm.assert_series_equal(result, expected)
+
+ result = s.reindex(new_index).ffill()
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([1, 5, 3, 5], index=new_index)
+ result = s.reindex(new_index, method="ffill")
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_inference():
+ # inference of new dtype
+ s = Series([True, False, False, True], index=list("abcd"))
+ new_index = "agc"
+ result = s.reindex(list(new_index)).ffill()
+ expected = Series([True, True, False], index=list(new_index), dtype=object)
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_downcasting():
+ # GH4618 shifted series downcasting
+ s = Series(False, index=range(5))
+ result = s.shift(1).bfill()
+ expected = Series(False, index=range(5), dtype=object)
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_nearest():
+ s = Series(np.arange(10, dtype="int64"))
+ target = [0.1, 0.9, 1.5, 2.0]
+ result = s.reindex(target, method="nearest")
+ expected = Series(np.around(target).astype("int64"), target)
+ tm.assert_series_equal(expected, result)
+
+ result = s.reindex(target, method="nearest", tolerance=0.2)
+ expected = Series([0, 1, np.nan, 2], target)
+ tm.assert_series_equal(expected, result)
+
+ result = s.reindex(target, method="nearest", tolerance=[0.3, 0.01, 0.4, 3])
+ expected = Series([0, np.nan, np.nan, 2], target)
+ tm.assert_series_equal(expected, result)
+
+
+def test_reindex_int(datetime_series):
+ ts = datetime_series[::2]
+ int_ts = Series(np.zeros(len(ts), dtype=int), index=ts.index)
+
+ # this should work fine
+ reindexed_int = int_ts.reindex(datetime_series.index)
+
+ # if NaNs introduced
+ assert reindexed_int.dtype == np.float64
+
+ # NO NaNs introduced
+ reindexed_int = int_ts.reindex(int_ts.index[::2])
+ assert reindexed_int.dtype == np.dtype(int)
+
+
+def test_reindex_bool(datetime_series):
+ # A series other than float, int, string, or object
+ ts = datetime_series[::2]
+ bool_ts = Series(np.zeros(len(ts), dtype=bool), index=ts.index)
+
+ # this should work fine
+ reindexed_bool = bool_ts.reindex(datetime_series.index)
+
+ # if NaNs introduced
+ assert reindexed_bool.dtype == np.object_
+
+ # NO NaNs introduced
+ reindexed_bool = bool_ts.reindex(bool_ts.index[::2])
+ assert reindexed_bool.dtype == np.bool_
+
+
+def test_reindex_bool_pad(datetime_series):
+ # fail
+ ts = datetime_series[5:]
+ bool_ts = Series(np.zeros(len(ts), dtype=bool), index=ts.index)
+ filled_bool = bool_ts.reindex(datetime_series.index, method="pad")
+ assert isna(filled_bool[:5]).all()
+
+
+def test_reindex_categorical():
+ index = date_range("20000101", periods=3)
+
+ # reindexing to an invalid Categorical
+ s = Series(["a", "b", "c"], dtype="category")
+ result = s.reindex(index)
+ expected = Series(
+ Categorical(values=[np.nan, np.nan, np.nan], categories=["a", "b", "c"])
+ )
+ expected.index = index
+ tm.assert_series_equal(result, expected)
+
+ # partial reindexing
+ expected = Series(
+ Categorical(values=["b", "c"], categories=["a", "b", "c"]), index=range(1, 3)
+ )
+ result = s.reindex([1, 2])
+ tm.assert_series_equal(result, expected)
+
+ expected = Series(
+ Categorical(values=["c", np.nan], categories=["a", "b", "c"]), index=range(2, 4)
+ )
+ result = s.reindex([2, 3])
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_astype_order_consistency():
+ # GH#17444
+ ser = Series([1, 2, 3], index=[2, 0, 1])
+ new_index = [0, 1, 2]
+ temp_dtype = "category"
+ new_dtype = str
+ result = ser.reindex(new_index).astype(temp_dtype).astype(new_dtype)
+ expected = ser.astype(temp_dtype).reindex(new_index).astype(new_dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_fill_value():
+ # -----------------------------------------------------------
+ # floats
+ floats = Series([1.0, 2.0, 3.0])
+ result = floats.reindex([1, 2, 3])
+ expected = Series([2.0, 3.0, np.nan], index=range(1, 4))
+ tm.assert_series_equal(result, expected)
+
+ result = floats.reindex([1, 2, 3], fill_value=0)
+ expected = Series([2.0, 3.0, 0], index=range(1, 4))
+ tm.assert_series_equal(result, expected)
+
+ # -----------------------------------------------------------
+ # ints
+ ints = Series([1, 2, 3])
+
+ result = ints.reindex([1, 2, 3])
+ expected = Series([2.0, 3.0, np.nan], index=range(1, 4))
+ tm.assert_series_equal(result, expected)
+
+ # don't upcast
+ result = ints.reindex([1, 2, 3], fill_value=0)
+ expected = Series([2, 3, 0], index=range(1, 4))
+ assert issubclass(result.dtype.type, np.integer)
+ tm.assert_series_equal(result, expected)
+
+ # -----------------------------------------------------------
+ # objects
+ objects = Series([1, 2, 3], dtype=object)
+
+ result = objects.reindex([1, 2, 3])
+ expected = Series([2, 3, np.nan], index=range(1, 4), dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ result = objects.reindex([1, 2, 3], fill_value="foo")
+ expected = Series([2, 3, "foo"], index=range(1, 4), dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ # ------------------------------------------------------------
+ # bools
+ bools = Series([True, False, True])
+
+ result = bools.reindex([1, 2, 3])
+ expected = Series([False, True, np.nan], index=range(1, 4), dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ result = bools.reindex([1, 2, 3], fill_value=False)
+ expected = Series([False, True, False], index=range(1, 4))
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("dtype", ["datetime64[ns]", "timedelta64[ns]"])
+@pytest.mark.parametrize("fill_value", ["string", 0, Timedelta(0)])
+def test_reindex_fill_value_datetimelike_upcast(dtype, fill_value):
+ # https://github.com/pandas-dev/pandas/issues/42921
+ if dtype == "timedelta64[ns]" and fill_value == Timedelta(0):
+ # use the scalar that is not compatible with the dtype for this test
+ fill_value = Timestamp(0)
+
+ ser = Series([NaT], dtype=dtype)
+
+ result = ser.reindex([0, 1], fill_value=fill_value)
+ expected = Series([NaT, fill_value], index=range(2), dtype=object)
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_datetimeindexes_tz_naive_and_aware():
+ # GH 8306
+ idx = date_range("20131101", tz="America/Chicago", periods=7, unit="ns")
+ newidx = date_range("20131103", periods=10, freq="h", unit="ns")
+ s = Series(range(7), index=idx)
+ msg = (
+ r"Cannot compare dtypes datetime64\[ns, America/Chicago\] "
+ r"and datetime64\[ns\]"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s.reindex(newidx, method="ffill")
+
+
+def test_reindex_empty_series_tz_dtype():
+ # GH 20869
+ result = Series(dtype="datetime64[ns, UTC]").reindex([0, 1])
+ expected = Series([NaT] * 2, dtype="datetime64[ns, UTC]")
+ tm.assert_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "p_values, o_values, values, expected_values",
+ [
+ (
+ [Period("2019Q1", "Q-DEC"), Period("2019Q2", "Q-DEC")],
+ [Period("2019Q1", "Q-DEC"), Period("2019Q2", "Q-DEC"), "All"],
+ [1.0, 1.0],
+ [1.0, 1.0, np.nan],
+ ),
+ (
+ [Period("2019Q1", "Q-DEC"), Period("2019Q2", "Q-DEC")],
+ [Period("2019Q1", "Q-DEC"), Period("2019Q2", "Q-DEC")],
+ [1.0, 1.0],
+ [1.0, 1.0],
+ ),
+ ],
+)
+def test_reindex_periodindex_with_object(p_values, o_values, values, expected_values):
+ # GH#28337
+ period_index = PeriodIndex(p_values)
+ object_index = Index(o_values)
+
+ ser = Series(values, index=period_index)
+ result = ser.reindex(object_index)
+ expected = Series(expected_values, index=object_index)
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_too_many_args():
+ # GH 40980
+ ser = Series([1, 2])
+ msg = r"reindex\(\) takes from 1 to 2 positional arguments but 3 were given"
+ with pytest.raises(TypeError, match=msg):
+ ser.reindex([2, 3], False)
+
+
+def test_reindex_double_index():
+ # GH 40980
+ ser = Series([1, 2])
+ msg = r"reindex\(\) got multiple values for argument 'index'"
+ with pytest.raises(TypeError, match=msg):
+ ser.reindex([2, 3], index=[3, 4])
+
+
+def test_reindex_no_posargs():
+ # GH 40980
+ ser = Series([1, 2])
+ result = ser.reindex(index=[1, 0])
+ expected = Series([2, 1], index=[1, 0])
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("values", [[["a"], ["x"]], [[], []]])
+def test_reindex_empty_with_level(values):
+ # GH41170
+ ser = Series(
+ range(len(values[0])), index=MultiIndex.from_arrays(values), dtype="object"
+ )
+ result = ser.reindex(np.array(["b"]), level=0)
+ expected = Series(
+ index=MultiIndex(levels=[["b"], values[1]], codes=[[], []]), dtype="object"
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_missing_category():
+ # GH#18185
+ ser = Series([1, 2, 3, 1], dtype="category")
+ msg = r"Cannot setitem on a Categorical with a new category \(-1\)"
+ with pytest.raises(TypeError, match=msg):
+ ser.reindex([1, 2, 3, 4, 5], fill_value=-1)
+
+
+def test_reindexing_with_float64_NA_log():
+ # GH 47055
+ s = Series([1.0, NA], dtype=Float64Dtype())
+ s_reindex = s.reindex(range(3))
+ result = s_reindex.values._data
+ expected = np.array([1, np.nan, np.nan])
+ tm.assert_numpy_array_equal(result, expected)
+ with tm.assert_produces_warning(None):
+ result_log = np.log(s_reindex)
+ expected_log = Series([0, NA, NA], dtype=Float64Dtype())
+ tm.assert_series_equal(result_log, expected_log)
+
+
+@pytest.mark.parametrize("dtype", ["timedelta64", "datetime64"])
+def test_reindex_expand_nonnano_nat(dtype):
+ # GH 53497
+ ser = Series(np.array([1], dtype=f"{dtype}[s]"))
+ result = ser.reindex(RangeIndex(2))
+ expected = Series(
+ np.array([1, getattr(np, dtype)("nat", "s")], dtype=f"{dtype}[s]")
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "name, expected_match_level_a",
+ [
+ ("a", True),
+ (None, False),
+ ("x", False),
+ ],
+)
+def test_reindex_multiindex_automatic_level(name, expected_match_level_a):
+ series = Series([26.73, 24.255], index=Index([81, 82], name=name))
+ target = MultiIndex.from_product(
+ [[81, 82], [np.nan], ["2018-06-01", "2018-07-01"]], names=["a", "b", "c"]
+ )
+
+ result = series.reindex(target)
+
+ if expected_match_level_a:
+ expected = Series(
+ data=[26.73, 26.73, 24.255, 24.255],
+ index=MultiIndex.from_product(
+ [[81, 82], [np.nan], ["2018-06-01", "2018-07-01"]],
+ names=["a", "b", "c"],
+ ),
+ dtype=series.dtype,
+ )
+ else:
+ expected = Series(
+ data=[np.nan] * 4,
+ index=MultiIndex.from_product(
+ [[81, 82], [np.nan], ["2018-06-01", "2018-07-01"]],
+ names=["a", "b", "c"],
+ ),
+ dtype=series.dtype,
+ )
+
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_reindex_like.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_reindex_like.py
new file mode 100644
index 0000000000000000000000000000000000000000..15cd1502dadfb935da8c0d3415af1c9669025d9b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_reindex_like.py
@@ -0,0 +1,47 @@
+from datetime import datetime
+
+import numpy as np
+
+from pandas.errors import Pandas4Warning
+
+from pandas import Series
+import pandas._testing as tm
+
+
+def test_reindex_like(datetime_series):
+ other = datetime_series[::2]
+ tm.assert_series_equal(
+ datetime_series.reindex(other.index), datetime_series.reindex_like(other)
+ )
+
+ # GH#7179
+ day1 = datetime(2013, 3, 5)
+ day2 = datetime(2013, 5, 5)
+ day3 = datetime(2014, 3, 5)
+
+ series1 = Series([5, None, None], [day1, day2, day3])
+ series2 = Series([None, None], [day1, day3])
+
+ with tm.assert_produces_warning(Pandas4Warning):
+ result = series1.reindex_like(series2, method="pad")
+ expected = Series([5, np.nan], index=[day1, day3])
+ tm.assert_series_equal(result, expected)
+
+
+def test_reindex_like_nearest():
+ ser = Series(np.arange(10, dtype="int64"))
+
+ target = [0.1, 0.9, 1.5, 2.0]
+ other = ser.reindex(target, method="nearest")
+ expected = Series(np.around(target).astype("int64"), target)
+
+ with tm.assert_produces_warning(Pandas4Warning):
+ result = ser.reindex_like(other, method="nearest")
+ tm.assert_series_equal(expected, result)
+
+ with tm.assert_produces_warning(Pandas4Warning):
+ result = ser.reindex_like(other, method="nearest", tolerance=1)
+ tm.assert_series_equal(expected, result)
+ with tm.assert_produces_warning(Pandas4Warning):
+ result = ser.reindex_like(other, method="nearest", tolerance=[1, 2, 3, 4])
+ tm.assert_series_equal(expected, result)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_rename.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_rename.py
new file mode 100644
index 0000000000000000000000000000000000000000..c912b9abd22ca86682de287060042b58719b1fb1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_rename.py
@@ -0,0 +1,179 @@
+from datetime import datetime
+import re
+
+import numpy as np
+import pytest
+
+from pandas import (
+ Index,
+ MultiIndex,
+ Series,
+ array,
+)
+import pandas._testing as tm
+
+
+class TestRename:
+ def test_rename(self, datetime_series):
+ ts = datetime_series
+ renamer = lambda x: x.strftime("%Y%m%d")
+ renamed = ts.rename(renamer)
+ assert renamed.index[0] == renamer(ts.index[0])
+
+ # dict
+ rename_dict = dict(zip(ts.index, renamed.index, strict=True))
+ renamed2 = ts.rename(rename_dict)
+ tm.assert_series_equal(renamed, renamed2)
+
+ def test_rename_partial_dict(self):
+ # partial dict
+ ser = Series(np.arange(4), index=["a", "b", "c", "d"], dtype="int64")
+ renamed = ser.rename({"b": "foo", "d": "bar"})
+ tm.assert_index_equal(renamed.index, Index(["a", "foo", "c", "bar"]))
+
+ def test_rename_retain_index_name(self):
+ # index with name
+ renamer = Series(
+ np.arange(4), index=Index(["a", "b", "c", "d"], name="name"), dtype="int64"
+ )
+ renamed = renamer.rename({})
+ assert renamed.index.name == renamer.index.name
+
+ def test_rename_by_series(self):
+ ser = Series(range(5), name="foo")
+ renamer = Series({1: 10, 2: 20})
+ result = ser.rename(renamer)
+ expected = Series(range(5), index=[0, 10, 20, 3, 4], name="foo")
+ tm.assert_series_equal(result, expected)
+
+ def test_rename_set_name(self, using_infer_string):
+ ser = Series(range(4), index=list("abcd"))
+ for name in ["foo", 123, 123.0, datetime(2001, 11, 11), ("foo",)]:
+ result = ser.rename(name)
+ assert result.name == name
+ if using_infer_string:
+ tm.assert_extension_array_equal(result.index.values, ser.index.values)
+ else:
+ tm.assert_numpy_array_equal(result.index.values, ser.index.values)
+ assert ser.name is None
+
+ def test_rename_set_name_inplace(self, using_infer_string):
+ ser = Series(range(3), index=list("abc"))
+ for name in ["foo", 123, 123.0, datetime(2001, 11, 11), ("foo",)]:
+ ser.rename(name, inplace=True)
+ assert ser.name == name
+ exp = np.array(["a", "b", "c"], dtype=np.object_)
+ if using_infer_string:
+ exp = array(exp, dtype="str")
+ tm.assert_extension_array_equal(ser.index.values, exp)
+ else:
+ tm.assert_numpy_array_equal(ser.index.values, exp)
+
+ def test_rename_axis_supported(self):
+ # Supporting axis for compatibility, detailed in GH-18589
+ ser = Series(range(5))
+ ser.rename({}, axis=0)
+ ser.rename({}, axis="index")
+
+ with pytest.raises(ValueError, match="No axis named 5"):
+ ser.rename({}, axis=5)
+
+ def test_rename_inplace(self, datetime_series):
+ renamer = lambda x: x.strftime("%Y%m%d")
+ expected = renamer(datetime_series.index[0])
+
+ datetime_series.rename(renamer, inplace=True)
+ assert datetime_series.index[0] == expected
+
+ def test_rename_with_custom_indexer(self):
+ # GH 27814
+ class MyIndexer:
+ pass
+
+ ix = MyIndexer()
+ ser = Series([1, 2, 3]).rename(ix)
+ assert ser.name is ix
+
+ def test_rename_with_custom_indexer_inplace(self):
+ # GH 27814
+ class MyIndexer:
+ pass
+
+ ix = MyIndexer()
+ ser = Series([1, 2, 3])
+ ser.rename(ix, inplace=True)
+ assert ser.name is ix
+
+ def test_rename_callable(self):
+ # GH 17407
+ ser = Series(range(1, 6), index=Index(range(2, 7), name="IntIndex"))
+ result = ser.rename(str)
+ expected = ser.rename(lambda i: str(i))
+ tm.assert_series_equal(result, expected)
+
+ assert result.name == expected.name
+
+ def test_rename_none(self):
+ # GH 40977
+ ser = Series([1, 2], name="foo")
+ result = ser.rename(None)
+ expected = Series([1, 2])
+ tm.assert_series_equal(result, expected)
+
+ def test_rename_series_with_multiindex(self):
+ # issue #43659
+ arrays = [
+ ["bar", "baz", "baz", "foo", "qux"],
+ ["one", "one", "two", "two", "one"],
+ ]
+
+ index = MultiIndex.from_arrays(arrays, names=["first", "second"])
+ ser = Series(np.ones(5), index=index)
+ result = ser.rename(index={"one": "yes"}, level="second", errors="raise")
+
+ arrays_expected = [
+ ["bar", "baz", "baz", "foo", "qux"],
+ ["yes", "yes", "two", "two", "yes"],
+ ]
+
+ index_expected = MultiIndex.from_arrays(
+ arrays_expected, names=["first", "second"]
+ )
+ series_expected = Series(np.ones(5), index=index_expected)
+
+ tm.assert_series_equal(result, series_expected)
+
+ def test_rename_series_with_multiindex_keeps_ea_dtypes(self):
+ # GH21055
+ arrays = [
+ Index([1, 2, 3], dtype="Int64").astype("category"),
+ Index([1, 2, 3], dtype="Int64"),
+ ]
+ mi = MultiIndex.from_arrays(arrays, names=["A", "B"])
+ ser = Series(1, index=mi)
+ result = ser.rename({1: 4}, level=1)
+
+ arrays_expected = [
+ Index([1, 2, 3], dtype="Int64").astype("category"),
+ Index([4, 2, 3], dtype="Int64"),
+ ]
+ mi_expected = MultiIndex.from_arrays(arrays_expected, names=["A", "B"])
+ expected = Series(1, index=mi_expected)
+
+ tm.assert_series_equal(result, expected)
+
+ def test_rename_error_arg(self):
+ # GH 46889
+ ser = Series(["foo", "bar"])
+ match = re.escape("[2] not found in axis")
+ with pytest.raises(KeyError, match=match):
+ ser.rename({2: 9}, errors="raise")
+
+ def test_rename_copy_false(self):
+ # GH 46889
+ ser = Series(["foo", "bar"])
+ ser_orig = ser.copy()
+ shallow_copy = ser.rename({1: 9})
+ ser[0] = "foobar"
+ assert ser_orig[0] == shallow_copy[0]
+ assert ser_orig[1] == shallow_copy[9]
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_rename_axis.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_rename_axis.py
new file mode 100644
index 0000000000000000000000000000000000000000..58c095d697ede213be5c730e305bf1789810ac4a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_rename_axis.py
@@ -0,0 +1,47 @@
+import pytest
+
+from pandas import (
+ Index,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestSeriesRenameAxis:
+ def test_rename_axis_mapper(self):
+ # GH 19978
+ mi = MultiIndex.from_product([["a", "b", "c"], [1, 2]], names=["ll", "nn"])
+ ser = Series(list(range(len(mi))), index=mi)
+
+ result = ser.rename_axis(index={"ll": "foo"})
+ assert result.index.names == ["foo", "nn"]
+
+ result = ser.rename_axis(index=str.upper, axis=0)
+ assert result.index.names == ["LL", "NN"]
+
+ result = ser.rename_axis(index=["foo", "goo"])
+ assert result.index.names == ["foo", "goo"]
+
+ with pytest.raises(TypeError, match="unexpected"):
+ ser.rename_axis(columns="wrong")
+
+ def test_rename_axis_inplace(self, datetime_series):
+ # GH 15704
+ expected = datetime_series.rename_axis("foo")
+ result = datetime_series
+ no_return = result.rename_axis("foo", inplace=True)
+
+ assert no_return is None
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("kwargs", [{"mapper": None}, {"index": None}, {}])
+ def test_rename_axis_none(self, kwargs):
+ # GH 25034
+ index = Index(list("abc"), name="foo")
+ ser = Series([1, 2, 3], index=index)
+
+ result = ser.rename_axis(**kwargs)
+ expected_index = index.rename(None) if kwargs else index
+ expected = Series([1, 2, 3], index=expected_index)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_repeat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_repeat.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ecc8052ff49c150444cf395b68e6163fb761775
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_repeat.py
@@ -0,0 +1,40 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestRepeat:
+ def test_repeat(self):
+ ser = Series(np.random.default_rng(2).standard_normal(3), index=["a", "b", "c"])
+
+ reps = ser.repeat(5)
+ exp = Series(ser.values.repeat(5), index=ser.index.values.repeat(5))
+ tm.assert_series_equal(reps, exp)
+
+ to_rep = [2, 3, 4]
+ reps = ser.repeat(to_rep)
+ exp = Series(ser.values.repeat(to_rep), index=ser.index.values.repeat(to_rep))
+ tm.assert_series_equal(reps, exp)
+
+ def test_numpy_repeat(self):
+ ser = Series(np.arange(3), name="x")
+ expected = Series(
+ ser.values.repeat(2), name="x", index=ser.index.values.repeat(2)
+ )
+ tm.assert_series_equal(np.repeat(ser, 2), expected)
+
+ msg = "the 'axis' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.repeat(ser, 2, axis=0)
+
+ def test_repeat_with_multiindex(self):
+ # GH#9361, fixed by GH#7891
+ m_idx = MultiIndex.from_tuples([(1, 2), (3, 4), (5, 6), (7, 8)])
+ data = ["a", "b", "c", "d"]
+ m_df = Series(data, index=m_idx)
+ assert m_df.repeat(3).shape == (3 * len(data),)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_replace.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_replace.py
new file mode 100644
index 0000000000000000000000000000000000000000..57cab07ada9a4e84ce3cc037e151d233efd5831b
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_replace.py
@@ -0,0 +1,741 @@
+import re
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.core.arrays import IntervalArray
+
+
+class TestSeriesReplace:
+ def test_replace_explicit_none(self):
+ # GH#36984 if the user explicitly passes value=None, give it to them
+ ser = pd.Series([0, 0, ""], dtype=object)
+ result = ser.replace("", None)
+ expected = pd.Series([0, 0, None], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ # Cast column 2 to object to avoid implicit cast when setting entry to ""
+ df = pd.DataFrame(np.zeros((3, 3))).astype({2: object})
+ df.iloc[2, 2] = ""
+ result = df.replace("", None)
+ expected = pd.DataFrame(
+ {
+ 0: np.zeros(3),
+ 1: np.zeros(3),
+ 2: np.array([0.0, 0.0, None], dtype=object),
+ }
+ )
+ assert expected.iloc[2, 2] is None
+ tm.assert_frame_equal(result, expected)
+
+ # GH#19998 same thing with object dtype
+ ser = pd.Series([10, 20, 30, "a", "a", "b", "a"])
+ result = ser.replace("a", None)
+ expected = pd.Series([10, 20, 30, None, None, "b", None])
+ assert expected.iloc[-1] is None
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_noop_doesnt_downcast(self):
+ # GH#44498
+ ser = pd.Series([None, None, pd.Timestamp("2021-12-16 17:31")], dtype=object)
+ res = ser.replace({np.nan: None}) # should be a no-op
+ tm.assert_series_equal(res, ser)
+ assert res.dtype == object
+
+ # same thing but different calling convention
+ res = ser.replace(np.nan, None)
+ tm.assert_series_equal(res, ser)
+ assert res.dtype == object
+
+ def test_replace(self):
+ N = 50
+ ser = pd.Series(np.random.default_rng(2).standard_normal(N))
+ ser[0:4] = np.nan
+ ser[6:10] = 0
+
+ # replace list with a single value
+ result = ser.replace([np.nan], -1, inplace=True)
+ assert result is ser
+
+ exp = ser.fillna(-1)
+ tm.assert_series_equal(ser, exp)
+
+ rs = ser.replace(0.0, np.nan)
+ ser[ser == 0.0] = np.nan
+ tm.assert_series_equal(rs, ser)
+
+ ser = pd.Series(
+ np.fabs(np.random.default_rng(2).standard_normal(N)),
+ pd.date_range("2020-01-01", periods=N),
+ dtype=object,
+ )
+ ser[:5] = np.nan
+ ser[6:10] = "foo"
+ ser[20:30] = "bar"
+
+ # replace list with a single value
+
+ rs = ser.replace([np.nan, "foo", "bar"], -1)
+
+ assert (rs[:5] == -1).all()
+ assert (rs[6:10] == -1).all()
+ assert (rs[20:30] == -1).all()
+ assert (pd.isna(ser[:5])).all()
+
+ # replace with different values
+ rs = ser.replace({np.nan: -1, "foo": -2, "bar": -3})
+
+ assert (rs[:5] == -1).all()
+ assert (rs[6:10] == -2).all()
+ assert (rs[20:30] == -3).all()
+ assert (pd.isna(ser[:5])).all()
+
+ # replace with different values with 2 lists
+ rs2 = ser.replace([np.nan, "foo", "bar"], [-1, -2, -3])
+ tm.assert_series_equal(rs, rs2)
+
+ # replace inplace
+ result = ser.replace([np.nan, "foo", "bar"], -1, inplace=True)
+ assert result is ser
+
+ assert (ser[:5] == -1).all()
+ assert (ser[6:10] == -1).all()
+ assert (ser[20:30] == -1).all()
+
+ def test_replace_nan_with_inf(self):
+ ser = pd.Series([np.nan, 0, np.inf])
+ tm.assert_series_equal(ser.replace(np.nan, 0), ser.fillna(0))
+
+ ser = pd.Series([np.nan, 0, "foo", "bar", np.inf, None, pd.NaT])
+ tm.assert_series_equal(ser.replace(np.nan, 0), ser.fillna(0))
+ filled = ser.copy()
+ filled[4] = 0
+ tm.assert_series_equal(ser.replace(np.inf, 0), filled)
+
+ def test_replace_listlike_value_listlike_target(self, datetime_series):
+ ser = pd.Series(datetime_series.index)
+ tm.assert_series_equal(ser.replace(np.nan, 0), ser.fillna(0))
+
+ # malformed
+ msg = r"Replacement lists must match in length\. Expecting 3 got 2"
+ with pytest.raises(ValueError, match=msg):
+ ser.replace([1, 2, 3], [np.nan, 0])
+
+ # ser is dt64 so can't hold 1 or 2, so this replace is a no-op
+ result = ser.replace([1, 2], [np.nan, 0])
+ tm.assert_series_equal(result, ser)
+
+ ser = pd.Series([0, 1, 2, 3, 4])
+ result = ser.replace([0, 1, 2, 3, 4], [4, 3, 2, 1, 0])
+ tm.assert_series_equal(result, pd.Series([4, 3, 2, 1, 0]))
+
+ def test_replace_gh5319(self):
+ # API change from 0.12?
+ # GH 5319
+ ser = pd.Series([0, np.nan, 2, 3, 4])
+ msg = (
+ "Series.replace must specify either 'value', "
+ "a dict-like 'to_replace', or dict-like 'regex'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ser.replace([np.nan])
+
+ with pytest.raises(ValueError, match=msg):
+ ser.replace(np.nan)
+
+ def test_replace_datetime64(self):
+ # GH 5797
+ ser = pd.Series(pd.date_range("20130101", periods=5))
+ expected = ser.copy()
+ expected.loc[2] = pd.Timestamp("20120101")
+ result = ser.replace({pd.Timestamp("20130103"): pd.Timestamp("20120101")})
+ tm.assert_series_equal(result, expected)
+ result = ser.replace(pd.Timestamp("20130103"), pd.Timestamp("20120101"))
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_nat_with_tz(self):
+ # GH 11792: Test with replacing NaT in a list with tz data
+ ts = pd.Timestamp("2015/01/01", tz="UTC")
+ s = pd.Series([pd.NaT, pd.Timestamp("2015/01/01", tz="UTC")])
+ result = s.replace([np.nan, pd.NaT], pd.Timestamp.min)
+ expected = pd.Series([pd.Timestamp.min, ts], dtype=object)
+ tm.assert_series_equal(expected, result)
+
+ def test_replace_timedelta_td64(self):
+ tdi = pd.timedelta_range(0, periods=5)
+ ser = pd.Series(tdi)
+
+ # Using a single dict argument means we go through replace_list
+ result = ser.replace({ser[1]: ser[3]})
+
+ expected = pd.Series([ser[0], ser[3], ser[2], ser[3], ser[4]])
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_with_single_list(self):
+ ser = pd.Series([0, 1, 2, 3, 4])
+ msg = (
+ "Series.replace must specify either 'value', "
+ "a dict-like 'to_replace', or dict-like 'regex'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ser.replace([1, 2, 3])
+
+ s = ser.copy()
+ with pytest.raises(ValueError, match=msg):
+ s.replace([1, 2, 3], inplace=True)
+
+ def test_replace_mixed_types(self):
+ ser = pd.Series(np.arange(5), dtype="int64")
+
+ def check_replace(to_rep, val, expected):
+ sc = ser.copy()
+ result = ser.replace(to_rep, val)
+ result = sc.replace(to_rep, val, inplace=True)
+ assert result is sc
+ tm.assert_series_equal(expected, result)
+ tm.assert_series_equal(expected, sc)
+
+ # 3.0 can still be held in our int64 series, so we do not upcast GH#44940
+ tr, v = [3], [3.0]
+ check_replace(tr, v, ser)
+ # Note this matches what we get with the scalars 3 and 3.0
+ check_replace(tr[0], v[0], ser)
+
+ # MUST upcast to float
+ e = pd.Series([0, 1, 2, 3.5, 4])
+ tr, v = [3], [3.5]
+ check_replace(tr, v, e)
+
+ # casts to object
+ e = pd.Series([0, 1, 2, 3.5, "a"])
+ tr, v = [3, 4], [3.5, "a"]
+ check_replace(tr, v, e)
+
+ # again casts to object
+ e = pd.Series([0, 1, 2, 3.5, pd.Timestamp("20130101")])
+ tr, v = [3, 4], [3.5, pd.Timestamp("20130101")]
+ check_replace(tr, v, e)
+
+ # casts to object
+ e = pd.Series([0, 1, 2, 3.5, True], dtype="object")
+ tr, v = [3, 4], [3.5, True]
+ check_replace(tr, v, e)
+
+ # test an object with dates + floats + integers + strings
+ dr = pd.Series(pd.date_range("1/1/2001", "1/10/2001", freq="D"))
+ result = dr.astype(object).replace([dr[0], dr[1], dr[2]], [1.0, 2, "a"])
+ expected = pd.Series([1.0, 2, "a", *dr[3:].tolist()], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_bool_with_string_no_op(self):
+ s = pd.Series([True, False, True])
+ result = s.replace("fun", "in-the-sun")
+ tm.assert_series_equal(s, result)
+
+ def test_replace_bool_with_string(self):
+ # nonexistent elements
+ s = pd.Series([True, False, True])
+ result = s.replace(True, "2u")
+ expected = pd.Series(["2u", False, "2u"])
+ tm.assert_series_equal(expected, result)
+
+ def test_replace_bool_with_bool(self):
+ s = pd.Series([True, False, True])
+ result = s.replace(True, False)
+ expected = pd.Series([False] * len(s))
+ tm.assert_series_equal(expected, result)
+
+ def test_replace_with_dict_with_bool_keys(self):
+ s = pd.Series([True, False, True])
+ result = s.replace({"asdf": "asdb", True: "yes"})
+ expected = pd.Series(["yes", False, "yes"])
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_Int_with_na(self, any_int_ea_dtype):
+ # GH 38267
+ result = pd.Series([0, None], dtype=any_int_ea_dtype).replace(0, pd.NA)
+ expected = pd.Series([pd.NA, pd.NA], dtype=any_int_ea_dtype)
+ tm.assert_series_equal(result, expected)
+ result = pd.Series([0, 1], dtype=any_int_ea_dtype).replace(0, pd.NA)
+ result2 = result.replace(1, pd.NA, inplace=True)
+ assert result2 is result
+ tm.assert_series_equal(result, expected)
+
+ def test_replace2(self):
+ N = 50
+ ser = pd.Series(
+ np.fabs(np.random.default_rng(2).standard_normal(N)),
+ pd.date_range("2020-01-01", periods=N),
+ dtype=object,
+ )
+ ser[:5] = np.nan
+ ser[6:10] = "foo"
+ ser[20:30] = "bar"
+
+ # replace list with a single value
+ rs = ser.replace([np.nan, "foo", "bar"], -1)
+
+ assert (rs[:5] == -1).all()
+ assert (rs[6:10] == -1).all()
+ assert (rs[20:30] == -1).all()
+ assert (pd.isna(ser[:5])).all()
+
+ # replace with different values
+ rs = ser.replace({np.nan: -1, "foo": -2, "bar": -3})
+
+ assert (rs[:5] == -1).all()
+ assert (rs[6:10] == -2).all()
+ assert (rs[20:30] == -3).all()
+ assert (pd.isna(ser[:5])).all()
+
+ # replace with different values with 2 lists
+ rs2 = ser.replace([np.nan, "foo", "bar"], [-1, -2, -3])
+ tm.assert_series_equal(rs, rs2)
+
+ # replace inplace
+ result = ser.replace([np.nan, "foo", "bar"], -1, inplace=True)
+ assert result is ser
+ assert (ser[:5] == -1).all()
+ assert (ser[6:10] == -1).all()
+ assert (ser[20:30] == -1).all()
+
+ @pytest.mark.parametrize("inplace", [True, False])
+ def test_replace_cascade(self, inplace):
+ # Test that replaced values are not replaced again
+ # GH #50778
+ ser = pd.Series([1, 2, 3])
+ expected = pd.Series([2, 3, 4])
+
+ res = ser.replace([1, 2, 3], [2, 3, 4], inplace=inplace)
+ if inplace:
+ tm.assert_series_equal(ser, expected)
+ else:
+ tm.assert_series_equal(res, expected)
+
+ def test_replace_with_dictlike_and_string_dtype(self, nullable_string_dtype):
+ # GH 32621, GH#44940
+ ser = pd.Series(["one", "two", np.nan], dtype=nullable_string_dtype)
+ expected = pd.Series(["1", "2", np.nan], dtype=nullable_string_dtype)
+ result = ser.replace({"one": "1", "two": "2"})
+ tm.assert_series_equal(expected, result)
+
+ def test_replace_with_empty_dictlike(self):
+ # GH 15289
+ s = pd.Series(list("abcd"))
+ tm.assert_series_equal(s, s.replace({}))
+
+ empty_series = pd.Series([])
+ tm.assert_series_equal(s, s.replace(empty_series))
+
+ def test_replace_string_with_number(self):
+ # GH 15743
+ s = pd.Series([1, 2, 3])
+ result = s.replace("2", np.nan)
+ expected = pd.Series([1, 2, 3])
+ tm.assert_series_equal(expected, result)
+
+ def test_replace_replacer_equals_replacement(self):
+ # GH 20656
+ # make sure all replacers are matching against original values
+ s = pd.Series(["a", "b"])
+ expected = pd.Series(["b", "a"])
+ result = s.replace({"a": "b", "b": "a"})
+ tm.assert_series_equal(expected, result)
+
+ def test_replace_unicode_with_number(self):
+ # GH 15743
+ s = pd.Series([1, 2, 3])
+ result = s.replace("2", np.nan)
+ expected = pd.Series([1, 2, 3])
+ tm.assert_series_equal(expected, result)
+
+ def test_replace_mixed_types_with_string(self):
+ # Testing mixed
+ s = pd.Series([1, 2, 3, "4", 4, 5])
+ result = s.replace([2, "4"], np.nan)
+ expected = pd.Series([1, np.nan, 3, np.nan, 4, 5], dtype=object)
+ tm.assert_series_equal(expected, result)
+
+ @pytest.mark.parametrize(
+ "categorical, numeric",
+ [
+ (["A"], [1]),
+ (["A", "B"], [1, 2]),
+ ],
+ )
+ def test_replace_categorical(self, categorical, numeric):
+ # GH 24971, GH#23305
+ ser = pd.Series(pd.Categorical(categorical, categories=["A", "B"]))
+ result = ser.cat.rename_categories({"A": 1, "B": 2})
+ expected = pd.Series(numeric).astype("category")
+ if 2 not in expected.cat.categories:
+ # i.e. categories should be [1, 2] even if there are no "B"s present
+ # GH#44940
+ expected = expected.cat.add_categories(2)
+ tm.assert_series_equal(expected, result, check_categorical=False)
+
+ def test_replace_categorical_inplace(self):
+ # GH 53358
+ data = ["a", "b", "c"]
+ data_exp = ["b", "b", "c"]
+ ser = pd.Series(data, dtype="category")
+ result = ser.replace(to_replace="a", value="b", inplace=True)
+ assert result is ser
+ expected = pd.Series(pd.Categorical(data_exp, categories=data))
+ tm.assert_series_equal(ser, expected)
+
+ def test_replace_categorical_single(self):
+ # GH 26988
+ dti = pd.date_range("2016-01-01", periods=3, tz="US/Pacific")
+ s = pd.Series(dti)
+ c = s.astype("category")
+
+ expected = c.copy()
+ expected = expected.cat.add_categories("foo")
+ expected[2] = "foo"
+ expected = expected.cat.remove_unused_categories()
+ assert c[2] != "foo"
+
+ result = c.cat.rename_categories({c.values[2]: "foo"})
+ tm.assert_series_equal(expected, result)
+ assert c[2] != "foo" # ensure non-inplace call does not alter original
+
+ def test_replace_with_no_overflowerror(self):
+ # GH 25616
+ # casts to object without Exception from OverflowError
+ s = pd.Series([0, 1, 2, 3, 4])
+ result = s.replace([3], ["100000000000000000000"])
+ expected = pd.Series([0, 1, 2, "100000000000000000000", 4])
+ tm.assert_series_equal(result, expected)
+
+ s = pd.Series([0, "100000000000000000000", "100000000000000000001"])
+ result = s.replace(["100000000000000000000"], [1])
+ expected = pd.Series([0, 1, "100000000000000000001"])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "ser, to_replace, exp",
+ [
+ ([1, 2, 3], {1: 2, 2: 3, 3: 4}, [2, 3, 4]),
+ (["1", "2", "3"], {"1": "2", "2": "3", "3": "4"}, ["2", "3", "4"]),
+ ],
+ )
+ def test_replace_commutative(self, ser, to_replace, exp):
+ # GH 16051
+ # DataFrame.replace() overwrites when values are non-numeric
+
+ series = pd.Series(ser)
+
+ expected = pd.Series(exp)
+ result = series.replace(to_replace)
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "ser, exp", [([1, 2, 3], [1, True, 3]), (["x", 2, 3], ["x", True, 3])]
+ )
+ def test_replace_no_cast(self, ser, exp):
+ # GH 9113
+ # BUG: replace int64 dtype with bool coerces to int64
+
+ series = pd.Series(ser)
+ result = series.replace(2, True)
+ expected = pd.Series(exp)
+
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_invalid_to_replace(self):
+ # GH 18634
+ # API: replace() should raise an exception if invalid argument is given
+ series = pd.Series(["a", "b", "c "])
+ msg = (
+ r"Expecting 'to_replace' to be either a scalar, array-like, "
+ r"dict or None, got invalid type.*"
+ )
+ with pytest.raises(TypeError, match=msg):
+ series.replace(lambda x: x.strip())
+
+ @pytest.mark.parametrize("frame", [False, True])
+ def test_replace_nonbool_regex(self, frame):
+ obj = pd.Series(["a", "b", "c "])
+ if frame:
+ obj = obj.to_frame()
+
+ msg = "'to_replace' must be 'None' if 'regex' is not a bool"
+ with pytest.raises(ValueError, match=msg):
+ obj.replace(to_replace=["a"], regex="foo")
+
+ @pytest.mark.parametrize("frame", [False, True])
+ def test_replace_empty_copy(self, frame):
+ obj = pd.Series([], dtype=np.float64)
+ if frame:
+ obj = obj.to_frame()
+
+ res = obj.replace(4, 5, inplace=True)
+ assert res is obj
+
+ res = obj.replace(4, 5, inplace=False)
+ tm.assert_equal(res, obj)
+ assert res is not obj
+
+ def test_replace_only_one_dictlike_arg(self, fixed_now_ts):
+ # GH#33340
+
+ ser = pd.Series([1, 2, "A", fixed_now_ts, True])
+ to_replace = {0: 1, 2: "A"}
+ value = "foo"
+ msg = (
+ "Series.replace cannot specify both a dict-like 'to_replace' and a 'value'"
+ )
+ with pytest.raises(ValueError, match=msg):
+ ser.replace(to_replace, value)
+
+ to_replace = 1
+ value = {0: "foo", 2: "bar"}
+ msg = "Series.replace cannot use dict-value and non-None to_replace"
+ with pytest.raises(ValueError, match=msg):
+ ser.replace(to_replace, value)
+
+ def test_replace_dict_like_with_dict_like(self):
+ # GH 59452
+ s = pd.Series([1, 2, 3, 4, 5])
+ to_replace = pd.Series([1])
+ value = pd.Series([75])
+ msg = "to_replace and value cannot be dict-like for Series.replace"
+ with pytest.raises(ValueError, match=msg):
+ s.replace(to_replace, value)
+
+ def test_replace_extension_other(self, frame_or_series):
+ # https://github.com/pandas-dev/pandas/issues/34530
+ obj = frame_or_series(pd.array([1, 2, 3], dtype="Int64"))
+ result = obj.replace("", "") # no exception
+ # should not have changed dtype
+ tm.assert_equal(obj, result)
+
+ def test_replace_with_compiled_regex(self):
+ # https://github.com/pandas-dev/pandas/issues/35680
+ s = pd.Series(["a", "b", "c"])
+ regex = re.compile("^a$")
+ result = s.replace({regex: "z"}, regex=True)
+ expected = pd.Series(["z", "b", "c"])
+ tm.assert_series_equal(result, expected)
+
+ def test_pandas_replace_na(self):
+ # GH#43344
+ # GH#56599
+ ser = pd.Series(["AA", "BB", "CC", "DD", "EE", "", pd.NA, "AA"], dtype="string")
+ regex_mapping = {
+ "AA": "CC",
+ "BB": "CC",
+ "EE": "CC",
+ "CC": "CC-REPL",
+ }
+ result = ser.replace(regex_mapping, regex=True)
+ exp = pd.Series(
+ ["CC", "CC", "CC-REPL", "DD", "CC", "", pd.NA, "CC"], dtype="string"
+ )
+ tm.assert_series_equal(result, exp)
+
+ @pytest.mark.parametrize(
+ "dtype, input_data, to_replace, expected_data",
+ [
+ ("bool", [True, False], {True: False}, [False, False]),
+ ("int64", [1, 2], {1: 10, 2: 20}, [10, 20]),
+ ("Int64", [1, 2], {1: 10, 2: 20}, [10, 20]),
+ ("float64", [1.1, 2.2], {1.1: 10.1, 2.2: 20.5}, [10.1, 20.5]),
+ ("Float64", [1.1, 2.2], {1.1: 10.1, 2.2: 20.5}, [10.1, 20.5]),
+ ("string", ["one", "two"], {"one": "1", "two": "2"}, ["1", "2"]),
+ (
+ pd.IntervalDtype("int64"),
+ IntervalArray([pd.Interval(1, 2), pd.Interval(2, 3)]),
+ {pd.Interval(1, 2): pd.Interval(10, 20)},
+ IntervalArray([pd.Interval(10, 20), pd.Interval(2, 3)]),
+ ),
+ (
+ pd.IntervalDtype("float64"),
+ IntervalArray([pd.Interval(1.0, 2.7), pd.Interval(2.8, 3.1)]),
+ {pd.Interval(1.0, 2.7): pd.Interval(10.6, 20.8)},
+ IntervalArray([pd.Interval(10.6, 20.8), pd.Interval(2.8, 3.1)]),
+ ),
+ (
+ pd.PeriodDtype("M"),
+ [pd.Period("2020-05", freq="M")],
+ {pd.Period("2020-05", freq="M"): pd.Period("2020-06", freq="M")},
+ [pd.Period("2020-06", freq="M")],
+ ),
+ ],
+ )
+ def test_replace_dtype(self, dtype, input_data, to_replace, expected_data):
+ # GH#33484
+ ser = pd.Series(input_data, dtype=dtype)
+ result = ser.replace(to_replace)
+ expected = pd.Series(expected_data, dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_string_dtype(self):
+ # GH#40732, GH#44940
+ ser = pd.Series(["one", "two", np.nan], dtype="string")
+ res = ser.replace({"one": "1", "two": "2"})
+ expected = pd.Series(["1", "2", np.nan], dtype="string")
+ tm.assert_series_equal(res, expected)
+
+ # GH#31644
+ ser2 = pd.Series(["A", np.nan], dtype="string")
+ res2 = ser2.replace("A", "B")
+ expected2 = pd.Series(["B", np.nan], dtype="string")
+ tm.assert_series_equal(res2, expected2)
+
+ ser3 = pd.Series(["A", "B"], dtype="string")
+ res3 = ser3.replace("A", pd.NA)
+ expected3 = pd.Series([pd.NA, "B"], dtype="string")
+ tm.assert_series_equal(res3, expected3)
+
+ def test_replace_string_dtype_list_to_replace(self):
+ # GH#41215, GH#44940
+ ser = pd.Series(["abc", "def"], dtype="string")
+ res = ser.replace(["abc", "any other string"], "xyz")
+ expected = pd.Series(["xyz", "def"], dtype="string")
+ tm.assert_series_equal(res, expected)
+
+ def test_replace_string_dtype_regex(self):
+ # GH#31644
+ ser = pd.Series(["A", "B"], dtype="string")
+ res = ser.replace(r".", "C", regex=True)
+ expected = pd.Series(["C", "C"], dtype="string")
+ tm.assert_series_equal(res, expected)
+
+ def test_replace_nullable_numeric(self):
+ # GH#40732, GH#44940
+
+ floats = pd.Series([1.0, 2.0, 3.999, 4.4], dtype=pd.Float64Dtype())
+ assert floats.replace({1.0: 9}).dtype == floats.dtype
+ assert floats.replace(1.0, 9).dtype == floats.dtype
+ assert floats.replace({1.0: 9.0}).dtype == floats.dtype
+ assert floats.replace(1.0, 9.0).dtype == floats.dtype
+
+ res = floats.replace(to_replace=[1.0, 2.0], value=[9.0, 10.0])
+ assert res.dtype == floats.dtype
+
+ ints = pd.Series([1, 2, 3, 4], dtype=pd.Int64Dtype())
+ assert ints.replace({1: 9}).dtype == ints.dtype
+ assert ints.replace(1, 9).dtype == ints.dtype
+ assert ints.replace({1: 9.0}).dtype == ints.dtype
+ assert ints.replace(1, 9.0).dtype == ints.dtype
+
+ # nullable (for now) raises instead of casting
+ with pytest.raises(TypeError, match="Invalid value"):
+ ints.replace({1: 9.5})
+ with pytest.raises(TypeError, match="Invalid value"):
+ ints.replace(1, 9.5)
+
+ @pytest.mark.parametrize("regex", [False, True])
+ def test_replace_regex_dtype_series(self, regex):
+ # GH-48644
+ series = pd.Series(["0"], dtype=object)
+ expected = pd.Series([1], dtype=object)
+ result = series.replace(to_replace="0", value=1, regex=regex)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("regex", [False, True])
+ def test_replace_regex_dtype_series_string(self, regex):
+ series = pd.Series(["0"], dtype="str")
+ expected = pd.Series([1], dtype=object)
+ result = series.replace(to_replace="0", value=1, regex=regex)
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_different_int_types(self, any_int_numpy_dtype):
+ # GH#45311
+ labs = pd.Series([1, 1, 1, 0, 0, 2, 2, 2], dtype=any_int_numpy_dtype)
+
+ maps = pd.Series([0, 2, 1], dtype=any_int_numpy_dtype)
+ map_dict = dict(zip(maps.values, maps.index, strict=True))
+
+ result = labs.replace(map_dict)
+ expected = labs.replace({0: 0, 2: 1, 1: 2})
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("val", [2, np.nan, 2.0])
+ def test_replace_value_none_dtype_numeric(self, val):
+ # GH#48231
+ ser = pd.Series([1, val])
+ result = ser.replace(val, None)
+ expected = pd.Series([1, None], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_change_dtype_series(self):
+ # GH#25797
+ df = pd.DataFrame({"Test": ["0.5", True, "0.6"]}, dtype=object)
+ df["Test"] = df["Test"].replace([True], [np.nan])
+ expected = pd.DataFrame({"Test": ["0.5", np.nan, "0.6"]}, dtype=object)
+ tm.assert_frame_equal(df, expected)
+
+ df = pd.DataFrame({"Test": ["0.5", None, "0.6"]}, dtype=object)
+ df["Test"] = df["Test"].replace([None], [np.nan])
+ tm.assert_frame_equal(df, expected)
+
+ df = pd.DataFrame({"Test": ["0.5", None, "0.6"]}, dtype=object)
+ df["Test"] = df["Test"].fillna(np.nan)
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize("dtype", ["object", "Int64"])
+ def test_replace_na_in_obj_column(self, dtype):
+ # GH#47480
+ ser = pd.Series([0, 1, pd.NA], dtype=dtype)
+ expected = pd.Series([0, 2, pd.NA], dtype=dtype)
+ result = ser.replace(to_replace=1, value=2)
+ tm.assert_series_equal(result, expected)
+
+ result = ser.replace(to_replace=1, value=2, inplace=True)
+ assert result is ser
+ tm.assert_series_equal(ser, expected)
+
+ @pytest.mark.parametrize("val", [0, 0.5])
+ def test_replace_numeric_column_with_na(self, val):
+ # GH#50758
+ ser = pd.Series([val, 1])
+ expected = pd.Series([val, pd.NA])
+ result = ser.replace(to_replace=1, value=pd.NA)
+ tm.assert_series_equal(result, expected)
+
+ result = ser.replace(to_replace=1, value=pd.NA, inplace=True)
+ assert result is ser
+ tm.assert_series_equal(ser, expected)
+
+ def test_replace_ea_float_with_bool(self):
+ # GH#55398
+ ser = pd.Series([0.0], dtype="Float64")
+ expected = ser.copy()
+ result = ser.replace(False, 1.0)
+ tm.assert_series_equal(result, expected)
+
+ ser = pd.Series([False], dtype="boolean")
+ expected = ser.copy()
+ result = ser.replace(0.0, True)
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_all_NA(self):
+ # GH#60688
+ df = pd.Series([pd.NA, pd.NA])
+ result = df.replace({r"^#": "$"}, regex=True)
+ expected = pd.Series([pd.NA, pd.NA])
+ tm.assert_series_equal(result, expected)
+
+ def test_replace_mixed_types_with_none(self):
+ # GH#29813
+ df = pd.Series([np.nan, 1, "foo"])
+ result = df.replace({np.nan: None})
+ expected = pd.Series([None, 1, "foo"])
+ tm.assert_series_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_replace_from_index():
+ # https://github.com/pandas-dev/pandas/issues/61622
+ idx = pd.Index(["a", "b", "c"], dtype="string[pyarrow]")
+ expected = pd.Series(["d", "b", "c"], dtype="string[pyarrow]")
+ result = pd.Series(idx).replace({"z": "b", "a": "d"})
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_reset_index.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_reset_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..82de2349a6e98fd73ffb9571f1c64a54de63a5b1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_reset_index.py
@@ -0,0 +1,227 @@
+from datetime import datetime
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ RangeIndex,
+ Series,
+ date_range,
+ option_context,
+)
+import pandas._testing as tm
+
+
+class TestResetIndex:
+ def test_reset_index_dti_round_trip(self):
+ dti = date_range(
+ start="1/1/2001", end="6/1/2001", freq="D", unit="ns"
+ )._with_freq(None)
+ d1 = DataFrame({"v": np.random.default_rng(2).random(len(dti))}, index=dti)
+ d2 = d1.reset_index()
+ assert d2.dtypes.iloc[0] == np.dtype("M8[ns]")
+ d3 = d2.set_index("index")
+ tm.assert_frame_equal(d1, d3, check_names=False)
+
+ # GH#2329
+ stamp = datetime(2012, 11, 22)
+ df = DataFrame([[stamp, 12.1]], columns=["Date", "Value"])
+ df = df.set_index("Date")
+
+ assert df.index[0] == stamp
+ assert df.reset_index()["Date"].iloc[0] == stamp
+
+ def test_reset_index(self):
+ df = DataFrame(
+ 1.1 * np.arange(120).reshape((30, 4)),
+ columns=Index(list("ABCD"), dtype=object),
+ index=Index([f"i-{i}" for i in range(30)], dtype=object),
+ )[:5]
+ ser = df.stack()
+ ser.index.names = ["hash", "category"]
+
+ ser.name = "value"
+ df = ser.reset_index()
+ assert "value" in df
+
+ df = ser.reset_index(name="value2")
+ assert "value2" in df
+
+ # check inplace
+ s = ser.reset_index(drop=True)
+ s2 = ser
+ return_value = s2.reset_index(drop=True, inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(s, s2)
+
+ # level
+ index = MultiIndex(
+ levels=[["bar"], ["one", "two", "three"], [0, 1]],
+ codes=[[0, 0, 0, 0, 0, 0], [0, 1, 2, 0, 1, 2], [0, 1, 0, 1, 0, 1]],
+ )
+ s = Series(np.random.default_rng(2).standard_normal(6), index=index)
+ rs = s.reset_index(level=1)
+ assert len(rs.columns) == 2
+
+ rs = s.reset_index(level=[0, 2], drop=True)
+ tm.assert_index_equal(rs.index, Index(index.get_level_values(1)))
+ assert isinstance(rs, Series)
+
+ def test_reset_index_name(self):
+ s = Series([1, 2, 3], index=Index(range(3), name="x"))
+ assert s.reset_index().index.name is None
+ assert s.reset_index(drop=True).index.name is None
+
+ def test_reset_index_level(self):
+ df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=["A", "B", "C"])
+
+ for levels in ["A", "B"], [0, 1]:
+ # With MultiIndex
+ s = df.set_index(["A", "B"])["C"]
+
+ result = s.reset_index(level=levels[0])
+ tm.assert_frame_equal(result, df.set_index("B"))
+
+ result = s.reset_index(level=levels[:1])
+ tm.assert_frame_equal(result, df.set_index("B"))
+
+ result = s.reset_index(level=levels)
+ tm.assert_frame_equal(result, df)
+
+ result = df.set_index(["A", "B"]).reset_index(level=levels, drop=True)
+ tm.assert_frame_equal(result, df[["C"]])
+
+ with pytest.raises(KeyError, match="Level E "):
+ s.reset_index(level=["A", "E"])
+
+ # With single-level Index
+ s = df.set_index("A")["B"]
+
+ result = s.reset_index(level=levels[0])
+ tm.assert_frame_equal(result, df[["A", "B"]])
+
+ result = s.reset_index(level=levels[:1])
+ tm.assert_frame_equal(result, df[["A", "B"]])
+
+ result = s.reset_index(level=levels[0], drop=True)
+ tm.assert_series_equal(result, df["B"])
+
+ with pytest.raises(IndexError, match="Too many levels"):
+ s.reset_index(level=[0, 1, 2])
+
+ # Check that .reset_index([],drop=True) doesn't fail
+ result = Series(range(4)).reset_index([], drop=True)
+ expected = Series(range(4))
+ tm.assert_series_equal(result, expected)
+
+ def test_reset_index_range(self):
+ # GH 12071
+ s = Series(range(2), name="A", dtype="int64")
+ series_result = s.reset_index()
+ assert isinstance(series_result.index, RangeIndex)
+ series_expected = DataFrame(
+ [[0, 0], [1, 1]], columns=["index", "A"], index=RangeIndex(stop=2)
+ )
+ tm.assert_frame_equal(series_result, series_expected)
+
+ def test_reset_index_drop_errors(self):
+ # GH 20925
+
+ # KeyError raised for series index when passed level name is missing
+ s = Series(range(4))
+ with pytest.raises(KeyError, match="does not match index name"):
+ s.reset_index("wrong", drop=True)
+ with pytest.raises(KeyError, match="does not match index name"):
+ s.reset_index("wrong")
+
+ # KeyError raised for series when level to be dropped is missing
+ s = Series(range(4), index=MultiIndex.from_product([[1, 2]] * 2))
+ with pytest.raises(KeyError, match="not found"):
+ s.reset_index("wrong", drop=True)
+
+ def test_reset_index_with_drop(self):
+ arrays = [
+ ["bar", "bar", "baz", "baz", "qux", "qux", "foo", "foo"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ ]
+ tuples = zip(*arrays, strict=True)
+ index = MultiIndex.from_tuples(tuples)
+ data = np.random.default_rng(2).standard_normal(8)
+ ser = Series(data, index=index)
+ ser.iloc[3] = np.nan
+
+ deleveled = ser.reset_index()
+ assert isinstance(deleveled, DataFrame)
+ assert len(deleveled.columns) == len(ser.index.levels) + 1
+ assert deleveled.index.name == ser.index.name
+
+ deleveled = ser.reset_index(drop=True)
+ assert isinstance(deleveled, Series)
+ assert deleveled.index.name == ser.index.name
+
+ def test_reset_index_inplace_and_drop_ignore_name(self):
+ # GH#44575
+ ser = Series(range(2), name="old")
+ ser.reset_index(name="new", drop=True, inplace=True)
+ expected = Series(range(2), name="old")
+ tm.assert_series_equal(ser, expected)
+
+ def test_reset_index_drop_infer_string(self):
+ # GH#56160
+ pytest.importorskip("pyarrow")
+ ser = Series(["a", "b", "c"], dtype=object)
+ with option_context("future.infer_string", True):
+ result = ser.reset_index(drop=True)
+ tm.assert_series_equal(result, ser)
+
+
+@pytest.mark.parametrize(
+ "array, dtype",
+ [
+ (["a", "b"], object),
+ (
+ pd.period_range("12-1-2000", periods=2, freq="Q-DEC"),
+ pd.PeriodDtype(freq="Q-DEC"),
+ ),
+ ],
+)
+def test_reset_index_dtypes_on_empty_series_with_multiindex(
+ array, dtype, using_infer_string
+):
+ # GH 19602 - Preserve dtype on empty Series with MultiIndex
+ idx = MultiIndex.from_product([[0, 1], [0.5, 1.0], array])
+ result = Series(dtype=object, index=idx)[:0].reset_index().dtypes
+ exp = "str" if using_infer_string else object
+ expected = Series(
+ {
+ "level_0": np.int64,
+ "level_1": np.float64,
+ "level_2": exp if dtype == object else dtype,
+ 0: object,
+ }
+ )
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "names, expected_names",
+ [
+ (["A", "A"], ["A", "A"]),
+ (["level_1", None], ["level_1", "level_1"]),
+ ],
+)
+@pytest.mark.parametrize("allow_duplicates", [False, True])
+def test_column_name_duplicates(names, expected_names, allow_duplicates):
+ # GH#44755 reset_index with duplicate column labels
+ s = Series([1], index=MultiIndex.from_arrays([[1], [1]], names=names))
+ if allow_duplicates:
+ result = s.reset_index(allow_duplicates=True)
+ expected = DataFrame([[1, 1, 1]], columns=[*expected_names, 0])
+ tm.assert_frame_equal(result, expected)
+ else:
+ with pytest.raises(ValueError, match="cannot insert"):
+ s.reset_index()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_round.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_round.py
new file mode 100644
index 0000000000000000000000000000000000000000..46232863532294091be51a99b9708eff22c3b1ca
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_round.py
@@ -0,0 +1,119 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import Series
+import pandas._testing as tm
+
+
+class TestSeriesRound:
+ def test_round(self, datetime_series):
+ datetime_series.index.name = "index_name"
+ result = datetime_series.round(2)
+ expected = Series(
+ np.round(datetime_series.values, 2), index=datetime_series.index, name="ts"
+ )
+ tm.assert_series_equal(result, expected)
+ assert result.name == datetime_series.name
+
+ def test_round_numpy(self, any_float_dtype):
+ # See GH#12600
+ ser = Series([1.53, 1.36, 0.06], dtype=any_float_dtype)
+ out = np.round(ser, decimals=0)
+ expected = Series([2.0, 1.0, 0.0], dtype=any_float_dtype)
+ tm.assert_series_equal(out, expected)
+
+ msg = "the 'out' parameter is not supported"
+ with pytest.raises(ValueError, match=msg):
+ np.round(ser, decimals=0, out=ser)
+
+ def test_round_numpy_with_nan(self, any_float_dtype):
+ # See GH#14197
+ ser = Series([1.53, np.nan, 0.06], dtype=any_float_dtype)
+ with tm.assert_produces_warning(None):
+ result = ser.round()
+ expected = Series([2.0, np.nan, 0.0], dtype=any_float_dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_round_builtin(self, any_float_dtype):
+ ser = Series(
+ [1.123, 2.123, 3.123],
+ index=range(3),
+ dtype=any_float_dtype,
+ )
+ result = round(ser)
+ expected_rounded0 = Series(
+ [1.0, 2.0, 3.0], index=range(3), dtype=any_float_dtype
+ )
+ tm.assert_series_equal(result, expected_rounded0)
+
+ decimals = 2
+ expected_rounded = Series(
+ [1.12, 2.12, 3.12], index=range(3), dtype=any_float_dtype
+ )
+ result = round(ser, decimals)
+ tm.assert_series_equal(result, expected_rounded)
+
+ @pytest.mark.parametrize("method", ["round", "floor", "ceil"])
+ @pytest.mark.parametrize("freq", ["s", "5s", "min", "5min", "h", "5h"])
+ def test_round_nat(self, method, freq, unit):
+ # GH14940, GH#56158
+ ser = Series([pd.NaT], dtype=f"M8[{unit}]")
+ expected = Series(pd.NaT, dtype=f"M8[{unit}]")
+ round_method = getattr(ser.dt, method)
+ result = round_method(freq)
+ tm.assert_series_equal(result, expected)
+
+ def test_round_ea_boolean(self):
+ # GH#55936
+ ser = Series([True, False], dtype="boolean")
+ expected = ser.copy()
+ result = ser.round(2)
+ tm.assert_series_equal(result, expected)
+ result.iloc[0] = False
+ tm.assert_series_equal(ser, expected)
+
+ @pytest.mark.parametrize(
+ "data,decimals,expected_data",
+ [
+ ([0.2], 0, [0.0]),
+ ([1.234, 2.567], 1, [1.2, 2.6]),
+ ([1.234, 2.567], 2, [1.23, 2.57]),
+ ([1.234, 2.567], 0, [1.0, 3.0]),
+ ],
+ )
+ def test_round_dtype_object(self, data, decimals, expected_data):
+ # GH#63444
+ ser = Series(data, dtype="object")
+ result = ser.round(decimals)
+ expected = Series(expected_data, dtype="object")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype",
+ [
+ None,
+ "float64",
+ "int64",
+ "object",
+ ],
+ )
+ def test_round_empty_series(self, dtype):
+ # GH#63444
+ result = Series(dtype=dtype).round(4)
+ expected = Series(dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "dtype,data",
+ [
+ ("string", ["a", "b", "c"]),
+ ("category", ["cat1", "cat2", "cat3"]),
+ (pd.StringDtype(), ["x", "y", "z"]),
+ ],
+ )
+ def test_round_non_numeric_dtype_noop(self, dtype, data):
+ # GH#63444, GH#63559
+ ser = Series(data, dtype=dtype)
+ result = ser.round(2)
+ tm.assert_series_equal(result, ser)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_searchsorted.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_searchsorted.py
new file mode 100644
index 0000000000000000000000000000000000000000..239496052b99b42df262262a9ac89b71c93e0a26
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_searchsorted.py
@@ -0,0 +1,77 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+from pandas.api.types import is_scalar
+
+
+class TestSeriesSearchSorted:
+ def test_searchsorted(self):
+ ser = Series([1, 2, 3])
+
+ result = ser.searchsorted(1, side="left")
+ assert is_scalar(result)
+ assert result == 0
+
+ result = ser.searchsorted(1, side="right")
+ assert is_scalar(result)
+ assert result == 1
+
+ def test_searchsorted_numeric_dtypes_scalar(self):
+ ser = Series([1, 2, 90, 1000, 3e9])
+ res = ser.searchsorted(30)
+ assert is_scalar(res)
+ assert res == 2
+
+ res = ser.searchsorted([30])
+ exp = np.array([2], dtype=np.intp)
+ tm.assert_numpy_array_equal(res, exp)
+
+ def test_searchsorted_numeric_dtypes_vector(self):
+ ser = Series([1, 2, 90, 1000, 3e9])
+ res = ser.searchsorted([91, 2e6])
+ exp = np.array([3, 4], dtype=np.intp)
+ tm.assert_numpy_array_equal(res, exp)
+
+ def test_searchsorted_datetime64_scalar(self):
+ ser = Series(date_range("20120101", periods=10, freq="2D"))
+ val = Timestamp("20120102")
+ res = ser.searchsorted(val)
+ assert is_scalar(res)
+ assert res == 1
+
+ def test_searchsorted_datetime64_scalar_mixed_timezones(self):
+ # GH 30086
+ ser = Series(date_range("20120101", periods=10, freq="2D", tz="UTC"))
+ val = Timestamp("20120102", tz="America/New_York")
+ res = ser.searchsorted(val)
+ assert is_scalar(res)
+ assert res == 1
+
+ def test_searchsorted_datetime64_list(self):
+ ser = Series(date_range("20120101", periods=10, freq="2D"))
+ vals = [Timestamp("20120102"), Timestamp("20120104")]
+ res = ser.searchsorted(vals)
+ exp = np.array([1, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(res, exp)
+
+ def test_searchsorted_sorter(self):
+ # GH8490
+ ser = Series([3, 1, 2])
+ res = ser.searchsorted([0, 3], sorter=np.argsort(ser))
+ exp = np.array([0, 2], dtype=np.intp)
+ tm.assert_numpy_array_equal(res, exp)
+
+ def test_searchsorted_dataframe_fail(self):
+ # GH#49620
+ ser = Series([1, 2, 3, 4, 5])
+ vals = pd.DataFrame([[1, 2], [3, 4]])
+ msg = "Value must be 1-D array-like or scalar, DataFrame is not supported"
+ with pytest.raises(ValueError, match=msg):
+ ser.searchsorted(vals)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_set_name.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_set_name.py
new file mode 100644
index 0000000000000000000000000000000000000000..137207053c2258fd2db551d787a1bd70f09bf8ca
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_set_name.py
@@ -0,0 +1,21 @@
+from datetime import datetime
+
+from pandas import Series
+
+
+class TestSetName:
+ def test_set_name(self):
+ ser = Series([1, 2, 3])
+ ser2 = ser._set_name("foo")
+ assert ser2.name == "foo"
+ assert ser.name is None
+ assert ser is not ser2
+
+ def test_set_name_attribute(self):
+ ser = Series([1, 2, 3])
+ ser2 = Series([1, 2, 3], name="bar")
+ for name in [7, 7.0, "name", datetime(2001, 1, 1), (1,), "\u05d0"]:
+ ser.name = name
+ assert ser.name == name
+ ser2.name = name
+ assert ser2.name == name
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_size.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_size.py
new file mode 100644
index 0000000000000000000000000000000000000000..043e4b66dbf16d0163b07b272af23de0ce003e22
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_size.py
@@ -0,0 +1,20 @@
+import pytest
+
+from pandas import Series
+
+
+@pytest.mark.parametrize(
+ "data, index, expected",
+ [
+ ([1, 2, 3], None, 3),
+ ({"a": 1, "b": 2, "c": 3}, None, 3),
+ ([1, 2, 3], ["x", "y", "z"], 3),
+ ([1, 2, 3, 4, 5], ["x", "y", "z", "w", "n"], 5),
+ ([1, 2, 3, 4], ["x", "y", "z", "w"], 4),
+ ],
+)
+def test_series(data, index, expected):
+ # GH#52897
+ ser = Series(data, index=index)
+ assert ser.size == expected
+ assert isinstance(ser.size, int)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_sort_index.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_sort_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..861017f448195ef1a1e10f2ebdfac076972921b2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_sort_index.py
@@ -0,0 +1,337 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ DatetimeIndex,
+ IntervalIndex,
+ MultiIndex,
+ Series,
+)
+import pandas._testing as tm
+
+
+@pytest.fixture(params=["quicksort", "mergesort", "heapsort", "stable"])
+def sort_kind(request):
+ return request.param
+
+
+class TestSeriesSortIndex:
+ def test_sort_index_name(self, datetime_series):
+ result = datetime_series.sort_index(ascending=False)
+ assert result.name == datetime_series.name
+
+ def test_sort_index(self, datetime_series):
+ datetime_series.index = datetime_series.index._with_freq(None)
+
+ rindex = list(datetime_series.index)
+ np.random.default_rng(2).shuffle(rindex)
+
+ random_order = datetime_series.reindex(rindex)
+ sorted_series = random_order.sort_index()
+ tm.assert_series_equal(sorted_series, datetime_series)
+
+ # descending
+ sorted_series = random_order.sort_index(ascending=False)
+ tm.assert_series_equal(
+ sorted_series, datetime_series.reindex(datetime_series.index[::-1])
+ )
+
+ # compat on level
+ sorted_series = random_order.sort_index(level=0)
+ tm.assert_series_equal(sorted_series, datetime_series)
+
+ # compat on axis
+ sorted_series = random_order.sort_index(axis=0)
+ tm.assert_series_equal(sorted_series, datetime_series)
+
+ msg = "No axis named 1 for object type Series"
+ with pytest.raises(ValueError, match=msg):
+ random_order.sort_values(axis=1)
+
+ sorted_series = random_order.sort_index(level=0, axis=0)
+ tm.assert_series_equal(sorted_series, datetime_series)
+
+ with pytest.raises(ValueError, match=msg):
+ random_order.sort_index(level=0, axis=1)
+
+ def test_sort_index_inplace(self, datetime_series):
+ datetime_series.index = datetime_series.index._with_freq(None)
+
+ # For GH#11402
+ rindex = list(datetime_series.index)
+ np.random.default_rng(2).shuffle(rindex)
+
+ # descending
+ random_order = datetime_series.reindex(rindex)
+ result = random_order.sort_index(ascending=False, inplace=True)
+
+ assert result is None
+ expected = datetime_series.reindex(datetime_series.index[::-1])
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(random_order, expected)
+
+ # ascending
+ random_order = datetime_series.reindex(rindex)
+ result = random_order.sort_index(ascending=True, inplace=True)
+
+ assert result is None
+ expected = datetime_series.copy()
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(random_order, expected)
+
+ def test_sort_index_level(self):
+ mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list("ABC"))
+ s = Series([1, 2], mi)
+ backwards = s.iloc[[1, 0]]
+
+ res = s.sort_index(level="A")
+ tm.assert_series_equal(backwards, res)
+
+ res = s.sort_index(level=["A", "B"])
+ tm.assert_series_equal(backwards, res)
+
+ res = s.sort_index(level="A", sort_remaining=False)
+ tm.assert_series_equal(s, res)
+
+ res = s.sort_index(level=["A", "B"], sort_remaining=False)
+ tm.assert_series_equal(s, res)
+
+ @pytest.mark.parametrize("level", ["A", 0]) # GH#21052
+ def test_sort_index_multiindex(self, level):
+ mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list("ABC"))
+ s = Series([1, 2], mi)
+ backwards = s.iloc[[1, 0]]
+
+ # implicit sort_remaining=True
+ res = s.sort_index(level=level)
+ tm.assert_series_equal(backwards, res)
+
+ # GH#13496
+ # sort has no effect without remaining lvls
+ res = s.sort_index(level=level, sort_remaining=False)
+ tm.assert_series_equal(s, res)
+
+ def test_sort_index_kind(self, sort_kind):
+ # GH#14444 & GH#13589: Add support for sort algo choosing
+ series = Series(index=[3, 2, 1, 4, 3], dtype=object)
+ expected_series = Series(index=[1, 2, 3, 3, 4], dtype=object)
+
+ index_sorted_series = series.sort_index(kind=sort_kind)
+ tm.assert_series_equal(expected_series, index_sorted_series)
+
+ def test_sort_index_na_position(self):
+ series = Series(index=[3, 2, 1, 4, 3, np.nan], dtype=object)
+ expected_series_first = Series(index=[np.nan, 1, 2, 3, 3, 4], dtype=object)
+
+ index_sorted_series = series.sort_index(na_position="first")
+ tm.assert_series_equal(expected_series_first, index_sorted_series)
+
+ expected_series_last = Series(index=[1, 2, 3, 3, 4, np.nan], dtype=object)
+
+ index_sorted_series = series.sort_index(na_position="last")
+ tm.assert_series_equal(expected_series_last, index_sorted_series)
+
+ def test_sort_index_intervals(self):
+ s = Series(
+ [np.nan, 1, 2, 3], IntervalIndex.from_arrays([0, 1, 2, 3], [1, 2, 3, 4])
+ )
+
+ result = s.sort_index()
+ expected = s
+ tm.assert_series_equal(result, expected)
+
+ result = s.sort_index(ascending=False)
+ expected = Series(
+ [3, 2, 1, np.nan], IntervalIndex.from_arrays([3, 2, 1, 0], [4, 3, 2, 1])
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("inplace", [True, False])
+ @pytest.mark.parametrize(
+ "original_list, sorted_list, ascending, ignore_index, output_index",
+ [
+ ([2, 3, 6, 1], [2, 3, 6, 1], True, True, [0, 1, 2, 3]),
+ ([2, 3, 6, 1], [2, 3, 6, 1], True, False, [0, 1, 2, 3]),
+ ([2, 3, 6, 1], [1, 6, 3, 2], False, True, [0, 1, 2, 3]),
+ ([2, 3, 6, 1], [1, 6, 3, 2], False, False, [3, 2, 1, 0]),
+ ],
+ )
+ def test_sort_index_ignore_index(
+ self, inplace, original_list, sorted_list, ascending, ignore_index, output_index
+ ):
+ # GH 30114
+ ser = Series(original_list)
+ expected = Series(sorted_list, index=output_index)
+ kwargs = {
+ "ascending": ascending,
+ "ignore_index": ignore_index,
+ "inplace": inplace,
+ }
+
+ if inplace:
+ result_ser = ser.copy()
+ result_ser.sort_index(**kwargs)
+ else:
+ result_ser = ser.sort_index(**kwargs)
+
+ tm.assert_series_equal(result_ser, expected)
+ tm.assert_series_equal(ser, Series(original_list))
+
+ def test_sort_index_ascending_list(self):
+ # GH#16934
+
+ # Set up a Series with a three level MultiIndex
+ arrays = [
+ ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
+ ["one", "two", "one", "two", "one", "two", "one", "two"],
+ [4, 3, 2, 1, 4, 3, 2, 1],
+ ]
+ tuples = zip(*arrays, strict=True)
+ mi = MultiIndex.from_tuples(tuples, names=["first", "second", "third"])
+ ser = Series(range(8), index=mi)
+
+ # Sort with boolean ascending
+ result = ser.sort_index(level=["third", "first"], ascending=False)
+ expected = ser.iloc[[4, 0, 5, 1, 6, 2, 7, 3]]
+ tm.assert_series_equal(result, expected)
+
+ # Sort with list of boolean ascending
+ result = ser.sort_index(level=["third", "first"], ascending=[False, True])
+ expected = ser.iloc[[0, 4, 1, 5, 2, 6, 3, 7]]
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "ascending",
+ [
+ None,
+ (True, None),
+ (False, "True"),
+ ],
+ )
+ def test_sort_index_ascending_bad_value_raises(self, ascending):
+ ser = Series(range(10), index=[0, 3, 2, 1, 4, 5, 7, 6, 8, 9])
+ match = 'For argument "ascending" expected type bool'
+ with pytest.raises(ValueError, match=match):
+ ser.sort_index(ascending=ascending)
+
+
+class TestSeriesSortIndexKey:
+ def test_sort_index_multiindex_key(self):
+ mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list("ABC"))
+ s = Series([1, 2], mi)
+ backwards = s.iloc[[1, 0]]
+
+ result = s.sort_index(level="C", key=lambda x: -x)
+ tm.assert_series_equal(s, result)
+
+ result = s.sort_index(level="C", key=lambda x: x) # nothing happens
+ tm.assert_series_equal(backwards, result)
+
+ def test_sort_index_multiindex_key_multi_level(self):
+ mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list("ABC"))
+ s = Series([1, 2], mi)
+ backwards = s.iloc[[1, 0]]
+
+ result = s.sort_index(level=["A", "C"], key=lambda x: -x)
+ tm.assert_series_equal(s, result)
+
+ result = s.sort_index(level=["A", "C"], key=lambda x: x) # nothing happens
+ tm.assert_series_equal(backwards, result)
+
+ def test_sort_index_key(self):
+ series = Series(np.arange(6, dtype="int64"), index=list("aaBBca"))
+
+ result = series.sort_index()
+ expected = series.iloc[[2, 3, 0, 1, 5, 4]]
+ tm.assert_series_equal(result, expected)
+
+ result = series.sort_index(key=lambda x: x.str.lower())
+ expected = series.iloc[[0, 1, 5, 2, 3, 4]]
+ tm.assert_series_equal(result, expected)
+
+ result = series.sort_index(key=lambda x: x.str.lower(), ascending=False)
+ expected = series.iloc[[4, 2, 3, 0, 1, 5]]
+ tm.assert_series_equal(result, expected)
+
+ def test_sort_index_key_int(self):
+ series = Series(np.arange(6, dtype="int64"), index=np.arange(6, dtype="int64"))
+
+ result = series.sort_index()
+ tm.assert_series_equal(result, series)
+
+ result = series.sort_index(key=lambda x: -x)
+ expected = series.sort_index(ascending=False)
+ tm.assert_series_equal(result, expected)
+
+ result = series.sort_index(key=lambda x: 2 * x)
+ tm.assert_series_equal(result, series)
+
+ def test_sort_index_kind_key(self, sort_kind, sort_by_key):
+ # GH #14444 & #13589: Add support for sort algo choosing
+ series = Series(index=[3, 2, 1, 4, 3], dtype=object)
+ expected_series = Series(index=[1, 2, 3, 3, 4], dtype=object)
+
+ index_sorted_series = series.sort_index(kind=sort_kind, key=sort_by_key)
+ tm.assert_series_equal(expected_series, index_sorted_series)
+
+ def test_sort_index_kind_neg_key(self, sort_kind):
+ # GH #14444 & #13589: Add support for sort algo choosing
+ series = Series(index=[3, 2, 1, 4, 3], dtype=object)
+ expected_series = Series(index=[4, 3, 3, 2, 1], dtype=object)
+
+ index_sorted_series = series.sort_index(kind=sort_kind, key=lambda x: -x)
+ tm.assert_series_equal(expected_series, index_sorted_series)
+
+ def test_sort_index_na_position_key(self, sort_by_key):
+ series = Series(index=[3, 2, 1, 4, 3, np.nan], dtype=object)
+ expected_series_first = Series(index=[np.nan, 1, 2, 3, 3, 4], dtype=object)
+
+ index_sorted_series = series.sort_index(na_position="first", key=sort_by_key)
+ tm.assert_series_equal(expected_series_first, index_sorted_series)
+
+ expected_series_last = Series(index=[1, 2, 3, 3, 4, np.nan], dtype=object)
+
+ index_sorted_series = series.sort_index(na_position="last", key=sort_by_key)
+ tm.assert_series_equal(expected_series_last, index_sorted_series)
+
+ def test_changes_length_raises(self):
+ s = Series([1, 2, 3])
+ with pytest.raises(ValueError, match="change the shape"):
+ s.sort_index(key=lambda x: x[:1])
+
+ def test_sort_values_key_type(self):
+ s = Series([1, 2, 3], DatetimeIndex(["2008-10-24", "2008-11-23", "2007-12-22"]))
+
+ result = s.sort_index(key=lambda x: x.month)
+ expected = s.iloc[[0, 1, 2]]
+ tm.assert_series_equal(result, expected)
+
+ result = s.sort_index(key=lambda x: x.day)
+ expected = s.iloc[[2, 1, 0]]
+ tm.assert_series_equal(result, expected)
+
+ result = s.sort_index(key=lambda x: x.year)
+ expected = s.iloc[[2, 0, 1]]
+ tm.assert_series_equal(result, expected)
+
+ result = s.sort_index(key=lambda x: x.month_name())
+ expected = s.iloc[[2, 1, 0]]
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "ascending",
+ [
+ [True, False],
+ [False, True],
+ ],
+ )
+ def test_sort_index_multi_already_monotonic(self, ascending):
+ # GH 56049
+ mi = MultiIndex.from_product([[1, 2], [3, 4]])
+ ser = Series(range(len(mi)), index=mi)
+ result = ser.sort_index(ascending=ascending)
+ if ascending == [True, False]:
+ expected = ser.take([1, 0, 3, 2])
+ elif ascending == [False, True]:
+ expected = ser.take([2, 3, 0, 1])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_sort_values.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_sort_values.py
new file mode 100644
index 0000000000000000000000000000000000000000..995d9d176fc2b3ff79072f87eefcb656d17bc2b5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_sort_values.py
@@ -0,0 +1,235 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ Categorical,
+ DataFrame,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestSeriesSortValues:
+ def test_sort_values(self, datetime_series):
+ # check indexes are reordered corresponding with the values
+ ser = Series([3, 2, 4, 1], ["A", "B", "C", "D"])
+ expected = Series([1, 2, 3, 4], ["D", "B", "A", "C"])
+ result = ser.sort_values()
+ tm.assert_series_equal(expected, result)
+
+ ts = datetime_series.copy()
+ ts[:5] = np.nan
+ vals = ts.values
+
+ result = ts.sort_values()
+ assert np.isnan(result[-5:]).all()
+ tm.assert_numpy_array_equal(result[:-5].values, np.sort(vals[5:]))
+
+ # na_position
+ result = ts.sort_values(na_position="first")
+ assert np.isnan(result[:5]).all()
+ tm.assert_numpy_array_equal(result[5:].values, np.sort(vals[5:]))
+
+ # something object-type
+ ser = Series(["A", "B"], [1, 2])
+ # no failure
+ ser.sort_values()
+
+ # ascending=False
+ ordered = ts.sort_values(ascending=False)
+ expected = np.sort(ts.dropna().values)[::-1]
+ tm.assert_almost_equal(expected, ordered.dropna().values)
+ ordered = ts.sort_values(ascending=False, na_position="first")
+ tm.assert_almost_equal(expected, ordered.dropna().values)
+
+ # ascending=[False] should behave the same as ascending=False
+ ordered = ts.sort_values(ascending=[False])
+ expected = ts.sort_values(ascending=False)
+ tm.assert_series_equal(expected, ordered)
+ ordered = ts.sort_values(ascending=[False], na_position="first")
+ expected = ts.sort_values(ascending=False, na_position="first")
+ tm.assert_series_equal(expected, ordered)
+
+ msg = 'For argument "ascending" expected type bool, received type NoneType.'
+ with pytest.raises(ValueError, match=msg):
+ ts.sort_values(ascending=None)
+ msg = r"Length of ascending \(0\) must be 1 for Series"
+ with pytest.raises(ValueError, match=msg):
+ ts.sort_values(ascending=[])
+ msg = r"Length of ascending \(3\) must be 1 for Series"
+ with pytest.raises(ValueError, match=msg):
+ ts.sort_values(ascending=[1, 2, 3])
+ msg = r"Length of ascending \(2\) must be 1 for Series"
+ with pytest.raises(ValueError, match=msg):
+ ts.sort_values(ascending=[False, False])
+ msg = 'For argument "ascending" expected type bool, received type str.'
+ with pytest.raises(ValueError, match=msg):
+ ts.sort_values(ascending="foobar")
+
+ # inplace=True
+ ts = datetime_series.copy()
+ return_value = ts.sort_values(ascending=False, inplace=True)
+ assert return_value is None
+ tm.assert_series_equal(ts, datetime_series.sort_values(ascending=False))
+ tm.assert_index_equal(
+ ts.index, datetime_series.sort_values(ascending=False).index
+ )
+
+ # GH#5856/5853
+ # Series.sort_values operating on a view
+ df = DataFrame(np.random.default_rng(2).standard_normal((10, 4)))
+ s = df.iloc[:, 0]
+ s.sort_values(inplace=True)
+ tm.assert_series_equal(s, df.iloc[:, 0].sort_values())
+
+ def test_sort_values_categorical(self):
+ cat = Series(Categorical(["a", "b", "b", "a"], ordered=False))
+
+ # sort in the categories order
+ expected = Series(
+ Categorical(["a", "a", "b", "b"], ordered=False), index=[0, 3, 1, 2]
+ )
+ result = cat.sort_values()
+ tm.assert_series_equal(result, expected)
+
+ cat = Series(Categorical(["a", "c", "b", "d"], ordered=True))
+ res = cat.sort_values()
+ exp = np.array(["a", "b", "c", "d"], dtype=np.object_)
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+
+ cat = Series(
+ Categorical(
+ ["a", "c", "b", "d"], categories=["a", "b", "c", "d"], ordered=True
+ )
+ )
+ res = cat.sort_values()
+ exp = np.array(["a", "b", "c", "d"], dtype=np.object_)
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+
+ res = cat.sort_values(ascending=False)
+ exp = np.array(["d", "c", "b", "a"], dtype=np.object_)
+ tm.assert_numpy_array_equal(res.__array__(), exp)
+
+ raw_cat1 = Categorical(
+ ["a", "b", "c", "d"], categories=["a", "b", "c", "d"], ordered=False
+ )
+ raw_cat2 = Categorical(
+ ["a", "b", "c", "d"], categories=["d", "c", "b", "a"], ordered=True
+ )
+ s = ["a", "b", "c", "d"]
+ df = DataFrame(
+ {"unsort": raw_cat1, "sort": raw_cat2, "string": s, "values": [1, 2, 3, 4]}
+ )
+
+ # Cats must be sorted in a dataframe
+ res = df.sort_values(by=["string"], ascending=False)
+ exp = np.array(["d", "c", "b", "a"], dtype=np.object_)
+ tm.assert_numpy_array_equal(res["sort"].values.__array__(), exp)
+ assert res["sort"].dtype == "category"
+
+ res = df.sort_values(by=["sort"], ascending=False)
+ exp = df.sort_values(by=["string"], ascending=True)
+ tm.assert_series_equal(res["values"], exp["values"])
+ assert res["sort"].dtype == "category"
+ assert res["unsort"].dtype == "category"
+
+ # unordered cat, but we allow this
+ df.sort_values(by=["unsort"], ascending=False)
+
+ # multi-columns sort
+ # GH#7848
+ df = DataFrame(
+ {"id": [6, 5, 4, 3, 2, 1], "raw_grade": ["a", "b", "b", "a", "a", "e"]}
+ )
+ df["grade"] = Categorical(df["raw_grade"], ordered=True)
+ df["grade"] = df["grade"].cat.set_categories(["b", "e", "a"])
+
+ # sorts 'grade' according to the order of the categories
+ result = df.sort_values(by=["grade"])
+ expected = df.iloc[[1, 2, 5, 0, 3, 4]]
+ tm.assert_frame_equal(result, expected)
+
+ # multi
+ result = df.sort_values(by=["grade", "id"])
+ expected = df.iloc[[2, 1, 5, 4, 3, 0]]
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("inplace", [True, False])
+ @pytest.mark.parametrize(
+ "original_list, sorted_list, ignore_index, output_index",
+ [
+ ([2, 3, 6, 1], [6, 3, 2, 1], True, [0, 1, 2, 3]),
+ ([2, 3, 6, 1], [6, 3, 2, 1], False, [2, 1, 0, 3]),
+ ],
+ )
+ def test_sort_values_ignore_index(
+ self, inplace, original_list, sorted_list, ignore_index, output_index
+ ):
+ # GH 30114
+ ser = Series(original_list)
+ expected = Series(sorted_list, index=output_index)
+ kwargs = {"ignore_index": ignore_index, "inplace": inplace}
+
+ if inplace:
+ result_ser = ser.copy()
+ result_ser.sort_values(ascending=False, **kwargs)
+ else:
+ result_ser = ser.sort_values(ascending=False, **kwargs)
+
+ tm.assert_series_equal(result_ser, expected)
+ tm.assert_series_equal(ser, Series(original_list))
+
+ def test_mergesort_descending_stability(self):
+ # GH 28697
+ s = Series([1, 2, 1, 3], ["first", "b", "second", "c"])
+ result = s.sort_values(ascending=False, kind="mergesort")
+ expected = Series([3, 2, 1, 1], ["c", "b", "first", "second"])
+ tm.assert_series_equal(result, expected)
+
+ def test_sort_values_validate_ascending_for_value_error(self):
+ # GH41634
+ ser = Series([23, 7, 21])
+
+ msg = 'For argument "ascending" expected type bool, received type str.'
+ with pytest.raises(ValueError, match=msg):
+ ser.sort_values(ascending="False")
+
+ def test_sort_values_validate_ascending_functional(self, ascending):
+ # GH41634
+ ser = Series([23, 7, 21])
+ expected = np.sort(ser.values)
+
+ sorted_ser = ser.sort_values(ascending=ascending)
+ if not ascending:
+ expected = expected[::-1]
+
+ result = sorted_ser.values
+ tm.assert_numpy_array_equal(result, expected)
+
+
+class TestSeriesSortingKey:
+ def test_sort_values_key(self):
+ series = Series(np.array(["Hello", "goodbye"]))
+
+ result = series.sort_values(axis=0)
+ expected = series
+ tm.assert_series_equal(result, expected)
+
+ result = series.sort_values(axis=0, key=lambda x: x.str.lower())
+ expected = series[::-1]
+ tm.assert_series_equal(result, expected)
+
+ def test_sort_values_key_nan(self):
+ series = Series(np.array([0, 5, np.nan, 3, 2, np.nan]))
+
+ result = series.sort_values(axis=0)
+ expected = series.iloc[[0, 4, 3, 1, 2, 5]]
+ tm.assert_series_equal(result, expected)
+
+ result = series.sort_values(axis=0, key=lambda x: x + 5)
+ expected = series.iloc[[0, 4, 3, 1, 2, 5]]
+ tm.assert_series_equal(result, expected)
+
+ result = series.sort_values(axis=0, key=lambda x: -x, ascending=False)
+ expected = series.iloc[[0, 4, 3, 1, 2, 5]]
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_csv.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_csv.py
new file mode 100644
index 0000000000000000000000000000000000000000..d67d0b5235aba66e4f52fae11e79fee0be701277
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_csv.py
@@ -0,0 +1,176 @@
+from datetime import datetime
+from io import StringIO
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import Series
+import pandas._testing as tm
+
+from pandas.io.common import get_handle
+
+
+class TestSeriesToCSV:
+ def read_csv(self, path, **kwargs):
+ params = {"index_col": 0, "header": None}
+ params.update(**kwargs)
+
+ header = params.get("header")
+ out = pd.read_csv(path, **params).squeeze("columns")
+
+ if header is None:
+ out.name = out.index.name = None
+
+ return out
+
+ def test_from_csv(self, datetime_series, string_series, temp_file):
+ # freq doesn't round-trip
+ datetime_series.index = datetime_series.index._with_freq(None)
+
+ path = temp_file
+ datetime_series.to_csv(path, header=False)
+ ts = self.read_csv(path, parse_dates=True)
+ expected = datetime_series.copy()
+ expected.index = expected.index.as_unit("us")
+ tm.assert_series_equal(expected, ts, check_names=False)
+
+ assert ts.name is None
+ assert ts.index.name is None
+
+ # see gh-10483
+ datetime_series.to_csv(path, header=True)
+ ts_h = self.read_csv(path, header=0)
+ assert ts_h.name == "ts"
+
+ string_series.to_csv(path, header=False)
+ series = self.read_csv(path)
+ tm.assert_series_equal(string_series, series, check_names=False)
+
+ assert series.name is None
+ assert series.index.name is None
+
+ string_series.to_csv(path, header=True)
+ series_h = self.read_csv(path, header=0)
+ assert series_h.name == "series"
+
+ with open(path, "w", encoding="utf-8") as outfile:
+ outfile.write("1998-01-01|1.0\n1999-01-01|2.0")
+
+ series = self.read_csv(path, sep="|", parse_dates=True)
+ check_series = Series({datetime(1998, 1, 1): 1.0, datetime(1999, 1, 1): 2.0})
+ tm.assert_series_equal(check_series, series)
+
+ series = self.read_csv(path, sep="|", parse_dates=False)
+ check_series = Series({"1998-01-01": 1.0, "1999-01-01": 2.0})
+ tm.assert_series_equal(check_series, series)
+
+ def test_to_csv(self, datetime_series, temp_file):
+ datetime_series.to_csv(temp_file, header=False)
+
+ with open(temp_file, newline=None, encoding="utf-8") as f:
+ lines = f.readlines()
+ assert lines[1] != "\n"
+
+ datetime_series.to_csv(temp_file, index=False, header=False)
+ arr = np.loadtxt(temp_file)
+ tm.assert_almost_equal(arr, datetime_series.values)
+
+ def test_to_csv_unicode_index(self):
+ buf = StringIO()
+ s = Series(["\u05d0", "d2"], index=["\u05d0", "\u05d1"])
+
+ s.to_csv(buf, encoding="UTF-8", header=False)
+ buf.seek(0)
+
+ s2 = self.read_csv(buf, index_col=0, encoding="UTF-8")
+ tm.assert_series_equal(s, s2)
+
+ def test_to_csv_float_format(self, temp_file):
+ ser = Series([0.123456, 0.234567, 0.567567])
+ ser.to_csv(temp_file, float_format="%.2f", header=False)
+
+ rs = self.read_csv(temp_file)
+ xp = Series([0.12, 0.23, 0.57])
+ tm.assert_series_equal(rs, xp)
+
+ def test_to_csv_list_entries(self):
+ s = Series(["jack and jill", "jesse and frank"])
+
+ split = s.str.split(r"\s+and\s+")
+
+ buf = StringIO()
+ split.to_csv(buf, header=False)
+
+ def test_to_csv_path_is_none(self):
+ # GH 8215
+ # Series.to_csv() was returning None, inconsistent with
+ # DataFrame.to_csv() which returned string
+ s = Series([1, 2, 3])
+ csv_str = s.to_csv(path_or_buf=None, header=False)
+ assert isinstance(csv_str, str)
+
+ @pytest.mark.parametrize(
+ "s,encoding",
+ [
+ (
+ Series([0.123456, 0.234567, 0.567567], index=["A", "B", "C"], name="X"),
+ None,
+ ),
+ # GH 21241, 21118
+ (Series(["abc", "def", "ghi"], name="X"), "ascii"),
+ (Series(["123", "你好", "世界"], name="中文"), "gb2312"),
+ (
+ Series(["123", "Γειά σου", "Κόσμε"], name="Ελληνικά"), # noqa: RUF001
+ "cp737",
+ ),
+ ],
+ )
+ def test_to_csv_compression(self, s, encoding, compression, temp_file):
+ filename = temp_file
+ s.to_csv(filename, compression=compression, encoding=encoding, header=True)
+ # test the round trip - to_csv -> read_csv
+ result = pd.read_csv(
+ filename,
+ compression=compression,
+ encoding=encoding,
+ index_col=0,
+ ).squeeze("columns")
+ tm.assert_series_equal(s, result)
+
+ # test the round trip using file handle - to_csv -> read_csv
+ with get_handle(
+ filename, "w", compression=compression, encoding=encoding
+ ) as handles:
+ s.to_csv(handles.handle, encoding=encoding, header=True)
+
+ result = pd.read_csv(
+ filename,
+ compression=compression,
+ encoding=encoding,
+ index_col=0,
+ ).squeeze("columns")
+ tm.assert_series_equal(s, result)
+
+ # explicitly ensure file was compressed
+ with tm.decompress_file(filename, compression) as fh:
+ text = fh.read().decode(encoding or "utf8")
+ assert s.name in text
+
+ with tm.decompress_file(filename, compression) as fh:
+ tm.assert_series_equal(
+ s,
+ pd.read_csv(fh, index_col=0, encoding=encoding).squeeze("columns"),
+ )
+
+ def test_to_csv_interval_index(self, using_infer_string, temp_file):
+ # GH 28210
+ s = Series(["foo", "bar", "baz"], index=pd.interval_range(0, 3))
+
+ s.to_csv(temp_file, header=False)
+ result = self.read_csv(temp_file, index_col=0)
+
+ # can't roundtrip intervalindex via read_csv so check string repr (GH 23595)
+ expected = s
+ expected.index = expected.index.astype("str")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_dict.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_dict.py
new file mode 100644
index 0000000000000000000000000000000000000000..41c01f4537f23fdb90110685d2e774427916aee1
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_dict.py
@@ -0,0 +1,38 @@
+import collections
+
+import numpy as np
+import pytest
+
+from pandas import Series
+import pandas._testing as tm
+
+
+class TestSeriesToDict:
+ @pytest.mark.parametrize(
+ "mapping", (dict, collections.defaultdict(list), collections.OrderedDict)
+ )
+ def test_to_dict(self, mapping, datetime_series):
+ # GH#16122
+ result = Series(datetime_series.to_dict(into=mapping), name="ts")
+ expected = datetime_series.copy()
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+
+ from_method = Series(datetime_series.to_dict(into=collections.Counter))
+ from_constructor = Series(collections.Counter(datetime_series.items()))
+ tm.assert_series_equal(from_method, from_constructor)
+
+ @pytest.mark.parametrize(
+ "input",
+ (
+ {"a": np.int64(64), "b": 10},
+ {"a": np.int64(64), "b": 10, "c": "ABC"},
+ {"a": np.uint64(64), "b": 10, "c": "ABC"},
+ ),
+ )
+ def test_to_dict_return_types(self, input):
+ # GH25969
+
+ d = Series(input).to_dict()
+ assert isinstance(d["a"], int)
+ assert isinstance(d["b"], int)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_frame.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_frame.py
new file mode 100644
index 0000000000000000000000000000000000000000..0eadf696b34cc034d2be76ce5daba2cff679da74
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_frame.py
@@ -0,0 +1,63 @@
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestToFrame:
+ def test_to_frame_respects_name_none(self):
+ # GH#44212 if we explicitly pass name=None, then that should be respected,
+ # not changed to 0
+ # GH-45448 this is first deprecated & enforced in 2.0
+ ser = Series(range(3))
+ result = ser.to_frame(None)
+
+ exp_index = Index([None], dtype=object)
+ tm.assert_index_equal(result.columns, exp_index)
+
+ result = ser.rename("foo").to_frame(None)
+ exp_index = Index([None], dtype=object)
+ tm.assert_index_equal(result.columns, exp_index)
+
+ def test_to_frame(self, datetime_series):
+ datetime_series.name = None
+ rs = datetime_series.to_frame()
+ xp = DataFrame(datetime_series.values, index=datetime_series.index)
+ tm.assert_frame_equal(rs, xp)
+
+ datetime_series.name = "testname"
+ rs = datetime_series.to_frame()
+ xp = DataFrame(
+ {"testname": datetime_series.values}, index=datetime_series.index
+ )
+ tm.assert_frame_equal(rs, xp)
+
+ rs = datetime_series.to_frame(name="testdifferent")
+ xp = DataFrame(
+ {"testdifferent": datetime_series.values}, index=datetime_series.index
+ )
+ tm.assert_frame_equal(rs, xp)
+
+ @pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
+ )
+ def test_to_frame_expanddim(self):
+ # GH#9762
+
+ class SubclassedSeries(Series):
+ @property
+ def _constructor_expanddim(self):
+ return SubclassedFrame
+
+ class SubclassedFrame(DataFrame):
+ pass
+
+ ser = SubclassedSeries([1, 2, 3], name="X")
+ result = ser.to_frame()
+ assert isinstance(result, SubclassedFrame)
+ expected = SubclassedFrame({"X": [1, 2, 3]})
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_numpy.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_numpy.py
new file mode 100644
index 0000000000000000000000000000000000000000..4bc7631090761e720c61049f9b8fd2a7fadd89af
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_to_numpy.py
@@ -0,0 +1,49 @@
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas import (
+ NA,
+ Series,
+ Timedelta,
+)
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("dtype", ["int64", "float64"])
+def test_to_numpy_na_value(dtype):
+ # GH#48951
+ ser = Series([1, 2, NA, 4])
+ result = ser.to_numpy(dtype=dtype, na_value=0)
+ expected = np.array([1, 2, 0, 4], dtype=dtype)
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_to_numpy_cast_before_setting_na():
+ # GH#50600
+ ser = Series([1])
+ result = ser.to_numpy(dtype=np.float64, na_value=np.nan)
+ expected = np.array([1.0])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_to_numpy_arrow_dtype_given():
+ # GH#57121
+ ser = Series([1, NA], dtype="int64[pyarrow]")
+ result = ser.to_numpy(dtype="float64")
+ expected = np.array([1.0, np.nan])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_astype_ea_int_to_td_ts():
+ # GH#57093
+ ser = Series([1, None], dtype="Int64")
+ result = ser.astype("m8[ns]")
+ expected = Series([1, Timedelta("nat")], dtype="m8[ns]")
+ tm.assert_series_equal(result, expected)
+
+ result = ser.astype("M8[ns]")
+ expected = Series([1, Timedelta("nat")], dtype="M8[ns]")
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_tolist.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_tolist.py
new file mode 100644
index 0000000000000000000000000000000000000000..4af473528e23850794139ac563cc04c6d3c54617
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_tolist.py
@@ -0,0 +1,36 @@
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas import (
+ Interval,
+ Period,
+ Series,
+ Timedelta,
+ Timestamp,
+)
+
+
+@pytest.mark.parametrize(
+ "values, dtype, expected_dtype",
+ (
+ ([1], "int64", int),
+ ([1], "Int64", int),
+ ([1.0], "float64", float),
+ ([1.0], "Float64", float),
+ (["abc"], "object", str),
+ (["abc"], "string", str),
+ ([Interval(1, 3)], "interval", Interval),
+ ([Period("2000-01-01", "D")], "period[D]", Period),
+ ([Timedelta(days=1)], "timedelta64[ns]", Timedelta),
+ ([Timestamp("2000-01-01")], "datetime64[ns]", Timestamp),
+ pytest.param([1], "int64[pyarrow]", int, marks=td.skip_if_no("pyarrow")),
+ pytest.param([1.0], "float64[pyarrow]", float, marks=td.skip_if_no("pyarrow")),
+ pytest.param(["abc"], "string[pyarrow]", str, marks=td.skip_if_no("pyarrow")),
+ ),
+)
+def test_tolist_scalar_dtype(values, dtype, expected_dtype):
+ # GH49890
+ ser = Series(values, dtype=dtype)
+ result_dtype = type(ser.tolist()[0])
+ assert result_dtype == expected_dtype
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_truncate.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_truncate.py
new file mode 100644
index 0000000000000000000000000000000000000000..33eb5c10ae163862e342b1871669d64d74602e4e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_truncate.py
@@ -0,0 +1,67 @@
+from datetime import datetime
+
+import pytest
+
+import pandas as pd
+from pandas import (
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestTruncate:
+ def test_truncate_datetimeindex_tz(self):
+ # GH 9243
+ idx = date_range("4/1/2005", "4/30/2005", freq="D", tz="US/Pacific")
+ s = Series(range(len(idx)), index=idx)
+ with pytest.raises(TypeError, match="Cannot compare tz-naive"):
+ # GH#36148 as of 2.0 we require tzawareness compat
+ s.truncate(datetime(2005, 4, 2), datetime(2005, 4, 4))
+
+ lb = idx[1]
+ ub = idx[3]
+ result = s.truncate(lb.to_pydatetime(), ub.to_pydatetime())
+ expected = Series([1, 2, 3], index=idx[1:4])
+ tm.assert_series_equal(result, expected)
+
+ def test_truncate_periodindex(self):
+ # GH 17717
+ idx1 = pd.PeriodIndex(
+ [pd.Period("2017-09-02"), pd.Period("2017-09-02"), pd.Period("2017-09-03")]
+ )
+ series1 = Series([1, 2, 3], index=idx1)
+ result1 = series1.truncate(after="2017-09-02")
+
+ expected_idx1 = pd.PeriodIndex(
+ [pd.Period("2017-09-02"), pd.Period("2017-09-02")]
+ )
+ tm.assert_series_equal(result1, Series([1, 2], index=expected_idx1))
+
+ idx2 = pd.PeriodIndex(
+ [pd.Period("2017-09-03"), pd.Period("2017-09-02"), pd.Period("2017-09-03")]
+ )
+ series2 = Series([1, 2, 3], index=idx2)
+ result2 = series2.sort_index().truncate(after="2017-09-02")
+
+ expected_idx2 = pd.PeriodIndex([pd.Period("2017-09-02")])
+ tm.assert_series_equal(result2, Series([2], index=expected_idx2))
+
+ def test_truncate_one_element_series(self):
+ # GH 35544
+ series = Series([0.1], index=pd.DatetimeIndex(["2020-08-04"]))
+ before = pd.Timestamp("2020-08-02")
+ after = pd.Timestamp("2020-08-04")
+
+ result = series.truncate(before=before, after=after)
+
+ # the input Series and the expected Series are the same
+ tm.assert_series_equal(result, series)
+
+ def test_truncate_index_only_one_unique_value(self):
+ # GH 42365
+ obj = Series(0, index=date_range("2021-06-30", "2021-06-30")).repeat(5)
+
+ truncated = obj.truncate("2021-06-28", "2021-07-01")
+
+ tm.assert_series_equal(truncated, obj)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_tz_localize.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_tz_localize.py
new file mode 100644
index 0000000000000000000000000000000000000000..53288e8a1f8e7e44513a6ec7eca48786e1116714
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_tz_localize.py
@@ -0,0 +1,122 @@
+from datetime import timezone
+
+import pytest
+
+from pandas._libs.tslibs import timezones
+
+from pandas import (
+ DatetimeIndex,
+ NaT,
+ Series,
+ Timestamp,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestTZLocalize:
+ def test_series_tz_localize_ambiguous_bool(self):
+ # make sure that we are correctly accepting bool values as ambiguous
+
+ # GH#14402
+ ts = Timestamp("2015-11-01 01:00:03")
+ expected0 = Timestamp("2015-11-01 01:00:03-0500", tz="US/Central")
+ expected1 = Timestamp("2015-11-01 01:00:03-0600", tz="US/Central")
+
+ ser = Series([ts])
+ expected0 = Series([expected0])
+ expected1 = Series([expected1])
+
+ with tm.external_error_raised(ValueError):
+ ser.dt.tz_localize("US/Central")
+
+ result = ser.dt.tz_localize("US/Central", ambiguous=True)
+ tm.assert_series_equal(result, expected0)
+
+ result = ser.dt.tz_localize("US/Central", ambiguous=[True])
+ tm.assert_series_equal(result, expected0)
+
+ result = ser.dt.tz_localize("US/Central", ambiguous=False)
+ tm.assert_series_equal(result, expected1)
+
+ result = ser.dt.tz_localize("US/Central", ambiguous=[False])
+ tm.assert_series_equal(result, expected1)
+
+ def test_series_tz_localize_matching_index(self):
+ # Matching the index of the result with that of the original series
+ # GH 43080
+ dt_series = Series(
+ date_range(start="2021-01-01T02:00:00", periods=5, freq="1D"),
+ index=[2, 6, 7, 8, 11],
+ dtype="category",
+ )
+ result = dt_series.dt.tz_localize("Europe/Berlin")
+ expected = Series(
+ date_range(
+ start="2021-01-01T02:00:00", periods=5, freq="1D", tz="Europe/Berlin"
+ ),
+ index=[2, 6, 7, 8, 11],
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "method, exp",
+ [
+ ["shift_forward", "2015-03-29 03:00:00"],
+ ["shift_backward", "2015-03-29 01:59:59.999999999"],
+ ["NaT", NaT],
+ ["raise", None],
+ ["foo", "invalid"],
+ ],
+ )
+ def test_tz_localize_nonexistent(self, warsaw, method, exp, unit):
+ # GH 8917
+ tz = warsaw
+ n = 60
+ dti = date_range(start="2015-03-29 02:00:00", periods=n, freq="min", unit=unit)
+ ser = Series(1, index=dti)
+ df = ser.to_frame()
+
+ if method == "raise":
+ with tm.external_error_raised(ValueError):
+ dti.tz_localize(tz, nonexistent=method)
+ with tm.external_error_raised(ValueError):
+ ser.tz_localize(tz, nonexistent=method)
+ with tm.external_error_raised(ValueError):
+ df.tz_localize(tz, nonexistent=method)
+
+ elif exp == "invalid":
+ msg = (
+ "The nonexistent argument must be one of "
+ "'raise', 'NaT', 'shift_forward', 'shift_backward' "
+ "or a timedelta object"
+ )
+ with pytest.raises(ValueError, match=msg):
+ dti.tz_localize(tz, nonexistent=method)
+ with pytest.raises(ValueError, match=msg):
+ ser.tz_localize(tz, nonexistent=method)
+ with pytest.raises(ValueError, match=msg):
+ df.tz_localize(tz, nonexistent=method)
+
+ else:
+ result = ser.tz_localize(tz, nonexistent=method)
+ expected = Series(1, index=DatetimeIndex([exp] * n, tz=tz).as_unit(unit))
+ tm.assert_series_equal(result, expected)
+
+ result = df.tz_localize(tz, nonexistent=method)
+ expected = expected.to_frame()
+ tm.assert_frame_equal(result, expected)
+
+ res_index = dti.tz_localize(tz, nonexistent=method)
+ tm.assert_index_equal(res_index, expected.index)
+
+ @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
+ def test_series_tz_localize_empty(self, tzstr):
+ # GH#2248
+ ser = Series(dtype=object)
+
+ ser2 = ser.tz_localize("utc")
+ assert ser2.index.tz == timezone.utc
+
+ ser2 = ser.tz_localize(tzstr)
+ timezones.tz_compare(ser2.index.tz, timezones.maybe_get_tz(tzstr))
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_unique.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_unique.py
new file mode 100644
index 0000000000000000000000000000000000000000..14e247332503253ede23e94fe1335e712a2675e7
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_unique.py
@@ -0,0 +1,76 @@
+import numpy as np
+
+from pandas import (
+ Categorical,
+ IntervalIndex,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+class TestUnique:
+ def test_unique_uint64(self):
+ ser = Series([1, 2, 2**63, 2**63], dtype=np.uint64)
+ res = ser.unique()
+ exp = np.array([1, 2, 2**63], dtype=np.uint64)
+ tm.assert_numpy_array_equal(res, exp)
+
+ def test_unique_data_ownership(self):
+ # it works! GH#1807
+ Series(Series(["a", "c", "b"]).unique()).sort_values()
+
+ def test_unique(self):
+ # GH#714 also, dtype=float
+ ser = Series([1.2345] * 100)
+ ser[::2] = np.nan
+ result = ser.unique()
+ assert len(result) == 2
+
+ # explicit f4 dtype
+ ser = Series([1.2345] * 100, dtype="f4")
+ ser[::2] = np.nan
+ result = ser.unique()
+ assert len(result) == 2
+
+ def test_unique_nan_object_dtype(self):
+ # NAs in object arrays GH#714
+ ser = Series(["foo"] * 100, dtype="O")
+ ser[::2] = np.nan
+ result = ser.unique()
+ assert len(result) == 2
+
+ def test_unique_none(self):
+ # decision about None
+ ser = Series([1, 2, 3, None, None, None], dtype=object)
+ result = ser.unique()
+ expected = np.array([1, 2, 3, None], dtype=object)
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_unique_categorical(self):
+ # GH#18051
+ cat = Categorical([])
+ ser = Series(cat)
+ result = ser.unique()
+ tm.assert_categorical_equal(result, cat)
+
+ cat = Categorical([np.nan])
+ ser = Series(cat)
+ result = ser.unique()
+ tm.assert_categorical_equal(result, cat)
+
+ def test_tz_unique(self):
+ # GH 46128
+ dti1 = date_range("2016-01-01", periods=3)
+ ii1 = IntervalIndex.from_breaks(dti1)
+ ser1 = Series(ii1)
+ uni1 = ser1.unique()
+ tm.assert_interval_array_equal(ser1.array, uni1)
+
+ dti2 = date_range("2016-01-01", periods=3, tz="US/Eastern")
+ ii2 = IntervalIndex.from_breaks(dti2)
+ ser2 = Series(ii2)
+ uni2 = ser2.unique()
+ tm.assert_interval_array_equal(ser2.array, uni2)
+
+ assert uni1.dtype != uni2.dtype
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_unstack.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_unstack.py
new file mode 100644
index 0000000000000000000000000000000000000000..f61e20c43657d0f6b2f30be082b2bbdcb633945a
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_unstack.py
@@ -0,0 +1,169 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ MultiIndex,
+ Series,
+ date_range,
+)
+import pandas._testing as tm
+
+
+def test_unstack_preserves_object():
+ mi = MultiIndex.from_product([["bar", "foo"], ["one", "two"]])
+
+ ser = Series(np.arange(4.0), index=mi, dtype=object)
+
+ res1 = ser.unstack()
+ assert (res1.dtypes == object).all()
+
+ res2 = ser.unstack(level=0)
+ assert (res2.dtypes == object).all()
+
+
+def test_unstack():
+ index = MultiIndex(
+ levels=[["bar", "foo"], ["one", "three", "two"]],
+ codes=[[1, 1, 0, 0], [0, 1, 0, 2]],
+ )
+
+ s = Series(np.arange(4.0), index=index)
+ unstacked = s.unstack()
+
+ expected = DataFrame(
+ [[2.0, np.nan, 3.0], [0.0, 1.0, np.nan]],
+ index=["bar", "foo"],
+ columns=["one", "three", "two"],
+ )
+
+ tm.assert_frame_equal(unstacked, expected)
+
+ unstacked = s.unstack(level=0)
+ tm.assert_frame_equal(unstacked, expected.T)
+
+ index = MultiIndex(
+ levels=[["bar"], ["one", "two", "three"], [0, 1]],
+ codes=[[0, 0, 0, 0, 0, 0], [0, 1, 2, 0, 1, 2], [0, 1, 0, 1, 0, 1]],
+ )
+ s = Series(np.random.default_rng(2).standard_normal(6), index=index)
+ exp_index = MultiIndex(
+ levels=[["one", "two", "three"], [0, 1]],
+ codes=[[0, 1, 2, 0, 1, 2], [0, 1, 0, 1, 0, 1]],
+ )
+ expected = DataFrame({"bar": s.values}, index=exp_index).sort_index(level=0)
+ unstacked = s.unstack(0).sort_index()
+ tm.assert_frame_equal(unstacked, expected)
+
+ # GH5873
+ idx = MultiIndex.from_arrays([[101, 102], [3.5, np.nan]])
+ ts = Series([1, 2], index=idx)
+ left = ts.unstack()
+ right = DataFrame(
+ [[np.nan, 1], [2, np.nan]], index=[101, 102], columns=[np.nan, 3.5]
+ )
+ tm.assert_frame_equal(left, right)
+
+ idx = MultiIndex.from_arrays(
+ [
+ ["cat", "cat", "cat", "dog", "dog"],
+ ["a", "a", "b", "a", "b"],
+ [1, 2, 1, 1, np.nan],
+ ]
+ )
+ ts = Series([1.0, 1.1, 1.2, 1.3, 1.4], index=idx)
+ right = DataFrame(
+ [[1.0, 1.3], [1.1, np.nan], [np.nan, 1.4], [1.2, np.nan]],
+ columns=["cat", "dog"],
+ )
+ tpls = [("a", 1), ("a", 2), ("b", np.nan), ("b", 1)]
+ right.index = MultiIndex.from_tuples(tpls)
+ tm.assert_frame_equal(ts.unstack(level=0), right)
+
+
+def test_unstack_tuplename_in_multiindex():
+ # GH 19966
+ idx = MultiIndex.from_product(
+ [["a", "b", "c"], [1, 2, 3]], names=[("A", "a"), ("B", "b")]
+ )
+ ser = Series(1, index=idx)
+ result = ser.unstack(("A", "a"))
+
+ expected = DataFrame(
+ [[1, 1, 1], [1, 1, 1], [1, 1, 1]],
+ columns=MultiIndex.from_tuples([("a",), ("b",), ("c",)], names=[("A", "a")]),
+ index=Index([1, 2, 3], name=("B", "b")),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "unstack_idx, expected_values, expected_index, expected_columns",
+ [
+ (
+ ("A", "a"),
+ [[1, 1], [1, 1], [1, 1], [1, 1]],
+ MultiIndex.from_tuples([(1, 3), (1, 4), (2, 3), (2, 4)], names=["B", "C"]),
+ MultiIndex.from_tuples([("a",), ("b",)], names=[("A", "a")]),
+ ),
+ (
+ (("A", "a"), "B"),
+ [[1, 1, 1, 1], [1, 1, 1, 1]],
+ Index([3, 4], name="C"),
+ MultiIndex.from_tuples(
+ [("a", 1), ("a", 2), ("b", 1), ("b", 2)], names=[("A", "a"), "B"]
+ ),
+ ),
+ ],
+)
+def test_unstack_mixed_type_name_in_multiindex(
+ unstack_idx, expected_values, expected_index, expected_columns
+):
+ # GH 19966
+ idx = MultiIndex.from_product(
+ [["a", "b"], [1, 2], [3, 4]], names=[("A", "a"), "B", "C"]
+ )
+ ser = Series(1, index=idx)
+ result = ser.unstack(unstack_idx)
+
+ expected = DataFrame(
+ expected_values, columns=expected_columns, index=expected_index
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_unstack_multi_index_categorical_values():
+ df = DataFrame(
+ np.random.default_rng(2).standard_normal((10, 4)),
+ columns=Index(list("ABCD")),
+ index=date_range("2000-01-01", periods=10, freq="B"),
+ )
+ mi = df.stack().index.rename(["major", "minor"])
+ ser = Series(["foo"] * len(mi), index=mi, name="category", dtype="category")
+
+ result = ser.unstack()
+
+ dti = ser.index.levels[0]
+ c = pd.Categorical(["foo"] * len(dti))
+ expected = DataFrame(
+ {"A": c, "B": c, "C": c, "D": c},
+ columns=Index(list("ABCD"), name="minor"),
+ index=dti.rename("major"),
+ )
+ tm.assert_frame_equal(result, expected)
+
+
+def test_unstack_mixed_level_names():
+ # GH#48763
+ arrays = [["a", "a"], [1, 2], ["red", "blue"]]
+ idx = MultiIndex.from_arrays(arrays, names=("x", 0, "y"))
+ ser = Series([1, 2], index=idx)
+ result = ser.unstack("x")
+ expected = DataFrame(
+ [[1], [2]],
+ columns=Index(["a"], name="x"),
+ index=MultiIndex.from_tuples([(1, "red"), (2, "blue")], names=[0, "y"]),
+ )
+ tm.assert_frame_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_update.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b5fb098bf3eec3146e64e1510a59cfdef526770
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_update.py
@@ -0,0 +1,135 @@
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas import (
+ CategoricalDtype,
+ DataFrame,
+ NaT,
+ Series,
+ Timestamp,
+)
+import pandas._testing as tm
+
+
+class TestUpdate:
+ def test_update(self):
+ s = Series([1.5, np.nan, 3.0, 4.0, np.nan])
+ s2 = Series([np.nan, 3.5, np.nan, 5.0])
+ s.update(s2)
+
+ expected = Series([1.5, 3.5, 3.0, 5.0, np.nan])
+ tm.assert_series_equal(s, expected)
+
+ # GH 3217
+ df = DataFrame([{"a": 1}, {"a": 3, "b": 2}])
+ df["c"] = np.nan
+ # Cast to object to avoid upcast when setting "foo"
+ df["c"] = df["c"].astype(object)
+ df_orig = df.copy()
+
+ with tm.raises_chained_assignment_error():
+ df["c"].update(Series(["foo"], index=[0]))
+ expected = df_orig
+ tm.assert_frame_equal(df, expected)
+
+ @pytest.mark.parametrize(
+ "other, dtype, expected, raises",
+ [
+ # other is int
+ ([61, 63], "int32", Series([10, 61, 12], dtype="int32"), False),
+ ([61, 63], "int64", Series([10, 61, 12]), False),
+ ([61, 63], float, Series([10.0, 61.0, 12.0]), False),
+ ([61, 63], object, Series([10, 61, 12], dtype=object), False),
+ # other is float, but can be cast to int
+ ([61.0, 63.0], "int32", Series([10, 61, 12], dtype="int32"), False),
+ ([61.0, 63.0], "int64", Series([10, 61, 12]), False),
+ ([61.0, 63.0], float, Series([10.0, 61.0, 12.0]), False),
+ ([61.0, 63.0], object, Series([10, 61.0, 12], dtype=object), False),
+ # others is float, cannot be cast to int
+ ([61.1, 63.1], "int32", Series([10.0, 61.1, 12.0]), True),
+ ([61.1, 63.1], "int64", Series([10.0, 61.1, 12.0]), True),
+ ([61.1, 63.1], float, Series([10.0, 61.1, 12.0]), False),
+ ([61.1, 63.1], object, Series([10, 61.1, 12], dtype=object), False),
+ # other is object, cannot be cast
+ ([(61,), (63,)], "int32", Series([10, (61,), 12]), True),
+ ([(61,), (63,)], "int64", Series([10, (61,), 12]), True),
+ ([(61,), (63,)], float, Series([10.0, (61,), 12.0]), True),
+ ([(61,), (63,)], object, Series([10, (61,), 12]), False),
+ ],
+ )
+ def test_update_dtypes(self, other, dtype, expected, raises):
+ ser = Series([10, 11, 12], dtype=dtype)
+ other = Series(other, index=[1, 3])
+ if raises:
+ with pytest.raises(TypeError, match="Invalid value"):
+ ser.update(other)
+ else:
+ ser.update(other)
+ tm.assert_series_equal(ser, expected)
+
+ @pytest.mark.parametrize(
+ "values, other, expected",
+ [
+ # update by key
+ (
+ {"a": 1, "b": 2, "c": 3, "d": 4},
+ {"b": 5, "c": np.nan},
+ {"a": 1, "b": 5, "c": 3, "d": 4},
+ ),
+ # update by position
+ ([1, 2, 3, 4], [np.nan, 5, 1], [1, 5, 1, 4]),
+ ],
+ )
+ def test_update_from_non_series(self, values, other, expected):
+ # GH 33215
+ series = Series(values)
+ series.update(other)
+ expected = Series(expected)
+ tm.assert_series_equal(series, expected)
+
+ @pytest.mark.parametrize(
+ "data, other, expected, dtype",
+ [
+ (["a", None], [None, "b"], ["a", "b"], "string[python]"),
+ pytest.param(
+ ["a", None],
+ [None, "b"],
+ ["a", "b"],
+ "string[pyarrow]",
+ marks=td.skip_if_no("pyarrow"),
+ ),
+ ([1, None], [None, 2], [1, 2], "Int64"),
+ ([True, None], [None, False], [True, False], "boolean"),
+ (
+ ["a", None],
+ [None, "b"],
+ ["a", "b"],
+ CategoricalDtype(categories=["a", "b"]),
+ ),
+ (
+ [Timestamp(year=2020, month=1, day=1, tz="Europe/London"), NaT],
+ [NaT, Timestamp(year=2020, month=1, day=1, tz="Europe/London")],
+ [Timestamp(year=2020, month=1, day=1, tz="Europe/London")] * 2,
+ "datetime64[ns, Europe/London]",
+ ),
+ ],
+ )
+ def test_update_extension_array_series(self, data, other, expected, dtype):
+ result = Series(data, dtype=dtype)
+ other = Series(other, dtype=dtype)
+ expected = Series(expected, dtype=dtype)
+
+ result.update(other)
+ tm.assert_series_equal(result, expected)
+
+ def test_update_with_categorical_type(self):
+ # GH 25744
+ dtype = CategoricalDtype(["a", "b", "c", "d"])
+ s1 = Series(["a", "b", "c"], index=[1, 2, 3], dtype=dtype)
+ s2 = Series(["b", "a"], index=[1, 2], dtype=dtype)
+ s1.update(s2)
+ result = s1
+ expected = Series(["b", "a", "c"], index=[1, 2, 3], dtype=dtype)
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_value_counts.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_value_counts.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f882fa348b7e4f70852d636b8f404092e646dae
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_value_counts.py
@@ -0,0 +1,255 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ CategoricalIndex,
+ Index,
+ Series,
+)
+import pandas._testing as tm
+
+
+class TestSeriesValueCounts:
+ def test_value_counts_datetime(self, unit):
+ # most dtypes are tested in tests/base
+ values = [
+ pd.Timestamp("2011-01-01 09:00"),
+ pd.Timestamp("2011-01-01 10:00"),
+ pd.Timestamp("2011-01-01 11:00"),
+ pd.Timestamp("2011-01-01 09:00"),
+ pd.Timestamp("2011-01-01 09:00"),
+ pd.Timestamp("2011-01-01 11:00"),
+ ]
+
+ exp_idx = pd.DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 11:00", "2011-01-01 10:00"],
+ name="xxx",
+ ).as_unit(unit)
+ exp = Series([3, 2, 1], index=exp_idx, name="count")
+
+ ser = Series(values, name="xxx").dt.as_unit(unit)
+ tm.assert_series_equal(ser.value_counts(), exp)
+ # check DatetimeIndex outputs the same result
+ idx = pd.DatetimeIndex(values, name="xxx").as_unit(unit)
+ tm.assert_series_equal(idx.value_counts(), exp)
+
+ # normalize
+ exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion")
+ tm.assert_series_equal(ser.value_counts(normalize=True), exp)
+ tm.assert_series_equal(idx.value_counts(normalize=True), exp)
+
+ def test_value_counts_datetime_tz(self, unit):
+ values = [
+ pd.Timestamp("2011-01-01 09:00", tz="US/Eastern"),
+ pd.Timestamp("2011-01-01 10:00", tz="US/Eastern"),
+ pd.Timestamp("2011-01-01 11:00", tz="US/Eastern"),
+ pd.Timestamp("2011-01-01 09:00", tz="US/Eastern"),
+ pd.Timestamp("2011-01-01 09:00", tz="US/Eastern"),
+ pd.Timestamp("2011-01-01 11:00", tz="US/Eastern"),
+ ]
+
+ exp_idx = pd.DatetimeIndex(
+ ["2011-01-01 09:00", "2011-01-01 11:00", "2011-01-01 10:00"],
+ tz="US/Eastern",
+ name="xxx",
+ ).as_unit(unit)
+ exp = Series([3, 2, 1], index=exp_idx, name="count")
+
+ ser = Series(values, name="xxx").dt.as_unit(unit)
+ tm.assert_series_equal(ser.value_counts(), exp)
+ idx = pd.DatetimeIndex(values, name="xxx").as_unit(unit)
+ tm.assert_series_equal(idx.value_counts(), exp)
+
+ exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion")
+ tm.assert_series_equal(ser.value_counts(normalize=True), exp)
+ tm.assert_series_equal(idx.value_counts(normalize=True), exp)
+
+ def test_value_counts_period(self):
+ values = [
+ pd.Period("2011-01", freq="M"),
+ pd.Period("2011-02", freq="M"),
+ pd.Period("2011-03", freq="M"),
+ pd.Period("2011-01", freq="M"),
+ pd.Period("2011-01", freq="M"),
+ pd.Period("2011-03", freq="M"),
+ ]
+
+ exp_idx = pd.PeriodIndex(
+ ["2011-01", "2011-03", "2011-02"], freq="M", name="xxx"
+ )
+ exp = Series([3, 2, 1], index=exp_idx, name="count")
+
+ ser = Series(values, name="xxx")
+ tm.assert_series_equal(ser.value_counts(), exp)
+ # check DatetimeIndex outputs the same result
+ idx = pd.PeriodIndex(values, name="xxx")
+ tm.assert_series_equal(idx.value_counts(), exp)
+
+ # normalize
+ exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion")
+ tm.assert_series_equal(ser.value_counts(normalize=True), exp)
+ tm.assert_series_equal(idx.value_counts(normalize=True), exp)
+
+ def test_value_counts_categorical_ordered(self):
+ # most dtypes are tested in tests/base
+ values = Categorical([1, 2, 3, 1, 1, 3], ordered=True)
+
+ exp_idx = CategoricalIndex(
+ [1, 3, 2], categories=[1, 2, 3], ordered=True, name="xxx"
+ )
+ exp = Series([3, 2, 1], index=exp_idx, name="count")
+
+ ser = Series(values, name="xxx")
+ tm.assert_series_equal(ser.value_counts(), exp)
+ # check CategoricalIndex outputs the same result
+ idx = CategoricalIndex(values, name="xxx")
+ tm.assert_series_equal(idx.value_counts(), exp)
+
+ # normalize
+ exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion")
+ tm.assert_series_equal(ser.value_counts(normalize=True), exp)
+ tm.assert_series_equal(idx.value_counts(normalize=True), exp)
+
+ def test_value_counts_categorical_not_ordered(self):
+ values = Categorical([1, 2, 3, 1, 1, 3], ordered=False)
+
+ exp_idx = CategoricalIndex(
+ [1, 3, 2], categories=[1, 2, 3], ordered=False, name="xxx"
+ )
+ exp = Series([3, 2, 1], index=exp_idx, name="count")
+
+ ser = Series(values, name="xxx")
+ tm.assert_series_equal(ser.value_counts(), exp)
+ # check CategoricalIndex outputs the same result
+ idx = CategoricalIndex(values, name="xxx")
+ tm.assert_series_equal(idx.value_counts(), exp)
+
+ # normalize
+ exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion")
+ tm.assert_series_equal(ser.value_counts(normalize=True), exp)
+ tm.assert_series_equal(idx.value_counts(normalize=True), exp)
+
+ def test_value_counts_categorical(self):
+ # GH#12835
+ cats = Categorical(list("abcccb"), categories=list("cabd"))
+ ser = Series(cats, name="xxx")
+ res = ser.value_counts(sort=False)
+
+ exp_index = CategoricalIndex(
+ list("cabd"), categories=cats.categories, name="xxx"
+ )
+ exp = Series([3, 1, 2, 0], name="count", index=exp_index)
+ tm.assert_series_equal(res, exp)
+
+ res = ser.value_counts(sort=True)
+
+ exp_index = CategoricalIndex(
+ list("cbad"), categories=cats.categories, name="xxx"
+ )
+ exp = Series([3, 2, 1, 0], name="count", index=exp_index)
+ tm.assert_series_equal(res, exp)
+
+ # check object dtype handles the Series.name as the same
+ # (tested in tests/base)
+ ser = Series(["a", "b", "c", "c", "c", "b"], name="xxx")
+ res = ser.value_counts()
+ exp = Series([3, 2, 1], name="count", index=Index(["c", "b", "a"], name="xxx"))
+ tm.assert_series_equal(res, exp)
+
+ def test_value_counts_categorical_with_nan(self):
+ # see GH#9443
+
+ # sanity check
+ ser = Series(["a", "b", "a"], dtype="category")
+ exp = Series([2, 1], index=CategoricalIndex(["a", "b"]), name="count")
+
+ res = ser.value_counts(dropna=True)
+ tm.assert_series_equal(res, exp)
+
+ res = ser.value_counts(dropna=True)
+ tm.assert_series_equal(res, exp)
+
+ # same Series via two different constructions --> same behaviour
+ series = [
+ Series(["a", "b", None, "a", None, None], dtype="category"),
+ Series(
+ Categorical(["a", "b", None, "a", None, None], categories=["a", "b"])
+ ),
+ ]
+
+ for ser in series:
+ # None is a NaN value, so we exclude its count here
+ exp = Series([2, 1], index=CategoricalIndex(["a", "b"]), name="count")
+ res = ser.value_counts(dropna=True)
+ tm.assert_series_equal(res, exp)
+
+ # we don't exclude the count of None and sort by counts
+ exp = Series(
+ [3, 2, 1], index=CategoricalIndex([np.nan, "a", "b"]), name="count"
+ )
+ res = ser.value_counts(dropna=False)
+ tm.assert_series_equal(res, exp)
+
+ # When we aren't sorting by counts, and np.nan isn't a
+ # category, it should be last.
+ exp = Series(
+ [2, 1, 3], index=CategoricalIndex(["a", "b", np.nan]), name="count"
+ )
+ res = ser.value_counts(dropna=False, sort=False)
+ tm.assert_series_equal(res, exp)
+
+ @pytest.mark.parametrize(
+ "ser, dropna, exp",
+ [
+ (
+ Series([False, True, True, pd.NA]),
+ False,
+ Series([2, 1, 1], index=[True, False, pd.NA], name="count"),
+ ),
+ (
+ Series([False, True, True, pd.NA]),
+ True,
+ Series([2, 1], index=Index([True, False], dtype=object), name="count"),
+ ),
+ (
+ Series(range(3), index=[True, False, np.nan]).index,
+ False,
+ Series([1, 1, 1], index=[True, False, np.nan], name="count"),
+ ),
+ ],
+ )
+ def test_value_counts_bool_with_nan(self, ser, dropna, exp):
+ # GH32146
+ out = ser.value_counts(dropna=dropna)
+ tm.assert_series_equal(out, exp)
+
+ @pytest.mark.parametrize("dtype", [np.complex128, np.complex64])
+ def test_value_counts_complex_numbers(self, dtype):
+ # GH 17927
+ input_array = np.array([1 + 1j, 1 + 1j, 1, 3j, 3j, 3j], dtype=dtype)
+ result = Series(input_array).value_counts()
+ expected = Series(
+ [3, 2, 1], index=Index([3j, 1 + 1j, 1], dtype=dtype), name="count"
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_value_counts_masked(self):
+ # GH#54984
+ dtype = "Int64"
+ ser = Series([1, 2, None, 2, None, 3], dtype=dtype)
+ result = ser.value_counts(dropna=False)
+ expected = Series(
+ [2, 2, 1, 1],
+ index=Index([2, None, 1, 3], dtype=dtype),
+ dtype=dtype,
+ name="count",
+ )
+ tm.assert_series_equal(result, expected)
+
+ result = ser.value_counts(dropna=True)
+ expected = Series(
+ [2, 1, 1], index=Index([2, 1, 3], dtype=dtype), dtype=dtype, name="count"
+ )
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_values.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_values.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb1595e68264fbe5f07b014be4975657fa2fa8cf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/methods/test_values.py
@@ -0,0 +1,29 @@
+import numpy as np
+import pytest
+
+from pandas import (
+ IntervalIndex,
+ Series,
+ period_range,
+)
+import pandas._testing as tm
+
+
+class TestValues:
+ @pytest.mark.parametrize(
+ "data",
+ [
+ period_range("2000", periods=4),
+ IntervalIndex.from_breaks([1, 2, 3, 4]),
+ ],
+ )
+ def test_values_object_extension_dtypes(self, data):
+ # https://github.com/pandas-dev/pandas/issues/23995
+ result = Series(data).values
+ expected = np.array(data.astype(object))
+ tm.assert_numpy_array_equal(result, expected)
+
+ def test_values(self, datetime_series):
+ tm.assert_almost_equal(
+ datetime_series.values, list(datetime_series), check_dtype=False
+ )
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b369bb0bc86935ac94113a858cfb5d99082e34f
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_api.py
@@ -0,0 +1,278 @@
+import inspect
+import pydoc
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ date_range,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+
+
+class TestSeriesMisc:
+ def test_tab_completion(self):
+ # GH 9910
+ s = Series(list("abcd"))
+ # Series of str values should have .str but not .dt/.cat in __dir__
+ assert "str" in dir(s)
+ assert "dt" not in dir(s)
+ assert "cat" not in dir(s)
+
+ def test_tab_completion_dt(self):
+ # similarly for .dt
+ s = Series(date_range("1/1/2015", periods=5))
+ assert "dt" in dir(s)
+ assert "str" not in dir(s)
+ assert "cat" not in dir(s)
+
+ def test_tab_completion_cat(self):
+ # Similarly for .cat, but with the twist that str and dt should be
+ # there if the categories are of that type first cat and str.
+ s = Series(list("abbcd"), dtype="category")
+ assert "cat" in dir(s)
+ assert "str" in dir(s) # as it is a string categorical
+ assert "dt" not in dir(s)
+
+ def test_tab_completion_cat_str(self):
+ # similar to cat and str
+ s = Series(date_range("1/1/2015", periods=5)).astype("category")
+ assert "cat" in dir(s)
+ assert "str" not in dir(s)
+ assert "dt" in dir(s) # as it is a datetime categorical
+
+ def test_tab_completion_with_categorical(self):
+ # test the tab completion display
+ ok_for_cat = [
+ "categories",
+ "codes",
+ "ordered",
+ "set_categories",
+ "add_categories",
+ "remove_categories",
+ "rename_categories",
+ "reorder_categories",
+ "remove_unused_categories",
+ "as_ordered",
+ "as_unordered",
+ ]
+
+ s = Series(list("aabbcde")).astype("category")
+ results = sorted({r for r in s.cat.__dir__() if not r.startswith("_")})
+ tm.assert_almost_equal(results, sorted(set(ok_for_cat)))
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ Index(list("ab") * 5, dtype="category"),
+ Index([str(i) for i in range(10)]),
+ Index(["foo", "bar", "baz"] * 2),
+ date_range("2020-01-01", periods=10),
+ period_range("2020-01-01", periods=10, freq="D"),
+ timedelta_range("1 day", periods=10),
+ Index(np.arange(10), dtype=np.uint64),
+ Index(np.arange(10), dtype=np.int64),
+ Index(np.arange(10), dtype=np.float64),
+ Index([True, False]),
+ Index([f"a{i}" for i in range(101)]),
+ pd.MultiIndex.from_tuples(zip("ABCD", "EFGH")),
+ pd.MultiIndex.from_tuples(zip([0, 1, 2, 3], "EFGH")),
+ ],
+ )
+ def test_index_tab_completion(self, index):
+ # dir contains string-like values of the Index.
+ s = Series(index=index, dtype=object)
+ dir_s = dir(s)
+ for i, x in enumerate(s.index.unique(level=0)):
+ if i < 100:
+ assert not isinstance(x, str) or not x.isidentifier() or x in dir_s
+ else:
+ assert x not in dir_s
+
+ @pytest.mark.parametrize("ser", [Series(dtype=object), Series([1])])
+ def test_not_hashable(self, ser):
+ msg = "unhashable type: 'Series'"
+ with pytest.raises(TypeError, match=msg):
+ hash(ser)
+
+ def test_contains(self, datetime_series):
+ tm.assert_contains_all(datetime_series.index, datetime_series)
+
+ def test_axis_alias(self):
+ s = Series([1, 2, np.nan])
+ tm.assert_series_equal(s.dropna(axis="rows"), s.dropna(axis="index"))
+ assert s.dropna().sum(axis="rows") == 3
+ assert s._get_axis_number("rows") == 0
+ assert s._get_axis_name("rows") == "index"
+
+ def test_class_axis(self):
+ # https://github.com/pandas-dev/pandas/issues/18147
+ # no exception and no empty docstring
+ assert pydoc.getdoc(Series.index)
+
+ def test_ndarray_compat(self):
+ # test numpy compat with Series as sub-class of NDFrame
+ tsdf = DataFrame(
+ np.random.default_rng(2).standard_normal((1000, 3)),
+ columns=["A", "B", "C"],
+ index=date_range("1/1/2000", periods=1000),
+ )
+
+ def f(x):
+ return x[x.idxmax()]
+
+ result = tsdf.apply(f)
+ expected = tsdf.max()
+ tm.assert_series_equal(result, expected)
+
+ def test_ndarray_compat_like_func(self):
+ # using an ndarray like function
+ s = Series(np.random.default_rng(2).standard_normal(10))
+ result = Series(np.ones_like(s))
+ expected = Series(1, index=range(10), dtype="float64")
+ tm.assert_series_equal(result, expected)
+
+ def test_empty_method(self):
+ s_empty = Series(dtype=object)
+ assert s_empty.empty
+
+ @pytest.mark.parametrize("dtype", ["int64", object])
+ def test_empty_method_full_series(self, dtype):
+ full_series = Series(index=[1], dtype=dtype)
+ assert not full_series.empty
+
+ @pytest.mark.parametrize("dtype", [None, "Int64"])
+ def test_integer_series_size(self, dtype):
+ # GH 25580
+ s = Series(range(9), dtype=dtype)
+ assert s.size == 9
+
+ def test_attrs(self):
+ s = Series([0, 1], name="abc")
+ assert s.attrs == {}
+ s.attrs["version"] = 1
+ result = s + 1
+ assert result.attrs == {"version": 1}
+
+ def test_inspect_getmembers(self):
+ # GH38782
+ ser = Series(dtype=object)
+ inspect.getmembers(ser)
+
+ def test_unknown_attribute(self):
+ # GH#9680
+ tdi = timedelta_range(start=0, periods=10, freq="1s")
+ ser = Series(np.random.default_rng(2).normal(size=10), index=tdi)
+ assert "foo" not in ser.__dict__
+ msg = "'Series' object has no attribute 'foo'"
+ with pytest.raises(AttributeError, match=msg):
+ ser.foo
+
+ @pytest.mark.parametrize("op", ["year", "day", "second", "weekday"])
+ def test_datetime_series_no_datelike_attrs(self, op, datetime_series):
+ # GH#7206
+ msg = f"'Series' object has no attribute '{op}'"
+ with pytest.raises(AttributeError, match=msg):
+ getattr(datetime_series, op)
+
+ def test_series_datetimelike_attribute_access(self):
+ # attribute access should still work!
+ ser = Series({"year": 2000, "month": 1, "day": 10})
+ assert ser.year == 2000
+ assert ser.month == 1
+ assert ser.day == 10
+
+ def test_series_datetimelike_attribute_access_invalid(self):
+ ser = Series({"year": 2000, "month": 1, "day": 10})
+ msg = "'Series' object has no attribute 'weekday'"
+ with pytest.raises(AttributeError, match=msg):
+ ser.weekday
+
+ @pytest.mark.parametrize(
+ "kernel, has_numeric_only",
+ [
+ ("skew", True),
+ ("var", True),
+ ("all", False),
+ ("prod", True),
+ ("any", False),
+ ("idxmin", False),
+ ("quantile", False),
+ ("idxmax", False),
+ ("min", True),
+ ("sem", True),
+ ("mean", True),
+ ("nunique", False),
+ ("max", True),
+ ("sum", True),
+ ("count", False),
+ ("median", True),
+ ("std", True),
+ ("rank", True),
+ ("pct_change", False),
+ ("cummax", False),
+ ("shift", False),
+ ("diff", False),
+ ("cumsum", False),
+ ("cummin", False),
+ ("cumprod", False),
+ ("fillna", False),
+ ("ffill", False),
+ ("bfill", False),
+ ("sample", False),
+ ("tail", False),
+ ("take", False),
+ ("head", False),
+ ("cov", False),
+ ("corr", False),
+ ],
+ )
+ @pytest.mark.parametrize("dtype", [bool, int, float, object])
+ def test_numeric_only(self, kernel, has_numeric_only, dtype):
+ # GH#47500
+ ser = Series([0, 1, 1], dtype=dtype)
+ if kernel == "corrwith":
+ args = (ser,)
+ elif kernel == "corr":
+ args = (ser,)
+ elif kernel == "cov":
+ args = (ser,)
+ elif kernel == "nth":
+ args = (0,)
+ elif kernel == "fillna":
+ args = (True,)
+ elif kernel == "fillna":
+ args = ("ffill",)
+ elif kernel == "take":
+ args = ([0],)
+ elif kernel == "quantile":
+ args = (0.5,)
+ else:
+ args = ()
+ method = getattr(ser, kernel)
+ if not has_numeric_only:
+ msg = (
+ "(got an unexpected keyword argument 'numeric_only'"
+ "|too many arguments passed in)"
+ )
+ with pytest.raises(TypeError, match=msg):
+ method(*args, numeric_only=True)
+ elif dtype is object:
+ msg = f"Series.{kernel} does not allow numeric_only=True with non-numeric"
+ with pytest.raises(TypeError, match=msg):
+ method(*args, numeric_only=True)
+ else:
+ result = method(*args, numeric_only=True)
+ expected = method(*args, numeric_only=False)
+ if isinstance(expected, Series):
+ # transformer
+ tm.assert_series_equal(result, expected)
+ else:
+ # reducer
+ assert result == expected
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_arithmetic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_arithmetic.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c2bba16b86cd64353203dcdf473bbe155c13b13
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_arithmetic.py
@@ -0,0 +1,1085 @@
+from datetime import (
+ date,
+ timedelta,
+ timezone,
+)
+from decimal import Decimal
+from enum import (
+ Enum,
+ auto,
+)
+import operator
+
+import numpy as np
+import pytest
+
+from pandas._libs import lib
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DatetimeTZDtype,
+ Index,
+ Series,
+ Timedelta,
+ bdate_range,
+ date_range,
+ isna,
+)
+import pandas._testing as tm
+from pandas.core import ops
+from pandas.core.computation import expressions as expr
+
+
+@pytest.fixture(autouse=True, params=[0, 1000000], ids=["numexpr", "python"])
+def switch_numexpr_min_elements(request, monkeypatch):
+ with monkeypatch.context() as m:
+ m.setattr(expr, "_MIN_ELEMENTS", request.param)
+ yield
+
+
+def _permute(obj):
+ return obj.take(np.random.default_rng(2).permutation(len(obj)))
+
+
+class TestSeriesFlexArithmetic:
+ @pytest.mark.parametrize(
+ "ts",
+ [
+ (lambda x: x, lambda x: x * 2, False),
+ (lambda x: x, lambda x: x[::2], False),
+ (lambda x: x, lambda x: 5, True),
+ (
+ lambda x: Series(range(10), dtype=np.float64),
+ lambda x: Series(range(10), dtype=np.float64),
+ True,
+ ),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "opname", ["add", "sub", "mul", "floordiv", "truediv", "pow"]
+ )
+ def test_flex_method_equivalence(self, opname, ts):
+ # check that Series.{opname} behaves like Series.__{opname}__,
+ tser = Series(
+ np.arange(20, dtype=np.float64),
+ index=date_range("2020-01-01", periods=20),
+ name="ts",
+ )
+
+ series = ts[0](tser)
+ other = ts[1](tser)
+ check_reverse = ts[2]
+
+ op = getattr(Series, opname)
+ alt = getattr(operator, opname)
+
+ result = op(series, other)
+ expected = alt(series, other)
+ tm.assert_almost_equal(result, expected)
+ if check_reverse:
+ rop = getattr(Series, "r" + opname)
+ result = rop(series, other)
+ expected = alt(other, series)
+ tm.assert_almost_equal(result, expected)
+
+ def test_flex_method_subclass_metadata_preservation(self, all_arithmetic_operators):
+ # GH 13208
+ class MySeries(Series):
+ _metadata = ["x"]
+
+ @property
+ def _constructor(self):
+ return MySeries
+
+ opname = all_arithmetic_operators
+ op = getattr(Series, opname)
+ m = MySeries([1, 2, 3], name="test")
+ m.x = 42
+ result = op(m, 1)
+ assert result.x == 42
+
+ def test_flex_add_scalar_fill_value(self):
+ # GH12723
+ ser = Series([0, 1, np.nan, 3, 4, 5])
+
+ exp = ser.fillna(0).add(2)
+ res = ser.add(2, fill_value=0)
+ tm.assert_series_equal(res, exp)
+
+ pairings = [(Series.div, operator.truediv, 1), (Series.rdiv, ops.rtruediv, 1)]
+ for op in ["add", "sub", "mul", "pow", "truediv", "floordiv"]:
+ fv = 0
+ lop = getattr(Series, op)
+ lequiv = getattr(operator, op)
+ rop = getattr(Series, "r" + op)
+ # bind op at definition time...
+ requiv = lambda x, y, op=op: getattr(operator, op)(y, x)
+ pairings.append((lop, lequiv, fv))
+ pairings.append((rop, requiv, fv))
+
+ @pytest.mark.parametrize("op, equiv_op, fv", pairings)
+ def test_operators_combine(self, op, equiv_op, fv):
+ def _check_fill(meth, op, a, b, fill_value=0):
+ exp_index = a.index.union(b.index)
+ a = a.reindex(exp_index)
+ b = b.reindex(exp_index)
+
+ amask = isna(a)
+ bmask = isna(b)
+
+ exp_values = []
+ for i in range(len(exp_index)):
+ with np.errstate(all="ignore"):
+ if amask[i]:
+ if bmask[i]:
+ exp_values.append(np.nan)
+ continue
+ exp_values.append(op(fill_value, b[i]))
+ elif bmask[i]:
+ if amask[i]:
+ exp_values.append(np.nan)
+ continue
+ exp_values.append(op(a[i], fill_value))
+ else:
+ exp_values.append(op(a[i], b[i]))
+
+ result = meth(a, b, fill_value=fill_value)
+ expected = Series(exp_values, exp_index)
+ tm.assert_series_equal(result, expected)
+
+ a = Series([np.nan, 1.0, 2.0, 3.0, np.nan], index=np.arange(5))
+ b = Series([np.nan, 1, np.nan, 3, np.nan, 4.0], index=np.arange(6))
+
+ result = op(a, b)
+ exp = equiv_op(a, b)
+ tm.assert_series_equal(result, exp)
+ _check_fill(op, equiv_op, a, b, fill_value=fv)
+ # should accept axis=0 or axis='rows'
+ op(a, b, axis=0)
+
+ @pytest.mark.parametrize("kind", ["datetime", "timedelta"])
+ def test_rhs_extension_array_sub_with_fill_value(self, kind):
+ # GH:62467
+ if kind == "datetime":
+ left = Series(
+ [pd.Timestamp("2025-08-20"), pd.Timestamp("2025-08-21")],
+ dtype=np.dtype("datetime64[ns]"),
+ )
+ else:
+ left = Series(
+ [Timedelta(days=1), Timedelta(days=2)],
+ dtype=np.dtype("timedelta64[ns]"),
+ )
+
+ right = (
+ left._values
+ ) # DatetimeArray or TimedeltaArray which is an ExtensionArray
+
+ result = left.sub(right, fill_value=left.iloc[0])
+ expected = Series(np.zeros(len(left), dtype=np.dtype("timedelta64[ns]")))
+ tm.assert_series_equal(result, expected)
+
+ def test_flex_disallows_dataframe(self):
+ # GH#46179
+ df = pd.DataFrame(
+ {2010: [1], 2020: [3]},
+ index=pd.MultiIndex.from_product([["a"], ["b"]], names=["scen", "mod"]),
+ )
+
+ ser = Series(
+ [10.0, 20.0, 30.0],
+ index=pd.MultiIndex.from_product(
+ [["a"], ["b"], [0, 1, 2]], names=["scen", "mod", "id"]
+ ),
+ )
+
+ msg = "Series.add does not support a DataFrame `other`"
+ with pytest.raises(TypeError, match=msg):
+ ser.add(df, axis=0)
+
+
+class TestSeriesArithmetic:
+ # Some of these may end up in tests/arithmetic, but are not yet sorted
+
+ def test_add_series_with_period_index(self):
+ rng = pd.period_range("1/1/2000", "1/1/2010", freq="Y")
+ ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ result = ts + ts[::2]
+ expected = ts + ts
+ expected.iloc[1::2] = np.nan
+ tm.assert_series_equal(result, expected)
+
+ result = ts + _permute(ts[::2])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "target_add,input_value,expected_value",
+ [
+ ("!", ["hello", "world"], ["hello!", "world!"]),
+ ("m", ["hello", "world"], ["hellom", "worldm"]),
+ ],
+ )
+ def test_string_addition(self, target_add, input_value, expected_value):
+ # GH28658 - ensure adding 'm' does not raise an error
+ a = Series(input_value)
+
+ result = a + target_add
+ expected = Series(expected_value)
+ tm.assert_series_equal(result, expected)
+
+ def test_divmod(self):
+ # GH#25557
+ a = Series([1, 1, 1, np.nan], index=["a", "b", "c", "d"])
+ b = Series([2, np.nan, 1, np.nan], index=["a", "b", "d", "e"])
+
+ result = a.divmod(b)
+ expected = divmod(a, b)
+ tm.assert_series_equal(result[0], expected[0])
+ tm.assert_series_equal(result[1], expected[1])
+
+ result = a.rdivmod(b)
+ expected = divmod(b, a)
+ tm.assert_series_equal(result[0], expected[0])
+ tm.assert_series_equal(result[1], expected[1])
+
+ @pytest.mark.parametrize("index", [None, range(9)])
+ def test_series_integer_mod(self, index):
+ # GH#24396
+ s1 = Series(range(1, 10))
+ s2 = Series("foo", index=index)
+
+ msg = "not all arguments converted during string formatting|'mod' not supported"
+
+ with pytest.raises(TypeError, match=msg):
+ s2 % s1
+
+ def test_add_with_duplicate_index(self):
+ # GH14227
+ s1 = Series([1, 2], index=[1, 1])
+ s2 = Series([10, 10], index=[1, 2])
+ result = s1 + s2
+ expected = Series([11, 12, np.nan], index=[1, 1, 2])
+ tm.assert_series_equal(result, expected)
+
+ def test_add_na_handling(self):
+ ser = Series(
+ [Decimal("1.3"), Decimal("2.3")], index=[date(2012, 1, 1), date(2012, 1, 2)]
+ )
+
+ result = ser + ser.shift(1)
+ result2 = ser.shift(1) + ser
+ assert isna(result.iloc[0])
+ assert isna(result2.iloc[0])
+
+ def test_add_corner_cases(self, datetime_series):
+ empty = Series([], index=Index([]), dtype=np.float64)
+
+ result = datetime_series + empty
+ assert np.isnan(result).all()
+
+ result = empty + empty
+ assert len(result) == 0
+
+ def test_add_float_plus_int(self, datetime_series):
+ # float + int
+ int_ts = datetime_series.astype(int)[:-5]
+ added = datetime_series + int_ts
+ expected = Series(
+ datetime_series.values[:-5] + int_ts.values,
+ index=datetime_series.index[:-5],
+ name="ts",
+ )
+ tm.assert_series_equal(added[:-5], expected)
+
+ def test_mul_empty_int_corner_case(self):
+ s1 = Series([], [], dtype=np.int32)
+ s2 = Series({"x": 0.0})
+ tm.assert_series_equal(s1 * s2, Series([np.nan], index=["x"]))
+
+ def test_sub_datetimelike_align(self):
+ # GH#7500
+ # datetimelike ops need to align
+ dt = Series(date_range("2012-1-1", periods=3, freq="D", unit="ns"))
+ dt.iloc[2] = np.nan
+ dt2 = dt[::-1]
+
+ expected = Series([timedelta(0), timedelta(0), pd.NaT], dtype="m8[ns]")
+ # name is reset
+ result = dt2 - dt
+ tm.assert_series_equal(result, expected)
+
+ expected = Series(expected, name=0)
+ result = (dt2.to_frame() - dt.to_frame())[0]
+ tm.assert_series_equal(result, expected)
+
+ def test_alignment_doesnt_change_tz(self):
+ # GH#33671
+ dti = date_range("2016-01-01", periods=10, tz="CET")
+ dti_utc = dti.tz_convert("UTC")
+ ser = Series(10, index=dti)
+ ser_utc = Series(10, index=dti_utc)
+
+ # we don't care about the result, just that original indexes are unchanged
+ ser * ser_utc
+
+ assert ser.index is dti
+ assert ser_utc.index is dti_utc
+
+ def test_alignment_categorical(self):
+ # GH13365
+ cat = Categorical(["3z53", "3z53", "LoJG", "LoJG", "LoJG", "N503"])
+ ser1 = Series(2, index=cat)
+ ser2 = Series(2, index=cat[:-1])
+ result = ser1 * ser2
+
+ exp_index = ["3z53"] * 4 + ["LoJG"] * 9 + ["N503"]
+ exp_index = pd.CategoricalIndex(exp_index, categories=cat.categories)
+ exp_values = [4.0] * 13 + [np.nan]
+ expected = Series(exp_values, exp_index)
+
+ tm.assert_series_equal(result, expected)
+
+ def test_arithmetic_with_duplicate_index(self):
+ # GH#8363
+ # integer ops with a non-unique index
+ index = [2, 2, 3, 3, 4]
+ ser = Series(np.arange(1, 6, dtype="int64"), index=index)
+ other = Series(np.arange(5, dtype="int64"), index=index)
+ result = ser - other
+ expected = Series(1, index=[2, 2, 3, 3, 4])
+ tm.assert_series_equal(result, expected)
+
+ # GH#8363
+ # datetime ops with a non-unique index
+ ser = Series(date_range("20130101 09:00:00", periods=5, unit="ns"), index=index)
+ other = Series(date_range("20130101", periods=5, unit="ns"), index=index)
+ result = ser - other
+ expected = Series(Timedelta("9 hours"), index=[2, 2, 3, 3, 4], dtype="m8[ns]")
+ tm.assert_series_equal(result, expected)
+
+ def test_masked_and_non_masked_propagate_na(self):
+ # GH#45810
+ ser1 = Series([0, np.nan], dtype="float")
+ ser2 = Series([0, 1], dtype="Int64")
+ result = ser1 * ser2
+ expected = Series([0, pd.NA], dtype="Float64")
+ tm.assert_series_equal(result, expected)
+
+ def test_mask_div_propagate_na_for_non_na_dtype(self):
+ # GH#42630
+ ser1 = Series([15, pd.NA, 5, 4], dtype="Int64")
+ ser2 = Series([15, 5, np.nan, 4])
+ result = ser1 / ser2
+ expected = Series([1.0, pd.NA, pd.NA, 1.0], dtype="Float64")
+ tm.assert_series_equal(result, expected)
+
+ result = ser2 / ser1
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("val", [3, 3.5])
+ def test_add_list_to_masked_array(self, val):
+ # GH#22962, behavior changed by GH#62552
+ ser = Series([1, None, 3], dtype="Int64")
+ result = ser + [1, None, val] # noqa: RUF005
+ expected = Series([2, pd.NA, 3 + val], dtype="Float64")
+ tm.assert_series_equal(result, expected)
+
+ result = [1, None, val] + ser # noqa: RUF005
+ tm.assert_series_equal(result, expected)
+
+ def test_add_list_to_masked_array_boolean(self):
+ # GH#22962
+ ser = Series([True, None, False], dtype="boolean")
+ result = ser + [True, None, True] # noqa: RUF005
+ expected = Series([2, pd.NA, 1], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ result = [True, None, True] + ser # noqa: RUF005
+ tm.assert_series_equal(result, expected)
+
+
+# ------------------------------------------------------------------
+# Comparisons
+
+
+class TestSeriesFlexComparison:
+ @pytest.mark.parametrize("axis", [0, None, "index"])
+ def test_comparison_flex_basic(self, axis, comparison_op):
+ left = Series(np.random.default_rng(2).standard_normal(10))
+ right = Series(np.random.default_rng(2).standard_normal(10))
+ result = getattr(left, comparison_op.__name__)(right, axis=axis)
+ expected = comparison_op(left, right)
+ tm.assert_series_equal(result, expected)
+
+ def test_comparison_bad_axis(self, comparison_op):
+ left = Series(np.random.default_rng(2).standard_normal(10))
+ right = Series(np.random.default_rng(2).standard_normal(10))
+
+ msg = "No axis named 1 for object type"
+ with pytest.raises(ValueError, match=msg):
+ getattr(left, comparison_op.__name__)(right, axis=1)
+
+ @pytest.mark.parametrize(
+ "values, op",
+ [
+ ([False, False, True, False], "eq"),
+ ([True, True, False, True], "ne"),
+ ([False, False, True, False], "le"),
+ ([False, False, False, False], "lt"),
+ ([False, True, True, False], "ge"),
+ ([False, True, False, False], "gt"),
+ ],
+ )
+ def test_comparison_flex_alignment(self, values, op):
+ left = Series([1, 3, 2], index=list("abc"))
+ right = Series([2, 2, 2], index=list("bcd"))
+ result = getattr(left, op)(right)
+ expected = Series(values, index=list("abcd"))
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "left",
+ [
+ Series(Categorical(["a", "b", "a"])),
+ Series(pd.period_range("2020Q1", periods=3, freq="Q")),
+ ],
+ ids=["categorical", "period"],
+ )
+ def test_rhs_extension_array_eq_with_fill_value(self, left):
+ # GH:#62467
+ right = left._values # this is an ExtensionArray
+
+ result = left.eq(right, fill_value=left.iloc[0])
+ expected = Series([True, True, True])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "values, op, fill_value",
+ [
+ ([False, False, True, True], "eq", 2),
+ ([True, True, False, False], "ne", 2),
+ ([False, False, True, True], "le", 0),
+ ([False, False, False, True], "lt", 0),
+ ([True, True, True, False], "ge", 0),
+ ([True, True, False, False], "gt", 0),
+ ],
+ )
+ def test_comparison_flex_alignment_fill(self, values, op, fill_value):
+ left = Series([1, 3, 2], index=list("abc"))
+ right = Series([2, 2, 2], index=list("bcd"))
+ result = getattr(left, op)(right, fill_value=fill_value)
+ expected = Series(values, index=list("abcd"))
+ tm.assert_series_equal(result, expected)
+
+ def test_eq_objects(self) -> None:
+ # GH#62191 Test eq with Enum and List elements
+
+ class Thing(Enum):
+ FIRST = auto()
+ SECOND = auto()
+
+ left = Series([Thing.FIRST, Thing.SECOND])
+ py_l = [Thing.FIRST, Thing.SECOND]
+
+ result = left.eq(Thing.FIRST)
+ expected = Series([True, False])
+ tm.assert_series_equal(result, expected)
+
+ result = left.eq(py_l)
+ expected = Series([True, True])
+ tm.assert_series_equal(result, expected)
+
+ result = left.eq(np.asarray(py_l))
+ expected = Series([True, True])
+ tm.assert_series_equal(result, expected)
+
+ result = left.eq(Series(py_l))
+ expected = Series([True, True])
+ tm.assert_series_equal(result, expected)
+
+ result = Series([[1, 2], [3, 4]]).eq([1, 2])
+ expected = Series([True, False])
+ with pytest.raises(AssertionError):
+ tm.assert_series_equal(result, expected)
+ expected = Series([False, False])
+ tm.assert_series_equal(result, expected)
+
+ def test_eq_with_index(self) -> None:
+ # GH#62191 Test eq with non-trivial indices
+ left = Series([1, 2], index=[1, 0])
+ py_l = [1, 2]
+
+ # assuming Python list has the same index as the Series
+ result = left.eq(py_l)
+ expected = Series([True, True], index=[1, 0])
+ tm.assert_series_equal(result, expected)
+
+ # assuming np.ndarray has the same index as the Series
+ result = left.eq(np.asarray(py_l))
+ expected = Series([True, True], index=[1, 0])
+ tm.assert_series_equal(result, expected)
+
+ result = left.eq(Series(py_l))
+ expected = Series([False, False])
+ tm.assert_series_equal(result, expected)
+
+ result = left.eq(Series([2, 1]))
+ expected = Series([True, True])
+ tm.assert_series_equal(result, expected)
+
+
+class TestSeriesComparison:
+ def test_comparison_different_length(self):
+ a = Series(["a", "b", "c"])
+ b = Series(["b", "a"])
+ msg = "only compare identically-labeled Series"
+ with pytest.raises(ValueError, match=msg):
+ a < b
+
+ a = Series([1, 2])
+ b = Series([2, 3, 4])
+ with pytest.raises(ValueError, match=msg):
+ a == b
+
+ @pytest.mark.parametrize("opname", ["eq", "ne", "gt", "lt", "ge", "le"])
+ def test_ser_flex_cmp_return_dtypes(self, opname):
+ # GH#15115
+ ser = Series([1, 3, 2], index=range(3))
+ const = 2
+ result = getattr(ser, opname)(const).dtypes
+ expected = np.dtype("bool")
+ assert result == expected
+
+ @pytest.mark.parametrize("opname", ["eq", "ne", "gt", "lt", "ge", "le"])
+ def test_ser_flex_cmp_return_dtypes_empty(self, opname):
+ # GH#15115 empty Series case
+ ser = Series([1, 3, 2], index=range(3))
+ empty = ser.iloc[:0]
+ const = 2
+ result = getattr(empty, opname)(const).dtypes
+ expected = np.dtype("bool")
+ assert result == expected
+
+ @pytest.mark.parametrize(
+ "names", [(None, None, None), ("foo", "bar", None), ("baz", "baz", "baz")]
+ )
+ def test_ser_cmp_result_names(self, names, comparison_op):
+ # datetime64 dtype
+ op = comparison_op
+ dti = date_range("1949-06-07 03:00:00", freq="h", periods=5, name=names[0])
+ ser = Series(dti).rename(names[1])
+ result = op(ser, dti)
+ assert result.name == names[2]
+
+ # datetime64tz dtype
+ dti = dti.tz_localize("US/Central")
+ dti = pd.DatetimeIndex(dti, freq="infer") # freq not preserved by tz_localize
+ ser = Series(dti).rename(names[1])
+ result = op(ser, dti)
+ assert result.name == names[2]
+
+ # timedelta64 dtype
+ tdi = dti - dti.shift(1)
+ ser = Series(tdi).rename(names[1])
+ result = op(ser, tdi)
+ assert result.name == names[2]
+
+ # interval dtype
+ if op in [operator.eq, operator.ne]:
+ # interval dtype comparisons not yet implemented
+ ii = pd.interval_range(start=0, periods=5, name=names[0])
+ ser = Series(ii).rename(names[1])
+ result = op(ser, ii)
+ assert result.name == names[2]
+
+ # categorical
+ if op in [operator.eq, operator.ne]:
+ # categorical dtype comparisons raise for inequalities
+ cidx = tdi.astype("category")
+ ser = Series(cidx).rename(names[1])
+ result = op(ser, cidx)
+ assert result.name == names[2]
+
+ def test_comparisons(self):
+ s = Series(["a", "b", "c"])
+ s2 = Series([False, True, False])
+
+ # it works!
+ exp = Series([False, False, False])
+ tm.assert_series_equal(s == s2, exp)
+ tm.assert_series_equal(s2 == s, exp)
+
+ # -----------------------------------------------------------------
+ # Categorical Dtype Comparisons
+
+ def test_categorical_comparisons(self):
+ # GH#8938
+ # allow equality comparisons
+ a = Series(list("abc"), dtype="category")
+ b = Series(list("abc"), dtype="object")
+ c = Series(["a", "b", "cc"], dtype="object")
+ d = Series(list("acb"), dtype="object")
+ e = Categorical(list("abc"))
+ f = Categorical(list("acb"))
+
+ # vs scalar
+ assert not (a == "a").all()
+ assert ((a != "a") == ~(a == "a")).all()
+
+ assert not ("a" == a).all()
+ assert (a == "a")[0]
+ assert ("a" == a)[0]
+ assert not ("a" != a)[0]
+
+ # vs list-like
+ assert (a == a).all()
+ assert not (a != a).all()
+
+ assert (a == list(a)).all()
+ assert (a == b).all()
+ assert (b == a).all()
+ assert ((~(a == b)) == (a != b)).all()
+ assert ((~(b == a)) == (b != a)).all()
+
+ assert not (a == c).all()
+ assert not (c == a).all()
+ assert not (a == d).all()
+ assert not (d == a).all()
+
+ # vs a cat-like
+ assert (a == e).all()
+ assert (e == a).all()
+ assert not (a == f).all()
+ assert not (f == a).all()
+
+ assert (~(a == e) == (a != e)).all()
+ assert (~(e == a) == (e != a)).all()
+ assert (~(a == f) == (a != f)).all()
+ assert (~(f == a) == (f != a)).all()
+
+ # non-equality is not comparable
+ msg = "can only compare equality or not"
+ with pytest.raises(TypeError, match=msg):
+ a < b
+ with pytest.raises(TypeError, match=msg):
+ b < a
+ with pytest.raises(TypeError, match=msg):
+ a > b
+ with pytest.raises(TypeError, match=msg):
+ b > a
+
+ def test_unequal_categorical_comparison_raises_type_error(self):
+ # unequal comparison should raise for unordered cats
+ cat = Series(Categorical(list("abc")))
+ msg = "can only compare equality or not"
+ with pytest.raises(TypeError, match=msg):
+ cat > "b"
+
+ cat = Series(Categorical(list("abc"), ordered=False))
+ with pytest.raises(TypeError, match=msg):
+ cat > "b"
+
+ # https://github.com/pandas-dev/pandas/issues/9836#issuecomment-92123057
+ # and following comparisons with scalars not in categories should raise
+ # for unequal comps, but not for equal/not equal
+ cat = Series(Categorical(list("abc"), ordered=True))
+
+ msg = "Invalid comparison between dtype=category and str"
+ with pytest.raises(TypeError, match=msg):
+ cat < "d"
+ with pytest.raises(TypeError, match=msg):
+ cat > "d"
+ with pytest.raises(TypeError, match=msg):
+ "d" < cat
+ with pytest.raises(TypeError, match=msg):
+ "d" > cat
+
+ tm.assert_series_equal(cat == "d", Series([False, False, False]))
+ tm.assert_series_equal(cat != "d", Series([True, True, True]))
+
+ # -----------------------------------------------------------------
+
+ def test_comparison_tuples(self):
+ # GH#11339
+ # comparisons vs tuple
+ s = Series([(1, 1), (1, 2)])
+
+ result = s == (1, 2)
+ expected = Series([False, True])
+ tm.assert_series_equal(result, expected)
+
+ result = s != (1, 2)
+ expected = Series([True, False])
+ tm.assert_series_equal(result, expected)
+
+ result = s == (0, 0)
+ expected = Series([False, False])
+ tm.assert_series_equal(result, expected)
+
+ result = s != (0, 0)
+ expected = Series([True, True])
+ tm.assert_series_equal(result, expected)
+
+ s = Series([(1, 1), (1, 1)])
+
+ result = s == (1, 1)
+ expected = Series([True, True])
+ tm.assert_series_equal(result, expected)
+
+ result = s != (1, 1)
+ expected = Series([False, False])
+ tm.assert_series_equal(result, expected)
+
+ def test_comparison_frozenset(self):
+ ser = Series([frozenset([1]), frozenset([1, 2])])
+
+ result = ser == frozenset([1])
+ expected = Series([True, False])
+ tm.assert_series_equal(result, expected)
+
+ def test_comparison_operators_with_nas(self, comparison_op):
+ ser = Series(bdate_range("1/1/2000", periods=10), dtype=object)
+ ser[::2] = np.nan
+
+ # test that comparisons work
+ val = ser[5]
+
+ result = comparison_op(ser, val)
+ expected = comparison_op(ser.dropna(), val).reindex(ser.index)
+
+ if comparison_op is operator.ne:
+ expected = expected.fillna(True).astype(bool)
+ else:
+ expected = expected.fillna(False).astype(bool)
+
+ tm.assert_series_equal(result, expected)
+
+ def test_ne(self):
+ ts = Series([3, 4, 5, 6, 7], [3, 4, 5, 6, 7], dtype=float)
+ expected = np.array([True, True, False, True, True])
+ tm.assert_numpy_array_equal(ts.index != 5, expected)
+ tm.assert_numpy_array_equal(~(ts.index == 5), expected)
+
+ @pytest.mark.parametrize("right_data", [[2, 2, 2], [2, 2, 2, 2]])
+ def test_comp_ops_df_compat(self, right_data, frame_or_series):
+ # GH 1134
+ # GH 50083 to clarify that index and columns must be identically labeled
+ left = Series([1, 2, 3], index=list("ABC"), name="x")
+ right = Series(right_data, index=list("ABDC")[: len(right_data)], name="x")
+ if frame_or_series is not Series:
+ msg = (
+ rf"Can only compare identically-labeled \(both index and columns\) "
+ f"{frame_or_series.__name__} objects"
+ )
+ left = left.to_frame()
+ right = right.to_frame()
+ else:
+ msg = (
+ f"Can only compare identically-labeled {frame_or_series.__name__} "
+ f"objects"
+ )
+
+ with pytest.raises(ValueError, match=msg):
+ left == right
+ with pytest.raises(ValueError, match=msg):
+ right == left
+
+ with pytest.raises(ValueError, match=msg):
+ left != right
+ with pytest.raises(ValueError, match=msg):
+ right != left
+
+ with pytest.raises(ValueError, match=msg):
+ left < right
+ with pytest.raises(ValueError, match=msg):
+ right < left
+
+ def test_compare_series_interval_keyword(self):
+ # GH#25338
+ ser = Series(["IntervalA", "IntervalB", "IntervalC"])
+ result = ser == "IntervalA"
+ expected = Series([True, False, False])
+ tm.assert_series_equal(result, expected)
+
+
+# ------------------------------------------------------------------
+# Unsorted
+# These arithmetic tests were previously in other files, eventually
+# should be parametrized and put into tests.arithmetic
+
+
+class TestTimeSeriesArithmetic:
+ def test_series_add_tz_mismatch_converts_to_utc(self):
+ rng = date_range("1/1/2011", periods=100, freq="h", tz="utc")
+
+ perm = np.random.default_rng(2).permutation(100)[:90]
+ ser1 = Series(
+ np.random.default_rng(2).standard_normal(90),
+ index=rng.take(perm).tz_convert("US/Eastern"),
+ )
+
+ perm = np.random.default_rng(2).permutation(100)[:90]
+ ser2 = Series(
+ np.random.default_rng(2).standard_normal(90),
+ index=rng.take(perm).tz_convert("Europe/Berlin"),
+ )
+
+ result = ser1 + ser2
+
+ uts1 = ser1.tz_convert("utc")
+ uts2 = ser2.tz_convert("utc")
+ expected = uts1 + uts2
+
+ # sort since input indexes are not equal
+ expected = expected.sort_index()
+
+ assert result.index.tz is timezone.utc
+ tm.assert_series_equal(result, expected)
+
+ def test_series_add_aware_naive_raises(self):
+ rng = date_range("1/1/2011", periods=10, freq="h")
+ ser = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng)
+
+ ser_utc = ser.tz_localize("utc")
+
+ msg = "Cannot join tz-naive with tz-aware DatetimeIndex"
+ with pytest.raises(Exception, match=msg):
+ ser + ser_utc
+
+ with pytest.raises(Exception, match=msg):
+ ser_utc + ser
+
+ # TODO: belongs in tests/arithmetic?
+ def test_datetime_understood(self, unit):
+ # Ensures it doesn't fail to create the right series
+ # reported in issue#16726
+ series = Series(date_range("2012-01-01", periods=3, unit=unit))
+ offset = pd.offsets.DateOffset(days=6)
+ result = series - offset
+ exp_dti = pd.to_datetime(["2011-12-26", "2011-12-27", "2011-12-28"]).as_unit(
+ unit
+ )
+ expected = Series(exp_dti)
+ tm.assert_series_equal(result, expected)
+
+ def test_align_date_objects_with_datetimeindex(self):
+ rng = date_range("1/1/2000", periods=20)
+ ts = Series(np.random.default_rng(2).standard_normal(20), index=rng)
+
+ ts_slice = ts[5:]
+ ts2 = ts_slice.copy()
+ ts2.index = [x.date() for x in ts2.index]
+
+ result = ts + ts2
+ result2 = ts2 + ts
+ expected = ts + ts[5:]
+ expected.index = expected.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+ tm.assert_series_equal(result2, expected)
+
+
+class TestNamePreservation:
+ @pytest.mark.parametrize("box", [list, tuple, np.array, Index, Series, pd.array])
+ @pytest.mark.parametrize("flex", [True, False])
+ def test_series_ops_name_retention(self, flex, box, names, all_binary_operators):
+ # GH#33930 consistent name-retention
+ op = all_binary_operators
+
+ left = Series(range(10), name=names[0])
+ right = Series(range(10), name=names[1])
+
+ name = op.__name__.strip("_")
+ is_logical = name in ["and", "rand", "xor", "rxor", "or", "ror"]
+
+ msg = (
+ r"Logical ops \(and, or, xor\) between Pandas objects and "
+ "dtype-less sequences"
+ )
+
+ right = box(right)
+ if flex:
+ if is_logical:
+ # Series doesn't have these as flex methods
+ return
+ result = getattr(left, name)(right)
+ else:
+ if is_logical and box in [list, tuple]:
+ with pytest.raises(TypeError, match=msg):
+ # GH#52264 logical ops with dtype-less sequences deprecated
+ op(left, right)
+ return
+ result = op(left, right)
+
+ assert isinstance(result, Series)
+ if box in [Index, Series]:
+ assert result.name is names[2] or result.name == names[2]
+ else:
+ assert result.name is names[0] or result.name == names[0]
+
+ def test_binop_maybe_preserve_name(self, datetime_series):
+ # names match, preserve
+ result = datetime_series * datetime_series
+ assert result.name == datetime_series.name
+ result = datetime_series.mul(datetime_series)
+ assert result.name == datetime_series.name
+
+ result = datetime_series * datetime_series[:-2]
+ assert result.name == datetime_series.name
+
+ # names don't match, don't preserve
+ cp = datetime_series.copy()
+ cp.name = "something else"
+ result = datetime_series + cp
+ assert result.name is None
+ result = datetime_series.add(cp)
+ assert result.name is None
+
+ ops = ["add", "sub", "mul", "div", "truediv", "floordiv", "mod", "pow"]
+ ops = ops + ["r" + op for op in ops]
+ for op in ops:
+ # names match, preserve
+ ser = datetime_series.copy()
+ result = getattr(ser, op)(ser)
+ assert result.name == datetime_series.name
+
+ # names don't match, don't preserve
+ cp = datetime_series.copy()
+ cp.name = "changed"
+ result = getattr(ser, op)(cp)
+ assert result.name is None
+
+ def test_scalarop_preserve_name(self, datetime_series):
+ result = datetime_series * 2
+ assert result.name == datetime_series.name
+
+
+class TestInplaceOperations:
+ @pytest.mark.parametrize(
+ "dtype1, dtype2, dtype_expected, dtype_mul",
+ (
+ ("Int64", "Int64", "Int64", "Int64"),
+ ("float", "float", "float", "float"),
+ ("Int64", "float", "Float64", "Float64"),
+ ("Int64", "Float64", "Float64", "Float64"),
+ ),
+ )
+ def test_series_inplace_ops(self, dtype1, dtype2, dtype_expected, dtype_mul):
+ # GH 37910
+
+ ser1 = Series([1], dtype=dtype1)
+ ser2 = Series([2], dtype=dtype2)
+ ser1 += ser2
+ expected = Series([3], dtype=dtype_expected)
+ tm.assert_series_equal(ser1, expected)
+
+ ser1 -= ser2
+ expected = Series([1], dtype=dtype_expected)
+ tm.assert_series_equal(ser1, expected)
+
+ ser1 *= ser2
+ expected = Series([2], dtype=dtype_mul)
+ tm.assert_series_equal(ser1, expected)
+
+
+def test_none_comparison(request, series_with_simple_index):
+ series = series_with_simple_index
+
+ if len(series) < 1:
+ request.applymarker(
+ pytest.mark.xfail(reason="Test doesn't make sense on empty data")
+ )
+
+ # bug brought up by #1079
+ # changed from TypeError in 0.17.0
+ series.iloc[0] = np.nan
+
+ # noinspection PyComparisonWithNone
+ result = series == None # noqa: E711
+ assert not result.iat[0]
+ assert not result.iat[1]
+
+ # noinspection PyComparisonWithNone
+ result = series != None # noqa: E711
+ assert result.iat[0]
+ assert result.iat[1]
+
+ result = None == series # noqa: E711
+ assert not result.iat[0]
+ assert not result.iat[1]
+
+ result = None != series # noqa: E711
+ assert result.iat[0]
+ assert result.iat[1]
+
+ if lib.is_np_dtype(series.dtype, "M") or isinstance(series.dtype, DatetimeTZDtype):
+ # Following DatetimeIndex (and Timestamp) convention,
+ # inequality comparisons with Series[datetime64] raise
+ msg = "Invalid comparison"
+ with pytest.raises(TypeError, match=msg):
+ None > series
+ with pytest.raises(TypeError, match=msg):
+ series > None
+ else:
+ result = None > series
+ assert not result.iat[0]
+ assert not result.iat[1]
+
+ result = series < None
+ assert not result.iat[0]
+ assert not result.iat[1]
+
+
+def test_series_varied_multiindex_alignment():
+ # GH 20414
+ s1 = Series(
+ range(8),
+ index=pd.MultiIndex.from_product(
+ [list("ab"), list("xy"), [1, 2]], names=["ab", "xy", "num"]
+ ),
+ )
+ s2 = Series(
+ [1000 * i for i in range(1, 5)],
+ index=pd.MultiIndex.from_product([list("xy"), [1, 2]], names=["xy", "num"]),
+ )
+ result = s1.loc[pd.IndexSlice[["a"], :, :]] + s2
+ expected = Series(
+ [1000, 2001, 3002, 4003],
+ index=pd.MultiIndex.from_tuples(
+ [("a", "x", 1), ("a", "x", 2), ("a", "y", 1), ("a", "y", 2)],
+ names=["ab", "xy", "num"],
+ ),
+ )
+ tm.assert_series_equal(result, expected)
+
+
+def test_rmod_consistent_large_series():
+ # GH 29602
+ result = Series([2] * 10001).rmod(-1)
+ expected = Series([1] * 10001)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize(
+ "index",
+ [
+ date_range("2016-01-01", periods=3),
+ date_range("2016-01-01", tz="US/Pacific", periods=3),
+ pd.timedelta_range("1 Day", periods=3),
+ ],
+)
+def test_comparison_mismatched_datetime_units(index):
+ # GH#63459
+
+ ser = Series(1, index=index)
+ ser2 = Series(1, index=index.as_unit("ns"))
+
+ result = ser == ser2
+ expected = Series([True, True, True], index=ser.index)
+ tm.assert_series_equal(result, expected)
+
+ result2 = ser2 < ser
+ expected2 = Series([False, False, False], index=ser2.index)
+ tm.assert_series_equal(result2, expected2)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_arrow_interface.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_arrow_interface.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b4340064ea1bc4827c858d4deff602538240eb2
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_arrow_interface.py
@@ -0,0 +1,117 @@
+import ctypes
+
+import pytest
+
+import pandas.util._test_decorators as td
+
+import pandas as pd
+import pandas._testing as tm
+
+pa = pytest.importorskip("pyarrow", minversion="16.0")
+
+
+def test_series_arrow_interface():
+ s = pd.Series([1, 4, 2])
+
+ capsule = s.__arrow_c_stream__()
+ assert (
+ ctypes.pythonapi.PyCapsule_IsValid(
+ ctypes.py_object(capsule), b"arrow_array_stream"
+ )
+ == 1
+ )
+
+ ca = pa.chunked_array(s)
+ expected = pa.chunked_array([[1, 4, 2]])
+ assert ca.equals(expected)
+ ca = pa.chunked_array(s, type=pa.int32())
+ expected = pa.chunked_array([[1, 4, 2]], type=pa.int32())
+ assert ca.equals(expected)
+
+
+def test_series_arrow_interface_arrow_dtypes():
+ s = pd.Series([1, 4, 2], dtype="Int64[pyarrow]")
+
+ capsule = s.__arrow_c_stream__()
+ assert (
+ ctypes.pythonapi.PyCapsule_IsValid(
+ ctypes.py_object(capsule), b"arrow_array_stream"
+ )
+ == 1
+ )
+
+ ca = pa.chunked_array(s)
+ expected = pa.chunked_array([[1, 4, 2]])
+ assert ca.equals(expected)
+ ca = pa.chunked_array(s, type=pa.int32())
+ expected = pa.chunked_array([[1, 4, 2]], type=pa.int32())
+ assert ca.equals(expected)
+
+
+def test_series_arrow_interface_stringdtype():
+ s = pd.Series(["foo", "bar"], dtype="string[pyarrow]")
+
+ capsule = s.__arrow_c_stream__()
+ assert (
+ ctypes.pythonapi.PyCapsule_IsValid(
+ ctypes.py_object(capsule), b"arrow_array_stream"
+ )
+ == 1
+ )
+
+ ca = pa.chunked_array(s)
+ expected = pa.chunked_array([["foo", "bar"]], type=pa.large_string())
+ assert ca.equals(expected)
+
+
+class ArrowArrayWrapper:
+ def __init__(self, array):
+ self.array = array
+
+ def __arrow_c_array__(self, requested_schema=None):
+ return self.array.__arrow_c_array__(requested_schema)
+
+
+class ArrowStreamWrapper:
+ def __init__(self, chunked_array):
+ self.stream = chunked_array
+
+ def __arrow_c_stream__(self, requested_schema=None):
+ return self.stream.__arrow_c_stream__(requested_schema)
+
+
+@td.skip_if_no("pyarrow", min_version="14.0")
+def test_dataframe_from_arrow():
+ # objects with __arrow_c_stream__
+ arr = pa.chunked_array([[1, 2, 3], [4, 5]])
+
+ result = pd.Series.from_arrow(arr)
+ expected = pd.Series([1, 2, 3, 4, 5])
+ tm.assert_series_equal(result, expected)
+
+ # not only pyarrow object are supported
+ result = pd.Series.from_arrow(ArrowStreamWrapper(arr))
+ tm.assert_series_equal(result, expected)
+
+ # table works as well, but will be seen as a StructArray
+ table = pa.table({"a": [1, 2, 3], "b": ["a", "b", "c"]})
+
+ result = pd.Series.from_arrow(table)
+ expected = pd.Series([{"a": 1, "b": "a"}, {"a": 2, "b": "b"}, {"a": 3, "b": "c"}])
+ tm.assert_series_equal(result, expected)
+
+ # objects with __arrow_c_array__
+ arr = pa.array([1, 2, 3])
+
+ expected = pd.Series([1, 2, 3])
+ result = pd.Series.from_arrow(arr)
+ tm.assert_series_equal(result, expected)
+
+ result = pd.Series.from_arrow(ArrowArrayWrapper(arr))
+ tm.assert_series_equal(result, expected)
+
+ # only accept actual Arrow objects
+ with pytest.raises(
+ TypeError, match="Expected an Arrow-compatible array-like object"
+ ):
+ pd.Series.from_arrow([1, 2, 3])
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_constructors.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b0dcb9ec5facdbd08ba5608c64c4a454b6419db
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_constructors.py
@@ -0,0 +1,2294 @@
+from collections import OrderedDict
+from collections.abc import Iterator
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+from dateutil.tz import tzoffset
+import numpy as np
+from numpy import ma
+import pytest
+
+from pandas._libs import (
+ iNaT,
+ lib,
+)
+from pandas.compat import HAS_PYARROW
+from pandas.compat.numpy import np_version_gt2
+from pandas.errors import (
+ IntCastingNaNError,
+ Pandas4Warning,
+)
+
+from pandas.core.dtypes.dtypes import CategoricalDtype
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ DatetimeIndex,
+ DatetimeTZDtype,
+ Index,
+ Interval,
+ IntervalIndex,
+ MultiIndex,
+ NaT,
+ Period,
+ RangeIndex,
+ Series,
+ Timestamp,
+ date_range,
+ isna,
+ period_range,
+ timedelta_range,
+)
+import pandas._testing as tm
+from pandas.core.arrays import (
+ IntegerArray,
+ IntervalArray,
+ period_array,
+)
+from pandas.core.internals.blocks import NumpyBlock
+
+
+class TestSeriesConstructors:
+ def test_from_ints_with_non_nano_dt64_dtype(self, index_or_series):
+ values = np.arange(10)
+
+ res = index_or_series(values, dtype="M8[s]")
+ expected = index_or_series(values.astype("M8[s]"))
+ tm.assert_equal(res, expected)
+
+ res = index_or_series(list(values), dtype="M8[s]")
+ tm.assert_equal(res, expected)
+
+ def test_from_na_value_and_interval_of_datetime_dtype(self):
+ # GH#41805
+ ser = Series([None], dtype="interval[datetime64[ns]]")
+ assert ser.isna().all()
+ assert ser.dtype == "interval[datetime64[ns], right]"
+
+ def test_infer_with_date_and_datetime(self):
+ # GH#49341 pre-2.0 we inferred datetime-and-date to datetime64, which
+ # was inconsistent with Index behavior
+ ts = Timestamp(2016, 1, 1)
+ vals = [ts.to_pydatetime(), ts.date()]
+
+ ser = Series(vals)
+ expected = Series(vals, dtype=object)
+ tm.assert_series_equal(ser, expected)
+
+ idx = Index(vals)
+ expected = Index(vals, dtype=object)
+ tm.assert_index_equal(idx, expected)
+
+ def test_unparsable_strings_with_dt64_dtype(self):
+ # pre-2.0 these would be silently ignored and come back with object dtype
+ vals = ["aa"]
+ msg = "^Unknown datetime string format, unable to parse: aa$"
+ with pytest.raises(ValueError, match=msg):
+ Series(vals, dtype="datetime64[ns]")
+
+ with pytest.raises(ValueError, match=msg):
+ Series(np.array(vals, dtype=object), dtype="datetime64[ns]")
+
+ def test_invalid_dtype_conversion_datetime_to_timedelta(self):
+ # GH#60728
+ vals = Series([NaT, Timestamp(2025, 1, 1)], dtype="datetime64[ns]")
+ msg = r"^Cannot cast DatetimeArray to dtype timedelta64\[ns\]$"
+ with pytest.raises(TypeError, match=msg):
+ Series(vals, dtype="timedelta64[ns]")
+
+ @pytest.mark.parametrize(
+ "constructor",
+ [
+ # NOTE: some overlap with test_constructor_empty but that test does not
+ # test for None or an empty generator.
+ # test_constructor_pass_none tests None but only with the index also
+ # passed.
+ (lambda idx: Series(index=idx)),
+ (lambda idx: Series(None, index=idx)),
+ (lambda idx: Series({}, index=idx)),
+ (lambda idx: Series((), index=idx)),
+ (lambda idx: Series([], index=idx)),
+ (lambda idx: Series((_ for _ in []), index=idx)),
+ (lambda idx: Series(data=None, index=idx)),
+ (lambda idx: Series(data={}, index=idx)),
+ (lambda idx: Series(data=(), index=idx)),
+ (lambda idx: Series(data=[], index=idx)),
+ (lambda idx: Series(data=(_ for _ in []), index=idx)),
+ ],
+ )
+ @pytest.mark.parametrize("empty_index", [None, []])
+ def test_empty_constructor(self, constructor, empty_index):
+ # GH 49573 (addition of empty_index parameter)
+ expected = Series(index=empty_index)
+ result = constructor(empty_index)
+
+ assert result.dtype == object
+ assert len(result.index) == 0
+ tm.assert_series_equal(result, expected, check_index_type=True)
+
+ def test_invalid_dtype(self):
+ # GH15520
+ msg = "not understood"
+ invalid_list = [Timestamp, "Timestamp", list]
+ for dtype in invalid_list:
+ with pytest.raises(TypeError, match=msg):
+ Series([], name="time", dtype=dtype)
+
+ def test_invalid_compound_dtype(self):
+ # GH#13296
+ c_dtype = np.dtype([("a", "i8"), ("b", "f4")])
+ cdt_arr = np.array([(1, 0.4), (256, -13)], dtype=c_dtype)
+
+ with pytest.raises(ValueError, match="Use DataFrame instead"):
+ Series(cdt_arr, index=["A", "B"])
+
+ def test_scalar_conversion(self):
+ # Pass in scalar is disabled
+ scalar = Series(0.5)
+ assert not isinstance(scalar, float)
+
+ def test_scalar_extension_dtype(self, ea_scalar_and_dtype):
+ # GH 28401
+
+ ea_scalar, ea_dtype = ea_scalar_and_dtype
+
+ ser = Series(ea_scalar, index=range(3))
+ expected = Series([ea_scalar] * 3, dtype=ea_dtype)
+
+ assert ser.dtype == ea_dtype
+ tm.assert_series_equal(ser, expected)
+
+ def test_constructor(self, datetime_series, using_infer_string):
+ empty_series = Series()
+ assert datetime_series.index._is_all_dates
+
+ # Pass in Series
+ derived = Series(datetime_series)
+ assert derived.index._is_all_dates
+
+ tm.assert_index_equal(derived.index, datetime_series.index)
+ # Ensure new index is not created
+ assert id(datetime_series.index) == id(derived.index)
+
+ # Mixed type Series
+ mixed = Series(["hello", np.nan], index=[0, 1])
+ assert mixed.dtype == np.object_ if not using_infer_string else "str"
+ assert np.isnan(mixed[1])
+
+ assert not empty_series.index._is_all_dates
+ assert not Series().index._is_all_dates
+
+ # exception raised is of type ValueError GH35744
+ with pytest.raises(
+ ValueError,
+ match=r"Data must be 1-dimensional, got ndarray of shape \(3, 3\) instead",
+ ):
+ Series(np.random.default_rng(2).standard_normal((3, 3)), index=np.arange(3))
+
+ mixed.name = "Series"
+ rs = Series(mixed).name
+ xp = "Series"
+ assert rs == xp
+
+ # raise on MultiIndex GH4187
+ m = MultiIndex.from_arrays([[1, 2], [3, 4]])
+ msg = "initializing a Series from a MultiIndex is not supported"
+ with pytest.raises(NotImplementedError, match=msg):
+ Series(m)
+
+ def test_constructor_index_ndim_gt_1_raises(self):
+ # GH#18579
+ df = DataFrame([[1, 2], [3, 4], [5, 6]], index=[3, 6, 9])
+ with pytest.raises(ValueError, match="Index data must be 1-dimensional"):
+ Series([1, 3, 2], index=df)
+
+ @pytest.mark.parametrize("input_class", [list, dict, OrderedDict])
+ def test_constructor_empty(self, input_class, using_infer_string):
+ empty = Series()
+ empty2 = Series(input_class())
+
+ # these are Index() and RangeIndex() which don't compare type equal
+ # but are just .equals
+ tm.assert_series_equal(empty, empty2, check_index_type=False)
+
+ # With explicit dtype:
+ empty = Series(dtype="float64")
+ empty2 = Series(input_class(), dtype="float64")
+ tm.assert_series_equal(empty, empty2, check_index_type=False)
+
+ # GH 18515 : with dtype=category:
+ empty = Series(dtype="category")
+ empty2 = Series(input_class(), dtype="category")
+ tm.assert_series_equal(empty, empty2, check_index_type=False)
+
+ if input_class is not list:
+ # With index:
+ empty = Series(index=range(10))
+ empty2 = Series(input_class(), index=range(10))
+ tm.assert_series_equal(empty, empty2)
+
+ # With index and dtype float64:
+ empty = Series(np.nan, index=range(10))
+ empty2 = Series(input_class(), index=range(10), dtype="float64")
+ tm.assert_series_equal(empty, empty2)
+
+ # GH 19853 : with empty string, index and dtype str
+ empty = Series("", dtype=str, index=range(3))
+ if using_infer_string:
+ empty2 = Series("", index=range(3), dtype="str")
+ else:
+ empty2 = Series("", index=range(3))
+ tm.assert_series_equal(empty, empty2)
+
+ @pytest.mark.parametrize("input_arg", [np.nan, float("nan")])
+ def test_constructor_nan(self, input_arg):
+ empty = Series(dtype="float64", index=range(10))
+ empty2 = Series(input_arg, index=range(10))
+
+ tm.assert_series_equal(empty, empty2, check_index_type=False)
+
+ @pytest.mark.parametrize(
+ "dtype",
+ ["f8", "i8", "M8[ns]", "m8[ns]", "category", "object", "datetime64[ns, UTC]"],
+ )
+ @pytest.mark.parametrize("index", [None, Index([])])
+ def test_constructor_dtype_only(self, dtype, index):
+ # GH-20865
+ result = Series(dtype=dtype, index=index)
+ assert result.dtype == dtype
+ assert len(result) == 0
+
+ def test_constructor_no_data_index_order(self):
+ result = Series(index=["b", "a", "c"])
+ assert result.index.tolist() == ["b", "a", "c"]
+
+ def test_constructor_no_data_string_type(self):
+ # GH 22477
+ result = Series(index=[1], dtype=str)
+ assert np.isnan(result.iloc[0])
+
+ @pytest.mark.parametrize("item", ["entry", "ѐ", 13])
+ def test_constructor_string_element_string_type(self, item):
+ # GH 22477
+ result = Series(item, index=[1], dtype=str)
+ assert result.iloc[0] == str(item)
+
+ def test_constructor_dtype_str_na_values(self, string_dtype):
+ # https://github.com/pandas-dev/pandas/issues/21083
+ ser = Series(["x", None], dtype=string_dtype)
+ result = ser.isna()
+ expected = Series([False, True])
+ tm.assert_series_equal(result, expected)
+ assert ser.iloc[1] is None
+
+ ser = Series(["x", np.nan], dtype=string_dtype)
+ assert np.isnan(ser.iloc[1])
+
+ def test_constructor_series(self):
+ index1 = ["d", "b", "a", "c"]
+ index2 = sorted(index1)
+ s1 = Series([4, 7, -5, 3], index=index1)
+ s2 = Series(s1, index=index2)
+
+ tm.assert_series_equal(s2, s1.sort_index())
+
+ def test_constructor_iterable(self):
+ # GH 21987
+ class Iter:
+ def __iter__(self) -> Iterator:
+ yield from range(10)
+
+ expected = Series(list(range(10)), dtype="int64")
+ result = Series(Iter(), dtype="int64")
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_sequence(self):
+ # GH 21987
+ expected = Series(list(range(10)), dtype="int64")
+ result = Series(range(10), dtype="int64")
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_single_str(self):
+ # GH 21987
+ expected = Series(["abc"])
+ result = Series("abc")
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_list_like(self):
+ # make sure that we are coercing different
+ # list-likes to standard dtypes and not
+ # platform specific
+ expected = Series([1, 2, 3], dtype="int64")
+ for obj in [[1, 2, 3], (1, 2, 3), np.array([1, 2, 3], dtype="int64")]:
+ result = Series(obj, index=[0, 1, 2])
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_boolean_index(self):
+ # GH#18579
+ s1 = Series([1, 2, 3], index=[4, 5, 6])
+
+ index = s1 == 2
+ result = Series([1, 3, 2], index=index)
+ expected = Series([1, 3, 2], index=[False, True, False])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("dtype", ["bool", "int32", "int64", "float64"])
+ def test_constructor_index_dtype(self, dtype):
+ # GH 17088
+
+ s = Series(Index([0, 2, 4]), dtype=dtype)
+ assert s.dtype == dtype
+
+ @pytest.mark.parametrize(
+ "input_vals",
+ [
+ [1, 2],
+ ["1", "2"],
+ list(date_range("1/1/2011", periods=2, freq="h")),
+ list(date_range("1/1/2011", periods=2, freq="h", tz="US/Eastern")),
+ [Interval(left=0, right=5)],
+ ],
+ )
+ def test_constructor_list_str(self, input_vals, string_dtype):
+ # GH 16605
+ # Ensure that data elements from a list are converted to strings
+ # when dtype is str, 'str', or 'U'
+ result = Series(input_vals, dtype=string_dtype)
+ expected = Series(input_vals).astype(string_dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_list_str_na(self, string_dtype):
+ result = Series([1.0, 2.0, np.nan], dtype=string_dtype)
+ expected = Series(["1.0", "2.0", np.nan], dtype=object)
+ tm.assert_series_equal(result, expected)
+ assert np.isnan(result[2])
+
+ def test_constructor_generator(self):
+ gen = (i for i in range(10))
+
+ result = Series(gen)
+ exp = Series(range(10))
+ tm.assert_series_equal(result, exp)
+
+ # same but with non-default index
+ gen = (i for i in range(10))
+ result = Series(gen, index=range(10, 20))
+ exp.index = range(10, 20)
+ tm.assert_series_equal(result, exp)
+
+ def test_constructor_map(self):
+ # GH8909
+ m = (x for x in range(10))
+
+ result = Series(m)
+ exp = Series(range(10))
+ tm.assert_series_equal(result, exp)
+
+ # same but with non-default index
+ m = (x for x in range(10))
+ result = Series(m, index=range(10, 20))
+ exp.index = range(10, 20)
+ tm.assert_series_equal(result, exp)
+
+ def test_constructor_categorical(self):
+ msg = "Constructing a Categorical with a dtype and values containing"
+ with tm.assert_produces_warning(Pandas4Warning, match=msg):
+ cat = Categorical([0, 1, 2, 0, 1, 2], ["a", "b", "c"])
+ res = Series(cat)
+ tm.assert_categorical_equal(res.values, cat)
+
+ # can cast to a new dtype
+ result = Series(Categorical([1, 2, 3]), dtype="int64")
+ expected = Series([1, 2, 3], dtype="int64")
+ tm.assert_series_equal(result, expected)
+
+ def test_construct_from_categorical_with_dtype(self):
+ # GH12574
+ ser = Series(Categorical([1, 2, 3]), dtype="category")
+ assert isinstance(ser.dtype, CategoricalDtype)
+
+ def test_construct_intlist_values_category_dtype(self):
+ ser = Series([1, 2, 3], dtype="category")
+ assert isinstance(ser.dtype, CategoricalDtype)
+
+ def test_constructor_categorical_with_coercion(self):
+ factor = Categorical(["a", "b", "b", "a", "a", "c", "c", "c"])
+ # test basic creation / coercion of categoricals
+ s = Series(factor, name="A")
+ assert s.dtype == "category"
+ assert len(s) == len(factor)
+
+ # in a frame
+ df = DataFrame({"A": factor})
+ result = df["A"]
+ tm.assert_series_equal(result, s)
+ result = df.iloc[:, 0]
+ tm.assert_series_equal(result, s)
+ assert len(df) == len(factor)
+
+ df = DataFrame({"A": s})
+ result = df["A"]
+ tm.assert_series_equal(result, s)
+ assert len(df) == len(factor)
+
+ # multiples
+ df = DataFrame({"A": s, "B": s, "C": 1})
+ result1 = df["A"]
+ result2 = df["B"]
+ tm.assert_series_equal(result1, s)
+ tm.assert_series_equal(result2, s, check_names=False)
+ assert result2.name == "B"
+ assert len(df) == len(factor)
+
+ def test_constructor_categorical_with_coercion2(self):
+ # GH8623
+ x = DataFrame(
+ [[1, "John P. Doe"], [2, "Jane Dove"], [1, "John P. Doe"]],
+ columns=["person_id", "person_name"],
+ )
+ x["person_name"] = Categorical(x.person_name) # doing this breaks transform
+
+ expected = x.iloc[0].person_name
+ result = x.person_name.iloc[0]
+ assert result == expected
+
+ result = x.person_name[0]
+ assert result == expected
+
+ result = x.person_name.loc[0]
+ assert result == expected
+
+ def test_constructor_series_to_categorical(self):
+ # see GH#16524: test conversion of Series to Categorical
+ series = Series(["a", "b", "c"])
+
+ result = Series(series, dtype="category")
+ expected = Series(["a", "b", "c"], dtype="category")
+
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_categorical_dtype(self):
+ result = Series(
+ ["a", "b"], dtype=CategoricalDtype(["a", "b", "c"], ordered=True)
+ )
+ assert isinstance(result.dtype, CategoricalDtype)
+ tm.assert_index_equal(result.cat.categories, Index(["a", "b", "c"]))
+ assert result.cat.ordered
+
+ result = Series(["a", "b"], dtype=CategoricalDtype(["b", "a"]))
+ assert isinstance(result.dtype, CategoricalDtype)
+ tm.assert_index_equal(result.cat.categories, Index(["b", "a"]))
+ assert result.cat.ordered is False
+
+ # GH 19565 - Check broadcasting of scalar with Categorical dtype
+ result = Series(
+ "a", index=[0, 1], dtype=CategoricalDtype(["a", "b"], ordered=True)
+ )
+ expected = Series(
+ ["a", "a"], index=[0, 1], dtype=CategoricalDtype(["a", "b"], ordered=True)
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_categorical_string(self):
+ # GH 26336: the string 'category' maintains existing CategoricalDtype
+ cdt = CategoricalDtype(categories=list("dabc"), ordered=True)
+ expected = Series(list("abcabc"), dtype=cdt)
+
+ # Series(Categorical, dtype='category') keeps existing dtype
+ cat = Categorical(list("abcabc"), dtype=cdt)
+ result = Series(cat, dtype="category")
+ tm.assert_series_equal(result, expected)
+
+ # Series(Series[Categorical], dtype='category') keeps existing dtype
+ result = Series(result, dtype="category")
+ tm.assert_series_equal(result, expected)
+
+ def test_categorical_sideeffects_free(self):
+ # Passing a categorical to a Series and then changing values in either
+ # the series or the categorical should not change the values in the
+ # other one, IF you specify copy!
+ cat = Categorical(["a", "b", "c", "a"])
+ s = Series(cat, copy=True)
+ assert s.cat is not cat
+ s = s.cat.rename_categories([1, 2, 3])
+ exp_s = np.array([1, 2, 3, 1], dtype=np.int64)
+ exp_cat = np.array(["a", "b", "c", "a"], dtype=np.object_)
+ tm.assert_numpy_array_equal(s.__array__(), exp_s)
+ tm.assert_numpy_array_equal(cat.__array__(), exp_cat)
+
+ # setting
+ s[0] = 2
+ exp_s2 = np.array([2, 2, 3, 1], dtype=np.int64)
+ tm.assert_numpy_array_equal(s.__array__(), exp_s2)
+ tm.assert_numpy_array_equal(cat.__array__(), exp_cat)
+
+ # however, copy is False by default
+ # so this WILL change values
+ cat = Categorical(["a", "b", "c", "a"])
+ s = Series(cat, copy=False)
+ assert s._values is cat
+ s = s.cat.rename_categories([1, 2, 3])
+ assert s._values is not cat
+ exp_s = np.array([1, 2, 3, 1], dtype=np.int64)
+ tm.assert_numpy_array_equal(s.__array__(), exp_s)
+
+ s[0] = 2
+ exp_s2 = np.array([2, 2, 3, 1], dtype=np.int64)
+ tm.assert_numpy_array_equal(s.__array__(), exp_s2)
+
+ def test_unordered_compare_equal(self):
+ left = Series(["a", "b", None], dtype=CategoricalDtype(["a", "b"]))
+ right = Series(Categorical(["a", "b", np.nan], categories=["a", "b"]))
+ tm.assert_series_equal(left, right)
+
+ def test_constructor_maskedarray(self):
+ data = ma.masked_all((3,), dtype=float)
+ result = Series(data)
+ expected = Series([np.nan, np.nan, np.nan])
+ tm.assert_series_equal(result, expected)
+
+ data[0] = 0.0
+ data[2] = 2.0
+ index = ["a", "b", "c"]
+ result = Series(data, index=index)
+ expected = Series([0.0, np.nan, 2.0], index=index)
+ tm.assert_series_equal(result, expected)
+
+ data[1] = 1.0
+ result = Series(data, index=index)
+ expected = Series([0.0, 1.0, 2.0], index=index)
+ tm.assert_series_equal(result, expected)
+
+ data = ma.masked_all((3,), dtype=int)
+ result = Series(data)
+ expected = Series([np.nan, np.nan, np.nan], dtype=float)
+ tm.assert_series_equal(result, expected)
+
+ data[0] = 0
+ data[2] = 2
+ index = ["a", "b", "c"]
+ result = Series(data, index=index)
+ expected = Series([0, np.nan, 2], index=index, dtype=float)
+ tm.assert_series_equal(result, expected)
+
+ data[1] = 1
+ result = Series(data, index=index)
+ expected = Series([0, 1, 2], index=index, dtype=int)
+ with pytest.raises(AssertionError, match="Series classes are different"):
+ # TODO should this be raising at all?
+ # https://github.com/pandas-dev/pandas/issues/56131
+ tm.assert_series_equal(result, expected)
+
+ data = ma.masked_all((3,), dtype=bool)
+ result = Series(data)
+ expected = Series([np.nan, np.nan, np.nan], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ data[0] = True
+ data[2] = False
+ index = ["a", "b", "c"]
+ result = Series(data, index=index)
+ expected = Series([True, np.nan, False], index=index, dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ data[1] = True
+ result = Series(data, index=index)
+ expected = Series([True, True, False], index=index, dtype=bool)
+ with pytest.raises(AssertionError, match="Series classes are different"):
+ # TODO should this be raising at all?
+ # https://github.com/pandas-dev/pandas/issues/56131
+ tm.assert_series_equal(result, expected)
+
+ data = ma.masked_all((3,), dtype="M8[ns]")
+ result = Series(data)
+ expected = Series([iNaT, iNaT, iNaT], dtype="M8[ns]")
+ tm.assert_series_equal(result, expected)
+
+ data[0] = datetime(2001, 1, 1)
+ data[2] = datetime(2001, 1, 3)
+ index = ["a", "b", "c"]
+ result = Series(data, index=index)
+ expected = Series(
+ [datetime(2001, 1, 1), iNaT, datetime(2001, 1, 3)],
+ index=index,
+ dtype="M8[ns]",
+ )
+ tm.assert_series_equal(result, expected)
+
+ data[1] = datetime(2001, 1, 2)
+ result = Series(data, index=index)
+ expected = Series(
+ [datetime(2001, 1, 1), datetime(2001, 1, 2), datetime(2001, 1, 3)],
+ index=index,
+ dtype="M8[ns]",
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_maskedarray_hardened(self):
+ # Check numpy masked arrays with hard masks -- from GH24574
+ data = ma.masked_all((3,), dtype=float).harden_mask()
+ result = Series(data)
+ expected = Series([np.nan, np.nan, np.nan])
+ tm.assert_series_equal(result, expected)
+
+ def test_series_ctor_plus_datetimeindex(self):
+ rng = date_range("20090415", "20090519", freq="B")
+ data = dict.fromkeys(rng, 1)
+
+ result = Series(data, index=rng)
+ assert result.index.is_(rng)
+
+ def test_constructor_default_index(self):
+ s = Series([0, 1, 2])
+ tm.assert_index_equal(s.index, Index(range(3)), exact=True)
+
+ @pytest.mark.parametrize(
+ "input",
+ [
+ [1, 2, 3],
+ (1, 2, 3),
+ list(range(3)),
+ Categorical(["a", "b", "a"]),
+ (i for i in range(3)),
+ (x for x in range(3)),
+ ],
+ )
+ def test_constructor_index_mismatch(self, input):
+ # GH 19342
+ # test that construction of a Series with an index of different length
+ # raises an error
+ msg = r"Length of values \(3\) does not match length of index \(4\)"
+ with pytest.raises(ValueError, match=msg):
+ Series(input, index=np.arange(4))
+
+ def test_constructor_numpy_scalar(self):
+ # GH 19342
+ # construction with a numpy scalar
+ # should not raise
+ result = Series(np.array(100), index=np.arange(4), dtype="int64")
+ expected = Series(100, index=np.arange(4), dtype="int64")
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_broadcast_list(self):
+ # GH 19342
+ # construction with single-element container and index
+ # should raise
+ msg = r"Length of values \(1\) does not match length of index \(3\)"
+ with pytest.raises(ValueError, match=msg):
+ Series(["foo"], index=["a", "b", "c"])
+
+ def test_constructor_corner(self):
+ df = DataFrame(range(5), index=date_range("2020-01-01", periods=5))
+ objs = [df, df]
+ s = Series(objs, index=[0, 1])
+ assert isinstance(s, Series)
+
+ def test_constructor_sanitize(self):
+ s = Series(np.array([1.0, 1.0, 8.0]), dtype="i8")
+ assert s.dtype == np.dtype("i8")
+
+ msg = r"Cannot convert non-finite values \(NA or inf\) to integer"
+ with pytest.raises(IntCastingNaNError, match=msg):
+ Series(np.array([1.0, 1.0, np.nan]), copy=True, dtype="i8")
+
+ def test_constructor_copy(self):
+ # GH15125
+ # test dtype parameter has no side effects on copy=True
+ for data in [[1.0], np.array([1.0])]:
+ x = Series(data)
+ y = Series(x, copy=True, dtype=float)
+
+ # copy=True maintains original data in Series
+ tm.assert_series_equal(x, y)
+
+ # changes to origin of copy does not affect the copy
+ x[0] = 2.0
+ assert not x.equals(y)
+ assert x[0] == 2.0
+ assert y[0] == 1.0
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ date_range("20170101", periods=3, tz="US/Eastern"),
+ date_range("20170101", periods=3),
+ timedelta_range("1 day", periods=3),
+ period_range("2012Q1", periods=3, freq="Q"),
+ Index(list("abc")),
+ Index([1, 2, 3]),
+ RangeIndex(0, 3),
+ ],
+ ids=lambda x: type(x).__name__,
+ )
+ def test_constructor_limit_copies(self, index):
+ # GH 17449
+ # limit copies of input
+ s = Series(index)
+
+ # we make 1 copy; this is just a smoke test here
+ assert s._mgr.blocks[0].values is not index
+
+ def test_constructor_shallow_copy(self):
+ # constructing a Series from Series with copy=False should still
+ # give a "shallow" copy (share data, not attributes)
+ # https://github.com/pandas-dev/pandas/issues/49523
+ s = Series([1, 2, 3])
+ s_orig = s.copy()
+ s2 = Series(s)
+ assert s2._mgr is not s._mgr
+ # Overwriting index of s2 doesn't change s
+ s2.index = ["a", "b", "c"]
+ tm.assert_series_equal(s, s_orig)
+
+ def test_constructor_pass_none(self):
+ s = Series(None, index=range(5))
+ assert s.dtype == np.float64
+
+ s = Series(None, index=range(5), dtype=object)
+ assert s.dtype == np.object_
+
+ # GH 7431
+ # inference on the index
+ s = Series(index=np.array([None]))
+ expected = Series(index=Index([None]))
+ tm.assert_series_equal(s, expected)
+
+ def test_constructor_pass_nan_nat(self):
+ # GH 13467
+ exp = Series([np.nan, np.nan], dtype=np.float64)
+ assert exp.dtype == np.float64
+ tm.assert_series_equal(Series([np.nan, np.nan]), exp)
+ tm.assert_series_equal(Series(np.array([np.nan, np.nan])), exp)
+
+ exp = Series([NaT, NaT])
+ assert exp.dtype == "datetime64[s]"
+ tm.assert_series_equal(Series([NaT, NaT]), exp)
+ tm.assert_series_equal(Series(np.array([NaT, NaT])), exp)
+
+ tm.assert_series_equal(Series([NaT, np.nan]), exp)
+ tm.assert_series_equal(Series(np.array([NaT, np.nan])), exp)
+
+ tm.assert_series_equal(Series([np.nan, NaT]), exp)
+ tm.assert_series_equal(Series(np.array([np.nan, NaT])), exp)
+
+ def test_constructor_cast(self):
+ msg = "could not convert string to float"
+ with pytest.raises(ValueError, match=msg):
+ Series(["a", "b", "c"], dtype=float)
+
+ def test_constructor_signed_int_overflow_raises(self):
+ # GH#41734 disallow silent overflow, enforced in 2.0
+ if np_version_gt2:
+ msg = "The elements provided in the data cannot all be casted to the dtype"
+ err = OverflowError
+ else:
+ msg = "Values are too large to be losslessly converted"
+ err = ValueError
+ with pytest.raises(err, match=msg):
+ Series([1, 200, 923442], dtype="int8")
+
+ with pytest.raises(err, match=msg):
+ Series([1, 200, 923442], dtype="uint8")
+
+ @pytest.mark.parametrize(
+ "values",
+ [
+ np.array([1], dtype=np.uint16),
+ np.array([1], dtype=np.uint32),
+ np.array([1], dtype=np.uint64),
+ [np.uint16(1)],
+ [np.uint32(1)],
+ [np.uint64(1)],
+ ],
+ )
+ def test_constructor_numpy_uints(self, values):
+ # GH#47294
+ value = values[0]
+ result = Series(values)
+
+ assert result[0].dtype == value.dtype
+ assert result[0] == value
+
+ def test_constructor_unsigned_dtype_overflow(self, any_unsigned_int_numpy_dtype):
+ # see gh-15832
+ if np_version_gt2:
+ msg = (
+ f"The elements provided in the data cannot "
+ f"all be casted to the dtype {any_unsigned_int_numpy_dtype}"
+ )
+ else:
+ msg = "Trying to coerce negative values to unsigned integers"
+ with pytest.raises(OverflowError, match=msg):
+ Series([-1], dtype=any_unsigned_int_numpy_dtype)
+
+ def test_constructor_floating_data_int_dtype(self, frame_or_series):
+ # GH#40110
+ arr = np.random.default_rng(2).standard_normal(2)
+
+ # Long-standing behavior (for Series, new in 2.0 for DataFrame)
+ # has been to ignore the dtype on these;
+ # not clear if this is what we want long-term
+ # expected = frame_or_series(arr)
+
+ # GH#49599 as of 2.0 we raise instead of silently retaining float dtype
+ msg = "Trying to coerce float values to integer"
+ with pytest.raises(ValueError, match=msg):
+ frame_or_series(arr, dtype="i8")
+
+ with pytest.raises(ValueError, match=msg):
+ frame_or_series(list(arr), dtype="i8")
+
+ # pre-2.0, when we had NaNs, we silently ignored the integer dtype
+ arr[0] = np.nan
+ # expected = frame_or_series(arr)
+
+ msg = r"Cannot convert non-finite values \(NA or inf\) to integer"
+ with pytest.raises(IntCastingNaNError, match=msg):
+ frame_or_series(arr, dtype="i8")
+
+ exc = IntCastingNaNError
+ if frame_or_series is Series:
+ # TODO: try to align these
+ exc = ValueError
+ msg = "cannot convert float NaN to integer"
+ with pytest.raises(exc, match=msg):
+ # same behavior if we pass list instead of the ndarray
+ frame_or_series(list(arr), dtype="i8")
+
+ # float array that can be losslessly cast to integers
+ arr = np.array([1.0, 2.0], dtype="float64")
+ expected = frame_or_series(arr.astype("i8"))
+
+ obj = frame_or_series(arr, dtype="i8")
+ tm.assert_equal(obj, expected)
+
+ obj = frame_or_series(list(arr), dtype="i8")
+ tm.assert_equal(obj, expected)
+
+ def test_constructor_coerce_float_fail(self, any_int_numpy_dtype):
+ # see gh-15832
+ # Updated: make sure we treat this list the same as we would treat
+ # the equivalent ndarray
+ # GH#49599 pre-2.0 we silently retained float dtype, in 2.0 we raise
+ vals = [1, 2, 3.5]
+
+ msg = "Trying to coerce float values to integer"
+ with pytest.raises(ValueError, match=msg):
+ Series(vals, dtype=any_int_numpy_dtype)
+ with pytest.raises(ValueError, match=msg):
+ Series(np.array(vals), dtype=any_int_numpy_dtype)
+
+ def test_constructor_coerce_float_valid(self, float_numpy_dtype):
+ s = Series([1, 2, 3.5], dtype=float_numpy_dtype)
+ expected = Series([1, 2, 3.5]).astype(float_numpy_dtype)
+ tm.assert_series_equal(s, expected)
+
+ def test_constructor_invalid_coerce_ints_with_float_nan(self, any_int_numpy_dtype):
+ # GH 22585
+ # Updated: make sure we treat this list the same as we would treat the
+ # equivalent ndarray
+ vals = [1, 2, np.nan]
+ # pre-2.0 this would return with a float dtype, in 2.0 we raise
+
+ msg = "cannot convert float NaN to integer"
+ with pytest.raises(ValueError, match=msg):
+ Series(vals, dtype=any_int_numpy_dtype)
+ msg = r"Cannot convert non-finite values \(NA or inf\) to integer"
+ with pytest.raises(IntCastingNaNError, match=msg):
+ Series(np.array(vals), dtype=any_int_numpy_dtype)
+
+ def test_constructor_dtype_no_cast(self):
+ # see gh-1572
+ s = Series([1, 2, 3])
+ s2 = Series(s, dtype=np.int64)
+
+ s2[1] = 5
+ assert s[1] == 2
+
+ def test_constructor_datelike_coercion(self):
+ # GH 9477
+ # incorrectly inferring on dateimelike looking when object dtype is
+ # specified
+ s = Series([Timestamp("20130101"), "NOV"], dtype=object)
+ assert s.iloc[0] == Timestamp("20130101")
+ assert s.iloc[1] == "NOV"
+ assert s.dtype == object
+
+ def test_constructor_datelike_coercion2(self):
+ # the dtype was being reset on the slicing and re-inferred to datetime
+ # even thought the blocks are mixed
+ belly = "216 3T19".split()
+ wing1 = "2T15 4H19".split()
+ wing2 = "416 4T20".split()
+ mat = pd.to_datetime("2016-01-22 2019-09-07".split())
+ df = DataFrame({"wing1": wing1, "wing2": wing2, "mat": mat}, index=belly)
+
+ result = df.loc["3T19"]
+ assert result.dtype == object
+ result = df.loc["216"]
+ assert result.dtype == object
+
+ def test_constructor_mixed_int_and_timestamp(self, frame_or_series):
+ # specifically Timestamp with nanos, not datetimes
+ objs = [Timestamp(9), 10, NaT._value]
+ result = frame_or_series(objs, dtype="M8[ns]")
+
+ expected = frame_or_series([Timestamp(9), Timestamp(10), NaT])
+ tm.assert_equal(result, expected)
+
+ def test_constructor_datetimes_with_nulls(self):
+ # gh-15869
+ for arr in [
+ np.array([None, None, None, None, datetime.now(), None]),
+ np.array([None, None, datetime.now(), None]),
+ ]:
+ result = Series(arr)
+ assert result.dtype == "M8[us]"
+
+ def test_constructor_dtype_datetime64(self):
+ s = Series(iNaT, dtype="M8[ns]", index=range(5))
+ assert isna(s).all()
+
+ # in theory this should be all nulls, but since
+ # we are not specifying a dtype is ambiguous
+ s = Series(iNaT, index=range(5))
+ assert not isna(s).all()
+
+ s = Series(np.nan, dtype="M8[ns]", index=range(5))
+ assert isna(s).all()
+
+ s = Series([datetime(2001, 1, 2, 0, 0), iNaT], dtype="M8[ns]")
+ assert isna(s[1])
+ assert s.dtype == "M8[ns]"
+
+ s = Series([datetime(2001, 1, 2, 0, 0), np.nan], dtype="M8[ns]")
+ assert isna(s[1])
+ assert s.dtype == "M8[ns]"
+
+ def test_constructor_dtype_datetime64_10(self):
+ # GH3416
+ pydates = [datetime(2013, 1, 1), datetime(2013, 1, 2), datetime(2013, 1, 3)]
+ dates = [np.datetime64(x) for x in pydates]
+
+ ser = Series(dates)
+ assert ser.dtype == "M8[us]"
+
+ ser.iloc[0] = np.nan
+ assert ser.dtype == "M8[us]"
+
+ # GH3414 related
+ expected = Series(pydates, dtype="datetime64[ms]")
+
+ result = Series(Series(dates).astype(np.int64) / 1000, dtype="M8[ms]")
+ tm.assert_series_equal(result, expected)
+
+ result = Series(dates, dtype="datetime64[ms]")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series(
+ [NaT, datetime(2013, 1, 2), datetime(2013, 1, 3)], dtype="datetime64[ns]"
+ )
+ result = Series([np.nan, *dates[1:]], dtype="datetime64[ns]")
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_dtype_datetime64_11(self):
+ pydates = [datetime(2013, 1, 1), datetime(2013, 1, 2), datetime(2013, 1, 3)]
+ dates = [np.datetime64(x) for x in pydates]
+
+ dts = Series(dates, dtype="datetime64[ns]")
+
+ # valid astype
+ dts.astype("int64")
+
+ # invalid casting
+ msg = r"Converting from datetime64\[ns\] to int32 is not supported"
+ with pytest.raises(TypeError, match=msg):
+ dts.astype("int32")
+
+ # ints are ok
+ # we test with np.int64 to get similar results on
+ # windows / 32-bit platforms
+ result = Series(dts, dtype=np.int64)
+ expected = Series(dts.astype(np.int64))
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_dtype_datetime64_9(self):
+ # invalid dates can be help as object
+ result = Series([datetime(2, 1, 1)])
+ assert result[0] == datetime(2, 1, 1, 0, 0)
+
+ result = Series([datetime(3000, 1, 1)])
+ assert result[0] == datetime(3000, 1, 1, 0, 0)
+
+ def test_constructor_dtype_datetime64_8(self):
+ # don't mix types
+ result = Series([Timestamp("20130101"), 1], index=["a", "b"])
+ assert result["a"] == Timestamp("20130101")
+ assert result["b"] == 1
+
+ def test_constructor_dtype_datetime64_7(self):
+ # GH6529
+ # coerce datetime64 non-ns properly
+ dates = date_range("01-Jan-2015", "01-Dec-2015", freq="ME")
+ values2 = dates.view(np.ndarray).astype("datetime64[ns]")
+ expected = Series(values2, index=dates)
+
+ for unit in ["s", "D", "ms", "us", "ns"]:
+ dtype = np.dtype(f"M8[{unit}]")
+ values1 = dates.view(np.ndarray).astype(dtype)
+ result = Series(values1, dates)
+ if unit == "D":
+ # for unit="D" we cast to nearest-supported reso, i.e. "s"
+ dtype = np.dtype("M8[s]")
+ assert result.dtype == dtype
+ tm.assert_series_equal(result, expected.astype(dtype))
+
+ # GH 13876
+ # coerce to non-ns to object properly
+ expected = Series(values2, index=dates, dtype=object)
+ for dtype in ["s", "D", "ms", "us", "ns"]:
+ values1 = dates.view(np.ndarray).astype(f"M8[{dtype}]")
+ result = Series(values1, index=dates, dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ # leave datetime.date alone
+ dates2 = np.array([d.date() for d in dates.to_pydatetime()], dtype=object)
+ series1 = Series(dates2, dates)
+ tm.assert_numpy_array_equal(series1.values, dates2)
+ assert series1.dtype == object
+
+ def test_constructor_dtype_datetime64_6(self):
+ # as of 2.0, these no longer infer datetime64 based on the strings,
+ # matching the Index behavior
+
+ ser = Series([None, NaT, "2013-08-05 15:30:00.000001"])
+ assert ser.dtype == object
+
+ ser = Series([np.nan, NaT, "2013-08-05 15:30:00.000001"])
+ assert ser.dtype == object
+
+ ser = Series([NaT, None, "2013-08-05 15:30:00.000001"])
+ assert ser.dtype == object
+
+ ser = Series([NaT, np.nan, "2013-08-05 15:30:00.000001"])
+ assert ser.dtype == object
+
+ def test_constructor_dtype_datetime64_5(self):
+ # tz-aware (UTC and other tz's)
+ # GH 8411
+ dr = date_range("20130101", periods=3)
+ assert Series(dr).iloc[0].tz is None
+ dr = date_range("20130101", periods=3, tz="UTC")
+ assert str(Series(dr).iloc[0].tz) == "UTC"
+ dr = date_range("20130101", periods=3, tz="US/Eastern")
+ assert str(Series(dr).iloc[0].tz) == "US/Eastern"
+
+ def test_constructor_dtype_datetime64_4(self):
+ # non-convertible
+ ser = Series([1479596223000, -1479590, NaT])
+ assert ser.dtype == "object"
+ assert ser[2] is NaT
+ assert "NaT" in str(ser)
+
+ def test_constructor_dtype_datetime64_3(self):
+ # if we passed a NaT it remains
+ ser = Series([datetime(2010, 1, 1), datetime(2, 1, 1), NaT])
+ assert ser.dtype == "M8[us]"
+ assert ser[2] is NaT
+ assert "NaT" in str(ser)
+
+ def test_constructor_dtype_datetime64_2(self):
+ # if we passed a nan it remains
+ ser = Series([datetime(2010, 1, 1), datetime(2, 1, 1), np.nan])
+ assert ser.dtype == "M8[us]"
+ assert ser[2] is NaT
+ assert "NaT" in str(ser)
+
+ def test_constructor_with_datetime_tz(self):
+ # 8260
+ # support datetime64 with tz
+
+ dr = date_range("20130101", periods=3, tz="US/Eastern", unit="ns")
+ s = Series(dr)
+ assert s.dtype.name == "datetime64[ns, US/Eastern]"
+ assert s.dtype == "datetime64[ns, US/Eastern]"
+ assert isinstance(s.dtype, DatetimeTZDtype)
+ assert "datetime64[ns, US/Eastern]" in str(s)
+
+ # export
+ result = s.values
+ assert isinstance(result, np.ndarray)
+ assert result.dtype == "datetime64[ns]"
+
+ exp = DatetimeIndex(result)
+ exp = exp.tz_localize("UTC").tz_convert(tz=s.dt.tz)
+ tm.assert_index_equal(dr, exp)
+
+ # indexing
+ result = s.iloc[0]
+ assert result == Timestamp("2013-01-01 00:00:00-0500", tz="US/Eastern")
+ result = s[0]
+ assert result == Timestamp("2013-01-01 00:00:00-0500", tz="US/Eastern")
+
+ result = s[Series([True, True, False], index=s.index)]
+ tm.assert_series_equal(result, s[0:2])
+
+ result = s.iloc[0:1]
+ tm.assert_series_equal(result, Series(dr[0:1]))
+
+ # concat
+ result = pd.concat([s.iloc[0:1], s.iloc[1:]])
+ tm.assert_series_equal(result, s)
+
+ # short str
+ assert "datetime64[ns, US/Eastern]" in str(s)
+
+ # formatting with NaT
+ result = s.shift()
+ assert "datetime64[ns, US/Eastern]" in str(result)
+ assert "NaT" in str(result)
+
+ result = DatetimeIndex(s, freq="infer")
+ tm.assert_index_equal(result, dr)
+
+ def test_constructor_with_datetime_tz5(self):
+ # long str
+ ser = Series(date_range("20130101", periods=1000, tz="US/Eastern", unit="ns"))
+ assert "datetime64[ns, US/Eastern]" in str(ser)
+
+ def test_constructor_with_datetime_tz4(self):
+ # inference
+ ser = Series(
+ [
+ Timestamp("2013-01-01 13:00:00-0800", tz="US/Pacific").as_unit("s"),
+ Timestamp("2013-01-02 14:00:00-0800", tz="US/Pacific").as_unit("s"),
+ ]
+ )
+ assert ser.dtype == "datetime64[s, US/Pacific]"
+ assert lib.infer_dtype(ser, skipna=True) == "datetime64"
+
+ def test_constructor_with_datetime_tz3(self):
+ ser = Series(
+ [
+ Timestamp("2013-01-01 13:00:00-0800", tz="US/Pacific"),
+ Timestamp("2013-01-02 14:00:00-0800", tz="US/Eastern"),
+ ]
+ )
+ assert ser.dtype == "object"
+ assert lib.infer_dtype(ser, skipna=True) == "datetime"
+
+ def test_constructor_with_datetime_tz2(self):
+ # with all NaT
+ ser = Series(NaT, index=[0, 1], dtype="datetime64[ns, US/Eastern]")
+ dti = DatetimeIndex(["NaT", "NaT"], tz="US/Eastern").as_unit("ns")
+ expected = Series(dti)
+ tm.assert_series_equal(ser, expected)
+
+ def test_constructor_no_partial_datetime_casting(self):
+ # GH#40111
+ vals = [
+ "nan",
+ Timestamp("1990-01-01"),
+ "2015-03-14T16:15:14.123-08:00",
+ "2019-03-04T21:56:32.620-07:00",
+ None,
+ ]
+ ser = Series(vals)
+ assert all(ser[i] is vals[i] for i in range(len(vals)))
+
+ @pytest.mark.parametrize("arr_dtype", [np.int64, np.float64])
+ @pytest.mark.parametrize("kind", ["M", "m"])
+ @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s", "h", "m", "D"])
+ def test_construction_to_datetimelike_unit(self, arr_dtype, kind, unit):
+ # tests all units
+ # gh-19223
+ # TODO: GH#19223 was about .astype, doesn't belong here
+ dtype = f"{kind}8[{unit}]"
+ arr = np.array([1, 2, 3], dtype=arr_dtype)
+ ser = Series(arr)
+ result = ser.astype(dtype)
+
+ expected = Series(arr.astype(dtype))
+
+ if unit in ["ns", "us", "ms", "s"]:
+ assert result.dtype == dtype
+ assert expected.dtype == dtype
+ else:
+ # Otherwise we cast to nearest-supported unit, i.e. seconds
+ assert result.dtype == f"{kind}8[s]"
+ assert expected.dtype == f"{kind}8[s]"
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("arg", ["2013-01-01 00:00:00", NaT, np.nan, None])
+ def test_constructor_with_naive_string_and_datetimetz_dtype(self, arg):
+ # GH 17415: With naive string
+ result = Series([arg], dtype="datetime64[ns, CET]")
+ expected = Series([Timestamp(arg)], dtype="M8[ns]").dt.tz_localize("CET")
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_datetime64_bigendian(self):
+ # GH#30976
+ ms = np.datetime64(1, "ms")
+ arr = np.array([np.datetime64(1, "ms")], dtype=">M8[ms]")
+
+ result = Series(arr)
+ expected = Series([Timestamp(ms)]).astype("M8[ms]")
+ assert expected.dtype == "M8[ms]"
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("interval_constructor", [IntervalIndex, IntervalArray])
+ def test_construction_interval(self, interval_constructor):
+ # construction from interval & array of intervals
+ intervals = interval_constructor.from_breaks(np.arange(3), closed="right")
+ result = Series(intervals)
+ assert result.dtype == "interval[int64, right]"
+ tm.assert_index_equal(Index(result.values), Index(intervals))
+
+ @pytest.mark.parametrize(
+ "data_constructor", [list, np.array], ids=["list", "ndarray[object]"]
+ )
+ def test_constructor_infer_interval(self, data_constructor):
+ # GH 23563: consistent closed results in interval dtype
+ data = [Interval(0, 1), Interval(0, 2), None]
+ result = Series(data_constructor(data))
+ expected = Series(IntervalArray(data))
+ assert result.dtype == "interval[float64, right]"
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "data_constructor", [list, np.array], ids=["list", "ndarray[object]"]
+ )
+ def test_constructor_interval_mixed_closed(self, data_constructor):
+ # GH 23563: mixed closed results in object dtype (not interval dtype)
+ data = [Interval(0, 1, closed="both"), Interval(0, 2, closed="neither")]
+ result = Series(data_constructor(data))
+ assert result.dtype == object
+ assert result.tolist() == data
+
+ def test_construction_consistency(self):
+ # make sure that we are not re-localizing upon construction
+ # GH 14928
+ ser = Series(date_range("20130101", periods=3, tz="US/Eastern"))
+
+ result = Series(ser, dtype=ser.dtype)
+ tm.assert_series_equal(result, ser)
+
+ result = Series(ser.dt.tz_convert("UTC"), dtype=ser.dtype)
+ tm.assert_series_equal(result, ser)
+
+ # Pre-2.0 dt64 values were treated as utc, which was inconsistent
+ # with DatetimeIndex, which treats them as wall times, see GH#33401
+ result = Series(ser.values, dtype=ser.dtype)
+ expected = Series(ser.values).dt.tz_localize(ser.dtype.tz)
+ tm.assert_series_equal(result, expected)
+
+ with tm.assert_produces_warning(None):
+ # one suggested alternative to the deprecated (changed in 2.0) usage
+ middle = Series(ser.values).dt.tz_localize("UTC")
+ result = middle.dt.tz_convert(ser.dtype.tz)
+ tm.assert_series_equal(result, ser)
+
+ with tm.assert_produces_warning(None):
+ # the other suggested alternative to the deprecated usage
+ result = Series(ser.values.view("int64"), dtype=ser.dtype)
+ tm.assert_series_equal(result, ser)
+
+ @pytest.mark.parametrize(
+ "data_constructor", [list, np.array], ids=["list", "ndarray[object]"]
+ )
+ def test_constructor_infer_period(self, data_constructor):
+ data = [Period("2000", "D"), Period("2001", "D"), None]
+ result = Series(data_constructor(data))
+ expected = Series(period_array(data))
+ tm.assert_series_equal(result, expected)
+ assert result.dtype == "Period[D]"
+
+ @pytest.mark.xfail(reason="PeriodDtype Series not supported yet")
+ def test_construct_from_ints_including_iNaT_scalar_period_dtype(self):
+ series = Series([0, 1000, 2000, pd._libs.iNaT], dtype="period[D]")
+
+ val = series[3]
+ assert isna(val)
+
+ series[2] = val
+ assert isna(series[2])
+
+ def test_constructor_period_incompatible_frequency(self):
+ data = [Period("2000", "D"), Period("2001", "Y")]
+ result = Series(data)
+ assert result.dtype == object
+ assert result.tolist() == data
+
+ def test_constructor_periodindex(self):
+ # GH7932
+ # converting a PeriodIndex when put in a Series
+
+ pi = period_range("20130101", periods=5, freq="D")
+ s = Series(pi)
+ assert s.dtype == "Period[D]"
+ expected = Series(pi.astype(object))
+ assert expected.dtype == object
+
+ def test_constructor_dict(self):
+ d = {"a": 0.0, "b": 1.0, "c": 2.0}
+
+ result = Series(d)
+ expected = Series(d, index=sorted(d.keys()))
+ tm.assert_series_equal(result, expected)
+
+ result = Series(d, index=["b", "c", "d", "a"])
+ expected = Series([1, 2, np.nan, 0], index=["b", "c", "d", "a"])
+ tm.assert_series_equal(result, expected)
+
+ pidx = period_range("2020-01-01", periods=10, freq="D")
+ d = {pidx[0]: 0, pidx[1]: 1}
+ result = Series(d, index=pidx)
+ expected = Series(np.nan, pidx, dtype=np.float64)
+ expected.iloc[0] = 0
+ expected.iloc[1] = 1
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_dict_list_value_explicit_dtype(self):
+ # GH 18625
+ d = {"a": [[2], [3], [4]]}
+ result = Series(d, index=["a"], dtype="object")
+ expected = Series(d, index=["a"])
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_dict_order(self):
+ # GH19018
+ # initialization ordering: by insertion order
+ d = {"b": 1, "a": 0, "c": 2}
+ result = Series(d)
+ expected = Series([1, 0, 2], index=list("bac"))
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_dict_extension(self, ea_scalar_and_dtype):
+ ea_scalar, ea_dtype = ea_scalar_and_dtype
+ d = {"a": ea_scalar}
+ result = Series(d, index=["a"])
+ expected = Series(ea_scalar, index=["a"], dtype=ea_dtype)
+
+ assert result.dtype == ea_dtype
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("value", [2, np.nan, None, float("nan")])
+ def test_constructor_dict_nan_key(self, value):
+ # GH 18480
+ d = {1: "a", value: "b", float("nan"): "c", 4: "d"}
+ result = Series(d).sort_values()
+ expected = Series(["a", "b", "c", "d"], index=[1, value, np.nan, 4])
+ tm.assert_series_equal(result, expected)
+
+ # MultiIndex:
+ d = {(1, 1): "a", (2, np.nan): "b", (3, value): "c"}
+ result = Series(d).sort_values()
+ expected = Series(
+ ["a", "b", "c"], index=Index([(1, 1), (2, np.nan), (3, value)])
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_dict_datetime64_index(self):
+ # GH 9456
+
+ dates_as_str = ["1984-02-19", "1988-11-06", "1989-12-03", "1990-03-15"]
+ values = [42544017.198965244, 1234565, 40512335.181958228, -1]
+
+ def create_data(constructor):
+ return dict(zip((constructor(x) for x in dates_as_str), values))
+
+ data_datetime64 = create_data(np.datetime64)
+ data_datetime = create_data(lambda x: datetime.strptime(x, "%Y-%m-%d"))
+ data_Timestamp = create_data(Timestamp)
+
+ expected = Series(values, (Timestamp(x) for x in dates_as_str))
+
+ result_datetime64 = Series(data_datetime64)
+ result_datetime = Series(data_datetime)
+ result_Timestamp = Series(data_Timestamp)
+
+ tm.assert_series_equal(
+ result_datetime64, expected.set_axis(expected.index.as_unit("s"))
+ )
+ tm.assert_series_equal(result_datetime, expected)
+ tm.assert_series_equal(result_Timestamp, expected)
+
+ def test_constructor_dict_tuple_indexer(self):
+ # GH 12948
+ data = {(1, 1, None): -1.0}
+ result = Series(data)
+ expected = Series(
+ -1.0, index=MultiIndex(levels=[[1], [1], [np.nan]], codes=[[0], [0], [-1]])
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_mapping(self, non_dict_mapping_subclass):
+ # GH 29788
+ ndm = non_dict_mapping_subclass({3: "three"})
+ result = Series(ndm)
+ expected = Series(["three"], index=[3])
+
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_list_of_tuples(self):
+ data = [(1, 1), (2, 2), (2, 3)]
+ s = Series(data)
+ assert list(s) == data
+
+ def test_constructor_tuple_of_tuples(self):
+ data = ((1, 1), (2, 2), (2, 3))
+ s = Series(data)
+ assert tuple(s) == data
+
+ @pytest.mark.parametrize(
+ "data, expected_values, expected_index",
+ [
+ ({(1, 2): 3, (None, 5): 6}, [3, 6], [(1, 2), (None, 5)]),
+ ({(1,): 3, (4, 5): 6}, [3, 6], [(1, None), (4, 5)]),
+ ],
+ )
+ def test_constructor_dict_of_tuples(self, data, expected_values, expected_index):
+ # GH 60695
+ result = Series(data).sort_values()
+ expected = Series(expected_values, index=MultiIndex.from_tuples(expected_index))
+ tm.assert_series_equal(result, expected)
+
+ # https://github.com/pandas-dev/pandas/issues/22698
+ @pytest.mark.filterwarnings("ignore:elementwise comparison:FutureWarning")
+ def test_fromDict(self, using_infer_string):
+ data = {"a": 0, "b": 1, "c": 2, "d": 3}
+
+ series = Series(data)
+ tm.assert_is_sorted(series.index)
+
+ data = {"a": 0, "b": "1", "c": "2", "d": datetime.now()}
+ series = Series(data)
+ assert series.dtype == np.object_
+
+ data = {"a": 0, "b": "1", "c": "2", "d": "3"}
+ series = Series(data)
+ assert series.dtype == np.object_ if not using_infer_string else "str"
+
+ data = {"a": "0", "b": "1"}
+ series = Series(data, dtype=float)
+ assert series.dtype == np.float64
+
+ def test_fromValue(self, datetime_series, using_infer_string):
+ nans = Series(np.nan, index=datetime_series.index, dtype=np.float64)
+ assert nans.dtype == np.float64
+ assert len(nans) == len(datetime_series)
+
+ strings = Series("foo", index=datetime_series.index)
+ assert strings.dtype == np.object_ if not using_infer_string else "str"
+ assert len(strings) == len(datetime_series)
+
+ d = datetime.now()
+ dates = Series(d, index=datetime_series.index)
+ assert dates.dtype == "M8[us]"
+ assert len(dates) == len(datetime_series)
+
+ # GH12336
+ # Test construction of categorical series from value
+ categorical = Series(0, index=datetime_series.index, dtype="category")
+ expected = Series(0, index=datetime_series.index).astype("category")
+ assert categorical.dtype == "category"
+ assert len(categorical) == len(datetime_series)
+ tm.assert_series_equal(categorical, expected)
+
+ def test_constructor_dtype_timedelta64(self):
+ # basic
+ td = Series([timedelta(days=i) for i in range(3)])
+ assert td.dtype == "timedelta64[us]"
+
+ td = Series([timedelta(days=1)])
+ assert td.dtype == "timedelta64[us]"
+
+ td = Series([timedelta(days=1), timedelta(days=2), np.timedelta64(1, "s")])
+
+ assert td.dtype == "timedelta64[us]"
+
+ # mixed with NaT
+ td = Series([timedelta(days=1), NaT], dtype="m8[ns]")
+ assert td.dtype == "timedelta64[ns]"
+
+ td = Series([timedelta(days=1), np.nan], dtype="m8[ns]")
+ assert td.dtype == "timedelta64[ns]"
+
+ td = Series([np.timedelta64(300000000), NaT], dtype="m8[ns]")
+ assert td.dtype == "timedelta64[ns]"
+
+ # improved inference
+ # GH5689
+ td = Series([np.timedelta64(300000000), NaT])
+ assert td.dtype == "timedelta64[ns]"
+
+ # because iNaT is int, not coerced to timedelta
+ td = Series([np.timedelta64(300000000), iNaT])
+ assert td.dtype == "object"
+
+ td = Series([np.timedelta64(300000000), np.nan])
+ assert td.dtype == "timedelta64[ns]"
+
+ td = Series([NaT, np.timedelta64(300000000)])
+ assert td.dtype == "timedelta64[ns]"
+
+ td = Series([np.timedelta64(1, "s")])
+ assert td.dtype == "timedelta64[s]"
+
+ # valid astype
+ td.astype("int64")
+
+ # invalid casting
+ msg = r"Converting from timedelta64\[s\] to int32 is not supported"
+ with pytest.raises(TypeError, match=msg):
+ td.astype("int32")
+
+ # this is an invalid casting
+ msg = "|".join(
+ [
+ "Could not convert object to NumPy timedelta",
+ "Could not convert 'foo' to NumPy timedelta",
+ ]
+ )
+ with pytest.raises(ValueError, match=msg):
+ Series([timedelta(days=1), "foo"], dtype="m8[ns]")
+
+ # leave as object here
+ td = Series([timedelta(days=i) for i in range(3)] + ["foo"])
+ assert td.dtype == "object"
+
+ # as of 2.0, these no longer infer timedelta64 based on the strings,
+ # matching Index behavior
+ ser = Series([None, NaT, "1 Day"])
+ assert ser.dtype == object
+
+ ser = Series([np.nan, NaT, "1 Day"])
+ assert ser.dtype == object
+
+ ser = Series([NaT, None, "1 Day"])
+ assert ser.dtype == object
+
+ ser = Series([NaT, np.nan, "1 Day"])
+ assert ser.dtype == object
+
+ # GH 16406
+ def test_constructor_mixed_tz(self):
+ s = Series([Timestamp("20130101"), Timestamp("20130101", tz="US/Eastern")])
+ expected = Series(
+ [Timestamp("20130101"), Timestamp("20130101", tz="US/Eastern")],
+ dtype="object",
+ )
+ tm.assert_series_equal(s, expected)
+
+ def test_NaT_scalar(self):
+ series = Series([0, 1000, 2000, iNaT], dtype="M8[ns]")
+
+ val = series[3]
+ assert isna(val)
+
+ series[2] = val
+ assert isna(series[2])
+
+ def test_NaT_cast(self):
+ # GH10747
+ result = Series([np.nan]).astype("M8[ns]")
+ expected = Series([NaT], dtype="M8[ns]")
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_name_hashable(self):
+ for n in [777, 777.0, "name", datetime(2001, 11, 11), (1,), "\u05d0"]:
+ for data in [[1, 2, 3], np.ones(3), {"a": 0, "b": 1}]:
+ s = Series(data, name=n)
+ assert s.name == n
+
+ def test_constructor_name_unhashable(self):
+ msg = r"Series\.name must be a hashable type"
+ for n in [["name_list"], np.ones(2), {1: 2}]:
+ for data in [["name_list"], np.ones(2), {1: 2}]:
+ with pytest.raises(TypeError, match=msg):
+ Series(data, name=n)
+
+ def test_auto_conversion(self):
+ series = Series(list(date_range("1/1/2000", periods=10, unit="ns")))
+ assert series.dtype == "M8[ns]"
+
+ def test_convert_non_ns(self):
+ # convert from a numpy array of non-ns timedelta64
+ arr = np.array([1, 2, 3], dtype="timedelta64[s]")
+ ser = Series(arr)
+ assert ser.dtype == arr.dtype
+
+ tdi = timedelta_range("00:00:01", periods=3, freq="s").as_unit("s")
+ expected = Series(tdi)
+ assert expected.dtype == arr.dtype
+ tm.assert_series_equal(ser, expected)
+
+ # convert from a numpy array of non-ns datetime64
+ arr = np.array(
+ ["2013-01-01", "2013-01-02", "2013-01-03"], dtype="datetime64[D]"
+ )
+ ser = Series(arr)
+ expected = Series(date_range("20130101", periods=3, freq="D"), dtype="M8[s]")
+ assert expected.dtype == "M8[s]"
+ tm.assert_series_equal(ser, expected)
+
+ arr = np.array(
+ ["2013-01-01 00:00:01", "2013-01-01 00:00:02", "2013-01-01 00:00:03"],
+ dtype="datetime64[s]",
+ )
+ ser = Series(arr)
+ expected = Series(
+ date_range("20130101 00:00:01", periods=3, freq="s"), dtype="M8[s]"
+ )
+ assert expected.dtype == "M8[s]"
+ tm.assert_series_equal(ser, expected)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ date_range("1/1/2000", periods=10),
+ timedelta_range("1 day", periods=10),
+ period_range("2000-Q1", periods=10, freq="Q"),
+ ],
+ ids=lambda x: type(x).__name__,
+ )
+ def test_constructor_cant_cast_datetimelike(self, index):
+ # floats are not ok
+ # strip Index to convert PeriodIndex -> Period
+ # We don't care whether the error message says
+ # PeriodIndex or PeriodArray
+ msg = f"Cannot cast {type(index).__name__.rstrip('Index')}.*? to "
+
+ with pytest.raises(TypeError, match=msg):
+ Series(index, dtype=float)
+
+ # ints are ok
+ # we test with np.int64 to get similar results on
+ # windows / 32-bit platforms
+ result = Series(index, dtype=np.int64)
+ expected = Series(index.astype(np.int64))
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "index",
+ [
+ date_range("1/1/2000", periods=10),
+ timedelta_range("1 day", periods=10),
+ period_range("2000-Q1", periods=10, freq="Q"),
+ ],
+ ids=lambda x: type(x).__name__,
+ )
+ def test_constructor_cast_object(self, index):
+ s = Series(index, dtype=object)
+ exp = Series(index).astype(object)
+ tm.assert_series_equal(s, exp)
+
+ s = Series(Index(index, dtype=object), dtype=object)
+ exp = Series(index).astype(object)
+ tm.assert_series_equal(s, exp)
+
+ s = Series(index.astype(object), dtype=object)
+ exp = Series(index).astype(object)
+ tm.assert_series_equal(s, exp)
+
+ @pytest.mark.parametrize("dtype", [np.datetime64, np.timedelta64])
+ def test_constructor_generic_timestamp_no_frequency(self, dtype, request):
+ # see gh-15524, gh-15987
+ msg = "dtype has no unit. Please pass in"
+
+ if np.dtype(dtype).name not in ["timedelta64", "datetime64"]:
+ mark = pytest.mark.xfail(reason="GH#33890 Is assigned ns unit")
+ request.applymarker(mark)
+
+ with pytest.raises(ValueError, match=msg):
+ Series([], dtype=dtype)
+
+ @pytest.mark.parametrize("unit", ["ps", "as", "fs", "Y", "M", "W", "D", "h", "m"])
+ @pytest.mark.parametrize("kind", ["m", "M"])
+ def test_constructor_generic_timestamp_bad_frequency(self, kind, unit):
+ # see gh-15524, gh-15987
+ # as of 2.0 we raise on any non-supported unit rather than silently
+ # cast to nanos; previously we only raised for frequencies higher
+ # than ns
+ dtype = f"{kind}8[{unit}]"
+
+ msg = "dtype=.* is not supported. Supported resolutions are"
+ with pytest.raises(TypeError, match=msg):
+ Series([], dtype=dtype)
+
+ with pytest.raises(TypeError, match=msg):
+ # pre-2.0 the DataFrame cast raised but the Series case did not
+ DataFrame([[0]], dtype=dtype)
+
+ @pytest.mark.parametrize("dtype", [None, "uint8", "category"])
+ def test_constructor_range_dtype(self, dtype):
+ # GH 16804
+ expected = Series([0, 1, 2, 3, 4], dtype=dtype or "int64")
+ result = Series(range(5), dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_range_overflows(self):
+ # GH#30173 range objects that overflow int64
+ rng = range(2**63, 2**63 + 4)
+ ser = Series(rng)
+ expected = Series(list(rng))
+ tm.assert_series_equal(ser, expected)
+ assert list(ser) == list(rng)
+ assert ser.dtype == np.uint64
+
+ rng2 = range(2**63 + 4, 2**63, -1)
+ ser2 = Series(rng2)
+ expected2 = Series(list(rng2))
+ tm.assert_series_equal(ser2, expected2)
+ assert list(ser2) == list(rng2)
+ assert ser2.dtype == np.uint64
+
+ rng3 = range(-(2**63), -(2**63) - 4, -1)
+ ser3 = Series(rng3)
+ expected3 = Series(list(rng3))
+ tm.assert_series_equal(ser3, expected3)
+ assert list(ser3) == list(rng3)
+ assert ser3.dtype == object
+
+ rng4 = range(2**73, 2**73 + 4)
+ ser4 = Series(rng4)
+ expected4 = Series(list(rng4))
+ tm.assert_series_equal(ser4, expected4)
+ assert list(ser4) == list(rng4)
+ assert ser4.dtype == object
+
+ def test_constructor_tz_mixed_data(self):
+ # GH 13051
+ dt_list = [
+ Timestamp("2016-05-01 02:03:37"),
+ Timestamp("2016-04-30 19:03:37-0700", tz="US/Pacific"),
+ ]
+ result = Series(dt_list)
+ expected = Series(dt_list, dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("pydt", [True, False])
+ def test_constructor_data_aware_dtype_naive(self, tz_aware_fixture, pydt):
+ # GH#25843, GH#41555, GH#33401
+ tz = tz_aware_fixture
+ ts = Timestamp("2019", tz=tz)
+ if pydt:
+ ts = ts.to_pydatetime()
+
+ msg = (
+ "Cannot convert timezone-aware data to timezone-naive dtype. "
+ r"Use pd.Series\(values\).dt.tz_localize\(None\) instead."
+ )
+ with pytest.raises(ValueError, match=msg):
+ Series([ts], dtype="datetime64[ns]")
+
+ with pytest.raises(ValueError, match=msg):
+ Series(np.array([ts], dtype=object), dtype="datetime64[ns]")
+
+ with pytest.raises(ValueError, match=msg):
+ Series({0: ts}, dtype="datetime64[ns]")
+
+ msg = "Cannot unbox tzaware Timestamp to tznaive dtype"
+ with pytest.raises(TypeError, match=msg):
+ Series(ts, index=[0], dtype="datetime64[ns]")
+
+ def test_constructor_datetime64(self):
+ rng = date_range("1/1/2000 00:00:00", "1/1/2000 1:59:50", freq="10s")
+ dates = np.asarray(rng)
+
+ series = Series(dates)
+ assert np.issubdtype(series.dtype, np.dtype("M8[ns]"))
+
+ def test_constructor_datetimelike_scalar_to_string_dtype(
+ self, nullable_string_dtype
+ ):
+ # https://github.com/pandas-dev/pandas/pull/33846
+ result = Series("M", index=[1, 2, 3], dtype=nullable_string_dtype)
+ expected = Series(["M", "M", "M"], index=[1, 2, 3], dtype=nullable_string_dtype)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("box", [lambda x: x, np.datetime64])
+ def test_constructor_sparse_datetime64(self, box):
+ # https://github.com/pandas-dev/pandas/issues/35762
+ values = [box("2012-01-01"), box("2013-01-01")]
+ dtype = pd.SparseDtype("datetime64[ns]")
+ result = Series(values, dtype=dtype)
+ arr = pd.arrays.SparseArray(values, dtype=dtype)
+ expected = Series(arr)
+ tm.assert_series_equal(result, expected)
+
+ def test_construction_from_ordered_collection(self):
+ # https://github.com/pandas-dev/pandas/issues/36044
+ result = Series({"a": 1, "b": 2}.keys())
+ expected = Series(["a", "b"])
+ tm.assert_series_equal(result, expected)
+
+ result = Series({"a": 1, "b": 2}.values())
+ expected = Series([1, 2])
+ tm.assert_series_equal(result, expected)
+
+ def test_construction_from_large_int_scalar_no_overflow(self):
+ # https://github.com/pandas-dev/pandas/issues/36291
+ n = 1_000_000_000_000_000_000_000
+ result = Series(n, index=[0])
+ expected = Series(n)
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_list_of_periods_infers_period_dtype(self):
+ series = Series(list(period_range("2000-01-01", periods=10, freq="D")))
+ assert series.dtype == "Period[D]"
+
+ series = Series(
+ [Period("2011-01-01", freq="D"), Period("2011-02-01", freq="D")]
+ )
+ assert series.dtype == "Period[D]"
+
+ def test_constructor_subclass_dict(self, dict_subclass):
+ data = dict_subclass((x, 10.0 * x) for x in range(10))
+ series = Series(data)
+ expected = Series(dict(data.items()))
+ tm.assert_series_equal(series, expected)
+
+ def test_constructor_ordereddict(self):
+ # GH3283
+ data = OrderedDict(
+ (f"col{i}", np.random.default_rng(2).random()) for i in range(12)
+ )
+
+ series = Series(data)
+ expected = Series(list(data.values()), list(data.keys()))
+ tm.assert_series_equal(series, expected)
+
+ # Test with subclass
+ class A(OrderedDict):
+ pass
+
+ series = Series(A(data))
+ tm.assert_series_equal(series, expected)
+
+ @pytest.mark.parametrize(
+ "data, expected_index_multi",
+ [
+ ({("a", "a"): 0.0, ("b", "a"): 1.0, ("b", "c"): 2.0}, True),
+ ({("a",): 0.0, ("a", "b"): 1.0}, True),
+ ({"z": 111.0, ("a", "a"): 0.0, ("b", "a"): 1.0, ("b", "c"): 2.0}, False),
+ ],
+ )
+ def test_constructor_dict_multiindex(self, data, expected_index_multi):
+ # GH#60695
+ result = Series(data)
+
+ if expected_index_multi:
+ expected = Series(
+ list(data.values()),
+ index=MultiIndex.from_tuples(list(data.keys())),
+ )
+ tm.assert_series_equal(result, expected)
+ else:
+ expected = Series(
+ list(data.values()),
+ index=Index(list(data.keys())),
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_dict_multiindex_reindex_flat(self):
+ # construction involves reindexing with a MultiIndex corner case
+ data = {("i", "i"): 0, ("i", "j"): 1, ("j", "i"): 2, "j": np.nan}
+ expected = Series(data)
+
+ result = Series(expected[:-1].to_dict(), index=expected.index)
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_dict_timedelta_index(self):
+ # GH #12169 : Resample category data with timedelta index
+ # construct Series from dict as data and TimedeltaIndex as index
+ # will result NaN in result Series data
+ expected = Series(
+ data=["A", "B", "C"], index=pd.to_timedelta([0, 10, 20], unit="s")
+ )
+
+ result = Series(
+ data={
+ pd.to_timedelta(0, unit="s"): "A",
+ pd.to_timedelta(10, unit="s"): "B",
+ pd.to_timedelta(20, unit="s"): "C",
+ },
+ index=pd.to_timedelta([0, 10, 20], unit="s"),
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_infer_index_tz(self):
+ values = [188.5, 328.25]
+ tzinfo = tzoffset(None, 7200)
+ index = [
+ datetime(2012, 5, 11, 11, tzinfo=tzinfo),
+ datetime(2012, 5, 11, 12, tzinfo=tzinfo),
+ ]
+ series = Series(data=values, index=index)
+
+ assert series.index.tz == tzinfo
+
+ # it works! GH#2443
+ repr(series.index[0])
+
+ def test_constructor_with_pandas_dtype(self):
+ # going through 2D->1D path
+ vals = [(1,), (2,), (3,)]
+ ser = Series(vals)
+ dtype = ser.array.dtype # NumpyEADtype
+ ser2 = Series(vals, dtype=dtype)
+ tm.assert_series_equal(ser, ser2)
+
+ def test_constructor_int_dtype_missing_values(self):
+ # GH#43017
+ result = Series(index=[0], dtype="int64")
+ expected = Series(np.nan, index=[0], dtype="float64")
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_bool_dtype_missing_values(self):
+ # GH#43018
+ result = Series(index=[0], dtype="bool")
+ expected = Series(True, index=[0], dtype="bool")
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_int64_dtype(self, any_int_dtype):
+ # GH#44923
+ result = Series(["0", "1", "2"], dtype=any_int_dtype)
+ expected = Series([0, 1, 2], dtype=any_int_dtype)
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_raise_on_lossy_conversion_of_strings(self):
+ # GH#44923
+ if not np_version_gt2:
+ raises = pytest.raises(
+ ValueError, match="string values cannot be losslessly cast to int8"
+ )
+ else:
+ raises = pytest.raises(
+ OverflowError, match="The elements provided in the data"
+ )
+ with raises:
+ Series(["128"], dtype="int8")
+
+ def test_constructor_dtype_timedelta_alternative_construct(self):
+ # GH#35465
+ result = Series([1000000, 200000, 3000000], dtype="timedelta64[us]")
+ expected = Series(pd.to_timedelta([1000000, 200000, 3000000], unit="us"))
+ tm.assert_series_equal(result, expected)
+
+ def test_constructor_dtype_timedelta_ns_s_astype_int64(self):
+ # GH#35465
+ result = Series([1000000, 200000, 3000000], dtype="timedelta64[ns]").astype(
+ "int64"
+ )
+ expected = Series([1000000, 200000, 3000000], dtype="timedelta64[s]").astype(
+ "int64"
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.filterwarnings(
+ "ignore:elementwise comparison failed:DeprecationWarning"
+ )
+ @pytest.mark.parametrize("func", [Series, DataFrame, Index, pd.array])
+ def test_constructor_mismatched_null_nullable_dtype(
+ self, func, any_numeric_ea_dtype
+ ):
+ # GH#44514
+ msg = "|".join(
+ [
+ "cannot safely cast non-equivalent object",
+ r"int\(\) argument must be a string, a bytes-like object "
+ "or a (real )?number",
+ r"Cannot cast array data from dtype\('O'\) to dtype\('float64'\) "
+ "according to the rule 'safe'",
+ "object cannot be converted to a FloatingDtype",
+ "'values' contains non-numeric NA",
+ ]
+ )
+
+ for null in [*tm.NP_NAT_OBJECTS, NaT]:
+ with pytest.raises(TypeError, match=msg):
+ func([null, 1.0, 3.0], dtype=any_numeric_ea_dtype)
+
+ def test_series_constructor_ea_int_from_bool(self):
+ # GH#42137
+ result = Series([True, False, True, pd.NA], dtype="Int64")
+ expected = Series([1, 0, 1, pd.NA], dtype="Int64")
+ tm.assert_series_equal(result, expected)
+
+ result = Series([True, False, True], dtype="Int64")
+ expected = Series([1, 0, 1], dtype="Int64")
+ tm.assert_series_equal(result, expected)
+
+ def test_series_constructor_ea_int_from_string_bool(self):
+ # GH#42137
+ with pytest.raises(ValueError, match="invalid literal"):
+ Series(["True", "False", "True", pd.NA], dtype="Int64")
+
+ @pytest.mark.parametrize("val", [1, 1.0])
+ def test_series_constructor_overflow_uint_ea(self, val):
+ # GH#38798
+ max_val = np.iinfo(np.uint64).max - 1
+ result = Series([max_val, val], dtype="UInt64")
+ expected = Series(np.array([max_val, 1], dtype="uint64"), dtype="UInt64")
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("val", [1, 1.0])
+ def test_series_constructor_overflow_uint_ea_with_na(self, val):
+ # GH#38798
+ max_val = np.iinfo(np.uint64).max - 1
+ result = Series([max_val, val, pd.NA], dtype="UInt64")
+ expected = Series(
+ IntegerArray(
+ np.array([max_val, 1, 0], dtype="uint64"),
+ np.array([0, 0, 1], dtype=np.bool_),
+ )
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_series_constructor_overflow_uint_with_nan(self):
+ # GH#38798
+ max_val = np.iinfo(np.uint64).max - 1
+ result = Series([max_val, pd.NA], dtype="UInt64")
+ expected = Series(
+ IntegerArray(
+ np.array([max_val, 1], dtype="uint64"),
+ np.array([0, 1], dtype=np.bool_),
+ )
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_series_constructor_ea_all_na(self):
+ # GH#38798
+ result = Series([pd.NA, pd.NA], dtype="UInt64")
+ expected = Series(
+ IntegerArray(
+ np.array([1, 1], dtype="uint64"),
+ np.array([1, 1], dtype=np.bool_),
+ )
+ )
+ tm.assert_series_equal(result, expected)
+
+ def test_series_from_index_dtype_equal_does_not_copy(self):
+ # GH#52008
+ idx = Index([1, 2, 3])
+ expected = idx.copy(deep=True)
+ ser = Series(idx, dtype="int64")
+ ser.iloc[0] = 100
+ tm.assert_index_equal(idx, expected)
+
+ def test_series_string_inference(self):
+ # GH#54430
+ with pd.option_context("future.infer_string", True):
+ ser = Series(["a", "b"])
+ dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan)
+ expected = Series(["a", "b"], dtype=dtype)
+ tm.assert_series_equal(ser, expected)
+
+ expected = Series(["a", 1], dtype="object")
+ with pd.option_context("future.infer_string", True):
+ ser = Series(["a", 1])
+ tm.assert_series_equal(ser, expected)
+
+ @pytest.mark.parametrize("na_value", [None, np.nan, pd.NA])
+ def test_series_string_with_na_inference(self, na_value):
+ # GH#54430
+ with pd.option_context("future.infer_string", True):
+ ser = Series(["a", na_value])
+ dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan)
+ expected = Series(["a", None], dtype=dtype)
+ tm.assert_series_equal(ser, expected)
+
+ def test_series_string_inference_scalar(self):
+ # GH#54430
+ with pd.option_context("future.infer_string", True):
+ ser = Series("a", index=[1])
+ dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan)
+ expected = Series("a", index=[1], dtype=dtype)
+ tm.assert_series_equal(ser, expected)
+
+ def test_series_string_inference_array_string_dtype(self):
+ # GH#54496
+ with pd.option_context("future.infer_string", True):
+ ser = Series(np.array(["a", "b"]))
+ dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan)
+ expected = Series(["a", "b"], dtype=dtype)
+ tm.assert_series_equal(ser, expected)
+
+ def test_series_string_inference_storage_definition(self):
+ # https://github.com/pandas-dev/pandas/issues/54793
+ # but after PDEP-14 (string dtype), it was decided to keep dtype="string"
+ # returning the NA string dtype, so expected is changed from
+ # "string[pyarrow_numpy]" to "string[python]"
+ expected = Series(
+ ["a", "b"], dtype="string[pyarrow]" if HAS_PYARROW else "string[python]"
+ )
+ with pd.option_context("future.infer_string", True):
+ result = Series(["a", "b"], dtype="string")
+ tm.assert_series_equal(result, expected)
+
+ expected = Series(["a", "b"], dtype=pd.StringDtype(na_value=np.nan))
+ with pd.option_context("future.infer_string", True):
+ result = Series(["a", "b"], dtype="str")
+ tm.assert_series_equal(result, expected)
+
+ def test_series_constructor_infer_string_scalar(self):
+ # GH#55537
+ with pd.option_context("future.infer_string", True):
+ ser = Series("a", index=[1, 2], dtype="string[python]")
+ expected = Series(["a", "a"], index=[1, 2], dtype="string[python]")
+ tm.assert_series_equal(ser, expected)
+ assert ser.dtype.storage == "python"
+
+ def test_series_string_inference_na_first(self):
+ # GH#55655
+ with pd.option_context("future.infer_string", True):
+ result = Series([pd.NA, "b"])
+ dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan)
+ expected = Series([None, "b"], dtype=dtype)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize("klass", [Series, Index])
+ def test_inference_on_pandas_objects(self, klass):
+ # GH#56012
+ obj = klass([Timestamp("2019-12-31")], dtype=object)
+ # This doesn't do inference
+ result = Series(obj)
+ assert result.dtype == np.object_
+
+
+class TestSeriesConstructorIndexCoercion:
+ def test_series_constructor_datetimelike_index_coercion(self):
+ idx = date_range("2020-01-01", periods=5)
+ ser = Series(
+ np.random.default_rng(2).standard_normal(len(idx)), idx.astype(object)
+ )
+ # as of 2.0, we no longer silently cast the object-dtype index
+ # to DatetimeIndex GH#39307, GH#23598
+ assert not isinstance(ser.index, DatetimeIndex)
+
+ @pytest.mark.parametrize("container", [None, np.array, Series, Index])
+ @pytest.mark.parametrize("data", [1.0, range(4)])
+ def test_series_constructor_infer_multiindex(self, container, data):
+ indexes = [["a", "a", "b", "b"], ["x", "y", "x", "y"]]
+ if container is not None:
+ indexes = [container(ind) for ind in indexes]
+
+ multi = Series(data, index=indexes)
+ assert isinstance(multi.index, MultiIndex)
+
+ # TODO: make this not cast to object in pandas 3.0
+ @pytest.mark.skipif(
+ not np_version_gt2, reason="StringDType only available in numpy 2 and above"
+ )
+ @pytest.mark.parametrize(
+ "data",
+ [
+ ["a", "b", "c"],
+ ["a", "b", np.nan],
+ ],
+ )
+ def test_np_string_array_object_cast(self, data):
+ from numpy.dtypes import StringDType
+
+ arr = np.array(data, dtype=StringDType())
+ res = Series(arr)
+ assert res.dtype == np.object_
+
+ if data[-1] is np.nan:
+ # as of GH#62522 the comparison op for `res==data` casts data
+ # using sanitize_array, which casts to 'str' dtype, which does not
+ # consider string 'nan' to be equal to np.nan,
+ # (which apparently numpy does? weird.)
+ assert (res.iloc[:-1] == data[:-1]).all()
+ assert res.iloc[-1] == "nan"
+ else:
+ assert (res == data).all()
+
+
+class TestSeriesConstructorInternals:
+ def test_constructor_no_pandas_array(self):
+ ser = Series([1, 2, 3])
+ result = Series(ser.array)
+ tm.assert_series_equal(ser, result)
+ assert isinstance(result._mgr.blocks[0], NumpyBlock)
+ assert result._mgr.blocks[0].is_numeric
+
+ def test_from_array(self):
+ result = Series(pd.array(["1h", "2h"], dtype="timedelta64[ns]"))
+ assert result._mgr.blocks[0].is_extension is False
+
+ result = Series(pd.array(["2015"], dtype="datetime64[ns]"))
+ assert result._mgr.blocks[0].is_extension is False
+
+ def test_from_list_dtype(self):
+ result = Series(["1h", "2h"], dtype="timedelta64[ns]")
+ assert result._mgr.blocks[0].is_extension is False
+
+ result = Series(["2015"], dtype="datetime64[ns]")
+ assert result._mgr.blocks[0].is_extension is False
+
+
+def test_constructor(rand_series_with_duplicate_datetimeindex):
+ dups = rand_series_with_duplicate_datetimeindex
+ assert isinstance(dups, Series)
+ assert isinstance(dups.index, DatetimeIndex)
+
+
+@pytest.mark.parametrize(
+ "input_dict,expected",
+ [
+ ({0: 0}, np.array([[0]], dtype=np.int64)),
+ ({"a": "a"}, np.array([["a"]], dtype=object)),
+ ({1: 1}, np.array([[1]], dtype=np.int64)),
+ ],
+)
+def test_numpy_array(input_dict, expected):
+ result = np.array([Series(input_dict)])
+ tm.assert_numpy_array_equal(result, expected)
+
+
+def test_index_ordered_dict_keys():
+ # GH 22077
+
+ param_index = OrderedDict(
+ [
+ ((("a", "b"), ("c", "d")), 1),
+ ((("a", None), ("c", "d")), 2),
+ ]
+ )
+ series = Series([1, 2], index=param_index.keys())
+ expected = Series(
+ [1, 2],
+ index=MultiIndex.from_tuples(
+ [(("a", "b"), ("c", "d")), (("a", None), ("c", "d"))]
+ ),
+ )
+ tm.assert_series_equal(series, expected)
+
+
+@pytest.mark.parametrize(
+ "input_list",
+ [
+ [1, complex("nan"), 2],
+ [1 + 1j, complex("nan"), 2 + 2j],
+ ],
+)
+def test_series_with_complex_nan(input_list):
+ # GH#53627
+ ser = Series(input_list)
+ result = Series(ser.array)
+ assert ser.dtype == "complex128"
+ tm.assert_series_equal(ser, result)
+
+
+def test_dict_keys_rangeindex():
+ result = Series({0: 1, 1: 2})
+ expected = Series([1, 2], index=RangeIndex(2))
+ tm.assert_series_equal(result, expected, check_index_type=True)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_cumulative.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_cumulative.py
new file mode 100644
index 0000000000000000000000000000000000000000..db83cf1112e7452df3328dd52b2ebe8a6232161c
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_cumulative.py
@@ -0,0 +1,284 @@
+"""
+Tests for Series cumulative operations.
+
+See also
+--------
+tests.frame.test_cumulative
+"""
+
+import re
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+methods = {
+ "cumsum": np.cumsum,
+ "cumprod": np.cumprod,
+ "cummin": np.minimum.accumulate,
+ "cummax": np.maximum.accumulate,
+}
+
+
+class TestSeriesCumulativeOps:
+ @pytest.mark.parametrize("func", [np.cumsum, np.cumprod])
+ def test_datetime_series(self, datetime_series, func):
+ tm.assert_numpy_array_equal(
+ func(datetime_series).values,
+ func(np.array(datetime_series)),
+ check_dtype=True,
+ )
+
+ # with missing values
+ ts = datetime_series.copy()
+ ts[::2] = np.nan
+
+ result = func(ts)[1::2]
+ expected = func(np.array(ts.dropna()))
+
+ tm.assert_numpy_array_equal(result.values, expected, check_dtype=False)
+
+ @pytest.mark.parametrize("method", ["cummin", "cummax"])
+ def test_cummin_cummax(self, datetime_series, method):
+ ufunc = methods[method]
+
+ result = getattr(datetime_series, method)().values
+ expected = ufunc(np.array(datetime_series))
+
+ tm.assert_numpy_array_equal(result, expected)
+ ts = datetime_series.copy()
+ ts[::2] = np.nan
+ result = getattr(ts, method)()[1::2]
+ expected = ufunc(ts.dropna())
+
+ result.index = result.index._with_freq(None)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "ts",
+ [
+ pd.Timedelta(0),
+ pd.Timestamp("1999-12-31"),
+ pd.Timestamp("1999-12-31").tz_localize("US/Pacific"),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "method, skipna, exp_tdi",
+ [
+ ["cummax", True, ["NaT", "2 days", "NaT", "2 days", "NaT", "3 days"]],
+ ["cummin", True, ["NaT", "2 days", "NaT", "1 days", "NaT", "1 days"]],
+ [
+ "cummax",
+ False,
+ ["NaT", "NaT", "NaT", "NaT", "NaT", "NaT"],
+ ],
+ [
+ "cummin",
+ False,
+ ["NaT", "NaT", "NaT", "NaT", "NaT", "NaT"],
+ ],
+ ],
+ )
+ def test_cummin_cummax_datetimelike(self, ts, method, skipna, exp_tdi):
+ # with ts==pd.Timedelta(0), we are testing td64; with naive Timestamp
+ # we are testing datetime64[ns]; with Timestamp[US/Pacific]
+ # we are testing dt64tz
+ tdi = pd.to_timedelta(["NaT", "2 days", "NaT", "1 days", "NaT", "3 days"])
+ ser = pd.Series(tdi + ts)
+
+ exp_tdi = pd.to_timedelta(exp_tdi)
+ expected = pd.Series(exp_tdi + ts)
+ result = getattr(ser, method)(skipna=skipna)
+ tm.assert_series_equal(expected, result)
+
+ def test_cumsum_datetimelike(self):
+ # GH#57956
+ df = pd.DataFrame(
+ [
+ [pd.Timedelta(0), pd.Timedelta(days=1)],
+ [pd.Timedelta(days=2), pd.NaT],
+ [pd.Timedelta(hours=-6), pd.Timedelta(hours=12)],
+ ]
+ )
+ result = df.cumsum()
+ expected = pd.DataFrame(
+ [
+ [pd.Timedelta(0), pd.Timedelta(days=1)],
+ [pd.Timedelta(days=2), pd.NaT],
+ [pd.Timedelta(days=1, hours=18), pd.Timedelta(days=1, hours=12)],
+ ]
+ )
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "func, exp",
+ [
+ ("cummin", "2012-1-1"),
+ ("cummax", "2012-1-2"),
+ ],
+ )
+ def test_cummin_cummax_period(self, func, exp):
+ # GH#28385
+ ser = pd.Series(
+ [pd.Period("2012-1-1", freq="D"), pd.NaT, pd.Period("2012-1-2", freq="D")]
+ )
+ result = getattr(ser, func)(skipna=False)
+ expected = pd.Series([pd.Period("2012-1-1", freq="D"), pd.NaT, pd.NaT])
+ tm.assert_series_equal(result, expected)
+
+ result = getattr(ser, func)(skipna=True)
+ exp = pd.Period(exp, freq="D")
+ expected = pd.Series([pd.Period("2012-1-1", freq="D"), pd.NaT, exp])
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "arg",
+ [
+ [False, False, False, True, True, False, False],
+ [False, False, False, False, False, False, False],
+ ],
+ )
+ @pytest.mark.parametrize(
+ "func", [lambda x: x, lambda x: ~x], ids=["identity", "inverse"]
+ )
+ @pytest.mark.parametrize("method", methods.keys())
+ def test_cummethods_bool(self, arg, func, method):
+ # GH#6270
+ # checking Series method vs the ufunc applied to the values
+
+ ser = func(pd.Series(arg))
+ ufunc = methods[method]
+
+ exp_vals = ufunc(ser.values)
+ expected = pd.Series(exp_vals)
+
+ result = getattr(ser, method)()
+
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "method, expected",
+ [
+ ["cumsum", pd.Series([0, 1, np.nan, 1], dtype=object)],
+ ["cumprod", pd.Series([False, 0, np.nan, 0])],
+ ["cummin", pd.Series([False, False, np.nan, False])],
+ ["cummax", pd.Series([False, True, np.nan, True])],
+ ],
+ )
+ def test_cummethods_bool_in_object_dtype(self, method, expected):
+ ser = pd.Series([False, True, np.nan, False])
+ result = getattr(ser, method)()
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "method, order",
+ [
+ ["cummax", "abc"],
+ ["cummin", "cba"],
+ ],
+ )
+ def test_cummax_cummin_on_ordered_categorical(self, method, order):
+ # GH#52335
+ cat = pd.CategoricalDtype(list(order), ordered=True)
+ ser = pd.Series(
+ list("ababcab"),
+ dtype=cat,
+ )
+ result = getattr(ser, method)()
+ expected = pd.Series(
+ list("abbbccc"),
+ dtype=cat,
+ )
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "skip, exp",
+ [
+ [True, ["a", np.nan, "b", "b", "c"]],
+ [False, ["a", np.nan, np.nan, np.nan, np.nan]],
+ ],
+ )
+ @pytest.mark.parametrize(
+ "method, order",
+ [
+ ["cummax", "abc"],
+ ["cummin", "cba"],
+ ],
+ )
+ def test_cummax_cummin_ordered_categorical_nan(self, skip, exp, method, order):
+ # GH#52335
+ cat = pd.CategoricalDtype(list(order), ordered=True)
+ ser = pd.Series(
+ ["a", np.nan, "b", "a", "c"],
+ dtype=cat,
+ )
+ result = getattr(ser, method)(skipna=skip)
+ expected = pd.Series(
+ exp,
+ dtype=cat,
+ )
+ tm.assert_series_equal(
+ result,
+ expected,
+ )
+
+ def test_cumprod_timedelta(self):
+ # GH#48111
+ ser = pd.Series([pd.Timedelta(days=1), pd.Timedelta(days=3)])
+ with pytest.raises(TypeError, match="cumprod not supported for Timedelta"):
+ ser.cumprod()
+
+ @pytest.mark.parametrize(
+ "data, op, skipna, expected_data",
+ [
+ ([], "cumsum", True, []),
+ ([], "cumsum", False, []),
+ (["x", "z", "y"], "cumsum", True, ["x", "xz", "xzy"]),
+ (["x", "z", "y"], "cumsum", False, ["x", "xz", "xzy"]),
+ (["x", pd.NA, "y"], "cumsum", True, ["x", pd.NA, "xy"]),
+ (["x", pd.NA, "y"], "cumsum", False, ["x", pd.NA, pd.NA]),
+ ([pd.NA, "x", "y"], "cumsum", True, [pd.NA, "x", "xy"]),
+ ([pd.NA, "x", "y"], "cumsum", False, [pd.NA, pd.NA, pd.NA]),
+ ([pd.NA, pd.NA, pd.NA], "cumsum", True, [pd.NA, pd.NA, pd.NA]),
+ ([pd.NA, pd.NA, pd.NA], "cumsum", False, [pd.NA, pd.NA, pd.NA]),
+ ([], "cummin", True, []),
+ ([], "cummin", False, []),
+ (["y", "z", "x"], "cummin", True, ["y", "y", "x"]),
+ (["y", "z", "x"], "cummin", False, ["y", "y", "x"]),
+ (["y", pd.NA, "x"], "cummin", True, ["y", pd.NA, "x"]),
+ (["y", pd.NA, "x"], "cummin", False, ["y", pd.NA, pd.NA]),
+ ([pd.NA, "y", "x"], "cummin", True, [pd.NA, "y", "x"]),
+ ([pd.NA, "y", "x"], "cummin", False, [pd.NA, pd.NA, pd.NA]),
+ ([pd.NA, pd.NA, pd.NA], "cummin", True, [pd.NA, pd.NA, pd.NA]),
+ ([pd.NA, pd.NA, pd.NA], "cummin", False, [pd.NA, pd.NA, pd.NA]),
+ ([], "cummax", True, []),
+ ([], "cummax", False, []),
+ (["x", "z", "y"], "cummax", True, ["x", "z", "z"]),
+ (["x", "z", "y"], "cummax", False, ["x", "z", "z"]),
+ (["x", pd.NA, "y"], "cummax", True, ["x", pd.NA, "y"]),
+ (["x", pd.NA, "y"], "cummax", False, ["x", pd.NA, pd.NA]),
+ ([pd.NA, "x", "y"], "cummax", True, [pd.NA, "x", "y"]),
+ ([pd.NA, "x", "y"], "cummax", False, [pd.NA, pd.NA, pd.NA]),
+ ([pd.NA, pd.NA, pd.NA], "cummax", True, [pd.NA, pd.NA, pd.NA]),
+ ([pd.NA, pd.NA, pd.NA], "cummax", False, [pd.NA, pd.NA, pd.NA]),
+ ],
+ )
+ def test_cum_methods_ea_strings(
+ self, string_dtype_no_object, data, op, skipna, expected_data
+ ):
+ # https://github.com/pandas-dev/pandas/pull/60633 - pyarrow
+ # https://github.com/pandas-dev/pandas/pull/60938 - Python
+ ser = pd.Series(data, dtype=string_dtype_no_object)
+ method = getattr(ser, op)
+ expected = pd.Series(expected_data, dtype=string_dtype_no_object)
+ result = method(skipna=skipna)
+ tm.assert_series_equal(result, expected)
+
+ def test_cumprod_pyarrow_strings(self, pyarrow_string_dtype, skipna):
+ # https://github.com/pandas-dev/pandas/pull/60633
+ ser = pd.Series(list("xyz"), dtype=pyarrow_string_dtype)
+ msg = re.escape(f"operation 'cumprod' not supported for dtype '{ser.dtype}'")
+ with pytest.raises(TypeError, match=msg):
+ ser.cumprod(skipna=skipna)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_formats.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_formats.py
new file mode 100644
index 0000000000000000000000000000000000000000..76c8914e60b76c71a39604c9fde1f8e731fc7e8e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_formats.py
@@ -0,0 +1,592 @@
+from datetime import (
+ datetime,
+ timedelta,
+)
+
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ DataFrame,
+ Index,
+ Series,
+ date_range,
+ option_context,
+ period_range,
+ timedelta_range,
+)
+
+
+class TestSeriesRepr:
+ def test_multilevel_name_print_0(self):
+ # GH#55415 None does not get printed, but 0 does
+ # (matching DataFrame and flat index behavior)
+ mi = pd.MultiIndex.from_product([range(2, 3), range(3, 4)], names=[0, None])
+ ser = Series(1.5, index=mi)
+
+ res = repr(ser)
+ expected = "0 \n2 3 1.5\ndtype: float64"
+ assert res == expected
+
+ def test_multilevel_name_print(self, lexsorted_two_level_string_multiindex):
+ index = lexsorted_two_level_string_multiindex
+ ser = Series(range(len(index)), index=index, name="sth")
+ expected = [
+ "first second",
+ "foo one 0",
+ " two 1",
+ " three 2",
+ "bar one 3",
+ " two 4",
+ "baz two 5",
+ " three 6",
+ "qux one 7",
+ " two 8",
+ " three 9",
+ "Name: sth, dtype: int64",
+ ]
+ expected = "\n".join(expected)
+ assert repr(ser) == expected
+
+ def test_small_name_printing(self):
+ # Test small Series.
+ s = Series([0, 1, 2])
+
+ s.name = "test"
+ assert "Name: test" in repr(s)
+
+ s.name = None
+ assert "Name:" not in repr(s)
+
+ def test_big_name_printing(self):
+ # Test big Series (diff code path).
+ s = Series(range(1000))
+
+ s.name = "test"
+ assert "Name: test" in repr(s)
+
+ s.name = None
+ assert "Name:" not in repr(s)
+
+ def test_empty_name_printing(self):
+ s = Series(index=date_range("20010101", "20020101"), name="test", dtype=object)
+ assert "Name: test" in repr(s)
+
+ @pytest.mark.parametrize("args", [(), (0, -1)])
+ def test_float_range(self, args):
+ str(
+ Series(
+ np.random.default_rng(2).standard_normal(1000),
+ index=np.arange(1000, *args),
+ )
+ )
+
+ def test_empty_object(self):
+ # empty
+ str(Series(dtype=object))
+
+ def test_string(self, string_series):
+ str(string_series)
+ str(string_series.astype(int))
+
+ # with NaNs
+ string_series[5:7] = np.nan
+ str(string_series)
+
+ def test_object(self, object_series):
+ str(object_series)
+
+ def test_datetime(self, datetime_series):
+ str(datetime_series)
+ # with Nones
+ ots = datetime_series.astype("O")
+ ots[::2] = None
+ repr(ots)
+
+ @pytest.mark.parametrize(
+ "name",
+ [
+ "",
+ 1,
+ 1.2,
+ "foo",
+ "\u03b1\u03b2\u03b3",
+ "loooooooooooooooooooooooooooooooooooooooooooooooooooong",
+ ("foo", "bar", "baz"),
+ (1, 2),
+ ("foo", 1, 2.3),
+ ("\u03b1", "\u03b2", "\u03b3"),
+ ("\u03b1", "bar"),
+ ],
+ )
+ def test_various_names(self, name, string_series):
+ # various names
+ string_series.name = name
+ repr(string_series)
+
+ def test_tuple_name(self):
+ biggie = Series(
+ np.random.default_rng(2).standard_normal(1000),
+ index=np.arange(1000),
+ name=("foo", "bar", "baz"),
+ )
+ repr(biggie)
+
+ @pytest.mark.parametrize("arg", [100, 1001])
+ def test_tidy_repr_name_0(self, arg):
+ # tidy repr
+ ser = Series(np.random.default_rng(2).standard_normal(arg), name=0)
+ rep_str = repr(ser)
+ assert "Name: 0" in rep_str
+
+ def test_newline(self, any_string_dtype):
+ ser = Series(
+ ["a\n\r\tb"],
+ name="a\n\r\td",
+ index=Index(["a\n\r\tf"], dtype=any_string_dtype),
+ dtype=any_string_dtype,
+ )
+ assert "\t" not in repr(ser)
+ assert "\r" not in repr(ser)
+ assert "a\n" not in repr(ser)
+
+ @pytest.mark.parametrize(
+ "name, expected",
+ [
+ ["foo", "Series([], Name: foo, dtype: int64)"],
+ [None, "Series([], dtype: int64)"],
+ ],
+ )
+ def test_empty_int64(self, name, expected):
+ # with empty series (#4651)
+ s = Series([], dtype=np.int64, name=name)
+ assert repr(s) == expected
+
+ def test_repr_bool_fails(self, capsys):
+ s = Series(
+ [
+ DataFrame(np.random.default_rng(2).standard_normal((2, 2)))
+ for i in range(5)
+ ]
+ )
+
+ # It works (with no Cython exception barf)!
+ repr(s)
+
+ captured = capsys.readouterr()
+ assert captured.err == ""
+
+ def test_repr_name_iterable_indexable(self):
+ s = Series([1, 2, 3], name=np.int64(3))
+
+ # it works!
+ repr(s)
+
+ s.name = ("\u05d0",) * 2
+ repr(s)
+
+ def test_repr_max_rows(self):
+ # GH 6863
+ with option_context("display.max_rows", None):
+ str(Series(range(1001))) # should not raise exception
+
+ def test_unicode_string_with_unicode(self):
+ df = Series(["\u05d0"], name="\u05d1")
+ str(df)
+
+ ser = Series(["\u03c3"] * 10)
+ repr(ser)
+
+ ser2 = Series(["\u05d0"] * 1000)
+ ser2.name = "title1"
+ repr(ser2)
+
+ def test_str_to_bytes_raises(self):
+ # GH 26447
+ df = Series(["abc"], name="abc")
+ msg = "^'str' object cannot be interpreted as an integer$"
+ with pytest.raises(TypeError, match=msg):
+ bytes(df)
+
+ def test_timeseries_repr_object_dtype(self):
+ index = Index(
+ [datetime(2000, 1, 1) + timedelta(i) for i in range(1000)], dtype=object
+ )
+ ts = Series(np.random.default_rng(2).standard_normal(len(index)), index)
+ repr(ts)
+
+ ts = Series(
+ np.arange(20, dtype=np.float64), index=date_range("2020-01-01", periods=20)
+ )
+ assert repr(ts).splitlines()[-1].startswith("Freq:")
+
+ ts2 = ts.iloc[np.random.default_rng(2).integers(0, len(ts) - 1, 400)]
+ repr(ts2).splitlines()[-1]
+
+ def test_latex_repr(self):
+ pytest.importorskip("jinja2") # uses Styler implementation
+ result = r"""\begin{tabular}{ll}
+\toprule
+ & 0 \\
+\midrule
+0 & $\alpha$ \\
+1 & b \\
+2 & c \\
+\bottomrule
+\end{tabular}
+"""
+ with option_context(
+ "styler.format.escape", None, "styler.render.repr", "latex"
+ ):
+ s = Series([r"$\alpha$", "b", "c"])
+ assert result == s._repr_latex_()
+
+ assert s._repr_latex_() is None
+
+ def test_index_repr_in_frame_with_nan(self):
+ # see gh-25061
+ i = Index([1, np.nan])
+ s = Series([1, 2], index=i)
+ exp = """1.0 1\nNaN 2\ndtype: int64"""
+
+ assert repr(s) == exp
+
+ def test_series_repr_nat(self):
+ series = Series([0, 1000, 2000, pd.NaT._value], dtype="M8[ns]")
+
+ result = repr(series)
+ expected = (
+ "0 1970-01-01 00:00:00.000000\n"
+ "1 1970-01-01 00:00:00.000001\n"
+ "2 1970-01-01 00:00:00.000002\n"
+ "3 NaT\n"
+ "dtype: datetime64[ns]"
+ )
+ assert result == expected
+
+ def test_float_repr(self):
+ # GH#35603
+ # check float format when cast to object
+ ser = Series([1.0]).astype(object)
+ expected = "0 1.0\ndtype: object"
+ assert repr(ser) == expected
+
+ def test_different_null_objects(self):
+ # GH#45263
+ ser = Series([1, 2, 3, 4], [True, None, np.nan, pd.NaT])
+ result = repr(ser)
+ expected = "True 1\nNone 2\nNaN 3\nNaT 4\ndtype: int64"
+ assert result == expected
+
+ def test_2d_extension_type(self):
+ # GH#33770
+
+ # Define a stub extension type with just enough code to run Series.__repr__()
+ class DtypeStub(pd.api.extensions.ExtensionDtype):
+ @property
+ def type(self):
+ return np.ndarray
+
+ @property
+ def name(self):
+ return "DtypeStub"
+
+ class ExtTypeStub(pd.api.extensions.ExtensionArray):
+ def __len__(self) -> int:
+ return 2
+
+ def __getitem__(self, ix):
+ return [ix == 1, ix == 0]
+
+ @property
+ def dtype(self):
+ return DtypeStub()
+
+ series = Series(ExtTypeStub(), copy=False)
+ res = repr(series) # This line crashed before GH#33770 was fixed.
+ expected = "\n".join(
+ ["0 [False True]", "1 [True False]", "dtype: DtypeStub"]
+ )
+ assert res == expected
+
+
+class TestCategoricalRepr:
+ def test_categorical_repr_unicode(self):
+ # see gh-21002
+
+ class County:
+ name = "San Sebastián"
+ state = "PR"
+
+ def __repr__(self) -> str:
+ return self.name + ", " + self.state
+
+ cat = Categorical([County() for _ in range(61)])
+ idx = Index(cat)
+ ser = idx.to_series()
+
+ repr(ser)
+ str(ser)
+
+ def test_categorical_repr(self, using_infer_string):
+ a = Series(Categorical([1, 2, 3, 4]))
+ exp = (
+ "0 1\n1 2\n2 3\n3 4\n"
+ "dtype: category\nCategories (4, int64): [1, 2, 3, 4]"
+ )
+
+ assert exp == a.__str__()
+
+ a = Series(Categorical(["a", "b"] * 25))
+ exp = (
+ "0 a\n1 b\n"
+ " ..\n"
+ "48 a\n49 b\n"
+ "Length: 50, dtype: category\nCategories (2, object): ['a', 'b']"
+ )
+ if using_infer_string:
+ exp = exp.replace("object", "str")
+ with option_context("display.max_rows", 5):
+ assert exp == repr(a)
+
+ levs = list("abcdefghijklmnopqrstuvwxyz")
+ a = Series(Categorical(["a", "b"], categories=levs, ordered=True))
+ exp = (
+ "0 a\n1 b\n"
+ "dtype: category\n"
+ "Categories (26, object): ['a' < 'b' < 'c' < 'd' ... "
+ "'w' < 'x' < 'y' < 'z']"
+ )
+ if using_infer_string:
+ exp = exp.replace("object", "str")
+ assert exp == a.__str__()
+
+ def test_categorical_series_repr(self):
+ s = Series(Categorical([1, 2, 3]))
+ exp = """0 1
+1 2
+2 3
+dtype: category
+Categories (3, int64): [1, 2, 3]"""
+
+ assert repr(s) == exp
+
+ s = Series(Categorical(np.arange(10)))
+ exp = f"""0 0
+1 1
+2 2
+3 3
+4 4
+5 5
+6 6
+7 7
+8 8
+9 9
+dtype: category
+Categories (10, {np.dtype(int)}): [0, 1, 2, 3, ..., 6, 7, 8, 9]"""
+
+ assert repr(s) == exp
+
+ def test_categorical_series_repr_ordered(self):
+ s = Series(Categorical([1, 2, 3], ordered=True))
+ exp = """0 1
+1 2
+2 3
+dtype: category
+Categories (3, int64): [1 < 2 < 3]"""
+
+ assert repr(s) == exp
+
+ s = Series(Categorical(np.arange(10), ordered=True))
+ exp = f"""0 0
+1 1
+2 2
+3 3
+4 4
+5 5
+6 6
+7 7
+8 8
+9 9
+dtype: category
+Categories (10, {np.dtype(int)}): [0 < 1 < 2 < 3 ... 6 < 7 < 8 < 9]"""
+
+ assert repr(s) == exp
+
+ def test_categorical_series_repr_datetime(self):
+ idx = date_range("2011-01-01 09:00", freq="h", periods=5, unit="ns")
+ s = Series(Categorical(idx))
+ exp = """0 2011-01-01 09:00:00
+1 2011-01-01 10:00:00
+2 2011-01-01 11:00:00
+3 2011-01-01 12:00:00
+4 2011-01-01 13:00:00
+dtype: category
+Categories (5, datetime64[ns]): [2011-01-01 09:00:00, 2011-01-01 10:00:00, 2011-01-01 11:00:00,
+ 2011-01-01 12:00:00, 2011-01-01 13:00:00]""" # noqa: E501
+
+ assert repr(s) == exp
+
+ idx = date_range(
+ "2011-01-01 09:00", freq="h", periods=5, tz="US/Eastern", unit="ns"
+ )
+ s = Series(Categorical(idx))
+ exp = """0 2011-01-01 09:00:00-05:00
+1 2011-01-01 10:00:00-05:00
+2 2011-01-01 11:00:00-05:00
+3 2011-01-01 12:00:00-05:00
+4 2011-01-01 13:00:00-05:00
+dtype: category
+Categories (5, datetime64[ns, US/Eastern]): [2011-01-01 09:00:00-05:00, 2011-01-01 10:00:00-05:00,
+ 2011-01-01 11:00:00-05:00, 2011-01-01 12:00:00-05:00,
+ 2011-01-01 13:00:00-05:00]""" # noqa: E501
+
+ assert repr(s) == exp
+
+ def test_categorical_series_repr_datetime_ordered(self):
+ idx = date_range("2011-01-01 09:00", freq="h", periods=5, unit="ns")
+ s = Series(Categorical(idx, ordered=True))
+ exp = """0 2011-01-01 09:00:00
+1 2011-01-01 10:00:00
+2 2011-01-01 11:00:00
+3 2011-01-01 12:00:00
+4 2011-01-01 13:00:00
+dtype: category
+Categories (5, datetime64[ns]): [2011-01-01 09:00:00 < 2011-01-01 10:00:00 < 2011-01-01 11:00:00 <
+ 2011-01-01 12:00:00 < 2011-01-01 13:00:00]""" # noqa: E501
+
+ assert repr(s) == exp
+
+ idx = date_range(
+ "2011-01-01 09:00", freq="h", periods=5, tz="US/Eastern", unit="ns"
+ )
+ s = Series(Categorical(idx, ordered=True))
+ exp = """0 2011-01-01 09:00:00-05:00
+1 2011-01-01 10:00:00-05:00
+2 2011-01-01 11:00:00-05:00
+3 2011-01-01 12:00:00-05:00
+4 2011-01-01 13:00:00-05:00
+dtype: category
+Categories (5, datetime64[ns, US/Eastern]): [2011-01-01 09:00:00-05:00 < 2011-01-01 10:00:00-05:00 <
+ 2011-01-01 11:00:00-05:00 < 2011-01-01 12:00:00-05:00 <
+ 2011-01-01 13:00:00-05:00]""" # noqa: E501
+
+ assert repr(s) == exp
+
+ def test_categorical_series_repr_period(self):
+ idx = period_range("2011-01-01 09:00", freq="h", periods=5)
+ s = Series(Categorical(idx))
+ exp = """0 2011-01-01 09:00
+1 2011-01-01 10:00
+2 2011-01-01 11:00
+3 2011-01-01 12:00
+4 2011-01-01 13:00
+dtype: category
+Categories (5, period[h]): [2011-01-01 09:00, 2011-01-01 10:00, 2011-01-01 11:00, 2011-01-01 12:00,
+ 2011-01-01 13:00]""" # noqa: E501
+
+ assert repr(s) == exp
+
+ idx = period_range("2011-01", freq="M", periods=5)
+ s = Series(Categorical(idx))
+ exp = """0 2011-01
+1 2011-02
+2 2011-03
+3 2011-04
+4 2011-05
+dtype: category
+Categories (5, period[M]): [2011-01, 2011-02, 2011-03, 2011-04, 2011-05]"""
+
+ assert repr(s) == exp
+
+ def test_categorical_series_repr_period_ordered(self):
+ idx = period_range("2011-01-01 09:00", freq="h", periods=5)
+ s = Series(Categorical(idx, ordered=True))
+ exp = """0 2011-01-01 09:00
+1 2011-01-01 10:00
+2 2011-01-01 11:00
+3 2011-01-01 12:00
+4 2011-01-01 13:00
+dtype: category
+Categories (5, period[h]): [2011-01-01 09:00 < 2011-01-01 10:00 < 2011-01-01 11:00 < 2011-01-01 12:00 <
+ 2011-01-01 13:00]""" # noqa: E501
+
+ assert repr(s) == exp
+
+ idx = period_range("2011-01", freq="M", periods=5)
+ s = Series(Categorical(idx, ordered=True))
+ exp = """0 2011-01
+1 2011-02
+2 2011-03
+3 2011-04
+4 2011-05
+dtype: category
+Categories (5, period[M]): [2011-01 < 2011-02 < 2011-03 < 2011-04 < 2011-05]"""
+
+ assert repr(s) == exp
+
+ def test_categorical_series_repr_timedelta(self):
+ idx = timedelta_range("1 days", periods=5)
+ s = Series(Categorical(idx))
+ exp = """0 1 days
+1 2 days
+2 3 days
+3 4 days
+4 5 days
+dtype: category
+Categories (5, timedelta64[us]): [1 days, 2 days, 3 days, 4 days, 5 days]"""
+
+ assert repr(s) == exp
+
+ idx = timedelta_range("1 hours", periods=10)
+ s = Series(Categorical(idx))
+ exp = """0 0 days 01:00:00
+1 1 days 01:00:00
+2 2 days 01:00:00
+3 3 days 01:00:00
+4 4 days 01:00:00
+5 5 days 01:00:00
+6 6 days 01:00:00
+7 7 days 01:00:00
+8 8 days 01:00:00
+9 9 days 01:00:00
+dtype: category
+Categories (10, timedelta64[us]): [0 days 01:00:00, 1 days 01:00:00, 2 days 01:00:00,
+ 3 days 01:00:00, ..., 6 days 01:00:00, 7 days 01:00:00,
+ 8 days 01:00:00, 9 days 01:00:00]""" # noqa: E501
+
+ assert repr(s) == exp
+
+ def test_categorical_series_repr_timedelta_ordered(self):
+ idx = timedelta_range("1 days", periods=5)
+ s = Series(Categorical(idx, ordered=True))
+ exp = """0 1 days
+1 2 days
+2 3 days
+3 4 days
+4 5 days
+dtype: category
+Categories (5, timedelta64[us]): [1 days < 2 days < 3 days < 4 days < 5 days]"""
+
+ assert repr(s) == exp
+
+ idx = timedelta_range("1 hours", periods=10)
+ s = Series(Categorical(idx, ordered=True))
+ exp = """0 0 days 01:00:00
+1 1 days 01:00:00
+2 2 days 01:00:00
+3 3 days 01:00:00
+4 4 days 01:00:00
+5 5 days 01:00:00
+6 6 days 01:00:00
+7 7 days 01:00:00
+8 8 days 01:00:00
+9 9 days 01:00:00
+dtype: category
+Categories (10, timedelta64[us]): [0 days 01:00:00 < 1 days 01:00:00 < 2 days 01:00:00 <
+ 3 days 01:00:00 ... 6 days 01:00:00 < 7 days 01:00:00 <
+ 8 days 01:00:00 < 9 days 01:00:00]""" # noqa: E501
+
+ assert repr(s) == exp
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_iteration.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_iteration.py
new file mode 100644
index 0000000000000000000000000000000000000000..db5d80b3798b91b6c3ba212c2dbe7c5c5c56a5a6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_iteration.py
@@ -0,0 +1,33 @@
+class TestIteration:
+ def test_keys(self, datetime_series):
+ assert datetime_series.keys() is datetime_series.index
+
+ def test_iter_datetimes(self, datetime_series):
+ for i, val in enumerate(datetime_series):
+ assert val == datetime_series.iloc[i]
+
+ def test_iter_strings(self, string_series):
+ for i, val in enumerate(string_series):
+ assert val == string_series.iloc[i]
+
+ def test_iteritems_datetimes(self, datetime_series):
+ for idx, val in datetime_series.items():
+ assert val == datetime_series[idx] # noqa: PLR1733
+
+ def test_iteritems_strings(self, string_series):
+ for idx, val in string_series.items():
+ assert val == string_series[idx] # noqa: PLR1733
+
+ # assert is lazy (generators don't define reverse, lists do)
+ assert not hasattr(string_series.items(), "reverse")
+
+ def test_items_datetimes(self, datetime_series):
+ for idx, val in datetime_series.items():
+ assert val == datetime_series[idx] # noqa: PLR1733
+
+ def test_items_strings(self, string_series):
+ for idx, val in string_series.items():
+ assert val == string_series[idx] # noqa: PLR1733
+
+ # assert is lazy (generators don't define reverse, lists do)
+ assert not hasattr(string_series.items(), "reverse")
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_logical_ops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_logical_ops.py
new file mode 100644
index 0000000000000000000000000000000000000000..a32a88b77c3a1bd864e819e5041a1844025bc4b5
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_logical_ops.py
@@ -0,0 +1,510 @@
+from datetime import datetime
+import operator
+
+import numpy as np
+import pytest
+
+from pandas import (
+ DataFrame,
+ Index,
+ Series,
+ bdate_range,
+)
+import pandas._testing as tm
+from pandas.core import ops
+
+
+class TestSeriesLogicalOps:
+ @pytest.mark.parametrize("bool_op", [operator.and_, operator.or_, operator.xor])
+ def test_bool_operators_with_nas(self, bool_op):
+ # boolean &, |, ^ should work with object arrays and propagate NAs
+ ser = Series(bdate_range("1/1/2000", periods=10), dtype=object)
+ ser[::2] = np.nan
+
+ mask = ser.isna()
+ filled = ser.fillna(ser[0])
+
+ result = bool_op(ser < ser[9], ser > ser[3])
+
+ expected = bool_op(filled < filled[9], filled > filled[3])
+ expected[mask] = False
+ tm.assert_series_equal(result, expected)
+
+ def test_logical_operators_bool_dtype_with_empty(self):
+ # GH#9016: support bitwise op for integer types
+ index = list("bca")
+
+ s_tft = Series([True, False, True], index=index)
+ s_fff = Series([False, False, False], index=index)
+ s_empty = Series([], dtype=object)
+
+ res = s_tft & s_empty
+ expected = s_fff.sort_index()
+ tm.assert_series_equal(res, expected)
+
+ res = s_tft | s_empty
+ expected = s_tft.sort_index()
+ tm.assert_series_equal(res, expected)
+
+ def test_logical_operators_int_dtype_with_int_dtype(self):
+ # GH#9016: support bitwise op for integer types
+
+ s_0123 = Series(range(4), dtype="int64")
+ s_3333 = Series([3] * 4)
+ s_4444 = Series([4] * 4)
+
+ res = s_0123 & s_3333
+ expected = Series(range(4), dtype="int64")
+ tm.assert_series_equal(res, expected)
+
+ res = s_0123 | s_4444
+ expected = Series(range(4, 8), dtype="int64")
+ tm.assert_series_equal(res, expected)
+
+ s_1111 = Series([1] * 4, dtype="int8")
+ res = s_0123 & s_1111
+ expected = Series([0, 1, 0, 1], dtype="int64")
+ tm.assert_series_equal(res, expected)
+
+ res = s_0123.astype(np.int16) | s_1111.astype(np.int32)
+ expected = Series([1, 1, 3, 3], dtype="int32")
+ tm.assert_series_equal(res, expected)
+
+ def test_logical_operators_int_dtype_with_int_scalar(self):
+ # GH#9016: support bitwise op for integer types
+ s_0123 = Series(range(4), dtype="int64")
+
+ res = s_0123 & 0
+ expected = Series([0] * 4)
+ tm.assert_series_equal(res, expected)
+
+ res = s_0123 & 1
+ expected = Series([0, 1, 0, 1])
+ tm.assert_series_equal(res, expected)
+
+ def test_logical_operators_int_dtype_with_float(self):
+ # GH#9016: support bitwise op for integer types
+ s_0123 = Series(range(4), dtype="int64")
+
+ err_msg = (
+ r"Logical ops \(and, or, xor\) between Pandas objects and "
+ "dtype-less sequences"
+ )
+
+ msg = "Cannot perform.+with a dtyped.+array and scalar of type"
+ with pytest.raises(TypeError, match=msg):
+ s_0123 & np.nan
+ with pytest.raises(TypeError, match=msg):
+ s_0123 & 3.14
+ msg = "unsupported operand type.+for &:"
+ with pytest.raises(TypeError, match=err_msg):
+ s_0123 & [0.1, 4, 3.14, 2]
+ with pytest.raises(TypeError, match=msg):
+ s_0123 & np.array([0.1, 4, 3.14, 2])
+ with pytest.raises(TypeError, match=msg):
+ s_0123 & Series([0.1, 4, -3.14, 2])
+
+ def test_logical_operators_int_dtype_with_str(self):
+ s_1111 = Series([1] * 4, dtype="int8")
+
+ err_msg = (
+ r"Logical ops \(and, or, xor\) between Pandas objects and "
+ "dtype-less sequences"
+ )
+
+ msg = "Cannot perform 'and_' with a dtyped.+array and scalar of type"
+ with pytest.raises(TypeError, match=msg):
+ s_1111 & "a"
+ with pytest.raises(TypeError, match=err_msg):
+ s_1111 & ["a", "b", "c", "d"]
+
+ def test_logical_operators_int_dtype_with_bool(self):
+ # GH#9016: support bitwise op for integer types
+ s_0123 = Series(range(4), dtype="int64")
+
+ expected = Series([False] * 4)
+
+ result = s_0123 & False
+ tm.assert_series_equal(result, expected)
+
+ msg = (
+ r"Logical ops \(and, or, xor\) between Pandas objects and "
+ "dtype-less sequences"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s_0123 & [False]
+
+ with pytest.raises(TypeError, match=msg):
+ s_0123 & (False,)
+
+ result = s_0123 ^ False
+ expected = Series([False, True, True, True])
+ tm.assert_series_equal(result, expected)
+
+ def test_logical_operators_int_dtype_with_object(self):
+ # GH#9016: support bitwise op for integer types
+ s_0123 = Series(range(4), dtype="int64")
+
+ result = s_0123 & Series([False, np.nan, False, False])
+ expected = Series([False] * 4)
+ tm.assert_series_equal(result, expected)
+
+ s_abNd = Series(["a", "b", np.nan, "d"])
+ with pytest.raises(
+ TypeError, match="unsupported.* 'int' and 'str'|'rand_' not supported"
+ ):
+ s_0123 & s_abNd
+
+ def test_logical_operators_bool_dtype_with_int(self):
+ index = list("bca")
+
+ s_tft = Series([True, False, True], index=index)
+ s_fff = Series([False, False, False], index=index)
+
+ res = s_tft & 0
+ expected = s_fff
+ tm.assert_series_equal(res, expected)
+
+ res = s_tft & 1
+ expected = s_tft
+ tm.assert_series_equal(res, expected)
+
+ def test_logical_ops_bool_dtype_with_ndarray(self):
+ # make sure we operate on ndarray the same as Series
+ left = Series([True, True, True, False, True])
+ right = [True, False, None, True, np.nan]
+
+ msg = (
+ r"Logical ops \(and, or, xor\) between Pandas objects and "
+ "dtype-less sequences"
+ )
+
+ expected = Series([True, False, False, False, False])
+ with pytest.raises(TypeError, match=msg):
+ left & right
+ result = left & np.array(right)
+ tm.assert_series_equal(result, expected)
+ result = left & Index(right)
+ tm.assert_series_equal(result, expected)
+ result = left & Series(right)
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([True, True, True, True, True])
+ with pytest.raises(TypeError, match=msg):
+ left | right
+ result = left | np.array(right)
+ tm.assert_series_equal(result, expected)
+ result = left | Index(right)
+ tm.assert_series_equal(result, expected)
+ result = left | Series(right)
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([False, True, True, True, True])
+ with pytest.raises(TypeError, match=msg):
+ left ^ right
+ result = left ^ np.array(right)
+ tm.assert_series_equal(result, expected)
+ result = left ^ Index(right)
+ tm.assert_series_equal(result, expected)
+ result = left ^ Series(right)
+ tm.assert_series_equal(result, expected)
+
+ def test_logical_operators_int_dtype_with_bool_dtype_and_reindex(self):
+ # GH#9016: support bitwise op for integer types
+
+ index = list("bca")
+
+ s_tft = Series([True, False, True], index=index)
+ s_tft = Series([True, False, True], index=index)
+ s_tff = Series([True, False, False], index=index)
+
+ s_0123 = Series(range(4), dtype="int64")
+
+ # s_0123 will be all false now because of reindexing like s_tft
+ expected = Series([False] * 7, index=[0, 1, 2, 3, "a", "b", "c"])
+ result = s_tft & s_0123
+ tm.assert_series_equal(result, expected)
+
+ # GH#52538: no longer to object type when reindex is needed;
+ # matches DataFrame behavior
+ msg = r"unsupported operand type\(s\) for &: 'float' and 'bool'"
+ with pytest.raises(TypeError, match=msg):
+ s_0123 & s_tft
+
+ s_a0b1c0 = Series([1], list("b"))
+
+ res = s_tft & s_a0b1c0
+ expected = s_tff.reindex(list("abc"))
+ tm.assert_series_equal(res, expected)
+
+ res = s_tft | s_a0b1c0
+ expected = s_tft.reindex(list("abc"))
+ tm.assert_series_equal(res, expected)
+
+ def test_scalar_na_logical_ops_corners(self):
+ s = Series([2, 3, 4, 5, 6, 7, 8, 9, 10])
+
+ msg = "Cannot perform.+with a dtyped.+array and scalar of type"
+ with pytest.raises(TypeError, match=msg):
+ s & datetime(2005, 1, 1)
+
+ s = Series([2, 3, 4, 5, 6, 7, 8, 9, datetime(2005, 1, 1)])
+ s[::2] = np.nan
+
+ expected = Series(True, index=s.index)
+ expected[::2] = False
+
+ msg = (
+ r"Logical ops \(and, or, xor\) between Pandas objects and "
+ "dtype-less sequences"
+ )
+ with pytest.raises(TypeError, match=msg):
+ s & list(s)
+
+ def test_scalar_na_logical_ops_corners_aligns(self):
+ s = Series([2, 3, 4, 5, 6, 7, 8, 9, datetime(2005, 1, 1)])
+ s[::2] = np.nan
+ d = DataFrame({"A": s})
+
+ expected = DataFrame(False, index=range(9), columns=["A", *list(range(9))])
+
+ result = s & d
+ tm.assert_frame_equal(result, expected)
+
+ result = d & s
+ tm.assert_frame_equal(result, expected)
+
+ @pytest.mark.parametrize("op", [operator.and_, operator.or_, operator.xor])
+ def test_logical_ops_with_index(self, op):
+ # GH#22092, GH#19792
+ ser = Series([True, True, False, False])
+ idx1 = Index([True, False, True, False])
+ idx2 = Index([1, 0, 1, 0])
+
+ expected = Series([op(ser[n], idx1[n]) for n in range(len(ser))])
+
+ result = op(ser, idx1)
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([op(ser[n], idx2[n]) for n in range(len(ser))], dtype=bool)
+
+ result = op(ser, idx2)
+ tm.assert_series_equal(result, expected)
+
+ def test_reversed_xor_with_index_returns_series(self):
+ # GH#22092, GH#19792 pre-2.0 these were aliased to setops
+ ser = Series([True, True, False, False])
+ idx1 = Index([True, False, True, False], dtype=bool)
+ idx2 = Index([1, 0, 1, 0])
+
+ expected = Series([False, True, True, False])
+ result = idx1 ^ ser
+ tm.assert_series_equal(result, expected)
+
+ result = idx2 ^ ser
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "op",
+ [
+ ops.rand_,
+ ops.ror_,
+ ],
+ )
+ def test_reversed_logical_op_with_index_returns_series(self, op):
+ # GH#22092, GH#19792
+ ser = Series([True, True, False, False])
+ idx1 = Index([True, False, True, False])
+ idx2 = Index([1, 0, 1, 0])
+
+ expected = Series(op(idx1.values, ser.values))
+ result = op(ser, idx1)
+ tm.assert_series_equal(result, expected)
+
+ expected = op(ser, Series(idx2))
+ result = op(ser, idx2)
+ tm.assert_series_equal(result, expected)
+
+ @pytest.mark.parametrize(
+ "op, expected",
+ [
+ (ops.rand_, [False, False]),
+ (ops.ror_, [True, True]),
+ (ops.rxor, [True, True]),
+ ],
+ )
+ def test_reverse_ops_with_index(self, op, expected):
+ # https://github.com/pandas-dev/pandas/pull/23628
+ # multi-set Index ops are buggy, so let's avoid duplicates...
+ # GH#49503
+ ser = Series([True, False])
+ idx = Index([False, True])
+
+ result = op(ser, idx)
+ expected = Series(expected)
+ tm.assert_series_equal(result, expected)
+
+ def test_logical_ops_label_based(self, using_infer_string):
+ # GH#4947
+ # logical ops should be label based
+
+ a = Series([True, False, True], list("bca"))
+ b = Series([False, True, False], list("abc"))
+
+ expected = Series([False, True, False], list("abc"))
+ result = a & b
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([True, True, False], list("abc"))
+ result = a | b
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([True, False, False], list("abc"))
+ result = a ^ b
+ tm.assert_series_equal(result, expected)
+
+ # rhs is bigger
+ a = Series([True, False, True], list("bca"))
+ b = Series([False, True, False, True], list("abcd"))
+
+ expected = Series([False, True, False, False], list("abcd"))
+ result = a & b
+ tm.assert_series_equal(result, expected)
+
+ expected = Series([True, True, False, False], list("abcd"))
+ result = a | b
+ tm.assert_series_equal(result, expected)
+
+ # filling
+
+ # vs empty
+ empty = Series([], dtype=object)
+
+ result = a & empty
+ expected = Series([False, False, False], list("abc"))
+ tm.assert_series_equal(result, expected)
+
+ result = a | empty
+ expected = Series([True, True, False], list("abc"))
+ tm.assert_series_equal(result, expected)
+
+ # vs non-matching
+ result = a & Series([1], ["z"])
+ expected = Series([False, False, False, False], list("abcz"))
+ tm.assert_series_equal(result, expected)
+
+ result = a | Series([1], ["z"])
+ expected = Series([True, True, False, False], list("abcz"))
+ tm.assert_series_equal(result, expected)
+
+ # identity
+ # we would like s[s|e] == s to hold for any e, whether empty or not
+ for e in [
+ empty.copy(),
+ Series([1], ["z"]),
+ Series(np.nan, b.index),
+ Series(np.nan, a.index),
+ ]:
+ result = a[a | e]
+ tm.assert_series_equal(result, a[a])
+
+ for e in [Series(["z"])]:
+ if using_infer_string:
+ # TODO(infer_string) should this behave differently?
+ # -> https://github.com/pandas-dev/pandas/issues/60234
+ with pytest.raises(
+ TypeError, match="not supported for dtype|unsupported operand type"
+ ):
+ result = a[a | e]
+ else:
+ result = a[a | e]
+ tm.assert_series_equal(result, a[a])
+
+ # vs scalars
+ index = list("bca")
+ t = Series([True, False, True])
+
+ for v in [True, 1, 2]:
+ result = Series([True, False, True], index=index) | v
+ expected = Series([True, True, True], index=index)
+ tm.assert_series_equal(result, expected)
+
+ msg = "Cannot perform.+with a dtyped.+array and scalar of type"
+ for v in [np.nan, "foo"]:
+ with pytest.raises(TypeError, match=msg):
+ t | v
+
+ for v in [False, 0]:
+ result = Series([True, False, True], index=index) | v
+ expected = Series([True, False, True], index=index)
+ tm.assert_series_equal(result, expected)
+
+ for v in [True, 1]:
+ result = Series([True, False, True], index=index) & v
+ expected = Series([True, False, True], index=index)
+ tm.assert_series_equal(result, expected)
+
+ for v in [False, 0]:
+ result = Series([True, False, True], index=index) & v
+ expected = Series([False, False, False], index=index)
+ tm.assert_series_equal(result, expected)
+ msg = "Cannot perform.+with a dtyped.+array and scalar of type"
+ for v in [np.nan]:
+ with pytest.raises(TypeError, match=msg):
+ t & v
+
+ def test_logical_ops_df_compat(self):
+ # GH#1134
+ s1 = Series([True, False, True], index=list("ABC"), name="x")
+ s2 = Series([True, True, False], index=list("ABD"), name="x")
+
+ exp = Series([True, False, False, False], index=list("ABCD"), name="x")
+ tm.assert_series_equal(s1 & s2, exp)
+ tm.assert_series_equal(s2 & s1, exp)
+
+ # True | np.nan => True
+ exp_or1 = Series([True, True, True, False], index=list("ABCD"), name="x")
+ tm.assert_series_equal(s1 | s2, exp_or1)
+ # np.nan | True => np.nan, filled with False
+ exp_or = Series([True, True, False, False], index=list("ABCD"), name="x")
+ tm.assert_series_equal(s2 | s1, exp_or)
+
+ # DataFrame doesn't fill nan with False
+ tm.assert_frame_equal(s1.to_frame() & s2.to_frame(), exp.to_frame())
+ tm.assert_frame_equal(s2.to_frame() & s1.to_frame(), exp.to_frame())
+
+ exp = DataFrame({"x": [True, True, np.nan, np.nan]}, index=list("ABCD"))
+ tm.assert_frame_equal(s1.to_frame() | s2.to_frame(), exp_or1.to_frame())
+ tm.assert_frame_equal(s2.to_frame() | s1.to_frame(), exp_or.to_frame())
+
+ # different length
+ s3 = Series([True, False, True], index=list("ABC"), name="x")
+ s4 = Series([True, True, True, True], index=list("ABCD"), name="x")
+
+ exp = Series([True, False, True, False], index=list("ABCD"), name="x")
+ tm.assert_series_equal(s3 & s4, exp)
+ tm.assert_series_equal(s4 & s3, exp)
+
+ # np.nan | True => np.nan, filled with False
+ exp_or1 = Series([True, True, True, False], index=list("ABCD"), name="x")
+ tm.assert_series_equal(s3 | s4, exp_or1)
+ # True | np.nan => True
+ exp_or = Series([True, True, True, True], index=list("ABCD"), name="x")
+ tm.assert_series_equal(s4 | s3, exp_or)
+
+ tm.assert_frame_equal(s3.to_frame() & s4.to_frame(), exp.to_frame())
+ tm.assert_frame_equal(s4.to_frame() & s3.to_frame(), exp.to_frame())
+
+ tm.assert_frame_equal(s3.to_frame() | s4.to_frame(), exp_or1.to_frame())
+ tm.assert_frame_equal(s4.to_frame() | s3.to_frame(), exp_or.to_frame())
+
+ def test_int_dtype_different_index_not_bool(self):
+ # GH 52500
+ ser1 = Series([1, 2, 3], index=[10, 11, 23], name="a")
+ ser2 = Series([10, 20, 30], index=[11, 10, 23], name="a")
+ result = np.bitwise_xor(ser1, ser2)
+ expected = Series([21, 8, 29], index=[10, 11, 23], name="a")
+ tm.assert_series_equal(result, expected)
+
+ result = ser1 ^ ser2
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_missing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_missing.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c88329a83b0ef5f67468b5b6db8e595ea55d2a4
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_missing.py
@@ -0,0 +1,88 @@
+from datetime import timedelta
+
+import numpy as np
+import pytest
+
+from pandas._libs import iNaT
+
+import pandas as pd
+from pandas import (
+ Categorical,
+ Index,
+ NaT,
+ Series,
+ isna,
+)
+import pandas._testing as tm
+
+
+class TestSeriesMissingData:
+ def test_categorical_nan_handling(self):
+ # NaNs are represented as -1 in labels
+ s = Series(Categorical(["a", "b", np.nan, "a"]))
+ tm.assert_index_equal(s.cat.categories, Index(["a", "b"]))
+ tm.assert_numpy_array_equal(
+ s.values.codes, np.array([0, 1, -1, 0], dtype=np.int8)
+ )
+
+ def test_timedelta64_nan(self):
+ td = Series([timedelta(days=i) for i in range(10)])
+
+ # nan ops on timedeltas
+ td1 = td.copy()
+ td1[0] = np.nan
+ assert isna(td1[0])
+ assert td1[0]._value == iNaT
+ td1[0] = td[0]
+ assert not isna(td1[0])
+
+ # GH#16674 iNaT is treated as an integer when given by the user
+ with pytest.raises(TypeError, match="Invalid value"):
+ td1[1] = iNaT
+
+ td1[2] = NaT
+ assert isna(td1[2])
+ assert td1[2]._value == iNaT
+ td1[2] = td[2]
+ assert not isna(td1[2])
+
+ # boolean setting
+ # GH#2899 boolean setting
+ td3 = np.timedelta64(timedelta(days=3))
+ td7 = np.timedelta64(timedelta(days=7))
+ td[(td > td3) & (td < td7)] = np.nan
+ assert isna(td).sum() == 3
+
+ @pytest.mark.xfail(
+ reason="Chained inequality raises when trying to define 'selector'"
+ )
+ def test_logical_range_select(self, datetime_series):
+ # NumPy limitation =(
+ # https://github.com/pandas-dev/pandas/commit/9030dc021f07c76809848925cb34828f6c8484f3
+
+ selector = -0.5 <= datetime_series <= 0.5
+ expected = (datetime_series >= -0.5) & (datetime_series <= 0.5)
+ tm.assert_series_equal(selector, expected)
+
+ def test_valid(self, datetime_series):
+ ts = datetime_series.copy()
+ ts.index = ts.index._with_freq(None)
+ ts[::2] = np.nan
+
+ result = ts.dropna()
+ assert len(result) == ts.count()
+ tm.assert_series_equal(result, ts[1::2])
+ tm.assert_series_equal(result, ts[pd.notna(ts)])
+
+
+def test_hasnans_uncached_for_series():
+ # GH#19700
+ # set float64 dtype to avoid upcast when setting nan
+ idx = Index([0, 1], dtype="float64")
+ assert idx.hasnans is False
+ assert "hasnans" in idx._cache
+ ser = idx.to_series()
+ assert ser.hasnans is False
+ assert not hasattr(ser, "_cache")
+ ser.iloc[-1] = np.nan
+ assert ser.hasnans is True
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_npfuncs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_npfuncs.py
new file mode 100644
index 0000000000000000000000000000000000000000..f30c01b49639935a8ace4ed1d91baa84f899d3b6
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_npfuncs.py
@@ -0,0 +1,52 @@
+"""
+Tests for np.foo applied to Series, not necessarily ufuncs.
+"""
+
+import numpy as np
+import pytest
+
+import pandas.util._test_decorators as td
+
+from pandas import Series
+import pandas._testing as tm
+
+
+class TestPtp:
+ def test_ptp(self):
+ # GH#21614
+ N = 1000
+ arr = np.random.default_rng(2).standard_normal(N)
+ ser = Series(arr)
+ assert np.ptp(ser) == np.ptp(arr)
+
+
+def test_numpy_unique(datetime_series):
+ # it works!
+ np.unique(datetime_series)
+
+
+@pytest.mark.parametrize("index", [["a", "b", "c", "d", "e"], None])
+def test_numpy_argwhere(index):
+ # GH#35331
+
+ s = Series(range(5), index=index, dtype=np.int64)
+
+ result = np.argwhere(s > 2).astype(np.int64)
+ expected = np.array([[3], [4]], dtype=np.int64)
+
+ tm.assert_numpy_array_equal(result, expected)
+
+
+@td.skip_if_no("pyarrow")
+def test_log_arrow_backed_missing_value(using_nan_is_na):
+ # GH#56285
+ ser = Series([1, 2, None], dtype="float64[pyarrow]")
+ if using_nan_is_na:
+ result = np.log(ser)
+ expected = np.log(Series([1, 2, None], dtype="float64[pyarrow]"))
+ tm.assert_series_equal(result, expected)
+ else:
+ # we get cast to object which raises
+ msg = "loop of ufunc does not support argument"
+ with pytest.raises(TypeError, match=msg):
+ np.log(ser)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_reductions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_reductions.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0be93324dde56e6d393873d3c62fc3c750dee8e
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_reductions.py
@@ -0,0 +1,233 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+from pandas import Series
+import pandas._testing as tm
+
+
+@pytest.mark.parametrize("operation, expected", [("min", "a"), ("max", "b")])
+def test_reductions_series_strings(operation, expected):
+ # GH#31746
+ ser = Series(["a", "b"], dtype="string")
+ res_operation_serie = getattr(ser, operation)()
+ assert res_operation_serie == expected
+
+
+@pytest.mark.parametrize("as_period", [True, False])
+def test_mode_extension_dtype(as_period):
+ # GH#41927 preserve dt64tz dtype
+ ser = Series([pd.Timestamp(1979, 4, n) for n in range(1, 5)])
+
+ if as_period:
+ ser = ser.dt.to_period("D")
+ else:
+ ser = ser.dt.tz_localize("US/Central")
+
+ res = ser.mode()
+ assert res.dtype == ser.dtype
+ tm.assert_series_equal(res, ser)
+
+
+def test_mode_nullable_dtype(any_numeric_ea_dtype):
+ # GH#55340
+ ser = Series([1, 3, 2, pd.NA, 3, 2, pd.NA], dtype=any_numeric_ea_dtype)
+ result = ser.mode(dropna=False)
+ expected = Series([2, 3, pd.NA], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(result, expected)
+
+ result = ser.mode(dropna=True)
+ expected = Series([2, 3], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(result, expected)
+
+ ser[-1] = pd.NA
+
+ result = ser.mode(dropna=True)
+ expected = Series([2, 3], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(result, expected)
+
+ result = ser.mode(dropna=False)
+ expected = Series([pd.NA], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_mode_nullable_dtype_edge_case(any_numeric_ea_dtype):
+ # GH##58926
+ ser = Series([1, 2, 3, 1], dtype=any_numeric_ea_dtype)
+ result = ser.mode(dropna=False)
+ expected = Series([1], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(result, expected)
+
+ ser2 = Series([1, 1, 2, 3, pd.NA], dtype=any_numeric_ea_dtype)
+ result = ser2.mode(dropna=False)
+ expected = Series([1], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(result, expected)
+
+ ser3 = Series([1, pd.NA, pd.NA], dtype=any_numeric_ea_dtype)
+ result = ser3.mode(dropna=False)
+ expected = Series([pd.NA], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(result, expected)
+
+ ser4 = Series([1, 1, pd.NA, pd.NA], dtype=any_numeric_ea_dtype)
+ result = ser4.mode(dropna=False)
+ expected = Series([1, pd.NA], dtype=any_numeric_ea_dtype)
+ tm.assert_series_equal(result, expected)
+
+
+def test_mode_infer_string():
+ # GH#56183
+ pytest.importorskip("pyarrow")
+ ser = Series(["a", "b"], dtype=object)
+ with pd.option_context("future.infer_string", True):
+ result = ser.mode()
+ expected = Series(["a", "b"], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+
+def test_reductions_td64_with_nat():
+ # GH#8617
+ ser = Series([0, pd.NaT], dtype="m8[ns]")
+ exp = ser[0]
+ assert ser.median() == exp
+ assert ser.min() == exp
+ assert ser.max() == exp
+
+
+def test_td64_sum_empty(skipna):
+ # GH#37151
+ ser = Series([], dtype="timedelta64[ns]")
+
+ result = ser.sum(skipna=skipna)
+ assert isinstance(result, pd.Timedelta)
+ assert result == pd.Timedelta(0)
+
+
+def test_td64_summation_overflow():
+ # GH#9442
+ ser = Series(pd.date_range("20130101", periods=100000, freq="h", unit="ns"))
+ ser[0] += pd.Timedelta("1s 1ms")
+
+ # mean
+ result = (ser - ser.min()).mean()
+ expected = pd.Timedelta((pd.TimedeltaIndex(ser - ser.min()).asi8 / len(ser)).sum())
+
+ # the computation is converted to float so
+ # might be some loss of precision
+ assert np.allclose(result._value / 1000, expected._value / 1000)
+
+ # sum
+ msg = "overflow in timedelta operation"
+ with pytest.raises(ValueError, match=msg):
+ (ser - ser.min()).sum()
+
+ s1 = ser[0:10000]
+ with pytest.raises(ValueError, match=msg):
+ (s1 - s1.min()).sum()
+ s2 = ser[0:1000]
+ (s2 - s2.min()).sum()
+
+
+def test_prod_numpy16_bug():
+ ser = Series([1.0, 1.0, 1.0], index=range(3))
+ result = ser.prod()
+
+ assert not isinstance(result, Series)
+
+
+@pytest.mark.parametrize("func", [np.any, np.all])
+@pytest.mark.parametrize("kwargs", [{"keepdims": True}, {"out": object()}])
+def test_validate_any_all_out_keepdims_raises(kwargs, func):
+ ser = Series([1, 2])
+ param = next(iter(kwargs))
+ name = func.__name__
+
+ msg = (
+ f"the '{param}' parameter is not "
+ "supported in the pandas "
+ rf"implementation of {name}\(\)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ func(ser, **kwargs)
+
+
+def test_validate_sum_initial():
+ ser = Series([1, 2])
+ msg = (
+ r"the 'initial' parameter is not "
+ r"supported in the pandas "
+ r"implementation of sum\(\)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ np.sum(ser, initial=10)
+
+
+def test_validate_median_initial():
+ ser = Series([1, 2])
+ msg = (
+ r"the 'overwrite_input' parameter is not "
+ r"supported in the pandas "
+ r"implementation of median\(\)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ # It seems like np.median doesn't dispatch, so we use the
+ # method instead of the ufunc.
+ ser.median(overwrite_input=True)
+
+
+def test_validate_stat_keepdims():
+ ser = Series([1, 2])
+ msg = (
+ r"the 'keepdims' parameter is not "
+ r"supported in the pandas "
+ r"implementation of sum\(\)"
+ )
+ with pytest.raises(ValueError, match=msg):
+ np.sum(ser, keepdims=True)
+
+
+def test_mean_with_convertible_string_raises():
+ # GH#44008
+ ser = Series(["1", "2"])
+ assert ser.sum() == "12"
+
+ msg = "Could not convert string '12' to numeric|does not support|Cannot perform"
+ with pytest.raises(TypeError, match=msg):
+ ser.mean()
+
+ df = ser.to_frame()
+ msg = r"Could not convert \['12'\] to numeric|does not support|Cannot perform"
+ with pytest.raises(TypeError, match=msg):
+ df.mean()
+
+
+def test_mean_dont_convert_j_to_complex():
+ # GH#36703
+ df = pd.DataFrame([{"db": "J", "numeric": 123}])
+ msg = r"Could not convert \['J'\] to numeric|does not support|Cannot perform"
+ with pytest.raises(TypeError, match=msg):
+ df.mean()
+
+ with pytest.raises(TypeError, match=msg):
+ df.agg("mean")
+
+ msg = "Could not convert string 'J' to numeric|does not support|Cannot perform"
+ with pytest.raises(TypeError, match=msg):
+ df["db"].mean()
+ msg = "Could not convert string 'J' to numeric|ufunc 'divide'|Cannot perform"
+ with pytest.raises(TypeError, match=msg):
+ np.mean(df["db"].astype("string").array)
+
+
+def test_median_with_convertible_string_raises():
+ # GH#34671 this _could_ return a string "2", but definitely not float 2.0
+ msg = r"Cannot convert \['1' '2' '3'\] to numeric|does not support|Cannot perform"
+ ser = Series(["1", "2", "3"])
+ with pytest.raises(TypeError, match=msg):
+ ser.median()
+
+ msg = (
+ r"Cannot convert \[\['1' '2' '3'\]\] to numeric|does not support|Cannot perform"
+ )
+ df = ser.to_frame()
+ with pytest.raises(TypeError, match=msg):
+ df.median()
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_subclass.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_subclass.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2d5afcf884b12b3007905061b7c503359e71a5d
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_subclass.py
@@ -0,0 +1,82 @@
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+
+pytestmark = pytest.mark.filterwarnings(
+ "ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
+)
+
+
+class TestSeriesSubclassing:
+ @pytest.mark.parametrize(
+ "idx_method, indexer, exp_data, exp_idx",
+ [
+ ["loc", ["a", "b"], [1, 2], "ab"],
+ ["iloc", [2, 3], [3, 4], "cd"],
+ ],
+ )
+ def test_indexing_sliced(self, idx_method, indexer, exp_data, exp_idx):
+ s = tm.SubclassedSeries([1, 2, 3, 4], index=list("abcd"))
+ res = getattr(s, idx_method)[indexer]
+ exp = tm.SubclassedSeries(exp_data, index=list(exp_idx))
+ tm.assert_series_equal(res, exp)
+
+ def test_to_frame(self):
+ s = tm.SubclassedSeries([1, 2, 3, 4], index=list("abcd"), name="xxx")
+ res = s.to_frame()
+ exp = tm.SubclassedDataFrame({"xxx": [1, 2, 3, 4]}, index=list("abcd"))
+ tm.assert_frame_equal(res, exp)
+
+ def test_subclass_unstack(self):
+ # GH 15564
+ s = tm.SubclassedSeries([1, 2, 3, 4], index=[list("aabb"), list("xyxy")])
+
+ res = s.unstack()
+ exp = tm.SubclassedDataFrame({"x": [1, 3], "y": [2, 4]}, index=["a", "b"])
+
+ tm.assert_frame_equal(res, exp)
+
+ def test_subclass_empty_repr(self):
+ sub_series = tm.SubclassedSeries()
+ assert "SubclassedSeries" in repr(sub_series)
+
+ def test_asof(self):
+ N = 3
+ rng = pd.date_range("1/1/1990", periods=N, freq="53s")
+ s = tm.SubclassedSeries({"A": [np.nan, np.nan, np.nan]}, index=rng)
+
+ result = s.asof(rng[-2:])
+ assert isinstance(result, tm.SubclassedSeries)
+
+ def test_explode(self):
+ s = tm.SubclassedSeries([[1, 2, 3], "foo", [], [3, 4]])
+ result = s.explode()
+ assert isinstance(result, tm.SubclassedSeries)
+
+ def test_equals(self):
+ # https://github.com/pandas-dev/pandas/pull/34402
+ # allow subclass in both directions
+ s1 = pd.Series([1, 2, 3])
+ s2 = tm.SubclassedSeries([1, 2, 3])
+ assert s1.equals(s2)
+ assert s2.equals(s1)
+
+
+class SubclassedSeries(pd.Series):
+ @property
+ def _constructor(self):
+ def _new(*args, **kwargs):
+ # some constructor logic that accesses the Series' name
+ if self.name == "test":
+ return pd.Series(*args, **kwargs)
+ return SubclassedSeries(*args, **kwargs)
+
+ return _new
+
+
+def test_constructor_from_dict():
+ # https://github.com/pandas-dev/pandas/issues/52445
+ result = SubclassedSeries({"a": 1, "b": 2, "c": 3})
+ assert isinstance(result, SubclassedSeries)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_ufunc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_ufunc.py
new file mode 100644
index 0000000000000000000000000000000000000000..9eaf1632528d87be4d79b69631151842e9050cdc
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_ufunc.py
@@ -0,0 +1,475 @@
+from collections import deque
+import re
+import string
+
+import numpy as np
+import pytest
+
+import pandas as pd
+import pandas._testing as tm
+from pandas.arrays import SparseArray
+
+
+@pytest.fixture(params=[np.add, np.logaddexp])
+def ufunc(request):
+ # dunder op
+ return request.param
+
+
+@pytest.fixture(
+ params=[pytest.param(True, marks=pytest.mark.fails_arm_wheels), False],
+ ids=["sparse", "dense"],
+)
+def sparse(request):
+ return request.param
+
+
+@pytest.fixture
+def arrays_for_binary_ufunc():
+ """
+ A pair of random, length-100 integer-dtype arrays, that are mostly 0.
+ """
+ a1 = np.random.default_rng(2).integers(0, 10, 100, dtype="int64")
+ a2 = np.random.default_rng(2).integers(0, 10, 100, dtype="int64")
+ a1[::3] = 0
+ a2[::4] = 0
+ return a1, a2
+
+
+@pytest.mark.parametrize("ufunc", [np.positive, np.floor, np.exp])
+def test_unary_ufunc(ufunc, sparse):
+ # Test that ufunc(pd.Series) == pd.Series(ufunc)
+ arr = np.random.default_rng(2).integers(0, 10, 10, dtype="int64")
+ arr[::2] = 0
+ if sparse:
+ arr = SparseArray(arr, dtype=pd.SparseDtype("int64", 0))
+
+ index = list(string.ascii_letters[:10])
+ name = "name"
+ series = pd.Series(arr, index=index, name=name)
+
+ result = ufunc(series)
+ expected = pd.Series(ufunc(arr), index=index, name=name)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("flip", [True, False], ids=["flipped", "straight"])
+def test_binary_ufunc_with_array(flip, sparse, ufunc, arrays_for_binary_ufunc):
+ # Test that ufunc(pd.Series(a), array) == pd.Series(ufunc(a, b))
+ a1, a2 = arrays_for_binary_ufunc
+ if sparse:
+ a1 = SparseArray(a1, dtype=pd.SparseDtype("int64", 0))
+ a2 = SparseArray(a2, dtype=pd.SparseDtype("int64", 0))
+
+ name = "name" # op(pd.Series, array) preserves the name.
+ series = pd.Series(a1, name=name)
+ other = a2
+
+ array_args = (a1, a2)
+ series_args = (series, other) # ufunc(series, array)
+
+ if flip:
+ array_args = reversed(array_args)
+ series_args = reversed(series_args) # ufunc(array, series)
+
+ expected = pd.Series(ufunc(*array_args), name=name)
+ result = ufunc(*series_args)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("flip", [True, False], ids=["flipped", "straight"])
+def test_binary_ufunc_with_index(flip, sparse, ufunc, arrays_for_binary_ufunc):
+ # Test that
+ # * func(pd.Series(a), pd.Series(b)) == pd.Series(ufunc(a, b))
+ # * ufunc(Index, pd.Series) dispatches to pd.Series (returns a pd.Series)
+ a1, a2 = arrays_for_binary_ufunc
+ if sparse:
+ a1 = SparseArray(a1, dtype=pd.SparseDtype("int64", 0))
+ a2 = SparseArray(a2, dtype=pd.SparseDtype("int64", 0))
+
+ name = "name" # op(pd.Series, array) preserves the name.
+ series = pd.Series(a1, name=name)
+
+ other = pd.Index(a2, name=name).astype("int64")
+
+ array_args = (a1, a2)
+ series_args = (series, other) # ufunc(series, array)
+
+ if flip:
+ array_args = reversed(array_args)
+ series_args = reversed(series_args) # ufunc(array, series)
+
+ expected = pd.Series(ufunc(*array_args), name=name)
+ result = ufunc(*series_args)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("shuffle", [True, False], ids=["unaligned", "aligned"])
+@pytest.mark.parametrize("flip", [True, False], ids=["flipped", "straight"])
+def test_binary_ufunc_with_series(
+ flip, shuffle, sparse, ufunc, arrays_for_binary_ufunc
+):
+ # Test that
+ # * func(pd.Series(a), pd.Series(b)) == pd.Series(ufunc(a, b))
+ # with alignment between the indices
+ a1, a2 = arrays_for_binary_ufunc
+ if sparse:
+ a1 = SparseArray(a1, dtype=pd.SparseDtype("int64", 0))
+ a2 = SparseArray(a2, dtype=pd.SparseDtype("int64", 0))
+
+ name = "name" # op(pd.Series, array) preserves the name.
+ series = pd.Series(a1, name=name)
+ other = pd.Series(a2, name=name)
+
+ idx = np.random.default_rng(2).permutation(len(a1))
+
+ if shuffle:
+ other = other.take(idx)
+ if flip:
+ index = other.align(series)[0].index
+ else:
+ index = series.align(other)[0].index
+ else:
+ index = series.index
+
+ array_args = (a1, a2)
+ series_args = (series, other) # ufunc(series, array)
+
+ if flip:
+ array_args = tuple(reversed(array_args))
+ series_args = tuple(reversed(series_args)) # ufunc(array, series)
+
+ expected = pd.Series(ufunc(*array_args), index=index, name=name)
+ result = ufunc(*series_args)
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("flip", [True, False])
+def test_binary_ufunc_scalar(ufunc, sparse, flip, arrays_for_binary_ufunc):
+ # Test that
+ # * ufunc(pd.Series, scalar) == pd.Series(ufunc(array, scalar))
+ # * ufunc(pd.Series, scalar) == ufunc(scalar, pd.Series)
+ arr, _ = arrays_for_binary_ufunc
+ if sparse:
+ arr = SparseArray(arr)
+ other = 2
+ series = pd.Series(arr, name="name")
+
+ series_args = (series, other)
+ array_args = (arr, other)
+
+ if flip:
+ series_args = tuple(reversed(series_args))
+ array_args = tuple(reversed(array_args))
+
+ expected = pd.Series(ufunc(*array_args), name="name")
+ result = ufunc(*series_args)
+
+ tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.parametrize("ufunc", [np.divmod]) # TODO: np.modf, np.frexp
+@pytest.mark.parametrize("shuffle", [True, False])
+@pytest.mark.filterwarnings("ignore:divide by zero:RuntimeWarning")
+def test_multiple_output_binary_ufuncs(ufunc, sparse, shuffle, arrays_for_binary_ufunc):
+ # Test that
+ # the same conditions from binary_ufunc_scalar apply to
+ # ufuncs with multiple outputs.
+
+ a1, a2 = arrays_for_binary_ufunc
+ # work around https://github.com/pandas-dev/pandas/issues/26987
+ a1[a1 == 0] = 1
+ a2[a2 == 0] = 1
+
+ if sparse:
+ a1 = SparseArray(a1, dtype=pd.SparseDtype("int64", 0))
+ a2 = SparseArray(a2, dtype=pd.SparseDtype("int64", 0))
+
+ s1 = pd.Series(a1)
+ s2 = pd.Series(a2)
+
+ if shuffle:
+ # ensure we align before applying the ufunc
+ s2 = s2.sample(frac=1)
+
+ expected = ufunc(a1, a2)
+ assert isinstance(expected, tuple)
+
+ result = ufunc(s1, s2)
+ assert isinstance(result, tuple)
+ tm.assert_series_equal(result[0], pd.Series(expected[0]))
+ tm.assert_series_equal(result[1], pd.Series(expected[1]))
+
+
+def test_multiple_output_ufunc(sparse, arrays_for_binary_ufunc):
+ # Test that the same conditions from unary input apply to multi-output
+ # ufuncs
+ arr, _ = arrays_for_binary_ufunc
+
+ if sparse:
+ arr = SparseArray(arr)
+
+ series = pd.Series(arr, name="name")
+ result = np.modf(series)
+ expected = np.modf(arr)
+
+ assert isinstance(result, tuple)
+ assert isinstance(expected, tuple)
+
+ tm.assert_series_equal(result[0], pd.Series(expected[0], name="name"))
+ tm.assert_series_equal(result[1], pd.Series(expected[1], name="name"))
+
+
+def test_binary_ufunc_drops_series_name(ufunc, sparse, arrays_for_binary_ufunc):
+ # Drop the names when they differ.
+ a1, a2 = arrays_for_binary_ufunc
+ s1 = pd.Series(a1, name="a")
+ s2 = pd.Series(a2, name="b")
+
+ result = ufunc(s1, s2)
+ assert result.name is None
+
+
+def test_object_series_ok():
+ class Dummy:
+ def __init__(self, value) -> None:
+ self.value = value
+
+ def __add__(self, other):
+ return self.value + other.value
+
+ arr = np.array([Dummy(0), Dummy(1)])
+ ser = pd.Series(arr)
+ tm.assert_series_equal(np.add(ser, ser), pd.Series(np.add(ser, arr)))
+ tm.assert_series_equal(np.add(ser, Dummy(1)), pd.Series(np.add(ser, Dummy(1))))
+
+
+@pytest.fixture(
+ params=[
+ pd.array([1, 3, 2], dtype=np.int64),
+ pd.array([1, 3, 2], dtype="Int64"),
+ pd.array([1, 3, 2], dtype="Float32"),
+ pd.array([1, 10, 2], dtype="Sparse[int]"),
+ pd.to_datetime(["2000", "2010", "2001"]),
+ pd.to_datetime(["2000", "2010", "2001"]).tz_localize("CET"),
+ pd.to_datetime(["2000", "2010", "2001"]).to_period(freq="D"),
+ pd.to_timedelta(["1 Day", "3 Days", "2 Days"]),
+ pd.IntervalIndex([pd.Interval(0, 1), pd.Interval(2, 3), pd.Interval(1, 2)]),
+ ],
+ ids=lambda x: str(x.dtype),
+)
+def values_for_np_reduce(request):
+ # min/max tests assume that these are monotonic increasing
+ return request.param
+
+
+class TestNumpyReductions:
+ # TODO: cases with NAs, axis kwarg for DataFrame
+
+ def test_multiply(self, values_for_np_reduce, box_with_array, request):
+ box = box_with_array
+ values = values_for_np_reduce
+
+ with tm.assert_produces_warning(None):
+ obj = box(values)
+
+ if isinstance(values, pd.core.arrays.SparseArray):
+ mark = pytest.mark.xfail(reason="SparseArray has no 'prod'")
+ request.applymarker(mark)
+
+ if values.dtype.kind in "iuf":
+ result = np.multiply.reduce(obj)
+ if box is pd.DataFrame:
+ expected = obj.prod(numeric_only=False)
+ tm.assert_series_equal(result, expected)
+ elif box is pd.Index:
+ # Index has no 'prod'
+ expected = obj._values.prod()
+ assert result == expected
+ else:
+ expected = obj.prod()
+ assert result == expected
+ else:
+ msg = "|".join(
+ [
+ "does not support operation",
+ "unsupported operand type",
+ "ufunc 'multiply' cannot use operands",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ np.multiply.reduce(obj)
+
+ def test_add(self, values_for_np_reduce, box_with_array):
+ box = box_with_array
+ values = values_for_np_reduce
+
+ with tm.assert_produces_warning(None):
+ obj = box(values)
+
+ if values.dtype.kind in "miuf":
+ result = np.add.reduce(obj)
+ if box is pd.DataFrame:
+ expected = obj.sum(numeric_only=False)
+ tm.assert_series_equal(result, expected)
+ elif box is pd.Index:
+ # Index has no 'sum'
+ expected = obj._values.sum()
+ assert result == expected
+ else:
+ expected = obj.sum()
+ assert result == expected
+ else:
+ msg = "|".join(
+ [
+ "does not support operation",
+ "unsupported operand type",
+ "ufunc 'add' cannot use operands",
+ ]
+ )
+ with pytest.raises(TypeError, match=msg):
+ np.add.reduce(obj)
+
+ def test_max(self, values_for_np_reduce, box_with_array, using_python_scalars):
+ box = box_with_array
+ values = values_for_np_reduce
+
+ same_type = True
+ if box is pd.Index and values.dtype.kind in "if":
+ # ATM Index casts to object, so we get python ints/floats
+ same_type = False
+
+ with tm.assert_produces_warning(None):
+ obj = box(values)
+
+ result = np.maximum.reduce(obj)
+ if box is pd.DataFrame:
+ # TODO: cases with axis kwarg
+ expected = obj.max(numeric_only=False)
+ tm.assert_series_equal(result, expected)
+ else:
+ expected = values[1]
+ if using_python_scalars and values.dtype.kind in "if":
+ expected = expected.item()
+ assert result == expected
+ if same_type:
+ # check we have e.g. Timestamp instead of dt64
+ assert type(result) == type(expected)
+
+ def test_min(self, values_for_np_reduce, box_with_array, using_python_scalars):
+ box = box_with_array
+ values = values_for_np_reduce
+
+ same_type = True
+ if box is pd.Index and values.dtype.kind in "if":
+ # ATM Index casts to object, so we get python ints/floats
+ same_type = False
+
+ with tm.assert_produces_warning(None):
+ obj = box(values)
+
+ result = np.minimum.reduce(obj)
+ if box is pd.DataFrame:
+ expected = obj.min(numeric_only=False)
+ tm.assert_series_equal(result, expected)
+ else:
+ expected = values[0]
+ if using_python_scalars and values.dtype.kind in ["i", "f"]:
+ expected = expected.item()
+ assert result == expected
+ if same_type:
+ # check we have e.g. Timestamp instead of dt64
+ assert type(result) == type(expected)
+
+
+@pytest.mark.parametrize("type_", [list, deque, tuple])
+def test_binary_ufunc_other_types(type_):
+ a = pd.Series([1, 2, 3], name="name")
+ b = type_([3, 4, 5])
+
+ result = np.add(a, b)
+ expected = pd.Series(np.add(a.to_numpy(), b), name="name")
+ tm.assert_series_equal(result, expected)
+
+
+def test_object_dtype_ok():
+ class Thing:
+ def __init__(self, value) -> None:
+ self.value = value
+
+ def __add__(self, other):
+ other = getattr(other, "value", other)
+ return type(self)(self.value + other)
+
+ def __eq__(self, other) -> bool:
+ return type(other) is Thing and self.value == other.value
+
+ def __repr__(self) -> str:
+ return f"Thing({self.value})"
+
+ s = pd.Series([Thing(1), Thing(2)])
+ result = np.add(s, Thing(1))
+ expected = pd.Series([Thing(2), Thing(3)])
+ tm.assert_series_equal(result, expected)
+
+
+def test_outer():
+ # https://github.com/pandas-dev/pandas/issues/27186
+ ser = pd.Series([1, 2, 3])
+ obj = np.array([1, 2, 3])
+
+ with pytest.raises(NotImplementedError, match="^$"):
+ np.subtract.outer(ser, obj)
+
+
+def test_np_matmul():
+ # GH26650
+ df1 = pd.DataFrame(data=[[-1, 1, 10]])
+ df2 = pd.DataFrame(data=[-1, 1, 10])
+ expected = pd.DataFrame(data=[102])
+
+ result = np.matmul(df1, df2)
+ tm.assert_frame_equal(expected, result)
+
+
+@pytest.mark.parametrize("box", [pd.Index, pd.Series])
+def test_np_matmul_1D(box, using_python_scalars):
+ result = np.matmul(box([1, 2]), box([2, 3]))
+ assert result == 8
+ if using_python_scalars:
+ assert type(result) == int, type(result)
+ else:
+ assert type(result) == np.int64, type(result)
+
+
+def test_array_ufuncs_for_many_arguments():
+ # GH39853
+ def add3(x, y, z):
+ return x + y + z
+
+ ufunc = np.frompyfunc(add3, 3, 1)
+ ser = pd.Series([1, 2])
+
+ result = ufunc(ser, ser, 1)
+ expected = pd.Series([3, 5], dtype=object)
+ tm.assert_series_equal(result, expected)
+
+ df = pd.DataFrame([[1, 2]])
+
+ msg = (
+ "Cannot apply ufunc "
+ "to mixed DataFrame and Series inputs."
+ )
+ with pytest.raises(NotImplementedError, match=re.escape(msg)):
+ ufunc(ser, ser, df)
+
+
+def test_np_trunc():
+ # This used to test np.fix, which is not a ufunc but is composed of
+ # several ufunc calls under the hood with `out` and `where` keywords. But numpy
+ # is deprecating that (or at least discussing deprecating) in favor of np.trunc,
+ # which _is_ a ufunc without the out keyword usage.
+ ser = pd.Series([-1.5, -0.5, 0.5, 1.5])
+ result = np.trunc(ser)
+ expected = pd.Series([-1.0, -0.0, 0.0, 1.0])
+ tm.assert_series_equal(result, expected)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_unary.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_unary.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f153788e413c5e9198dc35867bc628823555dbf
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_unary.py
@@ -0,0 +1,50 @@
+import pytest
+
+from pandas import Series
+import pandas._testing as tm
+
+
+class TestSeriesUnaryOps:
+ # __neg__, __pos__, __invert__
+
+ def test_neg(self):
+ ser = Series(range(5), dtype="float64", name="series")
+ tm.assert_series_equal(-ser, -1 * ser)
+
+ def test_invert(self):
+ ser = Series(range(5), dtype="float64", name="series")
+ tm.assert_series_equal(-(ser < 0), ~(ser < 0))
+
+ @pytest.mark.parametrize(
+ "source, neg_target, abs_target",
+ [
+ ([1, 2, 3], [-1, -2, -3], [1, 2, 3]),
+ ([1, 2, None], [-1, -2, None], [1, 2, None]),
+ ],
+ )
+ def test_all_numeric_unary_operators(
+ self, any_numeric_ea_dtype, source, neg_target, abs_target
+ ):
+ # GH38794
+ dtype = any_numeric_ea_dtype
+ ser = Series(source, dtype=dtype)
+ neg_result, pos_result, abs_result = -ser, +ser, abs(ser)
+ if dtype.startswith("U"):
+ neg_target = -Series(source, dtype=dtype)
+ else:
+ neg_target = Series(neg_target, dtype=dtype)
+
+ abs_target = Series(abs_target, dtype=dtype)
+
+ tm.assert_series_equal(neg_result, neg_target)
+ tm.assert_series_equal(pos_result, ser)
+ tm.assert_series_equal(abs_result, abs_target)
+
+ @pytest.mark.parametrize("op", ["__neg__", "__abs__"])
+ def test_unary_float_op_mask(self, float_ea_dtype, op):
+ dtype = float_ea_dtype
+ ser = Series([1.1, 2.2, 3.3], dtype=dtype)
+ result = getattr(ser, op)()
+ target = result.copy(deep=True)
+ ser[0] = None
+ tm.assert_series_equal(result, target)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_validate.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_validate.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c867f7582b7d3250bf5e009ffbf7545da404712
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/series/test_validate.py
@@ -0,0 +1,26 @@
+import pytest
+
+
+@pytest.mark.parametrize(
+ "func",
+ [
+ "reset_index",
+ "_set_name",
+ "sort_values",
+ "sort_index",
+ "rename",
+ "dropna",
+ "drop_duplicates",
+ ],
+)
+@pytest.mark.parametrize("inplace", [1, "True", [1, 2, 3], 5.0])
+def test_validate_bool_args(string_series, func, inplace):
+ """Tests for error handling related to data types of method arguments."""
+ msg = 'For argument "inplace" expected type bool'
+ kwargs = {"inplace": inplace}
+
+ if func == "_set_name":
+ kwargs["name"] = "hello"
+
+ with pytest.raises(ValueError, match=msg):
+ getattr(string_series, func)(**kwargs)
diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/test_aggregation.py b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/test_aggregation.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a01805cc2365c3f5024064465341d1fe664eeed
--- /dev/null
+++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas/tests/test_aggregation.py
@@ -0,0 +1,93 @@
+import numpy as np
+import pytest
+
+from pandas.core.apply import (
+ _make_unique_kwarg_list,
+ maybe_mangle_lambdas,
+)
+
+
+def test_maybe_mangle_lambdas_passthrough():
+ assert maybe_mangle_lambdas("mean") == "mean"
+ assert maybe_mangle_lambdas(lambda x: x).__name__ == ""
+ # don't mangle single lambda.
+ assert maybe_mangle_lambdas([lambda x: x])[0].__name__ == ""
+
+
+def test_maybe_mangle_lambdas_listlike():
+ aggfuncs = [lambda x: 1, lambda x: 2]
+ result = maybe_mangle_lambdas(aggfuncs)
+ assert result[0].__name__ == ""
+ assert result[1].__name__ == ""
+ assert aggfuncs[0](None) == result[0](None)
+ assert aggfuncs[1](None) == result[1](None)
+
+
+def test_maybe_mangle_lambdas():
+ func = {"A": [lambda x: 0, lambda x: 1]}
+ result = maybe_mangle_lambdas(func)
+ assert result["A"][0].__name__ == ""
+ assert result["A"][1].__name__ == ""
+
+
+def test_maybe_mangle_lambdas_args():
+ func = {"A": [lambda x, a, b=1: (0, a, b), lambda x: 1]}
+ result = maybe_mangle_lambdas(func)
+ assert result["A"][0].__name__ == ""
+ assert result["A"][1].__name__ == ""
+
+ assert func["A"][0](0, 1) == (0, 1, 1)
+ assert func["A"][0](0, 1, 2) == (0, 1, 2)
+ assert func["A"][0](0, 2, b=3) == (0, 2, 3)
+
+
+def test_maybe_mangle_lambdas_named():
+ func = {"C": np.mean, "D": {"foo": np.mean, "bar": np.mean}}
+ result = maybe_mangle_lambdas(func)
+ assert result == func
+
+
+@pytest.mark.parametrize(
+ "order, expected_reorder",
+ [
+ (
+ [
+ ("height", "